From 34437b4f9c9c51b0a6f93788bdb9a105b8e46b66 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 19 Feb 2016 17:58:52 +0100 Subject: sd-lldp: rework sd-lldp API This reworks the sd-lldp substantially, simplifying things on one hand, and extending the logic a bit on the other. Specifically: - Besides the sd_lldp object only one other object is maintained now, sd_lldp_neighbor. It's used both as storage for literal LLDP packets, and for maintainging info about peers in the database. Separation between packet, TLV and chassis data is not maintained anymore. This should be a major simplification. - The sd-lldp API has been extended so that a couple of per-neighbor fields may be queried directly, without iterating through the object. Other fields that may appear multiple times, OTOH have to be iterated through. - The maximum number of entries in the neighbor database is now configurable during runtime. - The generation of callbacks from sd_lldp objects is more restricted: callbacks are only invoked when actual data changed. - The TTL information is now hooked with a timer event, so that removals from the neighbor database due to TTLs now result in a callback event. - Querying LLDP neighbor database will now return a strictly ordered array, to guarantee stability. - A "capabilities" mask may now be configured, that selects what type of LLDP neighbor data is collected. This may be used to restrict collection of LLDP info about routers instead of all neighbors. This is now exposed via networkd's LLDP= setting. - sd-lldp's API to serialize the collected data to text files has been removed. Instead, there's now an API to extract the raw binary data from LLDP neighbor objects, as well as one to convert this raw binary data back to an LLDP neighbor object. networkd will save this raw binary data to /run now, and the client side can simply parse the information. - support for parsing the more exotic TLVs has been removed, since we are not using that. Instead there are now APIs to extract the raw data from TLVs. Given how easy it is to parse the TLVs clients should do so now directly instead of relying on our APIs for that. - A lot of the APIs that parse out LLDP strings have been simplified so that they actually return strings, instead of char arrays with a length. To deal with possibly dangerous characters the strings are escaped if needed. - APIs to extract and format the chassis and port IDs as strings has been added. - lldp.h has been simplified a lot. The enums are anonymous now, since they were never used as enums, but simply as constants. Most definitions we don't actually use ourselves have eben removed. --- src/network/networkd-network.h | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'src/network/networkd-network.h') diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 03c3f206c3..9dcebfbf7b 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -58,6 +58,14 @@ typedef enum DHCPUseDomains { _DHCP_USE_DOMAINS_INVALID = -1, } DHCPUseDomains; +typedef enum LLDPMode { + LLDP_MODE_NO = 0, + LLDP_MODE_YES = 1, + LLDP_MODE_ROUTERS_ONLY = 2, + _LLDP_MODE_MAX, + _LLDP_MODE_INVALID = -1, +} LLDPMode; + struct Network { Manager *manager; @@ -137,7 +145,7 @@ struct Network { struct ether_addr *mac; unsigned mtu; - bool lldp; + LLDPMode lldp_mode; LIST_HEAD(Address, static_addresses); LIST_HEAD(Route, static_routes); @@ -181,6 +189,7 @@ int config_parse_dhcp_server_dns(const char *unit, const char *filename, unsigne int config_parse_dhcp_server_ntp(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); int config_parse_dnssec_negative_trust_anchors(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); int config_parse_dhcp_use_domains(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); +int config_parse_lldp_mode(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); /* Legacy IPv4LL support */ int config_parse_ipv4ll(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); @@ -197,3 +206,6 @@ IPv6PrivacyExtensions ipv6_privacy_extensions_from_string(const char *s) _pure_; const char* dhcp_use_domains_to_string(DHCPUseDomains p) _const_; DHCPUseDomains dhcp_use_domains_from_string(const char *s) _pure_; + +const char* lldp_mode_to_string(LLDPMode m) _const_; +LLDPMode lldp_mode_from_string(const char *s) _pure_; -- cgit v1.2.3-54-g00ecf From 8e1ad1eaf74cd8eadf6a9b14e5d6edb24ab2da91 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 21 Feb 2016 14:14:08 +0100 Subject: networkd: add basic LLDP transmission support Let's add some minimalistic LLDP sender support. The idea is that this is either on or off, and all fields determined automatically rather than configured explicitly. --- Makefile.am | 4 +- src/basic/hostname-util.c | 28 +++ src/basic/hostname-util.h | 1 + src/network/networkd-link.c | 29 ++- src/network/networkd-link.h | 5 + src/network/networkd-lldp-tx.c | 347 +++++++++++++++++++++++++++++++ src/network/networkd-lldp-tx.h | 25 +++ src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.h | 3 +- 9 files changed, 439 insertions(+), 4 deletions(-) create mode 100644 src/network/networkd-lldp-tx.c create mode 100644 src/network/networkd-lldp-tx.h (limited to 'src/network/networkd-network.h') diff --git a/Makefile.am b/Makefile.am index 70fcedf0da..627a076cd9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5341,7 +5341,9 @@ libnetworkd_core_la_SOURCES = \ src/network/networkd-address-pool.h \ src/network/networkd-address-pool.c \ src/network/networkd-util.h \ - src/network/networkd-util.c + src/network/networkd-util.c \ + src/network/networkd-lldp-tx.h \ + src/network/networkd-lldp-tx.c nodist_libnetworkd_core_la_SOURCES = \ src/network/networkd-network-gperf.c \ diff --git a/src/basic/hostname-util.c b/src/basic/hostname-util.c index 57031b645c..f900c509a3 100644 --- a/src/basic/hostname-util.c +++ b/src/basic/hostname-util.c @@ -48,6 +48,9 @@ bool hostname_is_set(void) { char* gethostname_malloc(void) { struct utsname u; + /* This call tries to return something useful, either the actual hostname or it makes something up. The only + * reason it might mail is OOM. It might even return "localhost" if that's set. */ + assert_se(uname(&u) >= 0); if (isempty(u.nodename) || streq(u.nodename, "(none)")) @@ -56,6 +59,31 @@ char* gethostname_malloc(void) { return strdup(u.nodename); } +int gethostname_strict(char **ret) { + struct utsname u; + char *k; + + /* This call will rather fail than make up a name. It will not return "localhost" either. */ + + assert_se(uname(&u) >= 0); + + if (isempty(u.nodename)) + return -ENXIO; + + if (streq(u.nodename, "(none)")) + return -ENXIO; + + if (is_localhost(u.nodename)) + return -ENXIO; + + k = strdup(u.nodename); + if (!k) + return -ENOMEM; + + *ret = k; + return 0; +} + static bool hostname_valid_char(char c) { return (c >= 'a' && c <= 'z') || diff --git a/src/basic/hostname-util.h b/src/basic/hostname-util.h index d062eddea1..7af4e6c7ec 100644 --- a/src/basic/hostname-util.h +++ b/src/basic/hostname-util.h @@ -26,6 +26,7 @@ bool hostname_is_set(void); char* gethostname_malloc(void); +int gethostname_strict(char **ret); bool hostname_is_valid(const char *s, bool allow_trailing_dot) _pure_; char* hostname_cleanup(char *s); diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 2bc6e3c842..86fa4f07f2 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -30,6 +30,7 @@ #include "netlink-util.h" #include "network-internal.h" #include "networkd-link.h" +#include "networkd-lldp-tx.h" #include "networkd-netdev.h" #include "set.h" #include "socket-util.h" @@ -99,7 +100,7 @@ static bool link_ipv6ll_enabled(Link *link) { return link->network->link_local & ADDRESS_FAMILY_IPV6; } -static bool link_lldp_enabled(Link *link) { +static bool link_lldp_rx_enabled(Link *link) { assert(link); if (link->flags & IFF_LOOPBACK) @@ -117,6 +118,21 @@ static bool link_lldp_enabled(Link *link) { return link->network->lldp_mode != LLDP_MODE_NO; } +static bool link_lldp_tx_enabled(Link *link) { + assert(link); + + if (link->flags & IFF_LOOPBACK) + return false; + + if (link->iftype != ARPHRD_ETHER) + return false; + + if (!link->network) + return false; + + return link->network->lldp_emit; +} + static bool link_ipv4_forward_enabled(Link *link) { assert(link); @@ -419,6 +435,8 @@ static void link_free(Link *link) { sd_dhcp_client_unref(link->dhcp_client); sd_dhcp_lease_unref(link->dhcp_lease); + link_lldp_tx_stop(link); + free(link->lease_file); sd_lldp_unref(link->lldp); @@ -546,6 +564,7 @@ static int link_stop_clients(Link *link) { r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Discovery: %m"); } + link_lldp_tx_stop(link); return r; } @@ -1383,6 +1402,12 @@ static int link_acquire_conf(Link *link) { return log_link_warning_errno(link, r, "Could not acquire DHCPv4 lease: %m"); } + if (link_lldp_tx_enabled(link)) { + r = link_lldp_tx_start(link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to start LLDP transmission: %m"); + } + return 0; } @@ -2191,7 +2216,7 @@ static int link_configure(Link *link) { return r; } - if (link_lldp_enabled(link)) { + if (link_lldp_rx_enabled(link)) { r = sd_lldp_new(&link->lldp, link->ifindex); if (r < 0) return r; diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index d9f637cb6e..f2a64ca9b5 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -112,9 +112,14 @@ struct Link { sd_dhcp6_client *dhcp6_client; bool rtnl_extended_attrs; + /* This is about LLDP reception */ sd_lldp *lldp; char *lldp_file; + /* This is about LLDP transmission */ + unsigned lldp_tx_fast; /* The LLDP txFast counter (See 802.1ab-2009, section 9.2.5.18) */ + sd_event_source *lldp_tx_event_source; + Hashmap *bound_by_links; Hashmap *bound_to_links; }; diff --git a/src/network/networkd-lldp-tx.c b/src/network/networkd-lldp-tx.c new file mode 100644 index 0000000000..ae8367a60e --- /dev/null +++ b/src/network/networkd-lldp-tx.c @@ -0,0 +1,347 @@ +/*** + This file is part of systemd. + + Copyright 2016 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 +#include + +#include "fd-util.h" +#include "fileio.h" +#include "hostname-util.h" +#include "lldp.h" +#include "networkd-lldp-tx.h" +#include "random-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "unaligned.h" + +/* The LLDP spec calls this "txFastInit", see 9.2.5.19 */ +#define LLDP_TX_FAST_INIT 4U + +/* The LLDP spec calls this "msgTxHold", see 9.2.5.6 */ +#define LLDP_TX_HOLD 4U + +/* The jitter range to add, see 9.2.2. */ +#define LLDP_JITTER_USEC (400U * USEC_PER_MSEC) + +/* The LLDP spec calls this msgTxInterval, but we subtract half the jitter off it. */ +#define LLDP_TX_INTERVAL_USEC (30U * USEC_PER_SEC - LLDP_JITTER_USEC / 2) + +/* The LLDP spec calls this msgFastTx, but we subtract half the jitter off it. */ +#define LLDP_FAST_TX_USEC (1U * USEC_PER_SEC - LLDP_JITTER_USEC / 2) + +static int lldp_write_tlv_header(uint8_t **p, uint8_t id, size_t sz) { + assert(p); + + if (id > 127) + return -EBADMSG; + if (sz > 511) + return -ENOBUFS; + + (*p)[0] = (id << 1) | !!(sz & 256); + (*p)[1] = sz & 255; + + *p = *p + 2; + return 0; +} + +static int lldp_make_packet( + const struct ether_addr *hwaddr, + const char *machine_id, + const char *ifname, + uint16_t ttl, + const char *port_description, + const char *hostname, + const char *pretty_hostname, + uint16_t system_capabilities, + uint16_t enabled_capabilities, + void **ret, size_t *sz) { + + size_t machine_id_length, ifname_length, port_description_length = 0, hostname_length = 0, pretty_hostname_length = 0; + _cleanup_free_ void *packet = NULL; + struct ether_header *h; + uint8_t *p; + size_t l; + int r; + + assert(hwaddr); + assert(machine_id); + assert(ifname); + assert(ret); + assert(sz); + + machine_id_length = strlen(machine_id); + ifname_length = strlen(ifname); + + if (port_description) + port_description_length = strlen(port_description); + + if (hostname) + hostname_length = strlen(hostname); + + if (pretty_hostname) + pretty_hostname_length = strlen(pretty_hostname); + + l = sizeof(struct ether_header) + + /* Chassis ID */ + 2 + 1 + machine_id_length + + /* Port ID */ + 2 + 1 + ifname_length + + /* TTL */ + 2 + 2 + + /* System Capabilities */ + 2 + 4 + + /* End */ + 2; + + /* Port Description */ + if (port_description) + l += 2 + port_description_length; + + /* System Name */ + if (hostname) + l += 2 + hostname_length; + + /* System Description */ + if (pretty_hostname) + l += 2 + pretty_hostname_length; + + packet = malloc(l); + if (!packet) + return -ENOMEM; + + h = (struct ether_header*) packet; + h->ether_type = htobe16(ETHERTYPE_LLDP); + memcpy(h->ether_dhost, &(struct ether_addr) { LLDP_MULTICAST_ADDR }, ETH_ALEN); + memcpy(h->ether_shost, hwaddr, ETH_ALEN); + + p = (uint8_t*) packet + sizeof(struct ether_header); + + r = lldp_write_tlv_header(&p, LLDP_TYPE_CHASSIS_ID, 1 + machine_id_length); + if (r < 0) + return r; + *(p++) = LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED; + p = mempcpy(p, machine_id, machine_id_length); + + r = lldp_write_tlv_header(&p, LLDP_TYPE_PORT_ID, 1 + ifname_length); + if (r < 0) + return r; + *(p++) = LLDP_PORT_SUBTYPE_INTERFACE_NAME; + p = mempcpy(p, ifname, ifname_length); + + r = lldp_write_tlv_header(&p, LLDP_TYPE_TTL, 2); + if (r < 0) + return r; + unaligned_write_be16(p, ttl); + p += 2; + + if (port_description) { + r = lldp_write_tlv_header(&p, LLDP_TYPE_PORT_DESCRIPTION, port_description_length); + if (r < 0) + return r; + p = mempcpy(p, port_description, port_description_length); + } + + if (hostname) { + r = lldp_write_tlv_header(&p, LLDP_TYPE_SYSTEM_NAME, hostname_length); + if (r < 0) + return r; + p = mempcpy(p, hostname, hostname_length); + } + + if (pretty_hostname) { + r = lldp_write_tlv_header(&p, LLDP_TYPE_SYSTEM_DESCRIPTION, pretty_hostname_length); + if (r < 0) + return r; + p = mempcpy(p, pretty_hostname, pretty_hostname_length); + } + + r = lldp_write_tlv_header(&p, LLDP_TYPE_SYSTEM_CAPABILITIES, 4); + if (r < 0) + return r; + unaligned_write_be16(p, system_capabilities); + p += 2; + unaligned_write_be16(p, enabled_capabilities); + p += 2; + + r = lldp_write_tlv_header(&p, LLDP_TYPE_END, 0); + if (r < 0) + return r; + + assert(p == (uint8_t*) packet + l); + + *ret = packet; + *sz = l; + + packet = NULL; + return 0; +} + +static int lldp_send_packet(int ifindex, const void *packet, size_t packet_size) { + + union sockaddr_union sa = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htobe16(ETHERTYPE_LLDP), + .ll.sll_ifindex = ifindex, + .ll.sll_halen = ETH_ALEN, + .ll.sll_addr = LLDP_MULTICAST_ADDR, + }; + + _cleanup_close_ int fd = -1; + ssize_t l; + + assert(ifindex > 0); + assert(packet || packet_size <= 0); + + fd = socket(PF_PACKET, SOCK_RAW|SOCK_CLOEXEC, IPPROTO_RAW); + if (fd < 0) + return -errno; + + l = sendto(fd, packet, packet_size, MSG_NOSIGNAL, &sa.sa, sizeof(sa.ll)); + if (l < 0) + return -errno; + + if ((size_t) l != packet_size) + return -EIO; + + return 0; +} + +static int link_send_lldp(Link *link) { + char machine_id_string[SD_ID128_STRING_MAX]; + _cleanup_free_ char *hostname = NULL, *pretty_hostname = NULL; + _cleanup_free_ void *packet = NULL; + size_t packet_size = 0; + sd_id128_t machine_id; + uint16_t caps; + usec_t ttl; + int r; + + r = sd_id128_get_machine(&machine_id); + if (r < 0) + return r; + + (void) gethostname_strict(&hostname); + (void) parse_env_file("/etc/machine-info", NEWLINE, "PRETTY_HOSTNAME", &pretty_hostname, NULL); + + ttl = DIV_ROUND_UP(LLDP_TX_INTERVAL_USEC * LLDP_TX_HOLD + 1, USEC_PER_SEC); + if (ttl > (usec_t) UINT16_MAX) + ttl = (usec_t) UINT16_MAX; + + caps = (link->network && link->network->ip_forward != ADDRESS_FAMILY_NO) ? + LLDP_SYSTEM_CAPABILITIES_ROUTER : + LLDP_SYSTEM_CAPABILITIES_STATION; + + r = lldp_make_packet(&link->mac, + sd_id128_to_string(machine_id, machine_id_string), + link->ifname, + (uint16_t) ttl, + link->network ? link->network->description : NULL, + hostname, + pretty_hostname, + LLDP_SYSTEM_CAPABILITIES_STATION|LLDP_SYSTEM_CAPABILITIES_BRIDGE|LLDP_SYSTEM_CAPABILITIES_ROUTER, + caps, + &packet, &packet_size); + if (r < 0) + return r; + + return lldp_send_packet(link->ifindex, packet, packet_size); +} + +static int on_lldp_timer(sd_event_source *s, usec_t t, void *userdata) { + Link *link = userdata; + usec_t current, delay, next; + int r; + + assert(s); + assert(userdata); + + log_link_debug(link, "Sending LLDP packet..."); + + r = link_send_lldp(link); + if (r < 0) + log_link_debug_errno(link, r, "Failed to send LLDP packet, ignoring: %m"); + + if (link->lldp_tx_fast > 0) + link->lldp_tx_fast--; + + assert_se(sd_event_now(sd_event_source_get_event(s), clock_boottime_or_monotonic(), ¤t) >= 0); + + delay = link->lldp_tx_fast > 0 ? LLDP_FAST_TX_USEC : LLDP_TX_INTERVAL_USEC; + next = usec_add(usec_add(current, delay), (usec_t) random_u64() % LLDP_JITTER_USEC); + + r = sd_event_source_set_time(s, next); + if (r < 0) + return log_link_error_errno(link, r, "Failed to restart LLDP timer: %m"); + + r = sd_event_source_set_enabled(s, SD_EVENT_ONESHOT); + if (r < 0) + return log_link_error_errno(link, r, "Failed to enable LLDP timer: %m"); + + return 0; +} + +int link_lldp_tx_start(Link *link) { + usec_t next; + int r; + + assert(link); + + /* Starts the LLDP transmission in "fast" mode. If it is already started, turns "fast" mode back on again. */ + + link->lldp_tx_fast = LLDP_TX_FAST_INIT; + + next = usec_add(usec_add(now(clock_boottime_or_monotonic()), LLDP_FAST_TX_USEC), + (usec_t) random_u64() % LLDP_JITTER_USEC); + + if (link->lldp_tx_event_source) { + usec_t old; + + /* Lower the timeout, maybe */ + r = sd_event_source_get_time(link->lldp_tx_event_source, &old); + if (r < 0) + return r; + + if (old <= next) + return 0; + + return sd_event_source_set_time(link->lldp_tx_event_source, next); + } else { + r = sd_event_add_time( + link->manager->event, + &link->lldp_tx_event_source, + clock_boottime_or_monotonic(), + next, + 0, + on_lldp_timer, + link); + if (r < 0) + return r; + + (void) sd_event_source_set_description(link->lldp_tx_event_source, "lldp-tx"); + } + + return 0; +} + +void link_lldp_tx_stop(Link *link) { + assert(link); + + link->lldp_tx_event_source = sd_event_source_unref(link->lldp_tx_event_source); +} diff --git a/src/network/networkd-lldp-tx.h b/src/network/networkd-lldp-tx.h new file mode 100644 index 0000000000..8c7f403005 --- /dev/null +++ b/src/network/networkd-lldp-tx.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 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 "networkd-link.h" + +int link_lldp_tx_start(Link *link); +void link_lldp_tx_stop(Link *link); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index d67da5d7b6..a5d1714293 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -41,6 +41,7 @@ Network.LinkLocalAddressing, config_parse_address_family_boolean, Network.IPv4LLRoute, config_parse_bool, 0, offsetof(Network, ipv4ll_route) Network.IPv6Token, config_parse_ipv6token, 0, offsetof(Network, ipv6_token) Network.LLDP, config_parse_lldp_mode, 0, offsetof(Network, lldp_mode) +Network.EmitLLDP, config_parse_bool, 0, offsetof(Network, lldp_emit) Network.Address, config_parse_address, 0, 0 Network.Gateway, config_parse_gateway, 0, 0 Network.Domains, config_parse_domains, 0, 0 diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 9dcebfbf7b..4a13e2b574 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -145,7 +145,8 @@ struct Network { struct ether_addr *mac; unsigned mtu; - LLDPMode lldp_mode; + LLDPMode lldp_mode; /* LLDP reception */ + bool lldp_emit; /* LLDP transmission */ LIST_HEAD(Address, static_addresses); LIST_HEAD(Route, static_routes); -- cgit v1.2.3-54-g00ecf From c83321e6d40b294e73e2881e4a98172c4244323b Mon Sep 17 00:00:00 2001 From: Vinay Kulkarni Date: Wed, 9 Mar 2016 21:58:44 -0800 Subject: DHCP DUID and IAID configurability --- Makefile-man.am | 7 ++ Makefile.am | 4 + man/networkd.conf.xml | 112 +++++++++++++++++++++++++ man/systemd.network.xml | 6 ++ src/libsystemd-network/dhcp-identifier.c | 2 +- src/libsystemd-network/dhcp-identifier.h | 41 ++++++++- src/libsystemd-network/dhcp6-protocol.h | 7 -- src/libsystemd-network/network-internal.c | 28 +++++++ src/libsystemd-network/network-internal.h | 4 + src/libsystemd-network/sd-dhcp-client.c | 48 ++++++++++- src/libsystemd-network/sd-dhcp6-client.c | 49 +++++------ src/network/networkd-conf.c | 133 ++++++++++++++++++++++++++++++ src/network/networkd-conf.h | 32 +++++++ src/network/networkd-dhcp4.c | 8 +- src/network/networkd-dhcp6.c | 10 +++ src/network/networkd-gperf.gperf | 18 ++++ src/network/networkd-link.c | 18 ++++ src/network/networkd-manager.c | 2 + src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.h | 2 + src/network/networkd.c | 5 ++ src/network/networkd.h | 5 ++ src/systemd/sd-dhcp-client.h | 4 + src/systemd/sd-dhcp6-client.h | 7 +- 24 files changed, 509 insertions(+), 44 deletions(-) create mode 100644 man/networkd.conf.xml create mode 100644 src/network/networkd-conf.c create mode 100644 src/network/networkd-conf.h create mode 100644 src/network/networkd-gperf.gperf (limited to 'src/network/networkd-network.h') diff --git a/Makefile-man.am b/Makefile-man.am index 3f03afc2ef..a7e348b1f1 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -1960,15 +1960,21 @@ endif if ENABLE_NETWORKD MANPAGES += \ man/networkctl.1 \ + man/networkd.conf.5 \ man/systemd-networkd-wait-online.service.8 \ man/systemd-networkd.service.8 \ man/systemd.netdev.5 \ man/systemd.network.5 MANPAGES_ALIAS += \ + man/networkd.conf.d.5 \ man/systemd-networkd-wait-online.8 \ man/systemd-networkd.8 +man/networkd.conf.d.5: man/networkd.conf.5 man/systemd-networkd-wait-online.8: man/systemd-networkd-wait-online.service.8 man/systemd-networkd.8: man/systemd-networkd.service.8 +man/networkd.conf.d.html: man/networkd.conf.html + $(html-alias) + man/systemd-networkd-wait-online.html: man/systemd-networkd-wait-online.service.html $(html-alias) @@ -2479,6 +2485,7 @@ EXTRA_DIST += \ man/machinectl.xml \ man/modules-load.d.xml \ man/networkctl.xml \ + man/networkd.conf.xml \ man/nss-myhostname.xml \ man/nss-mymachines.xml \ man/nss-resolve.xml \ diff --git a/Makefile.am b/Makefile.am index 04553d81b2..5f47a81762 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5306,6 +5306,8 @@ libnetworkd_core_la_CFLAGS = \ libnetworkd_core_la_SOURCES = \ src/libsystemd-network/network-internal.h \ src/network/networkd.h \ + src/network/networkd-conf.h \ + src/network/networkd-conf.c \ src/network/networkd-link.h \ src/network/networkd-link.c \ src/network/networkd-netdev.h \ @@ -5354,6 +5356,7 @@ libnetworkd_core_la_SOURCES = \ src/network/networkd-lldp-tx.c nodist_libnetworkd_core_la_SOURCES = \ + src/network/networkd-gperf.c \ src/network/networkd-network-gperf.c \ src/network/networkd-netdev-gperf.c @@ -5450,6 +5453,7 @@ BUSNAMES_TARGET_WANTS += \ endif gperf_gperf_sources += \ + src/network/networkd-gperf.gperf \ src/network/networkd-network-gperf.gperf \ src/network/networkd-netdev-gperf.gperf diff --git a/man/networkd.conf.xml b/man/networkd.conf.xml new file mode 100644 index 0000000000..5e2927ba54 --- /dev/null +++ b/man/networkd.conf.xml @@ -0,0 +1,112 @@ + + + + + + + + networkd.conf + systemd + + + + Developer + Vinay + Kulkarni + kulkarniv@vmware.com + + + + + + networkd.conf + 5 + + + + networkd.conf + networkd.conf.d + Global Network configuration files + + + + /etc/systemd/networkd.conf + /etc/systemd/networkd.conf.d/*.conf + /usr/lib/systemd/networkd.conf.d/*.conf + + + + Description + + These configuration files control global network parameters. + For e.g. DHCP Unique Identifier (DUID). + + + + + + + [DUID] Section Options + + This section configures the DUID value used by the DHCP protocol. The DUID value + specified here overrides the DUID that systemd-networkd generates using the machine-id + from the /etc/machine-id file. + + The configured DHCP DUID should conform to the specification in + RFC 3315, + RFC 6355. To configure IAID, see + systemd.network5 + . + + The following options are available in [DUID] section: + + + + + Type= + The type of DUID specified in this section. The following values are + supported: + raw : If Type=raw, then RawData= specifies + the entire DUID. For e.g: RawData=00:02:00:00:ab:11:f9:2a:c2:77:29:f9:5c:00 + specifies a 14 byte long DUID-EN ("00:02"), with enterprise number 43793 ("00:00:ab:11"), + and identifier value "f9:2a:c2:77:29:f9:5c:00". + + + + RawData= + Specifies the DUID bytes as a single newline-terminated, hexadecimal + string, with each byte separated by a ':'. + + + + + + + See Also + + systemd1, + systemd.network5, + machine-id1 + + + + diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 752a15a4e0..c6bbb95833 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -204,6 +204,12 @@ understood to the base of 1024. + + IAIDValue= + + Identity Association Identifier for the interface. This is a 32-bit value specified in host byte order. + + diff --git a/src/libsystemd-network/dhcp-identifier.c b/src/libsystemd-network/dhcp-identifier.c index 1d9ec7be82..1bef368852 100644 --- a/src/libsystemd-network/dhcp-identifier.c +++ b/src/libsystemd-network/dhcp-identifier.c @@ -43,7 +43,7 @@ int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len) { if (r < 0) return r; - unaligned_write_be16(&duid->type, DHCP6_DUID_EN); + unaligned_write_be16(&duid->type, DHCP_DUID_TYPE_EN); unaligned_write_be32(&duid->en.pen, SYSTEMD_PEN); *len = sizeof(duid->type) + sizeof(duid->en); diff --git a/src/libsystemd-network/dhcp-identifier.h b/src/libsystemd-network/dhcp-identifier.h index 93f06f5938..cb953cb416 100644 --- a/src/libsystemd-network/dhcp-identifier.h +++ b/src/libsystemd-network/dhcp-identifier.h @@ -25,13 +25,23 @@ #include "sparse-endian.h" #include "unaligned.h" +typedef enum DHCPDUIDType { + DHCP_DUID_TYPE_RAW = 0, + DHCP_DUID_TYPE_LLT = 1, + DHCP_DUID_TYPE_EN = 2, + DHCP_DUID_TYPE_LL = 3, + DHCP_DUID_TYPE_UUID = 4, + _DHCP_DUID_TYPE_MAX, + _DHCP_DUID_TYPE_INVALID = -1, +} DHCPDUIDType; + /* RFC 3315 section 9.1: * A DUID can be no more than 128 octets long (not including the type code). */ #define MAX_DUID_LEN 128 struct duid { - uint16_t type; + be16_t type; union { struct { /* DHCP6_DUID_LLT */ @@ -61,3 +71,32 @@ struct duid { int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len); int dhcp_identifier_set_iaid(int ifindex, uint8_t *mac, size_t mac_len, void *_id); + +static inline int dhcp_validate_duid_len(be16_t duid_type, size_t duid_len) { + struct duid d; + + assert_return(duid_len > 0 && duid_len <= MAX_DUID_LEN, -EINVAL); + + switch (be16toh(duid_type)) { + case DHCP_DUID_TYPE_LLT: + if (duid_len <= sizeof(d.llt)) + return -EINVAL; + break; + case DHCP_DUID_TYPE_EN: + if (duid_len != sizeof(d.en)) + return -EINVAL; + break; + case DHCP_DUID_TYPE_LL: + if (duid_len <= sizeof(d.ll)) + return -EINVAL; + break; + case DHCP_DUID_TYPE_UUID: + if (duid_len != sizeof(d.uuid)) + return -EINVAL; + break; + default: + /* accept unknown type in order to be forward compatible */ + break; + } + return 0; +} diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h index ee4bdfb07f..2487c470ab 100644 --- a/src/libsystemd-network/dhcp6-protocol.h +++ b/src/libsystemd-network/dhcp6-protocol.h @@ -62,13 +62,6 @@ enum { #define DHCP6_REB_TIMEOUT 10 * USEC_PER_SEC #define DHCP6_REB_MAX_RT 600 * USEC_PER_SEC -enum { - DHCP6_DUID_LLT = 1, - DHCP6_DUID_EN = 2, - DHCP6_DUID_LL = 3, - DHCP6_DUID_UUID = 4, -}; - enum DHCP6State { DHCP6_STATE_STOPPED = 0, DHCP6_STATE_INFORMATION_REQUEST = 1, diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c index cb7252bbeb..7c21f42591 100644 --- a/src/libsystemd-network/network-internal.c +++ b/src/libsystemd-network/network-internal.c @@ -335,6 +335,34 @@ int config_parse_hwaddr(const char *unit, return 0; } +int config_parse_iaid_value(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) { + uint32_t iaid_value; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((r = safe_atou32(rvalue, &iaid_value)) < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Unable to read IAID: %s", rvalue); + return r; + } + + *((be32_t *)data) = htobe32(iaid_value); + + return 0; +} + void serialize_in_addrs(FILE *f, const struct in_addr *addresses, size_t size) { unsigned i; diff --git a/src/libsystemd-network/network-internal.h b/src/libsystemd-network/network-internal.h index c8a531ab0f..d8b551e8ce 100644 --- a/src/libsystemd-network/network-internal.h +++ b/src/libsystemd-network/network-internal.h @@ -62,6 +62,10 @@ int config_parse_ifalias(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); +int config_parse_iaid_value(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); + int net_get_unique_predictable_data(struct udev_device *device, uint64_t *result); const char *net_get_name(struct udev_device *device); diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 1188b31500..b108e35386 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -82,7 +82,7 @@ struct sd_dhcp_client { } _packed_ ll; struct { /* 255: Node-specific (RFC 4361) */ - uint32_t iaid; + be32_t iaid; struct duid duid; } _packed_ ns; struct { @@ -298,6 +298,51 @@ int sd_dhcp_client_set_client_id(sd_dhcp_client *client, uint8_t type, return 0; } +int sd_dhcp_client_set_iaid_duid(sd_dhcp_client *client, be32_t iaid, + size_t duid_len, struct duid *duid) { + DHCP_CLIENT_DONT_DESTROY(client); + int r; + assert_return(client, -EINVAL); + zero(client->client_id); + + client->client_id.type = 255; + + /* If IAID is not configured, generate it. */ + if (iaid == 0) { + r = dhcp_identifier_set_iaid(client->index, client->mac_addr, + client->mac_addr_len, + &client->client_id.ns.iaid); + if (r < 0) + return r; + } else + client->client_id.ns.iaid = iaid; + + /* If DUID is not configured, generate DUID-EN. */ + if (duid_len == 0) { + r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, + &duid_len); + if (r < 0) + return r; + } else { + r = dhcp_validate_duid_len(client->client_id.type, + duid_len - sizeof(client->client_id.type)); + if (r < 0) + return r; + memcpy(&client->client_id.ns.duid, duid, duid_len); + } + + client->client_id_len = sizeof(client->client_id.type) + duid_len + + sizeof(client->client_id.ns.iaid); + + if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) { + log_dhcp_client(client, "Configured IAID+DUID, restarting."); + client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); + sd_dhcp_client_start(client); + } + + return 0; +} + int sd_dhcp_client_set_hostname(sd_dhcp_client *client, const char *hostname) { char *new_hostname = NULL; @@ -469,7 +514,6 @@ static int client_message_init(sd_dhcp_client *client, DHCPPacket **ret, if (client->arp_type == ARPHRD_ETHER) memcpy(&packet->dhcp.chaddr, &client->mac_addr, ETH_ALEN); - /* If no client identifier exists, construct an RFC 4361-compliant one */ if (client->client_id_len == 0) { size_t duid_len; diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index af4709d788..7cecba120c 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -180,41 +180,30 @@ static int client_ensure_duid(sd_dhcp6_client *client) { return dhcp_identifier_set_duid_en(&client->duid, &client->duid_len); } -int sd_dhcp6_client_set_duid( - sd_dhcp6_client *client, - uint16_t type, - uint8_t *duid, size_t duid_len) { +int sd_dhcp6_client_set_duid(sd_dhcp6_client *client, size_t duid_len, + struct duid *duid) { + int r; assert_return(client, -EINVAL); - assert_return(duid, -EINVAL); - assert_return(duid_len > 0 && duid_len <= MAX_DUID_LEN, -EINVAL); - assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); - switch (type) { - case DHCP6_DUID_LLT: - if (duid_len <= sizeof(client->duid.llt)) - return -EINVAL; - break; - case DHCP6_DUID_EN: - if (duid_len != sizeof(client->duid.en)) - return -EINVAL; - break; - case DHCP6_DUID_LL: - if (duid_len <= sizeof(client->duid.ll)) - return -EINVAL; - break; - case DHCP6_DUID_UUID: - if (duid_len != sizeof(client->duid.uuid)) - return -EINVAL; - break; - default: - /* accept unknown type in order to be forward compatible */ - break; + if (duid_len > 0) { + r = dhcp_validate_duid_len(duid->type, + duid_len - sizeof(duid->type)); + if (r < 0) + return r; + + memcpy(&client->duid, duid, duid_len); + client->duid_len = duid_len; } - client->duid.type = htobe16(type); - memcpy(&client->duid.raw.data, duid, duid_len); - client->duid_len = duid_len + sizeof(client->duid.type); + return 0; +} + +int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, be32_t iaid) { + assert_return(client, -EINVAL); + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); + + client->ia_na.id = iaid; return 0; } diff --git a/src/network/networkd-conf.c b/src/network/networkd-conf.c new file mode 100644 index 0000000000..4bc92b8171 --- /dev/null +++ b/src/network/networkd-conf.c @@ -0,0 +1,133 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 "conf-parser.h" +#include "def.h" +#include "dhcp-identifier.h" +#include "networkd-conf.h" +#include "string-table.h" + +int manager_parse_config_file(Manager *m) { + assert(m); + + return config_parse_many(PKGSYSCONFDIR "/networkd.conf", + CONF_PATHS_NULSTR("systemd/networkd.conf.d"), + "DUID\0", + config_item_perf_lookup, networkd_gperf_lookup, + false, m); +} + +static const char* const dhcp_duid_type_table[_DHCP_DUID_TYPE_MAX] = { + [DHCP_DUID_TYPE_RAW] = "raw", + [DHCP_DUID_TYPE_LLT] = "link-layer-time", + [DHCP_DUID_TYPE_EN] = "vendor", + [DHCP_DUID_TYPE_LL] = "link-layer", + [DHCP_DUID_TYPE_UUID] = "uuid" +}; +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(dhcp_duid_type, DHCPDUIDType); +DEFINE_CONFIG_PARSE_ENUM(config_parse_dhcp_duid_type, dhcp_duid_type, DHCPDUIDType, "Failed to parse DHCP DUID type"); + +int config_parse_dhcp_duid_raw( + 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) { + int r; + long byte; + char *cbyte, *pnext; + const char *pduid = (const char *)rvalue; + size_t count = 0, duid_len = 0; + Manager *m = userdata; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(m); + assert(m->dhcp_duid_type != _DHCP_DUID_TYPE_INVALID); + + switch (m->dhcp_duid_type) { + case DHCP_DUID_TYPE_LLT: + /* RawData contains DUID-LLT link-layer address (offset 6) */ + duid_len = 6; + break; + case DHCP_DUID_TYPE_EN: + /* RawData contains DUID-EN identifier (offset 4) */ + duid_len = 4; + break; + case DHCP_DUID_TYPE_LL: + /* RawData contains DUID-LL link-layer address (offset 2) */ + duid_len = 2; + break; + case DHCP_DUID_TYPE_UUID: + /* RawData specifies UUID (offset 0) - fall thru */ + case DHCP_DUID_TYPE_RAW: + /* First two bytes of RawData is DUID Type - fall thru */ + default: + break; + } + + if (m->dhcp_duid_type != DHCP_DUID_TYPE_RAW) + m->dhcp_duid.type = htobe16(m->dhcp_duid_type); + + /* RawData contains DUID in format " NN:NN:NN... " */ + while (true) { + r = extract_first_word(&pduid, &cbyte, ":", 0); + if (r < 0) { + log_error("Failed to read DUID."); + return -EINVAL; + } + if (r == 0) + break; + if (duid_len >= MAX_DUID_LEN) { + log_error("DUID length exceeds maximum length."); + return -EINVAL; + } + + errno = 0; + byte = strtol(cbyte, &pnext, 16); + if ((errno == ERANGE && (byte == LONG_MAX || byte == LONG_MIN)) + || (errno != 0 && byte == 0) || (cbyte == pnext)) { + log_error("Invalid DUID byte: %s.", cbyte); + return -EINVAL; + } + + /* If DHCP_DUID_TYPE_RAW, first two bytes holds DUID Type */ + if ((m->dhcp_duid_type == DHCP_DUID_TYPE_RAW) && (count < 2)) { + m->dhcp_duid.type |= (byte << (8 * count)); + count++; + continue; + } + + m->dhcp_duid.raw.data[duid_len++] = byte; + } + + m->dhcp_duid_len = sizeof(m->dhcp_duid.type) + duid_len; + + return 0; +} diff --git a/src/network/networkd-conf.h b/src/network/networkd-conf.h new file mode 100644 index 0000000000..6d9ce010e3 --- /dev/null +++ b/src/network/networkd-conf.h @@ -0,0 +1,32 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 "networkd.h" + + +int manager_parse_config_file(Manager *m); + +const struct ConfigPerfItem* networkd_gperf_lookup(const char *key, unsigned length); + +int config_parse_dhcp_duid_type(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); +int config_parse_dhcp_duid_raw(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); diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 68998eabf2..3bbb21295c 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -625,7 +625,13 @@ int dhcp4_configure(Link *link) { switch (link->network->dhcp_client_identifier) { case DHCP_CLIENT_ID_DUID: - /* Library defaults to this. */ + /* If configured, apply user specified DUID and/or IAID */ + r = sd_dhcp_client_set_iaid_duid(link->dhcp_client, + link->network->iaid_value, + link->manager->dhcp_duid_len, + &link->manager->dhcp_duid); + if (r < 0) + return r; break; case DHCP_CLIENT_ID_MAC: r = sd_dhcp_client_set_client_id(link->dhcp_client, diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 5f7a005c36..9f59cb3f8a 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -230,6 +230,16 @@ int dhcp6_configure(Link *link) { if (r < 0) goto error; + r = sd_dhcp6_client_set_iaid(client, link->network->iaid_value); + if (r < 0) + goto error; + + r = sd_dhcp6_client_set_duid(client, + link->manager->dhcp_duid_len, + &link->manager->dhcp_duid); + if (r < 0) + goto error; + r = sd_dhcp6_client_set_index(client, link->ifindex); if (r < 0) goto error; diff --git a/src/network/networkd-gperf.gperf b/src/network/networkd-gperf.gperf new file mode 100644 index 0000000000..3ef4155476 --- /dev/null +++ b/src/network/networkd-gperf.gperf @@ -0,0 +1,18 @@ +%{ +#include +#include "conf-parser.h" +#include "networkd-conf.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name networkd_gperf_hash +%define lookup-function-name networkd_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +DUID.Type, config_parse_dhcp_duid_type, 0, offsetof(Manager, dhcp_duid_type) +DUID.RawData, config_parse_dhcp_duid_raw, 0, offsetof(Manager, dhcp_duid) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index ff4bd76554..67b04560cd 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -2781,6 +2781,13 @@ int link_update(Link *link, sd_netlink_message *m) { ARPHRD_ETHER); if (r < 0) return log_link_warning_errno(link, r, "Could not update MAC address in DHCP client: %m"); + + r = sd_dhcp_client_set_iaid_duid(link->dhcp_client, + link->network->iaid_value, + link->manager->dhcp_duid_len, + &link->manager->dhcp_duid); + if (r < 0) + return log_link_warning_errno(link, r, "Could not update DUID/IAID in DHCP client: %m"); } if (link->dhcp6_client) { @@ -2790,6 +2797,17 @@ int link_update(Link *link, sd_netlink_message *m) { ARPHRD_ETHER); if (r < 0) return log_link_warning_errno(link, r, "Could not update MAC address in DHCPv6 client: %m"); + + r = sd_dhcp6_client_set_iaid(link->dhcp6_client, + link->network->iaid_value); + if (r < 0) + return log_link_warning_errno(link, r, "Could not update DHCPv6 IAID: %m"); + + r = sd_dhcp6_client_set_duid(link->dhcp6_client, + link->manager->dhcp_duid_len, + &link->manager->dhcp_duid); + if (r < 0) + return log_link_warning_errno(link, r, "Could not update DHCPv6 DUID: %m"); } } } diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index b8cb7f875d..8d443f7b0f 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -1037,6 +1037,8 @@ int manager_new(Manager **ret) { if (r < 0) return r; + m->dhcp_duid_type = _DHCP_DUID_TYPE_INVALID; + *ret = m; m = NULL; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index a5d1714293..7a9a136d5b 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -26,6 +26,7 @@ Match.KernelCommandLine, config_parse_net_condition, Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(Network, match_arch) Link.MACAddress, config_parse_hwaddr, 0, offsetof(Network, mac) Link.MTUBytes, config_parse_iec_size, 0, offsetof(Network, mtu) +Link.IAIDValue, config_parse_iaid_value, 0, offsetof(Network, iaid_value) Network.Description, config_parse_string, 0, offsetof(Network, description) Network.Bridge, config_parse_netdev, 0, offsetof(Network, bridge) Network.Bond, config_parse_netdev, 0, offsetof(Network, bond) diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 4a13e2b574..c5530cdfba 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -30,6 +30,7 @@ typedef struct Network Network; #include "networkd-route.h" #include "networkd-util.h" #include "networkd.h" +#include "sparse-endian.h" #define DHCP_ROUTE_METRIC 1024 #define IPV4LL_ROUTE_METRIC 2048 @@ -144,6 +145,7 @@ struct Network { struct ether_addr *mac; unsigned mtu; + be32_t iaid_value; LLDPMode lldp_mode; /* LLDP reception */ bool lldp_emit; /* LLDP transmission */ diff --git a/src/network/networkd.c b/src/network/networkd.c index 3a2615e6fd..c8f81a2ca6 100644 --- a/src/network/networkd.c +++ b/src/network/networkd.c @@ -21,6 +21,7 @@ #include "capability-util.h" #include "networkd.h" +#include "networkd-conf.h" #include "signal-util.h" #include "user-util.h" @@ -89,6 +90,10 @@ int main(int argc, char *argv[]) { goto out; } + r = manager_parse_config_file(m); + if (r < 0) + log_warning_errno(r, "Failed to parse configuration file: %m"); + r = manager_load_config(m); if (r < 0) { log_error_errno(r, "Could not load configuration files: %m"); diff --git a/src/network/networkd.h b/src/network/networkd.h index 6bdd8302a0..d815f30610 100644 --- a/src/network/networkd.h +++ b/src/network/networkd.h @@ -35,6 +35,7 @@ typedef struct Manager Manager; #include "networkd-link.h" #include "networkd-network.h" #include "networkd-util.h" +#include "dhcp-identifier.h" struct Manager { sd_netlink *rtnl; @@ -61,6 +62,10 @@ struct Manager { LIST_HEAD(AddressPool, address_pools); usec_t network_dirs_ts_usec; + + DHCPDUIDType dhcp_duid_type; + size_t dhcp_duid_len; + struct duid dhcp_duid; }; extern const char* const network_dirs[]; diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index ef45370505..7873cb1e04 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -27,6 +27,7 @@ #include "sd-dhcp-lease.h" #include "sd-event.h" +#include "sparse-endian.h" #include "_sd-common.h" @@ -82,6 +83,7 @@ enum { SD_DHCP_OPTION_END = 255, }; +struct duid; typedef struct sd_dhcp_client sd_dhcp_client; typedef void (*sd_dhcp_client_callback_t)(sd_dhcp_client *client, int event, @@ -98,6 +100,8 @@ int sd_dhcp_client_set_mac(sd_dhcp_client *client, const uint8_t *addr, size_t addr_len, uint16_t arp_type); int sd_dhcp_client_set_client_id(sd_dhcp_client *client, uint8_t type, const uint8_t *data, size_t data_len); +int sd_dhcp_client_set_iaid_duid(sd_dhcp_client *client, be32_t iaid, + size_t duid_len, struct duid *duid); int sd_dhcp_client_get_client_id(sd_dhcp_client *client, uint8_t *type, const uint8_t **data, size_t *data_len); int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu); diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h index 1bedc941aa..ebdd017628 100644 --- a/src/systemd/sd-dhcp6-client.h +++ b/src/systemd/sd-dhcp6-client.h @@ -26,6 +26,7 @@ #include "sd-dhcp6-lease.h" #include "sd-event.h" +#include "sparse-endian.h" #include "_sd-common.h" @@ -74,6 +75,7 @@ enum { /* option codes 144-65535 are unassigned */ }; +struct duid; typedef struct sd_dhcp6_client sd_dhcp6_client; typedef void (*sd_dhcp6_client_callback_t)(sd_dhcp6_client *client, int event, @@ -85,8 +87,9 @@ int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index); int sd_dhcp6_client_set_local_address(sd_dhcp6_client *client, const struct in6_addr *local_address); int sd_dhcp6_client_set_mac(sd_dhcp6_client *client, const uint8_t *addr, size_t addr_len, uint16_t arp_type); -int sd_dhcp6_client_set_duid(sd_dhcp6_client *client, uint16_t type, uint8_t *duid, - size_t duid_len); +int sd_dhcp6_client_set_duid(sd_dhcp6_client *client, size_t duid_len, + struct duid *duid); +int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, be32_t iaid); int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, int enabled); int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, int *enabled); int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, -- cgit v1.2.3-54-g00ecf From afec45395fb019f19ac1e157fce9128b0137b25e Mon Sep 17 00:00:00 2001 From: Zbigniew Jędrzejewski-Szmek Date: Mon, 21 Mar 2016 18:24:24 -0400 Subject: Revert "DHCP DUID and IAID configurability" --- Makefile-man.am | 7 -- Makefile.am | 4 - man/networkd.conf.xml | 112 ------------------------- man/systemd.network.xml | 6 -- src/libsystemd-network/dhcp-identifier.c | 2 +- src/libsystemd-network/dhcp-identifier.h | 41 +-------- src/libsystemd-network/dhcp6-protocol.h | 7 ++ src/libsystemd-network/network-internal.c | 28 ------- src/libsystemd-network/network-internal.h | 4 - src/libsystemd-network/sd-dhcp-client.c | 48 +---------- src/libsystemd-network/sd-dhcp6-client.c | 49 ++++++----- src/network/networkd-conf.c | 133 ------------------------------ src/network/networkd-conf.h | 32 ------- src/network/networkd-dhcp4.c | 8 +- src/network/networkd-dhcp6.c | 10 --- src/network/networkd-gperf.gperf | 18 ---- src/network/networkd-link.c | 18 ---- src/network/networkd-manager.c | 2 - src/network/networkd-network-gperf.gperf | 1 - src/network/networkd-network.h | 2 - src/network/networkd.c | 5 -- src/network/networkd.h | 5 -- src/systemd/sd-dhcp-client.h | 4 - src/systemd/sd-dhcp6-client.h | 7 +- 24 files changed, 44 insertions(+), 509 deletions(-) delete mode 100644 man/networkd.conf.xml delete mode 100644 src/network/networkd-conf.c delete mode 100644 src/network/networkd-conf.h delete mode 100644 src/network/networkd-gperf.gperf (limited to 'src/network/networkd-network.h') diff --git a/Makefile-man.am b/Makefile-man.am index a7e348b1f1..3f03afc2ef 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -1960,21 +1960,15 @@ endif if ENABLE_NETWORKD MANPAGES += \ man/networkctl.1 \ - man/networkd.conf.5 \ man/systemd-networkd-wait-online.service.8 \ man/systemd-networkd.service.8 \ man/systemd.netdev.5 \ man/systemd.network.5 MANPAGES_ALIAS += \ - man/networkd.conf.d.5 \ man/systemd-networkd-wait-online.8 \ man/systemd-networkd.8 -man/networkd.conf.d.5: man/networkd.conf.5 man/systemd-networkd-wait-online.8: man/systemd-networkd-wait-online.service.8 man/systemd-networkd.8: man/systemd-networkd.service.8 -man/networkd.conf.d.html: man/networkd.conf.html - $(html-alias) - man/systemd-networkd-wait-online.html: man/systemd-networkd-wait-online.service.html $(html-alias) @@ -2485,7 +2479,6 @@ EXTRA_DIST += \ man/machinectl.xml \ man/modules-load.d.xml \ man/networkctl.xml \ - man/networkd.conf.xml \ man/nss-myhostname.xml \ man/nss-mymachines.xml \ man/nss-resolve.xml \ diff --git a/Makefile.am b/Makefile.am index c2a82a5a35..9a7ae2c286 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5398,8 +5398,6 @@ libnetworkd_core_la_CFLAGS = \ libnetworkd_core_la_SOURCES = \ src/libsystemd-network/network-internal.h \ src/network/networkd.h \ - src/network/networkd-conf.h \ - src/network/networkd-conf.c \ src/network/networkd-link.h \ src/network/networkd-link.c \ src/network/networkd-netdev.h \ @@ -5448,7 +5446,6 @@ libnetworkd_core_la_SOURCES = \ src/network/networkd-lldp-tx.c nodist_libnetworkd_core_la_SOURCES = \ - src/network/networkd-gperf.c \ src/network/networkd-network-gperf.c \ src/network/networkd-netdev-gperf.c @@ -5545,7 +5542,6 @@ BUSNAMES_TARGET_WANTS += \ endif gperf_gperf_sources += \ - src/network/networkd-gperf.gperf \ src/network/networkd-network-gperf.gperf \ src/network/networkd-netdev-gperf.gperf diff --git a/man/networkd.conf.xml b/man/networkd.conf.xml deleted file mode 100644 index 5e2927ba54..0000000000 --- a/man/networkd.conf.xml +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - networkd.conf - systemd - - - - Developer - Vinay - Kulkarni - kulkarniv@vmware.com - - - - - - networkd.conf - 5 - - - - networkd.conf - networkd.conf.d - Global Network configuration files - - - - /etc/systemd/networkd.conf - /etc/systemd/networkd.conf.d/*.conf - /usr/lib/systemd/networkd.conf.d/*.conf - - - - Description - - These configuration files control global network parameters. - For e.g. DHCP Unique Identifier (DUID). - - - - - - - [DUID] Section Options - - This section configures the DUID value used by the DHCP protocol. The DUID value - specified here overrides the DUID that systemd-networkd generates using the machine-id - from the /etc/machine-id file. - - The configured DHCP DUID should conform to the specification in - RFC 3315, - RFC 6355. To configure IAID, see - systemd.network5 - . - - The following options are available in [DUID] section: - - - - - Type= - The type of DUID specified in this section. The following values are - supported: - raw : If Type=raw, then RawData= specifies - the entire DUID. For e.g: RawData=00:02:00:00:ab:11:f9:2a:c2:77:29:f9:5c:00 - specifies a 14 byte long DUID-EN ("00:02"), with enterprise number 43793 ("00:00:ab:11"), - and identifier value "f9:2a:c2:77:29:f9:5c:00". - - - - RawData= - Specifies the DUID bytes as a single newline-terminated, hexadecimal - string, with each byte separated by a ':'. - - - - - - - See Also - - systemd1, - systemd.network5, - machine-id1 - - - - diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 73b9c00543..f2e715cf6f 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -204,12 +204,6 @@ understood to the base of 1024. - - IAIDValue= - - Identity Association Identifier for the interface. This is a 32-bit value specified in host byte order. - - diff --git a/src/libsystemd-network/dhcp-identifier.c b/src/libsystemd-network/dhcp-identifier.c index 1bef368852..1d9ec7be82 100644 --- a/src/libsystemd-network/dhcp-identifier.c +++ b/src/libsystemd-network/dhcp-identifier.c @@ -43,7 +43,7 @@ int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len) { if (r < 0) return r; - unaligned_write_be16(&duid->type, DHCP_DUID_TYPE_EN); + unaligned_write_be16(&duid->type, DHCP6_DUID_EN); unaligned_write_be32(&duid->en.pen, SYSTEMD_PEN); *len = sizeof(duid->type) + sizeof(duid->en); diff --git a/src/libsystemd-network/dhcp-identifier.h b/src/libsystemd-network/dhcp-identifier.h index cb953cb416..93f06f5938 100644 --- a/src/libsystemd-network/dhcp-identifier.h +++ b/src/libsystemd-network/dhcp-identifier.h @@ -25,23 +25,13 @@ #include "sparse-endian.h" #include "unaligned.h" -typedef enum DHCPDUIDType { - DHCP_DUID_TYPE_RAW = 0, - DHCP_DUID_TYPE_LLT = 1, - DHCP_DUID_TYPE_EN = 2, - DHCP_DUID_TYPE_LL = 3, - DHCP_DUID_TYPE_UUID = 4, - _DHCP_DUID_TYPE_MAX, - _DHCP_DUID_TYPE_INVALID = -1, -} DHCPDUIDType; - /* RFC 3315 section 9.1: * A DUID can be no more than 128 octets long (not including the type code). */ #define MAX_DUID_LEN 128 struct duid { - be16_t type; + uint16_t type; union { struct { /* DHCP6_DUID_LLT */ @@ -71,32 +61,3 @@ struct duid { int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len); int dhcp_identifier_set_iaid(int ifindex, uint8_t *mac, size_t mac_len, void *_id); - -static inline int dhcp_validate_duid_len(be16_t duid_type, size_t duid_len) { - struct duid d; - - assert_return(duid_len > 0 && duid_len <= MAX_DUID_LEN, -EINVAL); - - switch (be16toh(duid_type)) { - case DHCP_DUID_TYPE_LLT: - if (duid_len <= sizeof(d.llt)) - return -EINVAL; - break; - case DHCP_DUID_TYPE_EN: - if (duid_len != sizeof(d.en)) - return -EINVAL; - break; - case DHCP_DUID_TYPE_LL: - if (duid_len <= sizeof(d.ll)) - return -EINVAL; - break; - case DHCP_DUID_TYPE_UUID: - if (duid_len != sizeof(d.uuid)) - return -EINVAL; - break; - default: - /* accept unknown type in order to be forward compatible */ - break; - } - return 0; -} diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h index 2487c470ab..ee4bdfb07f 100644 --- a/src/libsystemd-network/dhcp6-protocol.h +++ b/src/libsystemd-network/dhcp6-protocol.h @@ -62,6 +62,13 @@ enum { #define DHCP6_REB_TIMEOUT 10 * USEC_PER_SEC #define DHCP6_REB_MAX_RT 600 * USEC_PER_SEC +enum { + DHCP6_DUID_LLT = 1, + DHCP6_DUID_EN = 2, + DHCP6_DUID_LL = 3, + DHCP6_DUID_UUID = 4, +}; + enum DHCP6State { DHCP6_STATE_STOPPED = 0, DHCP6_STATE_INFORMATION_REQUEST = 1, diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c index 7c21f42591..cb7252bbeb 100644 --- a/src/libsystemd-network/network-internal.c +++ b/src/libsystemd-network/network-internal.c @@ -335,34 +335,6 @@ int config_parse_hwaddr(const char *unit, return 0; } -int config_parse_iaid_value(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) { - uint32_t iaid_value; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if ((r = safe_atou32(rvalue, &iaid_value)) < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Unable to read IAID: %s", rvalue); - return r; - } - - *((be32_t *)data) = htobe32(iaid_value); - - return 0; -} - void serialize_in_addrs(FILE *f, const struct in_addr *addresses, size_t size) { unsigned i; diff --git a/src/libsystemd-network/network-internal.h b/src/libsystemd-network/network-internal.h index d8b551e8ce..c8a531ab0f 100644 --- a/src/libsystemd-network/network-internal.h +++ b/src/libsystemd-network/network-internal.h @@ -62,10 +62,6 @@ int config_parse_ifalias(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); -int config_parse_iaid_value(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); - int net_get_unique_predictable_data(struct udev_device *device, uint64_t *result); const char *net_get_name(struct udev_device *device); diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index b108e35386..1188b31500 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -82,7 +82,7 @@ struct sd_dhcp_client { } _packed_ ll; struct { /* 255: Node-specific (RFC 4361) */ - be32_t iaid; + uint32_t iaid; struct duid duid; } _packed_ ns; struct { @@ -298,51 +298,6 @@ int sd_dhcp_client_set_client_id(sd_dhcp_client *client, uint8_t type, return 0; } -int sd_dhcp_client_set_iaid_duid(sd_dhcp_client *client, be32_t iaid, - size_t duid_len, struct duid *duid) { - DHCP_CLIENT_DONT_DESTROY(client); - int r; - assert_return(client, -EINVAL); - zero(client->client_id); - - client->client_id.type = 255; - - /* If IAID is not configured, generate it. */ - if (iaid == 0) { - r = dhcp_identifier_set_iaid(client->index, client->mac_addr, - client->mac_addr_len, - &client->client_id.ns.iaid); - if (r < 0) - return r; - } else - client->client_id.ns.iaid = iaid; - - /* If DUID is not configured, generate DUID-EN. */ - if (duid_len == 0) { - r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, - &duid_len); - if (r < 0) - return r; - } else { - r = dhcp_validate_duid_len(client->client_id.type, - duid_len - sizeof(client->client_id.type)); - if (r < 0) - return r; - memcpy(&client->client_id.ns.duid, duid, duid_len); - } - - client->client_id_len = sizeof(client->client_id.type) + duid_len + - sizeof(client->client_id.ns.iaid); - - if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) { - log_dhcp_client(client, "Configured IAID+DUID, restarting."); - client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); - sd_dhcp_client_start(client); - } - - return 0; -} - int sd_dhcp_client_set_hostname(sd_dhcp_client *client, const char *hostname) { char *new_hostname = NULL; @@ -514,6 +469,7 @@ static int client_message_init(sd_dhcp_client *client, DHCPPacket **ret, if (client->arp_type == ARPHRD_ETHER) memcpy(&packet->dhcp.chaddr, &client->mac_addr, ETH_ALEN); + /* If no client identifier exists, construct an RFC 4361-compliant one */ if (client->client_id_len == 0) { size_t duid_len; diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 7cecba120c..af4709d788 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -180,30 +180,41 @@ static int client_ensure_duid(sd_dhcp6_client *client) { return dhcp_identifier_set_duid_en(&client->duid, &client->duid_len); } -int sd_dhcp6_client_set_duid(sd_dhcp6_client *client, size_t duid_len, - struct duid *duid) { - int r; +int sd_dhcp6_client_set_duid( + sd_dhcp6_client *client, + uint16_t type, + uint8_t *duid, size_t duid_len) { assert_return(client, -EINVAL); - assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); + assert_return(duid, -EINVAL); + assert_return(duid_len > 0 && duid_len <= MAX_DUID_LEN, -EINVAL); - if (duid_len > 0) { - r = dhcp_validate_duid_len(duid->type, - duid_len - sizeof(duid->type)); - if (r < 0) - return r; + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); - memcpy(&client->duid, duid, duid_len); - client->duid_len = duid_len; + switch (type) { + case DHCP6_DUID_LLT: + if (duid_len <= sizeof(client->duid.llt)) + return -EINVAL; + break; + case DHCP6_DUID_EN: + if (duid_len != sizeof(client->duid.en)) + return -EINVAL; + break; + case DHCP6_DUID_LL: + if (duid_len <= sizeof(client->duid.ll)) + return -EINVAL; + break; + case DHCP6_DUID_UUID: + if (duid_len != sizeof(client->duid.uuid)) + return -EINVAL; + break; + default: + /* accept unknown type in order to be forward compatible */ + break; } - return 0; -} - -int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, be32_t iaid) { - assert_return(client, -EINVAL); - assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); - - client->ia_na.id = iaid; + client->duid.type = htobe16(type); + memcpy(&client->duid.raw.data, duid, duid_len); + client->duid_len = duid_len + sizeof(client->duid.type); return 0; } diff --git a/src/network/networkd-conf.c b/src/network/networkd-conf.c deleted file mode 100644 index 4bc92b8171..0000000000 --- a/src/network/networkd-conf.c +++ /dev/null @@ -1,133 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 "conf-parser.h" -#include "def.h" -#include "dhcp-identifier.h" -#include "networkd-conf.h" -#include "string-table.h" - -int manager_parse_config_file(Manager *m) { - assert(m); - - return config_parse_many(PKGSYSCONFDIR "/networkd.conf", - CONF_PATHS_NULSTR("systemd/networkd.conf.d"), - "DUID\0", - config_item_perf_lookup, networkd_gperf_lookup, - false, m); -} - -static const char* const dhcp_duid_type_table[_DHCP_DUID_TYPE_MAX] = { - [DHCP_DUID_TYPE_RAW] = "raw", - [DHCP_DUID_TYPE_LLT] = "link-layer-time", - [DHCP_DUID_TYPE_EN] = "vendor", - [DHCP_DUID_TYPE_LL] = "link-layer", - [DHCP_DUID_TYPE_UUID] = "uuid" -}; -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(dhcp_duid_type, DHCPDUIDType); -DEFINE_CONFIG_PARSE_ENUM(config_parse_dhcp_duid_type, dhcp_duid_type, DHCPDUIDType, "Failed to parse DHCP DUID type"); - -int config_parse_dhcp_duid_raw( - 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) { - int r; - long byte; - char *cbyte, *pnext; - const char *pduid = (const char *)rvalue; - size_t count = 0, duid_len = 0; - Manager *m = userdata; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(m); - assert(m->dhcp_duid_type != _DHCP_DUID_TYPE_INVALID); - - switch (m->dhcp_duid_type) { - case DHCP_DUID_TYPE_LLT: - /* RawData contains DUID-LLT link-layer address (offset 6) */ - duid_len = 6; - break; - case DHCP_DUID_TYPE_EN: - /* RawData contains DUID-EN identifier (offset 4) */ - duid_len = 4; - break; - case DHCP_DUID_TYPE_LL: - /* RawData contains DUID-LL link-layer address (offset 2) */ - duid_len = 2; - break; - case DHCP_DUID_TYPE_UUID: - /* RawData specifies UUID (offset 0) - fall thru */ - case DHCP_DUID_TYPE_RAW: - /* First two bytes of RawData is DUID Type - fall thru */ - default: - break; - } - - if (m->dhcp_duid_type != DHCP_DUID_TYPE_RAW) - m->dhcp_duid.type = htobe16(m->dhcp_duid_type); - - /* RawData contains DUID in format " NN:NN:NN... " */ - while (true) { - r = extract_first_word(&pduid, &cbyte, ":", 0); - if (r < 0) { - log_error("Failed to read DUID."); - return -EINVAL; - } - if (r == 0) - break; - if (duid_len >= MAX_DUID_LEN) { - log_error("DUID length exceeds maximum length."); - return -EINVAL; - } - - errno = 0; - byte = strtol(cbyte, &pnext, 16); - if ((errno == ERANGE && (byte == LONG_MAX || byte == LONG_MIN)) - || (errno != 0 && byte == 0) || (cbyte == pnext)) { - log_error("Invalid DUID byte: %s.", cbyte); - return -EINVAL; - } - - /* If DHCP_DUID_TYPE_RAW, first two bytes holds DUID Type */ - if ((m->dhcp_duid_type == DHCP_DUID_TYPE_RAW) && (count < 2)) { - m->dhcp_duid.type |= (byte << (8 * count)); - count++; - continue; - } - - m->dhcp_duid.raw.data[duid_len++] = byte; - } - - m->dhcp_duid_len = sizeof(m->dhcp_duid.type) + duid_len; - - return 0; -} diff --git a/src/network/networkd-conf.h b/src/network/networkd-conf.h deleted file mode 100644 index 6d9ce010e3..0000000000 --- a/src/network/networkd-conf.h +++ /dev/null @@ -1,32 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 "networkd.h" - - -int manager_parse_config_file(Manager *m); - -const struct ConfigPerfItem* networkd_gperf_lookup(const char *key, unsigned length); - -int config_parse_dhcp_duid_type(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); -int config_parse_dhcp_duid_raw(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); diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 3bbb21295c..68998eabf2 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -625,13 +625,7 @@ int dhcp4_configure(Link *link) { switch (link->network->dhcp_client_identifier) { case DHCP_CLIENT_ID_DUID: - /* If configured, apply user specified DUID and/or IAID */ - r = sd_dhcp_client_set_iaid_duid(link->dhcp_client, - link->network->iaid_value, - link->manager->dhcp_duid_len, - &link->manager->dhcp_duid); - if (r < 0) - return r; + /* Library defaults to this. */ break; case DHCP_CLIENT_ID_MAC: r = sd_dhcp_client_set_client_id(link->dhcp_client, diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 9f59cb3f8a..5f7a005c36 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -230,16 +230,6 @@ int dhcp6_configure(Link *link) { if (r < 0) goto error; - r = sd_dhcp6_client_set_iaid(client, link->network->iaid_value); - if (r < 0) - goto error; - - r = sd_dhcp6_client_set_duid(client, - link->manager->dhcp_duid_len, - &link->manager->dhcp_duid); - if (r < 0) - goto error; - r = sd_dhcp6_client_set_index(client, link->ifindex); if (r < 0) goto error; diff --git a/src/network/networkd-gperf.gperf b/src/network/networkd-gperf.gperf deleted file mode 100644 index 3ef4155476..0000000000 --- a/src/network/networkd-gperf.gperf +++ /dev/null @@ -1,18 +0,0 @@ -%{ -#include -#include "conf-parser.h" -#include "networkd-conf.h" -%} -struct ConfigPerfItem; -%null_strings -%language=ANSI-C -%define slot-name section_and_lvalue -%define hash-function-name networkd_gperf_hash -%define lookup-function-name networkd_gperf_lookup -%readonly-tables -%omit-struct-type -%struct-type -%includes -%% -DUID.Type, config_parse_dhcp_duid_type, 0, offsetof(Manager, dhcp_duid_type) -DUID.RawData, config_parse_dhcp_duid_raw, 0, offsetof(Manager, dhcp_duid) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 67b04560cd..ff4bd76554 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -2781,13 +2781,6 @@ int link_update(Link *link, sd_netlink_message *m) { ARPHRD_ETHER); if (r < 0) return log_link_warning_errno(link, r, "Could not update MAC address in DHCP client: %m"); - - r = sd_dhcp_client_set_iaid_duid(link->dhcp_client, - link->network->iaid_value, - link->manager->dhcp_duid_len, - &link->manager->dhcp_duid); - if (r < 0) - return log_link_warning_errno(link, r, "Could not update DUID/IAID in DHCP client: %m"); } if (link->dhcp6_client) { @@ -2797,17 +2790,6 @@ int link_update(Link *link, sd_netlink_message *m) { ARPHRD_ETHER); if (r < 0) return log_link_warning_errno(link, r, "Could not update MAC address in DHCPv6 client: %m"); - - r = sd_dhcp6_client_set_iaid(link->dhcp6_client, - link->network->iaid_value); - if (r < 0) - return log_link_warning_errno(link, r, "Could not update DHCPv6 IAID: %m"); - - r = sd_dhcp6_client_set_duid(link->dhcp6_client, - link->manager->dhcp_duid_len, - &link->manager->dhcp_duid); - if (r < 0) - return log_link_warning_errno(link, r, "Could not update DHCPv6 DUID: %m"); } } } diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 8d443f7b0f..b8cb7f875d 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -1037,8 +1037,6 @@ int manager_new(Manager **ret) { if (r < 0) return r; - m->dhcp_duid_type = _DHCP_DUID_TYPE_INVALID; - *ret = m; m = NULL; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 7a9a136d5b..a5d1714293 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -26,7 +26,6 @@ Match.KernelCommandLine, config_parse_net_condition, Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(Network, match_arch) Link.MACAddress, config_parse_hwaddr, 0, offsetof(Network, mac) Link.MTUBytes, config_parse_iec_size, 0, offsetof(Network, mtu) -Link.IAIDValue, config_parse_iaid_value, 0, offsetof(Network, iaid_value) Network.Description, config_parse_string, 0, offsetof(Network, description) Network.Bridge, config_parse_netdev, 0, offsetof(Network, bridge) Network.Bond, config_parse_netdev, 0, offsetof(Network, bond) diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index c5530cdfba..4a13e2b574 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -30,7 +30,6 @@ typedef struct Network Network; #include "networkd-route.h" #include "networkd-util.h" #include "networkd.h" -#include "sparse-endian.h" #define DHCP_ROUTE_METRIC 1024 #define IPV4LL_ROUTE_METRIC 2048 @@ -145,7 +144,6 @@ struct Network { struct ether_addr *mac; unsigned mtu; - be32_t iaid_value; LLDPMode lldp_mode; /* LLDP reception */ bool lldp_emit; /* LLDP transmission */ diff --git a/src/network/networkd.c b/src/network/networkd.c index c8f81a2ca6..3a2615e6fd 100644 --- a/src/network/networkd.c +++ b/src/network/networkd.c @@ -21,7 +21,6 @@ #include "capability-util.h" #include "networkd.h" -#include "networkd-conf.h" #include "signal-util.h" #include "user-util.h" @@ -90,10 +89,6 @@ int main(int argc, char *argv[]) { goto out; } - r = manager_parse_config_file(m); - if (r < 0) - log_warning_errno(r, "Failed to parse configuration file: %m"); - r = manager_load_config(m); if (r < 0) { log_error_errno(r, "Could not load configuration files: %m"); diff --git a/src/network/networkd.h b/src/network/networkd.h index d815f30610..6bdd8302a0 100644 --- a/src/network/networkd.h +++ b/src/network/networkd.h @@ -35,7 +35,6 @@ typedef struct Manager Manager; #include "networkd-link.h" #include "networkd-network.h" #include "networkd-util.h" -#include "dhcp-identifier.h" struct Manager { sd_netlink *rtnl; @@ -62,10 +61,6 @@ struct Manager { LIST_HEAD(AddressPool, address_pools); usec_t network_dirs_ts_usec; - - DHCPDUIDType dhcp_duid_type; - size_t dhcp_duid_len; - struct duid dhcp_duid; }; extern const char* const network_dirs[]; diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index 7873cb1e04..ef45370505 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -27,7 +27,6 @@ #include "sd-dhcp-lease.h" #include "sd-event.h" -#include "sparse-endian.h" #include "_sd-common.h" @@ -83,7 +82,6 @@ enum { SD_DHCP_OPTION_END = 255, }; -struct duid; typedef struct sd_dhcp_client sd_dhcp_client; typedef void (*sd_dhcp_client_callback_t)(sd_dhcp_client *client, int event, @@ -100,8 +98,6 @@ int sd_dhcp_client_set_mac(sd_dhcp_client *client, const uint8_t *addr, size_t addr_len, uint16_t arp_type); int sd_dhcp_client_set_client_id(sd_dhcp_client *client, uint8_t type, const uint8_t *data, size_t data_len); -int sd_dhcp_client_set_iaid_duid(sd_dhcp_client *client, be32_t iaid, - size_t duid_len, struct duid *duid); int sd_dhcp_client_get_client_id(sd_dhcp_client *client, uint8_t *type, const uint8_t **data, size_t *data_len); int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu); diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h index ebdd017628..1bedc941aa 100644 --- a/src/systemd/sd-dhcp6-client.h +++ b/src/systemd/sd-dhcp6-client.h @@ -26,7 +26,6 @@ #include "sd-dhcp6-lease.h" #include "sd-event.h" -#include "sparse-endian.h" #include "_sd-common.h" @@ -75,7 +74,6 @@ enum { /* option codes 144-65535 are unassigned */ }; -struct duid; typedef struct sd_dhcp6_client sd_dhcp6_client; typedef void (*sd_dhcp6_client_callback_t)(sd_dhcp6_client *client, int event, @@ -87,9 +85,8 @@ int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index); int sd_dhcp6_client_set_local_address(sd_dhcp6_client *client, const struct in6_addr *local_address); int sd_dhcp6_client_set_mac(sd_dhcp6_client *client, const uint8_t *addr, size_t addr_len, uint16_t arp_type); -int sd_dhcp6_client_set_duid(sd_dhcp6_client *client, size_t duid_len, - struct duid *duid); -int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, be32_t iaid); +int sd_dhcp6_client_set_duid(sd_dhcp6_client *client, uint16_t type, uint8_t *duid, + size_t duid_len); int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, int enabled); int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, int *enabled); int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, -- cgit v1.2.3-54-g00ecf From 413708d106afb5ec36464c3fad56f081373320e0 Mon Sep 17 00:00:00 2001 From: Vinay Kulkarni Date: Wed, 30 Mar 2016 16:33:55 -0700 Subject: DHCP DUID, IAID configuration options --- Makefile-man.am | 7 ++ Makefile.am | 4 + man/networkd.conf.xml | 159 ++++++++++++++++++++++++++++++ man/systemd.network.xml | 86 ++++++++++++++++ src/libsystemd-network/dhcp-identifier.c | 2 +- src/libsystemd-network/dhcp-identifier.h | 41 +++++++- src/libsystemd-network/dhcp6-protocol.h | 7 -- src/libsystemd-network/network-internal.c | 29 ++++++ src/libsystemd-network/network-internal.h | 4 + src/libsystemd-network/sd-dhcp-client.c | 48 ++++++++- src/libsystemd-network/sd-dhcp6-client.c | 48 ++++----- src/network/networkd-conf.c | 155 +++++++++++++++++++++++++++++ src/network/networkd-conf.h | 36 +++++++ src/network/networkd-dhcp4.c | 16 ++- src/network/networkd-dhcp6.c | 17 ++++ src/network/networkd-gperf.gperf | 18 ++++ src/network/networkd-link.c | 33 +++++++ src/network/networkd-manager.c | 2 + src/network/networkd-network-gperf.gperf | 4 + src/network/networkd-network.c | 2 + src/network/networkd-network.h | 8 ++ src/network/networkd.c | 5 + src/network/networkd.h | 8 ++ src/systemd/sd-dhcp-client.h | 2 + src/systemd/sd-dhcp6-client.h | 5 +- 25 files changed, 703 insertions(+), 43 deletions(-) create mode 100644 man/networkd.conf.xml create mode 100644 src/network/networkd-conf.c create mode 100644 src/network/networkd-conf.h create mode 100644 src/network/networkd-gperf.gperf (limited to 'src/network/networkd-network.h') diff --git a/Makefile-man.am b/Makefile-man.am index 3f03afc2ef..a7e348b1f1 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -1960,15 +1960,21 @@ endif if ENABLE_NETWORKD MANPAGES += \ man/networkctl.1 \ + man/networkd.conf.5 \ man/systemd-networkd-wait-online.service.8 \ man/systemd-networkd.service.8 \ man/systemd.netdev.5 \ man/systemd.network.5 MANPAGES_ALIAS += \ + man/networkd.conf.d.5 \ man/systemd-networkd-wait-online.8 \ man/systemd-networkd.8 +man/networkd.conf.d.5: man/networkd.conf.5 man/systemd-networkd-wait-online.8: man/systemd-networkd-wait-online.service.8 man/systemd-networkd.8: man/systemd-networkd.service.8 +man/networkd.conf.d.html: man/networkd.conf.html + $(html-alias) + man/systemd-networkd-wait-online.html: man/systemd-networkd-wait-online.service.html $(html-alias) @@ -2479,6 +2485,7 @@ EXTRA_DIST += \ man/machinectl.xml \ man/modules-load.d.xml \ man/networkctl.xml \ + man/networkd.conf.xml \ man/nss-myhostname.xml \ man/nss-mymachines.xml \ man/nss-resolve.xml \ diff --git a/Makefile.am b/Makefile.am index 2b72a53ecd..350416af30 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5398,6 +5398,8 @@ libnetworkd_core_la_CFLAGS = \ libnetworkd_core_la_SOURCES = \ src/libsystemd-network/network-internal.h \ src/network/networkd.h \ + src/network/networkd-conf.h \ + src/network/networkd-conf.c \ src/network/networkd-link.h \ src/network/networkd-link.c \ src/network/networkd-netdev.h \ @@ -5446,6 +5448,7 @@ libnetworkd_core_la_SOURCES = \ src/network/networkd-lldp-tx.c nodist_libnetworkd_core_la_SOURCES = \ + src/network/networkd-gperf.c \ src/network/networkd-network-gperf.c \ src/network/networkd-netdev-gperf.c @@ -5542,6 +5545,7 @@ BUSNAMES_TARGET_WANTS += \ endif gperf_gperf_sources += \ + src/network/networkd-gperf.gperf \ src/network/networkd-network-gperf.gperf \ src/network/networkd-netdev-gperf.gperf diff --git a/man/networkd.conf.xml b/man/networkd.conf.xml new file mode 100644 index 0000000000..30674e12bc --- /dev/null +++ b/man/networkd.conf.xml @@ -0,0 +1,159 @@ + + + + + + + + networkd.conf + systemd + + + + Developer + Vinay + Kulkarni + kulkarniv@vmware.com + + + + + + networkd.conf + 5 + + + + networkd.conf + networkd.conf.d + Global Network configuration files + + + + /etc/systemd/networkd.conf + /etc/systemd/networkd.conf.d/*.conf + /usr/lib/systemd/networkd.conf.d/*.conf + + + + Description + + These configuration files control global network parameters. + For e.g. DHCP Unique Identifier (DUID). + + + + + + + [DUID] Section Options + + This section configures the DHCP Unique Idendifier (DUID) value used by DHCP + protocol. DHCPv6 client protocol sends the DHCP Unique Identifier and the interface + Identity Association Identifier (IAID) to a DHCP server when acquiring a dynamic IPv6 + address. DHCPv4 client protocol sends IAID and DUID to the DHCP server when acquiring + a dynamic IPv4 address if . IAID and DUID allows + a DHCP server to uniquely identify the machine and the interface requesting a DHCP IP. + To configure IAID and ClientIdentifier, see systemd.network + 5. + + The DUID value specified here overrides the DUID that systemd-networkd + generates using the machine-id from the /etc/machine-id file. + To configure DUID per-network, see systemd.network + 5. + + The configured DHCP DUID should conform to the specification in + RFC 3315, + RFC 6355. To configure IAID, see + systemd.network5 + . + + The following options are available in [DUID] section: + + + + + Type= + The type of DUID specified in this section. The following values are + supported: + raw : If Type=raw, then RawData= specifies + the entire DUID. For e.g: RawData=00:02:00:00:ab:11:f9:2a:c2:77:29:f9:5c:00 + specifies a 14 byte long DUID-EN ("00:02"), with enterprise number 43793 ("00:00:ab:11"), + and identifier value "f9:2a:c2:77:29:f9:5c:00".If Type is not specified and + RawData is specified, Type defaults to 'raw'. + Type will support the following values in the future: + link-layer-and-time : If Type=link-layer-and-time, then + MACAddress= and TimeStamp= specify the hardware + address and time-stamp for DUID-LLT. + vendor : If Type=vendor, then EnterpriseNumber= + and RawData= specify the enterprise number and identifier for DUID-EN. + link-layer : If Type=link-layer, then MACAddress= + specifies the hardware address for DUID-LL. + uuid : If Type=uuid, then UUID= specifies DUID-UUID. + + + + + RawData= + Specifies the DUID bytes as a single newline-terminated, hexadecimal + string, with each byte separated by a ':'. + + + + + + The following options will be supported in the future: + + + + MACAddress= + Specifies the link-layer address for DUID Type or . + + + TimeStamp= + Specifies the DUID generation time for DUID Type . + + + EnterpriseNumber= + Specifies the enterprise number for DUID Type + . + + + UUID= + Specifies the UUID for DUID Type . + + + + + + + See Also + + systemd1, + systemd.network5, + machine-id1 + + + + diff --git a/man/systemd.network.xml b/man/systemd.network.xml index f2e715cf6f..8611c0032d 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -204,6 +204,12 @@ understood to the base of 1024. + + IAIDValue= + + Identity Association Identifier for the interface, a 32-bit unsigned integer. + + @@ -823,6 +829,86 @@ + + [DUID] Section Options + + This section configures the DHCP Unique Idendifier (DUID) value used by DHCP + protocol. DHCPv6 client protocol sends the DHCP Unique Identifier and the interface + Identity Association Identifier (IAID) to a DHCP server when acquiring a dynamic IPv6 + address. DHCPv4 client protocol sends IAID and DUID to the DHCP server when acquiring + a dynamic IPv4 address if . IAID and DUID allows a + DHCP server to uniquely identify the machine and the interface requesting a DHCP IP. + + The DUID value specified here overrides the DUID that systemd-networkd generates + using the machine-id from the /etc/machine-id file, as well as the + global DUID that may be specified in networkd.conf + 5. + + The configured DHCP DUID should conform to the specification in + RFC 3315, + RFC 6355. + + The following options are available in [DUID] section: + + + + + Type= + The type of DUID specified in this section. The following values are + supported: + raw : If Type=raw, then RawData= specifies + the entire DUID. For e.g: RawData=00:02:00:00:ab:11:f9:2a:c2:77:29:f9:5c:00 + specifies a 14 byte long DUID-EN ("00:02"), with enterprise number 43793 ("00:00:ab:11"), + and identifier value "f9:2a:c2:77:29:f9:5c:00".If Type is not specified and + RawData is specified, Type defaults to 'raw'. + Type will support the following values in the future: + link-layer-and-time : If Type=link-layer-and-time, then + MACAddress= and TimeStamp= specify the hardware + address and time-stamp for DUID-LLT. + vendor : If Type=vendor, then EnterpriseNumber= + and RawData= specify the enterprise number and identifier for DUID-EN. + link-layer : If Type=link-layer, then MACAddress= + specifies the hardware address for DUID-LL. + uuid : If Type=uuid, then UUID= specifies DUID-UUID. + + + + + RawData= + Specifies the DUID bytes as a single newline-terminated, hexadecimal + string, with each byte separated by a ':'. + + + + + + + The following options will be supported in the future: + + + + MACAddress= + Specifies the link-layer address for DUID Type or . + + + TimeStamp= + Specifies the DUID generation time for DUID Type . + + + EnterpriseNumber= + Specifies the enterprise number for DUID Type . + + + UUID= + Specifies the UUID for DUID Type . + + + + + [DHCPServer] Section Options The [DHCPServer] section contains diff --git a/src/libsystemd-network/dhcp-identifier.c b/src/libsystemd-network/dhcp-identifier.c index 1d9ec7be82..4f7d4d8bf2 100644 --- a/src/libsystemd-network/dhcp-identifier.c +++ b/src/libsystemd-network/dhcp-identifier.c @@ -43,7 +43,7 @@ int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len) { if (r < 0) return r; - unaligned_write_be16(&duid->type, DHCP6_DUID_EN); + unaligned_write_be16(&duid->type, DUID_TYPE_EN); unaligned_write_be32(&duid->en.pen, SYSTEMD_PEN); *len = sizeof(duid->type) + sizeof(duid->en); diff --git a/src/libsystemd-network/dhcp-identifier.h b/src/libsystemd-network/dhcp-identifier.h index 93f06f5938..babae15c5b 100644 --- a/src/libsystemd-network/dhcp-identifier.h +++ b/src/libsystemd-network/dhcp-identifier.h @@ -25,13 +25,23 @@ #include "sparse-endian.h" #include "unaligned.h" +typedef enum DUIDType { + DUID_TYPE_RAW = 0, + DUID_TYPE_LLT = 1, + DUID_TYPE_EN = 2, + DUID_TYPE_LL = 3, + DUID_TYPE_UUID = 4, + _DUID_TYPE_MAX, + _DUID_TYPE_INVALID = -1, +} DUIDType; + /* RFC 3315 section 9.1: * A DUID can be no more than 128 octets long (not including the type code). */ #define MAX_DUID_LEN 128 struct duid { - uint16_t type; + be16_t type; union { struct { /* DHCP6_DUID_LLT */ @@ -61,3 +71,32 @@ struct duid { int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len); int dhcp_identifier_set_iaid(int ifindex, uint8_t *mac, size_t mac_len, void *_id); + +static inline int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len) { + struct duid d; + + assert_return(duid_len > 0 && duid_len <= MAX_DUID_LEN, -EINVAL); + + switch (duid_type) { + case DUID_TYPE_LLT: + if (duid_len <= sizeof(d.llt)) + return -EINVAL; + break; + case DUID_TYPE_EN: + if (duid_len != sizeof(d.en)) + return -EINVAL; + break; + case DUID_TYPE_LL: + if (duid_len <= sizeof(d.ll)) + return -EINVAL; + break; + case DUID_TYPE_UUID: + if (duid_len != sizeof(d.uuid)) + return -EINVAL; + break; + default: + /* accept unknown type in order to be forward compatible */ + break; + } + return 0; +} diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h index ee4bdfb07f..2487c470ab 100644 --- a/src/libsystemd-network/dhcp6-protocol.h +++ b/src/libsystemd-network/dhcp6-protocol.h @@ -62,13 +62,6 @@ enum { #define DHCP6_REB_TIMEOUT 10 * USEC_PER_SEC #define DHCP6_REB_MAX_RT 600 * USEC_PER_SEC -enum { - DHCP6_DUID_LLT = 1, - DHCP6_DUID_EN = 2, - DHCP6_DUID_LL = 3, - DHCP6_DUID_UUID = 4, -}; - enum DHCP6State { DHCP6_STATE_STOPPED = 0, DHCP6_STATE_INFORMATION_REQUEST = 1, diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c index cb7252bbeb..0e2d757b2b 100644 --- a/src/libsystemd-network/network-internal.c +++ b/src/libsystemd-network/network-internal.c @@ -335,6 +335,35 @@ int config_parse_hwaddr(const char *unit, return 0; } +int config_parse_iaid(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) { + uint32_t iaid; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atou32(rvalue, &iaid); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Unable to read IAID: %s", rvalue); + return r; + } + + *((uint32_t *)data) = iaid; + + return 0; +} + void serialize_in_addrs(FILE *f, const struct in_addr *addresses, size_t size) { unsigned i; diff --git a/src/libsystemd-network/network-internal.h b/src/libsystemd-network/network-internal.h index c8a531ab0f..72432774d7 100644 --- a/src/libsystemd-network/network-internal.h +++ b/src/libsystemd-network/network-internal.h @@ -62,6 +62,10 @@ int config_parse_ifalias(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); +int config_parse_iaid(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); + int net_get_unique_predictable_data(struct udev_device *device, uint64_t *result); const char *net_get_name(struct udev_device *device); diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 1188b31500..287b6e26fa 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -82,7 +82,7 @@ struct sd_dhcp_client { } _packed_ ll; struct { /* 255: Node-specific (RFC 4361) */ - uint32_t iaid; + be32_t iaid; struct duid duid; } _packed_ ns; struct { @@ -298,6 +298,52 @@ int sd_dhcp_client_set_client_id(sd_dhcp_client *client, uint8_t type, return 0; } +int sd_dhcp_client_set_iaid_duid(sd_dhcp_client *client, uint32_t iaid, + uint16_t duid_type, uint8_t *duid, size_t duid_len) { + DHCP_CLIENT_DONT_DESTROY(client); + int r; + assert_return(client, -EINVAL); + zero(client->client_id); + + client->client_id.type = 255; + + /* If IAID is not configured, generate it. */ + if (iaid == 0) { + r = dhcp_identifier_set_iaid(client->index, client->mac_addr, + client->mac_addr_len, + &client->client_id.ns.iaid); + if (r < 0) + return r; + } else + client->client_id.ns.iaid = htobe32(iaid); + + /* If DUID is not configured, generate DUID-EN. */ + if (duid_len == 0) { + r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, + &duid_len); + if (r < 0) + return r; + } else { + r = dhcp_validate_duid_len(client->client_id.type, duid_len); + if (r < 0) + return r; + client->client_id.ns.duid.type = htobe16(duid_type); + memcpy(&client->client_id.ns.duid.raw.data, duid, duid_len); + duid_len += sizeof(client->client_id.ns.duid.type); + } + + client->client_id_len = sizeof(client->client_id.type) + duid_len + + sizeof(client->client_id.ns.iaid); + + if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) { + log_dhcp_client(client, "Configured IAID+DUID, restarting."); + client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); + sd_dhcp_client_start(client); + } + + return 0; +} + int sd_dhcp_client_set_hostname(sd_dhcp_client *client, const char *hostname) { char *new_hostname = NULL; diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index af4709d788..ee4fb4fc1e 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -180,41 +180,29 @@ static int client_ensure_duid(sd_dhcp6_client *client) { return dhcp_identifier_set_duid_en(&client->duid, &client->duid_len); } -int sd_dhcp6_client_set_duid( - sd_dhcp6_client *client, - uint16_t type, - uint8_t *duid, size_t duid_len) { +int sd_dhcp6_client_set_duid(sd_dhcp6_client *client, uint16_t duid_type, + uint8_t *duid, size_t duid_len) { + int r; assert_return(client, -EINVAL); - assert_return(duid, -EINVAL); - assert_return(duid_len > 0 && duid_len <= MAX_DUID_LEN, -EINVAL); - assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); - switch (type) { - case DHCP6_DUID_LLT: - if (duid_len <= sizeof(client->duid.llt)) - return -EINVAL; - break; - case DHCP6_DUID_EN: - if (duid_len != sizeof(client->duid.en)) - return -EINVAL; - break; - case DHCP6_DUID_LL: - if (duid_len <= sizeof(client->duid.ll)) - return -EINVAL; - break; - case DHCP6_DUID_UUID: - if (duid_len != sizeof(client->duid.uuid)) - return -EINVAL; - break; - default: - /* accept unknown type in order to be forward compatible */ - break; + if (duid_len > 0) { + r = dhcp_validate_duid_len(duid_type, duid_len); + if (r < 0) + return r; + client->duid.type = htobe16(duid_type); + memcpy(&client->duid.raw.data, duid, duid_len); + client->duid_len = duid_len + sizeof(client->duid.type); } - client->duid.type = htobe16(type); - memcpy(&client->duid.raw.data, duid, duid_len); - client->duid_len = duid_len + sizeof(client->duid.type); + return 0; +} + +int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) { + assert_return(client, -EINVAL); + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); + + client->ia_na.id = htobe32(iaid); return 0; } diff --git a/src/network/networkd-conf.c b/src/network/networkd-conf.c new file mode 100644 index 0000000000..8b149124b3 --- /dev/null +++ b/src/network/networkd-conf.c @@ -0,0 +1,155 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Vinay Kulkarni + + 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 "conf-parser.h" +#include "def.h" +#include "dhcp-identifier.h" +#include "networkd-conf.h" +#include "string-table.h" + +int manager_parse_config_file(Manager *m) { + assert(m); + + return config_parse_many(PKGSYSCONFDIR "/networkd.conf", + CONF_PATHS_NULSTR("systemd/networkd.conf.d"), + "DUID\0", + config_item_perf_lookup, networkd_gperf_lookup, + false, m); +} + +static const char* const duid_type_table[_DUID_TYPE_MAX] = { + [DUID_TYPE_RAW] = "raw", + [DUID_TYPE_LLT] = "link-layer-time", + [DUID_TYPE_EN] = "vendor", + [DUID_TYPE_LL] = "link-layer", + [DUID_TYPE_UUID] = "uuid" +}; +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(duid_type, DUIDType); +DEFINE_CONFIG_PARSE_ENUM(config_parse_duid_type, duid_type, DUIDType, "Failed to parse DUID type"); + +int config_parse_duid_rawdata( + 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) { + int r; + long byte; + char *cbyte, *pnext; + const char *pduid = rvalue; + size_t count = 0, duid_index = 0; + Manager *m; + Network *n; + DUIDType *duid_type; + uint16_t *dhcp_duid_type; + size_t *dhcp_duid_len; + uint8_t *dhcp_duid; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(userdata); + + if (ltype == DUID_CONFIG_SOURCE_GLOBAL) { + m = userdata; + duid_type = &m->duid_type; + dhcp_duid_type = &m->dhcp_duid_type; + dhcp_duid_len = &m->dhcp_duid_len; + dhcp_duid = m->dhcp_duid; + } else { + /* DUID_CONFIG_SOURCE_NETWORK */ + n = userdata; + duid_type = &n->duid_type; + dhcp_duid_type = &n->dhcp_duid_type; + dhcp_duid_len = &n->dhcp_duid_len; + dhcp_duid = n->dhcp_duid; + } + + if (*duid_type == _DUID_TYPE_INVALID) + *duid_type = DUID_TYPE_RAW; + + switch (*duid_type) { + case DUID_TYPE_LLT: + /* RawData contains DUID-LLT link-layer address (offset 6) */ + duid_index = 6; + break; + case DUID_TYPE_EN: + /* RawData contains DUID-EN identifier (offset 4) */ + duid_index = 4; + break; + case DUID_TYPE_LL: + /* RawData contains DUID-LL link-layer address (offset 2) */ + duid_index = 2; + break; + case DUID_TYPE_UUID: + /* RawData specifies UUID (offset 0) - fall thru */ + case DUID_TYPE_RAW: + /* First two bytes of RawData is DUID Type - fall thru */ + default: + break; + } + + if (*duid_type != DUID_TYPE_RAW) + *dhcp_duid_type = (uint16_t)(*duid_type); + + /* RawData contains DUID in format " NN:NN:NN... " */ + while (true) { + r = extract_first_word(&pduid, &cbyte, ":", 0); + if (r < 0) { + log_error("Failed to read DUID."); + return -EINVAL; + } + if (r == 0) + break; + if (duid_index >= MAX_DUID_LEN) { + log_error("DUID length exceeds maximum length."); + return -EINVAL; + } + + errno = 0; + byte = strtol(cbyte, &pnext, 16); + if ((errno == ERANGE && (byte == LONG_MAX || byte == LONG_MIN)) + || (errno != 0 && byte == 0) || (cbyte == pnext)) { + log_error("Invalid DUID byte: %s.", cbyte); + return -EINVAL; + } + + /* If DUID_TYPE_RAW, first two bytes hold DHCP DUID type code */ + if ((*duid_type == DUID_TYPE_RAW) && (count < 2)) { + *dhcp_duid_type |= (byte << (8 * (1 - count))); + count++; + continue; + } + + dhcp_duid[duid_index++] = byte; + } + + *dhcp_duid_len = duid_index; + + return 0; +} diff --git a/src/network/networkd-conf.h b/src/network/networkd-conf.h new file mode 100644 index 0000000000..efc370f839 --- /dev/null +++ b/src/network/networkd-conf.h @@ -0,0 +1,36 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Vinay Kulkarni + + 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 "networkd.h" + +typedef enum DuidConfigSource { + DUID_CONFIG_SOURCE_GLOBAL = 0, + DUID_CONFIG_SOURCE_NETWORK, +} DuidConfigSource; + +int manager_parse_config_file(Manager *m); + +const struct ConfigPerfItem* networkd_gperf_lookup(const char *key, unsigned length); + +int config_parse_duid_type(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); +int config_parse_duid_rawdata(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); diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 68998eabf2..0589ebf227 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -625,7 +625,21 @@ int dhcp4_configure(Link *link) { switch (link->network->dhcp_client_identifier) { case DHCP_CLIENT_ID_DUID: - /* Library defaults to this. */ + /* If configured, apply user specified DUID and/or IAID */ + if (link->network->duid_type != _DUID_TYPE_INVALID) + r = sd_dhcp_client_set_iaid_duid(link->dhcp_client, + link->network->iaid, + link->network->dhcp_duid_type, + link->network->dhcp_duid, + link->network->dhcp_duid_len); + else + r = sd_dhcp_client_set_iaid_duid(link->dhcp_client, + link->network->iaid, + link->manager->dhcp_duid_type, + link->manager->dhcp_duid, + link->manager->dhcp_duid_len); + if (r < 0) + return r; break; case DHCP_CLIENT_ID_MAC: r = sd_dhcp_client_set_client_id(link->dhcp_client, diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 5f7a005c36..d4b2fbfc57 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -230,6 +230,23 @@ int dhcp6_configure(Link *link) { if (r < 0) goto error; + r = sd_dhcp6_client_set_iaid(client, link->network->iaid); + if (r < 0) + goto error; + + if (link->network->duid_type != _DUID_TYPE_INVALID) + r = sd_dhcp6_client_set_duid(client, + link->network->dhcp_duid_type, + link->network->dhcp_duid, + link->network->dhcp_duid_len); + else + r = sd_dhcp6_client_set_duid(client, + link->manager->dhcp_duid_type, + link->manager->dhcp_duid, + link->manager->dhcp_duid_len); + if (r < 0) + goto error; + r = sd_dhcp6_client_set_index(client, link->ifindex); if (r < 0) goto error; diff --git a/src/network/networkd-gperf.gperf b/src/network/networkd-gperf.gperf new file mode 100644 index 0000000000..0625fb335b --- /dev/null +++ b/src/network/networkd-gperf.gperf @@ -0,0 +1,18 @@ +%{ +#include +#include "conf-parser.h" +#include "networkd-conf.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name networkd_gperf_hash +%define lookup-function-name networkd_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +DUID.Type, config_parse_duid_type, 0, offsetof(Manager, duid_type) +DUID.RawData, config_parse_duid_rawdata, DUID_CONFIG_SOURCE_GLOBAL, offsetof(Manager, dhcp_duid) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index ff4bd76554..eac8123311 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -2781,6 +2781,21 @@ int link_update(Link *link, sd_netlink_message *m) { ARPHRD_ETHER); if (r < 0) return log_link_warning_errno(link, r, "Could not update MAC address in DHCP client: %m"); + + if (link->network->duid_type != _DUID_TYPE_INVALID) + r = sd_dhcp_client_set_iaid_duid(link->dhcp_client, + link->network->iaid, + link->network->dhcp_duid_type, + link->network->dhcp_duid, + link->network->dhcp_duid_len); + else + r = sd_dhcp_client_set_iaid_duid(link->dhcp_client, + link->network->iaid, + link->manager->dhcp_duid_type, + link->manager->dhcp_duid, + link->manager->dhcp_duid_len); + if (r < 0) + return log_link_warning_errno(link, r, "Could not update DUID/IAID in DHCP client: %m"); } if (link->dhcp6_client) { @@ -2790,6 +2805,24 @@ int link_update(Link *link, sd_netlink_message *m) { ARPHRD_ETHER); if (r < 0) return log_link_warning_errno(link, r, "Could not update MAC address in DHCPv6 client: %m"); + + r = sd_dhcp6_client_set_iaid(link->dhcp6_client, + link->network->iaid); + if (r < 0) + return log_link_warning_errno(link, r, "Could not update DHCPv6 IAID: %m"); + + if (link->network->duid_type != _DUID_TYPE_INVALID) + r = sd_dhcp6_client_set_duid(link->dhcp6_client, + link->network->dhcp_duid_type, + link->network->dhcp_duid, + link->network->dhcp_duid_len); + else + r = sd_dhcp6_client_set_duid(link->dhcp6_client, + link->manager->dhcp_duid_type, + link->manager->dhcp_duid, + link->manager->dhcp_duid_len); + if (r < 0) + return log_link_warning_errno(link, r, "Could not update DHCPv6 DUID: %m"); } } } diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index b8cb7f875d..d355aaa19c 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -1037,6 +1037,8 @@ int manager_new(Manager **ret) { if (r < 0) return r; + m->duid_type = _DUID_TYPE_INVALID; + *ret = m; m = NULL; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index a5d1714293..9793938080 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -2,6 +2,7 @@ #include #include "conf-parser.h" #include "networkd.h" +#include "networkd-conf.h" #include "network-internal.h" %} struct ConfigPerfItem; @@ -26,6 +27,9 @@ Match.KernelCommandLine, config_parse_net_condition, Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(Network, match_arch) Link.MACAddress, config_parse_hwaddr, 0, offsetof(Network, mac) Link.MTUBytes, config_parse_iec_size, 0, offsetof(Network, mtu) +Link.IAID, config_parse_iaid, 0, offsetof(Network, iaid) +DUID.Type, config_parse_duid_type, 0, offsetof(Network, duid_type) +DUID.RawData, config_parse_duid_rawdata, DUID_CONFIG_SOURCE_NETWORK, offsetof(Network, dhcp_duid) Network.Description, config_parse_string, 0, offsetof(Network, description) Network.Bridge, config_parse_netdev, 0, offsetof(Network, bridge) Network.Bond, config_parse_netdev, 0, offsetof(Network, bond) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 491b9a3efa..5946ba18dc 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -131,10 +131,12 @@ static int network_load_one(Manager *manager, const char *filename) { network->ipv6_accept_ra = -1; network->ipv6_dad_transmits = -1; network->ipv6_hop_limit = -1; + network->duid_type = _DUID_TYPE_INVALID; r = config_parse(NULL, filename, file, "Match\0" "Link\0" + "DUID\0" "Network\0" "Address\0" "Route\0" diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 4a13e2b574..5400a8bc9d 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -24,6 +24,7 @@ typedef struct Network Network; +#include "dhcp-identifier.h" #include "networkd-address.h" #include "networkd-fdb.h" #include "networkd-netdev.h" @@ -144,6 +145,13 @@ struct Network { struct ether_addr *mac; unsigned mtu; + uint32_t iaid; + /* Value of Type in [DUID] section */ + DUIDType duid_type; + /* DUID type code - RFC 3315 */ + uint16_t dhcp_duid_type; + size_t dhcp_duid_len; + uint8_t dhcp_duid[MAX_DUID_LEN]; LLDPMode lldp_mode; /* LLDP reception */ bool lldp_emit; /* LLDP transmission */ diff --git a/src/network/networkd.c b/src/network/networkd.c index 3a2615e6fd..c8f81a2ca6 100644 --- a/src/network/networkd.c +++ b/src/network/networkd.c @@ -21,6 +21,7 @@ #include "capability-util.h" #include "networkd.h" +#include "networkd-conf.h" #include "signal-util.h" #include "user-util.h" @@ -89,6 +90,10 @@ int main(int argc, char *argv[]) { goto out; } + r = manager_parse_config_file(m); + if (r < 0) + log_warning_errno(r, "Failed to parse configuration file: %m"); + r = manager_load_config(m); if (r < 0) { log_error_errno(r, "Could not load configuration files: %m"); diff --git a/src/network/networkd.h b/src/network/networkd.h index 6bdd8302a0..72a2438ac8 100644 --- a/src/network/networkd.h +++ b/src/network/networkd.h @@ -31,6 +31,7 @@ typedef struct Manager Manager; +#include "dhcp-identifier.h" #include "networkd-address-pool.h" #include "networkd-link.h" #include "networkd-network.h" @@ -61,6 +62,13 @@ struct Manager { LIST_HEAD(AddressPool, address_pools); usec_t network_dirs_ts_usec; + + /* Value of Type in [DUID] section */ + DUIDType duid_type; + /* DUID type code - RFC 3315 */ + uint16_t dhcp_duid_type; + size_t dhcp_duid_len; + uint8_t dhcp_duid[MAX_DUID_LEN]; }; extern const char* const network_dirs[]; diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index ef45370505..374ff8774e 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -98,6 +98,8 @@ int sd_dhcp_client_set_mac(sd_dhcp_client *client, const uint8_t *addr, size_t addr_len, uint16_t arp_type); int sd_dhcp_client_set_client_id(sd_dhcp_client *client, uint8_t type, const uint8_t *data, size_t data_len); +int sd_dhcp_client_set_iaid_duid(sd_dhcp_client *client, uint32_t iaid, + uint16_t duid_type, uint8_t *duid, size_t duid_len); int sd_dhcp_client_get_client_id(sd_dhcp_client *client, uint8_t *type, const uint8_t **data, size_t *data_len); int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu); diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h index 1bedc941aa..4604cb6382 100644 --- a/src/systemd/sd-dhcp6-client.h +++ b/src/systemd/sd-dhcp6-client.h @@ -85,8 +85,9 @@ int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index); int sd_dhcp6_client_set_local_address(sd_dhcp6_client *client, const struct in6_addr *local_address); int sd_dhcp6_client_set_mac(sd_dhcp6_client *client, const uint8_t *addr, size_t addr_len, uint16_t arp_type); -int sd_dhcp6_client_set_duid(sd_dhcp6_client *client, uint16_t type, uint8_t *duid, - size_t duid_len); +int sd_dhcp6_client_set_duid(sd_dhcp6_client *client, uint16_t duid_type, + uint8_t *duid, size_t duid_len); +int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid); int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, int enabled); int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, int *enabled); int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, -- cgit v1.2.3-54-g00ecf From 23d8b221c0490b1b00044072b329d3ab76d561e3 Mon Sep 17 00:00:00 2001 From: Susant Sahani Date: Thu, 14 Apr 2016 15:26:57 +0530 Subject: networkd: Add support to configure proxy arp support to interfaces (#3020) Fixes: #2889 --- man/systemd.network.xml | 11 +++++++++- src/network/networkd-link.c | 35 ++++++++++++++++++++++++++++++++ src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.c | 1 + src/network/networkd-network.h | 1 + 5 files changed, 48 insertions(+), 1 deletion(-) (limited to 'src/network/networkd-network.h') diff --git a/man/systemd.network.xml b/man/systemd.network.xml index c9ef041004..d7947836e9 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -548,6 +548,15 @@ Defaults to unset. + + ProxyARP= + A boolean. Configures proxy ARP. Proxy ARP is the technique in which one host, + usually a router, answers ARP requests intended for another machine. By "faking" its identity, + the router accepts responsibility for routing packets to the "real" destination. (see RFC 1027. + Defaults to unset. + + Bridge= @@ -844,7 +853,7 @@ global DUID that may be specified in networkd.conf 5. - The configured DHCP DUID should conform to the specification in + The configured DHCP DUID should conform to the specification in RFC 3315, RFC 6355. diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 88b3cbe90a..9059a68fe3 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -165,6 +165,21 @@ static bool link_ipv6_forward_enabled(Link *link) { return link->network->ip_forward & ADDRESS_FAMILY_IPV6; } +static bool link_proxy_arp_enabled(Link *link) { + assert(link); + + if (link->flags & IFF_LOOPBACK) + return false; + + if (!link->network) + return false; + + if (link->network->proxy_arp < 0) + return false; + + return true; +} + static bool link_ipv6_accept_ra_enabled(Link *link) { assert(link); @@ -1039,6 +1054,22 @@ static int link_set_bridge_fdb(Link *const link) { return r; } +static int link_set_proxy_arp(Link *const link) { + const char *p = NULL; + int r; + + if (!link_proxy_arp_enabled(link)) + return 0; + + p = strjoina("/proc/sys/net/ipv4/conf/", link->ifname, "/proxy_arp"); + + r = write_string_file(p, one_zero(link->network->proxy_arp), WRITE_STRING_FILE_VERIFY_ON_FAILURE); + if (r < 0) + log_link_warning_errno(link, r, "Cannot configure proxy ARP for interface: %m"); + + return 0; +} + static int link_set_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { _cleanup_link_unref_ Link *link = userdata; int r; @@ -2167,6 +2198,10 @@ static int link_configure(Link *link) { if (r < 0) return r; + r = link_set_proxy_arp(link); + if (r < 0) + return r; + r = link_set_ipv4_forward(link); if (r < 0) return r; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 9793938080..1da99cd5bc 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -61,6 +61,7 @@ Network.IPv6PrivacyExtensions, config_parse_ipv6_privacy_extensions, Network.IPv6AcceptRouterAdvertisements, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra) Network.IPv6DuplicateAddressDetection, config_parse_int, 0, offsetof(Network, ipv6_dad_transmits) Network.IPv6HopLimit, config_parse_int, 0, offsetof(Network, ipv6_hop_limit) +Network.ProxyARP, config_parse_tristate, 0, offsetof(Network, proxy_arp) Network.BindCarrier, config_parse_strv, 0, offsetof(Network, bind_carrier) Address.Address, config_parse_address, 0, 0 Address.Peer, config_parse_address, 0, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 5946ba18dc..1c7adf5180 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -132,6 +132,7 @@ static int network_load_one(Manager *manager, const char *filename) { network->ipv6_dad_transmits = -1; network->ipv6_hop_limit = -1; network->duid_type = _DUID_TYPE_INVALID; + network->proxy_arp = -1; r = config_parse(NULL, filename, file, "Match\0" diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 5400a8bc9d..3d44113b05 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -139,6 +139,7 @@ struct Network { int ipv6_accept_ra; int ipv6_dad_transmits; int ipv6_hop_limit; + int proxy_arp; union in_addr_union ipv6_token; IPv6PrivacyExtensions ipv6_privacy_extensions; -- cgit v1.2.3-54-g00ecf From 439689c6ec48faba67565562d75701d5736567e7 Mon Sep 17 00:00:00 2001 From: Susant Sahani Date: Thu, 21 Apr 2016 06:04:13 +0530 Subject: networkd: bump MTU to 1280 for interfaces which have IPv6 enabled (#3077) IPv6 protocol requires a minimum MTU of 1280 bytes on the interface. This fixes #3046. Introduce helper link_ipv6_enabled() to figure out whether IPV6 is enabled. Introduce network_has_static_ipv6_addresses() to find out if any static ipv6 address configured. If IPv6 is not configured on any interface that is SLAAC, DHCPv6 and static IPv6 addresses not configured, then IPv6 will be automatically disabled for that interface, that is we write "1" to /proc/sys/net/ipv6/conf//disable_ipv6. --- man/systemd.network.xml | 11 ++++++++++ src/basic/missing.h | 4 ++++ src/network/networkd-link.c | 50 +++++++++++++++++++++++++++++++++++++++++- src/network/networkd-network.c | 13 +++++++++++ src/network/networkd-network.h | 2 ++ 5 files changed, 79 insertions(+), 1 deletion(-) (limited to 'src/network/networkd-network.h') diff --git a/man/systemd.network.xml b/man/systemd.network.xml index d7947836e9..8ae384185d 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -202,6 +202,8 @@ The maximum transmission unit in bytes to set for the device. The usual suffixes K, M, G, are supported and are understood to the base of 1024. + Note that if IPv6 is enabled on the interface, and the MTU is chosen + below 1280 (the minimum MTU for IPv6) it will automatically be increased to this value. @@ -210,6 +212,15 @@ Identity Association Identifier for the interface, a 32-bit unsigned integer. + + + Note that an interface without any static IPv6 addresses configured, and neither + DHCPv6 nor IPv6LL enabled, shall be considered to have no IPv6 support. IPv6 will be + automatically disabled for that interface by writing "1" to + /proc/sys/net/ipv6/conf/ifname/disable_ipv6. + + + diff --git a/src/basic/missing.h b/src/basic/missing.h index 6a49ccd281..6616f0b720 100644 --- a/src/basic/missing.h +++ b/src/basic/missing.h @@ -828,6 +828,10 @@ struct btrfs_ioctl_quota_ctl_args { #define IPV6_UNICAST_IF 76 #endif +#ifndef IPV6_MIN_MTU +#define IPV6_MIN_MTU 1280 +#endif + #ifndef IFF_MULTI_QUEUE #define IFF_MULTI_QUEUE 0x100 #endif diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 9059a68fe3..0fb3aa6c43 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -99,6 +99,15 @@ static bool link_ipv6ll_enabled(Link *link) { return link->network->link_local & ADDRESS_FAMILY_IPV6; } +static bool link_ipv6_enabled(Link *link) { + assert(link); + + if (!socket_ipv6_is_supported()) + return false; + + return link_dhcp6_enabled(link) || link_ipv6ll_enabled(link) || network_has_static_ipv6_addresses(link->network); +} + static bool link_lldp_rx_enabled(Link *link) { assert(link); @@ -218,6 +227,31 @@ static IPv6PrivacyExtensions link_ipv6_privacy_extensions(Link *link) { return link->network->ipv6_privacy_extensions; } +static int link_enable_ipv6(Link *link) { + const char *p = NULL; + bool disabled; + int r; + + if (link->flags & IFF_LOOPBACK) + return 0; + + disabled = !link_ipv6_enabled(link); + + p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/disable_ipv6"); + + r = write_string_file(p, one_zero(disabled), WRITE_STRING_FILE_VERIFY_ON_FAILURE); + if (r < 0) + log_link_warning_errno(link, r, "Cannot %s IPv6 for interface %s: %m", disabled ? "disable" : "enable", link->ifname); + else { + if (disabled) + log_link_info(link, "IPv6 disabled for interface: %m"); + else + log_link_info(link, "IPv6 enabled for interface: %m"); + } + + return 0; +} + void link_update_operstate(Link *link) { LinkOperationalState operstate; assert(link); @@ -1510,7 +1544,21 @@ static int link_up(Link *link) { return log_link_error_errno(link, r, "Could not set MAC address: %m"); } + /* If IPv6 not configured (no static IPv6 address and neither DHCPv6 nor IPv6LL is enabled) + for this interface then disable IPv6 else enable it. */ + (void) link_enable_ipv6(link); + if (link->network->mtu) { + /* IPv6 protocol requires a minimum MTU of IPV6_MTU_MIN(1280) bytes + on the interface. Bump up MTU bytes to IPV6_MTU_MIN. */ + if (link_ipv6_enabled(link) && link->network->mtu < IPV6_MIN_MTU) { + + log_link_warning(link, "Bumping MTU to " STRINGIFY(IPV6_MIN_MTU) ", as " + "IPv6 is requested and requires a minimum MTU of " STRINGIFY(IPV6_MIN_MTU) " bytes: %m"); + + link->network->mtu = IPV6_MIN_MTU; + } + r = sd_netlink_message_append_u32(req, IFLA_MTU, link->network->mtu); if (r < 0) return log_link_error_errno(link, r, "Could not set MTU: %m"); @@ -1520,7 +1568,7 @@ static int link_up(Link *link) { if (r < 0) return log_link_error_errno(link, r, "Could not open IFLA_AF_SPEC container: %m"); - if (socket_ipv6_is_supported()) { + if (link_ipv6_enabled(link)) { /* if the kernel lacks ipv6 support setting IFF_UP fails if any ipv6 options are passed */ r = sd_netlink_message_open_container(req, AF_INET6); if (r < 0) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 1c7adf5180..07f8fb028f 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -399,6 +399,19 @@ int network_apply(Manager *manager, Network *network, Link *link) { return 0; } +bool network_has_static_ipv6_addresses(Network *network) { + Address *address; + + assert(network); + + LIST_FOREACH(addresses, address, network->static_addresses) { + if (address->family == AF_INET6) + return true; + } + + return false; +} + int config_parse_netdev(const char *unit, const char *filename, unsigned line, diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 3d44113b05..15417f4828 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -186,6 +186,8 @@ int network_get_by_name(Manager *manager, const char *name, Network **ret); int network_get(Manager *manager, struct udev_device *device, const char *ifname, const struct ether_addr *mac, Network **ret); int network_apply(Manager *manager, Network *network, Link *link); +bool network_has_static_ipv6_addresses(Network *network); + int config_parse_netdev(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); int config_parse_domains(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); int config_parse_tunnel(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); -- cgit v1.2.3-54-g00ecf From 634f0f983c57e41292dd36c7327e99316ff61aa3 Mon Sep 17 00:00:00 2001 From: Zbigniew Jędrzejewski-Szmek Date: Thu, 28 Apr 2016 22:52:04 -0400 Subject: networkd: rework headers to avoid circular includes Header files were organized in a way where the includer would add various typedefs used by the includee before including it, resulting in a tangled web of dependencies between files. Replace this with the following logic: networkd.h / \ networkd-link.h \ networkd-ipv4ll.h--\__\ networkd-fdb.h \ networkd-network.h netword-netdev-*.h networkd-route.h \ networkd-netdev.h If a pointer to a structure defined in a different header file is needed, use a typedef line instead of including the whole header. --- src/network/networkd-address-pool.h | 4 ++- src/network/networkd-address.h | 4 ++- src/network/networkd-dhcp4.c | 2 +- src/network/networkd-dhcp6.c | 2 +- src/network/networkd-fdb.h | 8 +++-- src/network/networkd-ipv4ll.c | 2 +- src/network/networkd-link.c | 3 +- src/network/networkd-link.h | 15 +++++---- src/network/networkd-lldp-tx.c | 5 ++- src/network/networkd-ndisc.c | 2 +- src/network/networkd-netdev-bond.c | 1 + src/network/networkd-netdev-bond.h | 8 ++--- src/network/networkd-netdev-bridge.c | 1 + src/network/networkd-netdev-bridge.h | 7 ++--- src/network/networkd-netdev-dummy.h | 7 ++--- src/network/networkd-netdev-gperf.gperf | 12 +++++-- src/network/networkd-netdev-ipvlan.h | 7 ++--- src/network/networkd-netdev-macvlan.h | 2 ++ src/network/networkd-netdev-tunnel.h | 17 +++++++--- src/network/networkd-netdev-tuntap.c | 7 +++-- src/network/networkd-netdev-tuntap.h | 2 ++ src/network/networkd-netdev-veth.h | 1 + src/network/networkd-netdev-vlan.h | 1 + src/network/networkd-netdev-vxlan.c | 2 ++ src/network/networkd-netdev-vxlan.h | 1 + src/network/networkd-netdev.h | 55 +++++++-------------------------- src/network/networkd-network.h | 11 ++++--- src/network/networkd-route.h | 1 - src/network/networkd.h | 23 +++++++++----- 29 files changed, 114 insertions(+), 99 deletions(-) (limited to 'src/network/networkd-network.h') diff --git a/src/network/networkd-address-pool.h b/src/network/networkd-address-pool.h index 8e1378ff40..af30decfe0 100644 --- a/src/network/networkd-address-pool.h +++ b/src/network/networkd-address-pool.h @@ -22,7 +22,9 @@ typedef struct AddressPool AddressPool; #include "in-addr-util.h" -#include "networkd.h" +#include "list.h" + +typedef struct Manager Manager; struct AddressPool { Manager *manager; diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h index 338f6eb9a2..3b5285abf0 100644 --- a/src/network/networkd-address.h +++ b/src/network/networkd-address.h @@ -28,10 +28,12 @@ typedef struct Address Address; #include "networkd-link.h" #include "networkd-network.h" -#include "networkd.h" #define CACHE_INFO_INFINITY_LIFE_TIME 0xFFFFFFFFU +typedef struct Network Network; +typedef struct Link Link; + struct Address { Network *network; unsigned section; diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index c5b61abc9e..d31ea4d066 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -24,7 +24,7 @@ #include "dhcp-lease-internal.h" #include "hostname-util.h" #include "network-internal.h" -#include "networkd-link.h" +#include "networkd.h" static int dhcp4_route_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index d4b2fbfc57..b9c4b8962c 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -23,7 +23,7 @@ #include "sd-dhcp6-client.h" #include "network-internal.h" -#include "networkd-link.h" +#include "networkd.h" static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link); diff --git a/src/network/networkd-fdb.h b/src/network/networkd-fdb.h index 89b3e29405..84410714f5 100644 --- a/src/network/networkd-fdb.h +++ b/src/network/networkd-fdb.h @@ -19,10 +19,12 @@ along with systemd; If not, see . ***/ -typedef struct FdbEntry FdbEntry; +#include "list.h" +#include "macro.h" -#include "networkd-network.h" -#include "networkd.h" +typedef struct Network Network; +typedef struct FdbEntry FdbEntry; +typedef struct Link Link; struct FdbEntry { Network *network; diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c index e05fd3eea7..35c9b06473 100644 --- a/src/network/networkd-ipv4ll.c +++ b/src/network/networkd-ipv4ll.c @@ -21,7 +21,7 @@ #include #include "network-internal.h" -#include "networkd-link.h" +#include "networkd.h" static int ipv4ll_address_lost(Link *link) { _cleanup_address_free_ Address *address = NULL; diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 5fc513bfda..a88e4c34ed 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -28,9 +28,8 @@ #include "fileio.h" #include "netlink-util.h" #include "network-internal.h" -#include "networkd-link.h" +#include "networkd.h" #include "networkd-lldp-tx.h" -#include "networkd-netdev.h" #include "set.h" #include "socket-util.h" #include "stdio-util.h" diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index f2a64ca9b5..86139be557 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -21,14 +21,17 @@ #include +#include "sd-bus.h" #include "sd-dhcp-client.h" #include "sd-dhcp-server.h" #include "sd-dhcp6-client.h" #include "sd-ipv4ll.h" #include "sd-lldp.h" #include "sd-ndisc.h" +#include "sd-netlink.h" -typedef struct Link Link; +#include "list.h" +#include "set.h" typedef enum LinkState { LINK_STATE_PENDING, @@ -54,11 +57,11 @@ typedef enum LinkOperationalState { _LINK_OPERSTATE_INVALID = -1 } LinkOperationalState; -#include "networkd-address.h" -#include "networkd-network.h" -#include "networkd.h" +typedef struct Manager Manager; +typedef struct Network Network; +typedef struct Address Address; -struct Link { +typedef struct Link { Manager *manager; int n_ref; @@ -122,7 +125,7 @@ struct Link { Hashmap *bound_by_links; Hashmap *bound_to_links; -}; +} Link; Link *link_unref(Link *link); Link *link_ref(Link *link); diff --git a/src/network/networkd-lldp-tx.c b/src/network/networkd-lldp-tx.c index 6bde04bc32..03b694c3f1 100644 --- a/src/network/networkd-lldp-tx.c +++ b/src/network/networkd-lldp-tx.c @@ -21,15 +21,18 @@ #include #include +#include "alloc-util.h" #include "fd-util.h" #include "fileio.h" #include "hostname-util.h" -#include "networkd-lldp-tx.h" #include "random-util.h" #include "socket-util.h" #include "string-util.h" #include "unaligned.h" +#include "networkd.h" +#include "networkd-lldp-tx.h" + #define LLDP_MULTICAST_ADDR { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e } /* The LLDP spec calls this "txFastInit", see 9.2.5.19 */ diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 4577292e44..b22c58bfe5 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -24,7 +24,7 @@ #include "sd-ndisc.h" -#include "networkd-link.h" +#include "networkd.h" static int ndisc_netlink_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { _cleanup_link_unref_ Link *link = userdata; diff --git a/src/network/networkd-netdev-bond.c b/src/network/networkd-netdev-bond.c index 6b9cbcded6..7913b0088e 100644 --- a/src/network/networkd-netdev-bond.c +++ b/src/network/networkd-netdev-bond.c @@ -25,6 +25,7 @@ #include "alloc-util.h" #include "conf-parser.h" +#include "extract-word.h" #include "missing.h" #include "networkd-netdev-bond.h" #include "string-table.h" diff --git a/src/network/networkd-netdev-bond.h b/src/network/networkd-netdev-bond.h index cb6baea24f..b941edb344 100644 --- a/src/network/networkd-netdev-bond.h +++ b/src/network/networkd-netdev-bond.h @@ -20,8 +20,7 @@ ***/ #include "in-addr-util.h" - -typedef struct Bond Bond; +#include "list.h" #include "networkd-netdev.h" @@ -106,7 +105,7 @@ typedef struct ArpIpTarget { LIST_FIELDS(struct ArpIpTarget, arp_ip_target); } ArpIpTarget; -struct Bond { +typedef struct Bond { NetDev meta; BondMode mode; @@ -133,8 +132,9 @@ struct Bond { int n_arp_ip_targets; ArpIpTarget *arp_ip_targets; -}; +} Bond; +DEFINE_NETDEV_CAST(BOND, Bond); extern const NetDevVTable bond_vtable; const char *bond_mode_to_string(BondMode d) _const_; diff --git a/src/network/networkd-netdev-bridge.c b/src/network/networkd-netdev-bridge.c index 3f91b2eaea..3e44dd7960 100644 --- a/src/network/networkd-netdev-bridge.c +++ b/src/network/networkd-netdev-bridge.c @@ -22,6 +22,7 @@ #include "missing.h" #include "netlink-util.h" +#include "networkd.h" #include "networkd-netdev-bridge.h" /* callback for brige netdev's parameter set */ diff --git a/src/network/networkd-netdev-bridge.h b/src/network/networkd-netdev-bridge.h index 3f6f1d0502..b921439f02 100644 --- a/src/network/networkd-netdev-bridge.h +++ b/src/network/networkd-netdev-bridge.h @@ -19,11 +19,9 @@ along with systemd; If not, see . ***/ -typedef struct Bridge Bridge; - #include "networkd-netdev.h" -struct Bridge { +typedef struct Bridge { NetDev meta; int mcast_querier; @@ -31,6 +29,7 @@ struct Bridge { usec_t forward_delay; usec_t hello_time; usec_t max_age; -}; +} Bridge; +DEFINE_NETDEV_CAST(BRIDGE, Bridge); extern const NetDevVTable bridge_vtable; diff --git a/src/network/networkd-netdev-dummy.h b/src/network/networkd-netdev-dummy.h index 42da62ebe4..efe302267e 100644 --- a/src/network/networkd-netdev-dummy.h +++ b/src/network/networkd-netdev-dummy.h @@ -19,12 +19,11 @@ along with systemd; If not, see . ***/ -typedef struct Dummy Dummy; - #include "networkd-netdev.h" -struct Dummy { +typedef struct Dummy { NetDev meta; -}; +} Dummy; +DEFINE_NETDEV_CAST(DUMMY, Dummy); extern const NetDevVTable dummy_vtable; diff --git a/src/network/networkd-netdev-gperf.gperf b/src/network/networkd-netdev-gperf.gperf index 15a787a9e3..1ebd0fdf20 100644 --- a/src/network/networkd-netdev-gperf.gperf +++ b/src/network/networkd-netdev-gperf.gperf @@ -1,11 +1,17 @@ %{ #include #include "conf-parser.h" -#include "networkd-netdev.h" -#include "networkd-netdev-tunnel.h" +#include "network-internal.h" #include "networkd-netdev-bond.h" +#include "networkd-netdev-ipvlan.h" #include "networkd-netdev-macvlan.h" -#include "network-internal.h" +#include "networkd-netdev-tunnel.h" +#include "networkd-netdev-tuntap.h" +#include "networkd-netdev-veth.h" +#include "networkd-netdev-vlan.h" +#include "networkd-netdev-vxlan.h" +#include "networkd-netdev-bridge.h" +#include "networkd-netdev.h" %} struct ConfigPerfItem; %null_strings diff --git a/src/network/networkd-netdev-ipvlan.h b/src/network/networkd-netdev-ipvlan.h index 4bd0b67866..10d4079844 100644 --- a/src/network/networkd-netdev-ipvlan.h +++ b/src/network/networkd-netdev-ipvlan.h @@ -19,8 +19,6 @@ along with systemd; If not, see . ***/ -typedef struct IPVlan IPVlan; - #include "missing.h" #include "networkd-netdev.h" @@ -31,12 +29,13 @@ typedef enum IPVlanMode { _NETDEV_IPVLAN_MODE_INVALID = -1 } IPVlanMode; -struct IPVlan { +typedef struct IPVlan { NetDev meta; IPVlanMode mode; -}; +} IPVlan; +DEFINE_NETDEV_CAST(IPVLAN, IPVlan); extern const NetDevVTable ipvlan_vtable; const char *ipvlan_mode_to_string(IPVlanMode d) _const_; diff --git a/src/network/networkd-netdev-macvlan.h b/src/network/networkd-netdev-macvlan.h index 622ef9ef53..3663f4f051 100644 --- a/src/network/networkd-netdev-macvlan.h +++ b/src/network/networkd-netdev-macvlan.h @@ -38,6 +38,8 @@ struct MacVlan { MacVlanMode mode; }; +DEFINE_NETDEV_CAST(MACVLAN, MacVlan); +DEFINE_NETDEV_CAST(MACVTAP, MacVlan); extern const NetDevVTable macvlan_vtable; extern const NetDevVTable macvtap_vtable; diff --git a/src/network/networkd-netdev-tunnel.h b/src/network/networkd-netdev-tunnel.h index 0d41f80a3c..7d31e7b687 100644 --- a/src/network/networkd-netdev-tunnel.h +++ b/src/network/networkd-netdev-tunnel.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -typedef struct Tunnel Tunnel; +#include "in-addr-util.h" #include "networkd-netdev.h" @@ -37,7 +37,7 @@ typedef enum IPv6FlowLabel { _NETDEV_IPV6_FLOWLABEL_INVALID = -1, } IPv6FlowLabel; -struct Tunnel { +typedef struct Tunnel { NetDev meta; uint8_t encap_limit; @@ -56,8 +56,17 @@ struct Tunnel { bool pmtudisc; bool copy_dscp; -}; - +} Tunnel; + +DEFINE_NETDEV_CAST(IPIP, Tunnel); +DEFINE_NETDEV_CAST(GRE, Tunnel); +DEFINE_NETDEV_CAST(GRETAP, Tunnel); +DEFINE_NETDEV_CAST(IP6GRE, Tunnel); +DEFINE_NETDEV_CAST(IP6GRETAP, Tunnel); +DEFINE_NETDEV_CAST(SIT, Tunnel); +DEFINE_NETDEV_CAST(VTI, Tunnel); +DEFINE_NETDEV_CAST(VTI6, Tunnel); +DEFINE_NETDEV_CAST(IP6TNL, Tunnel); extern const NetDevVTable ipip_vtable; extern const NetDevVTable sit_vtable; extern const NetDevVTable vti_vtable; diff --git a/src/network/networkd-netdev-tuntap.c b/src/network/networkd-netdev-tuntap.c index 32917fe6d5..088a4d8d32 100644 --- a/src/network/networkd-netdev-tuntap.c +++ b/src/network/networkd-netdev-tuntap.c @@ -17,10 +17,13 @@ along with systemd; If not, see . ***/ -#include -#include +#include #include +#include #include +#include +#include +#include #include "alloc-util.h" #include "fd-util.h" diff --git a/src/network/networkd-netdev-tuntap.h b/src/network/networkd-netdev-tuntap.h index cbb7ee05a6..120f00a353 100644 --- a/src/network/networkd-netdev-tuntap.h +++ b/src/network/networkd-netdev-tuntap.h @@ -34,5 +34,7 @@ struct TunTap { bool vnet_hdr; }; +DEFINE_NETDEV_CAST(TUN, TunTap); +DEFINE_NETDEV_CAST(TAP, TunTap); extern const NetDevVTable tun_vtable; extern const NetDevVTable tap_vtable; diff --git a/src/network/networkd-netdev-veth.h b/src/network/networkd-netdev-veth.h index ae5785783c..e69bfbc8f0 100644 --- a/src/network/networkd-netdev-veth.h +++ b/src/network/networkd-netdev-veth.h @@ -30,4 +30,5 @@ struct Veth { struct ether_addr *mac_peer; }; +DEFINE_NETDEV_CAST(VETH, Veth); extern const NetDevVTable veth_vtable; diff --git a/src/network/networkd-netdev-vlan.h b/src/network/networkd-netdev-vlan.h index 1de6a1cc36..73aacf4a0f 100644 --- a/src/network/networkd-netdev-vlan.h +++ b/src/network/networkd-netdev-vlan.h @@ -31,4 +31,5 @@ struct VLan { uint64_t id; }; +DEFINE_NETDEV_CAST(VLAN, VLan); extern const NetDevVTable vlan_vtable; diff --git a/src/network/networkd-netdev-vxlan.c b/src/network/networkd-netdev-vxlan.c index dabbd97c87..724f9861be 100644 --- a/src/network/networkd-netdev-vxlan.c +++ b/src/network/networkd-netdev-vxlan.c @@ -23,8 +23,10 @@ #include "conf-parser.h" #include "alloc-util.h" +#include "extract-word.h" #include "parse-util.h" #include "missing.h" + #include "networkd-link.h" #include "networkd-netdev-vxlan.h" diff --git a/src/network/networkd-netdev-vxlan.h b/src/network/networkd-netdev-vxlan.h index a4bb44635a..4614c66fd1 100644 --- a/src/network/networkd-netdev-vxlan.h +++ b/src/network/networkd-netdev-vxlan.h @@ -55,6 +55,7 @@ struct VxLan { struct ifla_vxlan_port_range port_range; }; +DEFINE_NETDEV_CAST(VXLAN, VxLan); extern const NetDevVTable vxlan_vtable; int config_parse_vxlan_group_address(const char *unit, diff --git a/src/network/networkd-netdev.h b/src/network/networkd-netdev.h index 7ea825fcb4..20244c0309 100644 --- a/src/network/networkd-netdev.h +++ b/src/network/networkd-netdev.h @@ -19,15 +19,13 @@ along with systemd; If not, see . ***/ -#include "list.h" - -typedef struct NetDev NetDev; -typedef struct NetDevVTable NetDevVTable; +#include "sd-netlink.h" -#include "networkd-link.h" -#include "networkd.h" +#include "list.h" +#include "time-util.h" typedef struct netdev_join_callback netdev_join_callback; +typedef struct Link Link; struct netdev_join_callback { sd_netlink_message_handler_t callback; @@ -78,7 +76,10 @@ typedef enum NetDevCreateType { _NETDEV_CREATE_INVALID = -1, } NetDevCreateType; -struct NetDev { +typedef struct Manager Manager; +typedef struct Condition Condition; + +typedef struct NetDev { Manager *manager; int n_ref; @@ -99,20 +100,9 @@ struct NetDev { int ifindex; LIST_HEAD(netdev_join_callback, callbacks); -}; +} NetDev; -#include "networkd-netdev-bond.h" -#include "networkd-netdev-bridge.h" -#include "networkd-netdev-dummy.h" -#include "networkd-netdev-ipvlan.h" -#include "networkd-netdev-macvlan.h" -#include "networkd-netdev-tunnel.h" -#include "networkd-netdev-tuntap.h" -#include "networkd-netdev-veth.h" -#include "networkd-netdev-vlan.h" -#include "networkd-netdev-vxlan.h" - -struct NetDevVTable { +typedef struct NetDevVTable { /* How much memory does an object of this unit type need */ size_t object_size; @@ -144,14 +134,14 @@ struct NetDevVTable { /* verify that compulsory configuration options were specified */ int (*config_verify)(NetDev *netdev, const char *filename); -}; +} NetDevVTable; extern const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX]; #define NETDEV_VTABLE(n) netdev_vtable[(n)->kind] /* For casting a netdev into the various netdev kinds */ -#define DEFINE_CAST(UPPERCASE, MixedCase) \ +#define DEFINE_NETDEV_CAST(UPPERCASE, MixedCase) \ static inline MixedCase* UPPERCASE(NetDev *n) { \ if (_unlikely_(!n || n->kind != NETDEV_KIND_##UPPERCASE)) \ return NULL; \ @@ -162,27 +152,6 @@ extern const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX]; /* For casting the various netdev kinds into a netdev */ #define NETDEV(n) (&(n)->meta) -DEFINE_CAST(BRIDGE, Bridge); -DEFINE_CAST(BOND, Bond); -DEFINE_CAST(VLAN, VLan); -DEFINE_CAST(MACVLAN, MacVlan); -DEFINE_CAST(MACVTAP, MacVlan); -DEFINE_CAST(IPVLAN, IPVlan); -DEFINE_CAST(VXLAN, VxLan); -DEFINE_CAST(IPIP, Tunnel); -DEFINE_CAST(GRE, Tunnel); -DEFINE_CAST(GRETAP, Tunnel); -DEFINE_CAST(IP6GRE, Tunnel); -DEFINE_CAST(IP6GRETAP, Tunnel); -DEFINE_CAST(SIT, Tunnel); -DEFINE_CAST(VTI, Tunnel); -DEFINE_CAST(VTI6, Tunnel); -DEFINE_CAST(IP6TNL, Tunnel); -DEFINE_CAST(VETH, Veth); -DEFINE_CAST(DUMMY, Dummy); -DEFINE_CAST(TUN, TunTap); -DEFINE_CAST(TAP, TunTap); - int netdev_load(Manager *manager); void netdev_drop(NetDev *netdev); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 15417f4828..9b8096f9ae 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -19,18 +19,19 @@ along with systemd; If not, see . ***/ +#include "sd-bus.h" +#include "udev.h" + #include "condition.h" +#include "dhcp-identifier.h" +#include "hashmap.h" #include "resolve-util.h" -typedef struct Network Network; - -#include "dhcp-identifier.h" #include "networkd-address.h" #include "networkd-fdb.h" #include "networkd-netdev.h" #include "networkd-route.h" #include "networkd-util.h" -#include "networkd.h" #define DHCP_ROUTE_METRIC 1024 #define IPV4LL_ROUTE_METRIC 2048 @@ -67,6 +68,8 @@ typedef enum LLDPMode { _LLDP_MODE_INVALID = -1, } LLDPMode; +typedef struct Manager Manager; + struct Network { Manager *manager; diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h index a4a4bf2653..59843162f0 100644 --- a/src/network/networkd-route.h +++ b/src/network/networkd-route.h @@ -22,7 +22,6 @@ typedef struct Route Route; #include "networkd-network.h" -#include "networkd.h" struct Route { Network *network; diff --git a/src/network/networkd.h b/src/network/networkd.h index 72a2438ac8..39826a440d 100644 --- a/src/network/networkd.h +++ b/src/network/networkd.h @@ -24,19 +24,30 @@ #include "sd-bus.h" #include "sd-event.h" #include "sd-netlink.h" +#include "udev.h" +#include "dhcp-identifier.h" #include "hashmap.h" #include "list.h" -#include "udev.h" -typedef struct Manager Manager; - -#include "dhcp-identifier.h" #include "networkd-address-pool.h" #include "networkd-link.h" +#include "networkd-netdev-bond.h" +#include "networkd-netdev-bridge.h" +#include "networkd-netdev-dummy.h" +#include "networkd-netdev-ipvlan.h" +#include "networkd-netdev-macvlan.h" +#include "networkd-netdev-tunnel.h" +#include "networkd-netdev-tuntap.h" +#include "networkd-netdev-veth.h" +#include "networkd-netdev-vlan.h" +#include "networkd-netdev-vlan.h" +#include "networkd-netdev-vxlan.h" #include "networkd-network.h" #include "networkd-util.h" +extern const char* const network_dirs[]; + struct Manager { sd_netlink *rtnl; sd_event *event; @@ -71,10 +82,6 @@ struct Manager { uint8_t dhcp_duid[MAX_DUID_LEN]; }; -extern const char* const network_dirs[]; - -/* Manager */ - extern const sd_bus_vtable manager_vtable[]; int manager_new(Manager **ret); -- cgit v1.2.3-54-g00ecf From 8341a5c381a72d504768fe296f8e30a324eeff77 Mon Sep 17 00:00:00 2001 From: Zbigniew Jędrzejewski-Szmek Date: Thu, 28 Apr 2016 23:23:45 -0400 Subject: networkd: rework duid_{type,duid_type,duid,duid_len} setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Separate fields are replaced with a struct. Second second duid type field is removed. The first field was used to carry the result of DUIDType= configuration, and the second was either a copy of this, or contained the type extracted from DuidRawData. The semantics are changed so that the type specified in DUIDType is always used. DUIDRawData= no longer overrides the type setting. The networkd code is now more constrained than the sd-dhcp code: DUIDRawData cannot have 0 length, length 0 is treated the same as unsetting. Likewise, it is not possible to set a DUIDType=0. If it ever becomes necessary to set type=0 or a zero-length duid, the code can be changed to support that. Nevertheless, I think that's unlikely. This addresses #3127 § 1 and 3. v2: - rename DUID.duid, DUID.duid_len to DUID.raw_data, DUID.raw_data_len --- src/network/networkd-conf.c | 87 +++++--------------------------- src/network/networkd-conf.h | 29 ++++++++--- src/network/networkd-dhcp4.c | 24 ++++----- src/network/networkd-dhcp6.c | 16 +++--- src/network/networkd-gperf.gperf | 4 +- src/network/networkd-link.c | 35 +++++-------- src/network/networkd-manager.c | 2 +- src/network/networkd-network-gperf.gperf | 4 +- src/network/networkd-network.c | 2 +- src/network/networkd-network.h | 15 +++--- src/network/networkd.h | 14 ++--- 11 files changed, 88 insertions(+), 144 deletions(-) (limited to 'src/network/networkd-network.h') diff --git a/src/network/networkd-conf.c b/src/network/networkd-conf.c index 70f0121d6d..1b2047f8f4 100644 --- a/src/network/networkd-conf.c +++ b/src/network/networkd-conf.c @@ -41,7 +41,7 @@ static const char* const duid_type_table[_DUID_TYPE_MAX] = { [DUID_TYPE_LLT] = "link-layer-time", [DUID_TYPE_EN] = "vendor", [DUID_TYPE_LL] = "link-layer", - [DUID_TYPE_UUID] = "uuid" + [DUID_TYPE_UUID] = "uuid", }; DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(duid_type, DUIDType); DEFINE_CONFIG_PARSE_ENUM(config_parse_duid_type, duid_type, DUIDType, "Failed to parse DUID type"); @@ -58,69 +58,29 @@ int config_parse_duid_rawdata( void *data, void *userdata) { - int r; - char *cbyte; - const char *pduid = rvalue; - Manager *m = userdata; - Network *n = userdata; - DUIDType duidtype; - uint16_t dhcp_duid_type = 0; - uint8_t dhcp_duid[MAX_DUID_LEN]; - size_t len, count = 0, duid_start_offset = 0, dhcp_duid_len = 0; + DUID *ret = data; + uint8_t raw_data[MAX_DUID_LEN]; + unsigned count = 0; assert(filename); assert(lvalue); assert(rvalue); - assert(userdata); + assert(ret); - duidtype = (ltype == DUID_CONFIG_SOURCE_GLOBAL) ? m->duid_type : n->duid_type; - - if (duidtype == _DUID_TYPE_INVALID) - duidtype = DUID_TYPE_RAW; - - switch (duidtype) { - - case DUID_TYPE_LLT: - /* RawData contains DUID-LLT link-layer address (offset 6) */ - duid_start_offset = 6; - break; - - case DUID_TYPE_EN: - /* RawData contains DUID-EN identifier (offset 4) */ - duid_start_offset = 4; - break; - - case DUID_TYPE_LL: - /* RawData contains DUID-LL link-layer address (offset 2) */ - duid_start_offset = 2; - break; - - case DUID_TYPE_UUID: - /* RawData specifies UUID (offset 0) - fall thru */ - - case DUID_TYPE_RAW: - /* First two bytes of RawData is DUID Type - fall thru */ - - default: - break; - } - - if (duidtype != DUID_TYPE_RAW) - dhcp_duid_type = (uint16_t) duidtype; - - /* RawData contains DUID in format " NN:NN:NN... " */ + /* RawData contains DUID in format "NN:NN:NN..." */ for (;;) { - int n1, n2; + int n1, n2, len, r; uint32_t byte; + char *cbyte; - r = extract_first_word(&pduid, &cbyte, ":", 0); + r = extract_first_word(&rvalue, &cbyte, ":", 0); if (r < 0) { log_syntax(unit, LOG_ERR, filename, line, r, "Failed to read DUID, ignoring assignment: %s.", rvalue); return 0; } if (r == 0) break; - if (duid_start_offset + dhcp_duid_len >= MAX_DUID_LEN) { + if (count >= MAX_DUID_LEN) { log_syntax(unit, LOG_ERR, filename, line, 0, "Max DUID length exceeded, ignoring assignment: %s.", rvalue); return 0; } @@ -142,30 +102,11 @@ int config_parse_duid_rawdata( } byte = ((uint8_t) n1 << (4 * (len-1))) | (uint8_t) n2; - - /* If DUID_TYPE_RAW, first two bytes hold DHCP DUID type code */ - if (duidtype == DUID_TYPE_RAW && count < 2) { - dhcp_duid_type |= (byte << (8 * (1 - count))); - count++; - continue; - } - - dhcp_duid[duid_start_offset + dhcp_duid_len] = byte; - dhcp_duid_len++; - } - - if (ltype == DUID_CONFIG_SOURCE_GLOBAL) { - m->duid_type = duidtype; - m->dhcp_duid_type = dhcp_duid_type; - m->dhcp_duid_len = dhcp_duid_len; - memcpy(&m->dhcp_duid[duid_start_offset], dhcp_duid, dhcp_duid_len); - } else { - /* DUID_CONFIG_SOURCE_NETWORK */ - n->duid_type = duidtype; - n->dhcp_duid_type = dhcp_duid_type; - n->dhcp_duid_len = dhcp_duid_len; - memcpy(&n->dhcp_duid[duid_start_offset], dhcp_duid, dhcp_duid_len); + raw_data[count++] = byte; } + assert_cc(sizeof(raw_data) == sizeof(ret->raw_data)); + memcpy(ret->raw_data, raw_data, count); + ret->raw_data_len = count; return 0; } diff --git a/src/network/networkd-conf.h b/src/network/networkd-conf.h index 671e656d7b..c7bfb42a72 100644 --- a/src/network/networkd-conf.h +++ b/src/network/networkd-conf.h @@ -21,14 +21,29 @@ #include "networkd.h" -typedef enum DuidConfigSource { - DUID_CONFIG_SOURCE_GLOBAL = 0, - DUID_CONFIG_SOURCE_NETWORK, -} DuidConfigSource; - int manager_parse_config_file(Manager *m); const struct ConfigPerfItem* networkd_gperf_lookup(const char *key, unsigned length); -int config_parse_duid_type(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); -int config_parse_duid_rawdata(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); +int config_parse_duid_type( + 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); +int config_parse_duid_rawdata( + 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); diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index d31ea4d066..89926e8a7e 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -628,28 +628,24 @@ int dhcp4_configure(Link *link) { } switch (link->network->dhcp_client_identifier) { - case DHCP_CLIENT_ID_DUID: + case DHCP_CLIENT_ID_DUID: { /* If configured, apply user specified DUID and/or IAID */ - if (link->network->duid_type != _DUID_TYPE_INVALID) - r = sd_dhcp_client_set_iaid_duid(link->dhcp_client, - link->network->iaid, - link->network->dhcp_duid_type, - link->network->dhcp_duid, - link->network->dhcp_duid_len); - else - r = sd_dhcp_client_set_iaid_duid(link->dhcp_client, - link->network->iaid, - link->manager->dhcp_duid_type, - link->manager->dhcp_duid, - link->manager->dhcp_duid_len); + const DUID *duid = link_duid(link); + + r = sd_dhcp_client_set_iaid_duid(link->dhcp_client, + link->network->iaid, + duid->type, + duid->raw_data_len > 0 ? duid->raw_data : NULL, + duid->raw_data_len); if (r < 0) return r; break; + } case DHCP_CLIENT_ID_MAC: r = sd_dhcp_client_set_client_id(link->dhcp_client, ARPHRD_ETHER, (const uint8_t *) &link->mac, - sizeof (link->mac)); + sizeof(link->mac)); if (r < 0) return r; break; diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index b9c4b8962c..ccca4e9522 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -206,6 +206,7 @@ int dhcp6_request_address(Link *link) { int dhcp6_configure(Link *link) { sd_dhcp6_client *client = NULL; int r; + const DUID *duid; assert(link); @@ -234,16 +235,11 @@ int dhcp6_configure(Link *link) { if (r < 0) goto error; - if (link->network->duid_type != _DUID_TYPE_INVALID) - r = sd_dhcp6_client_set_duid(client, - link->network->dhcp_duid_type, - link->network->dhcp_duid, - link->network->dhcp_duid_len); - else - r = sd_dhcp6_client_set_duid(client, - link->manager->dhcp_duid_type, - link->manager->dhcp_duid, - link->manager->dhcp_duid_len); + duid = link_duid(link); + r = sd_dhcp6_client_set_duid(client, + duid->type, + duid->raw_data_len > 0 ? duid->raw_data : NULL, + duid->raw_data_len); if (r < 0) goto error; diff --git a/src/network/networkd-gperf.gperf b/src/network/networkd-gperf.gperf index afc71b4cb8..3fdfe74955 100644 --- a/src/network/networkd-gperf.gperf +++ b/src/network/networkd-gperf.gperf @@ -14,5 +14,5 @@ struct ConfigPerfItem; %struct-type %includes %% -DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Manager, duid_type) -DHCP.DUIDRawData, config_parse_duid_rawdata, DUID_CONFIG_SOURCE_GLOBAL, offsetof(Manager, dhcp_duid) +DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Manager, duid.type) +DHCP.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Manager, duid) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index a88e4c34ed..c646af1f1a 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -2879,6 +2879,8 @@ int link_update(Link *link, sd_netlink_message *m) { } if (link->dhcp_client) { + const DUID *duid = link_duid(link); + r = sd_dhcp_client_set_mac(link->dhcp_client, (const uint8_t *) &link->mac, sizeof (link->mac), @@ -2886,23 +2888,18 @@ int link_update(Link *link, sd_netlink_message *m) { if (r < 0) return log_link_warning_errno(link, r, "Could not update MAC address in DHCP client: %m"); - if (link->network->duid_type != _DUID_TYPE_INVALID) - r = sd_dhcp_client_set_iaid_duid(link->dhcp_client, - link->network->iaid, - link->network->dhcp_duid_type, - link->network->dhcp_duid, - link->network->dhcp_duid_len); - else - r = sd_dhcp_client_set_iaid_duid(link->dhcp_client, - link->network->iaid, - link->manager->dhcp_duid_type, - link->manager->dhcp_duid, - link->manager->dhcp_duid_len); + r = sd_dhcp_client_set_iaid_duid(link->dhcp_client, + link->network->iaid, + duid->type, + duid->raw_data_len > 0 ? duid->raw_data : NULL, + duid->raw_data_len); if (r < 0) return log_link_warning_errno(link, r, "Could not update DUID/IAID in DHCP client: %m"); } if (link->dhcp6_client) { + const DUID* duid = link_duid(link); + r = sd_dhcp6_client_set_mac(link->dhcp6_client, (const uint8_t *) &link->mac, sizeof (link->mac), @@ -2915,16 +2912,10 @@ int link_update(Link *link, sd_netlink_message *m) { if (r < 0) return log_link_warning_errno(link, r, "Could not update DHCPv6 IAID: %m"); - if (link->network->duid_type != _DUID_TYPE_INVALID) - r = sd_dhcp6_client_set_duid(link->dhcp6_client, - link->network->dhcp_duid_type, - link->network->dhcp_duid, - link->network->dhcp_duid_len); - else - r = sd_dhcp6_client_set_duid(link->dhcp6_client, - link->manager->dhcp_duid_type, - link->manager->dhcp_duid, - link->manager->dhcp_duid_len); + r = sd_dhcp6_client_set_duid(link->dhcp6_client, + duid->type, + duid->raw_data_len > 0 ? duid->raw_data : NULL, + duid->raw_data_len); if (r < 0) return log_link_warning_errno(link, r, "Could not update DHCPv6 DUID: %m"); } diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 0cb5781c71..5dcd4df536 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -1037,7 +1037,7 @@ int manager_new(Manager **ret) { if (r < 0) return r; - m->duid_type = DUID_TYPE_EN; + m->duid.type = DUID_TYPE_EN; *ret = m; m = NULL; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 654d6a0316..6bf57cdf99 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -83,8 +83,8 @@ DHCP.Hostname, config_parse_hostname, DHCP.RequestBroadcast, config_parse_bool, 0, offsetof(Network, dhcp_broadcast) DHCP.CriticalConnection, config_parse_bool, 0, offsetof(Network, dhcp_critical) DHCP.VendorClassIdentifier, config_parse_string, 0, offsetof(Network, dhcp_vendor_class_identifier) -DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Network, duid_type) -DHCP.DUIDRawData, config_parse_duid_rawdata, DUID_CONFIG_SOURCE_NETWORK, offsetof(Network, dhcp_duid) +DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Network, duid.type) +DHCP.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, duid) DHCP.RouteMetric, config_parse_unsigned, 0, offsetof(Network, dhcp_route_metric) DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone) DHCPServer.MaxLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_max_lease_time_usec) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 2ebcdfa744..206c270e50 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -131,7 +131,7 @@ static int network_load_one(Manager *manager, const char *filename) { network->ipv6_accept_ra = -1; network->ipv6_dad_transmits = -1; network->ipv6_hop_limit = -1; - network->duid_type = _DUID_TYPE_INVALID; + network->duid.type = _DUID_TYPE_INVALID; network->proxy_arp = -1; r = config_parse(NULL, filename, file, diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 9b8096f9ae..ff2414efdd 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -68,6 +68,14 @@ typedef enum LLDPMode { _LLDP_MODE_INVALID = -1, } LLDPMode; +typedef struct DUID { + /* Value of Type in [DHCP] section */ + DUIDType type; + + uint8_t raw_data_len; + uint8_t raw_data[MAX_DUID_LEN]; +} DUID; + typedef struct Manager Manager; struct Network { @@ -150,12 +158,7 @@ struct Network { struct ether_addr *mac; unsigned mtu; uint32_t iaid; - /* Value of Type in [DUID] section */ - DUIDType duid_type; - /* DUID type code - RFC 3315 */ - uint16_t dhcp_duid_type; - size_t dhcp_duid_len; - uint8_t dhcp_duid[MAX_DUID_LEN]; + DUID duid; LLDPMode lldp_mode; /* LLDP reception */ bool lldp_emit; /* LLDP transmission */ diff --git a/src/network/networkd.h b/src/network/networkd.h index 39826a440d..26d9e7d6e0 100644 --- a/src/network/networkd.h +++ b/src/network/networkd.h @@ -74,14 +74,16 @@ struct Manager { usec_t network_dirs_ts_usec; - /* Value of Type in [DUID] section */ - DUIDType duid_type; - /* DUID type code - RFC 3315 */ - uint16_t dhcp_duid_type; - size_t dhcp_duid_len; - uint8_t dhcp_duid[MAX_DUID_LEN]; + DUID duid; }; +static inline const DUID* link_duid(const Link *link) { + if (link->network->duid.type != _DUID_TYPE_INVALID) + return &link->network->duid; + else + return &link->manager->duid; +} + extern const sd_bus_vtable manager_vtable[]; int manager_new(Manager **ret); -- cgit v1.2.3-54-g00ecf From 7272b25e163d1c7395ff0da1397511c7946c0873 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 6 May 2016 21:27:36 +0200 Subject: networkd: reworkd LLDP emission to allow control of propagation level This allows selecting the propagation level of emitted LLDP packets (specifically: the destination MAC address of the packets). This is useful because it allows generating LLDP packets that optionally cross certain types of bridges. See 802.11ab-2009, Table 7-1 for details. --- man/systemd.network.xml | 28 ++++++--- src/network/networkd-link.c | 16 ++--- src/network/networkd-link.h | 2 +- src/network/networkd-lldp-tx.c | 102 +++++++++++++++++++++++++------ src/network/networkd-lldp-tx.h | 14 ++++- src/network/networkd-network-gperf.gperf | 2 +- src/network/networkd-network.h | 3 +- 7 files changed, 126 insertions(+), 41 deletions(-) (limited to 'src/network/networkd-network.h') diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 6d45d6c807..70e3804746 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -363,18 +363,26 @@ EmitLLDP= - Controls support for Ethernet LLDP packet emission. Accepts a boolean parameter and defaults to - false. If enabled a short LLDP packet with information about the local system is sent out in regular - intervals on the link. The LLDP packet will contain information about the local host name, the local - machine ID (as stored in - machine-id5) and the + Controls support for Ethernet LLDP packet emission. Accepts a boolean parameter or the special values + nearest-bridge, non-tpmr-bridge and + customer-bridge. Defaults to false, which turns off LLDP packet emission. If not false, + a short LLDP packet with information about the local system is sent out in regular intervals on the + link. The LLDP packet will contain information about the local host name, the local machine ID (as stored + in machine-id5) and the local interface name, as well as the pretty hostname of the system (as set in machine-info5). LLDP - emission is only available on Ethernet links. Note that this setting passed data suitable for - identification of host to the network and should thus not be used on untrusted networks, where such - identification data should not be made available. Use this option to enable other systems to identify on - which interface they are connected to this system. See LLDP= above for an option to - enable LLDP reception. + emission is only available on Ethernet links. Note that this setting passes data suitable for + identification of host to the network and should thus not be enabled on untrusted networks, where such + identification data should not be made available. Use this option to permit other systems to identify on + which interfaces they are connected to this system. The three special values control propagation of the + LLDP packets. The nearest-bridge setting permits propagation only to the nearest + connected bridge, non-tpmr-bridge permits propagation across Two-Port MAC Relays, but + not any other bridges, and customer-bridge permits propagation until a customer bridge + is reached. For details about these concepts, see IEEE 802.1AB-2009. Note that + configuring this setting to true is equivalent to nearest-bridge, the recommended and + most restricted level of propagation. See LLDP= above for an option to enable LLDP + reception. diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index c646af1f1a..4e3f62cf51 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -131,7 +131,7 @@ static bool link_lldp_rx_enabled(Link *link) { return link->network->lldp_mode != LLDP_MODE_NO; } -static bool link_lldp_tx_enabled(Link *link) { +static bool link_lldp_emit_enabled(Link *link) { assert(link); if (link->flags & IFF_LOOPBACK) @@ -143,7 +143,7 @@ static bool link_lldp_tx_enabled(Link *link) { if (!link->network) return false; - return link->network->lldp_emit; + return link->network->lldp_emit != LLDP_EMIT_NO; } static bool link_ipv4_forward_enabled(Link *link) { @@ -491,7 +491,7 @@ static void link_free(Link *link) { sd_dhcp_client_unref(link->dhcp_client); sd_dhcp_lease_unref(link->dhcp_lease); - link_lldp_tx_stop(link); + link_lldp_emit_stop(link); free(link->lease_file); @@ -618,7 +618,7 @@ static int link_stop_clients(Link *link) { r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Discovery: %m"); } - link_lldp_tx_stop(link); + link_lldp_emit_stop(link); return r; } @@ -1411,12 +1411,12 @@ static void lldp_handler(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n (void) link_lldp_save(link); - if (link_lldp_tx_enabled(link) && event == SD_LLDP_EVENT_ADDED) { + if (link_lldp_emit_enabled(link) && event == SD_LLDP_EVENT_ADDED) { /* If we received information about a new neighbor, restart the LLDP "fast" logic */ log_link_debug(link, "Received LLDP datagram from previously unknown neighbor, restarting 'fast' LLDP transmission."); - r = link_lldp_tx_start(link); + r = link_lldp_emit_start(link); if (r < 0) log_link_warning_errno(link, r, "Failed to restart LLDP transmission: %m"); } @@ -1501,8 +1501,8 @@ static int link_acquire_conf(Link *link) { return r; } - if (link_lldp_tx_enabled(link)) { - r = link_lldp_tx_start(link); + if (link_lldp_emit_enabled(link)) { + r = link_lldp_emit_start(link); if (r < 0) return log_link_warning_errno(link, r, "Failed to start LLDP transmission: %m"); } diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 86139be557..14c4a02c7e 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -121,7 +121,7 @@ typedef struct Link { /* This is about LLDP transmission */ unsigned lldp_tx_fast; /* The LLDP txFast counter (See 802.1ab-2009, section 9.2.5.18) */ - sd_event_source *lldp_tx_event_source; + sd_event_source *lldp_emit_event_source; Hashmap *bound_by_links; Hashmap *bound_to_links; diff --git a/src/network/networkd-lldp-tx.c b/src/network/networkd-lldp-tx.c index 03b694c3f1..3aa768388b 100644 --- a/src/network/networkd-lldp-tx.c +++ b/src/network/networkd-lldp-tx.c @@ -25,16 +25,14 @@ #include "fd-util.h" #include "fileio.h" #include "hostname-util.h" +#include "networkd-lldp-tx.h" +#include "networkd.h" +#include "parse-util.h" #include "random-util.h" #include "socket-util.h" #include "string-util.h" #include "unaligned.h" -#include "networkd.h" -#include "networkd-lldp-tx.h" - -#define LLDP_MULTICAST_ADDR { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e } - /* The LLDP spec calls this "txFastInit", see 9.2.5.19 */ #define LLDP_TX_FAST_INIT 4U @@ -50,6 +48,12 @@ /* The LLDP spec calls this msgFastTx, but we subtract half the jitter off it. */ #define LLDP_FAST_TX_USEC (1U * USEC_PER_SEC - LLDP_JITTER_USEC / 2) +static const struct ether_addr lldp_multicast_addr[_LLDP_EMIT_MAX] = { + [LLDP_EMIT_NEAREST_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e }}, + [LLDP_EMIT_NON_TPMR_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 }}, + [LLDP_EMIT_CUSTOMER_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 }}, +}; + static int lldp_write_tlv_header(uint8_t **p, uint8_t id, size_t sz) { assert(p); @@ -66,6 +70,7 @@ static int lldp_write_tlv_header(uint8_t **p, uint8_t id, size_t sz) { } static int lldp_make_packet( + LLDPEmit mode, const struct ether_addr *hwaddr, const char *machine_id, const char *ifname, @@ -84,6 +89,8 @@ static int lldp_make_packet( size_t l; int r; + assert(mode > LLDP_EMIT_NO); + assert(mode < _LLDP_EMIT_MAX); assert(hwaddr); assert(machine_id); assert(ifname); @@ -132,7 +139,7 @@ static int lldp_make_packet( h = (struct ether_header*) packet; h->ether_type = htobe16(ETHERTYPE_LLDP); - memcpy(h->ether_dhost, &(struct ether_addr) { LLDP_MULTICAST_ADDR }, ETH_ALEN); + memcpy(h->ether_dhost, lldp_multicast_addr + mode, ETH_ALEN); memcpy(h->ether_shost, hwaddr, ETH_ALEN); p = (uint8_t*) packet + sizeof(struct ether_header); @@ -197,22 +204,28 @@ static int lldp_make_packet( return 0; } -static int lldp_send_packet(int ifindex, const void *packet, size_t packet_size) { +static int lldp_send_packet( + int ifindex, + const struct ether_addr *address, + const void *packet, + size_t packet_size) { union sockaddr_union sa = { .ll.sll_family = AF_PACKET, .ll.sll_protocol = htobe16(ETHERTYPE_LLDP), .ll.sll_ifindex = ifindex, .ll.sll_halen = ETH_ALEN, - .ll.sll_addr = LLDP_MULTICAST_ADDR, }; _cleanup_close_ int fd = -1; ssize_t l; assert(ifindex > 0); + assert(address); assert(packet || packet_size <= 0); + memcpy(sa.ll.sll_addr, address, ETH_ALEN); + fd = socket(PF_PACKET, SOCK_RAW|SOCK_CLOEXEC, IPPROTO_RAW); if (fd < 0) return -errno; @@ -237,6 +250,13 @@ static int link_send_lldp(Link *link) { usec_t ttl; int r; + assert(link); + + if (!link->network || link->network->lldp_emit == LLDP_EMIT_NO) + return 0; + + assert(link->network->lldp_emit < _LLDP_EMIT_MAX); + r = sd_id128_get_machine(&machine_id); if (r < 0) return r; @@ -251,7 +271,8 @@ static int link_send_lldp(Link *link) { SD_LLDP_SYSTEM_CAPABILITIES_ROUTER : SD_LLDP_SYSTEM_CAPABILITIES_STATION; - r = lldp_make_packet(&link->mac, + r = lldp_make_packet(link->network->lldp_emit, + &link->mac, sd_id128_to_string(machine_id, machine_id_string), link->ifname, (uint16_t) ttl, @@ -264,7 +285,7 @@ static int link_send_lldp(Link *link) { if (r < 0) return r; - return lldp_send_packet(link->ifindex, packet, packet_size); + return lldp_send_packet(link->ifindex, lldp_multicast_addr + link->network->lldp_emit, packet, packet_size); } static int on_lldp_timer(sd_event_source *s, usec_t t, void *userdata) { @@ -300,12 +321,17 @@ static int on_lldp_timer(sd_event_source *s, usec_t t, void *userdata) { return 0; } -int link_lldp_tx_start(Link *link) { +int link_lldp_emit_start(Link *link) { usec_t next; int r; assert(link); + if (!link->network || link->network->lldp_emit == LLDP_EMIT_NO) { + link_lldp_emit_stop(link); + return 0; + } + /* Starts the LLDP transmission in "fast" mode. If it is already started, turns "fast" mode back on again. */ link->lldp_tx_fast = LLDP_TX_FAST_INIT; @@ -313,22 +339,22 @@ int link_lldp_tx_start(Link *link) { next = usec_add(usec_add(now(clock_boottime_or_monotonic()), LLDP_FAST_TX_USEC), (usec_t) random_u64() % LLDP_JITTER_USEC); - if (link->lldp_tx_event_source) { + if (link->lldp_emit_event_source) { usec_t old; /* Lower the timeout, maybe */ - r = sd_event_source_get_time(link->lldp_tx_event_source, &old); + r = sd_event_source_get_time(link->lldp_emit_event_source, &old); if (r < 0) return r; if (old <= next) return 0; - return sd_event_source_set_time(link->lldp_tx_event_source, next); + return sd_event_source_set_time(link->lldp_emit_event_source, next); } else { r = sd_event_add_time( link->manager->event, - &link->lldp_tx_event_source, + &link->lldp_emit_event_source, clock_boottime_or_monotonic(), next, 0, @@ -337,14 +363,54 @@ int link_lldp_tx_start(Link *link) { if (r < 0) return r; - (void) sd_event_source_set_description(link->lldp_tx_event_source, "lldp-tx"); + (void) sd_event_source_set_description(link->lldp_emit_event_source, "lldp-tx"); } return 0; } -void link_lldp_tx_stop(Link *link) { +void link_lldp_emit_stop(Link *link) { assert(link); - link->lldp_tx_event_source = sd_event_source_unref(link->lldp_tx_event_source); + link->lldp_emit_event_source = sd_event_source_unref(link->lldp_emit_event_source); +} + +int config_parse_lldp_emit( + 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) { + + LLDPEmit *emit = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) + *emit = LLDP_EMIT_NO; + else if (streq(rvalue, "nearest-bridge")) + *emit = LLDP_EMIT_NEAREST_BRIDGE; + else if (streq(rvalue, "non-tpmr-bridge")) + *emit = LLDP_EMIT_NON_TPMR_BRIDGE; + else if (streq(rvalue, "customer-bridge")) + *emit = LLDP_EMIT_CUSTOMER_BRIDGE; + else { + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse LLDP emission setting, ignoring: %s", rvalue); + return 0; + } + + *emit = r ? LLDP_EMIT_NEAREST_BRIDGE : LLDP_EMIT_NO; + } + + return 0; } diff --git a/src/network/networkd-lldp-tx.h b/src/network/networkd-lldp-tx.h index 8c7f403005..4680c9d950 100644 --- a/src/network/networkd-lldp-tx.h +++ b/src/network/networkd-lldp-tx.h @@ -21,5 +21,15 @@ #include "networkd-link.h" -int link_lldp_tx_start(Link *link); -void link_lldp_tx_stop(Link *link); +typedef enum LLDPEmit { + LLDP_EMIT_NO, + LLDP_EMIT_NEAREST_BRIDGE, + LLDP_EMIT_NON_TPMR_BRIDGE, + LLDP_EMIT_CUSTOMER_BRIDGE, + _LLDP_EMIT_MAX, +} LLDPEmit; + +int link_lldp_emit_start(Link *link); +void link_lldp_emit_stop(Link *link); + +int config_parse_lldp_emit(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); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index a9a541559e..4425ee4e2f 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -42,7 +42,7 @@ Network.LinkLocalAddressing, config_parse_address_family_boolean, Network.IPv4LLRoute, config_parse_bool, 0, offsetof(Network, ipv4ll_route) Network.IPv6Token, config_parse_ipv6token, 0, offsetof(Network, ipv6_token) Network.LLDP, config_parse_lldp_mode, 0, offsetof(Network, lldp_mode) -Network.EmitLLDP, config_parse_bool, 0, offsetof(Network, lldp_emit) +Network.EmitLLDP, config_parse_lldp_emit, 0, offsetof(Network, lldp_emit) Network.Address, config_parse_address, 0, 0 Network.Gateway, config_parse_gateway, 0, 0 Network.Domains, config_parse_domains, 0, 0 diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index ff2414efdd..4cd0fa4ab8 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -29,6 +29,7 @@ #include "networkd-address.h" #include "networkd-fdb.h" +#include "networkd-lldp-tx.h" #include "networkd-netdev.h" #include "networkd-route.h" #include "networkd-util.h" @@ -161,7 +162,7 @@ struct Network { DUID duid; LLDPMode lldp_mode; /* LLDP reception */ - bool lldp_emit; /* LLDP transmission */ + LLDPEmit lldp_emit; /* LLDP transmission */ LIST_HEAD(Address, static_addresses); LIST_HEAD(Route, static_routes); -- cgit v1.2.3-54-g00ecf From 77ff6022fa30005f8e965c42064e0274d329b6c0 Mon Sep 17 00:00:00 2001 From: Clemens Gruber Date: Wed, 18 May 2016 01:34:25 +0200 Subject: networkd: Add EmitRouter= option for DHCP Server (#3251) Add an option to disable appending DHCP option 3 (Router) to the DHCP OFFER and ACK packets. This commit adds the boolean option EmitRouter= for the [DHCPServer] section in .network files. Rationale: On embedded devices, it is very useful to have a DHCP server running on an USB OTG ethernet gadget interface to avoid manual setup on the client PCs, but it should only serve IP addresses, no route(r)s. Otherwise, Windows clients experience network connectivity issues, due to them using the address set in DHCP option 3 as default gateway. Signed-off-by: Clemens Gruber --- NEWS | 32 +++++++++++++++------------ man/systemd.network.xml | 10 +++++++++ src/libsystemd-network/dhcp-server-internal.h | 2 ++ src/libsystemd-network/sd-dhcp-server.c | 31 +++++++++++++++++++------- src/network/networkd-link.c | 6 +++++ src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.c | 1 + src/network/networkd-network.h | 1 + src/systemd/sd-dhcp-server.h | 1 + 9 files changed, 63 insertions(+), 22 deletions(-) (limited to 'src/network/networkd-network.h') diff --git a/NEWS b/NEWS index ff2dd9abbf..a6e0581879 100644 --- a/NEWS +++ b/NEWS @@ -180,22 +180,26 @@ CHANGES WITH 230 in spe: service. Please leave PrivateDevices= off if you run into problems with this. + * The systemd-networkd DHCP server gained the option EmitRouter=, which + defaults to yes, to configure if the DHCP Option 3 (Router) should be + emitted. + Contributions from: Alban Crequy, Alexander Kuleshov, Alex Crawford, Andrew Eikum, Beniamino Galvani, Benjamin Robin, Benjamin ROBIN, Biao - Lu, Bjørnar Ness, Calvin Owens, Christian Hesse, Colin Guthrie, Daniel - J Walsh, Daniel Mack, Dan Nicholson, daurnimator, David Herrmann, David - R. Hedges, Elias Probst, Emmanuel Gil Peyrot, EMOziko, Evgeny - Vereshchagin, Federico, Felipe Sateler, Filipe Brandenburger, Franck - Bui, frankheckenbach, Georgia Brikis, Harald Hoyer, Hendrik Brueckner, - Hristo Venev, Iago López Galeiras, Ian Kelling, Ismo Puustinen, Jakub - Wilk, Jaroslav Škarvada, Jeff Huang, Joel Holdsworth, kayrus, Klearchos - Chaloulos, Lennart Poettering, Lubomir Rintel, Lukas Nykryn, Lukáš - Nykrýn, Mantas Mikulėnas, Marcel Holtmann, Martin Pitt, Michael Biebl, - michaelolbrich, Michał Bartoszkiewicz, Michal Koutný, Michal Sekletar, - Mike Frysinger, Mike Gilbert, Mingcong Bai, Ming Lin, mulkieran, - muzena, Nalin Dahyabhai, Naohiro Aota, Nathan McSween, Nicolas - Braud-Santoni, Patrik Flykt, Peter Hutterer, Petr Lautrbach, Petros - Angelatos, Piotr Drąg, Rabin Vincent, Robert Węcławski, Ronny + Lu, Bjørnar Ness, Calvin Owens, Christian Hesse, Clemens Gruber, Colin + Guthrie, Daniel J Walsh, Daniel Mack, Dan Nicholson, daurnimator, David + Herrmann, David R. Hedges, Elias Probst, Emmanuel Gil Peyrot, EMOziko, + Evgeny Vereshchagin, Federico, Felipe Sateler, Filipe Brandenburger, + Franck Bui, frankheckenbach, Georgia Brikis, Harald Hoyer, Hendrik + Brueckner, Hristo Venev, Iago López Galeiras, Ian Kelling, Ismo + Puustinen, Jakub Wilk, Jaroslav Škarvada, Jeff Huang, Joel Holdsworth, + kayrus, Klearchos Chaloulos, Lennart Poettering, Lubomir Rintel, Lukas + Nykryn, Lukáš Nykrýn, Mantas Mikulėnas, Marcel Holtmann, Martin Pitt, + Michael Biebl, michaelolbrich, Michał Bartoszkiewicz, Michal Koutný, + Michal Sekletar, Mike Frysinger, Mike Gilbert, Mingcong Bai, Ming Lin, + mulkieran, muzena, Nalin Dahyabhai, Naohiro Aota, Nathan McSween, + Nicolas Braud-Santoni, Patrik Flykt, Peter Hutterer, Petr Lautrbach, + Petros Angelatos, Piotr Drąg, Rabin Vincent, Robert Węcławski, Ronny Chevalier, Samuel Tardieu, Stefan Schallenberg, Steven Siloti, Susant Sahani, Sylvain Plantefève, Taylor Smock, tblume, Tejun Heo, Thomas Blume, Thomas Haller, Thomas Hindoe Paaboel Andersen, Thomas diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 70e3804746..d917fe2c12 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -981,6 +981,16 @@ DNS=. + + EmitRouter= + + Similar to the EmitDNS= + setting described above, this setting configures whether the + DHCP lease should contain the router option. The same syntax, + propagation semantics and defaults apply as for + EmitDNS=. + + EmitTimezone= Timezone= diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h index adb557167a..0c76956fad 100644 --- a/src/libsystemd-network/dhcp-server-internal.h +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -63,6 +63,8 @@ struct sd_dhcp_server { struct in_addr *ntp, *dns; unsigned n_ntp, n_dns; + bool emit_router; + Hashmap *leases_by_client_id; DHCPLease **bound_leases; DHCPLease invalid_lease; diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index 9adf8ec19d..fb335337c4 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -468,10 +468,12 @@ static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req, if (r < 0) return r; - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_ROUTER, 4, &server->address); - if (r < 0) - return r; + if (server->emit_router) { + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_ROUTER, 4, &server->address); + if (r < 0) + return r; + } r = dhcp_server_send_packet(server, req, packet, DHCP_OFFER, offset); if (r < 0) @@ -505,10 +507,12 @@ static int server_send_ack(sd_dhcp_server *server, DHCPRequest *req, if (r < 0) return r; - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_ROUTER, 4, &server->address); - if (r < 0) - return r; + if (server->emit_router) { + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_ROUTER, 4, &server->address); + if (r < 0) + return r; + } if (server->n_dns > 0) { r = dhcp_option_append( @@ -1158,3 +1162,14 @@ int sd_dhcp_server_set_ntp(sd_dhcp_server *server, const struct in_addr ntp[], u return 1; } + +int sd_dhcp_server_set_emit_router(sd_dhcp_server *server, int enabled) { + assert_return(server, -EINVAL); + + if (enabled == server->emit_router) + return 0; + + server->emit_router = enabled; + + return 1; +} diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index f3a79b0ec8..16a3609a0b 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -1020,6 +1020,12 @@ static int link_enter_set_addresses(Link *link) { log_link_warning_errno(link, r, "Failed to set NTP server for DHCP server, ignoring: %m"); } + r = sd_dhcp_server_set_emit_router(link->dhcp_server, link->network->dhcp_server_emit_router); + if (r < 0) { + log_link_warning_errno(link, r, "Failed to set router emission for DHCP server: %m"); + return r; + } + if (link->network->dhcp_server_emit_timezone) { _cleanup_free_ char *buffer = NULL; const char *tz = NULL; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 4425ee4e2f..03e4e3b39f 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -95,6 +95,7 @@ DHCPServer.EmitDNS, config_parse_bool, DHCPServer.DNS, config_parse_dhcp_server_dns, 0, 0 DHCPServer.EmitNTP, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_ntp) DHCPServer.NTP, config_parse_dhcp_server_ntp, 0, 0 +DHCPServer.EmitRouter, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_router) DHCPServer.EmitTimezone, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_timezone) DHCPServer.Timezone, config_parse_timezone, 0, offsetof(Network, dhcp_server_timezone) DHCPServer.PoolOffset, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_offset) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 206c270e50..dd89b3770c 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -113,6 +113,7 @@ static int network_load_one(Manager *manager, const char *filename) { network->dhcp_server_emit_dns = true; network->dhcp_server_emit_ntp = true; + network->dhcp_server_emit_router = true; network->dhcp_server_emit_timezone = true; network->use_bpdu = true; diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 4cd0fa4ab8..91099161ce 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -127,6 +127,7 @@ struct Network { bool dhcp_server_emit_ntp; struct in_addr *dhcp_server_ntp; unsigned n_dhcp_server_ntp; + bool dhcp_server_emit_router; bool dhcp_server_emit_timezone; char *dhcp_server_timezone; usec_t dhcp_server_default_lease_time_usec, dhcp_server_max_lease_time_usec; diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h index fcef083ce6..d4517a26d6 100644 --- a/src/systemd/sd-dhcp-server.h +++ b/src/systemd/sd-dhcp-server.h @@ -51,6 +51,7 @@ int sd_dhcp_server_configure_pool(sd_dhcp_server *server, struct in_addr *addres int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *timezone); int sd_dhcp_server_set_dns(sd_dhcp_server *server, const struct in_addr ntp[], unsigned n); int sd_dhcp_server_set_ntp(sd_dhcp_server *server, const struct in_addr dns[], unsigned n); +int sd_dhcp_server_set_emit_router(sd_dhcp_server *server, int enabled); int sd_dhcp_server_set_max_lease_time(sd_dhcp_server *server, uint32_t t); int sd_dhcp_server_set_default_lease_time(sd_dhcp_server *server, uint32_t t); -- cgit v1.2.3-54-g00ecf From 8231d17032fdd535e58c5aead8eae3f6cbc048b5 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Thu, 9 Jun 2016 03:13:06 -0400 Subject: ./move.sh --- build-aux/Makefile.each.head/.gitignore | 0 build-aux/Makefile.each.tail/.gitignore | 0 build-aux/Makefile.each.tail/20-systemd.mk | 49 + build-aux/Makefile.once.head/.gitignore | 0 build-aux/Makefile.once.head/20-systemd.mk | 186 + build-aux/Makefile.once.tail/.gitignore | 0 build-aux/Makefile.once.tail/20-systemd.mk | 97 + catalog/Makefile | 1 - catalog/systemd.be.catalog | 260 - catalog/systemd.be@latin.catalog | 260 - catalog/systemd.bg.catalog | 324 - catalog/systemd.catalog | 334 - catalog/systemd.da.catalog | 261 - catalog/systemd.fr.catalog | 320 - catalog/systemd.hr.catalog | 314 - catalog/systemd.hu.catalog | 262 - catalog/systemd.it.catalog | 254 - catalog/systemd.ko.catalog | 264 - catalog/systemd.pl.catalog | 315 - catalog/systemd.pt_BR.catalog | 264 - catalog/systemd.ru.catalog | 354 - catalog/systemd.sr.catalog | 262 - catalog/systemd.zh_CN.catalog | 253 - catalog/systemd.zh_TW.catalog | 263 - config.mk.in | 77 + discard.mk | 1195 +++ docs/Makefile | 1 - docs/sysvinit/Makefile | 1 - docs/var-log/Makefile | 1 - hwdb/Makefile | 37 +- man/Makefile | 1 - man/kernel-install.xml | 192 - network/Makefile | 1 - rules/Makefile | 1 - shell-completion/Makefile | 1 - shell-completion/bash/Makefile | 1 - shell-completion/bash/kernel-install | 50 - shell-completion/zsh/Makefile | 1 - shell-completion/zsh/_kernel-install | 26 - src/Makefile | 21 +- src/ac-power/Makefile | 1 - src/ac-power/ac-power.c | 35 - src/activate/Makefile | 1 - src/activate/activate.c | 545 -- src/analyze/.gitignore | 1 - src/analyze/Makefile | 1 - src/analyze/analyze-verify.c | 304 - src/analyze/analyze-verify.h | 26 - src/analyze/analyze.c | 1485 ---- src/ask-password/Makefile | 1 - src/ask-password/ask-password.c | 188 - src/backlight/Makefile | 1 - src/backlight/backlight.c | 434 -- src/basic/.gitignore | 16 - src/basic/Makefile | 1 - src/basic/MurmurHash2.c | 86 - src/basic/MurmurHash2.h | 33 - src/basic/af-list.c | 56 - src/basic/af-list.h | 41 - src/basic/alloc-util.c | 83 - src/basic/alloc-util.h | 111 - src/basic/architecture.c | 179 - src/basic/architecture.h | 199 - src/basic/arphrd-list.c | 56 - src/basic/arphrd-list.h | 25 - src/basic/async.c | 94 - src/basic/async.h | 25 - src/basic/audit-util.c | 104 - src/basic/audit-util.h | 31 - src/basic/barrier.c | 415 -- src/basic/barrier.h | 91 - src/basic/bitmap.c | 220 - src/basic/bitmap.h | 50 - src/basic/blkid-util.h | 31 - src/basic/btrfs-ctree.h | 96 - src/basic/btrfs-util.c | 2075 ------ src/basic/btrfs-util.h | 131 - src/basic/build.h | 155 - src/basic/bus-label.c | 98 - src/basic/bus-label.h | 31 - src/basic/calendarspec.c | 1089 --- src/basic/calendarspec.h | 58 - src/basic/cap-list.c | 66 - src/basic/cap-list.h | 24 - src/basic/capability-util.c | 361 - src/basic/capability-util.h | 57 - src/basic/cgroup-util.c | 2338 ------ src/basic/cgroup-util.h | 228 - src/basic/chattr-util.c | 107 - src/basic/chattr-util.h | 26 - src/basic/clock-util.c | 165 - src/basic/clock-util.h | 29 - src/basic/conf-files.c | 166 - src/basic/conf-files.h | 25 - src/basic/copy.c | 603 -- src/basic/copy.h | 36 - src/basic/cpu-set-util.c | 114 - src/basic/cpu-set-util.h | 32 - src/basic/def.h | 90 - src/basic/device-nodes.c | 80 - src/basic/device-nodes.h | 26 - src/basic/dirent-util.c | 74 - src/basic/dirent-util.h | 52 - src/basic/env-util.c | 624 -- src/basic/env-util.h | 51 - src/basic/errno-list.c | 57 - src/basic/errno-list.h | 25 - src/basic/escape.c | 502 -- src/basic/escape.h | 54 - src/basic/ether-addr-util.c | 125 - src/basic/ether-addr-util.h | 39 - src/basic/exit-status.c | 240 - src/basic/exit-status.h | 102 - src/basic/extract-word.c | 298 - src/basic/extract-word.h | 35 - src/basic/fd-util.c | 374 - src/basic/fd-util.h | 79 - src/basic/fdset.c | 273 - src/basic/fdset.h | 58 - src/basic/fileio-label.c | 68 - src/basic/fileio-label.h | 30 - src/basic/fileio.c | 1356 ---- src/basic/fileio.h | 88 - src/basic/formats-util.h | 63 - src/basic/fs-util.c | 510 -- src/basic/fs-util.h | 76 - src/basic/glob-util.c | 70 - src/basic/glob-util.h | 36 - src/basic/gunicode.c | 112 - src/basic/gunicode.h | 30 - src/basic/hash-funcs.c | 81 - src/basic/hash-funcs.h | 65 - src/basic/hashmap.c | 1803 ----- src/basic/hashmap.h | 372 - src/basic/hexdecoct.c | 754 -- src/basic/hexdecoct.h | 56 - src/basic/hostname-util.c | 252 - src/basic/hostname-util.h | 41 - src/basic/in-addr-util.c | 356 - src/basic/in-addr-util.h | 59 - src/basic/io-util.c | 269 - src/basic/io-util.h | 95 - src/basic/ioprio.h | 55 - src/basic/label.c | 82 - src/basic/label.h | 28 - src/basic/list.h | 182 - src/basic/locale-util.c | 322 - src/basic/locale-util.h | 73 - src/basic/lockfile-util.c | 153 - src/basic/lockfile-util.h | 39 - src/basic/log.c | 1170 --- src/basic/log.h | 249 - src/basic/login-util.c | 31 - src/basic/login-util.h | 29 - src/basic/macro.h | 406 - src/basic/memfd-util.c | 174 - src/basic/memfd-util.h | 36 - src/basic/mempool.c | 104 - src/basic/mempool.h | 47 - src/basic/missing.h | 1016 --- src/basic/missing_syscall.h | 310 - src/basic/mkdir-label.c | 38 - src/basic/mkdir.c | 128 - src/basic/mkdir.h | 38 - src/basic/mount-util.c | 533 -- src/basic/mount-util.h | 52 - src/basic/nss-util.h | 199 - src/basic/ordered-set.c | 64 - src/basic/ordered-set.h | 74 - src/basic/parse-util.c | 534 -- src/basic/parse-util.h | 107 - src/basic/path-util.c | 816 -- src/basic/path-util.h | 127 - src/basic/prioq.c | 320 - src/basic/prioq.h | 43 - src/basic/proc-cmdline.c | 176 - src/basic/proc-cmdline.h | 27 - src/basic/process-util.c | 782 -- src/basic/process-util.h | 105 - src/basic/random-util.c | 133 - src/basic/random-util.h | 39 - src/basic/ratelimit.c | 56 - src/basic/ratelimit.h | 58 - src/basic/refcnt.h | 34 - src/basic/replace-var.c | 112 - src/basic/replace-var.h | 22 - src/basic/rlimit-util.c | 321 - src/basic/rlimit-util.h | 36 - src/basic/rm-rf.c | 236 - src/basic/rm-rf.h | 41 - src/basic/securebits.h | 45 - src/basic/selinux-util.c | 485 -- src/basic/selinux-util.h | 51 - src/basic/set.h | 136 - src/basic/sigbus.c | 152 - src/basic/sigbus.h | 25 - src/basic/signal-util.c | 278 - src/basic/signal-util.h | 56 - src/basic/siphash24.c | 191 - src/basic/siphash24.h | 23 - src/basic/smack-util.c | 241 - src/basic/smack-util.h | 54 - src/basic/socket-label.c | 170 - src/basic/socket-util.c | 1050 --- src/basic/socket-util.h | 154 - src/basic/sparse-endian.h | 88 - src/basic/special.h | 119 - src/basic/stat-util.c | 218 - src/basic/stat-util.h | 69 - src/basic/stdio-util.h | 76 - src/basic/strbuf.c | 205 - src/basic/strbuf.h | 54 - src/basic/string-table.c | 34 - src/basic/string-table.h | 117 - src/basic/string-util.c | 855 --- src/basic/string-util.h | 190 - src/basic/strv.c | 927 --- src/basic/strv.h | 172 - src/basic/strxcpyx.c | 100 - src/basic/strxcpyx.h | 31 - src/basic/syslog-util.c | 114 - src/basic/syslog-util.h | 32 - src/basic/terminal-util.c | 1153 --- src/basic/terminal-util.h | 126 - src/basic/time-util.c | 1157 --- src/basic/time-util.h | 153 - src/basic/umask-util.h | 46 - src/basic/unaligned.h | 111 - src/basic/unit-name.c | 1049 --- src/basic/unit-name.h | 368 - src/basic/user-util.c | 481 -- src/basic/user-util.h | 70 - src/basic/utf8.c | 409 - src/basic/utf8.h | 60 - src/basic/util.c | 808 -- src/basic/util.h | 189 - src/basic/verbs.c | 101 - src/basic/verbs.h | 33 - src/basic/virt.c | 516 -- src/basic/virt.h | 72 - src/basic/web-util.c | 76 - src/basic/web-util.h | 30 - src/basic/xattr-util.c | 200 - src/basic/xattr-util.h | 37 - src/basic/xml.c | 255 - src/basic/xml.h | 32 - src/binfmt/Makefile | 1 - src/binfmt/binfmt.c | 203 - src/boot/Makefile | 1 - src/boot/bootctl.c | 1143 --- src/boot/efi/.gitignore | 2 - src/boot/efi/boot.c | 1857 ----- src/boot/efi/console.c | 139 - src/boot/efi/console.h | 32 - src/boot/efi/disk.c | 49 - src/boot/efi/disk.h | 19 - src/boot/efi/graphics.c | 88 - src/boot/efi/graphics.h | 22 - src/boot/efi/linux.c | 128 - src/boot/efi/linux.h | 22 - src/boot/efi/measure.c | 312 - src/boot/efi/measure.h | 21 - src/boot/efi/pefile.c | 170 - src/boot/efi/pefile.h | 20 - src/boot/efi/splash.c | 321 - src/boot/efi/splash.h | 20 - src/boot/efi/stub.c | 130 - src/boot/efi/util.c | 345 - src/boot/efi/util.h | 48 - src/busctl/Makefile | 37 + src/busctl/busctl-introspect.c | 790 ++ src/busctl/busctl-introspect.h | 32 + src/busctl/busctl.c | 2086 ++++++ src/cgls/Makefile | 1 - src/cgls/cgls.c | 288 - src/cgroups-agent/Makefile | 1 - src/cgroups-agent/cgroups-agent.c | 67 - src/cgtop/Makefile | 1 - src/cgtop/cgtop.c | 1115 --- src/core/.gitignore | 3 - src/core/Makefile | 1 - src/core/audit-fd.c | 73 - src/core/audit-fd.h | 23 - src/core/automount.c | 1108 --- src/core/automount.h | 59 - src/core/bus-policy.c | 180 - src/core/bus-policy.h | 64 - src/core/busname.c | 1082 --- src/core/busname.h | 69 - src/core/cgroup.c | 1895 ----- src/core/cgroup.h | 181 - src/core/dbus-automount.c | 34 - src/core/dbus-automount.h | 23 - src/core/dbus-busname.c | 37 - src/core/dbus-busname.h | 23 - src/core/dbus-cgroup.c | 1004 --- src/core/dbus-cgroup.h | 28 - src/core/dbus-device.c | 28 - src/core/dbus-device.h | 24 - src/core/dbus-execute.c | 1545 ---- src/core/dbus-execute.h | 45 - src/core/dbus-job.c | 193 - src/core/dbus-job.h | 31 - src/core/dbus-kill.c | 122 - src/core/dbus-kill.h | 29 - src/core/dbus-manager.c | 2305 ------ src/core/dbus-manager.h | 28 - src/core/dbus-mount.c | 211 - src/core/dbus-mount.h | 29 - src/core/dbus-path.c | 86 - src/core/dbus-path.h | 24 - src/core/dbus-scope.c | 229 - src/core/dbus-scope.h | 31 - src/core/dbus-service.c | 322 - src/core/dbus-service.h | 29 - src/core/dbus-slice.c | 52 - src/core/dbus-slice.h | 29 - src/core/dbus-socket.c | 184 - src/core/dbus-socket.h | 29 - src/core/dbus-swap.c | 115 - src/core/dbus-swap.h | 30 - src/core/dbus-target.c | 26 - src/core/dbus-target.h | 24 - src/core/dbus-timer.c | 352 - src/core/dbus-timer.h | 28 - src/core/dbus-unit.c | 1423 ---- src/core/dbus-unit.h | 41 - src/core/dbus.c | 1243 ---- src/core/dbus.h | 44 - src/core/device.c | 876 --- src/core/device.h | 47 - src/core/execute.c | 3092 -------- src/core/execute.h | 287 - src/core/failure-action.c | 127 - src/core/failure-action.h | 41 - src/core/hostname-setup.c | 68 - src/core/hostname-setup.h | 22 - src/core/ima-setup.c | 80 - src/core/ima-setup.h | 24 - src/core/job.c | 1259 ---- src/core/job.h | 242 - src/core/kill.c | 68 - src/core/kill.h | 65 - src/core/killall.c | 249 - src/core/killall.h | 22 - src/core/kmod-setup.c | 131 - src/core/kmod-setup.h | 22 - src/core/load-dropin.c | 90 - src/core/load-dropin.h | 34 - src/core/load-fragment-gperf.gperf.m4 | 391 - src/core/load-fragment.c | 4083 ---------- src/core/load-fragment.h | 123 - src/core/locale-setup.c | 124 - src/core/locale-setup.h | 22 - src/core/loopback-setup.c | 90 - src/core/loopback-setup.h | 22 - src/core/machine-id-setup.c | 364 - src/core/machine-id-setup.h | 23 - src/core/macros.systemd.in | 105 - src/core/main.c | 2149 ------ src/core/manager.c | 3134 -------- src/core/manager.h | 379 - src/core/mount-setup.c | 413 -- src/core/mount-setup.h | 30 - src/core/mount.c | 1881 ----- src/core/mount.h | 108 - src/core/namespace.c | 650 -- src/core/namespace.h | 63 - src/core/org.freedesktop.systemd1.conf | 232 - src/core/org.freedesktop.systemd1.policy.in.in | 70 - src/core/org.freedesktop.systemd1.service | 11 - src/core/path.c | 784 -- src/core/path.h | 93 - src/core/scope.c | 608 -- src/core/scope.h | 55 - src/core/selinux-access.c | 283 - src/core/selinux-access.h | 45 - src/core/selinux-setup.c | 121 - src/core/selinux-setup.h | 24 - src/core/service.c | 3353 --------- src/core/service.h | 220 - src/core/show-status.c | 124 - src/core/show-status.h | 39 - src/core/shutdown.c | 440 -- src/core/slice.c | 346 - src/core/slice.h | 32 - src/core/smack-setup.c | 346 - src/core/smack-setup.h | 24 - src/core/socket.c | 2992 -------- src/core/socket.h | 185 - src/core/swap.c | 1532 ---- src/core/swap.h | 110 - src/core/system.conf | 61 - src/core/systemd.pc.in | 34 - src/core/target.c | 223 - src/core/target.h | 30 - src/core/timer.c | 859 --- src/core/timer.h | 89 - src/core/transaction.c | 1099 --- src/core/transaction.h | 51 - src/core/triggers.systemd.in | 66 - src/core/umount.c | 614 -- src/core/umount.h | 28 - src/core/unit-printf.c | 304 - src/core/unit-printf.h | 26 - src/core/unit.c | 3818 ---------- src/core/unit.h | 639 -- src/core/user.conf | 44 - src/coredump/Makefile | 1 - src/coredump/coredump-vacuum.c | 268 - src/coredump/coredump-vacuum.h | 25 - src/coredump/coredump.c | 1149 --- src/coredump/coredump.conf | 21 - src/coredump/coredumpctl.c | 878 --- src/coredump/stacktrace.c | 200 - src/coredump/stacktrace.h | 22 - src/coredump/test-coredump-vacuum.c | 30 - src/cryptsetup/Makefile | 1 - src/cryptsetup/cryptsetup-generator.c | 509 -- src/cryptsetup/cryptsetup.c | 757 -- src/dbus1-generator/Makefile | 1 - src/dbus1-generator/dbus1-generator.c | 331 - src/debug-generator/Makefile | 1 - src/debug-generator/debug-generator.c | 202 - src/delta/Makefile | 1 - src/delta/delta.c | 635 -- src/detect-virt/Makefile | 1 - src/detect-virt/detect-virt.c | 169 - src/escape/Makefile | 1 - src/escape/escape.c | 237 - src/firstboot/Makefile | 1 - src/firstboot/firstboot.c | 870 --- src/fsck/Makefile | 1 - src/fsck/fsck.c | 487 -- src/fstab-generator/Makefile | 1 - src/fstab-generator/fstab-generator.c | 713 -- src/getty-generator/Makefile | 1 - src/getty-generator/getty-generator.c | 233 - src/gpt-auto-generator/Makefile | 1 - src/gpt-auto-generator/gpt-auto-generator.c | 1042 --- src/grp-boot/Makefile | 28 + src/grp-boot/bootctl/Makefile | 54 + src/grp-boot/bootctl/bootctl.c | 1143 +++ src/grp-boot/systemd-boot/.gitignore | 2 + src/grp-boot/systemd-boot/Makefile | 194 + src/grp-boot/systemd-boot/boot.c | 1857 +++++ src/grp-boot/systemd-boot/console.c | 139 + src/grp-boot/systemd-boot/console.h | 32 + src/grp-boot/systemd-boot/disk.c | 49 + src/grp-boot/systemd-boot/disk.h | 19 + src/grp-boot/systemd-boot/graphics.c | 88 + src/grp-boot/systemd-boot/graphics.h | 22 + src/grp-boot/systemd-boot/linux.c | 128 + src/grp-boot/systemd-boot/linux.h | 22 + src/grp-boot/systemd-boot/measure.c | 312 + src/grp-boot/systemd-boot/measure.h | 21 + src/grp-boot/systemd-boot/pefile.c | 170 + src/grp-boot/systemd-boot/pefile.h | 20 + src/grp-boot/systemd-boot/splash.c | 321 + src/grp-boot/systemd-boot/splash.h | 20 + src/grp-boot/systemd-boot/stub.c | 130 + src/grp-boot/systemd-boot/test-efi-create-disk.sh | 42 + src/grp-boot/systemd-boot/util.c | 345 + src/grp-boot/systemd-boot/util.h | 48 + src/grp-coredump/Makefile | 28 + src/grp-coredump/coredumpctl/Makefile | 41 + src/grp-coredump/coredumpctl/coredumpctl.c | 878 +++ src/grp-coredump/systemd-coredump/Makefile | 81 + .../systemd-coredump/coredump-vacuum.c | 268 + .../systemd-coredump/coredump-vacuum.h | 25 + src/grp-coredump/systemd-coredump/coredump.c | 1149 +++ src/grp-coredump/systemd-coredump/coredump.conf | 21 + src/grp-coredump/systemd-coredump/stacktrace.c | 200 + src/grp-coredump/systemd-coredump/stacktrace.h | 22 + .../systemd-coredump/test-coredump-vacuum.c | 30 + src/grp-journal-remote/.gitignore | 2 + src/grp-journal-remote/browse.html | 544 ++ src/grp-journal-remote/log-generator.py | 76 + src/grp-journal-remote/microhttpd-util.c | 327 + src/grp-journal-remote/microhttpd-util.h | 60 + .../systemd-journal-gatewayd/Makefile | 68 + .../systemd-journal-gatewayd/journal-gatewayd.c | 1077 +++ .../systemd-journal-remote/Makefile | 85 + .../systemd-journal-remote/journal-remote-parse.c | 506 ++ .../systemd-journal-remote/journal-remote-parse.h | 69 + .../systemd-journal-remote/journal-remote-write.c | 168 + .../systemd-journal-remote/journal-remote-write.h | 70 + .../systemd-journal-remote/journal-remote.c | 1601 ++++ .../systemd-journal-remote/journal-remote.conf.in | 6 + .../systemd-journal-remote/journal-remote.h | 52 + .../systemd-journal-upload/Makefile | 54 + .../journal-upload-journal.c | 422 ++ .../systemd-journal-upload/journal-upload.c | 880 +++ .../systemd-journal-upload/journal-upload.conf.in | 5 + .../systemd-journal-upload/journal-upload.h | 71 + src/grp-journal/.gitignore | 4 + src/grp-journal/Makefile | 170 + src/grp-journal/catalog/systemd.be.catalog | 260 + src/grp-journal/catalog/systemd.be@latin.catalog | 260 + src/grp-journal/catalog/systemd.bg.catalog | 324 + src/grp-journal/catalog/systemd.catalog | 334 + src/grp-journal/catalog/systemd.da.catalog | 261 + src/grp-journal/catalog/systemd.fr.catalog | 320 + src/grp-journal/catalog/systemd.hr.catalog | 314 + src/grp-journal/catalog/systemd.hu.catalog | 262 + src/grp-journal/catalog/systemd.it.catalog | 254 + src/grp-journal/catalog/systemd.ko.catalog | 264 + src/grp-journal/catalog/systemd.pl.catalog | 315 + src/grp-journal/catalog/systemd.pt_BR.catalog | 264 + src/grp-journal/catalog/systemd.ru.catalog | 354 + src/grp-journal/catalog/systemd.sr.catalog | 262 + src/grp-journal/catalog/systemd.zh_CN.catalog | 253 + src/grp-journal/catalog/systemd.zh_TW.catalog | 263 + src/grp-journal/journalctl/Makefile | 49 + src/grp-journal/journalctl/journalctl.c | 2600 +++++++ src/grp-journal/libjournal-core/Makefile | 56 + src/grp-journal/libjournal-core/cat.c | 161 + src/grp-journal/libjournal-core/journal-qrcode.c | 135 + src/grp-journal/libjournal-core/journal-qrcode.h | 27 + src/grp-journal/libjournal-core/journald-audit.c | 564 ++ src/grp-journal/libjournal-core/journald-audit.h | 27 + src/grp-journal/libjournal-core/journald-console.c | 115 + src/grp-journal/libjournal-core/journald-console.h | 24 + .../libjournal-core/journald-gperf.gperf | 46 + src/grp-journal/libjournal-core/journald-kmsg.c | 473 ++ src/grp-journal/libjournal-core/journald-kmsg.h | 29 + src/grp-journal/libjournal-core/journald-native.c | 501 ++ src/grp-journal/libjournal-core/journald-native.h | 35 + .../libjournal-core/journald-rate-limit.c | 271 + .../libjournal-core/journald-rate-limit.h | 28 + src/grp-journal/libjournal-core/journald-server.c | 2007 +++++ src/grp-journal/libjournal-core/journald-server.h | 186 + src/grp-journal/libjournal-core/journald-stream.c | 785 ++ src/grp-journal/libjournal-core/journald-stream.h | 31 + src/grp-journal/libjournal-core/journald-syslog.c | 454 ++ src/grp-journal/libjournal-core/journald-syslog.h | 33 + src/grp-journal/libjournal-core/journald-wall.c | 71 + src/grp-journal/libjournal-core/journald-wall.h | 24 + src/grp-journal/libjournal-core/journald.conf | 41 + src/grp-journal/libjournal-core/test-audit-type.c | 42 + src/grp-journal/libjournal-core/test-catalog.c | 264 + .../libjournal-core/test-compress-benchmark.c | 180 + src/grp-journal/libjournal-core/test-compress.c | 308 + .../libjournal-core/test-journal-enum.c | 53 + .../libjournal-core/test-journal-flush.c | 75 + .../libjournal-core/test-journal-init.c | 64 + .../libjournal-core/test-journal-interleaving.c | 307 + .../libjournal-core/test-journal-match.c | 76 + .../libjournal-core/test-journal-send.c | 102 + .../libjournal-core/test-journal-stream.c | 196 + .../libjournal-core/test-journal-syslog.c | 44 + .../libjournal-core/test-journal-verify.c | 150 + src/grp-journal/libjournal-core/test-journal.c | 178 + src/grp-journal/libjournal-core/test-mmap-cache.c | 79 + src/grp-journal/systemd-journald/Makefile | 94 + src/grp-journal/systemd-journald/journald.c | 120 + src/grp-machine/Makefile | 29 + src/grp-machine/libmachine-core/.gitignore | 1 + src/grp-machine/libmachine-core/Makefile | 52 + src/grp-machine/libmachine-core/image-dbus.c | 422 ++ src/grp-machine/libmachine-core/image-dbus.h | 35 + src/grp-machine/libmachine-core/machine-dbus.c | 1475 ++++ src/grp-machine/libmachine-core/machine-dbus.h | 44 + src/grp-machine/libmachine-core/machine.c | 630 ++ src/grp-machine/libmachine-core/machine.h | 110 + src/grp-machine/libmachine-core/machined-dbus.c | 1660 +++++ src/grp-machine/libmachine-core/machined.h | 82 + src/grp-machine/libmachine-core/operation.c | 131 + src/grp-machine/libmachine-core/operation.h | 47 + .../libmachine-core/org.freedesktop.machine1.conf | 194 + .../org.freedesktop.machine1.policy.in | 102 + .../org.freedesktop.machine1.service | 12 + .../libmachine-core/test-machine-tables.c | 29 + src/grp-machine/machinectl/Makefile | 42 + src/grp-machine/machinectl/machinectl.c | 2782 +++++++ src/grp-machine/nss-mymachines/Makefile | 46 + src/grp-machine/nss-mymachines/nss-mymachines.c | 738 ++ src/grp-machine/nss-mymachines/nss-mymachines.sym | 21 + src/grp-machine/systemd-machined/Makefile | 67 + src/grp-machine/systemd-machined/machined.c | 415 ++ src/grp-resolve/nss-resolve/nss-resolve.c | 675 ++ src/grp-resolve/nss-resolve/nss-resolve.sym | 19 + src/grp-resolve/systemd-resolved/.gitignore | 6 + src/grp-resolve/systemd-resolved/Makefile | 247 + src/grp-resolve/systemd-resolved/RFCs | 59 + src/grp-resolve/systemd-resolved/dns-type.c | 323 + src/grp-resolve/systemd-resolved/dns-type.h | 161 + .../systemd-resolved/org.freedesktop.resolve1.conf | 27 + .../org.freedesktop.resolve1.service | 12 + src/grp-resolve/systemd-resolved/resolve-tool.c | 1484 ++++ src/grp-resolve/systemd-resolved/resolved-bus.c | 1655 +++++ src/grp-resolve/systemd-resolved/resolved-bus.h | 25 + src/grp-resolve/systemd-resolved/resolved-conf.c | 236 + src/grp-resolve/systemd-resolved/resolved-conf.h | 36 + src/grp-resolve/systemd-resolved/resolved-def.h | 38 + .../systemd-resolved/resolved-dns-answer.c | 858 +++ .../systemd-resolved/resolved-dns-answer.h | 143 + .../systemd-resolved/resolved-dns-cache.c | 1050 +++ .../systemd-resolved/resolved-dns-cache.h | 52 + .../systemd-resolved/resolved-dns-dnssec.c | 2199 ++++++ .../systemd-resolved/resolved-dns-dnssec.h | 102 + .../systemd-resolved/resolved-dns-packet.c | 2255 ++++++ .../systemd-resolved/resolved-dns-packet.h | 270 + .../systemd-resolved/resolved-dns-query.c | 1109 +++ .../systemd-resolved/resolved-dns-query.h | 133 + .../systemd-resolved/resolved-dns-question.c | 468 ++ .../systemd-resolved/resolved-dns-question.h | 69 + src/grp-resolve/systemd-resolved/resolved-dns-rr.c | 1594 ++++ src/grp-resolve/systemd-resolved/resolved-dns-rr.h | 342 + .../systemd-resolved/resolved-dns-scope.c | 1028 +++ .../systemd-resolved/resolved-dns-scope.h | 109 + .../systemd-resolved/resolved-dns-search-domain.c | 227 + .../systemd-resolved/resolved-dns-search-domain.h | 74 + .../systemd-resolved/resolved-dns-server.c | 741 ++ .../systemd-resolved/resolved-dns-server.h | 143 + .../systemd-resolved/resolved-dns-stream.c | 403 + .../systemd-resolved/resolved-dns-stream.h | 61 + .../systemd-resolved/resolved-dns-synthesize.c | 413 ++ .../systemd-resolved/resolved-dns-synthesize.h | 30 + .../systemd-resolved/resolved-dns-transaction.c | 3048 ++++++++ .../systemd-resolved/resolved-dns-transaction.h | 174 + .../systemd-resolved/resolved-dns-trust-anchor.c | 743 ++ .../systemd-resolved/resolved-dns-trust-anchor.h | 43 + .../systemd-resolved/resolved-dns-zone.c | 661 ++ .../systemd-resolved/resolved-dns-zone.h | 81 + .../systemd-resolved/resolved-etc-hosts.c | 448 ++ .../systemd-resolved/resolved-etc-hosts.h | 28 + .../systemd-resolved/resolved-gperf.gperf | 21 + .../systemd-resolved/resolved-link-bus.c | 550 ++ .../systemd-resolved/resolved-link-bus.h | 38 + src/grp-resolve/systemd-resolved/resolved-link.c | 840 +++ src/grp-resolve/systemd-resolved/resolved-link.h | 111 + src/grp-resolve/systemd-resolved/resolved-llmnr.c | 476 ++ src/grp-resolve/systemd-resolved/resolved-llmnr.h | 32 + .../systemd-resolved/resolved-manager.c | 1239 ++++ .../systemd-resolved/resolved-manager.h | 171 + src/grp-resolve/systemd-resolved/resolved-mdns.c | 287 + src/grp-resolve/systemd-resolved/resolved-mdns.h | 30 + .../systemd-resolved/resolved-resolv-conf.c | 267 + .../systemd-resolved/resolved-resolv-conf.h | 27 + src/grp-resolve/systemd-resolved/resolved.c | 112 + src/grp-resolve/systemd-resolved/resolved.conf.in | 19 + .../test-data/_443._tcp.fedoraproject.org.pkts | Bin 0 -> 169 bytes .../test-data/_openpgpkey.fedoraproject.org.pkts | Bin 0 -> 986 bytes .../systemd-resolved/test-data/fake-caa.pkts | Bin 0 -> 196 bytes .../test-data/fedoraproject.org.pkts | Bin 0 -> 1483 bytes .../systemd-resolved/test-data/gandi.net.pkts | Bin 0 -> 1010 bytes .../systemd-resolved/test-data/google.com.pkts | Bin 0 -> 747 bytes .../systemd-resolved/test-data/kyhwana.org.pkts | Bin 0 -> 1803 bytes .../systemd-resolved/test-data/root.pkts | Bin 0 -> 1061 bytes ...sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts | Bin 0 -> 330 bytes .../systemd-resolved/test-data/teamits.com.pkts | Bin 0 -> 1021 bytes .../test-data/zbyszek@fedoraproject.org.pkts | Bin 0 -> 2533 bytes src/grp-resolve/systemd-resolved/test-dns-packet.c | 114 + .../systemd-resolved/test-dnssec-complex.c | 236 + src/grp-resolve/systemd-resolved/test-dnssec.c | 343 + .../systemd-resolved/test-resolve-tables.c | 64 + src/grp-system/systemctl/Makefile | 32 + src/grp-system/systemctl/systemctl.c | 7815 ++++++++++++++++++++ .../systemctl/systemd-sysv-install.SKELETON | 47 + src/grp-system/systemd/Makefile | 70 + src/grp-system/systemd/macros.systemd.in | 105 + src/grp-system/systemd/main.c | 2149 ++++++ .../systemd/org.freedesktop.systemd1.conf | 232 + .../systemd/org.freedesktop.systemd1.policy.in.in | 70 + .../systemd/org.freedesktop.systemd1.service | 11 + src/grp-system/systemd/system.conf | 61 + src/grp-system/systemd/systemd.pc.in | 34 + src/grp-system/systemd/triggers.systemd.in | 66 + src/grp-system/systemd/user.conf | 44 + src/grp-timedate/systemd-timedated/.gitignore | 1 + src/grp-timedate/systemd-timedated/Makefile | 65 + .../org.freedesktop.timedate1.conf | 27 + .../org.freedesktop.timedate1.policy.in | 62 + .../org.freedesktop.timedate1.service | 12 + src/grp-timedate/systemd-timedated/timedated.c | 747 ++ src/grp-timedate/timedatectl/Makefile | 42 + src/grp-timedate/timedatectl/timedatectl.c | 506 ++ src/hibernate-resume/Makefile | 1 - src/hibernate-resume/hibernate-resume-generator.c | 99 - src/hibernate-resume/hibernate-resume.c | 82 - src/hostname/Makefile | 80 +- src/hostname/hostnamectl.c | 4 +- src/hwdb/Makefile | 1 - src/hwdb/hwdb.c | 739 -- src/import/Makefile | 193 +- src/import/curl-util.h | 2 +- src/import/export-raw.c | 2 +- src/import/export-raw.h | 2 +- src/import/export-tar.c | 2 +- src/import/export-tar.h | 2 +- src/import/export.c | 2 +- src/import/import-raw.c | 4 +- src/import/import-raw.h | 2 +- src/import/import-tar.c | 4 +- src/import/import-tar.h | 2 +- src/import/import.c | 2 +- src/import/importd.c | 2 +- src/import/pull-raw.c | 2 +- src/import/pull-raw.h | 2 +- src/import/pull-tar.c | 2 +- src/import/pull-tar.h | 2 +- src/import/pull.c | 2 +- src/initctl/Makefile | 33 +- src/initctl/initctl.c | 4 +- src/journal-remote/.gitignore | 2 - src/journal-remote/Makefile | 1 - src/journal-remote/browse.html | 544 -- src/journal-remote/journal-gatewayd.c | 1077 --- src/journal-remote/journal-remote-parse.c | 506 -- src/journal-remote/journal-remote-parse.h | 69 - src/journal-remote/journal-remote-write.c | 168 - src/journal-remote/journal-remote-write.h | 70 - src/journal-remote/journal-remote.c | 1601 ---- src/journal-remote/journal-remote.conf.in | 6 - src/journal-remote/journal-remote.h | 52 - src/journal-remote/journal-upload-journal.c | 422 -- src/journal-remote/journal-upload.c | 880 --- src/journal-remote/journal-upload.conf.in | 5 - src/journal-remote/journal-upload.h | 71 - src/journal-remote/log-generator.py | 76 - src/journal-remote/microhttpd-util.c | 327 - src/journal-remote/microhttpd-util.h | 60 - src/journal/.gitignore | 4 - src/journal/Makefile | 1 - src/journal/audit-type.c | 29 - src/journal/audit-type.h | 37 - src/journal/cat.c | 161 - src/journal/catalog.c | 767 -- src/journal/catalog.h | 36 - src/journal/compress.c | 683 -- src/journal/compress.h | 85 - src/journal/fsprg.c | 374 - src/journal/fsprg.h | 65 - src/journal/journal-authenticate.c | 551 -- src/journal/journal-authenticate.h | 41 - src/journal/journal-def.h | 237 - src/journal/journal-file.c | 3616 --------- src/journal/journal-file.h | 265 - src/journal/journal-internal.h | 143 - src/journal/journal-qrcode.c | 135 - src/journal/journal-qrcode.h | 27 - src/journal/journal-send.c | 559 -- src/journal/journal-vacuum.c | 349 - src/journal/journal-vacuum.h | 27 - src/journal/journal-verify.c | 1294 ---- src/journal/journal-verify.h | 24 - src/journal/journalctl.c | 2600 ------- src/journal/journald-audit.c | 564 -- src/journal/journald-audit.h | 27 - src/journal/journald-console.c | 115 - src/journal/journald-console.h | 24 - src/journal/journald-gperf.gperf | 46 - src/journal/journald-kmsg.c | 473 -- src/journal/journald-kmsg.h | 29 - src/journal/journald-native.c | 501 -- src/journal/journald-native.h | 35 - src/journal/journald-rate-limit.c | 271 - src/journal/journald-rate-limit.h | 28 - src/journal/journald-server.c | 2007 ----- src/journal/journald-server.h | 186 - src/journal/journald-stream.c | 785 -- src/journal/journald-stream.h | 31 - src/journal/journald-syslog.c | 454 -- src/journal/journald-syslog.h | 33 - src/journal/journald-wall.c | 71 - src/journal/journald-wall.h | 24 - src/journal/journald.c | 120 - src/journal/journald.conf | 41 - src/journal/lookup3.c | 1009 --- src/journal/lookup3.h | 22 - src/journal/mmap-cache.c | 725 -- src/journal/mmap-cache.h | 49 - src/journal/sd-journal.c | 2985 -------- src/journal/test-audit-type.c | 42 - src/journal/test-catalog.c | 264 - src/journal/test-compress-benchmark.c | 180 - src/journal/test-compress.c | 308 - src/journal/test-journal-enum.c | 53 - src/journal/test-journal-flush.c | 75 - src/journal/test-journal-init.c | 64 - src/journal/test-journal-interleaving.c | 307 - src/journal/test-journal-match.c | 76 - src/journal/test-journal-send.c | 102 - src/journal/test-journal-stream.c | 196 - src/journal/test-journal-syslog.c | 44 - src/journal/test-journal-verify.c | 150 - src/journal/test-journal.c | 178 - src/journal/test-mmap-cache.c | 79 - src/kernel-install/Makefile | 34 +- src/kernel-install/bash-completion_kernel-install | 50 + src/kernel-install/kernel-install.xml | 192 + src/kernel-install/zsh-completion_kernel-install | 26 + src/libbasic/.gitignore | 16 + src/libbasic/Makefile | 279 + src/libbasic/MurmurHash2.c | 86 + src/libbasic/MurmurHash2.h | 33 + src/libbasic/af-list.c | 56 + src/libbasic/af-list.h | 41 + src/libbasic/alloc-util.c | 83 + src/libbasic/alloc-util.h | 111 + src/libbasic/architecture.c | 179 + src/libbasic/architecture.h | 199 + src/libbasic/arphrd-list.c | 56 + src/libbasic/arphrd-list.h | 25 + src/libbasic/async.c | 94 + src/libbasic/async.h | 25 + src/libbasic/audit-util.c | 104 + src/libbasic/audit-util.h | 31 + src/libbasic/barrier.c | 415 ++ src/libbasic/barrier.h | 91 + src/libbasic/bitmap.c | 220 + src/libbasic/bitmap.h | 50 + src/libbasic/blkid-util.h | 31 + src/libbasic/btrfs-ctree.h | 96 + src/libbasic/btrfs-util.c | 2075 ++++++ src/libbasic/btrfs-util.h | 131 + src/libbasic/build.h | 155 + src/libbasic/bus-label.c | 98 + src/libbasic/bus-label.h | 31 + src/libbasic/calendarspec.c | 1089 +++ src/libbasic/calendarspec.h | 58 + src/libbasic/cap-list.c | 66 + src/libbasic/cap-list.h | 24 + src/libbasic/capability-util.c | 361 + src/libbasic/capability-util.h | 57 + src/libbasic/cgroup-util.c | 2338 ++++++ src/libbasic/cgroup-util.h | 228 + src/libbasic/chattr-util.c | 107 + src/libbasic/chattr-util.h | 26 + src/libbasic/clock-util.c | 165 + src/libbasic/clock-util.h | 29 + src/libbasic/conf-files.c | 166 + src/libbasic/conf-files.h | 25 + src/libbasic/copy.c | 603 ++ src/libbasic/copy.h | 36 + src/libbasic/cpu-set-util.c | 114 + src/libbasic/cpu-set-util.h | 32 + src/libbasic/def.h | 90 + src/libbasic/device-nodes.c | 80 + src/libbasic/device-nodes.h | 26 + src/libbasic/dirent-util.c | 74 + src/libbasic/dirent-util.h | 52 + src/libbasic/env-util.c | 624 ++ src/libbasic/env-util.h | 51 + src/libbasic/errno-list.c | 57 + src/libbasic/errno-list.h | 25 + src/libbasic/escape.c | 502 ++ src/libbasic/escape.h | 54 + src/libbasic/ether-addr-util.c | 125 + src/libbasic/ether-addr-util.h | 39 + src/libbasic/exit-status.c | 240 + src/libbasic/exit-status.h | 102 + src/libbasic/extract-word.c | 298 + src/libbasic/extract-word.h | 35 + src/libbasic/fd-util.c | 374 + src/libbasic/fd-util.h | 79 + src/libbasic/fdset.c | 273 + src/libbasic/fdset.h | 58 + src/libbasic/fileio-label.c | 68 + src/libbasic/fileio-label.h | 30 + src/libbasic/fileio.c | 1356 ++++ src/libbasic/fileio.h | 88 + src/libbasic/formats-util.h | 63 + src/libbasic/fs-util.c | 510 ++ src/libbasic/fs-util.h | 76 + src/libbasic/glob-util.c | 70 + src/libbasic/glob-util.h | 36 + src/libbasic/gunicode.c | 112 + src/libbasic/gunicode.h | 30 + src/libbasic/hash-funcs.c | 81 + src/libbasic/hash-funcs.h | 65 + src/libbasic/hashmap.c | 1803 +++++ src/libbasic/hashmap.h | 372 + src/libbasic/hexdecoct.c | 754 ++ src/libbasic/hexdecoct.h | 56 + src/libbasic/hostname-util.c | 252 + src/libbasic/hostname-util.h | 41 + src/libbasic/in-addr-util.c | 356 + src/libbasic/in-addr-util.h | 59 + src/libbasic/io-util.c | 269 + src/libbasic/io-util.h | 95 + src/libbasic/ioprio.h | 55 + src/libbasic/label.c | 82 + src/libbasic/label.h | 28 + src/libbasic/list.h | 182 + src/libbasic/locale-util.c | 322 + src/libbasic/locale-util.h | 73 + src/libbasic/lockfile-util.c | 153 + src/libbasic/lockfile-util.h | 39 + src/libbasic/log.c | 1170 +++ src/libbasic/log.h | 249 + src/libbasic/login-util.c | 31 + src/libbasic/login-util.h | 29 + src/libbasic/macro.h | 406 + src/libbasic/memfd-util.c | 174 + src/libbasic/memfd-util.h | 36 + src/libbasic/mempool.c | 104 + src/libbasic/mempool.h | 47 + src/libbasic/missing.h | 1016 +++ src/libbasic/missing_syscall.h | 310 + src/libbasic/mkdir-label.c | 38 + src/libbasic/mkdir.c | 128 + src/libbasic/mkdir.h | 38 + src/libbasic/mount-util.c | 533 ++ src/libbasic/mount-util.h | 52 + src/libbasic/nss-util.h | 199 + src/libbasic/ordered-set.c | 64 + src/libbasic/ordered-set.h | 74 + src/libbasic/parse-util.c | 534 ++ src/libbasic/parse-util.h | 107 + src/libbasic/path-util.c | 816 ++ src/libbasic/path-util.h | 127 + src/libbasic/prioq.c | 320 + src/libbasic/prioq.h | 43 + src/libbasic/proc-cmdline.c | 176 + src/libbasic/proc-cmdline.h | 27 + src/libbasic/process-util.c | 782 ++ src/libbasic/process-util.h | 105 + src/libbasic/random-util.c | 133 + src/libbasic/random-util.h | 39 + src/libbasic/ratelimit.c | 56 + src/libbasic/ratelimit.h | 58 + src/libbasic/refcnt.h | 34 + src/libbasic/replace-var.c | 112 + src/libbasic/replace-var.h | 22 + src/libbasic/rlimit-util.c | 321 + src/libbasic/rlimit-util.h | 36 + src/libbasic/rm-rf.c | 236 + src/libbasic/rm-rf.h | 41 + src/libbasic/securebits.h | 45 + src/libbasic/selinux-util.c | 485 ++ src/libbasic/selinux-util.h | 51 + src/libbasic/set.h | 136 + src/libbasic/sigbus.c | 152 + src/libbasic/sigbus.h | 25 + src/libbasic/signal-util.c | 278 + src/libbasic/signal-util.h | 56 + src/libbasic/siphash24.c | 191 + src/libbasic/siphash24.h | 23 + src/libbasic/smack-util.c | 241 + src/libbasic/smack-util.h | 54 + src/libbasic/socket-label.c | 170 + src/libbasic/socket-util.c | 1050 +++ src/libbasic/socket-util.h | 154 + src/libbasic/sparse-endian.h | 88 + src/libbasic/special.h | 119 + src/libbasic/stat-util.c | 218 + src/libbasic/stat-util.h | 69 + src/libbasic/stdio-util.h | 76 + src/libbasic/strbuf.c | 205 + src/libbasic/strbuf.h | 54 + src/libbasic/string-table.c | 34 + src/libbasic/string-table.h | 117 + src/libbasic/string-util.c | 855 +++ src/libbasic/string-util.h | 190 + src/libbasic/strv.c | 927 +++ src/libbasic/strv.h | 172 + src/libbasic/strxcpyx.c | 100 + src/libbasic/strxcpyx.h | 31 + src/libbasic/syslog-util.c | 114 + src/libbasic/syslog-util.h | 32 + src/libbasic/terminal-util.c | 1153 +++ src/libbasic/terminal-util.h | 126 + src/libbasic/time-util.c | 1157 +++ src/libbasic/time-util.h | 153 + src/libbasic/umask-util.h | 46 + src/libbasic/unaligned.h | 111 + src/libbasic/unit-name.c | 1049 +++ src/libbasic/unit-name.h | 368 + src/libbasic/user-util.c | 481 ++ src/libbasic/user-util.h | 70 + src/libbasic/utf8.c | 409 + src/libbasic/utf8.h | 60 + src/libbasic/util.c | 808 ++ src/libbasic/util.h | 189 + src/libbasic/verbs.c | 101 + src/libbasic/verbs.h | 33 + src/libbasic/virt.c | 516 ++ src/libbasic/virt.h | 72 + src/libbasic/web-util.c | 76 + src/libbasic/web-util.h | 30 + src/libbasic/xattr-util.c | 200 + src/libbasic/xattr-util.h | 37 + src/libbasic/xml.c | 255 + src/libbasic/xml.h | 32 + src/libcore/.gitignore | 3 + src/libcore/Makefile | 170 + src/libcore/audit-fd.c | 73 + src/libcore/audit-fd.h | 23 + src/libcore/automount.c | 1108 +++ src/libcore/automount.h | 59 + src/libcore/bus-policy.c | 180 + src/libcore/bus-policy.h | 64 + src/libcore/busname.c | 1082 +++ src/libcore/busname.h | 69 + src/libcore/cgroup.c | 1895 +++++ src/libcore/cgroup.h | 181 + src/libcore/dbus-automount.c | 34 + src/libcore/dbus-automount.h | 23 + src/libcore/dbus-busname.c | 37 + src/libcore/dbus-busname.h | 23 + src/libcore/dbus-cgroup.c | 1004 +++ src/libcore/dbus-cgroup.h | 28 + src/libcore/dbus-device.c | 28 + src/libcore/dbus-device.h | 24 + src/libcore/dbus-execute.c | 1545 ++++ src/libcore/dbus-execute.h | 45 + src/libcore/dbus-job.c | 193 + src/libcore/dbus-job.h | 31 + src/libcore/dbus-kill.c | 122 + src/libcore/dbus-kill.h | 29 + src/libcore/dbus-manager.c | 2305 ++++++ src/libcore/dbus-manager.h | 28 + src/libcore/dbus-mount.c | 211 + src/libcore/dbus-mount.h | 29 + src/libcore/dbus-path.c | 86 + src/libcore/dbus-path.h | 24 + src/libcore/dbus-scope.c | 229 + src/libcore/dbus-scope.h | 31 + src/libcore/dbus-service.c | 322 + src/libcore/dbus-service.h | 29 + src/libcore/dbus-slice.c | 52 + src/libcore/dbus-slice.h | 29 + src/libcore/dbus-socket.c | 184 + src/libcore/dbus-socket.h | 29 + src/libcore/dbus-swap.c | 115 + src/libcore/dbus-swap.h | 30 + src/libcore/dbus-target.c | 26 + src/libcore/dbus-target.h | 24 + src/libcore/dbus-timer.c | 352 + src/libcore/dbus-timer.h | 28 + src/libcore/dbus-unit.c | 1423 ++++ src/libcore/dbus-unit.h | 41 + src/libcore/dbus.c | 1243 ++++ src/libcore/dbus.h | 44 + src/libcore/device.c | 876 +++ src/libcore/device.h | 47 + src/libcore/execute.c | 3092 ++++++++ src/libcore/execute.h | 287 + src/libcore/failure-action.c | 127 + src/libcore/failure-action.h | 41 + src/libcore/hostname-setup.c | 68 + src/libcore/hostname-setup.h | 22 + src/libcore/ima-setup.c | 80 + src/libcore/ima-setup.h | 24 + src/libcore/job.c | 1259 ++++ src/libcore/job.h | 242 + src/libcore/kill.c | 68 + src/libcore/kill.h | 65 + src/libcore/killall.c | 249 + src/libcore/killall.h | 22 + src/libcore/kmod-setup.c | 131 + src/libcore/kmod-setup.h | 22 + src/libcore/linux/auto_dev-ioctl.h | 228 + src/libcore/load-dropin.c | 90 + src/libcore/load-dropin.h | 34 + src/libcore/load-fragment-gperf.gperf.m4 | 391 + src/libcore/load-fragment.c | 4083 ++++++++++ src/libcore/load-fragment.h | 123 + src/libcore/locale-setup.c | 124 + src/libcore/locale-setup.h | 22 + src/libcore/loopback-setup.c | 90 + src/libcore/loopback-setup.h | 22 + src/libcore/machine-id-setup.c | 364 + src/libcore/machine-id-setup.h | 23 + src/libcore/manager.c | 3134 ++++++++ src/libcore/manager.h | 379 + src/libcore/mount-setup.c | 413 ++ src/libcore/mount-setup.h | 30 + src/libcore/mount.c | 1881 +++++ src/libcore/mount.h | 108 + src/libcore/namespace.c | 650 ++ src/libcore/namespace.h | 63 + src/libcore/path.c | 784 ++ src/libcore/path.h | 93 + src/libcore/scope.c | 608 ++ src/libcore/scope.h | 55 + src/libcore/selinux-access.c | 283 + src/libcore/selinux-access.h | 45 + src/libcore/selinux-setup.c | 121 + src/libcore/selinux-setup.h | 24 + src/libcore/service.c | 3353 +++++++++ src/libcore/service.h | 220 + src/libcore/show-status.c | 124 + src/libcore/show-status.h | 39 + src/libcore/shutdown.c | 440 ++ src/libcore/slice.c | 346 + src/libcore/slice.h | 32 + src/libcore/smack-setup.c | 346 + src/libcore/smack-setup.h | 24 + src/libcore/socket.c | 2992 ++++++++ src/libcore/socket.h | 185 + src/libcore/swap.c | 1532 ++++ src/libcore/swap.h | 110 + src/libcore/target.c | 223 + src/libcore/target.h | 30 + src/libcore/timer.c | 859 +++ src/libcore/timer.h | 89 + src/libcore/transaction.c | 1099 +++ src/libcore/transaction.h | 51 + src/libcore/umount.c | 614 ++ src/libcore/umount.h | 28 + src/libcore/unit-printf.c | 304 + src/libcore/unit-printf.h | 26 + src/libcore/unit.c | 3818 ++++++++++ src/libcore/unit.h | 639 ++ src/libfirewall/Makefile | 42 + src/libfirewall/firewall-util.c | 357 + src/libfirewall/firewall-util.h | 83 + src/libshared/Makefile | 149 + src/libshared/acl-util.c | 429 ++ src/libshared/acl-util.h | 48 + src/libshared/acpi-fpdt.c | 164 + src/libshared/acpi-fpdt.h | 24 + src/libshared/apparmor-util.c | 39 + src/libshared/apparmor-util.h | 24 + src/libshared/ask-password-api.c | 736 ++ src/libshared/ask-password-api.h | 38 + src/libshared/base-filesystem.c | 129 + src/libshared/base-filesystem.h | 24 + src/libshared/boot-timestamps.c | 64 + src/libshared/boot-timestamps.h | 25 + src/libshared/bus-unit-util.c | 1307 ++++ src/libshared/bus-unit-util.h | 57 + src/libshared/bus-util.c | 1571 ++++ src/libshared/bus-util.h | 163 + src/libshared/cgroup-show.c | 312 + src/libshared/cgroup-show.h | 32 + src/libshared/clean-ipc.c | 365 + src/libshared/clean-ipc.h | 24 + src/libshared/condition.c | 541 ++ src/libshared/condition.h | 94 + src/libshared/conf-parser.c | 913 +++ src/libshared/conf-parser.h | 229 + src/libshared/dev-setup.c | 73 + src/libshared/dev-setup.h | 24 + src/libshared/dns-domain.c | 1322 ++++ src/libshared/dns-domain.h | 109 + src/libshared/dropin.c | 251 + src/libshared/dropin.h | 61 + src/libshared/efivars.c | 715 ++ src/libshared/efivars.h | 131 + src/libshared/fstab-util.c | 263 + src/libshared/fstab-util.h | 52 + src/libshared/gcrypt-util.c | 71 + src/libshared/gcrypt-util.h | 39 + src/libshared/generator.c | 207 + src/libshared/generator.h | 40 + src/libshared/gpt.h | 66 + src/libshared/ima-util.c | 32 + src/libshared/ima-util.h | 24 + src/libshared/import-util.c | 185 + src/libshared/import-util.h | 43 + src/libshared/initreq.h | 77 + src/libshared/install-printf.c | 133 + src/libshared/install-printf.h | 24 + src/libshared/install.c | 2953 ++++++++ src/libshared/install.h | 256 + src/libshared/local-addresses.c | 275 + src/libshared/local-addresses.h | 36 + src/libshared/logs-show.c | 1310 ++++ src/libshared/logs-show.h | 70 + src/libshared/machine-image.c | 818 ++ src/libshared/machine-image.h | 103 + src/libshared/machine-pool.c | 426 ++ src/libshared/machine-pool.h | 30 + src/libshared/output-mode.c | 37 + src/libshared/output-mode.h | 57 + src/libshared/pager.c | 226 + src/libshared/pager.h | 30 + src/libshared/path-lookup.c | 822 ++ src/libshared/path-lookup.h | 76 + src/libshared/ptyfwd.c | 484 ++ src/libshared/ptyfwd.h | 48 + src/libshared/resolve-util.c | 39 + src/libshared/resolve-util.h | 60 + src/libshared/seccomp-util.c | 90 + src/libshared/seccomp-util.h | 28 + src/libshared/sleep-config.c | 278 + src/libshared/sleep-config.h | 26 + src/libshared/spawn-ask-password-agent.c | 62 + src/libshared/spawn-ask-password-agent.h | 23 + src/libshared/spawn-polkit-agent.c | 102 + src/libshared/spawn-polkit-agent.h | 23 + src/libshared/specifier.c | 188 + src/libshared/specifier.h | 37 + src/libshared/switch-root.c | 156 + src/libshared/switch-root.h | 23 + src/libshared/sysctl-util.c | 73 + src/libshared/sysctl-util.h | 25 + src/libshared/test-local-addresses.c | 56 + src/libshared/test-tables.h | 60 + src/libshared/tests.c | 33 + src/libshared/tests.h | 22 + src/libshared/udev-util.h | 44 + src/libshared/uid-range.c | 208 + src/libshared/uid-range.h | 33 + src/libshared/utmp-wtmp.c | 445 ++ src/libshared/utmp-wtmp.h | 74 + src/libshared/watchdog.c | 164 + src/libshared/watchdog.h | 29 + src/libsystemd-network/Makefile | 174 +- src/libsystemd-network/dhcp-identifier.c | 2 +- src/libsystemd-network/dhcp-identifier.h | 2 +- src/libsystemd-network/dhcp-internal.h | 2 +- src/libsystemd-network/dhcp-lease-internal.h | 2 +- src/libsystemd-network/dhcp-server-internal.h | 4 +- src/libsystemd-network/dhcp6-internal.h | 2 +- src/libsystemd-network/dhcp6-lease-internal.h | 2 +- src/libsystemd-network/dhcp6-option.c | 2 +- src/libsystemd-network/lldp-internal.h | 4 +- src/libsystemd-network/lldp-neighbor.h | 2 +- src/libsystemd-network/lldp-network.h | 2 +- src/libsystemd-network/network-internal.c | 2 +- src/libsystemd-network/network-internal.h | 2 +- src/libsystemd-network/sd-dhcp-client.c | 2 +- src/libsystemd-network/sd-dhcp-lease.c | 2 +- src/libsystemd-network/sd-dhcp-server.c | 2 +- src/libsystemd-network/sd-dhcp6-client.c | 2 +- src/libsystemd-network/sd-ipv4acd.c | 2 +- src/libsystemd-network/sd-ipv4ll.c | 4 +- src/libsystemd-network/sd-lldp.c | 2 +- src/libsystemd-network/sd-ndisc.c | 2 +- src/libsystemd-network/test-acd.c | 6 +- src/libsystemd-network/test-dhcp-client.c | 4 +- src/libsystemd-network/test-dhcp-server.c | 4 +- src/libsystemd-network/test-dhcp6-client.c | 4 +- src/libsystemd-network/test-ipv4ll-manual.c | 6 +- src/libsystemd-network/test-ipv4ll.c | 2 +- src/libsystemd-network/test-lldp.c | 4 +- src/libsystemd-network/test-ndisc-rs.c | 2 +- src/libsystemd/Makefile | 111 +- src/libsystemd/include/systemd/_sd-common.h | 83 + src/libsystemd/include/systemd/sd-bus-protocol.h | 102 + src/libsystemd/include/systemd/sd-bus-vtable.h | 141 + src/libsystemd/include/systemd/sd-bus.h | 456 ++ src/libsystemd/include/systemd/sd-daemon.h | 289 + src/libsystemd/include/systemd/sd-device.h | 101 + src/libsystemd/include/systemd/sd-dhcp-client.h | 158 + src/libsystemd/include/systemd/sd-dhcp-lease.h | 67 + src/libsystemd/include/systemd/sd-dhcp-server.h | 65 + src/libsystemd/include/systemd/sd-dhcp6-client.h | 135 + src/libsystemd/include/systemd/sd-dhcp6-lease.h | 52 + src/libsystemd/include/systemd/sd-event.h | 142 + src/libsystemd/include/systemd/sd-hwdb.h | 49 + src/libsystemd/include/systemd/sd-id128.h | 115 + src/libsystemd/include/systemd/sd-ipv4acd.h | 60 + src/libsystemd/include/systemd/sd-ipv4ll.h | 60 + src/libsystemd/include/systemd/sd-journal.h | 175 + src/libsystemd/include/systemd/sd-lldp.h | 177 + src/libsystemd/include/systemd/sd-login.h | 245 + src/libsystemd/include/systemd/sd-messages.h | 91 + src/libsystemd/include/systemd/sd-ndisc.h | 84 + src/libsystemd/include/systemd/sd-netlink.h | 163 + src/libsystemd/include/systemd/sd-network.h | 176 + src/libsystemd/include/systemd/sd-path.h | 91 + src/libsystemd/include/systemd/sd-resolve.h | 117 + src/libsystemd/include/systemd/sd-utf8.h | 32 + src/libsystemd/libsystemd-internal/Makefile | 247 + .../libsystemd-internal/sd-bus/DIFFERENCES | 25 + .../sd-bus/GVARIANT-SERIALIZATION | 110 + .../libsystemd-internal/sd-bus/PORTING-DBUS1 | 535 ++ .../libsystemd-internal/sd-bus/bus-bloom.c | 156 + .../libsystemd-internal/sd-bus/bus-bloom.h | 43 + .../libsystemd-internal/sd-bus/bus-common-errors.c | 87 + .../libsystemd-internal/sd-bus/bus-common-errors.h | 86 + .../libsystemd-internal/sd-bus/bus-container.c | 277 + .../libsystemd-internal/sd-bus/bus-container.h | 25 + .../libsystemd-internal/sd-bus/bus-control.c | 1588 ++++ .../libsystemd-internal/sd-bus/bus-control.h | 32 + .../libsystemd-internal/sd-bus/bus-convenience.c | 626 ++ .../libsystemd-internal/sd-bus/bus-creds.c | 1349 ++++ .../libsystemd-internal/sd-bus/bus-creds.h | 90 + .../libsystemd-internal/sd-bus/bus-dump.c | 602 ++ .../libsystemd-internal/sd-bus/bus-dump.h | 37 + .../libsystemd-internal/sd-bus/bus-error.c | 608 ++ .../libsystemd-internal/sd-bus/bus-error.h | 64 + .../libsystemd-internal/sd-bus/bus-gvariant.c | 311 + .../libsystemd-internal/sd-bus/bus-gvariant.h | 30 + .../libsystemd-internal/sd-bus/bus-internal.c | 374 + .../libsystemd-internal/sd-bus/bus-internal.h | 399 + .../libsystemd-internal/sd-bus/bus-introspect.c | 212 + .../libsystemd-internal/sd-bus/bus-introspect.h | 40 + .../libsystemd-internal/sd-bus/bus-kernel.c | 1782 +++++ .../libsystemd-internal/sd-bus/bus-kernel.h | 93 + .../libsystemd-internal/sd-bus/bus-match.c | 1218 +++ .../libsystemd-internal/sd-bus/bus-match.h | 100 + .../libsystemd-internal/sd-bus/bus-message.c | 5939 +++++++++++++++ .../libsystemd-internal/sd-bus/bus-message.h | 244 + .../libsystemd-internal/sd-bus/bus-objects.c | 2806 +++++++ .../libsystemd-internal/sd-bus/bus-objects.h | 25 + .../libsystemd-internal/sd-bus/bus-protocol.h | 180 + .../libsystemd-internal/sd-bus/bus-signature.c | 158 + .../libsystemd-internal/sd-bus/bus-signature.h | 28 + .../libsystemd-internal/sd-bus/bus-slot.c | 286 + .../libsystemd-internal/sd-bus/bus-slot.h | 28 + .../libsystemd-internal/sd-bus/bus-socket.c | 1064 +++ .../libsystemd-internal/sd-bus/bus-socket.h | 37 + .../libsystemd-internal/sd-bus/bus-track.c | 337 + .../libsystemd-internal/sd-bus/bus-track.h | 22 + .../libsystemd-internal/sd-bus/bus-type.c | 176 + .../libsystemd-internal/sd-bus/bus-type.h | 37 + src/libsystemd/libsystemd-internal/sd-bus/kdbus.h | 980 +++ src/libsystemd/libsystemd-internal/sd-bus/sd-bus.c | 3791 ++++++++++ .../sd-bus/test-bus-benchmark.c | 371 + .../libsystemd-internal/sd-bus/test-bus-chat.c | 560 ++ .../libsystemd-internal/sd-bus/test-bus-cleanup.c | 95 + .../libsystemd-internal/sd-bus/test-bus-creds.c | 50 + .../libsystemd-internal/sd-bus/test-bus-error.c | 232 + .../libsystemd-internal/sd-bus/test-bus-gvariant.c | 224 + .../sd-bus/test-bus-introspect.c | 63 + .../sd-bus/test-bus-kernel-bloom.c | 141 + .../libsystemd-internal/sd-bus/test-bus-kernel.c | 190 + .../libsystemd-internal/sd-bus/test-bus-marshal.c | 432 ++ .../libsystemd-internal/sd-bus/test-bus-match.c | 159 + .../libsystemd-internal/sd-bus/test-bus-objects.c | 555 ++ .../libsystemd-internal/sd-bus/test-bus-server.c | 216 + .../sd-bus/test-bus-signature.c | 164 + .../sd-bus/test-bus-zero-copy.c | 210 + .../libsystemd-internal/sd-daemon/sd-daemon.c | 622 ++ .../sd-device/device-enumerator-private.h | 34 + .../sd-device/device-enumerator.c | 986 +++ .../sd-device/device-internal.h | 126 + .../libsystemd-internal/sd-device/device-private.c | 1119 +++ .../libsystemd-internal/sd-device/device-private.h | 68 + .../libsystemd-internal/sd-device/device-util.h | 52 + .../libsystemd-internal/sd-device/sd-device.c | 1884 +++++ .../libsystemd-internal/sd-event/sd-event.c | 2898 ++++++++ .../libsystemd-internal/sd-event/test-event.c | 361 + .../libsystemd-internal/sd-hwdb/hwdb-internal.h | 72 + .../libsystemd-internal/sd-hwdb/hwdb-util.h | 26 + .../libsystemd-internal/sd-hwdb/sd-hwdb.c | 470 ++ .../libsystemd-internal/sd-id128/sd-id128.c | 225 + .../libsystemd-internal/sd-login/sd-login.c | 1062 +++ .../libsystemd-internal/sd-login/test-login.c | 264 + .../sd-netlink/netlink-internal.h | 137 + .../sd-netlink/netlink-message.c | 961 +++ .../sd-netlink/netlink-socket.c | 474 ++ .../libsystemd-internal/sd-netlink/netlink-types.c | 684 ++ .../libsystemd-internal/sd-netlink/netlink-types.h | 94 + .../libsystemd-internal/sd-netlink/netlink-util.c | 170 + .../libsystemd-internal/sd-netlink/netlink-util.h | 39 + .../libsystemd-internal/sd-netlink/rtnl-message.c | 702 ++ .../libsystemd-internal/sd-netlink/sd-netlink.c | 954 +++ .../libsystemd-internal/sd-netlink/test-netlink.c | 440 ++ .../libsystemd-internal/sd-network/network-util.c | 37 + .../libsystemd-internal/sd-network/network-util.h | 24 + .../libsystemd-internal/sd-network/sd-network.c | 400 + .../libsystemd-internal/sd-path/sd-path.c | 638 ++ .../libsystemd-internal/sd-resolve/sd-resolve.c | 1245 ++++ .../libsystemd-internal/sd-resolve/test-resolve.c | 114 + .../libsystemd-internal/sd-utf8/sd-utf8.c | 35 + src/libsystemd/libsystemd-internal/subdir.mk | 29 + .../libsystemd-journal-internal/Makefile | 113 + .../libsystemd-journal-internal/audit-type.c | 29 + .../libsystemd-journal-internal/audit-type.h | 37 + .../libsystemd-journal-internal/catalog.c | 767 ++ .../libsystemd-journal-internal/catalog.h | 36 + .../libsystemd-journal-internal/compress.c | 683 ++ .../libsystemd-journal-internal/compress.h | 85 + src/libsystemd/libsystemd-journal-internal/fsprg.c | 374 + src/libsystemd/libsystemd-journal-internal/fsprg.h | 65 + .../journal-authenticate.c | 551 ++ .../journal-authenticate.h | 41 + .../libsystemd-journal-internal/journal-def.h | 237 + .../libsystemd-journal-internal/journal-file.c | 3616 +++++++++ .../libsystemd-journal-internal/journal-file.h | 265 + .../libsystemd-journal-internal/journal-internal.h | 143 + .../libsystemd-journal-internal/journal-send.c | 559 ++ .../libsystemd-journal-internal/journal-vacuum.c | 349 + .../libsystemd-journal-internal/journal-vacuum.h | 27 + .../libsystemd-journal-internal/journal-verify.c | 1294 ++++ .../libsystemd-journal-internal/journal-verify.h | 24 + .../libsystemd-journal-internal/lookup3.c | 1009 +++ .../libsystemd-journal-internal/lookup3.h | 22 + .../libsystemd-journal-internal/mmap-cache.c | 725 ++ .../libsystemd-journal-internal/mmap-cache.h | 49 + .../libsystemd-journal-internal/sd-journal.c | 2985 ++++++++ src/libsystemd/sd-bus/DIFFERENCES | 25 - src/libsystemd/sd-bus/GVARIANT-SERIALIZATION | 110 - src/libsystemd/sd-bus/Makefile | 1 - src/libsystemd/sd-bus/PORTING-DBUS1 | 535 -- src/libsystemd/sd-bus/bus-bloom.c | 156 - src/libsystemd/sd-bus/bus-bloom.h | 43 - src/libsystemd/sd-bus/bus-common-errors.c | 87 - src/libsystemd/sd-bus/bus-common-errors.h | 86 - src/libsystemd/sd-bus/bus-container.c | 277 - src/libsystemd/sd-bus/bus-container.h | 25 - src/libsystemd/sd-bus/bus-control.c | 1588 ---- src/libsystemd/sd-bus/bus-control.h | 32 - src/libsystemd/sd-bus/bus-convenience.c | 626 -- src/libsystemd/sd-bus/bus-creds.c | 1349 ---- src/libsystemd/sd-bus/bus-creds.h | 90 - src/libsystemd/sd-bus/bus-dump.c | 602 -- src/libsystemd/sd-bus/bus-dump.h | 37 - src/libsystemd/sd-bus/bus-error.c | 608 -- src/libsystemd/sd-bus/bus-error.h | 64 - src/libsystemd/sd-bus/bus-gvariant.c | 311 - src/libsystemd/sd-bus/bus-gvariant.h | 30 - src/libsystemd/sd-bus/bus-internal.c | 374 - src/libsystemd/sd-bus/bus-internal.h | 399 - src/libsystemd/sd-bus/bus-introspect.c | 212 - src/libsystemd/sd-bus/bus-introspect.h | 40 - src/libsystemd/sd-bus/bus-kernel.c | 1782 ----- src/libsystemd/sd-bus/bus-kernel.h | 93 - src/libsystemd/sd-bus/bus-match.c | 1218 --- src/libsystemd/sd-bus/bus-match.h | 100 - src/libsystemd/sd-bus/bus-message.c | 5939 --------------- src/libsystemd/sd-bus/bus-message.h | 244 - src/libsystemd/sd-bus/bus-objects.c | 2806 ------- src/libsystemd/sd-bus/bus-objects.h | 25 - src/libsystemd/sd-bus/bus-protocol.h | 180 - src/libsystemd/sd-bus/bus-signature.c | 158 - src/libsystemd/sd-bus/bus-signature.h | 28 - src/libsystemd/sd-bus/bus-slot.c | 286 - src/libsystemd/sd-bus/bus-slot.h | 28 - src/libsystemd/sd-bus/bus-socket.c | 1064 --- src/libsystemd/sd-bus/bus-socket.h | 37 - src/libsystemd/sd-bus/bus-track.c | 337 - src/libsystemd/sd-bus/bus-track.h | 22 - src/libsystemd/sd-bus/bus-type.c | 176 - src/libsystemd/sd-bus/bus-type.h | 37 - src/libsystemd/sd-bus/busctl-introspect.c | 790 -- src/libsystemd/sd-bus/busctl-introspect.h | 32 - src/libsystemd/sd-bus/busctl.c | 2086 ------ src/libsystemd/sd-bus/kdbus.h | 980 --- src/libsystemd/sd-bus/sd-bus.c | 3791 ---------- src/libsystemd/sd-bus/test-bus-benchmark.c | 371 - src/libsystemd/sd-bus/test-bus-chat.c | 560 -- src/libsystemd/sd-bus/test-bus-cleanup.c | 95 - src/libsystemd/sd-bus/test-bus-creds.c | 50 - src/libsystemd/sd-bus/test-bus-error.c | 232 - src/libsystemd/sd-bus/test-bus-gvariant.c | 224 - src/libsystemd/sd-bus/test-bus-introspect.c | 63 - src/libsystemd/sd-bus/test-bus-kernel-bloom.c | 141 - src/libsystemd/sd-bus/test-bus-kernel.c | 190 - src/libsystemd/sd-bus/test-bus-marshal.c | 432 -- src/libsystemd/sd-bus/test-bus-match.c | 159 - src/libsystemd/sd-bus/test-bus-objects.c | 555 -- src/libsystemd/sd-bus/test-bus-server.c | 216 - src/libsystemd/sd-bus/test-bus-signature.c | 164 - src/libsystemd/sd-bus/test-bus-zero-copy.c | 210 - src/libsystemd/sd-daemon/Makefile | 1 - src/libsystemd/sd-daemon/sd-daemon.c | 622 -- src/libsystemd/sd-device/Makefile | 1 - .../sd-device/device-enumerator-private.h | 34 - src/libsystemd/sd-device/device-enumerator.c | 986 --- src/libsystemd/sd-device/device-internal.h | 126 - src/libsystemd/sd-device/device-private.c | 1119 --- src/libsystemd/sd-device/device-private.h | 68 - src/libsystemd/sd-device/device-util.h | 52 - src/libsystemd/sd-device/sd-device.c | 1884 ----- src/libsystemd/sd-event/Makefile | 1 - src/libsystemd/sd-event/sd-event.c | 2898 -------- src/libsystemd/sd-event/test-event.c | 361 - src/libsystemd/sd-hwdb/Makefile | 1 - src/libsystemd/sd-hwdb/hwdb-internal.h | 72 - src/libsystemd/sd-hwdb/hwdb-util.h | 26 - src/libsystemd/sd-hwdb/sd-hwdb.c | 470 -- src/libsystemd/sd-id128/Makefile | 1 - src/libsystemd/sd-id128/sd-id128.c | 225 - src/libsystemd/sd-login/Makefile | 1 - src/libsystemd/sd-login/sd-login.c | 1062 --- src/libsystemd/sd-login/test-login.c | 264 - src/libsystemd/sd-netlink/Makefile | 1 - src/libsystemd/sd-netlink/local-addresses.c | 275 - src/libsystemd/sd-netlink/local-addresses.h | 36 - src/libsystemd/sd-netlink/netlink-internal.h | 137 - src/libsystemd/sd-netlink/netlink-message.c | 961 --- src/libsystemd/sd-netlink/netlink-socket.c | 474 -- src/libsystemd/sd-netlink/netlink-types.c | 684 -- src/libsystemd/sd-netlink/netlink-types.h | 94 - src/libsystemd/sd-netlink/netlink-util.c | 170 - src/libsystemd/sd-netlink/netlink-util.h | 39 - src/libsystemd/sd-netlink/rtnl-message.c | 702 -- src/libsystemd/sd-netlink/sd-netlink.c | 954 --- src/libsystemd/sd-netlink/test-local-addresses.c | 56 - src/libsystemd/sd-netlink/test-netlink.c | 440 -- src/libsystemd/sd-network/Makefile | 1 - src/libsystemd/sd-network/network-util.c | 37 - src/libsystemd/sd-network/network-util.h | 24 - src/libsystemd/sd-network/sd-network.c | 400 - src/libsystemd/sd-path/Makefile | 1 - src/libsystemd/sd-path/sd-path.c | 638 -- src/libsystemd/sd-resolve/Makefile | 1 - src/libsystemd/sd-resolve/sd-resolve.c | 1245 ---- src/libsystemd/sd-resolve/test-resolve.c | 114 - src/libsystemd/sd-utf8/Makefile | 1 - src/libsystemd/sd-utf8/sd-utf8.c | 35 - src/libudev/.gitignore | 1 - src/libudev/Makefile | 29 +- src/libudev/include/libudev.h | 207 + src/libudev/libudev-device-internal.h | 58 - src/libudev/libudev-device-private.c | 411 - src/libudev/libudev-device.c | 958 --- src/libudev/libudev-enumerate.c | 419 -- src/libudev/libudev-hwdb.c | 146 - src/libudev/libudev-list.c | 352 - src/libudev/libudev-monitor.c | 845 --- src/libudev/libudev-private.h | 150 - src/libudev/libudev-queue.c | 268 - src/libudev/libudev-util.c | 268 - src/libudev/libudev.c | 253 - src/libudev/libudev.h | 207 - src/libudev/libudev.pc.in | 17 - src/libudev/libudev.sym | 120 - src/libudev/src/.gitignore | 1 + src/libudev/src/Makefile | 77 + src/libudev/src/libudev-device-internal.h | 58 + src/libudev/src/libudev-device-private.c | 411 + src/libudev/src/libudev-device.c | 958 +++ src/libudev/src/libudev-enumerate.c | 419 ++ src/libudev/src/libudev-hwdb.c | 146 + src/libudev/src/libudev-list.c | 352 + src/libudev/src/libudev-monitor.c | 845 +++ src/libudev/src/libudev-private.h | 150 + src/libudev/src/libudev-queue.c | 268 + src/libudev/src/libudev-util.c | 268 + src/libudev/src/libudev.c | 253 + src/libudev/src/libudev.pc.in | 17 + src/libudev/src/libudev.sym | 120 + src/libudev/src/udev.h | 216 + src/locale/Makefile | 91 +- src/locale/localectl.c | 2 +- src/locale/localed.c | 2 +- src/login/Makefile | 245 +- src/login/inhibit.c | 2 +- src/login/loginctl.c | 2 +- src/login/logind-button.c | 2 +- src/login/logind-dbus.c | 2 +- src/login/logind-seat.c | 2 +- src/login/logind-session.c | 2 +- src/login/logind-utmp.c | 2 +- src/login/logind.c | 2 +- src/login/logind.h | 4 +- src/login/test-inhibit.c | 2 +- src/machine-id-setup/Makefile | 38 +- src/machine/.gitignore | 1 - src/machine/Makefile | 1 - src/machine/image-dbus.c | 422 -- src/machine/image-dbus.h | 35 - src/machine/machine-dbus.c | 1475 ---- src/machine/machine-dbus.h | 44 - src/machine/machine.c | 630 -- src/machine/machine.h | 110 - src/machine/machinectl.c | 2782 ------- src/machine/machined-dbus.c | 1660 ----- src/machine/machined.c | 415 -- src/machine/machined.h | 82 - src/machine/operation.c | 131 - src/machine/operation.h | 47 - src/machine/org.freedesktop.machine1.conf | 194 - src/machine/org.freedesktop.machine1.policy.in | 102 - src/machine/org.freedesktop.machine1.service | 12 - src/machine/test-machine-tables.c | 29 - src/modules-load/Makefile | 61 +- src/network/Makefile | 214 +- src/network/networkctl.c | 10 +- src/network/networkd-dhcp6.c | 2 +- src/network/networkd-link.h | 16 +- src/network/networkd-manager.c | 4 +- src/network/networkd-ndisc.c | 2 +- src/network/networkd-netdev-bond.c | 2 +- src/network/networkd-netdev-tunnel.c | 2 +- src/network/networkd-netdev-veth.c | 2 +- src/network/networkd-netdev-vxlan.c | 2 +- src/network/networkd-netdev.h | 2 +- src/network/networkd-network.h | 2 +- src/network/networkd-wait-online-link.c | 2 +- src/network/networkd-wait-online.c | 2 +- src/network/networkd-wait-online.h | 6 +- src/network/networkd.c | 2 +- src/network/networkd.h | 6 +- src/notify/Makefile | 1 - src/notify/notify.c | 203 - src/nspawn/.gitignore | 1 - src/nspawn/Makefile | 1 - src/nspawn/nspawn-cgroup.c | 162 - src/nspawn/nspawn-cgroup.h | 27 - src/nspawn/nspawn-expose-ports.c | 245 - src/nspawn/nspawn-expose-ports.h | 44 - src/nspawn/nspawn-gperf.gperf | 44 - src/nspawn/nspawn-mount.c | 943 --- src/nspawn/nspawn-mount.h | 69 - src/nspawn/nspawn-network.c | 694 -- src/nspawn/nspawn-network.h | 39 - src/nspawn/nspawn-patch-uid.c | 469 -- src/nspawn/nspawn-patch-uid.h | 23 - src/nspawn/nspawn-register.c | 236 - src/nspawn/nspawn-register.h | 29 - src/nspawn/nspawn-settings.c | 516 -- src/nspawn/nspawn-settings.h | 116 - src/nspawn/nspawn-setuid.c | 273 - src/nspawn/nspawn-setuid.h | 22 - src/nspawn/nspawn-stub-pid1.c | 170 - src/nspawn/nspawn-stub-pid1.h | 22 - src/nspawn/nspawn.c | 4116 ----------- src/nspawn/test-patch-uid.c | 61 - src/nss-myhostname/Makefile | 48 +- src/nss-mymachines/Makefile | 1 - src/nss-mymachines/nss-mymachines.c | 738 -- src/nss-mymachines/nss-mymachines.sym | 21 - src/nss-resolve/Makefile | 1 - src/nss-resolve/nss-resolve.c | 675 -- src/nss-resolve/nss-resolve.sym | 19 - src/path/Makefile | 1 - src/path/path.c | 198 - src/quotacheck/Makefile | 1 - src/quotacheck/quotacheck.c | 124 - src/random-seed/Makefile | 1 - src/random-seed/random-seed.c | 176 - src/rc-local-generator/Makefile | 1 - src/rc-local-generator/rc-local-generator.c | 101 - src/remount-fs/Makefile | 1 - src/remount-fs/remount-fs.c | 155 - src/reply-password/Makefile | 1 - src/reply-password/reply-password.c | 96 - src/resolve/.gitignore | 6 - src/resolve/Makefile | 1 - src/resolve/RFCs | 59 - src/resolve/dns-type.c | 323 - src/resolve/dns-type.h | 161 - src/resolve/org.freedesktop.resolve1.conf | 27 - src/resolve/org.freedesktop.resolve1.service | 12 - src/resolve/resolve-tool.c | 1484 ---- src/resolve/resolved-bus.c | 1655 ----- src/resolve/resolved-bus.h | 25 - src/resolve/resolved-conf.c | 236 - src/resolve/resolved-conf.h | 36 - src/resolve/resolved-def.h | 38 - src/resolve/resolved-dns-answer.c | 858 --- src/resolve/resolved-dns-answer.h | 143 - src/resolve/resolved-dns-cache.c | 1050 --- src/resolve/resolved-dns-cache.h | 52 - src/resolve/resolved-dns-dnssec.c | 2199 ------ src/resolve/resolved-dns-dnssec.h | 102 - src/resolve/resolved-dns-packet.c | 2255 ------ src/resolve/resolved-dns-packet.h | 270 - src/resolve/resolved-dns-query.c | 1109 --- src/resolve/resolved-dns-query.h | 133 - src/resolve/resolved-dns-question.c | 468 -- src/resolve/resolved-dns-question.h | 69 - src/resolve/resolved-dns-rr.c | 1594 ---- src/resolve/resolved-dns-rr.h | 342 - src/resolve/resolved-dns-scope.c | 1028 --- src/resolve/resolved-dns-scope.h | 109 - src/resolve/resolved-dns-search-domain.c | 227 - src/resolve/resolved-dns-search-domain.h | 74 - src/resolve/resolved-dns-server.c | 741 -- src/resolve/resolved-dns-server.h | 143 - src/resolve/resolved-dns-stream.c | 403 - src/resolve/resolved-dns-stream.h | 61 - src/resolve/resolved-dns-synthesize.c | 413 -- src/resolve/resolved-dns-synthesize.h | 30 - src/resolve/resolved-dns-transaction.c | 3048 -------- src/resolve/resolved-dns-transaction.h | 174 - src/resolve/resolved-dns-trust-anchor.c | 743 -- src/resolve/resolved-dns-trust-anchor.h | 43 - src/resolve/resolved-dns-zone.c | 661 -- src/resolve/resolved-dns-zone.h | 81 - src/resolve/resolved-etc-hosts.c | 448 -- src/resolve/resolved-etc-hosts.h | 28 - src/resolve/resolved-gperf.gperf | 21 - src/resolve/resolved-link-bus.c | 550 -- src/resolve/resolved-link-bus.h | 38 - src/resolve/resolved-link.c | 840 --- src/resolve/resolved-link.h | 111 - src/resolve/resolved-llmnr.c | 476 -- src/resolve/resolved-llmnr.h | 32 - src/resolve/resolved-manager.c | 1239 ---- src/resolve/resolved-manager.h | 171 - src/resolve/resolved-mdns.c | 287 - src/resolve/resolved-mdns.h | 30 - src/resolve/resolved-resolv-conf.c | 267 - src/resolve/resolved-resolv-conf.h | 27 - src/resolve/resolved.c | 112 - src/resolve/resolved.conf.in | 19 - .../test-data/_443._tcp.fedoraproject.org.pkts | Bin 169 -> 0 bytes .../test-data/_openpgpkey.fedoraproject.org.pkts | Bin 986 -> 0 bytes src/resolve/test-data/fake-caa.pkts | Bin 196 -> 0 bytes src/resolve/test-data/fedoraproject.org.pkts | Bin 1483 -> 0 bytes src/resolve/test-data/gandi.net.pkts | Bin 1010 -> 0 bytes src/resolve/test-data/google.com.pkts | Bin 747 -> 0 bytes src/resolve/test-data/kyhwana.org.pkts | Bin 1803 -> 0 bytes src/resolve/test-data/root.pkts | Bin 1061 -> 0 bytes ...sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts | Bin 330 -> 0 bytes src/resolve/test-data/teamits.com.pkts | Bin 1021 -> 0 bytes .../test-data/zbyszek@fedoraproject.org.pkts | Bin 2533 -> 0 bytes src/resolve/test-dns-packet.c | 114 - src/resolve/test-dnssec-complex.c | 236 - src/resolve/test-dnssec.c | 343 - src/resolve/test-resolve-tables.c | 64 - src/rfkill/Makefile | 1 - src/rfkill/rfkill.c | 426 -- src/run/Makefile | 1 - src/run/run.c | 1261 ---- src/shared/Makefile | 1 - src/shared/acl-util.c | 429 -- src/shared/acl-util.h | 48 - src/shared/acpi-fpdt.c | 164 - src/shared/acpi-fpdt.h | 24 - src/shared/apparmor-util.c | 39 - src/shared/apparmor-util.h | 24 - src/shared/ask-password-api.c | 736 -- src/shared/ask-password-api.h | 38 - src/shared/base-filesystem.c | 129 - src/shared/base-filesystem.h | 24 - src/shared/boot-timestamps.c | 64 - src/shared/boot-timestamps.h | 25 - src/shared/bus-unit-util.c | 1307 ---- src/shared/bus-unit-util.h | 57 - src/shared/bus-util.c | 1571 ---- src/shared/bus-util.h | 163 - src/shared/cgroup-show.c | 312 - src/shared/cgroup-show.h | 32 - src/shared/clean-ipc.c | 365 - src/shared/clean-ipc.h | 24 - src/shared/condition.c | 541 -- src/shared/condition.h | 94 - src/shared/conf-parser.c | 913 --- src/shared/conf-parser.h | 229 - src/shared/dev-setup.c | 73 - src/shared/dev-setup.h | 24 - src/shared/dns-domain.c | 1322 ---- src/shared/dns-domain.h | 109 - src/shared/dropin.c | 251 - src/shared/dropin.h | 61 - src/shared/efivars.c | 715 -- src/shared/efivars.h | 131 - src/shared/firewall-util.c | 357 - src/shared/firewall-util.h | 83 - src/shared/fstab-util.c | 263 - src/shared/fstab-util.h | 52 - src/shared/gcrypt-util.c | 71 - src/shared/gcrypt-util.h | 39 - src/shared/generator.c | 207 - src/shared/generator.h | 40 - src/shared/gpt.h | 66 - src/shared/ima-util.c | 32 - src/shared/ima-util.h | 24 - src/shared/import-util.c | 185 - src/shared/import-util.h | 43 - src/shared/initreq.h | 77 - src/shared/install-printf.c | 133 - src/shared/install-printf.h | 24 - src/shared/install.c | 2953 -------- src/shared/install.h | 256 - src/shared/linux/auto_dev-ioctl.h | 228 - src/shared/logs-show.c | 1310 ---- src/shared/logs-show.h | 70 - src/shared/machine-image.c | 818 -- src/shared/machine-image.h | 103 - src/shared/machine-pool.c | 426 -- src/shared/machine-pool.h | 30 - src/shared/output-mode.c | 37 - src/shared/output-mode.h | 57 - src/shared/pager.c | 226 - src/shared/pager.h | 30 - src/shared/path-lookup.c | 822 -- src/shared/path-lookup.h | 76 - src/shared/ptyfwd.c | 484 -- src/shared/ptyfwd.h | 48 - src/shared/resolve-util.c | 39 - src/shared/resolve-util.h | 60 - src/shared/seccomp-util.c | 90 - src/shared/seccomp-util.h | 28 - src/shared/sleep-config.c | 278 - src/shared/sleep-config.h | 26 - src/shared/spawn-ask-password-agent.c | 62 - src/shared/spawn-ask-password-agent.h | 23 - src/shared/spawn-polkit-agent.c | 102 - src/shared/spawn-polkit-agent.h | 23 - src/shared/specifier.c | 188 - src/shared/specifier.h | 37 - src/shared/switch-root.c | 156 - src/shared/switch-root.h | 23 - src/shared/sysctl-util.c | 73 - src/shared/sysctl-util.h | 25 - src/shared/test-tables.h | 60 - src/shared/tests.c | 33 - src/shared/tests.h | 22 - src/shared/udev-util.h | 44 - src/shared/uid-range.c | 208 - src/shared/uid-range.h | 33 - src/shared/utmp-wtmp.c | 445 -- src/shared/utmp-wtmp.h | 74 - src/shared/watchdog.c | 164 - src/shared/watchdog.h | 29 - src/sleep/Makefile | 33 +- src/sleep/sleep.c | 2 +- src/socket-proxy/Makefile | 34 +- src/socket-proxy/socket-proxyd.c | 6 +- src/stdio-bridge/stdio-bridge.c | 302 - src/sysctl/Makefile | 33 +- src/system-update-generator/Makefile | 1 - .../system-update-generator.c | 73 - src/systemctl/Makefile | 1 - src/systemctl/systemctl.c | 7815 -------------------- src/systemctl/systemd-sysv-install.SKELETON | 47 - src/systemd-ac-power/Makefile | 32 + src/systemd-ac-power/ac-power.c | 35 + src/systemd-activate/Makefile | 36 + src/systemd-activate/activate.c | 545 ++ src/systemd-analyze/.gitignore | 1 + src/systemd-analyze/Makefile | 39 + src/systemd-analyze/analyze-verify.c | 304 + src/systemd-analyze/analyze-verify.h | 26 + src/systemd-analyze/analyze.c | 1485 ++++ src/systemd-ask-password/Makefile | 32 + src/systemd-ask-password/ask-password.c | 188 + src/systemd-backlight/Makefile | 43 + src/systemd-backlight/backlight.c | 434 ++ src/systemd-binfmt/Makefile | 56 + src/systemd-binfmt/binfmt.c | 203 + src/systemd-cgls/Makefile | 32 + src/systemd-cgls/cgls.c | 288 + src/systemd-cgroups-agent/Makefile | 32 + src/systemd-cgroups-agent/cgroups-agent.c | 67 + src/systemd-cgtop/Makefile | 32 + src/systemd-cgtop/cgtop.c | 1115 +++ src/systemd-cryptsetup/Makefile | 59 + src/systemd-cryptsetup/cryptsetup-generator.c | 509 ++ src/systemd-cryptsetup/cryptsetup.c | 757 ++ src/systemd-dbus1-generator/Makefile | 49 + src/systemd-dbus1-generator/dbus1-generator.c | 331 + src/systemd-debug-generator/Makefile | 32 + src/systemd-debug-generator/debug-generator.c | 202 + src/systemd-delta/Makefile | 32 + src/systemd-delta/delta.c | 635 ++ src/systemd-detect-virt/Makefile | 35 + src/systemd-detect-virt/detect-virt.c | 169 + src/systemd-escape/Makefile | 32 + src/systemd-escape/escape.c | 237 + src/systemd-firstboot/Makefile | 47 + src/systemd-firstboot/firstboot.c | 870 +++ src/systemd-fsck/Makefile | 32 + src/systemd-fsck/fsck.c | 487 ++ src/systemd-fstab-generator/Makefile | 33 + src/systemd-fstab-generator/fstab-generator.c | 713 ++ src/systemd-getty-generator/Makefile | 32 + src/systemd-getty-generator/getty-generator.c | 233 + src/systemd-gpt-auto-generator/Makefile | 43 + .../gpt-auto-generator.c | 1042 +++ src/systemd-hibernate-resume/Makefile | 60 + .../hibernate-resume-generator.c | 99 + src/systemd-hibernate-resume/hibernate-resume.c | 82 + src/systemd-hwdb/Makefile | 76 + src/systemd-hwdb/hwdb.c | 739 ++ src/systemd-notify/Makefile | 32 + src/systemd-notify/notify.c | 203 + src/systemd-nspawn/.gitignore | 1 + src/systemd-nspawn/Makefile | 84 + src/systemd-nspawn/nspawn-cgroup.c | 162 + src/systemd-nspawn/nspawn-cgroup.h | 27 + src/systemd-nspawn/nspawn-expose-ports.c | 245 + src/systemd-nspawn/nspawn-expose-ports.h | 44 + src/systemd-nspawn/nspawn-gperf.gperf | 44 + src/systemd-nspawn/nspawn-mount.c | 943 +++ src/systemd-nspawn/nspawn-mount.h | 69 + src/systemd-nspawn/nspawn-network.c | 694 ++ src/systemd-nspawn/nspawn-network.h | 39 + src/systemd-nspawn/nspawn-patch-uid.c | 469 ++ src/systemd-nspawn/nspawn-patch-uid.h | 23 + src/systemd-nspawn/nspawn-register.c | 236 + src/systemd-nspawn/nspawn-register.h | 29 + src/systemd-nspawn/nspawn-settings.c | 516 ++ src/systemd-nspawn/nspawn-settings.h | 116 + src/systemd-nspawn/nspawn-setuid.c | 273 + src/systemd-nspawn/nspawn-setuid.h | 22 + src/systemd-nspawn/nspawn-stub-pid1.c | 170 + src/systemd-nspawn/nspawn-stub-pid1.h | 22 + src/systemd-nspawn/nspawn.c | 4116 +++++++++++ src/systemd-nspawn/test-patch-uid.c | 61 + src/systemd-path/Makefile | 32 + src/systemd-path/path.c | 198 + src/systemd-quotacheck/Makefile | 46 + src/systemd-quotacheck/quotacheck.c | 124 + src/systemd-random-seed/Makefile | 47 + src/systemd-random-seed/random-seed.c | 176 + src/systemd-rc-local-generator/Makefile | 32 + .../rc-local-generator.c | 101 + src/systemd-remount-fs/Makefile | 34 + src/systemd-remount-fs/remount-fs.c | 155 + src/systemd-reply-password/Makefile | 32 + src/systemd-reply-password/reply-password.c | 96 + src/systemd-rfkill/Makefile | 46 + src/systemd-rfkill/rfkill.c | 426 ++ src/systemd-run/Makefile | 32 + src/systemd-run/run.c | 1261 ++++ src/systemd-shutdown/Makefile | 38 + src/systemd-stdio-bridge/Makefile | 32 + src/systemd-stdio-bridge/stdio-bridge.c | 302 + src/systemd-system-update-generator/Makefile | 32 + .../system-update-generator.c | 73 + src/systemd-sysv-generator/Makefile | 32 + src/systemd-sysv-generator/sysv-generator.c | 1039 +++ src/systemd-timesync/.gitignore | 2 + src/systemd-timesync/Makefile | 64 + src/systemd-timesync/timesyncd-conf.c | 106 + src/systemd-timesync/timesyncd-conf.h | 31 + src/systemd-timesync/timesyncd-gperf.gperf | 19 + src/systemd-timesync/timesyncd-manager.c | 1156 +++ src/systemd-timesync/timesyncd-manager.h | 104 + src/systemd-timesync/timesyncd-server.c | 150 + src/systemd-timesync/timesyncd-server.h | 65 + src/systemd-timesync/timesyncd.c | 164 + src/systemd-timesync/timesyncd.conf.in | 16 + src/systemd-tmpfiles/Makefile | 84 + src/systemd-tmpfiles/tmpfiles.c | 2343 ++++++ src/systemd-tty-ask-password-agent/Makefile | 32 + .../tty-ask-password-agent.c | 680 ++ src/systemd-update-done/Makefile | 32 + src/systemd-update-done/update-done.c | 115 + src/systemd-update-utmp/Makefile | 37 + src/systemd-update-utmp/update-utmp.c | 283 + src/systemd-user-sessions/user-sessions.c | 84 + src/systemd-vconsole/.gitignore | 1 + src/systemd-vconsole/90-vconsole.rules.in | 10 + src/systemd-vconsole/Makefile | 50 + src/systemd-vconsole/vconsole-setup.c | 332 + src/systemd/Makefile | 1 - src/systemd/_sd-common.h | 83 - src/systemd/sd-bus-protocol.h | 102 - src/systemd/sd-bus-vtable.h | 141 - src/systemd/sd-bus.h | 456 -- src/systemd/sd-daemon.h | 289 - src/systemd/sd-device.h | 101 - src/systemd/sd-dhcp-client.h | 158 - src/systemd/sd-dhcp-lease.h | 67 - src/systemd/sd-dhcp-server.h | 65 - src/systemd/sd-dhcp6-client.h | 135 - src/systemd/sd-dhcp6-lease.h | 52 - src/systemd/sd-event.h | 142 - src/systemd/sd-hwdb.h | 49 - src/systemd/sd-id128.h | 115 - src/systemd/sd-ipv4acd.h | 60 - src/systemd/sd-ipv4ll.h | 60 - src/systemd/sd-journal.h | 175 - src/systemd/sd-lldp.h | 177 - src/systemd/sd-login.h | 245 - src/systemd/sd-messages.h | 91 - src/systemd/sd-ndisc.h | 84 - src/systemd/sd-netlink.h | 163 - src/systemd/sd-network.h | 176 - src/systemd/sd-path.h | 91 - src/systemd/sd-resolve.h | 117 - src/systemd/sd-utf8.h | 32 - src/sysusers/Makefile | 62 +- src/sysv-generator/Makefile | 1 - src/sysv-generator/sysv-generator.c | 1039 --- src/test/Makefile | 36 +- src/test/test-condition.c | 2 +- src/test/test-daemon.c | 2 +- src/test/test-helper.h | 2 +- src/test/test-id128.c | 4 +- src/test/test-netlink-manual.c | 2 +- src/timedate/.gitignore | 1 - src/timedate/Makefile | 1 - src/timedate/org.freedesktop.timedate1.conf | 27 - src/timedate/org.freedesktop.timedate1.policy.in | 62 - src/timedate/org.freedesktop.timedate1.service | 12 - src/timedate/timedatectl.c | 506 -- src/timedate/timedated.c | 747 -- src/timesync/.gitignore | 2 - src/timesync/Makefile | 1 - src/timesync/timesyncd-conf.c | 106 - src/timesync/timesyncd-conf.h | 31 - src/timesync/timesyncd-gperf.gperf | 19 - src/timesync/timesyncd-manager.c | 1156 --- src/timesync/timesyncd-manager.h | 104 - src/timesync/timesyncd-server.c | 150 - src/timesync/timesyncd-server.h | 65 - src/timesync/timesyncd.c | 164 - src/timesync/timesyncd.conf.in | 16 - src/tmpfiles/Makefile | 1 - src/tmpfiles/tmpfiles.c | 2343 ------ src/tty-ask-password-agent/Makefile | 1 - .../tty-ask-password-agent.c | 680 -- src/udev/Makefile | 182 +- src/udev/ata_id/Makefile | 36 +- src/udev/cdrom_id/Makefile | 39 +- src/udev/collect/Makefile | 36 +- src/udev/mtd_probe/Makefile | 38 +- src/udev/net/Makefile | 1 - src/udev/net/link-config.c | 2 +- src/udev/scsi_id/Makefile | 42 +- src/udev/udev-builtin-blkid.c | 2 +- src/udev/udev-builtin-hwdb.c | 2 +- src/udev/udev-builtin-uaccess.c | 2 +- src/udev/udev.h | 216 - src/udev/udevd.c | 4 +- src/udev/v4l_id/Makefile | 39 +- src/update-done/Makefile | 1 - src/update-done/update-done.c | 115 - src/update-utmp/Makefile | 1 - src/update-utmp/update-utmp.c | 283 - src/user-sessions/Makefile | 1 - src/user-sessions/user-sessions.c | 84 - src/vconsole/.gitignore | 1 - src/vconsole/90-vconsole.rules.in | 10 - src/vconsole/Makefile | 1 - src/vconsole/vconsole-setup.c | 332 - sysctl.d/Makefile | 1 - system-preset/Makefile | 1 - sysusers.d/Makefile | 1 - test/Makefile | 953 ++- test/TEST-01-BASIC/Makefile | 10 - test/TEST-02-CRYPTSETUP/Makefile | 1 - test/TEST-03-JOBS/Makefile | 1 - test/TEST-04-JOURNAL/Makefile | 1 - test/TEST-05-RLIMITS/Makefile | 1 - test/TEST-06-SELINUX/Makefile | 1 - test/TEST-07-ISSUE-1981/Makefile | 1 - test/TEST-08-ISSUE-2730/Makefile | 1 - test/TEST-09-ISSUE-2691/Makefile | 1 - test/TEST-10-ISSUE-2467/Makefile | 1 - test/TEST-11-ISSUE-3166/Makefile | 1 - test/TEST-12-ISSUE-3171/Makefile | 1 - test/test-efi-create-disk.sh | 42 - tmpfiles.d/Makefile | 1 - units/Makefile | 1 - units/user/Makefile | 1 - 2023 files changed, 310497 insertions(+), 301557 deletions(-) create mode 100644 build-aux/Makefile.each.head/.gitignore create mode 100644 build-aux/Makefile.each.tail/.gitignore create mode 100644 build-aux/Makefile.each.tail/20-systemd.mk create mode 100644 build-aux/Makefile.once.head/.gitignore create mode 100644 build-aux/Makefile.once.head/20-systemd.mk create mode 100644 build-aux/Makefile.once.tail/.gitignore create mode 100644 build-aux/Makefile.once.tail/20-systemd.mk delete mode 120000 catalog/Makefile delete mode 100644 catalog/systemd.be.catalog delete mode 100644 catalog/systemd.be@latin.catalog delete mode 100644 catalog/systemd.bg.catalog delete mode 100644 catalog/systemd.catalog delete mode 100644 catalog/systemd.da.catalog delete mode 100644 catalog/systemd.fr.catalog delete mode 100644 catalog/systemd.hr.catalog delete mode 100644 catalog/systemd.hu.catalog delete mode 100644 catalog/systemd.it.catalog delete mode 100644 catalog/systemd.ko.catalog delete mode 100644 catalog/systemd.pl.catalog delete mode 100644 catalog/systemd.pt_BR.catalog delete mode 100644 catalog/systemd.ru.catalog delete mode 100644 catalog/systemd.sr.catalog delete mode 100644 catalog/systemd.zh_CN.catalog delete mode 100644 catalog/systemd.zh_TW.catalog create mode 100644 config.mk.in create mode 100644 discard.mk delete mode 120000 docs/Makefile delete mode 120000 docs/sysvinit/Makefile delete mode 120000 docs/var-log/Makefile mode change 120000 => 100644 hwdb/Makefile delete mode 120000 man/Makefile delete mode 100644 man/kernel-install.xml delete mode 120000 network/Makefile delete mode 120000 rules/Makefile delete mode 120000 shell-completion/Makefile delete mode 120000 shell-completion/bash/Makefile delete mode 100644 shell-completion/bash/kernel-install delete mode 120000 shell-completion/zsh/Makefile delete mode 100644 shell-completion/zsh/_kernel-install delete mode 120000 src/ac-power/Makefile delete mode 100644 src/ac-power/ac-power.c delete mode 120000 src/activate/Makefile delete mode 100644 src/activate/activate.c delete mode 100644 src/analyze/.gitignore delete mode 120000 src/analyze/Makefile delete mode 100644 src/analyze/analyze-verify.c delete mode 100644 src/analyze/analyze-verify.h delete mode 100644 src/analyze/analyze.c delete mode 120000 src/ask-password/Makefile delete mode 100644 src/ask-password/ask-password.c delete mode 120000 src/backlight/Makefile delete mode 100644 src/backlight/backlight.c delete mode 100644 src/basic/.gitignore delete mode 120000 src/basic/Makefile delete mode 100644 src/basic/MurmurHash2.c delete mode 100644 src/basic/MurmurHash2.h delete mode 100644 src/basic/af-list.c delete mode 100644 src/basic/af-list.h delete mode 100644 src/basic/alloc-util.c delete mode 100644 src/basic/alloc-util.h delete mode 100644 src/basic/architecture.c delete mode 100644 src/basic/architecture.h delete mode 100644 src/basic/arphrd-list.c delete mode 100644 src/basic/arphrd-list.h delete mode 100644 src/basic/async.c delete mode 100644 src/basic/async.h delete mode 100644 src/basic/audit-util.c delete mode 100644 src/basic/audit-util.h delete mode 100644 src/basic/barrier.c delete mode 100644 src/basic/barrier.h delete mode 100644 src/basic/bitmap.c delete mode 100644 src/basic/bitmap.h delete mode 100644 src/basic/blkid-util.h delete mode 100644 src/basic/btrfs-ctree.h delete mode 100644 src/basic/btrfs-util.c delete mode 100644 src/basic/btrfs-util.h delete mode 100644 src/basic/build.h delete mode 100644 src/basic/bus-label.c delete mode 100644 src/basic/bus-label.h delete mode 100644 src/basic/calendarspec.c delete mode 100644 src/basic/calendarspec.h delete mode 100644 src/basic/cap-list.c delete mode 100644 src/basic/cap-list.h delete mode 100644 src/basic/capability-util.c delete mode 100644 src/basic/capability-util.h delete mode 100644 src/basic/cgroup-util.c delete mode 100644 src/basic/cgroup-util.h delete mode 100644 src/basic/chattr-util.c delete mode 100644 src/basic/chattr-util.h delete mode 100644 src/basic/clock-util.c delete mode 100644 src/basic/clock-util.h delete mode 100644 src/basic/conf-files.c delete mode 100644 src/basic/conf-files.h delete mode 100644 src/basic/copy.c delete mode 100644 src/basic/copy.h delete mode 100644 src/basic/cpu-set-util.c delete mode 100644 src/basic/cpu-set-util.h delete mode 100644 src/basic/def.h delete mode 100644 src/basic/device-nodes.c delete mode 100644 src/basic/device-nodes.h delete mode 100644 src/basic/dirent-util.c delete mode 100644 src/basic/dirent-util.h delete mode 100644 src/basic/env-util.c delete mode 100644 src/basic/env-util.h delete mode 100644 src/basic/errno-list.c delete mode 100644 src/basic/errno-list.h delete mode 100644 src/basic/escape.c delete mode 100644 src/basic/escape.h delete mode 100644 src/basic/ether-addr-util.c delete mode 100644 src/basic/ether-addr-util.h delete mode 100644 src/basic/exit-status.c delete mode 100644 src/basic/exit-status.h delete mode 100644 src/basic/extract-word.c delete mode 100644 src/basic/extract-word.h delete mode 100644 src/basic/fd-util.c delete mode 100644 src/basic/fd-util.h delete mode 100644 src/basic/fdset.c delete mode 100644 src/basic/fdset.h delete mode 100644 src/basic/fileio-label.c delete mode 100644 src/basic/fileio-label.h delete mode 100644 src/basic/fileio.c delete mode 100644 src/basic/fileio.h delete mode 100644 src/basic/formats-util.h delete mode 100644 src/basic/fs-util.c delete mode 100644 src/basic/fs-util.h delete mode 100644 src/basic/glob-util.c delete mode 100644 src/basic/glob-util.h delete mode 100644 src/basic/gunicode.c delete mode 100644 src/basic/gunicode.h delete mode 100644 src/basic/hash-funcs.c delete mode 100644 src/basic/hash-funcs.h delete mode 100644 src/basic/hashmap.c delete mode 100644 src/basic/hashmap.h delete mode 100644 src/basic/hexdecoct.c delete mode 100644 src/basic/hexdecoct.h delete mode 100644 src/basic/hostname-util.c delete mode 100644 src/basic/hostname-util.h delete mode 100644 src/basic/in-addr-util.c delete mode 100644 src/basic/in-addr-util.h delete mode 100644 src/basic/io-util.c delete mode 100644 src/basic/io-util.h delete mode 100644 src/basic/ioprio.h delete mode 100644 src/basic/label.c delete mode 100644 src/basic/label.h delete mode 100644 src/basic/list.h delete mode 100644 src/basic/locale-util.c delete mode 100644 src/basic/locale-util.h delete mode 100644 src/basic/lockfile-util.c delete mode 100644 src/basic/lockfile-util.h delete mode 100644 src/basic/log.c delete mode 100644 src/basic/log.h delete mode 100644 src/basic/login-util.c delete mode 100644 src/basic/login-util.h delete mode 100644 src/basic/macro.h delete mode 100644 src/basic/memfd-util.c delete mode 100644 src/basic/memfd-util.h delete mode 100644 src/basic/mempool.c delete mode 100644 src/basic/mempool.h delete mode 100644 src/basic/missing.h delete mode 100644 src/basic/missing_syscall.h delete mode 100644 src/basic/mkdir-label.c delete mode 100644 src/basic/mkdir.c delete mode 100644 src/basic/mkdir.h delete mode 100644 src/basic/mount-util.c delete mode 100644 src/basic/mount-util.h delete mode 100644 src/basic/nss-util.h delete mode 100644 src/basic/ordered-set.c delete mode 100644 src/basic/ordered-set.h delete mode 100644 src/basic/parse-util.c delete mode 100644 src/basic/parse-util.h delete mode 100644 src/basic/path-util.c delete mode 100644 src/basic/path-util.h delete mode 100644 src/basic/prioq.c delete mode 100644 src/basic/prioq.h delete mode 100644 src/basic/proc-cmdline.c delete mode 100644 src/basic/proc-cmdline.h delete mode 100644 src/basic/process-util.c delete mode 100644 src/basic/process-util.h delete mode 100644 src/basic/random-util.c delete mode 100644 src/basic/random-util.h delete mode 100644 src/basic/ratelimit.c delete mode 100644 src/basic/ratelimit.h delete mode 100644 src/basic/refcnt.h delete mode 100644 src/basic/replace-var.c delete mode 100644 src/basic/replace-var.h delete mode 100644 src/basic/rlimit-util.c delete mode 100644 src/basic/rlimit-util.h delete mode 100644 src/basic/rm-rf.c delete mode 100644 src/basic/rm-rf.h delete mode 100644 src/basic/securebits.h delete mode 100644 src/basic/selinux-util.c delete mode 100644 src/basic/selinux-util.h delete mode 100644 src/basic/set.h delete mode 100644 src/basic/sigbus.c delete mode 100644 src/basic/sigbus.h delete mode 100644 src/basic/signal-util.c delete mode 100644 src/basic/signal-util.h delete mode 100644 src/basic/siphash24.c delete mode 100644 src/basic/siphash24.h delete mode 100644 src/basic/smack-util.c delete mode 100644 src/basic/smack-util.h delete mode 100644 src/basic/socket-label.c delete mode 100644 src/basic/socket-util.c delete mode 100644 src/basic/socket-util.h delete mode 100644 src/basic/sparse-endian.h delete mode 100644 src/basic/special.h delete mode 100644 src/basic/stat-util.c delete mode 100644 src/basic/stat-util.h delete mode 100644 src/basic/stdio-util.h delete mode 100644 src/basic/strbuf.c delete mode 100644 src/basic/strbuf.h delete mode 100644 src/basic/string-table.c delete mode 100644 src/basic/string-table.h delete mode 100644 src/basic/string-util.c delete mode 100644 src/basic/string-util.h delete mode 100644 src/basic/strv.c delete mode 100644 src/basic/strv.h delete mode 100644 src/basic/strxcpyx.c delete mode 100644 src/basic/strxcpyx.h delete mode 100644 src/basic/syslog-util.c delete mode 100644 src/basic/syslog-util.h delete mode 100644 src/basic/terminal-util.c delete mode 100644 src/basic/terminal-util.h delete mode 100644 src/basic/time-util.c delete mode 100644 src/basic/time-util.h delete mode 100644 src/basic/umask-util.h delete mode 100644 src/basic/unaligned.h delete mode 100644 src/basic/unit-name.c delete mode 100644 src/basic/unit-name.h delete mode 100644 src/basic/user-util.c delete mode 100644 src/basic/user-util.h delete mode 100644 src/basic/utf8.c delete mode 100644 src/basic/utf8.h delete mode 100644 src/basic/util.c delete mode 100644 src/basic/util.h delete mode 100644 src/basic/verbs.c delete mode 100644 src/basic/verbs.h delete mode 100644 src/basic/virt.c delete mode 100644 src/basic/virt.h delete mode 100644 src/basic/web-util.c delete mode 100644 src/basic/web-util.h delete mode 100644 src/basic/xattr-util.c delete mode 100644 src/basic/xattr-util.h delete mode 100644 src/basic/xml.c delete mode 100644 src/basic/xml.h delete mode 120000 src/binfmt/Makefile delete mode 100644 src/binfmt/binfmt.c delete mode 120000 src/boot/Makefile delete mode 100644 src/boot/bootctl.c delete mode 100644 src/boot/efi/.gitignore delete mode 100644 src/boot/efi/boot.c delete mode 100644 src/boot/efi/console.c delete mode 100644 src/boot/efi/console.h delete mode 100644 src/boot/efi/disk.c delete mode 100644 src/boot/efi/disk.h delete mode 100644 src/boot/efi/graphics.c delete mode 100644 src/boot/efi/graphics.h delete mode 100644 src/boot/efi/linux.c delete mode 100644 src/boot/efi/linux.h delete mode 100644 src/boot/efi/measure.c delete mode 100644 src/boot/efi/measure.h delete mode 100644 src/boot/efi/pefile.c delete mode 100644 src/boot/efi/pefile.h delete mode 100644 src/boot/efi/splash.c delete mode 100644 src/boot/efi/splash.h delete mode 100644 src/boot/efi/stub.c delete mode 100644 src/boot/efi/util.c delete mode 100644 src/boot/efi/util.h create mode 100644 src/busctl/Makefile create mode 100644 src/busctl/busctl-introspect.c create mode 100644 src/busctl/busctl-introspect.h create mode 100644 src/busctl/busctl.c delete mode 120000 src/cgls/Makefile delete mode 100644 src/cgls/cgls.c delete mode 120000 src/cgroups-agent/Makefile delete mode 100644 src/cgroups-agent/cgroups-agent.c delete mode 120000 src/cgtop/Makefile delete mode 100644 src/cgtop/cgtop.c delete mode 100644 src/core/.gitignore delete mode 120000 src/core/Makefile delete mode 100644 src/core/audit-fd.c delete mode 100644 src/core/audit-fd.h delete mode 100644 src/core/automount.c delete mode 100644 src/core/automount.h delete mode 100644 src/core/bus-policy.c delete mode 100644 src/core/bus-policy.h delete mode 100644 src/core/busname.c delete mode 100644 src/core/busname.h delete mode 100644 src/core/cgroup.c delete mode 100644 src/core/cgroup.h delete mode 100644 src/core/dbus-automount.c delete mode 100644 src/core/dbus-automount.h delete mode 100644 src/core/dbus-busname.c delete mode 100644 src/core/dbus-busname.h delete mode 100644 src/core/dbus-cgroup.c delete mode 100644 src/core/dbus-cgroup.h delete mode 100644 src/core/dbus-device.c delete mode 100644 src/core/dbus-device.h delete mode 100644 src/core/dbus-execute.c delete mode 100644 src/core/dbus-execute.h delete mode 100644 src/core/dbus-job.c delete mode 100644 src/core/dbus-job.h delete mode 100644 src/core/dbus-kill.c delete mode 100644 src/core/dbus-kill.h delete mode 100644 src/core/dbus-manager.c delete mode 100644 src/core/dbus-manager.h delete mode 100644 src/core/dbus-mount.c delete mode 100644 src/core/dbus-mount.h delete mode 100644 src/core/dbus-path.c delete mode 100644 src/core/dbus-path.h delete mode 100644 src/core/dbus-scope.c delete mode 100644 src/core/dbus-scope.h delete mode 100644 src/core/dbus-service.c delete mode 100644 src/core/dbus-service.h delete mode 100644 src/core/dbus-slice.c delete mode 100644 src/core/dbus-slice.h delete mode 100644 src/core/dbus-socket.c delete mode 100644 src/core/dbus-socket.h delete mode 100644 src/core/dbus-swap.c delete mode 100644 src/core/dbus-swap.h delete mode 100644 src/core/dbus-target.c delete mode 100644 src/core/dbus-target.h delete mode 100644 src/core/dbus-timer.c delete mode 100644 src/core/dbus-timer.h delete mode 100644 src/core/dbus-unit.c delete mode 100644 src/core/dbus-unit.h delete mode 100644 src/core/dbus.c delete mode 100644 src/core/dbus.h delete mode 100644 src/core/device.c delete mode 100644 src/core/device.h delete mode 100644 src/core/execute.c delete mode 100644 src/core/execute.h delete mode 100644 src/core/failure-action.c delete mode 100644 src/core/failure-action.h delete mode 100644 src/core/hostname-setup.c delete mode 100644 src/core/hostname-setup.h delete mode 100644 src/core/ima-setup.c delete mode 100644 src/core/ima-setup.h delete mode 100644 src/core/job.c delete mode 100644 src/core/job.h delete mode 100644 src/core/kill.c delete mode 100644 src/core/kill.h delete mode 100644 src/core/killall.c delete mode 100644 src/core/killall.h delete mode 100644 src/core/kmod-setup.c delete mode 100644 src/core/kmod-setup.h delete mode 100644 src/core/load-dropin.c delete mode 100644 src/core/load-dropin.h delete mode 100644 src/core/load-fragment-gperf.gperf.m4 delete mode 100644 src/core/load-fragment.c delete mode 100644 src/core/load-fragment.h delete mode 100644 src/core/locale-setup.c delete mode 100644 src/core/locale-setup.h delete mode 100644 src/core/loopback-setup.c delete mode 100644 src/core/loopback-setup.h delete mode 100644 src/core/machine-id-setup.c delete mode 100644 src/core/machine-id-setup.h delete mode 100644 src/core/macros.systemd.in delete mode 100644 src/core/main.c delete mode 100644 src/core/manager.c delete mode 100644 src/core/manager.h delete mode 100644 src/core/mount-setup.c delete mode 100644 src/core/mount-setup.h delete mode 100644 src/core/mount.c delete mode 100644 src/core/mount.h delete mode 100644 src/core/namespace.c delete mode 100644 src/core/namespace.h delete mode 100644 src/core/org.freedesktop.systemd1.conf delete mode 100644 src/core/org.freedesktop.systemd1.policy.in.in delete mode 100644 src/core/org.freedesktop.systemd1.service delete mode 100644 src/core/path.c delete mode 100644 src/core/path.h delete mode 100644 src/core/scope.c delete mode 100644 src/core/scope.h delete mode 100644 src/core/selinux-access.c delete mode 100644 src/core/selinux-access.h delete mode 100644 src/core/selinux-setup.c delete mode 100644 src/core/selinux-setup.h delete mode 100644 src/core/service.c delete mode 100644 src/core/service.h delete mode 100644 src/core/show-status.c delete mode 100644 src/core/show-status.h delete mode 100644 src/core/shutdown.c delete mode 100644 src/core/slice.c delete mode 100644 src/core/slice.h delete mode 100644 src/core/smack-setup.c delete mode 100644 src/core/smack-setup.h delete mode 100644 src/core/socket.c delete mode 100644 src/core/socket.h delete mode 100644 src/core/swap.c delete mode 100644 src/core/swap.h delete mode 100644 src/core/system.conf delete mode 100644 src/core/systemd.pc.in delete mode 100644 src/core/target.c delete mode 100644 src/core/target.h delete mode 100644 src/core/timer.c delete mode 100644 src/core/timer.h delete mode 100644 src/core/transaction.c delete mode 100644 src/core/transaction.h delete mode 100644 src/core/triggers.systemd.in delete mode 100644 src/core/umount.c delete mode 100644 src/core/umount.h delete mode 100644 src/core/unit-printf.c delete mode 100644 src/core/unit-printf.h delete mode 100644 src/core/unit.c delete mode 100644 src/core/unit.h delete mode 100644 src/core/user.conf delete mode 120000 src/coredump/Makefile delete mode 100644 src/coredump/coredump-vacuum.c delete mode 100644 src/coredump/coredump-vacuum.h delete mode 100644 src/coredump/coredump.c delete mode 100644 src/coredump/coredump.conf delete mode 100644 src/coredump/coredumpctl.c delete mode 100644 src/coredump/stacktrace.c delete mode 100644 src/coredump/stacktrace.h delete mode 100644 src/coredump/test-coredump-vacuum.c delete mode 120000 src/cryptsetup/Makefile delete mode 100644 src/cryptsetup/cryptsetup-generator.c delete mode 100644 src/cryptsetup/cryptsetup.c delete mode 120000 src/dbus1-generator/Makefile delete mode 100644 src/dbus1-generator/dbus1-generator.c delete mode 120000 src/debug-generator/Makefile delete mode 100644 src/debug-generator/debug-generator.c delete mode 120000 src/delta/Makefile delete mode 100644 src/delta/delta.c delete mode 120000 src/detect-virt/Makefile delete mode 100644 src/detect-virt/detect-virt.c delete mode 120000 src/escape/Makefile delete mode 100644 src/escape/escape.c delete mode 120000 src/firstboot/Makefile delete mode 100644 src/firstboot/firstboot.c delete mode 120000 src/fsck/Makefile delete mode 100644 src/fsck/fsck.c delete mode 120000 src/fstab-generator/Makefile delete mode 100644 src/fstab-generator/fstab-generator.c delete mode 120000 src/getty-generator/Makefile delete mode 100644 src/getty-generator/getty-generator.c delete mode 120000 src/gpt-auto-generator/Makefile delete mode 100644 src/gpt-auto-generator/gpt-auto-generator.c create mode 100644 src/grp-boot/Makefile create mode 100644 src/grp-boot/bootctl/Makefile create mode 100644 src/grp-boot/bootctl/bootctl.c create mode 100644 src/grp-boot/systemd-boot/.gitignore create mode 100644 src/grp-boot/systemd-boot/Makefile create mode 100644 src/grp-boot/systemd-boot/boot.c create mode 100644 src/grp-boot/systemd-boot/console.c create mode 100644 src/grp-boot/systemd-boot/console.h create mode 100644 src/grp-boot/systemd-boot/disk.c create mode 100644 src/grp-boot/systemd-boot/disk.h create mode 100644 src/grp-boot/systemd-boot/graphics.c create mode 100644 src/grp-boot/systemd-boot/graphics.h create mode 100644 src/grp-boot/systemd-boot/linux.c create mode 100644 src/grp-boot/systemd-boot/linux.h create mode 100644 src/grp-boot/systemd-boot/measure.c create mode 100644 src/grp-boot/systemd-boot/measure.h create mode 100644 src/grp-boot/systemd-boot/pefile.c create mode 100644 src/grp-boot/systemd-boot/pefile.h create mode 100644 src/grp-boot/systemd-boot/splash.c create mode 100644 src/grp-boot/systemd-boot/splash.h create mode 100644 src/grp-boot/systemd-boot/stub.c create mode 100755 src/grp-boot/systemd-boot/test-efi-create-disk.sh create mode 100644 src/grp-boot/systemd-boot/util.c create mode 100644 src/grp-boot/systemd-boot/util.h create mode 100644 src/grp-coredump/Makefile create mode 100644 src/grp-coredump/coredumpctl/Makefile create mode 100644 src/grp-coredump/coredumpctl/coredumpctl.c create mode 100644 src/grp-coredump/systemd-coredump/Makefile create mode 100644 src/grp-coredump/systemd-coredump/coredump-vacuum.c create mode 100644 src/grp-coredump/systemd-coredump/coredump-vacuum.h create mode 100644 src/grp-coredump/systemd-coredump/coredump.c create mode 100644 src/grp-coredump/systemd-coredump/coredump.conf create mode 100644 src/grp-coredump/systemd-coredump/stacktrace.c create mode 100644 src/grp-coredump/systemd-coredump/stacktrace.h create mode 100644 src/grp-coredump/systemd-coredump/test-coredump-vacuum.c create mode 100644 src/grp-journal-remote/.gitignore create mode 100644 src/grp-journal-remote/browse.html create mode 100755 src/grp-journal-remote/log-generator.py create mode 100644 src/grp-journal-remote/microhttpd-util.c create mode 100644 src/grp-journal-remote/microhttpd-util.h create mode 100644 src/grp-journal-remote/systemd-journal-gatewayd/Makefile create mode 100644 src/grp-journal-remote/systemd-journal-gatewayd/journal-gatewayd.c create mode 100644 src/grp-journal-remote/systemd-journal-remote/Makefile create mode 100644 src/grp-journal-remote/systemd-journal-remote/journal-remote-parse.c create mode 100644 src/grp-journal-remote/systemd-journal-remote/journal-remote-parse.h create mode 100644 src/grp-journal-remote/systemd-journal-remote/journal-remote-write.c create mode 100644 src/grp-journal-remote/systemd-journal-remote/journal-remote-write.h create mode 100644 src/grp-journal-remote/systemd-journal-remote/journal-remote.c create mode 100644 src/grp-journal-remote/systemd-journal-remote/journal-remote.conf.in create mode 100644 src/grp-journal-remote/systemd-journal-remote/journal-remote.h create mode 100644 src/grp-journal-remote/systemd-journal-upload/Makefile create mode 100644 src/grp-journal-remote/systemd-journal-upload/journal-upload-journal.c create mode 100644 src/grp-journal-remote/systemd-journal-upload/journal-upload.c create mode 100644 src/grp-journal-remote/systemd-journal-upload/journal-upload.conf.in create mode 100644 src/grp-journal-remote/systemd-journal-upload/journal-upload.h create mode 100644 src/grp-journal/.gitignore create mode 100644 src/grp-journal/Makefile create mode 100644 src/grp-journal/catalog/systemd.be.catalog create mode 100644 src/grp-journal/catalog/systemd.be@latin.catalog create mode 100644 src/grp-journal/catalog/systemd.bg.catalog create mode 100644 src/grp-journal/catalog/systemd.catalog create mode 100644 src/grp-journal/catalog/systemd.da.catalog create mode 100644 src/grp-journal/catalog/systemd.fr.catalog create mode 100644 src/grp-journal/catalog/systemd.hr.catalog create mode 100644 src/grp-journal/catalog/systemd.hu.catalog create mode 100644 src/grp-journal/catalog/systemd.it.catalog create mode 100644 src/grp-journal/catalog/systemd.ko.catalog create mode 100644 src/grp-journal/catalog/systemd.pl.catalog create mode 100644 src/grp-journal/catalog/systemd.pt_BR.catalog create mode 100644 src/grp-journal/catalog/systemd.ru.catalog create mode 100644 src/grp-journal/catalog/systemd.sr.catalog create mode 100644 src/grp-journal/catalog/systemd.zh_CN.catalog create mode 100644 src/grp-journal/catalog/systemd.zh_TW.catalog create mode 100644 src/grp-journal/journalctl/Makefile create mode 100644 src/grp-journal/journalctl/journalctl.c create mode 100644 src/grp-journal/libjournal-core/Makefile create mode 100644 src/grp-journal/libjournal-core/cat.c create mode 100644 src/grp-journal/libjournal-core/journal-qrcode.c create mode 100644 src/grp-journal/libjournal-core/journal-qrcode.h create mode 100644 src/grp-journal/libjournal-core/journald-audit.c create mode 100644 src/grp-journal/libjournal-core/journald-audit.h create mode 100644 src/grp-journal/libjournal-core/journald-console.c create mode 100644 src/grp-journal/libjournal-core/journald-console.h create mode 100644 src/grp-journal/libjournal-core/journald-gperf.gperf create mode 100644 src/grp-journal/libjournal-core/journald-kmsg.c create mode 100644 src/grp-journal/libjournal-core/journald-kmsg.h create mode 100644 src/grp-journal/libjournal-core/journald-native.c create mode 100644 src/grp-journal/libjournal-core/journald-native.h create mode 100644 src/grp-journal/libjournal-core/journald-rate-limit.c create mode 100644 src/grp-journal/libjournal-core/journald-rate-limit.h create mode 100644 src/grp-journal/libjournal-core/journald-server.c create mode 100644 src/grp-journal/libjournal-core/journald-server.h create mode 100644 src/grp-journal/libjournal-core/journald-stream.c create mode 100644 src/grp-journal/libjournal-core/journald-stream.h create mode 100644 src/grp-journal/libjournal-core/journald-syslog.c create mode 100644 src/grp-journal/libjournal-core/journald-syslog.h create mode 100644 src/grp-journal/libjournal-core/journald-wall.c create mode 100644 src/grp-journal/libjournal-core/journald-wall.h create mode 100644 src/grp-journal/libjournal-core/journald.conf create mode 100644 src/grp-journal/libjournal-core/test-audit-type.c create mode 100644 src/grp-journal/libjournal-core/test-catalog.c create mode 100644 src/grp-journal/libjournal-core/test-compress-benchmark.c create mode 100644 src/grp-journal/libjournal-core/test-compress.c create mode 100644 src/grp-journal/libjournal-core/test-journal-enum.c create mode 100644 src/grp-journal/libjournal-core/test-journal-flush.c create mode 100644 src/grp-journal/libjournal-core/test-journal-init.c create mode 100644 src/grp-journal/libjournal-core/test-journal-interleaving.c create mode 100644 src/grp-journal/libjournal-core/test-journal-match.c create mode 100644 src/grp-journal/libjournal-core/test-journal-send.c create mode 100644 src/grp-journal/libjournal-core/test-journal-stream.c create mode 100644 src/grp-journal/libjournal-core/test-journal-syslog.c create mode 100644 src/grp-journal/libjournal-core/test-journal-verify.c create mode 100644 src/grp-journal/libjournal-core/test-journal.c create mode 100644 src/grp-journal/libjournal-core/test-mmap-cache.c create mode 100644 src/grp-journal/systemd-journald/Makefile create mode 100644 src/grp-journal/systemd-journald/journald.c create mode 100644 src/grp-machine/Makefile create mode 100644 src/grp-machine/libmachine-core/.gitignore create mode 100644 src/grp-machine/libmachine-core/Makefile create mode 100644 src/grp-machine/libmachine-core/image-dbus.c create mode 100644 src/grp-machine/libmachine-core/image-dbus.h create mode 100644 src/grp-machine/libmachine-core/machine-dbus.c create mode 100644 src/grp-machine/libmachine-core/machine-dbus.h create mode 100644 src/grp-machine/libmachine-core/machine.c create mode 100644 src/grp-machine/libmachine-core/machine.h create mode 100644 src/grp-machine/libmachine-core/machined-dbus.c create mode 100644 src/grp-machine/libmachine-core/machined.h create mode 100644 src/grp-machine/libmachine-core/operation.c create mode 100644 src/grp-machine/libmachine-core/operation.h create mode 100644 src/grp-machine/libmachine-core/org.freedesktop.machine1.conf create mode 100644 src/grp-machine/libmachine-core/org.freedesktop.machine1.policy.in create mode 100644 src/grp-machine/libmachine-core/org.freedesktop.machine1.service create mode 100644 src/grp-machine/libmachine-core/test-machine-tables.c create mode 100644 src/grp-machine/machinectl/Makefile create mode 100644 src/grp-machine/machinectl/machinectl.c create mode 100644 src/grp-machine/nss-mymachines/Makefile create mode 100644 src/grp-machine/nss-mymachines/nss-mymachines.c create mode 100644 src/grp-machine/nss-mymachines/nss-mymachines.sym create mode 100644 src/grp-machine/systemd-machined/Makefile create mode 100644 src/grp-machine/systemd-machined/machined.c create mode 100644 src/grp-resolve/nss-resolve/nss-resolve.c create mode 100644 src/grp-resolve/nss-resolve/nss-resolve.sym create mode 100644 src/grp-resolve/systemd-resolved/.gitignore create mode 100644 src/grp-resolve/systemd-resolved/Makefile create mode 100644 src/grp-resolve/systemd-resolved/RFCs create mode 100644 src/grp-resolve/systemd-resolved/dns-type.c create mode 100644 src/grp-resolve/systemd-resolved/dns-type.h create mode 100644 src/grp-resolve/systemd-resolved/org.freedesktop.resolve1.conf create mode 100644 src/grp-resolve/systemd-resolved/org.freedesktop.resolve1.service create mode 100644 src/grp-resolve/systemd-resolved/resolve-tool.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-bus.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-bus.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-conf.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-conf.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-def.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-answer.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-answer.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-cache.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-cache.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-dnssec.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-dnssec.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-packet.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-packet.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-query.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-query.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-question.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-question.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-rr.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-rr.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-scope.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-scope.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-search-domain.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-search-domain.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-server.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-server.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-stream.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-stream.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-synthesize.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-synthesize.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-transaction.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-transaction.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-trust-anchor.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-trust-anchor.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-zone.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-zone.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-etc-hosts.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-etc-hosts.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-gperf.gperf create mode 100644 src/grp-resolve/systemd-resolved/resolved-link-bus.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-link-bus.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-link.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-link.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-llmnr.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-llmnr.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-manager.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-manager.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-mdns.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-mdns.h create mode 100644 src/grp-resolve/systemd-resolved/resolved-resolv-conf.c create mode 100644 src/grp-resolve/systemd-resolved/resolved-resolv-conf.h create mode 100644 src/grp-resolve/systemd-resolved/resolved.c create mode 100644 src/grp-resolve/systemd-resolved/resolved.conf.in create mode 100644 src/grp-resolve/systemd-resolved/test-data/_443._tcp.fedoraproject.org.pkts create mode 100644 src/grp-resolve/systemd-resolved/test-data/_openpgpkey.fedoraproject.org.pkts create mode 100644 src/grp-resolve/systemd-resolved/test-data/fake-caa.pkts create mode 100644 src/grp-resolve/systemd-resolved/test-data/fedoraproject.org.pkts create mode 100644 src/grp-resolve/systemd-resolved/test-data/gandi.net.pkts create mode 100644 src/grp-resolve/systemd-resolved/test-data/google.com.pkts create mode 100644 src/grp-resolve/systemd-resolved/test-data/kyhwana.org.pkts create mode 100644 src/grp-resolve/systemd-resolved/test-data/root.pkts create mode 100644 src/grp-resolve/systemd-resolved/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts create mode 100644 src/grp-resolve/systemd-resolved/test-data/teamits.com.pkts create mode 100644 src/grp-resolve/systemd-resolved/test-data/zbyszek@fedoraproject.org.pkts create mode 100644 src/grp-resolve/systemd-resolved/test-dns-packet.c create mode 100644 src/grp-resolve/systemd-resolved/test-dnssec-complex.c create mode 100644 src/grp-resolve/systemd-resolved/test-dnssec.c create mode 100644 src/grp-resolve/systemd-resolved/test-resolve-tables.c create mode 100644 src/grp-system/systemctl/Makefile create mode 100644 src/grp-system/systemctl/systemctl.c create mode 100755 src/grp-system/systemctl/systemd-sysv-install.SKELETON create mode 100644 src/grp-system/systemd/Makefile create mode 100644 src/grp-system/systemd/macros.systemd.in create mode 100644 src/grp-system/systemd/main.c create mode 100644 src/grp-system/systemd/org.freedesktop.systemd1.conf create mode 100644 src/grp-system/systemd/org.freedesktop.systemd1.policy.in.in create mode 100644 src/grp-system/systemd/org.freedesktop.systemd1.service create mode 100644 src/grp-system/systemd/system.conf create mode 100644 src/grp-system/systemd/systemd.pc.in create mode 100644 src/grp-system/systemd/triggers.systemd.in create mode 100644 src/grp-system/systemd/user.conf create mode 100644 src/grp-timedate/systemd-timedated/.gitignore create mode 100644 src/grp-timedate/systemd-timedated/Makefile create mode 100644 src/grp-timedate/systemd-timedated/org.freedesktop.timedate1.conf create mode 100644 src/grp-timedate/systemd-timedated/org.freedesktop.timedate1.policy.in create mode 100644 src/grp-timedate/systemd-timedated/org.freedesktop.timedate1.service create mode 100644 src/grp-timedate/systemd-timedated/timedated.c create mode 100644 src/grp-timedate/timedatectl/Makefile create mode 100644 src/grp-timedate/timedatectl/timedatectl.c delete mode 120000 src/hibernate-resume/Makefile delete mode 100644 src/hibernate-resume/hibernate-resume-generator.c delete mode 100644 src/hibernate-resume/hibernate-resume.c mode change 120000 => 100644 src/hostname/Makefile delete mode 120000 src/hwdb/Makefile delete mode 100644 src/hwdb/hwdb.c mode change 120000 => 100644 src/import/Makefile mode change 120000 => 100644 src/initctl/Makefile delete mode 100644 src/journal-remote/.gitignore delete mode 120000 src/journal-remote/Makefile delete mode 100644 src/journal-remote/browse.html delete mode 100644 src/journal-remote/journal-gatewayd.c delete mode 100644 src/journal-remote/journal-remote-parse.c delete mode 100644 src/journal-remote/journal-remote-parse.h delete mode 100644 src/journal-remote/journal-remote-write.c delete mode 100644 src/journal-remote/journal-remote-write.h delete mode 100644 src/journal-remote/journal-remote.c delete mode 100644 src/journal-remote/journal-remote.conf.in delete mode 100644 src/journal-remote/journal-remote.h delete mode 100644 src/journal-remote/journal-upload-journal.c delete mode 100644 src/journal-remote/journal-upload.c delete mode 100644 src/journal-remote/journal-upload.conf.in delete mode 100644 src/journal-remote/journal-upload.h delete mode 100755 src/journal-remote/log-generator.py delete mode 100644 src/journal-remote/microhttpd-util.c delete mode 100644 src/journal-remote/microhttpd-util.h delete mode 100644 src/journal/.gitignore delete mode 120000 src/journal/Makefile delete mode 100644 src/journal/audit-type.c delete mode 100644 src/journal/audit-type.h delete mode 100644 src/journal/cat.c delete mode 100644 src/journal/catalog.c delete mode 100644 src/journal/catalog.h delete mode 100644 src/journal/compress.c delete mode 100644 src/journal/compress.h delete mode 100644 src/journal/fsprg.c delete mode 100644 src/journal/fsprg.h delete mode 100644 src/journal/journal-authenticate.c delete mode 100644 src/journal/journal-authenticate.h delete mode 100644 src/journal/journal-def.h delete mode 100644 src/journal/journal-file.c delete mode 100644 src/journal/journal-file.h delete mode 100644 src/journal/journal-internal.h delete mode 100644 src/journal/journal-qrcode.c delete mode 100644 src/journal/journal-qrcode.h delete mode 100644 src/journal/journal-send.c delete mode 100644 src/journal/journal-vacuum.c delete mode 100644 src/journal/journal-vacuum.h delete mode 100644 src/journal/journal-verify.c delete mode 100644 src/journal/journal-verify.h delete mode 100644 src/journal/journalctl.c delete mode 100644 src/journal/journald-audit.c delete mode 100644 src/journal/journald-audit.h delete mode 100644 src/journal/journald-console.c delete mode 100644 src/journal/journald-console.h delete mode 100644 src/journal/journald-gperf.gperf delete mode 100644 src/journal/journald-kmsg.c delete mode 100644 src/journal/journald-kmsg.h delete mode 100644 src/journal/journald-native.c delete mode 100644 src/journal/journald-native.h delete mode 100644 src/journal/journald-rate-limit.c delete mode 100644 src/journal/journald-rate-limit.h delete mode 100644 src/journal/journald-server.c delete mode 100644 src/journal/journald-server.h delete mode 100644 src/journal/journald-stream.c delete mode 100644 src/journal/journald-stream.h delete mode 100644 src/journal/journald-syslog.c delete mode 100644 src/journal/journald-syslog.h delete mode 100644 src/journal/journald-wall.c delete mode 100644 src/journal/journald-wall.h delete mode 100644 src/journal/journald.c delete mode 100644 src/journal/journald.conf delete mode 100644 src/journal/lookup3.c delete mode 100644 src/journal/lookup3.h delete mode 100644 src/journal/mmap-cache.c delete mode 100644 src/journal/mmap-cache.h delete mode 100644 src/journal/sd-journal.c delete mode 100644 src/journal/test-audit-type.c delete mode 100644 src/journal/test-catalog.c delete mode 100644 src/journal/test-compress-benchmark.c delete mode 100644 src/journal/test-compress.c delete mode 100644 src/journal/test-journal-enum.c delete mode 100644 src/journal/test-journal-flush.c delete mode 100644 src/journal/test-journal-init.c delete mode 100644 src/journal/test-journal-interleaving.c delete mode 100644 src/journal/test-journal-match.c delete mode 100644 src/journal/test-journal-send.c delete mode 100644 src/journal/test-journal-stream.c delete mode 100644 src/journal/test-journal-syslog.c delete mode 100644 src/journal/test-journal-verify.c delete mode 100644 src/journal/test-journal.c delete mode 100644 src/journal/test-mmap-cache.c mode change 120000 => 100644 src/kernel-install/Makefile create mode 100644 src/kernel-install/bash-completion_kernel-install create mode 100644 src/kernel-install/kernel-install.xml create mode 100644 src/kernel-install/zsh-completion_kernel-install create mode 100644 src/libbasic/.gitignore create mode 100644 src/libbasic/Makefile create mode 100644 src/libbasic/MurmurHash2.c create mode 100644 src/libbasic/MurmurHash2.h create mode 100644 src/libbasic/af-list.c create mode 100644 src/libbasic/af-list.h create mode 100644 src/libbasic/alloc-util.c create mode 100644 src/libbasic/alloc-util.h create mode 100644 src/libbasic/architecture.c create mode 100644 src/libbasic/architecture.h create mode 100644 src/libbasic/arphrd-list.c create mode 100644 src/libbasic/arphrd-list.h create mode 100644 src/libbasic/async.c create mode 100644 src/libbasic/async.h create mode 100644 src/libbasic/audit-util.c create mode 100644 src/libbasic/audit-util.h create mode 100644 src/libbasic/barrier.c create mode 100644 src/libbasic/barrier.h create mode 100644 src/libbasic/bitmap.c create mode 100644 src/libbasic/bitmap.h create mode 100644 src/libbasic/blkid-util.h create mode 100644 src/libbasic/btrfs-ctree.h create mode 100644 src/libbasic/btrfs-util.c create mode 100644 src/libbasic/btrfs-util.h create mode 100644 src/libbasic/build.h create mode 100644 src/libbasic/bus-label.c create mode 100644 src/libbasic/bus-label.h create mode 100644 src/libbasic/calendarspec.c create mode 100644 src/libbasic/calendarspec.h create mode 100644 src/libbasic/cap-list.c create mode 100644 src/libbasic/cap-list.h create mode 100644 src/libbasic/capability-util.c create mode 100644 src/libbasic/capability-util.h create mode 100644 src/libbasic/cgroup-util.c create mode 100644 src/libbasic/cgroup-util.h create mode 100644 src/libbasic/chattr-util.c create mode 100644 src/libbasic/chattr-util.h create mode 100644 src/libbasic/clock-util.c create mode 100644 src/libbasic/clock-util.h create mode 100644 src/libbasic/conf-files.c create mode 100644 src/libbasic/conf-files.h create mode 100644 src/libbasic/copy.c create mode 100644 src/libbasic/copy.h create mode 100644 src/libbasic/cpu-set-util.c create mode 100644 src/libbasic/cpu-set-util.h create mode 100644 src/libbasic/def.h create mode 100644 src/libbasic/device-nodes.c create mode 100644 src/libbasic/device-nodes.h create mode 100644 src/libbasic/dirent-util.c create mode 100644 src/libbasic/dirent-util.h create mode 100644 src/libbasic/env-util.c create mode 100644 src/libbasic/env-util.h create mode 100644 src/libbasic/errno-list.c create mode 100644 src/libbasic/errno-list.h create mode 100644 src/libbasic/escape.c create mode 100644 src/libbasic/escape.h create mode 100644 src/libbasic/ether-addr-util.c create mode 100644 src/libbasic/ether-addr-util.h create mode 100644 src/libbasic/exit-status.c create mode 100644 src/libbasic/exit-status.h create mode 100644 src/libbasic/extract-word.c create mode 100644 src/libbasic/extract-word.h create mode 100644 src/libbasic/fd-util.c create mode 100644 src/libbasic/fd-util.h create mode 100644 src/libbasic/fdset.c create mode 100644 src/libbasic/fdset.h create mode 100644 src/libbasic/fileio-label.c create mode 100644 src/libbasic/fileio-label.h create mode 100644 src/libbasic/fileio.c create mode 100644 src/libbasic/fileio.h create mode 100644 src/libbasic/formats-util.h create mode 100644 src/libbasic/fs-util.c create mode 100644 src/libbasic/fs-util.h create mode 100644 src/libbasic/glob-util.c create mode 100644 src/libbasic/glob-util.h create mode 100644 src/libbasic/gunicode.c create mode 100644 src/libbasic/gunicode.h create mode 100644 src/libbasic/hash-funcs.c create mode 100644 src/libbasic/hash-funcs.h create mode 100644 src/libbasic/hashmap.c create mode 100644 src/libbasic/hashmap.h create mode 100644 src/libbasic/hexdecoct.c create mode 100644 src/libbasic/hexdecoct.h create mode 100644 src/libbasic/hostname-util.c create mode 100644 src/libbasic/hostname-util.h create mode 100644 src/libbasic/in-addr-util.c create mode 100644 src/libbasic/in-addr-util.h create mode 100644 src/libbasic/io-util.c create mode 100644 src/libbasic/io-util.h create mode 100644 src/libbasic/ioprio.h create mode 100644 src/libbasic/label.c create mode 100644 src/libbasic/label.h create mode 100644 src/libbasic/list.h create mode 100644 src/libbasic/locale-util.c create mode 100644 src/libbasic/locale-util.h create mode 100644 src/libbasic/lockfile-util.c create mode 100644 src/libbasic/lockfile-util.h create mode 100644 src/libbasic/log.c create mode 100644 src/libbasic/log.h create mode 100644 src/libbasic/login-util.c create mode 100644 src/libbasic/login-util.h create mode 100644 src/libbasic/macro.h create mode 100644 src/libbasic/memfd-util.c create mode 100644 src/libbasic/memfd-util.h create mode 100644 src/libbasic/mempool.c create mode 100644 src/libbasic/mempool.h create mode 100644 src/libbasic/missing.h create mode 100644 src/libbasic/missing_syscall.h create mode 100644 src/libbasic/mkdir-label.c create mode 100644 src/libbasic/mkdir.c create mode 100644 src/libbasic/mkdir.h create mode 100644 src/libbasic/mount-util.c create mode 100644 src/libbasic/mount-util.h create mode 100644 src/libbasic/nss-util.h create mode 100644 src/libbasic/ordered-set.c create mode 100644 src/libbasic/ordered-set.h create mode 100644 src/libbasic/parse-util.c create mode 100644 src/libbasic/parse-util.h create mode 100644 src/libbasic/path-util.c create mode 100644 src/libbasic/path-util.h create mode 100644 src/libbasic/prioq.c create mode 100644 src/libbasic/prioq.h create mode 100644 src/libbasic/proc-cmdline.c create mode 100644 src/libbasic/proc-cmdline.h create mode 100644 src/libbasic/process-util.c create mode 100644 src/libbasic/process-util.h create mode 100644 src/libbasic/random-util.c create mode 100644 src/libbasic/random-util.h create mode 100644 src/libbasic/ratelimit.c create mode 100644 src/libbasic/ratelimit.h create mode 100644 src/libbasic/refcnt.h create mode 100644 src/libbasic/replace-var.c create mode 100644 src/libbasic/replace-var.h create mode 100644 src/libbasic/rlimit-util.c create mode 100644 src/libbasic/rlimit-util.h create mode 100644 src/libbasic/rm-rf.c create mode 100644 src/libbasic/rm-rf.h create mode 100644 src/libbasic/securebits.h create mode 100644 src/libbasic/selinux-util.c create mode 100644 src/libbasic/selinux-util.h create mode 100644 src/libbasic/set.h create mode 100644 src/libbasic/sigbus.c create mode 100644 src/libbasic/sigbus.h create mode 100644 src/libbasic/signal-util.c create mode 100644 src/libbasic/signal-util.h create mode 100644 src/libbasic/siphash24.c create mode 100644 src/libbasic/siphash24.h create mode 100644 src/libbasic/smack-util.c create mode 100644 src/libbasic/smack-util.h create mode 100644 src/libbasic/socket-label.c create mode 100644 src/libbasic/socket-util.c create mode 100644 src/libbasic/socket-util.h create mode 100644 src/libbasic/sparse-endian.h create mode 100644 src/libbasic/special.h create mode 100644 src/libbasic/stat-util.c create mode 100644 src/libbasic/stat-util.h create mode 100644 src/libbasic/stdio-util.h create mode 100644 src/libbasic/strbuf.c create mode 100644 src/libbasic/strbuf.h create mode 100644 src/libbasic/string-table.c create mode 100644 src/libbasic/string-table.h create mode 100644 src/libbasic/string-util.c create mode 100644 src/libbasic/string-util.h create mode 100644 src/libbasic/strv.c create mode 100644 src/libbasic/strv.h create mode 100644 src/libbasic/strxcpyx.c create mode 100644 src/libbasic/strxcpyx.h create mode 100644 src/libbasic/syslog-util.c create mode 100644 src/libbasic/syslog-util.h create mode 100644 src/libbasic/terminal-util.c create mode 100644 src/libbasic/terminal-util.h create mode 100644 src/libbasic/time-util.c create mode 100644 src/libbasic/time-util.h create mode 100644 src/libbasic/umask-util.h create mode 100644 src/libbasic/unaligned.h create mode 100644 src/libbasic/unit-name.c create mode 100644 src/libbasic/unit-name.h create mode 100644 src/libbasic/user-util.c create mode 100644 src/libbasic/user-util.h create mode 100644 src/libbasic/utf8.c create mode 100644 src/libbasic/utf8.h create mode 100644 src/libbasic/util.c create mode 100644 src/libbasic/util.h create mode 100644 src/libbasic/verbs.c create mode 100644 src/libbasic/verbs.h create mode 100644 src/libbasic/virt.c create mode 100644 src/libbasic/virt.h create mode 100644 src/libbasic/web-util.c create mode 100644 src/libbasic/web-util.h create mode 100644 src/libbasic/xattr-util.c create mode 100644 src/libbasic/xattr-util.h create mode 100644 src/libbasic/xml.c create mode 100644 src/libbasic/xml.h create mode 100644 src/libcore/.gitignore create mode 100644 src/libcore/Makefile create mode 100644 src/libcore/audit-fd.c create mode 100644 src/libcore/audit-fd.h create mode 100644 src/libcore/automount.c create mode 100644 src/libcore/automount.h create mode 100644 src/libcore/bus-policy.c create mode 100644 src/libcore/bus-policy.h create mode 100644 src/libcore/busname.c create mode 100644 src/libcore/busname.h create mode 100644 src/libcore/cgroup.c create mode 100644 src/libcore/cgroup.h create mode 100644 src/libcore/dbus-automount.c create mode 100644 src/libcore/dbus-automount.h create mode 100644 src/libcore/dbus-busname.c create mode 100644 src/libcore/dbus-busname.h create mode 100644 src/libcore/dbus-cgroup.c create mode 100644 src/libcore/dbus-cgroup.h create mode 100644 src/libcore/dbus-device.c create mode 100644 src/libcore/dbus-device.h create mode 100644 src/libcore/dbus-execute.c create mode 100644 src/libcore/dbus-execute.h create mode 100644 src/libcore/dbus-job.c create mode 100644 src/libcore/dbus-job.h create mode 100644 src/libcore/dbus-kill.c create mode 100644 src/libcore/dbus-kill.h create mode 100644 src/libcore/dbus-manager.c create mode 100644 src/libcore/dbus-manager.h create mode 100644 src/libcore/dbus-mount.c create mode 100644 src/libcore/dbus-mount.h create mode 100644 src/libcore/dbus-path.c create mode 100644 src/libcore/dbus-path.h create mode 100644 src/libcore/dbus-scope.c create mode 100644 src/libcore/dbus-scope.h create mode 100644 src/libcore/dbus-service.c create mode 100644 src/libcore/dbus-service.h create mode 100644 src/libcore/dbus-slice.c create mode 100644 src/libcore/dbus-slice.h create mode 100644 src/libcore/dbus-socket.c create mode 100644 src/libcore/dbus-socket.h create mode 100644 src/libcore/dbus-swap.c create mode 100644 src/libcore/dbus-swap.h create mode 100644 src/libcore/dbus-target.c create mode 100644 src/libcore/dbus-target.h create mode 100644 src/libcore/dbus-timer.c create mode 100644 src/libcore/dbus-timer.h create mode 100644 src/libcore/dbus-unit.c create mode 100644 src/libcore/dbus-unit.h create mode 100644 src/libcore/dbus.c create mode 100644 src/libcore/dbus.h create mode 100644 src/libcore/device.c create mode 100644 src/libcore/device.h create mode 100644 src/libcore/execute.c create mode 100644 src/libcore/execute.h create mode 100644 src/libcore/failure-action.c create mode 100644 src/libcore/failure-action.h create mode 100644 src/libcore/hostname-setup.c create mode 100644 src/libcore/hostname-setup.h create mode 100644 src/libcore/ima-setup.c create mode 100644 src/libcore/ima-setup.h create mode 100644 src/libcore/job.c create mode 100644 src/libcore/job.h create mode 100644 src/libcore/kill.c create mode 100644 src/libcore/kill.h create mode 100644 src/libcore/killall.c create mode 100644 src/libcore/killall.h create mode 100644 src/libcore/kmod-setup.c create mode 100644 src/libcore/kmod-setup.h create mode 100644 src/libcore/linux/auto_dev-ioctl.h create mode 100644 src/libcore/load-dropin.c create mode 100644 src/libcore/load-dropin.h create mode 100644 src/libcore/load-fragment-gperf.gperf.m4 create mode 100644 src/libcore/load-fragment.c create mode 100644 src/libcore/load-fragment.h create mode 100644 src/libcore/locale-setup.c create mode 100644 src/libcore/locale-setup.h create mode 100644 src/libcore/loopback-setup.c create mode 100644 src/libcore/loopback-setup.h create mode 100644 src/libcore/machine-id-setup.c create mode 100644 src/libcore/machine-id-setup.h create mode 100644 src/libcore/manager.c create mode 100644 src/libcore/manager.h create mode 100644 src/libcore/mount-setup.c create mode 100644 src/libcore/mount-setup.h create mode 100644 src/libcore/mount.c create mode 100644 src/libcore/mount.h create mode 100644 src/libcore/namespace.c create mode 100644 src/libcore/namespace.h create mode 100644 src/libcore/path.c create mode 100644 src/libcore/path.h create mode 100644 src/libcore/scope.c create mode 100644 src/libcore/scope.h create mode 100644 src/libcore/selinux-access.c create mode 100644 src/libcore/selinux-access.h create mode 100644 src/libcore/selinux-setup.c create mode 100644 src/libcore/selinux-setup.h create mode 100644 src/libcore/service.c create mode 100644 src/libcore/service.h create mode 100644 src/libcore/show-status.c create mode 100644 src/libcore/show-status.h create mode 100644 src/libcore/shutdown.c create mode 100644 src/libcore/slice.c create mode 100644 src/libcore/slice.h create mode 100644 src/libcore/smack-setup.c create mode 100644 src/libcore/smack-setup.h create mode 100644 src/libcore/socket.c create mode 100644 src/libcore/socket.h create mode 100644 src/libcore/swap.c create mode 100644 src/libcore/swap.h create mode 100644 src/libcore/target.c create mode 100644 src/libcore/target.h create mode 100644 src/libcore/timer.c create mode 100644 src/libcore/timer.h create mode 100644 src/libcore/transaction.c create mode 100644 src/libcore/transaction.h create mode 100644 src/libcore/umount.c create mode 100644 src/libcore/umount.h create mode 100644 src/libcore/unit-printf.c create mode 100644 src/libcore/unit-printf.h create mode 100644 src/libcore/unit.c create mode 100644 src/libcore/unit.h create mode 100644 src/libfirewall/Makefile create mode 100644 src/libfirewall/firewall-util.c create mode 100644 src/libfirewall/firewall-util.h create mode 100644 src/libshared/Makefile create mode 100644 src/libshared/acl-util.c create mode 100644 src/libshared/acl-util.h create mode 100644 src/libshared/acpi-fpdt.c create mode 100644 src/libshared/acpi-fpdt.h create mode 100644 src/libshared/apparmor-util.c create mode 100644 src/libshared/apparmor-util.h create mode 100644 src/libshared/ask-password-api.c create mode 100644 src/libshared/ask-password-api.h create mode 100644 src/libshared/base-filesystem.c create mode 100644 src/libshared/base-filesystem.h create mode 100644 src/libshared/boot-timestamps.c create mode 100644 src/libshared/boot-timestamps.h create mode 100644 src/libshared/bus-unit-util.c create mode 100644 src/libshared/bus-unit-util.h create mode 100644 src/libshared/bus-util.c create mode 100644 src/libshared/bus-util.h create mode 100644 src/libshared/cgroup-show.c create mode 100644 src/libshared/cgroup-show.h create mode 100644 src/libshared/clean-ipc.c create mode 100644 src/libshared/clean-ipc.h create mode 100644 src/libshared/condition.c create mode 100644 src/libshared/condition.h create mode 100644 src/libshared/conf-parser.c create mode 100644 src/libshared/conf-parser.h create mode 100644 src/libshared/dev-setup.c create mode 100644 src/libshared/dev-setup.h create mode 100644 src/libshared/dns-domain.c create mode 100644 src/libshared/dns-domain.h create mode 100644 src/libshared/dropin.c create mode 100644 src/libshared/dropin.h create mode 100644 src/libshared/efivars.c create mode 100644 src/libshared/efivars.h create mode 100644 src/libshared/fstab-util.c create mode 100644 src/libshared/fstab-util.h create mode 100644 src/libshared/gcrypt-util.c create mode 100644 src/libshared/gcrypt-util.h create mode 100644 src/libshared/generator.c create mode 100644 src/libshared/generator.h create mode 100644 src/libshared/gpt.h create mode 100644 src/libshared/ima-util.c create mode 100644 src/libshared/ima-util.h create mode 100644 src/libshared/import-util.c create mode 100644 src/libshared/import-util.h create mode 100644 src/libshared/initreq.h create mode 100644 src/libshared/install-printf.c create mode 100644 src/libshared/install-printf.h create mode 100644 src/libshared/install.c create mode 100644 src/libshared/install.h create mode 100644 src/libshared/local-addresses.c create mode 100644 src/libshared/local-addresses.h create mode 100644 src/libshared/logs-show.c create mode 100644 src/libshared/logs-show.h create mode 100644 src/libshared/machine-image.c create mode 100644 src/libshared/machine-image.h create mode 100644 src/libshared/machine-pool.c create mode 100644 src/libshared/machine-pool.h create mode 100644 src/libshared/output-mode.c create mode 100644 src/libshared/output-mode.h create mode 100644 src/libshared/pager.c create mode 100644 src/libshared/pager.h create mode 100644 src/libshared/path-lookup.c create mode 100644 src/libshared/path-lookup.h create mode 100644 src/libshared/ptyfwd.c create mode 100644 src/libshared/ptyfwd.h create mode 100644 src/libshared/resolve-util.c create mode 100644 src/libshared/resolve-util.h create mode 100644 src/libshared/seccomp-util.c create mode 100644 src/libshared/seccomp-util.h create mode 100644 src/libshared/sleep-config.c create mode 100644 src/libshared/sleep-config.h create mode 100644 src/libshared/spawn-ask-password-agent.c create mode 100644 src/libshared/spawn-ask-password-agent.h create mode 100644 src/libshared/spawn-polkit-agent.c create mode 100644 src/libshared/spawn-polkit-agent.h create mode 100644 src/libshared/specifier.c create mode 100644 src/libshared/specifier.h create mode 100644 src/libshared/switch-root.c create mode 100644 src/libshared/switch-root.h create mode 100644 src/libshared/sysctl-util.c create mode 100644 src/libshared/sysctl-util.h create mode 100644 src/libshared/test-local-addresses.c create mode 100644 src/libshared/test-tables.h create mode 100644 src/libshared/tests.c create mode 100644 src/libshared/tests.h create mode 100644 src/libshared/udev-util.h create mode 100644 src/libshared/uid-range.c create mode 100644 src/libshared/uid-range.h create mode 100644 src/libshared/utmp-wtmp.c create mode 100644 src/libshared/utmp-wtmp.h create mode 100644 src/libshared/watchdog.c create mode 100644 src/libshared/watchdog.h mode change 120000 => 100644 src/libsystemd-network/Makefile mode change 120000 => 100644 src/libsystemd/Makefile create mode 100644 src/libsystemd/include/systemd/_sd-common.h create mode 100644 src/libsystemd/include/systemd/sd-bus-protocol.h create mode 100644 src/libsystemd/include/systemd/sd-bus-vtable.h create mode 100644 src/libsystemd/include/systemd/sd-bus.h create mode 100644 src/libsystemd/include/systemd/sd-daemon.h create mode 100644 src/libsystemd/include/systemd/sd-device.h create mode 100644 src/libsystemd/include/systemd/sd-dhcp-client.h create mode 100644 src/libsystemd/include/systemd/sd-dhcp-lease.h create mode 100644 src/libsystemd/include/systemd/sd-dhcp-server.h create mode 100644 src/libsystemd/include/systemd/sd-dhcp6-client.h create mode 100644 src/libsystemd/include/systemd/sd-dhcp6-lease.h create mode 100644 src/libsystemd/include/systemd/sd-event.h create mode 100644 src/libsystemd/include/systemd/sd-hwdb.h create mode 100644 src/libsystemd/include/systemd/sd-id128.h create mode 100644 src/libsystemd/include/systemd/sd-ipv4acd.h create mode 100644 src/libsystemd/include/systemd/sd-ipv4ll.h create mode 100644 src/libsystemd/include/systemd/sd-journal.h create mode 100644 src/libsystemd/include/systemd/sd-lldp.h create mode 100644 src/libsystemd/include/systemd/sd-login.h create mode 100644 src/libsystemd/include/systemd/sd-messages.h create mode 100644 src/libsystemd/include/systemd/sd-ndisc.h create mode 100644 src/libsystemd/include/systemd/sd-netlink.h create mode 100644 src/libsystemd/include/systemd/sd-network.h create mode 100644 src/libsystemd/include/systemd/sd-path.h create mode 100644 src/libsystemd/include/systemd/sd-resolve.h create mode 100644 src/libsystemd/include/systemd/sd-utf8.h create mode 100644 src/libsystemd/libsystemd-internal/Makefile create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/DIFFERENCES create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/GVARIANT-SERIALIZATION create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/PORTING-DBUS1 create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-bloom.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-bloom.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-common-errors.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-common-errors.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-container.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-container.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-control.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-control.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-convenience.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-creds.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-creds.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-dump.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-dump.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-error.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-error.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-gvariant.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-gvariant.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-internal.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-internal.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-introspect.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-introspect.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-kernel.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-kernel.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-match.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-match.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-message.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-message.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-objects.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-objects.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-protocol.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-signature.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-signature.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-slot.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-slot.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-socket.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-socket.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-track.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-track.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-type.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-type.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/kdbus.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/sd-bus.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-benchmark.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-chat.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-cleanup.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-creds.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-error.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-gvariant.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-introspect.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-kernel-bloom.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-kernel.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-marshal.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-match.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-objects.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-server.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-signature.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-zero-copy.c create mode 100644 src/libsystemd/libsystemd-internal/sd-daemon/sd-daemon.c create mode 100644 src/libsystemd/libsystemd-internal/sd-device/device-enumerator-private.h create mode 100644 src/libsystemd/libsystemd-internal/sd-device/device-enumerator.c create mode 100644 src/libsystemd/libsystemd-internal/sd-device/device-internal.h create mode 100644 src/libsystemd/libsystemd-internal/sd-device/device-private.c create mode 100644 src/libsystemd/libsystemd-internal/sd-device/device-private.h create mode 100644 src/libsystemd/libsystemd-internal/sd-device/device-util.h create mode 100644 src/libsystemd/libsystemd-internal/sd-device/sd-device.c create mode 100644 src/libsystemd/libsystemd-internal/sd-event/sd-event.c create mode 100644 src/libsystemd/libsystemd-internal/sd-event/test-event.c create mode 100644 src/libsystemd/libsystemd-internal/sd-hwdb/hwdb-internal.h create mode 100644 src/libsystemd/libsystemd-internal/sd-hwdb/hwdb-util.h create mode 100644 src/libsystemd/libsystemd-internal/sd-hwdb/sd-hwdb.c create mode 100644 src/libsystemd/libsystemd-internal/sd-id128/sd-id128.c create mode 100644 src/libsystemd/libsystemd-internal/sd-login/sd-login.c create mode 100644 src/libsystemd/libsystemd-internal/sd-login/test-login.c create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/netlink-internal.h create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/netlink-message.c create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/netlink-socket.c create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/netlink-types.c create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/netlink-types.h create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/netlink-util.c create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/netlink-util.h create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/rtnl-message.c create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/sd-netlink.c create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/test-netlink.c create mode 100644 src/libsystemd/libsystemd-internal/sd-network/network-util.c create mode 100644 src/libsystemd/libsystemd-internal/sd-network/network-util.h create mode 100644 src/libsystemd/libsystemd-internal/sd-network/sd-network.c create mode 100644 src/libsystemd/libsystemd-internal/sd-path/sd-path.c create mode 100644 src/libsystemd/libsystemd-internal/sd-resolve/sd-resolve.c create mode 100644 src/libsystemd/libsystemd-internal/sd-resolve/test-resolve.c create mode 100644 src/libsystemd/libsystemd-internal/sd-utf8/sd-utf8.c create mode 100644 src/libsystemd/libsystemd-internal/subdir.mk create mode 100644 src/libsystemd/libsystemd-journal-internal/Makefile create mode 100644 src/libsystemd/libsystemd-journal-internal/audit-type.c create mode 100644 src/libsystemd/libsystemd-journal-internal/audit-type.h create mode 100644 src/libsystemd/libsystemd-journal-internal/catalog.c create mode 100644 src/libsystemd/libsystemd-journal-internal/catalog.h create mode 100644 src/libsystemd/libsystemd-journal-internal/compress.c create mode 100644 src/libsystemd/libsystemd-journal-internal/compress.h create mode 100644 src/libsystemd/libsystemd-journal-internal/fsprg.c create mode 100644 src/libsystemd/libsystemd-journal-internal/fsprg.h create mode 100644 src/libsystemd/libsystemd-journal-internal/journal-authenticate.c create mode 100644 src/libsystemd/libsystemd-journal-internal/journal-authenticate.h create mode 100644 src/libsystemd/libsystemd-journal-internal/journal-def.h create mode 100644 src/libsystemd/libsystemd-journal-internal/journal-file.c create mode 100644 src/libsystemd/libsystemd-journal-internal/journal-file.h create mode 100644 src/libsystemd/libsystemd-journal-internal/journal-internal.h create mode 100644 src/libsystemd/libsystemd-journal-internal/journal-send.c create mode 100644 src/libsystemd/libsystemd-journal-internal/journal-vacuum.c create mode 100644 src/libsystemd/libsystemd-journal-internal/journal-vacuum.h create mode 100644 src/libsystemd/libsystemd-journal-internal/journal-verify.c create mode 100644 src/libsystemd/libsystemd-journal-internal/journal-verify.h create mode 100644 src/libsystemd/libsystemd-journal-internal/lookup3.c create mode 100644 src/libsystemd/libsystemd-journal-internal/lookup3.h create mode 100644 src/libsystemd/libsystemd-journal-internal/mmap-cache.c create mode 100644 src/libsystemd/libsystemd-journal-internal/mmap-cache.h create mode 100644 src/libsystemd/libsystemd-journal-internal/sd-journal.c delete mode 100644 src/libsystemd/sd-bus/DIFFERENCES delete mode 100644 src/libsystemd/sd-bus/GVARIANT-SERIALIZATION delete mode 120000 src/libsystemd/sd-bus/Makefile delete mode 100644 src/libsystemd/sd-bus/PORTING-DBUS1 delete mode 100644 src/libsystemd/sd-bus/bus-bloom.c delete mode 100644 src/libsystemd/sd-bus/bus-bloom.h delete mode 100644 src/libsystemd/sd-bus/bus-common-errors.c delete mode 100644 src/libsystemd/sd-bus/bus-common-errors.h delete mode 100644 src/libsystemd/sd-bus/bus-container.c delete mode 100644 src/libsystemd/sd-bus/bus-container.h delete mode 100644 src/libsystemd/sd-bus/bus-control.c delete mode 100644 src/libsystemd/sd-bus/bus-control.h delete mode 100644 src/libsystemd/sd-bus/bus-convenience.c delete mode 100644 src/libsystemd/sd-bus/bus-creds.c delete mode 100644 src/libsystemd/sd-bus/bus-creds.h delete mode 100644 src/libsystemd/sd-bus/bus-dump.c delete mode 100644 src/libsystemd/sd-bus/bus-dump.h delete mode 100644 src/libsystemd/sd-bus/bus-error.c delete mode 100644 src/libsystemd/sd-bus/bus-error.h delete mode 100644 src/libsystemd/sd-bus/bus-gvariant.c delete mode 100644 src/libsystemd/sd-bus/bus-gvariant.h delete mode 100644 src/libsystemd/sd-bus/bus-internal.c delete mode 100644 src/libsystemd/sd-bus/bus-internal.h delete mode 100644 src/libsystemd/sd-bus/bus-introspect.c delete mode 100644 src/libsystemd/sd-bus/bus-introspect.h delete mode 100644 src/libsystemd/sd-bus/bus-kernel.c delete mode 100644 src/libsystemd/sd-bus/bus-kernel.h delete mode 100644 src/libsystemd/sd-bus/bus-match.c delete mode 100644 src/libsystemd/sd-bus/bus-match.h delete mode 100644 src/libsystemd/sd-bus/bus-message.c delete mode 100644 src/libsystemd/sd-bus/bus-message.h delete mode 100644 src/libsystemd/sd-bus/bus-objects.c delete mode 100644 src/libsystemd/sd-bus/bus-objects.h delete mode 100644 src/libsystemd/sd-bus/bus-protocol.h delete mode 100644 src/libsystemd/sd-bus/bus-signature.c delete mode 100644 src/libsystemd/sd-bus/bus-signature.h delete mode 100644 src/libsystemd/sd-bus/bus-slot.c delete mode 100644 src/libsystemd/sd-bus/bus-slot.h delete mode 100644 src/libsystemd/sd-bus/bus-socket.c delete mode 100644 src/libsystemd/sd-bus/bus-socket.h delete mode 100644 src/libsystemd/sd-bus/bus-track.c delete mode 100644 src/libsystemd/sd-bus/bus-track.h delete mode 100644 src/libsystemd/sd-bus/bus-type.c delete mode 100644 src/libsystemd/sd-bus/bus-type.h delete mode 100644 src/libsystemd/sd-bus/busctl-introspect.c delete mode 100644 src/libsystemd/sd-bus/busctl-introspect.h delete mode 100644 src/libsystemd/sd-bus/busctl.c delete mode 100644 src/libsystemd/sd-bus/kdbus.h delete mode 100644 src/libsystemd/sd-bus/sd-bus.c delete mode 100644 src/libsystemd/sd-bus/test-bus-benchmark.c delete mode 100644 src/libsystemd/sd-bus/test-bus-chat.c delete mode 100644 src/libsystemd/sd-bus/test-bus-cleanup.c delete mode 100644 src/libsystemd/sd-bus/test-bus-creds.c delete mode 100644 src/libsystemd/sd-bus/test-bus-error.c delete mode 100644 src/libsystemd/sd-bus/test-bus-gvariant.c delete mode 100644 src/libsystemd/sd-bus/test-bus-introspect.c delete mode 100644 src/libsystemd/sd-bus/test-bus-kernel-bloom.c delete mode 100644 src/libsystemd/sd-bus/test-bus-kernel.c delete mode 100644 src/libsystemd/sd-bus/test-bus-marshal.c delete mode 100644 src/libsystemd/sd-bus/test-bus-match.c delete mode 100644 src/libsystemd/sd-bus/test-bus-objects.c delete mode 100644 src/libsystemd/sd-bus/test-bus-server.c delete mode 100644 src/libsystemd/sd-bus/test-bus-signature.c delete mode 100644 src/libsystemd/sd-bus/test-bus-zero-copy.c delete mode 120000 src/libsystemd/sd-daemon/Makefile delete mode 100644 src/libsystemd/sd-daemon/sd-daemon.c delete mode 120000 src/libsystemd/sd-device/Makefile delete mode 100644 src/libsystemd/sd-device/device-enumerator-private.h delete mode 100644 src/libsystemd/sd-device/device-enumerator.c delete mode 100644 src/libsystemd/sd-device/device-internal.h delete mode 100644 src/libsystemd/sd-device/device-private.c delete mode 100644 src/libsystemd/sd-device/device-private.h delete mode 100644 src/libsystemd/sd-device/device-util.h delete mode 100644 src/libsystemd/sd-device/sd-device.c delete mode 120000 src/libsystemd/sd-event/Makefile delete mode 100644 src/libsystemd/sd-event/sd-event.c delete mode 100644 src/libsystemd/sd-event/test-event.c delete mode 120000 src/libsystemd/sd-hwdb/Makefile delete mode 100644 src/libsystemd/sd-hwdb/hwdb-internal.h delete mode 100644 src/libsystemd/sd-hwdb/hwdb-util.h delete mode 100644 src/libsystemd/sd-hwdb/sd-hwdb.c delete mode 120000 src/libsystemd/sd-id128/Makefile delete mode 100644 src/libsystemd/sd-id128/sd-id128.c delete mode 120000 src/libsystemd/sd-login/Makefile delete mode 100644 src/libsystemd/sd-login/sd-login.c delete mode 100644 src/libsystemd/sd-login/test-login.c delete mode 120000 src/libsystemd/sd-netlink/Makefile delete mode 100644 src/libsystemd/sd-netlink/local-addresses.c delete mode 100644 src/libsystemd/sd-netlink/local-addresses.h delete mode 100644 src/libsystemd/sd-netlink/netlink-internal.h delete mode 100644 src/libsystemd/sd-netlink/netlink-message.c delete mode 100644 src/libsystemd/sd-netlink/netlink-socket.c delete mode 100644 src/libsystemd/sd-netlink/netlink-types.c delete mode 100644 src/libsystemd/sd-netlink/netlink-types.h delete mode 100644 src/libsystemd/sd-netlink/netlink-util.c delete mode 100644 src/libsystemd/sd-netlink/netlink-util.h delete mode 100644 src/libsystemd/sd-netlink/rtnl-message.c delete mode 100644 src/libsystemd/sd-netlink/sd-netlink.c delete mode 100644 src/libsystemd/sd-netlink/test-local-addresses.c delete mode 100644 src/libsystemd/sd-netlink/test-netlink.c delete mode 120000 src/libsystemd/sd-network/Makefile delete mode 100644 src/libsystemd/sd-network/network-util.c delete mode 100644 src/libsystemd/sd-network/network-util.h delete mode 100644 src/libsystemd/sd-network/sd-network.c delete mode 120000 src/libsystemd/sd-path/Makefile delete mode 100644 src/libsystemd/sd-path/sd-path.c delete mode 120000 src/libsystemd/sd-resolve/Makefile delete mode 100644 src/libsystemd/sd-resolve/sd-resolve.c delete mode 100644 src/libsystemd/sd-resolve/test-resolve.c delete mode 120000 src/libsystemd/sd-utf8/Makefile delete mode 100644 src/libsystemd/sd-utf8/sd-utf8.c delete mode 100644 src/libudev/.gitignore mode change 120000 => 100644 src/libudev/Makefile create mode 100644 src/libudev/include/libudev.h delete mode 100644 src/libudev/libudev-device-internal.h delete mode 100644 src/libudev/libudev-device-private.c delete mode 100644 src/libudev/libudev-device.c delete mode 100644 src/libudev/libudev-enumerate.c delete mode 100644 src/libudev/libudev-hwdb.c delete mode 100644 src/libudev/libudev-list.c delete mode 100644 src/libudev/libudev-monitor.c delete mode 100644 src/libudev/libudev-private.h delete mode 100644 src/libudev/libudev-queue.c delete mode 100644 src/libudev/libudev-util.c delete mode 100644 src/libudev/libudev.c delete mode 100644 src/libudev/libudev.h delete mode 100644 src/libudev/libudev.pc.in delete mode 100644 src/libudev/libudev.sym create mode 100644 src/libudev/src/.gitignore create mode 100644 src/libudev/src/Makefile create mode 100644 src/libudev/src/libudev-device-internal.h create mode 100644 src/libudev/src/libudev-device-private.c create mode 100644 src/libudev/src/libudev-device.c create mode 100644 src/libudev/src/libudev-enumerate.c create mode 100644 src/libudev/src/libudev-hwdb.c create mode 100644 src/libudev/src/libudev-list.c create mode 100644 src/libudev/src/libudev-monitor.c create mode 100644 src/libudev/src/libudev-private.h create mode 100644 src/libudev/src/libudev-queue.c create mode 100644 src/libudev/src/libudev-util.c create mode 100644 src/libudev/src/libudev.c create mode 100644 src/libudev/src/libudev.pc.in create mode 100644 src/libudev/src/libudev.sym create mode 100644 src/libudev/src/udev.h mode change 120000 => 100644 src/locale/Makefile mode change 120000 => 100644 src/login/Makefile mode change 120000 => 100644 src/machine-id-setup/Makefile delete mode 100644 src/machine/.gitignore delete mode 120000 src/machine/Makefile delete mode 100644 src/machine/image-dbus.c delete mode 100644 src/machine/image-dbus.h delete mode 100644 src/machine/machine-dbus.c delete mode 100644 src/machine/machine-dbus.h delete mode 100644 src/machine/machine.c delete mode 100644 src/machine/machine.h delete mode 100644 src/machine/machinectl.c delete mode 100644 src/machine/machined-dbus.c delete mode 100644 src/machine/machined.c delete mode 100644 src/machine/machined.h delete mode 100644 src/machine/operation.c delete mode 100644 src/machine/operation.h delete mode 100644 src/machine/org.freedesktop.machine1.conf delete mode 100644 src/machine/org.freedesktop.machine1.policy.in delete mode 100644 src/machine/org.freedesktop.machine1.service delete mode 100644 src/machine/test-machine-tables.c mode change 120000 => 100644 src/modules-load/Makefile mode change 120000 => 100644 src/network/Makefile delete mode 120000 src/notify/Makefile delete mode 100644 src/notify/notify.c delete mode 100644 src/nspawn/.gitignore delete mode 120000 src/nspawn/Makefile delete mode 100644 src/nspawn/nspawn-cgroup.c delete mode 100644 src/nspawn/nspawn-cgroup.h delete mode 100644 src/nspawn/nspawn-expose-ports.c delete mode 100644 src/nspawn/nspawn-expose-ports.h delete mode 100644 src/nspawn/nspawn-gperf.gperf delete mode 100644 src/nspawn/nspawn-mount.c delete mode 100644 src/nspawn/nspawn-mount.h delete mode 100644 src/nspawn/nspawn-network.c delete mode 100644 src/nspawn/nspawn-network.h delete mode 100644 src/nspawn/nspawn-patch-uid.c delete mode 100644 src/nspawn/nspawn-patch-uid.h delete mode 100644 src/nspawn/nspawn-register.c delete mode 100644 src/nspawn/nspawn-register.h delete mode 100644 src/nspawn/nspawn-settings.c delete mode 100644 src/nspawn/nspawn-settings.h delete mode 100644 src/nspawn/nspawn-setuid.c delete mode 100644 src/nspawn/nspawn-setuid.h delete mode 100644 src/nspawn/nspawn-stub-pid1.c delete mode 100644 src/nspawn/nspawn-stub-pid1.h delete mode 100644 src/nspawn/nspawn.c delete mode 100644 src/nspawn/test-patch-uid.c mode change 120000 => 100644 src/nss-myhostname/Makefile delete mode 120000 src/nss-mymachines/Makefile delete mode 100644 src/nss-mymachines/nss-mymachines.c delete mode 100644 src/nss-mymachines/nss-mymachines.sym delete mode 120000 src/nss-resolve/Makefile delete mode 100644 src/nss-resolve/nss-resolve.c delete mode 100644 src/nss-resolve/nss-resolve.sym delete mode 120000 src/path/Makefile delete mode 100644 src/path/path.c delete mode 120000 src/quotacheck/Makefile delete mode 100644 src/quotacheck/quotacheck.c delete mode 120000 src/random-seed/Makefile delete mode 100644 src/random-seed/random-seed.c delete mode 120000 src/rc-local-generator/Makefile delete mode 100644 src/rc-local-generator/rc-local-generator.c delete mode 120000 src/remount-fs/Makefile delete mode 100644 src/remount-fs/remount-fs.c delete mode 120000 src/reply-password/Makefile delete mode 100644 src/reply-password/reply-password.c delete mode 100644 src/resolve/.gitignore delete mode 120000 src/resolve/Makefile delete mode 100644 src/resolve/RFCs delete mode 100644 src/resolve/dns-type.c delete mode 100644 src/resolve/dns-type.h delete mode 100644 src/resolve/org.freedesktop.resolve1.conf delete mode 100644 src/resolve/org.freedesktop.resolve1.service delete mode 100644 src/resolve/resolve-tool.c delete mode 100644 src/resolve/resolved-bus.c delete mode 100644 src/resolve/resolved-bus.h delete mode 100644 src/resolve/resolved-conf.c delete mode 100644 src/resolve/resolved-conf.h delete mode 100644 src/resolve/resolved-def.h delete mode 100644 src/resolve/resolved-dns-answer.c delete mode 100644 src/resolve/resolved-dns-answer.h delete mode 100644 src/resolve/resolved-dns-cache.c delete mode 100644 src/resolve/resolved-dns-cache.h delete mode 100644 src/resolve/resolved-dns-dnssec.c delete mode 100644 src/resolve/resolved-dns-dnssec.h delete mode 100644 src/resolve/resolved-dns-packet.c delete mode 100644 src/resolve/resolved-dns-packet.h delete mode 100644 src/resolve/resolved-dns-query.c delete mode 100644 src/resolve/resolved-dns-query.h delete mode 100644 src/resolve/resolved-dns-question.c delete mode 100644 src/resolve/resolved-dns-question.h delete mode 100644 src/resolve/resolved-dns-rr.c delete mode 100644 src/resolve/resolved-dns-rr.h delete mode 100644 src/resolve/resolved-dns-scope.c delete mode 100644 src/resolve/resolved-dns-scope.h delete mode 100644 src/resolve/resolved-dns-search-domain.c delete mode 100644 src/resolve/resolved-dns-search-domain.h delete mode 100644 src/resolve/resolved-dns-server.c delete mode 100644 src/resolve/resolved-dns-server.h delete mode 100644 src/resolve/resolved-dns-stream.c delete mode 100644 src/resolve/resolved-dns-stream.h delete mode 100644 src/resolve/resolved-dns-synthesize.c delete mode 100644 src/resolve/resolved-dns-synthesize.h delete mode 100644 src/resolve/resolved-dns-transaction.c delete mode 100644 src/resolve/resolved-dns-transaction.h delete mode 100644 src/resolve/resolved-dns-trust-anchor.c delete mode 100644 src/resolve/resolved-dns-trust-anchor.h delete mode 100644 src/resolve/resolved-dns-zone.c delete mode 100644 src/resolve/resolved-dns-zone.h delete mode 100644 src/resolve/resolved-etc-hosts.c delete mode 100644 src/resolve/resolved-etc-hosts.h delete mode 100644 src/resolve/resolved-gperf.gperf delete mode 100644 src/resolve/resolved-link-bus.c delete mode 100644 src/resolve/resolved-link-bus.h delete mode 100644 src/resolve/resolved-link.c delete mode 100644 src/resolve/resolved-link.h delete mode 100644 src/resolve/resolved-llmnr.c delete mode 100644 src/resolve/resolved-llmnr.h delete mode 100644 src/resolve/resolved-manager.c delete mode 100644 src/resolve/resolved-manager.h delete mode 100644 src/resolve/resolved-mdns.c delete mode 100644 src/resolve/resolved-mdns.h delete mode 100644 src/resolve/resolved-resolv-conf.c delete mode 100644 src/resolve/resolved-resolv-conf.h delete mode 100644 src/resolve/resolved.c delete mode 100644 src/resolve/resolved.conf.in delete mode 100644 src/resolve/test-data/_443._tcp.fedoraproject.org.pkts delete mode 100644 src/resolve/test-data/_openpgpkey.fedoraproject.org.pkts delete mode 100644 src/resolve/test-data/fake-caa.pkts delete mode 100644 src/resolve/test-data/fedoraproject.org.pkts delete mode 100644 src/resolve/test-data/gandi.net.pkts delete mode 100644 src/resolve/test-data/google.com.pkts delete mode 100644 src/resolve/test-data/kyhwana.org.pkts delete mode 100644 src/resolve/test-data/root.pkts delete mode 100644 src/resolve/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts delete mode 100644 src/resolve/test-data/teamits.com.pkts delete mode 100644 src/resolve/test-data/zbyszek@fedoraproject.org.pkts delete mode 100644 src/resolve/test-dns-packet.c delete mode 100644 src/resolve/test-dnssec-complex.c delete mode 100644 src/resolve/test-dnssec.c delete mode 100644 src/resolve/test-resolve-tables.c delete mode 120000 src/rfkill/Makefile delete mode 100644 src/rfkill/rfkill.c delete mode 120000 src/run/Makefile delete mode 100644 src/run/run.c delete mode 120000 src/shared/Makefile delete mode 100644 src/shared/acl-util.c delete mode 100644 src/shared/acl-util.h delete mode 100644 src/shared/acpi-fpdt.c delete mode 100644 src/shared/acpi-fpdt.h delete mode 100644 src/shared/apparmor-util.c delete mode 100644 src/shared/apparmor-util.h delete mode 100644 src/shared/ask-password-api.c delete mode 100644 src/shared/ask-password-api.h delete mode 100644 src/shared/base-filesystem.c delete mode 100644 src/shared/base-filesystem.h delete mode 100644 src/shared/boot-timestamps.c delete mode 100644 src/shared/boot-timestamps.h delete mode 100644 src/shared/bus-unit-util.c delete mode 100644 src/shared/bus-unit-util.h delete mode 100644 src/shared/bus-util.c delete mode 100644 src/shared/bus-util.h delete mode 100644 src/shared/cgroup-show.c delete mode 100644 src/shared/cgroup-show.h delete mode 100644 src/shared/clean-ipc.c delete mode 100644 src/shared/clean-ipc.h delete mode 100644 src/shared/condition.c delete mode 100644 src/shared/condition.h delete mode 100644 src/shared/conf-parser.c delete mode 100644 src/shared/conf-parser.h delete mode 100644 src/shared/dev-setup.c delete mode 100644 src/shared/dev-setup.h delete mode 100644 src/shared/dns-domain.c delete mode 100644 src/shared/dns-domain.h delete mode 100644 src/shared/dropin.c delete mode 100644 src/shared/dropin.h delete mode 100644 src/shared/efivars.c delete mode 100644 src/shared/efivars.h delete mode 100644 src/shared/firewall-util.c delete mode 100644 src/shared/firewall-util.h delete mode 100644 src/shared/fstab-util.c delete mode 100644 src/shared/fstab-util.h delete mode 100644 src/shared/gcrypt-util.c delete mode 100644 src/shared/gcrypt-util.h delete mode 100644 src/shared/generator.c delete mode 100644 src/shared/generator.h delete mode 100644 src/shared/gpt.h delete mode 100644 src/shared/ima-util.c delete mode 100644 src/shared/ima-util.h delete mode 100644 src/shared/import-util.c delete mode 100644 src/shared/import-util.h delete mode 100644 src/shared/initreq.h delete mode 100644 src/shared/install-printf.c delete mode 100644 src/shared/install-printf.h delete mode 100644 src/shared/install.c delete mode 100644 src/shared/install.h delete mode 100644 src/shared/linux/auto_dev-ioctl.h delete mode 100644 src/shared/logs-show.c delete mode 100644 src/shared/logs-show.h delete mode 100644 src/shared/machine-image.c delete mode 100644 src/shared/machine-image.h delete mode 100644 src/shared/machine-pool.c delete mode 100644 src/shared/machine-pool.h delete mode 100644 src/shared/output-mode.c delete mode 100644 src/shared/output-mode.h delete mode 100644 src/shared/pager.c delete mode 100644 src/shared/pager.h delete mode 100644 src/shared/path-lookup.c delete mode 100644 src/shared/path-lookup.h delete mode 100644 src/shared/ptyfwd.c delete mode 100644 src/shared/ptyfwd.h delete mode 100644 src/shared/resolve-util.c delete mode 100644 src/shared/resolve-util.h delete mode 100644 src/shared/seccomp-util.c delete mode 100644 src/shared/seccomp-util.h delete mode 100644 src/shared/sleep-config.c delete mode 100644 src/shared/sleep-config.h delete mode 100644 src/shared/spawn-ask-password-agent.c delete mode 100644 src/shared/spawn-ask-password-agent.h delete mode 100644 src/shared/spawn-polkit-agent.c delete mode 100644 src/shared/spawn-polkit-agent.h delete mode 100644 src/shared/specifier.c delete mode 100644 src/shared/specifier.h delete mode 100644 src/shared/switch-root.c delete mode 100644 src/shared/switch-root.h delete mode 100644 src/shared/sysctl-util.c delete mode 100644 src/shared/sysctl-util.h delete mode 100644 src/shared/test-tables.h delete mode 100644 src/shared/tests.c delete mode 100644 src/shared/tests.h delete mode 100644 src/shared/udev-util.h delete mode 100644 src/shared/uid-range.c delete mode 100644 src/shared/uid-range.h delete mode 100644 src/shared/utmp-wtmp.c delete mode 100644 src/shared/utmp-wtmp.h delete mode 100644 src/shared/watchdog.c delete mode 100644 src/shared/watchdog.h mode change 120000 => 100644 src/sleep/Makefile mode change 120000 => 100644 src/socket-proxy/Makefile delete mode 100644 src/stdio-bridge/stdio-bridge.c mode change 120000 => 100644 src/sysctl/Makefile delete mode 120000 src/system-update-generator/Makefile delete mode 100644 src/system-update-generator/system-update-generator.c delete mode 120000 src/systemctl/Makefile delete mode 100644 src/systemctl/systemctl.c delete mode 100755 src/systemctl/systemd-sysv-install.SKELETON create mode 100644 src/systemd-ac-power/Makefile create mode 100644 src/systemd-ac-power/ac-power.c create mode 100644 src/systemd-activate/Makefile create mode 100644 src/systemd-activate/activate.c create mode 100644 src/systemd-analyze/.gitignore create mode 100644 src/systemd-analyze/Makefile create mode 100644 src/systemd-analyze/analyze-verify.c create mode 100644 src/systemd-analyze/analyze-verify.h create mode 100644 src/systemd-analyze/analyze.c create mode 100644 src/systemd-ask-password/Makefile create mode 100644 src/systemd-ask-password/ask-password.c create mode 100644 src/systemd-backlight/Makefile create mode 100644 src/systemd-backlight/backlight.c create mode 100644 src/systemd-binfmt/Makefile create mode 100644 src/systemd-binfmt/binfmt.c create mode 100644 src/systemd-cgls/Makefile create mode 100644 src/systemd-cgls/cgls.c create mode 100644 src/systemd-cgroups-agent/Makefile create mode 100644 src/systemd-cgroups-agent/cgroups-agent.c create mode 100644 src/systemd-cgtop/Makefile create mode 100644 src/systemd-cgtop/cgtop.c create mode 100644 src/systemd-cryptsetup/Makefile create mode 100644 src/systemd-cryptsetup/cryptsetup-generator.c create mode 100644 src/systemd-cryptsetup/cryptsetup.c create mode 100644 src/systemd-dbus1-generator/Makefile create mode 100644 src/systemd-dbus1-generator/dbus1-generator.c create mode 100644 src/systemd-debug-generator/Makefile create mode 100644 src/systemd-debug-generator/debug-generator.c create mode 100644 src/systemd-delta/Makefile create mode 100644 src/systemd-delta/delta.c create mode 100644 src/systemd-detect-virt/Makefile create mode 100644 src/systemd-detect-virt/detect-virt.c create mode 100644 src/systemd-escape/Makefile create mode 100644 src/systemd-escape/escape.c create mode 100644 src/systemd-firstboot/Makefile create mode 100644 src/systemd-firstboot/firstboot.c create mode 100644 src/systemd-fsck/Makefile create mode 100644 src/systemd-fsck/fsck.c create mode 100644 src/systemd-fstab-generator/Makefile create mode 100644 src/systemd-fstab-generator/fstab-generator.c create mode 100644 src/systemd-getty-generator/Makefile create mode 100644 src/systemd-getty-generator/getty-generator.c create mode 100644 src/systemd-gpt-auto-generator/Makefile create mode 100644 src/systemd-gpt-auto-generator/gpt-auto-generator.c create mode 100644 src/systemd-hibernate-resume/Makefile create mode 100644 src/systemd-hibernate-resume/hibernate-resume-generator.c create mode 100644 src/systemd-hibernate-resume/hibernate-resume.c create mode 100644 src/systemd-hwdb/Makefile create mode 100644 src/systemd-hwdb/hwdb.c create mode 100644 src/systemd-notify/Makefile create mode 100644 src/systemd-notify/notify.c create mode 100644 src/systemd-nspawn/.gitignore create mode 100644 src/systemd-nspawn/Makefile create mode 100644 src/systemd-nspawn/nspawn-cgroup.c create mode 100644 src/systemd-nspawn/nspawn-cgroup.h create mode 100644 src/systemd-nspawn/nspawn-expose-ports.c create mode 100644 src/systemd-nspawn/nspawn-expose-ports.h create mode 100644 src/systemd-nspawn/nspawn-gperf.gperf create mode 100644 src/systemd-nspawn/nspawn-mount.c create mode 100644 src/systemd-nspawn/nspawn-mount.h create mode 100644 src/systemd-nspawn/nspawn-network.c create mode 100644 src/systemd-nspawn/nspawn-network.h create mode 100644 src/systemd-nspawn/nspawn-patch-uid.c create mode 100644 src/systemd-nspawn/nspawn-patch-uid.h create mode 100644 src/systemd-nspawn/nspawn-register.c create mode 100644 src/systemd-nspawn/nspawn-register.h create mode 100644 src/systemd-nspawn/nspawn-settings.c create mode 100644 src/systemd-nspawn/nspawn-settings.h create mode 100644 src/systemd-nspawn/nspawn-setuid.c create mode 100644 src/systemd-nspawn/nspawn-setuid.h create mode 100644 src/systemd-nspawn/nspawn-stub-pid1.c create mode 100644 src/systemd-nspawn/nspawn-stub-pid1.h create mode 100644 src/systemd-nspawn/nspawn.c create mode 100644 src/systemd-nspawn/test-patch-uid.c create mode 100644 src/systemd-path/Makefile create mode 100644 src/systemd-path/path.c create mode 100644 src/systemd-quotacheck/Makefile create mode 100644 src/systemd-quotacheck/quotacheck.c create mode 100644 src/systemd-random-seed/Makefile create mode 100644 src/systemd-random-seed/random-seed.c create mode 100644 src/systemd-rc-local-generator/Makefile create mode 100644 src/systemd-rc-local-generator/rc-local-generator.c create mode 100644 src/systemd-remount-fs/Makefile create mode 100644 src/systemd-remount-fs/remount-fs.c create mode 100644 src/systemd-reply-password/Makefile create mode 100644 src/systemd-reply-password/reply-password.c create mode 100644 src/systemd-rfkill/Makefile create mode 100644 src/systemd-rfkill/rfkill.c create mode 100644 src/systemd-run/Makefile create mode 100644 src/systemd-run/run.c create mode 100644 src/systemd-shutdown/Makefile create mode 100644 src/systemd-stdio-bridge/Makefile create mode 100644 src/systemd-stdio-bridge/stdio-bridge.c create mode 100644 src/systemd-system-update-generator/Makefile create mode 100644 src/systemd-system-update-generator/system-update-generator.c create mode 100644 src/systemd-sysv-generator/Makefile create mode 100644 src/systemd-sysv-generator/sysv-generator.c create mode 100644 src/systemd-timesync/.gitignore create mode 100644 src/systemd-timesync/Makefile create mode 100644 src/systemd-timesync/timesyncd-conf.c create mode 100644 src/systemd-timesync/timesyncd-conf.h create mode 100644 src/systemd-timesync/timesyncd-gperf.gperf create mode 100644 src/systemd-timesync/timesyncd-manager.c create mode 100644 src/systemd-timesync/timesyncd-manager.h create mode 100644 src/systemd-timesync/timesyncd-server.c create mode 100644 src/systemd-timesync/timesyncd-server.h create mode 100644 src/systemd-timesync/timesyncd.c create mode 100644 src/systemd-timesync/timesyncd.conf.in create mode 100644 src/systemd-tmpfiles/Makefile create mode 100644 src/systemd-tmpfiles/tmpfiles.c create mode 100644 src/systemd-tty-ask-password-agent/Makefile create mode 100644 src/systemd-tty-ask-password-agent/tty-ask-password-agent.c create mode 100644 src/systemd-update-done/Makefile create mode 100644 src/systemd-update-done/update-done.c create mode 100644 src/systemd-update-utmp/Makefile create mode 100644 src/systemd-update-utmp/update-utmp.c create mode 100644 src/systemd-user-sessions/user-sessions.c create mode 100644 src/systemd-vconsole/.gitignore create mode 100644 src/systemd-vconsole/90-vconsole.rules.in create mode 100644 src/systemd-vconsole/Makefile create mode 100644 src/systemd-vconsole/vconsole-setup.c delete mode 120000 src/systemd/Makefile delete mode 100644 src/systemd/_sd-common.h delete mode 100644 src/systemd/sd-bus-protocol.h delete mode 100644 src/systemd/sd-bus-vtable.h delete mode 100644 src/systemd/sd-bus.h delete mode 100644 src/systemd/sd-daemon.h delete mode 100644 src/systemd/sd-device.h delete mode 100644 src/systemd/sd-dhcp-client.h delete mode 100644 src/systemd/sd-dhcp-lease.h delete mode 100644 src/systemd/sd-dhcp-server.h delete mode 100644 src/systemd/sd-dhcp6-client.h delete mode 100644 src/systemd/sd-dhcp6-lease.h delete mode 100644 src/systemd/sd-event.h delete mode 100644 src/systemd/sd-hwdb.h delete mode 100644 src/systemd/sd-id128.h delete mode 100644 src/systemd/sd-ipv4acd.h delete mode 100644 src/systemd/sd-ipv4ll.h delete mode 100644 src/systemd/sd-journal.h delete mode 100644 src/systemd/sd-lldp.h delete mode 100644 src/systemd/sd-login.h delete mode 100644 src/systemd/sd-messages.h delete mode 100644 src/systemd/sd-ndisc.h delete mode 100644 src/systemd/sd-netlink.h delete mode 100644 src/systemd/sd-network.h delete mode 100644 src/systemd/sd-path.h delete mode 100644 src/systemd/sd-resolve.h delete mode 100644 src/systemd/sd-utf8.h mode change 120000 => 100644 src/sysusers/Makefile delete mode 120000 src/sysv-generator/Makefile delete mode 100644 src/sysv-generator/sysv-generator.c mode change 120000 => 100644 src/test/Makefile delete mode 100644 src/timedate/.gitignore delete mode 120000 src/timedate/Makefile delete mode 100644 src/timedate/org.freedesktop.timedate1.conf delete mode 100644 src/timedate/org.freedesktop.timedate1.policy.in delete mode 100644 src/timedate/org.freedesktop.timedate1.service delete mode 100644 src/timedate/timedatectl.c delete mode 100644 src/timedate/timedated.c delete mode 100644 src/timesync/.gitignore delete mode 120000 src/timesync/Makefile delete mode 100644 src/timesync/timesyncd-conf.c delete mode 100644 src/timesync/timesyncd-conf.h delete mode 100644 src/timesync/timesyncd-gperf.gperf delete mode 100644 src/timesync/timesyncd-manager.c delete mode 100644 src/timesync/timesyncd-manager.h delete mode 100644 src/timesync/timesyncd-server.c delete mode 100644 src/timesync/timesyncd-server.h delete mode 100644 src/timesync/timesyncd.c delete mode 100644 src/timesync/timesyncd.conf.in delete mode 120000 src/tmpfiles/Makefile delete mode 100644 src/tmpfiles/tmpfiles.c delete mode 120000 src/tty-ask-password-agent/Makefile delete mode 100644 src/tty-ask-password-agent/tty-ask-password-agent.c mode change 120000 => 100644 src/udev/Makefile mode change 120000 => 100644 src/udev/ata_id/Makefile mode change 120000 => 100644 src/udev/cdrom_id/Makefile mode change 120000 => 100644 src/udev/collect/Makefile mode change 120000 => 100644 src/udev/mtd_probe/Makefile delete mode 120000 src/udev/net/Makefile mode change 120000 => 100644 src/udev/scsi_id/Makefile delete mode 100644 src/udev/udev.h mode change 120000 => 100644 src/udev/v4l_id/Makefile delete mode 120000 src/update-done/Makefile delete mode 100644 src/update-done/update-done.c delete mode 120000 src/update-utmp/Makefile delete mode 100644 src/update-utmp/update-utmp.c delete mode 120000 src/user-sessions/Makefile delete mode 100644 src/user-sessions/user-sessions.c delete mode 100644 src/vconsole/.gitignore delete mode 100644 src/vconsole/90-vconsole.rules.in delete mode 120000 src/vconsole/Makefile delete mode 100644 src/vconsole/vconsole-setup.c delete mode 120000 sysctl.d/Makefile delete mode 120000 system-preset/Makefile delete mode 120000 sysusers.d/Makefile delete mode 100644 test/TEST-01-BASIC/Makefile delete mode 120000 test/TEST-02-CRYPTSETUP/Makefile delete mode 120000 test/TEST-03-JOBS/Makefile delete mode 120000 test/TEST-04-JOURNAL/Makefile delete mode 120000 test/TEST-05-RLIMITS/Makefile delete mode 120000 test/TEST-06-SELINUX/Makefile delete mode 120000 test/TEST-07-ISSUE-1981/Makefile delete mode 120000 test/TEST-08-ISSUE-2730/Makefile delete mode 120000 test/TEST-09-ISSUE-2691/Makefile delete mode 120000 test/TEST-10-ISSUE-2467/Makefile delete mode 120000 test/TEST-11-ISSUE-3166/Makefile delete mode 120000 test/TEST-12-ISSUE-3171/Makefile delete mode 100755 test/test-efi-create-disk.sh delete mode 120000 tmpfiles.d/Makefile delete mode 120000 units/Makefile delete mode 120000 units/user/Makefile (limited to 'src/network/networkd-network.h') diff --git a/build-aux/Makefile.each.head/.gitignore b/build-aux/Makefile.each.head/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/build-aux/Makefile.each.tail/.gitignore b/build-aux/Makefile.each.tail/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/build-aux/Makefile.each.tail/20-systemd.mk b/build-aux/Makefile.each.tail/20-systemd.mk new file mode 100644 index 0000000000..72dbec6714 --- /dev/null +++ b/build-aux/Makefile.each.tail/20-systemd.mk @@ -0,0 +1,49 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +%-from-name.gperf: %-list.txt + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)$(AWK) 'BEGIN{ print "struct $(notdir $*)_name { const char* name; int id; };"; print "%null-strings"; print "%%";} { printf "%s, %s\n", $$1, $$1 }' <$< >$@ + +%-from-name.h: %-from-name.gperf + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GPERF)$(GPERF) -L ANSI-C -t --ignore-case -N lookup_$(notdir $*) -H hash_$(notdir $*)_name -p -C <$< >$@ + +$(outdir)/%: sysctl.d/%.in + $(SED_PROCESS) + +%.sh: %.sh.in + $(SED_PROCESS) + $(AM_V_GEN)chmod +x $@ + +$(outdir)/%.c: src/%.gperf + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GPERF)$(GPERF) < $< > $@ + +$(outdir)/%: src/%.m4 $(top_builddir)/config.status + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_M4)$(M4) -P $(M4_DEFINES) < $< > $@ + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/build-aux/Makefile.once.head/.gitignore b/build-aux/Makefile.once.head/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/build-aux/Makefile.once.head/20-systemd.mk b/build-aux/Makefile.once.head/20-systemd.mk new file mode 100644 index 0000000000..caed83526e --- /dev/null +++ b/build-aux/Makefile.once.head/20-systemd.mk @@ -0,0 +1,186 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} +AM_MAKEFLAGS = --no-print-directory +AUTOMAKE_OPTIONS = color-tests parallel-tests + +GCC_COLORS ?= 'ooh, shiny!' +export GCC_COLORS + +SUBDIRS = . po + +# remove targets if the command fails +.DELETE_ON_ERROR: + +# keep intermediate files +.SECONDARY: + +# Keep the test-suite.log +.PRECIOUS: $(TEST_SUITE_LOG) Makefile + +V ?= + +AM_V_M4 = $(AM_V_M4_$(V)) +AM_V_M4_ = $(AM_V_M4_$(AM_DEFAULT_VERBOSITY)) +AM_V_M4_0 = @echo " M4 " $@; +AM_V_M4_1 = + +AM_V_XSLT = $(AM_V_XSLT_$(V)) +AM_V_XSLT_ = $(AM_V_XSLT_$(AM_DEFAULT_VERBOSITY)) +AM_V_XSLT_0 = @echo " XSLT " $@; +AM_V_XSLT_1 = + +AM_V_GPERF = $(AM_V_GPERF_$(V)) +AM_V_GPERF_ = $(AM_V_GPERF_$(AM_DEFAULT_VERBOSITY)) +AM_V_GPERF_0 = @echo " GPERF " $@; +AM_V_GPERF_1 = + +AM_V_LN = $(AM_V_LN_$(V)) +AM_V_LN_ = $(AM_V_LN_$(AM_DEFAULT_VERBOSITY)) +AM_V_LN_0 = @echo " LN " $@; +AM_V_LN_1 = + +AM_V_RM = $(AM_V_RM_$(V)) +AM_V_RM_ = $(AM_V_RM_$(AM_DEFAULT_VERBOSITY)) +AM_V_RM_0 = @echo " RM " $@; +AM_V_RM_1 = + +AM_V_CC = $(AM_V_CC_$(V)) +AM_V_CC_ = $(AM_V_CC_$(AM_DEFAULT_VERBOSITY)) +AM_V_CC_0 = @echo " CC " $@; +AM_V_CC_1 = + +AM_V_CCLD = $(AM_V_CCLD_$(V)) +AM_V_CCLD_ = $(AM_V_CCLD_$(AM_DEFAULT_VERBOSITY)) +AM_V_CCLD_0 = @echo " CCLD " $@; +AM_V_CCLD_1 = + +AM_V_P = $(AM_V_P_$(V)) +AM_V_P_ = $(AM_V_P_$(AM_DEFAULT_VERBOSITY)) +AM_V_P_0 = false +AM_V_P_1 = : + +AM_V_GEN = $(AM_V_GEN_$(V)) +AM_V_GEN_ = $(AM_V_GEN_$(AM_DEFAULT_VERBOSITY)) +AM_V_GEN_0 = @echo " GEN " $@; +AM_V_GEN_1 = + +AM_V_at = $(AM_V_at_$(V)) +AM_V_at_ = $(AM_V_at_$(AM_DEFAULT_VERBOSITY)) +AM_V_at_0 = @ +AM_V_at_1 = + +AM_V_lt = $(AM_V_lt_$(V)) +AM_V_lt_ = $(AM_V_lt_$(AM_DEFAULT_VERBOSITY)) +AM_V_lt_0 = --silent +AM_V_lt_1 = + +INTLTOOL_V_MERGE = $(INTLTOOL_V_MERGE_$(V)) +INTLTOOL_V_MERGE_OPTIONS = $(intltool_v_merge_options_$(V)) +INTLTOOL_V_MERGE_ = $(INTLTOOL_V_MERGE_$(AM_DEFAULT_VERBOSITY)) +INTLTOOL_V_MERGE_0 = @echo " ITMRG " $@; +INTLTOOL_V_MERGE_1 = + +substitutions = \ + '|libexecdir=$(libexecdir)|' \ + '|bindir=$(bindir)|' \ + '|bindir=$(bindir)|' \ + '|SYSTEMCTL=$(bindir)/systemctl|' \ + '|SYSTEMD_NOTIFY=$(bindir)/systemd-notify|' \ + '|pkgsysconfdir=$(pkgsysconfdir)|' \ + '|SYSTEM_CONFIG_UNIT_PATH=$(pkgsysconfdir)/system|' \ + '|USER_CONFIG_UNIT_PATH=$(pkgsysconfdir)/user|' \ + '|pkgdatadir=$(pkgdatadir)|' \ + '|systemunitdir=$(systemunitdir)|' \ + '|userunitdir=$(userunitdir)|' \ + '|systempresetdir=$(systempresetdir)|' \ + '|userpresetdir=$(userpresetdir)|' \ + '|udevhwdbdir=$(udevhwdbdir)|' \ + '|udevrulesdir=$(udevrulesdir)|' \ + '|catalogdir=$(catalogdir)|' \ + '|tmpfilesdir=$(tmpfilesdir)|' \ + '|sysusersdir=$(sysusersdir)|' \ + '|sysctldir=$(sysctldir)|' \ + '|systemgeneratordir=$(systemgeneratordir)|' \ + '|usergeneratordir=$(usergeneratordir)|' \ + '|CERTIFICATEROOT=$(CERTIFICATEROOT)|' \ + '|PACKAGE_VERSION=$(PACKAGE_VERSION)|' \ + '|PACKAGE_NAME=$(PACKAGE_NAME)|' \ + '|PACKAGE_URL=$(PACKAGE_URL)|' \ + '|RANDOM_SEED_DIR=$(localstatedir)/lib/systemd/|' \ + '|RANDOM_SEED=$(localstatedir)/lib/systemd/random-seed|' \ + '|prefix=$(prefix)|' \ + '|exec_prefix=$(exec_prefix)|' \ + '|libdir=$(libdir)|' \ + '|includedir=$(includedir)|' \ + '|VERSION=$(VERSION)|' \ + '|prefix=$(prefix)|' \ + '|udevlibexecdir=$(udevlibexecdir)|' \ + '|SUSHELL=$(SUSHELL)|' \ + '|SULOGIN=$(SULOGIN)|' \ + '|DEBUGTTY=$(DEBUGTTY)|' \ + '|KILL=$(KILL)|' \ + '|KMOD=$(KMOD)|' \ + '|MOUNT_PATH=$(MOUNT_PATH)|' \ + '|UMOUNT_PATH=$(UMOUNT_PATH)|' \ + '|MKDIR_P=$(MKDIR_P)|' \ + '|QUOTAON=$(QUOTAON)|' \ + '|QUOTACHECK=$(QUOTACHECK)|' \ + '|SYSTEM_SYSVINIT_PATH=$(sysvinitdir)|' \ + '|VARLOGDIR=$(varlogdir)|' \ + '|RC_LOCAL_SCRIPT_PATH_START=$(RC_LOCAL_SCRIPT_PATH_START)|' \ + '|RC_LOCAL_SCRIPT_PATH_STOP=$(RC_LOCAL_SCRIPT_PATH_STOP)|' \ + '|PYTHON=$(PYTHON)|' \ + '|NTP_SERVERS=$(NTP_SERVERS)|' \ + '|DNS_SERVERS=$(DNS_SERVERS)|' \ + '|DEFAULT_DNSSEC_MODE=$(DEFAULT_DNSSEC_MODE)|' \ + '|KILL_USER_PROCESSES=$(KILL_USER_PROCESSES)|' \ + '|systemuidmax=$(SYSTEM_UID_MAX)|' \ + '|systemgidmax=$(SYSTEM_GID_MAX)|' \ + '|TTY_GID=$(TTY_GID)|' \ + '|systemsleepdir=$(systemsleepdir)|' \ + '|systemshutdowndir=$(systemshutdowndir)|' \ + '|binfmtdir=$(binfmtdir)|' \ + '|modulesloaddir=$(modulesloaddir)|' + +SED_PROCESS = \ + $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ + $(SED) $(subst '|,-e 's|@,$(subst =,\@|,$(subst |',|g',$(substitutions)))) \ + < $< > $@ + +# Stupid test that everything purported to be exported really is +define generate-sym-test + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_at)printf '#include \n' > $@ + $(AM_V_at)printf '#include "%s"\n' $(notdir $(filter %.h, $^)) >> $@ + $(AM_V_at)printf 'void* functions[] = {\n' >> $@ + $(AM_V_GEN)sed -r -n 's/^ +([a-zA-Z0-9_]+);/\1,/p' $< >> $@ + $(AM_V_at)printf '};\nint main(void) {\n' >> $@ + $(AM_V_at)printf 'unsigned i; for (i=0;i> $@ + $(AM_V_at)printf 'return 0; }\n' >> $@ +endef + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/build-aux/Makefile.once.tail/.gitignore b/build-aux/Makefile.once.tail/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/build-aux/Makefile.once.tail/20-systemd.mk b/build-aux/Makefile.once.tail/20-systemd.mk new file mode 100644 index 0000000000..7455244e3c --- /dev/null +++ b/build-aux/Makefile.once.tail/20-systemd.mk @@ -0,0 +1,97 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +# Let's run all tests of the test suite, but under valgrind. Let's +# exclude perl/python/shell scripts we have in there +.PHONY: valgrind-tests +valgrind-tests: $(TESTS) + $(AM_V_GEN)for f in $(filter-out %.pl %.py, $^); do \ + if file $$f | grep -q shell; then \ + echo -e "$${x}Skipping non-binary $$f"; else \ + echo -e "$${x}Running $$f"; \ + $(LIBTOOL) --mode=execute valgrind -q --leak-check=full --max-stackframe=5242880 --error-exitcode=55 $(builddir)/$$f ; fi; \ + x="\n\n"; \ + done + +exported-%: % + $(AM_V_GEN)$(NM) -g --defined-only $(builddir)/.libs/$(<:.la=.so) 2>&1 /dev/null | grep " T " | cut -d" " -f3 > $@ + +exported: $(addprefix exported-, $(lib_LTLIBRARIES)) + $(AM_V_GEN)sort -u $^ > $@ + +.PHONY: check-api-docs +check-api-docs: exported man + $(AM_V_GEN)for symbol in `cat exported` ; do \ + if test -f $(builddir)/man/$$symbol.html ; then \ + echo " Symbol $$symbol() is documented." ; \ + else \ + echo "‣ Symbol $$symbol() lacks documentation." ; \ + fi ; \ + done + +OBJECT_VARIABLES:=$(filter %_OBJECTS,$(.VARIABLES)) +ALL_OBJECTS:=$(foreach v,$(OBJECT_VARIABLES),$($(v))) + +undefined defined: $(ALL_OBJECTS) + $(AM_V_GEN)for f in $(ALL_OBJECTS) ; do \ + $(NM) -g --$@-only `echo $(builddir)/"$$f" | sed -e 's,\([^/]*\).lo$$,.libs/\1.o,'` ; \ + done | cut -c 20- | cut -d @ -f 1 | sort -u > $@ + +CLEANFILES += \ + defined \ + undefined + +.PHONY: check-api-unused +check-api-unused: defined undefined exported + ( cat exported undefined ) | sort -u | diff -u - defined | grep ^+ | grep -v ^+++ | cut -c2- + +.PHONY: check-includes +check-includes: $(top_srcdir)/tools/check-includes.pl + $(AM_V_GEN) find * -name '*.[hcS]' -type f -print | sort -u \ + | xargs $(top_srcdir)/tools/check-includes.pl + +EXTRA_DIST += \ + $(top_srcdir)/tools/check-includes.pl + +.PHONY: cppcheck +cppcheck: + cppcheck --enable=all -q $(top_srcdir) + +# Used to extract compile flags for YCM. +print-%: + @echo $($*) + +git-contrib: + @git shortlog -s `git describe --abbrev=0`.. | cut -c8- | awk '{ print $$0 "," }' | sort -u + +EXTRA_DIST += \ + tools/gdb-sd_dump_hashmaps.py + +list-keys: + gpg --verbose --no-options --no-default-keyring --no-auto-key-locate --batch --trust-model=always --keyring=$(srcdir)/src/import/import-pubring.gpg --list-keys + +add-key: + gpg --verbose --no-options --no-default-keyring --no-auto-key-locate --batch --trust-model=always --keyring=$(srcdir)/src/import/import-pubring.gpg --import - +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/catalog/Makefile b/catalog/Makefile deleted file mode 120000 index bd1047548b..0000000000 --- a/catalog/Makefile +++ /dev/null @@ -1 +0,0 @@ -../src/Makefile \ No newline at end of file diff --git a/catalog/systemd.be.catalog b/catalog/systemd.be.catalog deleted file mode 100644 index 051f49492f..0000000000 --- a/catalog/systemd.be.catalog +++ /dev/null @@ -1,260 +0,0 @@ -# This file is part of systemd. -# -# Copyright 2012 Lennart Poettering -# Copyright 2015 Viktar Vaŭčkievič -# -# 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 . - -# Message catalog for systemd's own messages -# Belarusian translation - -# The catalog format is documented on -# Фармат каталога апісаны на старонцы -# http://www.freedesktop.org/wiki/Software/systemd/catalog - -# For an explanation why we do all this, see https://xkcd.com/1024/ - --- f77379a8490b408bbe5f6940505a777b -Subject: Сэрвіс журналявання запусціўся -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Працэс сістэмнага журналявання запусціўся, адкрыў файлы для -запісу і гатовы апрацоўваць запыты. - --- d93fb3c9c24d451a97cea615ce59c00b -Subject: Сэрвіс журналявання спыніўся -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Працэс сістэмнага журналявання спыніўся і закрыў усе файлы. - --- a596d6fe7bfa4994828e72309e95d61e -Subject: Паведамленні з сэрвісу адкінуты -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:journald.conf(5) - -Сэрвіс адправіў занадта штат паведамленняў за кароткі прамежак часу. -Частка паведамленняў была адкінута. - -Майце на ўвазе, што былі адкінуты паведамлення толькі гэтага сэрвісу. -Паведамленні іншых сэрвісаў засталіся. - -Мяжа, пасля якой паведамленні будуць адкінуты, наладжваецца з -дапамогай RateLimitIntervalSec= і RateLimitBurst= у файле -/etc/systemd/journald.conf. Глядзіце journald.conf(5) для дэталей. - --- e9bf28e6e834481bb6f48f548ad13606 -Subject: Паведамленні страчаны -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Паведамленні ядра былі страчаны, так як сістэма журналявання не паспела -іх апрацаваць. - --- fc2e22bc6ee647b6b90729ab34a250b1 -Subject: Працэс @COREDUMP_PID@ (@COREDUMP_COMM@) скінуў дамп памяці -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:core(5) - -Працэс @COREDUMP_PID@ (@COREDUMP_COMM@) разбіўся і скінуў дамп памяці. - -Звычайна гэта сведчыць аб памылцы ў праграмным кодзе. -Рэкамендуецца паведаміць аб гэтым распрацоўнікам. - --- 8d45620c1a4348dbb17410da57c60c66 -Subject: Новая сесія № @SESSION_ID@ створана для карыстальніка @USER_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Новая сесія з № @SESSION_ID@ створана для карыстальніка @USER_ID@. - -Лідар гэтай сесіі пад № @LEADER@. - --- 3354939424b4456d9802ca8333ed424a -Subject: Сесія № @SESSION_ID@ спынена -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Сесія № @SESSION_ID@ спынена. - --- fcbefc5da23d428093f97c82a9290f7b -Subject: Даступна новае працоўнае месца № @SEAT_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Новае працоўнае месца № @SEAT_ID@ наладжана і даступна для выкарыстання. - --- e7852bfe46784ed0accde04bc864c2d5 -Subject: Працоўнае месца № @SEAT_ID@ выдалена -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Працоўнае месца № @SEAT_ID@ выдалена і больш не даступна. - --- c7a787079b354eaaa9e77b371893cd27 -Subject: Час зменены -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Сістэмны гадзіннік зменены на @REALTIME@ мікрасекунд ад 1 студзеня 1970. - --- 45f82f4aef7a4bbf942ce861d1f20990 -Subject: Часавы пояс зменены на @TIMEZONE@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Сістэмны часавы пояс зменены на @TIMEZONE@. - --- b07a249cd024414a82dd00cd181378ff -Subject: Запуск сістэмы завяршыўся -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Усе сістэмныя сэрвісы, неабходныя для загрузкі сістэмы, паспяхова -запусціліся. Майце на ўвазе, што гэта не значыць, што машына нічога не -робіць. Магчыма, некаторыя сэрвісы яшчэ ініцыялізіруюцца. - -На запуск ядра спатрэбілася @KERNEL_USEC@ мікрасекунд. - -На запуск пачатковага RAM-дыска спатрэбілася @INITRD_USEC@ мікрасекунд. - -На запуск сістэмных сэрвісаў спатрэбілася @USERSPACE_USEC@ мікрасекунд. - --- 6bbd95ee977941e497c48be27c254128 -Subject: Сістэма перайшла ў стан сну @SLEEP@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Цяпер сістэма перайшла у стан сну @SLEEP@. - --- 8811e6df2a8e40f58a94cea26f8ebf14 -Subject: Сістэма выйшла са стана сну @SLEEP@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Цяпер сістэма выйшла са стана сну @SLEEP@. - --- 98268866d1d54a499c4e98921d93bc40 -Subject: Сістэма завяршае работу -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Пачаўся працэс выключэння сістэмы. -Спыняюцца ўсе сістэмныя сэрвісы і дэмантуюцца файлавыя сістэмы. - --- 7d4958e842da4a758f6c1cdc7b36dcc5 -Subject: Юніт @UNIT@ запускаецца -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Пачаўся працэс запуску юніта @UNIT@. - --- 39f53479d3a045ac8e11786248231fbf -Subject: Юніт @UNIT@ запусціўся -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Працэс запуску юніта @UNIT@ завершаны. - -Вынік: @RESULT@. - --- de5b426a63be47a7b6ac3eaac82e2f6f -Subject: Юніт @UNIT@ спыняецца -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Пачаўся працэс спынення юніта @UNIT@. - --- 9d1aaa27d60140bd96365438aad20286 -Subject: Юніт @UNIT@ спынены -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Працэс спынення юніта @UNIT@ завершаны. - --- be02cf6855d2428ba40df7e9d022f03d -Subject: Збой юніта @UNIT@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Збой юніта @UNIT@. - -Вынік: @RESULT@. - --- d34d037fff1847e6ae669a370e694725 -Subject: Юніт @UNIT@ перачытвае сваю канфігурацыю -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Юніт @UNIT@ пачаў перачытваць сваю канфігурацыю. - --- 7b05ebc668384222baa8881179cfda54 -Subject: Юніт @UNIT@ перачытаў сваю канфігурацыю -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Юніт @UNIT@ перачытаў сваю канфігурацыю. - -Вынік: @RESULT@. - --- 641257651c1b4ec9a8624d7a40a9e1e7 -Subject: Працэс @EXECUTABLE@ не можа быць выкананы -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Працэс @EXECUTABLE@ не можа быць выкананы ў выніку збою. - -Ён вярнуў памылку нумар @ERRNO@. - --- 0027229ca0644181a76c4e92458afa2e -Sibject: Адно ці больш паведамленняў не былі накіраваны ў syslog -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Адно ці больш паведамленняў не былі накіраваны ў syslog сэрвіс, які -выконваецца паралельна з journald. Звычайна гэта значыць, што -рэалізацыя syslog не паспявае апрацаваць паведамленні з неабходнай -хуткасцю. - --- 1dee0369c7fc4736b7099b38ecb46ee7 -Subject: Кропка мантавання не пустая -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Каталог @WHERE@ указаны як кропка мантавання (другое поле ў /etc/fstab -ці Where= поле ў файле юніта systemd) і не пусты. Гэта не перашкаджае -мантаванню, але існуючыя ў ім файлы будуць недаступны. Для доступу да -іх, калі ласка, змантуйце гэтую файлавую сістэму ў іншае месца. - --- 24d8d4452573402496068381a6312df2 -Subject: Віртуальная машына або кантэйнер запусціўся -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Віртуальная машына @NAME@ з лідарам № @LEADER@ запусцілася і -гатова для выкарыстання. - --- 58432bd3bace477cb514b56381b8a758 -Subject: Віртуальная машына або кантэйнер спынены -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Віртуальная машына @NAME@ з лідарам № @LEADER@ спынена. diff --git a/catalog/systemd.be@latin.catalog b/catalog/systemd.be@latin.catalog deleted file mode 100644 index 6ab361aafb..0000000000 --- a/catalog/systemd.be@latin.catalog +++ /dev/null @@ -1,260 +0,0 @@ -# This file is part of systemd. -# -# Copyright 2012 Lennart Poettering -# Copyright 2015 Viktar Vaŭčkievič -# -# 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 . - -# Message catalog for systemd's own messages -# Belarusian Latin translation - -# The catalog format is documented on -# Farmat kataloha apisany na staroncy -# http://www.freedesktop.org/wiki/Software/systemd/catalog - -# For an explanation why we do all this, see https://xkcd.com/1024/ - --- f77379a8490b408bbe5f6940505a777b -Subject: Servis žurnaliavannia zapusciŭsia -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Praces sistemnaha žurnaliavannia zapusciŭsia, adkryŭ fajly dlia -zapisu i hatovy apracoŭvać zapyty. - --- d93fb3c9c24d451a97cea615ce59c00b -Subject: Servis žurnaliavannia spyniŭsia -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Praces sistemnaha žurnaliavannia spyniŭsia i zakryŭ usie fajly. - --- a596d6fe7bfa4994828e72309e95d61e -Subject: Paviedamlienni z servisu adkinuty -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:journald.conf(5) - -Servis adpraviŭ zanadta štat paviedamlienniaŭ za karotki pramiežak času. -Častka paviedamlienniaŭ byla adkinuta. - -Majcie na ŭvazie, što byli adkinuty paviedamliennia toĺki hetaha servisu. -Paviedamlienni inšych servisaŭ zastalisia. - -Miaža, paslia jakoj paviedamlienni buduć adkinuty, naladžvajecca z -dapamohaj RateLimitIntervalSec= i RateLimitBurst= u fajlie -/etc/systemd/journald.conf. Hliadzicie journald.conf(5) dlia detaliej. - --- e9bf28e6e834481bb6f48f548ad13606 -Subject: Paviedamlienni stračany -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Paviedamlienni jadra byli stračany, tak jak sistema žurnaliavannia nie paspiela -ich apracavać. - --- fc2e22bc6ee647b6b90729ab34a250b1 -Subject: Praces @COREDUMP_PID@ (@COREDUMP_COMM@) skinuŭ damp pamiaci -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:core(5) - -Praces @COREDUMP_PID@ (@COREDUMP_COMM@) razbiŭsia i skinuŭ damp pamiaci. - -Zvyčajna heta sviedčyć ab pamylcy ŭ prahramnym kodzie. -Rekamiendujecca paviedamić ab hetym raspracoŭnikam. - --- 8d45620c1a4348dbb17410da57c60c66 -Subject: Novaja siesija № @SESSION_ID@ stvorana dlia karystaĺnika @USER_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Novaja siesija z № @SESSION_ID@ stvorana dlia karystaĺnika @USER_ID@. - -Lidar hetaj siesii pad № @LEADER@. - --- 3354939424b4456d9802ca8333ed424a -Subject: Siesija № @SESSION_ID@ spyniena -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Siesija № @SESSION_ID@ spyniena. - --- fcbefc5da23d428093f97c82a9290f7b -Subject: Dastupna novaje pracoŭnaje miesca № @SEAT_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Novaje pracoŭnaje miesca № @SEAT_ID@ naladžana i dastupna dlia vykarystannia. - --- e7852bfe46784ed0accde04bc864c2d5 -Subject: Pracoŭnaje miesca № @SEAT_ID@ vydaliena -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Pracoŭnaje miesca № @SEAT_ID@ vydaliena i boĺš nie dastupna. - --- c7a787079b354eaaa9e77b371893cd27 -Subject: Čas zmienieny -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Sistemny hadzinnik zmienieny na @REALTIME@ mikrasiekund ad 1 studzienia 1970. - --- 45f82f4aef7a4bbf942ce861d1f20990 -Subject: Časavy pojas zmienieny na @TIMEZONE@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Sistemny časavy pojas zmienieny na @TIMEZONE@. - --- b07a249cd024414a82dd00cd181378ff -Subject: Zapusk sistemy zaviaršyŭsia -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Usie sistemnyja servisy, nieabchodnyja dlia zahruzki sistemy, paspiachova -zapuscilisia. Majcie na ŭvazie, što heta nie značyć, što mašyna ničoha nie -robić. Mahčyma, niekatoryja servisy jašče inicyjalizirujucca. - -Na zapusk jadra spatrebilasia @KERNEL_USEC@ mikrasiekund. - -Na zapusk pačatkovaha RAM-dyska spatrebilasia @INITRD_USEC@ mikrasiekund. - -Na zapusk sistemnych servisaŭ spatrebilasia @USERSPACE_USEC@ mikrasiekund. - --- 6bbd95ee977941e497c48be27c254128 -Subject: Sistema pierajšla ŭ stan snu @SLEEP@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Ciapier sistema pierajšla u stan snu @SLEEP@. - --- 8811e6df2a8e40f58a94cea26f8ebf14 -Subject: Sistema vyjšla sa stana snu @SLEEP@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Ciapier sistema vyjšla sa stana snu @SLEEP@. - --- 98268866d1d54a499c4e98921d93bc40 -Subject: Sistema zaviaršaje rabotu -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Pačaŭsia praces vykliučennia sistemy. -Spyniajucca ŭsie sistemnyja servisy i demantujucca fajlavyja sistemy. - --- 7d4958e842da4a758f6c1cdc7b36dcc5 -Subject: Junit @UNIT@ zapuskajecca -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Pačaŭsia praces zapusku junita @UNIT@. - --- 39f53479d3a045ac8e11786248231fbf -Subject: Junit @UNIT@ zapusciŭsia -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Praces zapusku junita @UNIT@ zavieršany. - -Vynik: @RESULT@. - --- de5b426a63be47a7b6ac3eaac82e2f6f -Subject: Junit @UNIT@ spyniajecca -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Pačaŭsia praces spyniennia junita @UNIT@. - --- 9d1aaa27d60140bd96365438aad20286 -Subject: Junit @UNIT@ spynieny -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Praces spyniennia junita @UNIT@ zavieršany. - --- be02cf6855d2428ba40df7e9d022f03d -Subject: Zboj junita @UNIT@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Zboj junita @UNIT@. - -Vynik: @RESULT@. - --- d34d037fff1847e6ae669a370e694725 -Subject: Junit @UNIT@ pieračytvaje svaju kanfihuracyju -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Junit @UNIT@ pačaŭ pieračytvać svaju kanfihuracyju. - --- 7b05ebc668384222baa8881179cfda54 -Subject: Junit @UNIT@ pieračytaŭ svaju kanfihuracyju -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Junit @UNIT@ pieračytaŭ svaju kanfihuracyju. - -Vynik: @RESULT@. - --- 641257651c1b4ec9a8624d7a40a9e1e7 -Subject: Praces @EXECUTABLE@ nie moža być vykanany -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Praces @EXECUTABLE@ nie moža być vykanany ŭ vyniku zboju. - -Jon viarnuŭ pamylku numar @ERRNO@. - --- 0027229ca0644181a76c4e92458afa2e -Sibject: Adno ci boĺš paviedamlienniaŭ nie byli nakiravany ŭ syslog -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Adno ci boĺš paviedamlienniaŭ nie byli nakiravany ŭ syslog servis, jaki -vykonvajecca paralieĺna z journald. Zvyčajna heta značyć, što -realizacyja syslog nie paspiavaje apracavać paviedamlienni z nieabchodnaj -chutkasciu. - --- 1dee0369c7fc4736b7099b38ecb46ee7 -Subject: Kropka mantavannia nie pustaja -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Kataloh @WHERE@ ukazany jak kropka mantavannia (druhoje polie ŭ /etc/fstab -ci Where= polie ŭ fajlie junita systemd) i nie pusty. Heta nie pieraškadžaje -mantavanniu, alie isnujučyja ŭ im fajly buduć niedastupny. Dlia dostupu da -ich, kali laska, zmantujcie hetuju fajlavuju sistemu ŭ inšaje miesca. - --- 24d8d4452573402496068381a6312df2 -Subject: Virtuaĺnaja mašyna abo kantejnier zapusciŭsia -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Virtuaĺnaja mašyna @NAME@ z lidaram № @LEADER@ zapuscilasia i -hatova dlia vykarystannia. - --- 58432bd3bace477cb514b56381b8a758 -Subject: Virtuaĺnaja mašyna abo kantejnier spynieny -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Virtuaĺnaja mašyna @NAME@ z lidaram № @LEADER@ spyniena. diff --git a/catalog/systemd.bg.catalog b/catalog/systemd.bg.catalog deleted file mode 100644 index 30246c0bbe..0000000000 --- a/catalog/systemd.bg.catalog +++ /dev/null @@ -1,324 +0,0 @@ -# This file is part of systemd. -# -# Copyright 2012 Lennart Poettering -# Copyright 2016 Alexander Shopov -# -# 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 . - -# Message catalog for systemd's own messages - -# The catalog format is documented on -# http://www.freedesktop.org/wiki/Software/systemd/catalog - -# For an explanation why we do all this, see https://xkcd.com/1024/ - --- f77379a8490b408bbe5f6940505a777b -Subject: Журналният процес е пуснат -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Журналният процес на системата е стартирал, отворил е журналните файлове -за запис и може да приема заявки. - --- d93fb3c9c24d451a97cea615ce59c00b -Subject: Журналният процес е спрян -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Журналният процес на системата е спрян, затворени са всички отворени -журнални файлове. - --- ec387f577b844b8fa948f33cad9a75e6 -Subject: Пространството върху диска заето от журналните файлове -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@JOURNAL_NAME@ (@JOURNAL_PATH@) в момента заема @CURRENT_USE_PRETTY@. -Максималният зададен размер е @MAX_USE_PRETTY@. -Свободни се оставят поне @DISK_KEEP_FREE_PRETTY@ (от текущо наличните @DISK_AVAILABLE_PRETTY@). -Максималният наложен размер е @LIMIT_PRETTY@, от който @AVAILABLE_PRETTY@ са свободни. - -Настройките за максималния размер на журнала върху диска се -управляват чрез директивите „SystemMaxUse=“, „SystemKeepFree=“, -„SystemMaxFileSize=“, „RuntimeMaxUse=“, „RuntimeKeepFree=“ и -„RuntimeMaxFileSize=“ във файла „/etc/systemd/journald.conf“. -За повече информация прегледайте „journald.conf(5)“ от ръководството. - --- a596d6fe7bfa4994828e72309e95d61e -Subject: Съобщенията от някоя услуга не са допуснати -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:journald.conf(5) - -Някоя услуга генерира прекалено много съобщения за кратък период. -Част само от нейните съобщения са отхвърляни. - -Съобщенията от другите услуги не са засегнати. - -Настройките за максималния брой съобщения, които ще се обработят, се -управляват чрез директивите „RateLimitInterval=“ и „RateLimitBurst=“ във -файла „/etc/systemd/journald.conf“. За повече информация прегледайте -„journald.conf(5)“ от ръководството. - --- e9bf28e6e834481bb6f48f548ad13606 -Subject: Пропуснати журнални съобщения -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Някои от съобщенията на ядрото може и да са пропуснати, защото системата не -смогваше да ги обработи достатъчно бързо. - --- fc2e22bc6ee647b6b90729ab34a250b1 -Subject: Процес № @COREDUMP_PID@ (@COREDUMP_COMM@) запази освободената памет -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:core(5) - -Процес № @COREDUMP_PID@ (@COREDUMP_COMM@) заби, представянето му в паметта -бе запазено. - -Най-често това се дължи на грешка в забилата програма и следва да я -докладвате на създателите на програмата. - --- 8d45620c1a4348dbb17410da57c60c66 -Subject: Създадена е нова сесия № @SESSION_ID@ за потребителя „@USER_ID@“ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -За потребителя „@USER_ID@“ е създадена нова сесия № @SESSION_ID@. - -Водещият процес на сесията е: @LEADER@ - --- 3354939424b4456d9802ca8333ed424a -Subject: Сесия № @SESSION_ID@ приключи -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Сесия № @SESSION_ID@ приключи работа. - --- fcbefc5da23d428093f97c82a9290f7b -Subject: Налично е ново работно място № @SEAT_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Новото работно място № @SEAT_ID@ е настроено и готово за работа. - --- e7852bfe46784ed0accde04bc864c2d5 -Subject: Работното място № @SEAT_ID@ е премахнато -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Работното място № @SEAT_ID@ вече не е налично. - --- c7a787079b354eaaa9e77b371893cd27 -Subject: Смяна на системното време -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Часовникът на системата е сверен да сочи @REALTIME@ микросекунди след -1 януари 1970. - --- 45f82f4aef7a4bbf942ce861d1f20990 -Subject: Смяна на часовия пояс да е „@TIMEZONE@“ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Часовият пояс на системата е сменен на „@TIMEZONE@“. - --- b07a249cd024414a82dd00cd181378ff -Subject: Стартирането на системата завърши -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Успешно са стартирали всички услуги, които са посочени за задействане при -стартиране на системата. Това не означава, че системата бездейства, защото -някои от услугите може да извършват специфични действия при стартиране. - -Стартирането на ядрото отне @KERNEL_USEC@ микросекунди. - -Стартирането на RAM диска за първоначално зареждане отне @INITRD_USEC@ -микросекунди. - -Стартирането на потребителските програми отне @USERSPACE_USEC@ микросекунди. - --- 6bbd95ee977941e497c48be27c254128 -Subject: Системата е приспана на ниво „@SLEEP@“ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Системата премина в състояние на приспиване „@SLEEP@“. - --- 8811e6df2a8e40f58a94cea26f8ebf14 -Subject: Системата се събуди след приспиване на ниво„@SLEEP@“ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Системата се събуди от състояние на приспиване „@SLEEP@“. - --- 98268866d1d54a499c4e98921d93bc40 -Subject: Започна процедура на спиране на системата -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Започна процедурата на Systemd за спиране на системата. Всички процеси и -услуги се спират, всички файлови системи се демонтират. - --- 7d4958e842da4a758f6c1cdc7b36dcc5 -Subject: Модул „@UNIT@“ се стартира -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Модулът „@UNIT@“ се стартира в момента - --- 39f53479d3a045ac8e11786248231fbf -Subject: Модул „@UNIT@“ вече е стартиран -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Стартирането на модул „@UNIT@“ завърши. - -Резултатът е: @RESULT@ - --- de5b426a63be47a7b6ac3eaac82e2f6f -Subject: Модул „@UNIT@“ се спира -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Модулът „@UNIT@“ се спира в момента. - --- 9d1aaa27d60140bd96365438aad20286 -Subject: Модул „@UNIT@“ вече е спрян -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Спирането на модул „@UNIT@“ завърши. - --- be02cf6855d2428ba40df7e9d022f03d -Subject: Модулът „@UNIT@“ не успя да стартира -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Модулът „@UNIT@“ не успя да стартира. - -Резултатът е: @RESULT@ - --- d34d037fff1847e6ae669a370e694725 -Subject: Модулът „@UNIT@“ започна презареждане на настройките си -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Модулът „@UNIT@“ започна презареждане на настройките си. - --- 7b05ebc668384222baa8881179cfda54 -Subject: Модулът „@UNIT@“ завърши презареждането на настройките си -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Модулът „@UNIT@“ завърши презареждането на настройките си. - -Резултатът e: @RESULT@ - --- 641257651c1b4ec9a8624d7a40a9e1e7 -Subject: Програмата „@EXECUTABLE@“ не успя да се стартира -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Програмата „@EXECUTABLE@“ не успя да се стартира. - -Върнатият номер на грешка е: @ERRNO@ - --- 0027229ca0644181a76c4e92458afa2e -Subject: Поне едно съобщение не бе препратено към syslog -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Поне едно съобщение не бе препратено към журналната услуга syslog, която -работи успоредно с journald. - -Най-често това указва, че тази реализация на syslog не може да поеме текущия -обем съобщения. - --- 1dee0369c7fc4736b7099b38ecb46ee7 -Subject: Точката за монтиране не е празна -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Директорията „@WHERE@“ не е празна. - -Тя е указана като точка за монтиране — или като второ поле във файла -„/etc/fstab“, или чрез директивата „Where=“ в някой от файловете за -модул на Systemd. - -Това не пречи на самото монтиране, но вече съществуващите там файлове и -директории няма да се виждат повече, освен ако ръчно не монтирате тази -непразна директория някъде другаде. - --- 24d8d4452573402496068381a6312df2 -Subject: Стартирана е виртуална машина или контейнер -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Виртуалната машина „@NAME@“ с идентификатор на водещия процес @LEADER@ -е стартирана и готова за работа. - --- 58432bd3bace477cb514b56381b8a758 -Subject: Спряна е виртуална машина или контейнер -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Виртуалната машина „@NAME@“ с идентификатор на водещия процес @LEADER@ -е спряна. - --- 36db2dfa5a9045e1bd4af5f93e1cf057 -Subject: Режимът DNSSEC е изключен, защото сървърът не го поддържа -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:systemd-resolved.service(8) resolved.conf(5) - -Локалната услуга за имена (systemd-resolved.service) установи, че -настроения сървър за DNS не поддържа DNSSEC, затова този режим е изключен. - -Това се случва, когато директивата „DNSSEC=allow-downgrade“ е включена във -файла „resolved.conf“ и зададеният сървър за DNS не е съвместим с DNSSEC. - -Внимавайте, защото това може да позволи атака, при която трета страна ви -връща отговори, които да предизвикат понижаването на сигурността от DNSSEC -до DNS. - -Такова събитие означава, че или сървърът за DNS не е съвместим с DNSSEC, -или някой успешно ви е атакувал за понижаване на сигурността на имената. - --- 1675d7f172174098b1108bf8c7dc8f5d -Subject: Неуспешна проверка на DNSSEC -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:systemd-resolved.service(8) - -Заявка или запис в DNS не издържа проверка с DNSSEC. - -Това обикновено показва вмешателство на трета страна в канала ви за връзка. - --- 4d4408cfd0d144859184d1e65d7c8a65 -Subject: Анулирана доверена котва в DNSSEC -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:systemd-resolved.service(8) - -Анулирана е доверена котва за DNSSEC и трябва да настроите нова. - -Понякога новата идва с обновяване на системата. diff --git a/catalog/systemd.catalog b/catalog/systemd.catalog deleted file mode 100644 index 90929bca6d..0000000000 --- a/catalog/systemd.catalog +++ /dev/null @@ -1,334 +0,0 @@ -# This file is part of systemd. -# -# Copyright 2012 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 . - -# Message catalog for systemd's own messages - -# The catalog format is documented on -# http://www.freedesktop.org/wiki/Software/systemd/catalog - -# For an explanation why we do all this, see https://xkcd.com/1024/ - --- f77379a8490b408bbe5f6940505a777b -Subject: The journal has been started -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -The system journal process has started up, opened the journal -files for writing and is now ready to process requests. - --- d93fb3c9c24d451a97cea615ce59c00b -Subject: The journal has been stopped -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -The system journal process has shut down and closed all currently -active journal files. - --- ec387f577b844b8fa948f33cad9a75e6 -Subject: Disk space used by the journal -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@JOURNAL_NAME@ (@JOURNAL_PATH@) is currently using @CURRENT_USE_PRETTY@. -Maximum allowed usage is set to @MAX_USE_PRETTY@. -Leaving at least @DISK_KEEP_FREE_PRETTY@ free (of currently available @DISK_AVAILABLE_PRETTY@ of disk space). -Enforced usage limit is thus @LIMIT_PRETTY@, of which @AVAILABLE_PRETTY@ are still available. - -The limits controlling how much disk space is used by the journal may -be configured with SystemMaxUse=, SystemKeepFree=, SystemMaxFileSize=, -RuntimeMaxUse=, RuntimeKeepFree=, RuntimeMaxFileSize= settings in -/etc/systemd/journald.conf. See journald.conf(5) for details. - --- a596d6fe7bfa4994828e72309e95d61e -Subject: Messages from a service have been suppressed -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:journald.conf(5) - -A service has logged too many messages within a time period. Messages -from the service have been dropped. - -Note that only messages from the service in question have been -dropped, other services' messages are unaffected. - -The limits controlling when messages are dropped may be configured -with RateLimitIntervalSec= and RateLimitBurst= in -/etc/systemd/journald.conf. See journald.conf(5) for details. - --- e9bf28e6e834481bb6f48f548ad13606 -Subject: Journal messages have been missed -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Kernel messages have been lost as the journal system has been unable -to process them quickly enough. - --- fc2e22bc6ee647b6b90729ab34a250b1 -Subject: Process @COREDUMP_PID@ (@COREDUMP_COMM@) dumped core -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:core(5) - -Process @COREDUMP_PID@ (@COREDUMP_COMM@) crashed and dumped core. - -This usually indicates a programming error in the crashing program and -should be reported to its vendor as a bug. - --- fc2e22bc6ee647b6b90729ab34a250b1 de -Subject: Speicherabbild für Prozess @COREDUMP_PID@ (@COREDUMP_COMM) generiert -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:core(5) - -Prozess @COREDUMP_PID@ (@COREDUMP_COMM@) ist abgebrochen worden und -ein Speicherabbild wurde generiert. - -Üblicherweise ist dies ein Hinweis auf einen Programmfehler und sollte -als Fehler dem jeweiligen Hersteller gemeldet werden. - --- 8d45620c1a4348dbb17410da57c60c66 -Subject: A new session @SESSION_ID@ has been created for user @USER_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -A new session with the ID @SESSION_ID@ has been created for the user @USER_ID@. - -The leading process of the session is @LEADER@. - --- 3354939424b4456d9802ca8333ed424a -Subject: Session @SESSION_ID@ has been terminated -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -A session with the ID @SESSION_ID@ has been terminated. - --- fcbefc5da23d428093f97c82a9290f7b -Subject: A new seat @SEAT_ID@ is now available -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -A new seat @SEAT_ID@ has been configured and is now available. - --- e7852bfe46784ed0accde04bc864c2d5 -Subject: Seat @SEAT_ID@ has now been removed -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -A seat @SEAT_ID@ has been removed and is no longer available. - --- c7a787079b354eaaa9e77b371893cd27 -Subject: Time change -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -The system clock has been changed to @REALTIME@ microseconds after January 1st, 1970. - --- c7a787079b354eaaa9e77b371893cd27 de -Subject: Zeitänderung -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Die System-Zeit wurde geändert auf @REALTIME@ Mikrosekunden nach dem 1. Januar 1970. - --- 45f82f4aef7a4bbf942ce861d1f20990 -Subject: Time zone change to @TIMEZONE@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -The system timezone has been changed to @TIMEZONE@. - --- b07a249cd024414a82dd00cd181378ff -Subject: System start-up is now complete -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -All system services necessary queued for starting at boot have been -successfully started. Note that this does not mean that the machine is -now idle as services might still be busy with completing start-up. - -Kernel start-up required @KERNEL_USEC@ microseconds. - -Initial RAM disk start-up required @INITRD_USEC@ microseconds. - -Userspace start-up required @USERSPACE_USEC@ microseconds. - --- 6bbd95ee977941e497c48be27c254128 -Subject: System sleep state @SLEEP@ entered -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -The system has now entered the @SLEEP@ sleep state. - --- 8811e6df2a8e40f58a94cea26f8ebf14 -Subject: System sleep state @SLEEP@ left -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -The system has now left the @SLEEP@ sleep state. - --- 98268866d1d54a499c4e98921d93bc40 -Subject: System shutdown initiated -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Systemd shutdown has been initiated. The shutdown has now begun and -all system services are terminated and all file systems unmounted. - --- 7d4958e842da4a758f6c1cdc7b36dcc5 -Subject: Unit @UNIT@ has begun start-up -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Unit @UNIT@ has begun starting up. - --- 39f53479d3a045ac8e11786248231fbf -Subject: Unit @UNIT@ has finished start-up -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Unit @UNIT@ has finished starting up. - -The start-up result is @RESULT@. - --- de5b426a63be47a7b6ac3eaac82e2f6f -Subject: Unit @UNIT@ has begun shutting down -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Unit @UNIT@ has begun shutting down. - --- 9d1aaa27d60140bd96365438aad20286 -Subject: Unit @UNIT@ has finished shutting down -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Unit @UNIT@ has finished shutting down. - --- be02cf6855d2428ba40df7e9d022f03d -Subject: Unit @UNIT@ has failed -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Unit @UNIT@ has failed. - -The result is @RESULT@. - --- d34d037fff1847e6ae669a370e694725 -Subject: Unit @UNIT@ has begun reloading its configuration -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Unit @UNIT@ has begun reloading its configuration - --- 7b05ebc668384222baa8881179cfda54 -Subject: Unit @UNIT@ has finished reloading its configuration -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Unit @UNIT@ has finished reloading its configuration - -The result is @RESULT@. - --- 641257651c1b4ec9a8624d7a40a9e1e7 -Subject: Process @EXECUTABLE@ could not be executed -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -The process @EXECUTABLE@ could not be executed and failed. - -The error number returned by this process is @ERRNO@. - --- 0027229ca0644181a76c4e92458afa2e -Subject: One or more messages could not be forwarded to syslog -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -One or more messages could not be forwarded to the syslog service -running side-by-side with journald. This usually indicates that the -syslog implementation has not been able to keep up with the speed of -messages queued. - --- 1dee0369c7fc4736b7099b38ecb46ee7 -Subject: Mount point is not empty -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -The directory @WHERE@ is specified as the mount point (second field in -/etc/fstab or Where= field in systemd unit file) and is not empty. -This does not interfere with mounting, but the pre-exisiting files in -this directory become inaccessible. To see those over-mounted files, -please manually mount the underlying file system to a secondary -location. - --- 24d8d4452573402496068381a6312df2 -Subject: A virtual machine or container has been started -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -The virtual machine @NAME@ with its leader PID @LEADER@ has been -started is now ready to use. - --- 58432bd3bace477cb514b56381b8a758 -Subject: A virtual machine or container has been terminated -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -The virtual machine @NAME@ with its leader PID @LEADER@ has been -shut down. - --- 36db2dfa5a9045e1bd4af5f93e1cf057 -Subject: DNSSEC mode has been turned off, as server doesn't support it -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:systemd-resolved.service(8) resolved.conf(5) - -The resolver service (systemd-resolved.service) has detected that the -configured DNS server does not support DNSSEC, and DNSSEC validation has been -turned off as result. - -This event will take place if DNSSEC=allow-downgrade is configured in -resolved.conf and the configured DNS server is incompatible with DNSSEC. Note -that using this mode permits DNSSEC downgrade attacks, as an attacker might be -able turn off DNSSEC validation on the system by inserting DNS replies in the -communication channel that result in a downgrade like this. - -This event might be indication that the DNS server is indeed incompatible with -DNSSEC or that an attacker has successfully managed to stage such a downgrade -attack. - --- 1675d7f172174098b1108bf8c7dc8f5d -Subject: DNSSEC validation failed -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:systemd-resolved.service(8) - -A DNS query or resource record set failed DNSSEC validation. This is usually -indication that the communication channel used was tampered with. - --- 4d4408cfd0d144859184d1e65d7c8a65 -Subject: A DNSSEC trust anchor has been revoked -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:systemd-resolved.service(8) - -A DNSSEC trust anchor has been revoked. A new trust anchor has to be -configured, or the operating system needs to be updated, to provide an updated -DNSSEC trust anchor. diff --git a/catalog/systemd.da.catalog b/catalog/systemd.da.catalog deleted file mode 100644 index 093e8139da..0000000000 --- a/catalog/systemd.da.catalog +++ /dev/null @@ -1,261 +0,0 @@ -# This file is part of systemd. -# -# Copyright 2012 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 . - -# Message catalog for systemd's own messages -# Danish translation - -# The catalog format is documented on -# http://www.freedesktop.org/wiki/Software/systemd/catalog - -# For an explanation why we do all this, see https://xkcd.com/1024/ - --- f77379a8490b408bbe5f6940505a777b -Subject: Journalen er blevet startet -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -System-journal processen har startet op, åbnet journal filerne for -tilskrivning og er nu klar til at modtage anmodninger. - --- d93fb3c9c24d451a97cea615ce59c00b -Subject: Journalen er blevet stoppet -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -System-journal processen er stoppet og har lukket alle aktive journal -filer. - --- a596d6fe7bfa4994828e72309e95d61e -Subject: Beskeder fra en service er blevet undertrykt -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:journald.conf(5) - -En service har logget for mange beskeder inden for en given tidsperiode. -Beskeder fra omtalte service er blevet smidt væk. - -Kun beskeder fra omtalte service er smidt væk. Beskeder fra andre -services er ikke påvirket. - -Grænsen for hvornår beskeder bliver smidt væk kan konfigureres -med RateLimitIntervalSec= og RateLimitBurst= i -/etc/systemd/journald.conf. Se journald.conf(5) for detaljer herom. - --- e9bf28e6e834481bb6f48f548ad13606 -Subject: Journal beskeder er gået tabt -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Kernel beskeder er gået tabt da journal systemet ikke har været i stand -til at håndtere dem hurtigt nok. - --- fc2e22bc6ee647b6b90729ab34a250b1 -Subject: Fejl-fil genereret for process @COREDUMP_PID@ (@COREDUMP_COMM@) -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:core(5) - -Process @COREDUMP_PID@ (@COREDUMP_COMM@) har lukket ned og genereret en -fejl-fil. - -Dette indikerer som regel en programmeringsfejl i det nedlukkede program -og burde blive reporteret som en bug til folkene bag - --- 8d45620c1a4348dbb17410da57c60c66 -Subject: En ny session @SESSION_ID@ er blevet lavet for bruger @USER_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -En ny session med ID @SESSION_ID@ er blevet lavet for brugeren @USER_ID@. - -Den ledende process for sessionen er @LEADER@. - --- 3354939424b4456d9802ca8333ed424a -Subject: Session @SESSION_ID@ er blevet lukket ned -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -En session med ID @SESSION_ID@ er blevet lukket ned. - --- fcbefc5da23d428093f97c82a9290f7b -Subject: En ny arbejdsstation $SEAT_ID@ er nu tilgængelig -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -En ny arbejdsstation @SEAT_ID@ er blevet konfigureret og er nu tilgængelig. - --- e7852bfe46784ed0accde04bc864c2d5 -Subject: Arbejdsstation @SEAT_ID@ er nu blevet fjernet -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -En arbejdsstation @SEAT_ID@ er blevet fjernet og er ikke længere tilgængelig. - --- c7a787079b354eaaa9e77b371893cd27 -Subject: Tidsændring -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Systemtiden er blevet ændret til @REALTIME@ mikrosekunder efter d. 1. Januar 1970. - --- 45f82f4aef7a4bbf942ce861d1f20990 -Subject: Tidszoneændring til @TIMEZONE@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Tidszonen for systemet er blevet ændret til @TIMEZONE@. - --- b07a249cd024414a82dd00cd181378ff -Subject: Opstart af systemet er nu fuldført -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Alle system services i kø til at køre ved opstart, er blevet startet -med success. Bemærk at dette ikke betyder at maskinen er i dvale, da -services stadig kan være i gang med at færdiggøre deres opstart. - -Opstart af kernel tog @KERNEL_USEC@ mikrosekunder. - -Opstart af initrd tog @INITRD_USEC@ mikrosekunder. - -Opstart af userspace tog @USERSPACE_USEC@ mikrosekunder. - --- 6bbd95ee977941e497c48be27c254128 -Subject: System slumretilstand @SLEEP@ trådt i kraft -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -System er nu gået i @SLEEP@ slumretilstand. - --- 8811e6df2a8e40f58a94cea26f8ebf14 -Subject: System slumretilstand @SLEEP@ forladt -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Systemet har nu forladt @SLEEP@ slumretilstand. - --- 98268866d1d54a499c4e98921d93bc40 -Subject: Systemnedlukning påbegyndt -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Systemnedlukning er blevet påbegyndt. Nedlukningen er nu begyndt og -alle system services er blevet afbrudt og alle filsystemer afmonteret. - --- 7d4958e842da4a758f6c1cdc7b36dcc5 -Subject: Enhed @UNIT@ har påbegyndt opstart -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Enhed @UNIT@ er begyndt at starte op. - --- 39f53479d3a045ac8e11786248231fbf -Subject: Enhed @UNIT har færdiggjort opstart -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Enhed @UNIT@ er færdig med at starte op. - -Resultat for opstart er @RESULT@. - --- de5b426a63be47a7b6ac3eaac82e2f6f -Subject: Enhed @UNIT@ har påbegyndt nedlukning -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Enhed @UNIT@ har påbegyndt nedlukning. - --- 9d1aaa27d60140bd96365438aad20286 -Subject: Enhed @UNIT@ har færdiggjort nedlukning -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Enhed @UNIT@ har færdiggjort nedlukning. - --- be02cf6855d2428ba40df7e9d022f03d -Subject: Enhed @UNIT@ har fejlet -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Enhed @UNIT@ har fejlet. - -Resultatet er @RESULT@ - --- d34d037fff1847e6ae669a370e694725 -Subject: Enhed @UNIT@ har påbegyndt genindlæsning af sin konfiguration -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Enhed @UNIT@ er begyndt at genindlæse sin konfiguration - --- 7b05ebc668384222baa8881179cfda54 -Subject: Enhed @UNIT@ har færdiggjort genindlæsning af sin konfiguration -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Enhed @UNIT@ er færdig med at genindlæse sin konfiguration - -Resultatet er: @RESULT@. - --- 641257651c1b4ec9a8624d7a40a9e1e7 -Subject: Process @EXECUTABLE@ kunne ikke eksekveres -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Processen @EXECUTABLE@ kunne ikke eksekveres og fejlede. - -Processens returnerede fejlkode er @ERRNO@. - --- 0027229ca0644181a76c4e92458afa2e -Subject: Èn eller flere beskeder kunne ikke videresendes til syslog -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Èn eller flere beskeder kunne ikke videresendes til syslog servicen -der kører side-om-side med journald. Dette indikerer typisk at syslog -implementationen ikke har kunnet følge med mængden af ventende beskeder. - --- 1dee0369c7fc4736b7099b38ecb46ee7 -Subject: Monteringspunkt er ikke tomt -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Folderen @WHERE@ er specificeret som monteringspunkt (andet felt i -/etc/fstab eller Where= feltet i systemd enhedsfil) men er ikke tom. -Dette forstyrrer ikke monteringen, men de pre-eksisterende filer i folderen -bliver utilgængelige. For at se de over-monterede filer; montér det -underlæggende filsystem til en anden lokation. - --- 24d8d4452573402496068381a6312df2 -Subject: En virtuel maskine eller container er blevet startet -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Den virtuelle maskine @NAME@ med dens leder PID @LEADER@ er blevet -startet og er klar til brug. - --- 58432bd3bace477cb514b56381b8a758 -Subject: En virtuel maskine eller container er blevet afbrudt -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Den virtuelle maskine @NAME@ med dens leder PID @LEADER@ er blevet -nedlukket. diff --git a/catalog/systemd.fr.catalog b/catalog/systemd.fr.catalog deleted file mode 100644 index 0cea629c31..0000000000 --- a/catalog/systemd.fr.catalog +++ /dev/null @@ -1,320 +0,0 @@ -# This file is part of systemd. -# -# Copyright 2012 Lennart Poettering -# Copyright 2013-2015 Sylvain Plantefève -# -# 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 . - -# Message catalog for systemd's own messages -# French translation - -# Le format du catalogue de messages est décrit (en anglais) içi : -# http://www.freedesktop.org/wiki/Software/systemd/catalog - --- f77379a8490b408bbe5f6940505a777b -Subject: Le journal a été démarré -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Le processus du journal système a démarré, ouvert ses fichiers en écriture -et est prêt à traiter les requêtes. - --- d93fb3c9c24d451a97cea615ce59c00b -Subject: Le journal a été arrêté -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Le processus du journal système a été arrêté et tous ses fichiers actifs -ont été fermés. - --- ec387f577b844b8fa948f33cad9a75e6 -Subject: Espace disque utilisé par le journal -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:journald.conf(5) - -@JOURNAL_NAME@ (@JOURNAL_PATH@) utilise actuellement @CURRENT_USE_PRETTY@. -Le maximum autorisé est défini à @MAX_USE_PRETTY@. -Au moins @DISK_KEEP_FREE_PRETTY@ doivent être laissés libres -(sur @DISK_AVAILABLE_PRETTY@ d'espace disque actuellement libre). -La limite appliquée est donc @LIMIT_PRETTY@, dont @AVAILABLE_PRETTY@ -sont toujours disponibles. - -Les limites définissant la quantité d'espace disque que peut utiliser le -journal peuvent être configurées avec les paramètres SystemMaxUse=, -SystemKeepFree=, SystemMaxFileSize=, RuntimeMaxUse=, RuntimeKeepFree=, -RuntimeMaxFileSize= dans le fichier /etc/systemd/journald.conf. -Voir journald.conf(5) pour plus de détails. - --- a596d6fe7bfa4994828e72309e95d61e -Subject: Des messages d'un service ont été supprimés -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:journald.conf(5) - -Un service a essayé d'enregistrer un trop grand nombre de messages sur un -intervalle de temps donné. Des messages de ce service ont été évincés. - -Notez que seuls des messages de ce service ont été évincés, les messages des -autres services ne sont pas affectés. - -Les limites définissant ce comportement peuvent être configurées avec les -paramètres RateLimitIntervalSec= et RateLimitBurst= dans le fichier -/etc/systemd/journald.conf. Voir journald.conf(5) pour plus de détails. - --- e9bf28e6e834481bb6f48f548ad13606 -Subject: Des messages du journal ont été manqués -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Des messages du noyau ont été manqués car le journal système n'a pas été -capable de les traiter suffisamment vite. - --- fc2e22bc6ee647b6b90729ab34a250b1 -Subject: Le processus @COREDUMP_PID@ (@COREDUMP_COMM@) a généré un fichier « core » -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:core(5) - -Le processus @COREDUMP_PID@ (@COREDUMP_COMM@) a planté et généré un fichier « core ». - -Cela indique généralement une erreur de programmation dans le programme -incriminé, et cela devrait être notifié à son concepteur comme un défaut (bug). - --- 8d45620c1a4348dbb17410da57c60c66 -Subject: Une nouvelle session @SESSION_ID@ a été créée pour l'utilisateur @USER_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Une nouvelle session a été créée pour l'utilisateur @USER_ID@ avec -l'identifiant (ID) @SESSION_ID@. - -Le processus maître de la session est @LEADER@. - --- 3354939424b4456d9802ca8333ed424a -Subject: La session @SESSION_ID@ s'est terminée -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -La session d'identifiant (ID) @SESSION_ID@ s'est terminée. - --- fcbefc5da23d428093f97c82a9290f7b -Subject: Un nouveau poste (seat) @SEAT_ID@ est disponible -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Un nouveau poste (seat) @SEAT_ID@ a été configuré et est maintenant -disponible. - --- e7852bfe46784ed0accde04bc864c2d5 -Subject: Le poste (seat) @SEAT_ID@ a été retiré -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Le poste (seat) @SEAT_ID@ a été retiré et n'est plus disponible. - --- c7a787079b354eaaa9e77b371893cd27 -Subject: Changement d'heure -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -L'horloge système a été modifiée et positionnée à @REALTIME@ microsecondes -après le 1er janvier 1970. - --- 45f82f4aef7a4bbf942ce861d1f20990 -Subject: Fuseau horaire modifié en @TIMEZONE@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Le fuseau horaire du système a été modifié et positionné à @TIMEZONE@. - --- b07a249cd024414a82dd00cd181378ff -Subject: Le démarrage du système est terminé -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Tous les services nécessaires au démarrage du système ont été lancés avec -succès. Notez que cela ne signifie pas que le système est maintenant au -repos, car des services peuvent encore être en train de terminer leur -démarrage. - -Le chargement du noyau a nécessité @KERNEL_USEC@ microsecondes. - -Le chargement du « RAM disk » initial a nécessité @INITRD_USEC@ microsecondes. - -Le chargement de l'espace utilisateur a nécessité @USERSPACE_USEC@ microsecondes. - --- 6bbd95ee977941e497c48be27c254128 -Subject: Le système entre dans l'état de repos (sleep state) @SLEEP@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Le système est maintenant à l'état de repos (sleep state) @SLEEP@. - --- 8811e6df2a8e40f58a94cea26f8ebf14 -Subject: Le système sorti de l'état de repos (sleep state) @SLEEP@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Le système est maintenant sorti de l'état de repos (sleep state) @SLEEP@. - --- 98268866d1d54a499c4e98921d93bc40 -Subject: Arrêt du système amorcé -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -L'arrêt du système a été amorcé. L'arrêt a maintenant commencé, tous les -services du système sont terminés et tous les systèmes de fichiers sont -démontés. - --- 7d4958e842da4a758f6c1cdc7b36dcc5 -Subject: L'unité (unit) @UNIT@ a commencé à démarrer -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -L'unité (unit) @UNIT@ a commencé à démarrer. - --- 39f53479d3a045ac8e11786248231fbf -Subject: L'unité (unit) @UNIT@ a terminé son démarrage -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -L'unité (unit) @UNIT@ a terminé son démarrage, avec le résultat @RESULT@. - --- de5b426a63be47a7b6ac3eaac82e2f6f -Subject: L'unité (unit) @UNIT@ a commencé à s'arrêter -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -L'unité (unit) @UNIT@ a commencé à s'arrêter. - --- 9d1aaa27d60140bd96365438aad20286 -Subject: L'unité (unit) @UNIT@ a terminé son arrêt -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -L'unité (unit) @UNIT@ a terminé son arrêt. - --- be02cf6855d2428ba40df7e9d022f03d -Subject: L'unité (unit) @UNIT@ a échoué -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -L'unité (unit) @UNIT@ a échoué, avec le résultat @RESULT@. - --- d34d037fff1847e6ae669a370e694725 -Subject: L'unité (unit) @UNIT@ a commencé à recharger sa configuration -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -L'unité (unit) @UNIT@ a commencé à recharger sa configuration. - --- 7b05ebc668384222baa8881179cfda54 -Subject: L'unité (unit) @UNIT@ a terminé de recharger configuration -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -L'unité (unit) @UNIT@ a terminé de recharger configuration, -avec le résultat @RESULT@. - --- 641257651c1b4ec9a8624d7a40a9e1e7 -Subject: Le processus @EXECUTABLE@ n'a pas pu être exécuté -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Le processus @EXECUTABLE@ n'a pas pu être exécuté, et a donc échoué. - -Le code d'erreur renvoyé est @ERRNO@. - --- 0027229ca0644181a76c4e92458afa2e -Subject: Un ou plusieurs messages n'ont pas pu être transmis à syslog -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Un ou plusieurs messages n'ont pas pu être transmis au service syslog -s'exécutant conjointement avec journald. Cela indique généralement que -l'implémentation de syslog utilisée n'a pas été capable de suivre -la cadence du flux de messages. - --- 1dee0369c7fc4736b7099b38ecb46ee7 -Subject: Le point de montage n'est pas vide -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Le répertoire @WHERE@ est spécifié comme point de montage (second champ du -fichier /etc/fstab, ou champ Where= dans une unité (unit) systemd) et n'est -pas vide. -Cela ne perturbe pas le montage du système de fichiers, mais les fichiers -préalablement présents dans ce répertoire sont devenus inaccessibles. -Pour atteindre ces fichiers, veuillez monter manuellement le système de -fichiers sous-jacent à un autre emplacement. - --- 24d8d4452573402496068381a6312df2 -Subject: Une machine virtuelle ou un conteneur (container) a été démarré -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -La machine virtuelle @NAME@ a été démarrée avec le PID maître @LEADER@, -et est maintenant prête à l'emploi. - --- 58432bd3bace477cb514b56381b8a758 -Subject: Une machine virtuelle ou un conteneur (container) a été arrêté -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -La machine virtuelle @NAME@ avec le PID maître @LEADER@ a été arrêtée. - --- 36db2dfa5a9045e1bd4af5f93e1cf057 -Subject: Le mode DNSSEC a été désactivé, car il n'est pas supporté par le serveur -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:systemd-resolved.service(8) resolved.conf(5) - -Le service de résolution (systemd-resolved.service) a détecté que le serveur -DNS configuré ne supporte pas DNSSEC, et la validation DNSSEC a donc été -désactivée. - -Cet évènement se produit si DNSSEC=allow-downgrade est configuré dans -resolved.conf et que le serveur DNS configuré n'est pas compatible avec -DNSSEC. -Veuillez noter que ce mode permet des attaques de rétrogradation DNSSEC, -car un attaquant peut être capable de désactiver la validation DNSSEC sur -le système en injectant des réponses DNS dans le canal de communication. - -Cet évènement indique que le serveur DNS est effectivement incompatible avec -DNSSEC, ou qu'un attaquant a peut-être conduit une telle attaque avec succès. - --- 1675d7f172174098b1108bf8c7dc8f5d -Subject: La validation DNSSEC a échoué -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:systemd-resolved.service(8) - -Une requête ou une ressource DNS n'a pas passé la validation DNSSEC. -Ceci est généralement une indication que le canal de communication a été -altéré. - --- 4d4408cfd0d144859184d1e65d7c8a65 -Subject: Une ancre de confiance DNSSEC a été révoquée -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:systemd-resolved.service(8) - -Une ancre de confiance DNSSEC a été révoquée. Une nouvelle ancre de -confiance doit être configurée, ou le système d'exploitation a besoin -d'être mis à jour, pour fournir une version à jour de l'ancre de confiance. diff --git a/catalog/systemd.hr.catalog b/catalog/systemd.hr.catalog deleted file mode 100644 index 350988dd87..0000000000 --- a/catalog/systemd.hr.catalog +++ /dev/null @@ -1,314 +0,0 @@ -# This file is part of systemd. -# -# Copyright 2012 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 . - -# Message catalog for systemd's own messages -# Croatian translation - -# Format kataloga je dokumentiran na -# http://www.freedesktop.org/wiki/Software/systemd/catalog - -# Za pojašnjenje zašto ovo radimo, posjetite https://xkcd.com/1024/ - --- f77379a8490b408bbe5f6940505a777b -Subject: journal je pokrenut -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Journal proces sustava se pokrenuo, otvorio je journal - datoteke za upis i spreman je za obradu zahtjeva. - --- d93fb3c9c24d451a97cea615ce59c00b -Subject: journal je zaustavljen -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Journal proces sustava je isključio i zatvorio sve trenutno -aktivne journal datoteke. - --- ec387f577b844b8fa948f33cad9a75e6 -Subject: Diskovni prostor koji koristi journal -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@JOURNAL_NAME@ (@JOURNAL_PATH@) trenutno koristi @CURRENT_USE_PRETTY@. -Najveća dopuštena upotreba je postavljena na @MAX_USE_PRETTY@. -Ostavljam najmanje @DISK_KEEP_FREE_PRETTY@ slobodno (trenutno dostupno @DISK_AVAILABLE_PRETTY@ diskovnog prostora). -Prisilno ograničenje upotrebe je @LIMIT_PRETTY@, od kojeg je @AVAILABLE_PRETTY@ još dostupno. - -Ograničenja kontroliraju koliko diskovnog prostora koristi journal mogu -se podesiti sa SystemMaxUse=, SystemKeepFree=, SystemMaxFileSize=, -RuntimeMaxUse=, RuntimeKeepFree=, RuntimeMaxFileSize= settings u -/etc/systemd/journald.conf. Pogledajte journald.conf(5) za više pojedinosti. - --- a596d6fe7bfa4994828e72309e95d61e -Subject: Poruka iz usluge je potisnuta -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:journald.conf(5) - -Usluga je prijavila previše poruka u određenom vremenskom razdoblju. Poruke -iz usluge su odbačene. - -Zapamtite da samo poruke iz usluge u upitu su -odbačene, ostale poruke usluga nisu zahvaćene. - -Ograničenja koja kontroliraju kada je poruka odbačena mogu se podesiti -sa RateLimitIntervalSec= i RateLimitBurst= u -/etc/systemd/journald.conf. Pogledajte journald.conf(5) za više pojedinosti. - --- e9bf28e6e834481bb6f48f548ad13606 -Subject: Journal poruka je propuštena -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Kernel poruka je izgubljena zato jer ih journal sustav nije mogao -dovoljno brzo obraditi. - --- fc2e22bc6ee647b6b90729ab34a250b1 -Subject: Proces @COREDUMP_PID@ (@COREDUMP_COMM@) je izbacio jezgru -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:core(5) - -Proces @COREDUMP_PID@ (@COREDUMP_COMM@) se srušio i izbacio jezgru. - -Rušenje programa je uobičajeno uzrokovano greškom u programiranju i -trebalo bi se prijaviti razvijatelju kao greška. - --- 8d45620c1a4348dbb17410da57c60c66 -Subject: Nova sesija @SESSION_ID@ je stvorena za korisnika @USER_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Nova sesija sa ID @SESSION_ID@ je stvorena za korisnika @USER_ID@. - -Glavni proces sesije je @LEADER@. - --- 3354939424b4456d9802ca8333ed424a -Subject: Sesija @SESSION_ID@ je prekinuta -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Sesija sa ID @SESSION_ID@ je prekinuta. - --- fcbefc5da23d428093f97c82a9290f7b -Subject: Novo sjedište @SEAT_ID@ je sada dostupno -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Novo sjedište @SEAT_ID@ je podešeno i sada je dostupno. - --- e7852bfe46784ed0accde04bc864c2d5 -Subject: Sjedište @SEAT_ID@ je sada uklonjeno -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Sjedište @SEAT_ID@ je uklonjeno i više nije dostupno. - --- c7a787079b354eaaa9e77b371893cd27 -Subject: Vrijeme promjene -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Sat sustava je promijenjen na @REALTIME@ microsekundi nakon 1. Siječnja, 1970 godine. - --- 45f82f4aef7a4bbf942ce861d1f20990 -Subject: Vremenska zona je promijenjena u @TIMEZONE@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Vremenska zona je promijenjena u @TIMEZONE@. - --- b07a249cd024414a82dd00cd181378ff -Subject: Pokretanje sustava je sada završeno -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Sve usluge sustava koje su zadane za pokretanje pri pokretanju sustava -su uspješno pokrenute. Zapamtite da ovo ne znači da sada računalo -miruje zato jer se neke usluge još uvijek mogu pokretati. - -Pokretanje kernela zahtijeva @KERNEL_USEC@ mikrosekundi. - -Pokretanje početnog RAM diska zahtijeva @INITRD_USEC@ mikrosekundi. - -Pokretanje prostora korisnika zahtijeva @USERSPACE_USEC@ mikrosekundi. - --- 6bbd95ee977941e497c48be27c254128 -Subject: Pokrenuto je stanje spavanja @SLEEP@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Sustav je sada pokrenuo stanje spavanja @SLEEP@ - --- 8811e6df2a8e40f58a94cea26f8ebf14 -Subject: Završeno je stanje spavanja @SLEEP@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Sustav je sada završio stanje spavanja @SLEEP@ - --- 98268866d1d54a499c4e98921d93bc40 -Subject: Pokrenuto je isključivanje sustava -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Pokrenuto je isključivanje sustava. Isključivanje je sada pokrenuto, -sve usluge sustava su prekinute i svi datotečni sustavi su odmontirani. - --- 7d4958e842da4a758f6c1cdc7b36dcc5 -Subject: Jedinica @UNIT@ je započela pokretanje -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Jedinica @UNIT@ je započela pokretanje. - --- 39f53479d3a045ac8e11786248231fbf -Subject: Jedinica @UNIT@ je završila pokretanje -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Jedinica @UNIT@ je završila pokretanje. - -Rezultat pokretanja je @RESULT@. - --- de5b426a63be47a7b6ac3eaac82e2f6f -Subject: Jedinica @UNIT@ je započela isključivanje -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Jedinica @UNIT@ je započela isključivanje. - --- 9d1aaa27d60140bd96365438aad20286 -Subject: Jedinica @UNIT@ je završila isključivanje -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Jedinica @UNIT@ je završila isključivanje. - --- be02cf6855d2428ba40df7e9d022f03d -Subject: Jedinica @UNIT@ nije uspjela -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Jedinica @UNIT@ nije uspjela. - -Rezultat je @RESULT@. - --- d34d037fff1847e6ae669a370e694725 -Subject: Jedinica @UNIT@ je započela ponovno učitavati podešavanja -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Jedinica @UNIT@ je započela ponovno učitavati podešavanja - --- 7b05ebc668384222baa8881179cfda54 -Subject: Jedinica @UNIT@ je završila ponovno učitavati podešavanja -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Jedinica @UNIT@ je završila ponovno učitavati podešavanja - -Rezultat je @RESULT@. - --- 641257651c1b4ec9a8624d7a40a9e1e7 -Subject: Proces @EXECUTABLE@ se ne može pokrenuti -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Proces @EXECUTABLE@ se ne može pokrenuti i nije uspio. - -Broj greške vraćen ovim procesom je @ERRNO@. - --- 0027229ca0644181a76c4e92458afa2e -Subject: Jedna ili više poruka se ne mogu proslijediti u dnevnik sustava -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Jedna ili više poruka se ne mogu proslijediti u dnevnik sustava, usluge -su pokrenute istovremeno s journalom. Ovo uobičajeno označava da -implementacija dnevnika sustava ne može slijediti brzinu -zahtjeva poruka. - --- 1dee0369c7fc4736b7099b38ecb46ee7 -Subject: Točka montiranja nije prazna -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Direktorij @WHERE@ je određen za točku montiranja (drugi redak u -/etc/fstab ili Where= redak u datoteci systemd jedinice) i nije prazan. -To ne utječe na montiranje, ali postojeće datoteke u ovom direktoriju -postaju nedostupne. Kako bi vidjeli datoteke preko kojih je montirano, -ručno montirajte osnovni datotečni sustav na drugu lokaciju. - --- 24d8d4452573402496068381a6312df2 -Subject: Virtualni stroj ili spremnik su pokrenuti -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Virtualni stroj @NAME@ sa vodećim @LEADER@ PID-om je -pokrenut i spreman je za korištenje. - --- 58432bd3bace477cb514b56381b8a758 -Subject: Virtualni stroj ili spremnik su isključeni -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Virtualni stroj @NAME@ sa vodećim PID-om @LEADER@ je -isključen. - --- 36db2dfa5a9045e1bd4af5f93e1cf057 -Subject: DNSSEC način je isključen, jer ga poslužitelj ne podržava -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:systemd-resolved.service(8) resolved.conf(5) - -Usluga razrješavanja (systemd-resolved.service) je otkrila da -podešeni DNS poslužitelj ne podržava DNSSEC, i DNSSEC, kao rezultat -provjera je isključena. - -Ovaj događaj će zauzeti mjesto ako je DNSSEC=allow-downgrade podešen u -resolved.conf i podešeni DNS poslužitelj je nekompatibilan s DNSSEC. Zapamtite -da korištenje ovog načina dopušta povećanje DNSSEC napada, napadač bi mogao -isključiti DNSSEC provjeru na sustavu umetanjem DNS odgovora u -komunikacijski kanal što rezultira povećanjem napada poput ovog. - -Ovaj događaj bi mogao označavati da je DNS poslužitelj uistinu nekompatibilan s -DNSSEC ili da je napadač uspješno izvršio takav napad. - --- 1675d7f172174098b1108bf8c7dc8f5d -Subject: DNSSEC provjera neuspješna -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:systemd-resolved.service(8) - -DNS zahtjev ili snimak resursa nije prošao DNSSEC provjeru. To uobičajeno -označava da je komunikacijski kanal mijenjan. - --- 4d4408cfd0d144859184d1e65d7c8a65 -Subject: DNSSEC pouzdano sidro je opozvano -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:systemd-resolved.service(8) - -A DNSSEC trust anchor has been revoked. A new trust anchor has to be -configured, or the operating system needs to be updated, to provide an updated -DNSSEC trust anchor. diff --git a/catalog/systemd.hu.catalog b/catalog/systemd.hu.catalog deleted file mode 100644 index 68e8c2572e..0000000000 --- a/catalog/systemd.hu.catalog +++ /dev/null @@ -1,262 +0,0 @@ -# This file is part of systemd. -# -# Copyright 2012 Lennart Poettering -# Copyright 2016 Gabor Kelemen -# -# 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 . - -# Message catalog for systemd's own messages - -# The catalog format is documented on -# http://www.freedesktop.org/wiki/Software/systemd/catalog - -# For an explanation why we do all this, see https://xkcd.com/1024/ - --- f77379a8490b408bbe5f6940505a777b -Subject: A napló elindult -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A rendszernapló folyamat elindult, megnyitotta írásra a naplófájlokat, -és most készen áll kérések feldolgozására. - --- d93fb3c9c24d451a97cea615ce59c00b -Subject: A napló leállt -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A rendszernapló folyamat leállt, és bezárt minden jelenleg aktív naplófájlt. - --- a596d6fe7bfa4994828e72309e95d61e -Subject: Egy szolgáltatás üzenetei elnémítva -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:journald.conf(5) - -Egy szolgáltatás túl sok üzenetet naplózott adott idő alatt. A -szolgáltatástól származó üzenetek eldobásra kerültek. - -Ne feledje, hogy csak a kérdéses szolgáltatás üzenetei kerültek eldobásra, - más szolgáltatások üzeneteit ez nem befolyásolja. - -Az üzenetek eldobását vezérlő korlátok az /etc/systemd/journald.conf -RateLimitIntervalSec= és RateLimitBurst= beállításaival adhatók meg. -Részletekért lásd a journald.conf(5) man oldalt. - --- e9bf28e6e834481bb6f48f548ad13606 -Subject: Naplóüzenetek vesztek el -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Kernelüzenetek vesztek el, mert a naplózó rendszer nem tudta elég gyorsan -feldolgozni azokat. - --- fc2e22bc6ee647b6b90729ab34a250b1 -Subject: Egy folyamat összeomlott: @COREDUMP_PID@ (@COREDUMP_COMM@) -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:core(5) - -Ez a folyamat: @COREDUMP_PID@ (@COREDUMP_COMM@) összeomlott, és core fájlt - írt ki. - -Ez általában programozási hibát jelez az összeomló programban, és -a szállítója felé kell bejelenteni. - --- 8d45620c1a4348dbb17410da57c60c66 -Subject: Új munkamenet (@SESSION_ID@) létrehozva, felhasználója: @USER_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Létrejött egy új munkamenet @SESSION_ID@ azonosítóval ezen felhasználóhoz: -@USER_ID@. - -A munkamenet vezető folyamata: @LEADER@. - --- 3354939424b4456d9802ca8333ed424a -Subject: Munkamenet (@SESSION_ID@) befejezve -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -A következő azonosítójú munkamenet befejeződött: @SESSION_ID@. - --- fcbefc5da23d428093f97c82a9290f7b -Subject: Elérhető egy új munkaállomás: @SEAT_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Beállításra kerül és használható egy új munkaállomás: @SEAT_ID@. - --- e7852bfe46784ed0accde04bc864c2d5 -Subject: A munkaállomás eltávolítva: @SEAT_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -A munkaállomás el lett távolítva, és már nem érhető el: @SEAT_ID@ - --- c7a787079b354eaaa9e77b371893cd27 -Subject: Időmódosítás -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A rendszeróra beállítva @REALTIME@ ezredmásodpercre 1970. január 1. után. - --- 45f82f4aef7a4bbf942ce861d1f20990 -Subject: Időzóna-módosítás erre: @TIMEZONE@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A rendszer időzónája módosítva lett erre: @TIMEZONE@. - --- b07a249cd024414a82dd00cd181378ff -Subject: A rendszer indítása kész -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A rendszerindításkor szükséges indításhoz sorba állított összes -rendszerszolgáltatás elindult. Ne feledje, hogy ez nem jelenti, hogy a -gép üresjáratban van, mivel egyes szolgáltatások még az indítás -befejezésével lehetnek elfoglalva. - -A kernel indítása @KERNEL_USEC@ ezredmásodpercet igényelt. - -A kiinduló RAM lemez indítása @INITRD_USEC@ ezredmásodpercet igényelt. - -A felhasználói programok indítása @USERSPACE_USEC@ ezredmásodpercet igényelt. - --- 6bbd95ee977941e497c48be27c254128 -Subject: A rendszer „@SLEEP@” alvási állapotba lépett -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A rendszer belépett ebbe az alvási állapotba: @SLEEP@. - --- 8811e6df2a8e40f58a94cea26f8ebf14 -Subject: A rendszer „@SLEEP@” alvási állapotból kilépett -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A rendszer kilépett ebből az alvási állapotból: @SLEEP@. - --- 98268866d1d54a499c4e98921d93bc40 -Subject: Rendszer leállítása kezdeményezve -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A systemd leállítása kezdeményezve. A leállítás megkezdődött, minden -rendszerszolgáltatás befejeződik, minden fájlrendszer leválasztásra kerül. - --- 7d4958e842da4a758f6c1cdc7b36dcc5 -Subject: A(z) @UNIT@ egység indítása megkezdődött -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A(z) @UNIT@ egység megkezdte az indulást. - --- 39f53479d3a045ac8e11786248231fbf -Subject: A(z) @UNIT@ egység befejezte az indulást -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A(z) @UNIT@ egység befejezte az indulást - -Az indítás eredménye: @RESULT@. - --- de5b426a63be47a7b6ac3eaac82e2f6f -Subject: A(z) @UNIT@ egység megkezdte a leállást -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A(z) @UNIT@ egység megkezdte a leállást. - --- 9d1aaa27d60140bd96365438aad20286 -Subject: A(z) @UNIT@ egység befejezte a leállást -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A(z) @UNIT@ egység befejezte a leállást. - --- be02cf6855d2428ba40df7e9d022f03d -Subject: A(z) @UNIT@ egység hibát jelzett -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A(z) @UNIT@ egység hibát jelzett. - -Az eredmény: @RESULT@. - --- d34d037fff1847e6ae669a370e694725 -Subject: A(z) @UNIT@ egység megkezdte a beállításainak újratöltését -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A(z) @UNIT@ egység megkezdte a beállításainak újratöltését. - --- 7b05ebc668384222baa8881179cfda54 -Subject: A(z) @UNIT@ egység befejezte a beállításainak újratöltését -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A(z) @UNIT@ egység befejezte a beállításainak újratöltését. - -Az eredmény: @RESULT@. - --- 641257651c1b4ec9a8624d7a40a9e1e7 -Subject: A folyamat végrehajtása sikertelen: @EXECUTABLE@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A folyamat végrehajtása sikertelen volt, és hibát jelzett: @EXECUTABLE@. - -A folyamat által visszaadott hibaszám: @ERRNO@. - --- 0027229ca0644181a76c4e92458afa2e -Subject: Legalább egy üzenet nem továbbítható a rendszernaplónak -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Legalább egy üzenet nem volt továbbítható a journald-vel párhuzamosan futó -syslog szolgáltatásnak. Ez általában azt jelenti, hogy a syslog -megvalósítás nem volt képes lépést tartani a sorba állított -üzenetek sebességével. - --- 1dee0369c7fc4736b7099b38ecb46ee7 -Subject: A csatolási pont nem üres -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A csatolási pontként megadott @WHERE@ könyvtár (második mező az /etc/fstab -fájlban, vagy a Where= sor a systemd egységfájlban) nem üres. Ez nem -akadályozza meg a csatolást, de a könyvtárban már meglévő fájlok -elérhetetlenné válnak. A fájlok láthatóvá tételéhez csatolja -az azokat tartalmazó fájlrendszert egy másodlagos helyre. - --- 24d8d4452573402496068381a6312df2 -Subject: Egy virtuális gép vagy konténer elindult -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A(z) @NAME@ nevű virtuális gép (vezető PID: @LEADER@) elindult, és -használatra kész. - --- 58432bd3bace477cb514b56381b8a758 -Subject: Egy virtuális gép vagy konténer befejeződött -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A(z) @NAME@ nevű virtuális gép (vezető PID: @LEADER@) leállt. diff --git a/catalog/systemd.it.catalog b/catalog/systemd.it.catalog deleted file mode 100644 index b6fca48221..0000000000 --- a/catalog/systemd.it.catalog +++ /dev/null @@ -1,254 +0,0 @@ -# This file is part of systemd. -# -# Copyright 2013 Daniele Medri -# -# 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 . - -# Message catalog for systemd's own messages - --- f77379a8490b408bbe5f6940505a777b -Subject: Il registro è stato avviato -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Il processo relativo al registro di sistema è stato avviato, ha aperto i -file in scrittura ed è ora pronto a gestire richieste. - --- d93fb3c9c24d451a97cea615ce59c00b -Subject: Il registro è stato terminato -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Il processo relativo al registro di sistema è stato terminato e ha chiuso -tutti i file attivi. - --- a596d6fe7bfa4994828e72309e95d61e -Subject: I messaggi di un servizio sono stati soppressi -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:journald.conf(5) - -Un servizio ha registrato troppi messaggi in un dato periodo di tempo. -I messaggi del servizio sono stati eliminati. - -Solo i messaggi del servizio indicato sono stati -eliminati, i messaggi degli altri servizi rimangono invariati. - -I limiti oltre i quali i messaggi si eliminano si configurano -con RateLimitIntervalSec= e RateLimitBurst= in -/etc/systemd/journald.conf. Vedi journald.conf(5) per maggiori informazioni. - --- e9bf28e6e834481bb6f48f548ad13606 -Subject: I messaggi di un servizio sono stati perduti -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -I messaggi del kernel sono stati perduti perché, il registro di sistema -non è stato in grado di gestirli abbastanza velocemente. - --- fc2e22bc6ee647b6b90729ab34a250b1 -Subject: Il processo @COREDUMP_PID@ (@COREDUMP_COMM@) ha generato un dump. -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:core(5) - -Il processo @COREDUMP_PID@ (@COREDUMP_COMM@) si è bloccato generando un dump. - -Questo di solito capita per un errore di programmazione nell'applicazione e -dovrebbe essere segnalato al vendor come un bug. - --- 8d45620c1a4348dbb17410da57c60c66 -Subject: La nuova sessione @SESSION_ID@ è stata creata per l'utente @USER_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Una nuova sessione con ID @SESSION_ID@ è stata creata per l'utente @USER_ID@. - -Il processo primario della sessione è @LEADER@. - --- 3354939424b4456d9802ca8333ed424a -Subject: La sessione @SESSION_ID@ è terminata -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -La sessione con ID @SESSION_ID@ è terminata. - --- fcbefc5da23d428093f97c82a9290f7b -Subject: La nuova postazione @SEAT_ID@ è ora disponibile -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -La nuova postazione @SEAT_ID@ è stata configurata ed è ora disponibile. - --- e7852bfe46784ed0accde04bc864c2d5 -Subject: La postazione @SEAT_ID@ è stata rimossa -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -La postazione @SEAT_ID@ è stata rimossa e non è più disponibile. - --- c7a787079b354eaaa9e77b371893cd27 -Subject: Cambio d'orario -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -L'orologio di sistema è cambiato in @REALTIME@ microsecondi dal 1 gennaio, 1970. - --- 45f82f4aef7a4bbf942ce861d1f20990 -Subject: Il fuso orario è cambiato in @TIMEZONE@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Il fuso orario di sistema è cambiato in @TIMEZONE@. - --- b07a249cd024414a82dd00cd181378ff -Subject: Avvio del sistema completato. -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Tutti i servizi di sistema richiesti per la fase di avvio sono stati eseguiti -con successo. Nota che la macchina potrebbe non essere ancora pronta in quanto -i servizi attivati sono in fase di completamento. - -L'avvio del kernel ha richiesto @KERNEL_USEC@ microsecondi. - -L'avvio del disco RAM ha richiesto @INITRD_USEC@ microsecondi. - -L'avvio dello userspace ha richiesto @USERSPACE_USEC@ microsecondi. - --- 6bbd95ee977941e497c48be27c254128 -Subject: Il sistema è entrato in fase di pausa @SLEEP@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Il sistema è entrato nello stato di pausa @SLEEP@. - --- 8811e6df2a8e40f58a94cea26f8ebf14 -Subject: Il sistema è uscito dalla fase di pausa @SLEEP@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Il sistema è uscito dallo stato di pausa @SLEEP@. - --- 98268866d1d54a499c4e98921d93bc40 -Subject: Il sistema è in fase di spegnimento -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Systemd è in fase di spegnimento. Tutti i servizi di sistema -saranno terminati e tutti i file systems smontati. - --- 7d4958e842da4a758f6c1cdc7b36dcc5 -Subject: L'unità @UNIT@ inizia la fase di avvio -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -L'unità @UNIT@ ha iniziato la fase di avvio. - --- 39f53479d3a045ac8e11786248231fbf -Subject: L'unità @UNIT@ termina la fase di avvio -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -L'unità @UNIT@ ha terminato la fase di avvio. - -La fase di avvio è @RESULT@. - --- de5b426a63be47a7b6ac3eaac82e2f6f -Subject: L'unità @UNIT@ inizia la fase di spegnimento -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -L'unità @UNIT@ ha iniziato la fase di spegnimento. - --- 9d1aaa27d60140bd96365438aad20286 -Subject: L'unità @UNIT@ termina la fase di spegnimento -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -L'unità @UNIT@ ha terminato la fase di spegnimento. - --- be02cf6855d2428ba40df7e9d022f03d -Subject: L'unità @UNIT@ è fallita -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -L'unità @UNIT@ è fallita. - -Il risultato è @RESULT@. - --- d34d037fff1847e6ae669a370e694725 -Subject: L'unità @UNIT@ inizia a caricare la propria configurazione -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -L'unità @UNIT@ è iniziata ricaricando la propria configurazione - --- 7b05ebc668384222baa8881179cfda54 -Subject: L'unità @UNIT@ termina il caricamento della propria configurazione -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -L'unità @UNIT@ è terminata ricaricando la propria configurazione - -Il risultato è @RESULT@. - --- 641257651c1b4ec9a8624d7a40a9e1e7 -Subject: Il processo @EXECUTABLE@ non può essere eseguito -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Il processo @EXECUTABLE@ non può essere eseguito e termina. - -Il numero di errore restituito durante l'esecuzione del processo è @ERRNO@. - --- 0027229ca0644181a76c4e92458afa2e -Subject: Uno o più messaggi non possono essere inoltrati a syslog -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Uno o più messaggi non possono essere inviati al servizio syslog -eseguito in parallelo a journald. Questo di solito capita perché, -l'implementazione di syslog non sta al passo con la -velocità dei messaggi accodati. - --- 1dee0369c7fc4736b7099b38ecb46ee7 -Subject: Il punto di montaggio non è vuoto -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -La directory @WHERE@ è specificata come punto di montaggio (secondo campo -in /etc/fstab o nel campo Where= del file unità di systemd) e non è vuoto. -Questo non interferisce con il montaggio, ma i file pre-esistenti in questa -directory diventano inaccessibili. Per visualizzare i file, si suggerisce -di montare manualmente il file system indicato in una posizione secondaria. - --- 24d8d4452573402496068381a6312df2 -Subject: Avviata macchina virtuale o container -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -La macchina virtuale @NAME@ con PID primario @LEADER@ è stata -avviata ed è pronta all'uso. - --- 58432bd3bace477cb514b56381b8a758 -Subject: Terminata macchina virtuale o container -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -La macchina virtuale @NAME@ con PID primario @LEADER@ è stata spenta. diff --git a/catalog/systemd.ko.catalog b/catalog/systemd.ko.catalog deleted file mode 100644 index 2fc6b60b1b..0000000000 --- a/catalog/systemd.ko.catalog +++ /dev/null @@ -1,264 +0,0 @@ -# This file is part of systemd. -# -# Copyright 2012 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 . - -# Message catalog for systemd's own messages -# Korean translation - -# The catalog format is documented on -# http://www.freedesktop.org/wiki/Software/systemd/catalog - -# For an explanation why we do all this, see https://xkcd.com/1024/ -# -# Translator : -# Seong-ho Cho , 2015. - --- f77379a8490b408bbe5f6940505a777b -Subject: 저널 시작 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -시스템 저널 프로세스를 시작했고 기록목적으로 저널 파일을 열었으며, -프로세스 요청을 기다리고 있습니다. - --- d93fb3c9c24d451a97cea615ce59c00b -Subject: 저널 멈춤 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -시스템 저널 프로세스를 껐고 현재 활성화 중인 저널 파일을 모두 -닫았습니다. - --- a596d6fe7bfa4994828e72309e95d61e -Subject: 서비스의 메시지를 거절함 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:journald.conf(5) - -일정 시간동안 서비스에서 너무 많은 메시지를 기록했습니다. -서비스에서 오는 메시지를 거절했습니다. - -의문점이 있는 서비스로부터 오는 메시지만 거절했음을 참고하십시오 -다른 서비스의 메시지에는 영향을 주지 않습니다. - -메시지 거절 제어 제한 값은 /etc/systemd/journald.conf 의 -RateLimitIntervalSec= 변수와 RateLimitBurst= 변수로 설정합니다. -자세한 내용은 ournald.conf(5)를 살펴보십시오. - --- e9bf28e6e834481bb6f48f548ad13606 -Subject: 저널 메시지 놓침 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -저널 시스템에서 커널 메시지를 충분히 빠르게 처리할 수 없어 커널 - 메시지를 잃었습니다. - --- fc2e22bc6ee647b6b90729ab34a250b1 -Subject: 프로세스 @COREDUMP_PID@번 코어 덤프(@COREDUMP_COMM@) 생성함 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:core(5) - -프로세스 @COREDUMP_PID@번 (@COREDUMP_COMM@)이 비정상적으로 끝나 -코어 덤프를 생성했습니다. - -보통 비정상 종료 관리 프로그램에서 프로그래밍 오류를 나타내며, -제작자에게 버그로 보고해야합니다. - --- 8d45620c1a4348dbb17410da57c60c66 -Subject: @USER_ID@ 사용자의 새 @SESSION_ID@ 세션 만듦 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -@USER_ID@ 사용자의 새 @SESSION_ID@ 세션을 만들었습니다. - -이 세션의 관리 프로세스는 @LEADER@ 입니다. - --- 3354939424b4456d9802ca8333ed424a -Subject: @SESSION_ID@ 세션 마침 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -@SESSION_ID@ 세션을 끝냈습니다. - --- fcbefc5da23d428093f97c82a9290f7b -Subject: 새 @SEAT_ID@ 시트 사용할 수 있음 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -새 @SEAT_ID@ 시트를 설정했고 사용할 수 있습니다. - --- e7852bfe46784ed0accde04bc864c2d5 -Subject: @SEAT_ID@ 시트 제거함 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -@SEAT_ID@ 시트를 제거했으며 더이상 사용할 수 없습니다. - --- c7a787079b354eaaa9e77b371893cd27 -Subject: 시간 바꿈 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -시스템 시계를 1970년 1월 1일 이후로 @REALTIME@ 마이크로초 지난 값으로 -설정했습니다. - --- 45f82f4aef7a4bbf942ce861d1f20990 -Subject: @TIMEZONE@ 시간대로 시간대 바꿈 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -시스템 시간대를 @TIMEZONE@ 시간대로 바꾸었습니다. - --- b07a249cd024414a82dd00cd181378ff -Subject: 시스템 시동 마침 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -부팅 과정에 시작하려고 준비한 모든 시스템 서비스를 성공적으로 - 시작했습니다. 머신이 서비스처럼 대기중이라는 의미는 아니며 -지동을 완전히 마칠 때까지 사용중일 수도 있는 점 참고하십시오. - -커널 시동에 @KERNEL_USEC@ 마이크로초가 걸립니다. - -초기 램 디스크 시동에 @INITRD_USEC@ 마이크로초가 걸립니다. - -사용자 영역 시동에 @USERSPACE_USEC@ 마이크로초가 걸립니다. - --- 6bbd95ee977941e497c48be27c254128 -Subject: @SLEEP@ 대기 상태 진입 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@SLEEP@ 대기 상태로 진입했습니다. - --- 8811e6df2a8e40f58a94cea26f8ebf14 -Subject: @SLEEP@ 대기 상태 마침 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@SLEEP@ 대기 상태를 마쳤습니다. - --- 98268866d1d54a499c4e98921d93bc40 -Subject: 컴퓨터 끄기 시작 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -컴퓨터 끄기 동작을 시작했습니다. 모든 시스템 동작을 멈추고 -모든 파일 시스템의 마운트를 해제합니다. - --- 7d4958e842da4a758f6c1cdc7b36dcc5 -Subject: @UNIT@ 유닛 시작 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@UNIT@ 유닛을 시작했습니다. - --- 39f53479d3a045ac8e11786248231fbf -Subject: @UNIT@ 유닛 시동 마침 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@UNIT@ 유닛 시동을 마쳤습니다. - -시동 결과는 @RESULT@ 입니다. - --- de5b426a63be47a7b6ac3eaac82e2f6f -Subject: @UNIT@ 유닛 끝내기 동작 시작 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@UNIT@ 유닛 끝내기 동작을 시작했습니다. - --- 9d1aaa27d60140bd96365438aad20286 -Subject: @UNIT@ 유닛 끝내기 동작 마침 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@UNIT@ 유닛 끝내기 동작을 마쳤습니다. - --- be02cf6855d2428ba40df7e9d022f03d -Subject: @UNIT@ 유닛 동작 실패 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@UNIT@ 유닛 동작에 실패했습니다. - -결과는 @RESULT@ 입니다. - --- d34d037fff1847e6ae669a370e694725 -Subject: @UNIT@ 유닛 설정 다시 읽기 시작 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@UNIT@ 유닛의 설정 다시 읽기를 시작했습니다 - --- 7b05ebc668384222baa8881179cfda54 -Subject: @UNIT@ 유닛 설정 다시 읽기 완료 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@UNIT@ 유닛의 설정 다시 읽기 동작을 끝냈습니다. - -결과는 @RESULT@ 입니다. - --- 641257651c1b4ec9a8624d7a40a9e1e7 -Subject: @EXECUTABLE@ 프로세스 시작할 수 없음 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@EXECUTABLE@ 프로세스를 시작할 수 없어 실행에 실패했습니다. - -이 프로세스에서 반환한 오류 번호는 @ERRNO@번 입니다. - --- 0027229ca0644181a76c4e92458afa2e -Subject: 하나 이상의 메시지를 syslog에 전달할 수 없음 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -journald 서비스와 동시에 실행중인 syslog 서비스에 하나 이상의 메시지를 -전달할 수 없습니다. 보통 순차적으로 오는 메시지의 속도를 syslog 구현체가 -따라가지 못함을 의미합니다. - --- 1dee0369c7fc4736b7099b38ecb46ee7 -Subject: 마운트 지점 비어있지 않음 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@WHERE@ 디렉터리를 마운트 지점으로 지정했으며 (/etc/fstab 파일의 - 두번째 필드 또는 systemd 유닛 파일의 Where= 필드) 비어있지 않습니다. -마운트 과정에 방해가 되진 않지만 이전에 이 디렉터리에 존재하는 파일에 - 접근할 수 없게 됩니다. 중복으로 마운트한 파일을 보려면, 근본 파일 -시스템의 다음 위치에 직접 마운트하십시오. - --- 24d8d4452573402496068381a6312df2 -Subject: 가상 머신 또는 컨테이너 시작 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@LEADER@ 프로세스 ID로 동작하는 @NAME@ 가상 머신을 시작했으며, -이제부터 사용할 수 있습니다. - --- 58432bd3bace477cb514b56381b8a758 -Subject: 가상 머신 또는 컨테이너 마침 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@LEADER@ 프로세스 ID로 동작하는 @NAME@ 가상 머신을 껐습니다. diff --git a/catalog/systemd.pl.catalog b/catalog/systemd.pl.catalog deleted file mode 100644 index d8059e93cd..0000000000 --- a/catalog/systemd.pl.catalog +++ /dev/null @@ -1,315 +0,0 @@ -# This file is part of systemd. -# -# Copyright 2012 Lennart Poettering -# Copyright 2014, 2015, 2016 Piotr Drąg -# -# 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 . - -# Message catalog for systemd's own messages -# Polish translation - -# The catalog format is documented on -# http://www.freedesktop.org/wiki/Software/systemd/catalog - -# For an explanation why we do all this, see https://xkcd.com/1024/ - --- f77379a8490b408bbe5f6940505a777b -Subject: Uruchomiono dziennik -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Systemowy proces dziennika został uruchomiony, otworzył pliki dziennika do -zapisu i jest gotowy do przetwarzania żądań. - --- d93fb3c9c24d451a97cea615ce59c00b -Subject: Zatrzymano dziennik -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Systemowy proces dziennika został wyłączony i zamknął wszystkie obecnie -aktywne pliki dziennika. - --- ec387f577b844b8fa948f33cad9a75e6 -Subject: Miejsce na dysku używane przez dziennik -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@JOURNAL_NAME@ (@JOURNAL_PATH@) obecnie używa @CURRENT_USE_PRETTY@. -Maksymalnie może używać @MAX_USE_PRETTY@. -Zostawianie co najmniej @DISK_KEEP_FREE_PRETTY@ wolnego (z obecnie dostępnego @DISK_AVAILABLE_PRETTY@ miejsca na dysku). -Wymuszone ograniczenie użycia wynosi więc @LIMIT_PRETTY@, z czego @AVAILABLE_PRETTY@ jest nadal dostępne. - -Ograniczenia kontrolujące ilość miejsca na dysku używanego przez dziennik -można konfigurować za pomocą ustawień SystemMaxUse=, SystemKeepFree=, -SystemMaxFileSize=, RuntimeMaxUse=, RuntimeKeepFree=, RuntimeMaxFileSize= -w pliku /etc/systemd/journald.conf. Strona journald.conf(5) zawiera więcej -informacji. - --- a596d6fe7bfa4994828e72309e95d61e -Subject: Ograniczono komunikaty z usługi -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:journald.conf(5) - -Usługa zapisała za dużo komunikatów w określonym czasie. Komunikaty z usługi -zostały pominięte. - -Proszę zauważyć, że tylko komunikaty z danej usługi zostały pominięte. Nie ma -to wpływu na komunikaty innych usług. - -Ograniczenia kontrolujące pomijanie komunikatów mogą być konfigurowane -za pomocą opcji RateLimitIntervalSec= i RateLimitBurst= w pliku -/etc/systemd/journald.conf. Strona journald.conf(5) zawiera więcej informacji. - --- e9bf28e6e834481bb6f48f548ad13606 -Subject: Utracono komunikaty dziennika -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Komunikaty jądra zostały utracone, ponieważ system dziennika nie mógł -przetworzyć ich odpowiednio szybko. - --- fc2e22bc6ee647b6b90729ab34a250b1 -Subject: Proces @COREDUMP_PID@ (@COREDUMP_COMM@) zrzucił plik core -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:core(5) - -Proces @COREDUMP_PID@ (@COREDUMP_COMM@) uległ awarii i zrzucił plik core. - -Zwykle wskazuje to na błąd programistyczny w danym programie i powinno zostać -zgłoszone jego producentowi jako błąd. - --- 8d45620c1a4348dbb17410da57c60c66 -Subject: Utworzono nową sesję @SESSION_ID@ dla użytkownika @USER_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Nowa sesja o identyfikatorze @SESSION_ID@ została utworzona dla użytkownika -@USER_ID@. - -Proces prowadzący sesji: @LEADER@. - --- 3354939424b4456d9802ca8333ed424a -Subject: Zakończono sesję @SESSION_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Sesja o identyfikatorze @SESSION_ID@ została zakończona. - --- fcbefc5da23d428093f97c82a9290f7b -Subject: Dostępne jest nowe stanowisko @SEAT_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Nowe stanowisko @SEAT_ID@ zostało skonfigurowane i jest teraz dostępne. - --- e7852bfe46784ed0accde04bc864c2d5 -Subject: Usunięto stanowisko @SEAT_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Stanowisko @SEAT_ID@ zostało usunięte i nie jest już dostępne. - --- c7a787079b354eaaa9e77b371893cd27 -Subject: Zmiana czasu -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Zegar systemowy został zmieniony na @REALTIME@ μs po 1 stycznia 1970. - --- 45f82f4aef7a4bbf942ce861d1f20990 -Subject: Zmiana strefy czasowej na @TIMEZONE@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Systemowa strefa czasowa została zmieniona na @TIMEZONE@. - --- b07a249cd024414a82dd00cd181378ff -Subject: Ukończono uruchamianie systemu -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Wszystkie usługi systemowe obowiązkowo zakolejkowane do włączenia podczas -uruchamiania systemu zostały pomyślnie uruchomione. Proszę zauważyć, że nie -oznacza to, że komputer jest bezczynny, jako że usługi mogą wciąż kończyć -proces uruchamiania. - -Uruchamianie jądra zajęło @KERNEL_USEC@ μs. - -Uruchamianie początkowego dysku RAM zajęło @INITRD_USEC@ μs. - -Uruchamianie przestrzeni użytkownika zajęło @USERSPACE_USEC@ μs. - --- 6bbd95ee977941e497c48be27c254128 -Subject: Przejście do stanu uśpienia @SLEEP@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -System przeszedł do stanu uśpienia @SLEEP@. - --- 8811e6df2a8e40f58a94cea26f8ebf14 -Subject: Wyjście ze stanu uśpienia @SLEEP@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -System wyszedł ze stanu uśpienia @SLEEP@. - --- 98268866d1d54a499c4e98921d93bc40 -Subject: Zainicjowano wyłączenie systemu -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Zainicjowano wyłączenie systemd. Wyłączenie zostało rozpoczęte i wszystkie -usługi systemowe zostały zakończone, a wszystkie systemy plików odmontowane. - --- 7d4958e842da4a758f6c1cdc7b36dcc5 -Subject: Rozpoczęto uruchamianie jednostki @UNIT@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Jednostka @UNIT@ rozpoczęła uruchamianie. - --- 39f53479d3a045ac8e11786248231fbf -Subject: Ukończono uruchamianie jednostki @UNIT@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Jednostka @UNIT@ ukończyła uruchamianie. - -Wynik uruchamiania: @RESULT@. - --- de5b426a63be47a7b6ac3eaac82e2f6f -Subject: Rozpoczęto wyłączanie jednostki @UNIT@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Jednostka @UNIT@ rozpoczęła wyłączanie. - --- 9d1aaa27d60140bd96365438aad20286 -Subject: Ukończono wyłączanie jednostki @UNIT@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Jednostka @UNIT@ ukończyła wyłączanie. - --- be02cf6855d2428ba40df7e9d022f03d -Subject: Jednostka @UNIT@ się nie powiodła -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Jednostka @UNIT@ się nie powiodła. - -Wynik: @RESULT@. - --- d34d037fff1847e6ae669a370e694725 -Subject: Rozpoczęto ponowne wczytywanie konfiguracji jednostki @UNIT@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Jednostka @UNIT@ rozpoczęła ponowne wczytywanie swojej konfiguracji. - --- 7b05ebc668384222baa8881179cfda54 -Subject: Ukończono ponowne wczytywanie konfiguracji jednostki @UNIT@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Jednostka @UNIT@ ukończyła ponowne wczytywanie swojej konfiguracji. - -Wynik: @RESULT@. - --- 641257651c1b4ec9a8624d7a40a9e1e7 -Subject: Nie można wykonać procesu @EXECUTABLE@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Proces @EXECUTABLE@ nie mógł zostać wykonany i się nie powiódł. - -Numer błędu zwrócony przez ten proces: @ERRNO@. - --- 0027229ca0644181a76c4e92458afa2e -Subject: Nie można przekazać jednego lub więcej komunikatów do syslog -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Jeden lub więcej komunikatów nie może zostać przekazanych do usługi syslog -uruchomionej obok journald. Zwykle oznacza to, że implementacja syslog nie -jest w stanie nadążyć za prędkością kolejki komunikatów. - --- 1dee0369c7fc4736b7099b38ecb46ee7 -Subject: Punkt montowania nie jest pusty -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Katalog @WHERE@ został podany jako punkt montowania (drugie pole w pliku -/etc/fstab lub pole Where= w pliku jednostki systemd) i nie jest pusty. Nie -wpływa to na montowanie, ale wcześniej istniejące pliki w tym katalogu stają -się niedostępne. Aby zobaczyć te pliki, proszę ręcznie zamontować system -plików w innym położeniu. - --- 24d8d4452573402496068381a6312df2 -Subject: Uruchomiono maszynę wirtualną lub kontener -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Maszyna wirtualna @NAME@ (PID prowadzący @LEADER@) została uruchomiona i jest -gotowa do użycia. - --- 58432bd3bace477cb514b56381b8a758 -Subject: Zakończono maszynę wirtualną lub kontener -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Maszyna wirtualna @NAME@ (PID prowadzący @LEADER@) została wyłączona. - --- 36db2dfa5a9045e1bd4af5f93e1cf057 -Subject: Wyłączono tryb DNSSEC, ponieważ serwer go nie obsługuje -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:systemd-resolved.service(8) resolved.conf(5) - -Usługa resolver (systemd-resolved.service) wykryła, że skonfigurowany serwer -DNS nie obsługuje DNSSEC, w wyniku czego walidacja DNSSEC została wyłączona. - -To zdarzenie będzie miało miejsce, jeśli skonfigurowano DNSSEC=allow-downgrade -w pliku resolved.conf, a skonfigurowany serwer DNS jest niezgodny z DNSSEC. -Proszę zauważyć, że używanie tego trybu umożliwia ataki wyłączające DNSSEC, -ponieważ atakujący będzie mógł wyłączyć walidację DNSSEC na komputerze przez -umieszczenie odpowiednich odpowiedzi DNS w kanale komunikacji. - -To zdarzenie może wskazywać, że serwer DNS jest faktycznie niezgodny z DNSSEC, -albo że atakującemu udało się upozorować atak tego typu. - --- 1675d7f172174098b1108bf8c7dc8f5d -Subject: Walidacja DNSSEC się nie powiodła -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:systemd-resolved.service(8) - -Zapytanie DNS lub ustawiony wpis zasobu nie przeszedł walidacji DNSSEC. -Zwykle wskazuje to, że ktoś manipulował używanym kanałem komunikacji. - --- 4d4408cfd0d144859184d1e65d7c8a65 -Subject: Unieważniono kotwicę zaufania DNSSEC -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:systemd-resolved.service(8) - -Kotwica zaufania DNSSEC została unieważniona. Należy skonfigurować nową, albo -system operacyjny musi zostać zaktualizowany, aby dostarczyć zaktualizowaną -kotwicę zaufania DNSSEC. diff --git a/catalog/systemd.pt_BR.catalog b/catalog/systemd.pt_BR.catalog deleted file mode 100644 index 8b856e8355..0000000000 --- a/catalog/systemd.pt_BR.catalog +++ /dev/null @@ -1,264 +0,0 @@ -# This file is part of systemd. -# -# Copyright 2012 Lennart Poettering -# Copyright 2015 Rafael Ferreira (translation) -# -# 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 . - -# Catálogo de mensagens para as mensagens do próprio systemd - -# O formato do catálogo está documentado em -# http://www.freedesktop.org/wiki/Software/systemd/catalog - -# Para uma explicação do porquê de fazermos tudo isso, veja -# https://xkcd.com/1024/ - --- f77379a8490b408bbe5f6940505a777b -Subject: O jornal foi inciado -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -O processo jornal do sistema foi iniciado, arquivos foram abertos e está -pronto para processar requisições. - --- d93fb3c9c24d451a97cea615ce59c00b -Subject: O jornal foi interrompido -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -O processo do jornal do sistema foi desligado e todos os arquivos de jornal -do sistema foram fechados. - --- a596d6fe7bfa4994828e72309e95d61e -Subject: Mensagens de um serviço foram suprimidas -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:journald.conf(5) - -Um serviço registrou no log um número excessivo de mensagens dentro de um -período de tempo. Mensagens do serviço foram descartadas. - -Note que apenas mensagens de um serviço em questão foram descartadas; outras -mensagens dos serviços não foram afetadas. - -Os controles de limites de quando as mensagens são descartadas pode ser -configurado com RateLimitIntervalSec= e RateLimitBurst= no -/etc/systemd/journald.conf. Veja journald.conf(5) para detalhes. - --- e9bf28e6e834481bb6f48f548ad13606 -Subject: Mensagens do jornal foram perdidas -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Mensagens do kernel foram perdidas pois o sistema do jornal não pôde -processá-las em velocidade suficiente para a demanda. - --- fc2e22bc6ee647b6b90729ab34a250b1 -Subject: Processo @COREDUMP_PID@ (@COREDUMP_COMM@) despejou núcleo -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:core(5) - -Processo @COREDUMP_PID@ (@COREDUMP_COMM@) travou e despejou o núcleo. - -Isso normalmente indica um erro de programação no programa que travou e -deveria ser relatado para seu fabricante como um erro. - --- 8d45620c1a4348dbb17410da57c60c66 -Subject: A nova sessão @SESSION_ID@ foi criada para usuário o @USER_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Uma nova sessão com o ID @SESSION_ID@ foi criada para o usuário @USER_ID@. - -O processo originador da sessão é @LEADER@. - --- 3354939424b4456d9802ca8333ed424a -Subject: Sessão @SESSION_ID@ foi terminada -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Um sessão com o ID @SESSION_ID@ foi terminada. - --- fcbefc5da23d428093f97c82a9290f7b -Subject: Um novo seat @SEAT_ID@ está disponível -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Um novo seat @SEAT_ID@ foi configurado e está disponível. - --- e7852bfe46784ed0accde04bc864c2d5 -Subject: Seat @SEAT_ID@ foi removido agora -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Um seat @SEAT_ID@ foi removido e não está mais disponível. - --- c7a787079b354eaaa9e77b371893cd27 -Subject: Time change -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -O relógio do sistema foi alterado para @REALTIME@ microssegundos após 1º de -janeiro de 1970. - --- 45f82f4aef7a4bbf942ce861d1f20990 -Subject: Fuso horário alterado para @TIMEZONE@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -O fuso horário do sistema foi alterado para @TIMEZONE@. - --- b07a249cd024414a82dd00cd181378ff -Subject: Inicialização do sistema foi concluída -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Todos os serviços do sistema necessários que estão enfileirados para -executar na inicialização do sistema, foram iniciados com sucesso. Note -que isso não significa que a máquina está ociosa, pois os serviços podem -ainda estar ocupados com a inicialização completa. - -Inicialização do kernel precisou @KERNEL_USEC@ microssegundos. - -Disco de RAM inicial precisou de @INITRD_USEC@ microssegundos. - -Inicialização do espaço do usuário precisou de @USERSPACE_USEC@ microssegundos. - --- 6bbd95ee977941e497c48be27c254128 -Subject: Estado de suspensão do sistema @SLEEP@ iniciado -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -O sistema entrou agora no estado de suspensão @SLEEP@. - --- 8811e6df2a8e40f58a94cea26f8ebf14 -Subject: Estado de suspensão do sistema @SLEEP@ finalizado -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -O sistema saiu agora do estado de suspensão @SLEEP@. - --- 98268866d1d54a499c4e98921d93bc40 -Subject: Desligamento do sistema iniciado -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Desligamento do sistema foi inicializado. O desligamento se iniciou e todos -os serviços do sistema foram terminados e todos os sistemas desmontados. - --- 7d4958e842da4a758f6c1cdc7b36dcc5 -Subject: Unidade @UNIT@ sendo iniciado -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A unidade @UNIT@ está sendo iniciada. - --- 39f53479d3a045ac8e11786248231fbf -Subject: Unidade @UNIT@ concluiu a inicialização -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A unidade @UNIT@ concluiu a inicialização. - -The start-up result is @RESULT@. - --- de5b426a63be47a7b6ac3eaac82e2f6f -Subject: Unidade @UNIT@ sendo desligado -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A unidade @UNIT@ está sendo desligada. - --- 9d1aaa27d60140bd96365438aad20286 -Subject: A unidade @UNIT@ concluiu o desligamento -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A unidade @UNIT@ concluiu o desligamento. - --- be02cf6855d2428ba40df7e9d022f03d -Subject: A unidade @UNIT@ falhou -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A unidade @UNIT@ falhou. - -O resultado é @RESULT@. - --- d34d037fff1847e6ae669a370e694725 -Subject: Unidade @UNIT@ iniciou recarregamento de sua configuração -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A unidade @UNIT@ iniciou o recarregamento de sua configuração. - --- 7b05ebc668384222baa8881179cfda54 -Subject: Unidade @UNIT@ concluiu recarregamento de sua configuração -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A unidade @UNIT@ concluiu o recarregamento de sua configuração. - -O resultado é @RESULT@. - --- 641257651c1b4ec9a8624d7a40a9e1e7 -Subject: Processo @EXECUTABLE@ não pôde ser executado -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -O processo @EXECUTABLE@ não pôde ser executado e falhou. - -O número de erro retornado por este processo é @ERRNO@. - --- 0027229ca0644181a76c4e92458afa2e -Subject: Uma ou mais mensagens não puderam ser encaminhadas para o syslog -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Uma ou mais mensagens não puderam ser encaminhadas para o serviço do syslog -em execução paralela ao journald. Isso normalmente indica que a implementação -do syslog não foi capaz de se manter com a velocidade das mensagens -enfileiradas. - --- 1dee0369c7fc4736b7099b38ecb46ee7 -Subject: Ponto de montagem não está vazio -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -O diretório @WHERE@ está especificado como ponto de montagem (o segundo campo -no /etc/fstab ou campo Where= no arquivo de unidade do systemd) e não está -vazio. Isso não interfere com a montagem, mas os arquivos pré-existentes -neste diretório se tornaram inacessívels. Para ver aqueles arquivos, sobre os -quais foi realizada a montagem, por favor monte manualmente o sistema de -arquivos subjacente para uma localização secundária. - --- 24d8d4452573402496068381a6312df2 -Subject: Uma máquina virtual ou contêiner foi iniciado -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A máquina virtual @NAME@ com seu PID @LEADER@ incial foi iniciada e está -pronto para ser usad. - --- 58432bd3bace477cb514b56381b8a758 -Subject: Uma máquina virtual ou contêiner foi terminado -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -A máquina virtual @NAME@ com seu PID @LEADER@ incial foi desligada. diff --git a/catalog/systemd.ru.catalog b/catalog/systemd.ru.catalog deleted file mode 100644 index e56dbe3acc..0000000000 --- a/catalog/systemd.ru.catalog +++ /dev/null @@ -1,354 +0,0 @@ -# This file is part of systemd. -# -# Copyright 2012 Lennart Poettering -# Copyright 2013-2016 Sergey Ptashnick -# -# 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 . - -# Message catalog for systemd's own messages -# Russian translation - -# Формат каталога сообщений описан по ссылке -# http://www.freedesktop.org/wiki/Software/systemd/catalog - -# Перед каждым элементом в комментарии указан Subject исходного -# сообщения (на английском). - -# Subject: The Journal has been started --- f77379a8490b408bbe5f6940505a777b -Subject: Запущена служба журналирования -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Процесс, отвечающий за журналирование системных событий, успешно запустился, -открыл для записи файлы журнала, и готов обрабатывать запросы. - -# Subject: The Journal has been stopped --- d93fb3c9c24d451a97cea615ce59c00b -Subject: Служба журналирования остановлена -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Процесс, отвечающий за журналирование системных событий, завершил работу и -закрыл все свои файлы. - -# Subject: Disk space used by the journal --- ec387f577b844b8fa948f33cad9a75e6 -Subject: Место на диске, занятое журналом -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@JOURNAL_NAME@ (@JOURNAL_PATH@) сейчас занимает @CURRENT_USE_PRETTY@. -Максимальный разрешенный размер составляет @MAX_USE_PRETTY@. -Оставляем свободными как минимум @DISK_KEEP_FREE_PRETTY@ (сейчас на диске -свободно @DISK_AVAILABLE_PRETTY@). -Таким образом, предел использования составляет @LIMIT_PRETTY@, из которых -@AVAILABLE_PRETTY@ пока свободно. - -Ограничения на размер журнала настраиваются при помощи параметров -SystemMaxUse=, SystemKeepFree=, SystemMaxFileSize=, RuntimeMaxUse=, -RuntimeKeepFree=, RuntimeMaxFileSize= в файле /etc/systemd/journald.conf. -Более подробные сведения вы можете получить на справочной странице -journald.conf(5). - -# Subject: Messages from a service have been suppressed --- a596d6fe7bfa4994828e72309e95d61e -Subject: Часть сообщений от службы пропущена -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:journald.conf(5) - -Служба отправила слишком много сообщений за короткий промежуток времени. -Часть сообщений была пропущена. - -Обратите внимание, что были пропущены сообщения только от этой службы, -сообщения других служб не затронуты. - -Предел, после которого служба журнала начинает игнорировать сообщения, -настраивается параметрами RateLimitIntervalSec= и RateLimitBurst= в файле -/etc/systemd/journald.conf. Подробности смотрите на странице руководства -journald.conf(5). - -# Subject: Journal messages have been missed --- e9bf28e6e834481bb6f48f548ad13606 -Subject: Часть сообщений ядра пропущена -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Часть сообщений, поступивших от ядра, была потеряна, так как служба -журналирования не успела их обработать. - -# Subject: Process @COREDUMP_PID@ (@COREDUMP_COMM@) dumped core --- fc2e22bc6ee647b6b90729ab34a250b1 -Subject: Процесс @COREDUMP_PID@ (@COREDUMP_COMM@) сбросил дамп памяти -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:core(5) - -Процесс @COREDUMP_PID@ (@COREDUMP_COMM@) завершился из-за критической ошибки. -Записан дамп памяти. - -Вероятно, это произошло из-за ошибки, допущенной в коде программы. -Рекомендуется сообщить её разработчикам о возникшей проблеме. - -# Subject: A new session @SESSION_ID@ has been created for user @USER_ID@ --- 8d45620c1a4348dbb17410da57c60c66 -Subject: Для пользователя @USER_ID@ создан новый сеанс @SESSION_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Для пользователя @USER_ID@ создан новый сеанс с идентификатором @SESSION_ID@. - -Главный процесс нового сеанса имеет индентификатор @LEADER@. - -# Subject: A session @SESSION_ID@ has been terminated --- 3354939424b4456d9802ca8333ed424a -Subject: Сеанс @SESSION_ID@ завершен -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Сеанс с идентификатором @SESSION_ID@ завершился. - -# Subject: A new seat @SEAT_ID@ is now available --- fcbefc5da23d428093f97c82a9290f7b -Subject: Добавлено новое рабочее место @SEAT_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Новое рабочее место (seat) @SEAT_ID@ полностью настроено и готово к -использованию. - -# Subject: A seat @SEAT_ID@ has now been removed --- e7852bfe46784ed0accde04bc864c2d5 -Subject: Рабочее место @SEAT_ID@ отключено -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Рабочее место (seat) @SEAT_ID@ было отключено. - -# Subject: Time change --- c7a787079b354eaaa9e77b371893cd27 -Subject: Переведены системные часы -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Системные часы были переведены. Сейчас они показывают @REALTIME@ микросекунд -с момента 00:00:00 1 января 1970 года. - -# Subject: Time zone change to @TIMEZONE@ --- 45f82f4aef7a4bbf942ce861d1f20990 -Subject: Часовой пояс изменен на @TIMEZONE@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Системный часовой пояс был изменен. Новое значение: @TIMEZONE@. - -# Subject: System start-up is now complete --- b07a249cd024414a82dd00cd181378ff -Subject: Запуск системы завершен -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Все системные службы, запуск которых предписан настройками, были запущены. -Впрочем, это ещё не означает, что система в данный момент ничем не занята, -так как некоторые службы могут продолжать инициализацию даже после того, как -отчитались о своем запуске. - -Запуск ядра занял @KERNEL_USEC@ микросекунд. - -Процессы начального RAM-диска (initrd) отработали за @INITRD_USEC@ микросекунд. - -Запуск системных служб занял @USERSPACE_USEC@ микросекунд. - -# Subject: System sleep state @SLEEP@ entered --- 6bbd95ee977941e497c48be27c254128 -Subject: Система перешла в состояние сна (@SLEEP@) -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Система была переведена в состояние сна (@SLEEP@). - -# Subject: System sleep state @SLEEP@ left --- 8811e6df2a8e40f58a94cea26f8ebf14 -Subject: Система вышла из состояния сна (@SLEEP@) -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Система была выведена из состояния сна (@SLEEP@). - -# Subject: System shutdown initiated --- 98268866d1d54a499c4e98921d93bc40 -Subject: Подготовка системы к выключению -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Начат процесс подготовки к выключению компьютера. Останавливаются все системные -службы, отмонтируются все файловые системы. - -# Subject: Unit @UNIT@ has begun with start-up --- 7d4958e842da4a758f6c1cdc7b36dcc5 -Subject: Начинается запуск юнита @UNIT@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Начат процесс запуска юнита @UNIT@. - -# Subject: Unit @UNIT@ has finished start-up --- 39f53479d3a045ac8e11786248231fbf -Subject: Запуск юнита @UNIT@ завершен -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Процесс запуска юнита @UNIT@ был завершен. - -Результат: @RESULT@. - -# Subject: Unit @UNIT@ has begun shutting down --- de5b426a63be47a7b6ac3eaac82e2f6f -Subject: Начинается остановка юнита @UNIT@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Начат процесс остановки юнита @UNIT@. - -# Subject: Unit @UNIT@ has finished shutting down --- 9d1aaa27d60140bd96365438aad20286 -Subject: Завершена остановка юнита @UNIT@. -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Процесс остановки юнита @UNIT@ был завершен. - -# Subject: Unit @UNIT@ has failed --- be02cf6855d2428ba40df7e9d022f03d -Subject: Ошибка юнита @UNIT@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Произошел сбой юнита @UNIT@. - -Результат: @RESULT@. - -# Subject: Unit @UNIT@ has begun with reloading its configuration --- d34d037fff1847e6ae669a370e694725 -Subject: Юнит @UNIT@ начал перечитывать свои настройки -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Юнит @UNIT@ начал процесс перечитывания своей конфигурации. - -# Subject: Unit @UNIT@ has finished reloading its configuration --- 7b05ebc668384222baa8881179cfda54 -Subject: Юнит @UNIT@ завершил перечитывание своих настроек -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Юнит @UNIT@ завершил процесс перечитывания своей конфигурации. - -Результат: @RESULT@. - -# Subject: Process @EXECUTABLE@ could not be executed --- 641257651c1b4ec9a8624d7a40a9e1e7 -Subject: Не удалось запустить процесс @EXECUTABLE@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Сбой: не удалось запустить процесс @EXECUTABLE@. - -Код ошибки: @ERRNO@. - -# Subject: One or more messages could not be forwarded to syslog --- 0027229ca0644181a76c4e92458afa2e -Subject: Часть сообщений не удалось передать процессу syslog -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Не удалось передать некоторые сообщения демону системного лога (syslog), -дублирующему работу службы системного журнала. Скорее всего, причина в том, что -используемая реализация syslog не успевает обрабатывать сообщения с достаточной -скоростью. - -# Subject: Mount point is not empty --- 1dee0369c7fc4736b7099b38ecb46ee7 -Subject: Каталог, являющийся точкой монтирования, не пуст -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Каталог @WHERE@, который был указан в качестве точки монтирования (во втором -столбце файла /etc/fstab, либо в параметре Where= файла конфигурации юнита), -не является пустым. Это никак не мешает монтированию, однако ранее находившиеся -в нем файлы будут недоступны. Чтобы получить к ним доступ, вы можете вручную -перемонтировать эту файловую систему в другую точку. - -# Subject: A virtual machine or container has been started --- 24d8d4452573402496068381a6312df2 -Subject: Запущена виртуальная машина/контейнер -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Виртуальная машина @NAME@ (идентификатор главного процесса: @LEADER@) запущена и -готова к работе. - -# Subject: A virtual machine or container has been terminated --- 58432bd3bace477cb514b56381b8a758 -Subject: Остановлена виртуальная машина/контейнер -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Виртуальная машина @NAME@ (идентификатор главного процесса: @LEADER@) выключена. - -# Subject: DNSSEC mode has been turned off, as server doesn't support it --- 36db2dfa5a9045e1bd4af5f93e1cf057 -Subject: Механизм DNSSEC был отключен, так как DNS-сервер его не поддерживает -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:systemd-resolved.service(8) resolved.conf(5) - -Служба разрешения имен хостов (systemd-resolved.service) определила, что -указанный в настойках DNS-сервер не поддерживает технологию DNSSEC, и -автоматически отключила DNSSEC-проверки. - -Данное событие возникает, если в файле resolved.conf указан параметр -DNSSEC=allow-downgrade, и вышестоящий DNS-сервер не поддерживает DNSSEC. -Обратите внимание, что режим allow-downgrade допускает возможность атаки -"DNSSEC downgrade", в ходе которой атакующий хакер блокирует проверки DNSSEC -путем отправки ложных сообщений от имени DNS-сервера. - -Возникновение данного события может свидетельствовать как о том, что ваш -DNS-сервер не поддерживает DNSSEC, так и о том, что некий хакер успешно провел -против вас атаку, направленную на блокировку DNSSEC-проверок. - -# Subject: DNSSEC validation failed --- 1675d7f172174098b1108bf8c7dc8f5d -Subject: Проверка DNSSEC провалена -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:systemd-resolved.service(8) - -DNS-запрос или отдельная ресурсная запись не прошла проверку DNSSEC. -Как правило, это свидетельствует о постороннем вмешательстве в канал связи. - -# Subject: A DNSSEC trust anchor has been revoked --- 4d4408cfd0d144859184d1e65d7c8a65 -Subject: Открытый ключ DNSSEC был отозван -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:systemd-resolved.service(8) - -Открытый ключ (trust ahcnor) DNSSEC был отозван. Необходимо настроить новый -открытый ключ, либо обновить систему, чтобы получить обновленный открытый ключ. diff --git a/catalog/systemd.sr.catalog b/catalog/systemd.sr.catalog deleted file mode 100644 index cc689b7956..0000000000 --- a/catalog/systemd.sr.catalog +++ /dev/null @@ -1,262 +0,0 @@ -# This file is part of systemd. -# -# Copyright 2012 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 . - -# Message catalog for systemd's own messages -# Serbian translation - -# Формат каталога је документован на -# http://www.freedesktop.org/wiki/Software/systemd/catalog - -# Да бисте видели зашто ово радимо, погледајте https://xkcd.com/1024/ - --- f77379a8490b408bbe5f6940505a777b -Subject: Журнал је покренут -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Системски журналски процес се покренуо, отворио журналске -датотеке за упис и спреман је за обраду захтева. - --- d93fb3c9c24d451a97cea615ce59c00b -Subject: Журнал је заустављен -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Системски журналски процес се зауставио и затворио све тренутно -отворене журналске датотеке. - --- a596d6fe7bfa4994828e72309e95d61e -Subject: Поруке од услуге су утишане -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:journald.conf(5) - -Услуга је уписала сувише порука за једно време. Поруке -од услуге су одбачене. - -Знајте да су само поруке од ове услуге одбачене, друге -услуге нису захваћене овим. - -Ограничења која подешавају начин на који се поруке одбацују се могу подесити -помоћу „RateLimitIntervalSec=“ и „RateLimitBurst=“ параметара унутар датотеке -/etc/systemd/journald.conf. Погледајте journald.conf(5) за појединости. - --- e9bf28e6e834481bb6f48f548ad13606 -Subject: Журналске поруке су изгубљене -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Поруке кернела су изгубљене јер журналски систем није могао да их -обради довољно брзо. - --- fc2e22bc6ee647b6b90729ab34a250b1 -Subject: Процес @COREDUMP_PID@ (@COREDUMP_COMM@) је избацио своје језгро -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:core(5) - -Процес @COREDUMP_PID@ (@COREDUMP_COMM@) је пао и избацио своје језгро. - -Ово обично значи да постоји грешка у програму који је пао и ова -грешка треба да се пријави продавцу. - --- 8d45620c1a4348dbb17410da57c60c66 -Subject: Нова сесија @SESSION_ID@ је направљена за корисника @USER_ID@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Нова сесија са ИБ-ом @SESSION_ID@ је направљена за корисника @USER_ID@. - -Водећи процес сесије је @LEADER@. - --- 3354939424b4456d9802ca8333ed424a -Subject: Сесија @SESSION_ID@ је окончана -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Сесија са ИБ-ом @SESSION_ID@ је окончана. - --- fcbefc5da23d428093f97c82a9290f7b -Subject: Ново седиште @SEAT_ID@ је сада доступно -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Ново седиште @SEAT_ID@ је исподешавано и сада је доступно. - --- e7852bfe46784ed0accde04bc864c2d5 -Subject: Седиште @SEAT_ID@ је сада уклоњено -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -Седиште @SEAT_ID@ је сада уклоњено и више није доступно. - --- c7a787079b354eaaa9e77b371893cd27 -Subject: Време је промењено -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Системски сат је сада подешен на @REALTIME@ микросекунде након 1. јануара 1970. године. - --- 45f82f4aef7a4bbf942ce861d1f20990 -Subject: Временска зона је промењена на @TIMEZONE@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Временска зона је промењена на @TIMEZONE@. - --- b07a249cd024414a82dd00cd181378ff -Subject: Подизање система је сада готово -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Све системске услуге које су заказане за подизање су успешно покренуте. -Знајте да ово не значи да је машина сада беспослена јер услуге могу -и даље бити заузете завршавањем покретања система. - -Подизање кернела је трајало @KERNEL_USEC@ микросекунде. - -Подизање почетног РАМ диска је трајало @INITRD_USEC@ микросекунде. - -Подизање корисничких програма је трајало @USERSPACE_USEC@ микросекунде. - --- 6bbd95ee977941e497c48be27c254128 -Subject: Системско стање спавања @SLEEP@ започето -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Систем је сада ушао у @SLEEP@ стање спавања. - --- 8811e6df2a8e40f58a94cea26f8ebf14 -Subject: Системско стање спавања @SLEEP@ напуштено -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Систем је изашао из @SLEEP@ стања спавања. - --- 98268866d1d54a499c4e98921d93bc40 -Subject: Гашење система започето -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Систем-де гашење је започето. Гашење је сада почело и све -системске услуге су окончане и сви системи датотека откачени. - --- 7d4958e842da4a758f6c1cdc7b36dcc5 -Subject: Јединица @UNIT@ је почела са покретањем -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Јединица @UNIT@ је почела са покретањем. - --- 39f53479d3a045ac8e11786248231fbf -Subject: Јединица @UNIT@ је завршила са покретањем -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Јединица @UNIT@ је завршила са покретањем. - -Исход покретања је @RESULT@. - --- de5b426a63be47a7b6ac3eaac82e2f6f -Subject: Јединица @UNIT@ је почела са гашењем -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Јединица @UNIT@ је почела са гашењем. - --- 9d1aaa27d60140bd96365438aad20286 -Subject: Јединица @UNIT@ је завршила са гашењем -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Јединица @UNIT@ је завршила са гашењем. - --- be02cf6855d2428ba40df7e9d022f03d -Subject: Јединица @UNIT@ је пукла -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Јединица @UNIT@ је пукла. - -Исход је @RESULT@. - --- d34d037fff1847e6ae669a370e694725 -Subject: Јединица @UNIT@ је почела са поновним учитавањем свог подешавања -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Јединица @UNIT@ је почела са поновним учитавањем свог подешавања - --- 7b05ebc668384222baa8881179cfda54 -Subject: Јединица @UNIT@ је завршила са поновним учитавањем свог подешавања -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Јединица @UNIT@ је завршила са поновним учитавањем свог подешавања - -Исход је @RESULT@. - --- 641257651c1b4ec9a8624d7a40a9e1e7 -Subject: Процес @EXECUTABLE@ није могао бити извршен -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Процес @EXECUTABLE@ није могао бити извршен и пукао је. - -Овај процес је вратио број грешке @ERRNO@. - --- 0027229ca0644181a76c4e92458afa2e -Subject: Једна или више порука није могло бити прослеђено системском записнику -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Једна или више порука није могло бити прослеђено „syslog“ услузи -која ради упоредно са журнал-деом. Ово обично значи да спроведена -„syslog“ услуга није могла да издржи брзину свих надолазећих -порука у реду. - --- 1dee0369c7fc4736b7099b38ecb46ee7 -Subject: Тачка качења није празна -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Директоријум @WHERE@ је наведен као тачка качења (друго поље у -/etc/fstab датотеци или у „Where=“ пољу систем-де јединичне датотеке) -и он није празан. Ово не утиче на качење али ће већ постојеће датотеке у -овом директоријуму постати недоступне. Да бисте видели ове недоступне -датотеке, ручно прикачите основни систем датотека у другу -путању. - --- 24d8d4452573402496068381a6312df2 -Subject: Виртуелна машина или контејнер је покренут(а) -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Виртуелна машина @NAME@ са водећим ПИБ-ом @LEADER@ је -покренута и сада је спремна за коришћење. - --- 58432bd3bace477cb514b56381b8a758 -Subject: Виртуелна машина или контејнер је окончан(а) -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Виртуелна машина @NAME@ са водећим ПИБ-ом @LEADER@ је -угашена. diff --git a/catalog/systemd.zh_CN.catalog b/catalog/systemd.zh_CN.catalog deleted file mode 100644 index ed59fc9250..0000000000 --- a/catalog/systemd.zh_CN.catalog +++ /dev/null @@ -1,253 +0,0 @@ -# This file is part of systemd. -# -# Copyright 2012 Lennart Poettering -# Copyright 2015 Boyuan Yang -# -# 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 . - -# Message catalog for systemd's own messages -# Simplified Chinese translation - -# 本 catalog 文档格式被记载在 -# http://www.freedesktop.org/wiki/Software/systemd/catalog - -# 如需了解我们为什么做这些工作,请见 https://xkcd.com/1024/ - --- f77379a8490b408bbe5f6940505a777b -Subject: 日志已开始 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -系统日志进程已启动,已打开供写入的日志文件并准备好处理请求。 - --- d93fb3c9c24d451a97cea615ce59c00b -Subject: 日志已停止 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -系统日志进程已终止,并已关闭所有当前活动的日志文件。 - --- a596d6fe7bfa4994828e72309e95d61e -Subject: 由某个服务而来的消息已被抑制 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:journald.conf(5) - -某个服务在一个时间周期内记录了太多消息。 -从该服务而来的消息已被丢弃。 - -请注意只有由有问题的服务传来的消息被丢弃, -其它服务的消息不受影响。 - -可以在 /etc/systemd/journald.conf 中设定 RateLimitIntervalSec= -以及 RateLimitBurst = 的值以控制丢弃信息的限制。 -请参见 journald.conf(5) 以了解详情。 - --- e9bf28e6e834481bb6f48f548ad13606 -Subject: 日志消息已遗失 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -因日志系统对内核消息的处理速度不够快, -部分信息已经遗失。 - --- fc2e22bc6ee647b6b90729ab34a250b1 -Subject: 进程 @COREDUMP_PID@ (@COREDUMP_COMM@) 核心已转储 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:core(5) - -进程 @COREDUMP_PID@ (@COREDUMP_COMM@) 已崩溃并进行核心转储。 - -这通常意味着崩溃程序中存在编程错误,并应当将此错误向其开发者报告。 - --- 8d45620c1a4348dbb17410da57c60c66 -Subject: 一个新会话 @SESSION_ID@ 已为用户 @USER_ID@ 建立 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -一个 ID 为 @SESSION_ID@ 的新会话已为用户 @USER_ID@ 建立。 - -该会话的首进程为 @LEADER@。 - --- 3354939424b4456d9802ca8333ed424a -Subject: 会话 @SESSION_ID@ 已终止 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -一个 ID 为 @SESSION_ID@ 的会话已终止。 - --- fcbefc5da23d428093f97c82a9290f7b -Subject: 一个新的座位 @SEAT_ID@ 可用 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -一个新的座位 @SEAT_ID@ 已被配置并已可用。 - --- e7852bfe46784ed0accde04bc864c2d5 -Subject: 座位 @SEAT_ID@ 已被移除 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -座位 @SEAT_ID@ 已被移除并不再可用。 - --- c7a787079b354eaaa9e77b371893cd27 -Subject: 时间已变更 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -系统时钟已变更为1970年1月1日后 @REALTIME@ 微秒。 - --- 45f82f4aef7a4bbf942ce861d1f20990 -Subject: 时区变更为 @TIMEZONE@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -系统时区已变更为 @TIMEZONE@。 - --- b07a249cd024414a82dd00cd181378ff -Subject: 系统启动已完成 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -所有系统启动时需要的系统服务均已成功启动。 -请注意这并不代表现在机器已经空闲,因为某些服务可能仍处于完成启动的过程中。 - -内核启动使用了 @KERNEL_USEC@ 毫秒。 - -初始内存盘启动使用了 @INITRD_USEC@ 毫秒。 - -用户空间启动使用了 @USERSPACE_USEC@ 毫秒。 - --- 6bbd95ee977941e497c48be27c254128 -Subject: 系统已进入 @SLEEP@ 睡眠状态 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-deve - -系统现已进入 @SLEEP@ 睡眠状态。 - --- 8811e6df2a8e40f58a94cea26f8ebf14 -Subject: 系统已离开 @SLEEP@ 睡眠状态 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -系统现已离开 @SLEEP@ 睡眠状态。 - --- 98268866d1d54a499c4e98921d93bc40 -Subject: 系统关机已开始 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -系统关机操作已初始化。 -关机已开始,所有系统服务均已结束,所有文件系统已卸载。 - --- 7d4958e842da4a758f6c1cdc7b36dcc5 -Subject: @UNIT@ 单元已开始启动 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@UNIT@ 单元已开始启动。 - --- 39f53479d3a045ac8e11786248231fbf -Subject: @UNIT@ 单元已结束启动 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@UNIT@ 单元已结束启动。 - -启动结果为“@RESULT@”。 - --- de5b426a63be47a7b6ac3eaac82e2f6f -Subject: @UNIT@ 单元已开始停止操作 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@UNIT@ 单元已开始停止操作。 - --- 9d1aaa27d60140bd96365438aad20286 -Subject: @UNIT@ 单元已结束停止操作 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@UNIT@ 单元已结束停止操作。 - --- be02cf6855d2428ba40df7e9d022f03d -Subject: @UNIT@ 单元已失败 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@UNIT@ 单元已失败。 - -结果为“@RESULT@”。 - --- d34d037fff1847e6ae669a370e694725 -Subject: @UNIT@ 单元已开始重新载入其配置 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@UNIT@ 单元已开始重新载入其配置。 - --- 7b05ebc668384222baa8881179cfda54 -Subject: @UNIT@ 单元已结束配置重载入 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -@UNIT@ 单元已结束配置重载入操作。 - -结果为“@RESULT@”。 - --- 641257651c1b4ec9a8624d7a40a9e1e7 -Subject: 进程 @EXECUTABLE@ 无法执行 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -进程 @EXECUTABLE@ 无法被执行并已失败。 - -该进程返回的错误代码为 @ERRNO@。 - --- 0027229ca0644181a76c4e92458afa2e -Subject: 一个或更多消息无法被转发至 syslog -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -有一条或更多的消息无法被转发至与 journald 同时运行的 syslog 服务。 -这通常意味着 syslog 实现无法跟上队列中消息进入的速度。 - --- 1dee0369c7fc4736b7099b38ecb46ee7 -Subject: 挂载点不为空 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -目录 @WHERE@ 被指定为挂载点(即 /etc/fstab 文件的第二栏,或 systemd 单元 -文件的 Where= 字段),且该目录非空。 -这并不会影响挂载行为,但该目录中先前已存在的文件将无法被访问。 -如需查看这些文件,请手动将其下的文件系统挂载到另一个位置。 - --- 24d8d4452573402496068381a6312df2 -Subject: 一个虚拟机或容器已启动 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -虚拟机 @NAME@,以及其首进程 PID @LEADER@,已被启动并可被使用。 - --- 58432bd3bace477cb514b56381b8a758 -Subject: 一个虚拟机或容器已被终止 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -虚拟机 @NAME@,以及其首进程 PID @LEADER@,已被关闭并停止。 diff --git a/catalog/systemd.zh_TW.catalog b/catalog/systemd.zh_TW.catalog deleted file mode 100644 index aa5004db08..0000000000 --- a/catalog/systemd.zh_TW.catalog +++ /dev/null @@ -1,263 +0,0 @@ -# This file is part of systemd. -# -# Copyright 2012 Lennart Poettering -# Copyright 2015 Jeff Huang -# -# 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 . - -# Message catalog for systemd's own messages -# Traditional Chinese translation - -# Catalog 的格式記錄於 -# http://www.freedesktop.org/wiki/Software/systemd/catalog - -# For an explanation why we do all this, see https://xkcd.com/1024/ - --- f77379a8490b408bbe5f6940505a777b -Subject: 日誌已開始 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -系統日誌行程已啟動,已開啟日誌 -檔案供寫入並準備好對行程的要求做出回應。 - --- d93fb3c9c24d451a97cea615ce59c00b -Subject: 日誌已停止 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -系統日誌行程已關閉,且關閉所有目前 -活躍的日誌檔案。 - --- a596d6fe7bfa4994828e72309e95d61e -Subject: 從服務而來的訊息已被抑制 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:journald.conf(5) - -有一個服務在一個時間週期內記錄了太多訊息。 -從該服務而來的訊息已被丟棄。 - -注意,只有有問題的服務之訊息被丟棄, -其他服務的訊息則不受影響。 - -可以在 /etc/systemd/journald.conf 中設定 -RateLimitIntervalSec= 以及 RateLimitBurst= -來控制當訊息要開始被丟棄時的限制。參見 journald.conf(5) 以獲得更多資訊。 - --- e9bf28e6e834481bb6f48f548ad13606 -Subject: 日誌訊息已遺失 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -因日誌系統對核心訊息的處理不夠快速, -部份訊息已遺失。 - --- fc2e22bc6ee647b6b90729ab34a250b1 -Subject: 行程 @COREDUMP_PID@ (@COREDUMP_COMM@) 核心傾印 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: man:core(5) - -行程 @COREDUMP_PID@ (@COREDUMP_COMM@) 當掉並核心傾印。 - -這通常代表了在當掉的程式中的一個程式錯誤 -並需要回報錯誤給其開發者。 - --- 8d45620c1a4348dbb17410da57c60c66 -Subject: 新的工作階段 @SESSION_ID@ 已為使用者 @USER_ID@ 建立 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -一個新的工作階段,ID @SESSION_ID@ 已為使用者 @USER_ID@ 建立。 - -這個工作階段的領導行程為 @LEADER@。 - --- 3354939424b4456d9802ca8333ed424a -Subject: 工作階段 @SESSION_ID@ 已結束 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -一個工作階段,ID @SESSION_ID@ 已結束。 - --- fcbefc5da23d428093f97c82a9290f7b -Subject: 新的座位 @SEAT_ID@ 可用 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -一個新的座位 @SEAT_ID@ 已被設定且現在可用。 - --- e7852bfe46784ed0accde04bc864c2d5 -Subject: 座位 @SEAT_ID@ 已被移除 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat - -座位 @SEAT_ID@ 已被移除且不再可用。 - --- c7a787079b354eaaa9e77b371893cd27 -Subject: 時間變更 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -系統時間已變更為1970年1月1日後 @REALTIME@ 微秒。 - --- 45f82f4aef7a4bbf942ce861d1f20990 -Subject: 時區變更為 @TIMEZONE@ -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -系統時區已變更為 @TIMEZONE@。 - --- b07a249cd024414a82dd00cd181378ff -Subject: 系統啟動已完成 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -所有開機所必要的系統服務都已成功啟動。 -注意這並不代表這臺機器有空閒的時間 -可以服務,可能仍忙於完成啟動。 - -核心啟動需要 @KERNEL_USEC@ 微秒。 - -初始 RAM 磁碟啟動需要 @INITRD_USEC@ 微秒。 - -使用者空間啟動需要 @USERSPACE_USEC@ 微秒。 - --- 6bbd95ee977941e497c48be27c254128 -Subject: 系統進入 @SLEEP@ 睡眠狀態 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -系統現在已進入 @SLEEP@ 睡眠狀態。 - --- 8811e6df2a8e40f58a94cea26f8ebf14 -Subject: 系統離開 @SLEEP@ 睡眠狀態 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -系統現在已離開 @SLEEP@ 睡眠狀態。 - --- 98268866d1d54a499c4e98921d93bc40 -Subject: 系統關機開始 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -Systemd 關閉已經開始。關閉已開始且所有系統服務 -都已結束,所有的檔案系統也都已被卸載。 - --- 7d4958e842da4a758f6c1cdc7b36dcc5 -Subject: 單位 @UNIT@ 已開始啟動 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -單位 @UNIT@ 已開始啟動。 - --- 39f53479d3a045ac8e11786248231fbf -Subject: 單位 @UNIT@ 啟動已結束 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -單位 @UNIT@ 啟動已結束。 - -啟動結果為 @RESULT@。 - --- de5b426a63be47a7b6ac3eaac82e2f6f -Subject: 單位 @UNIT@ 已開始關閉 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -單位 @UNIT@ 已開始關閉。 - --- 9d1aaa27d60140bd96365438aad20286 -Subject: 單位 @UNIT@ 已關閉結束 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -單位 @UNIT@ 已關閉結束。 - --- be02cf6855d2428ba40df7e9d022f03d -Subject: 單位 @UNIT@ 已失敗 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -單位 @UNIT@ 已失敗。 - -結果為 @RESULT@。 - --- d34d037fff1847e6ae669a370e694725 -Subject: 單位 @UNIT@ 已開始重新載入其設定 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -單位 @UNIT@ 已開始重新載入其設定 - --- 7b05ebc668384222baa8881179cfda54 -Subject: 單位 @UNIT@ 已結束重新載入其設定 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -單位 @UNIT@ 已結束重新載入其設定 - -結果為 @RESULT@。 - --- 641257651c1b4ec9a8624d7a40a9e1e7 -Subject: 行程 @EXECUTABLE@ 無法執行 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -行程 @EXECUTABLE@ 無法執行且失敗。 - -由該行程所回傳的錯誤碼為 @ERRNO@。 - --- 0027229ca0644181a76c4e92458afa2e -Subject: 一個或更多訊息無法被轉發到 syslog -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -一個或更多訊息無法被轉發到 syslog 服務 -以及並行執行的 journald。這通常代表著 -syslog 實作並無未跟上佇列中訊息 -的速度。 - --- 1dee0369c7fc4736b7099b38ecb46ee7 -Subject: 掛載點不為空 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -目錄 @WHERE@ 被指定為掛載點(在 /etc/fstab 中的 -第二欄或是在 systemd 單位檔案中的 Where= 欄位)且其不為空。 -這並不會干擾掛載,但在此目錄中已存在的檔案 -會變成無法存取的狀態。要檢視這些 over-mounted 的檔案, -請手動掛載下面的檔案系統到次要 -位置。 - --- 24d8d4452573402496068381a6312df2 -Subject: 虛擬機器或容器已啟動 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -虛擬機器 @NAME@ 包含它的領導 PID @LEADER@ 現在 -已經開始並已經可以使用。 - --- 58432bd3bace477cb514b56381b8a758 -Subject: 虛擬機器或容器已結束 -Defined-By: systemd -Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - -虛擬機器 @NAME@ 包含它的領導 PID @LEADER@ 已經 -關閉。 diff --git a/config.mk.in b/config.mk.in new file mode 100644 index 0000000000..82c9123194 --- /dev/null +++ b/config.mk.in @@ -0,0 +1,77 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +# Dirs of external packages +dbuspolicydir=@dbuspolicydir@ +dbussessionservicedir=@dbussessionservicedir@ +dbussystemservicedir=@dbussystemservicedir@ +pamlibdir=@pamlibdir@ +pamconfdir=@pamconfdir@ +pkgconfigdatadir=$(datadir)/pkgconfig +pkgconfiglibdir=$(libdir)/pkgconfig +polkitpolicydir=$(datadir)/polkit-1/actions +bashcompletiondir=@bashcompletiondir@ +zshcompletiondir=@zshcompletiondir@ +rpmmacrosdir=$(prefix)/lib/rpm/macros.d +sysvinitdir=$(SYSTEM_SYSVINIT_PATH) +sysvrcnddir=$(SYSTEM_SYSVRCND_PATH) +varlogdir=$(localstatedir)/log +systemdstatedir=$(localstatedir)/lib/systemd +catalogstatedir=$(systemdstatedir)/catalog +xinitrcdir=$(sysconfdir)/X11/xinit/xinitrc.d + +# Our own, non-special dirs +pkgsysconfdir=$(sysconfdir)/systemd +userunitdir=$(prefix)/lib/systemd/user +userpresetdir=$(prefix)/lib/systemd/user-preset +tmpfilesdir=$(prefix)/lib/tmpfiles.d +sysusersdir=$(prefix)/lib/sysusers.d +sysctldir=$(prefix)/lib/sysctl.d +binfmtdir=$(prefix)/lib/binfmt.d +modulesloaddir=$(prefix)/lib/modules-load.d +networkdir=$(prefix)/lib/systemd/network +pkgincludedir=$(includedir)/systemd +systemgeneratordir=$(libexecdir)/system-generators +usergeneratordir=$(prefix)/lib/systemd/user-generators +systemshutdowndir=$(libexecdir)/system-shutdown +systemsleepdir=$(libexecdir)/system-sleep +systemunitdir=$(prefix)/lib/systemd/system +systempresetdir=$(prefix)/lib/systemd/system-preset +udevlibexecdir=$(prefix)/lib/udev +udevhomedir=$(udevlibexecdir) +udevrulesdir=$(udevlibexecdir)/rules.d +udevhwdbdir=$(udevlibexecdir)/hwdb.d +catalogdir=$(prefix)/lib/systemd/catalog +kernelinstalldir = $(prefix)/lib/kernel/install.d +factory_etcdir = $(datadir)/factory/etc +factory_pamdir = $(datadir)/factory/etc/pam.d +bootlibdir = $(prefix)/lib/systemd/boot/efi + +# And these are the special ones for / +prefix=@prefix@ +bindir=$(prefix)/bin +libexecdir=$(prefix)/lib/systemd + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/discard.mk b/discard.mk new file mode 100644 index 0000000000..3faf228b6c --- /dev/null +++ b/discard.mk @@ -0,0 +1,1195 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +EXTRA_DIST = +BUILT_SOURCES = +INSTALL_EXEC_HOOKS = +UNINSTALL_EXEC_HOOKS = +INSTALL_DATA_HOOKS = +UNINSTALL_DATA_HOOKS = +DISTCLEAN_LOCAL_HOOKS = +CLEAN_LOCAL_HOOKS = +pkginclude_HEADERS = +noinst_LTLIBRARIES = +lib_LTLIBRARIES = +include_HEADERS = +noinst_DATA = +pkgconfigdata_DATA = +pkgconfiglib_DATA = +polkitpolicy_in_in_files = +polkitpolicy_in_files = +polkitpolicy_files = +dist_udevrules_DATA = +nodist_udevrules_DATA = +dist_pkgsysconf_DATA = +nodist_pkgsysconf_DATA = +dist_dbuspolicy_DATA = +dist_dbussystemservice_DATA = +dist_systemunit_DATA_busnames = +dist_sysusers_DATA = +check_PROGRAMS = +check_DATA = +tests= +manual_tests = +TEST_EXTENSIONS = .py +PY_LOG_COMPILER = $(PYTHON) +DISABLE_HARD_ERRORS = yes +ifneq ($(ENABLE_TESTS),) +noinst_PROGRAMS = $(manual_tests) $(tests) $(unsafe_tests) +TESTS = $(tests) +ifneq ($(ENABLE_UNSAFE_TESTS),) +TESTS += \ + $(unsafe_tests) +endif +else +noinst_PROGRAMS = +TESTS = +endif # ENABLE_TESTS +ifneq ($(ENABLE_BASH_COMPLETION),) +dist_bashcompletion_DATA = $(dist_bashcompletion_data) +nodist_bashcompletion_DATA = $(nodist_bashcompletion_data) +endif # ENABLE_BASH_COMPLETION +ifneq ($(ENABLE_ZSH_COMPLETION),) +dist_zshcompletion_DATA = $(dist_zshcompletion_data) +nodist_zshcompletion_DATA = $(nodist_zshcompletion_data) +endif # ENABLE_ZSH_COMPLETION +udevlibexec_PROGRAMS = +gperf_gperf_sources = + +in_files = $(filter %.in,$(EXTRA_DIST)) +in_in_files = $(filter %.in.in, $(in_files)) +m4_files = $(filter %.m4,$(EXTRA_DIST) $(in_files:.m4.in=.m4)) + +CLEANFILES = $(BUILT_SOURCES) \ + $(pkgconfigdata_DATA) \ + $(pkgconfiglib_DATA) \ + $(nodist_bashcompletion_data) \ + $(nodist_zshcompletion_data) \ + $(in_files:.in=) $(in_in_files:.in.in=) \ + $(m4_files:.m4=) + +.PHONY: $(INSTALL_EXEC_HOOKS) $(UNINSTALL_EXEC_HOOKS) \ + $(INSTALL_DATA_HOOKS) $(UNINSTALL_DATA_HOOKS) \ + $(DISTCLEAN_LOCAL_HOOKS) $(CLEAN_LOCAL_HOOKS) + +AM_CPPFLAGS = \ + -include $(top_builddir)/config.h \ + -DPKGSYSCONFDIR=\"$(pkgsysconfdir)\" \ + -DSYSTEM_CONFIG_UNIT_PATH=\"$(pkgsysconfdir)/system\" \ + -DSYSTEM_DATA_UNIT_PATH=\"$(systemunitdir)\" \ + -DSYSTEM_SYSVINIT_PATH=\"$(SYSTEM_SYSVINIT_PATH)\" \ + -DSYSTEM_SYSVRCND_PATH=\"$(SYSTEM_SYSVRCND_PATH)\" \ + -DUSER_CONFIG_UNIT_PATH=\"$(pkgsysconfdir)/user\" \ + -DUSER_DATA_UNIT_PATH=\"$(userunitdir)\" \ + -DCERTIFICATE_ROOT=\"$(CERTIFICATEROOT)\" \ + -DCATALOG_DATABASE=\"$(catalogstatedir)/database\" \ + -DSYSTEMD_CGROUP_AGENT_PATH=\"$(libexecdir)/systemd-cgroups-agent\" \ + -DSYSTEMD_BINARY_PATH=\"$(libexecdir)/systemd\" \ + -DSYSTEMD_FSCK_PATH=\"$(libexecdir)/systemd-fsck\" \ + -DSYSTEMD_SHUTDOWN_BINARY_PATH=\"$(libexecdir)/systemd-shutdown\" \ + -DSYSTEMD_SLEEP_BINARY_PATH=\"$(libexecdir)/systemd-sleep\" \ + -DSYSTEMCTL_BINARY_PATH=\"$(bindir)/systemctl\" \ + -DSYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH=\"$(bindir)/systemd-tty-ask-password-agent\" \ + -DSYSTEMD_STDIO_BRIDGE_BINARY_PATH=\"$(bindir)/systemd-stdio-bridge\" \ + -DROOTPREFIX=\"$(prefix)\" \ + -DRANDOM_SEED_DIR=\"$(localstatedir)/lib/systemd/\" \ + -DRANDOM_SEED=\"$(localstatedir)/lib/systemd/random-seed\" \ + -DSYSTEMD_CRYPTSETUP_PATH=\"$(libexecdir)/systemd-cryptsetup\" \ + -DSYSTEM_GENERATOR_PATH=\"$(systemgeneratordir)\" \ + -DUSER_GENERATOR_PATH=\"$(usergeneratordir)\" \ + -DSYSTEM_SHUTDOWN_PATH=\"$(systemshutdowndir)\" \ + -DSYSTEM_SLEEP_PATH=\"$(systemsleepdir)\" \ + -DSYSTEMD_KBD_MODEL_MAP=\"$(pkgdatadir)/kbd-model-map\" \ + -DSYSTEMD_LANGUAGE_FALLBACK_MAP=\"$(pkgdatadir)/language-fallback-map\" \ + -DUDEVLIBEXECDIR=\"$(udevlibexecdir)\" \ + -DPOLKIT_AGENT_BINARY_PATH=\"$(bindir)/pkttyagent\" \ + -DQUOTACHECK=\"$(QUOTACHECK)\" \ + -DKEXEC=\"$(KEXEC)\" \ + -DMOUNT_PATH=\"$(MOUNT_PATH)\" \ + -DUMOUNT_PATH=\"$(UMOUNT_PATH)\" \ + -DLIBDIR=\"$(libdir)\" \ + -DROOTLIBDIR=\"$(libdir)\" \ + -DROOTLIBEXECDIR=\"$(libexecdir)\" \ + -DTEST_DIR=\"$(abs_top_srcdir)/test\" \ + -I $(top_srcdir)/src \ + -I $(top_builddir)/src/basic \ + -I $(top_srcdir)/src/basic \ + -I $(top_srcdir)/src/shared \ + -I $(top_builddir)/src/shared \ + -I $(top_srcdir)/src/network \ + -I $(top_srcdir)/src/login \ + -I $(top_srcdir)/src/journal \ + -I $(top_builddir)/src/journal \ + -I $(top_srcdir)/src/timedate \ + -I $(top_srcdir)/src/timesync \ + -I $(top_srcdir)/src/nspawn \ + -I $(top_srcdir)/src/resolve \ + -I $(top_builddir)/src/resolve \ + -I $(top_srcdir)/src/systemd \ + -I $(top_builddir)/src/core \ + -I $(top_srcdir)/src/core \ + -I $(top_srcdir)/src/libudev \ + -I $(top_srcdir)/src/udev \ + -I $(top_srcdir)/src/udev/net \ + -I $(top_builddir)/src/udev \ + -I $(top_srcdir)/src/libsystemd/sd-bus \ + -I $(top_srcdir)/src/libsystemd/sd-event \ + -I $(top_srcdir)/src/libsystemd/sd-login \ + -I $(top_srcdir)/src/libsystemd/sd-netlink \ + -I $(top_srcdir)/src/libsystemd/sd-network \ + -I $(top_srcdir)/src/libsystemd/sd-hwdb \ + -I $(top_srcdir)/src/libsystemd/sd-device \ + -I $(top_srcdir)/src/libsystemd-network \ + $(OUR_CPPFLAGS) + +AM_CFLAGS = $(OUR_CFLAGS) +AM_LDFLAGS = $(OUR_LDFLAGS) + +# ------------------------------------------------------------------------------ +define move-to-libdir + if test "$(libdir)" != "$(libdir)"; then \ + $(MKDIR_P) $(DESTDIR)$(libdir) && \ + so_img_name=$$(readlink $(DESTDIR)$(libdir)/$$libname) && \ + rm -f $(DESTDIR)$(libdir)/$$libname && \ + $(LN_S) --relative -f $(DESTDIR)$(libdir)/$$so_img_name $(DESTDIR)$(libdir)/$$libname && \ + mv $(DESTDIR)$(libdir)/$$libname.* $(DESTDIR)$(libdir); \ + fi +endef + +INSTALL_DIRS = + +SHUTDOWN_TARGET_WANTS = +LOCAL_FS_TARGET_WANTS = +MULTI_USER_TARGET_WANTS = +GRAPHICAL_TARGET_WANTS = +RESCUE_TARGET_WANTS = +SYSINIT_TARGET_WANTS = +SOCKETS_TARGET_WANTS = +BUSNAMES_TARGET_WANTS = +TIMERS_TARGET_WANTS = +USER_SOCKETS_TARGET_WANTS = +USER_DEFAULT_TARGET_WANTS = +USER_BUSNAMES_TARGET_WANTS = + +SYSTEM_UNIT_ALIASES = +USER_UNIT_ALIASES = +GENERAL_ALIASES = + +install-target-wants-hook: + what="$(SHUTDOWN_TARGET_WANTS)" && wants=shutdown.target && dir=$(systemunitdir) && $(add-wants) + what="$(LOCAL_FS_TARGET_WANTS)" && wants=local-fs.target && dir=$(systemunitdir) && $(add-wants) + what="$(MULTI_USER_TARGET_WANTS)" && wants=multi-user.target && dir=$(systemunitdir) && $(add-wants) + what="$(GRAPHICAL_TARGET_WANTS)" && wants=graphical.target && dir=$(systemunitdir) && $(add-wants) + what="$(RESCUE_TARGET_WANTS)" && wants=rescue.target && dir=$(systemunitdir) && $(add-wants) + what="$(SYSINIT_TARGET_WANTS)" && wants=sysinit.target && dir=$(systemunitdir) && $(add-wants) + what="$(SOCKETS_TARGET_WANTS)" && wants=sockets.target && dir=$(systemunitdir) && $(add-wants) + what="$(TIMERS_TARGET_WANTS)" && wants=timers.target && dir=$(systemunitdir) && $(add-wants) + what="$(SLICES_TARGET_WANTS)" && wants=slices.target && dir=$(systemunitdir) && $(add-wants) + what="$(USER_SOCKETS_TARGET_WANTS)" && wants=sockets.target && dir=$(userunitdir) && $(add-wants) + what="$(USER_DEFAULT_TARGET_WANTS)" && wants=default.target && dir=$(userunitdir) && $(add-wants) + +install-busnames-target-wants-hook: + what="$(BUSNAMES_TARGET_WANTS)" && wants=busnames.target && dir=$(systemunitdir) && $(add-wants) + what="$(USER_BUSNAMES_TARGET_WANTS)" && wants=busnames.target && dir=$(userunitdir) && $(add-wants) + +define add-wants + [ -z "$$what" ] || ( \ + dir=$(DESTDIR)$$dir/$$wants.wants && \ + $(MKDIR_P) -m 0755 $$dir && \ + cd $$dir && \ + rm -f $$what && \ + for i in $$what; do $(LN_S) ../$$i . || exit $$? ; done ) +endef + +install-directories-hook: + $(MKDIR_P) $(addprefix $(DESTDIR),$(INSTALL_DIRS)) + +install-aliases-hook: + set -- $(SYSTEM_UNIT_ALIASES) && \ + dir=$(systemunitdir) && $(install-aliases) + set -- $(USER_UNIT_ALIASES) && \ + dir=$(userunitdir) && $(install-relative-aliases) + set -- $(GENERAL_ALIASES) && \ + dir= && $(install-relative-aliases) + +define install-aliases + while [ -n "$$1" ]; do \ + $(MKDIR_P) `dirname $(DESTDIR)$$dir/$$2` && \ + rm -f $(DESTDIR)$$dir/$$2 && \ + $(LN_S) $$1 $(DESTDIR)$$dir/$$2 && \ + shift 2 || exit $$?; \ + done +endef + +define install-relative-aliases + while [ -n "$$1" ]; do \ + $(MKDIR_P) `dirname $(DESTDIR)$$dir/$$2` && \ + rm -f $(DESTDIR)$$dir/$$2 && \ + $(LN_S) --relative $(DESTDIR)$$1 $(DESTDIR)$$dir/$$2 && \ + shift 2 || exit $$?; \ + done +endef + +install-touch-usr-hook: + touch -c $(DESTDIR)/$(prefix) + +INSTALL_EXEC_HOOKS += \ + install-target-wants-hook \ + install-directories-hook \ + install-aliases-hook \ + install-touch-usr-hook + +INSTALL_EXEC_HOOKS += \ + install-busnames-target-wants-hook + +bin_PROGRAMS = \ + systemctl \ + systemd-notify \ + systemd-ask-password \ + systemd-tty-ask-password-agent \ + systemd-machine-id-setup \ + systemd-escape + +bin_PROGRAMS = \ + systemd-cgls \ + systemd-cgtop \ + systemd-nspawn \ + systemd-detect-virt \ + systemd-delta \ + systemd-analyze \ + systemd-run \ + systemd-stdio-bridge \ + systemd-path + +libexec_PROGRAMS = \ + systemd \ + systemd-cgroups-agent \ + systemd-initctl \ + systemd-shutdown \ + systemd-remount-fs \ + systemd-reply-password \ + systemd-fsck \ + systemd-ac-power \ + systemd-sysctl \ + systemd-sleep \ + systemd-socket-proxyd \ + systemd-update-done + +ifneq ($(HAVE_UTMP),) +libexec_PROGRAMS += \ + systemd-update-utmp +endif # HAVE_UTMP + +systemgenerator_PROGRAMS = \ + systemd-getty-generator \ + systemd-fstab-generator \ + systemd-system-update-generator \ + systemd-debug-generator + +dist_bashcompletion_data = \ + shell-completion/bash/busctl \ + shell-completion/bash/journalctl \ + shell-completion/bash/systemd-analyze \ + shell-completion/bash/systemd-cat \ + shell-completion/bash/systemd-cgls \ + shell-completion/bash/systemd-cgtop \ + shell-completion/bash/systemd-delta \ + shell-completion/bash/systemd-detect-virt \ + shell-completion/bash/systemd-nspawn \ + shell-completion/bash/systemd-path \ + shell-completion/bash/systemd-run \ + shell-completion/bash/udevadm \ + shell-completion/bash/kernel-install + +nodist_bashcompletion_data = \ + shell-completion/bash/systemctl + +dist_zshcompletion_data = \ + shell-completion/zsh/_busctl \ + shell-completion/zsh/_journalctl \ + shell-completion/zsh/_udevadm \ + shell-completion/zsh/_kernel-install \ + shell-completion/zsh/_systemd-nspawn \ + shell-completion/zsh/_systemd-analyze \ + shell-completion/zsh/_systemd-run \ + shell-completion/zsh/_sd_hosts_or_user_at_host \ + shell-completion/zsh/_sd_outputmodes \ + shell-completion/zsh/_sd_unit_files \ + shell-completion/zsh/_systemd-delta \ + shell-completion/zsh/_systemd + +nodist_zshcompletion_data = \ + shell-completion/zsh/_systemctl + +EXTRA_DIST += \ + shell-completion/bash/systemctl.in \ + shell-completion/zsh/_systemctl.in + +dist_sysctl_DATA = \ + sysctl.d/50-default.conf + +dist_systemunit_DATA = \ + units/graphical.target \ + units/multi-user.target \ + units/emergency.target \ + units/sysinit.target \ + units/basic.target \ + units/getty.target \ + units/halt.target \ + units/kexec.target \ + units/exit.target \ + units/local-fs.target \ + units/local-fs-pre.target \ + units/initrd.target \ + units/initrd-fs.target \ + units/initrd-root-device.target \ + units/initrd-root-fs.target \ + units/remote-fs.target \ + units/remote-fs-pre.target \ + units/network.target \ + units/network-pre.target \ + units/network-online.target \ + units/nss-lookup.target \ + units/nss-user-lookup.target \ + units/poweroff.target \ + units/reboot.target \ + units/rescue.target \ + units/rpcbind.target \ + units/time-sync.target \ + units/shutdown.target \ + units/final.target \ + units/umount.target \ + units/sigpwr.target \ + units/sleep.target \ + units/sockets.target \ + units/timers.target \ + units/paths.target \ + units/suspend.target \ + units/swap.target \ + units/slices.target \ + units/system.slice \ + units/x-.slice \ + units/systemd-initctl.socket \ + units/syslog.socket \ + units/dev-hugepages.mount \ + units/dev-mqueue.mount \ + units/sys-kernel-config.mount \ + units/sys-kernel-debug.mount \ + units/sys-fs-fuse-connections.mount \ + units/tmp.mount \ + units/var-lib-machines.mount \ + units/printer.target \ + units/sound.target \ + units/bluetooth.target \ + units/smartcard.target \ + units/systemd-ask-password-wall.path \ + units/systemd-ask-password-console.path \ + units/systemd-udevd-control.socket \ + units/systemd-udevd-kernel.socket \ + units/system-update.target \ + units/initrd-switch-root.target \ + units/machines.target + +dist_systemunit_DATA += \ + $(dist_systemunit_DATA_busnames) + +dist_systemunit_DATA_busnames += \ + units/busnames.target + +nodist_systemunit_DATA = \ + units/getty@.service \ + units/serial-getty@.service \ + units/console-shell.service \ + units/console-getty.service \ + units/container-getty@.service \ + units/systemd-initctl.service \ + units/systemd-remount-fs.service \ + units/systemd-ask-password-wall.service \ + units/systemd-ask-password-console.service \ + units/systemd-sysctl.service \ + units/emergency.service \ + units/rescue.service \ + units/user@.service \ + units/systemd-suspend.service \ + units/systemd-halt.service \ + units/systemd-poweroff.service \ + units/systemd-reboot.service \ + units/systemd-kexec.service \ + units/systemd-exit.service \ + units/systemd-fsck@.service \ + units/systemd-fsck-root.service \ + units/systemd-machine-id-commit.service \ + units/systemd-udevd.service \ + units/systemd-udev-trigger.service \ + units/systemd-udev-settle.service \ + units/systemd-hwdb-update.service \ + units/debug-shell.service \ + units/initrd-parse-etc.service \ + units/initrd-cleanup.service \ + units/initrd-udevadm-cleanup-db.service \ + units/initrd-switch-root.service \ + units/systemd-nspawn@.service \ + units/systemd-update-done.service + +ifneq ($(HAVE_UTMP),) +nodist_systemunit_DATA += \ + units/systemd-update-utmp.service \ + units/systemd-update-utmp-runlevel.service +endif # HAVE_UTMP + +dist_userunit_DATA = \ + units/user/basic.target \ + units/user/default.target \ + units/user/exit.target + +nodist_userunit_DATA = \ + units/user/systemd-exit.service + +dist_systempreset_DATA = \ + system-preset/90-systemd.preset + +EXTRA_DIST += \ + units/getty@.service.m4 \ + units/serial-getty@.service.m4 \ + units/console-shell.service.m4.in \ + units/console-getty.service.m4.in \ + units/container-getty@.service.m4.in \ + units/rescue.service.in \ + units/systemd-initctl.service.in \ + units/systemd-remount-fs.service.in \ + units/systemd-update-utmp.service.in \ + units/systemd-update-utmp-runlevel.service.in \ + units/systemd-ask-password-wall.service.in \ + units/systemd-ask-password-console.service.in \ + units/systemd-sysctl.service.in \ + units/emergency.service.in \ + units/systemd-halt.service.in \ + units/systemd-poweroff.service.in \ + units/systemd-reboot.service.in \ + units/systemd-kexec.service.in \ + units/systemd-exit.service.in \ + units/user/systemd-exit.service.in \ + units/systemd-fsck@.service.in \ + units/systemd-fsck-root.service.in \ + units/systemd-machine-id-commit.service.in \ + units/user@.service.m4.in \ + units/debug-shell.service.in \ + units/systemd-suspend.service.in \ + units/quotaon.service.in \ + units/initrd-parse-etc.service.in \ + units/initrd-cleanup.service.in \ + units/initrd-udevadm-cleanup-db.service.in \ + units/initrd-switch-root.service.in \ + units/systemd-nspawn@.service.in \ + units/systemd-update-done.service.in \ + units/tmp.mount.m4 + +ifneq ($(HAVE_SYSV_COMPAT),) +nodist_systemunit_DATA += \ + units/rc-local.service \ + units/halt-local.service + +systemgenerator_PROGRAMS += \ + systemd-sysv-generator \ + systemd-rc-local-generator +endif # HAVE_SYSV_COMPAT + +EXTRA_DIST += \ + src/systemctl/systemd-sysv-install.SKELETON \ + units/rc-local.service.in \ + units/halt-local.service.in + +# automake is broken and can't handle files with a dash in front +# http://debbugs.gnu.org/cgi/bugreport.cgi?bug=14728#8 +units-install-hook: + mv $(DESTDIR)$(systemunitdir)/x-.slice $(DESTDIR)/$(systemunitdir)/-.slice + +units-uninstall-hook: + rm -f $(DESTDIR)/$(systemunitdir)/-.slice + +INSTALL_DATA_HOOKS += units-install-hook +UNINSTALL_DATA_HOOKS += units-uninstall-hook + +dist_doc_DATA = \ + README \ + NEWS \ + CODING_STYLE \ + LICENSE.LGPL2.1 \ + LICENSE.GPL2 \ + DISTRO_PORTING \ + src/libsystemd/sd-bus/PORTING-DBUS1 \ + src/libsystemd/sd-bus/DIFFERENCES \ + src/libsystemd/sd-bus/GVARIANT-SERIALIZATION + +EXTRA_DIST += \ + README.md \ + autogen.sh \ + .dir-locals.el \ + .editorconfig \ + .vimrc \ + .ycm_extra_conf.py \ + .travis.yml \ + .mailmap + +@INTLTOOL_POLICY_RULE@ + +# ------------------------------------------------------------------------------ + +MANPAGES = +MANPAGES_ALIAS = + +include Makefile-man.am + +.PHONY: man update-man-list +man: $(MANPAGES) $(MANPAGES_ALIAS) $(HTML_FILES) $(HTML_ALIAS) + +XML_FILES = \ + ${patsubst %.1,%.xml,${patsubst %.3,%.xml,${patsubst %.5,%.xml,${patsubst %.7,%.xml,${patsubst %.8,%.xml,$(MANPAGES)}}}}} +HTML_FILES = \ + ${XML_FILES:.xml=.html} +HTML_ALIAS = \ + ${patsubst %.1,%.html,${patsubst %.3,%.html,${patsubst %.5,%.html,${patsubst %.7,%.html,${patsubst %.8,%.html,$(MANPAGES_ALIAS)}}}}} + +ifneq ($(ENABLE_MANPAGES),) +man_MANS = \ + $(MANPAGES) \ + $(MANPAGES_ALIAS) + +noinst_DATA += \ + $(HTML_FILES) \ + $(HTML_ALIAS) \ + docs/html/man +endif # ENABLE_MANPAGES + +CLEANFILES += \ + $(man_MANS) \ + $(HTML_FILES) \ + $(HTML_ALIAS) \ + docs/html/man + +$(outdir)/man: + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_LN)$(LN_S) -f ../../man $@ + +$(outdir)/index.html: man/systemd.index.html + $(AM_V_LN)$(LN_S) -f systemd.index.html $@ + +ifneq ($(HAVE_PYTHON),) +noinst_DATA += \ + man/index.html +endif # HAVE_PYTHON + +CLEANFILES += \ + man/index.html + +XML_GLOB = $(wildcard $(top_srcdir)/man/*.xml) +NON_INDEX_XML_FILES = $(filter-out man/systemd.index.xml,$(XML_FILES)) +SOURCE_XML_FILES = ${patsubst %,$(top_srcdir)/%,$(filter-out man/systemd.directives.xml,$(NON_INDEX_XML_FILES))} + +# This target should only be run manually. It recreates Makefile-man.am +# file in the source directory based on all man/*.xml files. Run it after +# adding, removing, or changing the conditional in a man page. +update-man-list: $(top_srcdir)/tools/make-man-rules.py $(XML_GLOB) man/custom-entities.ent + $(AM_V_GEN)$(PYTHON) $< $(XML_GLOB) > $(top_srcdir)/Makefile-man.tmp + $(AM_V_at)mv $(top_srcdir)/Makefile-man.tmp $(top_srcdir)/Makefile-man.am + @echo "Makefile-man.am has been regenerated" + +$(outdir)/systemd.index.xml: $(top_srcdir)/tools/make-man-index.py $(NON_INDEX_XML_FILES) + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)$(PYTHON) $< $@ $(filter-out $<,$^) + +$(outdir)/systemd.directives.xml: $(top_srcdir)/tools/make-directive-index.py man/custom-entities.ent $(SOURCE_XML_FILES) + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)$(PYTHON) $< $@ $(SOURCE_XML_FILES) + +CLEANFILES += \ + man/systemd.index.xml \ + man/systemd.directives.xml + +EXTRA_DIST += \ + $(filter-out man/systemd.directives.xml man/systemd.index.xml,$(XML_FILES)) \ + tools/make-man-index.py \ + tools/make-man-rules.py \ + tools/make-directive-index.py \ + tools/xml_helper.py \ + man/glib-event-glue.c + +ifneq ($(ENABLE_LDCONFIG),) +dist_systemunit_DATA += \ + units/ldconfig.service + +SYSINIT_TARGET_WANTS += \ + ldconfig.service +endif # ENABLE_LDCONFIG + +gperf_gperf_m4_sources = \ + src/core/load-fragment-gperf.gperf.m4 + +gperf_txt_sources = \ + src/basic/errno-list.txt \ + src/basic/af-list.txt \ + src/basic/arphrd-list.txt \ + src/basic/cap-list.txt + +BUILT_SOURCES += \ + $(gperf_gperf_m4_sources:-gperf.gperf.m4=-gperf.c) \ + $(gperf_gperf_m4_sources:-gperf.gperf.m4=-gperf-nulstr.c) \ + $(gperf_gperf_sources:-gperf.gperf=-gperf.c) \ + $(gperf_txt_sources:-list.txt=-from-name.h) \ + $(filter-out %keyboard-keys-to-name.h,$(gperf_txt_sources:-list.txt=-to-name.h)) + +CLEANFILES += \ + $(gperf_txt_sources:-list.txt=-from-name.gperf) +DISTCLEANFILES = \ + $(gperf_txt_sources) + +EXTRA_DIST += \ + $(gperf_gperf_m4_sources) \ + $(gperf_gperf_sources) + +CLEANFILES += \ + $(gperf_txt_sources) + +## .PHONY so it always rebuilds it +.PHONY: coverage lcov-run lcov-report coverage-sync + +# run lcov from scratch, always +coverage: all + $(MAKE) lcov-run + $(MAKE) lcov-report + +coverage_dir = coverage +coverage_opts = --base-directory $(srcdir) --directory $(builddir) --rc 'geninfo_adjust_src_path=$(abspath $(srcdir))=>$(abspath $(builddir))' + +ifneq ($(ENABLE_COVERAGE),) +# reset run coverage tests +lcov-run: + @rm -rf $(coverage_dir) + lcov $(coverage_opts) --zerocounters + -$(MAKE) check + +# generate report based on current coverage data +lcov-report: + $(MKDIR_P) $(coverage_dir) + lcov $(coverage_opts) --compat-libtool --capture --no-external \ + | sed 's|$(abspath $(builddir))|$(abspath $(srcdir))|' > $(coverage_dir)/.lcov.info + lcov --remove $(coverage_dir)/.lcov.info --output-file $(coverage_dir)/.lcov-clean.info 'test-*' + genhtml -t "systemd test coverage" -o $(coverage_dir) $(coverage_dir)/.lcov-clean.info + @echo "Coverage report generated in $(abs_builddir)/$(coverage_dir)/index.html" + +# lcov doesn't work properly with vpath builds, make sure that bad +# output is not uploaded by mistake. +coverage-sync: coverage + test "$(builddir)" = "$(srcdir)" + rsync -rlv --delete --omit-dir-times coverage/ $(www_target)/coverage + +else +lcov-run lcov-report: + echo "Need to reconfigure with --enable-coverage" +endif # ENABLE_COVERAGE + +dist_factory_etc_DATA = \ + factory/etc/nsswitch.conf + +ifneq ($(HAVE_PAM),) +dist_factory_pam_DATA = \ + factory/etc/pam.d/system-auth \ + factory/etc/pam.d/other +endif # HAVE_PAM + +libsystemd-install-hook: + libname=libsystemd.so && $(move-to-libdir) + +libsystemd-uninstall-hook: + rm -f $(DESTDIR)$(libdir)/libsystemd.so* + +INSTALL_EXEC_HOOKS += libsystemd-install-hook +UNINSTALL_EXEC_HOOKS += libsystemd-uninstall-hook + +# move lib from $(libdir) to $(libdir) and update devel link, if needed +libudev-install-hook: + libname=libudev.so && $(move-to-libdir) + +libudev-uninstall-hook: + rm -f $(DESTDIR)$(libdir)/libudev.so* + +INSTALL_EXEC_HOOKS += libudev-install-hook +UNINSTALL_EXEC_HOOKS += libudev-uninstall-hook + +# ------------------------------------------------------------------------------ +noinst_LTLIBRARIES += \ + libudev-internal.la + +libudev_internal_la_SOURCES =\ + $(libudev_la_SOURCES) + +ifneq ($(ENABLE_TESTS),) +TESTS += \ + test/udev-test.pl + +ifneq ($(HAVE_PYTHON),) +TESTS += \ + test/rule-syntax-check.py + +ifneq ($(HAVE_SYSV_COMPAT),) +TESTS += \ + test/sysv-generator-test.py +endif # HAVE_SYSV_COMPAT +endif # HAVE_PYTHON +endif # ENABLE_TESTS + +tests += \ + test-libudev + +manual_tests += \ + test-udev + +test_libudev_SOURCES = \ + src/test/test-libudev.c + +test_libudev_LDADD = \ + libshared.la + +test_udev_SOURCES = \ + src/test/test-udev.c + +test_udev_LDADD = \ + libudev-core.la \ + $(BLKID_LIBS) \ + $(KMOD_LIBS) + +ifneq ($(ENABLE_TESTS),) +check_DATA += \ + test/sys +endif # ENABLE_TESTS + +# packed sysfs test tree +$(outdir)/sys: test/sys.tar.xz + -rm -rf test/sys + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)tar -C test/ -xJf $(top_srcdir)/test/sys.tar.xz + -touch test/sys + +test-sys-distclean: + -rm -rf test/sys +DISTCLEAN_LOCAL_HOOKS += test-sys-distclean + +EXTRA_DIST += \ + test/sys.tar.xz \ + test/udev-test.pl \ + test/rule-syntax-check.py \ + test/sysv-generator-test.py \ + test/mocks/fsck + +test_nss_SOURCES = \ + src/test/test-nss.c + +test_nss_LDADD = \ + libsystemd-internal.la \ + -ldl + +manual_tests += \ + test-nss + +EXTRA_DIST += \ + test/Makefile \ + test/README.testsuite \ + test/TEST-01-BASIC \ + test/TEST-01-BASIC/Makefile \ + test/TEST-01-BASIC/test.sh \ + test/TEST-02-CRYPTSETUP \ + test/TEST-02-CRYPTSETUP/Makefile \ + test/TEST-02-CRYPTSETUP/test.sh \ + test/TEST-03-JOBS \ + test/TEST-03-JOBS/Makefile \ + test/TEST-03-JOBS/test-jobs.sh \ + test/TEST-03-JOBS/test.sh \ + test/TEST-04-JOURNAL/Makefile \ + test/TEST-04-JOURNAL/test-journal.sh \ + test/TEST-04-JOURNAL/test.sh \ + test/TEST-05-RLIMITS/Makefile \ + test/TEST-05-RLIMITS/test-rlimits.sh \ + test/TEST-05-RLIMITS/test.sh \ + test/TEST-06-SELINUX/Makefile \ + test/TEST-06-SELINUX/test-selinux-checks.sh \ + test/TEST-06-SELINUX/test.sh \ + test/TEST-06-SELINUX/systemd_test.te \ + test/TEST-06-SELINUX/systemd_test.if \ + test/TEST-07-ISSUE-1981/Makefile \ + test/TEST-07-ISSUE-1981/test-segfault.sh \ + test/TEST-07-ISSUE-1981/test.sh \ + test/TEST-08-ISSUE-2730/Makefile \ + test/TEST-08-ISSUE-2730/test.sh \ + test/TEST-09-ISSUE-2691/Makefile \ + test/TEST-09-ISSUE-2691/test.sh \ + test/TEST-10-ISSUE-2467/Makefile \ + test/TEST-10-ISSUE-2467/test.sh \ + test/TEST-11-ISSUE-3166/Makefile \ + test/TEST-11-ISSUE-3166/test.sh \ + test/TEST-12-ISSUE-3171/Makefile \ + test/TEST-12-ISSUE-3171/test.sh \ + test/test-functions + +EXTRA_DIST += \ + test/loopy2.service \ + test/loopy3.service \ + test/loopy4.service \ + test/loopy.service \ + test/loopy.service.d \ + test/loopy.service.d/compat.conf + +$(outdir)/%: units/%.in + $(SED_PROCESS) + +$(outdir)/%: man/%.in + $(SED_PROCESS) + +%.pc: %.pc.in + $(SED_PROCESS) + +%.conf: %.conf.in + $(SED_PROCESS) + +$(outdir)/%.systemd: src/core/%.systemd.in + $(SED_PROCESS) + +$(outdir)/%.policy.in: src/%.policy.in.in + $(SED_PROCESS) + +$(outdir)/%: shell-completion/%.in + $(SED_PROCESS) + +%.rules: %.rules.in + $(SED_PROCESS) + +%.conf: %.conf.in + $(SED_PROCESS) + +$(outdir)/%: sysusers.d/%.m4 $(top_builddir)/config.status + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_M4)$(M4) -P $(M4_DEFINES) < $< > $@ + +$(outdir)/%: tmpfiles.d/%.m4 $(top_builddir)/config.status + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_M4)$(M4) -P $(M4_DEFINES) < $< > $@ + + +$(outdir)/%: units/%.m4 $(top_builddir)/config.status + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_M4)$(M4) -P $(M4_DEFINES) -DFOR_SYSTEM=1 < $< > $@ + +$(outdir)/%: units/user/%.m4 $(top_builddir)/config.status + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_M4)$(M4) -P $(M4_DEFINES) -DFOR_USER=1 < $< > $@ + +ifneq ($(ENABLE_POLKIT),) +nodist_polkitpolicy_DATA = \ + $(polkitpolicy_files) \ + $(polkitpolicy_in_in_files:.policy.in.in=.policy) +endif # ENABLE_POLKIT + +EXTRA_DIST += \ + $(polkitpolicy_in_files) \ + $(polkitpolicy_in_in_files) + +# ------------------------------------------------------------------------------ +$(outdir)/custom-entities.ent: configure.ac + $(AM_V_GEN)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)(echo '' && \ + printf '$(subst '|,\n,$(substitutions))))') \ + > $@ # ' + +CLEANFILES += \ + man/custom-entities.ent + +XSLTPROC_FLAGS = \ + --nonet \ + --xinclude \ + --stringparam man.output.quietly 1 \ + --stringparam funcsynopsis.style ansi \ + --stringparam man.authors.section.enabled 0 \ + --stringparam man.copyright.section.enabled 0 \ + --stringparam systemd.version $(VERSION) \ + --path '$(builddir)/man:$(srcdir)/man' + +XSLT = $(if $(XSLTPROC), $(XSLTPROC), xsltproc) +XSLTPROC_PROCESS_MAN = \ + $(AM_V_XSLT)$(XSLT) -o $@ $(XSLTPROC_FLAGS) $(srcdir)/man/custom-man.xsl $< + +XSLTPROC_PROCESS_HTML = \ + $(AM_V_XSLT)$(XSLT) -o $@ $(XSLTPROC_FLAGS) $(srcdir)/man/custom-html.xsl $< + +$(outdir)/%.1: man/%.xml man/custom-man.xsl man/custom-entities.ent + $(XSLTPROC_PROCESS_MAN) + +$(outdir)/%.3: man/%.xml man/custom-man.xsl man/custom-entities.ent + $(XSLTPROC_PROCESS_MAN) + +$(outdir)/%.5: man/%.xml man/custom-man.xsl man/custom-entities.ent + $(XSLTPROC_PROCESS_MAN) + +$(outdir)/%.7: man/%.xml man/custom-man.xsl man/custom-entities.ent + $(XSLTPROC_PROCESS_MAN) + +$(outdir)/%.8: man/%.xml man/custom-man.xsl man/custom-entities.ent + $(XSLTPROC_PROCESS_MAN) + +$(outdir)/%.html: man/%.xml man/custom-html.xsl man/custom-entities.ent + $(XSLTPROC_PROCESS_HTML) + +define html-alias + $(AM_V_LN)$(LN_S) -f $(notdir $<) $@ +endef + +EXTRA_DIST += \ + man/custom-html.xsl \ + man/custom-man.xsl + +# ------------------------------------------------------------------------------ +ifneq ($(HAVE_SYSV_COMPAT),) +sysvinit_DATA = \ + docs/sysvinit/README + +varlog_DATA = \ + docs/var-log/README + +$(outdir)/README: docs/sysvinit/README.in + $(SED_PROCESS) + +$(outdir)/README: docs/var-log/README.in + $(SED_PROCESS) + +CLEANFILES += \ + docs/sysvinit/README \ + docs/var-log/README +endif # HAVE_SYSV_COMPAT + +EXTRA_DIST += \ + docs/sysvinit/README.in \ + docs/var-log/README.in + +SOCKETS_TARGET_WANTS += \ + systemd-initctl.socket + +ifneq ($(HAVE_UTMP),) +ifneq ($(HAVE_SYSV_COMPAT),) +MULTI_USER_TARGET_WANTS += \ + systemd-update-utmp-runlevel.service +GRAPHICAL_TARGET_WANTS += \ + systemd-update-utmp-runlevel.service +RESCUE_TARGET_WANTS += \ + systemd-update-utmp-runlevel.service +endif # HAVE_SYSV_COMPAT + +SYSINIT_TARGET_WANTS += \ + systemd-update-utmp.service +endif # HAVE_UTMP + +SYSINIT_TARGET_WANTS += \ + systemd-update-done.service + +LOCAL_FS_TARGET_WANTS += \ + systemd-remount-fs.service \ + tmp.mount \ + var-lib-machines.mount + +MULTI_USER_TARGET_WANTS += \ + getty.target \ + systemd-ask-password-wall.path + +SYSINIT_TARGET_WANTS += \ + dev-hugepages.mount \ + dev-mqueue.mount \ + sys-kernel-config.mount \ + sys-kernel-debug.mount \ + sys-fs-fuse-connections.mount \ + systemd-sysctl.service \ + systemd-ask-password-console.path + +ifneq ($(HAVE_SYSV_COMPAT),) +SYSTEM_UNIT_ALIASES += \ + poweroff.target runlevel0.target \ + rescue.target runlevel1.target \ + multi-user.target runlevel2.target \ + multi-user.target runlevel3.target \ + multi-user.target runlevel4.target \ + graphical.target runlevel5.target \ + reboot.target runlevel6.target +endif # HAVE_SYSV_COMPAT + +SYSTEM_UNIT_ALIASES += \ + graphical.target default.target \ + reboot.target ctrl-alt-del.target \ + getty@.service autovt@.service + +USER_UNIT_ALIASES += \ + $(systemunitdir)/shutdown.target shutdown.target \ + $(systemunitdir)/sockets.target sockets.target \ + $(systemunitdir)/timers.target timers.target \ + $(systemunitdir)/paths.target paths.target \ + $(systemunitdir)/bluetooth.target bluetooth.target \ + $(systemunitdir)/printer.target printer.target \ + $(systemunitdir)/sound.target sound.target \ + $(systemunitdir)/smartcard.target smartcard.target + +USER_UNIT_ALIASES += \ + $(systemunitdir)/busnames.target busnames.target + +GENERAL_ALIASES += \ + $(systemunitdir)/remote-fs.target $(pkgsysconfdir)/system/multi-user.target.wants/remote-fs.target \ + $(systemunitdir)/getty@.service $(pkgsysconfdir)/system/getty.target.wants/getty@tty1.service \ + $(pkgsysconfdir)/user $(sysconfdir)/xdg/systemd/user \ + $(dbussystemservicedir)/org.freedesktop.systemd1.service $(dbussessionservicedir)/org.freedesktop.systemd1.service + +ifneq ($(HAVE_SYSV_COMPAT),) +INSTALL_DIRS += \ + $(systemunitdir)/runlevel1.target.wants \ + $(systemunitdir)/runlevel2.target.wants \ + $(systemunitdir)/runlevel3.target.wants \ + $(systemunitdir)/runlevel4.target.wants \ + $(systemunitdir)/runlevel5.target.wants +endif # HAVE_SYSV_COMPAT + +INSTALL_DIRS += \ + $(prefix)/lib/modules-load.d \ + $(sysconfdir)/modules-load.d \ + $(prefix)/lib/systemd/network \ + $(sysconfdir)/systemd/network \ + $(prefix)/lib/sysctl.d \ + $(sysconfdir)/sysctl.d \ + $(prefix)/lib/kernel/install.d \ + $(sysconfdir)/kernel/install.d \ + $(systemshutdowndir) \ + $(systemsleepdir) \ + $(systemgeneratordir) \ + $(usergeneratordir) \ + \ + $(userunitdir) \ + $(pkgsysconfdir)/system \ + $(pkgsysconfdir)/system/multi-user.target.wants \ + $(pkgsysconfdir)/system/getty.target.wants \ + $(pkgsysconfdir)/user \ + $(dbussessionservicedir) \ + $(sysconfdir)/xdg/systemd + +install-exec-hook: $(INSTALL_EXEC_HOOKS) + +uninstall-hook: $(UNINSTALL_DATA_HOOKS) $(UNINSTALL_EXEC_HOOKS) + +install-data-hook: $(INSTALL_DATA_HOOKS) + +distclean-local: $(DISTCLEAN_LOCAL_HOOKS) + +clean-local: $(CLEAN_LOCAL_HOOKS) + rm -rf $(abs_srcdir)/install-tree + rm -f $(abs_srcdir)/hwdb/usb.ids $(abs_srcdir)/hwdb/pci.ids $(abs_srcdir)/hwdb/oui.txt \ + $(abs_srcdir)/hwdb/iab.txt + +DISTCHECK_CONFIGURE_FLAGS = \ + --with-dbuspolicydir=$$dc_install_base/$(dbuspolicydir) \ + --with-dbussessionservicedir=$$dc_install_base/$(dbussessionservicedir) \ + --with-dbussystemservicedir=$$dc_install_base/$(dbussystemservicedir) \ + --with-bashcompletiondir=$$dc_install_base/$(bashcompletiondir) \ + --with-zshcompletiondir=$$dc_install_base/$(zshcompletiondir) \ + --with-pamlibdir=$$dc_install_base/$(pamlibdir) \ + --with-pamconfdir=$$dc_install_base/$(pamconfdir) \ + --with-prefix=$$dc_install_base \ + --enable-compat-libs + +ifneq ($(HAVE_SYSV_COMPAT),) +DISTCHECK_CONFIGURE_FLAGS += \ + --with-sysvinit-path=$$dc_install_base/$(sysvinitdir) \ + --with-sysvrcnd-path=$$dc_install_base/$(sysvrcnddir) +else +DISTCHECK_CONFIGURE_FLAGS += \ + --with-sysvinit-path= \ + --with-sysvrcnd-path= +endif # HAVE_SYSV_COMPAT + +ifneq ($(ENABLE_SPLIT_USR),) +DISTCHECK_CONFIGURE_FLAGS += \ + --enable-split-usr +else +DISTCHECK_CONFIGURE_FLAGS += \ + --disable-split-usr +endif # ENABLE_SPLIT_USR + +.PHONY: dist-check-help +dist-check-help: $(bin_PROGRAMS) $(bin_PROGRAMS) + for i in $(abspath $^); do \ + if $$i --help | grep -v 'default:' | grep -E -q '.{80}.' ; then \ + echo "$(basename $$i) --help output is too wide:"; \ + $$i --help | awk 'length > 80' | grep -E --color=yes '.{80}'; \ + exit 1; \ + fi; done + +include_compilers = "$(CC)" "$(CC) -ansi" "$(CC) -std=iso9899:1990" +public_headers = $(filter-out src/systemd/_sd-common.h, $(pkginclude_HEADERS) $(include_HEADERS)) +.PHONY: dist-check-includes +dist-check-includes: $(public_headers) + @res=0; \ + for i in $(abspath $^); do \ + for cc in $(include_compilers); do \ + echo "$$cc -o/dev/null -c -x c -include "$$i" - systemd-$(VERSION).tar.gz + +www_target = www.freedesktop.org:/srv/www.freedesktop.org/www/software/systemd + +.PHONY: doc-sync +doc-sync: all + rsync -rlv --delete-excluded --include="*.html" --exclude="*" --omit-dir-times man/ $(www_target)/man/ + +.PHONY: install-tree +install-tree: all + rm -rf $(abs_srcdir)/install-tree + $(MAKE) install DESTDIR=$(abs_srcdir)/install-tree + tree $(abs_srcdir)/install-tree + +BUILT_SOURCES += \ + test-libsystemd-sym.c \ + test-libudev-sym.c + +CLEANFILES += \ + test-libsystemd-sym.c \ + test-libudev-sym.c + +tests += \ + test-libsystemd-sym \ + test-libudev-sym + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/docs/Makefile b/docs/Makefile deleted file mode 120000 index bd1047548b..0000000000 --- a/docs/Makefile +++ /dev/null @@ -1 +0,0 @@ -../src/Makefile \ No newline at end of file diff --git a/docs/sysvinit/Makefile b/docs/sysvinit/Makefile deleted file mode 120000 index 50be21181f..0000000000 --- a/docs/sysvinit/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../src/Makefile \ No newline at end of file diff --git a/docs/var-log/Makefile b/docs/var-log/Makefile deleted file mode 120000 index 50be21181f..0000000000 --- a/docs/var-log/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../src/Makefile \ No newline at end of file diff --git a/hwdb/Makefile b/hwdb/Makefile deleted file mode 120000 index bd1047548b..0000000000 --- a/hwdb/Makefile +++ /dev/null @@ -1 +0,0 @@ -../src/Makefile \ No newline at end of file diff --git a/hwdb/Makefile b/hwdb/Makefile new file mode 100644 index 0000000000..3f89c1ed8f --- /dev/null +++ b/hwdb/Makefile @@ -0,0 +1,36 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +.PHONY: hwdb-update +hwdb-update: + ( cd $(top_srcdir)/hwdb && \ + wget -O usb.ids 'http://www.linux-usb.org/usb.ids' && \ + wget -O pci.ids 'http://pci-ids.ucw.cz/v2.2/pci.ids' && \ + wget -O ma-large.txt 'http://standards.ieee.org/develop/regauth/oui/oui.txt' && \ + wget -O ma-medium.txt 'http://standards.ieee.org/develop/regauth/oui28/mam.txt' && \ + wget -O ma-small.txt 'http://standards.ieee.org/develop/regauth/oui36/oui36.txt' && \ + ./ids-update.pl ) + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/man/Makefile b/man/Makefile deleted file mode 120000 index bd1047548b..0000000000 --- a/man/Makefile +++ /dev/null @@ -1 +0,0 @@ -../src/Makefile \ No newline at end of file diff --git a/man/kernel-install.xml b/man/kernel-install.xml deleted file mode 100644 index eb519188a6..0000000000 --- a/man/kernel-install.xml +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - - - - kernel-install - systemd - - - - Developer - Harald - Hoyer - harald@redhat.com - - - - - - kernel-install - 8 - - - - kernel-install - Add and remove kernel and initramfs images to and from /boot - - - - - kernel-install - COMMAND - KERNEL-VERSION - KERNEL-IMAGE - - - - - Description - - kernel-install is used to install and remove kernel and - initramfs images to and from /boot. - - - kernel-install will execute the files - located in the directory /usr/lib/kernel/install.d/ - and the local administration directory /etc/kernel/install.d/. - All files are collectively sorted and executed in lexical order, regardless of the directory in - which they live. However, files with identical filenames replace each other. - Files in /etc/kernel/install.d/ take precedence over files with the same name - in /usr/lib/kernel/install.d/. This can be used to override a system-supplied - executables with a local file if needed; a symbolic link in /etc/kernel/install.d/ - with the same name as an executable in /usr/lib/kernel/install.d/, - pointing to /dev/null, disables the executable entirely. Executables must have the - extension .install; other extensions are ignored. - - - - - Commands - The following commands are understood: - - - add KERNEL-VERSION KERNEL-IMAGE - - kernel-install creates the directory - /boot/MACHINE-ID/KERNEL-VERSION/ - and calls every executable - /usr/lib/kernel/install.d/*.install and - /etc/kernel/install.d/*.install with - the arguments - add KERNEL-VERSION /boot/MACHINE-ID/KERNEL-VERSION/ - - - The kernel-install plugin 50-depmod.install runs depmod for the KERNEL-VERSION. - - The kernel-install plugin - 90-loaderentry.install copies - KERNEL-IMAGE to - /boot/MACHINE-ID/KERNEL-VERSION/linux. - It also creates a boot loader entry according to the boot - loader specification in - /boot/loader/entries/MACHINE-ID-KERNEL-VERSION.conf. - The title of the entry is the - PRETTY_NAME parameter specified - in /etc/os-release or - /usr/lib/os-release (if the former is - missing), or "GNU/Linux - KERNEL-VERSION", if unset. If - the file initrd is found next to the - linux file, the initrd will be added to - the configuration. - - - - remove KERNEL-VERSION - - Calls every executable /usr/lib/kernel/install.d/*.install - and /etc/kernel/install.d/*.install with the arguments - remove KERNEL-VERSION /boot/MACHINE-ID/KERNEL-VERSION/ - - - kernel-install removes the entire directory - /boot/MACHINE-ID/KERNEL-VERSION/ afterwards. - - The kernel-install plugin 90-loaderentry.install removes the file - /boot/loader/entries/MACHINE-ID-KERNEL-VERSION.conf. - - - - - - - - - Exit status - If every executable returns with 0, 0 is returned, a non-zero failure code otherwise. - - - - Files - - - - /usr/lib/kernel/install.d/*.install - /etc/kernel/install.d/*.install - - - Drop-in files which are executed by kernel-install. - - - - - /etc/kernel/cmdline - /proc/cmdline - - - The content of the file /etc/kernel/cmdline specifies the kernel command line to use. - If that file does not exist, /proc/cmdline is used. - - - - - /etc/machine-id - - - The content of the file specifies the machine identification MACHINE-ID. - - - - - /etc/os-release - /usr/lib/os-release - - - The content of the file specifies the operating system title PRETTY_NAME. - - - - - - - See Also - - machine-id5, - os-release5, - Boot loader specification - - - - diff --git a/network/Makefile b/network/Makefile deleted file mode 120000 index bd1047548b..0000000000 --- a/network/Makefile +++ /dev/null @@ -1 +0,0 @@ -../src/Makefile \ No newline at end of file diff --git a/rules/Makefile b/rules/Makefile deleted file mode 120000 index bd1047548b..0000000000 --- a/rules/Makefile +++ /dev/null @@ -1 +0,0 @@ -../src/Makefile \ No newline at end of file diff --git a/shell-completion/Makefile b/shell-completion/Makefile deleted file mode 120000 index bd1047548b..0000000000 --- a/shell-completion/Makefile +++ /dev/null @@ -1 +0,0 @@ -../src/Makefile \ No newline at end of file diff --git a/shell-completion/bash/Makefile b/shell-completion/bash/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/shell-completion/bash/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/shell-completion/bash/kernel-install b/shell-completion/bash/kernel-install deleted file mode 100644 index 7cd2494cf7..0000000000 --- a/shell-completion/bash/kernel-install +++ /dev/null @@ -1,50 +0,0 @@ -# kernel-install(8) completion -*- shell-script -*- -# -# This file is part of systemd. -# -# Copyright 2013 Kay Sievers -# Copyright 2013 Harald Hoyer -# -# 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 -# 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 . - -_kernel_install() { - local comps - local MACHINE_ID - local cur=${COMP_WORDS[COMP_CWORD]} - - case $COMP_CWORD in - 1) - comps="add remove" - ;; - 2) - comps=$(cd /lib/modules; echo [0-9]*) - if [[ ${COMP_WORDS[1]} == "remove" ]] && [[ -f /etc/machine-id ]]; then - read MACHINE_ID < /etc/machine-id - if [[ $MACHINE_ID ]] && ( [[ -d /boot/$MACHINE_ID ]] || [[ -L /boot/$MACHINE_ID ]] ); then - comps=$(cd "/boot/$MACHINE_ID"; echo [0-9]*) - fi - fi - ;; - 3) - [[ "$cur" ]] || cur=/boot/vmlinuz-${COMP_WORDS[2]} - comps=$(compgen -f -- "$cur") - compopt -o filenames - ;; - esac - - COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) - return 0 -} - -complete -F _kernel_install kernel-install diff --git a/shell-completion/zsh/Makefile b/shell-completion/zsh/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/shell-completion/zsh/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/shell-completion/zsh/_kernel-install b/shell-completion/zsh/_kernel-install deleted file mode 100644 index 4fdd3a4ae7..0000000000 --- a/shell-completion/zsh/_kernel-install +++ /dev/null @@ -1,26 +0,0 @@ -#compdef kernel-install - -_images(){ - if [[ "$words[2]" == "remove" ]]; then - _message 'No more options' - else - _path_files -W /boot/ -P /boot/ -g "vmlinuz-*" - fi -} - -_kernels(){ - read _MACHINE_ID < /etc/machine-id - _kernel=( /lib/modules/[0-9]* ) - if [[ "$cmd" == "remove" && -n "$_MACHINE_ID" ]]; then - _kernel=( "/boot/$_MACHINE_ID"/[0-9]* ) - fi - _kernel=( ${_kernel##*/} ) - _describe "installed kernels" _kernel -} - -_arguments \ - '1::add or remove:(add remove)' \ - '2::kernel versions:_kernels' \ - '3::kernel images:_images' - -#vim: set ft=zsh sw=4 ts=4 et diff --git a/src/Makefile b/src/Makefile index 9d07505194..b84e6076a0 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,6 +1,12 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# # This file is part of systemd. # -# Copyright 2010 Lennart Poettering +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker # # 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 @@ -14,15 +20,8 @@ # # You should have received a copy of the GNU Lesser General Public License # along with systemd; If not, see . +include $(dir $(lastword $(MAKEFILE_LIST)))/../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk -# This file is a dirty trick to simplify compilation from within -# emacs. This file is not intended to be distributed. So, don't touch -# it, even better ignore it! - -all: - $(MAKE) -C .. - -clean: - $(MAKE) -C .. clean -.PHONY: all clean +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/ac-power/Makefile b/src/ac-power/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/ac-power/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/ac-power/ac-power.c b/src/ac-power/ac-power.c deleted file mode 100644 index c5277884a8..0000000000 --- a/src/ac-power/ac-power.c +++ /dev/null @@ -1,35 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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" - -int main(int argc, char *argv[]) { - int r; - - /* This is mostly intended to be used for scripts which want - * to detect whether AC power is plugged in or not. */ - - r = on_ac_power(); - if (r < 0) { - log_error_errno(r, "Failed to read AC status: %m"); - return EXIT_FAILURE; - } - - return r != 0 ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/src/activate/Makefile b/src/activate/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/activate/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/activate/activate.c b/src/activate/activate.c deleted file mode 100644 index a0cfc22000..0000000000 --- a/src/activate/activate.c +++ /dev/null @@ -1,545 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 Zbigniew Jędrzejewski-Szmek - - 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 -#include -#include -#include -#include - -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "escape.h" -#include "fd-util.h" -#include "log.h" -#include "macro.h" -#include "signal-util.h" -#include "socket-util.h" -#include "string-util.h" -#include "strv.h" - -static char** arg_listen = NULL; -static bool arg_accept = false; -static int arg_socket_type = SOCK_STREAM; -static char** arg_args = NULL; -static char** arg_setenv = NULL; -static char **arg_fdnames = NULL; -static bool arg_inetd = false; - -static int add_epoll(int epoll_fd, int fd) { - struct epoll_event ev = { - .events = EPOLLIN - }; - int r; - - assert(epoll_fd >= 0); - assert(fd >= 0); - - ev.data.fd = fd; - r = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev); - if (r < 0) - return log_error_errno(errno, "Failed to add event on epoll fd:%d for fd:%d: %m", epoll_fd, fd); - - return 0; -} - -static int open_sockets(int *epoll_fd, bool accept) { - char **address; - int n, fd, r; - int count = 0; - - n = sd_listen_fds(true); - if (n < 0) - return log_error_errno(n, "Failed to read listening file descriptors from environment: %m"); - if (n > 0) { - log_info("Received %i descriptors via the environment.", n); - - for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { - r = fd_cloexec(fd, arg_accept); - if (r < 0) - return r; - - count++; - } - } - - /* Close logging and all other descriptors */ - if (arg_listen) { - int except[3 + n]; - - for (fd = 0; fd < SD_LISTEN_FDS_START + n; fd++) - except[fd] = fd; - - log_close(); - close_all_fds(except, 3 + n); - } - - /** Note: we leak some fd's on error here. I doesn't matter - * much, since the program will exit immediately anyway, but - * would be a pain to fix. - */ - - STRV_FOREACH(address, arg_listen) { - fd = make_socket_fd(LOG_DEBUG, *address, arg_socket_type, (arg_accept*SOCK_CLOEXEC)); - if (fd < 0) { - log_open(); - return log_error_errno(fd, "Failed to open '%s': %m", *address); - } - - assert(fd == SD_LISTEN_FDS_START + count); - count++; - } - - if (arg_listen) - log_open(); - - *epoll_fd = epoll_create1(EPOLL_CLOEXEC); - if (*epoll_fd < 0) - return log_error_errno(errno, "Failed to create epoll object: %m"); - - for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + count; fd++) { - _cleanup_free_ char *name = NULL; - - getsockname_pretty(fd, &name); - log_info("Listening on %s as %i.", strna(name), fd); - - r = add_epoll(*epoll_fd, fd); - if (r < 0) - return r; - } - - return count; -} - -static int exec_process(const char* name, char **argv, char **env, int start_fd, int n_fds) { - - _cleanup_strv_free_ char **envp = NULL; - _cleanup_free_ char *joined = NULL; - unsigned n_env = 0, length; - const char *tocopy; - char **s; - int r; - - if (arg_inetd && n_fds != 1) { - log_error("--inetd only supported for single file descriptors."); - return -EINVAL; - } - - length = strv_length(arg_setenv); - - /* PATH, TERM, HOME, USER, LISTEN_FDS, LISTEN_PID, LISTEN_FDNAMES, NULL */ - envp = new0(char *, length + 8); - if (!envp) - return log_oom(); - - STRV_FOREACH(s, arg_setenv) { - - if (strchr(*s, '=')) { - char *k; - - k = strdup(*s); - if (!k) - return log_oom(); - - envp[n_env++] = k; - } else { - _cleanup_free_ char *p; - const char *n; - - p = strappend(*s, "="); - if (!p) - return log_oom(); - - n = strv_find_prefix(env, p); - if (!n) - continue; - - envp[n_env] = strdup(n); - if (!envp[n_env]) - return log_oom(); - - n_env++; - } - } - - FOREACH_STRING(tocopy, "TERM=", "PATH=", "USER=", "HOME=") { - const char *n; - - n = strv_find_prefix(env, tocopy); - if (!n) - continue; - - envp[n_env] = strdup(n); - if (!envp[n_env]) - return log_oom(); - - n_env++; - } - - if (arg_inetd) { - assert(n_fds == 1); - - r = dup2(start_fd, STDIN_FILENO); - if (r < 0) - return log_error_errno(errno, "Failed to dup connection to stdin: %m"); - - r = dup2(start_fd, STDOUT_FILENO); - if (r < 0) - return log_error_errno(errno, "Failed to dup connection to stdout: %m"); - - start_fd = safe_close(start_fd); - } else { - if (start_fd != SD_LISTEN_FDS_START) { - assert(n_fds == 1); - - r = dup2(start_fd, SD_LISTEN_FDS_START); - if (r < 0) - return log_error_errno(errno, "Failed to dup connection: %m"); - - safe_close(start_fd); - start_fd = SD_LISTEN_FDS_START; - } - - if (asprintf((char**)(envp + n_env++), "LISTEN_FDS=%i", n_fds) < 0) - return log_oom(); - - if (asprintf((char**)(envp + n_env++), "LISTEN_PID=" PID_FMT, getpid()) < 0) - return log_oom(); - - if (arg_fdnames) { - _cleanup_free_ char *names = NULL; - size_t len; - char *e; - int i; - - len = strv_length(arg_fdnames); - if (len == 1) - for (i = 1; i < n_fds; i++) { - r = strv_extend(&arg_fdnames, arg_fdnames[0]); - if (r < 0) - return log_error_errno(r, "Failed to extend strv: %m"); - } - else if (len != (unsigned) n_fds) - log_warning("The number of fd names is different than number of fds: %zu vs %d", - len, n_fds); - - names = strv_join(arg_fdnames, ":"); - if (!names) - return log_oom(); - - e = strappend("LISTEN_FDNAMES=", names); - if (!e) - return log_oom(); - - envp[n_env++] = e; - } - } - - joined = strv_join(argv, " "); - if (!joined) - return log_oom(); - - log_info("Execing %s (%s)", name, joined); - execvpe(name, argv, envp); - - return log_error_errno(errno, "Failed to execp %s (%s): %m", name, joined); -} - -static int fork_and_exec_process(const char* child, char** argv, char **env, int fd) { - _cleanup_free_ char *joined = NULL; - pid_t parent_pid, child_pid; - - joined = strv_join(argv, " "); - if (!joined) - return log_oom(); - - parent_pid = getpid(); - - child_pid = fork(); - if (child_pid < 0) - return log_error_errno(errno, "Failed to fork: %m"); - - /* In the child */ - if (child_pid == 0) { - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - /* Make sure the child goes away when the parent dies */ - if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) - _exit(EXIT_FAILURE); - - /* Check whether our parent died before we were able - * to set the death signal */ - if (getppid() != parent_pid) - _exit(EXIT_SUCCESS); - - exec_process(child, argv, env, fd, 1); - _exit(EXIT_FAILURE); - } - - log_info("Spawned %s (%s) as PID %d", child, joined, child_pid); - return 0; -} - -static int do_accept(const char* name, char **argv, char **envp, int fd) { - _cleanup_free_ char *local = NULL, *peer = NULL; - _cleanup_close_ int fd_accepted = -1; - - fd_accepted = accept4(fd, NULL, NULL, 0); - if (fd_accepted < 0) - return log_error_errno(errno, "Failed to accept connection on fd:%d: %m", fd); - - getsockname_pretty(fd_accepted, &local); - getpeername_pretty(fd_accepted, true, &peer); - log_info("Connection from %s to %s", strna(peer), strna(local)); - - return fork_and_exec_process(name, argv, envp, fd_accepted); -} - -/* SIGCHLD handler. */ -static void sigchld_hdl(int sig) { - PROTECT_ERRNO; - - for (;;) { - siginfo_t si; - int r; - - si.si_pid = 0; - r = waitid(P_ALL, 0, &si, WEXITED|WNOHANG); - if (r < 0) { - if (errno != ECHILD) - log_error_errno(errno, "Failed to reap children: %m"); - return; - } - if (si.si_pid == 0) - return; - - log_info("Child %d died with code %d", si.si_pid, si.si_status); - } -} - -static int install_chld_handler(void) { - static const struct sigaction act = { - .sa_flags = SA_NOCLDSTOP, - .sa_handler = sigchld_hdl, - }; - - int r; - - r = sigaction(SIGCHLD, &act, 0); - if (r < 0) - return log_error_errno(errno, "Failed to install SIGCHLD handler: %m"); - - return 0; -} - -static void help(void) { - printf("%s [OPTIONS...]\n\n" - "Listen on sockets and launch child on connection.\n\n" - "Options:\n" - " -h --help Show this help and exit\n" - " --version Print version string and exit\n" - " -l --listen=ADDR Listen for raw connections at ADDR\n" - " -d --datagram Listen on datagram instead of stream socket\n" - " --seqpacket Listen on SOCK_SEQPACKET instead of stream socket\n" - " -a --accept Spawn separate child for each connection\n" - " -E --setenv=NAME[=VALUE] Pass an environment variable to children\n" - " --fdname=NAME[:NAME...] Specify names for file descriptors\n" - " --inetd Enable inetd file descriptor passing protocol\n" - "\n" - "Note: file descriptors from sd_listen_fds() will be passed through.\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_FDNAME, - ARG_SEQPACKET, - ARG_INETD, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "datagram", no_argument, NULL, 'd' }, - { "seqpacket", no_argument, NULL, ARG_SEQPACKET }, - { "listen", required_argument, NULL, 'l' }, - { "accept", no_argument, NULL, 'a' }, - { "setenv", required_argument, NULL, 'E' }, - { "environment", required_argument, NULL, 'E' }, /* legacy alias */ - { "fdname", required_argument, NULL, ARG_FDNAME }, - { "inetd", no_argument, NULL, ARG_INETD }, - {} - }; - - int c, r; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "+hl:aE:d", options, NULL)) >= 0) - switch(c) { - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case 'l': - r = strv_extend(&arg_listen, optarg); - if (r < 0) - return log_oom(); - - break; - - case 'd': - if (arg_socket_type == SOCK_SEQPACKET) { - log_error("--datagram may not be combined with --seqpacket."); - return -EINVAL; - } - - arg_socket_type = SOCK_DGRAM; - break; - - case ARG_SEQPACKET: - if (arg_socket_type == SOCK_DGRAM) { - log_error("--seqpacket may not be combined with --datagram."); - return -EINVAL; - } - - arg_socket_type = SOCK_SEQPACKET; - break; - - case 'a': - arg_accept = true; - break; - - case 'E': - r = strv_extend(&arg_setenv, optarg); - if (r < 0) - return log_oom(); - - break; - - case ARG_FDNAME: { - _cleanup_strv_free_ char **names; - char **s; - - names = strv_split(optarg, ":"); - if (!names) - return log_oom(); - - STRV_FOREACH(s, names) - if (!fdname_is_valid(*s)) { - _cleanup_free_ char *esc; - - esc = cescape(*s); - log_warning("File descriptor name \"%s\" is not valid.", esc); - } - - /* Empty optargs means one empty name */ - r = strv_extend_strv(&arg_fdnames, - strv_isempty(names) ? STRV_MAKE("") : names, - false); - if (r < 0) - return log_error_errno(r, "strv_extend_strv: %m"); - break; - } - - case ARG_INETD: - arg_inetd = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (optind == argc) { - log_error("%s: command to execute is missing.", - program_invocation_short_name); - return -EINVAL; - } - - if (arg_socket_type == SOCK_DGRAM && arg_accept) { - log_error("Datagram sockets do not accept connections. " - "The --datagram and --accept options may not be combined."); - return -EINVAL; - } - - arg_args = argv + optind; - - return 1 /* work to do */; -} - -int main(int argc, char **argv, char **envp) { - int r, n; - int epoll_fd = -1; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; - - r = install_chld_handler(); - if (r < 0) - return EXIT_FAILURE; - - n = open_sockets(&epoll_fd, arg_accept); - if (n < 0) - return EXIT_FAILURE; - if (n == 0) { - log_error("No sockets to listen on specified or passed in."); - return EXIT_FAILURE; - } - - for (;;) { - struct epoll_event event; - - r = epoll_wait(epoll_fd, &event, 1, -1); - if (r < 0) { - if (errno == EINTR) - continue; - - log_error_errno(errno, "epoll_wait() failed: %m"); - return EXIT_FAILURE; - } - - log_info("Communication attempt on fd %i.", event.data.fd); - if (arg_accept) { - r = do_accept(argv[optind], argv + optind, envp, event.data.fd); - if (r < 0) - return EXIT_FAILURE; - } else - break; - } - - exec_process(argv[optind], argv + optind, envp, SD_LISTEN_FDS_START, n); - - return EXIT_SUCCESS; -} diff --git a/src/analyze/.gitignore b/src/analyze/.gitignore deleted file mode 100644 index 752ea236c8..0000000000 --- a/src/analyze/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/systemd-analyze diff --git a/src/analyze/Makefile b/src/analyze/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/analyze/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/analyze/analyze-verify.c b/src/analyze/analyze-verify.c deleted file mode 100644 index 5fd3ee49eb..0000000000 --- a/src/analyze/analyze-verify.c +++ /dev/null @@ -1,304 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Zbigniew Jędrzejewski-Szmek - - 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 "alloc-util.h" -#include "analyze-verify.h" -#include "bus-error.h" -#include "bus-util.h" -#include "log.h" -#include "manager.h" -#include "pager.h" -#include "path-util.h" -#include "strv.h" -#include "unit-name.h" - -static int prepare_filename(const char *filename, char **ret) { - int r; - const char *name; - _cleanup_free_ char *abspath = NULL; - _cleanup_free_ char *dir = NULL; - _cleanup_free_ char *with_instance = NULL; - char *c; - - assert(filename); - assert(ret); - - r = path_make_absolute_cwd(filename, &abspath); - if (r < 0) - return r; - - name = basename(abspath); - if (!unit_name_is_valid(name, UNIT_NAME_ANY)) - return -EINVAL; - - if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { - r = unit_name_replace_instance(name, "i", &with_instance); - if (r < 0) - return r; - } - - dir = dirname_malloc(abspath); - if (!dir) - return -ENOMEM; - - if (with_instance) - c = path_join(NULL, dir, with_instance); - else - c = path_join(NULL, dir, name); - if (!c) - return -ENOMEM; - - *ret = c; - return 0; -} - -static int generate_path(char **var, char **filenames) { - char **filename; - - _cleanup_strv_free_ char **ans = NULL; - int r; - - STRV_FOREACH(filename, filenames) { - char *t; - - t = dirname_malloc(*filename); - if (!t) - return -ENOMEM; - - r = strv_consume(&ans, t); - if (r < 0) - return r; - } - - assert_se(strv_uniq(ans)); - - r = strv_extend(&ans, ""); - if (r < 0) - return r; - - *var = strv_join(ans, ":"); - if (!*var) - return -ENOMEM; - - return 0; -} - -static int verify_socket(Unit *u) { - int r; - - assert(u); - - if (u->type != UNIT_SOCKET) - return 0; - - /* Cannot run this without the service being around */ - - /* This makes sure instance is created if necessary. */ - r = socket_instantiate_service(SOCKET(u)); - if (r < 0) { - log_unit_error_errno(u, r, "Socket cannot be started, failed to create instance: %m"); - return r; - } - - /* This checks both type of sockets */ - if (UNIT_ISSET(SOCKET(u)->service)) { - Service *service; - - service = SERVICE(UNIT_DEREF(SOCKET(u)->service)); - log_unit_debug(u, "Using %s", UNIT(service)->id); - - if (UNIT(service)->load_state != UNIT_LOADED) { - log_unit_error(u, "Service %s not loaded, %s cannot be started.", UNIT(service)->id, u->id); - return -ENOENT; - } - } - - return 0; -} - -static int verify_executable(Unit *u, ExecCommand *exec) { - if (exec == NULL) - return 0; - - if (access(exec->path, X_OK) < 0) - return log_unit_error_errno(u, errno, "Command %s is not executable: %m", exec->path); - - return 0; -} - -static int verify_executables(Unit *u) { - ExecCommand *exec; - int r = 0, k; - unsigned i; - - assert(u); - - exec = u->type == UNIT_SOCKET ? SOCKET(u)->control_command : - u->type == UNIT_MOUNT ? MOUNT(u)->control_command : - u->type == UNIT_SWAP ? SWAP(u)->control_command : NULL; - k = verify_executable(u, exec); - if (k < 0 && r == 0) - r = k; - - if (u->type == UNIT_SERVICE) - for (i = 0; i < ELEMENTSOF(SERVICE(u)->exec_command); i++) { - k = verify_executable(u, SERVICE(u)->exec_command[i]); - if (k < 0 && r == 0) - r = k; - } - - if (u->type == UNIT_SOCKET) - for (i = 0; i < ELEMENTSOF(SOCKET(u)->exec_command); i++) { - k = verify_executable(u, SOCKET(u)->exec_command[i]); - if (k < 0 && r == 0) - r = k; - } - - return r; -} - -static int verify_documentation(Unit *u, bool check_man) { - char **p; - int r = 0, k; - - STRV_FOREACH(p, u->documentation) { - log_unit_debug(u, "Found documentation item: %s", *p); - - if (check_man && startswith(*p, "man:")) { - k = show_man_page(*p + 4, true); - if (k != 0) { - if (k < 0) - log_unit_error_errno(u, r, "Can't show %s: %m", *p); - else { - log_unit_error_errno(u, r, "man %s command failed with code %d", *p + 4, k); - k = -ENOEXEC; - } - if (r == 0) - r = k; - } - } - } - - /* Check remote URLs? */ - - return r; -} - -static int verify_unit(Unit *u, bool check_man) { - _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL; - int r, k; - - assert(u); - - if (log_get_max_level() >= LOG_DEBUG) - unit_dump(u, stdout, "\t"); - - log_unit_debug(u, "Creating %s/start job", u->id); - r = manager_add_job(u->manager, JOB_START, u, JOB_REPLACE, &err, NULL); - if (r < 0) - log_unit_error_errno(u, r, "Failed to create %s/start: %s", u->id, bus_error_message(&err, r)); - - k = verify_socket(u); - if (k < 0 && r == 0) - r = k; - - k = verify_executables(u); - if (k < 0 && r == 0) - r = k; - - k = verify_documentation(u, check_man); - if (k < 0 && r == 0) - r = k; - - return r; -} - -int verify_units(char **filenames, UnitFileScope scope, bool check_man) { - _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL; - _cleanup_free_ char *var = NULL; - Manager *m = NULL; - FILE *serial = NULL; - FDSet *fdset = NULL; - char **filename; - int r = 0, k; - - Unit *units[strv_length(filenames)]; - int i, count = 0; - - if (strv_isempty(filenames)) - return 0; - - /* set the path */ - r = generate_path(&var, filenames); - if (r < 0) - return log_error_errno(r, "Failed to generate unit load path: %m"); - - assert_se(set_unit_path(var) >= 0); - - r = manager_new(scope, true, &m); - if (r < 0) - return log_error_errno(r, "Failed to initialize manager: %m"); - - log_debug("Starting manager..."); - - r = manager_startup(m, serial, fdset); - if (r < 0) { - log_error_errno(r, "Failed to start manager: %m"); - goto finish; - } - - manager_clear_jobs(m); - - log_debug("Loading remaining units from the command line..."); - - STRV_FOREACH(filename, filenames) { - _cleanup_free_ char *prepared = NULL; - - log_debug("Handling %s...", *filename); - - k = prepare_filename(*filename, &prepared); - if (k < 0) { - log_error_errno(k, "Failed to prepare filename %s: %m", *filename); - if (r == 0) - r = k; - continue; - } - - k = manager_load_unit(m, NULL, prepared, &err, &units[count]); - if (k < 0) { - log_error_errno(k, "Failed to load %s: %m", *filename); - if (r == 0) - r = k; - } else - count++; - } - - for (i = 0; i < count; i++) { - k = verify_unit(units[i], check_man); - if (k < 0 && r == 0) - r = k; - } - -finish: - manager_free(m); - - return r; -} diff --git a/src/analyze/analyze-verify.h b/src/analyze/analyze-verify.h deleted file mode 100644 index d8204dc69c..0000000000 --- a/src/analyze/analyze-verify.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Zbigniew Jędrzejewski-Szmek - - 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 "path-lookup.h" - -int verify_units(char **filenames, UnitFileScope scope, bool check_man); diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c deleted file mode 100644 index 53c97f957f..0000000000 --- a/src/analyze/analyze.c +++ /dev/null @@ -1,1485 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010-2013 Lennart Poettering - Copyright 2013 Simon Peeters - - 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 -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "analyze-verify.h" -#include "bus-error.h" -#include "bus-unit-util.h" -#include "bus-util.h" -#include "glob-util.h" -#include "hashmap.h" -#include "locale-util.h" -#include "log.h" -#include "pager.h" -#include "parse-util.h" -#include "special.h" -#include "strv.h" -#include "strxcpyx.h" -#include "terminal-util.h" -#include "unit-name.h" -#include "util.h" - -#define SCALE_X (0.1 / 1000.0) /* pixels per us */ -#define SCALE_Y (20.0) - -#define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0)) - -#define svg(...) printf(__VA_ARGS__) - -#define svg_bar(class, x1, x2, y) \ - svg(" \n", \ - (class), \ - SCALE_X * (x1), SCALE_Y * (y), \ - SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0) - -#define svg_text(b, x, y, format, ...) \ - do { \ - svg(" ", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \ - svg(format, ## __VA_ARGS__); \ - svg("\n"); \ - } while (false) - -static enum dot { - DEP_ALL, - DEP_ORDER, - DEP_REQUIRE -} arg_dot = DEP_ALL; -static char** arg_dot_from_patterns = NULL; -static char** arg_dot_to_patterns = NULL; -static usec_t arg_fuzz = 0; -static bool arg_no_pager = false; -static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; -static char *arg_host = NULL; -static bool arg_user = false; -static bool arg_man = true; - -struct boot_times { - usec_t firmware_time; - usec_t loader_time; - usec_t kernel_time; - usec_t kernel_done_time; - usec_t initrd_time; - usec_t userspace_time; - usec_t finish_time; - usec_t security_start_time; - usec_t security_finish_time; - usec_t generators_start_time; - usec_t generators_finish_time; - usec_t unitsload_start_time; - usec_t unitsload_finish_time; - - /* - * If we're analyzing the user instance, all timestamps will be offset - * by its own start-up timestamp, which may be arbitrarily big. - * With "plot", this causes arbitrarily wide output SVG files which almost - * completely consist of empty space. Thus we cancel out this offset. - * - * This offset is subtracted from times above by acquire_boot_times(), - * but it still needs to be subtracted from unit-specific timestamps - * (so it is stored here for reference). - */ - usec_t reverse_offset; -}; - -struct unit_times { - char *name; - usec_t activating; - usec_t activated; - usec_t deactivated; - usec_t deactivating; - usec_t time; -}; - -struct host_info { - char *hostname; - char *kernel_name; - char *kernel_release; - char *kernel_version; - char *os_pretty_name; - char *virtualization; - char *architecture; -}; - -static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *interface, const char *property, uint64_t *val) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(property); - assert(val); - - r = sd_bus_get_property_trivial( - bus, - "org.freedesktop.systemd1", - path, - interface, - property, - &error, - 't', val); - - if (r < 0) { - log_error("Failed to parse reply: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char *property, char ***strv) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(path); - assert(property); - assert(strv); - - r = sd_bus_get_property_strv( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - property, - &error, - strv); - if (r < 0) { - log_error("Failed to get unit property %s: %s", property, bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int compare_unit_time(const void *a, const void *b) { - return compare(((struct unit_times *)b)->time, - ((struct unit_times *)a)->time); -} - -static int compare_unit_start(const void *a, const void *b) { - return compare(((struct unit_times *)a)->activating, - ((struct unit_times *)b)->activating); -} - -static void free_unit_times(struct unit_times *t, unsigned n) { - struct unit_times *p; - - for (p = t; p < t + n; p++) - free(p->name); - - free(t); -} - -static void subtract_timestamp(usec_t *a, usec_t b) { - assert(a); - - if (*a > 0) { - assert(*a >= b); - *a -= b; - } -} - -static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) { - static struct boot_times times; - static bool cached = false; - - if (cached) - goto finish; - - assert_cc(sizeof(usec_t) == sizeof(uint64_t)); - - if (bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "FirmwareTimestampMonotonic", - ×.firmware_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "LoaderTimestampMonotonic", - ×.loader_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "KernelTimestamp", - ×.kernel_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "InitRDTimestampMonotonic", - ×.initrd_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "UserspaceTimestampMonotonic", - ×.userspace_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "FinishTimestampMonotonic", - ×.finish_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SecurityStartTimestampMonotonic", - ×.security_start_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SecurityFinishTimestampMonotonic", - ×.security_finish_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GeneratorsStartTimestampMonotonic", - ×.generators_start_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GeneratorsFinishTimestampMonotonic", - ×.generators_finish_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "UnitsLoadStartTimestampMonotonic", - ×.unitsload_start_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "UnitsLoadFinishTimestampMonotonic", - ×.unitsload_finish_time) < 0) - return -EIO; - - if (times.finish_time <= 0) { - log_error("Bootup is not yet finished. Please try again later."); - return -EINPROGRESS; - } - - if (arg_user) { - /* - * User-instance-specific timestamps processing - * (see comment to reverse_offset in struct boot_times). - */ - times.reverse_offset = times.userspace_time; - - times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time = times.userspace_time = 0; - subtract_timestamp(×.finish_time, times.reverse_offset); - - subtract_timestamp(×.security_start_time, times.reverse_offset); - subtract_timestamp(×.security_finish_time, times.reverse_offset); - - subtract_timestamp(×.generators_start_time, times.reverse_offset); - subtract_timestamp(×.generators_finish_time, times.reverse_offset); - - subtract_timestamp(×.unitsload_start_time, times.reverse_offset); - subtract_timestamp(×.unitsload_finish_time, times.reverse_offset); - } else { - if (times.initrd_time) - times.kernel_done_time = times.initrd_time; - else - times.kernel_done_time = times.userspace_time; - } - - cached = true; - -finish: - *bt = × - return 0; -} - -static void free_host_info(struct host_info *hi) { - - if (!hi) - return; - - free(hi->hostname); - free(hi->kernel_name); - free(hi->kernel_release); - free(hi->kernel_version); - free(hi->os_pretty_name); - free(hi->virtualization); - free(hi->architecture); - free(hi); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info*, free_host_info); - -static int acquire_time_data(sd_bus *bus, struct unit_times **out) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r, c = 0; - struct boot_times *boot_times = NULL; - struct unit_times *unit_times = NULL; - size_t size = 0; - UnitInfo u; - - r = acquire_boot_times(bus, &boot_times); - if (r < 0) - goto fail; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "ListUnits", - &error, &reply, - NULL); - if (r < 0) { - log_error("Failed to list units: %s", bus_error_message(&error, -r)); - goto fail; - } - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); - if (r < 0) { - bus_log_parse_error(r); - goto fail; - } - - while ((r = bus_parse_unit_info(reply, &u)) > 0) { - struct unit_times *t; - - if (!GREEDY_REALLOC(unit_times, size, c+1)) { - r = log_oom(); - goto fail; - } - - t = unit_times+c; - t->name = NULL; - - assert_cc(sizeof(usec_t) == sizeof(uint64_t)); - - if (bus_get_uint64_property(bus, u.unit_path, - "org.freedesktop.systemd1.Unit", - "InactiveExitTimestampMonotonic", - &t->activating) < 0 || - bus_get_uint64_property(bus, u.unit_path, - "org.freedesktop.systemd1.Unit", - "ActiveEnterTimestampMonotonic", - &t->activated) < 0 || - bus_get_uint64_property(bus, u.unit_path, - "org.freedesktop.systemd1.Unit", - "ActiveExitTimestampMonotonic", - &t->deactivating) < 0 || - bus_get_uint64_property(bus, u.unit_path, - "org.freedesktop.systemd1.Unit", - "InactiveEnterTimestampMonotonic", - &t->deactivated) < 0) { - r = -EIO; - goto fail; - } - - subtract_timestamp(&t->activating, boot_times->reverse_offset); - subtract_timestamp(&t->activated, boot_times->reverse_offset); - subtract_timestamp(&t->deactivating, boot_times->reverse_offset); - subtract_timestamp(&t->deactivated, boot_times->reverse_offset); - - if (t->activated >= t->activating) - t->time = t->activated - t->activating; - else if (t->deactivated >= t->activating) - t->time = t->deactivated - t->activating; - else - t->time = 0; - - if (t->activating == 0) - continue; - - t->name = strdup(u.id); - if (t->name == NULL) { - r = log_oom(); - goto fail; - } - c++; - } - if (r < 0) { - bus_log_parse_error(r); - goto fail; - } - - *out = unit_times; - return c; - -fail: - if (unit_times) - free_unit_times(unit_times, (unsigned) c); - return r; -} - -static int acquire_host_info(sd_bus *bus, struct host_info **hi) { - static const struct bus_properties_map hostname_map[] = { - { "Hostname", "s", NULL, offsetof(struct host_info, hostname) }, - { "KernelName", "s", NULL, offsetof(struct host_info, kernel_name) }, - { "KernelRelease", "s", NULL, offsetof(struct host_info, kernel_release) }, - { "KernelVersion", "s", NULL, offsetof(struct host_info, kernel_version) }, - { "OperatingSystemPrettyName", "s", NULL, offsetof(struct host_info, os_pretty_name) }, - {} - }; - - static const struct bus_properties_map manager_map[] = { - { "Virtualization", "s", NULL, offsetof(struct host_info, virtualization) }, - { "Architecture", "s", NULL, offsetof(struct host_info, architecture) }, - {} - }; - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(free_host_infop) struct host_info *host; - int r; - - host = new0(struct host_info, 1); - if (!host) - return log_oom(); - - r = bus_map_all_properties(bus, - "org.freedesktop.hostname1", - "/org/freedesktop/hostname1", - hostname_map, - host); - if (r < 0) - log_debug_errno(r, "Failed to get host information from systemd-hostnamed: %s", bus_error_message(&error, r)); - - r = bus_map_all_properties(bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - manager_map, - host); - if (r < 0) - return log_error_errno(r, "Failed to get host information from systemd: %s", bus_error_message(&error, r)); - - *hi = host; - host = NULL; - - return 0; -} - -static int pretty_boot_time(sd_bus *bus, char **_buf) { - char ts[FORMAT_TIMESPAN_MAX]; - struct boot_times *t; - static char buf[4096]; - size_t size; - char *ptr; - int r; - - r = acquire_boot_times(bus, &t); - if (r < 0) - return r; - - ptr = buf; - size = sizeof(buf); - - size = strpcpyf(&ptr, size, "Startup finished in "); - if (t->firmware_time) - size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC)); - if (t->loader_time) - size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC)); - if (t->kernel_time) - size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC)); - if (t->initrd_time > 0) - size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC)); - - size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC)); - strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC)); - - ptr = strdup(buf); - if (!ptr) - return log_oom(); - - *_buf = ptr; - return 0; -} - -static void svg_graph_box(double height, double begin, double end) { - long long i; - - /* outside box, fill */ - svg("\n", - SCALE_X * (end - begin), SCALE_Y * height); - - for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) { - /* lines for each second */ - if (i % 5000000 == 0) - svg(" \n" - " %.01fs\n", - SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i); - else if (i % 1000000 == 0) - svg(" \n" - " %.01fs\n", - SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i); - else - svg(" \n", - SCALE_X * i, SCALE_X * i, SCALE_Y * height); - } -} - -static int analyze_plot(sd_bus *bus) { - _cleanup_(free_host_infop) struct host_info *host = NULL; - struct unit_times *times; - struct boot_times *boot; - int n, m = 1, y=0; - double width; - _cleanup_free_ char *pretty_times = NULL; - struct unit_times *u; - - n = acquire_boot_times(bus, &boot); - if (n < 0) - return n; - - n = pretty_boot_time(bus, &pretty_times); - if (n < 0) - return n; - - n = acquire_host_info(bus, &host); - if (n < 0) - return n; - - n = acquire_time_data(bus, ×); - if (n <= 0) - return n; - - qsort(times, n, sizeof(struct unit_times), compare_unit_start); - - width = SCALE_X * (boot->firmware_time + boot->finish_time); - if (width < 800.0) - width = 800.0; - - if (boot->firmware_time > boot->loader_time) - m++; - if (boot->loader_time) { - m++; - if (width < 1000.0) - width = 1000.0; - } - if (boot->initrd_time) - m++; - if (boot->kernel_time) - m++; - - for (u = times; u < times + n; u++) { - double text_start, text_width; - - if (u->activating < boot->userspace_time || - u->activating > boot->finish_time) { - u->name = mfree(u->name); - continue; - } - - /* If the text cannot fit on the left side then - * increase the svg width so it fits on the right. - * TODO: calculate the text width more accurately */ - text_width = 8.0 * strlen(u->name); - text_start = (boot->firmware_time + u->activating) * SCALE_X; - if (text_width > text_start && text_width + text_start > width) - width = text_width + text_start; - - if (u->deactivated > u->activating && u->deactivated <= boot->finish_time - && u->activated == 0 && u->deactivating == 0) - u->activated = u->deactivating = u->deactivated; - if (u->activated < u->activating || u->activated > boot->finish_time) - u->activated = boot->finish_time; - if (u->deactivating < u->activated || u->activated > boot->finish_time) - u->deactivating = boot->finish_time; - if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time) - u->deactivated = boot->finish_time; - m++; - } - - svg("\n" - "\n"); - - svg("\n\n", - 80.0 + width, 150.0 + (m * SCALE_Y) + - 5 * SCALE_Y /* legend */); - - /* write some basic info as a comment, including some help */ - svg("\n" - "\n" - "\n" - "\n" - "\n\n" - "\n\n", VERSION); - - /* style sheet */ - svg("\n \n\n\n"); - - svg("\n"); - svg("%s", pretty_times); - svg("%s %s (%s %s %s) %s %s", - isempty(host->os_pretty_name) ? "GNU/Linux" : host->os_pretty_name, - strempty(host->hostname), - strempty(host->kernel_name), - strempty(host->kernel_release), - strempty(host->kernel_version), - strempty(host->architecture), - strempty(host->virtualization)); - - svg("\n", 20.0 + (SCALE_X * boot->firmware_time)); - svg_graph_box(m, -(double) boot->firmware_time, boot->finish_time); - - if (boot->firmware_time) { - svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y); - svg_text(true, -(double) boot->firmware_time, y, "firmware"); - y++; - } - if (boot->loader_time) { - svg_bar("loader", -(double) boot->loader_time, 0, y); - svg_text(true, -(double) boot->loader_time, y, "loader"); - y++; - } - if (boot->kernel_time) { - svg_bar("kernel", 0, boot->kernel_done_time, y); - svg_text(true, 0, y, "kernel"); - y++; - } - if (boot->initrd_time) { - svg_bar("initrd", boot->initrd_time, boot->userspace_time, y); - svg_text(true, boot->initrd_time, y, "initrd"); - y++; - } - svg_bar("active", boot->userspace_time, boot->finish_time, y); - svg_bar("security", boot->security_start_time, boot->security_finish_time, y); - svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y); - svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y); - svg_text(true, boot->userspace_time, y, "systemd"); - y++; - - for (u = times; u < times + n; u++) { - char ts[FORMAT_TIMESPAN_MAX]; - bool b; - - if (!u->name) - continue; - - svg_bar("activating", u->activating, u->activated, y); - svg_bar("active", u->activated, u->deactivating, y); - svg_bar("deactivating", u->deactivating, u->deactivated, y); - - /* place the text on the left if we have passed the half of the svg width */ - b = u->activating * SCALE_X < width / 2; - if (u->time) - svg_text(b, u->activating, y, "%s (%s)", - u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC)); - else - svg_text(b, u->activating, y, "%s", u->name); - y++; - } - - svg("\n"); - - /* Legend */ - svg("\n"); - y++; - svg_bar("activating", 0, 300000, y); - svg_text(true, 400000, y, "Activating"); - y++; - svg_bar("active", 0, 300000, y); - svg_text(true, 400000, y, "Active"); - y++; - svg_bar("deactivating", 0, 300000, y); - svg_text(true, 400000, y, "Deactivating"); - y++; - svg_bar("security", 0, 300000, y); - svg_text(true, 400000, y, "Setting up security module"); - y++; - svg_bar("generators", 0, 300000, y); - svg_text(true, 400000, y, "Generators"); - y++; - svg_bar("unitsload", 0, 300000, y); - svg_text(true, 400000, y, "Loading unit files"); - y++; - - svg("\n\n"); - - svg("\n"); - - free_unit_times(times, (unsigned) n); - - n = 0; - return n; -} - -static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches, - bool last, struct unit_times *times, struct boot_times *boot) { - unsigned int i; - char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX]; - - for (i = level; i != 0; i--) - printf("%s", special_glyph(branches & (1 << (i-1)) ? TREE_VERTICAL : TREE_SPACE)); - - printf("%s", special_glyph(last ? TREE_RIGHT : TREE_BRANCH)); - - if (times) { - if (times->time) - printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED, name, - format_timespan(ts, sizeof(ts), times->activating - boot->userspace_time, USEC_PER_MSEC), - format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ANSI_NORMAL); - else if (times->activated > boot->userspace_time) - printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC)); - else - printf("%s", name); - } else - printf("%s", name); - printf("\n"); - - return 0; -} - -static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) { - _cleanup_free_ char *path = NULL; - - assert(bus); - assert(name); - assert(deps); - - path = unit_dbus_path_from_name(name); - if (path == NULL) - return -ENOMEM; - - return bus_get_unit_property_strv(bus, path, "After", deps); -} - -static Hashmap *unit_times_hashmap; - -static int list_dependencies_compare(const void *_a, const void *_b) { - const char **a = (const char**) _a, **b = (const char**) _b; - usec_t usa = 0, usb = 0; - struct unit_times *times; - - times = hashmap_get(unit_times_hashmap, *a); - if (times) - usa = times->activated; - times = hashmap_get(unit_times_hashmap, *b); - if (times) - usb = times->activated; - - return usb - usa; -} - -static int list_dependencies_one(sd_bus *bus, const char *name, unsigned int level, char ***units, - unsigned int branches) { - _cleanup_strv_free_ char **deps = NULL; - char **c; - int r = 0; - usec_t service_longest = 0; - int to_print = 0; - struct unit_times *times; - struct boot_times *boot; - - if (strv_extend(units, name)) - return log_oom(); - - r = list_dependencies_get_dependencies(bus, name, &deps); - if (r < 0) - return r; - - qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare); - - r = acquire_boot_times(bus, &boot); - if (r < 0) - return r; - - STRV_FOREACH(c, deps) { - times = hashmap_get(unit_times_hashmap, *c); - if (times - && times->activated - && times->activated <= boot->finish_time - && (times->activated >= service_longest - || service_longest == 0)) { - service_longest = times->activated; - break; - } - } - - if (service_longest == 0 ) - return r; - - STRV_FOREACH(c, deps) { - times = hashmap_get(unit_times_hashmap, *c); - if (times && times->activated && times->activated <= boot->finish_time && (service_longest - times->activated) <= arg_fuzz) - to_print++; - } - - if (!to_print) - return r; - - STRV_FOREACH(c, deps) { - times = hashmap_get(unit_times_hashmap, *c); - if (!times - || !times->activated - || times->activated > boot->finish_time - || service_longest - times->activated > arg_fuzz) - continue; - - to_print--; - - r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot); - if (r < 0) - return r; - - if (strv_contains(*units, *c)) { - r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0), - true, NULL, boot); - if (r < 0) - return r; - continue; - } - - r = list_dependencies_one(bus, *c, level + 1, units, - (branches << 1) | (to_print ? 1 : 0)); - if (r < 0) - return r; - - if (!to_print) - break; - } - return 0; -} - -static int list_dependencies(sd_bus *bus, const char *name) { - _cleanup_strv_free_ char **units = NULL; - char ts[FORMAT_TIMESPAN_MAX]; - struct unit_times *times; - int r; - const char *id; - _cleanup_free_ char *path = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - struct boot_times *boot; - - assert(bus); - - path = unit_dbus_path_from_name(name); - if (path == NULL) - return -ENOMEM; - - r = sd_bus_get_property( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "Id", - &error, - &reply, - "s"); - if (r < 0) { - log_error("Failed to get ID: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "s", &id); - if (r < 0) - return bus_log_parse_error(r); - - times = hashmap_get(unit_times_hashmap, id); - - r = acquire_boot_times(bus, &boot); - if (r < 0) - return r; - - if (times) { - if (times->time) - printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED, id, - format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ANSI_NORMAL); - else if (times->activated > boot->userspace_time) - printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC)); - else - printf("%s\n", id); - } - - return list_dependencies_one(bus, name, 0, &units, 0); -} - -static int analyze_critical_chain(sd_bus *bus, char *names[]) { - struct unit_times *times; - unsigned int i; - Hashmap *h; - int n, r; - - n = acquire_time_data(bus, ×); - if (n <= 0) - return n; - - h = hashmap_new(&string_hash_ops); - if (!h) - return -ENOMEM; - - for (i = 0; i < (unsigned)n; i++) { - r = hashmap_put(h, times[i].name, ×[i]); - if (r < 0) - return r; - } - unit_times_hashmap = h; - - pager_open(arg_no_pager, false); - - puts("The time after the unit is active or started is printed after the \"@\" character.\n" - "The time the unit takes to start is printed after the \"+\" character.\n"); - - if (!strv_isempty(names)) { - char **name; - STRV_FOREACH(name, names) - list_dependencies(bus, *name); - } else - list_dependencies(bus, SPECIAL_DEFAULT_TARGET); - - hashmap_free(h); - free_unit_times(times, (unsigned) n); - return 0; -} - -static int analyze_blame(sd_bus *bus) { - struct unit_times *times; - unsigned i; - int n; - - n = acquire_time_data(bus, ×); - if (n <= 0) - return n; - - qsort(times, n, sizeof(struct unit_times), compare_unit_time); - - pager_open(arg_no_pager, false); - - for (i = 0; i < (unsigned) n; i++) { - char ts[FORMAT_TIMESPAN_MAX]; - - if (times[i].time > 0) - printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name); - } - - free_unit_times(times, (unsigned) n); - return 0; -} - -static int analyze_time(sd_bus *bus) { - _cleanup_free_ char *buf = NULL; - int r; - - r = pretty_boot_time(bus, &buf); - if (r < 0) - return r; - - puts(buf); - return 0; -} - -static int graph_one_property(sd_bus *bus, const UnitInfo *u, const char* prop, const char *color, char* patterns[], char* from_patterns[], char* to_patterns[]) { - _cleanup_strv_free_ char **units = NULL; - char **unit; - int r; - bool match_patterns; - - assert(u); - assert(prop); - assert(color); - - match_patterns = strv_fnmatch(patterns, u->id, 0); - - if (!strv_isempty(from_patterns) && - !match_patterns && - !strv_fnmatch(from_patterns, u->id, 0)) - return 0; - - r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units); - if (r < 0) - return r; - - STRV_FOREACH(unit, units) { - bool match_patterns2; - - match_patterns2 = strv_fnmatch(patterns, *unit, 0); - - if (!strv_isempty(to_patterns) && - !match_patterns2 && - !strv_fnmatch(to_patterns, *unit, 0)) - continue; - - if (!strv_isempty(patterns) && !match_patterns && !match_patterns2) - continue; - - printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u->id, *unit, color); - } - - return 0; -} - -static int graph_one(sd_bus *bus, const UnitInfo *u, char *patterns[], char *from_patterns[], char *to_patterns[]) { - int r; - - assert(bus); - assert(u); - - if (IN_SET(arg_dot, DEP_ORDER, DEP_ALL)) { - r = graph_one_property(bus, u, "After", "green", patterns, from_patterns, to_patterns); - if (r < 0) - return r; - } - - if (IN_SET(arg_dot, DEP_REQUIRE, DEP_ALL)) { - r = graph_one_property(bus, u, "Requires", "black", patterns, from_patterns, to_patterns); - if (r < 0) - return r; - r = graph_one_property(bus, u, "Requisite", "darkblue", patterns, from_patterns, to_patterns); - if (r < 0) - return r; - r = graph_one_property(bus, u, "Wants", "grey66", patterns, from_patterns, to_patterns); - if (r < 0) - return r; - r = graph_one_property(bus, u, "Conflicts", "red", patterns, from_patterns, to_patterns); - if (r < 0) - return r; - } - - return 0; -} - -static int expand_patterns(sd_bus *bus, char **patterns, char ***ret) { - _cleanup_strv_free_ char **expanded_patterns = NULL; - char **pattern; - int r; - - STRV_FOREACH(pattern, patterns) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *unit = NULL, *unit_id = NULL; - - if (strv_extend(&expanded_patterns, *pattern) < 0) - return log_oom(); - - if (string_is_glob(*pattern)) - continue; - - unit = unit_dbus_path_from_name(*pattern); - if (!unit) - return log_oom(); - - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - unit, - "org.freedesktop.systemd1.Unit", - "Id", - &error, - &unit_id); - if (r < 0) - return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r)); - - if (!streq(*pattern, unit_id)) { - if (strv_extend(&expanded_patterns, unit_id) < 0) - return log_oom(); - } - } - - *ret = expanded_patterns; - expanded_patterns = NULL; /* do not free */ - - return 0; -} - -static int dot(sd_bus *bus, char* patterns[]) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_strv_free_ char **expanded_patterns = NULL; - _cleanup_strv_free_ char **expanded_from_patterns = NULL; - _cleanup_strv_free_ char **expanded_to_patterns = NULL; - int r; - UnitInfo u; - - r = expand_patterns(bus, patterns, &expanded_patterns); - if (r < 0) - return r; - - r = expand_patterns(bus, arg_dot_from_patterns, &expanded_from_patterns); - if (r < 0) - return r; - - r = expand_patterns(bus, arg_dot_to_patterns, &expanded_to_patterns); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "ListUnits", - &error, - &reply, - ""); - if (r < 0) { - log_error("Failed to list units: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); - if (r < 0) - return bus_log_parse_error(r); - - printf("digraph systemd {\n"); - - while ((r = bus_parse_unit_info(reply, &u)) > 0) { - - r = graph_one(bus, &u, expanded_patterns, expanded_from_patterns, expanded_to_patterns); - if (r < 0) - return r; - } - if (r < 0) - return bus_log_parse_error(r); - - printf("}\n"); - - log_info(" Color legend: black = Requires\n" - " dark blue = Requisite\n" - " dark grey = Wants\n" - " red = Conflicts\n" - " green = After\n"); - - if (on_tty()) - log_notice("-- You probably want to process this output with graphviz' dot tool.\n" - "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n"); - - return 0; -} - -static int dump(sd_bus *bus, char **args) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *text = NULL; - int r; - - if (!strv_isempty(args)) { - log_error("Too many arguments."); - return -E2BIG; - } - - pager_open(arg_no_pager, false); - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "Dump", - &error, - &reply, - ""); - if (r < 0) - return log_error_errno(r, "Failed issue method call: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "s", &text); - if (r < 0) - return bus_log_parse_error(r); - - fputs(text, stdout); - return 0; -} - -static int set_log_level(sd_bus *bus, char **args) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(args); - - if (strv_length(args) != 1) { - log_error("This command expects one argument only."); - return -E2BIG; - } - - r = sd_bus_set_property( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "LogLevel", - &error, - "s", - args[0]); - if (r < 0) - return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r)); - - return 0; -} - -static int set_log_target(sd_bus *bus, char **args) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(args); - - if (strv_length(args) != 1) { - log_error("This command expects one argument only."); - return -E2BIG; - } - - r = sd_bus_set_property( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "LogTarget", - &error, - "s", - args[0]); - if (r < 0) - return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r)); - - return 0; -} - -static void help(void) { - - pager_open(arg_no_pager, false); - - printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Profile systemd, show unit dependencies, check unit files.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --system Operate on system systemd instance\n" - " --user Operate on user systemd instance\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --order Show only order in the graph\n" - " --require Show only requirement in the graph\n" - " --from-pattern=GLOB Show only origins in the graph\n" - " --to-pattern=GLOB Show only destinations in the graph\n" - " --fuzz=SECONDS Also print also services which finished SECONDS\n" - " earlier than the latest in the branch\n" - " --man[=BOOL] Do [not] check for existence of man pages\n\n" - "Commands:\n" - " time Print time spent in the kernel\n" - " blame Print list of running units ordered by time to init\n" - " critical-chain Print a tree of the time critical chain of units\n" - " plot Output SVG graphic showing service initialization\n" - " dot Output dependency graph in dot(1) format\n" - " set-log-level LEVEL Set logging threshold for manager\n" - " set-log-target TARGET Set logging target for manager\n" - " dump Output state serialization of service manager\n" - " verify FILE... Check unit files for correctness\n" - , program_invocation_short_name); - - /* When updating this list, including descriptions, apply - * changes to shell-completion/bash/systemd-analyze and - * shell-completion/zsh/_systemd-analyze too. */ -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_ORDER, - ARG_REQUIRE, - ARG_USER, - ARG_SYSTEM, - ARG_DOT_FROM_PATTERN, - ARG_DOT_TO_PATTERN, - ARG_FUZZ, - ARG_NO_PAGER, - ARG_MAN, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "order", no_argument, NULL, ARG_ORDER }, - { "require", no_argument, NULL, ARG_REQUIRE }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN }, - { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN }, - { "fuzz", required_argument, NULL, ARG_FUZZ }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "man", optional_argument, NULL, ARG_MAN }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - {} - }; - - int r, c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_USER: - arg_user = true; - break; - - case ARG_SYSTEM: - arg_user = false; - break; - - case ARG_ORDER: - arg_dot = DEP_ORDER; - break; - - case ARG_REQUIRE: - arg_dot = DEP_REQUIRE; - break; - - case ARG_DOT_FROM_PATTERN: - if (strv_extend(&arg_dot_from_patterns, optarg) < 0) - return log_oom(); - - break; - - case ARG_DOT_TO_PATTERN: - if (strv_extend(&arg_dot_to_patterns, optarg) < 0) - return log_oom(); - - break; - - case ARG_FUZZ: - r = parse_sec(optarg, &arg_fuzz); - if (r < 0) - return r; - break; - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; - break; - - case 'M': - arg_transport = BUS_TRANSPORT_MACHINE; - arg_host = optarg; - break; - - case ARG_MAN: - if (optarg) { - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --man= argument."); - return -EINVAL; - } - - arg_man = !!r; - } else - arg_man = true; - - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option code."); - } - - return 1; /* work to do */ -} - -int main(int argc, char *argv[]) { - int r; - - setlocale(LC_ALL, ""); - setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */ - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (streq_ptr(argv[optind], "verify")) - r = verify_units(argv+optind+1, - arg_user ? UNIT_FILE_USER : UNIT_FILE_SYSTEM, - arg_man); - else { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - - r = bus_connect_transport_systemd(arg_transport, arg_host, arg_user, &bus); - if (r < 0) { - log_error_errno(r, "Failed to create bus connection: %m"); - goto finish; - } - - if (!argv[optind] || streq(argv[optind], "time")) - r = analyze_time(bus); - else if (streq(argv[optind], "blame")) - r = analyze_blame(bus); - else if (streq(argv[optind], "critical-chain")) - r = analyze_critical_chain(bus, argv+optind+1); - else if (streq(argv[optind], "plot")) - r = analyze_plot(bus); - else if (streq(argv[optind], "dot")) - r = dot(bus, argv+optind+1); - else if (streq(argv[optind], "dump")) - r = dump(bus, argv+optind+1); - else if (streq(argv[optind], "set-log-level")) - r = set_log_level(bus, argv+optind+1); - else if (streq(argv[optind], "set-log-target")) - r = set_log_target(bus, argv+optind+1); - else - log_error("Unknown operation '%s'.", argv[optind]); - } - -finish: - pager_close(); - - strv_free(arg_dot_from_patterns); - strv_free(arg_dot_to_patterns); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/ask-password/Makefile b/src/ask-password/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/ask-password/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/ask-password/ask-password.c b/src/ask-password/ask-password.c deleted file mode 100644 index 6d53dd982c..0000000000 --- a/src/ask-password/ask-password.c +++ /dev/null @@ -1,188 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include - -#include "ask-password-api.h" -#include "def.h" -#include "log.h" -#include "macro.h" -#include "strv.h" - -static const char *arg_icon = NULL; -static const char *arg_id = NULL; -static const char *arg_keyname = NULL; -static char *arg_message = NULL; -static usec_t arg_timeout = DEFAULT_TIMEOUT_USEC; -static bool arg_multiple = false; -static bool arg_no_output = false; -static AskPasswordFlags arg_flags = ASK_PASSWORD_PUSH_CACHE; - -static void help(void) { - printf("%s [OPTIONS...] MESSAGE\n\n" - "Query the user for a system passphrase, via the TTY or an UI agent.\n\n" - " -h --help Show this help\n" - " --icon=NAME Icon name\n" - " --id=ID Query identifier (e.g. \"cryptsetup:/dev/sda5\")\n" - " --keyname=NAME Kernel key name for caching passwords (e.g. \"cryptsetup\")\n" - " --timeout=SEC Timeout in seconds\n" - " --echo Do not mask input (useful for usernames)\n" - " --no-tty Ask question via agent even on TTY\n" - " --accept-cached Accept cached passwords\n" - " --multiple List multiple passwords if available\n" - " --no-output Do not print password to standard output\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_ICON = 0x100, - ARG_TIMEOUT, - ARG_ECHO, - ARG_NO_TTY, - ARG_ACCEPT_CACHED, - ARG_MULTIPLE, - ARG_ID, - ARG_KEYNAME, - ARG_NO_OUTPUT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "icon", required_argument, NULL, ARG_ICON }, - { "timeout", required_argument, NULL, ARG_TIMEOUT }, - { "echo", no_argument, NULL, ARG_ECHO }, - { "no-tty", no_argument, NULL, ARG_NO_TTY }, - { "accept-cached", no_argument, NULL, ARG_ACCEPT_CACHED }, - { "multiple", no_argument, NULL, ARG_MULTIPLE }, - { "id", required_argument, NULL, ARG_ID }, - { "keyname", required_argument, NULL, ARG_KEYNAME }, - { "no-output", no_argument, NULL, ARG_NO_OUTPUT }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_ICON: - arg_icon = optarg; - break; - - case ARG_TIMEOUT: - if (parse_sec(optarg, &arg_timeout) < 0) { - log_error("Failed to parse --timeout parameter %s", optarg); - return -EINVAL; - } - break; - - case ARG_ECHO: - arg_flags |= ASK_PASSWORD_ECHO; - break; - - case ARG_NO_TTY: - arg_flags |= ASK_PASSWORD_NO_TTY; - break; - - case ARG_ACCEPT_CACHED: - arg_flags |= ASK_PASSWORD_ACCEPT_CACHED; - break; - - case ARG_MULTIPLE: - arg_multiple = true; - break; - - case ARG_ID: - arg_id = optarg; - break; - - case ARG_KEYNAME: - arg_keyname = optarg; - break; - - case ARG_NO_OUTPUT: - arg_no_output = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (argc > optind) { - arg_message = strv_join(argv + optind, " "); - if (!arg_message) - return log_oom(); - } - - return 1; -} - -int main(int argc, char *argv[]) { - _cleanup_strv_free_erase_ char **l = NULL; - usec_t timeout; - char **p; - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (arg_timeout > 0) - timeout = now(CLOCK_MONOTONIC) + arg_timeout; - else - timeout = 0; - - r = ask_password_auto(arg_message, arg_icon, arg_id, arg_keyname, timeout, arg_flags, &l); - if (r < 0) { - log_error_errno(r, "Failed to query password: %m"); - goto finish; - } - - STRV_FOREACH(p, l) { - if (!arg_no_output) - puts(*p); - - if (!arg_multiple) - break; - } - -finish: - free(arg_message); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/backlight/Makefile b/src/backlight/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/backlight/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/backlight/backlight.c b/src/backlight/backlight.c deleted file mode 100644 index 45be135a23..0000000000 --- a/src/backlight/backlight.c +++ /dev/null @@ -1,434 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "libudev.h" - -#include "alloc-util.h" -#include "def.h" -#include "escape.h" -#include "fileio.h" -#include "mkdir.h" -#include "parse-util.h" -#include "proc-cmdline.h" -#include "string-util.h" -#include "udev-util.h" -#include "util.h" - -static struct udev_device *find_pci_or_platform_parent(struct udev_device *device) { - struct udev_device *parent; - const char *subsystem, *sysname; - - assert(device); - - parent = udev_device_get_parent(device); - if (!parent) - return NULL; - - subsystem = udev_device_get_subsystem(parent); - if (!subsystem) - return NULL; - - sysname = udev_device_get_sysname(parent); - if (!sysname) - return NULL; - - if (streq(subsystem, "drm")) { - const char *c; - - c = startswith(sysname, "card"); - if (!c) - return NULL; - - c += strspn(c, DIGITS); - if (*c == '-') { - /* A connector DRM device, let's ignore all but LVDS and eDP! */ - - if (!startswith(c, "-LVDS-") && - !startswith(c, "-Embedded DisplayPort-")) - return NULL; - } - - } else if (streq(subsystem, "pci")) { - const char *value; - - value = udev_device_get_sysattr_value(parent, "class"); - if (value) { - unsigned long class = 0; - - if (safe_atolu(value, &class) < 0) { - log_warning("Cannot parse PCI class %s of device %s:%s.", - value, subsystem, sysname); - return NULL; - } - - /* Graphics card */ - if (class == 0x30000) - return parent; - } - - } else if (streq(subsystem, "platform")) - return parent; - - return find_pci_or_platform_parent(parent); -} - -static bool same_device(struct udev_device *a, struct udev_device *b) { - assert(a); - assert(b); - - if (!streq_ptr(udev_device_get_subsystem(a), udev_device_get_subsystem(b))) - return false; - - if (!streq_ptr(udev_device_get_sysname(a), udev_device_get_sysname(b))) - return false; - - return true; -} - -static bool validate_device(struct udev *udev, struct udev_device *device) { - _cleanup_udev_enumerate_unref_ struct udev_enumerate *enumerate = NULL; - struct udev_list_entry *item = NULL, *first = NULL; - struct udev_device *parent; - const char *v, *subsystem; - int r; - - assert(udev); - assert(device); - - /* Verify whether we should actually care for a specific - * backlight device. For backlight devices there might be - * multiple ways to access the same control: "firmware" - * (i.e. ACPI), "platform" (i.e. via the machine's EC) and - * "raw" (via the graphics card). In general we should prefer - * "firmware" (i.e. ACPI) or "platform" access over "raw" - * access, in order not to confuse the BIOS/EC, and - * compatibility with possible low-level hotkey handling of - * screen brightness. The kernel will already make sure to - * expose only one of "firmware" and "platform" for the same - * device to userspace. However, we still need to make sure - * that we use "raw" only if no "firmware" or "platform" - * device for the same device exists. */ - - subsystem = udev_device_get_subsystem(device); - if (!streq_ptr(subsystem, "backlight")) - return true; - - v = udev_device_get_sysattr_value(device, "type"); - if (!streq_ptr(v, "raw")) - return true; - - parent = find_pci_or_platform_parent(device); - if (!parent) - return true; - - subsystem = udev_device_get_subsystem(parent); - if (!subsystem) - return true; - - enumerate = udev_enumerate_new(udev); - if (!enumerate) - return true; - - r = udev_enumerate_add_match_subsystem(enumerate, "backlight"); - if (r < 0) - return true; - - r = udev_enumerate_scan_devices(enumerate); - if (r < 0) - return true; - - first = udev_enumerate_get_list_entry(enumerate); - udev_list_entry_foreach(item, first) { - _cleanup_udev_device_unref_ struct udev_device *other; - struct udev_device *other_parent; - const char *other_subsystem; - - other = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); - if (!other) - return true; - - if (same_device(device, other)) - continue; - - v = udev_device_get_sysattr_value(other, "type"); - if (!streq_ptr(v, "platform") && !streq_ptr(v, "firmware")) - continue; - - /* OK, so there's another backlight device, and it's a - * platform or firmware device, so, let's see if we - * can verify it belongs to the same device as - * ours. */ - other_parent = find_pci_or_platform_parent(other); - if (!other_parent) - continue; - - if (same_device(parent, other_parent)) { - /* Both have the same PCI parent, that means - * we are out. */ - log_debug("Skipping backlight device %s, since device %s is on same PCI device and takes precedence.", - udev_device_get_sysname(device), - udev_device_get_sysname(other)); - return false; - } - - other_subsystem = udev_device_get_subsystem(other_parent); - if (streq_ptr(other_subsystem, "platform") && streq_ptr(subsystem, "pci")) { - /* The other is connected to the platform bus - * and we are a PCI device, that also means we - * are out. */ - log_debug("Skipping backlight device %s, since device %s is a platform device and takes precedence.", - udev_device_get_sysname(device), - udev_device_get_sysname(other)); - return false; - } - } - - return true; -} - -static unsigned get_max_brightness(struct udev_device *device) { - int r; - const char *max_brightness_str; - unsigned max_brightness; - - max_brightness_str = udev_device_get_sysattr_value(device, "max_brightness"); - if (!max_brightness_str) { - log_warning("Failed to read 'max_brightness' attribute."); - return 0; - } - - r = safe_atou(max_brightness_str, &max_brightness); - if (r < 0) { - log_warning_errno(r, "Failed to parse 'max_brightness' \"%s\": %m", max_brightness_str); - return 0; - } - - if (max_brightness <= 0) { - log_warning("Maximum brightness is 0, ignoring device."); - return 0; - } - - return max_brightness; -} - -/* Some systems turn the backlight all the way off at the lowest levels. - * clamp_brightness clamps the saved brightness to at least 1 or 5% of - * max_brightness in case of 'backlight' subsystem. This avoids preserving - * an unreadably dim screen, which would otherwise force the user to - * disable state restoration. */ -static void clamp_brightness(struct udev_device *device, char **value, unsigned max_brightness) { - int r; - unsigned brightness, new_brightness, min_brightness; - const char *subsystem; - - r = safe_atou(*value, &brightness); - if (r < 0) { - log_warning_errno(r, "Failed to parse brightness \"%s\": %m", *value); - return; - } - - subsystem = udev_device_get_subsystem(device); - if (streq_ptr(subsystem, "backlight")) - min_brightness = MAX(1U, max_brightness/20); - else - min_brightness = 0; - - new_brightness = CLAMP(brightness, min_brightness, max_brightness); - if (new_brightness != brightness) { - char *old_value = *value; - - r = asprintf(value, "%u", new_brightness); - if (r < 0) { - log_oom(); - return; - } - - log_info("Saved brightness %s %s to %s.", old_value, - new_brightness > brightness ? - "too low; increasing" : "too high; decreasing", - *value); - - free(old_value); - } -} - -int main(int argc, char *argv[]) { - _cleanup_udev_unref_ struct udev *udev = NULL; - _cleanup_udev_device_unref_ struct udev_device *device = NULL; - _cleanup_free_ char *saved = NULL, *ss = NULL, *escaped_ss = NULL, *escaped_sysname = NULL, *escaped_path_id = NULL; - const char *sysname, *path_id; - unsigned max_brightness; - int r; - - if (argc != 3) { - log_error("This program requires two arguments."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - r = mkdir_p("/var/lib/systemd/backlight", 0755); - if (r < 0) { - log_error_errno(r, "Failed to create backlight directory /var/lib/systemd/backlight: %m"); - return EXIT_FAILURE; - } - - udev = udev_new(); - if (!udev) { - log_oom(); - return EXIT_FAILURE; - } - - sysname = strchr(argv[2], ':'); - if (!sysname) { - log_error("Requires a subsystem and sysname pair specifying a backlight device."); - return EXIT_FAILURE; - } - - ss = strndup(argv[2], sysname - argv[2]); - if (!ss) { - log_oom(); - return EXIT_FAILURE; - } - - sysname++; - - if (!streq(ss, "backlight") && !streq(ss, "leds")) { - log_error("Not a backlight or LED device: '%s:%s'", ss, sysname); - return EXIT_FAILURE; - } - - errno = 0; - device = udev_device_new_from_subsystem_sysname(udev, ss, sysname); - if (!device) { - if (errno > 0) - log_error_errno(errno, "Failed to get backlight or LED device '%s:%s': %m", ss, sysname); - else - log_oom(); - - return EXIT_FAILURE; - } - - /* If max_brightness is 0, then there is no actual backlight - * device. This happens on desktops with Asus mainboards - * that load the eeepc-wmi module. - */ - max_brightness = get_max_brightness(device); - if (max_brightness == 0) - return EXIT_SUCCESS; - - escaped_ss = cescape(ss); - if (!escaped_ss) { - log_oom(); - return EXIT_FAILURE; - } - - escaped_sysname = cescape(sysname); - if (!escaped_sysname) { - log_oom(); - return EXIT_FAILURE; - } - - path_id = udev_device_get_property_value(device, "ID_PATH"); - if (path_id) { - escaped_path_id = cescape(path_id); - if (!escaped_path_id) { - log_oom(); - return EXIT_FAILURE; - } - - saved = strjoin("/var/lib/systemd/backlight/", escaped_path_id, ":", escaped_ss, ":", escaped_sysname, NULL); - } else - saved = strjoin("/var/lib/systemd/backlight/", escaped_ss, ":", escaped_sysname, NULL); - - if (!saved) { - log_oom(); - return EXIT_FAILURE; - } - - /* If there are multiple conflicting backlight devices, then - * their probing at boot-time might happen in any order. This - * means the validity checking of the device then is not - * reliable, since it might not see other devices conflicting - * with a specific backlight. To deal with this, we will - * actively delete backlight state files at shutdown (where - * device probing should be complete), so that the validity - * check at boot time doesn't have to be reliable. */ - - if (streq(argv[1], "load")) { - _cleanup_free_ char *value = NULL; - const char *clamp; - - if (shall_restore_state() == 0) - return EXIT_SUCCESS; - - if (!validate_device(udev, device)) - return EXIT_SUCCESS; - - r = read_one_line_file(saved, &value); - if (r < 0) { - - if (r == -ENOENT) - return EXIT_SUCCESS; - - log_error_errno(r, "Failed to read %s: %m", saved); - return EXIT_FAILURE; - } - - clamp = udev_device_get_property_value(device, "ID_BACKLIGHT_CLAMP"); - if (!clamp || parse_boolean(clamp) != 0) /* default to clamping */ - clamp_brightness(device, &value, max_brightness); - - r = udev_device_set_sysattr_value(device, "brightness", value); - if (r < 0) { - log_error_errno(r, "Failed to write system 'brightness' attribute: %m"); - return EXIT_FAILURE; - } - - } else if (streq(argv[1], "save")) { - const char *value; - - if (!validate_device(udev, device)) { - unlink(saved); - return EXIT_SUCCESS; - } - - value = udev_device_get_sysattr_value(device, "brightness"); - if (!value) { - log_error("Failed to read system 'brightness' attribute"); - return EXIT_FAILURE; - } - - r = write_string_file(saved, value, WRITE_STRING_FILE_CREATE); - if (r < 0) { - log_error_errno(r, "Failed to write %s: %m", saved); - return EXIT_FAILURE; - } - - } else { - log_error("Unknown verb %s.", argv[1]); - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} diff --git a/src/basic/.gitignore b/src/basic/.gitignore deleted file mode 100644 index e22411e484..0000000000 --- a/src/basic/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -/cap-from-name.gperf -/cap-from-name.h -/cap-list.txt -/cap-to-name.h -/errno-from-name.gperf -/errno-from-name.h -/errno-list.txt -/errno-to-name.h -/af-from-name.gperf -/af-from-name.h -/af-list.txt -/af-to-name.h -/arphrd-from-name.gperf -/arphrd-from-name.h -/arphrd-list.txt -/arphrd-to-name.h diff --git a/src/basic/Makefile b/src/basic/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/basic/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/basic/MurmurHash2.c b/src/basic/MurmurHash2.c deleted file mode 100644 index 9020793930..0000000000 --- a/src/basic/MurmurHash2.c +++ /dev/null @@ -1,86 +0,0 @@ -//----------------------------------------------------------------------------- -// MurmurHash2 was written by Austin Appleby, and is placed in the public -// domain. The author hereby disclaims copyright to this source code. - -// Note - This code makes a few assumptions about how your machine behaves - - -// 1. We can read a 4-byte value from any address without crashing -// 2. sizeof(int) == 4 - -// And it has a few limitations - - -// 1. It will not work incrementally. -// 2. It will not produce the same results on little-endian and big-endian -// machines. - -#include "MurmurHash2.h" - -//----------------------------------------------------------------------------- -// Platform-specific functions and macros - -// Microsoft Visual Studio - -#if defined(_MSC_VER) - -#define BIG_CONSTANT(x) (x) - -// Other compilers - -#else // defined(_MSC_VER) - -#define BIG_CONSTANT(x) (x##LLU) - -#endif // !defined(_MSC_VER) - -//----------------------------------------------------------------------------- - -uint32_t MurmurHash2 ( const void * key, int len, uint32_t seed ) -{ - // 'm' and 'r' are mixing constants generated offline. - // They're not really 'magic', they just happen to work well. - - const uint32_t m = 0x5bd1e995; - const int r = 24; - - // Initialize the hash to a 'random' value - - uint32_t h = seed ^ len; - - // Mix 4 bytes at a time into the hash - - const unsigned char * data = (const unsigned char *)key; - - while (len >= 4) - { - uint32_t k = *(uint32_t*)data; - - k *= m; - k ^= k >> r; - k *= m; - - h *= m; - h ^= k; - - data += 4; - len -= 4; - } - - // Handle the last few bytes of the input array - - switch(len) - { - case 3: h ^= data[2] << 16; - case 2: h ^= data[1] << 8; - case 1: h ^= data[0]; - h *= m; - }; - - // Do a few final mixes of the hash to ensure the last few - // bytes are well-incorporated. - - h ^= h >> 13; - h *= m; - h ^= h >> 15; - - return h; -} diff --git a/src/basic/MurmurHash2.h b/src/basic/MurmurHash2.h deleted file mode 100644 index 93362dd485..0000000000 --- a/src/basic/MurmurHash2.h +++ /dev/null @@ -1,33 +0,0 @@ -//----------------------------------------------------------------------------- -// MurmurHash2 was written by Austin Appleby, and is placed in the public -// domain. The author hereby disclaims copyright to this source code. - -#ifndef _MURMURHASH2_H_ -#define _MURMURHASH2_H_ - -//----------------------------------------------------------------------------- -// Platform-specific functions and macros - -// Microsoft Visual Studio - -#if defined(_MSC_VER) - -typedef unsigned char uint8_t; -typedef unsigned long uint32_t; -typedef unsigned __int64 uint64_t; - -// Other compilers - -#else // defined(_MSC_VER) - -#include - -#endif // !defined(_MSC_VER) - -//----------------------------------------------------------------------------- - -uint32_t MurmurHash2 ( const void * key, int len, uint32_t seed ); - -//----------------------------------------------------------------------------- - -#endif // _MURMURHASH2_H_ diff --git a/src/basic/af-list.c b/src/basic/af-list.c deleted file mode 100644 index 3fac9c508b..0000000000 --- a/src/basic/af-list.c +++ /dev/null @@ -1,56 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 - -#include "af-list.h" -#include "macro.h" - -static const struct af_name* lookup_af(register const char *str, register unsigned int len); - -#include "af-from-name.h" -#include "af-to-name.h" - -const char *af_to_name(int id) { - - if (id <= 0) - return NULL; - - if (id >= (int) ELEMENTSOF(af_names)) - return NULL; - - return af_names[id]; -} - -int af_from_name(const char *name) { - const struct af_name *sc; - - assert(name); - - sc = lookup_af(name, strlen(name)); - if (!sc) - return AF_UNSPEC; - - return sc->id; -} - -int af_max(void) { - return ELEMENTSOF(af_names); -} diff --git a/src/basic/af-list.h b/src/basic/af-list.h deleted file mode 100644 index 6a4cc03839..0000000000 --- a/src/basic/af-list.h +++ /dev/null @@ -1,41 +0,0 @@ -#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 "string-util.h" - -const char *af_to_name(int id); -int af_from_name(const char *name); - -static inline const char* af_to_name_short(int id) { - const char *f; - - if (id == AF_UNSPEC) - return "*"; - - f = af_to_name(id); - if (!f) - return "unknown"; - - assert(startswith(f, "AF_")); - return f + 3; -} - -int af_max(void); diff --git a/src/basic/alloc-util.c b/src/basic/alloc-util.c deleted file mode 100644 index b540dcddf5..0000000000 --- a/src/basic/alloc-util.c +++ /dev/null @@ -1,83 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 - -#include "alloc-util.h" -#include "macro.h" -#include "util.h" - -void* memdup(const void *p, size_t l) { - void *r; - - assert(p); - - r = malloc(l); - if (!r) - return NULL; - - memcpy(r, p, l); - return r; -} - -void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size) { - size_t a, newalloc; - void *q; - - assert(p); - assert(allocated); - - if (*allocated >= need) - return *p; - - newalloc = MAX(need * 2, 64u / size); - a = newalloc * size; - - /* check for overflows */ - if (a < size * need) - return NULL; - - q = realloc(*p, a); - if (!q) - return NULL; - - *p = q; - *allocated = newalloc; - return q; -} - -void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size) { - size_t prev; - uint8_t *q; - - assert(p); - assert(allocated); - - prev = *allocated; - - q = greedy_realloc(p, allocated, need, size); - if (!q) - return NULL; - - if (*allocated > prev) - memzero(q + prev * size, (*allocated - prev) * size); - - return q; -} diff --git a/src/basic/alloc-util.h b/src/basic/alloc-util.h deleted file mode 100644 index ceeee519b7..0000000000 --- a/src/basic/alloc-util.h +++ /dev/null @@ -1,111 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include - -#include "macro.h" - -#define new(t, n) ((t*) malloc_multiply(sizeof(t), (n))) - -#define new0(t, n) ((t*) calloc((n), sizeof(t))) - -#define newa(t, n) ((t*) alloca(sizeof(t)*(n))) - -#define newa0(t, n) ((t*) alloca0(sizeof(t)*(n))) - -#define newdup(t, p, n) ((t*) memdup_multiply(p, sizeof(t), (n))) - -#define malloc0(n) (calloc(1, (n))) - -static inline void *mfree(void *memory) { - free(memory); - return NULL; -} - -void* memdup(const void *p, size_t l) _alloc_(2); - -static inline void freep(void *p) { - free(*(void**) p); -} - -#define _cleanup_free_ _cleanup_(freep) - -static inline bool size_multiply_overflow(size_t size, size_t need) { - return _unlikely_(need != 0 && size > (SIZE_MAX / need)); -} - -_malloc_ _alloc_(1, 2) static inline void *malloc_multiply(size_t size, size_t need) { - if (size_multiply_overflow(size, need)) - return NULL; - - return malloc(size * need); -} - -_alloc_(2, 3) static inline void *realloc_multiply(void *p, size_t size, size_t need) { - if (size_multiply_overflow(size, need)) - return NULL; - - return realloc(p, size * need); -} - -_alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t size, size_t need) { - if (size_multiply_overflow(size, need)) - return NULL; - - return memdup(p, size * need); -} - -void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size); -void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size); - -#define GREEDY_REALLOC(array, allocated, need) \ - greedy_realloc((void**) &(array), &(allocated), (need), sizeof((array)[0])) - -#define GREEDY_REALLOC0(array, allocated, need) \ - greedy_realloc0((void**) &(array), &(allocated), (need), sizeof((array)[0])) - -#define alloca0(n) \ - ({ \ - char *_new_; \ - size_t _len_ = n; \ - _new_ = alloca(_len_); \ - (void *) memset(_new_, 0, _len_); \ - }) - -/* It's not clear what alignment glibc/gcc alloca() guarantee, hence provide a guaranteed safe version */ -#define alloca_align(size, align) \ - ({ \ - void *_ptr_; \ - size_t _mask_ = (align) - 1; \ - _ptr_ = alloca((size) + _mask_); \ - (void*)(((uintptr_t)_ptr_ + _mask_) & ~_mask_); \ - }) - -#define alloca0_align(size, align) \ - ({ \ - void *_new_; \ - size_t _size_ = (size); \ - _new_ = alloca_align(_size_, (align)); \ - (void*)memset(_new_, 0, _size_); \ - }) diff --git a/src/basic/architecture.c b/src/basic/architecture.c deleted file mode 100644 index b1c8e91f50..0000000000 --- a/src/basic/architecture.c +++ /dev/null @@ -1,179 +0,0 @@ -/*** - 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" -#include "macro.h" -#include "string-table.h" -#include "string-util.h" - -int 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 distinguish CPUs not CPU features, but - * actual architectures, i.e. that have genuinely different - * code. */ - - static const struct { - const char *machine; - int 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 }, - { "ppc64le", ARCHITECTURE_PPC64_LE }, - { "ppc", ARCHITECTURE_PPC }, - { "ppcle", ARCHITECTURE_PPC_LE }, -#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__) - { "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__) - { "sh5", ARCHITECTURE_SH64 }, - { "sh2", ARCHITECTURE_SH }, - { "sh2a", ARCHITECTURE_SH }, - { "sh3", ARCHITECTURE_SH }, - { "sh4", ARCHITECTURE_SH }, - { "sh4a", ARCHITECTURE_SH }, -#elif defined(__m68k__) - { "m68k", ARCHITECTURE_M68K }, -#elif defined(__tilegx__) - { "tilegx", ARCHITECTURE_TILEGX }, -#elif defined(__cris__) - { "crisv32", ARCHITECTURE_CRIS }, -#elif defined(__nios2__) - { "nios2", ARCHITECTURE_NIOS2 }, -#else -#error "Please register your architecture here!" -#endif - }; - - static int 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_PPC_LE] = "ppc-le", - [ARCHITECTURE_PPC64] = "ppc64", - [ARCHITECTURE_PPC64_LE] = "ppc64-le", - [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_MIPS_LE] = "mips-le", - [ARCHITECTURE_MIPS64] = "mips64", - [ARCHITECTURE_MIPS64_LE] = "mips64-le", - [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", - [ARCHITECTURE_TILEGX] = "tilegx", - [ARCHITECTURE_CRIS] = "cris", - [ARCHITECTURE_NIOS2] = "nios2", -}; - -DEFINE_STRING_TABLE_LOOKUP(architecture, int); diff --git a/src/basic/architecture.h b/src/basic/architecture.h deleted file mode 100644 index b3e4d85906..0000000000 --- a/src/basic/architecture.h +++ /dev/null @@ -1,199 +0,0 @@ -#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 - -#include "macro.h" -#include "util.h" - -/* A cleaned up architecture definition. We don't want to get lost in - * processor features, models, generations or even ABIs. Hence we - * focus on general family, and distinguish word width and - * endianness. */ - -enum { - ARCHITECTURE_X86 = 0, - ARCHITECTURE_X86_64, - ARCHITECTURE_PPC, - ARCHITECTURE_PPC_LE, - ARCHITECTURE_PPC64, - ARCHITECTURE_PPC64_LE, - ARCHITECTURE_IA64, - ARCHITECTURE_PARISC, - ARCHITECTURE_PARISC64, - ARCHITECTURE_S390, - ARCHITECTURE_S390X, - ARCHITECTURE_SPARC, - ARCHITECTURE_SPARC64, - ARCHITECTURE_MIPS, - ARCHITECTURE_MIPS_LE, - ARCHITECTURE_MIPS64, - ARCHITECTURE_MIPS64_LE, - ARCHITECTURE_ALPHA, - ARCHITECTURE_ARM, - ARCHITECTURE_ARM_BE, - ARCHITECTURE_ARM64, - ARCHITECTURE_ARM64_BE, - ARCHITECTURE_SH, - ARCHITECTURE_SH64, - ARCHITECTURE_M68K, - ARCHITECTURE_TILEGX, - ARCHITECTURE_CRIS, - ARCHITECTURE_NIOS2, - _ARCHITECTURE_MAX, - _ARCHITECTURE_INVALID = -1 -}; - -int uname_architecture(void); - -/* - * LIB_ARCH_TUPLE should resolve to the local library path - * architecture tuple systemd is built for, according to the Debian - * tuple list: - * - * https://wiki.debian.org/Multiarch/Tuples - * - * This is used in library search paths that should understand - * Debian's paths on all distributions. - */ - -#if defined(__x86_64__) -# define native_architecture() ARCHITECTURE_X86_64 -# define LIB_ARCH_TUPLE "x86_64-linux-gnu" -# define SECONDARY_ARCHITECTURE ARCHITECTURE_X86 -#elif defined(__i386__) -# define native_architecture() ARCHITECTURE_X86 -# define LIB_ARCH_TUPLE "i386-linux-gnu" -#elif defined(__powerpc64__) -# if __BYTE_ORDER == __BIG_ENDIAN -# define native_architecture() ARCHITECTURE_PPC64 -# define LIB_ARCH_TUPLE "ppc64-linux-gnu" -# define SECONDARY_ARCHITECTURE ARCHITECTURE_PPC -# else -# define native_architecture() ARCHITECTURE_PPC64_LE -# define LIB_ARCH_TUPLE "powerpc64le-linux-gnu" -# define SECONDARY_ARCHITECTURE ARCHITECTURE_PPC_LE -# endif -#elif defined(__powerpc__) -# if __BYTE_ORDER == __BIG_ENDIAN -# define native_architecture() ARCHITECTURE_PPC -# define LIB_ARCH_TUPLE "powerpc-linux-gnu" -# else -# define native_architecture() ARCHITECTURE_PPC_LE -# error "Missing LIB_ARCH_TUPLE for PPCLE" -# endif -#elif defined(__ia64__) -# define native_architecture() ARCHITECTURE_IA64 -# define LIB_ARCH_TUPLE "ia64-linux-gnu" -#elif defined(__hppa64__) -# define native_architecture() ARCHITECTURE_PARISC64 -# error "Missing LIB_ARCH_TUPLE for HPPA64" -#elif defined(__hppa__) -# define native_architecture() ARCHITECTURE_PARISC -# define LIB_ARCH_TUPLE "hppa‑linux‑gnu" -#elif defined(__s390x__) -# define native_architecture() ARCHITECTURE_S390X -# define LIB_ARCH_TUPLE "s390x-linux-gnu" -# define SECONDARY_ARCHITECTURE ARCHITECTURE_S390 -#elif defined(__s390__) -# define native_architecture() ARCHITECTURE_S390 -# define LIB_ARCH_TUPLE "s390-linux-gnu" -#elif defined(__sparc__) && defined (__arch64__) -# define native_architecture() ARCHITECTURE_SPARC64 -# define LIB_ARCH_TUPLE "sparc64-linux-gnu" -#elif defined(__sparc__) -# define native_architecture() ARCHITECTURE_SPARC -# define LIB_ARCH_TUPLE "sparc-linux-gnu" -#elif defined(__mips64__) -# if __BYTE_ORDER == __BIG_ENDIAN -# define native_architecture() ARCHITECTURE_MIPS64 -# error "Missing LIB_ARCH_TUPLE for MIPS64" -# else -# define native_architecture() ARCHITECTURE_MIPS64_LE -# error "Missing LIB_ARCH_TUPLE for MIPS64_LE" -# endif -#elif defined(__mips__) -# if __BYTE_ORDER == __BIG_ENDIAN -# define native_architecture() ARCHITECTURE_MIPS -# define LIB_ARCH_TUPLE "mips-linux-gnu" -# else -# define native_architecture() ARCHITECTURE_MIPS_LE -# define LIB_ARCH_TUPLE "mipsel-linux-gnu" -# endif -#elif defined(__alpha__) -# define native_architecture() ARCHITECTURE_ALPHA -# define LIB_ARCH_TUPLE "alpha-linux-gnu" -#elif defined(__aarch64__) -# if __BYTE_ORDER == __BIG_ENDIAN -# define native_architecture() ARCHITECTURE_ARM64_BE -# define LIB_ARCH_TUPLE "aarch64_be-linux-gnu" -# else -# define native_architecture() ARCHITECTURE_ARM64 -# define LIB_ARCH_TUPLE "aarch64-linux-gnu" -# endif -#elif defined(__arm__) -# if __BYTE_ORDER == __BIG_ENDIAN -# define native_architecture() ARCHITECTURE_ARM_BE -# if defined(__ARM_EABI__) -# if defined(__ARM_PCS_VFP) -# define LIB_ARCH_TUPLE "armeb-linux-gnueabihf" -# else -# define LIB_ARCH_TUPLE "armeb-linux-gnueabi" -# endif -# else -# define LIB_ARCH_TUPLE "armeb-linux-gnu" -# endif -# else -# define native_architecture() ARCHITECTURE_ARM -# if defined(__ARM_EABI__) -# if defined(__ARM_PCS_VFP) -# define LIB_ARCH_TUPLE "arm-linux-gnueabihf" -# else -# define LIB_ARCH_TUPLE "arm-linux-gnueabi" -# endif -# else -# define LIB_ARCH_TUPLE "arm-linux-gnu" -# endif -# endif -#elif defined(__sh64__) -# define native_architecture() ARCHITECTURE_SH64 -# error "Missing LIB_ARCH_TUPLE for SH64" -#elif defined(__sh__) -# define native_architecture() ARCHITECTURE_SH -# define LIB_ARCH_TUPLE "sh4-linux-gnu" -#elif defined(__m68k__) -# define native_architecture() ARCHITECTURE_M68K -# define LIB_ARCH_TUPLE "m68k-linux-gnu" -#elif defined(__tilegx__) -# define native_architecture() ARCHITECTURE_TILEGX -# error "Missing LIB_ARCH_TUPLE for TILEGX" -#elif defined(__cris__) -# define native_architecture() ARCHITECTURE_CRIS -# error "Missing LIB_ARCH_TUPLE for CRIS" -#elif defined(__nios2__) -# define native_architecture() ARCHITECTURE_NIOS2 -# define LIB_ARCH_TUPLE "nios2-linux-gnu" -#else -# error "Please register your architecture here!" -#endif - -const char *architecture_to_string(int a) _const_; -int architecture_from_string(const char *s) _pure_; diff --git a/src/basic/arphrd-list.c b/src/basic/arphrd-list.c deleted file mode 100644 index 6792d1ee3f..0000000000 --- a/src/basic/arphrd-list.c +++ /dev/null @@ -1,56 +0,0 @@ -/*** - 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 - -#include "arphrd-list.h" -#include "macro.h" - -static const struct arphrd_name* lookup_arphrd(register const char *str, register unsigned int len); - -#include "arphrd-from-name.h" -#include "arphrd-to-name.h" - -const char *arphrd_to_name(int id) { - - if (id <= 0) - return NULL; - - if (id >= (int) ELEMENTSOF(arphrd_names)) - return NULL; - - return arphrd_names[id]; -} - -int arphrd_from_name(const char *name) { - const struct arphrd_name *sc; - - assert(name); - - sc = lookup_arphrd(name, strlen(name)); - if (!sc) - return 0; - - return sc->id; -} - -int arphrd_max(void) { - return ELEMENTSOF(arphrd_names); -} diff --git a/src/basic/arphrd-list.h b/src/basic/arphrd-list.h deleted file mode 100644 index c0f8758dbe..0000000000 --- a/src/basic/arphrd-list.h +++ /dev/null @@ -1,25 +0,0 @@ -#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 . -***/ - -const char *arphrd_to_name(int id); -int arphrd_from_name(const char *name); - -int arphrd_max(void); diff --git a/src/basic/async.c b/src/basic/async.c deleted file mode 100644 index a1f163f27b..0000000000 --- a/src/basic/async.c +++ /dev/null @@ -1,94 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 -#include -#include - -#include "async.h" -#include "fd-util.h" -#include "log.h" -#include "macro.h" -#include "util.h" - -int asynchronous_job(void* (*func)(void *p), void *arg) { - pthread_attr_t a; - pthread_t t; - int r; - - /* It kinda sucks that we have to resort to threads to - * implement an asynchronous sync(), but well, such is - * life. - * - * Note that issuing this command right before exiting a - * process will cause the process to wait for the sync() to - * complete. This function hence is nicely asynchronous really - * only in long running processes. */ - - r = pthread_attr_init(&a); - if (r > 0) - return -r; - - r = pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED); - if (r > 0) - goto finish; - - r = pthread_create(&t, &a, func, arg); - -finish: - pthread_attr_destroy(&a); - return -r; -} - -static void *sync_thread(void *p) { - sync(); - return NULL; -} - -int asynchronous_sync(void) { - log_debug("Spawning new thread for sync"); - - return asynchronous_job(sync_thread, NULL); -} - -static void *close_thread(void *p) { - assert_se(close_nointr(PTR_TO_FD(p)) != -EBADF); - return NULL; -} - -int asynchronous_close(int fd) { - int r; - - /* This is supposed to behave similar to safe_close(), but - * actually invoke close() asynchronously, so that it will - * never block. Ideally the kernel would have an API for this, - * but it doesn't, so we work around it, and hide this as a - * far away as we can. */ - - if (fd >= 0) { - PROTECT_ERRNO; - - r = asynchronous_job(close_thread, FD_TO_PTR(fd)); - if (r < 0) - assert_se(close_nointr(fd) != -EBADF); - } - - return -1; -} diff --git a/src/basic/async.h b/src/basic/async.h deleted file mode 100644 index 9bd13ff6e0..0000000000 --- a/src/basic/async.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -int asynchronous_job(void* (*func)(void *p), void *arg); - -int asynchronous_sync(void); -int asynchronous_close(int fd); diff --git a/src/basic/audit-util.c b/src/basic/audit-util.c deleted file mode 100644 index 5741fecdd6..0000000000 --- a/src/basic/audit-util.c +++ /dev/null @@ -1,104 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include - -#include "alloc-util.h" -#include "audit-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "macro.h" -#include "parse-util.h" -#include "process-util.h" -#include "user-util.h" - -int audit_session_from_pid(pid_t pid, uint32_t *id) { - _cleanup_free_ char *s = NULL; - const char *p; - uint32_t u; - int r; - - assert(id); - - /* We don't convert ENOENT to ESRCH here, since we can't - * really distuingish between "audit is not available in the - * kernel" and "the process does not exist", both which will - * result in ENOENT. */ - - p = procfs_file_alloca(pid, "sessionid"); - - r = read_one_line_file(p, &s); - if (r < 0) - return r; - - r = safe_atou32(s, &u); - if (r < 0) - return r; - - if (u == AUDIT_SESSION_INVALID || u <= 0) - return -ENODATA; - - *id = u; - return 0; -} - -int audit_loginuid_from_pid(pid_t pid, uid_t *uid) { - _cleanup_free_ char *s = NULL; - const char *p; - uid_t u; - int r; - - assert(uid); - - p = procfs_file_alloca(pid, "loginuid"); - - r = read_one_line_file(p, &s); - if (r < 0) - return r; - - r = parse_uid(s, &u); - if (r == -ENXIO) /* the UID was -1 */ - return -ENODATA; - if (r < 0) - return r; - - *uid = (uid_t) u; - return 0; -} - -bool use_audit(void) { - static int cached_use = -1; - - if (cached_use < 0) { - int fd; - - fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_AUDIT); - if (fd < 0) - cached_use = errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT; - else { - cached_use = true; - safe_close(fd); - } - } - - return cached_use; -} diff --git a/src/basic/audit-util.h b/src/basic/audit-util.h deleted file mode 100644 index e048503991..0000000000 --- a/src/basic/audit-util.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include - -#define AUDIT_SESSION_INVALID ((uint32_t) -1) - -int audit_session_from_pid(pid_t pid, uint32_t *id); -int audit_loginuid_from_pid(pid_t pid, uid_t *uid); - -bool use_audit(void); diff --git a/src/basic/barrier.c b/src/basic/barrier.c deleted file mode 100644 index 2da633b311..0000000000 --- a/src/basic/barrier.c +++ /dev/null @@ -1,415 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann - - 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 -#include -#include -#include -#include -#include -#include -#include - -#include "barrier.h" -#include "fd-util.h" -#include "macro.h" - -/** - * Barriers - * This barrier implementation provides a simple synchronization method based - * on file-descriptors that can safely be used between threads and processes. A - * barrier object contains 2 shared counters based on eventfd. Both processes - * can now place barriers and wait for the other end to reach a random or - * specific barrier. - * Barriers are numbered, so you can either wait for the other end to reach any - * barrier or the last barrier that you placed. This way, you can use barriers - * for one-way *and* full synchronization. Note that even-though barriers are - * numbered, these numbers are internal and recycled once both sides reached the - * same barrier (implemented as a simple signed counter). It is thus not - * possible to address barriers by their ID. - * - * Barrier-API: Both ends can place as many barriers via barrier_place() as - * they want and each pair of barriers on both sides will be implicitly linked. - * Each side can use the barrier_wait/sync_*() family of calls to wait for the - * other side to place a specific barrier. barrier_wait_next() waits until the - * other side calls barrier_place(). No links between the barriers are - * considered and this simply serves as most basic asynchronous barrier. - * barrier_sync_next() is like barrier_wait_next() and waits for the other side - * to place their next barrier via barrier_place(). However, it only waits for - * barriers that are linked to a barrier we already placed. If the other side - * already placed more barriers than we did, barrier_sync_next() returns - * immediately. - * barrier_sync() extends barrier_sync_next() and waits until the other end - * placed as many barriers via barrier_place() as we did. If they already placed - * as many as we did (or more), it returns immediately. - * - * Additionally to basic barriers, an abortion event is available. - * barrier_abort() places an abortion event that cannot be undone. An abortion - * immediately cancels all placed barriers and replaces them. Any running and - * following wait/sync call besides barrier_wait_abortion() will immediately - * return false on both sides (otherwise, they always return true). - * barrier_abort() can be called multiple times on both ends and will be a - * no-op if already called on this side. - * barrier_wait_abortion() can be used to wait for the other side to call - * barrier_abort() and is the only wait/sync call that does not return - * immediately if we aborted outself. It only returns once the other side - * called barrier_abort(). - * - * Barriers can be used for in-process and inter-process synchronization. - * However, for in-process synchronization you could just use mutexes. - * Therefore, main target is IPC and we require both sides to *not* share the FD - * table. If that's given, barriers provide target tracking: If the remote side - * exit()s, an abortion event is implicitly queued on the other side. This way, - * a sync/wait call will be woken up if the remote side crashed or exited - * unexpectedly. However, note that these abortion events are only queued if the - * barrier-queue has been drained. Therefore, it is safe to place a barrier and - * exit. The other side can safely wait on the barrier even though the exit - * queued an abortion event. Usually, the abortion event would overwrite the - * barrier, however, that's not true for exit-abortion events. Those are only - * queued if the barrier-queue is drained (thus, the receiving side has placed - * more barriers than the remote side). - */ - -/** - * barrier_create() - Initialize a barrier object - * @obj: barrier to initialize - * - * This initializes a barrier object. The caller is responsible of allocating - * the memory and keeping it valid. The memory does not have to be zeroed - * beforehand. - * Two eventfd objects are allocated for each barrier. If allocation fails, an - * error is returned. - * - * If this function fails, the barrier is reset to an invalid state so it is - * safe to call barrier_destroy() on the object regardless whether the - * initialization succeeded or not. - * - * The caller is responsible to destroy the object via barrier_destroy() before - * releasing the underlying memory. - * - * Returns: 0 on success, negative error code on failure. - */ -int barrier_create(Barrier *b) { - _cleanup_(barrier_destroyp) Barrier *staging = b; - int r; - - assert(b); - - b->me = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); - if (b->me < 0) - return -errno; - - b->them = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); - if (b->them < 0) - return -errno; - - r = pipe2(b->pipe, O_CLOEXEC | O_NONBLOCK); - if (r < 0) - return -errno; - - staging = NULL; - return 0; -} - -/** - * barrier_destroy() - Destroy a barrier object - * @b: barrier to destroy or NULL - * - * This destroys a barrier object that has previously been passed to - * barrier_create(). The object is released and reset to invalid - * state. Therefore, it is safe to call barrier_destroy() multiple - * times or even if barrier_create() failed. However, barrier must be - * always initialized with BARRIER_NULL. - * - * If @b is NULL, this is a no-op. - */ -void barrier_destroy(Barrier *b) { - if (!b) - return; - - b->me = safe_close(b->me); - b->them = safe_close(b->them); - safe_close_pair(b->pipe); - b->barriers = 0; -} - -/** - * barrier_set_role() - Set the local role of the barrier - * @b: barrier to operate on - * @role: role to set on the barrier - * - * This sets the roles on a barrier object. This is needed to know - * which side of the barrier you're on. Usually, the parent creates - * the barrier via barrier_create() and then calls fork() or clone(). - * Therefore, the FDs are duplicated and the child retains the same - * barrier object. - * - * Both sides need to call barrier_set_role() after fork() or clone() - * are done. If this is not done, barriers will not work correctly. - * - * Note that barriers could be supported without fork() or clone(). However, - * this is currently not needed so it hasn't been implemented. - */ -void barrier_set_role(Barrier *b, unsigned int role) { - int fd; - - assert(b); - assert(role == BARRIER_PARENT || role == BARRIER_CHILD); - /* make sure this is only called once */ - assert(b->pipe[0] >= 0 && b->pipe[1] >= 0); - - if (role == BARRIER_PARENT) - b->pipe[1] = safe_close(b->pipe[1]); - else { - b->pipe[0] = safe_close(b->pipe[0]); - - /* swap me/them for children */ - fd = b->me; - b->me = b->them; - b->them = fd; - } -} - -/* places barrier; returns false if we aborted, otherwise true */ -static bool barrier_write(Barrier *b, uint64_t buf) { - ssize_t len; - - /* prevent new sync-points if we already aborted */ - if (barrier_i_aborted(b)) - return false; - - assert(b->me >= 0); - do { - len = write(b->me, &buf, sizeof(buf)); - } while (len < 0 && IN_SET(errno, EAGAIN, EINTR)); - - if (len != sizeof(buf)) - goto error; - - /* lock if we aborted */ - if (buf >= (uint64_t)BARRIER_ABORTION) { - if (barrier_they_aborted(b)) - b->barriers = BARRIER_WE_ABORTED; - else - b->barriers = BARRIER_I_ABORTED; - } else if (!barrier_is_aborted(b)) - b->barriers += buf; - - return !barrier_i_aborted(b); - -error: - /* If there is an unexpected error, we have to make this fatal. There - * is no way we can recover from sync-errors. Therefore, we close the - * pipe-ends and treat this as abortion. The other end will notice the - * pipe-close and treat it as abortion, too. */ - - safe_close_pair(b->pipe); - b->barriers = BARRIER_WE_ABORTED; - return false; -} - -/* waits for barriers; returns false if they aborted, otherwise true */ -static bool barrier_read(Barrier *b, int64_t comp) { - if (barrier_they_aborted(b)) - return false; - - while (b->barriers > comp) { - struct pollfd pfd[2] = { - { .fd = b->pipe[0] >= 0 ? b->pipe[0] : b->pipe[1], - .events = POLLHUP }, - { .fd = b->them, - .events = POLLIN }}; - uint64_t buf; - int r; - - r = poll(pfd, 2, -1); - if (r < 0 && IN_SET(errno, EAGAIN, EINTR)) - continue; - else if (r < 0) - goto error; - - if (pfd[1].revents) { - ssize_t len; - - /* events on @them signal new data for us */ - len = read(b->them, &buf, sizeof(buf)); - if (len < 0 && IN_SET(errno, EAGAIN, EINTR)) - continue; - - if (len != sizeof(buf)) - goto error; - } else if (pfd[0].revents & (POLLHUP | POLLERR | POLLNVAL)) - /* POLLHUP on the pipe tells us the other side exited. - * We treat this as implicit abortion. But we only - * handle it if there's no event on the eventfd. This - * guarantees that exit-abortions do not overwrite real - * barriers. */ - buf = BARRIER_ABORTION; - else - continue; - - /* lock if they aborted */ - if (buf >= (uint64_t)BARRIER_ABORTION) { - if (barrier_i_aborted(b)) - b->barriers = BARRIER_WE_ABORTED; - else - b->barriers = BARRIER_THEY_ABORTED; - } else if (!barrier_is_aborted(b)) - b->barriers -= buf; - } - - return !barrier_they_aborted(b); - -error: - /* If there is an unexpected error, we have to make this fatal. There - * is no way we can recover from sync-errors. Therefore, we close the - * pipe-ends and treat this as abortion. The other end will notice the - * pipe-close and treat it as abortion, too. */ - - safe_close_pair(b->pipe); - b->barriers = BARRIER_WE_ABORTED; - return false; -} - -/** - * barrier_place() - Place a new barrier - * @b: barrier object - * - * This places a new barrier on the barrier object. If either side already - * aborted, this is a no-op and returns "false". Otherwise, the barrier is - * placed and this returns "true". - * - * Returns: true if barrier was placed, false if either side aborted. - */ -bool barrier_place(Barrier *b) { - assert(b); - - if (barrier_is_aborted(b)) - return false; - - barrier_write(b, BARRIER_SINGLE); - return true; -} - -/** - * barrier_abort() - Abort the synchronization - * @b: barrier object to abort - * - * This aborts the barrier-synchronization. If barrier_abort() was already - * called on this side, this is a no-op. Otherwise, the barrier is put into the - * ABORT-state and will stay there. The other side is notified about the - * abortion. Any following attempt to place normal barriers or to wait on normal - * barriers will return immediately as "false". - * - * You can wait for the other side to call barrier_abort(), too. Use - * barrier_wait_abortion() for that. - * - * Returns: false if the other side already aborted, true otherwise. - */ -bool barrier_abort(Barrier *b) { - assert(b); - - barrier_write(b, BARRIER_ABORTION); - return !barrier_they_aborted(b); -} - -/** - * barrier_wait_next() - Wait for the next barrier of the other side - * @b: barrier to operate on - * - * This waits until the other side places its next barrier. This is independent - * of any barrier-links and just waits for any next barrier of the other side. - * - * If either side aborted, this returns false. - * - * Returns: false if either side aborted, true otherwise. - */ -bool barrier_wait_next(Barrier *b) { - assert(b); - - if (barrier_is_aborted(b)) - return false; - - barrier_read(b, b->barriers - 1); - return !barrier_is_aborted(b); -} - -/** - * barrier_wait_abortion() - Wait for the other side to abort - * @b: barrier to operate on - * - * This waits until the other side called barrier_abort(). This can be called - * regardless whether the local side already called barrier_abort() or not. - * - * If the other side has already aborted, this returns immediately. - * - * Returns: false if the local side aborted, true otherwise. - */ -bool barrier_wait_abortion(Barrier *b) { - assert(b); - - barrier_read(b, BARRIER_THEY_ABORTED); - return !barrier_i_aborted(b); -} - -/** - * barrier_sync_next() - Wait for the other side to place a next linked barrier - * @b: barrier to operate on - * - * This is like barrier_wait_next() and waits for the other side to call - * barrier_place(). However, this only waits for linked barriers. That means, if - * the other side already placed more barriers than (or as much as) we did, this - * returns immediately instead of waiting. - * - * If either side aborted, this returns false. - * - * Returns: false if either side aborted, true otherwise. - */ -bool barrier_sync_next(Barrier *b) { - assert(b); - - if (barrier_is_aborted(b)) - return false; - - barrier_read(b, MAX((int64_t)0, b->barriers - 1)); - return !barrier_is_aborted(b); -} - -/** - * barrier_sync() - Wait for the other side to place as many barriers as we did - * @b: barrier to operate on - * - * This is like barrier_sync_next() but waits for the other side to call - * barrier_place() as often as we did (in total). If they already placed as much - * as we did (or more), this returns immediately instead of waiting. - * - * If either side aborted, this returns false. - * - * Returns: false if either side aborted, true otherwise. - */ -bool barrier_sync(Barrier *b) { - assert(b); - - if (barrier_is_aborted(b)) - return false; - - barrier_read(b, 0); - return !barrier_is_aborted(b); -} diff --git a/src/basic/barrier.h b/src/basic/barrier.h deleted file mode 100644 index 6347fddc4d..0000000000 --- a/src/basic/barrier.h +++ /dev/null @@ -1,91 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann - - 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 -#include - -#include "macro.h" - -/* See source file for an API description. */ - -typedef struct Barrier Barrier; - -enum { - BARRIER_SINGLE = 1LL, - BARRIER_ABORTION = INT64_MAX, - - /* bias values to store state; keep @WE < @THEY < @I */ - BARRIER_BIAS = INT64_MIN, - BARRIER_WE_ABORTED = BARRIER_BIAS + 1LL, - BARRIER_THEY_ABORTED = BARRIER_BIAS + 2LL, - BARRIER_I_ABORTED = BARRIER_BIAS + 3LL, -}; - -enum { - BARRIER_PARENT, - BARRIER_CHILD, -}; - -struct Barrier { - int me; - int them; - int pipe[2]; - int64_t barriers; -}; - -#define BARRIER_NULL {-1, -1, {-1, -1}, 0} - -int barrier_create(Barrier *obj); -void barrier_destroy(Barrier *b); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Barrier*, barrier_destroy); - -void barrier_set_role(Barrier *b, unsigned int role); - -bool barrier_place(Barrier *b); -bool barrier_abort(Barrier *b); - -bool barrier_wait_next(Barrier *b); -bool barrier_wait_abortion(Barrier *b); -bool barrier_sync_next(Barrier *b); -bool barrier_sync(Barrier *b); - -static inline bool barrier_i_aborted(Barrier *b) { - return b->barriers == BARRIER_I_ABORTED || b->barriers == BARRIER_WE_ABORTED; -} - -static inline bool barrier_they_aborted(Barrier *b) { - return b->barriers == BARRIER_THEY_ABORTED || b->barriers == BARRIER_WE_ABORTED; -} - -static inline bool barrier_we_aborted(Barrier *b) { - return b->barriers == BARRIER_WE_ABORTED; -} - -static inline bool barrier_is_aborted(Barrier *b) { - return b->barriers == BARRIER_I_ABORTED || b->barriers == BARRIER_THEY_ABORTED || b->barriers == BARRIER_WE_ABORTED; -} - -static inline bool barrier_place_and_sync(Barrier *b) { - (void) barrier_place(b); - return barrier_sync(b); -} diff --git a/src/basic/bitmap.c b/src/basic/bitmap.c deleted file mode 100644 index ad1fda0198..0000000000 --- a/src/basic/bitmap.c +++ /dev/null @@ -1,220 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 Tom Gundersen - - 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 -#include -#include -#include - -#include "alloc-util.h" -#include "bitmap.h" -#include "hashmap.h" -#include "macro.h" - -struct Bitmap { - uint64_t *bitmaps; - size_t n_bitmaps; - size_t bitmaps_allocated; -}; - -/* Bitmaps are only meant to store relatively small numbers - * (corresponding to, say, an enum), so it is ok to limit - * the max entry. 64k should be plenty. */ -#define BITMAPS_MAX_ENTRY 0xffff - -/* This indicates that we reached the end of the bitmap */ -#define BITMAP_END ((unsigned) -1) - -#define BITMAP_NUM_TO_OFFSET(n) ((n) / (sizeof(uint64_t) * 8)) -#define BITMAP_NUM_TO_REM(n) ((n) % (sizeof(uint64_t) * 8)) -#define BITMAP_OFFSET_TO_NUM(offset, rem) ((offset) * sizeof(uint64_t) * 8 + (rem)) - -Bitmap *bitmap_new(void) { - return new0(Bitmap, 1); -} - -void bitmap_free(Bitmap *b) { - if (!b) - return; - - free(b->bitmaps); - free(b); -} - -int bitmap_ensure_allocated(Bitmap **b) { - Bitmap *a; - - assert(b); - - if (*b) - return 0; - - a = bitmap_new(); - if (!a) - return -ENOMEM; - - *b = a; - - return 0; -} - -int bitmap_set(Bitmap *b, unsigned n) { - uint64_t bitmask; - unsigned offset; - - assert(b); - - /* we refuse to allocate huge bitmaps */ - if (n > BITMAPS_MAX_ENTRY) - return -ERANGE; - - offset = BITMAP_NUM_TO_OFFSET(n); - - if (offset >= b->n_bitmaps) { - if (!GREEDY_REALLOC0(b->bitmaps, b->bitmaps_allocated, offset + 1)) - return -ENOMEM; - - b->n_bitmaps = offset + 1; - } - - bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n); - - b->bitmaps[offset] |= bitmask; - - return 0; -} - -void bitmap_unset(Bitmap *b, unsigned n) { - uint64_t bitmask; - unsigned offset; - - if (!b) - return; - - offset = BITMAP_NUM_TO_OFFSET(n); - - if (offset >= b->n_bitmaps) - return; - - bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n); - - b->bitmaps[offset] &= ~bitmask; -} - -bool bitmap_isset(Bitmap *b, unsigned n) { - uint64_t bitmask; - unsigned offset; - - if (!b) - return false; - - offset = BITMAP_NUM_TO_OFFSET(n); - - if (offset >= b->n_bitmaps) - return false; - - bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n); - - return !!(b->bitmaps[offset] & bitmask); -} - -bool bitmap_isclear(Bitmap *b) { - unsigned i; - - if (!b) - return true; - - for (i = 0; i < b->n_bitmaps; i++) - if (b->bitmaps[i] != 0) - return false; - - return true; -} - -void bitmap_clear(Bitmap *b) { - - if (!b) - return; - - b->bitmaps = mfree(b->bitmaps); - b->n_bitmaps = 0; - b->bitmaps_allocated = 0; -} - -bool bitmap_iterate(Bitmap *b, Iterator *i, unsigned *n) { - uint64_t bitmask; - unsigned offset, rem; - - assert(i); - assert(n); - - if (!b || i->idx == BITMAP_END) - return false; - - offset = BITMAP_NUM_TO_OFFSET(i->idx); - rem = BITMAP_NUM_TO_REM(i->idx); - bitmask = UINT64_C(1) << rem; - - for (; offset < b->n_bitmaps; offset ++) { - if (b->bitmaps[offset]) { - for (; bitmask; bitmask <<= 1, rem ++) { - if (b->bitmaps[offset] & bitmask) { - *n = BITMAP_OFFSET_TO_NUM(offset, rem); - i->idx = *n + 1; - - return true; - } - } - } - - rem = 0; - bitmask = 1; - } - - i->idx = BITMAP_END; - - return false; -} - -bool bitmap_equal(Bitmap *a, Bitmap *b) { - size_t common_n_bitmaps; - Bitmap *c; - unsigned i; - - if (a == b) - return true; - - if (!a != !b) - return false; - - if (!a) - return true; - - common_n_bitmaps = MIN(a->n_bitmaps, b->n_bitmaps); - if (memcmp(a->bitmaps, b->bitmaps, sizeof(uint64_t) * common_n_bitmaps) != 0) - return false; - - c = a->n_bitmaps > b->n_bitmaps ? a : b; - for (i = common_n_bitmaps; i < c->n_bitmaps; i++) - if (c->bitmaps[i] != 0) - return false; - - return true; -} diff --git a/src/basic/bitmap.h b/src/basic/bitmap.h deleted file mode 100644 index f5f8f2f018..0000000000 --- a/src/basic/bitmap.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 Tom Gundersen - - 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 "hashmap.h" -#include "macro.h" - -typedef struct Bitmap Bitmap; - -Bitmap *bitmap_new(void); - -void bitmap_free(Bitmap *b); - -int bitmap_ensure_allocated(Bitmap **b); - -int bitmap_set(Bitmap *b, unsigned n); -void bitmap_unset(Bitmap *b, unsigned n); -bool bitmap_isset(Bitmap *b, unsigned n); -bool bitmap_isclear(Bitmap *b); -void bitmap_clear(Bitmap *b); - -bool bitmap_iterate(Bitmap *b, Iterator *i, unsigned *n); - -bool bitmap_equal(Bitmap *a, Bitmap *b); - -#define BITMAP_FOREACH(n, b, i) \ - for ((i).idx = 0; bitmap_iterate((b), &(i), (unsigned*)&(n)); ) - -DEFINE_TRIVIAL_CLEANUP_FUNC(Bitmap*, bitmap_free); - -#define _cleanup_bitmap_free_ _cleanup_(bitmap_freep) diff --git a/src/basic/blkid-util.h b/src/basic/blkid-util.h deleted file mode 100644 index 7aa75eb091..0000000000 --- a/src/basic/blkid-util.h +++ /dev/null @@ -1,31 +0,0 @@ -#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 . -***/ - -#ifdef HAVE_BLKID -#include -#endif - -#include "util.h" - -#ifdef HAVE_BLKID -DEFINE_TRIVIAL_CLEANUP_FUNC(blkid_probe, blkid_free_probe); -#define _cleanup_blkid_free_probe_ _cleanup_(blkid_free_probep) -#endif diff --git a/src/basic/btrfs-ctree.h b/src/basic/btrfs-ctree.h deleted file mode 100644 index 66bdf9736e..0000000000 --- a/src/basic/btrfs-ctree.h +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once - -#include "macro.h" -#include "sparse-endian.h" - -/* Stolen from btrfs' ctree.h */ - -struct btrfs_timespec { - le64_t sec; - le32_t nsec; -} _packed_; - -struct btrfs_disk_key { - le64_t objectid; - uint8_t type; - le64_t offset; -} _packed_; - -struct btrfs_inode_item { - le64_t generation; - le64_t transid; - le64_t size; - le64_t nbytes; - le64_t block_group; - le32_t nlink; - le32_t uid; - le32_t gid; - le32_t mode; - le64_t rdev; - le64_t flags; - le64_t sequence; - le64_t reserved[4]; - struct btrfs_timespec atime; - struct btrfs_timespec ctime; - struct btrfs_timespec mtime; - struct btrfs_timespec otime; -} _packed_; - -struct btrfs_root_item { - struct btrfs_inode_item inode; - le64_t generation; - le64_t root_dirid; - le64_t bytenr; - le64_t byte_limit; - le64_t bytes_used; - le64_t last_snapshot; - le64_t flags; - le32_t refs; - struct btrfs_disk_key drop_progress; - uint8_t drop_level; - uint8_t level; - le64_t generation_v2; - uint8_t uuid[BTRFS_UUID_SIZE]; - uint8_t parent_uuid[BTRFS_UUID_SIZE]; - uint8_t received_uuid[BTRFS_UUID_SIZE]; - le64_t ctransid; - le64_t otransid; - le64_t stransid; - le64_t rtransid; - struct btrfs_timespec ctime; - struct btrfs_timespec otime; - struct btrfs_timespec stime; - struct btrfs_timespec rtime; - le64_t reserved[8]; -} _packed_; - -#define BTRFS_ROOT_SUBVOL_RDONLY (1ULL << 0) - -struct btrfs_qgroup_info_item { - le64_t generation; - le64_t rfer; - le64_t rfer_cmpr; - le64_t excl; - le64_t excl_cmpr; -} _packed_; - -#define BTRFS_QGROUP_LIMIT_MAX_RFER (1ULL << 0) -#define BTRFS_QGROUP_LIMIT_MAX_EXCL (1ULL << 1) -#define BTRFS_QGROUP_LIMIT_RSV_RFER (1ULL << 2) -#define BTRFS_QGROUP_LIMIT_RSV_EXCL (1ULL << 3) -#define BTRFS_QGROUP_LIMIT_RFER_CMPR (1ULL << 4) -#define BTRFS_QGROUP_LIMIT_EXCL_CMPR (1ULL << 5) - -struct btrfs_qgroup_limit_item { - le64_t flags; - le64_t max_rfer; - le64_t max_excl; - le64_t rsv_rfer; - le64_t rsv_excl; -} _packed_; - -struct btrfs_root_ref { - le64_t dirid; - le64_t sequence; - le16_t name_len; -} _packed_; diff --git a/src/basic/btrfs-util.c b/src/basic/btrfs-util.c deleted file mode 100644 index 359d85f2e8..0000000000 --- a/src/basic/btrfs-util.c +++ /dev/null @@ -1,2075 +0,0 @@ -/*** - 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_LINUX_BTRFS_H -#include -#endif - -#include "alloc-util.h" -#include "btrfs-ctree.h" -#include "btrfs-util.h" -#include "copy.h" -#include "fd-util.h" -#include "fileio.h" -#include "io-util.h" -#include "macro.h" -#include "missing.h" -#include "path-util.h" -#include "selinux-util.h" -#include "smack-util.h" -#include "sparse-endian.h" -#include "stat-util.h" -#include "string-util.h" -#include "time-util.h" -#include "util.h" - -/* WARNING: Be careful with file system ioctls! When we get an fd, we - * need to make sure it either refers to only a regular file or - * directory, or that it is located on btrfs, before invoking any - * btrfs ioctls. The ioctl numbers are reused by some device drivers - * (such as DRM), and hence might have bad effects when invoked on - * device nodes (that reference drivers) rather than fds to normal - * files or directories. */ - -static int validate_subvolume_name(const char *name) { - - if (!filename_is_valid(name)) - return -EINVAL; - - if (strlen(name) > BTRFS_SUBVOL_NAME_MAX) - return -E2BIG; - - return 0; -} - -static int open_parent(const char *path, int flags) { - _cleanup_free_ char *parent = NULL; - int fd; - - assert(path); - - parent = dirname_malloc(path); - if (!parent) - return -ENOMEM; - - fd = open(parent, flags); - if (fd < 0) - return -errno; - - return fd; -} - -static int extract_subvolume_name(const char *path, const char **subvolume) { - const char *fn; - int r; - - assert(path); - assert(subvolume); - - fn = basename(path); - - r = validate_subvolume_name(fn); - if (r < 0) - return r; - - *subvolume = fn; - return 0; -} - -int btrfs_is_filesystem(int fd) { - struct statfs sfs; - - assert(fd >= 0); - - if (fstatfs(fd, &sfs) < 0) - return -errno; - - return F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC); -} - -int btrfs_is_subvol_fd(int fd) { - struct stat st; - - assert(fd >= 0); - - /* On btrfs subvolumes always have the inode 256 */ - - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISDIR(st.st_mode) || st.st_ino != 256) - return 0; - - return btrfs_is_filesystem(fd); -} - -int btrfs_is_subvol(const char *path) { - _cleanup_close_ int fd = -1; - - assert(path); - - fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - return -errno; - - return btrfs_is_subvol_fd(fd); -} - -int btrfs_subvol_make(const char *path) { - struct btrfs_ioctl_vol_args args = {}; - _cleanup_close_ int fd = -1; - const char *subvolume; - int r; - - assert(path); - - r = extract_subvolume_name(path, &subvolume); - if (r < 0) - return r; - - fd = open_parent(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - return fd; - - strncpy(args.name, subvolume, sizeof(args.name)-1); - - if (ioctl(fd, BTRFS_IOC_SUBVOL_CREATE, &args) < 0) - return -errno; - - return 0; -} - -int btrfs_subvol_make_label(const char *path) { - int r; - - assert(path); - - r = mac_selinux_create_file_prepare(path, S_IFDIR); - if (r < 0) - return r; - - r = btrfs_subvol_make(path); - mac_selinux_create_file_clear(); - - if (r < 0) - return r; - - return mac_smack_fix(path, false, false); -} - -int btrfs_subvol_set_read_only_fd(int fd, bool b) { - uint64_t flags, nflags; - struct stat st; - - assert(fd >= 0); - - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISDIR(st.st_mode) || st.st_ino != 256) - return -EINVAL; - - if (ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags) < 0) - return -errno; - - if (b) - nflags = flags | BTRFS_SUBVOL_RDONLY; - else - nflags = flags & ~BTRFS_SUBVOL_RDONLY; - - if (flags == nflags) - return 0; - - if (ioctl(fd, BTRFS_IOC_SUBVOL_SETFLAGS, &nflags) < 0) - return -errno; - - return 0; -} - -int btrfs_subvol_set_read_only(const char *path, bool b) { - _cleanup_close_ int fd = -1; - - fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - return -errno; - - return btrfs_subvol_set_read_only_fd(fd, b); -} - -int btrfs_subvol_get_read_only_fd(int fd) { - uint64_t flags; - struct stat st; - - assert(fd >= 0); - - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISDIR(st.st_mode) || st.st_ino != 256) - return -EINVAL; - - if (ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags) < 0) - return -errno; - - return !!(flags & BTRFS_SUBVOL_RDONLY); -} - -int btrfs_reflink(int infd, int outfd) { - struct stat st; - int r; - - assert(infd >= 0); - assert(outfd >= 0); - - /* Make sure we invoke the ioctl on a regular file, so that no - * device driver accidentally gets it. */ - - if (fstat(outfd, &st) < 0) - return -errno; - - if (!S_ISREG(st.st_mode)) - return -EINVAL; - - r = ioctl(outfd, BTRFS_IOC_CLONE, infd); - if (r < 0) - return -errno; - - return 0; -} - -int btrfs_clone_range(int infd, uint64_t in_offset, int outfd, uint64_t out_offset, uint64_t sz) { - struct btrfs_ioctl_clone_range_args args = { - .src_fd = infd, - .src_offset = in_offset, - .src_length = sz, - .dest_offset = out_offset, - }; - struct stat st; - int r; - - assert(infd >= 0); - assert(outfd >= 0); - assert(sz > 0); - - if (fstat(outfd, &st) < 0) - return -errno; - - if (!S_ISREG(st.st_mode)) - return -EINVAL; - - r = ioctl(outfd, BTRFS_IOC_CLONE_RANGE, &args); - if (r < 0) - return -errno; - - return 0; -} - -int btrfs_get_block_device_fd(int fd, dev_t *dev) { - struct btrfs_ioctl_fs_info_args fsi = {}; - uint64_t id; - int r; - - assert(fd >= 0); - assert(dev); - - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - - if (ioctl(fd, BTRFS_IOC_FS_INFO, &fsi) < 0) - return -errno; - - /* We won't do this for btrfs RAID */ - if (fsi.num_devices != 1) - return 0; - - for (id = 1; id <= fsi.max_id; id++) { - struct btrfs_ioctl_dev_info_args di = { - .devid = id, - }; - struct stat st; - - if (ioctl(fd, BTRFS_IOC_DEV_INFO, &di) < 0) { - if (errno == ENODEV) - continue; - - return -errno; - } - - if (stat((char*) di.path, &st) < 0) - return -errno; - - if (!S_ISBLK(st.st_mode)) - return -ENODEV; - - if (major(st.st_rdev) == 0) - return -ENODEV; - - *dev = st.st_rdev; - return 1; - } - - return -ENODEV; -} - -int btrfs_get_block_device(const char *path, dev_t *dev) { - _cleanup_close_ int fd = -1; - - assert(path); - assert(dev); - - fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return -errno; - - return btrfs_get_block_device_fd(fd, dev); -} - -int btrfs_subvol_get_id_fd(int fd, uint64_t *ret) { - struct btrfs_ioctl_ino_lookup_args args = { - .objectid = BTRFS_FIRST_FREE_OBJECTID - }; - int r; - - assert(fd >= 0); - assert(ret); - - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - - if (ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args) < 0) - return -errno; - - *ret = args.treeid; - return 0; -} - -int btrfs_subvol_get_id(int fd, const char *subvol, uint64_t *ret) { - _cleanup_close_ int subvol_fd = -1; - - assert(fd >= 0); - assert(ret); - - subvol_fd = openat(fd, subvol, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (subvol_fd < 0) - return -errno; - - return btrfs_subvol_get_id_fd(subvol_fd, ret); -} - -static bool btrfs_ioctl_search_args_inc(struct btrfs_ioctl_search_args *args) { - assert(args); - - /* the objectid, type, offset together make up the btrfs key, - * which is considered a single 136byte integer when - * comparing. This call increases the counter by one, dealing - * with the overflow between the overflows */ - - if (args->key.min_offset < (uint64_t) -1) { - args->key.min_offset++; - return true; - } - - if (args->key.min_type < (uint8_t) -1) { - args->key.min_type++; - args->key.min_offset = 0; - return true; - } - - if (args->key.min_objectid < (uint64_t) -1) { - args->key.min_objectid++; - args->key.min_offset = 0; - args->key.min_type = 0; - return true; - } - - return 0; -} - -static void btrfs_ioctl_search_args_set(struct btrfs_ioctl_search_args *args, const struct btrfs_ioctl_search_header *h) { - assert(args); - assert(h); - - args->key.min_objectid = h->objectid; - args->key.min_type = h->type; - args->key.min_offset = h->offset; -} - -static int btrfs_ioctl_search_args_compare(const struct btrfs_ioctl_search_args *args) { - assert(args); - - /* Compare min and max */ - - if (args->key.min_objectid < args->key.max_objectid) - return -1; - if (args->key.min_objectid > args->key.max_objectid) - return 1; - - if (args->key.min_type < args->key.max_type) - return -1; - if (args->key.min_type > args->key.max_type) - return 1; - - if (args->key.min_offset < args->key.max_offset) - return -1; - if (args->key.min_offset > args->key.max_offset) - return 1; - - return 0; -} - -#define FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) \ - for ((i) = 0, \ - (sh) = (const struct btrfs_ioctl_search_header*) (args).buf; \ - (i) < (args).key.nr_items; \ - (i)++, \ - (sh) = (const struct btrfs_ioctl_search_header*) ((uint8_t*) (sh) + sizeof(struct btrfs_ioctl_search_header) + (sh)->len)) - -#define BTRFS_IOCTL_SEARCH_HEADER_BODY(sh) \ - ((void*) ((uint8_t*) sh + sizeof(struct btrfs_ioctl_search_header))) - -int btrfs_subvol_get_info_fd(int fd, uint64_t subvol_id, BtrfsSubvolInfo *ret) { - struct btrfs_ioctl_search_args args = { - /* Tree of tree roots */ - .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, - - /* Look precisely for the subvolume items */ - .key.min_type = BTRFS_ROOT_ITEM_KEY, - .key.max_type = BTRFS_ROOT_ITEM_KEY, - - .key.min_offset = 0, - .key.max_offset = (uint64_t) -1, - - /* No restrictions on the other components */ - .key.min_transid = 0, - .key.max_transid = (uint64_t) -1, - }; - - bool found = false; - int r; - - assert(fd >= 0); - assert(ret); - - if (subvol_id == 0) { - r = btrfs_subvol_get_id_fd(fd, &subvol_id); - if (r < 0) - return r; - } else { - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - } - - args.key.min_objectid = args.key.max_objectid = subvol_id; - - while (btrfs_ioctl_search_args_compare(&args) <= 0) { - const struct btrfs_ioctl_search_header *sh; - unsigned i; - - args.key.nr_items = 256; - if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) - return -errno; - - if (args.key.nr_items <= 0) - break; - - FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { - - const struct btrfs_root_item *ri; - - /* Make sure we start the next search at least from this entry */ - btrfs_ioctl_search_args_set(&args, sh); - - if (sh->objectid != subvol_id) - continue; - if (sh->type != BTRFS_ROOT_ITEM_KEY) - continue; - - /* Older versions of the struct lacked the otime setting */ - if (sh->len < offsetof(struct btrfs_root_item, otime) + sizeof(struct btrfs_timespec)) - continue; - - ri = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); - - ret->otime = (usec_t) le64toh(ri->otime.sec) * USEC_PER_SEC + - (usec_t) le32toh(ri->otime.nsec) / NSEC_PER_USEC; - - ret->subvol_id = subvol_id; - ret->read_only = !!(le64toh(ri->flags) & BTRFS_ROOT_SUBVOL_RDONLY); - - assert_cc(sizeof(ri->uuid) == sizeof(ret->uuid)); - memcpy(&ret->uuid, ri->uuid, sizeof(ret->uuid)); - memcpy(&ret->parent_uuid, ri->parent_uuid, sizeof(ret->parent_uuid)); - - found = true; - goto finish; - } - - /* Increase search key by one, to read the next item, if we can. */ - if (!btrfs_ioctl_search_args_inc(&args)) - break; - } - -finish: - if (!found) - return -ENODATA; - - return 0; -} - -int btrfs_qgroup_get_quota_fd(int fd, uint64_t qgroupid, BtrfsQuotaInfo *ret) { - - struct btrfs_ioctl_search_args args = { - /* Tree of quota items */ - .key.tree_id = BTRFS_QUOTA_TREE_OBJECTID, - - /* The object ID is always 0 */ - .key.min_objectid = 0, - .key.max_objectid = 0, - - /* Look precisely for the quota items */ - .key.min_type = BTRFS_QGROUP_STATUS_KEY, - .key.max_type = BTRFS_QGROUP_LIMIT_KEY, - - /* No restrictions on the other components */ - .key.min_transid = 0, - .key.max_transid = (uint64_t) -1, - }; - - bool found_info = false, found_limit = false; - int r; - - assert(fd >= 0); - assert(ret); - - if (qgroupid == 0) { - r = btrfs_subvol_get_id_fd(fd, &qgroupid); - if (r < 0) - return r; - } else { - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - } - - args.key.min_offset = args.key.max_offset = qgroupid; - - while (btrfs_ioctl_search_args_compare(&args) <= 0) { - const struct btrfs_ioctl_search_header *sh; - unsigned i; - - args.key.nr_items = 256; - if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) { - if (errno == ENOENT) /* quota tree is missing: quota disabled */ - break; - - return -errno; - } - - if (args.key.nr_items <= 0) - break; - - FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { - - /* Make sure we start the next search at least from this entry */ - btrfs_ioctl_search_args_set(&args, sh); - - if (sh->objectid != 0) - continue; - if (sh->offset != qgroupid) - continue; - - if (sh->type == BTRFS_QGROUP_INFO_KEY) { - const struct btrfs_qgroup_info_item *qii = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); - - ret->referenced = le64toh(qii->rfer); - ret->exclusive = le64toh(qii->excl); - - found_info = true; - - } else if (sh->type == BTRFS_QGROUP_LIMIT_KEY) { - const struct btrfs_qgroup_limit_item *qli = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); - - if (le64toh(qli->flags) & BTRFS_QGROUP_LIMIT_MAX_RFER) - ret->referenced_max = le64toh(qli->max_rfer); - else - ret->referenced_max = (uint64_t) -1; - - if (le64toh(qli->flags) & BTRFS_QGROUP_LIMIT_MAX_EXCL) - ret->exclusive_max = le64toh(qli->max_excl); - else - ret->exclusive_max = (uint64_t) -1; - - found_limit = true; - } - - if (found_info && found_limit) - goto finish; - } - - /* Increase search key by one, to read the next item, if we can. */ - if (!btrfs_ioctl_search_args_inc(&args)) - break; - } - -finish: - if (!found_limit && !found_info) - return -ENODATA; - - if (!found_info) { - ret->referenced = (uint64_t) -1; - ret->exclusive = (uint64_t) -1; - } - - if (!found_limit) { - ret->referenced_max = (uint64_t) -1; - ret->exclusive_max = (uint64_t) -1; - } - - return 0; -} - -int btrfs_qgroup_get_quota(const char *path, uint64_t qgroupid, BtrfsQuotaInfo *ret) { - _cleanup_close_ int fd = -1; - - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; - - return btrfs_qgroup_get_quota_fd(fd, qgroupid, ret); -} - -int btrfs_subvol_find_subtree_qgroup(int fd, uint64_t subvol_id, uint64_t *ret) { - uint64_t level, lowest = (uint64_t) -1, lowest_qgroupid = 0; - _cleanup_free_ uint64_t *qgroups = NULL; - int r, n, i; - - assert(fd >= 0); - assert(ret); - - /* This finds the "subtree" qgroup for a specific - * subvolume. This only works for subvolumes that have been - * prepared with btrfs_subvol_auto_qgroup_fd() with - * insert_intermediary_qgroup=true (or equivalent). For others - * it will return the leaf qgroup instead. The two cases may - * be distuingished via the return value, which is 1 in case - * an appropriate "subtree" qgroup was found, and 0 - * otherwise. */ - - if (subvol_id == 0) { - r = btrfs_subvol_get_id_fd(fd, &subvol_id); - if (r < 0) - return r; - } - - r = btrfs_qgroupid_split(subvol_id, &level, NULL); - if (r < 0) - return r; - if (level != 0) /* Input must be a leaf qgroup */ - return -EINVAL; - - n = btrfs_qgroup_find_parents(fd, subvol_id, &qgroups); - if (n < 0) - return n; - - for (i = 0; i < n; i++) { - uint64_t id; - - r = btrfs_qgroupid_split(qgroups[i], &level, &id); - if (r < 0) - return r; - - if (id != subvol_id) - continue; - - if (lowest == (uint64_t) -1 || level < lowest) { - lowest_qgroupid = qgroups[i]; - lowest = level; - } - } - - if (lowest == (uint64_t) -1) { - /* No suitable higher-level qgroup found, let's return - * the leaf qgroup instead, and indicate that with the - * return value. */ - - *ret = subvol_id; - return 0; - } - - *ret = lowest_qgroupid; - return 1; -} - -int btrfs_subvol_get_subtree_quota_fd(int fd, uint64_t subvol_id, BtrfsQuotaInfo *ret) { - uint64_t qgroupid; - int r; - - assert(fd >= 0); - assert(ret); - - /* This determines the quota data of the qgroup with the - * lowest level, that shares the id part with the specified - * subvolume. This is useful for determining the quota data - * for entire subvolume subtrees, as long as the subtrees have - * been set up with btrfs_qgroup_subvol_auto_fd() or in a - * compatible way */ - - r = btrfs_subvol_find_subtree_qgroup(fd, subvol_id, &qgroupid); - if (r < 0) - return r; - - return btrfs_qgroup_get_quota_fd(fd, qgroupid, ret); -} - -int btrfs_subvol_get_subtree_quota(const char *path, uint64_t subvol_id, BtrfsQuotaInfo *ret) { - _cleanup_close_ int fd = -1; - - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; - - return btrfs_subvol_get_subtree_quota_fd(fd, subvol_id, ret); -} - -int btrfs_defrag_fd(int fd) { - struct stat st; - - assert(fd >= 0); - - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISREG(st.st_mode)) - return -EINVAL; - - if (ioctl(fd, BTRFS_IOC_DEFRAG, NULL) < 0) - return -errno; - - return 0; -} - -int btrfs_defrag(const char *p) { - _cleanup_close_ int fd = -1; - - fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; - - return btrfs_defrag_fd(fd); -} - -int btrfs_quota_enable_fd(int fd, bool b) { - struct btrfs_ioctl_quota_ctl_args args = { - .cmd = b ? BTRFS_QUOTA_CTL_ENABLE : BTRFS_QUOTA_CTL_DISABLE, - }; - int r; - - assert(fd >= 0); - - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - - if (ioctl(fd, BTRFS_IOC_QUOTA_CTL, &args) < 0) - return -errno; - - return 0; -} - -int btrfs_quota_enable(const char *path, bool b) { - _cleanup_close_ int fd = -1; - - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; - - return btrfs_quota_enable_fd(fd, b); -} - -int btrfs_qgroup_set_limit_fd(int fd, uint64_t qgroupid, uint64_t referenced_max) { - - struct btrfs_ioctl_qgroup_limit_args args = { - .lim.max_rfer = referenced_max, - .lim.flags = BTRFS_QGROUP_LIMIT_MAX_RFER, - }; - unsigned c; - int r; - - assert(fd >= 0); - - if (qgroupid == 0) { - r = btrfs_subvol_get_id_fd(fd, &qgroupid); - if (r < 0) - return r; - } else { - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - } - - args.qgroupid = qgroupid; - - for (c = 0;; c++) { - if (ioctl(fd, BTRFS_IOC_QGROUP_LIMIT, &args) < 0) { - - if (errno == EBUSY && c < 10) { - (void) btrfs_quota_scan_wait(fd); - continue; - } - - return -errno; - } - - break; - } - - return 0; -} - -int btrfs_qgroup_set_limit(const char *path, uint64_t qgroupid, uint64_t referenced_max) { - _cleanup_close_ int fd = -1; - - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; - - return btrfs_qgroup_set_limit_fd(fd, qgroupid, referenced_max); -} - -int btrfs_subvol_set_subtree_quota_limit_fd(int fd, uint64_t subvol_id, uint64_t referenced_max) { - uint64_t qgroupid; - int r; - - assert(fd >= 0); - - r = btrfs_subvol_find_subtree_qgroup(fd, subvol_id, &qgroupid); - if (r < 0) - return r; - - return btrfs_qgroup_set_limit_fd(fd, qgroupid, referenced_max); -} - -int btrfs_subvol_set_subtree_quota_limit(const char *path, uint64_t subvol_id, uint64_t referenced_max) { - _cleanup_close_ int fd = -1; - - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; - - return btrfs_subvol_set_subtree_quota_limit_fd(fd, subvol_id, referenced_max); -} - -int btrfs_resize_loopback_fd(int fd, uint64_t new_size, bool grow_only) { - struct btrfs_ioctl_vol_args args = {}; - _cleanup_free_ char *p = NULL, *loop = NULL, *backing = NULL; - _cleanup_close_ int loop_fd = -1, backing_fd = -1; - struct stat st; - dev_t dev = 0; - int r; - - /* In contrast to btrfs quota ioctls ftruncate() cannot make sense of "infinity" or file sizes > 2^31 */ - if (!FILE_SIZE_VALID(new_size)) - return -EINVAL; - - /* btrfs cannot handle file systems < 16M, hence use this as minimum */ - if (new_size < 16*1024*1024) - new_size = 16*1024*1024; - - r = btrfs_get_block_device_fd(fd, &dev); - if (r < 0) - return r; - if (r == 0) - return -ENODEV; - - if (asprintf(&p, "/sys/dev/block/%u:%u/loop/backing_file", major(dev), minor(dev)) < 0) - return -ENOMEM; - r = read_one_line_file(p, &backing); - if (r == -ENOENT) - return -ENODEV; - if (r < 0) - return r; - if (isempty(backing) || !path_is_absolute(backing)) - return -ENODEV; - - backing_fd = open(backing, O_RDWR|O_CLOEXEC|O_NOCTTY); - if (backing_fd < 0) - return -errno; - - if (fstat(backing_fd, &st) < 0) - return -errno; - if (!S_ISREG(st.st_mode)) - return -ENODEV; - - if (new_size == (uint64_t) st.st_size) - return 0; - - if (grow_only && new_size < (uint64_t) st.st_size) - return -EINVAL; - - if (asprintf(&loop, "/dev/block/%u:%u", major(dev), minor(dev)) < 0) - return -ENOMEM; - loop_fd = open(loop, O_RDWR|O_CLOEXEC|O_NOCTTY); - if (loop_fd < 0) - return -errno; - - if (snprintf(args.name, sizeof(args.name), "%" PRIu64, new_size) >= (int) sizeof(args.name)) - return -EINVAL; - - if (new_size < (uint64_t) st.st_size) { - /* Decrease size: first decrease btrfs size, then shorten loopback */ - if (ioctl(fd, BTRFS_IOC_RESIZE, &args) < 0) - return -errno; - } - - if (ftruncate(backing_fd, new_size) < 0) - return -errno; - - if (ioctl(loop_fd, LOOP_SET_CAPACITY, 0) < 0) - return -errno; - - if (new_size > (uint64_t) st.st_size) { - /* Increase size: first enlarge loopback, then increase btrfs size */ - if (ioctl(fd, BTRFS_IOC_RESIZE, &args) < 0) - return -errno; - } - - /* Make sure the free disk space is correctly updated for both file systems */ - (void) fsync(fd); - (void) fsync(backing_fd); - - return 1; -} - -int btrfs_resize_loopback(const char *p, uint64_t new_size, bool grow_only) { - _cleanup_close_ int fd = -1; - - fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return -errno; - - return btrfs_resize_loopback_fd(fd, new_size, grow_only); -} - -int btrfs_qgroupid_make(uint64_t level, uint64_t id, uint64_t *ret) { - assert(ret); - - if (level >= (UINT64_C(1) << (64 - BTRFS_QGROUP_LEVEL_SHIFT))) - return -EINVAL; - - if (id >= (UINT64_C(1) << BTRFS_QGROUP_LEVEL_SHIFT)) - return -EINVAL; - - *ret = (level << BTRFS_QGROUP_LEVEL_SHIFT) | id; - return 0; -} - -int btrfs_qgroupid_split(uint64_t qgroupid, uint64_t *level, uint64_t *id) { - assert(level || id); - - if (level) - *level = qgroupid >> BTRFS_QGROUP_LEVEL_SHIFT; - - if (id) - *id = qgroupid & ((UINT64_C(1) << BTRFS_QGROUP_LEVEL_SHIFT) - 1); - - return 0; -} - -static int qgroup_create_or_destroy(int fd, bool b, uint64_t qgroupid) { - - struct btrfs_ioctl_qgroup_create_args args = { - .create = b, - .qgroupid = qgroupid, - }; - unsigned c; - int r; - - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (r == 0) - return -ENOTTY; - - for (c = 0;; c++) { - if (ioctl(fd, BTRFS_IOC_QGROUP_CREATE, &args) < 0) { - - /* If quota is not enabled, we get EINVAL. Turn this into a recognizable error */ - if (errno == EINVAL) - return -ENOPROTOOPT; - - if (errno == EBUSY && c < 10) { - (void) btrfs_quota_scan_wait(fd); - continue; - } - - return -errno; - } - - break; - } - - return 0; -} - -int btrfs_qgroup_create(int fd, uint64_t qgroupid) { - return qgroup_create_or_destroy(fd, true, qgroupid); -} - -int btrfs_qgroup_destroy(int fd, uint64_t qgroupid) { - return qgroup_create_or_destroy(fd, false, qgroupid); -} - -int btrfs_qgroup_destroy_recursive(int fd, uint64_t qgroupid) { - _cleanup_free_ uint64_t *qgroups = NULL; - uint64_t subvol_id; - int i, n, r; - - /* Destroys the specified qgroup, but unassigns it from all - * its parents first. Also, it recursively destroys all - * qgroups it is assgined to that have the same id part of the - * qgroupid as the specified group. */ - - r = btrfs_qgroupid_split(qgroupid, NULL, &subvol_id); - if (r < 0) - return r; - - n = btrfs_qgroup_find_parents(fd, qgroupid, &qgroups); - if (n < 0) - return n; - - for (i = 0; i < n; i++) { - uint64_t id; - - r = btrfs_qgroupid_split(qgroups[i], NULL, &id); - if (r < 0) - return r; - - r = btrfs_qgroup_unassign(fd, qgroupid, qgroups[i]); - if (r < 0) - return r; - - if (id != subvol_id) - continue; - - /* The parent qgroupid shares the same id part with - * us? If so, destroy it too. */ - - (void) btrfs_qgroup_destroy_recursive(fd, qgroups[i]); - } - - return btrfs_qgroup_destroy(fd, qgroupid); -} - -int btrfs_quota_scan_start(int fd) { - struct btrfs_ioctl_quota_rescan_args args = {}; - - assert(fd >= 0); - - if (ioctl(fd, BTRFS_IOC_QUOTA_RESCAN, &args) < 0) - return -errno; - - return 0; -} - -int btrfs_quota_scan_wait(int fd) { - assert(fd >= 0); - - if (ioctl(fd, BTRFS_IOC_QUOTA_RESCAN_WAIT) < 0) - return -errno; - - return 0; -} - -int btrfs_quota_scan_ongoing(int fd) { - struct btrfs_ioctl_quota_rescan_args args = {}; - - assert(fd >= 0); - - if (ioctl(fd, BTRFS_IOC_QUOTA_RESCAN_STATUS, &args) < 0) - return -errno; - - return !!args.flags; -} - -static int qgroup_assign_or_unassign(int fd, bool b, uint64_t child, uint64_t parent) { - struct btrfs_ioctl_qgroup_assign_args args = { - .assign = b, - .src = child, - .dst = parent, - }; - unsigned c; - int r; - - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (r == 0) - return -ENOTTY; - - for (c = 0;; c++) { - r = ioctl(fd, BTRFS_IOC_QGROUP_ASSIGN, &args); - if (r < 0) { - if (errno == EBUSY && c < 10) { - (void) btrfs_quota_scan_wait(fd); - continue; - } - - return -errno; - } - - if (r == 0) - return 0; - - /* If the return value is > 0, we need to request a rescan */ - - (void) btrfs_quota_scan_start(fd); - return 1; - } -} - -int btrfs_qgroup_assign(int fd, uint64_t child, uint64_t parent) { - return qgroup_assign_or_unassign(fd, true, child, parent); -} - -int btrfs_qgroup_unassign(int fd, uint64_t child, uint64_t parent) { - return qgroup_assign_or_unassign(fd, false, child, parent); -} - -static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol_id, BtrfsRemoveFlags flags) { - struct btrfs_ioctl_search_args args = { - .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, - - .key.min_objectid = BTRFS_FIRST_FREE_OBJECTID, - .key.max_objectid = BTRFS_LAST_FREE_OBJECTID, - - .key.min_type = BTRFS_ROOT_BACKREF_KEY, - .key.max_type = BTRFS_ROOT_BACKREF_KEY, - - .key.min_transid = 0, - .key.max_transid = (uint64_t) -1, - }; - - struct btrfs_ioctl_vol_args vol_args = {}; - _cleanup_close_ int subvol_fd = -1; - struct stat st; - bool made_writable = false; - int r; - - assert(fd >= 0); - assert(subvolume); - - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISDIR(st.st_mode)) - return -EINVAL; - - subvol_fd = openat(fd, subvolume, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (subvol_fd < 0) - return -errno; - - if (subvol_id == 0) { - r = btrfs_subvol_get_id_fd(subvol_fd, &subvol_id); - if (r < 0) - return r; - } - - /* First, try to remove the subvolume. If it happens to be - * already empty, this will just work. */ - strncpy(vol_args.name, subvolume, sizeof(vol_args.name)-1); - if (ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &vol_args) >= 0) { - (void) btrfs_qgroup_destroy_recursive(fd, subvol_id); /* for the leaf subvolumes, the qgroup id is identical to the subvol id */ - return 0; - } - if (!(flags & BTRFS_REMOVE_RECURSIVE) || errno != ENOTEMPTY) - return -errno; - - /* OK, the subvolume is not empty, let's look for child - * subvolumes, and remove them, first */ - - args.key.min_offset = args.key.max_offset = subvol_id; - - while (btrfs_ioctl_search_args_compare(&args) <= 0) { - const struct btrfs_ioctl_search_header *sh; - unsigned i; - - args.key.nr_items = 256; - if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) - return -errno; - - if (args.key.nr_items <= 0) - break; - - FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { - _cleanup_free_ char *p = NULL; - const struct btrfs_root_ref *ref; - struct btrfs_ioctl_ino_lookup_args ino_args; - - btrfs_ioctl_search_args_set(&args, sh); - - if (sh->type != BTRFS_ROOT_BACKREF_KEY) - continue; - if (sh->offset != subvol_id) - continue; - - ref = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); - - p = strndup((char*) ref + sizeof(struct btrfs_root_ref), le64toh(ref->name_len)); - if (!p) - return -ENOMEM; - - zero(ino_args); - ino_args.treeid = subvol_id; - ino_args.objectid = htole64(ref->dirid); - - if (ioctl(fd, BTRFS_IOC_INO_LOOKUP, &ino_args) < 0) - return -errno; - - if (!made_writable) { - r = btrfs_subvol_set_read_only_fd(subvol_fd, false); - if (r < 0) - return r; - - made_writable = true; - } - - if (isempty(ino_args.name)) - /* Subvolume is in the top-level - * directory of the subvolume. */ - r = subvol_remove_children(subvol_fd, p, sh->objectid, flags); - else { - _cleanup_close_ int child_fd = -1; - - /* Subvolume is somewhere further down, - * hence we need to open the - * containing directory first */ - - child_fd = openat(subvol_fd, ino_args.name, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (child_fd < 0) - return -errno; - - r = subvol_remove_children(child_fd, p, sh->objectid, flags); - } - if (r < 0) - return r; - } - - /* Increase search key by one, to read the next item, if we can. */ - if (!btrfs_ioctl_search_args_inc(&args)) - break; - } - - /* OK, the child subvolumes should all be gone now, let's try - * again to remove the subvolume */ - if (ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &vol_args) < 0) - return -errno; - - (void) btrfs_qgroup_destroy_recursive(fd, subvol_id); - return 0; -} - -int btrfs_subvol_remove(const char *path, BtrfsRemoveFlags flags) { - _cleanup_close_ int fd = -1; - const char *subvolume; - int r; - - assert(path); - - r = extract_subvolume_name(path, &subvolume); - if (r < 0) - return r; - - fd = open_parent(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - return fd; - - return subvol_remove_children(fd, subvolume, 0, flags); -} - -int btrfs_subvol_remove_fd(int fd, const char *subvolume, BtrfsRemoveFlags flags) { - return subvol_remove_children(fd, subvolume, 0, flags); -} - -int btrfs_qgroup_copy_limits(int fd, uint64_t old_qgroupid, uint64_t new_qgroupid) { - - struct btrfs_ioctl_search_args args = { - /* Tree of quota items */ - .key.tree_id = BTRFS_QUOTA_TREE_OBJECTID, - - /* The object ID is always 0 */ - .key.min_objectid = 0, - .key.max_objectid = 0, - - /* Look precisely for the quota items */ - .key.min_type = BTRFS_QGROUP_LIMIT_KEY, - .key.max_type = BTRFS_QGROUP_LIMIT_KEY, - - /* For our qgroup */ - .key.min_offset = old_qgroupid, - .key.max_offset = old_qgroupid, - - /* No restrictions on the other components */ - .key.min_transid = 0, - .key.max_transid = (uint64_t) -1, - }; - - int r; - - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - - while (btrfs_ioctl_search_args_compare(&args) <= 0) { - const struct btrfs_ioctl_search_header *sh; - unsigned i; - - args.key.nr_items = 256; - if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) { - if (errno == ENOENT) /* quota tree missing: quota is not enabled, hence nothing to copy */ - break; - - return -errno; - } - - if (args.key.nr_items <= 0) - break; - - FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { - const struct btrfs_qgroup_limit_item *qli = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); - struct btrfs_ioctl_qgroup_limit_args qargs; - unsigned c; - - /* Make sure we start the next search at least from this entry */ - btrfs_ioctl_search_args_set(&args, sh); - - if (sh->objectid != 0) - continue; - if (sh->type != BTRFS_QGROUP_LIMIT_KEY) - continue; - if (sh->offset != old_qgroupid) - continue; - - /* We found the entry, now copy things over. */ - - qargs = (struct btrfs_ioctl_qgroup_limit_args) { - .qgroupid = new_qgroupid, - - .lim.max_rfer = le64toh(qli->max_rfer), - .lim.max_excl = le64toh(qli->max_excl), - .lim.rsv_rfer = le64toh(qli->rsv_rfer), - .lim.rsv_excl = le64toh(qli->rsv_excl), - - .lim.flags = le64toh(qli->flags) & (BTRFS_QGROUP_LIMIT_MAX_RFER| - BTRFS_QGROUP_LIMIT_MAX_EXCL| - BTRFS_QGROUP_LIMIT_RSV_RFER| - BTRFS_QGROUP_LIMIT_RSV_EXCL), - }; - - for (c = 0;; c++) { - if (ioctl(fd, BTRFS_IOC_QGROUP_LIMIT, &qargs) < 0) { - if (errno == EBUSY && c < 10) { - (void) btrfs_quota_scan_wait(fd); - continue; - } - return -errno; - } - - break; - } - - return 1; - } - - /* Increase search key by one, to read the next item, if we can. */ - if (!btrfs_ioctl_search_args_inc(&args)) - break; - } - - return 0; -} - -static int copy_quota_hierarchy(int fd, uint64_t old_subvol_id, uint64_t new_subvol_id) { - _cleanup_free_ uint64_t *old_qgroups = NULL, *old_parent_qgroups = NULL; - bool copy_from_parent = false, insert_intermediary_qgroup = false; - int n_old_qgroups, n_old_parent_qgroups, r, i; - uint64_t old_parent_id; - - assert(fd >= 0); - - /* Copies a reduced form of quota information from the old to - * the new subvolume. */ - - n_old_qgroups = btrfs_qgroup_find_parents(fd, old_subvol_id, &old_qgroups); - if (n_old_qgroups <= 0) /* Nothing to copy */ - return n_old_qgroups; - - r = btrfs_subvol_get_parent(fd, old_subvol_id, &old_parent_id); - if (r == -ENXIO) - /* We have no parent, hence nothing to copy. */ - n_old_parent_qgroups = 0; - else if (r < 0) - return r; - else { - n_old_parent_qgroups = btrfs_qgroup_find_parents(fd, old_parent_id, &old_parent_qgroups); - if (n_old_parent_qgroups < 0) - return n_old_parent_qgroups; - } - - for (i = 0; i < n_old_qgroups; i++) { - uint64_t id; - int j; - - r = btrfs_qgroupid_split(old_qgroups[i], NULL, &id); - if (r < 0) - return r; - - if (id == old_subvol_id) { - /* The old subvolume was member of a qgroup - * that had the same id, but a different level - * as it self. Let's set up something similar - * in the destination. */ - insert_intermediary_qgroup = true; - break; - } - - for (j = 0; j < n_old_parent_qgroups; j++) - if (old_parent_qgroups[j] == old_qgroups[i]) { - /* The old subvolume shared a common - * parent qgroup with its parent - * subvolume. Let's set up something - * similar in the destination. */ - copy_from_parent = true; - } - } - - if (!insert_intermediary_qgroup && !copy_from_parent) - return 0; - - return btrfs_subvol_auto_qgroup_fd(fd, new_subvol_id, insert_intermediary_qgroup); -} - -static int copy_subtree_quota_limits(int fd, uint64_t old_subvol, uint64_t new_subvol) { - uint64_t old_subtree_qgroup, new_subtree_qgroup; - bool changed; - int r; - - /* First copy the leaf limits */ - r = btrfs_qgroup_copy_limits(fd, old_subvol, new_subvol); - if (r < 0) - return r; - changed = r > 0; - - /* Then, try to copy the subtree limits, if there are any. */ - r = btrfs_subvol_find_subtree_qgroup(fd, old_subvol, &old_subtree_qgroup); - if (r < 0) - return r; - if (r == 0) - return changed; - - r = btrfs_subvol_find_subtree_qgroup(fd, new_subvol, &new_subtree_qgroup); - if (r < 0) - return r; - if (r == 0) - return changed; - - r = btrfs_qgroup_copy_limits(fd, old_subtree_qgroup, new_subtree_qgroup); - if (r != 0) - return r; - - return changed; -} - -static int subvol_snapshot_children(int old_fd, int new_fd, const char *subvolume, uint64_t old_subvol_id, BtrfsSnapshotFlags flags) { - - struct btrfs_ioctl_search_args args = { - .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, - - .key.min_objectid = BTRFS_FIRST_FREE_OBJECTID, - .key.max_objectid = BTRFS_LAST_FREE_OBJECTID, - - .key.min_type = BTRFS_ROOT_BACKREF_KEY, - .key.max_type = BTRFS_ROOT_BACKREF_KEY, - - .key.min_transid = 0, - .key.max_transid = (uint64_t) -1, - }; - - struct btrfs_ioctl_vol_args_v2 vol_args = { - .flags = flags & BTRFS_SNAPSHOT_READ_ONLY ? BTRFS_SUBVOL_RDONLY : 0, - .fd = old_fd, - }; - _cleanup_close_ int subvolume_fd = -1; - uint64_t new_subvol_id; - int r; - - assert(old_fd >= 0); - assert(new_fd >= 0); - assert(subvolume); - - strncpy(vol_args.name, subvolume, sizeof(vol_args.name)-1); - - if (ioctl(new_fd, BTRFS_IOC_SNAP_CREATE_V2, &vol_args) < 0) - return -errno; - - if (!(flags & BTRFS_SNAPSHOT_RECURSIVE) && - !(flags & BTRFS_SNAPSHOT_QUOTA)) - return 0; - - if (old_subvol_id == 0) { - r = btrfs_subvol_get_id_fd(old_fd, &old_subvol_id); - if (r < 0) - return r; - } - - r = btrfs_subvol_get_id(new_fd, vol_args.name, &new_subvol_id); - if (r < 0) - return r; - - if (flags & BTRFS_SNAPSHOT_QUOTA) - (void) copy_quota_hierarchy(new_fd, old_subvol_id, new_subvol_id); - - if (!(flags & BTRFS_SNAPSHOT_RECURSIVE)) { - - if (flags & BTRFS_SNAPSHOT_QUOTA) - (void) copy_subtree_quota_limits(new_fd, old_subvol_id, new_subvol_id); - - return 0; - } - - args.key.min_offset = args.key.max_offset = old_subvol_id; - - while (btrfs_ioctl_search_args_compare(&args) <= 0) { - const struct btrfs_ioctl_search_header *sh; - unsigned i; - - args.key.nr_items = 256; - if (ioctl(old_fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) - return -errno; - - if (args.key.nr_items <= 0) - break; - - FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { - _cleanup_free_ char *p = NULL, *c = NULL, *np = NULL; - struct btrfs_ioctl_ino_lookup_args ino_args; - const struct btrfs_root_ref *ref; - _cleanup_close_ int old_child_fd = -1, new_child_fd = -1; - - btrfs_ioctl_search_args_set(&args, sh); - - if (sh->type != BTRFS_ROOT_BACKREF_KEY) - continue; - - /* Avoid finding the source subvolume a second - * time */ - if (sh->offset != old_subvol_id) - continue; - - /* Avoid running into loops if the new - * subvolume is below the old one. */ - if (sh->objectid == new_subvol_id) - continue; - - ref = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); - p = strndup((char*) ref + sizeof(struct btrfs_root_ref), le64toh(ref->name_len)); - if (!p) - return -ENOMEM; - - zero(ino_args); - ino_args.treeid = old_subvol_id; - ino_args.objectid = htole64(ref->dirid); - - if (ioctl(old_fd, BTRFS_IOC_INO_LOOKUP, &ino_args) < 0) - return -errno; - - /* The kernel returns an empty name if the - * subvolume is in the top-level directory, - * and otherwise appends a slash, so that we - * can just concatenate easily here, without - * adding a slash. */ - c = strappend(ino_args.name, p); - if (!c) - return -ENOMEM; - - old_child_fd = openat(old_fd, c, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (old_child_fd < 0) - return -errno; - - np = strjoin(subvolume, "/", ino_args.name, NULL); - if (!np) - return -ENOMEM; - - new_child_fd = openat(new_fd, np, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (new_child_fd < 0) - return -errno; - - if (flags & BTRFS_SNAPSHOT_READ_ONLY) { - /* If the snapshot is read-only we - * need to mark it writable - * temporarily, to put the subsnapshot - * into place. */ - - if (subvolume_fd < 0) { - subvolume_fd = openat(new_fd, subvolume, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (subvolume_fd < 0) - return -errno; - } - - r = btrfs_subvol_set_read_only_fd(subvolume_fd, false); - if (r < 0) - return r; - } - - /* When btrfs clones the subvolumes, child - * subvolumes appear as empty directories. Remove - * them, so that we can create a new snapshot - * in their place */ - if (unlinkat(new_child_fd, p, AT_REMOVEDIR) < 0) { - int k = -errno; - - if (flags & BTRFS_SNAPSHOT_READ_ONLY) - (void) btrfs_subvol_set_read_only_fd(subvolume_fd, true); - - return k; - } - - r = subvol_snapshot_children(old_child_fd, new_child_fd, p, sh->objectid, flags & ~BTRFS_SNAPSHOT_FALLBACK_COPY); - - /* Restore the readonly flag */ - if (flags & BTRFS_SNAPSHOT_READ_ONLY) { - int k; - - k = btrfs_subvol_set_read_only_fd(subvolume_fd, true); - if (r >= 0 && k < 0) - return k; - } - - if (r < 0) - return r; - } - - /* Increase search key by one, to read the next item, if we can. */ - if (!btrfs_ioctl_search_args_inc(&args)) - break; - } - - if (flags & BTRFS_SNAPSHOT_QUOTA) - (void) copy_subtree_quota_limits(new_fd, old_subvol_id, new_subvol_id); - - return 0; -} - -int btrfs_subvol_snapshot_fd(int old_fd, const char *new_path, BtrfsSnapshotFlags flags) { - _cleanup_close_ int new_fd = -1; - const char *subvolume; - int r; - - assert(old_fd >= 0); - assert(new_path); - - r = btrfs_is_subvol_fd(old_fd); - if (r < 0) - return r; - if (r == 0) { - if (!(flags & BTRFS_SNAPSHOT_FALLBACK_COPY)) - return -EISDIR; - - r = btrfs_subvol_make(new_path); - if (r < 0) - return r; - - r = copy_directory_fd(old_fd, new_path, true); - if (r < 0) { - (void) btrfs_subvol_remove(new_path, BTRFS_REMOVE_QUOTA); - return r; - } - - if (flags & BTRFS_SNAPSHOT_READ_ONLY) { - r = btrfs_subvol_set_read_only(new_path, true); - if (r < 0) { - (void) btrfs_subvol_remove(new_path, BTRFS_REMOVE_QUOTA); - return r; - } - } - - return 0; - } - - r = extract_subvolume_name(new_path, &subvolume); - if (r < 0) - return r; - - new_fd = open_parent(new_path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (new_fd < 0) - return new_fd; - - return subvol_snapshot_children(old_fd, new_fd, subvolume, 0, flags); -} - -int btrfs_subvol_snapshot(const char *old_path, const char *new_path, BtrfsSnapshotFlags flags) { - _cleanup_close_ int old_fd = -1; - - assert(old_path); - assert(new_path); - - old_fd = open(old_path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (old_fd < 0) - return -errno; - - return btrfs_subvol_snapshot_fd(old_fd, new_path, flags); -} - -int btrfs_qgroup_find_parents(int fd, uint64_t qgroupid, uint64_t **ret) { - - struct btrfs_ioctl_search_args args = { - /* Tree of quota items */ - .key.tree_id = BTRFS_QUOTA_TREE_OBJECTID, - - /* Look precisely for the quota relation items */ - .key.min_type = BTRFS_QGROUP_RELATION_KEY, - .key.max_type = BTRFS_QGROUP_RELATION_KEY, - - /* No restrictions on the other components */ - .key.min_offset = 0, - .key.max_offset = (uint64_t) -1, - - .key.min_transid = 0, - .key.max_transid = (uint64_t) -1, - }; - - _cleanup_free_ uint64_t *items = NULL; - size_t n_items = 0, n_allocated = 0; - int r; - - assert(fd >= 0); - assert(ret); - - if (qgroupid == 0) { - r = btrfs_subvol_get_id_fd(fd, &qgroupid); - if (r < 0) - return r; - } else { - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - } - - args.key.min_objectid = args.key.max_objectid = qgroupid; - - while (btrfs_ioctl_search_args_compare(&args) <= 0) { - const struct btrfs_ioctl_search_header *sh; - unsigned i; - - args.key.nr_items = 256; - if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) { - if (errno == ENOENT) /* quota tree missing: quota is disabled */ - break; - - return -errno; - } - - if (args.key.nr_items <= 0) - break; - - FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { - - /* Make sure we start the next search at least from this entry */ - btrfs_ioctl_search_args_set(&args, sh); - - if (sh->type != BTRFS_QGROUP_RELATION_KEY) - continue; - if (sh->offset < sh->objectid) - continue; - if (sh->objectid != qgroupid) - continue; - - if (!GREEDY_REALLOC(items, n_allocated, n_items+1)) - return -ENOMEM; - - items[n_items++] = sh->offset; - } - - /* Increase search key by one, to read the next item, if we can. */ - if (!btrfs_ioctl_search_args_inc(&args)) - break; - } - - if (n_items <= 0) { - *ret = NULL; - return 0; - } - - *ret = items; - items = NULL; - - return (int) n_items; -} - -int btrfs_subvol_auto_qgroup_fd(int fd, uint64_t subvol_id, bool insert_intermediary_qgroup) { - _cleanup_free_ uint64_t *qgroups = NULL; - uint64_t parent_subvol; - bool changed = false; - int n = 0, r; - - assert(fd >= 0); - - /* - * Sets up the specified subvolume's qgroup automatically in - * one of two ways: - * - * If insert_intermediary_qgroup is false, the subvolume's - * leaf qgroup will be assigned to the same parent qgroups as - * the subvolume's parent subvolume. - * - * If insert_intermediary_qgroup is true a new intermediary - * higher-level qgroup is created, with a higher level number, - * but reusing the id of the subvolume. The level number is - * picked as one smaller than the lowest level qgroup the - * parent subvolume is a member of. If the parent subvolume's - * leaf qgroup is assigned to no higher-level qgroup a new - * qgroup of level 255 is created instead. Either way, the new - * qgroup is then assigned to the parent's higher-level - * qgroup, and the subvolume itself is assigned to it. - * - * If the subvolume is already assigned to a higher level - * qgroup, no operation is executed. - * - * Effectively this means: regardless if - * insert_intermediary_qgroup is true or not, after this - * function is invoked the subvolume will be accounted within - * the same qgroups as the parent. However, if it is true, it - * will also get its own higher-level qgroup, which may in - * turn be used by subvolumes created beneath this subvolume - * later on. - * - * This hence defines a simple default qgroup setup for - * subvolumes, as long as this function is invoked on each - * created subvolume: each subvolume is always accounting - * together with its immediate parents. Optionally, if - * insert_intermediary_qgroup is true, it will also get a - * qgroup that then includes all its own child subvolumes. - */ - - if (subvol_id == 0) { - r = btrfs_is_subvol_fd(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - - r = btrfs_subvol_get_id_fd(fd, &subvol_id); - if (r < 0) - return r; - } - - n = btrfs_qgroup_find_parents(fd, subvol_id, &qgroups); - if (n < 0) - return n; - if (n > 0) /* already parent qgroups set up, let's bail */ - return 0; - - qgroups = mfree(qgroups); - - r = btrfs_subvol_get_parent(fd, subvol_id, &parent_subvol); - if (r == -ENXIO) - /* No parent, hence no qgroup memberships */ - n = 0; - else if (r < 0) - return r; - else { - n = btrfs_qgroup_find_parents(fd, parent_subvol, &qgroups); - if (n < 0) - return n; - } - - if (insert_intermediary_qgroup) { - uint64_t lowest = 256, new_qgroupid; - bool created = false; - int i; - - /* Determine the lowest qgroup that the parent - * subvolume is assigned to. */ - - for (i = 0; i < n; i++) { - uint64_t level; - - r = btrfs_qgroupid_split(qgroups[i], &level, NULL); - if (r < 0) - return r; - - if (level < lowest) - lowest = level; - } - - if (lowest <= 1) /* There are no levels left we could use insert an intermediary qgroup at */ - return -EBUSY; - - r = btrfs_qgroupid_make(lowest - 1, subvol_id, &new_qgroupid); - if (r < 0) - return r; - - /* Create the new intermediary group, unless it already exists */ - r = btrfs_qgroup_create(fd, new_qgroupid); - if (r < 0 && r != -EEXIST) - return r; - if (r >= 0) - changed = created = true; - - for (i = 0; i < n; i++) { - r = btrfs_qgroup_assign(fd, new_qgroupid, qgroups[i]); - if (r < 0 && r != -EEXIST) { - if (created) - (void) btrfs_qgroup_destroy_recursive(fd, new_qgroupid); - - return r; - } - if (r >= 0) - changed = true; - } - - r = btrfs_qgroup_assign(fd, subvol_id, new_qgroupid); - if (r < 0 && r != -EEXIST) { - if (created) - (void) btrfs_qgroup_destroy_recursive(fd, new_qgroupid); - return r; - } - if (r >= 0) - changed = true; - - } else { - int i; - - /* Assign our subvolume to all the same qgroups as the parent */ - - for (i = 0; i < n; i++) { - r = btrfs_qgroup_assign(fd, subvol_id, qgroups[i]); - if (r < 0 && r != -EEXIST) - return r; - if (r >= 0) - changed = true; - } - } - - return changed; -} - -int btrfs_subvol_auto_qgroup(const char *path, uint64_t subvol_id, bool create_intermediary_qgroup) { - _cleanup_close_ int fd = -1; - - fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - return -errno; - - return btrfs_subvol_auto_qgroup_fd(fd, subvol_id, create_intermediary_qgroup); -} - -int btrfs_subvol_get_parent(int fd, uint64_t subvol_id, uint64_t *ret) { - - struct btrfs_ioctl_search_args args = { - /* Tree of tree roots */ - .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, - - /* Look precisely for the subvolume items */ - .key.min_type = BTRFS_ROOT_BACKREF_KEY, - .key.max_type = BTRFS_ROOT_BACKREF_KEY, - - /* No restrictions on the other components */ - .key.min_offset = 0, - .key.max_offset = (uint64_t) -1, - - .key.min_transid = 0, - .key.max_transid = (uint64_t) -1, - }; - int r; - - assert(fd >= 0); - assert(ret); - - if (subvol_id == 0) { - r = btrfs_subvol_get_id_fd(fd, &subvol_id); - if (r < 0) - return r; - } else { - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - } - - args.key.min_objectid = args.key.max_objectid = subvol_id; - - while (btrfs_ioctl_search_args_compare(&args) <= 0) { - const struct btrfs_ioctl_search_header *sh; - unsigned i; - - args.key.nr_items = 256; - if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) - return negative_errno(); - - if (args.key.nr_items <= 0) - break; - - FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { - - if (sh->type != BTRFS_ROOT_BACKREF_KEY) - continue; - if (sh->objectid != subvol_id) - continue; - - *ret = sh->offset; - return 0; - } - } - - return -ENXIO; -} diff --git a/src/basic/btrfs-util.h b/src/basic/btrfs-util.h deleted file mode 100644 index 1d852d502c..0000000000 --- a/src/basic/btrfs-util.h +++ /dev/null @@ -1,131 +0,0 @@ -#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 -#include -#include - -#include "sd-id128.h" - -#include "time-util.h" - -typedef struct BtrfsSubvolInfo { - uint64_t subvol_id; - usec_t otime; - - sd_id128_t uuid; - sd_id128_t parent_uuid; - - bool read_only; -} BtrfsSubvolInfo; - -typedef struct BtrfsQuotaInfo { - uint64_t referenced; - uint64_t exclusive; - uint64_t referenced_max; - uint64_t exclusive_max; -} BtrfsQuotaInfo; - -typedef enum BtrfsSnapshotFlags { - BTRFS_SNAPSHOT_FALLBACK_COPY = 1, - BTRFS_SNAPSHOT_READ_ONLY = 2, - BTRFS_SNAPSHOT_RECURSIVE = 4, - BTRFS_SNAPSHOT_QUOTA = 8, -} BtrfsSnapshotFlags; - -typedef enum BtrfsRemoveFlags { - BTRFS_REMOVE_RECURSIVE = 1, - BTRFS_REMOVE_QUOTA = 2, -} BtrfsRemoveFlags; - -int btrfs_is_filesystem(int fd); - -int btrfs_is_subvol_fd(int fd); -int btrfs_is_subvol(const char *path); - -int btrfs_reflink(int infd, int outfd); -int btrfs_clone_range(int infd, uint64_t in_offset, int ofd, uint64_t out_offset, uint64_t sz); - -int btrfs_get_block_device_fd(int fd, dev_t *dev); -int btrfs_get_block_device(const char *path, dev_t *dev); - -int btrfs_defrag_fd(int fd); -int btrfs_defrag(const char *p); - -int btrfs_quota_enable_fd(int fd, bool b); -int btrfs_quota_enable(const char *path, bool b); - -int btrfs_quota_scan_start(int fd); -int btrfs_quota_scan_wait(int fd); -int btrfs_quota_scan_ongoing(int fd); - -int btrfs_resize_loopback_fd(int fd, uint64_t size, bool grow_only); -int btrfs_resize_loopback(const char *path, uint64_t size, bool grow_only); - -int btrfs_subvol_make(const char *path); -int btrfs_subvol_make_label(const char *path); - -int btrfs_subvol_snapshot_fd(int old_fd, const char *new_path, BtrfsSnapshotFlags flags); -int btrfs_subvol_snapshot(const char *old_path, const char *new_path, BtrfsSnapshotFlags flags); - -int btrfs_subvol_remove(const char *path, BtrfsRemoveFlags flags); -int btrfs_subvol_remove_fd(int fd, const char *subvolume, BtrfsRemoveFlags flags); - -int btrfs_subvol_set_read_only_fd(int fd, bool b); -int btrfs_subvol_set_read_only(const char *path, bool b); -int btrfs_subvol_get_read_only_fd(int fd); - -int btrfs_subvol_get_id(int fd, const char *subvolume, uint64_t *ret); -int btrfs_subvol_get_id_fd(int fd, uint64_t *ret); -int btrfs_subvol_get_parent(int fd, uint64_t subvol_id, uint64_t *ret); - -int btrfs_subvol_get_info_fd(int fd, uint64_t subvol_id, BtrfsSubvolInfo *info); - -int btrfs_subvol_find_subtree_qgroup(int fd, uint64_t subvol_id, uint64_t *ret); - -int btrfs_subvol_get_subtree_quota(const char *path, uint64_t subvol_id, BtrfsQuotaInfo *quota); -int btrfs_subvol_get_subtree_quota_fd(int fd, uint64_t subvol_id, BtrfsQuotaInfo *quota); - -int btrfs_subvol_set_subtree_quota_limit(const char *path, uint64_t subvol_id, uint64_t referenced_max); -int btrfs_subvol_set_subtree_quota_limit_fd(int fd, uint64_t subvol_id, uint64_t referenced_max); - -int btrfs_subvol_auto_qgroup_fd(int fd, uint64_t subvol_id, bool new_qgroup); -int btrfs_subvol_auto_qgroup(const char *path, uint64_t subvol_id, bool create_intermediary_qgroup); - -int btrfs_qgroupid_make(uint64_t level, uint64_t id, uint64_t *ret); -int btrfs_qgroupid_split(uint64_t qgroupid, uint64_t *level, uint64_t *id); - -int btrfs_qgroup_create(int fd, uint64_t qgroupid); -int btrfs_qgroup_destroy(int fd, uint64_t qgroupid); -int btrfs_qgroup_destroy_recursive(int fd, uint64_t qgroupid); - -int btrfs_qgroup_set_limit_fd(int fd, uint64_t qgroupid, uint64_t referenced_max); -int btrfs_qgroup_set_limit(const char *path, uint64_t qgroupid, uint64_t referenced_max); - -int btrfs_qgroup_copy_limits(int fd, uint64_t old_qgroupid, uint64_t new_qgroupid); - -int btrfs_qgroup_assign(int fd, uint64_t child, uint64_t parent); -int btrfs_qgroup_unassign(int fd, uint64_t child, uint64_t parent); - -int btrfs_qgroup_find_parents(int fd, uint64_t qgroupid, uint64_t **ret); - -int btrfs_qgroup_get_quota_fd(int fd, uint64_t qgroupid, BtrfsQuotaInfo *quota); -int btrfs_qgroup_get_quota(const char *path, uint64_t qgroupid, BtrfsQuotaInfo *quota); diff --git a/src/basic/build.h b/src/basic/build.h deleted file mode 100644 index 633c2aaccb..0000000000 --- a/src/basic/build.h +++ /dev/null @@ -1,155 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#ifdef HAVE_PAM -#define _PAM_FEATURE_ "+PAM" -#else -#define _PAM_FEATURE_ "-PAM" -#endif - -#ifdef HAVE_AUDIT -#define _AUDIT_FEATURE_ "+AUDIT" -#else -#define _AUDIT_FEATURE_ "-AUDIT" -#endif - -#ifdef HAVE_SELINUX -#define _SELINUX_FEATURE_ "+SELINUX" -#else -#define _SELINUX_FEATURE_ "-SELINUX" -#endif - -#ifdef HAVE_APPARMOR -#define _APPARMOR_FEATURE_ "+APPARMOR" -#else -#define _APPARMOR_FEATURE_ "-APPARMOR" -#endif - -#ifdef HAVE_IMA -#define _IMA_FEATURE_ "+IMA" -#else -#define _IMA_FEATURE_ "-IMA" -#endif - -#ifdef HAVE_SMACK -#define _SMACK_FEATURE_ "+SMACK" -#else -#define _SMACK_FEATURE_ "-SMACK" -#endif - -#ifdef HAVE_SYSV_COMPAT -#define _SYSVINIT_FEATURE_ "+SYSVINIT" -#else -#define _SYSVINIT_FEATURE_ "-SYSVINIT" -#endif - -#ifdef HAVE_UTMP -#define _UTMP_FEATURE_ "+UTMP" -#else -#define _UTMP_FEATURE_ "-UTMP" -#endif - -#ifdef HAVE_LIBCRYPTSETUP -#define _LIBCRYPTSETUP_FEATURE_ "+LIBCRYPTSETUP" -#else -#define _LIBCRYPTSETUP_FEATURE_ "-LIBCRYPTSETUP" -#endif - -#ifdef HAVE_GCRYPT -#define _GCRYPT_FEATURE_ "+GCRYPT" -#else -#define _GCRYPT_FEATURE_ "-GCRYPT" -#endif - -#ifdef HAVE_GNUTLS -#define _GNUTLS_FEATURE_ "+GNUTLS" -#else -#define _GNUTLS_FEATURE_ "-GNUTLS" -#endif - -#ifdef HAVE_ACL -#define _ACL_FEATURE_ "+ACL" -#else -#define _ACL_FEATURE_ "-ACL" -#endif - -#ifdef HAVE_XZ -#define _XZ_FEATURE_ "+XZ" -#else -#define _XZ_FEATURE_ "-XZ" -#endif - -#ifdef HAVE_LZ4 -#define _LZ4_FEATURE_ "+LZ4" -#else -#define _LZ4_FEATURE_ "-LZ4" -#endif - -#ifdef HAVE_SECCOMP -#define _SECCOMP_FEATURE_ "+SECCOMP" -#else -#define _SECCOMP_FEATURE_ "-SECCOMP" -#endif - -#ifdef HAVE_BLKID -#define _BLKID_FEATURE_ "+BLKID" -#else -#define _BLKID_FEATURE_ "-BLKID" -#endif - -#ifdef HAVE_ELFUTILS -#define _ELFUTILS_FEATURE_ "+ELFUTILS" -#else -#define _ELFUTILS_FEATURE_ "-ELFUTILS" -#endif - -#ifdef HAVE_KMOD -#define _KMOD_FEATURE_ "+KMOD" -#else -#define _KMOD_FEATURE_ "-KMOD" -#endif - -#ifdef HAVE_LIBIDN -#define _IDN_FEATURE_ "+IDN" -#else -#define _IDN_FEATURE_ "-IDN" -#endif - -#define SYSTEMD_FEATURES \ - _PAM_FEATURE_ " " \ - _AUDIT_FEATURE_ " " \ - _SELINUX_FEATURE_ " " \ - _IMA_FEATURE_ " " \ - _APPARMOR_FEATURE_ " " \ - _SMACK_FEATURE_ " " \ - _SYSVINIT_FEATURE_ " " \ - _UTMP_FEATURE_ " " \ - _LIBCRYPTSETUP_FEATURE_ " " \ - _GCRYPT_FEATURE_ " " \ - _GNUTLS_FEATURE_ " " \ - _ACL_FEATURE_ " " \ - _XZ_FEATURE_ " " \ - _LZ4_FEATURE_ " " \ - _SECCOMP_FEATURE_ " " \ - _BLKID_FEATURE_ " " \ - _ELFUTILS_FEATURE_ " " \ - _KMOD_FEATURE_ " " \ - _IDN_FEATURE_ diff --git a/src/basic/bus-label.c b/src/basic/bus-label.c deleted file mode 100644 index d4531c7947..0000000000 --- a/src/basic/bus-label.c +++ /dev/null @@ -1,98 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "alloc-util.h" -#include "bus-label.h" -#include "hexdecoct.h" -#include "macro.h" - -char *bus_label_escape(const char *s) { - char *r, *t; - const char *f; - - assert_return(s, NULL); - - /* Escapes all chars that D-Bus' object path cannot deal - * with. Can be reversed with bus_path_unescape(). We special - * case the empty string. */ - - if (*s == 0) - return strdup("_"); - - r = new(char, strlen(s)*3 + 1); - if (!r) - return NULL; - - for (f = s, t = r; *f; f++) { - - /* Escape everything that is not a-zA-Z0-9. We also - * escape 0-9 if it's the first character */ - - if (!(*f >= 'A' && *f <= 'Z') && - !(*f >= 'a' && *f <= 'z') && - !(f > s && *f >= '0' && *f <= '9')) { - *(t++) = '_'; - *(t++) = hexchar(*f >> 4); - *(t++) = hexchar(*f); - } else - *(t++) = *f; - } - - *t = 0; - - return r; -} - -char *bus_label_unescape_n(const char *f, size_t l) { - char *r, *t; - size_t i; - - assert_return(f, NULL); - - /* Special case for the empty string */ - if (l == 1 && *f == '_') - return strdup(""); - - r = new(char, l + 1); - if (!r) - return NULL; - - for (i = 0, t = r; i < l; ++i) { - if (f[i] == '_') { - int a, b; - - if (l - i < 3 || - (a = unhexchar(f[i + 1])) < 0 || - (b = unhexchar(f[i + 2])) < 0) { - /* Invalid escape code, let's take it literal then */ - *(t++) = '_'; - } else { - *(t++) = (char) ((a << 4) | b); - i += 2; - } - } else - *(t++) = f[i]; - } - - *t = 0; - - return r; -} diff --git a/src/basic/bus-label.h b/src/basic/bus-label.h deleted file mode 100644 index 62fb2c450c..0000000000 --- a/src/basic/bus-label.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 -#include - -char *bus_label_escape(const char *s); -char *bus_label_unescape_n(const char *f, size_t l); - -static inline char *bus_label_unescape(const char *f) { - return bus_label_unescape_n(f, f ? strlen(f) : 0); -} diff --git a/src/basic/calendarspec.c b/src/basic/calendarspec.c deleted file mode 100644 index 6e0bab9b94..0000000000 --- a/src/basic/calendarspec.c +++ /dev/null @@ -1,1089 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "calendarspec.h" -#include "fileio.h" -#include "macro.h" -#include "parse-util.h" -#include "string-util.h" - -#define BITS_WEEKDAYS 127 - -static void free_chain(CalendarComponent *c) { - CalendarComponent *n; - - while (c) { - n = c->next; - free(c); - c = n; - } -} - -void calendar_spec_free(CalendarSpec *c) { - - if (!c) - return; - - free_chain(c->year); - free_chain(c->month); - free_chain(c->day); - free_chain(c->hour); - free_chain(c->minute); - free_chain(c->microsecond); - - free(c); -} - -static int component_compare(const void *_a, const void *_b) { - CalendarComponent * const *a = _a, * const *b = _b; - - if ((*a)->value < (*b)->value) - return -1; - if ((*a)->value > (*b)->value) - return 1; - - if ((*a)->repeat < (*b)->repeat) - return -1; - if ((*a)->repeat > (*b)->repeat) - return 1; - - return 0; -} - -static void sort_chain(CalendarComponent **c) { - unsigned n = 0, k; - CalendarComponent **b, *i, **j, *next; - - assert(c); - - for (i = *c; i; i = i->next) - n++; - - if (n <= 1) - return; - - j = b = alloca(sizeof(CalendarComponent*) * n); - for (i = *c; i; i = i->next) - *(j++) = i; - - qsort(b, n, sizeof(CalendarComponent*), component_compare); - - b[n-1]->next = NULL; - next = b[n-1]; - - /* Drop non-unique entries */ - for (k = n-1; k > 0; k--) { - if (b[k-1]->value == next->value && - b[k-1]->repeat == next->repeat) { - free(b[k-1]); - continue; - } - - b[k-1]->next = next; - next = b[k-1]; - } - - *c = next; -} - -static void fix_year(CalendarComponent *c) { - /* Turns 12 → 2012, 89 → 1989 */ - - while (c) { - CalendarComponent *n = c->next; - - if (c->value >= 0 && c->value < 70) - c->value += 2000; - - if (c->value >= 70 && c->value < 100) - c->value += 1900; - - c = n; - } -} - -int calendar_spec_normalize(CalendarSpec *c) { - assert(c); - - if (c->weekdays_bits <= 0 || c->weekdays_bits >= BITS_WEEKDAYS) - c->weekdays_bits = -1; - - fix_year(c->year); - - sort_chain(&c->year); - sort_chain(&c->month); - sort_chain(&c->day); - sort_chain(&c->hour); - sort_chain(&c->minute); - sort_chain(&c->microsecond); - - return 0; -} - -_pure_ static bool chain_valid(CalendarComponent *c, int from, int to) { - if (!c) - return true; - - if (c->value < from || c->value > to) - return false; - - if (c->value + c->repeat > to) - return false; - - if (c->next) - return chain_valid(c->next, from, to); - - return true; -} - -_pure_ bool calendar_spec_valid(CalendarSpec *c) { - assert(c); - - if (c->weekdays_bits > BITS_WEEKDAYS) - return false; - - if (!chain_valid(c->year, 1970, 2199)) - return false; - - if (!chain_valid(c->month, 1, 12)) - return false; - - if (!chain_valid(c->day, 1, 31)) - return false; - - if (!chain_valid(c->hour, 0, 23)) - return false; - - if (!chain_valid(c->minute, 0, 59)) - return false; - - if (!chain_valid(c->microsecond, 0, 60*USEC_PER_SEC-1)) - return false; - - return true; -} - -static void format_weekdays(FILE *f, const CalendarSpec *c) { - static const char *const days[] = { - "Mon", - "Tue", - "Wed", - "Thu", - "Fri", - "Sat", - "Sun" - }; - - int l, x; - bool need_colon = false; - - assert(f); - assert(c); - assert(c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS); - - for (x = 0, l = -1; x < (int) ELEMENTSOF(days); x++) { - - if (c->weekdays_bits & (1 << x)) { - - if (l < 0) { - if (need_colon) - fputc(',', f); - else - need_colon = true; - - fputs(days[x], f); - l = x; - } - - } else if (l >= 0) { - - if (x > l + 1) { - fputc(x > l + 2 ? '-' : ',', f); - fputs(days[x-1], f); - } - - l = -1; - } - } - - if (l >= 0 && x > l + 1) { - fputc(x > l + 2 ? '-' : ',', f); - fputs(days[x-1], f); - } -} - -static void format_chain(FILE *f, int space, const CalendarComponent *c, bool usec) { - assert(f); - - if (!c) { - fputc('*', f); - return; - } - - assert(c->value >= 0); - if (!usec) - fprintf(f, "%0*i", space, c->value); - else if (c->value % USEC_PER_SEC == 0) - fprintf(f, "%0*i", space, (int) (c->value / USEC_PER_SEC)); - else - fprintf(f, "%0*i.%06i", space, (int) (c->value / USEC_PER_SEC), (int) (c->value % USEC_PER_SEC)); - - if (c->repeat > 0) { - if (!usec) - fprintf(f, "/%i", c->repeat); - else if (c->repeat % USEC_PER_SEC == 0) - fprintf(f, "/%i", (int) (c->repeat / USEC_PER_SEC)); - else - fprintf(f, "/%i.%06i", (int) (c->repeat / USEC_PER_SEC), (int) (c->repeat % USEC_PER_SEC)); - } - - if (c->next) { - fputc(',', f); - format_chain(f, space, c->next, usec); - } -} - -int calendar_spec_to_string(const CalendarSpec *c, char **p) { - char *buf = NULL; - size_t sz = 0; - FILE *f; - int r; - - assert(c); - assert(p); - - f = open_memstream(&buf, &sz); - if (!f) - return -ENOMEM; - - if (c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS) { - format_weekdays(f, c); - fputc(' ', f); - } - - format_chain(f, 4, c->year, false); - fputc('-', f); - format_chain(f, 2, c->month, false); - fputc('-', f); - format_chain(f, 2, c->day, false); - fputc(' ', f); - format_chain(f, 2, c->hour, false); - fputc(':', f); - format_chain(f, 2, c->minute, false); - fputc(':', f); - format_chain(f, 2, c->microsecond, true); - - if (c->utc) - fputs(" UTC", f); - - r = fflush_and_check(f); - if (r < 0) { - free(buf); - fclose(f); - return r; - } - - fclose(f); - - *p = buf; - return 0; -} - -static int parse_weekdays(const char **p, CalendarSpec *c) { - static const struct { - const char *name; - const int nr; - } day_nr[] = { - { "Monday", 0 }, - { "Mon", 0 }, - { "Tuesday", 1 }, - { "Tue", 1 }, - { "Wednesday", 2 }, - { "Wed", 2 }, - { "Thursday", 3 }, - { "Thu", 3 }, - { "Friday", 4 }, - { "Fri", 4 }, - { "Saturday", 5 }, - { "Sat", 5 }, - { "Sunday", 6 }, - { "Sun", 6 } - }; - - int l = -1; - bool first = true; - - assert(p); - assert(*p); - assert(c); - - for (;;) { - unsigned i; - - if (!first && **p == ' ') - return 0; - - for (i = 0; i < ELEMENTSOF(day_nr); i++) { - size_t skip; - - if (!startswith_no_case(*p, day_nr[i].name)) - continue; - - skip = strlen(day_nr[i].name); - - if ((*p)[skip] != '-' && - (*p)[skip] != ',' && - (*p)[skip] != ' ' && - (*p)[skip] != 0) - return -EINVAL; - - c->weekdays_bits |= 1 << day_nr[i].nr; - - if (l >= 0) { - int j; - - if (l > day_nr[i].nr) - return -EINVAL; - - for (j = l + 1; j < day_nr[i].nr; j++) - c->weekdays_bits |= 1 << j; - } - - *p += skip; - break; - } - - /* Couldn't find this prefix, so let's assume the - weekday was not specified and let's continue with - the date */ - if (i >= ELEMENTSOF(day_nr)) - return first ? 0 : -EINVAL; - - /* We reached the end of the string */ - if (**p == 0) - return 0; - - /* We reached the end of the weekday spec part */ - if (**p == ' ') { - *p += strspn(*p, " "); - return 0; - } - - if (**p == '-') { - if (l >= 0) - return -EINVAL; - - l = day_nr[i].nr; - } else - l = -1; - - *p += 1; - first = false; - } -} - -static int parse_component_decimal(const char **p, bool usec, unsigned long *res) { - unsigned long value; - const char *e = NULL; - char *ee = NULL; - int r; - - errno = 0; - value = strtoul(*p, &ee, 10); - if (errno > 0) - return -errno; - if (ee == *p) - return -EINVAL; - if ((unsigned long) (int) value != value) - return -ERANGE; - e = ee; - - if (usec) { - if (value * USEC_PER_SEC / USEC_PER_SEC != value) - return -ERANGE; - - value *= USEC_PER_SEC; - if (*e == '.') { - unsigned add; - - e++; - r = parse_fractional_part_u(&e, 6, &add); - if (r < 0) - return r; - - if (add + value < value) - return -ERANGE; - value += add; - } - } - - *p = e; - *res = value; - - return 0; -} - -static int prepend_component(const char **p, bool usec, CalendarComponent **c) { - unsigned long value, repeat = 0; - CalendarComponent *cc; - int r; - const char *e; - - assert(p); - assert(c); - - e = *p; - - r = parse_component_decimal(&e, usec, &value); - if (r < 0) - return r; - - if (*e == '/') { - e++; - r = parse_component_decimal(&e, usec, &repeat); - if (r < 0) - return r; - - if (repeat == 0) - return -ERANGE; - } - - if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != ':') - return -EINVAL; - - cc = new0(CalendarComponent, 1); - if (!cc) - return -ENOMEM; - - cc->value = value; - cc->repeat = repeat; - cc->next = *c; - - *p = e; - *c = cc; - - if (*e ==',') { - *p += 1; - return prepend_component(p, usec, c); - } - - return 0; -} - -static int const_chain(int value, CalendarComponent **c) { - CalendarComponent *cc = NULL; - - assert(c); - - cc = new0(CalendarComponent, 1); - if (!cc) - return -ENOMEM; - - cc->value = value; - cc->repeat = 0; - cc->next = *c; - - *c = cc; - - return 0; -} - -static int parse_chain(const char **p, bool usec, CalendarComponent **c) { - const char *t; - CalendarComponent *cc = NULL; - int r; - - assert(p); - assert(c); - - t = *p; - - if (t[0] == '*') { - if (usec) { - r = const_chain(0, c); - if (r < 0) - return r; - (*c)->repeat = USEC_PER_SEC; - } else - *c = NULL; - - *p = t + 1; - return 0; - } - - r = prepend_component(&t, usec, &cc); - if (r < 0) { - free_chain(cc); - return r; - } - - *p = t; - *c = cc; - return 0; -} - -static int parse_date(const char **p, CalendarSpec *c) { - const char *t; - int r; - CalendarComponent *first, *second, *third; - - assert(p); - assert(*p); - assert(c); - - t = *p; - - if (*t == 0) - return 0; - - r = parse_chain(&t, false, &first); - if (r < 0) - return r; - - /* Already the end? A ':' as separator? In that case this was a time, not a date */ - if (*t == 0 || *t == ':') { - free_chain(first); - return 0; - } - - if (*t != '-') { - free_chain(first); - return -EINVAL; - } - - t++; - r = parse_chain(&t, false, &second); - if (r < 0) { - free_chain(first); - return r; - } - - /* Got two parts, hence it's month and day */ - if (*t == ' ' || *t == 0) { - *p = t + strspn(t, " "); - c->month = first; - c->day = second; - return 0; - } - - if (*t != '-') { - free_chain(first); - free_chain(second); - return -EINVAL; - } - - t++; - r = parse_chain(&t, false, &third); - if (r < 0) { - free_chain(first); - free_chain(second); - return r; - } - - /* Got tree parts, hence it is year, month and day */ - if (*t == ' ' || *t == 0) { - *p = t + strspn(t, " "); - c->year = first; - c->month = second; - c->day = third; - return 0; - } - - free_chain(first); - free_chain(second); - free_chain(third); - return -EINVAL; -} - -static int parse_calendar_time(const char **p, CalendarSpec *c) { - CalendarComponent *h = NULL, *m = NULL, *s = NULL; - const char *t; - int r; - - assert(p); - assert(*p); - assert(c); - - t = *p; - - if (*t == 0) { - /* If no time is specified at all, but a date of some - * kind, then this means 00:00:00 */ - if (c->day || c->weekdays_bits > 0) - goto null_hour; - - goto finish; - } - - r = parse_chain(&t, false, &h); - if (r < 0) - goto fail; - - if (*t != ':') { - r = -EINVAL; - goto fail; - } - - t++; - r = parse_chain(&t, false, &m); - if (r < 0) - goto fail; - - /* Already at the end? Then it's hours and minutes, and seconds are 0 */ - if (*t == 0) { - if (m != NULL) - goto null_second; - - goto finish; - } - - if (*t != ':') { - r = -EINVAL; - goto fail; - } - - t++; - r = parse_chain(&t, true, &s); - if (r < 0) - goto fail; - - /* At the end? Then it's hours, minutes and seconds */ - if (*t == 0) - goto finish; - - r = -EINVAL; - goto fail; - -null_hour: - r = const_chain(0, &h); - if (r < 0) - goto fail; - - r = const_chain(0, &m); - if (r < 0) - goto fail; - -null_second: - r = const_chain(0, &s); - if (r < 0) - goto fail; - -finish: - *p = t; - c->hour = h; - c->minute = m; - c->microsecond = s; - - return 0; - -fail: - free_chain(h); - free_chain(m); - free_chain(s); - return r; -} - -int calendar_spec_from_string(const char *p, CalendarSpec **spec) { - CalendarSpec *c; - int r; - const char *utc; - - assert(p); - assert(spec); - - if (isempty(p)) - return -EINVAL; - - c = new0(CalendarSpec, 1); - if (!c) - return -ENOMEM; - - utc = endswith_no_case(p, " UTC"); - if (utc) { - c->utc = true; - p = strndupa(p, utc - p); - } - - if (strcaseeq(p, "minutely")) { - r = const_chain(0, &c->microsecond); - if (r < 0) - goto fail; - - } else if (strcaseeq(p, "hourly")) { - r = const_chain(0, &c->minute); - if (r < 0) - goto fail; - r = const_chain(0, &c->microsecond); - if (r < 0) - goto fail; - - } else if (strcaseeq(p, "daily")) { - r = const_chain(0, &c->hour); - if (r < 0) - goto fail; - r = const_chain(0, &c->minute); - if (r < 0) - goto fail; - r = const_chain(0, &c->microsecond); - if (r < 0) - goto fail; - - } else if (strcaseeq(p, "monthly")) { - r = const_chain(1, &c->day); - if (r < 0) - goto fail; - r = const_chain(0, &c->hour); - if (r < 0) - goto fail; - r = const_chain(0, &c->minute); - if (r < 0) - goto fail; - r = const_chain(0, &c->microsecond); - if (r < 0) - goto fail; - - } else if (strcaseeq(p, "annually") || - strcaseeq(p, "yearly") || - strcaseeq(p, "anually") /* backwards compatibility */ ) { - - r = const_chain(1, &c->month); - if (r < 0) - goto fail; - r = const_chain(1, &c->day); - if (r < 0) - goto fail; - r = const_chain(0, &c->hour); - if (r < 0) - goto fail; - r = const_chain(0, &c->minute); - if (r < 0) - goto fail; - r = const_chain(0, &c->microsecond); - if (r < 0) - goto fail; - - } else if (strcaseeq(p, "weekly")) { - - c->weekdays_bits = 1; - - r = const_chain(0, &c->hour); - if (r < 0) - goto fail; - r = const_chain(0, &c->minute); - if (r < 0) - goto fail; - r = const_chain(0, &c->microsecond); - if (r < 0) - goto fail; - - } else if (strcaseeq(p, "quarterly")) { - - r = const_chain(1, &c->month); - if (r < 0) - goto fail; - r = const_chain(4, &c->month); - if (r < 0) - goto fail; - r = const_chain(7, &c->month); - if (r < 0) - goto fail; - r = const_chain(10, &c->month); - if (r < 0) - goto fail; - r = const_chain(1, &c->day); - if (r < 0) - goto fail; - r = const_chain(0, &c->hour); - if (r < 0) - goto fail; - r = const_chain(0, &c->minute); - if (r < 0) - goto fail; - r = const_chain(0, &c->microsecond); - if (r < 0) - goto fail; - - } else if (strcaseeq(p, "biannually") || - strcaseeq(p, "bi-annually") || - strcaseeq(p, "semiannually") || - strcaseeq(p, "semi-annually")) { - - r = const_chain(1, &c->month); - if (r < 0) - goto fail; - r = const_chain(7, &c->month); - if (r < 0) - goto fail; - r = const_chain(1, &c->day); - if (r < 0) - goto fail; - r = const_chain(0, &c->hour); - if (r < 0) - goto fail; - r = const_chain(0, &c->minute); - if (r < 0) - goto fail; - r = const_chain(0, &c->microsecond); - if (r < 0) - goto fail; - - } else { - r = parse_weekdays(&p, c); - if (r < 0) - goto fail; - - r = parse_date(&p, c); - if (r < 0) - goto fail; - - r = parse_calendar_time(&p, c); - if (r < 0) - goto fail; - - if (*p != 0) { - r = -EINVAL; - goto fail; - } - } - - r = calendar_spec_normalize(c); - if (r < 0) - goto fail; - - if (!calendar_spec_valid(c)) { - r = -EINVAL; - goto fail; - } - - *spec = c; - return 0; - -fail: - calendar_spec_free(c); - return r; -} - -static int find_matching_component(const CalendarComponent *c, int *val) { - const CalendarComponent *n; - int d = -1; - bool d_set = false; - int r; - - assert(val); - - if (!c) - return 0; - - while (c) { - n = c->next; - - if (c->value >= *val) { - - if (!d_set || c->value < d) { - d = c->value; - d_set = true; - } - - } else if (c->repeat > 0) { - int k; - - k = c->value + c->repeat * ((*val - c->value + c->repeat -1) / c->repeat); - - if (!d_set || k < d) { - d = k; - d_set = true; - } - } - - c = n; - } - - if (!d_set) - return -ENOENT; - - r = *val != d; - *val = d; - return r; -} - -static bool tm_out_of_bounds(const struct tm *tm, bool utc) { - struct tm t; - assert(tm); - - t = *tm; - - if (mktime_or_timegm(&t, utc) == (time_t) -1) - return true; - - /* Did any normalization take place? If so, it was out of bounds before */ - return - t.tm_year != tm->tm_year || - t.tm_mon != tm->tm_mon || - t.tm_mday != tm->tm_mday || - t.tm_hour != tm->tm_hour || - t.tm_min != tm->tm_min || - t.tm_sec != tm->tm_sec; -} - -static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) { - struct tm t; - int k; - - if (weekdays_bits < 0 || weekdays_bits >= BITS_WEEKDAYS) - return true; - - t = *tm; - if (mktime_or_timegm(&t, utc) == (time_t) -1) - return false; - - k = t.tm_wday == 0 ? 6 : t.tm_wday - 1; - return (weekdays_bits & (1 << k)); -} - -static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { - struct tm c; - int tm_usec; - int r; - - assert(spec); - assert(tm); - - c = *tm; - tm_usec = *usec; - - for (;;) { - /* Normalize the current date */ - (void) mktime_or_timegm(&c, spec->utc); - c.tm_isdst = -1; - - c.tm_year += 1900; - r = find_matching_component(spec->year, &c.tm_year); - c.tm_year -= 1900; - - if (r > 0) { - c.tm_mon = 0; - c.tm_mday = 1; - c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; - } - if (r < 0) - return r; - if (tm_out_of_bounds(&c, spec->utc)) - return -ENOENT; - - c.tm_mon += 1; - r = find_matching_component(spec->month, &c.tm_mon); - c.tm_mon -= 1; - - if (r > 0) { - c.tm_mday = 1; - c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; - } - if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { - c.tm_year++; - c.tm_mon = 0; - c.tm_mday = 1; - c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; - continue; - } - - r = find_matching_component(spec->day, &c.tm_mday); - if (r > 0) - c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; - if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { - c.tm_mon++; - c.tm_mday = 1; - c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; - continue; - } - - if (!matches_weekday(spec->weekdays_bits, &c, spec->utc)) { - c.tm_mday++; - c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; - continue; - } - - r = find_matching_component(spec->hour, &c.tm_hour); - if (r > 0) - c.tm_min = c.tm_sec = tm_usec = 0; - if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { - c.tm_mday++; - c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; - continue; - } - - r = find_matching_component(spec->minute, &c.tm_min); - if (r > 0) - c.tm_sec = tm_usec = 0; - if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { - c.tm_hour++; - c.tm_min = c.tm_sec = tm_usec = 0; - continue; - } - - c.tm_sec = c.tm_sec * USEC_PER_SEC + tm_usec; - r = find_matching_component(spec->microsecond, &c.tm_sec); - tm_usec = c.tm_sec % USEC_PER_SEC; - c.tm_sec /= USEC_PER_SEC; - - if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { - c.tm_min++; - c.tm_sec = tm_usec = 0; - continue; - } - - *tm = c; - *usec = tm_usec; - return 0; - } -} - -int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) { - struct tm tm; - time_t t; - int r; - usec_t tm_usec; - - assert(spec); - assert(next); - - usec++; - t = (time_t) (usec / USEC_PER_SEC); - assert_se(localtime_or_gmtime_r(&t, &tm, spec->utc)); - tm_usec = usec % USEC_PER_SEC; - - r = find_next(spec, &tm, &tm_usec); - if (r < 0) - return r; - - t = mktime_or_timegm(&tm, spec->utc); - if (t == (time_t) -1) - return -EINVAL; - - *next = (usec_t) t * USEC_PER_SEC + tm_usec; - return 0; -} diff --git a/src/basic/calendarspec.h b/src/basic/calendarspec.h deleted file mode 100644 index f6472c1244..0000000000 --- a/src/basic/calendarspec.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -/* A structure for specifying (possibly repetitive) points in calendar - * time, a la cron */ - -#include - -#include "time-util.h" -#include "util.h" - -typedef struct CalendarComponent { - int value; - int repeat; - - struct CalendarComponent *next; -} CalendarComponent; - -typedef struct CalendarSpec { - int weekdays_bits; - bool utc; - - CalendarComponent *year; - CalendarComponent *month; - CalendarComponent *day; - - CalendarComponent *hour; - CalendarComponent *minute; - CalendarComponent *microsecond; -} CalendarSpec; - -void calendar_spec_free(CalendarSpec *c); - -int calendar_spec_normalize(CalendarSpec *spec); -bool calendar_spec_valid(CalendarSpec *spec); - -int calendar_spec_to_string(const CalendarSpec *spec, char **p); -int calendar_spec_from_string(const char *p, CalendarSpec **spec); - -int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next); diff --git a/src/basic/cap-list.c b/src/basic/cap-list.c deleted file mode 100644 index 3e773a06f5..0000000000 --- a/src/basic/cap-list.c +++ /dev/null @@ -1,66 +0,0 @@ -/*** - 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 - -#include "cap-list.h" -#include "macro.h" -#include "missing.h" -#include "parse-util.h" -#include "util.h" - -static const struct capability_name* lookup_capability(register const char *str, register unsigned int len); - -#include "cap-from-name.h" -#include "cap-to-name.h" - -const char *capability_to_name(int id) { - - if (id < 0) - return NULL; - - if (id >= (int) ELEMENTSOF(capability_names)) - return NULL; - - return capability_names[id]; -} - -int capability_from_name(const char *name) { - const struct capability_name *sc; - int r, i; - - assert(name); - - /* Try to parse numeric capability */ - r = safe_atoi(name, &i); - if (r >= 0 && i >= 0) - return i; - - /* Try to parse string capability */ - sc = lookup_capability(name, strlen(name)); - if (!sc) - return -EINVAL; - - return sc->id; -} - -int capability_list_length(void) { - return (int) ELEMENTSOF(capability_names); -} diff --git a/src/basic/cap-list.h b/src/basic/cap-list.h deleted file mode 100644 index c1f6b94ad3..0000000000 --- a/src/basic/cap-list.h +++ /dev/null @@ -1,24 +0,0 @@ -#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 . -***/ - -const char *capability_to_name(int id); -int capability_from_name(const char *name); -int capability_list_length(void); diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c deleted file mode 100644 index d4c5bd6937..0000000000 --- a/src/basic/capability-util.c +++ /dev/null @@ -1,361 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "capability-util.h" -#include "fileio.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "util.h" - -int have_effective_cap(int value) { - _cleanup_cap_free_ cap_t cap; - cap_flag_value_t fv; - - cap = cap_get_proc(); - if (!cap) - return -errno; - - if (cap_get_flag(cap, value, CAP_EFFECTIVE, &fv) < 0) - return -errno; - else - return fv == CAP_SET; -} - -unsigned long cap_last_cap(void) { - static thread_local unsigned long saved; - static thread_local bool valid = false; - _cleanup_free_ char *content = NULL; - unsigned long p = 0; - int r; - - if (valid) - return saved; - - /* available since linux-3.2 */ - r = read_one_line_file("/proc/sys/kernel/cap_last_cap", &content); - if (r >= 0) { - r = safe_atolu(content, &p); - if (r >= 0) { - saved = p; - valid = true; - return p; - } - } - - /* fall back to syscall-probing for pre linux-3.2 */ - p = (unsigned long) CAP_LAST_CAP; - - if (prctl(PR_CAPBSET_READ, p) < 0) { - - /* Hmm, look downwards, until we find one that - * works */ - for (p--; p > 0; p --) - if (prctl(PR_CAPBSET_READ, p) >= 0) - break; - - } else { - - /* Hmm, look upwards, until we find one that doesn't - * work */ - for (;; p++) - if (prctl(PR_CAPBSET_READ, p+1) < 0) - break; - } - - saved = p; - valid = true; - - return p; -} - -int capability_update_inherited_set(cap_t caps, uint64_t set) { - unsigned long i; - - /* Add capabilities in the set to the inherited caps. Do not apply - * them yet. */ - - for (i = 0; i < cap_last_cap(); i++) { - - if (set & (UINT64_C(1) << i)) { - cap_value_t v; - - v = (cap_value_t) i; - - /* Make the capability inheritable. */ - if (cap_set_flag(caps, CAP_INHERITABLE, 1, &v, CAP_SET) < 0) - return -errno; - } - } - - return 0; -} - -int capability_ambient_set_apply(uint64_t set, bool also_inherit) { - unsigned long i; - _cleanup_cap_free_ cap_t caps = NULL; - - /* Add the capabilities to the ambient set. */ - - if (also_inherit) { - int r; - caps = cap_get_proc(); - if (!caps) - return -errno; - - r = capability_update_inherited_set(caps, set); - if (r < 0) - return -errno; - - if (cap_set_proc(caps) < 0) - return -errno; - } - - for (i = 0; i < cap_last_cap(); i++) { - - if (set & (UINT64_C(1) << i)) { - - /* Add the capability to the ambient set. */ - if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, i, 0, 0) < 0) - return -errno; - } - } - - return 0; -} - -int capability_bounding_set_drop(uint64_t keep, bool right_now) { - _cleanup_cap_free_ cap_t after_cap = NULL; - cap_flag_value_t fv; - unsigned long i; - int r; - - /* If we are run as PID 1 we will lack CAP_SETPCAP by default - * in the effective set (yes, the kernel drops that when - * executing init!), so get it back temporarily so that we can - * call PR_CAPBSET_DROP. */ - - after_cap = cap_get_proc(); - if (!after_cap) - return -errno; - - if (cap_get_flag(after_cap, CAP_SETPCAP, CAP_EFFECTIVE, &fv) < 0) - return -errno; - - if (fv != CAP_SET) { - _cleanup_cap_free_ cap_t temp_cap = NULL; - static const cap_value_t v = CAP_SETPCAP; - - temp_cap = cap_dup(after_cap); - if (!temp_cap) { - r = -errno; - goto finish; - } - - if (cap_set_flag(temp_cap, CAP_EFFECTIVE, 1, &v, CAP_SET) < 0) { - r = -errno; - goto finish; - } - - if (cap_set_proc(temp_cap) < 0) { - r = -errno; - goto finish; - } - } - - for (i = 0; i <= cap_last_cap(); i++) { - - if (!(keep & (UINT64_C(1) << i))) { - cap_value_t v; - - /* Drop it from the bounding set */ - if (prctl(PR_CAPBSET_DROP, i) < 0) { - r = -errno; - goto finish; - } - v = (cap_value_t) i; - - /* Also drop it from the inheritable set, so - * that anything we exec() loses the - * capability for good. */ - if (cap_set_flag(after_cap, CAP_INHERITABLE, 1, &v, CAP_CLEAR) < 0) { - r = -errno; - goto finish; - } - - /* If we shall apply this right now drop it - * also from our own capability sets. */ - if (right_now) { - if (cap_set_flag(after_cap, CAP_PERMITTED, 1, &v, CAP_CLEAR) < 0 || - cap_set_flag(after_cap, CAP_EFFECTIVE, 1, &v, CAP_CLEAR) < 0) { - r = -errno; - goto finish; - } - } - } - } - - r = 0; - -finish: - if (cap_set_proc(after_cap) < 0) - return -errno; - - return r; -} - -static int drop_from_file(const char *fn, uint64_t keep) { - int r, k; - uint32_t hi, lo; - uint64_t current, after; - char *p; - - r = read_one_line_file(fn, &p); - if (r < 0) - return r; - - assert_cc(sizeof(hi) == sizeof(unsigned)); - assert_cc(sizeof(lo) == sizeof(unsigned)); - - k = sscanf(p, "%u %u", &lo, &hi); - free(p); - - if (k != 2) - return -EIO; - - current = (uint64_t) lo | ((uint64_t) hi << 32ULL); - after = current & keep; - - if (current == after) - return 0; - - lo = (unsigned) (after & 0xFFFFFFFFULL); - hi = (unsigned) ((after >> 32ULL) & 0xFFFFFFFFULL); - - if (asprintf(&p, "%u %u", lo, hi) < 0) - return -ENOMEM; - - r = write_string_file(fn, p, WRITE_STRING_FILE_CREATE); - free(p); - - return r; -} - -int capability_bounding_set_drop_usermode(uint64_t keep) { - int r; - - r = drop_from_file("/proc/sys/kernel/usermodehelper/inheritable", keep); - if (r < 0) - return r; - - r = drop_from_file("/proc/sys/kernel/usermodehelper/bset", keep); - if (r < 0) - return r; - - return r; -} - -int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities) { - _cleanup_cap_free_ cap_t d = NULL; - unsigned i, j = 0; - int r; - - /* Unfortunately we cannot leave privilege dropping to PID 1 - * here, since we want to run as user but want to keep some - * capabilities. Since file capabilities have been introduced - * this cannot be done across exec() anymore, unless our - * binary has the capability configured in the file system, - * which we want to avoid. */ - - if (setresgid(gid, gid, gid) < 0) - return log_error_errno(errno, "Failed to change group ID: %m"); - - if (setgroups(0, NULL) < 0) - return log_error_errno(errno, "Failed to drop auxiliary groups list: %m"); - - /* Ensure we keep the permitted caps across the setresuid() */ - if (prctl(PR_SET_KEEPCAPS, 1) < 0) - return log_error_errno(errno, "Failed to enable keep capabilities flag: %m"); - - r = setresuid(uid, uid, uid); - if (r < 0) - return log_error_errno(errno, "Failed to change user ID: %m"); - - if (prctl(PR_SET_KEEPCAPS, 0) < 0) - return log_error_errno(errno, "Failed to disable keep capabilities flag: %m"); - - /* Drop all caps from the bounding set, except the ones we want */ - r = capability_bounding_set_drop(keep_capabilities, true); - if (r < 0) - return log_error_errno(r, "Failed to drop capabilities: %m"); - - /* Now upgrade the permitted caps we still kept to effective caps */ - d = cap_init(); - if (!d) - return log_oom(); - - if (keep_capabilities) { - cap_value_t bits[u64log2(keep_capabilities) + 1]; - - for (i = 0; i < ELEMENTSOF(bits); i++) - if (keep_capabilities & (1ULL << i)) - bits[j++] = i; - - /* use enough bits */ - assert(i == 64 || (keep_capabilities >> i) == 0); - /* don't use too many bits */ - assert(keep_capabilities & (1ULL << (i - 1))); - - if (cap_set_flag(d, CAP_EFFECTIVE, j, bits, CAP_SET) < 0 || - cap_set_flag(d, CAP_PERMITTED, j, bits, CAP_SET) < 0) - return log_error_errno(errno, "Failed to enable capabilities bits: %m"); - - if (cap_set_proc(d) < 0) - return log_error_errno(errno, "Failed to increase capabilities: %m"); - } - - return 0; -} - -int drop_capability(cap_value_t cv) { - _cleanup_cap_free_ cap_t tmp_cap = NULL; - - tmp_cap = cap_get_proc(); - if (!tmp_cap) - return -errno; - - if ((cap_set_flag(tmp_cap, CAP_INHERITABLE, 1, &cv, CAP_CLEAR) < 0) || - (cap_set_flag(tmp_cap, CAP_PERMITTED, 1, &cv, CAP_CLEAR) < 0) || - (cap_set_flag(tmp_cap, CAP_EFFECTIVE, 1, &cv, CAP_CLEAR) < 0)) - return -errno; - - if (cap_set_proc(tmp_cap) < 0) - return -errno; - - return 0; -} diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h deleted file mode 100644 index 35a896e229..0000000000 --- a/src/basic/capability-util.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include - -#include "macro.h" -#include "util.h" - -#define CAP_ALL (uint64_t) -1 - -unsigned long cap_last_cap(void); -int have_effective_cap(int value); -int capability_bounding_set_drop(uint64_t keep, bool right_now); -int capability_bounding_set_drop_usermode(uint64_t keep); - -int capability_ambient_set_apply(uint64_t set, bool also_inherit); -int capability_update_inherited_set(cap_t caps, uint64_t ambient_set); - -int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities); - -int drop_capability(cap_value_t cv); - -DEFINE_TRIVIAL_CLEANUP_FUNC(cap_t, cap_free); -#define _cleanup_cap_free_ _cleanup_(cap_freep) - -static inline void cap_free_charpp(char **p) { - if (*p) - cap_free(*p); -} -#define _cleanup_cap_free_charp_ _cleanup_(cap_free_charpp) - -static inline bool cap_test_all(uint64_t caps) { - uint64_t m; - m = (UINT64_C(1) << (cap_last_cap() + 1)) - 1; - return (caps & m) == m; -} diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c deleted file mode 100644 index 7cdc97ee3c..0000000000 --- a/src/basic/cgroup-util.c +++ /dev/null @@ -1,2338 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "cgroup-util.h" -#include "def.h" -#include "dirent-util.h" -#include "extract-word.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "log.h" -#include "login-util.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "parse-util.h" -#include "path-util.h" -#include "proc-cmdline.h" -#include "process-util.h" -#include "set.h" -#include "special.h" -#include "stat-util.h" -#include "stdio-util.h" -#include "string-table.h" -#include "string-util.h" -#include "unit-name.h" -#include "user-util.h" - -int cg_enumerate_processes(const char *controller, const char *path, FILE **_f) { - _cleanup_free_ char *fs = NULL; - FILE *f; - int r; - - assert(_f); - - r = cg_get_path(controller, path, "cgroup.procs", &fs); - if (r < 0) - return r; - - f = fopen(fs, "re"); - if (!f) - return -errno; - - *_f = f; - return 0; -} - -int cg_read_pid(FILE *f, pid_t *_pid) { - unsigned long ul; - - /* Note that the cgroup.procs might contain duplicates! See - * cgroups.txt for details. */ - - assert(f); - assert(_pid); - - errno = 0; - if (fscanf(f, "%lu", &ul) != 1) { - - if (feof(f)) - return 0; - - return errno > 0 ? -errno : -EIO; - } - - if (ul <= 0) - return -EIO; - - *_pid = (pid_t) ul; - return 1; -} - -int cg_read_event(const char *controller, const char *path, const char *event, - char **val) -{ - _cleanup_free_ char *events = NULL, *content = NULL; - char *p, *line; - int r; - - r = cg_get_path(controller, path, "cgroup.events", &events); - if (r < 0) - return r; - - r = read_full_file(events, &content, NULL); - if (r < 0) - return r; - - p = content; - while ((line = strsep(&p, "\n"))) { - char *key; - - key = strsep(&line, " "); - if (!key || !line) - return -EINVAL; - - if (strcmp(key, event)) - continue; - - *val = strdup(line); - return 0; - } - - return -ENOENT; -} - -int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d) { - _cleanup_free_ char *fs = NULL; - int r; - DIR *d; - - assert(_d); - - /* This is not recursive! */ - - r = cg_get_path(controller, path, NULL, &fs); - if (r < 0) - return r; - - d = opendir(fs); - if (!d) - return -errno; - - *_d = d; - return 0; -} - -int cg_read_subgroup(DIR *d, char **fn) { - struct dirent *de; - - assert(d); - assert(fn); - - FOREACH_DIRENT_ALL(de, d, return -errno) { - char *b; - - if (de->d_type != DT_DIR) - continue; - - if (streq(de->d_name, ".") || - streq(de->d_name, "..")) - continue; - - b = strdup(de->d_name); - if (!b) - return -ENOMEM; - - *fn = b; - return 1; - } - - return 0; -} - -int cg_rmdir(const char *controller, const char *path) { - _cleanup_free_ char *p = NULL; - int r; - - r = cg_get_path(controller, path, NULL, &p); - if (r < 0) - return r; - - r = rmdir(p); - if (r < 0 && errno != ENOENT) - return -errno; - - return 0; -} - -int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s) { - _cleanup_set_free_ Set *allocated_set = NULL; - bool done = false; - int r, ret = 0; - pid_t my_pid; - - assert(sig >= 0); - - /* This goes through the tasks list and kills them all. This - * is repeated until no further processes are added to the - * tasks list, to properly handle forking processes */ - - if (!s) { - s = allocated_set = set_new(NULL); - if (!s) - return -ENOMEM; - } - - my_pid = getpid(); - - do { - _cleanup_fclose_ FILE *f = NULL; - pid_t pid = 0; - done = true; - - r = cg_enumerate_processes(controller, path, &f); - if (r < 0) { - if (ret >= 0 && r != -ENOENT) - return r; - - return ret; - } - - while ((r = cg_read_pid(f, &pid)) > 0) { - - if (ignore_self && pid == my_pid) - continue; - - if (set_get(s, PID_TO_PTR(pid)) == PID_TO_PTR(pid)) - continue; - - /* If we haven't killed this process yet, kill - * it */ - if (kill(pid, sig) < 0) { - if (ret >= 0 && errno != ESRCH) - ret = -errno; - } else { - if (sigcont && sig != SIGKILL) - (void) kill(pid, SIGCONT); - - if (ret == 0) - ret = 1; - } - - done = false; - - r = set_put(s, PID_TO_PTR(pid)); - if (r < 0) { - if (ret >= 0) - return r; - - return ret; - } - } - - if (r < 0) { - if (ret >= 0) - return r; - - return ret; - } - - /* To avoid racing against processes which fork - * quicker than we can kill them we repeat this until - * no new pids need to be killed. */ - - } while (!done); - - return ret; -} - -int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool rem, Set *s) { - _cleanup_set_free_ Set *allocated_set = NULL; - _cleanup_closedir_ DIR *d = NULL; - int r, ret; - char *fn; - - assert(path); - assert(sig >= 0); - - if (!s) { - s = allocated_set = set_new(NULL); - if (!s) - return -ENOMEM; - } - - ret = cg_kill(controller, path, sig, sigcont, ignore_self, s); - - r = cg_enumerate_subgroups(controller, path, &d); - if (r < 0) { - if (ret >= 0 && r != -ENOENT) - return r; - - return ret; - } - - while ((r = cg_read_subgroup(d, &fn)) > 0) { - _cleanup_free_ char *p = NULL; - - p = strjoin(path, "/", fn, NULL); - free(fn); - if (!p) - return -ENOMEM; - - r = cg_kill_recursive(controller, p, sig, sigcont, ignore_self, rem, s); - if (r != 0 && ret >= 0) - ret = r; - } - - if (ret >= 0 && r < 0) - ret = r; - - if (rem) { - r = cg_rmdir(controller, path); - if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY) - return r; - } - - return ret; -} - -int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self) { - bool done = false; - _cleanup_set_free_ Set *s = NULL; - int r, ret = 0; - pid_t my_pid; - - assert(cfrom); - assert(pfrom); - assert(cto); - assert(pto); - - s = set_new(NULL); - if (!s) - return -ENOMEM; - - my_pid = getpid(); - - do { - _cleanup_fclose_ FILE *f = NULL; - pid_t pid = 0; - done = true; - - r = cg_enumerate_processes(cfrom, pfrom, &f); - if (r < 0) { - if (ret >= 0 && r != -ENOENT) - return r; - - return ret; - } - - while ((r = cg_read_pid(f, &pid)) > 0) { - - /* This might do weird stuff if we aren't a - * single-threaded program. However, we - * luckily know we are not */ - if (ignore_self && pid == my_pid) - continue; - - if (set_get(s, PID_TO_PTR(pid)) == PID_TO_PTR(pid)) - continue; - - /* Ignore kernel threads. Since they can only - * exist in the root cgroup, we only check for - * them there. */ - if (cfrom && - (isempty(pfrom) || path_equal(pfrom, "/")) && - is_kernel_thread(pid) > 0) - continue; - - r = cg_attach(cto, pto, pid); - if (r < 0) { - if (ret >= 0 && r != -ESRCH) - ret = r; - } else if (ret == 0) - ret = 1; - - done = false; - - r = set_put(s, PID_TO_PTR(pid)); - if (r < 0) { - if (ret >= 0) - return r; - - return ret; - } - } - - if (r < 0) { - if (ret >= 0) - return r; - - return ret; - } - } while (!done); - - return ret; -} - -int cg_migrate_recursive( - const char *cfrom, - const char *pfrom, - const char *cto, - const char *pto, - bool ignore_self, - bool rem) { - - _cleanup_closedir_ DIR *d = NULL; - int r, ret = 0; - char *fn; - - assert(cfrom); - assert(pfrom); - assert(cto); - assert(pto); - - ret = cg_migrate(cfrom, pfrom, cto, pto, ignore_self); - - r = cg_enumerate_subgroups(cfrom, pfrom, &d); - if (r < 0) { - if (ret >= 0 && r != -ENOENT) - return r; - - return ret; - } - - while ((r = cg_read_subgroup(d, &fn)) > 0) { - _cleanup_free_ char *p = NULL; - - p = strjoin(pfrom, "/", fn, NULL); - free(fn); - if (!p) - return -ENOMEM; - - r = cg_migrate_recursive(cfrom, p, cto, pto, ignore_self, rem); - if (r != 0 && ret >= 0) - ret = r; - } - - if (r < 0 && ret >= 0) - ret = r; - - if (rem) { - r = cg_rmdir(cfrom, pfrom); - if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY) - return r; - } - - return ret; -} - -int cg_migrate_recursive_fallback( - const char *cfrom, - const char *pfrom, - const char *cto, - const char *pto, - bool ignore_self, - bool rem) { - - int r; - - assert(cfrom); - assert(pfrom); - assert(cto); - assert(pto); - - r = cg_migrate_recursive(cfrom, pfrom, cto, pto, ignore_self, rem); - if (r < 0) { - char prefix[strlen(pto) + 1]; - - /* This didn't work? Then let's try all prefixes of the destination */ - - PATH_FOREACH_PREFIX(prefix, pto) { - int q; - - q = cg_migrate_recursive(cfrom, pfrom, cto, prefix, ignore_self, rem); - if (q >= 0) - return q; - } - } - - return r; -} - -static const char *controller_to_dirname(const char *controller) { - const char *e; - - assert(controller); - - /* Converts a controller name to the directory name below - * /sys/fs/cgroup/ we want to mount it to. Effectively, this - * just cuts off the name= prefixed used for named - * hierarchies, if it is specified. */ - - e = startswith(controller, "name="); - if (e) - return e; - - return controller; -} - -static int join_path_legacy(const char *controller, const char *path, const char *suffix, char **fs) { - const char *dn; - char *t = NULL; - - assert(fs); - assert(controller); - - dn = controller_to_dirname(controller); - - if (isempty(path) && isempty(suffix)) - t = strappend("/sys/fs/cgroup/", dn); - else if (isempty(path)) - t = strjoin("/sys/fs/cgroup/", dn, "/", suffix, NULL); - else if (isempty(suffix)) - t = strjoin("/sys/fs/cgroup/", dn, "/", path, NULL); - else - t = strjoin("/sys/fs/cgroup/", dn, "/", path, "/", suffix, NULL); - if (!t) - return -ENOMEM; - - *fs = t; - return 0; -} - -static int join_path_unified(const char *path, const char *suffix, char **fs) { - char *t; - - assert(fs); - - if (isempty(path) && isempty(suffix)) - t = strdup("/sys/fs/cgroup"); - else if (isempty(path)) - t = strappend("/sys/fs/cgroup/", suffix); - else if (isempty(suffix)) - t = strappend("/sys/fs/cgroup/", path); - else - t = strjoin("/sys/fs/cgroup/", path, "/", suffix, NULL); - if (!t) - return -ENOMEM; - - *fs = t; - return 0; -} - -int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs) { - int unified, r; - - assert(fs); - - if (!controller) { - char *t; - - /* If no controller is specified, we return the path - * *below* the controllers, without any prefix. */ - - if (!path && !suffix) - return -EINVAL; - - if (!suffix) - t = strdup(path); - else if (!path) - t = strdup(suffix); - else - t = strjoin(path, "/", suffix, NULL); - if (!t) - return -ENOMEM; - - *fs = path_kill_slashes(t); - return 0; - } - - if (!cg_controller_is_valid(controller)) - return -EINVAL; - - unified = cg_unified(); - if (unified < 0) - return unified; - - if (unified > 0) - r = join_path_unified(path, suffix, fs); - else - r = join_path_legacy(controller, path, suffix, fs); - if (r < 0) - return r; - - path_kill_slashes(*fs); - return 0; -} - -static int controller_is_accessible(const char *controller) { - int unified; - - assert(controller); - - /* Checks whether a specific controller is accessible, - * i.e. its hierarchy mounted. In the unified hierarchy all - * controllers are considered accessible, except for the named - * hierarchies */ - - if (!cg_controller_is_valid(controller)) - return -EINVAL; - - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified > 0) { - /* We don't support named hierarchies if we are using - * the unified hierarchy. */ - - if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) - return 0; - - if (startswith(controller, "name=")) - return -EOPNOTSUPP; - - } else { - const char *cc, *dn; - - dn = controller_to_dirname(controller); - cc = strjoina("/sys/fs/cgroup/", dn); - - if (laccess(cc, F_OK) < 0) - return -errno; - } - - return 0; -} - -int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs) { - int r; - - assert(controller); - assert(fs); - - /* Check if the specified controller is actually accessible */ - r = controller_is_accessible(controller); - if (r < 0) - return r; - - return cg_get_path(controller, path, suffix, fs); -} - -static int trim_cb(const char *path, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { - assert(path); - assert(sb); - assert(ftwbuf); - - if (typeflag != FTW_DP) - return 0; - - if (ftwbuf->level < 1) - return 0; - - (void) rmdir(path); - return 0; -} - -int cg_trim(const char *controller, const char *path, bool delete_root) { - _cleanup_free_ char *fs = NULL; - int r = 0; - - assert(path); - - r = cg_get_path(controller, path, NULL, &fs); - if (r < 0) - return r; - - errno = 0; - if (nftw(fs, trim_cb, 64, FTW_DEPTH|FTW_MOUNT|FTW_PHYS) != 0) { - if (errno == ENOENT) - r = 0; - else if (errno > 0) - r = -errno; - else - r = -EIO; - } - - if (delete_root) { - if (rmdir(fs) < 0 && errno != ENOENT) - return -errno; - } - - return r; -} - -int cg_create(const char *controller, const char *path) { - _cleanup_free_ char *fs = NULL; - int r; - - r = cg_get_path_and_check(controller, path, NULL, &fs); - if (r < 0) - return r; - - r = mkdir_parents(fs, 0755); - if (r < 0) - return r; - - if (mkdir(fs, 0755) < 0) { - - if (errno == EEXIST) - return 0; - - return -errno; - } - - return 1; -} - -int cg_create_and_attach(const char *controller, const char *path, pid_t pid) { - int r, q; - - assert(pid >= 0); - - r = cg_create(controller, path); - if (r < 0) - return r; - - q = cg_attach(controller, path, pid); - if (q < 0) - return q; - - /* This does not remove the cgroup on failure */ - return r; -} - -int cg_attach(const char *controller, const char *path, pid_t pid) { - _cleanup_free_ char *fs = NULL; - char c[DECIMAL_STR_MAX(pid_t) + 2]; - int r; - - assert(path); - assert(pid >= 0); - - r = cg_get_path_and_check(controller, path, "cgroup.procs", &fs); - if (r < 0) - return r; - - if (pid == 0) - pid = getpid(); - - xsprintf(c, PID_FMT "\n", pid); - - return write_string_file(fs, c, 0); -} - -int cg_attach_fallback(const char *controller, const char *path, pid_t pid) { - int r; - - assert(controller); - assert(path); - assert(pid >= 0); - - r = cg_attach(controller, path, pid); - if (r < 0) { - char prefix[strlen(path) + 1]; - - /* This didn't work? Then let's try all prefixes of - * the destination */ - - PATH_FOREACH_PREFIX(prefix, path) { - int q; - - q = cg_attach(controller, prefix, pid); - if (q >= 0) - return q; - } - } - - return r; -} - -int cg_set_group_access( - const char *controller, - const char *path, - mode_t mode, - uid_t uid, - gid_t gid) { - - _cleanup_free_ char *fs = NULL; - int r; - - if (mode == MODE_INVALID && uid == UID_INVALID && gid == GID_INVALID) - return 0; - - if (mode != MODE_INVALID) - mode &= 0777; - - r = cg_get_path(controller, path, NULL, &fs); - if (r < 0) - return r; - - return chmod_and_chown(fs, mode, uid, gid); -} - -int cg_set_task_access( - const char *controller, - const char *path, - mode_t mode, - uid_t uid, - gid_t gid) { - - _cleanup_free_ char *fs = NULL, *procs = NULL; - int r, unified; - - assert(path); - - if (mode == MODE_INVALID && uid == UID_INVALID && gid == GID_INVALID) - return 0; - - if (mode != MODE_INVALID) - mode &= 0666; - - r = cg_get_path(controller, path, "cgroup.procs", &fs); - if (r < 0) - return r; - - r = chmod_and_chown(fs, mode, uid, gid); - if (r < 0) - return r; - - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified) - return 0; - - /* Compatibility, Always keep values for "tasks" in sync with - * "cgroup.procs" */ - if (cg_get_path(controller, path, "tasks", &procs) >= 0) - (void) chmod_and_chown(procs, mode, uid, gid); - - return 0; -} - -int cg_pid_get_path(const char *controller, pid_t pid, char **path) { - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - const char *fs; - size_t cs = 0; - int unified; - - assert(path); - assert(pid >= 0); - - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified == 0) { - if (controller) { - if (!cg_controller_is_valid(controller)) - return -EINVAL; - } else - controller = SYSTEMD_CGROUP_CONTROLLER; - - cs = strlen(controller); - } - - fs = procfs_file_alloca(pid, "cgroup"); - f = fopen(fs, "re"); - if (!f) - return errno == ENOENT ? -ESRCH : -errno; - - FOREACH_LINE(line, f, return -errno) { - char *e, *p; - - truncate_nl(line); - - if (unified) { - e = startswith(line, "0:"); - if (!e) - continue; - - e = strchr(e, ':'); - if (!e) - continue; - } else { - char *l; - size_t k; - const char *word, *state; - bool found = false; - - l = strchr(line, ':'); - if (!l) - continue; - - l++; - e = strchr(l, ':'); - if (!e) - continue; - - *e = 0; - FOREACH_WORD_SEPARATOR(word, k, l, ",", state) { - if (k == cs && memcmp(word, controller, cs) == 0) { - found = true; - break; - } - } - - if (!found) - continue; - } - - p = strdup(e + 1); - if (!p) - return -ENOMEM; - - *path = p; - return 0; - } - - return -ENODATA; -} - -int cg_install_release_agent(const char *controller, const char *agent) { - _cleanup_free_ char *fs = NULL, *contents = NULL; - const char *sc; - int r, unified; - - assert(agent); - - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified) /* doesn't apply to unified hierarchy */ - return -EOPNOTSUPP; - - r = cg_get_path(controller, NULL, "release_agent", &fs); - if (r < 0) - return r; - - r = read_one_line_file(fs, &contents); - if (r < 0) - return r; - - sc = strstrip(contents); - if (isempty(sc)) { - r = write_string_file(fs, agent, 0); - if (r < 0) - return r; - } else if (!path_equal(sc, agent)) - return -EEXIST; - - fs = mfree(fs); - r = cg_get_path(controller, NULL, "notify_on_release", &fs); - if (r < 0) - return r; - - contents = mfree(contents); - r = read_one_line_file(fs, &contents); - if (r < 0) - return r; - - sc = strstrip(contents); - if (streq(sc, "0")) { - r = write_string_file(fs, "1", 0); - if (r < 0) - return r; - - return 1; - } - - if (!streq(sc, "1")) - return -EIO; - - return 0; -} - -int cg_uninstall_release_agent(const char *controller) { - _cleanup_free_ char *fs = NULL; - int r, unified; - - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified) /* Doesn't apply to unified hierarchy */ - return -EOPNOTSUPP; - - r = cg_get_path(controller, NULL, "notify_on_release", &fs); - if (r < 0) - return r; - - r = write_string_file(fs, "0", 0); - if (r < 0) - return r; - - fs = mfree(fs); - - r = cg_get_path(controller, NULL, "release_agent", &fs); - if (r < 0) - return r; - - r = write_string_file(fs, "", 0); - if (r < 0) - return r; - - return 0; -} - -int cg_is_empty(const char *controller, const char *path) { - _cleanup_fclose_ FILE *f = NULL; - pid_t pid; - int r; - - assert(path); - - r = cg_enumerate_processes(controller, path, &f); - if (r == -ENOENT) - return 1; - if (r < 0) - return r; - - r = cg_read_pid(f, &pid); - if (r < 0) - return r; - - return r == 0; -} - -int cg_is_empty_recursive(const char *controller, const char *path) { - int unified, r; - - assert(path); - - /* The root cgroup is always populated */ - if (controller && (isempty(path) || path_equal(path, "/"))) - return false; - - unified = cg_unified(); - if (unified < 0) - return unified; - - if (unified > 0) { - _cleanup_free_ char *t = NULL; - - /* On the unified hierarchy we can check empty state - * via the "populated" attribute of "cgroup.events". */ - - r = cg_read_event(controller, path, "populated", &t); - if (r < 0) - return r; - - return streq(t, "0"); - } else { - _cleanup_closedir_ DIR *d = NULL; - char *fn; - - r = cg_is_empty(controller, path); - if (r <= 0) - return r; - - r = cg_enumerate_subgroups(controller, path, &d); - if (r == -ENOENT) - return 1; - if (r < 0) - return r; - - while ((r = cg_read_subgroup(d, &fn)) > 0) { - _cleanup_free_ char *p = NULL; - - p = strjoin(path, "/", fn, NULL); - free(fn); - if (!p) - return -ENOMEM; - - r = cg_is_empty_recursive(controller, p); - if (r <= 0) - return r; - } - if (r < 0) - return r; - - return true; - } -} - -int cg_split_spec(const char *spec, char **controller, char **path) { - char *t = NULL, *u = NULL; - const char *e; - - assert(spec); - - if (*spec == '/') { - if (!path_is_safe(spec)) - return -EINVAL; - - if (path) { - t = strdup(spec); - if (!t) - return -ENOMEM; - - *path = path_kill_slashes(t); - } - - if (controller) - *controller = NULL; - - return 0; - } - - e = strchr(spec, ':'); - if (!e) { - if (!cg_controller_is_valid(spec)) - return -EINVAL; - - if (controller) { - t = strdup(spec); - if (!t) - return -ENOMEM; - - *controller = t; - } - - if (path) - *path = NULL; - - return 0; - } - - t = strndup(spec, e-spec); - if (!t) - return -ENOMEM; - if (!cg_controller_is_valid(t)) { - free(t); - return -EINVAL; - } - - if (isempty(e+1)) - u = NULL; - else { - u = strdup(e+1); - if (!u) { - free(t); - return -ENOMEM; - } - - if (!path_is_safe(u) || - !path_is_absolute(u)) { - free(t); - free(u); - return -EINVAL; - } - - path_kill_slashes(u); - } - - if (controller) - *controller = t; - else - free(t); - - if (path) - *path = u; - else - free(u); - - return 0; -} - -int cg_mangle_path(const char *path, char **result) { - _cleanup_free_ char *c = NULL, *p = NULL; - char *t; - int r; - - assert(path); - assert(result); - - /* First, check if it already is a filesystem path */ - if (path_startswith(path, "/sys/fs/cgroup")) { - - t = strdup(path); - if (!t) - return -ENOMEM; - - *result = path_kill_slashes(t); - return 0; - } - - /* Otherwise, treat it as cg spec */ - r = cg_split_spec(path, &c, &p); - if (r < 0) - return r; - - return cg_get_path(c ?: SYSTEMD_CGROUP_CONTROLLER, p ?: "/", NULL, result); -} - -int cg_get_root_path(char **path) { - char *p, *e; - int r; - - assert(path); - - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &p); - if (r < 0) - return r; - - e = endswith(p, "/" SPECIAL_INIT_SCOPE); - if (!e) - e = endswith(p, "/" SPECIAL_SYSTEM_SLICE); /* legacy */ - if (!e) - e = endswith(p, "/system"); /* even more legacy */ - if (e) - *e = 0; - - *path = p; - return 0; -} - -int cg_shift_path(const char *cgroup, const char *root, const char **shifted) { - _cleanup_free_ char *rt = NULL; - char *p; - int r; - - assert(cgroup); - assert(shifted); - - if (!root) { - /* If the root was specified let's use that, otherwise - * let's determine it from PID 1 */ - - r = cg_get_root_path(&rt); - if (r < 0) - return r; - - root = rt; - } - - p = path_startswith(cgroup, root); - if (p && p > cgroup) - *shifted = p - 1; - else - *shifted = cgroup; - - return 0; -} - -int cg_pid_get_path_shifted(pid_t pid, const char *root, char **cgroup) { - _cleanup_free_ char *raw = NULL; - const char *c; - int r; - - assert(pid >= 0); - assert(cgroup); - - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &raw); - if (r < 0) - return r; - - r = cg_shift_path(raw, root, &c); - if (r < 0) - return r; - - if (c == raw) { - *cgroup = raw; - raw = NULL; - } else { - char *n; - - n = strdup(c); - if (!n) - return -ENOMEM; - - *cgroup = n; - } - - return 0; -} - -int cg_path_decode_unit(const char *cgroup, char **unit) { - char *c, *s; - size_t n; - - assert(cgroup); - assert(unit); - - n = strcspn(cgroup, "/"); - if (n < 3) - return -ENXIO; - - c = strndupa(cgroup, n); - c = cg_unescape(c); - - if (!unit_name_is_valid(c, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) - return -ENXIO; - - s = strdup(c); - if (!s) - return -ENOMEM; - - *unit = s; - return 0; -} - -static bool valid_slice_name(const char *p, size_t n) { - - if (!p) - return false; - - if (n < strlen("x.slice")) - return false; - - if (memcmp(p + n - 6, ".slice", 6) == 0) { - char buf[n+1], *c; - - memcpy(buf, p, n); - buf[n] = 0; - - c = cg_unescape(buf); - - return unit_name_is_valid(c, UNIT_NAME_PLAIN); - } - - return false; -} - -static const char *skip_slices(const char *p) { - assert(p); - - /* Skips over all slice assignments */ - - for (;;) { - size_t n; - - p += strspn(p, "/"); - - n = strcspn(p, "/"); - if (!valid_slice_name(p, n)) - return p; - - p += n; - } -} - -int cg_path_get_unit(const char *path, char **ret) { - const char *e; - char *unit; - int r; - - assert(path); - assert(ret); - - e = skip_slices(path); - - r = cg_path_decode_unit(e, &unit); - if (r < 0) - return r; - - /* We skipped over the slices, don't accept any now */ - if (endswith(unit, ".slice")) { - free(unit); - return -ENXIO; - } - - *ret = unit; - return 0; -} - -int cg_pid_get_unit(pid_t pid, char **unit) { - _cleanup_free_ char *cgroup = NULL; - int r; - - assert(unit); - - r = cg_pid_get_path_shifted(pid, NULL, &cgroup); - if (r < 0) - return r; - - return cg_path_get_unit(cgroup, unit); -} - -/** - * Skip session-*.scope, but require it to be there. - */ -static const char *skip_session(const char *p) { - size_t n; - - if (isempty(p)) - return NULL; - - p += strspn(p, "/"); - - n = strcspn(p, "/"); - if (n < strlen("session-x.scope")) - return NULL; - - if (memcmp(p, "session-", 8) == 0 && memcmp(p + n - 6, ".scope", 6) == 0) { - char buf[n - 8 - 6 + 1]; - - memcpy(buf, p + 8, n - 8 - 6); - buf[n - 8 - 6] = 0; - - /* Note that session scopes never need unescaping, - * since they cannot conflict with the kernel's own - * names, hence we don't need to call cg_unescape() - * here. */ - - if (!session_id_valid(buf)) - return false; - - p += n; - p += strspn(p, "/"); - return p; - } - - return NULL; -} - -/** - * Skip user@*.service, but require it to be there. - */ -static const char *skip_user_manager(const char *p) { - size_t n; - - if (isempty(p)) - return NULL; - - p += strspn(p, "/"); - - n = strcspn(p, "/"); - if (n < strlen("user@x.service")) - return NULL; - - if (memcmp(p, "user@", 5) == 0 && memcmp(p + n - 8, ".service", 8) == 0) { - char buf[n - 5 - 8 + 1]; - - memcpy(buf, p + 5, n - 5 - 8); - buf[n - 5 - 8] = 0; - - /* Note that user manager services never need unescaping, - * since they cannot conflict with the kernel's own - * names, hence we don't need to call cg_unescape() - * here. */ - - if (parse_uid(buf, NULL) < 0) - return NULL; - - p += n; - p += strspn(p, "/"); - - return p; - } - - return NULL; -} - -static const char *skip_user_prefix(const char *path) { - const char *e, *t; - - assert(path); - - /* Skip slices, if there are any */ - e = skip_slices(path); - - /* Skip the user manager, if it's in the path now... */ - t = skip_user_manager(e); - if (t) - return t; - - /* Alternatively skip the user session if it is in the path... */ - return skip_session(e); -} - -int cg_path_get_user_unit(const char *path, char **ret) { - const char *t; - - assert(path); - assert(ret); - - t = skip_user_prefix(path); - if (!t) - return -ENXIO; - - /* And from here on it looks pretty much the same as for a - * system unit, hence let's use the same parser from here - * on. */ - return cg_path_get_unit(t, ret); -} - -int cg_pid_get_user_unit(pid_t pid, char **unit) { - _cleanup_free_ char *cgroup = NULL; - int r; - - assert(unit); - - r = cg_pid_get_path_shifted(pid, NULL, &cgroup); - if (r < 0) - return r; - - return cg_path_get_user_unit(cgroup, unit); -} - -int cg_path_get_machine_name(const char *path, char **machine) { - _cleanup_free_ char *u = NULL; - const char *sl; - int r; - - r = cg_path_get_unit(path, &u); - if (r < 0) - return r; - - sl = strjoina("/run/systemd/machines/unit:", u); - return readlink_malloc(sl, machine); -} - -int cg_pid_get_machine_name(pid_t pid, char **machine) { - _cleanup_free_ char *cgroup = NULL; - int r; - - assert(machine); - - r = cg_pid_get_path_shifted(pid, NULL, &cgroup); - if (r < 0) - return r; - - return cg_path_get_machine_name(cgroup, machine); -} - -int cg_path_get_session(const char *path, char **session) { - _cleanup_free_ char *unit = NULL; - char *start, *end; - int r; - - assert(path); - - r = cg_path_get_unit(path, &unit); - if (r < 0) - return r; - - start = startswith(unit, "session-"); - if (!start) - return -ENXIO; - end = endswith(start, ".scope"); - if (!end) - return -ENXIO; - - *end = 0; - if (!session_id_valid(start)) - return -ENXIO; - - if (session) { - char *rr; - - rr = strdup(start); - if (!rr) - return -ENOMEM; - - *session = rr; - } - - return 0; -} - -int cg_pid_get_session(pid_t pid, char **session) { - _cleanup_free_ char *cgroup = NULL; - int r; - - r = cg_pid_get_path_shifted(pid, NULL, &cgroup); - if (r < 0) - return r; - - return cg_path_get_session(cgroup, session); -} - -int cg_path_get_owner_uid(const char *path, uid_t *uid) { - _cleanup_free_ char *slice = NULL; - char *start, *end; - int r; - - assert(path); - - r = cg_path_get_slice(path, &slice); - if (r < 0) - return r; - - start = startswith(slice, "user-"); - if (!start) - return -ENXIO; - end = endswith(start, ".slice"); - if (!end) - return -ENXIO; - - *end = 0; - if (parse_uid(start, uid) < 0) - return -ENXIO; - - return 0; -} - -int cg_pid_get_owner_uid(pid_t pid, uid_t *uid) { - _cleanup_free_ char *cgroup = NULL; - int r; - - r = cg_pid_get_path_shifted(pid, NULL, &cgroup); - if (r < 0) - return r; - - return cg_path_get_owner_uid(cgroup, uid); -} - -int cg_path_get_slice(const char *p, char **slice) { - const char *e = NULL; - - assert(p); - assert(slice); - - /* Finds the right-most slice unit from the beginning, but - * stops before we come to the first non-slice unit. */ - - for (;;) { - size_t n; - - p += strspn(p, "/"); - - n = strcspn(p, "/"); - if (!valid_slice_name(p, n)) { - - if (!e) { - char *s; - - s = strdup("-.slice"); - if (!s) - return -ENOMEM; - - *slice = s; - return 0; - } - - return cg_path_decode_unit(e, slice); - } - - e = p; - p += n; - } -} - -int cg_pid_get_slice(pid_t pid, char **slice) { - _cleanup_free_ char *cgroup = NULL; - int r; - - assert(slice); - - r = cg_pid_get_path_shifted(pid, NULL, &cgroup); - if (r < 0) - return r; - - return cg_path_get_slice(cgroup, slice); -} - -int cg_path_get_user_slice(const char *p, char **slice) { - const char *t; - assert(p); - assert(slice); - - t = skip_user_prefix(p); - if (!t) - return -ENXIO; - - /* And now it looks pretty much the same as for a system - * slice, so let's just use the same parser from here on. */ - return cg_path_get_slice(t, slice); -} - -int cg_pid_get_user_slice(pid_t pid, char **slice) { - _cleanup_free_ char *cgroup = NULL; - int r; - - assert(slice); - - r = cg_pid_get_path_shifted(pid, NULL, &cgroup); - if (r < 0) - return r; - - return cg_path_get_user_slice(cgroup, slice); -} - -char *cg_escape(const char *p) { - bool need_prefix = false; - - /* This implements very minimal escaping for names to be used - * as file names in the cgroup tree: any name which might - * conflict with a kernel name or is prefixed with '_' is - * prefixed with a '_'. That way, when reading cgroup names it - * is sufficient to remove a single prefixing underscore if - * there is one. */ - - /* The return value of this function (unlike cg_unescape()) - * needs free()! */ - - if (p[0] == 0 || - p[0] == '_' || - p[0] == '.' || - streq(p, "notify_on_release") || - streq(p, "release_agent") || - streq(p, "tasks") || - startswith(p, "cgroup.")) - need_prefix = true; - else { - const char *dot; - - dot = strrchr(p, '.'); - if (dot) { - CGroupController c; - size_t l = dot - p; - - for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { - const char *n; - - n = cgroup_controller_to_string(c); - - if (l != strlen(n)) - continue; - - if (memcmp(p, n, l) != 0) - continue; - - need_prefix = true; - break; - } - } - } - - if (need_prefix) - return strappend("_", p); - - return strdup(p); -} - -char *cg_unescape(const char *p) { - assert(p); - - /* The return value of this function (unlike cg_escape()) - * doesn't need free()! */ - - if (p[0] == '_') - return (char*) p+1; - - return (char*) p; -} - -#define CONTROLLER_VALID \ - DIGITS LETTERS \ - "_" - -bool cg_controller_is_valid(const char *p) { - const char *t, *s; - - if (!p) - return false; - - s = startswith(p, "name="); - if (s) - p = s; - - if (*p == 0 || *p == '_') - return false; - - for (t = p; *t; t++) - if (!strchr(CONTROLLER_VALID, *t)) - return false; - - if (t - p > FILENAME_MAX) - return false; - - return true; -} - -int cg_slice_to_path(const char *unit, char **ret) { - _cleanup_free_ char *p = NULL, *s = NULL, *e = NULL; - const char *dash; - int r; - - assert(unit); - assert(ret); - - if (streq(unit, "-.slice")) { - char *x; - - x = strdup(""); - if (!x) - return -ENOMEM; - *ret = x; - return 0; - } - - if (!unit_name_is_valid(unit, UNIT_NAME_PLAIN)) - return -EINVAL; - - if (!endswith(unit, ".slice")) - return -EINVAL; - - r = unit_name_to_prefix(unit, &p); - if (r < 0) - return r; - - dash = strchr(p, '-'); - - /* Don't allow initial dashes */ - if (dash == p) - return -EINVAL; - - while (dash) { - _cleanup_free_ char *escaped = NULL; - char n[dash - p + sizeof(".slice")]; - - /* Don't allow trailing or double dashes */ - if (dash[1] == 0 || dash[1] == '-') - return -EINVAL; - - strcpy(stpncpy(n, p, dash - p), ".slice"); - if (!unit_name_is_valid(n, UNIT_NAME_PLAIN)) - return -EINVAL; - - escaped = cg_escape(n); - if (!escaped) - return -ENOMEM; - - if (!strextend(&s, escaped, "/", NULL)) - return -ENOMEM; - - dash = strchr(dash+1, '-'); - } - - e = cg_escape(unit); - if (!e) - return -ENOMEM; - - if (!strextend(&s, e, NULL)) - return -ENOMEM; - - *ret = s; - s = NULL; - - return 0; -} - -int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value) { - _cleanup_free_ char *p = NULL; - int r; - - r = cg_get_path(controller, path, attribute, &p); - if (r < 0) - return r; - - return write_string_file(p, value, 0); -} - -int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret) { - _cleanup_free_ char *p = NULL; - int r; - - r = cg_get_path(controller, path, attribute, &p); - if (r < 0) - return r; - - return read_one_line_file(p, ret); -} - -int cg_create_everywhere(CGroupMask supported, CGroupMask mask, const char *path) { - CGroupController c; - int r, unified; - - /* This one will create a cgroup in our private tree, but also - * duplicate it in the trees specified in mask, and remove it - * in all others */ - - /* First create the cgroup in our own hierarchy. */ - r = cg_create(SYSTEMD_CGROUP_CONTROLLER, path); - if (r < 0) - return r; - - /* If we are in the unified hierarchy, we are done now */ - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified > 0) - return 0; - - /* Otherwise, do the same in the other hierarchies */ - for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { - CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); - const char *n; - - n = cgroup_controller_to_string(c); - - if (mask & bit) - (void) cg_create(n, path); - else if (supported & bit) - (void) cg_trim(n, path, true); - } - - return 0; -} - -int cg_attach_everywhere(CGroupMask supported, const char *path, pid_t pid, cg_migrate_callback_t path_callback, void *userdata) { - CGroupController c; - int r, unified; - - r = cg_attach(SYSTEMD_CGROUP_CONTROLLER, path, pid); - if (r < 0) - return r; - - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified > 0) - return 0; - - for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { - CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); - const char *p = NULL; - - if (!(supported & bit)) - continue; - - if (path_callback) - p = path_callback(bit, userdata); - - if (!p) - p = path; - - (void) cg_attach_fallback(cgroup_controller_to_string(c), p, pid); - } - - return 0; -} - -int cg_attach_many_everywhere(CGroupMask supported, const char *path, Set* pids, cg_migrate_callback_t path_callback, void *userdata) { - Iterator i; - void *pidp; - int r = 0; - - SET_FOREACH(pidp, pids, i) { - pid_t pid = PTR_TO_PID(pidp); - int q; - - q = cg_attach_everywhere(supported, path, pid, path_callback, userdata); - if (q < 0 && r >= 0) - r = q; - } - - return r; -} - -int cg_migrate_everywhere(CGroupMask supported, const char *from, const char *to, cg_migrate_callback_t to_callback, void *userdata) { - CGroupController c; - int r = 0, unified; - - if (!path_equal(from, to)) { - r = cg_migrate_recursive(SYSTEMD_CGROUP_CONTROLLER, from, SYSTEMD_CGROUP_CONTROLLER, to, false, true); - if (r < 0) - return r; - } - - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified > 0) - return r; - - for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { - CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); - const char *p = NULL; - - if (!(supported & bit)) - continue; - - if (to_callback) - p = to_callback(bit, userdata); - - if (!p) - p = to; - - (void) cg_migrate_recursive_fallback(SYSTEMD_CGROUP_CONTROLLER, to, cgroup_controller_to_string(c), p, false, false); - } - - return 0; -} - -int cg_trim_everywhere(CGroupMask supported, const char *path, bool delete_root) { - CGroupController c; - int r, unified; - - r = cg_trim(SYSTEMD_CGROUP_CONTROLLER, path, delete_root); - if (r < 0) - return r; - - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified > 0) - return r; - - for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { - CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); - - if (!(supported & bit)) - continue; - - (void) cg_trim(cgroup_controller_to_string(c), path, delete_root); - } - - return 0; -} - -int cg_mask_supported(CGroupMask *ret) { - CGroupMask mask = 0; - int r, unified; - - /* Determines the mask of supported cgroup controllers. Only - * includes controllers we can make sense of and that are - * actually accessible. */ - - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified > 0) { - _cleanup_free_ char *root = NULL, *controllers = NULL, *path = NULL; - const char *c; - - /* In the unified hierarchy we can read the supported - * and accessible controllers from a the top-level - * cgroup attribute */ - - r = cg_get_root_path(&root); - if (r < 0) - return r; - - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, root, "cgroup.controllers", &path); - if (r < 0) - return r; - - r = read_one_line_file(path, &controllers); - if (r < 0) - return r; - - c = controllers; - for (;;) { - _cleanup_free_ char *n = NULL; - CGroupController v; - - r = extract_first_word(&c, &n, NULL, 0); - if (r < 0) - return r; - if (r == 0) - break; - - v = cgroup_controller_from_string(n); - if (v < 0) - continue; - - mask |= CGROUP_CONTROLLER_TO_MASK(v); - } - - /* Currently, we only support the memory, io and pids - * controller in the unified hierarchy, mask - * everything else off. */ - mask &= CGROUP_MASK_MEMORY | CGROUP_MASK_IO | CGROUP_MASK_PIDS; - - } else { - CGroupController c; - - /* In the legacy hierarchy, we check whether which - * hierarchies are mounted. */ - - for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { - const char *n; - - n = cgroup_controller_to_string(c); - if (controller_is_accessible(n) >= 0) - mask |= CGROUP_CONTROLLER_TO_MASK(c); - } - } - - *ret = mask; - return 0; -} - -int cg_kernel_controllers(Set *controllers) { - _cleanup_fclose_ FILE *f = NULL; - char buf[LINE_MAX]; - int r; - - assert(controllers); - - /* Determines the full list of kernel-known controllers. Might - * include controllers we don't actually support, arbitrary - * named hierarchies and controllers that aren't currently - * accessible (because not mounted). */ - - f = fopen("/proc/cgroups", "re"); - if (!f) { - if (errno == ENOENT) - return 0; - return -errno; - } - - /* Ignore the header line */ - (void) fgets(buf, sizeof(buf), f); - - for (;;) { - char *controller; - int enabled = 0; - - errno = 0; - if (fscanf(f, "%ms %*i %*i %i", &controller, &enabled) != 2) { - - if (feof(f)) - break; - - if (ferror(f) && errno > 0) - return -errno; - - return -EBADMSG; - } - - if (!enabled) { - free(controller); - continue; - } - - if (!cg_controller_is_valid(controller)) { - free(controller); - return -EBADMSG; - } - - r = set_consume(controllers, controller); - if (r < 0) - return r; - } - - return 0; -} - -static thread_local int unified_cache = -1; - -int cg_unified(void) { - struct statfs fs; - - /* Checks if we support the unified hierarchy. Returns an - * error when the cgroup hierarchies aren't mounted yet or we - * have any other trouble determining if the unified hierarchy - * is supported. */ - - if (unified_cache >= 0) - return unified_cache; - - if (statfs("/sys/fs/cgroup/", &fs) < 0) - return -errno; - - if (F_TYPE_EQUAL(fs.f_type, CGROUP2_SUPER_MAGIC)) - unified_cache = true; - else if (F_TYPE_EQUAL(fs.f_type, TMPFS_MAGIC)) - unified_cache = false; - else - return -ENOMEDIUM; - - return unified_cache; -} - -void cg_unified_flush(void) { - unified_cache = -1; -} - -int cg_enable_everywhere(CGroupMask supported, CGroupMask mask, const char *p) { - _cleanup_free_ char *fs = NULL; - CGroupController c; - int r, unified; - - assert(p); - - if (supported == 0) - return 0; - - unified = cg_unified(); - if (unified < 0) - return unified; - if (!unified) /* on the legacy hiearchy there's no joining of controllers defined */ - return 0; - - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, p, "cgroup.subtree_control", &fs); - if (r < 0) - return r; - - for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { - CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); - const char *n; - - if (!(supported & bit)) - continue; - - n = cgroup_controller_to_string(c); - { - char s[1 + strlen(n) + 1]; - - s[0] = mask & bit ? '+' : '-'; - strcpy(s + 1, n); - - r = write_string_file(fs, s, 0); - if (r < 0) - log_debug_errno(r, "Failed to enable controller %s for %s (%s): %m", n, p, fs); - } - } - - return 0; -} - -bool cg_is_unified_wanted(void) { - static thread_local int wanted = -1; - int r, unified; - - /* If the hierarchy is already mounted, then follow whatever - * was chosen for it. */ - unified = cg_unified(); - if (unified >= 0) - return unified; - - /* Otherwise, let's see what the kernel command line has to - * say. Since checking that is expensive, let's cache the - * result. */ - if (wanted >= 0) - return wanted; - - r = get_proc_cmdline_key("systemd.unified_cgroup_hierarchy", NULL); - if (r > 0) - return (wanted = true); - else { - _cleanup_free_ char *value = NULL; - - r = get_proc_cmdline_key("systemd.unified_cgroup_hierarchy=", &value); - if (r < 0) - return false; - if (r == 0) - return (wanted = false); - - return (wanted = parse_boolean(value) > 0); - } -} - -bool cg_is_legacy_wanted(void) { - return !cg_is_unified_wanted(); -} - -int cg_weight_parse(const char *s, uint64_t *ret) { - uint64_t u; - int r; - - if (isempty(s)) { - *ret = CGROUP_WEIGHT_INVALID; - return 0; - } - - r = safe_atou64(s, &u); - if (r < 0) - return r; - - if (u < CGROUP_WEIGHT_MIN || u > CGROUP_WEIGHT_MAX) - return -ERANGE; - - *ret = u; - return 0; -} - -const uint64_t cgroup_io_limit_defaults[_CGROUP_IO_LIMIT_TYPE_MAX] = { - [CGROUP_IO_RBPS_MAX] = CGROUP_LIMIT_MAX, - [CGROUP_IO_WBPS_MAX] = CGROUP_LIMIT_MAX, - [CGROUP_IO_RIOPS_MAX] = CGROUP_LIMIT_MAX, - [CGROUP_IO_WIOPS_MAX] = CGROUP_LIMIT_MAX, -}; - -static const char* const cgroup_io_limit_type_table[_CGROUP_IO_LIMIT_TYPE_MAX] = { - [CGROUP_IO_RBPS_MAX] = "IOReadBandwidthMax", - [CGROUP_IO_WBPS_MAX] = "IOWriteBandwidthMax", - [CGROUP_IO_RIOPS_MAX] = "IOReadIOPSMax", - [CGROUP_IO_WIOPS_MAX] = "IOWriteIOPSMax", -}; - -DEFINE_STRING_TABLE_LOOKUP(cgroup_io_limit_type, CGroupIOLimitType); - -int cg_cpu_shares_parse(const char *s, uint64_t *ret) { - uint64_t u; - int r; - - if (isempty(s)) { - *ret = CGROUP_CPU_SHARES_INVALID; - return 0; - } - - r = safe_atou64(s, &u); - if (r < 0) - return r; - - if (u < CGROUP_CPU_SHARES_MIN || u > CGROUP_CPU_SHARES_MAX) - return -ERANGE; - - *ret = u; - return 0; -} - -int cg_blkio_weight_parse(const char *s, uint64_t *ret) { - uint64_t u; - int r; - - if (isempty(s)) { - *ret = CGROUP_BLKIO_WEIGHT_INVALID; - return 0; - } - - r = safe_atou64(s, &u); - if (r < 0) - return r; - - if (u < CGROUP_BLKIO_WEIGHT_MIN || u > CGROUP_BLKIO_WEIGHT_MAX) - return -ERANGE; - - *ret = u; - return 0; -} - -static const char *cgroup_controller_table[_CGROUP_CONTROLLER_MAX] = { - [CGROUP_CONTROLLER_CPU] = "cpu", - [CGROUP_CONTROLLER_CPUACCT] = "cpuacct", - [CGROUP_CONTROLLER_IO] = "io", - [CGROUP_CONTROLLER_BLKIO] = "blkio", - [CGROUP_CONTROLLER_MEMORY] = "memory", - [CGROUP_CONTROLLER_DEVICES] = "devices", - [CGROUP_CONTROLLER_PIDS] = "pids", -}; - -DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController); diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h deleted file mode 100644 index 4bb5291296..0000000000 --- a/src/basic/cgroup-util.h +++ /dev/null @@ -1,228 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include - -#include "def.h" -#include "hashmap.h" -#include "macro.h" -#include "set.h" - -/* An enum of well known cgroup controllers */ -typedef enum CGroupController { - CGROUP_CONTROLLER_CPU, - CGROUP_CONTROLLER_CPUACCT, - CGROUP_CONTROLLER_IO, - CGROUP_CONTROLLER_BLKIO, - CGROUP_CONTROLLER_MEMORY, - CGROUP_CONTROLLER_DEVICES, - CGROUP_CONTROLLER_PIDS, - _CGROUP_CONTROLLER_MAX, - _CGROUP_CONTROLLER_INVALID = -1, -} CGroupController; - -#define CGROUP_CONTROLLER_TO_MASK(c) (1 << (c)) - -/* A bit mask of well known cgroup controllers */ -typedef enum CGroupMask { - CGROUP_MASK_CPU = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_CPU), - CGROUP_MASK_CPUACCT = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_CPUACCT), - CGROUP_MASK_IO = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_IO), - CGROUP_MASK_BLKIO = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BLKIO), - CGROUP_MASK_MEMORY = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_MEMORY), - CGROUP_MASK_DEVICES = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_DEVICES), - CGROUP_MASK_PIDS = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_PIDS), - _CGROUP_MASK_ALL = CGROUP_CONTROLLER_TO_MASK(_CGROUP_CONTROLLER_MAX) - 1 -} CGroupMask; - -/* Special values for all weight knobs on unified hierarchy */ -#define CGROUP_WEIGHT_INVALID ((uint64_t) -1) -#define CGROUP_WEIGHT_MIN UINT64_C(1) -#define CGROUP_WEIGHT_MAX UINT64_C(10000) -#define CGROUP_WEIGHT_DEFAULT UINT64_C(100) - -#define CGROUP_LIMIT_MIN UINT64_C(0) -#define CGROUP_LIMIT_MAX ((uint64_t) -1) - -static inline bool CGROUP_WEIGHT_IS_OK(uint64_t x) { - return - x == CGROUP_WEIGHT_INVALID || - (x >= CGROUP_WEIGHT_MIN && x <= CGROUP_WEIGHT_MAX); -} - -/* IO limits on unified hierarchy */ -typedef enum CGroupIOLimitType { - CGROUP_IO_RBPS_MAX, - CGROUP_IO_WBPS_MAX, - CGROUP_IO_RIOPS_MAX, - CGROUP_IO_WIOPS_MAX, - - _CGROUP_IO_LIMIT_TYPE_MAX, - _CGROUP_IO_LIMIT_TYPE_INVALID = -1 -} CGroupIOLimitType; - -extern const uint64_t cgroup_io_limit_defaults[_CGROUP_IO_LIMIT_TYPE_MAX]; - -const char* cgroup_io_limit_type_to_string(CGroupIOLimitType t) _const_; -CGroupIOLimitType cgroup_io_limit_type_from_string(const char *s) _pure_; - -/* Special values for the cpu.shares attribute */ -#define CGROUP_CPU_SHARES_INVALID ((uint64_t) -1) -#define CGROUP_CPU_SHARES_MIN UINT64_C(2) -#define CGROUP_CPU_SHARES_MAX UINT64_C(262144) -#define CGROUP_CPU_SHARES_DEFAULT UINT64_C(1024) - -static inline bool CGROUP_CPU_SHARES_IS_OK(uint64_t x) { - return - x == CGROUP_CPU_SHARES_INVALID || - (x >= CGROUP_CPU_SHARES_MIN && x <= CGROUP_CPU_SHARES_MAX); -} - -/* Special values for the blkio.weight attribute */ -#define CGROUP_BLKIO_WEIGHT_INVALID ((uint64_t) -1) -#define CGROUP_BLKIO_WEIGHT_MIN UINT64_C(10) -#define CGROUP_BLKIO_WEIGHT_MAX UINT64_C(1000) -#define CGROUP_BLKIO_WEIGHT_DEFAULT UINT64_C(500) - -static inline bool CGROUP_BLKIO_WEIGHT_IS_OK(uint64_t x) { - return - x == CGROUP_BLKIO_WEIGHT_INVALID || - (x >= CGROUP_BLKIO_WEIGHT_MIN && x <= CGROUP_BLKIO_WEIGHT_MAX); -} - -/* - * General rules: - * - * We accept named hierarchies in the syntax "foo" and "name=foo". - * - * We expect that named hierarchies do not conflict in name with a - * kernel hierarchy, modulo the "name=" prefix. - * - * We always generate "normalized" controller names, i.e. without the - * "name=" prefix. - * - * We require absolute cgroup paths. When returning, we will always - * generate paths with multiple adjacent / removed. - */ - -int cg_enumerate_processes(const char *controller, const char *path, FILE **_f); -int cg_read_pid(FILE *f, pid_t *_pid); -int cg_read_event(const char *controller, const char *path, const char *event, - char **val); - -int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d); -int cg_read_subgroup(DIR *d, char **fn); - -int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s); -int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool remove, Set *s); - -int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self); -int cg_migrate_recursive(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self, bool remove); -int cg_migrate_recursive_fallback(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self, bool rem); - -int cg_split_spec(const char *spec, char **controller, char **path); -int cg_mangle_path(const char *path, char **result); - -int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs); -int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs); - -int cg_pid_get_path(const char *controller, pid_t pid, char **path); - -int cg_trim(const char *controller, const char *path, bool delete_root); - -int cg_rmdir(const char *controller, const char *path); - -int cg_create(const char *controller, const char *path); -int cg_attach(const char *controller, const char *path, pid_t pid); -int cg_attach_fallback(const char *controller, const char *path, pid_t pid); -int cg_create_and_attach(const char *controller, const char *path, pid_t pid); - -int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value); -int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret); - -int cg_set_group_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid); -int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid); - -int cg_install_release_agent(const char *controller, const char *agent); -int cg_uninstall_release_agent(const char *controller); - -int cg_is_empty(const char *controller, const char *path); -int cg_is_empty_recursive(const char *controller, const char *path); - -int cg_get_root_path(char **path); - -int cg_path_get_session(const char *path, char **session); -int cg_path_get_owner_uid(const char *path, uid_t *uid); -int cg_path_get_unit(const char *path, char **unit); -int cg_path_get_user_unit(const char *path, char **unit); -int cg_path_get_machine_name(const char *path, char **machine); -int cg_path_get_slice(const char *path, char **slice); -int cg_path_get_user_slice(const char *path, char **slice); - -int cg_shift_path(const char *cgroup, const char *cached_root, const char **shifted); -int cg_pid_get_path_shifted(pid_t pid, const char *cached_root, char **cgroup); - -int cg_pid_get_session(pid_t pid, char **session); -int cg_pid_get_owner_uid(pid_t pid, uid_t *uid); -int cg_pid_get_unit(pid_t pid, char **unit); -int cg_pid_get_user_unit(pid_t pid, char **unit); -int cg_pid_get_machine_name(pid_t pid, char **machine); -int cg_pid_get_slice(pid_t pid, char **slice); -int cg_pid_get_user_slice(pid_t pid, char **slice); - -int cg_path_decode_unit(const char *cgroup, char **unit); - -char *cg_escape(const char *p); -char *cg_unescape(const char *p) _pure_; - -bool cg_controller_is_valid(const char *p); - -int cg_slice_to_path(const char *unit, char **ret); - -typedef const char* (*cg_migrate_callback_t)(CGroupMask mask, void *userdata); - -int cg_create_everywhere(CGroupMask supported, CGroupMask mask, const char *path); -int cg_attach_everywhere(CGroupMask supported, const char *path, pid_t pid, cg_migrate_callback_t callback, void *userdata); -int cg_attach_many_everywhere(CGroupMask supported, const char *path, Set* pids, cg_migrate_callback_t callback, void *userdata); -int cg_migrate_everywhere(CGroupMask supported, const char *from, const char *to, cg_migrate_callback_t callback, void *userdata); -int cg_trim_everywhere(CGroupMask supported, const char *path, bool delete_root); -int cg_enable_everywhere(CGroupMask supported, CGroupMask mask, const char *p); - -int cg_mask_supported(CGroupMask *ret); - -int cg_kernel_controllers(Set *controllers); - -int cg_unified(void); -void cg_unified_flush(void); - -bool cg_is_unified_wanted(void); -bool cg_is_legacy_wanted(void); - -const char* cgroup_controller_to_string(CGroupController c) _const_; -CGroupController cgroup_controller_from_string(const char *s) _pure_; - -int cg_weight_parse(const char *s, uint64_t *ret); -int cg_cpu_shares_parse(const char *s, uint64_t *ret); -int cg_blkio_weight_parse(const char *s, uint64_t *ret); diff --git a/src/basic/chattr-util.c b/src/basic/chattr-util.c deleted file mode 100644 index 2896a729af..0000000000 --- a/src/basic/chattr-util.c +++ /dev/null @@ -1,107 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include - -#include "chattr-util.h" -#include "fd-util.h" -#include "macro.h" - -int chattr_fd(int fd, unsigned value, unsigned mask) { - unsigned old_attr, new_attr; - struct stat st; - - assert(fd >= 0); - - if (fstat(fd, &st) < 0) - return -errno; - - /* Explicitly check whether this is a regular file or - * directory. If it is anything else (such as a device node or - * fifo), then the ioctl will not hit the file systems but - * possibly drivers, where the ioctl might have different - * effects. Notably, DRM is using the same ioctl() number. */ - - if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) - return -ENOTTY; - - if (mask == 0) - return 0; - - if (ioctl(fd, FS_IOC_GETFLAGS, &old_attr) < 0) - return -errno; - - new_attr = (old_attr & ~mask) | (value & mask); - if (new_attr == old_attr) - return 0; - - if (ioctl(fd, FS_IOC_SETFLAGS, &new_attr) < 0) - return -errno; - - return 1; -} - -int chattr_path(const char *p, unsigned value, unsigned mask) { - _cleanup_close_ int fd = -1; - - assert(p); - - if (mask == 0) - return 0; - - fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; - - return chattr_fd(fd, value, mask); -} - -int read_attr_fd(int fd, unsigned *ret) { - struct stat st; - - assert(fd >= 0); - - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) - return -ENOTTY; - - if (ioctl(fd, FS_IOC_GETFLAGS, ret) < 0) - return -errno; - - return 0; -} - -int read_attr_path(const char *p, unsigned *ret) { - _cleanup_close_ int fd = -1; - - assert(p); - assert(ret); - - fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; - - return read_attr_fd(fd, ret); -} diff --git a/src/basic/chattr-util.h b/src/basic/chattr-util.h deleted file mode 100644 index 960cf6d5b3..0000000000 --- a/src/basic/chattr-util.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -int chattr_fd(int fd, unsigned value, unsigned mask); -int chattr_path(const char *p, unsigned value, unsigned mask); - -int read_attr_fd(int fd, unsigned *ret); -int read_attr_path(const char *p, unsigned *ret); diff --git a/src/basic/clock-util.c b/src/basic/clock-util.c deleted file mode 100644 index 7fe8d35ea5..0000000000 --- a/src/basic/clock-util.c +++ /dev/null @@ -1,165 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010-2012 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 -#include -#include -#include -#include -#include -#include -#include - -#include "clock-util.h" -#include "fd-util.h" -#include "macro.h" -#include "string-util.h" -#include "util.h" - -int clock_get_hwclock(struct tm *tm) { - _cleanup_close_ int fd = -1; - - assert(tm); - - fd = open("/dev/rtc", O_RDONLY|O_CLOEXEC); - if (fd < 0) - return -errno; - - /* This leaves the timezone fields of struct tm - * uninitialized! */ - if (ioctl(fd, RTC_RD_TIME, tm) < 0) - return -errno; - - /* We don't know daylight saving, so we reset this in order not - * to confuse mktime(). */ - tm->tm_isdst = -1; - - return 0; -} - -int clock_set_hwclock(const struct tm *tm) { - _cleanup_close_ int fd = -1; - - assert(tm); - - fd = open("/dev/rtc", O_RDONLY|O_CLOEXEC); - if (fd < 0) - return -errno; - - if (ioctl(fd, RTC_SET_TIME, tm) < 0) - return -errno; - - return 0; -} - -int clock_is_localtime(const char* adjtime_path) { - _cleanup_fclose_ FILE *f; - - if (adjtime_path == NULL) - adjtime_path = "/etc/adjtime"; - - /* - * The third line of adjtime is "UTC" or "LOCAL" or nothing. - * # /etc/adjtime - * 0.0 0 0 - * 0 - * UTC - */ - f = fopen(adjtime_path, "re"); - if (f) { - char line[LINE_MAX]; - bool b; - - b = fgets(line, sizeof(line), f) && - fgets(line, sizeof(line), f) && - fgets(line, sizeof(line), f); - if (!b) - /* less than three lines -> default to UTC */ - return 0; - - truncate_nl(line); - return streq(line, "LOCAL"); - - } else if (errno != ENOENT) - return -errno; - - /* adjtime not present -> default to UTC */ - return 0; -} - -int clock_set_timezone(int *min) { - const struct timeval *tv_null = NULL; - struct timespec ts; - struct tm *tm; - int minutesdelta; - struct timezone tz; - - assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); - assert_se(tm = localtime(&ts.tv_sec)); - minutesdelta = tm->tm_gmtoff / 60; - - tz.tz_minuteswest = -minutesdelta; - tz.tz_dsttime = 0; /* DST_NONE */ - - /* - * If the RTC does not run in UTC but in local time, the very first - * call to settimeofday() will set the kernel's timezone and will warp the - * system clock, so that it runs in UTC instead of the local time we - * have read from the RTC. - */ - if (settimeofday(tv_null, &tz) < 0) - return negative_errno(); - - if (min) - *min = minutesdelta; - return 0; -} - -int clock_reset_timewarp(void) { - const struct timeval *tv_null = NULL; - struct timezone tz; - - tz.tz_minuteswest = 0; - tz.tz_dsttime = 0; /* DST_NONE */ - - /* - * The very first call to settimeofday() does time warp magic. Do a - * dummy call here, so the time warping is sealed and all later calls - * behave as expected. - */ - if (settimeofday(tv_null, &tz) < 0) - return -errno; - - return 0; -} - -#define TIME_EPOCH_USEC ((usec_t) TIME_EPOCH * USEC_PER_SEC) - -int clock_apply_epoch(void) { - struct timespec ts; - - if (now(CLOCK_REALTIME) >= TIME_EPOCH_USEC) - return 0; - - if (clock_settime(CLOCK_REALTIME, timespec_store(&ts, TIME_EPOCH_USEC)) < 0) - return -errno; - - return 1; -} diff --git a/src/basic/clock-util.h b/src/basic/clock-util.h deleted file mode 100644 index 8830cd2f38..0000000000 --- a/src/basic/clock-util.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010-2012 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 - -int clock_is_localtime(const char* adjtime_path); -int clock_set_timezone(int *min); -int clock_reset_timewarp(void); -int clock_get_hwclock(struct tm *tm); -int clock_set_hwclock(const struct tm *tm); -int clock_apply_epoch(void); diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c deleted file mode 100644 index c781610e14..0000000000 --- a/src/basic/conf-files.c +++ /dev/null @@ -1,166 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include - -#include "conf-files.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "hashmap.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "path-util.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -static int files_add(Hashmap *h, const char *root, const char *path, const char *suffix) { - _cleanup_closedir_ DIR *dir = NULL; - const char *dirpath; - struct dirent *de; - int r; - - assert(path); - assert(suffix); - - dirpath = prefix_roota(root, path); - - dir = opendir(dirpath); - if (!dir) { - if (errno == ENOENT) - return 0; - return -errno; - } - - FOREACH_DIRENT(de, dir, return -errno) { - char *p; - - if (!dirent_is_file_with_suffix(de, suffix)) - continue; - - p = strjoin(dirpath, "/", de->d_name, NULL); - if (!p) - return -ENOMEM; - - r = hashmap_put(h, basename(p), p); - if (r == -EEXIST) { - log_debug("Skipping overridden file: %s.", p); - free(p); - } else if (r < 0) { - free(p); - return r; - } else if (r == 0) { - log_debug("Duplicate file %s", p); - free(p); - } - } - - return 0; -} - -static int base_cmp(const void *a, const void *b) { - const char *s1, *s2; - - s1 = *(char * const *)a; - s2 = *(char * const *)b; - return strcmp(basename(s1), basename(s2)); -} - -static int conf_files_list_strv_internal(char ***strv, const char *suffix, const char *root, char **dirs) { - _cleanup_hashmap_free_ Hashmap *fh = NULL; - char **files, **p; - int r; - - assert(strv); - assert(suffix); - - /* This alters the dirs string array */ - if (!path_strv_resolve_uniq(dirs, root)) - return -ENOMEM; - - fh = hashmap_new(&string_hash_ops); - if (!fh) - return -ENOMEM; - - STRV_FOREACH(p, dirs) { - r = files_add(fh, root, *p, suffix); - if (r == -ENOMEM) - return r; - if (r < 0) - log_debug_errno(r, "Failed to search for files in %s, ignoring: %m", *p); - } - - files = hashmap_get_strv(fh); - if (!files) - return -ENOMEM; - - qsort_safe(files, hashmap_size(fh), sizeof(char *), base_cmp); - *strv = files; - - return 0; -} - -int conf_files_list_strv(char ***strv, const char *suffix, const char *root, const char* const* dirs) { - _cleanup_strv_free_ char **copy = NULL; - - assert(strv); - assert(suffix); - - copy = strv_copy((char**) dirs); - if (!copy) - return -ENOMEM; - - return conf_files_list_strv_internal(strv, suffix, root, copy); -} - -int conf_files_list(char ***strv, const char *suffix, const char *root, const char *dir, ...) { - _cleanup_strv_free_ char **dirs = NULL; - va_list ap; - - assert(strv); - assert(suffix); - - va_start(ap, dir); - dirs = strv_new_ap(dir, ap); - va_end(ap); - - if (!dirs) - return -ENOMEM; - - return conf_files_list_strv_internal(strv, suffix, root, dirs); -} - -int conf_files_list_nulstr(char ***strv, const char *suffix, const char *root, const char *d) { - _cleanup_strv_free_ char **dirs = NULL; - - assert(strv); - assert(suffix); - - dirs = strv_split_nulstr(d); - if (!dirs) - return -ENOMEM; - - return conf_files_list_strv_internal(strv, suffix, root, dirs); -} diff --git a/src/basic/conf-files.h b/src/basic/conf-files.h deleted file mode 100644 index e00e0e81fb..0000000000 --- a/src/basic/conf-files.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010-2012 Lennart Poettering - Copyright 2010-2012 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 . -***/ - -int conf_files_list(char ***ret, const char *suffix, const char *root, const char *dir, ...); -int conf_files_list_strv(char ***ret, const char *suffix, const char *root, const char* const* dirs); -int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, const char *dirs); diff --git a/src/basic/copy.c b/src/basic/copy.c deleted file mode 100644 index c3586728d0..0000000000 --- a/src/basic/copy.c +++ /dev/null @@ -1,603 +0,0 @@ -/*** - 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "btrfs-util.h" -#include "chattr-util.h" -#include "copy.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "io-util.h" -#include "macro.h" -#include "missing.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" -#include "umask-util.h" -#include "xattr-util.h" - -#define COPY_BUFFER_SIZE (16*1024u) - -static ssize_t try_copy_file_range(int fd_in, loff_t *off_in, - int fd_out, loff_t *off_out, - size_t len, - unsigned int flags) { - static int have = -1; - ssize_t r; - - if (have == false) - return -ENOSYS; - - r = copy_file_range(fd_in, off_in, fd_out, off_out, len, flags); - if (_unlikely_(have < 0)) - have = r >= 0 || errno != ENOSYS; - if (r >= 0) - return r; - else - return -errno; -} - -int copy_bytes(int fdf, int fdt, uint64_t max_bytes, bool try_reflink) { - bool try_cfr = true, try_sendfile = true, try_splice = true; - int r; - size_t m = SSIZE_MAX; /* that is the maximum that sendfile and c_f_r accept */ - - assert(fdf >= 0); - assert(fdt >= 0); - - /* Try btrfs reflinks first. */ - if (try_reflink && - max_bytes == (uint64_t) -1 && - lseek(fdf, 0, SEEK_CUR) == 0 && - lseek(fdt, 0, SEEK_CUR) == 0) { - - r = btrfs_reflink(fdf, fdt); - if (r >= 0) - return 0; /* we copied the whole thing, hence hit EOF, return 0 */ - } - - for (;;) { - ssize_t n; - - if (max_bytes != (uint64_t) -1) { - if (max_bytes <= 0) - return 1; /* return > 0 if we hit the max_bytes limit */ - - if (m > max_bytes) - m = max_bytes; - } - - /* First try copy_file_range(), unless we already tried */ - if (try_cfr) { - n = try_copy_file_range(fdf, NULL, fdt, NULL, m, 0u); - if (n < 0) { - if (!IN_SET(n, -EINVAL, -ENOSYS, -EXDEV, -EBADF)) - return n; - - try_cfr = false; - /* use fallback below */ - } else if (n == 0) /* EOF */ - break; - else - /* Success! */ - goto next; - } - - /* First try sendfile(), unless we already tried */ - if (try_sendfile) { - n = sendfile(fdt, fdf, NULL, m); - if (n < 0) { - if (!IN_SET(errno, EINVAL, ENOSYS)) - return -errno; - - try_sendfile = false; - /* use fallback below */ - } else if (n == 0) /* EOF */ - break; - else - /* Success! */ - goto next; - } - - /* Then try splice, unless we already tried */ - if (try_splice) { - n = splice(fdf, NULL, fdt, NULL, m, 0); - if (n < 0) { - if (!IN_SET(errno, EINVAL, ENOSYS)) - return -errno; - - try_splice = false; - /* use fallback below */ - } else if (n == 0) /* EOF */ - break; - else - /* Success! */ - goto next; - } - - /* As a fallback just copy bits by hand */ - { - uint8_t buf[MIN(m, COPY_BUFFER_SIZE)]; - - n = read(fdf, buf, sizeof buf); - if (n < 0) - return -errno; - if (n == 0) /* EOF */ - break; - - r = loop_write(fdt, buf, (size_t) n, false); - if (r < 0) - return r; - } - - next: - if (max_bytes != (uint64_t) -1) { - assert(max_bytes >= (uint64_t) n); - max_bytes -= n; - } - /* sendfile accepts at most SSIZE_MAX-offset bytes to copy, - * so reduce our maximum by the amount we already copied, - * but don't go below our copy buffer size, unless we are - * close the the limit of bytes we are allowed to copy. */ - m = MAX(MIN(COPY_BUFFER_SIZE, max_bytes), m - n); - } - - return 0; /* return 0 if we hit EOF earlier than the size limit */ -} - -static int fd_copy_symlink(int df, const char *from, const struct stat *st, int dt, const char *to) { - _cleanup_free_ char *target = NULL; - int r; - - assert(from); - assert(st); - assert(to); - - r = readlinkat_malloc(df, from, &target); - if (r < 0) - return r; - - if (symlinkat(target, dt, to) < 0) - return -errno; - - if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0) - return -errno; - - return 0; -} - -static int fd_copy_regular(int df, const char *from, const struct stat *st, int dt, const char *to) { - _cleanup_close_ int fdf = -1, fdt = -1; - struct timespec ts[2]; - int r, q; - - assert(from); - assert(st); - assert(to); - - fdf = openat(df, from, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fdf < 0) - return -errno; - - fdt = openat(dt, to, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, st->st_mode & 07777); - if (fdt < 0) - return -errno; - - r = copy_bytes(fdf, fdt, (uint64_t) -1, true); - if (r < 0) { - unlinkat(dt, to, 0); - return r; - } - - if (fchown(fdt, st->st_uid, st->st_gid) < 0) - r = -errno; - - if (fchmod(fdt, st->st_mode & 07777) < 0) - r = -errno; - - ts[0] = st->st_atim; - ts[1] = st->st_mtim; - (void) futimens(fdt, ts); - - (void) copy_xattr(fdf, fdt); - - q = close(fdt); - fdt = -1; - - if (q < 0) { - r = -errno; - unlinkat(dt, to, 0); - } - - return r; -} - -static int fd_copy_fifo(int df, const char *from, const struct stat *st, int dt, const char *to) { - int r; - - assert(from); - assert(st); - assert(to); - - r = mkfifoat(dt, to, st->st_mode & 07777); - if (r < 0) - return -errno; - - if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0) - r = -errno; - - if (fchmodat(dt, to, st->st_mode & 07777, 0) < 0) - r = -errno; - - return r; -} - -static int fd_copy_node(int df, const char *from, const struct stat *st, int dt, const char *to) { - int r; - - assert(from); - assert(st); - assert(to); - - r = mknodat(dt, to, st->st_mode, st->st_rdev); - if (r < 0) - return -errno; - - if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0) - r = -errno; - - if (fchmodat(dt, to, st->st_mode & 07777, 0) < 0) - r = -errno; - - return r; -} - -static int fd_copy_directory( - int df, - const char *from, - const struct stat *st, - int dt, - const char *to, - dev_t original_device, - bool merge) { - - _cleanup_close_ int fdf = -1, fdt = -1; - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - bool created; - int r; - - assert(st); - assert(to); - - if (from) - fdf = openat(df, from, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - else - fdf = fcntl(df, F_DUPFD_CLOEXEC, 3); - if (fdf < 0) - return -errno; - - d = fdopendir(fdf); - if (!d) - return -errno; - fdf = -1; - - r = mkdirat(dt, to, st->st_mode & 07777); - if (r >= 0) - created = true; - else if (errno == EEXIST && merge) - created = false; - else - return -errno; - - fdt = openat(dt, to, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fdt < 0) - return -errno; - - r = 0; - - FOREACH_DIRENT_ALL(de, d, return -errno) { - struct stat buf; - int q; - - if (STR_IN_SET(de->d_name, ".", "..")) - continue; - - if (fstatat(dirfd(d), de->d_name, &buf, AT_SYMLINK_NOFOLLOW) < 0) { - r = -errno; - continue; - } - - if (buf.st_dev != original_device) - continue; - - if (S_ISREG(buf.st_mode)) - q = fd_copy_regular(dirfd(d), de->d_name, &buf, fdt, de->d_name); - else if (S_ISDIR(buf.st_mode)) - q = fd_copy_directory(dirfd(d), de->d_name, &buf, fdt, de->d_name, original_device, merge); - else if (S_ISLNK(buf.st_mode)) - q = fd_copy_symlink(dirfd(d), de->d_name, &buf, fdt, de->d_name); - else if (S_ISFIFO(buf.st_mode)) - q = fd_copy_fifo(dirfd(d), de->d_name, &buf, fdt, de->d_name); - else if (S_ISBLK(buf.st_mode) || S_ISCHR(buf.st_mode) || S_ISSOCK(buf.st_mode)) - q = fd_copy_node(dirfd(d), de->d_name, &buf, fdt, de->d_name); - else - q = -EOPNOTSUPP; - - if (q == -EEXIST && merge) - q = 0; - - if (q < 0) - r = q; - } - - if (created) { - struct timespec ut[2] = { - st->st_atim, - st->st_mtim - }; - - if (fchown(fdt, st->st_uid, st->st_gid) < 0) - r = -errno; - - if (fchmod(fdt, st->st_mode & 07777) < 0) - r = -errno; - - (void) copy_xattr(dirfd(d), fdt); - (void) futimens(fdt, ut); - } - - return r; -} - -int copy_tree_at(int fdf, const char *from, int fdt, const char *to, bool merge) { - struct stat st; - - assert(from); - assert(to); - - if (fstatat(fdf, from, &st, AT_SYMLINK_NOFOLLOW) < 0) - return -errno; - - if (S_ISREG(st.st_mode)) - return fd_copy_regular(fdf, from, &st, fdt, to); - else if (S_ISDIR(st.st_mode)) - return fd_copy_directory(fdf, from, &st, fdt, to, st.st_dev, merge); - else if (S_ISLNK(st.st_mode)) - return fd_copy_symlink(fdf, from, &st, fdt, to); - else if (S_ISFIFO(st.st_mode)) - return fd_copy_fifo(fdf, from, &st, fdt, to); - else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode) || S_ISSOCK(st.st_mode)) - return fd_copy_node(fdf, from, &st, fdt, to); - else - return -EOPNOTSUPP; -} - -int copy_tree(const char *from, const char *to, bool merge) { - return copy_tree_at(AT_FDCWD, from, AT_FDCWD, to, merge); -} - -int copy_directory_fd(int dirfd, const char *to, bool merge) { - struct stat st; - - assert(dirfd >= 0); - assert(to); - - if (fstat(dirfd, &st) < 0) - return -errno; - - if (!S_ISDIR(st.st_mode)) - return -ENOTDIR; - - return fd_copy_directory(dirfd, NULL, &st, AT_FDCWD, to, st.st_dev, merge); -} - -int copy_directory(const char *from, const char *to, bool merge) { - struct stat st; - - assert(from); - assert(to); - - if (lstat(from, &st) < 0) - return -errno; - - if (!S_ISDIR(st.st_mode)) - return -ENOTDIR; - - return fd_copy_directory(AT_FDCWD, from, &st, AT_FDCWD, to, st.st_dev, merge); -} - -int copy_file_fd(const char *from, int fdt, bool try_reflink) { - _cleanup_close_ int fdf = -1; - int r; - - assert(from); - assert(fdt >= 0); - - fdf = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fdf < 0) - return -errno; - - r = copy_bytes(fdf, fdt, (uint64_t) -1, try_reflink); - - (void) copy_times(fdf, fdt); - (void) copy_xattr(fdf, fdt); - - return r; -} - -int copy_file(const char *from, const char *to, int flags, mode_t mode, unsigned chattr_flags) { - int fdt = -1, r; - - assert(from); - assert(to); - - RUN_WITH_UMASK(0000) { - fdt = open(to, flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, mode); - if (fdt < 0) - return -errno; - } - - if (chattr_flags != 0) - (void) chattr_fd(fdt, chattr_flags, (unsigned) -1); - - r = copy_file_fd(from, fdt, true); - if (r < 0) { - close(fdt); - unlink(to); - return r; - } - - if (close(fdt) < 0) { - unlink_noerrno(to); - return -errno; - } - - return 0; -} - -int copy_file_atomic(const char *from, const char *to, mode_t mode, bool replace, unsigned chattr_flags) { - _cleanup_free_ char *t = NULL; - int r; - - assert(from); - assert(to); - - r = tempfn_random(to, NULL, &t); - if (r < 0) - return r; - - r = copy_file(from, t, O_NOFOLLOW|O_EXCL, mode, chattr_flags); - if (r < 0) - return r; - - if (replace) { - r = renameat(AT_FDCWD, t, AT_FDCWD, to); - if (r < 0) - r = -errno; - } else - r = rename_noreplace(AT_FDCWD, t, AT_FDCWD, to); - if (r < 0) { - (void) unlink_noerrno(t); - return r; - } - - return 0; -} - -int copy_times(int fdf, int fdt) { - struct timespec ut[2]; - struct stat st; - usec_t crtime = 0; - - assert(fdf >= 0); - assert(fdt >= 0); - - if (fstat(fdf, &st) < 0) - return -errno; - - ut[0] = st.st_atim; - ut[1] = st.st_mtim; - - if (futimens(fdt, ut) < 0) - return -errno; - - if (fd_getcrtime(fdf, &crtime) >= 0) - (void) fd_setcrtime(fdt, crtime); - - return 0; -} - -int copy_xattr(int fdf, int fdt) { - _cleanup_free_ char *bufa = NULL, *bufb = NULL; - size_t sza = 100, szb = 100; - ssize_t n; - int ret = 0; - const char *p; - - for (;;) { - bufa = malloc(sza); - if (!bufa) - return -ENOMEM; - - n = flistxattr(fdf, bufa, sza); - if (n == 0) - return 0; - if (n > 0) - break; - if (errno != ERANGE) - return -errno; - - sza *= 2; - - bufa = mfree(bufa); - } - - p = bufa; - while (n > 0) { - size_t l; - - l = strlen(p); - assert(l < (size_t) n); - - if (startswith(p, "user.")) { - ssize_t m; - - if (!bufb) { - bufb = malloc(szb); - if (!bufb) - return -ENOMEM; - } - - m = fgetxattr(fdf, p, bufb, szb); - if (m < 0) { - if (errno == ERANGE) { - szb *= 2; - bufb = mfree(bufb); - continue; - } - - return -errno; - } - - if (fsetxattr(fdt, p, bufb, m, 0) < 0) - ret = -errno; - } - - p += l + 1; - n -= l + 1; - } - - return ret; -} diff --git a/src/basic/copy.h b/src/basic/copy.h deleted file mode 100644 index b5d08ebafe..0000000000 --- a/src/basic/copy.h +++ /dev/null @@ -1,36 +0,0 @@ -#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 -#include -#include -#include - -int copy_file_fd(const char *from, int to, bool try_reflink); -int copy_file(const char *from, const char *to, int flags, mode_t mode, unsigned chattr_flags); -int copy_file_atomic(const char *from, const char *to, mode_t mode, bool replace, unsigned chattr_flags); -int copy_tree(const char *from, const char *to, bool merge); -int copy_tree_at(int fdf, const char *from, int fdt, const char *to, bool merge); -int copy_directory_fd(int dirfd, const char *to, bool merge); -int copy_directory(const char *from, const char *to, bool merge); -int copy_bytes(int fdf, int fdt, uint64_t max_bytes, bool try_reflink); -int copy_times(int fdf, int fdt); -int copy_xattr(int fdf, int fdt); diff --git a/src/basic/cpu-set-util.c b/src/basic/cpu-set-util.c deleted file mode 100644 index 95ed6928ff..0000000000 --- a/src/basic/cpu-set-util.c +++ /dev/null @@ -1,114 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010-2015 Lennart Poettering - Copyright 2015 Filipe Brandenburger - - 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 -#include - -#include "alloc-util.h" -#include "cpu-set-util.h" -#include "extract-word.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "string-util.h" - -cpu_set_t* cpu_set_malloc(unsigned *ncpus) { - cpu_set_t *c; - unsigned n = 1024; - - /* Allocates the cpuset in the right size */ - - for (;;) { - c = CPU_ALLOC(n); - if (!c) - return NULL; - - if (sched_getaffinity(0, CPU_ALLOC_SIZE(n), c) >= 0) { - CPU_ZERO_S(CPU_ALLOC_SIZE(n), c); - - if (ncpus) - *ncpus = n; - - return c; - } - - CPU_FREE(c); - - if (errno != EINVAL) - return NULL; - - n *= 2; - } -} - -int parse_cpu_set_and_warn( - const char *rvalue, - cpu_set_t **cpu_set, - const char *unit, - const char *filename, - unsigned line, - const char *lvalue) { - - const char *whole_rvalue = rvalue; - _cleanup_cpu_free_ cpu_set_t *c = NULL; - unsigned ncpus = 0; - - assert(lvalue); - assert(rvalue); - - for (;;) { - _cleanup_free_ char *word = NULL; - unsigned cpu, cpu_lower, cpu_upper; - int r; - - r = extract_first_word(&rvalue, &word, WHITESPACE ",", EXTRACT_QUOTES); - if (r < 0) - return log_syntax(unit, LOG_ERR, filename, line, r, "Invalid value for %s: %s", lvalue, whole_rvalue); - if (r == 0) - break; - - if (!c) { - c = cpu_set_malloc(&ncpus); - if (!c) - return log_oom(); - } - - r = parse_range(word, &cpu_lower, &cpu_upper); - if (r < 0) - return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CPU affinity '%s'", word); - if (cpu_lower >= ncpus || cpu_upper >= ncpus) - return log_syntax(unit, LOG_ERR, filename, line, EINVAL, "CPU out of range '%s' ncpus is %u", word, ncpus); - - if (cpu_lower > cpu_upper) - log_syntax(unit, LOG_WARNING, filename, line, 0, "Range '%s' is invalid, %u > %u", word, cpu_lower, cpu_upper); - else - for (cpu = cpu_lower; cpu <= cpu_upper; cpu++) - CPU_SET_S(cpu, CPU_ALLOC_SIZE(ncpus), c); - } - - /* On success, sets *cpu_set and returns ncpus for the system. */ - if (c) { - *cpu_set = c; - c = NULL; - } - - return (int) ncpus; -} diff --git a/src/basic/cpu-set-util.h b/src/basic/cpu-set-util.h deleted file mode 100644 index 6f49d9afb0..0000000000 --- a/src/basic/cpu-set-util.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010-2015 Lennart Poettering - Copyright 2015 Filipe Brandenburger - - 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 "macro.h" - -DEFINE_TRIVIAL_CLEANUP_FUNC(cpu_set_t*, CPU_FREE); -#define _cleanup_cpu_free_ _cleanup_(CPU_FREEp) - -cpu_set_t* cpu_set_malloc(unsigned *ncpus); - -int parse_cpu_set_and_warn(const char *rvalue, cpu_set_t **cpu_set, const char *unit, const char *filename, unsigned line, const char *lvalue); diff --git a/src/basic/def.h b/src/basic/def.h deleted file mode 100644 index 1a7a0f4928..0000000000 --- a/src/basic/def.h +++ /dev/null @@ -1,90 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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" - -#define DEFAULT_TIMEOUT_USEC (90*USEC_PER_SEC) -#define DEFAULT_RESTART_USEC (100*USEC_PER_MSEC) -#define DEFAULT_CONFIRM_USEC (30*USEC_PER_SEC) - -#define DEFAULT_START_LIMIT_INTERVAL (10*USEC_PER_SEC) -#define DEFAULT_START_LIMIT_BURST 5 - -/* The default time after which exit-on-idle services exit. This - * should be kept lower than the watchdog timeout, because otherwise - * the watchdog pings will keep the loop busy. */ -#define DEFAULT_EXIT_USEC (30*USEC_PER_SEC) - -/* The default value for the net.unix.max_dgram_qlen sysctl */ -#define DEFAULT_UNIX_MAX_DGRAM_QLEN 512UL - -#define SYSTEMD_CGROUP_CONTROLLER "name=systemd" - -#define SIGNALS_CRASH_HANDLER SIGSEGV,SIGILL,SIGFPE,SIGBUS,SIGQUIT,SIGABRT -#define SIGNALS_IGNORE SIGPIPE - -#ifdef HAVE_SPLIT_USR -#define KBD_KEYMAP_DIRS \ - "/usr/share/keymaps/\0" \ - "/usr/share/kbd/keymaps/\0" \ - "/usr/lib/kbd/keymaps/\0" \ - "/lib/kbd/keymaps/\0" -#else -#define KBD_KEYMAP_DIRS \ - "/usr/share/keymaps/\0" \ - "/usr/share/kbd/keymaps/\0" \ - "/usr/lib/kbd/keymaps/\0" -#endif - -#define UNIX_SYSTEM_BUS_ADDRESS "unix:path=/var/run/dbus/system_bus_socket" -#define KERNEL_SYSTEM_BUS_ADDRESS "kernel:path=/sys/fs/kdbus/0-system/bus" -#define DEFAULT_SYSTEM_BUS_ADDRESS KERNEL_SYSTEM_BUS_ADDRESS ";" UNIX_SYSTEM_BUS_ADDRESS -#define UNIX_USER_BUS_ADDRESS_FMT "unix:path=%s/bus" -#define KERNEL_USER_BUS_ADDRESS_FMT "kernel:path=/sys/fs/kdbus/"UID_FMT"-user/bus" - -#define PLYMOUTH_SOCKET { \ - .un.sun_family = AF_UNIX, \ - .un.sun_path = "\0/org/freedesktop/plymouthd", \ - } - -#ifndef TTY_GID -#define TTY_GID 5 -#endif - -#define NOTIFY_FD_MAX 768 -#define NOTIFY_BUFFER_MAX PIPE_BUF - -#ifdef HAVE_SPLIT_USR -#define _CONF_PATHS_SPLIT_USR(n) "/lib/" n "\0" -#else -#define _CONF_PATHS_SPLIT_USR(n) -#endif - -/* Return a nulstr for a standard cascade of configuration paths, - * suitable to pass to conf_files_list_nulstr() or config_parse_many() - * to implement drop-in directories for extending configuration - * files. */ -#define CONF_PATHS_NULSTR(n) \ - "/etc/" n "\0" \ - "/run/" n "\0" \ - "/usr/local/lib/" n "\0" \ - "/usr/lib/" n "\0" \ - _CONF_PATHS_SPLIT_USR(n) diff --git a/src/basic/device-nodes.c b/src/basic/device-nodes.c deleted file mode 100644 index 38c0628a90..0000000000 --- a/src/basic/device-nodes.c +++ /dev/null @@ -1,80 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2011 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 -#include -#include - -#include "device-nodes.h" -#include "utf8.h" - -int whitelisted_char_for_devnode(char c, const char *white) { - - if ((c >= '0' && c <= '9') || - (c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z') || - strchr("#+-.:=@_", c) != NULL || - (white != NULL && strchr(white, c) != NULL)) - return 1; - - return 0; -} - -int encode_devnode_name(const char *str, char *str_enc, size_t len) { - size_t i, j; - - if (str == NULL || str_enc == NULL) - return -EINVAL; - - for (i = 0, j = 0; str[i] != '\0'; i++) { - int seqlen; - - seqlen = utf8_encoded_valid_unichar(&str[i]); - if (seqlen > 1) { - - if (len-j < (size_t)seqlen) - return -EINVAL; - - memcpy(&str_enc[j], &str[i], seqlen); - j += seqlen; - i += (seqlen-1); - - } else if (str[i] == '\\' || !whitelisted_char_for_devnode(str[i], NULL)) { - - if (len-j < 4) - return -EINVAL; - - sprintf(&str_enc[j], "\\x%02x", (unsigned char) str[i]); - j += 4; - - } else { - if (len-j < 1) - return -EINVAL; - - str_enc[j] = str[i]; - j++; - } - } - - if (len-j < 1) - return -EINVAL; - - str_enc[j] = '\0'; - return 0; -} diff --git a/src/basic/device-nodes.h b/src/basic/device-nodes.h deleted file mode 100644 index 94f385abcb..0000000000 --- a/src/basic/device-nodes.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 - -int encode_devnode_name(const char *str, char *str_enc, size_t len); -int whitelisted_char_for_devnode(char c, const char *additional); diff --git a/src/basic/dirent-util.c b/src/basic/dirent-util.c deleted file mode 100644 index 59067121b7..0000000000 --- a/src/basic/dirent-util.c +++ /dev/null @@ -1,74 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010-2012 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 - -#include "dirent-util.h" -#include "path-util.h" -#include "string-util.h" - -int dirent_ensure_type(DIR *d, struct dirent *de) { - struct stat st; - - assert(d); - assert(de); - - if (de->d_type != DT_UNKNOWN) - return 0; - - if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) - return -errno; - - de->d_type = - S_ISREG(st.st_mode) ? DT_REG : - S_ISDIR(st.st_mode) ? DT_DIR : - S_ISLNK(st.st_mode) ? DT_LNK : - S_ISFIFO(st.st_mode) ? DT_FIFO : - S_ISSOCK(st.st_mode) ? DT_SOCK : - S_ISCHR(st.st_mode) ? DT_CHR : - S_ISBLK(st.st_mode) ? DT_BLK : - DT_UNKNOWN; - - return 0; -} - -bool dirent_is_file(const struct dirent *de) { - assert(de); - - if (!IN_SET(de->d_type, DT_REG, DT_LNK, DT_UNKNOWN)) - return false; - - if (hidden_or_backup_file(de->d_name)) - return false; - - return true; -} - -bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) { - assert(de); - - if (!IN_SET(de->d_type, DT_REG, DT_LNK, DT_UNKNOWN)) - return false; - - if (de->d_name[0] == '.') - return false; - - return endswith(de->d_name, suffix); -} diff --git a/src/basic/dirent-util.h b/src/basic/dirent-util.h deleted file mode 100644 index b91d04908f..0000000000 --- a/src/basic/dirent-util.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include - -#include "macro.h" -#include "path-util.h" - -int dirent_ensure_type(DIR *d, struct dirent *de); - -bool dirent_is_file(const struct dirent *de) _pure_; -bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) _pure_; - -#define FOREACH_DIRENT(de, d, on_error) \ - for (errno = 0, de = readdir(d);; errno = 0, de = readdir(d)) \ - if (!de) { \ - if (errno > 0) { \ - on_error; \ - } \ - break; \ - } else if (hidden_or_backup_file((de)->d_name)) \ - continue; \ - else - -#define FOREACH_DIRENT_ALL(de, d, on_error) \ - for (errno = 0, de = readdir(d);; errno = 0, de = readdir(d)) \ - if (!de) { \ - if (errno > 0) { \ - on_error; \ - } \ - break; \ - } else diff --git a/src/basic/env-util.c b/src/basic/env-util.c deleted file mode 100644 index 7f5fddb700..0000000000 --- a/src/basic/env-util.c +++ /dev/null @@ -1,624 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 -#include -#include -#include -#include - -#include "alloc-util.h" -#include "env-util.h" -#include "extract-word.h" -#include "macro.h" -#include "parse-util.h" -#include "string-util.h" -#include "strv.h" -#include "utf8.h" - -#define VALID_CHARS_ENV_NAME \ - DIGITS LETTERS \ - "_" - -#ifndef ARG_MAX -#define ARG_MAX ((size_t) sysconf(_SC_ARG_MAX)) -#endif - -static bool env_name_is_valid_n(const char *e, size_t n) { - const char *p; - - if (!e) - return false; - - if (n <= 0) - return false; - - if (e[0] >= '0' && e[0] <= '9') - return false; - - /* POSIX says the overall size of the environment block cannot - * be > ARG_MAX, an individual assignment hence cannot be - * either. Discounting the equal sign and trailing NUL this - * hence leaves ARG_MAX-2 as longest possible variable - * name. */ - if (n > ARG_MAX - 2) - return false; - - for (p = e; p < e + n; p++) - if (!strchr(VALID_CHARS_ENV_NAME, *p)) - return false; - - return true; -} - -bool env_name_is_valid(const char *e) { - if (!e) - return false; - - return env_name_is_valid_n(e, strlen(e)); -} - -bool env_value_is_valid(const char *e) { - if (!e) - return false; - - if (!utf8_is_valid(e)) - return false; - - /* bash allows tabs in environment variables, and so should - * we */ - if (string_has_cc(e, "\t")) - return false; - - /* POSIX says the overall size of the environment block cannot - * be > ARG_MAX, an individual assignment hence cannot be - * either. Discounting the shortest possible variable name of - * length 1, the equal sign and trailing NUL this hence leaves - * ARG_MAX-3 as longest possible variable value. */ - if (strlen(e) > ARG_MAX - 3) - return false; - - return true; -} - -bool env_assignment_is_valid(const char *e) { - const char *eq; - - eq = strchr(e, '='); - if (!eq) - return false; - - if (!env_name_is_valid_n(e, eq - e)) - return false; - - if (!env_value_is_valid(eq + 1)) - return false; - - /* POSIX says the overall size of the environment block cannot - * be > ARG_MAX, hence the individual variable assignments - * cannot be either, but let's leave room for one trailing NUL - * byte. */ - if (strlen(e) > ARG_MAX - 1) - return false; - - return true; -} - -bool strv_env_is_valid(char **e) { - char **p, **q; - - STRV_FOREACH(p, e) { - size_t k; - - if (!env_assignment_is_valid(*p)) - return false; - - /* Check if there are duplicate assginments */ - k = strcspn(*p, "="); - STRV_FOREACH(q, p + 1) - if (strneq(*p, *q, k) && (*q)[k] == '=') - return false; - } - - return true; -} - -bool strv_env_name_is_valid(char **l) { - char **p, **q; - - STRV_FOREACH(p, l) { - if (!env_name_is_valid(*p)) - return false; - - STRV_FOREACH(q, p + 1) - if (streq(*p, *q)) - return false; - } - - return true; -} - -bool strv_env_name_or_assignment_is_valid(char **l) { - char **p, **q; - - STRV_FOREACH(p, l) { - if (!env_assignment_is_valid(*p) && !env_name_is_valid(*p)) - return false; - - STRV_FOREACH(q, p + 1) - if (streq(*p, *q)) - return false; - } - - return true; -} - -static int env_append(char **r, char ***k, char **a) { - assert(r); - assert(k); - - if (!a) - return 0; - - /* Add the entries of a to *k unless they already exist in *r - * in which case they are overridden instead. This assumes - * there is enough space in the r array. */ - - for (; *a; a++) { - char **j; - size_t n; - - n = strcspn(*a, "="); - - if ((*a)[n] == '=') - n++; - - for (j = r; j < *k; j++) - if (strneq(*j, *a, n)) - break; - - if (j >= *k) - (*k)++; - else - free(*j); - - *j = strdup(*a); - if (!*j) - return -ENOMEM; - } - - return 0; -} - -char **strv_env_merge(unsigned n_lists, ...) { - size_t n = 0; - char **l, **k, **r; - va_list ap; - unsigned i; - - /* Merges an arbitrary number of environment sets */ - - va_start(ap, n_lists); - for (i = 0; i < n_lists; i++) { - l = va_arg(ap, char**); - n += strv_length(l); - } - va_end(ap); - - r = new(char*, n+1); - if (!r) - return NULL; - - k = r; - - va_start(ap, n_lists); - for (i = 0; i < n_lists; i++) { - l = va_arg(ap, char**); - if (env_append(r, &k, l) < 0) - goto fail; - } - va_end(ap); - - *k = NULL; - - return r; - -fail: - va_end(ap); - strv_free(r); - - return NULL; -} - -_pure_ static bool env_match(const char *t, const char *pattern) { - assert(t); - assert(pattern); - - /* pattern a matches string a - * a matches a= - * a matches a=b - * a= matches a= - * a=b matches a=b - * a= does not match a - * a=b does not match a= - * a=b does not match a - * a=b does not match a=c */ - - if (streq(t, pattern)) - return true; - - if (!strchr(pattern, '=')) { - size_t l = strlen(pattern); - - return strneq(t, pattern, l) && t[l] == '='; - } - - return false; -} - -char **strv_env_delete(char **x, unsigned n_lists, ...) { - size_t n, i = 0; - char **k, **r; - va_list ap; - - /* Deletes every entry from x that is mentioned in the other - * string lists */ - - n = strv_length(x); - - r = new(char*, n+1); - if (!r) - return NULL; - - STRV_FOREACH(k, x) { - unsigned v; - - va_start(ap, n_lists); - for (v = 0; v < n_lists; v++) { - char **l, **j; - - l = va_arg(ap, char**); - STRV_FOREACH(j, l) - if (env_match(*k, *j)) - goto skip; - } - va_end(ap); - - r[i] = strdup(*k); - if (!r[i]) { - strv_free(r); - return NULL; - } - - i++; - continue; - - skip: - va_end(ap); - } - - r[i] = NULL; - - assert(i <= n); - - return r; -} - -char **strv_env_unset(char **l, const char *p) { - - char **f, **t; - - if (!l) - return NULL; - - assert(p); - - /* Drops every occurrence of the env var setting p in the - * string list. Edits in-place. */ - - for (f = t = l; *f; f++) { - - if (env_match(*f, p)) { - free(*f); - continue; - } - - *(t++) = *f; - } - - *t = NULL; - return l; -} - -char **strv_env_unset_many(char **l, ...) { - - char **f, **t; - - if (!l) - return NULL; - - /* Like strv_env_unset() but applies many at once. Edits in-place. */ - - for (f = t = l; *f; f++) { - bool found = false; - const char *p; - va_list ap; - - va_start(ap, l); - - while ((p = va_arg(ap, const char*))) { - if (env_match(*f, p)) { - found = true; - break; - } - } - - va_end(ap); - - if (found) { - free(*f); - continue; - } - - *(t++) = *f; - } - - *t = NULL; - return l; -} - -char **strv_env_set(char **x, const char *p) { - - char **k, **r; - char* m[2] = { (char*) p, NULL }; - - /* Overrides the env var setting of p, returns a new copy */ - - r = new(char*, strv_length(x)+2); - if (!r) - return NULL; - - k = r; - if (env_append(r, &k, x) < 0) - goto fail; - - if (env_append(r, &k, m) < 0) - goto fail; - - *k = NULL; - - return r; - -fail: - strv_free(r); - return NULL; -} - -char *strv_env_get_n(char **l, const char *name, size_t k) { - char **i; - - assert(name); - - if (k <= 0) - return NULL; - - STRV_FOREACH(i, l) - if (strneq(*i, name, k) && - (*i)[k] == '=') - return *i + k + 1; - - return NULL; -} - -char *strv_env_get(char **l, const char *name) { - assert(name); - - return strv_env_get_n(l, name, strlen(name)); -} - -char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) { - char **p, **q; - int k = 0; - - STRV_FOREACH(p, e) { - size_t n; - bool duplicate = false; - - if (!env_assignment_is_valid(*p)) { - if (invalid_callback) - invalid_callback(*p, userdata); - free(*p); - continue; - } - - n = strcspn(*p, "="); - STRV_FOREACH(q, p + 1) - if (strneq(*p, *q, n) && (*q)[n] == '=') { - duplicate = true; - break; - } - - if (duplicate) { - free(*p); - continue; - } - - e[k++] = *p; - } - - if (e) - e[k] = NULL; - - return e; -} - -char *replace_env(const char *format, char **env) { - enum { - WORD, - CURLY, - VARIABLE - } state = WORD; - - const char *e, *word = format; - char *r = NULL, *k; - - assert(format); - - for (e = format; *e; e ++) { - - switch (state) { - - case WORD: - if (*e == '$') - state = CURLY; - break; - - case CURLY: - if (*e == '{') { - k = strnappend(r, word, e-word-1); - if (!k) - goto fail; - - free(r); - r = k; - - word = e-1; - state = VARIABLE; - - } else if (*e == '$') { - k = strnappend(r, word, e-word); - if (!k) - goto fail; - - free(r); - r = k; - - word = e+1; - state = WORD; - } else - state = WORD; - break; - - case VARIABLE: - if (*e == '}') { - const char *t; - - t = strempty(strv_env_get_n(env, word+2, e-word-2)); - - k = strappend(r, t); - if (!k) - goto fail; - - free(r); - r = k; - - word = e+1; - state = WORD; - } - break; - } - } - - k = strnappend(r, word, e-word); - if (!k) - goto fail; - - free(r); - return k; - -fail: - free(r); - return NULL; -} - -char **replace_env_argv(char **argv, char **env) { - char **ret, **i; - unsigned k = 0, l = 0; - - l = strv_length(argv); - - ret = new(char*, l+1); - if (!ret) - return NULL; - - STRV_FOREACH(i, argv) { - - /* If $FOO appears as single word, replace it by the split up variable */ - if ((*i)[0] == '$' && (*i)[1] != '{' && (*i)[1] != '$') { - char *e; - char **w, **m = NULL; - unsigned q; - - e = strv_env_get(env, *i+1); - if (e) { - int r; - - r = strv_split_extract(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_QUOTES); - if (r < 0) { - ret[k] = NULL; - strv_free(ret); - return NULL; - } - } else - m = NULL; - - q = strv_length(m); - l = l + q - 1; - - w = realloc(ret, sizeof(char*) * (l+1)); - if (!w) { - ret[k] = NULL; - strv_free(ret); - strv_free(m); - return NULL; - } - - ret = w; - if (m) { - memcpy(ret + k, m, q * sizeof(char*)); - free(m); - } - - k += q; - continue; - } - - /* If ${FOO} appears as part of a word, replace it by the variable as-is */ - ret[k] = replace_env(*i, env); - if (!ret[k]) { - strv_free(ret); - return NULL; - } - k++; - } - - ret[k] = NULL; - return ret; -} - -int getenv_bool(const char *p) { - const char *e; - - e = getenv(p); - if (!e) - return -ENXIO; - - return parse_boolean(e); -} diff --git a/src/basic/env-util.h b/src/basic/env-util.h deleted file mode 100644 index b1fef704c2..0000000000 --- a/src/basic/env-util.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 - -#include "macro.h" - -bool env_name_is_valid(const char *e); -bool env_value_is_valid(const char *e); -bool env_assignment_is_valid(const char *e); - -char *replace_env(const char *format, char **env); -char **replace_env_argv(char **argv, char **env); - -bool strv_env_is_valid(char **e); -#define strv_env_clean(l) strv_env_clean_with_callback(l, NULL, NULL) -char **strv_env_clean_with_callback(char **l, void (*invalid_callback)(const char *p, void *userdata), void *userdata); - -bool strv_env_name_is_valid(char **l); -bool strv_env_name_or_assignment_is_valid(char **l); - -char **strv_env_merge(unsigned n_lists, ...); -char **strv_env_delete(char **x, unsigned n_lists, ...); /* New copy */ - -char **strv_env_set(char **x, const char *p); /* New copy ... */ -char **strv_env_unset(char **l, const char *p); /* In place ... */ -char **strv_env_unset_many(char **l, ...) _sentinel_; - -char *strv_env_get_n(char **l, const char *name, size_t k) _pure_; -char *strv_env_get(char **x, const char *n) _pure_; - -int getenv_bool(const char *p); diff --git a/src/basic/errno-list.c b/src/basic/errno-list.c deleted file mode 100644 index 31b66bad5e..0000000000 --- a/src/basic/errno-list.c +++ /dev/null @@ -1,57 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "errno-list.h" -#include "macro.h" - -static const struct errno_name* lookup_errno(register const char *str, - register unsigned int len); - -#include "errno-from-name.h" -#include "errno-to-name.h" - -const char *errno_to_name(int id) { - - if (id < 0) - id = -id; - - if (id >= (int) ELEMENTSOF(errno_names)) - return NULL; - - return errno_names[id]; -} - -int errno_from_name(const char *name) { - const struct errno_name *sc; - - assert(name); - - sc = lookup_errno(name, strlen(name)); - if (!sc) - return -EINVAL; - - assert(sc->id > 0); - return sc->id; -} - -int errno_max(void) { - return ELEMENTSOF(errno_names); -} diff --git a/src/basic/errno-list.h b/src/basic/errno-list.h deleted file mode 100644 index 4eec0cc786..0000000000 --- a/src/basic/errno-list.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -const char *errno_to_name(int id); -int errno_from_name(const char *name); - -int errno_max(void); diff --git a/src/basic/escape.c b/src/basic/escape.c deleted file mode 100644 index 01daf11ce7..0000000000 --- a/src/basic/escape.c +++ /dev/null @@ -1,502 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include - -#include "alloc-util.h" -#include "escape.h" -#include "hexdecoct.h" -#include "macro.h" -#include "utf8.h" - -size_t cescape_char(char c, char *buf) { - char * buf_old = buf; - - switch (c) { - - case '\a': - *(buf++) = '\\'; - *(buf++) = 'a'; - break; - case '\b': - *(buf++) = '\\'; - *(buf++) = 'b'; - break; - case '\f': - *(buf++) = '\\'; - *(buf++) = 'f'; - break; - case '\n': - *(buf++) = '\\'; - *(buf++) = 'n'; - break; - case '\r': - *(buf++) = '\\'; - *(buf++) = 'r'; - break; - case '\t': - *(buf++) = '\\'; - *(buf++) = 't'; - break; - case '\v': - *(buf++) = '\\'; - *(buf++) = 'v'; - break; - case '\\': - *(buf++) = '\\'; - *(buf++) = '\\'; - break; - case '"': - *(buf++) = '\\'; - *(buf++) = '"'; - break; - case '\'': - *(buf++) = '\\'; - *(buf++) = '\''; - break; - - default: - /* For special chars we prefer octal over - * hexadecimal encoding, simply because glib's - * g_strescape() does the same */ - if ((c < ' ') || (c >= 127)) { - *(buf++) = '\\'; - *(buf++) = octchar((unsigned char) c >> 6); - *(buf++) = octchar((unsigned char) c >> 3); - *(buf++) = octchar((unsigned char) c); - } else - *(buf++) = c; - break; - } - - return buf - buf_old; -} - -char *cescape_length(const char *s, size_t n) { - const char *f; - char *r, *t; - - assert(s || n == 0); - - /* Does C style string escaping. May be reversed with - * cunescape(). */ - - r = new(char, n*4 + 1); - if (!r) - return NULL; - - for (f = s, t = r; f < s + n; f++) - t += cescape_char(*f, t); - - *t = 0; - - return r; -} - -char *cescape(const char *s) { - assert(s); - - return cescape_length(s, strlen(s)); -} - -int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit) { - int r = 1; - - assert(p); - assert(*p); - assert(ret); - - /* Unescapes C style. Returns the unescaped character in ret. - * Sets *eight_bit to true if the escaped sequence either fits in - * one byte in UTF-8 or is a non-unicode literal byte and should - * instead be copied directly. - */ - - if (length != (size_t) -1 && length < 1) - return -EINVAL; - - switch (p[0]) { - - case 'a': - *ret = '\a'; - break; - case 'b': - *ret = '\b'; - break; - case 'f': - *ret = '\f'; - break; - case 'n': - *ret = '\n'; - break; - case 'r': - *ret = '\r'; - break; - case 't': - *ret = '\t'; - break; - case 'v': - *ret = '\v'; - break; - case '\\': - *ret = '\\'; - break; - case '"': - *ret = '"'; - break; - case '\'': - *ret = '\''; - break; - - case 's': - /* This is an extension of the XDG syntax files */ - *ret = ' '; - break; - - case 'x': { - /* hexadecimal encoding */ - int a, b; - - if (length != (size_t) -1 && length < 3) - return -EINVAL; - - a = unhexchar(p[1]); - if (a < 0) - return -EINVAL; - - b = unhexchar(p[2]); - if (b < 0) - return -EINVAL; - - /* Don't allow NUL bytes */ - if (a == 0 && b == 0) - return -EINVAL; - - *ret = (a << 4U) | b; - *eight_bit = true; - r = 3; - break; - } - - case 'u': { - /* C++11 style 16bit unicode */ - - int a[4]; - unsigned i; - uint32_t c; - - if (length != (size_t) -1 && length < 5) - return -EINVAL; - - for (i = 0; i < 4; i++) { - a[i] = unhexchar(p[1 + i]); - if (a[i] < 0) - return a[i]; - } - - c = ((uint32_t) a[0] << 12U) | ((uint32_t) a[1] << 8U) | ((uint32_t) a[2] << 4U) | (uint32_t) a[3]; - - /* Don't allow 0 chars */ - if (c == 0) - return -EINVAL; - - *ret = c; - r = 5; - break; - } - - case 'U': { - /* C++11 style 32bit unicode */ - - int a[8]; - unsigned i; - char32_t c; - - if (length != (size_t) -1 && length < 9) - return -EINVAL; - - for (i = 0; i < 8; i++) { - a[i] = unhexchar(p[1 + i]); - if (a[i] < 0) - return a[i]; - } - - c = ((uint32_t) a[0] << 28U) | ((uint32_t) a[1] << 24U) | ((uint32_t) a[2] << 20U) | ((uint32_t) a[3] << 16U) | - ((uint32_t) a[4] << 12U) | ((uint32_t) a[5] << 8U) | ((uint32_t) a[6] << 4U) | (uint32_t) a[7]; - - /* Don't allow 0 chars */ - if (c == 0) - return -EINVAL; - - /* Don't allow invalid code points */ - if (!unichar_is_valid(c)) - return -EINVAL; - - *ret = c; - r = 9; - break; - } - - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': { - /* octal encoding */ - int a, b, c; - char32_t m; - - if (length != (size_t) -1 && length < 3) - return -EINVAL; - - a = unoctchar(p[0]); - if (a < 0) - return -EINVAL; - - b = unoctchar(p[1]); - if (b < 0) - return -EINVAL; - - c = unoctchar(p[2]); - if (c < 0) - return -EINVAL; - - /* don't allow NUL bytes */ - if (a == 0 && b == 0 && c == 0) - return -EINVAL; - - /* Don't allow bytes above 255 */ - m = ((uint32_t) a << 6U) | ((uint32_t) b << 3U) | (uint32_t) c; - if (m > 255) - return -EINVAL; - - *ret = m; - *eight_bit = true; - r = 3; - break; - } - - default: - return -EINVAL; - } - - return r; -} - -int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) { - char *r, *t; - const char *f; - size_t pl; - - assert(s); - assert(ret); - - /* Undoes C style string escaping, and optionally prefixes it. */ - - pl = prefix ? strlen(prefix) : 0; - - r = new(char, pl+length+1); - if (!r) - return -ENOMEM; - - if (prefix) - memcpy(r, prefix, pl); - - for (f = s, t = r + pl; f < s + length; f++) { - size_t remaining; - bool eight_bit = false; - char32_t u; - int k; - - remaining = s + length - f; - assert(remaining > 0); - - if (*f != '\\') { - /* A literal literal, copy verbatim */ - *(t++) = *f; - continue; - } - - if (remaining == 1) { - if (flags & UNESCAPE_RELAX) { - /* A trailing backslash, copy verbatim */ - *(t++) = *f; - continue; - } - - free(r); - return -EINVAL; - } - - k = cunescape_one(f + 1, remaining - 1, &u, &eight_bit); - if (k < 0) { - if (flags & UNESCAPE_RELAX) { - /* Invalid escape code, let's take it literal then */ - *(t++) = '\\'; - continue; - } - - free(r); - return k; - } - - f += k; - if (eight_bit) - /* One byte? Set directly as specified */ - *(t++) = u; - else - /* Otherwise encode as multi-byte UTF-8 */ - t += utf8_encode_unichar(t, u); - } - - *t = 0; - - *ret = r; - return t - r; -} - -int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) { - return cunescape_length_with_prefix(s, length, NULL, flags, ret); -} - -int cunescape(const char *s, UnescapeFlags flags, char **ret) { - return cunescape_length(s, strlen(s), flags, ret); -} - -char *xescape(const char *s, const char *bad) { - char *r, *t; - const char *f; - - /* Escapes all chars in bad, in addition to \ and all special - * chars, in \xFF style escaping. May be reversed with - * cunescape(). */ - - r = new(char, strlen(s) * 4 + 1); - if (!r) - return NULL; - - for (f = s, t = r; *f; f++) { - - if ((*f < ' ') || (*f >= 127) || - (*f == '\\') || strchr(bad, *f)) { - *(t++) = '\\'; - *(t++) = 'x'; - *(t++) = hexchar(*f >> 4); - *(t++) = hexchar(*f); - } else - *(t++) = *f; - } - - *t = 0; - - return r; -} - -char *octescape(const char *s, size_t len) { - char *r, *t; - const char *f; - - /* Escapes all chars in bad, in addition to \ and " chars, - * in \nnn style escaping. */ - - r = new(char, len * 4 + 1); - if (!r) - return NULL; - - for (f = s, t = r; f < s + len; f++) { - - if (*f < ' ' || *f >= 127 || *f == '\\' || *f == '"') { - *(t++) = '\\'; - *(t++) = '0' + (*f >> 6); - *(t++) = '0' + ((*f >> 3) & 8); - *(t++) = '0' + (*f & 8); - } else - *(t++) = *f; - } - - *t = 0; - - return r; - -} - -static char *strcpy_backslash_escaped(char *t, const char *s, const char *bad) { - assert(bad); - - for (; *s; s++) { - if (*s == '\\' || strchr(bad, *s)) - *(t++) = '\\'; - - *(t++) = *s; - } - - return t; -} - -char *shell_escape(const char *s, const char *bad) { - char *r, *t; - - r = new(char, strlen(s)*2+1); - if (!r) - return NULL; - - t = strcpy_backslash_escaped(r, s, bad); - *t = 0; - - return r; -} - -char *shell_maybe_quote(const char *s) { - const char *p; - char *r, *t; - - assert(s); - - /* Encloses a string in double quotes if necessary to make it - * OK as shell string. */ - - for (p = s; *p; p++) - if (*p <= ' ' || - *p >= 127 || - strchr(SHELL_NEED_QUOTES, *p)) - break; - - if (!*p) - return strdup(s); - - r = new(char, 1+strlen(s)*2+1+1); - if (!r) - return NULL; - - t = r; - *(t++) = '"'; - t = mempcpy(t, s, p - s); - - t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE); - - *(t++)= '"'; - *t = 0; - - return r; -} diff --git a/src/basic/escape.h b/src/basic/escape.h deleted file mode 100644 index deaa4def28..0000000000 --- a/src/basic/escape.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include - -#include "string-util.h" -#include "missing.h" - -/* What characters are special in the shell? */ -/* must be escaped outside and inside double-quotes */ -#define SHELL_NEED_ESCAPE "\"\\`$" -/* can be escaped or double-quoted */ -#define SHELL_NEED_QUOTES SHELL_NEED_ESCAPE GLOB_CHARS "'()<>|&;" - -typedef enum UnescapeFlags { - UNESCAPE_RELAX = 1, -} UnescapeFlags; - -char *cescape(const char *s); -char *cescape_length(const char *s, size_t n); -size_t cescape_char(char c, char *buf); - -int cunescape(const char *s, UnescapeFlags flags, char **ret); -int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret); -int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret); -int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit); - -char *xescape(const char *s, const char *bad); -char *octescape(const char *s, size_t len); - -char *shell_escape(const char *s, const char *bad); -char *shell_maybe_quote(const char *s); diff --git a/src/basic/ether-addr-util.c b/src/basic/ether-addr-util.c deleted file mode 100644 index 5697e8d132..0000000000 --- a/src/basic/ether-addr-util.c +++ /dev/null @@ -1,125 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 -#include - -#include "ether-addr-util.h" -#include "macro.h" -#include "string-util.h" - -char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR_TO_STRING_MAX]) { - assert(addr); - assert(buffer); - - /* Like ether_ntoa() but uses %02x instead of %x to print - * ethernet addresses, which makes them look less funny. Also, - * doesn't use a static buffer. */ - - sprintf(buffer, "%02x:%02x:%02x:%02x:%02x:%02x", - addr->ether_addr_octet[0], - addr->ether_addr_octet[1], - addr->ether_addr_octet[2], - addr->ether_addr_octet[3], - addr->ether_addr_octet[4], - addr->ether_addr_octet[5]); - - return buffer; -} - -bool ether_addr_equal(const struct ether_addr *a, const struct ether_addr *b) { - assert(a); - assert(b); - - return a->ether_addr_octet[0] == b->ether_addr_octet[0] && - a->ether_addr_octet[1] == b->ether_addr_octet[1] && - a->ether_addr_octet[2] == b->ether_addr_octet[2] && - a->ether_addr_octet[3] == b->ether_addr_octet[3] && - a->ether_addr_octet[4] == b->ether_addr_octet[4] && - a->ether_addr_octet[5] == b->ether_addr_octet[5]; -} - -int ether_addr_from_string(const char *s, struct ether_addr *ret, size_t *offset) { - size_t pos = 0, n, field; - char sep = '\0'; - const char *hex = HEXDIGITS, *hexoff; - size_t x; - bool touched; - -#define parse_fields(v) \ - for (field = 0; field < ELEMENTSOF(v); field++) { \ - touched = false; \ - for (n = 0; n < (2 * sizeof(v[0])); n++) { \ - if (s[pos] == '\0') \ - break; \ - hexoff = strchr(hex, s[pos]); \ - if (hexoff == NULL) \ - break; \ - assert(hexoff >= hex); \ - x = hexoff - hex; \ - if (x >= 16) \ - x -= 6; /* A-F */ \ - assert(x < 16); \ - touched = true; \ - v[field] <<= 4; \ - v[field] += x; \ - pos++; \ - } \ - if (!touched) \ - return -EINVAL; \ - if (field < (ELEMENTSOF(v)-1)) { \ - if (s[pos] != sep) \ - return -EINVAL; \ - else \ - pos++; \ - } \ - } - - assert(s); - assert(ret); - - sep = s[strspn(s, hex)]; - if (sep == '\n') - return -EINVAL; - if (strchr(":.-", sep) == NULL) - return -EINVAL; - - if (sep == '.') { - uint16_t shorts[3] = { 0 }; - - parse_fields(shorts); - - for (n = 0; n < ELEMENTSOF(shorts); n++) { - ret->ether_addr_octet[2*n] = ((shorts[n] & (uint16_t)0xff00) >> 8); - ret->ether_addr_octet[2*n + 1] = (shorts[n] & (uint16_t)0x00ff); - } - } else { - struct ether_addr out = { .ether_addr_octet = { 0 } }; - - parse_fields(out.ether_addr_octet); - - for (n = 0; n < ELEMENTSOF(out.ether_addr_octet); n++) - ret->ether_addr_octet[n] = out.ether_addr_octet[n]; - } - - if (offset) - *offset = pos; - return 0; -} diff --git a/src/basic/ether-addr-util.h b/src/basic/ether-addr-util.h deleted file mode 100644 index 74e125a95f..0000000000 --- a/src/basic/ether-addr-util.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 - -#define ETHER_ADDR_FORMAT_STR "%02X%02X%02X%02X%02X%02X" -#define ETHER_ADDR_FORMAT_VAL(x) (x).ether_addr_octet[0], (x).ether_addr_octet[1], (x).ether_addr_octet[2], (x).ether_addr_octet[3], (x).ether_addr_octet[4], (x).ether_addr_octet[5] - -#define ETHER_ADDR_TO_STRING_MAX (3*6) -char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR_TO_STRING_MAX]); - -bool ether_addr_equal(const struct ether_addr *a, const struct ether_addr *b); - -#define ETHER_ADDR_NULL ((const struct ether_addr){}) - -static inline bool ether_addr_is_null(const struct ether_addr *addr) { - return ether_addr_equal(addr, ÐER_ADDR_NULL); -} - -int ether_addr_from_string(const char *s, struct ether_addr *ret, size_t *offset); diff --git a/src/basic/exit-status.c b/src/basic/exit-status.c deleted file mode 100644 index 92fa5ace61..0000000000 --- a/src/basic/exit-status.c +++ /dev/null @@ -1,240 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 - -#include "exit-status.h" -#include "macro.h" -#include "set.h" - -const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) { - - /* We cast to int here, so that -Wenum doesn't complain that - * EXIT_SUCCESS/EXIT_FAILURE aren't in the enum */ - - switch ((int) status) { - - case EXIT_SUCCESS: - return "SUCCESS"; - - case EXIT_FAILURE: - return "FAILURE"; - } - - - if (level == EXIT_STATUS_SYSTEMD || level == EXIT_STATUS_LSB) { - switch ((int) status) { - - case EXIT_CHDIR: - return "CHDIR"; - - case EXIT_NICE: - return "NICE"; - - case EXIT_FDS: - return "FDS"; - - case EXIT_EXEC: - return "EXEC"; - - case EXIT_MEMORY: - return "MEMORY"; - - case EXIT_LIMITS: - return "LIMITS"; - - case EXIT_OOM_ADJUST: - return "OOM_ADJUST"; - - case EXIT_SIGNAL_MASK: - return "SIGNAL_MASK"; - - case EXIT_STDIN: - return "STDIN"; - - case EXIT_STDOUT: - return "STDOUT"; - - case EXIT_CHROOT: - return "CHROOT"; - - case EXIT_IOPRIO: - return "IOPRIO"; - - case EXIT_TIMERSLACK: - return "TIMERSLACK"; - - case EXIT_SECUREBITS: - return "SECUREBITS"; - - case EXIT_SETSCHEDULER: - return "SETSCHEDULER"; - - case EXIT_CPUAFFINITY: - return "CPUAFFINITY"; - - case EXIT_GROUP: - return "GROUP"; - - case EXIT_USER: - return "USER"; - - case EXIT_CAPABILITIES: - return "CAPABILITIES"; - - case EXIT_CGROUP: - return "CGROUP"; - - case EXIT_SETSID: - return "SETSID"; - - case EXIT_CONFIRM: - return "CONFIRM"; - - case EXIT_STDERR: - return "STDERR"; - - case EXIT_PAM: - return "PAM"; - - case EXIT_NETWORK: - return "NETWORK"; - - case EXIT_NAMESPACE: - return "NAMESPACE"; - - case EXIT_NO_NEW_PRIVILEGES: - return "NO_NEW_PRIVILEGES"; - - case EXIT_SECCOMP: - return "SECCOMP"; - - case EXIT_SELINUX_CONTEXT: - return "SELINUX_CONTEXT"; - - case EXIT_PERSONALITY: - return "PERSONALITY"; - - case EXIT_APPARMOR_PROFILE: - return "APPARMOR"; - - case EXIT_ADDRESS_FAMILIES: - return "ADDRESS_FAMILIES"; - - case EXIT_RUNTIME_DIRECTORY: - return "RUNTIME_DIRECTORY"; - - case EXIT_CHOWN: - return "CHOWN"; - - case EXIT_MAKE_STARTER: - return "MAKE_STARTER"; - - case EXIT_SMACK_PROCESS_LABEL: - return "SMACK_PROCESS_LABEL"; - } - } - - if (level == EXIT_STATUS_LSB) { - switch ((int) status) { - - case EXIT_INVALIDARGUMENT: - return "INVALIDARGUMENT"; - - case EXIT_NOTIMPLEMENTED: - return "NOTIMPLEMENTED"; - - case EXIT_NOPERMISSION: - return "NOPERMISSION"; - - case EXIT_NOTINSTALLED: - return "NOTINSTALLED"; - - case EXIT_NOTCONFIGURED: - return "NOTCONFIGURED"; - - case EXIT_NOTRUNNING: - return "NOTRUNNING"; - } - } - - return NULL; -} - - -bool is_clean_exit(int code, int status, ExitStatusSet *success_status) { - - if (code == CLD_EXITED) - return status == 0 || - (success_status && - set_contains(success_status->status, INT_TO_PTR(status))); - - /* If a daemon does not implement handlers for some of the - * signals that's not considered an unclean shutdown */ - if (code == CLD_KILLED) - return - status == SIGHUP || - status == SIGINT || - status == SIGTERM || - status == SIGPIPE || - (success_status && - set_contains(success_status->signal, INT_TO_PTR(status))); - - return false; -} - -bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status) { - - if (is_clean_exit(code, status, success_status)) - return true; - - return - code == CLD_EXITED && - (status == EXIT_NOTINSTALLED || status == EXIT_NOTCONFIGURED); -} - -void exit_status_set_free(ExitStatusSet *x) { - assert(x); - - set_free(x->status); - set_free(x->signal); - x->status = x->signal = NULL; -} - -bool exit_status_set_is_empty(ExitStatusSet *x) { - if (!x) - return true; - - return set_isempty(x->status) && set_isempty(x->signal); -} - -bool exit_status_set_test(ExitStatusSet *x, int code, int status) { - - if (exit_status_set_is_empty(x)) - return false; - - if (code == CLD_EXITED && set_contains(x->status, INT_TO_PTR(status))) - return true; - - if (IN_SET(code, CLD_KILLED, CLD_DUMPED) && set_contains(x->signal, INT_TO_PTR(status))) - return true; - - return false; -} diff --git a/src/basic/exit-status.h b/src/basic/exit-status.h deleted file mode 100644 index 1208c8feed..0000000000 --- a/src/basic/exit-status.h +++ /dev/null @@ -1,102 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "hashmap.h" -#include "macro.h" -#include "set.h" - -typedef enum ExitStatus { - /* EXIT_SUCCESS defined by libc */ - /* EXIT_FAILURE defined by libc */ - EXIT_INVALIDARGUMENT = 2, - EXIT_NOTIMPLEMENTED = 3, - EXIT_NOPERMISSION = 4, - EXIT_NOTINSTALLED = 5, - EXIT_NOTCONFIGURED = 6, - EXIT_NOTRUNNING = 7, - - /* The LSB suggests that error codes >= 200 are "reserved". We - * use them here under the assumption that they hence are - * unused by init scripts. - * - * http://refspecs.linuxfoundation.org/LSB_3.2.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html */ - - EXIT_CHDIR = 200, - EXIT_NICE, - EXIT_FDS, - EXIT_EXEC, - EXIT_MEMORY, - EXIT_LIMITS, - EXIT_OOM_ADJUST, - EXIT_SIGNAL_MASK, - EXIT_STDIN, - EXIT_STDOUT, - EXIT_CHROOT, /* 210 */ - EXIT_IOPRIO, - EXIT_TIMERSLACK, - EXIT_SECUREBITS, - EXIT_SETSCHEDULER, - EXIT_CPUAFFINITY, - EXIT_GROUP, - EXIT_USER, - EXIT_CAPABILITIES, - EXIT_CGROUP, - EXIT_SETSID, /* 220 */ - EXIT_CONFIRM, - EXIT_STDERR, - _EXIT_RESERVED, /* used to be tcpwrap, don't reuse! */ - EXIT_PAM, - EXIT_NETWORK, - EXIT_NAMESPACE, - EXIT_NO_NEW_PRIVILEGES, - EXIT_SECCOMP, - EXIT_SELINUX_CONTEXT, - EXIT_PERSONALITY, /* 230 */ - EXIT_APPARMOR_PROFILE, - EXIT_ADDRESS_FAMILIES, - EXIT_RUNTIME_DIRECTORY, - EXIT_MAKE_STARTER, - EXIT_CHOWN, - EXIT_SMACK_PROCESS_LABEL, -} ExitStatus; - -typedef enum ExitStatusLevel { - EXIT_STATUS_MINIMAL, - EXIT_STATUS_SYSTEMD, - EXIT_STATUS_LSB, - EXIT_STATUS_FULL = EXIT_STATUS_LSB -} ExitStatusLevel; - -typedef struct ExitStatusSet { - Set *status; - Set *signal; -} ExitStatusSet; - -const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) _const_; - -bool is_clean_exit(int code, int status, ExitStatusSet *success_status); -bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status); - -void exit_status_set_free(ExitStatusSet *x); -bool exit_status_set_is_empty(ExitStatusSet *x); -bool exit_status_set_test(ExitStatusSet *x, int code, int status); diff --git a/src/basic/extract-word.c b/src/basic/extract-word.c deleted file mode 100644 index d6c1228463..0000000000 --- a/src/basic/extract-word.c +++ /dev/null @@ -1,298 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "escape.h" -#include "extract-word.h" -#include "log.h" -#include "macro.h" -#include "string-util.h" -#include "utf8.h" - -int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags) { - _cleanup_free_ char *s = NULL; - size_t allocated = 0, sz = 0; - char c; - int r; - - char quote = 0; /* 0 or ' or " */ - bool backslash = false; /* whether we've just seen a backslash */ - - assert(p); - assert(ret); - - /* Bail early if called after last value or with no input */ - if (!*p) - goto finish_force_terminate; - c = **p; - - if (!separators) - separators = WHITESPACE; - - /* Parses the first word of a string, and returns it in - * *ret. Removes all quotes in the process. When parsing fails - * (because of an uneven number of quotes or similar), leaves - * the pointer *p at the first invalid character. */ - - if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) - if (!GREEDY_REALLOC(s, allocated, sz+1)) - return -ENOMEM; - - for (;; (*p)++, c = **p) { - if (c == 0) - goto finish_force_terminate; - else if (strchr(separators, c)) { - if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) { - (*p)++; - goto finish_force_next; - } - } else { - /* We found a non-blank character, so we will always - * want to return a string (even if it is empty), - * allocate it here. */ - if (!GREEDY_REALLOC(s, allocated, sz+1)) - return -ENOMEM; - break; - } - } - - for (;; (*p)++, c = **p) { - if (backslash) { - if (!GREEDY_REALLOC(s, allocated, sz+7)) - return -ENOMEM; - - if (c == 0) { - if ((flags & EXTRACT_CUNESCAPE_RELAX) && - (!quote || flags & EXTRACT_RELAX)) { - /* If we find an unquoted trailing backslash and we're in - * EXTRACT_CUNESCAPE_RELAX mode, keep it verbatim in the - * output. - * - * Unbalanced quotes will only be allowed in EXTRACT_RELAX - * mode, EXTRACT_CUNESCAPE_RELAX mode does not allow them. - */ - s[sz++] = '\\'; - goto finish_force_terminate; - } - if (flags & EXTRACT_RELAX) - goto finish_force_terminate; - return -EINVAL; - } - - if (flags & EXTRACT_CUNESCAPE) { - bool eight_bit = false; - char32_t u; - - r = cunescape_one(*p, (size_t) -1, &u, &eight_bit); - if (r < 0) { - if (flags & EXTRACT_CUNESCAPE_RELAX) { - s[sz++] = '\\'; - s[sz++] = c; - } else - return -EINVAL; - } else { - (*p) += r - 1; - - if (eight_bit) - s[sz++] = u; - else - sz += utf8_encode_unichar(s + sz, u); - } - } else - s[sz++] = c; - - backslash = false; - - } else if (quote) { /* inside either single or double quotes */ - for (;; (*p)++, c = **p) { - if (c == 0) { - if (flags & EXTRACT_RELAX) - goto finish_force_terminate; - return -EINVAL; - } else if (c == quote) { /* found the end quote */ - quote = 0; - break; - } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) { - backslash = true; - break; - } else { - if (!GREEDY_REALLOC(s, allocated, sz+2)) - return -ENOMEM; - - s[sz++] = c; - } - } - - } else { - for (;; (*p)++, c = **p) { - if (c == 0) - goto finish_force_terminate; - else if ((c == '\'' || c == '"') && (flags & EXTRACT_QUOTES)) { - quote = c; - break; - } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) { - backslash = true; - break; - } else if (strchr(separators, c)) { - if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) { - (*p)++; - goto finish_force_next; - } - /* Skip additional coalesced separators. */ - for (;; (*p)++, c = **p) { - if (c == 0) - goto finish_force_terminate; - if (!strchr(separators, c)) - break; - } - goto finish; - - } else { - if (!GREEDY_REALLOC(s, allocated, sz+2)) - return -ENOMEM; - - s[sz++] = c; - } - } - } - } - -finish_force_terminate: - *p = NULL; -finish: - if (!s) { - *p = NULL; - *ret = NULL; - return 0; - } - -finish_force_next: - s[sz] = 0; - *ret = s; - s = NULL; - - return 1; -} - -int extract_first_word_and_warn( - const char **p, - char **ret, - const char *separators, - ExtractFlags flags, - const char *unit, - const char *filename, - unsigned line, - const char *rvalue) { - - /* Try to unquote it, if it fails, warn about it and try again - * but this time using EXTRACT_CUNESCAPE_RELAX to keep the - * backslashes verbatim in invalid escape sequences. */ - - const char *save; - int r; - - save = *p; - r = extract_first_word(p, ret, separators, flags); - if (r >= 0) - return r; - - if (r == -EINVAL && !(flags & EXTRACT_CUNESCAPE_RELAX)) { - - /* Retry it with EXTRACT_CUNESCAPE_RELAX. */ - *p = save; - r = extract_first_word(p, ret, separators, flags|EXTRACT_CUNESCAPE_RELAX); - if (r >= 0) { - /* It worked this time, hence it must have been an invalid escape sequence we could correct. */ - log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Invalid escape sequences in line, correcting: \"%s\"", rvalue); - return r; - } - - /* If it's still EINVAL; then it must be unbalanced quoting, report this. */ - if (r == -EINVAL) - return log_syntax(unit, LOG_ERR, filename, line, r, "Unbalanced quoting, ignoring: \"%s\"", rvalue); - } - - /* Can be any error, report it */ - return log_syntax(unit, LOG_ERR, filename, line, r, "Unable to decode word \"%s\", ignoring: %m", rvalue); -} - -int extract_many_words(const char **p, const char *separators, ExtractFlags flags, ...) { - va_list ap; - char **l; - int n = 0, i, c, r; - - /* Parses a number of words from a string, stripping any - * quotes if necessary. */ - - assert(p); - - /* Count how many words are expected */ - va_start(ap, flags); - for (;;) { - if (!va_arg(ap, char **)) - break; - n++; - } - va_end(ap); - - if (n <= 0) - return 0; - - /* Read all words into a temporary array */ - l = newa0(char*, n); - for (c = 0; c < n; c++) { - - r = extract_first_word(p, &l[c], separators, flags); - if (r < 0) { - int j; - - for (j = 0; j < c; j++) - free(l[j]); - - return r; - } - - if (r == 0) - break; - } - - /* If we managed to parse all words, return them in the passed - * in parameters */ - va_start(ap, flags); - for (i = 0; i < n; i++) { - char **v; - - v = va_arg(ap, char **); - assert(v); - - *v = l[i]; - } - va_end(ap); - - return c; -} diff --git a/src/basic/extract-word.h b/src/basic/extract-word.h deleted file mode 100644 index 21db5ef33f..0000000000 --- a/src/basic/extract-word.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "macro.h" - -typedef enum ExtractFlags { - EXTRACT_RELAX = 1, - EXTRACT_CUNESCAPE = 2, - EXTRACT_CUNESCAPE_RELAX = 4, - EXTRACT_QUOTES = 8, - EXTRACT_DONT_COALESCE_SEPARATORS = 16, - EXTRACT_RETAIN_ESCAPE = 32, -} ExtractFlags; - -int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags); -int extract_first_word_and_warn(const char **p, char **ret, const char *separators, ExtractFlags flags, const char *unit, const char *filename, unsigned line, const char *rvalue); -int extract_many_words(const char **p, const char *separators, ExtractFlags flags, ...) _sentinel_; diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c deleted file mode 100644 index 8b466cff15..0000000000 --- a/src/basic/fd-util.c +++ /dev/null @@ -1,374 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include - -#include "fd-util.h" -#include "fs-util.h" -#include "macro.h" -#include "missing.h" -#include "parse-util.h" -#include "path-util.h" -#include "socket-util.h" -#include "stdio-util.h" -#include "util.h" - -int close_nointr(int fd) { - assert(fd >= 0); - - if (close(fd) >= 0) - return 0; - - /* - * Just ignore EINTR; a retry loop is the wrong thing to do on - * Linux. - * - * http://lkml.indiana.edu/hypermail/linux/kernel/0509.1/0877.html - * https://bugzilla.gnome.org/show_bug.cgi?id=682819 - * http://utcc.utoronto.ca/~cks/space/blog/unix/CloseEINTR - * https://sites.google.com/site/michaelsafyan/software-engineering/checkforeintrwheninvokingclosethinkagain - */ - if (errno == EINTR) - return 0; - - return -errno; -} - -int safe_close(int fd) { - - /* - * Like close_nointr() but cannot fail. Guarantees errno is - * unchanged. Is a NOP with negative fds passed, and returns - * -1, so that it can be used in this syntax: - * - * fd = safe_close(fd); - */ - - if (fd >= 0) { - PROTECT_ERRNO; - - /* The kernel might return pretty much any error code - * via close(), but the fd will be closed anyway. The - * only condition we want to check for here is whether - * the fd was invalid at all... */ - - assert_se(close_nointr(fd) != -EBADF); - } - - return -1; -} - -void safe_close_pair(int p[]) { - assert(p); - - if (p[0] == p[1]) { - /* Special case pairs which use the same fd in both - * directions... */ - p[0] = p[1] = safe_close(p[0]); - return; - } - - p[0] = safe_close(p[0]); - p[1] = safe_close(p[1]); -} - -void close_many(const int fds[], unsigned n_fd) { - unsigned i; - - assert(fds || n_fd <= 0); - - for (i = 0; i < n_fd; i++) - safe_close(fds[i]); -} - -int fclose_nointr(FILE *f) { - assert(f); - - /* Same as close_nointr(), but for fclose() */ - - if (fclose(f) == 0) - return 0; - - if (errno == EINTR) - return 0; - - return -errno; -} - -FILE* safe_fclose(FILE *f) { - - /* Same as safe_close(), but for fclose() */ - - if (f) { - PROTECT_ERRNO; - - assert_se(fclose_nointr(f) != EBADF); - } - - return NULL; -} - -DIR* safe_closedir(DIR *d) { - - if (d) { - PROTECT_ERRNO; - - assert_se(closedir(d) >= 0 || errno != EBADF); - } - - return NULL; -} - -int fd_nonblock(int fd, bool nonblock) { - int flags, nflags; - - assert(fd >= 0); - - flags = fcntl(fd, F_GETFL, 0); - if (flags < 0) - return -errno; - - if (nonblock) - nflags = flags | O_NONBLOCK; - else - nflags = flags & ~O_NONBLOCK; - - if (nflags == flags) - return 0; - - if (fcntl(fd, F_SETFL, nflags) < 0) - return -errno; - - return 0; -} - -int fd_cloexec(int fd, bool cloexec) { - int flags, nflags; - - assert(fd >= 0); - - flags = fcntl(fd, F_GETFD, 0); - if (flags < 0) - return -errno; - - if (cloexec) - nflags = flags | FD_CLOEXEC; - else - nflags = flags & ~FD_CLOEXEC; - - if (nflags == flags) - return 0; - - if (fcntl(fd, F_SETFD, nflags) < 0) - return -errno; - - return 0; -} - -_pure_ static bool fd_in_set(int fd, const int fdset[], unsigned n_fdset) { - unsigned i; - - assert(n_fdset == 0 || fdset); - - for (i = 0; i < n_fdset; i++) - if (fdset[i] == fd) - return true; - - return false; -} - -int close_all_fds(const int except[], unsigned n_except) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - int r = 0; - - assert(n_except == 0 || except); - - d = opendir("/proc/self/fd"); - if (!d) { - int fd; - struct rlimit rl; - - /* When /proc isn't available (for example in chroots) - * the fallback is brute forcing through the fd - * table */ - - assert_se(getrlimit(RLIMIT_NOFILE, &rl) >= 0); - for (fd = 3; fd < (int) rl.rlim_max; fd ++) { - - if (fd_in_set(fd, except, n_except)) - continue; - - if (close_nointr(fd) < 0) - if (errno != EBADF && r == 0) - r = -errno; - } - - return r; - } - - while ((de = readdir(d))) { - int fd = -1; - - if (hidden_or_backup_file(de->d_name)) - continue; - - if (safe_atoi(de->d_name, &fd) < 0) - /* Let's better ignore this, just in case */ - continue; - - if (fd < 3) - continue; - - if (fd == dirfd(d)) - continue; - - if (fd_in_set(fd, except, n_except)) - continue; - - if (close_nointr(fd) < 0) { - /* Valgrind has its own FD and doesn't want to have it closed */ - if (errno != EBADF && r == 0) - r = -errno; - } - } - - return r; -} - -int same_fd(int a, int b) { - struct stat sta, stb; - pid_t pid; - int r, fa, fb; - - assert(a >= 0); - assert(b >= 0); - - /* Compares two file descriptors. Note that semantics are - * quite different depending on whether we have kcmp() or we - * don't. If we have kcmp() this will only return true for - * dup()ed file descriptors, but not otherwise. If we don't - * have kcmp() this will also return true for two fds of the same - * file, created by separate open() calls. Since we use this - * call mostly for filtering out duplicates in the fd store - * this difference hopefully doesn't matter too much. */ - - if (a == b) - return true; - - /* Try to use kcmp() if we have it. */ - pid = getpid(); - r = kcmp(pid, pid, KCMP_FILE, a, b); - if (r == 0) - return true; - if (r > 0) - return false; - if (errno != ENOSYS) - return -errno; - - /* We don't have kcmp(), use fstat() instead. */ - if (fstat(a, &sta) < 0) - return -errno; - - if (fstat(b, &stb) < 0) - return -errno; - - if ((sta.st_mode & S_IFMT) != (stb.st_mode & S_IFMT)) - return false; - - /* We consider all device fds different, since two device fds - * might refer to quite different device contexts even though - * they share the same inode and backing dev_t. */ - - if (S_ISCHR(sta.st_mode) || S_ISBLK(sta.st_mode)) - return false; - - if (sta.st_dev != stb.st_dev || sta.st_ino != stb.st_ino) - return false; - - /* The fds refer to the same inode on disk, let's also check - * if they have the same fd flags. This is useful to - * distinguish the read and write side of a pipe created with - * pipe(). */ - fa = fcntl(a, F_GETFL); - if (fa < 0) - return -errno; - - fb = fcntl(b, F_GETFL); - if (fb < 0) - return -errno; - - return fa == fb; -} - -void cmsg_close_all(struct msghdr *mh) { - struct cmsghdr *cmsg; - - assert(mh); - - CMSG_FOREACH(cmsg, mh) - if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) - close_many((int*) CMSG_DATA(cmsg), (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int)); -} - -bool fdname_is_valid(const char *s) { - const char *p; - - /* Validates a name for $LISTEN_FDNAMES. We basically allow - * everything ASCII that's not a control character. Also, as - * special exception the ":" character is not allowed, as we - * use that as field separator in $LISTEN_FDNAMES. - * - * Note that the empty string is explicitly allowed - * here. However, we limit the length of the names to 255 - * characters. */ - - if (!s) - return false; - - for (p = s; *p; p++) { - if (*p < ' ') - return false; - if (*p >= 127) - return false; - if (*p == ':') - return false; - } - - return p - s < 256; -} - -int fd_get_path(int fd, char **ret) { - char procfs_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; - int r; - - xsprintf(procfs_path, "/proc/self/fd/%i", fd); - - r = readlink_malloc(procfs_path, ret); - - if (r == -ENOENT) /* If the file doesn't exist the fd is invalid */ - return -EBADF; - - return r; -} diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h deleted file mode 100644 index b86e41698a..0000000000 --- a/src/basic/fd-util.h +++ /dev/null @@ -1,79 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include - -#include "macro.h" - -/* Make sure we can distinguish fd 0 and NULL */ -#define FD_TO_PTR(fd) INT_TO_PTR((fd)+1) -#define PTR_TO_FD(p) (PTR_TO_INT(p)-1) - -int close_nointr(int fd); -int safe_close(int fd); -void safe_close_pair(int p[]); - -void close_many(const int fds[], unsigned n_fd); - -int fclose_nointr(FILE *f); -FILE* safe_fclose(FILE *f); -DIR* safe_closedir(DIR *f); - -static inline void closep(int *fd) { - safe_close(*fd); -} - -static inline void close_pairp(int (*p)[2]) { - safe_close_pair(*p); -} - -static inline void fclosep(FILE **f) { - safe_fclose(*f); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, pclose); -DEFINE_TRIVIAL_CLEANUP_FUNC(DIR*, closedir); - -#define _cleanup_close_ _cleanup_(closep) -#define _cleanup_fclose_ _cleanup_(fclosep) -#define _cleanup_pclose_ _cleanup_(pclosep) -#define _cleanup_closedir_ _cleanup_(closedirp) -#define _cleanup_close_pair_ _cleanup_(close_pairp) - -int fd_nonblock(int fd, bool nonblock); -int fd_cloexec(int fd, bool cloexec); - -int close_all_fds(const int except[], unsigned n_except); - -int same_fd(int a, int b); - -void cmsg_close_all(struct msghdr *mh); - -bool fdname_is_valid(const char *s); - -int fd_get_path(int fd, char **ret); - -/* Hint: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5 */ -#define ERRNO_IS_DISCONNECT(r) \ - IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, ENETUNREACH) diff --git a/src/basic/fdset.c b/src/basic/fdset.c deleted file mode 100644 index 527f27bc67..0000000000 --- a/src/basic/fdset.c +++ /dev/null @@ -1,273 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include - -#include "sd-daemon.h" - -#include "fd-util.h" -#include "fdset.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "path-util.h" -#include "set.h" - -#define MAKE_SET(s) ((Set*) s) -#define MAKE_FDSET(s) ((FDSet*) s) - -FDSet *fdset_new(void) { - return MAKE_FDSET(set_new(NULL)); -} - -int fdset_new_array(FDSet **ret, const int *fds, unsigned n_fds) { - unsigned i; - FDSet *s; - int r; - - assert(ret); - - s = fdset_new(); - if (!s) - return -ENOMEM; - - for (i = 0; i < n_fds; i++) { - - r = fdset_put(s, fds[i]); - if (r < 0) { - set_free(MAKE_SET(s)); - return r; - } - } - - *ret = s; - return 0; -} - -FDSet* fdset_free(FDSet *s) { - void *p; - - while ((p = set_steal_first(MAKE_SET(s)))) { - /* Valgrind's fd might have ended up in this set here, - * due to fdset_new_fill(). We'll ignore all failures - * here, so that the EBADFD that valgrind will return - * us on close() doesn't influence us */ - - /* When reloading duplicates of the private bus - * connection fds and suchlike are closed here, which - * has no effect at all, since they are only - * duplicates. So don't be surprised about these log - * messages. */ - - log_debug("Closing left-over fd %i", PTR_TO_FD(p)); - close_nointr(PTR_TO_FD(p)); - } - - set_free(MAKE_SET(s)); - return NULL; -} - -int fdset_put(FDSet *s, int fd) { - assert(s); - assert(fd >= 0); - - return set_put(MAKE_SET(s), FD_TO_PTR(fd)); -} - -int fdset_put_dup(FDSet *s, int fd) { - int copy, r; - - assert(s); - assert(fd >= 0); - - copy = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (copy < 0) - return -errno; - - r = fdset_put(s, copy); - if (r < 0) { - safe_close(copy); - return r; - } - - return copy; -} - -bool fdset_contains(FDSet *s, int fd) { - assert(s); - assert(fd >= 0); - - return !!set_get(MAKE_SET(s), FD_TO_PTR(fd)); -} - -int fdset_remove(FDSet *s, int fd) { - assert(s); - assert(fd >= 0); - - return set_remove(MAKE_SET(s), FD_TO_PTR(fd)) ? fd : -ENOENT; -} - -int fdset_new_fill(FDSet **_s) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - int r = 0; - FDSet *s; - - assert(_s); - - /* Creates an fdset and fills in all currently open file - * descriptors. */ - - d = opendir("/proc/self/fd"); - if (!d) - return -errno; - - s = fdset_new(); - if (!s) { - r = -ENOMEM; - goto finish; - } - - while ((de = readdir(d))) { - int fd = -1; - - if (hidden_or_backup_file(de->d_name)) - continue; - - r = safe_atoi(de->d_name, &fd); - if (r < 0) - goto finish; - - if (fd < 3) - continue; - - if (fd == dirfd(d)) - continue; - - r = fdset_put(s, fd); - if (r < 0) - goto finish; - } - - r = 0; - *_s = s; - s = NULL; - -finish: - /* We won't close the fds here! */ - if (s) - set_free(MAKE_SET(s)); - - return r; -} - -int fdset_cloexec(FDSet *fds, bool b) { - Iterator i; - void *p; - int r; - - assert(fds); - - SET_FOREACH(p, MAKE_SET(fds), i) { - r = fd_cloexec(PTR_TO_FD(p), b); - if (r < 0) - return r; - } - - return 0; -} - -int fdset_new_listen_fds(FDSet **_s, bool unset) { - int n, fd, r; - FDSet *s; - - assert(_s); - - /* Creates an fdset and fills in all passed file descriptors */ - - s = fdset_new(); - if (!s) { - r = -ENOMEM; - goto fail; - } - - n = sd_listen_fds(unset); - for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) { - r = fdset_put(s, fd); - if (r < 0) - goto fail; - } - - *_s = s; - return 0; - - -fail: - if (s) - set_free(MAKE_SET(s)); - - return r; -} - -int fdset_close_others(FDSet *fds) { - void *e; - Iterator i; - int *a; - unsigned j, m; - - j = 0, m = fdset_size(fds); - a = alloca(sizeof(int) * m); - SET_FOREACH(e, MAKE_SET(fds), i) - a[j++] = PTR_TO_FD(e); - - assert(j == m); - - return close_all_fds(a, j); -} - -unsigned fdset_size(FDSet *fds) { - return set_size(MAKE_SET(fds)); -} - -bool fdset_isempty(FDSet *fds) { - return set_isempty(MAKE_SET(fds)); -} - -int fdset_iterate(FDSet *s, Iterator *i) { - void *p; - - if (!set_iterate(MAKE_SET(s), i, &p)) - return -ENOENT; - - return PTR_TO_FD(p); -} - -int fdset_steal_first(FDSet *fds) { - void *p; - - p = set_steal_first(MAKE_SET(fds)); - if (!p) - return -ENOENT; - - return PTR_TO_FD(p); -} diff --git a/src/basic/fdset.h b/src/basic/fdset.h deleted file mode 100644 index 16efe5bdf2..0000000000 --- a/src/basic/fdset.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "hashmap.h" -#include "macro.h" -#include "set.h" - -typedef struct FDSet FDSet; - -FDSet* fdset_new(void); -FDSet* fdset_free(FDSet *s); - -int fdset_put(FDSet *s, int fd); -int fdset_put_dup(FDSet *s, int fd); - -bool fdset_contains(FDSet *s, int fd); -int fdset_remove(FDSet *s, int fd); - -int fdset_new_array(FDSet **ret, const int *fds, unsigned n_fds); -int fdset_new_fill(FDSet **ret); -int fdset_new_listen_fds(FDSet **ret, bool unset); - -int fdset_cloexec(FDSet *fds, bool b); - -int fdset_close_others(FDSet *fds); - -unsigned fdset_size(FDSet *fds); -bool fdset_isempty(FDSet *fds); - -int fdset_iterate(FDSet *s, Iterator *i); - -int fdset_steal_first(FDSet *fds); - -#define FDSET_FOREACH(fd, fds, i) \ - for ((i) = ITERATOR_FIRST, (fd) = fdset_iterate((fds), &(i)); (fd) >= 0; (fd) = fdset_iterate((fds), &(i))) - -DEFINE_TRIVIAL_CLEANUP_FUNC(FDSet*, fdset_free); -#define _cleanup_fdset_free_ _cleanup_(fdset_freep) diff --git a/src/basic/fileio-label.c b/src/basic/fileio-label.c deleted file mode 100644 index 66dbc0fe1e..0000000000 --- a/src/basic/fileio-label.c +++ /dev/null @@ -1,68 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2010 Harald Hoyer - - 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 "fileio-label.h" -#include "fileio.h" -#include "selinux-util.h" - -int write_string_file_atomic_label(const char *fn, const char *line) { - int r; - - r = mac_selinux_create_file_prepare(fn, S_IFREG); - if (r < 0) - return r; - - r = write_string_file(fn, line, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); - - mac_selinux_create_file_clear(); - - return r; -} - -int write_env_file_label(const char *fname, char **l) { - int r; - - r = mac_selinux_create_file_prepare(fname, S_IFREG); - if (r < 0) - return r; - - r = write_env_file(fname, l); - - mac_selinux_create_file_clear(); - - return r; -} - -int fopen_temporary_label(const char *target, - const char *path, FILE **f, char **temp_path) { - int r; - - r = mac_selinux_create_file_prepare(target, S_IFREG); - if (r < 0) - return r; - - r = fopen_temporary(path, f, temp_path); - - mac_selinux_create_file_clear(); - - return r; -} diff --git a/src/basic/fileio-label.h b/src/basic/fileio-label.h deleted file mode 100644 index fe7543013d..0000000000 --- a/src/basic/fileio-label.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2010 Harald Hoyer - - 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 "fileio.h" - -int write_string_file_atomic_label(const char *fn, const char *line); -int write_env_file_label(const char *fname, char **l); -int fopen_temporary_label(const char *target, - const char *path, FILE **f, char **temp_path); diff --git a/src/basic/fileio.c b/src/basic/fileio.c deleted file mode 100644 index 29f5374222..0000000000 --- a/src/basic/fileio.c +++ /dev/null @@ -1,1356 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "ctype.h" -#include "escape.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "hexdecoct.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "path-util.h" -#include "random-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" -#include "umask-util.h" -#include "utf8.h" - -int write_string_stream(FILE *f, const char *line, bool enforce_newline) { - - assert(f); - assert(line); - - fputs(line, f); - if (enforce_newline && !endswith(line, "\n")) - fputc('\n', f); - - return fflush_and_check(f); -} - -static int write_string_file_atomic(const char *fn, const char *line, bool enforce_newline) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *p = NULL; - int r; - - assert(fn); - assert(line); - - r = fopen_temporary(fn, &f, &p); - if (r < 0) - return r; - - (void) fchmod_umask(fileno(f), 0644); - - r = write_string_stream(f, line, enforce_newline); - if (r >= 0) { - if (rename(p, fn) < 0) - r = -errno; - } - - if (r < 0) - (void) unlink(p); - - return r; -} - -int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) { - _cleanup_fclose_ FILE *f = NULL; - int q, r; - - assert(fn); - assert(line); - - if (flags & WRITE_STRING_FILE_ATOMIC) { - assert(flags & WRITE_STRING_FILE_CREATE); - - r = write_string_file_atomic(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE)); - if (r < 0) - goto fail; - - return r; - } - - if (flags & WRITE_STRING_FILE_CREATE) { - f = fopen(fn, "we"); - if (!f) { - r = -errno; - goto fail; - } - } else { - int fd; - - /* We manually build our own version of fopen(..., "we") that - * works without O_CREAT */ - fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) { - r = -errno; - goto fail; - } - - f = fdopen(fd, "we"); - if (!f) { - r = -errno; - safe_close(fd); - goto fail; - } - } - - r = write_string_stream(f, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE)); - if (r < 0) - goto fail; - - return 0; - -fail: - if (!(flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE)) - return r; - - f = safe_fclose(f); - - /* OK, the operation failed, but let's see if the right - * contents in place already. If so, eat up the error. */ - - q = verify_file(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE)); - if (q <= 0) - return r; - - return 0; -} - -int read_one_line_file(const char *fn, char **line) { - _cleanup_fclose_ FILE *f = NULL; - char t[LINE_MAX], *c; - - assert(fn); - assert(line); - - f = fopen(fn, "re"); - if (!f) - return -errno; - - if (!fgets(t, sizeof(t), f)) { - - if (ferror(f)) - return errno > 0 ? -errno : -EIO; - - t[0] = 0; - } - - c = strdup(t); - if (!c) - return -ENOMEM; - truncate_nl(c); - - *line = c; - return 0; -} - -int verify_file(const char *fn, const char *blob, bool accept_extra_nl) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *buf = NULL; - size_t l, k; - - assert(fn); - assert(blob); - - l = strlen(blob); - - if (accept_extra_nl && endswith(blob, "\n")) - accept_extra_nl = false; - - buf = malloc(l + accept_extra_nl + 1); - if (!buf) - return -ENOMEM; - - f = fopen(fn, "re"); - if (!f) - return -errno; - - /* We try to read one byte more than we need, so that we know whether we hit eof */ - errno = 0; - k = fread(buf, 1, l + accept_extra_nl + 1, f); - if (ferror(f)) - return errno > 0 ? -errno : -EIO; - - if (k != l && k != l + accept_extra_nl) - return 0; - if (memcmp(buf, blob, l) != 0) - return 0; - if (k > l && buf[l] != '\n') - return 0; - - return 1; -} - -int read_full_stream(FILE *f, char **contents, size_t *size) { - size_t n, l; - _cleanup_free_ char *buf = NULL; - struct stat st; - - assert(f); - assert(contents); - - if (fstat(fileno(f), &st) < 0) - return -errno; - - n = LINE_MAX; - - if (S_ISREG(st.st_mode)) { - - /* Safety check */ - if (st.st_size > 4*1024*1024) - return -E2BIG; - - /* Start with the right file size, but be prepared for - * files from /proc which generally report a file size - * of 0 */ - if (st.st_size > 0) - n = st.st_size; - } - - l = 0; - for (;;) { - char *t; - size_t k; - - t = realloc(buf, n+1); - if (!t) - return -ENOMEM; - - buf = t; - k = fread(buf + l, 1, n - l, f); - - if (k <= 0) { - if (ferror(f)) - return -errno; - - break; - } - - l += k; - n *= 2; - - /* Safety check */ - if (n > 4*1024*1024) - return -E2BIG; - } - - buf[l] = 0; - *contents = buf; - buf = NULL; /* do not free */ - - if (size) - *size = l; - - return 0; -} - -int read_full_file(const char *fn, char **contents, size_t *size) { - _cleanup_fclose_ FILE *f = NULL; - - assert(fn); - assert(contents); - - f = fopen(fn, "re"); - if (!f) - return -errno; - - return read_full_stream(f, contents, size); -} - -static int parse_env_file_internal( - FILE *f, - const char *fname, - const char *newline, - int (*push) (const char *filename, unsigned line, - const char *key, char *value, void *userdata, int *n_pushed), - void *userdata, - int *n_pushed) { - - _cleanup_free_ char *contents = NULL, *key = NULL; - size_t key_alloc = 0, n_key = 0, value_alloc = 0, n_value = 0, last_value_whitespace = (size_t) -1, last_key_whitespace = (size_t) -1; - char *p, *value = NULL; - int r; - unsigned line = 1; - - enum { - PRE_KEY, - KEY, - PRE_VALUE, - VALUE, - VALUE_ESCAPE, - SINGLE_QUOTE_VALUE, - SINGLE_QUOTE_VALUE_ESCAPE, - DOUBLE_QUOTE_VALUE, - DOUBLE_QUOTE_VALUE_ESCAPE, - COMMENT, - COMMENT_ESCAPE - } state = PRE_KEY; - - assert(newline); - - if (f) - r = read_full_stream(f, &contents, NULL); - else - r = read_full_file(fname, &contents, NULL); - if (r < 0) - return r; - - for (p = contents; *p; p++) { - char c = *p; - - switch (state) { - - case PRE_KEY: - if (strchr(COMMENTS, c)) - state = COMMENT; - else if (!strchr(WHITESPACE, c)) { - state = KEY; - last_key_whitespace = (size_t) -1; - - if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) { - r = -ENOMEM; - goto fail; - } - - key[n_key++] = c; - } - break; - - case KEY: - if (strchr(newline, c)) { - state = PRE_KEY; - line++; - n_key = 0; - } else if (c == '=') { - state = PRE_VALUE; - last_value_whitespace = (size_t) -1; - } else { - if (!strchr(WHITESPACE, c)) - last_key_whitespace = (size_t) -1; - else if (last_key_whitespace == (size_t) -1) - last_key_whitespace = n_key; - - if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) { - r = -ENOMEM; - goto fail; - } - - key[n_key++] = c; - } - - break; - - case PRE_VALUE: - if (strchr(newline, c)) { - state = PRE_KEY; - line++; - key[n_key] = 0; - - if (value) - value[n_value] = 0; - - /* strip trailing whitespace from key */ - if (last_key_whitespace != (size_t) -1) - key[last_key_whitespace] = 0; - - r = push(fname, line, key, value, userdata, n_pushed); - if (r < 0) - goto fail; - - n_key = 0; - value = NULL; - value_alloc = n_value = 0; - - } else if (c == '\'') - state = SINGLE_QUOTE_VALUE; - else if (c == '\"') - state = DOUBLE_QUOTE_VALUE; - else if (c == '\\') - state = VALUE_ESCAPE; - else if (!strchr(WHITESPACE, c)) { - state = VALUE; - - if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { - r = -ENOMEM; - goto fail; - } - - value[n_value++] = c; - } - - break; - - case VALUE: - if (strchr(newline, c)) { - state = PRE_KEY; - line++; - - key[n_key] = 0; - - if (value) - value[n_value] = 0; - - /* Chomp off trailing whitespace from value */ - if (last_value_whitespace != (size_t) -1) - value[last_value_whitespace] = 0; - - /* strip trailing whitespace from key */ - if (last_key_whitespace != (size_t) -1) - key[last_key_whitespace] = 0; - - r = push(fname, line, key, value, userdata, n_pushed); - if (r < 0) - goto fail; - - n_key = 0; - value = NULL; - value_alloc = n_value = 0; - - } else if (c == '\\') { - state = VALUE_ESCAPE; - last_value_whitespace = (size_t) -1; - } else { - if (!strchr(WHITESPACE, c)) - last_value_whitespace = (size_t) -1; - else if (last_value_whitespace == (size_t) -1) - last_value_whitespace = n_value; - - if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { - r = -ENOMEM; - goto fail; - } - - value[n_value++] = c; - } - - break; - - case VALUE_ESCAPE: - state = VALUE; - - if (!strchr(newline, c)) { - /* Escaped newlines we eat up entirely */ - if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { - r = -ENOMEM; - goto fail; - } - - value[n_value++] = c; - } - break; - - case SINGLE_QUOTE_VALUE: - if (c == '\'') - state = PRE_VALUE; - else if (c == '\\') - state = SINGLE_QUOTE_VALUE_ESCAPE; - else { - if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { - r = -ENOMEM; - goto fail; - } - - value[n_value++] = c; - } - - break; - - case SINGLE_QUOTE_VALUE_ESCAPE: - state = SINGLE_QUOTE_VALUE; - - if (!strchr(newline, c)) { - if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { - r = -ENOMEM; - goto fail; - } - - value[n_value++] = c; - } - break; - - case DOUBLE_QUOTE_VALUE: - if (c == '\"') - state = PRE_VALUE; - else if (c == '\\') - state = DOUBLE_QUOTE_VALUE_ESCAPE; - else { - if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { - r = -ENOMEM; - goto fail; - } - - value[n_value++] = c; - } - - break; - - case DOUBLE_QUOTE_VALUE_ESCAPE: - state = DOUBLE_QUOTE_VALUE; - - if (!strchr(newline, c)) { - if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { - r = -ENOMEM; - goto fail; - } - - value[n_value++] = c; - } - break; - - case COMMENT: - if (c == '\\') - state = COMMENT_ESCAPE; - else if (strchr(newline, c)) { - state = PRE_KEY; - line++; - } - break; - - case COMMENT_ESCAPE: - state = COMMENT; - break; - } - } - - if (state == PRE_VALUE || - state == VALUE || - state == VALUE_ESCAPE || - state == SINGLE_QUOTE_VALUE || - state == SINGLE_QUOTE_VALUE_ESCAPE || - state == DOUBLE_QUOTE_VALUE || - state == DOUBLE_QUOTE_VALUE_ESCAPE) { - - key[n_key] = 0; - - if (value) - value[n_value] = 0; - - if (state == VALUE) - if (last_value_whitespace != (size_t) -1) - value[last_value_whitespace] = 0; - - /* strip trailing whitespace from key */ - if (last_key_whitespace != (size_t) -1) - key[last_key_whitespace] = 0; - - r = push(fname, line, key, value, userdata, n_pushed); - if (r < 0) - goto fail; - } - - return 0; - -fail: - free(value); - return r; -} - -static int parse_env_file_push( - const char *filename, unsigned line, - const char *key, char *value, - void *userdata, - int *n_pushed) { - - const char *k; - va_list aq, *ap = userdata; - - if (!utf8_is_valid(key)) { - _cleanup_free_ char *p = NULL; - - p = utf8_escape_invalid(key); - log_error("%s:%u: invalid UTF-8 in key '%s', ignoring.", strna(filename), line, p); - return -EINVAL; - } - - if (value && !utf8_is_valid(value)) { - _cleanup_free_ char *p = NULL; - - p = utf8_escape_invalid(value); - log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, p); - return -EINVAL; - } - - va_copy(aq, *ap); - - while ((k = va_arg(aq, const char *))) { - char **v; - - v = va_arg(aq, char **); - - if (streq(key, k)) { - va_end(aq); - free(*v); - *v = value; - - if (n_pushed) - (*n_pushed)++; - - return 1; - } - } - - va_end(aq); - free(value); - - return 0; -} - -int parse_env_file( - const char *fname, - const char *newline, ...) { - - va_list ap; - int r, n_pushed = 0; - - if (!newline) - newline = NEWLINE; - - va_start(ap, newline); - r = parse_env_file_internal(NULL, fname, newline, parse_env_file_push, &ap, &n_pushed); - va_end(ap); - - return r < 0 ? r : n_pushed; -} - -static int load_env_file_push( - const char *filename, unsigned line, - const char *key, char *value, - void *userdata, - int *n_pushed) { - char ***m = userdata; - char *p; - int r; - - if (!utf8_is_valid(key)) { - _cleanup_free_ char *t = utf8_escape_invalid(key); - - log_error("%s:%u: invalid UTF-8 for key '%s', ignoring.", strna(filename), line, t); - return -EINVAL; - } - - if (value && !utf8_is_valid(value)) { - _cleanup_free_ char *t = utf8_escape_invalid(value); - - log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, t); - return -EINVAL; - } - - p = strjoin(key, "=", strempty(value), NULL); - if (!p) - return -ENOMEM; - - r = strv_consume(m, p); - if (r < 0) - return r; - - if (n_pushed) - (*n_pushed)++; - - free(value); - return 0; -} - -int load_env_file(FILE *f, const char *fname, const char *newline, char ***rl) { - char **m = NULL; - int r; - - if (!newline) - newline = NEWLINE; - - r = parse_env_file_internal(f, fname, newline, load_env_file_push, &m, NULL); - if (r < 0) { - strv_free(m); - return r; - } - - *rl = m; - return 0; -} - -static int load_env_file_push_pairs( - const char *filename, unsigned line, - const char *key, char *value, - void *userdata, - int *n_pushed) { - char ***m = userdata; - int r; - - if (!utf8_is_valid(key)) { - _cleanup_free_ char *t = utf8_escape_invalid(key); - - log_error("%s:%u: invalid UTF-8 for key '%s', ignoring.", strna(filename), line, t); - return -EINVAL; - } - - if (value && !utf8_is_valid(value)) { - _cleanup_free_ char *t = utf8_escape_invalid(value); - - log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, t); - return -EINVAL; - } - - r = strv_extend(m, key); - if (r < 0) - return -ENOMEM; - - if (!value) { - r = strv_extend(m, ""); - if (r < 0) - return -ENOMEM; - } else { - r = strv_push(m, value); - if (r < 0) - return r; - } - - if (n_pushed) - (*n_pushed)++; - - return 0; -} - -int load_env_file_pairs(FILE *f, const char *fname, const char *newline, char ***rl) { - char **m = NULL; - int r; - - if (!newline) - newline = NEWLINE; - - r = parse_env_file_internal(f, fname, newline, load_env_file_push_pairs, &m, NULL); - if (r < 0) { - strv_free(m); - return r; - } - - *rl = m; - return 0; -} - -static void write_env_var(FILE *f, const char *v) { - const char *p; - - p = strchr(v, '='); - if (!p) { - /* Fallback */ - fputs(v, f); - fputc('\n', f); - return; - } - - p++; - fwrite(v, 1, p-v, f); - - if (string_has_cc(p, NULL) || chars_intersect(p, WHITESPACE SHELL_NEED_QUOTES)) { - fputc('\"', f); - - for (; *p; p++) { - if (strchr(SHELL_NEED_ESCAPE, *p)) - fputc('\\', f); - - fputc(*p, f); - } - - fputc('\"', f); - } else - fputs(p, f); - - fputc('\n', f); -} - -int write_env_file(const char *fname, char **l) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *p = NULL; - char **i; - int r; - - assert(fname); - - r = fopen_temporary(fname, &f, &p); - if (r < 0) - return r; - - fchmod_umask(fileno(f), 0644); - - STRV_FOREACH(i, l) - write_env_var(f, *i); - - r = fflush_and_check(f); - if (r >= 0) { - if (rename(p, fname) >= 0) - return 0; - - r = -errno; - } - - unlink(p); - return r; -} - -int executable_is_script(const char *path, char **interpreter) { - int r; - _cleanup_free_ char *line = NULL; - int len; - char *ans; - - assert(path); - - r = read_one_line_file(path, &line); - if (r < 0) - return r; - - if (!startswith(line, "#!")) - return 0; - - ans = strstrip(line + 2); - len = strcspn(ans, " \t"); - - if (len == 0) - return 0; - - ans = strndup(ans, len); - if (!ans) - return -ENOMEM; - - *interpreter = ans; - return 1; -} - -/** - * Retrieve one field from a file like /proc/self/status. pattern - * should not include whitespace or the delimiter (':'). pattern matches only - * the beginning of a line. Whitespace before ':' is skipped. Whitespace and - * zeros after the ':' will be skipped. field must be freed afterwards. - * terminator specifies the terminating characters of the field value (not - * included in the value). - */ -int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field) { - _cleanup_free_ char *status = NULL; - char *t, *f; - size_t len; - int r; - - assert(terminator); - assert(filename); - assert(pattern); - assert(field); - - r = read_full_file(filename, &status, NULL); - if (r < 0) - return r; - - t = status; - - do { - bool pattern_ok; - - do { - t = strstr(t, pattern); - if (!t) - return -ENOENT; - - /* Check that pattern occurs in beginning of line. */ - pattern_ok = (t == status || t[-1] == '\n'); - - t += strlen(pattern); - - } while (!pattern_ok); - - t += strspn(t, " \t"); - if (!*t) - return -ENOENT; - - } while (*t != ':'); - - t++; - - if (*t) { - t += strspn(t, " \t"); - - /* Also skip zeros, because when this is used for - * capabilities, we don't want the zeros. This way the - * same capability set always maps to the same string, - * irrespective of the total capability set size. For - * other numbers it shouldn't matter. */ - t += strspn(t, "0"); - /* Back off one char if there's nothing but whitespace - and zeros */ - if (!*t || isspace(*t)) - t--; - } - - len = strcspn(t, terminator); - - f = strndup(t, len); - if (!f) - return -ENOMEM; - - *field = f; - return 0; -} - -DIR *xopendirat(int fd, const char *name, int flags) { - int nfd; - DIR *d; - - assert(!(flags & O_CREAT)); - - nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags, 0); - if (nfd < 0) - return NULL; - - d = fdopendir(nfd); - if (!d) { - safe_close(nfd); - return NULL; - } - - return d; -} - -static int search_and_fopen_internal(const char *path, const char *mode, const char *root, char **search, FILE **_f) { - char **i; - - assert(path); - assert(mode); - assert(_f); - - if (!path_strv_resolve_uniq(search, root)) - return -ENOMEM; - - STRV_FOREACH(i, search) { - _cleanup_free_ char *p = NULL; - FILE *f; - - if (root) - p = strjoin(root, *i, "/", path, NULL); - else - p = strjoin(*i, "/", path, NULL); - if (!p) - return -ENOMEM; - - f = fopen(p, mode); - if (f) { - *_f = f; - return 0; - } - - if (errno != ENOENT) - return -errno; - } - - return -ENOENT; -} - -int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **_f) { - _cleanup_strv_free_ char **copy = NULL; - - assert(path); - assert(mode); - assert(_f); - - if (path_is_absolute(path)) { - FILE *f; - - f = fopen(path, mode); - if (f) { - *_f = f; - return 0; - } - - return -errno; - } - - copy = strv_copy((char**) search); - if (!copy) - return -ENOMEM; - - return search_and_fopen_internal(path, mode, root, copy, _f); -} - -int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **_f) { - _cleanup_strv_free_ char **s = NULL; - - if (path_is_absolute(path)) { - FILE *f; - - f = fopen(path, mode); - if (f) { - *_f = f; - return 0; - } - - return -errno; - } - - s = strv_split_nulstr(search); - if (!s) - return -ENOMEM; - - return search_and_fopen_internal(path, mode, root, s, _f); -} - -int fopen_temporary(const char *path, FILE **_f, char **_temp_path) { - FILE *f; - char *t; - int r, fd; - - assert(path); - assert(_f); - assert(_temp_path); - - r = tempfn_xxxxxx(path, NULL, &t); - if (r < 0) - return r; - - fd = mkostemp_safe(t, O_WRONLY|O_CLOEXEC); - if (fd < 0) { - free(t); - return -errno; - } - - f = fdopen(fd, "we"); - if (!f) { - unlink_noerrno(t); - free(t); - safe_close(fd); - return -errno; - } - - *_f = f; - *_temp_path = t; - - return 0; -} - -int fflush_and_check(FILE *f) { - assert(f); - - errno = 0; - fflush(f); - - if (ferror(f)) - return errno > 0 ? -errno : -EIO; - - return 0; -} - -/* This is much like like mkostemp() but is subject to umask(). */ -int mkostemp_safe(char *pattern, int flags) { - _cleanup_umask_ mode_t u = 0; - int fd; - - assert(pattern); - - u = umask(077); - - fd = mkostemp(pattern, flags); - if (fd < 0) - return -errno; - - return fd; -} - -int tempfn_xxxxxx(const char *p, const char *extra, char **ret) { - const char *fn; - char *t; - - assert(p); - assert(ret); - - /* - * Turns this: - * /foo/bar/waldo - * - * Into this: - * /foo/bar/.#waldoXXXXXX - */ - - fn = basename(p); - if (!filename_is_valid(fn)) - return -EINVAL; - - if (extra == NULL) - extra = ""; - - t = new(char, strlen(p) + 2 + strlen(extra) + 6 + 1); - if (!t) - return -ENOMEM; - - strcpy(stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn), "XXXXXX"); - - *ret = path_kill_slashes(t); - return 0; -} - -int tempfn_random(const char *p, const char *extra, char **ret) { - const char *fn; - char *t, *x; - uint64_t u; - unsigned i; - - assert(p); - assert(ret); - - /* - * Turns this: - * /foo/bar/waldo - * - * Into this: - * /foo/bar/.#waldobaa2a261115984a9 - */ - - fn = basename(p); - if (!filename_is_valid(fn)) - return -EINVAL; - - if (!extra) - extra = ""; - - t = new(char, strlen(p) + 2 + strlen(extra) + 16 + 1); - if (!t) - return -ENOMEM; - - x = stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn); - - u = random_u64(); - for (i = 0; i < 16; i++) { - *(x++) = hexchar(u & 0xF); - u >>= 4; - } - - *x = 0; - - *ret = path_kill_slashes(t); - return 0; -} - -int tempfn_random_child(const char *p, const char *extra, char **ret) { - char *t, *x; - uint64_t u; - unsigned i; - - assert(p); - assert(ret); - - /* Turns this: - * /foo/bar/waldo - * Into this: - * /foo/bar/waldo/.#3c2b6219aa75d7d0 - */ - - if (!extra) - extra = ""; - - t = new(char, strlen(p) + 3 + strlen(extra) + 16 + 1); - if (!t) - return -ENOMEM; - - x = stpcpy(stpcpy(stpcpy(t, p), "/.#"), extra); - - u = random_u64(); - for (i = 0; i < 16; i++) { - *(x++) = hexchar(u & 0xF); - u >>= 4; - } - - *x = 0; - - *ret = path_kill_slashes(t); - return 0; -} - -int write_timestamp_file_atomic(const char *fn, usec_t n) { - char ln[DECIMAL_STR_MAX(n)+2]; - - /* Creates a "timestamp" file, that contains nothing but a - * usec_t timestamp, formatted in ASCII. */ - - if (n <= 0 || n >= USEC_INFINITY) - return -ERANGE; - - xsprintf(ln, USEC_FMT "\n", n); - - return write_string_file(fn, ln, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); -} - -int read_timestamp_file(const char *fn, usec_t *ret) { - _cleanup_free_ char *ln = NULL; - uint64_t t; - int r; - - r = read_one_line_file(fn, &ln); - if (r < 0) - return r; - - r = safe_atou64(ln, &t); - if (r < 0) - return r; - - if (t <= 0 || t >= (uint64_t) USEC_INFINITY) - return -ERANGE; - - *ret = (usec_t) t; - return 0; -} - -int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space) { - int r; - - assert(s); - - /* Outputs the specified string with fputs(), but optionally prefixes it with a separator. The *space parameter - * when specified shall initially point to a boolean variable initialized to false. It is set to true after the - * first invocation. This call is supposed to be use in loops, where a separator shall be inserted between each - * element, but not before the first one. */ - - if (!f) - f = stdout; - - if (space) { - if (!separator) - separator = " "; - - if (*space) { - r = fputs(separator, f); - if (r < 0) - return r; - } - - *space = true; - } - - return fputs(s, f); -} - -int open_tmpfile_unlinkable(const char *directory, int flags) { - char *p; - int fd; - - assert(directory); - - /* Returns an unlinked temporary file that cannot be linked into the file system anymore */ - -#ifdef O_TMPFILE - /* Try O_TMPFILE first, if it is supported */ - fd = open(directory, flags|O_TMPFILE|O_EXCL, S_IRUSR|S_IWUSR); - if (fd >= 0) - return fd; -#endif - - /* Fall back to unguessable name + unlinking */ - p = strjoina(directory, "/systemd-tmp-XXXXXX"); - - fd = mkostemp_safe(p, flags); - if (fd < 0) - return fd; - - (void) unlink(p); - - return fd; -} - -int open_tmpfile_linkable(const char *target, int flags, char **ret_path) { - _cleanup_free_ char *tmp = NULL; - int r, fd; - - assert(target); - assert(ret_path); - - /* Don't allow O_EXCL, as that has a special meaning for O_TMPFILE */ - assert((flags & O_EXCL) == 0); - - /* Creates a temporary file, that shall be renamed to "target" later. If possible, this uses O_TMPFILE – in - * which case "ret_path" will be returned as NULL. If not possible a the tempoary path name used is returned in - * "ret_path". Use link_tmpfile() below to rename the result after writing the file in full. */ - -#ifdef O_TMPFILE - { - _cleanup_free_ char *dn = NULL; - - dn = dirname_malloc(target); - if (!dn) - return -ENOMEM; - - fd = open(dn, O_TMPFILE|flags, 0640); - if (fd >= 0) { - *ret_path = NULL; - return fd; - } - - log_debug_errno(errno, "Failed to use O_TMPFILE on %s: %m", dn); - } -#endif - - r = tempfn_random(target, NULL, &tmp); - if (r < 0) - return r; - - fd = open(tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640); - if (fd < 0) - return -errno; - - *ret_path = tmp; - tmp = NULL; - - return fd; -} - -int link_tmpfile(int fd, const char *path, const char *target) { - - assert(fd >= 0); - assert(target); - - /* Moves a temporary file created with open_tmpfile() above into its final place. if "path" is NULL an fd - * created with O_TMPFILE is assumed, and linkat() is used. Otherwise it is assumed O_TMPFILE is not supported - * on the directory, and renameat2() is used instead. - * - * Note that in both cases we will not replace existing files. This is because linkat() does not support this - * operation currently (renameat2() does), and there is no nice way to emulate this. */ - - if (path) { - if (rename_noreplace(AT_FDCWD, path, AT_FDCWD, target) < 0) - return -errno; - } else { - char proc_fd_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1]; - - xsprintf(proc_fd_path, "/proc/self/fd/%i", fd); - - if (linkat(AT_FDCWD, proc_fd_path, AT_FDCWD, target, AT_SYMLINK_FOLLOW) < 0) - return -errno; - } - - return 0; -} diff --git a/src/basic/fileio.h b/src/basic/fileio.h deleted file mode 100644 index 58dbc80c24..0000000000 --- a/src/basic/fileio.h +++ /dev/null @@ -1,88 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include - -#include "macro.h" -#include "time-util.h" - -typedef enum { - WRITE_STRING_FILE_CREATE = 1, - WRITE_STRING_FILE_ATOMIC = 2, - WRITE_STRING_FILE_AVOID_NEWLINE = 4, - WRITE_STRING_FILE_VERIFY_ON_FAILURE = 8, -} WriteStringFileFlags; - -int write_string_stream(FILE *f, const char *line, bool enforce_newline); -int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags); - -int read_one_line_file(const char *fn, char **line); -int read_full_file(const char *fn, char **contents, size_t *size); -int read_full_stream(FILE *f, char **contents, size_t *size); - -int verify_file(const char *fn, const char *blob, bool accept_extra_nl); - -int parse_env_file(const char *fname, const char *separator, ...) _sentinel_; -int load_env_file(FILE *f, const char *fname, const char *separator, char ***l); -int load_env_file_pairs(FILE *f, const char *fname, const char *separator, char ***l); - -int write_env_file(const char *fname, char **l); - -int executable_is_script(const char *path, char **interpreter); - -int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field); - -DIR *xopendirat(int dirfd, const char *name, int flags); - -int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **_f); -int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **_f); - -#define FOREACH_LINE(line, f, on_error) \ - for (;;) \ - if (!fgets(line, sizeof(line), f)) { \ - if (ferror(f)) { \ - on_error; \ - } \ - break; \ - } else - -int fflush_and_check(FILE *f); - -int fopen_temporary(const char *path, FILE **_f, char **_temp_path); -int mkostemp_safe(char *pattern, int flags); - -int tempfn_xxxxxx(const char *p, const char *extra, char **ret); -int tempfn_random(const char *p, const char *extra, char **ret); -int tempfn_random_child(const char *p, const char *extra, char **ret); - -int write_timestamp_file_atomic(const char *fn, usec_t n); -int read_timestamp_file(const char *fn, usec_t *ret); - -int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space); - -int open_tmpfile_unlinkable(const char *directory, int flags); -int open_tmpfile_linkable(const char *target, int flags, char **ret_path); - -int link_tmpfile(int fd, const char *path, const char *target); diff --git a/src/basic/formats-util.h b/src/basic/formats-util.h deleted file mode 100644 index 9b4e8e98fa..0000000000 --- a/src/basic/formats-util.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 Ronny Chevalier - - 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 - -#if SIZEOF_PID_T == 4 -# define PID_PRI PRIi32 -#elif SIZEOF_PID_T == 2 -# define PID_PRI PRIi16 -#else -# error Unknown pid_t size -#endif -#define PID_FMT "%" PID_PRI - -#if SIZEOF_UID_T == 4 -# define UID_FMT "%" PRIu32 -#elif SIZEOF_UID_T == 2 -# define UID_FMT "%" PRIu16 -#else -# error Unknown uid_t size -#endif - -#if SIZEOF_GID_T == 4 -# define GID_FMT "%" PRIu32 -#elif SIZEOF_GID_T == 2 -# define GID_FMT "%" PRIu16 -#else -# error Unknown gid_t size -#endif - -#if SIZEOF_TIME_T == 8 -# define PRI_TIME PRIi64 -#elif SIZEOF_TIME_T == 4 -# define PRI_TIME "li" -#else -# error Unknown time_t size -#endif - -#if SIZEOF_RLIM_T == 8 -# define RLIM_FMT "%" PRIu64 -#elif SIZEOF_RLIM_T == 4 -# define RLIM_FMT "%" PRIu32 -#else -# error Unknown rlim_t size -#endif diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c deleted file mode 100644 index e24e7036f7..0000000000 --- a/src/basic/fs-util.c +++ /dev/null @@ -1,510 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "parse-util.h" -#include "path-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" -#include "user-util.h" -#include "util.h" - -int unlink_noerrno(const char *path) { - PROTECT_ERRNO; - int r; - - r = unlink(path); - if (r < 0) - return -errno; - - return 0; -} - -int rmdir_parents(const char *path, const char *stop) { - size_t l; - int r = 0; - - assert(path); - assert(stop); - - l = strlen(path); - - /* Skip trailing slashes */ - while (l > 0 && path[l-1] == '/') - l--; - - while (l > 0) { - char *t; - - /* Skip last component */ - while (l > 0 && path[l-1] != '/') - l--; - - /* Skip trailing slashes */ - while (l > 0 && path[l-1] == '/') - l--; - - if (l <= 0) - break; - - t = strndup(path, l); - if (!t) - return -ENOMEM; - - if (path_startswith(stop, t)) { - free(t); - return 0; - } - - r = rmdir(t); - free(t); - - if (r < 0) - if (errno != ENOENT) - return -errno; - } - - return 0; -} - - -int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { - struct stat buf; - int ret; - - ret = renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE); - if (ret >= 0) - return 0; - - /* renameat2() exists since Linux 3.15, btrfs added support for it later. - * If it is not implemented, fallback to another method. */ - if (!IN_SET(errno, EINVAL, ENOSYS)) - return -errno; - - /* The link()/unlink() fallback does not work on directories. But - * renameat() without RENAME_NOREPLACE gives the same semantics on - * directories, except when newpath is an *empty* directory. This is - * good enough. */ - ret = fstatat(olddirfd, oldpath, &buf, AT_SYMLINK_NOFOLLOW); - if (ret >= 0 && S_ISDIR(buf.st_mode)) { - ret = renameat(olddirfd, oldpath, newdirfd, newpath); - return ret >= 0 ? 0 : -errno; - } - - /* If it is not a directory, use the link()/unlink() fallback. */ - ret = linkat(olddirfd, oldpath, newdirfd, newpath, 0); - if (ret < 0) - return -errno; - - ret = unlinkat(olddirfd, oldpath, 0); - if (ret < 0) { - /* backup errno before the following unlinkat() alters it */ - ret = errno; - (void) unlinkat(newdirfd, newpath, 0); - errno = ret; - return -errno; - } - - return 0; -} - -int readlinkat_malloc(int fd, const char *p, char **ret) { - size_t l = 100; - int r; - - assert(p); - assert(ret); - - for (;;) { - char *c; - ssize_t n; - - c = new(char, l); - if (!c) - return -ENOMEM; - - n = readlinkat(fd, p, c, l-1); - if (n < 0) { - r = -errno; - free(c); - return r; - } - - if ((size_t) n < l-1) { - c[n] = 0; - *ret = c; - return 0; - } - - free(c); - l *= 2; - } -} - -int readlink_malloc(const char *p, char **ret) { - return readlinkat_malloc(AT_FDCWD, p, ret); -} - -int readlink_value(const char *p, char **ret) { - _cleanup_free_ char *link = NULL; - char *value; - int r; - - r = readlink_malloc(p, &link); - if (r < 0) - return r; - - value = basename(link); - if (!value) - return -ENOENT; - - value = strdup(value); - if (!value) - return -ENOMEM; - - *ret = value; - - return 0; -} - -int readlink_and_make_absolute(const char *p, char **r) { - _cleanup_free_ char *target = NULL; - char *k; - int j; - - assert(p); - assert(r); - - j = readlink_malloc(p, &target); - if (j < 0) - return j; - - k = file_in_same_dir(p, target); - if (!k) - return -ENOMEM; - - *r = k; - return 0; -} - -int readlink_and_canonicalize(const char *p, char **r) { - char *t, *s; - int j; - - assert(p); - assert(r); - - j = readlink_and_make_absolute(p, &t); - if (j < 0) - return j; - - s = canonicalize_file_name(t); - if (s) { - free(t); - *r = s; - } else - *r = t; - - path_kill_slashes(*r); - - return 0; -} - -int readlink_and_make_absolute_root(const char *root, const char *path, char **ret) { - _cleanup_free_ char *target = NULL, *t = NULL; - const char *full; - int r; - - full = prefix_roota(root, path); - r = readlink_malloc(full, &target); - if (r < 0) - return r; - - t = file_in_same_dir(path, target); - if (!t) - return -ENOMEM; - - *ret = t; - t = NULL; - - return 0; -} - -int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { - assert(path); - - /* Under the assumption that we are running privileged we - * first change the access mode and only then hand out - * ownership to avoid a window where access is too open. */ - - if (mode != MODE_INVALID) - if (chmod(path, mode) < 0) - return -errno; - - if (uid != UID_INVALID || gid != GID_INVALID) - if (chown(path, uid, gid) < 0) - return -errno; - - return 0; -} - -int fchmod_umask(int fd, mode_t m) { - mode_t u; - int r; - - u = umask(0777); - r = fchmod(fd, m & (~u)) < 0 ? -errno : 0; - umask(u); - - return r; -} - -int fd_warn_permissions(const char *path, int fd) { - struct stat st; - - if (fstat(fd, &st) < 0) - return -errno; - - if (st.st_mode & 0111) - log_warning("Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway.", path); - - if (st.st_mode & 0002) - log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path); - - if (getpid() == 1 && (st.st_mode & 0044) != 0044) - log_warning("Configuration file %s is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway.", path); - - return 0; -} - -int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) { - _cleanup_close_ int fd; - int r; - - assert(path); - - if (parents) - mkdir_parents(path, 0755); - - fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, - (mode == 0 || mode == MODE_INVALID) ? 0644 : mode); - if (fd < 0) - return -errno; - - if (mode != MODE_INVALID) { - r = fchmod(fd, mode); - if (r < 0) - return -errno; - } - - if (uid != UID_INVALID || gid != GID_INVALID) { - r = fchown(fd, uid, gid); - if (r < 0) - return -errno; - } - - if (stamp != USEC_INFINITY) { - struct timespec ts[2]; - - timespec_store(&ts[0], stamp); - ts[1] = ts[0]; - r = futimens(fd, ts); - } else - r = futimens(fd, NULL); - if (r < 0) - return -errno; - - return 0; -} - -int touch(const char *path) { - return touch_file(path, false, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID); -} - -int symlink_idempotent(const char *from, const char *to) { - _cleanup_free_ char *p = NULL; - int r; - - assert(from); - assert(to); - - if (symlink(from, to) < 0) { - if (errno != EEXIST) - return -errno; - - r = readlink_malloc(to, &p); - if (r < 0) - return r; - - if (!streq(p, from)) - return -EINVAL; - } - - return 0; -} - -int symlink_atomic(const char *from, const char *to) { - _cleanup_free_ char *t = NULL; - int r; - - assert(from); - assert(to); - - r = tempfn_random(to, NULL, &t); - if (r < 0) - return r; - - if (symlink(from, t) < 0) - return -errno; - - if (rename(t, to) < 0) { - unlink_noerrno(t); - return -errno; - } - - return 0; -} - -int mknod_atomic(const char *path, mode_t mode, dev_t dev) { - _cleanup_free_ char *t = NULL; - int r; - - assert(path); - - r = tempfn_random(path, NULL, &t); - if (r < 0) - return r; - - if (mknod(t, mode, dev) < 0) - return -errno; - - if (rename(t, path) < 0) { - unlink_noerrno(t); - return -errno; - } - - return 0; -} - -int mkfifo_atomic(const char *path, mode_t mode) { - _cleanup_free_ char *t = NULL; - int r; - - assert(path); - - r = tempfn_random(path, NULL, &t); - if (r < 0) - return r; - - if (mkfifo(t, mode) < 0) - return -errno; - - if (rename(t, path) < 0) { - unlink_noerrno(t); - return -errno; - } - - return 0; -} - -int get_files_in_directory(const char *path, char ***list) { - _cleanup_closedir_ DIR *d = NULL; - size_t bufsize = 0, n = 0; - _cleanup_strv_free_ char **l = NULL; - - assert(path); - - /* Returns all files in a directory in *list, and the number - * of files as return value. If list is NULL returns only the - * number. */ - - d = opendir(path); - if (!d) - return -errno; - - for (;;) { - struct dirent *de; - - errno = 0; - de = readdir(d); - if (!de && errno > 0) - return -errno; - if (!de) - break; - - dirent_ensure_type(d, de); - - if (!dirent_is_file(de)) - continue; - - if (list) { - /* one extra slot is needed for the terminating NULL */ - if (!GREEDY_REALLOC(l, bufsize, n + 2)) - return -ENOMEM; - - l[n] = strdup(de->d_name); - if (!l[n]) - return -ENOMEM; - - l[++n] = NULL; - } else - n++; - } - - if (list) { - *list = l; - l = NULL; /* avoid freeing */ - } - - return n; -} - -int inotify_add_watch_fd(int fd, int what, uint32_t mask) { - char path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; - int r; - - /* This is like inotify_add_watch(), except that the file to watch is not referenced by a path, but by an fd */ - xsprintf(path, "/proc/self/fd/%i", what); - - r = inotify_add_watch(fd, path, mask); - if (r < 0) - return -errno; - - return r; -} diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h deleted file mode 100644 index 517b599d6f..0000000000 --- a/src/basic/fs-util.h +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include - -#include "time-util.h" - -int unlink_noerrno(const char *path); - -int rmdir_parents(const char *path, const char *stop); - -int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); - -int readlinkat_malloc(int fd, const char *p, char **ret); -int readlink_malloc(const char *p, char **r); -int readlink_value(const char *p, char **ret); -int readlink_and_make_absolute(const char *p, char **r); -int readlink_and_canonicalize(const char *p, char **r); -int readlink_and_make_absolute_root(const char *root, const char *path, char **ret); - -int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid); - -int fchmod_umask(int fd, mode_t mode); - -int fd_warn_permissions(const char *path, int fd); - -#define laccess(path, mode) faccessat(AT_FDCWD, (path), (mode), AT_SYMLINK_NOFOLLOW) - -int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode); -int touch(const char *path); - -int symlink_idempotent(const char *from, const char *to); - -int symlink_atomic(const char *from, const char *to); -int mknod_atomic(const char *path, mode_t mode, dev_t dev); -int mkfifo_atomic(const char *path, mode_t mode); - -int get_files_in_directory(const char *path, char ***list); - -#define INOTIFY_EVENT_MAX (sizeof(struct inotify_event) + NAME_MAX + 1) - -#define FOREACH_INOTIFY_EVENT(e, buffer, sz) \ - for ((e) = &buffer.ev; \ - (uint8_t*) (e) < (uint8_t*) (buffer.raw) + (sz); \ - (e) = (struct inotify_event*) ((uint8_t*) (e) + sizeof(struct inotify_event) + (e)->len)) - -union inotify_event_buffer { - struct inotify_event ev; - uint8_t raw[INOTIFY_EVENT_MAX]; -}; - -int inotify_add_watch_fd(int fd, int what, uint32_t mask); diff --git a/src/basic/glob-util.c b/src/basic/glob-util.c deleted file mode 100644 index 007198c269..0000000000 --- a/src/basic/glob-util.c +++ /dev/null @@ -1,70 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 - -#include "glob-util.h" -#include "macro.h" -#include "strv.h" - -int glob_exists(const char *path) { - _cleanup_globfree_ glob_t g = {}; - int k; - - assert(path); - - errno = 0; - k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g); - - if (k == GLOB_NOMATCH) - return 0; - if (k == GLOB_NOSPACE) - return -ENOMEM; - if (k != 0) - return errno > 0 ? -errno : -EIO; - - return !strv_isempty(g.gl_pathv); -} - -int glob_extend(char ***strv, const char *path) { - _cleanup_globfree_ glob_t g = {}; - int k; - char **p; - - errno = 0; - k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g); - - if (k == GLOB_NOMATCH) - return -ENOENT; - if (k == GLOB_NOSPACE) - return -ENOMEM; - if (k != 0) - return errno > 0 ? -errno : -EIO; - if (strv_isempty(g.gl_pathv)) - return -ENOENT; - - STRV_FOREACH(p, g.gl_pathv) { - k = strv_extend(strv, *p); - if (k < 0) - return k; - } - - return 0; -} diff --git a/src/basic/glob-util.h b/src/basic/glob-util.h deleted file mode 100644 index 5d8fb47a26..0000000000 --- a/src/basic/glob-util.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 - -#include "macro.h" -#include "string-util.h" - -int glob_exists(const char *path); -int glob_extend(char ***strv, const char *path); - -#define _cleanup_globfree_ _cleanup_(globfree) - -_pure_ static inline bool string_is_glob(const char *p) { - /* Check if a string contains any glob patterns. */ - return !!strpbrk(p, GLOB_CHARS); -} diff --git a/src/basic/gunicode.c b/src/basic/gunicode.c deleted file mode 100644 index 542110503f..0000000000 --- a/src/basic/gunicode.c +++ /dev/null @@ -1,112 +0,0 @@ -/* gunicode.c - Unicode manipulation functions - * - * Copyright (C) 1999, 2000 Tom Tromey - * Copyright 2000, 2005 Red Hat, Inc. - */ - -#include - -#include "gunicode.h" - -#define unichar uint32_t - -/** - * g_utf8_prev_char: - * @p: a pointer to a position within a UTF-8 encoded string - * - * Finds the previous UTF-8 character in the string before @p. - * - * @p does not have to be at the beginning of a UTF-8 character. No check - * is made to see if the character found is actually valid other than - * it starts with an appropriate byte. If @p might be the first - * character of the string, you must use g_utf8_find_prev_char() instead. - * - * Return value: a pointer to the found character. - **/ -char * -utf8_prev_char (const char *p) -{ - while (1) - { - p--; - if ((*p & 0xc0) != 0x80) - return (char *)p; - } -} - -struct Interval -{ - unichar start, end; -}; - -static int -interval_compare (const void *key, const void *elt) -{ - unichar c = (unichar) (long) (key); - struct Interval *interval = (struct Interval *)elt; - - if (c < interval->start) - return -1; - if (c > interval->end) - return +1; - - return 0; -} - -/* - * NOTE: - * - * The tables for g_unichar_iswide() and g_unichar_iswide_cjk() are - * generated from the Unicode Character Database's file - * extracted/DerivedEastAsianWidth.txt using the gen-iswide-table.py - * in this way: - * - * ./gen-iswide-table.py < path/to/ucd/extracted/DerivedEastAsianWidth.txt | fmt - * - * Last update for Unicode 6.0. - */ - -/** - * g_unichar_iswide: - * @c: a Unicode character - * - * Determines if a character is typically rendered in a double-width - * cell. - * - * Return value: %TRUE if the character is wide - **/ -bool -unichar_iswide (unichar c) -{ - /* See NOTE earlier for how to update this table. */ - static const struct Interval wide[] = { - {0x1100, 0x115F}, {0x2329, 0x232A}, {0x2E80, 0x2E99}, {0x2E9B, 0x2EF3}, - {0x2F00, 0x2FD5}, {0x2FF0, 0x2FFB}, {0x3000, 0x303E}, {0x3041, 0x3096}, - {0x3099, 0x30FF}, {0x3105, 0x312D}, {0x3131, 0x318E}, {0x3190, 0x31BA}, - {0x31C0, 0x31E3}, {0x31F0, 0x321E}, {0x3220, 0x3247}, {0x3250, 0x32FE}, - {0x3300, 0x4DBF}, {0x4E00, 0xA48C}, {0xA490, 0xA4C6}, {0xA960, 0xA97C}, - {0xAC00, 0xD7A3}, {0xF900, 0xFAFF}, {0xFE10, 0xFE19}, {0xFE30, 0xFE52}, - {0xFE54, 0xFE66}, {0xFE68, 0xFE6B}, {0xFF01, 0xFF60}, {0xFFE0, 0xFFE6}, - {0x1B000, 0x1B001}, {0x1F200, 0x1F202}, {0x1F210, 0x1F23A}, - {0x1F240, 0x1F248}, {0x1F250, 0x1F251}, - {0x1F300, 0x1F567}, /* Miscellaneous Symbols and Pictographs */ - {0x20000, 0x2FFFD}, {0x30000, 0x3FFFD}, - }; - - if (bsearch ((void *)(uintptr_t)c, wide, (sizeof (wide) / sizeof ((wide)[0])), sizeof wide[0], - interval_compare)) - return true; - - return false; -} - -const char utf8_skip_data[256] = { - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, - 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1 -}; diff --git a/src/basic/gunicode.h b/src/basic/gunicode.h deleted file mode 100644 index 5975bc8fc9..0000000000 --- a/src/basic/gunicode.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/* gunicode.h - Unicode manipulation functions - * - * Copyright (C) 1999, 2000 Tom Tromey - * Copyright 2000, 2005 Red Hat, Inc. - */ - -#include -#include -#include - -char *utf8_prev_char (const char *p); - -extern const char utf8_skip_data[256]; - -/** - * g_utf8_next_char: - * @p: Pointer to the start of a valid UTF-8 character - * - * Skips to the next character in a UTF-8 string. The string must be - * valid; this macro is as fast as possible, and has no error-checking. - * You would use this macro to iterate over a string character by - * character. The macro returns the start of the next UTF-8 character. - * Before using this macro, use g_utf8_validate() to validate strings - * that may contain invalid UTF-8. - */ -#define utf8_next_char(p) (char *)((p) + utf8_skip_data[*(const unsigned char *)(p)]) - -bool unichar_iswide (uint32_t c); diff --git a/src/basic/hash-funcs.c b/src/basic/hash-funcs.c deleted file mode 100644 index c3a4a011b5..0000000000 --- a/src/basic/hash-funcs.c +++ /dev/null @@ -1,81 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2014 Michal Schmidt - - 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 "hash-funcs.h" - -void string_hash_func(const void *p, struct siphash *state) { - siphash24_compress(p, strlen(p) + 1, state); -} - -int string_compare_func(const void *a, const void *b) { - return strcmp(a, b); -} - -const struct hash_ops string_hash_ops = { - .hash = string_hash_func, - .compare = string_compare_func -}; - -void trivial_hash_func(const void *p, struct siphash *state) { - siphash24_compress(&p, sizeof(p), state); -} - -int trivial_compare_func(const void *a, const void *b) { - return a < b ? -1 : (a > b ? 1 : 0); -} - -const struct hash_ops trivial_hash_ops = { - .hash = trivial_hash_func, - .compare = trivial_compare_func -}; - -void uint64_hash_func(const void *p, struct siphash *state) { - siphash24_compress(p, sizeof(uint64_t), state); -} - -int uint64_compare_func(const void *_a, const void *_b) { - uint64_t a, b; - a = *(const uint64_t*) _a; - b = *(const uint64_t*) _b; - return a < b ? -1 : (a > b ? 1 : 0); -} - -const struct hash_ops uint64_hash_ops = { - .hash = uint64_hash_func, - .compare = uint64_compare_func -}; - -#if SIZEOF_DEV_T != 8 -void devt_hash_func(const void *p, struct siphash *state) { - siphash24_compress(p, sizeof(dev_t), state); -} - -int devt_compare_func(const void *_a, const void *_b) { - dev_t a, b; - a = *(const dev_t*) _a; - b = *(const dev_t*) _b; - return a < b ? -1 : (a > b ? 1 : 0); -} - -const struct hash_ops devt_hash_ops = { - .hash = devt_hash_func, - .compare = devt_compare_func -}; -#endif diff --git a/src/basic/hash-funcs.h b/src/basic/hash-funcs.h deleted file mode 100644 index 299189d143..0000000000 --- a/src/basic/hash-funcs.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2014 Michal Schmidt - - 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 "macro.h" -#include "siphash24.h" - -typedef void (*hash_func_t)(const void *p, struct siphash *state); -typedef int (*compare_func_t)(const void *a, const void *b); - -struct hash_ops { - hash_func_t hash; - compare_func_t compare; -}; - -void string_hash_func(const void *p, struct siphash *state); -int string_compare_func(const void *a, const void *b) _pure_; -extern const struct hash_ops string_hash_ops; - -/* This will compare the passed pointers directly, and will not - * dereference them. This is hence not useful for strings or - * suchlike. */ -void trivial_hash_func(const void *p, struct siphash *state); -int trivial_compare_func(const void *a, const void *b) _const_; -extern const struct hash_ops trivial_hash_ops; - -/* 32bit values we can always just embed in the pointer itself, but - * in order to support 32bit archs we need store 64bit values - * indirectly, since they don't fit in a pointer. */ -void uint64_hash_func(const void *p, struct siphash *state); -int uint64_compare_func(const void *a, const void *b) _pure_; -extern const struct hash_ops uint64_hash_ops; - -/* On some archs dev_t is 32bit, and on others 64bit. And sometimes - * it's 64bit on 32bit archs, and sometimes 32bit on 64bit archs. Yuck! */ -#if SIZEOF_DEV_T != 8 -void devt_hash_func(const void *p, struct siphash *state) _pure_; -int devt_compare_func(const void *a, const void *b) _pure_; -extern const struct hash_ops devt_hash_ops = { - .hash = devt_hash_func, - .compare = devt_compare_func -}; -#else -#define devt_hash_func uint64_hash_func -#define devt_compare_func uint64_compare_func -#define devt_hash_ops uint64_hash_ops -#endif diff --git a/src/basic/hashmap.c b/src/basic/hashmap.c deleted file mode 100644 index 49a0479592..0000000000 --- a/src/basic/hashmap.c +++ /dev/null @@ -1,1803 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2014 Michal Schmidt - - 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 -#include -#include - -#include "alloc-util.h" -#include "hashmap.h" -#include "macro.h" -#include "mempool.h" -#include "process-util.h" -#include "random-util.h" -#include "set.h" -#include "siphash24.h" -#include "strv.h" -#include "util.h" - -#ifdef ENABLE_DEBUG_HASHMAP -#include -#include "list.h" -#endif - -/* - * Implementation of hashmaps. - * Addressing: open - * - uses less RAM compared to closed addressing (chaining), because - * our entries are small (especially in Sets, which tend to contain - * the majority of entries in systemd). - * Collision resolution: Robin Hood - * - tends to equalize displacement of entries from their optimal buckets. - * Probe sequence: linear - * - though theoretically worse than random probing/uniform hashing/double - * hashing, it is good for cache locality. - * - * References: - * Celis, P. 1986. Robin Hood Hashing. - * Ph.D. Dissertation. University of Waterloo, Waterloo, Ont., Canada, Canada. - * https://cs.uwaterloo.ca/research/tr/1986/CS-86-14.pdf - * - The results are derived for random probing. Suggests deletion with - * tombstones and two mean-centered search methods. None of that works - * well for linear probing. - * - * Janson, S. 2005. Individual displacements for linear probing hashing with different insertion policies. - * ACM Trans. Algorithms 1, 2 (October 2005), 177-213. - * DOI=10.1145/1103963.1103964 http://doi.acm.org/10.1145/1103963.1103964 - * http://www.math.uu.se/~svante/papers/sj157.pdf - * - Applies to Robin Hood with linear probing. Contains remarks on - * the unsuitability of mean-centered search with linear probing. - * - * Viola, A. 2005. Exact distribution of individual displacements in linear probing hashing. - * ACM Trans. Algorithms 1, 2 (October 2005), 214-242. - * DOI=10.1145/1103963.1103965 http://doi.acm.org/10.1145/1103963.1103965 - * - Similar to Janson. Note that Viola writes about C_{m,n} (number of probes - * in a successful search), and Janson writes about displacement. C = d + 1. - * - * Goossaert, E. 2013. Robin Hood hashing: backward shift deletion. - * http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/ - * - Explanation of backward shift deletion with pictures. - * - * Khuong, P. 2013. The Other Robin Hood Hashing. - * http://www.pvk.ca/Blog/2013/11/26/the-other-robin-hood-hashing/ - * - Short summary of random vs. linear probing, and tombstones vs. backward shift. - */ - -/* - * XXX Ideas for improvement: - * For unordered hashmaps, randomize iteration order, similarly to Perl: - * http://blog.booking.com/hardening-perls-hash-function.html - */ - -/* INV_KEEP_FREE = 1 / (1 - max_load_factor) - * e.g. 1 / (1 - 0.8) = 5 ... keep one fifth of the buckets free. */ -#define INV_KEEP_FREE 5U - -/* Fields common to entries of all hashmap/set types */ -struct hashmap_base_entry { - const void *key; -}; - -/* Entry types for specific hashmap/set types - * hashmap_base_entry must be at the beginning of each entry struct. */ - -struct plain_hashmap_entry { - struct hashmap_base_entry b; - void *value; -}; - -struct ordered_hashmap_entry { - struct plain_hashmap_entry p; - unsigned iterate_next, iterate_previous; -}; - -struct set_entry { - struct hashmap_base_entry b; -}; - -/* In several functions it is advantageous to have the hash table extended - * virtually by a couple of additional buckets. We reserve special index values - * for these "swap" buckets. */ -#define _IDX_SWAP_BEGIN (UINT_MAX - 3) -#define IDX_PUT (_IDX_SWAP_BEGIN + 0) -#define IDX_TMP (_IDX_SWAP_BEGIN + 1) -#define _IDX_SWAP_END (_IDX_SWAP_BEGIN + 2) - -#define IDX_FIRST (UINT_MAX - 1) /* special index for freshly initialized iterators */ -#define IDX_NIL UINT_MAX /* special index value meaning "none" or "end" */ - -assert_cc(IDX_FIRST == _IDX_SWAP_END); -assert_cc(IDX_FIRST == _IDX_ITERATOR_FIRST); - -/* Storage space for the "swap" buckets. - * All entry types can fit into a ordered_hashmap_entry. */ -struct swap_entries { - struct ordered_hashmap_entry e[_IDX_SWAP_END - _IDX_SWAP_BEGIN]; -}; - -/* Distance from Initial Bucket */ -typedef uint8_t dib_raw_t; -#define DIB_RAW_OVERFLOW ((dib_raw_t)0xfdU) /* indicates DIB value is greater than representable */ -#define DIB_RAW_REHASH ((dib_raw_t)0xfeU) /* entry yet to be rehashed during in-place resize */ -#define DIB_RAW_FREE ((dib_raw_t)0xffU) /* a free bucket */ -#define DIB_RAW_INIT ((char)DIB_RAW_FREE) /* a byte to memset a DIB store with when initializing */ - -#define DIB_FREE UINT_MAX - -#ifdef ENABLE_DEBUG_HASHMAP -struct hashmap_debug_info { - LIST_FIELDS(struct hashmap_debug_info, debug_list); - unsigned max_entries; /* high watermark of n_entries */ - - /* who allocated this hashmap */ - int line; - const char *file; - const char *func; - - /* fields to detect modification while iterating */ - unsigned put_count; /* counts puts into the hashmap */ - unsigned rem_count; /* counts removals from hashmap */ - unsigned last_rem_idx; /* remembers last removal index */ -}; - -/* Tracks all existing hashmaps. Get at it from gdb. See sd_dump_hashmaps.py */ -static LIST_HEAD(struct hashmap_debug_info, hashmap_debug_list); -static pthread_mutex_t hashmap_debug_list_mutex = PTHREAD_MUTEX_INITIALIZER; - -#define HASHMAP_DEBUG_FIELDS struct hashmap_debug_info debug; - -#else /* !ENABLE_DEBUG_HASHMAP */ -#define HASHMAP_DEBUG_FIELDS -#endif /* ENABLE_DEBUG_HASHMAP */ - -enum HashmapType { - HASHMAP_TYPE_PLAIN, - HASHMAP_TYPE_ORDERED, - HASHMAP_TYPE_SET, - _HASHMAP_TYPE_MAX -}; - -struct _packed_ indirect_storage { - void *storage; /* where buckets and DIBs are stored */ - uint8_t hash_key[HASH_KEY_SIZE]; /* hash key; changes during resize */ - - unsigned n_entries; /* number of stored entries */ - unsigned n_buckets; /* number of buckets */ - - unsigned idx_lowest_entry; /* Index below which all buckets are free. - Makes "while(hashmap_steal_first())" loops - O(n) instead of O(n^2) for unordered hashmaps. */ - uint8_t _pad[3]; /* padding for the whole HashmapBase */ - /* The bitfields in HashmapBase complete the alignment of the whole thing. */ -}; - -struct direct_storage { - /* This gives us 39 bytes on 64bit, or 35 bytes on 32bit. - * That's room for 4 set_entries + 4 DIB bytes + 3 unused bytes on 64bit, - * or 7 set_entries + 7 DIB bytes + 0 unused bytes on 32bit. */ - uint8_t storage[sizeof(struct indirect_storage)]; -}; - -#define DIRECT_BUCKETS(entry_t) \ - (sizeof(struct direct_storage) / (sizeof(entry_t) + sizeof(dib_raw_t))) - -/* We should be able to store at least one entry directly. */ -assert_cc(DIRECT_BUCKETS(struct ordered_hashmap_entry) >= 1); - -/* We have 3 bits for n_direct_entries. */ -assert_cc(DIRECT_BUCKETS(struct set_entry) < (1 << 3)); - -/* Hashmaps with directly stored entries all use this shared hash key. - * It's no big deal if the key is guessed, because there can be only - * a handful of directly stored entries in a hashmap. When a hashmap - * outgrows direct storage, it gets its own key for indirect storage. */ -static uint8_t shared_hash_key[HASH_KEY_SIZE]; -static bool shared_hash_key_initialized; - -/* Fields that all hashmap/set types must have */ -struct HashmapBase { - const struct hash_ops *hash_ops; /* hash and compare ops to use */ - - union _packed_ { - struct indirect_storage indirect; /* if has_indirect */ - struct direct_storage direct; /* if !has_indirect */ - }; - - enum HashmapType type:2; /* HASHMAP_TYPE_* */ - bool has_indirect:1; /* whether indirect storage is used */ - unsigned n_direct_entries:3; /* Number of entries in direct storage. - * Only valid if !has_indirect. */ - bool from_pool:1; /* whether was allocated from mempool */ - HASHMAP_DEBUG_FIELDS /* optional hashmap_debug_info */ -}; - -/* Specific hash types - * HashmapBase must be at the beginning of each hashmap struct. */ - -struct Hashmap { - struct HashmapBase b; -}; - -struct OrderedHashmap { - struct HashmapBase b; - unsigned iterate_list_head, iterate_list_tail; -}; - -struct Set { - struct HashmapBase b; -}; - -DEFINE_MEMPOOL(hashmap_pool, Hashmap, 8); -DEFINE_MEMPOOL(ordered_hashmap_pool, OrderedHashmap, 8); -/* No need for a separate Set pool */ -assert_cc(sizeof(Hashmap) == sizeof(Set)); - -struct hashmap_type_info { - size_t head_size; - size_t entry_size; - struct mempool *mempool; - unsigned n_direct_buckets; -}; - -static const struct hashmap_type_info hashmap_type_info[_HASHMAP_TYPE_MAX] = { - [HASHMAP_TYPE_PLAIN] = { - .head_size = sizeof(Hashmap), - .entry_size = sizeof(struct plain_hashmap_entry), - .mempool = &hashmap_pool, - .n_direct_buckets = DIRECT_BUCKETS(struct plain_hashmap_entry), - }, - [HASHMAP_TYPE_ORDERED] = { - .head_size = sizeof(OrderedHashmap), - .entry_size = sizeof(struct ordered_hashmap_entry), - .mempool = &ordered_hashmap_pool, - .n_direct_buckets = DIRECT_BUCKETS(struct ordered_hashmap_entry), - }, - [HASHMAP_TYPE_SET] = { - .head_size = sizeof(Set), - .entry_size = sizeof(struct set_entry), - .mempool = &hashmap_pool, - .n_direct_buckets = DIRECT_BUCKETS(struct set_entry), - }, -}; - -static unsigned n_buckets(HashmapBase *h) { - return h->has_indirect ? h->indirect.n_buckets - : hashmap_type_info[h->type].n_direct_buckets; -} - -static unsigned n_entries(HashmapBase *h) { - return h->has_indirect ? h->indirect.n_entries - : h->n_direct_entries; -} - -static void n_entries_inc(HashmapBase *h) { - if (h->has_indirect) - h->indirect.n_entries++; - else - h->n_direct_entries++; -} - -static void n_entries_dec(HashmapBase *h) { - if (h->has_indirect) - h->indirect.n_entries--; - else - h->n_direct_entries--; -} - -static void *storage_ptr(HashmapBase *h) { - return h->has_indirect ? h->indirect.storage - : h->direct.storage; -} - -static uint8_t *hash_key(HashmapBase *h) { - return h->has_indirect ? h->indirect.hash_key - : shared_hash_key; -} - -static unsigned base_bucket_hash(HashmapBase *h, const void *p) { - struct siphash state; - uint64_t hash; - - siphash24_init(&state, hash_key(h)); - - h->hash_ops->hash(p, &state); - - hash = siphash24_finalize(&state); - - return (unsigned) (hash % n_buckets(h)); -} -#define bucket_hash(h, p) base_bucket_hash(HASHMAP_BASE(h), p) - -static void get_hash_key(uint8_t hash_key[HASH_KEY_SIZE], bool reuse_is_ok) { - static uint8_t current[HASH_KEY_SIZE]; - static bool current_initialized = false; - - /* Returns a hash function key to use. In order to keep things - * fast we will not generate a new key each time we allocate a - * new hash table. Instead, we'll just reuse the most recently - * generated one, except if we never generated one or when we - * are rehashing an entire hash table because we reached a - * fill level */ - - if (!current_initialized || !reuse_is_ok) { - random_bytes(current, sizeof(current)); - current_initialized = true; - } - - memcpy(hash_key, current, sizeof(current)); -} - -static struct hashmap_base_entry *bucket_at(HashmapBase *h, unsigned idx) { - return (struct hashmap_base_entry*) - ((uint8_t*) storage_ptr(h) + idx * hashmap_type_info[h->type].entry_size); -} - -static struct plain_hashmap_entry *plain_bucket_at(Hashmap *h, unsigned idx) { - return (struct plain_hashmap_entry*) bucket_at(HASHMAP_BASE(h), idx); -} - -static struct ordered_hashmap_entry *ordered_bucket_at(OrderedHashmap *h, unsigned idx) { - return (struct ordered_hashmap_entry*) bucket_at(HASHMAP_BASE(h), idx); -} - -static struct set_entry *set_bucket_at(Set *h, unsigned idx) { - return (struct set_entry*) bucket_at(HASHMAP_BASE(h), idx); -} - -static struct ordered_hashmap_entry *bucket_at_swap(struct swap_entries *swap, unsigned idx) { - return &swap->e[idx - _IDX_SWAP_BEGIN]; -} - -/* Returns a pointer to the bucket at index idx. - * Understands real indexes and swap indexes, hence "_virtual". */ -static struct hashmap_base_entry *bucket_at_virtual(HashmapBase *h, struct swap_entries *swap, - unsigned idx) { - if (idx < _IDX_SWAP_BEGIN) - return bucket_at(h, idx); - - if (idx < _IDX_SWAP_END) - return &bucket_at_swap(swap, idx)->p.b; - - assert_not_reached("Invalid index"); -} - -static dib_raw_t *dib_raw_ptr(HashmapBase *h) { - return (dib_raw_t*) - ((uint8_t*) storage_ptr(h) + hashmap_type_info[h->type].entry_size * n_buckets(h)); -} - -static unsigned bucket_distance(HashmapBase *h, unsigned idx, unsigned from) { - return idx >= from ? idx - from - : n_buckets(h) + idx - from; -} - -static unsigned bucket_calculate_dib(HashmapBase *h, unsigned idx, dib_raw_t raw_dib) { - unsigned initial_bucket; - - if (raw_dib == DIB_RAW_FREE) - return DIB_FREE; - - if (_likely_(raw_dib < DIB_RAW_OVERFLOW)) - return raw_dib; - - /* - * Having an overflow DIB value is very unlikely. The hash function - * would have to be bad. For example, in a table of size 2^24 filled - * to load factor 0.9 the maximum observed DIB is only about 60. - * In theory (assuming I used Maxima correctly), for an infinite size - * hash table with load factor 0.8 the probability of a given entry - * having DIB > 40 is 1.9e-8. - * This returns the correct DIB value by recomputing the hash value in - * the unlikely case. XXX Hitting this case could be a hint to rehash. - */ - initial_bucket = bucket_hash(h, bucket_at(h, idx)->key); - return bucket_distance(h, idx, initial_bucket); -} - -static void bucket_set_dib(HashmapBase *h, unsigned idx, unsigned dib) { - dib_raw_ptr(h)[idx] = dib != DIB_FREE ? MIN(dib, DIB_RAW_OVERFLOW) : DIB_RAW_FREE; -} - -static unsigned skip_free_buckets(HashmapBase *h, unsigned idx) { - dib_raw_t *dibs; - - dibs = dib_raw_ptr(h); - - for ( ; idx < n_buckets(h); idx++) - if (dibs[idx] != DIB_RAW_FREE) - return idx; - - return IDX_NIL; -} - -static void bucket_mark_free(HashmapBase *h, unsigned idx) { - memzero(bucket_at(h, idx), hashmap_type_info[h->type].entry_size); - bucket_set_dib(h, idx, DIB_FREE); -} - -static void bucket_move_entry(HashmapBase *h, struct swap_entries *swap, - unsigned from, unsigned to) { - struct hashmap_base_entry *e_from, *e_to; - - assert(from != to); - - e_from = bucket_at_virtual(h, swap, from); - e_to = bucket_at_virtual(h, swap, to); - - memcpy(e_to, e_from, hashmap_type_info[h->type].entry_size); - - if (h->type == HASHMAP_TYPE_ORDERED) { - OrderedHashmap *lh = (OrderedHashmap*) h; - struct ordered_hashmap_entry *le, *le_to; - - le_to = (struct ordered_hashmap_entry*) e_to; - - if (le_to->iterate_next != IDX_NIL) { - le = (struct ordered_hashmap_entry*) - bucket_at_virtual(h, swap, le_to->iterate_next); - le->iterate_previous = to; - } - - if (le_to->iterate_previous != IDX_NIL) { - le = (struct ordered_hashmap_entry*) - bucket_at_virtual(h, swap, le_to->iterate_previous); - le->iterate_next = to; - } - - if (lh->iterate_list_head == from) - lh->iterate_list_head = to; - if (lh->iterate_list_tail == from) - lh->iterate_list_tail = to; - } -} - -static unsigned next_idx(HashmapBase *h, unsigned idx) { - return (idx + 1U) % n_buckets(h); -} - -static unsigned prev_idx(HashmapBase *h, unsigned idx) { - return (n_buckets(h) + idx - 1U) % n_buckets(h); -} - -static void *entry_value(HashmapBase *h, struct hashmap_base_entry *e) { - switch (h->type) { - - case HASHMAP_TYPE_PLAIN: - case HASHMAP_TYPE_ORDERED: - return ((struct plain_hashmap_entry*)e)->value; - - case HASHMAP_TYPE_SET: - return (void*) e->key; - - default: - assert_not_reached("Unknown hashmap type"); - } -} - -static void base_remove_entry(HashmapBase *h, unsigned idx) { - unsigned left, right, prev, dib; - dib_raw_t raw_dib, *dibs; - - dibs = dib_raw_ptr(h); - assert(dibs[idx] != DIB_RAW_FREE); - -#ifdef ENABLE_DEBUG_HASHMAP - h->debug.rem_count++; - h->debug.last_rem_idx = idx; -#endif - - left = idx; - /* Find the stop bucket ("right"). It is either free or has DIB == 0. */ - for (right = next_idx(h, left); ; right = next_idx(h, right)) { - raw_dib = dibs[right]; - if (raw_dib == 0 || raw_dib == DIB_RAW_FREE) - break; - - /* The buckets are not supposed to be all occupied and with DIB > 0. - * That would mean we could make everyone better off by shifting them - * backward. This scenario is impossible. */ - assert(left != right); - } - - if (h->type == HASHMAP_TYPE_ORDERED) { - OrderedHashmap *lh = (OrderedHashmap*) h; - struct ordered_hashmap_entry *le = ordered_bucket_at(lh, idx); - - if (le->iterate_next != IDX_NIL) - ordered_bucket_at(lh, le->iterate_next)->iterate_previous = le->iterate_previous; - else - lh->iterate_list_tail = le->iterate_previous; - - if (le->iterate_previous != IDX_NIL) - ordered_bucket_at(lh, le->iterate_previous)->iterate_next = le->iterate_next; - else - lh->iterate_list_head = le->iterate_next; - } - - /* Now shift all buckets in the interval (left, right) one step backwards */ - for (prev = left, left = next_idx(h, left); left != right; - prev = left, left = next_idx(h, left)) { - dib = bucket_calculate_dib(h, left, dibs[left]); - assert(dib != 0); - bucket_move_entry(h, NULL, left, prev); - bucket_set_dib(h, prev, dib - 1); - } - - bucket_mark_free(h, prev); - n_entries_dec(h); -} -#define remove_entry(h, idx) base_remove_entry(HASHMAP_BASE(h), idx) - -static unsigned hashmap_iterate_in_insertion_order(OrderedHashmap *h, Iterator *i) { - struct ordered_hashmap_entry *e; - unsigned idx; - - assert(h); - assert(i); - - if (i->idx == IDX_NIL) - goto at_end; - - if (i->idx == IDX_FIRST && h->iterate_list_head == IDX_NIL) - goto at_end; - - if (i->idx == IDX_FIRST) { - idx = h->iterate_list_head; - e = ordered_bucket_at(h, idx); - } else { - idx = i->idx; - e = ordered_bucket_at(h, idx); - /* - * We allow removing the current entry while iterating, but removal may cause - * a backward shift. The next entry may thus move one bucket to the left. - * To detect when it happens, we remember the key pointer of the entry we were - * going to iterate next. If it does not match, there was a backward shift. - */ - if (e->p.b.key != i->next_key) { - idx = prev_idx(HASHMAP_BASE(h), idx); - e = ordered_bucket_at(h, idx); - } - assert(e->p.b.key == i->next_key); - } - -#ifdef ENABLE_DEBUG_HASHMAP - i->prev_idx = idx; -#endif - - if (e->iterate_next != IDX_NIL) { - struct ordered_hashmap_entry *n; - i->idx = e->iterate_next; - n = ordered_bucket_at(h, i->idx); - i->next_key = n->p.b.key; - } else - i->idx = IDX_NIL; - - return idx; - -at_end: - i->idx = IDX_NIL; - return IDX_NIL; -} - -static unsigned hashmap_iterate_in_internal_order(HashmapBase *h, Iterator *i) { - unsigned idx; - - assert(h); - assert(i); - - if (i->idx == IDX_NIL) - goto at_end; - - if (i->idx == IDX_FIRST) { - /* fast forward to the first occupied bucket */ - if (h->has_indirect) { - i->idx = skip_free_buckets(h, h->indirect.idx_lowest_entry); - h->indirect.idx_lowest_entry = i->idx; - } else - i->idx = skip_free_buckets(h, 0); - - if (i->idx == IDX_NIL) - goto at_end; - } else { - struct hashmap_base_entry *e; - - assert(i->idx > 0); - - e = bucket_at(h, i->idx); - /* - * We allow removing the current entry while iterating, but removal may cause - * a backward shift. The next entry may thus move one bucket to the left. - * To detect when it happens, we remember the key pointer of the entry we were - * going to iterate next. If it does not match, there was a backward shift. - */ - if (e->key != i->next_key) - e = bucket_at(h, --i->idx); - - assert(e->key == i->next_key); - } - - idx = i->idx; -#ifdef ENABLE_DEBUG_HASHMAP - i->prev_idx = idx; -#endif - - i->idx = skip_free_buckets(h, i->idx + 1); - if (i->idx != IDX_NIL) - i->next_key = bucket_at(h, i->idx)->key; - else - i->idx = IDX_NIL; - - return idx; - -at_end: - i->idx = IDX_NIL; - return IDX_NIL; -} - -static unsigned hashmap_iterate_entry(HashmapBase *h, Iterator *i) { - if (!h) { - i->idx = IDX_NIL; - return IDX_NIL; - } - -#ifdef ENABLE_DEBUG_HASHMAP - if (i->idx == IDX_FIRST) { - i->put_count = h->debug.put_count; - i->rem_count = h->debug.rem_count; - } else { - /* While iterating, must not add any new entries */ - assert(i->put_count == h->debug.put_count); - /* ... or remove entries other than the current one */ - assert(i->rem_count == h->debug.rem_count || - (i->rem_count == h->debug.rem_count - 1 && - i->prev_idx == h->debug.last_rem_idx)); - /* Reset our removals counter */ - i->rem_count = h->debug.rem_count; - } -#endif - - return h->type == HASHMAP_TYPE_ORDERED ? hashmap_iterate_in_insertion_order((OrderedHashmap*) h, i) - : hashmap_iterate_in_internal_order(h, i); -} - -bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key) { - struct hashmap_base_entry *e; - void *data; - unsigned idx; - - idx = hashmap_iterate_entry(h, i); - if (idx == IDX_NIL) { - if (value) - *value = NULL; - if (key) - *key = NULL; - - return false; - } - - e = bucket_at(h, idx); - data = entry_value(h, e); - if (value) - *value = data; - if (key) - *key = e->key; - - return true; -} - -bool set_iterate(Set *s, Iterator *i, void **value) { - return internal_hashmap_iterate(HASHMAP_BASE(s), i, value, NULL); -} - -#define HASHMAP_FOREACH_IDX(idx, h, i) \ - for ((i) = ITERATOR_FIRST, (idx) = hashmap_iterate_entry((h), &(i)); \ - (idx != IDX_NIL); \ - (idx) = hashmap_iterate_entry((h), &(i))) - -static void reset_direct_storage(HashmapBase *h) { - const struct hashmap_type_info *hi = &hashmap_type_info[h->type]; - void *p; - - assert(!h->has_indirect); - - p = mempset(h->direct.storage, 0, hi->entry_size * hi->n_direct_buckets); - memset(p, DIB_RAW_INIT, sizeof(dib_raw_t) * hi->n_direct_buckets); -} - -static struct HashmapBase *hashmap_base_new(const struct hash_ops *hash_ops, enum HashmapType type HASHMAP_DEBUG_PARAMS) { - HashmapBase *h; - const struct hashmap_type_info *hi = &hashmap_type_info[type]; - bool use_pool; - - use_pool = is_main_thread(); - - h = use_pool ? mempool_alloc0_tile(hi->mempool) : malloc0(hi->head_size); - - if (!h) - return NULL; - - h->type = type; - h->from_pool = use_pool; - h->hash_ops = hash_ops ? hash_ops : &trivial_hash_ops; - - if (type == HASHMAP_TYPE_ORDERED) { - OrderedHashmap *lh = (OrderedHashmap*)h; - lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL; - } - - reset_direct_storage(h); - - if (!shared_hash_key_initialized) { - random_bytes(shared_hash_key, sizeof(shared_hash_key)); - shared_hash_key_initialized= true; - } - -#ifdef ENABLE_DEBUG_HASHMAP - h->debug.func = func; - h->debug.file = file; - h->debug.line = line; - assert_se(pthread_mutex_lock(&hashmap_debug_list_mutex) == 0); - LIST_PREPEND(debug_list, hashmap_debug_list, &h->debug); - assert_se(pthread_mutex_unlock(&hashmap_debug_list_mutex) == 0); -#endif - - return h; -} - -Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { - return (Hashmap*) hashmap_base_new(hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS); -} - -OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { - return (OrderedHashmap*) hashmap_base_new(hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS); -} - -Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { - return (Set*) hashmap_base_new(hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS); -} - -static int hashmap_base_ensure_allocated(HashmapBase **h, const struct hash_ops *hash_ops, - enum HashmapType type HASHMAP_DEBUG_PARAMS) { - HashmapBase *q; - - assert(h); - - if (*h) - return 0; - - q = hashmap_base_new(hash_ops, type HASHMAP_DEBUG_PASS_ARGS); - if (!q) - return -ENOMEM; - - *h = q; - return 0; -} - -int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { - return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS); -} - -int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { - return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS); -} - -int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { - return hashmap_base_ensure_allocated((HashmapBase**)s, hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS); -} - -static void hashmap_free_no_clear(HashmapBase *h) { - assert(!h->has_indirect); - assert(!h->n_direct_entries); - -#ifdef ENABLE_DEBUG_HASHMAP - assert_se(pthread_mutex_lock(&hashmap_debug_list_mutex) == 0); - LIST_REMOVE(debug_list, hashmap_debug_list, &h->debug); - assert_se(pthread_mutex_unlock(&hashmap_debug_list_mutex) == 0); -#endif - - if (h->from_pool) - mempool_free_tile(hashmap_type_info[h->type].mempool, h); - else - free(h); -} - -HashmapBase *internal_hashmap_free(HashmapBase *h) { - - /* Free the hashmap, but nothing in it */ - - if (h) { - internal_hashmap_clear(h); - hashmap_free_no_clear(h); - } - - return NULL; -} - -HashmapBase *internal_hashmap_free_free(HashmapBase *h) { - - /* Free the hashmap and all data objects in it, but not the - * keys */ - - if (h) { - internal_hashmap_clear_free(h); - hashmap_free_no_clear(h); - } - - return NULL; -} - -Hashmap *hashmap_free_free_free(Hashmap *h) { - - /* Free the hashmap and all data and key objects in it */ - - if (h) { - hashmap_clear_free_free(h); - hashmap_free_no_clear(HASHMAP_BASE(h)); - } - - return NULL; -} - -void internal_hashmap_clear(HashmapBase *h) { - if (!h) - return; - - if (h->has_indirect) { - free(h->indirect.storage); - h->has_indirect = false; - } - - h->n_direct_entries = 0; - reset_direct_storage(h); - - if (h->type == HASHMAP_TYPE_ORDERED) { - OrderedHashmap *lh = (OrderedHashmap*) h; - lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL; - } -} - -void internal_hashmap_clear_free(HashmapBase *h) { - unsigned idx; - - if (!h) - return; - - for (idx = skip_free_buckets(h, 0); idx != IDX_NIL; - idx = skip_free_buckets(h, idx + 1)) - free(entry_value(h, bucket_at(h, idx))); - - internal_hashmap_clear(h); -} - -void hashmap_clear_free_free(Hashmap *h) { - unsigned idx; - - if (!h) - return; - - for (idx = skip_free_buckets(HASHMAP_BASE(h), 0); idx != IDX_NIL; - idx = skip_free_buckets(HASHMAP_BASE(h), idx + 1)) { - struct plain_hashmap_entry *e = plain_bucket_at(h, idx); - free((void*)e->b.key); - free(e->value); - } - - internal_hashmap_clear(HASHMAP_BASE(h)); -} - -static int resize_buckets(HashmapBase *h, unsigned entries_add); - -/* - * Finds an empty bucket to put an entry into, starting the scan at 'idx'. - * Performs Robin Hood swaps as it goes. The entry to put must be placed - * by the caller into swap slot IDX_PUT. - * If used for in-place resizing, may leave a displaced entry in swap slot - * IDX_PUT. Caller must rehash it next. - * Returns: true if it left a displaced entry to rehash next in IDX_PUT, - * false otherwise. - */ -static bool hashmap_put_robin_hood(HashmapBase *h, unsigned idx, - struct swap_entries *swap) { - dib_raw_t raw_dib, *dibs; - unsigned dib, distance; - -#ifdef ENABLE_DEBUG_HASHMAP - h->debug.put_count++; -#endif - - dibs = dib_raw_ptr(h); - - for (distance = 0; ; distance++) { - raw_dib = dibs[idx]; - if (raw_dib == DIB_RAW_FREE || raw_dib == DIB_RAW_REHASH) { - if (raw_dib == DIB_RAW_REHASH) - bucket_move_entry(h, swap, idx, IDX_TMP); - - if (h->has_indirect && h->indirect.idx_lowest_entry > idx) - h->indirect.idx_lowest_entry = idx; - - bucket_set_dib(h, idx, distance); - bucket_move_entry(h, swap, IDX_PUT, idx); - if (raw_dib == DIB_RAW_REHASH) { - bucket_move_entry(h, swap, IDX_TMP, IDX_PUT); - return true; - } - - return false; - } - - dib = bucket_calculate_dib(h, idx, raw_dib); - - if (dib < distance) { - /* Found a wealthier entry. Go Robin Hood! */ - bucket_set_dib(h, idx, distance); - - /* swap the entries */ - bucket_move_entry(h, swap, idx, IDX_TMP); - bucket_move_entry(h, swap, IDX_PUT, idx); - bucket_move_entry(h, swap, IDX_TMP, IDX_PUT); - - distance = dib; - } - - idx = next_idx(h, idx); - } -} - -/* - * Puts an entry into a hashmap, boldly - no check whether key already exists. - * The caller must place the entry (only its key and value, not link indexes) - * in swap slot IDX_PUT. - * Caller must ensure: the key does not exist yet in the hashmap. - * that resize is not needed if !may_resize. - * Returns: 1 if entry was put successfully. - * -ENOMEM if may_resize==true and resize failed with -ENOMEM. - * Cannot return -ENOMEM if !may_resize. - */ -static int hashmap_base_put_boldly(HashmapBase *h, unsigned idx, - struct swap_entries *swap, bool may_resize) { - struct ordered_hashmap_entry *new_entry; - int r; - - assert(idx < n_buckets(h)); - - new_entry = bucket_at_swap(swap, IDX_PUT); - - if (may_resize) { - r = resize_buckets(h, 1); - if (r < 0) - return r; - if (r > 0) - idx = bucket_hash(h, new_entry->p.b.key); - } - assert(n_entries(h) < n_buckets(h)); - - if (h->type == HASHMAP_TYPE_ORDERED) { - OrderedHashmap *lh = (OrderedHashmap*) h; - - new_entry->iterate_next = IDX_NIL; - new_entry->iterate_previous = lh->iterate_list_tail; - - if (lh->iterate_list_tail != IDX_NIL) { - struct ordered_hashmap_entry *old_tail; - - old_tail = ordered_bucket_at(lh, lh->iterate_list_tail); - assert(old_tail->iterate_next == IDX_NIL); - old_tail->iterate_next = IDX_PUT; - } - - lh->iterate_list_tail = IDX_PUT; - if (lh->iterate_list_head == IDX_NIL) - lh->iterate_list_head = IDX_PUT; - } - - assert_se(hashmap_put_robin_hood(h, idx, swap) == false); - - n_entries_inc(h); -#ifdef ENABLE_DEBUG_HASHMAP - h->debug.max_entries = MAX(h->debug.max_entries, n_entries(h)); -#endif - - return 1; -} -#define hashmap_put_boldly(h, idx, swap, may_resize) \ - hashmap_base_put_boldly(HASHMAP_BASE(h), idx, swap, may_resize) - -/* - * Returns 0 if resize is not needed. - * 1 if successfully resized. - * -ENOMEM on allocation failure. - */ -static int resize_buckets(HashmapBase *h, unsigned entries_add) { - struct swap_entries swap; - void *new_storage; - dib_raw_t *old_dibs, *new_dibs; - const struct hashmap_type_info *hi; - unsigned idx, optimal_idx; - unsigned old_n_buckets, new_n_buckets, n_rehashed, new_n_entries; - uint8_t new_shift; - bool rehash_next; - - assert(h); - - hi = &hashmap_type_info[h->type]; - new_n_entries = n_entries(h) + entries_add; - - /* overflow? */ - if (_unlikely_(new_n_entries < entries_add)) - return -ENOMEM; - - /* For direct storage we allow 100% load, because it's tiny. */ - if (!h->has_indirect && new_n_entries <= hi->n_direct_buckets) - return 0; - - /* - * Load factor = n/m = 1 - (1/INV_KEEP_FREE). - * From it follows: m = n + n/(INV_KEEP_FREE - 1) - */ - new_n_buckets = new_n_entries + new_n_entries / (INV_KEEP_FREE - 1); - /* overflow? */ - if (_unlikely_(new_n_buckets < new_n_entries)) - return -ENOMEM; - - if (_unlikely_(new_n_buckets > UINT_MAX / (hi->entry_size + sizeof(dib_raw_t)))) - return -ENOMEM; - - old_n_buckets = n_buckets(h); - - if (_likely_(new_n_buckets <= old_n_buckets)) - return 0; - - new_shift = log2u_round_up(MAX( - new_n_buckets * (hi->entry_size + sizeof(dib_raw_t)), - 2 * sizeof(struct direct_storage))); - - /* Realloc storage (buckets and DIB array). */ - new_storage = realloc(h->has_indirect ? h->indirect.storage : NULL, - 1U << new_shift); - if (!new_storage) - return -ENOMEM; - - /* Must upgrade direct to indirect storage. */ - if (!h->has_indirect) { - memcpy(new_storage, h->direct.storage, - old_n_buckets * (hi->entry_size + sizeof(dib_raw_t))); - h->indirect.n_entries = h->n_direct_entries; - h->indirect.idx_lowest_entry = 0; - h->n_direct_entries = 0; - } - - /* Get a new hash key. If we've just upgraded to indirect storage, - * allow reusing a previously generated key. It's still a different key - * from the shared one that we used for direct storage. */ - get_hash_key(h->indirect.hash_key, !h->has_indirect); - - h->has_indirect = true; - h->indirect.storage = new_storage; - h->indirect.n_buckets = (1U << new_shift) / - (hi->entry_size + sizeof(dib_raw_t)); - - old_dibs = (dib_raw_t*)((uint8_t*) new_storage + hi->entry_size * old_n_buckets); - new_dibs = dib_raw_ptr(h); - - /* - * Move the DIB array to the new place, replacing valid DIB values with - * DIB_RAW_REHASH to indicate all of the used buckets need rehashing. - * Note: Overlap is not possible, because we have at least doubled the - * number of buckets and dib_raw_t is smaller than any entry type. - */ - for (idx = 0; idx < old_n_buckets; idx++) { - assert(old_dibs[idx] != DIB_RAW_REHASH); - new_dibs[idx] = old_dibs[idx] == DIB_RAW_FREE ? DIB_RAW_FREE - : DIB_RAW_REHASH; - } - - /* Zero the area of newly added entries (including the old DIB area) */ - memzero(bucket_at(h, old_n_buckets), - (n_buckets(h) - old_n_buckets) * hi->entry_size); - - /* The upper half of the new DIB array needs initialization */ - memset(&new_dibs[old_n_buckets], DIB_RAW_INIT, - (n_buckets(h) - old_n_buckets) * sizeof(dib_raw_t)); - - /* Rehash entries that need it */ - n_rehashed = 0; - for (idx = 0; idx < old_n_buckets; idx++) { - if (new_dibs[idx] != DIB_RAW_REHASH) - continue; - - optimal_idx = bucket_hash(h, bucket_at(h, idx)->key); - - /* - * Not much to do if by luck the entry hashes to its current - * location. Just set its DIB. - */ - if (optimal_idx == idx) { - new_dibs[idx] = 0; - n_rehashed++; - continue; - } - - new_dibs[idx] = DIB_RAW_FREE; - bucket_move_entry(h, &swap, idx, IDX_PUT); - /* bucket_move_entry does not clear the source */ - memzero(bucket_at(h, idx), hi->entry_size); - - do { - /* - * Find the new bucket for the current entry. This may make - * another entry homeless and load it into IDX_PUT. - */ - rehash_next = hashmap_put_robin_hood(h, optimal_idx, &swap); - n_rehashed++; - - /* Did the current entry displace another one? */ - if (rehash_next) - optimal_idx = bucket_hash(h, bucket_at_swap(&swap, IDX_PUT)->p.b.key); - } while (rehash_next); - } - - assert(n_rehashed == n_entries(h)); - - return 1; -} - -/* - * Finds an entry with a matching key - * Returns: index of the found entry, or IDX_NIL if not found. - */ -static unsigned base_bucket_scan(HashmapBase *h, unsigned idx, const void *key) { - struct hashmap_base_entry *e; - unsigned dib, distance; - dib_raw_t *dibs = dib_raw_ptr(h); - - assert(idx < n_buckets(h)); - - for (distance = 0; ; distance++) { - if (dibs[idx] == DIB_RAW_FREE) - return IDX_NIL; - - dib = bucket_calculate_dib(h, idx, dibs[idx]); - - if (dib < distance) - return IDX_NIL; - if (dib == distance) { - e = bucket_at(h, idx); - if (h->hash_ops->compare(e->key, key) == 0) - return idx; - } - - idx = next_idx(h, idx); - } -} -#define bucket_scan(h, idx, key) base_bucket_scan(HASHMAP_BASE(h), idx, key) - -int hashmap_put(Hashmap *h, const void *key, void *value) { - struct swap_entries swap; - struct plain_hashmap_entry *e; - unsigned hash, idx; - - assert(h); - - hash = bucket_hash(h, key); - idx = bucket_scan(h, hash, key); - if (idx != IDX_NIL) { - e = plain_bucket_at(h, idx); - if (e->value == value) - return 0; - return -EEXIST; - } - - e = &bucket_at_swap(&swap, IDX_PUT)->p; - e->b.key = key; - e->value = value; - return hashmap_put_boldly(h, hash, &swap, true); -} - -int set_put(Set *s, const void *key) { - struct swap_entries swap; - struct hashmap_base_entry *e; - unsigned hash, idx; - - assert(s); - - hash = bucket_hash(s, key); - idx = bucket_scan(s, hash, key); - if (idx != IDX_NIL) - return 0; - - e = &bucket_at_swap(&swap, IDX_PUT)->p.b; - e->key = key; - return hashmap_put_boldly(s, hash, &swap, true); -} - -int hashmap_replace(Hashmap *h, const void *key, void *value) { - struct swap_entries swap; - struct plain_hashmap_entry *e; - unsigned hash, idx; - - assert(h); - - hash = bucket_hash(h, key); - idx = bucket_scan(h, hash, key); - if (idx != IDX_NIL) { - e = plain_bucket_at(h, idx); -#ifdef ENABLE_DEBUG_HASHMAP - /* Although the key is equal, the key pointer may have changed, - * and this would break our assumption for iterating. So count - * this operation as incompatible with iteration. */ - if (e->b.key != key) { - h->b.debug.put_count++; - h->b.debug.rem_count++; - h->b.debug.last_rem_idx = idx; - } -#endif - e->b.key = key; - e->value = value; - return 0; - } - - e = &bucket_at_swap(&swap, IDX_PUT)->p; - e->b.key = key; - e->value = value; - return hashmap_put_boldly(h, hash, &swap, true); -} - -int hashmap_update(Hashmap *h, const void *key, void *value) { - struct plain_hashmap_entry *e; - unsigned hash, idx; - - assert(h); - - hash = bucket_hash(h, key); - idx = bucket_scan(h, hash, key); - if (idx == IDX_NIL) - return -ENOENT; - - e = plain_bucket_at(h, idx); - e->value = value; - return 0; -} - -void *internal_hashmap_get(HashmapBase *h, const void *key) { - struct hashmap_base_entry *e; - unsigned hash, idx; - - if (!h) - return NULL; - - hash = bucket_hash(h, key); - idx = bucket_scan(h, hash, key); - if (idx == IDX_NIL) - return NULL; - - e = bucket_at(h, idx); - return entry_value(h, e); -} - -void *hashmap_get2(Hashmap *h, const void *key, void **key2) { - struct plain_hashmap_entry *e; - unsigned hash, idx; - - if (!h) - return NULL; - - hash = bucket_hash(h, key); - idx = bucket_scan(h, hash, key); - if (idx == IDX_NIL) - return NULL; - - e = plain_bucket_at(h, idx); - if (key2) - *key2 = (void*) e->b.key; - - return e->value; -} - -bool internal_hashmap_contains(HashmapBase *h, const void *key) { - unsigned hash; - - if (!h) - return false; - - hash = bucket_hash(h, key); - return bucket_scan(h, hash, key) != IDX_NIL; -} - -void *internal_hashmap_remove(HashmapBase *h, const void *key) { - struct hashmap_base_entry *e; - unsigned hash, idx; - void *data; - - if (!h) - return NULL; - - hash = bucket_hash(h, key); - idx = bucket_scan(h, hash, key); - if (idx == IDX_NIL) - return NULL; - - e = bucket_at(h, idx); - data = entry_value(h, e); - remove_entry(h, idx); - - return data; -} - -void *hashmap_remove2(Hashmap *h, const void *key, void **rkey) { - struct plain_hashmap_entry *e; - unsigned hash, idx; - void *data; - - if (!h) { - if (rkey) - *rkey = NULL; - return NULL; - } - - hash = bucket_hash(h, key); - idx = bucket_scan(h, hash, key); - if (idx == IDX_NIL) { - if (rkey) - *rkey = NULL; - return NULL; - } - - e = plain_bucket_at(h, idx); - data = e->value; - if (rkey) - *rkey = (void*) e->b.key; - - remove_entry(h, idx); - - return data; -} - -int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value) { - struct swap_entries swap; - struct plain_hashmap_entry *e; - unsigned old_hash, new_hash, idx; - - if (!h) - return -ENOENT; - - old_hash = bucket_hash(h, old_key); - idx = bucket_scan(h, old_hash, old_key); - if (idx == IDX_NIL) - return -ENOENT; - - new_hash = bucket_hash(h, new_key); - if (bucket_scan(h, new_hash, new_key) != IDX_NIL) - return -EEXIST; - - remove_entry(h, idx); - - e = &bucket_at_swap(&swap, IDX_PUT)->p; - e->b.key = new_key; - e->value = value; - assert_se(hashmap_put_boldly(h, new_hash, &swap, false) == 1); - - return 0; -} - -int set_remove_and_put(Set *s, const void *old_key, const void *new_key) { - struct swap_entries swap; - struct hashmap_base_entry *e; - unsigned old_hash, new_hash, idx; - - if (!s) - return -ENOENT; - - old_hash = bucket_hash(s, old_key); - idx = bucket_scan(s, old_hash, old_key); - if (idx == IDX_NIL) - return -ENOENT; - - new_hash = bucket_hash(s, new_key); - if (bucket_scan(s, new_hash, new_key) != IDX_NIL) - return -EEXIST; - - remove_entry(s, idx); - - e = &bucket_at_swap(&swap, IDX_PUT)->p.b; - e->key = new_key; - assert_se(hashmap_put_boldly(s, new_hash, &swap, false) == 1); - - return 0; -} - -int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value) { - struct swap_entries swap; - struct plain_hashmap_entry *e; - unsigned old_hash, new_hash, idx_old, idx_new; - - if (!h) - return -ENOENT; - - old_hash = bucket_hash(h, old_key); - idx_old = bucket_scan(h, old_hash, old_key); - if (idx_old == IDX_NIL) - return -ENOENT; - - old_key = bucket_at(HASHMAP_BASE(h), idx_old)->key; - - new_hash = bucket_hash(h, new_key); - idx_new = bucket_scan(h, new_hash, new_key); - if (idx_new != IDX_NIL) - if (idx_old != idx_new) { - remove_entry(h, idx_new); - /* Compensate for a possible backward shift. */ - if (old_key != bucket_at(HASHMAP_BASE(h), idx_old)->key) - idx_old = prev_idx(HASHMAP_BASE(h), idx_old); - assert(old_key == bucket_at(HASHMAP_BASE(h), idx_old)->key); - } - - remove_entry(h, idx_old); - - e = &bucket_at_swap(&swap, IDX_PUT)->p; - e->b.key = new_key; - e->value = value; - assert_se(hashmap_put_boldly(h, new_hash, &swap, false) == 1); - - return 0; -} - -void *hashmap_remove_value(Hashmap *h, const void *key, void *value) { - struct plain_hashmap_entry *e; - unsigned hash, idx; - - if (!h) - return NULL; - - hash = bucket_hash(h, key); - idx = bucket_scan(h, hash, key); - if (idx == IDX_NIL) - return NULL; - - e = plain_bucket_at(h, idx); - if (e->value != value) - return NULL; - - remove_entry(h, idx); - - return value; -} - -static unsigned find_first_entry(HashmapBase *h) { - Iterator i = ITERATOR_FIRST; - - if (!h || !n_entries(h)) - return IDX_NIL; - - return hashmap_iterate_entry(h, &i); -} - -void *internal_hashmap_first(HashmapBase *h) { - unsigned idx; - - idx = find_first_entry(h); - if (idx == IDX_NIL) - return NULL; - - return entry_value(h, bucket_at(h, idx)); -} - -void *internal_hashmap_first_key(HashmapBase *h) { - struct hashmap_base_entry *e; - unsigned idx; - - idx = find_first_entry(h); - if (idx == IDX_NIL) - return NULL; - - e = bucket_at(h, idx); - return (void*) e->key; -} - -void *internal_hashmap_steal_first(HashmapBase *h) { - struct hashmap_base_entry *e; - void *data; - unsigned idx; - - idx = find_first_entry(h); - if (idx == IDX_NIL) - return NULL; - - e = bucket_at(h, idx); - data = entry_value(h, e); - remove_entry(h, idx); - - return data; -} - -void *internal_hashmap_steal_first_key(HashmapBase *h) { - struct hashmap_base_entry *e; - void *key; - unsigned idx; - - idx = find_first_entry(h); - if (idx == IDX_NIL) - return NULL; - - e = bucket_at(h, idx); - key = (void*) e->key; - remove_entry(h, idx); - - return key; -} - -unsigned internal_hashmap_size(HashmapBase *h) { - - if (!h) - return 0; - - return n_entries(h); -} - -unsigned internal_hashmap_buckets(HashmapBase *h) { - - if (!h) - return 0; - - return n_buckets(h); -} - -int internal_hashmap_merge(Hashmap *h, Hashmap *other) { - Iterator i; - unsigned idx; - - assert(h); - - HASHMAP_FOREACH_IDX(idx, HASHMAP_BASE(other), i) { - struct plain_hashmap_entry *pe = plain_bucket_at(other, idx); - int r; - - r = hashmap_put(h, pe->b.key, pe->value); - if (r < 0 && r != -EEXIST) - return r; - } - - return 0; -} - -int set_merge(Set *s, Set *other) { - Iterator i; - unsigned idx; - - assert(s); - - HASHMAP_FOREACH_IDX(idx, HASHMAP_BASE(other), i) { - struct set_entry *se = set_bucket_at(other, idx); - int r; - - r = set_put(s, se->b.key); - if (r < 0) - return r; - } - - return 0; -} - -int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add) { - int r; - - assert(h); - - r = resize_buckets(h, entries_add); - if (r < 0) - return r; - - return 0; -} - -/* - * The same as hashmap_merge(), but every new item from other is moved to h. - * Keys already in h are skipped and stay in other. - * Returns: 0 on success. - * -ENOMEM on alloc failure, in which case no move has been done. - */ -int internal_hashmap_move(HashmapBase *h, HashmapBase *other) { - struct swap_entries swap; - struct hashmap_base_entry *e, *n; - Iterator i; - unsigned idx; - int r; - - assert(h); - - if (!other) - return 0; - - assert(other->type == h->type); - - /* - * This reserves buckets for the worst case, where none of other's - * entries are yet present in h. This is preferable to risking - * an allocation failure in the middle of the moving and having to - * rollback or return a partial result. - */ - r = resize_buckets(h, n_entries(other)); - if (r < 0) - return r; - - HASHMAP_FOREACH_IDX(idx, other, i) { - unsigned h_hash; - - e = bucket_at(other, idx); - h_hash = bucket_hash(h, e->key); - if (bucket_scan(h, h_hash, e->key) != IDX_NIL) - continue; - - n = &bucket_at_swap(&swap, IDX_PUT)->p.b; - n->key = e->key; - if (h->type != HASHMAP_TYPE_SET) - ((struct plain_hashmap_entry*) n)->value = - ((struct plain_hashmap_entry*) e)->value; - assert_se(hashmap_put_boldly(h, h_hash, &swap, false) == 1); - - remove_entry(other, idx); - } - - return 0; -} - -int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key) { - struct swap_entries swap; - unsigned h_hash, other_hash, idx; - struct hashmap_base_entry *e, *n; - int r; - - assert(h); - - h_hash = bucket_hash(h, key); - if (bucket_scan(h, h_hash, key) != IDX_NIL) - return -EEXIST; - - if (!other) - return -ENOENT; - - assert(other->type == h->type); - - other_hash = bucket_hash(other, key); - idx = bucket_scan(other, other_hash, key); - if (idx == IDX_NIL) - return -ENOENT; - - e = bucket_at(other, idx); - - n = &bucket_at_swap(&swap, IDX_PUT)->p.b; - n->key = e->key; - if (h->type != HASHMAP_TYPE_SET) - ((struct plain_hashmap_entry*) n)->value = - ((struct plain_hashmap_entry*) e)->value; - r = hashmap_put_boldly(h, h_hash, &swap, true); - if (r < 0) - return r; - - remove_entry(other, idx); - return 0; -} - -HashmapBase *internal_hashmap_copy(HashmapBase *h) { - HashmapBase *copy; - int r; - - assert(h); - - copy = hashmap_base_new(h->hash_ops, h->type HASHMAP_DEBUG_SRC_ARGS); - if (!copy) - return NULL; - - switch (h->type) { - case HASHMAP_TYPE_PLAIN: - case HASHMAP_TYPE_ORDERED: - r = hashmap_merge((Hashmap*)copy, (Hashmap*)h); - break; - case HASHMAP_TYPE_SET: - r = set_merge((Set*)copy, (Set*)h); - break; - default: - assert_not_reached("Unknown hashmap type"); - } - - if (r < 0) { - internal_hashmap_free(copy); - return NULL; - } - - return copy; -} - -char **internal_hashmap_get_strv(HashmapBase *h) { - char **sv; - Iterator i; - unsigned idx, n; - - sv = new(char*, n_entries(h)+1); - if (!sv) - return NULL; - - n = 0; - HASHMAP_FOREACH_IDX(idx, h, i) - sv[n++] = entry_value(h, bucket_at(h, idx)); - sv[n] = NULL; - - return sv; -} - -void *ordered_hashmap_next(OrderedHashmap *h, const void *key) { - struct ordered_hashmap_entry *e; - unsigned hash, idx; - - if (!h) - return NULL; - - hash = bucket_hash(h, key); - idx = bucket_scan(h, hash, key); - if (idx == IDX_NIL) - return NULL; - - e = ordered_bucket_at(h, idx); - if (e->iterate_next == IDX_NIL) - return NULL; - return ordered_bucket_at(h, e->iterate_next)->p.value; -} - -int set_consume(Set *s, void *value) { - int r; - - r = set_put(s, value); - if (r <= 0) - free(value); - - return r; -} - -int set_put_strdup(Set *s, const char *p) { - char *c; - - assert(s); - assert(p); - - if (set_contains(s, (char*) p)) - return 0; - - c = strdup(p); - if (!c) - return -ENOMEM; - - return set_consume(s, c); -} - -int set_put_strdupv(Set *s, char **l) { - int n = 0, r; - char **i; - - STRV_FOREACH(i, l) { - r = set_put_strdup(s, *i); - if (r < 0) - return r; - - n += r; - } - - return n; -} diff --git a/src/basic/hashmap.h b/src/basic/hashmap.h deleted file mode 100644 index 6d1ae48b21..0000000000 --- a/src/basic/hashmap.h +++ /dev/null @@ -1,372 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2014 Michal Schmidt - - 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 -#include - -#include "hash-funcs.h" -#include "macro.h" -#include "util.h" - -/* - * A hash table implementation. As a minor optimization a NULL hashmap object - * will be treated as empty hashmap for all read operations. That way it is not - * necessary to instantiate an object for each Hashmap use. - * - * If ENABLE_DEBUG_HASHMAP is defined (by configuring with --enable-debug=hashmap), - * the implemention will: - * - store extra data for debugging and statistics (see tools/gdb-sd_dump_hashmaps.py) - * - perform extra checks for invalid use of iterators - */ - -#define HASH_KEY_SIZE 16 - -/* The base type for all hashmap and set types. Many functions in the - * implementation take (HashmapBase*) parameters and are run-time polymorphic, - * though the API is not meant to be polymorphic (do not call functions - * internal_*() directly). */ -typedef struct HashmapBase HashmapBase; - -/* Specific hashmap/set types */ -typedef struct Hashmap Hashmap; /* Maps keys to values */ -typedef struct OrderedHashmap OrderedHashmap; /* Like Hashmap, but also remembers entry insertion order */ -typedef struct Set Set; /* Stores just keys */ - -/* Ideally the Iterator would be an opaque struct, but it is instantiated - * by hashmap users, so the definition has to be here. Do not use its fields - * directly. */ -typedef struct { - unsigned idx; /* index of an entry to be iterated next */ - const void *next_key; /* expected value of that entry's key pointer */ -#ifdef ENABLE_DEBUG_HASHMAP - unsigned put_count; /* hashmap's put_count recorded at start of iteration */ - unsigned rem_count; /* hashmap's rem_count in previous iteration */ - unsigned prev_idx; /* idx in previous iteration */ -#endif -} Iterator; - -#define _IDX_ITERATOR_FIRST (UINT_MAX - 1) -#define ITERATOR_FIRST ((Iterator) { .idx = _IDX_ITERATOR_FIRST, .next_key = NULL }) - -/* Macros for type checking */ -#define PTR_COMPATIBLE_WITH_HASHMAP_BASE(h) \ - (__builtin_types_compatible_p(typeof(h), HashmapBase*) || \ - __builtin_types_compatible_p(typeof(h), Hashmap*) || \ - __builtin_types_compatible_p(typeof(h), OrderedHashmap*) || \ - __builtin_types_compatible_p(typeof(h), Set*)) - -#define PTR_COMPATIBLE_WITH_PLAIN_HASHMAP(h) \ - (__builtin_types_compatible_p(typeof(h), Hashmap*) || \ - __builtin_types_compatible_p(typeof(h), OrderedHashmap*)) \ - -#define HASHMAP_BASE(h) \ - __builtin_choose_expr(PTR_COMPATIBLE_WITH_HASHMAP_BASE(h), \ - (HashmapBase*)(h), \ - (void)0) - -#define PLAIN_HASHMAP(h) \ - __builtin_choose_expr(PTR_COMPATIBLE_WITH_PLAIN_HASHMAP(h), \ - (Hashmap*)(h), \ - (void)0) - -#ifdef ENABLE_DEBUG_HASHMAP -# define HASHMAP_DEBUG_PARAMS , const char *func, const char *file, int line -# define HASHMAP_DEBUG_SRC_ARGS , __func__, __FILE__, __LINE__ -# define HASHMAP_DEBUG_PASS_ARGS , func, file, line -#else -# define HASHMAP_DEBUG_PARAMS -# define HASHMAP_DEBUG_SRC_ARGS -# define HASHMAP_DEBUG_PASS_ARGS -#endif - -Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); -OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); -#define hashmap_new(ops) internal_hashmap_new(ops HASHMAP_DEBUG_SRC_ARGS) -#define ordered_hashmap_new(ops) internal_ordered_hashmap_new(ops HASHMAP_DEBUG_SRC_ARGS) - -HashmapBase *internal_hashmap_free(HashmapBase *h); -static inline Hashmap *hashmap_free(Hashmap *h) { - return (void*)internal_hashmap_free(HASHMAP_BASE(h)); -} -static inline OrderedHashmap *ordered_hashmap_free(OrderedHashmap *h) { - return (void*)internal_hashmap_free(HASHMAP_BASE(h)); -} - -HashmapBase *internal_hashmap_free_free(HashmapBase *h); -static inline Hashmap *hashmap_free_free(Hashmap *h) { - return (void*)internal_hashmap_free_free(HASHMAP_BASE(h)); -} -static inline OrderedHashmap *ordered_hashmap_free_free(OrderedHashmap *h) { - return (void*)internal_hashmap_free_free(HASHMAP_BASE(h)); -} - -Hashmap *hashmap_free_free_free(Hashmap *h); -static inline OrderedHashmap *ordered_hashmap_free_free_free(OrderedHashmap *h) { - return (void*)hashmap_free_free_free(PLAIN_HASHMAP(h)); -} - -HashmapBase *internal_hashmap_copy(HashmapBase *h); -static inline Hashmap *hashmap_copy(Hashmap *h) { - return (Hashmap*) internal_hashmap_copy(HASHMAP_BASE(h)); -} -static inline OrderedHashmap *ordered_hashmap_copy(OrderedHashmap *h) { - return (OrderedHashmap*) internal_hashmap_copy(HASHMAP_BASE(h)); -} - -int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); -int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); -#define hashmap_ensure_allocated(h, ops) internal_hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) -#define ordered_hashmap_ensure_allocated(h, ops) internal_ordered_hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) - -int hashmap_put(Hashmap *h, const void *key, void *value); -static inline int ordered_hashmap_put(OrderedHashmap *h, const void *key, void *value) { - return hashmap_put(PLAIN_HASHMAP(h), key, value); -} - -int hashmap_update(Hashmap *h, const void *key, void *value); -static inline int ordered_hashmap_update(OrderedHashmap *h, const void *key, void *value) { - return hashmap_update(PLAIN_HASHMAP(h), key, value); -} - -int hashmap_replace(Hashmap *h, const void *key, void *value); -static inline int ordered_hashmap_replace(OrderedHashmap *h, const void *key, void *value) { - return hashmap_replace(PLAIN_HASHMAP(h), key, value); -} - -void *internal_hashmap_get(HashmapBase *h, const void *key); -static inline void *hashmap_get(Hashmap *h, const void *key) { - return internal_hashmap_get(HASHMAP_BASE(h), key); -} -static inline void *ordered_hashmap_get(OrderedHashmap *h, const void *key) { - return internal_hashmap_get(HASHMAP_BASE(h), key); -} - -void *hashmap_get2(Hashmap *h, const void *key, void **rkey); -static inline void *ordered_hashmap_get2(OrderedHashmap *h, const void *key, void **rkey) { - return hashmap_get2(PLAIN_HASHMAP(h), key, rkey); -} - -bool internal_hashmap_contains(HashmapBase *h, const void *key); -static inline bool hashmap_contains(Hashmap *h, const void *key) { - return internal_hashmap_contains(HASHMAP_BASE(h), key); -} -static inline bool ordered_hashmap_contains(OrderedHashmap *h, const void *key) { - return internal_hashmap_contains(HASHMAP_BASE(h), key); -} - -void *internal_hashmap_remove(HashmapBase *h, const void *key); -static inline void *hashmap_remove(Hashmap *h, const void *key) { - return internal_hashmap_remove(HASHMAP_BASE(h), key); -} -static inline void *ordered_hashmap_remove(OrderedHashmap *h, const void *key) { - return internal_hashmap_remove(HASHMAP_BASE(h), key); -} - -void *hashmap_remove2(Hashmap *h, const void *key, void **rkey); -static inline void *ordered_hashmap_remove2(OrderedHashmap *h, const void *key, void **rkey) { - return hashmap_remove2(PLAIN_HASHMAP(h), key, rkey); -} - -void *hashmap_remove_value(Hashmap *h, const void *key, void *value); -static inline void *ordered_hashmap_remove_value(OrderedHashmap *h, const void *key, void *value) { - return hashmap_remove_value(PLAIN_HASHMAP(h), key, value); -} - -int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value); -static inline int ordered_hashmap_remove_and_put(OrderedHashmap *h, const void *old_key, const void *new_key, void *value) { - return hashmap_remove_and_put(PLAIN_HASHMAP(h), old_key, new_key, value); -} - -int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value); -static inline int ordered_hashmap_remove_and_replace(OrderedHashmap *h, const void *old_key, const void *new_key, void *value) { - return hashmap_remove_and_replace(PLAIN_HASHMAP(h), old_key, new_key, value); -} - -/* Since merging data from a OrderedHashmap into a Hashmap or vice-versa - * should just work, allow this by having looser type-checking here. */ -int internal_hashmap_merge(Hashmap *h, Hashmap *other); -#define hashmap_merge(h, other) internal_hashmap_merge(PLAIN_HASHMAP(h), PLAIN_HASHMAP(other)) -#define ordered_hashmap_merge(h, other) hashmap_merge(h, other) - -int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add); -static inline int hashmap_reserve(Hashmap *h, unsigned entries_add) { - return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add); -} -static inline int ordered_hashmap_reserve(OrderedHashmap *h, unsigned entries_add) { - return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add); -} - -int internal_hashmap_move(HashmapBase *h, HashmapBase *other); -/* Unlike hashmap_merge, hashmap_move does not allow mixing the types. */ -static inline int hashmap_move(Hashmap *h, Hashmap *other) { - return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other)); -} -static inline int ordered_hashmap_move(OrderedHashmap *h, OrderedHashmap *other) { - return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other)); -} - -int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key); -static inline int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key) { - return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key); -} -static inline int ordered_hashmap_move_one(OrderedHashmap *h, OrderedHashmap *other, const void *key) { - return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key); -} - -unsigned internal_hashmap_size(HashmapBase *h) _pure_; -static inline unsigned hashmap_size(Hashmap *h) { - return internal_hashmap_size(HASHMAP_BASE(h)); -} -static inline unsigned ordered_hashmap_size(OrderedHashmap *h) { - return internal_hashmap_size(HASHMAP_BASE(h)); -} - -static inline bool hashmap_isempty(Hashmap *h) { - return hashmap_size(h) == 0; -} -static inline bool ordered_hashmap_isempty(OrderedHashmap *h) { - return ordered_hashmap_size(h) == 0; -} - -unsigned internal_hashmap_buckets(HashmapBase *h) _pure_; -static inline unsigned hashmap_buckets(Hashmap *h) { - return internal_hashmap_buckets(HASHMAP_BASE(h)); -} -static inline unsigned ordered_hashmap_buckets(OrderedHashmap *h) { - return internal_hashmap_buckets(HASHMAP_BASE(h)); -} - -bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key); -static inline bool hashmap_iterate(Hashmap *h, Iterator *i, void **value, const void **key) { - return internal_hashmap_iterate(HASHMAP_BASE(h), i, value, key); -} -static inline bool ordered_hashmap_iterate(OrderedHashmap *h, Iterator *i, void **value, const void **key) { - return internal_hashmap_iterate(HASHMAP_BASE(h), i, value, key); -} - -void internal_hashmap_clear(HashmapBase *h); -static inline void hashmap_clear(Hashmap *h) { - internal_hashmap_clear(HASHMAP_BASE(h)); -} -static inline void ordered_hashmap_clear(OrderedHashmap *h) { - internal_hashmap_clear(HASHMAP_BASE(h)); -} - -void internal_hashmap_clear_free(HashmapBase *h); -static inline void hashmap_clear_free(Hashmap *h) { - internal_hashmap_clear_free(HASHMAP_BASE(h)); -} -static inline void ordered_hashmap_clear_free(OrderedHashmap *h) { - internal_hashmap_clear_free(HASHMAP_BASE(h)); -} - -void hashmap_clear_free_free(Hashmap *h); -static inline void ordered_hashmap_clear_free_free(OrderedHashmap *h) { - hashmap_clear_free_free(PLAIN_HASHMAP(h)); -} - -/* - * Note about all *_first*() functions - * - * For plain Hashmaps and Sets the order of entries is undefined. - * The functions find whatever entry is first in the implementation - * internal order. - * - * Only for OrderedHashmaps the order is well defined and finding - * the first entry is O(1). - */ - -void *internal_hashmap_steal_first(HashmapBase *h); -static inline void *hashmap_steal_first(Hashmap *h) { - return internal_hashmap_steal_first(HASHMAP_BASE(h)); -} -static inline void *ordered_hashmap_steal_first(OrderedHashmap *h) { - return internal_hashmap_steal_first(HASHMAP_BASE(h)); -} - -void *internal_hashmap_steal_first_key(HashmapBase *h); -static inline void *hashmap_steal_first_key(Hashmap *h) { - return internal_hashmap_steal_first_key(HASHMAP_BASE(h)); -} -static inline void *ordered_hashmap_steal_first_key(OrderedHashmap *h) { - return internal_hashmap_steal_first_key(HASHMAP_BASE(h)); -} - -void *internal_hashmap_first_key(HashmapBase *h) _pure_; -static inline void *hashmap_first_key(Hashmap *h) { - return internal_hashmap_first_key(HASHMAP_BASE(h)); -} -static inline void *ordered_hashmap_first_key(OrderedHashmap *h) { - return internal_hashmap_first_key(HASHMAP_BASE(h)); -} - -void *internal_hashmap_first(HashmapBase *h) _pure_; -static inline void *hashmap_first(Hashmap *h) { - return internal_hashmap_first(HASHMAP_BASE(h)); -} -static inline void *ordered_hashmap_first(OrderedHashmap *h) { - return internal_hashmap_first(HASHMAP_BASE(h)); -} - -/* no hashmap_next */ -void *ordered_hashmap_next(OrderedHashmap *h, const void *key); - -char **internal_hashmap_get_strv(HashmapBase *h); -static inline char **hashmap_get_strv(Hashmap *h) { - return internal_hashmap_get_strv(HASHMAP_BASE(h)); -} -static inline char **ordered_hashmap_get_strv(OrderedHashmap *h) { - return internal_hashmap_get_strv(HASHMAP_BASE(h)); -} - -/* - * Hashmaps are iterated in unpredictable order. - * OrderedHashmaps are an exception to this. They are iterated in the order - * the entries were inserted. - * It is safe to remove the current entry. - */ -#define HASHMAP_FOREACH(e, h, i) \ - for ((i) = ITERATOR_FIRST; hashmap_iterate((h), &(i), (void**)&(e), NULL); ) - -#define ORDERED_HASHMAP_FOREACH(e, h, i) \ - for ((i) = ITERATOR_FIRST; ordered_hashmap_iterate((h), &(i), (void**)&(e), NULL); ) - -#define HASHMAP_FOREACH_KEY(e, k, h, i) \ - for ((i) = ITERATOR_FIRST; hashmap_iterate((h), &(i), (void**)&(e), (const void**) &(k)); ) - -#define ORDERED_HASHMAP_FOREACH_KEY(e, k, h, i) \ - for ((i) = ITERATOR_FIRST; ordered_hashmap_iterate((h), &(i), (void**)&(e), (const void**) &(k)); ) - -DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_free); - -#define _cleanup_hashmap_free_ _cleanup_(hashmap_freep) -#define _cleanup_hashmap_free_free_ _cleanup_(hashmap_free_freep) -#define _cleanup_hashmap_free_free_free_ _cleanup_(hashmap_free_free_freep) -#define _cleanup_ordered_hashmap_free_ _cleanup_(ordered_hashmap_freep) -#define _cleanup_ordered_hashmap_free_free_ _cleanup_(ordered_hashmap_free_freep) -#define _cleanup_ordered_hashmap_free_free_free_ _cleanup_(ordered_hashmap_free_free_freep) diff --git a/src/basic/hexdecoct.c b/src/basic/hexdecoct.c deleted file mode 100644 index c5bda6c4d6..0000000000 --- a/src/basic/hexdecoct.c +++ /dev/null @@ -1,754 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include - -#include "alloc-util.h" -#include "hexdecoct.h" -#include "macro.h" -#include "util.h" - -char octchar(int x) { - return '0' + (x & 7); -} - -int unoctchar(char c) { - - if (c >= '0' && c <= '7') - return c - '0'; - - return -EINVAL; -} - -char decchar(int x) { - return '0' + (x % 10); -} - -int undecchar(char c) { - - if (c >= '0' && c <= '9') - return c - '0'; - - return -EINVAL; -} - -char hexchar(int x) { - static const char table[16] = "0123456789abcdef"; - - return table[x & 15]; -} - -int unhexchar(char c) { - - if (c >= '0' && c <= '9') - return c - '0'; - - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - - return -EINVAL; -} - -char *hexmem(const void *p, size_t l) { - char *r, *z; - const uint8_t *x; - - z = r = malloc(l * 2 + 1); - if (!r) - return NULL; - - for (x = p; x < (const uint8_t*) p + l; x++) { - *(z++) = hexchar(*x >> 4); - *(z++) = hexchar(*x & 15); - } - - *z = 0; - return r; -} - -int unhexmem(const char *p, size_t l, void **mem, size_t *len) { - _cleanup_free_ uint8_t *r = NULL; - uint8_t *z; - const char *x; - - assert(mem); - assert(len); - assert(p); - - z = r = malloc((l + 1) / 2 + 1); - if (!r) - return -ENOMEM; - - for (x = p; x < p + l; x += 2) { - int a, b; - - a = unhexchar(x[0]); - if (a < 0) - return a; - else if (x+1 < p + l) { - b = unhexchar(x[1]); - if (b < 0) - return b; - } else - b = 0; - - *(z++) = (uint8_t) a << 4 | (uint8_t) b; - } - - *z = 0; - - *mem = r; - r = NULL; - *len = (l + 1) / 2; - - return 0; -} - -/* https://tools.ietf.org/html/rfc4648#section-6 - * Notice that base32hex differs from base32 in the alphabet it uses. - * The distinction is that the base32hex representation preserves the - * order of the underlying data when compared as bytestrings, this is - * useful when representing NSEC3 hashes, as one can then verify the - * order of hashes directly from their representation. */ -char base32hexchar(int x) { - static const char table[32] = "0123456789" - "ABCDEFGHIJKLMNOPQRSTUV"; - - return table[x & 31]; -} - -int unbase32hexchar(char c) { - unsigned offset; - - if (c >= '0' && c <= '9') - return c - '0'; - - offset = '9' - '0' + 1; - - if (c >= 'A' && c <= 'V') - return c - 'A' + offset; - - return -EINVAL; -} - -char *base32hexmem(const void *p, size_t l, bool padding) { - char *r, *z; - const uint8_t *x; - size_t len; - - if (padding) - /* five input bytes makes eight output bytes, padding is added so we must round up */ - len = 8 * (l + 4) / 5; - else { - /* same, but round down as there is no padding */ - len = 8 * l / 5; - - switch (l % 5) { - case 4: - len += 7; - break; - case 3: - len += 5; - break; - case 2: - len += 4; - break; - case 1: - len += 2; - break; - } - } - - z = r = malloc(len + 1); - if (!r) - return NULL; - - for (x = p; x < (const uint8_t*) p + (l / 5) * 5; x += 5) { - /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ - x[3] == QQQQQQQQ; x[4] == WWWWWWWW */ - *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ - *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ - *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ - *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */ - *(z++) = base32hexchar((x[2] & 15) << 1 | x[3] >> 7); /* 000ZZZZQ */ - *(z++) = base32hexchar((x[3] & 127) >> 2); /* 000QQQQQ */ - *(z++) = base32hexchar((x[3] & 3) << 3 | x[4] >> 5); /* 000QQWWW */ - *(z++) = base32hexchar((x[4] & 31)); /* 000WWWWW */ - } - - switch (l % 5) { - case 4: - *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ - *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ - *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ - *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */ - *(z++) = base32hexchar((x[2] & 15) << 1 | x[3] >> 7); /* 000ZZZZQ */ - *(z++) = base32hexchar((x[3] & 127) >> 2); /* 000QQQQQ */ - *(z++) = base32hexchar((x[3] & 3) << 3); /* 000QQ000 */ - if (padding) - *(z++) = '='; - - break; - - case 3: - *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ - *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ - *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ - *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */ - *(z++) = base32hexchar((x[2] & 15) << 1); /* 000ZZZZ0 */ - if (padding) { - *(z++) = '='; - *(z++) = '='; - *(z++) = '='; - } - - break; - - case 2: - *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ - *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ - *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ - *(z++) = base32hexchar((x[1] & 1) << 4); /* 000Y0000 */ - if (padding) { - *(z++) = '='; - *(z++) = '='; - *(z++) = '='; - *(z++) = '='; - } - - break; - - case 1: - *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ - *(z++) = base32hexchar((x[0] & 7) << 2); /* 000XXX00 */ - if (padding) { - *(z++) = '='; - *(z++) = '='; - *(z++) = '='; - *(z++) = '='; - *(z++) = '='; - *(z++) = '='; - } - - break; - } - - *z = 0; - return r; -} - -int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *_len) { - _cleanup_free_ uint8_t *r = NULL; - int a, b, c, d, e, f, g, h; - uint8_t *z; - const char *x; - size_t len; - unsigned pad = 0; - - assert(p); - - /* padding ensures any base32hex input has input divisible by 8 */ - if (padding && l % 8 != 0) - return -EINVAL; - - if (padding) { - /* strip the padding */ - while (l > 0 && p[l - 1] == '=' && pad < 7) { - pad++; - l--; - } - } - - /* a group of eight input bytes needs five output bytes, in case of - padding we need to add some extra bytes */ - len = (l / 8) * 5; - - switch (l % 8) { - case 7: - len += 4; - break; - case 5: - len += 3; - break; - case 4: - len += 2; - break; - case 2: - len += 1; - break; - case 0: - break; - default: - return -EINVAL; - } - - z = r = malloc(len + 1); - if (!r) - return -ENOMEM; - - for (x = p; x < p + (l / 8) * 8; x += 8) { - /* a == 000XXXXX; b == 000YYYYY; c == 000ZZZZZ; d == 000WWWWW - e == 000SSSSS; f == 000QQQQQ; g == 000VVVVV; h == 000RRRRR */ - a = unbase32hexchar(x[0]); - if (a < 0) - return -EINVAL; - - b = unbase32hexchar(x[1]); - if (b < 0) - return -EINVAL; - - c = unbase32hexchar(x[2]); - if (c < 0) - return -EINVAL; - - d = unbase32hexchar(x[3]); - if (d < 0) - return -EINVAL; - - e = unbase32hexchar(x[4]); - if (e < 0) - return -EINVAL; - - f = unbase32hexchar(x[5]); - if (f < 0) - return -EINVAL; - - g = unbase32hexchar(x[6]); - if (g < 0) - return -EINVAL; - - h = unbase32hexchar(x[7]); - if (h < 0) - return -EINVAL; - - *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ - *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ - *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1; /* WWWWSSSS */ - *(z++) = (uint8_t) e << 7 | (uint8_t) f << 2 | (uint8_t) g >> 3; /* SQQQQQVV */ - *(z++) = (uint8_t) g << 5 | (uint8_t) h; /* VVVRRRRR */ - } - - switch (l % 8) { - case 7: - a = unbase32hexchar(x[0]); - if (a < 0) - return -EINVAL; - - b = unbase32hexchar(x[1]); - if (b < 0) - return -EINVAL; - - c = unbase32hexchar(x[2]); - if (c < 0) - return -EINVAL; - - d = unbase32hexchar(x[3]); - if (d < 0) - return -EINVAL; - - e = unbase32hexchar(x[4]); - if (e < 0) - return -EINVAL; - - f = unbase32hexchar(x[5]); - if (f < 0) - return -EINVAL; - - g = unbase32hexchar(x[6]); - if (g < 0) - return -EINVAL; - - /* g == 000VV000 */ - if (g & 7) - return -EINVAL; - - *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ - *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ - *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1; /* WWWWSSSS */ - *(z++) = (uint8_t) e << 7 | (uint8_t) f << 2 | (uint8_t) g >> 3; /* SQQQQQVV */ - - break; - case 5: - a = unbase32hexchar(x[0]); - if (a < 0) - return -EINVAL; - - b = unbase32hexchar(x[1]); - if (b < 0) - return -EINVAL; - - c = unbase32hexchar(x[2]); - if (c < 0) - return -EINVAL; - - d = unbase32hexchar(x[3]); - if (d < 0) - return -EINVAL; - - e = unbase32hexchar(x[4]); - if (e < 0) - return -EINVAL; - - /* e == 000SSSS0 */ - if (e & 1) - return -EINVAL; - - *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ - *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ - *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1; /* WWWWSSSS */ - - break; - case 4: - a = unbase32hexchar(x[0]); - if (a < 0) - return -EINVAL; - - b = unbase32hexchar(x[1]); - if (b < 0) - return -EINVAL; - - c = unbase32hexchar(x[2]); - if (c < 0) - return -EINVAL; - - d = unbase32hexchar(x[3]); - if (d < 0) - return -EINVAL; - - /* d == 000W0000 */ - if (d & 15) - return -EINVAL; - - *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ - *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ - - break; - case 2: - a = unbase32hexchar(x[0]); - if (a < 0) - return -EINVAL; - - b = unbase32hexchar(x[1]); - if (b < 0) - return -EINVAL; - - /* b == 000YYY00 */ - if (b & 3) - return -EINVAL; - - *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ - - break; - case 0: - break; - default: - return -EINVAL; - } - - *z = 0; - - *mem = r; - r = NULL; - *_len = len; - - return 0; -} - -/* https://tools.ietf.org/html/rfc4648#section-4 */ -char base64char(int x) { - static const char table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - return table[x & 63]; -} - -int unbase64char(char c) { - unsigned offset; - - if (c >= 'A' && c <= 'Z') - return c - 'A'; - - offset = 'Z' - 'A' + 1; - - if (c >= 'a' && c <= 'z') - return c - 'a' + offset; - - offset += 'z' - 'a' + 1; - - if (c >= '0' && c <= '9') - return c - '0' + offset; - - offset += '9' - '0' + 1; - - if (c == '+') - return offset; - - offset++; - - if (c == '/') - return offset; - - return -EINVAL; -} - -ssize_t base64mem(const void *p, size_t l, char **out) { - char *r, *z; - const uint8_t *x; - - /* three input bytes makes four output bytes, padding is added so we must round up */ - z = r = malloc(4 * (l + 2) / 3 + 1); - if (!r) - return -ENOMEM; - - for (x = p; x < (const uint8_t*) p + (l / 3) * 3; x += 3) { - /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ */ - *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ - *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ - *(z++) = base64char((x[1] & 15) << 2 | x[2] >> 6); /* 00YYYYZZ */ - *(z++) = base64char(x[2] & 63); /* 00ZZZZZZ */ - } - - switch (l % 3) { - case 2: - *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ - *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ - *(z++) = base64char((x[1] & 15) << 2); /* 00YYYY00 */ - *(z++) = '='; - - break; - case 1: - *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ - *(z++) = base64char((x[0] & 3) << 4); /* 00XX0000 */ - *(z++) = '='; - *(z++) = '='; - - break; - } - - *z = 0; - *out = r; - return z - r; -} - -static int base64_append_width(char **prefix, int plen, - const char *sep, int indent, - const void *p, size_t l, - int width) { - - _cleanup_free_ char *x = NULL; - char *t, *s; - ssize_t slen, len, avail; - int line, lines; - - len = base64mem(p, l, &x); - if (len <= 0) - return len; - - lines = (len + width - 1) / width; - - slen = sep ? strlen(sep) : 0; - t = realloc(*prefix, plen + 1 + slen + (indent + width + 1) * lines); - if (!t) - return -ENOMEM; - - memcpy_safe(t + plen, sep, slen); - - for (line = 0, s = t + plen + slen, avail = len; line < lines; line++) { - int act = MIN(width, avail); - - if (line > 0 || sep) { - memset(s, ' ', indent); - s += indent; - } - - memcpy(s, x + width * line, act); - s += act; - *(s++) = line < lines - 1 ? '\n' : '\0'; - avail -= act; - } - assert(avail == 0); - - *prefix = t; - return 0; -} - -int base64_append(char **prefix, int plen, - const void *p, size_t l, - int indent, int width) { - if (plen > width / 2 || plen + indent > width) - /* leave indent on the left, keep last column free */ - return base64_append_width(prefix, plen, "\n", indent, p, l, width - indent - 1); - else - /* leave plen on the left, keep last column free */ - return base64_append_width(prefix, plen, NULL, plen, p, l, width - plen - 1); -}; - - -int unbase64mem(const char *p, size_t l, void **mem, size_t *_len) { - _cleanup_free_ uint8_t *r = NULL; - int a, b, c, d; - uint8_t *z; - const char *x; - size_t len; - - assert(p); - - /* padding ensures any base63 input has input divisible by 4 */ - if (l % 4 != 0) - return -EINVAL; - - /* strip the padding */ - if (l > 0 && p[l - 1] == '=') - l--; - if (l > 0 && p[l - 1] == '=') - l--; - - /* a group of four input bytes needs three output bytes, in case of - padding we need to add two or three extra bytes */ - len = (l / 4) * 3 + (l % 4 ? (l % 4) - 1 : 0); - - z = r = malloc(len + 1); - if (!r) - return -ENOMEM; - - for (x = p; x < p + (l / 4) * 4; x += 4) { - /* a == 00XXXXXX; b == 00YYYYYY; c == 00ZZZZZZ; d == 00WWWWWW */ - a = unbase64char(x[0]); - if (a < 0) - return -EINVAL; - - b = unbase64char(x[1]); - if (b < 0) - return -EINVAL; - - c = unbase64char(x[2]); - if (c < 0) - return -EINVAL; - - d = unbase64char(x[3]); - if (d < 0) - return -EINVAL; - - *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ - *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ - *(z++) = (uint8_t) c << 6 | (uint8_t) d; /* ZZWWWWWW */ - } - - switch (l % 4) { - case 3: - a = unbase64char(x[0]); - if (a < 0) - return -EINVAL; - - b = unbase64char(x[1]); - if (b < 0) - return -EINVAL; - - c = unbase64char(x[2]); - if (c < 0) - return -EINVAL; - - /* c == 00ZZZZ00 */ - if (c & 3) - return -EINVAL; - - *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ - *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ - - break; - case 2: - a = unbase64char(x[0]); - if (a < 0) - return -EINVAL; - - b = unbase64char(x[1]); - if (b < 0) - return -EINVAL; - - /* b == 00YY0000 */ - if (b & 15) - return -EINVAL; - - *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */ - - break; - case 0: - - break; - default: - return -EINVAL; - } - - *z = 0; - - *mem = r; - r = NULL; - *_len = len; - - return 0; -} - -void hexdump(FILE *f, const void *p, size_t s) { - const uint8_t *b = p; - unsigned n = 0; - - assert(s == 0 || b); - - while (s > 0) { - size_t i; - - fprintf(f, "%04x ", n); - - for (i = 0; i < 16; i++) { - - if (i >= s) - fputs(" ", f); - else - fprintf(f, "%02x ", b[i]); - - if (i == 7) - fputc(' ', f); - } - - fputc(' ', f); - - for (i = 0; i < 16; i++) { - - if (i >= s) - fputc(' ', f); - else - fputc(isprint(b[i]) ? (char) b[i] : '.', f); - } - - fputc('\n', f); - - if (s < 16) - break; - - n += 16; - b += 16; - s -= 16; - } -} diff --git a/src/basic/hexdecoct.h b/src/basic/hexdecoct.h deleted file mode 100644 index 1ba2f69ebd..0000000000 --- a/src/basic/hexdecoct.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include - -#include "macro.h" - -char octchar(int x) _const_; -int unoctchar(char c) _const_; - -char decchar(int x) _const_; -int undecchar(char c) _const_; - -char hexchar(int x) _const_; -int unhexchar(char c) _const_; - -char *hexmem(const void *p, size_t l); -int unhexmem(const char *p, size_t l, void **mem, size_t *len); - -char base32hexchar(int x) _const_; -int unbase32hexchar(char c) _const_; - -char base64char(int x) _const_; -int unbase64char(char c) _const_; - -char *base32hexmem(const void *p, size_t l, bool padding); -int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *len); - -ssize_t base64mem(const void *p, size_t l, char **out); -int base64_append(char **prefix, int plen, - const void *p, size_t l, - int margin, int width); -int unbase64mem(const char *p, size_t l, void **mem, size_t *len); - -void hexdump(FILE *f, const void *p, size_t s); diff --git a/src/basic/hostname-util.c b/src/basic/hostname-util.c deleted file mode 100644 index 13c3bb6446..0000000000 --- a/src/basic/hostname-util.c +++ /dev/null @@ -1,252 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 -#include -#include -#include -#include - -#include "fd-util.h" -#include "fileio.h" -#include "hostname-util.h" -#include "macro.h" -#include "string-util.h" - -bool hostname_is_set(void) { - struct utsname u; - - assert_se(uname(&u) >= 0); - - if (isempty(u.nodename)) - return false; - - /* This is the built-in kernel default host name */ - if (streq(u.nodename, "(none)")) - return false; - - return true; -} - -char* gethostname_malloc(void) { - struct utsname u; - - /* This call tries to return something useful, either the actual hostname - * or it makes something up. The only reason it might fail is OOM. - * It might even return "localhost" if that's set. */ - - assert_se(uname(&u) >= 0); - - if (isempty(u.nodename) || streq(u.nodename, "(none)")) - return strdup(u.sysname); - - return strdup(u.nodename); -} - -int gethostname_strict(char **ret) { - struct utsname u; - char *k; - - /* This call will rather fail than make up a name. It will not return "localhost" either. */ - - assert_se(uname(&u) >= 0); - - if (isempty(u.nodename)) - return -ENXIO; - - if (streq(u.nodename, "(none)")) - return -ENXIO; - - if (is_localhost(u.nodename)) - return -ENXIO; - - k = strdup(u.nodename); - if (!k) - return -ENOMEM; - - *ret = k; - return 0; -} - -static bool hostname_valid_char(char c) { - return - (c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9') || - c == '-' || - c == '_' || - c == '.'; -} - -/** - * Check if s looks like a valid host name or FQDN. This does not do - * full DNS validation, but only checks if the name is composed of - * allowed characters and the length is not above the maximum allowed - * by Linux (c.f. dns_name_is_valid()). Trailing dot is allowed if - * allow_trailing_dot is true and at least two components are present - * in the name. Note that due to the restricted charset and length - * this call is substantially more conservative than - * dns_name_is_valid(). - */ -bool hostname_is_valid(const char *s, bool allow_trailing_dot) { - unsigned n_dots = 0; - const char *p; - bool dot; - - if (isempty(s)) - return false; - - /* Doesn't accept empty hostnames, hostnames with - * leading dots, and hostnames with multiple dots in a - * sequence. Also ensures that the length stays below - * HOST_NAME_MAX. */ - - for (p = s, dot = true; *p; p++) { - if (*p == '.') { - if (dot) - return false; - - dot = true; - n_dots++; - } else { - if (!hostname_valid_char(*p)) - return false; - - dot = false; - } - } - - if (dot && (n_dots < 2 || !allow_trailing_dot)) - return false; - - if (p-s > HOST_NAME_MAX) /* Note that HOST_NAME_MAX is 64 on - * Linux, but DNS allows domain names - * up to 255 characters */ - return false; - - return true; -} - -char* hostname_cleanup(char *s) { - char *p, *d; - bool dot; - - assert(s); - - strshorten(s, HOST_NAME_MAX); - - for (p = s, d = s, dot = true; *p; p++) { - if (*p == '.') { - if (dot) - continue; - - *(d++) = '.'; - dot = true; - } else if (hostname_valid_char(*p)) { - *(d++) = *p; - dot = false; - } - - } - - if (dot && d > s) - d[-1] = 0; - else - *d = 0; - - return s; -} - -bool is_localhost(const char *hostname) { - assert(hostname); - - /* This tries to identify local host and domain names - * described in RFC6761 plus the redhatism of localdomain */ - - return strcaseeq(hostname, "localhost") || - strcaseeq(hostname, "localhost.") || - strcaseeq(hostname, "localhost.localdomain") || - strcaseeq(hostname, "localhost.localdomain.") || - endswith_no_case(hostname, ".localhost") || - endswith_no_case(hostname, ".localhost.") || - endswith_no_case(hostname, ".localhost.localdomain") || - endswith_no_case(hostname, ".localhost.localdomain."); -} - -bool is_gateway_hostname(const char *hostname) { - assert(hostname); - - /* This tries to identify the valid syntaxes for the our - * synthetic "gateway" host. */ - - return - strcaseeq(hostname, "gateway") || - strcaseeq(hostname, "gateway."); -} - -int sethostname_idempotent(const char *s) { - char buf[HOST_NAME_MAX + 1] = {}; - - assert(s); - - if (gethostname(buf, sizeof(buf)) < 0) - return -errno; - - if (streq(buf, s)) - return 0; - - if (sethostname(s, strlen(s)) < 0) - return -errno; - - return 1; -} - -int read_hostname_config(const char *path, char **hostname) { - _cleanup_fclose_ FILE *f = NULL; - char l[LINE_MAX]; - char *name = NULL; - - assert(path); - assert(hostname); - - f = fopen(path, "re"); - if (!f) - return -errno; - - /* may have comments, ignore them */ - FOREACH_LINE(l, f, return -errno) { - truncate_nl(l); - if (l[0] != '\0' && l[0] != '#') { - /* found line with value */ - name = hostname_cleanup(l); - name = strdup(name); - if (!name) - return -ENOMEM; - break; - } - } - - if (!name) - /* no non-empty line found */ - return -ENOENT; - - *hostname = name; - return 0; -} diff --git a/src/basic/hostname-util.h b/src/basic/hostname-util.h deleted file mode 100644 index 7af4e6c7ec..0000000000 --- a/src/basic/hostname-util.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010-2015 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 "macro.h" - -bool hostname_is_set(void); - -char* gethostname_malloc(void); -int gethostname_strict(char **ret); - -bool hostname_is_valid(const char *s, bool allow_trailing_dot) _pure_; -char* hostname_cleanup(char *s); - -#define machine_name_is_valid(s) hostname_is_valid(s, false) - -bool is_localhost(const char *hostname); -bool is_gateway_hostname(const char *hostname); - -int sethostname_idempotent(const char *s); - -int read_hostname_config(const char *path, char **hostname); diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c deleted file mode 100644 index 245107ebb8..0000000000 --- a/src/basic/in-addr-util.c +++ /dev/null @@ -1,356 +0,0 @@ -/*** - 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 -#include -#include -#include - -#include "alloc-util.h" -#include "in-addr-util.h" -#include "macro.h" -#include "util.h" - -int in_addr_is_null(int family, const union in_addr_union *u) { - assert(u); - - if (family == AF_INET) - return u->in.s_addr == 0; - - if (family == AF_INET6) - return - u->in6.s6_addr32[0] == 0 && - u->in6.s6_addr32[1] == 0 && - u->in6.s6_addr32[2] == 0 && - u->in6.s6_addr32[3] == 0; - - return -EAFNOSUPPORT; -} - -int in_addr_is_link_local(int family, const union in_addr_union *u) { - assert(u); - - if (family == AF_INET) - return (be32toh(u->in.s_addr) & UINT32_C(0xFFFF0000)) == (UINT32_C(169) << 24 | UINT32_C(254) << 16); - - if (family == AF_INET6) - return IN6_IS_ADDR_LINKLOCAL(&u->in6); - - return -EAFNOSUPPORT; -} - -int in_addr_is_localhost(int family, const union in_addr_union *u) { - assert(u); - - if (family == AF_INET) - /* All of 127.x.x.x is localhost. */ - return (be32toh(u->in.s_addr) & UINT32_C(0xFF000000)) == UINT32_C(127) << 24; - - if (family == AF_INET6) - return IN6_IS_ADDR_LOOPBACK(&u->in6); - - return -EAFNOSUPPORT; -} - -int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b) { - assert(a); - assert(b); - - if (family == AF_INET) - return a->in.s_addr == b->in.s_addr; - - if (family == AF_INET6) - return - a->in6.s6_addr32[0] == b->in6.s6_addr32[0] && - a->in6.s6_addr32[1] == b->in6.s6_addr32[1] && - a->in6.s6_addr32[2] == b->in6.s6_addr32[2] && - a->in6.s6_addr32[3] == b->in6.s6_addr32[3]; - - return -EAFNOSUPPORT; -} - -int in_addr_prefix_intersect( - int family, - const union in_addr_union *a, - unsigned aprefixlen, - const union in_addr_union *b, - unsigned bprefixlen) { - - unsigned m; - - assert(a); - assert(b); - - /* Checks whether there are any addresses that are in both - * networks */ - - m = MIN(aprefixlen, bprefixlen); - - if (family == AF_INET) { - uint32_t x, nm; - - x = be32toh(a->in.s_addr ^ b->in.s_addr); - nm = (m == 0) ? 0 : 0xFFFFFFFFUL << (32 - m); - - return (x & nm) == 0; - } - - if (family == AF_INET6) { - unsigned i; - - if (m > 128) - m = 128; - - for (i = 0; i < 16; i++) { - uint8_t x, nm; - - x = a->in6.s6_addr[i] ^ b->in6.s6_addr[i]; - - if (m < 8) - nm = 0xFF << (8 - m); - else - nm = 0xFF; - - if ((x & nm) != 0) - return 0; - - if (m > 8) - m -= 8; - else - m = 0; - } - - return 1; - } - - return -EAFNOSUPPORT; -} - -int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen) { - assert(u); - - /* Increases the network part of an address by one. Returns - * positive it that succeeds, or 0 if this overflows. */ - - if (prefixlen <= 0) - return 0; - - if (family == AF_INET) { - uint32_t c, n; - - if (prefixlen > 32) - prefixlen = 32; - - c = be32toh(u->in.s_addr); - n = c + (1UL << (32 - prefixlen)); - if (n < c) - return 0; - n &= 0xFFFFFFFFUL << (32 - prefixlen); - - u->in.s_addr = htobe32(n); - return 1; - } - - if (family == AF_INET6) { - struct in6_addr add = {}, result; - uint8_t overflow = 0; - unsigned i; - - if (prefixlen > 128) - prefixlen = 128; - - /* First calculate what we have to add */ - add.s6_addr[(prefixlen-1) / 8] = 1 << (7 - (prefixlen-1) % 8); - - for (i = 16; i > 0; i--) { - unsigned j = i - 1; - - result.s6_addr[j] = u->in6.s6_addr[j] + add.s6_addr[j] + overflow; - overflow = (result.s6_addr[j] < u->in6.s6_addr[j]); - } - - if (overflow) - return 0; - - u->in6 = result; - return 1; - } - - return -EAFNOSUPPORT; -} - -int in_addr_to_string(int family, const union in_addr_union *u, char **ret) { - char *x; - size_t l; - - assert(u); - assert(ret); - - if (family == AF_INET) - l = INET_ADDRSTRLEN; - else if (family == AF_INET6) - l = INET6_ADDRSTRLEN; - else - return -EAFNOSUPPORT; - - x = new(char, l); - if (!x) - return -ENOMEM; - - errno = 0; - if (!inet_ntop(family, u, x, l)) { - free(x); - return errno > 0 ? -errno : -EINVAL; - } - - *ret = x; - return 0; -} - -int in_addr_from_string(int family, const char *s, union in_addr_union *ret) { - - assert(s); - assert(ret); - - if (!IN_SET(family, AF_INET, AF_INET6)) - return -EAFNOSUPPORT; - - errno = 0; - if (inet_pton(family, s, ret) <= 0) - return errno > 0 ? -errno : -EINVAL; - - return 0; -} - -int in_addr_from_string_auto(const char *s, int *family, union in_addr_union *ret) { - int r; - - assert(s); - assert(family); - assert(ret); - - r = in_addr_from_string(AF_INET, s, ret); - if (r >= 0) { - *family = AF_INET; - return 0; - } - - r = in_addr_from_string(AF_INET6, s, ret); - if (r >= 0) { - *family = AF_INET6; - return 0; - } - - return -EINVAL; -} - -unsigned char in_addr_netmask_to_prefixlen(const struct in_addr *addr) { - assert(addr); - - return 32 - u32ctz(be32toh(addr->s_addr)); -} - -struct in_addr* in_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen) { - assert(addr); - assert(prefixlen <= 32); - - /* Shifting beyond 32 is not defined, handle this specially. */ - if (prefixlen == 0) - addr->s_addr = 0; - else - addr->s_addr = htobe32((0xffffffff << (32 - prefixlen)) & 0xffffffff); - - return addr; -} - -int in_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen) { - uint8_t msb_octet = *(uint8_t*) addr; - - /* addr may not be aligned, so make sure we only access it byte-wise */ - - assert(addr); - assert(prefixlen); - - if (msb_octet < 128) - /* class A, leading bits: 0 */ - *prefixlen = 8; - else if (msb_octet < 192) - /* class B, leading bits 10 */ - *prefixlen = 16; - else if (msb_octet < 224) - /* class C, leading bits 110 */ - *prefixlen = 24; - else - /* class D or E, no default prefixlen */ - return -ERANGE; - - return 0; -} - -int in_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask) { - unsigned char prefixlen; - int r; - - assert(addr); - assert(mask); - - r = in_addr_default_prefixlen(addr, &prefixlen); - if (r < 0) - return r; - - in_addr_prefixlen_to_netmask(mask, prefixlen); - return 0; -} - -int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen) { - assert(addr); - - if (family == AF_INET) { - struct in_addr mask; - - if (!in_addr_prefixlen_to_netmask(&mask, prefixlen)) - return -EINVAL; - - addr->in.s_addr &= mask.s_addr; - return 0; - } - - if (family == AF_INET6) { - unsigned i; - - for (i = 0; i < 16; i++) { - uint8_t mask; - - if (prefixlen >= 8) { - mask = 0xFF; - prefixlen -= 8; - } else { - mask = 0xFF << (8 - prefixlen); - prefixlen = 0; - } - - addr->in6.s6_addr[i] &= mask; - } - - return 0; - } - - return -EAFNOSUPPORT; -} diff --git a/src/basic/in-addr-util.h b/src/basic/in-addr-util.h deleted file mode 100644 index 17798ce816..0000000000 --- a/src/basic/in-addr-util.h +++ /dev/null @@ -1,59 +0,0 @@ -#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 -#include -#include - -#include "macro.h" -#include "util.h" - -union in_addr_union { - struct in_addr in; - struct in6_addr in6; -}; - -struct in_addr_data { - int family; - union in_addr_union address; -}; - -int in_addr_is_null(int family, const union in_addr_union *u); -int in_addr_is_link_local(int family, const union in_addr_union *u); -int in_addr_is_localhost(int family, const union in_addr_union *u); -int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b); -int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen); -int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen); -int in_addr_to_string(int family, const union in_addr_union *u, char **ret); -int in_addr_from_string(int family, const char *s, union in_addr_union *ret); -int in_addr_from_string_auto(const char *s, int *family, union in_addr_union *ret); -unsigned char in_addr_netmask_to_prefixlen(const struct in_addr *addr); -struct in_addr* in_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen); -int in_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen); -int in_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask); -int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen); - -static inline size_t FAMILY_ADDRESS_SIZE(int family) { - assert(family == AF_INET || family == AF_INET6); - return family == AF_INET6 ? 16 : 4; -} - -#define IN_ADDR_NULL ((union in_addr_union) {}) diff --git a/src/basic/io-util.c b/src/basic/io-util.c deleted file mode 100644 index cc6dfa8c1b..0000000000 --- a/src/basic/io-util.c +++ /dev/null @@ -1,269 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include - -#include "io-util.h" -#include "time-util.h" - -int flush_fd(int fd) { - struct pollfd pollfd = { - .fd = fd, - .events = POLLIN, - }; - - /* Read from the specified file descriptor, until POLLIN is not set anymore, throwing away everything - * read. Note that some file descriptors (notable IP sockets) will trigger POLLIN even when no data can be read - * (due to IP packet checksum mismatches), hence this function is only safe to be non-blocking if the fd used - * was set to non-blocking too. */ - - for (;;) { - char buf[LINE_MAX]; - ssize_t l; - int r; - - r = poll(&pollfd, 1, 0); - if (r < 0) { - if (errno == EINTR) - continue; - - return -errno; - - } else if (r == 0) - return 0; - - l = read(fd, buf, sizeof(buf)); - if (l < 0) { - - if (errno == EINTR) - continue; - - if (errno == EAGAIN) - return 0; - - return -errno; - } else if (l == 0) - return 0; - } -} - -ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { - uint8_t *p = buf; - ssize_t n = 0; - - assert(fd >= 0); - assert(buf); - - /* If called with nbytes == 0, let's call read() at least - * once, to validate the operation */ - - if (nbytes > (size_t) SSIZE_MAX) - return -EINVAL; - - do { - ssize_t k; - - k = read(fd, p, nbytes); - if (k < 0) { - if (errno == EINTR) - continue; - - if (errno == EAGAIN && do_poll) { - - /* We knowingly ignore any return value here, - * and expect that any error/EOF is reported - * via read() */ - - (void) fd_wait_for_event(fd, POLLIN, USEC_INFINITY); - continue; - } - - return n > 0 ? n : -errno; - } - - if (k == 0) - return n; - - assert((size_t) k <= nbytes); - - p += k; - nbytes -= k; - n += k; - } while (nbytes > 0); - - return n; -} - -int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll) { - ssize_t n; - - n = loop_read(fd, buf, nbytes, do_poll); - if (n < 0) - return (int) n; - if ((size_t) n != nbytes) - return -EIO; - - return 0; -} - -int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { - const uint8_t *p = buf; - - assert(fd >= 0); - assert(buf); - - if (nbytes > (size_t) SSIZE_MAX) - return -EINVAL; - - do { - ssize_t k; - - k = write(fd, p, nbytes); - if (k < 0) { - if (errno == EINTR) - continue; - - if (errno == EAGAIN && do_poll) { - /* We knowingly ignore any return value here, - * and expect that any error/EOF is reported - * via write() */ - - (void) fd_wait_for_event(fd, POLLOUT, USEC_INFINITY); - continue; - } - - return -errno; - } - - if (_unlikely_(nbytes > 0 && k == 0)) /* Can't really happen */ - return -EIO; - - assert((size_t) k <= nbytes); - - p += k; - nbytes -= k; - } while (nbytes > 0); - - return 0; -} - -int pipe_eof(int fd) { - struct pollfd pollfd = { - .fd = fd, - .events = POLLIN|POLLHUP, - }; - - int r; - - r = poll(&pollfd, 1, 0); - if (r < 0) - return -errno; - - if (r == 0) - return 0; - - return pollfd.revents & POLLHUP; -} - -int fd_wait_for_event(int fd, int event, usec_t t) { - - struct pollfd pollfd = { - .fd = fd, - .events = event, - }; - - struct timespec ts; - int r; - - r = ppoll(&pollfd, 1, t == USEC_INFINITY ? NULL : timespec_store(&ts, t), NULL); - if (r < 0) - return -errno; - - if (r == 0) - return 0; - - return pollfd.revents; -} - -static size_t nul_length(const uint8_t *p, size_t sz) { - size_t n = 0; - - while (sz > 0) { - if (*p != 0) - break; - - n++; - p++; - sz--; - } - - return n; -} - -ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length) { - const uint8_t *q, *w, *e; - ssize_t l; - - q = w = p; - e = q + sz; - while (q < e) { - size_t n; - - n = nul_length(q, e - q); - - /* If there are more than the specified run length of - * NUL bytes, or if this is the beginning or the end - * of the buffer, then seek instead of write */ - if ((n > run_length) || - (n > 0 && q == p) || - (n > 0 && q + n >= e)) { - if (q > w) { - l = write(fd, w, q - w); - if (l < 0) - return -errno; - if (l != q -w) - return -EIO; - } - - if (lseek(fd, n, SEEK_CUR) == (off_t) -1) - return -errno; - - q += n; - w = q; - } else if (n > 0) - q += n; - else - q++; - } - - if (q > w) { - l = write(fd, w, q - w); - if (l < 0) - return -errno; - if (l != q - w) - return -EIO; - } - - return q - (const uint8_t*) p; -} diff --git a/src/basic/io-util.h b/src/basic/io-util.h deleted file mode 100644 index 4684ed3bfc..0000000000 --- a/src/basic/io-util.h +++ /dev/null @@ -1,95 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include - -#include "macro.h" -#include "time-util.h" - -int flush_fd(int fd); - -ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll); -int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll); -int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll); - -int pipe_eof(int fd); - -int fd_wait_for_event(int fd, int event, usec_t timeout); - -ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length); - -#define IOVEC_SET_STRING(i, s) \ - do { \ - struct iovec *_i = &(i); \ - char *_s = (char *)(s); \ - _i->iov_base = _s; \ - _i->iov_len = strlen(_s); \ - } while (false) - -static inline size_t IOVEC_TOTAL_SIZE(const struct iovec *i, unsigned n) { - unsigned j; - size_t r = 0; - - for (j = 0; j < n; j++) - r += i[j].iov_len; - - return r; -} - -static inline size_t IOVEC_INCREMENT(struct iovec *i, unsigned n, size_t k) { - unsigned j; - - for (j = 0; j < n; j++) { - size_t sub; - - if (_unlikely_(k <= 0)) - break; - - sub = MIN(i[j].iov_len, k); - i[j].iov_len -= sub; - i[j].iov_base = (uint8_t*) i[j].iov_base + sub; - k -= sub; - } - - return k; -} - -static inline bool FILE_SIZE_VALID(uint64_t l) { - /* ftruncate() and friends take an unsigned file size, but actually cannot deal with file sizes larger than - * 2^63 since the kernel internally handles it as signed value. This call allows checking for this early. */ - - return (l >> 63) == 0; -} - -static inline bool FILE_SIZE_VALID_OR_INFINITY(uint64_t l) { - - /* Same as above, but allows one extra value: -1 as indication for infinity. */ - - if (l == (uint64_t) -1) - return true; - - return FILE_SIZE_VALID(l); - -} diff --git a/src/basic/ioprio.h b/src/basic/ioprio.h deleted file mode 100644 index d8bb6eb497..0000000000 --- a/src/basic/ioprio.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef IOPRIO_H -#define IOPRIO_H - -/* This is minimal version of Linux' linux/ioprio.h header file, which - * is licensed GPL2 */ - -#include -#include - -/* - * Gives us 8 prio classes with 13-bits of data for each class - */ -#define IOPRIO_BITS (16) -#define IOPRIO_CLASS_SHIFT (13) -#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1) - -#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT) -#define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK) -#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data) - -#define ioprio_valid(mask) (IOPRIO_PRIO_CLASS((mask)) != IOPRIO_CLASS_NONE) - -/* - * These are the io priority groups as implemented by CFQ. RT is the realtime - * class, it always gets premium service. BE is the best-effort scheduling - * class, the default for any process. IDLE is the idle scheduling class, it - * is only served when no one else is using the disk. - */ -enum { - IOPRIO_CLASS_NONE, - IOPRIO_CLASS_RT, - IOPRIO_CLASS_BE, - IOPRIO_CLASS_IDLE, -}; - -/* - * 8 best effort priority levels are supported - */ -#define IOPRIO_BE_NR (8) - -enum { - IOPRIO_WHO_PROCESS = 1, - IOPRIO_WHO_PGRP, - IOPRIO_WHO_USER, -}; - -static inline int ioprio_set(int which, int who, int ioprio) { - return syscall(__NR_ioprio_set, which, who, ioprio); -} - -static inline int ioprio_get(int which, int who) { - return syscall(__NR_ioprio_get, which, who); -} - -#endif diff --git a/src/basic/label.c b/src/basic/label.c deleted file mode 100644 index f5ab855d32..0000000000 --- a/src/basic/label.c +++ /dev/null @@ -1,82 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include - -#include "label.h" -#include "macro.h" -#include "selinux-util.h" -#include "smack-util.h" - -int label_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { - int r, q; - - r = mac_selinux_fix(path, ignore_enoent, ignore_erofs); - q = mac_smack_fix(path, ignore_enoent, ignore_erofs); - - if (r < 0) - return r; - if (q < 0) - return q; - - return 0; -} - -int mkdir_label(const char *path, mode_t mode) { - int r; - - assert(path); - - r = mac_selinux_create_file_prepare(path, S_IFDIR); - if (r < 0) - return r; - - if (mkdir(path, mode) < 0) - r = -errno; - - mac_selinux_create_file_clear(); - - if (r < 0) - return r; - - return mac_smack_fix(path, false, false); -} - -int symlink_label(const char *old_path, const char *new_path) { - int r; - - assert(old_path); - assert(new_path); - - r = mac_selinux_create_file_prepare(new_path, S_IFLNK); - if (r < 0) - return r; - - if (symlink(old_path, new_path) < 0) - r = -errno; - - mac_selinux_create_file_clear(); - - if (r < 0) - return r; - - return mac_smack_fix(new_path, false, false); -} diff --git a/src/basic/label.h b/src/basic/label.h deleted file mode 100644 index 3e9251aa71..0000000000 --- a/src/basic/label.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 - -int label_fix(const char *path, bool ignore_enoent, bool ignore_erofs); - -int mkdir_label(const char *path, mode_t mode); -int symlink_label(const char *old_path, const char *new_path); diff --git a/src/basic/list.h b/src/basic/list.h deleted file mode 100644 index 5962aa4211..0000000000 --- a/src/basic/list.h +++ /dev/null @@ -1,182 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -/* The head of the linked list. Use this in the structure that shall - * contain the head of the linked list */ -#define LIST_HEAD(t,name) \ - t *name - -/* The pointers in the linked list's items. Use this in the item structure */ -#define LIST_FIELDS(t,name) \ - t *name##_next, *name##_prev - -/* Initialize the list's head */ -#define LIST_HEAD_INIT(head) \ - do { \ - (head) = NULL; } \ - while (false) - -/* Initialize a list item */ -#define LIST_INIT(name,item) \ - do { \ - typeof(*(item)) *_item = (item); \ - assert(_item); \ - _item->name##_prev = _item->name##_next = NULL; \ - } while (false) - -/* Prepend an item to the list */ -#define LIST_PREPEND(name,head,item) \ - do { \ - typeof(*(head)) **_head = &(head), *_item = (item); \ - assert(_item); \ - if ((_item->name##_next = *_head)) \ - _item->name##_next->name##_prev = _item; \ - _item->name##_prev = NULL; \ - *_head = _item; \ - } while (false) - -/* Append an item to the list */ -#define LIST_APPEND(name,head,item) \ - do { \ - typeof(*(head)) *_tail; \ - LIST_FIND_TAIL(name,head,_tail); \ - LIST_INSERT_AFTER(name,head,_tail,item); \ - } while (false) - -/* Remove an item from the list */ -#define LIST_REMOVE(name,head,item) \ - do { \ - typeof(*(head)) **_head = &(head), *_item = (item); \ - assert(_item); \ - if (_item->name##_next) \ - _item->name##_next->name##_prev = _item->name##_prev; \ - if (_item->name##_prev) \ - _item->name##_prev->name##_next = _item->name##_next; \ - else { \ - assert(*_head == _item); \ - *_head = _item->name##_next; \ - } \ - _item->name##_next = _item->name##_prev = NULL; \ - } while (false) - -/* Find the head of the list */ -#define LIST_FIND_HEAD(name,item,head) \ - do { \ - typeof(*(item)) *_item = (item); \ - if (!_item) \ - (head) = NULL; \ - else { \ - while (_item->name##_prev) \ - _item = _item->name##_prev; \ - (head) = _item; \ - } \ - } while (false) - -/* Find the tail of the list */ -#define LIST_FIND_TAIL(name,item,tail) \ - do { \ - typeof(*(item)) *_item = (item); \ - if (!_item) \ - (tail) = NULL; \ - else { \ - while (_item->name##_next) \ - _item = _item->name##_next; \ - (tail) = _item; \ - } \ - } while (false) - -/* Insert an item after another one (a = where, b = what) */ -#define LIST_INSERT_AFTER(name,head,a,b) \ - do { \ - typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \ - assert(_b); \ - if (!_a) { \ - if ((_b->name##_next = *_head)) \ - _b->name##_next->name##_prev = _b; \ - _b->name##_prev = NULL; \ - *_head = _b; \ - } else { \ - if ((_b->name##_next = _a->name##_next)) \ - _b->name##_next->name##_prev = _b; \ - _b->name##_prev = _a; \ - _a->name##_next = _b; \ - } \ - } while (false) - -/* Insert an item before another one (a = where, b = what) */ -#define LIST_INSERT_BEFORE(name,head,a,b) \ - do { \ - typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \ - assert(_b); \ - if (!_a) { \ - if (!*_head) { \ - _b->name##_next = NULL; \ - _b->name##_prev = NULL; \ - *_head = _b; \ - } else { \ - typeof(*(head)) *_tail = (head); \ - while (_tail->name##_next) \ - _tail = _tail->name##_next; \ - _b->name##_next = NULL; \ - _b->name##_prev = _tail; \ - _tail->name##_next = _b; \ - } \ - } else { \ - if ((_b->name##_prev = _a->name##_prev)) \ - _b->name##_prev->name##_next = _b; \ - _b->name##_next = _a; \ - _a->name##_prev = _b; \ - } \ - } while (false) - -#define LIST_JUST_US(name,item) \ - (!(item)->name##_prev && !(item)->name##_next) \ - -#define LIST_FOREACH(name,i,head) \ - for ((i) = (head); (i); (i) = (i)->name##_next) - -#define LIST_FOREACH_SAFE(name,i,n,head) \ - for ((i) = (head); (i) && (((n) = (i)->name##_next), 1); (i) = (n)) - -#define LIST_FOREACH_BEFORE(name,i,p) \ - for ((i) = (p)->name##_prev; (i); (i) = (i)->name##_prev) - -#define LIST_FOREACH_AFTER(name,i,p) \ - for ((i) = (p)->name##_next; (i); (i) = (i)->name##_next) - -/* Iterate through all the members of the list p is included in, but skip over p */ -#define LIST_FOREACH_OTHERS(name,i,p) \ - for (({ \ - (i) = (p); \ - while ((i) && (i)->name##_prev) \ - (i) = (i)->name##_prev; \ - if ((i) == (p)) \ - (i) = (p)->name##_next; \ - }); \ - (i); \ - (i) = (i)->name##_next == (p) ? (p)->name##_next : (i)->name##_next) - -/* Loop starting from p->next until p->prev. - p can be adjusted meanwhile. */ -#define LIST_LOOP_BUT_ONE(name,i,head,p) \ - for ((i) = (p)->name##_next ? (p)->name##_next : (head); \ - (i) != (p); \ - (i) = (i)->name##_next ? (i)->name##_next : (head)) diff --git a/src/basic/locale-util.c b/src/basic/locale-util.c deleted file mode 100644 index ada0a28cd8..0000000000 --- a/src/basic/locale-util.c +++ /dev/null @@ -1,322 +0,0 @@ -/*** - 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dirent-util.h" -#include "fd-util.h" -#include "hashmap.h" -#include "locale-util.h" -#include "path-util.h" -#include "set.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "utf8.h" - -static int add_locales_from_archive(Set *locales) { - /* Stolen from glibc... */ - - struct locarhead { - uint32_t magic; - /* Serial number. */ - uint32_t serial; - /* Name hash table. */ - uint32_t namehash_offset; - uint32_t namehash_used; - uint32_t namehash_size; - /* String table. */ - uint32_t string_offset; - uint32_t string_used; - uint32_t string_size; - /* Table with locale records. */ - uint32_t locrectab_offset; - uint32_t locrectab_used; - uint32_t locrectab_size; - /* MD5 sum hash table. */ - uint32_t sumhash_offset; - uint32_t sumhash_used; - uint32_t sumhash_size; - }; - - struct namehashent { - /* Hash value of the name. */ - uint32_t hashval; - /* Offset of the name in the string table. */ - uint32_t name_offset; - /* Offset of the locale record. */ - uint32_t locrec_offset; - }; - - const struct locarhead *h; - const struct namehashent *e; - const void *p = MAP_FAILED; - _cleanup_close_ int fd = -1; - size_t sz = 0; - struct stat st; - unsigned i; - int r; - - fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return errno == ENOENT ? 0 : -errno; - - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISREG(st.st_mode)) - return -EBADMSG; - - if (st.st_size < (off_t) sizeof(struct locarhead)) - return -EBADMSG; - - p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); - if (p == MAP_FAILED) - return -errno; - - h = (const struct locarhead *) p; - if (h->magic != 0xde020109 || - h->namehash_offset + h->namehash_size > st.st_size || - h->string_offset + h->string_size > st.st_size || - h->locrectab_offset + h->locrectab_size > st.st_size || - h->sumhash_offset + h->sumhash_size > st.st_size) { - r = -EBADMSG; - goto finish; - } - - e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset); - for (i = 0; i < h->namehash_size; i++) { - char *z; - - if (e[i].locrec_offset == 0) - continue; - - if (!utf8_is_valid((char*) p + e[i].name_offset)) - continue; - - z = strdup((char*) p + e[i].name_offset); - if (!z) { - r = -ENOMEM; - goto finish; - } - - r = set_consume(locales, z); - if (r < 0) - goto finish; - } - - r = 0; - - finish: - if (p != MAP_FAILED) - munmap((void*) p, sz); - - return r; -} - -static int add_locales_from_libdir (Set *locales) { - _cleanup_closedir_ DIR *dir = NULL; - struct dirent *entry; - int r; - - dir = opendir("/usr/lib/locale"); - if (!dir) - return errno == ENOENT ? 0 : -errno; - - FOREACH_DIRENT(entry, dir, return -errno) { - char *z; - - dirent_ensure_type(dir, entry); - - if (entry->d_type != DT_DIR) - continue; - - z = strdup(entry->d_name); - if (!z) - return -ENOMEM; - - r = set_consume(locales, z); - if (r < 0 && r != -EEXIST) - return r; - } - - return 0; -} - -int get_locales(char ***ret) { - _cleanup_set_free_ Set *locales = NULL; - _cleanup_strv_free_ char **l = NULL; - int r; - - locales = set_new(&string_hash_ops); - if (!locales) - return -ENOMEM; - - r = add_locales_from_archive(locales); - if (r < 0 && r != -ENOENT) - return r; - - r = add_locales_from_libdir(locales); - if (r < 0) - return r; - - l = set_get_strv(locales); - if (!l) - return -ENOMEM; - - strv_sort(l); - - *ret = l; - l = NULL; - - return 0; -} - -bool locale_is_valid(const char *name) { - - if (isempty(name)) - return false; - - if (strlen(name) >= 128) - return false; - - if (!utf8_is_valid(name)) - return false; - - if (!filename_is_valid(name)) - return false; - - if (!string_is_safe(name)) - return false; - - return true; -} - -void init_gettext(void) { - setlocale(LC_ALL, ""); - textdomain(GETTEXT_PACKAGE); -} - -bool is_locale_utf8(void) { - const char *set; - static int cached_answer = -1; - - /* Note that we default to 'true' here, since today UTF8 is - * pretty much supported everywhere. */ - - if (cached_answer >= 0) - goto out; - - if (!setlocale(LC_ALL, "")) { - cached_answer = true; - goto out; - } - - set = nl_langinfo(CODESET); - if (!set) { - cached_answer = true; - goto out; - } - - if (streq(set, "UTF-8")) { - cached_answer = true; - goto out; - } - - /* For LC_CTYPE=="C" return true, because CTYPE is effectly - * unset and everything can do to UTF-8 nowadays. */ - set = setlocale(LC_CTYPE, NULL); - if (!set) { - cached_answer = true; - goto out; - } - - /* Check result, but ignore the result if C was set - * explicitly. */ - cached_answer = - STR_IN_SET(set, "C", "POSIX") && - !getenv("LC_ALL") && - !getenv("LC_CTYPE") && - !getenv("LANG"); - -out: - return (bool) cached_answer; -} - - -const char *special_glyph(SpecialGlyph code) { - - static const char* const draw_table[2][_SPECIAL_GLYPH_MAX] = { - /* ASCII fallback */ - [false] = { - [TREE_VERTICAL] = "| ", - [TREE_BRANCH] = "|-", - [TREE_RIGHT] = "`-", - [TREE_SPACE] = " ", - [TRIANGULAR_BULLET] = ">", - [BLACK_CIRCLE] = "*", - [ARROW] = "->", - [MDASH] = "-", - }, - - /* UTF-8 */ - [ true ] = { - [TREE_VERTICAL] = "\342\224\202 ", /* │ */ - [TREE_BRANCH] = "\342\224\234\342\224\200", /* ├─ */ - [TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */ - [TREE_SPACE] = " ", /* */ - [TRIANGULAR_BULLET] = "\342\200\243", /* ‣ */ - [BLACK_CIRCLE] = "\342\227\217", /* ● */ - [ARROW] = "\342\206\222", /* → */ - [MDASH] = "\342\200\223", /* – */ - }, - }; - - return draw_table[is_locale_utf8()][code]; -} - -static const char * const locale_variable_table[_VARIABLE_LC_MAX] = { - [VARIABLE_LANG] = "LANG", - [VARIABLE_LANGUAGE] = "LANGUAGE", - [VARIABLE_LC_CTYPE] = "LC_CTYPE", - [VARIABLE_LC_NUMERIC] = "LC_NUMERIC", - [VARIABLE_LC_TIME] = "LC_TIME", - [VARIABLE_LC_COLLATE] = "LC_COLLATE", - [VARIABLE_LC_MONETARY] = "LC_MONETARY", - [VARIABLE_LC_MESSAGES] = "LC_MESSAGES", - [VARIABLE_LC_PAPER] = "LC_PAPER", - [VARIABLE_LC_NAME] = "LC_NAME", - [VARIABLE_LC_ADDRESS] = "LC_ADDRESS", - [VARIABLE_LC_TELEPHONE] = "LC_TELEPHONE", - [VARIABLE_LC_MEASUREMENT] = "LC_MEASUREMENT", - [VARIABLE_LC_IDENTIFICATION] = "LC_IDENTIFICATION" -}; - -DEFINE_STRING_TABLE_LOOKUP(locale_variable, LocaleVariable); diff --git a/src/basic/locale-util.h b/src/basic/locale-util.h deleted file mode 100644 index 0630a034ab..0000000000 --- a/src/basic/locale-util.h +++ /dev/null @@ -1,73 +0,0 @@ -#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 -#include - -#include "macro.h" - -typedef enum LocaleVariable { - /* We don't list LC_ALL here on purpose. People should be - * using LANG instead. */ - - VARIABLE_LANG, - VARIABLE_LANGUAGE, - VARIABLE_LC_CTYPE, - VARIABLE_LC_NUMERIC, - VARIABLE_LC_TIME, - VARIABLE_LC_COLLATE, - VARIABLE_LC_MONETARY, - VARIABLE_LC_MESSAGES, - VARIABLE_LC_PAPER, - VARIABLE_LC_NAME, - VARIABLE_LC_ADDRESS, - VARIABLE_LC_TELEPHONE, - VARIABLE_LC_MEASUREMENT, - VARIABLE_LC_IDENTIFICATION, - _VARIABLE_LC_MAX, - _VARIABLE_LC_INVALID = -1 -} LocaleVariable; - -int get_locales(char ***l); -bool locale_is_valid(const char *name); - -#define _(String) gettext(String) -#define N_(String) String -void init_gettext(void); - -bool is_locale_utf8(void); - -typedef enum { - TREE_VERTICAL, - TREE_BRANCH, - TREE_RIGHT, - TREE_SPACE, - TRIANGULAR_BULLET, - BLACK_CIRCLE, - ARROW, - MDASH, - _SPECIAL_GLYPH_MAX -} SpecialGlyph; - -const char *special_glyph(SpecialGlyph code) _const_; - -const char* locale_variable_to_string(LocaleVariable i) _const_; -LocaleVariable locale_variable_from_string(const char *s) _pure_; diff --git a/src/basic/lockfile-util.c b/src/basic/lockfile-util.c deleted file mode 100644 index 3ee4191e4d..0000000000 --- a/src/basic/lockfile-util.c +++ /dev/null @@ -1,153 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "fs-util.h" -#include "lockfile-util.h" -#include "macro.h" -#include "path-util.h" - -int make_lock_file(const char *p, int operation, LockFile *ret) { - _cleanup_close_ int fd = -1; - _cleanup_free_ char *t = NULL; - int r; - - /* - * We use UNPOSIX locks if they are available. They have nice - * semantics, and are mostly compatible with NFS. However, - * they are only available on new kernels. When we detect we - * are running on an older kernel, then we fall back to good - * old BSD locks. They also have nice semantics, but are - * slightly problematic on NFS, where they are upgraded to - * POSIX locks, even though locally they are orthogonal to - * POSIX locks. - */ - - t = strdup(p); - if (!t) - return -ENOMEM; - - for (;;) { - struct flock fl = { - .l_type = (operation & ~LOCK_NB) == LOCK_EX ? F_WRLCK : F_RDLCK, - .l_whence = SEEK_SET, - }; - struct stat st; - - fd = open(p, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600); - if (fd < 0) - return -errno; - - r = fcntl(fd, (operation & LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW, &fl); - if (r < 0) { - - /* If the kernel is too old, use good old BSD locks */ - if (errno == EINVAL) - r = flock(fd, operation); - - if (r < 0) - return errno == EAGAIN ? -EBUSY : -errno; - } - - /* If we acquired the lock, let's check if the file - * still exists in the file system. If not, then the - * previous exclusive owner removed it and then closed - * it. In such a case our acquired lock is worthless, - * hence try again. */ - - r = fstat(fd, &st); - if (r < 0) - return -errno; - if (st.st_nlink > 0) - break; - - fd = safe_close(fd); - } - - ret->path = t; - ret->fd = fd; - ret->operation = operation; - - fd = -1; - t = NULL; - - return r; -} - -int make_lock_file_for(const char *p, int operation, LockFile *ret) { - const char *fn; - char *t; - - assert(p); - assert(ret); - - fn = basename(p); - if (!filename_is_valid(fn)) - return -EINVAL; - - t = newa(char, strlen(p) + 2 + 4 + 1); - stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn), ".lck"); - - return make_lock_file(t, operation, ret); -} - -void release_lock_file(LockFile *f) { - int r; - - if (!f) - return; - - if (f->path) { - - /* If we are the exclusive owner we can safely delete - * the lock file itself. If we are not the exclusive - * owner, we can try becoming it. */ - - if (f->fd >= 0 && - (f->operation & ~LOCK_NB) == LOCK_SH) { - static const struct flock fl = { - .l_type = F_WRLCK, - .l_whence = SEEK_SET, - }; - - r = fcntl(f->fd, F_OFD_SETLK, &fl); - if (r < 0 && errno == EINVAL) - r = flock(f->fd, LOCK_EX|LOCK_NB); - - if (r >= 0) - f->operation = LOCK_EX|LOCK_NB; - } - - if ((f->operation & ~LOCK_NB) == LOCK_EX) - unlink_noerrno(f->path); - - f->path = mfree(f->path); - } - - f->fd = safe_close(f->fd); - f->operation = 0; -} diff --git a/src/basic/lockfile-util.h b/src/basic/lockfile-util.h deleted file mode 100644 index 22491ee8e1..0000000000 --- a/src/basic/lockfile-util.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 "macro.h" -#include "missing.h" - -typedef struct LockFile { - char *path; - int fd; - int operation; -} LockFile; - -int make_lock_file(const char *p, int operation, LockFile *ret); -int make_lock_file_for(const char *p, int operation, LockFile *ret); -void release_lock_file(LockFile *f); - -#define _cleanup_release_lock_file_ _cleanup_(release_lock_file) - -#define LOCK_FILE_INIT { .fd = -1, .path = NULL } diff --git a/src/basic/log.c b/src/basic/log.c deleted file mode 100644 index 3ea643b6e6..0000000000 --- a/src/basic/log.c +++ /dev/null @@ -1,1170 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-messages.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "io-util.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "parse-util.h" -#include "proc-cmdline.h" -#include "process-util.h" -#include "signal-util.h" -#include "socket-util.h" -#include "stdio-util.h" -#include "string-table.h" -#include "string-util.h" -#include "syslog-util.h" -#include "terminal-util.h" -#include "time-util.h" -#include "util.h" - -#define SNDBUF_SIZE (8*1024*1024) - -static LogTarget log_target = LOG_TARGET_CONSOLE; -static int log_max_level = LOG_INFO; -static int log_facility = LOG_DAEMON; - -static int console_fd = STDERR_FILENO; -static int syslog_fd = -1; -static int kmsg_fd = -1; -static int journal_fd = -1; - -static bool syslog_is_stream = false; - -static bool show_color = false; -static bool show_location = false; - -static bool upgrade_syslog_to_journal = false; - -/* Akin to glibc's __abort_msg; which is private and we hence cannot - * use here. */ -static char *log_abort_msg = NULL; - -void log_close_console(void) { - - if (console_fd < 0) - return; - - if (getpid() == 1) { - if (console_fd >= 3) - safe_close(console_fd); - - console_fd = -1; - } -} - -static int log_open_console(void) { - - if (console_fd >= 0) - return 0; - - if (getpid() == 1) { - console_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); - if (console_fd < 0) - return console_fd; - } else - console_fd = STDERR_FILENO; - - return 0; -} - -void log_close_kmsg(void) { - kmsg_fd = safe_close(kmsg_fd); -} - -static int log_open_kmsg(void) { - - if (kmsg_fd >= 0) - return 0; - - kmsg_fd = open("/dev/kmsg", O_WRONLY|O_NOCTTY|O_CLOEXEC); - if (kmsg_fd < 0) - return -errno; - - return 0; -} - -void log_close_syslog(void) { - syslog_fd = safe_close(syslog_fd); -} - -static int create_log_socket(int type) { - struct timeval tv; - int fd; - - fd = socket(AF_UNIX, type|SOCK_CLOEXEC, 0); - if (fd < 0) - return -errno; - - fd_inc_sndbuf(fd, SNDBUF_SIZE); - - /* We need a blocking fd here since we'd otherwise lose - messages way too early. However, let's not hang forever in the - unlikely case of a deadlock. */ - if (getpid() == 1) - timeval_store(&tv, 10 * USEC_PER_MSEC); - else - timeval_store(&tv, 10 * USEC_PER_SEC); - (void) setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); - - return fd; -} - -static int log_open_syslog(void) { - - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/dev/log", - }; - - int r; - - if (syslog_fd >= 0) - return 0; - - syslog_fd = create_log_socket(SOCK_DGRAM); - if (syslog_fd < 0) { - r = syslog_fd; - goto fail; - } - - if (connect(syslog_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) { - safe_close(syslog_fd); - - /* Some legacy syslog systems still use stream - * sockets. They really shouldn't. But what can we - * do... */ - syslog_fd = create_log_socket(SOCK_STREAM); - if (syslog_fd < 0) { - r = syslog_fd; - goto fail; - } - - if (connect(syslog_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) { - r = -errno; - goto fail; - } - - syslog_is_stream = true; - } else - syslog_is_stream = false; - - return 0; - -fail: - log_close_syslog(); - return r; -} - -void log_close_journal(void) { - journal_fd = safe_close(journal_fd); -} - -static int log_open_journal(void) { - - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/journal/socket", - }; - - int r; - - if (journal_fd >= 0) - return 0; - - journal_fd = create_log_socket(SOCK_DGRAM); - if (journal_fd < 0) { - r = journal_fd; - goto fail; - } - - if (connect(journal_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) { - r = -errno; - goto fail; - } - - return 0; - -fail: - log_close_journal(); - return r; -} - -int log_open(void) { - int r; - - /* If we don't use the console we close it here, to not get - * killed by SAK. If we don't use syslog we close it here so - * that we are not confused by somebody deleting the socket in - * the fs. If we don't use /dev/kmsg we still keep it open, - * because there is no reason to close it. */ - - if (log_target == LOG_TARGET_NULL) { - log_close_journal(); - log_close_syslog(); - log_close_console(); - return 0; - } - - if ((log_target != LOG_TARGET_AUTO && log_target != LOG_TARGET_SAFE) || - getpid() == 1 || - isatty(STDERR_FILENO) <= 0) { - - if (log_target == LOG_TARGET_AUTO || - log_target == LOG_TARGET_JOURNAL_OR_KMSG || - log_target == LOG_TARGET_JOURNAL) { - r = log_open_journal(); - if (r >= 0) { - log_close_syslog(); - log_close_console(); - return r; - } - } - - if (log_target == LOG_TARGET_SYSLOG_OR_KMSG || - log_target == LOG_TARGET_SYSLOG) { - r = log_open_syslog(); - if (r >= 0) { - log_close_journal(); - log_close_console(); - return r; - } - } - - if (log_target == LOG_TARGET_AUTO || - log_target == LOG_TARGET_SAFE || - log_target == LOG_TARGET_JOURNAL_OR_KMSG || - log_target == LOG_TARGET_SYSLOG_OR_KMSG || - log_target == LOG_TARGET_KMSG) { - r = log_open_kmsg(); - if (r >= 0) { - log_close_journal(); - log_close_syslog(); - log_close_console(); - return r; - } - } - } - - log_close_journal(); - log_close_syslog(); - - return log_open_console(); -} - -void log_set_target(LogTarget target) { - assert(target >= 0); - assert(target < _LOG_TARGET_MAX); - - if (upgrade_syslog_to_journal) { - if (target == LOG_TARGET_SYSLOG) - target = LOG_TARGET_JOURNAL; - else if (target == LOG_TARGET_SYSLOG_OR_KMSG) - target = LOG_TARGET_JOURNAL_OR_KMSG; - } - - log_target = target; -} - -void log_close(void) { - log_close_journal(); - log_close_syslog(); - log_close_kmsg(); - log_close_console(); -} - -void log_forget_fds(void) { - console_fd = kmsg_fd = syslog_fd = journal_fd = -1; -} - -void log_set_max_level(int level) { - assert((level & LOG_PRIMASK) == level); - - log_max_level = level; -} - -void log_set_facility(int facility) { - log_facility = facility; -} - -static int write_to_console( - int level, - int error, - const char *file, - int line, - const char *func, - const char *object_field, - const char *object, - const char *buffer) { - - char location[64], prefix[1 + DECIMAL_STR_MAX(int) + 2]; - struct iovec iovec[6] = {}; - unsigned n = 0; - bool highlight; - - if (console_fd < 0) - return 0; - - if (log_target == LOG_TARGET_CONSOLE_PREFIXED) { - sprintf(prefix, "<%i>", level); - IOVEC_SET_STRING(iovec[n++], prefix); - } - - highlight = LOG_PRI(level) <= LOG_ERR && show_color; - - if (show_location) { - xsprintf(location, "(%s:%i) ", file, line); - IOVEC_SET_STRING(iovec[n++], location); - } - - if (highlight) - IOVEC_SET_STRING(iovec[n++], ANSI_HIGHLIGHT_RED); - IOVEC_SET_STRING(iovec[n++], buffer); - if (highlight) - IOVEC_SET_STRING(iovec[n++], ANSI_NORMAL); - IOVEC_SET_STRING(iovec[n++], "\n"); - - if (writev(console_fd, iovec, n) < 0) { - - if (errno == EIO && getpid() == 1) { - - /* If somebody tried to kick us from our - * console tty (via vhangup() or suchlike), - * try to reconnect */ - - log_close_console(); - log_open_console(); - - if (console_fd < 0) - return 0; - - if (writev(console_fd, iovec, n) < 0) - return -errno; - } else - return -errno; - } - - return 1; -} - -static int write_to_syslog( - int level, - int error, - const char *file, - int line, - const char *func, - const char *object_field, - const char *object, - const char *buffer) { - - char header_priority[2 + DECIMAL_STR_MAX(int) + 1], - header_time[64], - header_pid[4 + DECIMAL_STR_MAX(pid_t) + 1]; - struct iovec iovec[5] = {}; - struct msghdr msghdr = { - .msg_iov = iovec, - .msg_iovlen = ELEMENTSOF(iovec), - }; - time_t t; - struct tm *tm; - - if (syslog_fd < 0) - return 0; - - xsprintf(header_priority, "<%i>", level); - - t = (time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC); - tm = localtime(&t); - if (!tm) - return -EINVAL; - - if (strftime(header_time, sizeof(header_time), "%h %e %T ", tm) <= 0) - return -EINVAL; - - xsprintf(header_pid, "["PID_FMT"]: ", getpid()); - - IOVEC_SET_STRING(iovec[0], header_priority); - IOVEC_SET_STRING(iovec[1], header_time); - IOVEC_SET_STRING(iovec[2], program_invocation_short_name); - IOVEC_SET_STRING(iovec[3], header_pid); - IOVEC_SET_STRING(iovec[4], buffer); - - /* When using syslog via SOCK_STREAM separate the messages by NUL chars */ - if (syslog_is_stream) - iovec[4].iov_len++; - - for (;;) { - ssize_t n; - - n = sendmsg(syslog_fd, &msghdr, MSG_NOSIGNAL); - if (n < 0) - return -errno; - - if (!syslog_is_stream || - (size_t) n >= IOVEC_TOTAL_SIZE(iovec, ELEMENTSOF(iovec))) - break; - - IOVEC_INCREMENT(iovec, ELEMENTSOF(iovec), n); - } - - return 1; -} - -static int write_to_kmsg( - int level, - int error, - const char *file, - int line, - const char *func, - const char *object_field, - const char *object, - const char *buffer) { - - char header_priority[2 + DECIMAL_STR_MAX(int) + 1], - header_pid[4 + DECIMAL_STR_MAX(pid_t) + 1]; - struct iovec iovec[5] = {}; - - if (kmsg_fd < 0) - return 0; - - xsprintf(header_priority, "<%i>", level); - xsprintf(header_pid, "["PID_FMT"]: ", getpid()); - - IOVEC_SET_STRING(iovec[0], header_priority); - IOVEC_SET_STRING(iovec[1], program_invocation_short_name); - IOVEC_SET_STRING(iovec[2], header_pid); - IOVEC_SET_STRING(iovec[3], buffer); - IOVEC_SET_STRING(iovec[4], "\n"); - - if (writev(kmsg_fd, iovec, ELEMENTSOF(iovec)) < 0) - return -errno; - - return 1; -} - -static int log_do_header( - char *header, - size_t size, - int level, - int error, - const char *file, int line, const char *func, - const char *object_field, const char *object) { - - snprintf(header, size, - "PRIORITY=%i\n" - "SYSLOG_FACILITY=%i\n" - "%s%s%s" - "%s%.*i%s" - "%s%s%s" - "%s%.*i%s" - "%s%s%s" - "SYSLOG_IDENTIFIER=%s\n", - LOG_PRI(level), - LOG_FAC(level), - isempty(file) ? "" : "CODE_FILE=", - isempty(file) ? "" : file, - isempty(file) ? "" : "\n", - line ? "CODE_LINE=" : "", - line ? 1 : 0, line, /* %.0d means no output too, special case for 0 */ - line ? "\n" : "", - isempty(func) ? "" : "CODE_FUNCTION=", - isempty(func) ? "" : func, - isempty(func) ? "" : "\n", - error ? "ERRNO=" : "", - error ? 1 : 0, error, - error ? "\n" : "", - isempty(object) ? "" : object_field, - isempty(object) ? "" : object, - isempty(object) ? "" : "\n", - program_invocation_short_name); - - return 0; -} - -static int write_to_journal( - int level, - int error, - const char *file, - int line, - const char *func, - const char *object_field, - const char *object, - const char *buffer) { - - char header[LINE_MAX]; - struct iovec iovec[4] = {}; - struct msghdr mh = {}; - - if (journal_fd < 0) - return 0; - - log_do_header(header, sizeof(header), level, error, file, line, func, object_field, object); - - IOVEC_SET_STRING(iovec[0], header); - IOVEC_SET_STRING(iovec[1], "MESSAGE="); - IOVEC_SET_STRING(iovec[2], buffer); - IOVEC_SET_STRING(iovec[3], "\n"); - - mh.msg_iov = iovec; - mh.msg_iovlen = ELEMENTSOF(iovec); - - if (sendmsg(journal_fd, &mh, MSG_NOSIGNAL) < 0) - return -errno; - - return 1; -} - -static int log_dispatch( - int level, - int error, - const char *file, - int line, - const char *func, - const char *object_field, - const char *object, - char *buffer) { - - assert(buffer); - - if (log_target == LOG_TARGET_NULL) - return -error; - - /* Patch in LOG_DAEMON facility if necessary */ - if ((level & LOG_FACMASK) == 0) - level = log_facility | LOG_PRI(level); - - if (error < 0) - error = -error; - - do { - char *e; - int k = 0; - - buffer += strspn(buffer, NEWLINE); - - if (buffer[0] == 0) - break; - - if ((e = strpbrk(buffer, NEWLINE))) - *(e++) = 0; - - if (log_target == LOG_TARGET_AUTO || - log_target == LOG_TARGET_JOURNAL_OR_KMSG || - log_target == LOG_TARGET_JOURNAL) { - - k = write_to_journal(level, error, file, line, func, object_field, object, buffer); - if (k < 0) { - if (k != -EAGAIN) - log_close_journal(); - log_open_kmsg(); - } - } - - if (log_target == LOG_TARGET_SYSLOG_OR_KMSG || - log_target == LOG_TARGET_SYSLOG) { - - k = write_to_syslog(level, error, file, line, func, object_field, object, buffer); - if (k < 0) { - if (k != -EAGAIN) - log_close_syslog(); - log_open_kmsg(); - } - } - - if (k <= 0 && - (log_target == LOG_TARGET_AUTO || - log_target == LOG_TARGET_SAFE || - log_target == LOG_TARGET_SYSLOG_OR_KMSG || - log_target == LOG_TARGET_JOURNAL_OR_KMSG || - log_target == LOG_TARGET_KMSG)) { - - k = write_to_kmsg(level, error, file, line, func, object_field, object, buffer); - if (k < 0) { - log_close_kmsg(); - log_open_console(); - } - } - - if (k <= 0) - (void) write_to_console(level, error, file, line, func, object_field, object, buffer); - - buffer = e; - } while (buffer); - - return -error; -} - -int log_dump_internal( - int level, - int error, - const char *file, - int line, - const char *func, - char *buffer) { - - PROTECT_ERRNO; - - /* This modifies the buffer... */ - - if (error < 0) - error = -error; - - if (_likely_(LOG_PRI(level) > log_max_level)) - return -error; - - return log_dispatch(level, error, file, line, func, NULL, NULL, buffer); -} - -int log_internalv( - int level, - int error, - const char *file, - int line, - const char *func, - const char *format, - va_list ap) { - - PROTECT_ERRNO; - char buffer[LINE_MAX]; - - if (error < 0) - error = -error; - - if (_likely_(LOG_PRI(level) > log_max_level)) - return -error; - - /* Make sure that %m maps to the specified error */ - if (error != 0) - errno = error; - - vsnprintf(buffer, sizeof(buffer), format, ap); - - return log_dispatch(level, error, file, line, func, NULL, NULL, buffer); -} - -int log_internal( - int level, - int error, - const char *file, - int line, - const char *func, - const char *format, ...) { - - va_list ap; - int r; - - va_start(ap, format); - r = log_internalv(level, error, file, line, func, format, ap); - va_end(ap); - - return r; -} - -int log_object_internalv( - int level, - int error, - const char *file, - int line, - const char *func, - const char *object_field, - const char *object, - const char *format, - va_list ap) { - - PROTECT_ERRNO; - char *buffer, *b; - size_t l; - - if (error < 0) - error = -error; - - if (_likely_(LOG_PRI(level) > log_max_level)) - return -error; - - /* Make sure that %m maps to the specified error */ - if (error != 0) - errno = error; - - /* Prepend the object name before the message */ - if (object) { - size_t n; - - n = strlen(object); - l = n + 2 + LINE_MAX; - - buffer = newa(char, l); - b = stpcpy(stpcpy(buffer, object), ": "); - } else { - l = LINE_MAX; - b = buffer = newa(char, l); - } - - vsnprintf(b, l, format, ap); - - return log_dispatch(level, error, file, line, func, object_field, object, buffer); -} - -int log_object_internal( - int level, - int error, - const char *file, - int line, - const char *func, - const char *object_field, - const char *object, - const char *format, ...) { - - va_list ap; - int r; - - va_start(ap, format); - r = log_object_internalv(level, error, file, line, func, object_field, object, format, ap); - va_end(ap); - - return r; -} - -static void log_assert( - int level, - const char *text, - const char *file, - int line, - const char *func, - const char *format) { - - static char buffer[LINE_MAX]; - - if (_likely_(LOG_PRI(level) > log_max_level)) - return; - - DISABLE_WARNING_FORMAT_NONLITERAL; - xsprintf(buffer, format, text, file, line, func); - REENABLE_WARNING; - - log_abort_msg = buffer; - - log_dispatch(level, 0, file, line, func, NULL, NULL, buffer); -} - -noreturn void log_assert_failed(const char *text, const char *file, int line, const char *func) { - log_assert(LOG_CRIT, text, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Aborting."); - abort(); -} - -noreturn void log_assert_failed_unreachable(const char *text, const char *file, int line, const char *func) { - log_assert(LOG_CRIT, text, file, line, func, "Code should not be reached '%s' at %s:%u, function %s(). Aborting."); - abort(); -} - -void log_assert_failed_return(const char *text, const char *file, int line, const char *func) { - PROTECT_ERRNO; - log_assert(LOG_DEBUG, text, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Ignoring."); -} - -int log_oom_internal(const char *file, int line, const char *func) { - log_internal(LOG_ERR, ENOMEM, file, line, func, "Out of memory."); - return -ENOMEM; -} - -int log_format_iovec( - struct iovec *iovec, - unsigned iovec_len, - unsigned *n, - bool newline_separator, - int error, - const char *format, - va_list ap) { - - static const char nl = '\n'; - - while (format && *n + 1 < iovec_len) { - va_list aq; - char *m; - int r; - - /* We need to copy the va_list structure, - * since vasprintf() leaves it afterwards at - * an undefined location */ - - if (error != 0) - errno = error; - - va_copy(aq, ap); - r = vasprintf(&m, format, aq); - va_end(aq); - if (r < 0) - return -EINVAL; - - /* Now, jump enough ahead, so that we point to - * the next format string */ - VA_FORMAT_ADVANCE(format, ap); - - IOVEC_SET_STRING(iovec[(*n)++], m); - - if (newline_separator) { - iovec[*n].iov_base = (char*) &nl; - iovec[*n].iov_len = 1; - (*n)++; - } - - format = va_arg(ap, char *); - } - return 0; -} - -int log_struct_internal( - int level, - int error, - const char *file, - int line, - const char *func, - const char *format, ...) { - - char buf[LINE_MAX]; - bool found = false; - PROTECT_ERRNO; - va_list ap; - - if (error < 0) - error = -error; - - if (_likely_(LOG_PRI(level) > log_max_level)) - return -error; - - if (log_target == LOG_TARGET_NULL) - return -error; - - if ((level & LOG_FACMASK) == 0) - level = log_facility | LOG_PRI(level); - - if ((log_target == LOG_TARGET_AUTO || - log_target == LOG_TARGET_JOURNAL_OR_KMSG || - log_target == LOG_TARGET_JOURNAL) && - journal_fd >= 0) { - char header[LINE_MAX]; - struct iovec iovec[17] = {}; - unsigned n = 0, i; - int r; - struct msghdr mh = { - .msg_iov = iovec, - }; - bool fallback = false; - - /* If the journal is available do structured logging */ - log_do_header(header, sizeof(header), level, error, file, line, func, NULL, NULL); - IOVEC_SET_STRING(iovec[n++], header); - - va_start(ap, format); - r = log_format_iovec(iovec, ELEMENTSOF(iovec), &n, true, error, format, ap); - if (r < 0) - fallback = true; - else { - mh.msg_iovlen = n; - (void) sendmsg(journal_fd, &mh, MSG_NOSIGNAL); - } - - va_end(ap); - for (i = 1; i < n; i += 2) - free(iovec[i].iov_base); - - if (!fallback) - return -error; - } - - /* Fallback if journal logging is not available or didn't work. */ - - va_start(ap, format); - while (format) { - va_list aq; - - if (error != 0) - errno = error; - - va_copy(aq, ap); - vsnprintf(buf, sizeof(buf), format, aq); - va_end(aq); - - if (startswith(buf, "MESSAGE=")) { - found = true; - break; - } - - VA_FORMAT_ADVANCE(format, ap); - - format = va_arg(ap, char *); - } - va_end(ap); - - if (!found) - return -error; - - return log_dispatch(level, error, file, line, func, NULL, NULL, buf + 8); -} - -int log_set_target_from_string(const char *e) { - LogTarget t; - - t = log_target_from_string(e); - if (t < 0) - return -EINVAL; - - log_set_target(t); - return 0; -} - -int log_set_max_level_from_string(const char *e) { - int t; - - t = log_level_from_string(e); - if (t < 0) - return -EINVAL; - - log_set_max_level(t); - return 0; -} - -static int parse_proc_cmdline_item(const char *key, const char *value) { - - /* - * The systemd.log_xyz= settings are parsed by all tools, and - * so is "debug". - * - * However, "quiet" is only parsed by PID 1, and only turns of - * status output to /dev/console, but does not alter the log - * level. - */ - - if (streq(key, "debug") && !value) - log_set_max_level(LOG_DEBUG); - - else if (streq(key, "systemd.log_target") && value) { - - if (log_set_target_from_string(value) < 0) - log_warning("Failed to parse log target '%s'. Ignoring.", value); - - } else if (streq(key, "systemd.log_level") && value) { - - if (log_set_max_level_from_string(value) < 0) - log_warning("Failed to parse log level '%s'. Ignoring.", value); - - } else if (streq(key, "systemd.log_color") && value) { - - if (log_show_color_from_string(value) < 0) - log_warning("Failed to parse log color setting '%s'. Ignoring.", value); - - } else if (streq(key, "systemd.log_location") && value) { - - if (log_show_location_from_string(value) < 0) - log_warning("Failed to parse log location setting '%s'. Ignoring.", value); - } - - return 0; -} - -void log_parse_environment(void) { - const char *e; - - if (get_ctty_devnr(0, NULL) < 0) - /* Only try to read the command line in daemons. - We assume that anything that has a controlling - tty is user stuff. */ - (void) parse_proc_cmdline(parse_proc_cmdline_item); - - e = secure_getenv("SYSTEMD_LOG_TARGET"); - if (e && log_set_target_from_string(e) < 0) - log_warning("Failed to parse log target '%s'. Ignoring.", e); - - e = secure_getenv("SYSTEMD_LOG_LEVEL"); - if (e && log_set_max_level_from_string(e) < 0) - log_warning("Failed to parse log level '%s'. Ignoring.", e); - - e = secure_getenv("SYSTEMD_LOG_COLOR"); - if (e && log_show_color_from_string(e) < 0) - log_warning("Failed to parse bool '%s'. Ignoring.", e); - - e = secure_getenv("SYSTEMD_LOG_LOCATION"); - if (e && log_show_location_from_string(e) < 0) - log_warning("Failed to parse bool '%s'. Ignoring.", e); -} - -LogTarget log_get_target(void) { - return log_target; -} - -int log_get_max_level(void) { - return log_max_level; -} - -void log_show_color(bool b) { - show_color = b; -} - -bool log_get_show_color(void) { - return show_color; -} - -void log_show_location(bool b) { - show_location = b; -} - -bool log_get_show_location(void) { - return show_location; -} - -int log_show_color_from_string(const char *e) { - int t; - - t = parse_boolean(e); - if (t < 0) - return t; - - log_show_color(t); - return 0; -} - -int log_show_location_from_string(const char *e) { - int t; - - t = parse_boolean(e); - if (t < 0) - return t; - - log_show_location(t); - return 0; -} - -bool log_on_console(void) { - if (log_target == LOG_TARGET_CONSOLE || - log_target == LOG_TARGET_CONSOLE_PREFIXED) - return true; - - return syslog_fd < 0 && kmsg_fd < 0 && journal_fd < 0; -} - -static const char *const log_target_table[_LOG_TARGET_MAX] = { - [LOG_TARGET_CONSOLE] = "console", - [LOG_TARGET_CONSOLE_PREFIXED] = "console-prefixed", - [LOG_TARGET_KMSG] = "kmsg", - [LOG_TARGET_JOURNAL] = "journal", - [LOG_TARGET_JOURNAL_OR_KMSG] = "journal-or-kmsg", - [LOG_TARGET_SYSLOG] = "syslog", - [LOG_TARGET_SYSLOG_OR_KMSG] = "syslog-or-kmsg", - [LOG_TARGET_AUTO] = "auto", - [LOG_TARGET_SAFE] = "safe", - [LOG_TARGET_NULL] = "null" -}; - -DEFINE_STRING_TABLE_LOOKUP(log_target, LogTarget); - -void log_received_signal(int level, const struct signalfd_siginfo *si) { - if (si->ssi_pid > 0) { - _cleanup_free_ char *p = NULL; - - get_process_comm(si->ssi_pid, &p); - - log_full(level, - "Received SIG%s from PID %"PRIu32" (%s).", - signal_to_string(si->ssi_signo), - si->ssi_pid, strna(p)); - } else - log_full(level, - "Received SIG%s.", - signal_to_string(si->ssi_signo)); - -} - -void log_set_upgrade_syslog_to_journal(bool b) { - upgrade_syslog_to_journal = b; -} - -int log_syntax_internal( - const char *unit, - int level, - const char *config_file, - unsigned config_line, - int error, - const char *file, - int line, - const char *func, - const char *format, ...) { - - PROTECT_ERRNO; - char buffer[LINE_MAX]; - int r; - va_list ap; - - if (error < 0) - error = -error; - - if (_likely_(LOG_PRI(level) > log_max_level)) - return -error; - - if (log_target == LOG_TARGET_NULL) - return -error; - - if (error != 0) - errno = error; - - va_start(ap, format); - vsnprintf(buffer, sizeof(buffer), format, ap); - va_end(ap); - - if (unit) - r = log_struct_internal( - level, error, - file, line, func, - getpid() == 1 ? "UNIT=%s" : "USER_UNIT=%s", unit, - LOG_MESSAGE_ID(SD_MESSAGE_INVALID_CONFIGURATION), - "CONFIG_FILE=%s", config_file, - "CONFIG_LINE=%u", config_line, - LOG_MESSAGE("[%s:%u] %s", config_file, config_line, buffer), - NULL); - else - r = log_struct_internal( - level, error, - file, line, func, - LOG_MESSAGE_ID(SD_MESSAGE_INVALID_CONFIGURATION), - "CONFIG_FILE=%s", config_file, - "CONFIG_LINE=%u", config_line, - LOG_MESSAGE("[%s:%u] %s", config_file, config_line, buffer), - NULL); - - return r; -} diff --git a/src/basic/log.h b/src/basic/log.h deleted file mode 100644 index b6356228d9..0000000000 --- a/src/basic/log.h +++ /dev/null @@ -1,249 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include - -#include "sd-id128.h" - -#include "macro.h" - -typedef enum LogTarget{ - LOG_TARGET_CONSOLE, - LOG_TARGET_CONSOLE_PREFIXED, - LOG_TARGET_KMSG, - LOG_TARGET_JOURNAL, - LOG_TARGET_JOURNAL_OR_KMSG, - LOG_TARGET_SYSLOG, - LOG_TARGET_SYSLOG_OR_KMSG, - LOG_TARGET_AUTO, /* console if stderr is tty, JOURNAL_OR_KMSG otherwise */ - LOG_TARGET_SAFE, /* console if stderr is tty, KMSG otherwise */ - LOG_TARGET_NULL, - _LOG_TARGET_MAX, - _LOG_TARGET_INVALID = -1 -} LogTarget; - -void log_set_target(LogTarget target); -void log_set_max_level(int level); -void log_set_facility(int facility); - -int log_set_target_from_string(const char *e); -int log_set_max_level_from_string(const char *e); - -void log_show_color(bool b); -bool log_get_show_color(void) _pure_; -void log_show_location(bool b); -bool log_get_show_location(void) _pure_; - -int log_show_color_from_string(const char *e); -int log_show_location_from_string(const char *e); - -LogTarget log_get_target(void) _pure_; -int log_get_max_level(void) _pure_; - -int log_open(void); -void log_close(void); -void log_forget_fds(void); - -void log_close_syslog(void); -void log_close_journal(void); -void log_close_kmsg(void); -void log_close_console(void); - -void log_parse_environment(void); - -int log_internal( - int level, - int error, - const char *file, - int line, - const char *func, - const char *format, ...) _printf_(6,7); - -int log_internalv( - int level, - int error, - const char *file, - int line, - const char *func, - const char *format, - va_list ap) _printf_(6,0); - -int log_object_internal( - int level, - int error, - const char *file, - int line, - const char *func, - const char *object_field, - const char *object, - const char *format, ...) _printf_(8,9); - -int log_object_internalv( - int level, - int error, - const char*file, - int line, - const char *func, - const char *object_field, - const char *object, - const char *format, - va_list ap) _printf_(8,0); - -int log_struct_internal( - int level, - int error, - const char *file, - int line, - const char *func, - const char *format, ...) _printf_(6,0) _sentinel_; - -int log_oom_internal( - const char *file, - int line, - const char *func); - -int log_format_iovec( - struct iovec *iovec, - unsigned iovec_len, - unsigned *n, - bool newline_separator, - int error, - const char *format, - va_list ap); - -/* This modifies the buffer passed! */ -int log_dump_internal( - int level, - int error, - const char *file, - int line, - const char *func, - char *buffer); - -/* Logging for various assertions */ -noreturn void log_assert_failed( - const char *text, - const char *file, - int line, - const char *func); - -noreturn void log_assert_failed_unreachable( - const char *text, - const char *file, - int line, - const char *func); - -void log_assert_failed_return( - const char *text, - const char *file, - int line, - const char *func); - -/* Logging with level */ -#define log_full_errno(level, error, ...) \ - ({ \ - int _level = (level), _e = (error); \ - (log_get_max_level() >= LOG_PRI(_level)) \ - ? log_internal(_level, _e, __FILE__, __LINE__, __func__, __VA_ARGS__) \ - : -abs(_e); \ - }) - -#define log_full(level, ...) log_full_errno(level, 0, __VA_ARGS__) - -/* Normal logging */ -#define log_debug(...) log_full(LOG_DEBUG, __VA_ARGS__) -#define log_info(...) log_full(LOG_INFO, __VA_ARGS__) -#define log_notice(...) log_full(LOG_NOTICE, __VA_ARGS__) -#define log_warning(...) log_full(LOG_WARNING, __VA_ARGS__) -#define log_error(...) log_full(LOG_ERR, __VA_ARGS__) -#define log_emergency(...) log_full(getpid() == 1 ? LOG_EMERG : LOG_ERR, __VA_ARGS__) - -/* Logging triggered by an errno-like error */ -#define log_debug_errno(error, ...) log_full_errno(LOG_DEBUG, error, __VA_ARGS__) -#define log_info_errno(error, ...) log_full_errno(LOG_INFO, error, __VA_ARGS__) -#define log_notice_errno(error, ...) log_full_errno(LOG_NOTICE, error, __VA_ARGS__) -#define log_warning_errno(error, ...) log_full_errno(LOG_WARNING, error, __VA_ARGS__) -#define log_error_errno(error, ...) log_full_errno(LOG_ERR, error, __VA_ARGS__) -#define log_emergency_errno(error, ...) log_full_errno(getpid() == 1 ? LOG_EMERG : LOG_ERR, error, __VA_ARGS__) - -#ifdef LOG_TRACE -# define log_trace(...) log_debug(__VA_ARGS__) -#else -# define log_trace(...) do {} while (0) -#endif - -/* Structured logging */ -#define log_struct(level, ...) log_struct_internal(level, 0, __FILE__, __LINE__, __func__, __VA_ARGS__) -#define log_struct_errno(level, error, ...) log_struct_internal(level, error, __FILE__, __LINE__, __func__, __VA_ARGS__) - -/* This modifies the buffer passed! */ -#define log_dump(level, buffer) log_dump_internal(level, 0, __FILE__, __LINE__, __func__, buffer) - -#define log_oom() log_oom_internal(__FILE__, __LINE__, __func__) - -bool log_on_console(void) _pure_; - -const char *log_target_to_string(LogTarget target) _const_; -LogTarget log_target_from_string(const char *s) _pure_; - -/* Helpers to prepare various fields for structured logging */ -#define LOG_MESSAGE(fmt, ...) "MESSAGE=" fmt, ##__VA_ARGS__ -#define LOG_MESSAGE_ID(x) "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(x) - -void log_received_signal(int level, const struct signalfd_siginfo *si); - -void log_set_upgrade_syslog_to_journal(bool b); - -int log_syntax_internal( - const char *unit, - int level, - const char *config_file, - unsigned config_line, - int error, - const char *file, - int line, - const char *func, - const char *format, ...) _printf_(9, 10); - -#define log_syntax(unit, level, config_file, config_line, error, ...) \ - ({ \ - int _level = (level), _e = (error); \ - (log_get_max_level() >= LOG_PRI(_level)) \ - ? log_syntax_internal(unit, _level, config_file, config_line, _e, __FILE__, __LINE__, __func__, __VA_ARGS__) \ - : -abs(_e); \ - }) - -#define log_syntax_invalid_utf8(unit, level, config_file, config_line, rvalue) \ - ({ \ - int _level = (level); \ - if (log_get_max_level() >= LOG_PRI(_level)) { \ - _cleanup_free_ char *_p = NULL; \ - _p = utf8_escape_invalid(rvalue); \ - log_syntax_internal(unit, _level, config_file, config_line, 0, __FILE__, __LINE__, __func__, \ - "String is not UTF-8 clean, ignoring assignment: %s", strna(_p)); \ - } \ - }) diff --git a/src/basic/login-util.c b/src/basic/login-util.c deleted file mode 100644 index 339e94f12d..0000000000 --- a/src/basic/login-util.c +++ /dev/null @@ -1,31 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 Zbigniew Jędrzejewski-Szmek - - 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 "login-util.h" -#include "string-util.h" - -bool session_id_valid(const char *id) { - - if (isempty(id)) - return false; - - return id[strspn(id, LETTERS DIGITS)] == '\0'; -} diff --git a/src/basic/login-util.h b/src/basic/login-util.h deleted file mode 100644 index b01ee25c88..0000000000 --- a/src/basic/login-util.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 Zbigniew Jędrzejewski-Szmek - - 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 - -bool session_id_valid(const char *id); - -static inline bool logind_running(void) { - return access("/run/systemd/seats/", F_OK) >= 0; -} diff --git a/src/basic/macro.h b/src/basic/macro.h deleted file mode 100644 index e41aa4260f..0000000000 --- a/src/basic/macro.h +++ /dev/null @@ -1,406 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include - -#define _printf_(a,b) __attribute__ ((format (printf, a, b))) -#ifdef __clang__ -# define _alloc_(...) -#else -# define _alloc_(...) __attribute__ ((alloc_size(__VA_ARGS__))) -#endif -#define _sentinel_ __attribute__ ((sentinel)) -#define _unused_ __attribute__ ((unused)) -#define _destructor_ __attribute__ ((destructor)) -#define _pure_ __attribute__ ((pure)) -#define _const_ __attribute__ ((const)) -#define _deprecated_ __attribute__ ((deprecated)) -#define _packed_ __attribute__ ((packed)) -#define _malloc_ __attribute__ ((malloc)) -#define _weak_ __attribute__ ((weak)) -#define _likely_(x) (__builtin_expect(!!(x),1)) -#define _unlikely_(x) (__builtin_expect(!!(x),0)) -#define _public_ __attribute__ ((visibility("default"))) -#define _hidden_ __attribute__ ((visibility("hidden"))) -#define _weakref_(x) __attribute__((weakref(#x))) -#define _alignas_(x) __attribute__((aligned(__alignof(x)))) -#define _cleanup_(x) __attribute__((cleanup(x))) - -/* Temporarily disable some warnings */ -#define DISABLE_WARNING_DECLARATION_AFTER_STATEMENT \ - _Pragma("GCC diagnostic push"); \ - _Pragma("GCC diagnostic ignored \"-Wdeclaration-after-statement\"") - -#define DISABLE_WARNING_FORMAT_NONLITERAL \ - _Pragma("GCC diagnostic push"); \ - _Pragma("GCC diagnostic ignored \"-Wformat-nonliteral\"") - -#define DISABLE_WARNING_MISSING_PROTOTYPES \ - _Pragma("GCC diagnostic push"); \ - _Pragma("GCC diagnostic ignored \"-Wmissing-prototypes\"") - -#define DISABLE_WARNING_NONNULL \ - _Pragma("GCC diagnostic push"); \ - _Pragma("GCC diagnostic ignored \"-Wnonnull\"") - -#define DISABLE_WARNING_SHADOW \ - _Pragma("GCC diagnostic push"); \ - _Pragma("GCC diagnostic ignored \"-Wshadow\"") - -#define DISABLE_WARNING_INCOMPATIBLE_POINTER_TYPES \ - _Pragma("GCC diagnostic push"); \ - _Pragma("GCC diagnostic ignored \"-Wincompatible-pointer-types\"") - -#define REENABLE_WARNING \ - _Pragma("GCC diagnostic pop") - -/* automake test harness */ -#define EXIT_TEST_SKIP 77 - -#define XSTRINGIFY(x) #x -#define STRINGIFY(x) XSTRINGIFY(x) - -#define XCONCATENATE(x, y) x ## y -#define CONCATENATE(x, y) XCONCATENATE(x, y) - -#define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq)) -#define UNIQ __COUNTER__ - -/* Rounds up */ - -#define ALIGN4(l) (((l) + 3) & ~3) -#define ALIGN8(l) (((l) + 7) & ~7) - -#if __SIZEOF_POINTER__ == 8 -#define ALIGN(l) ALIGN8(l) -#elif __SIZEOF_POINTER__ == 4 -#define ALIGN(l) ALIGN4(l) -#else -#error "Wut? Pointers are neither 4 nor 8 bytes long?" -#endif - -#define ALIGN_PTR(p) ((void*) ALIGN((unsigned long) (p))) -#define ALIGN4_PTR(p) ((void*) ALIGN4((unsigned long) (p))) -#define ALIGN8_PTR(p) ((void*) ALIGN8((unsigned long) (p))) - -static inline size_t ALIGN_TO(size_t l, size_t ali) { - return ((l + ali - 1) & ~(ali - 1)); -} - -#define ALIGN_TO_PTR(p, ali) ((void*) ALIGN_TO((unsigned long) (p), (ali))) - -/* align to next higher power-of-2 (except for: 0 => 0, overflow => 0) */ -static inline unsigned long ALIGN_POWER2(unsigned long u) { - /* clz(0) is undefined */ - if (u == 1) - return 1; - - /* left-shift overflow is undefined */ - if (__builtin_clzl(u - 1UL) < 1) - return 0; - - return 1UL << (sizeof(u) * 8 - __builtin_clzl(u - 1UL)); -} - -#define ELEMENTSOF(x) \ - __extension__ (__builtin_choose_expr( \ - !__builtin_types_compatible_p(typeof(x), typeof(&*(x))), \ - sizeof(x)/sizeof((x)[0]), \ - (void)0)) -/* - * container_of - cast a member of a structure out to the containing structure - * @ptr: the pointer to the member. - * @type: the type of the container struct this is embedded in. - * @member: the name of the member within the struct. - */ -#define container_of(ptr, type, member) __container_of(UNIQ, (ptr), type, member) -#define __container_of(uniq, ptr, type, member) \ - __extension__ ({ \ - const typeof( ((type*)0)->member ) *UNIQ_T(A, uniq) = (ptr); \ - (type*)( (char *)UNIQ_T(A, uniq) - offsetof(type,member) ); \ - }) - -#undef MAX -#define MAX(a, b) __MAX(UNIQ, (a), UNIQ, (b)) -#define __MAX(aq, a, bq, b) \ - __extension__ ({ \ - const typeof(a) UNIQ_T(A, aq) = (a); \ - const typeof(b) UNIQ_T(B, bq) = (b); \ - UNIQ_T(A,aq) > UNIQ_T(B,bq) ? UNIQ_T(A,aq) : UNIQ_T(B,bq); \ - }) - -/* evaluates to (void) if _A or _B are not constant or of different types */ -#define CONST_MAX(_A, _B) \ - __extension__ (__builtin_choose_expr( \ - __builtin_constant_p(_A) && \ - __builtin_constant_p(_B) && \ - __builtin_types_compatible_p(typeof(_A), typeof(_B)), \ - ((_A) > (_B)) ? (_A) : (_B), \ - (void)0)) - -/* takes two types and returns the size of the larger one */ -#define MAXSIZE(A, B) (sizeof(union _packed_ { typeof(A) a; typeof(B) b; })) - -#define MAX3(x,y,z) \ - __extension__ ({ \ - const typeof(x) _c = MAX(x,y); \ - MAX(_c, z); \ - }) - -#undef MIN -#define MIN(a, b) __MIN(UNIQ, (a), UNIQ, (b)) -#define __MIN(aq, a, bq, b) \ - __extension__ ({ \ - const typeof(a) UNIQ_T(A, aq) = (a); \ - const typeof(b) UNIQ_T(B, bq) = (b); \ - UNIQ_T(A,aq) < UNIQ_T(B,bq) ? UNIQ_T(A,aq) : UNIQ_T(B,bq); \ - }) - -#define MIN3(x,y,z) \ - __extension__ ({ \ - const typeof(x) _c = MIN(x,y); \ - MIN(_c, z); \ - }) - -#define LESS_BY(a, b) __LESS_BY(UNIQ, (a), UNIQ, (b)) -#define __LESS_BY(aq, a, bq, b) \ - __extension__ ({ \ - const typeof(a) UNIQ_T(A, aq) = (a); \ - const typeof(b) UNIQ_T(B, bq) = (b); \ - UNIQ_T(A,aq) > UNIQ_T(B,bq) ? UNIQ_T(A,aq) - UNIQ_T(B,bq) : 0; \ - }) - -#undef CLAMP -#define CLAMP(x, low, high) __CLAMP(UNIQ, (x), UNIQ, (low), UNIQ, (high)) -#define __CLAMP(xq, x, lowq, low, highq, high) \ - __extension__ ({ \ - const typeof(x) UNIQ_T(X,xq) = (x); \ - const typeof(low) UNIQ_T(LOW,lowq) = (low); \ - const typeof(high) UNIQ_T(HIGH,highq) = (high); \ - UNIQ_T(X,xq) > UNIQ_T(HIGH,highq) ? \ - UNIQ_T(HIGH,highq) : \ - UNIQ_T(X,xq) < UNIQ_T(LOW,lowq) ? \ - UNIQ_T(LOW,lowq) : \ - UNIQ_T(X,xq); \ - }) - -/* [(x + y - 1) / y] suffers from an integer overflow, even though the - * computation should be possible in the given type. Therefore, we use - * [x / y + !!(x % y)]. Note that on "Real CPUs" a division returns both the - * quotient and the remainder, so both should be equally fast. */ -#define DIV_ROUND_UP(_x, _y) \ - __extension__ ({ \ - const typeof(_x) __x = (_x); \ - const typeof(_y) __y = (_y); \ - (__x / __y + !!(__x % __y)); \ - }) - -#define assert_message_se(expr, message) \ - do { \ - if (_unlikely_(!(expr))) \ - log_assert_failed(message, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ - } while (false) - -#define assert_se(expr) assert_message_se(expr, #expr) - -/* We override the glibc assert() here. */ -#undef assert -#ifdef NDEBUG -#define assert(expr) do {} while (false) -#else -#define assert(expr) assert_message_se(expr, #expr) -#endif - -#define assert_not_reached(t) \ - do { \ - log_assert_failed_unreachable(t, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ - } while (false) - -#if defined(static_assert) -/* static_assert() is sometimes defined in a way that trips up - * -Wdeclaration-after-statement, hence let's temporarily turn off - * this warning around it. */ -#define assert_cc(expr) \ - DISABLE_WARNING_DECLARATION_AFTER_STATEMENT; \ - static_assert(expr, #expr); \ - REENABLE_WARNING -#else -#define assert_cc(expr) \ - DISABLE_WARNING_DECLARATION_AFTER_STATEMENT; \ - struct CONCATENATE(_assert_struct_, __COUNTER__) { \ - char x[(expr) ? 0 : -1]; \ - }; \ - REENABLE_WARNING -#endif - -#define assert_log(expr, message) ((_likely_(expr)) \ - ? (true) \ - : (log_assert_failed_return(message, __FILE__, __LINE__, __PRETTY_FUNCTION__), false)) - -#define assert_return(expr, r) \ - do { \ - if (!assert_log(expr, #expr)) \ - return (r); \ - } while (false) - -#define assert_return_errno(expr, r, err) \ - do { \ - if (!assert_log(expr, #expr)) { \ - errno = err; \ - return (r); \ - } \ - } while (false) - -#define PTR_TO_INT(p) ((int) ((intptr_t) (p))) -#define INT_TO_PTR(u) ((void *) ((intptr_t) (u))) -#define PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p))) -#define UINT_TO_PTR(u) ((void *) ((uintptr_t) (u))) - -#define PTR_TO_LONG(p) ((long) ((intptr_t) (p))) -#define LONG_TO_PTR(u) ((void *) ((intptr_t) (u))) -#define PTR_TO_ULONG(p) ((unsigned long) ((uintptr_t) (p))) -#define ULONG_TO_PTR(u) ((void *) ((uintptr_t) (u))) - -#define PTR_TO_INT32(p) ((int32_t) ((intptr_t) (p))) -#define INT32_TO_PTR(u) ((void *) ((intptr_t) (u))) -#define PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p))) -#define UINT32_TO_PTR(u) ((void *) ((uintptr_t) (u))) - -#define PTR_TO_INT64(p) ((int64_t) ((intptr_t) (p))) -#define INT64_TO_PTR(u) ((void *) ((intptr_t) (u))) -#define PTR_TO_UINT64(p) ((uint64_t) ((uintptr_t) (p))) -#define UINT64_TO_PTR(u) ((void *) ((uintptr_t) (u))) - -#define PTR_TO_SIZE(p) ((size_t) ((uintptr_t) (p))) -#define SIZE_TO_PTR(u) ((void *) ((uintptr_t) (u))) - -#define CHAR_TO_STR(x) ((char[2]) { x, 0 }) - -#define char_array_0(x) x[sizeof(x)-1] = 0; - -/* Returns the number of chars needed to format variables of the - * specified type as a decimal string. Adds in extra space for a - * negative '-' prefix (hence works correctly on signed - * types). Includes space for the trailing NUL. */ -#define DECIMAL_STR_MAX(type) \ - (2+(sizeof(type) <= 1 ? 3 : \ - sizeof(type) <= 2 ? 5 : \ - sizeof(type) <= 4 ? 10 : \ - sizeof(type) <= 8 ? 20 : sizeof(int[-2*(sizeof(type) > 8)]))) - -#define DECIMAL_STR_WIDTH(x) \ - ({ \ - typeof(x) _x_ = (x); \ - unsigned ans = 1; \ - while (_x_ /= 10) \ - ans++; \ - ans; \ - }) - -#define SET_FLAG(v, flag, b) \ - (v) = (b) ? ((v) | (flag)) : ((v) & ~(flag)) - -#define CASE_F(X) case X: -#define CASE_F_1(CASE, X) CASE_F(X) -#define CASE_F_2(CASE, X, ...) CASE(X) CASE_F_1(CASE, __VA_ARGS__) -#define CASE_F_3(CASE, X, ...) CASE(X) CASE_F_2(CASE, __VA_ARGS__) -#define CASE_F_4(CASE, X, ...) CASE(X) CASE_F_3(CASE, __VA_ARGS__) -#define CASE_F_5(CASE, X, ...) CASE(X) CASE_F_4(CASE, __VA_ARGS__) -#define CASE_F_6(CASE, X, ...) CASE(X) CASE_F_5(CASE, __VA_ARGS__) -#define CASE_F_7(CASE, X, ...) CASE(X) CASE_F_6(CASE, __VA_ARGS__) -#define CASE_F_8(CASE, X, ...) CASE(X) CASE_F_7(CASE, __VA_ARGS__) -#define CASE_F_9(CASE, X, ...) CASE(X) CASE_F_8(CASE, __VA_ARGS__) -#define CASE_F_10(CASE, X, ...) CASE(X) CASE_F_9(CASE, __VA_ARGS__) -#define CASE_F_11(CASE, X, ...) CASE(X) CASE_F_10(CASE, __VA_ARGS__) -#define CASE_F_12(CASE, X, ...) CASE(X) CASE_F_11(CASE, __VA_ARGS__) -#define CASE_F_13(CASE, X, ...) CASE(X) CASE_F_12(CASE, __VA_ARGS__) -#define CASE_F_14(CASE, X, ...) CASE(X) CASE_F_13(CASE, __VA_ARGS__) -#define CASE_F_15(CASE, X, ...) CASE(X) CASE_F_14(CASE, __VA_ARGS__) -#define CASE_F_16(CASE, X, ...) CASE(X) CASE_F_15(CASE, __VA_ARGS__) -#define CASE_F_17(CASE, X, ...) CASE(X) CASE_F_16(CASE, __VA_ARGS__) -#define CASE_F_18(CASE, X, ...) CASE(X) CASE_F_17(CASE, __VA_ARGS__) -#define CASE_F_19(CASE, X, ...) CASE(X) CASE_F_18(CASE, __VA_ARGS__) -#define CASE_F_20(CASE, X, ...) CASE(X) CASE_F_19(CASE, __VA_ARGS__) - -#define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,NAME,...) NAME -#define FOR_EACH_MAKE_CASE(...) \ - GET_CASE_F(__VA_ARGS__,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12,CASE_F_11, \ - CASE_F_10,CASE_F_9,CASE_F_8,CASE_F_7,CASE_F_6,CASE_F_5,CASE_F_4,CASE_F_3,CASE_F_2,CASE_F_1) \ - (CASE_F,__VA_ARGS__) - -#define IN_SET(x, ...) \ - ({ \ - bool _found = false; \ - /* If the build breaks in the line below, you need to extend the case macros */ \ - static _unused_ char _static_assert__macros_need_to_be_extended[20 - sizeof((int[]){__VA_ARGS__})/sizeof(int)]; \ - switch(x) { \ - FOR_EACH_MAKE_CASE(__VA_ARGS__) \ - _found = true; \ - break; \ - default: \ - break; \ - } \ - _found; \ - }) - -#define SWAP_TWO(x, y) do { \ - typeof(x) _t = (x); \ - (x) = (y); \ - (y) = (_t); \ - } while (false) - -/* Define C11 thread_local attribute even on older gcc compiler - * version */ -#ifndef thread_local -/* - * Don't break on glibc < 2.16 that doesn't define __STDC_NO_THREADS__ - * see http://gcc.gnu.org/bugzilla/show_bug.cgi?id=53769 - */ -#if __STDC_VERSION__ >= 201112L && !(defined(__STDC_NO_THREADS__) || (defined(__GNU_LIBRARY__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16)) -#define thread_local _Thread_local -#else -#define thread_local __thread -#endif -#endif - -/* Define C11 noreturn without and even on older gcc - * compiler versions */ -#ifndef noreturn -#if __STDC_VERSION__ >= 201112L -#define noreturn _Noreturn -#else -#define noreturn __attribute__((noreturn)) -#endif -#endif - -#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \ - static inline void func##p(type *p) { \ - if (*p) \ - func(*p); \ - } \ - struct __useless_struct_to_allow_trailing_semicolon__ - -#include "log.h" diff --git a/src/basic/memfd-util.c b/src/basic/memfd-util.c deleted file mode 100644 index 8c8cc78ebf..0000000000 --- a/src/basic/memfd-util.c +++ /dev/null @@ -1,174 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 -#include -#include -#ifdef HAVE_LINUX_MEMFD_H -#include -#endif -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "macro.h" -#include "memfd-util.h" -#include "missing.h" -#include "string-util.h" -#include "utf8.h" - -int memfd_new(const char *name) { - _cleanup_free_ char *g = NULL; - int fd; - - if (!name) { - char pr[17] = {}; - - /* If no name is specified we generate one. We include - * a hint indicating our library implementation, and - * add the thread name to it */ - - assert_se(prctl(PR_GET_NAME, (unsigned long) pr) >= 0); - - if (isempty(pr)) - name = "sd"; - else { - _cleanup_free_ char *e = NULL; - - e = utf8_escape_invalid(pr); - if (!e) - return -ENOMEM; - - g = strappend("sd-", e); - if (!g) - return -ENOMEM; - - name = g; - } - } - - fd = memfd_create(name, MFD_ALLOW_SEALING | MFD_CLOEXEC); - if (fd < 0) - return -errno; - - return fd; -} - -int memfd_map(int fd, uint64_t offset, size_t size, void **p) { - void *q; - int sealed; - - assert(fd >= 0); - assert(size > 0); - assert(p); - - sealed = memfd_get_sealed(fd); - if (sealed < 0) - return sealed; - - if (sealed) - q = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, offset); - else - q = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset); - - if (q == MAP_FAILED) - return -errno; - - *p = q; - return 0; -} - -int memfd_set_sealed(int fd) { - int r; - - assert(fd >= 0); - - r = fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL); - if (r < 0) - return -errno; - - return 0; -} - -int memfd_get_sealed(int fd) { - int r; - - assert(fd >= 0); - - r = fcntl(fd, F_GET_SEALS); - if (r < 0) - return -errno; - - return r == (F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL); -} - -int memfd_get_size(int fd, uint64_t *sz) { - struct stat stat; - int r; - - assert(fd >= 0); - assert(sz); - - r = fstat(fd, &stat); - if (r < 0) - return -errno; - - *sz = stat.st_size; - return 0; -} - -int memfd_set_size(int fd, uint64_t sz) { - int r; - - assert(fd >= 0); - - r = ftruncate(fd, sz); - if (r < 0) - return -errno; - - return 0; -} - -int memfd_new_and_map(const char *name, size_t sz, void **p) { - _cleanup_close_ int fd = -1; - int r; - - assert(sz > 0); - assert(p); - - fd = memfd_new(name); - if (fd < 0) - return fd; - - r = memfd_set_size(fd, sz); - if (r < 0) - return r; - - r = memfd_map(fd, 0, sz, p); - if (r < 0) - return r; - - r = fd; - fd = -1; - - return r; -} diff --git a/src/basic/memfd-util.h b/src/basic/memfd-util.h deleted file mode 100644 index 46d4989e4c..0000000000 --- a/src/basic/memfd-util.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 -#include -#include - -int memfd_new(const char *name); -int memfd_new_and_map(const char *name, size_t sz, void **p); - -int memfd_map(int fd, uint64_t offset, size_t size, void **p); - -int memfd_set_sealed(int fd); -int memfd_get_sealed(int fd); - -int memfd_get_size(int fd, uint64_t *sz); -int memfd_set_size(int fd, uint64_t sz); diff --git a/src/basic/mempool.c b/src/basic/mempool.c deleted file mode 100644 index f95e2beb0f..0000000000 --- a/src/basic/mempool.c +++ /dev/null @@ -1,104 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010-2014 Lennart Poettering - Copyright 2014 Michal Schmidt - - 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 - -#include "macro.h" -#include "mempool.h" -#include "util.h" - -struct pool { - struct pool *next; - unsigned n_tiles; - unsigned n_used; -}; - -void* mempool_alloc_tile(struct mempool *mp) { - unsigned i; - - /* When a tile is released we add it to the list and simply - * place the next pointer at its offset 0. */ - - assert(mp->tile_size >= sizeof(void*)); - assert(mp->at_least > 0); - - if (mp->freelist) { - void *r; - - r = mp->freelist; - mp->freelist = * (void**) mp->freelist; - return r; - } - - if (_unlikely_(!mp->first_pool) || - _unlikely_(mp->first_pool->n_used >= mp->first_pool->n_tiles)) { - unsigned n; - size_t size; - struct pool *p; - - n = mp->first_pool ? mp->first_pool->n_tiles : 0; - n = MAX(mp->at_least, n * 2); - size = PAGE_ALIGN(ALIGN(sizeof(struct pool)) + n*mp->tile_size); - n = (size - ALIGN(sizeof(struct pool))) / mp->tile_size; - - p = malloc(size); - if (!p) - return NULL; - - p->next = mp->first_pool; - p->n_tiles = n; - p->n_used = 0; - - mp->first_pool = p; - } - - i = mp->first_pool->n_used++; - - return ((uint8_t*) mp->first_pool) + ALIGN(sizeof(struct pool)) + i*mp->tile_size; -} - -void* mempool_alloc0_tile(struct mempool *mp) { - void *p; - - p = mempool_alloc_tile(mp); - if (p) - memzero(p, mp->tile_size); - return p; -} - -void mempool_free_tile(struct mempool *mp, void *p) { - * (void**) p = mp->freelist; - mp->freelist = p; -} - -#ifdef VALGRIND - -void mempool_drop(struct mempool *mp) { - struct pool *p = mp->first_pool; - while (p) { - struct pool *n; - n = p->next; - free(p); - p = n; - } -} - -#endif diff --git a/src/basic/mempool.h b/src/basic/mempool.h deleted file mode 100644 index 0618b8dd22..0000000000 --- a/src/basic/mempool.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011-2014 Lennart Poettering - Copyright 2014 Michal Schmidt - - 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 - -struct pool; - -struct mempool { - struct pool *first_pool; - void *freelist; - size_t tile_size; - unsigned at_least; -}; - -void* mempool_alloc_tile(struct mempool *mp); -void* mempool_alloc0_tile(struct mempool *mp); -void mempool_free_tile(struct mempool *mp, void *p); - -#define DEFINE_MEMPOOL(pool_name, tile_type, alloc_at_least) \ -static struct mempool pool_name = { \ - .tile_size = sizeof(tile_type), \ - .at_least = alloc_at_least, \ -} - - -#ifdef VALGRIND -void mempool_drop(struct mempool *mp); -#endif diff --git a/src/basic/missing.h b/src/basic/missing.h deleted file mode 100644 index 651e414395..0000000000 --- a/src/basic/missing.h +++ /dev/null @@ -1,1016 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -/* Missing glibc definitions to access certain kernel APIs */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_AUDIT -#include -#endif - -#ifdef ARCH_MIPS -#include -#endif - -#ifdef HAVE_LINUX_BTRFS_H -#include -#endif - -#include "macro.h" - -#ifndef RLIMIT_RTTIME -#define RLIMIT_RTTIME 15 -#endif - -/* If RLIMIT_RTTIME is not defined, then we cannot use RLIMIT_NLIMITS as is */ -#define _RLIMIT_MAX (RLIMIT_RTTIME+1 > RLIMIT_NLIMITS ? RLIMIT_RTTIME+1 : RLIMIT_NLIMITS) - -#ifndef F_LINUX_SPECIFIC_BASE -#define F_LINUX_SPECIFIC_BASE 1024 -#endif - -#ifndef F_SETPIPE_SZ -#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7) -#endif - -#ifndef F_GETPIPE_SZ -#define F_GETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 8) -#endif - -#ifndef F_ADD_SEALS -#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) -#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10) - -#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */ -#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */ -#define F_SEAL_GROW 0x0004 /* prevent file from growing */ -#define F_SEAL_WRITE 0x0008 /* prevent writes */ -#endif - -#ifndef F_OFD_GETLK -#define F_OFD_GETLK 36 -#define F_OFD_SETLK 37 -#define F_OFD_SETLKW 38 -#endif - -#ifndef MFD_ALLOW_SEALING -#define MFD_ALLOW_SEALING 0x0002U -#endif - -#ifndef MFD_CLOEXEC -#define MFD_CLOEXEC 0x0001U -#endif - -#ifndef IP_FREEBIND -#define IP_FREEBIND 15 -#endif - -#ifndef OOM_SCORE_ADJ_MIN -#define OOM_SCORE_ADJ_MIN (-1000) -#endif - -#ifndef OOM_SCORE_ADJ_MAX -#define OOM_SCORE_ADJ_MAX 1000 -#endif - -#ifndef AUDIT_SERVICE_START -#define AUDIT_SERVICE_START 1130 /* Service (daemon) start */ -#endif - -#ifndef AUDIT_SERVICE_STOP -#define AUDIT_SERVICE_STOP 1131 /* Service (daemon) stop */ -#endif - -#ifndef TIOCVHANGUP -#define TIOCVHANGUP 0x5437 -#endif - -#ifndef IP_TRANSPARENT -#define IP_TRANSPARENT 19 -#endif - -#ifndef SOL_NETLINK -#define SOL_NETLINK 270 -#endif - -#ifndef NETLINK_LIST_MEMBERSHIPS -#define NETLINK_LIST_MEMBERSHIPS 9 -#endif - -#ifndef SOL_SCTP -#define SOL_SCTP 132 -#endif - -#ifndef GRND_NONBLOCK -#define GRND_NONBLOCK 0x0001 -#endif - -#ifndef GRND_RANDOM -#define GRND_RANDOM 0x0002 -#endif - -#ifndef BTRFS_IOCTL_MAGIC -#define BTRFS_IOCTL_MAGIC 0x94 -#endif - -#ifndef BTRFS_PATH_NAME_MAX -#define BTRFS_PATH_NAME_MAX 4087 -#endif - -#ifndef BTRFS_DEVICE_PATH_NAME_MAX -#define BTRFS_DEVICE_PATH_NAME_MAX 1024 -#endif - -#ifndef BTRFS_FSID_SIZE -#define BTRFS_FSID_SIZE 16 -#endif - -#ifndef BTRFS_UUID_SIZE -#define BTRFS_UUID_SIZE 16 -#endif - -#ifndef BTRFS_SUBVOL_RDONLY -#define BTRFS_SUBVOL_RDONLY (1ULL << 1) -#endif - -#ifndef BTRFS_SUBVOL_NAME_MAX -#define BTRFS_SUBVOL_NAME_MAX 4039 -#endif - -#ifndef BTRFS_INO_LOOKUP_PATH_MAX -#define BTRFS_INO_LOOKUP_PATH_MAX 4080 -#endif - -#ifndef BTRFS_SEARCH_ARGS_BUFSIZE -#define BTRFS_SEARCH_ARGS_BUFSIZE (4096 - sizeof(struct btrfs_ioctl_search_key)) -#endif - -#ifndef BTRFS_QGROUP_LEVEL_SHIFT -#define BTRFS_QGROUP_LEVEL_SHIFT 48 -#endif - -#ifndef HAVE_LINUX_BTRFS_H -struct btrfs_ioctl_vol_args { - int64_t fd; - char name[BTRFS_PATH_NAME_MAX + 1]; -}; - -struct btrfs_qgroup_limit { - __u64 flags; - __u64 max_rfer; - __u64 max_excl; - __u64 rsv_rfer; - __u64 rsv_excl; -}; - -struct btrfs_qgroup_inherit { - __u64 flags; - __u64 num_qgroups; - __u64 num_ref_copies; - __u64 num_excl_copies; - struct btrfs_qgroup_limit lim; - __u64 qgroups[0]; -}; - -struct btrfs_ioctl_qgroup_limit_args { - __u64 qgroupid; - struct btrfs_qgroup_limit lim; -}; - -struct btrfs_ioctl_vol_args_v2 { - __s64 fd; - __u64 transid; - __u64 flags; - union { - struct { - __u64 size; - struct btrfs_qgroup_inherit *qgroup_inherit; - }; - __u64 unused[4]; - }; - char name[BTRFS_SUBVOL_NAME_MAX + 1]; -}; - -struct btrfs_ioctl_dev_info_args { - uint64_t devid; /* in/out */ - uint8_t uuid[BTRFS_UUID_SIZE]; /* in/out */ - uint64_t bytes_used; /* out */ - uint64_t total_bytes; /* out */ - uint64_t unused[379]; /* pad to 4k */ - char path[BTRFS_DEVICE_PATH_NAME_MAX]; /* out */ -}; - -struct btrfs_ioctl_fs_info_args { - uint64_t max_id; /* out */ - uint64_t num_devices; /* out */ - uint8_t fsid[BTRFS_FSID_SIZE]; /* out */ - uint64_t reserved[124]; /* pad to 1k */ -}; - -struct btrfs_ioctl_ino_lookup_args { - __u64 treeid; - __u64 objectid; - char name[BTRFS_INO_LOOKUP_PATH_MAX]; -}; - -struct btrfs_ioctl_search_key { - /* which root are we searching. 0 is the tree of tree roots */ - __u64 tree_id; - - /* keys returned will be >= min and <= max */ - __u64 min_objectid; - __u64 max_objectid; - - /* keys returned will be >= min and <= max */ - __u64 min_offset; - __u64 max_offset; - - /* max and min transids to search for */ - __u64 min_transid; - __u64 max_transid; - - /* keys returned will be >= min and <= max */ - __u32 min_type; - __u32 max_type; - - /* - * how many items did userland ask for, and how many are we - * returning - */ - __u32 nr_items; - - /* align to 64 bits */ - __u32 unused; - - /* some extra for later */ - __u64 unused1; - __u64 unused2; - __u64 unused3; - __u64 unused4; -}; - -struct btrfs_ioctl_search_header { - __u64 transid; - __u64 objectid; - __u64 offset; - __u32 type; - __u32 len; -}; - - -struct btrfs_ioctl_search_args { - struct btrfs_ioctl_search_key key; - char buf[BTRFS_SEARCH_ARGS_BUFSIZE]; -}; - -struct btrfs_ioctl_clone_range_args { - __s64 src_fd; - __u64 src_offset, src_length; - __u64 dest_offset; -}; - -#define BTRFS_QUOTA_CTL_ENABLE 1 -#define BTRFS_QUOTA_CTL_DISABLE 2 -#define BTRFS_QUOTA_CTL_RESCAN__NOTUSED 3 -struct btrfs_ioctl_quota_ctl_args { - __u64 cmd; - __u64 status; -}; -#endif - -#ifndef BTRFS_IOC_DEFRAG -#define BTRFS_IOC_DEFRAG _IOW(BTRFS_IOCTL_MAGIC, 2, \ - struct btrfs_ioctl_vol_args) -#endif - -#ifndef BTRFS_IOC_RESIZE -#define BTRFS_IOC_RESIZE _IOW(BTRFS_IOCTL_MAGIC, 3, \ - struct btrfs_ioctl_vol_args) -#endif - -#ifndef BTRFS_IOC_CLONE -#define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int) -#endif - -#ifndef BTRFS_IOC_CLONE_RANGE -#define BTRFS_IOC_CLONE_RANGE _IOW(BTRFS_IOCTL_MAGIC, 13, \ - struct btrfs_ioctl_clone_range_args) -#endif - -#ifndef BTRFS_IOC_SUBVOL_CREATE -#define BTRFS_IOC_SUBVOL_CREATE _IOW(BTRFS_IOCTL_MAGIC, 14, \ - struct btrfs_ioctl_vol_args) -#endif - -#ifndef BTRFS_IOC_SNAP_DESTROY -#define BTRFS_IOC_SNAP_DESTROY _IOW(BTRFS_IOCTL_MAGIC, 15, \ - struct btrfs_ioctl_vol_args) -#endif - -#ifndef BTRFS_IOC_TREE_SEARCH -#define BTRFS_IOC_TREE_SEARCH _IOWR(BTRFS_IOCTL_MAGIC, 17, \ - struct btrfs_ioctl_search_args) -#endif - -#ifndef BTRFS_IOC_INO_LOOKUP -#define BTRFS_IOC_INO_LOOKUP _IOWR(BTRFS_IOCTL_MAGIC, 18, \ - struct btrfs_ioctl_ino_lookup_args) -#endif - -#ifndef BTRFS_IOC_SNAP_CREATE_V2 -#define BTRFS_IOC_SNAP_CREATE_V2 _IOW(BTRFS_IOCTL_MAGIC, 23, \ - struct btrfs_ioctl_vol_args_v2) -#endif - -#ifndef BTRFS_IOC_SUBVOL_GETFLAGS -#define BTRFS_IOC_SUBVOL_GETFLAGS _IOR(BTRFS_IOCTL_MAGIC, 25, __u64) -#endif - -#ifndef BTRFS_IOC_SUBVOL_SETFLAGS -#define BTRFS_IOC_SUBVOL_SETFLAGS _IOW(BTRFS_IOCTL_MAGIC, 26, __u64) -#endif - -#ifndef BTRFS_IOC_DEV_INFO -#define BTRFS_IOC_DEV_INFO _IOWR(BTRFS_IOCTL_MAGIC, 30, \ - struct btrfs_ioctl_dev_info_args) -#endif - -#ifndef BTRFS_IOC_FS_INFO -#define BTRFS_IOC_FS_INFO _IOR(BTRFS_IOCTL_MAGIC, 31, \ - struct btrfs_ioctl_fs_info_args) -#endif - -#ifndef BTRFS_IOC_DEVICES_READY -#define BTRFS_IOC_DEVICES_READY _IOR(BTRFS_IOCTL_MAGIC, 39, \ - struct btrfs_ioctl_vol_args) -#endif - -#ifndef BTRFS_IOC_QUOTA_CTL -#define BTRFS_IOC_QUOTA_CTL _IOWR(BTRFS_IOCTL_MAGIC, 40, \ - struct btrfs_ioctl_quota_ctl_args) -#endif - -#ifndef BTRFS_IOC_QGROUP_LIMIT -#define BTRFS_IOC_QGROUP_LIMIT _IOR(BTRFS_IOCTL_MAGIC, 43, \ - struct btrfs_ioctl_qgroup_limit_args) -#endif - -#ifndef BTRFS_IOC_QUOTA_RESCAN_WAIT -#define BTRFS_IOC_QUOTA_RESCAN_WAIT _IO(BTRFS_IOCTL_MAGIC, 46) -#endif - -#ifndef BTRFS_FIRST_FREE_OBJECTID -#define BTRFS_FIRST_FREE_OBJECTID 256 -#endif - -#ifndef BTRFS_LAST_FREE_OBJECTID -#define BTRFS_LAST_FREE_OBJECTID -256ULL -#endif - -#ifndef BTRFS_ROOT_TREE_OBJECTID -#define BTRFS_ROOT_TREE_OBJECTID 1 -#endif - -#ifndef BTRFS_QUOTA_TREE_OBJECTID -#define BTRFS_QUOTA_TREE_OBJECTID 8ULL -#endif - -#ifndef BTRFS_ROOT_ITEM_KEY -#define BTRFS_ROOT_ITEM_KEY 132 -#endif - -#ifndef BTRFS_QGROUP_STATUS_KEY -#define BTRFS_QGROUP_STATUS_KEY 240 -#endif - -#ifndef BTRFS_QGROUP_INFO_KEY -#define BTRFS_QGROUP_INFO_KEY 242 -#endif - -#ifndef BTRFS_QGROUP_LIMIT_KEY -#define BTRFS_QGROUP_LIMIT_KEY 244 -#endif - -#ifndef BTRFS_QGROUP_RELATION_KEY -#define BTRFS_QGROUP_RELATION_KEY 246 -#endif - -#ifndef BTRFS_ROOT_BACKREF_KEY -#define BTRFS_ROOT_BACKREF_KEY 144 -#endif - -#ifndef BTRFS_SUPER_MAGIC -#define BTRFS_SUPER_MAGIC 0x9123683E -#endif - -#ifndef CGROUP_SUPER_MAGIC -#define CGROUP_SUPER_MAGIC 0x27e0eb -#endif - -#ifndef CGROUP2_SUPER_MAGIC -#define CGROUP2_SUPER_MAGIC 0x63677270 -#endif - -#ifndef TMPFS_MAGIC -#define TMPFS_MAGIC 0x01021994 -#endif - -#ifndef MQUEUE_MAGIC -#define MQUEUE_MAGIC 0x19800202 -#endif - -#ifndef MS_MOVE -#define MS_MOVE 8192 -#endif - -#ifndef MS_PRIVATE -#define MS_PRIVATE (1 << 18) -#endif - -#ifndef SCM_SECURITY -#define SCM_SECURITY 0x03 -#endif - -#ifndef MS_STRICTATIME -#define MS_STRICTATIME (1<<24) -#endif - -#ifndef MS_REC -#define MS_REC 16384 -#endif - -#ifndef MS_SHARED -#define MS_SHARED (1<<20) -#endif - -#ifndef PR_SET_NO_NEW_PRIVS -#define PR_SET_NO_NEW_PRIVS 38 -#endif - -#ifndef PR_SET_CHILD_SUBREAPER -#define PR_SET_CHILD_SUBREAPER 36 -#endif - -#ifndef MAX_HANDLE_SZ -#define MAX_HANDLE_SZ 128 -#endif - -#ifndef HAVE_SECURE_GETENV -# ifdef HAVE___SECURE_GETENV -# define secure_getenv __secure_getenv -# else -# error "neither secure_getenv nor __secure_getenv are available" -# endif -#endif - -#ifndef CIFS_MAGIC_NUMBER -# define CIFS_MAGIC_NUMBER 0xFF534D42 -#endif - -#ifndef TFD_TIMER_CANCEL_ON_SET -# define TFD_TIMER_CANCEL_ON_SET (1 << 1) -#endif - -#ifndef SO_REUSEPORT -# define SO_REUSEPORT 15 -#endif - -#ifndef EVIOCREVOKE -# define EVIOCREVOKE _IOW('E', 0x91, int) -#endif - -#ifndef DRM_IOCTL_SET_MASTER -# define DRM_IOCTL_SET_MASTER _IO('d', 0x1e) -#endif - -#ifndef DRM_IOCTL_DROP_MASTER -# define DRM_IOCTL_DROP_MASTER _IO('d', 0x1f) -#endif - -#if defined(__i386__) || defined(__x86_64__) - -/* The precise definition of __O_TMPFILE is arch specific, so let's - * just define this on x86 where we know the value. */ - -#ifndef __O_TMPFILE -#define __O_TMPFILE 020000000 -#endif - -/* a horrid kludge trying to make sure that this will fail on old kernels */ -#ifndef O_TMPFILE -#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY) -#endif - -#endif - -#if !HAVE_DECL_LO_FLAGS_PARTSCAN -#define LO_FLAGS_PARTSCAN 8 -#endif - -#ifndef LOOP_CTL_REMOVE -#define LOOP_CTL_REMOVE 0x4C81 -#endif - -#ifndef LOOP_CTL_GET_FREE -#define LOOP_CTL_GET_FREE 0x4C82 -#endif - -#if !HAVE_DECL_IFLA_INET6_ADDR_GEN_MODE -#define IFLA_INET6_UNSPEC 0 -#define IFLA_INET6_FLAGS 1 -#define IFLA_INET6_CONF 2 -#define IFLA_INET6_STATS 3 -#define IFLA_INET6_MCAST 4 -#define IFLA_INET6_CACHEINFO 5 -#define IFLA_INET6_ICMP6STATS 6 -#define IFLA_INET6_TOKEN 7 -#define IFLA_INET6_ADDR_GEN_MODE 8 -#define __IFLA_INET6_MAX 9 - -#define IFLA_INET6_MAX (__IFLA_INET6_MAX - 1) - -#define IN6_ADDR_GEN_MODE_EUI64 0 -#define IN6_ADDR_GEN_MODE_NONE 1 -#endif - -#if !HAVE_DECL_IFLA_MACVLAN_FLAGS -#define IFLA_MACVLAN_UNSPEC 0 -#define IFLA_MACVLAN_MODE 1 -#define IFLA_MACVLAN_FLAGS 2 -#define __IFLA_MACVLAN_MAX 3 - -#define IFLA_MACVLAN_MAX (__IFLA_MACVLAN_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_IPVLAN_MODE -#define IFLA_IPVLAN_UNSPEC 0 -#define IFLA_IPVLAN_MODE 1 -#define __IFLA_IPVLAN_MAX 2 - -#define IFLA_IPVLAN_MAX (__IFLA_IPVLAN_MAX - 1) - -#define IPVLAN_MODE_L2 0 -#define IPVLAN_MODE_L3 1 -#define IPVLAN_MAX 2 -#endif - -#if !HAVE_DECL_IFLA_VTI_REMOTE -#define IFLA_VTI_UNSPEC 0 -#define IFLA_VTI_LINK 1 -#define IFLA_VTI_IKEY 2 -#define IFLA_VTI_OKEY 3 -#define IFLA_VTI_LOCAL 4 -#define IFLA_VTI_REMOTE 5 -#define __IFLA_VTI_MAX 6 - -#define IFLA_VTI_MAX (__IFLA_VTI_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_PHYS_PORT_ID -#define IFLA_EXT_MASK 29 -#undef IFLA_PROMISCUITY -#define IFLA_PROMISCUITY 30 -#define IFLA_NUM_TX_QUEUES 31 -#define IFLA_NUM_RX_QUEUES 32 -#define IFLA_CARRIER 33 -#define IFLA_PHYS_PORT_ID 34 -#define __IFLA_MAX 35 - -#define IFLA_MAX (__IFLA_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_BOND_AD_INFO -#define IFLA_BOND_UNSPEC 0 -#define IFLA_BOND_MODE 1 -#define IFLA_BOND_ACTIVE_SLAVE 2 -#define IFLA_BOND_MIIMON 3 -#define IFLA_BOND_UPDELAY 4 -#define IFLA_BOND_DOWNDELAY 5 -#define IFLA_BOND_USE_CARRIER 6 -#define IFLA_BOND_ARP_INTERVAL 7 -#define IFLA_BOND_ARP_IP_TARGET 8 -#define IFLA_BOND_ARP_VALIDATE 9 -#define IFLA_BOND_ARP_ALL_TARGETS 10 -#define IFLA_BOND_PRIMARY 11 -#define IFLA_BOND_PRIMARY_RESELECT 12 -#define IFLA_BOND_FAIL_OVER_MAC 13 -#define IFLA_BOND_XMIT_HASH_POLICY 14 -#define IFLA_BOND_RESEND_IGMP 15 -#define IFLA_BOND_NUM_PEER_NOTIF 16 -#define IFLA_BOND_ALL_SLAVES_ACTIVE 17 -#define IFLA_BOND_MIN_LINKS 18 -#define IFLA_BOND_LP_INTERVAL 19 -#define IFLA_BOND_PACKETS_PER_SLAVE 20 -#define IFLA_BOND_AD_LACP_RATE 21 -#define IFLA_BOND_AD_SELECT 22 -#define IFLA_BOND_AD_INFO 23 -#define __IFLA_BOND_MAX 24 - -#define IFLA_BOND_MAX (__IFLA_BOND_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_VLAN_PROTOCOL -#define IFLA_VLAN_UNSPEC 0 -#define IFLA_VLAN_ID 1 -#define IFLA_VLAN_FLAGS 2 -#define IFLA_VLAN_EGRESS_QOS 3 -#define IFLA_VLAN_INGRESS_QOS 4 -#define IFLA_VLAN_PROTOCOL 5 -#define __IFLA_VLAN_MAX 6 - -#define IFLA_VLAN_MAX (__IFLA_VLAN_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_VXLAN_REMCSUM_NOPARTIAL -#define IFLA_VXLAN_UNSPEC 0 -#define IFLA_VXLAN_ID 1 -#define IFLA_VXLAN_GROUP 2 -#define IFLA_VXLAN_LINK 3 -#define IFLA_VXLAN_LOCAL 4 -#define IFLA_VXLAN_TTL 5 -#define IFLA_VXLAN_TOS 6 -#define IFLA_VXLAN_LEARNING 7 -#define IFLA_VXLAN_AGEING 8 -#define IFLA_VXLAN_LIMIT 9 -#define IFLA_VXLAN_PORT_RANGE 10 -#define IFLA_VXLAN_PROXY 11 -#define IFLA_VXLAN_RSC 12 -#define IFLA_VXLAN_L2MISS 13 -#define IFLA_VXLAN_L3MISS 14 -#define IFLA_VXLAN_PORT 15 -#define IFLA_VXLAN_GROUP6 16 -#define IFLA_VXLAN_LOCAL6 17 -#define IFLA_VXLAN_UDP_CSUM 18 -#define IFLA_VXLAN_UDP_ZERO_CSUM6_TX 19 -#define IFLA_VXLAN_UDP_ZERO_CSUM6_RX 20 -#define IFLA_VXLAN_REMCSUM_TX 21 -#define IFLA_VXLAN_REMCSUM_RX 22 -#define IFLA_VXLAN_GBP 23 -#define IFLA_VXLAN_REMCSUM_NOPARTIAL 24 -#define __IFLA_VXLAN_MAX 25 - -#define IFLA_VXLAN_MAX (__IFLA_VXLAN_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_IPTUN_ENCAP_DPORT -#define IFLA_IPTUN_UNSPEC 0 -#define IFLA_IPTUN_LINK 1 -#define IFLA_IPTUN_LOCAL 2 -#define IFLA_IPTUN_REMOTE 3 -#define IFLA_IPTUN_TTL 4 -#define IFLA_IPTUN_TOS 5 -#define IFLA_IPTUN_ENCAP_LIMIT 6 -#define IFLA_IPTUN_FLOWINFO 7 -#define IFLA_IPTUN_FLAGS 8 -#define IFLA_IPTUN_PROTO 9 -#define IFLA_IPTUN_PMTUDISC 10 -#define IFLA_IPTUN_6RD_PREFIX 11 -#define IFLA_IPTUN_6RD_RELAY_PREFIX 12 -#define IFLA_IPTUN_6RD_PREFIXLEN 13 -#define IFLA_IPTUN_6RD_RELAY_PREFIXLEN 14 -#define IFLA_IPTUN_ENCAP_TYPE 15 -#define IFLA_IPTUN_ENCAP_FLAGS 16 -#define IFLA_IPTUN_ENCAP_SPORT 17 -#define IFLA_IPTUN_ENCAP_DPORT 18 - -#define __IFLA_IPTUN_MAX 19 - -#define IFLA_IPTUN_MAX (__IFLA_IPTUN_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_GRE_ENCAP_DPORT -#define IFLA_GRE_UNSPEC 0 -#define IFLA_GRE_LINK 1 -#define IFLA_GRE_IFLAGS 2 -#define IFLA_GRE_OFLAGS 3 -#define IFLA_GRE_IKEY 4 -#define IFLA_GRE_OKEY 5 -#define IFLA_GRE_LOCAL 6 -#define IFLA_GRE_REMOTE 7 -#define IFLA_GRE_TTL 8 -#define IFLA_GRE_TOS 9 -#define IFLA_GRE_PMTUDISC 10 -#define IFLA_GRE_ENCAP_LIMIT 11 -#define IFLA_GRE_FLOWINFO 12 -#define IFLA_GRE_FLAGS 13 -#define IFLA_GRE_ENCAP_TYPE 14 -#define IFLA_GRE_ENCAP_FLAGS 15 -#define IFLA_GRE_ENCAP_SPORT 16 -#define IFLA_GRE_ENCAP_DPORT 17 - -#define __IFLA_GRE_MAX 18 - -#define IFLA_GRE_MAX (__IFLA_GRE_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_BRIDGE_VLAN_INFO -#define IFLA_BRIDGE_FLAGS 0 -#define IFLA_BRIDGE_MODE 1 -#define IFLA_BRIDGE_VLAN_INFO 2 -#define __IFLA_BRIDGE_MAX 3 - -#define IFLA_BRIDGE_MAX (__IFLA_BRIDGE_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_BR_VLAN_DEFAULT_PVID -#define IFLA_BR_UNSPEC 0 -#define IFLA_BR_FORWARD_DELAY 1 -#define IFLA_BR_HELLO_TIME 2 -#define IFLA_BR_MAX_AGE 3 -#define IFLA_BR_AGEING_TIME 4 -#define IFLA_BR_STP_STATE 5 -#define IFLA_BR_PRIORITY 6 -#define IFLA_BR_VLAN_FILTERING 7 -#define IFLA_BR_VLAN_PROTOCOL 8 -#define IFLA_BR_GROUP_FWD_MASK 9 -#define IFLA_BR_ROOT_ID 10 -#define IFLA_BR_BRIDGE_ID 11 -#define IFLA_BR_ROOT_PORT 12 -#define IFLA_BR_ROOT_PATH_COST 13 -#define IFLA_BR_TOPOLOGY_CHANGE 14 -#define IFLA_BR_TOPOLOGY_CHANGE_DETECTED 15 -#define IFLA_BR_HELLO_TIMER 16 -#define IFLA_BR_TCN_TIMER 17 -#define IFLA_BR_TOPOLOGY_CHANGE_TIMER 18 -#define IFLA_BR_GC_TIMER 19 -#define IFLA_BR_GROUP_ADDR 20 -#define IFLA_BR_FDB_FLUSH 21 -#define IFLA_BR_MCAST_ROUTER 22 -#define IFLA_BR_MCAST_SNOOPING 23 -#define IFLA_BR_MCAST_QUERY_USE_IFADDR 24 -#define IFLA_BR_MCAST_QUERIER 25 -#define IFLA_BR_MCAST_HASH_ELASTICITY 26 -#define IFLA_BR_MCAST_HASH_MAX 27 -#define IFLA_BR_MCAST_LAST_MEMBER_CNT 28 -#define IFLA_BR_MCAST_STARTUP_QUERY_CNT 29 -#define IFLA_BR_MCAST_LAST_MEMBER_INTVL 30 -#define IFLA_BR_MCAST_MEMBERSHIP_INTVL 31 -#define IFLA_BR_MCAST_QUERIER_INTVL 32 -#define IFLA_BR_MCAST_QUERY_INTVL 33 -#define IFLA_BR_MCAST_QUERY_RESPONSE_INTVL 34 -#define IFLA_BR_MCAST_STARTUP_QUERY_INTVL 35 -#define IFLA_BR_NF_CALL_IPTABLES 36 -#define IFLA_BR_NF_CALL_IP6TABLES 37 -#define IFLA_BR_NF_CALL_ARPTABLES 38 -#define IFLA_BR_VLAN_DEFAULT_PVID 39 -#define __IFLA_BR_MAX 40 - -#define IFLA_BR_MAX (__IFLA_BR_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_BRPORT_LEARNING_SYNC -#define IFLA_BRPORT_UNSPEC 0 -#define IFLA_BRPORT_STATE 1 -#define IFLA_BRPORT_PRIORITY 2 -#define IFLA_BRPORT_COST 3 -#define IFLA_BRPORT_MODE 4 -#define IFLA_BRPORT_GUARD 5 -#define IFLA_BRPORT_PROTECT 6 -#define IFLA_BRPORT_FAST_LEAVE 7 -#define IFLA_BRPORT_LEARNING 8 -#define IFLA_BRPORT_UNICAST_FLOOD 9 -#define IFLA_BRPORT_LEARNING_SYNC 11 -#define __IFLA_BRPORT_MAX 12 - -#define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_BRPORT_PROXYARP -#define IFLA_BRPORT_PROXYARP 10 -#endif - -#if !HAVE_DECL_NDA_IFINDEX -#define NDA_UNSPEC 0 -#define NDA_DST 1 -#define NDA_LLADDR 2 -#define NDA_CACHEINFO 3 -#define NDA_PROBES 4 -#define NDA_VLAN 5 -#define NDA_PORT 6 -#define NDA_VNI 7 -#define NDA_IFINDEX 8 -#define __NDA_MAX 9 - -#define NDA_MAX (__NDA_MAX - 1) -#endif - -#ifndef RTA_PREF -#define RTA_PREF 20 -#endif - -#ifndef IPV6_UNICAST_IF -#define IPV6_UNICAST_IF 76 -#endif - -#ifndef IPV6_MIN_MTU -#define IPV6_MIN_MTU 1280 -#endif - -#ifndef IFF_MULTI_QUEUE -#define IFF_MULTI_QUEUE 0x100 -#endif - -#ifndef IFF_LOWER_UP -#define IFF_LOWER_UP 0x10000 -#endif - -#ifndef IFF_DORMANT -#define IFF_DORMANT 0x20000 -#endif - -#ifndef BOND_XMIT_POLICY_ENCAP23 -#define BOND_XMIT_POLICY_ENCAP23 3 -#endif - -#ifndef BOND_XMIT_POLICY_ENCAP34 -#define BOND_XMIT_POLICY_ENCAP34 4 -#endif - -#ifndef NET_ADDR_RANDOM -# define NET_ADDR_RANDOM 1 -#endif - -#ifndef NET_NAME_UNKNOWN -# define NET_NAME_UNKNOWN 0 -#endif - -#ifndef NET_NAME_ENUM -# define NET_NAME_ENUM 1 -#endif - -#ifndef NET_NAME_PREDICTABLE -# define NET_NAME_PREDICTABLE 2 -#endif - -#ifndef NET_NAME_USER -# define NET_NAME_USER 3 -#endif - -#ifndef NET_NAME_RENAMED -# define NET_NAME_RENAMED 4 -#endif - -#ifndef BPF_XOR -# define BPF_XOR 0xa0 -#endif - -/* Note that LOOPBACK_IFINDEX is currently not exported by the - * kernel/glibc, but hardcoded internally by the kernel. However, as - * it is exported to userspace indirectly via rtnetlink and the - * ioctls, and made use of widely we define it here too, in a way that - * is compatible with the kernel's internal definition. */ -#ifndef LOOPBACK_IFINDEX -#define LOOPBACK_IFINDEX 1 -#endif - -#if !HAVE_DECL_IFA_FLAGS -#define IFA_FLAGS 8 -#endif - -#ifndef IFA_F_MANAGETEMPADDR -#define IFA_F_MANAGETEMPADDR 0x100 -#endif - -#ifndef IFA_F_NOPREFIXROUTE -#define IFA_F_NOPREFIXROUTE 0x200 -#endif - -#ifndef MAX_AUDIT_MESSAGE_LENGTH -#define MAX_AUDIT_MESSAGE_LENGTH 8970 -#endif - -#ifndef AUDIT_NLGRP_MAX -#define AUDIT_NLGRP_READLOG 1 -#endif - -#ifndef CAP_MAC_OVERRIDE -#define CAP_MAC_OVERRIDE 32 -#endif - -#ifndef CAP_MAC_ADMIN -#define CAP_MAC_ADMIN 33 -#endif - -#ifndef CAP_SYSLOG -#define CAP_SYSLOG 34 -#endif - -#ifndef CAP_WAKE_ALARM -#define CAP_WAKE_ALARM 35 -#endif - -#ifndef CAP_BLOCK_SUSPEND -#define CAP_BLOCK_SUSPEND 36 -#endif - -#ifndef CAP_AUDIT_READ -#define CAP_AUDIT_READ 37 -#endif - -#ifndef RENAME_NOREPLACE -#define RENAME_NOREPLACE (1 << 0) -#endif - -#ifndef KCMP_FILE -#define KCMP_FILE 0 -#endif - -#ifndef INPUT_PROP_POINTING_STICK -#define INPUT_PROP_POINTING_STICK 0x05 -#endif - -#ifndef INPUT_PROP_ACCELEROMETER -#define INPUT_PROP_ACCELEROMETER 0x06 -#endif - -#ifndef HAVE_KEY_SERIAL_T -typedef int32_t key_serial_t; -#endif - -#ifndef KEYCTL_READ -#define KEYCTL_READ 11 -#endif - -#ifndef KEYCTL_SET_TIMEOUT -#define KEYCTL_SET_TIMEOUT 15 -#endif - -#ifndef KEY_SPEC_USER_KEYRING -#define KEY_SPEC_USER_KEYRING -4 -#endif - -#ifndef PR_CAP_AMBIENT -#define PR_CAP_AMBIENT 47 -#endif - -#ifndef PR_CAP_AMBIENT_IS_SET -#define PR_CAP_AMBIENT_IS_SET 1 -#endif - -#ifndef PR_CAP_AMBIENT_RAISE -#define PR_CAP_AMBIENT_RAISE 2 -#endif - -#ifndef PR_CAP_AMBIENT_CLEAR_ALL -#define PR_CAP_AMBIENT_CLEAR_ALL 4 -#endif - -/* The following two defines are actually available in the kernel headers for longer, but we define them here anyway, - * since that makes it easier to use them in conjunction with the glibc net/if.h header which conflicts with - * linux/if.h. */ -#ifndef IF_OPER_UNKNOWN -#define IF_OPER_UNKNOWN 0 -#endif - -#ifndef IF_OPER_UP -#define IF_OPER_UP 6 - -#ifndef HAVE_CHAR32_T -#define char32_t uint32_t -#endif - -#ifndef HAVE_CHAR16_T -#define char16_t uint16_t -#endif - -#ifndef ETHERTYPE_LLDP -#define ETHERTYPE_LLDP 0x88cc -#endif - -#endif - -#include "missing_syscall.h" diff --git a/src/basic/missing_syscall.h b/src/basic/missing_syscall.h deleted file mode 100644 index d502d3b9ca..0000000000 --- a/src/basic/missing_syscall.h +++ /dev/null @@ -1,310 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2016 Zbigniew Jędrzejewski-Szmek - - 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 . -***/ - -/* Missing glibc definitions to access certain kernel APIs */ - -#if !HAVE_DECL_PIVOT_ROOT -static inline int pivot_root(const char *new_root, const char *put_old) { - return syscall(SYS_pivot_root, new_root, put_old); -} -#endif - -/* ======================================================================= */ - -#if !HAVE_DECL_MEMFD_CREATE -# ifndef __NR_memfd_create -# if defined __x86_64__ -# define __NR_memfd_create 319 -# elif defined __arm__ -# define __NR_memfd_create 385 -# elif defined __aarch64__ -# define __NR_memfd_create 279 -# elif defined __s390__ -# define __NR_memfd_create 350 -# elif defined _MIPS_SIM -# if _MIPS_SIM == _MIPS_SIM_ABI32 -# define __NR_memfd_create 4354 -# endif -# if _MIPS_SIM == _MIPS_SIM_NABI32 -# define __NR_memfd_create 6318 -# endif -# if _MIPS_SIM == _MIPS_SIM_ABI64 -# define __NR_memfd_create 5314 -# endif -# elif defined __i386__ -# define __NR_memfd_create 356 -# else -# warning "__NR_memfd_create unknown for your architecture" -# endif -# endif - -static inline int memfd_create(const char *name, unsigned int flags) { -# ifdef __NR_memfd_create - return syscall(__NR_memfd_create, name, flags); -# else - errno = ENOSYS; - return -1; -# endif -} -#endif - -/* ======================================================================= */ - -#if !HAVE_DECL_GETRANDOM -# ifndef __NR_getrandom -# if defined __x86_64__ -# define __NR_getrandom 318 -# elif defined(__i386__) -# define __NR_getrandom 355 -# elif defined(__arm__) -# define __NR_getrandom 384 -# elif defined(__aarch64__) -# define __NR_getrandom 278 -# elif defined(__ia64__) -# define __NR_getrandom 1339 -# elif defined(__m68k__) -# define __NR_getrandom 352 -# elif defined(__s390x__) -# define __NR_getrandom 349 -# elif defined(__powerpc__) -# define __NR_getrandom 359 -# elif defined _MIPS_SIM -# if _MIPS_SIM == _MIPS_SIM_ABI32 -# define __NR_getrandom 4353 -# endif -# if _MIPS_SIM == _MIPS_SIM_NABI32 -# define __NR_getrandom 6317 -# endif -# if _MIPS_SIM == _MIPS_SIM_ABI64 -# define __NR_getrandom 5313 -# endif -# else -# warning "__NR_getrandom unknown for your architecture" -# endif -# endif - -static inline int getrandom(void *buffer, size_t count, unsigned flags) { -# ifdef __NR_getrandom - return syscall(__NR_getrandom, buffer, count, flags); -# else - errno = ENOSYS; - return -1; -# endif -} -#endif - -/* ======================================================================= */ - -#if !HAVE_DECL_GETTID -static inline pid_t gettid(void) { - return (pid_t) syscall(SYS_gettid); -} -#endif - -/* ======================================================================= */ - -#if !HAVE_DECL_NAME_TO_HANDLE_AT -# ifndef __NR_name_to_handle_at -# if defined(__x86_64__) -# define __NR_name_to_handle_at 303 -# elif defined(__i386__) -# define __NR_name_to_handle_at 341 -# elif defined(__arm__) -# define __NR_name_to_handle_at 370 -# elif defined(__powerpc__) -# define __NR_name_to_handle_at 345 -# else -# error "__NR_name_to_handle_at is not defined" -# endif -# endif - -struct file_handle { - unsigned int handle_bytes; - int handle_type; - unsigned char f_handle[0]; -}; - -static inline int name_to_handle_at(int fd, const char *name, struct file_handle *handle, int *mnt_id, int flags) { -# ifdef __NR_name_to_handle_at - return syscall(__NR_name_to_handle_at, fd, name, handle, mnt_id, flags); -# else - errno = ENOSYS; - return -1; -# endif -} -#endif - -/* ======================================================================= */ - -#if !HAVE_DECL_SETNS -# ifndef __NR_setns -# if defined(__x86_64__) -# define __NR_setns 308 -# elif defined(__i386__) -# define __NR_setns 346 -# else -# error "__NR_setns is not defined" -# endif -# endif - -static inline int setns(int fd, int nstype) { -# ifdef __NR_setns - return syscall(__NR_setns, fd, nstype); -# else - errno = ENOSYS; - return -1; -# endif -} -#endif - -/* ======================================================================= */ - -static inline int raw_clone(unsigned long flags, void *child_stack) { -#if defined(__s390__) || defined(__CRIS__) - /* On s390 and cris the order of the first and second arguments - * of the raw clone() system call is reversed. */ - return (int) syscall(__NR_clone, child_stack, flags); -#else - return (int) syscall(__NR_clone, flags, child_stack); -#endif -} - -/* ======================================================================= */ - -static inline pid_t raw_getpid(void) { -#if defined(__alpha__) - return (pid_t) syscall(__NR_getxpid); -#else - return (pid_t) syscall(__NR_getpid); -#endif -} - -/* ======================================================================= */ - -#if !HAVE_DECL_RENAMEAT2 -# ifndef __NR_renameat2 -# if defined __x86_64__ -# define __NR_renameat2 316 -# elif defined __arm__ -# define __NR_renameat2 382 -# elif defined _MIPS_SIM -# if _MIPS_SIM == _MIPS_SIM_ABI32 -# define __NR_renameat2 4351 -# endif -# if _MIPS_SIM == _MIPS_SIM_NABI32 -# define __NR_renameat2 6315 -# endif -# if _MIPS_SIM == _MIPS_SIM_ABI64 -# define __NR_renameat2 5311 -# endif -# elif defined __i386__ -# define __NR_renameat2 353 -# else -# warning "__NR_renameat2 unknown for your architecture" -# endif -# endif - -static inline int renameat2(int oldfd, const char *oldname, int newfd, const char *newname, unsigned flags) { -# ifdef __NR_renameat2 - return syscall(__NR_renameat2, oldfd, oldname, newfd, newname, flags); -# else - errno = ENOSYS; - return -1; -# endif -} -#endif - -/* ======================================================================= */ - -#if !HAVE_DECL_KCMP -static inline int kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2) { -# ifdef __NR_kcmp - return syscall(__NR_kcmp, pid1, pid2, type, idx1, idx2); -# else - errno = ENOSYS; - return -1; -# endif -} -#endif - -/* ======================================================================= */ - -#if !HAVE_DECL_KEYCTL -static inline long keyctl(int cmd, unsigned long arg2, unsigned long arg3, unsigned long arg4,unsigned long arg5) { -# ifdef __NR_keyctl - return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5); -# else - errno = ENOSYS; - return -1; -# endif -} - -static inline key_serial_t add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid) { -# ifdef __NR_add_key - return syscall(__NR_add_key, type, description, payload, plen, ringid); -# else - errno = ENOSYS; - return -1; -# endif -} - -static inline key_serial_t request_key(const char *type, const char *description, const char * callout_info, key_serial_t destringid) { -# ifdef __NR_request_key - return syscall(__NR_request_key, type, description, callout_info, destringid); -# else - errno = ENOSYS; - return -1; -# endif -} -#endif - -/* ======================================================================= */ - -#if !HAVE_DECL_COPY_FILE_RANGE -# ifndef __NR_copy_file_range -# if defined(__x86_64__) -# define __NR_copy_file_range 326 -# elif defined(__i386__) -# define __NR_copy_file_range 377 -# elif defined __s390__ -# define __NR_copy_file_range 375 -# elif defined __arm__ -# define __NR_copy_file_range 391 -# elif defined __aarch64__ -# define __NR_copy_file_range 285 -# else -# warning "__NR_copy_file_range not defined for your architecture" -# endif -# endif - -static inline ssize_t copy_file_range(int fd_in, loff_t *off_in, - int fd_out, loff_t *off_out, - size_t len, - unsigned int flags) { -# ifdef __NR_copy_file_range - return syscall(__NR_copy_file_range, fd_in, off_in, fd_out, off_out, len, flags); -# else - errno = ENOSYS; - return -1; -# endif -} -#endif diff --git a/src/basic/mkdir-label.c b/src/basic/mkdir-label.c deleted file mode 100644 index aa6878cdf0..0000000000 --- a/src/basic/mkdir-label.c +++ /dev/null @@ -1,38 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2013 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 -#include -#include - -#include "label.h" -#include "mkdir.h" - -int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid) { - return mkdir_safe_internal(path, mode, uid, gid, mkdir_label); -} - -int mkdir_parents_label(const char *path, mode_t mode) { - return mkdir_parents_internal(NULL, path, mode, mkdir_label); -} - -int mkdir_p_label(const char *path, mode_t mode) { - return mkdir_p_internal(NULL, path, mode, mkdir_label); -} diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c deleted file mode 100644 index 6b1a98402c..0000000000 --- a/src/basic/mkdir.c +++ /dev/null @@ -1,128 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include - -#include "fs-util.h" -#include "macro.h" -#include "mkdir.h" -#include "path-util.h" -#include "stat-util.h" -#include "user-util.h" - -int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, mkdir_func_t _mkdir) { - struct stat st; - - if (_mkdir(path, mode) >= 0) - if (chmod_and_chown(path, mode, uid, gid) < 0) - return -errno; - - if (lstat(path, &st) < 0) - return -errno; - - if ((st.st_mode & 0007) > (mode & 0007) || - (st.st_mode & 0070) > (mode & 0070) || - (st.st_mode & 0700) > (mode & 0700) || - (uid != UID_INVALID && st.st_uid != uid) || - (gid != GID_INVALID && st.st_gid != gid) || - !S_ISDIR(st.st_mode)) - return -EEXIST; - - return 0; -} - -int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid) { - return mkdir_safe_internal(path, mode, uid, gid, mkdir); -} - -int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir) { - const char *p, *e; - int r; - - assert(path); - - if (prefix && !path_startswith(path, prefix)) - return -ENOTDIR; - - /* return immediately if directory exists */ - e = strrchr(path, '/'); - if (!e) - return -EINVAL; - - if (e == path) - return 0; - - p = strndupa(path, e - path); - r = is_dir(p, true); - if (r > 0) - return 0; - if (r == 0) - return -ENOTDIR; - - /* create every parent directory in the path, except the last component */ - p = path + strspn(path, "/"); - for (;;) { - char t[strlen(path) + 1]; - - e = p + strcspn(p, "/"); - p = e + strspn(e, "/"); - - /* Is this the last component? If so, then we're - * done */ - if (*p == 0) - return 0; - - memcpy(t, path, e - path); - t[e-path] = 0; - - if (prefix && path_startswith(prefix, t)) - continue; - - r = _mkdir(t, mode); - if (r < 0 && errno != EEXIST) - return -errno; - } -} - -int mkdir_parents(const char *path, mode_t mode) { - return mkdir_parents_internal(NULL, path, mode, mkdir); -} - -int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir) { - int r; - - /* Like mkdir -p */ - - r = mkdir_parents_internal(prefix, path, mode, _mkdir); - if (r < 0) - return r; - - r = _mkdir(path, mode); - if (r < 0 && (errno != EEXIST || is_dir(path, true) <= 0)) - return -errno; - - return 0; -} - -int mkdir_p(const char *path, mode_t mode) { - return mkdir_p_internal(NULL, path, mode, mkdir); -} diff --git a/src/basic/mkdir.h b/src/basic/mkdir.h deleted file mode 100644 index d564a3547f..0000000000 --- a/src/basic/mkdir.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2013 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 - -int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid); -int mkdir_parents(const char *path, mode_t mode); -int mkdir_p(const char *path, mode_t mode); - -/* mandatory access control(MAC) versions */ -int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid); -int mkdir_parents_label(const char *path, mode_t mode); -int mkdir_p_label(const char *path, mode_t mode); - -/* internally used */ -typedef int (*mkdir_func_t)(const char *pathname, mode_t mode); -int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, mkdir_func_t _mkdir); -int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir); -int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir); diff --git a/src/basic/mount-util.c b/src/basic/mount-util.c deleted file mode 100644 index ba698959b7..0000000000 --- a/src/basic/mount-util.c +++ /dev/null @@ -1,533 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "escape.h" -#include "fd-util.h" -#include "fileio.h" -#include "hashmap.h" -#include "mount-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "set.h" -#include "stdio-util.h" -#include "string-util.h" - -static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *mnt_id) { - char path[strlen("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)]; - _cleanup_free_ char *fdinfo = NULL; - _cleanup_close_ int subfd = -1; - char *p; - int r; - - if ((flags & AT_EMPTY_PATH) && isempty(filename)) - xsprintf(path, "/proc/self/fdinfo/%i", fd); - else { - subfd = openat(fd, filename, O_CLOEXEC|O_PATH); - if (subfd < 0) - return -errno; - - xsprintf(path, "/proc/self/fdinfo/%i", subfd); - } - - r = read_full_file(path, &fdinfo, NULL); - if (r == -ENOENT) /* The fdinfo directory is a relatively new addition */ - return -EOPNOTSUPP; - if (r < 0) - return -errno; - - p = startswith(fdinfo, "mnt_id:"); - if (!p) { - p = strstr(fdinfo, "\nmnt_id:"); - if (!p) /* The mnt_id field is a relatively new addition */ - return -EOPNOTSUPP; - - p += 8; - } - - p += strspn(p, WHITESPACE); - p[strcspn(p, WHITESPACE)] = 0; - - return safe_atoi(p, mnt_id); -} - - -int fd_is_mount_point(int fd, const char *filename, int flags) { - union file_handle_union h = FILE_HANDLE_INIT, h_parent = FILE_HANDLE_INIT; - int mount_id = -1, mount_id_parent = -1; - bool nosupp = false, check_st_dev = true; - struct stat a, b; - int r; - - assert(fd >= 0); - assert(filename); - - /* First we will try the name_to_handle_at() syscall, which - * tells us the mount id and an opaque file "handle". It is - * not supported everywhere though (kernel compile-time - * option, not all file systems are hooked up). If it works - * the mount id is usually good enough to tell us whether - * something is a mount point. - * - * If that didn't work we will try to read the mount id from - * /proc/self/fdinfo/. This is almost as good as - * name_to_handle_at(), however, does not return the - * opaque file handle. The opaque file handle is pretty useful - * to detect the root directory, which we should always - * consider a mount point. Hence we use this only as - * fallback. Exporting the mnt_id in fdinfo is a pretty recent - * kernel addition. - * - * As last fallback we do traditional fstat() based st_dev - * comparisons. This is how things were traditionally done, - * but unionfs breaks breaks this since it exposes file - * systems with a variety of st_dev reported. Also, btrfs - * subvolumes have different st_dev, even though they aren't - * real mounts of their own. */ - - r = name_to_handle_at(fd, filename, &h.handle, &mount_id, flags); - if (r < 0) { - if (errno == ENOSYS) - /* This kernel does not support name_to_handle_at() - * fall back to simpler logic. */ - goto fallback_fdinfo; - else if (errno == EOPNOTSUPP) - /* This kernel or file system does not support - * name_to_handle_at(), hence let's see if the - * upper fs supports it (in which case it is a - * mount point), otherwise fallback to the - * traditional stat() logic */ - nosupp = true; - else - return -errno; - } - - r = name_to_handle_at(fd, "", &h_parent.handle, &mount_id_parent, AT_EMPTY_PATH); - if (r < 0) { - if (errno == EOPNOTSUPP) { - if (nosupp) - /* Neither parent nor child do name_to_handle_at()? - We have no choice but to fall back. */ - goto fallback_fdinfo; - else - /* The parent can't do name_to_handle_at() but the - * directory we are interested in can? - * If so, it must be a mount point. */ - return 1; - } else - return -errno; - } - - /* The parent can do name_to_handle_at() but the - * directory we are interested in can't? If so, it - * must be a mount point. */ - if (nosupp) - return 1; - - /* If the file handle for the directory we are - * interested in and its parent are identical, we - * assume this is the root directory, which is a mount - * point. */ - - if (h.handle.handle_bytes == h_parent.handle.handle_bytes && - h.handle.handle_type == h_parent.handle.handle_type && - memcmp(h.handle.f_handle, h_parent.handle.f_handle, h.handle.handle_bytes) == 0) - return 1; - - return mount_id != mount_id_parent; - -fallback_fdinfo: - r = fd_fdinfo_mnt_id(fd, filename, flags, &mount_id); - if (r == -EOPNOTSUPP) - goto fallback_fstat; - if (r < 0) - return r; - - r = fd_fdinfo_mnt_id(fd, "", AT_EMPTY_PATH, &mount_id_parent); - if (r < 0) - return r; - - if (mount_id != mount_id_parent) - return 1; - - /* Hmm, so, the mount ids are the same. This leaves one - * special case though for the root file system. For that, - * let's see if the parent directory has the same inode as we - * are interested in. Hence, let's also do fstat() checks now, - * too, but avoid the st_dev comparisons, since they aren't - * that useful on unionfs mounts. */ - check_st_dev = false; - -fallback_fstat: - /* yay for fstatat() taking a different set of flags than the other - * _at() above */ - if (flags & AT_SYMLINK_FOLLOW) - flags &= ~AT_SYMLINK_FOLLOW; - else - flags |= AT_SYMLINK_NOFOLLOW; - if (fstatat(fd, filename, &a, flags) < 0) - return -errno; - - if (fstatat(fd, "", &b, AT_EMPTY_PATH) < 0) - return -errno; - - /* A directory with same device and inode as its parent? Must - * be the root directory */ - if (a.st_dev == b.st_dev && - a.st_ino == b.st_ino) - return 1; - - return check_st_dev && (a.st_dev != b.st_dev); -} - -/* flags can be AT_SYMLINK_FOLLOW or 0 */ -int path_is_mount_point(const char *t, int flags) { - _cleanup_close_ int fd = -1; - _cleanup_free_ char *canonical = NULL, *parent = NULL; - - assert(t); - - if (path_equal(t, "/")) - return 1; - - /* we need to resolve symlinks manually, we can't just rely on - * fd_is_mount_point() to do that for us; if we have a structure like - * /bin -> /usr/bin/ and /usr is a mount point, then the parent that we - * look at needs to be /usr, not /. */ - if (flags & AT_SYMLINK_FOLLOW) { - canonical = canonicalize_file_name(t); - if (!canonical) - return -errno; - - t = canonical; - } - - parent = dirname_malloc(t); - if (!parent) - return -ENOMEM; - - fd = openat(AT_FDCWD, parent, O_DIRECTORY|O_CLOEXEC|O_PATH); - if (fd < 0) - return -errno; - - return fd_is_mount_point(fd, basename(t), flags); -} - -int umount_recursive(const char *prefix, int flags) { - bool again; - int n = 0, r; - - /* Try to umount everything recursively below a - * directory. Also, take care of stacked mounts, and keep - * unmounting them until they are gone. */ - - do { - _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; - - again = false; - r = 0; - - proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); - if (!proc_self_mountinfo) - return -errno; - - for (;;) { - _cleanup_free_ char *path = NULL, *p = NULL; - int k; - - k = fscanf(proc_self_mountinfo, - "%*s " /* (1) mount id */ - "%*s " /* (2) parent id */ - "%*s " /* (3) major:minor */ - "%*s " /* (4) root */ - "%ms " /* (5) mount point */ - "%*s" /* (6) mount options */ - "%*[^-]" /* (7) optional fields */ - "- " /* (8) separator */ - "%*s " /* (9) file system type */ - "%*s" /* (10) mount source */ - "%*s" /* (11) mount options 2 */ - "%*[^\n]", /* some rubbish at the end */ - &path); - if (k != 1) { - if (k == EOF) - break; - - continue; - } - - r = cunescape(path, UNESCAPE_RELAX, &p); - if (r < 0) - return r; - - if (!path_startswith(p, prefix)) - continue; - - if (umount2(p, flags) < 0) { - r = -errno; - continue; - } - - again = true; - n++; - - break; - } - - } while (again); - - return r ? r : n; -} - -static int get_mount_flags(const char *path, unsigned long *flags) { - struct statvfs buf; - - if (statvfs(path, &buf) < 0) - return -errno; - *flags = buf.f_flag; - return 0; -} - -int bind_remount_recursive(const char *prefix, bool ro) { - _cleanup_set_free_free_ Set *done = NULL; - _cleanup_free_ char *cleaned = NULL; - int r; - - /* Recursively remount a directory (and all its submounts) - * read-only or read-write. If the directory is already - * mounted, we reuse the mount and simply mark it - * MS_BIND|MS_RDONLY (or remove the MS_RDONLY for read-write - * operation). If it isn't we first make it one. Afterwards we - * apply MS_BIND|MS_RDONLY (or remove MS_RDONLY) to all - * submounts we can access, too. When mounts are stacked on - * the same mount point we only care for each individual - * "top-level" mount on each point, as we cannot - * influence/access the underlying mounts anyway. We do not - * have any effect on future submounts that might get - * propagated, they migt be writable. This includes future - * submounts that have been triggered via autofs. */ - - cleaned = strdup(prefix); - if (!cleaned) - return -ENOMEM; - - path_kill_slashes(cleaned); - - done = set_new(&string_hash_ops); - if (!done) - return -ENOMEM; - - for (;;) { - _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; - _cleanup_set_free_free_ Set *todo = NULL; - bool top_autofs = false; - char *x; - unsigned long orig_flags; - - todo = set_new(&string_hash_ops); - if (!todo) - return -ENOMEM; - - proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); - if (!proc_self_mountinfo) - return -errno; - - for (;;) { - _cleanup_free_ char *path = NULL, *p = NULL, *type = NULL; - int k; - - k = fscanf(proc_self_mountinfo, - "%*s " /* (1) mount id */ - "%*s " /* (2) parent id */ - "%*s " /* (3) major:minor */ - "%*s " /* (4) root */ - "%ms " /* (5) mount point */ - "%*s" /* (6) mount options (superblock) */ - "%*[^-]" /* (7) optional fields */ - "- " /* (8) separator */ - "%ms " /* (9) file system type */ - "%*s" /* (10) mount source */ - "%*s" /* (11) mount options (bind mount) */ - "%*[^\n]", /* some rubbish at the end */ - &path, - &type); - if (k != 2) { - if (k == EOF) - break; - - continue; - } - - r = cunescape(path, UNESCAPE_RELAX, &p); - if (r < 0) - return r; - - /* Let's ignore autofs mounts. If they aren't - * triggered yet, we want to avoid triggering - * them, as we don't make any guarantees for - * future submounts anyway. If they are - * already triggered, then we will find - * another entry for this. */ - if (streq(type, "autofs")) { - top_autofs = top_autofs || path_equal(cleaned, p); - continue; - } - - if (path_startswith(p, cleaned) && - !set_contains(done, p)) { - - r = set_consume(todo, p); - p = NULL; - - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - } - - /* If we have no submounts to process anymore and if - * the root is either already done, or an autofs, we - * are done */ - if (set_isempty(todo) && - (top_autofs || set_contains(done, cleaned))) - return 0; - - if (!set_contains(done, cleaned) && - !set_contains(todo, cleaned)) { - /* The prefix directory itself is not yet a - * mount, make it one. */ - if (mount(cleaned, cleaned, NULL, MS_BIND|MS_REC, NULL) < 0) - return -errno; - - orig_flags = 0; - (void) get_mount_flags(cleaned, &orig_flags); - orig_flags &= ~MS_RDONLY; - - if (mount(NULL, prefix, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) - return -errno; - - x = strdup(cleaned); - if (!x) - return -ENOMEM; - - r = set_consume(done, x); - if (r < 0) - return r; - } - - while ((x = set_steal_first(todo))) { - - r = set_consume(done, x); - if (r == -EEXIST || r == 0) - continue; - if (r < 0) - return r; - - /* Try to reuse the original flag set, but - * don't care for errors, in case of - * obstructed mounts */ - orig_flags = 0; - (void) get_mount_flags(x, &orig_flags); - orig_flags &= ~MS_RDONLY; - - if (mount(NULL, x, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) { - - /* Deal with mount points that are - * obstructed by a later mount */ - - if (errno != ENOENT) - return -errno; - } - - } - } -} - -int mount_move_root(const char *path) { - assert(path); - - if (chdir(path) < 0) - return -errno; - - if (mount(path, "/", NULL, MS_MOVE, NULL) < 0) - return -errno; - - if (chroot(".") < 0) - return -errno; - - if (chdir("/") < 0) - return -errno; - - return 0; -} - -bool fstype_is_network(const char *fstype) { - static const char table[] = - "afs\0" - "cifs\0" - "smbfs\0" - "sshfs\0" - "ncpfs\0" - "ncp\0" - "nfs\0" - "nfs4\0" - "gfs\0" - "gfs2\0" - "glusterfs\0" - "pvfs2\0" /* OrangeFS */ - ; - - const char *x; - - x = startswith(fstype, "fuse."); - if (x) - fstype = x; - - return nulstr_contains(table, fstype); -} - -int repeat_unmount(const char *path, int flags) { - bool done = false; - - assert(path); - - /* If there are multiple mounts on a mount point, this - * removes them all */ - - for (;;) { - if (umount2(path, flags) < 0) { - - if (errno == EINVAL) - return done; - - return -errno; - } - - done = true; - } -} diff --git a/src/basic/mount-util.h b/src/basic/mount-util.h deleted file mode 100644 index bdb525d6b0..0000000000 --- a/src/basic/mount-util.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include - -#include "macro.h" -#include "missing.h" - -int fd_is_mount_point(int fd, const char *filename, int flags); -int path_is_mount_point(const char *path, int flags); - -int repeat_unmount(const char *path, int flags); - -int umount_recursive(const char *target, int flags); -int bind_remount_recursive(const char *prefix, bool ro); - -int mount_move_root(const char *path); - -DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, endmntent); -#define _cleanup_endmntent_ _cleanup_(endmntentp) - -bool fstype_is_network(const char *fstype); - -union file_handle_union { - struct file_handle handle; - char padding[sizeof(struct file_handle) + MAX_HANDLE_SZ]; -}; - -#define FILE_HANDLE_INIT { .handle.handle_bytes = MAX_HANDLE_SZ } diff --git a/src/basic/nss-util.h b/src/basic/nss-util.h deleted file mode 100644 index bf7c4854fc..0000000000 --- a/src/basic/nss-util.h +++ /dev/null @@ -1,199 +0,0 @@ -#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 -#include -#include -#include -#include - -#define NSS_SIGNALS_BLOCK SIGALRM,SIGVTALRM,SIGPIPE,SIGCHLD,SIGTSTP,SIGIO,SIGHUP,SIGUSR1,SIGUSR2,SIGPROF,SIGURG,SIGWINCH - -#define NSS_GETHOSTBYNAME_PROTOTYPES(module) \ -enum nss_status _nss_##module##_gethostbyname4_r( \ - const char *name, \ - struct gaih_addrtuple **pat, \ - char *buffer, size_t buflen, \ - int *errnop, int *h_errnop, \ - int32_t *ttlp) _public_; \ -enum nss_status _nss_##module##_gethostbyname3_r( \ - const char *name, \ - int af, \ - struct hostent *host, \ - char *buffer, size_t buflen, \ - int *errnop, int *h_errnop, \ - int32_t *ttlp, \ - char **canonp) _public_; \ -enum nss_status _nss_##module##_gethostbyname2_r( \ - const char *name, \ - int af, \ - struct hostent *host, \ - char *buffer, size_t buflen, \ - int *errnop, int *h_errnop) _public_; \ -enum nss_status _nss_##module##_gethostbyname_r( \ - const char *name, \ - struct hostent *host, \ - char *buffer, size_t buflen, \ - int *errnop, int *h_errnop) _public_ - -#define NSS_GETHOSTBYADDR_PROTOTYPES(module) \ -enum nss_status _nss_##module##_gethostbyaddr2_r( \ - const void* addr, socklen_t len, \ - int af, \ - struct hostent *host, \ - char *buffer, size_t buflen, \ - int *errnop, int *h_errnop, \ - int32_t *ttlp) _public_; \ -enum nss_status _nss_##module##_gethostbyaddr_r( \ - const void* addr, socklen_t len, \ - int af, \ - struct hostent *host, \ - char *buffer, size_t buflen, \ - int *errnop, int *h_errnop) _public_ - -#define NSS_GETHOSTBYNAME_FALLBACKS(module) \ -enum nss_status _nss_##module##_gethostbyname2_r( \ - const char *name, \ - int af, \ - struct hostent *host, \ - char *buffer, size_t buflen, \ - int *errnop, int *h_errnop) { \ - return _nss_##module##_gethostbyname3_r( \ - name, \ - af, \ - host, \ - buffer, buflen, \ - errnop, h_errnop, \ - NULL, \ - NULL); \ -} \ -enum nss_status _nss_##module##_gethostbyname_r( \ - const char *name, \ - struct hostent *host, \ - char *buffer, size_t buflen, \ - int *errnop, int *h_errnop) { \ - enum nss_status ret = NSS_STATUS_NOTFOUND; \ - \ - if (_res.options & RES_USE_INET6) \ - ret = _nss_##module##_gethostbyname3_r( \ - name, \ - AF_INET6, \ - host, \ - buffer, buflen, \ - errnop, h_errnop, \ - NULL, \ - NULL); \ - if (ret == NSS_STATUS_NOTFOUND) \ - ret = _nss_##module##_gethostbyname3_r( \ - name, \ - AF_INET, \ - host, \ - buffer, buflen, \ - errnop, h_errnop, \ - NULL, \ - NULL); \ - return ret; \ -} \ -struct __useless_struct_to_allow_trailing_semicolon__ - -#define NSS_GETHOSTBYADDR_FALLBACKS(module) \ -enum nss_status _nss_##module##_gethostbyaddr_r( \ - const void* addr, socklen_t len, \ - int af, \ - struct hostent *host, \ - char *buffer, size_t buflen, \ - int *errnop, int *h_errnop) { \ - return _nss_##module##_gethostbyaddr2_r( \ - addr, len, \ - af, \ - host, \ - buffer, buflen, \ - errnop, h_errnop, \ - NULL); \ -} \ -struct __useless_struct_to_allow_trailing_semicolon__ - -#define NSS_GETPW_PROTOTYPES(module) \ -enum nss_status _nss_##module##_getpwnam_r( \ - const char *name, \ - struct passwd *pwd, \ - char *buffer, size_t buflen, \ - int *errnop) _public_; \ -enum nss_status _nss_mymachines_getpwuid_r( \ - uid_t uid, \ - struct passwd *pwd, \ - char *buffer, size_t buflen, \ - int *errnop) _public_ - -#define NSS_GETGR_PROTOTYPES(module) \ -enum nss_status _nss_##module##_getgrnam_r( \ - const char *name, \ - struct group *gr, \ - char *buffer, size_t buflen, \ - int *errnop) _public_; \ -enum nss_status _nss_##module##_getgrgid_r( \ - gid_t gid, \ - struct group *gr, \ - char *buffer, size_t buflen, \ - int *errnop) _public_ - -typedef enum nss_status (*_nss_gethostbyname4_r_t)( - const char *name, - struct gaih_addrtuple **pat, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp); - -typedef enum nss_status (*_nss_gethostbyname3_r_t)( - const char *name, - int af, - struct hostent *result, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp, - char **canonp); - -typedef enum nss_status (*_nss_gethostbyname2_r_t)( - const char *name, - int af, - struct hostent *result, - char *buffer, size_t buflen, - int *errnop, int *h_errnop); - -typedef enum nss_status (*_nss_gethostbyname_r_t)( - const char *name, - struct hostent *result, - char *buffer, size_t buflen, - int *errnop, int *h_errnop); - -typedef enum nss_status (*_nss_gethostbyaddr2_r_t)( - const void* addr, socklen_t len, - int af, - struct hostent *result, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp); -typedef enum nss_status (*_nss_gethostbyaddr_r_t)( - const void* addr, socklen_t len, - int af, - struct hostent *host, - char *buffer, size_t buflen, - int *errnop, int *h_errnop); diff --git a/src/basic/ordered-set.c b/src/basic/ordered-set.c deleted file mode 100644 index 2e0bdf6488..0000000000 --- a/src/basic/ordered-set.c +++ /dev/null @@ -1,64 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2016 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 "ordered-set.h" -#include "strv.h" - -int ordered_set_consume(OrderedSet *s, void *p) { - int r; - - r = ordered_set_put(s, p); - if (r <= 0) - free(p); - - return r; -} - -int ordered_set_put_strdup(OrderedSet *s, const char *p) { - char *c; - int r; - - assert(s); - assert(p); - - c = strdup(p); - if (!c) - return -ENOMEM; - - r = ordered_set_consume(s, c); - if (r == -EEXIST) - return 0; - - return r; -} - -int ordered_set_put_strdupv(OrderedSet *s, char **l) { - int n = 0, r; - char **i; - - STRV_FOREACH(i, l) { - r = ordered_set_put_strdup(s, *i); - if (r < 0) - return r; - - n += r; - } - - return n; -} diff --git a/src/basic/ordered-set.h b/src/basic/ordered-set.h deleted file mode 100644 index e1dfc86380..0000000000 --- a/src/basic/ordered-set.h +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 "hashmap.h" - -typedef struct OrderedSet OrderedSet; - -static inline OrderedSet* ordered_set_new(const struct hash_ops *ops) { - return (OrderedSet*) ordered_hashmap_new(ops); -} - -static inline int ordered_set_ensure_allocated(OrderedSet **s, const struct hash_ops *ops) { - if (*s) - return 0; - - *s = ordered_set_new(ops); - if (!*s) - return -ENOMEM; - - return 0; -} - -static inline OrderedSet* ordered_set_free(OrderedSet *s) { - ordered_hashmap_free((OrderedHashmap*) s); - return NULL; -} - -static inline OrderedSet* ordered_set_free_free(OrderedSet *s) { - ordered_hashmap_free_free((OrderedHashmap*) s); - return NULL; -} - -static inline int ordered_set_put(OrderedSet *s, void *p) { - return ordered_hashmap_put((OrderedHashmap*) s, p, p); -} - -static inline bool ordered_set_isempty(OrderedSet *s) { - return ordered_hashmap_isempty((OrderedHashmap*) s); -} - -static inline bool ordered_set_iterate(OrderedSet *s, Iterator *i, void **value) { - return ordered_hashmap_iterate((OrderedHashmap*) s, i, value, NULL); -} - -int ordered_set_consume(OrderedSet *s, void *p); -int ordered_set_put_strdup(OrderedSet *s, const char *p); -int ordered_set_put_strdupv(OrderedSet *s, char **l); - -#define ORDERED_SET_FOREACH(e, s, i) \ - for ((i) = ITERATOR_FIRST; ordered_set_iterate((s), &(i), (void**)&(e)); ) - -DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedSet*, ordered_set_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedSet*, ordered_set_free_free); - -#define _cleanup_ordered_set_free_ _cleanup_(ordered_set_freep) -#define _cleanup_ordered_set_free_free_ _cleanup_(ordered_set_free_freep) diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c deleted file mode 100644 index 6c11b605a9..0000000000 --- a/src/basic/parse-util.c +++ /dev/null @@ -1,534 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "extract-word.h" -#include "macro.h" -#include "parse-util.h" -#include "string-util.h" - -int parse_boolean(const char *v) { - assert(v); - - if (streq(v, "1") || strcaseeq(v, "yes") || strcaseeq(v, "y") || strcaseeq(v, "true") || strcaseeq(v, "t") || strcaseeq(v, "on")) - return 1; - else if (streq(v, "0") || strcaseeq(v, "no") || strcaseeq(v, "n") || strcaseeq(v, "false") || strcaseeq(v, "f") || strcaseeq(v, "off")) - return 0; - - return -EINVAL; -} - -int parse_pid(const char *s, pid_t* ret_pid) { - unsigned long ul = 0; - pid_t pid; - int r; - - assert(s); - assert(ret_pid); - - r = safe_atolu(s, &ul); - if (r < 0) - return r; - - pid = (pid_t) ul; - - if ((unsigned long) pid != ul) - return -ERANGE; - - if (pid <= 0) - return -ERANGE; - - *ret_pid = pid; - return 0; -} - -int parse_mode(const char *s, mode_t *ret) { - char *x; - long l; - - assert(s); - assert(ret); - - s += strspn(s, WHITESPACE); - if (s[0] == '-') - return -ERANGE; - - errno = 0; - l = strtol(s, &x, 8); - if (errno > 0) - return -errno; - if (!x || x == s || *x) - return -EINVAL; - if (l < 0 || l > 07777) - return -ERANGE; - - *ret = (mode_t) l; - return 0; -} - -int parse_ifindex(const char *s, int *ret) { - int ifi, r; - - r = safe_atoi(s, &ifi); - if (r < 0) - return r; - if (ifi <= 0) - return -EINVAL; - - *ret = ifi; - return 0; -} - -int parse_size(const char *t, uint64_t base, uint64_t *size) { - - /* Soo, sometimes we want to parse IEC binary suffixes, and - * sometimes SI decimal suffixes. This function can parse - * both. Which one is the right way depends on the - * context. Wikipedia suggests that SI is customary for - * hardware metrics and network speeds, while IEC is - * customary for most data sizes used by software and volatile - * (RAM) memory. Hence be careful which one you pick! - * - * In either case we use just K, M, G as suffix, and not Ki, - * Mi, Gi or so (as IEC would suggest). That's because that's - * frickin' ugly. But this means you really need to make sure - * to document which base you are parsing when you use this - * call. */ - - struct table { - const char *suffix; - unsigned long long factor; - }; - - static const struct table iec[] = { - { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, - { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, - { "T", 1024ULL*1024ULL*1024ULL*1024ULL }, - { "G", 1024ULL*1024ULL*1024ULL }, - { "M", 1024ULL*1024ULL }, - { "K", 1024ULL }, - { "B", 1ULL }, - { "", 1ULL }, - }; - - static const struct table si[] = { - { "E", 1000ULL*1000ULL*1000ULL*1000ULL*1000ULL*1000ULL }, - { "P", 1000ULL*1000ULL*1000ULL*1000ULL*1000ULL }, - { "T", 1000ULL*1000ULL*1000ULL*1000ULL }, - { "G", 1000ULL*1000ULL*1000ULL }, - { "M", 1000ULL*1000ULL }, - { "K", 1000ULL }, - { "B", 1ULL }, - { "", 1ULL }, - }; - - const struct table *table; - const char *p; - unsigned long long r = 0; - unsigned n_entries, start_pos = 0; - - assert(t); - assert(base == 1000 || base == 1024); - assert(size); - - if (base == 1000) { - table = si; - n_entries = ELEMENTSOF(si); - } else { - table = iec; - n_entries = ELEMENTSOF(iec); - } - - p = t; - do { - unsigned long long l, tmp; - double frac = 0; - char *e; - unsigned i; - - p += strspn(p, WHITESPACE); - - errno = 0; - l = strtoull(p, &e, 10); - if (errno > 0) - return -errno; - if (e == p) - return -EINVAL; - if (*p == '-') - return -ERANGE; - - if (*e == '.') { - e++; - - /* strtoull() itself would accept space/+/- */ - if (*e >= '0' && *e <= '9') { - unsigned long long l2; - char *e2; - - l2 = strtoull(e, &e2, 10); - if (errno > 0) - return -errno; - - /* Ignore failure. E.g. 10.M is valid */ - frac = l2; - for (; e < e2; e++) - frac /= 10; - } - } - - e += strspn(e, WHITESPACE); - - for (i = start_pos; i < n_entries; i++) - if (startswith(e, table[i].suffix)) - break; - - if (i >= n_entries) - return -EINVAL; - - if (l + (frac > 0) > ULLONG_MAX / table[i].factor) - return -ERANGE; - - tmp = l * table[i].factor + (unsigned long long) (frac * table[i].factor); - if (tmp > ULLONG_MAX - r) - return -ERANGE; - - r += tmp; - if ((unsigned long long) (uint64_t) r != r) - return -ERANGE; - - p = e + strlen(table[i].suffix); - - start_pos = i + 1; - - } while (*p); - - *size = r; - - return 0; -} - -int parse_range(const char *t, unsigned *lower, unsigned *upper) { - _cleanup_free_ char *word = NULL; - unsigned l, u; - int r; - - assert(lower); - assert(upper); - - /* Extract the lower bound. */ - r = extract_first_word(&t, &word, "-", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - r = safe_atou(word, &l); - if (r < 0) - return r; - - /* Check for the upper bound and extract it if needed */ - if (!t) - /* Single number with no dashes. */ - u = l; - else if (!*t) - /* Trailing dash is an error. */ - return -EINVAL; - else { - r = safe_atou(t, &u); - if (r < 0) - return r; - } - - *lower = l; - *upper = u; - return 0; -} - -char *format_bytes(char *buf, size_t l, uint64_t t) { - unsigned i; - - /* This only does IEC units so far */ - - static const struct { - const char *suffix; - uint64_t factor; - } table[] = { - { "E", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, - { "P", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, - { "T", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, - { "G", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, - { "M", UINT64_C(1024)*UINT64_C(1024) }, - { "K", UINT64_C(1024) }, - }; - - if (t == (uint64_t) -1) - return NULL; - - for (i = 0; i < ELEMENTSOF(table); i++) { - - if (t >= table[i].factor) { - snprintf(buf, l, - "%" PRIu64 ".%" PRIu64 "%s", - t / table[i].factor, - ((t*UINT64_C(10)) / table[i].factor) % UINT64_C(10), - table[i].suffix); - - goto finish; - } - } - - snprintf(buf, l, "%" PRIu64 "B", t); - -finish: - buf[l-1] = 0; - return buf; - -} - -int safe_atou(const char *s, unsigned *ret_u) { - char *x = NULL; - unsigned long l; - - assert(s); - assert(ret_u); - - /* strtoul() is happy to parse negative values, and silently - * converts them to unsigned values without generating an - * error. We want a clean error, hence let's look for the "-" - * prefix on our own, and generate an error. But let's do so - * only after strtoul() validated that the string is clean - * otherwise, so that we return EINVAL preferably over - * ERANGE. */ - - s += strspn(s, WHITESPACE); - - errno = 0; - l = strtoul(s, &x, 0); - if (errno > 0) - return -errno; - if (!x || x == s || *x) - return -EINVAL; - if (s[0] == '-') - return -ERANGE; - if ((unsigned long) (unsigned) l != l) - return -ERANGE; - - *ret_u = (unsigned) l; - return 0; -} - -int safe_atoi(const char *s, int *ret_i) { - char *x = NULL; - long l; - - assert(s); - assert(ret_i); - - errno = 0; - l = strtol(s, &x, 0); - if (errno > 0) - return -errno; - if (!x || x == s || *x) - return -EINVAL; - if ((long) (int) l != l) - return -ERANGE; - - *ret_i = (int) l; - return 0; -} - -int safe_atollu(const char *s, long long unsigned *ret_llu) { - char *x = NULL; - unsigned long long l; - - assert(s); - assert(ret_llu); - - s += strspn(s, WHITESPACE); - - errno = 0; - l = strtoull(s, &x, 0); - if (errno > 0) - return -errno; - if (!x || x == s || *x) - return -EINVAL; - if (*s == '-') - return -ERANGE; - - *ret_llu = l; - return 0; -} - -int safe_atolli(const char *s, long long int *ret_lli) { - char *x = NULL; - long long l; - - assert(s); - assert(ret_lli); - - errno = 0; - l = strtoll(s, &x, 0); - if (errno > 0) - return -errno; - if (!x || x == s || *x) - return -EINVAL; - - *ret_lli = l; - return 0; -} - -int safe_atou8(const char *s, uint8_t *ret) { - char *x = NULL; - unsigned long l; - - assert(s); - assert(ret); - - s += strspn(s, WHITESPACE); - - errno = 0; - l = strtoul(s, &x, 0); - if (errno > 0) - return -errno; - if (!x || x == s || *x) - return -EINVAL; - if (s[0] == '-') - return -ERANGE; - if ((unsigned long) (uint8_t) l != l) - return -ERANGE; - - *ret = (uint8_t) l; - return 0; -} - -int safe_atou16(const char *s, uint16_t *ret) { - char *x = NULL; - unsigned long l; - - assert(s); - assert(ret); - - s += strspn(s, WHITESPACE); - - errno = 0; - l = strtoul(s, &x, 0); - if (errno > 0) - return -errno; - if (!x || x == s || *x) - return -EINVAL; - if (s[0] == '-') - return -ERANGE; - if ((unsigned long) (uint16_t) l != l) - return -ERANGE; - - *ret = (uint16_t) l; - return 0; -} - -int safe_atoi16(const char *s, int16_t *ret) { - char *x = NULL; - long l; - - assert(s); - assert(ret); - - errno = 0; - l = strtol(s, &x, 0); - if (errno > 0) - return -errno; - if (!x || x == s || *x) - return -EINVAL; - if ((long) (int16_t) l != l) - return -ERANGE; - - *ret = (int16_t) l; - return 0; -} - -int safe_atod(const char *s, double *ret_d) { - char *x = NULL; - double d = 0; - locale_t loc; - - assert(s); - assert(ret_d); - - loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); - if (loc == (locale_t) 0) - return -errno; - - errno = 0; - d = strtod_l(s, &x, loc); - if (errno > 0) { - freelocale(loc); - return -errno; - } - if (!x || x == s || *x) { - freelocale(loc); - return -EINVAL; - } - - freelocale(loc); - *ret_d = (double) d; - return 0; -} - -int parse_fractional_part_u(const char **p, size_t digits, unsigned *res) { - size_t i; - unsigned val = 0; - const char *s; - - s = *p; - - /* accept any number of digits, strtoull is limted to 19 */ - for (i=0; i < digits; i++,s++) { - if (*s < '0' || *s > '9') { - if (i == 0) - return -EINVAL; - - /* too few digits, pad with 0 */ - for (; i < digits; i++) - val *= 10; - - break; - } - - val *= 10; - val += *s - '0'; - } - - /* maybe round up */ - if (*s >= '5' && *s <= '9') - val++; - - s += strspn(s, DIGITS); - - *p = s; - *res = val; - - return 0; -} diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h deleted file mode 100644 index 7dc579a159..0000000000 --- a/src/basic/parse-util.h +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include - -#include "macro.h" - -#define MODE_INVALID ((mode_t) -1) - -int parse_boolean(const char *v) _pure_; -int parse_pid(const char *s, pid_t* ret_pid); -int parse_mode(const char *s, mode_t *ret); -int parse_ifindex(const char *s, int *ret); - -int parse_size(const char *t, uint64_t base, uint64_t *size); -int parse_range(const char *t, unsigned *lower, unsigned *upper); - -#define FORMAT_BYTES_MAX 8 -char *format_bytes(char *buf, size_t l, uint64_t t); - -int safe_atou(const char *s, unsigned *ret_u); -int safe_atoi(const char *s, int *ret_i); -int safe_atollu(const char *s, unsigned long long *ret_u); -int safe_atolli(const char *s, long long int *ret_i); - -int safe_atou8(const char *s, uint8_t *ret); - -int safe_atou16(const char *s, uint16_t *ret); -int safe_atoi16(const char *s, int16_t *ret); - -static inline int safe_atou32(const char *s, uint32_t *ret_u) { - assert_cc(sizeof(uint32_t) == sizeof(unsigned)); - return safe_atou(s, (unsigned*) ret_u); -} - -static inline int safe_atoi32(const char *s, int32_t *ret_i) { - assert_cc(sizeof(int32_t) == sizeof(int)); - return safe_atoi(s, (int*) ret_i); -} - -static inline int safe_atou64(const char *s, uint64_t *ret_u) { - assert_cc(sizeof(uint64_t) == sizeof(unsigned long long)); - return safe_atollu(s, (unsigned long long*) ret_u); -} - -static inline int safe_atoi64(const char *s, int64_t *ret_i) { - assert_cc(sizeof(int64_t) == sizeof(long long int)); - return safe_atolli(s, (long long int*) ret_i); -} - -#if LONG_MAX == INT_MAX -static inline int safe_atolu(const char *s, unsigned long *ret_u) { - assert_cc(sizeof(unsigned long) == sizeof(unsigned)); - return safe_atou(s, (unsigned*) ret_u); -} -static inline int safe_atoli(const char *s, long int *ret_u) { - assert_cc(sizeof(long int) == sizeof(int)); - return safe_atoi(s, (int*) ret_u); -} -#else -static inline int safe_atolu(const char *s, unsigned long *ret_u) { - assert_cc(sizeof(unsigned long) == sizeof(unsigned long long)); - return safe_atollu(s, (unsigned long long*) ret_u); -} -static inline int safe_atoli(const char *s, long int *ret_u) { - assert_cc(sizeof(long int) == sizeof(long long int)); - return safe_atolli(s, (long long int*) ret_u); -} -#endif - -#if SIZE_MAX == UINT_MAX -static inline int safe_atozu(const char *s, size_t *ret_u) { - assert_cc(sizeof(size_t) == sizeof(unsigned)); - return safe_atou(s, (unsigned *) ret_u); -} -#else -static inline int safe_atozu(const char *s, size_t *ret_u) { - assert_cc(sizeof(size_t) == sizeof(long unsigned)); - return safe_atolu(s, ret_u); -} -#endif - -int safe_atod(const char *s, double *ret_d); - -int parse_fractional_part_u(const char **s, size_t digits, unsigned *res); diff --git a/src/basic/path-util.c b/src/basic/path-util.c deleted file mode 100644 index b2fa81a294..0000000000 --- a/src/basic/path-util.c +++ /dev/null @@ -1,816 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010-2012 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 -#include -#include -#include -#include -#include - -/* When we include libgen.h because we need dirname() we immediately - * undefine basename() since libgen.h defines it as a macro to the - * POSIX version which is really broken. We prefer GNU basename(). */ -#include -#undef basename - -#include "alloc-util.h" -#include "extract-word.h" -#include "fs-util.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "path-util.h" -#include "stat-util.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" - -bool path_is_absolute(const char *p) { - return p[0] == '/'; -} - -bool is_path(const char *p) { - return !!strchr(p, '/'); -} - -int path_split_and_make_absolute(const char *p, char ***ret) { - char **l; - int r; - - assert(p); - assert(ret); - - l = strv_split(p, ":"); - if (!l) - return -ENOMEM; - - r = path_strv_make_absolute_cwd(l); - if (r < 0) { - strv_free(l); - return r; - } - - *ret = l; - return r; -} - -char *path_make_absolute(const char *p, const char *prefix) { - assert(p); - - /* Makes every item in the list an absolute path by prepending - * the prefix, if specified and necessary */ - - if (path_is_absolute(p) || !prefix) - return strdup(p); - - return strjoin(prefix, "/", p, NULL); -} - -int path_make_absolute_cwd(const char *p, char **ret) { - char *c; - - assert(p); - assert(ret); - - /* Similar to path_make_absolute(), but prefixes with the - * current working directory. */ - - if (path_is_absolute(p)) - c = strdup(p); - else { - _cleanup_free_ char *cwd = NULL; - - cwd = get_current_dir_name(); - if (!cwd) - return negative_errno(); - - c = strjoin(cwd, "/", p, NULL); - } - if (!c) - return -ENOMEM; - - *ret = c; - return 0; -} - -int path_make_relative(const char *from_dir, const char *to_path, char **_r) { - char *r, *p; - unsigned n_parents; - - assert(from_dir); - assert(to_path); - assert(_r); - - /* Strips the common part, and adds ".." elements as necessary. */ - - if (!path_is_absolute(from_dir)) - return -EINVAL; - - if (!path_is_absolute(to_path)) - return -EINVAL; - - /* Skip the common part. */ - for (;;) { - size_t a; - size_t b; - - from_dir += strspn(from_dir, "/"); - to_path += strspn(to_path, "/"); - - if (!*from_dir) { - if (!*to_path) - /* from_dir equals to_path. */ - r = strdup("."); - else - /* from_dir is a parent directory of to_path. */ - r = strdup(to_path); - - if (!r) - return -ENOMEM; - - path_kill_slashes(r); - - *_r = r; - return 0; - } - - if (!*to_path) - break; - - a = strcspn(from_dir, "/"); - b = strcspn(to_path, "/"); - - if (a != b) - break; - - if (memcmp(from_dir, to_path, a) != 0) - break; - - from_dir += a; - to_path += b; - } - - /* If we're here, then "from_dir" has one or more elements that need to - * be replaced with "..". */ - - /* Count the number of necessary ".." elements. */ - for (n_parents = 0;;) { - from_dir += strspn(from_dir, "/"); - - if (!*from_dir) - break; - - from_dir += strcspn(from_dir, "/"); - n_parents++; - } - - r = malloc(n_parents * 3 + strlen(to_path) + 1); - if (!r) - return -ENOMEM; - - for (p = r; n_parents > 0; n_parents--, p += 3) - memcpy(p, "../", 3); - - strcpy(p, to_path); - path_kill_slashes(r); - - *_r = r; - return 0; -} - -int path_strv_make_absolute_cwd(char **l) { - char **s; - int r; - - /* Goes through every item in the string list and makes it - * absolute. This works in place and won't rollback any - * changes on failure. */ - - STRV_FOREACH(s, l) { - char *t; - - r = path_make_absolute_cwd(*s, &t); - if (r < 0) - return r; - - free(*s); - *s = t; - } - - return 0; -} - -char **path_strv_resolve(char **l, const char *prefix) { - char **s; - unsigned k = 0; - bool enomem = false; - - if (strv_isempty(l)) - return l; - - /* Goes through every item in the string list and canonicalize - * the path. This works in place and won't rollback any - * changes on failure. */ - - STRV_FOREACH(s, l) { - char *t, *u; - _cleanup_free_ char *orig = NULL; - - if (!path_is_absolute(*s)) { - free(*s); - continue; - } - - if (prefix) { - orig = *s; - t = strappend(prefix, orig); - if (!t) { - enomem = true; - continue; - } - } else - t = *s; - - errno = 0; - u = canonicalize_file_name(t); - if (!u) { - if (errno == ENOENT) { - if (prefix) { - u = orig; - orig = NULL; - free(t); - } else - u = t; - } else { - free(t); - if (errno == ENOMEM || errno == 0) - enomem = true; - - continue; - } - } else if (prefix) { - char *x; - - free(t); - x = path_startswith(u, prefix); - if (x) { - /* restore the slash if it was lost */ - if (!startswith(x, "/")) - *(--x) = '/'; - - t = strdup(x); - free(u); - if (!t) { - enomem = true; - continue; - } - u = t; - } else { - /* canonicalized path goes outside of - * prefix, keep the original path instead */ - free(u); - u = orig; - orig = NULL; - } - } else - free(t); - - l[k++] = u; - } - - l[k] = NULL; - - if (enomem) - return NULL; - - return l; -} - -char **path_strv_resolve_uniq(char **l, const char *prefix) { - - if (strv_isempty(l)) - return l; - - if (!path_strv_resolve(l, prefix)) - return NULL; - - return strv_uniq(l); -} - -char *path_kill_slashes(char *path) { - char *f, *t; - bool slash = false; - - /* Removes redundant inner and trailing slashes. Modifies the - * passed string in-place. - * - * ///foo///bar/ becomes /foo/bar - */ - - for (f = path, t = path; *f; f++) { - - if (*f == '/') { - slash = true; - continue; - } - - if (slash) { - slash = false; - *(t++) = '/'; - } - - *(t++) = *f; - } - - /* Special rule, if we are talking of the root directory, a - trailing slash is good */ - - if (t == path && slash) - *(t++) = '/'; - - *t = 0; - return path; -} - -char* path_startswith(const char *path, const char *prefix) { - assert(path); - assert(prefix); - - if ((path[0] == '/') != (prefix[0] == '/')) - return NULL; - - for (;;) { - size_t a, b; - - path += strspn(path, "/"); - prefix += strspn(prefix, "/"); - - if (*prefix == 0) - return (char*) path; - - if (*path == 0) - return NULL; - - a = strcspn(path, "/"); - b = strcspn(prefix, "/"); - - if (a != b) - return NULL; - - if (memcmp(path, prefix, a) != 0) - return NULL; - - path += a; - prefix += b; - } -} - -int path_compare(const char *a, const char *b) { - int d; - - assert(a); - assert(b); - - /* A relative path and an abolute path must not compare as equal. - * Which one is sorted before the other does not really matter. - * Here a relative path is ordered before an absolute path. */ - d = (a[0] == '/') - (b[0] == '/'); - if (d != 0) - return d; - - for (;;) { - size_t j, k; - - a += strspn(a, "/"); - b += strspn(b, "/"); - - if (*a == 0 && *b == 0) - return 0; - - /* Order prefixes first: "/foo" before "/foo/bar" */ - if (*a == 0) - return -1; - if (*b == 0) - return 1; - - j = strcspn(a, "/"); - k = strcspn(b, "/"); - - /* Alphabetical sort: "/foo/aaa" before "/foo/b" */ - d = memcmp(a, b, MIN(j, k)); - if (d != 0) - return (d > 0) - (d < 0); /* sign of d */ - - /* Sort "/foo/a" before "/foo/aaa" */ - d = (j > k) - (j < k); /* sign of (j - k) */ - if (d != 0) - return d; - - a += j; - b += k; - } -} - -bool path_equal(const char *a, const char *b) { - return path_compare(a, b) == 0; -} - -bool path_equal_or_files_same(const char *a, const char *b) { - return path_equal(a, b) || files_same(a, b) > 0; -} - -char* path_join(const char *root, const char *path, const char *rest) { - assert(path); - - if (!isempty(root)) - return strjoin(root, endswith(root, "/") ? "" : "/", - path[0] == '/' ? path+1 : path, - rest ? (endswith(path, "/") ? "" : "/") : NULL, - rest && rest[0] == '/' ? rest+1 : rest, - NULL); - else - return strjoin(path, - rest ? (endswith(path, "/") ? "" : "/") : NULL, - rest && rest[0] == '/' ? rest+1 : rest, - NULL); -} - -int find_binary(const char *name, char **ret) { - int last_error, r; - const char *p; - - assert(name); - - if (is_path(name)) { - if (access(name, X_OK) < 0) - return -errno; - - if (ret) { - r = path_make_absolute_cwd(name, ret); - if (r < 0) - return r; - } - - return 0; - } - - /** - * Plain getenv, not secure_getenv, because we want - * to actually allow the user to pick the binary. - */ - p = getenv("PATH"); - if (!p) - p = DEFAULT_PATH; - - last_error = -ENOENT; - - for (;;) { - _cleanup_free_ char *j = NULL, *element = NULL; - - r = extract_first_word(&p, &element, ":", EXTRACT_RELAX|EXTRACT_DONT_COALESCE_SEPARATORS); - if (r < 0) - return r; - if (r == 0) - break; - - if (!path_is_absolute(element)) - continue; - - j = strjoin(element, "/", name, NULL); - if (!j) - return -ENOMEM; - - if (access(j, X_OK) >= 0) { - /* Found it! */ - - if (ret) { - *ret = path_kill_slashes(j); - j = NULL; - } - - return 0; - } - - last_error = -errno; - } - - return last_error; -} - -bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) { - bool changed = false; - const char* const* i; - - assert(timestamp); - - if (paths == NULL) - return false; - - STRV_FOREACH(i, paths) { - struct stat stats; - usec_t u; - - if (stat(*i, &stats) < 0) - continue; - - u = timespec_load(&stats.st_mtim); - - /* first check */ - if (*timestamp >= u) - continue; - - log_debug("timestamp of '%s' changed", *i); - - /* update timestamp */ - if (update) { - *timestamp = u; - changed = true; - } else - return true; - } - - return changed; -} - -static int binary_is_good(const char *binary) { - _cleanup_free_ char *p = NULL, *d = NULL; - int r; - - r = find_binary(binary, &p); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - - /* An fsck that is linked to /bin/true is a non-existent - * fsck */ - - r = readlink_malloc(p, &d); - if (r == -EINVAL) /* not a symlink */ - return 1; - if (r < 0) - return r; - - return !PATH_IN_SET(d, "true" - "/bin/true", - "/usr/bin/true", - "/dev/null"); -} - -int fsck_exists(const char *fstype) { - const char *checker; - - assert(fstype); - - if (streq(fstype, "auto")) - return -EINVAL; - - checker = strjoina("fsck.", fstype); - return binary_is_good(checker); -} - -int mkfs_exists(const char *fstype) { - const char *mkfs; - - assert(fstype); - - if (streq(fstype, "auto")) - return -EINVAL; - - mkfs = strjoina("mkfs.", fstype); - return binary_is_good(mkfs); -} - -char *prefix_root(const char *root, const char *path) { - char *n, *p; - size_t l; - - /* If root is passed, prefixes path with it. Otherwise returns - * it as is. */ - - assert(path); - - /* First, drop duplicate prefixing slashes from the path */ - while (path[0] == '/' && path[1] == '/') - path++; - - if (isempty(root) || path_equal(root, "/")) - return strdup(path); - - l = strlen(root) + 1 + strlen(path) + 1; - - n = new(char, l); - if (!n) - return NULL; - - p = stpcpy(n, root); - - while (p > n && p[-1] == '/') - p--; - - if (path[0] != '/') - *(p++) = '/'; - - strcpy(p, path); - return n; -} - -int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg) { - char *p; - int r; - - /* - * This function is intended to be used in command line - * parsers, to handle paths that are passed in. It makes the - * path absolute, and reduces it to NULL if omitted or - * root (the latter optionally). - * - * NOTE THAT THIS WILL FREE THE PREVIOUS ARGUMENT POINTER ON - * SUCCESS! Hence, do not pass in uninitialized pointers. - */ - - if (isempty(path)) { - *arg = mfree(*arg); - return 0; - } - - r = path_make_absolute_cwd(path, &p); - if (r < 0) - return log_error_errno(r, "Failed to parse path \"%s\" and make it absolute: %m", path); - - path_kill_slashes(p); - if (suppress_root && path_equal(p, "/")) - p = mfree(p); - - free(*arg); - *arg = p; - return 0; -} - -char* dirname_malloc(const char *path) { - char *d, *dir, *dir2; - - assert(path); - - d = strdup(path); - if (!d) - return NULL; - - dir = dirname(d); - assert(dir); - - if (dir == d) - return d; - - dir2 = strdup(dir); - free(d); - - return dir2; -} - -bool filename_is_valid(const char *p) { - const char *e; - - if (isempty(p)) - return false; - - if (streq(p, ".")) - return false; - - if (streq(p, "..")) - return false; - - e = strchrnul(p, '/'); - if (*e != 0) - return false; - - if (e - p > FILENAME_MAX) - return false; - - return true; -} - -bool path_is_safe(const char *p) { - - if (isempty(p)) - return false; - - if (streq(p, "..") || startswith(p, "../") || endswith(p, "/..") || strstr(p, "/../")) - return false; - - if (strlen(p)+1 > PATH_MAX) - return false; - - /* The following two checks are not really dangerous, but hey, they still are confusing */ - if (streq(p, ".") || startswith(p, "./") || endswith(p, "/.") || strstr(p, "/./")) - return false; - - if (strstr(p, "//")) - return false; - - return true; -} - -char *file_in_same_dir(const char *path, const char *filename) { - char *e, *ret; - size_t k; - - assert(path); - assert(filename); - - /* This removes the last component of path and appends - * filename, unless the latter is absolute anyway or the - * former isn't */ - - if (path_is_absolute(filename)) - return strdup(filename); - - e = strrchr(path, '/'); - if (!e) - return strdup(filename); - - k = strlen(filename); - ret = new(char, (e + 1 - path) + k + 1); - if (!ret) - return NULL; - - memcpy(mempcpy(ret, path, e + 1 - path), filename, k + 1); - return ret; -} - -bool hidden_or_backup_file(const char *filename) { - const char *p; - - assert(filename); - - if (filename[0] == '.' || - streq(filename, "lost+found") || - streq(filename, "aquota.user") || - streq(filename, "aquota.group") || - endswith(filename, "~")) - return true; - - p = strrchr(filename, '.'); - if (!p) - return false; - - /* Please, let's not add more entries to the list below. If external projects think it's a good idea to come up - * with always new suffixes and that everybody else should just adjust to that, then it really should be on - * them. Hence, in future, let's not add any more entries. Instead, let's ask those packages to instead adopt - * one of the generic suffixes/prefixes for hidden files or backups, possibly augmented with an additional - * string. Specifically: there's now: - * - * The generic suffixes "~" and ".bak" for backup files - * The generic prefix "." for hidden files - * - * Thus, if a new package manager "foopkg" wants its own set of ".foopkg-new", ".foopkg-old", ".foopkg-dist" - * or so registered, let's refuse that and ask them to use ".foopkg.new", ".foopkg.old" or ".foopkg~" instead. - */ - - return STR_IN_SET(p + 1, - "rpmnew", - "rpmsave", - "rpmorig", - "dpkg-old", - "dpkg-new", - "dpkg-tmp", - "dpkg-dist", - "dpkg-bak", - "dpkg-backup", - "dpkg-remove", - "ucf-new", - "ucf-old", - "ucf-dist", - "swp", - "bak", - "old", - "new"); -} - -bool is_device_path(const char *path) { - - /* Returns true on paths that refer to a device, either in - * sysfs or in /dev */ - - return - path_startswith(path, "/dev/") || - path_startswith(path, "/sys/"); -} diff --git a/src/basic/path-util.h b/src/basic/path-util.h deleted file mode 100644 index a27c13fcc3..0000000000 --- a/src/basic/path-util.h +++ /dev/null @@ -1,127 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010-2012 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 -#include - -#include "macro.h" -#include "time-util.h" - -#define DEFAULT_PATH_NORMAL "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin" -#define DEFAULT_PATH_SPLIT_USR DEFAULT_PATH_NORMAL ":/sbin:/bin" - -#ifdef HAVE_SPLIT_USR -# define DEFAULT_PATH DEFAULT_PATH_SPLIT_USR -#else -# define DEFAULT_PATH DEFAULT_PATH_NORMAL -#endif - -bool is_path(const char *p) _pure_; -int path_split_and_make_absolute(const char *p, char ***ret); -bool path_is_absolute(const char *p) _pure_; -char* path_make_absolute(const char *p, const char *prefix); -int path_make_absolute_cwd(const char *p, char **ret); -int path_make_relative(const char *from_dir, const char *to_path, char **_r); -char* path_kill_slashes(char *path); -char* path_startswith(const char *path, const char *prefix) _pure_; -int path_compare(const char *a, const char *b) _pure_; -bool path_equal(const char *a, const char *b) _pure_; -bool path_equal_or_files_same(const char *a, const char *b); -char* path_join(const char *root, const char *path, const char *rest); - -static inline bool path_equal_ptr(const char *a, const char *b) { - return !!a == !!b && (!a || path_equal(a, b)); -} - -/* Note: the search terminates on the first NULL item. */ -#define PATH_IN_SET(p, ...) \ - ({ \ - char **s; \ - bool _found = false; \ - STRV_FOREACH(s, STRV_MAKE(__VA_ARGS__)) \ - if (path_equal(p, *s)) { \ - _found = true; \ - break; \ - } \ - _found; \ - }) - -int path_strv_make_absolute_cwd(char **l); -char** path_strv_resolve(char **l, const char *prefix); -char** path_strv_resolve_uniq(char **l, const char *prefix); - -int find_binary(const char *name, char **filename); - -bool paths_check_timestamp(const char* const* paths, usec_t *paths_ts_usec, bool update); - -int fsck_exists(const char *fstype); -int mkfs_exists(const char *fstype); - -/* Iterates through the path prefixes of the specified path, going up - * the tree, to root. Also returns "" (and not "/"!) for the root - * directory. Excludes the specified directory itself */ -#define PATH_FOREACH_PREFIX(prefix, path) \ - for (char *_slash = ({ path_kill_slashes(strcpy(prefix, path)); streq(prefix, "/") ? NULL : strrchr(prefix, '/'); }); _slash && ((*_slash = 0), true); _slash = strrchr((prefix), '/')) - -/* Same as PATH_FOREACH_PREFIX but also includes the specified path itself */ -#define PATH_FOREACH_PREFIX_MORE(prefix, path) \ - for (char *_slash = ({ path_kill_slashes(strcpy(prefix, path)); if (streq(prefix, "/")) prefix[0] = 0; strrchr(prefix, 0); }); _slash && ((*_slash = 0), true); _slash = strrchr((prefix), '/')) - -char *prefix_root(const char *root, const char *path); - -/* Similar to prefix_root(), but returns an alloca() buffer, or - * possibly a const pointer into the path parameter */ -#define prefix_roota(root, path) \ - ({ \ - const char* _path = (path), *_root = (root), *_ret; \ - char *_p, *_n; \ - size_t _l; \ - while (_path[0] == '/' && _path[1] == '/') \ - _path ++; \ - if (isempty(_root) || path_equal(_root, "/")) \ - _ret = _path; \ - else { \ - _l = strlen(_root) + 1 + strlen(_path) + 1; \ - _n = alloca(_l); \ - _p = stpcpy(_n, _root); \ - while (_p > _n && _p[-1] == '/') \ - _p--; \ - if (_path[0] != '/') \ - *(_p++) = '/'; \ - strcpy(_p, _path); \ - _ret = _n; \ - } \ - _ret; \ - }) - -int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg); - -char* dirname_malloc(const char *path); - -bool filename_is_valid(const char *p) _pure_; -bool path_is_safe(const char *p) _pure_; - -char *file_in_same_dir(const char *path, const char *filename); - -bool hidden_or_backup_file(const char *filename) _pure_; - -bool is_device_path(const char *path); diff --git a/src/basic/prioq.c b/src/basic/prioq.c deleted file mode 100644 index d2ec516d29..0000000000 --- a/src/basic/prioq.c +++ /dev/null @@ -1,320 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -/* - * Priority Queue - * The prioq object implements a priority queue. That is, it orders objects by - * their priority and allows O(1) access to the object with the highest - * priority. Insertion and removal are Θ(log n). Optionally, the caller can - * provide a pointer to an index which will be kept up-to-date by the prioq. - * - * The underlying algorithm used in this implementation is a Heap. - */ - -#include -#include - -#include "alloc-util.h" -#include "hashmap.h" -#include "prioq.h" - -struct prioq_item { - void *data; - unsigned *idx; -}; - -struct Prioq { - compare_func_t compare_func; - unsigned n_items, n_allocated; - - struct prioq_item *items; -}; - -Prioq *prioq_new(compare_func_t compare_func) { - Prioq *q; - - q = new0(Prioq, 1); - if (!q) - return q; - - q->compare_func = compare_func; - return q; -} - -Prioq* prioq_free(Prioq *q) { - if (!q) - return NULL; - - free(q->items); - free(q); - - return NULL; -} - -int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func) { - assert(q); - - if (*q) - return 0; - - *q = prioq_new(compare_func); - if (!*q) - return -ENOMEM; - - return 0; -} - -static void swap(Prioq *q, unsigned j, unsigned k) { - void *saved_data; - unsigned *saved_idx; - - assert(q); - assert(j < q->n_items); - assert(k < q->n_items); - - assert(!q->items[j].idx || *(q->items[j].idx) == j); - assert(!q->items[k].idx || *(q->items[k].idx) == k); - - saved_data = q->items[j].data; - saved_idx = q->items[j].idx; - q->items[j].data = q->items[k].data; - q->items[j].idx = q->items[k].idx; - q->items[k].data = saved_data; - q->items[k].idx = saved_idx; - - if (q->items[j].idx) - *q->items[j].idx = j; - - if (q->items[k].idx) - *q->items[k].idx = k; -} - -static unsigned shuffle_up(Prioq *q, unsigned idx) { - assert(q); - - while (idx > 0) { - unsigned k; - - k = (idx-1)/2; - - if (q->compare_func(q->items[k].data, q->items[idx].data) <= 0) - break; - - swap(q, idx, k); - idx = k; - } - - return idx; -} - -static unsigned shuffle_down(Prioq *q, unsigned idx) { - assert(q); - - for (;;) { - unsigned j, k, s; - - k = (idx+1)*2; /* right child */ - j = k-1; /* left child */ - - if (j >= q->n_items) - break; - - if (q->compare_func(q->items[j].data, q->items[idx].data) < 0) - - /* So our left child is smaller than we are, let's - * remember this fact */ - s = j; - else - s = idx; - - if (k < q->n_items && - q->compare_func(q->items[k].data, q->items[s].data) < 0) - - /* So our right child is smaller than we are, let's - * remember this fact */ - s = k; - - /* s now points to the smallest of the three items */ - - if (s == idx) - /* No swap necessary, we're done */ - break; - - swap(q, idx, s); - idx = s; - } - - return idx; -} - -int prioq_put(Prioq *q, void *data, unsigned *idx) { - struct prioq_item *i; - unsigned k; - - assert(q); - - if (q->n_items >= q->n_allocated) { - unsigned n; - struct prioq_item *j; - - n = MAX((q->n_items+1) * 2, 16u); - j = realloc(q->items, sizeof(struct prioq_item) * n); - if (!j) - return -ENOMEM; - - q->items = j; - q->n_allocated = n; - } - - k = q->n_items++; - i = q->items + k; - i->data = data; - i->idx = idx; - - if (idx) - *idx = k; - - shuffle_up(q, k); - - return 0; -} - -static void remove_item(Prioq *q, struct prioq_item *i) { - struct prioq_item *l; - - assert(q); - assert(i); - - l = q->items + q->n_items - 1; - - if (i == l) - /* Last entry, let's just remove it */ - q->n_items--; - else { - unsigned k; - - /* Not last entry, let's replace the last entry with - * this one, and reshuffle */ - - k = i - q->items; - - i->data = l->data; - i->idx = l->idx; - if (i->idx) - *i->idx = k; - q->n_items--; - - k = shuffle_down(q, k); - shuffle_up(q, k); - } -} - -_pure_ static struct prioq_item* find_item(Prioq *q, void *data, unsigned *idx) { - struct prioq_item *i; - - assert(q); - - if (idx) { - if (*idx == PRIOQ_IDX_NULL || - *idx > q->n_items) - return NULL; - - i = q->items + *idx; - if (i->data != data) - return NULL; - - return i; - } else { - for (i = q->items; i < q->items + q->n_items; i++) - if (i->data == data) - return i; - return NULL; - } -} - -int prioq_remove(Prioq *q, void *data, unsigned *idx) { - struct prioq_item *i; - - if (!q) - return 0; - - i = find_item(q, data, idx); - if (!i) - return 0; - - remove_item(q, i); - return 1; -} - -int prioq_reshuffle(Prioq *q, void *data, unsigned *idx) { - struct prioq_item *i; - unsigned k; - - assert(q); - - i = find_item(q, data, idx); - if (!i) - return 0; - - k = i - q->items; - k = shuffle_down(q, k); - shuffle_up(q, k); - return 1; -} - -void *prioq_peek(Prioq *q) { - - if (!q) - return NULL; - - if (q->n_items <= 0) - return NULL; - - return q->items[0].data; -} - -void *prioq_pop(Prioq *q) { - void *data; - - if (!q) - return NULL; - - if (q->n_items <= 0) - return NULL; - - data = q->items[0].data; - remove_item(q, q->items); - return data; -} - -unsigned prioq_size(Prioq *q) { - - if (!q) - return 0; - - return q->n_items; -} - -bool prioq_isempty(Prioq *q) { - - if (!q) - return true; - - return q->n_items <= 0; -} diff --git a/src/basic/prioq.h b/src/basic/prioq.h deleted file mode 100644 index 113c73d040..0000000000 --- a/src/basic/prioq.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "hashmap.h" -#include "macro.h" - -typedef struct Prioq Prioq; - -#define PRIOQ_IDX_NULL ((unsigned) -1) - -Prioq *prioq_new(compare_func_t compare); -Prioq *prioq_free(Prioq *q); -int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func); - -int prioq_put(Prioq *q, void *data, unsigned *idx); -int prioq_remove(Prioq *q, void *data, unsigned *idx); -int prioq_reshuffle(Prioq *q, void *data, unsigned *idx); - -void *prioq_peek(Prioq *q) _pure_; -void *prioq_pop(Prioq *q); - -unsigned prioq_size(Prioq *q) _pure_; -bool prioq_isempty(Prioq *q) _pure_; diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c deleted file mode 100644 index 3505fa9c9a..0000000000 --- a/src/basic/proc-cmdline.c +++ /dev/null @@ -1,176 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include - -#include "alloc-util.h" -#include "extract-word.h" -#include "fileio.h" -#include "macro.h" -#include "parse-util.h" -#include "proc-cmdline.h" -#include "process-util.h" -#include "special.h" -#include "string-util.h" -#include "util.h" -#include "virt.h" - -int proc_cmdline(char **ret) { - assert(ret); - - if (detect_container() > 0) - return get_process_cmdline(1, 0, false, ret); - else - return read_one_line_file("/proc/cmdline", ret); -} - -int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value)) { - _cleanup_free_ char *line = NULL; - const char *p; - int r; - - assert(parse_item); - - r = proc_cmdline(&line); - if (r < 0) - return r; - - p = line; - for (;;) { - _cleanup_free_ char *word = NULL; - char *value = NULL; - - r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); - if (r < 0) - return r; - if (r == 0) - break; - - /* Filter out arguments that are intended only for the - * initrd */ - if (!in_initrd() && startswith(word, "rd.")) - continue; - - value = strchr(word, '='); - if (value) - *(value++) = 0; - - r = parse_item(word, value); - if (r < 0) - return r; - } - - return 0; -} - -int get_proc_cmdline_key(const char *key, char **value) { - _cleanup_free_ char *line = NULL, *ret = NULL; - bool found = false; - const char *p; - int r; - - assert(key); - - r = proc_cmdline(&line); - if (r < 0) - return r; - - p = line; - for (;;) { - _cleanup_free_ char *word = NULL; - const char *e; - - r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); - if (r < 0) - return r; - if (r == 0) - break; - - /* Filter out arguments that are intended only for the - * initrd */ - if (!in_initrd() && startswith(word, "rd.")) - continue; - - if (value) { - e = startswith(word, key); - if (!e) - continue; - - r = free_and_strdup(&ret, e); - if (r < 0) - return r; - - found = true; - } else { - if (streq(word, key)) - found = true; - } - } - - if (value) { - *value = ret; - ret = NULL; - } - - return found; - -} - -int shall_restore_state(void) { - _cleanup_free_ char *value = NULL; - int r; - - r = get_proc_cmdline_key("systemd.restore_state=", &value); - if (r < 0) - return r; - if (r == 0) - return true; - - return parse_boolean(value); -} - -static const char * const rlmap[] = { - "emergency", SPECIAL_EMERGENCY_TARGET, - "-b", SPECIAL_EMERGENCY_TARGET, - "rescue", SPECIAL_RESCUE_TARGET, - "single", SPECIAL_RESCUE_TARGET, - "-s", SPECIAL_RESCUE_TARGET, - "s", SPECIAL_RESCUE_TARGET, - "S", SPECIAL_RESCUE_TARGET, - "1", SPECIAL_RESCUE_TARGET, - "2", SPECIAL_MULTI_USER_TARGET, - "3", SPECIAL_MULTI_USER_TARGET, - "4", SPECIAL_MULTI_USER_TARGET, - "5", SPECIAL_GRAPHICAL_TARGET, -}; - -const char* runlevel_to_target(const char *word) { - size_t i; - - if (!word) - return NULL; - - for (i = 0; i < ELEMENTSOF(rlmap); i += 2) - if (streq(word, rlmap[i])) - return rlmap[i+1]; - - return NULL; -} diff --git a/src/basic/proc-cmdline.h b/src/basic/proc-cmdline.h deleted file mode 100644 index 452642a2f5..0000000000 --- a/src/basic/proc-cmdline.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -int proc_cmdline(char **ret); -int parse_proc_cmdline(int (*parse_word)(const char *key, const char *value)); -int get_proc_cmdline_key(const char *parameter, char **value); - -int shall_restore_state(void); -const char* runlevel_to_target(const char *rl); diff --git a/src/basic/process-util.c b/src/basic/process-util.c deleted file mode 100644 index 1ad8816206..0000000000 --- a/src/basic/process-util.c +++ /dev/null @@ -1,782 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_VALGRIND_VALGRIND_H -#include -#endif - -#include "alloc-util.h" -#include "architecture.h" -#include "escape.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "ioprio.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "process-util.h" -#include "signal-util.h" -#include "stat-util.h" -#include "string-table.h" -#include "string-util.h" -#include "user-util.h" -#include "util.h" - -int get_process_state(pid_t pid) { - const char *p; - char state; - int r; - _cleanup_free_ char *line = NULL; - - assert(pid >= 0); - - p = procfs_file_alloca(pid, "stat"); - - r = read_one_line_file(p, &line); - if (r == -ENOENT) - return -ESRCH; - if (r < 0) - return r; - - p = strrchr(line, ')'); - if (!p) - return -EIO; - - p++; - - if (sscanf(p, " %c", &state) != 1) - return -EIO; - - return (unsigned char) state; -} - -int get_process_comm(pid_t pid, char **name) { - const char *p; - int r; - - assert(name); - assert(pid >= 0); - - p = procfs_file_alloca(pid, "comm"); - - r = read_one_line_file(p, name); - if (r == -ENOENT) - return -ESRCH; - - return r; -} - -int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) { - _cleanup_fclose_ FILE *f = NULL; - char *r = NULL, *k; - const char *p; - int c; - - assert(line); - assert(pid >= 0); - - p = procfs_file_alloca(pid, "cmdline"); - - f = fopen(p, "re"); - if (!f) { - if (errno == ENOENT) - return -ESRCH; - return -errno; - } - - if (max_length == 0) { - size_t len = 0, allocated = 0; - - while ((c = getc(f)) != EOF) { - - if (!GREEDY_REALLOC(r, allocated, len+2)) { - free(r); - return -ENOMEM; - } - - r[len++] = isprint(c) ? c : ' '; - } - - if (len > 0) - r[len-1] = 0; - - } else { - bool space = false; - size_t left; - - r = new(char, max_length); - if (!r) - return -ENOMEM; - - k = r; - left = max_length; - while ((c = getc(f)) != EOF) { - - if (isprint(c)) { - if (space) { - if (left <= 4) - break; - - *(k++) = ' '; - left--; - space = false; - } - - if (left <= 4) - break; - - *(k++) = (char) c; - left--; - } else - space = true; - } - - if (left <= 4) { - size_t n = MIN(left-1, 3U); - memcpy(k, "...", n); - k[n] = 0; - } else - *k = 0; - } - - /* Kernel threads have no argv[] */ - if (isempty(r)) { - _cleanup_free_ char *t = NULL; - int h; - - free(r); - - if (!comm_fallback) - return -ENOENT; - - h = get_process_comm(pid, &t); - if (h < 0) - return h; - - r = strjoin("[", t, "]", NULL); - if (!r) - return -ENOMEM; - } - - *line = r; - return 0; -} - -void rename_process(const char name[8]) { - assert(name); - - /* This is a like a poor man's setproctitle(). It changes the - * comm field, argv[0], and also the glibc's internally used - * name of the process. For the first one a limit of 16 chars - * applies, to the second one usually one of 10 (i.e. length - * of "/sbin/init"), to the third one one of 7 (i.e. length of - * "systemd"). If you pass a longer string it will be - * truncated */ - - (void) prctl(PR_SET_NAME, name); - - if (program_invocation_name) - strncpy(program_invocation_name, name, strlen(program_invocation_name)); - - if (saved_argc > 0) { - int i; - - if (saved_argv[0]) - strncpy(saved_argv[0], name, strlen(saved_argv[0])); - - for (i = 1; i < saved_argc; i++) { - if (!saved_argv[i]) - break; - - memzero(saved_argv[i], strlen(saved_argv[i])); - } - } -} - -int is_kernel_thread(pid_t pid) { - const char *p; - size_t count; - char c; - bool eof; - FILE *f; - - if (pid == 0 || pid == 1) /* pid 1, and we ourselves certainly aren't a kernel thread */ - return 0; - - assert(pid > 1); - - p = procfs_file_alloca(pid, "cmdline"); - f = fopen(p, "re"); - if (!f) { - if (errno == ENOENT) - return -ESRCH; - return -errno; - } - - count = fread(&c, 1, 1, f); - eof = feof(f); - fclose(f); - - /* Kernel threads have an empty cmdline */ - - if (count <= 0) - return eof ? 1 : -errno; - - return 0; -} - -int get_process_capeff(pid_t pid, char **capeff) { - const char *p; - int r; - - assert(capeff); - assert(pid >= 0); - - p = procfs_file_alloca(pid, "status"); - - r = get_proc_field(p, "CapEff", WHITESPACE, capeff); - if (r == -ENOENT) - return -ESRCH; - - return r; -} - -static int get_process_link_contents(const char *proc_file, char **name) { - int r; - - assert(proc_file); - assert(name); - - r = readlink_malloc(proc_file, name); - if (r == -ENOENT) - return -ESRCH; - if (r < 0) - return r; - - return 0; -} - -int get_process_exe(pid_t pid, char **name) { - const char *p; - char *d; - int r; - - assert(pid >= 0); - - p = procfs_file_alloca(pid, "exe"); - r = get_process_link_contents(p, name); - if (r < 0) - return r; - - d = endswith(*name, " (deleted)"); - if (d) - *d = '\0'; - - return 0; -} - -static int get_process_id(pid_t pid, const char *field, uid_t *uid) { - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - const char *p; - - assert(field); - assert(uid); - - if (pid == 0) - return getuid(); - - p = procfs_file_alloca(pid, "status"); - f = fopen(p, "re"); - if (!f) { - if (errno == ENOENT) - return -ESRCH; - return -errno; - } - - FOREACH_LINE(line, f, return -errno) { - char *l; - - l = strstrip(line); - - if (startswith(l, field)) { - l += strlen(field); - l += strspn(l, WHITESPACE); - - l[strcspn(l, WHITESPACE)] = 0; - - return parse_uid(l, uid); - } - } - - return -EIO; -} - -int get_process_uid(pid_t pid, uid_t *uid) { - return get_process_id(pid, "Uid:", uid); -} - -int get_process_gid(pid_t pid, gid_t *gid) { - assert_cc(sizeof(uid_t) == sizeof(gid_t)); - return get_process_id(pid, "Gid:", gid); -} - -int get_process_cwd(pid_t pid, char **cwd) { - const char *p; - - assert(pid >= 0); - - p = procfs_file_alloca(pid, "cwd"); - - return get_process_link_contents(p, cwd); -} - -int get_process_root(pid_t pid, char **root) { - const char *p; - - assert(pid >= 0); - - p = procfs_file_alloca(pid, "root"); - - return get_process_link_contents(p, root); -} - -int get_process_environ(pid_t pid, char **env) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *outcome = NULL; - int c; - const char *p; - size_t allocated = 0, sz = 0; - - assert(pid >= 0); - assert(env); - - p = procfs_file_alloca(pid, "environ"); - - f = fopen(p, "re"); - if (!f) { - if (errno == ENOENT) - return -ESRCH; - return -errno; - } - - while ((c = fgetc(f)) != EOF) { - if (!GREEDY_REALLOC(outcome, allocated, sz + 5)) - return -ENOMEM; - - if (c == '\0') - outcome[sz++] = '\n'; - else - sz += cescape_char(c, outcome + sz); - } - - if (!outcome) { - outcome = strdup(""); - if (!outcome) - return -ENOMEM; - } else - outcome[sz] = '\0'; - - *env = outcome; - outcome = NULL; - - return 0; -} - -int get_process_ppid(pid_t pid, pid_t *_ppid) { - int r; - _cleanup_free_ char *line = NULL; - long unsigned ppid; - const char *p; - - assert(pid >= 0); - assert(_ppid); - - if (pid == 0) { - *_ppid = getppid(); - return 0; - } - - p = procfs_file_alloca(pid, "stat"); - r = read_one_line_file(p, &line); - if (r == -ENOENT) - return -ESRCH; - if (r < 0) - return r; - - /* Let's skip the pid and comm fields. The latter is enclosed - * in () but does not escape any () in its value, so let's - * skip over it manually */ - - p = strrchr(line, ')'); - if (!p) - return -EIO; - - p++; - - if (sscanf(p, " " - "%*c " /* state */ - "%lu ", /* ppid */ - &ppid) != 1) - return -EIO; - - if ((long unsigned) (pid_t) ppid != ppid) - return -ERANGE; - - *_ppid = (pid_t) ppid; - - return 0; -} - -int wait_for_terminate(pid_t pid, siginfo_t *status) { - siginfo_t dummy; - - assert(pid >= 1); - - if (!status) - status = &dummy; - - for (;;) { - zero(*status); - - if (waitid(P_PID, pid, status, WEXITED) < 0) { - - if (errno == EINTR) - continue; - - return -errno; - } - - return 0; - } -} - -/* - * Return values: - * < 0 : wait_for_terminate() failed to get the state of the - * process, the process was terminated by a signal, or - * failed for an unknown reason. - * >=0 : The process terminated normally, and its exit code is - * returned. - * - * That is, success is indicated by a return value of zero, and an - * error is indicated by a non-zero value. - * - * A warning is emitted if the process terminates abnormally, - * and also if it returns non-zero unless check_exit_code is true. - */ -int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_code) { - int r; - siginfo_t status; - - assert(name); - assert(pid > 1); - - r = wait_for_terminate(pid, &status); - if (r < 0) - return log_warning_errno(r, "Failed to wait for %s: %m", name); - - if (status.si_code == CLD_EXITED) { - if (status.si_status != 0) - log_full(check_exit_code ? LOG_WARNING : LOG_DEBUG, - "%s failed with error code %i.", name, status.si_status); - else - log_debug("%s succeeded.", name); - - return status.si_status; - } else if (status.si_code == CLD_KILLED || - status.si_code == CLD_DUMPED) { - - log_warning("%s terminated by signal %s.", name, signal_to_string(status.si_status)); - return -EPROTO; - } - - log_warning("%s failed due to unknown reason.", name); - return -EPROTO; -} - -void sigkill_wait(pid_t pid) { - assert(pid > 1); - - if (kill(pid, SIGKILL) > 0) - (void) wait_for_terminate(pid, NULL); -} - -void sigkill_waitp(pid_t *pid) { - if (!pid) - return; - if (*pid <= 1) - return; - - sigkill_wait(*pid); -} - -int kill_and_sigcont(pid_t pid, int sig) { - int r; - - r = kill(pid, sig) < 0 ? -errno : 0; - - if (r >= 0) - kill(pid, SIGCONT); - - return r; -} - -int getenv_for_pid(pid_t pid, const char *field, char **_value) { - _cleanup_fclose_ FILE *f = NULL; - char *value = NULL; - int r; - bool done = false; - size_t l; - const char *path; - - assert(pid >= 0); - assert(field); - assert(_value); - - path = procfs_file_alloca(pid, "environ"); - - f = fopen(path, "re"); - if (!f) { - if (errno == ENOENT) - return -ESRCH; - return -errno; - } - - l = strlen(field); - r = 0; - - do { - char line[LINE_MAX]; - unsigned i; - - for (i = 0; i < sizeof(line)-1; i++) { - int c; - - c = getc(f); - if (_unlikely_(c == EOF)) { - done = true; - break; - } else if (c == 0) - break; - - line[i] = c; - } - line[i] = 0; - - if (memcmp(line, field, l) == 0 && line[l] == '=') { - value = strdup(line + l + 1); - if (!value) - return -ENOMEM; - - r = 1; - break; - } - - } while (!done); - - *_value = value; - return r; -} - -bool pid_is_unwaited(pid_t pid) { - /* Checks whether a PID is still valid at all, including a zombie */ - - if (pid < 0) - return false; - - if (pid <= 1) /* If we or PID 1 would be dead and have been waited for, this code would not be running */ - return true; - - if (kill(pid, 0) >= 0) - return true; - - return errno != ESRCH; -} - -bool pid_is_alive(pid_t pid) { - int r; - - /* Checks whether a PID is still valid and not a zombie */ - - if (pid < 0) - return false; - - if (pid <= 1) /* If we or PID 1 would be a zombie, this code would not be running */ - return true; - - r = get_process_state(pid); - if (r == -ESRCH || r == 'Z') - return false; - - return true; -} - -int pid_from_same_root_fs(pid_t pid) { - const char *root; - - if (pid < 0) - return 0; - - root = procfs_file_alloca(pid, "root"); - - return files_same(root, "/proc/1/root"); -} - -bool is_main_thread(void) { - static thread_local int cached = 0; - - if (_unlikely_(cached == 0)) - cached = getpid() == gettid() ? 1 : -1; - - return cached > 0; -} - -noreturn void freeze(void) { - - log_close(); - - /* Make sure nobody waits for us on a socket anymore */ - close_all_fds(NULL, 0); - - sync(); - - for (;;) - pause(); -} - -bool oom_score_adjust_is_valid(int oa) { - return oa >= OOM_SCORE_ADJ_MIN && oa <= OOM_SCORE_ADJ_MAX; -} - -unsigned long personality_from_string(const char *p) { - int architecture; - - if (!p) - return PERSONALITY_INVALID; - - /* Parse a personality specifier. We use our own identifiers that indicate specific ABIs, rather than just - * hints regarding the register size, since we want to keep things open for multiple locally supported ABIs for - * the same register size. */ - - architecture = architecture_from_string(p); - if (architecture < 0) - return PERSONALITY_INVALID; - - if (architecture == native_architecture()) - return PER_LINUX; -#ifdef SECONDARY_ARCHITECTURE - if (architecture == SECONDARY_ARCHITECTURE) - return PER_LINUX32; -#endif - - return PERSONALITY_INVALID; -} - -const char* personality_to_string(unsigned long p) { - int architecture = _ARCHITECTURE_INVALID; - - if (p == PER_LINUX) - architecture = native_architecture(); -#ifdef SECONDARY_ARCHITECTURE - else if (p == PER_LINUX32) - architecture = SECONDARY_ARCHITECTURE; -#endif - - if (architecture < 0) - return NULL; - - return architecture_to_string(architecture); -} - -void valgrind_summary_hack(void) { -#ifdef HAVE_VALGRIND_VALGRIND_H - if (getpid() == 1 && RUNNING_ON_VALGRIND) { - pid_t pid; - pid = raw_clone(SIGCHLD, NULL); - if (pid < 0) - log_emergency_errno(errno, "Failed to fork off valgrind helper: %m"); - else if (pid == 0) - exit(EXIT_SUCCESS); - else { - log_info("Spawned valgrind helper as PID "PID_FMT".", pid); - (void) wait_for_terminate(pid, NULL); - } - } -#endif -} - -int pid_compare_func(const void *a, const void *b) { - const pid_t *p = a, *q = b; - - /* Suitable for usage in qsort() */ - - if (*p < *q) - return -1; - if (*p > *q) - return 1; - return 0; -} - -static const char *const ioprio_class_table[] = { - [IOPRIO_CLASS_NONE] = "none", - [IOPRIO_CLASS_RT] = "realtime", - [IOPRIO_CLASS_BE] = "best-effort", - [IOPRIO_CLASS_IDLE] = "idle" -}; - -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ioprio_class, int, INT_MAX); - -static const char *const sigchld_code_table[] = { - [CLD_EXITED] = "exited", - [CLD_KILLED] = "killed", - [CLD_DUMPED] = "dumped", - [CLD_TRAPPED] = "trapped", - [CLD_STOPPED] = "stopped", - [CLD_CONTINUED] = "continued", -}; - -DEFINE_STRING_TABLE_LOOKUP(sigchld_code, int); - -static const char* const sched_policy_table[] = { - [SCHED_OTHER] = "other", - [SCHED_BATCH] = "batch", - [SCHED_IDLE] = "idle", - [SCHED_FIFO] = "fifo", - [SCHED_RR] = "rr" -}; - -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(sched_policy, int, INT_MAX); diff --git a/src/basic/process-util.h b/src/basic/process-util.h deleted file mode 100644 index 9f75088796..0000000000 --- a/src/basic/process-util.h +++ /dev/null @@ -1,105 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include - -#include "formats-util.h" -#include "macro.h" - -#define procfs_file_alloca(pid, field) \ - ({ \ - pid_t _pid_ = (pid); \ - const char *_r_; \ - if (_pid_ == 0) { \ - _r_ = ("/proc/self/" field); \ - } else { \ - _r_ = alloca(strlen("/proc/") + DECIMAL_STR_MAX(pid_t) + 1 + sizeof(field)); \ - sprintf((char*) _r_, "/proc/"PID_FMT"/" field, _pid_); \ - } \ - _r_; \ - }) - -int get_process_state(pid_t pid); -int get_process_comm(pid_t pid, char **name); -int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line); -int get_process_exe(pid_t pid, char **name); -int get_process_uid(pid_t pid, uid_t *uid); -int get_process_gid(pid_t pid, gid_t *gid); -int get_process_capeff(pid_t pid, char **capeff); -int get_process_cwd(pid_t pid, char **cwd); -int get_process_root(pid_t pid, char **root); -int get_process_environ(pid_t pid, char **environ); -int get_process_ppid(pid_t pid, pid_t *ppid); - -int wait_for_terminate(pid_t pid, siginfo_t *status); -int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_code); - -void sigkill_wait(pid_t pid); -void sigkill_waitp(pid_t *pid); - -int kill_and_sigcont(pid_t pid, int sig); - -void rename_process(const char name[8]); -int is_kernel_thread(pid_t pid); - -int getenv_for_pid(pid_t pid, const char *field, char **_value); - -bool pid_is_alive(pid_t pid); -bool pid_is_unwaited(pid_t pid); -int pid_from_same_root_fs(pid_t pid); - -bool is_main_thread(void); - -noreturn void freeze(void); - -bool oom_score_adjust_is_valid(int oa); - -#ifndef PERSONALITY_INVALID -/* personality(7) documents that 0xffffffffUL is used for querying the - * current personality, hence let's use that here as error - * indicator. */ -#define PERSONALITY_INVALID 0xffffffffLU -#endif - -unsigned long personality_from_string(const char *p); -const char *personality_to_string(unsigned long); - -int ioprio_class_to_string_alloc(int i, char **s); -int ioprio_class_from_string(const char *s); - -const char *sigchld_code_to_string(int i) _const_; -int sigchld_code_from_string(const char *s) _pure_; - -int sched_policy_to_string_alloc(int i, char **s); -int sched_policy_from_string(const char *s); - -#define PTR_TO_PID(p) ((pid_t) ((uintptr_t) p)) -#define PID_TO_PTR(p) ((void*) ((uintptr_t) p)) - -void valgrind_summary_hack(void); - -int pid_compare_func(const void *a, const void *b); diff --git a/src/basic/random-util.c b/src/basic/random-util.c deleted file mode 100644 index 2f468db770..0000000000 --- a/src/basic/random-util.c +++ /dev/null @@ -1,133 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_SYS_AUXV_H -#include -#endif - -#include "fd-util.h" -#include "io-util.h" -#include "missing.h" -#include "random-util.h" -#include "time-util.h" - -int dev_urandom(void *p, size_t n) { - static int have_syscall = -1; - - _cleanup_close_ int fd = -1; - int r; - - /* Gathers some randomness from the kernel. This call will - * never block, and will always return some data from the - * kernel, regardless if the random pool is fully initialized - * or not. It thus makes no guarantee for the quality of the - * returned entropy, but is good enough for or usual usecases - * of seeding the hash functions for hashtable */ - - /* Use the getrandom() syscall unless we know we don't have - * it, or when the requested size is too large for it. */ - if (have_syscall != 0 || (size_t) (int) n != n) { - r = getrandom(p, n, GRND_NONBLOCK); - if (r == (int) n) { - have_syscall = true; - return 0; - } - - if (r < 0) { - if (errno == ENOSYS) - /* we lack the syscall, continue with - * reading from /dev/urandom */ - have_syscall = false; - else if (errno == EAGAIN) - /* not enough entropy for now. Let's - * remember to use the syscall the - * next time, again, but also read - * from /dev/urandom for now, which - * doesn't care about the current - * amount of entropy. */ - have_syscall = true; - else - return -errno; - } else - /* too short read? */ - return -ENODATA; - } - - fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return errno == ENOENT ? -ENOSYS : -errno; - - return loop_read_exact(fd, p, n, true); -} - -void initialize_srand(void) { - static bool srand_called = false; - unsigned x; -#ifdef HAVE_SYS_AUXV_H - void *auxv; -#endif - - if (srand_called) - return; - -#ifdef HAVE_SYS_AUXV_H - /* The kernel provides us with 16 bytes of entropy in auxv, so let's try to make use of that to seed the - * pseudo-random generator. It's better than nothing... */ - - auxv = (void*) getauxval(AT_RANDOM); - if (auxv) { - assert_cc(sizeof(x) < 16); - memcpy(&x, auxv, sizeof(x)); - } else -#endif - x = 0; - - - x ^= (unsigned) now(CLOCK_REALTIME); - x ^= (unsigned) gettid(); - - srand(x); - srand_called = true; -} - -void random_bytes(void *p, size_t n) { - uint8_t *q; - int r; - - r = dev_urandom(p, n); - if (r >= 0) - return; - - /* If some idiot made /dev/urandom unavailable to us, he'll - * get a PRNG instead. */ - - initialize_srand(); - - for (q = p; q < (uint8_t*) p + n; q ++) - *q = rand(); -} diff --git a/src/basic/random-util.h b/src/basic/random-util.h deleted file mode 100644 index 3cee4c5014..0000000000 --- a/src/basic/random-util.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 - -int dev_urandom(void *p, size_t n); -void random_bytes(void *p, size_t n); -void initialize_srand(void); - -static inline uint64_t random_u64(void) { - uint64_t u; - random_bytes(&u, sizeof(u)); - return u; -} - -static inline uint32_t random_u32(void) { - uint32_t u; - random_bytes(&u, sizeof(u)); - return u; -} diff --git a/src/basic/ratelimit.c b/src/basic/ratelimit.c deleted file mode 100644 index 3ca5625e4d..0000000000 --- a/src/basic/ratelimit.c +++ /dev/null @@ -1,56 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 "macro.h" -#include "ratelimit.h" - -/* Modelled after Linux' lib/ratelimit.c by Dave Young - * , which is licensed GPLv2. */ - -bool ratelimit_test(RateLimit *r) { - usec_t ts; - - assert(r); - - if (r->interval <= 0 || r->burst <= 0) - return true; - - ts = now(CLOCK_MONOTONIC); - - if (r->begin <= 0 || - r->begin + r->interval < ts) { - r->begin = ts; - - /* Reset counter */ - r->num = 0; - goto good; - } - - if (r->num < r->burst) - goto good; - - return false; - -good: - r->num++; - return true; -} diff --git a/src/basic/ratelimit.h b/src/basic/ratelimit.h deleted file mode 100644 index 9c8dddf5ad..0000000000 --- a/src/basic/ratelimit.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "time-util.h" -#include "util.h" - -typedef struct RateLimit { - usec_t interval; - usec_t begin; - unsigned burst; - unsigned num; -} RateLimit; - -#define RATELIMIT_DEFINE(_name, _interval, _burst) \ - RateLimit _name = { \ - .interval = (_interval), \ - .burst = (_burst), \ - .num = 0, \ - .begin = 0 \ - } - -#define RATELIMIT_INIT(v, _interval, _burst) \ - do { \ - RateLimit *_r = &(v); \ - _r->interval = (_interval); \ - _r->burst = (_burst); \ - _r->num = 0; \ - _r->begin = 0; \ - } while (false) - -#define RATELIMIT_RESET(v) \ - do { \ - RateLimit *_r = &(v); \ - _r->num = 0; \ - _r->begin = 0; \ - } while (false) - -bool ratelimit_test(RateLimit *r); diff --git a/src/basic/refcnt.h b/src/basic/refcnt.h deleted file mode 100644 index 1d77a6445a..0000000000 --- a/src/basic/refcnt.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -/* A type-safe atomic refcounter. - * - * DO NOT USE THIS UNLESS YOU ACTUALLY CARE ABOUT THREAD SAFETY! */ - -typedef struct { - volatile unsigned _value; -} RefCount; - -#define REFCNT_GET(r) ((r)._value) -#define REFCNT_INC(r) (__sync_add_and_fetch(&(r)._value, 1)) -#define REFCNT_DEC(r) (__sync_sub_and_fetch(&(r)._value, 1)) - -#define REFCNT_INIT ((RefCount) { ._value = 1 }) diff --git a/src/basic/replace-var.c b/src/basic/replace-var.c deleted file mode 100644 index 6a204b9ec3..0000000000 --- a/src/basic/replace-var.c +++ /dev/null @@ -1,112 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 -#include -#include - -#include "alloc-util.h" -#include "macro.h" -#include "replace-var.h" -#include "string-util.h" - -/* - * Generic infrastructure for replacing @FOO@ style variables in - * strings. Will call a callback for each replacement. - */ - -static int get_variable(const char *b, char **r) { - size_t k; - char *t; - - assert(b); - assert(r); - - if (*b != '@') - return 0; - - k = strspn(b + 1, UPPERCASE_LETTERS "_"); - if (k <= 0 || b[k+1] != '@') - return 0; - - t = strndup(b + 1, k); - if (!t) - return -ENOMEM; - - *r = t; - return 1; -} - -char *replace_var(const char *text, char *(*lookup)(const char *variable, void*userdata), void *userdata) { - char *r, *t; - const char *f; - size_t l; - - assert(text); - assert(lookup); - - l = strlen(text); - r = new(char, l+1); - if (!r) - return NULL; - - f = text; - t = r; - while (*f) { - _cleanup_free_ char *v = NULL, *n = NULL; - char *a; - int k; - size_t skip, d, nl; - - k = get_variable(f, &v); - if (k < 0) - goto oom; - if (k == 0) { - *(t++) = *(f++); - continue; - } - - n = lookup(v, userdata); - if (!n) - goto oom; - - skip = strlen(v) + 2; - - d = t - r; - nl = l - skip + strlen(n); - a = realloc(r, nl + 1); - if (!a) - goto oom; - - l = nl; - r = a; - t = r + d; - - t = stpcpy(t, n); - f += skip; - } - - *t = 0; - return r; - -oom: - free(r); - return NULL; -} diff --git a/src/basic/replace-var.h b/src/basic/replace-var.h deleted file mode 100644 index 78412910b2..0000000000 --- a/src/basic/replace-var.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -char *replace_var(const char *text, char *(*lookup)(const char *variable, void*userdata), void *userdata); diff --git a/src/basic/rlimit-util.c b/src/basic/rlimit-util.c deleted file mode 100644 index ee063720ed..0000000000 --- a/src/basic/rlimit-util.c +++ /dev/null @@ -1,321 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 - -#include "alloc-util.h" -#include "extract-word.h" -#include "formats-util.h" -#include "macro.h" -#include "missing.h" -#include "rlimit-util.h" -#include "string-table.h" -#include "time-util.h" - -int setrlimit_closest(int resource, const struct rlimit *rlim) { - struct rlimit highest, fixed; - - assert(rlim); - - if (setrlimit(resource, rlim) >= 0) - return 0; - - if (errno != EPERM) - return -errno; - - /* So we failed to set the desired setrlimit, then let's try - * to get as close as we can */ - assert_se(getrlimit(resource, &highest) == 0); - - fixed.rlim_cur = MIN(rlim->rlim_cur, highest.rlim_max); - fixed.rlim_max = MIN(rlim->rlim_max, highest.rlim_max); - - if (setrlimit(resource, &fixed) < 0) - return -errno; - - return 0; -} - -static int rlimit_parse_u64(const char *val, rlim_t *ret) { - uint64_t u; - int r; - - assert(val); - assert(ret); - - if (streq(val, "infinity")) { - *ret = RLIM_INFINITY; - return 0; - } - - /* setrlimit(2) suggests rlim_t is always 64bit on Linux. */ - assert_cc(sizeof(rlim_t) == sizeof(uint64_t)); - - r = safe_atou64(val, &u); - if (r < 0) - return r; - if (u >= (uint64_t) RLIM_INFINITY) - return -ERANGE; - - *ret = (rlim_t) u; - return 0; -} - -static int rlimit_parse_size(const char *val, rlim_t *ret) { - uint64_t u; - int r; - - assert(val); - assert(ret); - - if (streq(val, "infinity")) { - *ret = RLIM_INFINITY; - return 0; - } - - r = parse_size(val, 1024, &u); - if (r < 0) - return r; - if (u >= (uint64_t) RLIM_INFINITY) - return -ERANGE; - - *ret = (rlim_t) u; - return 0; -} - -static int rlimit_parse_sec(const char *val, rlim_t *ret) { - uint64_t u; - usec_t t; - int r; - - assert(val); - assert(ret); - - if (streq(val, "infinity")) { - *ret = RLIM_INFINITY; - return 0; - } - - r = parse_sec(val, &t); - if (r < 0) - return r; - if (t == USEC_INFINITY) { - *ret = RLIM_INFINITY; - return 0; - } - - u = (uint64_t) DIV_ROUND_UP(t, USEC_PER_SEC); - if (u >= (uint64_t) RLIM_INFINITY) - return -ERANGE; - - *ret = (rlim_t) u; - return 0; -} - -static int rlimit_parse_usec(const char *val, rlim_t *ret) { - usec_t t; - int r; - - assert(val); - assert(ret); - - if (streq(val, "infinity")) { - *ret = RLIM_INFINITY; - return 0; - } - - r = parse_time(val, &t, 1); - if (r < 0) - return r; - if (t == USEC_INFINITY) { - *ret = RLIM_INFINITY; - return 0; - } - - *ret = (rlim_t) t; - return 0; -} - -static int rlimit_parse_nice(const char *val, rlim_t *ret) { - uint64_t rl; - int r; - - /* So, Linux is weird. The range for RLIMIT_NICE is 40..1, mapping to the nice levels -20..19. However, the - * RLIMIT_NICE limit defaults to 0 by the kernel, i.e. a value that maps to nice level 20, which of course is - * bogus and does not exist. In order to permit parsing the RLIMIT_NICE of 0 here we hence implement a slight - * asymmetry: when parsing as positive nice level we permit 0..19. When parsing as negative nice level, we - * permit -20..0. But when parsing as raw resource limit value then we also allow the special value 0. - * - * Yeah, Linux is quality engineering sometimes... */ - - if (val[0] == '+') { - - /* Prefixed with "+": Parse as positive user-friendly nice value */ - r = safe_atou64(val + 1, &rl); - if (r < 0) - return r; - - if (rl >= PRIO_MAX) - return -ERANGE; - - rl = 20 - rl; - - } else if (val[0] == '-') { - - /* Prefixed with "-": Parse as negative user-friendly nice value */ - r = safe_atou64(val + 1, &rl); - if (r < 0) - return r; - - if (rl > (uint64_t) (-PRIO_MIN)) - return -ERANGE; - - rl = 20 + rl; - } else { - - /* Not prefixed: parse as raw resource limit value */ - r = safe_atou64(val, &rl); - if (r < 0) - return r; - - if (rl > (uint64_t) (20 - PRIO_MIN)) - return -ERANGE; - } - - *ret = (rlim_t) rl; - return 0; -} - -static int (*const rlimit_parse_table[_RLIMIT_MAX])(const char *val, rlim_t *ret) = { - [RLIMIT_CPU] = rlimit_parse_sec, - [RLIMIT_FSIZE] = rlimit_parse_size, - [RLIMIT_DATA] = rlimit_parse_size, - [RLIMIT_STACK] = rlimit_parse_size, - [RLIMIT_CORE] = rlimit_parse_size, - [RLIMIT_RSS] = rlimit_parse_size, - [RLIMIT_NOFILE] = rlimit_parse_u64, - [RLIMIT_AS] = rlimit_parse_size, - [RLIMIT_NPROC] = rlimit_parse_u64, - [RLIMIT_MEMLOCK] = rlimit_parse_size, - [RLIMIT_LOCKS] = rlimit_parse_u64, - [RLIMIT_SIGPENDING] = rlimit_parse_u64, - [RLIMIT_MSGQUEUE] = rlimit_parse_size, - [RLIMIT_NICE] = rlimit_parse_nice, - [RLIMIT_RTPRIO] = rlimit_parse_u64, - [RLIMIT_RTTIME] = rlimit_parse_usec, -}; - -int rlimit_parse_one(int resource, const char *val, rlim_t *ret) { - assert(val); - assert(ret); - - if (resource < 0) - return -EINVAL; - if (resource >= _RLIMIT_MAX) - return -EINVAL; - - return rlimit_parse_table[resource](val, ret); -} - -int rlimit_parse(int resource, const char *val, struct rlimit *ret) { - _cleanup_free_ char *hard = NULL, *soft = NULL; - rlim_t hl, sl; - int r; - - assert(val); - assert(ret); - - r = extract_first_word(&val, &soft, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - r = rlimit_parse_one(resource, soft, &sl); - if (r < 0) - return r; - - r = extract_first_word(&val, &hard, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r < 0) - return r; - if (!isempty(val)) - return -EINVAL; - if (r == 0) - hl = sl; - else { - r = rlimit_parse_one(resource, hard, &hl); - if (r < 0) - return r; - if (sl > hl) - return -EILSEQ; - } - - *ret = (struct rlimit) { - .rlim_cur = sl, - .rlim_max = hl, - }; - - return 0; -} - -int rlimit_format(const struct rlimit *rl, char **ret) { - char *s = NULL; - - assert(rl); - assert(ret); - - if (rl->rlim_cur >= RLIM_INFINITY && rl->rlim_max >= RLIM_INFINITY) - s = strdup("infinity"); - else if (rl->rlim_cur >= RLIM_INFINITY) - (void) asprintf(&s, "infinity:" RLIM_FMT, rl->rlim_max); - else if (rl->rlim_max >= RLIM_INFINITY) - (void) asprintf(&s, RLIM_FMT ":infinity", rl->rlim_cur); - else if (rl->rlim_cur == rl->rlim_max) - (void) asprintf(&s, RLIM_FMT, rl->rlim_cur); - else - (void) asprintf(&s, RLIM_FMT ":" RLIM_FMT, rl->rlim_cur, rl->rlim_max); - - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - -static const char* const rlimit_table[_RLIMIT_MAX] = { - [RLIMIT_CPU] = "LimitCPU", - [RLIMIT_FSIZE] = "LimitFSIZE", - [RLIMIT_DATA] = "LimitDATA", - [RLIMIT_STACK] = "LimitSTACK", - [RLIMIT_CORE] = "LimitCORE", - [RLIMIT_RSS] = "LimitRSS", - [RLIMIT_NOFILE] = "LimitNOFILE", - [RLIMIT_AS] = "LimitAS", - [RLIMIT_NPROC] = "LimitNPROC", - [RLIMIT_MEMLOCK] = "LimitMEMLOCK", - [RLIMIT_LOCKS] = "LimitLOCKS", - [RLIMIT_SIGPENDING] = "LimitSIGPENDING", - [RLIMIT_MSGQUEUE] = "LimitMSGQUEUE", - [RLIMIT_NICE] = "LimitNICE", - [RLIMIT_RTPRIO] = "LimitRTPRIO", - [RLIMIT_RTTIME] = "LimitRTTIME" -}; - -DEFINE_STRING_TABLE_LOOKUP(rlimit, int); diff --git a/src/basic/rlimit-util.h b/src/basic/rlimit-util.h deleted file mode 100644 index d4594eccd6..0000000000 --- a/src/basic/rlimit-util.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "macro.h" - -const char *rlimit_to_string(int i) _const_; -int rlimit_from_string(const char *s) _pure_; - -int setrlimit_closest(int resource, const struct rlimit *rlim); - -int rlimit_parse_one(int resource, const char *val, rlim_t *ret); -int rlimit_parse(int resource, const char *val, struct rlimit *ret); - -int rlimit_format(const struct rlimit *rl, char **ret); - -#define RLIMIT_MAKE_CONST(lim) ((struct rlimit) { lim, lim }) diff --git a/src/basic/rm-rf.c b/src/basic/rm-rf.c deleted file mode 100644 index 43816fd1bb..0000000000 --- a/src/basic/rm-rf.c +++ /dev/null @@ -1,236 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 -#include -#include -#include -#include -#include -#include - -#include "btrfs-util.h" -#include "fd-util.h" -#include "log.h" -#include "macro.h" -#include "mount-util.h" -#include "path-util.h" -#include "rm-rf.h" -#include "stat-util.h" -#include "string-util.h" - -int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { - _cleanup_closedir_ DIR *d = NULL; - int ret = 0, r; - - assert(fd >= 0); - - /* This returns the first error we run into, but nevertheless - * tries to go on. This closes the passed fd. */ - - if (!(flags & REMOVE_PHYSICAL)) { - - r = fd_is_temporary_fs(fd); - if (r < 0) { - safe_close(fd); - return r; - } - - if (!r) { - /* We refuse to clean physical file systems - * with this call, unless explicitly - * requested. This is extra paranoia just to - * be sure we never ever remove non-state - * data */ - - log_error("Attempted to remove disk file system, and we can't allow that."); - safe_close(fd); - return -EPERM; - } - } - - d = fdopendir(fd); - if (!d) { - safe_close(fd); - return errno == ENOENT ? 0 : -errno; - } - - for (;;) { - struct dirent *de; - bool is_dir; - struct stat st; - - errno = 0; - de = readdir(d); - if (!de) { - if (errno > 0 && ret == 0) - ret = -errno; - return ret; - } - - if (streq(de->d_name, ".") || streq(de->d_name, "..")) - continue; - - if (de->d_type == DT_UNKNOWN || - (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) { - if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; - continue; - } - - is_dir = S_ISDIR(st.st_mode); - } else - is_dir = de->d_type == DT_DIR; - - if (is_dir) { - int subdir_fd; - - /* if root_dev is set, remove subdirectories only if device is same */ - if (root_dev && st.st_dev != root_dev->st_dev) - continue; - - subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); - if (subdir_fd < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; - continue; - } - - /* Stop at mount points */ - r = fd_is_mount_point(fd, de->d_name, 0); - if (r < 0) { - if (ret == 0 && r != -ENOENT) - ret = r; - - safe_close(subdir_fd); - continue; - } - if (r) { - safe_close(subdir_fd); - continue; - } - - if ((flags & REMOVE_SUBVOLUME) && st.st_ino == 256) { - - /* This could be a subvolume, try to remove it */ - - r = btrfs_subvol_remove_fd(fd, de->d_name, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); - if (r < 0) { - if (r != -ENOTTY && r != -EINVAL) { - if (ret == 0) - ret = r; - - safe_close(subdir_fd); - continue; - } - - /* ENOTTY, then it wasn't a - * btrfs subvolume, continue - * below. */ - } else { - /* It was a subvolume, continue. */ - safe_close(subdir_fd); - continue; - } - } - - /* We pass REMOVE_PHYSICAL here, to avoid - * doing the fstatfs() to check the file - * system type again for each directory */ - r = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev); - if (r < 0 && ret == 0) - ret = r; - - if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; - } - - } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) { - - if (unlinkat(fd, de->d_name, 0) < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; - } - } - } -} - -int rm_rf(const char *path, RemoveFlags flags) { - int fd, r; - struct statfs s; - - assert(path); - - /* We refuse to clean the root file system with this - * call. This is extra paranoia to never cause a really - * seriously broken system. */ - if (path_equal(path, "/")) { - log_error("Attempted to remove entire root file system, and we can't allow that."); - return -EPERM; - } - - if ((flags & (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) == (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) { - /* Try to remove as subvolume first */ - r = btrfs_subvol_remove(path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); - if (r >= 0) - return r; - - if (r != -ENOTTY && r != -EINVAL && r != -ENOTDIR) - return r; - - /* Not btrfs or not a subvolume */ - } - - fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); - if (fd < 0) { - - if (errno != ENOTDIR && errno != ELOOP) - return -errno; - - if (!(flags & REMOVE_PHYSICAL)) { - if (statfs(path, &s) < 0) - return -errno; - - if (!is_temporary_fs(&s)) { - log_error("Attempted to remove disk file system, and we can't allow that."); - return -EPERM; - } - } - - if ((flags & REMOVE_ROOT) && !(flags & REMOVE_ONLY_DIRECTORIES)) - if (unlink(path) < 0 && errno != ENOENT) - return -errno; - - return 0; - } - - r = rm_rf_children(fd, flags, NULL); - - if (flags & REMOVE_ROOT) { - if (rmdir(path) < 0) { - if (r == 0 && errno != ENOENT) - r = -errno; - } - } - - return r; -} diff --git a/src/basic/rm-rf.h b/src/basic/rm-rf.h deleted file mode 100644 index f693a5bb7c..0000000000 --- a/src/basic/rm-rf.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 - -typedef enum RemoveFlags { - REMOVE_ONLY_DIRECTORIES = 1, - REMOVE_ROOT = 2, - REMOVE_PHYSICAL = 4, /* if not set, only removes files on tmpfs, never physical file systems */ - REMOVE_SUBVOLUME = 8, -} RemoveFlags; - -int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev); -int rm_rf(const char *path, RemoveFlags flags); - -/* Useful for usage with _cleanup_(), destroys a directory and frees the pointer */ -static inline void rm_rf_physical_and_free(char *p) { - if (!p) - return; - (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); - free(p); -} -DEFINE_TRIVIAL_CLEANUP_FUNC(char*, rm_rf_physical_and_free); diff --git a/src/basic/securebits.h b/src/basic/securebits.h deleted file mode 100644 index 98fbe0d433..0000000000 --- a/src/basic/securebits.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef _LINUX_SECUREBITS_H -#define _LINUX_SECUREBITS_H 1 - -/* This is minimal version of Linux' linux/securebits.h header file, - * which is licensed GPL2 */ - -#define SECUREBITS_DEFAULT 0x00000000 - -/* When set UID 0 has no special privileges. When unset, we support - inheritance of root-permissions and suid-root executable under - compatibility mode. We raise the effective and inheritable bitmasks - *of the executable file* if the effective uid of the new process is - 0. If the real uid is 0, we raise the effective (legacy) bit of the - executable file. */ -#define SECURE_NOROOT 0 -#define SECURE_NOROOT_LOCKED 1 /* make bit-0 immutable */ - -/* When set, setuid to/from uid 0 does not trigger capability-"fixup". - When unset, to provide compatibility with old programs relying on - set*uid to gain/lose privilege, transitions to/from uid 0 cause - capabilities to be gained/lost. */ -#define SECURE_NO_SETUID_FIXUP 2 -#define SECURE_NO_SETUID_FIXUP_LOCKED 3 /* make bit-2 immutable */ - -/* When set, a process can retain its capabilities even after - transitioning to a non-root user (the set-uid fixup suppressed by - bit 2). Bit-4 is cleared when a process calls exec(); setting both - bit 4 and 5 will create a barrier through exec that no exec()'d - child can use this feature again. */ -#define SECURE_KEEP_CAPS 4 -#define SECURE_KEEP_CAPS_LOCKED 5 /* make bit-4 immutable */ - -/* Each securesetting is implemented using two bits. One bit specifies - whether the setting is on or off. The other bit specify whether the - setting is locked or not. A setting which is locked cannot be - changed from user-level. */ -#define issecure_mask(X) (1 << (X)) -#define issecure(X) (issecure_mask(X) & current_cred_xxx(securebits)) - -#define SECURE_ALL_BITS (issecure_mask(SECURE_NOROOT) | \ - issecure_mask(SECURE_NO_SETUID_FIXUP) | \ - issecure_mask(SECURE_KEEP_CAPS)) -#define SECURE_ALL_LOCKS (SECURE_ALL_BITS << 1) - -#endif /* !_LINUX_SECUREBITS_H */ diff --git a/src/basic/selinux-util.c b/src/basic/selinux-util.c deleted file mode 100644 index 10c2f39369..0000000000 --- a/src/basic/selinux-util.c +++ /dev/null @@ -1,485 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_SELINUX -#include -#include -#include -#endif - -#include "alloc-util.h" -#include "log.h" -#include "macro.h" -#include "path-util.h" -#include "selinux-util.h" -#include "time-util.h" -#include "util.h" - -#ifdef HAVE_SELINUX -DEFINE_TRIVIAL_CLEANUP_FUNC(security_context_t, freecon); -DEFINE_TRIVIAL_CLEANUP_FUNC(context_t, context_free); - -#define _cleanup_security_context_free_ _cleanup_(freeconp) -#define _cleanup_context_free_ _cleanup_(context_freep) - -static int cached_use = -1; -static struct selabel_handle *label_hnd = NULL; - -#define log_enforcing(...) log_full(security_getenforce() == 1 ? LOG_ERR : LOG_DEBUG, __VA_ARGS__) -#endif - -bool mac_selinux_have(void) { -#ifdef HAVE_SELINUX - if (cached_use < 0) - cached_use = is_selinux_enabled() > 0; - - return cached_use; -#else - return false; -#endif -} - -bool mac_selinux_use(void) { - if (!mac_selinux_have()) - return false; - - /* Never try to configure SELinux features if we aren't - * root */ - - return getuid() == 0; -} - -void mac_selinux_retest(void) { -#ifdef HAVE_SELINUX - cached_use = -1; -#endif -} - -int mac_selinux_init(void) { - int r = 0; - -#ifdef HAVE_SELINUX - usec_t before_timestamp, after_timestamp; - struct mallinfo before_mallinfo, after_mallinfo; - - if (label_hnd) - return 0; - - if (!mac_selinux_use()) - return 0; - - before_mallinfo = mallinfo(); - before_timestamp = now(CLOCK_MONOTONIC); - - label_hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0); - if (!label_hnd) { - log_enforcing("Failed to initialize SELinux context: %m"); - r = security_getenforce() == 1 ? -errno : 0; - } else { - char timespan[FORMAT_TIMESPAN_MAX]; - int l; - - after_timestamp = now(CLOCK_MONOTONIC); - after_mallinfo = mallinfo(); - - l = after_mallinfo.uordblks > before_mallinfo.uordblks ? after_mallinfo.uordblks - before_mallinfo.uordblks : 0; - - log_debug("Successfully loaded SELinux database in %s, size on heap is %iK.", - format_timespan(timespan, sizeof(timespan), after_timestamp - before_timestamp, 0), - (l+1023)/1024); - } -#endif - - return r; -} - -void mac_selinux_finish(void) { - -#ifdef HAVE_SELINUX - if (!label_hnd) - return; - - selabel_close(label_hnd); - label_hnd = NULL; -#endif -} - -int mac_selinux_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { - -#ifdef HAVE_SELINUX - struct stat st; - int r; - - assert(path); - - /* if mac_selinux_init() wasn't called before we are a NOOP */ - if (!label_hnd) - return 0; - - r = lstat(path, &st); - if (r >= 0) { - _cleanup_security_context_free_ security_context_t fcon = NULL; - - r = selabel_lookup_raw(label_hnd, &fcon, path, st.st_mode); - - /* If there's no label to set, then exit without warning */ - if (r < 0 && errno == ENOENT) - return 0; - - if (r >= 0) { - r = lsetfilecon_raw(path, fcon); - - /* If the FS doesn't support labels, then exit without warning */ - if (r < 0 && errno == EOPNOTSUPP) - return 0; - } - } - - if (r < 0) { - /* Ignore ENOENT in some cases */ - if (ignore_enoent && errno == ENOENT) - return 0; - - if (ignore_erofs && errno == EROFS) - return 0; - - log_enforcing("Unable to fix SELinux security context of %s: %m", path); - if (security_getenforce() == 1) - return -errno; - } -#endif - - return 0; -} - -int mac_selinux_apply(const char *path, const char *label) { - -#ifdef HAVE_SELINUX - if (!mac_selinux_use()) - return 0; - - assert(path); - assert(label); - - if (setfilecon(path, (security_context_t) label) < 0) { - log_enforcing("Failed to set SELinux security context %s on path %s: %m", label, path); - if (security_getenforce() > 0) - return -errno; - } -#endif - return 0; -} - -int mac_selinux_get_create_label_from_exe(const char *exe, char **label) { - int r = -EOPNOTSUPP; - -#ifdef HAVE_SELINUX - _cleanup_security_context_free_ security_context_t mycon = NULL, fcon = NULL; - security_class_t sclass; - - assert(exe); - assert(label); - - if (!mac_selinux_have()) - return -EOPNOTSUPP; - - r = getcon_raw(&mycon); - if (r < 0) - return -errno; - - r = getfilecon_raw(exe, &fcon); - if (r < 0) - return -errno; - - sclass = string_to_security_class("process"); - r = security_compute_create_raw(mycon, fcon, sclass, (security_context_t *) label); - if (r < 0) - return -errno; -#endif - - return r; -} - -int mac_selinux_get_our_label(char **label) { - int r = -EOPNOTSUPP; - - assert(label); - -#ifdef HAVE_SELINUX - if (!mac_selinux_have()) - return -EOPNOTSUPP; - - r = getcon_raw(label); - if (r < 0) - return -errno; -#endif - - return r; -} - -int mac_selinux_get_child_mls_label(int socket_fd, const char *exe, const char *exec_label, char **label) { - int r = -EOPNOTSUPP; - -#ifdef HAVE_SELINUX - _cleanup_security_context_free_ security_context_t mycon = NULL, peercon = NULL, fcon = NULL; - _cleanup_context_free_ context_t pcon = NULL, bcon = NULL; - security_class_t sclass; - const char *range = NULL; - - assert(socket_fd >= 0); - assert(exe); - assert(label); - - if (!mac_selinux_have()) - return -EOPNOTSUPP; - - r = getcon_raw(&mycon); - if (r < 0) - return -errno; - - r = getpeercon_raw(socket_fd, &peercon); - if (r < 0) - return -errno; - - if (!exec_label) { - /* If there is no context set for next exec let's use context - of target executable */ - r = getfilecon_raw(exe, &fcon); - if (r < 0) - return -errno; - } - - bcon = context_new(mycon); - if (!bcon) - return -ENOMEM; - - pcon = context_new(peercon); - if (!pcon) - return -ENOMEM; - - range = context_range_get(pcon); - if (!range) - return -errno; - - r = context_range_set(bcon, range); - if (r) - return -errno; - - freecon(mycon); - mycon = strdup(context_str(bcon)); - if (!mycon) - return -ENOMEM; - - sclass = string_to_security_class("process"); - r = security_compute_create_raw(mycon, fcon, sclass, (security_context_t *) label); - if (r < 0) - return -errno; -#endif - - return r; -} - -char* mac_selinux_free(char *label) { - -#ifdef HAVE_SELINUX - if (!label) - return NULL; - - if (!mac_selinux_have()) - return NULL; - - - freecon((security_context_t) label); -#endif - - return NULL; -} - -int mac_selinux_create_file_prepare(const char *path, mode_t mode) { - -#ifdef HAVE_SELINUX - _cleanup_security_context_free_ security_context_t filecon = NULL; - int r; - - assert(path); - - if (!label_hnd) - return 0; - - if (path_is_absolute(path)) - r = selabel_lookup_raw(label_hnd, &filecon, path, mode); - else { - _cleanup_free_ char *newpath = NULL; - - r = path_make_absolute_cwd(path, &newpath); - if (r < 0) - return r; - - r = selabel_lookup_raw(label_hnd, &filecon, newpath, mode); - } - - if (r < 0) { - /* No context specified by the policy? Proceed without setting it. */ - if (errno == ENOENT) - return 0; - - log_enforcing("Failed to determine SELinux security context for %s: %m", path); - } else { - if (setfscreatecon_raw(filecon) >= 0) - return 0; /* Success! */ - - log_enforcing("Failed to set SELinux security context %s for %s: %m", filecon, path); - } - - if (security_getenforce() > 0) - return -errno; - -#endif - return 0; -} - -void mac_selinux_create_file_clear(void) { - -#ifdef HAVE_SELINUX - PROTECT_ERRNO; - - if (!mac_selinux_use()) - return; - - setfscreatecon_raw(NULL); -#endif -} - -int mac_selinux_create_socket_prepare(const char *label) { - -#ifdef HAVE_SELINUX - if (!mac_selinux_use()) - return 0; - - assert(label); - - if (setsockcreatecon((security_context_t) label) < 0) { - log_enforcing("Failed to set SELinux security context %s for sockets: %m", label); - - if (security_getenforce() == 1) - return -errno; - } -#endif - - return 0; -} - -void mac_selinux_create_socket_clear(void) { - -#ifdef HAVE_SELINUX - PROTECT_ERRNO; - - if (!mac_selinux_use()) - return; - - setsockcreatecon_raw(NULL); -#endif -} - -int mac_selinux_bind(int fd, const struct sockaddr *addr, socklen_t addrlen) { - - /* Binds a socket and label its file system object according to the SELinux policy */ - -#ifdef HAVE_SELINUX - _cleanup_security_context_free_ security_context_t fcon = NULL; - const struct sockaddr_un *un; - bool context_changed = false; - char *path; - int r; - - assert(fd >= 0); - assert(addr); - assert(addrlen >= sizeof(sa_family_t)); - - if (!label_hnd) - goto skipped; - - /* Filter out non-local sockets */ - if (addr->sa_family != AF_UNIX) - goto skipped; - - /* Filter out anonymous sockets */ - if (addrlen < offsetof(struct sockaddr_un, sun_path) + 1) - goto skipped; - - /* Filter out abstract namespace sockets */ - un = (const struct sockaddr_un*) addr; - if (un->sun_path[0] == 0) - goto skipped; - - path = strndupa(un->sun_path, addrlen - offsetof(struct sockaddr_un, sun_path)); - - if (path_is_absolute(path)) - r = selabel_lookup_raw(label_hnd, &fcon, path, S_IFSOCK); - else { - _cleanup_free_ char *newpath = NULL; - - r = path_make_absolute_cwd(path, &newpath); - if (r < 0) - return r; - - r = selabel_lookup_raw(label_hnd, &fcon, newpath, S_IFSOCK); - } - - if (r < 0) { - /* No context specified by the policy? Proceed without setting it */ - if (errno == ENOENT) - goto skipped; - - log_enforcing("Failed to determine SELinux security context for %s: %m", path); - if (security_getenforce() > 0) - return -errno; - - } else { - if (setfscreatecon_raw(fcon) < 0) { - log_enforcing("Failed to set SELinux security context %s for %s: %m", fcon, path); - if (security_getenforce() > 0) - return -errno; - } else - context_changed = true; - } - - r = bind(fd, addr, addrlen) < 0 ? -errno : 0; - - if (context_changed) - setfscreatecon_raw(NULL); - - return r; - -skipped: -#endif - if (bind(fd, addr, addrlen) < 0) - return -errno; - - return 0; -} diff --git a/src/basic/selinux-util.h b/src/basic/selinux-util.h deleted file mode 100644 index ce6bc8e44c..0000000000 --- a/src/basic/selinux-util.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include - -#include "macro.h" - -bool mac_selinux_use(void); -bool mac_selinux_have(void); -void mac_selinux_retest(void); - -int mac_selinux_init(void); -void mac_selinux_finish(void); - -int mac_selinux_fix(const char *path, bool ignore_enoent, bool ignore_erofs); -int mac_selinux_apply(const char *path, const char *label); - -int mac_selinux_get_create_label_from_exe(const char *exe, char **label); -int mac_selinux_get_our_label(char **label); -int mac_selinux_get_child_mls_label(int socket_fd, const char *exe, const char *exec_label, char **label); -char* mac_selinux_free(char *label); - -int mac_selinux_create_file_prepare(const char *path, mode_t mode); -void mac_selinux_create_file_clear(void); - -int mac_selinux_create_socket_prepare(const char *label); -void mac_selinux_create_socket_clear(void); - -int mac_selinux_bind(int fd, const struct sockaddr *addr, socklen_t addrlen); - -DEFINE_TRIVIAL_CLEANUP_FUNC(char*, mac_selinux_free); diff --git a/src/basic/set.h b/src/basic/set.h deleted file mode 100644 index e0d9dd001c..0000000000 --- a/src/basic/set.h +++ /dev/null @@ -1,136 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "hashmap.h" -#include "macro.h" - -Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); -#define set_new(ops) internal_set_new(ops HASHMAP_DEBUG_SRC_ARGS) - -static inline Set *set_free(Set *s) { - internal_hashmap_free(HASHMAP_BASE(s)); - return NULL; -} - -static inline Set *set_free_free(Set *s) { - internal_hashmap_free_free(HASHMAP_BASE(s)); - return NULL; -} - -/* no set_free_free_free */ - -static inline Set *set_copy(Set *s) { - return (Set*) internal_hashmap_copy(HASHMAP_BASE(s)); -} - -int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); -#define set_ensure_allocated(h, ops) internal_set_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) - -int set_put(Set *s, const void *key); -/* no set_update */ -/* no set_replace */ -static inline void *set_get(Set *s, void *key) { - return internal_hashmap_get(HASHMAP_BASE(s), key); -} -/* no set_get2 */ - -static inline bool set_contains(Set *s, const void *key) { - return internal_hashmap_contains(HASHMAP_BASE(s), key); -} - -static inline void *set_remove(Set *s, const void *key) { - return internal_hashmap_remove(HASHMAP_BASE(s), key); -} - -/* no set_remove2 */ -/* no set_remove_value */ -int set_remove_and_put(Set *s, const void *old_key, const void *new_key); -/* no set_remove_and_replace */ -int set_merge(Set *s, Set *other); - -static inline int set_reserve(Set *h, unsigned entries_add) { - return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add); -} - -static inline int set_move(Set *s, Set *other) { - return internal_hashmap_move(HASHMAP_BASE(s), HASHMAP_BASE(other)); -} - -static inline int set_move_one(Set *s, Set *other, const void *key) { - return internal_hashmap_move_one(HASHMAP_BASE(s), HASHMAP_BASE(other), key); -} - -static inline unsigned set_size(Set *s) { - return internal_hashmap_size(HASHMAP_BASE(s)); -} - -static inline bool set_isempty(Set *s) { - return set_size(s) == 0; -} - -static inline unsigned set_buckets(Set *s) { - return internal_hashmap_buckets(HASHMAP_BASE(s)); -} - -bool set_iterate(Set *s, Iterator *i, void **value); - -static inline void set_clear(Set *s) { - internal_hashmap_clear(HASHMAP_BASE(s)); -} - -static inline void set_clear_free(Set *s) { - internal_hashmap_clear_free(HASHMAP_BASE(s)); -} - -/* no set_clear_free_free */ - -static inline void *set_steal_first(Set *s) { - return internal_hashmap_steal_first(HASHMAP_BASE(s)); -} - -/* no set_steal_first_key */ -/* no set_first_key */ - -static inline void *set_first(Set *s) { - return internal_hashmap_first(HASHMAP_BASE(s)); -} - -/* no set_next */ - -static inline char **set_get_strv(Set *s) { - return internal_hashmap_get_strv(HASHMAP_BASE(s)); -} - -int set_consume(Set *s, void *value); -int set_put_strdup(Set *s, const char *p); -int set_put_strdupv(Set *s, char **l); - -#define SET_FOREACH(e, s, i) \ - for ((i) = ITERATOR_FIRST; set_iterate((s), &(i), (void**)&(e)); ) - -#define SET_FOREACH_MOVE(e, d, s) \ - for (; ({ e = set_first(s); assert_se(!e || set_move_one(d, s, e) >= 0); e; }); ) - -DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free_free); - -#define _cleanup_set_free_ _cleanup_(set_freep) -#define _cleanup_set_free_free_ _cleanup_(set_free_freep) diff --git a/src/basic/sigbus.c b/src/basic/sigbus.c deleted file mode 100644 index 0ce4f75684..0000000000 --- a/src/basic/sigbus.c +++ /dev/null @@ -1,152 +0,0 @@ -/*** - 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 -#include -#include - -#include "macro.h" -#include "sigbus.h" -#include "util.h" - -#define SIGBUS_QUEUE_MAX 64 - -static struct sigaction old_sigaction; -static unsigned n_installed = 0; - -/* We maintain a fixed size list of page addresses that triggered a - SIGBUS. We access with list with atomic operations, so that we - don't have to deal with locks between signal handler and main - programs in possibly multiple threads. */ - -static void* volatile sigbus_queue[SIGBUS_QUEUE_MAX]; -static volatile sig_atomic_t n_sigbus_queue = 0; - -static void sigbus_push(void *addr) { - unsigned u; - - assert(addr); - - /* Find a free place, increase the number of entries and leave, if we can */ - for (u = 0; u < SIGBUS_QUEUE_MAX; u++) - if (__sync_bool_compare_and_swap(&sigbus_queue[u], NULL, addr)) { - __sync_fetch_and_add(&n_sigbus_queue, 1); - return; - } - - /* If we can't, make sure the queue size is out of bounds, to - * mark it as overflow */ - for (;;) { - unsigned c; - - __sync_synchronize(); - c = n_sigbus_queue; - - if (c > SIGBUS_QUEUE_MAX) /* already overflow */ - return; - - if (__sync_bool_compare_and_swap(&n_sigbus_queue, c, c + SIGBUS_QUEUE_MAX)) - return; - } -} - -int sigbus_pop(void **ret) { - assert(ret); - - for (;;) { - unsigned u, c; - - __sync_synchronize(); - c = n_sigbus_queue; - - if (_likely_(c == 0)) - return 0; - - if (_unlikely_(c >= SIGBUS_QUEUE_MAX)) - return -EOVERFLOW; - - for (u = 0; u < SIGBUS_QUEUE_MAX; u++) { - void *addr; - - addr = sigbus_queue[u]; - if (!addr) - continue; - - if (__sync_bool_compare_and_swap(&sigbus_queue[u], addr, NULL)) { - __sync_fetch_and_sub(&n_sigbus_queue, 1); - *ret = addr; - return 1; - } - } - } -} - -static void sigbus_handler(int sn, siginfo_t *si, void *data) { - unsigned long ul; - void *aligned; - - assert(sn == SIGBUS); - assert(si); - - if (si->si_code != BUS_ADRERR || !si->si_addr) { - assert_se(sigaction(SIGBUS, &old_sigaction, NULL) == 0); - raise(SIGBUS); - return; - } - - ul = (unsigned long) si->si_addr; - ul = ul / page_size(); - ul = ul * page_size(); - aligned = (void*) ul; - - /* Let's remember which address failed */ - sigbus_push(aligned); - - /* Replace mapping with an anonymous page, so that the - * execution can continue, however with a zeroed out page */ - assert_se(mmap(aligned, page_size(), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0) == aligned); -} - -void sigbus_install(void) { - struct sigaction sa = { - .sa_sigaction = sigbus_handler, - .sa_flags = SA_SIGINFO, - }; - - n_installed++; - - if (n_installed == 1) - assert_se(sigaction(SIGBUS, &sa, &old_sigaction) == 0); - - return; -} - -void sigbus_reset(void) { - - if (n_installed <= 0) - return; - - n_installed--; - - if (n_installed == 0) - assert_se(sigaction(SIGBUS, &old_sigaction, NULL) == 0); - - return; -} diff --git a/src/basic/sigbus.h b/src/basic/sigbus.h deleted file mode 100644 index 980243d9ce..0000000000 --- a/src/basic/sigbus.h +++ /dev/null @@ -1,25 +0,0 @@ -#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 . -***/ - -void sigbus_install(void); -void sigbus_reset(void); - -int sigbus_pop(void **ret); diff --git a/src/basic/signal-util.c b/src/basic/signal-util.c deleted file mode 100644 index 280b5c3251..0000000000 --- a/src/basic/signal-util.c +++ /dev/null @@ -1,278 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 -#include - -#include "macro.h" -#include "parse-util.h" -#include "signal-util.h" -#include "stdio-util.h" -#include "string-table.h" -#include "string-util.h" - -int reset_all_signal_handlers(void) { - static const struct sigaction sa = { - .sa_handler = SIG_DFL, - .sa_flags = SA_RESTART, - }; - int sig, r = 0; - - for (sig = 1; sig < _NSIG; sig++) { - - /* These two cannot be caught... */ - if (sig == SIGKILL || sig == SIGSTOP) - continue; - - /* On Linux the first two RT signals are reserved by - * glibc, and sigaction() will return EINVAL for them. */ - if ((sigaction(sig, &sa, NULL) < 0)) - if (errno != EINVAL && r >= 0) - r = -errno; - } - - return r; -} - -int reset_signal_mask(void) { - sigset_t ss; - - if (sigemptyset(&ss) < 0) - return -errno; - - if (sigprocmask(SIG_SETMASK, &ss, NULL) < 0) - return -errno; - - return 0; -} - -static int sigaction_many_ap(const struct sigaction *sa, int sig, va_list ap) { - int r = 0; - - /* negative signal ends the list. 0 signal is skipped. */ - - if (sig < 0) - return 0; - - if (sig > 0) { - if (sigaction(sig, sa, NULL) < 0) - r = -errno; - } - - while ((sig = va_arg(ap, int)) >= 0) { - - if (sig == 0) - continue; - - if (sigaction(sig, sa, NULL) < 0) { - if (r >= 0) - r = -errno; - } - } - - return r; -} - -int sigaction_many(const struct sigaction *sa, ...) { - va_list ap; - int r; - - va_start(ap, sa); - r = sigaction_many_ap(sa, 0, ap); - va_end(ap); - - return r; -} - -int ignore_signals(int sig, ...) { - - static const struct sigaction sa = { - .sa_handler = SIG_IGN, - .sa_flags = SA_RESTART, - }; - - va_list ap; - int r; - - va_start(ap, sig); - r = sigaction_many_ap(&sa, sig, ap); - va_end(ap); - - return r; -} - -int default_signals(int sig, ...) { - - static const struct sigaction sa = { - .sa_handler = SIG_DFL, - .sa_flags = SA_RESTART, - }; - - va_list ap; - int r; - - va_start(ap, sig); - r = sigaction_many_ap(&sa, sig, ap); - va_end(ap); - - return r; -} - -static int sigset_add_many_ap(sigset_t *ss, va_list ap) { - int sig, r = 0; - - assert(ss); - - while ((sig = va_arg(ap, int)) >= 0) { - - if (sig == 0) - continue; - - if (sigaddset(ss, sig) < 0) { - if (r >= 0) - r = -errno; - } - } - - return r; -} - -int sigset_add_many(sigset_t *ss, ...) { - va_list ap; - int r; - - va_start(ap, ss); - r = sigset_add_many_ap(ss, ap); - va_end(ap); - - return r; -} - -int sigprocmask_many(int how, sigset_t *old, ...) { - va_list ap; - sigset_t ss; - int r; - - if (sigemptyset(&ss) < 0) - return -errno; - - va_start(ap, old); - r = sigset_add_many_ap(&ss, ap); - va_end(ap); - - if (r < 0) - return r; - - if (sigprocmask(how, &ss, old) < 0) - return -errno; - - return 0; -} - -static const char *const __signal_table[] = { - [SIGHUP] = "HUP", - [SIGINT] = "INT", - [SIGQUIT] = "QUIT", - [SIGILL] = "ILL", - [SIGTRAP] = "TRAP", - [SIGABRT] = "ABRT", - [SIGBUS] = "BUS", - [SIGFPE] = "FPE", - [SIGKILL] = "KILL", - [SIGUSR1] = "USR1", - [SIGSEGV] = "SEGV", - [SIGUSR2] = "USR2", - [SIGPIPE] = "PIPE", - [SIGALRM] = "ALRM", - [SIGTERM] = "TERM", -#ifdef SIGSTKFLT - [SIGSTKFLT] = "STKFLT", /* Linux on SPARC doesn't know SIGSTKFLT */ -#endif - [SIGCHLD] = "CHLD", - [SIGCONT] = "CONT", - [SIGSTOP] = "STOP", - [SIGTSTP] = "TSTP", - [SIGTTIN] = "TTIN", - [SIGTTOU] = "TTOU", - [SIGURG] = "URG", - [SIGXCPU] = "XCPU", - [SIGXFSZ] = "XFSZ", - [SIGVTALRM] = "VTALRM", - [SIGPROF] = "PROF", - [SIGWINCH] = "WINCH", - [SIGIO] = "IO", - [SIGPWR] = "PWR", - [SIGSYS] = "SYS" -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP(__signal, int); - -const char *signal_to_string(int signo) { - static thread_local char buf[sizeof("RTMIN+")-1 + DECIMAL_STR_MAX(int) + 1]; - const char *name; - - name = __signal_to_string(signo); - if (name) - return name; - - if (signo >= SIGRTMIN && signo <= SIGRTMAX) - xsprintf(buf, "RTMIN+%d", signo - SIGRTMIN); - else - xsprintf(buf, "%d", signo); - - return buf; -} - -int signal_from_string(const char *s) { - int signo; - int offset = 0; - unsigned u; - - signo = __signal_from_string(s); - if (signo > 0) - return signo; - - if (startswith(s, "RTMIN+")) { - s += 6; - offset = SIGRTMIN; - } - if (safe_atou(s, &u) >= 0) { - signo = (int) u + offset; - if (SIGNAL_VALID(signo)) - return signo; - } - return -EINVAL; -} - -int signal_from_string_try_harder(const char *s) { - int signo; - assert(s); - - signo = signal_from_string(s); - if (signo <= 0) - if (startswith(s, "SIG")) - return signal_from_string(s+3); - - return signo; -} - -void nop_signal_handler(int sig) { - /* nothing here */ -} diff --git a/src/basic/signal-util.h b/src/basic/signal-util.h deleted file mode 100644 index dfd6eb564d..0000000000 --- a/src/basic/signal-util.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010-2015 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 "macro.h" - -int reset_all_signal_handlers(void); -int reset_signal_mask(void); - -int ignore_signals(int sig, ...); -int default_signals(int sig, ...); -int sigaction_many(const struct sigaction *sa, ...); - -int sigset_add_many(sigset_t *ss, ...); -int sigprocmask_many(int how, sigset_t *old, ...); - -const char *signal_to_string(int i) _const_; -int signal_from_string(const char *s) _pure_; - -int signal_from_string_try_harder(const char *s); - -void nop_signal_handler(int sig); - -static inline void block_signals_reset(sigset_t *ss) { - assert_se(sigprocmask(SIG_SETMASK, ss, NULL) >= 0); -} - -#define BLOCK_SIGNALS(...) \ - _cleanup_(block_signals_reset) _unused_ sigset_t _saved_sigset = ({ \ - sigset_t t; \ - assert_se(sigprocmask_many(SIG_BLOCK, &t, __VA_ARGS__, -1) >= 0); \ - t; \ - }) - -static inline bool SIGNAL_VALID(int signo) { - return signo > 0 && signo < _NSIG; -} diff --git a/src/basic/siphash24.c b/src/basic/siphash24.c deleted file mode 100644 index 060e8ba387..0000000000 --- a/src/basic/siphash24.c +++ /dev/null @@ -1,191 +0,0 @@ -/* - SipHash reference C implementation - - Written in 2012 by - Jean-Philippe Aumasson - Daniel J. Bernstein - - To the extent possible under law, the author(s) have dedicated all copyright - and related and neighboring rights to this software to the public domain - worldwide. This software is distributed without any warranty. - - You should have received a copy of the CC0 Public Domain Dedication along with - this software. If not, see . - - (Minimal changes made by Lennart Poettering, to make clean for inclusion in systemd) - (Refactored by Tom Gundersen to split up in several functions and follow systemd - coding style) -*/ - -#include "macro.h" -#include "siphash24.h" -#include "unaligned.h" - -static inline uint64_t rotate_left(uint64_t x, uint8_t b) { - assert(b < 64); - - return (x << b) | (x >> (64 - b)); -} - -static inline void sipround(struct siphash *state) { - assert(state); - - state->v0 += state->v1; - state->v1 = rotate_left(state->v1, 13); - state->v1 ^= state->v0; - state->v0 = rotate_left(state->v0, 32); - state->v2 += state->v3; - state->v3 = rotate_left(state->v3, 16); - state->v3 ^= state->v2; - state->v0 += state->v3; - state->v3 = rotate_left(state->v3, 21); - state->v3 ^= state->v0; - state->v2 += state->v1; - state->v1 = rotate_left(state->v1, 17); - state->v1 ^= state->v2; - state->v2 = rotate_left(state->v2, 32); -} - -void siphash24_init(struct siphash *state, const uint8_t k[16]) { - uint64_t k0, k1; - - assert(state); - assert(k); - - k0 = unaligned_read_le64(k); - k1 = unaligned_read_le64(k + 8); - - *state = (struct siphash) { - /* "somepseudorandomlygeneratedbytes" */ - .v0 = 0x736f6d6570736575ULL ^ k0, - .v1 = 0x646f72616e646f6dULL ^ k1, - .v2 = 0x6c7967656e657261ULL ^ k0, - .v3 = 0x7465646279746573ULL ^ k1, - .padding = 0, - .inlen = 0, - }; -} - -void siphash24_compress(const void *_in, size_t inlen, struct siphash *state) { - - const uint8_t *in = _in; - const uint8_t *end = in + inlen; - size_t left = state->inlen & 7; - uint64_t m; - - assert(in); - assert(state); - - /* Update total length */ - state->inlen += inlen; - - /* If padding exists, fill it out */ - if (left > 0) { - for ( ; in < end && left < 8; in ++, left ++) - state->padding |= ((uint64_t) *in) << (left * 8); - - if (in == end && left < 8) - /* We did not have enough input to fill out the padding completely */ - return; - -#ifdef DEBUG - printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); - printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); - printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); - printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); - printf("(%3zu) compress padding %08x %08x\n", state->inlen, (uint32_t) (state->padding >> 32), (uint32_t)state->padding); -#endif - - state->v3 ^= state->padding; - sipround(state); - sipround(state); - state->v0 ^= state->padding; - - state->padding = 0; - } - - end -= (state->inlen % sizeof(uint64_t)); - - for ( ; in < end; in += 8) { - m = unaligned_read_le64(in); -#ifdef DEBUG - printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); - printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); - printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); - printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); - printf("(%3zu) compress %08x %08x\n", state->inlen, (uint32_t) (m >> 32), (uint32_t) m); -#endif - state->v3 ^= m; - sipround(state); - sipround(state); - state->v0 ^= m; - } - - left = state->inlen & 7; - switch (left) { - case 7: - state->padding |= ((uint64_t) in[6]) << 48; - case 6: - state->padding |= ((uint64_t) in[5]) << 40; - case 5: - state->padding |= ((uint64_t) in[4]) << 32; - case 4: - state->padding |= ((uint64_t) in[3]) << 24; - case 3: - state->padding |= ((uint64_t) in[2]) << 16; - case 2: - state->padding |= ((uint64_t) in[1]) << 8; - case 1: - state->padding |= ((uint64_t) in[0]); - case 0: - break; - } -} - -uint64_t siphash24_finalize(struct siphash *state) { - uint64_t b; - - assert(state); - - b = state->padding | (((uint64_t) state->inlen) << 56); - -#ifdef DEBUG - printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); - printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); - printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); - printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); - printf("(%3zu) padding %08x %08x\n", state->inlen, (uint32_t) (state->padding >> 32), (uint32_t) state->padding); -#endif - - state->v3 ^= b; - sipround(state); - sipround(state); - state->v0 ^= b; - -#ifdef DEBUG - printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); - printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); - printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); - printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); -#endif - state->v2 ^= 0xff; - - sipround(state); - sipround(state); - sipround(state); - sipround(state); - - return state->v0 ^ state->v1 ^ state->v2 ^ state->v3; -} - -uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]) { - struct siphash state; - - assert(in); - assert(k); - - siphash24_init(&state, k); - siphash24_compress(in, inlen, &state); - - return siphash24_finalize(&state); -} diff --git a/src/basic/siphash24.h b/src/basic/siphash24.h deleted file mode 100644 index 54e2420cc6..0000000000 --- a/src/basic/siphash24.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -struct siphash { - uint64_t v0; - uint64_t v1; - uint64_t v2; - uint64_t v3; - uint64_t padding; - size_t inlen; -}; - -void siphash24_init(struct siphash *state, const uint8_t k[16]); -void siphash24_compress(const void *in, size_t inlen, struct siphash *state); -#define siphash24_compress_byte(byte, state) siphash24_compress((const uint8_t[]) { (byte) }, 1, (state)) - -uint64_t siphash24_finalize(struct siphash *state); - -uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]); diff --git a/src/basic/smack-util.c b/src/basic/smack-util.c deleted file mode 100644 index 3a3df987df..0000000000 --- a/src/basic/smack-util.c +++ /dev/null @@ -1,241 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 Intel Corporation - - Author: Auke Kok - - 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 -#include -#include -#include - -#include "alloc-util.h" -#include "fileio.h" -#include "log.h" -#include "macro.h" -#include "path-util.h" -#include "process-util.h" -#include "smack-util.h" -#include "string-table.h" -#include "xattr-util.h" - -#ifdef HAVE_SMACK -bool mac_smack_use(void) { - static int cached_use = -1; - - if (cached_use < 0) - cached_use = access("/sys/fs/smackfs/", F_OK) >= 0; - - return cached_use; -} - -static const char* const smack_attr_table[_SMACK_ATTR_MAX] = { - [SMACK_ATTR_ACCESS] = "security.SMACK64", - [SMACK_ATTR_EXEC] = "security.SMACK64EXEC", - [SMACK_ATTR_MMAP] = "security.SMACK64MMAP", - [SMACK_ATTR_TRANSMUTE] = "security.SMACK64TRANSMUTE", - [SMACK_ATTR_IPIN] = "security.SMACK64IPIN", - [SMACK_ATTR_IPOUT] = "security.SMACK64IPOUT", -}; - -DEFINE_STRING_TABLE_LOOKUP(smack_attr, SmackAttr); - -int mac_smack_read(const char *path, SmackAttr attr, char **label) { - assert(path); - assert(attr >= 0 && attr < _SMACK_ATTR_MAX); - assert(label); - - if (!mac_smack_use()) - return 0; - - return getxattr_malloc(path, smack_attr_to_string(attr), label, true); -} - -int mac_smack_read_fd(int fd, SmackAttr attr, char **label) { - assert(fd >= 0); - assert(attr >= 0 && attr < _SMACK_ATTR_MAX); - assert(label); - - if (!mac_smack_use()) - return 0; - - return fgetxattr_malloc(fd, smack_attr_to_string(attr), label); -} - -int mac_smack_apply(const char *path, SmackAttr attr, const char *label) { - int r; - - assert(path); - assert(attr >= 0 && attr < _SMACK_ATTR_MAX); - - if (!mac_smack_use()) - return 0; - - if (label) - r = lsetxattr(path, smack_attr_to_string(attr), label, strlen(label), 0); - else - r = lremovexattr(path, smack_attr_to_string(attr)); - if (r < 0) - return -errno; - - return 0; -} - -int mac_smack_apply_fd(int fd, SmackAttr attr, const char *label) { - int r; - - assert(fd >= 0); - assert(attr >= 0 && attr < _SMACK_ATTR_MAX); - - if (!mac_smack_use()) - return 0; - - if (label) - r = fsetxattr(fd, smack_attr_to_string(attr), label, strlen(label), 0); - else - r = fremovexattr(fd, smack_attr_to_string(attr)); - if (r < 0) - return -errno; - - return 0; -} - -int mac_smack_apply_pid(pid_t pid, const char *label) { - const char *p; - int r = 0; - - assert(label); - - if (!mac_smack_use()) - return 0; - - p = procfs_file_alloca(pid, "attr/current"); - r = write_string_file(p, label, 0); - if (r < 0) - return r; - - return r; -} - -int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { - struct stat st; - int r = 0; - - assert(path); - - if (!mac_smack_use()) - return 0; - - /* - * Path must be in /dev and must exist - */ - if (!path_startswith(path, "/dev")) - return 0; - - r = lstat(path, &st); - if (r >= 0) { - const char *label; - - /* - * Label directories and character devices "*". - * Label symlinks "_". - * Don't change anything else. - */ - - if (S_ISDIR(st.st_mode)) - label = SMACK_STAR_LABEL; - else if (S_ISLNK(st.st_mode)) - label = SMACK_FLOOR_LABEL; - else if (S_ISCHR(st.st_mode)) - label = SMACK_STAR_LABEL; - else - return 0; - - r = lsetxattr(path, "security.SMACK64", label, strlen(label), 0); - - /* If the FS doesn't support labels, then exit without warning */ - if (r < 0 && errno == EOPNOTSUPP) - return 0; - } - - if (r < 0) { - /* Ignore ENOENT in some cases */ - if (ignore_enoent && errno == ENOENT) - return 0; - - if (ignore_erofs && errno == EROFS) - return 0; - - r = log_debug_errno(errno, "Unable to fix SMACK label of %s: %m", path); - } - - return r; -} - -int mac_smack_copy(const char *dest, const char *src) { - int r = 0; - _cleanup_free_ char *label = NULL; - - assert(dest); - assert(src); - - r = mac_smack_read(src, SMACK_ATTR_ACCESS, &label); - if (r < 0) - return r; - - r = mac_smack_apply(dest, SMACK_ATTR_ACCESS, label); - if (r < 0) - return r; - - return r; -} - -#else -bool mac_smack_use(void) { - return false; -} - -int mac_smack_read(const char *path, SmackAttr attr, char **label) { - return -EOPNOTSUPP; -} - -int mac_smack_read_fd(int fd, SmackAttr attr, char **label) { - return -EOPNOTSUPP; -} - -int mac_smack_apply(const char *path, SmackAttr attr, const char *label) { - return 0; -} - -int mac_smack_apply_fd(int fd, SmackAttr attr, const char *label) { - return 0; -} - -int mac_smack_apply_pid(pid_t pid, const char *label) { - return 0; -} - -int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { - return 0; -} - -int mac_smack_copy(const char *dest, const char *src) { - return 0; -} -#endif diff --git a/src/basic/smack-util.h b/src/basic/smack-util.h deleted file mode 100644 index f90ba0a027..0000000000 --- a/src/basic/smack-util.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 Intel Corporation - - Author: Auke Kok - - 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 - -#include "macro.h" - -#define SMACK_FLOOR_LABEL "_" -#define SMACK_STAR_LABEL "*" - -typedef enum SmackAttr { - SMACK_ATTR_ACCESS = 0, - SMACK_ATTR_EXEC = 1, - SMACK_ATTR_MMAP = 2, - SMACK_ATTR_TRANSMUTE = 3, - SMACK_ATTR_IPIN = 4, - SMACK_ATTR_IPOUT = 5, - _SMACK_ATTR_MAX, - _SMACK_ATTR_INVALID = -1, -} SmackAttr; - -bool mac_smack_use(void); - -int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs); - -const char* smack_attr_to_string(SmackAttr i) _const_; -SmackAttr smack_attr_from_string(const char *s) _pure_; -int mac_smack_read(const char *path, SmackAttr attr, char **label); -int mac_smack_read_fd(int fd, SmackAttr attr, char **label); -int mac_smack_apply(const char *path, SmackAttr attr, const char *label); -int mac_smack_apply_fd(int fd, SmackAttr attr, const char *label); -int mac_smack_apply_pid(pid_t pid, const char *label); -int mac_smack_copy(const char *dest, const char *src); diff --git a/src/basic/socket-label.c b/src/basic/socket-label.c deleted file mode 100644 index 6d1dc83874..0000000000 --- a/src/basic/socket-label.c +++ /dev/null @@ -1,170 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "selinux-util.h" -#include "socket-util.h" -#include "umask-util.h" - -int socket_address_listen( - const SocketAddress *a, - int flags, - int backlog, - SocketAddressBindIPv6Only only, - const char *bind_to_device, - bool reuse_port, - bool free_bind, - bool transparent, - mode_t directory_mode, - mode_t socket_mode, - const char *label) { - - _cleanup_close_ int fd = -1; - int r, one; - - assert(a); - - r = socket_address_verify(a); - if (r < 0) - return r; - - if (socket_address_family(a) == AF_INET6 && !socket_ipv6_is_supported()) - return -EAFNOSUPPORT; - - if (label) { - r = mac_selinux_create_socket_prepare(label); - if (r < 0) - return r; - } - - fd = socket(socket_address_family(a), a->type | flags, a->protocol); - r = fd < 0 ? -errno : 0; - - if (label) - mac_selinux_create_socket_clear(); - - if (r < 0) - return r; - - if (socket_address_family(a) == AF_INET6 && only != SOCKET_ADDRESS_DEFAULT) { - int flag = only == SOCKET_ADDRESS_IPV6_ONLY; - - if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag)) < 0) - return -errno; - } - - if (socket_address_family(a) == AF_INET || socket_address_family(a) == AF_INET6) { - if (bind_to_device) - if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, bind_to_device, strlen(bind_to_device)+1) < 0) - return -errno; - - if (reuse_port) { - one = 1; - if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)) < 0) - log_warning_errno(errno, "SO_REUSEPORT failed: %m"); - } - - if (free_bind) { - one = 1; - if (setsockopt(fd, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one)) < 0) - log_warning_errno(errno, "IP_FREEBIND failed: %m"); - } - - if (transparent) { - one = 1; - if (setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &one, sizeof(one)) < 0) - log_warning_errno(errno, "IP_TRANSPARENT failed: %m"); - } - } - - one = 1; - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) - return -errno; - - if (socket_address_family(a) == AF_UNIX && a->sockaddr.un.sun_path[0] != 0) { - /* Create parents */ - (void) mkdir_parents_label(a->sockaddr.un.sun_path, directory_mode); - - /* Enforce the right access mode for the socket */ - RUN_WITH_UMASK(~socket_mode) { - r = mac_selinux_bind(fd, &a->sockaddr.sa, a->size); - if (r == -EADDRINUSE) { - /* Unlink and try again */ - unlink(a->sockaddr.un.sun_path); - if (bind(fd, &a->sockaddr.sa, a->size) < 0) - return -errno; - } else if (r < 0) - return r; - } - } else { - if (bind(fd, &a->sockaddr.sa, a->size) < 0) - return -errno; - } - - if (socket_address_can_accept(a)) - if (listen(fd, backlog) < 0) - return -errno; - - r = fd; - fd = -1; - - return r; -} - -int make_socket_fd(int log_level, const char* address, int type, int flags) { - SocketAddress a; - int fd, r; - - r = socket_address_parse(&a, address); - if (r < 0) - return log_error_errno(r, "Failed to parse socket address \"%s\": %m", address); - - a.type = type; - - fd = socket_address_listen(&a, type | flags, SOMAXCONN, SOCKET_ADDRESS_DEFAULT, - NULL, false, false, false, 0755, 0644, NULL); - if (fd < 0 || log_get_max_level() >= log_level) { - _cleanup_free_ char *p = NULL; - - r = socket_address_print(&a, &p); - if (r < 0) - return log_error_errno(r, "socket_address_print(): %m"); - - if (fd < 0) - log_error_errno(fd, "Failed to listen on %s: %m", p); - else - log_full(log_level, "Listening on %s", p); - } - - return fd; -} diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c deleted file mode 100644 index c8769a54f4..0000000000 --- a/src/basic/socket-util.c +++ /dev/null @@ -1,1050 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "parse-util.h" -#include "path-util.h" -#include "socket-util.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "user-util.h" -#include "utf8.h" -#include "util.h" - -int socket_address_parse(SocketAddress *a, const char *s) { - char *e, *n; - unsigned u; - int r; - - assert(a); - assert(s); - - zero(*a); - a->type = SOCK_STREAM; - - if (*s == '[') { - /* IPv6 in [x:.....:z]:p notation */ - - e = strchr(s+1, ']'); - if (!e) - return -EINVAL; - - n = strndupa(s+1, e-s-1); - - errno = 0; - if (inet_pton(AF_INET6, n, &a->sockaddr.in6.sin6_addr) <= 0) - return errno > 0 ? -errno : -EINVAL; - - e++; - if (*e != ':') - return -EINVAL; - - e++; - r = safe_atou(e, &u); - if (r < 0) - return r; - - if (u <= 0 || u > 0xFFFF) - return -EINVAL; - - a->sockaddr.in6.sin6_family = AF_INET6; - a->sockaddr.in6.sin6_port = htons((uint16_t) u); - a->size = sizeof(struct sockaddr_in6); - - } else if (*s == '/') { - /* AF_UNIX socket */ - - size_t l; - - l = strlen(s); - if (l >= sizeof(a->sockaddr.un.sun_path)) - return -EINVAL; - - a->sockaddr.un.sun_family = AF_UNIX; - memcpy(a->sockaddr.un.sun_path, s, l); - a->size = offsetof(struct sockaddr_un, sun_path) + l + 1; - - } else if (*s == '@') { - /* Abstract AF_UNIX socket */ - size_t l; - - l = strlen(s+1); - if (l >= sizeof(a->sockaddr.un.sun_path) - 1) - return -EINVAL; - - a->sockaddr.un.sun_family = AF_UNIX; - memcpy(a->sockaddr.un.sun_path+1, s+1, l); - a->size = offsetof(struct sockaddr_un, sun_path) + 1 + l; - - } else { - e = strchr(s, ':'); - if (e) { - r = safe_atou(e+1, &u); - if (r < 0) - return r; - - if (u <= 0 || u > 0xFFFF) - return -EINVAL; - - n = strndupa(s, e-s); - - /* IPv4 in w.x.y.z:p notation? */ - r = inet_pton(AF_INET, n, &a->sockaddr.in.sin_addr); - if (r < 0) - return -errno; - - if (r > 0) { - /* Gotcha, it's a traditional IPv4 address */ - a->sockaddr.in.sin_family = AF_INET; - a->sockaddr.in.sin_port = htons((uint16_t) u); - a->size = sizeof(struct sockaddr_in); - } else { - unsigned idx; - - if (strlen(n) > IF_NAMESIZE-1) - return -EINVAL; - - /* Uh, our last resort, an interface name */ - idx = if_nametoindex(n); - if (idx == 0) - return -EINVAL; - - a->sockaddr.in6.sin6_family = AF_INET6; - a->sockaddr.in6.sin6_port = htons((uint16_t) u); - a->sockaddr.in6.sin6_scope_id = idx; - a->sockaddr.in6.sin6_addr = in6addr_any; - a->size = sizeof(struct sockaddr_in6); - } - } else { - - /* Just a port */ - r = safe_atou(s, &u); - if (r < 0) - return r; - - if (u <= 0 || u > 0xFFFF) - return -EINVAL; - - if (socket_ipv6_is_supported()) { - a->sockaddr.in6.sin6_family = AF_INET6; - a->sockaddr.in6.sin6_port = htons((uint16_t) u); - a->sockaddr.in6.sin6_addr = in6addr_any; - a->size = sizeof(struct sockaddr_in6); - } else { - a->sockaddr.in.sin_family = AF_INET; - a->sockaddr.in.sin_port = htons((uint16_t) u); - a->sockaddr.in.sin_addr.s_addr = INADDR_ANY; - a->size = sizeof(struct sockaddr_in); - } - } - } - - return 0; -} - -int socket_address_parse_and_warn(SocketAddress *a, const char *s) { - SocketAddress b; - int r; - - /* Similar to socket_address_parse() but warns for IPv6 sockets when we don't support them. */ - - r = socket_address_parse(&b, s); - if (r < 0) - return r; - - if (!socket_ipv6_is_supported() && b.sockaddr.sa.sa_family == AF_INET6) { - log_warning("Binding to IPv6 address not available since kernel does not support IPv6."); - return -EAFNOSUPPORT; - } - - *a = b; - return 0; -} - -int socket_address_parse_netlink(SocketAddress *a, const char *s) { - int family; - unsigned group = 0; - _cleanup_free_ char *sfamily = NULL; - assert(a); - assert(s); - - zero(*a); - a->type = SOCK_RAW; - - errno = 0; - if (sscanf(s, "%ms %u", &sfamily, &group) < 1) - return errno > 0 ? -errno : -EINVAL; - - family = netlink_family_from_string(sfamily); - if (family < 0) - return -EINVAL; - - a->sockaddr.nl.nl_family = AF_NETLINK; - a->sockaddr.nl.nl_groups = group; - - a->type = SOCK_RAW; - a->size = sizeof(struct sockaddr_nl); - a->protocol = family; - - return 0; -} - -int socket_address_verify(const SocketAddress *a) { - assert(a); - - switch (socket_address_family(a)) { - - case AF_INET: - if (a->size != sizeof(struct sockaddr_in)) - return -EINVAL; - - if (a->sockaddr.in.sin_port == 0) - return -EINVAL; - - if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM) - return -EINVAL; - - return 0; - - case AF_INET6: - if (a->size != sizeof(struct sockaddr_in6)) - return -EINVAL; - - if (a->sockaddr.in6.sin6_port == 0) - return -EINVAL; - - if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM) - return -EINVAL; - - return 0; - - case AF_UNIX: - if (a->size < offsetof(struct sockaddr_un, sun_path)) - return -EINVAL; - - if (a->size > offsetof(struct sockaddr_un, sun_path)) { - - if (a->sockaddr.un.sun_path[0] != 0) { - char *e; - - /* path */ - e = memchr(a->sockaddr.un.sun_path, 0, sizeof(a->sockaddr.un.sun_path)); - if (!e) - return -EINVAL; - - if (a->size != offsetof(struct sockaddr_un, sun_path) + (e - a->sockaddr.un.sun_path) + 1) - return -EINVAL; - } - } - - if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM && a->type != SOCK_SEQPACKET) - return -EINVAL; - - return 0; - - case AF_NETLINK: - - if (a->size != sizeof(struct sockaddr_nl)) - return -EINVAL; - - if (a->type != SOCK_RAW && a->type != SOCK_DGRAM) - return -EINVAL; - - return 0; - - default: - return -EAFNOSUPPORT; - } -} - -int socket_address_print(const SocketAddress *a, char **ret) { - int r; - - assert(a); - assert(ret); - - r = socket_address_verify(a); - if (r < 0) - return r; - - if (socket_address_family(a) == AF_NETLINK) { - _cleanup_free_ char *sfamily = NULL; - - r = netlink_family_to_string_alloc(a->protocol, &sfamily); - if (r < 0) - return r; - - r = asprintf(ret, "%s %u", sfamily, a->sockaddr.nl.nl_groups); - if (r < 0) - return -ENOMEM; - - return 0; - } - - return sockaddr_pretty(&a->sockaddr.sa, a->size, false, true, ret); -} - -bool socket_address_can_accept(const SocketAddress *a) { - assert(a); - - return - a->type == SOCK_STREAM || - a->type == SOCK_SEQPACKET; -} - -bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) { - assert(a); - assert(b); - - /* Invalid addresses are unequal to all */ - if (socket_address_verify(a) < 0 || - socket_address_verify(b) < 0) - return false; - - if (a->type != b->type) - return false; - - if (socket_address_family(a) != socket_address_family(b)) - return false; - - switch (socket_address_family(a)) { - - case AF_INET: - if (a->sockaddr.in.sin_addr.s_addr != b->sockaddr.in.sin_addr.s_addr) - return false; - - if (a->sockaddr.in.sin_port != b->sockaddr.in.sin_port) - return false; - - break; - - case AF_INET6: - if (memcmp(&a->sockaddr.in6.sin6_addr, &b->sockaddr.in6.sin6_addr, sizeof(a->sockaddr.in6.sin6_addr)) != 0) - return false; - - if (a->sockaddr.in6.sin6_port != b->sockaddr.in6.sin6_port) - return false; - - break; - - case AF_UNIX: - if (a->size <= offsetof(struct sockaddr_un, sun_path) || - b->size <= offsetof(struct sockaddr_un, sun_path)) - return false; - - if ((a->sockaddr.un.sun_path[0] == 0) != (b->sockaddr.un.sun_path[0] == 0)) - return false; - - if (a->sockaddr.un.sun_path[0]) { - if (!path_equal_or_files_same(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path)) - return false; - } else { - if (a->size != b->size) - return false; - - if (memcmp(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, a->size) != 0) - return false; - } - - break; - - case AF_NETLINK: - if (a->protocol != b->protocol) - return false; - - if (a->sockaddr.nl.nl_groups != b->sockaddr.nl.nl_groups) - return false; - - break; - - default: - /* Cannot compare, so we assume the addresses are different */ - return false; - } - - return true; -} - -bool socket_address_is(const SocketAddress *a, const char *s, int type) { - struct SocketAddress b; - - assert(a); - assert(s); - - if (socket_address_parse(&b, s) < 0) - return false; - - b.type = type; - - return socket_address_equal(a, &b); -} - -bool socket_address_is_netlink(const SocketAddress *a, const char *s) { - struct SocketAddress b; - - assert(a); - assert(s); - - if (socket_address_parse_netlink(&b, s) < 0) - return false; - - return socket_address_equal(a, &b); -} - -const char* socket_address_get_path(const SocketAddress *a) { - assert(a); - - if (socket_address_family(a) != AF_UNIX) - return NULL; - - if (a->sockaddr.un.sun_path[0] == 0) - return NULL; - - return a->sockaddr.un.sun_path; -} - -bool socket_ipv6_is_supported(void) { - if (access("/proc/net/sockstat6", F_OK) != 0) - return false; - - return true; -} - -bool socket_address_matches_fd(const SocketAddress *a, int fd) { - SocketAddress b; - socklen_t solen; - - assert(a); - assert(fd >= 0); - - b.size = sizeof(b.sockaddr); - if (getsockname(fd, &b.sockaddr.sa, &b.size) < 0) - return false; - - if (b.sockaddr.sa.sa_family != a->sockaddr.sa.sa_family) - return false; - - solen = sizeof(b.type); - if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &b.type, &solen) < 0) - return false; - - if (b.type != a->type) - return false; - - if (a->protocol != 0) { - solen = sizeof(b.protocol); - if (getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &b.protocol, &solen) < 0) - return false; - - if (b.protocol != a->protocol) - return false; - } - - return socket_address_equal(a, &b); -} - -int sockaddr_port(const struct sockaddr *_sa) { - union sockaddr_union *sa = (union sockaddr_union*) _sa; - - assert(sa); - - if (!IN_SET(sa->sa.sa_family, AF_INET, AF_INET6)) - return -EAFNOSUPPORT; - - return ntohs(sa->sa.sa_family == AF_INET6 ? - sa->in6.sin6_port : - sa->in.sin_port); -} - -int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ipv6, bool include_port, char **ret) { - union sockaddr_union *sa = (union sockaddr_union*) _sa; - char *p; - int r; - - assert(sa); - assert(salen >= sizeof(sa->sa.sa_family)); - - switch (sa->sa.sa_family) { - - case AF_INET: { - uint32_t a; - - a = ntohl(sa->in.sin_addr.s_addr); - - if (include_port) - r = asprintf(&p, - "%u.%u.%u.%u:%u", - a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF, - ntohs(sa->in.sin_port)); - else - r = asprintf(&p, - "%u.%u.%u.%u", - a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF); - if (r < 0) - return -ENOMEM; - break; - } - - case AF_INET6: { - static const unsigned char ipv4_prefix[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF - }; - - if (translate_ipv6 && - memcmp(&sa->in6.sin6_addr, ipv4_prefix, sizeof(ipv4_prefix)) == 0) { - const uint8_t *a = sa->in6.sin6_addr.s6_addr+12; - if (include_port) - r = asprintf(&p, - "%u.%u.%u.%u:%u", - a[0], a[1], a[2], a[3], - ntohs(sa->in6.sin6_port)); - else - r = asprintf(&p, - "%u.%u.%u.%u", - a[0], a[1], a[2], a[3]); - if (r < 0) - return -ENOMEM; - } else { - char a[INET6_ADDRSTRLEN]; - - inet_ntop(AF_INET6, &sa->in6.sin6_addr, a, sizeof(a)); - - if (include_port) { - r = asprintf(&p, - "[%s]:%u", - a, - ntohs(sa->in6.sin6_port)); - if (r < 0) - return -ENOMEM; - } else { - p = strdup(a); - if (!p) - return -ENOMEM; - } - } - - break; - } - - case AF_UNIX: - if (salen <= offsetof(struct sockaddr_un, sun_path)) { - p = strdup(""); - if (!p) - return -ENOMEM; - - } else if (sa->un.sun_path[0] == 0) { - /* abstract */ - - /* FIXME: We assume we can print the - * socket path here and that it hasn't - * more than one NUL byte. That is - * actually an invalid assumption */ - - p = new(char, sizeof(sa->un.sun_path)+1); - if (!p) - return -ENOMEM; - - p[0] = '@'; - memcpy(p+1, sa->un.sun_path+1, sizeof(sa->un.sun_path)-1); - p[sizeof(sa->un.sun_path)] = 0; - - } else { - p = strndup(sa->un.sun_path, sizeof(sa->un.sun_path)); - if (!p) - return -ENOMEM; - } - - break; - - default: - return -EOPNOTSUPP; - } - - - *ret = p; - return 0; -} - -int getpeername_pretty(int fd, bool include_port, char **ret) { - union sockaddr_union sa; - socklen_t salen = sizeof(sa); - int r; - - assert(fd >= 0); - assert(ret); - - if (getpeername(fd, &sa.sa, &salen) < 0) - return -errno; - - if (sa.sa.sa_family == AF_UNIX) { - struct ucred ucred = {}; - - /* UNIX connection sockets are anonymous, so let's use - * PID/UID as pretty credentials instead */ - - r = getpeercred(fd, &ucred); - if (r < 0) - return r; - - if (asprintf(ret, "PID "PID_FMT"/UID "UID_FMT, ucred.pid, ucred.uid) < 0) - return -ENOMEM; - - return 0; - } - - /* For remote sockets we translate IPv6 addresses back to IPv4 - * if applicable, since that's nicer. */ - - return sockaddr_pretty(&sa.sa, salen, true, include_port, ret); -} - -int getsockname_pretty(int fd, char **ret) { - union sockaddr_union sa; - socklen_t salen = sizeof(sa); - - assert(fd >= 0); - assert(ret); - - if (getsockname(fd, &sa.sa, &salen) < 0) - return -errno; - - /* For local sockets we do not translate IPv6 addresses back - * to IPv6 if applicable, since this is usually used for - * listening sockets where the difference between IPv4 and - * IPv6 matters. */ - - return sockaddr_pretty(&sa.sa, salen, false, true, ret); -} - -int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret) { - int r; - char host[NI_MAXHOST], *ret; - - assert(_ret); - - r = getnameinfo(&sa->sa, salen, host, sizeof(host), NULL, 0, - NI_IDN|NI_IDN_USE_STD3_ASCII_RULES); - if (r != 0) { - int saved_errno = errno; - - r = sockaddr_pretty(&sa->sa, salen, true, true, &ret); - if (r < 0) - return r; - - log_debug_errno(saved_errno, "getnameinfo(%s) failed: %m", ret); - } else { - ret = strdup(host); - if (!ret) - return -ENOMEM; - } - - *_ret = ret; - return 0; -} - -int getnameinfo_pretty(int fd, char **ret) { - union sockaddr_union sa; - socklen_t salen = sizeof(sa); - - assert(fd >= 0); - assert(ret); - - if (getsockname(fd, &sa.sa, &salen) < 0) - return -errno; - - return socknameinfo_pretty(&sa, salen, ret); -} - -int socket_address_unlink(SocketAddress *a) { - assert(a); - - if (socket_address_family(a) != AF_UNIX) - return 0; - - if (a->sockaddr.un.sun_path[0] == 0) - return 0; - - if (unlink(a->sockaddr.un.sun_path) < 0) - return -errno; - - return 1; -} - -static const char* const netlink_family_table[] = { - [NETLINK_ROUTE] = "route", - [NETLINK_FIREWALL] = "firewall", - [NETLINK_INET_DIAG] = "inet-diag", - [NETLINK_NFLOG] = "nflog", - [NETLINK_XFRM] = "xfrm", - [NETLINK_SELINUX] = "selinux", - [NETLINK_ISCSI] = "iscsi", - [NETLINK_AUDIT] = "audit", - [NETLINK_FIB_LOOKUP] = "fib-lookup", - [NETLINK_CONNECTOR] = "connector", - [NETLINK_NETFILTER] = "netfilter", - [NETLINK_IP6_FW] = "ip6-fw", - [NETLINK_DNRTMSG] = "dnrtmsg", - [NETLINK_KOBJECT_UEVENT] = "kobject-uevent", - [NETLINK_GENERIC] = "generic", - [NETLINK_SCSITRANSPORT] = "scsitransport", - [NETLINK_ECRYPTFS] = "ecryptfs" -}; - -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(netlink_family, int, INT_MAX); - -static const char* const socket_address_bind_ipv6_only_table[_SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX] = { - [SOCKET_ADDRESS_DEFAULT] = "default", - [SOCKET_ADDRESS_BOTH] = "both", - [SOCKET_ADDRESS_IPV6_ONLY] = "ipv6-only" -}; - -DEFINE_STRING_TABLE_LOOKUP(socket_address_bind_ipv6_only, SocketAddressBindIPv6Only); - -bool sockaddr_equal(const union sockaddr_union *a, const union sockaddr_union *b) { - assert(a); - assert(b); - - if (a->sa.sa_family != b->sa.sa_family) - return false; - - if (a->sa.sa_family == AF_INET) - return a->in.sin_addr.s_addr == b->in.sin_addr.s_addr; - - if (a->sa.sa_family == AF_INET6) - return memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a->in6.sin6_addr)) == 0; - - return false; -} - -int fd_inc_sndbuf(int fd, size_t n) { - int r, value; - socklen_t l = sizeof(value); - - r = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, &l); - if (r >= 0 && l == sizeof(value) && (size_t) value >= n*2) - return 0; - - /* If we have the privileges we will ignore the kernel limit. */ - - value = (int) n; - if (setsockopt(fd, SOL_SOCKET, SO_SNDBUFFORCE, &value, sizeof(value)) < 0) - if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)) < 0) - return -errno; - - return 1; -} - -int fd_inc_rcvbuf(int fd, size_t n) { - int r, value; - socklen_t l = sizeof(value); - - r = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, &l); - if (r >= 0 && l == sizeof(value) && (size_t) value >= n*2) - return 0; - - /* If we have the privileges we will ignore the kernel limit. */ - - value = (int) n; - if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &value, sizeof(value)) < 0) - if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)) < 0) - return -errno; - return 1; -} - -static const char* const ip_tos_table[] = { - [IPTOS_LOWDELAY] = "low-delay", - [IPTOS_THROUGHPUT] = "throughput", - [IPTOS_RELIABILITY] = "reliability", - [IPTOS_LOWCOST] = "low-cost", -}; - -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ip_tos, int, 0xff); - -bool ifname_valid(const char *p) { - bool numeric = true; - - /* Checks whether a network interface name is valid. This is inspired by dev_valid_name() in the kernel sources - * but slightly stricter, as we only allow non-control, non-space ASCII characters in the interface name. We - * also don't permit names that only container numbers, to avoid confusion with numeric interface indexes. */ - - if (isempty(p)) - return false; - - if (strlen(p) >= IFNAMSIZ) - return false; - - if (STR_IN_SET(p, ".", "..")) - return false; - - while (*p) { - if ((unsigned char) *p >= 127U) - return false; - - if ((unsigned char) *p <= 32U) - return false; - - if (*p == ':' || *p == '/') - return false; - - numeric = numeric && (*p >= '0' && *p <= '9'); - p++; - } - - if (numeric) - return false; - - return true; -} - -int getpeercred(int fd, struct ucred *ucred) { - socklen_t n = sizeof(struct ucred); - struct ucred u; - int r; - - assert(fd >= 0); - assert(ucred); - - r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &u, &n); - if (r < 0) - return -errno; - - if (n != sizeof(struct ucred)) - return -EIO; - - /* Check if the data is actually useful and not suppressed due - * to namespacing issues */ - if (u.pid <= 0) - return -ENODATA; - if (u.uid == UID_INVALID) - return -ENODATA; - if (u.gid == GID_INVALID) - return -ENODATA; - - *ucred = u; - return 0; -} - -int getpeersec(int fd, char **ret) { - socklen_t n = 64; - char *s; - int r; - - assert(fd >= 0); - assert(ret); - - s = new0(char, n); - if (!s) - return -ENOMEM; - - r = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n); - if (r < 0) { - free(s); - - if (errno != ERANGE) - return -errno; - - s = new0(char, n); - if (!s) - return -ENOMEM; - - r = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n); - if (r < 0) { - free(s); - return -errno; - } - } - - if (isempty(s)) { - free(s); - return -EOPNOTSUPP; - } - - *ret = s; - return 0; -} - -int send_one_fd_sa( - int transport_fd, - int fd, - const struct sockaddr *sa, socklen_t len, - int flags) { - - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(int))]; - } control = {}; - struct msghdr mh = { - .msg_name = (struct sockaddr*) sa, - .msg_namelen = len, - .msg_control = &control, - .msg_controllen = sizeof(control), - }; - struct cmsghdr *cmsg; - - assert(transport_fd >= 0); - assert(fd >= 0); - - cmsg = CMSG_FIRSTHDR(&mh); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - cmsg->cmsg_len = CMSG_LEN(sizeof(int)); - memcpy(CMSG_DATA(cmsg), &fd, sizeof(int)); - - mh.msg_controllen = CMSG_SPACE(sizeof(int)); - if (sendmsg(transport_fd, &mh, MSG_NOSIGNAL | flags) < 0) - return -errno; - - return 0; -} - -int receive_one_fd(int transport_fd, int flags) { - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(int))]; - } control = {}; - struct msghdr mh = { - .msg_control = &control, - .msg_controllen = sizeof(control), - }; - struct cmsghdr *cmsg, *found = NULL; - - assert(transport_fd >= 0); - - /* - * Receive a single FD via @transport_fd. We don't care for - * the transport-type. We retrieve a single FD at most, so for - * packet-based transports, the caller must ensure to send - * only a single FD per packet. This is best used in - * combination with send_one_fd(). - */ - - if (recvmsg(transport_fd, &mh, MSG_NOSIGNAL | MSG_CMSG_CLOEXEC | flags) < 0) - return -errno; - - CMSG_FOREACH(cmsg, &mh) { - if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SCM_RIGHTS && - cmsg->cmsg_len == CMSG_LEN(sizeof(int))) { - assert(!found); - found = cmsg; - break; - } - } - - if (!found) { - cmsg_close_all(&mh); - return -EIO; - } - - return *(int*) CMSG_DATA(found); -} - -ssize_t next_datagram_size_fd(int fd) { - ssize_t l; - int k; - - /* This is a bit like FIONREAD/SIOCINQ, however a bit more powerful. The difference being: recv(MSG_PEEK) will - * actually cause the next datagram in the queue to be validated regarding checksums, which FIONREAD doesn't - * do. This difference is actually of major importance as we need to be sure that the size returned here - * actually matches what we will read with recvmsg() next, as otherwise we might end up allocating a buffer of - * the wrong size. */ - - l = recv(fd, NULL, 0, MSG_PEEK|MSG_TRUNC); - if (l < 0) { - if (errno == EOPNOTSUPP) - goto fallback; - - return -errno; - } - if (l == 0) - goto fallback; - - return l; - -fallback: - k = 0; - - /* Some sockets (AF_PACKET) do not support null-sized recv() with MSG_TRUNC set, let's fall back to FIONREAD - * for them. Checksums don't matter for raw sockets anyway, hence this should be fine. */ - - if (ioctl(fd, FIONREAD, &k) < 0) - return -errno; - - return (ssize_t) k; -} - -int flush_accept(int fd) { - - struct pollfd pollfd = { - .fd = fd, - .events = POLLIN, - }; - int r; - - - /* Similar to flush_fd() but flushes all incoming connection by accepting them and immediately closing them. */ - - for (;;) { - int cfd; - - r = poll(&pollfd, 1, 0); - if (r < 0) { - if (errno == EINTR) - continue; - - return -errno; - - } else if (r == 0) - return 0; - - cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); - if (cfd < 0) { - if (errno == EINTR) - continue; - - if (errno == EAGAIN) - return 0; - - return -errno; - } - - close(cfd); - } -} diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h deleted file mode 100644 index e9230e4a9f..0000000000 --- a/src/basic/socket-util.h +++ /dev/null @@ -1,154 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include - -#include "macro.h" -#include "util.h" - -union sockaddr_union { - struct sockaddr sa; - struct sockaddr_in in; - struct sockaddr_in6 in6; - struct sockaddr_un un; - struct sockaddr_nl nl; - struct sockaddr_storage storage; - struct sockaddr_ll ll; -}; - -typedef struct SocketAddress { - union sockaddr_union sockaddr; - - /* We store the size here explicitly due to the weird - * sockaddr_un semantics for abstract sockets */ - socklen_t size; - - /* Socket type, i.e. SOCK_STREAM, SOCK_DGRAM, ... */ - int type; - - /* Socket protocol, IPPROTO_xxx, usually 0, except for netlink */ - int protocol; -} SocketAddress; - -typedef enum SocketAddressBindIPv6Only { - SOCKET_ADDRESS_DEFAULT, - SOCKET_ADDRESS_BOTH, - SOCKET_ADDRESS_IPV6_ONLY, - _SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX, - _SOCKET_ADDRESS_BIND_IPV6_ONLY_INVALID = -1 -} SocketAddressBindIPv6Only; - -#define socket_address_family(a) ((a)->sockaddr.sa.sa_family) - -int socket_address_parse(SocketAddress *a, const char *s); -int socket_address_parse_and_warn(SocketAddress *a, const char *s); -int socket_address_parse_netlink(SocketAddress *a, const char *s); -int socket_address_print(const SocketAddress *a, char **p); -int socket_address_verify(const SocketAddress *a) _pure_; -int socket_address_unlink(SocketAddress *a); - -bool socket_address_can_accept(const SocketAddress *a) _pure_; - -int socket_address_listen( - const SocketAddress *a, - int flags, - int backlog, - SocketAddressBindIPv6Only only, - const char *bind_to_device, - bool reuse_port, - bool free_bind, - bool transparent, - mode_t directory_mode, - mode_t socket_mode, - const char *label); -int make_socket_fd(int log_level, const char* address, int type, int flags); - -bool socket_address_is(const SocketAddress *a, const char *s, int type); -bool socket_address_is_netlink(const SocketAddress *a, const char *s); - -bool socket_address_matches_fd(const SocketAddress *a, int fd); - -bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) _pure_; - -const char* socket_address_get_path(const SocketAddress *a); - -bool socket_ipv6_is_supported(void); - -int sockaddr_port(const struct sockaddr *_sa) _pure_; - -int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ipv6, bool include_port, char **ret); -int getpeername_pretty(int fd, bool include_port, char **ret); -int getsockname_pretty(int fd, char **ret); - -int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret); -int getnameinfo_pretty(int fd, char **ret); - -const char* socket_address_bind_ipv6_only_to_string(SocketAddressBindIPv6Only b) _const_; -SocketAddressBindIPv6Only socket_address_bind_ipv6_only_from_string(const char *s) _pure_; - -int netlink_family_to_string_alloc(int b, char **s); -int netlink_family_from_string(const char *s) _pure_; - -bool sockaddr_equal(const union sockaddr_union *a, const union sockaddr_union *b); - -int fd_inc_sndbuf(int fd, size_t n); -int fd_inc_rcvbuf(int fd, size_t n); - -int ip_tos_to_string_alloc(int i, char **s); -int ip_tos_from_string(const char *s); - -bool ifname_valid(const char *p); - -int getpeercred(int fd, struct ucred *ucred); -int getpeersec(int fd, char **ret); - -int send_one_fd_sa(int transport_fd, - int fd, - const struct sockaddr *sa, socklen_t len, - int flags); -#define send_one_fd(transport_fd, fd, flags) send_one_fd_sa(transport_fd, fd, NULL, 0, flags) -int receive_one_fd(int transport_fd, int flags); - -ssize_t next_datagram_size_fd(int fd); - -int flush_accept(int fd); - -#define CMSG_FOREACH(cmsg, mh) \ - for ((cmsg) = CMSG_FIRSTHDR(mh); (cmsg); (cmsg) = CMSG_NXTHDR((mh), (cmsg))) - -/* Covers only file system and abstract AF_UNIX socket addresses, but not unnamed socket addresses. */ -#define SOCKADDR_UN_LEN(sa) \ - ({ \ - const struct sockaddr_un *_sa = &(sa); \ - assert(_sa->sun_family == AF_UNIX); \ - offsetof(struct sockaddr_un, sun_path) + \ - (_sa->sun_path[0] == 0 ? \ - 1 + strnlen(_sa->sun_path+1, sizeof(_sa->sun_path)-1) : \ - strnlen(_sa->sun_path, sizeof(_sa->sun_path))); \ - }) diff --git a/src/basic/sparse-endian.h b/src/basic/sparse-endian.h deleted file mode 100644 index c913fda8c5..0000000000 --- a/src/basic/sparse-endian.h +++ /dev/null @@ -1,88 +0,0 @@ -/* Copyright (c) 2012 Josh Triplett - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ -#ifndef SPARSE_ENDIAN_H -#define SPARSE_ENDIAN_H - -#include -#include -#include - -#ifdef __CHECKER__ -#define __bitwise __attribute__((bitwise)) -#define __force __attribute__((force)) -#else -#define __bitwise -#define __force -#endif - -typedef uint16_t __bitwise le16_t; -typedef uint16_t __bitwise be16_t; -typedef uint32_t __bitwise le32_t; -typedef uint32_t __bitwise be32_t; -typedef uint64_t __bitwise le64_t; -typedef uint64_t __bitwise be64_t; - -#undef htobe16 -#undef htole16 -#undef be16toh -#undef le16toh -#undef htobe32 -#undef htole32 -#undef be32toh -#undef le32toh -#undef htobe64 -#undef htole64 -#undef be64toh -#undef le64toh - -#if __BYTE_ORDER == __LITTLE_ENDIAN -#define bswap_16_on_le(x) __bswap_16(x) -#define bswap_32_on_le(x) __bswap_32(x) -#define bswap_64_on_le(x) __bswap_64(x) -#define bswap_16_on_be(x) (x) -#define bswap_32_on_be(x) (x) -#define bswap_64_on_be(x) (x) -#elif __BYTE_ORDER == __BIG_ENDIAN -#define bswap_16_on_le(x) (x) -#define bswap_32_on_le(x) (x) -#define bswap_64_on_le(x) (x) -#define bswap_16_on_be(x) __bswap_16(x) -#define bswap_32_on_be(x) __bswap_32(x) -#define bswap_64_on_be(x) __bswap_64(x) -#endif - -static inline le16_t htole16(uint16_t value) { return (le16_t __force) bswap_16_on_be(value); } -static inline le32_t htole32(uint32_t value) { return (le32_t __force) bswap_32_on_be(value); } -static inline le64_t htole64(uint64_t value) { return (le64_t __force) bswap_64_on_be(value); } - -static inline be16_t htobe16(uint16_t value) { return (be16_t __force) bswap_16_on_le(value); } -static inline be32_t htobe32(uint32_t value) { return (be32_t __force) bswap_32_on_le(value); } -static inline be64_t htobe64(uint64_t value) { return (be64_t __force) bswap_64_on_le(value); } - -static inline uint16_t le16toh(le16_t value) { return bswap_16_on_be((uint16_t __force)value); } -static inline uint32_t le32toh(le32_t value) { return bswap_32_on_be((uint32_t __force)value); } -static inline uint64_t le64toh(le64_t value) { return bswap_64_on_be((uint64_t __force)value); } - -static inline uint16_t be16toh(be16_t value) { return bswap_16_on_le((uint16_t __force)value); } -static inline uint32_t be32toh(be32_t value) { return bswap_32_on_le((uint32_t __force)value); } -static inline uint64_t be64toh(be64_t value) { return bswap_64_on_le((uint64_t __force)value); } - -#endif /* SPARSE_ENDIAN_H */ diff --git a/src/basic/special.h b/src/basic/special.h deleted file mode 100644 index 084d3dfa23..0000000000 --- a/src/basic/special.h +++ /dev/null @@ -1,119 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#define SPECIAL_DEFAULT_TARGET "default.target" - -/* Shutdown targets */ -#define SPECIAL_UMOUNT_TARGET "umount.target" -/* This is not really intended to be started by directly. This is - * mostly so that other targets (reboot/halt/poweroff) can depend on - * it to bring all services down that want to be brought down on - * system shutdown. */ -#define SPECIAL_SHUTDOWN_TARGET "shutdown.target" -#define SPECIAL_HALT_TARGET "halt.target" -#define SPECIAL_POWEROFF_TARGET "poweroff.target" -#define SPECIAL_REBOOT_TARGET "reboot.target" -#define SPECIAL_KEXEC_TARGET "kexec.target" -#define SPECIAL_EXIT_TARGET "exit.target" -#define SPECIAL_SUSPEND_TARGET "suspend.target" -#define SPECIAL_HIBERNATE_TARGET "hibernate.target" -#define SPECIAL_HYBRID_SLEEP_TARGET "hybrid-sleep.target" - -/* Special boot targets */ -#define SPECIAL_RESCUE_TARGET "rescue.target" -#define SPECIAL_EMERGENCY_TARGET "emergency.target" -#define SPECIAL_MULTI_USER_TARGET "multi-user.target" -#define SPECIAL_GRAPHICAL_TARGET "graphical.target" - -/* Early boot targets */ -#define SPECIAL_SYSINIT_TARGET "sysinit.target" -#define SPECIAL_SOCKETS_TARGET "sockets.target" -#define SPECIAL_BUSNAMES_TARGET "busnames.target" -#define SPECIAL_TIMERS_TARGET "timers.target" -#define SPECIAL_PATHS_TARGET "paths.target" -#define SPECIAL_LOCAL_FS_TARGET "local-fs.target" -#define SPECIAL_LOCAL_FS_PRE_TARGET "local-fs-pre.target" -#define SPECIAL_INITRD_FS_TARGET "initrd-fs.target" -#define SPECIAL_INITRD_ROOT_DEVICE_TARGET "initrd-root-device.target" -#define SPECIAL_INITRD_ROOT_FS_TARGET "initrd-root-fs.target" -#define SPECIAL_REMOTE_FS_TARGET "remote-fs.target" /* LSB's $remote_fs */ -#define SPECIAL_REMOTE_FS_PRE_TARGET "remote-fs-pre.target" -#define SPECIAL_SWAP_TARGET "swap.target" -#define SPECIAL_NETWORK_ONLINE_TARGET "network-online.target" -#define SPECIAL_TIME_SYNC_TARGET "time-sync.target" /* LSB's $time */ -#define SPECIAL_BASIC_TARGET "basic.target" - -/* LSB compatibility */ -#define SPECIAL_NETWORK_TARGET "network.target" /* LSB's $network */ -#define SPECIAL_NSS_LOOKUP_TARGET "nss-lookup.target" /* LSB's $named */ -#define SPECIAL_RPCBIND_TARGET "rpcbind.target" /* LSB's $portmap */ - -/* - * Rules regarding adding further high level targets like the above: - * - * - Be conservative, only add more of these when we really need - * them. We need strong usecases for further additions. - * - * - When there can be multiple implementations running side-by-side, - * it needs to be a .target unit which can pull in all - * implementations. - * - * - If something can be implemented with socket activation, and - * without, it needs to be a .target unit, so that it can pull in - * the appropriate unit. - * - * - Otherwise, it should be a .service unit. - * - * - In some cases it is OK to have both a .service and a .target - * unit, i.e. if there can be multiple parallel implementations, but - * only one is the "system" one. Example: syslog. - * - * Or to put this in other words: .service symlinks can be used to - * arbitrate between multiple implementations if there can be only one - * of a kind. .target units can be used to support multiple - * implementations that can run side-by-side. - */ - -/* Magic early boot services */ -#define SPECIAL_FSCK_SERVICE "systemd-fsck@.service" -#define SPECIAL_QUOTACHECK_SERVICE "systemd-quotacheck.service" -#define SPECIAL_QUOTAON_SERVICE "quotaon.service" -#define SPECIAL_REMOUNT_FS_SERVICE "systemd-remount-fs.service" - -/* Services systemd relies on */ -#define SPECIAL_DBUS_SERVICE "dbus.service" -#define SPECIAL_DBUS_SOCKET "dbus.socket" -#define SPECIAL_JOURNALD_SOCKET "systemd-journald.socket" -#define SPECIAL_JOURNALD_SERVICE "systemd-journald.service" - -/* Magic init signals */ -#define SPECIAL_KBREQUEST_TARGET "kbrequest.target" -#define SPECIAL_SIGPWR_TARGET "sigpwr.target" -#define SPECIAL_CTRL_ALT_DEL_TARGET "ctrl-alt-del.target" - -/* Where we add all our system units, users and machines by default */ -#define SPECIAL_SYSTEM_SLICE "system.slice" -#define SPECIAL_USER_SLICE "user.slice" -#define SPECIAL_MACHINE_SLICE "machine.slice" -#define SPECIAL_ROOT_SLICE "-.slice" - -/* The scope unit systemd itself lives in. */ -#define SPECIAL_INIT_SCOPE "init.scope" diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c deleted file mode 100644 index 309e84b93d..0000000000 --- a/src/basic/stat-util.c +++ /dev/null @@ -1,218 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010-2012 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 -#include -#include -#include -#include -#include -#include - -#include "dirent-util.h" -#include "fd-util.h" -#include "macro.h" -#include "missing.h" -#include "stat-util.h" -#include "string-util.h" - -int is_symlink(const char *path) { - struct stat info; - - assert(path); - - if (lstat(path, &info) < 0) - return -errno; - - return !!S_ISLNK(info.st_mode); -} - -int is_dir(const char* path, bool follow) { - struct stat st; - int r; - - assert(path); - - if (follow) - r = stat(path, &st); - else - r = lstat(path, &st); - if (r < 0) - return -errno; - - return !!S_ISDIR(st.st_mode); -} - -int is_device_node(const char *path) { - struct stat info; - - assert(path); - - if (lstat(path, &info) < 0) - return -errno; - - return !!(S_ISBLK(info.st_mode) || S_ISCHR(info.st_mode)); -} - -int dir_is_empty(const char *path) { - _cleanup_closedir_ DIR *d; - struct dirent *de; - - d = opendir(path); - if (!d) - return -errno; - - FOREACH_DIRENT(de, d, return -errno) - return 0; - - return 1; -} - -bool null_or_empty(struct stat *st) { - assert(st); - - if (S_ISREG(st->st_mode) && st->st_size <= 0) - return true; - - /* We don't want to hardcode the major/minor of /dev/null, - * hence we do a simpler "is this a device node?" check. */ - - if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) - return true; - - return false; -} - -int null_or_empty_path(const char *fn) { - struct stat st; - - assert(fn); - - if (stat(fn, &st) < 0) - return -errno; - - return null_or_empty(&st); -} - -int null_or_empty_fd(int fd) { - struct stat st; - - assert(fd >= 0); - - if (fstat(fd, &st) < 0) - return -errno; - - return null_or_empty(&st); -} - -int path_is_read_only_fs(const char *path) { - struct statvfs st; - - assert(path); - - if (statvfs(path, &st) < 0) - return -errno; - - if (st.f_flag & ST_RDONLY) - return true; - - /* On NFS, statvfs() might not reflect whether we can actually - * write to the remote share. Let's try again with - * access(W_OK) which is more reliable, at least sometimes. */ - if (access(path, W_OK) < 0 && errno == EROFS) - return true; - - return false; -} - -int path_is_os_tree(const char *path) { - char *p; - int r; - - assert(path); - - /* We use /usr/lib/os-release as flag file if something is an OS */ - p = strjoina(path, "/usr/lib/os-release"); - r = access(p, F_OK); - if (r >= 0) - return 1; - - /* Also check for the old location in /etc, just in case. */ - p = strjoina(path, "/etc/os-release"); - r = access(p, F_OK); - - return r >= 0; -} - -int files_same(const char *filea, const char *fileb) { - struct stat a, b; - - assert(filea); - assert(fileb); - - if (stat(filea, &a) < 0) - return -errno; - - if (stat(fileb, &b) < 0) - return -errno; - - return a.st_dev == b.st_dev && - a.st_ino == b.st_ino; -} - -bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) { - assert(s); - assert_cc(sizeof(statfs_f_type_t) >= sizeof(s->f_type)); - - return F_TYPE_EQUAL(s->f_type, magic_value); -} - -int fd_check_fstype(int fd, statfs_f_type_t magic_value) { - struct statfs s; - - if (fstatfs(fd, &s) < 0) - return -errno; - - return is_fs_type(&s, magic_value); -} - -int path_check_fstype(const char *path, statfs_f_type_t magic_value) { - _cleanup_close_ int fd = -1; - - fd = open(path, O_RDONLY); - if (fd < 0) - return -errno; - - return fd_check_fstype(fd, magic_value); -} - -bool is_temporary_fs(const struct statfs *s) { - return is_fs_type(s, TMPFS_MAGIC) || - is_fs_type(s, RAMFS_MAGIC); -} - -int fd_is_temporary_fs(int fd) { - struct statfs s; - - if (fstatfs(fd, &s) < 0) - return -errno; - - return is_temporary_fs(&s); -} diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h deleted file mode 100644 index 56d28f791e..0000000000 --- a/src/basic/stat-util.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010-2012 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 -#include -#include -#include -#include - -#include "macro.h" - -int is_symlink(const char *path); -int is_dir(const char *path, bool follow); -int is_device_node(const char *path); - -int dir_is_empty(const char *path); - -static inline int dir_is_populated(const char *path) { - int r; - r = dir_is_empty(path); - if (r < 0) - return r; - return !r; -} - -bool null_or_empty(struct stat *st) _pure_; -int null_or_empty_path(const char *fn); -int null_or_empty_fd(int fd); - -int path_is_read_only_fs(const char *path); -int path_is_os_tree(const char *path); - -int files_same(const char *filea, const char *fileb); - -/* The .f_type field of struct statfs is really weird defined on - * different archs. Let's give its type a name. */ -typedef typeof(((struct statfs*)NULL)->f_type) statfs_f_type_t; - -bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) _pure_; -int fd_check_fstype(int fd, statfs_f_type_t magic_value); -int path_check_fstype(const char *path, statfs_f_type_t magic_value); - -bool is_temporary_fs(const struct statfs *s) _pure_; -int fd_is_temporary_fs(int fd); - -/* Because statfs.t_type can be int on some architectures, we have to cast - * the const magic to the type, otherwise the compiler warns about - * signed/unsigned comparison, because the magic can be 32 bit unsigned. - */ -#define F_TYPE_EQUAL(a, b) (a == (typeof(a)) b) diff --git a/src/basic/stdio-util.h b/src/basic/stdio-util.h deleted file mode 100644 index bd1144b4c9..0000000000 --- a/src/basic/stdio-util.h +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include - -#include "macro.h" - -#define xsprintf(buf, fmt, ...) \ - assert_message_se((size_t) snprintf(buf, ELEMENTSOF(buf), fmt, __VA_ARGS__) < ELEMENTSOF(buf), "xsprintf: " #buf "[] must be big enough") - - -#define VA_FORMAT_ADVANCE(format, ap) \ -do { \ - int _argtypes[128]; \ - size_t _i, _k; \ - _k = parse_printf_format((format), ELEMENTSOF(_argtypes), _argtypes); \ - assert(_k < ELEMENTSOF(_argtypes)); \ - for (_i = 0; _i < _k; _i++) { \ - if (_argtypes[_i] & PA_FLAG_PTR) { \ - (void) va_arg(ap, void*); \ - continue; \ - } \ - \ - switch (_argtypes[_i]) { \ - case PA_INT: \ - case PA_INT|PA_FLAG_SHORT: \ - case PA_CHAR: \ - (void) va_arg(ap, int); \ - break; \ - case PA_INT|PA_FLAG_LONG: \ - (void) va_arg(ap, long int); \ - break; \ - case PA_INT|PA_FLAG_LONG_LONG: \ - (void) va_arg(ap, long long int); \ - break; \ - case PA_WCHAR: \ - (void) va_arg(ap, wchar_t); \ - break; \ - case PA_WSTRING: \ - case PA_STRING: \ - case PA_POINTER: \ - (void) va_arg(ap, void*); \ - break; \ - case PA_FLOAT: \ - case PA_DOUBLE: \ - (void) va_arg(ap, double); \ - break; \ - case PA_DOUBLE|PA_FLAG_LONG_DOUBLE: \ - (void) va_arg(ap, long double); \ - break; \ - default: \ - assert_not_reached("Unknown format string argument."); \ - } \ - } \ -} while (false) diff --git a/src/basic/strbuf.c b/src/basic/strbuf.c deleted file mode 100644 index 4bef87d3c2..0000000000 --- a/src/basic/strbuf.c +++ /dev/null @@ -1,205 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 -#include -#include - -#include "alloc-util.h" -#include "strbuf.h" - -/* - * Strbuf stores given strings in a single continuous allocated memory - * area. Identical strings are de-duplicated and return the same offset - * as the first string stored. If the tail of a string already exists - * in the buffer, the tail is returned. - * - * A trie (http://en.wikipedia.org/wiki/Trie) is used to maintain the - * information about the stored strings. - * - * Example of udev rules: - * $ ./udevadm test . - * ... - * read rules file: /usr/lib/udev/rules.d/99-systemd.rules - * rules contain 196608 bytes tokens (16384 * 12 bytes), 39742 bytes strings - * 23939 strings (207859 bytes), 20404 de-duplicated (171653 bytes), 3536 trie nodes used - * ... - */ - -struct strbuf *strbuf_new(void) { - struct strbuf *str; - - str = new0(struct strbuf, 1); - if (!str) - return NULL; - - str->buf = new0(char, 1); - if (!str->buf) - goto err; - str->len = 1; - - str->root = new0(struct strbuf_node, 1); - if (!str->root) - goto err; - str->nodes_count = 1; - return str; -err: - free(str->buf); - free(str->root); - free(str); - return NULL; -} - -static void strbuf_node_cleanup(struct strbuf_node *node) { - size_t i; - - for (i = 0; i < node->children_count; i++) - strbuf_node_cleanup(node->children[i].child); - free(node->children); - free(node); -} - -/* clean up trie data, leave only the string buffer */ -void strbuf_complete(struct strbuf *str) { - if (!str) - return; - if (str->root) - strbuf_node_cleanup(str->root); - str->root = NULL; -} - -/* clean up everything */ -void strbuf_cleanup(struct strbuf *str) { - if (!str) - return; - if (str->root) - strbuf_node_cleanup(str->root); - free(str->buf); - free(str); -} - -static int strbuf_children_cmp(const struct strbuf_child_entry *n1, - const struct strbuf_child_entry *n2) { - return n1->c - n2->c; -} - -static void bubbleinsert(struct strbuf_node *node, - uint8_t c, - struct strbuf_node *node_child) { - - struct strbuf_child_entry new = { - .c = c, - .child = node_child, - }; - int left = 0, right = node->children_count; - - while (right > left) { - int middle = (right + left) / 2 ; - if (strbuf_children_cmp(&node->children[middle], &new) <= 0) - left = middle + 1; - else - right = middle; - } - - memmove(node->children + left + 1, node->children + left, - sizeof(struct strbuf_child_entry) * (node->children_count - left)); - node->children[left] = new; - - node->children_count++; -} - -/* add string, return the index/offset into the buffer */ -ssize_t strbuf_add_string(struct strbuf *str, const char *s, size_t len) { - uint8_t c; - struct strbuf_node *node; - size_t depth; - char *buf_new; - struct strbuf_child_entry *child; - struct strbuf_node *node_child; - ssize_t off; - - if (!str->root) - return -EINVAL; - - /* search string; start from last character to find possibly matching tails */ - if (len == 0) - return 0; - str->in_count++; - str->in_len += len; - - node = str->root; - c = s[len-1]; - for (depth = 0; depth <= len; depth++) { - struct strbuf_child_entry search; - - /* match against current node */ - off = node->value_off + node->value_len - len; - if (depth == len || (node->value_len >= len && memcmp(str->buf + off, s, len) == 0)) { - str->dedup_len += len; - str->dedup_count++; - return off; - } - - c = s[len - 1 - depth]; - - /* bsearch is not allowed on a NULL sequence */ - if (node->children_count == 0) - break; - - /* lookup child node */ - search.c = c; - child = bsearch(&search, node->children, node->children_count, - sizeof(struct strbuf_child_entry), - (__compar_fn_t) strbuf_children_cmp); - if (!child) - break; - node = child->child; - } - - /* add new string */ - buf_new = realloc(str->buf, str->len + len+1); - if (!buf_new) - return -ENOMEM; - str->buf = buf_new; - off = str->len; - memcpy(str->buf + off, s, len); - str->len += len; - str->buf[str->len++] = '\0'; - - /* new node */ - node_child = new0(struct strbuf_node, 1); - if (!node_child) - return -ENOMEM; - node_child->value_off = off; - node_child->value_len = len; - - /* extend array, add new entry, sort for bisection */ - child = realloc(node->children, (node->children_count + 1) * sizeof(struct strbuf_child_entry)); - if (!child) { - free(node_child); - return -ENOMEM; - } - - str->nodes_count++; - - node->children = child; - bubbleinsert(node, c, node_child); - - return off; -} diff --git a/src/basic/strbuf.h b/src/basic/strbuf.h deleted file mode 100644 index a1632da0e8..0000000000 --- a/src/basic/strbuf.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 -#include -#include - -struct strbuf { - char *buf; - size_t len; - struct strbuf_node *root; - - size_t nodes_count; - size_t in_count; - size_t in_len; - size_t dedup_len; - size_t dedup_count; -}; - -struct strbuf_node { - size_t value_off; - size_t value_len; - - struct strbuf_child_entry *children; - uint8_t children_count; -}; - -struct strbuf_child_entry { - uint8_t c; - struct strbuf_node *child; -}; - -struct strbuf *strbuf_new(void); -ssize_t strbuf_add_string(struct strbuf *str, const char *s, size_t len); -void strbuf_complete(struct strbuf *str); -void strbuf_cleanup(struct strbuf *str); diff --git a/src/basic/string-table.c b/src/basic/string-table.c deleted file mode 100644 index a1499ab126..0000000000 --- a/src/basic/string-table.c +++ /dev/null @@ -1,34 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 "string-table.h" -#include "string-util.h" - -ssize_t string_table_lookup(const char * const *table, size_t len, const char *key) { - size_t i; - - if (!key) - return -1; - - for (i = 0; i < len; ++i) - if (streq_ptr(table[i], key)) - return (ssize_t) i; - - return -1; -} diff --git a/src/basic/string-table.h b/src/basic/string-table.h deleted file mode 100644 index d88625fca7..0000000000 --- a/src/basic/string-table.h +++ /dev/null @@ -1,117 +0,0 @@ - -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include - -#include "macro.h" -#include "parse-util.h" -#include "string-util.h" - -ssize_t string_table_lookup(const char * const *table, size_t len, const char *key); - -/* For basic lookup tables with strictly enumerated entries */ -#define _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ - scope const char *name##_to_string(type i) { \ - if (i < 0 || i >= (type) ELEMENTSOF(name##_table)) \ - return NULL; \ - return name##_table[i]; \ - } - -#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope) \ - scope type name##_from_string(const char *s) { \ - return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \ - } - -#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \ - scope type name##_from_string(const char *s) { \ - int b; \ - b = parse_boolean(s); \ - if (b == 0) \ - return (type) 0; \ - else if (b > 0) \ - return yes; \ - return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \ - } - -#define _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,scope) \ - scope int name##_to_string_alloc(type i, char **str) { \ - char *s; \ - if (i < 0 || i > max) \ - return -ERANGE; \ - if (i < (type) ELEMENTSOF(name##_table)) { \ - s = strdup(name##_table[i]); \ - if (!s) \ - return -ENOMEM; \ - } else { \ - if (asprintf(&s, "%i", i) < 0) \ - return -ENOMEM; \ - } \ - *str = s; \ - return 0; \ - } - -#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,scope) \ - type name##_from_string(const char *s) { \ - type i; \ - unsigned u = 0; \ - if (!s) \ - return (type) -1; \ - for (i = 0; i < (type) ELEMENTSOF(name##_table); i++) \ - if (streq_ptr(name##_table[i], s)) \ - return i; \ - if (safe_atou(s, &u) >= 0 && u <= max) \ - return (type) u; \ - return (type) -1; \ - } \ - - -#define _DEFINE_STRING_TABLE_LOOKUP(name,type,scope) \ - _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ - _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope) \ - struct __useless_struct_to_allow_trailing_semicolon__ - -#define _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,scope) \ - _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ - _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \ - struct __useless_struct_to_allow_trailing_semicolon__ - -#define DEFINE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,) -#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,static) -#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,static) -#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,static) - -#define DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes) _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,) - -/* For string conversions where numbers are also acceptable */ -#define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max) \ - _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,) \ - _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,) \ - struct __useless_struct_to_allow_trailing_semicolon__ - -#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max) \ - _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,static) -#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max) \ - _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,static) diff --git a/src/basic/string-util.c b/src/basic/string-util.c deleted file mode 100644 index 293a15f9c0..0000000000 --- a/src/basic/string-util.c +++ /dev/null @@ -1,855 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include - -#include "alloc-util.h" -#include "gunicode.h" -#include "macro.h" -#include "string-util.h" -#include "utf8.h" -#include "util.h" - -int strcmp_ptr(const char *a, const char *b) { - - /* Like strcmp(), but tries to make sense of NULL pointers */ - if (a && b) - return strcmp(a, b); - - if (!a && b) - return -1; - - if (a && !b) - return 1; - - return 0; -} - -char* endswith(const char *s, const char *postfix) { - size_t sl, pl; - - assert(s); - assert(postfix); - - sl = strlen(s); - pl = strlen(postfix); - - if (pl == 0) - return (char*) s + sl; - - if (sl < pl) - return NULL; - - if (memcmp(s + sl - pl, postfix, pl) != 0) - return NULL; - - return (char*) s + sl - pl; -} - -char* endswith_no_case(const char *s, const char *postfix) { - size_t sl, pl; - - assert(s); - assert(postfix); - - sl = strlen(s); - pl = strlen(postfix); - - if (pl == 0) - return (char*) s + sl; - - if (sl < pl) - return NULL; - - if (strcasecmp(s + sl - pl, postfix) != 0) - return NULL; - - return (char*) s + sl - pl; -} - -char* first_word(const char *s, const char *word) { - size_t sl, wl; - const char *p; - - assert(s); - assert(word); - - /* Checks if the string starts with the specified word, either - * followed by NUL or by whitespace. Returns a pointer to the - * NUL or the first character after the whitespace. */ - - sl = strlen(s); - wl = strlen(word); - - if (sl < wl) - return NULL; - - if (wl == 0) - return (char*) s; - - if (memcmp(s, word, wl) != 0) - return NULL; - - p = s + wl; - if (*p == 0) - return (char*) p; - - if (!strchr(WHITESPACE, *p)) - return NULL; - - p += strspn(p, WHITESPACE); - return (char*) p; -} - -static size_t strcspn_escaped(const char *s, const char *reject) { - bool escaped = false; - int n; - - for (n=0; s[n]; n++) { - if (escaped) - escaped = false; - else if (s[n] == '\\') - escaped = true; - else if (strchr(reject, s[n])) - break; - } - - /* if s ends in \, return index of previous char */ - return n - escaped; -} - -/* Split a string into words. */ -const char* split(const char **state, size_t *l, const char *separator, bool quoted) { - const char *current; - - current = *state; - - if (!*current) { - assert(**state == '\0'); - return NULL; - } - - current += strspn(current, separator); - if (!*current) { - *state = current; - return NULL; - } - - if (quoted && strchr("\'\"", *current)) { - char quotechars[2] = {*current, '\0'}; - - *l = strcspn_escaped(current + 1, quotechars); - if (current[*l + 1] == '\0' || current[*l + 1] != quotechars[0] || - (current[*l + 2] && !strchr(separator, current[*l + 2]))) { - /* right quote missing or garbage at the end */ - *state = current; - return NULL; - } - *state = current++ + *l + 2; - } else if (quoted) { - *l = strcspn_escaped(current, separator); - if (current[*l] && !strchr(separator, current[*l])) { - /* unfinished escape */ - *state = current; - return NULL; - } - *state = current + *l; - } else { - *l = strcspn(current, separator); - *state = current + *l; - } - - return current; -} - -char *strnappend(const char *s, const char *suffix, size_t b) { - size_t a; - char *r; - - if (!s && !suffix) - return strdup(""); - - if (!s) - return strndup(suffix, b); - - if (!suffix) - return strdup(s); - - assert(s); - assert(suffix); - - a = strlen(s); - if (b > ((size_t) -1) - a) - return NULL; - - r = new(char, a+b+1); - if (!r) - return NULL; - - memcpy(r, s, a); - memcpy(r+a, suffix, b); - r[a+b] = 0; - - return r; -} - -char *strappend(const char *s, const char *suffix) { - return strnappend(s, suffix, suffix ? strlen(suffix) : 0); -} - -char *strjoin(const char *x, ...) { - va_list ap; - size_t l; - char *r, *p; - - va_start(ap, x); - - if (x) { - l = strlen(x); - - for (;;) { - const char *t; - size_t n; - - t = va_arg(ap, const char *); - if (!t) - break; - - n = strlen(t); - if (n > ((size_t) -1) - l) { - va_end(ap); - return NULL; - } - - l += n; - } - } else - l = 0; - - va_end(ap); - - r = new(char, l+1); - if (!r) - return NULL; - - if (x) { - p = stpcpy(r, x); - - va_start(ap, x); - - for (;;) { - const char *t; - - t = va_arg(ap, const char *); - if (!t) - break; - - p = stpcpy(p, t); - } - - va_end(ap); - } else - r[0] = 0; - - return r; -} - -char *strstrip(char *s) { - char *e; - - /* Drops trailing whitespace. Modifies the string in - * place. Returns pointer to first non-space character */ - - s += strspn(s, WHITESPACE); - - for (e = strchr(s, 0); e > s; e --) - if (!strchr(WHITESPACE, e[-1])) - break; - - *e = 0; - - return s; -} - -char *delete_chars(char *s, const char *bad) { - char *f, *t; - - /* Drops all whitespace, regardless where in the string */ - - for (f = s, t = s; *f; f++) { - if (strchr(bad, *f)) - continue; - - *(t++) = *f; - } - - *t = 0; - - return s; -} - -char *truncate_nl(char *s) { - assert(s); - - s[strcspn(s, NEWLINE)] = 0; - return s; -} - -char ascii_tolower(char x) { - - if (x >= 'A' && x <= 'Z') - return x - 'A' + 'a'; - - return x; -} - -char *ascii_strlower(char *t) { - char *p; - - assert(t); - - for (p = t; *p; p++) - *p = ascii_tolower(*p); - - return t; -} - -char *ascii_strlower_n(char *t, size_t n) { - size_t i; - - if (n <= 0) - return t; - - for (i = 0; i < n; i++) - t[i] = ascii_tolower(t[i]); - - return t; -} - -int ascii_strcasecmp_n(const char *a, const char *b, size_t n) { - - for (; n > 0; a++, b++, n--) { - int x, y; - - x = (int) (uint8_t) ascii_tolower(*a); - y = (int) (uint8_t) ascii_tolower(*b); - - if (x != y) - return x - y; - } - - return 0; -} - -int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m) { - int r; - - r = ascii_strcasecmp_n(a, b, MIN(n, m)); - if (r != 0) - return r; - - if (n < m) - return -1; - else if (n > m) - return 1; - else - return 0; -} - -bool chars_intersect(const char *a, const char *b) { - const char *p; - - /* Returns true if any of the chars in a are in b. */ - for (p = a; *p; p++) - if (strchr(b, *p)) - return true; - - return false; -} - -bool string_has_cc(const char *p, const char *ok) { - const char *t; - - assert(p); - - /* - * Check if a string contains control characters. If 'ok' is - * non-NULL it may be a string containing additional CCs to be - * considered OK. - */ - - for (t = p; *t; t++) { - if (ok && strchr(ok, *t)) - continue; - - if (*t > 0 && *t < ' ') - return true; - - if (*t == 127) - return true; - } - - return false; -} - -static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { - size_t x; - char *r; - - assert(s); - assert(percent <= 100); - assert(new_length >= 3); - - if (old_length <= 3 || old_length <= new_length) - return strndup(s, old_length); - - r = new0(char, new_length+1); - if (!r) - return NULL; - - x = (new_length * percent) / 100; - - if (x > new_length - 3) - x = new_length - 3; - - memcpy(r, s, x); - r[x] = '.'; - r[x+1] = '.'; - r[x+2] = '.'; - memcpy(r + x + 3, - s + old_length - (new_length - x - 3), - new_length - x - 3); - - return r; -} - -char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { - size_t x; - char *e; - const char *i, *j; - unsigned k, len, len2; - int r; - - assert(s); - assert(percent <= 100); - assert(new_length >= 3); - - /* if no multibyte characters use ascii_ellipsize_mem for speed */ - if (ascii_is_valid(s)) - return ascii_ellipsize_mem(s, old_length, new_length, percent); - - if (old_length <= 3 || old_length <= new_length) - return strndup(s, old_length); - - x = (new_length * percent) / 100; - - if (x > new_length - 3) - x = new_length - 3; - - k = 0; - for (i = s; k < x && i < s + old_length; i = utf8_next_char(i)) { - char32_t c; - - r = utf8_encoded_to_unichar(i, &c); - if (r < 0) - return NULL; - k += unichar_iswide(c) ? 2 : 1; - } - - if (k > x) /* last character was wide and went over quota */ - x++; - - for (j = s + old_length; k < new_length && j > i; ) { - char32_t c; - - j = utf8_prev_char(j); - r = utf8_encoded_to_unichar(j, &c); - if (r < 0) - return NULL; - k += unichar_iswide(c) ? 2 : 1; - } - assert(i <= j); - - /* we don't actually need to ellipsize */ - if (i == j) - return memdup(s, old_length + 1); - - /* make space for ellipsis */ - j = utf8_next_char(j); - - len = i - s; - len2 = s + old_length - j; - e = new(char, len + 3 + len2 + 1); - if (!e) - return NULL; - - /* - printf("old_length=%zu new_length=%zu x=%zu len=%u len2=%u k=%u\n", - old_length, new_length, x, len, len2, k); - */ - - memcpy(e, s, len); - e[len] = 0xe2; /* tri-dot ellipsis: … */ - e[len + 1] = 0x80; - e[len + 2] = 0xa6; - - memcpy(e + len + 3, j, len2 + 1); - - return e; -} - -char *ellipsize(const char *s, size_t length, unsigned percent) { - return ellipsize_mem(s, strlen(s), length, percent); -} - -bool nulstr_contains(const char*nulstr, const char *needle) { - const char *i; - - if (!nulstr) - return false; - - NULSTR_FOREACH(i, nulstr) - if (streq(i, needle)) - return true; - - return false; -} - -char* strshorten(char *s, size_t l) { - assert(s); - - if (l < strlen(s)) - s[l] = 0; - - return s; -} - -char *strreplace(const char *text, const char *old_string, const char *new_string) { - const char *f; - char *t, *r; - size_t l, old_len, new_len; - - assert(text); - assert(old_string); - assert(new_string); - - old_len = strlen(old_string); - new_len = strlen(new_string); - - l = strlen(text); - r = new(char, l+1); - if (!r) - return NULL; - - f = text; - t = r; - while (*f) { - char *a; - size_t d, nl; - - if (!startswith(f, old_string)) { - *(t++) = *(f++); - continue; - } - - d = t - r; - nl = l - old_len + new_len; - a = realloc(r, nl + 1); - if (!a) - goto oom; - - l = nl; - r = a; - t = r + d; - - t = stpcpy(t, new_string); - f += old_len; - } - - *t = 0; - return r; - -oom: - free(r); - return NULL; -} - -char *strip_tab_ansi(char **ibuf, size_t *_isz) { - const char *i, *begin = NULL; - enum { - STATE_OTHER, - STATE_ESCAPE, - STATE_BRACKET - } state = STATE_OTHER; - char *obuf = NULL; - size_t osz = 0, isz; - FILE *f; - - assert(ibuf); - assert(*ibuf); - - /* Strips ANSI color and replaces TABs by 8 spaces */ - - isz = _isz ? *_isz : strlen(*ibuf); - - f = open_memstream(&obuf, &osz); - if (!f) - return NULL; - - for (i = *ibuf; i < *ibuf + isz + 1; i++) { - - switch (state) { - - case STATE_OTHER: - if (i >= *ibuf + isz) /* EOT */ - break; - else if (*i == '\x1B') - state = STATE_ESCAPE; - else if (*i == '\t') - fputs(" ", f); - else - fputc(*i, f); - break; - - case STATE_ESCAPE: - if (i >= *ibuf + isz) { /* EOT */ - fputc('\x1B', f); - break; - } else if (*i == '[') { - state = STATE_BRACKET; - begin = i + 1; - } else { - fputc('\x1B', f); - fputc(*i, f); - state = STATE_OTHER; - } - - break; - - case STATE_BRACKET: - - if (i >= *ibuf + isz || /* EOT */ - (!(*i >= '0' && *i <= '9') && *i != ';' && *i != 'm')) { - fputc('\x1B', f); - fputc('[', f); - state = STATE_OTHER; - i = begin-1; - } else if (*i == 'm') - state = STATE_OTHER; - break; - } - } - - if (ferror(f)) { - fclose(f); - free(obuf); - return NULL; - } - - fclose(f); - - free(*ibuf); - *ibuf = obuf; - - if (_isz) - *_isz = osz; - - return obuf; -} - -char *strextend(char **x, ...) { - va_list ap; - size_t f, l; - char *r, *p; - - assert(x); - - l = f = *x ? strlen(*x) : 0; - - va_start(ap, x); - for (;;) { - const char *t; - size_t n; - - t = va_arg(ap, const char *); - if (!t) - break; - - n = strlen(t); - if (n > ((size_t) -1) - l) { - va_end(ap); - return NULL; - } - - l += n; - } - va_end(ap); - - r = realloc(*x, l+1); - if (!r) - return NULL; - - p = r + f; - - va_start(ap, x); - for (;;) { - const char *t; - - t = va_arg(ap, const char *); - if (!t) - break; - - p = stpcpy(p, t); - } - va_end(ap); - - *p = 0; - *x = r; - - return r + l; -} - -char *strrep(const char *s, unsigned n) { - size_t l; - char *r, *p; - unsigned i; - - assert(s); - - l = strlen(s); - p = r = malloc(l * n + 1); - if (!r) - return NULL; - - for (i = 0; i < n; i++) - p = stpcpy(p, s); - - *p = 0; - return r; -} - -int split_pair(const char *s, const char *sep, char **l, char **r) { - char *x, *a, *b; - - assert(s); - assert(sep); - assert(l); - assert(r); - - if (isempty(sep)) - return -EINVAL; - - x = strstr(s, sep); - if (!x) - return -EINVAL; - - a = strndup(s, x - s); - if (!a) - return -ENOMEM; - - b = strdup(x + strlen(sep)); - if (!b) { - free(a); - return -ENOMEM; - } - - *l = a; - *r = b; - - return 0; -} - -int free_and_strdup(char **p, const char *s) { - char *t; - - assert(p); - - /* Replaces a string pointer with an strdup()ed new string, - * possibly freeing the old one. */ - - if (streq_ptr(*p, s)) - return 0; - - if (s) { - t = strdup(s); - if (!t) - return -ENOMEM; - } else - t = NULL; - - free(*p); - *p = t; - - return 1; -} - -#pragma GCC push_options -#pragma GCC optimize("O0") - -void* memory_erase(void *p, size_t l) { - volatile uint8_t* x = (volatile uint8_t*) p; - - /* This basically does what memset() does, but hopefully isn't - * optimized away by the compiler. One of those days, when - * glibc learns memset_s() we should replace this call by - * memset_s(), but until then this has to do. */ - - for (; l > 0; l--) - *(x++) = 'x'; - - return p; -} - -#pragma GCC pop_options - -char* string_erase(char *x) { - - if (!x) - return NULL; - - /* A delicious drop of snake-oil! To be called on memory where - * we stored passphrases or so, after we used them. */ - - return memory_erase(x, strlen(x)); -} - -char *string_free_erase(char *s) { - return mfree(string_erase(s)); -} - -bool string_is_safe(const char *p) { - const char *t; - - if (!p) - return false; - - for (t = p; *t; t++) { - if (*t > 0 && *t < ' ') /* no control characters */ - return false; - - if (strchr(QUOTES "\\\x7f", *t)) - return false; - } - - return true; -} diff --git a/src/basic/string-util.h b/src/basic/string-util.h deleted file mode 100644 index 139cc8c91b..0000000000 --- a/src/basic/string-util.h +++ /dev/null @@ -1,190 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include - -#include "macro.h" - -/* What is interpreted as whitespace? */ -#define WHITESPACE " \t\n\r" -#define NEWLINE "\n\r" -#define QUOTES "\"\'" -#define COMMENTS "#;" -#define GLOB_CHARS "*?[" -#define DIGITS "0123456789" -#define LOWERCASE_LETTERS "abcdefghijklmnopqrstuvwxyz" -#define UPPERCASE_LETTERS "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -#define LETTERS LOWERCASE_LETTERS UPPERCASE_LETTERS -#define ALPHANUMERICAL LETTERS DIGITS -#define HEXDIGITS DIGITS "abcdefABCDEF" - -#define streq(a,b) (strcmp((a),(b)) == 0) -#define strneq(a, b, n) (strncmp((a), (b), (n)) == 0) -#define strcaseeq(a,b) (strcasecmp((a),(b)) == 0) -#define strncaseeq(a, b, n) (strncasecmp((a), (b), (n)) == 0) - -int strcmp_ptr(const char *a, const char *b) _pure_; - -static inline bool streq_ptr(const char *a, const char *b) { - return strcmp_ptr(a, b) == 0; -} - -static inline const char* strempty(const char *s) { - return s ? s : ""; -} - -static inline const char* strnull(const char *s) { - return s ? s : "(null)"; -} - -static inline const char *strna(const char *s) { - return s ? s : "n/a"; -} - -static inline bool isempty(const char *p) { - return !p || !p[0]; -} - -static inline char *startswith(const char *s, const char *prefix) { - size_t l; - - l = strlen(prefix); - if (strncmp(s, prefix, l) == 0) - return (char*) s + l; - - return NULL; -} - -static inline char *startswith_no_case(const char *s, const char *prefix) { - size_t l; - - l = strlen(prefix); - if (strncasecmp(s, prefix, l) == 0) - return (char*) s + l; - - return NULL; -} - -char *endswith(const char *s, const char *postfix) _pure_; -char *endswith_no_case(const char *s, const char *postfix) _pure_; - -char *first_word(const char *s, const char *word) _pure_; - -const char* split(const char **state, size_t *l, const char *separator, bool quoted); - -#define FOREACH_WORD(word, length, s, state) \ - _FOREACH_WORD(word, length, s, WHITESPACE, false, state) - -#define FOREACH_WORD_SEPARATOR(word, length, s, separator, state) \ - _FOREACH_WORD(word, length, s, separator, false, state) - -#define FOREACH_WORD_QUOTED(word, length, s, state) \ - _FOREACH_WORD(word, length, s, WHITESPACE, true, state) - -#define _FOREACH_WORD(word, length, s, separator, quoted, state) \ - for ((state) = (s), (word) = split(&(state), &(length), (separator), (quoted)); (word); (word) = split(&(state), &(length), (separator), (quoted))) - -char *strappend(const char *s, const char *suffix); -char *strnappend(const char *s, const char *suffix, size_t length); - -char *strjoin(const char *x, ...) _sentinel_; - -#define strjoina(a, ...) \ - ({ \ - const char *_appendees_[] = { a, __VA_ARGS__ }; \ - char *_d_, *_p_; \ - int _len_ = 0; \ - unsigned _i_; \ - for (_i_ = 0; _i_ < ELEMENTSOF(_appendees_) && _appendees_[_i_]; _i_++) \ - _len_ += strlen(_appendees_[_i_]); \ - _p_ = _d_ = alloca(_len_ + 1); \ - for (_i_ = 0; _i_ < ELEMENTSOF(_appendees_) && _appendees_[_i_]; _i_++) \ - _p_ = stpcpy(_p_, _appendees_[_i_]); \ - *_p_ = 0; \ - _d_; \ - }) - -char *strstrip(char *s); -char *delete_chars(char *s, const char *bad); -char *truncate_nl(char *s); - -char ascii_tolower(char x); -char *ascii_strlower(char *s); -char *ascii_strlower_n(char *s, size_t n); - -int ascii_strcasecmp_n(const char *a, const char *b, size_t n); -int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m); - -bool chars_intersect(const char *a, const char *b) _pure_; - -static inline bool _pure_ in_charset(const char *s, const char* charset) { - assert(s); - assert(charset); - return s[strspn(s, charset)] == '\0'; -} - -bool string_has_cc(const char *p, const char *ok) _pure_; - -char *ellipsize_mem(const char *s, size_t old_length_bytes, size_t new_length_columns, unsigned percent); -char *ellipsize(const char *s, size_t length, unsigned percent); - -bool nulstr_contains(const char*nulstr, const char *needle); - -char* strshorten(char *s, size_t l); - -char *strreplace(const char *text, const char *old_string, const char *new_string); - -char *strip_tab_ansi(char **p, size_t *l); - -char *strextend(char **x, ...) _sentinel_; - -char *strrep(const char *s, unsigned n); - -int split_pair(const char *s, const char *sep, char **l, char **r); - -int free_and_strdup(char **p, const char *s); - -/* Normal memmem() requires haystack to be nonnull, which is annoying for zero-length buffers */ -static inline void *memmem_safe(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) { - - if (needlelen <= 0) - return (void*) haystack; - - if (haystacklen < needlelen) - return NULL; - - assert(haystack); - assert(needle); - - return memmem(haystack, haystacklen, needle, needlelen); -} - -void* memory_erase(void *p, size_t l); -char *string_erase(char *x); - -char *string_free_erase(char *s); -DEFINE_TRIVIAL_CLEANUP_FUNC(char *, string_free_erase); -#define _cleanup_string_free_erase_ _cleanup_(string_free_erasep) - -bool string_is_safe(const char *p) _pure_; diff --git a/src/basic/strv.c b/src/basic/strv.c deleted file mode 100644 index 97a96e5762..0000000000 --- a/src/basic/strv.c +++ /dev/null @@ -1,927 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include - -#include "alloc-util.h" -#include "escape.h" -#include "extract-word.h" -#include "fileio.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -char *strv_find(char **l, const char *name) { - char **i; - - assert(name); - - STRV_FOREACH(i, l) - if (streq(*i, name)) - return *i; - - return NULL; -} - -char *strv_find_prefix(char **l, const char *name) { - char **i; - - assert(name); - - STRV_FOREACH(i, l) - if (startswith(*i, name)) - return *i; - - return NULL; -} - -char *strv_find_startswith(char **l, const char *name) { - char **i, *e; - - assert(name); - - /* Like strv_find_prefix, but actually returns only the - * suffix, not the whole item */ - - STRV_FOREACH(i, l) { - e = startswith(*i, name); - if (e) - return e; - } - - return NULL; -} - -void strv_clear(char **l) { - char **k; - - if (!l) - return; - - for (k = l; *k; k++) - free(*k); - - *l = NULL; -} - -char **strv_free(char **l) { - strv_clear(l); - free(l); - return NULL; -} - -char **strv_free_erase(char **l) { - char **i; - - STRV_FOREACH(i, l) - string_erase(*i); - - return strv_free(l); -} - -char **strv_copy(char * const *l) { - char **r, **k; - - k = r = new(char*, strv_length(l) + 1); - if (!r) - return NULL; - - if (l) - for (; *l; k++, l++) { - *k = strdup(*l); - if (!*k) { - strv_free(r); - return NULL; - } - } - - *k = NULL; - return r; -} - -unsigned strv_length(char * const *l) { - unsigned n = 0; - - if (!l) - return 0; - - for (; *l; l++) - n++; - - return n; -} - -char **strv_new_ap(const char *x, va_list ap) { - const char *s; - char **a; - unsigned n = 0, i = 0; - va_list aq; - - /* As a special trick we ignore all listed strings that equal - * (const char*) -1. This is supposed to be used with the - * STRV_IFNOTNULL() macro to include possibly NULL strings in - * the string list. */ - - if (x) { - n = x == (const char*) -1 ? 0 : 1; - - va_copy(aq, ap); - while ((s = va_arg(aq, const char*))) { - if (s == (const char*) -1) - continue; - - n++; - } - - va_end(aq); - } - - a = new(char*, n+1); - if (!a) - return NULL; - - if (x) { - if (x != (const char*) -1) { - a[i] = strdup(x); - if (!a[i]) - goto fail; - i++; - } - - while ((s = va_arg(ap, const char*))) { - - if (s == (const char*) -1) - continue; - - a[i] = strdup(s); - if (!a[i]) - goto fail; - - i++; - } - } - - a[i] = NULL; - - return a; - -fail: - strv_free(a); - return NULL; -} - -char **strv_new(const char *x, ...) { - char **r; - va_list ap; - - va_start(ap, x); - r = strv_new_ap(x, ap); - va_end(ap); - - return r; -} - -int strv_extend_strv(char ***a, char **b, bool filter_duplicates) { - char **s, **t; - size_t p, q, i = 0, j; - - assert(a); - - if (strv_isempty(b)) - return 0; - - p = strv_length(*a); - q = strv_length(b); - - t = realloc(*a, sizeof(char*) * (p + q + 1)); - if (!t) - return -ENOMEM; - - t[p] = NULL; - *a = t; - - STRV_FOREACH(s, b) { - - if (filter_duplicates && strv_contains(t, *s)) - continue; - - t[p+i] = strdup(*s); - if (!t[p+i]) - goto rollback; - - i++; - t[p+i] = NULL; - } - - assert(i <= q); - - return (int) i; - -rollback: - for (j = 0; j < i; j++) - free(t[p + j]); - - t[p] = NULL; - return -ENOMEM; -} - -int strv_extend_strv_concat(char ***a, char **b, const char *suffix) { - int r; - char **s; - - STRV_FOREACH(s, b) { - char *v; - - v = strappend(*s, suffix); - if (!v) - return -ENOMEM; - - r = strv_push(a, v); - if (r < 0) { - free(v); - return r; - } - } - - return 0; -} - -char **strv_split(const char *s, const char *separator) { - const char *word, *state; - size_t l; - unsigned n, i; - char **r; - - assert(s); - - n = 0; - FOREACH_WORD_SEPARATOR(word, l, s, separator, state) - n++; - - r = new(char*, n+1); - if (!r) - return NULL; - - i = 0; - FOREACH_WORD_SEPARATOR(word, l, s, separator, state) { - r[i] = strndup(word, l); - if (!r[i]) { - strv_free(r); - return NULL; - } - - i++; - } - - r[i] = NULL; - return r; -} - -char **strv_split_newlines(const char *s) { - char **l; - unsigned n; - - assert(s); - - /* Special version of strv_split() that splits on newlines and - * suppresses an empty string at the end */ - - l = strv_split(s, NEWLINE); - if (!l) - return NULL; - - n = strv_length(l); - if (n <= 0) - return l; - - if (isempty(l[n - 1])) - l[n - 1] = mfree(l[n - 1]); - - return l; -} - -int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags) { - _cleanup_strv_free_ char **l = NULL; - size_t n = 0, allocated = 0; - int r; - - assert(t); - assert(s); - - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&s, &word, separators, flags); - if (r < 0) - return r; - if (r == 0) - break; - - if (!GREEDY_REALLOC(l, allocated, n + 2)) - return -ENOMEM; - - l[n++] = word; - word = NULL; - - l[n] = NULL; - } - - if (!l) { - l = new0(char*, 1); - if (!l) - return -ENOMEM; - } - - *t = l; - l = NULL; - - return (int) n; -} - -char *strv_join(char **l, const char *separator) { - char *r, *e; - char **s; - size_t n, k; - - if (!separator) - separator = " "; - - k = strlen(separator); - - n = 0; - STRV_FOREACH(s, l) { - if (s != l) - n += k; - n += strlen(*s); - } - - r = new(char, n+1); - if (!r) - return NULL; - - e = r; - STRV_FOREACH(s, l) { - if (s != l) - e = stpcpy(e, separator); - - e = stpcpy(e, *s); - } - - *e = 0; - - return r; -} - -char *strv_join_quoted(char **l) { - char *buf = NULL; - char **s; - size_t allocated = 0, len = 0; - - STRV_FOREACH(s, l) { - /* assuming here that escaped string cannot be more - * than twice as long, and reserving space for the - * separator and quotes. - */ - _cleanup_free_ char *esc = NULL; - size_t needed; - - if (!GREEDY_REALLOC(buf, allocated, - len + strlen(*s) * 2 + 3)) - goto oom; - - esc = cescape(*s); - if (!esc) - goto oom; - - needed = snprintf(buf + len, allocated - len, "%s\"%s\"", - len > 0 ? " " : "", esc); - assert(needed < allocated - len); - len += needed; - } - - if (!buf) - buf = malloc0(1); - - return buf; - - oom: - free(buf); - return NULL; -} - -int strv_push(char ***l, char *value) { - char **c; - unsigned n, m; - - if (!value) - return 0; - - n = strv_length(*l); - - /* Increase and check for overflow */ - m = n + 2; - if (m < n) - return -ENOMEM; - - c = realloc_multiply(*l, sizeof(char*), m); - if (!c) - return -ENOMEM; - - c[n] = value; - c[n+1] = NULL; - - *l = c; - return 0; -} - -int strv_push_pair(char ***l, char *a, char *b) { - char **c; - unsigned n, m; - - if (!a && !b) - return 0; - - n = strv_length(*l); - - /* increase and check for overflow */ - m = n + !!a + !!b + 1; - if (m < n) - return -ENOMEM; - - c = realloc_multiply(*l, sizeof(char*), m); - if (!c) - return -ENOMEM; - - if (a) - c[n++] = a; - if (b) - c[n++] = b; - c[n] = NULL; - - *l = c; - return 0; -} - -int strv_push_prepend(char ***l, char *value) { - char **c; - unsigned n, m, i; - - if (!value) - return 0; - - n = strv_length(*l); - - /* increase and check for overflow */ - m = n + 2; - if (m < n) - return -ENOMEM; - - c = new(char*, m); - if (!c) - return -ENOMEM; - - for (i = 0; i < n; i++) - c[i+1] = (*l)[i]; - - c[0] = value; - c[n+1] = NULL; - - free(*l); - *l = c; - - return 0; -} - -int strv_consume(char ***l, char *value) { - int r; - - r = strv_push(l, value); - if (r < 0) - free(value); - - return r; -} - -int strv_consume_pair(char ***l, char *a, char *b) { - int r; - - r = strv_push_pair(l, a, b); - if (r < 0) { - free(a); - free(b); - } - - return r; -} - -int strv_consume_prepend(char ***l, char *value) { - int r; - - r = strv_push_prepend(l, value); - if (r < 0) - free(value); - - return r; -} - -int strv_extend(char ***l, const char *value) { - char *v; - - if (!value) - return 0; - - v = strdup(value); - if (!v) - return -ENOMEM; - - return strv_consume(l, v); -} - -int strv_extend_front(char ***l, const char *value) { - size_t n, m; - char *v, **c; - - assert(l); - - /* Like strv_extend(), but prepends rather than appends the new entry */ - - if (!value) - return 0; - - n = strv_length(*l); - - /* Increase and overflow check. */ - m = n + 2; - if (m < n) - return -ENOMEM; - - v = strdup(value); - if (!v) - return -ENOMEM; - - c = realloc_multiply(*l, sizeof(char*), m); - if (!c) { - free(v); - return -ENOMEM; - } - - memmove(c+1, c, n * sizeof(char*)); - c[0] = v; - c[n+1] = NULL; - - *l = c; - return 0; -} - -char **strv_uniq(char **l) { - char **i; - - /* Drops duplicate entries. The first identical string will be - * kept, the others dropped */ - - STRV_FOREACH(i, l) - strv_remove(i+1, *i); - - return l; -} - -bool strv_is_uniq(char **l) { - char **i; - - STRV_FOREACH(i, l) - if (strv_find(i+1, *i)) - return false; - - return true; -} - -char **strv_remove(char **l, const char *s) { - char **f, **t; - - if (!l) - return NULL; - - assert(s); - - /* Drops every occurrence of s in the string list, edits - * in-place. */ - - for (f = t = l; *f; f++) - if (streq(*f, s)) - free(*f); - else - *(t++) = *f; - - *t = NULL; - return l; -} - -char **strv_parse_nulstr(const char *s, size_t l) { - const char *p; - unsigned c = 0, i = 0; - char **v; - - assert(s || l <= 0); - - if (l <= 0) - return new0(char*, 1); - - for (p = s; p < s + l; p++) - if (*p == 0) - c++; - - if (s[l-1] != 0) - c++; - - v = new0(char*, c+1); - if (!v) - return NULL; - - p = s; - while (p < s + l) { - const char *e; - - e = memchr(p, 0, s + l - p); - - v[i] = strndup(p, e ? e - p : s + l - p); - if (!v[i]) { - strv_free(v); - return NULL; - } - - i++; - - if (!e) - break; - - p = e + 1; - } - - assert(i == c); - - return v; -} - -char **strv_split_nulstr(const char *s) { - const char *i; - char **r = NULL; - - NULSTR_FOREACH(i, s) - if (strv_extend(&r, i) < 0) { - strv_free(r); - return NULL; - } - - if (!r) - return strv_new(NULL, NULL); - - return r; -} - -int strv_make_nulstr(char **l, char **p, size_t *q) { - size_t n_allocated = 0, n = 0; - _cleanup_free_ char *m = NULL; - char **i; - - assert(p); - assert(q); - - STRV_FOREACH(i, l) { - size_t z; - - z = strlen(*i); - - if (!GREEDY_REALLOC(m, n_allocated, n + z + 1)) - return -ENOMEM; - - memcpy(m + n, *i, z + 1); - n += z + 1; - } - - if (!m) { - m = new0(char, 1); - if (!m) - return -ENOMEM; - n = 0; - } - - *p = m; - *q = n; - - m = NULL; - - return 0; -} - -bool strv_overlap(char **a, char **b) { - char **i; - - STRV_FOREACH(i, a) - if (strv_contains(b, *i)) - return true; - - return false; -} - -static int str_compare(const void *_a, const void *_b) { - const char **a = (const char**) _a, **b = (const char**) _b; - - return strcmp(*a, *b); -} - -char **strv_sort(char **l) { - - if (strv_isempty(l)) - return l; - - qsort(l, strv_length(l), sizeof(char*), str_compare); - return l; -} - -bool strv_equal(char **a, char **b) { - - if (strv_isempty(a)) - return strv_isempty(b); - - if (strv_isempty(b)) - return false; - - for ( ; *a || *b; ++a, ++b) - if (!streq_ptr(*a, *b)) - return false; - - return true; -} - -void strv_print(char **l) { - char **s; - - STRV_FOREACH(s, l) - puts(*s); -} - -int strv_extendf(char ***l, const char *format, ...) { - va_list ap; - char *x; - int r; - - va_start(ap, format); - r = vasprintf(&x, format, ap); - va_end(ap); - - if (r < 0) - return -ENOMEM; - - return strv_consume(l, x); -} - -char **strv_reverse(char **l) { - unsigned n, i; - - n = strv_length(l); - if (n <= 1) - return l; - - for (i = 0; i < n / 2; i++) { - char *t; - - t = l[i]; - l[i] = l[n-1-i]; - l[n-1-i] = t; - } - - return l; -} - -char **strv_shell_escape(char **l, const char *bad) { - char **s; - - /* Escapes every character in every string in l that is in bad, - * edits in-place, does not roll-back on error. */ - - STRV_FOREACH(s, l) { - char *v; - - v = shell_escape(*s, bad); - if (!v) - return NULL; - - free(*s); - *s = v; - } - - return l; -} - -bool strv_fnmatch(char* const* patterns, const char *s, int flags) { - char* const* p; - - STRV_FOREACH(p, patterns) - if (fnmatch(*p, s, 0) == 0) - return true; - - return false; -} - -char ***strv_free_free(char ***l) { - char ***i; - - if (!l) - return NULL; - - for (i = l; *i; i++) - strv_free(*i); - - free(l); - return NULL; -} - -char **strv_skip(char **l, size_t n) { - - while (n > 0) { - if (strv_isempty(l)) - return l; - - l++, n--; - } - - return l; -} - -int strv_extend_n(char ***l, const char *value, size_t n) { - size_t i, j, k; - char **nl; - - assert(l); - - if (!value) - return 0; - if (n == 0) - return 0; - - /* Adds the value value n times to l */ - - k = strv_length(*l); - - nl = realloc(*l, sizeof(char*) * (k + n + 1)); - if (!nl) - return -ENOMEM; - - *l = nl; - - for (i = k; i < k + n; i++) { - nl[i] = strdup(value); - if (!nl[i]) - goto rollback; - } - - nl[i] = NULL; - return 0; - -rollback: - for (j = k; j < i; j++) - free(nl[j]); - - nl[k] = NULL; - return -ENOMEM; -} - -int fputstrv(FILE *f, char **l, const char *separator, bool *space) { - bool b = false; - char **s; - int r; - - /* Like fputs(), but for strv, and with a less stupid argument order */ - - if (!space) - space = &b; - - STRV_FOREACH(s, l) { - r = fputs_with_space(f, *s, separator, space); - if (r < 0) - return r; - } - - return 0; -} diff --git a/src/basic/strv.h b/src/basic/strv.h deleted file mode 100644 index f61bbb5386..0000000000 --- a/src/basic/strv.h +++ /dev/null @@ -1,172 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include - -#include "alloc-util.h" -#include "extract-word.h" -#include "macro.h" -#include "util.h" - -char *strv_find(char **l, const char *name) _pure_; -char *strv_find_prefix(char **l, const char *name) _pure_; -char *strv_find_startswith(char **l, const char *name) _pure_; - -char **strv_free(char **l); -DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free); -#define _cleanup_strv_free_ _cleanup_(strv_freep) - -char **strv_free_erase(char **l); -DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free_erase); -#define _cleanup_strv_free_erase_ _cleanup_(strv_free_erasep) - -void strv_clear(char **l); - -char **strv_copy(char * const *l); -unsigned strv_length(char * const *l) _pure_; - -int strv_extend_strv(char ***a, char **b, bool filter_duplicates); -int strv_extend_strv_concat(char ***a, char **b, const char *suffix); -int strv_extend(char ***l, const char *value); -int strv_extendf(char ***l, const char *format, ...) _printf_(2,0); -int strv_extend_front(char ***l, const char *value); -int strv_push(char ***l, char *value); -int strv_push_pair(char ***l, char *a, char *b); -int strv_push_prepend(char ***l, char *value); -int strv_consume(char ***l, char *value); -int strv_consume_pair(char ***l, char *a, char *b); -int strv_consume_prepend(char ***l, char *value); - -char **strv_remove(char **l, const char *s); -char **strv_uniq(char **l); -bool strv_is_uniq(char **l); - -bool strv_equal(char **a, char **b); - -#define strv_contains(l, s) (!!strv_find((l), (s))) - -char **strv_new(const char *x, ...) _sentinel_; -char **strv_new_ap(const char *x, va_list ap); - -static inline const char* STRV_IFNOTNULL(const char *x) { - return x ? x : (const char *) -1; -} - -static inline bool strv_isempty(char * const *l) { - return !l || !*l; -} - -char **strv_split(const char *s, const char *separator); -char **strv_split_newlines(const char *s); - -int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags); - -char *strv_join(char **l, const char *separator); -char *strv_join_quoted(char **l); - -char **strv_parse_nulstr(const char *s, size_t l); -char **strv_split_nulstr(const char *s); -int strv_make_nulstr(char **l, char **p, size_t *n); - -bool strv_overlap(char **a, char **b) _pure_; - -#define STRV_FOREACH(s, l) \ - for ((s) = (l); (s) && *(s); (s)++) - -#define STRV_FOREACH_BACKWARDS(s, l) \ - STRV_FOREACH(s, l) \ - ; \ - for ((s)--; (l) && ((s) >= (l)); (s)--) - -#define STRV_FOREACH_PAIR(x, y, l) \ - for ((x) = (l), (y) = (x+1); (x) && *(x) && *(y); (x) += 2, (y) = (x + 1)) - -char **strv_sort(char **l); -void strv_print(char **l); - -#define STRV_MAKE(...) ((char**) ((const char*[]) { __VA_ARGS__, NULL })) - -#define STRV_MAKE_EMPTY ((char*[1]) { NULL }) - -#define strv_from_stdarg_alloca(first) \ - ({ \ - char **_l; \ - \ - if (!first) \ - _l = (char**) &first; \ - else { \ - unsigned _n; \ - va_list _ap; \ - \ - _n = 1; \ - va_start(_ap, first); \ - while (va_arg(_ap, char*)) \ - _n++; \ - va_end(_ap); \ - \ - _l = newa(char*, _n+1); \ - _l[_n = 0] = (char*) first; \ - va_start(_ap, first); \ - for (;;) { \ - _l[++_n] = va_arg(_ap, char*); \ - if (!_l[_n]) \ - break; \ - } \ - va_end(_ap); \ - } \ - _l; \ - }) - -#define STR_IN_SET(x, ...) strv_contains(STRV_MAKE(__VA_ARGS__), x) - -#define FOREACH_STRING(x, ...) \ - for (char **_l = ({ \ - char **_ll = STRV_MAKE(__VA_ARGS__); \ - x = _ll ? _ll[0] : NULL; \ - _ll; \ - }); \ - _l && *_l; \ - x = ({ \ - _l ++; \ - _l[0]; \ - })) - -char **strv_reverse(char **l); -char **strv_shell_escape(char **l, const char *bad); - -bool strv_fnmatch(char* const* patterns, const char *s, int flags); - -static inline bool strv_fnmatch_or_empty(char* const* patterns, const char *s, int flags) { - assert(s); - return strv_isempty(patterns) || - strv_fnmatch(patterns, s, flags); -} - -char ***strv_free_free(char ***l); - -char **strv_skip(char **l, size_t n); - -int strv_extend_n(char ***l, const char *value, size_t n); - -int fputstrv(FILE *f, char **l, const char *separator, bool *space); diff --git a/src/basic/strxcpyx.c b/src/basic/strxcpyx.c deleted file mode 100644 index aaf11d21f6..0000000000 --- a/src/basic/strxcpyx.c +++ /dev/null @@ -1,100 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -/* - * Concatenates/copies strings. In any case, terminates in all cases - * with '\0' * and moves the @dest pointer forward to the added '\0'. - * Returns the * remaining size, and 0 if the string was truncated. - */ - -#include -#include -#include - -#include "strxcpyx.h" - -size_t strpcpy(char **dest, size_t size, const char *src) { - size_t len; - - len = strlen(src); - if (len >= size) { - if (size > 1) - *dest = mempcpy(*dest, src, size-1); - size = 0; - } else { - if (len > 0) { - *dest = mempcpy(*dest, src, len); - size -= len; - } - } - *dest[0] = '\0'; - return size; -} - -size_t strpcpyf(char **dest, size_t size, const char *src, ...) { - va_list va; - int i; - - va_start(va, src); - i = vsnprintf(*dest, size, src, va); - if (i < (int)size) { - *dest += i; - size -= i; - } else { - *dest += size; - size = 0; - } - va_end(va); - *dest[0] = '\0'; - return size; -} - -size_t strpcpyl(char **dest, size_t size, const char *src, ...) { - va_list va; - - va_start(va, src); - do { - size = strpcpy(dest, size, src); - src = va_arg(va, char *); - } while (src != NULL); - va_end(va); - return size; -} - -size_t strscpy(char *dest, size_t size, const char *src) { - char *s; - - s = dest; - return strpcpy(&s, size, src); -} - -size_t strscpyl(char *dest, size_t size, const char *src, ...) { - va_list va; - char *s; - - va_start(va, src); - s = dest; - do { - size = strpcpy(&s, size, src); - src = va_arg(va, char *); - } while (src != NULL); - va_end(va); - - return size; -} diff --git a/src/basic/strxcpyx.h b/src/basic/strxcpyx.h deleted file mode 100644 index 80ff58726b..0000000000 --- a/src/basic/strxcpyx.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 - -#include "macro.h" - -size_t strpcpy(char **dest, size_t size, const char *src); -size_t strpcpyf(char **dest, size_t size, const char *src, ...) _printf_(3, 4); -size_t strpcpyl(char **dest, size_t size, const char *src, ...) _sentinel_; -size_t strscpy(char *dest, size_t size, const char *src); -size_t strscpyl(char *dest, size_t size, const char *src, ...) _sentinel_; diff --git a/src/basic/syslog-util.c b/src/basic/syslog-util.c deleted file mode 100644 index db3405154e..0000000000 --- a/src/basic/syslog-util.c +++ /dev/null @@ -1,114 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 - -#include "hexdecoct.h" -#include "macro.h" -#include "string-table.h" -#include "syslog-util.h" - -int syslog_parse_priority(const char **p, int *priority, bool with_facility) { - int a = 0, b = 0, c = 0; - int k; - - assert(p); - assert(*p); - assert(priority); - - if ((*p)[0] != '<') - return 0; - - if (!strchr(*p, '>')) - return 0; - - if ((*p)[2] == '>') { - c = undecchar((*p)[1]); - k = 3; - } else if ((*p)[3] == '>') { - b = undecchar((*p)[1]); - c = undecchar((*p)[2]); - k = 4; - } else if ((*p)[4] == '>') { - a = undecchar((*p)[1]); - b = undecchar((*p)[2]); - c = undecchar((*p)[3]); - k = 5; - } else - return 0; - - if (a < 0 || b < 0 || c < 0 || - (!with_facility && (a || b || c > 7))) - return 0; - - if (with_facility) - *priority = a*100 + b*10 + c; - else - *priority = (*priority & LOG_FACMASK) | c; - - *p += k; - return 1; -} - -static const char *const log_facility_unshifted_table[LOG_NFACILITIES] = { - [LOG_FAC(LOG_KERN)] = "kern", - [LOG_FAC(LOG_USER)] = "user", - [LOG_FAC(LOG_MAIL)] = "mail", - [LOG_FAC(LOG_DAEMON)] = "daemon", - [LOG_FAC(LOG_AUTH)] = "auth", - [LOG_FAC(LOG_SYSLOG)] = "syslog", - [LOG_FAC(LOG_LPR)] = "lpr", - [LOG_FAC(LOG_NEWS)] = "news", - [LOG_FAC(LOG_UUCP)] = "uucp", - [LOG_FAC(LOG_CRON)] = "cron", - [LOG_FAC(LOG_AUTHPRIV)] = "authpriv", - [LOG_FAC(LOG_FTP)] = "ftp", - [LOG_FAC(LOG_LOCAL0)] = "local0", - [LOG_FAC(LOG_LOCAL1)] = "local1", - [LOG_FAC(LOG_LOCAL2)] = "local2", - [LOG_FAC(LOG_LOCAL3)] = "local3", - [LOG_FAC(LOG_LOCAL4)] = "local4", - [LOG_FAC(LOG_LOCAL5)] = "local5", - [LOG_FAC(LOG_LOCAL6)] = "local6", - [LOG_FAC(LOG_LOCAL7)] = "local7" -}; - -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(log_facility_unshifted, int, LOG_FAC(~0)); - -bool log_facility_unshifted_is_valid(int facility) { - return facility >= 0 && facility <= LOG_FAC(~0); -} - -static const char *const log_level_table[] = { - [LOG_EMERG] = "emerg", - [LOG_ALERT] = "alert", - [LOG_CRIT] = "crit", - [LOG_ERR] = "err", - [LOG_WARNING] = "warning", - [LOG_NOTICE] = "notice", - [LOG_INFO] = "info", - [LOG_DEBUG] = "debug" -}; - -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(log_level, int, LOG_DEBUG); - -bool log_level_is_valid(int level) { - return level >= 0 && level <= LOG_DEBUG; -} diff --git a/src/basic/syslog-util.h b/src/basic/syslog-util.h deleted file mode 100644 index 5cb606a1bf..0000000000 --- a/src/basic/syslog-util.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 - -int log_facility_unshifted_to_string_alloc(int i, char **s); -int log_facility_unshifted_from_string(const char *s); -bool log_facility_unshifted_is_valid(int faciliy); - -int log_level_to_string_alloc(int i, char **s); -int log_level_from_string(const char *s); -bool log_level_is_valid(int level); - -int syslog_parse_priority(const char **p, int *priority, bool with_facility); diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c deleted file mode 100644 index 9521b79daa..0000000000 --- a/src/basic/terminal-util.c +++ /dev/null @@ -1,1153 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "io-util.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "process-util.h" -#include "socket-util.h" -#include "stat-util.h" -#include "string-util.h" -#include "terminal-util.h" -#include "time-util.h" -#include "util.h" - -static volatile unsigned cached_columns = 0; -static volatile unsigned cached_lines = 0; - -int chvt(int vt) { - _cleanup_close_ int fd; - - fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); - if (fd < 0) - return -errno; - - if (vt <= 0) { - int tiocl[2] = { - TIOCL_GETKMSGREDIRECT, - 0 - }; - - if (ioctl(fd, TIOCLINUX, tiocl) < 0) - return -errno; - - vt = tiocl[0] <= 0 ? 1 : tiocl[0]; - } - - if (ioctl(fd, VT_ACTIVATE, vt) < 0) - return -errno; - - return 0; -} - -int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) { - struct termios old_termios, new_termios; - char c, line[LINE_MAX]; - - assert(f); - assert(ret); - - if (tcgetattr(fileno(f), &old_termios) >= 0) { - new_termios = old_termios; - - new_termios.c_lflag &= ~ICANON; - new_termios.c_cc[VMIN] = 1; - new_termios.c_cc[VTIME] = 0; - - if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) { - size_t k; - - if (t != USEC_INFINITY) { - if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) { - tcsetattr(fileno(f), TCSADRAIN, &old_termios); - return -ETIMEDOUT; - } - } - - k = fread(&c, 1, 1, f); - - tcsetattr(fileno(f), TCSADRAIN, &old_termios); - - if (k <= 0) - return -EIO; - - if (need_nl) - *need_nl = c != '\n'; - - *ret = c; - return 0; - } - } - - if (t != USEC_INFINITY) { - if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) - return -ETIMEDOUT; - } - - errno = 0; - if (!fgets(line, sizeof(line), f)) - return errno > 0 ? -errno : -EIO; - - truncate_nl(line); - - if (strlen(line) != 1) - return -EBADMSG; - - if (need_nl) - *need_nl = false; - - *ret = line[0]; - return 0; -} - -int ask_char(char *ret, const char *replies, const char *text, ...) { - int r; - - assert(ret); - assert(replies); - assert(text); - - for (;;) { - va_list ap; - char c; - bool need_nl = true; - - if (on_tty()) - fputs(ANSI_HIGHLIGHT, stdout); - - va_start(ap, text); - vprintf(text, ap); - va_end(ap); - - if (on_tty()) - fputs(ANSI_NORMAL, stdout); - - fflush(stdout); - - r = read_one_char(stdin, &c, USEC_INFINITY, &need_nl); - if (r < 0) { - - if (r == -EBADMSG) { - puts("Bad input, please try again."); - continue; - } - - putchar('\n'); - return r; - } - - if (need_nl) - putchar('\n'); - - if (strchr(replies, c)) { - *ret = c; - return 0; - } - - puts("Read unexpected character, please try again."); - } -} - -int ask_string(char **ret, const char *text, ...) { - assert(ret); - assert(text); - - for (;;) { - char line[LINE_MAX]; - va_list ap; - - if (on_tty()) - fputs(ANSI_HIGHLIGHT, stdout); - - va_start(ap, text); - vprintf(text, ap); - va_end(ap); - - if (on_tty()) - fputs(ANSI_NORMAL, stdout); - - fflush(stdout); - - errno = 0; - if (!fgets(line, sizeof(line), stdin)) - return errno > 0 ? -errno : -EIO; - - if (!endswith(line, "\n")) - putchar('\n'); - else { - char *s; - - if (isempty(line)) - continue; - - truncate_nl(line); - s = strdup(line); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; - } - } -} - -int reset_terminal_fd(int fd, bool switch_to_text) { - struct termios termios; - int r = 0; - - /* Set terminal to some sane defaults */ - - assert(fd >= 0); - - /* We leave locked terminal attributes untouched, so that - * Plymouth may set whatever it wants to set, and we don't - * interfere with that. */ - - /* Disable exclusive mode, just in case */ - (void) ioctl(fd, TIOCNXCL); - - /* Switch to text mode */ - if (switch_to_text) - (void) ioctl(fd, KDSETMODE, KD_TEXT); - - /* Enable console unicode mode */ - (void) ioctl(fd, KDSKBMODE, K_UNICODE); - - if (tcgetattr(fd, &termios) < 0) { - r = -errno; - goto finish; - } - - /* We only reset the stuff that matters to the software. How - * hardware is set up we don't touch assuming that somebody - * else will do that for us */ - - termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC); - termios.c_iflag |= ICRNL | IMAXBEL | IUTF8; - termios.c_oflag |= ONLCR; - termios.c_cflag |= CREAD; - termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOPRT | ECHOKE; - - termios.c_cc[VINTR] = 03; /* ^C */ - termios.c_cc[VQUIT] = 034; /* ^\ */ - termios.c_cc[VERASE] = 0177; - termios.c_cc[VKILL] = 025; /* ^X */ - termios.c_cc[VEOF] = 04; /* ^D */ - termios.c_cc[VSTART] = 021; /* ^Q */ - termios.c_cc[VSTOP] = 023; /* ^S */ - termios.c_cc[VSUSP] = 032; /* ^Z */ - termios.c_cc[VLNEXT] = 026; /* ^V */ - termios.c_cc[VWERASE] = 027; /* ^W */ - termios.c_cc[VREPRINT] = 022; /* ^R */ - termios.c_cc[VEOL] = 0; - termios.c_cc[VEOL2] = 0; - - termios.c_cc[VTIME] = 0; - termios.c_cc[VMIN] = 1; - - if (tcsetattr(fd, TCSANOW, &termios) < 0) - r = -errno; - -finish: - /* Just in case, flush all crap out */ - (void) tcflush(fd, TCIOFLUSH); - - return r; -} - -int reset_terminal(const char *name) { - _cleanup_close_ int fd = -1; - - /* We open the terminal with O_NONBLOCK here, to ensure we - * don't block on carrier if this is a terminal with carrier - * configured. */ - - fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); - if (fd < 0) - return fd; - - return reset_terminal_fd(fd, true); -} - -int open_terminal(const char *name, int mode) { - int fd, r; - unsigned c = 0; - - /* - * If a TTY is in the process of being closed opening it might - * cause EIO. This is horribly awful, but unlikely to be - * changed in the kernel. Hence we work around this problem by - * retrying a couple of times. - * - * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245 - */ - - if (mode & O_CREAT) - return -EINVAL; - - for (;;) { - fd = open(name, mode, 0); - if (fd >= 0) - break; - - if (errno != EIO) - return -errno; - - /* Max 1s in total */ - if (c >= 20) - return -errno; - - usleep(50 * USEC_PER_MSEC); - c++; - } - - r = isatty(fd); - if (r < 0) { - safe_close(fd); - return -errno; - } - - if (!r) { - safe_close(fd); - return -ENOTTY; - } - - return fd; -} - -int acquire_terminal( - const char *name, - bool fail, - bool force, - bool ignore_tiocstty_eperm, - usec_t timeout) { - - int fd = -1, notify = -1, r = 0, wd = -1; - usec_t ts = 0; - - assert(name); - - /* We use inotify to be notified when the tty is closed. We - * create the watch before checking if we can actually acquire - * it, so that we don't lose any event. - * - * Note: strictly speaking this actually watches for the - * device being closed, it does *not* really watch whether a - * tty loses its controlling process. However, unless some - * rogue process uses TIOCNOTTY on /dev/tty *after* closing - * its tty otherwise this will not become a problem. As long - * as the administrator makes sure not configure any service - * on the same tty as an untrusted user this should not be a - * problem. (Which he probably should not do anyway.) */ - - if (timeout != USEC_INFINITY) - ts = now(CLOCK_MONOTONIC); - - if (!fail && !force) { - notify = inotify_init1(IN_CLOEXEC | (timeout != USEC_INFINITY ? IN_NONBLOCK : 0)); - if (notify < 0) { - r = -errno; - goto fail; - } - - wd = inotify_add_watch(notify, name, IN_CLOSE); - if (wd < 0) { - r = -errno; - goto fail; - } - } - - for (;;) { - struct sigaction sa_old, sa_new = { - .sa_handler = SIG_IGN, - .sa_flags = SA_RESTART, - }; - - if (notify >= 0) { - r = flush_fd(notify); - if (r < 0) - goto fail; - } - - /* We pass here O_NOCTTY only so that we can check the return - * value TIOCSCTTY and have a reliable way to figure out if we - * successfully became the controlling process of the tty */ - fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return fd; - - /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed - * if we already own the tty. */ - assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0); - - /* First, try to get the tty */ - if (ioctl(fd, TIOCSCTTY, force) < 0) - r = -errno; - - assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0); - - /* Sometimes, it makes sense to ignore TIOCSCTTY - * returning EPERM, i.e. when very likely we already - * are have this controlling terminal. */ - if (r < 0 && r == -EPERM && ignore_tiocstty_eperm) - r = 0; - - if (r < 0 && (force || fail || r != -EPERM)) - goto fail; - - if (r >= 0) - break; - - assert(!fail); - assert(!force); - assert(notify >= 0); - - for (;;) { - union inotify_event_buffer buffer; - struct inotify_event *e; - ssize_t l; - - if (timeout != USEC_INFINITY) { - usec_t n; - - n = now(CLOCK_MONOTONIC); - if (ts + timeout < n) { - r = -ETIMEDOUT; - goto fail; - } - - r = fd_wait_for_event(fd, POLLIN, ts + timeout - n); - if (r < 0) - goto fail; - - if (r == 0) { - r = -ETIMEDOUT; - goto fail; - } - } - - l = read(notify, &buffer, sizeof(buffer)); - if (l < 0) { - if (errno == EINTR || errno == EAGAIN) - continue; - - r = -errno; - goto fail; - } - - FOREACH_INOTIFY_EVENT(e, buffer, l) { - if (e->wd != wd || !(e->mask & IN_CLOSE)) { - r = -EIO; - goto fail; - } - } - - break; - } - - /* We close the tty fd here since if the old session - * ended our handle will be dead. It's important that - * we do this after sleeping, so that we don't enter - * an endless loop. */ - fd = safe_close(fd); - } - - safe_close(notify); - - return fd; - -fail: - safe_close(fd); - safe_close(notify); - - return r; -} - -int release_terminal(void) { - static const struct sigaction sa_new = { - .sa_handler = SIG_IGN, - .sa_flags = SA_RESTART, - }; - - _cleanup_close_ int fd = -1; - struct sigaction sa_old; - int r = 0; - - fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); - if (fd < 0) - return -errno; - - /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed - * by our own TIOCNOTTY */ - assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0); - - if (ioctl(fd, TIOCNOTTY) < 0) - r = -errno; - - assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0); - - return r; -} - -int terminal_vhangup_fd(int fd) { - assert(fd >= 0); - - if (ioctl(fd, TIOCVHANGUP) < 0) - return -errno; - - return 0; -} - -int terminal_vhangup(const char *name) { - _cleanup_close_ int fd; - - fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); - if (fd < 0) - return fd; - - return terminal_vhangup_fd(fd); -} - -int vt_disallocate(const char *name) { - _cleanup_close_ int fd = -1; - unsigned u; - int r; - - /* Deallocate the VT if possible. If not possible - * (i.e. because it is the active one), at least clear it - * entirely (including the scrollback buffer) */ - - if (!startswith(name, "/dev/")) - return -EINVAL; - - if (!tty_is_vc(name)) { - /* So this is not a VT. I guess we cannot deallocate - * it then. But let's at least clear the screen */ - - fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return fd; - - loop_write(fd, - "\033[r" /* clear scrolling region */ - "\033[H" /* move home */ - "\033[2J", /* clear screen */ - 10, false); - return 0; - } - - if (!startswith(name, "/dev/tty")) - return -EINVAL; - - r = safe_atou(name+8, &u); - if (r < 0) - return r; - - if (u <= 0) - return -EINVAL; - - /* Try to deallocate */ - fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); - if (fd < 0) - return fd; - - r = ioctl(fd, VT_DISALLOCATE, u); - fd = safe_close(fd); - - if (r >= 0) - return 0; - - if (errno != EBUSY) - return -errno; - - /* Couldn't deallocate, so let's clear it fully with - * scrollback */ - fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return fd; - - loop_write(fd, - "\033[r" /* clear scrolling region */ - "\033[H" /* move home */ - "\033[3J", /* clear screen including scrollback, requires Linux 2.6.40 */ - 10, false); - return 0; -} - -int make_console_stdio(void) { - int fd, r; - - /* Make /dev/console the controlling terminal and stdin/stdout/stderr */ - - fd = acquire_terminal("/dev/console", false, true, true, USEC_INFINITY); - if (fd < 0) - return log_error_errno(fd, "Failed to acquire terminal: %m"); - - r = reset_terminal_fd(fd, true); - if (r < 0) - log_warning_errno(r, "Failed to reset terminal, ignoring: %m"); - - r = make_stdio(fd); - if (r < 0) - return log_error_errno(r, "Failed to duplicate terminal fd: %m"); - - return 0; -} - -bool tty_is_vc(const char *tty) { - assert(tty); - - return vtnr_from_tty(tty) >= 0; -} - -bool tty_is_console(const char *tty) { - assert(tty); - - if (startswith(tty, "/dev/")) - tty += 5; - - return streq(tty, "console"); -} - -int vtnr_from_tty(const char *tty) { - int i, r; - - assert(tty); - - if (startswith(tty, "/dev/")) - tty += 5; - - if (!startswith(tty, "tty") ) - return -EINVAL; - - if (tty[3] < '0' || tty[3] > '9') - return -EINVAL; - - r = safe_atoi(tty+3, &i); - if (r < 0) - return r; - - if (i < 0 || i > 63) - return -EINVAL; - - return i; -} - -char *resolve_dev_console(char **active) { - char *tty; - - /* Resolve where /dev/console is pointing to, if /sys is actually ours - * (i.e. not read-only-mounted which is a sign for container setups) */ - - if (path_is_read_only_fs("/sys") > 0) - return NULL; - - if (read_one_line_file("/sys/class/tty/console/active", active) < 0) - return NULL; - - /* If multiple log outputs are configured the last one is what - * /dev/console points to */ - tty = strrchr(*active, ' '); - if (tty) - tty++; - else - tty = *active; - - if (streq(tty, "tty0")) { - char *tmp; - - /* Get the active VC (e.g. tty1) */ - if (read_one_line_file("/sys/class/tty/tty0/active", &tmp) >= 0) { - free(*active); - tty = *active = tmp; - } - } - - return tty; -} - -bool tty_is_vc_resolve(const char *tty) { - _cleanup_free_ char *active = NULL; - - assert(tty); - - if (startswith(tty, "/dev/")) - tty += 5; - - if (streq(tty, "console")) { - tty = resolve_dev_console(&active); - if (!tty) - return false; - } - - return tty_is_vc(tty); -} - -const char *default_term_for_tty(const char *tty) { - return tty && tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt220"; -} - -int fd_columns(int fd) { - struct winsize ws = {}; - - if (ioctl(fd, TIOCGWINSZ, &ws) < 0) - return -errno; - - if (ws.ws_col <= 0) - return -EIO; - - return ws.ws_col; -} - -unsigned columns(void) { - const char *e; - int c; - - if (_likely_(cached_columns > 0)) - return cached_columns; - - c = 0; - e = getenv("COLUMNS"); - if (e) - (void) safe_atoi(e, &c); - - if (c <= 0) - c = fd_columns(STDOUT_FILENO); - - if (c <= 0) - c = 80; - - cached_columns = c; - return cached_columns; -} - -int fd_lines(int fd) { - struct winsize ws = {}; - - if (ioctl(fd, TIOCGWINSZ, &ws) < 0) - return -errno; - - if (ws.ws_row <= 0) - return -EIO; - - return ws.ws_row; -} - -unsigned lines(void) { - const char *e; - int l; - - if (_likely_(cached_lines > 0)) - return cached_lines; - - l = 0; - e = getenv("LINES"); - if (e) - (void) safe_atoi(e, &l); - - if (l <= 0) - l = fd_lines(STDOUT_FILENO); - - if (l <= 0) - l = 24; - - cached_lines = l; - return cached_lines; -} - -/* intended to be used as a SIGWINCH sighandler */ -void columns_lines_cache_reset(int signum) { - cached_columns = 0; - cached_lines = 0; -} - -bool on_tty(void) { - static int cached_on_tty = -1; - - if (_unlikely_(cached_on_tty < 0)) - cached_on_tty = isatty(STDOUT_FILENO) > 0; - - return cached_on_tty; -} - -int make_stdio(int fd) { - int r, s, t; - - assert(fd >= 0); - - r = dup2(fd, STDIN_FILENO); - s = dup2(fd, STDOUT_FILENO); - t = dup2(fd, STDERR_FILENO); - - if (fd >= 3) - safe_close(fd); - - if (r < 0 || s < 0 || t < 0) - return -errno; - - /* Explicitly unset O_CLOEXEC, since if fd was < 3, then - * dup2() was a NOP and the bit hence possibly set. */ - fd_cloexec(STDIN_FILENO, false); - fd_cloexec(STDOUT_FILENO, false); - fd_cloexec(STDERR_FILENO, false); - - return 0; -} - -int make_null_stdio(void) { - int null_fd; - - null_fd = open("/dev/null", O_RDWR|O_NOCTTY); - if (null_fd < 0) - return -errno; - - return make_stdio(null_fd); -} - -int getttyname_malloc(int fd, char **ret) { - size_t l = 100; - int r; - - assert(fd >= 0); - assert(ret); - - for (;;) { - char path[l]; - - r = ttyname_r(fd, path, sizeof(path)); - if (r == 0) { - const char *p; - char *c; - - p = startswith(path, "/dev/"); - c = strdup(p ?: path); - if (!c) - return -ENOMEM; - - *ret = c; - return 0; - } - - if (r != ERANGE) - return -r; - - l *= 2; - } - - return 0; -} - -int getttyname_harder(int fd, char **r) { - int k; - char *s = NULL; - - k = getttyname_malloc(fd, &s); - if (k < 0) - return k; - - if (streq(s, "tty")) { - free(s); - return get_ctty(0, NULL, r); - } - - *r = s; - return 0; -} - -int get_ctty_devnr(pid_t pid, dev_t *d) { - int r; - _cleanup_free_ char *line = NULL; - const char *p; - unsigned long ttynr; - - assert(pid >= 0); - - p = procfs_file_alloca(pid, "stat"); - r = read_one_line_file(p, &line); - if (r < 0) - return r; - - p = strrchr(line, ')'); - if (!p) - return -EIO; - - p++; - - if (sscanf(p, " " - "%*c " /* state */ - "%*d " /* ppid */ - "%*d " /* pgrp */ - "%*d " /* session */ - "%lu ", /* ttynr */ - &ttynr) != 1) - return -EIO; - - if (major(ttynr) == 0 && minor(ttynr) == 0) - return -ENXIO; - - if (d) - *d = (dev_t) ttynr; - - return 0; -} - -int get_ctty(pid_t pid, dev_t *_devnr, char **r) { - char fn[sizeof("/dev/char/")-1 + 2*DECIMAL_STR_MAX(unsigned) + 1 + 1], *b = NULL; - _cleanup_free_ char *s = NULL; - const char *p; - dev_t devnr; - int k; - - assert(r); - - k = get_ctty_devnr(pid, &devnr); - if (k < 0) - return k; - - sprintf(fn, "/dev/char/%u:%u", major(devnr), minor(devnr)); - - k = readlink_malloc(fn, &s); - if (k < 0) { - - if (k != -ENOENT) - return k; - - /* This is an ugly hack */ - if (major(devnr) == 136) { - if (asprintf(&b, "pts/%u", minor(devnr)) < 0) - return -ENOMEM; - } else { - /* Probably something like the ptys which have no - * symlink in /dev/char. Let's return something - * vaguely useful. */ - - b = strdup(fn + 5); - if (!b) - return -ENOMEM; - } - } else { - if (startswith(s, "/dev/")) - p = s + 5; - else if (startswith(s, "../")) - p = s + 3; - else - p = s; - - b = strdup(p); - if (!b) - return -ENOMEM; - } - - *r = b; - if (_devnr) - *_devnr = devnr; - - return 0; -} - -int ptsname_malloc(int fd, char **ret) { - size_t l = 100; - - assert(fd >= 0); - assert(ret); - - for (;;) { - char *c; - - c = new(char, l); - if (!c) - return -ENOMEM; - - if (ptsname_r(fd, c, l) == 0) { - *ret = c; - return 0; - } - if (errno != ERANGE) { - free(c); - return -errno; - } - - free(c); - l *= 2; - } -} - -int ptsname_namespace(int pty, char **ret) { - int no = -1, r; - - /* Like ptsname(), but doesn't assume that the path is - * accessible in the local namespace. */ - - r = ioctl(pty, TIOCGPTN, &no); - if (r < 0) - return -errno; - - if (no < 0) - return -EIO; - - if (asprintf(ret, "/dev/pts/%i", no) < 0) - return -ENOMEM; - - return 0; -} - -int openpt_in_namespace(pid_t pid, int flags) { - _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1; - _cleanup_close_pair_ int pair[2] = { -1, -1 }; - siginfo_t si; - pid_t child; - int r; - - assert(pid > 0); - - r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); - if (r < 0) - return r; - - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) - return -errno; - - child = fork(); - if (child < 0) - return -errno; - - if (child == 0) { - int master; - - pair[0] = safe_close(pair[0]); - - r = namespace_enter(pidnsfd, mntnsfd, -1, usernsfd, rootfd); - if (r < 0) - _exit(EXIT_FAILURE); - - master = posix_openpt(flags|O_NOCTTY|O_CLOEXEC); - if (master < 0) - _exit(EXIT_FAILURE); - - if (unlockpt(master) < 0) - _exit(EXIT_FAILURE); - - if (send_one_fd(pair[1], master, 0) < 0) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); - } - - pair[1] = safe_close(pair[1]); - - r = wait_for_terminate(child, &si); - if (r < 0) - return r; - if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) - return -EIO; - - return receive_one_fd(pair[0], 0); -} - -int open_terminal_in_namespace(pid_t pid, const char *name, int mode) { - _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1; - _cleanup_close_pair_ int pair[2] = { -1, -1 }; - siginfo_t si; - pid_t child; - int r; - - r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); - if (r < 0) - return r; - - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) - return -errno; - - child = fork(); - if (child < 0) - return -errno; - - if (child == 0) { - int master; - - pair[0] = safe_close(pair[0]); - - r = namespace_enter(pidnsfd, mntnsfd, -1, usernsfd, rootfd); - if (r < 0) - _exit(EXIT_FAILURE); - - master = open_terminal(name, mode|O_NOCTTY|O_CLOEXEC); - if (master < 0) - _exit(EXIT_FAILURE); - - if (send_one_fd(pair[1], master, 0) < 0) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); - } - - pair[1] = safe_close(pair[1]); - - r = wait_for_terminate(child, &si); - if (r < 0) - return r; - if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) - return -EIO; - - return receive_one_fd(pair[0], 0); -} - -bool colors_enabled(void) { - static int enabled = -1; - - if (_unlikely_(enabled < 0)) { - const char *colors; - - colors = getenv("SYSTEMD_COLORS"); - if (colors) - enabled = parse_boolean(colors) != 0; - else if (streq_ptr(getenv("TERM"), "dumb")) - enabled = false; - else - enabled = on_tty(); - } - - return enabled; -} diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h deleted file mode 100644 index a7c96a77cb..0000000000 --- a/src/basic/terminal-util.h +++ /dev/null @@ -1,126 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include - -#include "macro.h" -#include "time-util.h" - -#define ANSI_RED "\x1B[0;31m" -#define ANSI_GREEN "\x1B[0;32m" -#define ANSI_UNDERLINE "\x1B[0;4m" -#define ANSI_HIGHLIGHT "\x1B[0;1;39m" -#define ANSI_HIGHLIGHT_RED "\x1B[0;1;31m" -#define ANSI_HIGHLIGHT_GREEN "\x1B[0;1;32m" -#define ANSI_HIGHLIGHT_YELLOW "\x1B[0;1;33m" -#define ANSI_HIGHLIGHT_BLUE "\x1B[0;1;34m" -#define ANSI_HIGHLIGHT_UNDERLINE "\x1B[0;1;4m" -#define ANSI_NORMAL "\x1B[0m" - -#define ANSI_ERASE_TO_END_OF_LINE "\x1B[K" - -/* Set cursor to top left corner and clear screen */ -#define ANSI_HOME_CLEAR "\x1B[H\x1B[2J" - -int reset_terminal_fd(int fd, bool switch_to_text); -int reset_terminal(const char *name); - -int open_terminal(const char *name, int mode); -int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocstty_eperm, usec_t timeout); -int release_terminal(void); - -int terminal_vhangup_fd(int fd); -int terminal_vhangup(const char *name); - -int chvt(int vt); - -int read_one_char(FILE *f, char *ret, usec_t timeout, bool *need_nl); -int ask_char(char *ret, const char *replies, const char *text, ...) _printf_(3, 4); -int ask_string(char **ret, const char *text, ...) _printf_(2, 3); - -int vt_disallocate(const char *name); - -char *resolve_dev_console(char **active); -bool tty_is_vc(const char *tty); -bool tty_is_vc_resolve(const char *tty); -bool tty_is_console(const char *tty) _pure_; -int vtnr_from_tty(const char *tty); -const char *default_term_for_tty(const char *tty); - -int make_stdio(int fd); -int make_null_stdio(void); -int make_console_stdio(void); - -int fd_columns(int fd); -unsigned columns(void); -int fd_lines(int fd); -unsigned lines(void); -void columns_lines_cache_reset(int _unused_ signum); - -bool on_tty(void); -bool colors_enabled(void); - -static inline const char *ansi_underline(void) { - return colors_enabled() ? ANSI_UNDERLINE : ""; -} - -static inline const char *ansi_highlight(void) { - return colors_enabled() ? ANSI_HIGHLIGHT : ""; -} - -static inline const char *ansi_highlight_underline(void) { - return colors_enabled() ? ANSI_HIGHLIGHT_UNDERLINE : ""; -} - -static inline const char *ansi_highlight_red(void) { - return colors_enabled() ? ANSI_HIGHLIGHT_RED : ""; -} - -static inline const char *ansi_highlight_green(void) { - return colors_enabled() ? ANSI_HIGHLIGHT_GREEN : ""; -} - -static inline const char *ansi_highlight_yellow(void) { - return colors_enabled() ? ANSI_HIGHLIGHT_YELLOW : ""; -} - -static inline const char *ansi_highlight_blue(void) { - return colors_enabled() ? ANSI_HIGHLIGHT_BLUE : ""; -} - -static inline const char *ansi_normal(void) { - return colors_enabled() ? ANSI_NORMAL : ""; -} - -int get_ctty_devnr(pid_t pid, dev_t *d); -int get_ctty(pid_t, dev_t *_devnr, char **r); - -int getttyname_malloc(int fd, char **r); -int getttyname_harder(int fd, char **r); - -int ptsname_malloc(int fd, char **ret); -int ptsname_namespace(int pty, char **ret); - -int openpt_in_namespace(pid_t pid, int flags); -int open_terminal_in_namespace(pid_t pid, const char *name, int mode); diff --git a/src/basic/time-util.c b/src/basic/time-util.c deleted file mode 100644 index edd9179cb8..0000000000 --- a/src/basic/time-util.c +++ /dev/null @@ -1,1157 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "path-util.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" - -static nsec_t timespec_load_nsec(const struct timespec *ts); - -static clockid_t map_clock_id(clockid_t c) { - - /* Some more exotic archs (s390, ppc, …) lack the "ALARM" flavour of the clocks. Thus, clock_gettime() will - * fail for them. Since they are essentially the same as their non-ALARM pendants (their only difference is - * when timers are set on them), let's just map them accordingly. This way, we can get the correct time even on - * those archs. */ - - switch (c) { - - case CLOCK_BOOTTIME_ALARM: - return CLOCK_BOOTTIME; - - case CLOCK_REALTIME_ALARM: - return CLOCK_REALTIME; - - default: - return c; - } -} - -usec_t now(clockid_t clock_id) { - struct timespec ts; - - assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0); - - return timespec_load(&ts); -} - -nsec_t now_nsec(clockid_t clock_id) { - struct timespec ts; - - assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0); - - return timespec_load_nsec(&ts); -} - -dual_timestamp* dual_timestamp_get(dual_timestamp *ts) { - assert(ts); - - ts->realtime = now(CLOCK_REALTIME); - ts->monotonic = now(CLOCK_MONOTONIC); - - return ts; -} - -dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) { - int64_t delta; - assert(ts); - - if (u == USEC_INFINITY || u <= 0) { - ts->realtime = ts->monotonic = u; - return ts; - } - - ts->realtime = u; - - delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u; - ts->monotonic = usec_sub(now(CLOCK_MONOTONIC), delta); - - return ts; -} - -dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) { - int64_t delta; - assert(ts); - - if (u == USEC_INFINITY) { - ts->realtime = ts->monotonic = USEC_INFINITY; - return ts; - } - - ts->monotonic = u; - delta = (int64_t) now(CLOCK_MONOTONIC) - (int64_t) u; - ts->realtime = usec_sub(now(CLOCK_REALTIME), delta); - - return ts; -} - -dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, usec_t u) { - int64_t delta; - - if (u == USEC_INFINITY) { - ts->realtime = ts->monotonic = USEC_INFINITY; - return ts; - } - - dual_timestamp_get(ts); - delta = (int64_t) now(clock_boottime_or_monotonic()) - (int64_t) u; - ts->realtime = usec_sub(ts->realtime, delta); - ts->monotonic = usec_sub(ts->monotonic, delta); - - return ts; -} - -usec_t timespec_load(const struct timespec *ts) { - assert(ts); - - if (ts->tv_sec == (time_t) -1 && ts->tv_nsec == (long) -1) - return USEC_INFINITY; - - if ((usec_t) ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC) - return USEC_INFINITY; - - return - (usec_t) ts->tv_sec * USEC_PER_SEC + - (usec_t) ts->tv_nsec / NSEC_PER_USEC; -} - -static nsec_t timespec_load_nsec(const struct timespec *ts) { - assert(ts); - - if (ts->tv_sec == (time_t) -1 && ts->tv_nsec == (long) -1) - return NSEC_INFINITY; - - if ((nsec_t) ts->tv_sec >= (UINT64_MAX - ts->tv_nsec) / NSEC_PER_SEC) - return NSEC_INFINITY; - - return (nsec_t) ts->tv_sec * NSEC_PER_SEC + (nsec_t) ts->tv_nsec; -} - -struct timespec *timespec_store(struct timespec *ts, usec_t u) { - assert(ts); - - if (u == USEC_INFINITY) { - ts->tv_sec = (time_t) -1; - ts->tv_nsec = (long) -1; - return ts; - } - - ts->tv_sec = (time_t) (u / USEC_PER_SEC); - ts->tv_nsec = (long int) ((u % USEC_PER_SEC) * NSEC_PER_USEC); - - return ts; -} - -usec_t timeval_load(const struct timeval *tv) { - assert(tv); - - if (tv->tv_sec == (time_t) -1 && - tv->tv_usec == (suseconds_t) -1) - return USEC_INFINITY; - - if ((usec_t) tv->tv_sec > (UINT64_MAX - tv->tv_usec) / USEC_PER_SEC) - return USEC_INFINITY; - - return - (usec_t) tv->tv_sec * USEC_PER_SEC + - (usec_t) tv->tv_usec; -} - -struct timeval *timeval_store(struct timeval *tv, usec_t u) { - assert(tv); - - if (u == USEC_INFINITY) { - tv->tv_sec = (time_t) -1; - tv->tv_usec = (suseconds_t) -1; - } else { - tv->tv_sec = (time_t) (u / USEC_PER_SEC); - tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC); - } - - return tv; -} - -static char *format_timestamp_internal(char *buf, size_t l, usec_t t, - bool utc, bool us) { - struct tm tm; - time_t sec; - int k; - - assert(buf); - assert(l > 0); - - if (t <= 0 || t == USEC_INFINITY) - return NULL; - - sec = (time_t) (t / USEC_PER_SEC); - localtime_or_gmtime_r(&sec, &tm, utc); - - if (us) - k = strftime(buf, l, "%a %Y-%m-%d %H:%M:%S", &tm); - else - k = strftime(buf, l, "%a %Y-%m-%d %H:%M:%S %Z", &tm); - - if (k <= 0) - return NULL; - if (us) { - snprintf(buf + strlen(buf), l - strlen(buf), ".%06llu", (unsigned long long) (t % USEC_PER_SEC)); - if (strftime(buf + strlen(buf), l - strlen(buf), " %Z", &tm) <= 0) - return NULL; - } - - return buf; -} - -char *format_timestamp(char *buf, size_t l, usec_t t) { - return format_timestamp_internal(buf, l, t, false, false); -} - -char *format_timestamp_utc(char *buf, size_t l, usec_t t) { - return format_timestamp_internal(buf, l, t, true, false); -} - -char *format_timestamp_us(char *buf, size_t l, usec_t t) { - return format_timestamp_internal(buf, l, t, false, true); -} - -char *format_timestamp_us_utc(char *buf, size_t l, usec_t t) { - return format_timestamp_internal(buf, l, t, true, true); -} - -char *format_timestamp_relative(char *buf, size_t l, usec_t t) { - const char *s; - usec_t n, d; - - if (t <= 0 || t == USEC_INFINITY) - return NULL; - - n = now(CLOCK_REALTIME); - if (n > t) { - d = n - t; - s = "ago"; - } else { - d = t - n; - s = "left"; - } - - if (d >= USEC_PER_YEAR) - snprintf(buf, l, USEC_FMT " years " USEC_FMT " months %s", - d / USEC_PER_YEAR, - (d % USEC_PER_YEAR) / USEC_PER_MONTH, s); - else if (d >= USEC_PER_MONTH) - snprintf(buf, l, USEC_FMT " months " USEC_FMT " days %s", - d / USEC_PER_MONTH, - (d % USEC_PER_MONTH) / USEC_PER_DAY, s); - else if (d >= USEC_PER_WEEK) - snprintf(buf, l, USEC_FMT " weeks " USEC_FMT " days %s", - d / USEC_PER_WEEK, - (d % USEC_PER_WEEK) / USEC_PER_DAY, s); - else if (d >= 2*USEC_PER_DAY) - snprintf(buf, l, USEC_FMT " days %s", d / USEC_PER_DAY, s); - else if (d >= 25*USEC_PER_HOUR) - snprintf(buf, l, "1 day " USEC_FMT "h %s", - (d - USEC_PER_DAY) / USEC_PER_HOUR, s); - else if (d >= 6*USEC_PER_HOUR) - snprintf(buf, l, USEC_FMT "h %s", - d / USEC_PER_HOUR, s); - else if (d >= USEC_PER_HOUR) - snprintf(buf, l, USEC_FMT "h " USEC_FMT "min %s", - d / USEC_PER_HOUR, - (d % USEC_PER_HOUR) / USEC_PER_MINUTE, s); - else if (d >= 5*USEC_PER_MINUTE) - snprintf(buf, l, USEC_FMT "min %s", - d / USEC_PER_MINUTE, s); - else if (d >= USEC_PER_MINUTE) - snprintf(buf, l, USEC_FMT "min " USEC_FMT "s %s", - d / USEC_PER_MINUTE, - (d % USEC_PER_MINUTE) / USEC_PER_SEC, s); - else if (d >= USEC_PER_SEC) - snprintf(buf, l, USEC_FMT "s %s", - d / USEC_PER_SEC, s); - else if (d >= USEC_PER_MSEC) - snprintf(buf, l, USEC_FMT "ms %s", - d / USEC_PER_MSEC, s); - else if (d > 0) - snprintf(buf, l, USEC_FMT"us %s", - d, s); - else - snprintf(buf, l, "now"); - - buf[l-1] = 0; - return buf; -} - -char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) { - static const struct { - const char *suffix; - usec_t usec; - } table[] = { - { "y", USEC_PER_YEAR }, - { "month", USEC_PER_MONTH }, - { "w", USEC_PER_WEEK }, - { "d", USEC_PER_DAY }, - { "h", USEC_PER_HOUR }, - { "min", USEC_PER_MINUTE }, - { "s", USEC_PER_SEC }, - { "ms", USEC_PER_MSEC }, - { "us", 1 }, - }; - - unsigned i; - char *p = buf; - bool something = false; - - assert(buf); - assert(l > 0); - - if (t == USEC_INFINITY) { - strncpy(p, "infinity", l-1); - p[l-1] = 0; - return p; - } - - if (t <= 0) { - strncpy(p, "0", l-1); - p[l-1] = 0; - return p; - } - - /* The result of this function can be parsed with parse_sec */ - - for (i = 0; i < ELEMENTSOF(table); i++) { - int k = 0; - size_t n; - bool done = false; - usec_t a, b; - - if (t <= 0) - break; - - if (t < accuracy && something) - break; - - if (t < table[i].usec) - continue; - - if (l <= 1) - break; - - a = t / table[i].usec; - b = t % table[i].usec; - - /* Let's see if we should shows this in dot notation */ - if (t < USEC_PER_MINUTE && b > 0) { - usec_t cc; - int j; - - j = 0; - for (cc = table[i].usec; cc > 1; cc /= 10) - j++; - - for (cc = accuracy; cc > 1; cc /= 10) { - b /= 10; - j--; - } - - if (j > 0) { - k = snprintf(p, l, - "%s"USEC_FMT".%0*llu%s", - p > buf ? " " : "", - a, - j, - (unsigned long long) b, - table[i].suffix); - - t = 0; - done = true; - } - } - - /* No? Then let's show it normally */ - if (!done) { - k = snprintf(p, l, - "%s"USEC_FMT"%s", - p > buf ? " " : "", - a, - table[i].suffix); - - t = b; - } - - n = MIN((size_t) k, l); - - l -= n; - p += n; - - something = true; - } - - *p = 0; - - return buf; -} - -void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) { - - assert(f); - assert(name); - assert(t); - - if (!dual_timestamp_is_set(t)) - return; - - fprintf(f, "%s="USEC_FMT" "USEC_FMT"\n", - name, - t->realtime, - t->monotonic); -} - -int dual_timestamp_deserialize(const char *value, dual_timestamp *t) { - unsigned long long a, b; - - assert(value); - assert(t); - - if (sscanf(value, "%llu %llu", &a, &b) != 2) { - log_debug("Failed to parse dual timestamp value \"%s\": %m", value); - return -EINVAL; - } - - t->realtime = a; - t->monotonic = b; - - return 0; -} - -int timestamp_deserialize(const char *value, usec_t *timestamp) { - int r; - - assert(value); - - r = safe_atou64(value, timestamp); - if (r < 0) - return log_debug_errno(r, "Failed to parse timestamp value \"%s\": %m", value); - - return r; -} - -int parse_timestamp(const char *t, usec_t *usec) { - static const struct { - const char *name; - const int nr; - } day_nr[] = { - { "Sunday", 0 }, - { "Sun", 0 }, - { "Monday", 1 }, - { "Mon", 1 }, - { "Tuesday", 2 }, - { "Tue", 2 }, - { "Wednesday", 3 }, - { "Wed", 3 }, - { "Thursday", 4 }, - { "Thu", 4 }, - { "Friday", 5 }, - { "Fri", 5 }, - { "Saturday", 6 }, - { "Sat", 6 }, - }; - - const char *k; - const char *utc; - struct tm tm, copy; - time_t x; - usec_t x_usec, plus = 0, minus = 0, ret; - int r, weekday = -1; - unsigned i; - - /* - * Allowed syntaxes: - * - * 2012-09-22 16:34:22 - * 2012-09-22 16:34 (seconds will be set to 0) - * 2012-09-22 (time will be set to 00:00:00) - * 16:34:22 (date will be set to today) - * 16:34 (date will be set to today, seconds to 0) - * now - * yesterday (time is set to 00:00:00) - * today (time is set to 00:00:00) - * tomorrow (time is set to 00:00:00) - * +5min - * -5days - * @2147483647 (seconds since epoch) - * - */ - - assert(t); - assert(usec); - - if (t[0] == '@') - return parse_sec(t + 1, usec); - - ret = now(CLOCK_REALTIME); - - if (streq(t, "now")) - goto finish; - - else if (t[0] == '+') { - r = parse_sec(t+1, &plus); - if (r < 0) - return r; - - goto finish; - - } else if (t[0] == '-') { - r = parse_sec(t+1, &minus); - if (r < 0) - return r; - - goto finish; - - } else if ((k = endswith(t, " ago"))) { - t = strndupa(t, k - t); - - r = parse_sec(t, &minus); - if (r < 0) - return r; - - goto finish; - - } else if ((k = endswith(t, " left"))) { - t = strndupa(t, k - t); - - r = parse_sec(t, &plus); - if (r < 0) - return r; - - goto finish; - } - - utc = endswith_no_case(t, " UTC"); - if (utc) - t = strndupa(t, utc - t); - - x = ret / USEC_PER_SEC; - x_usec = 0; - - assert_se(localtime_or_gmtime_r(&x, &tm, utc)); - tm.tm_isdst = -1; - - if (streq(t, "today")) { - tm.tm_sec = tm.tm_min = tm.tm_hour = 0; - goto from_tm; - - } else if (streq(t, "yesterday")) { - tm.tm_mday--; - tm.tm_sec = tm.tm_min = tm.tm_hour = 0; - goto from_tm; - - } else if (streq(t, "tomorrow")) { - tm.tm_mday++; - tm.tm_sec = tm.tm_min = tm.tm_hour = 0; - goto from_tm; - } - - - for (i = 0; i < ELEMENTSOF(day_nr); i++) { - size_t skip; - - if (!startswith_no_case(t, day_nr[i].name)) - continue; - - skip = strlen(day_nr[i].name); - if (t[skip] != ' ') - continue; - - weekday = day_nr[i].nr; - t += skip + 1; - break; - } - - copy = tm; - k = strptime(t, "%y-%m-%d %H:%M:%S", &tm); - if (k) { - if (*k == '.') - goto parse_usec; - else if (*k == 0) - goto from_tm; - } - - tm = copy; - k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm); - if (k) { - if (*k == '.') - goto parse_usec; - else if (*k == 0) - goto from_tm; - } - - tm = copy; - k = strptime(t, "%y-%m-%d %H:%M", &tm); - if (k && *k == 0) { - tm.tm_sec = 0; - goto from_tm; - } - - tm = copy; - k = strptime(t, "%Y-%m-%d %H:%M", &tm); - if (k && *k == 0) { - tm.tm_sec = 0; - goto from_tm; - } - - tm = copy; - k = strptime(t, "%y-%m-%d", &tm); - if (k && *k == 0) { - tm.tm_sec = tm.tm_min = tm.tm_hour = 0; - goto from_tm; - } - - tm = copy; - k = strptime(t, "%Y-%m-%d", &tm); - if (k && *k == 0) { - tm.tm_sec = tm.tm_min = tm.tm_hour = 0; - goto from_tm; - } - - tm = copy; - k = strptime(t, "%H:%M:%S", &tm); - if (k) { - if (*k == '.') - goto parse_usec; - else if (*k == 0) - goto from_tm; - } - - tm = copy; - k = strptime(t, "%H:%M", &tm); - if (k && *k == 0) { - tm.tm_sec = 0; - goto from_tm; - } - - return -EINVAL; - -parse_usec: - { - unsigned add; - - k++; - r = parse_fractional_part_u(&k, 6, &add); - if (r < 0) - return -EINVAL; - - if (*k) - return -EINVAL; - - x_usec = add; - - } - -from_tm: - x = mktime_or_timegm(&tm, utc); - if (x == (time_t) -1) - return -EINVAL; - - if (weekday >= 0 && tm.tm_wday != weekday) - return -EINVAL; - - ret = (usec_t) x * USEC_PER_SEC + x_usec; - -finish: - ret += plus; - if (ret > minus) - ret -= minus; - else - ret = 0; - - *usec = ret; - - return 0; -} - -static char* extract_multiplier(char *p, usec_t *multiplier) { - static const struct { - const char *suffix; - usec_t usec; - } table[] = { - { "seconds", USEC_PER_SEC }, - { "second", USEC_PER_SEC }, - { "sec", USEC_PER_SEC }, - { "s", USEC_PER_SEC }, - { "minutes", USEC_PER_MINUTE }, - { "minute", USEC_PER_MINUTE }, - { "min", USEC_PER_MINUTE }, - { "months", USEC_PER_MONTH }, - { "month", USEC_PER_MONTH }, - { "M", USEC_PER_MONTH }, - { "msec", USEC_PER_MSEC }, - { "ms", USEC_PER_MSEC }, - { "m", USEC_PER_MINUTE }, - { "hours", USEC_PER_HOUR }, - { "hour", USEC_PER_HOUR }, - { "hr", USEC_PER_HOUR }, - { "h", USEC_PER_HOUR }, - { "days", USEC_PER_DAY }, - { "day", USEC_PER_DAY }, - { "d", USEC_PER_DAY }, - { "weeks", USEC_PER_WEEK }, - { "week", USEC_PER_WEEK }, - { "w", USEC_PER_WEEK }, - { "years", USEC_PER_YEAR }, - { "year", USEC_PER_YEAR }, - { "y", USEC_PER_YEAR }, - { "usec", 1ULL }, - { "us", 1ULL }, - }; - unsigned i; - - for (i = 0; i < ELEMENTSOF(table); i++) { - char *e; - - e = startswith(p, table[i].suffix); - if (e) { - *multiplier = table[i].usec; - return e; - } - } - - return p; -} - -int parse_time(const char *t, usec_t *usec, usec_t default_unit) { - const char *p, *s; - usec_t r = 0; - bool something = false; - - assert(t); - assert(usec); - assert(default_unit > 0); - - p = t; - - p += strspn(p, WHITESPACE); - s = startswith(p, "infinity"); - if (s) { - s += strspn(s, WHITESPACE); - if (*s != 0) - return -EINVAL; - - *usec = USEC_INFINITY; - return 0; - } - - for (;;) { - long long l, z = 0; - char *e; - unsigned n = 0; - usec_t multiplier = default_unit, k; - - p += strspn(p, WHITESPACE); - - if (*p == 0) { - if (!something) - return -EINVAL; - - break; - } - - errno = 0; - l = strtoll(p, &e, 10); - if (errno > 0) - return -errno; - if (l < 0) - return -ERANGE; - - if (*e == '.') { - char *b = e + 1; - - errno = 0; - z = strtoll(b, &e, 10); - if (errno > 0) - return -errno; - - if (z < 0) - return -ERANGE; - - if (e == b) - return -EINVAL; - - n = e - b; - - } else if (e == p) - return -EINVAL; - - e += strspn(e, WHITESPACE); - p = extract_multiplier(e, &multiplier); - - something = true; - - k = (usec_t) z * multiplier; - - for (; n > 0; n--) - k /= 10; - - r += (usec_t) l * multiplier + k; - } - - *usec = r; - - return 0; -} - -int parse_sec(const char *t, usec_t *usec) { - return parse_time(t, usec, USEC_PER_SEC); -} - -int parse_nsec(const char *t, nsec_t *nsec) { - static const struct { - const char *suffix; - nsec_t nsec; - } table[] = { - { "seconds", NSEC_PER_SEC }, - { "second", NSEC_PER_SEC }, - { "sec", NSEC_PER_SEC }, - { "s", NSEC_PER_SEC }, - { "minutes", NSEC_PER_MINUTE }, - { "minute", NSEC_PER_MINUTE }, - { "min", NSEC_PER_MINUTE }, - { "months", NSEC_PER_MONTH }, - { "month", NSEC_PER_MONTH }, - { "msec", NSEC_PER_MSEC }, - { "ms", NSEC_PER_MSEC }, - { "m", NSEC_PER_MINUTE }, - { "hours", NSEC_PER_HOUR }, - { "hour", NSEC_PER_HOUR }, - { "hr", NSEC_PER_HOUR }, - { "h", NSEC_PER_HOUR }, - { "days", NSEC_PER_DAY }, - { "day", NSEC_PER_DAY }, - { "d", NSEC_PER_DAY }, - { "weeks", NSEC_PER_WEEK }, - { "week", NSEC_PER_WEEK }, - { "w", NSEC_PER_WEEK }, - { "years", NSEC_PER_YEAR }, - { "year", NSEC_PER_YEAR }, - { "y", NSEC_PER_YEAR }, - { "usec", NSEC_PER_USEC }, - { "us", NSEC_PER_USEC }, - { "nsec", 1ULL }, - { "ns", 1ULL }, - { "", 1ULL }, /* default is nsec */ - }; - - const char *p, *s; - nsec_t r = 0; - bool something = false; - - assert(t); - assert(nsec); - - p = t; - - p += strspn(p, WHITESPACE); - s = startswith(p, "infinity"); - if (s) { - s += strspn(s, WHITESPACE); - if (*s != 0) - return -EINVAL; - - *nsec = NSEC_INFINITY; - return 0; - } - - for (;;) { - long long l, z = 0; - char *e; - unsigned i, n = 0; - - p += strspn(p, WHITESPACE); - - if (*p == 0) { - if (!something) - return -EINVAL; - - break; - } - - errno = 0; - l = strtoll(p, &e, 10); - - if (errno > 0) - return -errno; - - if (l < 0) - return -ERANGE; - - if (*e == '.') { - char *b = e + 1; - - errno = 0; - z = strtoll(b, &e, 10); - if (errno > 0) - return -errno; - - if (z < 0) - return -ERANGE; - - if (e == b) - return -EINVAL; - - n = e - b; - - } else if (e == p) - return -EINVAL; - - e += strspn(e, WHITESPACE); - - for (i = 0; i < ELEMENTSOF(table); i++) - if (startswith(e, table[i].suffix)) { - nsec_t k = (nsec_t) z * table[i].nsec; - - for (; n > 0; n--) - k /= 10; - - r += (nsec_t) l * table[i].nsec + k; - p = e + strlen(table[i].suffix); - - something = true; - break; - } - - if (i >= ELEMENTSOF(table)) - return -EINVAL; - - } - - *nsec = r; - - return 0; -} - -bool ntp_synced(void) { - struct timex txc = {}; - - if (adjtimex(&txc) < 0) - return false; - - if (txc.status & STA_UNSYNC) - return false; - - return true; -} - -int get_timezones(char ***ret) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_strv_free_ char **zones = NULL; - size_t n_zones = 0, n_allocated = 0; - - assert(ret); - - zones = strv_new("UTC", NULL); - if (!zones) - return -ENOMEM; - - n_allocated = 2; - n_zones = 1; - - f = fopen("/usr/share/zoneinfo/zone.tab", "re"); - if (f) { - char l[LINE_MAX]; - - FOREACH_LINE(l, f, return -errno) { - char *p, *w; - size_t k; - - p = strstrip(l); - - if (isempty(p) || *p == '#') - continue; - - /* Skip over country code */ - p += strcspn(p, WHITESPACE); - p += strspn(p, WHITESPACE); - - /* Skip over coordinates */ - p += strcspn(p, WHITESPACE); - p += strspn(p, WHITESPACE); - - /* Found timezone name */ - k = strcspn(p, WHITESPACE); - if (k <= 0) - continue; - - w = strndup(p, k); - if (!w) - return -ENOMEM; - - if (!GREEDY_REALLOC(zones, n_allocated, n_zones + 2)) { - free(w); - return -ENOMEM; - } - - zones[n_zones++] = w; - zones[n_zones] = NULL; - } - - strv_sort(zones); - - } else if (errno != ENOENT) - return -errno; - - *ret = zones; - zones = NULL; - - return 0; -} - -bool timezone_is_valid(const char *name) { - bool slash = false; - const char *p, *t; - struct stat st; - - if (isempty(name)) - return false; - - if (name[0] == '/') - return false; - - for (p = name; *p; p++) { - if (!(*p >= '0' && *p <= '9') && - !(*p >= 'a' && *p <= 'z') && - !(*p >= 'A' && *p <= 'Z') && - !(*p == '-' || *p == '_' || *p == '+' || *p == '/')) - return false; - - if (*p == '/') { - - if (slash) - return false; - - slash = true; - } else - slash = false; - } - - if (slash) - return false; - - t = strjoina("/usr/share/zoneinfo/", name); - if (stat(t, &st) < 0) - return false; - - if (!S_ISREG(st.st_mode)) - return false; - - return true; -} - -bool clock_boottime_supported(void) { - static int supported = -1; - - /* Note that this checks whether CLOCK_BOOTTIME is available in general as well as available for timerfds()! */ - - if (supported < 0) { - int fd; - - fd = timerfd_create(CLOCK_BOOTTIME, TFD_NONBLOCK|TFD_CLOEXEC); - if (fd < 0) - supported = false; - else { - safe_close(fd); - supported = true; - } - } - - return supported; -} - -clockid_t clock_boottime_or_monotonic(void) { - if (clock_boottime_supported()) - return CLOCK_BOOTTIME; - else - return CLOCK_MONOTONIC; -} - -int get_timezone(char **tz) { - _cleanup_free_ char *t = NULL; - const char *e; - char *z; - int r; - - r = readlink_malloc("/etc/localtime", &t); - if (r < 0) - return r; /* returns EINVAL if not a symlink */ - - e = path_startswith(t, "/usr/share/zoneinfo/"); - if (!e) - e = path_startswith(t, "../usr/share/zoneinfo/"); - if (!e) - return -EINVAL; - - if (!timezone_is_valid(e)) - return -EINVAL; - - z = strdup(e); - if (!z) - return -ENOMEM; - - *tz = z; - return 0; -} - -time_t mktime_or_timegm(struct tm *tm, bool utc) { - return utc ? timegm(tm) : mktime(tm); -} - -struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc) { - return utc ? gmtime_r(t, tm) : localtime_r(t, tm); -} - -unsigned long usec_to_jiffies(usec_t u) { - static thread_local unsigned long hz = 0; - long r; - - if (hz == 0) { - r = sysconf(_SC_CLK_TCK); - - assert(r > 0); - hz = (unsigned long) r; - } - - return DIV_ROUND_UP(u , USEC_PER_SEC / hz); -} diff --git a/src/basic/time-util.h b/src/basic/time-util.h deleted file mode 100644 index a5e3f567ec..0000000000 --- a/src/basic/time-util.h +++ /dev/null @@ -1,153 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include - -typedef uint64_t usec_t; -typedef uint64_t nsec_t; - -#define NSEC_FMT "%" PRIu64 -#define USEC_FMT "%" PRIu64 - -#include "macro.h" - -typedef struct dual_timestamp { - usec_t realtime; - usec_t monotonic; -} dual_timestamp; - -#define USEC_INFINITY ((usec_t) -1) -#define NSEC_INFINITY ((nsec_t) -1) - -#define MSEC_PER_SEC 1000ULL -#define USEC_PER_SEC ((usec_t) 1000000ULL) -#define USEC_PER_MSEC ((usec_t) 1000ULL) -#define NSEC_PER_SEC ((nsec_t) 1000000000ULL) -#define NSEC_PER_MSEC ((nsec_t) 1000000ULL) -#define NSEC_PER_USEC ((nsec_t) 1000ULL) - -#define USEC_PER_MINUTE ((usec_t) (60ULL*USEC_PER_SEC)) -#define NSEC_PER_MINUTE ((nsec_t) (60ULL*NSEC_PER_SEC)) -#define USEC_PER_HOUR ((usec_t) (60ULL*USEC_PER_MINUTE)) -#define NSEC_PER_HOUR ((nsec_t) (60ULL*NSEC_PER_MINUTE)) -#define USEC_PER_DAY ((usec_t) (24ULL*USEC_PER_HOUR)) -#define NSEC_PER_DAY ((nsec_t) (24ULL*NSEC_PER_HOUR)) -#define USEC_PER_WEEK ((usec_t) (7ULL*USEC_PER_DAY)) -#define NSEC_PER_WEEK ((nsec_t) (7ULL*NSEC_PER_DAY)) -#define USEC_PER_MONTH ((usec_t) (2629800ULL*USEC_PER_SEC)) -#define NSEC_PER_MONTH ((nsec_t) (2629800ULL*NSEC_PER_SEC)) -#define USEC_PER_YEAR ((usec_t) (31557600ULL*USEC_PER_SEC)) -#define NSEC_PER_YEAR ((nsec_t) (31557600ULL*NSEC_PER_SEC)) - -#define FORMAT_TIMESTAMP_MAX ((4*4+1)+11+9+4+1) /* weekdays can be unicode */ -#define FORMAT_TIMESTAMP_WIDTH 28 /* when outputting, assume this width */ -#define FORMAT_TIMESTAMP_RELATIVE_MAX 256 -#define FORMAT_TIMESPAN_MAX 64 - -#define TIME_T_MAX (time_t)((UINTMAX_C(1) << ((sizeof(time_t) << 3) - 1)) - 1) - -#define DUAL_TIMESTAMP_NULL ((struct dual_timestamp) { 0ULL, 0ULL }) - -usec_t now(clockid_t clock); -nsec_t now_nsec(clockid_t clock); - -dual_timestamp* dual_timestamp_get(dual_timestamp *ts); -dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u); -dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u); -dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, usec_t u); - -static inline bool dual_timestamp_is_set(dual_timestamp *ts) { - return ((ts->realtime > 0 && ts->realtime != USEC_INFINITY) || - (ts->monotonic > 0 && ts->monotonic != USEC_INFINITY)); -} - -usec_t timespec_load(const struct timespec *ts) _pure_; -struct timespec *timespec_store(struct timespec *ts, usec_t u); - -usec_t timeval_load(const struct timeval *tv) _pure_; -struct timeval *timeval_store(struct timeval *tv, usec_t u); - -char *format_timestamp(char *buf, size_t l, usec_t t); -char *format_timestamp_utc(char *buf, size_t l, usec_t t); -char *format_timestamp_us(char *buf, size_t l, usec_t t); -char *format_timestamp_us_utc(char *buf, size_t l, usec_t t); -char *format_timestamp_relative(char *buf, size_t l, usec_t t); -char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy); - -void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t); -int dual_timestamp_deserialize(const char *value, dual_timestamp *t); -int timestamp_deserialize(const char *value, usec_t *timestamp); - -int parse_timestamp(const char *t, usec_t *usec); - -int parse_sec(const char *t, usec_t *usec); -int parse_time(const char *t, usec_t *usec, usec_t default_unit); -int parse_nsec(const char *t, nsec_t *nsec); - -bool ntp_synced(void); - -int get_timezones(char ***l); -bool timezone_is_valid(const char *name); - -bool clock_boottime_supported(void); -clockid_t clock_boottime_or_monotonic(void); - -#define xstrftime(buf, fmt, tm) \ - assert_message_se(strftime(buf, ELEMENTSOF(buf), fmt, tm) > 0, \ - "xstrftime: " #buf "[] must be big enough") - -int get_timezone(char **timezone); - -time_t mktime_or_timegm(struct tm *tm, bool utc); -struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc); - -unsigned long usec_to_jiffies(usec_t usec); - -static inline usec_t usec_add(usec_t a, usec_t b) { - usec_t c; - - /* Adds two time values, and makes sure USEC_INFINITY as input results as USEC_INFINITY in output, and doesn't - * overflow. */ - - c = a + b; - if (c < a || c < b) /* overflow check */ - return USEC_INFINITY; - - return c; -} - -static inline usec_t usec_sub(usec_t timestamp, int64_t delta) { - if (delta < 0) - return usec_add(timestamp, (usec_t) (-delta)); - - if (timestamp == USEC_INFINITY) /* Make sure infinity doesn't degrade */ - return USEC_INFINITY; - - if (timestamp < (usec_t) delta) - return 0; - - return timestamp - delta; -} diff --git a/src/basic/umask-util.h b/src/basic/umask-util.h deleted file mode 100644 index 359d87d27c..0000000000 --- a/src/basic/umask-util.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include - -#include "macro.h" - -static inline void umaskp(mode_t *u) { - umask(*u); -} - -#define _cleanup_umask_ _cleanup_(umaskp) - -struct _umask_struct_ { - mode_t mask; - bool quit; -}; - -static inline void _reset_umask_(struct _umask_struct_ *s) { - umask(s->mask); -}; - -#define RUN_WITH_UMASK(mask) \ - for (_cleanup_(_reset_umask_) struct _umask_struct_ _saved_umask_ = { umask(mask), false }; \ - !_saved_umask_.quit ; \ - _saved_umask_.quit = true) diff --git a/src/basic/unaligned.h b/src/basic/unaligned.h deleted file mode 100644 index 79be645bed..0000000000 --- a/src/basic/unaligned.h +++ /dev/null @@ -1,111 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 - -/* BE */ - -static inline uint16_t unaligned_read_be16(const void *_u) { - const uint8_t *u = _u; - - return (((uint16_t) u[0]) << 8) | - ((uint16_t) u[1]); -} - -static inline uint32_t unaligned_read_be32(const void *_u) { - const uint8_t *u = _u; - - return (((uint32_t) unaligned_read_be16(u)) << 16) | - ((uint32_t) unaligned_read_be16(u + 2)); -} - -static inline uint64_t unaligned_read_be64(const void *_u) { - const uint8_t *u = _u; - - return (((uint64_t) unaligned_read_be32(u)) << 32) | - ((uint64_t) unaligned_read_be32(u + 4)); -} - -static inline void unaligned_write_be16(void *_u, uint16_t a) { - uint8_t *u = _u; - - u[0] = (uint8_t) (a >> 8); - u[1] = (uint8_t) a; -} - -static inline void unaligned_write_be32(void *_u, uint32_t a) { - uint8_t *u = _u; - - unaligned_write_be16(u, (uint16_t) (a >> 16)); - unaligned_write_be16(u + 2, (uint16_t) a); -} - -static inline void unaligned_write_be64(void *_u, uint64_t a) { - uint8_t *u = _u; - - unaligned_write_be32(u, (uint32_t) (a >> 32)); - unaligned_write_be32(u + 4, (uint32_t) a); -} - -/* LE */ - -static inline uint16_t unaligned_read_le16(const void *_u) { - const uint8_t *u = _u; - - return (((uint16_t) u[1]) << 8) | - ((uint16_t) u[0]); -} - -static inline uint32_t unaligned_read_le32(const void *_u) { - const uint8_t *u = _u; - - return (((uint32_t) unaligned_read_le16(u + 2)) << 16) | - ((uint32_t) unaligned_read_le16(u)); -} - -static inline uint64_t unaligned_read_le64(const void *_u) { - const uint8_t *u = _u; - - return (((uint64_t) unaligned_read_le32(u + 4)) << 32) | - ((uint64_t) unaligned_read_le32(u)); -} - -static inline void unaligned_write_le16(void *_u, uint16_t a) { - uint8_t *u = _u; - - u[0] = (uint8_t) a; - u[1] = (uint8_t) (a >> 8); -} - -static inline void unaligned_write_le32(void *_u, uint32_t a) { - uint8_t *u = _u; - - unaligned_write_le16(u, (uint16_t) a); - unaligned_write_le16(u + 2, (uint16_t) (a >> 16)); -} - -static inline void unaligned_write_le64(void *_u, uint64_t a) { - uint8_t *u = _u; - - unaligned_write_le32(u, (uint32_t) a); - unaligned_write_le32(u + 4, (uint32_t) (a >> 32)); -} diff --git a/src/basic/unit-name.c b/src/basic/unit-name.c deleted file mode 100644 index fe883b95c7..0000000000 --- a/src/basic/unit-name.c +++ /dev/null @@ -1,1049 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include - -#include "alloc-util.h" -#include "bus-label.h" -#include "glob-util.h" -#include "hexdecoct.h" -#include "macro.h" -#include "path-util.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" - -/* Characters valid in a unit name. */ -#define VALID_CHARS \ - DIGITS \ - LETTERS \ - ":-_.\\" - -/* The same, but also permits the single @ character that may appear */ -#define VALID_CHARS_WITH_AT \ - "@" \ - VALID_CHARS - -/* All chars valid in a unit name glob */ -#define VALID_CHARS_GLOB \ - VALID_CHARS_WITH_AT \ - "[]!-*?" - -bool unit_name_is_valid(const char *n, UnitNameFlags flags) { - const char *e, *i, *at; - - assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0); - - if (_unlikely_(flags == 0)) - return false; - - if (isempty(n)) - return false; - - if (strlen(n) >= UNIT_NAME_MAX) - return false; - - e = strrchr(n, '.'); - if (!e || e == n) - return false; - - if (unit_type_from_string(e + 1) < 0) - return false; - - for (i = n, at = NULL; i < e; i++) { - - if (*i == '@' && !at) - at = i; - - if (!strchr("@" VALID_CHARS, *i)) - return false; - } - - if (at == n) - return false; - - if (flags & UNIT_NAME_PLAIN) - if (!at) - return true; - - if (flags & UNIT_NAME_INSTANCE) - if (at && e > at + 1) - return true; - - if (flags & UNIT_NAME_TEMPLATE) - if (at && e == at + 1) - return true; - - return false; -} - -bool unit_prefix_is_valid(const char *p) { - - /* We don't allow additional @ in the prefix string */ - - if (isempty(p)) - return false; - - return in_charset(p, VALID_CHARS); -} - -bool unit_instance_is_valid(const char *i) { - - /* The max length depends on the length of the string, so we - * don't really check this here. */ - - if (isempty(i)) - return false; - - /* We allow additional @ in the instance string, we do not - * allow them in the prefix! */ - - return in_charset(i, "@" VALID_CHARS); -} - -bool unit_suffix_is_valid(const char *s) { - if (isempty(s)) - return false; - - if (s[0] != '.') - return false; - - if (unit_type_from_string(s + 1) < 0) - return false; - - return true; -} - -int unit_name_to_prefix(const char *n, char **ret) { - const char *p; - char *s; - - assert(n); - assert(ret); - - if (!unit_name_is_valid(n, UNIT_NAME_ANY)) - return -EINVAL; - - p = strchr(n, '@'); - if (!p) - p = strrchr(n, '.'); - - assert_se(p); - - s = strndup(n, p - n); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - -int unit_name_to_instance(const char *n, char **instance) { - const char *p, *d; - char *i; - - assert(n); - assert(instance); - - if (!unit_name_is_valid(n, UNIT_NAME_ANY)) - return -EINVAL; - - /* Everything past the first @ and before the last . is the instance */ - p = strchr(n, '@'); - if (!p) { - *instance = NULL; - return 0; - } - - p++; - - d = strrchr(p, '.'); - if (!d) - return -EINVAL; - - i = strndup(p, d-p); - if (!i) - return -ENOMEM; - - *instance = i; - return 1; -} - -int unit_name_to_prefix_and_instance(const char *n, char **ret) { - const char *d; - char *s; - - assert(n); - assert(ret); - - if (!unit_name_is_valid(n, UNIT_NAME_ANY)) - return -EINVAL; - - d = strrchr(n, '.'); - if (!d) - return -EINVAL; - - s = strndup(n, d - n); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - -UnitType unit_name_to_type(const char *n) { - const char *e; - - assert(n); - - if (!unit_name_is_valid(n, UNIT_NAME_ANY)) - return _UNIT_TYPE_INVALID; - - assert_se(e = strrchr(n, '.')); - - return unit_type_from_string(e + 1); -} - -int unit_name_change_suffix(const char *n, const char *suffix, char **ret) { - char *e, *s; - size_t a, b; - - assert(n); - assert(suffix); - assert(ret); - - if (!unit_name_is_valid(n, UNIT_NAME_ANY)) - return -EINVAL; - - if (!unit_suffix_is_valid(suffix)) - return -EINVAL; - - assert_se(e = strrchr(n, '.')); - - a = e - n; - b = strlen(suffix); - - s = new(char, a + b + 1); - if (!s) - return -ENOMEM; - - strcpy(mempcpy(s, n, a), suffix); - *ret = s; - - return 0; -} - -int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) { - char *s; - - assert(prefix); - assert(suffix); - assert(ret); - - if (!unit_prefix_is_valid(prefix)) - return -EINVAL; - - if (instance && !unit_instance_is_valid(instance)) - return -EINVAL; - - if (!unit_suffix_is_valid(suffix)) - return -EINVAL; - - if (!instance) - s = strappend(prefix, suffix); - else - s = strjoin(prefix, "@", instance, suffix, NULL); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - -static char *do_escape_char(char c, char *t) { - assert(t); - - *(t++) = '\\'; - *(t++) = 'x'; - *(t++) = hexchar(c >> 4); - *(t++) = hexchar(c); - - return t; -} - -static char *do_escape(const char *f, char *t) { - assert(f); - assert(t); - - /* do not create units with a leading '.', like for "/.dotdir" mount points */ - if (*f == '.') { - t = do_escape_char(*f, t); - f++; - } - - for (; *f; f++) { - if (*f == '/') - *(t++) = '-'; - else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f)) - t = do_escape_char(*f, t); - else - *(t++) = *f; - } - - return t; -} - -char *unit_name_escape(const char *f) { - char *r, *t; - - assert(f); - - r = new(char, strlen(f)*4+1); - if (!r) - return NULL; - - t = do_escape(f, r); - *t = 0; - - return r; -} - -int unit_name_unescape(const char *f, char **ret) { - _cleanup_free_ char *r = NULL; - char *t; - - assert(f); - - r = strdup(f); - if (!r) - return -ENOMEM; - - for (t = r; *f; f++) { - if (*f == '-') - *(t++) = '/'; - else if (*f == '\\') { - int a, b; - - if (f[1] != 'x') - return -EINVAL; - - a = unhexchar(f[2]); - if (a < 0) - return -EINVAL; - - b = unhexchar(f[3]); - if (b < 0) - return -EINVAL; - - *(t++) = (char) (((uint8_t) a << 4U) | (uint8_t) b); - f += 3; - } else - *(t++) = *f; - } - - *t = 0; - - *ret = r; - r = NULL; - - return 0; -} - -int unit_name_path_escape(const char *f, char **ret) { - char *p, *s; - - assert(f); - assert(ret); - - p = strdupa(f); - if (!p) - return -ENOMEM; - - path_kill_slashes(p); - - if (STR_IN_SET(p, "/", "")) - s = strdup("-"); - else { - char *e; - - if (!path_is_safe(p)) - return -EINVAL; - - /* Truncate trailing slashes */ - e = endswith(p, "/"); - if (e) - *e = 0; - - /* Truncate leading slashes */ - if (p[0] == '/') - p++; - - s = unit_name_escape(p); - } - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - -int unit_name_path_unescape(const char *f, char **ret) { - char *s; - int r; - - assert(f); - - if (isempty(f)) - return -EINVAL; - - if (streq(f, "-")) { - s = strdup("/"); - if (!s) - return -ENOMEM; - } else { - char *w; - - r = unit_name_unescape(f, &w); - if (r < 0) - return r; - - /* Don't accept trailing or leading slashes */ - if (startswith(w, "/") || endswith(w, "/")) { - free(w); - return -EINVAL; - } - - /* Prefix a slash again */ - s = strappend("/", w); - free(w); - if (!s) - return -ENOMEM; - - if (!path_is_safe(s)) { - free(s); - return -EINVAL; - } - } - - if (ret) - *ret = s; - else - free(s); - - return 0; -} - -int unit_name_replace_instance(const char *f, const char *i, char **ret) { - const char *p, *e; - char *s; - size_t a, b; - - assert(f); - assert(i); - assert(ret); - - if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) - return -EINVAL; - if (!unit_instance_is_valid(i)) - return -EINVAL; - - assert_se(p = strchr(f, '@')); - assert_se(e = strrchr(f, '.')); - - a = p - f; - b = strlen(i); - - s = new(char, a + 1 + b + strlen(e) + 1); - if (!s) - return -ENOMEM; - - strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e); - - *ret = s; - return 0; -} - -int unit_name_template(const char *f, char **ret) { - const char *p, *e; - char *s; - size_t a; - - assert(f); - assert(ret); - - if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) - return -EINVAL; - - assert_se(p = strchr(f, '@')); - assert_se(e = strrchr(f, '.')); - - a = p - f; - - s = new(char, a + 1 + strlen(e) + 1); - if (!s) - return -ENOMEM; - - strcpy(mempcpy(s, f, a + 1), e); - - *ret = s; - return 0; -} - -int unit_name_from_path(const char *path, const char *suffix, char **ret) { - _cleanup_free_ char *p = NULL; - char *s = NULL; - int r; - - assert(path); - assert(suffix); - assert(ret); - - if (!unit_suffix_is_valid(suffix)) - return -EINVAL; - - r = unit_name_path_escape(path, &p); - if (r < 0) - return r; - - s = strappend(p, suffix); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - -int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) { - _cleanup_free_ char *p = NULL; - char *s; - int r; - - assert(prefix); - assert(path); - assert(suffix); - assert(ret); - - if (!unit_prefix_is_valid(prefix)) - return -EINVAL; - - if (!unit_suffix_is_valid(suffix)) - return -EINVAL; - - r = unit_name_path_escape(path, &p); - if (r < 0) - return r; - - s = strjoin(prefix, "@", p, suffix, NULL); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - -int unit_name_to_path(const char *name, char **ret) { - _cleanup_free_ char *prefix = NULL; - int r; - - assert(name); - - r = unit_name_to_prefix(name, &prefix); - if (r < 0) - return r; - - return unit_name_path_unescape(prefix, ret); -} - -char *unit_dbus_path_from_name(const char *name) { - _cleanup_free_ char *e = NULL; - - assert(name); - - e = bus_label_escape(name); - if (!e) - return NULL; - - return strappend("/org/freedesktop/systemd1/unit/", e); -} - -int unit_name_from_dbus_path(const char *path, char **name) { - const char *e; - char *n; - - e = startswith(path, "/org/freedesktop/systemd1/unit/"); - if (!e) - return -EINVAL; - - n = bus_label_unescape(e); - if (!n) - return -ENOMEM; - - *name = n; - return 0; -} - -const char* unit_dbus_interface_from_type(UnitType t) { - - static const char *const table[_UNIT_TYPE_MAX] = { - [UNIT_SERVICE] = "org.freedesktop.systemd1.Service", - [UNIT_SOCKET] = "org.freedesktop.systemd1.Socket", - [UNIT_BUSNAME] = "org.freedesktop.systemd1.BusName", - [UNIT_TARGET] = "org.freedesktop.systemd1.Target", - [UNIT_DEVICE] = "org.freedesktop.systemd1.Device", - [UNIT_MOUNT] = "org.freedesktop.systemd1.Mount", - [UNIT_AUTOMOUNT] = "org.freedesktop.systemd1.Automount", - [UNIT_SWAP] = "org.freedesktop.systemd1.Swap", - [UNIT_TIMER] = "org.freedesktop.systemd1.Timer", - [UNIT_PATH] = "org.freedesktop.systemd1.Path", - [UNIT_SLICE] = "org.freedesktop.systemd1.Slice", - [UNIT_SCOPE] = "org.freedesktop.systemd1.Scope", - }; - - if (t < 0) - return NULL; - if (t >= _UNIT_TYPE_MAX) - return NULL; - - return table[t]; -} - -const char *unit_dbus_interface_from_name(const char *name) { - UnitType t; - - t = unit_name_to_type(name); - if (t < 0) - return NULL; - - return unit_dbus_interface_from_type(t); -} - -static char *do_escape_mangle(const char *f, UnitNameMangle allow_globs, char *t) { - const char *valid_chars; - - assert(f); - assert(IN_SET(allow_globs, UNIT_NAME_GLOB, UNIT_NAME_NOGLOB)); - assert(t); - - /* We'll only escape the obvious characters here, to play - * safe. */ - - valid_chars = allow_globs == UNIT_NAME_GLOB ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT; - - for (; *f; f++) { - if (*f == '/') - *(t++) = '-'; - else if (!strchr(valid_chars, *f)) - t = do_escape_char(*f, t); - else - *(t++) = *f; - } - - return t; -} - -/** - * Convert a string to a unit name. /dev/blah is converted to dev-blah.device, - * /blah/blah is converted to blah-blah.mount, anything else is left alone, - * except that @suffix is appended if a valid unit suffix is not present. - * - * If @allow_globs, globs characters are preserved. Otherwise, they are escaped. - */ -int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, const char *suffix, char **ret) { - char *s, *t; - int r; - - assert(name); - assert(suffix); - assert(ret); - - if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */ - return -EINVAL; - - if (!unit_suffix_is_valid(suffix)) - return -EINVAL; - - /* Already a fully valid unit name? If so, no mangling is necessary... */ - if (unit_name_is_valid(name, UNIT_NAME_ANY)) - goto good; - - /* Already a fully valid globbing expression? If so, no mangling is necessary either... */ - if (allow_globs == UNIT_NAME_GLOB && - string_is_glob(name) && - in_charset(name, VALID_CHARS_GLOB)) - goto good; - - if (is_device_path(name)) { - r = unit_name_from_path(name, ".device", ret); - if (r >= 0) - return 1; - if (r != -EINVAL) - return r; - } - - if (path_is_absolute(name)) { - r = unit_name_from_path(name, ".mount", ret); - if (r >= 0) - return 1; - if (r != -EINVAL) - return r; - } - - s = new(char, strlen(name) * 4 + strlen(suffix) + 1); - if (!s) - return -ENOMEM; - - t = do_escape_mangle(name, allow_globs, s); - *t = 0; - - /* Append a suffix if it doesn't have any, but only if this is not a glob, so that we can allow "foo.*" as a - * valid glob. */ - if ((allow_globs != UNIT_NAME_GLOB || !string_is_glob(s)) && unit_name_to_type(s) < 0) - strcpy(t, suffix); - - *ret = s; - return 1; - -good: - s = strdup(name); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - -int slice_build_parent_slice(const char *slice, char **ret) { - char *s, *dash; - int r; - - assert(slice); - assert(ret); - - if (!slice_name_is_valid(slice)) - return -EINVAL; - - if (streq(slice, "-.slice")) { - *ret = NULL; - return 0; - } - - s = strdup(slice); - if (!s) - return -ENOMEM; - - dash = strrchr(s, '-'); - if (dash) - strcpy(dash, ".slice"); - else { - r = free_and_strdup(&s, "-.slice"); - if (r < 0) { - free(s); - return r; - } - } - - *ret = s; - return 1; -} - -int slice_build_subslice(const char *slice, const char*name, char **ret) { - char *subslice; - - assert(slice); - assert(name); - assert(ret); - - if (!slice_name_is_valid(slice)) - return -EINVAL; - - if (!unit_prefix_is_valid(name)) - return -EINVAL; - - if (streq(slice, "-.slice")) - subslice = strappend(name, ".slice"); - else { - char *e; - - assert_se(e = endswith(slice, ".slice")); - - subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1); - if (!subslice) - return -ENOMEM; - - stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice"); - } - - *ret = subslice; - return 0; -} - -bool slice_name_is_valid(const char *name) { - const char *p, *e; - bool dash = false; - - if (!unit_name_is_valid(name, UNIT_NAME_PLAIN)) - return false; - - if (streq(name, "-.slice")) - return true; - - e = endswith(name, ".slice"); - if (!e) - return false; - - for (p = name; p < e; p++) { - - if (*p == '-') { - - /* Don't allow initial dash */ - if (p == name) - return false; - - /* Don't allow multiple dashes */ - if (dash) - return false; - - dash = true; - } else - dash = false; - } - - /* Don't allow trailing hash */ - if (dash) - return false; - - return true; -} - -static const char* const unit_type_table[_UNIT_TYPE_MAX] = { - [UNIT_SERVICE] = "service", - [UNIT_SOCKET] = "socket", - [UNIT_BUSNAME] = "busname", - [UNIT_TARGET] = "target", - [UNIT_DEVICE] = "device", - [UNIT_MOUNT] = "mount", - [UNIT_AUTOMOUNT] = "automount", - [UNIT_SWAP] = "swap", - [UNIT_TIMER] = "timer", - [UNIT_PATH] = "path", - [UNIT_SLICE] = "slice", - [UNIT_SCOPE] = "scope", -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType); - -static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = { - [UNIT_STUB] = "stub", - [UNIT_LOADED] = "loaded", - [UNIT_NOT_FOUND] = "not-found", - [UNIT_ERROR] = "error", - [UNIT_MERGED] = "merged", - [UNIT_MASKED] = "masked" -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState); - -static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = { - [UNIT_ACTIVE] = "active", - [UNIT_RELOADING] = "reloading", - [UNIT_INACTIVE] = "inactive", - [UNIT_FAILED] = "failed", - [UNIT_ACTIVATING] = "activating", - [UNIT_DEACTIVATING] = "deactivating" -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState); - -static const char* const automount_state_table[_AUTOMOUNT_STATE_MAX] = { - [AUTOMOUNT_DEAD] = "dead", - [AUTOMOUNT_WAITING] = "waiting", - [AUTOMOUNT_RUNNING] = "running", - [AUTOMOUNT_FAILED] = "failed" -}; - -DEFINE_STRING_TABLE_LOOKUP(automount_state, AutomountState); - -static const char* const busname_state_table[_BUSNAME_STATE_MAX] = { - [BUSNAME_DEAD] = "dead", - [BUSNAME_MAKING] = "making", - [BUSNAME_REGISTERED] = "registered", - [BUSNAME_LISTENING] = "listening", - [BUSNAME_RUNNING] = "running", - [BUSNAME_SIGTERM] = "sigterm", - [BUSNAME_SIGKILL] = "sigkill", - [BUSNAME_FAILED] = "failed", -}; - -DEFINE_STRING_TABLE_LOOKUP(busname_state, BusNameState); - -static const char* const device_state_table[_DEVICE_STATE_MAX] = { - [DEVICE_DEAD] = "dead", - [DEVICE_TENTATIVE] = "tentative", - [DEVICE_PLUGGED] = "plugged", -}; - -DEFINE_STRING_TABLE_LOOKUP(device_state, DeviceState); - -static const char* const mount_state_table[_MOUNT_STATE_MAX] = { - [MOUNT_DEAD] = "dead", - [MOUNT_MOUNTING] = "mounting", - [MOUNT_MOUNTING_DONE] = "mounting-done", - [MOUNT_MOUNTED] = "mounted", - [MOUNT_REMOUNTING] = "remounting", - [MOUNT_UNMOUNTING] = "unmounting", - [MOUNT_MOUNTING_SIGTERM] = "mounting-sigterm", - [MOUNT_MOUNTING_SIGKILL] = "mounting-sigkill", - [MOUNT_REMOUNTING_SIGTERM] = "remounting-sigterm", - [MOUNT_REMOUNTING_SIGKILL] = "remounting-sigkill", - [MOUNT_UNMOUNTING_SIGTERM] = "unmounting-sigterm", - [MOUNT_UNMOUNTING_SIGKILL] = "unmounting-sigkill", - [MOUNT_FAILED] = "failed" -}; - -DEFINE_STRING_TABLE_LOOKUP(mount_state, MountState); - -static const char* const path_state_table[_PATH_STATE_MAX] = { - [PATH_DEAD] = "dead", - [PATH_WAITING] = "waiting", - [PATH_RUNNING] = "running", - [PATH_FAILED] = "failed" -}; - -DEFINE_STRING_TABLE_LOOKUP(path_state, PathState); - -static const char* const scope_state_table[_SCOPE_STATE_MAX] = { - [SCOPE_DEAD] = "dead", - [SCOPE_RUNNING] = "running", - [SCOPE_ABANDONED] = "abandoned", - [SCOPE_STOP_SIGTERM] = "stop-sigterm", - [SCOPE_STOP_SIGKILL] = "stop-sigkill", - [SCOPE_FAILED] = "failed", -}; - -DEFINE_STRING_TABLE_LOOKUP(scope_state, ScopeState); - -static const char* const service_state_table[_SERVICE_STATE_MAX] = { - [SERVICE_DEAD] = "dead", - [SERVICE_START_PRE] = "start-pre", - [SERVICE_START] = "start", - [SERVICE_START_POST] = "start-post", - [SERVICE_RUNNING] = "running", - [SERVICE_EXITED] = "exited", - [SERVICE_RELOAD] = "reload", - [SERVICE_STOP] = "stop", - [SERVICE_STOP_SIGABRT] = "stop-sigabrt", - [SERVICE_STOP_SIGTERM] = "stop-sigterm", - [SERVICE_STOP_SIGKILL] = "stop-sigkill", - [SERVICE_STOP_POST] = "stop-post", - [SERVICE_FINAL_SIGTERM] = "final-sigterm", - [SERVICE_FINAL_SIGKILL] = "final-sigkill", - [SERVICE_FAILED] = "failed", - [SERVICE_AUTO_RESTART] = "auto-restart", -}; - -DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState); - -static const char* const slice_state_table[_SLICE_STATE_MAX] = { - [SLICE_DEAD] = "dead", - [SLICE_ACTIVE] = "active" -}; - -DEFINE_STRING_TABLE_LOOKUP(slice_state, SliceState); - -static const char* const socket_state_table[_SOCKET_STATE_MAX] = { - [SOCKET_DEAD] = "dead", - [SOCKET_START_PRE] = "start-pre", - [SOCKET_START_CHOWN] = "start-chown", - [SOCKET_START_POST] = "start-post", - [SOCKET_LISTENING] = "listening", - [SOCKET_RUNNING] = "running", - [SOCKET_STOP_PRE] = "stop-pre", - [SOCKET_STOP_PRE_SIGTERM] = "stop-pre-sigterm", - [SOCKET_STOP_PRE_SIGKILL] = "stop-pre-sigkill", - [SOCKET_STOP_POST] = "stop-post", - [SOCKET_FINAL_SIGTERM] = "final-sigterm", - [SOCKET_FINAL_SIGKILL] = "final-sigkill", - [SOCKET_FAILED] = "failed" -}; - -DEFINE_STRING_TABLE_LOOKUP(socket_state, SocketState); - -static const char* const swap_state_table[_SWAP_STATE_MAX] = { - [SWAP_DEAD] = "dead", - [SWAP_ACTIVATING] = "activating", - [SWAP_ACTIVATING_DONE] = "activating-done", - [SWAP_ACTIVE] = "active", - [SWAP_DEACTIVATING] = "deactivating", - [SWAP_ACTIVATING_SIGTERM] = "activating-sigterm", - [SWAP_ACTIVATING_SIGKILL] = "activating-sigkill", - [SWAP_DEACTIVATING_SIGTERM] = "deactivating-sigterm", - [SWAP_DEACTIVATING_SIGKILL] = "deactivating-sigkill", - [SWAP_FAILED] = "failed" -}; - -DEFINE_STRING_TABLE_LOOKUP(swap_state, SwapState); - -static const char* const target_state_table[_TARGET_STATE_MAX] = { - [TARGET_DEAD] = "dead", - [TARGET_ACTIVE] = "active" -}; - -DEFINE_STRING_TABLE_LOOKUP(target_state, TargetState); - -static const char* const timer_state_table[_TIMER_STATE_MAX] = { - [TIMER_DEAD] = "dead", - [TIMER_WAITING] = "waiting", - [TIMER_RUNNING] = "running", - [TIMER_ELAPSED] = "elapsed", - [TIMER_FAILED] = "failed" -}; - -DEFINE_STRING_TABLE_LOOKUP(timer_state, TimerState); - -static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = { - [UNIT_REQUIRES] = "Requires", - [UNIT_REQUISITE] = "Requisite", - [UNIT_WANTS] = "Wants", - [UNIT_BINDS_TO] = "BindsTo", - [UNIT_PART_OF] = "PartOf", - [UNIT_REQUIRED_BY] = "RequiredBy", - [UNIT_REQUISITE_OF] = "RequisiteOf", - [UNIT_WANTED_BY] = "WantedBy", - [UNIT_BOUND_BY] = "BoundBy", - [UNIT_CONSISTS_OF] = "ConsistsOf", - [UNIT_CONFLICTS] = "Conflicts", - [UNIT_CONFLICTED_BY] = "ConflictedBy", - [UNIT_BEFORE] = "Before", - [UNIT_AFTER] = "After", - [UNIT_ON_FAILURE] = "OnFailure", - [UNIT_TRIGGERS] = "Triggers", - [UNIT_TRIGGERED_BY] = "TriggeredBy", - [UNIT_PROPAGATES_RELOAD_TO] = "PropagatesReloadTo", - [UNIT_RELOAD_PROPAGATED_FROM] = "ReloadPropagatedFrom", - [UNIT_JOINS_NAMESPACE_OF] = "JoinsNamespaceOf", - [UNIT_REFERENCES] = "References", - [UNIT_REFERENCED_BY] = "ReferencedBy", -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency); diff --git a/src/basic/unit-name.h b/src/basic/unit-name.h deleted file mode 100644 index f209a84634..0000000000 --- a/src/basic/unit-name.h +++ /dev/null @@ -1,368 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "macro.h" - -#define UNIT_NAME_MAX 256 - -typedef enum UnitType { - UNIT_SERVICE = 0, - UNIT_SOCKET, - UNIT_BUSNAME, - UNIT_TARGET, - UNIT_DEVICE, - UNIT_MOUNT, - UNIT_AUTOMOUNT, - UNIT_SWAP, - UNIT_TIMER, - UNIT_PATH, - UNIT_SLICE, - UNIT_SCOPE, - _UNIT_TYPE_MAX, - _UNIT_TYPE_INVALID = -1 -} UnitType; - -typedef enum UnitLoadState { - UNIT_STUB = 0, - UNIT_LOADED, - UNIT_NOT_FOUND, - UNIT_ERROR, - UNIT_MERGED, - UNIT_MASKED, - _UNIT_LOAD_STATE_MAX, - _UNIT_LOAD_STATE_INVALID = -1 -} UnitLoadState; - -typedef enum UnitActiveState { - UNIT_ACTIVE, - UNIT_RELOADING, - UNIT_INACTIVE, - UNIT_FAILED, - UNIT_ACTIVATING, - UNIT_DEACTIVATING, - _UNIT_ACTIVE_STATE_MAX, - _UNIT_ACTIVE_STATE_INVALID = -1 -} UnitActiveState; - -typedef enum AutomountState { - AUTOMOUNT_DEAD, - AUTOMOUNT_WAITING, - AUTOMOUNT_RUNNING, - AUTOMOUNT_FAILED, - _AUTOMOUNT_STATE_MAX, - _AUTOMOUNT_STATE_INVALID = -1 -} AutomountState; - -typedef enum BusNameState { - BUSNAME_DEAD, - BUSNAME_MAKING, - BUSNAME_REGISTERED, - BUSNAME_LISTENING, - BUSNAME_RUNNING, - BUSNAME_SIGTERM, - BUSNAME_SIGKILL, - BUSNAME_FAILED, - _BUSNAME_STATE_MAX, - _BUSNAME_STATE_INVALID = -1 -} BusNameState; - -/* We simply watch devices, we cannot plug/unplug them. That - * simplifies the state engine greatly */ -typedef enum DeviceState { - DEVICE_DEAD, - DEVICE_TENTATIVE, /* mounted or swapped, but not (yet) announced by udev */ - DEVICE_PLUGGED, /* announced by udev */ - _DEVICE_STATE_MAX, - _DEVICE_STATE_INVALID = -1 -} DeviceState; - -typedef enum MountState { - MOUNT_DEAD, - MOUNT_MOUNTING, /* /usr/bin/mount is running, but the mount is not done yet. */ - MOUNT_MOUNTING_DONE, /* /usr/bin/mount is running, and the mount is done. */ - MOUNT_MOUNTED, - MOUNT_REMOUNTING, - MOUNT_UNMOUNTING, - MOUNT_MOUNTING_SIGTERM, - MOUNT_MOUNTING_SIGKILL, - MOUNT_REMOUNTING_SIGTERM, - MOUNT_REMOUNTING_SIGKILL, - MOUNT_UNMOUNTING_SIGTERM, - MOUNT_UNMOUNTING_SIGKILL, - MOUNT_FAILED, - _MOUNT_STATE_MAX, - _MOUNT_STATE_INVALID = -1 -} MountState; - -typedef enum PathState { - PATH_DEAD, - PATH_WAITING, - PATH_RUNNING, - PATH_FAILED, - _PATH_STATE_MAX, - _PATH_STATE_INVALID = -1 -} PathState; - -typedef enum ScopeState { - SCOPE_DEAD, - SCOPE_RUNNING, - SCOPE_ABANDONED, - SCOPE_STOP_SIGTERM, - SCOPE_STOP_SIGKILL, - SCOPE_FAILED, - _SCOPE_STATE_MAX, - _SCOPE_STATE_INVALID = -1 -} ScopeState; - -typedef enum ServiceState { - SERVICE_DEAD, - SERVICE_START_PRE, - SERVICE_START, - SERVICE_START_POST, - SERVICE_RUNNING, - SERVICE_EXITED, /* Nothing is running anymore, but RemainAfterExit is true hence this is OK */ - SERVICE_RELOAD, - SERVICE_STOP, /* No STOP_PRE state, instead just register multiple STOP executables */ - SERVICE_STOP_SIGABRT, /* Watchdog timeout */ - SERVICE_STOP_SIGTERM, - SERVICE_STOP_SIGKILL, - SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, /* In case the STOP_POST executable hangs, we shoot that down, too */ - SERVICE_FINAL_SIGKILL, - SERVICE_FAILED, - SERVICE_AUTO_RESTART, - _SERVICE_STATE_MAX, - _SERVICE_STATE_INVALID = -1 -} ServiceState; - -typedef enum SliceState { - SLICE_DEAD, - SLICE_ACTIVE, - _SLICE_STATE_MAX, - _SLICE_STATE_INVALID = -1 -} SliceState; - -typedef enum SocketState { - SOCKET_DEAD, - SOCKET_START_PRE, - SOCKET_START_CHOWN, - SOCKET_START_POST, - SOCKET_LISTENING, - SOCKET_RUNNING, - SOCKET_STOP_PRE, - SOCKET_STOP_PRE_SIGTERM, - SOCKET_STOP_PRE_SIGKILL, - SOCKET_STOP_POST, - SOCKET_FINAL_SIGTERM, - SOCKET_FINAL_SIGKILL, - SOCKET_FAILED, - _SOCKET_STATE_MAX, - _SOCKET_STATE_INVALID = -1 -} SocketState; - -typedef enum SwapState { - SWAP_DEAD, - SWAP_ACTIVATING, /* /sbin/swapon is running, but the swap not yet enabled. */ - SWAP_ACTIVATING_DONE, /* /sbin/swapon is running, and the swap is done. */ - SWAP_ACTIVE, - SWAP_DEACTIVATING, - SWAP_ACTIVATING_SIGTERM, - SWAP_ACTIVATING_SIGKILL, - SWAP_DEACTIVATING_SIGTERM, - SWAP_DEACTIVATING_SIGKILL, - SWAP_FAILED, - _SWAP_STATE_MAX, - _SWAP_STATE_INVALID = -1 -} SwapState; - - -typedef enum TargetState { - TARGET_DEAD, - TARGET_ACTIVE, - _TARGET_STATE_MAX, - _TARGET_STATE_INVALID = -1 -} TargetState; - -typedef enum TimerState { - TIMER_DEAD, - TIMER_WAITING, - TIMER_RUNNING, - TIMER_ELAPSED, - TIMER_FAILED, - _TIMER_STATE_MAX, - _TIMER_STATE_INVALID = -1 -} TimerState; - -typedef enum UnitDependency { - /* Positive dependencies */ - UNIT_REQUIRES, - UNIT_REQUISITE, - UNIT_WANTS, - UNIT_BINDS_TO, - UNIT_PART_OF, - - /* Inverse of the above */ - UNIT_REQUIRED_BY, /* inverse of 'requires' is 'required_by' */ - UNIT_REQUISITE_OF, /* inverse of 'requisite' is 'requisite_of' */ - UNIT_WANTED_BY, /* inverse of 'wants' */ - UNIT_BOUND_BY, /* inverse of 'binds_to' */ - UNIT_CONSISTS_OF, /* inverse of 'part_of' */ - - /* Negative dependencies */ - UNIT_CONFLICTS, /* inverse of 'conflicts' is 'conflicted_by' */ - UNIT_CONFLICTED_BY, - - /* Order */ - UNIT_BEFORE, /* inverse of 'before' is 'after' and vice versa */ - UNIT_AFTER, - - /* On Failure */ - UNIT_ON_FAILURE, - - /* Triggers (i.e. a socket triggers a service) */ - UNIT_TRIGGERS, - UNIT_TRIGGERED_BY, - - /* Propagate reloads */ - UNIT_PROPAGATES_RELOAD_TO, - UNIT_RELOAD_PROPAGATED_FROM, - - /* Joins namespace of */ - UNIT_JOINS_NAMESPACE_OF, - - /* Reference information for GC logic */ - UNIT_REFERENCES, /* Inverse of 'references' is 'referenced_by' */ - UNIT_REFERENCED_BY, - - _UNIT_DEPENDENCY_MAX, - _UNIT_DEPENDENCY_INVALID = -1 -} UnitDependency; - -typedef enum UnitNameFlags { - UNIT_NAME_PLAIN = 1, /* Allow foo.service */ - UNIT_NAME_INSTANCE = 2, /* Allow foo@bar.service */ - UNIT_NAME_TEMPLATE = 4, /* Allow foo@.service */ - UNIT_NAME_ANY = UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE, -} UnitNameFlags; - -bool unit_name_is_valid(const char *n, UnitNameFlags flags) _pure_; -bool unit_prefix_is_valid(const char *p) _pure_; -bool unit_instance_is_valid(const char *i) _pure_; -bool unit_suffix_is_valid(const char *s) _pure_; - -static inline int unit_prefix_and_instance_is_valid(const char *p) { - /* For prefix+instance and instance the same rules apply */ - return unit_instance_is_valid(p); -} - -int unit_name_to_prefix(const char *n, char **prefix); -int unit_name_to_instance(const char *n, char **instance); -int unit_name_to_prefix_and_instance(const char *n, char **ret); - -UnitType unit_name_to_type(const char *n) _pure_; - -int unit_name_change_suffix(const char *n, const char *suffix, char **ret); - -int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret); - -char *unit_name_escape(const char *f); -int unit_name_unescape(const char *f, char **ret); -int unit_name_path_escape(const char *f, char **ret); -int unit_name_path_unescape(const char *f, char **ret); - -int unit_name_replace_instance(const char *f, const char *i, char **ret); - -int unit_name_template(const char *f, char **ret); - -int unit_name_from_path(const char *path, const char *suffix, char **ret); -int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret); -int unit_name_to_path(const char *name, char **ret); - -char *unit_dbus_path_from_name(const char *name); -int unit_name_from_dbus_path(const char *path, char **name); - -const char* unit_dbus_interface_from_type(UnitType t); -const char *unit_dbus_interface_from_name(const char *name); - -typedef enum UnitNameMangle { - UNIT_NAME_NOGLOB, - UNIT_NAME_GLOB, -} UnitNameMangle; - -int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, const char *suffix, char **ret); - -static inline int unit_name_mangle(const char *name, UnitNameMangle allow_globs, char **ret) { - return unit_name_mangle_with_suffix(name, allow_globs, ".service", ret); -} - -int slice_build_parent_slice(const char *slice, char **ret); -int slice_build_subslice(const char *slice, const char*name, char **subslice); -bool slice_name_is_valid(const char *name); - -const char *unit_type_to_string(UnitType i) _const_; -UnitType unit_type_from_string(const char *s) _pure_; - -const char *unit_load_state_to_string(UnitLoadState i) _const_; -UnitLoadState unit_load_state_from_string(const char *s) _pure_; - -const char *unit_active_state_to_string(UnitActiveState i) _const_; -UnitActiveState unit_active_state_from_string(const char *s) _pure_; - -const char* automount_state_to_string(AutomountState i) _const_; -AutomountState automount_state_from_string(const char *s) _pure_; - -const char* busname_state_to_string(BusNameState i) _const_; -BusNameState busname_state_from_string(const char *s) _pure_; - -const char* device_state_to_string(DeviceState i) _const_; -DeviceState device_state_from_string(const char *s) _pure_; - -const char* mount_state_to_string(MountState i) _const_; -MountState mount_state_from_string(const char *s) _pure_; - -const char* path_state_to_string(PathState i) _const_; -PathState path_state_from_string(const char *s) _pure_; - -const char* scope_state_to_string(ScopeState i) _const_; -ScopeState scope_state_from_string(const char *s) _pure_; - -const char* service_state_to_string(ServiceState i) _const_; -ServiceState service_state_from_string(const char *s) _pure_; - -const char* slice_state_to_string(SliceState i) _const_; -SliceState slice_state_from_string(const char *s) _pure_; - -const char* socket_state_to_string(SocketState i) _const_; -SocketState socket_state_from_string(const char *s) _pure_; - -const char* swap_state_to_string(SwapState i) _const_; -SwapState swap_state_from_string(const char *s) _pure_; - -const char* target_state_to_string(TargetState i) _const_; -TargetState target_state_from_string(const char *s) _pure_; - -const char *timer_state_to_string(TimerState i) _const_; -TimerState timer_state_from_string(const char *s) _pure_; - -const char *unit_dependency_to_string(UnitDependency i) _const_; -UnitDependency unit_dependency_from_string(const char *s) _pure_; diff --git a/src/basic/user-util.c b/src/basic/user-util.c deleted file mode 100644 index f65ca3edaa..0000000000 --- a/src/basic/user-util.c +++ /dev/null @@ -1,481 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "missing.h" -#include "alloc-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "macro.h" -#include "parse-util.h" -#include "path-util.h" -#include "string-util.h" -#include "user-util.h" - -bool uid_is_valid(uid_t uid) { - - /* Some libc APIs use UID_INVALID as special placeholder */ - if (uid == (uid_t) UINT32_C(0xFFFFFFFF)) - return false; - - /* A long time ago UIDs where 16bit, hence explicitly avoid the 16bit -1 too */ - if (uid == (uid_t) UINT32_C(0xFFFF)) - return false; - - return true; -} - -int parse_uid(const char *s, uid_t *ret) { - uint32_t uid = 0; - int r; - - assert(s); - - assert_cc(sizeof(uid_t) == sizeof(uint32_t)); - r = safe_atou32(s, &uid); - if (r < 0) - return r; - - if (!uid_is_valid(uid)) - return -ENXIO; /* we return ENXIO instead of EINVAL - * here, to make it easy to distuingish - * invalid numeric uids from invalid - * strings. */ - - if (ret) - *ret = uid; - - return 0; -} - -char* getlogname_malloc(void) { - uid_t uid; - struct stat st; - - if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0) - uid = st.st_uid; - else - uid = getuid(); - - return uid_to_name(uid); -} - -char *getusername_malloc(void) { - const char *e; - - e = getenv("USER"); - if (e) - return strdup(e); - - return uid_to_name(getuid()); -} - -int get_user_creds( - const char **username, - uid_t *uid, gid_t *gid, - const char **home, - const char **shell) { - - struct passwd *p; - uid_t u; - - assert(username); - assert(*username); - - /* We enforce some special rules for uid=0: in order to avoid - * NSS lookups for root we hardcode its data. */ - - if (streq(*username, "root") || streq(*username, "0")) { - *username = "root"; - - if (uid) - *uid = 0; - - if (gid) - *gid = 0; - - if (home) - *home = "/root"; - - if (shell) - *shell = "/bin/sh"; - - return 0; - } - - if (parse_uid(*username, &u) >= 0) { - errno = 0; - p = getpwuid(u); - - /* If there are multiple users with the same id, make - * sure to leave $USER to the configured value instead - * of the first occurrence in the database. However if - * the uid was configured by a numeric uid, then let's - * pick the real username from /etc/passwd. */ - if (p) - *username = p->pw_name; - } else { - errno = 0; - p = getpwnam(*username); - } - - if (!p) - return errno > 0 ? -errno : -ESRCH; - - if (uid) { - if (!uid_is_valid(p->pw_uid)) - return -EBADMSG; - - *uid = p->pw_uid; - } - - if (gid) { - if (!gid_is_valid(p->pw_gid)) - return -EBADMSG; - - *gid = p->pw_gid; - } - - if (home) - *home = p->pw_dir; - - if (shell) - *shell = p->pw_shell; - - return 0; -} - -int get_group_creds(const char **groupname, gid_t *gid) { - struct group *g; - gid_t id; - - assert(groupname); - - /* We enforce some special rules for gid=0: in order to avoid - * NSS lookups for root we hardcode its data. */ - - if (streq(*groupname, "root") || streq(*groupname, "0")) { - *groupname = "root"; - - if (gid) - *gid = 0; - - return 0; - } - - if (parse_gid(*groupname, &id) >= 0) { - errno = 0; - g = getgrgid(id); - - if (g) - *groupname = g->gr_name; - } else { - errno = 0; - g = getgrnam(*groupname); - } - - if (!g) - return errno > 0 ? -errno : -ESRCH; - - if (gid) { - if (!gid_is_valid(g->gr_gid)) - return -EBADMSG; - - *gid = g->gr_gid; - } - - return 0; -} - -char* uid_to_name(uid_t uid) { - char *ret; - int r; - - /* Shortcut things to avoid NSS lookups */ - if (uid == 0) - return strdup("root"); - - if (uid_is_valid(uid)) { - long bufsize; - - bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); - if (bufsize <= 0) - bufsize = 4096; - - for (;;) { - struct passwd pwbuf, *pw = NULL; - _cleanup_free_ char *buf = NULL; - - buf = malloc(bufsize); - if (!buf) - return NULL; - - r = getpwuid_r(uid, &pwbuf, buf, (size_t) bufsize, &pw); - if (r == 0 && pw) - return strdup(pw->pw_name); - if (r != ERANGE) - break; - - bufsize *= 2; - } - } - - if (asprintf(&ret, UID_FMT, uid) < 0) - return NULL; - - return ret; -} - -char* gid_to_name(gid_t gid) { - char *ret; - int r; - - if (gid == 0) - return strdup("root"); - - if (gid_is_valid(gid)) { - long bufsize; - - bufsize = sysconf(_SC_GETGR_R_SIZE_MAX); - if (bufsize <= 0) - bufsize = 4096; - - for (;;) { - struct group grbuf, *gr = NULL; - _cleanup_free_ char *buf = NULL; - - buf = malloc(bufsize); - if (!buf) - return NULL; - - r = getgrgid_r(gid, &grbuf, buf, (size_t) bufsize, &gr); - if (r == 0 && gr) - return strdup(gr->gr_name); - if (r != ERANGE) - break; - - bufsize *= 2; - } - } - - if (asprintf(&ret, GID_FMT, gid) < 0) - return NULL; - - return ret; -} - -int in_gid(gid_t gid) { - gid_t *gids; - int ngroups_max, r, i; - - if (getgid() == gid) - return 1; - - if (getegid() == gid) - return 1; - - if (!gid_is_valid(gid)) - return -EINVAL; - - ngroups_max = sysconf(_SC_NGROUPS_MAX); - assert(ngroups_max > 0); - - gids = alloca(sizeof(gid_t) * ngroups_max); - - r = getgroups(ngroups_max, gids); - if (r < 0) - return -errno; - - for (i = 0; i < r; i++) - if (gids[i] == gid) - return 1; - - return 0; -} - -int in_group(const char *name) { - int r; - gid_t gid; - - r = get_group_creds(&name, &gid); - if (r < 0) - return r; - - return in_gid(gid); -} - -int get_home_dir(char **_h) { - struct passwd *p; - const char *e; - char *h; - uid_t u; - - assert(_h); - - /* Take the user specified one */ - e = secure_getenv("HOME"); - if (e && path_is_absolute(e)) { - h = strdup(e); - if (!h) - return -ENOMEM; - - *_h = h; - return 0; - } - - /* Hardcode home directory for root to avoid NSS */ - u = getuid(); - if (u == 0) { - h = strdup("/root"); - if (!h) - return -ENOMEM; - - *_h = h; - return 0; - } - - /* Check the database... */ - errno = 0; - p = getpwuid(u); - if (!p) - return errno > 0 ? -errno : -ESRCH; - - if (!path_is_absolute(p->pw_dir)) - return -EINVAL; - - h = strdup(p->pw_dir); - if (!h) - return -ENOMEM; - - *_h = h; - return 0; -} - -int get_shell(char **_s) { - struct passwd *p; - const char *e; - char *s; - uid_t u; - - assert(_s); - - /* Take the user specified one */ - e = getenv("SHELL"); - if (e) { - s = strdup(e); - if (!s) - return -ENOMEM; - - *_s = s; - return 0; - } - - /* Hardcode home directory for root to avoid NSS */ - u = getuid(); - if (u == 0) { - s = strdup("/bin/sh"); - if (!s) - return -ENOMEM; - - *_s = s; - return 0; - } - - /* Check the database... */ - errno = 0; - p = getpwuid(u); - if (!p) - return errno > 0 ? -errno : -ESRCH; - - if (!path_is_absolute(p->pw_shell)) - return -EINVAL; - - s = strdup(p->pw_shell); - if (!s) - return -ENOMEM; - - *_s = s; - return 0; -} - -int reset_uid_gid(void) { - - if (setgroups(0, NULL) < 0) - return -errno; - - if (setresgid(0, 0, 0) < 0) - return -errno; - - if (setresuid(0, 0, 0) < 0) - return -errno; - - return 0; -} - -int take_etc_passwd_lock(const char *root) { - - struct flock flock = { - .l_type = F_WRLCK, - .l_whence = SEEK_SET, - .l_start = 0, - .l_len = 0, - }; - - const char *path; - int fd, r; - - /* This is roughly the same as lckpwdf(), but not as awful. We - * don't want to use alarm() and signals, hence we implement - * our own trivial version of this. - * - * Note that shadow-utils also takes per-database locks in - * addition to lckpwdf(). However, we don't given that they - * are redundant as they they invoke lckpwdf() first and keep - * it during everything they do. The per-database locks are - * awfully racy, and thus we just won't do them. */ - - if (root) - path = prefix_roota(root, "/etc/.pwd.lock"); - else - path = "/etc/.pwd.lock"; - - fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600); - if (fd < 0) - return -errno; - - r = fcntl(fd, F_SETLKW, &flock); - if (r < 0) { - safe_close(fd); - return -errno; - } - - return fd; -} diff --git a/src/basic/user-util.h b/src/basic/user-util.h deleted file mode 100644 index 8026eca3f4..0000000000 --- a/src/basic/user-util.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include - -bool uid_is_valid(uid_t uid); - -static inline bool gid_is_valid(gid_t gid) { - return uid_is_valid((uid_t) gid); -} - -int parse_uid(const char *s, uid_t* ret_uid); - -static inline int parse_gid(const char *s, gid_t *ret_gid) { - return parse_uid(s, (uid_t*) ret_gid); -} - -char* getlogname_malloc(void); -char* getusername_malloc(void); - -int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home, const char **shell); -int get_group_creds(const char **groupname, gid_t *gid); - -char* uid_to_name(uid_t uid); -char* gid_to_name(gid_t gid); - -int in_gid(gid_t gid); -int in_group(const char *name); - -int get_home_dir(char **ret); -int get_shell(char **_ret); - -int reset_uid_gid(void); - -int take_etc_passwd_lock(const char *root); - -#define UID_INVALID ((uid_t) -1) -#define GID_INVALID ((gid_t) -1) - -/* The following macros add 1 when converting things, since UID 0 is a - * valid UID, while the pointer NULL is special */ -#define PTR_TO_UID(p) ((uid_t) (((uintptr_t) (p))-1)) -#define UID_TO_PTR(u) ((void*) (((uintptr_t) (u))+1)) - -#define PTR_TO_GID(p) ((gid_t) (((uintptr_t) (p))-1)) -#define GID_TO_PTR(u) ((void*) (((uintptr_t) (u))+1)) - -static inline bool userns_supported(void) { - return access("/proc/self/uid_map", F_OK) >= 0; -} diff --git a/src/basic/utf8.c b/src/basic/utf8.c deleted file mode 100644 index 6eae2b983d..0000000000 --- a/src/basic/utf8.c +++ /dev/null @@ -1,409 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2011 Kay Sievers - Copyright 2012 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 . -***/ - -/* Parts of this file are based on the GLIB utf8 validation functions. The - * original license text follows. */ - -/* gutf8.c - Operations on UTF-8 strings. - * - * Copyright (C) 1999 Tom Tromey - * Copyright (C) 2000 Red Hat, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include -#include -#include -#include - -#include "alloc-util.h" -#include "hexdecoct.h" -#include "macro.h" -#include "utf8.h" - -bool unichar_is_valid(char32_t ch) { - - if (ch >= 0x110000) /* End of unicode space */ - return false; - if ((ch & 0xFFFFF800) == 0xD800) /* Reserved area for UTF-16 */ - return false; - if ((ch >= 0xFDD0) && (ch <= 0xFDEF)) /* Reserved */ - return false; - if ((ch & 0xFFFE) == 0xFFFE) /* BOM (Byte Order Mark) */ - return false; - - return true; -} - -static bool unichar_is_control(char32_t ch) { - - /* - 0 to ' '-1 is the C0 range. - DEL=0x7F, and DEL+1 to 0x9F is C1 range. - '\t' is in C0 range, but more or less harmless and commonly used. - */ - - return (ch < ' ' && ch != '\t' && ch != '\n') || - (0x7F <= ch && ch <= 0x9F); -} - -/* count of characters used to encode one unicode char */ -static int utf8_encoded_expected_len(const char *str) { - unsigned char c; - - assert(str); - - c = (unsigned char) str[0]; - if (c < 0x80) - return 1; - if ((c & 0xe0) == 0xc0) - return 2; - if ((c & 0xf0) == 0xe0) - return 3; - if ((c & 0xf8) == 0xf0) - return 4; - if ((c & 0xfc) == 0xf8) - return 5; - if ((c & 0xfe) == 0xfc) - return 6; - - return 0; -} - -/* decode one unicode char */ -int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar) { - char32_t unichar; - int len, i; - - assert(str); - - len = utf8_encoded_expected_len(str); - - switch (len) { - case 1: - *ret_unichar = (char32_t)str[0]; - return 0; - case 2: - unichar = str[0] & 0x1f; - break; - case 3: - unichar = (char32_t)str[0] & 0x0f; - break; - case 4: - unichar = (char32_t)str[0] & 0x07; - break; - case 5: - unichar = (char32_t)str[0] & 0x03; - break; - case 6: - unichar = (char32_t)str[0] & 0x01; - break; - default: - return -EINVAL; - } - - for (i = 1; i < len; i++) { - if (((char32_t)str[i] & 0xc0) != 0x80) - return -EINVAL; - unichar <<= 6; - unichar |= (char32_t)str[i] & 0x3f; - } - - *ret_unichar = unichar; - - return 0; -} - -bool utf8_is_printable_newline(const char* str, size_t length, bool newline) { - const char *p; - - assert(str); - - for (p = str; length;) { - int encoded_len, r; - char32_t val; - - encoded_len = utf8_encoded_valid_unichar(p); - if (encoded_len < 0 || - (size_t) encoded_len > length) - return false; - - r = utf8_encoded_to_unichar(p, &val); - if (r < 0 || - unichar_is_control(val) || - (!newline && val == '\n')) - return false; - - length -= encoded_len; - p += encoded_len; - } - - return true; -} - -const char *utf8_is_valid(const char *str) { - const uint8_t *p; - - assert(str); - - for (p = (const uint8_t*) str; *p; ) { - int len; - - len = utf8_encoded_valid_unichar((const char *)p); - if (len < 0) - return NULL; - - p += len; - } - - return str; -} - -char *utf8_escape_invalid(const char *str) { - char *p, *s; - - assert(str); - - p = s = malloc(strlen(str) * 4 + 1); - if (!p) - return NULL; - - while (*str) { - int len; - - len = utf8_encoded_valid_unichar(str); - if (len > 0) { - s = mempcpy(s, str, len); - str += len; - } else { - s = stpcpy(s, UTF8_REPLACEMENT_CHARACTER); - str += 1; - } - } - - *s = '\0'; - - return p; -} - -char *utf8_escape_non_printable(const char *str) { - char *p, *s; - - assert(str); - - p = s = malloc(strlen(str) * 4 + 1); - if (!p) - return NULL; - - while (*str) { - int len; - - len = utf8_encoded_valid_unichar(str); - if (len > 0) { - if (utf8_is_printable(str, len)) { - s = mempcpy(s, str, len); - str += len; - } else { - while (len > 0) { - *(s++) = '\\'; - *(s++) = 'x'; - *(s++) = hexchar((int) *str >> 4); - *(s++) = hexchar((int) *str); - - str += 1; - len--; - } - } - } else { - s = stpcpy(s, UTF8_REPLACEMENT_CHARACTER); - str += 1; - } - } - - *s = '\0'; - - return p; -} - -char *ascii_is_valid(const char *str) { - const char *p; - - assert(str); - - for (p = str; *p; p++) - if ((unsigned char) *p >= 128) - return NULL; - - return (char*) str; -} - -/** - * utf8_encode_unichar() - Encode single UCS-4 character as UTF-8 - * @out_utf8: output buffer of at least 4 bytes or NULL - * @g: UCS-4 character to encode - * - * This encodes a single UCS-4 character as UTF-8 and writes it into @out_utf8. - * The length of the character is returned. It is not zero-terminated! If the - * output buffer is NULL, only the length is returned. - * - * Returns: The length in bytes that the UTF-8 representation does or would - * occupy. - */ -size_t utf8_encode_unichar(char *out_utf8, char32_t g) { - - if (g < (1 << 7)) { - if (out_utf8) - out_utf8[0] = g & 0x7f; - return 1; - } else if (g < (1 << 11)) { - if (out_utf8) { - out_utf8[0] = 0xc0 | ((g >> 6) & 0x1f); - out_utf8[1] = 0x80 | (g & 0x3f); - } - return 2; - } else if (g < (1 << 16)) { - if (out_utf8) { - out_utf8[0] = 0xe0 | ((g >> 12) & 0x0f); - out_utf8[1] = 0x80 | ((g >> 6) & 0x3f); - out_utf8[2] = 0x80 | (g & 0x3f); - } - return 3; - } else if (g < (1 << 21)) { - if (out_utf8) { - out_utf8[0] = 0xf0 | ((g >> 18) & 0x07); - out_utf8[1] = 0x80 | ((g >> 12) & 0x3f); - out_utf8[2] = 0x80 | ((g >> 6) & 0x3f); - out_utf8[3] = 0x80 | (g & 0x3f); - } - return 4; - } - - return 0; -} - -char *utf16_to_utf8(const void *s, size_t length) { - const uint8_t *f; - char *r, *t; - - r = new(char, (length * 4 + 1) / 2 + 1); - if (!r) - return NULL; - - f = s; - t = r; - - while (f < (const uint8_t*) s + length) { - char16_t w1, w2; - - /* see RFC 2781 section 2.2 */ - - w1 = f[1] << 8 | f[0]; - f += 2; - - if (!utf16_is_surrogate(w1)) { - t += utf8_encode_unichar(t, w1); - - continue; - } - - if (utf16_is_trailing_surrogate(w1)) - continue; - else if (f >= (const uint8_t*) s + length) - break; - - w2 = f[1] << 8 | f[0]; - f += 2; - - if (!utf16_is_trailing_surrogate(w2)) { - f -= 2; - continue; - } - - t += utf8_encode_unichar(t, utf16_surrogate_pair_to_unichar(w1, w2)); - } - - *t = 0; - return r; -} - -/* expected size used to encode one unicode char */ -static int utf8_unichar_to_encoded_len(char32_t unichar) { - - if (unichar < 0x80) - return 1; - if (unichar < 0x800) - return 2; - if (unichar < 0x10000) - return 3; - if (unichar < 0x200000) - return 4; - if (unichar < 0x4000000) - return 5; - - return 6; -} - -/* validate one encoded unicode char and return its length */ -int utf8_encoded_valid_unichar(const char *str) { - int len, i, r; - char32_t unichar; - - assert(str); - - len = utf8_encoded_expected_len(str); - if (len == 0) - return -EINVAL; - - /* ascii is valid */ - if (len == 1) - return 1; - - /* check if expected encoded chars are available */ - for (i = 0; i < len; i++) - if ((str[i] & 0x80) != 0x80) - return -EINVAL; - - r = utf8_encoded_to_unichar(str, &unichar); - if (r < 0) - return r; - - /* check if encoded length matches encoded value */ - if (utf8_unichar_to_encoded_len(unichar) != len) - return -EINVAL; - - /* check if value has valid range */ - if (!unichar_is_valid(unichar)) - return -EINVAL; - - return len; -} diff --git a/src/basic/utf8.h b/src/basic/utf8.h deleted file mode 100644 index f9b9c9468b..0000000000 --- a/src/basic/utf8.h +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 -#include -#include - -#include "macro.h" -#include "missing.h" - -#define UTF8_REPLACEMENT_CHARACTER "\xef\xbf\xbd" -#define UTF8_BYTE_ORDER_MARK "\xef\xbb\xbf" - -bool unichar_is_valid(char32_t c); - -const char *utf8_is_valid(const char *s) _pure_; -char *ascii_is_valid(const char *s) _pure_; - -bool utf8_is_printable_newline(const char* str, size_t length, bool newline) _pure_; -#define utf8_is_printable(str, length) utf8_is_printable_newline(str, length, true) - -char *utf8_escape_invalid(const char *s); -char *utf8_escape_non_printable(const char *str); - -size_t utf8_encode_unichar(char *out_utf8, char32_t g); -char *utf16_to_utf8(const void *s, size_t length); - -int utf8_encoded_valid_unichar(const char *str); -int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar); - -static inline bool utf16_is_surrogate(char16_t c) { - return (0xd800 <= c && c <= 0xdfff); -} - -static inline bool utf16_is_trailing_surrogate(char16_t c) { - return (0xdc00 <= c && c <= 0xdfff); -} - -static inline char32_t utf16_surrogate_pair_to_unichar(char16_t lead, char16_t trail) { - return ((lead - 0xd800) << 10) + (trail - 0xdc00) + 0x10000; -} diff --git a/src/basic/util.c b/src/basic/util.c deleted file mode 100644 index 756c663be4..0000000000 --- a/src/basic/util.c +++ /dev/null @@ -1,808 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "build.h" -#include "def.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "hashmap.h" -#include "hostname-util.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "set.h" -#include "signal-util.h" -#include "stat-util.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" -#include "umask-util.h" -#include "user-util.h" -#include "util.h" - -/* Put this test here for a lack of better place */ -assert_cc(EAGAIN == EWOULDBLOCK); - -int saved_argc = 0; -char **saved_argv = NULL; - -size_t page_size(void) { - static thread_local size_t pgsz = 0; - long r; - - if (_likely_(pgsz > 0)) - return pgsz; - - r = sysconf(_SC_PAGESIZE); - assert(r > 0); - - pgsz = (size_t) r; - return pgsz; -} - -static int do_execute(char **directories, usec_t timeout, char *argv[]) { - _cleanup_hashmap_free_free_ Hashmap *pids = NULL; - _cleanup_set_free_free_ Set *seen = NULL; - char **directory; - - /* We fork this all off from a child process so that we can - * somewhat cleanly make use of SIGALRM to set a time limit */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - pids = hashmap_new(NULL); - if (!pids) - return log_oom(); - - seen = set_new(&string_hash_ops); - if (!seen) - return log_oom(); - - STRV_FOREACH(directory, directories) { - _cleanup_closedir_ DIR *d; - struct dirent *de; - - d = opendir(*directory); - if (!d) { - if (errno == ENOENT) - continue; - - return log_error_errno(errno, "Failed to open directory %s: %m", *directory); - } - - FOREACH_DIRENT(de, d, break) { - _cleanup_free_ char *path = NULL; - pid_t pid; - int r; - - if (!dirent_is_file(de)) - continue; - - if (set_contains(seen, de->d_name)) { - log_debug("%1$s/%2$s skipped (%2$s was already seen).", *directory, de->d_name); - continue; - } - - r = set_put_strdup(seen, de->d_name); - if (r < 0) - return log_oom(); - - path = strjoin(*directory, "/", de->d_name, NULL); - if (!path) - return log_oom(); - - if (null_or_empty_path(path)) { - log_debug("%s is empty (a mask).", path); - continue; - } - - pid = fork(); - if (pid < 0) { - log_error_errno(errno, "Failed to fork: %m"); - continue; - } else if (pid == 0) { - char *_argv[2]; - - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - if (!argv) { - _argv[0] = path; - _argv[1] = NULL; - argv = _argv; - } else - argv[0] = path; - - execv(path, argv); - return log_error_errno(errno, "Failed to execute %s: %m", path); - } - - log_debug("Spawned %s as " PID_FMT ".", path, pid); - - r = hashmap_put(pids, PID_TO_PTR(pid), path); - if (r < 0) - return log_oom(); - path = NULL; - } - } - - /* Abort execution of this process after the timout. We simply - * rely on SIGALRM as default action terminating the process, - * and turn on alarm(). */ - - if (timeout != USEC_INFINITY) - alarm((timeout + USEC_PER_SEC - 1) / USEC_PER_SEC); - - while (!hashmap_isempty(pids)) { - _cleanup_free_ char *path = NULL; - pid_t pid; - - pid = PTR_TO_PID(hashmap_first_key(pids)); - assert(pid > 0); - - path = hashmap_remove(pids, PID_TO_PTR(pid)); - assert(path); - - wait_for_terminate_and_warn(path, pid, true); - } - - return 0; -} - -void execute_directories(const char* const* directories, usec_t timeout, char *argv[]) { - pid_t executor_pid; - int r; - char *name; - char **dirs = (char**) directories; - - assert(!strv_isempty(dirs)); - - name = basename(dirs[0]); - assert(!isempty(name)); - - /* Executes all binaries in the directories in parallel and waits - * for them to finish. Optionally a timeout is applied. If a file - * with the same name exists in more than one directory, the - * earliest one wins. */ - - executor_pid = fork(); - if (executor_pid < 0) { - log_error_errno(errno, "Failed to fork: %m"); - return; - - } else if (executor_pid == 0) { - r = do_execute(dirs, timeout, argv); - _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); - } - - wait_for_terminate_and_warn(name, executor_pid, true); -} - -bool plymouth_running(void) { - return access("/run/plymouth/pid", F_OK) >= 0; -} - -bool display_is_local(const char *display) { - assert(display); - - return - display[0] == ':' && - display[1] >= '0' && - display[1] <= '9'; -} - -int socket_from_display(const char *display, char **path) { - size_t k; - char *f, *c; - - assert(display); - assert(path); - - if (!display_is_local(display)) - return -EINVAL; - - k = strspn(display+1, "0123456789"); - - f = new(char, strlen("/tmp/.X11-unix/X") + k + 1); - if (!f) - return -ENOMEM; - - c = stpcpy(f, "/tmp/.X11-unix/X"); - memcpy(c, display+1, k); - c[k] = 0; - - *path = f; - - return 0; -} - -int block_get_whole_disk(dev_t d, dev_t *ret) { - char *p, *s; - int r; - unsigned n, m; - - assert(ret); - - /* If it has a queue this is good enough for us */ - if (asprintf(&p, "/sys/dev/block/%u:%u/queue", major(d), minor(d)) < 0) - return -ENOMEM; - - r = access(p, F_OK); - free(p); - - if (r >= 0) { - *ret = d; - return 0; - } - - /* If it is a partition find the originating device */ - if (asprintf(&p, "/sys/dev/block/%u:%u/partition", major(d), minor(d)) < 0) - return -ENOMEM; - - r = access(p, F_OK); - free(p); - - if (r < 0) - return -ENOENT; - - /* Get parent dev_t */ - if (asprintf(&p, "/sys/dev/block/%u:%u/../dev", major(d), minor(d)) < 0) - return -ENOMEM; - - r = read_one_line_file(p, &s); - free(p); - - if (r < 0) - return r; - - r = sscanf(s, "%u:%u", &m, &n); - free(s); - - if (r != 2) - return -EINVAL; - - /* Only return this if it is really good enough for us. */ - if (asprintf(&p, "/sys/dev/block/%u:%u/queue", m, n) < 0) - return -ENOMEM; - - r = access(p, F_OK); - free(p); - - if (r >= 0) { - *ret = makedev(m, n); - return 0; - } - - return -ENOENT; -} - -bool kexec_loaded(void) { - bool loaded = false; - char *s; - - if (read_one_line_file("/sys/kernel/kexec_loaded", &s) >= 0) { - if (s[0] == '1') - loaded = true; - free(s); - } - return loaded; -} - -int prot_from_flags(int flags) { - - switch (flags & O_ACCMODE) { - - case O_RDONLY: - return PROT_READ; - - case O_WRONLY: - return PROT_WRITE; - - case O_RDWR: - return PROT_READ|PROT_WRITE; - - default: - return -EINVAL; - } -} - -int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...) { - bool stdout_is_tty, stderr_is_tty; - pid_t parent_pid, agent_pid; - sigset_t ss, saved_ss; - unsigned n, i; - va_list ap; - char **l; - - assert(pid); - assert(path); - - /* Spawns a temporary TTY agent, making sure it goes away when - * we go away */ - - parent_pid = getpid(); - - /* First we temporarily block all signals, so that the new - * child has them blocked initially. This way, we can be sure - * that SIGTERMs are not lost we might send to the agent. */ - assert_se(sigfillset(&ss) >= 0); - assert_se(sigprocmask(SIG_SETMASK, &ss, &saved_ss) >= 0); - - agent_pid = fork(); - if (agent_pid < 0) { - assert_se(sigprocmask(SIG_SETMASK, &saved_ss, NULL) >= 0); - return -errno; - } - - if (agent_pid != 0) { - assert_se(sigprocmask(SIG_SETMASK, &saved_ss, NULL) >= 0); - *pid = agent_pid; - return 0; - } - - /* In the child: - * - * Make sure the agent goes away when the parent dies */ - if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) - _exit(EXIT_FAILURE); - - /* Make sure we actually can kill the agent, if we need to, in - * case somebody invoked us from a shell script that trapped - * SIGTERM or so... */ - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - /* Check whether our parent died before we were able - * to set the death signal and unblock the signals */ - if (getppid() != parent_pid) - _exit(EXIT_SUCCESS); - - /* Don't leak fds to the agent */ - close_all_fds(except, n_except); - - stdout_is_tty = isatty(STDOUT_FILENO); - stderr_is_tty = isatty(STDERR_FILENO); - - if (!stdout_is_tty || !stderr_is_tty) { - int fd; - - /* Detach from stdout/stderr. and reopen - * /dev/tty for them. This is important to - * ensure that when systemctl is started via - * popen() or a similar call that expects to - * read EOF we actually do generate EOF and - * not delay this indefinitely by because we - * keep an unused copy of stdin around. */ - fd = open("/dev/tty", O_WRONLY); - if (fd < 0) { - log_error_errno(errno, "Failed to open /dev/tty: %m"); - _exit(EXIT_FAILURE); - } - - if (!stdout_is_tty && dup2(fd, STDOUT_FILENO) < 0) { - log_error_errno(errno, "Failed to dup2 /dev/tty: %m"); - _exit(EXIT_FAILURE); - } - - if (!stderr_is_tty && dup2(fd, STDERR_FILENO) < 0) { - log_error_errno(errno, "Failed to dup2 /dev/tty: %m"); - _exit(EXIT_FAILURE); - } - - if (fd > STDERR_FILENO) - close(fd); - } - - /* Count arguments */ - va_start(ap, path); - for (n = 0; va_arg(ap, char*); n++) - ; - va_end(ap); - - /* Allocate strv */ - l = alloca(sizeof(char *) * (n + 1)); - - /* Fill in arguments */ - va_start(ap, path); - for (i = 0; i <= n; i++) - l[i] = va_arg(ap, char*); - va_end(ap); - - execv(path, l); - _exit(EXIT_FAILURE); -} - -bool in_initrd(void) { - static int saved = -1; - struct statfs s; - - if (saved >= 0) - return saved; - - /* We make two checks here: - * - * 1. the flag file /etc/initrd-release must exist - * 2. the root file system must be a memory file system - * - * The second check is extra paranoia, since misdetecting an - * initrd can have bad bad consequences due the initrd - * emptying when transititioning to the main systemd. - */ - - saved = access("/etc/initrd-release", F_OK) >= 0 && - statfs("/", &s) >= 0 && - is_temporary_fs(&s); - - return saved; -} - -/* hey glibc, APIs with callbacks without a user pointer are so useless */ -void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, - int (*compar) (const void *, const void *, void *), void *arg) { - size_t l, u, idx; - const void *p; - int comparison; - - l = 0; - u = nmemb; - while (l < u) { - idx = (l + u) / 2; - p = (void *)(((const char *) base) + (idx * size)); - comparison = compar(key, p, arg); - if (comparison < 0) - u = idx; - else if (comparison > 0) - l = idx + 1; - else - return (void *)p; - } - return NULL; -} - -int on_ac_power(void) { - bool found_offline = false, found_online = false; - _cleanup_closedir_ DIR *d = NULL; - - d = opendir("/sys/class/power_supply"); - if (!d) - return errno == ENOENT ? true : -errno; - - for (;;) { - struct dirent *de; - _cleanup_close_ int fd = -1, device = -1; - char contents[6]; - ssize_t n; - - errno = 0; - de = readdir(d); - if (!de && errno > 0) - return -errno; - - if (!de) - break; - - if (hidden_or_backup_file(de->d_name)) - continue; - - device = openat(dirfd(d), de->d_name, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (device < 0) { - if (errno == ENOENT || errno == ENOTDIR) - continue; - - return -errno; - } - - fd = openat(device, "type", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) { - if (errno == ENOENT) - continue; - - return -errno; - } - - n = read(fd, contents, sizeof(contents)); - if (n < 0) - return -errno; - - if (n != 6 || memcmp(contents, "Mains\n", 6)) - continue; - - safe_close(fd); - fd = openat(device, "online", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) { - if (errno == ENOENT) - continue; - - return -errno; - } - - n = read(fd, contents, sizeof(contents)); - if (n < 0) - return -errno; - - if (n != 2 || contents[1] != '\n') - return -EIO; - - if (contents[0] == '1') { - found_online = true; - break; - } else if (contents[0] == '0') - found_offline = true; - else - return -EIO; - } - - return found_online || !found_offline; -} - -bool id128_is_valid(const char *s) { - size_t i, l; - - l = strlen(s); - if (l == 32) { - - /* Simple formatted 128bit hex string */ - - for (i = 0; i < l; i++) { - char c = s[i]; - - if (!(c >= '0' && c <= '9') && - !(c >= 'a' && c <= 'z') && - !(c >= 'A' && c <= 'Z')) - return false; - } - - } else if (l == 36) { - - /* Formatted UUID */ - - for (i = 0; i < l; i++) { - char c = s[i]; - - if ((i == 8 || i == 13 || i == 18 || i == 23)) { - if (c != '-') - return false; - } else { - if (!(c >= '0' && c <= '9') && - !(c >= 'a' && c <= 'z') && - !(c >= 'A' && c <= 'Z')) - return false; - } - } - - } else - return false; - - return true; -} - -int container_get_leader(const char *machine, pid_t *pid) { - _cleanup_free_ char *s = NULL, *class = NULL; - const char *p; - pid_t leader; - int r; - - assert(machine); - assert(pid); - - if (!machine_name_is_valid(machine)) - return -EINVAL; - - p = strjoina("/run/systemd/machines/", machine); - r = parse_env_file(p, NEWLINE, "LEADER", &s, "CLASS", &class, NULL); - if (r == -ENOENT) - return -EHOSTDOWN; - if (r < 0) - return r; - if (!s) - return -EIO; - - if (!streq_ptr(class, "container")) - return -EIO; - - r = parse_pid(s, &leader); - if (r < 0) - return r; - if (leader <= 1) - return -EIO; - - *pid = leader; - return 0; -} - -int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd) { - _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, netnsfd = -1, usernsfd = -1; - int rfd = -1; - - assert(pid >= 0); - - if (mntns_fd) { - const char *mntns; - - mntns = procfs_file_alloca(pid, "ns/mnt"); - mntnsfd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (mntnsfd < 0) - return -errno; - } - - if (pidns_fd) { - const char *pidns; - - pidns = procfs_file_alloca(pid, "ns/pid"); - pidnsfd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (pidnsfd < 0) - return -errno; - } - - if (netns_fd) { - const char *netns; - - netns = procfs_file_alloca(pid, "ns/net"); - netnsfd = open(netns, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (netnsfd < 0) - return -errno; - } - - if (userns_fd) { - const char *userns; - - userns = procfs_file_alloca(pid, "ns/user"); - usernsfd = open(userns, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (usernsfd < 0 && errno != ENOENT) - return -errno; - } - - if (root_fd) { - const char *root; - - root = procfs_file_alloca(pid, "root"); - rfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (rfd < 0) - return -errno; - } - - if (pidns_fd) - *pidns_fd = pidnsfd; - - if (mntns_fd) - *mntns_fd = mntnsfd; - - if (netns_fd) - *netns_fd = netnsfd; - - if (userns_fd) - *userns_fd = usernsfd; - - if (root_fd) - *root_fd = rfd; - - pidnsfd = mntnsfd = netnsfd = usernsfd = -1; - - return 0; -} - -int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd) { - if (userns_fd >= 0) { - /* Can't setns to your own userns, since then you could - * escalate from non-root to root in your own namespace, so - * check if namespaces equal before attempting to enter. */ - _cleanup_free_ char *userns_fd_path = NULL; - int r; - if (asprintf(&userns_fd_path, "/proc/self/fd/%d", userns_fd) < 0) - return -ENOMEM; - - r = files_same(userns_fd_path, "/proc/self/ns/user"); - if (r < 0) - return r; - if (r) - userns_fd = -1; - } - - if (pidns_fd >= 0) - if (setns(pidns_fd, CLONE_NEWPID) < 0) - return -errno; - - if (mntns_fd >= 0) - if (setns(mntns_fd, CLONE_NEWNS) < 0) - return -errno; - - if (netns_fd >= 0) - if (setns(netns_fd, CLONE_NEWNET) < 0) - return -errno; - - if (userns_fd >= 0) - if (setns(userns_fd, CLONE_NEWUSER) < 0) - return -errno; - - if (root_fd >= 0) { - if (fchdir(root_fd) < 0) - return -errno; - - if (chroot(".") < 0) - return -errno; - } - - return reset_uid_gid(); -} - -uint64_t physical_memory(void) { - long mem; - - /* We return this as uint64_t in case we are running as 32bit - * process on a 64bit kernel with huge amounts of memory */ - - mem = sysconf(_SC_PHYS_PAGES); - assert(mem > 0); - - return (uint64_t) mem * (uint64_t) page_size(); -} - -int update_reboot_parameter_and_warn(const char *param) { - int r; - - if (isempty(param)) { - if (unlink("/run/systemd/reboot-param") < 0) { - if (errno == ENOENT) - return 0; - - return log_warning_errno(errno, "Failed to unlink reboot parameter file: %m"); - } - - return 0; - } - - RUN_WITH_UMASK(0022) { - r = write_string_file("/run/systemd/reboot-param", param, WRITE_STRING_FILE_CREATE); - if (r < 0) - return log_warning_errno(r, "Failed to write reboot parameter file: %m"); - } - - return 0; -} - -int version(void) { - puts(PACKAGE_STRING "\n" - SYSTEMD_FEATURES); - return 0; -} diff --git a/src/basic/util.h b/src/basic/util.h deleted file mode 100644 index 1c032c15c9..0000000000 --- a/src/basic/util.h +++ /dev/null @@ -1,189 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "formats-util.h" -#include "macro.h" -#include "missing.h" -#include "time-util.h" - -size_t page_size(void) _pure_; -#define PAGE_ALIGN(l) ALIGN_TO((l), page_size()) - -static inline const char* yes_no(bool b) { - return b ? "yes" : "no"; -} - -static inline const char* true_false(bool b) { - return b ? "true" : "false"; -} - -static inline const char* one_zero(bool b) { - return b ? "1" : "0"; -} - -void execute_directories(const char* const* directories, usec_t timeout, char *argv[]); - -bool plymouth_running(void); - -bool display_is_local(const char *display) _pure_; -int socket_from_display(const char *display, char **path); - -int block_get_whole_disk(dev_t d, dev_t *ret); - -#define NULSTR_FOREACH(i, l) \ - for ((i) = (l); (i) && *(i); (i) = strchr((i), 0)+1) - -#define NULSTR_FOREACH_PAIR(i, j, l) \ - for ((i) = (l), (j) = strchr((i), 0)+1; (i) && *(i); (i) = strchr((j), 0)+1, (j) = *(i) ? strchr((i), 0)+1 : (i)) - -extern int saved_argc; -extern char **saved_argv; - -bool kexec_loaded(void); - -int prot_from_flags(int flags) _const_; - -int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...); - -bool in_initrd(void); - -void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, - int (*compar) (const void *, const void *, void *), - void *arg); - -/** - * Normal qsort requires base to be nonnull. Here were require - * that only if nmemb > 0. - */ -static inline void qsort_safe(void *base, size_t nmemb, size_t size, comparison_fn_t compar) { - if (nmemb <= 1) - return; - - assert(base); - qsort(base, nmemb, size, compar); -} - -/** - * Normal memcpy requires src to be nonnull. We do nothing if n is 0. - */ -static inline void memcpy_safe(void *dst, const void *src, size_t n) { - if (n == 0) - return; - assert(src); - memcpy(dst, src, n); -} - -int on_ac_power(void); - -#define memzero(x,l) (memset((x), 0, (l))) -#define zero(x) (memzero(&(x), sizeof(x))) - -static inline void *mempset(void *s, int c, size_t n) { - memset(s, c, n); - return (uint8_t*)s + n; -} - -static inline void _reset_errno_(int *saved_errno) { - errno = *saved_errno; -} - -#define PROTECT_ERRNO _cleanup_(_reset_errno_) __attribute__((unused)) int _saved_errno_ = errno - -static inline int negative_errno(void) { - /* This helper should be used to shut up gcc if you know 'errno' is - * negative. Instead of "return -errno;", use "return negative_errno();" - * It will suppress bogus gcc warnings in case it assumes 'errno' might - * be 0 and thus the caller's error-handling might not be triggered. */ - assert_return(errno > 0, -EINVAL); - return -errno; -} - -static inline unsigned u64log2(uint64_t n) { -#if __SIZEOF_LONG_LONG__ == 8 - return (n > 1) ? (unsigned) __builtin_clzll(n) ^ 63U : 0; -#else -#error "Wut?" -#endif -} - -static inline unsigned u32ctz(uint32_t n) { -#if __SIZEOF_INT__ == 4 - return __builtin_ctz(n); -#else -#error "Wut?" -#endif -} - -static inline unsigned log2i(int x) { - assert(x > 0); - - return __SIZEOF_INT__ * 8 - __builtin_clz(x) - 1; -} - -static inline unsigned log2u(unsigned x) { - assert(x > 0); - - return sizeof(unsigned) * 8 - __builtin_clz(x) - 1; -} - -static inline unsigned log2u_round_up(unsigned x) { - assert(x > 0); - - if (x == 1) - return 0; - - return log2u(x - 1) + 1; -} - -bool id128_is_valid(const char *s) _pure_; - -int container_get_leader(const char *machine, pid_t *pid); - -int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd); -int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd); - -uint64_t physical_memory(void); - -int update_reboot_parameter_and_warn(const char *param); - -int version(void); diff --git a/src/basic/verbs.c b/src/basic/verbs.c deleted file mode 100644 index d9cdb38d65..0000000000 --- a/src/basic/verbs.c +++ /dev/null @@ -1,101 +0,0 @@ -/*** - 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 -#include -#include - -#include "log.h" -#include "macro.h" -#include "string-util.h" -#include "verbs.h" -#include "virt.h" - -int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { - const Verb *verb; - const char *name; - unsigned i; - int left; - - assert(verbs); - assert(verbs[0].dispatch); - assert(argc >= 0); - assert(argv); - assert(argc >= optind); - - left = argc - optind; - name = argv[optind]; - - for (i = 0;; i++) { - bool found; - - /* At the end of the list? */ - if (!verbs[i].dispatch) { - if (name) - log_error("Unknown operation %s.", name); - else - log_error("Requires operation parameter."); - return -EINVAL; - } - - if (name) - found = streq(name, verbs[i].verb); - else - found = !!(verbs[i].flags & VERB_DEFAULT); - - if (found) { - verb = &verbs[i]; - break; - } - } - - assert(verb); - - if (!name) - left = 1; - - if (verb->min_args != VERB_ANY && - (unsigned) left < verb->min_args) { - log_error("Too few arguments."); - return -EINVAL; - } - - if (verb->max_args != VERB_ANY && - (unsigned) left > verb->max_args) { - log_error("Too many arguments."); - return -EINVAL; - } - - if ((verb->flags & VERB_NOCHROOT) && running_in_chroot() > 0) { - log_info("Running in chroot, ignoring request."); - return 0; - } - - if (name) - return verb->dispatch(left, argv + optind, userdata); - else { - char* fake[2] = { - (char*) verb->verb, - NULL - }; - - return verb->dispatch(1, fake, userdata); - } -} diff --git a/src/basic/verbs.h b/src/basic/verbs.h deleted file mode 100644 index 7b5e18510f..0000000000 --- a/src/basic/verbs.h +++ /dev/null @@ -1,33 +0,0 @@ -#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 . -***/ - -#define VERB_ANY ((unsigned) -1) -#define VERB_DEFAULT 1U -#define VERB_NOCHROOT 2U - -typedef struct { - const char *verb; - unsigned min_args, max_args; - unsigned flags; - int (* const dispatch)(int argc, char *argv[], void *userdata); -} Verb; - -int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata); diff --git a/src/basic/virt.c b/src/basic/virt.c deleted file mode 100644 index dace1f4328..0000000000 --- a/src/basic/virt.c +++ /dev/null @@ -1,516 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include -#include -#include - -#include "alloc-util.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "macro.h" -#include "process-util.h" -#include "stat-util.h" -#include "string-table.h" -#include "string-util.h" -#include "virt.h" - -static int detect_vm_cpuid(void) { - - /* CPUID is an x86 specific interface. */ -#if defined(__i386__) || defined(__x86_64__) - - static const struct { - const char *cpuid; - int id; - } cpuid_vendor_table[] = { - { "XenVMMXenVMM", VIRTUALIZATION_XEN }, - { "KVMKVMKVM", VIRTUALIZATION_KVM }, - /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ - { "VMwareVMware", VIRTUALIZATION_VMWARE }, - /* http://msdn.microsoft.com/en-us/library/ff542428.aspx */ - { "Microsoft Hv", VIRTUALIZATION_MICROSOFT }, - }; - - uint32_t eax, ecx; - bool hypervisor; - - /* http://lwn.net/Articles/301888/ */ - -#if defined (__i386__) -#define REG_a "eax" -#define REG_b "ebx" -#elif defined (__amd64__) -#define REG_a "rax" -#define REG_b "rbx" -#endif - - /* First detect whether there is a hypervisor */ - eax = 1; - __asm__ __volatile__ ( - /* ebx/rbx is being used for PIC! */ - " push %%"REG_b" \n\t" - " cpuid \n\t" - " pop %%"REG_b" \n\t" - - : "=a" (eax), "=c" (ecx) - : "0" (eax) - ); - - hypervisor = !!(ecx & 0x80000000U); - - if (hypervisor) { - union { - uint32_t sig32[3]; - char text[13]; - } sig = {}; - unsigned j; - - /* There is a hypervisor, see what it is */ - eax = 0x40000000U; - __asm__ __volatile__ ( - /* ebx/rbx is being used for PIC! */ - " push %%"REG_b" \n\t" - " cpuid \n\t" - " mov %%ebx, %1 \n\t" - " pop %%"REG_b" \n\t" - - : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2]) - : "0" (eax) - ); - - log_debug("Virtualization found, CPUID=%s", sig.text); - - for (j = 0; j < ELEMENTSOF(cpuid_vendor_table); j ++) - if (streq(sig.text, cpuid_vendor_table[j].cpuid)) - return cpuid_vendor_table[j].id; - - return VIRTUALIZATION_VM_OTHER; - } -#endif - log_debug("No virtualization found in CPUID"); - - return VIRTUALIZATION_NONE; -} - -static int detect_vm_device_tree(void) { -#if defined(__arm__) || defined(__aarch64__) || defined(__powerpc__) || defined(__powerpc64__) - _cleanup_free_ char *hvtype = NULL; - int r; - - r = read_one_line_file("/proc/device-tree/hypervisor/compatible", &hvtype); - if (r == -ENOENT) { - _cleanup_closedir_ DIR *dir = NULL; - struct dirent *dent; - - dir = opendir("/proc/device-tree"); - if (!dir) { - if (errno == ENOENT) { - log_debug_errno(errno, "/proc/device-tree: %m"); - return VIRTUALIZATION_NONE; - } - return -errno; - } - - FOREACH_DIRENT(dent, dir, return -errno) - if (strstr(dent->d_name, "fw-cfg")) { - log_debug("Virtualization QEMU: \"fw-cfg\" present in /proc/device-tree/%s", dent->d_name); - return VIRTUALIZATION_QEMU; - } - - log_debug("No virtualization found in /proc/device-tree/*"); - return VIRTUALIZATION_NONE; - } else if (r < 0) - return r; - - log_debug("Virtualization %s found in /proc/device-tree/hypervisor/compatible", hvtype); - if (streq(hvtype, "linux,kvm")) - return VIRTUALIZATION_KVM; - else if (strstr(hvtype, "xen")) - return VIRTUALIZATION_XEN; - else - return VIRTUALIZATION_VM_OTHER; -#else - log_debug("This platform does not support /proc/device-tree"); - return VIRTUALIZATION_NONE; -#endif -} - -static int detect_vm_dmi(void) { -#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) - - static const char *const dmi_vendors[] = { - "/sys/class/dmi/id/product_name", /* Test this before sys_vendor to detect KVM over QEMU */ - "/sys/class/dmi/id/sys_vendor", - "/sys/class/dmi/id/board_vendor", - "/sys/class/dmi/id/bios_vendor" - }; - - static const struct { - const char *vendor; - int id; - } dmi_vendor_table[] = { - { "KVM", VIRTUALIZATION_KVM }, - { "QEMU", VIRTUALIZATION_QEMU }, - /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ - { "VMware", VIRTUALIZATION_VMWARE }, - { "VMW", VIRTUALIZATION_VMWARE }, - { "innotek GmbH", VIRTUALIZATION_ORACLE }, - { "Xen", VIRTUALIZATION_XEN }, - { "Bochs", VIRTUALIZATION_BOCHS }, - { "Parallels", VIRTUALIZATION_PARALLELS }, - }; - unsigned i; - int r; - - for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) { - _cleanup_free_ char *s = NULL; - unsigned j; - - r = read_one_line_file(dmi_vendors[i], &s); - if (r < 0) { - if (r == -ENOENT) - continue; - - return r; - } - - - - for (j = 0; j < ELEMENTSOF(dmi_vendor_table); j++) - if (startswith(s, dmi_vendor_table[j].vendor)) { - log_debug("Virtualization %s found in DMI (%s)", s, dmi_vendors[i]); - return dmi_vendor_table[j].id; - } - } -#endif - - log_debug("No virtualization found in DMI"); - - return VIRTUALIZATION_NONE; -} - -static int detect_vm_xen(void) { - /* Check for Dom0 will be executed later in detect_vm_xen_dom0 - Thats why we dont check the content of /proc/xen/capabilities here. */ - if (access("/proc/xen/capabilities", F_OK) < 0) { - log_debug("Virtualization XEN not found, /proc/xen/capabilities does not exist"); - return VIRTUALIZATION_NONE; - } - - log_debug("Virtualization XEN found (/proc/xen/capabilities exists)"); - return VIRTUALIZATION_XEN; - -} - -static bool detect_vm_xen_dom0(void) { - _cleanup_free_ char *domcap = NULL; - char *cap, *i; - int r; - - r = read_one_line_file("/proc/xen/capabilities", &domcap); - if (r == -ENOENT) { - log_debug("Virtualization XEN not found, /proc/xen/capabilities does not exist"); - return false; - } - if (r < 0) - return r; - - i = domcap; - while ((cap = strsep(&i, ","))) - if (streq(cap, "control_d")) - break; - if (!cap) { - log_debug("Virtualization XEN DomU found (/proc/xen/capabilites)"); - return false; - } - - log_debug("Virtualization XEN Dom0 ignored (/proc/xen/capabilities)"); - return true; -} - -static int detect_vm_hypervisor(void) { - _cleanup_free_ char *hvtype = NULL; - int r; - - r = read_one_line_file("/sys/hypervisor/type", &hvtype); - if (r == -ENOENT) - return VIRTUALIZATION_NONE; - if (r < 0) - return r; - - log_debug("Virtualization %s found in /sys/hypervisor/type", hvtype); - - if (streq(hvtype, "xen")) - return VIRTUALIZATION_XEN; - else - return VIRTUALIZATION_VM_OTHER; -} - -static int detect_vm_uml(void) { - _cleanup_free_ char *cpuinfo_contents = NULL; - int r; - - /* Detect User-Mode Linux by reading /proc/cpuinfo */ - r = read_full_file("/proc/cpuinfo", &cpuinfo_contents, NULL); - if (r < 0) - return r; - - if (strstr(cpuinfo_contents, "\nvendor_id\t: User Mode Linux\n")) { - log_debug("UML virtualization found in /proc/cpuinfo"); - return VIRTUALIZATION_UML; - } - - log_debug("No virtualization found in /proc/cpuinfo."); - return VIRTUALIZATION_NONE; -} - -static int detect_vm_zvm(void) { - -#if defined(__s390__) - _cleanup_free_ char *t = NULL; - int r; - - r = get_proc_field("/proc/sysinfo", "VM00 Control Program", WHITESPACE, &t); - if (r == -ENOENT) - return VIRTUALIZATION_NONE; - if (r < 0) - return r; - - log_debug("Virtualization %s found in /proc/sysinfo", t); - if (streq(t, "z/VM")) - return VIRTUALIZATION_ZVM; - else - return VIRTUALIZATION_KVM; -#else - log_debug("This platform does not support /proc/sysinfo"); - return VIRTUALIZATION_NONE; -#endif -} - -/* Returns a short identifier for the various VM implementations */ -int detect_vm(void) { - static thread_local int cached_found = _VIRTUALIZATION_INVALID; - int r; - - if (cached_found >= 0) - return cached_found; - - /* We have to use the correct order here: - * Some virtualization technologies do use KVM hypervisor but are - * expected to be detected as something else. So detect DMI first. - * - * An example is Virtualbox since version 5.0, which uses KVM backend. - * Detection via DMI works corretly, the CPU ID would find KVM - * only. */ - r = detect_vm_dmi(); - if (r < 0) - return r; - if (r != VIRTUALIZATION_NONE) - goto finish; - - r = detect_vm_cpuid(); - if (r < 0) - return r; - if (r != VIRTUALIZATION_NONE) - goto finish; - - /* x86 xen will most likely be detected by cpuid. If not (most likely - * because we're not an x86 guest), then we should try the xen capabilities - * file next. If that's not found, then we check for the high-level - * hypervisor sysfs file: - * - * https://bugs.freedesktop.org/show_bug.cgi?id=77271 */ - - r = detect_vm_xen(); - if (r < 0) - return r; - if (r != VIRTUALIZATION_NONE) - goto finish; - - r = detect_vm_hypervisor(); - if (r < 0) - return r; - if (r != VIRTUALIZATION_NONE) - goto finish; - - r = detect_vm_device_tree(); - if (r < 0) - return r; - if (r != VIRTUALIZATION_NONE) - goto finish; - - r = detect_vm_uml(); - if (r < 0) - return r; - if (r != VIRTUALIZATION_NONE) - goto finish; - - r = detect_vm_zvm(); - if (r < 0) - return r; - -finish: - /* x86 xen Dom0 is detected as XEN in hypervisor and maybe others. - * In order to detect the Dom0 as not virtualization we need to - * double-check it */ - if (r == VIRTUALIZATION_XEN && detect_vm_xen_dom0()) - r = VIRTUALIZATION_NONE; - - cached_found = r; - log_debug("Found VM virtualization %s", virtualization_to_string(r)); - return r; -} - -int detect_container(void) { - - static const struct { - const char *value; - int id; - } value_table[] = { - { "lxc", VIRTUALIZATION_LXC }, - { "lxc-libvirt", VIRTUALIZATION_LXC_LIBVIRT }, - { "systemd-nspawn", VIRTUALIZATION_SYSTEMD_NSPAWN }, - { "docker", VIRTUALIZATION_DOCKER }, - { "rkt", VIRTUALIZATION_RKT }, - }; - - static thread_local int cached_found = _VIRTUALIZATION_INVALID; - _cleanup_free_ char *m = NULL; - const char *e = NULL; - unsigned j; - int r; - - if (cached_found >= 0) - return cached_found; - - /* /proc/vz exists in container and outside of the container, - * /proc/bc only outside of the container. */ - if (access("/proc/vz", F_OK) >= 0 && - access("/proc/bc", F_OK) < 0) { - r = VIRTUALIZATION_OPENVZ; - goto finish; - } - - if (getpid() == 1) { - /* If we are PID 1 we can just check our own - * environment variable */ - - e = getenv("container"); - if (isempty(e)) { - r = VIRTUALIZATION_NONE; - goto finish; - } - } else { - - /* Otherwise, PID 1 dropped this information into a - * file in /run. This is better than accessing - * /proc/1/environ, since we don't need CAP_SYS_PTRACE - * for that. */ - - r = read_one_line_file("/run/systemd/container", &m); - if (r == -ENOENT) { - - /* Fallback for cases where PID 1 was not - * systemd (for example, cases where - * init=/bin/sh is used. */ - - r = getenv_for_pid(1, "container", &m); - if (r <= 0) { - - /* If that didn't work, give up, - * assume no container manager. - * - * Note: This means we still cannot - * detect containers if init=/bin/sh - * is passed but privileges dropped, - * as /proc/1/environ is only readable - * with privileges. */ - - r = VIRTUALIZATION_NONE; - goto finish; - } - } - if (r < 0) - return r; - - e = m; - } - - for (j = 0; j < ELEMENTSOF(value_table); j++) - if (streq(e, value_table[j].value)) { - r = value_table[j].id; - goto finish; - } - - r = VIRTUALIZATION_CONTAINER_OTHER; - -finish: - log_debug("Found container virtualization %s", virtualization_to_string(r)); - cached_found = r; - return r; -} - -int detect_virtualization(void) { - int r; - - r = detect_container(); - if (r == 0) - r = detect_vm(); - - return r; -} - -int running_in_chroot(void) { - int ret; - - ret = files_same("/proc/1/root", "/"); - if (ret < 0) - return ret; - - return ret == 0; -} - -static const char *const virtualization_table[_VIRTUALIZATION_MAX] = { - [VIRTUALIZATION_NONE] = "none", - [VIRTUALIZATION_KVM] = "kvm", - [VIRTUALIZATION_QEMU] = "qemu", - [VIRTUALIZATION_BOCHS] = "bochs", - [VIRTUALIZATION_XEN] = "xen", - [VIRTUALIZATION_UML] = "uml", - [VIRTUALIZATION_VMWARE] = "vmware", - [VIRTUALIZATION_ORACLE] = "oracle", - [VIRTUALIZATION_MICROSOFT] = "microsoft", - [VIRTUALIZATION_ZVM] = "zvm", - [VIRTUALIZATION_PARALLELS] = "parallels", - [VIRTUALIZATION_VM_OTHER] = "vm-other", - - [VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn", - [VIRTUALIZATION_LXC_LIBVIRT] = "lxc-libvirt", - [VIRTUALIZATION_LXC] = "lxc", - [VIRTUALIZATION_OPENVZ] = "openvz", - [VIRTUALIZATION_DOCKER] = "docker", - [VIRTUALIZATION_RKT] = "rkt", - [VIRTUALIZATION_CONTAINER_OTHER] = "container-other", -}; - -DEFINE_STRING_TABLE_LOOKUP(virtualization, int); diff --git a/src/basic/virt.h b/src/basic/virt.h deleted file mode 100644 index a538f07f6b..0000000000 --- a/src/basic/virt.h +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 "macro.h" - -enum { - VIRTUALIZATION_NONE = 0, - - VIRTUALIZATION_VM_FIRST, - VIRTUALIZATION_KVM = VIRTUALIZATION_VM_FIRST, - VIRTUALIZATION_QEMU, - VIRTUALIZATION_BOCHS, - VIRTUALIZATION_XEN, - VIRTUALIZATION_UML, - VIRTUALIZATION_VMWARE, - VIRTUALIZATION_ORACLE, - VIRTUALIZATION_MICROSOFT, - VIRTUALIZATION_ZVM, - VIRTUALIZATION_PARALLELS, - VIRTUALIZATION_VM_OTHER, - VIRTUALIZATION_VM_LAST = VIRTUALIZATION_VM_OTHER, - - VIRTUALIZATION_CONTAINER_FIRST, - VIRTUALIZATION_SYSTEMD_NSPAWN = VIRTUALIZATION_CONTAINER_FIRST, - VIRTUALIZATION_LXC_LIBVIRT, - VIRTUALIZATION_LXC, - VIRTUALIZATION_OPENVZ, - VIRTUALIZATION_DOCKER, - VIRTUALIZATION_RKT, - VIRTUALIZATION_CONTAINER_OTHER, - VIRTUALIZATION_CONTAINER_LAST = VIRTUALIZATION_CONTAINER_OTHER, - - _VIRTUALIZATION_MAX, - _VIRTUALIZATION_INVALID = -1 -}; - -static inline bool VIRTUALIZATION_IS_VM(int x) { - return x >= VIRTUALIZATION_VM_FIRST && x <= VIRTUALIZATION_VM_LAST; -} - -static inline bool VIRTUALIZATION_IS_CONTAINER(int x) { - return x >= VIRTUALIZATION_CONTAINER_FIRST && x <= VIRTUALIZATION_CONTAINER_LAST; -} - -int detect_vm(void); -int detect_container(void); -int detect_virtualization(void); - -int running_in_chroot(void); - -const char *virtualization_to_string(int v) _const_; -int virtualization_from_string(const char *s) _pure_; diff --git a/src/basic/web-util.c b/src/basic/web-util.c deleted file mode 100644 index 595688ed93..0000000000 --- a/src/basic/web-util.c +++ /dev/null @@ -1,76 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 "string-util.h" -#include "utf8.h" -#include "web-util.h" - -bool http_etag_is_valid(const char *etag) { - if (isempty(etag)) - return false; - - if (!endswith(etag, "\"")) - return false; - - if (!startswith(etag, "\"") && !startswith(etag, "W/\"")) - return false; - - return true; -} - -bool http_url_is_valid(const char *url) { - const char *p; - - if (isempty(url)) - return false; - - p = startswith(url, "http://"); - if (!p) - p = startswith(url, "https://"); - if (!p) - return false; - - if (isempty(p)) - return false; - - return ascii_is_valid(p); -} - -bool documentation_url_is_valid(const char *url) { - const char *p; - - if (isempty(url)) - return false; - - if (http_url_is_valid(url)) - return true; - - p = startswith(url, "file:/"); - if (!p) - p = startswith(url, "info:"); - if (!p) - p = startswith(url, "man:"); - - if (isempty(p)) - return false; - - return ascii_is_valid(p); -} diff --git a/src/basic/web-util.h b/src/basic/web-util.h deleted file mode 100644 index e6bb6b53f5..0000000000 --- a/src/basic/web-util.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "macro.h" - -bool http_url_is_valid(const char *url) _pure_; - -bool documentation_url_is_valid(const char *url) _pure_; - -bool http_etag_is_valid(const char *etag); diff --git a/src/basic/xattr-util.c b/src/basic/xattr-util.c deleted file mode 100644 index 8256899eda..0000000000 --- a/src/basic/xattr-util.c +++ /dev/null @@ -1,200 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "macro.h" -#include "sparse-endian.h" -#include "stdio-util.h" -#include "time-util.h" -#include "xattr-util.h" - -int getxattr_malloc(const char *path, const char *name, char **value, bool allow_symlink) { - char *v; - size_t l; - ssize_t n; - - assert(path); - assert(name); - assert(value); - - for (l = 100; ; l = (size_t) n + 1) { - v = new0(char, l); - if (!v) - return -ENOMEM; - - if (allow_symlink) - n = lgetxattr(path, name, v, l); - else - n = getxattr(path, name, v, l); - - if (n >= 0 && (size_t) n < l) { - *value = v; - return n; - } - - free(v); - - if (n < 0 && errno != ERANGE) - return -errno; - - if (allow_symlink) - n = lgetxattr(path, name, NULL, 0); - else - n = getxattr(path, name, NULL, 0); - if (n < 0) - return -errno; - } -} - -int fgetxattr_malloc(int fd, const char *name, char **value) { - char *v; - size_t l; - ssize_t n; - - assert(fd >= 0); - assert(name); - assert(value); - - for (l = 100; ; l = (size_t) n + 1) { - v = new0(char, l); - if (!v) - return -ENOMEM; - - n = fgetxattr(fd, name, v, l); - - if (n >= 0 && (size_t) n < l) { - *value = v; - return n; - } - - free(v); - - if (n < 0 && errno != ERANGE) - return -errno; - - n = fgetxattr(fd, name, NULL, 0); - if (n < 0) - return -errno; - } -} - -ssize_t fgetxattrat_fake(int dirfd, const char *filename, const char *attribute, void *value, size_t size, int flags) { - char fn[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; - _cleanup_close_ int fd = -1; - ssize_t l; - - /* The kernel doesn't have a fgetxattrat() command, hence let's emulate one */ - - fd = openat(dirfd, filename, O_CLOEXEC|O_PATH|(flags & AT_SYMLINK_NOFOLLOW ? O_NOFOLLOW : 0)); - if (fd < 0) - return -errno; - - xsprintf(fn, "/proc/self/fd/%i", fd); - - l = getxattr(fn, attribute, value, size); - if (l < 0) - return -errno; - - return l; -} - -static int parse_crtime(le64_t le, usec_t *usec) { - uint64_t u; - - assert(usec); - - u = le64toh(le); - if (u == 0 || u == (uint64_t) -1) - return -EIO; - - *usec = (usec_t) u; - return 0; -} - -int fd_getcrtime(int fd, usec_t *usec) { - le64_t le; - ssize_t n; - - assert(fd >= 0); - assert(usec); - - /* Until Linux gets a real concept of birthtime/creation time, - * let's fake one with xattrs */ - - n = fgetxattr(fd, "user.crtime_usec", &le, sizeof(le)); - if (n < 0) - return -errno; - if (n != sizeof(le)) - return -EIO; - - return parse_crtime(le, usec); -} - -int fd_getcrtime_at(int dirfd, const char *name, usec_t *usec, int flags) { - le64_t le; - ssize_t n; - - n = fgetxattrat_fake(dirfd, name, "user.crtime_usec", &le, sizeof(le), flags); - if (n < 0) - return -errno; - if (n != sizeof(le)) - return -EIO; - - return parse_crtime(le, usec); -} - -int path_getcrtime(const char *p, usec_t *usec) { - le64_t le; - ssize_t n; - - assert(p); - assert(usec); - - n = getxattr(p, "user.crtime_usec", &le, sizeof(le)); - if (n < 0) - return -errno; - if (n != sizeof(le)) - return -EIO; - - return parse_crtime(le, usec); -} - -int fd_setcrtime(int fd, usec_t usec) { - le64_t le; - - assert(fd >= 0); - - if (usec <= 0) - usec = now(CLOCK_REALTIME); - - le = htole64((uint64_t) usec); - if (fsetxattr(fd, "user.crtime_usec", &le, sizeof(le), 0) < 0) - return -errno; - - return 0; -} diff --git a/src/basic/xattr-util.h b/src/basic/xattr-util.h deleted file mode 100644 index 6fa097bf7e..0000000000 --- a/src/basic/xattr-util.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include - -#include "time-util.h" - -int getxattr_malloc(const char *path, const char *name, char **value, bool allow_symlink); -int fgetxattr_malloc(int fd, const char *name, char **value); - -ssize_t fgetxattrat_fake(int dirfd, const char *filename, const char *attribute, void *value, size_t size, int flags); - -int fd_setcrtime(int fd, usec_t usec); - -int fd_getcrtime(int fd, usec_t *usec); -int path_getcrtime(const char *p, usec_t *usec); -int fd_getcrtime_at(int dirfd, const char *name, usec_t *usec, int flags); diff --git a/src/basic/xml.c b/src/basic/xml.c deleted file mode 100644 index 1dbeac7324..0000000000 --- a/src/basic/xml.c +++ /dev/null @@ -1,255 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 -#include - -#include "macro.h" -#include "string-util.h" -#include "xml.h" - -enum { - STATE_NULL, - STATE_TEXT, - STATE_TAG, - STATE_ATTRIBUTE, -}; - -static void inc_lines(unsigned *line, const char *s, size_t n) { - const char *p = s; - - if (!line) - return; - - for (;;) { - const char *f; - - f = memchr(p, '\n', n); - if (!f) - return; - - n -= (f - p) + 1; - p = f + 1; - (*line)++; - } -} - -/* We don't actually do real XML here. We only read a simplistic - * subset, that is a bit less strict that XML and lacks all the more - * complex features, like entities, or namespaces. However, we do - * support some HTML5-like simplifications */ - -int xml_tokenize(const char **p, char **name, void **state, unsigned *line) { - const char *c, *e, *b; - char *ret; - int t; - - assert(p); - assert(*p); - assert(name); - assert(state); - - t = PTR_TO_INT(*state); - c = *p; - - if (t == STATE_NULL) { - if (line) - *line = 1; - t = STATE_TEXT; - } - - for (;;) { - if (*c == 0) - return XML_END; - - switch (t) { - - case STATE_TEXT: { - int x; - - e = strchrnul(c, '<'); - if (e > c) { - /* More text... */ - ret = strndup(c, e - c); - if (!ret) - return -ENOMEM; - - inc_lines(line, c, e - c); - - *name = ret; - *p = e; - *state = INT_TO_PTR(STATE_TEXT); - - return XML_TEXT; - } - - assert(*e == '<'); - b = c + 1; - - if (startswith(b, "!--")) { - /* A comment */ - e = strstr(b + 3, "-->"); - if (!e) - return -EINVAL; - - inc_lines(line, b, e + 3 - b); - - c = e + 3; - continue; - } - - if (*b == '?') { - /* Processing instruction */ - - e = strstr(b + 1, "?>"); - if (!e) - return -EINVAL; - - inc_lines(line, b, e + 2 - b); - - c = e + 2; - continue; - } - - if (*b == '!') { - /* DTD */ - - e = strchr(b + 1, '>'); - if (!e) - return -EINVAL; - - inc_lines(line, b, e + 1 - b); - - c = e + 1; - continue; - } - - if (*b == '/') { - /* A closing tag */ - x = XML_TAG_CLOSE; - b++; - } else - x = XML_TAG_OPEN; - - e = strpbrk(b, WHITESPACE "/>"); - if (!e) - return -EINVAL; - - ret = strndup(b, e - b); - if (!ret) - return -ENOMEM; - - *name = ret; - *p = e; - *state = INT_TO_PTR(STATE_TAG); - - return x; - } - - case STATE_TAG: - - b = c + strspn(c, WHITESPACE); - if (*b == 0) - return -EINVAL; - - inc_lines(line, c, b - c); - - e = b + strcspn(b, WHITESPACE "=/>"); - if (e > b) { - /* An attribute */ - - ret = strndup(b, e - b); - if (!ret) - return -ENOMEM; - - *name = ret; - *p = e; - *state = INT_TO_PTR(STATE_ATTRIBUTE); - - return XML_ATTRIBUTE_NAME; - } - - if (startswith(b, "/>")) { - /* An empty tag */ - - *name = NULL; /* For empty tags we return a NULL name, the caller must be prepared for that */ - *p = b + 2; - *state = INT_TO_PTR(STATE_TEXT); - - return XML_TAG_CLOSE_EMPTY; - } - - if (*b != '>') - return -EINVAL; - - c = b + 1; - t = STATE_TEXT; - continue; - - case STATE_ATTRIBUTE: - - if (*c == '=') { - c++; - - if (*c == '\'' || *c == '\"') { - /* Tag with a quoted value */ - - e = strchr(c+1, *c); - if (!e) - return -EINVAL; - - inc_lines(line, c, e - c); - - ret = strndup(c+1, e - c - 1); - if (!ret) - return -ENOMEM; - - *name = ret; - *p = e + 1; - *state = INT_TO_PTR(STATE_TAG); - - return XML_ATTRIBUTE_VALUE; - - } - - /* Tag with a value without quotes */ - - b = strpbrk(c, WHITESPACE ">"); - if (!b) - b = c; - - ret = strndup(c, b - c); - if (!ret) - return -ENOMEM; - - *name = ret; - *p = b; - *state = INT_TO_PTR(STATE_TAG); - return XML_ATTRIBUTE_VALUE; - } - - t = STATE_TAG; - continue; - } - - } - - assert_not_reached("Bad state"); -} diff --git a/src/basic/xml.h b/src/basic/xml.h deleted file mode 100644 index 41cb69f0dc..0000000000 --- a/src/basic/xml.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -enum { - XML_END, - XML_TEXT, - XML_TAG_OPEN, - XML_TAG_CLOSE, - XML_TAG_CLOSE_EMPTY, - XML_ATTRIBUTE_NAME, - XML_ATTRIBUTE_VALUE, -}; - -int xml_tokenize(const char **p, char **name, void **state, unsigned *line); diff --git a/src/binfmt/Makefile b/src/binfmt/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/binfmt/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/binfmt/binfmt.c b/src/binfmt/binfmt.c deleted file mode 100644 index eeef04fb1c..0000000000 --- a/src/binfmt/binfmt.c +++ /dev/null @@ -1,203 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "conf-files.h" -#include "def.h" -#include "fd-util.h" -#include "fileio.h" -#include "log.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -static const char conf_file_dirs[] = CONF_PATHS_NULSTR("binfmt.d"); - -static int delete_rule(const char *rule) { - _cleanup_free_ char *x = NULL, *fn = NULL; - char *e; - - assert(rule[0]); - - x = strdup(rule); - if (!x) - return log_oom(); - - e = strchrnul(x+1, x[0]); - *e = 0; - - fn = strappend("/proc/sys/fs/binfmt_misc/", x+1); - if (!fn) - return log_oom(); - - return write_string_file(fn, "-1", 0); -} - -static int apply_rule(const char *rule) { - int r; - - delete_rule(rule); - - r = write_string_file("/proc/sys/fs/binfmt_misc/register", rule, 0); - if (r < 0) - return log_error_errno(r, "Failed to add binary format: %m"); - - return 0; -} - -static int apply_file(const char *path, bool ignore_enoent) { - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(path); - - r = search_and_fopen_nulstr(path, "re", NULL, conf_file_dirs, &f); - if (r < 0) { - if (ignore_enoent && r == -ENOENT) - return 0; - - return log_error_errno(r, "Failed to open file '%s', ignoring: %m", path); - } - - log_debug("apply: %s", path); - for (;;) { - char l[LINE_MAX], *p; - int k; - - if (!fgets(l, sizeof(l), f)) { - if (feof(f)) - break; - - return log_error_errno(errno, "Failed to read file '%s', ignoring: %m", path); - } - - p = strstrip(l); - if (!*p) - continue; - if (strchr(COMMENTS "\n", *p)) - continue; - - k = apply_rule(p); - if (k < 0 && r == 0) - r = k; - } - - return r; -} - -static void help(void) { - printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" - "Registers binary formats.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -int main(int argc, char *argv[]) { - int r, k; - - r = parse_argv(argc, argv); - if (r <= 0) - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - r = 0; - - if (argc > optind) { - int i; - - for (i = optind; i < argc; i++) { - k = apply_file(argv[i], false); - if (k < 0 && r == 0) - r = k; - } - } else { - _cleanup_strv_free_ char **files = NULL; - char **f; - - r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs); - if (r < 0) { - log_error_errno(r, "Failed to enumerate binfmt.d files: %m"); - goto finish; - } - - /* Flush out all rules */ - write_string_file("/proc/sys/fs/binfmt_misc/status", "-1", 0); - - STRV_FOREACH(f, files) { - k = apply_file(*f, true); - if (k < 0 && r == 0) - r = k; - } - } - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/boot/Makefile b/src/boot/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/boot/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c deleted file mode 100644 index 4a356d25d1..0000000000 --- a/src/boot/bootctl.c +++ /dev/null @@ -1,1143 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013-2015 Kay Sievers - Copyright 2013 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "blkid-util.h" -#include "dirent-util.h" -#include "efivars.h" -#include "fd-util.h" -#include "fileio.h" -#include "locale-util.h" -#include "rm-rf.h" -#include "string-util.h" -#include "util.h" - -static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t *psize, sd_id128_t *uuid) { - struct statfs sfs; - struct stat st, st2; - _cleanup_free_ char *t = NULL; - _cleanup_blkid_free_probe_ blkid_probe b = NULL; - int r; - const char *v, *t2; - - if (statfs(p, &sfs) < 0) - return log_error_errno(errno, "Failed to check file system type of \"%s\": %m", p); - - if (sfs.f_type != 0x4d44) { - log_error("File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p); - return -ENODEV; - } - - if (stat(p, &st) < 0) - return log_error_errno(errno, "Failed to determine block device node of \"%s\": %m", p); - - if (major(st.st_dev) == 0) { - log_error("Block device node of %p is invalid.", p); - return -ENODEV; - } - - t2 = strjoina(p, "/.."); - r = stat(t2, &st2); - if (r < 0) - return log_error_errno(errno, "Failed to determine block device node of parent of \"%s\": %m", p); - - if (st.st_dev == st2.st_dev) { - log_error("Directory \"%s\" is not the root of the EFI System Partition (ESP) file system.", p); - return -ENODEV; - } - - r = asprintf(&t, "/dev/block/%u:%u", major(st.st_dev), minor(st.st_dev)); - if (r < 0) - return log_oom(); - - errno = 0; - b = blkid_new_probe_from_filename(t); - if (!b) { - if (errno == 0) - return log_oom(); - - return log_error_errno(errno, "Failed to open file system \"%s\": %m", p); - } - - blkid_probe_enable_superblocks(b, 1); - blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE); - blkid_probe_enable_partitions(b, 1); - blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); - - errno = 0; - r = blkid_do_safeprobe(b); - if (r == -2) { - log_error("File system \"%s\" is ambigious.", p); - return -ENODEV; - } else if (r == 1) { - log_error("File system \"%s\" does not contain a label.", p); - return -ENODEV; - } else if (r != 0) { - r = errno ? -errno : -EIO; - return log_error_errno(r, "Failed to probe file system \"%s\": %m", p); - } - - errno = 0; - r = blkid_probe_lookup_value(b, "TYPE", &v, NULL); - if (r != 0) { - r = errno ? -errno : -EIO; - return log_error_errno(r, "Failed to probe file system type \"%s\": %m", p); - } - - if (!streq(v, "vfat")) { - log_error("File system \"%s\" is not FAT.", p); - return -ENODEV; - } - - errno = 0; - r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL); - if (r != 0) { - r = errno ? -errno : -EIO; - return log_error_errno(r, "Failed to probe partition scheme \"%s\": %m", p); - } - - if (!streq(v, "gpt")) { - log_error("File system \"%s\" is not on a GPT partition table.", p); - return -ENODEV; - } - - errno = 0; - r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL); - if (r != 0) { - r = errno ? -errno : -EIO; - return log_error_errno(r, "Failed to probe partition type UUID \"%s\": %m", p); - } - - if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b")) { - log_error("File system \"%s\" has wrong type for an EFI System Partition (ESP).", p); - return -ENODEV; - } - - errno = 0; - r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL); - if (r != 0) { - r = errno ? -errno : -EIO; - return log_error_errno(r, "Failed to probe partition entry UUID \"%s\": %m", p); - } - - r = sd_id128_from_string(v, uuid); - if (r < 0) { - log_error("Partition \"%s\" has invalid UUID \"%s\".", p, v); - return -EIO; - } - - errno = 0; - r = blkid_probe_lookup_value(b, "PART_ENTRY_NUMBER", &v, NULL); - if (r != 0) { - r = errno ? -errno : -EIO; - return log_error_errno(r, "Failed to probe partition number \"%s\": m", p); - } - *part = strtoul(v, NULL, 10); - - errno = 0; - r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL); - if (r != 0) { - r = errno ? -errno : -EIO; - return log_error_errno(r, "Failed to probe partition offset \"%s\": %m", p); - } - *pstart = strtoul(v, NULL, 10); - - errno = 0; - r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL); - if (r != 0) { - r = errno ? -errno : -EIO; - return log_error_errno(r, "Failed to probe partition size \"%s\": %m", p); - } - *psize = strtoul(v, NULL, 10); - - return 0; -} - -/* search for "#### LoaderInfo: systemd-boot 218 ####" string inside the binary */ -static int get_file_version(int fd, char **v) { - struct stat st; - char *buf; - const char *s, *e; - char *x = NULL; - int r = 0; - - assert(fd >= 0); - assert(v); - - if (fstat(fd, &st) < 0) - return -errno; - - if (st.st_size < 27) - return 0; - - buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); - if (buf == MAP_FAILED) - return -errno; - - s = memmem(buf, st.st_size - 8, "#### LoaderInfo: ", 17); - if (!s) - goto finish; - s += 17; - - e = memmem(s, st.st_size - (s - buf), " ####", 5); - if (!e || e - s < 3) { - log_error("Malformed version string."); - r = -EINVAL; - goto finish; - } - - x = strndup(s, e - s); - if (!x) { - r = log_oom(); - goto finish; - } - r = 1; - -finish: - munmap(buf, st.st_size); - *v = x; - return r; -} - -static int enumerate_binaries(const char *esp_path, const char *path, const char *prefix) { - char *p; - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - int r = 0, c = 0; - - p = strjoina(esp_path, "/", path); - d = opendir(p); - if (!d) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to read \"%s\": %m", p); - } - - FOREACH_DIRENT(de, d, break) { - _cleanup_close_ int fd = -1; - _cleanup_free_ char *v = NULL; - - if (!endswith_no_case(de->d_name, ".efi")) - continue; - - if (prefix && !startswith_no_case(de->d_name, prefix)) - continue; - - fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC); - if (fd < 0) - return log_error_errno(errno, "Failed to open \"%s/%s\" for reading: %m", p, de->d_name); - - r = get_file_version(fd, &v); - if (r < 0) - return r; - if (r > 0) - printf(" File: %s/%s/%s (%s)\n", special_glyph(TREE_RIGHT), path, de->d_name, v); - else - printf(" File: %s/%s/%s\n", special_glyph(TREE_RIGHT), path, de->d_name); - c++; - } - - return c; -} - -static int status_binaries(const char *esp_path, sd_id128_t partition) { - int r; - - printf("Boot Loader Binaries:\n"); - - printf(" ESP: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", SD_ID128_FORMAT_VAL(partition)); - - r = enumerate_binaries(esp_path, "EFI/systemd", NULL); - if (r == 0) - log_error("systemd-boot not installed in ESP."); - else if (r < 0) - return r; - - r = enumerate_binaries(esp_path, "EFI/Boot", "boot"); - if (r == 0) - log_error("No default/fallback boot loader installed in ESP."); - else if (r < 0) - return r; - - printf("\n"); - - return 0; -} - -static int print_efi_option(uint16_t id, bool in_order) { - _cleanup_free_ char *title = NULL; - _cleanup_free_ char *path = NULL; - sd_id128_t partition; - bool active; - int r = 0; - - r = efi_get_boot_option(id, &title, &partition, &path, &active); - if (r < 0) - return r; - - /* print only configured entries with partition information */ - if (!path || sd_id128_equal(partition, SD_ID128_NULL)) - return 0; - - efi_tilt_backslashes(path); - - printf(" Title: %s\n", strna(title)); - printf(" ID: 0x%04X\n", id); - printf(" Status: %sactive%s\n", active ? "" : "in", in_order ? ", boot-order" : ""); - printf(" Partition: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", SD_ID128_FORMAT_VAL(partition)); - printf(" File: %s%s\n", special_glyph(TREE_RIGHT), path); - printf("\n"); - - return 0; -} - -static int status_variables(void) { - int n_options, n_order; - _cleanup_free_ uint16_t *options = NULL, *order = NULL; - int i; - - if (!is_efi_boot()) { - log_notice("Not booted with EFI, not showing EFI variables."); - return 0; - } - - n_options = efi_get_boot_options(&options); - if (n_options == -ENOENT) - return log_error_errno(ENOENT, "Failed to access EFI variables, efivarfs" - " needs to be available at /sys/firmware/efi/efivars/."); - else if (n_options < 0) - return log_error_errno(n_options, "Failed to read EFI boot entries: %m"); - - n_order = efi_get_boot_order(&order); - if (n_order == -ENOENT) - n_order = 0; - else if (n_order < 0) - return log_error_errno(n_order, "Failed to read EFI boot order."); - - /* print entries in BootOrder first */ - printf("Boot Loader Entries in EFI Variables:\n"); - for (i = 0; i < n_order; i++) - print_efi_option(order[i], true); - - /* print remaining entries */ - for (i = 0; i < n_options; i++) { - int j; - - for (j = 0; j < n_order; j++) - if (options[i] == order[j]) - goto next; - - print_efi_option(options[i], false); - next: - continue; - } - - return 0; -} - -static int compare_product(const char *a, const char *b) { - size_t x, y; - - assert(a); - assert(b); - - x = strcspn(a, " "); - y = strcspn(b, " "); - if (x != y) - return x < y ? -1 : x > y ? 1 : 0; - - return strncmp(a, b, x); -} - -static int compare_version(const char *a, const char *b) { - assert(a); - assert(b); - - a += strcspn(a, " "); - a += strspn(a, " "); - b += strcspn(b, " "); - b += strspn(b, " "); - - return strverscmp(a, b); -} - -static int version_check(int fd, const char *from, const char *to) { - _cleanup_free_ char *a = NULL, *b = NULL; - _cleanup_close_ int fd2 = -1; - int r; - - assert(fd >= 0); - assert(from); - assert(to); - - r = get_file_version(fd, &a); - if (r < 0) - return r; - if (r == 0) { - log_error("Source file \"%s\" does not carry version information!", from); - return -EINVAL; - } - - fd2 = open(to, O_RDONLY|O_CLOEXEC); - if (fd2 < 0) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open \"%s\" for reading: %m", to); - } - - r = get_file_version(fd2, &b); - if (r < 0) - return r; - if (r == 0 || compare_product(a, b) != 0) { - log_notice("Skipping \"%s\", since it's owned by another boot loader.", to); - return -EEXIST; - } - - if (compare_version(a, b) < 0) { - log_warning("Skipping \"%s\", since a newer boot loader version exists already.", to); - return -ESTALE; - } - - return 0; -} - -static int copy_file(const char *from, const char *to, bool force) { - _cleanup_fclose_ FILE *f = NULL, *g = NULL; - char *p; - int r; - struct timespec t[2]; - struct stat st; - - assert(from); - assert(to); - - f = fopen(from, "re"); - if (!f) - return log_error_errno(errno, "Failed to open \"%s\" for reading: %m", from); - - if (!force) { - /* If this is an update, then let's compare versions first */ - r = version_check(fileno(f), from, to); - if (r < 0) - return r; - } - - p = strjoina(to, "~"); - g = fopen(p, "wxe"); - if (!g) { - /* Directory doesn't exist yet? Then let's skip this... */ - if (!force && errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open \"%s\" for writing: %m", to); - } - - rewind(f); - do { - size_t k; - uint8_t buf[32*1024]; - - k = fread(buf, 1, sizeof(buf), f); - if (ferror(f)) { - r = log_error_errno(EIO, "Failed to read \"%s\": %m", from); - goto error; - } - - if (k == 0) - break; - - fwrite(buf, 1, k, g); - if (ferror(g)) { - r = log_error_errno(EIO, "Failed to write \"%s\": %m", to); - goto error; - } - } while (!feof(f)); - - r = fflush_and_check(g); - if (r < 0) { - log_error_errno(r, "Failed to write \"%s\": %m", to); - goto error; - } - - r = fstat(fileno(f), &st); - if (r < 0) { - r = log_error_errno(errno, "Failed to get file timestamps of \"%s\": %m", from); - goto error; - } - - t[0] = st.st_atim; - t[1] = st.st_mtim; - - r = futimens(fileno(g), t); - if (r < 0) { - r = log_error_errno(errno, "Failed to set file timestamps on \"%s\": %m", p); - goto error; - } - - if (rename(p, to) < 0) { - r = log_error_errno(errno, "Failed to rename \"%s\" to \"%s\": %m", p, to); - goto error; - } - - log_info("Copied \"%s\" to \"%s\".", from, to); - return 0; - -error: - (void) unlink(p); - return r; -} - -static char* strupper(char *s) { - char *p; - - for (p = s; *p; p++) - *p = toupper(*p); - - return s; -} - -static int mkdir_one(const char *prefix, const char *suffix) { - char *p; - - p = strjoina(prefix, "/", suffix); - if (mkdir(p, 0700) < 0) { - if (errno != EEXIST) - return log_error_errno(errno, "Failed to create \"%s\": %m", p); - } else - log_info("Created \"%s\".", p); - - return 0; -} - -static const char *efi_subdirs[] = { - "EFI", - "EFI/systemd", - "EFI/Boot", - "loader", - "loader/entries" -}; - -static int create_dirs(const char *esp_path) { - int r; - unsigned i; - - for (i = 0; i < ELEMENTSOF(efi_subdirs); i++) { - r = mkdir_one(esp_path, efi_subdirs[i]); - if (r < 0) - return r; - } - - return 0; -} - -static int copy_one_file(const char *esp_path, const char *name, bool force) { - char *p, *q; - int r; - - p = strjoina(BOOTLIBDIR "/", name); - q = strjoina(esp_path, "/EFI/systemd/", name); - r = copy_file(p, q, force); - - if (startswith(name, "systemd-boot")) { - int k; - char *v; - - /* Create the EFI default boot loader name (specified for removable devices) */ - v = strjoina(esp_path, "/EFI/Boot/BOOT", name + strlen("systemd-boot")); - strupper(strrchr(v, '/') + 1); - - k = copy_file(p, v, force); - if (k < 0 && r == 0) - r = k; - } - - return r; -} - -static int install_binaries(const char *esp_path, bool force) { - struct dirent *de; - _cleanup_closedir_ DIR *d = NULL; - int r = 0; - - if (force) { - /* Don't create any of these directories when we are - * just updating. When we update we'll drop-in our - * files (unless there are newer ones already), but we - * won't create the directories for them in the first - * place. */ - r = create_dirs(esp_path); - if (r < 0) - return r; - } - - d = opendir(BOOTLIBDIR); - if (!d) - return log_error_errno(errno, "Failed to open \""BOOTLIBDIR"\": %m"); - - FOREACH_DIRENT(de, d, break) { - int k; - - if (!endswith_no_case(de->d_name, ".efi")) - continue; - - k = copy_one_file(esp_path, de->d_name, force); - if (k < 0 && r == 0) - r = k; - } - - return r; -} - -static bool same_entry(uint16_t id, const sd_id128_t uuid, const char *path) { - _cleanup_free_ char *opath = NULL; - sd_id128_t ouuid; - int r; - - r = efi_get_boot_option(id, NULL, &ouuid, &opath, NULL); - if (r < 0) - return false; - if (!sd_id128_equal(uuid, ouuid)) - return false; - if (!streq_ptr(path, opath)) - return false; - - return true; -} - -static int find_slot(sd_id128_t uuid, const char *path, uint16_t *id) { - _cleanup_free_ uint16_t *options = NULL; - int n, i; - - n = efi_get_boot_options(&options); - if (n < 0) - return n; - - /* find already existing systemd-boot entry */ - for (i = 0; i < n; i++) - if (same_entry(options[i], uuid, path)) { - *id = options[i]; - return 1; - } - - /* find free slot in the sorted BootXXXX variable list */ - for (i = 0; i < n; i++) - if (i != options[i]) { - *id = i; - return 1; - } - - /* use the next one */ - if (i == 0xffff) - return -ENOSPC; - *id = i; - return 0; -} - -static int insert_into_order(uint16_t slot, bool first) { - _cleanup_free_ uint16_t *order = NULL; - uint16_t *t; - int n, i; - - n = efi_get_boot_order(&order); - if (n <= 0) - /* no entry, add us */ - return efi_set_boot_order(&slot, 1); - - /* are we the first and only one? */ - if (n == 1 && order[0] == slot) - return 0; - - /* are we already in the boot order? */ - for (i = 0; i < n; i++) { - if (order[i] != slot) - continue; - - /* we do not require to be the first one, all is fine */ - if (!first) - return 0; - - /* move us to the first slot */ - memmove(order + 1, order, i * sizeof(uint16_t)); - order[0] = slot; - return efi_set_boot_order(order, n); - } - - /* extend array */ - t = realloc(order, (n + 1) * sizeof(uint16_t)); - if (!t) - return -ENOMEM; - order = t; - - /* add us to the top or end of the list */ - if (first) { - memmove(order + 1, order, n * sizeof(uint16_t)); - order[0] = slot; - } else - order[n] = slot; - - return efi_set_boot_order(order, n + 1); -} - -static int remove_from_order(uint16_t slot) { - _cleanup_free_ uint16_t *order = NULL; - int n, i; - - n = efi_get_boot_order(&order); - if (n <= 0) - return n; - - for (i = 0; i < n; i++) { - if (order[i] != slot) - continue; - - if (i + 1 < n) - memmove(order + i, order + i+1, (n - i) * sizeof(uint16_t)); - return efi_set_boot_order(order, n - 1); - } - - return 0; -} - -static int install_variables(const char *esp_path, - uint32_t part, uint64_t pstart, uint64_t psize, - sd_id128_t uuid, const char *path, - bool first) { - char *p; - uint16_t slot; - int r; - - if (!is_efi_boot()) { - log_warning("Not booted with EFI, skipping EFI variable setup."); - return 0; - } - - p = strjoina(esp_path, path); - if (access(p, F_OK) < 0) { - if (errno == ENOENT) - return 0; - else - return log_error_errno(errno, "Cannot access \"%s\": %m", p); - } - - r = find_slot(uuid, path, &slot); - if (r < 0) - return log_error_errno(r, - r == -ENOENT ? - "Failed to access EFI variables. Is the \"efivarfs\" filesystem mounted?" : - "Failed to determine current boot order: %m"); - - if (first || r == false) { - r = efi_add_boot_option(slot, "Systemd Boot Manager", - part, pstart, psize, - uuid, path); - if (r < 0) - return log_error_errno(r, "Failed to create EFI Boot variable entry: %m"); - - log_info("Created EFI boot entry \"Systemd Boot Manager\"."); - } - - return insert_into_order(slot, first); -} - -static int remove_boot_efi(const char *esp_path) { - char *p; - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - int r, c = 0; - - p = strjoina(esp_path, "/EFI/Boot"); - d = opendir(p); - if (!d) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open directory \"%s\": %m", p); - } - - FOREACH_DIRENT(de, d, break) { - _cleanup_close_ int fd = -1; - _cleanup_free_ char *v = NULL; - - if (!endswith_no_case(de->d_name, ".efi")) - continue; - - if (!startswith_no_case(de->d_name, "Boot")) - continue; - - fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC); - if (fd < 0) - return log_error_errno(errno, "Failed to open \"%s/%s\" for reading: %m", p, de->d_name); - - r = get_file_version(fd, &v); - if (r < 0) - return r; - if (r > 0 && startswith(v, "systemd-boot ")) { - r = unlinkat(dirfd(d), de->d_name, 0); - if (r < 0) - return log_error_errno(errno, "Failed to remove \"%s/%s\": %m", p, de->d_name); - - log_info("Removed \"%s/%s\".", p, de->d_name); - } - - c++; - } - - return c; -} - -static int rmdir_one(const char *prefix, const char *suffix) { - char *p; - - p = strjoina(prefix, "/", suffix); - if (rmdir(p) < 0) { - if (!IN_SET(errno, ENOENT, ENOTEMPTY)) - return log_error_errno(errno, "Failed to remove \"%s\": %m", p); - } else - log_info("Removed \"%s\".", p); - - return 0; -} - -static int remove_binaries(const char *esp_path) { - char *p; - int r, q; - unsigned i; - - p = strjoina(esp_path, "/EFI/systemd"); - r = rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); - - q = remove_boot_efi(esp_path); - if (q < 0 && r == 0) - r = q; - - for (i = ELEMENTSOF(efi_subdirs); i > 0; i--) { - q = rmdir_one(esp_path, efi_subdirs[i-1]); - if (q < 0 && r == 0) - r = q; - } - - return r; -} - -static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) { - uint16_t slot; - int r; - - if (!is_efi_boot()) - return 0; - - r = find_slot(uuid, path, &slot); - if (r != 1) - return 0; - - r = efi_remove_boot_option(slot); - if (r < 0) - return r; - - if (in_order) - return remove_from_order(slot); - else - return 0; -} - -static int install_loader_config(const char *esp_path) { - char *p; - char line[64]; - char *machine = NULL; - _cleanup_fclose_ FILE *f = NULL, *g = NULL; - - f = fopen("/etc/machine-id", "re"); - if (!f) - return errno == ENOENT ? 0 : -errno; - - if (fgets(line, sizeof(line), f) != NULL) { - char *s; - - s = strchr(line, '\n'); - if (s) - s[0] = '\0'; - if (strlen(line) == 32) - machine = line; - } - - if (!machine) - return -ESRCH; - - p = strjoina(esp_path, "/loader/loader.conf"); - g = fopen(p, "wxe"); - if (g) { - fprintf(g, "#timeout 3\n"); - fprintf(g, "default %s-*\n", machine); - if (ferror(g)) - return log_error_errno(EIO, "Failed to write \"%s\": %m", p); - } - - return 0; -} - -static int help(void) { - printf("%s [COMMAND] [OPTIONS...]\n" - "\n" - "Install, update or remove the systemd-boot EFI boot manager.\n\n" - " -h --help Show this help\n" - " --version Print version\n" - " --path=PATH Path to the EFI System Partition (ESP)\n" - " --no-variables Don't touch EFI variables\n" - "\n" - "Commands:\n" - " status Show status of installed systemd-boot and EFI variables\n" - " install Install systemd-boot to the ESP and EFI variables\n" - " update Update systemd-boot in the ESP and EFI variables\n" - " remove Remove systemd-boot from the ESP and EFI variables\n", - program_invocation_short_name); - - return 0; -} - -static const char *arg_path = "/boot"; -static bool arg_touch_variables = true; - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_PATH = 0x100, - ARG_VERSION, - ARG_NO_VARIABLES, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "path", required_argument, NULL, ARG_PATH }, - { "no-variables", no_argument, NULL, ARG_NO_VARIABLES }, - { NULL, 0, NULL, 0 } - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_PATH: - arg_path = optarg; - break; - - case ARG_NO_VARIABLES: - arg_touch_variables = false; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unknown option"); - } - - return 1; -} - -static void read_loader_efi_var(const char *name, char **var) { - int r; - - r = efi_get_variable_string(EFI_VENDOR_LOADER, name, var); - if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to read EFI variable %s: %m", name); -} - -static int bootctl_main(int argc, char*argv[]) { - enum action { - ACTION_STATUS, - ACTION_INSTALL, - ACTION_UPDATE, - ACTION_REMOVE - } arg_action = ACTION_STATUS; - static const struct { - const char* verb; - enum action action; - } verbs[] = { - { "status", ACTION_STATUS }, - { "install", ACTION_INSTALL }, - { "update", ACTION_UPDATE }, - { "remove", ACTION_REMOVE }, - }; - - sd_id128_t uuid = {}; - uint32_t part = 0; - uint64_t pstart = 0, psize = 0; - int r, q; - - if (argv[optind]) { - unsigned i; - - for (i = 0; i < ELEMENTSOF(verbs); i++) { - if (!streq(argv[optind], verbs[i].verb)) - continue; - arg_action = verbs[i].action; - break; - } - if (i >= ELEMENTSOF(verbs)) { - log_error("Unknown operation \"%s\"", argv[optind]); - return -EINVAL; - } - } - - if (geteuid() != 0) - return log_error_errno(EPERM, "Need to be root."); - - r = verify_esp(arg_path, &part, &pstart, &psize, &uuid); - if (r == -ENODEV && !arg_path) - log_notice("You might want to use --path= to indicate the path to your ESP, in case it is not mounted on /boot."); - if (r < 0) - return r; - - switch (arg_action) { - case ACTION_STATUS: { - _cleanup_free_ char *fw_type = NULL; - _cleanup_free_ char *fw_info = NULL; - _cleanup_free_ char *loader = NULL; - _cleanup_free_ char *loader_path = NULL; - sd_id128_t loader_part_uuid = {}; - - if (is_efi_boot()) { - read_loader_efi_var("LoaderFirmwareType", &fw_type); - read_loader_efi_var("LoaderFirmwareInfo", &fw_info); - read_loader_efi_var("LoaderInfo", &loader); - read_loader_efi_var("LoaderImageIdentifier", &loader_path); - if (loader_path) - efi_tilt_backslashes(loader_path); - r = efi_loader_get_device_part_uuid(&loader_part_uuid); - if (r < 0 && r == -ENOENT) - log_warning_errno(r, "Failed to read EFI variable LoaderDevicePartUUID: %m"); - - printf("System:\n"); - printf(" Firmware: %s (%s)\n", strna(fw_type), strna(fw_info)); - - r = is_efi_secure_boot(); - if (r < 0) - log_warning_errno(r, "Failed to query secure boot status: %m"); - else - printf(" Secure Boot: %s\n", r ? "enabled" : "disabled"); - - r = is_efi_secure_boot_setup_mode(); - if (r < 0) - log_warning_errno(r, "Failed to query secure boot mode: %m"); - else - printf(" Setup Mode: %s\n", r ? "setup" : "user"); - printf("\n"); - - printf("Loader:\n"); - printf(" Product: %s\n", strna(loader)); - if (!sd_id128_equal(loader_part_uuid, SD_ID128_NULL)) - printf(" Partition: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", - SD_ID128_FORMAT_VAL(loader_part_uuid)); - else - printf(" Partition: n/a\n"); - printf(" File: %s%s\n", special_glyph(TREE_RIGHT), strna(loader_path)); - printf("\n"); - } else - printf("System:\n Not booted with EFI\n"); - - r = status_binaries(arg_path, uuid); - if (r < 0) - return r; - - if (arg_touch_variables) - r = status_variables(); - break; - } - - case ACTION_INSTALL: - case ACTION_UPDATE: - umask(0002); - - r = install_binaries(arg_path, arg_action == ACTION_INSTALL); - if (r < 0) - return r; - - if (arg_action == ACTION_INSTALL) { - r = install_loader_config(arg_path); - if (r < 0) - return r; - } - - if (arg_touch_variables) - r = install_variables(arg_path, - part, pstart, psize, uuid, - "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", - arg_action == ACTION_INSTALL); - break; - - case ACTION_REMOVE: - r = remove_binaries(arg_path); - - if (arg_touch_variables) { - q = remove_variables(uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", true); - if (q < 0 && r == 0) - r = q; - } - break; - } - - return r; -} - -int main(int argc, char *argv[]) { - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = bootctl_main(argc, argv); - - finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/boot/efi/.gitignore b/src/boot/efi/.gitignore deleted file mode 100644 index e193acbe12..0000000000 --- a/src/boot/efi/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/systemd_boot.so -/stub.so diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c deleted file mode 100644 index 30c1ead1aa..0000000000 --- a/src/boot/efi/boot.c +++ /dev/null @@ -1,1857 +0,0 @@ -/* - * This program 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. - * - * This program 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. - * - * Copyright (C) 2012-2015 Kay Sievers - * Copyright (C) 2012-2015 Harald Hoyer - */ - -#include -#include - -#include "console.h" -#include "disk.h" -#include "graphics.h" -#include "linux.h" -#include "pefile.h" -#include "util.h" -#include "measure.h" - -#ifndef EFI_OS_INDICATIONS_BOOT_TO_FW_UI -#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001ULL -#endif - -/* magic string to find in the binary image */ -static const char __attribute__((used)) magic[] = "#### LoaderInfo: systemd-boot " VERSION " ####"; - -static const EFI_GUID global_guid = EFI_GLOBAL_VARIABLE; - -enum loader_type { - LOADER_UNDEFINED, - LOADER_EFI, - LOADER_LINUX -}; - -typedef struct { - CHAR16 *file; - CHAR16 *title_show; - CHAR16 *title; - CHAR16 *version; - CHAR16 *machine_id; - EFI_HANDLE *device; - enum loader_type type; - CHAR16 *loader; - CHAR16 *options; - CHAR16 key; - EFI_STATUS (*call)(VOID); - BOOLEAN no_autoselect; - BOOLEAN non_unique; -} ConfigEntry; - -typedef struct { - ConfigEntry **entries; - UINTN entry_count; - INTN idx_default; - INTN idx_default_efivar; - UINTN timeout_sec; - UINTN timeout_sec_config; - INTN timeout_sec_efivar; - CHAR16 *entry_default_pattern; - CHAR16 *entry_oneshot; - CHAR16 *options_edit; - BOOLEAN no_editor; -} Config; - -static VOID cursor_left(UINTN *cursor, UINTN *first) { - if ((*cursor) > 0) - (*cursor)--; - else if ((*first) > 0) - (*first)--; -} - -static VOID cursor_right(UINTN *cursor, UINTN *first, UINTN x_max, UINTN len) { - if ((*cursor)+1 < x_max) - (*cursor)++; - else if ((*first) + (*cursor) < len) - (*first)++; -} - -static BOOLEAN line_edit(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN y_pos) { - CHAR16 *line; - UINTN size; - UINTN len; - UINTN first; - CHAR16 *print; - UINTN cursor; - UINTN clear; - BOOLEAN exit; - BOOLEAN enter; - - if (!line_in) - line_in = L""; - size = StrLen(line_in) + 1024; - line = AllocatePool(size * sizeof(CHAR16)); - StrCpy(line, line_in); - len = StrLen(line); - print = AllocatePool((x_max+1) * sizeof(CHAR16)); - - uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, TRUE); - - first = 0; - cursor = 0; - clear = 0; - enter = FALSE; - exit = FALSE; - while (!exit) { - EFI_STATUS err; - UINT64 key; - UINTN i; - - i = len - first; - if (i >= x_max-1) - i = x_max-1; - CopyMem(print, line + first, i * sizeof(CHAR16)); - while (clear > 0 && i < x_max-1) { - clear--; - print[i++] = ' '; - } - print[i] = '\0'; - - uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_pos); - uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, print); - uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos); - - err = console_key_read(&key, TRUE); - if (EFI_ERROR(err)) - continue; - - switch (key) { - case KEYPRESS(0, SCAN_ESC, 0): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'c'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'g'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('c')): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('g')): - exit = TRUE; - break; - - case KEYPRESS(0, SCAN_HOME, 0): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'a'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('a')): - /* beginning-of-line */ - cursor = 0; - first = 0; - continue; - - case KEYPRESS(0, SCAN_END, 0): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'e'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('e')): - /* end-of-line */ - cursor = len - first; - if (cursor+1 >= x_max) { - cursor = x_max-1; - first = len - (x_max-1); - } - continue; - - case KEYPRESS(0, SCAN_DOWN, 0): - case KEYPRESS(EFI_ALT_PRESSED, 0, 'f'): - case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_RIGHT, 0): - /* forward-word */ - while (line[first + cursor] && line[first + cursor] == ' ') - cursor_right(&cursor, &first, x_max, len); - while (line[first + cursor] && line[first + cursor] != ' ') - cursor_right(&cursor, &first, x_max, len); - uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos); - continue; - - case KEYPRESS(0, SCAN_UP, 0): - case KEYPRESS(EFI_ALT_PRESSED, 0, 'b'): - case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_LEFT, 0): - /* backward-word */ - if ((first + cursor) > 0 && line[first + cursor-1] == ' ') { - cursor_left(&cursor, &first); - while ((first + cursor) > 0 && line[first + cursor] == ' ') - cursor_left(&cursor, &first); - } - while ((first + cursor) > 0 && line[first + cursor-1] != ' ') - cursor_left(&cursor, &first); - uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos); - continue; - - case KEYPRESS(0, SCAN_RIGHT, 0): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'f'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('f')): - /* forward-char */ - if (first + cursor == len) - continue; - cursor_right(&cursor, &first, x_max, len); - uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos); - continue; - - case KEYPRESS(0, SCAN_LEFT, 0): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'b'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('b')): - /* backward-char */ - cursor_left(&cursor, &first); - uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos); - continue; - - case KEYPRESS(EFI_ALT_PRESSED, 0, 'd'): - /* kill-word */ - clear = 0; - for (i = first + cursor; i < len && line[i] == ' '; i++) - clear++; - for (; i < len && line[i] != ' '; i++) - clear++; - - for (i = first + cursor; i + clear < len; i++) - line[i] = line[i + clear]; - len -= clear; - line[len] = '\0'; - continue; - - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'w'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('w')): - case KEYPRESS(EFI_ALT_PRESSED, 0, CHAR_BACKSPACE): - /* backward-kill-word */ - clear = 0; - if ((first + cursor) > 0 && line[first + cursor-1] == ' ') { - cursor_left(&cursor, &first); - clear++; - while ((first + cursor) > 0 && line[first + cursor] == ' ') { - cursor_left(&cursor, &first); - clear++; - } - } - while ((first + cursor) > 0 && line[first + cursor-1] != ' ') { - cursor_left(&cursor, &first); - clear++; - } - uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos); - - for (i = first + cursor; i + clear < len; i++) - line[i] = line[i + clear]; - len -= clear; - line[len] = '\0'; - continue; - - case KEYPRESS(0, SCAN_DELETE, 0): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'd'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('d')): - if (len == 0) - continue; - if (first + cursor == len) - continue; - for (i = first + cursor; i < len; i++) - line[i] = line[i+1]; - clear = 1; - len--; - continue; - - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'k'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('k')): - /* kill-line */ - line[first + cursor] = '\0'; - clear = len - (first + cursor); - len = first + cursor; - continue; - - case KEYPRESS(0, 0, CHAR_LINEFEED): - case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN): - if (StrCmp(line, line_in) != 0) { - *line_out = line; - line = NULL; - } - enter = TRUE; - exit = TRUE; - break; - - case KEYPRESS(0, 0, CHAR_BACKSPACE): - if (len == 0) - continue; - if (first == 0 && cursor == 0) - continue; - for (i = first + cursor-1; i < len; i++) - line[i] = line[i+1]; - clear = 1; - len--; - if (cursor > 0) - cursor--; - if (cursor > 0 || first == 0) - continue; - /* show full line if it fits */ - if (len < x_max) { - cursor = first; - first = 0; - continue; - } - /* jump left to see what we delete */ - if (first > 10) { - first -= 10; - cursor = 10; - } else { - cursor = first; - first = 0; - } - continue; - - case KEYPRESS(0, 0, ' ') ... KEYPRESS(0, 0, '~'): - case KEYPRESS(0, 0, 0x80) ... KEYPRESS(0, 0, 0xffff): - if (len+1 == size) - continue; - for (i = len; i > first + cursor; i--) - line[i] = line[i-1]; - line[first + cursor] = KEYCHAR(key); - len++; - line[len] = '\0'; - if (cursor+1 < x_max) - cursor++; - else if (first + cursor < len) - first++; - continue; - } - } - - uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE); - FreePool(print); - FreePool(line); - return enter; -} - -static UINTN entry_lookup_key(Config *config, UINTN start, CHAR16 key) { - UINTN i; - - if (key == 0) - return -1; - - /* select entry by number key */ - if (key >= '1' && key <= '9') { - i = key - '0'; - if (i > config->entry_count) - i = config->entry_count; - return i-1; - } - - /* find matching key in config entries */ - for (i = start; i < config->entry_count; i++) - if (config->entries[i]->key == key) - return i; - - for (i = 0; i < start; i++) - if (config->entries[i]->key == key) - return i; - - return -1; -} - -static VOID print_status(Config *config, CHAR16 *loaded_image_path) { - UINT64 key; - UINTN i; - CHAR16 *s; - CHAR8 *b; - UINTN x; - UINTN y; - UINTN size; - - uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); - uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); - - Print(L"systemd-boot version: " VERSION "\n"); - Print(L"architecture: " EFI_MACHINE_TYPE_NAME "\n"); - Print(L"loaded image: %s\n", loaded_image_path); - Print(L"UEFI specification: %d.%02d\n", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff); - Print(L"firmware vendor: %s\n", ST->FirmwareVendor); - Print(L"firmware version: %d.%02d\n", ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); - - if (uefi_call_wrapper(ST->ConOut->QueryMode, 4, ST->ConOut, ST->ConOut->Mode->Mode, &x, &y) == EFI_SUCCESS) - Print(L"console size: %d x %d\n", x, y); - - if (efivar_get_raw(&global_guid, L"SecureBoot", &b, &size) == EFI_SUCCESS) { - Print(L"SecureBoot: %s\n", yes_no(*b > 0)); - FreePool(b); - } - - if (efivar_get_raw(&global_guid, L"SetupMode", &b, &size) == EFI_SUCCESS) { - Print(L"SetupMode: %s\n", *b > 0 ? L"setup" : L"user"); - FreePool(b); - } - - if (efivar_get_raw(&global_guid, L"OsIndicationsSupported", &b, &size) == EFI_SUCCESS) { - Print(L"OsIndicationsSupported: %d\n", (UINT64)*b); - FreePool(b); - } - Print(L"\n"); - - Print(L"timeout: %d\n", config->timeout_sec); - if (config->timeout_sec_efivar >= 0) - Print(L"timeout (EFI var): %d\n", config->timeout_sec_efivar); - Print(L"timeout (config): %d\n", config->timeout_sec_config); - if (config->entry_default_pattern) - Print(L"default pattern: '%s'\n", config->entry_default_pattern); - Print(L"editor: %s\n", yes_no(!config->no_editor)); - Print(L"\n"); - - Print(L"config entry count: %d\n", config->entry_count); - Print(L"entry selected idx: %d\n", config->idx_default); - if (config->idx_default_efivar >= 0) - Print(L"entry EFI var idx: %d\n", config->idx_default_efivar); - Print(L"\n"); - - if (efivar_get_int(L"LoaderConfigTimeout", &i) == EFI_SUCCESS) - Print(L"LoaderConfigTimeout: %d\n", i); - if (config->entry_oneshot) - Print(L"LoaderEntryOneShot: %s\n", config->entry_oneshot); - if (efivar_get(L"LoaderDevicePartUUID", &s) == EFI_SUCCESS) { - Print(L"LoaderDevicePartUUID: %s\n", s); - FreePool(s); - } - if (efivar_get(L"LoaderEntryDefault", &s) == EFI_SUCCESS) { - Print(L"LoaderEntryDefault: %s\n", s); - FreePool(s); - } - - Print(L"\n--- press key ---\n\n"); - console_key_read(&key, TRUE); - - for (i = 0; i < config->entry_count; i++) { - ConfigEntry *entry; - - if (key == KEYPRESS(0, SCAN_ESC, 0) || key == KEYPRESS(0, 0, 'q')) - break; - - entry = config->entries[i]; - Print(L"config entry: %d/%d\n", i+1, config->entry_count); - if (entry->file) - Print(L"file '%s'\n", entry->file); - Print(L"title show '%s'\n", entry->title_show); - if (entry->title) - Print(L"title '%s'\n", entry->title); - if (entry->version) - Print(L"version '%s'\n", entry->version); - if (entry->machine_id) - Print(L"machine-id '%s'\n", entry->machine_id); - if (entry->device) { - EFI_DEVICE_PATH *device_path; - CHAR16 *str; - - device_path = DevicePathFromHandle(entry->device); - if (device_path) { - str = DevicePathToStr(device_path); - Print(L"device handle '%s'\n", str); - FreePool(str); - } - } - if (entry->loader) - Print(L"loader '%s'\n", entry->loader); - if (entry->options) - Print(L"options '%s'\n", entry->options); - Print(L"auto-select %s\n", yes_no(!entry->no_autoselect)); - if (entry->call) - Print(L"internal call yes\n"); - - Print(L"\n--- press key ---\n\n"); - console_key_read(&key, TRUE); - } - - uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); -} - -static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *loaded_image_path) { - EFI_STATUS err; - UINTN visible_max; - UINTN idx_highlight; - UINTN idx_highlight_prev; - UINTN idx_first; - UINTN idx_last; - BOOLEAN refresh; - BOOLEAN highlight; - UINTN i; - UINTN line_width; - CHAR16 **lines; - UINTN x_start; - UINTN y_start; - UINTN x_max; - UINTN y_max; - CHAR16 *status; - CHAR16 *clearline; - INTN timeout_remain; - INT16 idx; - BOOLEAN exit = FALSE; - BOOLEAN run = TRUE; - BOOLEAN wait = FALSE; - - graphics_mode(FALSE); - uefi_call_wrapper(ST->ConIn->Reset, 2, ST->ConIn, FALSE); - uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE); - uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); - - /* draw a single character to make ClearScreen work on some firmware */ - uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L" "); - uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); - - err = uefi_call_wrapper(ST->ConOut->QueryMode, 4, ST->ConOut, ST->ConOut->Mode->Mode, &x_max, &y_max); - if (EFI_ERROR(err)) { - x_max = 80; - y_max = 25; - } - - /* we check 10 times per second for a keystroke */ - if (config->timeout_sec > 0) - timeout_remain = config->timeout_sec * 10; - else - timeout_remain = -1; - - idx_highlight = config->idx_default; - idx_highlight_prev = 0; - - visible_max = y_max - 2; - - if ((UINTN)config->idx_default >= visible_max) - idx_first = config->idx_default-1; - else - idx_first = 0; - - idx_last = idx_first + visible_max-1; - - refresh = TRUE; - highlight = FALSE; - - /* length of the longest entry */ - line_width = 5; - for (i = 0; i < config->entry_count; i++) { - UINTN entry_len; - - entry_len = StrLen(config->entries[i]->title_show); - if (line_width < entry_len) - line_width = entry_len; - } - if (line_width > x_max-6) - line_width = x_max-6; - - /* offsets to center the entries on the screen */ - x_start = (x_max - (line_width)) / 2; - if (config->entry_count < visible_max) - y_start = ((visible_max - config->entry_count) / 2) + 1; - else - y_start = 0; - - /* menu entries title lines */ - lines = AllocatePool(sizeof(CHAR16 *) * config->entry_count); - for (i = 0; i < config->entry_count; i++) { - UINTN j, k; - - lines[i] = AllocatePool(((x_max+1) * sizeof(CHAR16))); - for (j = 0; j < x_start; j++) - lines[i][j] = ' '; - - for (k = 0; config->entries[i]->title_show[k] != '\0' && j < x_max; j++, k++) - lines[i][j] = config->entries[i]->title_show[k]; - - for (; j < x_max; j++) - lines[i][j] = ' '; - lines[i][x_max] = '\0'; - } - - status = NULL; - clearline = AllocatePool((x_max+1) * sizeof(CHAR16)); - for (i = 0; i < x_max; i++) - clearline[i] = ' '; - clearline[i] = 0; - - while (!exit) { - UINT64 key; - - if (refresh) { - for (i = 0; i < config->entry_count; i++) { - if (i < idx_first || i > idx_last) - continue; - uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_start + i - idx_first); - if (i == idx_highlight) - uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, - EFI_BLACK|EFI_BACKGROUND_LIGHTGRAY); - else - uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, - EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); - uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[i]); - if ((INTN)i == config->idx_default_efivar) { - uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, x_start-3, y_start + i - idx_first); - uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"=>"); - } - } - refresh = FALSE; - } else if (highlight) { - uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_start + idx_highlight_prev - idx_first); - uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); - uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[idx_highlight_prev]); - if ((INTN)idx_highlight_prev == config->idx_default_efivar) { - uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, x_start-3, y_start + idx_highlight_prev - idx_first); - uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"=>"); - } - - uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_start + idx_highlight - idx_first); - uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_BLACK|EFI_BACKGROUND_LIGHTGRAY); - uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[idx_highlight]); - if ((INTN)idx_highlight == config->idx_default_efivar) { - uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, x_start-3, y_start + idx_highlight - idx_first); - uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"=>"); - } - highlight = FALSE; - } - - if (timeout_remain > 0) { - FreePool(status); - status = PoolPrint(L"Boot in %d sec.", (timeout_remain + 5) / 10); - } - - /* print status at last line of screen */ - if (status) { - UINTN len; - UINTN x; - - /* center line */ - len = StrLen(status); - if (len < x_max) - x = (x_max - len) / 2; - else - x = 0; - uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); - uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1); - uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline + (x_max - x)); - uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, status); - uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1 + x + len); - } - - err = console_key_read(&key, wait); - if (EFI_ERROR(err)) { - /* timeout reached */ - if (timeout_remain == 0) { - exit = TRUE; - break; - } - - /* sleep and update status */ - if (timeout_remain > 0) { - uefi_call_wrapper(BS->Stall, 1, 100 * 1000); - timeout_remain--; - continue; - } - - /* timeout disabled, wait for next key */ - wait = TRUE; - continue; - } - - timeout_remain = -1; - - /* clear status after keystroke */ - if (status) { - FreePool(status); - status = NULL; - uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); - uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1); - uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1); - } - - idx_highlight_prev = idx_highlight; - - switch (key) { - case KEYPRESS(0, SCAN_UP, 0): - case KEYPRESS(0, 0, 'k'): - if (idx_highlight > 0) - idx_highlight--; - break; - - case KEYPRESS(0, SCAN_DOWN, 0): - case KEYPRESS(0, 0, 'j'): - if (idx_highlight < config->entry_count-1) - idx_highlight++; - break; - - case KEYPRESS(0, SCAN_HOME, 0): - case KEYPRESS(EFI_ALT_PRESSED, 0, '<'): - if (idx_highlight > 0) { - refresh = TRUE; - idx_highlight = 0; - } - break; - - case KEYPRESS(0, SCAN_END, 0): - case KEYPRESS(EFI_ALT_PRESSED, 0, '>'): - if (idx_highlight < config->entry_count-1) { - refresh = TRUE; - idx_highlight = config->entry_count-1; - } - break; - - case KEYPRESS(0, SCAN_PAGE_UP, 0): - if (idx_highlight > visible_max) - idx_highlight -= visible_max; - else - idx_highlight = 0; - break; - - case KEYPRESS(0, SCAN_PAGE_DOWN, 0): - idx_highlight += visible_max; - if (idx_highlight > config->entry_count-1) - idx_highlight = config->entry_count-1; - break; - - case KEYPRESS(0, 0, CHAR_LINEFEED): - case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN): - exit = TRUE; - break; - - case KEYPRESS(0, SCAN_F1, 0): - case KEYPRESS(0, 0, 'h'): - case KEYPRESS(0, 0, '?'): - status = StrDuplicate(L"(d)efault, (t/T)timeout, (e)dit, (v)ersion (Q)uit (P)rint (h)elp"); - break; - - case KEYPRESS(0, 0, 'Q'): - exit = TRUE; - run = FALSE; - break; - - case KEYPRESS(0, 0, 'd'): - if (config->idx_default_efivar != (INTN)idx_highlight) { - /* store the selected entry in a persistent EFI variable */ - efivar_set(L"LoaderEntryDefault", config->entries[idx_highlight]->file, TRUE); - config->idx_default_efivar = idx_highlight; - status = StrDuplicate(L"Default boot entry selected."); - } else { - /* clear the default entry EFI variable */ - efivar_set(L"LoaderEntryDefault", NULL, TRUE); - config->idx_default_efivar = -1; - status = StrDuplicate(L"Default boot entry cleared."); - } - refresh = TRUE; - break; - - case KEYPRESS(0, 0, '-'): - case KEYPRESS(0, 0, 'T'): - if (config->timeout_sec_efivar > 0) { - config->timeout_sec_efivar--; - efivar_set_int(L"LoaderConfigTimeout", config->timeout_sec_efivar, TRUE); - if (config->timeout_sec_efivar > 0) - status = PoolPrint(L"Menu timeout set to %d sec.", config->timeout_sec_efivar); - else - status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu."); - } else if (config->timeout_sec_efivar <= 0){ - config->timeout_sec_efivar = -1; - efivar_set(L"LoaderConfigTimeout", NULL, TRUE); - if (config->timeout_sec_config > 0) - status = PoolPrint(L"Menu timeout of %d sec is defined by configuration file.", - config->timeout_sec_config); - else - status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu."); - } - break; - - case KEYPRESS(0, 0, '+'): - case KEYPRESS(0, 0, 't'): - if (config->timeout_sec_efivar == -1 && config->timeout_sec_config == 0) - config->timeout_sec_efivar++; - config->timeout_sec_efivar++; - efivar_set_int(L"LoaderConfigTimeout", config->timeout_sec_efivar, TRUE); - if (config->timeout_sec_efivar > 0) - status = PoolPrint(L"Menu timeout set to %d sec.", - config->timeout_sec_efivar); - else - status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu."); - break; - - case KEYPRESS(0, 0, 'e'): - /* only the options of configured entries can be edited */ - if (config->no_editor || config->entries[idx_highlight]->type == LOADER_UNDEFINED) - break; - uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); - uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1); - uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1); - if (line_edit(config->entries[idx_highlight]->options, &config->options_edit, x_max-1, y_max-1)) - exit = TRUE; - uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1); - uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1); - break; - - case KEYPRESS(0, 0, 'v'): - status = PoolPrint(L"systemd-boot " VERSION " (" EFI_MACHINE_TYPE_NAME "), UEFI Specification %d.%02d, Vendor %s %d.%02d", - ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff, - ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); - break; - - case KEYPRESS(0, 0, 'P'): - print_status(config, loaded_image_path); - refresh = TRUE; - break; - - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'l'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('l')): - refresh = TRUE; - break; - - default: - /* jump with a hotkey directly to a matching entry */ - idx = entry_lookup_key(config, idx_highlight+1, KEYCHAR(key)); - if (idx < 0) - break; - idx_highlight = idx; - refresh = TRUE; - } - - if (idx_highlight > idx_last) { - idx_last = idx_highlight; - idx_first = 1 + idx_highlight - visible_max; - refresh = TRUE; - } else if (idx_highlight < idx_first) { - idx_first = idx_highlight; - idx_last = idx_highlight + visible_max-1; - refresh = TRUE; - } - - if (!refresh && idx_highlight != idx_highlight_prev) - highlight = TRUE; - } - - *chosen_entry = config->entries[idx_highlight]; - - for (i = 0; i < config->entry_count; i++) - FreePool(lines[i]); - FreePool(lines); - FreePool(clearline); - - uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_WHITE|EFI_BACKGROUND_BLACK); - uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); - return run; -} - -static VOID config_add_entry(Config *config, ConfigEntry *entry) { - if ((config->entry_count & 15) == 0) { - UINTN i; - - i = config->entry_count + 16; - if (config->entry_count == 0) - config->entries = AllocatePool(sizeof(VOID *) * i); - else - config->entries = ReallocatePool(config->entries, - sizeof(VOID *) * config->entry_count, sizeof(VOID *) * i); - } - config->entries[config->entry_count++] = entry; -} - -static VOID config_entry_free(ConfigEntry *entry) { - FreePool(entry->title_show); - FreePool(entry->title); - FreePool(entry->machine_id); - FreePool(entry->loader); - FreePool(entry->options); -} - -static BOOLEAN is_digit(CHAR16 c) { - return (c >= '0') && (c <= '9'); -} - -static UINTN c_order(CHAR16 c) { - if (c == '\0') - return 0; - if (is_digit(c)) - return 0; - else if ((c >= 'a') && (c <= 'z')) - return c; - else - return c + 0x10000; -} - -static INTN str_verscmp(CHAR16 *s1, CHAR16 *s2) { - CHAR16 *os1 = s1; - CHAR16 *os2 = s2; - - while (*s1 || *s2) { - INTN first; - - while ((*s1 && !is_digit(*s1)) || (*s2 && !is_digit(*s2))) { - INTN order; - - order = c_order(*s1) - c_order(*s2); - if (order) - return order; - s1++; - s2++; - } - - while (*s1 == '0') - s1++; - while (*s2 == '0') - s2++; - - first = 0; - while (is_digit(*s1) && is_digit(*s2)) { - if (first == 0) - first = *s1 - *s2; - s1++; - s2++; - } - - if (is_digit(*s1)) - return 1; - if (is_digit(*s2)) - return -1; - - if (first) - return first; - } - - return StrCmp(os1, os2); -} - -static CHAR8 *line_get_key_value(CHAR8 *content, CHAR8 *sep, UINTN *pos, CHAR8 **key_ret, CHAR8 **value_ret) { - CHAR8 *line; - UINTN linelen; - CHAR8 *value; - -skip: - line = content + *pos; - if (*line == '\0') - return NULL; - - linelen = 0; - while (line[linelen] && !strchra((CHAR8 *)"\n\r", line[linelen])) - linelen++; - - /* move pos to next line */ - *pos += linelen; - if (content[*pos]) - (*pos)++; - - /* empty line */ - if (linelen == 0) - goto skip; - - /* terminate line */ - line[linelen] = '\0'; - - /* remove leading whitespace */ - while (strchra((CHAR8 *)" \t", *line)) { - line++; - linelen--; - } - - /* remove trailing whitespace */ - while (linelen > 0 && strchra(sep, line[linelen-1])) - linelen--; - line[linelen] = '\0'; - - if (*line == '#') - goto skip; - - /* split key/value */ - value = line; - while (*value && !strchra(sep, *value)) - value++; - if (*value == '\0') - goto skip; - *value = '\0'; - value++; - while (*value && strchra(sep, *value)) - value++; - - /* unquote */ - if (value[0] == '\"' && line[linelen-1] == '\"') { - value++; - line[linelen-1] = '\0'; - } - - *key_ret = line; - *value_ret = value; - return line; -} - -static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) { - CHAR8 *line; - UINTN pos = 0; - CHAR8 *key, *value; - - line = content; - while ((line = line_get_key_value(content, (CHAR8 *)" \t", &pos, &key, &value))) { - if (strcmpa((CHAR8 *)"timeout", key) == 0) { - CHAR16 *s; - - s = stra_to_str(value); - config->timeout_sec_config = Atoi(s); - config->timeout_sec = config->timeout_sec_config; - FreePool(s); - continue; - } - - if (strcmpa((CHAR8 *)"default", key) == 0) { - FreePool(config->entry_default_pattern); - config->entry_default_pattern = stra_to_str(value); - StrLwr(config->entry_default_pattern); - continue; - } - - if (strcmpa((CHAR8 *)"editor", key) == 0) { - BOOLEAN on; - - if (EFI_ERROR(parse_boolean(value, &on))) - continue; - config->no_editor = !on; - } - } -} - -static VOID config_entry_add_from_file(Config *config, EFI_HANDLE *device, CHAR16 *file, CHAR8 *content, CHAR16 *loaded_image_path) { - ConfigEntry *entry; - CHAR8 *line; - UINTN pos = 0; - CHAR8 *key, *value; - UINTN len; - CHAR16 *initrd = NULL; - - entry = AllocateZeroPool(sizeof(ConfigEntry)); - - line = content; - while ((line = line_get_key_value(content, (CHAR8 *)" \t", &pos, &key, &value))) { - if (strcmpa((CHAR8 *)"title", key) == 0) { - FreePool(entry->title); - entry->title = stra_to_str(value); - continue; - } - - if (strcmpa((CHAR8 *)"version", key) == 0) { - FreePool(entry->version); - entry->version = stra_to_str(value); - continue; - } - - if (strcmpa((CHAR8 *)"machine-id", key) == 0) { - FreePool(entry->machine_id); - entry->machine_id = stra_to_str(value); - continue; - } - - if (strcmpa((CHAR8 *)"linux", key) == 0) { - FreePool(entry->loader); - entry->type = LOADER_LINUX; - entry->loader = stra_to_path(value); - entry->key = 'l'; - continue; - } - - if (strcmpa((CHAR8 *)"efi", key) == 0) { - entry->type = LOADER_EFI; - FreePool(entry->loader); - entry->loader = stra_to_path(value); - - /* do not add an entry for ourselves */ - if (StriCmp(entry->loader, loaded_image_path) == 0) { - entry->type = LOADER_UNDEFINED; - break; - } - continue; - } - - if (strcmpa((CHAR8 *)"architecture", key) == 0) { - /* do not add an entry for an EFI image of architecture not matching with that of the image */ - if (strcmpa((CHAR8 *)EFI_MACHINE_TYPE_NAME, value) != 0) { - entry->type = LOADER_UNDEFINED; - break; - } - continue; - } - - if (strcmpa((CHAR8 *)"initrd", key) == 0) { - CHAR16 *new; - - new = stra_to_path(value); - if (initrd) { - CHAR16 *s; - - s = PoolPrint(L"%s initrd=%s", initrd, new); - FreePool(initrd); - initrd = s; - } else - initrd = PoolPrint(L"initrd=%s", new); - FreePool(new); - continue; - } - - if (strcmpa((CHAR8 *)"options", key) == 0) { - CHAR16 *new; - - new = stra_to_str(value); - if (entry->options) { - CHAR16 *s; - - s = PoolPrint(L"%s %s", entry->options, new); - FreePool(entry->options); - entry->options = s; - } else { - entry->options = new; - new = NULL; - } - FreePool(new); - continue; - } - } - - if (entry->type == LOADER_UNDEFINED) { - config_entry_free(entry); - FreePool(initrd); - FreePool(entry); - return; - } - - /* add initrd= to options */ - if (entry->type == LOADER_LINUX && initrd) { - if (entry->options) { - CHAR16 *s; - - s = PoolPrint(L"%s %s", initrd, entry->options); - FreePool(entry->options); - entry->options = s; - } else { - entry->options = initrd; - initrd = NULL; - } - } - FreePool(initrd); - - entry->device = device; - entry->file = StrDuplicate(file); - len = StrLen(entry->file); - /* remove ".conf" */ - if (len > 5) - entry->file[len - 5] = '\0'; - StrLwr(entry->file); - - config_add_entry(config, entry); -} - -static VOID config_load_defaults(Config *config, EFI_FILE *root_dir) { - CHAR8 *content = NULL; - UINTN sec; - UINTN len; - EFI_STATUS err; - - len = file_read(root_dir, L"\\loader\\loader.conf", 0, 0, &content); - if (len > 0) - config_defaults_load_from_file(config, content); - FreePool(content); - - err = efivar_get_int(L"LoaderConfigTimeout", &sec); - if (!EFI_ERROR(err)) { - config->timeout_sec_efivar = sec; - config->timeout_sec = sec; - } else - config->timeout_sec_efivar = -1; -} - -static VOID config_load_entries(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path) { - EFI_FILE_HANDLE entries_dir; - EFI_STATUS err; - - err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &entries_dir, L"\\loader\\entries", EFI_FILE_MODE_READ, 0ULL); - if (!EFI_ERROR(err)) { - for (;;) { - CHAR16 buf[256]; - UINTN bufsize; - EFI_FILE_INFO *f; - CHAR8 *content = NULL; - UINTN len; - - bufsize = sizeof(buf); - err = uefi_call_wrapper(entries_dir->Read, 3, entries_dir, &bufsize, buf); - if (bufsize == 0 || EFI_ERROR(err)) - break; - - f = (EFI_FILE_INFO *) buf; - if (f->FileName[0] == '.') - continue; - if (f->Attribute & EFI_FILE_DIRECTORY) - continue; - - len = StrLen(f->FileName); - if (len < 6) - continue; - if (StriCmp(f->FileName + len - 5, L".conf") != 0) - continue; - if (StrnCmp(f->FileName, L"auto-", 5) == 0) - continue; - - len = file_read(entries_dir, f->FileName, 0, 0, &content); - if (len > 0) - config_entry_add_from_file(config, device, f->FileName, content, loaded_image_path); - FreePool(content); - } - uefi_call_wrapper(entries_dir->Close, 1, entries_dir); - } -} - -static VOID config_sort_entries(Config *config) { - UINTN i; - - for (i = 1; i < config->entry_count; i++) { - BOOLEAN more; - UINTN k; - - more = FALSE; - for (k = 0; k < config->entry_count - i; k++) { - ConfigEntry *entry; - - if (str_verscmp(config->entries[k]->file, config->entries[k+1]->file) <= 0) - continue; - entry = config->entries[k]; - config->entries[k] = config->entries[k+1]; - config->entries[k+1] = entry; - more = TRUE; - } - if (!more) - break; - } -} - -static VOID config_default_entry_select(Config *config) { - CHAR16 *var; - EFI_STATUS err; - UINTN i; - - /* - * The EFI variable to specify a boot entry for the next, and only the - * next reboot. The variable is always cleared directly after it is read. - */ - err = efivar_get(L"LoaderEntryOneShot", &var); - if (!EFI_ERROR(err)) { - BOOLEAN found = FALSE; - - for (i = 0; i < config->entry_count; i++) { - if (StrCmp(config->entries[i]->file, var) == 0) { - config->idx_default = i; - found = TRUE; - break; - } - } - - config->entry_oneshot = StrDuplicate(var); - efivar_set(L"LoaderEntryOneShot", NULL, TRUE); - FreePool(var); - if (found) - return; - } - - /* - * The EFI variable to select the default boot entry overrides the - * configured pattern. The variable can be set and cleared by pressing - * the 'd' key in the loader selection menu, the entry is marked with - * an '*'. - */ - err = efivar_get(L"LoaderEntryDefault", &var); - if (!EFI_ERROR(err)) { - BOOLEAN found = FALSE; - - for (i = 0; i < config->entry_count; i++) { - if (StrCmp(config->entries[i]->file, var) == 0) { - config->idx_default = i; - config->idx_default_efivar = i; - found = TRUE; - break; - } - } - FreePool(var); - if (found) - return; - } - config->idx_default_efivar = -1; - - if (config->entry_count == 0) - return; - - /* - * Match the pattern from the end of the list to the start, find last - * entry (largest number) matching the given pattern. - */ - if (config->entry_default_pattern) { - i = config->entry_count; - while (i--) { - if (config->entries[i]->no_autoselect) - continue; - if (MetaiMatch(config->entries[i]->file, config->entry_default_pattern)) { - config->idx_default = i; - return; - } - } - } - - /* select the last suitable entry */ - i = config->entry_count; - while (i--) { - if (config->entries[i]->no_autoselect) - continue; - config->idx_default = i; - return; - } - - /* no entry found */ - config->idx_default = -1; -} - -/* generate a unique title, avoiding non-distinguishable menu entries */ -static VOID config_title_generate(Config *config) { - UINTN i, k; - BOOLEAN unique; - - /* set title */ - for (i = 0; i < config->entry_count; i++) { - CHAR16 *title; - - FreePool(config->entries[i]->title_show); - title = config->entries[i]->title; - if (!title) - title = config->entries[i]->file; - config->entries[i]->title_show = StrDuplicate(title); - } - - unique = TRUE; - for (i = 0; i < config->entry_count; i++) { - for (k = 0; k < config->entry_count; k++) { - if (i == k) - continue; - if (StrCmp(config->entries[i]->title_show, config->entries[k]->title_show) != 0) - continue; - - unique = FALSE; - config->entries[i]->non_unique = TRUE; - config->entries[k]->non_unique = TRUE; - } - } - if (unique) - return; - - /* add version to non-unique titles */ - for (i = 0; i < config->entry_count; i++) { - CHAR16 *s; - - if (!config->entries[i]->non_unique) - continue; - if (!config->entries[i]->version) - continue; - - s = PoolPrint(L"%s (%s)", config->entries[i]->title_show, config->entries[i]->version); - FreePool(config->entries[i]->title_show); - config->entries[i]->title_show = s; - config->entries[i]->non_unique = FALSE; - } - - unique = TRUE; - for (i = 0; i < config->entry_count; i++) { - for (k = 0; k < config->entry_count; k++) { - if (i == k) - continue; - if (StrCmp(config->entries[i]->title_show, config->entries[k]->title_show) != 0) - continue; - - unique = FALSE; - config->entries[i]->non_unique = TRUE; - config->entries[k]->non_unique = TRUE; - } - } - if (unique) - return; - - /* add machine-id to non-unique titles */ - for (i = 0; i < config->entry_count; i++) { - CHAR16 *s; - CHAR16 *m; - - if (!config->entries[i]->non_unique) - continue; - if (!config->entries[i]->machine_id) - continue; - - m = StrDuplicate(config->entries[i]->machine_id); - m[8] = '\0'; - s = PoolPrint(L"%s (%s)", config->entries[i]->title_show, m); - FreePool(config->entries[i]->title_show); - config->entries[i]->title_show = s; - config->entries[i]->non_unique = FALSE; - FreePool(m); - } - - unique = TRUE; - for (i = 0; i < config->entry_count; i++) { - for (k = 0; k < config->entry_count; k++) { - if (i == k) - continue; - if (StrCmp(config->entries[i]->title_show, config->entries[k]->title_show) != 0) - continue; - - unique = FALSE; - config->entries[i]->non_unique = TRUE; - config->entries[k]->non_unique = TRUE; - } - } - if (unique) - return; - - /* add file name to non-unique titles */ - for (i = 0; i < config->entry_count; i++) { - CHAR16 *s; - - if (!config->entries[i]->non_unique) - continue; - s = PoolPrint(L"%s (%s)", config->entries[i]->title_show, config->entries[i]->file); - FreePool(config->entries[i]->title_show); - config->entries[i]->title_show = s; - config->entries[i]->non_unique = FALSE; - } -} - -static BOOLEAN config_entry_add_call(Config *config, CHAR16 *title, EFI_STATUS (*call)(VOID)) { - ConfigEntry *entry; - - entry = AllocateZeroPool(sizeof(ConfigEntry)); - entry->title = StrDuplicate(title); - entry->call = call; - entry->no_autoselect = TRUE; - config_add_entry(config, entry); - return TRUE; -} - -static ConfigEntry *config_entry_add_loader(Config *config, EFI_HANDLE *device, - enum loader_type type,CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) { - ConfigEntry *entry; - - entry = AllocateZeroPool(sizeof(ConfigEntry)); - entry->type = type; - entry->title = StrDuplicate(title); - entry->device = device; - entry->loader = StrDuplicate(loader); - entry->file = StrDuplicate(file); - StrLwr(entry->file); - entry->key = key; - config_add_entry(config, entry); - - return entry; -} - -static BOOLEAN config_entry_add_loader_auto(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path, - CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) { - EFI_FILE_HANDLE handle; - ConfigEntry *entry; - EFI_STATUS err; - - /* do not add an entry for ourselves */ - if (loaded_image_path && StriCmp(loader, loaded_image_path) == 0) - return FALSE; - - /* check existence */ - err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &handle, loader, EFI_FILE_MODE_READ, 0ULL); - if (EFI_ERROR(err)) - return FALSE; - uefi_call_wrapper(handle->Close, 1, handle); - - entry = config_entry_add_loader(config, device, LOADER_UNDEFINED, file, key, title, loader); - if (!entry) - return FALSE; - - /* do not boot right away into auto-detected entries */ - entry->no_autoselect = TRUE; - - return TRUE; -} - -static VOID config_entry_add_osx(Config *config) { - EFI_STATUS err; - UINTN handle_count = 0; - EFI_HANDLE *handles = NULL; - - err = LibLocateHandle(ByProtocol, &FileSystemProtocol, NULL, &handle_count, &handles); - if (!EFI_ERROR(err)) { - UINTN i; - - for (i = 0; i < handle_count; i++) { - EFI_FILE *root; - BOOLEAN found; - - root = LibOpenRoot(handles[i]); - if (!root) - continue; - found = config_entry_add_loader_auto(config, handles[i], root, NULL, L"auto-osx", 'a', L"OS X", - L"\\System\\Library\\CoreServices\\boot.efi"); - uefi_call_wrapper(root->Close, 1, root); - if (found) - break; - } - - FreePool(handles); - } -} - -static VOID config_entry_add_linux( Config *config, EFI_LOADED_IMAGE *loaded_image, EFI_FILE *root_dir) { - EFI_FILE_HANDLE linux_dir; - EFI_STATUS err; - ConfigEntry *entry; - - err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &linux_dir, L"\\EFI\\Linux", EFI_FILE_MODE_READ, 0ULL); - if (!EFI_ERROR(err)) { - for (;;) { - CHAR16 buf[256]; - UINTN bufsize; - EFI_FILE_INFO *f; - CHAR8 *sections[] = { - (UINT8 *)".osrel", - (UINT8 *)".cmdline", - NULL - }; - UINTN offs[ELEMENTSOF(sections)-1] = {}; - UINTN szs[ELEMENTSOF(sections)-1] = {}; - UINTN addrs[ELEMENTSOF(sections)-1] = {}; - CHAR8 *content = NULL; - UINTN len; - CHAR8 *line; - UINTN pos = 0; - CHAR8 *key, *value; - CHAR16 *os_name = NULL; - CHAR16 *os_id = NULL; - CHAR16 *os_version = NULL; - CHAR16 *os_build = NULL; - - bufsize = sizeof(buf); - err = uefi_call_wrapper(linux_dir->Read, 3, linux_dir, &bufsize, buf); - if (bufsize == 0 || EFI_ERROR(err)) - break; - - f = (EFI_FILE_INFO *) buf; - if (f->FileName[0] == '.') - continue; - if (f->Attribute & EFI_FILE_DIRECTORY) - continue; - len = StrLen(f->FileName); - if (len < 5) - continue; - if (StriCmp(f->FileName + len - 4, L".efi") != 0) - continue; - - /* look for .osrel and .cmdline sections in the .efi binary */ - err = pefile_locate_sections(linux_dir, f->FileName, sections, addrs, offs, szs); - if (EFI_ERROR(err)) - continue; - - len = file_read(linux_dir, f->FileName, offs[0], szs[0], &content); - if (len <= 0) - continue; - - /* read properties from the embedded os-release file */ - line = content; - while ((line = line_get_key_value(content, (CHAR8 *)"=", &pos, &key, &value))) { - if (strcmpa((CHAR8 *)"PRETTY_NAME", key) == 0) { - FreePool(os_name); - os_name = stra_to_str(value); - continue; - } - - if (strcmpa((CHAR8 *)"ID", key) == 0) { - FreePool(os_id); - os_id = stra_to_str(value); - continue; - } - - if (strcmpa((CHAR8 *)"VERSION_ID", key) == 0) { - FreePool(os_version); - os_version = stra_to_str(value); - continue; - } - - if (strcmpa((CHAR8 *)"BUILD_ID", key) == 0) { - FreePool(os_build); - os_build = stra_to_str(value); - continue; - } - } - - if (os_name && os_id && (os_version || os_build)) { - CHAR16 *conf; - CHAR16 *path; - CHAR16 *cmdline; - - conf = PoolPrint(L"%s-%s", os_id, os_version ? : os_build); - path = PoolPrint(L"\\EFI\\Linux\\%s", f->FileName); - entry = config_entry_add_loader(config, loaded_image->DeviceHandle, LOADER_LINUX, conf, 'l', os_name, path); - - FreePool(content); - /* read the embedded cmdline file */ - len = file_read(linux_dir, f->FileName, offs[1], szs[1] - 1 , &content); - if (len > 0) { - cmdline = stra_to_str(content); - entry->options = cmdline; - cmdline = NULL; - } - FreePool(cmdline); - FreePool(conf); - FreePool(path); - } - - FreePool(os_name); - FreePool(os_id); - FreePool(os_version); - FreePool(os_build); - FreePool(content); - } - uefi_call_wrapper(linux_dir->Close, 1, linux_dir); - } -} - -static EFI_STATUS image_start(EFI_HANDLE parent_image, const Config *config, const ConfigEntry *entry) { - EFI_HANDLE image; - EFI_DEVICE_PATH *path; - CHAR16 *options; - EFI_STATUS err; - - path = FileDevicePath(entry->device, entry->loader); - if (!path) { - Print(L"Error getting device path."); - uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); - return EFI_INVALID_PARAMETER; - } - - err = uefi_call_wrapper(BS->LoadImage, 6, FALSE, parent_image, path, NULL, 0, &image); - if (EFI_ERROR(err)) { - Print(L"Error loading %s: %r", entry->loader, err); - uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); - goto out; - } - - if (config->options_edit) - options = config->options_edit; - else if (entry->options) - options = entry->options; - else - options = NULL; - if (options) { - EFI_LOADED_IMAGE *loaded_image; - - err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, (VOID **)&loaded_image, - parent_image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); - if (EFI_ERROR(err)) { - Print(L"Error getting LoadedImageProtocol handle: %r", err); - uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); - goto out_unload; - } - loaded_image->LoadOptions = options; - loaded_image->LoadOptionsSize = (StrLen(loaded_image->LoadOptions)+1) * sizeof(CHAR16); - -#ifdef SD_BOOT_LOG_TPM - /* Try to log any options to the TPM, escpecially to catch manually edited options */ - err = tpm_log_event(SD_TPM_PCR, - (EFI_PHYSICAL_ADDRESS) loaded_image->LoadOptions, - loaded_image->LoadOptionsSize, loaded_image->LoadOptions); - if (EFI_ERROR(err)) { - Print(L"Unable to add image options measurement: %r", err); - uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); - return err; - } -#endif - } - - efivar_set_time_usec(L"LoaderTimeExecUSec", 0); - err = uefi_call_wrapper(BS->StartImage, 3, image, NULL, NULL); -out_unload: - uefi_call_wrapper(BS->UnloadImage, 1, image); -out: - FreePool(path); - return err; -} - -static EFI_STATUS reboot_into_firmware(VOID) { - CHAR8 *b; - UINTN size; - UINT64 osind; - EFI_STATUS err; - - osind = EFI_OS_INDICATIONS_BOOT_TO_FW_UI; - - err = efivar_get_raw(&global_guid, L"OsIndications", &b, &size); - if (!EFI_ERROR(err)) - osind |= (UINT64)*b; - FreePool(b); - - err = efivar_set_raw(&global_guid, L"OsIndications", (CHAR8 *)&osind, sizeof(UINT64), TRUE); - if (EFI_ERROR(err)) - return err; - - err = uefi_call_wrapper(RT->ResetSystem, 4, EfiResetCold, EFI_SUCCESS, 0, NULL); - Print(L"Error calling ResetSystem: %r", err); - uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); - return err; -} - -static VOID config_free(Config *config) { - UINTN i; - - for (i = 0; i < config->entry_count; i++) - config_entry_free(config->entries[i]); - FreePool(config->entries); - FreePool(config->entry_default_pattern); - FreePool(config->options_edit); - FreePool(config->entry_oneshot); -} - -EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { - CHAR16 *s; - CHAR8 *b; - UINTN size; - EFI_LOADED_IMAGE *loaded_image; - EFI_FILE *root_dir; - CHAR16 *loaded_image_path; - EFI_STATUS err; - Config config; - UINT64 init_usec; - BOOLEAN menu = FALSE; - CHAR16 uuid[37]; - - InitializeLib(image, sys_table); - init_usec = time_usec(); - efivar_set_time_usec(L"LoaderTimeInitUSec", init_usec); - efivar_set(L"LoaderInfo", L"systemd-boot " VERSION, FALSE); - s = PoolPrint(L"%s %d.%02d", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); - efivar_set(L"LoaderFirmwareInfo", s, FALSE); - FreePool(s); - s = PoolPrint(L"UEFI %d.%02d", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff); - efivar_set(L"LoaderFirmwareType", s, FALSE); - FreePool(s); - - err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, (VOID **)&loaded_image, - image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); - if (EFI_ERROR(err)) { - Print(L"Error getting a LoadedImageProtocol handle: %r ", err); - uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); - return err; - } - - /* export the device path this image is started from */ - if (disk_get_part_uuid(loaded_image->DeviceHandle, uuid) == EFI_SUCCESS) - efivar_set(L"LoaderDevicePartUUID", uuid, FALSE); - - root_dir = LibOpenRoot(loaded_image->DeviceHandle); - if (!root_dir) { - Print(L"Unable to open root directory: %r ", err); - uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); - return EFI_LOAD_ERROR; - } - - - /* the filesystem path to this image, to prevent adding ourselves to the menu */ - loaded_image_path = DevicePathToStr(loaded_image->FilePath); - efivar_set(L"LoaderImageIdentifier", loaded_image_path, FALSE); - - ZeroMem(&config, sizeof(Config)); - config_load_defaults(&config, root_dir); - - /* scan /EFI/Linux/ directory */ - config_entry_add_linux(&config, loaded_image, root_dir); - - /* scan /loader/entries/\*.conf files */ - config_load_entries(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path); - - /* sort entries after version number */ - config_sort_entries(&config); - - /* if we find some well-known loaders, add them to the end of the list */ - config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path, - L"auto-windows", 'w', L"Windows Boot Manager", L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi"); - config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path, - L"auto-efi-shell", 's', L"EFI Shell", L"\\shell" EFI_MACHINE_TYPE_NAME ".efi"); - config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path, - L"auto-efi-default", '\0', L"EFI Default Loader", L"\\EFI\\Boot\\boot" EFI_MACHINE_TYPE_NAME ".efi"); - config_entry_add_osx(&config); - - if (efivar_get_raw(&global_guid, L"OsIndicationsSupported", &b, &size) == EFI_SUCCESS) { - UINT64 osind = (UINT64)*b; - - if (osind & EFI_OS_INDICATIONS_BOOT_TO_FW_UI) - config_entry_add_call(&config, L"Reboot Into Firmware Interface", reboot_into_firmware); - FreePool(b); - } - - if (config.entry_count == 0) { - Print(L"No loader found. Configuration files in \\loader\\entries\\*.conf are needed."); - uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); - goto out; - } - - config_title_generate(&config); - - /* select entry by configured pattern or EFI LoaderDefaultEntry= variable*/ - config_default_entry_select(&config); - - /* if no configured entry to select from was found, enable the menu */ - if (config.idx_default == -1) { - config.idx_default = 0; - if (config.timeout_sec == 0) - config.timeout_sec = 10; - } - - /* select entry or show menu when key is pressed or timeout is set */ - if (config.timeout_sec == 0) { - UINT64 key; - - err = console_key_read(&key, FALSE); - if (!EFI_ERROR(err)) { - INT16 idx; - - /* find matching key in config entries */ - idx = entry_lookup_key(&config, config.idx_default, KEYCHAR(key)); - if (idx >= 0) - config.idx_default = idx; - else - menu = TRUE; - } - } else - menu = TRUE; - - for (;;) { - ConfigEntry *entry; - - entry = config.entries[config.idx_default]; - if (menu) { - efivar_set_time_usec(L"LoaderTimeMenuUSec", 0); - uefi_call_wrapper(BS->SetWatchdogTimer, 4, 0, 0x10000, 0, NULL); - if (!menu_run(&config, &entry, loaded_image_path)) - break; - - /* run special entry like "reboot" */ - if (entry->call) { - entry->call(); - continue; - } - } - - /* export the selected boot entry to the system */ - efivar_set(L"LoaderEntrySelected", entry->file, FALSE); - - uefi_call_wrapper(BS->SetWatchdogTimer, 4, 5 * 60, 0x10000, 0, NULL); - err = image_start(image, &config, entry); - if (EFI_ERROR(err)) { - graphics_mode(FALSE); - Print(L"\nFailed to execute %s (%s): %r\n", entry->title, entry->loader, err); - uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); - goto out; - } - - menu = TRUE; - config.timeout_sec = 0; - } - err = EFI_SUCCESS; -out: - FreePool(loaded_image_path); - config_free(&config); - uefi_call_wrapper(root_dir->Close, 1, root_dir); - uefi_call_wrapper(BS->CloseProtocol, 4, image, &LoadedImageProtocol, image, NULL); - return err; -} diff --git a/src/boot/efi/console.c b/src/boot/efi/console.c deleted file mode 100644 index c436f8b476..0000000000 --- a/src/boot/efi/console.c +++ /dev/null @@ -1,139 +0,0 @@ -/* - * This program 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. - * - * This program 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. - * - * Copyright (C) 2012-2013 Kay Sievers - * Copyright (C) 2012 Harald Hoyer - */ - -#include -#include - -#include "console.h" -#include "util.h" - -#define EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID \ - { 0xdd9e7534, 0x7762, 0x4698, { 0x8c, 0x14, 0xf5, 0x85, 0x17, 0xa6, 0x25, 0xaa } } - -struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL; - -typedef EFI_STATUS (EFIAPI *EFI_INPUT_RESET_EX)( - struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, - BOOLEAN ExtendedVerification -); - -typedef UINT8 EFI_KEY_TOGGLE_STATE; - -typedef struct { - UINT32 KeyShiftState; - EFI_KEY_TOGGLE_STATE KeyToggleState; -} EFI_KEY_STATE; - -typedef struct { - EFI_INPUT_KEY Key; - EFI_KEY_STATE KeyState; -} EFI_KEY_DATA; - -typedef EFI_STATUS (EFIAPI *EFI_INPUT_READ_KEY_EX)( - struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, - EFI_KEY_DATA *KeyData -); - -typedef EFI_STATUS (EFIAPI *EFI_SET_STATE)( - struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, - EFI_KEY_TOGGLE_STATE *KeyToggleState -); - -typedef EFI_STATUS (EFIAPI *EFI_KEY_NOTIFY_FUNCTION)( - EFI_KEY_DATA *KeyData -); - -typedef EFI_STATUS (EFIAPI *EFI_REGISTER_KEYSTROKE_NOTIFY)( - struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, - EFI_KEY_DATA KeyData, - EFI_KEY_NOTIFY_FUNCTION KeyNotificationFunction, - VOID **NotifyHandle -); - -typedef EFI_STATUS (EFIAPI *EFI_UNREGISTER_KEYSTROKE_NOTIFY)( - struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, - VOID *NotificationHandle -); - -typedef struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL { - EFI_INPUT_RESET_EX Reset; - EFI_INPUT_READ_KEY_EX ReadKeyStrokeEx; - EFI_EVENT WaitForKeyEx; - EFI_SET_STATE SetState; - EFI_REGISTER_KEYSTROKE_NOTIFY RegisterKeyNotify; - EFI_UNREGISTER_KEYSTROKE_NOTIFY UnregisterKeyNotify; -} EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL; - -EFI_STATUS console_key_read(UINT64 *key, BOOLEAN wait) { - EFI_GUID EfiSimpleTextInputExProtocolGuid = EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID; - static EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *TextInputEx; - static BOOLEAN checked; - UINTN index; - EFI_INPUT_KEY k; - EFI_STATUS err; - - if (!checked) { - err = LibLocateProtocol(&EfiSimpleTextInputExProtocolGuid, (VOID **)&TextInputEx); - if (EFI_ERROR(err)) - TextInputEx = NULL; - - checked = TRUE; - } - - /* wait until key is pressed */ - if (wait) { - if (TextInputEx) - uefi_call_wrapper(BS->WaitForEvent, 3, 1, &TextInputEx->WaitForKeyEx, &index); - else - uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index); - } - - if (TextInputEx) { - EFI_KEY_DATA keydata; - UINT64 keypress; - - err = uefi_call_wrapper(TextInputEx->ReadKeyStrokeEx, 2, TextInputEx, &keydata); - if (!EFI_ERROR(err)) { - UINT32 shift = 0; - - /* do not distinguish between left and right keys */ - if (keydata.KeyState.KeyShiftState & EFI_SHIFT_STATE_VALID) { - if (keydata.KeyState.KeyShiftState & (EFI_RIGHT_CONTROL_PRESSED|EFI_LEFT_CONTROL_PRESSED)) - shift |= EFI_CONTROL_PRESSED; - if (keydata.KeyState.KeyShiftState & (EFI_RIGHT_ALT_PRESSED|EFI_LEFT_ALT_PRESSED)) - shift |= EFI_ALT_PRESSED; - }; - - /* 32 bit modifier keys + 16 bit scan code + 16 bit unicode */ - keypress = KEYPRESS(shift, keydata.Key.ScanCode, keydata.Key.UnicodeChar); - if (keypress > 0) { - *key = keypress; - return 0; - } - } - } - - /* fallback for firmware which does not support SimpleTextInputExProtocol - * - * This is also called in case ReadKeyStrokeEx did not return a key, because - * some broken firmwares offer SimpleTextInputExProtocol, but never acually - * handle any key. */ - err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &k); - if (EFI_ERROR(err)) - return err; - - *key = KEYPRESS(0, k.ScanCode, k.UnicodeChar); - return 0; -} diff --git a/src/boot/efi/console.h b/src/boot/efi/console.h deleted file mode 100644 index 3fe0ce5ec4..0000000000 --- a/src/boot/efi/console.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * This program 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. - * - * This program 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. - * - * Copyright (C) 2012-2013 Kay Sievers - * Copyright (C) 2012 Harald Hoyer - */ - -#ifndef __SDBOOT_CONSOLE_H -#define __SDBOOT_CONSOLE_H - -#define EFI_SHIFT_STATE_VALID 0x80000000 -#define EFI_RIGHT_CONTROL_PRESSED 0x00000004 -#define EFI_LEFT_CONTROL_PRESSED 0x00000008 -#define EFI_RIGHT_ALT_PRESSED 0x00000010 -#define EFI_LEFT_ALT_PRESSED 0x00000020 - -#define EFI_CONTROL_PRESSED (EFI_RIGHT_CONTROL_PRESSED|EFI_LEFT_CONTROL_PRESSED) -#define EFI_ALT_PRESSED (EFI_RIGHT_ALT_PRESSED|EFI_LEFT_ALT_PRESSED) -#define KEYPRESS(keys, scan, uni) ((((UINT64)keys) << 32) | ((scan) << 16) | (uni)) -#define KEYCHAR(k) ((k) & 0xffff) -#define CHAR_CTRL(c) ((c) - 'a' + 1) - -EFI_STATUS console_key_read(UINT64 *key, BOOLEAN wait); -#endif diff --git a/src/boot/efi/disk.c b/src/boot/efi/disk.c deleted file mode 100644 index 3e3b5b224a..0000000000 --- a/src/boot/efi/disk.c +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This program 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. - * - * This program 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. - * - * Copyright (C) 2015 Kay Sievers - */ - -#include -#include - -#include "util.h" - -EFI_STATUS disk_get_part_uuid(EFI_HANDLE *handle, CHAR16 uuid[37]) { - EFI_DEVICE_PATH *device_path; - EFI_STATUS r = EFI_NOT_FOUND; - - /* export the device path this image is started from */ - device_path = DevicePathFromHandle(handle); - if (device_path) { - EFI_DEVICE_PATH *path, *paths; - - paths = UnpackDevicePath(device_path); - for (path = paths; !IsDevicePathEnd(path); path = NextDevicePathNode(path)) { - HARDDRIVE_DEVICE_PATH *drive; - - if (DevicePathType(path) != MEDIA_DEVICE_PATH) - continue; - if (DevicePathSubType(path) != MEDIA_HARDDRIVE_DP) - continue; - drive = (HARDDRIVE_DEVICE_PATH *)path; - if (drive->SignatureType != SIGNATURE_TYPE_GUID) - continue; - - GuidToString(uuid, (EFI_GUID *)&drive->Signature); - r = EFI_SUCCESS; - break; - } - FreePool(paths); - } - - return r; -} diff --git a/src/boot/efi/disk.h b/src/boot/efi/disk.h deleted file mode 100644 index af91a9c674..0000000000 --- a/src/boot/efi/disk.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * This program 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. - * - * This program 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. - * - * Copyright (C) 2015 Kay Sievers - */ - -#ifndef __SDBOOT_DISK_H -#define __SDBOOT_DISK_H - -EFI_STATUS disk_get_part_uuid(EFI_HANDLE *handle, CHAR16 uuid[37]); -#endif diff --git a/src/boot/efi/graphics.c b/src/boot/efi/graphics.c deleted file mode 100644 index 4854baf874..0000000000 --- a/src/boot/efi/graphics.c +++ /dev/null @@ -1,88 +0,0 @@ -/* - * This program 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. - * - * This program 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. - * - * Copyright (C) 2012-2013 Kay Sievers - * Copyright (C) 2012 Harald Hoyer - * Copyright (C) 2013 Intel Corporation - * Authored by Joonas Lahtinen - */ - -#include -#include - -#include "graphics.h" -#include "util.h" - -EFI_STATUS graphics_mode(BOOLEAN on) { - #define EFI_CONSOLE_CONTROL_PROTOCOL_GUID \ - { 0xf42f7782, 0x12e, 0x4c12, { 0x99, 0x56, 0x49, 0xf9, 0x43, 0x4, 0xf7, 0x21 } }; - - struct _EFI_CONSOLE_CONTROL_PROTOCOL; - - typedef enum { - EfiConsoleControlScreenText, - EfiConsoleControlScreenGraphics, - EfiConsoleControlScreenMaxValue, - } EFI_CONSOLE_CONTROL_SCREEN_MODE; - - typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_GET_MODE)( - struct _EFI_CONSOLE_CONTROL_PROTOCOL *This, - EFI_CONSOLE_CONTROL_SCREEN_MODE *Mode, - BOOLEAN *UgaExists, - BOOLEAN *StdInLocked - ); - - typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE)( - struct _EFI_CONSOLE_CONTROL_PROTOCOL *This, - EFI_CONSOLE_CONTROL_SCREEN_MODE Mode - ); - - typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_LOCK_STD_IN)( - struct _EFI_CONSOLE_CONTROL_PROTOCOL *This, - CHAR16 *Password - ); - - typedef struct _EFI_CONSOLE_CONTROL_PROTOCOL { - EFI_CONSOLE_CONTROL_PROTOCOL_GET_MODE GetMode; - EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE SetMode; - EFI_CONSOLE_CONTROL_PROTOCOL_LOCK_STD_IN LockStdIn; - } EFI_CONSOLE_CONTROL_PROTOCOL; - - EFI_GUID ConsoleControlProtocolGuid = EFI_CONSOLE_CONTROL_PROTOCOL_GUID; - EFI_CONSOLE_CONTROL_PROTOCOL *ConsoleControl = NULL; - EFI_CONSOLE_CONTROL_SCREEN_MODE new; - EFI_CONSOLE_CONTROL_SCREEN_MODE current; - BOOLEAN uga_exists; - BOOLEAN stdin_locked; - EFI_STATUS err; - - err = LibLocateProtocol(&ConsoleControlProtocolGuid, (VOID **)&ConsoleControl); - if (EFI_ERROR(err)) - /* console control protocol is nonstandard and might not exist. */ - return err == EFI_NOT_FOUND ? EFI_SUCCESS : err; - - /* check current mode */ - err = uefi_call_wrapper(ConsoleControl->GetMode, 4, ConsoleControl, ¤t, &uga_exists, &stdin_locked); - if (EFI_ERROR(err)) - return err; - - /* do not touch the mode */ - new = on ? EfiConsoleControlScreenGraphics : EfiConsoleControlScreenText; - if (new == current) - return EFI_SUCCESS; - - err = uefi_call_wrapper(ConsoleControl->SetMode, 2, ConsoleControl, new); - - /* some firmware enables the cursor when switching modes */ - uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE); - - return err; -} diff --git a/src/boot/efi/graphics.h b/src/boot/efi/graphics.h deleted file mode 100644 index cf48e647e7..0000000000 --- a/src/boot/efi/graphics.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * This program 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. - * - * This program 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. - * - * Copyright (C) 2012-2013 Kay Sievers - * Copyright (C) 2012 Harald Hoyer - * Copyright (C) 2013 Intel Corporation - * Authored by Joonas Lahtinen - */ - -#ifndef __SDBOOT_GRAPHICS_H -#define __SDBOOT_GRAPHICS_H - -EFI_STATUS graphics_mode(BOOLEAN on); -#endif diff --git a/src/boot/efi/linux.c b/src/boot/efi/linux.c deleted file mode 100644 index 0dc99a6c53..0000000000 --- a/src/boot/efi/linux.c +++ /dev/null @@ -1,128 +0,0 @@ -/* - * This program 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. - * - * This program 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. - * - * Copyright (C) 2015 Kay Sievers - */ - -#include -#include - -#include "linux.h" -#include "util.h" - -#define SETUP_MAGIC 0x53726448 /* "HdrS" */ -struct SetupHeader { - UINT8 boot_sector[0x01f1]; - UINT8 setup_secs; - UINT16 root_flags; - UINT32 sys_size; - UINT16 ram_size; - UINT16 video_mode; - UINT16 root_dev; - UINT16 signature; - UINT16 jump; - UINT32 header; - UINT16 version; - UINT16 su_switch; - UINT16 setup_seg; - UINT16 start_sys; - UINT16 kernel_ver; - UINT8 loader_id; - UINT8 load_flags; - UINT16 movesize; - UINT32 code32_start; - UINT32 ramdisk_start; - UINT32 ramdisk_len; - UINT32 bootsect_kludge; - UINT16 heap_end; - UINT8 ext_loader_ver; - UINT8 ext_loader_type; - UINT32 cmd_line_ptr; - UINT32 ramdisk_max; - UINT32 kernel_alignment; - UINT8 relocatable_kernel; - UINT8 min_alignment; - UINT16 xloadflags; - UINT32 cmdline_size; - UINT32 hardware_subarch; - UINT64 hardware_subarch_data; - UINT32 payload_offset; - UINT32 payload_length; - UINT64 setup_data; - UINT64 pref_address; - UINT32 init_size; - UINT32 handover_offset; -} __attribute__((packed)); - -#ifdef __x86_64__ -typedef VOID(*handover_f)(VOID *image, EFI_SYSTEM_TABLE *table, struct SetupHeader *setup); -static inline VOID linux_efi_handover(EFI_HANDLE image, struct SetupHeader *setup) { - handover_f handover; - - asm volatile ("cli"); - handover = (handover_f)((UINTN)setup->code32_start + 512 + setup->handover_offset); - handover(image, ST, setup); -} -#else -typedef VOID(*handover_f)(VOID *image, EFI_SYSTEM_TABLE *table, struct SetupHeader *setup) __attribute__((regparm(0))); -static inline VOID linux_efi_handover(EFI_HANDLE image, struct SetupHeader *setup) { - handover_f handover; - - handover = (handover_f)((UINTN)setup->code32_start + setup->handover_offset); - handover(image, ST, setup); -} -#endif - -EFI_STATUS linux_exec(EFI_HANDLE *image, - CHAR8 *cmdline, UINTN cmdline_len, - UINTN linux_addr, - UINTN initrd_addr, UINTN initrd_size) { - struct SetupHeader *image_setup; - struct SetupHeader *boot_setup; - EFI_PHYSICAL_ADDRESS addr; - EFI_STATUS err; - - image_setup = (struct SetupHeader *)(linux_addr); - if (image_setup->signature != 0xAA55 || image_setup->header != SETUP_MAGIC) - return EFI_LOAD_ERROR; - - if (image_setup->version < 0x20b || !image_setup->relocatable_kernel) - return EFI_LOAD_ERROR; - - addr = 0x3fffffff; - err = uefi_call_wrapper(BS->AllocatePages, 4, AllocateMaxAddress, EfiLoaderData, - EFI_SIZE_TO_PAGES(0x4000), &addr); - if (EFI_ERROR(err)) - return err; - boot_setup = (struct SetupHeader *)(UINTN)addr; - ZeroMem(boot_setup, 0x4000); - CopyMem(boot_setup, image_setup, sizeof(struct SetupHeader)); - boot_setup->loader_id = 0xff; - - boot_setup->code32_start = (UINT32)linux_addr + (image_setup->setup_secs+1) * 512; - - if (cmdline) { - addr = 0xA0000; - err = uefi_call_wrapper(BS->AllocatePages, 4, AllocateMaxAddress, EfiLoaderData, - EFI_SIZE_TO_PAGES(cmdline_len + 1), &addr); - if (EFI_ERROR(err)) - return err; - CopyMem((VOID *)(UINTN)addr, cmdline, cmdline_len); - ((CHAR8 *)addr)[cmdline_len] = 0; - boot_setup->cmd_line_ptr = (UINT32)addr; - } - - boot_setup->ramdisk_start = (UINT32)initrd_addr; - boot_setup->ramdisk_len = (UINT32)initrd_size; - - linux_efi_handover(image, boot_setup); - return EFI_LOAD_ERROR; -} diff --git a/src/boot/efi/linux.h b/src/boot/efi/linux.h deleted file mode 100644 index d9e6ed7955..0000000000 --- a/src/boot/efi/linux.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * This program 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. - * - * This program 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. - * - * Copyright (C) 2015 Kay Sievers - */ - -#ifndef __SDBOOT_kernel_H -#define __SDBOOT_kernel_H - -EFI_STATUS linux_exec(EFI_HANDLE *image, - CHAR8 *cmdline, UINTN cmdline_size, - UINTN linux_addr, - UINTN initrd_addr, UINTN initrd_size); -#endif diff --git a/src/boot/efi/measure.c b/src/boot/efi/measure.c deleted file mode 100644 index 7c016387c1..0000000000 --- a/src/boot/efi/measure.c +++ /dev/null @@ -1,312 +0,0 @@ -/* - * This program 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. - * - * This program 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. - * - */ - -#ifdef SD_BOOT_LOG_TPM - -#include -#include -#include "measure.h" - -#define EFI_TCG_PROTOCOL_GUID { 0xf541796d, 0xa62e, 0x4954, {0xa7, 0x75, 0x95, 0x84, 0xf6, 0x1b, 0x9c, 0xdd} } - -typedef struct _TCG_VERSION { - UINT8 Major; - UINT8 Minor; - UINT8 RevMajor; - UINT8 RevMinor; -} TCG_VERSION; - -typedef struct _TCG_BOOT_SERVICE_CAPABILITY { - UINT8 Size; - struct _TCG_VERSION StructureVersion; - struct _TCG_VERSION ProtocolSpecVersion; - UINT8 HashAlgorithmBitmap; - BOOLEAN TPMPresentFlag; - BOOLEAN TPMDeactivatedFlag; -} TCG_BOOT_SERVICE_CAPABILITY; - -typedef UINT32 TCG_ALGORITHM_ID; -#define TCG_ALG_SHA 0x00000004 // The SHA1 algorithm - -#define SHA1_DIGEST_SIZE 20 - -typedef struct _TCG_DIGEST { - UINT8 Digest[SHA1_DIGEST_SIZE]; -} TCG_DIGEST; - -#define EV_IPL 13 - -typedef struct _TCG_PCR_EVENT { - UINT32 PCRIndex; - UINT32 EventType; - struct _TCG_DIGEST digest; - UINT32 EventSize; - UINT8 Event[1]; -} TCG_PCR_EVENT; - -INTERFACE_DECL(_EFI_TCG); - -typedef EFI_STATUS(EFIAPI * EFI_TCG_STATUS_CHECK) (IN struct _EFI_TCG * This, - OUT struct _TCG_BOOT_SERVICE_CAPABILITY * ProtocolCapability, - OUT UINT32 * TCGFeatureFlags, - OUT EFI_PHYSICAL_ADDRESS * EventLogLocation, - OUT EFI_PHYSICAL_ADDRESS * EventLogLastEntry); - -typedef EFI_STATUS(EFIAPI * EFI_TCG_HASH_ALL) (IN struct _EFI_TCG * This, - IN UINT8 * HashData, - IN UINT64 HashDataLen, - IN TCG_ALGORITHM_ID AlgorithmId, - IN OUT UINT64 * HashedDataLen, IN OUT UINT8 ** HashedDataResult); - -typedef EFI_STATUS(EFIAPI * EFI_TCG_LOG_EVENT) (IN struct _EFI_TCG * This, - IN struct _TCG_PCR_EVENT * TCGLogData, - IN OUT UINT32 * EventNumber, IN UINT32 Flags); - -typedef EFI_STATUS(EFIAPI * EFI_TCG_PASS_THROUGH_TO_TPM) (IN struct _EFI_TCG * This, - IN UINT32 TpmInputParameterBlockSize, - IN UINT8 * TpmInputParameterBlock, - IN UINT32 TpmOutputParameterBlockSize, - IN UINT8 * TpmOutputParameterBlock); - -typedef EFI_STATUS(EFIAPI * EFI_TCG_HASH_LOG_EXTEND_EVENT) (IN struct _EFI_TCG * This, - IN EFI_PHYSICAL_ADDRESS HashData, - IN UINT64 HashDataLen, - IN TCG_ALGORITHM_ID AlgorithmId, - IN struct _TCG_PCR_EVENT * TCGLogData, - IN OUT UINT32 * EventNumber, - OUT EFI_PHYSICAL_ADDRESS * EventLogLastEntry); - -typedef struct _EFI_TCG { - EFI_TCG_STATUS_CHECK StatusCheck; - EFI_TCG_HASH_ALL HashAll; - EFI_TCG_LOG_EVENT LogEvent; - EFI_TCG_PASS_THROUGH_TO_TPM PassThroughToTPM; - EFI_TCG_HASH_LOG_EXTEND_EVENT HashLogExtendEvent; -} EFI_TCG; - -#define EFI_TCG2_PROTOCOL_GUID {0x607f766c, 0x7455, 0x42be, { 0x93, 0x0b, 0xe4, 0xd7, 0x6d, 0xb2, 0x72, 0x0f }} - -typedef struct tdEFI_TCG2_PROTOCOL EFI_TCG2_PROTOCOL; - -typedef struct tdEFI_TCG2_VERSION { - UINT8 Major; - UINT8 Minor; -} EFI_TCG2_VERSION; - -typedef UINT32 EFI_TCG2_EVENT_LOG_BITMAP; -typedef UINT32 EFI_TCG2_EVENT_LOG_FORMAT; -typedef UINT32 EFI_TCG2_EVENT_ALGORITHM_BITMAP; - -#define EFI_TCG2_EVENT_LOG_FORMAT_TCG_1_2 0x00000001 -#define EFI_TCG2_EVENT_LOG_FORMAT_TCG_2 0x00000002 - -typedef struct tdEFI_TCG2_BOOT_SERVICE_CAPABILITY { - UINT8 Size; - EFI_TCG2_VERSION StructureVersion; - EFI_TCG2_VERSION ProtocolVersion; - EFI_TCG2_EVENT_ALGORITHM_BITMAP HashAlgorithmBitmap; - EFI_TCG2_EVENT_LOG_BITMAP SupportedEventLogs; - BOOLEAN TPMPresentFlag; - UINT16 MaxCommandSize; - UINT16 MaxResponseSize; - UINT32 ManufacturerID; - UINT32 NumberOfPCRBanks; - EFI_TCG2_EVENT_ALGORITHM_BITMAP ActivePcrBanks; -} EFI_TCG2_BOOT_SERVICE_CAPABILITY; - -#define EFI_TCG2_EVENT_HEADER_VERSION 1 - -typedef struct { - UINT32 HeaderSize; - UINT16 HeaderVersion; - UINT32 PCRIndex; - UINT32 EventType; -} EFI_TCG2_EVENT_HEADER; - -typedef struct tdEFI_TCG2_EVENT { - UINT32 Size; - EFI_TCG2_EVENT_HEADER Header; - UINT8 Event[1]; -} EFI_TCG2_EVENT; - -typedef EFI_STATUS(EFIAPI * EFI_TCG2_GET_CAPABILITY) (IN EFI_TCG2_PROTOCOL * This, - IN OUT EFI_TCG2_BOOT_SERVICE_CAPABILITY * ProtocolCapability); - -typedef EFI_STATUS(EFIAPI * EFI_TCG2_GET_EVENT_LOG) (IN EFI_TCG2_PROTOCOL * This, - IN EFI_TCG2_EVENT_LOG_FORMAT EventLogFormat, - OUT EFI_PHYSICAL_ADDRESS * EventLogLocation, - OUT EFI_PHYSICAL_ADDRESS * EventLogLastEntry, - OUT BOOLEAN * EventLogTruncated); - -typedef EFI_STATUS(EFIAPI * EFI_TCG2_HASH_LOG_EXTEND_EVENT) (IN EFI_TCG2_PROTOCOL * This, - IN UINT64 Flags, - IN EFI_PHYSICAL_ADDRESS DataToHash, - IN UINT64 DataToHashLen, IN EFI_TCG2_EVENT * EfiTcgEvent); - -typedef EFI_STATUS(EFIAPI * EFI_TCG2_SUBMIT_COMMAND) (IN EFI_TCG2_PROTOCOL * This, - IN UINT32 InputParameterBlockSize, - IN UINT8 * InputParameterBlock, - IN UINT32 OutputParameterBlockSize, IN UINT8 * OutputParameterBlock); - -typedef EFI_STATUS(EFIAPI * EFI_TCG2_GET_ACTIVE_PCR_BANKS) (IN EFI_TCG2_PROTOCOL * This, OUT UINT32 * ActivePcrBanks); - -typedef EFI_STATUS(EFIAPI * EFI_TCG2_SET_ACTIVE_PCR_BANKS) (IN EFI_TCG2_PROTOCOL * This, IN UINT32 ActivePcrBanks); - -typedef EFI_STATUS(EFIAPI * EFI_TCG2_GET_RESULT_OF_SET_ACTIVE_PCR_BANKS) (IN EFI_TCG2_PROTOCOL * This, - OUT UINT32 * OperationPresent, OUT UINT32 * Response); - -typedef struct tdEFI_TCG2_PROTOCOL { - EFI_TCG2_GET_CAPABILITY GetCapability; - EFI_TCG2_GET_EVENT_LOG GetEventLog; - EFI_TCG2_HASH_LOG_EXTEND_EVENT HashLogExtendEvent; - EFI_TCG2_SUBMIT_COMMAND SubmitCommand; - EFI_TCG2_GET_ACTIVE_PCR_BANKS GetActivePcrBanks; - EFI_TCG2_SET_ACTIVE_PCR_BANKS SetActivePcrBanks; - EFI_TCG2_GET_RESULT_OF_SET_ACTIVE_PCR_BANKS GetResultOfSetActivePcrBanks; -} EFI_TCG2; - - -static EFI_STATUS tpm1_measure_to_pcr_and_event_log(const EFI_TCG *tcg, UINT32 pcrindex, const EFI_PHYSICAL_ADDRESS buffer, - UINTN buffer_size, const CHAR16 *description) { - EFI_STATUS status; - TCG_PCR_EVENT *tcg_event; - UINT32 event_number; - EFI_PHYSICAL_ADDRESS event_log_last; - UINTN desc_len; - - desc_len = (StrLen(description) + 1) * sizeof(CHAR16); - - tcg_event = AllocateZeroPool(desc_len + sizeof(TCG_PCR_EVENT)); - - if (tcg_event == NULL) - return EFI_OUT_OF_RESOURCES; - - tcg_event->EventSize = desc_len; - CopyMem((VOID *) & tcg_event->Event[0], (VOID *) description, desc_len); - - tcg_event->PCRIndex = pcrindex; - tcg_event->EventType = EV_IPL; - - event_number = 1; - status = uefi_call_wrapper(tcg->HashLogExtendEvent, 7, - tcg, buffer, buffer_size, TCG_ALG_SHA, tcg_event, &event_number, &event_log_last); - - if (EFI_ERROR(status)) - return status; - - uefi_call_wrapper(BS->FreePool, 1, tcg_event); - - return EFI_SUCCESS; -} - - -static EFI_STATUS tpm2_measure_to_pcr_and_event_log(const EFI_TCG2 *tcg, UINT32 pcrindex, const EFI_PHYSICAL_ADDRESS buffer, - UINT64 buffer_size, const CHAR16 *description) { - EFI_STATUS status; - EFI_TCG2_EVENT *tcg_event; - UINTN desc_len; - - desc_len = StrLen(description) * sizeof(CHAR16); - - tcg_event = AllocateZeroPool(sizeof(*tcg_event) - sizeof(tcg_event->Event) + desc_len + 1); - - if (tcg_event == NULL) - return EFI_OUT_OF_RESOURCES; - - tcg_event->Size = sizeof(EFI_TCG2_EVENT) - sizeof(tcg_event->Event) + desc_len + 1; - tcg_event->Header.HeaderSize = sizeof(EFI_TCG2_EVENT_HEADER); - tcg_event->Header.HeaderVersion = EFI_TCG2_EVENT_HEADER_VERSION; - tcg_event->Header.PCRIndex = pcrindex; - tcg_event->Header.EventType = EV_IPL; - - CopyMem((VOID *) tcg_event->Event, (VOID *) description, desc_len); - - status = uefi_call_wrapper(tcg->HashLogExtendEvent, 5, tcg, 0, buffer, buffer_size, tcg_event); - - uefi_call_wrapper(BS->FreePool, 1, tcg_event); - - if (EFI_ERROR(status)) - return status; - - return EFI_SUCCESS; -} - -static EFI_TCG * tcg1_interface_check(void) { - EFI_GUID tpm_guid = EFI_TCG_PROTOCOL_GUID; - EFI_STATUS status; - EFI_TCG *tcg; - TCG_BOOT_SERVICE_CAPABILITY capability; - UINT32 features; - EFI_PHYSICAL_ADDRESS event_log_location; - EFI_PHYSICAL_ADDRESS event_log_last_entry; - - status = LibLocateProtocol(&tpm_guid, (void **) &tcg); - - if (EFI_ERROR(status)) - return NULL; - - capability.Size = (UINT8) sizeof(capability); - status = uefi_call_wrapper(tcg->StatusCheck, 5, tcg, &capability, &features, &event_log_location, &event_log_last_entry); - - if (EFI_ERROR(status)) - return NULL; - - if (capability.TPMDeactivatedFlag) - return NULL; - - if (!capability.TPMPresentFlag) - return NULL; - - return tcg; -} - -static EFI_TCG2 * tcg2_interface_check(void) { - EFI_GUID tpm2_guid = EFI_TCG2_PROTOCOL_GUID; - EFI_STATUS status; - EFI_TCG2 *tcg; - EFI_TCG2_BOOT_SERVICE_CAPABILITY capability; - - status = LibLocateProtocol(&tpm2_guid, (void **) &tcg); - - if (EFI_ERROR(status)) - return NULL; - - capability.Size = (UINT8) sizeof(capability); - status = uefi_call_wrapper(tcg->GetCapability, 2, tcg, &capability); - - if (EFI_ERROR(status)) - return NULL; - - if (!capability.TPMPresentFlag) - return NULL; - - return tcg; -} - -EFI_STATUS tpm_log_event(UINT32 pcrindex, const EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const CHAR16 *description) { - EFI_TCG *tpm1; - EFI_TCG2 *tpm2; - - tpm2 = tcg2_interface_check(); - if (tpm2) - return tpm2_measure_to_pcr_and_event_log(tpm2, pcrindex, buffer, buffer_size, description); - - tpm1 = tcg1_interface_check(); - if (tpm1) - return tpm1_measure_to_pcr_and_event_log(tpm1, pcrindex, buffer, buffer_size, description); - - /* No active TPM found, so don't return an error */ - return EFI_SUCCESS; -} - -#endif diff --git a/src/boot/efi/measure.h b/src/boot/efi/measure.h deleted file mode 100644 index a2cfe817d0..0000000000 --- a/src/boot/efi/measure.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * This program 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. - * - * This program 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. - * - */ -#ifndef __SDBOOT_MEASURE_H -#define __SDBOOT_MEASURE_H - -#ifndef SD_TPM_PCR -#define SD_TPM_PCR 8 -#endif - -EFI_STATUS tpm_log_event(UINT32 pcrindex, const EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const CHAR16 *description); -#endif diff --git a/src/boot/efi/pefile.c b/src/boot/efi/pefile.c deleted file mode 100644 index 77fff77b69..0000000000 --- a/src/boot/efi/pefile.c +++ /dev/null @@ -1,170 +0,0 @@ -/* - * This program 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. - * - * This program 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. - * - * Copyright (C) 2015 Kay Sievers - */ - -#include -#include - -#include "pefile.h" -#include "util.h" - -struct DosFileHeader { - UINT8 Magic[2]; - UINT16 LastSize; - UINT16 nBlocks; - UINT16 nReloc; - UINT16 HdrSize; - UINT16 MinAlloc; - UINT16 MaxAlloc; - UINT16 ss; - UINT16 sp; - UINT16 Checksum; - UINT16 ip; - UINT16 cs; - UINT16 RelocPos; - UINT16 nOverlay; - UINT16 reserved[4]; - UINT16 OEMId; - UINT16 OEMInfo; - UINT16 reserved2[10]; - UINT32 ExeHeader; -} __attribute__((packed)); - -#define PE_HEADER_MACHINE_I386 0x014c -#define PE_HEADER_MACHINE_X64 0x8664 -struct PeFileHeader { - UINT16 Machine; - UINT16 NumberOfSections; - UINT32 TimeDateStamp; - UINT32 PointerToSymbolTable; - UINT32 NumberOfSymbols; - UINT16 SizeOfOptionalHeader; - UINT16 Characteristics; -} __attribute__((packed)); - -struct PeSectionHeader { - UINT8 Name[8]; - UINT32 VirtualSize; - UINT32 VirtualAddress; - UINT32 SizeOfRawData; - UINT32 PointerToRawData; - UINT32 PointerToRelocations; - UINT32 PointerToLinenumbers; - UINT16 NumberOfRelocations; - UINT16 NumberOfLinenumbers; - UINT32 Characteristics; -} __attribute__((packed)); - - -EFI_STATUS pefile_locate_sections(EFI_FILE *dir, CHAR16 *path, CHAR8 **sections, UINTN *addrs, UINTN *offsets, UINTN *sizes) { - EFI_FILE_HANDLE handle; - struct DosFileHeader dos; - uint8_t magic[4]; - struct PeFileHeader pe; - UINTN len; - UINTN i; - EFI_STATUS err; - - err = uefi_call_wrapper(dir->Open, 5, dir, &handle, path, EFI_FILE_MODE_READ, 0ULL); - if (EFI_ERROR(err)) - return err; - - /* MS-DOS stub */ - len = sizeof(dos); - err = uefi_call_wrapper(handle->Read, 3, handle, &len, &dos); - if (EFI_ERROR(err)) - goto out; - if (len != sizeof(dos)) { - err = EFI_LOAD_ERROR; - goto out; - } - - if (CompareMem(dos.Magic, "MZ", 2) != 0) { - err = EFI_LOAD_ERROR; - goto out; - } - - err = uefi_call_wrapper(handle->SetPosition, 2, handle, dos.ExeHeader); - if (EFI_ERROR(err)) - goto out; - - /* PE header */ - len = sizeof(magic); - err = uefi_call_wrapper(handle->Read, 3, handle, &len, &magic); - if (EFI_ERROR(err)) - goto out; - if (len != sizeof(magic)) { - err = EFI_LOAD_ERROR; - goto out; - } - - if (CompareMem(magic, "PE\0\0", 2) != 0) { - err = EFI_LOAD_ERROR; - goto out; - } - - len = sizeof(pe); - err = uefi_call_wrapper(handle->Read, 3, handle, &len, &pe); - if (EFI_ERROR(err)) - goto out; - if (len != sizeof(pe)) { - err = EFI_LOAD_ERROR; - goto out; - } - - /* PE32+ Subsystem type */ - if (pe.Machine != PE_HEADER_MACHINE_X64 && - pe.Machine != PE_HEADER_MACHINE_I386) { - err = EFI_LOAD_ERROR; - goto out; - } - - if (pe.NumberOfSections > 96) { - err = EFI_LOAD_ERROR; - goto out; - } - - /* the sections start directly after the headers */ - err = uefi_call_wrapper(handle->SetPosition, 2, handle, dos.ExeHeader + sizeof(magic) + sizeof(pe) + pe.SizeOfOptionalHeader); - if (EFI_ERROR(err)) - goto out; - - for (i = 0; i < pe.NumberOfSections; i++) { - struct PeSectionHeader sect; - UINTN j; - - len = sizeof(sect); - err = uefi_call_wrapper(handle->Read, 3, handle, &len, §); - if (EFI_ERROR(err)) - goto out; - if (len != sizeof(sect)) { - err = EFI_LOAD_ERROR; - goto out; - } - for (j = 0; sections[j]; j++) { - if (CompareMem(sect.Name, sections[j], strlena(sections[j])) != 0) - continue; - - if (addrs) - addrs[j] = (UINTN)sect.VirtualAddress; - if (offsets) - offsets[j] = (UINTN)sect.PointerToRawData; - if (sizes) - sizes[j] = (UINTN)sect.VirtualSize; - } - } - -out: - uefi_call_wrapper(handle->Close, 1, handle); - return err; -} diff --git a/src/boot/efi/pefile.h b/src/boot/efi/pefile.h deleted file mode 100644 index 2e445ede17..0000000000 --- a/src/boot/efi/pefile.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * This program 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. - * - * This program 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. - * - * Copyright (C) 2015 Kay Sievers - */ - -#ifndef __SDBOOT_PEFILE_H -#define __SDBOOT_PEFILE_H - -EFI_STATUS pefile_locate_sections(EFI_FILE *dir, CHAR16 *path, - CHAR8 **sections, UINTN *addrs, UINTN *offsets, UINTN *sizes); -#endif diff --git a/src/boot/efi/splash.c b/src/boot/efi/splash.c deleted file mode 100644 index c0ef7f64fe..0000000000 --- a/src/boot/efi/splash.c +++ /dev/null @@ -1,321 +0,0 @@ -/* - * This program 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. - * - * This program 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. - * - * Copyright (C) 2012-2013 Kay Sievers - * Copyright (C) 2012 Harald Hoyer - */ - -#include -#include - -#include "graphics.h" -#include "splash.h" -#include "util.h" - -struct bmp_file { - CHAR8 signature[2]; - UINT32 size; - UINT16 reserved[2]; - UINT32 offset; -} __attribute__((packed)); - -/* we require at least BITMAPINFOHEADER, later versions are - accepted, but their features ignored */ -struct bmp_dib { - UINT32 size; - UINT32 x; - UINT32 y; - UINT16 planes; - UINT16 depth; - UINT32 compression; - UINT32 image_size; - INT32 x_pixel_meter; - INT32 y_pixel_meter; - UINT32 colors_used; - UINT32 colors_important; -} __attribute__((packed)); - -struct bmp_map { - UINT8 blue; - UINT8 green; - UINT8 red; - UINT8 reserved; -} __attribute__((packed)); - -EFI_STATUS bmp_parse_header(UINT8 *bmp, UINTN size, struct bmp_dib **ret_dib, - struct bmp_map **ret_map, UINT8 **pixmap) { - struct bmp_file *file; - struct bmp_dib *dib; - struct bmp_map *map; - UINTN row_size; - - if (size < sizeof(struct bmp_file) + sizeof(struct bmp_dib)) - return EFI_INVALID_PARAMETER; - - /* check file header */ - file = (struct bmp_file *)bmp; - if (file->signature[0] != 'B' || file->signature[1] != 'M') - return EFI_INVALID_PARAMETER; - if (file->size != size) - return EFI_INVALID_PARAMETER; - if (file->size < file->offset) - return EFI_INVALID_PARAMETER; - - /* check device-independent bitmap */ - dib = (struct bmp_dib *)(bmp + sizeof(struct bmp_file)); - if (dib->size < sizeof(struct bmp_dib)) - return EFI_UNSUPPORTED; - - switch (dib->depth) { - case 1: - case 4: - case 8: - case 24: - if (dib->compression != 0) - return EFI_UNSUPPORTED; - - break; - - case 16: - case 32: - if (dib->compression != 0 && dib->compression != 3) - return EFI_UNSUPPORTED; - - break; - - default: - return EFI_UNSUPPORTED; - } - - row_size = ((UINTN) dib->depth * dib->x + 31) / 32 * 4; - if (file->size - file->offset < dib->y * row_size) - return EFI_INVALID_PARAMETER; - if (row_size * dib->y > 64 * 1024 * 1024) - return EFI_INVALID_PARAMETER; - - /* check color table */ - map = (struct bmp_map *)(bmp + sizeof(struct bmp_file) + dib->size); - if (file->offset < sizeof(struct bmp_file) + dib->size) - return EFI_INVALID_PARAMETER; - - if (file->offset > sizeof(struct bmp_file) + dib->size) { - UINT32 map_count; - UINTN map_size; - - if (dib->colors_used) - map_count = dib->colors_used; - else { - switch (dib->depth) { - case 1: - case 4: - case 8: - map_count = 1 << dib->depth; - break; - - default: - map_count = 0; - break; - } - } - - map_size = file->offset - (sizeof(struct bmp_file) + dib->size); - if (map_size != sizeof(struct bmp_map) * map_count) - return EFI_INVALID_PARAMETER; - } - - *ret_map = map; - *ret_dib = dib; - *pixmap = bmp + file->offset; - - return EFI_SUCCESS; -} - -static VOID pixel_blend(UINT32 *dst, const UINT32 source) { - UINT32 alpha, src, src_rb, src_g, dst_rb, dst_g, rb, g; - - alpha = (source & 0xff); - - /* convert src from RGBA to XRGB */ - src = source >> 8; - - /* decompose into RB and G components */ - src_rb = (src & 0xff00ff); - src_g = (src & 0x00ff00); - - dst_rb = (*dst & 0xff00ff); - dst_g = (*dst & 0x00ff00); - - /* blend */ - rb = ((((src_rb - dst_rb) * alpha + 0x800080) >> 8) + dst_rb) & 0xff00ff; - g = ((((src_g - dst_g) * alpha + 0x008000) >> 8) + dst_g) & 0x00ff00; - - *dst = (rb | g); -} - -EFI_STATUS bmp_to_blt(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *buf, - struct bmp_dib *dib, struct bmp_map *map, - UINT8 *pixmap) { - UINT8 *in; - UINTN y; - - /* transform and copy pixels */ - in = pixmap; - for (y = 0; y < dib->y; y++) { - EFI_GRAPHICS_OUTPUT_BLT_PIXEL *out; - UINTN row_size; - UINTN x; - - out = &buf[(dib->y - y - 1) * dib->x]; - for (x = 0; x < dib->x; x++, in++, out++) { - switch (dib->depth) { - case 1: { - UINTN i; - - for (i = 0; i < 8 && x < dib->x; i++) { - out->Red = map[((*in) >> (7 - i)) & 1].red; - out->Green = map[((*in) >> (7 - i)) & 1].green; - out->Blue = map[((*in) >> (7 - i)) & 1].blue; - out++; - x++; - } - out--; - x--; - break; - } - - case 4: { - UINTN i; - - i = (*in) >> 4; - out->Red = map[i].red; - out->Green = map[i].green; - out->Blue = map[i].blue; - if (x < (dib->x - 1)) { - out++; - x++; - i = (*in) & 0x0f; - out->Red = map[i].red; - out->Green = map[i].green; - out->Blue = map[i].blue; - } - break; - } - - case 8: - out->Red = map[*in].red; - out->Green = map[*in].green; - out->Blue = map[*in].blue; - break; - - case 16: { - UINT16 i = *(UINT16 *) in; - - out->Red = (i & 0x7c00) >> 7; - out->Green = (i & 0x3e0) >> 2; - out->Blue = (i & 0x1f) << 3; - in += 1; - break; - } - - case 24: - out->Red = in[2]; - out->Green = in[1]; - out->Blue = in[0]; - in += 2; - break; - - case 32: { - UINT32 i = *(UINT32 *) in; - - pixel_blend((UINT32 *)out, i); - - in += 3; - break; - } - } - } - - /* add row padding; new lines always start at 32 bit boundary */ - row_size = in - pixmap; - in += ((row_size + 3) & ~3) - row_size; - } - - return EFI_SUCCESS; -} - -EFI_STATUS graphics_splash(UINT8 *content, UINTN len, const EFI_GRAPHICS_OUTPUT_BLT_PIXEL *background) { - EFI_GRAPHICS_OUTPUT_BLT_PIXEL pixel = {}; - EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; - EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput = NULL; - struct bmp_dib *dib; - struct bmp_map *map; - UINT8 *pixmap; - UINT64 blt_size; - VOID *blt = NULL; - UINTN x_pos = 0; - UINTN y_pos = 0; - EFI_STATUS err; - - if (!background) { - if (StriCmp(L"Apple", ST->FirmwareVendor) == 0) { - pixel.Red = 0xc0; - pixel.Green = 0xc0; - pixel.Blue = 0xc0; - } - background = &pixel; - } - - err = LibLocateProtocol(&GraphicsOutputProtocolGuid, (VOID **)&GraphicsOutput); - if (EFI_ERROR(err)) - return err; - - err = bmp_parse_header(content, len, &dib, &map, &pixmap); - if (EFI_ERROR(err)) - goto err; - - if (dib->x < GraphicsOutput->Mode->Info->HorizontalResolution) - x_pos = (GraphicsOutput->Mode->Info->HorizontalResolution - dib->x) / 2; - if (dib->y < GraphicsOutput->Mode->Info->VerticalResolution) - y_pos = (GraphicsOutput->Mode->Info->VerticalResolution - dib->y) / 2; - - uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput, - (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)background, - EfiBltVideoFill, 0, 0, 0, 0, - GraphicsOutput->Mode->Info->HorizontalResolution, - GraphicsOutput->Mode->Info->VerticalResolution, 0); - - /* EFI buffer */ - blt_size = dib->x * dib->y * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL); - blt = AllocatePool(blt_size); - if (!blt) - return EFI_OUT_OF_RESOURCES; - - err = uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput, - blt, EfiBltVideoToBltBuffer, x_pos, y_pos, 0, 0, - dib->x, dib->y, 0); - if (EFI_ERROR(err)) - goto err; - - err = bmp_to_blt(blt, dib, map, pixmap); - if (EFI_ERROR(err)) - goto err; - - err = graphics_mode(TRUE); - if (EFI_ERROR(err)) - goto err; - - err = uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput, - blt, EfiBltBufferToVideo, 0, 0, x_pos, y_pos, - dib->x, dib->y, 0); -err: - FreePool(blt); - return err; -} diff --git a/src/boot/efi/splash.h b/src/boot/efi/splash.h deleted file mode 100644 index 09b543fb47..0000000000 --- a/src/boot/efi/splash.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * This program 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. - * - * This program 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. - * - * Copyright (C) 2012-2013 Kay Sievers - * Copyright (C) 2012 Harald Hoyer - */ - -#ifndef __SDBOOT_SPLASH_H -#define __SDBOOT_SPLASH_H - -EFI_STATUS graphics_splash(UINT8 *content, UINTN len, const EFI_GRAPHICS_OUTPUT_BLT_PIXEL *background); -#endif diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c deleted file mode 100644 index 1e250f34f4..0000000000 --- a/src/boot/efi/stub.c +++ /dev/null @@ -1,130 +0,0 @@ -/* This program 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. - * - * This program 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. - * - * Copyright (C) 2015 Kay Sievers - */ - -#include -#include - -#include "disk.h" -#include "graphics.h" -#include "linux.h" -#include "pefile.h" -#include "splash.h" -#include "util.h" -#include "measure.h" - -/* magic string to find in the binary image */ -static const char __attribute__((used)) magic[] = "#### LoaderInfo: systemd-stub " VERSION " ####"; - -static const EFI_GUID global_guid = EFI_GLOBAL_VARIABLE; - -EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { - EFI_LOADED_IMAGE *loaded_image; - EFI_FILE *root_dir; - CHAR16 *loaded_image_path; - CHAR8 *b; - UINTN size; - BOOLEAN secure = FALSE; - CHAR8 *sections[] = { - (UINT8 *)".cmdline", - (UINT8 *)".linux", - (UINT8 *)".initrd", - (UINT8 *)".splash", - NULL - }; - UINTN addrs[ELEMENTSOF(sections)-1] = {}; - UINTN offs[ELEMENTSOF(sections)-1] = {}; - UINTN szs[ELEMENTSOF(sections)-1] = {}; - CHAR8 *cmdline = NULL; - UINTN cmdline_len; - CHAR16 uuid[37]; - EFI_STATUS err; - - InitializeLib(image, sys_table); - - err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, (VOID **)&loaded_image, - image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); - if (EFI_ERROR(err)) { - Print(L"Error getting a LoadedImageProtocol handle: %r ", err); - uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); - return err; - } - - root_dir = LibOpenRoot(loaded_image->DeviceHandle); - if (!root_dir) { - Print(L"Unable to open root directory: %r ", err); - uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); - return EFI_LOAD_ERROR; - } - - loaded_image_path = DevicePathToStr(loaded_image->FilePath); - - if (efivar_get_raw(&global_guid, L"SecureBoot", &b, &size) == EFI_SUCCESS) { - if (*b > 0) - secure = TRUE; - FreePool(b); - } - - err = pefile_locate_sections(root_dir, loaded_image_path, sections, addrs, offs, szs); - if (EFI_ERROR(err)) { - Print(L"Unable to locate embedded .linux section: %r ", err); - uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); - return err; - } - - if (szs[0] > 0) - cmdline = (CHAR8 *)(loaded_image->ImageBase + addrs[0]); - - cmdline_len = szs[0]; - - /* if we are not in secure boot mode, accept a custom command line and replace the built-in one */ - if (!secure && loaded_image->LoadOptionsSize > 0) { - CHAR16 *options; - CHAR8 *line; - UINTN i; - - options = (CHAR16 *)loaded_image->LoadOptions; - cmdline_len = (loaded_image->LoadOptionsSize / sizeof(CHAR16)) * sizeof(CHAR8); - line = AllocatePool(cmdline_len); - for (i = 0; i < cmdline_len; i++) - line[i] = options[i]; - cmdline = line; - -#ifdef SD_BOOT_LOG_TPM - /* Try to log any options to the TPM, escpecially manually edited options */ - err = tpm_log_event(SD_TPM_PCR, - (EFI_PHYSICAL_ADDRESS) loaded_image->LoadOptions, - loaded_image->LoadOptionsSize, loaded_image->LoadOptions); - if (EFI_ERROR(err)) { - Print(L"Unable to add image options measurement: %r", err); - uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); - return err; - } -#endif - } - - /* export the device path this image is started from */ - if (disk_get_part_uuid(loaded_image->DeviceHandle, uuid) == EFI_SUCCESS) - efivar_set(L"LoaderDevicePartUUID", uuid, FALSE); - - if (szs[3] > 0) - graphics_splash((UINT8 *)((UINTN)loaded_image->ImageBase + addrs[3]), szs[3], NULL); - - err = linux_exec(image, cmdline, cmdline_len, - (UINTN)loaded_image->ImageBase + addrs[1], - (UINTN)loaded_image->ImageBase + addrs[2], szs[2]); - - graphics_mode(FALSE); - Print(L"Execution of embedded linux image failed: %r\n", err); - uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); - return err; -} diff --git a/src/boot/efi/util.c b/src/boot/efi/util.c deleted file mode 100644 index 98c5be74ce..0000000000 --- a/src/boot/efi/util.c +++ /dev/null @@ -1,345 +0,0 @@ -/* - * This program 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. - * - * This program 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. - * - * Copyright (C) 2012-2013 Kay Sievers - * Copyright (C) 2012 Harald Hoyer - */ - -#include -#include - -#include "util.h" - -/* - * Allocated random UUID, intended to be shared across tools that implement - * the (ESP)\loader\entries\-.conf convention and the - * associated EFI variables. - */ -static const EFI_GUID loader_guid = { 0x4a67b082, 0x0a4c, 0x41cf, {0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f} }; - -#ifdef __x86_64__ -UINT64 ticks_read(VOID) { - UINT64 a, d; - __asm__ volatile ("rdtsc" : "=a" (a), "=d" (d)); - return (d << 32) | a; -} -#elif defined(__i386__) -UINT64 ticks_read(VOID) { - UINT64 val; - __asm__ volatile ("rdtsc" : "=A" (val)); - return val; -} -#else -UINT64 ticks_read(VOID) { - UINT64 val = 1; - return val; -} -#endif - -/* count TSC ticks during a millisecond delay */ -UINT64 ticks_freq(VOID) { - UINT64 ticks_start, ticks_end; - - ticks_start = ticks_read(); - uefi_call_wrapper(BS->Stall, 1, 1000); - ticks_end = ticks_read(); - - return (ticks_end - ticks_start) * 1000; -} - -UINT64 time_usec(VOID) { - UINT64 ticks; - static UINT64 freq; - - ticks = ticks_read(); - if (ticks == 0) - return 0; - - if (freq == 0) { - freq = ticks_freq(); - if (freq == 0) - return 0; - } - - return 1000 * 1000 * ticks / freq; -} - -EFI_STATUS parse_boolean(CHAR8 *v, BOOLEAN *b) { - if (strcmpa(v, (CHAR8 *)"1") == 0 || - strcmpa(v, (CHAR8 *)"yes") == 0 || - strcmpa(v, (CHAR8 *)"y") == 0 || - strcmpa(v, (CHAR8 *)"true") == 0) { - *b = TRUE; - return EFI_SUCCESS; - } - - if (strcmpa(v, (CHAR8 *)"0") == 0 || - strcmpa(v, (CHAR8 *)"no") == 0 || - strcmpa(v, (CHAR8 *)"n") == 0 || - strcmpa(v, (CHAR8 *)"false") == 0) { - *b = FALSE; - return EFI_SUCCESS; - } - - return EFI_INVALID_PARAMETER; -} - -EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 *buf, UINTN size, BOOLEAN persistent) { - UINT32 flags; - - flags = EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS; - if (persistent) - flags |= EFI_VARIABLE_NON_VOLATILE; - - return uefi_call_wrapper(RT->SetVariable, 5, name, (EFI_GUID *)vendor, flags, size, buf); -} - -EFI_STATUS efivar_set(CHAR16 *name, CHAR16 *value, BOOLEAN persistent) { - return efivar_set_raw(&loader_guid, name, (CHAR8 *)value, value ? (StrLen(value)+1) * sizeof(CHAR16) : 0, persistent); -} - -EFI_STATUS efivar_set_int(CHAR16 *name, UINTN i, BOOLEAN persistent) { - CHAR16 str[32]; - - SPrint(str, 32, L"%d", i); - return efivar_set(name, str, persistent); -} - -EFI_STATUS efivar_get(CHAR16 *name, CHAR16 **value) { - CHAR8 *buf; - CHAR16 *val; - UINTN size; - EFI_STATUS err; - - err = efivar_get_raw(&loader_guid, name, &buf, &size); - if (EFI_ERROR(err)) - return err; - - val = StrDuplicate((CHAR16 *)buf); - if (!val) { - FreePool(buf); - return EFI_OUT_OF_RESOURCES; - } - - *value = val; - return EFI_SUCCESS; -} - -EFI_STATUS efivar_get_int(CHAR16 *name, UINTN *i) { - CHAR16 *val; - EFI_STATUS err; - - err = efivar_get(name, &val); - if (!EFI_ERROR(err)) { - *i = Atoi(val); - FreePool(val); - } - return err; -} - -EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 **buffer, UINTN *size) { - CHAR8 *buf; - UINTN l; - EFI_STATUS err; - - l = sizeof(CHAR16 *) * EFI_MAXIMUM_VARIABLE_SIZE; - buf = AllocatePool(l); - if (!buf) - return EFI_OUT_OF_RESOURCES; - - err = uefi_call_wrapper(RT->GetVariable, 5, name, (EFI_GUID *)vendor, NULL, &l, buf); - if (!EFI_ERROR(err)) { - *buffer = buf; - if (size) - *size = l; - } else - FreePool(buf); - return err; - -} - -VOID efivar_set_time_usec(CHAR16 *name, UINT64 usec) { - CHAR16 str[32]; - - if (usec == 0) - usec = time_usec(); - if (usec == 0) - return; - - SPrint(str, 32, L"%ld", usec); - efivar_set(name, str, FALSE); -} - -static INTN utf8_to_16(CHAR8 *stra, CHAR16 *c) { - CHAR16 unichar; - UINTN len; - UINTN i; - - if (stra[0] < 0x80) - len = 1; - else if ((stra[0] & 0xe0) == 0xc0) - len = 2; - else if ((stra[0] & 0xf0) == 0xe0) - len = 3; - else if ((stra[0] & 0xf8) == 0xf0) - len = 4; - else if ((stra[0] & 0xfc) == 0xf8) - len = 5; - else if ((stra[0] & 0xfe) == 0xfc) - len = 6; - else - return -1; - - switch (len) { - case 1: - unichar = stra[0]; - break; - case 2: - unichar = stra[0] & 0x1f; - break; - case 3: - unichar = stra[0] & 0x0f; - break; - case 4: - unichar = stra[0] & 0x07; - break; - case 5: - unichar = stra[0] & 0x03; - break; - case 6: - unichar = stra[0] & 0x01; - break; - } - - for (i = 1; i < len; i++) { - if ((stra[i] & 0xc0) != 0x80) - return -1; - unichar <<= 6; - unichar |= stra[i] & 0x3f; - } - - *c = unichar; - return len; -} - -CHAR16 *stra_to_str(CHAR8 *stra) { - UINTN strlen; - UINTN len; - UINTN i; - CHAR16 *str; - - len = strlena(stra); - str = AllocatePool((len + 1) * sizeof(CHAR16)); - - strlen = 0; - i = 0; - while (i < len) { - INTN utf8len; - - utf8len = utf8_to_16(stra + i, str + strlen); - if (utf8len <= 0) { - /* invalid utf8 sequence, skip the garbage */ - i++; - continue; - } - - strlen++; - i += utf8len; - } - str[strlen] = '\0'; - return str; -} - -CHAR16 *stra_to_path(CHAR8 *stra) { - CHAR16 *str; - UINTN strlen; - UINTN len; - UINTN i; - - len = strlena(stra); - str = AllocatePool((len + 2) * sizeof(CHAR16)); - - str[0] = '\\'; - strlen = 1; - i = 0; - while (i < len) { - INTN utf8len; - - utf8len = utf8_to_16(stra + i, str + strlen); - if (utf8len <= 0) { - /* invalid utf8 sequence, skip the garbage */ - i++; - continue; - } - - if (str[strlen] == '/') - str[strlen] = '\\'; - if (str[strlen] == '\\' && str[strlen-1] == '\\') { - /* skip double slashes */ - i += utf8len; - continue; - } - - strlen++; - i += utf8len; - } - str[strlen] = '\0'; - return str; -} - -CHAR8 *strchra(CHAR8 *s, CHAR8 c) { - do { - if (*s == c) - return s; - } while (*s++); - return NULL; -} - -INTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, UINTN off, UINTN size, CHAR8 **content) { - EFI_FILE_HANDLE handle; - CHAR8 *buf; - UINTN buflen; - EFI_STATUS err; - UINTN len; - - err = uefi_call_wrapper(dir->Open, 5, dir, &handle, name, EFI_FILE_MODE_READ, 0ULL); - if (EFI_ERROR(err)) - return err; - - if (size == 0) { - EFI_FILE_INFO *info; - - info = LibFileInfo(handle); - buflen = info->FileSize+1; - FreePool(info); - } else - buflen = size; - - if (off > 0) { - err = uefi_call_wrapper(handle->SetPosition, 2, handle, off); - if (EFI_ERROR(err)) - return err; - } - - buf = AllocatePool(buflen); - err = uefi_call_wrapper(handle->Read, 3, handle, &buflen, buf); - if (!EFI_ERROR(err)) { - buf[buflen] = '\0'; - *content = buf; - len = buflen; - } else { - len = err; - FreePool(buf); - } - - uefi_call_wrapper(handle->Close, 1, handle); - return len; -} diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h deleted file mode 100644 index e673cdf9a0..0000000000 --- a/src/boot/efi/util.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This program 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. - * - * This program 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. - * - * Copyright (C) 2012-2013 Kay Sievers - * Copyright (C) 2012 Harald Hoyer - */ - -#ifndef __SDBOOT_UTIL_H -#define __SDBOOT_UTIL_H - -#include -#include - -#define ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0])) - -static inline const CHAR16 *yes_no(BOOLEAN b) { - return b ? L"yes" : L"no"; -} - -EFI_STATUS parse_boolean(CHAR8 *v, BOOLEAN *b); - -UINT64 ticks_read(void); -UINT64 ticks_freq(void); -UINT64 time_usec(void); - -EFI_STATUS efivar_set(CHAR16 *name, CHAR16 *value, BOOLEAN persistent); -EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 *buf, UINTN size, BOOLEAN persistent); -EFI_STATUS efivar_set_int(CHAR16 *name, UINTN i, BOOLEAN persistent); -VOID efivar_set_time_usec(CHAR16 *name, UINT64 usec); - -EFI_STATUS efivar_get(CHAR16 *name, CHAR16 **value); -EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 **buffer, UINTN *size); -EFI_STATUS efivar_get_int(CHAR16 *name, UINTN *i); - -CHAR8 *strchra(CHAR8 *s, CHAR8 c); -CHAR16 *stra_to_path(CHAR8 *stra); -CHAR16 *stra_to_str(CHAR8 *stra); - -INTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, UINTN off, UINTN size, CHAR8 **content); -#endif diff --git a/src/busctl/Makefile b/src/busctl/Makefile new file mode 100644 index 0000000000..e7ef92824c --- /dev/null +++ b/src/busctl/Makefile @@ -0,0 +1,37 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +bin_PROGRAMS += \ + busctl + +busctl_SOURCES = \ + src/libsystemd/sd-bus/busctl.c \ + src/libsystemd/sd-bus/busctl-introspect.c \ + src/libsystemd/sd-bus/busctl-introspect.h + +busctl_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/busctl/busctl-introspect.c b/src/busctl/busctl-introspect.c new file mode 100644 index 0000000000..3c49ad5c57 --- /dev/null +++ b/src/busctl/busctl-introspect.c @@ -0,0 +1,790 @@ +/*** + 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 "alloc-util.h" +#include "busctl-introspect.h" +#include "string-util.h" +#include "util.h" +#include "xml.h" + +#define NODE_DEPTH_MAX 16 + +typedef struct Context { + const XMLIntrospectOps *ops; + void *userdata; + + char *interface_name; + uint64_t interface_flags; + + char *member_name; + char *member_signature; + char *member_result; + uint64_t member_flags; + bool member_writable; + + const char *current; + void *xml_state; +} Context; + +static void context_reset_member(Context *c) { + free(c->member_name); + free(c->member_signature); + free(c->member_result); + + c->member_name = c->member_signature = c->member_result = NULL; + c->member_flags = 0; + c->member_writable = false; +} + +static void context_reset_interface(Context *c) { + c->interface_name = mfree(c->interface_name); + c->interface_flags = 0; + + context_reset_member(c); +} + +static int parse_xml_annotation(Context *context, uint64_t *flags) { + + enum { + STATE_ANNOTATION, + STATE_NAME, + STATE_VALUE + } state = STATE_ANNOTATION; + + _cleanup_free_ char *field = NULL, *value = NULL; + + assert(context); + + for (;;) { + _cleanup_free_ char *name = NULL; + + int t; + + t = xml_tokenize(&context->current, &name, &context->xml_state, NULL); + if (t < 0) { + log_error("XML parse error."); + return t; + } + + if (t == XML_END) { + log_error("Premature end of XML data."); + return -EBADMSG; + } + + switch (state) { + + case STATE_ANNOTATION: + + if (t == XML_ATTRIBUTE_NAME) { + + if (streq_ptr(name, "name")) + state = STATE_NAME; + + else if (streq_ptr(name, "value")) + state = STATE_VALUE; + + else { + log_error("Unexpected attribute %s.", name); + return -EBADMSG; + } + + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "annotation"))) { + + if (flags) { + if (streq_ptr(field, "org.freedesktop.DBus.Deprecated")) { + + if (streq_ptr(value, "true")) + *flags |= SD_BUS_VTABLE_DEPRECATED; + + } else if (streq_ptr(field, "org.freedesktop.DBus.Method.NoReply")) { + + if (streq_ptr(value, "true")) + *flags |= SD_BUS_VTABLE_METHOD_NO_REPLY; + + } else if (streq_ptr(field, "org.freedesktop.DBus.Property.EmitsChangedSignal")) { + + if (streq_ptr(value, "const")) + *flags = (*flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) | SD_BUS_VTABLE_PROPERTY_CONST; + else if (streq_ptr(value, "invalidates")) + *flags = (*flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_CONST)) | SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION; + else if (streq_ptr(value, "false")) + *flags = *flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION); + } + } + + return 0; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token in . (1)"); + return -EINVAL; + } + + break; + + case STATE_NAME: + + if (t == XML_ATTRIBUTE_VALUE) { + free(field); + field = name; + name = NULL; + + state = STATE_ANNOTATION; + } else { + log_error("Unexpected token in . (2)"); + return -EINVAL; + } + + break; + + case STATE_VALUE: + + if (t == XML_ATTRIBUTE_VALUE) { + free(value); + value = name; + name = NULL; + + state = STATE_ANNOTATION; + } else { + log_error("Unexpected token in . (3)"); + return -EINVAL; + } + + break; + + default: + assert_not_reached("Bad state"); + } + } +} + +static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth) { + + enum { + STATE_NODE, + STATE_NODE_NAME, + STATE_INTERFACE, + STATE_INTERFACE_NAME, + STATE_METHOD, + STATE_METHOD_NAME, + STATE_METHOD_ARG, + STATE_METHOD_ARG_NAME, + STATE_METHOD_ARG_TYPE, + STATE_METHOD_ARG_DIRECTION, + STATE_SIGNAL, + STATE_SIGNAL_NAME, + STATE_SIGNAL_ARG, + STATE_SIGNAL_ARG_NAME, + STATE_SIGNAL_ARG_TYPE, + STATE_PROPERTY, + STATE_PROPERTY_NAME, + STATE_PROPERTY_TYPE, + STATE_PROPERTY_ACCESS, + } state = STATE_NODE; + + _cleanup_free_ char *node_path = NULL, *argument_type = NULL, *argument_direction = NULL; + const char *np = prefix; + int r; + + assert(context); + assert(prefix); + + if (n_depth > NODE_DEPTH_MAX) { + log_error(" depth too high."); + return -EINVAL; + } + + for (;;) { + _cleanup_free_ char *name = NULL; + int t; + + t = xml_tokenize(&context->current, &name, &context->xml_state, NULL); + if (t < 0) { + log_error("XML parse error."); + return t; + } + + if (t == XML_END) { + log_error("Premature end of XML data."); + return -EBADMSG; + } + + switch (state) { + + case STATE_NODE: + if (t == XML_ATTRIBUTE_NAME) { + + if (streq_ptr(name, "name")) + state = STATE_NODE_NAME; + else { + log_error("Unexpected attribute %s.", name); + return -EBADMSG; + } + + } else if (t == XML_TAG_OPEN) { + + if (streq_ptr(name, "interface")) + state = STATE_INTERFACE; + else if (streq_ptr(name, "node")) { + + r = parse_xml_node(context, np, n_depth+1); + if (r < 0) + return r; + } else { + log_error("Unexpected tag %s.", name); + return -EBADMSG; + } + + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "node"))) { + + if (context->ops->on_path) { + r = context->ops->on_path(node_path ? node_path : np, context->userdata); + if (r < 0) + return r; + } + + return 0; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token in . (1)"); + return -EINVAL; + } + + break; + + case STATE_NODE_NAME: + + if (t == XML_ATTRIBUTE_VALUE) { + + free(node_path); + + if (name[0] == '/') { + node_path = name; + name = NULL; + } else { + + if (endswith(prefix, "/")) + node_path = strappend(prefix, name); + else + node_path = strjoin(prefix, "/", name, NULL); + if (!node_path) + return log_oom(); + } + + np = node_path; + state = STATE_NODE; + } else { + log_error("Unexpected token in . (2)"); + return -EINVAL; + } + + break; + + case STATE_INTERFACE: + + if (t == XML_ATTRIBUTE_NAME) { + if (streq_ptr(name, "name")) + state = STATE_INTERFACE_NAME; + else { + log_error("Unexpected attribute %s.", name); + return -EBADMSG; + } + + } else if (t == XML_TAG_OPEN) { + if (streq_ptr(name, "method")) + state = STATE_METHOD; + else if (streq_ptr(name, "signal")) + state = STATE_SIGNAL; + else if (streq_ptr(name, "property")) { + context->member_flags |= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE; + state = STATE_PROPERTY; + } else if (streq_ptr(name, "annotation")) { + r = parse_xml_annotation(context, &context->interface_flags); + if (r < 0) + return r; + } else { + log_error("Unexpected tag %s.", name); + return -EINVAL; + } + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "interface"))) { + + if (n_depth == 0) { + if (context->ops->on_interface) { + r = context->ops->on_interface(context->interface_name, context->interface_flags, context->userdata); + if (r < 0) + return r; + } + + context_reset_interface(context); + } + + state = STATE_NODE; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token in . (1)"); + return -EINVAL; + } + + break; + + case STATE_INTERFACE_NAME: + + if (t == XML_ATTRIBUTE_VALUE) { + if (n_depth == 0) { + free(context->interface_name); + context->interface_name = name; + name = NULL; + } + + state = STATE_INTERFACE; + } else { + log_error("Unexpected token in . (2)"); + return -EINVAL; + } + + break; + + case STATE_METHOD: + + if (t == XML_ATTRIBUTE_NAME) { + if (streq_ptr(name, "name")) + state = STATE_METHOD_NAME; + else { + log_error("Unexpected attribute %s", name); + return -EBADMSG; + } + } else if (t == XML_TAG_OPEN) { + if (streq_ptr(name, "arg")) + state = STATE_METHOD_ARG; + else if (streq_ptr(name, "annotation")) { + r = parse_xml_annotation(context, &context->member_flags); + if (r < 0) + return r; + } else { + log_error("Unexpected tag %s.", name); + return -EINVAL; + } + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "method"))) { + + if (n_depth == 0) { + if (context->ops->on_method) { + r = context->ops->on_method(context->interface_name, context->member_name, context->member_signature, context->member_result, context->member_flags, context->userdata); + if (r < 0) + return r; + } + + context_reset_member(context); + } + + state = STATE_INTERFACE; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token in (1)."); + return -EINVAL; + } + + break; + + case STATE_METHOD_NAME: + + if (t == XML_ATTRIBUTE_VALUE) { + + if (n_depth == 0) { + free(context->member_name); + context->member_name = name; + name = NULL; + } + + state = STATE_METHOD; + } else { + log_error("Unexpected token in (2)."); + return -EINVAL; + } + + break; + + case STATE_METHOD_ARG: + + if (t == XML_ATTRIBUTE_NAME) { + if (streq_ptr(name, "name")) + state = STATE_METHOD_ARG_NAME; + else if (streq_ptr(name, "type")) + state = STATE_METHOD_ARG_TYPE; + else if (streq_ptr(name, "direction")) + state = STATE_METHOD_ARG_DIRECTION; + else { + log_error("Unexpected method attribute %s.", name); + return -EBADMSG; + } + } else if (t == XML_TAG_OPEN) { + if (streq_ptr(name, "annotation")) { + r = parse_xml_annotation(context, NULL); + if (r < 0) + return r; + } else { + log_error("Unexpected method tag %s.", name); + return -EINVAL; + } + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) { + + if (n_depth == 0) { + + if (argument_type) { + if (!argument_direction || streq(argument_direction, "in")) { + if (!strextend(&context->member_signature, argument_type, NULL)) + return log_oom(); + } else if (streq(argument_direction, "out")) { + if (!strextend(&context->member_result, argument_type, NULL)) + return log_oom(); + } + } + + argument_type = mfree(argument_type); + argument_direction = mfree(argument_direction); + } + + state = STATE_METHOD; + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token in method . (1)"); + return -EINVAL; + } + + break; + + case STATE_METHOD_ARG_NAME: + + if (t == XML_ATTRIBUTE_VALUE) + state = STATE_METHOD_ARG; + else { + log_error("Unexpected token in method . (2)"); + return -EINVAL; + } + + break; + + case STATE_METHOD_ARG_TYPE: + + if (t == XML_ATTRIBUTE_VALUE) { + free(argument_type); + argument_type = name; + name = NULL; + + state = STATE_METHOD_ARG; + } else { + log_error("Unexpected token in method . (3)"); + return -EINVAL; + } + + break; + + case STATE_METHOD_ARG_DIRECTION: + + if (t == XML_ATTRIBUTE_VALUE) { + free(argument_direction); + argument_direction = name; + name = NULL; + + state = STATE_METHOD_ARG; + } else { + log_error("Unexpected token in method . (4)"); + return -EINVAL; + } + + break; + + case STATE_SIGNAL: + + if (t == XML_ATTRIBUTE_NAME) { + if (streq_ptr(name, "name")) + state = STATE_SIGNAL_NAME; + else { + log_error("Unexpected attribute %s.", name); + return -EBADMSG; + } + } else if (t == XML_TAG_OPEN) { + if (streq_ptr(name, "arg")) + state = STATE_SIGNAL_ARG; + else if (streq_ptr(name, "annotation")) { + r = parse_xml_annotation(context, &context->member_flags); + if (r < 0) + return r; + } else { + log_error("Unexpected tag %s.", name); + return -EINVAL; + } + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "signal"))) { + + if (n_depth == 0) { + if (context->ops->on_signal) { + r = context->ops->on_signal(context->interface_name, context->member_name, context->member_signature, context->member_flags, context->userdata); + if (r < 0) + return r; + } + + context_reset_member(context); + } + + state = STATE_INTERFACE; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token in . (1)"); + return -EINVAL; + } + + break; + + case STATE_SIGNAL_NAME: + + if (t == XML_ATTRIBUTE_VALUE) { + + if (n_depth == 0) { + free(context->member_name); + context->member_name = name; + name = NULL; + } + + state = STATE_SIGNAL; + } else { + log_error("Unexpected token in . (2)"); + return -EINVAL; + } + + break; + + + case STATE_SIGNAL_ARG: + + if (t == XML_ATTRIBUTE_NAME) { + if (streq_ptr(name, "name")) + state = STATE_SIGNAL_ARG_NAME; + else if (streq_ptr(name, "type")) + state = STATE_SIGNAL_ARG_TYPE; + else { + log_error("Unexpected signal attribute %s.", name); + return -EBADMSG; + } + } else if (t == XML_TAG_OPEN) { + if (streq_ptr(name, "annotation")) { + r = parse_xml_annotation(context, NULL); + if (r < 0) + return r; + } else { + log_error("Unexpected signal tag %s.", name); + return -EINVAL; + } + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) { + + if (argument_type) { + if (!strextend(&context->member_signature, argument_type, NULL)) + return log_oom(); + + argument_type = mfree(argument_type); + } + + state = STATE_SIGNAL; + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token in signal (1)."); + return -EINVAL; + } + + break; + + case STATE_SIGNAL_ARG_NAME: + + if (t == XML_ATTRIBUTE_VALUE) + state = STATE_SIGNAL_ARG; + else { + log_error("Unexpected token in signal (2)."); + return -EINVAL; + } + + break; + + case STATE_SIGNAL_ARG_TYPE: + + if (t == XML_ATTRIBUTE_VALUE) { + free(argument_type); + argument_type = name; + name = NULL; + + state = STATE_SIGNAL_ARG; + } else { + log_error("Unexpected token in signal (3)."); + return -EINVAL; + } + + break; + + case STATE_PROPERTY: + + if (t == XML_ATTRIBUTE_NAME) { + if (streq_ptr(name, "name")) + state = STATE_PROPERTY_NAME; + else if (streq_ptr(name, "type")) + state = STATE_PROPERTY_TYPE; + else if (streq_ptr(name, "access")) + state = STATE_PROPERTY_ACCESS; + else { + log_error("Unexpected attribute %s.", name); + return -EBADMSG; + } + } else if (t == XML_TAG_OPEN) { + + if (streq_ptr(name, "annotation")) { + r = parse_xml_annotation(context, &context->member_flags); + if (r < 0) + return r; + } else { + log_error("Unexpected tag %s.", name); + return -EINVAL; + } + + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "property"))) { + + if (n_depth == 0) { + if (context->ops->on_property) { + r = context->ops->on_property(context->interface_name, context->member_name, context->member_signature, context->member_writable, context->member_flags, context->userdata); + if (r < 0) + return r; + } + + context_reset_member(context); + } + + state = STATE_INTERFACE; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token in . (1)"); + return -EINVAL; + } + + break; + + case STATE_PROPERTY_NAME: + + if (t == XML_ATTRIBUTE_VALUE) { + + if (n_depth == 0) { + free(context->member_name); + context->member_name = name; + name = NULL; + } + state = STATE_PROPERTY; + } else { + log_error("Unexpected token in . (2)"); + return -EINVAL; + } + + break; + + case STATE_PROPERTY_TYPE: + + if (t == XML_ATTRIBUTE_VALUE) { + + if (n_depth == 0) { + free(context->member_signature); + context->member_signature = name; + name = NULL; + } + + state = STATE_PROPERTY; + } else { + log_error("Unexpected token in . (3)"); + return -EINVAL; + } + + break; + + case STATE_PROPERTY_ACCESS: + + if (t == XML_ATTRIBUTE_VALUE) { + + if (streq(name, "readwrite") || streq(name, "write")) + context->member_writable = true; + + state = STATE_PROPERTY; + } else { + log_error("Unexpected token in . (4)"); + return -EINVAL; + } + + break; + } + } +} + +int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospectOps *ops, void *userdata) { + Context context = { + .ops = ops, + .userdata = userdata, + .current = xml, + }; + + int r; + + assert(prefix); + assert(xml); + assert(ops); + + for (;;) { + _cleanup_free_ char *name = NULL; + + r = xml_tokenize(&context.current, &name, &context.xml_state, NULL); + if (r < 0) { + log_error("XML parse error"); + goto finish; + } + + if (r == XML_END) { + r = 0; + break; + } + + if (r == XML_TAG_OPEN) { + + if (streq(name, "node")) { + r = parse_xml_node(&context, prefix, 0); + if (r < 0) + goto finish; + } else { + log_error("Unexpected tag '%s' in introspection data.", name); + r = -EBADMSG; + goto finish; + } + } else if (r != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token."); + r = -EBADMSG; + goto finish; + } + } + +finish: + context_reset_interface(&context); + + return r; +} diff --git a/src/busctl/busctl-introspect.h b/src/busctl/busctl-introspect.h new file mode 100644 index 0000000000..d922e352db --- /dev/null +++ b/src/busctl/busctl-introspect.h @@ -0,0 +1,32 @@ +#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 + +typedef struct XMLIntrospectOps { + int (*on_path)(const char *path, void *userdata); + int (*on_interface)(const char *name, uint64_t flags, void *userdata); + int (*on_method)(const char *interface, const char *name, const char *signature, const char *result, uint64_t flags, void *userdata); + int (*on_signal)(const char *interface, const char *name, const char *signature, uint64_t flags, void *userdata); + int (*on_property)(const char *interface, const char *name, const char *signature, bool writable, uint64_t flags, void *userdata); +} XMLIntrospectOps; + +int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospectOps *ops, void *userdata); diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c new file mode 100644 index 0000000000..0281409edf --- /dev/null +++ b/src/busctl/busctl.c @@ -0,0 +1,2086 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include "alloc-util.h" +#include "bus-dump.h" +#include "bus-internal.h" +#include "bus-signature.h" +#include "bus-type.h" +#include "bus-util.h" +#include "busctl-introspect.h" +#include "escape.h" +#include "fd-util.h" +#include "locale-util.h" +#include "log.h" +#include "pager.h" +#include "parse-util.h" +#include "path-util.h" +#include "set.h" +#include "strv.h" +#include "terminal-util.h" +#include "user-util.h" +#include "util.h" + +static bool arg_no_pager = false; +static bool arg_legend = true; +static char *arg_address = NULL; +static bool arg_unique = false; +static bool arg_acquired = false; +static bool arg_activatable = false; +static bool arg_show_machine = false; +static char **arg_matches = NULL; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static char *arg_host = NULL; +static bool arg_user = false; +static size_t arg_snaplen = 4096; +static bool arg_list = false; +static bool arg_quiet = false; +static bool arg_verbose = false; +static bool arg_expect_reply = true; +static bool arg_auto_start = true; +static bool arg_allow_interactive_authorization = true; +static bool arg_augment_creds = true; +static usec_t arg_timeout = 0; + +#define NAME_IS_ACQUIRED INT_TO_PTR(1) +#define NAME_IS_ACTIVATABLE INT_TO_PTR(2) + +static int list_bus_names(sd_bus *bus, char **argv) { + _cleanup_strv_free_ char **acquired = NULL, **activatable = NULL; + _cleanup_free_ char **merged = NULL; + _cleanup_hashmap_free_ Hashmap *names = NULL; + char **i; + int r; + size_t max_i = 0; + unsigned n = 0; + void *v; + char *k; + Iterator iterator; + + assert(bus); + + if (!arg_unique && !arg_acquired && !arg_activatable) + arg_unique = arg_acquired = arg_activatable = true; + + r = sd_bus_list_names(bus, (arg_acquired || arg_unique) ? &acquired : NULL, arg_activatable ? &activatable : NULL); + if (r < 0) + return log_error_errno(r, "Failed to list names: %m"); + + pager_open(arg_no_pager, false); + + names = hashmap_new(&string_hash_ops); + if (!names) + return log_oom(); + + STRV_FOREACH(i, acquired) { + max_i = MAX(max_i, strlen(*i)); + + r = hashmap_put(names, *i, NAME_IS_ACQUIRED); + if (r < 0) + return log_error_errno(r, "Failed to add to hashmap: %m"); + } + + STRV_FOREACH(i, activatable) { + max_i = MAX(max_i, strlen(*i)); + + r = hashmap_put(names, *i, NAME_IS_ACTIVATABLE); + if (r < 0 && r != -EEXIST) + return log_error_errno(r, "Failed to add to hashmap: %m"); + } + + merged = new(char*, hashmap_size(names) + 1); + HASHMAP_FOREACH_KEY(v, k, names, iterator) + merged[n++] = k; + + merged[n] = NULL; + strv_sort(merged); + + if (arg_legend) { + printf("%-*s %*s %-*s %-*s %-*s %-*s %-*s %-*s", + (int) max_i, "NAME", 10, "PID", 15, "PROCESS", 16, "USER", 13, "CONNECTION", 25, "UNIT", 10, "SESSION", 19, "DESCRIPTION"); + + if (arg_show_machine) + puts(" MACHINE"); + else + putchar('\n'); + } + + STRV_FOREACH(i, merged) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + sd_id128_t mid; + + if (hashmap_get(names, *i) == NAME_IS_ACTIVATABLE) { + /* Activatable */ + + printf("%-*s", (int) max_i, *i); + printf(" - - - (activatable) - - "); + if (arg_show_machine) + puts(" -"); + else + putchar('\n'); + continue; + + } + + if (!arg_unique && (*i)[0] == ':') + continue; + + if (!arg_acquired && (*i)[0] != ':') + continue; + + printf("%-*s", (int) max_i, *i); + + r = sd_bus_get_name_creds( + bus, *i, + (arg_augment_creds ? SD_BUS_CREDS_AUGMENT : 0) | + SD_BUS_CREDS_EUID|SD_BUS_CREDS_PID|SD_BUS_CREDS_COMM| + SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_SESSION| + SD_BUS_CREDS_DESCRIPTION, &creds); + if (r >= 0) { + const char *unique, *session, *unit, *cn; + pid_t pid; + uid_t uid; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r >= 0) { + const char *comm = NULL; + + sd_bus_creds_get_comm(creds, &comm); + + printf(" %10lu %-15s", (unsigned long) pid, strna(comm)); + } else + fputs(" - - ", stdout); + + r = sd_bus_creds_get_euid(creds, &uid); + if (r >= 0) { + _cleanup_free_ char *u = NULL; + + u = uid_to_name(uid); + if (!u) + return log_oom(); + + if (strlen(u) > 16) + u[16] = 0; + + printf(" %-16s", u); + } else + fputs(" - ", stdout); + + r = sd_bus_creds_get_unique_name(creds, &unique); + if (r >= 0) + printf(" %-13s", unique); + else + fputs(" - ", stdout); + + r = sd_bus_creds_get_unit(creds, &unit); + if (r >= 0) { + _cleanup_free_ char *e; + + e = ellipsize(unit, 25, 100); + if (!e) + return log_oom(); + + printf(" %-25s", e); + } else + fputs(" - ", stdout); + + r = sd_bus_creds_get_session(creds, &session); + if (r >= 0) + printf(" %-10s", session); + else + fputs(" - ", stdout); + + r = sd_bus_creds_get_description(creds, &cn); + if (r >= 0) + printf(" %-19s", cn); + else + fputs(" - ", stdout); + + } else + printf(" - - - - - - - "); + + if (arg_show_machine) { + r = sd_bus_get_name_machine_id(bus, *i, &mid); + if (r >= 0) { + char m[SD_ID128_STRING_MAX]; + printf(" %s\n", sd_id128_to_string(mid, m)); + } else + puts(" -"); + } else + putchar('\n'); + } + + return 0; +} + +static void print_subtree(const char *prefix, const char *path, char **l) { + const char *vertical, *space; + char **n; + + /* We assume the list is sorted. Let's first skip over the + * entry we are looking at. */ + for (;;) { + if (!*l) + return; + + if (!streq(*l, path)) + break; + + l++; + } + + vertical = strjoina(prefix, special_glyph(TREE_VERTICAL)); + space = strjoina(prefix, special_glyph(TREE_SPACE)); + + for (;;) { + bool has_more = false; + + if (!*l || !path_startswith(*l, path)) + break; + + n = l + 1; + for (;;) { + if (!*n || !path_startswith(*n, path)) + break; + + if (!path_startswith(*n, *l)) { + has_more = true; + break; + } + + n++; + } + + printf("%s%s%s\n", prefix, special_glyph(has_more ? TREE_BRANCH : TREE_RIGHT), *l); + + print_subtree(has_more ? vertical : space, *l, l); + l = n; + } +} + +static void print_tree(const char *prefix, char **l) { + + pager_open(arg_no_pager, false); + + prefix = strempty(prefix); + + if (arg_list) { + char **i; + + STRV_FOREACH(i, l) + printf("%s%s\n", prefix, *i); + return; + } + + if (strv_isempty(l)) { + printf("No objects discovered.\n"); + return; + } + + if (streq(l[0], "/") && !l[1]) { + printf("Only root object discovered.\n"); + return; + } + + print_subtree(prefix, "/", l); +} + +static int on_path(const char *path, void *userdata) { + Set *paths = userdata; + int r; + + assert(paths); + + r = set_put_strdup(paths, path); + if (r < 0) + return log_oom(); + + return 0; +} + +static int find_nodes(sd_bus *bus, const char *service, const char *path, Set *paths, bool many) { + static const XMLIntrospectOps ops = { + .on_path = on_path, + }; + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *xml; + int r; + + r = sd_bus_call_method(bus, service, path, "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); + if (r < 0) { + if (many) + printf("Failed to introspect object %s of service %s: %s\n", path, service, bus_error_message(&error, r)); + else + log_error("Failed to introspect object %s of service %s: %s", path, service, bus_error_message(&error, r)); + return r; + } + + r = sd_bus_message_read(reply, "s", &xml); + if (r < 0) + return bus_log_parse_error(r); + + return parse_xml_introspect(path, xml, &ops, paths); +} + +static int tree_one(sd_bus *bus, const char *service, const char *prefix, bool many) { + _cleanup_set_free_free_ Set *paths = NULL, *done = NULL, *failed = NULL; + _cleanup_free_ char **l = NULL; + char *m; + int r; + + paths = set_new(&string_hash_ops); + if (!paths) + return log_oom(); + + done = set_new(&string_hash_ops); + if (!done) + return log_oom(); + + failed = set_new(&string_hash_ops); + if (!failed) + return log_oom(); + + m = strdup("/"); + if (!m) + return log_oom(); + + r = set_put(paths, m); + if (r < 0) { + free(m); + return log_oom(); + } + + for (;;) { + _cleanup_free_ char *p = NULL; + int q; + + p = set_steal_first(paths); + if (!p) + break; + + if (set_contains(done, p) || + set_contains(failed, p)) + continue; + + q = find_nodes(bus, service, p, paths, many); + if (q < 0) { + if (r >= 0) + r = q; + + q = set_put(failed, p); + } else + q = set_put(done, p); + + if (q < 0) + return log_oom(); + + assert(q != 0); + p = NULL; + } + + pager_open(arg_no_pager, false); + + l = set_get_strv(done); + if (!l) + return log_oom(); + + strv_sort(l); + print_tree(prefix, l); + + fflush(stdout); + + return r; +} + +static int tree(sd_bus *bus, char **argv) { + char **i; + int r = 0; + + if (!arg_unique && !arg_acquired) + arg_acquired = true; + + if (strv_length(argv) <= 1) { + _cleanup_strv_free_ char **names = NULL; + bool not_first = false; + + r = sd_bus_list_names(bus, &names, NULL); + if (r < 0) + return log_error_errno(r, "Failed to get name list: %m"); + + pager_open(arg_no_pager, false); + + STRV_FOREACH(i, names) { + int q; + + if (!arg_unique && (*i)[0] == ':') + continue; + + if (!arg_acquired && (*i)[0] == ':') + continue; + + if (not_first) + printf("\n"); + + printf("Service %s%s%s:\n", ansi_highlight(), *i, ansi_normal()); + + q = tree_one(bus, *i, NULL, true); + if (q < 0 && r >= 0) + r = q; + + not_first = true; + } + } else { + STRV_FOREACH(i, argv+1) { + int q; + + if (i > argv+1) + printf("\n"); + + if (argv[2]) { + pager_open(arg_no_pager, false); + printf("Service %s%s%s:\n", ansi_highlight(), *i, ansi_normal()); + } + + q = tree_one(bus, *i, NULL, !!argv[2]); + if (q < 0 && r >= 0) + r = q; + } + } + + return r; +} + +static int format_cmdline(sd_bus_message *m, FILE *f, bool needs_space) { + int r; + + for (;;) { + const char *contents = NULL; + char type; + union { + uint8_t u8; + uint16_t u16; + int16_t s16; + uint32_t u32; + int32_t s32; + uint64_t u64; + int64_t s64; + double d64; + const char *string; + int i; + } basic; + + r = sd_bus_message_peek_type(m, &type, &contents); + if (r < 0) + return r; + if (r == 0) + return needs_space; + + if (bus_type_is_container(type) > 0) { + + r = sd_bus_message_enter_container(m, type, contents); + if (r < 0) + return r; + + if (type == SD_BUS_TYPE_ARRAY) { + unsigned n = 0; + + /* count array entries */ + for (;;) { + + r = sd_bus_message_skip(m, contents); + if (r < 0) + return r; + if (r == 0) + break; + + n++; + } + + r = sd_bus_message_rewind(m, false); + if (r < 0) + return r; + + if (needs_space) + fputc(' ', f); + + fprintf(f, "%u", n); + needs_space = true; + + } else if (type == SD_BUS_TYPE_VARIANT) { + + if (needs_space) + fputc(' ', f); + + fprintf(f, "%s", contents); + needs_space = true; + } + + r = format_cmdline(m, f, needs_space); + if (r < 0) + return r; + + needs_space = r > 0; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + continue; + } + + r = sd_bus_message_read_basic(m, type, &basic); + if (r < 0) + return r; + + if (needs_space) + fputc(' ', f); + + switch (type) { + case SD_BUS_TYPE_BYTE: + fprintf(f, "%u", basic.u8); + break; + + case SD_BUS_TYPE_BOOLEAN: + fputs(true_false(basic.i), f); + break; + + case SD_BUS_TYPE_INT16: + fprintf(f, "%i", basic.s16); + break; + + case SD_BUS_TYPE_UINT16: + fprintf(f, "%u", basic.u16); + break; + + case SD_BUS_TYPE_INT32: + fprintf(f, "%i", basic.s32); + break; + + case SD_BUS_TYPE_UINT32: + fprintf(f, "%u", basic.u32); + break; + + case SD_BUS_TYPE_INT64: + fprintf(f, "%" PRIi64, basic.s64); + break; + + case SD_BUS_TYPE_UINT64: + fprintf(f, "%" PRIu64, basic.u64); + break; + + case SD_BUS_TYPE_DOUBLE: + fprintf(f, "%g", basic.d64); + break; + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: { + _cleanup_free_ char *b = NULL; + + b = cescape(basic.string); + if (!b) + return -ENOMEM; + + fprintf(f, "\"%s\"", b); + break; + } + + case SD_BUS_TYPE_UNIX_FD: + fprintf(f, "%i", basic.i); + break; + + default: + assert_not_reached("Unknown basic type."); + } + + needs_space = true; + } +} + +typedef struct Member { + const char *type; + char *interface; + char *name; + char *signature; + char *result; + char *value; + bool writable; + uint64_t flags; +} Member; + +static void member_hash_func(const void *p, struct siphash *state) { + const Member *m = p; + uint64_t arity = 1; + + assert(m); + assert(m->type); + + string_hash_func(m->type, state); + + arity += !!m->name + !!m->interface; + + uint64_hash_func(&arity, state); + + if (m->name) + string_hash_func(m->name, state); + + if (m->interface) + string_hash_func(m->interface, state); +} + +static int member_compare_func(const void *a, const void *b) { + const Member *x = a, *y = b; + int d; + + assert(x); + assert(y); + assert(x->type); + assert(y->type); + + d = strcmp_ptr(x->interface, y->interface); + if (d != 0) + return d; + + d = strcmp(x->type, y->type); + if (d != 0) + return d; + + return strcmp_ptr(x->name, y->name); +} + +static int member_compare_funcp(const void *a, const void *b) { + const Member *const * x = (const Member *const *) a, * const *y = (const Member *const *) b; + + return member_compare_func(*x, *y); +} + +static void member_free(Member *m) { + if (!m) + return; + + free(m->interface); + free(m->name); + free(m->signature); + free(m->result); + free(m->value); + free(m); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Member*, member_free); + +static void member_set_free(Set *s) { + Member *m; + + while ((m = set_steal_first(s))) + member_free(m); + + set_free(s); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, member_set_free); + +static int on_interface(const char *interface, uint64_t flags, void *userdata) { + _cleanup_(member_freep) Member *m; + Set *members = userdata; + int r; + + assert(interface); + assert(members); + + m = new0(Member, 1); + if (!m) + return log_oom(); + + m->type = "interface"; + m->flags = flags; + + r = free_and_strdup(&m->interface, interface); + if (r < 0) + return log_oom(); + + r = set_put(members, m); + if (r <= 0) { + log_error("Duplicate interface"); + return -EINVAL; + } + + m = NULL; + return 0; +} + +static int on_method(const char *interface, const char *name, const char *signature, const char *result, uint64_t flags, void *userdata) { + _cleanup_(member_freep) Member *m; + Set *members = userdata; + int r; + + assert(interface); + assert(name); + + m = new0(Member, 1); + if (!m) + return log_oom(); + + m->type = "method"; + m->flags = flags; + + r = free_and_strdup(&m->interface, interface); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->name, name); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->signature, signature); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->result, result); + if (r < 0) + return log_oom(); + + r = set_put(members, m); + if (r <= 0) { + log_error("Duplicate method"); + return -EINVAL; + } + + m = NULL; + return 0; +} + +static int on_signal(const char *interface, const char *name, const char *signature, uint64_t flags, void *userdata) { + _cleanup_(member_freep) Member *m; + Set *members = userdata; + int r; + + assert(interface); + assert(name); + + m = new0(Member, 1); + if (!m) + return log_oom(); + + m->type = "signal"; + m->flags = flags; + + r = free_and_strdup(&m->interface, interface); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->name, name); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->signature, signature); + if (r < 0) + return log_oom(); + + r = set_put(members, m); + if (r <= 0) { + log_error("Duplicate signal"); + return -EINVAL; + } + + m = NULL; + return 0; +} + +static int on_property(const char *interface, const char *name, const char *signature, bool writable, uint64_t flags, void *userdata) { + _cleanup_(member_freep) Member *m; + Set *members = userdata; + int r; + + assert(interface); + assert(name); + + m = new0(Member, 1); + if (!m) + return log_oom(); + + m->type = "property"; + m->flags = flags; + m->writable = writable; + + r = free_and_strdup(&m->interface, interface); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->name, name); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->signature, signature); + if (r < 0) + return log_oom(); + + r = set_put(members, m); + if (r <= 0) { + log_error("Duplicate property"); + return -EINVAL; + } + + m = NULL; + return 0; +} + +static const char *strdash(const char *x) { + return isempty(x) ? "-" : x; +} + +static int introspect(sd_bus *bus, char **argv) { + static const struct hash_ops member_hash_ops = { + .hash = member_hash_func, + .compare = member_compare_func, + }; + + static const XMLIntrospectOps ops = { + .on_interface = on_interface, + .on_method = on_method, + .on_signal = on_signal, + .on_property = on_property, + }; + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(member_set_freep) Set *members = NULL; + Iterator i; + Member *m; + const char *xml; + int r; + unsigned name_width, type_width, signature_width, result_width; + Member **sorted = NULL; + unsigned k = 0, j, n_args; + + n_args = strv_length(argv); + if (n_args < 3) { + log_error("Requires service and object path argument."); + return -EINVAL; + } + + if (n_args > 4) { + log_error("Too many arguments."); + return -EINVAL; + } + + members = set_new(&member_hash_ops); + if (!members) + return log_oom(); + + r = sd_bus_call_method(bus, argv[1], argv[2], "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); + if (r < 0) { + log_error("Failed to introspect object %s of service %s: %s", argv[2], argv[1], bus_error_message(&error, r)); + return r; + } + + r = sd_bus_message_read(reply, "s", &xml); + if (r < 0) + return bus_log_parse_error(r); + + /* First, get list of all properties */ + r = parse_xml_introspect(argv[2], xml, &ops, members); + if (r < 0) + return r; + + /* Second, find the current values for them */ + SET_FOREACH(m, members, i) { + + if (!streq(m->type, "property")) + continue; + + if (m->value) + continue; + + if (argv[3] && !streq(argv[3], m->interface)) + continue; + + r = sd_bus_call_method(bus, argv[1], argv[2], "org.freedesktop.DBus.Properties", "GetAll", &error, &reply, "s", m->interface); + if (r < 0) { + log_error("%s", bus_error_message(&error, r)); + return r; + } + + r = sd_bus_message_enter_container(reply, 'a', "{sv}"); + if (r < 0) + return bus_log_parse_error(r); + + for (;;) { + Member *z; + _cleanup_free_ char *buf = NULL; + _cleanup_fclose_ FILE *mf = NULL; + size_t sz = 0; + const char *name; + + r = sd_bus_message_enter_container(reply, 'e', "sv"); + if (r < 0) + return bus_log_parse_error(r); + + if (r == 0) + break; + + r = sd_bus_message_read(reply, "s", &name); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_enter_container(reply, 'v', NULL); + if (r < 0) + return bus_log_parse_error(r); + + mf = open_memstream(&buf, &sz); + if (!mf) + return log_oom(); + + r = format_cmdline(reply, mf, false); + if (r < 0) + return bus_log_parse_error(r); + + fclose(mf); + mf = NULL; + + z = set_get(members, &((Member) { + .type = "property", + .interface = m->interface, + .name = (char*) name })); + if (z) { + free(z->value); + z->value = buf; + buf = NULL; + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + } + + pager_open(arg_no_pager, false); + + name_width = strlen("NAME"); + type_width = strlen("TYPE"); + signature_width = strlen("SIGNATURE"); + result_width = strlen("RESULT/VALUE"); + + sorted = newa(Member*, set_size(members)); + + SET_FOREACH(m, members, i) { + + if (argv[3] && !streq(argv[3], m->interface)) + continue; + + if (m->interface) + name_width = MAX(name_width, strlen(m->interface)); + if (m->name) + name_width = MAX(name_width, strlen(m->name) + 1); + if (m->type) + type_width = MAX(type_width, strlen(m->type)); + if (m->signature) + signature_width = MAX(signature_width, strlen(m->signature)); + if (m->result) + result_width = MAX(result_width, strlen(m->result)); + if (m->value) + result_width = MAX(result_width, strlen(m->value)); + + sorted[k++] = m; + } + + if (result_width > 40) + result_width = 40; + + qsort(sorted, k, sizeof(Member*), member_compare_funcp); + + if (arg_legend) { + printf("%-*s %-*s %-*s %-*s %s\n", + (int) name_width, "NAME", + (int) type_width, "TYPE", + (int) signature_width, "SIGNATURE", + (int) result_width, "RESULT/VALUE", + "FLAGS"); + } + + for (j = 0; j < k; j++) { + _cleanup_free_ char *ellipsized = NULL; + const char *rv; + bool is_interface; + + m = sorted[j]; + + if (argv[3] && !streq(argv[3], m->interface)) + continue; + + is_interface = streq(m->type, "interface"); + + if (argv[3] && is_interface) + continue; + + if (m->value) { + ellipsized = ellipsize(m->value, result_width, 100); + if (!ellipsized) + return log_oom(); + + rv = ellipsized; + } else + rv = strdash(m->result); + + printf("%s%s%-*s%s %-*s %-*s %-*s%s%s%s%s%s%s\n", + is_interface ? ansi_highlight() : "", + is_interface ? "" : ".", + - !is_interface + (int) name_width, strdash(streq_ptr(m->type, "interface") ? m->interface : m->name), + is_interface ? ansi_normal() : "", + (int) type_width, strdash(m->type), + (int) signature_width, strdash(m->signature), + (int) result_width, rv, + (m->flags & SD_BUS_VTABLE_DEPRECATED) ? " deprecated" : (m->flags || m->writable ? "" : " -"), + (m->flags & SD_BUS_VTABLE_METHOD_NO_REPLY) ? " no-reply" : "", + (m->flags & SD_BUS_VTABLE_PROPERTY_CONST) ? " const" : "", + (m->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) ? " emits-change" : "", + (m->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) ? " emits-invalidation" : "", + m->writable ? " writable" : ""); + } + + return 0; +} + +static int message_dump(sd_bus_message *m, FILE *f) { + return bus_message_dump(m, f, BUS_MESSAGE_DUMP_WITH_HEADER); +} + +static int message_pcap(sd_bus_message *m, FILE *f) { + return bus_message_pcap_frame(m, arg_snaplen, f); +} + +static int monitor(sd_bus *bus, char *argv[], int (*dump)(sd_bus_message *m, FILE *f)) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *message = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char **i; + uint32_t flags = 0; + int r; + + /* upgrade connection; it's not used for anything else after this call */ + r = sd_bus_message_new_method_call(bus, &message, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus.Monitoring", "BecomeMonitor"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(message, 'a', "s"); + if (r < 0) + return bus_log_create_error(r); + + STRV_FOREACH(i, argv+1) { + _cleanup_free_ char *m = NULL; + + if (!service_name_is_valid(*i)) { + log_error("Invalid service name '%s'", *i); + return -EINVAL; + } + + m = strjoin("sender='", *i, "'", NULL); + if (!m) + return log_oom(); + + r = sd_bus_message_append_basic(message, 's', m); + if (r < 0) + return bus_log_create_error(r); + + free(m); + m = strjoin("destination='", *i, "'", NULL); + if (!m) + return log_oom(); + + r = sd_bus_message_append_basic(message, 's', m); + if (r < 0) + return bus_log_create_error(r); + } + + STRV_FOREACH(i, arg_matches) { + r = sd_bus_message_append_basic(message, 's', *i); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(message); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_basic(message, 'u', &flags); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, message, arg_timeout, &error, NULL); + if (r < 0) { + log_error("%s", bus_error_message(&error, r)); + return r; + } + + log_info("Monitoring bus message stream."); + + for (;;) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + r = sd_bus_process(bus, &m); + if (r < 0) + return log_error_errno(r, "Failed to process bus: %m"); + + if (m) { + dump(m, stdout); + fflush(stdout); + + if (sd_bus_message_is_signal(m, "org.freedesktop.DBus.Local", "Disconnected") > 0) { + log_info("Connection terminated, exiting."); + return 0; + } + + continue; + } + + if (r > 0) + continue; + + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) + return log_error_errno(r, "Failed to wait for bus: %m"); + } +} + +static int capture(sd_bus *bus, char *argv[]) { + int r; + + if (isatty(fileno(stdout)) > 0) { + log_error("Refusing to write message data to console, please redirect output to a file."); + return -EINVAL; + } + + bus_pcap_header(arg_snaplen, stdout); + + r = monitor(bus, argv, message_pcap); + if (r < 0) + return r; + + if (ferror(stdout)) { + log_error("Couldn't write capture file."); + return -EIO; + } + + return r; +} + +static int status(sd_bus *bus, char *argv[]) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + pid_t pid; + int r; + + assert(bus); + + if (strv_length(argv) > 2) { + log_error("Expects no or one argument."); + return -EINVAL; + } + + if (argv[1]) { + r = parse_pid(argv[1], &pid); + if (r < 0) + r = sd_bus_get_name_creds( + bus, + argv[1], + (arg_augment_creds ? SD_BUS_CREDS_AUGMENT : 0) | _SD_BUS_CREDS_ALL, + &creds); + else + r = sd_bus_creds_new_from_pid( + &creds, + pid, + _SD_BUS_CREDS_ALL); + } else { + const char *scope, *address; + sd_id128_t bus_id; + + r = sd_bus_get_address(bus, &address); + if (r >= 0) + printf("BusAddress=%s%s%s\n", ansi_highlight(), address, ansi_normal()); + + r = sd_bus_get_scope(bus, &scope); + if (r >= 0) + printf("BusScope=%s%s%s\n", ansi_highlight(), scope, ansi_normal()); + + r = sd_bus_get_bus_id(bus, &bus_id); + if (r >= 0) + printf("BusID=%s" SD_ID128_FORMAT_STR "%s\n", ansi_highlight(), SD_ID128_FORMAT_VAL(bus_id), ansi_normal()); + + r = sd_bus_get_owner_creds( + bus, + (arg_augment_creds ? SD_BUS_CREDS_AUGMENT : 0) | _SD_BUS_CREDS_ALL, + &creds); + } + + if (r < 0) + return log_error_errno(r, "Failed to get credentials: %m"); + + bus_creds_dump(creds, NULL, false); + return 0; +} + +static int message_append_cmdline(sd_bus_message *m, const char *signature, char ***x) { + char **p; + int r; + + assert(m); + assert(signature); + assert(x); + + p = *x; + + for (;;) { + const char *v; + char t; + + t = *signature; + v = *p; + + if (t == 0) + break; + if (!v) { + log_error("Too few parameters for signature."); + return -EINVAL; + } + + signature++; + p++; + + switch (t) { + + case SD_BUS_TYPE_BOOLEAN: + + r = parse_boolean(v); + if (r < 0) { + log_error("Failed to parse as boolean: %s", v); + return r; + } + + r = sd_bus_message_append_basic(m, t, &r); + break; + + case SD_BUS_TYPE_BYTE: { + uint8_t z; + + r = safe_atou8(v, &z); + if (r < 0) { + log_error("Failed to parse as byte (unsigned 8bit integer): %s", v); + return r; + } + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_INT16: { + int16_t z; + + r = safe_atoi16(v, &z); + if (r < 0) { + log_error("Failed to parse as signed 16bit integer: %s", v); + return r; + } + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_UINT16: { + uint16_t z; + + r = safe_atou16(v, &z); + if (r < 0) { + log_error("Failed to parse as unsigned 16bit integer: %s", v); + return r; + } + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_INT32: { + int32_t z; + + r = safe_atoi32(v, &z); + if (r < 0) { + log_error("Failed to parse as signed 32bit integer: %s", v); + return r; + } + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_UINT32: { + uint32_t z; + + r = safe_atou32(v, &z); + if (r < 0) { + log_error("Failed to parse as unsigned 32bit integer: %s", v); + return r; + } + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_INT64: { + int64_t z; + + r = safe_atoi64(v, &z); + if (r < 0) { + log_error("Failed to parse as signed 64bit integer: %s", v); + return r; + } + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_UINT64: { + uint64_t z; + + r = safe_atou64(v, &z); + if (r < 0) { + log_error("Failed to parse as unsigned 64bit integer: %s", v); + return r; + } + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + + case SD_BUS_TYPE_DOUBLE: { + double z; + + r = safe_atod(v, &z); + if (r < 0) { + log_error("Failed to parse as double precision floating point: %s", v); + return r; + } + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: + + r = sd_bus_message_append_basic(m, t, v); + break; + + case SD_BUS_TYPE_ARRAY: { + uint32_t n; + size_t k; + + r = safe_atou32(v, &n); + if (r < 0) { + log_error("Failed to parse number of array entries: %s", v); + return r; + } + + r = signature_element_length(signature, &k); + if (r < 0) { + log_error("Invalid array signature."); + return r; + } + + { + unsigned i; + char s[k + 1]; + memcpy(s, signature, k); + s[k] = 0; + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, s); + if (r < 0) + return bus_log_create_error(r); + + for (i = 0; i < n; i++) { + r = message_append_cmdline(m, s, &p); + if (r < 0) + return r; + } + } + + signature += k; + + r = sd_bus_message_close_container(m); + break; + } + + case SD_BUS_TYPE_VARIANT: + r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT, v); + if (r < 0) + return bus_log_create_error(r); + + r = message_append_cmdline(m, v, &p); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + break; + + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { + size_t k; + + signature--; + p--; + + r = signature_element_length(signature, &k); + if (r < 0) { + log_error("Invalid struct/dict entry signature."); + return r; + } + + { + char s[k-1]; + memcpy(s, signature + 1, k - 2); + s[k - 2] = 0; + + r = sd_bus_message_open_container(m, t == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s); + if (r < 0) + return bus_log_create_error(r); + + r = message_append_cmdline(m, s, &p); + if (r < 0) + return r; + } + + signature += k; + + r = sd_bus_message_close_container(m); + break; + } + + case SD_BUS_TYPE_UNIX_FD: + log_error("UNIX file descriptor not supported as type."); + return -EINVAL; + + default: + log_error("Unknown signature type %c.", t); + return -EINVAL; + } + + if (r < 0) + return bus_log_create_error(r); + } + + *x = p; + return 0; +} + +static int call(sd_bus *bus, char *argv[]) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + int r; + + assert(bus); + + if (strv_length(argv) < 5) { + log_error("Expects at least four arguments."); + return -EINVAL; + } + + r = sd_bus_message_new_method_call(bus, &m, argv[1], argv[2], argv[3], argv[4]); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_expect_reply(m, arg_expect_reply); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_auto_start(m, arg_auto_start); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_allow_interactive_authorization(m, arg_allow_interactive_authorization); + if (r < 0) + return bus_log_create_error(r); + + if (!isempty(argv[5])) { + char **p; + + p = argv+6; + + r = message_append_cmdline(m, argv[5], &p); + if (r < 0) + return r; + + if (*p) { + log_error("Too many parameters for signature."); + return -EINVAL; + } + } + + if (!arg_expect_reply) { + r = sd_bus_send(bus, m, NULL); + if (r < 0) { + log_error("Failed to send message."); + return r; + } + + return 0; + } + + r = sd_bus_call(bus, m, arg_timeout, &error, &reply); + if (r < 0) { + log_error("%s", bus_error_message(&error, r)); + return r; + } + + r = sd_bus_message_is_empty(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (r == 0 && !arg_quiet) { + + if (arg_verbose) { + pager_open(arg_no_pager, false); + + r = bus_message_dump(reply, stdout, 0); + if (r < 0) + return r; + } else { + + fputs(sd_bus_message_get_signature(reply, true), stdout); + fputc(' ', stdout); + + r = format_cmdline(reply, stdout, false); + if (r < 0) + return bus_log_parse_error(r); + + fputc('\n', stdout); + } + } + + return 0; +} + +static int get_property(sd_bus *bus, char *argv[]) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + unsigned n; + char **i; + int r; + + assert(bus); + + n = strv_length(argv); + if (n < 5) { + log_error("Expects at least four arguments."); + return -EINVAL; + } + + STRV_FOREACH(i, argv + 4) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *contents = NULL; + char type; + + r = sd_bus_call_method(bus, argv[1], argv[2], "org.freedesktop.DBus.Properties", "Get", &error, &reply, "ss", argv[3], *i); + if (r < 0) { + log_error("%s", bus_error_message(&error, r)); + return r; + } + + r = sd_bus_message_peek_type(reply, &type, &contents); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_enter_container(reply, 'v', contents); + if (r < 0) + return bus_log_parse_error(r); + + if (arg_verbose) { + pager_open(arg_no_pager, false); + + r = bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_SUBTREE_ONLY); + if (r < 0) + return r; + } else { + fputs(contents, stdout); + fputc(' ', stdout); + + r = format_cmdline(reply, stdout, false); + if (r < 0) + return bus_log_parse_error(r); + + fputc('\n', stdout); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + } + + return 0; +} + +static int set_property(sd_bus *bus, char *argv[]) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + unsigned n; + char **p; + int r; + + assert(bus); + + n = strv_length(argv); + if (n < 6) { + log_error("Expects at least five arguments."); + return -EINVAL; + } + + r = sd_bus_message_new_method_call(bus, &m, argv[1], argv[2], "org.freedesktop.DBus.Properties", "Set"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "ss", argv[3], argv[4]); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'v', argv[5]); + if (r < 0) + return bus_log_create_error(r); + + p = argv+6; + r = message_append_cmdline(m, argv[5], &p); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + if (*p) { + log_error("Too many parameters for signature."); + return -EINVAL; + } + + r = sd_bus_call(bus, m, arg_timeout, &error, NULL); + if (r < 0) { + log_error("%s", bus_error_message(&error, r)); + return r; + } + + return 0; +} + +static int help(void) { + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Introspect the bus.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --no-legend Do not show the headers and footers\n" + " --system Connect to system bus\n" + " --user Connect to user bus\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --address=ADDRESS Connect to bus specified by address\n" + " --show-machine Show machine ID column in list\n" + " --unique Only show unique names\n" + " --acquired Only show acquired names\n" + " --activatable Only show activatable names\n" + " --match=MATCH Only show matching messages\n" + " --size=SIZE Maximum length of captured packet\n" + " --list Don't show tree, but simple object path list\n" + " --quiet Don't show method call reply\n" + " --verbose Show result values in long format\n" + " --expect-reply=BOOL Expect a method call reply\n" + " --auto-start=BOOL Auto-start destination service\n" + " --allow-interactive-authorization=BOOL\n" + " Allow interactive authorization for operation\n" + " --timeout=SECS Maximum time to wait for method call completion\n" + " --augment-creds=BOOL Extend credential data with data read from /proc/$PID\n\n" + "Commands:\n" + " list List bus names\n" + " status [SERVICE] Show bus service, process or bus owner credentials\n" + " monitor [SERVICE...] Show bus traffic\n" + " capture [SERVICE...] Capture bus traffic as pcap\n" + " tree [SERVICE...] Show object tree of service\n" + " introspect SERVICE OBJECT [INTERFACE]\n" + " call SERVICE OBJECT INTERFACE METHOD [SIGNATURE [ARGUMENT...]]\n" + " Call a method\n" + " get-property SERVICE OBJECT INTERFACE PROPERTY...\n" + " Get property value\n" + " set-property SERVICE OBJECT INTERFACE PROPERTY SIGNATURE ARGUMENT...\n" + " Set property value\n" + " help Show this help\n" + , program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_NO_LEGEND, + ARG_SYSTEM, + ARG_USER, + ARG_ADDRESS, + ARG_MATCH, + ARG_SHOW_MACHINE, + ARG_UNIQUE, + ARG_ACQUIRED, + ARG_ACTIVATABLE, + ARG_SIZE, + ARG_LIST, + ARG_VERBOSE, + ARG_EXPECT_REPLY, + ARG_AUTO_START, + ARG_ALLOW_INTERACTIVE_AUTHORIZATION, + ARG_TIMEOUT, + ARG_AUGMENT_CREDS, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "user", no_argument, NULL, ARG_USER }, + { "address", required_argument, NULL, ARG_ADDRESS }, + { "show-machine", no_argument, NULL, ARG_SHOW_MACHINE }, + { "unique", no_argument, NULL, ARG_UNIQUE }, + { "acquired", no_argument, NULL, ARG_ACQUIRED }, + { "activatable", no_argument, NULL, ARG_ACTIVATABLE }, + { "match", required_argument, NULL, ARG_MATCH }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "size", required_argument, NULL, ARG_SIZE }, + { "list", no_argument, NULL, ARG_LIST }, + { "quiet", no_argument, NULL, 'q' }, + { "verbose", no_argument, NULL, ARG_VERBOSE }, + { "expect-reply", required_argument, NULL, ARG_EXPECT_REPLY }, + { "auto-start", required_argument, NULL, ARG_AUTO_START }, + { "allow-interactive-authorization", required_argument, NULL, ARG_ALLOW_INTERACTIVE_AUTHORIZATION }, + { "timeout", required_argument, NULL, ARG_TIMEOUT }, + { "augment-creds",required_argument, NULL, ARG_AUGMENT_CREDS}, + {}, + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hH:M:q", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case ARG_NO_LEGEND: + arg_legend = false; + break; + + case ARG_USER: + arg_user = true; + break; + + case ARG_SYSTEM: + arg_user = false; + break; + + case ARG_ADDRESS: + arg_address = optarg; + break; + + case ARG_SHOW_MACHINE: + arg_show_machine = true; + break; + + case ARG_UNIQUE: + arg_unique = true; + break; + + case ARG_ACQUIRED: + arg_acquired = true; + break; + + case ARG_ACTIVATABLE: + arg_activatable = true; + break; + + case ARG_MATCH: + if (strv_extend(&arg_matches, optarg) < 0) + return log_oom(); + break; + + case ARG_SIZE: { + uint64_t sz; + + r = parse_size(optarg, 1024, &sz); + if (r < 0) { + log_error("Failed to parse size: %s", optarg); + return r; + } + + if ((uint64_t) (size_t) sz != sz) { + log_error("Size out of range."); + return -E2BIG; + } + + arg_snaplen = (size_t) sz; + break; + } + + case ARG_LIST: + arg_list = true; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case 'q': + arg_quiet = true; + break; + + case ARG_VERBOSE: + arg_verbose = true; + break; + + case ARG_EXPECT_REPLY: + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --expect-reply= parameter."); + return r; + } + + arg_expect_reply = !!r; + break; + + + case ARG_AUTO_START: + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --auto-start= parameter."); + return r; + } + + arg_auto_start = !!r; + break; + + + case ARG_ALLOW_INTERACTIVE_AUTHORIZATION: + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --allow-interactive-authorization= parameter."); + return r; + } + + arg_allow_interactive_authorization = !!r; + break; + + case ARG_TIMEOUT: + r = parse_sec(optarg, &arg_timeout); + if (r < 0) { + log_error("Failed to parse --timeout= parameter."); + return r; + } + + break; + + case ARG_AUGMENT_CREDS: + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --augment-creds= parameter."); + return r; + } + + arg_augment_creds = !!r; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +static int busctl_main(sd_bus *bus, int argc, char *argv[]) { + assert(bus); + + if (optind >= argc || + streq(argv[optind], "list")) + return list_bus_names(bus, argv + optind); + + if (streq(argv[optind], "monitor")) + return monitor(bus, argv + optind, message_dump); + + if (streq(argv[optind], "capture")) + return capture(bus, argv + optind); + + if (streq(argv[optind], "status")) + return status(bus, argv + optind); + + if (streq(argv[optind], "tree")) + return tree(bus, argv + optind); + + if (streq(argv[optind], "introspect")) + return introspect(bus, argv + optind); + + if (streq(argv[optind], "call")) + return call(bus, argv + optind); + + if (streq(argv[optind], "get-property")) + return get_property(bus, argv + optind); + + if (streq(argv[optind], "set-property")) + return set_property(bus, argv + optind); + + if (streq(argv[optind], "help")) + return help(); + + log_error("Unknown command '%s'", argv[optind]); + return -EINVAL; +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = sd_bus_new(&bus); + if (r < 0) { + log_error_errno(r, "Failed to allocate bus: %m"); + goto finish; + } + + if (streq_ptr(argv[optind], "monitor") || + streq_ptr(argv[optind], "capture")) { + + r = sd_bus_set_monitor(bus, true); + if (r < 0) { + log_error_errno(r, "Failed to set monitor mode: %m"); + goto finish; + } + + r = sd_bus_negotiate_creds(bus, true, _SD_BUS_CREDS_ALL); + if (r < 0) { + log_error_errno(r, "Failed to enable credentials: %m"); + goto finish; + } + + r = sd_bus_negotiate_timestamp(bus, true); + if (r < 0) { + log_error_errno(r, "Failed to enable timestamps: %m"); + goto finish; + } + + r = sd_bus_negotiate_fds(bus, true); + if (r < 0) { + log_error_errno(r, "Failed to enable fds: %m"); + goto finish; + } + } + + r = sd_bus_set_bus_client(bus, true); + if (r < 0) { + log_error_errno(r, "Failed to set bus client: %m"); + goto finish; + } + + if (arg_address) + r = sd_bus_set_address(bus, arg_address); + else { + switch (arg_transport) { + + case BUS_TRANSPORT_LOCAL: + if (arg_user) { + bus->is_user = true; + r = bus_set_address_user(bus); + } else { + bus->is_system = true; + r = bus_set_address_system(bus); + } + break; + + case BUS_TRANSPORT_REMOTE: + r = bus_set_address_system_remote(bus, arg_host); + break; + + case BUS_TRANSPORT_MACHINE: + r = bus_set_address_system_machine(bus, arg_host); + break; + + default: + assert_not_reached("Hmm, unknown transport type."); + } + } + if (r < 0) { + log_error_errno(r, "Failed to set address: %m"); + goto finish; + } + + r = sd_bus_start(bus); + if (r < 0) { + log_error_errno(r, "Failed to connect to bus: %m"); + goto finish; + } + + r = busctl_main(bus, argc, argv); + +finish: + pager_close(); + + strv_free(arg_matches); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/cgls/Makefile b/src/cgls/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/cgls/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/cgls/cgls.c b/src/cgls/cgls.c deleted file mode 100644 index dcb5912b83..0000000000 --- a/src/cgls/cgls.c +++ /dev/null @@ -1,288 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "cgroup-show.h" -#include "cgroup-util.h" -#include "fileio.h" -#include "log.h" -#include "output-mode.h" -#include "pager.h" -#include "path-util.h" -#include "unit-name.h" -#include "util.h" - -static bool arg_no_pager = false; -static bool arg_kernel_threads = false; -static bool arg_all = false; -static int arg_full = -1; -static char* arg_machine = NULL; - -static void help(void) { - printf("%s [OPTIONS...] [CGROUP...]\n\n" - "Recursively show control group contents.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " -a --all Show all groups, including empty\n" - " -l --full Do not ellipsize output\n" - " -k Include kernel threads in output\n" - " -M --machine= Show container\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_NO_PAGER = 0x100, - ARG_VERSION, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "all", no_argument, NULL, 'a' }, - { "full", no_argument, NULL, 'l' }, - { "machine", required_argument, NULL, 'M' }, - {} - }; - - int c; - - assert(argc >= 1); - assert(argv); - - while ((c = getopt_long(argc, argv, "hkalM:", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case 'a': - arg_all = true; - break; - - case 'l': - arg_full = true; - break; - - case 'k': - arg_kernel_threads = true; - break; - - case 'M': - arg_machine = optarg; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -static int get_cgroup_root(char **ret) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_free_ char *unit = NULL, *path = NULL; - const char *m; - int r; - - if (!arg_machine) { - r = cg_get_root_path(ret); - if (r == -ENOMEDIUM) - return log_error_errno(r, "Failed to get root control group path: No cgroup filesystem mounted on /sys/fs/cgroup"); - else if (r < 0) - return log_error_errno(r, "Failed to get root control group path: %m"); - - return 0; - } - - m = strjoina("/run/systemd/machines/", arg_machine); - r = parse_env_file(m, NEWLINE, "SCOPE", &unit, NULL); - if (r < 0) - return log_error_errno(r, "Failed to load machine data: %m"); - - path = unit_dbus_path_from_name(unit); - if (!path) - return log_oom(); - - r = bus_connect_transport_systemd(BUS_TRANSPORT_LOCAL, NULL, false, &bus); - if (r < 0) - return log_error_errno(r, "Failed to create bus connection: %m"); - - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - path, - unit_dbus_interface_from_name(unit), - "ControlGroup", - &error, - ret); - if (r < 0) - return log_error_errno(r, "Failed to query unit control group path: %s", bus_error_message(&error, r)); - - return 0; -} - -static void show_cg_info(const char *controller, const char *path) { - - if (cg_unified() <= 0 && controller && !streq(controller, SYSTEMD_CGROUP_CONTROLLER)) - printf("Controller %s; ", controller); - - printf("Control group %s:\n", isempty(path) ? "/" : path); - fflush(stdout); -} - -int main(int argc, char *argv[]) { - int r, output_flags; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (!arg_no_pager) { - r = pager_open(arg_no_pager, false); - if (r > 0 && arg_full < 0) - arg_full = true; - } - - output_flags = - arg_all * OUTPUT_SHOW_ALL | - (arg_full > 0) * OUTPUT_FULL_WIDTH | - arg_kernel_threads * OUTPUT_KERNEL_THREADS; - - if (optind < argc) { - _cleanup_free_ char *root = NULL; - int i; - - r = get_cgroup_root(&root); - if (r < 0) - goto finish; - - for (i = optind; i < argc; i++) { - int q; - - if (path_startswith(argv[i], "/sys/fs/cgroup")) { - - printf("Directory %s:\n", argv[i]); - fflush(stdout); - - q = show_cgroup_by_path(argv[i], NULL, 0, output_flags); - } else { - _cleanup_free_ char *c = NULL, *p = NULL, *j = NULL; - const char *controller, *path; - - r = cg_split_spec(argv[i], &c, &p); - if (r < 0) { - log_error_errno(r, "Failed to split argument %s: %m", argv[i]); - goto finish; - } - - controller = c ?: SYSTEMD_CGROUP_CONTROLLER; - if (p) { - j = strjoin(root, "/", p, NULL); - if (!j) { - r = log_oom(); - goto finish; - } - - path_kill_slashes(j); - path = j; - } else - path = root; - - show_cg_info(controller, path); - - q = show_cgroup(controller, path, NULL, 0, output_flags); - } - - if (q < 0) - r = q; - } - - } else { - bool done = false; - - if (!arg_machine) { - _cleanup_free_ char *cwd = NULL; - - cwd = get_current_dir_name(); - if (!cwd) { - r = log_error_errno(errno, "Cannot determine current working directory: %m"); - goto finish; - } - - if (path_startswith(cwd, "/sys/fs/cgroup")) { - printf("Working directory %s:\n", cwd); - fflush(stdout); - - r = show_cgroup_by_path(cwd, NULL, 0, output_flags); - done = true; - } - } - - if (!done) { - _cleanup_free_ char *root = NULL; - - r = get_cgroup_root(&root); - if (r < 0) - goto finish; - - show_cg_info(SYSTEMD_CGROUP_CONTROLLER, root); - - printf("-.slice\n"); - r = show_cgroup(SYSTEMD_CGROUP_CONTROLLER, root, NULL, 0, output_flags); - } - } - - if (r < 0) - log_error_errno(r, "Failed to list cgroup tree: %m"); - -finish: - pager_close(); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/cgroups-agent/Makefile b/src/cgroups-agent/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/cgroups-agent/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/cgroups-agent/cgroups-agent.c b/src/cgroups-agent/cgroups-agent.c deleted file mode 100644 index d7c722ac3d..0000000000 --- a/src/cgroups-agent/cgroups-agent.c +++ /dev/null @@ -1,67 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 - -#include "fd-util.h" -#include "log.h" -#include "socket-util.h" - -int main(int argc, char *argv[]) { - - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/cgroups-agent", - }; - - _cleanup_close_ int fd = -1; - ssize_t n; - size_t l; - - if (argc != 2) { - log_error("Incorrect number of arguments."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); - if (fd < 0) { - log_debug_errno(errno, "Failed to allocate socket: %m"); - return EXIT_FAILURE; - } - - l = strlen(argv[1]); - - n = sendto(fd, argv[1], l, 0, &sa.sa, SOCKADDR_UN_LEN(sa.un)); - if (n < 0) { - log_debug_errno(errno, "Failed to send cgroups agent message: %m"); - return EXIT_FAILURE; - } - - if ((size_t) n != l) { - log_debug("Datagram size mismatch"); - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} diff --git a/src/cgtop/Makefile b/src/cgtop/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/cgtop/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c deleted file mode 100644 index e088e4b197..0000000000 --- a/src/cgtop/cgtop.c +++ /dev/null @@ -1,1115 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 -#include -#include -#include -#include -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "cgroup-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "hashmap.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "stdio-util.h" -#include "terminal-util.h" -#include "unit-name.h" -#include "util.h" - -typedef struct Group { - char *path; - - bool n_tasks_valid:1; - bool cpu_valid:1; - bool memory_valid:1; - bool io_valid:1; - - uint64_t n_tasks; - - unsigned cpu_iteration; - nsec_t cpu_usage; - nsec_t cpu_timestamp; - double cpu_fraction; - - uint64_t memory; - - unsigned io_iteration; - uint64_t io_input, io_output; - nsec_t io_timestamp; - uint64_t io_input_bps, io_output_bps; -} Group; - -static unsigned arg_depth = 3; -static unsigned arg_iterations = (unsigned) -1; -static bool arg_batch = false; -static bool arg_raw = false; -static usec_t arg_delay = 1*USEC_PER_SEC; -static char* arg_machine = NULL; -static bool arg_recursive = true; - -static enum { - COUNT_PIDS, - COUNT_USERSPACE_PROCESSES, - COUNT_ALL_PROCESSES, -} arg_count = COUNT_PIDS; - -static enum { - ORDER_PATH, - ORDER_TASKS, - ORDER_CPU, - ORDER_MEMORY, - ORDER_IO, -} arg_order = ORDER_CPU; - -static enum { - CPU_PERCENT, - CPU_TIME, -} arg_cpu_type = CPU_PERCENT; - -static void group_free(Group *g) { - assert(g); - - free(g->path); - free(g); -} - -static void group_hashmap_clear(Hashmap *h) { - Group *g; - - while ((g = hashmap_steal_first(h))) - group_free(g); -} - -static void group_hashmap_free(Hashmap *h) { - group_hashmap_clear(h); - hashmap_free(h); -} - -static const char *maybe_format_bytes(char *buf, size_t l, bool is_valid, uint64_t t) { - if (!is_valid) - return "-"; - if (arg_raw) { - snprintf(buf, l, "%jd", t); - return buf; - } - return format_bytes(buf, l, t); -} - -static int process( - const char *controller, - const char *path, - Hashmap *a, - Hashmap *b, - unsigned iteration, - Group **ret) { - - Group *g; - int r; - - assert(controller); - assert(path); - assert(a); - - g = hashmap_get(a, path); - if (!g) { - g = hashmap_get(b, path); - if (!g) { - g = new0(Group, 1); - if (!g) - return -ENOMEM; - - g->path = strdup(path); - if (!g->path) { - group_free(g); - return -ENOMEM; - } - - r = hashmap_put(a, g->path, g); - if (r < 0) { - group_free(g); - return r; - } - } else { - r = hashmap_move_one(a, b, path); - if (r < 0) - return r; - - g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false; - } - } - - if (streq(controller, SYSTEMD_CGROUP_CONTROLLER) && IN_SET(arg_count, COUNT_ALL_PROCESSES, COUNT_USERSPACE_PROCESSES)) { - _cleanup_fclose_ FILE *f = NULL; - pid_t pid; - - r = cg_enumerate_processes(controller, path, &f); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - - g->n_tasks = 0; - while (cg_read_pid(f, &pid) > 0) { - - if (arg_count == COUNT_USERSPACE_PROCESSES && is_kernel_thread(pid) > 0) - continue; - - g->n_tasks++; - } - - if (g->n_tasks > 0) - g->n_tasks_valid = true; - - } else if (streq(controller, "pids") && arg_count == COUNT_PIDS) { - _cleanup_free_ char *p = NULL, *v = NULL; - - r = cg_get_path(controller, path, "pids.current", &p); - if (r < 0) - return r; - - r = read_one_line_file(p, &v); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - - r = safe_atou64(v, &g->n_tasks); - if (r < 0) - return r; - - if (g->n_tasks > 0) - g->n_tasks_valid = true; - - } else if (streq(controller, "cpuacct") && cg_unified() <= 0) { - _cleanup_free_ char *p = NULL, *v = NULL; - uint64_t new_usage; - nsec_t timestamp; - - r = cg_get_path(controller, path, "cpuacct.usage", &p); - if (r < 0) - return r; - - r = read_one_line_file(p, &v); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - - r = safe_atou64(v, &new_usage); - if (r < 0) - return r; - - timestamp = now_nsec(CLOCK_MONOTONIC); - - if (g->cpu_iteration == iteration - 1 && - (nsec_t) new_usage > g->cpu_usage) { - - nsec_t x, y; - - x = timestamp - g->cpu_timestamp; - if (x < 1) - x = 1; - - y = (nsec_t) new_usage - g->cpu_usage; - g->cpu_fraction = (double) y / (double) x; - g->cpu_valid = true; - } - - g->cpu_usage = (nsec_t) new_usage; - g->cpu_timestamp = timestamp; - g->cpu_iteration = iteration; - - } else if (streq(controller, "memory")) { - _cleanup_free_ char *p = NULL, *v = NULL; - - if (cg_unified() <= 0) - r = cg_get_path(controller, path, "memory.usage_in_bytes", &p); - else - r = cg_get_path(controller, path, "memory.current", &p); - if (r < 0) - return r; - - r = read_one_line_file(p, &v); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - - r = safe_atou64(v, &g->memory); - if (r < 0) - return r; - - if (g->memory > 0) - g->memory_valid = true; - - } else if ((streq(controller, "io") && cg_unified() > 0) || - (streq(controller, "blkio") && cg_unified() <= 0)) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *p = NULL; - bool unified = cg_unified() > 0; - uint64_t wr = 0, rd = 0; - nsec_t timestamp; - - r = cg_get_path(controller, path, unified ? "io.stat" : "blkio.io_service_bytes", &p); - if (r < 0) - return r; - - f = fopen(p, "re"); - if (!f) { - if (errno == ENOENT) - return 0; - return -errno; - } - - for (;;) { - char line[LINE_MAX], *l; - uint64_t k, *q; - - if (!fgets(line, sizeof(line), f)) - break; - - /* Trim and skip the device */ - l = strstrip(line); - l += strcspn(l, WHITESPACE); - l += strspn(l, WHITESPACE); - - if (unified) { - while (!isempty(l)) { - if (sscanf(l, "rbytes=%" SCNu64, &k)) - rd += k; - else if (sscanf(l, "wbytes=%" SCNu64, &k)) - wr += k; - - l += strcspn(l, WHITESPACE); - l += strspn(l, WHITESPACE); - } - } else { - if (first_word(l, "Read")) { - l += 4; - q = &rd; - } else if (first_word(l, "Write")) { - l += 5; - q = ≀ - } else - continue; - - l += strspn(l, WHITESPACE); - r = safe_atou64(l, &k); - if (r < 0) - continue; - - *q += k; - } - } - - timestamp = now_nsec(CLOCK_MONOTONIC); - - if (g->io_iteration == iteration - 1) { - uint64_t x, yr, yw; - - x = (uint64_t) (timestamp - g->io_timestamp); - if (x < 1) - x = 1; - - if (rd > g->io_input) - yr = rd - g->io_input; - else - yr = 0; - - if (wr > g->io_output) - yw = wr - g->io_output; - else - yw = 0; - - if (yr > 0 || yw > 0) { - g->io_input_bps = (yr * 1000000000ULL) / x; - g->io_output_bps = (yw * 1000000000ULL) / x; - g->io_valid = true; - } - } - - g->io_input = rd; - g->io_output = wr; - g->io_timestamp = timestamp; - g->io_iteration = iteration; - } - - if (ret) - *ret = g; - - return 0; -} - -static int refresh_one( - const char *controller, - const char *path, - Hashmap *a, - Hashmap *b, - unsigned iteration, - unsigned depth, - Group **ret) { - - _cleanup_closedir_ DIR *d = NULL; - Group *ours = NULL; - int r; - - assert(controller); - assert(path); - assert(a); - - if (depth > arg_depth) - return 0; - - r = process(controller, path, a, b, iteration, &ours); - if (r < 0) - return r; - - r = cg_enumerate_subgroups(controller, path, &d); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - - for (;;) { - _cleanup_free_ char *fn = NULL, *p = NULL; - Group *child = NULL; - - r = cg_read_subgroup(d, &fn); - if (r < 0) - return r; - if (r == 0) - break; - - p = strjoin(path, "/", fn, NULL); - if (!p) - return -ENOMEM; - - path_kill_slashes(p); - - r = refresh_one(controller, p, a, b, iteration, depth + 1, &child); - if (r < 0) - return r; - - if (arg_recursive && - IN_SET(arg_count, COUNT_ALL_PROCESSES, COUNT_USERSPACE_PROCESSES) && - child && - child->n_tasks_valid && - streq(controller, SYSTEMD_CGROUP_CONTROLLER)) { - - /* Recursively sum up processes */ - - if (ours->n_tasks_valid) - ours->n_tasks += child->n_tasks; - else { - ours->n_tasks = child->n_tasks; - ours->n_tasks_valid = true; - } - } - } - - if (ret) - *ret = ours; - - return 1; -} - -static int refresh(const char *root, Hashmap *a, Hashmap *b, unsigned iteration) { - int r; - - assert(a); - - r = refresh_one(SYSTEMD_CGROUP_CONTROLLER, root, a, b, iteration, 0, NULL); - if (r < 0) - return r; - r = refresh_one("cpuacct", root, a, b, iteration, 0, NULL); - if (r < 0) - return r; - r = refresh_one("memory", root, a, b, iteration, 0, NULL); - if (r < 0) - return r; - r = refresh_one("io", root, a, b, iteration, 0, NULL); - if (r < 0) - return r; - r = refresh_one("blkio", root, a, b, iteration, 0, NULL); - if (r < 0) - return r; - r = refresh_one("pids", root, a, b, iteration, 0, NULL); - if (r < 0) - return r; - - return 0; -} - -static int group_compare(const void*a, const void *b) { - const Group *x = *(Group**)a, *y = *(Group**)b; - - if (arg_order != ORDER_TASKS || arg_recursive) { - /* Let's make sure that the parent is always before - * the child. Except when ordering by tasks and - * recursive summing is off, since that is actually - * not accumulative for all children. */ - - if (path_startswith(y->path, x->path)) - return -1; - if (path_startswith(x->path, y->path)) - return 1; - } - - switch (arg_order) { - - case ORDER_PATH: - break; - - case ORDER_CPU: - if (arg_cpu_type == CPU_PERCENT) { - if (x->cpu_valid && y->cpu_valid) { - if (x->cpu_fraction > y->cpu_fraction) - return -1; - else if (x->cpu_fraction < y->cpu_fraction) - return 1; - } else if (x->cpu_valid) - return -1; - else if (y->cpu_valid) - return 1; - } else { - if (x->cpu_usage > y->cpu_usage) - return -1; - else if (x->cpu_usage < y->cpu_usage) - return 1; - } - - break; - - case ORDER_TASKS: - if (x->n_tasks_valid && y->n_tasks_valid) { - if (x->n_tasks > y->n_tasks) - return -1; - else if (x->n_tasks < y->n_tasks) - return 1; - } else if (x->n_tasks_valid) - return -1; - else if (y->n_tasks_valid) - return 1; - - break; - - case ORDER_MEMORY: - if (x->memory_valid && y->memory_valid) { - if (x->memory > y->memory) - return -1; - else if (x->memory < y->memory) - return 1; - } else if (x->memory_valid) - return -1; - else if (y->memory_valid) - return 1; - - break; - - case ORDER_IO: - if (x->io_valid && y->io_valid) { - if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps) - return -1; - else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps) - return 1; - } else if (x->io_valid) - return -1; - else if (y->io_valid) - return 1; - } - - return path_compare(x->path, y->path); -} - -static void display(Hashmap *a) { - Iterator i; - Group *g; - Group **array; - signed path_columns; - unsigned rows, n = 0, j, maxtcpu = 0, maxtpath = 3; /* 3 for ellipsize() to work properly */ - char buffer[MAX3(21, FORMAT_BYTES_MAX, FORMAT_TIMESPAN_MAX)]; - - assert(a); - - if (on_tty()) - fputs(ANSI_HOME_CLEAR, stdout); - - array = alloca(sizeof(Group*) * hashmap_size(a)); - - HASHMAP_FOREACH(g, a, i) - if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid) - array[n++] = g; - - qsort_safe(array, n, sizeof(Group*), group_compare); - - /* Find the longest names in one run */ - for (j = 0; j < n; j++) { - unsigned cputlen, pathtlen; - - format_timespan(buffer, sizeof(buffer), (usec_t) (array[j]->cpu_usage / NSEC_PER_USEC), 0); - cputlen = strlen(buffer); - maxtcpu = MAX(maxtcpu, cputlen); - - pathtlen = strlen(array[j]->path); - maxtpath = MAX(maxtpath, pathtlen); - } - - if (arg_cpu_type == CPU_PERCENT) - xsprintf(buffer, "%6s", "%CPU"); - else - xsprintf(buffer, "%*s", maxtcpu, "CPU Time"); - - rows = lines(); - if (rows <= 10) - rows = 10; - - if (on_tty()) { - const char *on, *off; - - path_columns = columns() - 36 - strlen(buffer); - if (path_columns < 10) - path_columns = 10; - - on = ansi_highlight_underline(); - off = ansi_underline(); - - printf("%s%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s%s\n", - ansi_underline(), - arg_order == ORDER_PATH ? on : "", path_columns, "Control Group", - arg_order == ORDER_PATH ? off : "", - arg_order == ORDER_TASKS ? on : "", arg_count == COUNT_PIDS ? "Tasks" : arg_count == COUNT_USERSPACE_PROCESSES ? "Procs" : "Proc+", - arg_order == ORDER_TASKS ? off : "", - arg_order == ORDER_CPU ? on : "", buffer, - arg_order == ORDER_CPU ? off : "", - arg_order == ORDER_MEMORY ? on : "", "Memory", - arg_order == ORDER_MEMORY ? off : "", - arg_order == ORDER_IO ? on : "", "Input/s", - arg_order == ORDER_IO ? off : "", - arg_order == ORDER_IO ? on : "", "Output/s", - arg_order == ORDER_IO ? off : "", - ansi_normal()); - } else - path_columns = maxtpath; - - for (j = 0; j < n; j++) { - _cleanup_free_ char *ellipsized = NULL; - const char *path; - - if (on_tty() && j + 6 > rows) - break; - - g = array[j]; - - path = isempty(g->path) ? "/" : g->path; - ellipsized = ellipsize(path, path_columns, 33); - printf("%-*s", path_columns, ellipsized ?: path); - - if (g->n_tasks_valid) - printf(" %7" PRIu64, g->n_tasks); - else - fputs(" -", stdout); - - if (arg_cpu_type == CPU_PERCENT) { - if (g->cpu_valid) - printf(" %6.1f", g->cpu_fraction*100); - else - fputs(" -", stdout); - } else - printf(" %*s", maxtcpu, format_timespan(buffer, sizeof(buffer), (usec_t) (g->cpu_usage / NSEC_PER_USEC), 0)); - - printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->memory_valid, g->memory)); - printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_input_bps)); - printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_output_bps)); - - putchar('\n'); - } -} - -static void help(void) { - printf("%s [OPTIONS...]\n\n" - "Show top control groups by their resource usage.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -p --order=path Order by path\n" - " -t --order=tasks Order by number of tasks/processes\n" - " -c --order=cpu Order by CPU load (default)\n" - " -m --order=memory Order by memory load\n" - " -i --order=io Order by IO load\n" - " -r --raw Provide raw (not human-readable) numbers\n" - " --cpu=percentage Show CPU usage as percentage (default)\n" - " --cpu=time Show CPU usage as time\n" - " -P Count userspace processes instead of tasks (excl. kernel)\n" - " -k Count all processes instead of tasks (incl. kernel)\n" - " --recursive=BOOL Sum up process count recursively\n" - " -d --delay=DELAY Delay between updates\n" - " -n --iterations=N Run for N iterations before exiting\n" - " -b --batch Run in batch mode, accepting no input\n" - " --depth=DEPTH Maximum traversal depth (default: %u)\n" - " -M --machine= Show container\n" - , program_invocation_short_name, arg_depth); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_DEPTH, - ARG_CPU_TYPE, - ARG_ORDER, - ARG_RECURSIVE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "delay", required_argument, NULL, 'd' }, - { "iterations", required_argument, NULL, 'n' }, - { "batch", no_argument, NULL, 'b' }, - { "raw", no_argument, NULL, 'r' }, - { "depth", required_argument, NULL, ARG_DEPTH }, - { "cpu", optional_argument, NULL, ARG_CPU_TYPE }, - { "order", required_argument, NULL, ARG_ORDER }, - { "recursive", required_argument, NULL, ARG_RECURSIVE }, - { "machine", required_argument, NULL, 'M' }, - {} - }; - - bool recursive_unset = false; - int c, r; - - assert(argc >= 1); - assert(argv); - - while ((c = getopt_long(argc, argv, "hptcmin:brd:kPM:", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_CPU_TYPE: - if (optarg) { - if (streq(optarg, "time")) - arg_cpu_type = CPU_TIME; - else if (streq(optarg, "percentage")) - arg_cpu_type = CPU_PERCENT; - else { - log_error("Unknown argument to --cpu=: %s", optarg); - return -EINVAL; - } - } else - arg_cpu_type = CPU_TIME; - - break; - - case ARG_DEPTH: - r = safe_atou(optarg, &arg_depth); - if (r < 0) { - log_error("Failed to parse depth parameter."); - return -EINVAL; - } - - break; - - case 'd': - r = parse_sec(optarg, &arg_delay); - if (r < 0 || arg_delay <= 0) { - log_error("Failed to parse delay parameter."); - return -EINVAL; - } - - break; - - case 'n': - r = safe_atou(optarg, &arg_iterations); - if (r < 0) { - log_error("Failed to parse iterations parameter."); - return -EINVAL; - } - - break; - - case 'b': - arg_batch = true; - break; - - case 'r': - arg_raw = true; - break; - - case 'p': - arg_order = ORDER_PATH; - break; - - case 't': - arg_order = ORDER_TASKS; - break; - - case 'c': - arg_order = ORDER_CPU; - break; - - case 'm': - arg_order = ORDER_MEMORY; - break; - - case 'i': - arg_order = ORDER_IO; - break; - - case ARG_ORDER: - if (streq(optarg, "path")) - arg_order = ORDER_PATH; - else if (streq(optarg, "tasks")) - arg_order = ORDER_TASKS; - else if (streq(optarg, "cpu")) - arg_order = ORDER_CPU; - else if (streq(optarg, "memory")) - arg_order = ORDER_MEMORY; - else if (streq(optarg, "io")) - arg_order = ORDER_IO; - else { - log_error("Invalid argument to --order=: %s", optarg); - return -EINVAL; - } - break; - - case 'k': - arg_count = COUNT_ALL_PROCESSES; - break; - - case 'P': - arg_count = COUNT_USERSPACE_PROCESSES; - break; - - case ARG_RECURSIVE: - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --recursive= argument: %s", optarg); - return r; - } - - arg_recursive = r; - recursive_unset = r == 0; - break; - - case 'M': - arg_machine = optarg; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (optind < argc) { - log_error("Too many arguments."); - return -EINVAL; - } - - if (recursive_unset && arg_count == COUNT_PIDS) { - log_error("Non-recursive counting is only supported when counting processes, not tasks. Use -P or -k."); - return -EINVAL; - } - - return 1; -} - -static const char* counting_what(void) { - if (arg_count == COUNT_PIDS) - return "tasks"; - else if (arg_count == COUNT_ALL_PROCESSES) - return "all processes (incl. kernel)"; - else - return "userspace processes (excl. kernel)"; -} - -static int get_cgroup_root(char **ret) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_free_ char *unit = NULL, *path = NULL; - const char *m; - int r; - - if (!arg_machine) { - r = cg_get_root_path(ret); - if (r < 0) - return log_error_errno(r, "Failed to get root control group path: %m"); - - return 0; - } - - m = strjoina("/run/systemd/machines/", arg_machine); - r = parse_env_file(m, NEWLINE, "SCOPE", &unit, NULL); - if (r < 0) - return log_error_errno(r, "Failed to load machine data: %m"); - - path = unit_dbus_path_from_name(unit); - if (!path) - return log_oom(); - - r = bus_connect_transport_systemd(BUS_TRANSPORT_LOCAL, NULL, false, &bus); - if (r < 0) - return log_error_errno(r, "Failed to create bus connection: %m"); - - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - path, - unit_dbus_interface_from_name(unit), - "ControlGroup", - &error, - ret); - if (r < 0) - return log_error_errno(r, "Failed to query unit control group path: %s", bus_error_message(&error, r)); - - return 0; -} - -int main(int argc, char *argv[]) { - int r; - Hashmap *a = NULL, *b = NULL; - unsigned iteration = 0; - usec_t last_refresh = 0; - bool quit = false, immediate_refresh = false; - _cleanup_free_ char *root = NULL; - CGroupMask mask; - - log_parse_environment(); - log_open(); - - r = cg_mask_supported(&mask); - if (r < 0) { - log_error_errno(r, "Failed to determine supported controllers: %m"); - goto finish; - } - - arg_count = (mask & CGROUP_MASK_PIDS) ? COUNT_PIDS : COUNT_USERSPACE_PROCESSES; - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = get_cgroup_root(&root); - if (r < 0) { - log_error_errno(r, "Failed to get root control group path: %m"); - goto finish; - } - - a = hashmap_new(&string_hash_ops); - b = hashmap_new(&string_hash_ops); - if (!a || !b) { - r = log_oom(); - goto finish; - } - - signal(SIGWINCH, columns_lines_cache_reset); - - if (arg_iterations == (unsigned) -1) - arg_iterations = on_tty() ? 0 : 1; - - while (!quit) { - Hashmap *c; - usec_t t; - char key; - char h[FORMAT_TIMESPAN_MAX]; - - t = now(CLOCK_MONOTONIC); - - if (t >= last_refresh + arg_delay || immediate_refresh) { - - r = refresh(root, a, b, iteration++); - if (r < 0) { - log_error_errno(r, "Failed to refresh: %m"); - goto finish; - } - - group_hashmap_clear(b); - - c = a; - a = b; - b = c; - - last_refresh = t; - immediate_refresh = false; - } - - display(b); - - if (arg_iterations && iteration >= arg_iterations) - break; - - if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */ - fputs("\n", stdout); - fflush(stdout); - - if (arg_batch) - (void) usleep(last_refresh + arg_delay - t); - else { - r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL); - if (r == -ETIMEDOUT) - continue; - if (r < 0) { - log_error_errno(r, "Couldn't read key: %m"); - goto finish; - } - } - - if (on_tty()) { /* TTY: Clear any user keystroke */ - fputs("\r \r", stdout); - fflush(stdout); - } - - if (arg_batch) - continue; - - switch (key) { - - case ' ': - immediate_refresh = true; - break; - - case 'q': - quit = true; - break; - - case 'p': - arg_order = ORDER_PATH; - break; - - case 't': - arg_order = ORDER_TASKS; - break; - - case 'c': - arg_order = ORDER_CPU; - break; - - case 'm': - arg_order = ORDER_MEMORY; - break; - - case 'i': - arg_order = ORDER_IO; - break; - - case '%': - arg_cpu_type = arg_cpu_type == CPU_TIME ? CPU_PERCENT : CPU_TIME; - break; - - case 'k': - arg_count = arg_count != COUNT_ALL_PROCESSES ? COUNT_ALL_PROCESSES : COUNT_PIDS; - fprintf(stdout, "\nCounting: %s.", counting_what()); - fflush(stdout); - sleep(1); - break; - - case 'P': - arg_count = arg_count != COUNT_USERSPACE_PROCESSES ? COUNT_USERSPACE_PROCESSES : COUNT_PIDS; - fprintf(stdout, "\nCounting: %s.", counting_what()); - fflush(stdout); - sleep(1); - break; - - case 'r': - if (arg_count == COUNT_PIDS) - fprintf(stdout, "\n\aCannot toggle recursive counting, not available in task counting mode."); - else { - arg_recursive = !arg_recursive; - fprintf(stdout, "\nRecursive process counting: %s", yes_no(arg_recursive)); - } - fflush(stdout); - sleep(1); - break; - - case '+': - if (arg_delay < USEC_PER_SEC) - arg_delay += USEC_PER_MSEC*250; - else - arg_delay += USEC_PER_SEC; - - fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0)); - fflush(stdout); - sleep(1); - break; - - case '-': - if (arg_delay <= USEC_PER_MSEC*500) - arg_delay = USEC_PER_MSEC*250; - else if (arg_delay < USEC_PER_MSEC*1250) - arg_delay -= USEC_PER_MSEC*250; - else - arg_delay -= USEC_PER_SEC; - - fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0)); - fflush(stdout); - sleep(1); - break; - - case '?': - case 'h': - -#define ON ANSI_HIGHLIGHT -#define OFF ANSI_NORMAL - - fprintf(stdout, - "\t<" ON "p" OFF "> By path; <" ON "t" OFF "> By tasks/procs; <" ON "c" OFF "> By CPU; <" ON "m" OFF "> By memory; <" ON "i" OFF "> By I/O\n" - "\t<" ON "+" OFF "> Inc. delay; <" ON "-" OFF "> Dec. delay; <" ON "%%" OFF "> Toggle time; <" ON "SPACE" OFF "> Refresh\n" - "\t<" ON "P" OFF "> Toggle count userspace processes; <" ON "k" OFF "> Toggle count all processes\n" - "\t<" ON "r" OFF "> Count processes recursively; <" ON "q" OFF "> Quit"); - fflush(stdout); - sleep(3); - break; - - default: - if (key < ' ') - fprintf(stdout, "\nUnknown key '\\x%x'. Ignoring.", key); - else - fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key); - fflush(stdout); - sleep(1); - break; - } - } - - r = 0; - -finish: - group_hashmap_free(a); - group_hashmap_free(b); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/core/.gitignore b/src/core/.gitignore deleted file mode 100644 index 465b4fcc20..0000000000 --- a/src/core/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/macros.systemd -/triggers.systemd -/systemd.pc diff --git a/src/core/Makefile b/src/core/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/core/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/core/audit-fd.c b/src/core/audit-fd.c deleted file mode 100644 index 76afe3fe15..0000000000 --- a/src/core/audit-fd.c +++ /dev/null @@ -1,73 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 "audit-fd.h" - -#ifdef HAVE_AUDIT - -#include -#include - -#include "fd-util.h" -#include "log.h" -#include "util.h" - -static bool initialized = false; -static int audit_fd; - -int get_audit_fd(void) { - - if (!initialized) { - audit_fd = audit_open(); - - if (audit_fd < 0) { - if (errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT) - log_error_errno(errno, "Failed to connect to audit log: %m"); - - audit_fd = errno ? -errno : -EINVAL; - } - - initialized = true; - } - - return audit_fd; -} - -void close_audit_fd(void) { - - if (initialized && audit_fd >= 0) - safe_close(audit_fd); - - initialized = true; - audit_fd = -ECONNRESET; -} - -#else - -int get_audit_fd(void) { - return -EAFNOSUPPORT; -} - -void close_audit_fd(void) { -} - -#endif diff --git a/src/core/audit-fd.h b/src/core/audit-fd.h deleted file mode 100644 index 0eccb59210..0000000000 --- a/src/core/audit-fd.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -int get_audit_fd(void); -void close_audit_fd(void); diff --git a/src/core/automount.c b/src/core/automount.c deleted file mode 100644 index f06d837e30..0000000000 --- a/src/core/automount.c +++ /dev/null @@ -1,1108 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "async.h" -#include "automount.h" -#include "bus-error.h" -#include "bus-util.h" -#include "dbus-automount.h" -#include "fd-util.h" -#include "formats-util.h" -#include "io-util.h" -#include "label.h" -#include "mkdir.h" -#include "mount-util.h" -#include "mount.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "special.h" -#include "stdio-util.h" -#include "string-table.h" -#include "string-util.h" -#include "unit-name.h" -#include "unit.h" - -static const UnitActiveState state_translation_table[_AUTOMOUNT_STATE_MAX] = { - [AUTOMOUNT_DEAD] = UNIT_INACTIVE, - [AUTOMOUNT_WAITING] = UNIT_ACTIVE, - [AUTOMOUNT_RUNNING] = UNIT_ACTIVE, - [AUTOMOUNT_FAILED] = UNIT_FAILED -}; - -struct expire_data { - int dev_autofs_fd; - int ioctl_fd; -}; - -static inline void expire_data_free(struct expire_data *data) { - if (!data) - return; - - safe_close(data->dev_autofs_fd); - safe_close(data->ioctl_fd); - free(data); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct expire_data*, expire_data_free); - -static int open_dev_autofs(Manager *m); -static int automount_dispatch_io(sd_event_source *s, int fd, uint32_t events, void *userdata); -static int automount_start_expire(Automount *a); -static void automount_stop_expire(Automount *a); -static int automount_send_ready(Automount *a, Set *tokens, int status); - -static void automount_init(Unit *u) { - Automount *a = AUTOMOUNT(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - a->pipe_fd = -1; - a->directory_mode = 0755; - UNIT(a)->ignore_on_isolate = true; -} - -static void unmount_autofs(Automount *a) { - int r; - - assert(a); - - if (a->pipe_fd < 0) - return; - - automount_send_ready(a, a->tokens, -EHOSTDOWN); - automount_send_ready(a, a->expire_tokens, -EHOSTDOWN); - - a->pipe_event_source = sd_event_source_unref(a->pipe_event_source); - a->pipe_fd = safe_close(a->pipe_fd); - - /* If we reload/reexecute things we keep the mount point - * around */ - if (a->where && - (UNIT(a)->manager->exit_code != MANAGER_RELOAD && - UNIT(a)->manager->exit_code != MANAGER_REEXECUTE)) { - r = repeat_unmount(a->where, MNT_DETACH); - if (r < 0) - log_error_errno(r, "Failed to unmount: %m"); - } -} - -static void automount_done(Unit *u) { - Automount *a = AUTOMOUNT(u); - - assert(a); - - unmount_autofs(a); - - a->where = mfree(a->where); - - a->tokens = set_free(a->tokens); - a->expire_tokens = set_free(a->expire_tokens); - - a->expire_event_source = sd_event_source_unref(a->expire_event_source); -} - -static int automount_add_mount_links(Automount *a) { - _cleanup_free_ char *parent = NULL; - - assert(a); - - parent = dirname_malloc(a->where); - if (!parent) - return -ENOMEM; - - return unit_require_mounts_for(UNIT(a), parent); -} - -static int automount_add_default_dependencies(Automount *a) { - int r; - - assert(a); - - if (!UNIT(a)->default_dependencies) - return 0; - - if (!MANAGER_IS_SYSTEM(UNIT(a)->manager)) - return 0; - - r = unit_add_two_dependencies_by_name(UNIT(a), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); - if (r < 0) - return r; - - return 0; -} - -static int automount_verify(Automount *a) { - _cleanup_free_ char *e = NULL; - int r; - - assert(a); - - if (UNIT(a)->load_state != UNIT_LOADED) - return 0; - - if (path_equal(a->where, "/")) { - log_unit_error(UNIT(a), "Cannot have an automount unit for the root directory. Refusing."); - return -EINVAL; - } - - r = unit_name_from_path(a->where, ".automount", &e); - if (r < 0) - return log_unit_error(UNIT(a), "Failed to generate unit name from path: %m"); - - if (!unit_has_name(UNIT(a), e)) { - log_unit_error(UNIT(a), "Where= setting doesn't match unit name. Refusing."); - return -EINVAL; - } - - return 0; -} - -static int automount_load(Unit *u) { - Automount *a = AUTOMOUNT(u); - int r; - - assert(u); - assert(u->load_state == UNIT_STUB); - - /* Load a .automount file */ - r = unit_load_fragment_and_dropin_optional(u); - if (r < 0) - return r; - - if (u->load_state == UNIT_LOADED) { - Unit *x; - - if (!a->where) { - r = unit_name_to_path(u->id, &a->where); - if (r < 0) - return r; - } - - path_kill_slashes(a->where); - - r = unit_load_related_unit(u, ".mount", &x); - if (r < 0) - return r; - - r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, x, true); - if (r < 0) - return r; - - r = automount_add_mount_links(a); - if (r < 0) - return r; - - r = automount_add_default_dependencies(a); - if (r < 0) - return r; - } - - return automount_verify(a); -} - -static void automount_set_state(Automount *a, AutomountState state) { - AutomountState old_state; - assert(a); - - old_state = a->state; - a->state = state; - - if (state != AUTOMOUNT_RUNNING) - automount_stop_expire(a); - - if (state != AUTOMOUNT_WAITING && - state != AUTOMOUNT_RUNNING) - unmount_autofs(a); - - if (state != old_state) - log_unit_debug(UNIT(a), "Changed %s -> %s", automount_state_to_string(old_state), automount_state_to_string(state)); - - unit_notify(UNIT(a), state_translation_table[old_state], state_translation_table[state], true); -} - -static int automount_coldplug(Unit *u) { - Automount *a = AUTOMOUNT(u); - int r; - - assert(a); - assert(a->state == AUTOMOUNT_DEAD); - - if (a->deserialized_state != a->state) { - - r = open_dev_autofs(u->manager); - if (r < 0) - return r; - - if (a->deserialized_state == AUTOMOUNT_WAITING || - a->deserialized_state == AUTOMOUNT_RUNNING) { - assert(a->pipe_fd >= 0); - - r = sd_event_add_io(u->manager->event, &a->pipe_event_source, a->pipe_fd, EPOLLIN, automount_dispatch_io, u); - if (r < 0) - return r; - - (void) sd_event_source_set_description(a->pipe_event_source, "automount-io"); - } - - automount_set_state(a, a->deserialized_state); - } - - return 0; -} - -static void automount_dump(Unit *u, FILE *f, const char *prefix) { - char time_string[FORMAT_TIMESPAN_MAX]; - Automount *a = AUTOMOUNT(u); - - assert(a); - - fprintf(f, - "%sAutomount State: %s\n" - "%sResult: %s\n" - "%sWhere: %s\n" - "%sDirectoryMode: %04o\n" - "%sTimeoutIdleUSec: %s\n", - prefix, automount_state_to_string(a->state), - prefix, automount_result_to_string(a->result), - prefix, a->where, - prefix, a->directory_mode, - prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, a->timeout_idle_usec, USEC_PER_SEC)); -} - -static void automount_enter_dead(Automount *a, AutomountResult f) { - assert(a); - - if (f != AUTOMOUNT_SUCCESS) - a->result = f; - - automount_set_state(a, a->result != AUTOMOUNT_SUCCESS ? AUTOMOUNT_FAILED : AUTOMOUNT_DEAD); -} - -static int open_dev_autofs(Manager *m) { - struct autofs_dev_ioctl param; - - assert(m); - - if (m->dev_autofs_fd >= 0) - return m->dev_autofs_fd; - - label_fix("/dev/autofs", false, false); - - m->dev_autofs_fd = open("/dev/autofs", O_CLOEXEC|O_RDONLY); - if (m->dev_autofs_fd < 0) - return log_error_errno(errno, "Failed to open /dev/autofs: %m"); - - init_autofs_dev_ioctl(¶m); - if (ioctl(m->dev_autofs_fd, AUTOFS_DEV_IOCTL_VERSION, ¶m) < 0) { - m->dev_autofs_fd = safe_close(m->dev_autofs_fd); - return -errno; - } - - log_debug("Autofs kernel version %i.%i", param.ver_major, param.ver_minor); - - return m->dev_autofs_fd; -} - -static int open_ioctl_fd(int dev_autofs_fd, const char *where, dev_t devid) { - struct autofs_dev_ioctl *param; - size_t l; - - assert(dev_autofs_fd >= 0); - assert(where); - - l = sizeof(struct autofs_dev_ioctl) + strlen(where) + 1; - param = alloca(l); - - init_autofs_dev_ioctl(param); - param->size = l; - param->ioctlfd = -1; - param->openmount.devid = devid; - strcpy(param->path, where); - - if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_OPENMOUNT, param) < 0) - return -errno; - - if (param->ioctlfd < 0) - return -EIO; - - (void) fd_cloexec(param->ioctlfd, true); - return param->ioctlfd; -} - -static int autofs_protocol(int dev_autofs_fd, int ioctl_fd) { - uint32_t major, minor; - struct autofs_dev_ioctl param; - - assert(dev_autofs_fd >= 0); - assert(ioctl_fd >= 0); - - init_autofs_dev_ioctl(¶m); - param.ioctlfd = ioctl_fd; - - if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_PROTOVER, ¶m) < 0) - return -errno; - - major = param.protover.version; - - init_autofs_dev_ioctl(¶m); - param.ioctlfd = ioctl_fd; - - if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_PROTOSUBVER, ¶m) < 0) - return -errno; - - minor = param.protosubver.sub_version; - - log_debug("Autofs protocol version %i.%i", major, minor); - return 0; -} - -static int autofs_set_timeout(int dev_autofs_fd, int ioctl_fd, usec_t usec) { - struct autofs_dev_ioctl param; - - assert(dev_autofs_fd >= 0); - assert(ioctl_fd >= 0); - - init_autofs_dev_ioctl(¶m); - param.ioctlfd = ioctl_fd; - - /* Convert to seconds, rounding up. */ - param.timeout.timeout = (usec + USEC_PER_SEC - 1) / USEC_PER_SEC; - - if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_TIMEOUT, ¶m) < 0) - return -errno; - - return 0; -} - -static int autofs_send_ready(int dev_autofs_fd, int ioctl_fd, uint32_t token, int status) { - struct autofs_dev_ioctl param; - - assert(dev_autofs_fd >= 0); - assert(ioctl_fd >= 0); - - init_autofs_dev_ioctl(¶m); - param.ioctlfd = ioctl_fd; - - if (status != 0) { - param.fail.token = token; - param.fail.status = status; - } else - param.ready.token = token; - - if (ioctl(dev_autofs_fd, status ? AUTOFS_DEV_IOCTL_FAIL : AUTOFS_DEV_IOCTL_READY, ¶m) < 0) - return -errno; - - return 0; -} - -static int automount_send_ready(Automount *a, Set *tokens, int status) { - _cleanup_close_ int ioctl_fd = -1; - unsigned token; - int r; - - assert(a); - assert(status <= 0); - - if (set_isempty(tokens)) - return 0; - - ioctl_fd = open_ioctl_fd(UNIT(a)->manager->dev_autofs_fd, a->where, a->dev_id); - if (ioctl_fd < 0) - return ioctl_fd; - - if (status != 0) - log_unit_debug_errno(UNIT(a), status, "Sending failure: %m"); - else - log_unit_debug(UNIT(a), "Sending success."); - - r = 0; - - /* Autofs thankfully does not hand out 0 as a token */ - while ((token = PTR_TO_UINT(set_steal_first(tokens)))) { - int k; - - /* Autofs fun fact II: - * - * if you pass a positive status code here, the kernel will - * freeze! Yay! */ - - k = autofs_send_ready(UNIT(a)->manager->dev_autofs_fd, - ioctl_fd, - token, - status); - if (k < 0) - r = k; - } - - return r; -} - -static void automount_trigger_notify(Unit *u, Unit *other) { - Automount *a = AUTOMOUNT(u); - int r; - - assert(a); - assert(other); - - /* Filter out invocations with bogus state */ - if (other->load_state != UNIT_LOADED || other->type != UNIT_MOUNT) - return; - - /* Don't propagate state changes from the mount if we are already down */ - if (!IN_SET(a->state, AUTOMOUNT_WAITING, AUTOMOUNT_RUNNING)) - return; - - /* Propagate start limit hit state */ - if (other->start_limit_hit) { - automount_enter_dead(a, AUTOMOUNT_FAILURE_MOUNT_START_LIMIT_HIT); - return; - } - - /* Don't propagate anything if there's still a job queued */ - if (other->job) - return; - - /* The mount is successfully established */ - if (IN_SET(MOUNT(other)->state, MOUNT_MOUNTED, MOUNT_REMOUNTING)) { - (void) automount_send_ready(a, a->tokens, 0); - - r = automount_start_expire(a); - if (r < 0) - log_unit_warning_errno(UNIT(a), r, "Failed to start expiration timer, ignoring: %m"); - - automount_set_state(a, AUTOMOUNT_RUNNING); - } - - /* The mount is in some unhappy state now, let's unfreeze any waiting clients */ - if (IN_SET(MOUNT(other)->state, - MOUNT_DEAD, MOUNT_UNMOUNTING, - MOUNT_MOUNTING_SIGTERM, MOUNT_MOUNTING_SIGKILL, - MOUNT_REMOUNTING_SIGTERM, MOUNT_REMOUNTING_SIGKILL, - MOUNT_UNMOUNTING_SIGTERM, MOUNT_UNMOUNTING_SIGKILL, - MOUNT_FAILED)) { - - (void) automount_send_ready(a, a->tokens, -ENODEV); - - automount_set_state(a, AUTOMOUNT_WAITING); - } -} - -static void automount_enter_waiting(Automount *a) { - _cleanup_close_ int ioctl_fd = -1; - int p[2] = { -1, -1 }; - char name[sizeof("systemd-")-1 + DECIMAL_STR_MAX(pid_t) + 1]; - char options[sizeof("fd=,pgrp=,minproto=5,maxproto=5,direct")-1 - + DECIMAL_STR_MAX(int) + DECIMAL_STR_MAX(gid_t) + 1]; - bool mounted = false; - int r, dev_autofs_fd; - struct stat st; - - assert(a); - assert(a->pipe_fd < 0); - assert(a->where); - - set_clear(a->tokens); - - r = unit_fail_if_symlink(UNIT(a), a->where); - if (r < 0) - goto fail; - - (void) mkdir_p_label(a->where, 0555); - - unit_warn_if_dir_nonempty(UNIT(a), a->where); - - dev_autofs_fd = open_dev_autofs(UNIT(a)->manager); - if (dev_autofs_fd < 0) { - r = dev_autofs_fd; - goto fail; - } - - if (pipe2(p, O_NONBLOCK|O_CLOEXEC) < 0) { - r = -errno; - goto fail; - } - - xsprintf(options, "fd=%i,pgrp="PID_FMT",minproto=5,maxproto=5,direct", p[1], getpgrp()); - xsprintf(name, "systemd-"PID_FMT, getpid()); - if (mount(name, a->where, "autofs", 0, options) < 0) { - r = -errno; - goto fail; - } - - mounted = true; - - p[1] = safe_close(p[1]); - - if (stat(a->where, &st) < 0) { - r = -errno; - goto fail; - } - - ioctl_fd = open_ioctl_fd(dev_autofs_fd, a->where, st.st_dev); - if (ioctl_fd < 0) { - r = ioctl_fd; - goto fail; - } - - r = autofs_protocol(dev_autofs_fd, ioctl_fd); - if (r < 0) - goto fail; - - r = autofs_set_timeout(dev_autofs_fd, ioctl_fd, a->timeout_idle_usec); - if (r < 0) - goto fail; - - /* Autofs fun fact: - * - * Unless we close the ioctl fd here, for some weird reason - * the direct mount will not receive events from the - * kernel. */ - - r = sd_event_add_io(UNIT(a)->manager->event, &a->pipe_event_source, p[0], EPOLLIN, automount_dispatch_io, a); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(a->pipe_event_source, "automount-io"); - - a->pipe_fd = p[0]; - a->dev_id = st.st_dev; - - automount_set_state(a, AUTOMOUNT_WAITING); - - return; - -fail: - log_unit_error_errno(UNIT(a), r, "Failed to initialize automounter: %m"); - - safe_close_pair(p); - - if (mounted) { - r = repeat_unmount(a->where, MNT_DETACH); - if (r < 0) - log_error_errno(r, "Failed to unmount, ignoring: %m"); - } - - automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); -} - -static void *expire_thread(void *p) { - struct autofs_dev_ioctl param; - _cleanup_(expire_data_freep) struct expire_data *data = (struct expire_data*)p; - int r; - - assert(data->dev_autofs_fd >= 0); - assert(data->ioctl_fd >= 0); - - init_autofs_dev_ioctl(¶m); - param.ioctlfd = data->ioctl_fd; - - do { - r = ioctl(data->dev_autofs_fd, AUTOFS_DEV_IOCTL_EXPIRE, ¶m); - } while (r >= 0); - - if (errno != EAGAIN) - log_warning_errno(errno, "Failed to expire automount, ignoring: %m"); - - return NULL; -} - -static int automount_dispatch_expire(sd_event_source *source, usec_t usec, void *userdata) { - Automount *a = AUTOMOUNT(userdata); - _cleanup_(expire_data_freep) struct expire_data *data = NULL; - int r; - - assert(a); - assert(source == a->expire_event_source); - - data = new0(struct expire_data, 1); - if (!data) - return log_oom(); - - data->ioctl_fd = -1; - - data->dev_autofs_fd = fcntl(UNIT(a)->manager->dev_autofs_fd, F_DUPFD_CLOEXEC, 3); - if (data->dev_autofs_fd < 0) - return log_unit_error_errno(UNIT(a), errno, "Failed to duplicate autofs fd: %m"); - - data->ioctl_fd = open_ioctl_fd(UNIT(a)->manager->dev_autofs_fd, a->where, a->dev_id); - if (data->ioctl_fd < 0) - return log_unit_error_errno(UNIT(a), data->ioctl_fd, "Couldn't open autofs ioctl fd: %m"); - - r = asynchronous_job(expire_thread, data); - if (r < 0) - return log_unit_error_errno(UNIT(a), r, "Failed to start expire job: %m"); - - data = NULL; - - return automount_start_expire(a); -} - -static int automount_start_expire(Automount *a) { - int r; - usec_t timeout; - - assert(a); - - if (a->timeout_idle_usec == 0) - return 0; - - timeout = now(CLOCK_MONOTONIC) + MAX(a->timeout_idle_usec/3, USEC_PER_SEC); - - if (a->expire_event_source) { - r = sd_event_source_set_time(a->expire_event_source, timeout); - if (r < 0) - return r; - - return sd_event_source_set_enabled(a->expire_event_source, SD_EVENT_ONESHOT); - } - - r = sd_event_add_time( - UNIT(a)->manager->event, - &a->expire_event_source, - CLOCK_MONOTONIC, timeout, 0, - automount_dispatch_expire, a); - if (r < 0) - return r; - - (void) sd_event_source_set_description(a->expire_event_source, "automount-expire"); - - return 0; -} - -static void automount_stop_expire(Automount *a) { - assert(a); - - if (!a->expire_event_source) - return; - - (void) sd_event_source_set_enabled(a->expire_event_source, SD_EVENT_OFF); -} - -static void automount_enter_runnning(Automount *a) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - struct stat st; - int r; - - assert(a); - - /* We don't take mount requests anymore if we are supposed to - * shut down anyway */ - if (unit_stop_pending(UNIT(a))) { - log_unit_debug(UNIT(a), "Suppressing automount request since unit stop is scheduled."); - automount_send_ready(a, a->tokens, -EHOSTDOWN); - automount_send_ready(a, a->expire_tokens, -EHOSTDOWN); - return; - } - - mkdir_p_label(a->where, a->directory_mode); - - /* Before we do anything, let's see if somebody is playing games with us? */ - if (lstat(a->where, &st) < 0) { - log_unit_warning_errno(UNIT(a), errno, "Failed to stat automount point: %m"); - goto fail; - } - - if (!S_ISDIR(st.st_mode) || st.st_dev != a->dev_id) - log_unit_info(UNIT(a), "Automount point already active?"); - else { - Unit *trigger; - - trigger = UNIT_TRIGGER(UNIT(a)); - if (!trigger) { - log_unit_error(UNIT(a), "Unit to trigger vanished."); - goto fail; - } - - r = manager_add_job(UNIT(a)->manager, JOB_START, trigger, JOB_REPLACE, &error, NULL); - if (r < 0) { - log_unit_warning(UNIT(a), "Failed to queue mount startup job: %s", bus_error_message(&error, r)); - goto fail; - } - } - - automount_set_state(a, AUTOMOUNT_RUNNING); - return; - -fail: - automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); -} - -static int automount_start(Unit *u) { - Automount *a = AUTOMOUNT(u); - Unit *trigger; - int r; - - assert(a); - assert(a->state == AUTOMOUNT_DEAD || a->state == AUTOMOUNT_FAILED); - - if (path_is_mount_point(a->where, 0) > 0) { - log_unit_error(u, "Path %s is already a mount point, refusing start.", a->where); - return -EEXIST; - } - - trigger = UNIT_TRIGGER(u); - if (!trigger || trigger->load_state != UNIT_LOADED) { - log_unit_error(u, "Refusing to start, unit to trigger not loaded."); - return -ENOENT; - } - - r = unit_start_limit_test(u); - if (r < 0) { - automount_enter_dead(a, AUTOMOUNT_FAILURE_START_LIMIT_HIT); - return r; - } - - a->result = AUTOMOUNT_SUCCESS; - automount_enter_waiting(a); - return 1; -} - -static int automount_stop(Unit *u) { - Automount *a = AUTOMOUNT(u); - - assert(a); - assert(a->state == AUTOMOUNT_WAITING || a->state == AUTOMOUNT_RUNNING); - - automount_enter_dead(a, AUTOMOUNT_SUCCESS); - return 1; -} - -static int automount_serialize(Unit *u, FILE *f, FDSet *fds) { - Automount *a = AUTOMOUNT(u); - Iterator i; - void *p; - int r; - - assert(a); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", automount_state_to_string(a->state)); - unit_serialize_item(u, f, "result", automount_result_to_string(a->result)); - unit_serialize_item_format(u, f, "dev-id", "%u", (unsigned) a->dev_id); - - SET_FOREACH(p, a->tokens, i) - unit_serialize_item_format(u, f, "token", "%u", PTR_TO_UINT(p)); - SET_FOREACH(p, a->expire_tokens, i) - unit_serialize_item_format(u, f, "expire-token", "%u", PTR_TO_UINT(p)); - - r = unit_serialize_item_fd(u, f, fds, "pipe-fd", a->pipe_fd); - if (r < 0) - return r; - - return 0; -} - -static int automount_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Automount *a = AUTOMOUNT(u); - int r; - - assert(a); - assert(fds); - - if (streq(key, "state")) { - AutomountState state; - - state = automount_state_from_string(value); - if (state < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - a->deserialized_state = state; - } else if (streq(key, "result")) { - AutomountResult f; - - f = automount_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse result value: %s", value); - else if (f != AUTOMOUNT_SUCCESS) - a->result = f; - - } else if (streq(key, "dev-id")) { - unsigned d; - - if (safe_atou(value, &d) < 0) - log_unit_debug(u, "Failed to parse dev-id value: %s", value); - else - a->dev_id = (unsigned) d; - } else if (streq(key, "token")) { - unsigned token; - - if (safe_atou(value, &token) < 0) - log_unit_debug(u, "Failed to parse token value: %s", value); - else { - r = set_ensure_allocated(&a->tokens, NULL); - if (r < 0) { - log_oom(); - return 0; - } - - r = set_put(a->tokens, UINT_TO_PTR(token)); - if (r < 0) - log_unit_error_errno(u, r, "Failed to add token to set: %m"); - } - } else if (streq(key, "expire-token")) { - unsigned token; - - if (safe_atou(value, &token) < 0) - log_unit_debug(u, "Failed to parse token value: %s", value); - else { - r = set_ensure_allocated(&a->expire_tokens, NULL); - if (r < 0) { - log_oom(); - return 0; - } - - r = set_put(a->expire_tokens, UINT_TO_PTR(token)); - if (r < 0) - log_unit_error_errno(u, r, "Failed to add expire token to set: %m"); - } - } else if (streq(key, "pipe-fd")) { - int fd; - - if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse pipe-fd value: %s", value); - else { - safe_close(a->pipe_fd); - a->pipe_fd = fdset_remove(fds, fd); - } - } else - log_unit_debug(u, "Unknown serialization key: %s", key); - - return 0; -} - -static UnitActiveState automount_active_state(Unit *u) { - assert(u); - - return state_translation_table[AUTOMOUNT(u)->state]; -} - -static const char *automount_sub_state_to_string(Unit *u) { - assert(u); - - return automount_state_to_string(AUTOMOUNT(u)->state); -} - -static bool automount_check_gc(Unit *u) { - assert(u); - - if (!UNIT_TRIGGER(u)) - return false; - - return UNIT_VTABLE(UNIT_TRIGGER(u))->check_gc(UNIT_TRIGGER(u)); -} - -static int automount_dispatch_io(sd_event_source *s, int fd, uint32_t events, void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - union autofs_v5_packet_union packet; - Automount *a = AUTOMOUNT(userdata); - struct stat st; - Unit *trigger; - int r; - - assert(a); - assert(fd == a->pipe_fd); - - if (events != EPOLLIN) { - log_unit_error(UNIT(a), "Got invalid poll event %"PRIu32" on pipe (fd=%d)", events, fd); - goto fail; - } - - r = loop_read_exact(a->pipe_fd, &packet, sizeof(packet), true); - if (r < 0) { - log_unit_error_errno(UNIT(a), r, "Invalid read from pipe: %m"); - goto fail; - } - - switch (packet.hdr.type) { - - case autofs_ptype_missing_direct: - - if (packet.v5_packet.pid > 0) { - _cleanup_free_ char *p = NULL; - - get_process_comm(packet.v5_packet.pid, &p); - log_unit_info(UNIT(a), "Got automount request for %s, triggered by %"PRIu32" (%s)", a->where, packet.v5_packet.pid, strna(p)); - } else - log_unit_debug(UNIT(a), "Got direct mount request on %s", a->where); - - r = set_ensure_allocated(&a->tokens, NULL); - if (r < 0) { - log_unit_error(UNIT(a), "Failed to allocate token set."); - goto fail; - } - - r = set_put(a->tokens, UINT_TO_PTR(packet.v5_packet.wait_queue_token)); - if (r < 0) { - log_unit_error_errno(UNIT(a), r, "Failed to remember token: %m"); - goto fail; - } - - automount_enter_runnning(a); - break; - - case autofs_ptype_expire_direct: - log_unit_debug(UNIT(a), "Got direct umount request on %s", a->where); - - automount_stop_expire(a); - - r = set_ensure_allocated(&a->expire_tokens, NULL); - if (r < 0) { - log_unit_error(UNIT(a), "Failed to allocate token set."); - goto fail; - } - - r = set_put(a->expire_tokens, UINT_TO_PTR(packet.v5_packet.wait_queue_token)); - if (r < 0) { - log_unit_error_errno(UNIT(a), r, "Failed to remember token: %m"); - goto fail; - } - - /* Before we do anything, let's see if somebody is playing games with us? */ - if (lstat(a->where, &st) < 0) { - log_unit_warning_errno(UNIT(a), errno, "Failed to stat automount point: %m"); - goto fail; - } - - if (!S_ISDIR(st.st_mode) || st.st_dev == a->dev_id) { - log_unit_info(UNIT(a), "Automount point already unmounted?"); - automount_send_ready(a, a->expire_tokens, 0); - break; - } - - trigger = UNIT_TRIGGER(UNIT(a)); - if (!trigger) { - log_unit_error(UNIT(a), "Unit to trigger vanished."); - goto fail; - } - - r = manager_add_job(UNIT(a)->manager, JOB_STOP, trigger, JOB_REPLACE, &error, NULL); - if (r < 0) { - log_unit_warning(UNIT(a), "Failed to queue umount startup job: %s", bus_error_message(&error, r)); - goto fail; - } - break; - - default: - log_unit_error(UNIT(a), "Received unknown automount request %i", packet.hdr.type); - break; - } - - return 0; - -fail: - automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); - return 0; -} - -static void automount_shutdown(Manager *m) { - assert(m); - - m->dev_autofs_fd = safe_close(m->dev_autofs_fd); -} - -static void automount_reset_failed(Unit *u) { - Automount *a = AUTOMOUNT(u); - - assert(a); - - if (a->state == AUTOMOUNT_FAILED) - automount_set_state(a, AUTOMOUNT_DEAD); - - a->result = AUTOMOUNT_SUCCESS; -} - -static bool automount_supported(void) { - static int supported = -1; - - if (supported < 0) - supported = access("/dev/autofs", F_OK) >= 0; - - return supported; -} - -static const char* const automount_result_table[_AUTOMOUNT_RESULT_MAX] = { - [AUTOMOUNT_SUCCESS] = "success", - [AUTOMOUNT_FAILURE_RESOURCES] = "resources", - [AUTOMOUNT_FAILURE_START_LIMIT_HIT] = "start-limit-hit", - [AUTOMOUNT_FAILURE_MOUNT_START_LIMIT_HIT] = "mount-start-limit-hit", -}; - -DEFINE_STRING_TABLE_LOOKUP(automount_result, AutomountResult); - -const UnitVTable automount_vtable = { - .object_size = sizeof(Automount), - - .sections = - "Unit\0" - "Automount\0" - "Install\0", - - .init = automount_init, - .load = automount_load, - .done = automount_done, - - .coldplug = automount_coldplug, - - .dump = automount_dump, - - .start = automount_start, - .stop = automount_stop, - - .serialize = automount_serialize, - .deserialize_item = automount_deserialize_item, - - .active_state = automount_active_state, - .sub_state_to_string = automount_sub_state_to_string, - - .check_gc = automount_check_gc, - - .trigger_notify = automount_trigger_notify, - - .reset_failed = automount_reset_failed, - - .bus_vtable = bus_automount_vtable, - - .shutdown = automount_shutdown, - .supported = automount_supported, - - .status_message_formats = { - .finished_start_job = { - [JOB_DONE] = "Set up automount %s.", - [JOB_FAILED] = "Failed to set up automount %s.", - }, - .finished_stop_job = { - [JOB_DONE] = "Unset automount %s.", - [JOB_FAILED] = "Failed to unset automount %s.", - }, - }, -}; diff --git a/src/core/automount.h b/src/core/automount.h deleted file mode 100644 index 76a201178e..0000000000 --- a/src/core/automount.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct Automount Automount; - -#include "unit.h" - -typedef enum AutomountResult { - AUTOMOUNT_SUCCESS, - AUTOMOUNT_FAILURE_RESOURCES, - AUTOMOUNT_FAILURE_START_LIMIT_HIT, - AUTOMOUNT_FAILURE_MOUNT_START_LIMIT_HIT, - _AUTOMOUNT_RESULT_MAX, - _AUTOMOUNT_RESULT_INVALID = -1 -} AutomountResult; - -struct Automount { - Unit meta; - - AutomountState state, deserialized_state; - - char *where; - usec_t timeout_idle_usec; - - int pipe_fd; - sd_event_source *pipe_event_source; - mode_t directory_mode; - dev_t dev_id; - - Set *tokens; - Set *expire_tokens; - - sd_event_source *expire_event_source; - - AutomountResult result; -}; - -extern const UnitVTable automount_vtable; - -const char* automount_result_to_string(AutomountResult i) _const_; -AutomountResult automount_result_from_string(const char *s) _pure_; diff --git a/src/core/bus-policy.c b/src/core/bus-policy.c deleted file mode 100644 index 4907c268e8..0000000000 --- a/src/core/bus-policy.c +++ /dev/null @@ -1,180 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Daniel Mack - - 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 "alloc-util.h" -#include "bus-kernel.h" -#include "bus-policy.h" -#include "kdbus.h" -#include "string-table.h" -#include "user-util.h" -#include "util.h" - -int bus_kernel_translate_access(BusPolicyAccess access) { - assert(access >= 0); - assert(access < _BUS_POLICY_ACCESS_MAX); - - switch (access) { - - case BUS_POLICY_ACCESS_SEE: - return KDBUS_POLICY_SEE; - - case BUS_POLICY_ACCESS_TALK: - return KDBUS_POLICY_TALK; - - case BUS_POLICY_ACCESS_OWN: - return KDBUS_POLICY_OWN; - - default: - assert_not_reached("Unknown policy access"); - } -} - -int bus_kernel_translate_policy(const BusNamePolicy *policy, struct kdbus_item *item) { - int r; - - assert(policy); - assert(item); - - switch (policy->type) { - - case BUSNAME_POLICY_TYPE_USER: { - const char *user = policy->name; - uid_t uid; - - r = get_user_creds(&user, &uid, NULL, NULL, NULL); - if (r < 0) - return r; - - item->policy_access.type = KDBUS_POLICY_ACCESS_USER; - item->policy_access.id = uid; - break; - } - - case BUSNAME_POLICY_TYPE_GROUP: { - const char *group = policy->name; - gid_t gid; - - r = get_group_creds(&group, &gid); - if (r < 0) - return r; - - item->policy_access.type = KDBUS_POLICY_ACCESS_GROUP; - item->policy_access.id = gid; - break; - } - - default: - assert_not_reached("Unknown policy type"); - } - - item->policy_access.access = bus_kernel_translate_access(policy->access); - - return 0; -} - -int bus_kernel_make_starter( - int fd, - const char *name, - bool activating, - bool accept_fd, - BusNamePolicy *policy, - BusPolicyAccess world_policy) { - - struct kdbus_cmd_free cmd_free = { .size = sizeof(cmd_free) }; - struct kdbus_cmd_hello *hello; - struct kdbus_item *n; - size_t policy_cnt = 0; - BusNamePolicy *po; - size_t size; - int r; - - assert(fd >= 0); - assert(name); - - LIST_FOREACH(policy, po, policy) - policy_cnt++; - - if (world_policy >= 0) - policy_cnt++; - - size = offsetof(struct kdbus_cmd_hello, items) + - ALIGN8(offsetof(struct kdbus_item, str) + strlen(name) + 1) + - policy_cnt * ALIGN8(offsetof(struct kdbus_item, policy_access) + sizeof(struct kdbus_policy_access)); - - hello = alloca0_align(size, 8); - - n = hello->items; - strcpy(n->str, name); - n->size = offsetof(struct kdbus_item, str) + strlen(n->str) + 1; - n->type = KDBUS_ITEM_NAME; - n = KDBUS_ITEM_NEXT(n); - - LIST_FOREACH(policy, po, policy) { - n->type = KDBUS_ITEM_POLICY_ACCESS; - n->size = offsetof(struct kdbus_item, policy_access) + sizeof(struct kdbus_policy_access); - - r = bus_kernel_translate_policy(po, n); - if (r < 0) - return r; - - n = KDBUS_ITEM_NEXT(n); - } - - if (world_policy >= 0) { - n->type = KDBUS_ITEM_POLICY_ACCESS; - n->size = offsetof(struct kdbus_item, policy_access) + sizeof(struct kdbus_policy_access); - n->policy_access.type = KDBUS_POLICY_ACCESS_WORLD; - n->policy_access.access = bus_kernel_translate_access(world_policy); - } - - hello->size = size; - hello->flags = - (activating ? KDBUS_HELLO_ACTIVATOR : KDBUS_HELLO_POLICY_HOLDER) | - (accept_fd ? KDBUS_HELLO_ACCEPT_FD : 0); - hello->pool_size = KDBUS_POOL_SIZE; - hello->attach_flags_send = _KDBUS_ATTACH_ANY; - hello->attach_flags_recv = _KDBUS_ATTACH_ANY; - - if (ioctl(fd, KDBUS_CMD_HELLO, hello) < 0) { - if (errno == ENOTTY) /* Major API change */ - return -ESOCKTNOSUPPORT; - return -errno; - } - - /* not interested in any output values */ - cmd_free.offset = hello->offset; - (void) ioctl(fd, KDBUS_CMD_FREE, &cmd_free); - - /* The higher 32bit of the bus_flags fields are considered - * 'incompatible flags'. Refuse them all for now. */ - if (hello->bus_flags > 0xFFFFFFFFULL) - return -ESOCKTNOSUPPORT; - - return fd; -} - -static const char* const bus_policy_access_table[_BUS_POLICY_ACCESS_MAX] = { - [BUS_POLICY_ACCESS_SEE] = "see", - [BUS_POLICY_ACCESS_TALK] = "talk", - [BUS_POLICY_ACCESS_OWN] = "own", -}; - -DEFINE_STRING_TABLE_LOOKUP(bus_policy_access, BusPolicyAccess); diff --git a/src/core/bus-policy.h b/src/core/bus-policy.h deleted file mode 100644 index 5b2c4d5953..0000000000 --- a/src/core/bus-policy.h +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Daniel Mack - - 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 "kdbus.h" -#include "list.h" -#include "macro.h" - -typedef struct BusNamePolicy BusNamePolicy; - -typedef enum BusPolicyAccess { - BUS_POLICY_ACCESS_SEE, - BUS_POLICY_ACCESS_TALK, - BUS_POLICY_ACCESS_OWN, - _BUS_POLICY_ACCESS_MAX, - _BUS_POLICY_ACCESS_INVALID = -1 -} BusPolicyAccess; - -typedef enum BusNamePolicyType { - BUSNAME_POLICY_TYPE_USER, - BUSNAME_POLICY_TYPE_GROUP, - _BUSNAME_POLICY_TYPE_MAX, - _BUSNAME_POLICY_TYPE_INVALID = -1 -} BusNamePolicyType; - -struct BusNamePolicy { - BusNamePolicyType type; - BusPolicyAccess access; - - char *name; - - LIST_FIELDS(BusNamePolicy, policy); -}; - -int bus_kernel_translate_access(BusPolicyAccess access); -int bus_kernel_translate_policy(const BusNamePolicy *policy, struct kdbus_item *item); - -const char* bus_policy_access_to_string(BusPolicyAccess i) _const_; -BusPolicyAccess bus_policy_access_from_string(const char *s) _pure_; - -int bus_kernel_make_starter( - int fd, - const char *name, - bool activating, - bool accept_fd, - BusNamePolicy *policy, - BusPolicyAccess world_policy); diff --git a/src/core/busname.c b/src/core/busname.c deleted file mode 100644 index f03a95c24e..0000000000 --- a/src/core/busname.c +++ /dev/null @@ -1,1082 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "alloc-util.h" -#include "bus-internal.h" -#include "bus-kernel.h" -#include "bus-policy.h" -#include "bus-util.h" -#include "busname.h" -#include "dbus-busname.h" -#include "fd-util.h" -#include "formats-util.h" -#include "kdbus.h" -#include "parse-util.h" -#include "process-util.h" -#include "service.h" -#include "signal-util.h" -#include "special.h" -#include "string-table.h" -#include "string-util.h" - -static const UnitActiveState state_translation_table[_BUSNAME_STATE_MAX] = { - [BUSNAME_DEAD] = UNIT_INACTIVE, - [BUSNAME_MAKING] = UNIT_ACTIVATING, - [BUSNAME_REGISTERED] = UNIT_ACTIVE, - [BUSNAME_LISTENING] = UNIT_ACTIVE, - [BUSNAME_RUNNING] = UNIT_ACTIVE, - [BUSNAME_SIGTERM] = UNIT_DEACTIVATING, - [BUSNAME_SIGKILL] = UNIT_DEACTIVATING, - [BUSNAME_FAILED] = UNIT_FAILED -}; - -static int busname_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); -static int busname_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); - -static void busname_init(Unit *u) { - BusName *n = BUSNAME(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - n->starter_fd = -1; - n->accept_fd = true; - n->activating = true; - - n->timeout_usec = u->manager->default_timeout_start_usec; -} - -static void busname_unwatch_control_pid(BusName *n) { - assert(n); - - if (n->control_pid <= 0) - return; - - unit_unwatch_pid(UNIT(n), n->control_pid); - n->control_pid = 0; -} - -static void busname_free_policy(BusName *n) { - BusNamePolicy *p; - - assert(n); - - while ((p = n->policy)) { - LIST_REMOVE(policy, n->policy, p); - - free(p->name); - free(p); - } -} - -static void busname_close_fd(BusName *n) { - assert(n); - - n->starter_event_source = sd_event_source_unref(n->starter_event_source); - n->starter_fd = safe_close(n->starter_fd); -} - -static void busname_done(Unit *u) { - BusName *n = BUSNAME(u); - - assert(n); - - n->name = mfree(n->name); - - busname_free_policy(n); - busname_unwatch_control_pid(n); - busname_close_fd(n); - - unit_ref_unset(&n->service); - - n->timer_event_source = sd_event_source_unref(n->timer_event_source); -} - -static int busname_arm_timer(BusName *n, usec_t usec) { - int r; - - assert(n); - - if (n->timer_event_source) { - r = sd_event_source_set_time(n->timer_event_source, usec); - if (r < 0) - return r; - - return sd_event_source_set_enabled(n->timer_event_source, SD_EVENT_ONESHOT); - } - - if (usec == USEC_INFINITY) - return 0; - - r = sd_event_add_time( - UNIT(n)->manager->event, - &n->timer_event_source, - CLOCK_MONOTONIC, - usec, 0, - busname_dispatch_timer, n); - if (r < 0) - return r; - - (void) sd_event_source_set_description(n->timer_event_source, "busname-timer"); - - return 0; -} - -static int busname_add_default_default_dependencies(BusName *n) { - int r; - - assert(n); - - r = unit_add_dependency_by_name(UNIT(n), UNIT_BEFORE, SPECIAL_BUSNAMES_TARGET, NULL, true); - if (r < 0) - return r; - - if (MANAGER_IS_SYSTEM(UNIT(n)->manager)) { - r = unit_add_two_dependencies_by_name(UNIT(n), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); - if (r < 0) - return r; - } - - return unit_add_two_dependencies_by_name(UNIT(n), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); -} - -static int busname_add_extras(BusName *n) { - Unit *u = UNIT(n); - int r; - - assert(n); - - if (!n->name) { - r = unit_name_to_prefix(u->id, &n->name); - if (r < 0) - return r; - } - - if (!u->description) { - r = unit_set_description(u, n->name); - if (r < 0) - return r; - } - - if (n->activating) { - if (!UNIT_DEREF(n->service)) { - Unit *x; - - r = unit_load_related_unit(u, ".service", &x); - if (r < 0) - return r; - - unit_ref_set(&n->service, x); - } - - r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(n->service), true); - if (r < 0) - return r; - } - - if (u->default_dependencies) { - r = busname_add_default_default_dependencies(n); - if (r < 0) - return r; - } - - return 0; -} - -static int busname_verify(BusName *n) { - char *e; - - assert(n); - - if (UNIT(n)->load_state != UNIT_LOADED) - return 0; - - if (!service_name_is_valid(n->name)) { - log_unit_error(UNIT(n), "Name= setting is not a valid service name Refusing."); - return -EINVAL; - } - - e = strjoina(n->name, ".busname"); - if (!unit_has_name(UNIT(n), e)) { - log_unit_error(UNIT(n), "Name= setting doesn't match unit name. Refusing."); - return -EINVAL; - } - - return 0; -} - -static int busname_load(Unit *u) { - BusName *n = BUSNAME(u); - int r; - - assert(u); - assert(u->load_state == UNIT_STUB); - - r = unit_load_fragment_and_dropin(u); - if (r < 0) - return r; - - if (u->load_state == UNIT_LOADED) { - /* This is a new unit? Then let's add in some extras */ - r = busname_add_extras(n); - if (r < 0) - return r; - } - - return busname_verify(n); -} - -static void busname_dump(Unit *u, FILE *f, const char *prefix) { - BusName *n = BUSNAME(u); - - assert(n); - assert(f); - - fprintf(f, - "%sBus Name State: %s\n" - "%sResult: %s\n" - "%sName: %s\n" - "%sActivating: %s\n" - "%sAccept FD: %s\n", - prefix, busname_state_to_string(n->state), - prefix, busname_result_to_string(n->result), - prefix, n->name, - prefix, yes_no(n->activating), - prefix, yes_no(n->accept_fd)); - - if (n->control_pid > 0) - fprintf(f, - "%sControl PID: "PID_FMT"\n", - prefix, n->control_pid); -} - -static void busname_unwatch_fd(BusName *n) { - int r; - - assert(n); - - if (!n->starter_event_source) - return; - - r = sd_event_source_set_enabled(n->starter_event_source, SD_EVENT_OFF); - if (r < 0) - log_unit_debug_errno(UNIT(n), r, "Failed to disable event source: %m"); -} - -static int busname_watch_fd(BusName *n) { - int r; - - assert(n); - - if (n->starter_fd < 0) - return 0; - - if (n->starter_event_source) { - r = sd_event_source_set_enabled(n->starter_event_source, SD_EVENT_ON); - if (r < 0) - goto fail; - } else { - r = sd_event_add_io(UNIT(n)->manager->event, &n->starter_event_source, n->starter_fd, EPOLLIN, busname_dispatch_io, n); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(n->starter_event_source, "busname-starter"); - } - - return 0; - -fail: - log_unit_warning_errno(UNIT(n), r, "Failed to watch starter fd: %m"); - busname_unwatch_fd(n); - return r; -} - -static int busname_open_fd(BusName *n) { - _cleanup_free_ char *path = NULL; - const char *mode; - - assert(n); - - if (n->starter_fd >= 0) - return 0; - - mode = MANAGER_IS_SYSTEM(UNIT(n)->manager) ? "system" : "user"; - n->starter_fd = bus_kernel_open_bus_fd(mode, &path); - if (n->starter_fd < 0) - return log_unit_warning_errno(UNIT(n), n->starter_fd, "Failed to open %s: %m", path ?: "kdbus"); - - return 0; -} - -static void busname_set_state(BusName *n, BusNameState state) { - BusNameState old_state; - assert(n); - - old_state = n->state; - n->state = state; - - if (!IN_SET(state, BUSNAME_MAKING, BUSNAME_SIGTERM, BUSNAME_SIGKILL)) { - n->timer_event_source = sd_event_source_unref(n->timer_event_source); - busname_unwatch_control_pid(n); - } - - if (state != BUSNAME_LISTENING) - busname_unwatch_fd(n); - - if (!IN_SET(state, BUSNAME_LISTENING, BUSNAME_MAKING, BUSNAME_REGISTERED, BUSNAME_RUNNING)) - busname_close_fd(n); - - if (state != old_state) - log_unit_debug(UNIT(n), "Changed %s -> %s", busname_state_to_string(old_state), busname_state_to_string(state)); - - unit_notify(UNIT(n), state_translation_table[old_state], state_translation_table[state], true); -} - -static int busname_coldplug(Unit *u) { - BusName *n = BUSNAME(u); - int r; - - assert(n); - assert(n->state == BUSNAME_DEAD); - - if (n->deserialized_state == n->state) - return 0; - - if (n->control_pid > 0 && - pid_is_unwaited(n->control_pid) && - IN_SET(n->deserialized_state, BUSNAME_MAKING, BUSNAME_SIGTERM, BUSNAME_SIGKILL)) { - - r = unit_watch_pid(UNIT(n), n->control_pid); - if (r < 0) - return r; - - r = busname_arm_timer(n, usec_add(u->state_change_timestamp.monotonic, n->timeout_usec)); - if (r < 0) - return r; - } - - if (IN_SET(n->deserialized_state, BUSNAME_MAKING, BUSNAME_LISTENING, BUSNAME_REGISTERED, BUSNAME_RUNNING)) { - r = busname_open_fd(n); - if (r < 0) - return r; - } - - if (n->deserialized_state == BUSNAME_LISTENING) { - r = busname_watch_fd(n); - if (r < 0) - return r; - } - - busname_set_state(n, n->deserialized_state); - return 0; -} - -static int busname_make_starter(BusName *n, pid_t *_pid) { - pid_t pid; - int r; - - r = busname_arm_timer(n, usec_add(now(CLOCK_MONOTONIC), n->timeout_usec)); - if (r < 0) - goto fail; - - /* We have to resolve the user/group names out-of-process, - * hence let's fork here. It's messy, but well, what can we - * do? */ - - pid = fork(); - if (pid < 0) - return -errno; - - if (pid == 0) { - int ret; - - (void) default_signals(SIGNALS_CRASH_HANDLER, SIGNALS_IGNORE, -1); - (void) ignore_signals(SIGPIPE, -1); - log_forget_fds(); - - r = bus_kernel_make_starter(n->starter_fd, n->name, n->activating, n->accept_fd, n->policy, n->policy_world); - if (r < 0) { - ret = EXIT_MAKE_STARTER; - goto fail_child; - } - - _exit(0); - - fail_child: - log_open(); - log_error_errno(r, "Failed to create starter connection at step %s: %m", exit_status_to_string(ret, EXIT_STATUS_SYSTEMD)); - - _exit(ret); - } - - r = unit_watch_pid(UNIT(n), pid); - if (r < 0) - goto fail; - - *_pid = pid; - return 0; - -fail: - n->timer_event_source = sd_event_source_unref(n->timer_event_source); - return r; -} - -static void busname_enter_dead(BusName *n, BusNameResult f) { - assert(n); - - if (f != BUSNAME_SUCCESS) - n->result = f; - - busname_set_state(n, n->result != BUSNAME_SUCCESS ? BUSNAME_FAILED : BUSNAME_DEAD); -} - -static void busname_enter_signal(BusName *n, BusNameState state, BusNameResult f) { - KillContext kill_context = {}; - int r; - - assert(n); - - if (f != BUSNAME_SUCCESS) - n->result = f; - - kill_context_init(&kill_context); - - r = unit_kill_context(UNIT(n), - &kill_context, - state != BUSNAME_SIGTERM ? KILL_KILL : KILL_TERMINATE, - -1, - n->control_pid, - false); - if (r < 0) { - log_unit_warning_errno(UNIT(n), r, "Failed to kill control process: %m"); - goto fail; - } - - if (r > 0) { - r = busname_arm_timer(n, usec_add(now(CLOCK_MONOTONIC), n->timeout_usec)); - if (r < 0) { - log_unit_warning_errno(UNIT(n), r, "Failed to arm timer: %m"); - goto fail; - } - - busname_set_state(n, state); - } else if (state == BUSNAME_SIGTERM) - busname_enter_signal(n, BUSNAME_SIGKILL, BUSNAME_SUCCESS); - else - busname_enter_dead(n, BUSNAME_SUCCESS); - - return; - -fail: - busname_enter_dead(n, BUSNAME_FAILURE_RESOURCES); -} - -static void busname_enter_listening(BusName *n) { - int r; - - assert(n); - - if (n->activating) { - r = busname_watch_fd(n); - if (r < 0) { - log_unit_warning_errno(UNIT(n), r, "Failed to watch names: %m"); - goto fail; - } - - busname_set_state(n, BUSNAME_LISTENING); - } else - busname_set_state(n, BUSNAME_REGISTERED); - - return; - -fail: - busname_enter_signal(n, BUSNAME_SIGTERM, BUSNAME_FAILURE_RESOURCES); -} - -static void busname_enter_making(BusName *n) { - int r; - - assert(n); - - r = busname_open_fd(n); - if (r < 0) - goto fail; - - if (n->policy) { - /* If there is a policy, we need to resolve user/group - * names, which we can't do from PID1, hence let's - * fork. */ - busname_unwatch_control_pid(n); - - r = busname_make_starter(n, &n->control_pid); - if (r < 0) { - log_unit_warning_errno(UNIT(n), r, "Failed to fork 'making' task: %m"); - goto fail; - } - - busname_set_state(n, BUSNAME_MAKING); - } else { - /* If there is no policy, we can do everything - * directly from PID 1, hence do so. */ - - r = bus_kernel_make_starter(n->starter_fd, n->name, n->activating, n->accept_fd, NULL, n->policy_world); - if (r < 0) { - log_unit_warning_errno(UNIT(n), r, "Failed to make starter: %m"); - goto fail; - } - - busname_enter_listening(n); - } - - return; - -fail: - busname_enter_dead(n, BUSNAME_FAILURE_RESOURCES); -} - -static void busname_enter_running(BusName *n) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - bool pending = false; - Unit *other; - Iterator i; - int r; - - assert(n); - - if (!n->activating) - return; - - /* We don't take connections anymore if we are supposed to - * shut down anyway */ - - if (unit_stop_pending(UNIT(n))) { - log_unit_debug(UNIT(n), "Suppressing activation request since unit stop is scheduled."); - - /* Flush all queued activation reqeuest by closing and reopening the connection */ - bus_kernel_drop_one(n->starter_fd); - - busname_enter_listening(n); - return; - } - - /* If there's already a start pending don't bother to do - * anything */ - SET_FOREACH(other, UNIT(n)->dependencies[UNIT_TRIGGERS], i) - if (unit_active_or_pending(other)) { - pending = true; - break; - } - - if (!pending) { - if (!UNIT_ISSET(n->service)) { - log_unit_error(UNIT(n), "Service to activate vanished, refusing activation."); - r = -ENOENT; - goto fail; - } - - r = manager_add_job(UNIT(n)->manager, JOB_START, UNIT_DEREF(n->service), JOB_REPLACE, &error, NULL); - if (r < 0) - goto fail; - } - - busname_set_state(n, BUSNAME_RUNNING); - return; - -fail: - log_unit_warning(UNIT(n), "Failed to queue service startup job: %s", bus_error_message(&error, r)); - busname_enter_dead(n, BUSNAME_FAILURE_RESOURCES); -} - -static int busname_start(Unit *u) { - BusName *n = BUSNAME(u); - int r; - - assert(n); - - /* We cannot fulfill this request right now, try again later - * please! */ - if (IN_SET(n->state, BUSNAME_SIGTERM, BUSNAME_SIGKILL)) - return -EAGAIN; - - /* Already on it! */ - if (n->state == BUSNAME_MAKING) - return 0; - - if (n->activating && UNIT_ISSET(n->service)) { - Service *service; - - service = SERVICE(UNIT_DEREF(n->service)); - - if (UNIT(service)->load_state != UNIT_LOADED) { - log_unit_error(u, "Bus service %s not loaded, refusing.", UNIT(service)->id); - return -ENOENT; - } - } - - assert(IN_SET(n->state, BUSNAME_DEAD, BUSNAME_FAILED)); - - r = unit_start_limit_test(u); - if (r < 0) { - busname_enter_dead(n, BUSNAME_FAILURE_START_LIMIT_HIT); - return r; - } - - n->result = BUSNAME_SUCCESS; - busname_enter_making(n); - - return 1; -} - -static int busname_stop(Unit *u) { - BusName *n = BUSNAME(u); - - assert(n); - - /* Already on it */ - if (IN_SET(n->state, BUSNAME_SIGTERM, BUSNAME_SIGKILL)) - return 0; - - /* If there's already something running, we go directly into - * kill mode. */ - - if (n->state == BUSNAME_MAKING) { - busname_enter_signal(n, BUSNAME_SIGTERM, BUSNAME_SUCCESS); - return -EAGAIN; - } - - assert(IN_SET(n->state, BUSNAME_REGISTERED, BUSNAME_LISTENING, BUSNAME_RUNNING)); - - busname_enter_dead(n, BUSNAME_SUCCESS); - return 1; -} - -static int busname_serialize(Unit *u, FILE *f, FDSet *fds) { - BusName *n = BUSNAME(u); - int r; - - assert(n); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", busname_state_to_string(n->state)); - unit_serialize_item(u, f, "result", busname_result_to_string(n->result)); - - if (n->control_pid > 0) - unit_serialize_item_format(u, f, "control-pid", PID_FMT, n->control_pid); - - r = unit_serialize_item_fd(u, f, fds, "starter-fd", n->starter_fd); - if (r < 0) - return r; - - return 0; -} - -static int busname_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - BusName *n = BUSNAME(u); - - assert(n); - assert(key); - assert(value); - - if (streq(key, "state")) { - BusNameState state; - - state = busname_state_from_string(value); - if (state < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - n->deserialized_state = state; - - } else if (streq(key, "result")) { - BusNameResult f; - - f = busname_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse result value: %s", value); - else if (f != BUSNAME_SUCCESS) - n->result = f; - - } else if (streq(key, "control-pid")) { - pid_t pid; - - if (parse_pid(value, &pid) < 0) - log_unit_debug(u, "Failed to parse control-pid value: %s", value); - else - n->control_pid = pid; - } else if (streq(key, "starter-fd")) { - int fd; - - if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse starter fd value: %s", value); - else { - safe_close(n->starter_fd); - n->starter_fd = fdset_remove(fds, fd); - } - } else - log_unit_debug(u, "Unknown serialization key: %s", key); - - return 0; -} - -_pure_ static UnitActiveState busname_active_state(Unit *u) { - assert(u); - - return state_translation_table[BUSNAME(u)->state]; -} - -_pure_ static const char *busname_sub_state_to_string(Unit *u) { - assert(u); - - return busname_state_to_string(BUSNAME(u)->state); -} - -static int busname_peek_message(BusName *n) { - struct kdbus_cmd_recv cmd_recv = { - .size = sizeof(cmd_recv), - .flags = KDBUS_RECV_PEEK, - }; - struct kdbus_cmd_free cmd_free = { - .size = sizeof(cmd_free), - }; - const char *comm = NULL; - struct kdbus_item *d; - struct kdbus_msg *k; - size_t start, ps, sz, delta; - void *p = NULL; - pid_t pid = 0; - int r; - - /* Generate a friendly debug log message about which process - * caused triggering of this bus name. This simply peeks the - * metadata of the first queued message and logs it. */ - - assert(n); - - /* Let's shortcut things a bit, if debug logging is turned off - * anyway. */ - - if (log_get_max_level() < LOG_DEBUG) - return 0; - - r = ioctl(n->starter_fd, KDBUS_CMD_RECV, &cmd_recv); - if (r < 0) { - if (errno == EINTR || errno == EAGAIN) - return 0; - - return log_unit_error_errno(UNIT(n), errno, "Failed to query activation message: %m"); - } - - /* We map as late as possible, and unmap imemdiately after - * use. On 32bit address space is scarce and we want to be - * able to handle a lot of activator connections at the same - * time, and hence shouldn't keep the mmap()s around for - * longer than necessary. */ - - ps = page_size(); - start = (cmd_recv.msg.offset / ps) * ps; - delta = cmd_recv.msg.offset - start; - sz = PAGE_ALIGN(delta + cmd_recv.msg.msg_size); - - p = mmap(NULL, sz, PROT_READ, MAP_SHARED, n->starter_fd, start); - if (p == MAP_FAILED) { - r = log_unit_error_errno(UNIT(n), errno, "Failed to map activation message: %m"); - goto finish; - } - - k = (struct kdbus_msg *) ((uint8_t *) p + delta); - KDBUS_ITEM_FOREACH(d, k, items) { - switch (d->type) { - - case KDBUS_ITEM_PIDS: - pid = d->pids.pid; - break; - - case KDBUS_ITEM_PID_COMM: - comm = d->str; - break; - } - } - - if (pid > 0) - log_unit_debug(UNIT(n), "Activation triggered by process " PID_FMT " (%s)", pid, strna(comm)); - - r = 0; - -finish: - if (p) - (void) munmap(p, sz); - - cmd_free.offset = cmd_recv.msg.offset; - if (ioctl(n->starter_fd, KDBUS_CMD_FREE, &cmd_free) < 0) - log_unit_warning(UNIT(n), "Failed to free peeked message, ignoring: %m"); - - return r; -} - -static int busname_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - BusName *n = userdata; - - assert(n); - assert(fd >= 0); - - if (n->state != BUSNAME_LISTENING) - return 0; - - log_unit_debug(UNIT(n), "Activation request"); - - if (revents != EPOLLIN) { - log_unit_error(UNIT(n), "Got unexpected poll event (0x%x) on starter fd.", revents); - goto fail; - } - - busname_peek_message(n); - busname_enter_running(n); - return 0; -fail: - - busname_enter_dead(n, BUSNAME_FAILURE_RESOURCES); - return 0; -} - -static void busname_sigchld_event(Unit *u, pid_t pid, int code, int status) { - BusName *n = BUSNAME(u); - BusNameResult f; - - assert(n); - assert(pid >= 0); - - if (pid != n->control_pid) - return; - - n->control_pid = 0; - - if (is_clean_exit(code, status, NULL)) - f = BUSNAME_SUCCESS; - else if (code == CLD_EXITED) - f = BUSNAME_FAILURE_EXIT_CODE; - else if (code == CLD_KILLED) - f = BUSNAME_FAILURE_SIGNAL; - else if (code == CLD_DUMPED) - f = BUSNAME_FAILURE_CORE_DUMP; - else - assert_not_reached("Unknown sigchld code"); - - log_unit_full(u, f == BUSNAME_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, - "Control process exited, code=%s status=%i", sigchld_code_to_string(code), status); - - if (f != BUSNAME_SUCCESS) - n->result = f; - - switch (n->state) { - - case BUSNAME_MAKING: - if (f == BUSNAME_SUCCESS) - busname_enter_listening(n); - else - busname_enter_signal(n, BUSNAME_SIGTERM, f); - break; - - case BUSNAME_SIGTERM: - case BUSNAME_SIGKILL: - busname_enter_dead(n, f); - break; - - default: - assert_not_reached("Uh, control process died at wrong time."); - } - - /* Notify clients about changed exit status */ - unit_add_to_dbus_queue(u); -} - -static int busname_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { - BusName *n = BUSNAME(userdata); - - assert(n); - assert(n->timer_event_source == source); - - switch (n->state) { - - case BUSNAME_MAKING: - log_unit_warning(UNIT(n), "Making timed out. Terminating."); - busname_enter_signal(n, BUSNAME_SIGTERM, BUSNAME_FAILURE_TIMEOUT); - break; - - case BUSNAME_SIGTERM: - log_unit_warning(UNIT(n), "Stopping timed out. Killing."); - busname_enter_signal(n, BUSNAME_SIGKILL, BUSNAME_FAILURE_TIMEOUT); - break; - - case BUSNAME_SIGKILL: - log_unit_warning(UNIT(n), "Processes still around after SIGKILL. Ignoring."); - busname_enter_dead(n, BUSNAME_FAILURE_TIMEOUT); - break; - - default: - assert_not_reached("Timeout at wrong time."); - } - - return 0; -} - -static void busname_reset_failed(Unit *u) { - BusName *n = BUSNAME(u); - - assert(n); - - if (n->state == BUSNAME_FAILED) - busname_set_state(n, BUSNAME_DEAD); - - n->result = BUSNAME_SUCCESS; -} - -static void busname_trigger_notify(Unit *u, Unit *other) { - BusName *n = BUSNAME(u); - - assert(n); - assert(other); - - if (!IN_SET(n->state, BUSNAME_RUNNING, BUSNAME_LISTENING)) - return; - - if (other->start_limit_hit) { - busname_enter_dead(n, BUSNAME_FAILURE_SERVICE_START_LIMIT_HIT); - return; - } - - if (other->load_state != UNIT_LOADED || other->type != UNIT_SERVICE) - return; - - if (IN_SET(SERVICE(other)->state, - SERVICE_DEAD, SERVICE_FAILED, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, - SERVICE_AUTO_RESTART)) - busname_enter_listening(n); - - if (SERVICE(other)->state == SERVICE_RUNNING) - busname_set_state(n, BUSNAME_RUNNING); -} - -static int busname_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { - return unit_kill_common(u, who, signo, -1, BUSNAME(u)->control_pid, error); -} - -static int busname_get_timeout(Unit *u, usec_t *timeout) { - BusName *n = BUSNAME(u); - usec_t t; - int r; - - if (!n->timer_event_source) - return 0; - - r = sd_event_source_get_time(n->timer_event_source, &t); - if (r < 0) - return r; - if (t == USEC_INFINITY) - return 0; - - *timeout = t; - return 1; -} - -static bool busname_supported(void) { - static int supported = -1; - - if (supported < 0) - supported = is_kdbus_available(); - - return supported; -} - -static int busname_control_pid(Unit *u) { - BusName *n = BUSNAME(u); - - assert(n); - - return n->control_pid; -} - -static const char* const busname_result_table[_BUSNAME_RESULT_MAX] = { - [BUSNAME_SUCCESS] = "success", - [BUSNAME_FAILURE_RESOURCES] = "resources", - [BUSNAME_FAILURE_TIMEOUT] = "timeout", - [BUSNAME_FAILURE_EXIT_CODE] = "exit-code", - [BUSNAME_FAILURE_SIGNAL] = "signal", - [BUSNAME_FAILURE_CORE_DUMP] = "core-dump", - [BUSNAME_FAILURE_START_LIMIT_HIT] = "start-limit-hit", - [BUSNAME_FAILURE_SERVICE_START_LIMIT_HIT] = "service-start-limit-hit", -}; - -DEFINE_STRING_TABLE_LOOKUP(busname_result, BusNameResult); - -const UnitVTable busname_vtable = { - .object_size = sizeof(BusName), - - .sections = - "Unit\0" - "BusName\0" - "Install\0", - .private_section = "BusName", - - .init = busname_init, - .done = busname_done, - .load = busname_load, - - .coldplug = busname_coldplug, - - .dump = busname_dump, - - .start = busname_start, - .stop = busname_stop, - - .kill = busname_kill, - - .get_timeout = busname_get_timeout, - - .serialize = busname_serialize, - .deserialize_item = busname_deserialize_item, - - .active_state = busname_active_state, - .sub_state_to_string = busname_sub_state_to_string, - - .sigchld_event = busname_sigchld_event, - - .trigger_notify = busname_trigger_notify, - - .reset_failed = busname_reset_failed, - - .supported = busname_supported, - - .control_pid = busname_control_pid, - - .bus_vtable = bus_busname_vtable, - - .status_message_formats = { - .finished_start_job = { - [JOB_DONE] = "Listening on %s.", - [JOB_FAILED] = "Failed to listen on %s.", - }, - .finished_stop_job = { - [JOB_DONE] = "Closed %s.", - [JOB_FAILED] = "Failed stopping %s.", - }, - }, -}; diff --git a/src/core/busname.h b/src/core/busname.h deleted file mode 100644 index a8562db458..0000000000 --- a/src/core/busname.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -typedef struct BusName BusName; -typedef struct BusNamePolicy BusNamePolicy; - -#include "unit.h" -#include "bus-policy.h" - -typedef enum BusNameResult { - BUSNAME_SUCCESS, - BUSNAME_FAILURE_RESOURCES, - BUSNAME_FAILURE_TIMEOUT, - BUSNAME_FAILURE_EXIT_CODE, - BUSNAME_FAILURE_SIGNAL, - BUSNAME_FAILURE_CORE_DUMP, - BUSNAME_FAILURE_START_LIMIT_HIT, - BUSNAME_FAILURE_SERVICE_START_LIMIT_HIT, - _BUSNAME_RESULT_MAX, - _BUSNAME_RESULT_INVALID = -1 -} BusNameResult; - -struct BusName { - Unit meta; - - char *name; - int starter_fd; - - bool activating; - bool accept_fd; - - UnitRef service; - - BusNameState state, deserialized_state; - BusNameResult result; - - usec_t timeout_usec; - - sd_event_source *starter_event_source; - sd_event_source *timer_event_source; - - pid_t control_pid; - - LIST_HEAD(BusNamePolicy, policy); - BusPolicyAccess policy_world; -}; - -extern const UnitVTable busname_vtable; - -const char* busname_result_to_string(BusNameResult i) _const_; -BusNameResult busname_result_from_string(const char *s) _pure_; diff --git a/src/core/cgroup.c b/src/core/cgroup.c deleted file mode 100644 index 0fb63b1bd1..0000000000 --- a/src/core/cgroup.c +++ /dev/null @@ -1,1895 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 - -#include "alloc-util.h" -#include "cgroup-util.h" -#include "cgroup.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "special.h" -#include "string-table.h" -#include "string-util.h" -#include "stdio-util.h" - -#define CGROUP_CPU_QUOTA_PERIOD_USEC ((usec_t) 100 * USEC_PER_MSEC) - -void cgroup_context_init(CGroupContext *c) { - assert(c); - - /* Initialize everything to the kernel defaults, assuming the - * structure is preinitialized to 0 */ - - c->cpu_shares = CGROUP_CPU_SHARES_INVALID; - c->startup_cpu_shares = CGROUP_CPU_SHARES_INVALID; - c->cpu_quota_per_sec_usec = USEC_INFINITY; - - c->memory_limit = (uint64_t) -1; - - c->io_weight = CGROUP_WEIGHT_INVALID; - c->startup_io_weight = CGROUP_WEIGHT_INVALID; - - c->blockio_weight = CGROUP_BLKIO_WEIGHT_INVALID; - c->startup_blockio_weight = CGROUP_BLKIO_WEIGHT_INVALID; - - c->tasks_max = (uint64_t) -1; -} - -void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a) { - assert(c); - assert(a); - - LIST_REMOVE(device_allow, c->device_allow, a); - free(a->path); - free(a); -} - -void cgroup_context_free_io_device_weight(CGroupContext *c, CGroupIODeviceWeight *w) { - assert(c); - assert(w); - - LIST_REMOVE(device_weights, c->io_device_weights, w); - free(w->path); - free(w); -} - -void cgroup_context_free_io_device_limit(CGroupContext *c, CGroupIODeviceLimit *l) { - assert(c); - assert(l); - - LIST_REMOVE(device_limits, c->io_device_limits, l); - free(l->path); - free(l); -} - -void cgroup_context_free_blockio_device_weight(CGroupContext *c, CGroupBlockIODeviceWeight *w) { - assert(c); - assert(w); - - LIST_REMOVE(device_weights, c->blockio_device_weights, w); - free(w->path); - free(w); -} - -void cgroup_context_free_blockio_device_bandwidth(CGroupContext *c, CGroupBlockIODeviceBandwidth *b) { - assert(c); - assert(b); - - LIST_REMOVE(device_bandwidths, c->blockio_device_bandwidths, b); - free(b->path); - free(b); -} - -void cgroup_context_done(CGroupContext *c) { - assert(c); - - while (c->io_device_weights) - cgroup_context_free_io_device_weight(c, c->io_device_weights); - - while (c->io_device_limits) - cgroup_context_free_io_device_limit(c, c->io_device_limits); - - while (c->blockio_device_weights) - cgroup_context_free_blockio_device_weight(c, c->blockio_device_weights); - - while (c->blockio_device_bandwidths) - cgroup_context_free_blockio_device_bandwidth(c, c->blockio_device_bandwidths); - - while (c->device_allow) - cgroup_context_free_device_allow(c, c->device_allow); -} - -void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { - CGroupIODeviceLimit *il; - CGroupIODeviceWeight *iw; - CGroupBlockIODeviceBandwidth *b; - CGroupBlockIODeviceWeight *w; - CGroupDeviceAllow *a; - char u[FORMAT_TIMESPAN_MAX]; - - assert(c); - assert(f); - - prefix = strempty(prefix); - - fprintf(f, - "%sCPUAccounting=%s\n" - "%sIOAccounting=%s\n" - "%sBlockIOAccounting=%s\n" - "%sMemoryAccounting=%s\n" - "%sTasksAccounting=%s\n" - "%sCPUShares=%" PRIu64 "\n" - "%sStartupCPUShares=%" PRIu64 "\n" - "%sCPUQuotaPerSecSec=%s\n" - "%sIOWeight=%" PRIu64 "\n" - "%sStartupIOWeight=%" PRIu64 "\n" - "%sBlockIOWeight=%" PRIu64 "\n" - "%sStartupBlockIOWeight=%" PRIu64 "\n" - "%sMemoryLimit=%" PRIu64 "\n" - "%sTasksMax=%" PRIu64 "\n" - "%sDevicePolicy=%s\n" - "%sDelegate=%s\n", - prefix, yes_no(c->cpu_accounting), - prefix, yes_no(c->io_accounting), - prefix, yes_no(c->blockio_accounting), - prefix, yes_no(c->memory_accounting), - prefix, yes_no(c->tasks_accounting), - prefix, c->cpu_shares, - prefix, c->startup_cpu_shares, - prefix, format_timespan(u, sizeof(u), c->cpu_quota_per_sec_usec, 1), - prefix, c->io_weight, - prefix, c->startup_io_weight, - prefix, c->blockio_weight, - prefix, c->startup_blockio_weight, - prefix, c->memory_limit, - prefix, c->tasks_max, - prefix, cgroup_device_policy_to_string(c->device_policy), - prefix, yes_no(c->delegate)); - - LIST_FOREACH(device_allow, a, c->device_allow) - fprintf(f, - "%sDeviceAllow=%s %s%s%s\n", - prefix, - a->path, - a->r ? "r" : "", a->w ? "w" : "", a->m ? "m" : ""); - - LIST_FOREACH(device_weights, iw, c->io_device_weights) - fprintf(f, - "%sIODeviceWeight=%s %" PRIu64, - prefix, - iw->path, - iw->weight); - - LIST_FOREACH(device_limits, il, c->io_device_limits) { - char buf[FORMAT_BYTES_MAX]; - CGroupIOLimitType type; - - for (type = 0; type < _CGROUP_IO_LIMIT_TYPE_MAX; type++) - if (il->limits[type] != cgroup_io_limit_defaults[type]) - fprintf(f, - "%s%s=%s %s\n", - prefix, - cgroup_io_limit_type_to_string(type), - il->path, - format_bytes(buf, sizeof(buf), il->limits[type])); - } - - LIST_FOREACH(device_weights, w, c->blockio_device_weights) - fprintf(f, - "%sBlockIODeviceWeight=%s %" PRIu64, - prefix, - w->path, - w->weight); - - LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { - char buf[FORMAT_BYTES_MAX]; - - if (b->rbps != CGROUP_LIMIT_MAX) - fprintf(f, - "%sBlockIOReadBandwidth=%s %s\n", - prefix, - b->path, - format_bytes(buf, sizeof(buf), b->rbps)); - if (b->wbps != CGROUP_LIMIT_MAX) - fprintf(f, - "%sBlockIOWriteBandwidth=%s %s\n", - prefix, - b->path, - format_bytes(buf, sizeof(buf), b->wbps)); - } -} - -static int lookup_block_device(const char *p, dev_t *dev) { - struct stat st; - int r; - - assert(p); - assert(dev); - - r = stat(p, &st); - if (r < 0) - return log_warning_errno(errno, "Couldn't stat device %s: %m", p); - - if (S_ISBLK(st.st_mode)) - *dev = st.st_rdev; - else if (major(st.st_dev) != 0) { - /* If this is not a device node then find the block - * device this file is stored on */ - *dev = st.st_dev; - - /* If this is a partition, try to get the originating - * block device */ - block_get_whole_disk(*dev, dev); - } else { - log_warning("%s is not a block device and file system block device cannot be determined or is not local.", p); - return -ENODEV; - } - - return 0; -} - -static int whitelist_device(const char *path, const char *node, const char *acc) { - char buf[2+DECIMAL_STR_MAX(dev_t)*2+2+4]; - struct stat st; - int r; - - assert(path); - assert(acc); - - if (stat(node, &st) < 0) { - log_warning("Couldn't stat device %s", node); - return -errno; - } - - if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) { - log_warning("%s is not a device.", node); - return -ENODEV; - } - - sprintf(buf, - "%c %u:%u %s", - S_ISCHR(st.st_mode) ? 'c' : 'b', - major(st.st_rdev), minor(st.st_rdev), - acc); - - r = cg_set_attribute("devices", path, "devices.allow", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set devices.allow on %s: %m", path); - - return r; -} - -static int whitelist_major(const char *path, const char *name, char type, const char *acc) { - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - bool good = false; - int r; - - assert(path); - assert(acc); - assert(type == 'b' || type == 'c'); - - f = fopen("/proc/devices", "re"); - if (!f) - return log_warning_errno(errno, "Cannot open /proc/devices to resolve %s (%c): %m", name, type); - - FOREACH_LINE(line, f, goto fail) { - char buf[2+DECIMAL_STR_MAX(unsigned)+3+4], *p, *w; - unsigned maj; - - truncate_nl(line); - - if (type == 'c' && streq(line, "Character devices:")) { - good = true; - continue; - } - - if (type == 'b' && streq(line, "Block devices:")) { - good = true; - continue; - } - - if (isempty(line)) { - good = false; - continue; - } - - if (!good) - continue; - - p = strstrip(line); - - w = strpbrk(p, WHITESPACE); - if (!w) - continue; - *w = 0; - - r = safe_atou(p, &maj); - if (r < 0) - continue; - if (maj <= 0) - continue; - - w++; - w += strspn(w, WHITESPACE); - - if (fnmatch(name, w, 0) != 0) - continue; - - sprintf(buf, - "%c %u:* %s", - type, - maj, - acc); - - r = cg_set_attribute("devices", path, "devices.allow", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set devices.allow on %s: %m", path); - } - - return 0; - -fail: - log_warning_errno(errno, "Failed to read /proc/devices: %m"); - return -errno; -} - -static bool cgroup_context_has_io_config(CGroupContext *c) { - return c->io_accounting || - c->io_weight != CGROUP_WEIGHT_INVALID || - c->startup_io_weight != CGROUP_WEIGHT_INVALID || - c->io_device_weights || - c->io_device_limits; -} - -static bool cgroup_context_has_blockio_config(CGroupContext *c) { - return c->blockio_accounting || - c->blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID || - c->startup_blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID || - c->blockio_device_weights || - c->blockio_device_bandwidths; -} - -static uint64_t cgroup_context_io_weight(CGroupContext *c, ManagerState state) { - if (IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && - c->startup_io_weight != CGROUP_WEIGHT_INVALID) - return c->startup_io_weight; - else if (c->io_weight != CGROUP_WEIGHT_INVALID) - return c->io_weight; - else - return CGROUP_WEIGHT_DEFAULT; -} - -static uint64_t cgroup_context_blkio_weight(CGroupContext *c, ManagerState state) { - if (IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && - c->startup_blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID) - return c->startup_blockio_weight; - else if (c->blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID) - return c->blockio_weight; - else - return CGROUP_BLKIO_WEIGHT_DEFAULT; -} - -static uint64_t cgroup_weight_blkio_to_io(uint64_t blkio_weight) { - return CLAMP(blkio_weight * CGROUP_WEIGHT_DEFAULT / CGROUP_BLKIO_WEIGHT_DEFAULT, - CGROUP_WEIGHT_MIN, CGROUP_WEIGHT_MAX); -} - -static uint64_t cgroup_weight_io_to_blkio(uint64_t io_weight) { - return CLAMP(io_weight * CGROUP_BLKIO_WEIGHT_DEFAULT / CGROUP_WEIGHT_DEFAULT, - CGROUP_BLKIO_WEIGHT_MIN, CGROUP_BLKIO_WEIGHT_MAX); -} - -static void cgroup_apply_io_device_weight(const char *path, const char *dev_path, uint64_t io_weight) { - char buf[DECIMAL_STR_MAX(dev_t)*2+2+DECIMAL_STR_MAX(uint64_t)+1]; - dev_t dev; - int r; - - r = lookup_block_device(dev_path, &dev); - if (r < 0) - return; - - xsprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), io_weight); - r = cg_set_attribute("io", path, "io.weight", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set io.weight on %s: %m", path); -} - -static void cgroup_apply_blkio_device_weight(const char *path, const char *dev_path, uint64_t blkio_weight) { - char buf[DECIMAL_STR_MAX(dev_t)*2+2+DECIMAL_STR_MAX(uint64_t)+1]; - dev_t dev; - int r; - - r = lookup_block_device(dev_path, &dev); - if (r < 0) - return; - - xsprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), blkio_weight); - r = cg_set_attribute("blkio", path, "blkio.weight_device", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set blkio.weight_device on %s: %m", path); -} - -static unsigned cgroup_apply_io_device_limit(const char *path, const char *dev_path, uint64_t *limits) { - char limit_bufs[_CGROUP_IO_LIMIT_TYPE_MAX][DECIMAL_STR_MAX(uint64_t)]; - char buf[DECIMAL_STR_MAX(dev_t)*2+2+(6+DECIMAL_STR_MAX(uint64_t)+1)*4]; - CGroupIOLimitType type; - dev_t dev; - unsigned n = 0; - int r; - - r = lookup_block_device(dev_path, &dev); - if (r < 0) - return 0; - - for (type = 0; type < _CGROUP_IO_LIMIT_TYPE_MAX; type++) { - if (limits[type] != cgroup_io_limit_defaults[type]) { - xsprintf(limit_bufs[type], "%" PRIu64, limits[type]); - n++; - } else { - xsprintf(limit_bufs[type], "%s", limits[type] == CGROUP_LIMIT_MAX ? "max" : "0"); - } - } - - xsprintf(buf, "%u:%u rbps=%s wbps=%s riops=%s wiops=%s\n", major(dev), minor(dev), - limit_bufs[CGROUP_IO_RBPS_MAX], limit_bufs[CGROUP_IO_WBPS_MAX], - limit_bufs[CGROUP_IO_RIOPS_MAX], limit_bufs[CGROUP_IO_WIOPS_MAX]); - r = cg_set_attribute("io", path, "io.max", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set io.max on %s: %m", path); - return n; -} - -static unsigned cgroup_apply_blkio_device_limit(const char *path, const char *dev_path, uint64_t rbps, uint64_t wbps) { - char buf[DECIMAL_STR_MAX(dev_t)*2+2+DECIMAL_STR_MAX(uint64_t)+1]; - dev_t dev; - unsigned n = 0; - int r; - - r = lookup_block_device(dev_path, &dev); - if (r < 0) - return 0; - - if (rbps != CGROUP_LIMIT_MAX) - n++; - sprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), rbps); - r = cg_set_attribute("blkio", path, "blkio.throttle.read_bps_device", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set blkio.throttle.read_bps_device on %s: %m", path); - - if (wbps != CGROUP_LIMIT_MAX) - n++; - sprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), wbps); - r = cg_set_attribute("blkio", path, "blkio.throttle.write_bps_device", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set blkio.throttle.write_bps_device on %s: %m", path); - - return n; -} - -void cgroup_context_apply(CGroupContext *c, CGroupMask mask, const char *path, ManagerState state) { - bool is_root; - int r; - - assert(c); - assert(path); - - if (mask == 0) - return; - - /* Some cgroup attributes are not supported on the root cgroup, - * hence silently ignore */ - is_root = isempty(path) || path_equal(path, "/"); - if (is_root) - /* Make sure we don't try to display messages with an empty path. */ - path = "/"; - - /* We generally ignore errors caused by read-only mounted - * cgroup trees (assuming we are running in a container then), - * and missing cgroups, i.e. EROFS and ENOENT. */ - - if ((mask & CGROUP_MASK_CPU) && !is_root) { - char buf[MAX(DECIMAL_STR_MAX(uint64_t), DECIMAL_STR_MAX(usec_t)) + 1]; - - sprintf(buf, "%" PRIu64 "\n", - IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && c->startup_cpu_shares != CGROUP_CPU_SHARES_INVALID ? c->startup_cpu_shares : - c->cpu_shares != CGROUP_CPU_SHARES_INVALID ? c->cpu_shares : CGROUP_CPU_SHARES_DEFAULT); - r = cg_set_attribute("cpu", path, "cpu.shares", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set cpu.shares on %s: %m", path); - - sprintf(buf, USEC_FMT "\n", CGROUP_CPU_QUOTA_PERIOD_USEC); - r = cg_set_attribute("cpu", path, "cpu.cfs_period_us", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set cpu.cfs_period_us on %s: %m", path); - - if (c->cpu_quota_per_sec_usec != USEC_INFINITY) { - sprintf(buf, USEC_FMT "\n", c->cpu_quota_per_sec_usec * CGROUP_CPU_QUOTA_PERIOD_USEC / USEC_PER_SEC); - r = cg_set_attribute("cpu", path, "cpu.cfs_quota_us", buf); - } else - r = cg_set_attribute("cpu", path, "cpu.cfs_quota_us", "-1"); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set cpu.cfs_quota_us on %s: %m", path); - } - - if (mask & CGROUP_MASK_IO) { - bool has_io = cgroup_context_has_io_config(c); - bool has_blockio = cgroup_context_has_blockio_config(c); - - if (!is_root) { - char buf[8+DECIMAL_STR_MAX(uint64_t)+1]; - uint64_t weight; - - if (has_io) - weight = cgroup_context_io_weight(c, state); - else if (has_blockio) - weight = cgroup_weight_blkio_to_io(cgroup_context_blkio_weight(c, state)); - else - weight = CGROUP_WEIGHT_DEFAULT; - - xsprintf(buf, "default %" PRIu64 "\n", weight); - r = cg_set_attribute("io", path, "io.weight", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set io.weight on %s: %m", path); - - if (has_io) { - CGroupIODeviceWeight *w; - - /* FIXME: no way to reset this list */ - LIST_FOREACH(device_weights, w, c->io_device_weights) - cgroup_apply_io_device_weight(path, w->path, w->weight); - } else if (has_blockio) { - CGroupBlockIODeviceWeight *w; - - /* FIXME: no way to reset this list */ - LIST_FOREACH(device_weights, w, c->blockio_device_weights) - cgroup_apply_io_device_weight(path, w->path, cgroup_weight_blkio_to_io(w->weight)); - } - } - - /* Apply limits and free ones without config. */ - if (has_io) { - CGroupIODeviceLimit *l, *next; - - LIST_FOREACH_SAFE(device_limits, l, next, c->io_device_limits) { - if (!cgroup_apply_io_device_limit(path, l->path, l->limits)) - cgroup_context_free_io_device_limit(c, l); - } - } else if (has_blockio) { - CGroupBlockIODeviceBandwidth *b, *next; - - LIST_FOREACH_SAFE(device_bandwidths, b, next, c->blockio_device_bandwidths) { - uint64_t limits[_CGROUP_IO_LIMIT_TYPE_MAX]; - CGroupIOLimitType type; - - for (type = 0; type < _CGROUP_IO_LIMIT_TYPE_MAX; type++) - limits[type] = cgroup_io_limit_defaults[type]; - - limits[CGROUP_IO_RBPS_MAX] = b->rbps; - limits[CGROUP_IO_WBPS_MAX] = b->wbps; - - if (!cgroup_apply_io_device_limit(path, b->path, limits)) - cgroup_context_free_blockio_device_bandwidth(c, b); - } - } - } - - if (mask & CGROUP_MASK_BLKIO) { - bool has_io = cgroup_context_has_io_config(c); - bool has_blockio = cgroup_context_has_blockio_config(c); - - if (!is_root) { - char buf[DECIMAL_STR_MAX(uint64_t)+1]; - uint64_t weight; - - if (has_blockio) - weight = cgroup_context_blkio_weight(c, state); - else if (has_io) - weight = cgroup_weight_io_to_blkio(cgroup_context_io_weight(c, state)); - else - weight = CGROUP_BLKIO_WEIGHT_DEFAULT; - - xsprintf(buf, "%" PRIu64 "\n", weight); - r = cg_set_attribute("blkio", path, "blkio.weight", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set blkio.weight on %s: %m", path); - - if (has_blockio) { - CGroupBlockIODeviceWeight *w; - - /* FIXME: no way to reset this list */ - LIST_FOREACH(device_weights, w, c->blockio_device_weights) - cgroup_apply_blkio_device_weight(path, w->path, w->weight); - } else if (has_io) { - CGroupIODeviceWeight *w; - - /* FIXME: no way to reset this list */ - LIST_FOREACH(device_weights, w, c->io_device_weights) - cgroup_apply_blkio_device_weight(path, w->path, cgroup_weight_io_to_blkio(w->weight)); - } - } - - /* Apply limits and free ones without config. */ - if (has_blockio) { - CGroupBlockIODeviceBandwidth *b, *next; - - LIST_FOREACH_SAFE(device_bandwidths, b, next, c->blockio_device_bandwidths) { - if (!cgroup_apply_blkio_device_limit(path, b->path, b->rbps, b->wbps)) - cgroup_context_free_blockio_device_bandwidth(c, b); - } - } else if (has_io) { - CGroupIODeviceLimit *l, *next; - - LIST_FOREACH_SAFE(device_limits, l, next, c->io_device_limits) { - if (!cgroup_apply_blkio_device_limit(path, l->path, l->limits[CGROUP_IO_RBPS_MAX], l->limits[CGROUP_IO_WBPS_MAX])) - cgroup_context_free_io_device_limit(c, l); - } - } - } - - if ((mask & CGROUP_MASK_MEMORY) && !is_root) { - if (c->memory_limit != (uint64_t) -1) { - char buf[DECIMAL_STR_MAX(uint64_t) + 1]; - - sprintf(buf, "%" PRIu64 "\n", c->memory_limit); - - if (cg_unified() <= 0) - r = cg_set_attribute("memory", path, "memory.limit_in_bytes", buf); - else - r = cg_set_attribute("memory", path, "memory.max", buf); - - } else { - if (cg_unified() <= 0) - r = cg_set_attribute("memory", path, "memory.limit_in_bytes", "-1"); - else - r = cg_set_attribute("memory", path, "memory.max", "max"); - } - - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set memory.limit_in_bytes/memory.max on %s: %m", path); - } - - if ((mask & CGROUP_MASK_DEVICES) && !is_root) { - CGroupDeviceAllow *a; - - /* Changing the devices list of a populated cgroup - * might result in EINVAL, hence ignore EINVAL - * here. */ - - if (c->device_allow || c->device_policy != CGROUP_AUTO) - r = cg_set_attribute("devices", path, "devices.deny", "a"); - else - r = cg_set_attribute("devices", path, "devices.allow", "a"); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to reset devices.list on %s: %m", path); - - if (c->device_policy == CGROUP_CLOSED || - (c->device_policy == CGROUP_AUTO && c->device_allow)) { - static const char auto_devices[] = - "/dev/null\0" "rwm\0" - "/dev/zero\0" "rwm\0" - "/dev/full\0" "rwm\0" - "/dev/random\0" "rwm\0" - "/dev/urandom\0" "rwm\0" - "/dev/tty\0" "rwm\0" - "/dev/pts/ptmx\0" "rw\0"; /* /dev/pts/ptmx may not be duplicated, but accessed */ - - const char *x, *y; - - NULSTR_FOREACH_PAIR(x, y, auto_devices) - whitelist_device(path, x, y); - - whitelist_major(path, "pts", 'c', "rw"); - whitelist_major(path, "kdbus", 'c', "rw"); - whitelist_major(path, "kdbus/*", 'c', "rw"); - } - - LIST_FOREACH(device_allow, a, c->device_allow) { - char acc[4]; - unsigned k = 0; - - if (a->r) - acc[k++] = 'r'; - if (a->w) - acc[k++] = 'w'; - if (a->m) - acc[k++] = 'm'; - - if (k == 0) - continue; - - acc[k++] = 0; - - if (startswith(a->path, "/dev/")) - whitelist_device(path, a->path, acc); - else if (startswith(a->path, "block-")) - whitelist_major(path, a->path + 6, 'b', acc); - else if (startswith(a->path, "char-")) - whitelist_major(path, a->path + 5, 'c', acc); - else - log_debug("Ignoring device %s while writing cgroup attribute.", a->path); - } - } - - if ((mask & CGROUP_MASK_PIDS) && !is_root) { - - if (c->tasks_max != (uint64_t) -1) { - char buf[DECIMAL_STR_MAX(uint64_t) + 2]; - - sprintf(buf, "%" PRIu64 "\n", c->tasks_max); - r = cg_set_attribute("pids", path, "pids.max", buf); - } else - r = cg_set_attribute("pids", path, "pids.max", "max"); - - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set pids.max on %s: %m", path); - } -} - -CGroupMask cgroup_context_get_mask(CGroupContext *c) { - CGroupMask mask = 0; - - /* Figure out which controllers we need */ - - if (c->cpu_accounting || - c->cpu_shares != CGROUP_CPU_SHARES_INVALID || - c->startup_cpu_shares != CGROUP_CPU_SHARES_INVALID || - c->cpu_quota_per_sec_usec != USEC_INFINITY) - mask |= CGROUP_MASK_CPUACCT | CGROUP_MASK_CPU; - - if (cgroup_context_has_io_config(c) || cgroup_context_has_blockio_config(c)) - mask |= CGROUP_MASK_IO | CGROUP_MASK_BLKIO; - - if (c->memory_accounting || - c->memory_limit != (uint64_t) -1) - mask |= CGROUP_MASK_MEMORY; - - if (c->device_allow || - c->device_policy != CGROUP_AUTO) - mask |= CGROUP_MASK_DEVICES; - - if (c->tasks_accounting || - c->tasks_max != (uint64_t) -1) - mask |= CGROUP_MASK_PIDS; - - return mask; -} - -CGroupMask unit_get_own_mask(Unit *u) { - CGroupContext *c; - - /* Returns the mask of controllers the unit needs for itself */ - - c = unit_get_cgroup_context(u); - if (!c) - return 0; - - /* If delegation is turned on, then turn on all cgroups, - * unless we are on the legacy hierarchy and the process we - * fork into it is known to drop privileges, and hence - * shouldn't get access to the controllers. - * - * Note that on the unified hierarchy it is safe to delegate - * controllers to unprivileged services. */ - - if (c->delegate) { - ExecContext *e; - - e = unit_get_exec_context(u); - if (!e || - exec_context_maintains_privileges(e) || - cg_unified() > 0) - return _CGROUP_MASK_ALL; - } - - return cgroup_context_get_mask(c); -} - -CGroupMask unit_get_members_mask(Unit *u) { - assert(u); - - /* Returns the mask of controllers all of the unit's children - * require, merged */ - - if (u->cgroup_members_mask_valid) - return u->cgroup_members_mask; - - u->cgroup_members_mask = 0; - - if (u->type == UNIT_SLICE) { - Unit *member; - Iterator i; - - SET_FOREACH(member, u->dependencies[UNIT_BEFORE], i) { - - if (member == u) - continue; - - if (UNIT_DEREF(member->slice) != u) - continue; - - u->cgroup_members_mask |= - unit_get_own_mask(member) | - unit_get_members_mask(member); - } - } - - u->cgroup_members_mask_valid = true; - return u->cgroup_members_mask; -} - -CGroupMask unit_get_siblings_mask(Unit *u) { - assert(u); - - /* Returns the mask of controllers all of the unit's siblings - * require, i.e. the members mask of the unit's parent slice - * if there is one. */ - - if (UNIT_ISSET(u->slice)) - return unit_get_members_mask(UNIT_DEREF(u->slice)); - - return unit_get_own_mask(u) | unit_get_members_mask(u); -} - -CGroupMask unit_get_subtree_mask(Unit *u) { - - /* Returns the mask of this subtree, meaning of the group - * itself and its children. */ - - return unit_get_own_mask(u) | unit_get_members_mask(u); -} - -CGroupMask unit_get_target_mask(Unit *u) { - CGroupMask mask; - - /* This returns the cgroup mask of all controllers to enable - * for a specific cgroup, i.e. everything it needs itself, - * plus all that its children need, plus all that its siblings - * need. This is primarily useful on the legacy cgroup - * hierarchy, where we need to duplicate each cgroup in each - * hierarchy that shall be enabled for it. */ - - mask = unit_get_own_mask(u) | unit_get_members_mask(u) | unit_get_siblings_mask(u); - mask &= u->manager->cgroup_supported; - - return mask; -} - -CGroupMask unit_get_enable_mask(Unit *u) { - CGroupMask mask; - - /* This returns the cgroup mask of all controllers to enable - * for the children of a specific cgroup. This is primarily - * useful for the unified cgroup hierarchy, where each cgroup - * controls which controllers are enabled for its children. */ - - mask = unit_get_members_mask(u); - mask &= u->manager->cgroup_supported; - - return mask; -} - -/* Recurse from a unit up through its containing slices, propagating - * mask bits upward. A unit is also member of itself. */ -void unit_update_cgroup_members_masks(Unit *u) { - CGroupMask m; - bool more; - - assert(u); - - /* Calculate subtree mask */ - m = unit_get_subtree_mask(u); - - /* See if anything changed from the previous invocation. If - * not, we're done. */ - if (u->cgroup_subtree_mask_valid && m == u->cgroup_subtree_mask) - return; - - more = - u->cgroup_subtree_mask_valid && - ((m & ~u->cgroup_subtree_mask) != 0) && - ((~m & u->cgroup_subtree_mask) == 0); - - u->cgroup_subtree_mask = m; - u->cgroup_subtree_mask_valid = true; - - if (UNIT_ISSET(u->slice)) { - Unit *s = UNIT_DEREF(u->slice); - - if (more) - /* There's more set now than before. We - * propagate the new mask to the parent's mask - * (not caring if it actually was valid or - * not). */ - - s->cgroup_members_mask |= m; - - else - /* There's less set now than before (or we - * don't know), we need to recalculate - * everything, so let's invalidate the - * parent's members mask */ - - s->cgroup_members_mask_valid = false; - - /* And now make sure that this change also hits our - * grandparents */ - unit_update_cgroup_members_masks(s); - } -} - -static const char *migrate_callback(CGroupMask mask, void *userdata) { - Unit *u = userdata; - - assert(mask != 0); - assert(u); - - while (u) { - if (u->cgroup_path && - u->cgroup_realized && - (u->cgroup_realized_mask & mask) == mask) - return u->cgroup_path; - - u = UNIT_DEREF(u->slice); - } - - return NULL; -} - -char *unit_default_cgroup_path(Unit *u) { - _cleanup_free_ char *escaped = NULL, *slice = NULL; - int r; - - assert(u); - - if (unit_has_name(u, SPECIAL_ROOT_SLICE)) - return strdup(u->manager->cgroup_root); - - if (UNIT_ISSET(u->slice) && !unit_has_name(UNIT_DEREF(u->slice), SPECIAL_ROOT_SLICE)) { - r = cg_slice_to_path(UNIT_DEREF(u->slice)->id, &slice); - if (r < 0) - return NULL; - } - - escaped = cg_escape(u->id); - if (!escaped) - return NULL; - - if (slice) - return strjoin(u->manager->cgroup_root, "/", slice, "/", escaped, NULL); - else - return strjoin(u->manager->cgroup_root, "/", escaped, NULL); -} - -int unit_set_cgroup_path(Unit *u, const char *path) { - _cleanup_free_ char *p = NULL; - int r; - - assert(u); - - if (path) { - p = strdup(path); - if (!p) - return -ENOMEM; - } else - p = NULL; - - if (streq_ptr(u->cgroup_path, p)) - return 0; - - if (p) { - r = hashmap_put(u->manager->cgroup_unit, p, u); - if (r < 0) - return r; - } - - unit_release_cgroup(u); - - u->cgroup_path = p; - p = NULL; - - return 1; -} - -int unit_watch_cgroup(Unit *u) { - _cleanup_free_ char *events = NULL; - int r; - - assert(u); - - if (!u->cgroup_path) - return 0; - - if (u->cgroup_inotify_wd >= 0) - return 0; - - /* Only applies to the unified hierarchy */ - r = cg_unified(); - if (r < 0) - return log_unit_error_errno(u, r, "Failed detect wether the unified hierarchy is used: %m"); - if (r == 0) - return 0; - - /* Don't watch the root slice, it's pointless. */ - if (unit_has_name(u, SPECIAL_ROOT_SLICE)) - return 0; - - r = hashmap_ensure_allocated(&u->manager->cgroup_inotify_wd_unit, &trivial_hash_ops); - if (r < 0) - return log_oom(); - - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.events", &events); - if (r < 0) - return log_oom(); - - u->cgroup_inotify_wd = inotify_add_watch(u->manager->cgroup_inotify_fd, events, IN_MODIFY); - if (u->cgroup_inotify_wd < 0) { - - if (errno == ENOENT) /* If the directory is already - * gone we don't need to track - * it, so this is not an error */ - return 0; - - return log_unit_error_errno(u, errno, "Failed to add inotify watch descriptor for control group %s: %m", u->cgroup_path); - } - - r = hashmap_put(u->manager->cgroup_inotify_wd_unit, INT_TO_PTR(u->cgroup_inotify_wd), u); - if (r < 0) - return log_unit_error_errno(u, r, "Failed to add inotify watch descriptor to hash map: %m"); - - return 0; -} - -static int unit_create_cgroup( - Unit *u, - CGroupMask target_mask, - CGroupMask enable_mask) { - - CGroupContext *c; - int r; - - assert(u); - - c = unit_get_cgroup_context(u); - if (!c) - return 0; - - if (!u->cgroup_path) { - _cleanup_free_ char *path = NULL; - - path = unit_default_cgroup_path(u); - if (!path) - return log_oom(); - - r = unit_set_cgroup_path(u, path); - if (r == -EEXIST) - return log_unit_error_errno(u, r, "Control group %s exists already.", path); - if (r < 0) - return log_unit_error_errno(u, r, "Failed to set unit's control group path to %s: %m", path); - } - - /* First, create our own group */ - r = cg_create_everywhere(u->manager->cgroup_supported, target_mask, u->cgroup_path); - if (r < 0) - return log_unit_error_errno(u, r, "Failed to create cgroup %s: %m", u->cgroup_path); - - /* Start watching it */ - (void) unit_watch_cgroup(u); - - /* Enable all controllers we need */ - r = cg_enable_everywhere(u->manager->cgroup_supported, enable_mask, u->cgroup_path); - if (r < 0) - log_unit_warning_errno(u, r, "Failed to enable controllers on cgroup %s, ignoring: %m", u->cgroup_path); - - /* Keep track that this is now realized */ - u->cgroup_realized = true; - u->cgroup_realized_mask = target_mask; - u->cgroup_enabled_mask = enable_mask; - - if (u->type != UNIT_SLICE && !c->delegate) { - - /* Then, possibly move things over, but not if - * subgroups may contain processes, which is the case - * for slice and delegation units. */ - r = cg_migrate_everywhere(u->manager->cgroup_supported, u->cgroup_path, u->cgroup_path, migrate_callback, u); - if (r < 0) - log_unit_warning_errno(u, r, "Failed to migrate cgroup from to %s, ignoring: %m", u->cgroup_path); - } - - return 0; -} - -int unit_attach_pids_to_cgroup(Unit *u) { - int r; - assert(u); - - r = unit_realize_cgroup(u); - if (r < 0) - return r; - - r = cg_attach_many_everywhere(u->manager->cgroup_supported, u->cgroup_path, u->pids, migrate_callback, u); - if (r < 0) - return r; - - return 0; -} - -static bool unit_has_mask_realized(Unit *u, CGroupMask target_mask, CGroupMask enable_mask) { - assert(u); - - return u->cgroup_realized && u->cgroup_realized_mask == target_mask && u->cgroup_enabled_mask == enable_mask; -} - -/* Check if necessary controllers and attributes for a unit are in place. - * - * If so, do nothing. - * If not, create paths, move processes over, and set attributes. - * - * Returns 0 on success and < 0 on failure. */ -static int unit_realize_cgroup_now(Unit *u, ManagerState state) { - CGroupMask target_mask, enable_mask; - int r; - - assert(u); - - if (u->in_cgroup_queue) { - LIST_REMOVE(cgroup_queue, u->manager->cgroup_queue, u); - u->in_cgroup_queue = false; - } - - target_mask = unit_get_target_mask(u); - enable_mask = unit_get_enable_mask(u); - - if (unit_has_mask_realized(u, target_mask, enable_mask)) - return 0; - - /* First, realize parents */ - if (UNIT_ISSET(u->slice)) { - r = unit_realize_cgroup_now(UNIT_DEREF(u->slice), state); - if (r < 0) - return r; - } - - /* And then do the real work */ - r = unit_create_cgroup(u, target_mask, enable_mask); - if (r < 0) - return r; - - /* Finally, apply the necessary attributes. */ - cgroup_context_apply(unit_get_cgroup_context(u), target_mask, u->cgroup_path, state); - - return 0; -} - -static void unit_add_to_cgroup_queue(Unit *u) { - - if (u->in_cgroup_queue) - return; - - LIST_PREPEND(cgroup_queue, u->manager->cgroup_queue, u); - u->in_cgroup_queue = true; -} - -unsigned manager_dispatch_cgroup_queue(Manager *m) { - ManagerState state; - unsigned n = 0; - Unit *i; - int r; - - state = manager_state(m); - - while ((i = m->cgroup_queue)) { - assert(i->in_cgroup_queue); - - r = unit_realize_cgroup_now(i, state); - if (r < 0) - log_warning_errno(r, "Failed to realize cgroups for queued unit %s, ignoring: %m", i->id); - - n++; - } - - return n; -} - -static void unit_queue_siblings(Unit *u) { - Unit *slice; - - /* This adds the siblings of the specified unit and the - * siblings of all parent units to the cgroup queue. (But - * neither the specified unit itself nor the parents.) */ - - while ((slice = UNIT_DEREF(u->slice))) { - Iterator i; - Unit *m; - - SET_FOREACH(m, slice->dependencies[UNIT_BEFORE], i) { - if (m == u) - continue; - - /* Skip units that have a dependency on the slice - * but aren't actually in it. */ - if (UNIT_DEREF(m->slice) != slice) - continue; - - /* No point in doing cgroup application for units - * without active processes. */ - if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(m))) - continue; - - /* If the unit doesn't need any new controllers - * and has current ones realized, it doesn't need - * any changes. */ - if (unit_has_mask_realized(m, unit_get_target_mask(m), unit_get_enable_mask(m))) - continue; - - unit_add_to_cgroup_queue(m); - } - - u = slice; - } -} - -int unit_realize_cgroup(Unit *u) { - assert(u); - - if (!UNIT_HAS_CGROUP_CONTEXT(u)) - return 0; - - /* So, here's the deal: when realizing the cgroups for this - * unit, we need to first create all parents, but there's more - * actually: for the weight-based controllers we also need to - * make sure that all our siblings (i.e. units that are in the - * same slice as we are) have cgroups, too. Otherwise, things - * would become very uneven as each of their processes would - * get as much resources as all our group together. This call - * will synchronously create the parent cgroups, but will - * defer work on the siblings to the next event loop - * iteration. */ - - /* Add all sibling slices to the cgroup queue. */ - unit_queue_siblings(u); - - /* And realize this one now (and apply the values) */ - return unit_realize_cgroup_now(u, manager_state(u->manager)); -} - -void unit_release_cgroup(Unit *u) { - assert(u); - - /* Forgets all cgroup details for this cgroup */ - - if (u->cgroup_path) { - (void) hashmap_remove(u->manager->cgroup_unit, u->cgroup_path); - u->cgroup_path = mfree(u->cgroup_path); - } - - if (u->cgroup_inotify_wd >= 0) { - if (inotify_rm_watch(u->manager->cgroup_inotify_fd, u->cgroup_inotify_wd) < 0) - log_unit_debug_errno(u, errno, "Failed to remove cgroup inotify watch %i for %s, ignoring", u->cgroup_inotify_wd, u->id); - - (void) hashmap_remove(u->manager->cgroup_inotify_wd_unit, INT_TO_PTR(u->cgroup_inotify_wd)); - u->cgroup_inotify_wd = -1; - } -} - -void unit_prune_cgroup(Unit *u) { - int r; - bool is_root_slice; - - assert(u); - - /* Removes the cgroup, if empty and possible, and stops watching it. */ - - if (!u->cgroup_path) - return; - - is_root_slice = unit_has_name(u, SPECIAL_ROOT_SLICE); - - r = cg_trim_everywhere(u->manager->cgroup_supported, u->cgroup_path, !is_root_slice); - if (r < 0) { - log_debug_errno(r, "Failed to destroy cgroup %s, ignoring: %m", u->cgroup_path); - return; - } - - if (is_root_slice) - return; - - unit_release_cgroup(u); - - u->cgroup_realized = false; - u->cgroup_realized_mask = 0; - u->cgroup_enabled_mask = 0; -} - -int unit_search_main_pid(Unit *u, pid_t *ret) { - _cleanup_fclose_ FILE *f = NULL; - pid_t pid = 0, npid, mypid; - int r; - - assert(u); - assert(ret); - - if (!u->cgroup_path) - return -ENXIO; - - r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, &f); - if (r < 0) - return r; - - mypid = getpid(); - while (cg_read_pid(f, &npid) > 0) { - pid_t ppid; - - if (npid == pid) - continue; - - /* Ignore processes that aren't our kids */ - if (get_process_ppid(npid, &ppid) >= 0 && ppid != mypid) - continue; - - if (pid != 0) - /* Dang, there's more than one daemonized PID - in this group, so we don't know what process - is the main process. */ - - return -ENODATA; - - pid = npid; - } - - *ret = pid; - return 0; -} - -static int unit_watch_pids_in_path(Unit *u, const char *path) { - _cleanup_closedir_ DIR *d = NULL; - _cleanup_fclose_ FILE *f = NULL; - int ret = 0, r; - - assert(u); - assert(path); - - r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, path, &f); - if (r < 0) - ret = r; - else { - pid_t pid; - - while ((r = cg_read_pid(f, &pid)) > 0) { - r = unit_watch_pid(u, pid); - if (r < 0 && ret >= 0) - ret = r; - } - - if (r < 0 && ret >= 0) - ret = r; - } - - r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, path, &d); - if (r < 0) { - if (ret >= 0) - ret = r; - } else { - char *fn; - - while ((r = cg_read_subgroup(d, &fn)) > 0) { - _cleanup_free_ char *p = NULL; - - p = strjoin(path, "/", fn, NULL); - free(fn); - - if (!p) - return -ENOMEM; - - r = unit_watch_pids_in_path(u, p); - if (r < 0 && ret >= 0) - ret = r; - } - - if (r < 0 && ret >= 0) - ret = r; - } - - return ret; -} - -int unit_watch_all_pids(Unit *u) { - assert(u); - - /* Adds all PIDs from our cgroup to the set of PIDs we - * watch. This is a fallback logic for cases where we do not - * get reliable cgroup empty notifications: we try to use - * SIGCHLD as replacement. */ - - if (!u->cgroup_path) - return -ENOENT; - - if (cg_unified() > 0) /* On unified we can use proper notifications */ - return 0; - - return unit_watch_pids_in_path(u, u->cgroup_path); -} - -int unit_notify_cgroup_empty(Unit *u) { - int r; - - assert(u); - - if (!u->cgroup_path) - return 0; - - r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path); - if (r <= 0) - return r; - - unit_add_to_gc_queue(u); - - if (UNIT_VTABLE(u)->notify_cgroup_empty) - UNIT_VTABLE(u)->notify_cgroup_empty(u); - - return 0; -} - -static int on_cgroup_inotify_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - - assert(s); - assert(fd >= 0); - assert(m); - - for (;;) { - union inotify_event_buffer buffer; - struct inotify_event *e; - ssize_t l; - - l = read(fd, &buffer, sizeof(buffer)); - if (l < 0) { - if (errno == EINTR || errno == EAGAIN) - return 0; - - return log_error_errno(errno, "Failed to read control group inotify events: %m"); - } - - FOREACH_INOTIFY_EVENT(e, buffer, l) { - Unit *u; - - if (e->wd < 0) - /* Queue overflow has no watch descriptor */ - continue; - - if (e->mask & IN_IGNORED) - /* The watch was just removed */ - continue; - - u = hashmap_get(m->cgroup_inotify_wd_unit, INT_TO_PTR(e->wd)); - if (!u) /* Not that inotify might deliver - * events for a watch even after it - * was removed, because it was queued - * before the removal. Let's ignore - * this here safely. */ - continue; - - (void) unit_notify_cgroup_empty(u); - } - } -} - -int manager_setup_cgroup(Manager *m) { - _cleanup_free_ char *path = NULL; - CGroupController c; - int r, unified; - char *e; - - assert(m); - - /* 1. Determine hierarchy */ - m->cgroup_root = mfree(m->cgroup_root); - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &m->cgroup_root); - if (r < 0) - return log_error_errno(r, "Cannot determine cgroup we are running in: %m"); - - /* Chop off the init scope, if we are already located in it */ - e = endswith(m->cgroup_root, "/" SPECIAL_INIT_SCOPE); - - /* LEGACY: Also chop off the system slice if we are in - * it. This is to support live upgrades from older systemd - * versions where PID 1 was moved there. Also see - * cg_get_root_path(). */ - if (!e && MANAGER_IS_SYSTEM(m)) { - e = endswith(m->cgroup_root, "/" SPECIAL_SYSTEM_SLICE); - if (!e) - e = endswith(m->cgroup_root, "/system"); /* even more legacy */ - } - if (e) - *e = 0; - - /* And make sure to store away the root value without trailing - * slash, even for the root dir, so that we can easily prepend - * it everywhere. */ - while ((e = endswith(m->cgroup_root, "/"))) - *e = 0; - - /* 2. Show data */ - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, NULL, &path); - if (r < 0) - return log_error_errno(r, "Cannot find cgroup mount point: %m"); - - unified = cg_unified(); - if (unified < 0) - return log_error_errno(r, "Couldn't determine if we are running in the unified hierarchy: %m"); - if (unified > 0) - log_debug("Unified cgroup hierarchy is located at %s.", path); - else - log_debug("Using cgroup controller " SYSTEMD_CGROUP_CONTROLLER ". File system hierarchy is at %s.", path); - - if (!m->test_run) { - const char *scope_path; - - /* 3. Install agent */ - if (unified) { - - /* In the unified hierarchy we can can get - * cgroup empty notifications via inotify. */ - - m->cgroup_inotify_event_source = sd_event_source_unref(m->cgroup_inotify_event_source); - safe_close(m->cgroup_inotify_fd); - - m->cgroup_inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); - if (m->cgroup_inotify_fd < 0) - return log_error_errno(errno, "Failed to create control group inotify object: %m"); - - r = sd_event_add_io(m->event, &m->cgroup_inotify_event_source, m->cgroup_inotify_fd, EPOLLIN, on_cgroup_inotify_event, m); - if (r < 0) - return log_error_errno(r, "Failed to watch control group inotify object: %m"); - - /* Process cgroup empty notifications early, but after service notifications and SIGCHLD. Also - * see handling of cgroup agent notifications, for the classic cgroup hierarchy support. */ - r = sd_event_source_set_priority(m->cgroup_inotify_event_source, SD_EVENT_PRIORITY_NORMAL-5); - if (r < 0) - return log_error_errno(r, "Failed to set priority of inotify event source: %m"); - - (void) sd_event_source_set_description(m->cgroup_inotify_event_source, "cgroup-inotify"); - - } else if (MANAGER_IS_SYSTEM(m)) { - - /* On the legacy hierarchy we only get - * notifications via cgroup agents. (Which - * isn't really reliable, since it does not - * generate events when control groups with - * children run empty. */ - - r = cg_install_release_agent(SYSTEMD_CGROUP_CONTROLLER, SYSTEMD_CGROUP_AGENT_PATH); - if (r < 0) - log_warning_errno(r, "Failed to install release agent, ignoring: %m"); - else if (r > 0) - log_debug("Installed release agent."); - else if (r == 0) - log_debug("Release agent already installed."); - } - - /* 4. Make sure we are in the special "init.scope" unit in the root slice. */ - scope_path = strjoina(m->cgroup_root, "/" SPECIAL_INIT_SCOPE); - r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, scope_path, 0); - if (r < 0) - return log_error_errno(r, "Failed to create %s control group: %m", scope_path); - - /* also, move all other userspace processes remaining - * in the root cgroup into that scope. */ - r = cg_migrate(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, SYSTEMD_CGROUP_CONTROLLER, scope_path, false); - if (r < 0) - log_warning_errno(r, "Couldn't move remaining userspace processes, ignoring: %m"); - - /* 5. And pin it, so that it cannot be unmounted */ - safe_close(m->pin_cgroupfs_fd); - m->pin_cgroupfs_fd = open(path, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY|O_NONBLOCK); - if (m->pin_cgroupfs_fd < 0) - return log_error_errno(errno, "Failed to open pin file: %m"); - - /* 6. Always enable hierarchical support if it exists... */ - if (!unified) - (void) cg_set_attribute("memory", "/", "memory.use_hierarchy", "1"); - } - - /* 7. Figure out which controllers are supported */ - r = cg_mask_supported(&m->cgroup_supported); - if (r < 0) - return log_error_errno(r, "Failed to determine supported controllers: %m"); - - for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) - log_debug("Controller '%s' supported: %s", cgroup_controller_to_string(c), yes_no(m->cgroup_supported & c)); - - return 0; -} - -void manager_shutdown_cgroup(Manager *m, bool delete) { - assert(m); - - /* We can't really delete the group, since we are in it. But - * let's trim it. */ - if (delete && m->cgroup_root) - (void) cg_trim(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, false); - - m->cgroup_inotify_wd_unit = hashmap_free(m->cgroup_inotify_wd_unit); - - m->cgroup_inotify_event_source = sd_event_source_unref(m->cgroup_inotify_event_source); - m->cgroup_inotify_fd = safe_close(m->cgroup_inotify_fd); - - m->pin_cgroupfs_fd = safe_close(m->pin_cgroupfs_fd); - - m->cgroup_root = mfree(m->cgroup_root); -} - -Unit* manager_get_unit_by_cgroup(Manager *m, const char *cgroup) { - char *p; - Unit *u; - - assert(m); - assert(cgroup); - - u = hashmap_get(m->cgroup_unit, cgroup); - if (u) - return u; - - p = strdupa(cgroup); - for (;;) { - char *e; - - e = strrchr(p, '/'); - if (!e || e == p) - return hashmap_get(m->cgroup_unit, SPECIAL_ROOT_SLICE); - - *e = 0; - - u = hashmap_get(m->cgroup_unit, p); - if (u) - return u; - } -} - -Unit *manager_get_unit_by_pid_cgroup(Manager *m, pid_t pid) { - _cleanup_free_ char *cgroup = NULL; - int r; - - assert(m); - - if (pid <= 0) - return NULL; - - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cgroup); - if (r < 0) - return NULL; - - return manager_get_unit_by_cgroup(m, cgroup); -} - -Unit *manager_get_unit_by_pid(Manager *m, pid_t pid) { - Unit *u; - - assert(m); - - if (pid <= 0) - return NULL; - - if (pid == 1) - return hashmap_get(m->units, SPECIAL_INIT_SCOPE); - - u = hashmap_get(m->watch_pids1, PID_TO_PTR(pid)); - if (u) - return u; - - u = hashmap_get(m->watch_pids2, PID_TO_PTR(pid)); - if (u) - return u; - - return manager_get_unit_by_pid_cgroup(m, pid); -} - -int manager_notify_cgroup_empty(Manager *m, const char *cgroup) { - Unit *u; - - assert(m); - assert(cgroup); - - log_debug("Got cgroup empty notification for: %s", cgroup); - - u = manager_get_unit_by_cgroup(m, cgroup); - if (!u) - return 0; - - return unit_notify_cgroup_empty(u); -} - -int unit_get_memory_current(Unit *u, uint64_t *ret) { - _cleanup_free_ char *v = NULL; - int r; - - assert(u); - assert(ret); - - if (!u->cgroup_path) - return -ENODATA; - - if ((u->cgroup_realized_mask & CGROUP_MASK_MEMORY) == 0) - return -ENODATA; - - if (cg_unified() <= 0) - r = cg_get_attribute("memory", u->cgroup_path, "memory.usage_in_bytes", &v); - else - r = cg_get_attribute("memory", u->cgroup_path, "memory.current", &v); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - - return safe_atou64(v, ret); -} - -int unit_get_tasks_current(Unit *u, uint64_t *ret) { - _cleanup_free_ char *v = NULL; - int r; - - assert(u); - assert(ret); - - if (!u->cgroup_path) - return -ENODATA; - - if ((u->cgroup_realized_mask & CGROUP_MASK_PIDS) == 0) - return -ENODATA; - - r = cg_get_attribute("pids", u->cgroup_path, "pids.current", &v); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - - return safe_atou64(v, ret); -} - -static int unit_get_cpu_usage_raw(Unit *u, nsec_t *ret) { - _cleanup_free_ char *v = NULL; - uint64_t ns; - int r; - - assert(u); - assert(ret); - - if (!u->cgroup_path) - return -ENODATA; - - if ((u->cgroup_realized_mask & CGROUP_MASK_CPUACCT) == 0) - return -ENODATA; - - r = cg_get_attribute("cpuacct", u->cgroup_path, "cpuacct.usage", &v); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - - r = safe_atou64(v, &ns); - if (r < 0) - return r; - - *ret = ns; - return 0; -} - -int unit_get_cpu_usage(Unit *u, nsec_t *ret) { - nsec_t ns; - int r; - - r = unit_get_cpu_usage_raw(u, &ns); - if (r < 0) - return r; - - if (ns > u->cpuacct_usage_base) - ns -= u->cpuacct_usage_base; - else - ns = 0; - - *ret = ns; - return 0; -} - -int unit_reset_cpu_usage(Unit *u) { - nsec_t ns; - int r; - - assert(u); - - r = unit_get_cpu_usage_raw(u, &ns); - if (r < 0) { - u->cpuacct_usage_base = 0; - return r; - } - - u->cpuacct_usage_base = ns; - return 0; -} - -bool unit_cgroup_delegate(Unit *u) { - CGroupContext *c; - - assert(u); - - c = unit_get_cgroup_context(u); - if (!c) - return false; - - return c->delegate; -} - -void unit_invalidate_cgroup(Unit *u, CGroupMask m) { - assert(u); - - if (!UNIT_HAS_CGROUP_CONTEXT(u)) - return; - - if (m == 0) - return; - - /* always invalidate compat pairs together */ - if (m & (CGROUP_MASK_IO | CGROUP_MASK_BLKIO)) - m |= CGROUP_MASK_IO | CGROUP_MASK_BLKIO; - - if ((u->cgroup_realized_mask & m) == 0) - return; - - u->cgroup_realized_mask &= ~m; - unit_add_to_cgroup_queue(u); -} - -void manager_invalidate_startup_units(Manager *m) { - Iterator i; - Unit *u; - - assert(m); - - SET_FOREACH(u, m->startup_units, i) - unit_invalidate_cgroup(u, CGROUP_MASK_CPU|CGROUP_MASK_IO|CGROUP_MASK_BLKIO); -} - -static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = { - [CGROUP_AUTO] = "auto", - [CGROUP_CLOSED] = "closed", - [CGROUP_STRICT] = "strict", -}; - -DEFINE_STRING_TABLE_LOOKUP(cgroup_device_policy, CGroupDevicePolicy); diff --git a/src/core/cgroup.h b/src/core/cgroup.h deleted file mode 100644 index 2b1edbafc4..0000000000 --- a/src/core/cgroup.h +++ /dev/null @@ -1,181 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "list.h" -#include "time-util.h" -#include "cgroup-util.h" - -typedef struct CGroupContext CGroupContext; -typedef struct CGroupDeviceAllow CGroupDeviceAllow; -typedef struct CGroupIODeviceWeight CGroupIODeviceWeight; -typedef struct CGroupIODeviceLimit CGroupIODeviceLimit; -typedef struct CGroupBlockIODeviceWeight CGroupBlockIODeviceWeight; -typedef struct CGroupBlockIODeviceBandwidth CGroupBlockIODeviceBandwidth; - -typedef enum CGroupDevicePolicy { - - /* When devices listed, will allow those, plus built-in ones, - if none are listed will allow everything. */ - CGROUP_AUTO, - - /* Everything forbidden, except built-in ones and listed ones. */ - CGROUP_CLOSED, - - /* Everythings forbidden, except for the listed devices */ - CGROUP_STRICT, - - _CGROUP_DEVICE_POLICY_MAX, - _CGROUP_DEVICE_POLICY_INVALID = -1 -} CGroupDevicePolicy; - -struct CGroupDeviceAllow { - LIST_FIELDS(CGroupDeviceAllow, device_allow); - char *path; - bool r:1; - bool w:1; - bool m:1; -}; - -struct CGroupIODeviceWeight { - LIST_FIELDS(CGroupIODeviceWeight, device_weights); - char *path; - uint64_t weight; -}; - -struct CGroupIODeviceLimit { - LIST_FIELDS(CGroupIODeviceLimit, device_limits); - char *path; - uint64_t limits[_CGROUP_IO_LIMIT_TYPE_MAX]; -}; - -struct CGroupBlockIODeviceWeight { - LIST_FIELDS(CGroupBlockIODeviceWeight, device_weights); - char *path; - uint64_t weight; -}; - -struct CGroupBlockIODeviceBandwidth { - LIST_FIELDS(CGroupBlockIODeviceBandwidth, device_bandwidths); - char *path; - uint64_t rbps; - uint64_t wbps; -}; - -struct CGroupContext { - bool cpu_accounting; - bool io_accounting; - bool blockio_accounting; - bool memory_accounting; - bool tasks_accounting; - - /* For unified hierarchy */ - uint64_t io_weight; - uint64_t startup_io_weight; - LIST_HEAD(CGroupIODeviceWeight, io_device_weights); - LIST_HEAD(CGroupIODeviceLimit, io_device_limits); - - /* For legacy hierarchies */ - uint64_t cpu_shares; - uint64_t startup_cpu_shares; - usec_t cpu_quota_per_sec_usec; - - uint64_t blockio_weight; - uint64_t startup_blockio_weight; - LIST_HEAD(CGroupBlockIODeviceWeight, blockio_device_weights); - LIST_HEAD(CGroupBlockIODeviceBandwidth, blockio_device_bandwidths); - - uint64_t memory_limit; - - CGroupDevicePolicy device_policy; - LIST_HEAD(CGroupDeviceAllow, device_allow); - - /* Common */ - uint64_t tasks_max; - - bool delegate; -}; - -#include "cgroup-util.h" -#include "unit.h" - -void cgroup_context_init(CGroupContext *c); -void cgroup_context_done(CGroupContext *c); -void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix); -void cgroup_context_apply(CGroupContext *c, CGroupMask mask, const char *path, ManagerState state); - -CGroupMask cgroup_context_get_mask(CGroupContext *c); - -void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a); -void cgroup_context_free_io_device_weight(CGroupContext *c, CGroupIODeviceWeight *w); -void cgroup_context_free_io_device_limit(CGroupContext *c, CGroupIODeviceLimit *l); -void cgroup_context_free_blockio_device_weight(CGroupContext *c, CGroupBlockIODeviceWeight *w); -void cgroup_context_free_blockio_device_bandwidth(CGroupContext *c, CGroupBlockIODeviceBandwidth *b); - -CGroupMask unit_get_own_mask(Unit *u); -CGroupMask unit_get_siblings_mask(Unit *u); -CGroupMask unit_get_members_mask(Unit *u); -CGroupMask unit_get_subtree_mask(Unit *u); - -CGroupMask unit_get_target_mask(Unit *u); -CGroupMask unit_get_enable_mask(Unit *u); - -void unit_update_cgroup_members_masks(Unit *u); - -char *unit_default_cgroup_path(Unit *u); -int unit_set_cgroup_path(Unit *u, const char *path); - -int unit_realize_cgroup(Unit *u); -void unit_release_cgroup(Unit *u); -void unit_prune_cgroup(Unit *u); -int unit_watch_cgroup(Unit *u); - -int unit_attach_pids_to_cgroup(Unit *u); - -int manager_setup_cgroup(Manager *m); -void manager_shutdown_cgroup(Manager *m, bool delete); - -unsigned manager_dispatch_cgroup_queue(Manager *m); - -Unit *manager_get_unit_by_cgroup(Manager *m, const char *cgroup); -Unit *manager_get_unit_by_pid_cgroup(Manager *m, pid_t pid); -Unit* manager_get_unit_by_pid(Manager *m, pid_t pid); - -int unit_search_main_pid(Unit *u, pid_t *ret); -int unit_watch_all_pids(Unit *u); - -int unit_get_memory_current(Unit *u, uint64_t *ret); -int unit_get_tasks_current(Unit *u, uint64_t *ret); -int unit_get_cpu_usage(Unit *u, nsec_t *ret); -int unit_reset_cpu_usage(Unit *u); - -bool unit_cgroup_delegate(Unit *u); - -int unit_notify_cgroup_empty(Unit *u); -int manager_notify_cgroup_empty(Manager *m, const char *group); - -void unit_invalidate_cgroup(Unit *u, CGroupMask m); - -void manager_invalidate_startup_units(Manager *m); - -const char* cgroup_device_policy_to_string(CGroupDevicePolicy i) _const_; -CGroupDevicePolicy cgroup_device_policy_from_string(const char *s) _pure_; diff --git a/src/core/dbus-automount.c b/src/core/dbus-automount.c deleted file mode 100644 index b2806ad86f..0000000000 --- a/src/core/dbus-automount.c +++ /dev/null @@ -1,34 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 "automount.h" -#include "bus-util.h" -#include "dbus-automount.h" -#include "string-util.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, automount_result, AutomountResult); - -const sd_bus_vtable bus_automount_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Where", "s", NULL, offsetof(Automount, where), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Automount, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Automount, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("TimeoutIdleUSec", "t", bus_property_get_usec, offsetof(Automount, timeout_idle_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_VTABLE_END -}; diff --git a/src/core/dbus-automount.h b/src/core/dbus-automount.h deleted file mode 100644 index 7b51eb973a..0000000000 --- a/src/core/dbus-automount.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - - -extern const sd_bus_vtable bus_automount_vtable[]; diff --git a/src/core/dbus-busname.c b/src/core/dbus-busname.c deleted file mode 100644 index cf816ba15b..0000000000 --- a/src/core/dbus-busname.c +++ /dev/null @@ -1,37 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "bus-util.h" -#include "busname.h" -#include "dbus-busname.h" -#include "string-util.h" -#include "unit.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, busname_result, BusNameResult); - -const sd_bus_vtable bus_busname_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Name", "s", NULL, offsetof(BusName, name), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TimeoutUSec", "t", bus_property_get_usec, offsetof(BusName, timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(BusName, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(BusName, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Activating", "b", bus_property_get_bool, offsetof(BusName, activating), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("AcceptFileDescriptors", "b", bus_property_get_bool, offsetof(BusName, accept_fd), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_VTABLE_END -}; diff --git a/src/core/dbus-busname.h b/src/core/dbus-busname.h deleted file mode 100644 index 8643d1a404..0000000000 --- a/src/core/dbus-busname.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - - -extern const sd_bus_vtable bus_busname_vtable[]; diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c deleted file mode 100644 index eef1c47c14..0000000000 --- a/src/core/dbus-cgroup.c +++ /dev/null @@ -1,1004 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "alloc-util.h" -#include "bus-util.h" -#include "cgroup-util.h" -#include "cgroup.h" -#include "dbus-cgroup.h" -#include "fd-util.h" -#include "fileio.h" -#include "path-util.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_cgroup_device_policy, cgroup_device_policy, CGroupDevicePolicy); - -static int property_get_io_device_weight( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - CGroupContext *c = userdata; - CGroupIODeviceWeight *w; - int r; - - assert(bus); - assert(reply); - assert(c); - - r = sd_bus_message_open_container(reply, 'a', "(st)"); - if (r < 0) - return r; - - LIST_FOREACH(device_weights, w, c->io_device_weights) { - r = sd_bus_message_append(reply, "(st)", w->path, w->weight); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_io_device_limits( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - CGroupContext *c = userdata; - CGroupIODeviceLimit *l; - int r; - - assert(bus); - assert(reply); - assert(c); - - r = sd_bus_message_open_container(reply, 'a', "(st)"); - if (r < 0) - return r; - - LIST_FOREACH(device_limits, l, c->io_device_limits) { - CGroupIOLimitType type; - - type = cgroup_io_limit_type_from_string(property); - if (type < 0 || l->limits[type] == cgroup_io_limit_defaults[type]) - continue; - - r = sd_bus_message_append(reply, "(st)", l->path, l->limits[type]); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_blockio_device_weight( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - CGroupContext *c = userdata; - CGroupBlockIODeviceWeight *w; - int r; - - assert(bus); - assert(reply); - assert(c); - - r = sd_bus_message_open_container(reply, 'a', "(st)"); - if (r < 0) - return r; - - LIST_FOREACH(device_weights, w, c->blockio_device_weights) { - r = sd_bus_message_append(reply, "(st)", w->path, w->weight); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_blockio_device_bandwidths( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - CGroupContext *c = userdata; - CGroupBlockIODeviceBandwidth *b; - int r; - - assert(bus); - assert(reply); - assert(c); - - r = sd_bus_message_open_container(reply, 'a', "(st)"); - if (r < 0) - return r; - - LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { - uint64_t v; - - if (streq(property, "BlockIOReadBandwidth")) - v = b->rbps; - else - v = b->wbps; - - if (v == CGROUP_LIMIT_MAX) - continue; - - r = sd_bus_message_append(reply, "(st)", b->path, v); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_device_allow( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - CGroupContext *c = userdata; - CGroupDeviceAllow *a; - int r; - - assert(bus); - assert(reply); - assert(c); - - r = sd_bus_message_open_container(reply, 'a', "(ss)"); - if (r < 0) - return r; - - LIST_FOREACH(device_allow, a, c->device_allow) { - unsigned k = 0; - char rwm[4]; - - if (a->r) - rwm[k++] = 'r'; - if (a->w) - rwm[k++] = 'w'; - if (a->m) - rwm[k++] = 'm'; - - rwm[k] = 0; - - r = sd_bus_message_append(reply, "(ss)", a->path, rwm); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -const sd_bus_vtable bus_cgroup_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Delegate", "b", bus_property_get_bool, offsetof(CGroupContext, delegate), 0), - SD_BUS_PROPERTY("CPUAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, cpu_accounting), 0), - SD_BUS_PROPERTY("CPUShares", "t", NULL, offsetof(CGroupContext, cpu_shares), 0), - SD_BUS_PROPERTY("StartupCPUShares", "t", NULL, offsetof(CGroupContext, startup_cpu_shares), 0), - SD_BUS_PROPERTY("CPUQuotaPerSecUSec", "t", bus_property_get_usec, offsetof(CGroupContext, cpu_quota_per_sec_usec), 0), - SD_BUS_PROPERTY("IOAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, io_accounting), 0), - SD_BUS_PROPERTY("IOWeight", "t", NULL, offsetof(CGroupContext, io_weight), 0), - SD_BUS_PROPERTY("StartupIOWeight", "t", NULL, offsetof(CGroupContext, startup_io_weight), 0), - SD_BUS_PROPERTY("IODeviceWeight", "a(st)", property_get_io_device_weight, 0, 0), - SD_BUS_PROPERTY("IOReadBandwidthMax", "a(st)", property_get_io_device_limits, 0, 0), - SD_BUS_PROPERTY("IOWriteBandwidthMax", "a(st)", property_get_io_device_limits, 0, 0), - SD_BUS_PROPERTY("IOReadIOPSMax", "a(st)", property_get_io_device_limits, 0, 0), - SD_BUS_PROPERTY("IOWriteIOPSMax", "a(st)", property_get_io_device_limits, 0, 0), - SD_BUS_PROPERTY("BlockIOAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, blockio_accounting), 0), - SD_BUS_PROPERTY("BlockIOWeight", "t", NULL, offsetof(CGroupContext, blockio_weight), 0), - SD_BUS_PROPERTY("StartupBlockIOWeight", "t", NULL, offsetof(CGroupContext, startup_blockio_weight), 0), - SD_BUS_PROPERTY("BlockIODeviceWeight", "a(st)", property_get_blockio_device_weight, 0, 0), - SD_BUS_PROPERTY("BlockIOReadBandwidth", "a(st)", property_get_blockio_device_bandwidths, 0, 0), - SD_BUS_PROPERTY("BlockIOWriteBandwidth", "a(st)", property_get_blockio_device_bandwidths, 0, 0), - SD_BUS_PROPERTY("MemoryAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, memory_accounting), 0), - SD_BUS_PROPERTY("MemoryLimit", "t", NULL, offsetof(CGroupContext, memory_limit), 0), - SD_BUS_PROPERTY("DevicePolicy", "s", property_get_cgroup_device_policy, offsetof(CGroupContext, device_policy), 0), - SD_BUS_PROPERTY("DeviceAllow", "a(ss)", property_get_device_allow, 0, 0), - SD_BUS_PROPERTY("TasksAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, tasks_accounting), 0), - SD_BUS_PROPERTY("TasksMax", "t", NULL, offsetof(CGroupContext, tasks_max), 0), - SD_BUS_VTABLE_END -}; - -static int bus_cgroup_set_transient_property( - Unit *u, - CGroupContext *c, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - int r; - - assert(u); - assert(c); - assert(name); - assert(message); - - if (streq(name, "Delegate")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->delegate = b; - unit_write_drop_in_private(u, mode, name, b ? "Delegate=yes" : "Delegate=no"); - } - - return 1; - } - - return 0; -} - -int bus_cgroup_set_property( - Unit *u, - CGroupContext *c, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - CGroupIOLimitType iol_type; - int r; - - assert(u); - assert(c); - assert(name); - assert(message); - - if (streq(name, "CPUAccounting")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->cpu_accounting = b; - unit_invalidate_cgroup(u, CGROUP_MASK_CPUACCT|CGROUP_MASK_CPU); - unit_write_drop_in_private(u, mode, name, b ? "CPUAccounting=yes" : "CPUAccounting=no"); - } - - return 1; - - } else if (streq(name, "CPUShares")) { - uint64_t shares; - - r = sd_bus_message_read(message, "t", &shares); - if (r < 0) - return r; - - if (!CGROUP_CPU_SHARES_IS_OK(shares)) - return sd_bus_error_set_errnof(error, EINVAL, "CPUShares value out of range"); - - if (mode != UNIT_CHECK) { - c->cpu_shares = shares; - unit_invalidate_cgroup(u, CGROUP_MASK_CPU); - - if (shares == CGROUP_CPU_SHARES_INVALID) - unit_write_drop_in_private(u, mode, name, "CPUShares="); - else - unit_write_drop_in_private_format(u, mode, name, "CPUShares=%" PRIu64, shares); - } - - return 1; - - } else if (streq(name, "StartupCPUShares")) { - uint64_t shares; - - r = sd_bus_message_read(message, "t", &shares); - if (r < 0) - return r; - - if (!CGROUP_CPU_SHARES_IS_OK(shares)) - return sd_bus_error_set_errnof(error, EINVAL, "StartupCPUShares value out of range"); - - if (mode != UNIT_CHECK) { - c->startup_cpu_shares = shares; - unit_invalidate_cgroup(u, CGROUP_MASK_CPU); - - if (shares == CGROUP_CPU_SHARES_INVALID) - unit_write_drop_in_private(u, mode, name, "StartupCPUShares="); - else - unit_write_drop_in_private_format(u, mode, name, "StartupCPUShares=%" PRIu64, shares); - } - - return 1; - - } else if (streq(name, "CPUQuotaPerSecUSec")) { - uint64_t u64; - - r = sd_bus_message_read(message, "t", &u64); - if (r < 0) - return r; - - if (u64 <= 0) - return sd_bus_error_set_errnof(error, EINVAL, "CPUQuotaPerSecUSec value out of range"); - - if (mode != UNIT_CHECK) { - c->cpu_quota_per_sec_usec = u64; - unit_invalidate_cgroup(u, CGROUP_MASK_CPU); - unit_write_drop_in_private_format(u, mode, "CPUQuota", "CPUQuota=%0.f%%", (double) (c->cpu_quota_per_sec_usec / 10000)); - } - - return 1; - - } else if (streq(name, "IOAccounting")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->io_accounting = b; - unit_invalidate_cgroup(u, CGROUP_MASK_IO); - unit_write_drop_in_private(u, mode, name, b ? "IOAccounting=yes" : "IOAccounting=no"); - } - - return 1; - - } else if (streq(name, "IOWeight")) { - uint64_t weight; - - r = sd_bus_message_read(message, "t", &weight); - if (r < 0) - return r; - - if (!CGROUP_WEIGHT_IS_OK(weight)) - return sd_bus_error_set_errnof(error, EINVAL, "IOWeight value out of range"); - - if (mode != UNIT_CHECK) { - c->io_weight = weight; - unit_invalidate_cgroup(u, CGROUP_MASK_IO); - - if (weight == CGROUP_WEIGHT_INVALID) - unit_write_drop_in_private(u, mode, name, "IOWeight="); - else - unit_write_drop_in_private_format(u, mode, name, "IOWeight=%" PRIu64, weight); - } - - return 1; - - } else if (streq(name, "StartupIOWeight")) { - uint64_t weight; - - r = sd_bus_message_read(message, "t", &weight); - if (r < 0) - return r; - - if (CGROUP_WEIGHT_IS_OK(weight)) - return sd_bus_error_set_errnof(error, EINVAL, "StartupIOWeight value out of range"); - - if (mode != UNIT_CHECK) { - c->startup_io_weight = weight; - unit_invalidate_cgroup(u, CGROUP_MASK_IO); - - if (weight == CGROUP_WEIGHT_INVALID) - unit_write_drop_in_private(u, mode, name, "StartupIOWeight="); - else - unit_write_drop_in_private_format(u, mode, name, "StartupIOWeight=%" PRIu64, weight); - } - - return 1; - - } else if ((iol_type = cgroup_io_limit_type_from_string(name)) >= 0) { - const char *path; - unsigned n = 0; - uint64_t u64; - - r = sd_bus_message_enter_container(message, 'a', "(st)"); - if (r < 0) - return r; - - while ((r = sd_bus_message_read(message, "(st)", &path, &u64)) > 0) { - - if (mode != UNIT_CHECK) { - CGroupIODeviceLimit *a = NULL, *b; - - LIST_FOREACH(device_limits, b, c->io_device_limits) { - if (path_equal(path, b->path)) { - a = b; - break; - } - } - - if (!a) { - CGroupIOLimitType type; - - a = new0(CGroupIODeviceLimit, 1); - if (!a) - return -ENOMEM; - - a->path = strdup(path); - if (!a->path) { - free(a); - return -ENOMEM; - } - - for (type = 0; type < _CGROUP_IO_LIMIT_TYPE_MAX; type++) - a->limits[type] = cgroup_io_limit_defaults[type]; - - LIST_PREPEND(device_limits, c->io_device_limits, a); - } - - a->limits[iol_type] = u64; - } - - n++; - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - CGroupIODeviceLimit *a; - _cleanup_free_ char *buf = NULL; - _cleanup_fclose_ FILE *f = NULL; - size_t size = 0; - - if (n == 0) { - LIST_FOREACH(device_limits, a, c->io_device_limits) - a->limits[iol_type] = cgroup_io_limit_defaults[iol_type]; - } - - unit_invalidate_cgroup(u, CGROUP_MASK_IO); - - f = open_memstream(&buf, &size); - if (!f) - return -ENOMEM; - - fprintf(f, "%s=\n", name); - LIST_FOREACH(device_limits, a, c->io_device_limits) - if (a->limits[iol_type] != cgroup_io_limit_defaults[iol_type]) - fprintf(f, "%s=%s %" PRIu64 "\n", name, a->path, a->limits[iol_type]); - - r = fflush_and_check(f); - if (r < 0) - return r; - unit_write_drop_in_private(u, mode, name, buf); - } - - return 1; - - } else if (streq(name, "IODeviceWeight")) { - const char *path; - uint64_t weight; - unsigned n = 0; - - r = sd_bus_message_enter_container(message, 'a', "(st)"); - if (r < 0) - return r; - - while ((r = sd_bus_message_read(message, "(st)", &path, &weight)) > 0) { - - if (!CGROUP_WEIGHT_IS_OK(weight) || weight == CGROUP_WEIGHT_INVALID) - return sd_bus_error_set_errnof(error, EINVAL, "IODeviceWeight out of range"); - - if (mode != UNIT_CHECK) { - CGroupIODeviceWeight *a = NULL, *b; - - LIST_FOREACH(device_weights, b, c->io_device_weights) { - if (path_equal(b->path, path)) { - a = b; - break; - } - } - - if (!a) { - a = new0(CGroupIODeviceWeight, 1); - if (!a) - return -ENOMEM; - - a->path = strdup(path); - if (!a->path) { - free(a); - return -ENOMEM; - } - LIST_PREPEND(device_weights,c->io_device_weights, a); - } - - a->weight = weight; - } - - n++; - } - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - _cleanup_free_ char *buf = NULL; - _cleanup_fclose_ FILE *f = NULL; - CGroupIODeviceWeight *a; - size_t size = 0; - - if (n == 0) { - while (c->io_device_weights) - cgroup_context_free_io_device_weight(c, c->io_device_weights); - } - - unit_invalidate_cgroup(u, CGROUP_MASK_IO); - - f = open_memstream(&buf, &size); - if (!f) - return -ENOMEM; - - fputs("IODeviceWeight=\n", f); - LIST_FOREACH(device_weights, a, c->io_device_weights) - fprintf(f, "IODeviceWeight=%s %" PRIu64 "\n", a->path, a->weight); - - r = fflush_and_check(f); - if (r < 0) - return r; - unit_write_drop_in_private(u, mode, name, buf); - } - - return 1; - - } else if (streq(name, "BlockIOAccounting")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->blockio_accounting = b; - unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); - unit_write_drop_in_private(u, mode, name, b ? "BlockIOAccounting=yes" : "BlockIOAccounting=no"); - } - - return 1; - - } else if (streq(name, "BlockIOWeight")) { - uint64_t weight; - - r = sd_bus_message_read(message, "t", &weight); - if (r < 0) - return r; - - if (!CGROUP_BLKIO_WEIGHT_IS_OK(weight)) - return sd_bus_error_set_errnof(error, EINVAL, "BlockIOWeight value out of range"); - - if (mode != UNIT_CHECK) { - c->blockio_weight = weight; - unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); - - if (weight == CGROUP_BLKIO_WEIGHT_INVALID) - unit_write_drop_in_private(u, mode, name, "BlockIOWeight="); - else - unit_write_drop_in_private_format(u, mode, name, "BlockIOWeight=%" PRIu64, weight); - } - - return 1; - - } else if (streq(name, "StartupBlockIOWeight")) { - uint64_t weight; - - r = sd_bus_message_read(message, "t", &weight); - if (r < 0) - return r; - - if (CGROUP_BLKIO_WEIGHT_IS_OK(weight)) - return sd_bus_error_set_errnof(error, EINVAL, "StartupBlockIOWeight value out of range"); - - if (mode != UNIT_CHECK) { - c->startup_blockio_weight = weight; - unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); - - if (weight == CGROUP_BLKIO_WEIGHT_INVALID) - unit_write_drop_in_private(u, mode, name, "StartupBlockIOWeight="); - else - unit_write_drop_in_private_format(u, mode, name, "StartupBlockIOWeight=%" PRIu64, weight); - } - - return 1; - - } else if (streq(name, "BlockIOReadBandwidth") || streq(name, "BlockIOWriteBandwidth")) { - const char *path; - bool read = true; - unsigned n = 0; - uint64_t u64; - - if (streq(name, "BlockIOWriteBandwidth")) - read = false; - - r = sd_bus_message_enter_container(message, 'a', "(st)"); - if (r < 0) - return r; - - while ((r = sd_bus_message_read(message, "(st)", &path, &u64)) > 0) { - - if (mode != UNIT_CHECK) { - CGroupBlockIODeviceBandwidth *a = NULL, *b; - - LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { - if (path_equal(path, b->path)) { - a = b; - break; - } - } - - if (!a) { - a = new0(CGroupBlockIODeviceBandwidth, 1); - if (!a) - return -ENOMEM; - - a->rbps = CGROUP_LIMIT_MAX; - a->wbps = CGROUP_LIMIT_MAX; - a->path = strdup(path); - if (!a->path) { - free(a); - return -ENOMEM; - } - - LIST_PREPEND(device_bandwidths, c->blockio_device_bandwidths, a); - } - - if (read) - a->rbps = u64; - else - a->wbps = u64; - } - - n++; - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - CGroupBlockIODeviceBandwidth *a; - _cleanup_free_ char *buf = NULL; - _cleanup_fclose_ FILE *f = NULL; - size_t size = 0; - - if (n == 0) { - LIST_FOREACH(device_bandwidths, a, c->blockio_device_bandwidths) { - if (read) - a->rbps = CGROUP_LIMIT_MAX; - else - a->wbps = CGROUP_LIMIT_MAX; - } - } - - unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); - - f = open_memstream(&buf, &size); - if (!f) - return -ENOMEM; - - if (read) { - fputs("BlockIOReadBandwidth=\n", f); - LIST_FOREACH(device_bandwidths, a, c->blockio_device_bandwidths) - if (a->rbps != CGROUP_LIMIT_MAX) - fprintf(f, "BlockIOReadBandwidth=%s %" PRIu64 "\n", a->path, a->rbps); - } else { - fputs("BlockIOWriteBandwidth=\n", f); - LIST_FOREACH(device_bandwidths, a, c->blockio_device_bandwidths) - if (a->wbps != CGROUP_LIMIT_MAX) - fprintf(f, "BlockIOWriteBandwidth=%s %" PRIu64 "\n", a->path, a->wbps); - } - - r = fflush_and_check(f); - if (r < 0) - return r; - unit_write_drop_in_private(u, mode, name, buf); - } - - return 1; - - } else if (streq(name, "BlockIODeviceWeight")) { - const char *path; - uint64_t weight; - unsigned n = 0; - - r = sd_bus_message_enter_container(message, 'a', "(st)"); - if (r < 0) - return r; - - while ((r = sd_bus_message_read(message, "(st)", &path, &weight)) > 0) { - - if (!CGROUP_BLKIO_WEIGHT_IS_OK(weight) || weight == CGROUP_BLKIO_WEIGHT_INVALID) - return sd_bus_error_set_errnof(error, EINVAL, "BlockIODeviceWeight out of range"); - - if (mode != UNIT_CHECK) { - CGroupBlockIODeviceWeight *a = NULL, *b; - - LIST_FOREACH(device_weights, b, c->blockio_device_weights) { - if (path_equal(b->path, path)) { - a = b; - break; - } - } - - if (!a) { - a = new0(CGroupBlockIODeviceWeight, 1); - if (!a) - return -ENOMEM; - - a->path = strdup(path); - if (!a->path) { - free(a); - return -ENOMEM; - } - LIST_PREPEND(device_weights,c->blockio_device_weights, a); - } - - a->weight = weight; - } - - n++; - } - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - _cleanup_free_ char *buf = NULL; - _cleanup_fclose_ FILE *f = NULL; - CGroupBlockIODeviceWeight *a; - size_t size = 0; - - if (n == 0) { - while (c->blockio_device_weights) - cgroup_context_free_blockio_device_weight(c, c->blockio_device_weights); - } - - unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); - - f = open_memstream(&buf, &size); - if (!f) - return -ENOMEM; - - fputs("BlockIODeviceWeight=\n", f); - LIST_FOREACH(device_weights, a, c->blockio_device_weights) - fprintf(f, "BlockIODeviceWeight=%s %" PRIu64 "\n", a->path, a->weight); - - r = fflush_and_check(f); - if (r < 0) - return r; - unit_write_drop_in_private(u, mode, name, buf); - } - - return 1; - - } else if (streq(name, "MemoryAccounting")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->memory_accounting = b; - unit_invalidate_cgroup(u, CGROUP_MASK_MEMORY); - unit_write_drop_in_private(u, mode, name, b ? "MemoryAccounting=yes" : "MemoryAccounting=no"); - } - - return 1; - - } else if (streq(name, "MemoryLimit")) { - uint64_t limit; - - r = sd_bus_message_read(message, "t", &limit); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->memory_limit = limit; - unit_invalidate_cgroup(u, CGROUP_MASK_MEMORY); - - if (limit == (uint64_t) -1) - unit_write_drop_in_private(u, mode, name, "MemoryLimit=infinity"); - else - unit_write_drop_in_private_format(u, mode, name, "MemoryLimit=%" PRIu64, limit); - } - - return 1; - - } else if (streq(name, "DevicePolicy")) { - const char *policy; - CGroupDevicePolicy p; - - r = sd_bus_message_read(message, "s", &policy); - if (r < 0) - return r; - - p = cgroup_device_policy_from_string(policy); - if (p < 0) - return -EINVAL; - - if (mode != UNIT_CHECK) { - char *buf; - - c->device_policy = p; - unit_invalidate_cgroup(u, CGROUP_MASK_DEVICES); - - buf = strjoina("DevicePolicy=", policy); - unit_write_drop_in_private(u, mode, name, buf); - } - - return 1; - - } else if (streq(name, "DeviceAllow")) { - const char *path, *rwm; - unsigned n = 0; - - r = sd_bus_message_enter_container(message, 'a', "(ss)"); - if (r < 0) - return r; - - while ((r = sd_bus_message_read(message, "(ss)", &path, &rwm)) > 0) { - - if ((!startswith(path, "/dev/") && - !startswith(path, "block-") && - !startswith(path, "char-")) || - strpbrk(path, WHITESPACE)) - return sd_bus_error_set_errnof(error, EINVAL, "DeviceAllow= requires device node"); - - if (isempty(rwm)) - rwm = "rwm"; - - if (!in_charset(rwm, "rwm")) - return sd_bus_error_set_errnof(error, EINVAL, "DeviceAllow= requires combination of rwm flags"); - - if (mode != UNIT_CHECK) { - CGroupDeviceAllow *a = NULL, *b; - - LIST_FOREACH(device_allow, b, c->device_allow) { - if (path_equal(b->path, path)) { - a = b; - break; - } - } - - if (!a) { - a = new0(CGroupDeviceAllow, 1); - if (!a) - return -ENOMEM; - - a->path = strdup(path); - if (!a->path) { - free(a); - return -ENOMEM; - } - - LIST_PREPEND(device_allow, c->device_allow, a); - } - - a->r = !!strchr(rwm, 'r'); - a->w = !!strchr(rwm, 'w'); - a->m = !!strchr(rwm, 'm'); - } - - n++; - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - _cleanup_free_ char *buf = NULL; - _cleanup_fclose_ FILE *f = NULL; - CGroupDeviceAllow *a; - size_t size = 0; - - if (n == 0) { - while (c->device_allow) - cgroup_context_free_device_allow(c, c->device_allow); - } - - unit_invalidate_cgroup(u, CGROUP_MASK_DEVICES); - - f = open_memstream(&buf, &size); - if (!f) - return -ENOMEM; - - fputs("DeviceAllow=\n", f); - LIST_FOREACH(device_allow, a, c->device_allow) - fprintf(f, "DeviceAllow=%s %s%s%s\n", a->path, a->r ? "r" : "", a->w ? "w" : "", a->m ? "m" : ""); - - r = fflush_and_check(f); - if (r < 0) - return r; - unit_write_drop_in_private(u, mode, name, buf); - } - - return 1; - - } else if (streq(name, "TasksAccounting")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->tasks_accounting = b; - unit_invalidate_cgroup(u, CGROUP_MASK_PIDS); - unit_write_drop_in_private(u, mode, name, b ? "TasksAccounting=yes" : "TasksAccounting=no"); - } - - return 1; - - } else if (streq(name, "TasksMax")) { - uint64_t limit; - - r = sd_bus_message_read(message, "t", &limit); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->tasks_max = limit; - unit_invalidate_cgroup(u, CGROUP_MASK_PIDS); - - if (limit == (uint64_t) -1) - unit_write_drop_in_private(u, mode, name, "TasksMax=infinity"); - else - unit_write_drop_in_private_format(u, mode, name, "TasksMax=%" PRIu64, limit); - } - - return 1; - } - - if (u->transient && u->load_state == UNIT_STUB) { - r = bus_cgroup_set_transient_property(u, c, name, message, mode, error); - if (r != 0) - return r; - - } - - return 0; -} diff --git a/src/core/dbus-cgroup.h b/src/core/dbus-cgroup.h deleted file mode 100644 index b2212fe44e..0000000000 --- a/src/core/dbus-cgroup.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -#include "cgroup.h" - -extern const sd_bus_vtable bus_cgroup_vtable[]; - -int bus_cgroup_set_property(Unit *u, CGroupContext *c, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); diff --git a/src/core/dbus-device.c b/src/core/dbus-device.c deleted file mode 100644 index e1a12224d3..0000000000 --- a/src/core/dbus-device.c +++ /dev/null @@ -1,28 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 "dbus-device.h" -#include "device.h" -#include "unit.h" - -const sd_bus_vtable bus_device_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("SysFSPath", "s", NULL, offsetof(Device, sysfs), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_VTABLE_END -}; diff --git a/src/core/dbus-device.h b/src/core/dbus-device.h deleted file mode 100644 index eb1d8c3278..0000000000 --- a/src/core/dbus-device.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "unit.h" - -extern const sd_bus_vtable bus_device_vtable[]; diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c deleted file mode 100644 index 06943c6365..0000000000 --- a/src/core/dbus-execute.c +++ /dev/null @@ -1,1545 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 - -#ifdef HAVE_SECCOMP -#include -#endif - -#include "af-list.h" -#include "alloc-util.h" -#include "bus-util.h" -#include "capability-util.h" -#include "dbus-execute.h" -#include "env-util.h" -#include "execute.h" -#include "fd-util.h" -#include "fileio.h" -#include "ioprio.h" -#include "missing.h" -#include "namespace.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "rlimit-util.h" -#ifdef HAVE_SECCOMP -#include "seccomp-util.h" -#endif -#include "strv.h" -#include "syslog-util.h" -#include "utf8.h" - -BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_exec_output, exec_output, ExecOutput); - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_input, exec_input, ExecInput); - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_utmp_mode, exec_utmp_mode, ExecUtmpMode); - -static BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_protect_home, protect_home, ProtectHome); -static BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_protect_system, protect_system, ProtectSystem); - -static int property_get_environment_files( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - char **j; - int r; - - assert(bus); - assert(reply); - assert(c); - - r = sd_bus_message_open_container(reply, 'a', "(sb)"); - if (r < 0) - return r; - - STRV_FOREACH(j, c->environment_files) { - const char *fn = *j; - - r = sd_bus_message_append(reply, "(sb)", fn[0] == '-' ? fn + 1 : fn, fn[0] == '-'); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_oom_score_adjust( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - - ExecContext *c = userdata; - int32_t n; - - assert(bus); - assert(reply); - assert(c); - - if (c->oom_score_adjust_set) - n = c->oom_score_adjust; - else { - _cleanup_free_ char *t = NULL; - - n = 0; - if (read_one_line_file("/proc/self/oom_score_adj", &t) >= 0) - safe_atoi32(t, &n); - } - - return sd_bus_message_append(reply, "i", n); -} - -static int property_get_nice( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - - ExecContext *c = userdata; - int32_t n; - - assert(bus); - assert(reply); - assert(c); - - if (c->nice_set) - n = c->nice; - else { - errno = 0; - n = getpriority(PRIO_PROCESS, 0); - if (errno > 0) - n = 0; - } - - return sd_bus_message_append(reply, "i", n); -} - -static int property_get_ioprio( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - - ExecContext *c = userdata; - int32_t n; - - assert(bus); - assert(reply); - assert(c); - - if (c->ioprio_set) - n = c->ioprio; - else { - n = ioprio_get(IOPRIO_WHO_PROCESS, 0); - if (n < 0) - n = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 4); - } - - return sd_bus_message_append(reply, "i", n); -} - -static int property_get_cpu_sched_policy( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - int32_t n; - - assert(bus); - assert(reply); - assert(c); - - if (c->cpu_sched_set) - n = c->cpu_sched_policy; - else { - n = sched_getscheduler(0); - if (n < 0) - n = SCHED_OTHER; - } - - return sd_bus_message_append(reply, "i", n); -} - -static int property_get_cpu_sched_priority( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - int32_t n; - - assert(bus); - assert(reply); - assert(c); - - if (c->cpu_sched_set) - n = c->cpu_sched_priority; - else { - struct sched_param p = {}; - - if (sched_getparam(0, &p) >= 0) - n = p.sched_priority; - else - n = 0; - } - - return sd_bus_message_append(reply, "i", n); -} - -static int property_get_cpu_affinity( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - if (c->cpuset) - return sd_bus_message_append_array(reply, 'y', c->cpuset, CPU_ALLOC_SIZE(c->cpuset_ncpus)); - else - return sd_bus_message_append_array(reply, 'y', NULL, 0); -} - -static int property_get_timer_slack_nsec( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - uint64_t u; - - assert(bus); - assert(reply); - assert(c); - - if (c->timer_slack_nsec != NSEC_INFINITY) - u = (uint64_t) c->timer_slack_nsec; - else - u = (uint64_t) prctl(PR_GET_TIMERSLACK); - - return sd_bus_message_append(reply, "t", u); -} - -static int property_get_capability_bounding_set( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - return sd_bus_message_append(reply, "t", c->capability_bounding_set); -} - -static int property_get_ambient_capabilities( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - return sd_bus_message_append(reply, "t", c->capability_ambient_set); -} - -static int property_get_empty_string( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - assert(bus); - assert(reply); - - return sd_bus_message_append(reply, "s", ""); -} - -static int property_get_syscall_filter( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - _cleanup_strv_free_ char **l = NULL; - int r; - -#ifdef HAVE_SECCOMP - Iterator i; - void *id; -#endif - - assert(bus); - assert(reply); - assert(c); - - r = sd_bus_message_open_container(reply, 'r', "bas"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "b", c->syscall_whitelist); - if (r < 0) - return r; - -#ifdef HAVE_SECCOMP - SET_FOREACH(id, c->syscall_filter, i) { - char *name; - - name = seccomp_syscall_resolve_num_arch(SCMP_ARCH_NATIVE, PTR_TO_INT(id) - 1); - if (!name) - continue; - - r = strv_consume(&l, name); - if (r < 0) - return r; - } -#endif - - strv_sort(l); - - r = sd_bus_message_append_strv(reply, l); - if (r < 0) - return r; - - return sd_bus_message_close_container(reply); -} - -static int property_get_syscall_archs( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - _cleanup_strv_free_ char **l = NULL; - int r; - -#ifdef HAVE_SECCOMP - Iterator i; - void *id; -#endif - - assert(bus); - assert(reply); - assert(c); - -#ifdef HAVE_SECCOMP - SET_FOREACH(id, c->syscall_archs, i) { - const char *name; - - name = seccomp_arch_to_string(PTR_TO_UINT32(id) - 1); - if (!name) - continue; - - r = strv_extend(&l, name); - if (r < 0) - return -ENOMEM; - } -#endif - - strv_sort(l); - - r = sd_bus_message_append_strv(reply, l); - if (r < 0) - return r; - - return 0; -} - -static int property_get_syscall_errno( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - return sd_bus_message_append(reply, "i", (int32_t) c->syscall_errno); -} - -static int property_get_selinux_context( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - return sd_bus_message_append(reply, "(bs)", c->selinux_context_ignore, c->selinux_context); -} - -static int property_get_apparmor_profile( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - return sd_bus_message_append(reply, "(bs)", c->apparmor_profile_ignore, c->apparmor_profile); -} - -static int property_get_smack_process_label( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - return sd_bus_message_append(reply, "(bs)", c->smack_process_label_ignore, c->smack_process_label); -} - -static int property_get_personality( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - return sd_bus_message_append(reply, "s", personality_to_string(c->personality)); -} - -static int property_get_address_families( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - _cleanup_strv_free_ char **l = NULL; - Iterator i; - void *af; - int r; - - assert(bus); - assert(reply); - assert(c); - - r = sd_bus_message_open_container(reply, 'r', "bas"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "b", c->address_families_whitelist); - if (r < 0) - return r; - - SET_FOREACH(af, c->address_families, i) { - const char *name; - - name = af_to_name(PTR_TO_INT(af)); - if (!name) - continue; - - r = strv_extend(&l, name); - if (r < 0) - return -ENOMEM; - } - - strv_sort(l); - - r = sd_bus_message_append_strv(reply, l); - if (r < 0) - return r; - - return sd_bus_message_close_container(reply); -} - -static int property_get_working_directory( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - const char *wd; - - assert(bus); - assert(reply); - assert(c); - - if (c->working_directory_home) - wd = "~"; - else - wd = c->working_directory; - - if (c->working_directory_missing_ok) - wd = strjoina("!", wd); - - return sd_bus_message_append(reply, "s", wd); -} - -static int property_get_syslog_level( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - return sd_bus_message_append(reply, "i", LOG_PRI(c->syslog_priority)); -} - -static int property_get_syslog_facility( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - return sd_bus_message_append(reply, "i", LOG_FAC(c->syslog_priority)); -} - -const sd_bus_vtable bus_exec_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("EnvironmentFiles", "a(sb)", property_get_environment_files, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PassEnvironment", "as", NULL, offsetof(ExecContext, pass_environment), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("UMask", "u", bus_property_get_mode, offsetof(ExecContext, umask), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitCPU", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitCPUSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitFSIZE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitFSIZESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitDATA", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitDATASoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitSTACK", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitSTACKSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitCORE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitCORESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitRSS", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitRSSSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitNOFILE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitNOFILESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitAS", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitASSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitNPROC", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitNPROCSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitMEMLOCK", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitMEMLOCKSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitLOCKS", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitLOCKSSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitSIGPENDING", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitSIGPENDINGSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitMSGQUEUE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitMSGQUEUESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitNICE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitNICESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitRTPRIO", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitRTPRIOSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitRTTIME", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitRTTIMESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("WorkingDirectory", "s", property_get_working_directory, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RootDirectory", "s", NULL, offsetof(ExecContext, root_directory), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("OOMScoreAdjust", "i", property_get_oom_score_adjust, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Nice", "i", property_get_nice, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("IOScheduling", "i", property_get_ioprio, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("CPUSchedulingPolicy", "i", property_get_cpu_sched_policy, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("CPUSchedulingPriority", "i", property_get_cpu_sched_priority, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("CPUAffinity", "ay", property_get_cpu_affinity, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TimerSlackNSec", "t", property_get_timer_slack_nsec, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("CPUSchedulingResetOnFork", "b", bus_property_get_bool, offsetof(ExecContext, cpu_sched_reset_on_fork), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("NonBlocking", "b", bus_property_get_bool, offsetof(ExecContext, non_blocking), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("StandardInput", "s", property_get_exec_input, offsetof(ExecContext, std_input), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("StandardOutput", "s", bus_property_get_exec_output, offsetof(ExecContext, std_output), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("StandardError", "s", bus_property_get_exec_output, offsetof(ExecContext, std_error), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TTYPath", "s", NULL, offsetof(ExecContext, tty_path), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TTYReset", "b", bus_property_get_bool, offsetof(ExecContext, tty_reset), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TTYVHangup", "b", bus_property_get_bool, offsetof(ExecContext, tty_vhangup), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TTYVTDisallocate", "b", bus_property_get_bool, offsetof(ExecContext, tty_vt_disallocate), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SyslogPriority", "i", bus_property_get_int, offsetof(ExecContext, syslog_priority), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SyslogIdentifier", "s", NULL, offsetof(ExecContext, syslog_identifier), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SyslogLevelPrefix", "b", bus_property_get_bool, offsetof(ExecContext, syslog_level_prefix), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SyslogLevel", "i", property_get_syslog_level, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SyslogFacility", "i", property_get_syslog_facility, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Capabilities", "s", property_get_empty_string, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("SecureBits", "i", bus_property_get_int, offsetof(ExecContext, secure_bits), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("CapabilityBoundingSet", "t", property_get_capability_bounding_set, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("AmbientCapabilities", "t", property_get_ambient_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("User", "s", NULL, offsetof(ExecContext, user), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Group", "s", NULL, offsetof(ExecContext, group), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PAMName", "s", NULL, offsetof(ExecContext, pam_name), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ReadWriteDirectories", "as", NULL, offsetof(ExecContext, read_write_dirs), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ReadOnlyDirectories", "as", NULL, offsetof(ExecContext, read_only_dirs), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("InaccessibleDirectories", "as", NULL, offsetof(ExecContext, inaccessible_dirs), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("MountFlags", "t", bus_property_get_ulong, offsetof(ExecContext, mount_flags), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PrivateTmp", "b", bus_property_get_bool, offsetof(ExecContext, private_tmp), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PrivateNetwork", "b", bus_property_get_bool, offsetof(ExecContext, private_network), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PrivateDevices", "b", bus_property_get_bool, offsetof(ExecContext, private_devices), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ProtectHome", "s", bus_property_get_protect_home, offsetof(ExecContext, protect_home), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ProtectSystem", "s", bus_property_get_protect_system, offsetof(ExecContext, protect_system), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SameProcessGroup", "b", bus_property_get_bool, offsetof(ExecContext, same_pgrp), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("UtmpIdentifier", "s", NULL, offsetof(ExecContext, utmp_id), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("UtmpMode", "s", property_get_exec_utmp_mode, offsetof(ExecContext, utmp_mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SELinuxContext", "(bs)", property_get_selinux_context, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("AppArmorProfile", "(bs)", property_get_apparmor_profile, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SmackProcessLabel", "(bs)", property_get_smack_process_label, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("IgnoreSIGPIPE", "b", bus_property_get_bool, offsetof(ExecContext, ignore_sigpipe), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("NoNewPrivileges", "b", bus_property_get_bool, offsetof(ExecContext, no_new_privileges), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SystemCallFilter", "(bas)", property_get_syscall_filter, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SystemCallArchitectures", "as", property_get_syscall_archs, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SystemCallErrorNumber", "i", property_get_syscall_errno, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Personality", "s", property_get_personality, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RestrictAddressFamilies", "(bas)", property_get_address_families, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RuntimeDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, runtime_directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RuntimeDirectory", "as", NULL, offsetof(ExecContext, runtime_directory), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_VTABLE_END -}; - -static int append_exec_command(sd_bus_message *reply, ExecCommand *c) { - int r; - - assert(reply); - assert(c); - - if (!c->path) - return 0; - - r = sd_bus_message_open_container(reply, 'r', "sasbttttuii"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "s", c->path); - if (r < 0) - return r; - - r = sd_bus_message_append_strv(reply, c->argv); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "bttttuii", - c->ignore, - c->exec_status.start_timestamp.realtime, - c->exec_status.start_timestamp.monotonic, - c->exec_status.exit_timestamp.realtime, - c->exec_status.exit_timestamp.monotonic, - (uint32_t) c->exec_status.pid, - (int32_t) c->exec_status.code, - (int32_t) c->exec_status.status); - if (r < 0) - return r; - - return sd_bus_message_close_container(reply); -} - -int bus_property_get_exec_command( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *ret_error) { - - ExecCommand *c = (ExecCommand*) userdata; - int r; - - assert(bus); - assert(reply); - - r = sd_bus_message_open_container(reply, 'a', "(sasbttttuii)"); - if (r < 0) - return r; - - r = append_exec_command(reply, c); - if (r < 0) - return r; - - return sd_bus_message_close_container(reply); -} - -int bus_property_get_exec_command_list( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *ret_error) { - - ExecCommand *c = *(ExecCommand**) userdata; - int r; - - assert(bus); - assert(reply); - - r = sd_bus_message_open_container(reply, 'a', "(sasbttttuii)"); - if (r < 0) - return r; - - LIST_FOREACH(command, c, c) { - r = append_exec_command(reply, c); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -int bus_exec_context_set_transient_property( - Unit *u, - ExecContext *c, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - const char *soft = NULL; - int r, ri; - - assert(u); - assert(c); - assert(name); - assert(message); - - if (streq(name, "User")) { - const char *uu; - - r = sd_bus_message_read(message, "s", &uu); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - - if (isempty(uu)) - c->user = mfree(c->user); - else if (free_and_strdup(&c->user, uu) < 0) - return -ENOMEM; - - unit_write_drop_in_private_format(u, mode, name, "User=%s\n", uu); - } - - return 1; - - } else if (streq(name, "Group")) { - const char *gg; - - r = sd_bus_message_read(message, "s", &gg); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - - if (isempty(gg)) - c->group = mfree(c->group); - else if (free_and_strdup(&c->group, gg) < 0) - return -ENOMEM; - - unit_write_drop_in_private_format(u, mode, name, "Group=%s\n", gg); - } - - return 1; - } else if (streq(name, "SyslogIdentifier")) { - const char *id; - - r = sd_bus_message_read(message, "s", &id); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - - if (isempty(id)) - c->syslog_identifier = mfree(c->syslog_identifier); - else if (free_and_strdup(&c->syslog_identifier, id) < 0) - return -ENOMEM; - - unit_write_drop_in_private_format(u, mode, name, "SyslogIdentifier=%s\n", id); - } - - return 1; - } else if (streq(name, "SyslogLevel")) { - int level; - - r = sd_bus_message_read(message, "i", &level); - if (r < 0) - return r; - - if (!log_level_is_valid(level)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Log level value out of range"); - - if (mode != UNIT_CHECK) { - c->syslog_priority = (c->syslog_priority & LOG_FACMASK) | level; - unit_write_drop_in_private_format(u, mode, name, "SyslogLevel=%i\n", level); - } - - return 1; - } else if (streq(name, "SyslogFacility")) { - int facility; - - r = sd_bus_message_read(message, "i", &facility); - if (r < 0) - return r; - - if (!log_facility_unshifted_is_valid(facility)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Log facility value out of range"); - - if (mode != UNIT_CHECK) { - c->syslog_priority = (facility << 3) | LOG_PRI(c->syslog_priority); - unit_write_drop_in_private_format(u, mode, name, "SyslogFacility=%i\n", facility); - } - - return 1; - } else if (streq(name, "Nice")) { - int n; - - r = sd_bus_message_read(message, "i", &n); - if (r < 0) - return r; - - if (n < PRIO_MIN || n >= PRIO_MAX) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Nice value out of range"); - - if (mode != UNIT_CHECK) { - c->nice = n; - unit_write_drop_in_private_format(u, mode, name, "Nice=%i\n", n); - } - - return 1; - - } else if (STR_IN_SET(name, "TTYPath", "RootDirectory")) { - const char *s; - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - if (!path_is_absolute(s)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "%s takes an absolute path", name); - - if (mode != UNIT_CHECK) { - if (streq(name, "TTYPath")) - r = free_and_strdup(&c->tty_path, s); - else { - assert(streq(name, "RootDirectory")); - r = free_and_strdup(&c->root_directory, s); - } - if (r < 0) - return r; - - unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, s); - } - - return 1; - - } else if (streq(name, "WorkingDirectory")) { - const char *s; - bool missing_ok; - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - if (s[0] == '-') { - missing_ok = true; - s++; - } else - missing_ok = false; - - if (!streq(s, "~") && !path_is_absolute(s)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "WorkingDirectory= expects an absolute path or '~'"); - - if (mode != UNIT_CHECK) { - if (streq(s, "~")) { - c->working_directory = mfree(c->working_directory); - c->working_directory_home = true; - } else { - r = free_and_strdup(&c->working_directory, s); - if (r < 0) - return r; - - c->working_directory_home = false; - } - - c->working_directory_missing_ok = missing_ok; - unit_write_drop_in_private_format(u, mode, name, "WorkingDirectory=%s%s", missing_ok ? "-" : "", s); - } - - return 1; - - } else if (streq(name, "StandardInput")) { - const char *s; - ExecInput p; - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - p = exec_input_from_string(s); - if (p < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid standard input name"); - - if (mode != UNIT_CHECK) { - c->std_input = p; - - unit_write_drop_in_private_format(u, mode, name, "StandardInput=%s\n", exec_input_to_string(p)); - } - - return 1; - - - } else if (streq(name, "StandardOutput")) { - const char *s; - ExecOutput p; - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - p = exec_output_from_string(s); - if (p < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid standard output name"); - - if (mode != UNIT_CHECK) { - c->std_output = p; - - unit_write_drop_in_private_format(u, mode, name, "StandardOutput=%s\n", exec_output_to_string(p)); - } - - return 1; - - } else if (streq(name, "StandardError")) { - const char *s; - ExecOutput p; - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - p = exec_output_from_string(s); - if (p < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid standard error name"); - - if (mode != UNIT_CHECK) { - c->std_error = p; - - unit_write_drop_in_private_format(u, mode, name, "StandardError=%s\n", exec_output_to_string(p)); - } - - return 1; - - } else if (STR_IN_SET(name, - "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", - "PrivateTmp", "PrivateDevices", "PrivateNetwork", - "NoNewPrivileges", "SyslogLevelPrefix")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - if (streq(name, "IgnoreSIGPIPE")) - c->ignore_sigpipe = b; - else if (streq(name, "TTYVHangup")) - c->tty_vhangup = b; - else if (streq(name, "TTYReset")) - c->tty_reset = b; - else if (streq(name, "PrivateTmp")) - c->private_tmp = b; - else if (streq(name, "PrivateDevices")) - c->private_devices = b; - else if (streq(name, "PrivateNetwork")) - c->private_network = b; - else if (streq(name, "NoNewPrivileges")) - c->no_new_privileges = b; - else if (streq(name, "SyslogLevelPrefix")) - c->syslog_level_prefix = b; - - unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, yes_no(b)); - } - - return 1; - - } else if (streq(name, "UtmpIdentifier")) { - const char *id; - - r = sd_bus_message_read(message, "s", &id); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - if (isempty(id)) - c->utmp_id = mfree(c->utmp_id); - else if (free_and_strdup(&c->utmp_id, id) < 0) - return -ENOMEM; - - unit_write_drop_in_private_format(u, mode, name, "UtmpIdentifier=%s\n", strempty(id)); - } - - return 1; - - } else if (streq(name, "UtmpMode")) { - const char *s; - ExecUtmpMode m; - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - m = exec_utmp_mode_from_string(s); - if (m < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid utmp mode"); - - if (mode != UNIT_CHECK) { - c->utmp_mode = m; - - unit_write_drop_in_private_format(u, mode, name, "UtmpMode=%s\n", exec_utmp_mode_to_string(m)); - } - - return 1; - - } else if (streq(name, "PAMName")) { - const char *n; - - r = sd_bus_message_read(message, "s", &n); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - if (isempty(n)) - c->pam_name = mfree(c->pam_name); - else if (free_and_strdup(&c->pam_name, n) < 0) - return -ENOMEM; - - unit_write_drop_in_private_format(u, mode, name, "PAMName=%s\n", strempty(n)); - } - - return 1; - - } else if (streq(name, "Environment")) { - - _cleanup_strv_free_ char **l = NULL; - - r = sd_bus_message_read_strv(message, &l); - if (r < 0) - return r; - - if (!strv_env_is_valid(l)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment block."); - - if (mode != UNIT_CHECK) { - _cleanup_free_ char *joined = NULL; - char **e; - - if (strv_length(l) == 0) { - c->environment = strv_free(c->environment); - unit_write_drop_in_private_format(u, mode, name, "Environment=\n"); - } else { - e = strv_env_merge(2, c->environment, l); - if (!e) - return -ENOMEM; - - strv_free(c->environment); - c->environment = e; - - joined = strv_join_quoted(c->environment); - if (!joined) - return -ENOMEM; - - unit_write_drop_in_private_format(u, mode, name, "Environment=%s\n", joined); - } - } - - return 1; - - } else if (streq(name, "TimerSlackNSec")) { - - nsec_t n; - - r = sd_bus_message_read(message, "t", &n); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->timer_slack_nsec = n; - unit_write_drop_in_private_format(u, mode, name, "TimerSlackNSec=" NSEC_FMT "\n", n); - } - - return 1; - - } else if (streq(name, "OOMScoreAdjust")) { - int oa; - - r = sd_bus_message_read(message, "i", &oa); - if (r < 0) - return r; - - if (!oom_score_adjust_is_valid(oa)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "OOM score adjust value out of range"); - - if (mode != UNIT_CHECK) { - c->oom_score_adjust = oa; - c->oom_score_adjust_set = true; - unit_write_drop_in_private_format(u, mode, name, "OOMScoreAdjust=%i\n", oa); - } - - return 1; - - } else if (streq(name, "EnvironmentFiles")) { - - _cleanup_free_ char *joined = NULL; - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char **l = NULL; - size_t size = 0; - char **i; - - r = sd_bus_message_enter_container(message, 'a', "(sb)"); - if (r < 0) - return r; - - f = open_memstream(&joined, &size); - if (!f) - return -ENOMEM; - - STRV_FOREACH(i, c->environment_files) - fprintf(f, "EnvironmentFile=%s\n", *i); - - while ((r = sd_bus_message_enter_container(message, 'r', "sb")) > 0) { - const char *path; - int b; - - r = sd_bus_message_read(message, "sb", &path, &b); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (!isempty(path) && !path_is_absolute(path)) - return sd_bus_error_set_errnof(error, EINVAL, "Path %s is not absolute.", path); - - if (mode != UNIT_CHECK) { - char *buf = NULL; - - buf = strjoin(b ? "-" : "", path, NULL); - if (!buf) - return -ENOMEM; - - fprintf(f, "EnvironmentFile=%s\n", buf); - - r = strv_consume(&l, buf); - if (r < 0) - return r; - } - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - r = fflush_and_check(f); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - if (strv_isempty(l)) { - c->environment_files = strv_free(c->environment_files); - unit_write_drop_in_private(u, mode, name, "EnvironmentFile=\n"); - } else { - r = strv_extend_strv(&c->environment_files, l, true); - if (r < 0) - return r; - - unit_write_drop_in_private(u, mode, name, joined); - } - } - - return 1; - - } else if (streq(name, "PassEnvironment")) { - - _cleanup_strv_free_ char **l = NULL; - - r = sd_bus_message_read_strv(message, &l); - if (r < 0) - return r; - - if (!strv_env_name_is_valid(l)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid PassEnvironment block."); - - if (mode != UNIT_CHECK) { - if (strv_isempty(l)) { - c->pass_environment = strv_free(c->pass_environment); - unit_write_drop_in_private_format(u, mode, name, "PassEnvironment=\n"); - } else { - _cleanup_free_ char *joined = NULL; - - r = strv_extend_strv(&c->pass_environment, l, true); - if (r < 0) - return r; - - joined = strv_join_quoted(c->pass_environment); - if (!joined) - return -ENOMEM; - - unit_write_drop_in_private_format(u, mode, name, "PassEnvironment=%s\n", joined); - } - } - - return 1; - - } else if (STR_IN_SET(name, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories")) { - - _cleanup_strv_free_ char **l = NULL; - char ***dirs; - char **p; - - r = sd_bus_message_read_strv(message, &l); - if (r < 0) - return r; - - STRV_FOREACH(p, l) { - int offset; - if (!utf8_is_valid(*p)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid %s", name); - - offset = **p == '-'; - if (!path_is_absolute(*p + offset)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid %s", name); - } - - if (mode != UNIT_CHECK) { - _cleanup_free_ char *joined = NULL; - - if (streq(name, "ReadWriteDirectories")) - dirs = &c->read_write_dirs; - else if (streq(name, "ReadOnlyDirectories")) - dirs = &c->read_only_dirs; - else /* "InaccessibleDirectories" */ - dirs = &c->inaccessible_dirs; - - if (strv_length(l) == 0) { - *dirs = strv_free(*dirs); - unit_write_drop_in_private_format(u, mode, name, "%s=\n", name); - } else { - r = strv_extend_strv(dirs, l, true); - - if (r < 0) - return -ENOMEM; - - joined = strv_join_quoted(*dirs); - if (!joined) - return -ENOMEM; - - unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, joined); - } - - } - - return 1; - - } else if (streq(name, "ProtectSystem")) { - const char *s; - ProtectSystem ps; - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - r = parse_boolean(s); - if (r > 0) - ps = PROTECT_SYSTEM_YES; - else if (r == 0) - ps = PROTECT_SYSTEM_NO; - else { - ps = protect_system_from_string(s); - if (ps < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse protect system value"); - } - - if (mode != UNIT_CHECK) { - c->protect_system = ps; - unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, s); - } - - return 1; - - } else if (streq(name, "ProtectHome")) { - const char *s; - ProtectHome ph; - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - r = parse_boolean(s); - if (r > 0) - ph = PROTECT_HOME_YES; - else if (r == 0) - ph = PROTECT_HOME_NO; - else { - ph = protect_home_from_string(s); - if (ph < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse protect home value"); - } - - if (mode != UNIT_CHECK) { - c->protect_home = ph; - unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, s); - } - - return 1; - - } else if (streq(name, "RuntimeDirectory")) { - _cleanup_strv_free_ char **l = NULL; - char **p; - - r = sd_bus_message_read_strv(message, &l); - if (r < 0) - return r; - - STRV_FOREACH(p, l) { - if (!filename_is_valid(*p)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Runtime directory is not valid %s", *p); - } - - if (mode != UNIT_CHECK) { - _cleanup_free_ char *joined = NULL; - - if (strv_isempty(l)) { - c->runtime_directory = strv_free(c->runtime_directory); - unit_write_drop_in_private_format(u, mode, name, "%s=\n", name); - } else { - r = strv_extend_strv(&c->runtime_directory, l, true); - - if (r < 0) - return -ENOMEM; - - joined = strv_join_quoted(c->runtime_directory); - if (!joined) - return -ENOMEM; - - unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, joined); - } - } - - return 1; - - } else if (streq(name, "SELinuxContext")) { - const char *s; - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - if (isempty(s)) - c->selinux_context = mfree(c->selinux_context); - else if (free_and_strdup(&c->selinux_context, s) < 0) - return -ENOMEM; - - unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, strempty(s)); - } - - return 1; - - } - - ri = rlimit_from_string(name); - if (ri < 0) { - soft = endswith(name, "Soft"); - if (soft) { - const char *n; - - n = strndupa(name, soft - name); - ri = rlimit_from_string(n); - if (ri >= 0) - name = n; - - } - } - - if (ri >= 0) { - uint64_t rl; - rlim_t x; - - r = sd_bus_message_read(message, "t", &rl); - if (r < 0) - return r; - - if (rl == (uint64_t) -1) - x = RLIM_INFINITY; - else { - x = (rlim_t) rl; - - if ((uint64_t) x != rl) - return -ERANGE; - } - - if (mode != UNIT_CHECK) { - _cleanup_free_ char *f = NULL; - struct rlimit nl; - - if (c->rlimit[ri]) { - nl = *c->rlimit[ri]; - - if (soft) - nl.rlim_cur = x; - else - nl.rlim_max = x; - } else - /* When the resource limit is not initialized yet, then assign the value to both fields */ - nl = (struct rlimit) { - .rlim_cur = x, - .rlim_max = x, - }; - - r = rlimit_format(&nl, &f); - if (r < 0) - return r; - - if (c->rlimit[ri]) - *c->rlimit[ri] = nl; - else { - c->rlimit[ri] = newdup(struct rlimit, &nl, 1); - if (!c->rlimit[ri]) - return -ENOMEM; - } - - unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, f); - } - - return 1; - } - - return 0; -} diff --git a/src/core/dbus-execute.h b/src/core/dbus-execute.h deleted file mode 100644 index d0aa8e1dd5..0000000000 --- a/src/core/dbus-execute.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "sd-bus.h" - -#include "execute.h" - -#define BUS_EXEC_STATUS_VTABLE(prefix, offset, flags) \ - BUS_PROPERTY_DUAL_TIMESTAMP(prefix "StartTimestamp", (offset) + offsetof(ExecStatus, start_timestamp), flags), \ - BUS_PROPERTY_DUAL_TIMESTAMP(prefix "ExitTimestamp", (offset) + offsetof(ExecStatus, exit_timestamp), flags), \ - SD_BUS_PROPERTY(prefix "PID", "u", bus_property_get_pid, (offset) + offsetof(ExecStatus, pid), flags), \ - SD_BUS_PROPERTY(prefix "Code", "i", bus_property_get_int, (offset) + offsetof(ExecStatus, code), flags), \ - SD_BUS_PROPERTY(prefix "Status", "i", bus_property_get_int, (offset) + offsetof(ExecStatus, status), flags) - -#define BUS_EXEC_COMMAND_VTABLE(name, offset, flags) \ - SD_BUS_PROPERTY(name, "a(sasbttttuii)", bus_property_get_exec_command, offset, flags) - -#define BUS_EXEC_COMMAND_LIST_VTABLE(name, offset, flags) \ - SD_BUS_PROPERTY(name, "a(sasbttttuii)", bus_property_get_exec_command_list, offset, flags) - -extern const sd_bus_vtable bus_exec_vtable[]; - -int bus_property_get_exec_output(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); -int bus_property_get_exec_command(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); -int bus_property_get_exec_command_list(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); - -int bus_exec_context_set_transient_property(Unit *u, ExecContext *c, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); diff --git a/src/core/dbus-job.c b/src/core/dbus-job.c deleted file mode 100644 index ccf7453d47..0000000000 --- a/src/core/dbus-job.c +++ /dev/null @@ -1,193 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 "sd-bus.h" - -#include "alloc-util.h" -#include "dbus-job.h" -#include "dbus.h" -#include "job.h" -#include "log.h" -#include "selinux-access.h" -#include "string-util.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, job_type, JobType); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_state, job_state, JobState); - -static int property_get_unit( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - _cleanup_free_ char *p = NULL; - Job *j = userdata; - - assert(bus); - assert(reply); - assert(j); - - p = unit_dbus_path(j->unit); - if (!p) - return -ENOMEM; - - return sd_bus_message_append(reply, "(so)", j->unit->id, p); -} - -int bus_job_method_cancel(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Job *j = userdata; - int r; - - assert(message); - assert(j); - - r = mac_selinux_unit_access_check(j->unit, message, "stop", error); - if (r < 0) - return r; - - /* Access is granted to the job owner */ - if (!sd_bus_track_contains(j->clients, sd_bus_message_get_sender(message))) { - - /* And for everybody else consult PolicyKit */ - r = bus_verify_manage_units_async(j->unit->manager, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - } - - job_finish_and_invalidate(j, JOB_CANCELED, true, false); - - return sd_bus_reply_method_return(message, NULL); -} - -const sd_bus_vtable bus_job_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_METHOD("Cancel", NULL, NULL, bus_job_method_cancel, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_PROPERTY("Id", "u", NULL, offsetof(Job, id), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Unit", "(so)", property_get_unit, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("JobType", "s", property_get_type, offsetof(Job, type), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("State", "s", property_get_state, offsetof(Job, state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_VTABLE_END -}; - -static int send_new_signal(sd_bus *bus, void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *p = NULL; - Job *j = userdata; - int r; - - assert(bus); - assert(j); - - p = job_dbus_path(j); - if (!p) - return -ENOMEM; - - r = sd_bus_message_new_signal( - bus, - &m, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "JobNew"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "uos", j->id, p, j->unit->id); - if (r < 0) - return r; - - return sd_bus_send(bus, m, NULL); -} - -static int send_changed_signal(sd_bus *bus, void *userdata) { - _cleanup_free_ char *p = NULL; - Job *j = userdata; - - assert(bus); - assert(j); - - p = job_dbus_path(j); - if (!p) - return -ENOMEM; - - return sd_bus_emit_properties_changed(bus, p, "org.freedesktop.systemd1.Job", "State", NULL); -} - -void bus_job_send_change_signal(Job *j) { - int r; - - assert(j); - - if (j->in_dbus_queue) { - LIST_REMOVE(dbus_queue, j->manager->dbus_job_queue, j); - j->in_dbus_queue = false; - } - - r = bus_foreach_bus(j->manager, j->clients, j->sent_dbus_new_signal ? send_changed_signal : send_new_signal, j); - if (r < 0) - log_debug_errno(r, "Failed to send job change signal for %u: %m", j->id); - - j->sent_dbus_new_signal = true; -} - -static int send_removed_signal(sd_bus *bus, void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *p = NULL; - Job *j = userdata; - int r; - - assert(bus); - assert(j); - - p = job_dbus_path(j); - if (!p) - return -ENOMEM; - - r = sd_bus_message_new_signal( - bus, - &m, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "JobRemoved"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "uoss", j->id, p, j->unit->id, job_result_to_string(j->result)); - if (r < 0) - return r; - - return sd_bus_send(bus, m, NULL); -} - -void bus_job_send_removed_signal(Job *j) { - int r; - - assert(j); - - if (!j->sent_dbus_new_signal) - bus_job_send_change_signal(j); - - r = bus_foreach_bus(j->manager, j->clients, send_removed_signal, j); - if (r < 0) - log_debug_errno(r, "Failed to send job remove signal for %u: %m", j->id); -} diff --git a/src/core/dbus-job.h b/src/core/dbus-job.h deleted file mode 100644 index 024d06719e..0000000000 --- a/src/core/dbus-job.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "sd-bus.h" - -#include "job.h" - -extern const sd_bus_vtable bus_job_vtable[]; - -int bus_job_method_cancel(sd_bus_message *message, void *job, sd_bus_error *error); - -void bus_job_send_change_signal(Job *j); -void bus_job_send_removed_signal(Job *j); diff --git a/src/core/dbus-kill.c b/src/core/dbus-kill.c deleted file mode 100644 index 0f54c6b84b..0000000000 --- a/src/core/dbus-kill.c +++ /dev/null @@ -1,122 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 "bus-util.h" -#include "dbus-kill.h" -#include "kill.h" -#include "signal-util.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_kill_mode, kill_mode, KillMode); - -const sd_bus_vtable bus_kill_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("KillMode", "s", property_get_kill_mode, offsetof(KillContext, kill_mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("KillSignal", "i", bus_property_get_int, offsetof(KillContext, kill_signal), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SendSIGKILL", "b", bus_property_get_bool, offsetof(KillContext, send_sigkill), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SendSIGHUP", "b", bus_property_get_bool, offsetof(KillContext, send_sighup), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_VTABLE_END -}; - -int bus_kill_context_set_transient_property( - Unit *u, - KillContext *c, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - int r; - - assert(u); - assert(c); - assert(name); - assert(message); - - if (streq(name, "KillMode")) { - const char *m; - KillMode k; - - r = sd_bus_message_read(message, "s", &m); - if (r < 0) - return r; - - k = kill_mode_from_string(m); - if (k < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Kill mode '%s' not known.", m); - - if (mode != UNIT_CHECK) { - c->kill_mode = k; - - unit_write_drop_in_private_format(u, mode, name, "KillMode=%s\n", kill_mode_to_string(k)); - } - - return 1; - - } else if (streq(name, "KillSignal")) { - int sig; - - r = sd_bus_message_read(message, "i", &sig); - if (r < 0) - return r; - - if (!SIGNAL_VALID(sig)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Signal %i out of range", sig); - - if (mode != UNIT_CHECK) { - c->kill_signal = sig; - - unit_write_drop_in_private_format(u, mode, name, "KillSignal=%s\n", signal_to_string(sig)); - } - - return 1; - - } else if (streq(name, "SendSIGHUP")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->send_sighup = b; - - unit_write_drop_in_private_format(u, mode, name, "SendSIGHUP=%s\n", yes_no(b)); - } - - return 1; - - } else if (streq(name, "SendSIGKILL")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->send_sigkill = b; - - unit_write_drop_in_private_format(u, mode, name, "SendSIGKILL=%s\n", yes_no(b)); - } - - return 1; - - } - - return 0; -} diff --git a/src/core/dbus-kill.h b/src/core/dbus-kill.h deleted file mode 100644 index b9b18811e3..0000000000 --- a/src/core/dbus-kill.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 "sd-bus.h" - -#include "kill.h" -#include "unit.h" - -extern const sd_bus_vtable bus_kill_vtable[]; - -int bus_kill_context_set_transient_property(Unit *u, KillContext *c, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c deleted file mode 100644 index 86722e1162..0000000000 --- a/src/core/dbus-manager.c +++ /dev/null @@ -1,2305 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include - -#include "alloc-util.h" -#include "architecture.h" -#include "build.h" -#include "bus-common-errors.h" -#include "clock-util.h" -#include "dbus-execute.h" -#include "dbus-job.h" -#include "dbus-manager.h" -#include "dbus-unit.h" -#include "dbus.h" -#include "env-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "install.h" -#include "log.h" -#include "path-util.h" -#include "selinux-access.h" -#include "stat-util.h" -#include "string-util.h" -#include "strv.h" -#include "syslog-util.h" -#include "virt.h" -#include "watchdog.h" - -static int property_get_version( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - assert(bus); - assert(reply); - - return sd_bus_message_append(reply, "s", PACKAGE_VERSION); -} - -static int property_get_features( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - assert(bus); - assert(reply); - - return sd_bus_message_append(reply, "s", SYSTEMD_FEATURES); -} - -static int property_get_virtualization( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - int v; - - assert(bus); - assert(reply); - - v = detect_virtualization(); - - /* Make sure to return the empty string when we detect no virtualization, as that is the API. - * - * https://github.com/systemd/systemd/issues/1423 - */ - - return sd_bus_message_append( - reply, "s", - v == VIRTUALIZATION_NONE ? "" : virtualization_to_string(v)); -} - -static int property_get_architecture( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - assert(bus); - assert(reply); - - return sd_bus_message_append(reply, "s", architecture_to_string(uname_architecture())); -} - -static int property_get_tainted( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - char buf[sizeof("split-usr:cgroups-missing:local-hwclock:")] = "", *e = buf; - Manager *m = userdata; - - assert(bus); - assert(reply); - assert(m); - - if (m->taint_usr) - e = stpcpy(e, "split-usr:"); - - if (access("/proc/cgroups", F_OK) < 0) - e = stpcpy(e, "cgroups-missing:"); - - if (clock_is_localtime(NULL) > 0) - e = stpcpy(e, "local-hwclock:"); - - /* remove the last ':' */ - if (e != buf) - e[-1] = 0; - - return sd_bus_message_append(reply, "s", buf); -} - -static int property_get_log_target( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - assert(bus); - assert(reply); - - return sd_bus_message_append(reply, "s", log_target_to_string(log_get_target())); -} - -static int property_set_log_target( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *value, - void *userdata, - sd_bus_error *error) { - - const char *t; - int r; - - assert(bus); - assert(value); - - r = sd_bus_message_read(value, "s", &t); - if (r < 0) - return r; - - return log_set_target_from_string(t); -} - -static int property_get_log_level( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - _cleanup_free_ char *t = NULL; - int r; - - assert(bus); - assert(reply); - - r = log_level_to_string_alloc(log_get_max_level(), &t); - if (r < 0) - return r; - - return sd_bus_message_append(reply, "s", t); -} - -static int property_set_log_level( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *value, - void *userdata, - sd_bus_error *error) { - - const char *t; - int r; - - assert(bus); - assert(value); - - r = sd_bus_message_read(value, "s", &t); - if (r < 0) - return r; - - r = log_set_max_level_from_string(t); - if (r == 0) - log_info("Setting log level to %s.", t); - return r; -} - -static int property_get_n_names( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Manager *m = userdata; - - assert(bus); - assert(reply); - assert(m); - - return sd_bus_message_append(reply, "u", (uint32_t) hashmap_size(m->units)); -} - -static int property_get_n_failed_units( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Manager *m = userdata; - - assert(bus); - assert(reply); - assert(m); - - return sd_bus_message_append(reply, "u", (uint32_t) set_size(m->failed_units)); -} - -static int property_get_n_jobs( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Manager *m = userdata; - - assert(bus); - assert(reply); - assert(m); - - return sd_bus_message_append(reply, "u", (uint32_t) hashmap_size(m->jobs)); -} - -static int property_get_progress( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Manager *m = userdata; - double d; - - assert(bus); - assert(reply); - assert(m); - - if (dual_timestamp_is_set(&m->finish_timestamp)) - d = 1.0; - else - d = 1.0 - ((double) hashmap_size(m->jobs) / (double) m->n_installed_jobs); - - return sd_bus_message_append(reply, "d", d); -} - -static int property_get_system_state( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Manager *m = userdata; - - assert(bus); - assert(reply); - assert(m); - - return sd_bus_message_append(reply, "s", manager_state_to_string(manager_state(m))); -} - -static int property_set_runtime_watchdog( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *value, - void *userdata, - sd_bus_error *error) { - - usec_t *t = userdata; - int r; - - assert(bus); - assert(value); - - assert_cc(sizeof(usec_t) == sizeof(uint64_t)); - - r = sd_bus_message_read(value, "t", t); - if (r < 0) - return r; - - return watchdog_set_timeout(t); -} - -static int property_get_timer_slack_nsec( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - assert(bus); - assert(reply); - - return sd_bus_message_append(reply, "t", (uint64_t) prctl(PR_GET_TIMERSLACK)); -} - -static int method_get_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *path = NULL; - Manager *m = userdata; - const char *name; - Unit *u; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - if (isempty(name)) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - pid_t pid; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_pid(creds, &pid); - if (r < 0) - return r; - - u = manager_get_unit_by_pid(m, pid); - if (!u) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Client not member of any unit."); - } else { - u = manager_get_unit(m, name); - if (!u) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s not loaded.", name); - } - - r = mac_selinux_unit_access_check(u, message, "status", error); - if (r < 0) - return r; - - path = unit_dbus_path(u); - if (!path) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", path); -} - -static int method_get_unit_by_pid(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *path = NULL; - Manager *m = userdata; - pid_t pid; - Unit *u; - int r; - - assert(message); - assert(m); - - assert_cc(sizeof(pid_t) == sizeof(uint32_t)); - - /* Anyone can call this method */ - - r = sd_bus_message_read(message, "u", &pid); - if (r < 0) - return r; - if (pid < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid PID " PID_FMT, pid); - - if (pid == 0) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_pid(creds, &pid); - if (r < 0) - return r; - } - - u = manager_get_unit_by_pid(m, pid); - if (!u) - return sd_bus_error_setf(error, BUS_ERROR_NO_UNIT_FOR_PID, "PID "PID_FMT" does not belong to any loaded unit.", pid); - - r = mac_selinux_unit_access_check(u, message, "status", error); - if (r < 0) - return r; - - path = unit_dbus_path(u); - if (!path) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", path); -} - -static int method_load_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *path = NULL; - Manager *m = userdata; - const char *name; - Unit *u; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - if (isempty(name)) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - pid_t pid; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_pid(creds, &pid); - if (r < 0) - return r; - - u = manager_get_unit_by_pid(m, pid); - if (!u) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Client not member of any unit."); - } else { - r = manager_load_unit(m, name, NULL, error, &u); - if (r < 0) - return r; - } - - r = mac_selinux_unit_access_check(u, message, "status", error); - if (r < 0) - return r; - - path = unit_dbus_path(u); - if (!path) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", path); -} - -static int method_start_unit_generic(sd_bus_message *message, Manager *m, JobType job_type, bool reload_if_possible, sd_bus_error *error) { - const char *name; - Unit *u; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - r = manager_load_unit(m, name, NULL, error, &u); - if (r < 0) - return r; - - return bus_unit_method_start_generic(message, u, job_type, reload_if_possible, error); -} - -static int method_start_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_start_unit_generic(message, userdata, JOB_START, false, error); -} - -static int method_stop_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_start_unit_generic(message, userdata, JOB_STOP, false, error); -} - -static int method_reload_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_start_unit_generic(message, userdata, JOB_RELOAD, false, error); -} - -static int method_restart_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_start_unit_generic(message, userdata, JOB_RESTART, false, error); -} - -static int method_try_restart_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_start_unit_generic(message, userdata, JOB_TRY_RESTART, false, error); -} - -static int method_reload_or_restart_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_start_unit_generic(message, userdata, JOB_RESTART, true, error); -} - -static int method_reload_or_try_restart_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_start_unit_generic(message, userdata, JOB_TRY_RESTART, true, error); -} - -static int method_start_unit_replace(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - const char *old_name; - Unit *u; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &old_name); - if (r < 0) - return r; - - u = manager_get_unit(m, old_name); - if (!u || !u->job || u->job->type != JOB_START) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_JOB, "No job queued for unit %s", old_name); - - return method_start_unit_generic(message, m, JOB_START, false, error); -} - -static int method_kill_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - const char *name; - Unit *u; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - u = manager_get_unit(m, name); - if (!u) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); - - return bus_unit_method_kill(message, u, error); -} - -static int method_reset_failed_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - const char *name; - Unit *u; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - u = manager_get_unit(m, name); - if (!u) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); - - return bus_unit_method_reset_failed(message, u, error); -} - -static int method_set_unit_properties(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - const char *name; - Unit *u; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - r = manager_load_unit(m, name, NULL, error, &u); - if (r < 0) - return r; - - r = bus_unit_check_load_state(u, error); - if (r < 0) - return r; - - return bus_unit_method_set_properties(message, u, error); -} - -static int reply_unit_info(sd_bus_message *reply, Unit *u) { - _cleanup_free_ char *unit_path = NULL, *job_path = NULL; - Unit *following; - - following = unit_following(u); - - unit_path = unit_dbus_path(u); - if (!unit_path) - return -ENOMEM; - - if (u->job) { - job_path = job_dbus_path(u->job); - if (!job_path) - return -ENOMEM; - } - - return sd_bus_message_append( - reply, "(ssssssouso)", - u->id, - unit_description(u), - unit_load_state_to_string(u->load_state), - unit_active_state_to_string(unit_active_state(u)), - unit_sub_state_to_string(u), - following ? following->id : "", - unit_path, - u->job ? u->job->id : 0, - u->job ? job_type_to_string(u->job->type) : "", - job_path ? job_path : "/"); -} - -static int method_list_units_by_names(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - Manager *m = userdata; - int r; - char **unit; - _cleanup_strv_free_ char **units = NULL; - - assert(message); - assert(m); - - r = sd_bus_message_read_strv(message, &units); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(ssssssouso)"); - if (r < 0) - return r; - - STRV_FOREACH(unit, units) { - Unit *u; - - if (!unit_name_is_valid(*unit, UNIT_NAME_ANY)) - continue; - - r = manager_load_unit(m, *unit, NULL, error, &u); - if (r < 0) - return r; - - r = reply_unit_info(reply, u); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -static int method_get_unit_processes(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - const char *name; - Unit *u; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - r = manager_load_unit(m, name, NULL, error, &u); - if (r < 0) - return r; - - r = bus_unit_check_load_state(u, error); - if (r < 0) - return r; - - return bus_unit_method_get_processes(message, u, error); -} - -static int transient_unit_from_message( - Manager *m, - sd_bus_message *message, - const char *name, - Unit **unit, - sd_bus_error *error) { - - UnitType t; - Unit *u; - int r; - - assert(m); - assert(message); - assert(name); - - t = unit_name_to_type(name); - if (t < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid unit name or type."); - - if (!unit_vtable[t]->can_transient) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit type %s does not support transient units.", unit_type_to_string(t)); - - r = manager_load_unit(m, name, NULL, error, &u); - if (r < 0) - return r; - - if (!unit_is_pristine(u)) - return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, "Unit %s already exists.", name); - - /* OK, the unit failed to load and is unreferenced, now let's - * fill in the transient data instead */ - r = unit_make_transient(u); - if (r < 0) - return r; - - /* Set our properties */ - r = bus_unit_set_properties(u, message, UNIT_RUNTIME, false, error); - if (r < 0) - return r; - - /* Now load the missing bits of the unit we just created */ - manager_dispatch_load_queue(m); - - *unit = u; - - return 0; -} - -static int transient_aux_units_from_message( - Manager *m, - sd_bus_message *message, - sd_bus_error *error) { - - int r; - - assert(m); - assert(message); - - r = sd_bus_message_enter_container(message, 'a', "(sa(sv))"); - if (r < 0) - return r; - - while ((r = sd_bus_message_enter_container(message, 'r', "sa(sv)")) > 0) { - const char *name = NULL; - Unit *u; - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - r = transient_unit_from_message(m, message, name, &u, error); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - return 0; -} - -static int method_start_transient_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - const char *name, *smode; - Manager *m = userdata; - JobMode mode; - Unit *u; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "start", error); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "ss", &name, &smode); - if (r < 0) - return r; - - mode = job_mode_from_string(smode); - if (mode < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Job mode %s is invalid.", smode); - - r = bus_verify_manage_units_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = transient_unit_from_message(m, message, name, &u, error); - if (r < 0) - return r; - - r = transient_aux_units_from_message(m, message, error); - if (r < 0) - return r; - - /* Finally, start it */ - return bus_unit_queue_job(message, u, JOB_START, mode, false, error); -} - -static int method_get_job(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *path = NULL; - Manager *m = userdata; - uint32_t id; - Job *j; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = sd_bus_message_read(message, "u", &id); - if (r < 0) - return r; - - j = manager_get_job(m, id); - if (!j) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_JOB, "Job %u does not exist.", (unsigned) id); - - r = mac_selinux_unit_access_check(j->unit, message, "status", error); - if (r < 0) - return r; - - path = job_dbus_path(j); - if (!path) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", path); -} - -static int method_cancel_job(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - uint32_t id; - Job *j; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "u", &id); - if (r < 0) - return r; - - j = manager_get_job(m, id); - if (!j) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_JOB, "Job %u does not exist.", (unsigned) id); - - return bus_job_method_cancel(message, j, error); -} - -static int method_clear_jobs(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reload", error); - if (r < 0) - return r; - - r = bus_verify_manage_units_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - manager_clear_jobs(m); - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_reset_failed(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reload", error); - if (r < 0) - return r; - - r = bus_verify_manage_units_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - manager_reset_failed(m); - - return sd_bus_reply_method_return(message, NULL); -} - -static int list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_error *error, char **states, char **patterns) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - Manager *m = userdata; - const char *k; - Iterator i; - Unit *u; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = mac_selinux_access_check(message, "status", error); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(ssssssouso)"); - if (r < 0) - return r; - - HASHMAP_FOREACH_KEY(u, k, m->units, i) { - if (k != u->id) - continue; - - if (!strv_isempty(states) && - !strv_contains(states, unit_load_state_to_string(u->load_state)) && - !strv_contains(states, unit_active_state_to_string(unit_active_state(u))) && - !strv_contains(states, unit_sub_state_to_string(u))) - continue; - - if (!strv_isempty(patterns) && - !strv_fnmatch_or_empty(patterns, u->id, FNM_NOESCAPE)) - continue; - - r = reply_unit_info(reply, u); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -static int method_list_units(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return list_units_filtered(message, userdata, error, NULL, NULL); -} - -static int method_list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_strv_free_ char **states = NULL; - int r; - - r = sd_bus_message_read_strv(message, &states); - if (r < 0) - return r; - - return list_units_filtered(message, userdata, error, states, NULL); -} - -static int method_list_units_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_strv_free_ char **states = NULL; - _cleanup_strv_free_ char **patterns = NULL; - int r; - - r = sd_bus_message_read_strv(message, &states); - if (r < 0) - return r; - - r = sd_bus_message_read_strv(message, &patterns); - if (r < 0) - return r; - - return list_units_filtered(message, userdata, error, states, patterns); -} - -static int method_list_jobs(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - Manager *m = userdata; - Iterator i; - Job *j; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = mac_selinux_access_check(message, "status", error); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(usssoo)"); - if (r < 0) - return r; - - HASHMAP_FOREACH(j, m->jobs, i) { - _cleanup_free_ char *unit_path = NULL, *job_path = NULL; - - job_path = job_dbus_path(j); - if (!job_path) - return -ENOMEM; - - unit_path = unit_dbus_path(j->unit); - if (!unit_path) - return -ENOMEM; - - r = sd_bus_message_append( - reply, "(usssoo)", - j->id, - j->unit->id, - job_type_to_string(j->type), - job_state_to_string(j->state), - job_path, - unit_path); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -static int method_subscribe(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = mac_selinux_access_check(message, "status", error); - if (r < 0) - return r; - - if (sd_bus_message_get_bus(message) == m->api_bus) { - - /* Note that direct bus connection subscribe by - * default, we only track peers on the API bus here */ - - if (!m->subscribed) { - r = sd_bus_track_new(sd_bus_message_get_bus(message), &m->subscribed, NULL, NULL); - if (r < 0) - return r; - } - - r = sd_bus_track_add_sender(m->subscribed, message); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_ALREADY_SUBSCRIBED, "Client is already subscribed."); - } - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_unsubscribe(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = mac_selinux_access_check(message, "status", error); - if (r < 0) - return r; - - if (sd_bus_message_get_bus(message) == m->api_bus) { - r = sd_bus_track_remove_sender(m->subscribed, message); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NOT_SUBSCRIBED, "Client is not subscribed."); - } - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_dump(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *dump = NULL; - _cleanup_fclose_ FILE *f = NULL; - Manager *m = userdata; - size_t size; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = mac_selinux_access_check(message, "status", error); - if (r < 0) - return r; - - f = open_memstream(&dump, &size); - if (!f) - return -ENOMEM; - - manager_dump_units(m, f, NULL); - manager_dump_jobs(m, f, NULL); - - r = fflush_and_check(f); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, "s", dump); -} - -static int method_refuse_snapshot(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Support for snapshots has been removed."); -} - -static int method_reload(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reload", error); - if (r < 0) - return r; - - r = bus_verify_reload_daemon_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - /* Instead of sending the reply back right away, we just - * remember that we need to and then send it after the reload - * is finished. That way the caller knows when the reload - * finished. */ - - assert(!m->queued_message); - r = sd_bus_message_new_method_return(message, &m->queued_message); - if (r < 0) - return r; - - m->exit_code = MANAGER_RELOAD; - - return 1; -} - -static int method_reexecute(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reload", error); - if (r < 0) - return r; - - r = bus_verify_reload_daemon_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - /* We don't send a reply back here, the client should - * just wait for us disconnecting. */ - - m->exit_code = MANAGER_REEXECUTE; - return 1; -} - -static int method_exit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "halt", error); - if (r < 0) - return r; - - /* Exit() (in contrast to SetExitCode()) is actually allowed even if - * we are running on the host. It will fall back on reboot() in - * systemd-shutdown if it cannot do the exit() because it isn't a - * container. */ - - m->exit_code = MANAGER_EXIT; - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_reboot(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reboot", error); - if (r < 0) - return r; - - if (!MANAGER_IS_SYSTEM(m)) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Reboot is only supported for system managers."); - - m->exit_code = MANAGER_REBOOT; - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_poweroff(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "halt", error); - if (r < 0) - return r; - - if (!MANAGER_IS_SYSTEM(m)) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Powering off is only supported for system managers."); - - m->exit_code = MANAGER_POWEROFF; - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_halt(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "halt", error); - if (r < 0) - return r; - - if (!MANAGER_IS_SYSTEM(m)) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Halt is only supported for system managers."); - - m->exit_code = MANAGER_HALT; - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_kexec(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reboot", error); - if (r < 0) - return r; - - if (!MANAGER_IS_SYSTEM(m)) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "KExec is only supported for system managers."); - - m->exit_code = MANAGER_KEXEC; - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_switch_root(sd_bus_message *message, void *userdata, sd_bus_error *error) { - char *ri = NULL, *rt = NULL; - const char *root, *init; - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reboot", error); - if (r < 0) - return r; - - if (!MANAGER_IS_SYSTEM(m)) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Root switching is only supported by system manager."); - - r = sd_bus_message_read(message, "ss", &root, &init); - if (r < 0) - return r; - - if (path_equal(root, "/") || !path_is_absolute(root)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid switch root path %s", root); - - /* Safety check */ - if (isempty(init)) { - if (!path_is_os_tree(root)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified switch root path %s does not seem to be an OS tree. os-release file is missing.", root); - } else { - _cleanup_free_ char *p = NULL; - - if (!path_is_absolute(init)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid init path %s", init); - - p = strappend(root, init); - if (!p) - return -ENOMEM; - - if (access(p, X_OK) < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified init binary %s does not exist.", p); - } - - rt = strdup(root); - if (!rt) - return -ENOMEM; - - if (!isempty(init)) { - ri = strdup(init); - if (!ri) { - free(rt); - return -ENOMEM; - } - } - - free(m->switch_root); - m->switch_root = rt; - - free(m->switch_root_init); - m->switch_root_init = ri; - - m->exit_code = MANAGER_SWITCH_ROOT; - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_set_environment(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_strv_free_ char **plus = NULL; - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reload", error); - if (r < 0) - return r; - - r = sd_bus_message_read_strv(message, &plus); - if (r < 0) - return r; - if (!strv_env_is_valid(plus)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments"); - - r = bus_verify_set_environment_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = manager_environment_add(m, NULL, plus); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_unset_environment(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_strv_free_ char **minus = NULL; - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reload", error); - if (r < 0) - return r; - - r = sd_bus_message_read_strv(message, &minus); - if (r < 0) - return r; - - if (!strv_env_name_or_assignment_is_valid(minus)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment variable names or assignments"); - - r = bus_verify_set_environment_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = manager_environment_add(m, minus, NULL); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_unset_and_set_environment(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_strv_free_ char **minus = NULL, **plus = NULL; - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reload", error); - if (r < 0) - return r; - - r = sd_bus_message_read_strv(message, &minus); - if (r < 0) - return r; - - r = sd_bus_message_read_strv(message, &plus); - if (r < 0) - return r; - - if (!strv_env_name_or_assignment_is_valid(minus)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment variable names or assignments"); - if (!strv_env_is_valid(plus)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments"); - - r = bus_verify_set_environment_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = manager_environment_add(m, minus, plus); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_set_exit_code(sd_bus_message *message, void *userdata, sd_bus_error *error) { - uint8_t code; - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "exit", error); - if (r < 0) - return r; - - r = sd_bus_message_read_basic(message, 'y', &code); - if (r < 0) - return r; - - if (MANAGER_IS_SYSTEM(m) && detect_container() <= 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "ExitCode can only be set for user service managers or in containers."); - - m->return_value = code; - - return sd_bus_reply_method_return(message, NULL); -} - -static int list_unit_files_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error, char **states, char **patterns) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - Manager *m = userdata; - UnitFileList *item; - Hashmap *h; - Iterator i; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = mac_selinux_access_check(message, "status", error); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - h = hashmap_new(&string_hash_ops); - if (!h) - return -ENOMEM; - - r = unit_file_get_list(m->unit_file_scope, NULL, h, states, patterns); - if (r < 0) - goto fail; - - r = sd_bus_message_open_container(reply, 'a', "(ss)"); - if (r < 0) - goto fail; - - HASHMAP_FOREACH(item, h, i) { - - r = sd_bus_message_append(reply, "(ss)", item->path, unit_file_state_to_string(item->state)); - if (r < 0) - goto fail; - } - - unit_file_list_free(h); - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); - -fail: - unit_file_list_free(h); - return r; -} - -static int method_list_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return list_unit_files_by_patterns(message, userdata, error, NULL, NULL); -} - -static int method_list_unit_files_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_strv_free_ char **states = NULL; - _cleanup_strv_free_ char **patterns = NULL; - int r; - - r = sd_bus_message_read_strv(message, &states); - if (r < 0) - return r; - - r = sd_bus_message_read_strv(message, &patterns); - if (r < 0) - return r; - - return list_unit_files_by_patterns(message, userdata, error, states, patterns); -} - -static int method_get_unit_file_state(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - const char *name; - UnitFileState state; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = mac_selinux_access_check(message, "status", error); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - r = unit_file_get_state(m->unit_file_scope, NULL, name, &state); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, "s", unit_file_state_to_string(state)); -} - -static int method_get_default_target(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *default_target = NULL; - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = mac_selinux_access_check(message, "status", error); - if (r < 0) - return r; - - r = unit_file_get_default(m->unit_file_scope, NULL, &default_target); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, "s", default_target); -} - -static int send_unit_files_changed(sd_bus *bus, void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *message = NULL; - int r; - - assert(bus); - - r = sd_bus_message_new_signal(bus, &message, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitFilesChanged"); - if (r < 0) - return r; - - return sd_bus_send(bus, message, NULL); -} - -static int reply_unit_file_changes_and_free( - Manager *m, - sd_bus_message *message, - int carries_install_info, - UnitFileChange *changes, - unsigned n_changes) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - unsigned i; - int r; - - if (unit_file_changes_have_modification(changes, n_changes)) { - r = bus_foreach_bus(m, NULL, send_unit_files_changed, NULL); - if (r < 0) - log_debug_errno(r, "Failed to send UnitFilesChanged signal: %m"); - } - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - goto fail; - - if (carries_install_info >= 0) { - r = sd_bus_message_append(reply, "b", carries_install_info); - if (r < 0) - goto fail; - } - - r = sd_bus_message_open_container(reply, 'a', "(sss)"); - if (r < 0) - goto fail; - - for (i = 0; i < n_changes; i++) - if (changes[i].type >= 0) { - const char *change = unit_file_change_type_to_string(changes[i].type); - assert(change != NULL); - - r = sd_bus_message_append( - reply, "(sss)", - change, - changes[i].path, - changes[i].source); - if (r < 0) - goto fail; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto fail; - - unit_file_changes_free(changes, n_changes); - return sd_bus_send(NULL, reply, NULL); - -fail: - unit_file_changes_free(changes, n_changes); - return r; -} - -/* Create an error reply, using the error information from changes[] - * if possible, and fall back to generating an error from error code c. - * The error message only describes the first error. - * - * Coordinate with unit_file_dump_changes() in install.c. - */ -static int install_error( - sd_bus_error *error, - int c, - UnitFileChange *changes, - unsigned n_changes) { - int r; - unsigned i; - assert(c < 0); - - for (i = 0; i < n_changes; i++) - switch(changes[i].type) { - case 0 ... INT_MAX: - continue; - case -EEXIST: - if (changes[i].source) - r = sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, - "File %s already exists and is a symlink to %s.", - changes[i].path, changes[i].source); - else - r = sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, - "File %s already exists.", - changes[i].path); - goto found; - case -ERFKILL: - r = sd_bus_error_setf(error, BUS_ERROR_UNIT_MASKED, - "Unit file %s is masked.", changes[i].path); - goto found; - case -EADDRNOTAVAIL: - r = sd_bus_error_setf(error, BUS_ERROR_UNIT_GENERATED, - "Unit %s is transient or generated.", changes[i].path); - goto found; - case -ELOOP: - r = sd_bus_error_setf(error, BUS_ERROR_UNIT_LINKED, - "Refusing to operate on linked unit file %s", changes[i].path); - goto found; - default: - r = sd_bus_error_set_errnof(error, changes[i].type, "File %s: %m", changes[i].path); - goto found; - } - - r = c; - found: - unit_file_changes_free(changes, n_changes); - return r; -} - -static int method_enable_unit_files_generic( - sd_bus_message *message, - Manager *m, - int (*call)(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes), - bool carries_install_info, - sd_bus_error *error) { - - _cleanup_strv_free_ char **l = NULL; - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - int runtime, force, r; - - assert(message); - assert(m); - - r = sd_bus_message_read_strv(message, &l); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "bb", &runtime, &force); - if (r < 0) - return r; - - r = bus_verify_manage_unit_files_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = call(m->unit_file_scope, runtime, NULL, l, force, &changes, &n_changes); - if (r < 0) - return install_error(error, r, changes, n_changes); - - return reply_unit_file_changes_and_free(m, message, carries_install_info ? r : -1, changes, n_changes); -} - -static int method_enable_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_enable_unit_files_generic(message, userdata, unit_file_enable, true, error); -} - -static int method_reenable_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_enable_unit_files_generic(message, userdata, unit_file_reenable, true, error); -} - -static int method_link_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_enable_unit_files_generic(message, userdata, unit_file_link, false, error); -} - -static int unit_file_preset_without_mode(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes) { - return unit_file_preset(scope, runtime, root_dir, files, UNIT_FILE_PRESET_FULL, force, changes, n_changes); -} - -static int method_preset_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_enable_unit_files_generic(message, userdata, unit_file_preset_without_mode, true, error); -} - -static int method_mask_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_enable_unit_files_generic(message, userdata, unit_file_mask, false, error); -} - -static int method_preset_unit_files_with_mode(sd_bus_message *message, void *userdata, sd_bus_error *error) { - - _cleanup_strv_free_ char **l = NULL; - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - Manager *m = userdata; - UnitFilePresetMode mm; - int runtime, force, r; - const char *mode; - - assert(message); - assert(m); - - r = sd_bus_message_read_strv(message, &l); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "sbb", &mode, &runtime, &force); - if (r < 0) - return r; - - if (isempty(mode)) - mm = UNIT_FILE_PRESET_FULL; - else { - mm = unit_file_preset_mode_from_string(mode); - if (mm < 0) - return -EINVAL; - } - - r = bus_verify_manage_unit_files_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = unit_file_preset(m->unit_file_scope, runtime, NULL, l, mm, force, &changes, &n_changes); - if (r < 0) - return install_error(error, r, changes, n_changes); - - return reply_unit_file_changes_and_free(m, message, r, changes, n_changes); -} - -static int method_disable_unit_files_generic( - sd_bus_message *message, - Manager *m, - int (*call)(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], UnitFileChange **changes, unsigned *n_changes), - sd_bus_error *error) { - - _cleanup_strv_free_ char **l = NULL; - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - int r, runtime; - - assert(message); - assert(m); - - r = sd_bus_message_read_strv(message, &l); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "b", &runtime); - if (r < 0) - return r; - - r = bus_verify_manage_unit_files_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = call(m->unit_file_scope, runtime, NULL, l, &changes, &n_changes); - if (r < 0) - return install_error(error, r, changes, n_changes); - - return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); -} - -static int method_disable_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_disable_unit_files_generic(message, userdata, unit_file_disable, error); -} - -static int method_unmask_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_disable_unit_files_generic(message, userdata, unit_file_unmask, error); -} - -static int method_revert_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_strv_free_ char **l = NULL; - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read_strv(message, &l); - if (r < 0) - return r; - - r = bus_verify_manage_unit_files_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = unit_file_revert(m->unit_file_scope, NULL, l, &changes, &n_changes); - if (r < 0) - return install_error(error, r, changes, n_changes); - - return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); -} - -static int method_set_default_target(sd_bus_message *message, void *userdata, sd_bus_error *error) { - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - Manager *m = userdata; - const char *name; - int force, r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "enable", error); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "sb", &name, &force); - if (r < 0) - return r; - - r = bus_verify_manage_unit_files_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = unit_file_set_default(m->unit_file_scope, NULL, name, force, &changes, &n_changes); - if (r < 0) - return install_error(error, r, changes, n_changes); - - return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); -} - -static int method_preset_all_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - Manager *m = userdata; - UnitFilePresetMode mm; - const char *mode; - int force, runtime, r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "enable", error); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "sbb", &mode, &runtime, &force); - if (r < 0) - return r; - - if (isempty(mode)) - mm = UNIT_FILE_PRESET_FULL; - else { - mm = unit_file_preset_mode_from_string(mode); - if (mm < 0) - return -EINVAL; - } - - r = bus_verify_manage_unit_files_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = unit_file_preset_all(m->unit_file_scope, runtime, NULL, mm, force, &changes, &n_changes); - if (r < 0) - return install_error(error, r, changes, n_changes); - - return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); -} - -static int method_add_dependency_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_strv_free_ char **l = NULL; - Manager *m = userdata; - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - int runtime, force, r; - char *target, *type; - UnitDependency dep; - - assert(message); - assert(m); - - r = bus_verify_manage_unit_files_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = sd_bus_message_read_strv(message, &l); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "ssbb", &target, &type, &runtime, &force); - if (r < 0) - return r; - - dep = unit_dependency_from_string(type); - if (dep < 0) - return -EINVAL; - - r = unit_file_add_dependency(m->unit_file_scope, runtime, NULL, l, target, dep, force, &changes, &n_changes); - if (r < 0) - return install_error(error, r, changes, n_changes); - - return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); -} - -const sd_bus_vtable bus_manager_vtable[] = { - SD_BUS_VTABLE_START(0), - - SD_BUS_PROPERTY("Version", "s", property_get_version, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Features", "s", property_get_features, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Virtualization", "s", property_get_virtualization, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Architecture", "s", property_get_architecture, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Tainted", "s", property_get_tainted, 0, SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("FirmwareTimestamp", offsetof(Manager, firmware_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("LoaderTimestamp", offsetof(Manager, loader_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("KernelTimestamp", offsetof(Manager, kernel_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("InitRDTimestamp", offsetof(Manager, initrd_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("UserspaceTimestamp", offsetof(Manager, userspace_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("FinishTimestamp", offsetof(Manager, finish_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("SecurityStartTimestamp", offsetof(Manager, security_start_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("SecurityFinishTimestamp", offsetof(Manager, security_finish_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("GeneratorsStartTimestamp", offsetof(Manager, generators_start_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("GeneratorsFinishTimestamp", offsetof(Manager, generators_finish_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("UnitsLoadStartTimestamp", offsetof(Manager, units_load_start_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("UnitsLoadFinishTimestamp", offsetof(Manager, units_load_finish_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_WRITABLE_PROPERTY("LogLevel", "s", property_get_log_level, property_set_log_level, 0, 0), - SD_BUS_WRITABLE_PROPERTY("LogTarget", "s", property_get_log_target, property_set_log_target, 0, 0), - SD_BUS_PROPERTY("NNames", "u", property_get_n_names, 0, 0), - SD_BUS_PROPERTY("NFailedUnits", "u", property_get_n_failed_units, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("NJobs", "u", property_get_n_jobs, 0, 0), - SD_BUS_PROPERTY("NInstalledJobs", "u", bus_property_get_unsigned, offsetof(Manager, n_installed_jobs), 0), - SD_BUS_PROPERTY("NFailedJobs", "u", bus_property_get_unsigned, offsetof(Manager, n_failed_jobs), 0), - SD_BUS_PROPERTY("Progress", "d", property_get_progress, 0, 0), - SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(Manager, environment), 0), - SD_BUS_PROPERTY("ConfirmSpawn", "b", bus_property_get_bool, offsetof(Manager, confirm_spawn), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ShowStatus", "b", bus_property_get_bool, offsetof(Manager, show_status), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("UnitPath", "as", NULL, offsetof(Manager, lookup_paths.search_path), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultStandardOutput", "s", bus_property_get_exec_output, offsetof(Manager, default_std_output), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultStandardError", "s", bus_property_get_exec_output, offsetof(Manager, default_std_output), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_WRITABLE_PROPERTY("RuntimeWatchdogUSec", "t", bus_property_get_usec, property_set_runtime_watchdog, offsetof(Manager, runtime_watchdog), 0), - SD_BUS_WRITABLE_PROPERTY("ShutdownWatchdogUSec", "t", bus_property_get_usec, bus_property_set_usec, offsetof(Manager, shutdown_watchdog), 0), - SD_BUS_PROPERTY("ControlGroup", "s", NULL, offsetof(Manager, cgroup_root), 0), - SD_BUS_PROPERTY("SystemState", "s", property_get_system_state, 0, 0), - SD_BUS_PROPERTY("ExitCode", "y", bus_property_get_unsigned, offsetof(Manager, return_value), 0), - SD_BUS_PROPERTY("DefaultTimerAccuracyUSec", "t", bus_property_get_usec, offsetof(Manager, default_timer_accuracy_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultTimeoutStartUSec", "t", bus_property_get_usec, offsetof(Manager, default_timeout_start_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultTimeoutStopUSec", "t", bus_property_get_usec, offsetof(Manager, default_timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultRestartUSec", "t", bus_property_get_usec, offsetof(Manager, default_restart_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultStartLimitIntervalSec", "t", bus_property_get_usec, offsetof(Manager, default_start_limit_interval), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultStartLimitInterval", "t", bus_property_get_usec, offsetof(Manager, default_start_limit_interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), /* obsolete alias name */ - SD_BUS_PROPERTY("DefaultStartLimitBurst", "u", bus_property_get_unsigned, offsetof(Manager, default_start_limit_burst), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultCPUAccounting", "b", bus_property_get_bool, offsetof(Manager, default_cpu_accounting), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultBlockIOAccounting", "b", bus_property_get_bool, offsetof(Manager, default_blockio_accounting), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultMemoryAccounting", "b", bus_property_get_bool, offsetof(Manager, default_memory_accounting), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultTasksAccounting", "b", bus_property_get_bool, offsetof(Manager, default_tasks_accounting), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitCPU", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitCPUSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitFSIZE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitFSIZESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitDATA", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitDATASoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitSTACK", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitSTACKSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitCORE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitCORESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitRSS", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitRSSSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitNOFILE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitNOFILESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitAS", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitASSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitNPROC", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitNPROCSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitMEMLOCK", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitMEMLOCKSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitLOCKS", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitLOCKSSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitSIGPENDING", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitSIGPENDINGSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitMSGQUEUE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitMSGQUEUESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitNICE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitNICESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitRTPRIO", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitRTPRIOSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitRTTIME", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitRTTIMESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultTasksMax", "t", NULL, offsetof(Manager, default_tasks_max), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TimerSlackNSec", "t", property_get_timer_slack_nsec, 0, SD_BUS_VTABLE_PROPERTY_CONST), - - SD_BUS_METHOD("GetUnit", "s", "o", method_get_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetUnitByPID", "u", "o", method_get_unit_by_pid, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("LoadUnit", "s", "o", method_load_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("StartUnit", "ss", "o", method_start_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("StartUnitReplace", "sss", "o", method_start_unit_replace, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("StopUnit", "ss", "o", method_stop_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ReloadUnit", "ss", "o", method_reload_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("RestartUnit", "ss", "o", method_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("TryRestartUnit", "ss", "o", method_try_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ReloadOrRestartUnit", "ss", "o", method_reload_or_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ReloadOrTryRestartUnit", "ss", "o", method_reload_or_try_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("KillUnit", "ssi", NULL, method_kill_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ResetFailedUnit", "s", NULL, method_reset_failed_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetUnitProperties", "sba(sv)", NULL, method_set_unit_properties, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("StartTransientUnit", "ssa(sv)a(sa(sv))", "o", method_start_transient_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetUnitProcesses", "s", "a(sus)", method_get_unit_processes, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetJob", "u", "o", method_get_job, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CancelJob", "u", NULL, method_cancel_job, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ClearJobs", NULL, NULL, method_clear_jobs, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ResetFailed", NULL, NULL, method_reset_failed, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListUnits", NULL, "a(ssssssouso)", method_list_units, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListUnitsFiltered", "as", "a(ssssssouso)", method_list_units_filtered, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListUnitsByPatterns", "asas", "a(ssssssouso)", method_list_units_by_patterns, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListUnitsByNames", "as", "a(ssssssouso)", method_list_units_by_names, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListJobs", NULL, "a(usssoo)", method_list_jobs, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Subscribe", NULL, NULL, method_subscribe, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Unsubscribe", NULL, NULL, method_unsubscribe, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Dump", NULL, "s", method_dump, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CreateSnapshot", "sb", "o", method_refuse_snapshot, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("RemoveSnapshot", "s", NULL, method_refuse_snapshot, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Reload", NULL, NULL, method_reload, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Reexecute", NULL, NULL, method_reexecute, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Exit", NULL, NULL, method_exit, 0), - SD_BUS_METHOD("Reboot", NULL, NULL, method_reboot, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), - SD_BUS_METHOD("PowerOff", NULL, NULL, method_poweroff, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), - SD_BUS_METHOD("Halt", NULL, NULL, method_halt, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), - SD_BUS_METHOD("KExec", NULL, NULL, method_kexec, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), - SD_BUS_METHOD("SwitchRoot", "ss", NULL, method_switch_root, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), - SD_BUS_METHOD("SetEnvironment", "as", NULL, method_set_environment, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("UnsetEnvironment", "as", NULL, method_unset_environment, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("UnsetAndSetEnvironment", "asas", NULL, method_unset_and_set_environment, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListUnitFiles", NULL, "a(ss)", method_list_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListUnitFilesByPatterns", "asas", "a(ss)", method_list_unit_files_by_patterns, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetUnitFileState", "s", "s", method_get_unit_file_state, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("EnableUnitFiles", "asbb", "ba(sss)", method_enable_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("DisableUnitFiles", "asb", "a(sss)", method_disable_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ReenableUnitFiles", "asbb", "ba(sss)", method_reenable_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("LinkUnitFiles", "asbb", "a(sss)", method_link_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("PresetUnitFiles", "asbb", "ba(sss)", method_preset_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("PresetUnitFilesWithMode", "assbb", "ba(sss)", method_preset_unit_files_with_mode, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MaskUnitFiles", "asbb", "a(sss)", method_mask_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("UnmaskUnitFiles", "asb", "a(sss)", method_unmask_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("RevertUnitFiles", "as", "a(sss)", method_revert_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetDefaultTarget", "sb", "a(sss)", method_set_default_target, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetDefaultTarget", NULL, "s", method_get_default_target, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("PresetAllUnitFiles", "sbb", "a(sss)", method_preset_all_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("AddDependencyUnitFiles", "asssbb", "a(sss)", method_add_dependency_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetExitCode", "y", NULL, method_set_exit_code, SD_BUS_VTABLE_UNPRIVILEGED), - - SD_BUS_SIGNAL("UnitNew", "so", 0), - SD_BUS_SIGNAL("UnitRemoved", "so", 0), - SD_BUS_SIGNAL("JobNew", "uos", 0), - SD_BUS_SIGNAL("JobRemoved", "uoss", 0), - SD_BUS_SIGNAL("StartupFinished", "tttttt", 0), - SD_BUS_SIGNAL("UnitFilesChanged", NULL, 0), - SD_BUS_SIGNAL("Reloading", "b", 0), - - SD_BUS_VTABLE_END -}; - -static int send_finished(sd_bus *bus, void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *message = NULL; - usec_t *times = userdata; - int r; - - assert(bus); - assert(times); - - r = sd_bus_message_new_signal(bus, &message, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartupFinished"); - if (r < 0) - return r; - - r = sd_bus_message_append(message, "tttttt", times[0], times[1], times[2], times[3], times[4], times[5]); - if (r < 0) - return r; - - return sd_bus_send(bus, message, NULL); -} - -void bus_manager_send_finished( - Manager *m, - usec_t firmware_usec, - usec_t loader_usec, - usec_t kernel_usec, - usec_t initrd_usec, - usec_t userspace_usec, - usec_t total_usec) { - - int r; - - assert(m); - - r = bus_foreach_bus( - m, - NULL, - send_finished, - (usec_t[6]) { - firmware_usec, - loader_usec, - kernel_usec, - initrd_usec, - userspace_usec, - total_usec - }); - if (r < 0) - log_debug_errno(r, "Failed to send finished signal: %m"); -} - -static int send_reloading(sd_bus *bus, void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *message = NULL; - int r; - - assert(bus); - - r = sd_bus_message_new_signal(bus, &message, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "Reloading"); - if (r < 0) - return r; - - r = sd_bus_message_append(message, "b", PTR_TO_INT(userdata)); - if (r < 0) - return r; - - return sd_bus_send(bus, message, NULL); -} - -void bus_manager_send_reloading(Manager *m, bool active) { - int r; - - assert(m); - - r = bus_foreach_bus(m, NULL, send_reloading, INT_TO_PTR(active)); - if (r < 0) - log_debug_errno(r, "Failed to send reloading signal: %m"); -} - -static int send_changed_signal(sd_bus *bus, void *userdata) { - assert(bus); - - return sd_bus_emit_properties_changed_strv(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - NULL); -} - -void bus_manager_send_change_signal(Manager *m) { - int r; - - assert(m); - - r = bus_foreach_bus(m, NULL, send_changed_signal, NULL); - if (r < 0) - log_debug_errno(r, "Failed to send manager change signal: %m"); -} diff --git a/src/core/dbus-manager.h b/src/core/dbus-manager.h deleted file mode 100644 index 36a2e9481b..0000000000 --- a/src/core/dbus-manager.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "manager.h" - -extern const sd_bus_vtable bus_manager_vtable[]; - -void bus_manager_send_finished(Manager *m, usec_t firmware_usec, usec_t loader_usec, usec_t kernel_usec, usec_t initrd_usec, usec_t userspace_usec, usec_t total_usec); -void bus_manager_send_reloading(Manager *m, bool active); -void bus_manager_send_change_signal(Manager *m); diff --git a/src/core/dbus-mount.c b/src/core/dbus-mount.c deleted file mode 100644 index 935db7c48b..0000000000 --- a/src/core/dbus-mount.c +++ /dev/null @@ -1,211 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 "bus-util.h" -#include "dbus-cgroup.h" -#include "dbus-execute.h" -#include "dbus-kill.h" -#include "dbus-mount.h" -#include "mount.h" -#include "string-util.h" -#include "unit.h" - -static int property_get_what( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Mount *m = userdata; - const char *d; - - assert(bus); - assert(reply); - assert(m); - - if (m->from_proc_self_mountinfo && m->parameters_proc_self_mountinfo.what) - d = m->parameters_proc_self_mountinfo.what; - else if (m->from_fragment && m->parameters_fragment.what) - d = m->parameters_fragment.what; - else - d = ""; - - return sd_bus_message_append(reply, "s", d); -} - -static int property_get_options( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Mount *m = userdata; - const char *d; - - assert(bus); - assert(reply); - assert(m); - - if (m->from_proc_self_mountinfo && m->parameters_proc_self_mountinfo.options) - d = m->parameters_proc_self_mountinfo.options; - else if (m->from_fragment && m->parameters_fragment.options) - d = m->parameters_fragment.options; - else - d = ""; - - return sd_bus_message_append(reply, "s", d); -} - -static int property_get_type( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Mount *m = userdata; - const char *d; - - assert(bus); - assert(reply); - assert(m); - - if (m->from_proc_self_mountinfo && m->parameters_proc_self_mountinfo.fstype) - d = m->parameters_proc_self_mountinfo.fstype; - else if (m->from_fragment && m->parameters_fragment.fstype) - d = m->parameters_fragment.fstype; - else - d = ""; - - return sd_bus_message_append(reply, "s", d); -} - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, mount_result, MountResult); - -const sd_bus_vtable bus_mount_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Where", "s", NULL, offsetof(Mount, where), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("What", "s", property_get_what, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Options","s", property_get_options, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Type", "s", property_get_type, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("TimeoutUSec", "t", bus_property_get_usec, offsetof(Mount, timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Mount, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Mount, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SloppyOptions", "b", bus_property_get_bool, offsetof(Mount, sloppy_options), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Mount, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_EXEC_COMMAND_VTABLE("ExecMount", offsetof(Mount, exec_command[MOUNT_EXEC_MOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_VTABLE("ExecUnmount", offsetof(Mount, exec_command[MOUNT_EXEC_UNMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_VTABLE("ExecRemount", offsetof(Mount, exec_command[MOUNT_EXEC_REMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - SD_BUS_VTABLE_END -}; - -static int bus_mount_set_transient_property( - Mount *m, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - const char *new_property; - char **property; - char *p; - int r; - - assert(m); - assert(name); - assert(message); - - if (streq(name, "What")) - property = &m->parameters_fragment.what; - else if (streq(name, "Options")) - property = &m->parameters_fragment.options; - else if (streq(name, "Type")) - property = &m->parameters_fragment.fstype; - else - return 0; - - r = sd_bus_message_read(message, "s", &new_property); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - p = strdup(new_property); - if (!p) - return -ENOMEM; - - free(*property); - *property = p; - } - - return 1; -} - -int bus_mount_set_property( - Unit *u, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - Mount *m = MOUNT(u); - int r; - - assert(m); - assert(name); - assert(message); - - r = bus_cgroup_set_property(u, &m->cgroup_context, name, message, mode, error); - if (r != 0) - return r; - - if (u->transient && u->load_state == UNIT_STUB) { - /* This is a transient unit, let's load a little more */ - - r = bus_mount_set_transient_property(m, name, message, mode, error); - if (r != 0) - return r; - - r = bus_exec_context_set_transient_property(u, &m->exec_context, name, message, mode, error); - if (r != 0) - return r; - - r = bus_kill_context_set_transient_property(u, &m->kill_context, name, message, mode, error); - if (r != 0) - return r; - } - - return 0; -} - -int bus_mount_commit_properties(Unit *u) { - assert(u); - - unit_update_cgroup_members_masks(u); - unit_realize_cgroup(u); - - return 0; -} diff --git a/src/core/dbus-mount.h b/src/core/dbus-mount.h deleted file mode 100644 index ec16166d36..0000000000 --- a/src/core/dbus-mount.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "sd-bus.h" - -#include "unit.h" - -extern const sd_bus_vtable bus_mount_vtable[]; - -int bus_mount_set_property(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); -int bus_mount_commit_properties(Unit *u); diff --git a/src/core/dbus-path.c b/src/core/dbus-path.c deleted file mode 100644 index 1e153e503f..0000000000 --- a/src/core/dbus-path.c +++ /dev/null @@ -1,86 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 "bus-util.h" -#include "dbus-path.h" -#include "path.h" -#include "string-util.h" -#include "unit.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, path_result, PathResult); - -static int property_get_paths( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Path *p = userdata; - PathSpec *k; - int r; - - assert(bus); - assert(reply); - assert(p); - - r = sd_bus_message_open_container(reply, 'a', "(ss)"); - if (r < 0) - return r; - - LIST_FOREACH(spec, k, p->specs) { - r = sd_bus_message_append(reply, "(ss)", path_type_to_string(k->type), k->path); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_unit( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *p = userdata, *trigger; - - assert(bus); - assert(reply); - assert(p); - - trigger = UNIT_TRIGGER(p); - - return sd_bus_message_append(reply, "s", trigger ? trigger->id : ""); -} - -const sd_bus_vtable bus_path_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Unit", "s", property_get_unit, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Paths", "a(ss)", property_get_paths, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("MakeDirectory", "b", bus_property_get_bool, offsetof(Path, make_directory), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Path, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Path, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_VTABLE_END -}; diff --git a/src/core/dbus-path.h b/src/core/dbus-path.h deleted file mode 100644 index d3c19e0c2b..0000000000 --- a/src/core/dbus-path.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - - - -extern const sd_bus_vtable bus_path_vtable[]; diff --git a/src/core/dbus-scope.c b/src/core/dbus-scope.c deleted file mode 100644 index 34ee9a8fa9..0000000000 --- a/src/core/dbus-scope.c +++ /dev/null @@ -1,229 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-internal.h" -#include "bus-util.h" -#include "dbus-cgroup.h" -#include "dbus-kill.h" -#include "dbus-scope.h" -#include "dbus-unit.h" -#include "dbus.h" -#include "scope.h" -#include "selinux-access.h" -#include "unit.h" - -static int bus_scope_abandon(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Scope *s = userdata; - int r; - - assert(message); - assert(s); - - r = mac_selinux_unit_access_check(UNIT(s), message, "stop", error); - if (r < 0) - return r; - - r = bus_verify_manage_units_async(UNIT(s)->manager, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = scope_abandon(s); - if (r == -ESTALE) - return sd_bus_error_setf(error, BUS_ERROR_SCOPE_NOT_RUNNING, "Scope %s is not running, cannot abandon.", UNIT(s)->id); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, scope_result, ScopeResult); - -const sd_bus_vtable bus_scope_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Controller", "s", NULL, offsetof(Scope, controller), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TimeoutStopUSec", "t", bus_property_get_usec, offsetof(Scope, timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Scope, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_SIGNAL("RequestStop", NULL, 0), - SD_BUS_METHOD("Abandon", NULL, NULL, bus_scope_abandon, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_VTABLE_END -}; - -static int bus_scope_set_transient_property( - Scope *s, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - int r; - - assert(s); - assert(name); - assert(message); - - if (streq(name, "PIDs")) { - unsigned n = 0; - uint32_t pid; - - r = sd_bus_message_enter_container(message, 'a', "u"); - if (r < 0) - return r; - - while ((r = sd_bus_message_read(message, "u", &pid)) > 0) { - - if (pid <= 1) - return -EINVAL; - - if (mode != UNIT_CHECK) { - r = unit_watch_pid(UNIT(s), pid); - if (r < 0 && r != -EEXIST) - return r; - } - - n++; - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (n <= 0) - return -EINVAL; - - return 1; - - } else if (streq(name, "Controller")) { - const char *controller; - char *c; - - r = sd_bus_message_read(message, "s", &controller); - if (r < 0) - return r; - - if (!isempty(controller) && !service_name_is_valid(controller)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Controller '%s' is not a valid bus name.", controller); - - if (mode != UNIT_CHECK) { - if (isempty(controller)) - c = NULL; - else { - c = strdup(controller); - if (!c) - return -ENOMEM; - } - - free(s->controller); - s->controller = c; - } - - return 1; - - } else if (streq(name, "TimeoutStopUSec")) { - - if (mode != UNIT_CHECK) { - r = sd_bus_message_read(message, "t", &s->timeout_stop_usec); - if (r < 0) - return r; - - unit_write_drop_in_format(UNIT(s), mode, name, "[Scope]\nTimeoutStopSec="USEC_FMT"us\n", s->timeout_stop_usec); - } else { - r = sd_bus_message_skip(message, "t"); - if (r < 0) - return r; - } - - return 1; - } - - return 0; -} - -int bus_scope_set_property( - Unit *u, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - Scope *s = SCOPE(u); - int r; - - assert(s); - assert(name); - assert(message); - - r = bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); - if (r != 0) - return r; - - if (u->load_state == UNIT_STUB) { - /* While we are created we still accept PIDs */ - - r = bus_scope_set_transient_property(s, name, message, mode, error); - if (r != 0) - return r; - - r = bus_kill_context_set_transient_property(u, &s->kill_context, name, message, mode, error); - if (r != 0) - return r; - } - - return 0; -} - -int bus_scope_commit_properties(Unit *u) { - assert(u); - - unit_update_cgroup_members_masks(u); - unit_realize_cgroup(u); - - return 0; -} - -int bus_scope_send_request_stop(Scope *s) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *p = NULL; - int r; - - assert(s); - - if (!s->controller) - return 0; - - p = unit_dbus_path(UNIT(s)); - if (!p) - return -ENOMEM; - - r = sd_bus_message_new_signal( - UNIT(s)->manager->api_bus, - &m, - p, - "org.freedesktop.systemd1.Scope", - "RequestStop"); - if (r < 0) - return r; - - return sd_bus_send_to(UNIT(s)->manager->api_bus, m, /* s->controller */ NULL, NULL); -} diff --git a/src/core/dbus-scope.h b/src/core/dbus-scope.h deleted file mode 100644 index 270306f508..0000000000 --- a/src/core/dbus-scope.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -#include "unit.h" - -extern const sd_bus_vtable bus_scope_vtable[]; - -int bus_scope_set_property(Unit *u, const char *name, sd_bus_message *i, UnitSetPropertiesMode mode, sd_bus_error *error); -int bus_scope_commit_properties(Unit *u); - -int bus_scope_send_request_stop(Scope *s); diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c deleted file mode 100644 index 03eecca911..0000000000 --- a/src/core/dbus-service.c +++ /dev/null @@ -1,322 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 "alloc-util.h" -#include "async.h" -#include "bus-util.h" -#include "dbus-cgroup.h" -#include "dbus-execute.h" -#include "dbus-kill.h" -#include "dbus-service.h" -#include "fd-util.h" -#include "fileio.h" -#include "path-util.h" -#include "service.h" -#include "string-util.h" -#include "strv.h" -#include "unit.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, service_type, ServiceType); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, service_result, ServiceResult); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_restart, service_restart, ServiceRestart); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_notify_access, notify_access, NotifyAccess); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_failure_action, failure_action, FailureAction); - -const sd_bus_vtable bus_service_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Service, type), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Restart", "s", property_get_restart, offsetof(Service, restart), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PIDFile", "s", NULL, offsetof(Service, pid_file), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("NotifyAccess", "s", property_get_notify_access, offsetof(Service, notify_access), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RestartUSec", "t", bus_property_get_usec, offsetof(Service, restart_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TimeoutStartUSec", "t", bus_property_get_usec, offsetof(Service, timeout_start_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TimeoutStopUSec", "t", bus_property_get_usec, offsetof(Service, timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RuntimeMaxUSec", "t", bus_property_get_usec, offsetof(Service, runtime_max_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("WatchdogUSec", "t", bus_property_get_usec, offsetof(Service, watchdog_usec), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("WatchdogTimestamp", offsetof(Service, watchdog_timestamp), 0), - /* The following four are obsolete, and thus marked hidden here. They moved into the Unit interface */ - SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("StartLimitAction", "s", property_get_failure_action, offsetof(Unit, start_limit_action), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("RebootArgument", "s", NULL, offsetof(Unit, reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("FailureAction", "s", property_get_failure_action, offsetof(Service, failure_action), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PermissionsStartOnly", "b", bus_property_get_bool, offsetof(Service, permissions_start_only), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RootDirectoryStartOnly", "b", bus_property_get_bool, offsetof(Service, root_directory_start_only), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RemainAfterExit", "b", bus_property_get_bool, offsetof(Service, remain_after_exit), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("GuessMainPID", "b", bus_property_get_bool, offsetof(Service, guess_main_pid), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("MainPID", "u", bus_property_get_pid, offsetof(Service, main_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Service, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("BusName", "s", NULL, offsetof(Service, bus_name), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("FileDescriptorStoreMax", "u", bus_property_get_unsigned, offsetof(Service, n_fd_store_max), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("NFileDescriptorStore", "u", bus_property_get_unsigned, offsetof(Service, n_fd_store), 0), - SD_BUS_PROPERTY("StatusText", "s", NULL, offsetof(Service, status_text), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("StatusErrno", "i", NULL, offsetof(Service, status_errno), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Service, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("USBFunctionDescriptors", "s", NULL, offsetof(Service, usb_function_descriptors), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("USBFunctionStrings", "s", NULL, offsetof(Service, usb_function_strings), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_EXEC_STATUS_VTABLE("ExecMain", offsetof(Service, main_exec_status), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPre", offsetof(Service, exec_command[SERVICE_EXEC_START_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecStart", offsetof(Service, exec_command[SERVICE_EXEC_START]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPost", offsetof(Service, exec_command[SERVICE_EXEC_START_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecReload", offsetof(Service, exec_command[SERVICE_EXEC_RELOAD]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecStop", offsetof(Service, exec_command[SERVICE_EXEC_STOP]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPost", offsetof(Service, exec_command[SERVICE_EXEC_STOP_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - SD_BUS_VTABLE_END -}; - -static int bus_service_set_transient_property( - Service *s, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - int r; - - assert(s); - assert(name); - assert(message); - - if (streq(name, "RemainAfterExit")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - s->remain_after_exit = b; - unit_write_drop_in_private_format(UNIT(s), mode, name, "RemainAfterExit=%s\n", yes_no(b)); - } - - return 1; - - } else if (streq(name, "Type")) { - const char *t; - ServiceType k; - - r = sd_bus_message_read(message, "s", &t); - if (r < 0) - return r; - - k = service_type_from_string(t); - if (k < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid service type %s", t); - - if (mode != UNIT_CHECK) { - s->type = k; - unit_write_drop_in_private_format(UNIT(s), mode, name, "Type=%s\n", service_type_to_string(s->type)); - } - - return 1; - } else if (streq(name, "RuntimeMaxUSec")) { - usec_t u; - - r = sd_bus_message_read(message, "t", &u); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - s->runtime_max_usec = u; - unit_write_drop_in_private_format(UNIT(s), mode, name, "RuntimeMaxSec=" USEC_FMT "us\n", u); - } - - return 1; - - } else if (STR_IN_SET(name, - "StandardInputFileDescriptor", - "StandardOutputFileDescriptor", - "StandardErrorFileDescriptor")) { - int fd; - - r = sd_bus_message_read(message, "h", &fd); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - int copy; - - copy = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (copy < 0) - return -errno; - - if (streq(name, "StandardInputFileDescriptor")) { - asynchronous_close(s->stdin_fd); - s->stdin_fd = copy; - } else if (streq(name, "StandardOutputFileDescriptor")) { - asynchronous_close(s->stdout_fd); - s->stdout_fd = copy; - } else { - asynchronous_close(s->stderr_fd); - s->stderr_fd = copy; - } - - s->exec_context.stdio_as_fds = true; - } - - return 1; - - } else if (streq(name, "ExecStart")) { - unsigned n = 0; - - r = sd_bus_message_enter_container(message, 'a', "(sasb)"); - if (r < 0) - return r; - - while ((r = sd_bus_message_enter_container(message, 'r', "sasb")) > 0) { - _cleanup_strv_free_ char **argv = NULL; - const char *path; - int b; - - r = sd_bus_message_read(message, "s", &path); - if (r < 0) - return r; - - if (!path_is_absolute(path)) - return sd_bus_error_set_errnof(error, EINVAL, "Path %s is not absolute.", path); - - r = sd_bus_message_read_strv(message, &argv); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - ExecCommand *c; - - c = new0(ExecCommand, 1); - if (!c) - return -ENOMEM; - - c->path = strdup(path); - if (!c->path) { - free(c); - return -ENOMEM; - } - - c->argv = argv; - argv = NULL; - - c->ignore = b; - - path_kill_slashes(c->path); - exec_command_append_list(&s->exec_command[SERVICE_EXEC_START], c); - } - - n++; - } - - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - _cleanup_free_ char *buf = NULL; - _cleanup_fclose_ FILE *f = NULL; - ExecCommand *c; - size_t size = 0; - - if (n == 0) - s->exec_command[SERVICE_EXEC_START] = exec_command_free_list(s->exec_command[SERVICE_EXEC_START]); - - f = open_memstream(&buf, &size); - if (!f) - return -ENOMEM; - - fputs("ExecStart=\n", f); - - LIST_FOREACH(command, c, s->exec_command[SERVICE_EXEC_START]) { - _cleanup_free_ char *a; - - a = strv_join_quoted(c->argv); - if (!a) - return -ENOMEM; - - fprintf(f, "ExecStart=%s@%s %s\n", - c->ignore ? "-" : "", - c->path, - a); - } - - r = fflush_and_check(f); - if (r < 0) - return r; - unit_write_drop_in_private(UNIT(s), mode, name, buf); - } - - return 1; - } - - return 0; -} - -int bus_service_set_property( - Unit *u, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - Service *s = SERVICE(u); - int r; - - assert(s); - assert(name); - assert(message); - - r = bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); - if (r != 0) - return r; - - if (u->transient && u->load_state == UNIT_STUB) { - /* This is a transient unit, let's load a little more */ - - r = bus_service_set_transient_property(s, name, message, mode, error); - if (r != 0) - return r; - - r = bus_exec_context_set_transient_property(u, &s->exec_context, name, message, mode, error); - if (r != 0) - return r; - - r = bus_kill_context_set_transient_property(u, &s->kill_context, name, message, mode, error); - if (r != 0) - return r; - } - - return 0; -} - -int bus_service_commit_properties(Unit *u) { - assert(u); - - unit_update_cgroup_members_masks(u); - unit_realize_cgroup(u); - - return 0; -} diff --git a/src/core/dbus-service.h b/src/core/dbus-service.h deleted file mode 100644 index 769a53769e..0000000000 --- a/src/core/dbus-service.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "sd-bus.h" - -#include "unit.h" - -extern const sd_bus_vtable bus_service_vtable[]; - -int bus_service_set_property(Unit *u, const char *name, sd_bus_message *i, UnitSetPropertiesMode mode, sd_bus_error *error); -int bus_service_commit_properties(Unit *u); diff --git a/src/core/dbus-slice.c b/src/core/dbus-slice.c deleted file mode 100644 index e37f50b283..0000000000 --- a/src/core/dbus-slice.c +++ /dev/null @@ -1,52 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "dbus-cgroup.h" -#include "dbus-slice.h" -#include "slice.h" -#include "unit.h" - -const sd_bus_vtable bus_slice_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_VTABLE_END -}; - -int bus_slice_set_property( - Unit *u, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - Slice *s = SLICE(u); - - assert(name); - assert(u); - - return bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); -} - -int bus_slice_commit_properties(Unit *u) { - assert(u); - - unit_update_cgroup_members_masks(u); - unit_realize_cgroup(u); - - return 0; -} diff --git a/src/core/dbus-slice.h b/src/core/dbus-slice.h deleted file mode 100644 index 52ceebb135..0000000000 --- a/src/core/dbus-slice.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -#include "unit.h" - -extern const sd_bus_vtable bus_slice_vtable[]; - -int bus_slice_set_property(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); -int bus_slice_commit_properties(Unit *u); diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c deleted file mode 100644 index 961340608d..0000000000 --- a/src/core/dbus-socket.c +++ /dev/null @@ -1,184 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 "alloc-util.h" -#include "bus-util.h" -#include "dbus-cgroup.h" -#include "dbus-execute.h" -#include "dbus-socket.h" -#include "socket.h" -#include "string-util.h" -#include "unit.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, socket_result, SocketResult); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_bind_ipv6_only, socket_address_bind_ipv6_only, SocketAddressBindIPv6Only); - -static int property_get_listen( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - - Socket *s = SOCKET(userdata); - SocketPort *p; - int r; - - assert(bus); - assert(reply); - assert(s); - - r = sd_bus_message_open_container(reply, 'a', "(ss)"); - if (r < 0) - return r; - - LIST_FOREACH(port, p, s->ports) { - _cleanup_free_ char *address = NULL; - const char *a; - - switch (p->type) { - case SOCKET_SOCKET: { - r = socket_address_print(&p->address, &address); - if (r) - return r; - - a = address; - break; - } - - case SOCKET_SPECIAL: - case SOCKET_MQUEUE: - case SOCKET_FIFO: - case SOCKET_USB_FUNCTION: - a = p->path; - break; - - default: - assert_not_reached("Unknown socket type"); - } - - r = sd_bus_message_append(reply, "(ss)", socket_port_type_to_string(p), a); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - - -static int property_get_fdname( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Socket *s = SOCKET(userdata); - - assert(bus); - assert(reply); - assert(s); - - return sd_bus_message_append(reply, "s", socket_fdname(s)); -} - -const sd_bus_vtable bus_socket_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("BindIPv6Only", "s", property_get_bind_ipv6_only, offsetof(Socket, bind_ipv6_only), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Backlog", "u", bus_property_get_unsigned, offsetof(Socket, backlog), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TimeoutUSec", "t", bus_property_get_usec, offsetof(Socket, timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("BindToDevice", "s", NULL, offsetof(Socket, bind_to_device), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SocketUser", "s", NULL, offsetof(Socket, user), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SocketGroup", "s", NULL, offsetof(Socket, group), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SocketMode", "u", bus_property_get_mode, offsetof(Socket, socket_mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Socket, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Accept", "b", bus_property_get_bool, offsetof(Socket, accept), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Writable", "b", bus_property_get_bool, offsetof(Socket, writable), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("KeepAlive", "b", bus_property_get_bool, offsetof(Socket, keep_alive), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("KeepAliveTimeUSec", "t", bus_property_get_usec, offsetof(Socket, keep_alive_time), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("KeepAliveIntervalUSec", "t", bus_property_get_usec, offsetof(Socket, keep_alive_interval), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("KeepAliveProbes", "u", bus_property_get_unsigned, offsetof(Socket, keep_alive_cnt), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DeferAcceptUSec" , "t", bus_property_get_usec, offsetof(Socket, defer_accept), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("NoDelay", "b", bus_property_get_bool, offsetof(Socket, no_delay), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Priority", "i", bus_property_get_int, offsetof(Socket, priority), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ReceiveBuffer", "t", bus_property_get_size, offsetof(Socket, receive_buffer), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SendBuffer", "t", bus_property_get_size, offsetof(Socket, send_buffer), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("IPTOS", "i", bus_property_get_int, offsetof(Socket, ip_tos), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("IPTTL", "i", bus_property_get_int, offsetof(Socket, ip_ttl), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PipeSize", "t", bus_property_get_size, offsetof(Socket, pipe_size), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("FreeBind", "b", bus_property_get_bool, offsetof(Socket, free_bind), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Transparent", "b", bus_property_get_bool, offsetof(Socket, transparent), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Broadcast", "b", bus_property_get_bool, offsetof(Socket, broadcast), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PassCredentials", "b", bus_property_get_bool, offsetof(Socket, pass_cred), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PassSecurity", "b", bus_property_get_bool, offsetof(Socket, pass_sec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RemoveOnStop", "b", bus_property_get_bool, offsetof(Socket, remove_on_stop), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Listen", "a(ss)", property_get_listen, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Symlinks", "as", NULL, offsetof(Socket, symlinks), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Mark", "i", bus_property_get_int, offsetof(Socket, mark), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("MaxConnections", "u", bus_property_get_unsigned, offsetof(Socket, max_connections), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("MessageQueueMaxMessages", "x", bus_property_get_long, offsetof(Socket, mq_maxmsg), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("MessageQueueMessageSize", "x", bus_property_get_long, offsetof(Socket, mq_msgsize), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ReusePort", "b", bus_property_get_bool, offsetof(Socket, reuse_port), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SmackLabel", "s", NULL, offsetof(Socket, smack), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SmackLabelIPIn", "s", NULL, offsetof(Socket, smack_ip_in), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SmackLabelIPOut", "s", NULL, offsetof(Socket, smack_ip_out), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Socket, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - 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("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), - SD_BUS_PROPERTY("TriggerLimitBurst", "u", bus_property_get_unsigned, offsetof(Socket, trigger_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPre", offsetof(Socket, exec_command[SOCKET_EXEC_START_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPost", offsetof(Socket, exec_command[SOCKET_EXEC_START_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPre", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPost", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - SD_BUS_VTABLE_END -}; - -int bus_socket_set_property( - Unit *u, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - Socket *s = SOCKET(u); - - assert(s); - assert(name); - assert(message); - - return bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); -} - -int bus_socket_commit_properties(Unit *u) { - assert(u); - - unit_update_cgroup_members_masks(u); - unit_realize_cgroup(u); - - return 0; -} diff --git a/src/core/dbus-socket.h b/src/core/dbus-socket.h deleted file mode 100644 index 7a792c7a89..0000000000 --- a/src/core/dbus-socket.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "sd-bus.h" - -#include "unit.h" - -extern const sd_bus_vtable bus_socket_vtable[]; - -int bus_socket_set_property(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); -int bus_socket_commit_properties(Unit *u); diff --git a/src/core/dbus-swap.c b/src/core/dbus-swap.c deleted file mode 100644 index 292f8738c6..0000000000 --- a/src/core/dbus-swap.c +++ /dev/null @@ -1,115 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2010 Maarten Lankhorst - - 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 "bus-util.h" -#include "dbus-cgroup.h" -#include "dbus-execute.h" -#include "dbus-swap.h" -#include "string-util.h" -#include "swap.h" -#include "unit.h" - -static int property_get_priority( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Swap *s = SWAP(userdata); - int p; - - assert(bus); - assert(reply); - assert(s); - - if (s->from_proc_swaps) - p = s->parameters_proc_swaps.priority; - else if (s->from_fragment) - p = s->parameters_fragment.priority; - else - p = -1; - - return sd_bus_message_append(reply, "i", p); -} - -static int property_get_options( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Swap *s = SWAP(userdata); - const char *options = NULL; - - assert(bus); - assert(reply); - assert(s); - - if (s->from_fragment) - options = s->parameters_fragment.options; - - return sd_bus_message_append(reply, "s", options); -} - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, swap_result, SwapResult); - -const sd_bus_vtable bus_swap_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("What", "s", NULL, offsetof(Swap, what), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Priority", "i", property_get_priority, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Options", "s", property_get_options, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("TimeoutUSec", "t", bus_property_get_usec, offsetof(Swap, timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Swap, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Swap, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_EXEC_COMMAND_VTABLE("ExecActivate", offsetof(Swap, exec_command[SWAP_EXEC_ACTIVATE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_VTABLE("ExecDeactivate", offsetof(Swap, exec_command[SWAP_EXEC_DEACTIVATE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - SD_BUS_VTABLE_END -}; - -int bus_swap_set_property( - Unit *u, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - Swap *s = SWAP(u); - - assert(s); - assert(name); - assert(message); - - return bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); -} - -int bus_swap_commit_properties(Unit *u) { - assert(u); - - unit_update_cgroup_members_masks(u); - unit_realize_cgroup(u); - - return 0; -} diff --git a/src/core/dbus-swap.h b/src/core/dbus-swap.h deleted file mode 100644 index 5238471f98..0000000000 --- a/src/core/dbus-swap.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2010 Maarten Lankhorst - - 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 "sd-bus.h" - -#include "unit.h" - -extern const sd_bus_vtable bus_swap_vtable[]; - -int bus_swap_set_property(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); -int bus_swap_commit_properties(Unit *u); diff --git a/src/core/dbus-target.c b/src/core/dbus-target.c deleted file mode 100644 index 6858b1ce72..0000000000 --- a/src/core/dbus-target.c +++ /dev/null @@ -1,26 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 "dbus-target.h" -#include "unit.h" - -const sd_bus_vtable bus_target_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_VTABLE_END -}; diff --git a/src/core/dbus-target.h b/src/core/dbus-target.h deleted file mode 100644 index 9be5ce06b7..0000000000 --- a/src/core/dbus-target.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "sd-bus.h" - -extern const sd_bus_vtable bus_target_vtable[]; diff --git a/src/core/dbus-timer.c b/src/core/dbus-timer.c deleted file mode 100644 index a0e61b023e..0000000000 --- a/src/core/dbus-timer.c +++ /dev/null @@ -1,352 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 "alloc-util.h" -#include "bus-util.h" -#include "dbus-timer.h" -#include "strv.h" -#include "timer.h" -#include "unit.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, timer_result, TimerResult); - -static int property_get_monotonic_timers( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Timer *t = userdata; - TimerValue *v; - int r; - - assert(bus); - assert(reply); - assert(t); - - r = sd_bus_message_open_container(reply, 'a', "(stt)"); - if (r < 0) - return r; - - LIST_FOREACH(value, v, t->values) { - _cleanup_free_ char *buf = NULL; - const char *s; - size_t l; - - if (v->base == TIMER_CALENDAR) - continue; - - s = timer_base_to_string(v->base); - assert(endswith(s, "Sec")); - - /* s/Sec/USec/ */ - l = strlen(s); - buf = new(char, l+2); - if (!buf) - return -ENOMEM; - - memcpy(buf, s, l-3); - memcpy(buf+l-3, "USec", 5); - - r = sd_bus_message_append(reply, "(stt)", buf, v->value, v->next_elapse); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_calendar_timers( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Timer *t = userdata; - TimerValue *v; - int r; - - assert(bus); - assert(reply); - assert(t); - - r = sd_bus_message_open_container(reply, 'a', "(sst)"); - if (r < 0) - return r; - - LIST_FOREACH(value, v, t->values) { - _cleanup_free_ char *buf = NULL; - - if (v->base != TIMER_CALENDAR) - continue; - - r = calendar_spec_to_string(v->calendar_spec, &buf); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "(sst)", timer_base_to_string(v->base), buf, v->next_elapse); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_unit( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata, *trigger; - - assert(bus); - assert(reply); - assert(u); - - trigger = UNIT_TRIGGER(u); - - return sd_bus_message_append(reply, "s", trigger ? trigger->id : ""); -} - -static int property_get_next_elapse_monotonic( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Timer *t = userdata; - usec_t x; - - assert(bus); - assert(reply); - assert(t); - - if (t->next_elapse_monotonic_or_boottime <= 0) - x = 0; - else if (t->wake_system) { - usec_t a, b; - - a = now(CLOCK_MONOTONIC); - b = now(clock_boottime_or_monotonic()); - - if (t->next_elapse_monotonic_or_boottime + a > b) - x = t->next_elapse_monotonic_or_boottime + a - b; - else - x = 0; - } else - x = t->next_elapse_monotonic_or_boottime; - - return sd_bus_message_append(reply, "t", x); -} - -const sd_bus_vtable bus_timer_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Unit", "s", property_get_unit, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TimersMonotonic", "a(stt)", property_get_monotonic_timers, 0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - SD_BUS_PROPERTY("TimersCalendar", "a(sst)", property_get_calendar_timers, 0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - SD_BUS_PROPERTY("NextElapseUSecRealtime", "t", bus_property_get_usec, offsetof(Timer, next_elapse_realtime), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("NextElapseUSecMonotonic", "t", property_get_next_elapse_monotonic, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_PROPERTY_DUAL_TIMESTAMP("LastTriggerUSec", offsetof(Timer, last_trigger), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Timer, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("AccuracyUSec", "t", bus_property_get_usec, offsetof(Timer, accuracy_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RandomizedDelayUSec", "t", bus_property_get_usec, offsetof(Timer, random_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Persistent", "b", bus_property_get_bool, offsetof(Timer, persistent), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("WakeSystem", "b", bus_property_get_bool, offsetof(Timer, wake_system), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RemainAfterElapse", "b", bus_property_get_bool, offsetof(Timer, remain_after_elapse), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_VTABLE_END -}; - -static int bus_timer_set_transient_property( - Timer *t, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - int r; - - assert(t); - assert(name); - assert(message); - - if (STR_IN_SET(name, - "OnActiveSec", - "OnBootSec", - "OnStartupSec", - "OnUnitActiveSec", - "OnUnitInactiveSec")) { - - TimerValue *v; - TimerBase b = _TIMER_BASE_INVALID; - usec_t u = 0; - - b = timer_base_from_string(name); - if (b < 0) - return -EINVAL; - - r = sd_bus_message_read(message, "t", &u); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - char time[FORMAT_TIMESPAN_MAX]; - - unit_write_drop_in_private_format(UNIT(t), mode, name, "%s=%s\n", name, format_timespan(time, sizeof(time), u, USEC_PER_MSEC)); - - v = new0(TimerValue, 1); - if (!v) - return -ENOMEM; - - v->base = b; - v->value = u; - - LIST_PREPEND(value, t->values, v); - } - - return 1; - - } else if (streq(name, "OnCalendar")) { - - TimerValue *v; - CalendarSpec *c = NULL; - const char *str; - - r = sd_bus_message_read(message, "s", &str); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - r = calendar_spec_from_string(str, &c); - if (r < 0) - return r; - - unit_write_drop_in_private_format(UNIT(t), mode, name, "%s=%s\n", name, str); - - v = new0(TimerValue, 1); - if (!v) { - calendar_spec_free(c); - return -ENOMEM; - } - - v->base = TIMER_CALENDAR; - v->calendar_spec = c; - - LIST_PREPEND(value, t->values, v); - } - - return 1; - - } else if (STR_IN_SET(name, "AccuracyUSec", "AccuracySec")) { - usec_t u = 0; - - if (streq(name, "AccuracySec")) - log_notice("Client is using obsolete AccuracySec= transient property, please use AccuracyUSec= instead."); - - r = sd_bus_message_read(message, "t", &u); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - t->accuracy_usec = u; - unit_write_drop_in_private_format(UNIT(t), mode, name, "AccuracySec=" USEC_FMT "us\n", u); - } - - return 1; - - } else if (streq(name, "RandomizedDelayUSec")) { - usec_t u = 0; - - r = sd_bus_message_read(message, "t", &u); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - t->random_usec = u; - unit_write_drop_in_private_format(UNIT(t), mode, name, "RandomizedDelaySec=" USEC_FMT "us\n", u); - } - - return 1; - - } else if (streq(name, "WakeSystem")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - t->wake_system = b; - unit_write_drop_in_private_format(UNIT(t), mode, name, "%s=%s\n", name, yes_no(b)); - } - - return 1; - - } else if (streq(name, "RemainAfterElapse")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - t->remain_after_elapse = b; - unit_write_drop_in_private_format(UNIT(t), mode, name, "%s=%s\n", name, yes_no(b)); - } - - return 1; - } - - return 0; -} - -int bus_timer_set_property( - Unit *u, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - Timer *t = TIMER(u); - int r; - - assert(t); - assert(name); - assert(message); - - if (u->transient && u->load_state == UNIT_STUB) { - r = bus_timer_set_transient_property(t, name, message, mode, error); - if (r != 0) - return r; - } - - return 0; -} diff --git a/src/core/dbus-timer.h b/src/core/dbus-timer.h deleted file mode 100644 index 39053dc4a2..0000000000 --- a/src/core/dbus-timer.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "sd-bus.h" - -#include "unit.h" - -extern const sd_bus_vtable bus_timer_vtable[]; - -int bus_timer_set_property(Unit *u, const char *name, sd_bus_message *i, UnitSetPropertiesMode mode, sd_bus_error *error); diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c deleted file mode 100644 index e912fe2192..0000000000 --- a/src/core/dbus-unit.c +++ /dev/null @@ -1,1423 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 "sd-bus.h" - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "cgroup-util.h" -#include "dbus-unit.h" -#include "dbus.h" -#include "fd-util.h" -#include "locale-util.h" -#include "log.h" -#include "process-util.h" -#include "selinux-access.h" -#include "signal-util.h" -#include "special.h" -#include "string-util.h" -#include "strv.h" -#include "user-util.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_load_state, unit_load_state, UnitLoadState); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_job_mode, job_mode, JobMode); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_failure_action, failure_action, FailureAction); - -static int property_get_names( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - Iterator i; - const char *t; - int r; - - assert(bus); - assert(reply); - assert(u); - - r = sd_bus_message_open_container(reply, 'a', "s"); - if (r < 0) - return r; - - SET_FOREACH(t, u->names, i) { - r = sd_bus_message_append(reply, "s", t); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_following( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata, *f; - - assert(bus); - assert(reply); - assert(u); - - f = unit_following(u); - return sd_bus_message_append(reply, "s", f ? f->id : ""); -} - -static int property_get_dependencies( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Set *s = *(Set**) userdata; - Iterator j; - Unit *u; - int r; - - assert(bus); - assert(reply); - - r = sd_bus_message_open_container(reply, 'a', "s"); - if (r < 0) - return r; - - SET_FOREACH(u, s, j) { - r = sd_bus_message_append(reply, "s", u->id); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_obsolete_dependencies( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - assert(bus); - assert(reply); - - /* For dependency types we don't support anymore always return an empty array */ - return sd_bus_message_append(reply, "as", 0); -} - -static int property_get_description( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - return sd_bus_message_append(reply, "s", unit_description(u)); -} - -static int property_get_active_state( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - return sd_bus_message_append(reply, "s", unit_active_state_to_string(unit_active_state(u))); -} - -static int property_get_sub_state( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - return sd_bus_message_append(reply, "s", unit_sub_state_to_string(u)); -} - -static int property_get_unit_file_preset( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - int r; - - assert(bus); - assert(reply); - assert(u); - - r = unit_get_unit_file_preset(u); - - return sd_bus_message_append(reply, "s", - r < 0 ? "": - r > 0 ? "enabled" : "disabled"); -} - -static int property_get_unit_file_state( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - return sd_bus_message_append(reply, "s", unit_file_state_to_string(unit_get_unit_file_state(u))); -} - -static int property_get_can_start( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - return sd_bus_message_append(reply, "b", unit_can_start(u) && !u->refuse_manual_start); -} - -static int property_get_can_stop( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - /* On the lower levels we assume that every unit we can start - * we can also stop */ - - return sd_bus_message_append(reply, "b", unit_can_start(u) && !u->refuse_manual_stop); -} - -static int property_get_can_reload( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - return sd_bus_message_append(reply, "b", unit_can_reload(u)); -} - -static int property_get_can_isolate( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - return sd_bus_message_append(reply, "b", unit_can_isolate(u) && !u->refuse_manual_start); -} - -static int property_get_job( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - _cleanup_free_ char *p = NULL; - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - if (!u->job) - return sd_bus_message_append(reply, "(uo)", 0, "/"); - - p = job_dbus_path(u->job); - if (!p) - return -ENOMEM; - - return sd_bus_message_append(reply, "(uo)", u->job->id, p); -} - -static int property_get_need_daemon_reload( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - return sd_bus_message_append(reply, "b", unit_need_daemon_reload(u)); -} - -static int property_get_conditions( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - const char *(*to_string)(ConditionType type) = NULL; - Condition **list = userdata, *c; - int r; - - assert(bus); - assert(reply); - assert(list); - - to_string = streq(property, "Asserts") ? assert_type_to_string : condition_type_to_string; - - r = sd_bus_message_open_container(reply, 'a', "(sbbsi)"); - if (r < 0) - return r; - - LIST_FOREACH(conditions, c, *list) { - int tristate; - - tristate = - c->result == CONDITION_UNTESTED ? 0 : - c->result == CONDITION_SUCCEEDED ? 1 : -1; - - r = sd_bus_message_append(reply, "(sbbsi)", - to_string(c->type), - c->trigger, c->negate, - c->parameter, tristate); - if (r < 0) - return r; - - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_load_error( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL; - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - if (u->load_error != 0) - sd_bus_error_set_errno(&e, u->load_error); - - return sd_bus_message_append(reply, "(ss)", e.name, e.message); -} - -static int bus_verify_manage_units_async_full( - Unit *u, - const char *verb, - int capability, - const char *polkit_message, - sd_bus_message *call, - sd_bus_error *error) { - - const char *details[9] = { - "unit", u->id, - "verb", verb, - }; - - if (polkit_message) { - details[4] = "polkit.message"; - details[5] = polkit_message; - details[6] = "polkit.gettext_domain"; - details[7] = GETTEXT_PACKAGE; - } - - return bus_verify_polkit_async(call, capability, "org.freedesktop.systemd1.manage-units", details, false, UID_INVALID, &u->manager->polkit_registry, error); -} - -int bus_unit_method_start_generic( - sd_bus_message *message, - Unit *u, - JobType job_type, - bool reload_if_possible, - sd_bus_error *error) { - - const char *smode; - JobMode mode; - _cleanup_free_ char *verb = NULL; - static const char *const polkit_message_for_job[_JOB_TYPE_MAX] = { - [JOB_START] = N_("Authentication is required to start '$(unit)'."), - [JOB_STOP] = N_("Authentication is required to stop '$(unit)'."), - [JOB_RELOAD] = N_("Authentication is required to reload '$(unit)'."), - [JOB_RESTART] = N_("Authentication is required to restart '$(unit)'."), - [JOB_TRY_RESTART] = N_("Authentication is required to restart '$(unit)'."), - }; - int r; - - assert(message); - assert(u); - assert(job_type >= 0 && job_type < _JOB_TYPE_MAX); - - r = mac_selinux_unit_access_check( - u, message, - job_type_to_access_method(job_type), - error); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "s", &smode); - if (r < 0) - return r; - - mode = job_mode_from_string(smode); - if (mode < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Job mode %s invalid", smode); - - if (reload_if_possible) - verb = strjoin("reload-or-", job_type_to_string(job_type), NULL); - else - verb = strdup(job_type_to_string(job_type)); - if (!verb) - return -ENOMEM; - - r = bus_verify_manage_units_async_full( - u, - verb, - CAP_SYS_ADMIN, - job_type < _JOB_TYPE_MAX ? polkit_message_for_job[job_type] : NULL, - message, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - return bus_unit_queue_job(message, u, job_type, mode, reload_if_possible, error); -} - -static int method_start(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return bus_unit_method_start_generic(message, userdata, JOB_START, false, error); -} - -static int method_stop(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return bus_unit_method_start_generic(message, userdata, JOB_STOP, false, error); -} - -static int method_reload(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return bus_unit_method_start_generic(message, userdata, JOB_RELOAD, false, error); -} - -static int method_restart(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return bus_unit_method_start_generic(message, userdata, JOB_RESTART, false, error); -} - -static int method_try_restart(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return bus_unit_method_start_generic(message, userdata, JOB_TRY_RESTART, false, error); -} - -static int method_reload_or_restart(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return bus_unit_method_start_generic(message, userdata, JOB_RESTART, true, error); -} - -static int method_reload_or_try_restart(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return bus_unit_method_start_generic(message, userdata, JOB_TRY_RESTART, true, error); -} - -int bus_unit_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Unit *u = userdata; - const char *swho; - int32_t signo; - KillWho who; - int r; - - assert(message); - assert(u); - - r = mac_selinux_unit_access_check(u, message, "stop", error); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "si", &swho, &signo); - if (r < 0) - return r; - - if (isempty(swho)) - who = KILL_ALL; - else { - who = kill_who_from_string(swho); - if (who < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid who argument %s", swho); - } - - if (!SIGNAL_VALID(signo)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Signal number out of range."); - - r = bus_verify_manage_units_async_full( - u, - "kill", - CAP_KILL, - N_("Authentication is required to kill '$(unit)'."), - message, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = unit_kill(u, who, signo, error); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_unit_method_reset_failed(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Unit *u = userdata; - int r; - - assert(message); - assert(u); - - r = mac_selinux_unit_access_check(u, message, "reload", error); - if (r < 0) - return r; - - r = bus_verify_manage_units_async_full( - u, - "reset-failed", - CAP_SYS_ADMIN, - N_("Authentication is required to reset the \"failed\" state of '$(unit)'."), - message, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - unit_reset_failed(u); - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_unit_method_set_properties(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Unit *u = userdata; - int runtime, r; - - assert(message); - assert(u); - - r = mac_selinux_unit_access_check(u, message, "start", error); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "b", &runtime); - if (r < 0) - return r; - - r = bus_verify_manage_units_async_full( - u, - "set-property", - CAP_SYS_ADMIN, - N_("Authentication is required to set properties on '$(unit)'."), - message, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = bus_unit_set_properties(u, message, runtime ? UNIT_RUNTIME : UNIT_PERSISTENT, true, error); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -const sd_bus_vtable bus_unit_vtable[] = { - SD_BUS_VTABLE_START(0), - - SD_BUS_PROPERTY("Id", "s", NULL, offsetof(Unit, id), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Names", "as", property_get_names, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Following", "s", property_get_following, 0, 0), - SD_BUS_PROPERTY("Requires", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUIRES]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Requisite", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUISITE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Wants", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_WANTS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("BindsTo", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_BINDS_TO]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PartOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_PART_OF]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RequiredBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUIRED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RequisiteOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUISITE_OF]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("WantedBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_WANTED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("BoundBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_BOUND_BY]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ConsistsOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_CONSISTS_OF]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Conflicts", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_CONFLICTS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ConflictedBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_CONFLICTED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Before", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_BEFORE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("After", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_AFTER]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("OnFailure", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_ON_FAILURE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Triggers", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_TRIGGERS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TriggeredBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_TRIGGERED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PropagatesReloadTo", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_PROPAGATES_RELOAD_TO]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ReloadPropagatedFrom", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_RELOAD_PROPAGATED_FROM]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("JoinsNamespaceOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_JOINS_NAMESPACE_OF]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RequiresOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("RequisiteOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("RequiredByOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("RequisiteOfOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("RequiresMountsFor", "as", NULL, offsetof(Unit, requires_mounts_for), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Documentation", "as", NULL, offsetof(Unit, documentation), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Description", "s", property_get_description, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LoadState", "s", property_get_load_state, offsetof(Unit, load_state), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ActiveState", "s", property_get_active_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("SubState", "s", property_get_sub_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("FragmentPath", "s", NULL, offsetof(Unit, fragment_path), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SourcePath", "s", NULL, offsetof(Unit, source_path), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DropInPaths", "as", NULL, offsetof(Unit, dropin_paths), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("UnitFileState", "s", property_get_unit_file_state, 0, 0), - SD_BUS_PROPERTY("UnitFilePreset", "s", property_get_unit_file_preset, 0, 0), - BUS_PROPERTY_DUAL_TIMESTAMP("StateChangeTimestamp", offsetof(Unit, state_change_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_PROPERTY_DUAL_TIMESTAMP("InactiveExitTimestamp", offsetof(Unit, inactive_exit_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_PROPERTY_DUAL_TIMESTAMP("ActiveEnterTimestamp", offsetof(Unit, active_enter_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_PROPERTY_DUAL_TIMESTAMP("ActiveExitTimestamp", offsetof(Unit, active_exit_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_PROPERTY_DUAL_TIMESTAMP("InactiveEnterTimestamp", offsetof(Unit, inactive_enter_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("CanStart", "b", property_get_can_start, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("CanStop", "b", property_get_can_stop, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("CanReload", "b", property_get_can_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("CanIsolate", "b", property_get_can_isolate, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Job", "(uo)", property_get_job, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("StopWhenUnneeded", "b", bus_property_get_bool, offsetof(Unit, stop_when_unneeded), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RefuseManualStart", "b", bus_property_get_bool, offsetof(Unit, refuse_manual_start), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RefuseManualStop", "b", bus_property_get_bool, offsetof(Unit, refuse_manual_stop), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("AllowIsolate", "b", bus_property_get_bool, offsetof(Unit, allow_isolate), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultDependencies", "b", bus_property_get_bool, offsetof(Unit, default_dependencies), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("OnFailureJobMode", "s", property_get_job_mode, offsetof(Unit, on_failure_job_mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("IgnoreOnIsolate", "b", bus_property_get_bool, offsetof(Unit, ignore_on_isolate), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("NeedDaemonReload", "b", property_get_need_daemon_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("JobTimeoutUSec", "t", bus_property_get_usec, offsetof(Unit, job_timeout), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("JobTimeoutAction", "s", property_get_failure_action, offsetof(Unit, job_timeout_action), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("JobTimeoutRebootArgument", "s", NULL, offsetof(Unit, job_timeout_reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ConditionResult", "b", bus_property_get_bool, offsetof(Unit, condition_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("AssertResult", "b", bus_property_get_bool, offsetof(Unit, assert_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_PROPERTY_DUAL_TIMESTAMP("ConditionTimestamp", offsetof(Unit, condition_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_PROPERTY_DUAL_TIMESTAMP("AssertTimestamp", offsetof(Unit, assert_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Conditions", "a(sbbsi)", property_get_conditions, offsetof(Unit, conditions), 0), - SD_BUS_PROPERTY("Asserts", "a(sbbsi)", property_get_conditions, offsetof(Unit, asserts), 0), - SD_BUS_PROPERTY("LoadError", "(ss)", property_get_load_error, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Transient", "b", bus_property_get_bool, offsetof(Unit, transient), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("StartLimitIntervalSec", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), /* obsolete alias name */ - SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("StartLimitAction", "s", property_get_failure_action, offsetof(Unit, start_limit_action), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RebootArgument", "s", NULL, offsetof(Unit, reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST), - - SD_BUS_METHOD("Start", "s", "o", method_start, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Stop", "s", "o", method_stop, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Reload", "s", "o", method_reload, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Restart", "s", "o", method_restart, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("TryRestart", "s", "o", method_try_restart, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ReloadOrRestart", "s", "o", method_reload_or_restart, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ReloadOrTryRestart", "s", "o", method_reload_or_try_restart, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Kill", "si", NULL, bus_unit_method_kill, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ResetFailed", NULL, NULL, bus_unit_method_reset_failed, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetProperties", "ba(sv)", NULL, bus_unit_method_set_properties, SD_BUS_VTABLE_UNPRIVILEGED), - - SD_BUS_VTABLE_END -}; - -static int property_get_slice( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - return sd_bus_message_append(reply, "s", unit_slice_name(u)); -} - -static int property_get_current_memory( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - uint64_t sz = (uint64_t) -1; - Unit *u = userdata; - int r; - - assert(bus); - assert(reply); - assert(u); - - r = unit_get_memory_current(u, &sz); - if (r < 0 && r != -ENODATA) - log_unit_warning_errno(u, r, "Failed to get memory.usage_in_bytes attribute: %m"); - - return sd_bus_message_append(reply, "t", sz); -} - -static int property_get_current_tasks( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - uint64_t cn = (uint64_t) -1; - Unit *u = userdata; - int r; - - assert(bus); - assert(reply); - assert(u); - - r = unit_get_tasks_current(u, &cn); - if (r < 0 && r != -ENODATA) - log_unit_warning_errno(u, r, "Failed to get pids.current attribute: %m"); - - return sd_bus_message_append(reply, "t", cn); -} - -static int property_get_cpu_usage( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - nsec_t ns = (nsec_t) -1; - Unit *u = userdata; - int r; - - assert(bus); - assert(reply); - assert(u); - - r = unit_get_cpu_usage(u, &ns); - if (r < 0 && r != -ENODATA) - log_unit_warning_errno(u, r, "Failed to get cpuacct.usage attribute: %m"); - - return sd_bus_message_append(reply, "t", ns); -} - -static int property_get_cgroup( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - const char *t; - - assert(bus); - assert(reply); - assert(u); - - /* Three cases: a) u->cgroup_path is NULL, in which case the - * unit has no control group, which we report as the empty - * string. b) u->cgroup_path is the empty string, which - * indicates the root cgroup, which we report as "/". c) all - * other cases we report as-is. */ - - if (u->cgroup_path) - t = isempty(u->cgroup_path) ? "/" : u->cgroup_path; - else - t = ""; - - return sd_bus_message_append(reply, "s", t); -} - -static int append_process(sd_bus_message *reply, const char *p, pid_t pid, Set *pids) { - _cleanup_free_ char *buf = NULL, *cmdline = NULL; - int r; - - assert(reply); - assert(pid > 0); - - r = set_put(pids, PID_TO_PTR(pid)); - if (r == -EEXIST || r == 0) - return 0; - if (r < 0) - return r; - - if (!p) { - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &buf); - if (r == -ESRCH) - return 0; - if (r < 0) - return r; - - p = buf; - } - - (void) get_process_cmdline(pid, 0, true, &cmdline); - - return sd_bus_message_append(reply, - "(sus)", - p, - (uint32_t) pid, - cmdline); -} - -static int append_cgroup(sd_bus_message *reply, const char *p, Set *pids) { - _cleanup_closedir_ DIR *d = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(reply); - assert(p); - - r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, p, &f); - if (r == ENOENT) - return 0; - if (r < 0) - return r; - - for (;;) { - pid_t pid; - - r = cg_read_pid(f, &pid); - if (r < 0) - return r; - if (r == 0) - break; - - if (is_kernel_thread(pid) > 0) - continue; - - r = append_process(reply, p, pid, pids); - if (r < 0) - return r; - } - - r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, p, &d); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - - for (;;) { - _cleanup_free_ char *g = NULL, *j = NULL; - - r = cg_read_subgroup(d, &g); - if (r < 0) - return r; - if (r == 0) - break; - - j = strjoin(p, "/", g, NULL); - if (!j) - return -ENOMEM; - - r = append_cgroup(reply, j, pids); - if (r < 0) - return r; - } - - return 0; -} - -int bus_unit_method_get_processes(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(set_freep) Set *pids = NULL; - Unit *u = userdata; - pid_t pid; - int r; - - assert(message); - - pids = set_new(NULL); - if (!pids) - return -ENOMEM; - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(sus)"); - if (r < 0) - return r; - - if (u->cgroup_path) { - r = append_cgroup(reply, u->cgroup_path, pids); - if (r < 0) - return r; - } - - /* The main and control pids might live outside of the cgroup, hence fetch them separately */ - pid = unit_main_pid(u); - if (pid > 0) { - r = append_process(reply, NULL, pid, pids); - if (r < 0) - return r; - } - - pid = unit_control_pid(u); - if (pid > 0) { - r = append_process(reply, NULL, pid, pids); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -const sd_bus_vtable bus_unit_cgroup_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Slice", "s", property_get_slice, 0, 0), - SD_BUS_PROPERTY("ControlGroup", "s", property_get_cgroup, 0, 0), - SD_BUS_PROPERTY("MemoryCurrent", "t", property_get_current_memory, 0, 0), - SD_BUS_PROPERTY("CPUUsageNSec", "t", property_get_cpu_usage, 0, 0), - SD_BUS_PROPERTY("TasksCurrent", "t", property_get_current_tasks, 0, 0), - SD_BUS_METHOD("GetProcesses", NULL, "a(sus)", bus_unit_method_get_processes, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_VTABLE_END -}; - -static int send_new_signal(sd_bus *bus, void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *p = NULL; - Unit *u = userdata; - int r; - - assert(bus); - assert(u); - - p = unit_dbus_path(u); - if (!p) - return -ENOMEM; - - r = sd_bus_message_new_signal( - bus, - &m, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "UnitNew"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "so", u->id, p); - if (r < 0) - return r; - - return sd_bus_send(bus, m, NULL); -} - -static int send_changed_signal(sd_bus *bus, void *userdata) { - _cleanup_free_ char *p = NULL; - Unit *u = userdata; - int r; - - assert(bus); - assert(u); - - p = unit_dbus_path(u); - if (!p) - return -ENOMEM; - - /* Send a properties changed signal. First for the specific - * type, then for the generic unit. The clients may rely on - * this order to get atomic behavior if needed. */ - - r = sd_bus_emit_properties_changed_strv( - bus, p, - unit_dbus_interface_from_type(u->type), - NULL); - if (r < 0) - return r; - - return sd_bus_emit_properties_changed_strv( - bus, p, - "org.freedesktop.systemd1.Unit", - NULL); -} - -void bus_unit_send_change_signal(Unit *u) { - int r; - assert(u); - - if (u->in_dbus_queue) { - LIST_REMOVE(dbus_queue, u->manager->dbus_unit_queue, u); - u->in_dbus_queue = false; - } - - if (!u->id) - return; - - r = bus_foreach_bus(u->manager, NULL, u->sent_dbus_new_signal ? send_changed_signal : send_new_signal, u); - if (r < 0) - log_unit_debug_errno(u, r, "Failed to send unit change signal for %s: %m", u->id); - - u->sent_dbus_new_signal = true; -} - -static int send_removed_signal(sd_bus *bus, void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *p = NULL; - Unit *u = userdata; - int r; - - assert(bus); - assert(u); - - p = unit_dbus_path(u); - if (!p) - return -ENOMEM; - - r = sd_bus_message_new_signal( - bus, - &m, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "UnitRemoved"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "so", u->id, p); - if (r < 0) - return r; - - return sd_bus_send(bus, m, NULL); -} - -void bus_unit_send_removed_signal(Unit *u) { - int r; - assert(u); - - if (!u->sent_dbus_new_signal) - bus_unit_send_change_signal(u); - - if (!u->id) - return; - - r = bus_foreach_bus(u->manager, NULL, send_removed_signal, u); - if (r < 0) - log_unit_debug_errno(u, r, "Failed to send unit remove signal for %s: %m", u->id); -} - -int bus_unit_queue_job( - sd_bus_message *message, - Unit *u, - JobType type, - JobMode mode, - bool reload_if_possible, - sd_bus_error *error) { - - _cleanup_free_ char *path = NULL; - Job *j; - int r; - - assert(message); - assert(u); - assert(type >= 0 && type < _JOB_TYPE_MAX); - assert(mode >= 0 && mode < _JOB_MODE_MAX); - - r = mac_selinux_unit_access_check( - u, message, - job_type_to_access_method(type), - error); - if (r < 0) - return r; - - if (reload_if_possible && unit_can_reload(u)) { - if (type == JOB_RESTART) - type = JOB_RELOAD_OR_START; - else if (type == JOB_TRY_RESTART) - type = JOB_TRY_RELOAD; - } - - if (type == JOB_STOP && - (u->load_state == UNIT_NOT_FOUND || u->load_state == UNIT_ERROR) && - unit_active_state(u) == UNIT_INACTIVE) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s not loaded.", u->id); - - if ((type == JOB_START && u->refuse_manual_start) || - (type == JOB_STOP && u->refuse_manual_stop) || - ((type == JOB_RESTART || type == JOB_TRY_RESTART) && (u->refuse_manual_start || u->refuse_manual_stop)) || - (type == JOB_RELOAD_OR_START && job_type_collapse(type, u) == JOB_START && u->refuse_manual_start)) - return sd_bus_error_setf(error, BUS_ERROR_ONLY_BY_DEPENDENCY, "Operation refused, unit %s may be requested by dependency only.", u->id); - - r = manager_add_job(u->manager, type, u, mode, error, &j); - if (r < 0) - return r; - - if (sd_bus_message_get_bus(message) == u->manager->api_bus) { - if (!j->clients) { - r = sd_bus_track_new(sd_bus_message_get_bus(message), &j->clients, NULL, NULL); - if (r < 0) - return r; - } - - r = sd_bus_track_add_sender(j->clients, message); - if (r < 0) - return r; - } - - path = job_dbus_path(j); - if (!path) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", path); -} - -static int bus_unit_set_transient_property( - Unit *u, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - int r; - - assert(u); - assert(name); - assert(message); - - if (streq(name, "Description")) { - const char *d; - - r = sd_bus_message_read(message, "s", &d); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - r = unit_set_description(u, d); - if (r < 0) - return r; - - unit_write_drop_in_format(u, mode, name, "[Unit]\nDescription=%s\n", d); - } - - return 1; - - } else if (streq(name, "DefaultDependencies")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - u->default_dependencies = b; - unit_write_drop_in_format(u, mode, name, "[Unit]\nDefaultDependencies=%s\n", yes_no(b)); - } - - return 1; - - } else if (streq(name, "Slice")) { - Unit *slice; - const char *s; - - if (!UNIT_HAS_CGROUP_CONTEXT(u)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "The slice property is only available for units with control groups."); - if (u->type == UNIT_SLICE) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Slice may not be set for slice units."); - if (unit_has_name(u, SPECIAL_INIT_SCOPE)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot set slice for init.scope"); - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - if (!unit_name_is_valid(s, UNIT_NAME_PLAIN)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid unit name '%s'", s); - - /* Note that we do not dispatch the load queue here yet, as we don't want our own transient unit to be - * loaded while we are still setting it up. Or in other words, we use manager_load_unit_prepare() - * instead of manager_load_unit() on purpose, here. */ - r = manager_load_unit_prepare(u->manager, s, NULL, error, &slice); - if (r < 0) - return r; - - if (slice->type != UNIT_SLICE) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit name '%s' is not a slice", s); - - if (mode != UNIT_CHECK) { - r = unit_set_slice(u, slice); - if (r < 0) - return r; - - unit_write_drop_in_private_format(u, mode, name, "Slice=%s\n", s); - } - - return 1; - - } else if (STR_IN_SET(name, - "Requires", "RequiresOverridable", - "Requisite", "RequisiteOverridable", - "Wants", - "BindsTo", - "Conflicts", - "Before", "After", - "OnFailure", - "PropagatesReloadTo", "ReloadPropagatedFrom", - "PartOf")) { - - UnitDependency d; - const char *other; - - if (streq(name, "RequiresOverridable")) - d = UNIT_REQUIRES; /* redirect for obsolete unit dependency type */ - else if (streq(name, "RequisiteOverridable")) - d = UNIT_REQUISITE; /* same here */ - else { - d = unit_dependency_from_string(name); - if (d < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid unit dependency: %s", name); - } - - r = sd_bus_message_enter_container(message, 'a', "s"); - if (r < 0) - return r; - - while ((r = sd_bus_message_read(message, "s", &other)) > 0) { - if (!unit_name_is_valid(other, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid unit name %s", other); - - if (mode != UNIT_CHECK) { - _cleanup_free_ char *label = NULL; - - r = unit_add_dependency_by_name(u, d, other, NULL, true); - if (r < 0) - return r; - - label = strjoin(name, "-", other, NULL); - if (!label) - return -ENOMEM; - - unit_write_drop_in_format(u, mode, label, "[Unit]\n%s=%s\n", name, other); - } - - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - return 1; - } - - return 0; -} - -int bus_unit_set_properties( - Unit *u, - sd_bus_message *message, - UnitSetPropertiesMode mode, - bool commit, - sd_bus_error *error) { - - bool for_real = false; - unsigned n = 0; - int r; - - assert(u); - assert(message); - - /* We iterate through the array twice. First run we just check - * if all passed data is valid, second run actually applies - * it. This is to implement transaction-like behaviour without - * actually providing full transactions. */ - - r = sd_bus_message_enter_container(message, 'a', "(sv)"); - if (r < 0) - return r; - - for (;;) { - const char *name; - - r = sd_bus_message_enter_container(message, 'r', "sv"); - if (r < 0) - return r; - if (r == 0) { - if (for_real || mode == UNIT_CHECK) - break; - - /* Reached EOF. Let's try again, and this time for realz... */ - r = sd_bus_message_rewind(message, false); - if (r < 0) - return r; - - for_real = true; - continue; - } - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - if (!UNIT_VTABLE(u)->bus_set_property) - return sd_bus_error_setf(error, SD_BUS_ERROR_PROPERTY_READ_ONLY, "Objects of this type do not support setting properties."); - - r = sd_bus_message_enter_container(message, 'v', NULL); - if (r < 0) - return r; - - r = UNIT_VTABLE(u)->bus_set_property(u, name, message, for_real ? mode : UNIT_CHECK, error); - if (r == 0 && u->transient && u->load_state == UNIT_STUB) - r = bus_unit_set_transient_property(u, name, message, for_real ? mode : UNIT_CHECK, error); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_PROPERTY_READ_ONLY, "Cannot set property %s, or unknown property.", name); - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - n += for_real; - } - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (commit && n > 0 && UNIT_VTABLE(u)->bus_commit_properties) - UNIT_VTABLE(u)->bus_commit_properties(u); - - return n; -} - -int bus_unit_check_load_state(Unit *u, sd_bus_error *error) { - assert(u); - - if (u->load_state == UNIT_LOADED) - return 0; - - /* Give a better description of the unit error when - * possible. Note that in the case of UNIT_MASKED, load_error - * is not set. */ - if (u->load_state == UNIT_MASKED) - return sd_bus_error_setf(error, BUS_ERROR_UNIT_MASKED, "Unit %s is masked.", u->id); - - if (u->load_state == UNIT_NOT_FOUND) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s not found.", u->id); - - return sd_bus_error_set_errnof(error, u->load_error, "Unit %s is not loaded properly: %m.", u->id); -} diff --git a/src/core/dbus-unit.h b/src/core/dbus-unit.h deleted file mode 100644 index 4db88dbebc..0000000000 --- a/src/core/dbus-unit.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "sd-bus.h" - -#include "unit.h" - -extern const sd_bus_vtable bus_unit_vtable[]; -extern const sd_bus_vtable bus_unit_cgroup_vtable[]; - -void bus_unit_send_change_signal(Unit *u); -void bus_unit_send_removed_signal(Unit *u); - -int bus_unit_method_start_generic(sd_bus_message *message, Unit *u, JobType job_type, bool reload_if_possible, sd_bus_error *error); -int bus_unit_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_unit_method_reset_failed(sd_bus_message *message, void *userdata, sd_bus_error *error); - -int bus_unit_queue_job(sd_bus_message *message, Unit *u, JobType type, JobMode mode, bool reload_if_possible, sd_bus_error *error); -int bus_unit_set_properties(Unit *u, sd_bus_message *message, UnitSetPropertiesMode mode, bool commit, sd_bus_error *error); -int bus_unit_method_set_properties(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_unit_method_get_processes(sd_bus_message *message, void *userdata, sd_bus_error *error); - -int bus_unit_check_load_state(Unit *u, sd_bus_error *error); diff --git a/src/core/dbus.c b/src/core/dbus.c deleted file mode 100644 index 3422a02d68..0000000000 --- a/src/core/dbus.c +++ /dev/null @@ -1,1243 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-error.h" -#include "bus-internal.h" -#include "bus-util.h" -#include "dbus-cgroup.h" -#include "dbus-execute.h" -#include "dbus-job.h" -#include "dbus-kill.h" -#include "dbus-manager.h" -#include "dbus-unit.h" -#include "dbus.h" -#include "fd-util.h" -#include "log.h" -#include "missing.h" -#include "mkdir.h" -#include "selinux-access.h" -#include "special.h" -#include "string-util.h" -#include "strv.h" -#include "strxcpyx.h" -#include "user-util.h" - -#define CONNECTIONS_MAX 4096 - -static void destroy_bus(Manager *m, sd_bus **bus); - -int bus_send_queued_message(Manager *m) { - int r; - - assert(m); - - if (!m->queued_message) - return 0; - - /* If we cannot get rid of this message we won't dispatch any - * D-Bus messages, so that we won't end up wanting to queue - * another message. */ - - r = sd_bus_send(NULL, m->queued_message, NULL); - if (r < 0) - log_warning_errno(r, "Failed to send queued message: %m"); - - m->queued_message = sd_bus_message_unref(m->queued_message); - - return 0; -} - -int bus_forward_agent_released(Manager *m, const char *path) { - int r; - - assert(m); - assert(path); - - if (!MANAGER_IS_SYSTEM(m)) - return 0; - - if (!m->system_bus) - return 0; - - /* If we are running a system instance we forward the agent message on the system bus, so that the user - * instances get notified about this, too */ - - r = sd_bus_emit_signal(m->system_bus, - "/org/freedesktop/systemd1/agent", - "org.freedesktop.systemd1.Agent", - "Released", - "s", path); - if (r < 0) - return log_warning_errno(r, "Failed to propagate agent release message: %m"); - - return 1; -} - -static int signal_agent_released(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - Manager *m = userdata; - const char *cgroup; - uid_t sender_uid; - int r; - - assert(message); - assert(m); - - /* only accept org.freedesktop.systemd1.Agent from UID=0 */ - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_euid(creds, &sender_uid); - if (r < 0 || sender_uid != 0) - return 0; - - /* parse 'cgroup-empty' notification */ - r = sd_bus_message_read(message, "s", &cgroup); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - manager_notify_cgroup_empty(m, cgroup); - return 0; -} - -static int signal_disconnected(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - sd_bus *bus; - - assert(message); - assert(m); - assert_se(bus = sd_bus_message_get_bus(message)); - - if (bus == m->api_bus) - destroy_bus(m, &m->api_bus); - if (bus == m->system_bus) - destroy_bus(m, &m->system_bus); - if (set_remove(m->private_buses, bus)) { - log_debug("Got disconnect on private connection."); - destroy_bus(m, &bus); - } - - return 0; -} - -static int signal_activation_request(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - Manager *m = userdata; - const char *name; - Unit *u; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - if (manager_unit_inactive_or_pending(m, SPECIAL_DBUS_SERVICE) || - manager_unit_inactive_or_pending(m, SPECIAL_DBUS_SOCKET)) { - r = sd_bus_error_setf(&error, BUS_ERROR_SHUTTING_DOWN, "Refusing activation, D-Bus is shutting down."); - goto failed; - } - - r = manager_load_unit(m, name, NULL, &error, &u); - if (r < 0) - goto failed; - - if (u->refuse_manual_start) { - r = sd_bus_error_setf(&error, BUS_ERROR_ONLY_BY_DEPENDENCY, "Operation refused, %s may be requested by dependency only.", u->id); - goto failed; - } - - r = manager_add_job(m, JOB_START, u, JOB_REPLACE, &error, NULL); - if (r < 0) - goto failed; - - /* Successfully queued, that's it for us */ - return 0; - -failed: - if (!sd_bus_error_is_set(&error)) - sd_bus_error_set_errno(&error, r); - - log_debug("D-Bus activation failed for %s: %s", name, bus_error_message(&error, r)); - - r = sd_bus_message_new_signal(sd_bus_message_get_bus(message), &reply, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Activator", "ActivationFailure"); - if (r < 0) { - bus_log_create_error(r); - return 0; - } - - r = sd_bus_message_append(reply, "sss", name, error.name, error.message); - if (r < 0) { - bus_log_create_error(r); - return 0; - } - - r = sd_bus_send_to(NULL, reply, "org.freedesktop.DBus", NULL); - if (r < 0) - return log_error_errno(r, "Failed to respond with to bus activation request: %m"); - - return 0; -} - -#ifdef HAVE_SELINUX -static int mac_selinux_filter(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - const char *verb, *path; - Unit *u = NULL; - Job *j; - int r; - - assert(message); - - /* Our own method calls are all protected individually with - * selinux checks, but the built-in interfaces need to be - * protected too. */ - - if (sd_bus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "Set")) - verb = "reload"; - else if (sd_bus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", NULL) || - sd_bus_message_is_method_call(message, "org.freedesktop.DBus.Properties", NULL) || - sd_bus_message_is_method_call(message, "org.freedesktop.DBus.ObjectManager", NULL) || - sd_bus_message_is_method_call(message, "org.freedesktop.DBus.Peer", NULL)) - verb = "status"; - else - return 0; - - path = sd_bus_message_get_path(message); - - if (object_path_startswith("/org/freedesktop/systemd1", path)) { - - r = mac_selinux_access_check(message, verb, error); - if (r < 0) - return r; - - return 0; - } - - if (streq_ptr(path, "/org/freedesktop/systemd1/unit/self")) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - pid_t pid; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return 0; - - r = sd_bus_creds_get_pid(creds, &pid); - if (r < 0) - return 0; - - u = manager_get_unit_by_pid(m, pid); - } else { - r = manager_get_job_from_dbus_path(m, path, &j); - if (r >= 0) - u = j->unit; - else - manager_load_unit_from_dbus_path(m, path, NULL, &u); - } - - if (!u) - return 0; - - r = mac_selinux_unit_access_check(u, message, verb, error); - if (r < 0) - return r; - - return 0; -} -#endif - -static int bus_job_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - Manager *m = userdata; - Job *j; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - r = manager_get_job_from_dbus_path(m, path, &j); - if (r < 0) - return 0; - - *found = j; - return 1; -} - -static int find_unit(Manager *m, sd_bus *bus, const char *path, Unit **unit, sd_bus_error *error) { - Unit *u; - int r; - - assert(m); - assert(bus); - assert(path); - - if (streq_ptr(path, "/org/freedesktop/systemd1/unit/self")) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - sd_bus_message *message; - pid_t pid; - - message = sd_bus_get_current_message(bus); - if (!message) - return 0; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_pid(creds, &pid); - if (r < 0) - return r; - - u = manager_get_unit_by_pid(m, pid); - } else { - r = manager_load_unit_from_dbus_path(m, path, error, &u); - if (r < 0) - return 0; - } - - if (!u) - return 0; - - *unit = u; - return 1; -} - -static int bus_unit_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - Manager *m = userdata; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - return find_unit(m, bus, path, (Unit**) found, error); -} - -static int bus_unit_interface_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - Manager *m = userdata; - Unit *u; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - r = find_unit(m, bus, path, &u, error); - if (r <= 0) - return r; - - if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) - return 0; - - *found = u; - return 1; -} - -static int bus_unit_cgroup_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - Manager *m = userdata; - Unit *u; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - r = find_unit(m, bus, path, &u, error); - if (r <= 0) - return r; - - if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) - return 0; - - if (!UNIT_HAS_CGROUP_CONTEXT(u)) - return 0; - - *found = u; - return 1; -} - -static int bus_cgroup_context_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - Manager *m = userdata; - CGroupContext *c; - Unit *u; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - r = find_unit(m, bus, path, &u, error); - if (r <= 0) - return r; - - if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) - return 0; - - c = unit_get_cgroup_context(u); - if (!c) - return 0; - - *found = c; - return 1; -} - -static int bus_exec_context_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - Manager *m = userdata; - ExecContext *c; - Unit *u; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - r = find_unit(m, bus, path, &u, error); - if (r <= 0) - return r; - - if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) - return 0; - - c = unit_get_exec_context(u); - if (!c) - return 0; - - *found = c; - return 1; -} - -static int bus_kill_context_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - Manager *m = userdata; - KillContext *c; - Unit *u; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - r = find_unit(m, bus, path, &u, error); - if (r <= 0) - return r; - - if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) - return 0; - - c = unit_get_kill_context(u); - if (!c) - return 0; - - *found = c; - return 1; -} - -static int bus_job_enumerate(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { - _cleanup_free_ char **l = NULL; - Manager *m = userdata; - unsigned k = 0; - Iterator i; - Job *j; - - l = new0(char*, hashmap_size(m->jobs)+1); - if (!l) - return -ENOMEM; - - HASHMAP_FOREACH(j, m->jobs, i) { - l[k] = job_dbus_path(j); - if (!l[k]) - return -ENOMEM; - - k++; - } - - assert(hashmap_size(m->jobs) == k); - - *nodes = l; - l = NULL; - - return k; -} - -static int bus_unit_enumerate(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { - _cleanup_free_ char **l = NULL; - Manager *m = userdata; - unsigned k = 0; - Iterator i; - Unit *u; - - l = new0(char*, hashmap_size(m->units)+1); - if (!l) - return -ENOMEM; - - HASHMAP_FOREACH(u, m->units, i) { - l[k] = unit_dbus_path(u); - if (!l[k]) - return -ENOMEM; - - k++; - } - - *nodes = l; - l = NULL; - - return k; -} - -static int bus_setup_api_vtables(Manager *m, sd_bus *bus) { - UnitType t; - int r; - - assert(m); - assert(bus); - -#ifdef HAVE_SELINUX - r = sd_bus_add_filter(bus, NULL, mac_selinux_filter, m); - if (r < 0) - return log_error_errno(r, "Failed to add SELinux access filter: %m"); -#endif - - r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", bus_manager_vtable, m); - if (r < 0) - return log_error_errno(r, "Failed to register Manager vtable: %m"); - - r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/job", "org.freedesktop.systemd1.Job", bus_job_vtable, bus_job_find, m); - if (r < 0) - return log_error_errno(r, "Failed to register Job vtable: %m"); - - r = sd_bus_add_node_enumerator(bus, NULL, "/org/freedesktop/systemd1/job", bus_job_enumerate, m); - if (r < 0) - return log_error_errno(r, "Failed to add job enumerator: %m"); - - r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", "org.freedesktop.systemd1.Unit", bus_unit_vtable, bus_unit_find, m); - if (r < 0) - return log_error_errno(r, "Failed to register Unit vtable: %m"); - - r = sd_bus_add_node_enumerator(bus, NULL, "/org/freedesktop/systemd1/unit", bus_unit_enumerate, m); - if (r < 0) - return log_error_errno(r, "Failed to add job enumerator: %m"); - - for (t = 0; t < _UNIT_TYPE_MAX; t++) { - const char *interface; - - assert_se(interface = unit_dbus_interface_from_type(t)); - - r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, unit_vtable[t]->bus_vtable, bus_unit_interface_find, m); - if (r < 0) - return log_error_errno(r, "Failed to register type specific vtable for %s: %m", interface); - - if (unit_vtable[t]->cgroup_context_offset > 0) { - r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, bus_unit_cgroup_vtable, bus_unit_cgroup_find, m); - if (r < 0) - return log_error_errno(r, "Failed to register control group unit vtable for %s: %m", interface); - - r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, bus_cgroup_vtable, bus_cgroup_context_find, m); - if (r < 0) - return log_error_errno(r, "Failed to register control group vtable for %s: %m", interface); - } - - if (unit_vtable[t]->exec_context_offset > 0) { - r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, bus_exec_vtable, bus_exec_context_find, m); - if (r < 0) - return log_error_errno(r, "Failed to register execute vtable for %s: %m", interface); - } - - if (unit_vtable[t]->kill_context_offset > 0) { - r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, bus_kill_vtable, bus_kill_context_find, m); - if (r < 0) - return log_error_errno(r, "Failed to register kill vtable for %s: %m", interface); - } - } - - return 0; -} - -static int bus_setup_disconnected_match(Manager *m, sd_bus *bus) { - int r; - - assert(m); - assert(bus); - - r = sd_bus_add_match( - bus, - NULL, - "sender='org.freedesktop.DBus.Local'," - "type='signal'," - "path='/org/freedesktop/DBus/Local'," - "interface='org.freedesktop.DBus.Local'," - "member='Disconnected'", - signal_disconnected, m); - - if (r < 0) - return log_error_errno(r, "Failed to register match for Disconnected message: %m"); - - return 0; -} - -static int bus_on_connection(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - _cleanup_close_ int nfd = -1; - Manager *m = userdata; - sd_id128_t id; - int r; - - assert(s); - assert(m); - - nfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); - if (nfd < 0) { - log_warning_errno(errno, "Failed to accept private connection, ignoring: %m"); - return 0; - } - - if (set_size(m->private_buses) >= CONNECTIONS_MAX) { - log_warning("Too many concurrent connections, refusing"); - return 0; - } - - r = set_ensure_allocated(&m->private_buses, NULL); - if (r < 0) { - log_oom(); - return 0; - } - - r = sd_bus_new(&bus); - if (r < 0) { - log_warning_errno(r, "Failed to allocate new private connection bus: %m"); - return 0; - } - - r = sd_bus_set_fd(bus, nfd, nfd); - if (r < 0) { - log_warning_errno(r, "Failed to set fd on new connection bus: %m"); - return 0; - } - - nfd = -1; - - r = bus_check_peercred(bus); - if (r < 0) { - log_warning_errno(r, "Incoming private connection from unprivileged client, refusing: %m"); - return 0; - } - - assert_se(sd_id128_randomize(&id) >= 0); - - r = sd_bus_set_server(bus, 1, id); - if (r < 0) { - log_warning_errno(r, "Failed to enable server support for new connection bus: %m"); - return 0; - } - - r = sd_bus_negotiate_creds(bus, 1, - SD_BUS_CREDS_PID|SD_BUS_CREDS_UID| - SD_BUS_CREDS_EUID|SD_BUS_CREDS_EFFECTIVE_CAPS| - SD_BUS_CREDS_SELINUX_CONTEXT); - if (r < 0) { - log_warning_errno(r, "Failed to enable credentials for new connection: %m"); - return 0; - } - - r = sd_bus_start(bus); - if (r < 0) { - log_warning_errno(r, "Failed to start new connection bus: %m"); - return 0; - } - - r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL); - if (r < 0) { - log_warning_errno(r, "Failed to attach new connection bus to event loop: %m"); - return 0; - } - - r = bus_setup_disconnected_match(m, bus); - if (r < 0) - return 0; - - r = bus_setup_api_vtables(m, bus); - if (r < 0) { - log_warning_errno(r, "Failed to set up API vtables on new connection bus: %m"); - return 0; - } - - r = set_put(m->private_buses, bus); - if (r < 0) { - log_warning_errno(r, "Failed to add new connection bus to set: %m"); - return 0; - } - - bus = NULL; - - log_debug("Accepted new private connection."); - - return 0; -} - -int manager_sync_bus_names(Manager *m, sd_bus *bus) { - _cleanup_strv_free_ char **names = NULL; - const char *name; - Iterator i; - Unit *u; - int r; - - assert(m); - assert(bus); - - r = sd_bus_list_names(bus, &names, NULL); - if (r < 0) - return log_error_errno(r, "Failed to get initial list of names: %m"); - - /* We have to synchronize the current bus names with the - * list of active services. To do this, walk the list of - * all units with bus names. */ - HASHMAP_FOREACH_KEY(u, name, m->watch_bus, i) { - Service *s = SERVICE(u); - - assert(s); - - if (!streq_ptr(s->bus_name, name)) { - log_unit_warning(u, "Bus name has changed from %s → %s, ignoring.", s->bus_name, name); - continue; - } - - /* Check if a service's bus name is in the list of currently - * active names */ - if (strv_contains(names, name)) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - const char *unique; - - /* If it is, determine its current owner */ - r = sd_bus_get_name_creds(bus, name, SD_BUS_CREDS_UNIQUE_NAME, &creds); - if (r < 0) { - log_error_errno(r, "Failed to get bus name owner %s: %m", name); - continue; - } - - r = sd_bus_creds_get_unique_name(creds, &unique); - if (r < 0) { - log_error_errno(r, "Failed to get unique name for %s: %m", name); - continue; - } - - /* Now, let's compare that to the previous bus owner, and - * if it's still the same, all is fine, so just don't - * bother the service. Otherwise, the name has apparently - * changed, so synthesize a name owner changed signal. */ - - if (!streq_ptr(unique, s->bus_name_owner)) - UNIT_VTABLE(u)->bus_name_owner_change(u, name, s->bus_name_owner, unique); - } else { - /* So, the name we're watching is not on the bus. - * This either means it simply hasn't appeared yet, - * or it was lost during the daemon reload. - * Check if the service has a stored name owner, - * and synthesize a name loss signal in this case. */ - - if (s->bus_name_owner) - UNIT_VTABLE(u)->bus_name_owner_change(u, name, s->bus_name_owner, NULL); - } - } - - return 0; -} - -static int bus_setup_api(Manager *m, sd_bus *bus) { - Iterator i; - char *name; - Unit *u; - int r; - - assert(m); - assert(bus); - - /* Let's make sure we have enough credential bits so that we can make security and selinux decisions */ - r = sd_bus_negotiate_creds(bus, 1, - SD_BUS_CREDS_PID|SD_BUS_CREDS_UID| - SD_BUS_CREDS_EUID|SD_BUS_CREDS_EFFECTIVE_CAPS| - SD_BUS_CREDS_SELINUX_CONTEXT); - if (r < 0) - log_warning_errno(r, "Failed to enable credential passing, ignoring: %m"); - - r = bus_setup_api_vtables(m, bus); - if (r < 0) - return r; - - HASHMAP_FOREACH_KEY(u, name, m->watch_bus, i) { - r = unit_install_bus_match(u, bus, name); - if (r < 0) - log_error_errno(r, "Failed to subscribe to NameOwnerChanged signal for '%s': %m", name); - } - - r = sd_bus_add_match( - bus, - NULL, - "type='signal'," - "sender='org.freedesktop.DBus'," - "path='/org/freedesktop/DBus'," - "interface='org.freedesktop.systemd1.Activator'," - "member='ActivationRequest'", - signal_activation_request, m); - if (r < 0) - log_warning_errno(r, "Failed to subscribe to activation signal: %m"); - - /* Allow replacing of our name, to ease implementation of - * reexecution, where we keep the old connection open until - * after the new connection is set up and the name installed - * to allow clients to synchronously wait for reexecution to - * finish */ - r = sd_bus_request_name(bus,"org.freedesktop.systemd1", SD_BUS_NAME_REPLACE_EXISTING|SD_BUS_NAME_ALLOW_REPLACEMENT); - if (r < 0) - return log_error_errno(r, "Failed to register name: %m"); - - r = manager_sync_bus_names(m, bus); - if (r < 0) - return r; - - log_debug("Successfully connected to API bus."); - return 0; -} - -static int bus_init_api(Manager *m) { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - int r; - - if (m->api_bus) - return 0; - - /* The API and system bus is the same if we are running in system mode */ - if (MANAGER_IS_SYSTEM(m) && m->system_bus) - bus = sd_bus_ref(m->system_bus); - else { - if (MANAGER_IS_SYSTEM(m)) - r = sd_bus_open_system(&bus); - else - r = sd_bus_open_user(&bus); - - if (r < 0) { - log_debug("Failed to connect to API bus, retrying later..."); - return 0; - } - - r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL); - if (r < 0) { - log_error_errno(r, "Failed to attach API bus to event loop: %m"); - return 0; - } - - r = bus_setup_disconnected_match(m, bus); - if (r < 0) - return 0; - } - - r = bus_setup_api(m, bus); - if (r < 0) { - log_error_errno(r, "Failed to set up API bus: %m"); - return 0; - } - - m->api_bus = bus; - bus = NULL; - - return 0; -} - -static int bus_setup_system(Manager *m, sd_bus *bus) { - int r; - - assert(m); - assert(bus); - - /* if we are a user instance we get the Released message via the system bus */ - if (MANAGER_IS_USER(m)) { - r = sd_bus_add_match( - bus, - NULL, - "type='signal'," - "interface='org.freedesktop.systemd1.Agent'," - "member='Released'," - "path='/org/freedesktop/systemd1/agent'", - signal_agent_released, m); - if (r < 0) - log_warning_errno(r, "Failed to register Released match on system bus: %m"); - } - - log_debug("Successfully connected to system bus."); - return 0; -} - -static int bus_init_system(Manager *m) { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - int r; - - if (m->system_bus) - return 0; - - /* The API and system bus is the same if we are running in system mode */ - if (MANAGER_IS_SYSTEM(m) && m->api_bus) { - m->system_bus = sd_bus_ref(m->api_bus); - return 0; - } - - r = sd_bus_open_system(&bus); - if (r < 0) { - log_debug("Failed to connect to system bus, retrying later..."); - return 0; - } - - r = bus_setup_disconnected_match(m, bus); - if (r < 0) - return 0; - - r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL); - if (r < 0) { - log_error_errno(r, "Failed to attach system bus to event loop: %m"); - return 0; - } - - r = bus_setup_system(m, bus); - if (r < 0) { - log_error_errno(r, "Failed to set up system bus: %m"); - return 0; - } - - m->system_bus = bus; - bus = NULL; - - return 0; -} - -static int bus_init_private(Manager *m) { - _cleanup_close_ int fd = -1; - union sockaddr_union sa = { - .un.sun_family = AF_UNIX - }; - sd_event_source *s; - socklen_t salen; - int r; - - assert(m); - - if (m->private_listen_fd >= 0) - return 0; - - /* We don't need the private socket if we have kdbus */ - if (m->kdbus_fd >= 0) - return 0; - - if (MANAGER_IS_SYSTEM(m)) { - - /* We want the private bus only when running as init */ - if (getpid() != 1) - return 0; - - strcpy(sa.un.sun_path, "/run/systemd/private"); - salen = SOCKADDR_UN_LEN(sa.un); - } else { - size_t left = sizeof(sa.un.sun_path); - char *p = sa.un.sun_path; - const char *e; - - e = secure_getenv("XDG_RUNTIME_DIR"); - if (!e) { - log_error("Failed to determine XDG_RUNTIME_DIR"); - return -EHOSTDOWN; - } - - left = strpcpy(&p, left, e); - left = strpcpy(&p, left, "/systemd/private"); - - salen = sizeof(sa.un) - left; - } - - (void) mkdir_parents_label(sa.un.sun_path, 0755); - (void) unlink(sa.un.sun_path); - - fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (fd < 0) - return log_error_errno(errno, "Failed to allocate private socket: %m"); - - r = bind(fd, &sa.sa, salen); - if (r < 0) - return log_error_errno(errno, "Failed to bind private socket: %m"); - - r = listen(fd, SOMAXCONN); - if (r < 0) - return log_error_errno(errno, "Failed to make private socket listening: %m"); - - r = sd_event_add_io(m->event, &s, fd, EPOLLIN, bus_on_connection, m); - if (r < 0) - return log_error_errno(r, "Failed to allocate event source: %m"); - - (void) sd_event_source_set_description(s, "bus-connection"); - - m->private_listen_fd = fd; - m->private_listen_event_source = s; - fd = -1; - - log_debug("Successfully created private D-Bus server."); - - return 0; -} - -int bus_init(Manager *m, bool try_bus_connect) { - int r; - - if (try_bus_connect) { - r = bus_init_system(m); - if (r < 0) - return r; - - r = bus_init_api(m); - if (r < 0) - return r; - } - - r = bus_init_private(m); - if (r < 0) - return r; - - return 0; -} - -static void destroy_bus(Manager *m, sd_bus **bus) { - Iterator i; - Job *j; - - assert(m); - assert(bus); - - if (!*bus) - return; - - /* Get rid of tracked clients on this bus */ - if (m->subscribed && sd_bus_track_get_bus(m->subscribed) == *bus) - m->subscribed = sd_bus_track_unref(m->subscribed); - - HASHMAP_FOREACH(j, m->jobs, i) - if (j->clients && sd_bus_track_get_bus(j->clients) == *bus) - j->clients = sd_bus_track_unref(j->clients); - - /* Get rid of queued message on this bus */ - if (m->queued_message && sd_bus_message_get_bus(m->queued_message) == *bus) - m->queued_message = sd_bus_message_unref(m->queued_message); - - /* Possibly flush unwritten data, but only if we are - * unprivileged, since we don't want to sync here */ - if (!MANAGER_IS_SYSTEM(m)) - sd_bus_flush(*bus); - - /* And destroy the object */ - sd_bus_close(*bus); - *bus = sd_bus_unref(*bus); -} - -void bus_done(Manager *m) { - sd_bus *b; - - assert(m); - - if (m->api_bus) - destroy_bus(m, &m->api_bus); - if (m->system_bus) - destroy_bus(m, &m->system_bus); - while ((b = set_steal_first(m->private_buses))) - destroy_bus(m, &b); - - m->private_buses = set_free(m->private_buses); - - m->subscribed = sd_bus_track_unref(m->subscribed); - m->deserialized_subscribed = strv_free(m->deserialized_subscribed); - - if (m->private_listen_event_source) - m->private_listen_event_source = sd_event_source_unref(m->private_listen_event_source); - - m->private_listen_fd = safe_close(m->private_listen_fd); - - bus_verify_polkit_async_registry_free(m->polkit_registry); -} - -int bus_fdset_add_all(Manager *m, FDSet *fds) { - Iterator i; - sd_bus *b; - int fd; - - assert(m); - assert(fds); - - /* When we are about to reexecute we add all D-Bus fds to the - * set to pass over to the newly executed systemd. They won't - * be used there however, except thatt they are closed at the - * very end of deserialization, those making it possible for - * clients to synchronously wait for systemd to reexec by - * simply waiting for disconnection */ - - if (m->api_bus) { - fd = sd_bus_get_fd(m->api_bus); - if (fd >= 0) { - fd = fdset_put_dup(fds, fd); - if (fd < 0) - return fd; - } - } - - SET_FOREACH(b, m->private_buses, i) { - fd = sd_bus_get_fd(b); - if (fd >= 0) { - fd = fdset_put_dup(fds, fd); - if (fd < 0) - return fd; - } - } - - /* We don't offer any APIs on the system bus (well, unless it - * is the same as the API bus) hence we don't bother with it - * here */ - - return 0; -} - -int bus_foreach_bus( - Manager *m, - sd_bus_track *subscribed2, - int (*send_message)(sd_bus *bus, void *userdata), - void *userdata) { - - Iterator i; - sd_bus *b; - int r, ret = 0; - - /* Send to all direct buses, unconditionally */ - SET_FOREACH(b, m->private_buses, i) { - r = send_message(b, userdata); - if (r < 0) - ret = r; - } - - /* Send to API bus, but only if somebody is subscribed */ - if (sd_bus_track_count(m->subscribed) > 0 || - sd_bus_track_count(subscribed2) > 0) { - r = send_message(m->api_bus, userdata); - if (r < 0) - ret = r; - } - - return ret; -} - -void bus_track_serialize(sd_bus_track *t, FILE *f) { - const char *n; - - assert(f); - - for (n = sd_bus_track_first(t); n; n = sd_bus_track_next(t)) - fprintf(f, "subscribed=%s\n", n); -} - -int bus_track_deserialize_item(char ***l, const char *line) { - const char *e; - int r; - - assert(l); - assert(line); - - e = startswith(line, "subscribed="); - if (!e) - return 0; - - r = strv_extend(l, e); - if (r < 0) - return r; - - return 1; -} - -int bus_track_coldplug(Manager *m, sd_bus_track **t, char ***l) { - int r = 0; - - assert(m); - assert(t); - assert(l); - - if (!strv_isempty(*l) && m->api_bus) { - char **i; - - if (!*t) { - r = sd_bus_track_new(m->api_bus, t, NULL, NULL); - if (r < 0) - return r; - } - - r = 0; - STRV_FOREACH(i, *l) { - int k; - - k = sd_bus_track_add_name(*t, *i); - if (k < 0) - r = k; - } - } - - *l = strv_free(*l); - - return r; -} - -int bus_verify_manage_units_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { - return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.manage-units", NULL, false, UID_INVALID, &m->polkit_registry, error); -} - -int bus_verify_manage_unit_files_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { - return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.manage-unit-files", NULL, false, UID_INVALID, &m->polkit_registry, error); -} - -int bus_verify_reload_daemon_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { - return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.reload-daemon", NULL, false, UID_INVALID, &m->polkit_registry, error); -} - -int bus_verify_set_environment_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { - return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.set-environment", NULL, false, UID_INVALID, &m->polkit_registry, error); -} diff --git a/src/core/dbus.h b/src/core/dbus.h deleted file mode 100644 index 6baaffbd75..0000000000 --- a/src/core/dbus.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "manager.h" - -int bus_send_queued_message(Manager *m); - -int bus_init(Manager *m, bool try_bus_connect); -void bus_done(Manager *m); - -int bus_fdset_add_all(Manager *m, FDSet *fds); - -void bus_track_serialize(sd_bus_track *t, FILE *f); -int bus_track_deserialize_item(char ***l, const char *line); -int bus_track_coldplug(Manager *m, sd_bus_track **t, char ***l); - -int manager_sync_bus_names(Manager *m, sd_bus *bus); - -int bus_foreach_bus(Manager *m, sd_bus_track *subscribed2, int (*send_message)(sd_bus *bus, void *userdata), void *userdata); - -int bus_verify_manage_units_async(Manager *m, sd_bus_message *call, sd_bus_error *error); -int bus_verify_manage_unit_files_async(Manager *m, sd_bus_message *call, sd_bus_error *error); -int bus_verify_reload_daemon_async(Manager *m, sd_bus_message *call, sd_bus_error *error); -int bus_verify_set_environment_async(Manager *m, sd_bus_message *call, sd_bus_error *error); - -int bus_forward_agent_released(Manager *m, const char *path); diff --git a/src/core/device.c b/src/core/device.c deleted file mode 100644 index 16e56efcc3..0000000000 --- a/src/core/device.c +++ /dev/null @@ -1,876 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 - -#include "libudev.h" - -#include "alloc-util.h" -#include "dbus-device.h" -#include "device.h" -#include "log.h" -#include "parse-util.h" -#include "path-util.h" -#include "stat-util.h" -#include "string-util.h" -#include "swap.h" -#include "udev-util.h" -#include "unit-name.h" -#include "unit.h" - -static const UnitActiveState state_translation_table[_DEVICE_STATE_MAX] = { - [DEVICE_DEAD] = UNIT_INACTIVE, - [DEVICE_TENTATIVE] = UNIT_ACTIVATING, - [DEVICE_PLUGGED] = UNIT_ACTIVE, -}; - -static int device_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); - -static void device_unset_sysfs(Device *d) { - Hashmap *devices; - Device *first; - - assert(d); - - if (!d->sysfs) - return; - - /* Remove this unit from the chain of devices which share the - * same sysfs path. */ - devices = UNIT(d)->manager->devices_by_sysfs; - first = hashmap_get(devices, d->sysfs); - LIST_REMOVE(same_sysfs, first, d); - - if (first) - hashmap_remove_and_replace(devices, d->sysfs, first->sysfs, first); - else - hashmap_remove(devices, d->sysfs); - - d->sysfs = mfree(d->sysfs); -} - -static int device_set_sysfs(Device *d, const char *sysfs) { - Device *first; - char *copy; - int r; - - assert(d); - - if (streq_ptr(d->sysfs, sysfs)) - return 0; - - r = hashmap_ensure_allocated(&UNIT(d)->manager->devices_by_sysfs, &string_hash_ops); - if (r < 0) - return r; - - copy = strdup(sysfs); - if (!copy) - return -ENOMEM; - - device_unset_sysfs(d); - - first = hashmap_get(UNIT(d)->manager->devices_by_sysfs, sysfs); - LIST_PREPEND(same_sysfs, first, d); - - r = hashmap_replace(UNIT(d)->manager->devices_by_sysfs, copy, first); - if (r < 0) { - LIST_REMOVE(same_sysfs, first, d); - free(copy); - return r; - } - - d->sysfs = copy; - - return 0; -} - -static void device_init(Unit *u) { - Device *d = DEVICE(u); - - assert(d); - assert(UNIT(d)->load_state == UNIT_STUB); - - /* In contrast to all other unit types we timeout jobs waiting - * for devices by default. This is because they otherwise wait - * indefinitely for plugged in devices, something which cannot - * happen for the other units since their operations time out - * anyway. */ - u->job_timeout = u->manager->default_timeout_start_usec; - - u->ignore_on_isolate = true; -} - -static void device_done(Unit *u) { - Device *d = DEVICE(u); - - assert(d); - - device_unset_sysfs(d); -} - -static void device_set_state(Device *d, DeviceState state) { - DeviceState old_state; - assert(d); - - old_state = d->state; - d->state = state; - - if (state != old_state) - log_unit_debug(UNIT(d), "Changed %s -> %s", device_state_to_string(old_state), device_state_to_string(state)); - - unit_notify(UNIT(d), state_translation_table[old_state], state_translation_table[state], true); -} - -static int device_coldplug(Unit *u) { - Device *d = DEVICE(u); - - assert(d); - assert(d->state == DEVICE_DEAD); - - if (d->found & DEVICE_FOUND_UDEV) - /* If udev says the device is around, it's around */ - device_set_state(d, DEVICE_PLUGGED); - else if (d->found != DEVICE_NOT_FOUND && d->deserialized_state != DEVICE_PLUGGED) - /* If a device is found in /proc/self/mountinfo or - * /proc/swaps, and was not yet announced via udev, - * it's "tentatively" around. */ - device_set_state(d, DEVICE_TENTATIVE); - - return 0; -} - -static int device_serialize(Unit *u, FILE *f, FDSet *fds) { - Device *d = DEVICE(u); - - assert(u); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", device_state_to_string(d->state)); - - return 0; -} - -static int device_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Device *d = DEVICE(u); - - assert(u); - assert(key); - assert(value); - assert(fds); - - if (streq(key, "state")) { - DeviceState state; - - state = device_state_from_string(value); - if (state < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - d->deserialized_state = state; - } else - log_unit_debug(u, "Unknown serialization key: %s", key); - - return 0; -} - -static void device_dump(Unit *u, FILE *f, const char *prefix) { - Device *d = DEVICE(u); - - assert(d); - - fprintf(f, - "%sDevice State: %s\n" - "%sSysfs Path: %s\n", - prefix, device_state_to_string(d->state), - prefix, strna(d->sysfs)); -} - -_pure_ static UnitActiveState device_active_state(Unit *u) { - assert(u); - - return state_translation_table[DEVICE(u)->state]; -} - -_pure_ static const char *device_sub_state_to_string(Unit *u) { - assert(u); - - return device_state_to_string(DEVICE(u)->state); -} - -static int device_update_description(Unit *u, struct udev_device *dev, const char *path) { - const char *model; - int r; - - assert(u); - assert(dev); - assert(path); - - model = udev_device_get_property_value(dev, "ID_MODEL_FROM_DATABASE"); - if (!model) - model = udev_device_get_property_value(dev, "ID_MODEL"); - - if (model) { - const char *label; - - /* Try to concatenate the device model string with a label, if there is one */ - label = udev_device_get_property_value(dev, "ID_FS_LABEL"); - if (!label) - label = udev_device_get_property_value(dev, "ID_PART_ENTRY_NAME"); - if (!label) - label = udev_device_get_property_value(dev, "ID_PART_ENTRY_NUMBER"); - - if (label) { - _cleanup_free_ char *j; - - j = strjoin(model, " ", label, NULL); - if (j) - r = unit_set_description(u, j); - else - r = -ENOMEM; - } else - r = unit_set_description(u, model); - } else - r = unit_set_description(u, path); - - if (r < 0) - log_unit_error_errno(u, r, "Failed to set device description: %m"); - - return r; -} - -static int device_add_udev_wants(Unit *u, struct udev_device *dev) { - const char *wants; - const char *word, *state; - size_t l; - int r; - const char *property; - - assert(u); - assert(dev); - - property = MANAGER_IS_USER(u->manager) ? "SYSTEMD_USER_WANTS" : "SYSTEMD_WANTS"; - wants = udev_device_get_property_value(dev, property); - if (!wants) - return 0; - - FOREACH_WORD_QUOTED(word, l, wants, state) { - _cleanup_free_ char *n = NULL; - char e[l+1]; - - memcpy(e, word, l); - e[l] = 0; - - r = unit_name_mangle(e, UNIT_NAME_NOGLOB, &n); - if (r < 0) - return log_unit_error_errno(u, r, "Failed to mangle unit name: %m"); - - r = unit_add_dependency_by_name(u, UNIT_WANTS, n, NULL, true); - if (r < 0) - return log_unit_error_errno(u, r, "Failed to add wants dependency: %m"); - } - if (!isempty(state)) - log_unit_warning(u, "Property %s on %s has trailing garbage, ignoring.", property, strna(udev_device_get_syspath(dev))); - - return 0; -} - -static int device_setup_unit(Manager *m, struct udev_device *dev, const char *path, bool main) { - _cleanup_free_ char *e = NULL; - const char *sysfs = NULL; - Unit *u = NULL; - bool delete; - int r; - - assert(m); - assert(path); - - if (dev) { - sysfs = udev_device_get_syspath(dev); - if (!sysfs) - return 0; - } - - r = unit_name_from_path(path, ".device", &e); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name from device path: %m"); - - u = manager_get_unit(m, e); - - /* The device unit can still be present even if the device was - * unplugged: a mount unit can reference it hence preventing - * the GC to have garbaged it. That's desired since the device - * unit may have a dependency on the mount unit which was - * added during the loading of the later. */ - if (dev && u && DEVICE(u)->state == DEVICE_PLUGGED) { - /* This unit is in plugged state: we're sure it's - * attached to a device. */ - if (!path_equal(DEVICE(u)->sysfs, sysfs)) { - log_unit_debug(u, "Dev %s appeared twice with different sysfs paths %s and %s", - e, DEVICE(u)->sysfs, sysfs); - return -EEXIST; - } - } - - if (!u) { - delete = true; - - u = unit_new(m, sizeof(Device)); - if (!u) - return log_oom(); - - r = unit_add_name(u, e); - if (r < 0) - goto fail; - - unit_add_to_load_queue(u); - } else - delete = false; - - /* If this was created via some dependency and has not - * actually been seen yet ->sysfs will not be - * initialized. Hence initialize it if necessary. */ - if (sysfs) { - r = device_set_sysfs(DEVICE(u), sysfs); - if (r < 0) - goto fail; - - (void) device_update_description(u, dev, path); - - /* The additional systemd udev properties we only interpret - * for the main object */ - if (main) - (void) device_add_udev_wants(u, dev); - } - - - /* Note that this won't dispatch the load queue, the caller - * has to do that if needed and appropriate */ - - unit_add_to_dbus_queue(u); - return 0; - -fail: - log_unit_warning_errno(u, r, "Failed to set up device unit: %m"); - - if (delete) - unit_free(u); - - return r; -} - -static int device_process_new(Manager *m, struct udev_device *dev) { - const char *sysfs, *dn, *alias; - struct udev_list_entry *item = NULL, *first = NULL; - int r; - - assert(m); - - sysfs = udev_device_get_syspath(dev); - if (!sysfs) - return 0; - - /* Add the main unit named after the sysfs path */ - r = device_setup_unit(m, dev, sysfs, true); - if (r < 0) - return r; - - /* Add an additional unit for the device node */ - dn = udev_device_get_devnode(dev); - if (dn) - (void) device_setup_unit(m, dev, dn, false); - - /* Add additional units for all symlinks */ - first = udev_device_get_devlinks_list_entry(dev); - udev_list_entry_foreach(item, first) { - const char *p; - struct stat st; - - /* Don't bother with the /dev/block links */ - p = udev_list_entry_get_name(item); - - if (path_startswith(p, "/dev/block/") || - path_startswith(p, "/dev/char/")) - continue; - - /* Verify that the symlink in the FS actually belongs - * to this device. This is useful to deal with - * conflicting devices, e.g. when two disks want the - * same /dev/disk/by-label/xxx link because they have - * the same label. We want to make sure that the same - * device that won the symlink wins in systemd, so we - * check the device node major/minor */ - if (stat(p, &st) >= 0) - if ((!S_ISBLK(st.st_mode) && !S_ISCHR(st.st_mode)) || - st.st_rdev != udev_device_get_devnum(dev)) - continue; - - (void) device_setup_unit(m, dev, p, false); - } - - /* Add additional units for all explicitly configured - * aliases */ - alias = udev_device_get_property_value(dev, "SYSTEMD_ALIAS"); - if (alias) { - const char *word, *state; - size_t l; - - FOREACH_WORD_QUOTED(word, l, alias, state) { - char e[l+1]; - - memcpy(e, word, l); - e[l] = 0; - - if (path_is_absolute(e)) - (void) device_setup_unit(m, dev, e, false); - else - log_warning("SYSTEMD_ALIAS for %s is not an absolute path, ignoring: %s", sysfs, e); - } - if (!isempty(state)) - log_warning("SYSTEMD_ALIAS for %s has trailing garbage, ignoring.", sysfs); - } - - return 0; -} - -static void device_update_found_one(Device *d, bool add, DeviceFound found, bool now) { - DeviceFound n, previous; - - assert(d); - - n = add ? (d->found | found) : (d->found & ~found); - if (n == d->found) - return; - - previous = d->found; - d->found = n; - - if (!now) - return; - - if (d->found & DEVICE_FOUND_UDEV) - /* When the device is known to udev we consider it - * plugged. */ - device_set_state(d, DEVICE_PLUGGED); - else if (d->found != DEVICE_NOT_FOUND && (previous & DEVICE_FOUND_UDEV) == 0) - /* If the device has not been seen by udev yet, but is - * now referenced by the kernel, then we assume the - * kernel knows it now, and udev might soon too. */ - device_set_state(d, DEVICE_TENTATIVE); - else - /* If nobody sees the device, or if the device was - * previously seen by udev and now is only referenced - * from the kernel, then we consider the device is - * gone, the kernel just hasn't noticed it yet. */ - device_set_state(d, DEVICE_DEAD); -} - -static int device_update_found_by_sysfs(Manager *m, const char *sysfs, bool add, DeviceFound found, bool now) { - Device *d, *l; - - assert(m); - assert(sysfs); - - if (found == DEVICE_NOT_FOUND) - return 0; - - l = hashmap_get(m->devices_by_sysfs, sysfs); - LIST_FOREACH(same_sysfs, d, l) - device_update_found_one(d, add, found, now); - - return 0; -} - -static int device_update_found_by_name(Manager *m, const char *path, bool add, DeviceFound found, bool now) { - _cleanup_free_ char *e = NULL; - Unit *u; - int r; - - assert(m); - assert(path); - - if (found == DEVICE_NOT_FOUND) - return 0; - - r = unit_name_from_path(path, ".device", &e); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name from device path: %m"); - - u = manager_get_unit(m, e); - if (!u) - return 0; - - device_update_found_one(DEVICE(u), add, found, now); - return 0; -} - -static bool device_is_ready(struct udev_device *dev) { - const char *ready; - - assert(dev); - - ready = udev_device_get_property_value(dev, "SYSTEMD_READY"); - if (!ready) - return true; - - return parse_boolean(ready) != 0; -} - -static Unit *device_following(Unit *u) { - Device *d = DEVICE(u); - Device *other, *first = NULL; - - assert(d); - - if (startswith(u->id, "sys-")) - return NULL; - - /* Make everybody follow the unit that's named after the sysfs path */ - for (other = d->same_sysfs_next; other; other = other->same_sysfs_next) - if (startswith(UNIT(other)->id, "sys-")) - return UNIT(other); - - for (other = d->same_sysfs_prev; other; other = other->same_sysfs_prev) { - if (startswith(UNIT(other)->id, "sys-")) - return UNIT(other); - - first = other; - } - - return UNIT(first); -} - -static int device_following_set(Unit *u, Set **_set) { - Device *d = DEVICE(u), *other; - Set *set; - int r; - - assert(d); - assert(_set); - - if (LIST_JUST_US(same_sysfs, d)) { - *_set = NULL; - return 0; - } - - set = set_new(NULL); - if (!set) - return -ENOMEM; - - LIST_FOREACH_AFTER(same_sysfs, other, d) { - r = set_put(set, other); - if (r < 0) - goto fail; - } - - LIST_FOREACH_BEFORE(same_sysfs, other, d) { - r = set_put(set, other); - if (r < 0) - goto fail; - } - - *_set = set; - return 1; - -fail: - set_free(set); - return r; -} - -static void device_shutdown(Manager *m) { - assert(m); - - m->udev_event_source = sd_event_source_unref(m->udev_event_source); - - if (m->udev_monitor) { - udev_monitor_unref(m->udev_monitor); - m->udev_monitor = NULL; - } - - m->devices_by_sysfs = hashmap_free(m->devices_by_sysfs); -} - -static void device_enumerate(Manager *m) { - _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; - struct udev_list_entry *item = NULL, *first = NULL; - int r; - - assert(m); - - if (!m->udev_monitor) { - m->udev_monitor = udev_monitor_new_from_netlink(m->udev, "udev"); - if (!m->udev_monitor) { - log_oom(); - goto fail; - } - - /* This will fail if we are unprivileged, but that - * should not matter much, as user instances won't run - * during boot. */ - (void) udev_monitor_set_receive_buffer_size(m->udev_monitor, 128*1024*1024); - - r = udev_monitor_filter_add_match_tag(m->udev_monitor, "systemd"); - if (r < 0) { - log_error_errno(r, "Failed to add udev tag match: %m"); - goto fail; - } - - r = udev_monitor_enable_receiving(m->udev_monitor); - if (r < 0) { - log_error_errno(r, "Failed to enable udev event reception: %m"); - goto fail; - } - - r = sd_event_add_io(m->event, &m->udev_event_source, udev_monitor_get_fd(m->udev_monitor), EPOLLIN, device_dispatch_io, m); - if (r < 0) { - log_error_errno(r, "Failed to watch udev file descriptor: %m"); - goto fail; - } - - (void) sd_event_source_set_description(m->udev_event_source, "device"); - } - - e = udev_enumerate_new(m->udev); - if (!e) { - log_oom(); - goto fail; - } - - r = udev_enumerate_add_match_tag(e, "systemd"); - if (r < 0) { - log_error_errno(r, "Failed to create udev tag enumeration: %m"); - goto fail; - } - - r = udev_enumerate_add_match_is_initialized(e); - if (r < 0) { - log_error_errno(r, "Failed to install initialization match into enumeration: %m"); - goto fail; - } - - r = udev_enumerate_scan_devices(e); - if (r < 0) { - log_error_errno(r, "Failed to enumerate devices: %m"); - goto fail; - } - - first = udev_enumerate_get_list_entry(e); - udev_list_entry_foreach(item, first) { - _cleanup_udev_device_unref_ struct udev_device *dev = NULL; - const char *sysfs; - - sysfs = udev_list_entry_get_name(item); - - dev = udev_device_new_from_syspath(m->udev, sysfs); - if (!dev) { - log_oom(); - continue; - } - - if (!device_is_ready(dev)) - continue; - - (void) device_process_new(m, dev); - - device_update_found_by_sysfs(m, sysfs, true, DEVICE_FOUND_UDEV, false); - } - - return; - -fail: - device_shutdown(m); -} - -static int device_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - _cleanup_udev_device_unref_ struct udev_device *dev = NULL; - Manager *m = userdata; - const char *action, *sysfs; - int r; - - assert(m); - - if (revents != EPOLLIN) { - static RATELIMIT_DEFINE(limit, 10*USEC_PER_SEC, 5); - - if (!ratelimit_test(&limit)) - log_error_errno(errno, "Failed to get udev event: %m"); - if (!(revents & EPOLLIN)) - return 0; - } - - /* - * libudev might filter-out devices which pass the bloom - * filter, so getting NULL here is not necessarily an error. - */ - dev = udev_monitor_receive_device(m->udev_monitor); - if (!dev) - return 0; - - sysfs = udev_device_get_syspath(dev); - if (!sysfs) { - log_error("Failed to get udev sys path."); - return 0; - } - - action = udev_device_get_action(dev); - if (!action) { - log_error("Failed to get udev action string."); - return 0; - } - - if (streq(action, "remove")) { - r = swap_process_device_remove(m, dev); - if (r < 0) - log_error_errno(r, "Failed to process swap device remove event: %m"); - - /* If we get notified that a device was removed by - * udev, then it's completely gone, hence unset all - * found bits */ - device_update_found_by_sysfs(m, sysfs, false, DEVICE_FOUND_UDEV|DEVICE_FOUND_MOUNT|DEVICE_FOUND_SWAP, true); - - } else if (device_is_ready(dev)) { - - (void) device_process_new(m, dev); - - r = swap_process_device_new(m, dev); - if (r < 0) - log_error_errno(r, "Failed to process swap device new event: %m"); - - manager_dispatch_load_queue(m); - - /* The device is found now, set the udev found bit */ - device_update_found_by_sysfs(m, sysfs, true, DEVICE_FOUND_UDEV, true); - - } else { - /* The device is nominally around, but not ready for - * us. Hence unset the udev bit, but leave the rest - * around. */ - - device_update_found_by_sysfs(m, sysfs, false, DEVICE_FOUND_UDEV, true); - } - - return 0; -} - -static bool device_supported(void) { - static int read_only = -1; - - /* If /sys is read-only we don't support device units, and any - * attempts to start one should fail immediately. */ - - if (read_only < 0) - read_only = path_is_read_only_fs("/sys"); - - return read_only <= 0; -} - -int device_found_node(Manager *m, const char *node, bool add, DeviceFound found, bool now) { - _cleanup_udev_device_unref_ struct udev_device *dev = NULL; - struct stat st; - - assert(m); - assert(node); - - if (!device_supported()) - return 0; - - /* This is called whenever we find a device referenced in - * /proc/swaps or /proc/self/mounts. Such a device might be - * mounted/enabled at a time where udev has not finished - * probing it yet, and we thus haven't learned about it - * yet. In this case we will set the device unit to - * "tentative" state. */ - - if (add) { - if (!path_startswith(node, "/dev")) - return 0; - - /* We make an extra check here, if the device node - * actually exists. If it's missing, then this is an - * indication that device was unplugged but is still - * referenced in /proc/swaps or - * /proc/self/mountinfo. Note that this check doesn't - * really cover all cases where a device might be gone - * away, since drives that can have a medium inserted - * will still have a device node even when the medium - * is not there... */ - - if (stat(node, &st) >= 0) { - if (!S_ISBLK(st.st_mode) && !S_ISCHR(st.st_mode)) - return 0; - - dev = udev_device_new_from_devnum(m->udev, S_ISBLK(st.st_mode) ? 'b' : 'c', st.st_rdev); - if (!dev && errno != ENOENT) - return log_error_errno(errno, "Failed to get udev device from devnum %u:%u: %m", major(st.st_rdev), minor(st.st_rdev)); - - } else if (errno != ENOENT) - return log_error_errno(errno, "Failed to stat device node file %s: %m", node); - - /* If the device is known in the kernel and newly - * appeared, then we'll create a device unit for it, - * under the name referenced in /proc/swaps or - * /proc/self/mountinfo. */ - - (void) device_setup_unit(m, dev, node, false); - } - - /* Update the device unit's state, should it exist */ - return device_update_found_by_name(m, node, add, found, now); -} - -const UnitVTable device_vtable = { - .object_size = sizeof(Device), - .sections = - "Unit\0" - "Device\0" - "Install\0", - - .init = device_init, - .done = device_done, - .load = unit_load_fragment_and_dropin_optional, - - .coldplug = device_coldplug, - - .serialize = device_serialize, - .deserialize_item = device_deserialize_item, - - .dump = device_dump, - - .active_state = device_active_state, - .sub_state_to_string = device_sub_state_to_string, - - .bus_vtable = bus_device_vtable, - - .following = device_following, - .following_set = device_following_set, - - .enumerate = device_enumerate, - .shutdown = device_shutdown, - .supported = device_supported, - - .status_message_formats = { - .starting_stopping = { - [0] = "Expecting device %s...", - }, - .finished_start_job = { - [JOB_DONE] = "Found device %s.", - [JOB_TIMEOUT] = "Timed out waiting for device %s.", - }, - }, -}; diff --git a/src/core/device.h b/src/core/device.h deleted file mode 100644 index 184a1a349b..0000000000 --- a/src/core/device.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct Device Device; - -typedef enum DeviceFound { - DEVICE_NOT_FOUND = 0, - DEVICE_FOUND_UDEV = 1, - DEVICE_FOUND_MOUNT = 2, - DEVICE_FOUND_SWAP = 4, -} DeviceFound; - -struct Device { - Unit meta; - - char *sysfs; - DeviceFound found; - - /* In order to be able to distinguish dependencies on - different device nodes we might end up creating multiple - devices for the same sysfs path. We chain them up here. */ - LIST_FIELDS(struct Device, same_sysfs); - - DeviceState state, deserialized_state; -}; - -extern const UnitVTable device_vtable; - -int device_found_node(Manager *m, const char *node, bool add, DeviceFound found, bool now); diff --git a/src/core/execute.c b/src/core/execute.c deleted file mode 100644 index 5eb3f13695..0000000000 --- a/src/core/execute.c +++ /dev/null @@ -1,3092 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_PAM -#include -#endif - -#ifdef HAVE_SELINUX -#include -#endif - -#ifdef HAVE_SECCOMP -#include -#endif - -#ifdef HAVE_APPARMOR -#include -#endif - -#include "sd-messages.h" - -#include "af-list.h" -#include "alloc-util.h" -#ifdef HAVE_APPARMOR -#include "apparmor-util.h" -#endif -#include "async.h" -#include "barrier.h" -#include "cap-list.h" -#include "capability-util.h" -#include "def.h" -#include "env-util.h" -#include "errno-list.h" -#include "execute.h" -#include "exit-status.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "glob-util.h" -#include "io-util.h" -#include "ioprio.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "namespace.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "rlimit-util.h" -#include "rm-rf.h" -#ifdef HAVE_SECCOMP -#include "seccomp-util.h" -#endif -#include "securebits.h" -#include "selinux-util.h" -#include "signal-util.h" -#include "smack-util.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "syslog-util.h" -#include "terminal-util.h" -#include "unit.h" -#include "user-util.h" -#include "util.h" -#include "utmp-wtmp.h" - -#define IDLE_TIMEOUT_USEC (5*USEC_PER_SEC) -#define IDLE_TIMEOUT2_USEC (1*USEC_PER_SEC) - -/* This assumes there is a 'tty' group */ -#define TTY_MODE 0620 - -#define SNDBUF_SIZE (8*1024*1024) - -static int shift_fds(int fds[], unsigned n_fds) { - int start, restart_from; - - if (n_fds <= 0) - return 0; - - /* Modifies the fds array! (sorts it) */ - - assert(fds); - - start = 0; - for (;;) { - int i; - - restart_from = -1; - - for (i = start; i < (int) n_fds; i++) { - int nfd; - - /* Already at right index? */ - if (fds[i] == i+3) - continue; - - nfd = fcntl(fds[i], F_DUPFD, i + 3); - if (nfd < 0) - return -errno; - - safe_close(fds[i]); - fds[i] = nfd; - - /* Hmm, the fd we wanted isn't free? Then - * let's remember that and try again from here */ - if (nfd != i+3 && restart_from < 0) - restart_from = i; - } - - if (restart_from < 0) - break; - - start = restart_from; - } - - return 0; -} - -static int flags_fds(const int fds[], unsigned n_fds, bool nonblock) { - unsigned i; - int r; - - if (n_fds <= 0) - return 0; - - assert(fds); - - /* Drops/Sets O_NONBLOCK and FD_CLOEXEC from the file flags */ - - for (i = 0; i < n_fds; i++) { - - r = fd_nonblock(fds[i], nonblock); - if (r < 0) - return r; - - /* We unconditionally drop FD_CLOEXEC from the fds, - * since after all we want to pass these fds to our - * children */ - - r = fd_cloexec(fds[i], false); - if (r < 0) - return r; - } - - return 0; -} - -static const char *exec_context_tty_path(const ExecContext *context) { - assert(context); - - if (context->stdio_as_fds) - return NULL; - - if (context->tty_path) - return context->tty_path; - - return "/dev/console"; -} - -static void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) { - const char *path; - - assert(context); - - path = exec_context_tty_path(context); - - if (context->tty_vhangup) { - if (p && p->stdin_fd >= 0) - (void) terminal_vhangup_fd(p->stdin_fd); - else if (path) - (void) terminal_vhangup(path); - } - - if (context->tty_reset) { - if (p && p->stdin_fd >= 0) - (void) reset_terminal_fd(p->stdin_fd, true); - else if (path) - (void) reset_terminal(path); - } - - if (context->tty_vt_disallocate && path) - (void) vt_disallocate(path); -} - -static bool is_terminal_output(ExecOutput o) { - return - o == EXEC_OUTPUT_TTY || - o == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || - o == EXEC_OUTPUT_KMSG_AND_CONSOLE || - o == EXEC_OUTPUT_JOURNAL_AND_CONSOLE; -} - -static int open_null_as(int flags, int nfd) { - int fd, r; - - assert(nfd >= 0); - - fd = open("/dev/null", flags|O_NOCTTY); - if (fd < 0) - return -errno; - - if (fd != nfd) { - r = dup2(fd, nfd) < 0 ? -errno : nfd; - safe_close(fd); - } else - r = nfd; - - return r; -} - -static int connect_journal_socket(int fd, uid_t uid, gid_t gid) { - union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/journal/stdout", - }; - uid_t olduid = UID_INVALID; - gid_t oldgid = GID_INVALID; - int r; - - if (gid != GID_INVALID) { - oldgid = getgid(); - - r = setegid(gid); - if (r < 0) - return -errno; - } - - if (uid != UID_INVALID) { - olduid = getuid(); - - r = seteuid(uid); - if (r < 0) { - r = -errno; - goto restore_gid; - } - } - - r = connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); - if (r < 0) - r = -errno; - - /* If we fail to restore the uid or gid, things will likely - fail later on. This should only happen if an LSM interferes. */ - - if (uid != UID_INVALID) - (void) seteuid(olduid); - - restore_gid: - if (gid != GID_INVALID) - (void) setegid(oldgid); - - return r; -} - -static int connect_logger_as(const ExecContext *context, ExecOutput output, const char *ident, const char *unit_id, int nfd, uid_t uid, gid_t gid) { - int fd, r; - - assert(context); - assert(output < _EXEC_OUTPUT_MAX); - assert(ident); - assert(nfd >= 0); - - fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (fd < 0) - return -errno; - - r = connect_journal_socket(fd, uid, gid); - if (r < 0) - return r; - - if (shutdown(fd, SHUT_RD) < 0) { - safe_close(fd); - return -errno; - } - - fd_inc_sndbuf(fd, SNDBUF_SIZE); - - dprintf(fd, - "%s\n" - "%s\n" - "%i\n" - "%i\n" - "%i\n" - "%i\n" - "%i\n", - context->syslog_identifier ? context->syslog_identifier : ident, - unit_id, - context->syslog_priority, - !!context->syslog_level_prefix, - output == EXEC_OUTPUT_SYSLOG || output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE, - output == EXEC_OUTPUT_KMSG || output == EXEC_OUTPUT_KMSG_AND_CONSOLE, - is_terminal_output(output)); - - if (fd != nfd) { - r = dup2(fd, nfd) < 0 ? -errno : nfd; - safe_close(fd); - } else - r = nfd; - - return r; -} -static int open_terminal_as(const char *path, mode_t mode, int nfd) { - int fd, r; - - assert(path); - assert(nfd >= 0); - - fd = open_terminal(path, mode | O_NOCTTY); - if (fd < 0) - return fd; - - if (fd != nfd) { - r = dup2(fd, nfd) < 0 ? -errno : nfd; - safe_close(fd); - } else - r = nfd; - - return r; -} - -static bool is_terminal_input(ExecInput i) { - return - i == EXEC_INPUT_TTY || - i == EXEC_INPUT_TTY_FORCE || - i == EXEC_INPUT_TTY_FAIL; -} - -static int fixup_input(ExecInput std_input, int socket_fd, bool apply_tty_stdin) { - - if (is_terminal_input(std_input) && !apply_tty_stdin) - return EXEC_INPUT_NULL; - - if (std_input == EXEC_INPUT_SOCKET && socket_fd < 0) - return EXEC_INPUT_NULL; - - return std_input; -} - -static int fixup_output(ExecOutput std_output, int socket_fd) { - - if (std_output == EXEC_OUTPUT_SOCKET && socket_fd < 0) - return EXEC_OUTPUT_INHERIT; - - return std_output; -} - -static int setup_input( - const ExecContext *context, - const ExecParameters *params, - int socket_fd) { - - ExecInput i; - - assert(context); - assert(params); - - if (params->stdin_fd >= 0) { - if (dup2(params->stdin_fd, STDIN_FILENO) < 0) - return -errno; - - /* Try to make this the controlling tty, if it is a tty, and reset it */ - (void) ioctl(STDIN_FILENO, TIOCSCTTY, context->std_input == EXEC_INPUT_TTY_FORCE); - (void) reset_terminal_fd(STDIN_FILENO, true); - - return STDIN_FILENO; - } - - i = fixup_input(context->std_input, socket_fd, params->apply_tty_stdin); - - switch (i) { - - case EXEC_INPUT_NULL: - return open_null_as(O_RDONLY, STDIN_FILENO); - - case EXEC_INPUT_TTY: - case EXEC_INPUT_TTY_FORCE: - case EXEC_INPUT_TTY_FAIL: { - int fd, r; - - fd = acquire_terminal(exec_context_tty_path(context), - i == EXEC_INPUT_TTY_FAIL, - i == EXEC_INPUT_TTY_FORCE, - false, - USEC_INFINITY); - if (fd < 0) - return fd; - - if (fd != STDIN_FILENO) { - r = dup2(fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO; - safe_close(fd); - } else - r = STDIN_FILENO; - - return r; - } - - case EXEC_INPUT_SOCKET: - return dup2(socket_fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO; - - default: - assert_not_reached("Unknown input type"); - } -} - -static int setup_output( - Unit *unit, - const ExecContext *context, - const ExecParameters *params, - int fileno, - int socket_fd, - const char *ident, - uid_t uid, gid_t gid) { - - ExecOutput o; - ExecInput i; - int r; - - assert(unit); - assert(context); - assert(params); - assert(ident); - - if (fileno == STDOUT_FILENO && params->stdout_fd >= 0) { - - if (dup2(params->stdout_fd, STDOUT_FILENO) < 0) - return -errno; - - return STDOUT_FILENO; - } - - if (fileno == STDERR_FILENO && params->stderr_fd >= 0) { - if (dup2(params->stderr_fd, STDERR_FILENO) < 0) - return -errno; - - return STDERR_FILENO; - } - - i = fixup_input(context->std_input, socket_fd, params->apply_tty_stdin); - o = fixup_output(context->std_output, socket_fd); - - if (fileno == STDERR_FILENO) { - ExecOutput e; - e = fixup_output(context->std_error, socket_fd); - - /* This expects the input and output are already set up */ - - /* Don't change the stderr file descriptor if we inherit all - * the way and are not on a tty */ - if (e == EXEC_OUTPUT_INHERIT && - o == EXEC_OUTPUT_INHERIT && - i == EXEC_INPUT_NULL && - !is_terminal_input(context->std_input) && - getppid () != 1) - return fileno; - - /* Duplicate from stdout if possible */ - if (e == o || e == EXEC_OUTPUT_INHERIT) - return dup2(STDOUT_FILENO, fileno) < 0 ? -errno : fileno; - - o = e; - - } else if (o == EXEC_OUTPUT_INHERIT) { - /* If input got downgraded, inherit the original value */ - if (i == EXEC_INPUT_NULL && is_terminal_input(context->std_input)) - return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno); - - /* If the input is connected to anything that's not a /dev/null, inherit that... */ - if (i != EXEC_INPUT_NULL) - return dup2(STDIN_FILENO, fileno) < 0 ? -errno : fileno; - - /* If we are not started from PID 1 we just inherit STDOUT from our parent process. */ - if (getppid() != 1) - return fileno; - - /* We need to open /dev/null here anew, to get the right access mode. */ - return open_null_as(O_WRONLY, fileno); - } - - switch (o) { - - case EXEC_OUTPUT_NULL: - return open_null_as(O_WRONLY, fileno); - - case EXEC_OUTPUT_TTY: - if (is_terminal_input(i)) - return dup2(STDIN_FILENO, fileno) < 0 ? -errno : fileno; - - /* We don't reset the terminal if this is just about output */ - return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno); - - case EXEC_OUTPUT_SYSLOG: - case EXEC_OUTPUT_SYSLOG_AND_CONSOLE: - case EXEC_OUTPUT_KMSG: - case EXEC_OUTPUT_KMSG_AND_CONSOLE: - case EXEC_OUTPUT_JOURNAL: - case EXEC_OUTPUT_JOURNAL_AND_CONSOLE: - r = connect_logger_as(context, o, ident, unit->id, fileno, uid, gid); - if (r < 0) { - log_unit_error_errno(unit, r, "Failed to connect %s to the journal socket, ignoring: %m", fileno == STDOUT_FILENO ? "stdout" : "stderr"); - r = open_null_as(O_WRONLY, fileno); - } - return r; - - case EXEC_OUTPUT_SOCKET: - assert(socket_fd >= 0); - return dup2(socket_fd, fileno) < 0 ? -errno : fileno; - - default: - assert_not_reached("Unknown error type"); - } -} - -static int chown_terminal(int fd, uid_t uid) { - struct stat st; - - assert(fd >= 0); - - /* This might fail. What matters are the results. */ - (void) fchown(fd, uid, -1); - (void) fchmod(fd, TTY_MODE); - - if (fstat(fd, &st) < 0) - return -errno; - - if (st.st_uid != uid || (st.st_mode & 0777) != TTY_MODE) - return -EPERM; - - return 0; -} - -static int setup_confirm_stdio(int *_saved_stdin, int *_saved_stdout) { - _cleanup_close_ int fd = -1, saved_stdin = -1, saved_stdout = -1; - int r; - - assert(_saved_stdin); - assert(_saved_stdout); - - saved_stdin = fcntl(STDIN_FILENO, F_DUPFD, 3); - if (saved_stdin < 0) - return -errno; - - saved_stdout = fcntl(STDOUT_FILENO, F_DUPFD, 3); - if (saved_stdout < 0) - return -errno; - - fd = acquire_terminal( - "/dev/console", - false, - false, - false, - DEFAULT_CONFIRM_USEC); - if (fd < 0) - return fd; - - r = chown_terminal(fd, getuid()); - if (r < 0) - return r; - - r = reset_terminal_fd(fd, true); - if (r < 0) - return r; - - if (dup2(fd, STDIN_FILENO) < 0) - return -errno; - - if (dup2(fd, STDOUT_FILENO) < 0) - return -errno; - - if (fd >= 2) - safe_close(fd); - fd = -1; - - *_saved_stdin = saved_stdin; - *_saved_stdout = saved_stdout; - - saved_stdin = saved_stdout = -1; - - return 0; -} - -_printf_(1, 2) static int write_confirm_message(const char *format, ...) { - _cleanup_close_ int fd = -1; - va_list ap; - - assert(format); - - fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return fd; - - va_start(ap, format); - vdprintf(fd, format, ap); - va_end(ap); - - return 0; -} - -static int restore_confirm_stdio(int *saved_stdin, int *saved_stdout) { - int r = 0; - - assert(saved_stdin); - assert(saved_stdout); - - release_terminal(); - - if (*saved_stdin >= 0) - if (dup2(*saved_stdin, STDIN_FILENO) < 0) - r = -errno; - - if (*saved_stdout >= 0) - if (dup2(*saved_stdout, STDOUT_FILENO) < 0) - r = -errno; - - *saved_stdin = safe_close(*saved_stdin); - *saved_stdout = safe_close(*saved_stdout); - - return r; -} - -static int ask_for_confirmation(char *response, char **argv) { - int saved_stdout = -1, saved_stdin = -1, r; - _cleanup_free_ char *line = NULL; - - r = setup_confirm_stdio(&saved_stdin, &saved_stdout); - if (r < 0) - return r; - - line = exec_command_line(argv); - if (!line) - return -ENOMEM; - - r = ask_char(response, "yns", "Execute %s? [Yes, No, Skip] ", line); - - restore_confirm_stdio(&saved_stdin, &saved_stdout); - - return r; -} - -static int enforce_groups(const ExecContext *context, const char *username, gid_t gid) { - bool keep_groups = false; - int r; - - assert(context); - - /* Lookup and set GID and supplementary group list. Here too - * we avoid NSS lookups for gid=0. */ - - if (context->group || username) { - /* First step, initialize groups from /etc/groups */ - if (username && gid != 0) { - if (initgroups(username, gid) < 0) - return -errno; - - keep_groups = true; - } - - /* Second step, set our gids */ - if (setresgid(gid, gid, gid) < 0) - return -errno; - } - - if (context->supplementary_groups) { - int ngroups_max, k; - gid_t *gids; - char **i; - - /* Final step, initialize any manually set supplementary groups */ - assert_se((ngroups_max = (int) sysconf(_SC_NGROUPS_MAX)) > 0); - - if (!(gids = new(gid_t, ngroups_max))) - return -ENOMEM; - - if (keep_groups) { - k = getgroups(ngroups_max, gids); - if (k < 0) { - free(gids); - return -errno; - } - } else - k = 0; - - STRV_FOREACH(i, context->supplementary_groups) { - const char *g; - - if (k >= ngroups_max) { - free(gids); - return -E2BIG; - } - - g = *i; - r = get_group_creds(&g, gids+k); - if (r < 0) { - free(gids); - return r; - } - - k++; - } - - if (setgroups(k, gids) < 0) { - free(gids); - return -errno; - } - - free(gids); - } - - return 0; -} - -static int enforce_user(const ExecContext *context, uid_t uid) { - assert(context); - - /* Sets (but doesn't look up) the uid and make sure we keep the - * capabilities while doing so. */ - - if (context->capability_ambient_set != 0) { - - /* First step: If we need to keep capabilities but - * drop privileges we need to make sure we keep our - * caps, while we drop privileges. */ - if (uid != 0) { - int sb = context->secure_bits | 1<= 0); - - parent_pid = getpid(); - - pam_pid = fork(); - if (pam_pid < 0) { - r = -errno; - goto fail; - } - - if (pam_pid == 0) { - int sig, ret = EXIT_PAM; - - /* The child's job is to reset the PAM session on - * termination */ - barrier_set_role(&barrier, BARRIER_CHILD); - - /* This string must fit in 10 chars (i.e. the length - * of "/sbin/init"), to look pretty in /bin/ps */ - rename_process("(sd-pam)"); - - /* Make sure we don't keep open the passed fds in this - child. We assume that otherwise only those fds are - open here that have been opened by PAM. */ - close_many(fds, n_fds); - - /* Drop privileges - we don't need any to pam_close_session - * and this will make PR_SET_PDEATHSIG work in most cases. - * If this fails, ignore the error - but expect sd-pam threads - * to fail to exit normally */ - if (setresuid(uid, uid, uid) < 0) - log_error_errno(r, "Error: Failed to setresuid() in sd-pam: %m"); - - (void) ignore_signals(SIGPIPE, -1); - - /* Wait until our parent died. This will only work if - * the above setresuid() succeeds, otherwise the kernel - * will not allow unprivileged parents kill their privileged - * children this way. We rely on the control groups kill logic - * to do the rest for us. */ - if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) - goto child_finish; - - /* Tell the parent that our setup is done. This is especially - * important regarding dropping privileges. Otherwise, unit - * setup might race against our setresuid(2) call. */ - barrier_place(&barrier); - - /* Check if our parent process might already have - * died? */ - if (getppid() == parent_pid) { - sigset_t ss; - - assert_se(sigemptyset(&ss) >= 0); - assert_se(sigaddset(&ss, SIGTERM) >= 0); - - for (;;) { - if (sigwait(&ss, &sig) < 0) { - if (errno == EINTR) - continue; - - goto child_finish; - } - - assert(sig == SIGTERM); - break; - } - } - - /* If our parent died we'll end the session */ - if (getppid() != parent_pid) { - pam_code = pam_close_session(handle, flags); - if (pam_code != PAM_SUCCESS) - goto child_finish; - } - - ret = 0; - - child_finish: - pam_end(handle, pam_code | flags); - _exit(ret); - } - - barrier_set_role(&barrier, BARRIER_PARENT); - - /* If the child was forked off successfully it will do all the - * cleanups, so forget about the handle here. */ - handle = NULL; - - /* Unblock SIGTERM again in the parent */ - assert_se(sigprocmask(SIG_SETMASK, &old_ss, NULL) >= 0); - - /* We close the log explicitly here, since the PAM modules - * might have opened it, but we don't want this fd around. */ - closelog(); - - /* Synchronously wait for the child to initialize. We don't care for - * errors as we cannot recover. However, warn loudly if it happens. */ - if (!barrier_place_and_sync(&barrier)) - log_error("PAM initialization failed"); - - *pam_env = e; - e = NULL; - - return 0; - -fail: - if (pam_code != PAM_SUCCESS) { - log_error("PAM failed: %s", pam_strerror(handle, pam_code)); - r = -EPERM; /* PAM errors do not map to errno */ - } else - log_error_errno(r, "PAM failed: %m"); - - if (handle) { - if (close_session) - pam_code = pam_close_session(handle, flags); - - pam_end(handle, pam_code | flags); - } - - strv_free(e); - closelog(); - - return r; -} -#endif - -static void rename_process_from_path(const char *path) { - char process_name[11]; - const char *p; - size_t l; - - /* This resulting string must fit in 10 chars (i.e. the length - * of "/sbin/init") to look pretty in /bin/ps */ - - p = basename(path); - if (isempty(p)) { - rename_process("(...)"); - return; - } - - l = strlen(p); - if (l > 8) { - /* The end of the process name is usually more - * interesting, since the first bit might just be - * "systemd-" */ - p = p + l - 8; - l = 8; - } - - process_name[0] = '('; - memcpy(process_name+1, p, l); - process_name[1+l] = ')'; - process_name[1+l+1] = 0; - - rename_process(process_name); -} - -#ifdef HAVE_SECCOMP - -static int apply_seccomp(const ExecContext *c) { - uint32_t negative_action, action; - scmp_filter_ctx *seccomp; - Iterator i; - void *id; - int r; - - assert(c); - - negative_action = c->syscall_errno == 0 ? SCMP_ACT_KILL : SCMP_ACT_ERRNO(c->syscall_errno); - - seccomp = seccomp_init(c->syscall_whitelist ? negative_action : SCMP_ACT_ALLOW); - if (!seccomp) - return -ENOMEM; - - if (c->syscall_archs) { - - SET_FOREACH(id, c->syscall_archs, i) { - r = seccomp_arch_add(seccomp, PTR_TO_UINT32(id) - 1); - if (r == -EEXIST) - continue; - if (r < 0) - goto finish; - } - - } else { - r = seccomp_add_secondary_archs(seccomp); - if (r < 0) - goto finish; - } - - action = c->syscall_whitelist ? SCMP_ACT_ALLOW : negative_action; - SET_FOREACH(id, c->syscall_filter, i) { - r = seccomp_rule_add(seccomp, action, PTR_TO_INT(id) - 1, 0); - if (r < 0) - goto finish; - } - - r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); - if (r < 0) - goto finish; - - r = seccomp_load(seccomp); - -finish: - seccomp_release(seccomp); - return r; -} - -static int apply_address_families(const ExecContext *c) { - scmp_filter_ctx *seccomp; - Iterator i; - int r; - - assert(c); - - seccomp = seccomp_init(SCMP_ACT_ALLOW); - if (!seccomp) - return -ENOMEM; - - r = seccomp_add_secondary_archs(seccomp); - if (r < 0) - goto finish; - - if (c->address_families_whitelist) { - int af, first = 0, last = 0; - void *afp; - - /* If this is a whitelist, we first block the address - * families that are out of range and then everything - * that is not in the set. First, we find the lowest - * and highest address family in the set. */ - - SET_FOREACH(afp, c->address_families, i) { - af = PTR_TO_INT(afp); - - if (af <= 0 || af >= af_max()) - continue; - - if (first == 0 || af < first) - first = af; - - if (last == 0 || af > last) - last = af; - } - - assert((first == 0) == (last == 0)); - - if (first == 0) { - - /* No entries in the valid range, block everything */ - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPROTONOSUPPORT), - SCMP_SYS(socket), - 0); - if (r < 0) - goto finish; - - } else { - - /* Block everything below the first entry */ - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPROTONOSUPPORT), - SCMP_SYS(socket), - 1, - SCMP_A0(SCMP_CMP_LT, first)); - if (r < 0) - goto finish; - - /* Block everything above the last entry */ - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPROTONOSUPPORT), - SCMP_SYS(socket), - 1, - SCMP_A0(SCMP_CMP_GT, last)); - if (r < 0) - goto finish; - - /* Block everything between the first and last - * entry */ - for (af = 1; af < af_max(); af++) { - - if (set_contains(c->address_families, INT_TO_PTR(af))) - continue; - - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPROTONOSUPPORT), - SCMP_SYS(socket), - 1, - SCMP_A0(SCMP_CMP_EQ, af)); - if (r < 0) - goto finish; - } - } - - } else { - void *af; - - /* If this is a blacklist, then generate one rule for - * each address family that are then combined in OR - * checks. */ - - SET_FOREACH(af, c->address_families, i) { - - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPROTONOSUPPORT), - SCMP_SYS(socket), - 1, - SCMP_A0(SCMP_CMP_EQ, PTR_TO_INT(af))); - if (r < 0) - goto finish; - } - } - - r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); - if (r < 0) - goto finish; - - r = seccomp_load(seccomp); - -finish: - seccomp_release(seccomp); - return r; -} - -#endif - -static void do_idle_pipe_dance(int idle_pipe[4]) { - assert(idle_pipe); - - - idle_pipe[1] = safe_close(idle_pipe[1]); - idle_pipe[2] = safe_close(idle_pipe[2]); - - if (idle_pipe[0] >= 0) { - int r; - - r = fd_wait_for_event(idle_pipe[0], POLLHUP, IDLE_TIMEOUT_USEC); - - if (idle_pipe[3] >= 0 && r == 0 /* timeout */) { - ssize_t n; - - /* Signal systemd that we are bored and want to continue. */ - n = write(idle_pipe[3], "x", 1); - if (n > 0) - /* Wait for systemd to react to the signal above. */ - fd_wait_for_event(idle_pipe[0], POLLHUP, IDLE_TIMEOUT2_USEC); - } - - idle_pipe[0] = safe_close(idle_pipe[0]); - - } - - idle_pipe[3] = safe_close(idle_pipe[3]); -} - -static int build_environment( - const ExecContext *c, - const ExecParameters *p, - unsigned n_fds, - const char *home, - const char *username, - const char *shell, - char ***ret) { - - _cleanup_strv_free_ char **our_env = NULL; - unsigned n_env = 0; - char *x; - - assert(c); - assert(ret); - - our_env = new0(char*, 11); - if (!our_env) - return -ENOMEM; - - if (n_fds > 0) { - _cleanup_free_ char *joined = NULL; - - if (asprintf(&x, "LISTEN_PID="PID_FMT, getpid()) < 0) - return -ENOMEM; - our_env[n_env++] = x; - - if (asprintf(&x, "LISTEN_FDS=%u", n_fds) < 0) - return -ENOMEM; - our_env[n_env++] = x; - - joined = strv_join(p->fd_names, ":"); - if (!joined) - return -ENOMEM; - - x = strjoin("LISTEN_FDNAMES=", joined, NULL); - if (!x) - return -ENOMEM; - our_env[n_env++] = x; - } - - if (p->watchdog_usec > 0) { - if (asprintf(&x, "WATCHDOG_PID="PID_FMT, getpid()) < 0) - return -ENOMEM; - our_env[n_env++] = x; - - if (asprintf(&x, "WATCHDOG_USEC="USEC_FMT, p->watchdog_usec) < 0) - return -ENOMEM; - our_env[n_env++] = x; - } - - if (home) { - x = strappend("HOME=", home); - if (!x) - return -ENOMEM; - our_env[n_env++] = x; - } - - if (username) { - x = strappend("LOGNAME=", username); - if (!x) - return -ENOMEM; - our_env[n_env++] = x; - - x = strappend("USER=", username); - if (!x) - return -ENOMEM; - our_env[n_env++] = x; - } - - if (shell) { - x = strappend("SHELL=", shell); - if (!x) - return -ENOMEM; - our_env[n_env++] = x; - } - - if (is_terminal_input(c->std_input) || - c->std_output == EXEC_OUTPUT_TTY || - c->std_error == EXEC_OUTPUT_TTY || - c->tty_path) { - - x = strdup(default_term_for_tty(exec_context_tty_path(c))); - if (!x) - return -ENOMEM; - our_env[n_env++] = x; - } - - our_env[n_env++] = NULL; - assert(n_env <= 11); - - *ret = our_env; - our_env = NULL; - - return 0; -} - -static int build_pass_environment(const ExecContext *c, char ***ret) { - _cleanup_strv_free_ char **pass_env = NULL; - size_t n_env = 0, n_bufsize = 0; - char **i; - - STRV_FOREACH(i, c->pass_environment) { - _cleanup_free_ char *x = NULL; - char *v; - - v = getenv(*i); - if (!v) - continue; - x = strjoin(*i, "=", v, NULL); - if (!x) - return -ENOMEM; - if (!GREEDY_REALLOC(pass_env, n_bufsize, n_env + 2)) - return -ENOMEM; - pass_env[n_env++] = x; - pass_env[n_env] = NULL; - x = NULL; - } - - *ret = pass_env; - pass_env = NULL; - - return 0; -} - -static bool exec_needs_mount_namespace( - const ExecContext *context, - const ExecParameters *params, - ExecRuntime *runtime) { - - assert(context); - assert(params); - - if (!strv_isempty(context->read_write_dirs) || - !strv_isempty(context->read_only_dirs) || - !strv_isempty(context->inaccessible_dirs)) - return true; - - if (context->mount_flags != 0) - return true; - - if (context->private_tmp && runtime && (runtime->tmp_dir || runtime->var_tmp_dir)) - return true; - - if (context->private_devices || - context->protect_system != PROTECT_SYSTEM_NO || - context->protect_home != PROTECT_HOME_NO) - return true; - - return false; -} - -static int close_remaining_fds( - const ExecParameters *params, - ExecRuntime *runtime, - int socket_fd, - int *fds, unsigned n_fds) { - - unsigned n_dont_close = 0; - int dont_close[n_fds + 7]; - - assert(params); - - if (params->stdin_fd >= 0) - dont_close[n_dont_close++] = params->stdin_fd; - if (params->stdout_fd >= 0) - dont_close[n_dont_close++] = params->stdout_fd; - if (params->stderr_fd >= 0) - dont_close[n_dont_close++] = params->stderr_fd; - - if (socket_fd >= 0) - dont_close[n_dont_close++] = socket_fd; - if (n_fds > 0) { - memcpy(dont_close + n_dont_close, fds, sizeof(int) * n_fds); - n_dont_close += n_fds; - } - - if (runtime) { - if (runtime->netns_storage_socket[0] >= 0) - dont_close[n_dont_close++] = runtime->netns_storage_socket[0]; - if (runtime->netns_storage_socket[1] >= 0) - dont_close[n_dont_close++] = runtime->netns_storage_socket[1]; - } - - return close_all_fds(dont_close, n_dont_close); -} - -static int exec_child( - Unit *unit, - ExecCommand *command, - const ExecContext *context, - const ExecParameters *params, - ExecRuntime *runtime, - char **argv, - int socket_fd, - int *fds, unsigned n_fds, - char **files_env, - int *exit_status) { - - _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **pam_env = NULL, **final_env = NULL, **final_argv = NULL; - _cleanup_free_ char *mac_selinux_context_net = NULL; - const char *username = NULL, *home = NULL, *shell = NULL, *wd; - uid_t uid = UID_INVALID; - gid_t gid = GID_INVALID; - int i, r; - bool needs_mount_namespace; - - assert(unit); - assert(command); - assert(context); - assert(params); - assert(exit_status); - - rename_process_from_path(command->path); - - /* We reset exactly these signals, since they are the - * only ones we set to SIG_IGN in the main daemon. All - * others we leave untouched because we set them to - * SIG_DFL or a valid handler initially, both of which - * will be demoted to SIG_DFL. */ - (void) default_signals(SIGNALS_CRASH_HANDLER, - SIGNALS_IGNORE, -1); - - if (context->ignore_sigpipe) - (void) ignore_signals(SIGPIPE, -1); - - r = reset_signal_mask(); - if (r < 0) { - *exit_status = EXIT_SIGNAL_MASK; - return r; - } - - if (params->idle_pipe) - do_idle_pipe_dance(params->idle_pipe); - - /* Close sockets very early to make sure we don't - * block init reexecution because it cannot bind its - * sockets */ - - log_forget_fds(); - - r = close_remaining_fds(params, runtime, socket_fd, fds, n_fds); - if (r < 0) { - *exit_status = EXIT_FDS; - return r; - } - - if (!context->same_pgrp) - if (setsid() < 0) { - *exit_status = EXIT_SETSID; - return -errno; - } - - exec_context_tty_reset(context, params); - - if (params->confirm_spawn) { - char response; - - r = ask_for_confirmation(&response, argv); - if (r == -ETIMEDOUT) - write_confirm_message("Confirmation question timed out, assuming positive response.\n"); - else if (r < 0) - write_confirm_message("Couldn't ask confirmation question, assuming positive response: %s\n", strerror(-r)); - else if (response == 's') { - write_confirm_message("Skipping execution.\n"); - *exit_status = EXIT_CONFIRM; - return -ECANCELED; - } else if (response == 'n') { - write_confirm_message("Failing execution.\n"); - *exit_status = 0; - return 0; - } - } - - if (context->user) { - username = context->user; - r = get_user_creds(&username, &uid, &gid, &home, &shell); - if (r < 0) { - *exit_status = EXIT_USER; - return r; - } - } - - if (context->group) { - const char *g = context->group; - - r = get_group_creds(&g, &gid); - if (r < 0) { - *exit_status = EXIT_GROUP; - return r; - } - } - - - /* If a socket is connected to STDIN/STDOUT/STDERR, we - * must sure to drop O_NONBLOCK */ - if (socket_fd >= 0) - (void) fd_nonblock(socket_fd, false); - - r = setup_input(context, params, socket_fd); - if (r < 0) { - *exit_status = EXIT_STDIN; - return r; - } - - r = setup_output(unit, context, params, STDOUT_FILENO, socket_fd, basename(command->path), uid, gid); - if (r < 0) { - *exit_status = EXIT_STDOUT; - return r; - } - - r = setup_output(unit, context, params, STDERR_FILENO, socket_fd, basename(command->path), uid, gid); - if (r < 0) { - *exit_status = EXIT_STDERR; - return r; - } - - if (params->cgroup_path) { - r = cg_attach_everywhere(params->cgroup_supported, params->cgroup_path, 0, NULL, NULL); - if (r < 0) { - *exit_status = EXIT_CGROUP; - return r; - } - } - - if (context->oom_score_adjust_set) { - char t[DECIMAL_STR_MAX(context->oom_score_adjust)]; - - /* When we can't make this change due to EPERM, then - * let's silently skip over it. User namespaces - * prohibit write access to this file, and we - * shouldn't trip up over that. */ - - sprintf(t, "%i", context->oom_score_adjust); - r = write_string_file("/proc/self/oom_score_adj", t, 0); - if (r == -EPERM || r == -EACCES) { - log_open(); - log_unit_debug_errno(unit, r, "Failed to adjust OOM setting, assuming containerized execution, ignoring: %m"); - log_close(); - } else if (r < 0) { - *exit_status = EXIT_OOM_ADJUST; - return -errno; - } - } - - if (context->nice_set) - if (setpriority(PRIO_PROCESS, 0, context->nice) < 0) { - *exit_status = EXIT_NICE; - return -errno; - } - - if (context->cpu_sched_set) { - struct sched_param param = { - .sched_priority = context->cpu_sched_priority, - }; - - r = sched_setscheduler(0, - context->cpu_sched_policy | - (context->cpu_sched_reset_on_fork ? - SCHED_RESET_ON_FORK : 0), - ¶m); - if (r < 0) { - *exit_status = EXIT_SETSCHEDULER; - return -errno; - } - } - - if (context->cpuset) - if (sched_setaffinity(0, CPU_ALLOC_SIZE(context->cpuset_ncpus), context->cpuset) < 0) { - *exit_status = EXIT_CPUAFFINITY; - return -errno; - } - - if (context->ioprio_set) - if (ioprio_set(IOPRIO_WHO_PROCESS, 0, context->ioprio) < 0) { - *exit_status = EXIT_IOPRIO; - return -errno; - } - - if (context->timer_slack_nsec != NSEC_INFINITY) - if (prctl(PR_SET_TIMERSLACK, context->timer_slack_nsec) < 0) { - *exit_status = EXIT_TIMERSLACK; - return -errno; - } - - if (context->personality != PERSONALITY_INVALID) - if (personality(context->personality) < 0) { - *exit_status = EXIT_PERSONALITY; - return -errno; - } - - if (context->utmp_id) - utmp_put_init_process(context->utmp_id, getpid(), getsid(0), context->tty_path, - context->utmp_mode == EXEC_UTMP_INIT ? INIT_PROCESS : - context->utmp_mode == EXEC_UTMP_LOGIN ? LOGIN_PROCESS : - USER_PROCESS, - username ? "root" : context->user); - - if (context->user && is_terminal_input(context->std_input)) { - r = chown_terminal(STDIN_FILENO, uid); - if (r < 0) { - *exit_status = EXIT_STDIN; - return r; - } - } - - /* If delegation is enabled we'll pass ownership of the cgroup - * (but only in systemd's own controller hierarchy!) to the - * user of the new process. */ - if (params->cgroup_path && context->user && params->cgroup_delegate) { - r = cg_set_task_access(SYSTEMD_CGROUP_CONTROLLER, params->cgroup_path, 0644, uid, gid); - if (r < 0) { - *exit_status = EXIT_CGROUP; - return r; - } - - - r = cg_set_group_access(SYSTEMD_CGROUP_CONTROLLER, params->cgroup_path, 0755, uid, gid); - if (r < 0) { - *exit_status = EXIT_CGROUP; - return r; - } - } - - if (!strv_isempty(context->runtime_directory) && params->runtime_prefix) { - char **rt; - - STRV_FOREACH(rt, context->runtime_directory) { - _cleanup_free_ char *p; - - p = strjoin(params->runtime_prefix, "/", *rt, NULL); - if (!p) { - *exit_status = EXIT_RUNTIME_DIRECTORY; - return -ENOMEM; - } - - r = mkdir_p_label(p, context->runtime_directory_mode); - if (r < 0) { - *exit_status = EXIT_RUNTIME_DIRECTORY; - return r; - } - - r = chmod_and_chown(p, context->runtime_directory_mode, uid, gid); - if (r < 0) { - *exit_status = EXIT_RUNTIME_DIRECTORY; - return r; - } - } - } - - umask(context->umask); - - if (params->apply_permissions) { - r = enforce_groups(context, username, gid); - if (r < 0) { - *exit_status = EXIT_GROUP; - return r; - } -#ifdef HAVE_SMACK - if (context->smack_process_label) { - r = mac_smack_apply_pid(0, context->smack_process_label); - if (r < 0) { - *exit_status = EXIT_SMACK_PROCESS_LABEL; - return r; - } - } -#ifdef SMACK_DEFAULT_PROCESS_LABEL - else { - _cleanup_free_ char *exec_label = NULL; - - r = mac_smack_read(command->path, SMACK_ATTR_EXEC, &exec_label); - if (r < 0 && r != -ENODATA && r != -EOPNOTSUPP) { - *exit_status = EXIT_SMACK_PROCESS_LABEL; - return r; - } - - r = mac_smack_apply_pid(0, exec_label ? : SMACK_DEFAULT_PROCESS_LABEL); - if (r < 0) { - *exit_status = EXIT_SMACK_PROCESS_LABEL; - return r; - } - } -#endif -#endif -#ifdef HAVE_PAM - if (context->pam_name && username) { - r = setup_pam(context->pam_name, username, uid, context->tty_path, &pam_env, fds, n_fds); - if (r < 0) { - *exit_status = EXIT_PAM; - return r; - } - } -#endif - } - - if (context->private_network && runtime && runtime->netns_storage_socket[0] >= 0) { - r = setup_netns(runtime->netns_storage_socket); - if (r < 0) { - *exit_status = EXIT_NETWORK; - return r; - } - } - - needs_mount_namespace = exec_needs_mount_namespace(context, params, runtime); - - if (needs_mount_namespace) { - char *tmp = NULL, *var = NULL; - - /* The runtime struct only contains the parent - * of the private /tmp, which is - * non-accessible to world users. Inside of it - * there's a /tmp that is sticky, and that's - * the one we want to use here. */ - - if (context->private_tmp && runtime) { - if (runtime->tmp_dir) - tmp = strjoina(runtime->tmp_dir, "/tmp"); - if (runtime->var_tmp_dir) - var = strjoina(runtime->var_tmp_dir, "/tmp"); - } - - r = setup_namespace( - params->apply_chroot ? context->root_directory : NULL, - context->read_write_dirs, - context->read_only_dirs, - context->inaccessible_dirs, - tmp, - var, - context->private_devices, - context->protect_home, - context->protect_system, - context->mount_flags); - - /* If we couldn't set up the namespace this is - * probably due to a missing capability. In this case, - * silently proceeed. */ - if (r == -EPERM || r == -EACCES) { - log_open(); - log_unit_debug_errno(unit, r, "Failed to set up namespace, assuming containerized execution, ignoring: %m"); - log_close(); - } else if (r < 0) { - *exit_status = EXIT_NAMESPACE; - return r; - } - } - - if (context->working_directory_home) - wd = home; - else if (context->working_directory) - wd = context->working_directory; - else - wd = "/"; - - if (params->apply_chroot) { - if (!needs_mount_namespace && context->root_directory) - if (chroot(context->root_directory) < 0) { - *exit_status = EXIT_CHROOT; - return -errno; - } - - if (chdir(wd) < 0 && - !context->working_directory_missing_ok) { - *exit_status = EXIT_CHDIR; - return -errno; - } - } else { - const char *d; - - d = strjoina(strempty(context->root_directory), "/", strempty(wd)); - if (chdir(d) < 0 && - !context->working_directory_missing_ok) { - *exit_status = EXIT_CHDIR; - return -errno; - } - } - -#ifdef HAVE_SELINUX - if (params->apply_permissions && mac_selinux_use() && params->selinux_context_net && socket_fd >= 0) { - r = mac_selinux_get_child_mls_label(socket_fd, command->path, context->selinux_context, &mac_selinux_context_net); - if (r < 0) { - *exit_status = EXIT_SELINUX_CONTEXT; - return r; - } - } -#endif - - /* We repeat the fd closing here, to make sure that - * nothing is leaked from the PAM modules. Note that - * we are more aggressive this time since socket_fd - * and the netns fds we don't need anymore. The custom - * endpoint fd was needed to upload the policy and can - * now be closed as well. */ - r = close_all_fds(fds, n_fds); - if (r >= 0) - r = shift_fds(fds, n_fds); - if (r >= 0) - r = flags_fds(fds, n_fds, context->non_blocking); - if (r < 0) { - *exit_status = EXIT_FDS; - return r; - } - - if (params->apply_permissions) { - - bool use_address_families = context->address_families_whitelist || - !set_isempty(context->address_families); - bool use_syscall_filter = context->syscall_whitelist || - !set_isempty(context->syscall_filter) || - !set_isempty(context->syscall_archs); - int secure_bits = context->secure_bits; - - for (i = 0; i < _RLIMIT_MAX; i++) { - if (!context->rlimit[i]) - continue; - - if (setrlimit_closest(i, context->rlimit[i]) < 0) { - *exit_status = EXIT_LIMITS; - return -errno; - } - } - - if (!cap_test_all(context->capability_bounding_set)) { - r = capability_bounding_set_drop(context->capability_bounding_set, false); - if (r < 0) { - *exit_status = EXIT_CAPABILITIES; - return r; - } - } - - /* This is done before enforce_user, but ambient set - * does not survive over setresuid() if keep_caps is not set. */ - if (context->capability_ambient_set != 0) { - r = capability_ambient_set_apply(context->capability_ambient_set, true); - if (r < 0) { - *exit_status = EXIT_CAPABILITIES; - return r; - } - } - - if (context->user) { - r = enforce_user(context, uid); - if (r < 0) { - *exit_status = EXIT_USER; - return r; - } - if (context->capability_ambient_set != 0) { - - /* Fix the ambient capabilities after user change. */ - r = capability_ambient_set_apply(context->capability_ambient_set, false); - if (r < 0) { - *exit_status = EXIT_CAPABILITIES; - return r; - } - - /* If we were asked to change user and ambient capabilities - * were requested, we had to add keep-caps to the securebits - * so that we would maintain the inherited capability set - * through the setresuid(). Make sure that the bit is added - * also to the context secure_bits so that we don't try to - * drop the bit away next. */ - - secure_bits |= 1<no_new_privileges || - (!have_effective_cap(CAP_SYS_ADMIN) && (use_address_families || use_syscall_filter))) - if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { - *exit_status = EXIT_NO_NEW_PRIVILEGES; - return -errno; - } - -#ifdef HAVE_SECCOMP - if (use_address_families) { - r = apply_address_families(context); - if (r < 0) { - *exit_status = EXIT_ADDRESS_FAMILIES; - return r; - } - } - - if (use_syscall_filter) { - r = apply_seccomp(context); - if (r < 0) { - *exit_status = EXIT_SECCOMP; - return r; - } - } -#endif - -#ifdef HAVE_SELINUX - if (mac_selinux_use()) { - char *exec_context = mac_selinux_context_net ?: context->selinux_context; - - if (exec_context) { - r = setexeccon(exec_context); - if (r < 0) { - *exit_status = EXIT_SELINUX_CONTEXT; - return r; - } - } - } -#endif - -#ifdef HAVE_APPARMOR - if (context->apparmor_profile && mac_apparmor_use()) { - r = aa_change_onexec(context->apparmor_profile); - if (r < 0 && !context->apparmor_profile_ignore) { - *exit_status = EXIT_APPARMOR_PROFILE; - return -errno; - } - } -#endif - } - - r = build_environment(context, params, n_fds, home, username, shell, &our_env); - if (r < 0) { - *exit_status = EXIT_MEMORY; - return r; - } - - r = build_pass_environment(context, &pass_env); - if (r < 0) { - *exit_status = EXIT_MEMORY; - return r; - } - - final_env = strv_env_merge(6, - params->environment, - our_env, - pass_env, - context->environment, - files_env, - pam_env, - NULL); - if (!final_env) { - *exit_status = EXIT_MEMORY; - return -ENOMEM; - } - - final_argv = replace_env_argv(argv, final_env); - if (!final_argv) { - *exit_status = EXIT_MEMORY; - return -ENOMEM; - } - - final_env = strv_env_clean(final_env); - - if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) { - _cleanup_free_ char *line; - - line = exec_command_line(final_argv); - if (line) { - log_open(); - log_struct(LOG_DEBUG, - LOG_UNIT_ID(unit), - "EXECUTABLE=%s", command->path, - LOG_UNIT_MESSAGE(unit, "Executing: %s", line), - NULL); - log_close(); - } - } - - execve(command->path, final_argv, final_env); - *exit_status = EXIT_EXEC; - return -errno; -} - -int exec_spawn(Unit *unit, - ExecCommand *command, - const ExecContext *context, - const ExecParameters *params, - ExecRuntime *runtime, - pid_t *ret) { - - _cleanup_strv_free_ char **files_env = NULL; - int *fds = NULL; unsigned n_fds = 0; - _cleanup_free_ char *line = NULL; - int socket_fd, r; - char **argv; - pid_t pid; - - assert(unit); - assert(command); - assert(context); - assert(ret); - assert(params); - assert(params->fds || params->n_fds <= 0); - - if (context->std_input == EXEC_INPUT_SOCKET || - context->std_output == EXEC_OUTPUT_SOCKET || - context->std_error == EXEC_OUTPUT_SOCKET) { - - if (params->n_fds != 1) { - log_unit_error(unit, "Got more than one socket."); - return -EINVAL; - } - - socket_fd = params->fds[0]; - } else { - socket_fd = -1; - fds = params->fds; - n_fds = params->n_fds; - } - - r = exec_context_load_environment(unit, context, &files_env); - if (r < 0) - return log_unit_error_errno(unit, r, "Failed to load environment files: %m"); - - argv = params->argv ?: command->argv; - line = exec_command_line(argv); - if (!line) - return log_oom(); - - log_struct(LOG_DEBUG, - LOG_UNIT_ID(unit), - LOG_UNIT_MESSAGE(unit, "About to execute: %s", line), - "EXECUTABLE=%s", command->path, - NULL); - pid = fork(); - if (pid < 0) - return log_unit_error_errno(unit, errno, "Failed to fork: %m"); - - if (pid == 0) { - int exit_status; - - r = exec_child(unit, - command, - context, - params, - runtime, - argv, - socket_fd, - fds, n_fds, - files_env, - &exit_status); - if (r < 0) { - log_open(); - log_struct_errno(LOG_ERR, r, - LOG_MESSAGE_ID(SD_MESSAGE_SPAWN_FAILED), - LOG_UNIT_ID(unit), - LOG_UNIT_MESSAGE(unit, "Failed at step %s spawning %s: %m", - exit_status_to_string(exit_status, EXIT_STATUS_SYSTEMD), - command->path), - "EXECUTABLE=%s", command->path, - NULL); - } - - _exit(exit_status); - } - - log_unit_debug(unit, "Forked %s as "PID_FMT, command->path, pid); - - /* We add the new process to the cgroup both in the child (so - * that we can be sure that no user code is ever executed - * outside of the cgroup) and in the parent (so that we can be - * sure that when we kill the cgroup the process will be - * killed too). */ - if (params->cgroup_path) - (void) cg_attach(SYSTEMD_CGROUP_CONTROLLER, params->cgroup_path, pid); - - exec_status_start(&command->exec_status, pid); - - *ret = pid; - return 0; -} - -void exec_context_init(ExecContext *c) { - assert(c); - - c->umask = 0022; - c->ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 0); - c->cpu_sched_policy = SCHED_OTHER; - c->syslog_priority = LOG_DAEMON|LOG_INFO; - c->syslog_level_prefix = true; - c->ignore_sigpipe = true; - c->timer_slack_nsec = NSEC_INFINITY; - c->personality = PERSONALITY_INVALID; - c->runtime_directory_mode = 0755; - c->capability_bounding_set = CAP_ALL; -} - -void exec_context_done(ExecContext *c) { - unsigned l; - - assert(c); - - c->environment = strv_free(c->environment); - c->environment_files = strv_free(c->environment_files); - c->pass_environment = strv_free(c->pass_environment); - - for (l = 0; l < ELEMENTSOF(c->rlimit); l++) - c->rlimit[l] = mfree(c->rlimit[l]); - - c->working_directory = mfree(c->working_directory); - c->root_directory = mfree(c->root_directory); - c->tty_path = mfree(c->tty_path); - c->syslog_identifier = mfree(c->syslog_identifier); - c->user = mfree(c->user); - c->group = mfree(c->group); - - c->supplementary_groups = strv_free(c->supplementary_groups); - - c->pam_name = mfree(c->pam_name); - - c->read_only_dirs = strv_free(c->read_only_dirs); - c->read_write_dirs = strv_free(c->read_write_dirs); - c->inaccessible_dirs = strv_free(c->inaccessible_dirs); - - if (c->cpuset) - CPU_FREE(c->cpuset); - - c->utmp_id = mfree(c->utmp_id); - c->selinux_context = mfree(c->selinux_context); - c->apparmor_profile = mfree(c->apparmor_profile); - - c->syscall_filter = set_free(c->syscall_filter); - c->syscall_archs = set_free(c->syscall_archs); - c->address_families = set_free(c->address_families); - - c->runtime_directory = strv_free(c->runtime_directory); -} - -int exec_context_destroy_runtime_directory(ExecContext *c, const char *runtime_prefix) { - char **i; - - assert(c); - - if (!runtime_prefix) - return 0; - - STRV_FOREACH(i, c->runtime_directory) { - _cleanup_free_ char *p; - - p = strjoin(runtime_prefix, "/", *i, NULL); - if (!p) - return -ENOMEM; - - /* We execute this synchronously, since we need to be - * sure this is gone when we start the service - * next. */ - (void) rm_rf(p, REMOVE_ROOT); - } - - return 0; -} - -void exec_command_done(ExecCommand *c) { - assert(c); - - c->path = mfree(c->path); - - c->argv = strv_free(c->argv); -} - -void exec_command_done_array(ExecCommand *c, unsigned n) { - unsigned i; - - for (i = 0; i < n; i++) - exec_command_done(c+i); -} - -ExecCommand* exec_command_free_list(ExecCommand *c) { - ExecCommand *i; - - while ((i = c)) { - LIST_REMOVE(command, c, i); - exec_command_done(i); - free(i); - } - - return NULL; -} - -void exec_command_free_array(ExecCommand **c, unsigned n) { - unsigned i; - - for (i = 0; i < n; i++) - c[i] = exec_command_free_list(c[i]); -} - -typedef struct InvalidEnvInfo { - Unit *unit; - const char *path; -} InvalidEnvInfo; - -static void invalid_env(const char *p, void *userdata) { - InvalidEnvInfo *info = userdata; - - log_unit_error(info->unit, "Ignoring invalid environment assignment '%s': %s", p, info->path); -} - -int exec_context_load_environment(Unit *unit, const ExecContext *c, char ***l) { - char **i, **r = NULL; - - assert(c); - assert(l); - - STRV_FOREACH(i, c->environment_files) { - char *fn; - int k; - bool ignore = false; - char **p; - _cleanup_globfree_ glob_t pglob = {}; - int count, n; - - fn = *i; - - if (fn[0] == '-') { - ignore = true; - fn++; - } - - if (!path_is_absolute(fn)) { - if (ignore) - continue; - - strv_free(r); - return -EINVAL; - } - - /* Filename supports globbing, take all matching files */ - errno = 0; - if (glob(fn, 0, NULL, &pglob) != 0) { - if (ignore) - continue; - - strv_free(r); - return errno > 0 ? -errno : -EINVAL; - } - count = pglob.gl_pathc; - if (count == 0) { - if (ignore) - continue; - - strv_free(r); - return -EINVAL; - } - for (n = 0; n < count; n++) { - k = load_env_file(NULL, pglob.gl_pathv[n], NULL, &p); - if (k < 0) { - if (ignore) - continue; - - strv_free(r); - return k; - } - /* Log invalid environment variables with filename */ - if (p) { - InvalidEnvInfo info = { - .unit = unit, - .path = pglob.gl_pathv[n] - }; - - p = strv_env_clean_with_callback(p, invalid_env, &info); - } - - if (r == NULL) - r = p; - else { - char **m; - - m = strv_env_merge(2, r, p); - strv_free(r); - strv_free(p); - if (!m) - return -ENOMEM; - - r = m; - } - } - } - - *l = r; - - return 0; -} - -static bool tty_may_match_dev_console(const char *tty) { - _cleanup_free_ char *active = NULL; - char *console; - - if (!tty) - return true; - - if (startswith(tty, "/dev/")) - tty += 5; - - /* trivial identity? */ - if (streq(tty, "console")) - return true; - - console = resolve_dev_console(&active); - /* if we could not resolve, assume it may */ - if (!console) - return true; - - /* "tty0" means the active VC, so it may be the same sometimes */ - return streq(console, tty) || (streq(console, "tty0") && tty_is_vc(tty)); -} - -bool exec_context_may_touch_console(ExecContext *ec) { - - return (ec->tty_reset || - ec->tty_vhangup || - ec->tty_vt_disallocate || - is_terminal_input(ec->std_input) || - is_terminal_output(ec->std_output) || - is_terminal_output(ec->std_error)) && - tty_may_match_dev_console(exec_context_tty_path(ec)); -} - -static void strv_fprintf(FILE *f, char **l) { - char **g; - - assert(f); - - STRV_FOREACH(g, l) - fprintf(f, " %s", *g); -} - -void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { - char **e, **d; - unsigned i; - - assert(c); - assert(f); - - prefix = strempty(prefix); - - fprintf(f, - "%sUMask: %04o\n" - "%sWorkingDirectory: %s\n" - "%sRootDirectory: %s\n" - "%sNonBlocking: %s\n" - "%sPrivateTmp: %s\n" - "%sPrivateNetwork: %s\n" - "%sPrivateDevices: %s\n" - "%sProtectHome: %s\n" - "%sProtectSystem: %s\n" - "%sIgnoreSIGPIPE: %s\n", - prefix, c->umask, - prefix, c->working_directory ? c->working_directory : "/", - prefix, c->root_directory ? c->root_directory : "/", - prefix, yes_no(c->non_blocking), - prefix, yes_no(c->private_tmp), - prefix, yes_no(c->private_network), - prefix, yes_no(c->private_devices), - prefix, protect_home_to_string(c->protect_home), - prefix, protect_system_to_string(c->protect_system), - prefix, yes_no(c->ignore_sigpipe)); - - STRV_FOREACH(e, c->environment) - fprintf(f, "%sEnvironment: %s\n", prefix, *e); - - STRV_FOREACH(e, c->environment_files) - fprintf(f, "%sEnvironmentFile: %s\n", prefix, *e); - - STRV_FOREACH(e, c->pass_environment) - fprintf(f, "%sPassEnvironment: %s\n", prefix, *e); - - fprintf(f, "%sRuntimeDirectoryMode: %04o\n", prefix, c->runtime_directory_mode); - - STRV_FOREACH(d, c->runtime_directory) - fprintf(f, "%sRuntimeDirectory: %s\n", prefix, *d); - - if (c->nice_set) - fprintf(f, - "%sNice: %i\n", - prefix, c->nice); - - if (c->oom_score_adjust_set) - fprintf(f, - "%sOOMScoreAdjust: %i\n", - prefix, c->oom_score_adjust); - - for (i = 0; i < RLIM_NLIMITS; i++) - if (c->rlimit[i]) { - fprintf(f, "%s%s: " RLIM_FMT "\n", - prefix, rlimit_to_string(i), c->rlimit[i]->rlim_max); - fprintf(f, "%s%sSoft: " RLIM_FMT "\n", - prefix, rlimit_to_string(i), c->rlimit[i]->rlim_cur); - } - - if (c->ioprio_set) { - _cleanup_free_ char *class_str = NULL; - - ioprio_class_to_string_alloc(IOPRIO_PRIO_CLASS(c->ioprio), &class_str); - fprintf(f, - "%sIOSchedulingClass: %s\n" - "%sIOPriority: %i\n", - prefix, strna(class_str), - prefix, (int) IOPRIO_PRIO_DATA(c->ioprio)); - } - - if (c->cpu_sched_set) { - _cleanup_free_ char *policy_str = NULL; - - sched_policy_to_string_alloc(c->cpu_sched_policy, &policy_str); - fprintf(f, - "%sCPUSchedulingPolicy: %s\n" - "%sCPUSchedulingPriority: %i\n" - "%sCPUSchedulingResetOnFork: %s\n", - prefix, strna(policy_str), - prefix, c->cpu_sched_priority, - prefix, yes_no(c->cpu_sched_reset_on_fork)); - } - - if (c->cpuset) { - fprintf(f, "%sCPUAffinity:", prefix); - for (i = 0; i < c->cpuset_ncpus; i++) - if (CPU_ISSET_S(i, CPU_ALLOC_SIZE(c->cpuset_ncpus), c->cpuset)) - fprintf(f, " %u", i); - fputs("\n", f); - } - - if (c->timer_slack_nsec != NSEC_INFINITY) - fprintf(f, "%sTimerSlackNSec: "NSEC_FMT "\n", prefix, c->timer_slack_nsec); - - fprintf(f, - "%sStandardInput: %s\n" - "%sStandardOutput: %s\n" - "%sStandardError: %s\n", - prefix, exec_input_to_string(c->std_input), - prefix, exec_output_to_string(c->std_output), - prefix, exec_output_to_string(c->std_error)); - - if (c->tty_path) - fprintf(f, - "%sTTYPath: %s\n" - "%sTTYReset: %s\n" - "%sTTYVHangup: %s\n" - "%sTTYVTDisallocate: %s\n", - prefix, c->tty_path, - prefix, yes_no(c->tty_reset), - prefix, yes_no(c->tty_vhangup), - prefix, yes_no(c->tty_vt_disallocate)); - - if (c->std_output == EXEC_OUTPUT_SYSLOG || - c->std_output == EXEC_OUTPUT_KMSG || - c->std_output == EXEC_OUTPUT_JOURNAL || - c->std_output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || - c->std_output == EXEC_OUTPUT_KMSG_AND_CONSOLE || - c->std_output == EXEC_OUTPUT_JOURNAL_AND_CONSOLE || - c->std_error == EXEC_OUTPUT_SYSLOG || - c->std_error == EXEC_OUTPUT_KMSG || - c->std_error == EXEC_OUTPUT_JOURNAL || - c->std_error == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || - c->std_error == EXEC_OUTPUT_KMSG_AND_CONSOLE || - c->std_error == EXEC_OUTPUT_JOURNAL_AND_CONSOLE) { - - _cleanup_free_ char *fac_str = NULL, *lvl_str = NULL; - - log_facility_unshifted_to_string_alloc(c->syslog_priority >> 3, &fac_str); - log_level_to_string_alloc(LOG_PRI(c->syslog_priority), &lvl_str); - - fprintf(f, - "%sSyslogFacility: %s\n" - "%sSyslogLevel: %s\n", - prefix, strna(fac_str), - prefix, strna(lvl_str)); - } - - if (c->secure_bits) - fprintf(f, "%sSecure Bits:%s%s%s%s%s%s\n", - prefix, - (c->secure_bits & 1<secure_bits & 1<secure_bits & 1<secure_bits & 1<secure_bits & 1<secure_bits & 1<capability_bounding_set != CAP_ALL) { - unsigned long l; - fprintf(f, "%sCapabilityBoundingSet:", prefix); - - for (l = 0; l <= cap_last_cap(); l++) - if (c->capability_bounding_set & (UINT64_C(1) << l)) - fprintf(f, " %s", strna(capability_to_name(l))); - - fputs("\n", f); - } - - if (c->capability_ambient_set != 0) { - unsigned long l; - fprintf(f, "%sAmbientCapabilities:", prefix); - - for (l = 0; l <= cap_last_cap(); l++) - if (c->capability_ambient_set & (UINT64_C(1) << l)) - fprintf(f, " %s", strna(capability_to_name(l))); - - fputs("\n", f); - } - - if (c->user) - fprintf(f, "%sUser: %s\n", prefix, c->user); - if (c->group) - fprintf(f, "%sGroup: %s\n", prefix, c->group); - - if (strv_length(c->supplementary_groups) > 0) { - fprintf(f, "%sSupplementaryGroups:", prefix); - strv_fprintf(f, c->supplementary_groups); - fputs("\n", f); - } - - if (c->pam_name) - fprintf(f, "%sPAMName: %s\n", prefix, c->pam_name); - - if (strv_length(c->read_write_dirs) > 0) { - fprintf(f, "%sReadWriteDirs:", prefix); - strv_fprintf(f, c->read_write_dirs); - fputs("\n", f); - } - - if (strv_length(c->read_only_dirs) > 0) { - fprintf(f, "%sReadOnlyDirs:", prefix); - strv_fprintf(f, c->read_only_dirs); - fputs("\n", f); - } - - if (strv_length(c->inaccessible_dirs) > 0) { - fprintf(f, "%sInaccessibleDirs:", prefix); - strv_fprintf(f, c->inaccessible_dirs); - fputs("\n", f); - } - - if (c->utmp_id) - fprintf(f, - "%sUtmpIdentifier: %s\n", - prefix, c->utmp_id); - - if (c->selinux_context) - fprintf(f, - "%sSELinuxContext: %s%s\n", - prefix, c->selinux_context_ignore ? "-" : "", c->selinux_context); - - if (c->personality != PERSONALITY_INVALID) - fprintf(f, - "%sPersonality: %s\n", - prefix, strna(personality_to_string(c->personality))); - - if (c->syscall_filter) { -#ifdef HAVE_SECCOMP - Iterator j; - void *id; - bool first = true; -#endif - - fprintf(f, - "%sSystemCallFilter: ", - prefix); - - if (!c->syscall_whitelist) - fputc('~', f); - -#ifdef HAVE_SECCOMP - SET_FOREACH(id, c->syscall_filter, j) { - _cleanup_free_ char *name = NULL; - - if (first) - first = false; - else - fputc(' ', f); - - name = seccomp_syscall_resolve_num_arch(SCMP_ARCH_NATIVE, PTR_TO_INT(id) - 1); - fputs(strna(name), f); - } -#endif - - fputc('\n', f); - } - - if (c->syscall_archs) { -#ifdef HAVE_SECCOMP - Iterator j; - void *id; -#endif - - fprintf(f, - "%sSystemCallArchitectures:", - prefix); - -#ifdef HAVE_SECCOMP - SET_FOREACH(id, c->syscall_archs, j) - fprintf(f, " %s", strna(seccomp_arch_to_string(PTR_TO_UINT32(id) - 1))); -#endif - fputc('\n', f); - } - - if (c->syscall_errno > 0) - fprintf(f, - "%sSystemCallErrorNumber: %s\n", - prefix, strna(errno_to_name(c->syscall_errno))); - - if (c->apparmor_profile) - fprintf(f, - "%sAppArmorProfile: %s%s\n", - prefix, c->apparmor_profile_ignore ? "-" : "", c->apparmor_profile); -} - -bool exec_context_maintains_privileges(ExecContext *c) { - assert(c); - - /* Returns true if the process forked off would run run under - * an unchanged UID or as root. */ - - if (!c->user) - return true; - - if (streq(c->user, "root") || streq(c->user, "0")) - return true; - - return false; -} - -void exec_status_start(ExecStatus *s, pid_t pid) { - assert(s); - - zero(*s); - s->pid = pid; - dual_timestamp_get(&s->start_timestamp); -} - -void exec_status_exit(ExecStatus *s, ExecContext *context, pid_t pid, int code, int status) { - assert(s); - - if (s->pid && s->pid != pid) - zero(*s); - - s->pid = pid; - dual_timestamp_get(&s->exit_timestamp); - - s->code = code; - s->status = status; - - if (context) { - if (context->utmp_id) - utmp_put_dead_process(context->utmp_id, pid, code, status); - - exec_context_tty_reset(context, NULL); - } -} - -void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix) { - char buf[FORMAT_TIMESTAMP_MAX]; - - assert(s); - assert(f); - - if (s->pid <= 0) - return; - - prefix = strempty(prefix); - - fprintf(f, - "%sPID: "PID_FMT"\n", - prefix, s->pid); - - if (s->start_timestamp.realtime > 0) - fprintf(f, - "%sStart Timestamp: %s\n", - prefix, format_timestamp(buf, sizeof(buf), s->start_timestamp.realtime)); - - if (s->exit_timestamp.realtime > 0) - fprintf(f, - "%sExit Timestamp: %s\n" - "%sExit Code: %s\n" - "%sExit Status: %i\n", - prefix, format_timestamp(buf, sizeof(buf), s->exit_timestamp.realtime), - prefix, sigchld_code_to_string(s->code), - prefix, s->status); -} - -char *exec_command_line(char **argv) { - size_t k; - char *n, *p, **a; - bool first = true; - - assert(argv); - - k = 1; - STRV_FOREACH(a, argv) - k += strlen(*a)+3; - - if (!(n = new(char, k))) - return NULL; - - p = n; - STRV_FOREACH(a, argv) { - - if (!first) - *(p++) = ' '; - else - first = false; - - if (strpbrk(*a, WHITESPACE)) { - *(p++) = '\''; - p = stpcpy(p, *a); - *(p++) = '\''; - } else - p = stpcpy(p, *a); - - } - - *p = 0; - - /* FIXME: this doesn't really handle arguments that have - * spaces and ticks in them */ - - return n; -} - -void exec_command_dump(ExecCommand *c, FILE *f, const char *prefix) { - _cleanup_free_ char *cmd = NULL; - const char *prefix2; - - assert(c); - assert(f); - - prefix = strempty(prefix); - prefix2 = strjoina(prefix, "\t"); - - cmd = exec_command_line(c->argv); - fprintf(f, - "%sCommand Line: %s\n", - prefix, cmd ? cmd : strerror(ENOMEM)); - - exec_status_dump(&c->exec_status, f, prefix2); -} - -void exec_command_dump_list(ExecCommand *c, FILE *f, const char *prefix) { - assert(f); - - prefix = strempty(prefix); - - LIST_FOREACH(command, c, c) - exec_command_dump(c, f, prefix); -} - -void exec_command_append_list(ExecCommand **l, ExecCommand *e) { - ExecCommand *end; - - assert(l); - assert(e); - - if (*l) { - /* It's kind of important, that we keep the order here */ - LIST_FIND_TAIL(command, *l, end); - LIST_INSERT_AFTER(command, *l, end, e); - } else - *l = e; -} - -int exec_command_set(ExecCommand *c, const char *path, ...) { - va_list ap; - char **l, *p; - - assert(c); - assert(path); - - va_start(ap, path); - l = strv_new_ap(path, ap); - va_end(ap); - - if (!l) - return -ENOMEM; - - p = strdup(path); - if (!p) { - strv_free(l); - return -ENOMEM; - } - - free(c->path); - c->path = p; - - strv_free(c->argv); - c->argv = l; - - return 0; -} - -int exec_command_append(ExecCommand *c, const char *path, ...) { - _cleanup_strv_free_ char **l = NULL; - va_list ap; - int r; - - assert(c); - assert(path); - - va_start(ap, path); - l = strv_new_ap(path, ap); - va_end(ap); - - if (!l) - return -ENOMEM; - - r = strv_extend_strv(&c->argv, l, false); - if (r < 0) - return r; - - return 0; -} - - -static int exec_runtime_allocate(ExecRuntime **rt) { - - if (*rt) - return 0; - - *rt = new0(ExecRuntime, 1); - if (!*rt) - return -ENOMEM; - - (*rt)->n_ref = 1; - (*rt)->netns_storage_socket[0] = (*rt)->netns_storage_socket[1] = -1; - - return 0; -} - -int exec_runtime_make(ExecRuntime **rt, ExecContext *c, const char *id) { - int r; - - assert(rt); - assert(c); - assert(id); - - if (*rt) - return 1; - - if (!c->private_network && !c->private_tmp) - return 0; - - r = exec_runtime_allocate(rt); - if (r < 0) - return r; - - if (c->private_network && (*rt)->netns_storage_socket[0] < 0) { - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, (*rt)->netns_storage_socket) < 0) - return -errno; - } - - if (c->private_tmp && !(*rt)->tmp_dir) { - r = setup_tmp_dirs(id, &(*rt)->tmp_dir, &(*rt)->var_tmp_dir); - if (r < 0) - return r; - } - - return 1; -} - -ExecRuntime *exec_runtime_ref(ExecRuntime *r) { - assert(r); - assert(r->n_ref > 0); - - r->n_ref++; - return r; -} - -ExecRuntime *exec_runtime_unref(ExecRuntime *r) { - - if (!r) - return NULL; - - assert(r->n_ref > 0); - - r->n_ref--; - if (r->n_ref > 0) - return NULL; - - free(r->tmp_dir); - free(r->var_tmp_dir); - safe_close_pair(r->netns_storage_socket); - free(r); - - return NULL; -} - -int exec_runtime_serialize(Unit *u, ExecRuntime *rt, FILE *f, FDSet *fds) { - assert(u); - assert(f); - assert(fds); - - if (!rt) - return 0; - - if (rt->tmp_dir) - unit_serialize_item(u, f, "tmp-dir", rt->tmp_dir); - - if (rt->var_tmp_dir) - unit_serialize_item(u, f, "var-tmp-dir", rt->var_tmp_dir); - - if (rt->netns_storage_socket[0] >= 0) { - int copy; - - copy = fdset_put_dup(fds, rt->netns_storage_socket[0]); - if (copy < 0) - return copy; - - unit_serialize_item_format(u, f, "netns-socket-0", "%i", copy); - } - - if (rt->netns_storage_socket[1] >= 0) { - int copy; - - copy = fdset_put_dup(fds, rt->netns_storage_socket[1]); - if (copy < 0) - return copy; - - unit_serialize_item_format(u, f, "netns-socket-1", "%i", copy); - } - - return 0; -} - -int exec_runtime_deserialize_item(Unit *u, ExecRuntime **rt, const char *key, const char *value, FDSet *fds) { - int r; - - assert(rt); - assert(key); - assert(value); - - if (streq(key, "tmp-dir")) { - char *copy; - - r = exec_runtime_allocate(rt); - if (r < 0) - return log_oom(); - - copy = strdup(value); - if (!copy) - return log_oom(); - - free((*rt)->tmp_dir); - (*rt)->tmp_dir = copy; - - } else if (streq(key, "var-tmp-dir")) { - char *copy; - - r = exec_runtime_allocate(rt); - if (r < 0) - return log_oom(); - - copy = strdup(value); - if (!copy) - return log_oom(); - - free((*rt)->var_tmp_dir); - (*rt)->var_tmp_dir = copy; - - } else if (streq(key, "netns-socket-0")) { - int fd; - - r = exec_runtime_allocate(rt); - if (r < 0) - return log_oom(); - - if (safe_atoi(value, &fd) < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse netns socket value: %s", value); - else { - safe_close((*rt)->netns_storage_socket[0]); - (*rt)->netns_storage_socket[0] = fdset_remove(fds, fd); - } - } else if (streq(key, "netns-socket-1")) { - int fd; - - r = exec_runtime_allocate(rt); - if (r < 0) - return log_oom(); - - if (safe_atoi(value, &fd) < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse netns socket value: %s", value); - else { - safe_close((*rt)->netns_storage_socket[1]); - (*rt)->netns_storage_socket[1] = fdset_remove(fds, fd); - } - } else - return 0; - - return 1; -} - -static void *remove_tmpdir_thread(void *p) { - _cleanup_free_ char *path = p; - - (void) rm_rf(path, REMOVE_ROOT|REMOVE_PHYSICAL); - return NULL; -} - -void exec_runtime_destroy(ExecRuntime *rt) { - int r; - - if (!rt) - return; - - /* If there are multiple users of this, let's leave the stuff around */ - if (rt->n_ref > 1) - return; - - if (rt->tmp_dir) { - log_debug("Spawning thread to nuke %s", rt->tmp_dir); - - r = asynchronous_job(remove_tmpdir_thread, rt->tmp_dir); - if (r < 0) { - log_warning_errno(r, "Failed to nuke %s: %m", rt->tmp_dir); - free(rt->tmp_dir); - } - - rt->tmp_dir = NULL; - } - - if (rt->var_tmp_dir) { - log_debug("Spawning thread to nuke %s", rt->var_tmp_dir); - - r = asynchronous_job(remove_tmpdir_thread, rt->var_tmp_dir); - if (r < 0) { - log_warning_errno(r, "Failed to nuke %s: %m", rt->var_tmp_dir); - free(rt->var_tmp_dir); - } - - rt->var_tmp_dir = NULL; - } - - safe_close_pair(rt->netns_storage_socket); -} - -static const char* const exec_input_table[_EXEC_INPUT_MAX] = { - [EXEC_INPUT_NULL] = "null", - [EXEC_INPUT_TTY] = "tty", - [EXEC_INPUT_TTY_FORCE] = "tty-force", - [EXEC_INPUT_TTY_FAIL] = "tty-fail", - [EXEC_INPUT_SOCKET] = "socket" -}; - -DEFINE_STRING_TABLE_LOOKUP(exec_input, ExecInput); - -static const char* const exec_output_table[_EXEC_OUTPUT_MAX] = { - [EXEC_OUTPUT_INHERIT] = "inherit", - [EXEC_OUTPUT_NULL] = "null", - [EXEC_OUTPUT_TTY] = "tty", - [EXEC_OUTPUT_SYSLOG] = "syslog", - [EXEC_OUTPUT_SYSLOG_AND_CONSOLE] = "syslog+console", - [EXEC_OUTPUT_KMSG] = "kmsg", - [EXEC_OUTPUT_KMSG_AND_CONSOLE] = "kmsg+console", - [EXEC_OUTPUT_JOURNAL] = "journal", - [EXEC_OUTPUT_JOURNAL_AND_CONSOLE] = "journal+console", - [EXEC_OUTPUT_SOCKET] = "socket" -}; - -DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput); - -static const char* const exec_utmp_mode_table[_EXEC_UTMP_MODE_MAX] = { - [EXEC_UTMP_INIT] = "init", - [EXEC_UTMP_LOGIN] = "login", - [EXEC_UTMP_USER] = "user", -}; - -DEFINE_STRING_TABLE_LOOKUP(exec_utmp_mode, ExecUtmpMode); diff --git a/src/core/execute.h b/src/core/execute.h deleted file mode 100644 index 41148bcea2..0000000000 --- a/src/core/execute.h +++ /dev/null @@ -1,287 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct ExecStatus ExecStatus; -typedef struct ExecCommand ExecCommand; -typedef struct ExecContext ExecContext; -typedef struct ExecRuntime ExecRuntime; -typedef struct ExecParameters ExecParameters; - -#include -#include -#include -#include - -#include "fdset.h" -#include "list.h" -#include "missing.h" -#include "namespace.h" - -typedef enum ExecUtmpMode { - EXEC_UTMP_INIT, - EXEC_UTMP_LOGIN, - EXEC_UTMP_USER, - _EXEC_UTMP_MODE_MAX, - _EXEC_UTMP_MODE_INVALID = -1 -} ExecUtmpMode; - -typedef enum ExecInput { - EXEC_INPUT_NULL, - EXEC_INPUT_TTY, - EXEC_INPUT_TTY_FORCE, - EXEC_INPUT_TTY_FAIL, - EXEC_INPUT_SOCKET, - _EXEC_INPUT_MAX, - _EXEC_INPUT_INVALID = -1 -} ExecInput; - -typedef enum ExecOutput { - EXEC_OUTPUT_INHERIT, - EXEC_OUTPUT_NULL, - EXEC_OUTPUT_TTY, - EXEC_OUTPUT_SYSLOG, - EXEC_OUTPUT_SYSLOG_AND_CONSOLE, - EXEC_OUTPUT_KMSG, - EXEC_OUTPUT_KMSG_AND_CONSOLE, - EXEC_OUTPUT_JOURNAL, - EXEC_OUTPUT_JOURNAL_AND_CONSOLE, - EXEC_OUTPUT_SOCKET, - _EXEC_OUTPUT_MAX, - _EXEC_OUTPUT_INVALID = -1 -} ExecOutput; - -struct ExecStatus { - dual_timestamp start_timestamp; - dual_timestamp exit_timestamp; - pid_t pid; - int code; /* as in siginfo_t::si_code */ - int status; /* as in sigingo_t::si_status */ -}; - -struct ExecCommand { - char *path; - char **argv; - ExecStatus exec_status; - LIST_FIELDS(ExecCommand, command); /* useful for chaining commands */ - bool ignore; -}; - -struct ExecRuntime { - int n_ref; - - char *tmp_dir; - char *var_tmp_dir; - - int netns_storage_socket[2]; -}; - -struct ExecContext { - char **environment; - char **environment_files; - char **pass_environment; - - struct rlimit *rlimit[_RLIMIT_MAX]; - char *working_directory, *root_directory; - bool working_directory_missing_ok; - bool working_directory_home; - - mode_t umask; - int oom_score_adjust; - int nice; - int ioprio; - int cpu_sched_policy; - int cpu_sched_priority; - - cpu_set_t *cpuset; - unsigned cpuset_ncpus; - - ExecInput std_input; - ExecOutput std_output; - ExecOutput std_error; - - nsec_t timer_slack_nsec; - - bool stdio_as_fds; - - char *tty_path; - - bool tty_reset; - bool tty_vhangup; - bool tty_vt_disallocate; - - bool ignore_sigpipe; - - /* Since resolving these names might might involve socket - * connections and we don't want to deadlock ourselves these - * names are resolved on execution only and in the child - * process. */ - char *user; - char *group; - char **supplementary_groups; - - char *pam_name; - - char *utmp_id; - ExecUtmpMode utmp_mode; - - bool selinux_context_ignore; - char *selinux_context; - - bool apparmor_profile_ignore; - char *apparmor_profile; - - bool smack_process_label_ignore; - char *smack_process_label; - - char **read_write_dirs, **read_only_dirs, **inaccessible_dirs; - unsigned long mount_flags; - - uint64_t capability_bounding_set; - uint64_t capability_ambient_set; - int secure_bits; - - int syslog_priority; - char *syslog_identifier; - bool syslog_level_prefix; - - bool cpu_sched_reset_on_fork; - bool non_blocking; - bool private_tmp; - bool private_network; - bool private_devices; - ProtectSystem protect_system; - ProtectHome protect_home; - - bool no_new_privileges; - - /* This is not exposed to the user but available - * internally. We need it to make sure that whenever we spawn - * /usr/bin/mount it is run in the same process group as us so - * that the autofs logic detects that it belongs to us and we - * don't enter a trigger loop. */ - bool same_pgrp; - - unsigned long personality; - - Set *syscall_filter; - Set *syscall_archs; - int syscall_errno; - bool syscall_whitelist:1; - - Set *address_families; - bool address_families_whitelist:1; - - char **runtime_directory; - mode_t runtime_directory_mode; - - bool oom_score_adjust_set:1; - bool nice_set:1; - bool ioprio_set:1; - bool cpu_sched_set:1; - bool no_new_privileges_set:1; -}; - -#include "cgroup-util.h" -#include "cgroup.h" - -struct ExecParameters { - char **argv; - char **environment; - - int *fds; - char **fd_names; - unsigned n_fds; - - bool apply_permissions:1; - bool apply_chroot:1; - bool apply_tty_stdin:1; - - bool confirm_spawn:1; - bool selinux_context_net:1; - - bool cgroup_delegate:1; - CGroupMask cgroup_supported; - const char *cgroup_path; - - const char *runtime_prefix; - - usec_t watchdog_usec; - - int *idle_pipe; - - int stdin_fd; - int stdout_fd; - int stderr_fd; -}; - -int exec_spawn(Unit *unit, - ExecCommand *command, - const ExecContext *context, - const ExecParameters *exec_params, - ExecRuntime *runtime, - pid_t *ret); - -void exec_command_done(ExecCommand *c); -void exec_command_done_array(ExecCommand *c, unsigned n); - -ExecCommand* exec_command_free_list(ExecCommand *c); -void exec_command_free_array(ExecCommand **c, unsigned n); - -char *exec_command_line(char **argv); - -void exec_command_dump(ExecCommand *c, FILE *f, const char *prefix); -void exec_command_dump_list(ExecCommand *c, FILE *f, const char *prefix); -void exec_command_append_list(ExecCommand **l, ExecCommand *e); -int exec_command_set(ExecCommand *c, const char *path, ...); -int exec_command_append(ExecCommand *c, const char *path, ...); - -void exec_context_init(ExecContext *c); -void exec_context_done(ExecContext *c); -void exec_context_dump(ExecContext *c, FILE* f, const char *prefix); - -int exec_context_destroy_runtime_directory(ExecContext *c, const char *runtime_root); - -int exec_context_load_environment(Unit *unit, const ExecContext *c, char ***l); - -bool exec_context_may_touch_console(ExecContext *c); -bool exec_context_maintains_privileges(ExecContext *c); - -void exec_status_start(ExecStatus *s, pid_t pid); -void exec_status_exit(ExecStatus *s, ExecContext *context, pid_t pid, int code, int status); -void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix); - -int exec_runtime_make(ExecRuntime **rt, ExecContext *c, const char *id); -ExecRuntime *exec_runtime_ref(ExecRuntime *r); -ExecRuntime *exec_runtime_unref(ExecRuntime *r); - -int exec_runtime_serialize(Unit *unit, ExecRuntime *rt, FILE *f, FDSet *fds); -int exec_runtime_deserialize_item(Unit *unit, ExecRuntime **rt, const char *key, const char *value, FDSet *fds); - -void exec_runtime_destroy(ExecRuntime *rt); - -const char* exec_output_to_string(ExecOutput i) _const_; -ExecOutput exec_output_from_string(const char *s) _pure_; - -const char* exec_input_to_string(ExecInput i) _const_; -ExecInput exec_input_from_string(const char *s) _pure_; - -const char* exec_utmp_mode_to_string(ExecUtmpMode i) _const_; -ExecUtmpMode exec_utmp_mode_from_string(const char *s) _pure_; diff --git a/src/core/failure-action.c b/src/core/failure-action.c deleted file mode 100644 index ddae46190f..0000000000 --- a/src/core/failure-action.c +++ /dev/null @@ -1,127 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - Copyright 2012 Michael Olbrich - - 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 - -#include "bus-error.h" -#include "bus-util.h" -#include "failure-action.h" -#include "special.h" -#include "string-table.h" -#include "terminal-util.h" - -static void log_and_status(Manager *m, const char *message) { - log_warning("%s", message); - manager_status_printf(m, STATUS_TYPE_EMERGENCY, - ANSI_HIGHLIGHT_RED " !! " ANSI_NORMAL, - "%s", message); -} - -int failure_action( - Manager *m, - FailureAction action, - const char *reboot_arg) { - - assert(m); - assert(action >= 0); - assert(action < _FAILURE_ACTION_MAX); - - if (action == FAILURE_ACTION_NONE) - return -ECANCELED; - - if (!MANAGER_IS_SYSTEM(m)) { - /* Downgrade all options to simply exiting if we run - * in user mode */ - - log_warning("Exiting as result of failure."); - m->exit_code = MANAGER_EXIT; - return -ECANCELED; - } - - switch (action) { - - case FAILURE_ACTION_REBOOT: - log_and_status(m, "Rebooting as result of failure."); - - (void) update_reboot_parameter_and_warn(reboot_arg); - (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_REBOOT_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL); - - break; - - case FAILURE_ACTION_REBOOT_FORCE: - log_and_status(m, "Forcibly rebooting as result of failure."); - - (void) update_reboot_parameter_and_warn(reboot_arg); - m->exit_code = MANAGER_REBOOT; - - break; - - case FAILURE_ACTION_REBOOT_IMMEDIATE: - log_and_status(m, "Rebooting immediately as result of failure."); - - sync(); - - if (!isempty(reboot_arg)) { - log_info("Rebooting with argument '%s'.", reboot_arg); - syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, reboot_arg); - log_warning_errno(errno, "Failed to reboot with parameter, retrying without: %m"); - } - - log_info("Rebooting."); - reboot(RB_AUTOBOOT); - break; - - case FAILURE_ACTION_POWEROFF: - log_and_status(m, "Powering off as result of failure."); - (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_POWEROFF_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL); - break; - - case FAILURE_ACTION_POWEROFF_FORCE: - log_and_status(m, "Forcibly powering off as result of failure."); - m->exit_code = MANAGER_POWEROFF; - break; - - case FAILURE_ACTION_POWEROFF_IMMEDIATE: - log_and_status(m, "Powering off immediately as result of failure."); - - sync(); - - log_info("Powering off."); - reboot(RB_POWER_OFF); - break; - - default: - assert_not_reached("Unknown failure action"); - } - - return -ECANCELED; -} - -static const char* const failure_action_table[_FAILURE_ACTION_MAX] = { - [FAILURE_ACTION_NONE] = "none", - [FAILURE_ACTION_REBOOT] = "reboot", - [FAILURE_ACTION_REBOOT_FORCE] = "reboot-force", - [FAILURE_ACTION_REBOOT_IMMEDIATE] = "reboot-immediate", - [FAILURE_ACTION_POWEROFF] = "poweroff", - [FAILURE_ACTION_POWEROFF_FORCE] = "poweroff-force", - [FAILURE_ACTION_POWEROFF_IMMEDIATE] = "poweroff-immediate" -}; -DEFINE_STRING_TABLE_LOOKUP(failure_action, FailureAction); diff --git a/src/core/failure-action.h b/src/core/failure-action.h deleted file mode 100644 index 1adac4ad5c..0000000000 --- a/src/core/failure-action.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - Copyright 2012 Michael Olbrich - - 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 . -***/ - -typedef enum FailureAction { - FAILURE_ACTION_NONE, - FAILURE_ACTION_REBOOT, - FAILURE_ACTION_REBOOT_FORCE, - FAILURE_ACTION_REBOOT_IMMEDIATE, - FAILURE_ACTION_POWEROFF, - FAILURE_ACTION_POWEROFF_FORCE, - FAILURE_ACTION_POWEROFF_IMMEDIATE, - _FAILURE_ACTION_MAX, - _FAILURE_ACTION_INVALID = -1 -} FailureAction; - -#include "macro.h" -#include "manager.h" - -int failure_action(Manager *m, FailureAction action, const char *reboot_arg); - -const char* failure_action_to_string(FailureAction i) _const_; -FailureAction failure_action_from_string(const char *s) _pure_; diff --git a/src/core/hostname-setup.c b/src/core/hostname-setup.c deleted file mode 100644 index 68be52856b..0000000000 --- a/src/core/hostname-setup.c +++ /dev/null @@ -1,68 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include - -#include "alloc-util.h" -#include "fileio.h" -#include "hostname-setup.h" -#include "hostname-util.h" -#include "log.h" -#include "macro.h" -#include "string-util.h" -#include "util.h" - -int hostname_setup(void) { - int r; - _cleanup_free_ char *b = NULL; - const char *hn; - bool enoent = false; - - r = read_hostname_config("/etc/hostname", &b); - if (r < 0) { - if (r == -ENOENT) - enoent = true; - else - log_warning_errno(r, "Failed to read configured hostname: %m"); - - hn = NULL; - } else - hn = b; - - if (isempty(hn)) { - /* Don't override the hostname if it is already set - * and not explicitly configured */ - if (hostname_is_set()) - return 0; - - if (enoent) - log_info("No hostname configured."); - - hn = "localhost"; - } - - r = sethostname_idempotent(hn); - if (r < 0) - return log_warning_errno(r, "Failed to set hostname to <%s>: %m", hn); - - log_info("Set hostname to <%s>.", hn); - return 0; -} diff --git a/src/core/hostname-setup.h b/src/core/hostname-setup.h deleted file mode 100644 index 73e8c75c71..0000000000 --- a/src/core/hostname-setup.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -int hostname_setup(void); diff --git a/src/core/ima-setup.c b/src/core/ima-setup.c deleted file mode 100644 index d1b0ce76ef..0000000000 --- a/src/core/ima-setup.c +++ /dev/null @@ -1,80 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright (C) 2012 Roberto Sassu - Politecnico di Torino, Italy - TORSEC group — http://security.polito.it - - 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 - -#include "fd-util.h" -#include "fileio.h" -#include "ima-setup.h" -#include "log.h" -#include "util.h" - -#define IMA_SECFS_DIR "/sys/kernel/security/ima" -#define IMA_SECFS_POLICY IMA_SECFS_DIR "/policy" -#define IMA_POLICY_PATH "/etc/ima/ima-policy" - -int ima_setup(void) { -#ifdef HAVE_IMA - _cleanup_fclose_ FILE *input = NULL; - _cleanup_close_ int imafd = -1; - unsigned lineno = 0; - char line[page_size()]; - - if (access(IMA_SECFS_DIR, F_OK) < 0) { - log_debug("IMA support is disabled in the kernel, ignoring."); - return 0; - } - - input = fopen(IMA_POLICY_PATH, "re"); - if (!input) { - log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno, - "Failed to open the IMA custom policy file "IMA_POLICY_PATH", ignoring: %m"); - return 0; - } - - if (access(IMA_SECFS_POLICY, F_OK) < 0) { - log_warning("Another IMA custom policy has already been loaded, ignoring."); - return 0; - } - - imafd = open(IMA_SECFS_POLICY, O_WRONLY|O_CLOEXEC); - if (imafd < 0) { - log_error_errno(errno, "Failed to open the IMA kernel interface "IMA_SECFS_POLICY", ignoring: %m"); - return 0; - } - - FOREACH_LINE(line, input, - return log_error_errno(errno, "Failed to read the IMA custom policy file "IMA_POLICY_PATH": %m")) { - size_t len; - - len = strlen(line); - lineno++; - - if (len > 0 && write(imafd, line, len) < 0) - return log_error_errno(errno, "Failed to load the IMA custom policy file "IMA_POLICY_PATH"%u: %m", - lineno); - } - - log_info("Successfully loaded the IMA custom policy "IMA_POLICY_PATH"."); -#endif /* HAVE_IMA */ - return 0; -} diff --git a/src/core/ima-setup.h b/src/core/ima-setup.h deleted file mode 100644 index 472b58cb00..0000000000 --- a/src/core/ima-setup.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright (C) 2012 Roberto Sassu - Politecnico di Torino, Italy - TORSEC group — http://security.polito.it - - 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 . -***/ - -int ima_setup(void); diff --git a/src/core/job.c b/src/core/job.c deleted file mode 100644 index 7557874d4d..0000000000 --- a/src/core/job.c +++ /dev/null @@ -1,1259 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 "sd-id128.h" -#include "sd-messages.h" - -#include "alloc-util.h" -#include "async.h" -#include "dbus-job.h" -#include "dbus.h" -#include "escape.h" -#include "job.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "set.h" -#include "special.h" -#include "stdio-util.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "unit.h" -#include "virt.h" - -Job* job_new_raw(Unit *unit) { - Job *j; - - /* used for deserialization */ - - assert(unit); - - j = new0(Job, 1); - if (!j) - return NULL; - - j->manager = unit->manager; - j->unit = unit; - j->type = _JOB_TYPE_INVALID; - - return j; -} - -Job* job_new(Unit *unit, JobType type) { - Job *j; - - assert(type < _JOB_TYPE_MAX); - - j = job_new_raw(unit); - if (!j) - return NULL; - - j->id = j->manager->current_job_id++; - j->type = type; - - /* We don't link it here, that's what job_dependency() is for */ - - return j; -} - -void job_free(Job *j) { - assert(j); - assert(!j->installed); - assert(!j->transaction_prev); - assert(!j->transaction_next); - assert(!j->subject_list); - assert(!j->object_list); - - if (j->in_run_queue) - LIST_REMOVE(run_queue, j->manager->run_queue, j); - - if (j->in_dbus_queue) - LIST_REMOVE(dbus_queue, j->manager->dbus_job_queue, j); - - sd_event_source_unref(j->timer_event_source); - - sd_bus_track_unref(j->clients); - strv_free(j->deserialized_clients); - - free(j); -} - -static void job_set_state(Job *j, JobState state) { - assert(j); - assert(state >= 0); - assert(state < _JOB_STATE_MAX); - - if (j->state == state) - return; - - j->state = state; - - if (!j->installed) - return; - - if (j->state == JOB_RUNNING) - j->unit->manager->n_running_jobs++; - else { - assert(j->state == JOB_WAITING); - assert(j->unit->manager->n_running_jobs > 0); - - j->unit->manager->n_running_jobs--; - - if (j->unit->manager->n_running_jobs <= 0) - j->unit->manager->jobs_in_progress_event_source = sd_event_source_unref(j->unit->manager->jobs_in_progress_event_source); - } -} - -void job_uninstall(Job *j) { - Job **pj; - - assert(j->installed); - - job_set_state(j, JOB_WAITING); - - pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job; - assert(*pj == j); - - /* Detach from next 'bigger' objects */ - - /* daemon-reload should be transparent to job observers */ - if (!MANAGER_IS_RELOADING(j->manager)) - bus_job_send_removed_signal(j); - - *pj = NULL; - - unit_add_to_gc_queue(j->unit); - - hashmap_remove(j->manager->jobs, UINT32_TO_PTR(j->id)); - j->installed = false; -} - -static bool job_type_allows_late_merge(JobType t) { - /* Tells whether it is OK to merge a job of type 't' with an already - * running job. - * Reloads cannot be merged this way. Think of the sequence: - * 1. Reload of a daemon is in progress; the daemon has already loaded - * its config file, but hasn't completed the reload operation yet. - * 2. Edit foo's config file. - * 3. Trigger another reload to have the daemon use the new config. - * Should the second reload job be merged into the first one, the daemon - * would not know about the new config. - * JOB_RESTART jobs on the other hand can be merged, because they get - * patched into JOB_START after stopping the unit. So if we see a - * JOB_RESTART running, it means the unit hasn't stopped yet and at - * this time the merge is still allowed. */ - return t != JOB_RELOAD; -} - -static void job_merge_into_installed(Job *j, Job *other) { - assert(j->installed); - assert(j->unit == other->unit); - - if (j->type != JOB_NOP) - job_type_merge_and_collapse(&j->type, other->type, j->unit); - else - assert(other->type == JOB_NOP); - - j->irreversible = j->irreversible || other->irreversible; - j->ignore_order = j->ignore_order || other->ignore_order; -} - -Job* job_install(Job *j) { - Job **pj; - Job *uj; - - assert(!j->installed); - assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION); - assert(j->state == JOB_WAITING); - - pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job; - uj = *pj; - - if (uj) { - if (job_type_is_conflicting(uj->type, j->type)) - job_finish_and_invalidate(uj, JOB_CANCELED, false, false); - else { - /* not conflicting, i.e. mergeable */ - - if (uj->state == JOB_WAITING || - (job_type_allows_late_merge(j->type) && job_type_is_superset(uj->type, j->type))) { - job_merge_into_installed(uj, j); - log_unit_debug(uj->unit, - "Merged into installed job %s/%s as %u", - uj->unit->id, job_type_to_string(uj->type), (unsigned) uj->id); - return uj; - } else { - /* already running and not safe to merge into */ - /* Patch uj to become a merged job and re-run it. */ - /* XXX It should be safer to queue j to run after uj finishes, but it is - * not currently possible to have more than one installed job per unit. */ - job_merge_into_installed(uj, j); - log_unit_debug(uj->unit, - "Merged into running job, re-running: %s/%s as %u", - uj->unit->id, job_type_to_string(uj->type), (unsigned) uj->id); - - job_set_state(uj, JOB_WAITING); - return uj; - } - } - } - - /* Install the job */ - *pj = j; - j->installed = true; - - j->manager->n_installed_jobs++; - log_unit_debug(j->unit, - "Installed new job %s/%s as %u", - j->unit->id, job_type_to_string(j->type), (unsigned) j->id); - return j; -} - -int job_install_deserialized(Job *j) { - Job **pj; - - assert(!j->installed); - - if (j->type < 0 || j->type >= _JOB_TYPE_MAX_IN_TRANSACTION) { - log_debug("Invalid job type %s in deserialization.", strna(job_type_to_string(j->type))); - return -EINVAL; - } - - pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job; - if (*pj) { - log_unit_debug(j->unit, "Unit already has a job installed. Not installing deserialized job."); - return -EEXIST; - } - - *pj = j; - j->installed = true; - - if (j->state == JOB_RUNNING) - j->unit->manager->n_running_jobs++; - - log_unit_debug(j->unit, - "Reinstalled deserialized job %s/%s as %u", - j->unit->id, job_type_to_string(j->type), (unsigned) j->id); - return 0; -} - -JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts) { - JobDependency *l; - - assert(object); - - /* Adds a new job link, which encodes that the 'subject' job - * needs the 'object' job in some way. If 'subject' is NULL - * this means the 'anchor' job (i.e. the one the user - * explicitly asked for) is the requester. */ - - if (!(l = new0(JobDependency, 1))) - return NULL; - - l->subject = subject; - l->object = object; - l->matters = matters; - l->conflicts = conflicts; - - if (subject) - LIST_PREPEND(subject, subject->subject_list, l); - - LIST_PREPEND(object, object->object_list, l); - - return l; -} - -void job_dependency_free(JobDependency *l) { - assert(l); - - if (l->subject) - LIST_REMOVE(subject, l->subject->subject_list, l); - - LIST_REMOVE(object, l->object->object_list, l); - - free(l); -} - -void job_dump(Job *j, FILE*f, const char *prefix) { - assert(j); - assert(f); - - if (!prefix) - prefix = ""; - - fprintf(f, - "%s-> Job %u:\n" - "%s\tAction: %s -> %s\n" - "%s\tState: %s\n" - "%s\tIrreversible: %s\n", - prefix, j->id, - prefix, j->unit->id, job_type_to_string(j->type), - prefix, job_state_to_string(j->state), - prefix, yes_no(j->irreversible)); -} - -/* - * Merging is commutative, so imagine the matrix as symmetric. We store only - * its lower triangle to avoid duplication. We don't store the main diagonal, - * because A merged with A is simply A. - * - * If the resulting type is collapsed immediately afterwards (to get rid of - * the JOB_RELOAD_OR_START, which lies outside the lookup function's domain), - * the following properties hold: - * - * Merging is associative! A merged with B, and then merged with C is the same - * as A merged with the result of B merged with C. - * - * Mergeability is transitive! If A can be merged with B and B with C then - * A also with C. - * - * Also, if A merged with B cannot be merged with C, then either A or B cannot - * be merged with C either. - */ -static const JobType job_merging_table[] = { -/* What \ With * JOB_START JOB_VERIFY_ACTIVE JOB_STOP JOB_RELOAD */ -/*********************************************************************************/ -/*JOB_START */ -/*JOB_VERIFY_ACTIVE */ JOB_START, -/*JOB_STOP */ -1, -1, -/*JOB_RELOAD */ JOB_RELOAD_OR_START, JOB_RELOAD, -1, -/*JOB_RESTART */ JOB_RESTART, JOB_RESTART, -1, JOB_RESTART, -}; - -JobType job_type_lookup_merge(JobType a, JobType b) { - assert_cc(ELEMENTSOF(job_merging_table) == _JOB_TYPE_MAX_MERGING * (_JOB_TYPE_MAX_MERGING - 1) / 2); - assert(a >= 0 && a < _JOB_TYPE_MAX_MERGING); - assert(b >= 0 && b < _JOB_TYPE_MAX_MERGING); - - if (a == b) - return a; - - if (a < b) { - JobType tmp = a; - a = b; - b = tmp; - } - - return job_merging_table[(a - 1) * a / 2 + b]; -} - -bool job_type_is_redundant(JobType a, UnitActiveState b) { - switch (a) { - - case JOB_START: - return - b == UNIT_ACTIVE || - b == UNIT_RELOADING; - - case JOB_STOP: - return - b == UNIT_INACTIVE || - b == UNIT_FAILED; - - case JOB_VERIFY_ACTIVE: - return - b == UNIT_ACTIVE || - b == UNIT_RELOADING; - - case JOB_RELOAD: - return - b == UNIT_RELOADING; - - case JOB_RESTART: - return - b == UNIT_ACTIVATING; - - case JOB_NOP: - return true; - - default: - assert_not_reached("Invalid job type"); - } -} - -JobType job_type_collapse(JobType t, Unit *u) { - UnitActiveState s; - - switch (t) { - - case JOB_TRY_RESTART: - s = unit_active_state(u); - if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s)) - return JOB_NOP; - - return JOB_RESTART; - - case JOB_TRY_RELOAD: - s = unit_active_state(u); - if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s)) - return JOB_NOP; - - return JOB_RELOAD; - - case JOB_RELOAD_OR_START: - s = unit_active_state(u); - if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s)) - return JOB_START; - - return JOB_RELOAD; - - default: - return t; - } -} - -int job_type_merge_and_collapse(JobType *a, JobType b, Unit *u) { - JobType t; - - t = job_type_lookup_merge(*a, b); - if (t < 0) - return -EEXIST; - - *a = job_type_collapse(t, u); - return 0; -} - -static bool job_is_runnable(Job *j) { - Iterator i; - Unit *other; - - assert(j); - assert(j->installed); - - /* Checks whether there is any job running for the units this - * job needs to be running after (in the case of a 'positive' - * job type) or before (in the case of a 'negative' job - * type. */ - - /* Note that unit types have a say in what is runnable, - * too. For example, if they return -EAGAIN from - * unit_start() they can indicate they are not - * runnable yet. */ - - /* First check if there is an override */ - if (j->ignore_order) - return true; - - if (j->type == JOB_NOP) - return true; - - if (j->type == JOB_START || - j->type == JOB_VERIFY_ACTIVE || - j->type == JOB_RELOAD) { - - /* Immediate result is that the job is or might be - * started. In this case let's wait for the - * dependencies, regardless whether they are - * starting or stopping something. */ - - SET_FOREACH(other, j->unit->dependencies[UNIT_AFTER], i) - if (other->job) - return false; - } - - /* Also, if something else is being stopped and we should - * change state after it, then let's wait. */ - - SET_FOREACH(other, j->unit->dependencies[UNIT_BEFORE], i) - if (other->job && - (other->job->type == JOB_STOP || - other->job->type == JOB_RESTART)) - return false; - - /* This means that for a service a and a service b where b - * shall be started after a: - * - * start a + start b → 1st step start a, 2nd step start b - * start a + stop b → 1st step stop b, 2nd step start a - * stop a + start b → 1st step stop a, 2nd step start b - * stop a + stop b → 1st step stop b, 2nd step stop a - * - * This has the side effect that restarts are properly - * synchronized too. */ - - return true; -} - -static void job_change_type(Job *j, JobType newtype) { - assert(j); - - log_unit_debug(j->unit, - "Converting job %s/%s -> %s/%s", - j->unit->id, job_type_to_string(j->type), - j->unit->id, job_type_to_string(newtype)); - - j->type = newtype; -} - -static int job_perform_on_unit(Job **j) { - uint32_t id; - Manager *m; - JobType t; - Unit *u; - int r; - - /* While we execute this operation the job might go away (for - * example: because it finishes immediately or is replaced by - * a new, conflicting job.) To make sure we don't access a - * freed job later on we store the id here, so that we can - * verify the job is still valid. */ - - assert(j); - assert(*j); - - m = (*j)->manager; - u = (*j)->unit; - t = (*j)->type; - id = (*j)->id; - - switch (t) { - case JOB_START: - r = unit_start(u); - break; - - case JOB_RESTART: - t = JOB_STOP; - /* fall through */ - case JOB_STOP: - r = unit_stop(u); - break; - - case JOB_RELOAD: - r = unit_reload(u); - break; - - default: - assert_not_reached("Invalid job type"); - } - - /* Log if the job still exists and the start/stop/reload function - * actually did something. */ - *j = manager_get_job(m, id); - if (*j && r > 0) - unit_status_emit_starting_stopping_reloading(u, t); - - return r; -} - -int job_run_and_invalidate(Job *j) { - int r; - - assert(j); - assert(j->installed); - assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION); - assert(j->in_run_queue); - - LIST_REMOVE(run_queue, j->manager->run_queue, j); - j->in_run_queue = false; - - if (j->state != JOB_WAITING) - return 0; - - if (!job_is_runnable(j)) - return -EAGAIN; - - job_set_state(j, JOB_RUNNING); - job_add_to_dbus_queue(j); - - - switch (j->type) { - - case JOB_VERIFY_ACTIVE: { - UnitActiveState t = unit_active_state(j->unit); - if (UNIT_IS_ACTIVE_OR_RELOADING(t)) - r = -EALREADY; - else if (t == UNIT_ACTIVATING) - r = -EAGAIN; - else - r = -EBADR; - break; - } - - case JOB_START: - case JOB_STOP: - case JOB_RESTART: - r = job_perform_on_unit(&j); - - /* If the unit type does not support starting/stopping, - * then simply wait. */ - if (r == -EBADR) - r = 0; - break; - - case JOB_RELOAD: - r = job_perform_on_unit(&j); - break; - - case JOB_NOP: - r = -EALREADY; - break; - - default: - assert_not_reached("Unknown job type"); - } - - if (j) { - if (r == -EALREADY) - r = job_finish_and_invalidate(j, JOB_DONE, true, true); - else if (r == -EBADR) - r = job_finish_and_invalidate(j, JOB_SKIPPED, true, false); - else if (r == -ENOEXEC) - r = job_finish_and_invalidate(j, JOB_INVALID, true, false); - else if (r == -EPROTO) - r = job_finish_and_invalidate(j, JOB_ASSERT, true, false); - else if (r == -EOPNOTSUPP) - r = job_finish_and_invalidate(j, JOB_UNSUPPORTED, true, false); - else if (r == -EAGAIN) - job_set_state(j, JOB_WAITING); - else if (r < 0) - r = job_finish_and_invalidate(j, JOB_FAILED, true, false); - } - - return r; -} - -_pure_ static const char *job_get_status_message_format(Unit *u, JobType t, JobResult result) { - - static const char *const generic_finished_start_job[_JOB_RESULT_MAX] = { - [JOB_DONE] = "Started %s.", - [JOB_TIMEOUT] = "Timed out starting %s.", - [JOB_FAILED] = "Failed to start %s.", - [JOB_DEPENDENCY] = "Dependency failed for %s.", - [JOB_ASSERT] = "Assertion failed for %s.", - [JOB_UNSUPPORTED] = "Starting of %s not supported.", - }; - static const char *const generic_finished_stop_job[_JOB_RESULT_MAX] = { - [JOB_DONE] = "Stopped %s.", - [JOB_FAILED] = "Stopped (with error) %s.", - [JOB_TIMEOUT] = "Timed out stopping %s.", - }; - static const char *const generic_finished_reload_job[_JOB_RESULT_MAX] = { - [JOB_DONE] = "Reloaded %s.", - [JOB_FAILED] = "Reload failed for %s.", - [JOB_TIMEOUT] = "Timed out reloading %s.", - }; - /* When verify-active detects the unit is inactive, report it. - * Most likely a DEPEND warning from a requisiting unit will - * occur next and it's nice to see what was requisited. */ - static const char *const generic_finished_verify_active_job[_JOB_RESULT_MAX] = { - [JOB_SKIPPED] = "%s is not active.", - }; - - const UnitStatusMessageFormats *format_table; - const char *format; - - assert(u); - assert(t >= 0); - assert(t < _JOB_TYPE_MAX); - - if (IN_SET(t, JOB_START, JOB_STOP, JOB_RESTART)) { - format_table = &UNIT_VTABLE(u)->status_message_formats; - if (format_table) { - format = t == JOB_START ? format_table->finished_start_job[result] : - format_table->finished_stop_job[result]; - if (format) - return format; - } - } - - /* Return generic strings */ - if (t == JOB_START) - return generic_finished_start_job[result]; - else if (t == JOB_STOP || t == JOB_RESTART) - return generic_finished_stop_job[result]; - else if (t == JOB_RELOAD) - return generic_finished_reload_job[result]; - else if (t == JOB_VERIFY_ACTIVE) - return generic_finished_verify_active_job[result]; - - return NULL; -} - -static void job_print_status_message(Unit *u, JobType t, JobResult result) { - static struct { - const char *color, *word; - } const statuses[_JOB_RESULT_MAX] = { - [JOB_DONE] = {ANSI_GREEN, " OK "}, - [JOB_TIMEOUT] = {ANSI_HIGHLIGHT_RED, " TIME "}, - [JOB_FAILED] = {ANSI_HIGHLIGHT_RED, "FAILED"}, - [JOB_DEPENDENCY] = {ANSI_HIGHLIGHT_YELLOW, "DEPEND"}, - [JOB_SKIPPED] = {ANSI_HIGHLIGHT, " INFO "}, - [JOB_ASSERT] = {ANSI_HIGHLIGHT_YELLOW, "ASSERT"}, - [JOB_UNSUPPORTED] = {ANSI_HIGHLIGHT_YELLOW, "UNSUPP"}, - }; - - const char *format; - const char *status; - - assert(u); - assert(t >= 0); - assert(t < _JOB_TYPE_MAX); - - /* Reload status messages have traditionally not been printed to console. */ - if (t == JOB_RELOAD) - return; - - format = job_get_status_message_format(u, t, result); - if (!format) - return; - - if (log_get_show_color()) - status = strjoina(statuses[result].color, statuses[result].word, ANSI_NORMAL); - else - status = statuses[result].word; - - if (result != JOB_DONE) - manager_flip_auto_status(u->manager, true); - - DISABLE_WARNING_FORMAT_NONLITERAL; - unit_status_printf(u, status, format); - REENABLE_WARNING; - - if (t == JOB_START && result == JOB_FAILED) { - _cleanup_free_ char *quoted; - - quoted = shell_maybe_quote(u->id); - manager_status_printf(u->manager, STATUS_TYPE_NORMAL, NULL, "See 'systemctl status %s' for details.", strna(quoted)); - } -} - -static void job_log_status_message(Unit *u, JobType t, JobResult result) { - const char *format; - char buf[LINE_MAX]; - sd_id128_t mid; - static const int job_result_log_level[_JOB_RESULT_MAX] = { - [JOB_DONE] = LOG_INFO, - [JOB_CANCELED] = LOG_INFO, - [JOB_TIMEOUT] = LOG_ERR, - [JOB_FAILED] = LOG_ERR, - [JOB_DEPENDENCY] = LOG_WARNING, - [JOB_SKIPPED] = LOG_NOTICE, - [JOB_INVALID] = LOG_INFO, - [JOB_ASSERT] = LOG_WARNING, - [JOB_UNSUPPORTED] = LOG_WARNING, - }; - - assert(u); - assert(t >= 0); - assert(t < _JOB_TYPE_MAX); - - /* Skip this if it goes to the console. since we already print - * to the console anyway... */ - - if (log_on_console()) - return; - - format = job_get_status_message_format(u, t, result); - if (!format) - return; - - DISABLE_WARNING_FORMAT_NONLITERAL; - xsprintf(buf, format, unit_description(u)); - REENABLE_WARNING; - - switch (t) { - - case JOB_START: - mid = result == JOB_DONE ? SD_MESSAGE_UNIT_STARTED : SD_MESSAGE_UNIT_FAILED; - break; - - case JOB_RELOAD: - mid = SD_MESSAGE_UNIT_RELOADED; - break; - - case JOB_STOP: - case JOB_RESTART: - mid = SD_MESSAGE_UNIT_STOPPED; - break; - - default: - log_struct(job_result_log_level[result], - LOG_UNIT_ID(u), - LOG_MESSAGE("%s", buf), - "RESULT=%s", job_result_to_string(result), - NULL); - return; - } - - log_struct(job_result_log_level[result], - LOG_MESSAGE_ID(mid), - LOG_UNIT_ID(u), - LOG_MESSAGE("%s", buf), - "RESULT=%s", job_result_to_string(result), - NULL); -} - -static void job_emit_status_message(Unit *u, JobType t, JobResult result) { - - /* No message if the job did not actually do anything due to failed condition. */ - if (t == JOB_START && result == JOB_DONE && !u->condition_result) - return; - - job_log_status_message(u, t, result); - job_print_status_message(u, t, result); -} - -static void job_fail_dependencies(Unit *u, UnitDependency d) { - Unit *other; - Iterator i; - - assert(u); - - SET_FOREACH(other, u->dependencies[d], i) { - Job *j = other->job; - - if (!j) - continue; - if (!IN_SET(j->type, JOB_START, JOB_VERIFY_ACTIVE)) - continue; - - job_finish_and_invalidate(j, JOB_DEPENDENCY, true, false); - } -} - -int job_finish_and_invalidate(Job *j, JobResult result, bool recursive, bool already) { - Unit *u; - Unit *other; - JobType t; - Iterator i; - - assert(j); - assert(j->installed); - assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION); - - u = j->unit; - t = j->type; - - j->result = result; - - log_unit_debug(u, "Job %s/%s finished, result=%s", u->id, job_type_to_string(t), job_result_to_string(result)); - - /* If this job did nothing to respective unit we don't log the status message */ - if (!already) - job_emit_status_message(u, t, result); - - job_add_to_dbus_queue(j); - - /* Patch restart jobs so that they become normal start jobs */ - if (result == JOB_DONE && t == JOB_RESTART) { - - job_change_type(j, JOB_START); - job_set_state(j, JOB_WAITING); - - job_add_to_run_queue(j); - - goto finish; - } - - if (result == JOB_FAILED || result == JOB_INVALID) - j->manager->n_failed_jobs++; - - job_uninstall(j); - job_free(j); - - /* Fail depending jobs on failure */ - if (result != JOB_DONE && recursive) { - if (IN_SET(t, JOB_START, JOB_VERIFY_ACTIVE)) { - job_fail_dependencies(u, UNIT_REQUIRED_BY); - job_fail_dependencies(u, UNIT_REQUISITE_OF); - job_fail_dependencies(u, UNIT_BOUND_BY); - } else if (t == JOB_STOP) - job_fail_dependencies(u, UNIT_CONFLICTED_BY); - } - - /* Trigger OnFailure dependencies that are not generated by - * the unit itself. We don't treat JOB_CANCELED as failure in - * this context. And JOB_FAILURE is already handled by the - * unit itself. */ - if (result == JOB_TIMEOUT || result == JOB_DEPENDENCY) { - log_struct(LOG_NOTICE, - "JOB_TYPE=%s", job_type_to_string(t), - "JOB_RESULT=%s", job_result_to_string(result), - LOG_UNIT_ID(u), - LOG_UNIT_MESSAGE(u, "Job %s/%s failed with result '%s'.", - u->id, - job_type_to_string(t), - job_result_to_string(result)), - NULL); - - unit_start_on_failure(u); - } - - unit_trigger_notify(u); - -finish: - /* Try to start the next jobs that can be started */ - SET_FOREACH(other, u->dependencies[UNIT_AFTER], i) - if (other->job) - job_add_to_run_queue(other->job); - SET_FOREACH(other, u->dependencies[UNIT_BEFORE], i) - if (other->job) - job_add_to_run_queue(other->job); - - manager_check_finished(u->manager); - - return 0; -} - -static int job_dispatch_timer(sd_event_source *s, uint64_t monotonic, void *userdata) { - Job *j = userdata; - Unit *u; - - assert(j); - assert(s == j->timer_event_source); - - log_unit_warning(j->unit, "Job %s/%s timed out.", j->unit->id, job_type_to_string(j->type)); - - u = j->unit; - job_finish_and_invalidate(j, JOB_TIMEOUT, true, false); - - failure_action(u->manager, u->job_timeout_action, u->job_timeout_reboot_arg); - - return 0; -} - -int job_start_timer(Job *j) { - int r; - - if (j->timer_event_source) - return 0; - - j->begin_usec = now(CLOCK_MONOTONIC); - - if (j->unit->job_timeout == USEC_INFINITY) - return 0; - - r = sd_event_add_time( - j->manager->event, - &j->timer_event_source, - CLOCK_MONOTONIC, - usec_add(j->begin_usec, j->unit->job_timeout), 0, - job_dispatch_timer, j); - if (r < 0) - return r; - - (void) sd_event_source_set_description(j->timer_event_source, "job-start"); - - return 0; -} - -void job_add_to_run_queue(Job *j) { - assert(j); - assert(j->installed); - - if (j->in_run_queue) - return; - - if (!j->manager->run_queue) - sd_event_source_set_enabled(j->manager->run_queue_event_source, SD_EVENT_ONESHOT); - - LIST_PREPEND(run_queue, j->manager->run_queue, j); - j->in_run_queue = true; -} - -void job_add_to_dbus_queue(Job *j) { - assert(j); - assert(j->installed); - - if (j->in_dbus_queue) - return; - - /* We don't check if anybody is subscribed here, since this - * job might just have been created and not yet assigned to a - * connection/client. */ - - LIST_PREPEND(dbus_queue, j->manager->dbus_job_queue, j); - j->in_dbus_queue = true; -} - -char *job_dbus_path(Job *j) { - char *p; - - assert(j); - - if (asprintf(&p, "/org/freedesktop/systemd1/job/%"PRIu32, j->id) < 0) - return NULL; - - return p; -} - -int job_serialize(Job *j, FILE *f, FDSet *fds) { - fprintf(f, "job-id=%u\n", j->id); - fprintf(f, "job-type=%s\n", job_type_to_string(j->type)); - fprintf(f, "job-state=%s\n", job_state_to_string(j->state)); - fprintf(f, "job-irreversible=%s\n", yes_no(j->irreversible)); - fprintf(f, "job-sent-dbus-new-signal=%s\n", yes_no(j->sent_dbus_new_signal)); - fprintf(f, "job-ignore-order=%s\n", yes_no(j->ignore_order)); - - if (j->begin_usec > 0) - fprintf(f, "job-begin="USEC_FMT"\n", j->begin_usec); - - bus_track_serialize(j->clients, f); - - /* End marker */ - fputc('\n', f); - return 0; -} - -int job_deserialize(Job *j, FILE *f, FDSet *fds) { - assert(j); - - for (;;) { - char line[LINE_MAX], *l, *v; - size_t k; - - if (!fgets(line, sizeof(line), f)) { - if (feof(f)) - return 0; - return -errno; - } - - char_array_0(line); - l = strstrip(line); - - /* End marker */ - if (l[0] == 0) - return 0; - - k = strcspn(l, "="); - - if (l[k] == '=') { - l[k] = 0; - v = l+k+1; - } else - v = l+k; - - if (streq(l, "job-id")) { - - if (safe_atou32(v, &j->id) < 0) - log_debug("Failed to parse job id value %s", v); - - } else if (streq(l, "job-type")) { - JobType t; - - t = job_type_from_string(v); - if (t < 0) - log_debug("Failed to parse job type %s", v); - else if (t >= _JOB_TYPE_MAX_IN_TRANSACTION) - log_debug("Cannot deserialize job of type %s", v); - else - j->type = t; - - } else if (streq(l, "job-state")) { - JobState s; - - s = job_state_from_string(v); - if (s < 0) - log_debug("Failed to parse job state %s", v); - else - job_set_state(j, s); - - } else if (streq(l, "job-irreversible")) { - int b; - - b = parse_boolean(v); - if (b < 0) - log_debug("Failed to parse job irreversible flag %s", v); - else - j->irreversible = j->irreversible || b; - - } else if (streq(l, "job-sent-dbus-new-signal")) { - int b; - - b = parse_boolean(v); - if (b < 0) - log_debug("Failed to parse job sent_dbus_new_signal flag %s", v); - else - j->sent_dbus_new_signal = j->sent_dbus_new_signal || b; - - } else if (streq(l, "job-ignore-order")) { - int b; - - b = parse_boolean(v); - if (b < 0) - log_debug("Failed to parse job ignore_order flag %s", v); - else - j->ignore_order = j->ignore_order || b; - - } else if (streq(l, "job-begin")) { - unsigned long long ull; - - if (sscanf(v, "%llu", &ull) != 1) - log_debug("Failed to parse job-begin value %s", v); - else - j->begin_usec = ull; - - } else if (streq(l, "subscribed")) { - - if (strv_extend(&j->deserialized_clients, v) < 0) - return log_oom(); - } - } -} - -int job_coldplug(Job *j) { - int r; - - assert(j); - - /* After deserialization is complete and the bus connection - * set up again, let's start watching our subscribers again */ - r = bus_track_coldplug(j->manager, &j->clients, &j->deserialized_clients); - if (r < 0) - return r; - - if (j->state == JOB_WAITING) - job_add_to_run_queue(j); - - if (j->begin_usec == 0 || j->unit->job_timeout == USEC_INFINITY) - return 0; - - j->timer_event_source = sd_event_source_unref(j->timer_event_source); - - r = sd_event_add_time( - j->manager->event, - &j->timer_event_source, - CLOCK_MONOTONIC, - usec_add(j->begin_usec, j->unit->job_timeout), 0, - job_dispatch_timer, j); - if (r < 0) - log_debug_errno(r, "Failed to restart timeout for job: %m"); - - (void) sd_event_source_set_description(j->timer_event_source, "job-timeout"); - - return r; -} - -void job_shutdown_magic(Job *j) { - assert(j); - - /* The shutdown target gets some special treatment here: we - * tell the kernel to begin with flushing its disk caches, to - * optimize shutdown time a bit. Ideally we wouldn't hardcode - * this magic into PID 1. However all other processes aren't - * options either since they'd exit much sooner than PID 1 and - * asynchronous sync() would cause their exit to be - * delayed. */ - - if (j->type != JOB_START) - return; - - if (!MANAGER_IS_SYSTEM(j->unit->manager)) - return; - - if (!unit_has_name(j->unit, SPECIAL_SHUTDOWN_TARGET)) - return; - - /* In case messages on console has been disabled on boot */ - j->unit->manager->no_console_output = false; - - if (detect_container() > 0) - return; - - asynchronous_sync(); -} - -int job_get_timeout(Job *j, usec_t *timeout) { - usec_t x = USEC_INFINITY, y = USEC_INFINITY; - Unit *u = j->unit; - int r; - - assert(u); - - if (j->timer_event_source) { - r = sd_event_source_get_time(j->timer_event_source, &x); - if (r < 0) - return r; - } - - if (UNIT_VTABLE(u)->get_timeout) { - r = UNIT_VTABLE(u)->get_timeout(u, &y); - if (r < 0) - return r; - } - - if (x == USEC_INFINITY && y == USEC_INFINITY) - return 0; - - *timeout = MIN(x, y); - return 1; -} - -static const char* const job_state_table[_JOB_STATE_MAX] = { - [JOB_WAITING] = "waiting", - [JOB_RUNNING] = "running" -}; - -DEFINE_STRING_TABLE_LOOKUP(job_state, JobState); - -static const char* const job_type_table[_JOB_TYPE_MAX] = { - [JOB_START] = "start", - [JOB_VERIFY_ACTIVE] = "verify-active", - [JOB_STOP] = "stop", - [JOB_RELOAD] = "reload", - [JOB_RELOAD_OR_START] = "reload-or-start", - [JOB_RESTART] = "restart", - [JOB_TRY_RESTART] = "try-restart", - [JOB_TRY_RELOAD] = "try-reload", - [JOB_NOP] = "nop", -}; - -DEFINE_STRING_TABLE_LOOKUP(job_type, JobType); - -static const char* const job_mode_table[_JOB_MODE_MAX] = { - [JOB_FAIL] = "fail", - [JOB_REPLACE] = "replace", - [JOB_REPLACE_IRREVERSIBLY] = "replace-irreversibly", - [JOB_ISOLATE] = "isolate", - [JOB_FLUSH] = "flush", - [JOB_IGNORE_DEPENDENCIES] = "ignore-dependencies", - [JOB_IGNORE_REQUIREMENTS] = "ignore-requirements", -}; - -DEFINE_STRING_TABLE_LOOKUP(job_mode, JobMode); - -static const char* const job_result_table[_JOB_RESULT_MAX] = { - [JOB_DONE] = "done", - [JOB_CANCELED] = "canceled", - [JOB_TIMEOUT] = "timeout", - [JOB_FAILED] = "failed", - [JOB_DEPENDENCY] = "dependency", - [JOB_SKIPPED] = "skipped", - [JOB_INVALID] = "invalid", - [JOB_ASSERT] = "assert", - [JOB_UNSUPPORTED] = "unsupported", -}; - -DEFINE_STRING_TABLE_LOOKUP(job_result, JobResult); - -const char* job_type_to_access_method(JobType t) { - assert(t >= 0); - assert(t < _JOB_TYPE_MAX); - - if (IN_SET(t, JOB_START, JOB_RESTART, JOB_TRY_RESTART)) - return "start"; - else if (t == JOB_STOP) - return "stop"; - else - return "reload"; -} diff --git a/src/core/job.h b/src/core/job.h deleted file mode 100644 index d359e8bb3e..0000000000 --- a/src/core/job.h +++ /dev/null @@ -1,242 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "sd-event.h" - -#include "list.h" -#include "unit-name.h" - -typedef struct Job Job; -typedef struct JobDependency JobDependency; -typedef enum JobType JobType; -typedef enum JobState JobState; -typedef enum JobMode JobMode; -typedef enum JobResult JobResult; - -/* Be careful when changing the job types! Adjust job_merging_table[] accordingly! */ -enum JobType { - JOB_START, /* if a unit does not support being started, we'll just wait until it becomes active */ - JOB_VERIFY_ACTIVE, - - JOB_STOP, - - JOB_RELOAD, /* if running, reload */ - - /* Note that restarts are first treated like JOB_STOP, but - * then instead of finishing are patched to become - * JOB_START. */ - JOB_RESTART, /* If running, stop. Then start unconditionally. */ - - _JOB_TYPE_MAX_MERGING, - - /* JOB_NOP can enter into a transaction, but as it won't pull in - * any dependencies and it uses the special 'nop_job' slot in Unit, - * it won't have to merge with anything (except possibly into another - * JOB_NOP, previously installed). JOB_NOP is special-cased in - * job_type_is_*() functions so that the transaction can be - * activated. */ - JOB_NOP = _JOB_TYPE_MAX_MERGING, /* do nothing */ - - _JOB_TYPE_MAX_IN_TRANSACTION, - - /* JOB_TRY_RESTART can never appear in a transaction, because - * it always collapses into JOB_RESTART or JOB_NOP before entering. - * Thus we never need to merge it with anything. */ - JOB_TRY_RESTART = _JOB_TYPE_MAX_IN_TRANSACTION, /* if running, stop and then start */ - - /* Similar to JOB_TRY_RESTART but collapses to JOB_RELOAD or JOB_NOP */ - JOB_TRY_RELOAD, - - /* JOB_RELOAD_OR_START won't enter into a transaction and cannot result - * from transaction merging (there's no way for JOB_RELOAD and - * JOB_START to meet in one transaction). It can result from a merge - * during job installation, but then it will immediately collapse into - * one of the two simpler types. */ - JOB_RELOAD_OR_START, /* if running, reload, otherwise start */ - - _JOB_TYPE_MAX, - _JOB_TYPE_INVALID = -1 -}; - -enum JobState { - JOB_WAITING, - JOB_RUNNING, - _JOB_STATE_MAX, - _JOB_STATE_INVALID = -1 -}; - -enum JobMode { - JOB_FAIL, /* Fail if a conflicting job is already queued */ - JOB_REPLACE, /* Replace an existing conflicting job */ - JOB_REPLACE_IRREVERSIBLY,/* Like JOB_REPLACE + produce irreversible jobs */ - JOB_ISOLATE, /* Start a unit, and stop all others */ - JOB_FLUSH, /* Flush out all other queued jobs when queing this one */ - JOB_IGNORE_DEPENDENCIES, /* Ignore both requirement and ordering dependencies */ - JOB_IGNORE_REQUIREMENTS, /* Ignore requirement dependencies */ - _JOB_MODE_MAX, - _JOB_MODE_INVALID = -1 -}; - -enum JobResult { - JOB_DONE, /* Job completed successfully */ - JOB_CANCELED, /* Job canceled by a conflicting job installation or by explicit cancel request */ - JOB_TIMEOUT, /* Job timeout elapsed */ - JOB_FAILED, /* Job failed */ - JOB_DEPENDENCY, /* A required dependency job did not result in JOB_DONE */ - JOB_SKIPPED, /* Negative result of JOB_VERIFY_ACTIVE */ - JOB_INVALID, /* JOB_RELOAD of inactive unit */ - JOB_ASSERT, /* Couldn't start a unit, because an assert didn't hold */ - JOB_UNSUPPORTED, /* Couldn't start a unit, because the unit type is not supported on the system */ - _JOB_RESULT_MAX, - _JOB_RESULT_INVALID = -1 -}; - -#include "unit.h" - -struct JobDependency { - /* Encodes that the 'subject' job needs the 'object' job in - * some way. This structure is used only while building a transaction. */ - Job *subject; - Job *object; - - LIST_FIELDS(JobDependency, subject); - LIST_FIELDS(JobDependency, object); - - bool matters; - bool conflicts; -}; - -struct Job { - Manager *manager; - Unit *unit; - - LIST_FIELDS(Job, transaction); - LIST_FIELDS(Job, run_queue); - LIST_FIELDS(Job, dbus_queue); - - LIST_HEAD(JobDependency, subject_list); - LIST_HEAD(JobDependency, object_list); - - /* Used for graph algs as a "I have been here" marker */ - Job* marker; - unsigned generation; - - uint32_t id; - - JobType type; - JobState state; - - sd_event_source *timer_event_source; - usec_t begin_usec; - - /* - * This tracks where to send signals, and also which clients - * are allowed to call DBus methods on the job (other than - * root). - * - * There can be more than one client, because of job merging. - */ - sd_bus_track *clients; - char **deserialized_clients; - - JobResult result; - - bool installed:1; - bool in_run_queue:1; - bool matters_to_anchor:1; - bool in_dbus_queue:1; - bool sent_dbus_new_signal:1; - bool ignore_order:1; - bool irreversible:1; -}; - -Job* job_new(Unit *unit, JobType type); -Job* job_new_raw(Unit *unit); -void job_free(Job *job); -Job* job_install(Job *j); -int job_install_deserialized(Job *j); -void job_uninstall(Job *j); -void job_dump(Job *j, FILE*f, const char *prefix); -int job_serialize(Job *j, FILE *f, FDSet *fds); -int job_deserialize(Job *j, FILE *f, FDSet *fds); -int job_coldplug(Job *j); - -JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts); -void job_dependency_free(JobDependency *l); - -int job_merge(Job *j, Job *other); - -JobType job_type_lookup_merge(JobType a, JobType b) _pure_; - -_pure_ static inline bool job_type_is_mergeable(JobType a, JobType b) { - return job_type_lookup_merge(a, b) >= 0; -} - -_pure_ static inline bool job_type_is_conflicting(JobType a, JobType b) { - return a != JOB_NOP && b != JOB_NOP && !job_type_is_mergeable(a, b); -} - -_pure_ static inline bool job_type_is_superset(JobType a, JobType b) { - /* Checks whether operation a is a "superset" of b in its actions */ - if (b == JOB_NOP) - return true; - if (a == JOB_NOP) - return false; - return a == job_type_lookup_merge(a, b); -} - -bool job_type_is_redundant(JobType a, UnitActiveState b) _pure_; - -/* Collapses a state-dependent job type into a simpler type by observing - * the state of the unit which it is going to be applied to. */ -JobType job_type_collapse(JobType t, Unit *u); - -int job_type_merge_and_collapse(JobType *a, JobType b, Unit *u); - -void job_add_to_run_queue(Job *j); -void job_add_to_dbus_queue(Job *j); - -int job_start_timer(Job *j); - -int job_run_and_invalidate(Job *j); -int job_finish_and_invalidate(Job *j, JobResult result, bool recursive, bool already); - -char *job_dbus_path(Job *j); - -void job_shutdown_magic(Job *j); - -int job_get_timeout(Job *j, usec_t *timeout) _pure_; - -const char* job_type_to_string(JobType t) _const_; -JobType job_type_from_string(const char *s) _pure_; - -const char* job_state_to_string(JobState t) _const_; -JobState job_state_from_string(const char *s) _pure_; - -const char* job_mode_to_string(JobMode t) _const_; -JobMode job_mode_from_string(const char *s) _pure_; - -const char* job_result_to_string(JobResult t) _const_; -JobResult job_result_from_string(const char *s) _pure_; - -const char* job_type_to_access_method(JobType t); diff --git a/src/core/kill.c b/src/core/kill.c deleted file mode 100644 index 6854587d54..0000000000 --- a/src/core/kill.c +++ /dev/null @@ -1,68 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 "kill.h" -#include "signal-util.h" -#include "string-table.h" -#include "util.h" - -void kill_context_init(KillContext *c) { - assert(c); - - c->kill_signal = SIGTERM; - c->send_sigkill = true; - c->send_sighup = false; -} - -void kill_context_dump(KillContext *c, FILE *f, const char *prefix) { - assert(c); - - if (!prefix) - prefix = ""; - - fprintf(f, - "%sKillMode: %s\n" - "%sKillSignal: SIG%s\n" - "%sSendSIGKILL: %s\n" - "%sSendSIGHUP: %s\n", - prefix, kill_mode_to_string(c->kill_mode), - prefix, signal_to_string(c->kill_signal), - prefix, yes_no(c->send_sigkill), - prefix, yes_no(c->send_sighup)); -} - -static const char* const kill_mode_table[_KILL_MODE_MAX] = { - [KILL_CONTROL_GROUP] = "control-group", - [KILL_PROCESS] = "process", - [KILL_MIXED] = "mixed", - [KILL_NONE] = "none" -}; - -DEFINE_STRING_TABLE_LOOKUP(kill_mode, KillMode); - -static const char* const kill_who_table[_KILL_WHO_MAX] = { - [KILL_MAIN] = "main", - [KILL_CONTROL] = "control", - [KILL_ALL] = "all", - [KILL_MAIN_FAIL] = "main-fail", - [KILL_CONTROL_FAIL] = "control-fail", - [KILL_ALL_FAIL] = "all-fail" -}; - -DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho); diff --git a/src/core/kill.h b/src/core/kill.h deleted file mode 100644 index b3d2056cb0..0000000000 --- a/src/core/kill.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -typedef struct KillContext KillContext; - -#include -#include - -#include "macro.h" - -typedef enum KillMode { - /* The kill mode is a property of a unit. */ - KILL_CONTROL_GROUP = 0, - KILL_PROCESS, - KILL_MIXED, - KILL_NONE, - _KILL_MODE_MAX, - _KILL_MODE_INVALID = -1 -} KillMode; - -struct KillContext { - KillMode kill_mode; - int kill_signal; - bool send_sigkill; - bool send_sighup; -}; - -typedef enum KillWho { - /* Kill who is a property of an operation */ - KILL_MAIN, - KILL_CONTROL, - KILL_ALL, - KILL_MAIN_FAIL, - KILL_CONTROL_FAIL, - KILL_ALL_FAIL, - _KILL_WHO_MAX, - _KILL_WHO_INVALID = -1 -} KillWho; - -void kill_context_init(KillContext *c); -void kill_context_dump(KillContext *c, FILE *f, const char *prefix); - -const char *kill_mode_to_string(KillMode k) _const_; -KillMode kill_mode_from_string(const char *s) _pure_; - -const char *kill_who_to_string(KillWho k) _const_; -KillWho kill_who_from_string(const char *s) _pure_; diff --git a/src/core/killall.c b/src/core/killall.c deleted file mode 100644 index 09378f7085..0000000000 --- a/src/core/killall.c +++ /dev/null @@ -1,249 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 ProFUSION embedded systems - - 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 -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "killall.h" -#include "parse-util.h" -#include "process-util.h" -#include "set.h" -#include "string-util.h" -#include "terminal-util.h" -#include "util.h" - -#define TIMEOUT_USEC (10 * USEC_PER_SEC) - -static bool ignore_proc(pid_t pid, bool warn_rootfs) { - _cleanup_fclose_ FILE *f = NULL; - char c; - const char *p; - size_t count; - uid_t uid; - int r; - - /* We are PID 1, let's not commit suicide */ - if (pid == 1) - return true; - - r = get_process_uid(pid, &uid); - if (r < 0) - return true; /* not really, but better safe than sorry */ - - /* Non-root processes otherwise are always subject to be killed */ - if (uid != 0) - return false; - - p = procfs_file_alloca(pid, "cmdline"); - f = fopen(p, "re"); - if (!f) - return true; /* not really, but has the desired effect */ - - count = fread(&c, 1, 1, f); - - /* Kernel threads have an empty cmdline */ - if (count <= 0) - return true; - - /* Processes with argv[0][0] = '@' we ignore from the killing - * spree. - * - * http://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons */ - if (c == '@' && warn_rootfs) { - _cleanup_free_ char *comm = NULL; - - r = pid_from_same_root_fs(pid); - if (r < 0) - return true; - - get_process_comm(pid, &comm); - - if (r) - log_notice("Process " PID_FMT " (%s) has been been marked to be excluded from killing. It is " - "running from the root file system, and thus likely to block re-mounting of the " - "root file system to read-only. Please consider moving it into an initrd file " - "system instead.", pid, strna(comm)); - return true; - } else if (c == '@') - return true; - - return false; -} - -static void wait_for_children(Set *pids, sigset_t *mask) { - usec_t until; - - assert(mask); - - if (set_isempty(pids)) - return; - - until = now(CLOCK_MONOTONIC) + TIMEOUT_USEC; - for (;;) { - struct timespec ts; - int k; - usec_t n; - void *p; - Iterator i; - - /* First, let the kernel inform us about killed - * children. Most processes will probably be our - * children, but some are not (might be our - * grandchildren instead...). */ - for (;;) { - pid_t pid; - - pid = waitpid(-1, NULL, WNOHANG); - if (pid == 0) - break; - if (pid < 0) { - if (errno == ECHILD) - break; - - log_error_errno(errno, "waitpid() failed: %m"); - return; - } - - (void) set_remove(pids, PID_TO_PTR(pid)); - } - - /* Now explicitly check who might be remaining, who - * might not be our child. */ - SET_FOREACH(p, pids, i) { - - /* We misuse getpgid as a check whether a - * process still exists. */ - if (getpgid(PTR_TO_PID(p)) >= 0) - continue; - - if (errno != ESRCH) - continue; - - set_remove(pids, p); - } - - if (set_isempty(pids)) - return; - - n = now(CLOCK_MONOTONIC); - if (n >= until) - return; - - timespec_store(&ts, until - n); - k = sigtimedwait(mask, NULL, &ts); - if (k != SIGCHLD) { - - if (k < 0 && errno != EAGAIN) { - log_error_errno(errno, "sigtimedwait() failed: %m"); - return; - } - - if (k >= 0) - log_warning("sigtimedwait() returned unexpected signal."); - } - } -} - -static int killall(int sig, Set *pids, bool send_sighup) { - _cleanup_closedir_ DIR *dir = NULL; - struct dirent *d; - - dir = opendir("/proc"); - if (!dir) - return -errno; - - while ((d = readdir(dir))) { - pid_t pid; - int r; - - if (d->d_type != DT_DIR && - d->d_type != DT_UNKNOWN) - continue; - - if (parse_pid(d->d_name, &pid) < 0) - continue; - - if (ignore_proc(pid, sig == SIGKILL && !in_initrd())) - continue; - - if (sig == SIGKILL) { - _cleanup_free_ char *s = NULL; - - get_process_comm(pid, &s); - log_notice("Sending SIGKILL to PID "PID_FMT" (%s).", pid, strna(s)); - } - - if (kill(pid, sig) >= 0) { - if (pids) { - r = set_put(pids, PID_TO_PTR(pid)); - if (r < 0) - log_oom(); - } - } else if (errno != ENOENT) - log_warning_errno(errno, "Could not kill %d: %m", pid); - - if (send_sighup) { - /* Optionally, also send a SIGHUP signal, but - only if the process has a controlling - tty. This is useful to allow handling of - shells which ignore SIGTERM but react to - SIGHUP. We do not send this to processes that - have no controlling TTY since we don't want to - trigger reloads of daemon processes. Also we - make sure to only send this after SIGTERM so - that SIGTERM is always first in the queue. */ - - - if (get_ctty_devnr(pid, NULL) >= 0) - kill(pid, SIGHUP); - } - } - - return set_size(pids); -} - -void broadcast_signal(int sig, bool wait_for_exit, bool send_sighup) { - sigset_t mask, oldmask; - _cleanup_set_free_ Set *pids = NULL; - - if (wait_for_exit) - pids = set_new(NULL); - - assert_se(sigemptyset(&mask) == 0); - assert_se(sigaddset(&mask, SIGCHLD) == 0); - assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0); - - if (kill(-1, SIGSTOP) < 0 && errno != ESRCH) - log_warning_errno(errno, "kill(-1, SIGSTOP) failed: %m"); - - killall(sig, pids, send_sighup); - - if (kill(-1, SIGCONT) < 0 && errno != ESRCH) - log_warning_errno(errno, "kill(-1, SIGCONT) failed: %m"); - - if (wait_for_exit) - wait_for_children(pids, &mask); - - assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0); -} diff --git a/src/core/killall.h b/src/core/killall.h deleted file mode 100644 index acc2439f00..0000000000 --- a/src/core/killall.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -void broadcast_signal(int sig, bool wait_for_exit, bool send_sighup); diff --git a/src/core/kmod-setup.c b/src/core/kmod-setup.c deleted file mode 100644 index 3503db52ed..0000000000 --- a/src/core/kmod-setup.c +++ /dev/null @@ -1,131 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 - -#ifdef HAVE_KMOD -#include -#endif - -#include "bus-util.h" -#include "capability-util.h" -#include "kmod-setup.h" -#include "macro.h" - -#ifdef HAVE_KMOD -static void systemd_kmod_log( - void *data, - int priority, - const char *file, int line, - const char *fn, - const char *format, - va_list args) { - - /* library logging is enabled at debug only */ - DISABLE_WARNING_FORMAT_NONLITERAL; - log_internalv(LOG_DEBUG, 0, file, line, fn, format, args); - REENABLE_WARNING; -} -#endif - -int kmod_setup(void) { -#ifdef HAVE_KMOD - - static const struct { - const char *module; - const char *path; - bool warn_if_unavailable:1; - bool warn_if_module:1; - bool (*condition_fn)(void); - } kmod_table[] = { - /* auto-loading on use doesn't work before udev is up */ - { "autofs4", "/sys/class/misc/autofs", true, false, NULL }, - - /* early configure of ::1 on the loopback device */ - { "ipv6", "/sys/module/ipv6", false, true, NULL }, - - /* this should never be a module */ - { "unix", "/proc/net/unix", true, true, NULL }, - - /* IPC is needed before we bring up any other services */ - { "kdbus", "/sys/fs/kdbus", false, false, is_kdbus_wanted }, - -#ifdef HAVE_LIBIPTC - /* netfilter is needed by networkd, nspawn among others, and cannot be autoloaded */ - { "ip_tables", "/proc/net/ip_tables_names", false, false, NULL }, -#endif - }; - struct kmod_ctx *ctx = NULL; - unsigned int i; - int r; - - if (have_effective_cap(CAP_SYS_MODULE) == 0) - return 0; - - for (i = 0; i < ELEMENTSOF(kmod_table); i++) { - struct kmod_module *mod; - - if (kmod_table[i].path && access(kmod_table[i].path, F_OK) >= 0) - continue; - - if (kmod_table[i].condition_fn && !kmod_table[i].condition_fn()) - continue; - - if (kmod_table[i].warn_if_module) - log_debug("Your kernel apparently lacks built-in %s support. Might be " - "a good idea to compile it in. We'll now try to work around " - "this by loading the module...", kmod_table[i].module); - - if (!ctx) { - ctx = kmod_new(NULL, NULL); - if (!ctx) - return log_oom(); - - kmod_set_log_fn(ctx, systemd_kmod_log, NULL); - kmod_load_resources(ctx); - } - - r = kmod_module_new_from_name(ctx, kmod_table[i].module, &mod); - if (r < 0) { - log_error("Failed to lookup module '%s'", kmod_table[i].module); - continue; - } - - r = kmod_module_probe_insert_module(mod, KMOD_PROBE_APPLY_BLACKLIST, NULL, NULL, NULL, NULL); - if (r == 0) - log_debug("Inserted module '%s'", kmod_module_get_name(mod)); - else if (r == KMOD_PROBE_APPLY_BLACKLIST) - log_info("Module '%s' is blacklisted", kmod_module_get_name(mod)); - else { - bool print_warning = kmod_table[i].warn_if_unavailable || (r < 0 && r != -ENOENT); - - log_full_errno(print_warning ? LOG_WARNING : LOG_DEBUG, r, - "Failed to insert module '%s': %m", kmod_module_get_name(mod)); - } - - kmod_module_unref(mod); - } - - if (ctx) - kmod_unref(ctx); - -#endif - return 0; -} diff --git a/src/core/kmod-setup.h b/src/core/kmod-setup.h deleted file mode 100644 index 685f4df301..0000000000 --- a/src/core/kmod-setup.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -int kmod_setup(void); diff --git a/src/core/load-dropin.c b/src/core/load-dropin.c deleted file mode 100644 index f83fa09301..0000000000 --- a/src/core/load-dropin.c +++ /dev/null @@ -1,90 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 "conf-parser.h" -#include "load-dropin.h" -#include "load-fragment.h" -#include "log.h" -#include "strv.h" -#include "unit-name.h" -#include "unit.h" - -static int add_dependency_consumer( - UnitDependency dependency, - const char *entry, - const char* filepath, - void *arg) { - Unit *u = arg; - int r; - - assert(u); - - r = unit_add_dependency_by_name(u, dependency, entry, filepath, true); - if (r < 0) - log_error_errno(r, "Cannot add dependency %s to %s, ignoring: %m", entry, u->id); - - return 0; -} - -int unit_load_dropin(Unit *u) { - _cleanup_strv_free_ char **l = NULL; - Iterator i; - char *t, **f; - int r; - - assert(u); - - /* Load dependencies from supplementary drop-in directories */ - - SET_FOREACH(t, u->names, i) { - char **p; - - STRV_FOREACH(p, u->manager->lookup_paths.search_path) { - unit_file_process_dir(u->manager->unit_path_cache, *p, t, ".wants", UNIT_WANTS, - add_dependency_consumer, u, NULL); - unit_file_process_dir(u->manager->unit_path_cache, *p, t, ".requires", UNIT_REQUIRES, - add_dependency_consumer, u, NULL); - } - } - - r = unit_find_dropin_paths(u, &l); - if (r <= 0) - return 0; - - if (!u->dropin_paths) { - u->dropin_paths = l; - l = NULL; - } else { - r = strv_extend_strv(&u->dropin_paths, l, true); - if (r < 0) - return log_oom(); - } - - STRV_FOREACH(f, u->dropin_paths) { - config_parse(u->id, *f, NULL, - UNIT_VTABLE(u)->sections, - config_item_perf_lookup, load_fragment_gperf_lookup, - false, false, false, u); - } - - u->dropin_mtime = now(CLOCK_REALTIME); - - return 0; -} diff --git a/src/core/load-dropin.h b/src/core/load-dropin.h deleted file mode 100644 index 942d26724e..0000000000 --- a/src/core/load-dropin.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "dropin.h" -#include "unit.h" - -/* Read service data supplementary drop-in directories */ - -static inline int unit_find_dropin_paths(Unit *u, char ***paths) { - return unit_file_find_dropin_paths(u->manager->lookup_paths.search_path, - u->manager->unit_path_cache, - u->names, - paths); -} - -int unit_load_dropin(Unit *u); diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 deleted file mode 100644 index 8193418980..0000000000 --- a/src/core/load-fragment-gperf.gperf.m4 +++ /dev/null @@ -1,391 +0,0 @@ -%{ -#include -#include "conf-parser.h" -#include "load-fragment.h" -#include "missing.h" -%} -struct ConfigPerfItem; -%null_strings -%language=ANSI-C -%define slot-name section_and_lvalue -%define hash-function-name load_fragment_gperf_hash -%define lookup-function-name load_fragment_gperf_lookup -%readonly-tables -%omit-struct-type -%struct-type -%includes -%% -m4_dnl Define the context options only once -m4_define(`EXEC_CONTEXT_CONFIG_ITEMS', -`$1.WorkingDirectory, config_parse_working_directory, 0, offsetof($1, exec_context) -$1.RootDirectory, config_parse_unit_path_printf, 0, offsetof($1, exec_context.root_directory) -$1.User, config_parse_unit_string_printf, 0, offsetof($1, exec_context.user) -$1.Group, config_parse_unit_string_printf, 0, offsetof($1, exec_context.group) -$1.SupplementaryGroups, config_parse_strv, 0, offsetof($1, exec_context.supplementary_groups) -$1.Nice, config_parse_exec_nice, 0, offsetof($1, exec_context) -$1.OOMScoreAdjust, config_parse_exec_oom_score_adjust, 0, offsetof($1, exec_context) -$1.IOSchedulingClass, config_parse_exec_io_class, 0, offsetof($1, exec_context) -$1.IOSchedulingPriority, config_parse_exec_io_priority, 0, offsetof($1, exec_context) -$1.CPUSchedulingPolicy, config_parse_exec_cpu_sched_policy, 0, offsetof($1, exec_context) -$1.CPUSchedulingPriority, config_parse_exec_cpu_sched_prio, 0, offsetof($1, exec_context) -$1.CPUSchedulingResetOnFork, config_parse_bool, 0, offsetof($1, exec_context.cpu_sched_reset_on_fork) -$1.CPUAffinity, config_parse_exec_cpu_affinity, 0, offsetof($1, exec_context) -$1.UMask, config_parse_mode, 0, offsetof($1, exec_context.umask) -$1.Environment, config_parse_environ, 0, offsetof($1, exec_context.environment) -$1.EnvironmentFile, config_parse_unit_env_file, 0, offsetof($1, exec_context.environment_files) -$1.PassEnvironment, config_parse_pass_environ, 0, offsetof($1, exec_context.pass_environment) -$1.StandardInput, config_parse_input, 0, offsetof($1, exec_context.std_input) -$1.StandardOutput, config_parse_output, 0, offsetof($1, exec_context.std_output) -$1.StandardError, config_parse_output, 0, offsetof($1, exec_context.std_error) -$1.TTYPath, config_parse_unit_path_printf, 0, offsetof($1, exec_context.tty_path) -$1.TTYReset, config_parse_bool, 0, offsetof($1, exec_context.tty_reset) -$1.TTYVHangup, config_parse_bool, 0, offsetof($1, exec_context.tty_vhangup) -$1.TTYVTDisallocate, config_parse_bool, 0, offsetof($1, exec_context.tty_vt_disallocate) -$1.SyslogIdentifier, config_parse_unit_string_printf, 0, offsetof($1, exec_context.syslog_identifier) -$1.SyslogFacility, config_parse_log_facility, 0, offsetof($1, exec_context.syslog_priority) -$1.SyslogLevel, config_parse_log_level, 0, offsetof($1, exec_context.syslog_priority) -$1.SyslogLevelPrefix, config_parse_bool, 0, offsetof($1, exec_context.syslog_level_prefix) -$1.Capabilities, config_parse_warn_compat, DISABLED_LEGACY, offsetof($1, exec_context) -$1.SecureBits, config_parse_exec_secure_bits, 0, offsetof($1, exec_context) -$1.CapabilityBoundingSet, config_parse_capability_set, 0, offsetof($1, exec_context.capability_bounding_set) -$1.AmbientCapabilities, config_parse_capability_set, 0, offsetof($1, exec_context.capability_ambient_set) -$1.TimerSlackNSec, config_parse_nsec, 0, offsetof($1, exec_context.timer_slack_nsec) -$1.NoNewPrivileges, config_parse_no_new_privileges, 0, offsetof($1, exec_context) -m4_ifdef(`HAVE_SECCOMP', -`$1.SystemCallFilter, config_parse_syscall_filter, 0, offsetof($1, exec_context) -$1.SystemCallArchitectures, config_parse_syscall_archs, 0, offsetof($1, exec_context.syscall_archs) -$1.SystemCallErrorNumber, config_parse_syscall_errno, 0, offsetof($1, exec_context) -$1.RestrictAddressFamilies, config_parse_address_families, 0, offsetof($1, exec_context)', -`$1.SystemCallFilter, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 -$1.SystemCallArchitectures, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 -$1.SystemCallErrorNumber, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 -$1.RestrictAddressFamilies, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') -$1.LimitCPU, config_parse_limit, RLIMIT_CPU, offsetof($1, exec_context.rlimit) -$1.LimitFSIZE, config_parse_limit, RLIMIT_FSIZE, offsetof($1, exec_context.rlimit) -$1.LimitDATA, config_parse_limit, RLIMIT_DATA, offsetof($1, exec_context.rlimit) -$1.LimitSTACK, config_parse_limit, RLIMIT_STACK, offsetof($1, exec_context.rlimit) -$1.LimitCORE, config_parse_limit, RLIMIT_CORE, offsetof($1, exec_context.rlimit) -$1.LimitRSS, config_parse_limit, RLIMIT_RSS, offsetof($1, exec_context.rlimit) -$1.LimitNOFILE, config_parse_limit, RLIMIT_NOFILE, offsetof($1, exec_context.rlimit) -$1.LimitAS, config_parse_limit, RLIMIT_AS, offsetof($1, exec_context.rlimit) -$1.LimitNPROC, config_parse_limit, RLIMIT_NPROC, offsetof($1, exec_context.rlimit) -$1.LimitMEMLOCK, config_parse_limit, RLIMIT_MEMLOCK, offsetof($1, exec_context.rlimit) -$1.LimitLOCKS, config_parse_limit, RLIMIT_LOCKS, offsetof($1, exec_context.rlimit) -$1.LimitSIGPENDING, config_parse_limit, RLIMIT_SIGPENDING, offsetof($1, exec_context.rlimit) -$1.LimitMSGQUEUE, config_parse_limit, RLIMIT_MSGQUEUE, offsetof($1, exec_context.rlimit) -$1.LimitNICE, config_parse_limit, RLIMIT_NICE, offsetof($1, exec_context.rlimit) -$1.LimitRTPRIO, config_parse_limit, RLIMIT_RTPRIO, offsetof($1, exec_context.rlimit) -$1.LimitRTTIME, config_parse_limit, RLIMIT_RTTIME, offsetof($1, exec_context.rlimit) -$1.ReadWriteDirectories, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_write_dirs) -$1.ReadOnlyDirectories, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_only_dirs) -$1.InaccessibleDirectories, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.inaccessible_dirs) -$1.PrivateTmp, config_parse_bool, 0, offsetof($1, exec_context.private_tmp) -$1.PrivateNetwork, config_parse_bool, 0, offsetof($1, exec_context.private_network) -$1.PrivateDevices, config_parse_bool, 0, offsetof($1, exec_context.private_devices) -$1.ProtectSystem, config_parse_protect_system, 0, offsetof($1, exec_context) -$1.ProtectHome, config_parse_protect_home, 0, offsetof($1, exec_context) -$1.MountFlags, config_parse_exec_mount_flags, 0, offsetof($1, exec_context) -$1.Personality, config_parse_personality, 0, offsetof($1, exec_context.personality) -$1.RuntimeDirectoryMode, config_parse_mode, 0, offsetof($1, exec_context.runtime_directory_mode) -$1.RuntimeDirectory, config_parse_runtime_directory, 0, offsetof($1, exec_context.runtime_directory) -m4_ifdef(`HAVE_PAM', -`$1.PAMName, config_parse_unit_string_printf, 0, offsetof($1, exec_context.pam_name)', -`$1.PAMName, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') -$1.IgnoreSIGPIPE, config_parse_bool, 0, offsetof($1, exec_context.ignore_sigpipe) -$1.UtmpIdentifier, config_parse_unit_string_printf, 0, offsetof($1, exec_context.utmp_id) -$1.UtmpMode, config_parse_exec_utmp_mode, 0, offsetof($1, exec_context.utmp_mode) -m4_ifdef(`HAVE_SELINUX', -`$1.SELinuxContext, config_parse_exec_selinux_context, 0, offsetof($1, exec_context)', -`$1.SELinuxContext, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') -m4_ifdef(`HAVE_APPARMOR', -`$1.AppArmorProfile, config_parse_exec_apparmor_profile, 0, offsetof($1, exec_context)', -`$1.AppArmorProfile, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') -m4_ifdef(`HAVE_SMACK', -`$1.SmackProcessLabel, config_parse_exec_smack_process_label, 0, offsetof($1, exec_context)', -`$1.SmackProcessLabel, config_parse_warn_compat, DISABLED_CONFIGURATION, 0')' -)m4_dnl -m4_define(`KILL_CONTEXT_CONFIG_ITEMS', -`$1.SendSIGKILL, config_parse_bool, 0, offsetof($1, kill_context.send_sigkill) -$1.SendSIGHUP, config_parse_bool, 0, offsetof($1, kill_context.send_sighup) -$1.KillMode, config_parse_kill_mode, 0, offsetof($1, kill_context.kill_mode) -$1.KillSignal, config_parse_signal, 0, offsetof($1, kill_context.kill_signal)' -)m4_dnl -m4_define(`CGROUP_CONTEXT_CONFIG_ITEMS', -`$1.Slice, config_parse_unit_slice, 0, 0 -$1.CPUAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.cpu_accounting) -$1.CPUShares, config_parse_cpu_shares, 0, offsetof($1, cgroup_context.cpu_shares) -$1.StartupCPUShares, config_parse_cpu_shares, 0, offsetof($1, cgroup_context.startup_cpu_shares) -$1.CPUQuota, config_parse_cpu_quota, 0, offsetof($1, cgroup_context) -$1.MemoryAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.memory_accounting) -$1.MemoryLimit, config_parse_memory_limit, 0, offsetof($1, cgroup_context) -$1.DeviceAllow, config_parse_device_allow, 0, offsetof($1, cgroup_context) -$1.DevicePolicy, config_parse_device_policy, 0, offsetof($1, cgroup_context.device_policy) -$1.IOAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.io_accounting) -$1.IOWeight, config_parse_io_weight, 0, offsetof($1, cgroup_context.io_weight) -$1.StartupIOWeight, config_parse_io_weight, 0, offsetof($1, cgroup_context.startup_io_weight) -$1.IODeviceWeight, config_parse_io_device_weight, 0, offsetof($1, cgroup_context) -$1.IOReadBandwidthMax, config_parse_io_limit, 0, offsetof($1, cgroup_context) -$1.IOWriteBandwidthMax, config_parse_io_limit, 0, offsetof($1, cgroup_context) -$1.IOReadIOPSMax, config_parse_io_limit, 0, offsetof($1, cgroup_context) -$1.IOWriteIOPSMax, config_parse_io_limit, 0, offsetof($1, cgroup_context) -$1.BlockIOAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.blockio_accounting) -$1.BlockIOWeight, config_parse_blockio_weight, 0, offsetof($1, cgroup_context.blockio_weight) -$1.StartupBlockIOWeight, config_parse_blockio_weight, 0, offsetof($1, cgroup_context.startup_blockio_weight) -$1.BlockIODeviceWeight, config_parse_blockio_device_weight, 0, offsetof($1, cgroup_context) -$1.BlockIOReadBandwidth, config_parse_blockio_bandwidth, 0, offsetof($1, cgroup_context) -$1.BlockIOWriteBandwidth, config_parse_blockio_bandwidth, 0, offsetof($1, cgroup_context) -$1.TasksAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.tasks_accounting) -$1.TasksMax, config_parse_tasks_max, 0, offsetof($1, cgroup_context.tasks_max) -$1.Delegate, config_parse_bool, 0, offsetof($1, cgroup_context.delegate) -$1.NetClass, config_parse_warn_compat, DISABLED_LEGACY, 0' -)m4_dnl -Unit.Description, config_parse_unit_string_printf, 0, offsetof(Unit, description) -Unit.Documentation, config_parse_documentation, 0, offsetof(Unit, documentation) -Unit.SourcePath, config_parse_path, 0, offsetof(Unit, source_path) -Unit.Requires, config_parse_unit_deps, UNIT_REQUIRES, 0 -Unit.Requisite, config_parse_unit_deps, UNIT_REQUISITE, 0 -Unit.Wants, config_parse_unit_deps, UNIT_WANTS, 0 -Unit.BindsTo, config_parse_unit_deps, UNIT_BINDS_TO, 0 -Unit.BindTo, config_parse_unit_deps, UNIT_BINDS_TO, 0 -Unit.Conflicts, config_parse_unit_deps, UNIT_CONFLICTS, 0 -Unit.Before, config_parse_unit_deps, UNIT_BEFORE, 0 -Unit.After, config_parse_unit_deps, UNIT_AFTER, 0 -Unit.OnFailure, config_parse_unit_deps, UNIT_ON_FAILURE, 0 -Unit.PropagatesReloadTo, config_parse_unit_deps, UNIT_PROPAGATES_RELOAD_TO, 0 -Unit.PropagateReloadTo, config_parse_unit_deps, UNIT_PROPAGATES_RELOAD_TO, 0 -Unit.ReloadPropagatedFrom, config_parse_unit_deps, UNIT_RELOAD_PROPAGATED_FROM, 0 -Unit.PropagateReloadFrom, config_parse_unit_deps, UNIT_RELOAD_PROPAGATED_FROM, 0 -Unit.PartOf, config_parse_unit_deps, UNIT_PART_OF, 0 -Unit.JoinsNamespaceOf, config_parse_unit_deps, UNIT_JOINS_NAMESPACE_OF, 0 -Unit.RequiresOverridable, config_parse_obsolete_unit_deps, UNIT_REQUIRES, 0 -Unit.RequisiteOverridable, config_parse_obsolete_unit_deps, UNIT_REQUISITE, 0 -Unit.RequiresMountsFor, config_parse_unit_requires_mounts_for, 0, 0 -Unit.StopWhenUnneeded, config_parse_bool, 0, offsetof(Unit, stop_when_unneeded) -Unit.RefuseManualStart, config_parse_bool, 0, offsetof(Unit, refuse_manual_start) -Unit.RefuseManualStop, config_parse_bool, 0, offsetof(Unit, refuse_manual_stop) -Unit.AllowIsolate, config_parse_bool, 0, offsetof(Unit, allow_isolate) -Unit.DefaultDependencies, config_parse_bool, 0, offsetof(Unit, default_dependencies) -Unit.OnFailureJobMode, config_parse_job_mode, 0, offsetof(Unit, on_failure_job_mode) -Unit.OnFailureIsolate, config_parse_job_mode_isolate, 0, offsetof(Unit, on_failure_job_mode) -Unit.IgnoreOnIsolate, config_parse_bool, 0, offsetof(Unit, ignore_on_isolate) -Unit.IgnoreOnSnapshot, config_parse_warn_compat, DISABLED_LEGACY, 0 -Unit.JobTimeoutSec, config_parse_sec_fix_0, 0, offsetof(Unit, job_timeout) -Unit.JobTimeoutAction, config_parse_failure_action, 0, offsetof(Unit, job_timeout_action) -Unit.JobTimeoutRebootArgument, config_parse_string, 0, offsetof(Unit, job_timeout_reboot_arg) -Unit.StartLimitIntervalSec, config_parse_sec, 0, offsetof(Unit, start_limit.interval) -m4_dnl The following is a legacy alias name for compatibility -Unit.StartLimitInterval, config_parse_sec, 0, offsetof(Unit, start_limit.interval) -Unit.StartLimitBurst, config_parse_unsigned, 0, offsetof(Unit, start_limit.burst) -Unit.StartLimitAction, config_parse_failure_action, 0, offsetof(Unit, start_limit_action) -Unit.RebootArgument, config_parse_string, 0, offsetof(Unit, reboot_arg) -Unit.ConditionPathExists, config_parse_unit_condition_path, CONDITION_PATH_EXISTS, offsetof(Unit, conditions) -Unit.ConditionPathExistsGlob, config_parse_unit_condition_path, CONDITION_PATH_EXISTS_GLOB, offsetof(Unit, conditions) -Unit.ConditionPathIsDirectory, config_parse_unit_condition_path, CONDITION_PATH_IS_DIRECTORY, offsetof(Unit, conditions) -Unit.ConditionPathIsSymbolicLink,config_parse_unit_condition_path, CONDITION_PATH_IS_SYMBOLIC_LINK,offsetof(Unit, conditions) -Unit.ConditionPathIsMountPoint, config_parse_unit_condition_path, CONDITION_PATH_IS_MOUNT_POINT, offsetof(Unit, conditions) -Unit.ConditionPathIsReadWrite, config_parse_unit_condition_path, CONDITION_PATH_IS_READ_WRITE, offsetof(Unit, conditions) -Unit.ConditionDirectoryNotEmpty, config_parse_unit_condition_path, CONDITION_DIRECTORY_NOT_EMPTY, offsetof(Unit, conditions) -Unit.ConditionFileNotEmpty, config_parse_unit_condition_path, CONDITION_FILE_NOT_EMPTY, offsetof(Unit, conditions) -Unit.ConditionFileIsExecutable, config_parse_unit_condition_path, CONDITION_FILE_IS_EXECUTABLE, offsetof(Unit, conditions) -Unit.ConditionNeedsUpdate, config_parse_unit_condition_path, CONDITION_NEEDS_UPDATE, offsetof(Unit, conditions) -Unit.ConditionFirstBoot, config_parse_unit_condition_string, CONDITION_FIRST_BOOT, offsetof(Unit, conditions) -Unit.ConditionKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, offsetof(Unit, conditions) -Unit.ConditionArchitecture, config_parse_unit_condition_string, CONDITION_ARCHITECTURE, offsetof(Unit, conditions) -Unit.ConditionVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, offsetof(Unit, conditions) -Unit.ConditionSecurity, config_parse_unit_condition_string, CONDITION_SECURITY, offsetof(Unit, conditions) -Unit.ConditionCapability, config_parse_unit_condition_string, CONDITION_CAPABILITY, offsetof(Unit, conditions) -Unit.ConditionHost, config_parse_unit_condition_string, CONDITION_HOST, offsetof(Unit, conditions) -Unit.ConditionACPower, config_parse_unit_condition_string, CONDITION_AC_POWER, offsetof(Unit, conditions) -Unit.ConditionNull, config_parse_unit_condition_null, 0, offsetof(Unit, conditions) -Unit.AssertPathExists, config_parse_unit_condition_path, CONDITION_PATH_EXISTS, offsetof(Unit, asserts) -Unit.AssertPathExistsGlob, config_parse_unit_condition_path, CONDITION_PATH_EXISTS_GLOB, offsetof(Unit, asserts) -Unit.AssertPathIsDirectory, config_parse_unit_condition_path, CONDITION_PATH_IS_DIRECTORY, offsetof(Unit, asserts) -Unit.AssertPathIsSymbolicLink, config_parse_unit_condition_path, CONDITION_PATH_IS_SYMBOLIC_LINK,offsetof(Unit, asserts) -Unit.AssertPathIsMountPoint, config_parse_unit_condition_path, CONDITION_PATH_IS_MOUNT_POINT, offsetof(Unit, asserts) -Unit.AssertPathIsReadWrite, config_parse_unit_condition_path, CONDITION_PATH_IS_READ_WRITE, offsetof(Unit, asserts) -Unit.AssertDirectoryNotEmpty, config_parse_unit_condition_path, CONDITION_DIRECTORY_NOT_EMPTY, offsetof(Unit, asserts) -Unit.AssertFileNotEmpty, config_parse_unit_condition_path, CONDITION_FILE_NOT_EMPTY, offsetof(Unit, asserts) -Unit.AssertFileIsExecutable, config_parse_unit_condition_path, CONDITION_FILE_IS_EXECUTABLE, offsetof(Unit, asserts) -Unit.AssertNeedsUpdate, config_parse_unit_condition_path, CONDITION_NEEDS_UPDATE, offsetof(Unit, asserts) -Unit.AssertFirstBoot, config_parse_unit_condition_string, CONDITION_FIRST_BOOT, offsetof(Unit, asserts) -Unit.AssertKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, offsetof(Unit, asserts) -Unit.AssertArchitecture, config_parse_unit_condition_string, CONDITION_ARCHITECTURE, offsetof(Unit, asserts) -Unit.AssertVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, offsetof(Unit, asserts) -Unit.AssertSecurity, config_parse_unit_condition_string, CONDITION_SECURITY, offsetof(Unit, asserts) -Unit.AssertCapability, config_parse_unit_condition_string, CONDITION_CAPABILITY, offsetof(Unit, asserts) -Unit.AssertHost, config_parse_unit_condition_string, CONDITION_HOST, offsetof(Unit, asserts) -Unit.AssertACPower, config_parse_unit_condition_string, CONDITION_AC_POWER, offsetof(Unit, asserts) -Unit.AssertNull, config_parse_unit_condition_null, 0, offsetof(Unit, asserts) -m4_dnl -Service.PIDFile, config_parse_unit_path_printf, 0, offsetof(Service, pid_file) -Service.ExecStartPre, config_parse_exec, SERVICE_EXEC_START_PRE, offsetof(Service, exec_command) -Service.ExecStart, config_parse_exec, SERVICE_EXEC_START, offsetof(Service, exec_command) -Service.ExecStartPost, config_parse_exec, SERVICE_EXEC_START_POST, offsetof(Service, exec_command) -Service.ExecReload, config_parse_exec, SERVICE_EXEC_RELOAD, offsetof(Service, exec_command) -Service.ExecStop, config_parse_exec, SERVICE_EXEC_STOP, offsetof(Service, exec_command) -Service.ExecStopPost, config_parse_exec, SERVICE_EXEC_STOP_POST, offsetof(Service, exec_command) -Service.RestartSec, config_parse_sec, 0, offsetof(Service, restart_usec) -Service.TimeoutSec, config_parse_service_timeout, 0, 0 -Service.TimeoutStartSec, config_parse_service_timeout, 0, 0 -Service.TimeoutStopSec, config_parse_service_timeout, 0, 0 -Service.RuntimeMaxSec, config_parse_sec, 0, offsetof(Service, runtime_max_usec) -Service.WatchdogSec, config_parse_sec, 0, offsetof(Service, watchdog_usec) -m4_dnl The following three only exist for compatibility, they moved into Unit, see above -Service.StartLimitInterval, config_parse_sec, 0, offsetof(Unit, start_limit.interval) -Service.StartLimitBurst, config_parse_unsigned, 0, offsetof(Unit, start_limit.burst) -Service.StartLimitAction, config_parse_failure_action, 0, offsetof(Unit, start_limit_action) -Service.RebootArgument, config_parse_string, 0, offsetof(Unit, reboot_arg) -Service.FailureAction, config_parse_failure_action, 0, offsetof(Service, failure_action) -Service.Type, config_parse_service_type, 0, offsetof(Service, type) -Service.Restart, config_parse_service_restart, 0, offsetof(Service, restart) -Service.PermissionsStartOnly, config_parse_bool, 0, offsetof(Service, permissions_start_only) -Service.RootDirectoryStartOnly, config_parse_bool, 0, offsetof(Service, root_directory_start_only) -Service.RemainAfterExit, config_parse_bool, 0, offsetof(Service, remain_after_exit) -Service.GuessMainPID, config_parse_bool, 0, offsetof(Service, guess_main_pid) -Service.RestartPreventExitStatus, config_parse_set_status, 0, offsetof(Service, restart_prevent_status) -Service.RestartForceExitStatus, config_parse_set_status, 0, offsetof(Service, restart_force_status) -Service.SuccessExitStatus, config_parse_set_status, 0, offsetof(Service, success_status) -Service.SysVStartPriority, config_parse_warn_compat, DISABLED_LEGACY, 0 -Service.NonBlocking, config_parse_bool, 0, offsetof(Service, exec_context.non_blocking) -Service.BusName, config_parse_bus_name, 0, offsetof(Service, bus_name) -Service.FileDescriptorStoreMax, config_parse_unsigned, 0, offsetof(Service, n_fd_store_max) -Service.NotifyAccess, config_parse_notify_access, 0, offsetof(Service, notify_access) -Service.Sockets, config_parse_service_sockets, 0, 0 -Service.BusPolicy, config_parse_warn_compat, DISABLED_LEGACY, 0 -Service.USBFunctionDescriptors, config_parse_path, 0, offsetof(Service, usb_function_descriptors) -Service.USBFunctionStrings, config_parse_path, 0, offsetof(Service, usb_function_strings) -EXEC_CONTEXT_CONFIG_ITEMS(Service)m4_dnl -CGROUP_CONTEXT_CONFIG_ITEMS(Service)m4_dnl -KILL_CONTEXT_CONFIG_ITEMS(Service)m4_dnl -m4_dnl -Socket.ListenStream, config_parse_socket_listen, SOCKET_SOCKET, 0 -Socket.ListenDatagram, config_parse_socket_listen, SOCKET_SOCKET, 0 -Socket.ListenSequentialPacket, config_parse_socket_listen, SOCKET_SOCKET, 0 -Socket.ListenFIFO, config_parse_socket_listen, SOCKET_FIFO, 0 -Socket.ListenNetlink, config_parse_socket_listen, SOCKET_SOCKET, 0 -Socket.ListenSpecial, config_parse_socket_listen, SOCKET_SPECIAL, 0 -Socket.ListenMessageQueue, config_parse_socket_listen, SOCKET_MQUEUE, 0 -Socket.ListenUSBFunction, config_parse_socket_listen, SOCKET_USB_FUNCTION, 0 -Socket.SocketProtocol, config_parse_socket_protocol, 0, 0 -Socket.BindIPv6Only, config_parse_socket_bind, 0, 0, -Socket.Backlog, config_parse_unsigned, 0, offsetof(Socket, backlog) -Socket.BindToDevice, config_parse_socket_bindtodevice, 0, 0 -Socket.ExecStartPre, config_parse_exec, SOCKET_EXEC_START_PRE, offsetof(Socket, exec_command) -Socket.ExecStartPost, config_parse_exec, SOCKET_EXEC_START_POST, offsetof(Socket, exec_command) -Socket.ExecStopPre, config_parse_exec, SOCKET_EXEC_STOP_PRE, offsetof(Socket, exec_command) -Socket.ExecStopPost, config_parse_exec, SOCKET_EXEC_STOP_POST, offsetof(Socket, exec_command) -Socket.TimeoutSec, config_parse_sec, 0, offsetof(Socket, timeout_usec) -Socket.SocketUser, config_parse_unit_string_printf, 0, offsetof(Socket, user) -Socket.SocketGroup, config_parse_unit_string_printf, 0, offsetof(Socket, group) -Socket.SocketMode, config_parse_mode, 0, offsetof(Socket, socket_mode) -Socket.DirectoryMode, config_parse_mode, 0, offsetof(Socket, directory_mode) -Socket.Accept, config_parse_bool, 0, offsetof(Socket, accept) -Socket.Writable, config_parse_bool, 0, offsetof(Socket, writable) -Socket.MaxConnections, config_parse_unsigned, 0, offsetof(Socket, max_connections) -Socket.KeepAlive, config_parse_bool, 0, offsetof(Socket, keep_alive) -Socket.KeepAliveTimeSec, config_parse_sec, 0, offsetof(Socket, keep_alive_time) -Socket.KeepAliveIntervalSec, config_parse_sec, 0, offsetof(Socket, keep_alive_interval) -Socket.KeepAliveProbes, config_parse_unsigned, 0, offsetof(Socket, keep_alive_cnt) -Socket.DeferAcceptSec, config_parse_sec, 0, offsetof(Socket, defer_accept) -Socket.NoDelay, config_parse_bool, 0, offsetof(Socket, no_delay) -Socket.Priority, config_parse_int, 0, offsetof(Socket, priority) -Socket.ReceiveBuffer, config_parse_iec_size, 0, offsetof(Socket, receive_buffer) -Socket.SendBuffer, config_parse_iec_size, 0, offsetof(Socket, send_buffer) -Socket.IPTOS, config_parse_ip_tos, 0, offsetof(Socket, ip_tos) -Socket.IPTTL, config_parse_int, 0, offsetof(Socket, ip_ttl) -Socket.Mark, config_parse_int, 0, offsetof(Socket, mark) -Socket.PipeSize, config_parse_iec_size, 0, offsetof(Socket, pipe_size) -Socket.FreeBind, config_parse_bool, 0, offsetof(Socket, free_bind) -Socket.Transparent, config_parse_bool, 0, offsetof(Socket, transparent) -Socket.Broadcast, config_parse_bool, 0, offsetof(Socket, broadcast) -Socket.PassCredentials, config_parse_bool, 0, offsetof(Socket, pass_cred) -Socket.PassSecurity, config_parse_bool, 0, offsetof(Socket, pass_sec) -Socket.TCPCongestion, config_parse_string, 0, offsetof(Socket, tcp_congestion) -Socket.ReusePort, config_parse_bool, 0, offsetof(Socket, reuse_port) -Socket.MessageQueueMaxMessages, config_parse_long, 0, offsetof(Socket, mq_maxmsg) -Socket.MessageQueueMessageSize, config_parse_long, 0, offsetof(Socket, mq_msgsize) -Socket.RemoveOnStop, config_parse_bool, 0, offsetof(Socket, remove_on_stop) -Socket.Symlinks, config_parse_unit_path_strv_printf, 0, offsetof(Socket, symlinks) -Socket.FileDescriptorName, config_parse_fdname, 0, 0 -Socket.Service, config_parse_socket_service, 0, 0 -Socket.TriggerLimitIntervalSec, config_parse_sec, 0, offsetof(Socket, trigger_limit.interval) -Socket.TriggerLimitBurst, config_parse_unsigned, 0, offsetof(Socket, trigger_limit.burst) -m4_ifdef(`HAVE_SMACK', -`Socket.SmackLabel, config_parse_string, 0, offsetof(Socket, smack) -Socket.SmackLabelIPIn, config_parse_string, 0, offsetof(Socket, smack_ip_in) -Socket.SmackLabelIPOut, config_parse_string, 0, offsetof(Socket, smack_ip_out)', -`Socket.SmackLabel, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 -Socket.SmackLabelIPIn, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 -Socket.SmackLabelIPOut, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') -m4_ifdef(`HAVE_SELINUX', -`Socket.SELinuxContextFromNet, config_parse_bool, 0, offsetof(Socket, selinux_context_from_net)', -`Socket.SELinuxContextFromNet, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') -EXEC_CONTEXT_CONFIG_ITEMS(Socket)m4_dnl -CGROUP_CONTEXT_CONFIG_ITEMS(Socket)m4_dnl -KILL_CONTEXT_CONFIG_ITEMS(Socket)m4_dnl -m4_dnl -BusName.Name, config_parse_string, 0, offsetof(BusName, name) -BusName.Activating, config_parse_bool, 0, offsetof(BusName, activating) -BusName.Service, config_parse_busname_service, 0, 0 -BusName.AllowUser, config_parse_bus_policy, 0, 0 -BusName.AllowGroup, config_parse_bus_policy, 0, 0 -BusName.AllowWorld, config_parse_bus_policy_world, 0, offsetof(BusName, policy_world) -BusName.SELinuxContext, config_parse_exec_selinux_context, 0, 0 -BusName.AcceptFileDescriptors, config_parse_bool, 0, offsetof(BusName, accept_fd) -m4_dnl -Mount.What, config_parse_string, 0, offsetof(Mount, parameters_fragment.what) -Mount.Where, config_parse_path, 0, offsetof(Mount, where) -Mount.Options, config_parse_string, 0, offsetof(Mount, parameters_fragment.options) -Mount.Type, config_parse_string, 0, offsetof(Mount, parameters_fragment.fstype) -Mount.TimeoutSec, config_parse_sec, 0, offsetof(Mount, timeout_usec) -Mount.DirectoryMode, config_parse_mode, 0, offsetof(Mount, directory_mode) -Mount.SloppyOptions, config_parse_bool, 0, offsetof(Mount, sloppy_options) -EXEC_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl -CGROUP_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl -KILL_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl -m4_dnl -Automount.Where, config_parse_path, 0, offsetof(Automount, where) -Automount.DirectoryMode, config_parse_mode, 0, offsetof(Automount, directory_mode) -Automount.TimeoutIdleSec, config_parse_sec, 0, offsetof(Automount, timeout_idle_usec) -m4_dnl -Swap.What, config_parse_path, 0, offsetof(Swap, parameters_fragment.what) -Swap.Priority, config_parse_int, 0, offsetof(Swap, parameters_fragment.priority) -Swap.Options, config_parse_string, 0, offsetof(Swap, parameters_fragment.options) -Swap.TimeoutSec, config_parse_sec, 0, offsetof(Swap, timeout_usec) -EXEC_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl -CGROUP_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl -KILL_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl -m4_dnl -Timer.OnCalendar, config_parse_timer, 0, 0 -Timer.OnActiveSec, config_parse_timer, 0, 0 -Timer.OnBootSec, config_parse_timer, 0, 0 -Timer.OnStartupSec, config_parse_timer, 0, 0 -Timer.OnUnitActiveSec, config_parse_timer, 0, 0 -Timer.OnUnitInactiveSec, config_parse_timer, 0, 0 -Timer.Persistent, config_parse_bool, 0, offsetof(Timer, persistent) -Timer.WakeSystem, config_parse_bool, 0, offsetof(Timer, wake_system) -Timer.RemainAfterElapse, config_parse_bool, 0, offsetof(Timer, remain_after_elapse) -Timer.AccuracySec, config_parse_sec, 0, offsetof(Timer, accuracy_usec) -Timer.RandomizedDelaySec, config_parse_sec, 0, offsetof(Timer, random_usec) -Timer.Unit, config_parse_trigger_unit, 0, 0 -m4_dnl -Path.PathExists, config_parse_path_spec, 0, 0 -Path.PathExistsGlob, config_parse_path_spec, 0, 0 -Path.PathChanged, config_parse_path_spec, 0, 0 -Path.PathModified, config_parse_path_spec, 0, 0 -Path.DirectoryNotEmpty, config_parse_path_spec, 0, 0 -Path.Unit, config_parse_trigger_unit, 0, 0 -Path.MakeDirectory, config_parse_bool, 0, offsetof(Path, make_directory) -Path.DirectoryMode, config_parse_mode, 0, offsetof(Path, directory_mode) -m4_dnl -CGROUP_CONTEXT_CONFIG_ITEMS(Slice)m4_dnl -m4_dnl -CGROUP_CONTEXT_CONFIG_ITEMS(Scope)m4_dnl -KILL_CONTEXT_CONFIG_ITEMS(Scope)m4_dnl -Scope.TimeoutStopSec, config_parse_sec, 0, offsetof(Scope, timeout_stop_usec) -m4_dnl The [Install] section is ignored here. -Install.Alias, NULL, 0, 0 -Install.WantedBy, NULL, 0, 0 -Install.RequiredBy, NULL, 0, 0 -Install.Also, NULL, 0, 0 -Install.DefaultInstance, NULL, 0, 0 diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c deleted file mode 100644 index 86b4fb071b..0000000000 --- a/src/core/load-fragment.c +++ /dev/null @@ -1,4083 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2012 Holger Hans Peter Freyther - - 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 -#include -#include -#ifdef HAVE_SECCOMP -#include -#endif -#include -#include -#include -#include - -#include "af-list.h" -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-internal.h" -#include "bus-util.h" -#include "cap-list.h" -#include "capability-util.h" -#include "cgroup.h" -#include "conf-parser.h" -#include "cpu-set-util.h" -#include "env-util.h" -#include "errno-list.h" -#include "escape.h" -#include "fd-util.h" -#include "fs-util.h" -#include "ioprio.h" -#include "load-fragment.h" -#include "log.h" -#include "missing.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "rlimit-util.h" -#ifdef HAVE_SECCOMP -#include "seccomp-util.h" -#endif -#include "securebits.h" -#include "signal-util.h" -#include "stat-util.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "unit-printf.h" -#include "unit.h" -#include "utf8.h" -#include "web-util.h" - -int config_parse_warn_compat( - 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) { - Disabled reason = ltype; - - switch(reason) { - case DISABLED_CONFIGURATION: - log_syntax(unit, LOG_DEBUG, filename, line, 0, - "Support for option %s= has been disabled at compile time and it is ignored", lvalue); - break; - case DISABLED_LEGACY: - log_syntax(unit, LOG_INFO, filename, line, 0, - "Support for option %s= has been removed and it is ignored", lvalue); - break; - case DISABLED_EXPERIMENTAL: - log_syntax(unit, LOG_INFO, filename, line, 0, - "Support for option %s= has not yet been enabled and it is ignored", lvalue); - break; - }; - - return 0; -} - -int config_parse_unit_deps( - 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) { - - UnitDependency d = ltype; - Unit *u = userdata; - const char *p; - - assert(filename); - assert(lvalue); - assert(rvalue); - - p = rvalue; - for (;;) { - _cleanup_free_ char *word = NULL, *k = NULL; - int r; - - r = extract_first_word(&p, &word, NULL, EXTRACT_RETAIN_ESCAPE); - if (r == 0) - break; - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue); - break; - } - - r = unit_name_printf(u, word, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); - continue; - } - - r = unit_add_dependency_by_name(u, d, k, NULL, true); - if (r < 0) - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add dependency on %s, ignoring: %m", k); - } - - return 0; -} - -int config_parse_obsolete_unit_deps( - 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) { - - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Unit dependency type %s= is obsolete, replacing by %s=, please update your unit file", lvalue, unit_dependency_to_string(ltype)); - - return config_parse_unit_deps(unit, filename, line, section, section_line, lvalue, ltype, rvalue, data, userdata); -} - -int config_parse_unit_string_printf( - 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) { - - _cleanup_free_ char *k = NULL; - Unit *u = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(u); - - r = unit_full_printf(u, rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); - return 0; - } - - return config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); -} - -int config_parse_unit_strv_printf( - 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) { - - Unit *u = userdata; - _cleanup_free_ char *k = NULL; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(u); - - r = unit_full_printf(u, rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); - return 0; - } - - return config_parse_strv(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); -} - -int config_parse_unit_path_printf( - 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) { - - _cleanup_free_ char *k = NULL; - Unit *u = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(u); - - r = unit_full_printf(u, rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); - return 0; - } - - return config_parse_path(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); -} - -int config_parse_unit_path_strv_printf( - 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) { - - char ***x = data; - const char *word, *state; - Unit *u = userdata; - size_t l; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(u); - - FOREACH_WORD_QUOTED(word, l, rvalue, state) { - _cleanup_free_ char *k = NULL; - char t[l+1]; - - memcpy(t, word, l); - t[l] = 0; - - r = unit_full_printf(u, t, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", t); - return 0; - } - - if (!utf8_is_valid(k)) { - log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); - return 0; - } - - if (!path_is_absolute(k)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Symlink path %s is not absolute, ignoring: %m", k); - return 0; - } - - path_kill_slashes(k); - - r = strv_push(x, k); - if (r < 0) - return log_oom(); - - k = NULL; - } - if (!isempty(state)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid syntax, ignoring."); - - return 0; -} - -int config_parse_socket_listen(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) { - - _cleanup_free_ SocketPort *p = NULL; - SocketPort *tail; - Socket *s; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - s = SOCKET(data); - - if (isempty(rvalue)) { - /* An empty assignment removes all ports */ - socket_free_ports(s); - return 0; - } - - p = new0(SocketPort, 1); - if (!p) - return log_oom(); - - if (ltype != SOCKET_SOCKET) { - - p->type = ltype; - r = unit_full_printf(UNIT(s), rvalue, &p->path); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); - return 0; - } - - path_kill_slashes(p->path); - - } else if (streq(lvalue, "ListenNetlink")) { - _cleanup_free_ char *k = NULL; - - p->type = SOCKET_SOCKET; - r = unit_full_printf(UNIT(s), rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); - return 0; - } - - r = socket_address_parse_netlink(&p->address, k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse address value, ignoring: %s", rvalue); - return 0; - } - - } else { - _cleanup_free_ char *k = NULL; - - p->type = SOCKET_SOCKET; - r = unit_full_printf(UNIT(s), rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r,"Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); - return 0; - } - - r = socket_address_parse_and_warn(&p->address, k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse address value, ignoring: %s", rvalue); - return 0; - } - - if (streq(lvalue, "ListenStream")) - p->address.type = SOCK_STREAM; - else if (streq(lvalue, "ListenDatagram")) - p->address.type = SOCK_DGRAM; - else { - assert(streq(lvalue, "ListenSequentialPacket")); - p->address.type = SOCK_SEQPACKET; - } - - if (socket_address_family(&p->address) != AF_LOCAL && p->address.type == SOCK_SEQPACKET) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Address family not supported, ignoring: %s", rvalue); - return 0; - } - } - - p->fd = -1; - p->auxiliary_fds = NULL; - p->n_auxiliary_fds = 0; - p->socket = s; - - if (s->ports) { - LIST_FIND_TAIL(port, s->ports, tail); - LIST_INSERT_AFTER(port, s->ports, tail, p); - } else - LIST_PREPEND(port, s->ports, p); - p = NULL; - - return 0; -} - -int config_parse_socket_protocol(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) { - Socket *s; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - s = SOCKET(data); - - if (streq(rvalue, "udplite")) - s->socket_protocol = IPPROTO_UDPLITE; - else if (streq(rvalue, "sctp")) - s->socket_protocol = IPPROTO_SCTP; - else { - log_syntax(unit, LOG_ERR, filename, line, 0, "Socket protocol not supported, ignoring: %s", rvalue); - return 0; - } - - return 0; -} - -int config_parse_socket_bind(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) { - - Socket *s; - SocketAddressBindIPv6Only b; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - s = SOCKET(data); - - b = socket_address_bind_ipv6_only_from_string(rvalue); - if (b < 0) { - int r; - - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse bind IPv6 only value, ignoring: %s", rvalue); - return 0; - } - - s->bind_ipv6_only = r ? SOCKET_ADDRESS_IPV6_ONLY : SOCKET_ADDRESS_BOTH; - } else - s->bind_ipv6_only = b; - - return 0; -} - -int config_parse_exec_nice(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) { - - ExecContext *c = data; - int priority, r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = safe_atoi(rvalue, &priority); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse nice priority, ignoring: %s", rvalue); - return 0; - } - - if (priority < PRIO_MIN || priority >= PRIO_MAX) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Nice priority out of range, ignoring: %s", rvalue); - return 0; - } - - c->nice = priority; - c->nice_set = true; - - return 0; -} - -int config_parse_exec_oom_score_adjust(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) { - - ExecContext *c = data; - int oa, r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = safe_atoi(rvalue, &oa); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse the OOM score adjust value, ignoring: %s", rvalue); - return 0; - } - - if (oa < OOM_SCORE_ADJ_MIN || oa > OOM_SCORE_ADJ_MAX) { - log_syntax(unit, LOG_ERR, filename, line, 0, "OOM score adjust value out of range, ignoring: %s", rvalue); - return 0; - } - - c->oom_score_adjust = oa; - c->oom_score_adjust_set = true; - - return 0; -} - -int config_parse_exec( - 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) { - - ExecCommand **e = data; - const char *p; - bool semicolon; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(e); - - e += ltype; - rvalue += strspn(rvalue, WHITESPACE); - - if (isempty(rvalue)) { - /* An empty assignment resets the list */ - *e = exec_command_free_list(*e); - return 0; - } - - p = rvalue; - do { - _cleanup_free_ char *path = NULL, *firstword = NULL; - bool separate_argv0 = false, ignore = false; - _cleanup_free_ ExecCommand *nce = NULL; - _cleanup_strv_free_ char **n = NULL; - size_t nlen = 0, nbufsize = 0; - char *f; - int i; - - semicolon = false; - - r = extract_first_word_and_warn(&p, &firstword, WHITESPACE, EXTRACT_QUOTES|EXTRACT_CUNESCAPE, unit, filename, line, rvalue); - if (r <= 0) - return 0; - - f = firstword; - for (i = 0; i < 2; i++) { - /* We accept an absolute path as first argument, or - * alternatively an absolute prefixed with @ to allow - * overriding of argv[0]. */ - if (*f == '-' && !ignore) - ignore = true; - else if (*f == '@' && !separate_argv0) - separate_argv0 = true; - else - break; - f++; - } - - if (isempty(f)) { - /* First word is either "-" or "@" with no command. */ - log_syntax(unit, LOG_ERR, filename, line, 0, "Empty path in command line, ignoring: \"%s\"", rvalue); - return 0; - } - if (!string_is_safe(f)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Executable path contains special characters, ignoring: %s", rvalue); - return 0; - } - if (!path_is_absolute(f)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Executable path is not absolute, ignoring: %s", rvalue); - return 0; - } - if (endswith(f, "/")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Executable path specifies a directory, ignoring: %s", rvalue); - return 0; - } - - if (f == firstword) { - path = firstword; - firstword = NULL; - } else { - path = strdup(f); - if (!path) - return log_oom(); - } - - if (!separate_argv0) { - if (!GREEDY_REALLOC(n, nbufsize, nlen + 2)) - return log_oom(); - f = strdup(path); - if (!f) - return log_oom(); - n[nlen++] = f; - n[nlen] = NULL; - } - - path_kill_slashes(path); - - while (!isempty(p)) { - _cleanup_free_ char *word = NULL; - - /* Check explicitly for an unquoted semicolon as - * command separator token. */ - if (p[0] == ';' && (!p[1] || strchr(WHITESPACE, p[1]))) { - p++; - p += strspn(p, WHITESPACE); - semicolon = true; - break; - } - - /* Check for \; explicitly, to not confuse it with \\; - * or "\;" or "\\;" etc. extract_first_word would - * return the same for all of those. */ - if (p[0] == '\\' && p[1] == ';' && (!p[2] || strchr(WHITESPACE, p[2]))) { - p += 2; - p += strspn(p, WHITESPACE); - if (!GREEDY_REALLOC(n, nbufsize, nlen + 2)) - return log_oom(); - f = strdup(";"); - if (!f) - return log_oom(); - n[nlen++] = f; - n[nlen] = NULL; - continue; - } - - r = extract_first_word_and_warn(&p, &word, WHITESPACE, EXTRACT_QUOTES|EXTRACT_CUNESCAPE, unit, filename, line, rvalue); - if (r == 0) - break; - else if (r < 0) - return 0; - - if (!GREEDY_REALLOC(n, nbufsize, nlen + 2)) - return log_oom(); - n[nlen++] = word; - n[nlen] = NULL; - word = NULL; - } - - if (!n || !n[0]) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Empty executable name or zeroeth argument, ignoring: %s", rvalue); - return 0; - } - - nce = new0(ExecCommand, 1); - if (!nce) - return log_oom(); - - nce->argv = n; - nce->path = path; - nce->ignore = ignore; - - exec_command_append_list(e, nce); - - /* Do not _cleanup_free_ these. */ - n = NULL; - path = NULL; - nce = NULL; - - rvalue = p; - } while (semicolon); - - return 0; -} - -DEFINE_CONFIG_PARSE_ENUM(config_parse_service_type, service_type, ServiceType, "Failed to parse service type"); -DEFINE_CONFIG_PARSE_ENUM(config_parse_service_restart, service_restart, ServiceRestart, "Failed to parse service restart specifier"); - -int config_parse_socket_bindtodevice( - 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) { - - Socket *s = data; - char *n; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (rvalue[0] && !streq(rvalue, "*")) { - if (!ifname_valid(rvalue)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is invalid, ignoring: %s", rvalue); - return 0; - } - - n = strdup(rvalue); - if (!n) - return log_oom(); - } else - n = NULL; - - free(s->bind_to_device); - s->bind_to_device = n; - - return 0; -} - -DEFINE_CONFIG_PARSE_ENUM(config_parse_output, exec_output, ExecOutput, "Failed to parse output specifier"); -DEFINE_CONFIG_PARSE_ENUM(config_parse_input, exec_input, ExecInput, "Failed to parse input specifier"); - -int config_parse_exec_io_class(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) { - - ExecContext *c = data; - int x; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - x = ioprio_class_from_string(rvalue); - if (x < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse IO scheduling class, ignoring: %s", rvalue); - return 0; - } - - c->ioprio = IOPRIO_PRIO_VALUE(x, IOPRIO_PRIO_DATA(c->ioprio)); - c->ioprio_set = true; - - return 0; -} - -int config_parse_exec_io_priority(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) { - - ExecContext *c = data; - int i, r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = safe_atoi(rvalue, &i); - if (r < 0 || i < 0 || i >= IOPRIO_BE_NR) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse IO priority, ignoring: %s", rvalue); - return 0; - } - - c->ioprio = IOPRIO_PRIO_VALUE(IOPRIO_PRIO_CLASS(c->ioprio), i); - c->ioprio_set = true; - - return 0; -} - -int config_parse_exec_cpu_sched_policy(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) { - - - ExecContext *c = data; - int x; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - x = sched_policy_from_string(rvalue); - if (x < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse CPU scheduling policy, ignoring: %s", rvalue); - return 0; - } - - c->cpu_sched_policy = x; - /* Moving to or from real-time policy? We need to adjust the priority */ - c->cpu_sched_priority = CLAMP(c->cpu_sched_priority, sched_get_priority_min(x), sched_get_priority_max(x)); - c->cpu_sched_set = true; - - return 0; -} - -int config_parse_exec_cpu_sched_prio(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) { - - ExecContext *c = data; - int i, min, max, r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = safe_atoi(rvalue, &i); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CPU scheduling policy, ignoring: %s", rvalue); - return 0; - } - - /* On Linux RR/FIFO range from 1 to 99 and OTHER/BATCH may only be 0 */ - min = sched_get_priority_min(c->cpu_sched_policy); - max = sched_get_priority_max(c->cpu_sched_policy); - - if (i < min || i > max) { - log_syntax(unit, LOG_ERR, filename, line, 0, "CPU scheduling priority is out of range, ignoring: %s", rvalue); - return 0; - } - - c->cpu_sched_priority = i; - c->cpu_sched_set = true; - - return 0; -} - -int config_parse_exec_cpu_affinity(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) { - - ExecContext *c = data; - _cleanup_cpu_free_ cpu_set_t *cpuset = NULL; - int ncpus; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - ncpus = parse_cpu_set_and_warn(rvalue, &cpuset, unit, filename, line, lvalue); - if (ncpus < 0) - return ncpus; - - if (c->cpuset) - CPU_FREE(c->cpuset); - - if (ncpus == 0) - /* An empty assignment resets the CPU list */ - c->cpuset = NULL; - else { - c->cpuset = cpuset; - cpuset = NULL; - } - c->cpuset_ncpus = ncpus; - - return 0; -} - -int config_parse_exec_secure_bits(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) { - - ExecContext *c = data; - size_t l; - const char *word, *state; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* An empty assignment resets the field */ - c->secure_bits = 0; - return 0; - } - - FOREACH_WORD_QUOTED(word, l, rvalue, state) { - if (first_word(word, "keep-caps")) - c->secure_bits |= 1<secure_bits |= 1<secure_bits |= 1<secure_bits |= 1<secure_bits |= 1<secure_bits |= 1< replace */ - *capability_set = sum; - else - /* previous data -> merge */ - *capability_set |= sum; - - return 0; -} - -int config_parse_limit( - 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) { - - struct rlimit **rl = data, d = {}; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = rlimit_parse(ltype, rvalue, &d); - if (r == -EILSEQ) { - log_syntax(unit, LOG_WARNING, filename, line, r, "Soft resource limit chosen higher than hard limit, ignoring: %s", rvalue); - return 0; - } - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", rvalue); - return 0; - } - - if (rl[ltype]) - *rl[ltype] = d; - else { - rl[ltype] = newdup(struct rlimit, &d, 1); - if (!rl[ltype]) - return log_oom(); - } - - return 0; -} - -#ifdef HAVE_SYSV_COMPAT -int config_parse_sysv_priority(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) { - - int *priority = data; - int i, r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = safe_atoi(rvalue, &i); - if (r < 0 || i < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse SysV start priority, ignoring: %s", rvalue); - return 0; - } - - *priority = (int) i; - return 0; -} -#endif - -DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_utmp_mode, exec_utmp_mode, ExecUtmpMode, "Failed to parse utmp mode"); -DEFINE_CONFIG_PARSE_ENUM(config_parse_kill_mode, kill_mode, KillMode, "Failed to parse kill mode"); - -int config_parse_exec_mount_flags(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 long flags = 0; - ExecContext *c = data; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (streq(rvalue, "shared")) - flags = MS_SHARED; - else if (streq(rvalue, "slave")) - flags = MS_SLAVE; - else if (streq(rvalue, "private")) - flags = MS_PRIVATE; - else { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse mount flag %s, ignoring.", rvalue); - return 0; - } - - c->mount_flags = flags; - - return 0; -} - -int config_parse_exec_selinux_context( - 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) { - - ExecContext *c = data; - Unit *u = userdata; - bool ignore; - char *k; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - c->selinux_context = mfree(c->selinux_context); - c->selinux_context_ignore = false; - return 0; - } - - if (rvalue[0] == '-') { - ignore = true; - rvalue++; - } else - ignore = false; - - r = unit_name_printf(u, rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); - return 0; - } - - free(c->selinux_context); - c->selinux_context = k; - c->selinux_context_ignore = ignore; - - return 0; -} - -int config_parse_exec_apparmor_profile( - 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) { - - ExecContext *c = data; - Unit *u = userdata; - bool ignore; - char *k; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - c->apparmor_profile = mfree(c->apparmor_profile); - c->apparmor_profile_ignore = false; - return 0; - } - - if (rvalue[0] == '-') { - ignore = true; - rvalue++; - } else - ignore = false; - - r = unit_name_printf(u, rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); - return 0; - } - - free(c->apparmor_profile); - c->apparmor_profile = k; - c->apparmor_profile_ignore = ignore; - - return 0; -} - -int config_parse_exec_smack_process_label( - 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) { - - ExecContext *c = data; - Unit *u = userdata; - bool ignore; - char *k; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - c->smack_process_label = mfree(c->smack_process_label); - c->smack_process_label_ignore = false; - return 0; - } - - if (rvalue[0] == '-') { - ignore = true; - rvalue++; - } else - ignore = false; - - r = unit_name_printf(u, rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); - return 0; - } - - free(c->smack_process_label); - c->smack_process_label = k; - c->smack_process_label_ignore = ignore; - - return 0; -} - -int config_parse_timer(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) { - - Timer *t = data; - usec_t u = 0; - TimerValue *v; - TimerBase b; - CalendarSpec *c = NULL; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment resets list */ - timer_free_values(t); - return 0; - } - - b = timer_base_from_string(lvalue); - if (b < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse timer base, ignoring: %s", lvalue); - return 0; - } - - if (b == TIMER_CALENDAR) { - if (calendar_spec_from_string(rvalue, &c) < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse calendar specification, ignoring: %s", rvalue); - return 0; - } - } else { - if (parse_sec(rvalue, &u) < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse timer value, ignoring: %s", rvalue); - return 0; - } - } - - v = new0(TimerValue, 1); - if (!v) { - calendar_spec_free(c); - return log_oom(); - } - - v->base = b; - v->value = u; - v->calendar_spec = c; - - LIST_PREPEND(value, t->values, v); - - return 0; -} - -int config_parse_trigger_unit( - 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) { - - _cleanup_free_ char *p = NULL; - Unit *u = data; - UnitType type; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (!set_isempty(u->dependencies[UNIT_TRIGGERS])) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Multiple units to trigger specified, ignoring: %s", rvalue); - return 0; - } - - r = unit_name_printf(u, rvalue, &p); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); - return 0; - } - - type = unit_name_to_type(p); - if (type < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Unit type not valid, ignoring: %s", rvalue); - return 0; - } - - if (type == u->type) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Trigger cannot be of same type, ignoring: %s", rvalue); - return 0; - } - - r = unit_add_two_dependencies_by_name(u, UNIT_BEFORE, UNIT_TRIGGERS, p, NULL, true); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add trigger on %s, ignoring: %m", p); - return 0; - } - - return 0; -} - -int config_parse_path_spec(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) { - - Path *p = data; - PathSpec *s; - PathType b; - _cleanup_free_ char *k = NULL; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment clears list */ - path_free_specs(p); - return 0; - } - - b = path_type_from_string(lvalue); - if (b < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse path type, ignoring: %s", lvalue); - return 0; - } - - r = unit_full_printf(UNIT(p), rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s. Ignoring.", rvalue); - return 0; - } - - if (!path_is_absolute(k)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Path is not absolute, ignoring: %s", k); - return 0; - } - - s = new0(PathSpec, 1); - if (!s) - return log_oom(); - - s->unit = UNIT(p); - s->path = path_kill_slashes(k); - k = NULL; - s->type = b; - s->inotify_fd = -1; - - LIST_PREPEND(spec, p->specs, s); - - return 0; -} - -int config_parse_socket_service( - 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) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *p = NULL; - Socket *s = data; - Unit *x; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = unit_name_printf(UNIT(s), rvalue, &p); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); - return 0; - } - - if (!endswith(p, ".service")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Unit must be of type service, ignoring: %s", rvalue); - return 0; - } - - r = manager_load_unit(UNIT(s)->manager, p, NULL, &error, &x); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to load unit %s, ignoring: %s", rvalue, bus_error_message(&error, r)); - return 0; - } - - unit_ref_set(&s->service, x); - - return 0; -} - -int config_parse_fdname( - 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) { - - _cleanup_free_ char *p = NULL; - Socket *s = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - s->fdname = mfree(s->fdname); - return 0; - } - - r = unit_name_printf(UNIT(s), rvalue, &p); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); - return 0; - } - - if (!fdname_is_valid(p)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid file descriptor name, ignoring: %s", p); - return 0; - } - - free(s->fdname); - s->fdname = p; - p = NULL; - - return 0; -} - -int config_parse_service_sockets( - 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) { - - Service *s = data; - const char *p; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - p = rvalue; - for (;;) { - _cleanup_free_ char *word = NULL, *k = NULL; - - r = extract_first_word(&p, &word, NULL, 0); - if (r == 0) - break; - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Trailing garbage in sockets, ignoring: %s", rvalue); - break; - } - - r = unit_name_printf(UNIT(s), word, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); - continue; - } - - if (!endswith(k, ".socket")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Unit must be of type socket, ignoring: %s", k); - continue; - } - - r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_WANTS, UNIT_AFTER, k, NULL, true); - if (r < 0) - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add dependency on %s, ignoring: %m", k); - - r = unit_add_dependency_by_name(UNIT(s), UNIT_TRIGGERED_BY, k, NULL, true); - if (r < 0) - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add dependency on %s, ignoring: %m", k); - } - - return 0; -} - -int config_parse_bus_name( - 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) { - - _cleanup_free_ char *k = NULL; - Unit *u = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(u); - - r = unit_full_printf(u, rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); - return 0; - } - - if (!service_name_is_valid(k)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid bus name %s, ignoring.", k); - return 0; - } - - return config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); -} - -int config_parse_service_timeout( - 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) { - - Service *s = userdata; - usec_t usec; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(s); - - /* This is called for three cases: TimeoutSec=, TimeoutStopSec= and TimeoutStartSec=. */ - - r = parse_sec(rvalue, &usec); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s= parameter, ignoring: %s", lvalue, rvalue); - return 0; - } - - /* Traditionally, these options accepted 0 to disable the timeouts. However, a timeout of 0 suggests it happens - * immediately, hence fix this to become USEC_INFINITY instead. This is in-line with how we internally handle - * all other timeouts. */ - if (usec <= 0) - usec = USEC_INFINITY; - - if (!streq(lvalue, "TimeoutStopSec")) { - s->start_timeout_defined = true; - s->timeout_start_usec = usec; - } - - if (!streq(lvalue, "TimeoutStartSec")) - s->timeout_stop_usec = usec; - - return 0; -} - -int config_parse_sec_fix_0( - 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) { - - usec_t *usec = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(usec); - - /* This is pretty much like config_parse_sec(), except that this treats a time of 0 as infinity, for - * compatibility with older versions of systemd where 0 instead of infinity was used as indicator to turn off a - * timeout. */ - - r = parse_sec(rvalue, usec); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s= parameter, ignoring: %s", lvalue, rvalue); - return 0; - } - - if (*usec <= 0) - *usec = USEC_INFINITY; - - return 0; -} - -int config_parse_busname_service( - 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) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - BusName *n = data; - int r; - Unit *x; - _cleanup_free_ char *p = NULL; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = unit_name_printf(UNIT(n), rvalue, &p); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); - return 0; - } - - if (!endswith(p, ".service")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Unit must be of type service, ignoring: %s", rvalue); - return 0; - } - - r = manager_load_unit(UNIT(n)->manager, p, NULL, &error, &x); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to load unit %s, ignoring: %s", rvalue, bus_error_message(&error, r)); - return 0; - } - - unit_ref_set(&n->service, x); - - return 0; -} - -DEFINE_CONFIG_PARSE_ENUM(config_parse_bus_policy_world, bus_policy_access, BusPolicyAccess, "Failed to parse bus name policy access"); - -int config_parse_bus_policy( - 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) { - - _cleanup_free_ BusNamePolicy *p = NULL; - _cleanup_free_ char *id_str = NULL; - BusName *busname = data; - char *access_str; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - p = new0(BusNamePolicy, 1); - if (!p) - return log_oom(); - - if (streq(lvalue, "AllowUser")) - p->type = BUSNAME_POLICY_TYPE_USER; - else if (streq(lvalue, "AllowGroup")) - p->type = BUSNAME_POLICY_TYPE_GROUP; - else - assert_not_reached("Unknown lvalue"); - - id_str = strdup(rvalue); - if (!id_str) - return log_oom(); - - access_str = strpbrk(id_str, WHITESPACE); - if (!access_str) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid busname policy value '%s'", rvalue); - return 0; - } - - *access_str = '\0'; - access_str++; - access_str += strspn(access_str, WHITESPACE); - - p->access = bus_policy_access_from_string(access_str); - if (p->access < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid busname policy access type '%s'", access_str); - return 0; - } - - p->name = id_str; - id_str = NULL; - - LIST_PREPEND(policy, busname->policy, p); - p = NULL; - - return 0; -} - -int config_parse_working_directory( - 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) { - - ExecContext *c = data; - Unit *u = userdata; - bool missing_ok; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(c); - assert(u); - - if (rvalue[0] == '-') { - missing_ok = true; - rvalue++; - } else - missing_ok = false; - - if (streq(rvalue, "~")) { - c->working_directory_home = true; - c->working_directory = mfree(c->working_directory); - } else { - _cleanup_free_ char *k = NULL; - - r = unit_full_printf(u, rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in working directory path '%s', ignoring: %m", rvalue); - return 0; - } - - path_kill_slashes(k); - - if (!utf8_is_valid(k)) { - log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); - return 0; - } - - if (!path_is_absolute(k)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Working directory path '%s' is not absolute, ignoring.", rvalue); - return 0; - } - - free(c->working_directory); - c->working_directory = k; - k = NULL; - - c->working_directory_home = false; - } - - c->working_directory_missing_ok = missing_ok; - return 0; -} - -int config_parse_unit_env_file(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) { - - char ***env = data; - Unit *u = userdata; - _cleanup_free_ char *n = NULL; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment frees the list */ - *env = strv_free(*env); - return 0; - } - - r = unit_full_printf(u, rvalue, &n); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); - return 0; - } - - if (!path_is_absolute(n[0] == '-' ? n + 1 : n)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Path '%s' is not absolute, ignoring.", n); - return 0; - } - - r = strv_extend(env, n); - if (r < 0) - return log_oom(); - - return 0; -} - -int config_parse_environ(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) { - - Unit *u = userdata; - char*** env = data; - const char *word, *state; - size_t l; - _cleanup_free_ char *k = NULL; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - *env = strv_free(*env); - return 0; - } - - if (u) { - r = unit_full_printf(u, rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); - return 0; - } - } - - if (!k) { - k = strdup(rvalue); - if (!k) - return log_oom(); - } - - FOREACH_WORD_QUOTED(word, l, k, state) { - _cleanup_free_ char *n = NULL; - char **x; - - r = cunescape_length(word, l, 0, &n); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Couldn't unescape assignment, ignoring: %s", rvalue); - continue; - } - - if (!env_assignment_is_valid(n)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid environment assignment, ignoring: %s", rvalue); - continue; - } - - x = strv_env_set(*env, n); - if (!x) - return log_oom(); - - strv_free(*env); - *env = x; - } - if (!isempty(state)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); - - return 0; -} - -int config_parse_pass_environ(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) { - - const char *whole_rvalue = rvalue; - char*** passenv = data; - _cleanup_strv_free_ char **n = NULL; - size_t nlen = 0, nbufsize = 0; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - *passenv = strv_free(*passenv); - return 0; - } - - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&rvalue, &word, WHITESPACE, EXTRACT_QUOTES); - if (r == 0) - break; - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, - "Trailing garbage in %s, ignoring: %s", lvalue, whole_rvalue); - break; - } - - if (!env_name_is_valid(word)) { - log_syntax(unit, LOG_ERR, filename, line, EINVAL, - "Invalid environment name for %s, ignoring: %s", lvalue, word); - continue; - } - - if (!GREEDY_REALLOC(n, nbufsize, nlen + 2)) - return log_oom(); - n[nlen++] = word; - n[nlen] = NULL; - word = NULL; - } - - if (n) { - r = strv_extend_strv(passenv, n, true); - if (r < 0) - return r; - } - - return 0; -} - -int config_parse_ip_tos(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) { - - int *ip_tos = data, x; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - x = ip_tos_from_string(rvalue); - if (x < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse IP TOS value, ignoring: %s", rvalue); - return 0; - } - - *ip_tos = x; - return 0; -} - -int config_parse_unit_condition_path( - 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) { - - _cleanup_free_ char *p = NULL; - Condition **list = data, *c; - ConditionType t = ltype; - bool trigger, negate; - Unit *u = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - *list = condition_free_list(*list); - return 0; - } - - trigger = rvalue[0] == '|'; - if (trigger) - rvalue++; - - negate = rvalue[0] == '!'; - if (negate) - rvalue++; - - r = unit_full_printf(u, rvalue, &p); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); - return 0; - } - - if (!path_is_absolute(p)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Path in condition not absolute, ignoring: %s", p); - return 0; - } - - c = condition_new(t, p, trigger, negate); - if (!c) - return log_oom(); - - LIST_PREPEND(conditions, *list, c); - return 0; -} - -int config_parse_unit_condition_string( - 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) { - - _cleanup_free_ char *s = NULL; - Condition **list = data, *c; - ConditionType t = ltype; - bool trigger, negate; - Unit *u = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - *list = condition_free_list(*list); - return 0; - } - - trigger = rvalue[0] == '|'; - if (trigger) - rvalue++; - - negate = rvalue[0] == '!'; - if (negate) - rvalue++; - - r = unit_full_printf(u, rvalue, &s); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); - return 0; - } - - c = condition_new(t, s, trigger, negate); - if (!c) - return log_oom(); - - LIST_PREPEND(conditions, *list, c); - return 0; -} - -int config_parse_unit_condition_null( - 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) { - - Condition **list = data, *c; - bool trigger, negate; - int b; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - *list = condition_free_list(*list); - return 0; - } - - trigger = rvalue[0] == '|'; - if (trigger) - rvalue++; - - negate = rvalue[0] == '!'; - if (negate) - rvalue++; - - b = parse_boolean(rvalue); - if (b < 0) { - log_syntax(unit, LOG_ERR, filename, line, b, "Failed to parse boolean value in condition, ignoring: %s", rvalue); - return 0; - } - - if (!b) - negate = !negate; - - c = condition_new(CONDITION_NULL, NULL, trigger, negate); - if (!c) - return log_oom(); - - LIST_PREPEND(conditions, *list, c); - return 0; -} - -DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess, "Failed to parse notify access specifier"); -DEFINE_CONFIG_PARSE_ENUM(config_parse_failure_action, failure_action, FailureAction, "Failed to parse failure action specifier"); - -int config_parse_unit_requires_mounts_for( - 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) { - - Unit *u = userdata; - const char *word, *state; - size_t l; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - FOREACH_WORD_QUOTED(word, l, rvalue, state) { - int r; - _cleanup_free_ char *n; - - n = strndup(word, l); - if (!n) - return log_oom(); - - if (!utf8_is_valid(n)) { - log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); - continue; - } - - r = unit_require_mounts_for(u, n); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add required mount for, ignoring: %s", rvalue); - continue; - } - } - if (!isempty(state)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); - - return 0; -} - -int config_parse_documentation(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) { - - Unit *u = userdata; - int r; - char **a, **b; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(u); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - u->documentation = strv_free(u->documentation); - return 0; - } - - r = config_parse_unit_strv_printf(unit, filename, line, section, section_line, lvalue, ltype, - rvalue, data, userdata); - if (r < 0) - return r; - - for (a = b = u->documentation; a && *a; a++) { - - if (documentation_url_is_valid(*a)) - *(b++) = *a; - else { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid URL, ignoring: %s", *a); - free(*a); - } - } - if (b) - *b = NULL; - - return r; -} - -#ifdef HAVE_SECCOMP -int config_parse_syscall_filter( - 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) { - - static const char default_syscalls[] = - "execve\0" - "exit\0" - "exit_group\0" - "rt_sigreturn\0" - "sigreturn\0"; - - ExecContext *c = data; - Unit *u = userdata; - bool invert = false; - const char *word, *state; - size_t l; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(u); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - c->syscall_filter = set_free(c->syscall_filter); - c->syscall_whitelist = false; - return 0; - } - - if (rvalue[0] == '~') { - invert = true; - rvalue++; - } - - if (!c->syscall_filter) { - c->syscall_filter = set_new(NULL); - if (!c->syscall_filter) - return log_oom(); - - if (invert) - /* Allow everything but the ones listed */ - c->syscall_whitelist = false; - else { - const char *i; - - /* Allow nothing but the ones listed */ - c->syscall_whitelist = true; - - /* Accept default syscalls if we are on a whitelist */ - NULSTR_FOREACH(i, default_syscalls) { - int id; - - id = seccomp_syscall_resolve_name(i); - if (id < 0) - continue; - - r = set_put(c->syscall_filter, INT_TO_PTR(id + 1)); - if (r == 0) - continue; - if (r < 0) - return log_oom(); - } - } - } - - FOREACH_WORD_QUOTED(word, l, rvalue, state) { - _cleanup_free_ char *t = NULL; - int id; - - t = strndup(word, l); - if (!t) - return log_oom(); - - id = seccomp_syscall_resolve_name(t); - if (id < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse system call, ignoring: %s", t); - continue; - } - - /* If we previously wanted to forbid a syscall and now - * we want to allow it, then remove it from the list - */ - if (!invert == c->syscall_whitelist) { - r = set_put(c->syscall_filter, INT_TO_PTR(id + 1)); - if (r == 0) - continue; - if (r < 0) - return log_oom(); - } else - set_remove(c->syscall_filter, INT_TO_PTR(id + 1)); - } - if (!isempty(state)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); - - /* Turn on NNP, but only if it wasn't configured explicitly - * before, and only if we are in user mode. */ - if (!c->no_new_privileges_set && MANAGER_IS_USER(u->manager)) - c->no_new_privileges = true; - - return 0; -} - -int config_parse_syscall_archs( - 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) { - - Set **archs = data; - const char *word, *state; - size_t l; - int r; - - if (isempty(rvalue)) { - *archs = set_free(*archs); - return 0; - } - - r = set_ensure_allocated(archs, NULL); - if (r < 0) - return log_oom(); - - FOREACH_WORD_QUOTED(word, l, rvalue, state) { - _cleanup_free_ char *t = NULL; - uint32_t a; - - t = strndup(word, l); - if (!t) - return log_oom(); - - r = seccomp_arch_from_string(t, &a); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse system call architecture, ignoring: %s", t); - continue; - } - - r = set_put(*archs, UINT32_TO_PTR(a + 1)); - if (r == 0) - continue; - if (r < 0) - return log_oom(); - } - if (!isempty(state)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); - - return 0; -} - -int config_parse_syscall_errno( - 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) { - - ExecContext *c = data; - int e; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - /* Empty assignment resets to KILL */ - c->syscall_errno = 0; - return 0; - } - - e = errno_from_name(rvalue); - if (e < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse error number, ignoring: %s", rvalue); - return 0; - } - - c->syscall_errno = e; - return 0; -} - -int config_parse_address_families( - 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) { - - ExecContext *c = data; - bool invert = false; - const char *word, *state; - size_t l; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - c->address_families = set_free(c->address_families); - c->address_families_whitelist = false; - return 0; - } - - if (rvalue[0] == '~') { - invert = true; - rvalue++; - } - - if (!c->address_families) { - c->address_families = set_new(NULL); - if (!c->address_families) - return log_oom(); - - c->address_families_whitelist = !invert; - } - - FOREACH_WORD_QUOTED(word, l, rvalue, state) { - _cleanup_free_ char *t = NULL; - int af; - - t = strndup(word, l); - if (!t) - return log_oom(); - - af = af_from_name(t); - if (af <= 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse address family, ignoring: %s", t); - continue; - } - - /* If we previously wanted to forbid an address family and now - * we want to allow it, then remove it from the list - */ - if (!invert == c->address_families_whitelist) { - r = set_put(c->address_families, INT_TO_PTR(af)); - if (r == 0) - continue; - if (r < 0) - return log_oom(); - } else - set_remove(c->address_families, INT_TO_PTR(af)); - } - if (!isempty(state)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); - - return 0; -} -#endif - -int config_parse_unit_slice( - 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) { - - _cleanup_free_ char *k = NULL; - Unit *u = userdata, *slice = NULL; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(u); - - r = unit_name_printf(u, rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s. Ignoring.", rvalue); - return 0; - } - - r = manager_load_unit(u->manager, k, NULL, NULL, &slice); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to load slice unit %s. Ignoring.", k); - return 0; - } - - r = unit_set_slice(u, slice); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to assign slice %s to unit %s. Ignoring.", slice->id, u->id); - return 0; - } - - return 0; -} - -DEFINE_CONFIG_PARSE_ENUM(config_parse_device_policy, cgroup_device_policy, CGroupDevicePolicy, "Failed to parse device policy"); - -int config_parse_cpu_shares( - 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) { - - uint64_t *shares = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = cg_cpu_shares_parse(rvalue, shares); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "CPU shares '%s' invalid. Ignoring.", rvalue); - return 0; - } - - return 0; -} - -int config_parse_cpu_quota( - 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) { - - CGroupContext *c = data; - double percent; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - c->cpu_quota_per_sec_usec = USEC_INFINITY; - return 0; - } - - if (!endswith(rvalue, "%")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "CPU quota '%s' not ending in '%%'. Ignoring.", rvalue); - return 0; - } - - if (sscanf(rvalue, "%lf%%", &percent) != 1 || percent <= 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "CPU quota '%s' invalid. Ignoring.", rvalue); - return 0; - } - - c->cpu_quota_per_sec_usec = (usec_t) (percent * USEC_PER_SEC / 100); - - return 0; -} - -int config_parse_memory_limit( - 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) { - - CGroupContext *c = data; - uint64_t bytes; - int r; - - if (isempty(rvalue) || streq(rvalue, "infinity")) { - c->memory_limit = (uint64_t) -1; - return 0; - } - - r = parse_size(rvalue, 1024, &bytes); - if (r < 0 || bytes < 1) { - log_syntax(unit, LOG_ERR, filename, line, r, "Memory limit '%s' invalid. Ignoring.", rvalue); - return 0; - } - - c->memory_limit = bytes; - return 0; -} - -int config_parse_tasks_max( - 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) { - - uint64_t *tasks_max = data, u; - int r; - - if (isempty(rvalue) || streq(rvalue, "infinity")) { - *tasks_max = (uint64_t) -1; - return 0; - } - - r = safe_atou64(rvalue, &u); - if (r < 0 || u < 1) { - log_syntax(unit, LOG_ERR, filename, line, r, "Maximum tasks value '%s' invalid. Ignoring.", rvalue); - return 0; - } - - *tasks_max = u; - return 0; -} - -int config_parse_device_allow( - 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) { - - _cleanup_free_ char *path = NULL, *t = NULL; - CGroupContext *c = data; - CGroupDeviceAllow *a; - const char *m = NULL; - size_t n; - int r; - - if (isempty(rvalue)) { - while (c->device_allow) - cgroup_context_free_device_allow(c, c->device_allow); - - return 0; - } - - r = unit_full_printf(userdata, rvalue, &t); - if(r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to resolve specifiers in %s, ignoring: %m", - rvalue); - } - - n = strcspn(t, WHITESPACE); - - path = strndup(t, n); - if (!path) - return log_oom(); - - if (!startswith(path, "/dev/") && - !startswith(path, "block-") && - !startswith(path, "char-")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device node path '%s'. Ignoring.", path); - return 0; - } - - m = t + n + strspn(t + n, WHITESPACE); - if (isempty(m)) - m = "rwm"; - - if (!in_charset(m, "rwm")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device rights '%s'. Ignoring.", m); - return 0; - } - - a = new0(CGroupDeviceAllow, 1); - if (!a) - return log_oom(); - - a->path = path; - path = NULL; - a->r = !!strchr(m, 'r'); - a->w = !!strchr(m, 'w'); - a->m = !!strchr(m, 'm'); - - LIST_PREPEND(device_allow, c->device_allow, a); - return 0; -} - -int config_parse_io_weight( - 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) { - - uint64_t *weight = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = cg_weight_parse(rvalue, weight); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "IO weight '%s' invalid. Ignoring.", rvalue); - return 0; - } - - return 0; -} - -int config_parse_io_device_weight( - 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) { - - _cleanup_free_ char *path = NULL; - CGroupIODeviceWeight *w; - CGroupContext *c = data; - const char *weight; - uint64_t u; - size_t n; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - while (c->io_device_weights) - cgroup_context_free_io_device_weight(c, c->io_device_weights); - - return 0; - } - - n = strcspn(rvalue, WHITESPACE); - weight = rvalue + n; - weight += strspn(weight, WHITESPACE); - - if (isempty(weight)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Expected block device and device weight. Ignoring."); - return 0; - } - - path = strndup(rvalue, n); - if (!path) - return log_oom(); - - if (!path_startswith(path, "/dev")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device node path '%s'. Ignoring.", path); - return 0; - } - - r = cg_weight_parse(weight, &u); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "IO weight '%s' invalid. Ignoring.", weight); - return 0; - } - - assert(u != CGROUP_WEIGHT_INVALID); - - w = new0(CGroupIODeviceWeight, 1); - if (!w) - return log_oom(); - - w->path = path; - path = NULL; - - w->weight = u; - - LIST_PREPEND(device_weights, c->io_device_weights, w); - return 0; -} - -int config_parse_io_limit( - 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) { - - _cleanup_free_ char *path = NULL; - CGroupIODeviceLimit *l = NULL, *t; - CGroupContext *c = data; - CGroupIOLimitType type; - const char *limit; - uint64_t num; - size_t n; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - type = cgroup_io_limit_type_from_string(lvalue); - assert(type >= 0); - - if (isempty(rvalue)) { - LIST_FOREACH(device_limits, l, c->io_device_limits) - l->limits[type] = cgroup_io_limit_defaults[type]; - return 0; - } - - n = strcspn(rvalue, WHITESPACE); - limit = rvalue + n; - limit += strspn(limit, WHITESPACE); - - if (!*limit) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Expected space separated pair of device node and bandwidth. Ignoring."); - return 0; - } - - path = strndup(rvalue, n); - if (!path) - return log_oom(); - - if (!path_startswith(path, "/dev")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device node path '%s'. Ignoring.", path); - return 0; - } - - if (streq("max", limit)) { - num = CGROUP_LIMIT_MAX; - } else { - r = parse_size(limit, 1000, &num); - if (r < 0 || num <= 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "IO Limit '%s' invalid. Ignoring.", rvalue); - return 0; - } - } - - LIST_FOREACH(device_limits, t, c->io_device_limits) { - if (path_equal(path, t->path)) { - l = t; - break; - } - } - - if (!l) { - CGroupIOLimitType ttype; - - l = new0(CGroupIODeviceLimit, 1); - if (!l) - return log_oom(); - - l->path = path; - path = NULL; - for (ttype = 0; ttype < _CGROUP_IO_LIMIT_TYPE_MAX; ttype++) - l->limits[ttype] = cgroup_io_limit_defaults[ttype]; - - LIST_PREPEND(device_limits, c->io_device_limits, l); - } - - l->limits[type] = num; - - return 0; -} - -int config_parse_blockio_weight( - 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) { - - uint64_t *weight = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = cg_blkio_weight_parse(rvalue, weight); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Block IO weight '%s' invalid. Ignoring.", rvalue); - return 0; - } - - return 0; -} - -int config_parse_blockio_device_weight( - 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) { - - _cleanup_free_ char *path = NULL; - CGroupBlockIODeviceWeight *w; - CGroupContext *c = data; - const char *weight; - uint64_t u; - size_t n; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - while (c->blockio_device_weights) - cgroup_context_free_blockio_device_weight(c, c->blockio_device_weights); - - return 0; - } - - n = strcspn(rvalue, WHITESPACE); - weight = rvalue + n; - weight += strspn(weight, WHITESPACE); - - if (isempty(weight)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Expected block device and device weight. Ignoring."); - return 0; - } - - path = strndup(rvalue, n); - if (!path) - return log_oom(); - - if (!path_startswith(path, "/dev")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device node path '%s'. Ignoring.", path); - return 0; - } - - r = cg_blkio_weight_parse(weight, &u); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Block IO weight '%s' invalid. Ignoring.", weight); - return 0; - } - - assert(u != CGROUP_BLKIO_WEIGHT_INVALID); - - w = new0(CGroupBlockIODeviceWeight, 1); - if (!w) - return log_oom(); - - w->path = path; - path = NULL; - - w->weight = u; - - LIST_PREPEND(device_weights, c->blockio_device_weights, w); - return 0; -} - -int config_parse_blockio_bandwidth( - 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) { - - _cleanup_free_ char *path = NULL; - CGroupBlockIODeviceBandwidth *b = NULL, *t; - CGroupContext *c = data; - const char *bandwidth; - uint64_t bytes; - bool read; - size_t n; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - read = streq("BlockIOReadBandwidth", lvalue); - - if (isempty(rvalue)) { - LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { - b->rbps = CGROUP_LIMIT_MAX; - b->wbps = CGROUP_LIMIT_MAX; - } - return 0; - } - - n = strcspn(rvalue, WHITESPACE); - bandwidth = rvalue + n; - bandwidth += strspn(bandwidth, WHITESPACE); - - if (!*bandwidth) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Expected space separated pair of device node and bandwidth. Ignoring."); - return 0; - } - - path = strndup(rvalue, n); - if (!path) - return log_oom(); - - if (!path_startswith(path, "/dev")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device node path '%s'. Ignoring.", path); - return 0; - } - - r = parse_size(bandwidth, 1000, &bytes); - if (r < 0 || bytes <= 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Block IO Bandwidth '%s' invalid. Ignoring.", rvalue); - return 0; - } - - LIST_FOREACH(device_bandwidths, t, c->blockio_device_bandwidths) { - if (path_equal(path, t->path)) { - b = t; - break; - } - } - - if (!t) { - b = new0(CGroupBlockIODeviceBandwidth, 1); - if (!b) - return log_oom(); - - b->path = path; - path = NULL; - b->rbps = CGROUP_LIMIT_MAX; - b->wbps = CGROUP_LIMIT_MAX; - - LIST_PREPEND(device_bandwidths, c->blockio_device_bandwidths, b); - } - - if (read) - b->rbps = bytes; - else - b->wbps = bytes; - - return 0; -} - -DEFINE_CONFIG_PARSE_ENUM(config_parse_job_mode, job_mode, JobMode, "Failed to parse job mode"); - -int config_parse_job_mode_isolate( - 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) { - - JobMode *m = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse boolean, ignoring: %s", rvalue); - return 0; - } - - *m = r ? JOB_ISOLATE : JOB_REPLACE; - return 0; -} - -int config_parse_runtime_directory( - 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) { - - char***rt = data; - Unit *u = userdata; - const char *word, *state; - size_t l; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - *rt = strv_free(*rt); - return 0; - } - - FOREACH_WORD_QUOTED(word, l, rvalue, state) { - _cleanup_free_ char *t = NULL, *n = NULL; - - t = strndup(word, l); - if (!t) - return log_oom(); - - r = unit_name_printf(u, t, &n); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); - continue; - } - - if (!filename_is_valid(n)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Runtime directory is not valid, ignoring assignment: %s", rvalue); - continue; - } - - r = strv_push(rt, n); - if (r < 0) - return log_oom(); - - n = NULL; - } - if (!isempty(state)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); - - return 0; -} - -int config_parse_set_status( - 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) { - - size_t l; - const char *word, *state; - int r; - ExitStatusSet *status_set = data; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - /* Empty assignment resets the list */ - if (isempty(rvalue)) { - exit_status_set_free(status_set); - return 0; - } - - FOREACH_WORD(word, l, rvalue, state) { - _cleanup_free_ char *temp; - int val; - Set **set; - - temp = strndup(word, l); - if (!temp) - return log_oom(); - - r = safe_atoi(temp, &val); - if (r < 0) { - val = signal_from_string_try_harder(temp); - - if (val <= 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse value, ignoring: %s", word); - continue; - } - set = &status_set->signal; - } else { - if (val < 0 || val > 255) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Value %d is outside range 0-255, ignoring", val); - continue; - } - set = &status_set->status; - } - - r = set_ensure_allocated(set, NULL); - if (r < 0) - return log_oom(); - - r = set_put(*set, INT_TO_PTR(val)); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Unable to store: %s", word); - return r; - } - } - if (!isempty(state)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); - - return 0; -} - -int config_parse_namespace_path_strv( - 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) { - - char*** sv = data; - const char *prev; - const char *cur; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - *sv = strv_free(*sv); - return 0; - } - - prev = cur = rvalue; - for (;;) { - _cleanup_free_ char *word = NULL; - int offset; - - r = extract_first_word(&cur, &word, NULL, EXTRACT_QUOTES); - if (r == 0) - break; - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Trailing garbage, ignoring: %s", prev); - return 0; - } - - if (!utf8_is_valid(word)) { - log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, word); - prev = cur; - continue; - } - - offset = word[0] == '-'; - if (!path_is_absolute(word + offset)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute path, ignoring: %s", word); - prev = cur; - continue; - } - - path_kill_slashes(word + offset); - - r = strv_push(sv, word); - if (r < 0) - return log_oom(); - - prev = cur; - word = NULL; - } - - return 0; -} - -int config_parse_no_new_privileges( - 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) { - - ExecContext *c = data; - int k; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - k = parse_boolean(rvalue); - if (k < 0) { - log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse boolean value, ignoring: %s", rvalue); - return 0; - } - - c->no_new_privileges = !!k; - c->no_new_privileges_set = true; - - return 0; -} - -int config_parse_protect_home( - 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) { - - ExecContext *c = data; - int k; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - /* Our enum shall be a superset of booleans, hence first try - * to parse as as boolean, and then as enum */ - - k = parse_boolean(rvalue); - if (k > 0) - c->protect_home = PROTECT_HOME_YES; - else if (k == 0) - c->protect_home = PROTECT_HOME_NO; - else { - ProtectHome h; - - h = protect_home_from_string(rvalue); - if (h < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse protect home value, ignoring: %s", rvalue); - return 0; - } - - c->protect_home = h; - } - - return 0; -} - -int config_parse_protect_system( - 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) { - - ExecContext *c = data; - int k; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - /* Our enum shall be a superset of booleans, hence first try - * to parse as as boolean, and then as enum */ - - k = parse_boolean(rvalue); - if (k > 0) - c->protect_system = PROTECT_SYSTEM_YES; - else if (k == 0) - c->protect_system = PROTECT_SYSTEM_NO; - else { - ProtectSystem s; - - s = protect_system_from_string(rvalue); - if (s < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse protect system value, ignoring: %s", rvalue); - return 0; - } - - c->protect_system = s; - } - - return 0; -} - -#define FOLLOW_MAX 8 - -static int open_follow(char **filename, FILE **_f, Set *names, char **_final) { - char *id = NULL; - unsigned c = 0; - int fd, r; - FILE *f; - - assert(filename); - assert(*filename); - assert(_f); - assert(names); - - /* This will update the filename pointer if the loaded file is - * reached by a symlink. The old string will be freed. */ - - for (;;) { - char *target, *name; - - if (c++ >= FOLLOW_MAX) - return -ELOOP; - - path_kill_slashes(*filename); - - /* Add the file name we are currently looking at to - * the names of this unit, but only if it is a valid - * unit name. */ - name = basename(*filename); - if (unit_name_is_valid(name, UNIT_NAME_ANY)) { - - id = set_get(names, name); - if (!id) { - id = strdup(name); - if (!id) - return -ENOMEM; - - r = set_consume(names, id); - if (r < 0) - return r; - } - } - - /* Try to open the file name, but don't if its a symlink */ - fd = open(*filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd >= 0) - break; - - if (errno != ELOOP) - return -errno; - - /* Hmm, so this is a symlink. Let's read the name, and follow it manually */ - r = readlink_and_make_absolute(*filename, &target); - if (r < 0) - return r; - - free(*filename); - *filename = target; - } - - f = fdopen(fd, "re"); - if (!f) { - safe_close(fd); - return -errno; - } - - *_f = f; - *_final = id; - - return 0; -} - -static int merge_by_names(Unit **u, Set *names, const char *id) { - char *k; - int r; - - assert(u); - assert(*u); - assert(names); - - /* Let's try to add in all symlink names we found */ - while ((k = set_steal_first(names))) { - - /* First try to merge in the other name into our - * unit */ - r = unit_merge_by_name(*u, k); - if (r < 0) { - Unit *other; - - /* Hmm, we couldn't merge the other unit into - * ours? Then let's try it the other way - * round */ - - /* If the symlink name we are looking at is unit template, then - we must search for instance of this template */ - if (unit_name_is_valid(k, UNIT_NAME_TEMPLATE)) { - _cleanup_free_ char *instance = NULL; - - r = unit_name_replace_instance(k, (*u)->instance, &instance); - if (r < 0) - return r; - - other = manager_get_unit((*u)->manager, instance); - } else - other = manager_get_unit((*u)->manager, k); - - free(k); - - if (other) { - r = unit_merge(other, *u); - if (r >= 0) { - *u = other; - return merge_by_names(u, names, NULL); - } - } - - return r; - } - - if (id == k) - unit_choose_id(*u, id); - - free(k); - } - - return 0; -} - -static int load_from_path(Unit *u, const char *path) { - _cleanup_set_free_free_ Set *symlink_names = NULL; - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *filename = NULL; - char *id = NULL; - Unit *merged; - struct stat st; - int r; - - assert(u); - assert(path); - - symlink_names = set_new(&string_hash_ops); - if (!symlink_names) - return -ENOMEM; - - if (path_is_absolute(path)) { - - filename = strdup(path); - if (!filename) - return -ENOMEM; - - r = open_follow(&filename, &f, symlink_names, &id); - if (r < 0) { - filename = mfree(filename); - if (r != -ENOENT) - return r; - } - - } else { - char **p; - - STRV_FOREACH(p, u->manager->lookup_paths.search_path) { - - /* Instead of opening the path right away, we manually - * follow all symlinks and add their name to our unit - * name set while doing so */ - filename = path_make_absolute(path, *p); - if (!filename) - return -ENOMEM; - - if (u->manager->unit_path_cache && - !set_get(u->manager->unit_path_cache, filename)) - r = -ENOENT; - else - r = open_follow(&filename, &f, symlink_names, &id); - if (r >= 0) - break; - filename = mfree(filename); - if (r != -ENOENT) - return r; - - /* Empty the symlink names for the next run */ - set_clear_free(symlink_names); - } - } - - if (!filename) - /* Hmm, no suitable file found? */ - return 0; - - if (!unit_type_may_alias(u->type) && set_size(symlink_names) > 1) { - log_unit_warning(u, "Unit type of %s does not support alias names, refusing loading via symlink.", u->id); - return -ELOOP; - } - - merged = u; - r = merge_by_names(&merged, symlink_names, id); - if (r < 0) - return r; - - if (merged != u) { - u->load_state = UNIT_MERGED; - return 0; - } - - if (fstat(fileno(f), &st) < 0) - return -errno; - - if (null_or_empty(&st)) { - u->load_state = UNIT_MASKED; - u->fragment_mtime = 0; - } else { - u->load_state = UNIT_LOADED; - u->fragment_mtime = timespec_load(&st.st_mtim); - - /* Now, parse the file contents */ - r = config_parse(u->id, filename, f, - UNIT_VTABLE(u)->sections, - config_item_perf_lookup, load_fragment_gperf_lookup, - false, true, false, u); - if (r < 0) - return r; - } - - free(u->fragment_path); - u->fragment_path = filename; - filename = NULL; - - if (u->source_path) { - if (stat(u->source_path, &st) >= 0) - u->source_mtime = timespec_load(&st.st_mtim); - else - u->source_mtime = 0; - } - - return 0; -} - -int unit_load_fragment(Unit *u) { - int r; - Iterator i; - const char *t; - - assert(u); - assert(u->load_state == UNIT_STUB); - assert(u->id); - - if (u->transient) { - u->load_state = UNIT_LOADED; - return 0; - } - - /* First, try to find the unit under its id. We always look - * for unit files in the default directories, to make it easy - * to override things by placing things in /etc/systemd/system */ - r = load_from_path(u, u->id); - if (r < 0) - return r; - - /* Try to find an alias we can load this with */ - if (u->load_state == UNIT_STUB) { - SET_FOREACH(t, u->names, i) { - - if (t == u->id) - continue; - - r = load_from_path(u, t); - if (r < 0) - return r; - - if (u->load_state != UNIT_STUB) - break; - } - } - - /* And now, try looking for it under the suggested (originally linked) path */ - if (u->load_state == UNIT_STUB && u->fragment_path) { - - r = load_from_path(u, u->fragment_path); - if (r < 0) - return r; - - if (u->load_state == UNIT_STUB) - /* Hmm, this didn't work? Then let's get rid - * of the fragment path stored for us, so that - * we don't point to an invalid location. */ - u->fragment_path = mfree(u->fragment_path); - } - - /* Look for a template */ - if (u->load_state == UNIT_STUB && u->instance) { - _cleanup_free_ char *k = NULL; - - r = unit_name_template(u->id, &k); - if (r < 0) - return r; - - r = load_from_path(u, k); - if (r < 0) - return r; - - if (u->load_state == UNIT_STUB) { - SET_FOREACH(t, u->names, i) { - _cleanup_free_ char *z = NULL; - - if (t == u->id) - continue; - - r = unit_name_template(t, &z); - if (r < 0) - return r; - - r = load_from_path(u, z); - if (r < 0) - return r; - - if (u->load_state != UNIT_STUB) - break; - } - } - } - - return 0; -} - -void unit_dump_config_items(FILE *f) { - static const struct { - const ConfigParserCallback callback; - const char *rvalue; - } table[] = { -#if !defined(HAVE_SYSV_COMPAT) || !defined(HAVE_SECCOMP) || !defined(HAVE_PAM) || !defined(HAVE_SELINUX) || !defined(HAVE_SMACK) || !defined(HAVE_APPARMOR) - { config_parse_warn_compat, "NOTSUPPORTED" }, -#endif - { config_parse_int, "INTEGER" }, - { config_parse_unsigned, "UNSIGNED" }, - { config_parse_iec_size, "SIZE" }, - { config_parse_iec_uint64, "SIZE" }, - { config_parse_si_size, "SIZE" }, - { config_parse_bool, "BOOLEAN" }, - { config_parse_string, "STRING" }, - { config_parse_path, "PATH" }, - { config_parse_unit_path_printf, "PATH" }, - { config_parse_strv, "STRING [...]" }, - { config_parse_exec_nice, "NICE" }, - { config_parse_exec_oom_score_adjust, "OOMSCOREADJUST" }, - { config_parse_exec_io_class, "IOCLASS" }, - { config_parse_exec_io_priority, "IOPRIORITY" }, - { config_parse_exec_cpu_sched_policy, "CPUSCHEDPOLICY" }, - { config_parse_exec_cpu_sched_prio, "CPUSCHEDPRIO" }, - { config_parse_exec_cpu_affinity, "CPUAFFINITY" }, - { config_parse_mode, "MODE" }, - { config_parse_unit_env_file, "FILE" }, - { config_parse_output, "OUTPUT" }, - { config_parse_input, "INPUT" }, - { config_parse_log_facility, "FACILITY" }, - { config_parse_log_level, "LEVEL" }, - { config_parse_exec_secure_bits, "SECUREBITS" }, - { config_parse_capability_set, "BOUNDINGSET" }, - { config_parse_limit, "LIMIT" }, - { config_parse_unit_deps, "UNIT [...]" }, - { config_parse_exec, "PATH [ARGUMENT [...]]" }, - { config_parse_service_type, "SERVICETYPE" }, - { config_parse_service_restart, "SERVICERESTART" }, -#ifdef HAVE_SYSV_COMPAT - { config_parse_sysv_priority, "SYSVPRIORITY" }, -#endif - { config_parse_kill_mode, "KILLMODE" }, - { config_parse_signal, "SIGNAL" }, - { config_parse_socket_listen, "SOCKET [...]" }, - { config_parse_socket_bind, "SOCKETBIND" }, - { config_parse_socket_bindtodevice, "NETWORKINTERFACE" }, - { config_parse_sec, "SECONDS" }, - { config_parse_nsec, "NANOSECONDS" }, - { config_parse_namespace_path_strv, "PATH [...]" }, - { config_parse_unit_requires_mounts_for, "PATH [...]" }, - { config_parse_exec_mount_flags, "MOUNTFLAG [...]" }, - { config_parse_unit_string_printf, "STRING" }, - { config_parse_trigger_unit, "UNIT" }, - { config_parse_timer, "TIMER" }, - { config_parse_path_spec, "PATH" }, - { config_parse_notify_access, "ACCESS" }, - { config_parse_ip_tos, "TOS" }, - { config_parse_unit_condition_path, "CONDITION" }, - { config_parse_unit_condition_string, "CONDITION" }, - { config_parse_unit_condition_null, "CONDITION" }, - { config_parse_unit_slice, "SLICE" }, - { config_parse_documentation, "URL" }, - { config_parse_service_timeout, "SECONDS" }, - { config_parse_failure_action, "ACTION" }, - { config_parse_set_status, "STATUS" }, - { config_parse_service_sockets, "SOCKETS" }, - { config_parse_environ, "ENVIRON" }, -#ifdef HAVE_SECCOMP - { config_parse_syscall_filter, "SYSCALLS" }, - { config_parse_syscall_archs, "ARCHS" }, - { config_parse_syscall_errno, "ERRNO" }, - { config_parse_address_families, "FAMILIES" }, -#endif - { config_parse_cpu_shares, "SHARES" }, - { config_parse_memory_limit, "LIMIT" }, - { config_parse_device_allow, "DEVICE" }, - { config_parse_device_policy, "POLICY" }, - { config_parse_io_limit, "LIMIT" }, - { config_parse_io_weight, "WEIGHT" }, - { config_parse_io_device_weight, "DEVICEWEIGHT" }, - { config_parse_blockio_bandwidth, "BANDWIDTH" }, - { config_parse_blockio_weight, "WEIGHT" }, - { config_parse_blockio_device_weight, "DEVICEWEIGHT" }, - { config_parse_long, "LONG" }, - { config_parse_socket_service, "SERVICE" }, -#ifdef HAVE_SELINUX - { config_parse_exec_selinux_context, "LABEL" }, -#endif - { config_parse_job_mode, "MODE" }, - { config_parse_job_mode_isolate, "BOOLEAN" }, - { config_parse_personality, "PERSONALITY" }, - }; - - const char *prev = NULL; - const char *i; - - assert(f); - - NULSTR_FOREACH(i, load_fragment_gperf_nulstr) { - const char *rvalue = "OTHER", *lvalue; - unsigned j; - size_t prefix_len; - const char *dot; - const ConfigPerfItem *p; - - assert_se(p = load_fragment_gperf_lookup(i, strlen(i))); - - dot = strchr(i, '.'); - lvalue = dot ? dot + 1 : i; - prefix_len = dot-i; - - if (dot) - if (!prev || !strneq(prev, i, prefix_len+1)) { - if (prev) - fputc('\n', f); - - fprintf(f, "[%.*s]\n", (int) prefix_len, i); - } - - for (j = 0; j < ELEMENTSOF(table); j++) - if (p->parse == table[j].callback) { - rvalue = table[j].rvalue; - break; - } - - fprintf(f, "%s=%s\n", lvalue, rvalue); - prev = i; - } -} diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h deleted file mode 100644 index b36a2e3a02..0000000000 --- a/src/core/load-fragment.h +++ /dev/null @@ -1,123 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "unit.h" - -/* Read service data from .desktop file style configuration fragments */ - -int unit_load_fragment(Unit *u); - -void unit_dump_config_items(FILE *f); - -int config_parse_warn_compat(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); -int config_parse_unit_deps(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); -int config_parse_obsolete_unit_deps(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); -int config_parse_unit_string_printf(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); -int config_parse_unit_strv_printf(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); -int config_parse_unit_path_printf(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); -int config_parse_unit_path_strv_printf(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); -int config_parse_documentation(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); -int config_parse_socket_listen(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); -int config_parse_socket_protocol(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); -int config_parse_socket_bind(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); -int config_parse_exec_nice(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); -int config_parse_exec_oom_score_adjust(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); -int config_parse_exec(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); -int config_parse_service_timeout(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); -int config_parse_service_type(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); -int config_parse_service_restart(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); -int config_parse_socket_bindtodevice(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); -int config_parse_output(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); -int config_parse_input(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); -int config_parse_exec_io_class(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); -int config_parse_exec_io_priority(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); -int config_parse_exec_cpu_sched_policy(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); -int config_parse_exec_cpu_sched_prio(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); -int config_parse_exec_cpu_affinity(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); -int config_parse_exec_secure_bits(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); -int config_parse_capability_set(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); -int config_parse_limit(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); -int config_parse_sysv_priority(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); -int config_parse_kill_signal(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); -int config_parse_exec_mount_flags(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); -int config_parse_timer(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); -int config_parse_trigger_unit(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); -int config_parse_path_spec(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); -int config_parse_socket_service(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); -int config_parse_service_sockets(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); -int config_parse_busname_service(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); -int config_parse_bus_policy(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); -int config_parse_bus_policy_world(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); -int config_parse_unit_env_file(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); -int config_parse_ip_tos(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); -int config_parse_unit_condition_path(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); -int config_parse_unit_condition_string(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); -int config_parse_unit_condition_null(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); -int config_parse_kill_mode(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); -int config_parse_notify_access(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); -int config_parse_failure_action(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); -int config_parse_unit_requires_mounts_for(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); -int config_parse_syscall_filter(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); -int config_parse_syscall_archs(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); -int config_parse_syscall_errno(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); -int config_parse_environ(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); -int config_parse_pass_environ(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); -int config_parse_unit_slice(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); -int config_parse_cpu_shares(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); -int config_parse_memory_limit(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); -int config_parse_tasks_max(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); -int config_parse_device_policy(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); -int config_parse_device_allow(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); -int config_parse_io_weight(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); -int config_parse_io_device_weight(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); -int config_parse_io_limit(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); -int config_parse_blockio_weight(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); -int config_parse_blockio_device_weight(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); -int config_parse_blockio_bandwidth(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); -int config_parse_netclass(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); -int config_parse_job_mode(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); -int config_parse_job_mode_isolate(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); -int config_parse_exec_selinux_context(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); -int config_parse_exec_apparmor_profile(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); -int config_parse_exec_smack_process_label(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); -int config_parse_address_families(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); -int config_parse_runtime_directory(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); -int config_parse_set_status(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); -int config_parse_namespace_path_strv(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); -int config_parse_no_new_privileges(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); -int config_parse_cpu_quota(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); -int config_parse_protect_home(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); -int config_parse_protect_system(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); -int config_parse_bus_name(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); -int config_parse_exec_utmp_mode(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); -int config_parse_working_directory(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); -int config_parse_fdname(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); -int config_parse_sec_fix_0(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); - -/* gperf prototypes */ -const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length); -extern const char load_fragment_gperf_nulstr[]; - -typedef enum Disabled { - DISABLED_CONFIGURATION, - DISABLED_LEGACY, - DISABLED_EXPERIMENTAL, -} Disabled; diff --git a/src/core/locale-setup.c b/src/core/locale-setup.c deleted file mode 100644 index ccf61d29fb..0000000000 --- a/src/core/locale-setup.c +++ /dev/null @@ -1,124 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 - -#include "env-util.h" -#include "fileio.h" -#include "locale-setup.h" -#include "locale-util.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" -#include "virt.h" - -int locale_setup(char ***environment) { - char **add; - char *variables[_VARIABLE_LC_MAX] = {}; - int r = 0, i; - - if (detect_container() <= 0) { - r = parse_env_file("/proc/cmdline", WHITESPACE, - "locale.LANG", &variables[VARIABLE_LANG], - "locale.LANGUAGE", &variables[VARIABLE_LANGUAGE], - "locale.LC_CTYPE", &variables[VARIABLE_LC_CTYPE], - "locale.LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], - "locale.LC_TIME", &variables[VARIABLE_LC_TIME], - "locale.LC_COLLATE", &variables[VARIABLE_LC_COLLATE], - "locale.LC_MONETARY", &variables[VARIABLE_LC_MONETARY], - "locale.LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], - "locale.LC_PAPER", &variables[VARIABLE_LC_PAPER], - "locale.LC_NAME", &variables[VARIABLE_LC_NAME], - "locale.LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], - "locale.LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], - "locale.LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], - "locale.LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION], - NULL); - - if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to read /proc/cmdline: %m"); - } - - /* Hmm, nothing set on the kernel cmd line? Then let's - * try /etc/locale.conf */ - if (r <= 0) { - r = parse_env_file("/etc/locale.conf", NEWLINE, - "LANG", &variables[VARIABLE_LANG], - "LANGUAGE", &variables[VARIABLE_LANGUAGE], - "LC_CTYPE", &variables[VARIABLE_LC_CTYPE], - "LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], - "LC_TIME", &variables[VARIABLE_LC_TIME], - "LC_COLLATE", &variables[VARIABLE_LC_COLLATE], - "LC_MONETARY", &variables[VARIABLE_LC_MONETARY], - "LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], - "LC_PAPER", &variables[VARIABLE_LC_PAPER], - "LC_NAME", &variables[VARIABLE_LC_NAME], - "LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], - "LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], - "LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], - "LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION], - NULL); - - if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to read /etc/locale.conf: %m"); - } - - add = NULL; - for (i = 0; i < _VARIABLE_LC_MAX; i++) { - char *s; - - if (!variables[i]) - continue; - - s = strjoin(locale_variable_to_string(i), "=", variables[i], NULL); - if (!s) { - r = -ENOMEM; - goto finish; - } - - if (strv_consume(&add, s) < 0) { - r = -ENOMEM; - goto finish; - } - } - - if (!strv_isempty(add)) { - char **e; - - e = strv_env_merge(2, *environment, add); - if (!e) { - r = -ENOMEM; - goto finish; - } - - strv_free(*environment); - *environment = e; - } - - r = 0; - -finish: - strv_free(add); - - for (i = 0; i < _VARIABLE_LC_MAX; i++) - free(variables[i]); - - return r; -} diff --git a/src/core/locale-setup.h b/src/core/locale-setup.h deleted file mode 100644 index 3b97497afe..0000000000 --- a/src/core/locale-setup.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -int locale_setup(char ***environment); diff --git a/src/core/loopback-setup.c b/src/core/loopback-setup.c deleted file mode 100644 index 04062a7910..0000000000 --- a/src/core/loopback-setup.c +++ /dev/null @@ -1,90 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 - -#include "sd-netlink.h" - -#include "loopback-setup.h" -#include "missing.h" -#include "netlink-util.h" - -static int start_loopback(sd_netlink *rtnl) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; - int r; - - r = sd_rtnl_message_new_link(rtnl, &req, RTM_SETLINK, LOOPBACK_IFINDEX); - if (r < 0) - return r; - - r = sd_rtnl_message_link_set_flags(req, IFF_UP, IFF_UP); - if (r < 0) - return r; - - r = sd_netlink_call(rtnl, req, 0, NULL); - if (r < 0) - return r; - - return 0; -} - -static bool check_loopback(sd_netlink *rtnl) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; - unsigned flags; - int r; - - r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, LOOPBACK_IFINDEX); - if (r < 0) - return false; - - r = sd_netlink_call(rtnl, req, 0, &reply); - if (r < 0) - return false; - - r = sd_rtnl_message_link_get_flags(reply, &flags); - if (r < 0) - return false; - - return flags & IFF_UP; -} - -int loopback_setup(void) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - int r; - - r = sd_netlink_open(&rtnl); - if (r < 0) - return r; - - r = start_loopback(rtnl); - if (r < 0) { - - /* If we lack the permissions to configure the - * loopback device, but we find it to be already - * configured, let's exit cleanly, in order to - * supported unprivileged containers. */ - if (r == -EPERM && check_loopback(rtnl)) - return 0; - - return log_warning_errno(r, "Failed to configure loopback device: %m"); - } - - return 0; -} diff --git a/src/core/loopback-setup.h b/src/core/loopback-setup.h deleted file mode 100644 index e7547b8a26..0000000000 --- a/src/core/loopback-setup.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -int loopback_setup(void); diff --git a/src/core/machine-id-setup.c b/src/core/machine-id-setup.c deleted file mode 100644 index 0145fe2894..0000000000 --- a/src/core/machine-id-setup.c +++ /dev/null @@ -1,364 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include - -#include "sd-id128.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "hexdecoct.h" -#include "io-util.h" -#include "log.h" -#include "machine-id-setup.h" -#include "macro.h" -#include "mkdir.h" -#include "mount-util.h" -#include "path-util.h" -#include "process-util.h" -#include "stat-util.h" -#include "string-util.h" -#include "umask-util.h" -#include "util.h" -#include "virt.h" - -static int shorten_uuid(char destination[34], const char source[36]) { - unsigned i, j; - - assert(destination); - assert(source); - - /* Converts a UUID into a machine ID, by lowercasing it and - * removing dashes. Validates everything. */ - - for (i = 0, j = 0; i < 36 && j < 32; i++) { - int t; - - t = unhexchar(source[i]); - if (t < 0) - continue; - - destination[j++] = hexchar(t); - } - - if (i != 36 || j != 32) - return -EINVAL; - - destination[32] = '\n'; - destination[33] = 0; - return 0; -} - -static int read_machine_id(int fd, char id[34]) { - char id_to_validate[34]; - int r; - - assert(fd >= 0); - assert(id); - - /* Reads a machine ID from a file, validates it, and returns - * it. The returned ID ends in a newline. */ - - r = loop_read_exact(fd, id_to_validate, 33, false); - if (r < 0) - return r; - - if (id_to_validate[32] != '\n') - return -EINVAL; - - id_to_validate[32] = 0; - - if (!id128_is_valid(id_to_validate)) - return -EINVAL; - - memcpy(id, id_to_validate, 32); - id[32] = '\n'; - id[33] = 0; - return 0; -} - -static int write_machine_id(int fd, const char id[34]) { - int r; - - assert(fd >= 0); - assert(id); - - if (lseek(fd, 0, SEEK_SET) < 0) - return -errno; - - r = loop_write(fd, id, 33, false); - if (r < 0) - return r; - - if (fsync(fd) < 0) - return -errno; - - return 0; -} - -static int generate_machine_id(char id[34], const char *root) { - int fd, r; - unsigned char *p; - sd_id128_t buf; - char *q; - const char *dbus_machine_id; - - assert(id); - - dbus_machine_id = prefix_roota(root, "/var/lib/dbus/machine-id"); - - /* First, try reading the D-Bus machine id, unless it is a symlink */ - fd = open(dbus_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd >= 0) { - r = read_machine_id(fd, id); - safe_close(fd); - - if (r >= 0) { - log_info("Initializing machine ID from D-Bus machine ID."); - return 0; - } - } - - if (isempty(root)) { - /* If that didn't work, see if we are running in a container, - * and a machine ID was passed in via $container_uuid the way - * libvirt/LXC does it */ - - if (detect_container() > 0) { - _cleanup_free_ char *e = NULL; - - r = getenv_for_pid(1, "container_uuid", &e); - if (r > 0) { - r = shorten_uuid(id, e); - if (r >= 0) { - log_info("Initializing machine ID from container UUID."); - return 0; - } - } - - } else if (detect_vm() == VIRTUALIZATION_KVM) { - - /* If we are not running in a container, see if we are - * running in qemu/kvm and a machine ID was passed in - * via -uuid on the qemu/kvm command line */ - - char uuid[36]; - - fd = open("/sys/class/dmi/id/product_uuid", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd >= 0) { - r = loop_read_exact(fd, uuid, 36, false); - safe_close(fd); - - if (r >= 0) { - r = shorten_uuid(id, uuid); - if (r >= 0) { - log_info("Initializing machine ID from KVM UUID."); - return 0; - } - } - } - } - } - - /* If that didn't work, generate a random machine id */ - r = sd_id128_randomize(&buf); - if (r < 0) - return log_error_errno(r, "Failed to open /dev/urandom: %m"); - - for (p = buf.bytes, q = id; p < buf.bytes + sizeof(buf); p++, q += 2) { - q[0] = hexchar(*p >> 4); - q[1] = hexchar(*p & 15); - } - - id[32] = '\n'; - id[33] = 0; - - log_info("Initializing machine ID from random generator."); - - return 0; -} - -int machine_id_setup(const char *root, sd_id128_t machine_id) { - const char *etc_machine_id, *run_machine_id; - _cleanup_close_ int fd = -1; - bool writable = true; - char id[34]; /* 32 + \n + \0 */ - int r; - - etc_machine_id = prefix_roota(root, "/etc/machine-id"); - run_machine_id = prefix_roota(root, "/run/machine-id"); - - RUN_WITH_UMASK(0000) { - /* We create this 0444, to indicate that this isn't really - * something you should ever modify. Of course, since the file - * will be owned by root it doesn't matter much, but maybe - * people look. */ - - mkdir_parents(etc_machine_id, 0755); - fd = open(etc_machine_id, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0444); - if (fd < 0) { - int old_errno = errno; - - fd = open(etc_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) { - if (old_errno == EROFS && errno == ENOENT) - log_error_errno(errno, - "System cannot boot: Missing /etc/machine-id and /etc is mounted read-only.\n" - "Booting up is supported only when:\n" - "1) /etc/machine-id exists and is populated.\n" - "2) /etc/machine-id exists and is empty.\n" - "3) /etc/machine-id is missing and /etc is writable.\n"); - else - log_error_errno(errno, "Cannot open %s: %m", etc_machine_id); - - return -errno; - } - - writable = false; - } - } - - /* A machine id argument overrides all other machined-ids */ - if (!sd_id128_is_null(machine_id)) { - sd_id128_to_string(machine_id, id); - id[32] = '\n'; - id[33] = 0; - } else { - if (read_machine_id(fd, id) >= 0) - return 0; - - /* Hmm, so, the id currently stored is not useful, then let's - * generate one */ - - r = generate_machine_id(id, root); - if (r < 0) - return r; - } - - if (writable) - if (write_machine_id(fd, id) >= 0) - return 0; - - fd = safe_close(fd); - - /* Hmm, we couldn't write it? So let's write it to - * /run/machine-id as a replacement */ - - RUN_WITH_UMASK(0022) { - r = write_string_file(run_machine_id, id, WRITE_STRING_FILE_CREATE); - if (r < 0) { - (void) unlink(run_machine_id); - return log_error_errno(r, "Cannot write %s: %m", run_machine_id); - } - } - - /* And now, let's mount it over */ - if (mount(run_machine_id, etc_machine_id, NULL, MS_BIND, NULL) < 0) { - (void) unlink_noerrno(run_machine_id); - return log_error_errno(errno, "Failed to mount %s: %m", etc_machine_id); - } - - log_info("Installed transient %s file.", etc_machine_id); - - /* Mark the mount read-only */ - if (mount(NULL, etc_machine_id, NULL, MS_BIND|MS_RDONLY|MS_REMOUNT, NULL) < 0) - log_warning_errno(errno, "Failed to make transient %s read-only: %m", etc_machine_id); - - return 0; -} - -int machine_id_commit(const char *root) { - _cleanup_close_ int fd = -1, initial_mntns_fd = -1; - const char *etc_machine_id; - char id[34]; /* 32 + \n + \0 */ - int r; - - etc_machine_id = prefix_roota(root, "/etc/machine-id"); - - r = path_is_mount_point(etc_machine_id, 0); - if (r < 0) - return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", etc_machine_id); - if (r == 0) { - log_debug("%s is is not a mount point. Nothing to do.", etc_machine_id); - return 0; - } - - /* Read existing machine-id */ - fd = open(etc_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return log_error_errno(errno, "Cannot open %s: %m", etc_machine_id); - - r = read_machine_id(fd, id); - if (r < 0) - return log_error_errno(r, "We didn't find a valid machine ID in %s.", etc_machine_id); - - r = fd_is_temporary_fs(fd); - if (r < 0) - return log_error_errno(r, "Failed to determine whether %s is on a temporary file system: %m", etc_machine_id); - if (r == 0) { - log_error("%s is not on a temporary file system.", etc_machine_id); - return -EROFS; - } - - fd = safe_close(fd); - - /* Store current mount namespace */ - r = namespace_open(0, NULL, &initial_mntns_fd, NULL, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Can't fetch current mount namespace: %m"); - - /* Switch to a new mount namespace, isolate ourself and unmount etc_machine_id in our new namespace */ - if (unshare(CLONE_NEWNS) < 0) - return log_error_errno(errno, "Failed to enter new namespace: %m"); - - if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) - return log_error_errno(errno, "Couldn't make-rslave / mountpoint in our private namespace: %m"); - - if (umount(etc_machine_id) < 0) - return log_error_errno(errno, "Failed to unmount transient %s file in our private namespace: %m", etc_machine_id); - - /* Update a persistent version of etc_machine_id */ - fd = open(etc_machine_id, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0444); - if (fd < 0) - return log_error_errno(errno, "Cannot open for writing %s. This is mandatory to get a persistent machine-id: %m", etc_machine_id); - - r = write_machine_id(fd, id); - if (r < 0) - return log_error_errno(r, "Cannot write %s: %m", etc_machine_id); - - fd = safe_close(fd); - - /* Return to initial namespace and proceed a lazy tmpfs unmount */ - r = namespace_enter(-1, initial_mntns_fd, -1, -1, -1); - if (r < 0) - return log_warning_errno(r, "Failed to switch back to initial mount namespace: %m.\nWe'll keep transient %s file until next reboot.", etc_machine_id); - - if (umount2(etc_machine_id, MNT_DETACH) < 0) - return log_warning_errno(errno, "Failed to unmount transient %s file: %m.\nWe keep that mount until next reboot.", etc_machine_id); - - return 0; -} diff --git a/src/core/machine-id-setup.h b/src/core/machine-id-setup.h deleted file mode 100644 index a7e7678ed9..0000000000 --- a/src/core/machine-id-setup.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -int machine_id_commit(const char *root); -int machine_id_setup(const char *root, sd_id128_t machine_id); diff --git a/src/core/macros.systemd.in b/src/core/macros.systemd.in deleted file mode 100644 index 2cace3d3ba..0000000000 --- a/src/core/macros.systemd.in +++ /dev/null @@ -1,105 +0,0 @@ -# -*- Mode: rpm-spec; indent-tabs-mode: nil -*- */ -# -# This file is part of systemd. -# -# Copyright 2012 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 . - -# RPM macros for packages installing systemd unit files - -%_unitdir @systemunitdir@ -%_userunitdir @userunitdir@ -%_presetdir @systempresetdir@ -%_udevhwdbdir @udevhwdbdir@ -%_udevrulesdir @udevrulesdir@ -%_journalcatalogdir @catalogdir@ -%_tmpfilesdir @tmpfilesdir@ -%_sysusersdir @sysusersdir@ -%_sysctldir @sysctldir@ -%_binfmtdir @binfmtdir@ - -%systemd_requires \ -Requires(post): systemd \ -Requires(preun): systemd \ -Requires(postun): systemd \ -%{nil} - -%systemd_post() \ -if [ $1 -eq 1 ] ; then \ - # Initial installation \ - systemctl --no-reload preset %{?*} >/dev/null 2>&1 || : \ -fi \ -%{nil} - -%systemd_user_post() %{expand:%systemd_post \\--user \\--global %%{?*}} - -%systemd_preun() \ -if [ $1 -eq 0 ] ; then \ - # Package removal, not upgrade \ - systemctl --no-reload disable --now %{?*} > /dev/null 2>&1 || : \ -fi \ -%{nil} - -%systemd_user_preun() \ -if [ $1 -eq 0 ] ; then \ - # Package removal, not upgrade \ - systemctl --no-reload --user --global disable %{?*} > /dev/null 2>&1 || : \ -fi \ -%{nil} - -%systemd_postun() %{nil} - -%systemd_user_postun() %{nil} - -%systemd_postun_with_restart() \ -if [ $1 -ge 1 ] ; then \ - # Package upgrade, not uninstall \ - systemctl try-restart %{?*} >/dev/null 2>&1 || : \ -fi \ -%{nil} - -%systemd_user_postun_with_restart() %{nil} - -%udev_hwdb_update() \ -udevadm hwdb --update >/dev/null 2>&1 || : \ -%{nil} - -%udev_rules_update() \ -udevadm control --reload >/dev/null 2>&1 || : \ -%{nil} - -%journal_catalog_update() \ -journalctl --update-catalog >/dev/null 2>&1 || : \ -%{nil} - -%tmpfiles_create() \ -systemd-tmpfiles --create %{?*} >/dev/null 2>&1 || : \ -%{nil} - -%sysusers_create() \ -systemd-sysusers %{?*} >/dev/null 2>&1 || : \ -%{nil} - -%sysusers_create_inline() \ -echo %{?*} | systemd-sysusers - >/dev/null 2>&1 || : \ -%{nil} - -%sysctl_apply() \ -@rootlibexecdir@/systemd-sysctl %{?*} >/dev/null 2>&1 || : \ -%{nil} - -%binfmt_apply() \ -@rootlibexecdir@/systemd-binfmt %{?*} >/dev/null 2>&1 || : \ -%{nil} diff --git a/src/core/main.c b/src/core/main.c deleted file mode 100644 index 5ed8c3d3f5..0000000000 --- a/src/core/main.c +++ /dev/null @@ -1,2149 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_SECCOMP -#include -#endif -#ifdef HAVE_VALGRIND_VALGRIND_H -#include -#endif - -#include "sd-bus.h" -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "architecture.h" -#include "build.h" -#include "bus-error.h" -#include "bus-util.h" -#include "capability-util.h" -#include "clock-util.h" -#include "conf-parser.h" -#include "cpu-set-util.h" -#include "dbus-manager.h" -#include "def.h" -#include "env-util.h" -#include "fd-util.h" -#include "fdset.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "hostname-setup.h" -#include "ima-setup.h" -#include "killall.h" -#include "kmod-setup.h" -#include "load-fragment.h" -#include "log.h" -#include "loopback-setup.h" -#include "machine-id-setup.h" -#include "manager.h" -#include "missing.h" -#include "mount-setup.h" -#include "pager.h" -#include "parse-util.h" -#include "proc-cmdline.h" -#include "process-util.h" -#include "rlimit-util.h" -#include "selinux-setup.h" -#include "selinux-util.h" -#include "signal-util.h" -#include "smack-setup.h" -#include "special.h" -#include "stat-util.h" -#include "stdio-util.h" -#include "strv.h" -#include "switch-root.h" -#include "terminal-util.h" -#include "umask-util.h" -#include "user-util.h" -#include "virt.h" -#include "watchdog.h" - -static enum { - ACTION_RUN, - ACTION_HELP, - ACTION_VERSION, - ACTION_TEST, - ACTION_DUMP_CONFIGURATION_ITEMS, - ACTION_DONE -} arg_action = ACTION_RUN; -static char *arg_default_unit = NULL; -static bool arg_system = false; -static bool arg_dump_core = true; -static int arg_crash_chvt = -1; -static bool arg_crash_shell = false; -static bool arg_crash_reboot = false; -static bool arg_confirm_spawn = false; -static ShowStatus arg_show_status = _SHOW_STATUS_UNSET; -static bool arg_switched_root = false; -static bool arg_no_pager = false; -static char ***arg_join_controllers = NULL; -static ExecOutput arg_default_std_output = EXEC_OUTPUT_JOURNAL; -static ExecOutput arg_default_std_error = EXEC_OUTPUT_INHERIT; -static usec_t arg_default_restart_usec = DEFAULT_RESTART_USEC; -static usec_t arg_default_timeout_start_usec = DEFAULT_TIMEOUT_USEC; -static usec_t arg_default_timeout_stop_usec = DEFAULT_TIMEOUT_USEC; -static usec_t arg_default_start_limit_interval = DEFAULT_START_LIMIT_INTERVAL; -static unsigned arg_default_start_limit_burst = DEFAULT_START_LIMIT_BURST; -static usec_t arg_runtime_watchdog = 0; -static usec_t arg_shutdown_watchdog = 10 * USEC_PER_MINUTE; -static char **arg_default_environment = NULL; -static struct rlimit *arg_default_rlimit[_RLIMIT_MAX] = {}; -static uint64_t arg_capability_bounding_set = CAP_ALL; -static nsec_t arg_timer_slack_nsec = NSEC_INFINITY; -static usec_t arg_default_timer_accuracy_usec = 1 * USEC_PER_MINUTE; -static Set* arg_syscall_archs = NULL; -static FILE* arg_serialization = NULL; -static bool arg_default_cpu_accounting = false; -static bool arg_default_io_accounting = false; -static bool arg_default_blockio_accounting = false; -static bool arg_default_memory_accounting = false; -static bool arg_default_tasks_accounting = true; -static uint64_t arg_default_tasks_max = UINT64_C(512); -static sd_id128_t arg_machine_id = {}; - -noreturn static void freeze_or_reboot(void) { - - if (arg_crash_reboot) { - log_notice("Rebooting in 10s..."); - (void) sleep(10); - - log_notice("Rebooting now..."); - (void) reboot(RB_AUTOBOOT); - log_emergency_errno(errno, "Failed to reboot: %m"); - } - - log_emergency("Freezing execution."); - freeze(); -} - -noreturn static void crash(int sig) { - struct sigaction sa; - pid_t pid; - - if (getpid() != 1) - /* Pass this on immediately, if this is not PID 1 */ - (void) raise(sig); - else if (!arg_dump_core) - log_emergency("Caught <%s>, not dumping core.", signal_to_string(sig)); - else { - sa = (struct sigaction) { - .sa_handler = nop_signal_handler, - .sa_flags = SA_NOCLDSTOP|SA_RESTART, - }; - - /* We want to wait for the core process, hence let's enable SIGCHLD */ - (void) sigaction(SIGCHLD, &sa, NULL); - - pid = raw_clone(SIGCHLD, NULL); - if (pid < 0) - log_emergency_errno(errno, "Caught <%s>, cannot fork for core dump: %m", signal_to_string(sig)); - else if (pid == 0) { - /* Enable default signal handler for core dump */ - - sa = (struct sigaction) { - .sa_handler = SIG_DFL, - }; - (void) sigaction(sig, &sa, NULL); - - /* Don't limit the coredump size */ - (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(RLIM_INFINITY)); - - /* Just to be sure... */ - (void) chdir("/"); - - /* Raise the signal again */ - pid = raw_getpid(); - (void) kill(pid, sig); /* raise() would kill the parent */ - - assert_not_reached("We shouldn't be here..."); - _exit(EXIT_FAILURE); - } else { - siginfo_t status; - int r; - - /* Order things nicely. */ - r = wait_for_terminate(pid, &status); - if (r < 0) - log_emergency_errno(r, "Caught <%s>, waitpid() failed: %m", signal_to_string(sig)); - else if (status.si_code != CLD_DUMPED) - log_emergency("Caught <%s>, core dump failed (child "PID_FMT", code=%s, status=%i/%s).", - signal_to_string(sig), - pid, sigchld_code_to_string(status.si_code), - status.si_status, - strna(status.si_code == CLD_EXITED - ? exit_status_to_string(status.si_status, EXIT_STATUS_FULL) - : signal_to_string(status.si_status))); - else - log_emergency("Caught <%s>, dumped core as pid "PID_FMT".", signal_to_string(sig), pid); - } - } - - if (arg_crash_chvt >= 0) - (void) chvt(arg_crash_chvt); - - sa = (struct sigaction) { - .sa_handler = SIG_IGN, - .sa_flags = SA_NOCLDSTOP|SA_NOCLDWAIT|SA_RESTART, - }; - - /* Let the kernel reap children for us */ - (void) sigaction(SIGCHLD, &sa, NULL); - - if (arg_crash_shell) { - log_notice("Executing crash shell in 10s..."); - (void) sleep(10); - - pid = raw_clone(SIGCHLD, NULL); - if (pid < 0) - log_emergency_errno(errno, "Failed to fork off crash shell: %m"); - else if (pid == 0) { - (void) setsid(); - (void) make_console_stdio(); - (void) execle("/bin/sh", "/bin/sh", NULL, environ); - - log_emergency_errno(errno, "execle() failed: %m"); - _exit(EXIT_FAILURE); - } else { - log_info("Spawned crash shell as PID "PID_FMT".", pid); - (void) wait_for_terminate(pid, NULL); - } - } - - freeze_or_reboot(); -} - -static void install_crash_handler(void) { - static const struct sigaction sa = { - .sa_handler = crash, - .sa_flags = SA_NODEFER, /* So that we can raise the signal again from the signal handler */ - }; - int r; - - /* We ignore the return value here, since, we don't mind if we - * cannot set up a crash handler */ - r = sigaction_many(&sa, SIGNALS_CRASH_HANDLER, -1); - if (r < 0) - log_debug_errno(r, "I had trouble setting up the crash handler, ignoring: %m"); -} - -static int console_setup(void) { - _cleanup_close_ int tty_fd = -1; - int r; - - tty_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); - if (tty_fd < 0) - return log_error_errno(tty_fd, "Failed to open /dev/console: %m"); - - /* We don't want to force text mode. plymouth may be showing - * pictures already from initrd. */ - r = reset_terminal_fd(tty_fd, false); - if (r < 0) - return log_error_errno(r, "Failed to reset /dev/console: %m"); - - return 0; -} - -static int parse_crash_chvt(const char *value) { - int b; - - if (safe_atoi(value, &arg_crash_chvt) >= 0) - return 0; - - b = parse_boolean(value); - if (b < 0) - return b; - - if (b > 0) - arg_crash_chvt = 0; /* switch to where kmsg goes */ - else - arg_crash_chvt = -1; /* turn off switching */ - - return 0; -} - -static int set_machine_id(const char *m) { - assert(m); - - if (sd_id128_from_string(m, &arg_machine_id) < 0) - return -EINVAL; - - if (sd_id128_is_null(arg_machine_id)) - return -EINVAL; - - return 0; -} - -static int parse_proc_cmdline_item(const char *key, const char *value) { - - int r; - - assert(key); - - if (streq(key, "systemd.unit") && value) { - - if (!in_initrd()) - return free_and_strdup(&arg_default_unit, value); - - } else if (streq(key, "rd.systemd.unit") && value) { - - if (in_initrd()) - return free_and_strdup(&arg_default_unit, value); - - } else if (streq(key, "systemd.dump_core") && value) { - - r = parse_boolean(value); - if (r < 0) - log_warning("Failed to parse dump core switch %s. Ignoring.", value); - else - arg_dump_core = r; - - } else if (streq(key, "systemd.crash_chvt") && value) { - - if (parse_crash_chvt(value) < 0) - log_warning("Failed to parse crash chvt switch %s. Ignoring.", value); - - } else if (streq(key, "systemd.crash_shell") && value) { - - r = parse_boolean(value); - if (r < 0) - log_warning("Failed to parse crash shell switch %s. Ignoring.", value); - else - arg_crash_shell = r; - - } else if (streq(key, "systemd.crash_reboot") && value) { - - r = parse_boolean(value); - if (r < 0) - log_warning("Failed to parse crash reboot switch %s. Ignoring.", value); - else - arg_crash_reboot = r; - - } else if (streq(key, "systemd.confirm_spawn") && value) { - - r = parse_boolean(value); - if (r < 0) - log_warning("Failed to parse confirm spawn switch %s. Ignoring.", value); - else - arg_confirm_spawn = r; - - } else if (streq(key, "systemd.show_status") && value) { - - r = parse_show_status(value, &arg_show_status); - if (r < 0) - log_warning("Failed to parse show status switch %s. Ignoring.", value); - - } else if (streq(key, "systemd.default_standard_output") && value) { - - r = exec_output_from_string(value); - if (r < 0) - log_warning("Failed to parse default standard output switch %s. Ignoring.", value); - else - arg_default_std_output = r; - - } else if (streq(key, "systemd.default_standard_error") && value) { - - r = exec_output_from_string(value); - if (r < 0) - log_warning("Failed to parse default standard error switch %s. Ignoring.", value); - else - arg_default_std_error = r; - - } else if (streq(key, "systemd.setenv") && value) { - - if (env_assignment_is_valid(value)) { - char **env; - - env = strv_env_set(arg_default_environment, value); - if (env) - arg_default_environment = env; - else - log_warning_errno(ENOMEM, "Setting environment variable '%s' failed, ignoring: %m", value); - } else - log_warning("Environment variable name '%s' is not valid. Ignoring.", value); - - } else if (streq(key, "systemd.machine_id") && value) { - - r = set_machine_id(value); - if (r < 0) - log_warning("MachineID '%s' is not valid. Ignoring.", value); - - } else if (streq(key, "quiet") && !value) { - - if (arg_show_status == _SHOW_STATUS_UNSET) - arg_show_status = SHOW_STATUS_AUTO; - - } else if (streq(key, "debug") && !value) { - - /* Note that log_parse_environment() handles 'debug' - * too, and sets the log level to LOG_DEBUG. */ - - if (detect_container() > 0) - log_set_target(LOG_TARGET_CONSOLE); - - } else if (!in_initrd() && !value) { - const char *target; - - /* SysV compatibility */ - target = runlevel_to_target(key); - if (target) - return free_and_strdup(&arg_default_unit, target); - - } else if (streq(key, "systemd.default_timeout_start_sec") && value) { - - r = parse_sec(value, &arg_default_timeout_start_usec); - if (r < 0) - log_warning_errno(r, "Failed to parse default start timeout: %s, ignoring.", value); - - if (arg_default_timeout_start_usec <= 0) - arg_default_timeout_start_usec = USEC_INFINITY; - } - - return 0; -} - -#define DEFINE_SETTER(name, func, descr) \ - static int name(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) { \ - \ - int r; \ - \ - assert(filename); \ - assert(lvalue); \ - assert(rvalue); \ - \ - r = func(rvalue); \ - if (r < 0) \ - log_syntax(unit, LOG_ERR, filename, line, r, \ - "Invalid " descr "'%s': %m", \ - rvalue); \ - \ - return 0; \ - } - -DEFINE_SETTER(config_parse_level2, log_set_max_level_from_string, "log level") -DEFINE_SETTER(config_parse_target, log_set_target_from_string, "target") -DEFINE_SETTER(config_parse_color, log_show_color_from_string, "color" ) -DEFINE_SETTER(config_parse_location, log_show_location_from_string, "location") - -static int config_parse_cpu_affinity2( - 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) { - - _cleanup_cpu_free_ cpu_set_t *c = NULL; - int ncpus; - - ncpus = parse_cpu_set_and_warn(rvalue, &c, unit, filename, line, lvalue); - if (ncpus < 0) - return ncpus; - - if (sched_setaffinity(0, CPU_ALLOC_SIZE(ncpus), c) < 0) - log_warning("Failed to set CPU affinity: %m"); - - return 0; -} - -static int config_parse_show_status( - 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) { - - int k; - ShowStatus *b = data; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - k = parse_show_status(rvalue, b); - if (k < 0) { - log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse show status setting, ignoring: %s", rvalue); - return 0; - } - - return 0; -} - -static int config_parse_crash_chvt( - 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) { - - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = parse_crash_chvt(rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CrashChangeVT= setting, ignoring: %s", rvalue); - return 0; - } - - return 0; -} - -static int config_parse_join_controllers(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) { - - const char *whole_rvalue = rvalue; - unsigned n = 0; - - assert(filename); - assert(lvalue); - assert(rvalue); - - arg_join_controllers = strv_free_free(arg_join_controllers); - - for (;;) { - _cleanup_free_ char *word = NULL; - char **l; - int r; - - r = extract_first_word(&rvalue, &word, WHITESPACE, EXTRACT_QUOTES); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Invalid value for %s: %s", lvalue, whole_rvalue); - return r; - } - if (r == 0) - break; - - l = strv_split(word, ","); - if (!l) - return log_oom(); - strv_uniq(l); - - if (strv_length(l) <= 1) { - strv_free(l); - continue; - } - - if (!arg_join_controllers) { - arg_join_controllers = new(char**, 2); - if (!arg_join_controllers) { - strv_free(l); - return log_oom(); - } - - arg_join_controllers[0] = l; - arg_join_controllers[1] = NULL; - - n = 1; - } else { - char ***a; - char ***t; - - t = new0(char**, n+2); - if (!t) { - strv_free(l); - return log_oom(); - } - - n = 0; - - for (a = arg_join_controllers; *a; a++) { - - if (strv_overlap(*a, l)) { - if (strv_extend_strv(&l, *a, false) < 0) { - strv_free(l); - strv_free_free(t); - return log_oom(); - } - - } else { - char **c; - - c = strv_copy(*a); - if (!c) { - strv_free(l); - strv_free_free(t); - return log_oom(); - } - - t[n++] = c; - } - } - - t[n++] = strv_uniq(l); - - strv_free_free(arg_join_controllers); - arg_join_controllers = t; - } - } - if (!isempty(rvalue)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); - - return 0; -} - -static int parse_config_file(void) { - - const ConfigTableItem items[] = { - { "Manager", "LogLevel", config_parse_level2, 0, NULL }, - { "Manager", "LogTarget", config_parse_target, 0, NULL }, - { "Manager", "LogColor", config_parse_color, 0, NULL }, - { "Manager", "LogLocation", config_parse_location, 0, NULL }, - { "Manager", "DumpCore", config_parse_bool, 0, &arg_dump_core }, - { "Manager", "CrashChVT", /* legacy */ config_parse_crash_chvt, 0, NULL }, - { "Manager", "CrashChangeVT", config_parse_crash_chvt, 0, NULL }, - { "Manager", "CrashShell", config_parse_bool, 0, &arg_crash_shell }, - { "Manager", "CrashReboot", config_parse_bool, 0, &arg_crash_reboot }, - { "Manager", "ShowStatus", config_parse_show_status, 0, &arg_show_status }, - { "Manager", "CPUAffinity", config_parse_cpu_affinity2, 0, NULL }, - { "Manager", "JoinControllers", config_parse_join_controllers, 0, &arg_join_controllers }, - { "Manager", "RuntimeWatchdogSec", config_parse_sec, 0, &arg_runtime_watchdog }, - { "Manager", "ShutdownWatchdogSec", config_parse_sec, 0, &arg_shutdown_watchdog }, - { "Manager", "CapabilityBoundingSet", config_parse_capability_set, 0, &arg_capability_bounding_set }, -#ifdef HAVE_SECCOMP - { "Manager", "SystemCallArchitectures", config_parse_syscall_archs, 0, &arg_syscall_archs }, -#endif - { "Manager", "TimerSlackNSec", config_parse_nsec, 0, &arg_timer_slack_nsec }, - { "Manager", "DefaultTimerAccuracySec", config_parse_sec, 0, &arg_default_timer_accuracy_usec }, - { "Manager", "DefaultStandardOutput", config_parse_output, 0, &arg_default_std_output }, - { "Manager", "DefaultStandardError", config_parse_output, 0, &arg_default_std_error }, - { "Manager", "DefaultTimeoutStartSec", config_parse_sec, 0, &arg_default_timeout_start_usec }, - { "Manager", "DefaultTimeoutStopSec", config_parse_sec, 0, &arg_default_timeout_stop_usec }, - { "Manager", "DefaultRestartSec", config_parse_sec, 0, &arg_default_restart_usec }, - { "Manager", "DefaultStartLimitInterval", config_parse_sec, 0, &arg_default_start_limit_interval }, /* obsolete alias */ - { "Manager", "DefaultStartLimitIntervalSec",config_parse_sec, 0, &arg_default_start_limit_interval }, - { "Manager", "DefaultStartLimitBurst", config_parse_unsigned, 0, &arg_default_start_limit_burst }, - { "Manager", "DefaultEnvironment", config_parse_environ, 0, &arg_default_environment }, - { "Manager", "DefaultLimitCPU", config_parse_limit, RLIMIT_CPU, arg_default_rlimit }, - { "Manager", "DefaultLimitFSIZE", config_parse_limit, RLIMIT_FSIZE, arg_default_rlimit }, - { "Manager", "DefaultLimitDATA", config_parse_limit, RLIMIT_DATA, arg_default_rlimit }, - { "Manager", "DefaultLimitSTACK", config_parse_limit, RLIMIT_STACK, arg_default_rlimit }, - { "Manager", "DefaultLimitCORE", config_parse_limit, RLIMIT_CORE, arg_default_rlimit }, - { "Manager", "DefaultLimitRSS", config_parse_limit, RLIMIT_RSS, arg_default_rlimit }, - { "Manager", "DefaultLimitNOFILE", config_parse_limit, RLIMIT_NOFILE, arg_default_rlimit }, - { "Manager", "DefaultLimitAS", config_parse_limit, RLIMIT_AS, arg_default_rlimit }, - { "Manager", "DefaultLimitNPROC", config_parse_limit, RLIMIT_NPROC, arg_default_rlimit }, - { "Manager", "DefaultLimitMEMLOCK", config_parse_limit, RLIMIT_MEMLOCK, arg_default_rlimit }, - { "Manager", "DefaultLimitLOCKS", config_parse_limit, RLIMIT_LOCKS, arg_default_rlimit }, - { "Manager", "DefaultLimitSIGPENDING", config_parse_limit, RLIMIT_SIGPENDING, arg_default_rlimit }, - { "Manager", "DefaultLimitMSGQUEUE", config_parse_limit, RLIMIT_MSGQUEUE, arg_default_rlimit }, - { "Manager", "DefaultLimitNICE", config_parse_limit, RLIMIT_NICE, arg_default_rlimit }, - { "Manager", "DefaultLimitRTPRIO", config_parse_limit, RLIMIT_RTPRIO, arg_default_rlimit }, - { "Manager", "DefaultLimitRTTIME", config_parse_limit, RLIMIT_RTTIME, arg_default_rlimit }, - { "Manager", "DefaultCPUAccounting", config_parse_bool, 0, &arg_default_cpu_accounting }, - { "Manager", "DefaultIOAccounting", config_parse_bool, 0, &arg_default_io_accounting }, - { "Manager", "DefaultBlockIOAccounting", config_parse_bool, 0, &arg_default_blockio_accounting }, - { "Manager", "DefaultMemoryAccounting", config_parse_bool, 0, &arg_default_memory_accounting }, - { "Manager", "DefaultTasksAccounting", config_parse_bool, 0, &arg_default_tasks_accounting }, - { "Manager", "DefaultTasksMax", config_parse_tasks_max, 0, &arg_default_tasks_max }, - {} - }; - - const char *fn, *conf_dirs_nulstr; - - fn = arg_system ? - PKGSYSCONFDIR "/system.conf" : - PKGSYSCONFDIR "/user.conf"; - - conf_dirs_nulstr = arg_system ? - CONF_PATHS_NULSTR("systemd/system.conf.d") : - CONF_PATHS_NULSTR("systemd/user.conf.d"); - - config_parse_many(fn, conf_dirs_nulstr, "Manager\0", config_item_table_lookup, items, false, NULL); - - /* Traditionally "0" was used to turn off the default unit timeouts. Fix this up so that we used USEC_INFINITY - * like everywhere else. */ - if (arg_default_timeout_start_usec <= 0) - arg_default_timeout_start_usec = USEC_INFINITY; - if (arg_default_timeout_stop_usec <= 0) - arg_default_timeout_stop_usec = USEC_INFINITY; - - return 0; -} - -static void manager_set_defaults(Manager *m) { - - assert(m); - - m->default_timer_accuracy_usec = arg_default_timer_accuracy_usec; - m->default_std_output = arg_default_std_output; - m->default_std_error = arg_default_std_error; - m->default_timeout_start_usec = arg_default_timeout_start_usec; - m->default_timeout_stop_usec = arg_default_timeout_stop_usec; - m->default_restart_usec = arg_default_restart_usec; - m->default_start_limit_interval = arg_default_start_limit_interval; - m->default_start_limit_burst = arg_default_start_limit_burst; - m->default_cpu_accounting = arg_default_cpu_accounting; - m->default_io_accounting = arg_default_io_accounting; - m->default_blockio_accounting = arg_default_blockio_accounting; - m->default_memory_accounting = arg_default_memory_accounting; - m->default_tasks_accounting = arg_default_tasks_accounting; - m->default_tasks_max = arg_default_tasks_max; - - manager_set_default_rlimits(m, arg_default_rlimit); - manager_environment_add(m, NULL, arg_default_environment); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_LOG_LEVEL = 0x100, - ARG_LOG_TARGET, - ARG_LOG_COLOR, - ARG_LOG_LOCATION, - ARG_UNIT, - ARG_SYSTEM, - ARG_USER, - ARG_TEST, - ARG_NO_PAGER, - ARG_VERSION, - ARG_DUMP_CONFIGURATION_ITEMS, - ARG_DUMP_CORE, - ARG_CRASH_CHVT, - ARG_CRASH_SHELL, - ARG_CRASH_REBOOT, - ARG_CONFIRM_SPAWN, - ARG_SHOW_STATUS, - ARG_DESERIALIZE, - ARG_SWITCHED_ROOT, - ARG_DEFAULT_STD_OUTPUT, - ARG_DEFAULT_STD_ERROR, - ARG_MACHINE_ID - }; - - static const struct option options[] = { - { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, - { "log-target", required_argument, NULL, ARG_LOG_TARGET }, - { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, - { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, - { "unit", required_argument, NULL, ARG_UNIT }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "test", no_argument, NULL, ARG_TEST }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "dump-configuration-items", no_argument, NULL, ARG_DUMP_CONFIGURATION_ITEMS }, - { "dump-core", optional_argument, NULL, ARG_DUMP_CORE }, - { "crash-chvt", required_argument, NULL, ARG_CRASH_CHVT }, - { "crash-shell", optional_argument, NULL, ARG_CRASH_SHELL }, - { "crash-reboot", optional_argument, NULL, ARG_CRASH_REBOOT }, - { "confirm-spawn", optional_argument, NULL, ARG_CONFIRM_SPAWN }, - { "show-status", optional_argument, NULL, ARG_SHOW_STATUS }, - { "deserialize", required_argument, NULL, ARG_DESERIALIZE }, - { "switched-root", no_argument, NULL, ARG_SWITCHED_ROOT }, - { "default-standard-output", required_argument, NULL, ARG_DEFAULT_STD_OUTPUT, }, - { "default-standard-error", required_argument, NULL, ARG_DEFAULT_STD_ERROR, }, - { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, - {} - }; - - int c, r; - - assert(argc >= 1); - assert(argv); - - if (getpid() == 1) - opterr = 0; - - while ((c = getopt_long(argc, argv, "hDbsz:", options, NULL)) >= 0) - - switch (c) { - - case ARG_LOG_LEVEL: - r = log_set_max_level_from_string(optarg); - if (r < 0) { - log_error("Failed to parse log level %s.", optarg); - return r; - } - - break; - - case ARG_LOG_TARGET: - r = log_set_target_from_string(optarg); - if (r < 0) { - log_error("Failed to parse log target %s.", optarg); - return r; - } - - break; - - case ARG_LOG_COLOR: - - if (optarg) { - r = log_show_color_from_string(optarg); - if (r < 0) { - log_error("Failed to parse log color setting %s.", optarg); - return r; - } - } else - log_show_color(true); - - break; - - case ARG_LOG_LOCATION: - if (optarg) { - r = log_show_location_from_string(optarg); - if (r < 0) { - log_error("Failed to parse log location setting %s.", optarg); - return r; - } - } else - log_show_location(true); - - break; - - case ARG_DEFAULT_STD_OUTPUT: - r = exec_output_from_string(optarg); - if (r < 0) { - log_error("Failed to parse default standard output setting %s.", optarg); - return r; - } else - arg_default_std_output = r; - break; - - case ARG_DEFAULT_STD_ERROR: - r = exec_output_from_string(optarg); - if (r < 0) { - log_error("Failed to parse default standard error output setting %s.", optarg); - return r; - } else - arg_default_std_error = r; - break; - - case ARG_UNIT: - - r = free_and_strdup(&arg_default_unit, optarg); - if (r < 0) - return log_error_errno(r, "Failed to set default unit %s: %m", optarg); - - break; - - case ARG_SYSTEM: - arg_system = true; - break; - - case ARG_USER: - arg_system = false; - break; - - case ARG_TEST: - arg_action = ACTION_TEST; - break; - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case ARG_VERSION: - arg_action = ACTION_VERSION; - break; - - case ARG_DUMP_CONFIGURATION_ITEMS: - arg_action = ACTION_DUMP_CONFIGURATION_ITEMS; - break; - - case ARG_DUMP_CORE: - if (!optarg) - arg_dump_core = true; - else { - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse dump core boolean: %s", optarg); - arg_dump_core = r; - } - break; - - case ARG_CRASH_CHVT: - r = parse_crash_chvt(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse crash virtual terminal index: %s", optarg); - break; - - case ARG_CRASH_SHELL: - if (!optarg) - arg_crash_shell = true; - else { - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse crash shell boolean: %s", optarg); - arg_crash_shell = r; - } - break; - - case ARG_CRASH_REBOOT: - if (!optarg) - arg_crash_reboot = true; - else { - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse crash shell boolean: %s", optarg); - arg_crash_reboot = r; - } - break; - - case ARG_CONFIRM_SPAWN: - r = optarg ? parse_boolean(optarg) : 1; - if (r < 0) { - log_error("Failed to parse confirm spawn boolean %s.", optarg); - return r; - } - arg_confirm_spawn = r; - break; - - case ARG_SHOW_STATUS: - if (optarg) { - r = parse_show_status(optarg, &arg_show_status); - if (r < 0) { - log_error("Failed to parse show status boolean %s.", optarg); - return r; - } - } else - arg_show_status = SHOW_STATUS_YES; - break; - - case ARG_DESERIALIZE: { - int fd; - FILE *f; - - r = safe_atoi(optarg, &fd); - if (r < 0 || fd < 0) { - log_error("Failed to parse deserialize option %s.", optarg); - return -EINVAL; - } - - (void) fd_cloexec(fd, true); - - f = fdopen(fd, "r"); - if (!f) - return log_error_errno(errno, "Failed to open serialization fd: %m"); - - safe_fclose(arg_serialization); - arg_serialization = f; - - break; - } - - case ARG_SWITCHED_ROOT: - arg_switched_root = true; - break; - - case ARG_MACHINE_ID: - r = set_machine_id(optarg); - if (r < 0) { - log_error("MachineID '%s' is not valid.", optarg); - return r; - } - break; - - case 'h': - arg_action = ACTION_HELP; - break; - - case 'D': - log_set_max_level(LOG_DEBUG); - break; - - case 'b': - case 's': - case 'z': - /* Just to eat away the sysvinit kernel - * cmdline args without getopt() error - * messages that we'll parse in - * parse_proc_cmdline_word() or ignore. */ - - case '?': - if (getpid() != 1) - return -EINVAL; - else - return 0; - - default: - assert_not_reached("Unhandled option code."); - } - - if (optind < argc && getpid() != 1) { - /* Hmm, when we aren't run as init system - * let's complain about excess arguments */ - - log_error("Excess arguments."); - return -EINVAL; - } - - return 0; -} - -static int help(void) { - - printf("%s [OPTIONS...]\n\n" - "Starts up and maintains the system or user services.\n\n" - " -h --help Show this help\n" - " --test Determine startup sequence, dump it and exit\n" - " --no-pager Do not pipe output into a pager\n" - " --dump-configuration-items Dump understood unit configuration items\n" - " --unit=UNIT Set default unit\n" - " --system Run a system instance, even if PID != 1\n" - " --user Run a user instance\n" - " --dump-core[=BOOL] Dump core on crash\n" - " --crash-vt=NR Change to specified VT on crash\n" - " --crash-reboot[=BOOL] Reboot on crash\n" - " --crash-shell[=BOOL] Run shell on crash\n" - " --confirm-spawn[=BOOL] Ask for confirmation when spawning processes\n" - " --show-status[=BOOL] Show status updates on the console during bootup\n" - " --log-target=TARGET Set log target (console, journal, kmsg, journal-or-kmsg, null)\n" - " --log-level=LEVEL Set log level (debug, info, notice, warning, err, crit, alert, emerg)\n" - " --log-color[=BOOL] Highlight important log messages\n" - " --log-location[=BOOL] Include code location in log messages\n" - " --default-standard-output= Set default standard output for services\n" - " --default-standard-error= Set default standard error output for services\n", - program_invocation_short_name); - - return 0; -} - -static int prepare_reexecute(Manager *m, FILE **_f, FDSet **_fds, bool switching_root) { - _cleanup_fdset_free_ FDSet *fds = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(m); - assert(_f); - assert(_fds); - - r = manager_open_serialization(m, &f); - if (r < 0) - return log_error_errno(r, "Failed to create serialization file: %m"); - - /* Make sure nothing is really destructed when we shut down */ - m->n_reloading++; - bus_manager_send_reloading(m, true); - - fds = fdset_new(); - if (!fds) - return log_oom(); - - r = manager_serialize(m, f, fds, switching_root); - if (r < 0) - return log_error_errno(r, "Failed to serialize state: %m"); - - if (fseeko(f, 0, SEEK_SET) == (off_t) -1) - return log_error_errno(errno, "Failed to rewind serialization fd: %m"); - - r = fd_cloexec(fileno(f), false); - if (r < 0) - return log_error_errno(r, "Failed to disable O_CLOEXEC for serialization: %m"); - - r = fdset_cloexec(fds, false); - if (r < 0) - return log_error_errno(r, "Failed to disable O_CLOEXEC for serialization fds: %m"); - - *_f = f; - *_fds = fds; - - f = NULL; - fds = NULL; - - return 0; -} - -static int bump_rlimit_nofile(struct rlimit *saved_rlimit) { - struct rlimit nl; - int r; - - assert(saved_rlimit); - - /* Save the original RLIMIT_NOFILE so that we can reset it - * later when transitioning from the initrd to the main - * systemd or suchlike. */ - if (getrlimit(RLIMIT_NOFILE, saved_rlimit) < 0) - return log_error_errno(errno, "Reading RLIMIT_NOFILE failed: %m"); - - /* Make sure forked processes get the default kernel setting */ - if (!arg_default_rlimit[RLIMIT_NOFILE]) { - struct rlimit *rl; - - rl = newdup(struct rlimit, saved_rlimit, 1); - if (!rl) - return log_oom(); - - arg_default_rlimit[RLIMIT_NOFILE] = rl; - } - - /* Bump up the resource limit for ourselves substantially */ - nl.rlim_cur = nl.rlim_max = 64*1024; - r = setrlimit_closest(RLIMIT_NOFILE, &nl); - if (r < 0) - return log_error_errno(r, "Setting RLIMIT_NOFILE failed: %m"); - - return 0; -} - -static void test_usr(void) { - - /* Check that /usr is not a separate fs */ - - if (dir_is_empty("/usr") <= 0) - return; - - log_warning("/usr appears to be on its own filesystem and is not already mounted. This is not a supported setup. " - "Some things will probably break (sometimes even silently) in mysterious ways. " - "Consult http://freedesktop.org/wiki/Software/systemd/separate-usr-is-broken for more information."); -} - -static int initialize_join_controllers(void) { - /* By default, mount "cpu" + "cpuacct" together, and "net_cls" - * + "net_prio". We'd like to add "cpuset" to the mix, but - * "cpuset" doesn't really work for groups with no initialized - * attributes. */ - - arg_join_controllers = new(char**, 3); - if (!arg_join_controllers) - return -ENOMEM; - - arg_join_controllers[0] = strv_new("cpu", "cpuacct", NULL); - if (!arg_join_controllers[0]) - goto oom; - - arg_join_controllers[1] = strv_new("net_cls", "net_prio", NULL); - if (!arg_join_controllers[1]) - goto oom; - - arg_join_controllers[2] = NULL; - return 0; - -oom: - arg_join_controllers = strv_free_free(arg_join_controllers); - return -ENOMEM; -} - -static int enforce_syscall_archs(Set *archs) { -#ifdef HAVE_SECCOMP - scmp_filter_ctx *seccomp; - Iterator i; - void *id; - int r; - - seccomp = seccomp_init(SCMP_ACT_ALLOW); - if (!seccomp) - return log_oom(); - - SET_FOREACH(id, arg_syscall_archs, i) { - r = seccomp_arch_add(seccomp, PTR_TO_UINT32(id) - 1); - if (r == -EEXIST) - continue; - if (r < 0) { - log_error_errno(r, "Failed to add architecture to seccomp: %m"); - goto finish; - } - } - - r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); - if (r < 0) { - log_error_errno(r, "Failed to unset NO_NEW_PRIVS: %m"); - goto finish; - } - - r = seccomp_load(seccomp); - if (r < 0) - log_error_errno(r, "Failed to add install architecture seccomp: %m"); - -finish: - seccomp_release(seccomp); - return r; -#else - return 0; -#endif -} - -static int status_welcome(void) { - _cleanup_free_ char *pretty_name = NULL, *ansi_color = NULL; - int r; - - r = parse_env_file("/etc/os-release", NEWLINE, - "PRETTY_NAME", &pretty_name, - "ANSI_COLOR", &ansi_color, - NULL); - if (r == -ENOENT) - r = parse_env_file("/usr/lib/os-release", NEWLINE, - "PRETTY_NAME", &pretty_name, - "ANSI_COLOR", &ansi_color, - NULL); - - if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to read os-release file: %m"); - - if (log_get_show_color()) - return status_printf(NULL, false, false, - "\nWelcome to \x1B[%sm%s\x1B[0m!\n", - isempty(ansi_color) ? "1" : ansi_color, - isempty(pretty_name) ? "GNU/Linux" : pretty_name); - else - return status_printf(NULL, false, false, - "\nWelcome to %s!\n", - isempty(pretty_name) ? "GNU/Linux" : pretty_name); -} - -static int write_container_id(void) { - const char *c; - int r; - - c = getenv("container"); - if (isempty(c)) - return 0; - - RUN_WITH_UMASK(0022) - r = write_string_file("/run/systemd/container", c, WRITE_STRING_FILE_CREATE); - if (r < 0) - return log_warning_errno(r, "Failed to write /run/systemd/container, ignoring: %m"); - - return 1; -} - -static int bump_unix_max_dgram_qlen(void) { - _cleanup_free_ char *qlen = NULL; - unsigned long v; - int r; - - /* Let's bump the net.unix.max_dgram_qlen sysctl. The kernel - * default of 16 is simply too low. We set the value really - * really early during boot, so that it is actually applied to - * all our sockets, including the $NOTIFY_SOCKET one. */ - - r = read_one_line_file("/proc/sys/net/unix/max_dgram_qlen", &qlen); - if (r < 0) - return log_warning_errno(r, "Failed to read AF_UNIX datagram queue length, ignoring: %m"); - - r = safe_atolu(qlen, &v); - if (r < 0) - return log_warning_errno(r, "Failed to parse AF_UNIX datagram queue length, ignoring: %m"); - - if (v >= DEFAULT_UNIX_MAX_DGRAM_QLEN) - return 0; - - qlen = mfree(qlen); - if (asprintf(&qlen, "%lu\n", DEFAULT_UNIX_MAX_DGRAM_QLEN) < 0) - return log_oom(); - - r = write_string_file("/proc/sys/net/unix/max_dgram_qlen", qlen, 0); - if (r < 0) - return log_full_errno(IN_SET(r, -EROFS, -EPERM, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to bump AF_UNIX datagram queue length, ignoring: %m"); - - return 1; -} - -int main(int argc, char *argv[]) { - Manager *m = NULL; - int r, retval = EXIT_FAILURE; - usec_t before_startup, after_startup; - char timespan[FORMAT_TIMESPAN_MAX]; - FDSet *fds = NULL; - bool reexecute = false; - const char *shutdown_verb = NULL; - dual_timestamp initrd_timestamp = DUAL_TIMESTAMP_NULL; - dual_timestamp userspace_timestamp = DUAL_TIMESTAMP_NULL; - dual_timestamp kernel_timestamp = DUAL_TIMESTAMP_NULL; - dual_timestamp security_start_timestamp = DUAL_TIMESTAMP_NULL; - dual_timestamp security_finish_timestamp = DUAL_TIMESTAMP_NULL; - static char systemd[] = "systemd"; - bool skip_setup = false; - unsigned j; - bool loaded_policy = false; - bool arm_reboot_watchdog = false; - bool queue_default_job = false; - bool empty_etc = false; - char *switch_root_dir = NULL, *switch_root_init = NULL; - struct rlimit saved_rlimit_nofile = RLIMIT_MAKE_CONST(0); - const char *error_message = NULL; - -#ifdef HAVE_SYSV_COMPAT - if (getpid() != 1 && strstr(program_invocation_short_name, "init")) { - /* This is compatibility support for SysV, where - * calling init as a user is identical to telinit. */ - - execv(SYSTEMCTL_BINARY_PATH, argv); - log_error_errno(errno, "Failed to exec " SYSTEMCTL_BINARY_PATH ": %m"); - return 1; - } -#endif - - dual_timestamp_from_monotonic(&kernel_timestamp, 0); - dual_timestamp_get(&userspace_timestamp); - - /* Determine if this is a reexecution or normal bootup. We do - * the full command line parsing much later, so let's just - * have a quick peek here. */ - if (strv_find(argv+1, "--deserialize")) - skip_setup = true; - - /* If we have switched root, do all the special setup - * things */ - if (strv_find(argv+1, "--switched-root")) - skip_setup = false; - - /* If we get started via the /sbin/init symlink then we are - called 'init'. After a subsequent reexecution we are then - called 'systemd'. That is confusing, hence let's call us - systemd right-away. */ - program_invocation_short_name = systemd; - prctl(PR_SET_NAME, systemd); - - saved_argv = argv; - saved_argc = argc; - - log_show_color(colors_enabled()); - log_set_upgrade_syslog_to_journal(true); - - /* Disable the umask logic */ - if (getpid() == 1) - umask(0); - - if (getpid() == 1 && detect_container() <= 0) { - - /* Running outside of a container as PID 1 */ - arg_system = true; - make_null_stdio(); - log_set_target(LOG_TARGET_KMSG); - log_open(); - - if (in_initrd()) - initrd_timestamp = userspace_timestamp; - - if (!skip_setup) { - r = mount_setup_early(); - if (r < 0) { - error_message = "Failed to early mount API filesystems"; - goto finish; - } - dual_timestamp_get(&security_start_timestamp); - if (mac_selinux_setup(&loaded_policy) < 0) { - error_message = "Failed to load SELinux policy"; - goto finish; - } else if (ima_setup() < 0) { - error_message = "Failed to load IMA policy"; - goto finish; - } else if (mac_smack_setup(&loaded_policy) < 0) { - error_message = "Failed to load SMACK policy"; - goto finish; - } - dual_timestamp_get(&security_finish_timestamp); - } - - if (mac_selinux_init() < 0) { - error_message = "Failed to initialize SELinux policy"; - goto finish; - } - - if (!skip_setup) { - if (clock_is_localtime(NULL) > 0) { - int min; - - /* - * The very first call of settimeofday() also does a time warp in the kernel. - * - * In the rtc-in-local time mode, we set the kernel's timezone, and rely on - * external tools to take care of maintaining the RTC and do all adjustments. - * This matches the behavior of Windows, which leaves the RTC alone if the - * registry tells that the RTC runs in UTC. - */ - r = clock_set_timezone(&min); - if (r < 0) - log_error_errno(r, "Failed to apply local time delta, ignoring: %m"); - else - log_info("RTC configured in localtime, applying delta of %i minutes to system time.", min); - } else if (!in_initrd()) { - /* - * Do a dummy very first call to seal the kernel's time warp magic. - * - * Do not call this this from inside the initrd. The initrd might not - * carry /etc/adjtime with LOCAL, but the real system could be set up - * that way. In such case, we need to delay the time-warp or the sealing - * until we reach the real system. - * - * Do no set the kernel's timezone. The concept of local time cannot - * be supported reliably, the time will jump or be incorrect at every daylight - * saving time change. All kernel local time concepts will be treated - * as UTC that way. - */ - (void) clock_reset_timewarp(); - } - - r = clock_apply_epoch(); - if (r < 0) - log_error_errno(r, "Current system time is before build time, but cannot correct: %m"); - else if (r > 0) - log_info("System time before build time, advancing clock."); - } - - /* Set the default for later on, but don't actually - * open the logs like this for now. Note that if we - * are transitioning from the initrd there might still - * be journal fd open, and we shouldn't attempt - * opening that before we parsed /proc/cmdline which - * might redirect output elsewhere. */ - log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); - - } else if (getpid() == 1) { - /* Running inside a container, as PID 1 */ - arg_system = true; - log_set_target(LOG_TARGET_CONSOLE); - log_close_console(); /* force reopen of /dev/console */ - log_open(); - - /* For the later on, see above... */ - log_set_target(LOG_TARGET_JOURNAL); - - /* clear the kernel timestamp, - * because we are in a container */ - kernel_timestamp = DUAL_TIMESTAMP_NULL; - } else { - /* Running as user instance */ - arg_system = false; - log_set_target(LOG_TARGET_AUTO); - log_open(); - - /* clear the kernel timestamp, - * because we are not PID 1 */ - kernel_timestamp = DUAL_TIMESTAMP_NULL; - } - - if (getpid() == 1) { - /* Don't limit the core dump size, so that coredump handlers such as systemd-coredump (which honour the limit) - * will process core dumps for system services by default. */ - (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(RLIM_INFINITY)); - - /* But at the same time, turn off the core_pattern logic by default, so that no coredumps are stored - * until the systemd-coredump tool is enabled via sysctl. */ - if (!skip_setup) - (void) write_string_file("/proc/sys/kernel/core_pattern", "|/bin/false", 0); - } - - /* Initialize default unit */ - r = free_and_strdup(&arg_default_unit, SPECIAL_DEFAULT_TARGET); - if (r < 0) { - log_emergency_errno(r, "Failed to set default unit %s: %m", SPECIAL_DEFAULT_TARGET); - error_message = "Failed to set default unit"; - goto finish; - } - - r = initialize_join_controllers(); - if (r < 0) { - error_message = "Failed to initialize cgroup controllers"; - goto finish; - } - - /* Mount /proc, /sys and friends, so that /proc/cmdline and - * /proc/$PID/fd is available. */ - if (getpid() == 1) { - - /* Load the kernel modules early, so that we kdbus.ko is loaded before kdbusfs shall be mounted */ - if (!skip_setup) - kmod_setup(); - - r = mount_setup(loaded_policy); - if (r < 0) { - error_message = "Failed to mount API filesystems"; - goto finish; - } - } - - /* Reset all signal handlers. */ - (void) reset_all_signal_handlers(); - (void) ignore_signals(SIGNALS_IGNORE, -1); - - if (parse_config_file() < 0) { - error_message = "Failed to parse config file"; - goto finish; - } - - if (arg_system) { - r = parse_proc_cmdline(parse_proc_cmdline_item); - if (r < 0) - log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - } - - /* Note that this also parses bits from the kernel command - * line, including "debug". */ - log_parse_environment(); - - if (parse_argv(argc, argv) < 0) { - error_message = "Failed to parse commandline arguments"; - goto finish; - } - - if (arg_action == ACTION_TEST && - geteuid() == 0) { - log_error("Don't run test mode as root."); - goto finish; - } - - if (!arg_system && - arg_action == ACTION_RUN && - sd_booted() <= 0) { - log_error("Trying to run as user instance, but the system has not been booted with systemd."); - goto finish; - } - - if (arg_system && - arg_action == ACTION_RUN && - running_in_chroot() > 0) { - log_error("Cannot be run in a chroot() environment."); - goto finish; - } - - if (arg_action == ACTION_TEST) - skip_setup = true; - - if (arg_action == ACTION_TEST || arg_action == ACTION_HELP) - pager_open(arg_no_pager, false); - - if (arg_action == ACTION_HELP) { - retval = help(); - goto finish; - } else if (arg_action == ACTION_VERSION) { - retval = version(); - goto finish; - } else if (arg_action == ACTION_DUMP_CONFIGURATION_ITEMS) { - unit_dump_config_items(stdout); - retval = EXIT_SUCCESS; - goto finish; - } else if (arg_action == ACTION_DONE) { - retval = EXIT_SUCCESS; - goto finish; - } - - if (!arg_system && - !getenv("XDG_RUNTIME_DIR")) { - log_error("Trying to run as user instance, but $XDG_RUNTIME_DIR is not set."); - goto finish; - } - - assert_se(arg_action == ACTION_RUN || arg_action == ACTION_TEST); - - /* Close logging fds, in order not to confuse fdset below */ - log_close(); - - /* Remember open file descriptors for later deserialization */ - r = fdset_new_fill(&fds); - if (r < 0) { - log_emergency_errno(r, "Failed to allocate fd set: %m"); - error_message = "Failed to allocate fd set"; - goto finish; - } else - fdset_cloexec(fds, true); - - if (arg_serialization) - assert_se(fdset_remove(fds, fileno(arg_serialization)) >= 0); - - if (arg_system) - /* Become a session leader if we aren't one yet. */ - setsid(); - - /* Move out of the way, so that we won't block unmounts */ - assert_se(chdir("/") == 0); - - /* Reset the console, but only if this is really init and we - * are freshly booted */ - if (arg_system && arg_action == ACTION_RUN) { - - /* If we are init, we connect stdin/stdout/stderr to - * /dev/null and make sure we don't have a controlling - * tty. */ - release_terminal(); - - if (getpid() == 1 && !skip_setup) - console_setup(); - } - - /* Open the logging devices, if possible and necessary */ - log_open(); - - if (arg_show_status == _SHOW_STATUS_UNSET) - arg_show_status = SHOW_STATUS_YES; - - /* Make sure we leave a core dump without panicing the - * kernel. */ - if (getpid() == 1) { - install_crash_handler(); - - r = mount_cgroup_controllers(arg_join_controllers); - if (r < 0) - goto finish; - } - - if (arg_system) { - int v; - - log_info(PACKAGE_STRING " running in %ssystem mode. (" SYSTEMD_FEATURES ")", - arg_action == ACTION_TEST ? "test " : "" ); - - v = detect_virtualization(); - if (v > 0) - log_info("Detected virtualization %s.", virtualization_to_string(v)); - - write_container_id(); - - log_info("Detected architecture %s.", architecture_to_string(uname_architecture())); - - if (in_initrd()) - log_info("Running in initial RAM disk."); - - /* Let's check whether /etc is already populated. We - * don't actually really check for that, but use - * /etc/machine-id as flag file. This allows container - * managers and installers to provision a couple of - * files already. If the container manager wants to - * provision the machine ID itself it should pass - * $container_uuid to PID 1. */ - - empty_etc = access("/etc/machine-id", F_OK) < 0; - if (empty_etc) - log_info("Running with unpopulated /etc."); - } else { - _cleanup_free_ char *t; - - t = uid_to_name(getuid()); - log_debug(PACKAGE_STRING " running in %suser mode for user "UID_FMT"/%s. (" SYSTEMD_FEATURES ")", - arg_action == ACTION_TEST ? " test" : "", getuid(), t); - } - - if (arg_system && !skip_setup) { - if (arg_show_status > 0) - status_welcome(); - - hostname_setup(); - machine_id_setup(NULL, arg_machine_id); - loopback_setup(); - bump_unix_max_dgram_qlen(); - - test_usr(); - } - - if (arg_system && arg_runtime_watchdog > 0 && arg_runtime_watchdog != USEC_INFINITY) - watchdog_set_timeout(&arg_runtime_watchdog); - - if (arg_timer_slack_nsec != NSEC_INFINITY) - if (prctl(PR_SET_TIMERSLACK, arg_timer_slack_nsec) < 0) - log_error_errno(errno, "Failed to adjust timer slack: %m"); - - if (!cap_test_all(arg_capability_bounding_set)) { - r = capability_bounding_set_drop_usermode(arg_capability_bounding_set); - if (r < 0) { - log_emergency_errno(r, "Failed to drop capability bounding set of usermode helpers: %m"); - error_message = "Failed to drop capability bounding set of usermode helpers"; - goto finish; - } - r = capability_bounding_set_drop(arg_capability_bounding_set, true); - if (r < 0) { - log_emergency_errno(r, "Failed to drop capability bounding set: %m"); - error_message = "Failed to drop capability bounding set"; - goto finish; - } - } - - if (arg_syscall_archs) { - r = enforce_syscall_archs(arg_syscall_archs); - if (r < 0) { - error_message = "Failed to set syscall architectures"; - goto finish; - } - } - - if (!arg_system) - /* Become reaper of our children */ - if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) - log_warning_errno(errno, "Failed to make us a subreaper: %m"); - - if (arg_system) { - bump_rlimit_nofile(&saved_rlimit_nofile); - - if (empty_etc) { - r = unit_file_preset_all(UNIT_FILE_SYSTEM, false, NULL, UNIT_FILE_PRESET_ENABLE_ONLY, false, NULL, 0); - if (r < 0) - log_full_errno(r == -EEXIST ? LOG_NOTICE : LOG_WARNING, r, "Failed to populate /etc with preset unit settings, ignoring: %m"); - else - log_info("Populated /etc with preset unit settings."); - } - } - - r = manager_new(arg_system ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, arg_action == ACTION_TEST, &m); - if (r < 0) { - log_emergency_errno(r, "Failed to allocate manager object: %m"); - error_message = "Failed to allocate manager object"; - goto finish; - } - - m->confirm_spawn = arg_confirm_spawn; - m->runtime_watchdog = arg_runtime_watchdog; - m->shutdown_watchdog = arg_shutdown_watchdog; - m->userspace_timestamp = userspace_timestamp; - m->kernel_timestamp = kernel_timestamp; - m->initrd_timestamp = initrd_timestamp; - m->security_start_timestamp = security_start_timestamp; - m->security_finish_timestamp = security_finish_timestamp; - - manager_set_defaults(m); - manager_set_show_status(m, arg_show_status); - manager_set_first_boot(m, empty_etc); - - /* Remember whether we should queue the default job */ - queue_default_job = !arg_serialization || arg_switched_root; - - before_startup = now(CLOCK_MONOTONIC); - - r = manager_startup(m, arg_serialization, fds); - if (r < 0) - log_error_errno(r, "Failed to fully start up daemon: %m"); - - /* This will close all file descriptors that were opened, but - * not claimed by any unit. */ - fds = fdset_free(fds); - - arg_serialization = safe_fclose(arg_serialization); - - if (queue_default_job) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - Unit *target = NULL; - Job *default_unit_job; - - log_debug("Activating default unit: %s", arg_default_unit); - - r = manager_load_unit(m, arg_default_unit, NULL, &error, &target); - if (r < 0) - log_error("Failed to load default target: %s", bus_error_message(&error, r)); - else if (target->load_state == UNIT_ERROR || target->load_state == UNIT_NOT_FOUND) - log_error_errno(target->load_error, "Failed to load default target: %m"); - else if (target->load_state == UNIT_MASKED) - log_error("Default target masked."); - - if (!target || target->load_state != UNIT_LOADED) { - log_info("Trying to load rescue target..."); - - r = manager_load_unit(m, SPECIAL_RESCUE_TARGET, NULL, &error, &target); - if (r < 0) { - log_emergency("Failed to load rescue target: %s", bus_error_message(&error, r)); - error_message = "Failed to load rescue target"; - goto finish; - } else if (target->load_state == UNIT_ERROR || target->load_state == UNIT_NOT_FOUND) { - log_emergency_errno(target->load_error, "Failed to load rescue target: %m"); - error_message = "Failed to load rescue target"; - goto finish; - } else if (target->load_state == UNIT_MASKED) { - log_emergency("Rescue target masked."); - error_message = "Rescue target masked"; - goto finish; - } - } - - assert(target->load_state == UNIT_LOADED); - - if (arg_action == ACTION_TEST) { - printf("-> By units:\n"); - manager_dump_units(m, stdout, "\t"); - } - - r = manager_add_job(m, JOB_START, target, JOB_ISOLATE, &error, &default_unit_job); - if (r == -EPERM) { - log_debug("Default target could not be isolated, starting instead: %s", bus_error_message(&error, r)); - - sd_bus_error_free(&error); - - r = manager_add_job(m, JOB_START, target, JOB_REPLACE, &error, &default_unit_job); - if (r < 0) { - log_emergency("Failed to start default target: %s", bus_error_message(&error, r)); - error_message = "Failed to start default target"; - goto finish; - } - } else if (r < 0) { - log_emergency("Failed to isolate default target: %s", bus_error_message(&error, r)); - error_message = "Failed to isolate default target"; - goto finish; - } - - m->default_unit_job_id = default_unit_job->id; - - after_startup = now(CLOCK_MONOTONIC); - log_full(arg_action == ACTION_TEST ? LOG_INFO : LOG_DEBUG, - "Loaded units and determined initial transaction in %s.", - format_timespan(timespan, sizeof(timespan), after_startup - before_startup, 100 * USEC_PER_MSEC)); - - if (arg_action == ACTION_TEST) { - printf("-> By jobs:\n"); - manager_dump_jobs(m, stdout, "\t"); - retval = EXIT_SUCCESS; - goto finish; - } - } - - for (;;) { - r = manager_loop(m); - if (r < 0) { - log_emergency_errno(r, "Failed to run main loop: %m"); - error_message = "Failed to run main loop"; - goto finish; - } - - switch (m->exit_code) { - - case MANAGER_RELOAD: - log_info("Reloading."); - - r = parse_config_file(); - if (r < 0) - log_error("Failed to parse config file."); - - manager_set_defaults(m); - - r = manager_reload(m); - if (r < 0) - log_error_errno(r, "Failed to reload: %m"); - break; - - case MANAGER_REEXECUTE: - - if (prepare_reexecute(m, &arg_serialization, &fds, false) < 0) { - error_message = "Failed to prepare for reexecution"; - goto finish; - } - - reexecute = true; - log_notice("Reexecuting."); - goto finish; - - case MANAGER_SWITCH_ROOT: - /* Steal the switch root parameters */ - switch_root_dir = m->switch_root; - switch_root_init = m->switch_root_init; - m->switch_root = m->switch_root_init = NULL; - - if (!switch_root_init) - if (prepare_reexecute(m, &arg_serialization, &fds, true) < 0) { - error_message = "Failed to prepare for reexecution"; - goto finish; - } - - reexecute = true; - log_notice("Switching root."); - goto finish; - - case MANAGER_EXIT: - retval = m->return_value; - - if (MANAGER_IS_USER(m)) { - log_debug("Exit."); - goto finish; - } - - /* fallthrough */ - case MANAGER_REBOOT: - case MANAGER_POWEROFF: - case MANAGER_HALT: - case MANAGER_KEXEC: { - static const char * const table[_MANAGER_EXIT_CODE_MAX] = { - [MANAGER_EXIT] = "exit", - [MANAGER_REBOOT] = "reboot", - [MANAGER_POWEROFF] = "poweroff", - [MANAGER_HALT] = "halt", - [MANAGER_KEXEC] = "kexec" - }; - - assert_se(shutdown_verb = table[m->exit_code]); - arm_reboot_watchdog = m->exit_code == MANAGER_REBOOT; - - log_notice("Shutting down."); - goto finish; - } - - default: - assert_not_reached("Unknown exit code."); - } - } - -finish: - pager_close(); - - if (m) - arg_shutdown_watchdog = m->shutdown_watchdog; - - m = manager_free(m); - - for (j = 0; j < ELEMENTSOF(arg_default_rlimit); j++) - arg_default_rlimit[j] = mfree(arg_default_rlimit[j]); - - arg_default_unit = mfree(arg_default_unit); - arg_join_controllers = strv_free_free(arg_join_controllers); - arg_default_environment = strv_free(arg_default_environment); - arg_syscall_archs = set_free(arg_syscall_archs); - - mac_selinux_finish(); - - if (reexecute) { - const char **args; - unsigned i, args_size; - - /* Close and disarm the watchdog, so that the new - * instance can reinitialize it, but doesn't get - * rebooted while we do that */ - watchdog_close(true); - - /* Reset the RLIMIT_NOFILE to the kernel default, so - * that the new systemd can pass the kernel default to - * its child processes */ - if (saved_rlimit_nofile.rlim_cur > 0) - (void) setrlimit(RLIMIT_NOFILE, &saved_rlimit_nofile); - - if (switch_root_dir) { - /* Kill all remaining processes from the - * initrd, but don't wait for them, so that we - * can handle the SIGCHLD for them after - * deserializing. */ - broadcast_signal(SIGTERM, false, true); - - /* And switch root with MS_MOVE, because we remove the old directory afterwards and detach it. */ - r = switch_root(switch_root_dir, "/mnt", true, MS_MOVE); - if (r < 0) - log_error_errno(r, "Failed to switch root, trying to continue: %m"); - } - - args_size = MAX(6, argc+1); - args = newa(const char*, args_size); - - if (!switch_root_init) { - char sfd[DECIMAL_STR_MAX(int) + 1]; - - /* First try to spawn ourselves with the right - * path, and with full serialization. We do - * this only if the user didn't specify an - * explicit init to spawn. */ - - assert(arg_serialization); - assert(fds); - - xsprintf(sfd, "%i", fileno(arg_serialization)); - - i = 0; - args[i++] = SYSTEMD_BINARY_PATH; - if (switch_root_dir) - args[i++] = "--switched-root"; - args[i++] = arg_system ? "--system" : "--user"; - args[i++] = "--deserialize"; - args[i++] = sfd; - args[i++] = NULL; - - /* do not pass along the environment we inherit from the kernel or initrd */ - if (switch_root_dir) - (void) clearenv(); - - assert(i <= args_size); - - /* - * We want valgrind to print its memory usage summary before reexecution. - * Valgrind won't do this is on its own on exec(), but it will do it on exit(). - * Hence, to ensure we get a summary here, fork() off a child, let it exit() cleanly, - * so that it prints the summary, and wait() for it in the parent, before proceeding into the exec(). - */ - valgrind_summary_hack(); - - (void) execv(args[0], (char* const*) args); - } - - /* Try the fallback, if there is any, without any - * serialization. We pass the original argv[] and - * envp[]. (Well, modulo the ordering changes due to - * getopt() in argv[], and some cleanups in envp[], - * but let's hope that doesn't matter.) */ - - arg_serialization = safe_fclose(arg_serialization); - fds = fdset_free(fds); - - /* Reopen the console */ - (void) make_console_stdio(); - - for (j = 1, i = 1; j < (unsigned) argc; j++) - args[i++] = argv[j]; - args[i++] = NULL; - assert(i <= args_size); - - /* Reenable any blocked signals, especially important - * if we switch from initial ramdisk to init=... */ - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - if (switch_root_init) { - args[0] = switch_root_init; - (void) execv(args[0], (char* const*) args); - log_warning_errno(errno, "Failed to execute configured init, trying fallback: %m"); - } - - args[0] = "/sbin/init"; - (void) execv(args[0], (char* const*) args); - - if (errno == ENOENT) { - log_warning("No /sbin/init, trying fallback"); - - args[0] = "/bin/sh"; - args[1] = NULL; - (void) execv(args[0], (char* const*) args); - log_error_errno(errno, "Failed to execute /bin/sh, giving up: %m"); - } else - log_warning_errno(errno, "Failed to execute /sbin/init, giving up: %m"); - } - - arg_serialization = safe_fclose(arg_serialization); - fds = fdset_free(fds); - -#ifdef HAVE_VALGRIND_VALGRIND_H - /* If we are PID 1 and running under valgrind, then let's exit - * here explicitly. valgrind will only generate nice output on - * exit(), not on exec(), hence let's do the former not the - * latter here. */ - if (getpid() == 1 && RUNNING_ON_VALGRIND) - return 0; -#endif - - if (shutdown_verb) { - char log_level[DECIMAL_STR_MAX(int) + 1]; - char exit_code[DECIMAL_STR_MAX(uint8_t) + 1]; - const char* command_line[11] = { - SYSTEMD_SHUTDOWN_BINARY_PATH, - shutdown_verb, - "--log-level", log_level, - "--log-target", - }; - unsigned pos = 5; - _cleanup_strv_free_ char **env_block = NULL; - - assert(command_line[pos] == NULL); - env_block = strv_copy(environ); - - xsprintf(log_level, "%d", log_get_max_level()); - - switch (log_get_target()) { - - case LOG_TARGET_KMSG: - case LOG_TARGET_JOURNAL_OR_KMSG: - case LOG_TARGET_SYSLOG_OR_KMSG: - command_line[pos++] = "kmsg"; - break; - - case LOG_TARGET_NULL: - command_line[pos++] = "null"; - break; - - case LOG_TARGET_CONSOLE: - default: - command_line[pos++] = "console"; - break; - }; - - if (log_get_show_color()) - command_line[pos++] = "--log-color"; - - if (log_get_show_location()) - command_line[pos++] = "--log-location"; - - if (streq(shutdown_verb, "exit")) { - command_line[pos++] = "--exit-code"; - command_line[pos++] = exit_code; - xsprintf(exit_code, "%d", retval); - } - - assert(pos < ELEMENTSOF(command_line)); - - if (arm_reboot_watchdog && arg_shutdown_watchdog > 0 && arg_shutdown_watchdog != USEC_INFINITY) { - char *e; - - /* If we reboot let's set the shutdown - * watchdog and tell the shutdown binary to - * repeatedly ping it */ - r = watchdog_set_timeout(&arg_shutdown_watchdog); - watchdog_close(r < 0); - - /* Tell the binary how often to ping, ignore failure */ - if (asprintf(&e, "WATCHDOG_USEC="USEC_FMT, arg_shutdown_watchdog) > 0) - (void) strv_push(&env_block, e); - } else - watchdog_close(true); - - /* Avoid the creation of new processes forked by the - * kernel; at this point, we will not listen to the - * signals anyway */ - if (detect_container() <= 0) - (void) cg_uninstall_release_agent(SYSTEMD_CGROUP_CONTROLLER); - - execve(SYSTEMD_SHUTDOWN_BINARY_PATH, (char **) command_line, env_block); - log_error_errno(errno, "Failed to execute shutdown binary, %s: %m", - getpid() == 1 ? "freezing" : "quitting"); - } - - if (getpid() == 1) { - if (error_message) - manager_status_printf(NULL, STATUS_TYPE_EMERGENCY, - ANSI_HIGHLIGHT_RED "!!!!!!" ANSI_NORMAL, - "%s, freezing.", error_message); - freeze_or_reboot(); - } - - return retval; -} diff --git a/src/core/manager.c b/src/core/manager.c deleted file mode 100644 index 7838f56fd2..0000000000 --- a/src/core/manager.c +++ /dev/null @@ -1,3134 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_AUDIT -#include -#endif - -#include "sd-daemon.h" -#include "sd-messages.h" - -#include "alloc-util.h" -#include "audit-fd.h" -#include "boot-timestamps.h" -#include "bus-common-errors.h" -#include "bus-error.h" -#include "bus-kernel.h" -#include "bus-util.h" -#include "dbus-job.h" -#include "dbus-manager.h" -#include "dbus-unit.h" -#include "dbus.h" -#include "dirent-util.h" -#include "env-util.h" -#include "escape.h" -#include "exit-status.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "hashmap.h" -#include "io-util.h" -#include "locale-setup.h" -#include "log.h" -#include "macro.h" -#include "manager.h" -#include "missing.h" -#include "mkdir.h" -#include "mkdir.h" -#include "parse-util.h" -#include "path-lookup.h" -#include "path-util.h" -#include "process-util.h" -#include "ratelimit.h" -#include "rm-rf.h" -#include "signal-util.h" -#include "special.h" -#include "stat-util.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "time-util.h" -#include "transaction.h" -#include "umask-util.h" -#include "unit-name.h" -#include "util.h" -#include "virt.h" -#include "watchdog.h" - -#define NOTIFY_RCVBUF_SIZE (8*1024*1024) -#define CGROUPS_AGENT_RCVBUF_SIZE (8*1024*1024) - -/* Initial delay and the interval for printing status messages about running jobs */ -#define JOBS_IN_PROGRESS_WAIT_USEC (5*USEC_PER_SEC) -#define JOBS_IN_PROGRESS_PERIOD_USEC (USEC_PER_SEC / 3) -#define JOBS_IN_PROGRESS_PERIOD_DIVISOR 3 - -static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); -static int manager_dispatch_cgroups_agent_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); -static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); -static int manager_dispatch_time_change_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); -static int manager_dispatch_idle_pipe_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); -static int manager_dispatch_jobs_in_progress(sd_event_source *source, usec_t usec, void *userdata); -static int manager_dispatch_run_queue(sd_event_source *source, void *userdata); -static int manager_run_generators(Manager *m); - -static void manager_watch_jobs_in_progress(Manager *m) { - usec_t next; - int r; - - assert(m); - - if (m->jobs_in_progress_event_source) - return; - - next = now(CLOCK_MONOTONIC) + JOBS_IN_PROGRESS_WAIT_USEC; - r = sd_event_add_time( - m->event, - &m->jobs_in_progress_event_source, - CLOCK_MONOTONIC, - next, 0, - manager_dispatch_jobs_in_progress, m); - if (r < 0) - return; - - (void) sd_event_source_set_description(m->jobs_in_progress_event_source, "manager-jobs-in-progress"); -} - -#define CYLON_BUFFER_EXTRA (2*(sizeof(ANSI_RED)-1) + sizeof(ANSI_HIGHLIGHT_RED)-1 + 2*(sizeof(ANSI_NORMAL)-1)) - -static void draw_cylon(char buffer[], size_t buflen, unsigned width, unsigned pos) { - char *p = buffer; - - assert(buflen >= CYLON_BUFFER_EXTRA + width + 1); - assert(pos <= width+1); /* 0 or width+1 mean that the center light is behind the corner */ - - if (pos > 1) { - if (pos > 2) - p = mempset(p, ' ', pos-2); - p = stpcpy(p, ANSI_RED); - *p++ = '*'; - } - - if (pos > 0 && pos <= width) { - p = stpcpy(p, ANSI_HIGHLIGHT_RED); - *p++ = '*'; - } - - p = stpcpy(p, ANSI_NORMAL); - - if (pos < width) { - p = stpcpy(p, ANSI_RED); - *p++ = '*'; - if (pos < width-1) - p = mempset(p, ' ', width-1-pos); - strcpy(p, ANSI_NORMAL); - } -} - -void manager_flip_auto_status(Manager *m, bool enable) { - assert(m); - - if (enable) { - if (m->show_status == SHOW_STATUS_AUTO) - manager_set_show_status(m, SHOW_STATUS_TEMPORARY); - } else { - if (m->show_status == SHOW_STATUS_TEMPORARY) - manager_set_show_status(m, SHOW_STATUS_AUTO); - } -} - -static void manager_print_jobs_in_progress(Manager *m) { - _cleanup_free_ char *job_of_n = NULL; - Iterator i; - Job *j; - unsigned counter = 0, print_nr; - char cylon[6 + CYLON_BUFFER_EXTRA + 1]; - unsigned cylon_pos; - char time[FORMAT_TIMESPAN_MAX], limit[FORMAT_TIMESPAN_MAX] = "no limit"; - uint64_t x; - - assert(m); - assert(m->n_running_jobs > 0); - - manager_flip_auto_status(m, true); - - print_nr = (m->jobs_in_progress_iteration / JOBS_IN_PROGRESS_PERIOD_DIVISOR) % m->n_running_jobs; - - HASHMAP_FOREACH(j, m->jobs, i) - if (j->state == JOB_RUNNING && counter++ == print_nr) - break; - - /* m->n_running_jobs must be consistent with the contents of m->jobs, - * so the above loop must have succeeded in finding j. */ - assert(counter == print_nr + 1); - assert(j); - - cylon_pos = m->jobs_in_progress_iteration % 14; - if (cylon_pos >= 8) - cylon_pos = 14 - cylon_pos; - draw_cylon(cylon, sizeof(cylon), 6, cylon_pos); - - m->jobs_in_progress_iteration++; - - if (m->n_running_jobs > 1) { - if (asprintf(&job_of_n, "(%u of %u) ", counter, m->n_running_jobs) < 0) - job_of_n = NULL; - } - - format_timespan(time, sizeof(time), now(CLOCK_MONOTONIC) - j->begin_usec, 1*USEC_PER_SEC); - if (job_get_timeout(j, &x) > 0) - format_timespan(limit, sizeof(limit), x - j->begin_usec, 1*USEC_PER_SEC); - - manager_status_printf(m, STATUS_TYPE_EPHEMERAL, cylon, - "%sA %s job is running for %s (%s / %s)", - strempty(job_of_n), - job_type_to_string(j->type), - unit_description(j->unit), - time, limit); -} - -static int have_ask_password(void) { - _cleanup_closedir_ DIR *dir; - - dir = opendir("/run/systemd/ask-password"); - if (!dir) { - if (errno == ENOENT) - return false; - else - return -errno; - } - - for (;;) { - struct dirent *de; - - errno = 0; - de = readdir(dir); - if (!de && errno > 0) - return -errno; - if (!de) - return false; - - if (startswith(de->d_name, "ask.")) - return true; - } -} - -static int manager_dispatch_ask_password_fd(sd_event_source *source, - int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - - assert(m); - - flush_fd(fd); - - m->have_ask_password = have_ask_password(); - if (m->have_ask_password < 0) - /* Log error but continue. Negative have_ask_password - * is treated as unknown status. */ - log_error_errno(m->have_ask_password, "Failed to list /run/systemd/ask-password: %m"); - - return 0; -} - -static void manager_close_ask_password(Manager *m) { - assert(m); - - m->ask_password_event_source = sd_event_source_unref(m->ask_password_event_source); - m->ask_password_inotify_fd = safe_close(m->ask_password_inotify_fd); - m->have_ask_password = -EINVAL; -} - -static int manager_check_ask_password(Manager *m) { - int r; - - assert(m); - - if (!m->ask_password_event_source) { - assert(m->ask_password_inotify_fd < 0); - - mkdir_p_label("/run/systemd/ask-password", 0755); - - m->ask_password_inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); - if (m->ask_password_inotify_fd < 0) - return log_error_errno(errno, "inotify_init1() failed: %m"); - - if (inotify_add_watch(m->ask_password_inotify_fd, "/run/systemd/ask-password", IN_CREATE|IN_DELETE|IN_MOVE) < 0) { - log_error_errno(errno, "Failed to add watch on /run/systemd/ask-password: %m"); - manager_close_ask_password(m); - return -errno; - } - - r = sd_event_add_io(m->event, &m->ask_password_event_source, - m->ask_password_inotify_fd, EPOLLIN, - manager_dispatch_ask_password_fd, m); - if (r < 0) { - log_error_errno(errno, "Failed to add event source for /run/systemd/ask-password: %m"); - manager_close_ask_password(m); - return -errno; - } - - (void) sd_event_source_set_description(m->ask_password_event_source, "manager-ask-password"); - - /* Queries might have been added meanwhile... */ - manager_dispatch_ask_password_fd(m->ask_password_event_source, - m->ask_password_inotify_fd, EPOLLIN, m); - } - - return m->have_ask_password; -} - -static int manager_watch_idle_pipe(Manager *m) { - int r; - - assert(m); - - if (m->idle_pipe_event_source) - return 0; - - if (m->idle_pipe[2] < 0) - return 0; - - r = sd_event_add_io(m->event, &m->idle_pipe_event_source, m->idle_pipe[2], EPOLLIN, manager_dispatch_idle_pipe_fd, m); - if (r < 0) - return log_error_errno(r, "Failed to watch idle pipe: %m"); - - (void) sd_event_source_set_description(m->idle_pipe_event_source, "manager-idle-pipe"); - - return 0; -} - -static void manager_close_idle_pipe(Manager *m) { - assert(m); - - m->idle_pipe_event_source = sd_event_source_unref(m->idle_pipe_event_source); - - safe_close_pair(m->idle_pipe); - safe_close_pair(m->idle_pipe + 2); -} - -static int manager_setup_time_change(Manager *m) { - int r; - - /* We only care for the cancellation event, hence we set the - * timeout to the latest possible value. */ - struct itimerspec its = { - .it_value.tv_sec = TIME_T_MAX, - }; - - assert(m); - assert_cc(sizeof(time_t) == sizeof(TIME_T_MAX)); - - if (m->test_run) - return 0; - - /* Uses TFD_TIMER_CANCEL_ON_SET to get notifications whenever - * CLOCK_REALTIME makes a jump relative to CLOCK_MONOTONIC */ - - m->time_change_fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC); - if (m->time_change_fd < 0) - return log_error_errno(errno, "Failed to create timerfd: %m"); - - if (timerfd_settime(m->time_change_fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0) { - log_debug_errno(errno, "Failed to set up TFD_TIMER_CANCEL_ON_SET, ignoring: %m"); - m->time_change_fd = safe_close(m->time_change_fd); - return 0; - } - - r = sd_event_add_io(m->event, &m->time_change_event_source, m->time_change_fd, EPOLLIN, manager_dispatch_time_change_fd, m); - if (r < 0) - return log_error_errno(r, "Failed to create time change event source: %m"); - - (void) sd_event_source_set_description(m->time_change_event_source, "manager-time-change"); - - log_debug("Set up TFD_TIMER_CANCEL_ON_SET timerfd."); - - return 0; -} - -static int enable_special_signals(Manager *m) { - _cleanup_close_ int fd = -1; - - assert(m); - - if (m->test_run) - return 0; - - /* Enable that we get SIGINT on control-alt-del. In containers - * this will fail with EPERM (older) or EINVAL (newer), so - * ignore that. */ - if (reboot(RB_DISABLE_CAD) < 0 && errno != EPERM && errno != EINVAL) - log_warning_errno(errno, "Failed to enable ctrl-alt-del handling: %m"); - - fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) { - /* Support systems without virtual console */ - if (fd != -ENOENT) - log_warning_errno(errno, "Failed to open /dev/tty0: %m"); - } else { - /* Enable that we get SIGWINCH on kbrequest */ - if (ioctl(fd, KDSIGACCEPT, SIGWINCH) < 0) - log_warning_errno(errno, "Failed to enable kbrequest handling: %m"); - } - - return 0; -} - -static int manager_setup_signals(Manager *m) { - struct sigaction sa = { - .sa_handler = SIG_DFL, - .sa_flags = SA_NOCLDSTOP|SA_RESTART, - }; - sigset_t mask; - int r; - - assert(m); - - assert_se(sigaction(SIGCHLD, &sa, NULL) == 0); - - /* We make liberal use of realtime signals here. On - * Linux/glibc we have 30 of them (with the exception of Linux - * on hppa, see below), between SIGRTMIN+0 ... SIGRTMIN+30 - * (aka SIGRTMAX). */ - - assert_se(sigemptyset(&mask) == 0); - sigset_add_many(&mask, - SIGCHLD, /* Child died */ - SIGTERM, /* Reexecute daemon */ - SIGHUP, /* Reload configuration */ - SIGUSR1, /* systemd/upstart: reconnect to D-Bus */ - SIGUSR2, /* systemd: dump status */ - SIGINT, /* Kernel sends us this on control-alt-del */ - SIGWINCH, /* Kernel sends us this on kbrequest (alt-arrowup) */ - SIGPWR, /* Some kernel drivers and upsd send us this on power failure */ - - SIGRTMIN+0, /* systemd: start default.target */ - SIGRTMIN+1, /* systemd: isolate rescue.target */ - SIGRTMIN+2, /* systemd: isolate emergency.target */ - SIGRTMIN+3, /* systemd: start halt.target */ - SIGRTMIN+4, /* systemd: start poweroff.target */ - SIGRTMIN+5, /* systemd: start reboot.target */ - SIGRTMIN+6, /* systemd: start kexec.target */ - - /* ... space for more special targets ... */ - - SIGRTMIN+13, /* systemd: Immediate halt */ - SIGRTMIN+14, /* systemd: Immediate poweroff */ - SIGRTMIN+15, /* systemd: Immediate reboot */ - SIGRTMIN+16, /* systemd: Immediate kexec */ - - /* ... space for more immediate system state changes ... */ - - SIGRTMIN+20, /* systemd: enable status messages */ - SIGRTMIN+21, /* systemd: disable status messages */ - SIGRTMIN+22, /* systemd: set log level to LOG_DEBUG */ - SIGRTMIN+23, /* systemd: set log level to LOG_INFO */ - SIGRTMIN+24, /* systemd: Immediate exit (--user only) */ - - /* .. one free signal here ... */ - -#if !defined(__hppa64__) && !defined(__hppa__) - /* Apparently Linux on hppa has fewer RT - * signals (SIGRTMAX is SIGRTMIN+25 there), - * hence let's not try to make use of them - * here. Since these commands are accessible - * by different means and only really a safety - * net, the missing functionality on hppa - * shouldn't matter. */ - - SIGRTMIN+26, /* systemd: set log target to journal-or-kmsg */ - SIGRTMIN+27, /* systemd: set log target to console */ - SIGRTMIN+28, /* systemd: set log target to kmsg */ - SIGRTMIN+29, /* systemd: set log target to syslog-or-kmsg (obsolete) */ - - /* ... one free signal here SIGRTMIN+30 ... */ -#endif - -1); - assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); - - m->signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); - if (m->signal_fd < 0) - return -errno; - - r = sd_event_add_io(m->event, &m->signal_event_source, m->signal_fd, EPOLLIN, manager_dispatch_signal_fd, m); - if (r < 0) - return r; - - (void) sd_event_source_set_description(m->signal_event_source, "manager-signal"); - - /* Process signals a bit earlier than the rest of things, but later than notify_fd processing, so that the - * notify processing can still figure out to which process/service a message belongs, before we reap the - * process. Also, process this before handling cgroup notifications, so that we always collect child exit - * status information before detecting that there's no process in a cgroup. */ - r = sd_event_source_set_priority(m->signal_event_source, SD_EVENT_PRIORITY_NORMAL-6); - if (r < 0) - return r; - - if (MANAGER_IS_SYSTEM(m)) - return enable_special_signals(m); - - return 0; -} - -static void manager_clean_environment(Manager *m) { - assert(m); - - /* Let's remove some environment variables that we - * need ourselves to communicate with our clients */ - strv_env_unset_many( - m->environment, - "NOTIFY_SOCKET", - "MAINPID", - "MANAGERPID", - "LISTEN_PID", - "LISTEN_FDS", - "LISTEN_FDNAMES", - "WATCHDOG_PID", - "WATCHDOG_USEC", - NULL); -} - -static int manager_default_environment(Manager *m) { - assert(m); - - if (MANAGER_IS_SYSTEM(m)) { - /* The system manager always starts with a clean - * environment for its children. It does not import - * the kernel or the parents exported variables. - * - * The initial passed environ is untouched to keep - * /proc/self/environ valid; it is used for tagging - * the init process inside containers. */ - m->environment = strv_new("PATH=" DEFAULT_PATH, - NULL); - - /* Import locale variables LC_*= from configuration */ - locale_setup(&m->environment); - } else { - /* The user manager passes its own environment - * along to its children. */ - m->environment = strv_copy(environ); - } - - if (!m->environment) - return -ENOMEM; - - manager_clean_environment(m); - strv_sort(m->environment); - - return 0; -} - - -int manager_new(UnitFileScope scope, bool test_run, Manager **_m) { - Manager *m; - int r; - - assert(_m); - assert(IN_SET(scope, UNIT_FILE_SYSTEM, UNIT_FILE_USER)); - - m = new0(Manager, 1); - if (!m) - return -ENOMEM; - - m->unit_file_scope = scope; - m->exit_code = _MANAGER_EXIT_CODE_INVALID; - m->default_timer_accuracy_usec = USEC_PER_MINUTE; - m->default_tasks_accounting = true; - m->default_tasks_max = UINT64_C(512); - -#ifdef ENABLE_EFI - if (MANAGER_IS_SYSTEM(m) && detect_container() <= 0) - boot_timestamps(&m->userspace_timestamp, &m->firmware_timestamp, &m->loader_timestamp); -#endif - - /* Prepare log fields we can use for structured logging */ - if (MANAGER_IS_SYSTEM(m)) { - m->unit_log_field = "UNIT="; - m->unit_log_format_string = "UNIT=%s"; - } else { - m->unit_log_field = "USER_UNIT="; - m->unit_log_format_string = "USER_UNIT=%s"; - } - - m->idle_pipe[0] = m->idle_pipe[1] = m->idle_pipe[2] = m->idle_pipe[3] = -1; - - m->pin_cgroupfs_fd = m->notify_fd = m->cgroups_agent_fd = m->signal_fd = m->time_change_fd = - m->dev_autofs_fd = m->private_listen_fd = m->kdbus_fd = m->cgroup_inotify_fd = - m->ask_password_inotify_fd = -1; - - m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */ - - m->have_ask_password = -EINVAL; /* we don't know */ - m->first_boot = -1; - - m->test_run = test_run; - - /* Reboot immediately if the user hits C-A-D more often than 7x per 2s */ - RATELIMIT_INIT(m->ctrl_alt_del_ratelimit, 2 * USEC_PER_SEC, 7); - - r = manager_default_environment(m); - if (r < 0) - goto fail; - - r = hashmap_ensure_allocated(&m->units, &string_hash_ops); - if (r < 0) - goto fail; - - r = hashmap_ensure_allocated(&m->jobs, NULL); - if (r < 0) - goto fail; - - r = hashmap_ensure_allocated(&m->cgroup_unit, &string_hash_ops); - if (r < 0) - goto fail; - - r = hashmap_ensure_allocated(&m->watch_bus, &string_hash_ops); - if (r < 0) - goto fail; - - r = sd_event_default(&m->event); - if (r < 0) - goto fail; - - r = sd_event_add_defer(m->event, &m->run_queue_event_source, manager_dispatch_run_queue, m); - if (r < 0) - goto fail; - - r = sd_event_source_set_priority(m->run_queue_event_source, SD_EVENT_PRIORITY_IDLE); - if (r < 0) - goto fail; - - r = sd_event_source_set_enabled(m->run_queue_event_source, SD_EVENT_OFF); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(m->run_queue_event_source, "manager-run-queue"); - - r = manager_setup_signals(m); - if (r < 0) - goto fail; - - r = manager_setup_cgroup(m); - if (r < 0) - goto fail; - - r = manager_setup_time_change(m); - if (r < 0) - goto fail; - - m->udev = udev_new(); - if (!m->udev) { - r = -ENOMEM; - goto fail; - } - - /* Note that we set up neither kdbus, nor the notify fd - * here. We do that after deserialization, since they might - * have gotten serialized across the reexec. */ - - m->taint_usr = dir_is_empty("/usr") > 0; - - *_m = m; - return 0; - -fail: - manager_free(m); - return r; -} - -static int manager_setup_notify(Manager *m) { - int r; - - if (m->test_run) - return 0; - - if (m->notify_fd < 0) { - _cleanup_close_ int fd = -1; - union sockaddr_union sa = { - .sa.sa_family = AF_UNIX, - }; - static const int one = 1; - const char *e; - - /* First free all secondary fields */ - m->notify_socket = mfree(m->notify_socket); - m->notify_event_source = sd_event_source_unref(m->notify_event_source); - - fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (fd < 0) - return log_error_errno(errno, "Failed to allocate notification socket: %m"); - - fd_inc_rcvbuf(fd, NOTIFY_RCVBUF_SIZE); - - e = manager_get_runtime_prefix(m); - if (!e) { - log_error("Failed to determine runtime prefix."); - return -EINVAL; - } - - m->notify_socket = strappend(e, "/systemd/notify"); - if (!m->notify_socket) - return log_oom(); - - (void) mkdir_parents_label(m->notify_socket, 0755); - (void) unlink(m->notify_socket); - - strncpy(sa.un.sun_path, m->notify_socket, sizeof(sa.un.sun_path)-1); - r = bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); - if (r < 0) - return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path); - - r = setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); - if (r < 0) - return log_error_errno(errno, "SO_PASSCRED failed: %m"); - - m->notify_fd = fd; - fd = -1; - - log_debug("Using notification socket %s", m->notify_socket); - } - - if (!m->notify_event_source) { - r = sd_event_add_io(m->event, &m->notify_event_source, m->notify_fd, EPOLLIN, manager_dispatch_notify_fd, m); - if (r < 0) - return log_error_errno(r, "Failed to allocate notify event source: %m"); - - /* Process notification messages a bit earlier than SIGCHLD, so that we can still identify to which - * service an exit message belongs. */ - r = sd_event_source_set_priority(m->notify_event_source, SD_EVENT_PRIORITY_NORMAL-7); - if (r < 0) - return log_error_errno(r, "Failed to set priority of notify event source: %m"); - - (void) sd_event_source_set_description(m->notify_event_source, "manager-notify"); - } - - return 0; -} - -static int manager_setup_cgroups_agent(Manager *m) { - - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/cgroups-agent", - }; - int r; - - /* This creates a listening socket we receive cgroups agent messages on. We do not use D-Bus for delivering - * these messages from the cgroups agent binary to PID 1, as the cgroups agent binary is very short-living, and - * each instance of it needs a new D-Bus connection. Since D-Bus connections are SOCK_STREAM/AF_UNIX, on - * overloaded systems the backlog of the D-Bus socket becomes relevant, as not more than the configured number - * of D-Bus connections may be queued until the kernel will start dropping further incoming connections, - * possibly resulting in lost cgroups agent messages. To avoid this, we'll use a private SOCK_DGRAM/AF_UNIX - * socket, where no backlog is relevant as communication may take place without an actual connect() cycle, and - * we thus won't lose messages. - * - * Note that PID 1 will forward the agent message to system bus, so that the user systemd instance may listen - * to it. The system instance hence listens on this special socket, but the user instances listen on the system - * bus for these messages. */ - - if (m->test_run) - return 0; - - if (!MANAGER_IS_SYSTEM(m)) - return 0; - - if (cg_unified() > 0) /* We don't need this anymore on the unified hierarchy */ - return 0; - - if (m->cgroups_agent_fd < 0) { - _cleanup_close_ int fd = -1; - - /* First free all secondary fields */ - m->cgroups_agent_event_source = sd_event_source_unref(m->cgroups_agent_event_source); - - fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (fd < 0) - return log_error_errno(errno, "Failed to allocate cgroups agent socket: %m"); - - fd_inc_rcvbuf(fd, CGROUPS_AGENT_RCVBUF_SIZE); - - (void) unlink(sa.un.sun_path); - - /* Only allow root to connect to this socket */ - RUN_WITH_UMASK(0077) - r = bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); - if (r < 0) - return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path); - - m->cgroups_agent_fd = fd; - fd = -1; - } - - if (!m->cgroups_agent_event_source) { - r = sd_event_add_io(m->event, &m->cgroups_agent_event_source, m->cgroups_agent_fd, EPOLLIN, manager_dispatch_cgroups_agent_fd, m); - if (r < 0) - return log_error_errno(r, "Failed to allocate cgroups agent event source: %m"); - - /* Process cgroups notifications early, but after having processed service notification messages or - * SIGCHLD signals, so that a cgroup running empty is always just the last safety net of notification, - * and we collected the metadata the notification and SIGCHLD stuff offers first. Also see handling of - * cgroup inotify for the unified cgroup stuff. */ - r = sd_event_source_set_priority(m->cgroups_agent_event_source, SD_EVENT_PRIORITY_NORMAL-5); - if (r < 0) - return log_error_errno(r, "Failed to set priority of cgroups agent event source: %m"); - - (void) sd_event_source_set_description(m->cgroups_agent_event_source, "manager-cgroups-agent"); - } - - return 0; -} - -static int manager_setup_kdbus(Manager *m) { - _cleanup_free_ char *p = NULL; - - assert(m); - - if (m->test_run || m->kdbus_fd >= 0) - return 0; - if (!is_kdbus_available()) - return -ESOCKTNOSUPPORT; - - m->kdbus_fd = bus_kernel_create_bus( - MANAGER_IS_SYSTEM(m) ? "system" : "user", - MANAGER_IS_SYSTEM(m), &p); - - if (m->kdbus_fd < 0) - return log_debug_errno(m->kdbus_fd, "Failed to set up kdbus: %m"); - - log_debug("Successfully set up kdbus on %s", p); - - return 0; -} - -static int manager_connect_bus(Manager *m, bool reexecuting) { - bool try_bus_connect; - - assert(m); - - if (m->test_run) - return 0; - - try_bus_connect = - m->kdbus_fd >= 0 || - reexecuting || - (MANAGER_IS_USER(m) && getenv("DBUS_SESSION_BUS_ADDRESS")); - - /* Try to connect to the buses, if possible. */ - return bus_init(m, try_bus_connect); -} - -static unsigned manager_dispatch_cleanup_queue(Manager *m) { - Unit *u; - unsigned n = 0; - - assert(m); - - while ((u = m->cleanup_queue)) { - assert(u->in_cleanup_queue); - - unit_free(u); - n++; - } - - return n; -} - -enum { - GC_OFFSET_IN_PATH, /* This one is on the path we were traveling */ - GC_OFFSET_UNSURE, /* No clue */ - GC_OFFSET_GOOD, /* We still need this unit */ - GC_OFFSET_BAD, /* We don't need this unit anymore */ - _GC_OFFSET_MAX -}; - -static void unit_gc_sweep(Unit *u, unsigned gc_marker) { - Iterator i; - Unit *other; - bool is_bad; - - assert(u); - - if (u->gc_marker == gc_marker + GC_OFFSET_GOOD || - u->gc_marker == gc_marker + GC_OFFSET_BAD || - u->gc_marker == gc_marker + GC_OFFSET_IN_PATH) - return; - - if (u->in_cleanup_queue) - goto bad; - - if (unit_check_gc(u)) - goto good; - - u->gc_marker = gc_marker + GC_OFFSET_IN_PATH; - - is_bad = true; - - SET_FOREACH(other, u->dependencies[UNIT_REFERENCED_BY], i) { - unit_gc_sweep(other, gc_marker); - - if (other->gc_marker == gc_marker + GC_OFFSET_GOOD) - goto good; - - if (other->gc_marker != gc_marker + GC_OFFSET_BAD) - is_bad = false; - } - - if (is_bad) - goto bad; - - /* We were unable to find anything out about this entry, so - * let's investigate it later */ - u->gc_marker = gc_marker + GC_OFFSET_UNSURE; - unit_add_to_gc_queue(u); - return; - -bad: - /* We definitely know that this one is not useful anymore, so - * let's mark it for deletion */ - u->gc_marker = gc_marker + GC_OFFSET_BAD; - unit_add_to_cleanup_queue(u); - return; - -good: - u->gc_marker = gc_marker + GC_OFFSET_GOOD; -} - -static unsigned manager_dispatch_gc_queue(Manager *m) { - Unit *u; - unsigned n = 0; - unsigned gc_marker; - - assert(m); - - /* log_debug("Running GC..."); */ - - m->gc_marker += _GC_OFFSET_MAX; - if (m->gc_marker + _GC_OFFSET_MAX <= _GC_OFFSET_MAX) - m->gc_marker = 1; - - gc_marker = m->gc_marker; - - while ((u = m->gc_queue)) { - assert(u->in_gc_queue); - - unit_gc_sweep(u, gc_marker); - - LIST_REMOVE(gc_queue, m->gc_queue, u); - u->in_gc_queue = false; - - n++; - - if (u->gc_marker == gc_marker + GC_OFFSET_BAD || - u->gc_marker == gc_marker + GC_OFFSET_UNSURE) { - if (u->id) - log_unit_debug(u, "Collecting."); - u->gc_marker = gc_marker + GC_OFFSET_BAD; - unit_add_to_cleanup_queue(u); - } - } - - m->n_in_gc_queue = 0; - - return n; -} - -static void manager_clear_jobs_and_units(Manager *m) { - Unit *u; - - assert(m); - - while ((u = hashmap_first(m->units))) - unit_free(u); - - manager_dispatch_cleanup_queue(m); - - assert(!m->load_queue); - assert(!m->run_queue); - assert(!m->dbus_unit_queue); - assert(!m->dbus_job_queue); - assert(!m->cleanup_queue); - assert(!m->gc_queue); - - assert(hashmap_isempty(m->jobs)); - assert(hashmap_isempty(m->units)); - - m->n_on_console = 0; - m->n_running_jobs = 0; -} - -Manager* manager_free(Manager *m) { - UnitType c; - int i; - - if (!m) - return NULL; - - manager_clear_jobs_and_units(m); - - for (c = 0; c < _UNIT_TYPE_MAX; c++) - if (unit_vtable[c]->shutdown) - unit_vtable[c]->shutdown(m); - - /* If we reexecute ourselves, we keep the root cgroup - * around */ - manager_shutdown_cgroup(m, m->exit_code != MANAGER_REEXECUTE); - - lookup_paths_flush_generator(&m->lookup_paths); - - bus_done(m); - - hashmap_free(m->units); - hashmap_free(m->jobs); - hashmap_free(m->watch_pids1); - hashmap_free(m->watch_pids2); - hashmap_free(m->watch_bus); - - set_free(m->startup_units); - set_free(m->failed_units); - - sd_event_source_unref(m->signal_event_source); - sd_event_source_unref(m->notify_event_source); - sd_event_source_unref(m->cgroups_agent_event_source); - sd_event_source_unref(m->time_change_event_source); - sd_event_source_unref(m->jobs_in_progress_event_source); - sd_event_source_unref(m->run_queue_event_source); - - safe_close(m->signal_fd); - safe_close(m->notify_fd); - safe_close(m->cgroups_agent_fd); - safe_close(m->time_change_fd); - safe_close(m->kdbus_fd); - - manager_close_ask_password(m); - - manager_close_idle_pipe(m); - - udev_unref(m->udev); - sd_event_unref(m->event); - - free(m->notify_socket); - - lookup_paths_free(&m->lookup_paths); - strv_free(m->environment); - - hashmap_free(m->cgroup_unit); - set_free_free(m->unit_path_cache); - - free(m->switch_root); - free(m->switch_root_init); - - for (i = 0; i < _RLIMIT_MAX; i++) - m->rlimit[i] = mfree(m->rlimit[i]); - - assert(hashmap_isempty(m->units_requiring_mounts_for)); - hashmap_free(m->units_requiring_mounts_for); - - free(m); - return NULL; -} - -void manager_enumerate(Manager *m) { - UnitType c; - - assert(m); - - /* Let's ask every type to load all units from disk/kernel - * that it might know */ - for (c = 0; c < _UNIT_TYPE_MAX; c++) { - if (!unit_type_supported(c)) { - log_debug("Unit type .%s is not supported on this system.", unit_type_to_string(c)); - continue; - } - - if (!unit_vtable[c]->enumerate) - continue; - - unit_vtable[c]->enumerate(m); - } - - manager_dispatch_load_queue(m); -} - -static void manager_coldplug(Manager *m) { - Iterator i; - Unit *u; - char *k; - int r; - - assert(m); - - /* Then, let's set up their initial state. */ - HASHMAP_FOREACH_KEY(u, k, m->units, i) { - - /* ignore aliases */ - if (u->id != k) - continue; - - r = unit_coldplug(u); - if (r < 0) - log_warning_errno(r, "We couldn't coldplug %s, proceeding anyway: %m", u->id); - } -} - -static void manager_build_unit_path_cache(Manager *m) { - char **i; - int r; - - assert(m); - - set_free_free(m->unit_path_cache); - - m->unit_path_cache = set_new(&string_hash_ops); - if (!m->unit_path_cache) { - r = -ENOMEM; - goto fail; - } - - /* This simply builds a list of files we know exist, so that - * we don't always have to go to disk */ - - STRV_FOREACH(i, m->lookup_paths.search_path) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - - d = opendir(*i); - if (!d) { - if (errno != ENOENT) - log_warning_errno(errno, "Failed to open directory %s, ignoring: %m", *i); - continue; - } - - FOREACH_DIRENT(de, d, r = -errno; goto fail) { - char *p; - - p = strjoin(streq(*i, "/") ? "" : *i, "/", de->d_name, NULL); - if (!p) { - r = -ENOMEM; - goto fail; - } - - r = set_consume(m->unit_path_cache, p); - if (r < 0) - goto fail; - } - } - - return; - -fail: - log_warning_errno(r, "Failed to build unit path cache, proceeding without: %m"); - m->unit_path_cache = set_free_free(m->unit_path_cache); -} - -static void manager_distribute_fds(Manager *m, FDSet *fds) { - Iterator i; - Unit *u; - - assert(m); - - HASHMAP_FOREACH(u, m->units, i) { - - if (fdset_size(fds) <= 0) - break; - - if (!UNIT_VTABLE(u)->distribute_fds) - continue; - - UNIT_VTABLE(u)->distribute_fds(u, fds); - } -} - -int manager_startup(Manager *m, FILE *serialization, FDSet *fds) { - int r, q; - - assert(m); - - r = lookup_paths_init(&m->lookup_paths, m->unit_file_scope, 0, NULL); - if (r < 0) - return r; - - /* Make sure the transient directory always exists, so that it remains in the search path */ - r = mkdir_p_label(m->lookup_paths.transient, 0755); - if (r < 0) - return r; - - dual_timestamp_get(&m->generators_start_timestamp); - r = manager_run_generators(m); - dual_timestamp_get(&m->generators_finish_timestamp); - if (r < 0) - return r; - - lookup_paths_reduce(&m->lookup_paths); - manager_build_unit_path_cache(m); - - /* If we will deserialize make sure that during enumeration - * this is already known, so we increase the counter here - * already */ - if (serialization) - m->n_reloading++; - - /* First, enumerate what we can from all config files */ - dual_timestamp_get(&m->units_load_start_timestamp); - manager_enumerate(m); - dual_timestamp_get(&m->units_load_finish_timestamp); - - /* Second, deserialize if there is something to deserialize */ - if (serialization) - r = manager_deserialize(m, serialization, fds); - - /* Any fds left? Find some unit which wants them. This is - * useful to allow container managers to pass some file - * descriptors to us pre-initialized. This enables - * socket-based activation of entire containers. */ - manager_distribute_fds(m, fds); - - /* We might have deserialized the notify fd, but if we didn't - * then let's create the bus now */ - q = manager_setup_notify(m); - if (q < 0 && r == 0) - r = q; - - q = manager_setup_cgroups_agent(m); - if (q < 0 && r == 0) - r = q; - - /* We might have deserialized the kdbus control fd, but if we - * didn't, then let's create the bus now. */ - manager_setup_kdbus(m); - manager_connect_bus(m, !!serialization); - bus_track_coldplug(m, &m->subscribed, &m->deserialized_subscribed); - - /* Third, fire things up! */ - manager_coldplug(m); - - if (serialization) { - assert(m->n_reloading > 0); - m->n_reloading--; - - /* Let's wait for the UnitNew/JobNew messages being - * sent, before we notify that the reload is - * finished */ - m->send_reloading_done = true; - } - - return r; -} - -int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, sd_bus_error *e, Job **_ret) { - int r; - Transaction *tr; - - assert(m); - assert(type < _JOB_TYPE_MAX); - assert(unit); - assert(mode < _JOB_MODE_MAX); - - if (mode == JOB_ISOLATE && type != JOB_START) - return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Isolate is only valid for start."); - - if (mode == JOB_ISOLATE && !unit->allow_isolate) - return sd_bus_error_setf(e, BUS_ERROR_NO_ISOLATION, "Operation refused, unit may not be isolated."); - - log_unit_debug(unit, "Trying to enqueue job %s/%s/%s", unit->id, job_type_to_string(type), job_mode_to_string(mode)); - - type = job_type_collapse(type, unit); - - tr = transaction_new(mode == JOB_REPLACE_IRREVERSIBLY); - if (!tr) - return -ENOMEM; - - r = transaction_add_job_and_dependencies(tr, type, unit, NULL, true, false, - mode == JOB_IGNORE_DEPENDENCIES || mode == JOB_IGNORE_REQUIREMENTS, - mode == JOB_IGNORE_DEPENDENCIES, e); - if (r < 0) - goto tr_abort; - - if (mode == JOB_ISOLATE) { - r = transaction_add_isolate_jobs(tr, m); - if (r < 0) - goto tr_abort; - } - - r = transaction_activate(tr, m, mode, e); - if (r < 0) - goto tr_abort; - - log_unit_debug(unit, - "Enqueued job %s/%s as %u", unit->id, - job_type_to_string(type), (unsigned) tr->anchor_job->id); - - if (_ret) - *_ret = tr->anchor_job; - - transaction_free(tr); - return 0; - -tr_abort: - transaction_abort(tr); - transaction_free(tr); - return r; -} - -int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, sd_bus_error *e, Job **ret) { - Unit *unit; - int r; - - assert(m); - assert(type < _JOB_TYPE_MAX); - assert(name); - assert(mode < _JOB_MODE_MAX); - - r = manager_load_unit(m, name, NULL, NULL, &unit); - if (r < 0) - return r; - - return manager_add_job(m, type, unit, mode, e, ret); -} - -int manager_add_job_by_name_and_warn(Manager *m, JobType type, const char *name, JobMode mode, Job **ret) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(m); - assert(type < _JOB_TYPE_MAX); - assert(name); - assert(mode < _JOB_MODE_MAX); - - r = manager_add_job_by_name(m, type, name, mode, &error, ret); - if (r < 0) - return log_warning_errno(r, "Failed to enqueue %s job for %s: %s", job_mode_to_string(mode), name, bus_error_message(&error, r)); - - return r; -} - -Job *manager_get_job(Manager *m, uint32_t id) { - assert(m); - - return hashmap_get(m->jobs, UINT32_TO_PTR(id)); -} - -Unit *manager_get_unit(Manager *m, const char *name) { - assert(m); - assert(name); - - return hashmap_get(m->units, name); -} - -unsigned manager_dispatch_load_queue(Manager *m) { - Unit *u; - unsigned n = 0; - - assert(m); - - /* Make sure we are not run recursively */ - if (m->dispatching_load_queue) - return 0; - - m->dispatching_load_queue = true; - - /* Dispatches the load queue. Takes a unit from the queue and - * tries to load its data until the queue is empty */ - - while ((u = m->load_queue)) { - assert(u->in_load_queue); - - unit_load(u); - n++; - } - - m->dispatching_load_queue = false; - return n; -} - -int manager_load_unit_prepare( - Manager *m, - const char *name, - const char *path, - sd_bus_error *e, - Unit **_ret) { - - Unit *ret; - UnitType t; - int r; - - assert(m); - assert(name || path); - - /* This will prepare the unit for loading, but not actually - * load anything from disk. */ - - if (path && !is_path(path)) - return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Path %s is not absolute.", path); - - if (!name) - name = basename(path); - - t = unit_name_to_type(name); - - if (t == _UNIT_TYPE_INVALID || !unit_name_is_valid(name, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) { - if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) - return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Unit name %s is missing the instance name.", name); - - return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Unit name %s is not valid.", name); - } - - ret = manager_get_unit(m, name); - if (ret) { - *_ret = ret; - return 1; - } - - ret = unit_new(m, unit_vtable[t]->object_size); - if (!ret) - return -ENOMEM; - - if (path) { - ret->fragment_path = strdup(path); - if (!ret->fragment_path) { - unit_free(ret); - return -ENOMEM; - } - } - - r = unit_add_name(ret, name); - if (r < 0) { - unit_free(ret); - return r; - } - - unit_add_to_load_queue(ret); - unit_add_to_dbus_queue(ret); - unit_add_to_gc_queue(ret); - - if (_ret) - *_ret = ret; - - return 0; -} - -int manager_load_unit( - Manager *m, - const char *name, - const char *path, - sd_bus_error *e, - Unit **_ret) { - - int r; - - assert(m); - - /* This will load the service information files, but not actually - * start any services or anything. */ - - r = manager_load_unit_prepare(m, name, path, e, _ret); - if (r != 0) - return r; - - manager_dispatch_load_queue(m); - - if (_ret) - *_ret = unit_follow_merge(*_ret); - - return 0; -} - -void manager_dump_jobs(Manager *s, FILE *f, const char *prefix) { - Iterator i; - Job *j; - - assert(s); - assert(f); - - HASHMAP_FOREACH(j, s->jobs, i) - job_dump(j, f, prefix); -} - -void manager_dump_units(Manager *s, FILE *f, const char *prefix) { - Iterator i; - Unit *u; - const char *t; - - assert(s); - assert(f); - - HASHMAP_FOREACH_KEY(u, t, s->units, i) - if (u->id == t) - unit_dump(u, f, prefix); -} - -void manager_clear_jobs(Manager *m) { - Job *j; - - assert(m); - - while ((j = hashmap_first(m->jobs))) - /* No need to recurse. We're cancelling all jobs. */ - job_finish_and_invalidate(j, JOB_CANCELED, false, false); -} - -static int manager_dispatch_run_queue(sd_event_source *source, void *userdata) { - Manager *m = userdata; - Job *j; - - assert(source); - assert(m); - - while ((j = m->run_queue)) { - assert(j->installed); - assert(j->in_run_queue); - - job_run_and_invalidate(j); - } - - if (m->n_running_jobs > 0) - manager_watch_jobs_in_progress(m); - - if (m->n_on_console > 0) - manager_watch_idle_pipe(m); - - return 1; -} - -static unsigned manager_dispatch_dbus_queue(Manager *m) { - Job *j; - Unit *u; - unsigned n = 0; - - assert(m); - - if (m->dispatching_dbus_queue) - return 0; - - m->dispatching_dbus_queue = true; - - while ((u = m->dbus_unit_queue)) { - assert(u->in_dbus_queue); - - bus_unit_send_change_signal(u); - n++; - } - - while ((j = m->dbus_job_queue)) { - assert(j->in_dbus_queue); - - bus_job_send_change_signal(j); - n++; - } - - m->dispatching_dbus_queue = false; - - if (m->send_reloading_done) { - m->send_reloading_done = false; - - bus_manager_send_reloading(m, false); - } - - if (m->queued_message) - bus_send_queued_message(m); - - return n; -} - -static int manager_dispatch_cgroups_agent_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - char buf[PATH_MAX+1]; - ssize_t n; - - n = recv(fd, buf, sizeof(buf), 0); - if (n < 0) - return log_error_errno(errno, "Failed to read cgroups agent message: %m"); - if (n == 0) { - log_error("Got zero-length cgroups agent message, ignoring."); - return 0; - } - if ((size_t) n >= sizeof(buf)) { - log_error("Got overly long cgroups agent message, ignoring."); - return 0; - } - - if (memchr(buf, 0, n)) { - log_error("Got cgroups agent message with embedded NUL byte, ignoring."); - return 0; - } - buf[n] = 0; - - manager_notify_cgroup_empty(m, buf); - bus_forward_agent_released(m, buf); - - return 0; -} - -static void manager_invoke_notify_message(Manager *m, Unit *u, pid_t pid, const char *buf, size_t n, FDSet *fds) { - _cleanup_strv_free_ char **tags = NULL; - - assert(m); - assert(u); - assert(buf); - assert(n > 0); - - tags = strv_split(buf, "\n\r"); - if (!tags) { - log_oom(); - return; - } - - if (UNIT_VTABLE(u)->notify_message) - UNIT_VTABLE(u)->notify_message(u, pid, tags, fds); - else - log_unit_debug(u, "Got notification message for unit. Ignoring."); -} - -static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - _cleanup_fdset_free_ FDSet *fds = NULL; - Manager *m = userdata; - - char buf[NOTIFY_BUFFER_MAX+1]; - struct iovec iovec = { - .iov_base = buf, - .iov_len = sizeof(buf)-1, - }; - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(struct ucred)) + - CMSG_SPACE(sizeof(int) * NOTIFY_FD_MAX)]; - } control = {}; - struct msghdr msghdr = { - .msg_iov = &iovec, - .msg_iovlen = 1, - .msg_control = &control, - .msg_controllen = sizeof(control), - }; - - struct cmsghdr *cmsg; - struct ucred *ucred = NULL; - bool found = false; - Unit *u1, *u2, *u3; - int r, *fd_array = NULL; - unsigned n_fds = 0; - ssize_t n; - - assert(m); - assert(m->notify_fd == fd); - - if (revents != EPOLLIN) { - log_warning("Got unexpected poll event for notify fd."); - return 0; - } - - n = recvmsg(m->notify_fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); - if (n < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return -errno; - } - - CMSG_FOREACH(cmsg, &msghdr) { - if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { - - fd_array = (int*) CMSG_DATA(cmsg); - n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); - - } else if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SCM_CREDENTIALS && - cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) { - - ucred = (struct ucred*) CMSG_DATA(cmsg); - } - } - - if (n_fds > 0) { - assert(fd_array); - - r = fdset_new_array(&fds, fd_array, n_fds); - if (r < 0) { - close_many(fd_array, n_fds); - return log_oom(); - } - } - - if (!ucred || ucred->pid <= 0) { - log_warning("Received notify message without valid credentials. Ignoring."); - return 0; - } - - if ((size_t) n >= sizeof(buf)) { - log_warning("Received notify message exceeded maximum size. Ignoring."); - return 0; - } - - buf[n] = 0; - - /* Notify every unit that might be interested, but try - * to avoid notifying the same one multiple times. */ - u1 = manager_get_unit_by_pid_cgroup(m, ucred->pid); - if (u1) { - manager_invoke_notify_message(m, u1, ucred->pid, buf, n, fds); - found = true; - } - - u2 = hashmap_get(m->watch_pids1, PID_TO_PTR(ucred->pid)); - if (u2 && u2 != u1) { - manager_invoke_notify_message(m, u2, ucred->pid, buf, n, fds); - found = true; - } - - u3 = hashmap_get(m->watch_pids2, PID_TO_PTR(ucred->pid)); - if (u3 && u3 != u2 && u3 != u1) { - manager_invoke_notify_message(m, u3, ucred->pid, buf, n, fds); - found = true; - } - - if (!found) - log_warning("Cannot find unit for notify message of PID "PID_FMT".", ucred->pid); - - if (fdset_size(fds) > 0) - log_warning("Got auxiliary fds with notification message, closing all."); - - return 0; -} - -static void invoke_sigchld_event(Manager *m, Unit *u, const siginfo_t *si) { - assert(m); - assert(u); - assert(si); - - log_unit_debug(u, "Child "PID_FMT" belongs to %s", si->si_pid, u->id); - - unit_unwatch_pid(u, si->si_pid); - - if (UNIT_VTABLE(u)->sigchld_event) - UNIT_VTABLE(u)->sigchld_event(u, si->si_pid, si->si_code, si->si_status); -} - -static int manager_dispatch_sigchld(Manager *m) { - assert(m); - - for (;;) { - siginfo_t si = {}; - - /* First we call waitd() for a PID and do not reap the - * zombie. That way we can still access /proc/$PID for - * it while it is a zombie. */ - if (waitid(P_ALL, 0, &si, WEXITED|WNOHANG|WNOWAIT) < 0) { - - if (errno == ECHILD) - break; - - if (errno == EINTR) - continue; - - return -errno; - } - - if (si.si_pid <= 0) - break; - - if (si.si_code == CLD_EXITED || si.si_code == CLD_KILLED || si.si_code == CLD_DUMPED) { - _cleanup_free_ char *name = NULL; - Unit *u1, *u2, *u3; - - get_process_comm(si.si_pid, &name); - - log_debug("Child "PID_FMT" (%s) died (code=%s, status=%i/%s)", - si.si_pid, strna(name), - sigchld_code_to_string(si.si_code), - si.si_status, - strna(si.si_code == CLD_EXITED - ? exit_status_to_string(si.si_status, EXIT_STATUS_FULL) - : signal_to_string(si.si_status))); - - /* And now figure out the unit this belongs - * to, it might be multiple... */ - u1 = manager_get_unit_by_pid_cgroup(m, si.si_pid); - if (u1) - invoke_sigchld_event(m, u1, &si); - u2 = hashmap_get(m->watch_pids1, PID_TO_PTR(si.si_pid)); - if (u2 && u2 != u1) - invoke_sigchld_event(m, u2, &si); - u3 = hashmap_get(m->watch_pids2, PID_TO_PTR(si.si_pid)); - if (u3 && u3 != u2 && u3 != u1) - invoke_sigchld_event(m, u3, &si); - } - - /* And now, we actually reap the zombie. */ - if (waitid(P_PID, si.si_pid, &si, WEXITED) < 0) { - if (errno == EINTR) - continue; - - return -errno; - } - } - - return 0; -} - -static int manager_start_target(Manager *m, const char *name, JobMode mode) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - log_debug("Activating special unit %s", name); - - r = manager_add_job_by_name(m, JOB_START, name, mode, &error, NULL); - if (r < 0) - log_error("Failed to enqueue %s job: %s", name, bus_error_message(&error, r)); - - return r; -} - -static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - ssize_t n; - struct signalfd_siginfo sfsi; - bool sigchld = false; - int r; - - assert(m); - assert(m->signal_fd == fd); - - if (revents != EPOLLIN) { - log_warning("Got unexpected events from signal file descriptor."); - return 0; - } - - for (;;) { - n = read(m->signal_fd, &sfsi, sizeof(sfsi)); - if (n != sizeof(sfsi)) { - - if (n >= 0) - return -EIO; - - if (errno == EINTR || errno == EAGAIN) - break; - - return -errno; - } - - log_received_signal(sfsi.ssi_signo == SIGCHLD || - (sfsi.ssi_signo == SIGTERM && MANAGER_IS_USER(m)) - ? LOG_DEBUG : LOG_INFO, - &sfsi); - - switch (sfsi.ssi_signo) { - - case SIGCHLD: - sigchld = true; - break; - - case SIGTERM: - if (MANAGER_IS_SYSTEM(m)) { - /* This is for compatibility with the - * original sysvinit */ - m->exit_code = MANAGER_REEXECUTE; - break; - } - - /* Fall through */ - - case SIGINT: - if (MANAGER_IS_SYSTEM(m)) { - - /* If the user presses C-A-D more than - * 7 times within 2s, we reboot - * immediately. */ - - if (ratelimit_test(&m->ctrl_alt_del_ratelimit)) - manager_start_target(m, SPECIAL_CTRL_ALT_DEL_TARGET, JOB_REPLACE_IRREVERSIBLY); - else { - log_notice("Ctrl-Alt-Del was pressed more than 7 times within 2s, rebooting immediately."); - status_printf(NULL, true, false, "Ctrl-Alt-Del was pressed more than 7 times within 2s, rebooting immediately."); - m->exit_code = MANAGER_REBOOT; - } - - break; - } - - /* Run the exit target if there is one, if not, just exit. */ - if (manager_start_target(m, SPECIAL_EXIT_TARGET, JOB_REPLACE) < 0) { - m->exit_code = MANAGER_EXIT; - return 0; - } - - break; - - case SIGWINCH: - if (MANAGER_IS_SYSTEM(m)) - manager_start_target(m, SPECIAL_KBREQUEST_TARGET, JOB_REPLACE); - - /* This is a nop on non-init */ - break; - - case SIGPWR: - if (MANAGER_IS_SYSTEM(m)) - manager_start_target(m, SPECIAL_SIGPWR_TARGET, JOB_REPLACE); - - /* This is a nop on non-init */ - break; - - case SIGUSR1: { - Unit *u; - - u = manager_get_unit(m, SPECIAL_DBUS_SERVICE); - - if (!u || UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) { - log_info("Trying to reconnect to bus..."); - bus_init(m, true); - } - - if (!u || !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) { - log_info("Loading D-Bus service..."); - manager_start_target(m, SPECIAL_DBUS_SERVICE, JOB_REPLACE); - } - - break; - } - - case SIGUSR2: { - _cleanup_free_ char *dump = NULL; - _cleanup_fclose_ FILE *f = NULL; - size_t size; - - f = open_memstream(&dump, &size); - if (!f) { - log_warning_errno(errno, "Failed to allocate memory stream: %m"); - break; - } - - manager_dump_units(m, f, "\t"); - manager_dump_jobs(m, f, "\t"); - - r = fflush_and_check(f); - if (r < 0) { - log_warning_errno(r, "Failed to write status stream: %m"); - break; - } - - log_dump(LOG_INFO, dump); - break; - } - - case SIGHUP: - m->exit_code = MANAGER_RELOAD; - break; - - default: { - - /* Starting SIGRTMIN+0 */ - static const char * const target_table[] = { - [0] = SPECIAL_DEFAULT_TARGET, - [1] = SPECIAL_RESCUE_TARGET, - [2] = SPECIAL_EMERGENCY_TARGET, - [3] = SPECIAL_HALT_TARGET, - [4] = SPECIAL_POWEROFF_TARGET, - [5] = SPECIAL_REBOOT_TARGET, - [6] = SPECIAL_KEXEC_TARGET - }; - - /* Starting SIGRTMIN+13, so that target halt and system halt are 10 apart */ - static const ManagerExitCode code_table[] = { - [0] = MANAGER_HALT, - [1] = MANAGER_POWEROFF, - [2] = MANAGER_REBOOT, - [3] = MANAGER_KEXEC - }; - - if ((int) sfsi.ssi_signo >= SIGRTMIN+0 && - (int) sfsi.ssi_signo < SIGRTMIN+(int) ELEMENTSOF(target_table)) { - int idx = (int) sfsi.ssi_signo - SIGRTMIN; - manager_start_target(m, target_table[idx], - (idx == 1 || idx == 2) ? JOB_ISOLATE : JOB_REPLACE); - break; - } - - if ((int) sfsi.ssi_signo >= SIGRTMIN+13 && - (int) sfsi.ssi_signo < SIGRTMIN+13+(int) ELEMENTSOF(code_table)) { - m->exit_code = code_table[sfsi.ssi_signo - SIGRTMIN - 13]; - break; - } - - switch (sfsi.ssi_signo - SIGRTMIN) { - - case 20: - manager_set_show_status(m, SHOW_STATUS_YES); - break; - - case 21: - manager_set_show_status(m, SHOW_STATUS_NO); - break; - - case 22: - log_set_max_level(LOG_DEBUG); - log_info("Setting log level to debug."); - break; - - case 23: - log_set_max_level(LOG_INFO); - log_info("Setting log level to info."); - break; - - case 24: - if (MANAGER_IS_USER(m)) { - m->exit_code = MANAGER_EXIT; - return 0; - } - - /* This is a nop on init */ - break; - - case 26: - case 29: /* compatibility: used to be mapped to LOG_TARGET_SYSLOG_OR_KMSG */ - log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); - log_notice("Setting log target to journal-or-kmsg."); - break; - - case 27: - log_set_target(LOG_TARGET_CONSOLE); - log_notice("Setting log target to console."); - break; - - case 28: - log_set_target(LOG_TARGET_KMSG); - log_notice("Setting log target to kmsg."); - break; - - default: - log_warning("Got unhandled signal <%s>.", signal_to_string(sfsi.ssi_signo)); - } - } - } - } - - if (sigchld) - manager_dispatch_sigchld(m); - - return 0; -} - -static int manager_dispatch_time_change_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - Iterator i; - Unit *u; - - assert(m); - assert(m->time_change_fd == fd); - - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_TIME_CHANGE), - LOG_MESSAGE("Time has been changed"), - NULL); - - /* Restart the watch */ - m->time_change_event_source = sd_event_source_unref(m->time_change_event_source); - m->time_change_fd = safe_close(m->time_change_fd); - - manager_setup_time_change(m); - - HASHMAP_FOREACH(u, m->units, i) - if (UNIT_VTABLE(u)->time_change) - UNIT_VTABLE(u)->time_change(u); - - return 0; -} - -static int manager_dispatch_idle_pipe_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - - assert(m); - assert(m->idle_pipe[2] == fd); - - m->no_console_output = m->n_on_console > 0; - - manager_close_idle_pipe(m); - - return 0; -} - -static int manager_dispatch_jobs_in_progress(sd_event_source *source, usec_t usec, void *userdata) { - Manager *m = userdata; - int r; - uint64_t next; - - assert(m); - assert(source); - - manager_print_jobs_in_progress(m); - - next = now(CLOCK_MONOTONIC) + JOBS_IN_PROGRESS_PERIOD_USEC; - r = sd_event_source_set_time(source, next); - if (r < 0) - return r; - - return sd_event_source_set_enabled(source, SD_EVENT_ONESHOT); -} - -int manager_loop(Manager *m) { - int r; - - RATELIMIT_DEFINE(rl, 1*USEC_PER_SEC, 50000); - - assert(m); - m->exit_code = MANAGER_OK; - - /* Release the path cache */ - m->unit_path_cache = set_free_free(m->unit_path_cache); - - manager_check_finished(m); - - /* There might still be some zombies hanging around from - * before we were exec()'ed. Let's reap them. */ - r = manager_dispatch_sigchld(m); - if (r < 0) - return r; - - while (m->exit_code == MANAGER_OK) { - usec_t wait_usec; - - if (m->runtime_watchdog > 0 && m->runtime_watchdog != USEC_INFINITY && MANAGER_IS_SYSTEM(m)) - watchdog_ping(); - - if (!ratelimit_test(&rl)) { - /* Yay, something is going seriously wrong, pause a little */ - log_warning("Looping too fast. Throttling execution a little."); - sleep(1); - } - - if (manager_dispatch_load_queue(m) > 0) - continue; - - if (manager_dispatch_gc_queue(m) > 0) - continue; - - if (manager_dispatch_cleanup_queue(m) > 0) - continue; - - if (manager_dispatch_cgroup_queue(m) > 0) - continue; - - if (manager_dispatch_dbus_queue(m) > 0) - continue; - - /* Sleep for half the watchdog time */ - if (m->runtime_watchdog > 0 && m->runtime_watchdog != USEC_INFINITY && MANAGER_IS_SYSTEM(m)) { - wait_usec = m->runtime_watchdog / 2; - if (wait_usec <= 0) - wait_usec = 1; - } else - wait_usec = USEC_INFINITY; - - r = sd_event_run(m->event, wait_usec); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); - } - - return m->exit_code; -} - -int manager_load_unit_from_dbus_path(Manager *m, const char *s, sd_bus_error *e, Unit **_u) { - _cleanup_free_ char *n = NULL; - Unit *u; - int r; - - assert(m); - assert(s); - assert(_u); - - r = unit_name_from_dbus_path(s, &n); - if (r < 0) - return r; - - r = manager_load_unit(m, n, NULL, e, &u); - if (r < 0) - return r; - - *_u = u; - - return 0; -} - -int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j) { - const char *p; - unsigned id; - Job *j; - int r; - - assert(m); - assert(s); - assert(_j); - - p = startswith(s, "/org/freedesktop/systemd1/job/"); - if (!p) - return -EINVAL; - - r = safe_atou(p, &id); - if (r < 0) - return r; - - j = manager_get_job(m, id); - if (!j) - return -ENOENT; - - *_j = j; - - return 0; -} - -void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success) { - -#ifdef HAVE_AUDIT - _cleanup_free_ char *p = NULL; - const char *msg; - int audit_fd, r; - - if (!MANAGER_IS_SYSTEM(m)) - return; - - audit_fd = get_audit_fd(); - if (audit_fd < 0) - return; - - /* Don't generate audit events if the service was already - * started and we're just deserializing */ - if (MANAGER_IS_RELOADING(m)) - return; - - if (u->type != UNIT_SERVICE) - return; - - r = unit_name_to_prefix_and_instance(u->id, &p); - if (r < 0) { - log_error_errno(r, "Failed to extract prefix and instance of unit name: %m"); - return; - } - - msg = strjoina("unit=", p); - if (audit_log_user_comm_message(audit_fd, type, msg, "systemd", NULL, NULL, NULL, success) < 0) { - if (errno == EPERM) - /* We aren't allowed to send audit messages? - * Then let's not retry again. */ - close_audit_fd(); - else - log_warning_errno(errno, "Failed to send audit message: %m"); - } -#endif - -} - -void manager_send_unit_plymouth(Manager *m, Unit *u) { - static const union sockaddr_union sa = PLYMOUTH_SOCKET; - _cleanup_free_ char *message = NULL; - _cleanup_close_ int fd = -1; - int n = 0; - - /* Don't generate plymouth events if the service was already - * started and we're just deserializing */ - if (MANAGER_IS_RELOADING(m)) - return; - - if (!MANAGER_IS_SYSTEM(m)) - return; - - if (detect_container() > 0) - return; - - if (u->type != UNIT_SERVICE && - u->type != UNIT_MOUNT && - u->type != UNIT_SWAP) - return; - - /* We set SOCK_NONBLOCK here so that we rather drop the - * message then wait for plymouth */ - fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (fd < 0) { - log_error_errno(errno, "socket() failed: %m"); - return; - } - - if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) { - - if (!IN_SET(errno, EPIPE, EAGAIN, ENOENT, ECONNREFUSED, ECONNRESET, ECONNABORTED)) - log_error_errno(errno, "connect() failed: %m"); - return; - } - - if (asprintf(&message, "U\002%c%s%n", (int) (strlen(u->id) + 1), u->id, &n) < 0) { - log_oom(); - return; - } - - errno = 0; - if (write(fd, message, n + 1) != n + 1) - if (!IN_SET(errno, EPIPE, EAGAIN, ENOENT, ECONNREFUSED, ECONNRESET, ECONNABORTED)) - log_error_errno(errno, "Failed to write Plymouth message: %m"); -} - -int manager_open_serialization(Manager *m, FILE **_f) { - const char *path; - int fd = -1; - FILE *f; - - assert(_f); - - path = MANAGER_IS_SYSTEM(m) ? "/run/systemd" : "/tmp"; - fd = open_tmpfile_unlinkable(path, O_RDWR|O_CLOEXEC); - if (fd < 0) - return -errno; - - log_debug("Serializing state to %s", path); - - f = fdopen(fd, "w+"); - if (!f) { - safe_close(fd); - return -errno; - } - - *_f = f; - - return 0; -} - -int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) { - Iterator i; - Unit *u; - const char *t; - char **e; - int r; - - assert(m); - assert(f); - assert(fds); - - m->n_reloading++; - - fprintf(f, "current-job-id=%"PRIu32"\n", m->current_job_id); - fprintf(f, "taint-usr=%s\n", yes_no(m->taint_usr)); - fprintf(f, "n-installed-jobs=%u\n", m->n_installed_jobs); - fprintf(f, "n-failed-jobs=%u\n", m->n_failed_jobs); - - dual_timestamp_serialize(f, "firmware-timestamp", &m->firmware_timestamp); - dual_timestamp_serialize(f, "loader-timestamp", &m->loader_timestamp); - dual_timestamp_serialize(f, "kernel-timestamp", &m->kernel_timestamp); - dual_timestamp_serialize(f, "initrd-timestamp", &m->initrd_timestamp); - - if (!in_initrd()) { - dual_timestamp_serialize(f, "userspace-timestamp", &m->userspace_timestamp); - dual_timestamp_serialize(f, "finish-timestamp", &m->finish_timestamp); - dual_timestamp_serialize(f, "security-start-timestamp", &m->security_start_timestamp); - dual_timestamp_serialize(f, "security-finish-timestamp", &m->security_finish_timestamp); - dual_timestamp_serialize(f, "generators-start-timestamp", &m->generators_start_timestamp); - dual_timestamp_serialize(f, "generators-finish-timestamp", &m->generators_finish_timestamp); - dual_timestamp_serialize(f, "units-load-start-timestamp", &m->units_load_start_timestamp); - dual_timestamp_serialize(f, "units-load-finish-timestamp", &m->units_load_finish_timestamp); - } - - if (!switching_root) { - STRV_FOREACH(e, m->environment) { - _cleanup_free_ char *ce; - - ce = cescape(*e); - if (!ce) - return -ENOMEM; - - fprintf(f, "env=%s\n", *e); - } - } - - if (m->notify_fd >= 0) { - int copy; - - copy = fdset_put_dup(fds, m->notify_fd); - if (copy < 0) - return copy; - - fprintf(f, "notify-fd=%i\n", copy); - fprintf(f, "notify-socket=%s\n", m->notify_socket); - } - - if (m->cgroups_agent_fd >= 0) { - int copy; - - copy = fdset_put_dup(fds, m->cgroups_agent_fd); - if (copy < 0) - return copy; - - fprintf(f, "cgroups-agent-fd=%i\n", copy); - } - - if (m->kdbus_fd >= 0) { - int copy; - - copy = fdset_put_dup(fds, m->kdbus_fd); - if (copy < 0) - return copy; - - fprintf(f, "kdbus-fd=%i\n", copy); - } - - bus_track_serialize(m->subscribed, f); - - fputc('\n', f); - - HASHMAP_FOREACH_KEY(u, t, m->units, i) { - if (u->id != t) - continue; - - /* Start marker */ - fputs(u->id, f); - fputc('\n', f); - - r = unit_serialize(u, f, fds, !switching_root); - if (r < 0) { - m->n_reloading--; - return r; - } - } - - assert(m->n_reloading > 0); - m->n_reloading--; - - if (ferror(f)) - return -EIO; - - r = bus_fdset_add_all(m, fds); - if (r < 0) - return r; - - return 0; -} - -int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { - int r = 0; - - assert(m); - assert(f); - - log_debug("Deserializing state..."); - - m->n_reloading++; - - for (;;) { - char line[LINE_MAX], *l; - - if (!fgets(line, sizeof(line), f)) { - if (feof(f)) - r = 0; - else - r = -errno; - - goto finish; - } - - char_array_0(line); - l = strstrip(line); - - if (l[0] == 0) - break; - - if (startswith(l, "current-job-id=")) { - uint32_t id; - - if (safe_atou32(l+15, &id) < 0) - log_debug("Failed to parse current job id value %s", l+15); - else - m->current_job_id = MAX(m->current_job_id, id); - - } else if (startswith(l, "n-installed-jobs=")) { - uint32_t n; - - if (safe_atou32(l+17, &n) < 0) - log_debug("Failed to parse installed jobs counter %s", l+17); - else - m->n_installed_jobs += n; - - } else if (startswith(l, "n-failed-jobs=")) { - uint32_t n; - - if (safe_atou32(l+14, &n) < 0) - log_debug("Failed to parse failed jobs counter %s", l+14); - else - m->n_failed_jobs += n; - - } else if (startswith(l, "taint-usr=")) { - int b; - - b = parse_boolean(l+10); - if (b < 0) - log_debug("Failed to parse taint /usr flag %s", l+10); - else - m->taint_usr = m->taint_usr || b; - - } else if (startswith(l, "firmware-timestamp=")) - dual_timestamp_deserialize(l+19, &m->firmware_timestamp); - else if (startswith(l, "loader-timestamp=")) - dual_timestamp_deserialize(l+17, &m->loader_timestamp); - else if (startswith(l, "kernel-timestamp=")) - dual_timestamp_deserialize(l+17, &m->kernel_timestamp); - else if (startswith(l, "initrd-timestamp=")) - dual_timestamp_deserialize(l+17, &m->initrd_timestamp); - else if (startswith(l, "userspace-timestamp=")) - dual_timestamp_deserialize(l+20, &m->userspace_timestamp); - else if (startswith(l, "finish-timestamp=")) - dual_timestamp_deserialize(l+17, &m->finish_timestamp); - else if (startswith(l, "security-start-timestamp=")) - dual_timestamp_deserialize(l+25, &m->security_start_timestamp); - else if (startswith(l, "security-finish-timestamp=")) - dual_timestamp_deserialize(l+26, &m->security_finish_timestamp); - else if (startswith(l, "generators-start-timestamp=")) - dual_timestamp_deserialize(l+27, &m->generators_start_timestamp); - else if (startswith(l, "generators-finish-timestamp=")) - dual_timestamp_deserialize(l+28, &m->generators_finish_timestamp); - else if (startswith(l, "units-load-start-timestamp=")) - dual_timestamp_deserialize(l+27, &m->units_load_start_timestamp); - else if (startswith(l, "units-load-finish-timestamp=")) - dual_timestamp_deserialize(l+28, &m->units_load_finish_timestamp); - else if (startswith(l, "env=")) { - _cleanup_free_ char *uce = NULL; - char **e; - - r = cunescape(l + 4, UNESCAPE_RELAX, &uce); - if (r < 0) - goto finish; - - e = strv_env_set(m->environment, uce); - if (!e) { - r = -ENOMEM; - goto finish; - } - - strv_free(m->environment); - m->environment = e; - - } else if (startswith(l, "notify-fd=")) { - int fd; - - if (safe_atoi(l + 10, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_debug("Failed to parse notify fd: %s", l + 10); - else { - m->notify_event_source = sd_event_source_unref(m->notify_event_source); - safe_close(m->notify_fd); - m->notify_fd = fdset_remove(fds, fd); - } - - } else if (startswith(l, "notify-socket=")) { - char *n; - - n = strdup(l+14); - if (!n) { - r = -ENOMEM; - goto finish; - } - - free(m->notify_socket); - m->notify_socket = n; - - } else if (startswith(l, "cgroups-agent-fd=")) { - int fd; - - if (safe_atoi(l + 17, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_debug("Failed to parse cgroups agent fd: %s", l + 10); - else { - m->cgroups_agent_event_source = sd_event_source_unref(m->cgroups_agent_event_source); - safe_close(m->cgroups_agent_fd); - m->cgroups_agent_fd = fdset_remove(fds, fd); - } - - } else if (startswith(l, "kdbus-fd=")) { - int fd; - - if (safe_atoi(l + 9, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_debug("Failed to parse kdbus fd: %s", l + 9); - else { - safe_close(m->kdbus_fd); - m->kdbus_fd = fdset_remove(fds, fd); - } - - } else { - int k; - - k = bus_track_deserialize_item(&m->deserialized_subscribed, l); - if (k < 0) - log_debug_errno(k, "Failed to deserialize bus tracker object: %m"); - else if (k == 0) - log_debug("Unknown serialization item '%s'", l); - } - } - - for (;;) { - Unit *u; - char name[UNIT_NAME_MAX+2]; - - /* Start marker */ - if (!fgets(name, sizeof(name), f)) { - if (feof(f)) - r = 0; - else - r = -errno; - - goto finish; - } - - char_array_0(name); - - r = manager_load_unit(m, strstrip(name), NULL, NULL, &u); - if (r < 0) - goto finish; - - r = unit_deserialize(u, f, fds); - if (r < 0) - goto finish; - } - -finish: - if (ferror(f)) - r = -EIO; - - assert(m->n_reloading > 0); - m->n_reloading--; - - return r; -} - -int manager_reload(Manager *m) { - int r, q; - _cleanup_fclose_ FILE *f = NULL; - _cleanup_fdset_free_ FDSet *fds = NULL; - - assert(m); - - r = manager_open_serialization(m, &f); - if (r < 0) - return r; - - m->n_reloading++; - bus_manager_send_reloading(m, true); - - fds = fdset_new(); - if (!fds) { - m->n_reloading--; - return -ENOMEM; - } - - r = manager_serialize(m, f, fds, false); - if (r < 0) { - m->n_reloading--; - return r; - } - - if (fseeko(f, 0, SEEK_SET) < 0) { - m->n_reloading--; - return -errno; - } - - /* From here on there is no way back. */ - manager_clear_jobs_and_units(m); - lookup_paths_flush_generator(&m->lookup_paths); - lookup_paths_free(&m->lookup_paths); - - q = lookup_paths_init(&m->lookup_paths, m->unit_file_scope, 0, NULL); - if (q < 0 && r >= 0) - r = q; - - /* Find new unit paths */ - q = manager_run_generators(m); - if (q < 0 && r >= 0) - r = q; - - lookup_paths_reduce(&m->lookup_paths); - manager_build_unit_path_cache(m); - - /* First, enumerate what we can from all config files */ - manager_enumerate(m); - - /* Second, deserialize our stored data */ - q = manager_deserialize(m, f, fds); - if (q < 0 && r >= 0) - r = q; - - fclose(f); - f = NULL; - - /* Re-register notify_fd as event source */ - q = manager_setup_notify(m); - if (q < 0 && r >= 0) - r = q; - - q = manager_setup_cgroups_agent(m); - if (q < 0 && r >= 0) - r = q; - - /* Third, fire things up! */ - manager_coldplug(m); - - /* Sync current state of bus names with our set of listening units */ - if (m->api_bus) - manager_sync_bus_names(m, m->api_bus); - - assert(m->n_reloading > 0); - m->n_reloading--; - - m->send_reloading_done = true; - - return r; -} - -void manager_reset_failed(Manager *m) { - Unit *u; - Iterator i; - - assert(m); - - HASHMAP_FOREACH(u, m->units, i) - unit_reset_failed(u); -} - -bool manager_unit_inactive_or_pending(Manager *m, const char *name) { - Unit *u; - - assert(m); - assert(name); - - /* Returns true if the unit is inactive or going down */ - u = manager_get_unit(m, name); - if (!u) - return true; - - return unit_inactive_or_pending(u); -} - -static void manager_notify_finished(Manager *m) { - char userspace[FORMAT_TIMESPAN_MAX], initrd[FORMAT_TIMESPAN_MAX], kernel[FORMAT_TIMESPAN_MAX], sum[FORMAT_TIMESPAN_MAX]; - usec_t firmware_usec, loader_usec, kernel_usec, initrd_usec, userspace_usec, total_usec; - - if (m->test_run) - return; - - if (MANAGER_IS_SYSTEM(m) && detect_container() <= 0) { - - /* Note that m->kernel_usec.monotonic is always at 0, - * and m->firmware_usec.monotonic and - * m->loader_usec.monotonic should be considered - * negative values. */ - - firmware_usec = m->firmware_timestamp.monotonic - m->loader_timestamp.monotonic; - loader_usec = m->loader_timestamp.monotonic - m->kernel_timestamp.monotonic; - userspace_usec = m->finish_timestamp.monotonic - m->userspace_timestamp.monotonic; - total_usec = m->firmware_timestamp.monotonic + m->finish_timestamp.monotonic; - - if (dual_timestamp_is_set(&m->initrd_timestamp)) { - - kernel_usec = m->initrd_timestamp.monotonic - m->kernel_timestamp.monotonic; - initrd_usec = m->userspace_timestamp.monotonic - m->initrd_timestamp.monotonic; - - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_STARTUP_FINISHED), - "KERNEL_USEC="USEC_FMT, kernel_usec, - "INITRD_USEC="USEC_FMT, initrd_usec, - "USERSPACE_USEC="USEC_FMT, userspace_usec, - LOG_MESSAGE("Startup finished in %s (kernel) + %s (initrd) + %s (userspace) = %s.", - format_timespan(kernel, sizeof(kernel), kernel_usec, USEC_PER_MSEC), - format_timespan(initrd, sizeof(initrd), initrd_usec, USEC_PER_MSEC), - format_timespan(userspace, sizeof(userspace), userspace_usec, USEC_PER_MSEC), - format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC)), - NULL); - } else { - kernel_usec = m->userspace_timestamp.monotonic - m->kernel_timestamp.monotonic; - initrd_usec = 0; - - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_STARTUP_FINISHED), - "KERNEL_USEC="USEC_FMT, kernel_usec, - "USERSPACE_USEC="USEC_FMT, userspace_usec, - LOG_MESSAGE("Startup finished in %s (kernel) + %s (userspace) = %s.", - format_timespan(kernel, sizeof(kernel), kernel_usec, USEC_PER_MSEC), - format_timespan(userspace, sizeof(userspace), userspace_usec, USEC_PER_MSEC), - format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC)), - NULL); - } - } else { - firmware_usec = loader_usec = initrd_usec = kernel_usec = 0; - total_usec = userspace_usec = m->finish_timestamp.monotonic - m->userspace_timestamp.monotonic; - - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_STARTUP_FINISHED), - "USERSPACE_USEC="USEC_FMT, userspace_usec, - LOG_MESSAGE("Startup finished in %s.", - format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC)), - NULL); - } - - bus_manager_send_finished(m, firmware_usec, loader_usec, kernel_usec, initrd_usec, userspace_usec, total_usec); - - sd_notifyf(false, - "READY=1\n" - "STATUS=Startup finished in %s.", - format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC)); -} - -void manager_check_finished(Manager *m) { - assert(m); - - if (MANAGER_IS_RELOADING(m)) - return; - - /* Verify that we are actually running currently. Initially - * the exit code is set to invalid, and during operation it is - * then set to MANAGER_OK */ - if (m->exit_code != MANAGER_OK) - return; - - if (hashmap_size(m->jobs) > 0) { - if (m->jobs_in_progress_event_source) - /* Ignore any failure, this is only for feedback */ - (void) sd_event_source_set_time(m->jobs_in_progress_event_source, now(CLOCK_MONOTONIC) + JOBS_IN_PROGRESS_WAIT_USEC); - - return; - } - - manager_flip_auto_status(m, false); - - /* Notify Type=idle units that we are done now */ - manager_close_idle_pipe(m); - - /* Turn off confirm spawn now */ - m->confirm_spawn = false; - - /* No need to update ask password status when we're going non-interactive */ - manager_close_ask_password(m); - - /* This is no longer the first boot */ - manager_set_first_boot(m, false); - - if (dual_timestamp_is_set(&m->finish_timestamp)) - return; - - dual_timestamp_get(&m->finish_timestamp); - - manager_notify_finished(m); - - manager_invalidate_startup_units(m); -} - -static int manager_run_generators(Manager *m) { - _cleanup_strv_free_ char **paths = NULL; - const char *argv[5]; - char **path; - int r; - - assert(m); - - if (m->test_run) - return 0; - - paths = generator_binary_paths(m->unit_file_scope); - if (!paths) - return log_oom(); - - /* Optimize by skipping the whole process by not creating output directories - * if no generators are found. */ - STRV_FOREACH(path, paths) { - if (access(*path, F_OK) >= 0) - goto found; - if (errno != ENOENT) - log_warning_errno(errno, "Failed to open generator directory %s: %m", *path); - } - - return 0; - - found: - r = lookup_paths_mkdir_generator(&m->lookup_paths); - if (r < 0) - goto finish; - - argv[0] = NULL; /* Leave this empty, execute_directory() will fill something in */ - argv[1] = m->lookup_paths.generator; - argv[2] = m->lookup_paths.generator_early; - argv[3] = m->lookup_paths.generator_late; - argv[4] = NULL; - - RUN_WITH_UMASK(0022) - execute_directories((const char* const*) paths, DEFAULT_TIMEOUT_USEC, (char**) argv); - -finish: - lookup_paths_trim_generator(&m->lookup_paths); - return r; -} - -int manager_environment_add(Manager *m, char **minus, char **plus) { - char **a = NULL, **b = NULL, **l; - assert(m); - - l = m->environment; - - if (!strv_isempty(minus)) { - a = strv_env_delete(l, 1, minus); - if (!a) - return -ENOMEM; - - l = a; - } - - if (!strv_isempty(plus)) { - b = strv_env_merge(2, l, plus); - if (!b) { - strv_free(a); - return -ENOMEM; - } - - l = b; - } - - if (m->environment != l) - strv_free(m->environment); - if (a != l) - strv_free(a); - if (b != l) - strv_free(b); - - m->environment = l; - manager_clean_environment(m); - strv_sort(m->environment); - - return 0; -} - -int manager_set_default_rlimits(Manager *m, struct rlimit **default_rlimit) { - int i; - - assert(m); - - for (i = 0; i < _RLIMIT_MAX; i++) { - m->rlimit[i] = mfree(m->rlimit[i]); - - if (!default_rlimit[i]) - continue; - - m->rlimit[i] = newdup(struct rlimit, default_rlimit[i], 1); - if (!m->rlimit[i]) - return -ENOMEM; - } - - return 0; -} - -void manager_recheck_journal(Manager *m) { - Unit *u; - - assert(m); - - if (!MANAGER_IS_SYSTEM(m)) - return; - - u = manager_get_unit(m, SPECIAL_JOURNALD_SOCKET); - if (u && SOCKET(u)->state != SOCKET_RUNNING) { - log_close_journal(); - return; - } - - u = manager_get_unit(m, SPECIAL_JOURNALD_SERVICE); - if (u && SERVICE(u)->state != SERVICE_RUNNING) { - log_close_journal(); - return; - } - - /* Hmm, OK, so the socket is fully up and the service is up - * too, then let's make use of the thing. */ - log_open(); -} - -void manager_set_show_status(Manager *m, ShowStatus mode) { - assert(m); - assert(IN_SET(mode, SHOW_STATUS_AUTO, SHOW_STATUS_NO, SHOW_STATUS_YES, SHOW_STATUS_TEMPORARY)); - - if (!MANAGER_IS_SYSTEM(m)) - return; - - if (m->show_status != mode) - log_debug("%s showing of status.", - mode == SHOW_STATUS_NO ? "Disabling" : "Enabling"); - m->show_status = mode; - - if (mode > 0) - (void) touch("/run/systemd/show-status"); - else - (void) unlink("/run/systemd/show-status"); -} - -static bool manager_get_show_status(Manager *m, StatusType type) { - assert(m); - - if (!MANAGER_IS_SYSTEM(m)) - return false; - - if (m->no_console_output) - return false; - - if (!IN_SET(manager_state(m), MANAGER_INITIALIZING, MANAGER_STARTING, MANAGER_STOPPING)) - return false; - - /* If we cannot find out the status properly, just proceed. */ - if (type != STATUS_TYPE_EMERGENCY && manager_check_ask_password(m) > 0) - return false; - - if (m->show_status > 0) - return true; - - return false; -} - -void manager_set_first_boot(Manager *m, bool b) { - assert(m); - - if (!MANAGER_IS_SYSTEM(m)) - return; - - if (m->first_boot != (int) b) { - if (b) - (void) touch("/run/systemd/first-boot"); - else - (void) unlink("/run/systemd/first-boot"); - } - - m->first_boot = b; -} - -void manager_status_printf(Manager *m, StatusType type, const char *status, const char *format, ...) { - va_list ap; - - /* If m is NULL, assume we're after shutdown and let the messages through. */ - - if (m && !manager_get_show_status(m, type)) - return; - - /* XXX We should totally drop the check for ephemeral here - * and thus effectively make 'Type=idle' pointless. */ - if (type == STATUS_TYPE_EPHEMERAL && m && m->n_on_console > 0) - return; - - va_start(ap, format); - status_vprintf(status, true, type == STATUS_TYPE_EPHEMERAL, format, ap); - va_end(ap); -} - -Set *manager_get_units_requiring_mounts_for(Manager *m, const char *path) { - char p[strlen(path)+1]; - - assert(m); - assert(path); - - strcpy(p, path); - path_kill_slashes(p); - - return hashmap_get(m->units_requiring_mounts_for, streq(p, "/") ? "" : p); -} - -const char *manager_get_runtime_prefix(Manager *m) { - assert(m); - - return MANAGER_IS_SYSTEM(m) ? - "/run" : - getenv("XDG_RUNTIME_DIR"); -} - -int manager_update_failed_units(Manager *m, Unit *u, bool failed) { - unsigned size; - int r; - - assert(m); - assert(u->manager == m); - - size = set_size(m->failed_units); - - if (failed) { - r = set_ensure_allocated(&m->failed_units, NULL); - if (r < 0) - return log_oom(); - - if (set_put(m->failed_units, u) < 0) - return log_oom(); - } else - (void) set_remove(m->failed_units, u); - - if (set_size(m->failed_units) != size) - bus_manager_send_change_signal(m); - - return 0; -} - -ManagerState manager_state(Manager *m) { - Unit *u; - - assert(m); - - /* Did we ever finish booting? If not then we are still starting up */ - if (!dual_timestamp_is_set(&m->finish_timestamp)) { - - u = manager_get_unit(m, SPECIAL_BASIC_TARGET); - if (!u || !UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) - return MANAGER_INITIALIZING; - - return MANAGER_STARTING; - } - - /* Is the special shutdown target queued? If so, we are in shutdown state */ - u = manager_get_unit(m, SPECIAL_SHUTDOWN_TARGET); - if (u && u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_RELOAD_OR_START)) - return MANAGER_STOPPING; - - /* Are the rescue or emergency targets active or queued? If so we are in maintenance state */ - u = manager_get_unit(m, SPECIAL_RESCUE_TARGET); - if (u && (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)) || - (u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_RELOAD_OR_START)))) - return MANAGER_MAINTENANCE; - - u = manager_get_unit(m, SPECIAL_EMERGENCY_TARGET); - if (u && (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)) || - (u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_RELOAD_OR_START)))) - return MANAGER_MAINTENANCE; - - /* Are there any failed units? If so, we are in degraded mode */ - if (set_size(m->failed_units) > 0) - return MANAGER_DEGRADED; - - return MANAGER_RUNNING; -} - -static const char *const manager_state_table[_MANAGER_STATE_MAX] = { - [MANAGER_INITIALIZING] = "initializing", - [MANAGER_STARTING] = "starting", - [MANAGER_RUNNING] = "running", - [MANAGER_DEGRADED] = "degraded", - [MANAGER_MAINTENANCE] = "maintenance", - [MANAGER_STOPPING] = "stopping", -}; - -DEFINE_STRING_TABLE_LOOKUP(manager_state, ManagerState); diff --git a/src/core/manager.h b/src/core/manager.h deleted file mode 100644 index 6ed15c1a41..0000000000 --- a/src/core/manager.h +++ /dev/null @@ -1,379 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include - -#include "sd-bus.h" -#include "sd-event.h" - -#include "cgroup-util.h" -#include "fdset.h" -#include "hashmap.h" -#include "list.h" -#include "ratelimit.h" - -/* Enforce upper limit how many names we allow */ -#define MANAGER_MAX_NAMES 131072 /* 128K */ - -typedef struct Manager Manager; - -typedef enum ManagerState { - MANAGER_INITIALIZING, - MANAGER_STARTING, - MANAGER_RUNNING, - MANAGER_DEGRADED, - MANAGER_MAINTENANCE, - MANAGER_STOPPING, - _MANAGER_STATE_MAX, - _MANAGER_STATE_INVALID = -1 -} ManagerState; - -typedef enum ManagerExitCode { - MANAGER_OK, - MANAGER_EXIT, - MANAGER_RELOAD, - MANAGER_REEXECUTE, - MANAGER_REBOOT, - MANAGER_POWEROFF, - MANAGER_HALT, - MANAGER_KEXEC, - MANAGER_SWITCH_ROOT, - _MANAGER_EXIT_CODE_MAX, - _MANAGER_EXIT_CODE_INVALID = -1 -} ManagerExitCode; - -typedef enum StatusType { - STATUS_TYPE_EPHEMERAL, - STATUS_TYPE_NORMAL, - STATUS_TYPE_EMERGENCY, -} StatusType; - -#include "execute.h" -#include "job.h" -#include "path-lookup.h" -#include "show-status.h" -#include "unit-name.h" - -struct Manager { - /* Note that the set of units we know of is allowed to be - * inconsistent. However the subset of it that is loaded may - * not, and the list of jobs may neither. */ - - /* Active jobs and units */ - Hashmap *units; /* name string => Unit object n:1 */ - Hashmap *jobs; /* job id => Job object 1:1 */ - - /* To make it easy to iterate through the units of a specific - * type we maintain a per type linked list */ - LIST_HEAD(Unit, units_by_type[_UNIT_TYPE_MAX]); - - /* Units that need to be loaded */ - LIST_HEAD(Unit, load_queue); /* this is actually more a stack than a queue, but uh. */ - - /* Jobs that need to be run */ - LIST_HEAD(Job, run_queue); /* more a stack than a queue, too */ - - /* Units and jobs that have not yet been announced via - * D-Bus. When something about a job changes it is added here - * if it is not in there yet. This allows easy coalescing of - * D-Bus change signals. */ - LIST_HEAD(Unit, dbus_unit_queue); - LIST_HEAD(Job, dbus_job_queue); - - /* Units to remove */ - LIST_HEAD(Unit, cleanup_queue); - - /* Units to check when doing GC */ - LIST_HEAD(Unit, gc_queue); - - /* Units that should be realized */ - LIST_HEAD(Unit, cgroup_queue); - - sd_event *event; - - /* We use two hash tables here, since the same PID might be - * watched by two different units: once the unit that forked - * it off, and possibly a different unit to which it was - * joined as cgroup member. Since we know that it is either - * one or two units for each PID we just use to hashmaps - * here. */ - Hashmap *watch_pids1; /* pid => Unit object n:1 */ - Hashmap *watch_pids2; /* pid => Unit object n:1 */ - - /* A set contains all units which cgroup should be refreshed after startup */ - Set *startup_units; - - /* A set which contains all currently failed units */ - Set *failed_units; - - sd_event_source *run_queue_event_source; - - char *notify_socket; - int notify_fd; - sd_event_source *notify_event_source; - - int cgroups_agent_fd; - sd_event_source *cgroups_agent_event_source; - - int signal_fd; - sd_event_source *signal_event_source; - - int time_change_fd; - sd_event_source *time_change_event_source; - - sd_event_source *jobs_in_progress_event_source; - - UnitFileScope unit_file_scope; - LookupPaths lookup_paths; - Set *unit_path_cache; - - char **environment; - - usec_t runtime_watchdog; - usec_t shutdown_watchdog; - - dual_timestamp firmware_timestamp; - dual_timestamp loader_timestamp; - dual_timestamp kernel_timestamp; - dual_timestamp initrd_timestamp; - dual_timestamp userspace_timestamp; - dual_timestamp finish_timestamp; - - dual_timestamp security_start_timestamp; - dual_timestamp security_finish_timestamp; - dual_timestamp generators_start_timestamp; - dual_timestamp generators_finish_timestamp; - dual_timestamp units_load_start_timestamp; - dual_timestamp units_load_finish_timestamp; - - struct udev* udev; - - /* Data specific to the device subsystem */ - struct udev_monitor* udev_monitor; - sd_event_source *udev_event_source; - Hashmap *devices_by_sysfs; - - /* Data specific to the mount subsystem */ - struct libmnt_monitor *mount_monitor; - sd_event_source *mount_event_source; - - /* Data specific to the swap filesystem */ - FILE *proc_swaps; - sd_event_source *swap_event_source; - Hashmap *swaps_by_devnode; - - /* Data specific to the D-Bus subsystem */ - sd_bus *api_bus, *system_bus; - Set *private_buses; - int private_listen_fd; - sd_event_source *private_listen_event_source; - - /* Contains all the clients that are subscribed to signals via - the API bus. Note that private bus connections are always - considered subscribes, since they last for very short only, - and it is much simpler that way. */ - sd_bus_track *subscribed; - char **deserialized_subscribed; - - /* This is used during reloading: before the reload we queue - * the reply message here, and afterwards we send it */ - sd_bus_message *queued_message; - - Hashmap *watch_bus; /* D-Bus names => Unit object n:1 */ - - bool send_reloading_done; - - uint32_t current_job_id; - uint32_t default_unit_job_id; - - /* Data specific to the Automount subsystem */ - int dev_autofs_fd; - - /* Data specific to the cgroup subsystem */ - Hashmap *cgroup_unit; - CGroupMask cgroup_supported; - char *cgroup_root; - - /* Notifications from cgroups, when the unified hierarchy is - * used is done via inotify. */ - int cgroup_inotify_fd; - sd_event_source *cgroup_inotify_event_source; - Hashmap *cgroup_inotify_wd_unit; - - /* Make sure the user cannot accidentally unmount our cgroup - * file system */ - int pin_cgroupfs_fd; - - int gc_marker; - unsigned n_in_gc_queue; - - /* Flags */ - ManagerExitCode exit_code:5; - - bool dispatching_load_queue:1; - bool dispatching_dbus_queue:1; - - bool taint_usr:1; - - bool test_run:1; - - /* If non-zero, exit with the following value when the systemd - * process terminate. Useful for containers: systemd-nspawn could get - * the return value. */ - uint8_t return_value; - - ShowStatus show_status; - bool confirm_spawn; - bool no_console_output; - - ExecOutput default_std_output, default_std_error; - - usec_t default_restart_usec, default_timeout_start_usec, default_timeout_stop_usec; - - usec_t default_start_limit_interval; - unsigned default_start_limit_burst; - - bool default_cpu_accounting; - bool default_memory_accounting; - bool default_io_accounting; - bool default_blockio_accounting; - bool default_tasks_accounting; - - uint64_t default_tasks_max; - usec_t default_timer_accuracy_usec; - - struct rlimit *rlimit[_RLIMIT_MAX]; - - /* non-zero if we are reloading or reexecuting, */ - int n_reloading; - - unsigned n_installed_jobs; - unsigned n_failed_jobs; - - /* Jobs in progress watching */ - unsigned n_running_jobs; - unsigned n_on_console; - unsigned jobs_in_progress_iteration; - - /* Do we have any outstanding password prompts? */ - int have_ask_password; - int ask_password_inotify_fd; - sd_event_source *ask_password_event_source; - - /* Type=idle pipes */ - int idle_pipe[4]; - sd_event_source *idle_pipe_event_source; - - char *switch_root; - char *switch_root_init; - - /* This maps all possible path prefixes to the units needing - * them. It's a hashmap with a path string as key and a Set as - * value where Unit objects are contained. */ - Hashmap *units_requiring_mounts_for; - - /* Reference to the kdbus bus control fd */ - int kdbus_fd; - - /* Used for processing polkit authorization responses */ - Hashmap *polkit_registry; - - /* When the user hits C-A-D more than 7 times per 2s, reboot immediately... */ - RateLimit ctrl_alt_del_ratelimit; - - const char *unit_log_field; - const char *unit_log_format_string; - - int first_boot; /* tri-state */ -}; - -#define MANAGER_IS_SYSTEM(m) ((m)->unit_file_scope == UNIT_FILE_SYSTEM) -#define MANAGER_IS_USER(m) ((m)->unit_file_scope != UNIT_FILE_SYSTEM) - -#define MANAGER_IS_RELOADING(m) ((m)->n_reloading > 0) - -int manager_new(UnitFileScope scope, bool test_run, Manager **m); -Manager* manager_free(Manager *m); - -void manager_enumerate(Manager *m); -int manager_startup(Manager *m, FILE *serialization, FDSet *fds); - -Job *manager_get_job(Manager *m, uint32_t id); -Unit *manager_get_unit(Manager *m, const char *name); - -int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j); - -int manager_load_unit_prepare(Manager *m, const char *name, const char *path, sd_bus_error *e, Unit **_ret); -int manager_load_unit(Manager *m, const char *name, const char *path, sd_bus_error *e, Unit **_ret); -int manager_load_unit_from_dbus_path(Manager *m, const char *s, sd_bus_error *e, Unit **_u); - -int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, sd_bus_error *e, Job **_ret); -int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, sd_bus_error *e, Job **_ret); -int manager_add_job_by_name_and_warn(Manager *m, JobType type, const char *name, JobMode mode, Job **ret); - -void manager_dump_units(Manager *s, FILE *f, const char *prefix); -void manager_dump_jobs(Manager *s, FILE *f, const char *prefix); - -void manager_clear_jobs(Manager *m); - -unsigned manager_dispatch_load_queue(Manager *m); - -int manager_environment_add(Manager *m, char **minus, char **plus); -int manager_set_default_rlimits(Manager *m, struct rlimit **default_rlimit); - -int manager_loop(Manager *m); - -int manager_open_serialization(Manager *m, FILE **_f); - -int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root); -int manager_deserialize(Manager *m, FILE *f, FDSet *fds); - -int manager_reload(Manager *m); - -void manager_reset_failed(Manager *m); - -void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success); -void manager_send_unit_plymouth(Manager *m, Unit *u); - -bool manager_unit_inactive_or_pending(Manager *m, const char *name); - -void manager_check_finished(Manager *m); - -void manager_recheck_journal(Manager *m); - -void manager_set_show_status(Manager *m, ShowStatus mode); -void manager_set_first_boot(Manager *m, bool b); - -void manager_status_printf(Manager *m, StatusType type, const char *status, const char *format, ...) _printf_(4,5); -void manager_flip_auto_status(Manager *m, bool enable); - -Set *manager_get_units_requiring_mounts_for(Manager *m, const char *path); - -const char *manager_get_runtime_prefix(Manager *m); - -ManagerState manager_state(Manager *m); - -int manager_update_failed_units(Manager *m, Unit *u, bool failed); - -const char *manager_state_to_string(ManagerState m) _const_; -ManagerState manager_state_from_string(const char *s) _pure_; diff --git a/src/core/mount-setup.c b/src/core/mount-setup.c deleted file mode 100644 index 40fc548b42..0000000000 --- a/src/core/mount-setup.c +++ /dev/null @@ -1,413 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include - -#include "alloc-util.h" -#include "bus-util.h" -#include "cgroup-util.h" -#include "dev-setup.h" -#include "efivars.h" -#include "label.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "mount-setup.h" -#include "mount-util.h" -#include "path-util.h" -#include "set.h" -#include "smack-util.h" -#include "strv.h" -#include "user-util.h" -#include "util.h" -#include "virt.h" - -typedef enum MountMode { - MNT_NONE = 0, - MNT_FATAL = 1 << 0, - MNT_IN_CONTAINER = 1 << 1, -} MountMode; - -typedef struct MountPoint { - const char *what; - const char *where; - const char *type; - const char *options; - unsigned long flags; - bool (*condition_fn)(void); - MountMode mode; -} MountPoint; - -/* The first three entries we might need before SELinux is up. The - * fourth (securityfs) is needed by IMA to load a custom policy. The - * other ones we can delay until SELinux and IMA are loaded. When - * SMACK is enabled we need smackfs, too, so it's a fifth one. */ -#ifdef HAVE_SMACK -#define N_EARLY_MOUNT 5 -#else -#define N_EARLY_MOUNT 4 -#endif - -static const MountPoint mount_table[] = { - { "sysfs", "/sys", "sysfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, - NULL, MNT_FATAL|MNT_IN_CONTAINER }, - { "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, - NULL, MNT_FATAL|MNT_IN_CONTAINER }, - { "devtmpfs", "/dev", "devtmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME, - NULL, MNT_FATAL|MNT_IN_CONTAINER }, - { "securityfs", "/sys/kernel/security", "securityfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, - NULL, MNT_NONE }, -#ifdef HAVE_SMACK - { "smackfs", "/sys/fs/smackfs", "smackfs", "smackfsdef=*", MS_NOSUID|MS_NOEXEC|MS_NODEV, - mac_smack_use, MNT_FATAL }, - { "tmpfs", "/dev/shm", "tmpfs", "mode=1777,smackfsroot=*", MS_NOSUID|MS_NODEV|MS_STRICTATIME, - mac_smack_use, MNT_FATAL }, -#endif - { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, - NULL, MNT_FATAL|MNT_IN_CONTAINER }, - { "devpts", "/dev/pts", "devpts", "mode=620,gid=" STRINGIFY(TTY_GID), MS_NOSUID|MS_NOEXEC, - NULL, MNT_IN_CONTAINER }, -#ifdef HAVE_SMACK - { "tmpfs", "/run", "tmpfs", "mode=755,smackfsroot=*", MS_NOSUID|MS_NODEV|MS_STRICTATIME, - mac_smack_use, MNT_FATAL }, -#endif - { "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME, - NULL, MNT_FATAL|MNT_IN_CONTAINER }, - { "cgroup", "/sys/fs/cgroup", "cgroup2", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, - cg_is_unified_wanted, MNT_FATAL|MNT_IN_CONTAINER }, - { "tmpfs", "/sys/fs/cgroup", "tmpfs", "mode=755", MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, - cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER }, - { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd,xattr", MS_NOSUID|MS_NOEXEC|MS_NODEV, - cg_is_legacy_wanted, MNT_IN_CONTAINER }, - { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd", MS_NOSUID|MS_NOEXEC|MS_NODEV, - cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER }, - { "pstore", "/sys/fs/pstore", "pstore", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, - NULL, MNT_NONE }, -#ifdef ENABLE_EFI - { "efivarfs", "/sys/firmware/efi/efivars", "efivarfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, - is_efi_boot, MNT_NONE }, -#endif - { "kdbusfs", "/sys/fs/kdbus", "kdbusfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, - is_kdbus_wanted, MNT_IN_CONTAINER }, -}; - -/* These are API file systems that might be mounted by other software, - * we just list them here so that we know that we should ignore them */ - -static const char ignore_paths[] = - /* SELinux file systems */ - "/sys/fs/selinux\0" - /* Container bind mounts */ - "/proc/sys\0" - "/dev/console\0" - "/proc/kmsg\0"; - -bool mount_point_is_api(const char *path) { - unsigned i; - - /* Checks if this mount point is considered "API", and hence - * should be ignored */ - - for (i = 0; i < ELEMENTSOF(mount_table); i ++) - if (path_equal(path, mount_table[i].where)) - return true; - - return path_startswith(path, "/sys/fs/cgroup/"); -} - -bool mount_point_ignore(const char *path) { - const char *i; - - NULSTR_FOREACH(i, ignore_paths) - if (path_equal(path, i)) - return true; - - return false; -} - -static int mount_one(const MountPoint *p, bool relabel) { - int r; - - assert(p); - - if (p->condition_fn && !p->condition_fn()) - return 0; - - /* Relabel first, just in case */ - if (relabel) - (void) label_fix(p->where, true, true); - - r = path_is_mount_point(p->where, AT_SYMLINK_FOLLOW); - if (r < 0 && r != -ENOENT) { - log_full_errno((p->mode & MNT_FATAL) ? LOG_ERR : LOG_DEBUG, r, "Failed to determine whether %s is a mount point: %m", p->where); - return (p->mode & MNT_FATAL) ? r : 0; - } - if (r > 0) - return 0; - - /* Skip securityfs in a container */ - if (!(p->mode & MNT_IN_CONTAINER) && detect_container() > 0) - return 0; - - /* The access mode here doesn't really matter too much, since - * the mounted file system will take precedence anyway. */ - if (relabel) - (void) mkdir_p_label(p->where, 0755); - else - (void) mkdir_p(p->where, 0755); - - log_debug("Mounting %s to %s of type %s with options %s.", - p->what, - p->where, - p->type, - strna(p->options)); - - if (mount(p->what, - p->where, - p->type, - p->flags, - p->options) < 0) { - log_full_errno((p->mode & MNT_FATAL) ? LOG_ERR : LOG_DEBUG, errno, "Failed to mount %s at %s: %m", p->type, p->where); - return (p->mode & MNT_FATAL) ? -errno : 0; - } - - /* Relabel again, since we now mounted something fresh here */ - if (relabel) - (void) label_fix(p->where, false, false); - - return 1; -} - -static int mount_points_setup(unsigned n, bool loaded_policy) { - unsigned i; - int r = 0; - - for (i = 0; i < n; i ++) { - int j; - - j = mount_one(mount_table + i, loaded_policy); - if (j != 0 && r >= 0) - r = j; - } - - return r; -} - -int mount_setup_early(void) { - assert_cc(N_EARLY_MOUNT <= ELEMENTSOF(mount_table)); - - /* Do a minimal mount of /proc and friends to enable the most - * basic stuff, such as SELinux */ - return mount_points_setup(N_EARLY_MOUNT, false); -} - -int mount_cgroup_controllers(char ***join_controllers) { - _cleanup_set_free_free_ Set *controllers = NULL; - int r; - - if (!cg_is_legacy_wanted()) - return 0; - - /* Mount all available cgroup controllers that are built into the kernel. */ - - controllers = set_new(&string_hash_ops); - if (!controllers) - return log_oom(); - - r = cg_kernel_controllers(controllers); - if (r < 0) - return log_error_errno(r, "Failed to enumerate cgroup controllers: %m"); - - for (;;) { - _cleanup_free_ char *options = NULL, *controller = NULL, *where = NULL; - MountPoint p = { - .what = "cgroup", - .type = "cgroup", - .flags = MS_NOSUID|MS_NOEXEC|MS_NODEV, - .mode = MNT_IN_CONTAINER, - }; - char ***k = NULL; - - controller = set_steal_first(controllers); - if (!controller) - break; - - if (join_controllers) - for (k = join_controllers; *k; k++) - if (strv_find(*k, controller)) - break; - - if (k && *k) { - char **i, **j; - - for (i = *k, j = *k; *i; i++) { - - if (!streq(*i, controller)) { - _cleanup_free_ char *t; - - t = set_remove(controllers, *i); - if (!t) { - free(*i); - continue; - } - } - - *(j++) = *i; - } - - *j = NULL; - - options = strv_join(*k, ","); - if (!options) - return log_oom(); - } else { - options = controller; - controller = NULL; - } - - where = strappend("/sys/fs/cgroup/", options); - if (!where) - return log_oom(); - - p.where = where; - p.options = options; - - r = mount_one(&p, true); - if (r < 0) - return r; - - if (r > 0 && k && *k) { - char **i; - - for (i = *k; *i; i++) { - _cleanup_free_ char *t = NULL; - - t = strappend("/sys/fs/cgroup/", *i); - if (!t) - return log_oom(); - - r = symlink(options, t); - if (r >= 0) { -#ifdef SMACK_RUN_LABEL - _cleanup_free_ char *src; - src = strappend("/sys/fs/cgroup/", options); - if (!src) - return log_oom(); - r = mac_smack_copy(t, src); - if (r < 0 && r != -EOPNOTSUPP) - return log_error_errno(r, "Failed to copy smack label from %s to %s: %m", src, t); -#endif - } else if (errno != EEXIST) - return log_error_errno(errno, "Failed to create symlink %s: %m", t); - } - } - } - - /* Now that we mounted everything, let's make the tmpfs the - * cgroup file systems are mounted into read-only. */ - (void) mount("tmpfs", "/sys/fs/cgroup", "tmpfs", MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, "mode=755"); - - return 0; -} - -#if defined(HAVE_SELINUX) || defined(HAVE_SMACK) -static int nftw_cb( - const char *fpath, - const struct stat *sb, - int tflag, - struct FTW *ftwbuf) { - - /* No need to label /dev twice in a row... */ - if (_unlikely_(ftwbuf->level == 0)) - return FTW_CONTINUE; - - label_fix(fpath, false, false); - - /* /run/initramfs is static data and big, no need to - * dynamically relabel its contents at boot... */ - if (_unlikely_(ftwbuf->level == 1 && - tflag == FTW_D && - streq(fpath, "/run/initramfs"))) - return FTW_SKIP_SUBTREE; - - return FTW_CONTINUE; -}; -#endif - -int mount_setup(bool loaded_policy) { - int r = 0; - - r = mount_points_setup(ELEMENTSOF(mount_table), loaded_policy); - - if (r < 0) - return r; - -#if defined(HAVE_SELINUX) || defined(HAVE_SMACK) - /* Nodes in devtmpfs and /run need to be manually updated for - * the appropriate labels, after mounting. The other virtual - * API file systems like /sys and /proc do not need that, they - * use the same label for all their files. */ - if (loaded_policy) { - usec_t before_relabel, after_relabel; - char timespan[FORMAT_TIMESPAN_MAX]; - - before_relabel = now(CLOCK_MONOTONIC); - - nftw("/dev", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL); - nftw("/dev/shm", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL); - nftw("/run", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL); - - after_relabel = now(CLOCK_MONOTONIC); - - log_info("Relabelled /dev and /run in %s.", - format_timespan(timespan, sizeof(timespan), after_relabel - before_relabel, 0)); - } -#endif - - /* Create a few default symlinks, which are normally created - * by udevd, but some scripts might need them before we start - * udevd. */ - dev_setup(NULL, UID_INVALID, GID_INVALID); - - /* Mark the root directory as shared in regards to mount - * propagation. The kernel defaults to "private", but we think - * it makes more sense to have a default of "shared" so that - * nspawn and the container tools work out of the box. If - * specific setups need other settings they can reset the - * propagation mode to private if needed. */ - if (detect_container() <= 0) - if (mount(NULL, "/", NULL, MS_REC|MS_SHARED, NULL) < 0) - log_warning_errno(errno, "Failed to set up the root directory for shared mount propagation: %m"); - - /* Create a few directories we always want around, Note that - * sd_booted() checks for /run/systemd/system, so this mkdir - * really needs to stay for good, otherwise software that - * copied sd-daemon.c into their sources will misdetect - * systemd. */ - mkdir_label("/run/systemd", 0755); - mkdir_label("/run/systemd/system", 0755); - mkdir_label("/run/systemd/inaccessible", 0000); - - return 0; -} diff --git a/src/core/mount-setup.h b/src/core/mount-setup.h deleted file mode 100644 index 647bd770ae..0000000000 --- a/src/core/mount-setup.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 - -int mount_setup_early(void); -int mount_setup(bool loaded_policy); - -int mount_cgroup_controllers(char ***join_controllers); - -bool mount_point_is_api(const char *path); -bool mount_point_ignore(const char *path); diff --git a/src/core/mount.c b/src/core/mount.c deleted file mode 100644 index 665a60bb55..0000000000 --- a/src/core/mount.c +++ /dev/null @@ -1,1881 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include - -#include "sd-messages.h" - -#include "alloc-util.h" -#include "dbus-mount.h" -#include "escape.h" -#include "exit-status.h" -#include "formats-util.h" -#include "fstab-util.h" -#include "log.h" -#include "manager.h" -#include "mkdir.h" -#include "mount-setup.h" -#include "mount-util.h" -#include "mount.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "special.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "unit.h" - -#define RETRY_UMOUNT_MAX 32 - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct libmnt_table*, mnt_free_table); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct libmnt_iter*, mnt_free_iter); - -static const UnitActiveState state_translation_table[_MOUNT_STATE_MAX] = { - [MOUNT_DEAD] = UNIT_INACTIVE, - [MOUNT_MOUNTING] = UNIT_ACTIVATING, - [MOUNT_MOUNTING_DONE] = UNIT_ACTIVE, - [MOUNT_MOUNTED] = UNIT_ACTIVE, - [MOUNT_REMOUNTING] = UNIT_RELOADING, - [MOUNT_UNMOUNTING] = UNIT_DEACTIVATING, - [MOUNT_MOUNTING_SIGTERM] = UNIT_DEACTIVATING, - [MOUNT_MOUNTING_SIGKILL] = UNIT_DEACTIVATING, - [MOUNT_REMOUNTING_SIGTERM] = UNIT_RELOADING, - [MOUNT_REMOUNTING_SIGKILL] = UNIT_RELOADING, - [MOUNT_UNMOUNTING_SIGTERM] = UNIT_DEACTIVATING, - [MOUNT_UNMOUNTING_SIGKILL] = UNIT_DEACTIVATING, - [MOUNT_FAILED] = UNIT_FAILED -}; - -static int mount_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); -static int mount_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); - -static bool mount_needs_network(const char *options, const char *fstype) { - if (fstab_test_option(options, "_netdev\0")) - return true; - - if (fstype && fstype_is_network(fstype)) - return true; - - return false; -} - -static bool mount_is_network(const MountParameters *p) { - assert(p); - - return mount_needs_network(p->options, p->fstype); -} - -static bool mount_is_loop(const MountParameters *p) { - assert(p); - - if (fstab_test_option(p->options, "loop\0")) - return true; - - return false; -} - -static bool mount_is_bind(const MountParameters *p) { - assert(p); - - if (fstab_test_option(p->options, "bind\0" "rbind\0")) - return true; - - if (p->fstype && STR_IN_SET(p->fstype, "bind", "rbind")) - return true; - - return false; -} - -static bool mount_is_auto(const MountParameters *p) { - assert(p); - - return !fstab_test_option(p->options, "noauto\0"); -} - -static bool mount_is_automount(const MountParameters *p) { - assert(p); - - return fstab_test_option(p->options, - "comment=systemd.automount\0" - "x-systemd.automount\0"); -} - -static bool mount_state_active(MountState state) { - return IN_SET(state, - MOUNT_MOUNTING, - MOUNT_MOUNTING_DONE, - MOUNT_REMOUNTING, - MOUNT_UNMOUNTING, - MOUNT_MOUNTING_SIGTERM, - MOUNT_MOUNTING_SIGKILL, - MOUNT_UNMOUNTING_SIGTERM, - MOUNT_UNMOUNTING_SIGKILL, - MOUNT_REMOUNTING_SIGTERM, - MOUNT_REMOUNTING_SIGKILL); -} - -static bool needs_quota(const MountParameters *p) { - assert(p); - - /* Quotas are not enabled on network filesystems, - * but we want them, for example, on storage connected via iscsi */ - if (p->fstype && fstype_is_network(p->fstype)) - return false; - - if (mount_is_bind(p)) - return false; - - return fstab_test_option(p->options, - "usrquota\0" "grpquota\0" "quota\0" "usrjquota\0" "grpjquota\0"); -} - -static void mount_init(Unit *u) { - Mount *m = MOUNT(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - m->timeout_usec = u->manager->default_timeout_start_usec; - m->directory_mode = 0755; - - if (unit_has_name(u, "-.mount")) { - /* Don't allow start/stop for root directory */ - u->refuse_manual_start = true; - u->refuse_manual_stop = true; - } else { - /* The stdio/kmsg bridge socket is on /, in order to avoid a - * dep loop, don't use kmsg logging for -.mount */ - m->exec_context.std_output = u->manager->default_std_output; - m->exec_context.std_error = u->manager->default_std_error; - } - - /* We need to make sure that /usr/bin/mount is always called - * in the same process group as us, so that the autofs kernel - * side doesn't send us another mount request while we are - * already trying to comply its last one. */ - m->exec_context.same_pgrp = true; - - m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; - - u->ignore_on_isolate = true; -} - -static int mount_arm_timer(Mount *m, usec_t usec) { - int r; - - assert(m); - - if (m->timer_event_source) { - r = sd_event_source_set_time(m->timer_event_source, usec); - if (r < 0) - return r; - - return sd_event_source_set_enabled(m->timer_event_source, SD_EVENT_ONESHOT); - } - - if (usec == USEC_INFINITY) - return 0; - - r = sd_event_add_time( - UNIT(m)->manager->event, - &m->timer_event_source, - CLOCK_MONOTONIC, - usec, 0, - mount_dispatch_timer, m); - if (r < 0) - return r; - - (void) sd_event_source_set_description(m->timer_event_source, "mount-timer"); - - return 0; -} - -static void mount_unwatch_control_pid(Mount *m) { - assert(m); - - if (m->control_pid <= 0) - return; - - unit_unwatch_pid(UNIT(m), m->control_pid); - m->control_pid = 0; -} - -static void mount_parameters_done(MountParameters *p) { - assert(p); - - free(p->what); - free(p->options); - free(p->fstype); - - p->what = p->options = p->fstype = NULL; -} - -static void mount_done(Unit *u) { - Mount *m = MOUNT(u); - - assert(m); - - m->where = mfree(m->where); - - mount_parameters_done(&m->parameters_proc_self_mountinfo); - mount_parameters_done(&m->parameters_fragment); - - m->exec_runtime = exec_runtime_unref(m->exec_runtime); - exec_command_done_array(m->exec_command, _MOUNT_EXEC_COMMAND_MAX); - m->control_command = NULL; - - mount_unwatch_control_pid(m); - - m->timer_event_source = sd_event_source_unref(m->timer_event_source); -} - -_pure_ static MountParameters* get_mount_parameters_fragment(Mount *m) { - assert(m); - - if (m->from_fragment) - return &m->parameters_fragment; - - return NULL; -} - -_pure_ static MountParameters* get_mount_parameters(Mount *m) { - assert(m); - - if (m->from_proc_self_mountinfo) - return &m->parameters_proc_self_mountinfo; - - return get_mount_parameters_fragment(m); -} - -static int mount_add_mount_links(Mount *m) { - _cleanup_free_ char *parent = NULL; - MountParameters *pm; - Unit *other; - Iterator i; - Set *s; - int r; - - assert(m); - - if (!path_equal(m->where, "/")) { - /* Adds in links to other mount points that might lie further - * up in the hierarchy */ - - parent = dirname_malloc(m->where); - if (!parent) - return -ENOMEM; - - r = unit_require_mounts_for(UNIT(m), parent); - if (r < 0) - return r; - } - - /* Adds in links to other mount points that might be needed - * for the source path (if this is a bind mount or a loop mount) to be - * available. */ - pm = get_mount_parameters_fragment(m); - if (pm && pm->what && - path_is_absolute(pm->what) && - (mount_is_bind(pm) || mount_is_loop(pm) || !mount_is_network(pm))) { - - r = unit_require_mounts_for(UNIT(m), pm->what); - if (r < 0) - return r; - } - - /* Adds in links to other units that use this path or paths - * further down in the hierarchy */ - s = manager_get_units_requiring_mounts_for(UNIT(m)->manager, m->where); - SET_FOREACH(other, s, i) { - - if (other->load_state != UNIT_LOADED) - continue; - - if (other == UNIT(m)) - continue; - - r = unit_add_dependency(other, UNIT_AFTER, UNIT(m), true); - if (r < 0) - return r; - - if (UNIT(m)->fragment_path) { - /* If we have fragment configuration, then make this dependency required */ - r = unit_add_dependency(other, UNIT_REQUIRES, UNIT(m), true); - if (r < 0) - return r; - } - } - - return 0; -} - -static int mount_add_device_links(Mount *m) { - MountParameters *p; - bool device_wants_mount = false; - int r; - - assert(m); - - p = get_mount_parameters(m); - if (!p) - return 0; - - if (!p->what) - return 0; - - if (mount_is_bind(p)) - return 0; - - if (!is_device_path(p->what)) - return 0; - - /* /dev/root is a really weird thing, it's not a real device, - * but just a path the kernel exports for the root file system - * specified on the kernel command line. Ignore it here. */ - if (path_equal(p->what, "/dev/root")) - return 0; - - if (path_equal(m->where, "/")) - return 0; - - if (mount_is_auto(p) && !mount_is_automount(p) && MANAGER_IS_SYSTEM(UNIT(m)->manager)) - device_wants_mount = true; - - r = unit_add_node_link(UNIT(m), p->what, device_wants_mount, m->from_fragment ? UNIT_BINDS_TO : UNIT_REQUIRES); - if (r < 0) - return r; - - return 0; -} - -static int mount_add_quota_links(Mount *m) { - int r; - MountParameters *p; - - assert(m); - - if (!MANAGER_IS_SYSTEM(UNIT(m)->manager)) - return 0; - - p = get_mount_parameters_fragment(m); - if (!p) - return 0; - - if (!needs_quota(p)) - return 0; - - r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTACHECK_SERVICE, NULL, true); - if (r < 0) - return r; - - r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTAON_SERVICE, NULL, true); - if (r < 0) - return r; - - return 0; -} - -static bool should_umount(Mount *m) { - MountParameters *p; - - if (PATH_IN_SET(m->where, "/", "/usr") || - path_startswith(m->where, "/run/initramfs")) - return false; - - p = get_mount_parameters(m); - if (p && fstab_test_option(p->options, "x-initrd.mount\0") && - !in_initrd()) - return false; - - return true; -} - -static int mount_add_default_dependencies(Mount *m) { - MountParameters *p; - const char *after; - int r; - - assert(m); - - if (!UNIT(m)->default_dependencies) - return 0; - - if (!MANAGER_IS_SYSTEM(UNIT(m)->manager)) - return 0; - - /* We do not add any default dependencies to /, /usr or - * /run/initramfs/, since they are guaranteed to stay - * mounted the whole time, since our system is on it. - * Also, don't bother with anything mounted below virtual - * file systems, it's also going to be virtual, and hence - * not worth the effort. */ - if (PATH_IN_SET(m->where, "/", "/usr") || - path_startswith(m->where, "/run/initramfs") || - path_startswith(m->where, "/proc") || - path_startswith(m->where, "/sys") || - path_startswith(m->where, "/dev")) - return 0; - - p = get_mount_parameters(m); - if (!p) - return 0; - - if (mount_is_network(p)) { - /* We order ourselves after network.target. This is - * primarily useful at shutdown: services that take - * down the network should order themselves before - * network.target, so that they are shut down only - * after this mount unit is stopped. */ - - r = unit_add_dependency_by_name(UNIT(m), UNIT_AFTER, SPECIAL_NETWORK_TARGET, NULL, true); - if (r < 0) - return r; - - /* We pull in network-online.target, and order - * ourselves after it. This is useful at start-up to - * actively pull in tools that want to be started - * before we start mounting network file systems, and - * whose purpose it is to delay this until the network - * is "up". */ - - r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_WANTS, UNIT_AFTER, SPECIAL_NETWORK_ONLINE_TARGET, NULL, true); - if (r < 0) - return r; - - after = SPECIAL_REMOTE_FS_PRE_TARGET; - } else - after = SPECIAL_LOCAL_FS_PRE_TARGET; - - r = unit_add_dependency_by_name(UNIT(m), UNIT_AFTER, after, NULL, true); - if (r < 0) - return r; - - if (should_umount(m)) { - r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); - if (r < 0) - return r; - } - - return 0; -} - -static int mount_verify(Mount *m) { - _cleanup_free_ char *e = NULL; - int r; - - assert(m); - - if (UNIT(m)->load_state != UNIT_LOADED) - return 0; - - if (!m->from_fragment && !m->from_proc_self_mountinfo) - return -ENOENT; - - r = unit_name_from_path(m->where, ".mount", &e); - if (r < 0) - return log_unit_error_errno(UNIT(m), r, "Failed to generate unit name from mount path: %m"); - - if (!unit_has_name(UNIT(m), e)) { - log_unit_error(UNIT(m), "Where= setting doesn't match unit name. Refusing."); - return -EINVAL; - } - - if (mount_point_is_api(m->where) || mount_point_ignore(m->where)) { - log_unit_error(UNIT(m), "Cannot create mount unit for API file system %s. Refusing.", m->where); - return -EINVAL; - } - - if (UNIT(m)->fragment_path && !m->parameters_fragment.what) { - log_unit_error(UNIT(m), "What= setting is missing. Refusing."); - return -EBADMSG; - } - - if (m->exec_context.pam_name && m->kill_context.kill_mode != KILL_CONTROL_GROUP) { - log_unit_error(UNIT(m), "Unit has PAM enabled. Kill mode must be set to control-group'. Refusing."); - return -EINVAL; - } - - return 0; -} - -static int mount_add_extras(Mount *m) { - Unit *u = UNIT(m); - int r; - - assert(m); - - if (u->fragment_path) - m->from_fragment = true; - - if (!m->where) { - r = unit_name_to_path(u->id, &m->where); - if (r < 0) - return r; - } - - path_kill_slashes(m->where); - - if (!u->description) { - r = unit_set_description(u, m->where); - if (r < 0) - return r; - } - - r = mount_add_device_links(m); - if (r < 0) - return r; - - r = mount_add_mount_links(m); - if (r < 0) - return r; - - r = mount_add_quota_links(m); - if (r < 0) - return r; - - r = unit_patch_contexts(u); - if (r < 0) - return r; - - r = unit_add_exec_dependencies(u, &m->exec_context); - if (r < 0) - return r; - - r = unit_set_default_slice(u); - if (r < 0) - return r; - - r = mount_add_default_dependencies(m); - if (r < 0) - return r; - - return 0; -} - -static int mount_load(Unit *u) { - Mount *m = MOUNT(u); - int r; - - assert(u); - assert(u->load_state == UNIT_STUB); - - if (m->from_proc_self_mountinfo) - r = unit_load_fragment_and_dropin_optional(u); - else - r = unit_load_fragment_and_dropin(u); - - if (r < 0) - return r; - - /* This is a new unit? Then let's add in some extras */ - if (u->load_state == UNIT_LOADED) { - r = mount_add_extras(m); - if (r < 0) - return r; - } - - return mount_verify(m); -} - -static void mount_set_state(Mount *m, MountState state) { - MountState old_state; - assert(m); - - old_state = m->state; - m->state = state; - - if (!mount_state_active(state)) { - m->timer_event_source = sd_event_source_unref(m->timer_event_source); - mount_unwatch_control_pid(m); - m->control_command = NULL; - m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; - } - - if (state != old_state) - log_unit_debug(UNIT(m), "Changed %s -> %s", mount_state_to_string(old_state), mount_state_to_string(state)); - - unit_notify(UNIT(m), state_translation_table[old_state], state_translation_table[state], m->reload_result == MOUNT_SUCCESS); - m->reload_result = MOUNT_SUCCESS; -} - -static int mount_coldplug(Unit *u) { - Mount *m = MOUNT(u); - MountState new_state = MOUNT_DEAD; - int r; - - assert(m); - assert(m->state == MOUNT_DEAD); - - if (m->deserialized_state != m->state) - new_state = m->deserialized_state; - else if (m->from_proc_self_mountinfo) - new_state = MOUNT_MOUNTED; - - if (new_state == m->state) - return 0; - - if (m->control_pid > 0 && - pid_is_unwaited(m->control_pid) && - mount_state_active(new_state)) { - - r = unit_watch_pid(UNIT(m), m->control_pid); - if (r < 0) - return r; - - r = mount_arm_timer(m, usec_add(u->state_change_timestamp.monotonic, m->timeout_usec)); - if (r < 0) - return r; - } - - mount_set_state(m, new_state); - return 0; -} - -static void mount_dump(Unit *u, FILE *f, const char *prefix) { - Mount *m = MOUNT(u); - MountParameters *p; - - assert(m); - assert(f); - - p = get_mount_parameters(m); - - fprintf(f, - "%sMount State: %s\n" - "%sResult: %s\n" - "%sWhere: %s\n" - "%sWhat: %s\n" - "%sFile System Type: %s\n" - "%sOptions: %s\n" - "%sFrom /proc/self/mountinfo: %s\n" - "%sFrom fragment: %s\n" - "%sDirectoryMode: %04o\n", - prefix, mount_state_to_string(m->state), - prefix, mount_result_to_string(m->result), - prefix, m->where, - prefix, p ? strna(p->what) : "n/a", - prefix, p ? strna(p->fstype) : "n/a", - prefix, p ? strna(p->options) : "n/a", - prefix, yes_no(m->from_proc_self_mountinfo), - prefix, yes_no(m->from_fragment), - prefix, m->directory_mode); - - if (m->control_pid > 0) - fprintf(f, - "%sControl PID: "PID_FMT"\n", - prefix, m->control_pid); - - exec_context_dump(&m->exec_context, f, prefix); - kill_context_dump(&m->kill_context, f, prefix); -} - -static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { - pid_t pid; - int r; - ExecParameters exec_params = { - .apply_permissions = true, - .apply_chroot = true, - .apply_tty_stdin = true, - .stdin_fd = -1, - .stdout_fd = -1, - .stderr_fd = -1, - }; - - assert(m); - assert(c); - assert(_pid); - - (void) unit_realize_cgroup(UNIT(m)); - if (m->reset_cpu_usage) { - (void) unit_reset_cpu_usage(UNIT(m)); - m->reset_cpu_usage = false; - } - - r = unit_setup_exec_runtime(UNIT(m)); - if (r < 0) - return r; - - r = mount_arm_timer(m, usec_add(now(CLOCK_MONOTONIC), m->timeout_usec)); - if (r < 0) - return r; - - exec_params.environment = UNIT(m)->manager->environment; - exec_params.confirm_spawn = UNIT(m)->manager->confirm_spawn; - exec_params.cgroup_supported = UNIT(m)->manager->cgroup_supported; - exec_params.cgroup_path = UNIT(m)->cgroup_path; - exec_params.cgroup_delegate = m->cgroup_context.delegate; - exec_params.runtime_prefix = manager_get_runtime_prefix(UNIT(m)->manager); - - r = exec_spawn(UNIT(m), - c, - &m->exec_context, - &exec_params, - m->exec_runtime, - &pid); - if (r < 0) - return r; - - r = unit_watch_pid(UNIT(m), pid); - if (r < 0) - /* FIXME: we need to do something here */ - return r; - - *_pid = pid; - - return 0; -} - -static void mount_enter_dead(Mount *m, MountResult f) { - assert(m); - - if (f != MOUNT_SUCCESS) - m->result = f; - - exec_runtime_destroy(m->exec_runtime); - m->exec_runtime = exec_runtime_unref(m->exec_runtime); - - exec_context_destroy_runtime_directory(&m->exec_context, manager_get_runtime_prefix(UNIT(m)->manager)); - - mount_set_state(m, m->result != MOUNT_SUCCESS ? MOUNT_FAILED : MOUNT_DEAD); -} - -static void mount_enter_mounted(Mount *m, MountResult f) { - assert(m); - - if (f != MOUNT_SUCCESS) - m->result = f; - - mount_set_state(m, MOUNT_MOUNTED); -} - -static void mount_enter_signal(Mount *m, MountState state, MountResult f) { - int r; - - assert(m); - - if (f != MOUNT_SUCCESS) - m->result = f; - - r = unit_kill_context( - UNIT(m), - &m->kill_context, - (state != MOUNT_MOUNTING_SIGTERM && state != MOUNT_UNMOUNTING_SIGTERM && state != MOUNT_REMOUNTING_SIGTERM) ? - KILL_KILL : KILL_TERMINATE, - -1, - m->control_pid, - false); - if (r < 0) - goto fail; - - if (r > 0) { - r = mount_arm_timer(m, usec_add(now(CLOCK_MONOTONIC), m->timeout_usec)); - if (r < 0) - goto fail; - - mount_set_state(m, state); - } else if (state == MOUNT_REMOUNTING_SIGTERM) - mount_enter_signal(m, MOUNT_REMOUNTING_SIGKILL, MOUNT_SUCCESS); - else if (state == MOUNT_REMOUNTING_SIGKILL) - mount_enter_mounted(m, MOUNT_SUCCESS); - else if (state == MOUNT_MOUNTING_SIGTERM) - mount_enter_signal(m, MOUNT_MOUNTING_SIGKILL, MOUNT_SUCCESS); - else if (state == MOUNT_UNMOUNTING_SIGTERM) - mount_enter_signal(m, MOUNT_UNMOUNTING_SIGKILL, MOUNT_SUCCESS); - else - mount_enter_dead(m, MOUNT_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(m), r, "Failed to kill processes: %m"); - - if (state == MOUNT_REMOUNTING_SIGTERM || state == MOUNT_REMOUNTING_SIGKILL) - mount_enter_mounted(m, MOUNT_FAILURE_RESOURCES); - else - mount_enter_dead(m, MOUNT_FAILURE_RESOURCES); -} - -static void mount_enter_unmounting(Mount *m) { - int r; - - assert(m); - - /* Start counting our attempts */ - if (!IN_SET(m->state, - MOUNT_UNMOUNTING, - MOUNT_UNMOUNTING_SIGTERM, - MOUNT_UNMOUNTING_SIGKILL)) - m->n_retry_umount = 0; - - m->control_command_id = MOUNT_EXEC_UNMOUNT; - m->control_command = m->exec_command + MOUNT_EXEC_UNMOUNT; - - r = exec_command_set(m->control_command, UMOUNT_PATH, m->where, NULL); - if (r < 0) - goto fail; - - mount_unwatch_control_pid(m); - - r = mount_spawn(m, m->control_command, &m->control_pid); - if (r < 0) - goto fail; - - mount_set_state(m, MOUNT_UNMOUNTING); - - return; - -fail: - log_unit_warning_errno(UNIT(m), r, "Failed to run 'umount' task: %m"); - mount_enter_mounted(m, MOUNT_FAILURE_RESOURCES); -} - -static int mount_get_opts(Mount *m, char **ret) { - return fstab_filter_options(m->parameters_fragment.options, - "nofail\0" "noauto\0" "auto\0", NULL, NULL, ret); -} - -static void mount_enter_mounting(Mount *m) { - int r; - MountParameters *p; - - assert(m); - - m->control_command_id = MOUNT_EXEC_MOUNT; - m->control_command = m->exec_command + MOUNT_EXEC_MOUNT; - - r = unit_fail_if_symlink(UNIT(m), m->where); - if (r < 0) - goto fail; - - (void) mkdir_p_label(m->where, m->directory_mode); - - unit_warn_if_dir_nonempty(UNIT(m), m->where); - - /* Create the source directory for bind-mounts if needed */ - p = get_mount_parameters_fragment(m); - if (p && mount_is_bind(p)) - (void) mkdir_p_label(p->what, m->directory_mode); - - if (m->from_fragment) { - _cleanup_free_ char *opts = NULL; - - r = mount_get_opts(m, &opts); - if (r < 0) - goto fail; - - r = exec_command_set(m->control_command, MOUNT_PATH, - m->parameters_fragment.what, m->where, NULL); - if (r >= 0 && m->sloppy_options) - r = exec_command_append(m->control_command, "-s", NULL); - if (r >= 0 && m->parameters_fragment.fstype) - r = exec_command_append(m->control_command, "-t", m->parameters_fragment.fstype, NULL); - if (r >= 0 && !isempty(opts)) - r = exec_command_append(m->control_command, "-o", opts, NULL); - } else - r = -ENOENT; - - if (r < 0) - goto fail; - - mount_unwatch_control_pid(m); - - r = mount_spawn(m, m->control_command, &m->control_pid); - if (r < 0) - goto fail; - - mount_set_state(m, MOUNT_MOUNTING); - - return; - -fail: - log_unit_warning_errno(UNIT(m), r, "Failed to run 'mount' task: %m"); - mount_enter_dead(m, MOUNT_FAILURE_RESOURCES); -} - -static void mount_enter_remounting(Mount *m) { - int r; - - assert(m); - - m->control_command_id = MOUNT_EXEC_REMOUNT; - m->control_command = m->exec_command + MOUNT_EXEC_REMOUNT; - - if (m->from_fragment) { - const char *o; - - if (m->parameters_fragment.options) - o = strjoina("remount,", m->parameters_fragment.options); - else - o = "remount"; - - r = exec_command_set(m->control_command, MOUNT_PATH, - m->parameters_fragment.what, m->where, - "-o", o, NULL); - if (r >= 0 && m->sloppy_options) - r = exec_command_append(m->control_command, "-s", NULL); - if (r >= 0 && m->parameters_fragment.fstype) - r = exec_command_append(m->control_command, "-t", m->parameters_fragment.fstype, NULL); - } else - r = -ENOENT; - - if (r < 0) - goto fail; - - mount_unwatch_control_pid(m); - - r = mount_spawn(m, m->control_command, &m->control_pid); - if (r < 0) - goto fail; - - mount_set_state(m, MOUNT_REMOUNTING); - - return; - -fail: - log_unit_warning_errno(UNIT(m), r, "Failed to run 'remount' task: %m"); - m->reload_result = MOUNT_FAILURE_RESOURCES; - mount_enter_mounted(m, MOUNT_SUCCESS); -} - -static int mount_start(Unit *u) { - Mount *m = MOUNT(u); - int r; - - assert(m); - - /* We cannot fulfill this request right now, try again later - * please! */ - if (m->state == MOUNT_UNMOUNTING || - m->state == MOUNT_UNMOUNTING_SIGTERM || - m->state == MOUNT_UNMOUNTING_SIGKILL || - m->state == MOUNT_MOUNTING_SIGTERM || - m->state == MOUNT_MOUNTING_SIGKILL) - return -EAGAIN; - - /* Already on it! */ - if (m->state == MOUNT_MOUNTING) - return 0; - - assert(m->state == MOUNT_DEAD || m->state == MOUNT_FAILED); - - r = unit_start_limit_test(u); - if (r < 0) { - mount_enter_dead(m, MOUNT_FAILURE_START_LIMIT_HIT); - return r; - } - - m->result = MOUNT_SUCCESS; - m->reload_result = MOUNT_SUCCESS; - m->reset_cpu_usage = true; - - mount_enter_mounting(m); - return 1; -} - -static int mount_stop(Unit *u) { - Mount *m = MOUNT(u); - - assert(m); - - /* Already on it */ - if (m->state == MOUNT_UNMOUNTING || - m->state == MOUNT_UNMOUNTING_SIGKILL || - m->state == MOUNT_UNMOUNTING_SIGTERM || - m->state == MOUNT_MOUNTING_SIGTERM || - m->state == MOUNT_MOUNTING_SIGKILL) - return 0; - - assert(m->state == MOUNT_MOUNTING || - m->state == MOUNT_MOUNTING_DONE || - m->state == MOUNT_MOUNTED || - m->state == MOUNT_REMOUNTING || - m->state == MOUNT_REMOUNTING_SIGTERM || - m->state == MOUNT_REMOUNTING_SIGKILL); - - mount_enter_unmounting(m); - return 1; -} - -static int mount_reload(Unit *u) { - Mount *m = MOUNT(u); - - assert(m); - - if (m->state == MOUNT_MOUNTING_DONE) - return -EAGAIN; - - assert(m->state == MOUNT_MOUNTED); - - mount_enter_remounting(m); - return 1; -} - -static int mount_serialize(Unit *u, FILE *f, FDSet *fds) { - Mount *m = MOUNT(u); - - assert(m); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", mount_state_to_string(m->state)); - unit_serialize_item(u, f, "result", mount_result_to_string(m->result)); - unit_serialize_item(u, f, "reload-result", mount_result_to_string(m->reload_result)); - - if (m->control_pid > 0) - unit_serialize_item_format(u, f, "control-pid", PID_FMT, m->control_pid); - - if (m->control_command_id >= 0) - unit_serialize_item(u, f, "control-command", mount_exec_command_to_string(m->control_command_id)); - - return 0; -} - -static int mount_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Mount *m = MOUNT(u); - - assert(u); - assert(key); - assert(value); - assert(fds); - - if (streq(key, "state")) { - MountState state; - - if ((state = mount_state_from_string(value)) < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - m->deserialized_state = state; - } else if (streq(key, "result")) { - MountResult f; - - f = mount_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse result value: %s", value); - else if (f != MOUNT_SUCCESS) - m->result = f; - - } else if (streq(key, "reload-result")) { - MountResult f; - - f = mount_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse reload result value: %s", value); - else if (f != MOUNT_SUCCESS) - m->reload_result = f; - - } else if (streq(key, "control-pid")) { - pid_t pid; - - if (parse_pid(value, &pid) < 0) - log_unit_debug(u, "Failed to parse control-pid value: %s", value); - else - m->control_pid = pid; - } else if (streq(key, "control-command")) { - MountExecCommand id; - - id = mount_exec_command_from_string(value); - if (id < 0) - log_unit_debug(u, "Failed to parse exec-command value: %s", value); - else { - m->control_command_id = id; - m->control_command = m->exec_command + id; - } - } else - log_unit_debug(u, "Unknown serialization key: %s", key); - - return 0; -} - -_pure_ static UnitActiveState mount_active_state(Unit *u) { - assert(u); - - return state_translation_table[MOUNT(u)->state]; -} - -_pure_ static const char *mount_sub_state_to_string(Unit *u) { - assert(u); - - return mount_state_to_string(MOUNT(u)->state); -} - -_pure_ static bool mount_check_gc(Unit *u) { - Mount *m = MOUNT(u); - - assert(m); - - return m->from_proc_self_mountinfo; -} - -static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { - Mount *m = MOUNT(u); - MountResult f; - - assert(m); - assert(pid >= 0); - - if (pid != m->control_pid) - return; - - m->control_pid = 0; - - if (is_clean_exit(code, status, NULL)) - f = MOUNT_SUCCESS; - else if (code == CLD_EXITED) - f = MOUNT_FAILURE_EXIT_CODE; - else if (code == CLD_KILLED) - f = MOUNT_FAILURE_SIGNAL; - else if (code == CLD_DUMPED) - f = MOUNT_FAILURE_CORE_DUMP; - else - assert_not_reached("Unknown code"); - - if (f != MOUNT_SUCCESS) - m->result = f; - - if (m->control_command) { - exec_status_exit(&m->control_command->exec_status, &m->exec_context, pid, code, status); - - m->control_command = NULL; - m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; - } - - log_unit_full(u, f == MOUNT_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, - "Mount process exited, code=%s status=%i", sigchld_code_to_string(code), status); - - /* Note that mount(8) returning and the kernel sending us a - * mount table change event might happen out-of-order. If an - * operation succeed we assume the kernel will follow soon too - * and already change into the resulting state. If it fails - * we check if the kernel still knows about the mount. and - * change state accordingly. */ - - switch (m->state) { - - case MOUNT_MOUNTING: - case MOUNT_MOUNTING_DONE: - case MOUNT_MOUNTING_SIGKILL: - case MOUNT_MOUNTING_SIGTERM: - - if (f == MOUNT_SUCCESS) - mount_enter_mounted(m, f); - else if (m->from_proc_self_mountinfo) - mount_enter_mounted(m, f); - else - mount_enter_dead(m, f); - break; - - case MOUNT_REMOUNTING: - case MOUNT_REMOUNTING_SIGKILL: - case MOUNT_REMOUNTING_SIGTERM: - - m->reload_result = f; - if (m->from_proc_self_mountinfo) - mount_enter_mounted(m, MOUNT_SUCCESS); - else - mount_enter_dead(m, MOUNT_SUCCESS); - - break; - - case MOUNT_UNMOUNTING: - case MOUNT_UNMOUNTING_SIGKILL: - case MOUNT_UNMOUNTING_SIGTERM: - - if (f == MOUNT_SUCCESS) { - - if (m->from_proc_self_mountinfo) { - - /* Still a mount point? If so, let's - * try again. Most likely there were - * multiple mount points stacked on - * top of each other. Note that due to - * the io event priority logic we can - * be sure the new mountinfo is loaded - * before we process the SIGCHLD for - * the mount command. */ - - if (m->n_retry_umount < RETRY_UMOUNT_MAX) { - log_unit_debug(u, "Mount still present, trying again."); - m->n_retry_umount++; - mount_enter_unmounting(m); - } else { - log_unit_debug(u, "Mount still present after %u attempts to unmount, giving up.", m->n_retry_umount); - mount_enter_mounted(m, f); - } - } else - mount_enter_dead(m, f); - - } else if (m->from_proc_self_mountinfo) - mount_enter_mounted(m, f); - else - mount_enter_dead(m, f); - break; - - default: - assert_not_reached("Uh, control process died at wrong time."); - } - - /* Notify clients about changed exit status */ - unit_add_to_dbus_queue(u); -} - -static int mount_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { - Mount *m = MOUNT(userdata); - - assert(m); - assert(m->timer_event_source == source); - - switch (m->state) { - - case MOUNT_MOUNTING: - case MOUNT_MOUNTING_DONE: - log_unit_warning(UNIT(m), "Mounting timed out. Stopping."); - mount_enter_signal(m, MOUNT_MOUNTING_SIGTERM, MOUNT_FAILURE_TIMEOUT); - break; - - case MOUNT_REMOUNTING: - log_unit_warning(UNIT(m), "Remounting timed out. Stopping."); - m->reload_result = MOUNT_FAILURE_TIMEOUT; - mount_enter_mounted(m, MOUNT_SUCCESS); - break; - - case MOUNT_UNMOUNTING: - log_unit_warning(UNIT(m), "Unmounting timed out. Stopping."); - mount_enter_signal(m, MOUNT_UNMOUNTING_SIGTERM, MOUNT_FAILURE_TIMEOUT); - break; - - case MOUNT_MOUNTING_SIGTERM: - if (m->kill_context.send_sigkill) { - log_unit_warning(UNIT(m), "Mounting timed out. Killing."); - mount_enter_signal(m, MOUNT_MOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(m), "Mounting timed out. Skipping SIGKILL. Ignoring."); - - if (m->from_proc_self_mountinfo) - mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); - else - mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); - } - break; - - case MOUNT_REMOUNTING_SIGTERM: - if (m->kill_context.send_sigkill) { - log_unit_warning(UNIT(m), "Remounting timed out. Killing."); - mount_enter_signal(m, MOUNT_REMOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(m), "Remounting timed out. Skipping SIGKILL. Ignoring."); - - if (m->from_proc_self_mountinfo) - mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); - else - mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); - } - break; - - case MOUNT_UNMOUNTING_SIGTERM: - if (m->kill_context.send_sigkill) { - log_unit_warning(UNIT(m), "Unmounting timed out. Killing."); - mount_enter_signal(m, MOUNT_UNMOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(m), "Unmounting timed out. Skipping SIGKILL. Ignoring."); - - if (m->from_proc_self_mountinfo) - mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); - else - mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); - } - break; - - case MOUNT_MOUNTING_SIGKILL: - case MOUNT_REMOUNTING_SIGKILL: - case MOUNT_UNMOUNTING_SIGKILL: - log_unit_warning(UNIT(m),"Mount process still around after SIGKILL. Ignoring."); - - if (m->from_proc_self_mountinfo) - mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); - else - mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); - break; - - default: - assert_not_reached("Timeout at wrong time."); - } - - return 0; -} - -static int mount_setup_unit( - Manager *m, - const char *what, - const char *where, - const char *options, - const char *fstype, - bool set_flags) { - - _cleanup_free_ char *e = NULL, *w = NULL, *o = NULL, *f = NULL; - bool load_extras = false; - MountParameters *p; - bool delete, changed = false; - Unit *u; - int r; - - assert(m); - assert(what); - assert(where); - assert(options); - assert(fstype); - - /* Ignore API mount points. They should never be referenced in - * dependencies ever. */ - if (mount_point_is_api(where) || mount_point_ignore(where)) - return 0; - - if (streq(fstype, "autofs")) - return 0; - - /* probably some kind of swap, ignore */ - if (!is_path(where)) - return 0; - - r = unit_name_from_path(where, ".mount", &e); - if (r < 0) - return r; - - u = manager_get_unit(m, e); - if (!u) { - delete = true; - - u = unit_new(m, sizeof(Mount)); - if (!u) - return log_oom(); - - r = unit_add_name(u, e); - if (r < 0) - goto fail; - - MOUNT(u)->where = strdup(where); - if (!MOUNT(u)->where) { - r = -ENOMEM; - goto fail; - } - - u->source_path = strdup("/proc/self/mountinfo"); - if (!u->source_path) { - r = -ENOMEM; - goto fail; - } - - if (MANAGER_IS_SYSTEM(m)) { - const char* target; - - target = mount_needs_network(options, fstype) ? SPECIAL_REMOTE_FS_TARGET : SPECIAL_LOCAL_FS_TARGET; - r = unit_add_dependency_by_name(u, UNIT_BEFORE, target, NULL, true); - if (r < 0) - goto fail; - - if (should_umount(MOUNT(u))) { - r = unit_add_dependency_by_name(u, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); - if (r < 0) - goto fail; - } - } - - unit_add_to_load_queue(u); - changed = true; - } else { - delete = false; - - if (!MOUNT(u)->where) { - MOUNT(u)->where = strdup(where); - if (!MOUNT(u)->where) { - r = -ENOMEM; - goto fail; - } - } - - if (MANAGER_IS_SYSTEM(m) && - mount_needs_network(options, fstype)) { - /* _netdev option may have shown up late, or on a - * remount. Add remote-fs dependencies, even though - * local-fs ones may already be there. */ - unit_add_dependency_by_name(u, UNIT_BEFORE, SPECIAL_REMOTE_FS_TARGET, NULL, true); - load_extras = true; - } - - if (u->load_state == UNIT_NOT_FOUND) { - u->load_state = UNIT_LOADED; - u->load_error = 0; - - /* Load in the extras later on, after we - * finished initialization of the unit */ - load_extras = true; - changed = true; - } - } - - w = strdup(what); - o = strdup(options); - f = strdup(fstype); - if (!w || !o || !f) { - r = -ENOMEM; - goto fail; - } - - p = &MOUNT(u)->parameters_proc_self_mountinfo; - - changed = changed || - !streq_ptr(p->options, options) || - !streq_ptr(p->what, what) || - !streq_ptr(p->fstype, fstype); - - if (set_flags) { - MOUNT(u)->is_mounted = true; - MOUNT(u)->just_mounted = !MOUNT(u)->from_proc_self_mountinfo; - MOUNT(u)->just_changed = changed; - } - - MOUNT(u)->from_proc_self_mountinfo = true; - - free(p->what); - p->what = w; - w = NULL; - - free(p->options); - p->options = o; - o = NULL; - - free(p->fstype); - p->fstype = f; - f = NULL; - - if (load_extras) { - r = mount_add_extras(MOUNT(u)); - if (r < 0) - goto fail; - } - - if (changed) - unit_add_to_dbus_queue(u); - - return 0; - -fail: - log_warning_errno(r, "Failed to set up mount unit: %m"); - - if (delete && u) - unit_free(u); - - return r; -} - -static int mount_load_proc_self_mountinfo(Manager *m, bool set_flags) { - _cleanup_(mnt_free_tablep) struct libmnt_table *t = NULL; - _cleanup_(mnt_free_iterp) struct libmnt_iter *i = NULL; - int r = 0; - - assert(m); - - t = mnt_new_table(); - if (!t) - return log_oom(); - - i = mnt_new_iter(MNT_ITER_FORWARD); - if (!i) - return log_oom(); - - r = mnt_table_parse_mtab(t, NULL); - if (r < 0) - return log_error_errno(r, "Failed to parse /proc/self/mountinfo: %m"); - - r = 0; - for (;;) { - const char *device, *path, *options, *fstype; - _cleanup_free_ char *d = NULL, *p = NULL; - struct libmnt_fs *fs; - int k; - - k = mnt_table_next_fs(t, i, &fs); - if (k == 1) - break; - if (k < 0) - return log_error_errno(k, "Failed to get next entry from /proc/self/mountinfo: %m"); - - device = mnt_fs_get_source(fs); - path = mnt_fs_get_target(fs); - options = mnt_fs_get_options(fs); - fstype = mnt_fs_get_fstype(fs); - - if (!device || !path) - continue; - - if (cunescape(device, UNESCAPE_RELAX, &d) < 0) - return log_oom(); - - if (cunescape(path, UNESCAPE_RELAX, &p) < 0) - return log_oom(); - - (void) device_found_node(m, d, true, DEVICE_FOUND_MOUNT, set_flags); - - k = mount_setup_unit(m, d, p, options, fstype, set_flags); - if (r == 0 && k < 0) - r = k; - } - - return r; -} - -static void mount_shutdown(Manager *m) { - - assert(m); - - m->mount_event_source = sd_event_source_unref(m->mount_event_source); - - mnt_unref_monitor(m->mount_monitor); - m->mount_monitor = NULL; -} - -static int mount_get_timeout(Unit *u, usec_t *timeout) { - Mount *m = MOUNT(u); - usec_t t; - int r; - - if (!m->timer_event_source) - return 0; - - r = sd_event_source_get_time(m->timer_event_source, &t); - if (r < 0) - return r; - if (t == USEC_INFINITY) - return 0; - - *timeout = t; - return 1; -} - -static void mount_enumerate(Manager *m) { - int r; - - assert(m); - - mnt_init_debug(0); - - if (!m->mount_monitor) { - int fd; - - m->mount_monitor = mnt_new_monitor(); - if (!m->mount_monitor) { - log_oom(); - goto fail; - } - - r = mnt_monitor_enable_kernel(m->mount_monitor, 1); - if (r < 0) { - log_error_errno(r, "Failed to enable watching of kernel mount events: %m"); - goto fail; - } - - r = mnt_monitor_enable_userspace(m->mount_monitor, 1, NULL); - if (r < 0) { - log_error_errno(r, "Failed to enable watching of userspace mount events: %m"); - goto fail; - } - - /* mnt_unref_monitor() will close the fd */ - fd = r = mnt_monitor_get_fd(m->mount_monitor); - if (r < 0) { - log_error_errno(r, "Failed to acquire watch file descriptor: %m"); - goto fail; - } - - r = sd_event_add_io(m->event, &m->mount_event_source, fd, EPOLLIN, mount_dispatch_io, m); - if (r < 0) { - log_error_errno(r, "Failed to watch mount file descriptor: %m"); - goto fail; - } - - r = sd_event_source_set_priority(m->mount_event_source, -10); - if (r < 0) { - log_error_errno(r, "Failed to adjust mount watch priority: %m"); - goto fail; - } - - (void) sd_event_source_set_description(m->mount_event_source, "mount-monitor-dispatch"); - } - - r = mount_load_proc_self_mountinfo(m, false); - if (r < 0) - goto fail; - - return; - -fail: - mount_shutdown(m); -} - -static int mount_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - _cleanup_set_free_ Set *around = NULL, *gone = NULL; - Manager *m = userdata; - const char *what; - Iterator i; - Unit *u; - int r; - - assert(m); - assert(revents & EPOLLIN); - - if (fd == mnt_monitor_get_fd(m->mount_monitor)) { - bool rescan = false; - - /* Drain all events and verify that the event is valid. - * - * Note that libmount also monitors /run/mount mkdir if the - * directory does not exist yet. The mkdir may generate event - * which is irrelevant for us. - * - * error: r < 0; valid: r == 0, false positive: rc == 1 */ - do { - r = mnt_monitor_next_change(m->mount_monitor, NULL, NULL); - if (r == 0) - rescan = true; - else if (r < 0) - return log_error_errno(r, "Failed to drain libmount events"); - } while (r == 0); - - log_debug("libmount event [rescan: %s]", yes_no(rescan)); - if (!rescan) - return 0; - } - - r = mount_load_proc_self_mountinfo(m, true); - if (r < 0) { - /* Reset flags, just in case, for later calls */ - LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_MOUNT]) { - Mount *mount = MOUNT(u); - - mount->is_mounted = mount->just_mounted = mount->just_changed = false; - } - - return 0; - } - - manager_dispatch_load_queue(m); - - LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_MOUNT]) { - Mount *mount = MOUNT(u); - - if (!mount->is_mounted) { - - /* A mount point is not around right now. It - * might be gone, or might never have - * existed. */ - - if (mount->from_proc_self_mountinfo && - mount->parameters_proc_self_mountinfo.what) { - - /* Remember that this device might just have disappeared */ - if (set_ensure_allocated(&gone, &string_hash_ops) < 0 || - set_put(gone, mount->parameters_proc_self_mountinfo.what) < 0) - log_oom(); /* we don't care too much about OOM here... */ - } - - mount->from_proc_self_mountinfo = false; - - switch (mount->state) { - - case MOUNT_MOUNTED: - /* This has just been unmounted by - * somebody else, follow the state - * change. */ - mount_enter_dead(mount, MOUNT_SUCCESS); - break; - - default: - break; - } - - } else if (mount->just_mounted || mount->just_changed) { - - /* A mount point was added or changed */ - - switch (mount->state) { - - case MOUNT_DEAD: - case MOUNT_FAILED: - /* This has just been mounted by - * somebody else, follow the state - * change. */ - mount_enter_mounted(mount, MOUNT_SUCCESS); - break; - - case MOUNT_MOUNTING: - mount_set_state(mount, MOUNT_MOUNTING_DONE); - break; - - default: - /* Nothing really changed, but let's - * issue an notification call - * nonetheless, in case somebody is - * waiting for this. (e.g. file system - * ro/rw remounts.) */ - mount_set_state(mount, mount->state); - break; - } - } - - if (mount->is_mounted && - mount->from_proc_self_mountinfo && - mount->parameters_proc_self_mountinfo.what) { - - if (set_ensure_allocated(&around, &string_hash_ops) < 0 || - set_put(around, mount->parameters_proc_self_mountinfo.what) < 0) - log_oom(); - } - - /* Reset the flags for later calls */ - mount->is_mounted = mount->just_mounted = mount->just_changed = false; - } - - SET_FOREACH(what, gone, i) { - if (set_contains(around, what)) - continue; - - /* Let the device units know that the device is no longer mounted */ - (void) device_found_node(m, what, false, DEVICE_FOUND_MOUNT, true); - } - - return 0; -} - -static void mount_reset_failed(Unit *u) { - Mount *m = MOUNT(u); - - assert(m); - - if (m->state == MOUNT_FAILED) - mount_set_state(m, MOUNT_DEAD); - - m->result = MOUNT_SUCCESS; - m->reload_result = MOUNT_SUCCESS; -} - -static int mount_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { - return unit_kill_common(u, who, signo, -1, MOUNT(u)->control_pid, error); -} - -static int mount_control_pid(Unit *u) { - Mount *m = MOUNT(u); - - assert(m); - - return m->control_pid; -} - -static const char* const mount_exec_command_table[_MOUNT_EXEC_COMMAND_MAX] = { - [MOUNT_EXEC_MOUNT] = "ExecMount", - [MOUNT_EXEC_UNMOUNT] = "ExecUnmount", - [MOUNT_EXEC_REMOUNT] = "ExecRemount", -}; - -DEFINE_STRING_TABLE_LOOKUP(mount_exec_command, MountExecCommand); - -static const char* const mount_result_table[_MOUNT_RESULT_MAX] = { - [MOUNT_SUCCESS] = "success", - [MOUNT_FAILURE_RESOURCES] = "resources", - [MOUNT_FAILURE_TIMEOUT] = "timeout", - [MOUNT_FAILURE_EXIT_CODE] = "exit-code", - [MOUNT_FAILURE_SIGNAL] = "signal", - [MOUNT_FAILURE_CORE_DUMP] = "core-dump", - [MOUNT_FAILURE_START_LIMIT_HIT] = "start-limit-hit", -}; - -DEFINE_STRING_TABLE_LOOKUP(mount_result, MountResult); - -const UnitVTable mount_vtable = { - .object_size = sizeof(Mount), - .exec_context_offset = offsetof(Mount, exec_context), - .cgroup_context_offset = offsetof(Mount, cgroup_context), - .kill_context_offset = offsetof(Mount, kill_context), - .exec_runtime_offset = offsetof(Mount, exec_runtime), - - .sections = - "Unit\0" - "Mount\0" - "Install\0", - .private_section = "Mount", - - .init = mount_init, - .load = mount_load, - .done = mount_done, - - .coldplug = mount_coldplug, - - .dump = mount_dump, - - .start = mount_start, - .stop = mount_stop, - .reload = mount_reload, - - .kill = mount_kill, - - .serialize = mount_serialize, - .deserialize_item = mount_deserialize_item, - - .active_state = mount_active_state, - .sub_state_to_string = mount_sub_state_to_string, - - .check_gc = mount_check_gc, - - .sigchld_event = mount_sigchld_event, - - .reset_failed = mount_reset_failed, - - .control_pid = mount_control_pid, - - .bus_vtable = bus_mount_vtable, - .bus_set_property = bus_mount_set_property, - .bus_commit_properties = bus_mount_commit_properties, - - .get_timeout = mount_get_timeout, - - .can_transient = true, - - .enumerate = mount_enumerate, - .shutdown = mount_shutdown, - - .status_message_formats = { - .starting_stopping = { - [0] = "Mounting %s...", - [1] = "Unmounting %s...", - }, - .finished_start_job = { - [JOB_DONE] = "Mounted %s.", - [JOB_FAILED] = "Failed to mount %s.", - [JOB_TIMEOUT] = "Timed out mounting %s.", - }, - .finished_stop_job = { - [JOB_DONE] = "Unmounted %s.", - [JOB_FAILED] = "Failed unmounting %s.", - [JOB_TIMEOUT] = "Timed out unmounting %s.", - }, - }, -}; diff --git a/src/core/mount.h b/src/core/mount.h deleted file mode 100644 index da529c44f4..0000000000 --- a/src/core/mount.h +++ /dev/null @@ -1,108 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct Mount Mount; - -#include "execute.h" -#include "kill.h" - -typedef enum MountExecCommand { - MOUNT_EXEC_MOUNT, - MOUNT_EXEC_UNMOUNT, - MOUNT_EXEC_REMOUNT, - _MOUNT_EXEC_COMMAND_MAX, - _MOUNT_EXEC_COMMAND_INVALID = -1 -} MountExecCommand; - -typedef enum MountResult { - MOUNT_SUCCESS, - MOUNT_FAILURE_RESOURCES, - MOUNT_FAILURE_TIMEOUT, - MOUNT_FAILURE_EXIT_CODE, - MOUNT_FAILURE_SIGNAL, - MOUNT_FAILURE_CORE_DUMP, - MOUNT_FAILURE_START_LIMIT_HIT, - _MOUNT_RESULT_MAX, - _MOUNT_RESULT_INVALID = -1 -} MountResult; - -typedef struct MountParameters { - char *what; - char *options; - char *fstype; -} MountParameters; - -struct Mount { - Unit meta; - - char *where; - - MountParameters parameters_proc_self_mountinfo; - MountParameters parameters_fragment; - - bool from_proc_self_mountinfo:1; - bool from_fragment:1; - - /* Used while looking for mount points that vanished or got - * added from/to /proc/self/mountinfo */ - bool is_mounted:1; - bool just_mounted:1; - bool just_changed:1; - - bool reset_cpu_usage:1; - - bool sloppy_options; - - MountResult result; - MountResult reload_result; - - mode_t directory_mode; - - usec_t timeout_usec; - - ExecCommand exec_command[_MOUNT_EXEC_COMMAND_MAX]; - - ExecContext exec_context; - KillContext kill_context; - CGroupContext cgroup_context; - - ExecRuntime *exec_runtime; - - MountState state, deserialized_state; - - ExecCommand* control_command; - MountExecCommand control_command_id; - pid_t control_pid; - - sd_event_source *timer_event_source; - - unsigned n_retry_umount; -}; - -extern const UnitVTable mount_vtable; - -void mount_fd_event(Manager *m, int events); - -const char* mount_exec_command_to_string(MountExecCommand i) _const_; -MountExecCommand mount_exec_command_from_string(const char *s) _pure_; - -const char* mount_result_to_string(MountResult i) _const_; -MountResult mount_result_from_string(const char *s) _pure_; diff --git a/src/core/namespace.c b/src/core/namespace.c deleted file mode 100644 index 203d122810..0000000000 --- a/src/core/namespace.c +++ /dev/null @@ -1,650 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "dev-setup.h" -#include "fd-util.h" -#include "loopback-setup.h" -#include "missing.h" -#include "mkdir.h" -#include "mount-util.h" -#include "namespace.h" -#include "path-util.h" -#include "selinux-util.h" -#include "socket-util.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "umask-util.h" -#include "user-util.h" -#include "util.h" - -#define DEV_MOUNT_OPTIONS (MS_NOSUID|MS_STRICTATIME|MS_NOEXEC) - -typedef enum MountMode { - /* This is ordered by priority! */ - INACCESSIBLE, - READONLY, - PRIVATE_TMP, - PRIVATE_VAR_TMP, - PRIVATE_DEV, - READWRITE -} MountMode; - -typedef struct BindMount { - const char *path; - MountMode mode; - bool done; - bool ignore; -} BindMount; - -static int append_mounts(BindMount **p, char **strv, MountMode mode) { - char **i; - - assert(p); - - STRV_FOREACH(i, strv) { - - (*p)->ignore = false; - (*p)->done = false; - - if ((mode == INACCESSIBLE || mode == READONLY || mode == READWRITE) && (*i)[0] == '-') { - (*p)->ignore = true; - (*i)++; - } - - if (!path_is_absolute(*i)) - return -EINVAL; - - (*p)->path = *i; - (*p)->mode = mode; - (*p)++; - } - - return 0; -} - -static int mount_path_compare(const void *a, const void *b) { - const BindMount *p = a, *q = b; - int d; - - d = path_compare(p->path, q->path); - - if (d == 0) { - /* If the paths are equal, check the mode */ - if (p->mode < q->mode) - return -1; - - if (p->mode > q->mode) - return 1; - - return 0; - } - - /* If the paths are not equal, then order prefixes first */ - return d; -} - -static void drop_duplicates(BindMount *m, unsigned *n) { - BindMount *f, *t, *previous; - - assert(m); - assert(n); - - for (f = m, t = m, previous = NULL; f < m+*n; f++) { - - /* The first one wins */ - if (previous && path_equal(f->path, previous->path)) - continue; - - *t = *f; - - previous = t; - - t++; - } - - *n = t - m; -} - -static int mount_dev(BindMount *m) { - static const char devnodes[] = - "/dev/null\0" - "/dev/zero\0" - "/dev/full\0" - "/dev/random\0" - "/dev/urandom\0" - "/dev/tty\0"; - - char temporary_mount[] = "/tmp/namespace-dev-XXXXXX"; - const char *d, *dev = NULL, *devpts = NULL, *devshm = NULL, *devhugepages = NULL, *devmqueue = NULL, *devlog = NULL, *devptmx = NULL; - _cleanup_umask_ mode_t u; - int r; - - assert(m); - - u = umask(0000); - - if (!mkdtemp(temporary_mount)) - return -errno; - - dev = strjoina(temporary_mount, "/dev"); - (void) mkdir(dev, 0755); - if (mount("tmpfs", dev, "tmpfs", DEV_MOUNT_OPTIONS, "mode=755") < 0) { - r = -errno; - goto fail; - } - - devpts = strjoina(temporary_mount, "/dev/pts"); - (void) mkdir(devpts, 0755); - if (mount("/dev/pts", devpts, NULL, MS_BIND, NULL) < 0) { - r = -errno; - goto fail; - } - - devptmx = strjoina(temporary_mount, "/dev/ptmx"); - if (symlink("pts/ptmx", devptmx) < 0) { - r = -errno; - goto fail; - } - - devshm = strjoina(temporary_mount, "/dev/shm"); - (void) mkdir(devshm, 01777); - r = mount("/dev/shm", devshm, NULL, MS_BIND, NULL); - if (r < 0) { - r = -errno; - goto fail; - } - - devmqueue = strjoina(temporary_mount, "/dev/mqueue"); - (void) mkdir(devmqueue, 0755); - (void) mount("/dev/mqueue", devmqueue, NULL, MS_BIND, NULL); - - devhugepages = strjoina(temporary_mount, "/dev/hugepages"); - (void) mkdir(devhugepages, 0755); - (void) mount("/dev/hugepages", devhugepages, NULL, MS_BIND, NULL); - - devlog = strjoina(temporary_mount, "/dev/log"); - (void) symlink("/run/systemd/journal/dev-log", devlog); - - NULSTR_FOREACH(d, devnodes) { - _cleanup_free_ char *dn = NULL; - struct stat st; - - r = stat(d, &st); - if (r < 0) { - - if (errno == ENOENT) - continue; - - r = -errno; - goto fail; - } - - if (!S_ISBLK(st.st_mode) && - !S_ISCHR(st.st_mode)) { - r = -EINVAL; - goto fail; - } - - if (st.st_rdev == 0) - continue; - - dn = strappend(temporary_mount, d); - if (!dn) { - r = -ENOMEM; - goto fail; - } - - mac_selinux_create_file_prepare(d, st.st_mode); - r = mknod(dn, st.st_mode, st.st_rdev); - mac_selinux_create_file_clear(); - - if (r < 0) { - r = -errno; - goto fail; - } - } - - dev_setup(temporary_mount, UID_INVALID, GID_INVALID); - - /* Create the /dev directory if missing. It is more likely to be - * missing when the service is started with RootDirectory. This is - * consistent with mount units creating the mount points when missing. - */ - (void) mkdir_p_label(m->path, 0755); - - /* Unmount everything in old /dev */ - umount_recursive(m->path, 0); - if (mount(dev, m->path, NULL, MS_MOVE, NULL) < 0) { - r = -errno; - goto fail; - } - - rmdir(dev); - rmdir(temporary_mount); - - return 0; - -fail: - if (devpts) - umount(devpts); - - if (devshm) - umount(devshm); - - if (devhugepages) - umount(devhugepages); - - if (devmqueue) - umount(devmqueue); - - umount(dev); - rmdir(dev); - rmdir(temporary_mount); - - return r; -} - -static int apply_mount( - BindMount *m, - const char *tmp_dir, - const char *var_tmp_dir) { - - const char *what; - int r; - - assert(m); - - switch (m->mode) { - - case INACCESSIBLE: - - /* First, get rid of everything that is below if there - * is anything... Then, overmount it with an - * inaccessible directory. */ - umount_recursive(m->path, 0); - - what = "/run/systemd/inaccessible"; - break; - - case READONLY: - case READWRITE: - /* Nothing to mount here, we just later toggle the - * MS_RDONLY bit for the mount point */ - return 0; - - case PRIVATE_TMP: - what = tmp_dir; - break; - - case PRIVATE_VAR_TMP: - what = var_tmp_dir; - break; - - case PRIVATE_DEV: - return mount_dev(m); - - default: - assert_not_reached("Unknown mode"); - } - - assert(what); - - r = mount(what, m->path, NULL, MS_BIND|MS_REC, NULL); - if (r >= 0) - log_debug("Successfully mounted %s to %s", what, m->path); - else if (m->ignore && errno == ENOENT) - return 0; - - return r; -} - -static int make_read_only(BindMount *m) { - int r; - - assert(m); - - if (IN_SET(m->mode, INACCESSIBLE, READONLY)) - r = bind_remount_recursive(m->path, true); - else if (IN_SET(m->mode, READWRITE, PRIVATE_TMP, PRIVATE_VAR_TMP, PRIVATE_DEV)) { - r = bind_remount_recursive(m->path, false); - if (r == 0 && m->mode == PRIVATE_DEV) /* can be readonly but the submounts can't*/ - r = mount(NULL, m->path, NULL, MS_REMOUNT|DEV_MOUNT_OPTIONS|MS_RDONLY, NULL); - } else - r = 0; - - if (m->ignore && r == -ENOENT) - return 0; - - return r; -} - -int setup_namespace( - const char* root_directory, - char** read_write_dirs, - char** read_only_dirs, - char** inaccessible_dirs, - const char* tmp_dir, - const char* var_tmp_dir, - bool private_dev, - ProtectHome protect_home, - ProtectSystem protect_system, - unsigned long mount_flags) { - - BindMount *m, *mounts = NULL; - unsigned n; - int r = 0; - - if (mount_flags == 0) - mount_flags = MS_SHARED; - - if (unshare(CLONE_NEWNS) < 0) - return -errno; - - n = !!tmp_dir + !!var_tmp_dir + - strv_length(read_write_dirs) + - strv_length(read_only_dirs) + - strv_length(inaccessible_dirs) + - private_dev + - (protect_home != PROTECT_HOME_NO ? 3 : 0) + - (protect_system != PROTECT_SYSTEM_NO ? 2 : 0) + - (protect_system == PROTECT_SYSTEM_FULL ? 1 : 0); - - if (n > 0) { - m = mounts = (BindMount *) alloca0(n * sizeof(BindMount)); - r = append_mounts(&m, read_write_dirs, READWRITE); - if (r < 0) - return r; - - r = append_mounts(&m, read_only_dirs, READONLY); - if (r < 0) - return r; - - r = append_mounts(&m, inaccessible_dirs, INACCESSIBLE); - if (r < 0) - return r; - - if (tmp_dir) { - m->path = prefix_roota(root_directory, "/tmp"); - m->mode = PRIVATE_TMP; - m++; - } - - if (var_tmp_dir) { - m->path = prefix_roota(root_directory, "/var/tmp"); - m->mode = PRIVATE_VAR_TMP; - m++; - } - - if (private_dev) { - m->path = prefix_roota(root_directory, "/dev"); - m->mode = PRIVATE_DEV; - m++; - } - - if (protect_home != PROTECT_HOME_NO) { - const char *home_dir, *run_user_dir, *root_dir; - - home_dir = prefix_roota(root_directory, "/home"); - home_dir = strjoina("-", home_dir); - run_user_dir = prefix_roota(root_directory, "/run/user"); - run_user_dir = strjoina("-", run_user_dir); - root_dir = prefix_roota(root_directory, "/root"); - root_dir = strjoina("-", root_dir); - - r = append_mounts(&m, STRV_MAKE(home_dir, run_user_dir, root_dir), - protect_home == PROTECT_HOME_READ_ONLY ? READONLY : INACCESSIBLE); - if (r < 0) - return r; - } - - if (protect_system != PROTECT_SYSTEM_NO) { - const char *usr_dir, *boot_dir, *etc_dir; - - usr_dir = prefix_roota(root_directory, "/usr"); - boot_dir = prefix_roota(root_directory, "/boot"); - boot_dir = strjoina("-", boot_dir); - etc_dir = prefix_roota(root_directory, "/etc"); - - r = append_mounts(&m, protect_system == PROTECT_SYSTEM_FULL - ? STRV_MAKE(usr_dir, boot_dir, etc_dir) - : STRV_MAKE(usr_dir, boot_dir), READONLY); - if (r < 0) - return r; - } - - assert(mounts + n == m); - - qsort(mounts, n, sizeof(BindMount), mount_path_compare); - drop_duplicates(mounts, &n); - } - - if (n > 0 || root_directory) { - /* Remount / as SLAVE so that nothing now mounted in the namespace - shows up in the parent */ - if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) - return -errno; - } - - if (root_directory) { - /* Turn directory into bind mount */ - if (mount(root_directory, root_directory, NULL, MS_BIND|MS_REC, NULL) < 0) - return -errno; - } - - if (n > 0) { - for (m = mounts; m < mounts + n; ++m) { - r = apply_mount(m, tmp_dir, var_tmp_dir); - if (r < 0) - goto fail; - } - - for (m = mounts; m < mounts + n; ++m) { - r = make_read_only(m); - if (r < 0) - goto fail; - } - } - - if (root_directory) { - /* MS_MOVE does not work on MS_SHARED so the remount MS_SHARED will be done later */ - r = mount_move_root(root_directory); - - /* at this point, we cannot rollback */ - if (r < 0) - return r; - } - - /* Remount / as the desired mode. Not that this will not - * reestablish propagation from our side to the host, since - * what's disconnected is disconnected. */ - if (mount(NULL, "/", NULL, mount_flags | MS_REC, NULL) < 0) - /* at this point, we cannot rollback */ - return -errno; - - return 0; - -fail: - if (n > 0) { - for (m = mounts; m < mounts + n; ++m) - if (m->done) - (void) umount2(m->path, MNT_DETACH); - } - - return r; -} - -static int setup_one_tmp_dir(const char *id, const char *prefix, char **path) { - _cleanup_free_ char *x = NULL; - char bid[SD_ID128_STRING_MAX]; - sd_id128_t boot_id; - int r; - - assert(id); - assert(prefix); - assert(path); - - /* We include the boot id in the directory so that after a - * reboot we can easily identify obsolete directories. */ - - r = sd_id128_get_boot(&boot_id); - if (r < 0) - return r; - - x = strjoin(prefix, "/systemd-private-", sd_id128_to_string(boot_id, bid), "-", id, "-XXXXXX", NULL); - if (!x) - return -ENOMEM; - - RUN_WITH_UMASK(0077) - if (!mkdtemp(x)) - return -errno; - - RUN_WITH_UMASK(0000) { - char *y; - - y = strjoina(x, "/tmp"); - - if (mkdir(y, 0777 | S_ISVTX) < 0) - return -errno; - } - - *path = x; - x = NULL; - - return 0; -} - -int setup_tmp_dirs(const char *id, char **tmp_dir, char **var_tmp_dir) { - char *a, *b; - int r; - - assert(id); - assert(tmp_dir); - assert(var_tmp_dir); - - r = setup_one_tmp_dir(id, "/tmp", &a); - if (r < 0) - return r; - - r = setup_one_tmp_dir(id, "/var/tmp", &b); - if (r < 0) { - char *t; - - t = strjoina(a, "/tmp"); - rmdir(t); - rmdir(a); - - free(a); - return r; - } - - *tmp_dir = a; - *var_tmp_dir = b; - - return 0; -} - -int setup_netns(int netns_storage_socket[2]) { - _cleanup_close_ int netns = -1; - int r, q; - - assert(netns_storage_socket); - assert(netns_storage_socket[0] >= 0); - assert(netns_storage_socket[1] >= 0); - - /* We use the passed socketpair as a storage buffer for our - * namespace reference fd. Whatever process runs this first - * shall create a new namespace, all others should just join - * it. To serialize that we use a file lock on the socket - * pair. - * - * It's a bit crazy, but hey, works great! */ - - if (lockf(netns_storage_socket[0], F_LOCK, 0) < 0) - return -errno; - - netns = receive_one_fd(netns_storage_socket[0], MSG_DONTWAIT); - if (netns == -EAGAIN) { - /* Nothing stored yet, so let's create a new namespace */ - - if (unshare(CLONE_NEWNET) < 0) { - r = -errno; - goto fail; - } - - loopback_setup(); - - netns = open("/proc/self/ns/net", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (netns < 0) { - r = -errno; - goto fail; - } - - r = 1; - - } else if (netns < 0) { - r = netns; - goto fail; - - } else { - /* Yay, found something, so let's join the namespace */ - if (setns(netns, CLONE_NEWNET) < 0) { - r = -errno; - goto fail; - } - - r = 0; - } - - q = send_one_fd(netns_storage_socket[1], netns, MSG_DONTWAIT); - if (q < 0) { - r = q; - goto fail; - } - -fail: - lockf(netns_storage_socket[0], F_ULOCK, 0); - return r; -} - -static const char *const protect_home_table[_PROTECT_HOME_MAX] = { - [PROTECT_HOME_NO] = "no", - [PROTECT_HOME_YES] = "yes", - [PROTECT_HOME_READ_ONLY] = "read-only", -}; - -DEFINE_STRING_TABLE_LOOKUP(protect_home, ProtectHome); - -static const char *const protect_system_table[_PROTECT_SYSTEM_MAX] = { - [PROTECT_SYSTEM_NO] = "no", - [PROTECT_SYSTEM_YES] = "yes", - [PROTECT_SYSTEM_FULL] = "full", -}; - -DEFINE_STRING_TABLE_LOOKUP(protect_system, ProtectSystem); diff --git a/src/core/namespace.h b/src/core/namespace.h deleted file mode 100644 index b54b7b47d6..0000000000 --- a/src/core/namespace.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "macro.h" - -typedef enum ProtectHome { - PROTECT_HOME_NO, - PROTECT_HOME_YES, - PROTECT_HOME_READ_ONLY, - _PROTECT_HOME_MAX, - _PROTECT_HOME_INVALID = -1 -} ProtectHome; - -typedef enum ProtectSystem { - PROTECT_SYSTEM_NO, - PROTECT_SYSTEM_YES, - PROTECT_SYSTEM_FULL, - _PROTECT_SYSTEM_MAX, - _PROTECT_SYSTEM_INVALID = -1 -} ProtectSystem; - -int setup_namespace(const char *chroot, - char **read_write_dirs, - char **read_only_dirs, - char **inaccessible_dirs, - const char *tmp_dir, - const char *var_tmp_dir, - bool private_dev, - ProtectHome protect_home, - ProtectSystem protect_system, - unsigned long mount_flags); - -int setup_tmp_dirs(const char *id, - char **tmp_dir, - char **var_tmp_dir); - -int setup_netns(int netns_storage_socket[2]); - -const char* protect_home_to_string(ProtectHome p) _const_; -ProtectHome protect_home_from_string(const char *s) _pure_; - -const char* protect_system_to_string(ProtectSystem p) _const_; -ProtectSystem protect_system_from_string(const char *s) _pure_; diff --git a/src/core/org.freedesktop.systemd1.conf b/src/core/org.freedesktop.systemd1.conf deleted file mode 100644 index 3c64f20872..0000000000 --- a/src/core/org.freedesktop.systemd1.conf +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/core/org.freedesktop.systemd1.policy.in.in b/src/core/org.freedesktop.systemd1.policy.in.in deleted file mode 100644 index cc39a9e1c3..0000000000 --- a/src/core/org.freedesktop.systemd1.policy.in.in +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - The systemd Project - http://www.freedesktop.org/wiki/Software/systemd - - - <_description>Send passphrase back to system - <_message>Authentication is required to send the entered passphrase back to the system. - - no - no - auth_admin_keep - - @rootlibexecdir@/systemd-reply-password - - - - <_description>Manage system services or other units - <_message>Authentication is required to manage system services or other units. - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Manage system service or unit files - <_message>Authentication is required to manage system service or unit files. - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Set or unset system and service manager environment variables - <_message>Authentication is required to set or unset system and service manager environment variables. - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Reload the systemd state - <_message>Authentication is required to reload the systemd state. - - auth_admin - auth_admin - auth_admin_keep - - - - diff --git a/src/core/org.freedesktop.systemd1.service b/src/core/org.freedesktop.systemd1.service deleted file mode 100644 index d4df3e93a2..0000000000 --- a/src/core/org.freedesktop.systemd1.service +++ /dev/null @@ -1,11 +0,0 @@ -# This file is part of systemd. -# -# 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. - -[D-BUS Service] -Name=org.freedesktop.systemd1 -Exec=/bin/false -User=root diff --git a/src/core/path.c b/src/core/path.c deleted file mode 100644 index 0dd0d375d8..0000000000 --- a/src/core/path.c +++ /dev/null @@ -1,784 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include - -#include "bus-error.h" -#include "bus-util.h" -#include "dbus-path.h" -#include "fd-util.h" -#include "fs-util.h" -#include "glob-util.h" -#include "macro.h" -#include "mkdir.h" -#include "path.h" -#include "special.h" -#include "stat-util.h" -#include "string-table.h" -#include "string-util.h" -#include "unit-name.h" -#include "unit.h" - -static const UnitActiveState state_translation_table[_PATH_STATE_MAX] = { - [PATH_DEAD] = UNIT_INACTIVE, - [PATH_WAITING] = UNIT_ACTIVE, - [PATH_RUNNING] = UNIT_ACTIVE, - [PATH_FAILED] = UNIT_FAILED -}; - -static int path_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); - -int path_spec_watch(PathSpec *s, sd_event_io_handler_t handler) { - - static const int flags_table[_PATH_TYPE_MAX] = { - [PATH_EXISTS] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB, - [PATH_EXISTS_GLOB] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB, - [PATH_CHANGED] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO, - [PATH_MODIFIED] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO|IN_MODIFY, - [PATH_DIRECTORY_NOT_EMPTY] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CREATE|IN_MOVED_TO - }; - - bool exists = false; - char *slash, *oldslash = NULL; - int r; - - assert(s); - assert(s->unit); - assert(handler); - - path_spec_unwatch(s); - - s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); - if (s->inotify_fd < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(s->unit->manager->event, &s->event_source, s->inotify_fd, EPOLLIN, handler, s); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(s->event_source, "path"); - - /* This assumes the path was passed through path_kill_slashes()! */ - - for (slash = strchr(s->path, '/'); ; slash = strchr(slash+1, '/')) { - char *cut = NULL; - int flags; - char tmp; - - if (slash) { - cut = slash + (slash == s->path); - tmp = *cut; - *cut = '\0'; - - flags = IN_MOVE_SELF | IN_DELETE_SELF | IN_ATTRIB | IN_CREATE | IN_MOVED_TO; - } else - flags = flags_table[s->type]; - - r = inotify_add_watch(s->inotify_fd, s->path, flags); - if (r < 0) { - if (errno == EACCES || errno == ENOENT) { - if (cut) - *cut = tmp; - break; - } - - r = log_warning_errno(errno, "Failed to add watch on %s: %s", s->path, errno == ENOSPC ? "too many watches" : strerror(-r)); - if (cut) - *cut = tmp; - goto fail; - } else { - exists = true; - - /* Path exists, we don't need to watch parent too closely. */ - if (oldslash) { - char *cut2 = oldslash + (oldslash == s->path); - char tmp2 = *cut2; - *cut2 = '\0'; - - (void) inotify_add_watch(s->inotify_fd, s->path, IN_MOVE_SELF); - /* Error is ignored, the worst can happen is we get spurious events. */ - - *cut2 = tmp2; - } - } - - if (cut) - *cut = tmp; - - if (slash) - oldslash = slash; - else { - /* whole path has been iterated over */ - s->primary_wd = r; - break; - } - } - - if (!exists) { - r = log_error_errno(errno, "Failed to add watch on any of the components of %s: %m", s->path); - /* either EACCESS or ENOENT */ - goto fail; - } - - return 0; - -fail: - path_spec_unwatch(s); - return r; -} - -void path_spec_unwatch(PathSpec *s) { - assert(s); - - s->event_source = sd_event_source_unref(s->event_source); - s->inotify_fd = safe_close(s->inotify_fd); -} - -int path_spec_fd_event(PathSpec *s, uint32_t revents) { - union inotify_event_buffer buffer; - struct inotify_event *e; - ssize_t l; - int r = 0; - - if (revents != EPOLLIN) { - log_error("Got invalid poll event on inotify."); - return -EINVAL; - } - - l = read(s->inotify_fd, &buffer, sizeof(buffer)); - if (l < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return log_error_errno(errno, "Failed to read inotify event: %m"); - } - - FOREACH_INOTIFY_EVENT(e, buffer, l) { - if ((s->type == PATH_CHANGED || s->type == PATH_MODIFIED) && - s->primary_wd == e->wd) - r = 1; - } - - return r; -} - -static bool path_spec_check_good(PathSpec *s, bool initial) { - bool good = false; - - switch (s->type) { - - case PATH_EXISTS: - good = access(s->path, F_OK) >= 0; - break; - - case PATH_EXISTS_GLOB: - good = glob_exists(s->path) > 0; - break; - - case PATH_DIRECTORY_NOT_EMPTY: { - int k; - - k = dir_is_empty(s->path); - good = !(k == -ENOENT || k > 0); - break; - } - - case PATH_CHANGED: - case PATH_MODIFIED: { - bool b; - - b = access(s->path, F_OK) >= 0; - good = !initial && b != s->previous_exists; - s->previous_exists = b; - break; - } - - default: - ; - } - - return good; -} - -static void path_spec_mkdir(PathSpec *s, mode_t mode) { - int r; - - if (s->type == PATH_EXISTS || s->type == PATH_EXISTS_GLOB) - return; - - r = mkdir_p_label(s->path, mode); - if (r < 0) - log_warning_errno(r, "mkdir(%s) failed: %m", s->path); -} - -static void path_spec_dump(PathSpec *s, FILE *f, const char *prefix) { - fprintf(f, - "%s%s: %s\n", - prefix, - path_type_to_string(s->type), - s->path); -} - -void path_spec_done(PathSpec *s) { - assert(s); - assert(s->inotify_fd == -1); - - free(s->path); -} - -static void path_init(Unit *u) { - Path *p = PATH(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - p->directory_mode = 0755; -} - -void path_free_specs(Path *p) { - PathSpec *s; - - assert(p); - - while ((s = p->specs)) { - path_spec_unwatch(s); - LIST_REMOVE(spec, p->specs, s); - path_spec_done(s); - free(s); - } -} - -static void path_done(Unit *u) { - Path *p = PATH(u); - - assert(p); - - path_free_specs(p); -} - -static int path_add_mount_links(Path *p) { - PathSpec *s; - int r; - - assert(p); - - LIST_FOREACH(spec, s, p->specs) { - r = unit_require_mounts_for(UNIT(p), s->path); - if (r < 0) - return r; - } - - return 0; -} - -static int path_verify(Path *p) { - assert(p); - - if (UNIT(p)->load_state != UNIT_LOADED) - return 0; - - if (!p->specs) { - log_unit_error(UNIT(p), "Path unit lacks path setting. Refusing."); - return -EINVAL; - } - - return 0; -} - -static int path_add_default_dependencies(Path *p) { - int r; - - assert(p); - - if (!UNIT(p)->default_dependencies) - return 0; - - r = unit_add_dependency_by_name(UNIT(p), UNIT_BEFORE, SPECIAL_PATHS_TARGET, NULL, true); - if (r < 0) - return r; - - if (MANAGER_IS_SYSTEM(UNIT(p)->manager)) { - r = unit_add_two_dependencies_by_name(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); - if (r < 0) - return r; - } - - return unit_add_two_dependencies_by_name(UNIT(p), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); -} - -static int path_load(Unit *u) { - Path *p = PATH(u); - int r; - - assert(u); - assert(u->load_state == UNIT_STUB); - - r = unit_load_fragment_and_dropin(u); - if (r < 0) - return r; - - if (u->load_state == UNIT_LOADED) { - - if (set_isempty(u->dependencies[UNIT_TRIGGERS])) { - Unit *x; - - r = unit_load_related_unit(u, ".service", &x); - if (r < 0) - return r; - - r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, x, true); - if (r < 0) - return r; - } - - r = path_add_mount_links(p); - if (r < 0) - return r; - - r = path_add_default_dependencies(p); - if (r < 0) - return r; - } - - return path_verify(p); -} - -static void path_dump(Unit *u, FILE *f, const char *prefix) { - Path *p = PATH(u); - Unit *trigger; - PathSpec *s; - - assert(p); - assert(f); - - trigger = UNIT_TRIGGER(u); - - fprintf(f, - "%sPath State: %s\n" - "%sResult: %s\n" - "%sUnit: %s\n" - "%sMakeDirectory: %s\n" - "%sDirectoryMode: %04o\n", - prefix, path_state_to_string(p->state), - prefix, path_result_to_string(p->result), - prefix, trigger ? trigger->id : "n/a", - prefix, yes_no(p->make_directory), - prefix, p->directory_mode); - - LIST_FOREACH(spec, s, p->specs) - path_spec_dump(s, f, prefix); -} - -static void path_unwatch(Path *p) { - PathSpec *s; - - assert(p); - - LIST_FOREACH(spec, s, p->specs) - path_spec_unwatch(s); -} - -static int path_watch(Path *p) { - int r; - PathSpec *s; - - assert(p); - - LIST_FOREACH(spec, s, p->specs) { - r = path_spec_watch(s, path_dispatch_io); - if (r < 0) - return r; - } - - return 0; -} - -static void path_set_state(Path *p, PathState state) { - PathState old_state; - assert(p); - - old_state = p->state; - p->state = state; - - if (state != PATH_WAITING && - (state != PATH_RUNNING || p->inotify_triggered)) - path_unwatch(p); - - if (state != old_state) - log_unit_debug(UNIT(p), "Changed %s -> %s", path_state_to_string(old_state), path_state_to_string(state)); - - unit_notify(UNIT(p), state_translation_table[old_state], state_translation_table[state], true); -} - -static void path_enter_waiting(Path *p, bool initial, bool recheck); - -static int path_coldplug(Unit *u) { - Path *p = PATH(u); - - assert(p); - assert(p->state == PATH_DEAD); - - if (p->deserialized_state != p->state) { - - if (p->deserialized_state == PATH_WAITING || - p->deserialized_state == PATH_RUNNING) - path_enter_waiting(p, true, true); - else - path_set_state(p, p->deserialized_state); - } - - return 0; -} - -static void path_enter_dead(Path *p, PathResult f) { - assert(p); - - if (f != PATH_SUCCESS) - p->result = f; - - path_set_state(p, p->result != PATH_SUCCESS ? PATH_FAILED : PATH_DEAD); -} - -static void path_enter_running(Path *p) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - Unit *trigger; - int r; - - assert(p); - - /* Don't start job if we are supposed to go down */ - if (unit_stop_pending(UNIT(p))) - return; - - trigger = UNIT_TRIGGER(UNIT(p)); - if (!trigger) { - log_unit_error(UNIT(p), "Unit to trigger vanished."); - path_enter_dead(p, PATH_FAILURE_RESOURCES); - return; - } - - r = manager_add_job(UNIT(p)->manager, JOB_START, trigger, JOB_REPLACE, &error, NULL); - if (r < 0) - goto fail; - - p->inotify_triggered = false; - - r = path_watch(p); - if (r < 0) - goto fail; - - path_set_state(p, PATH_RUNNING); - return; - -fail: - log_unit_warning(UNIT(p), "Failed to queue unit startup job: %s", bus_error_message(&error, r)); - path_enter_dead(p, PATH_FAILURE_RESOURCES); -} - -static bool path_check_good(Path *p, bool initial) { - PathSpec *s; - bool good = false; - - assert(p); - - LIST_FOREACH(spec, s, p->specs) { - good = path_spec_check_good(s, initial); - - if (good) - break; - } - - return good; -} - -static void path_enter_waiting(Path *p, bool initial, bool recheck) { - int r; - - if (recheck) - if (path_check_good(p, initial)) { - log_unit_debug(UNIT(p), "Got triggered."); - path_enter_running(p); - return; - } - - r = path_watch(p); - if (r < 0) - goto fail; - - /* Hmm, so now we have created inotify watches, but the file - * might have appeared/been removed by now, so we must - * recheck */ - - if (recheck) - if (path_check_good(p, false)) { - log_unit_debug(UNIT(p), "Got triggered."); - path_enter_running(p); - return; - } - - path_set_state(p, PATH_WAITING); - return; - -fail: - log_unit_warning_errno(UNIT(p), r, "Failed to enter waiting state: %m"); - path_enter_dead(p, PATH_FAILURE_RESOURCES); -} - -static void path_mkdir(Path *p) { - PathSpec *s; - - assert(p); - - if (!p->make_directory) - return; - - LIST_FOREACH(spec, s, p->specs) - path_spec_mkdir(s, p->directory_mode); -} - -static int path_start(Unit *u) { - Path *p = PATH(u); - Unit *trigger; - int r; - - assert(p); - assert(p->state == PATH_DEAD || p->state == PATH_FAILED); - - trigger = UNIT_TRIGGER(u); - if (!trigger || trigger->load_state != UNIT_LOADED) { - log_unit_error(u, "Refusing to start, unit to trigger not loaded."); - return -ENOENT; - } - - r = unit_start_limit_test(u); - if (r < 0) { - path_enter_dead(p, PATH_FAILURE_START_LIMIT_HIT); - return r; - } - - path_mkdir(p); - - p->result = PATH_SUCCESS; - path_enter_waiting(p, true, true); - - return 1; -} - -static int path_stop(Unit *u) { - Path *p = PATH(u); - - assert(p); - assert(p->state == PATH_WAITING || p->state == PATH_RUNNING); - - path_enter_dead(p, PATH_SUCCESS); - return 1; -} - -static int path_serialize(Unit *u, FILE *f, FDSet *fds) { - Path *p = PATH(u); - - assert(u); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", path_state_to_string(p->state)); - unit_serialize_item(u, f, "result", path_result_to_string(p->result)); - - return 0; -} - -static int path_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Path *p = PATH(u); - - assert(u); - assert(key); - assert(value); - assert(fds); - - if (streq(key, "state")) { - PathState state; - - state = path_state_from_string(value); - if (state < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - p->deserialized_state = state; - - } else if (streq(key, "result")) { - PathResult f; - - f = path_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse result value: %s", value); - else if (f != PATH_SUCCESS) - p->result = f; - - } else - log_unit_debug(u, "Unknown serialization key: %s", key); - - return 0; -} - -_pure_ static UnitActiveState path_active_state(Unit *u) { - assert(u); - - return state_translation_table[PATH(u)->state]; -} - -_pure_ static const char *path_sub_state_to_string(Unit *u) { - assert(u); - - return path_state_to_string(PATH(u)->state); -} - -static int path_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - PathSpec *s = userdata; - Path *p; - int changed; - - assert(s); - assert(s->unit); - assert(fd >= 0); - - p = PATH(s->unit); - - if (p->state != PATH_WAITING && - p->state != PATH_RUNNING) - return 0; - - /* log_debug("inotify wakeup on %s.", u->id); */ - - LIST_FOREACH(spec, s, p->specs) - if (path_spec_owns_inotify_fd(s, fd)) - break; - - if (!s) { - log_error("Got event on unknown fd."); - goto fail; - } - - changed = path_spec_fd_event(s, revents); - if (changed < 0) - goto fail; - - /* If we are already running, then remember that one event was - * dispatched so that we restart the service only if something - * actually changed on disk */ - p->inotify_triggered = true; - - if (changed) - path_enter_running(p); - else - path_enter_waiting(p, false, true); - - return 0; - -fail: - path_enter_dead(p, PATH_FAILURE_RESOURCES); - return 0; -} - -static void path_trigger_notify(Unit *u, Unit *other) { - Path *p = PATH(u); - - assert(u); - assert(other); - - /* Invoked whenever the unit we trigger changes state or gains - * or loses a job */ - - if (other->load_state != UNIT_LOADED) - return; - - if (p->state == PATH_RUNNING && - UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) { - log_unit_debug(UNIT(p), "Got notified about unit deactivation."); - - /* Hmm, so inotify was triggered since the - * last activation, so I guess we need to - * recheck what is going on. */ - path_enter_waiting(p, false, p->inotify_triggered); - } -} - -static void path_reset_failed(Unit *u) { - Path *p = PATH(u); - - assert(p); - - if (p->state == PATH_FAILED) - path_set_state(p, PATH_DEAD); - - p->result = PATH_SUCCESS; -} - -static const char* const path_type_table[_PATH_TYPE_MAX] = { - [PATH_EXISTS] = "PathExists", - [PATH_EXISTS_GLOB] = "PathExistsGlob", - [PATH_DIRECTORY_NOT_EMPTY] = "DirectoryNotEmpty", - [PATH_CHANGED] = "PathChanged", - [PATH_MODIFIED] = "PathModified", -}; - -DEFINE_STRING_TABLE_LOOKUP(path_type, PathType); - -static const char* const path_result_table[_PATH_RESULT_MAX] = { - [PATH_SUCCESS] = "success", - [PATH_FAILURE_RESOURCES] = "resources", - [PATH_FAILURE_START_LIMIT_HIT] = "start-limit-hit", -}; - -DEFINE_STRING_TABLE_LOOKUP(path_result, PathResult); - -const UnitVTable path_vtable = { - .object_size = sizeof(Path), - - .sections = - "Unit\0" - "Path\0" - "Install\0", - - .init = path_init, - .done = path_done, - .load = path_load, - - .coldplug = path_coldplug, - - .dump = path_dump, - - .start = path_start, - .stop = path_stop, - - .serialize = path_serialize, - .deserialize_item = path_deserialize_item, - - .active_state = path_active_state, - .sub_state_to_string = path_sub_state_to_string, - - .trigger_notify = path_trigger_notify, - - .reset_failed = path_reset_failed, - - .bus_vtable = bus_path_vtable -}; diff --git a/src/core/path.h b/src/core/path.h deleted file mode 100644 index 4230c8fb99..0000000000 --- a/src/core/path.h +++ /dev/null @@ -1,93 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct Path Path; -typedef struct PathSpec PathSpec; - -#include "unit.h" - -typedef enum PathType { - PATH_EXISTS, - PATH_EXISTS_GLOB, - PATH_DIRECTORY_NOT_EMPTY, - PATH_CHANGED, - PATH_MODIFIED, - _PATH_TYPE_MAX, - _PATH_TYPE_INVALID = -1 -} PathType; - -typedef struct PathSpec { - Unit *unit; - - char *path; - - sd_event_source *event_source; - - LIST_FIELDS(struct PathSpec, spec); - - PathType type; - int inotify_fd; - int primary_wd; - - bool previous_exists; -} PathSpec; - -int path_spec_watch(PathSpec *s, sd_event_io_handler_t handler); -void path_spec_unwatch(PathSpec *s); -int path_spec_fd_event(PathSpec *s, uint32_t events); -void path_spec_done(PathSpec *s); - -static inline bool path_spec_owns_inotify_fd(PathSpec *s, int fd) { - return s->inotify_fd == fd; -} - -typedef enum PathResult { - PATH_SUCCESS, - PATH_FAILURE_RESOURCES, - PATH_FAILURE_START_LIMIT_HIT, - _PATH_RESULT_MAX, - _PATH_RESULT_INVALID = -1 -} PathResult; - -struct Path { - Unit meta; - - LIST_HEAD(PathSpec, specs); - - PathState state, deserialized_state; - - bool inotify_triggered; - - bool make_directory; - mode_t directory_mode; - - PathResult result; -}; - -void path_free_specs(Path *p); - -extern const UnitVTable path_vtable; - -const char* path_type_to_string(PathType i) _const_; -PathType path_type_from_string(const char *s) _pure_; - -const char* path_result_to_string(PathResult i) _const_; -PathResult path_result_from_string(const char *s) _pure_; diff --git a/src/core/scope.c b/src/core/scope.c deleted file mode 100644 index 238f63a729..0000000000 --- a/src/core/scope.c +++ /dev/null @@ -1,608 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 - -#include "alloc-util.h" -#include "dbus-scope.h" -#include "load-dropin.h" -#include "log.h" -#include "scope.h" -#include "special.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "unit.h" - -static const UnitActiveState state_translation_table[_SCOPE_STATE_MAX] = { - [SCOPE_DEAD] = UNIT_INACTIVE, - [SCOPE_RUNNING] = UNIT_ACTIVE, - [SCOPE_ABANDONED] = UNIT_ACTIVE, - [SCOPE_STOP_SIGTERM] = UNIT_DEACTIVATING, - [SCOPE_STOP_SIGKILL] = UNIT_DEACTIVATING, - [SCOPE_FAILED] = UNIT_FAILED -}; - -static int scope_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); - -static void scope_init(Unit *u) { - Scope *s = SCOPE(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - s->timeout_stop_usec = u->manager->default_timeout_stop_usec; - u->ignore_on_isolate = true; -} - -static void scope_done(Unit *u) { - Scope *s = SCOPE(u); - - assert(u); - - free(s->controller); - - s->timer_event_source = sd_event_source_unref(s->timer_event_source); -} - -static int scope_arm_timer(Scope *s, usec_t usec) { - int r; - - assert(s); - - if (s->timer_event_source) { - r = sd_event_source_set_time(s->timer_event_source, usec); - if (r < 0) - return r; - - return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); - } - - if (usec == USEC_INFINITY) - return 0; - - r = sd_event_add_time( - UNIT(s)->manager->event, - &s->timer_event_source, - CLOCK_MONOTONIC, - usec, 0, - scope_dispatch_timer, s); - if (r < 0) - return r; - - (void) sd_event_source_set_description(s->timer_event_source, "scope-timer"); - - return 0; -} - -static void scope_set_state(Scope *s, ScopeState state) { - ScopeState old_state; - assert(s); - - old_state = s->state; - s->state = state; - - if (!IN_SET(state, SCOPE_STOP_SIGTERM, SCOPE_STOP_SIGKILL)) - s->timer_event_source = sd_event_source_unref(s->timer_event_source); - - if (IN_SET(state, SCOPE_DEAD, SCOPE_FAILED)) - unit_unwatch_all_pids(UNIT(s)); - - if (state != old_state) - log_debug("%s changed %s -> %s", UNIT(s)->id, scope_state_to_string(old_state), scope_state_to_string(state)); - - unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); -} - -static int scope_add_default_dependencies(Scope *s) { - int r; - - assert(s); - - if (!UNIT(s)->default_dependencies) - return 0; - - /* Make sure scopes are unloaded on shutdown */ - r = unit_add_two_dependencies_by_name( - UNIT(s), - UNIT_BEFORE, UNIT_CONFLICTS, - SPECIAL_SHUTDOWN_TARGET, NULL, true); - if (r < 0) - return r; - - return 0; -} - -static int scope_verify(Scope *s) { - assert(s); - - if (UNIT(s)->load_state != UNIT_LOADED) - return 0; - - if (set_isempty(UNIT(s)->pids) && - !MANAGER_IS_RELOADING(UNIT(s)->manager) && - !unit_has_name(UNIT(s), SPECIAL_INIT_SCOPE)) { - log_unit_error(UNIT(s), "Scope has no PIDs. Refusing."); - return -EINVAL; - } - - return 0; -} - -static int scope_load(Unit *u) { - Scope *s = SCOPE(u); - int r; - - assert(s); - assert(u->load_state == UNIT_STUB); - - if (!u->transient && !MANAGER_IS_RELOADING(u->manager)) - /* Refuse to load non-transient scope units, but allow them while reloading. */ - return -ENOENT; - - r = unit_load_fragment_and_dropin_optional(u); - if (r < 0) - return r; - - if (u->load_state == UNIT_LOADED) { - r = unit_patch_contexts(u); - if (r < 0) - return r; - - r = unit_set_default_slice(u); - if (r < 0) - return r; - - r = scope_add_default_dependencies(s); - if (r < 0) - return r; - } - - return scope_verify(s); -} - -static int scope_coldplug(Unit *u) { - Scope *s = SCOPE(u); - int r; - - assert(s); - assert(s->state == SCOPE_DEAD); - - if (s->deserialized_state == s->state) - return 0; - - if (IN_SET(s->deserialized_state, SCOPE_STOP_SIGKILL, SCOPE_STOP_SIGTERM)) { - r = scope_arm_timer(s, usec_add(u->state_change_timestamp.monotonic, s->timeout_stop_usec)); - if (r < 0) - return r; - } - - if (!IN_SET(s->deserialized_state, SCOPE_DEAD, SCOPE_FAILED)) - unit_watch_all_pids(UNIT(s)); - - scope_set_state(s, s->deserialized_state); - return 0; -} - -static void scope_dump(Unit *u, FILE *f, const char *prefix) { - Scope *s = SCOPE(u); - - assert(s); - assert(f); - - fprintf(f, - "%sScope State: %s\n" - "%sResult: %s\n", - prefix, scope_state_to_string(s->state), - prefix, scope_result_to_string(s->result)); - - cgroup_context_dump(&s->cgroup_context, f, prefix); - kill_context_dump(&s->kill_context, f, prefix); -} - -static void scope_enter_dead(Scope *s, ScopeResult f) { - assert(s); - - if (f != SCOPE_SUCCESS) - s->result = f; - - scope_set_state(s, s->result != SCOPE_SUCCESS ? SCOPE_FAILED : SCOPE_DEAD); -} - -static void scope_enter_signal(Scope *s, ScopeState state, ScopeResult f) { - bool skip_signal = false; - int r; - - assert(s); - - if (f != SCOPE_SUCCESS) - s->result = f; - - unit_watch_all_pids(UNIT(s)); - - /* If we have a controller set let's ask the controller nicely - * to terminate the scope, instead of us going directly into - * SIGTERM beserk mode */ - if (state == SCOPE_STOP_SIGTERM) - skip_signal = bus_scope_send_request_stop(s) > 0; - - if (!skip_signal) { - r = unit_kill_context( - UNIT(s), - &s->kill_context, - state != SCOPE_STOP_SIGTERM ? KILL_KILL : KILL_TERMINATE, - -1, -1, false); - if (r < 0) - goto fail; - } else - r = 1; - - if (r > 0) { - r = scope_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_stop_usec)); - if (r < 0) - goto fail; - - scope_set_state(s, state); - } else if (state == SCOPE_STOP_SIGTERM) - scope_enter_signal(s, SCOPE_STOP_SIGKILL, SCOPE_SUCCESS); - else - scope_enter_dead(s, SCOPE_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); - - scope_enter_dead(s, SCOPE_FAILURE_RESOURCES); -} - -static int scope_start(Unit *u) { - Scope *s = SCOPE(u); - int r; - - assert(s); - - if (unit_has_name(u, SPECIAL_INIT_SCOPE)) - return -EPERM; - - if (s->state == SCOPE_FAILED) - return -EPERM; - - /* We can't fulfill this right now, please try again later */ - if (s->state == SCOPE_STOP_SIGTERM || - s->state == SCOPE_STOP_SIGKILL) - return -EAGAIN; - - assert(s->state == SCOPE_DEAD); - - if (!u->transient && !MANAGER_IS_RELOADING(u->manager)) - return -ENOENT; - - (void) unit_realize_cgroup(u); - (void) unit_reset_cpu_usage(u); - - r = unit_attach_pids_to_cgroup(u); - if (r < 0) { - log_unit_warning_errno(UNIT(s), r, "Failed to add PIDs to scope's control group: %m"); - scope_enter_dead(s, SCOPE_FAILURE_RESOURCES); - return r; - } - - s->result = SCOPE_SUCCESS; - - scope_set_state(s, SCOPE_RUNNING); - return 1; -} - -static int scope_stop(Unit *u) { - Scope *s = SCOPE(u); - - assert(s); - - if (s->state == SCOPE_STOP_SIGTERM || - s->state == SCOPE_STOP_SIGKILL) - return 0; - - assert(s->state == SCOPE_RUNNING || - s->state == SCOPE_ABANDONED); - - scope_enter_signal(s, SCOPE_STOP_SIGTERM, SCOPE_SUCCESS); - return 1; -} - -static void scope_reset_failed(Unit *u) { - Scope *s = SCOPE(u); - - assert(s); - - if (s->state == SCOPE_FAILED) - scope_set_state(s, SCOPE_DEAD); - - s->result = SCOPE_SUCCESS; -} - -static int scope_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { - return unit_kill_common(u, who, signo, -1, -1, error); -} - -static int scope_get_timeout(Unit *u, usec_t *timeout) { - Scope *s = SCOPE(u); - usec_t t; - int r; - - if (!s->timer_event_source) - return 0; - - r = sd_event_source_get_time(s->timer_event_source, &t); - if (r < 0) - return r; - if (t == USEC_INFINITY) - return 0; - - *timeout = t; - return 1; -} - -static int scope_serialize(Unit *u, FILE *f, FDSet *fds) { - Scope *s = SCOPE(u); - - assert(s); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", scope_state_to_string(s->state)); - return 0; -} - -static int scope_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Scope *s = SCOPE(u); - - assert(u); - assert(key); - assert(value); - assert(fds); - - if (streq(key, "state")) { - ScopeState state; - - state = scope_state_from_string(value); - if (state < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - s->deserialized_state = state; - - } else - log_unit_debug(u, "Unknown serialization key: %s", key); - - return 0; -} - -static bool scope_check_gc(Unit *u) { - assert(u); - - /* Never clean up scopes that still have a process around, - * even if the scope is formally dead. */ - - if (!u->cgroup_path) - return false; - - return cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path) <= 0; -} - -static void scope_notify_cgroup_empty_event(Unit *u) { - Scope *s = SCOPE(u); - assert(u); - - log_unit_debug(u, "cgroup is empty"); - - if (IN_SET(s->state, SCOPE_RUNNING, SCOPE_ABANDONED, SCOPE_STOP_SIGTERM, SCOPE_STOP_SIGKILL)) - scope_enter_dead(s, SCOPE_SUCCESS); -} - -static void scope_sigchld_event(Unit *u, pid_t pid, int code, int status) { - - /* If we get a SIGCHLD event for one of the processes we were - interested in, then we look for others to watch, under the - assumption that we'll sooner or later get a SIGCHLD for - them, as the original process we watched was probably the - parent of them, and they are hence now our children. */ - - unit_tidy_watch_pids(u, 0, 0); - unit_watch_all_pids(u); - - /* If the PID set is empty now, then let's finish this off */ - if (set_isempty(u->pids)) - scope_notify_cgroup_empty_event(u); -} - -static int scope_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { - Scope *s = SCOPE(userdata); - - assert(s); - assert(s->timer_event_source == source); - - switch (s->state) { - - case SCOPE_STOP_SIGTERM: - if (s->kill_context.send_sigkill) { - log_unit_warning(UNIT(s), "Stopping timed out. Killing."); - scope_enter_signal(s, SCOPE_STOP_SIGKILL, SCOPE_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(s), "Stopping timed out. Skipping SIGKILL."); - scope_enter_dead(s, SCOPE_FAILURE_TIMEOUT); - } - - break; - - case SCOPE_STOP_SIGKILL: - log_unit_warning(UNIT(s), "Still around after SIGKILL. Ignoring."); - scope_enter_dead(s, SCOPE_FAILURE_TIMEOUT); - break; - - default: - assert_not_reached("Timeout at wrong time."); - } - - return 0; -} - -int scope_abandon(Scope *s) { - assert(s); - - if (unit_has_name(UNIT(s), SPECIAL_INIT_SCOPE)) - return -EPERM; - - if (!IN_SET(s->state, SCOPE_RUNNING, SCOPE_ABANDONED)) - return -ESTALE; - - s->controller = mfree(s->controller); - - /* The client is no longer watching the remaining processes, - * so let's step in here, under the assumption that the - * remaining processes will be sooner or later reassigned to - * us as parent. */ - - unit_tidy_watch_pids(UNIT(s), 0, 0); - unit_watch_all_pids(UNIT(s)); - - /* If the PID set is empty now, then let's finish this off */ - if (set_isempty(UNIT(s)->pids)) - scope_notify_cgroup_empty_event(UNIT(s)); - else - scope_set_state(s, SCOPE_ABANDONED); - - return 0; -} - -_pure_ static UnitActiveState scope_active_state(Unit *u) { - assert(u); - - return state_translation_table[SCOPE(u)->state]; -} - -_pure_ static const char *scope_sub_state_to_string(Unit *u) { - assert(u); - - return scope_state_to_string(SCOPE(u)->state); -} - -static void scope_enumerate(Manager *m) { - Unit *u; - int r; - - assert(m); - - /* Let's unconditionally add the "init.scope" special unit - * that encapsulates PID 1. Note that PID 1 already is in the - * cgroup for this, we hence just need to allocate the object - * for it and that's it. */ - - u = manager_get_unit(m, SPECIAL_INIT_SCOPE); - if (!u) { - u = unit_new(m, sizeof(Scope)); - if (!u) { - log_oom(); - return; - } - - r = unit_add_name(u, SPECIAL_INIT_SCOPE); - if (r < 0) { - unit_free(u); - log_error_errno(r, "Failed to add init.scope name"); - return; - } - } - - u->transient = true; - u->default_dependencies = false; - u->no_gc = true; - u->ignore_on_isolate = true; - u->refuse_manual_start = true; - u->refuse_manual_stop = true; - SCOPE(u)->deserialized_state = SCOPE_RUNNING; - SCOPE(u)->kill_context.kill_signal = SIGRTMIN+14; - - /* Prettify things, if we can. */ - if (!u->description) - u->description = strdup("System and Service Manager"); - if (!u->documentation) - (void) strv_extend(&u->documentation, "man:systemd(1)"); - - unit_add_to_load_queue(u); - unit_add_to_dbus_queue(u); -} - -static const char* const scope_result_table[_SCOPE_RESULT_MAX] = { - [SCOPE_SUCCESS] = "success", - [SCOPE_FAILURE_RESOURCES] = "resources", - [SCOPE_FAILURE_TIMEOUT] = "timeout", -}; - -DEFINE_STRING_TABLE_LOOKUP(scope_result, ScopeResult); - -const UnitVTable scope_vtable = { - .object_size = sizeof(Scope), - .cgroup_context_offset = offsetof(Scope, cgroup_context), - .kill_context_offset = offsetof(Scope, kill_context), - - .sections = - "Unit\0" - "Scope\0" - "Install\0", - .private_section = "Scope", - - .can_transient = true, - - .init = scope_init, - .load = scope_load, - .done = scope_done, - - .coldplug = scope_coldplug, - - .dump = scope_dump, - - .start = scope_start, - .stop = scope_stop, - - .kill = scope_kill, - - .get_timeout = scope_get_timeout, - - .serialize = scope_serialize, - .deserialize_item = scope_deserialize_item, - - .active_state = scope_active_state, - .sub_state_to_string = scope_sub_state_to_string, - - .check_gc = scope_check_gc, - - .sigchld_event = scope_sigchld_event, - - .reset_failed = scope_reset_failed, - - .notify_cgroup_empty = scope_notify_cgroup_empty_event, - - .bus_vtable = bus_scope_vtable, - .bus_set_property = bus_scope_set_property, - .bus_commit_properties = bus_scope_commit_properties, - - .enumerate = scope_enumerate, -}; diff --git a/src/core/scope.h b/src/core/scope.h deleted file mode 100644 index 2dc86325c5..0000000000 --- a/src/core/scope.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -typedef struct Scope Scope; - -#include "kill.h" - -typedef enum ScopeResult { - SCOPE_SUCCESS, - SCOPE_FAILURE_RESOURCES, - SCOPE_FAILURE_TIMEOUT, - _SCOPE_RESULT_MAX, - _SCOPE_RESULT_INVALID = -1 -} ScopeResult; - -struct Scope { - Unit meta; - - CGroupContext cgroup_context; - KillContext kill_context; - - ScopeState state, deserialized_state; - ScopeResult result; - - usec_t timeout_stop_usec; - - char *controller; - - sd_event_source *timer_event_source; -}; - -extern const UnitVTable scope_vtable; - -int scope_abandon(Scope *s); - -const char* scope_result_to_string(ScopeResult i) _const_; -ScopeResult scope_result_from_string(const char *s) _pure_; diff --git a/src/core/selinux-access.c b/src/core/selinux-access.c deleted file mode 100644 index cc287d602d..0000000000 --- a/src/core/selinux-access.c +++ /dev/null @@ -1,283 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 Dan Walsh - - 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 "selinux-access.h" - -#ifdef HAVE_SELINUX - -#include -#include -#include -#include -#ifdef HAVE_AUDIT -#include -#endif - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "audit-fd.h" -#include "bus-util.h" -#include "log.h" -#include "path-util.h" -#include "selinux-util.h" -#include "stdio-util.h" -#include "strv.h" -#include "util.h" - -static bool initialized = false; - -struct audit_info { - sd_bus_creds *creds; - const char *path; - const char *cmdline; -}; - -/* - Any time an access gets denied this callback will be called - with the audit data. We then need to just copy the audit data into the msgbuf. -*/ -static int audit_callback( - void *auditdata, - security_class_t cls, - char *msgbuf, - size_t msgbufsize) { - - const struct audit_info *audit = auditdata; - uid_t uid = 0, login_uid = 0; - gid_t gid = 0; - char login_uid_buf[DECIMAL_STR_MAX(uid_t) + 1] = "n/a"; - char uid_buf[DECIMAL_STR_MAX(uid_t) + 1] = "n/a"; - char gid_buf[DECIMAL_STR_MAX(gid_t) + 1] = "n/a"; - - if (sd_bus_creds_get_audit_login_uid(audit->creds, &login_uid) >= 0) - xsprintf(login_uid_buf, UID_FMT, login_uid); - if (sd_bus_creds_get_euid(audit->creds, &uid) >= 0) - xsprintf(uid_buf, UID_FMT, uid); - if (sd_bus_creds_get_egid(audit->creds, &gid) >= 0) - xsprintf(gid_buf, GID_FMT, gid); - - snprintf(msgbuf, msgbufsize, - "auid=%s uid=%s gid=%s%s%s%s%s%s%s", - login_uid_buf, uid_buf, gid_buf, - audit->path ? " path=\"" : "", strempty(audit->path), audit->path ? "\"" : "", - audit->cmdline ? " cmdline=\"" : "", strempty(audit->cmdline), audit->cmdline ? "\"" : ""); - - return 0; -} - -static int callback_type_to_priority(int type) { - switch(type) { - - case SELINUX_ERROR: - return LOG_ERR; - - case SELINUX_WARNING: - return LOG_WARNING; - - case SELINUX_INFO: - return LOG_INFO; - - case SELINUX_AVC: - default: - return LOG_NOTICE; - } -} - -/* - libselinux uses this callback when access gets denied or other - events happen. If audit is turned on, messages will be reported - using audit netlink, otherwise they will be logged using the usual - channels. - - Code copied from dbus and modified. -*/ -_printf_(2, 3) static int log_callback(int type, const char *fmt, ...) { - va_list ap; - const char *fmt2; - -#ifdef HAVE_AUDIT - int fd; - - fd = get_audit_fd(); - - if (fd >= 0) { - _cleanup_free_ char *buf = NULL; - int r; - - va_start(ap, fmt); - r = vasprintf(&buf, fmt, ap); - va_end(ap); - - if (r >= 0) { - audit_log_user_avc_message(fd, AUDIT_USER_AVC, buf, NULL, NULL, NULL, 0); - return 0; - } - } -#endif - - fmt2 = strjoina("selinux: ", fmt); - - va_start(ap, fmt); - log_internalv(LOG_AUTH | callback_type_to_priority(type), 0, __FILE__, __LINE__, __FUNCTION__, fmt2, ap); - va_end(ap); - - return 0; -} - -static int access_init(sd_bus_error *error) { - - if (!mac_selinux_use()) - return 0; - - if (initialized) - return 1; - - if (avc_open(NULL, 0) != 0) { - int enforce, saved_errno = errno; - - enforce = security_getenforce(); - log_full_errno(enforce != 0 ? LOG_ERR : LOG_WARNING, saved_errno, "Failed to open the SELinux AVC: %m"); - - /* If enforcement isn't on, then let's suppress this - * error, and just don't do any AVC checks. The - * warning we printed is hence all the admin will - * see. */ - if (enforce == 0) - return 0; - - /* Return an access denied error, if we couldn't load - * the AVC but enforcing mode was on, or we couldn't - * determine whether it is one. */ - return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to open the SELinux AVC: %s", strerror(saved_errno)); - } - - selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) audit_callback); - selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) log_callback); - - initialized = true; - return 1; -} - -/* - This function communicates with the kernel to check whether or not it should - allow the access. - If the machine is in permissive mode it will return ok. Audit messages will - still be generated if the access would be denied in enforcing mode. -*/ -int mac_selinux_generic_access_check( - sd_bus_message *message, - const char *path, - const char *permission, - sd_bus_error *error) { - - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - const char *tclass = NULL, *scon = NULL; - struct audit_info audit_info = {}; - _cleanup_free_ char *cl = NULL; - security_context_t fcon = NULL; - char **cmdline = NULL; - int r = 0; - - assert(message); - assert(permission); - assert(error); - - r = access_init(error); - if (r <= 0) - return r; - - r = sd_bus_query_sender_creds( - message, - SD_BUS_CREDS_PID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID| - SD_BUS_CREDS_CMDLINE|SD_BUS_CREDS_AUDIT_LOGIN_UID| - SD_BUS_CREDS_SELINUX_CONTEXT| - SD_BUS_CREDS_AUGMENT /* get more bits from /proc */, - &creds); - if (r < 0) - goto finish; - - /* The SELinux context is something we really should have - * gotten directly from the message or sender, and not be an - * augmented field. If it was augmented we cannot use it for - * authorization, since this is racy and vulnerable. Let's add - * an extra check, just in case, even though this really - * shouldn't be possible. */ - assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_SELINUX_CONTEXT) == 0, -EPERM); - - r = sd_bus_creds_get_selinux_context(creds, &scon); - if (r < 0) - goto finish; - - if (path) { - /* Get the file context of the unit file */ - - r = getfilecon_raw(path, &fcon); - if (r < 0) { - r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get file context on %s.", path); - goto finish; - } - - tclass = "service"; - } else { - r = getcon_raw(&fcon); - if (r < 0) { - r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get current context."); - goto finish; - } - - tclass = "system"; - } - - sd_bus_creds_get_cmdline(creds, &cmdline); - cl = strv_join(cmdline, " "); - - audit_info.creds = creds; - audit_info.path = path; - audit_info.cmdline = cl; - - r = selinux_check_access(scon, fcon, tclass, permission, &audit_info); - if (r < 0) - r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "SELinux policy denies access."); - - log_debug("SELinux access check scon=%s tcon=%s tclass=%s perm=%s path=%s cmdline=%s: %i", scon, fcon, tclass, permission, path, cl, r); - -finish: - freecon(fcon); - - if (r < 0 && security_getenforce() != 1) { - sd_bus_error_free(error); - r = 0; - } - - return r; -} - -#else - -int mac_selinux_generic_access_check( - sd_bus_message *message, - const char *path, - const char *permission, - sd_bus_error *error) { - - return 0; -} - -#endif diff --git a/src/core/selinux-access.h b/src/core/selinux-access.h deleted file mode 100644 index 8f1f058a32..0000000000 --- a/src/core/selinux-access.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 Dan Walsh - - 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 "sd-bus.h" - -#include "bus-util.h" -#include "manager.h" - -int mac_selinux_generic_access_check(sd_bus_message *message, const char *path, const char *permission, sd_bus_error *error); - -#ifdef HAVE_SELINUX - -#define mac_selinux_access_check(message, permission, error) \ - mac_selinux_generic_access_check((message), NULL, (permission), (error)) - -#define mac_selinux_unit_access_check(unit, message, permission, error) \ - ({ \ - Unit *_unit = (unit); \ - mac_selinux_generic_access_check((message), _unit->source_path ?: _unit->fragment_path, (permission), (error)); \ - }) - -#else - -#define mac_selinux_access_check(message, permission, error) 0 -#define mac_selinux_unit_access_check(unit, message, permission, error) 0 - -#endif diff --git a/src/core/selinux-setup.c b/src/core/selinux-setup.c deleted file mode 100644 index 4072df58e6..0000000000 --- a/src/core/selinux-setup.c +++ /dev/null @@ -1,121 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include - -#ifdef HAVE_SELINUX -#include -#endif - -#include "log.h" -#include "macro.h" -#include "selinux-setup.h" -#include "selinux-util.h" -#include "string-util.h" -#include "util.h" - -#ifdef HAVE_SELINUX -_printf_(2,3) -static int null_log(int type, const char *fmt, ...) { - return 0; -} -#endif - -int mac_selinux_setup(bool *loaded_policy) { - -#ifdef HAVE_SELINUX - int enforce = 0; - usec_t before_load, after_load; - security_context_t con; - int r; - union selinux_callback cb; - bool initialized = false; - - assert(loaded_policy); - - /* Turn off all of SELinux' own logging, we want to do that */ - cb.func_log = null_log; - selinux_set_callback(SELINUX_CB_LOG, cb); - - /* Don't load policy in the initrd if we don't appear to have - * it. For the real root, we check below if we've already - * loaded policy, and return gracefully. - */ - if (in_initrd() && access(selinux_path(), F_OK) < 0) - return 0; - - /* Already initialized by somebody else? */ - r = getcon_raw(&con); - if (r == 0) { - initialized = !streq(con, "kernel"); - freecon(con); - } - - /* Make sure we have no fds open while loading the policy and - * transitioning */ - log_close(); - - /* Now load the policy */ - before_load = now(CLOCK_MONOTONIC); - r = selinux_init_load_policy(&enforce); - if (r == 0) { - _cleanup_(mac_selinux_freep) char *label = NULL; - char timespan[FORMAT_TIMESPAN_MAX]; - - mac_selinux_retest(); - - /* Transition to the new context */ - r = mac_selinux_get_create_label_from_exe(SYSTEMD_BINARY_PATH, &label); - if (r < 0 || !label) { - log_open(); - log_error("Failed to compute init label, ignoring."); - } else { - r = setcon_raw(label); - - log_open(); - if (r < 0) - log_error("Failed to transition into init label '%s', ignoring.", label); - } - - after_load = now(CLOCK_MONOTONIC); - - log_info("Successfully loaded SELinux policy in %s.", - format_timespan(timespan, sizeof(timespan), after_load - before_load, 0)); - - *loaded_policy = true; - - } else { - log_open(); - - if (enforce > 0) { - if (!initialized) { - log_emergency("Failed to load SELinux policy."); - return -EIO; - } - - log_warning("Failed to load new SELinux policy. Continuing with old policy."); - } else - log_debug("Unable to load SELinux policy. Ignoring."); - } -#endif - - return 0; -} diff --git a/src/core/selinux-setup.h b/src/core/selinux-setup.h deleted file mode 100644 index 7b613249b0..0000000000 --- a/src/core/selinux-setup.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 - -int mac_selinux_setup(bool *loaded_policy); diff --git a/src/core/service.c b/src/core/service.c deleted file mode 100644 index 7ebabca5d6..0000000000 --- a/src/core/service.c +++ /dev/null @@ -1,3353 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include - -#include "alloc-util.h" -#include "async.h" -#include "bus-error.h" -#include "bus-kernel.h" -#include "bus-util.h" -#include "dbus-service.h" -#include "def.h" -#include "env-util.h" -#include "escape.h" -#include "exit-status.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "load-dropin.h" -#include "load-fragment.h" -#include "log.h" -#include "manager.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "service.h" -#include "signal-util.h" -#include "special.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "unit-printf.h" -#include "unit.h" -#include "utf8.h" -#include "util.h" - -static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = { - [SERVICE_DEAD] = UNIT_INACTIVE, - [SERVICE_START_PRE] = UNIT_ACTIVATING, - [SERVICE_START] = UNIT_ACTIVATING, - [SERVICE_START_POST] = UNIT_ACTIVATING, - [SERVICE_RUNNING] = UNIT_ACTIVE, - [SERVICE_EXITED] = UNIT_ACTIVE, - [SERVICE_RELOAD] = UNIT_RELOADING, - [SERVICE_STOP] = UNIT_DEACTIVATING, - [SERVICE_STOP_SIGABRT] = UNIT_DEACTIVATING, - [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING, - [SERVICE_STOP_SIGKILL] = UNIT_DEACTIVATING, - [SERVICE_STOP_POST] = UNIT_DEACTIVATING, - [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING, - [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING, - [SERVICE_FAILED] = UNIT_FAILED, - [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING -}; - -/* For Type=idle we never want to delay any other jobs, hence we - * consider idle jobs active as soon as we start working on them */ -static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] = { - [SERVICE_DEAD] = UNIT_INACTIVE, - [SERVICE_START_PRE] = UNIT_ACTIVE, - [SERVICE_START] = UNIT_ACTIVE, - [SERVICE_START_POST] = UNIT_ACTIVE, - [SERVICE_RUNNING] = UNIT_ACTIVE, - [SERVICE_EXITED] = UNIT_ACTIVE, - [SERVICE_RELOAD] = UNIT_RELOADING, - [SERVICE_STOP] = UNIT_DEACTIVATING, - [SERVICE_STOP_SIGABRT] = UNIT_DEACTIVATING, - [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING, - [SERVICE_STOP_SIGKILL] = UNIT_DEACTIVATING, - [SERVICE_STOP_POST] = UNIT_DEACTIVATING, - [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING, - [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING, - [SERVICE_FAILED] = UNIT_FAILED, - [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING -}; - -static int service_dispatch_io(sd_event_source *source, int fd, uint32_t events, void *userdata); -static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); -static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void *userdata); - -static void service_enter_signal(Service *s, ServiceState state, ServiceResult f); -static void service_enter_reload_by_notify(Service *s); - -static void service_init(Unit *u) { - Service *s = SERVICE(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - s->timeout_start_usec = u->manager->default_timeout_start_usec; - s->timeout_stop_usec = u->manager->default_timeout_stop_usec; - s->restart_usec = u->manager->default_restart_usec; - s->runtime_max_usec = USEC_INFINITY; - s->type = _SERVICE_TYPE_INVALID; - s->socket_fd = -1; - s->stdin_fd = s->stdout_fd = s->stderr_fd = -1; - s->guess_main_pid = true; - - s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; -} - -static void service_unwatch_control_pid(Service *s) { - assert(s); - - if (s->control_pid <= 0) - return; - - unit_unwatch_pid(UNIT(s), s->control_pid); - s->control_pid = 0; -} - -static void service_unwatch_main_pid(Service *s) { - assert(s); - - if (s->main_pid <= 0) - return; - - unit_unwatch_pid(UNIT(s), s->main_pid); - s->main_pid = 0; -} - -static void service_unwatch_pid_file(Service *s) { - if (!s->pid_file_pathspec) - return; - - log_unit_debug(UNIT(s), "Stopping watch for PID file %s", s->pid_file_pathspec->path); - path_spec_unwatch(s->pid_file_pathspec); - path_spec_done(s->pid_file_pathspec); - s->pid_file_pathspec = mfree(s->pid_file_pathspec); -} - -static int service_set_main_pid(Service *s, pid_t pid) { - pid_t ppid; - - assert(s); - - if (pid <= 1) - return -EINVAL; - - if (pid == getpid()) - return -EINVAL; - - if (s->main_pid == pid && s->main_pid_known) - return 0; - - if (s->main_pid != pid) { - service_unwatch_main_pid(s); - exec_status_start(&s->main_exec_status, pid); - } - - s->main_pid = pid; - s->main_pid_known = true; - - if (get_process_ppid(pid, &ppid) >= 0 && ppid != getpid()) { - log_unit_warning(UNIT(s), "Supervising process "PID_FMT" which is not our child. We'll most likely not notice when it exits.", pid); - s->main_pid_alien = true; - } else - s->main_pid_alien = false; - - return 0; -} - -void service_close_socket_fd(Service *s) { - assert(s); - - /* Undo the effect of service_set_socket_fd(). */ - - s->socket_fd = asynchronous_close(s->socket_fd); - - if (UNIT_ISSET(s->accept_socket)) { - socket_connection_unref(SOCKET(UNIT_DEREF(s->accept_socket))); - unit_ref_unset(&s->accept_socket); - } -} - -static void service_stop_watchdog(Service *s) { - assert(s); - - s->watchdog_event_source = sd_event_source_unref(s->watchdog_event_source); - s->watchdog_timestamp = DUAL_TIMESTAMP_NULL; -} - -static void service_start_watchdog(Service *s) { - int r; - - assert(s); - - if (s->watchdog_usec <= 0) - return; - - if (s->watchdog_event_source) { - r = sd_event_source_set_time(s->watchdog_event_source, usec_add(s->watchdog_timestamp.monotonic, s->watchdog_usec)); - if (r < 0) { - log_unit_warning_errno(UNIT(s), r, "Failed to reset watchdog timer: %m"); - return; - } - - r = sd_event_source_set_enabled(s->watchdog_event_source, SD_EVENT_ONESHOT); - } else { - r = sd_event_add_time( - UNIT(s)->manager->event, - &s->watchdog_event_source, - CLOCK_MONOTONIC, - usec_add(s->watchdog_timestamp.monotonic, s->watchdog_usec), 0, - service_dispatch_watchdog, s); - if (r < 0) { - log_unit_warning_errno(UNIT(s), r, "Failed to add watchdog timer: %m"); - return; - } - - (void) sd_event_source_set_description(s->watchdog_event_source, "service-watchdog"); - - /* Let's process everything else which might be a sign - * of living before we consider a service died. */ - r = sd_event_source_set_priority(s->watchdog_event_source, SD_EVENT_PRIORITY_IDLE); - } - - if (r < 0) - log_unit_warning_errno(UNIT(s), r, "Failed to install watchdog timer: %m"); -} - -static void service_reset_watchdog(Service *s) { - assert(s); - - dual_timestamp_get(&s->watchdog_timestamp); - service_start_watchdog(s); -} - -static void service_fd_store_unlink(ServiceFDStore *fs) { - - if (!fs) - return; - - if (fs->service) { - assert(fs->service->n_fd_store > 0); - LIST_REMOVE(fd_store, fs->service->fd_store, fs); - fs->service->n_fd_store--; - } - - if (fs->event_source) { - sd_event_source_set_enabled(fs->event_source, SD_EVENT_OFF); - sd_event_source_unref(fs->event_source); - } - - free(fs->fdname); - safe_close(fs->fd); - free(fs); -} - -static void service_release_resources(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - if (!s->fd_store && s->stdin_fd < 0 && s->stdout_fd < 0 && s->stderr_fd < 0) - return; - - log_unit_debug(u, "Releasing all resources."); - - s->stdin_fd = safe_close(s->stdin_fd); - s->stdout_fd = safe_close(s->stdout_fd); - s->stderr_fd = safe_close(s->stderr_fd); - - while (s->fd_store) - service_fd_store_unlink(s->fd_store); - - assert(s->n_fd_store == 0); -} - -static void service_done(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - s->pid_file = mfree(s->pid_file); - s->status_text = mfree(s->status_text); - - s->exec_runtime = exec_runtime_unref(s->exec_runtime); - exec_command_free_array(s->exec_command, _SERVICE_EXEC_COMMAND_MAX); - s->control_command = NULL; - s->main_command = NULL; - - exit_status_set_free(&s->restart_prevent_status); - exit_status_set_free(&s->restart_force_status); - exit_status_set_free(&s->success_status); - - /* This will leak a process, but at least no memory or any of - * our resources */ - service_unwatch_main_pid(s); - service_unwatch_control_pid(s); - service_unwatch_pid_file(s); - - if (s->bus_name) { - unit_unwatch_bus_name(u, s->bus_name); - s->bus_name = mfree(s->bus_name); - } - - s->bus_name_owner = mfree(s->bus_name_owner); - - service_close_socket_fd(s); - - unit_ref_unset(&s->accept_socket); - - service_stop_watchdog(s); - - s->timer_event_source = sd_event_source_unref(s->timer_event_source); - - service_release_resources(u); -} - -static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *userdata) { - ServiceFDStore *fs = userdata; - - assert(e); - assert(fs); - - /* If we get either EPOLLHUP or EPOLLERR, it's time to remove this entry from the fd store */ - service_fd_store_unlink(fs); - return 0; -} - -static int service_add_fd_store(Service *s, int fd, const char *name) { - ServiceFDStore *fs; - int r; - - assert(s); - assert(fd >= 0); - - if (s->n_fd_store >= s->n_fd_store_max) - return 0; - - LIST_FOREACH(fd_store, fs, s->fd_store) { - r = same_fd(fs->fd, fd); - if (r < 0) - return r; - if (r > 0) { - /* Already included */ - safe_close(fd); - return 1; - } - } - - fs = new0(ServiceFDStore, 1); - if (!fs) - return -ENOMEM; - - fs->fd = fd; - fs->service = s; - fs->fdname = strdup(name ?: "stored"); - if (!fs->fdname) { - free(fs); - return -ENOMEM; - } - - r = sd_event_add_io(UNIT(s)->manager->event, &fs->event_source, fd, 0, on_fd_store_io, fs); - if (r < 0) { - free(fs->fdname); - free(fs); - return r; - } - - (void) sd_event_source_set_description(fs->event_source, "service-fd-store"); - - LIST_PREPEND(fd_store, s->fd_store, fs); - s->n_fd_store++; - - return 1; -} - -static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name) { - int r; - - assert(s); - - if (fdset_size(fds) <= 0) - return 0; - - while (s->n_fd_store < s->n_fd_store_max) { - _cleanup_close_ int fd = -1; - - fd = fdset_steal_first(fds); - if (fd < 0) - break; - - r = service_add_fd_store(s, fd, name); - if (r < 0) - return log_unit_error_errno(UNIT(s), r, "Couldn't add fd to fd store: %m"); - if (r > 0) { - log_unit_debug(UNIT(s), "Added fd to fd store."); - fd = -1; - } - } - - if (fdset_size(fds) > 0) - log_unit_warning(UNIT(s), "Tried to store more fds than FileDescriptorStoreMax=%u allows, closing remaining.", s->n_fd_store_max); - - return 0; -} - -static int service_arm_timer(Service *s, usec_t usec) { - int r; - - assert(s); - - if (s->timer_event_source) { - r = sd_event_source_set_time(s->timer_event_source, usec); - if (r < 0) - return r; - - return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); - } - - if (usec == USEC_INFINITY) - return 0; - - r = sd_event_add_time( - UNIT(s)->manager->event, - &s->timer_event_source, - CLOCK_MONOTONIC, - usec, 0, - service_dispatch_timer, s); - if (r < 0) - return r; - - (void) sd_event_source_set_description(s->timer_event_source, "service-timer"); - - return 0; -} - -static int service_verify(Service *s) { - assert(s); - - if (UNIT(s)->load_state != UNIT_LOADED) - return 0; - - if (!s->exec_command[SERVICE_EXEC_START] && !s->exec_command[SERVICE_EXEC_STOP]) { - log_unit_error(UNIT(s), "Service lacks both ExecStart= and ExecStop= setting. Refusing."); - return -EINVAL; - } - - if (s->type != SERVICE_ONESHOT && !s->exec_command[SERVICE_EXEC_START]) { - log_unit_error(UNIT(s), "Service has no ExecStart= setting, which is only allowed for Type=oneshot services. Refusing."); - return -EINVAL; - } - - if (!s->remain_after_exit && !s->exec_command[SERVICE_EXEC_START]) { - log_unit_error(UNIT(s), "Service has no ExecStart= setting, which is only allowed for RemainAfterExit=yes services. Refusing."); - return -EINVAL; - } - - if (s->type != SERVICE_ONESHOT && s->exec_command[SERVICE_EXEC_START]->command_next) { - log_unit_error(UNIT(s), "Service has more than one ExecStart= setting, which is only allowed for Type=oneshot services. Refusing."); - return -EINVAL; - } - - if (s->type == SERVICE_ONESHOT && s->restart != SERVICE_RESTART_NO) { - log_unit_error(UNIT(s), "Service has Restart= setting other than no, which isn't allowed for Type=oneshot services. Refusing."); - return -EINVAL; - } - - if (s->type == SERVICE_ONESHOT && !exit_status_set_is_empty(&s->restart_force_status)) { - log_unit_error(UNIT(s), "Service has RestartForceStatus= set, which isn't allowed for Type=oneshot services. Refusing."); - return -EINVAL; - } - - if (s->type == SERVICE_DBUS && !s->bus_name) { - log_unit_error(UNIT(s), "Service is of type D-Bus but no D-Bus service name has been specified. Refusing."); - return -EINVAL; - } - - if (s->bus_name && s->type != SERVICE_DBUS) - log_unit_warning(UNIT(s), "Service has a D-Bus service name specified, but is not of type dbus. Ignoring."); - - if (s->exec_context.pam_name && !(s->kill_context.kill_mode == KILL_CONTROL_GROUP || s->kill_context.kill_mode == KILL_MIXED)) { - log_unit_error(UNIT(s), "Service has PAM enabled. Kill mode must be set to 'control-group' or 'mixed'. Refusing."); - return -EINVAL; - } - - if (s->usb_function_descriptors && !s->usb_function_strings) - log_unit_warning(UNIT(s), "Service has USBFunctionDescriptors= setting, but no USBFunctionStrings=. Ignoring."); - - if (!s->usb_function_descriptors && s->usb_function_strings) - log_unit_warning(UNIT(s), "Service has USBFunctionStrings= setting, but no USBFunctionDescriptors=. Ignoring."); - - if (s->runtime_max_usec != USEC_INFINITY && s->type == SERVICE_ONESHOT) - log_unit_warning(UNIT(s), "MaxRuntimeSec= has no effect in combination with Type=oneshot. Ignoring."); - - return 0; -} - -static int service_add_default_dependencies(Service *s) { - int r; - - assert(s); - - if (!UNIT(s)->default_dependencies) - return 0; - - /* Add a number of automatic dependencies useful for the - * majority of services. */ - - if (MANAGER_IS_SYSTEM(UNIT(s)->manager)) { - /* First, pull in the really early boot stuff, and - * require it, so that we fail if we can't acquire - * it. */ - - r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); - if (r < 0) - return r; - } else { - - /* In the --user instance there's no sysinit.target, - * in that case require basic.target instead. */ - - r = unit_add_dependency_by_name(UNIT(s), UNIT_REQUIRES, SPECIAL_BASIC_TARGET, NULL, true); - if (r < 0) - return r; - } - - /* Second, if the rest of the base system is in the same - * transaction, order us after it, but do not pull it in or - * even require it. */ - r = unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_BASIC_TARGET, NULL, true); - if (r < 0) - return r; - - /* Third, add us in for normal shutdown. */ - return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); -} - -static void service_fix_output(Service *s) { - assert(s); - - /* If nothing has been explicitly configured, patch default - * output in. If input is socket/tty we avoid this however, - * since in that case we want output to default to the same - * place as we read input from. */ - - if (s->exec_context.std_error == EXEC_OUTPUT_INHERIT && - s->exec_context.std_output == EXEC_OUTPUT_INHERIT && - s->exec_context.std_input == EXEC_INPUT_NULL) - s->exec_context.std_error = UNIT(s)->manager->default_std_error; - - if (s->exec_context.std_output == EXEC_OUTPUT_INHERIT && - s->exec_context.std_input == EXEC_INPUT_NULL) - s->exec_context.std_output = UNIT(s)->manager->default_std_output; -} - -static int service_setup_bus_name(Service *s) { - int r; - - assert(s); - - if (!s->bus_name) - return 0; - - if (is_kdbus_available()) { - const char *n; - - n = strjoina(s->bus_name, ".busname"); - r = unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, n, NULL, true); - if (r < 0) - return log_unit_error_errno(UNIT(s), r, "Failed to add dependency to .busname unit: %m"); - - } else { - /* If kdbus is not available, we know the dbus socket is required, hence pull it in, and require it */ - r = unit_add_dependency_by_name(UNIT(s), UNIT_REQUIRES, SPECIAL_DBUS_SOCKET, NULL, true); - if (r < 0) - return log_unit_error_errno(UNIT(s), r, "Failed to add dependency on " SPECIAL_DBUS_SOCKET ": %m"); - } - - /* Regardless if kdbus is used or not, we always want to be ordered against dbus.socket if both are in the transaction. */ - r = unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_DBUS_SOCKET, NULL, true); - if (r < 0) - return log_unit_error_errno(UNIT(s), r, "Failed to add dependency on " SPECIAL_DBUS_SOCKET ": %m"); - - r = unit_watch_bus_name(UNIT(s), s->bus_name); - if (r == -EEXIST) - return log_unit_error_errno(UNIT(s), r, "Two services allocated for the same bus name %s, refusing operation.", s->bus_name); - if (r < 0) - return log_unit_error_errno(UNIT(s), r, "Cannot watch bus name %s: %m", s->bus_name); - - return 0; -} - -static int service_add_extras(Service *s) { - int r; - - assert(s); - - if (s->type == _SERVICE_TYPE_INVALID) { - /* Figure out a type automatically */ - if (s->bus_name) - s->type = SERVICE_DBUS; - else if (s->exec_command[SERVICE_EXEC_START]) - s->type = SERVICE_SIMPLE; - else - s->type = SERVICE_ONESHOT; - } - - /* Oneshot services have disabled start timeout by default */ - if (s->type == SERVICE_ONESHOT && !s->start_timeout_defined) - s->timeout_start_usec = USEC_INFINITY; - - service_fix_output(s); - - r = unit_patch_contexts(UNIT(s)); - if (r < 0) - return r; - - r = unit_add_exec_dependencies(UNIT(s), &s->exec_context); - if (r < 0) - return r; - - r = unit_set_default_slice(UNIT(s)); - if (r < 0) - return r; - - if (s->type == SERVICE_NOTIFY && s->notify_access == NOTIFY_NONE) - s->notify_access = NOTIFY_MAIN; - - if (s->watchdog_usec > 0 && s->notify_access == NOTIFY_NONE) - s->notify_access = NOTIFY_MAIN; - - r = service_add_default_dependencies(s); - if (r < 0) - return r; - - r = service_setup_bus_name(s); - if (r < 0) - return r; - - return 0; -} - -static int service_load(Unit *u) { - Service *s = SERVICE(u); - int r; - - assert(s); - - /* Load a .service file */ - r = unit_load_fragment(u); - if (r < 0) - return r; - - /* Still nothing found? Then let's give up */ - if (u->load_state == UNIT_STUB) - return -ENOENT; - - /* This is a new unit? Then let's add in some extras */ - if (u->load_state == UNIT_LOADED) { - - /* We were able to load something, then let's add in - * the dropin directories. */ - r = unit_load_dropin(u); - if (r < 0) - return r; - - /* This is a new unit? Then let's add in some - * extras */ - r = service_add_extras(s); - if (r < 0) - return r; - } - - return service_verify(s); -} - -static void service_dump(Unit *u, FILE *f, const char *prefix) { - ServiceExecCommand c; - Service *s = SERVICE(u); - const char *prefix2; - - assert(s); - - prefix = strempty(prefix); - prefix2 = strjoina(prefix, "\t"); - - fprintf(f, - "%sService State: %s\n" - "%sResult: %s\n" - "%sReload Result: %s\n" - "%sPermissionsStartOnly: %s\n" - "%sRootDirectoryStartOnly: %s\n" - "%sRemainAfterExit: %s\n" - "%sGuessMainPID: %s\n" - "%sType: %s\n" - "%sRestart: %s\n" - "%sNotifyAccess: %s\n" - "%sNotifyState: %s\n", - prefix, service_state_to_string(s->state), - prefix, service_result_to_string(s->result), - prefix, service_result_to_string(s->reload_result), - prefix, yes_no(s->permissions_start_only), - prefix, yes_no(s->root_directory_start_only), - prefix, yes_no(s->remain_after_exit), - prefix, yes_no(s->guess_main_pid), - prefix, service_type_to_string(s->type), - prefix, service_restart_to_string(s->restart), - prefix, notify_access_to_string(s->notify_access), - prefix, notify_state_to_string(s->notify_state)); - - if (s->control_pid > 0) - fprintf(f, - "%sControl PID: "PID_FMT"\n", - prefix, s->control_pid); - - if (s->main_pid > 0) - fprintf(f, - "%sMain PID: "PID_FMT"\n" - "%sMain PID Known: %s\n" - "%sMain PID Alien: %s\n", - prefix, s->main_pid, - prefix, yes_no(s->main_pid_known), - prefix, yes_no(s->main_pid_alien)); - - if (s->pid_file) - fprintf(f, - "%sPIDFile: %s\n", - prefix, s->pid_file); - - if (s->bus_name) - fprintf(f, - "%sBusName: %s\n" - "%sBus Name Good: %s\n", - prefix, s->bus_name, - prefix, yes_no(s->bus_name_good)); - - kill_context_dump(&s->kill_context, f, prefix); - exec_context_dump(&s->exec_context, f, prefix); - - for (c = 0; c < _SERVICE_EXEC_COMMAND_MAX; c++) { - - if (!s->exec_command[c]) - continue; - - fprintf(f, "%s-> %s:\n", - prefix, service_exec_command_to_string(c)); - - exec_command_dump_list(s->exec_command[c], f, prefix2); - } - - if (s->status_text) - fprintf(f, "%sStatus Text: %s\n", - prefix, s->status_text); - - if (s->n_fd_store_max > 0) - fprintf(f, - "%sFile Descriptor Store Max: %u\n" - "%sFile Descriptor Store Current: %u\n", - prefix, s->n_fd_store_max, - prefix, s->n_fd_store); -} - -static int service_load_pid_file(Service *s, bool may_warn) { - _cleanup_free_ char *k = NULL; - int r; - pid_t pid; - - assert(s); - - if (!s->pid_file) - return -ENOENT; - - r = read_one_line_file(s->pid_file, &k); - if (r < 0) { - if (may_warn) - log_unit_info_errno(UNIT(s), r, "PID file %s not readable (yet?) after %s: %m", s->pid_file, service_state_to_string(s->state)); - return r; - } - - r = parse_pid(k, &pid); - if (r < 0) { - if (may_warn) - log_unit_info_errno(UNIT(s), r, "Failed to read PID from file %s: %m", s->pid_file); - return r; - } - - if (!pid_is_alive(pid)) { - if (may_warn) - log_unit_info(UNIT(s), "PID "PID_FMT" read from file %s does not exist or is a zombie.", pid, s->pid_file); - return -ESRCH; - } - - if (s->main_pid_known) { - if (pid == s->main_pid) - return 0; - - log_unit_debug(UNIT(s), "Main PID changing: "PID_FMT" -> "PID_FMT, s->main_pid, pid); - - service_unwatch_main_pid(s); - s->main_pid_known = false; - } else - log_unit_debug(UNIT(s), "Main PID loaded: "PID_FMT, pid); - - r = service_set_main_pid(s, pid); - if (r < 0) - return r; - - r = unit_watch_pid(UNIT(s), pid); - if (r < 0) { - /* FIXME: we need to do something here */ - log_unit_warning_errno(UNIT(s), r, "Failed to watch PID "PID_FMT" for service: %m", pid); - return r; - } - - return 0; -} - -static void service_search_main_pid(Service *s) { - pid_t pid = 0; - int r; - - assert(s); - - /* If we know it anyway, don't ever fallback to unreliable - * heuristics */ - if (s->main_pid_known) - return; - - if (!s->guess_main_pid) - return; - - assert(s->main_pid <= 0); - - if (unit_search_main_pid(UNIT(s), &pid) < 0) - return; - - log_unit_debug(UNIT(s), "Main PID guessed: "PID_FMT, pid); - if (service_set_main_pid(s, pid) < 0) - return; - - r = unit_watch_pid(UNIT(s), pid); - if (r < 0) - /* FIXME: we need to do something here */ - log_unit_warning_errno(UNIT(s), r, "Failed to watch PID "PID_FMT" from: %m", pid); -} - -static void service_set_state(Service *s, ServiceState state) { - ServiceState old_state; - const UnitActiveState *table; - - assert(s); - - table = s->type == SERVICE_IDLE ? state_translation_table_idle : state_translation_table; - - old_state = s->state; - s->state = state; - - service_unwatch_pid_file(s); - - if (!IN_SET(state, - SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, - SERVICE_RUNNING, - SERVICE_RELOAD, - SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, - SERVICE_AUTO_RESTART)) - s->timer_event_source = sd_event_source_unref(s->timer_event_source); - - if (!IN_SET(state, - SERVICE_START, SERVICE_START_POST, - SERVICE_RUNNING, SERVICE_RELOAD, - SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) { - service_unwatch_main_pid(s); - s->main_command = NULL; - } - - if (!IN_SET(state, - SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, - SERVICE_RELOAD, - SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) { - service_unwatch_control_pid(s); - s->control_command = NULL; - s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; - } - - if (IN_SET(state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART)) - unit_unwatch_all_pids(UNIT(s)); - - if (!IN_SET(state, - SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, - SERVICE_RUNNING, SERVICE_RELOAD, - SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL) && - !(state == SERVICE_DEAD && UNIT(s)->job)) - service_close_socket_fd(s); - - if (!IN_SET(state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) - service_stop_watchdog(s); - - /* For the inactive states unit_notify() will trim the cgroup, - * but for exit we have to do that ourselves... */ - if (state == SERVICE_EXITED && !MANAGER_IS_RELOADING(UNIT(s)->manager)) - unit_prune_cgroup(UNIT(s)); - - /* For remain_after_exit services, let's see if we can "release" the - * hold on the console, since unit_notify() only does that in case of - * change of state */ - if (state == SERVICE_EXITED && - s->remain_after_exit && - UNIT(s)->manager->n_on_console > 0) { - - ExecContext *ec; - - ec = unit_get_exec_context(UNIT(s)); - if (ec && exec_context_may_touch_console(ec)) { - Manager *m = UNIT(s)->manager; - - m->n_on_console--; - if (m->n_on_console == 0) - /* unset no_console_output flag, since the console is free */ - m->no_console_output = false; - } - } - - if (old_state != state) - log_unit_debug(UNIT(s), "Changed %s -> %s", service_state_to_string(old_state), service_state_to_string(state)); - - unit_notify(UNIT(s), table[old_state], table[state], s->reload_result == SERVICE_SUCCESS); -} - -static usec_t service_coldplug_timeout(Service *s) { - assert(s); - - switch (s->deserialized_state) { - - case SERVICE_START_PRE: - case SERVICE_START: - case SERVICE_START_POST: - case SERVICE_RELOAD: - return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_start_usec); - - case SERVICE_RUNNING: - return usec_add(UNIT(s)->active_enter_timestamp.monotonic, s->runtime_max_usec); - - case SERVICE_STOP: - case SERVICE_STOP_SIGABRT: - case SERVICE_STOP_SIGTERM: - case SERVICE_STOP_SIGKILL: - case SERVICE_STOP_POST: - case SERVICE_FINAL_SIGTERM: - case SERVICE_FINAL_SIGKILL: - return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_stop_usec); - - case SERVICE_AUTO_RESTART: - return usec_add(UNIT(s)->inactive_enter_timestamp.monotonic, s->restart_usec); - - default: - return USEC_INFINITY; - } -} - -static int service_coldplug(Unit *u) { - Service *s = SERVICE(u); - int r; - - assert(s); - assert(s->state == SERVICE_DEAD); - - if (s->deserialized_state == s->state) - return 0; - - r = service_arm_timer(s, service_coldplug_timeout(s)); - if (r < 0) - return r; - - if (s->main_pid > 0 && - pid_is_unwaited(s->main_pid) && - ((s->deserialized_state == SERVICE_START && IN_SET(s->type, SERVICE_FORKING, SERVICE_DBUS, SERVICE_ONESHOT, SERVICE_NOTIFY)) || - IN_SET(s->deserialized_state, - SERVICE_START, SERVICE_START_POST, - SERVICE_RUNNING, SERVICE_RELOAD, - SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL))) { - r = unit_watch_pid(UNIT(s), s->main_pid); - if (r < 0) - return r; - } - - if (s->control_pid > 0 && - pid_is_unwaited(s->control_pid) && - IN_SET(s->deserialized_state, - SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, - SERVICE_RELOAD, - SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) { - r = unit_watch_pid(UNIT(s), s->control_pid); - if (r < 0) - return r; - } - - if (!IN_SET(s->deserialized_state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART)) - unit_watch_all_pids(UNIT(s)); - - if (IN_SET(s->deserialized_state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) - service_start_watchdog(s); - - service_set_state(s, s->deserialized_state); - return 0; -} - -static int service_collect_fds(Service *s, int **fds, char ***fd_names) { - _cleanup_strv_free_ char **rfd_names = NULL; - _cleanup_free_ int *rfds = NULL; - int rn_fds = 0, r; - - assert(s); - assert(fds); - assert(fd_names); - - if (s->socket_fd >= 0) { - - /* Pass the per-connection socket */ - - rfds = new(int, 1); - if (!rfds) - return -ENOMEM; - rfds[0] = s->socket_fd; - - rfd_names = strv_new("connection", NULL); - if (!rfd_names) - return -ENOMEM; - - rn_fds = 1; - } else { - Iterator i; - Unit *u; - - /* Pass all our configured sockets for singleton services */ - - SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERED_BY], i) { - _cleanup_free_ int *cfds = NULL; - Socket *sock; - int cn_fds; - - if (u->type != UNIT_SOCKET) - continue; - - sock = SOCKET(u); - - cn_fds = socket_collect_fds(sock, &cfds); - if (cn_fds < 0) - return cn_fds; - - if (cn_fds <= 0) - continue; - - if (!rfds) { - rfds = cfds; - rn_fds = cn_fds; - - cfds = NULL; - } else { - int *t; - - t = realloc(rfds, (rn_fds + cn_fds) * sizeof(int)); - if (!t) - return -ENOMEM; - - memcpy(t + rn_fds, cfds, cn_fds * sizeof(int)); - - rfds = t; - rn_fds += cn_fds; - } - - r = strv_extend_n(&rfd_names, socket_fdname(sock), cn_fds); - if (r < 0) - return r; - } - } - - if (s->n_fd_store > 0) { - ServiceFDStore *fs; - char **nl; - int *t; - - t = realloc(rfds, (rn_fds + s->n_fd_store) * sizeof(int)); - if (!t) - return -ENOMEM; - - rfds = t; - - nl = realloc(rfd_names, (rn_fds + s->n_fd_store + 1) * sizeof(char*)); - if (!nl) - return -ENOMEM; - - rfd_names = nl; - - LIST_FOREACH(fd_store, fs, s->fd_store) { - rfds[rn_fds] = fs->fd; - rfd_names[rn_fds] = strdup(strempty(fs->fdname)); - if (!rfd_names[rn_fds]) - return -ENOMEM; - - rn_fds++; - } - - rfd_names[rn_fds] = NULL; - } - - *fds = rfds; - *fd_names = rfd_names; - - rfds = NULL; - rfd_names = NULL; - - return rn_fds; -} - -static int service_spawn( - Service *s, - ExecCommand *c, - usec_t timeout, - bool pass_fds, - bool apply_permissions, - bool apply_chroot, - bool apply_tty_stdin, - bool is_control, - pid_t *_pid) { - - _cleanup_strv_free_ char **argv = NULL, **final_env = NULL, **our_env = NULL, **fd_names = NULL; - _cleanup_free_ int *fds = NULL; - unsigned n_fds = 0, n_env = 0; - const char *path; - pid_t pid; - - ExecParameters exec_params = { - .apply_permissions = apply_permissions, - .apply_chroot = apply_chroot, - .apply_tty_stdin = apply_tty_stdin, - .stdin_fd = -1, - .stdout_fd = -1, - .stderr_fd = -1, - }; - - int r; - - assert(s); - assert(c); - assert(_pid); - - (void) unit_realize_cgroup(UNIT(s)); - if (s->reset_cpu_usage) { - (void) unit_reset_cpu_usage(UNIT(s)); - s->reset_cpu_usage = false; - } - - r = unit_setup_exec_runtime(UNIT(s)); - if (r < 0) - return r; - - if (pass_fds || - s->exec_context.std_input == EXEC_INPUT_SOCKET || - s->exec_context.std_output == EXEC_OUTPUT_SOCKET || - s->exec_context.std_error == EXEC_OUTPUT_SOCKET) { - - r = service_collect_fds(s, &fds, &fd_names); - if (r < 0) - return r; - - n_fds = r; - } - - r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), timeout)); - if (r < 0) - return r; - - r = unit_full_printf_strv(UNIT(s), c->argv, &argv); - if (r < 0) - return r; - - our_env = new0(char*, 6); - if (!our_env) - return -ENOMEM; - - if (is_control ? s->notify_access == NOTIFY_ALL : s->notify_access != NOTIFY_NONE) - if (asprintf(our_env + n_env++, "NOTIFY_SOCKET=%s", UNIT(s)->manager->notify_socket) < 0) - return -ENOMEM; - - if (s->main_pid > 0) - if (asprintf(our_env + n_env++, "MAINPID="PID_FMT, s->main_pid) < 0) - return -ENOMEM; - - if (!MANAGER_IS_SYSTEM(UNIT(s)->manager)) - if (asprintf(our_env + n_env++, "MANAGERPID="PID_FMT, getpid()) < 0) - return -ENOMEM; - - if (s->socket_fd >= 0) { - union sockaddr_union sa; - socklen_t salen = sizeof(sa); - - r = getpeername(s->socket_fd, &sa.sa, &salen); - if (r < 0) - return -errno; - - if (IN_SET(sa.sa.sa_family, AF_INET, AF_INET6)) { - _cleanup_free_ char *addr = NULL; - char *t; - int port; - - r = sockaddr_pretty(&sa.sa, salen, true, false, &addr); - if (r < 0) - return r; - - t = strappend("REMOTE_ADDR=", addr); - if (!t) - return -ENOMEM; - our_env[n_env++] = t; - - port = sockaddr_port(&sa.sa); - if (port < 0) - return port; - - if (asprintf(&t, "REMOTE_PORT=%u", port) < 0) - return -ENOMEM; - our_env[n_env++] = t; - } - } - - final_env = strv_env_merge(2, UNIT(s)->manager->environment, our_env, NULL); - if (!final_env) - return -ENOMEM; - - if (is_control && UNIT(s)->cgroup_path) { - path = strjoina(UNIT(s)->cgroup_path, "/control"); - (void) cg_create(SYSTEMD_CGROUP_CONTROLLER, path); - } else - path = UNIT(s)->cgroup_path; - - exec_params.argv = argv; - exec_params.fds = fds; - exec_params.fd_names = fd_names; - exec_params.n_fds = n_fds; - exec_params.environment = final_env; - exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn; - exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; - exec_params.cgroup_path = path; - exec_params.cgroup_delegate = s->cgroup_context.delegate; - exec_params.runtime_prefix = manager_get_runtime_prefix(UNIT(s)->manager); - exec_params.watchdog_usec = s->watchdog_usec; - exec_params.selinux_context_net = s->socket_fd_selinux_context_net; - if (s->type == SERVICE_IDLE) - exec_params.idle_pipe = UNIT(s)->manager->idle_pipe; - exec_params.stdin_fd = s->stdin_fd; - exec_params.stdout_fd = s->stdout_fd; - exec_params.stderr_fd = s->stderr_fd; - - r = exec_spawn(UNIT(s), - c, - &s->exec_context, - &exec_params, - s->exec_runtime, - &pid); - if (r < 0) - return r; - - r = unit_watch_pid(UNIT(s), pid); - if (r < 0) - /* FIXME: we need to do something here */ - return r; - - *_pid = pid; - - return 0; -} - -static int main_pid_good(Service *s) { - assert(s); - - /* Returns 0 if the pid is dead, 1 if it is good, -1 if we - * don't know */ - - /* If we know the pid file, then let's just check if it is - * still valid */ - if (s->main_pid_known) { - - /* If it's an alien child let's check if it is still - * alive ... */ - if (s->main_pid_alien && s->main_pid > 0) - return pid_is_alive(s->main_pid); - - /* .. otherwise assume we'll get a SIGCHLD for it, - * which we really should wait for to collect exit - * status and code */ - return s->main_pid > 0; - } - - /* We don't know the pid */ - return -EAGAIN; -} - -_pure_ static int control_pid_good(Service *s) { - assert(s); - - return s->control_pid > 0; -} - -static int cgroup_good(Service *s) { - int r; - - assert(s); - - if (!UNIT(s)->cgroup_path) - return 0; - - r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, UNIT(s)->cgroup_path); - if (r < 0) - return r; - - return !r; -} - -static bool service_shall_restart(Service *s) { - assert(s); - - /* Don't restart after manual stops */ - if (s->forbid_restart) - return false; - - /* Never restart if this is configured as special exception */ - if (exit_status_set_test(&s->restart_prevent_status, s->main_exec_status.code, s->main_exec_status.status)) - return false; - - /* Restart if the exit code/status are configured as restart triggers */ - if (exit_status_set_test(&s->restart_force_status, s->main_exec_status.code, s->main_exec_status.status)) - return true; - - switch (s->restart) { - - case SERVICE_RESTART_NO: - return false; - - case SERVICE_RESTART_ALWAYS: - return true; - - case SERVICE_RESTART_ON_SUCCESS: - return s->result == SERVICE_SUCCESS; - - case SERVICE_RESTART_ON_FAILURE: - return s->result != SERVICE_SUCCESS; - - case SERVICE_RESTART_ON_ABNORMAL: - return !IN_SET(s->result, SERVICE_SUCCESS, SERVICE_FAILURE_EXIT_CODE); - - case SERVICE_RESTART_ON_WATCHDOG: - return s->result == SERVICE_FAILURE_WATCHDOG; - - case SERVICE_RESTART_ON_ABORT: - return IN_SET(s->result, SERVICE_FAILURE_SIGNAL, SERVICE_FAILURE_CORE_DUMP); - - default: - assert_not_reached("unknown restart setting"); - } -} - -static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) { - int r; - assert(s); - - if (f != SERVICE_SUCCESS) - s->result = f; - - service_set_state(s, s->result != SERVICE_SUCCESS ? SERVICE_FAILED : SERVICE_DEAD); - - if (s->result != SERVICE_SUCCESS) { - log_unit_warning(UNIT(s), "Failed with result '%s'.", service_result_to_string(s->result)); - failure_action(UNIT(s)->manager, s->failure_action, UNIT(s)->reboot_arg); - } - - if (allow_restart && service_shall_restart(s)) { - - r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->restart_usec)); - if (r < 0) - goto fail; - - service_set_state(s, SERVICE_AUTO_RESTART); - } - - /* The next restart might not be a manual stop, hence reset the flag indicating manual stops */ - s->forbid_restart = false; - - /* We want fresh tmpdirs in case service is started again immediately */ - exec_runtime_destroy(s->exec_runtime); - s->exec_runtime = exec_runtime_unref(s->exec_runtime); - - /* Also, remove the runtime directory in */ - exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager)); - - /* Try to delete the pid file. At this point it will be - * out-of-date, and some software might be confused by it, so - * let's remove it. */ - if (s->pid_file) - (void) unlink(s->pid_file); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run install restart timer: %m"); - service_enter_dead(s, SERVICE_FAILURE_RESOURCES, false); -} - -static void service_enter_stop_post(Service *s, ServiceResult f) { - int r; - assert(s); - - if (f != SERVICE_SUCCESS) - s->result = f; - - service_unwatch_control_pid(s); - unit_watch_all_pids(UNIT(s)); - - s->control_command = s->exec_command[SERVICE_EXEC_STOP_POST]; - if (s->control_command) { - s->control_command_id = SERVICE_EXEC_STOP_POST; - - r = service_spawn(s, - s->control_command, - s->timeout_stop_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - true, - true, - &s->control_pid); - if (r < 0) - goto fail; - - service_set_state(s, SERVICE_STOP_POST); - } else - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'stop-post' task: %m"); - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); -} - -static int state_to_kill_operation(ServiceState state) { - switch (state) { - - case SERVICE_STOP_SIGABRT: - return KILL_ABORT; - - case SERVICE_STOP_SIGTERM: - case SERVICE_FINAL_SIGTERM: - return KILL_TERMINATE; - - case SERVICE_STOP_SIGKILL: - case SERVICE_FINAL_SIGKILL: - return KILL_KILL; - - default: - return _KILL_OPERATION_INVALID; - } -} - -static void service_enter_signal(Service *s, ServiceState state, ServiceResult f) { - int r; - - assert(s); - - if (f != SERVICE_SUCCESS) - s->result = f; - - unit_watch_all_pids(UNIT(s)); - - r = unit_kill_context( - UNIT(s), - &s->kill_context, - state_to_kill_operation(state), - s->main_pid, - s->control_pid, - s->main_pid_alien); - - if (r < 0) - goto fail; - - if (r > 0) { - r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_stop_usec)); - if (r < 0) - goto fail; - - service_set_state(s, state); - } else if (IN_SET(state, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM) && s->kill_context.send_sigkill) - service_enter_signal(s, SERVICE_STOP_SIGKILL, SERVICE_SUCCESS); - else if (IN_SET(state, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL)) - service_enter_stop_post(s, SERVICE_SUCCESS); - else if (state == SERVICE_FINAL_SIGTERM && s->kill_context.send_sigkill) - service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_SUCCESS); - else - service_enter_dead(s, SERVICE_SUCCESS, true); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); - - if (IN_SET(state, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL)) - service_enter_stop_post(s, SERVICE_FAILURE_RESOURCES); - else - service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); -} - -static void service_enter_stop_by_notify(Service *s) { - assert(s); - - unit_watch_all_pids(UNIT(s)); - - service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_stop_usec)); - - /* The service told us it's stopping, so it's as if we SIGTERM'd it. */ - service_set_state(s, SERVICE_STOP_SIGTERM); -} - -static void service_enter_stop(Service *s, ServiceResult f) { - int r; - - assert(s); - - if (f != SERVICE_SUCCESS) - s->result = f; - - service_unwatch_control_pid(s); - unit_watch_all_pids(UNIT(s)); - - s->control_command = s->exec_command[SERVICE_EXEC_STOP]; - if (s->control_command) { - s->control_command_id = SERVICE_EXEC_STOP; - - r = service_spawn(s, - s->control_command, - s->timeout_stop_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - false, - true, - &s->control_pid); - if (r < 0) - goto fail; - - service_set_state(s, SERVICE_STOP); - } else - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'stop' task: %m"); - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); -} - -static bool service_good(Service *s) { - int main_pid_ok; - assert(s); - - if (s->type == SERVICE_DBUS && !s->bus_name_good) - return false; - - main_pid_ok = main_pid_good(s); - if (main_pid_ok > 0) /* It's alive */ - return true; - if (main_pid_ok == 0) /* It's dead */ - return false; - - /* OK, we don't know anything about the main PID, maybe - * because there is none. Let's check the control group - * instead. */ - - return cgroup_good(s) != 0; -} - -static void service_enter_running(Service *s, ServiceResult f) { - assert(s); - - if (f != SERVICE_SUCCESS) - s->result = f; - - service_unwatch_control_pid(s); - - if (service_good(s)) { - - /* If there are any queued up sd_notify() - * notifications, process them now */ - if (s->notify_state == NOTIFY_RELOADING) - service_enter_reload_by_notify(s); - else if (s->notify_state == NOTIFY_STOPPING) - service_enter_stop_by_notify(s); - else { - service_set_state(s, SERVICE_RUNNING); - service_arm_timer(s, usec_add(UNIT(s)->active_enter_timestamp.monotonic, s->runtime_max_usec)); - } - - } else if (s->remain_after_exit) - service_set_state(s, SERVICE_EXITED); - else - service_enter_stop(s, SERVICE_SUCCESS); -} - -static void service_enter_start_post(Service *s) { - int r; - assert(s); - - service_unwatch_control_pid(s); - service_reset_watchdog(s); - - s->control_command = s->exec_command[SERVICE_EXEC_START_POST]; - if (s->control_command) { - s->control_command_id = SERVICE_EXEC_START_POST; - - r = service_spawn(s, - s->control_command, - s->timeout_start_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - false, - true, - &s->control_pid); - if (r < 0) - goto fail; - - service_set_state(s, SERVICE_START_POST); - } else - service_enter_running(s, SERVICE_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'start-post' task: %m"); - service_enter_stop(s, SERVICE_FAILURE_RESOURCES); -} - -static void service_kill_control_processes(Service *s) { - char *p; - - if (!UNIT(s)->cgroup_path) - return; - - p = strjoina(UNIT(s)->cgroup_path, "/control"); - cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, p, SIGKILL, true, true, true, NULL); -} - -static void service_enter_start(Service *s) { - ExecCommand *c; - usec_t timeout; - pid_t pid; - int r; - - assert(s); - - service_unwatch_control_pid(s); - service_unwatch_main_pid(s); - - /* We want to ensure that nobody leaks processes from - * START_PRE here, so let's go on a killing spree, People - * should not spawn long running processes from START_PRE. */ - service_kill_control_processes(s); - - if (s->type == SERVICE_FORKING) { - s->control_command_id = SERVICE_EXEC_START; - c = s->control_command = s->exec_command[SERVICE_EXEC_START]; - - s->main_command = NULL; - } else { - s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; - s->control_command = NULL; - - c = s->main_command = s->exec_command[SERVICE_EXEC_START]; - } - - if (!c) { - assert(s->type == SERVICE_ONESHOT); - service_enter_start_post(s); - return; - } - - if (IN_SET(s->type, SERVICE_SIMPLE, SERVICE_IDLE)) - /* For simple + idle this is the main process. We don't apply any timeout here, but - * service_enter_running() will later apply the .runtime_max_usec timeout. */ - timeout = USEC_INFINITY; - else - timeout = s->timeout_start_usec; - - r = service_spawn(s, - c, - timeout, - true, - true, - true, - true, - false, - &pid); - if (r < 0) - goto fail; - - if (IN_SET(s->type, SERVICE_SIMPLE, SERVICE_IDLE)) { - /* For simple services we immediately start - * the START_POST binaries. */ - - service_set_main_pid(s, pid); - service_enter_start_post(s); - - } else if (s->type == SERVICE_FORKING) { - - /* For forking services we wait until the start - * process exited. */ - - s->control_pid = pid; - service_set_state(s, SERVICE_START); - - } else if (IN_SET(s->type, SERVICE_ONESHOT, SERVICE_DBUS, SERVICE_NOTIFY)) { - - /* For oneshot services we wait until the start - * process exited, too, but it is our main process. */ - - /* For D-Bus services we know the main pid right away, - * but wait for the bus name to appear on the - * bus. Notify services are similar. */ - - service_set_main_pid(s, pid); - service_set_state(s, SERVICE_START); - } else - assert_not_reached("Unknown service type"); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'start' task: %m"); - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); -} - -static void service_enter_start_pre(Service *s) { - int r; - - assert(s); - - service_unwatch_control_pid(s); - - s->control_command = s->exec_command[SERVICE_EXEC_START_PRE]; - if (s->control_command) { - /* Before we start anything, let's clear up what might - * be left from previous runs. */ - service_kill_control_processes(s); - - s->control_command_id = SERVICE_EXEC_START_PRE; - - r = service_spawn(s, - s->control_command, - s->timeout_start_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - true, - true, - &s->control_pid); - if (r < 0) - goto fail; - - service_set_state(s, SERVICE_START_PRE); - } else - service_enter_start(s); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'start-pre' task: %m"); - service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); -} - -static void service_enter_restart(Service *s) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(s); - - if (UNIT(s)->job && UNIT(s)->job->type == JOB_STOP) { - /* Don't restart things if we are going down anyway */ - log_unit_info(UNIT(s), "Stop job pending for unit, delaying automatic restart."); - - r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->restart_usec)); - if (r < 0) - goto fail; - - return; - } - - /* Any units that are bound to this service must also be - * restarted. We use JOB_RESTART (instead of the more obvious - * JOB_START) here so that those dependency jobs will be added - * as well. */ - r = manager_add_job(UNIT(s)->manager, JOB_RESTART, UNIT(s), JOB_FAIL, &error, NULL); - if (r < 0) - goto fail; - - /* Note that we stay in the SERVICE_AUTO_RESTART state here, - * it will be canceled as part of the service_stop() call that - * is executed as part of JOB_RESTART. */ - - log_unit_debug(UNIT(s), "Scheduled restart job."); - return; - -fail: - log_unit_warning(UNIT(s), "Failed to schedule restart job: %s", bus_error_message(&error, -r)); - service_enter_dead(s, SERVICE_FAILURE_RESOURCES, false); -} - -static void service_enter_reload_by_notify(Service *s) { - assert(s); - - service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_start_usec)); - service_set_state(s, SERVICE_RELOAD); -} - -static void service_enter_reload(Service *s) { - int r; - - assert(s); - - service_unwatch_control_pid(s); - s->reload_result = SERVICE_SUCCESS; - - s->control_command = s->exec_command[SERVICE_EXEC_RELOAD]; - if (s->control_command) { - s->control_command_id = SERVICE_EXEC_RELOAD; - - r = service_spawn(s, - s->control_command, - s->timeout_start_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - false, - true, - &s->control_pid); - if (r < 0) - goto fail; - - service_set_state(s, SERVICE_RELOAD); - } else - service_enter_running(s, SERVICE_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'reload' task: %m"); - s->reload_result = SERVICE_FAILURE_RESOURCES; - service_enter_running(s, SERVICE_SUCCESS); -} - -static void service_run_next_control(Service *s) { - usec_t timeout; - int r; - - assert(s); - assert(s->control_command); - assert(s->control_command->command_next); - - assert(s->control_command_id != SERVICE_EXEC_START); - - s->control_command = s->control_command->command_next; - service_unwatch_control_pid(s); - - if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) - timeout = s->timeout_start_usec; - else - timeout = s->timeout_stop_usec; - - r = service_spawn(s, - s->control_command, - timeout, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - s->control_command_id == SERVICE_EXEC_START_PRE || - s->control_command_id == SERVICE_EXEC_STOP_POST, - true, - &s->control_pid); - if (r < 0) - goto fail; - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run next control task: %m"); - - if (s->state == SERVICE_START_PRE) - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); - else if (s->state == SERVICE_STOP) - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); - else if (s->state == SERVICE_STOP_POST) - service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); - else if (s->state == SERVICE_RELOAD) { - s->reload_result = SERVICE_FAILURE_RESOURCES; - service_enter_running(s, SERVICE_SUCCESS); - } else - service_enter_stop(s, SERVICE_FAILURE_RESOURCES); -} - -static void service_run_next_main(Service *s) { - pid_t pid; - int r; - - assert(s); - assert(s->main_command); - assert(s->main_command->command_next); - assert(s->type == SERVICE_ONESHOT); - - s->main_command = s->main_command->command_next; - service_unwatch_main_pid(s); - - r = service_spawn(s, - s->main_command, - s->timeout_start_usec, - true, - true, - true, - true, - false, - &pid); - if (r < 0) - goto fail; - - service_set_main_pid(s, pid); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run next main task: %m"); - service_enter_stop(s, SERVICE_FAILURE_RESOURCES); -} - -static int service_start(Unit *u) { - Service *s = SERVICE(u); - int r; - - assert(s); - - /* We cannot fulfill this request right now, try again later - * please! */ - if (IN_SET(s->state, - SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) - return -EAGAIN; - - /* Already on it! */ - if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST)) - return 0; - - /* A service that will be restarted must be stopped first to - * trigger BindsTo and/or OnFailure dependencies. If a user - * does not want to wait for the holdoff time to elapse, the - * service should be manually restarted, not started. We - * simply return EAGAIN here, so that any start jobs stay - * queued, and assume that the auto restart timer will - * eventually trigger the restart. */ - if (s->state == SERVICE_AUTO_RESTART) - return -EAGAIN; - - assert(IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED)); - - /* Make sure we don't enter a busy loop of some kind. */ - r = unit_start_limit_test(u); - if (r < 0) { - service_enter_dead(s, SERVICE_FAILURE_START_LIMIT_HIT, false); - return r; - } - - s->result = SERVICE_SUCCESS; - s->reload_result = SERVICE_SUCCESS; - s->main_pid_known = false; - s->main_pid_alien = false; - s->forbid_restart = false; - s->reset_cpu_usage = true; - - s->status_text = mfree(s->status_text); - s->status_errno = 0; - - s->notify_state = NOTIFY_UNKNOWN; - - service_enter_start_pre(s); - return 1; -} - -static int service_stop(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - /* Don't create restart jobs from manual stops. */ - s->forbid_restart = true; - - /* Already on it */ - if (IN_SET(s->state, - SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) - return 0; - - /* A restart will be scheduled or is in progress. */ - if (s->state == SERVICE_AUTO_RESTART) { - service_set_state(s, SERVICE_DEAD); - return 0; - } - - /* If there's already something running we go directly into - * kill mode. */ - if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD)) { - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS); - return 0; - } - - assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_EXITED)); - - service_enter_stop(s, SERVICE_SUCCESS); - return 1; -} - -static int service_reload(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - assert(s->state == SERVICE_RUNNING || s->state == SERVICE_EXITED); - - service_enter_reload(s); - return 1; -} - -_pure_ static bool service_can_reload(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - return !!s->exec_command[SERVICE_EXEC_RELOAD]; -} - -static int service_serialize(Unit *u, FILE *f, FDSet *fds) { - Service *s = SERVICE(u); - ServiceFDStore *fs; - int r; - - assert(u); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", service_state_to_string(s->state)); - unit_serialize_item(u, f, "result", service_result_to_string(s->result)); - unit_serialize_item(u, f, "reload-result", service_result_to_string(s->reload_result)); - - if (s->control_pid > 0) - unit_serialize_item_format(u, f, "control-pid", PID_FMT, s->control_pid); - - if (s->main_pid_known && s->main_pid > 0) - unit_serialize_item_format(u, f, "main-pid", PID_FMT, s->main_pid); - - unit_serialize_item(u, f, "main-pid-known", yes_no(s->main_pid_known)); - unit_serialize_item(u, f, "bus-name-good", yes_no(s->bus_name_good)); - unit_serialize_item(u, f, "bus-name-owner", s->bus_name_owner); - - r = unit_serialize_item_escaped(u, f, "status-text", s->status_text); - if (r < 0) - return r; - - /* FIXME: There's a minor uncleanliness here: if there are - * multiple commands attached here, we will start from the - * first one again */ - if (s->control_command_id >= 0) - unit_serialize_item(u, f, "control-command", service_exec_command_to_string(s->control_command_id)); - - r = unit_serialize_item_fd(u, f, fds, "stdin-fd", s->stdin_fd); - if (r < 0) - return r; - r = unit_serialize_item_fd(u, f, fds, "stdout-fd", s->stdout_fd); - if (r < 0) - return r; - r = unit_serialize_item_fd(u, f, fds, "stderr-fd", s->stderr_fd); - if (r < 0) - return r; - - r = unit_serialize_item_fd(u, f, fds, "socket-fd", s->socket_fd); - if (r < 0) - return r; - - LIST_FOREACH(fd_store, fs, s->fd_store) { - _cleanup_free_ char *c = NULL; - int copy; - - copy = fdset_put_dup(fds, fs->fd); - if (copy < 0) - return copy; - - c = cescape(fs->fdname); - - unit_serialize_item_format(u, f, "fd-store-fd", "%i %s", copy, strempty(c)); - } - - if (s->main_exec_status.pid > 0) { - unit_serialize_item_format(u, f, "main-exec-status-pid", PID_FMT, s->main_exec_status.pid); - dual_timestamp_serialize(f, "main-exec-status-start", &s->main_exec_status.start_timestamp); - dual_timestamp_serialize(f, "main-exec-status-exit", &s->main_exec_status.exit_timestamp); - - if (dual_timestamp_is_set(&s->main_exec_status.exit_timestamp)) { - unit_serialize_item_format(u, f, "main-exec-status-code", "%i", s->main_exec_status.code); - unit_serialize_item_format(u, f, "main-exec-status-status", "%i", s->main_exec_status.status); - } - } - - dual_timestamp_serialize(f, "watchdog-timestamp", &s->watchdog_timestamp); - - unit_serialize_item(u, f, "forbid-restart", yes_no(s->forbid_restart)); - - return 0; -} - -static int service_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Service *s = SERVICE(u); - int r; - - assert(u); - assert(key); - assert(value); - assert(fds); - - if (streq(key, "state")) { - ServiceState state; - - state = service_state_from_string(value); - if (state < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - s->deserialized_state = state; - } else if (streq(key, "result")) { - ServiceResult f; - - f = service_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse result value: %s", value); - else if (f != SERVICE_SUCCESS) - s->result = f; - - } else if (streq(key, "reload-result")) { - ServiceResult f; - - f = service_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse reload result value: %s", value); - else if (f != SERVICE_SUCCESS) - s->reload_result = f; - - } else if (streq(key, "control-pid")) { - pid_t pid; - - if (parse_pid(value, &pid) < 0) - log_unit_debug(u, "Failed to parse control-pid value: %s", value); - else - s->control_pid = pid; - } else if (streq(key, "main-pid")) { - pid_t pid; - - if (parse_pid(value, &pid) < 0) - log_unit_debug(u, "Failed to parse main-pid value: %s", value); - else { - service_set_main_pid(s, pid); - unit_watch_pid(UNIT(s), pid); - } - } else if (streq(key, "main-pid-known")) { - int b; - - b = parse_boolean(value); - if (b < 0) - log_unit_debug(u, "Failed to parse main-pid-known value: %s", value); - else - s->main_pid_known = b; - } else if (streq(key, "bus-name-good")) { - int b; - - b = parse_boolean(value); - if (b < 0) - log_unit_debug(u, "Failed to parse bus-name-good value: %s", value); - else - s->bus_name_good = b; - } else if (streq(key, "bus-name-owner")) { - r = free_and_strdup(&s->bus_name_owner, value); - if (r < 0) - log_unit_error_errno(u, r, "Unable to deserialize current bus owner %s: %m", value); - } else if (streq(key, "status-text")) { - char *t; - - r = cunescape(value, 0, &t); - if (r < 0) - log_unit_debug_errno(u, r, "Failed to unescape status text: %s", value); - else { - free(s->status_text); - s->status_text = t; - } - - } else if (streq(key, "control-command")) { - ServiceExecCommand id; - - id = service_exec_command_from_string(value); - if (id < 0) - log_unit_debug(u, "Failed to parse exec-command value: %s", value); - else { - s->control_command_id = id; - s->control_command = s->exec_command[id]; - } - } else if (streq(key, "socket-fd")) { - int fd; - - if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse socket-fd value: %s", value); - else { - asynchronous_close(s->socket_fd); - s->socket_fd = fdset_remove(fds, fd); - } - } else if (streq(key, "fd-store-fd")) { - const char *fdv; - size_t pf; - int fd; - - pf = strcspn(value, WHITESPACE); - fdv = strndupa(value, pf); - - if (safe_atoi(fdv, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse fd-store-fd value: %s", value); - else { - _cleanup_free_ char *t = NULL; - const char *fdn; - - fdn = value + pf; - fdn += strspn(fdn, WHITESPACE); - (void) cunescape(fdn, 0, &t); - - r = service_add_fd_store(s, fd, t); - if (r < 0) - log_unit_error_errno(u, r, "Failed to add fd to store: %m"); - else if (r > 0) - fdset_remove(fds, fd); - } - - } else if (streq(key, "main-exec-status-pid")) { - pid_t pid; - - if (parse_pid(value, &pid) < 0) - log_unit_debug(u, "Failed to parse main-exec-status-pid value: %s", value); - else - s->main_exec_status.pid = pid; - } else if (streq(key, "main-exec-status-code")) { - int i; - - if (safe_atoi(value, &i) < 0) - log_unit_debug(u, "Failed to parse main-exec-status-code value: %s", value); - else - s->main_exec_status.code = i; - } else if (streq(key, "main-exec-status-status")) { - int i; - - if (safe_atoi(value, &i) < 0) - log_unit_debug(u, "Failed to parse main-exec-status-status value: %s", value); - else - s->main_exec_status.status = i; - } else if (streq(key, "main-exec-status-start")) - dual_timestamp_deserialize(value, &s->main_exec_status.start_timestamp); - else if (streq(key, "main-exec-status-exit")) - dual_timestamp_deserialize(value, &s->main_exec_status.exit_timestamp); - else if (streq(key, "watchdog-timestamp")) - dual_timestamp_deserialize(value, &s->watchdog_timestamp); - else if (streq(key, "forbid-restart")) { - int b; - - b = parse_boolean(value); - if (b < 0) - log_unit_debug(u, "Failed to parse forbid-restart value: %s", value); - else - s->forbid_restart = b; - } else if (streq(key, "stdin-fd")) { - int fd; - - if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse stdin-fd value: %s", value); - else { - asynchronous_close(s->stdin_fd); - s->stdin_fd = fdset_remove(fds, fd); - s->exec_context.stdio_as_fds = true; - } - } else if (streq(key, "stdout-fd")) { - int fd; - - if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse stdout-fd value: %s", value); - else { - asynchronous_close(s->stdout_fd); - s->stdout_fd = fdset_remove(fds, fd); - s->exec_context.stdio_as_fds = true; - } - } else if (streq(key, "stderr-fd")) { - int fd; - - if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse stderr-fd value: %s", value); - else { - asynchronous_close(s->stderr_fd); - s->stderr_fd = fdset_remove(fds, fd); - s->exec_context.stdio_as_fds = true; - } - } else - log_unit_debug(u, "Unknown serialization key: %s", key); - - return 0; -} - -_pure_ static UnitActiveState service_active_state(Unit *u) { - const UnitActiveState *table; - - assert(u); - - table = SERVICE(u)->type == SERVICE_IDLE ? state_translation_table_idle : state_translation_table; - - return table[SERVICE(u)->state]; -} - -static const char *service_sub_state_to_string(Unit *u) { - assert(u); - - return service_state_to_string(SERVICE(u)->state); -} - -static bool service_check_gc(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - /* Never clean up services that still have a process around, - * even if the service is formally dead. */ - if (cgroup_good(s) > 0 || - main_pid_good(s) > 0 || - control_pid_good(s) > 0) - return true; - - return false; -} - -static int service_retry_pid_file(Service *s) { - int r; - - assert(s->pid_file); - assert(s->state == SERVICE_START || s->state == SERVICE_START_POST); - - r = service_load_pid_file(s, false); - if (r < 0) - return r; - - service_unwatch_pid_file(s); - - service_enter_running(s, SERVICE_SUCCESS); - return 0; -} - -static int service_watch_pid_file(Service *s) { - int r; - - log_unit_debug(UNIT(s), "Setting watch for PID file %s", s->pid_file_pathspec->path); - - r = path_spec_watch(s->pid_file_pathspec, service_dispatch_io); - if (r < 0) - goto fail; - - /* the pidfile might have appeared just before we set the watch */ - log_unit_debug(UNIT(s), "Trying to read PID file %s in case it changed", s->pid_file_pathspec->path); - service_retry_pid_file(s); - - return 0; -fail: - log_unit_error_errno(UNIT(s), r, "Failed to set a watch for PID file %s: %m", s->pid_file_pathspec->path); - service_unwatch_pid_file(s); - return r; -} - -static int service_demand_pid_file(Service *s) { - PathSpec *ps; - - assert(s->pid_file); - assert(!s->pid_file_pathspec); - - ps = new0(PathSpec, 1); - if (!ps) - return -ENOMEM; - - ps->unit = UNIT(s); - ps->path = strdup(s->pid_file); - if (!ps->path) { - free(ps); - return -ENOMEM; - } - - path_kill_slashes(ps->path); - - /* PATH_CHANGED would not be enough. There are daemons (sendmail) that - * keep their PID file open all the time. */ - ps->type = PATH_MODIFIED; - ps->inotify_fd = -1; - - s->pid_file_pathspec = ps; - - return service_watch_pid_file(s); -} - -static int service_dispatch_io(sd_event_source *source, int fd, uint32_t events, void *userdata) { - PathSpec *p = userdata; - Service *s; - - assert(p); - - s = SERVICE(p->unit); - - assert(s); - assert(fd >= 0); - assert(s->state == SERVICE_START || s->state == SERVICE_START_POST); - assert(s->pid_file_pathspec); - assert(path_spec_owns_inotify_fd(s->pid_file_pathspec, fd)); - - log_unit_debug(UNIT(s), "inotify event"); - - if (path_spec_fd_event(p, events) < 0) - goto fail; - - if (service_retry_pid_file(s) == 0) - return 0; - - if (service_watch_pid_file(s) < 0) - goto fail; - - return 0; - -fail: - service_unwatch_pid_file(s); - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); - return 0; -} - -static void service_notify_cgroup_empty_event(Unit *u) { - Service *s = SERVICE(u); - - assert(u); - - log_unit_debug(u, "cgroup is empty"); - - switch (s->state) { - - /* Waiting for SIGCHLD is usually more interesting, - * because it includes return codes/signals. Which is - * why we ignore the cgroup events for most cases, - * except when we don't know pid which to expect the - * SIGCHLD for. */ - - case SERVICE_START: - case SERVICE_START_POST: - /* If we were hoping for the daemon to write its PID file, - * we can give up now. */ - if (s->pid_file_pathspec) { - log_unit_warning(u, "Daemon never wrote its PID file. Failing."); - - service_unwatch_pid_file(s); - if (s->state == SERVICE_START) - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); - else - service_enter_stop(s, SERVICE_FAILURE_RESOURCES); - } - break; - - case SERVICE_RUNNING: - /* service_enter_running() will figure out what to do */ - service_enter_running(s, SERVICE_SUCCESS); - break; - - case SERVICE_STOP_SIGABRT: - case SERVICE_STOP_SIGTERM: - case SERVICE_STOP_SIGKILL: - - if (main_pid_good(s) <= 0 && !control_pid_good(s)) - service_enter_stop_post(s, SERVICE_SUCCESS); - - break; - - case SERVICE_STOP_POST: - case SERVICE_FINAL_SIGTERM: - case SERVICE_FINAL_SIGKILL: - if (main_pid_good(s) <= 0 && !control_pid_good(s)) - service_enter_dead(s, SERVICE_SUCCESS, true); - - break; - - default: - ; - } -} - -static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { - Service *s = SERVICE(u); - ServiceResult f; - - assert(s); - assert(pid >= 0); - - if (UNIT(s)->fragment_path ? is_clean_exit(code, status, &s->success_status) : - is_clean_exit_lsb(code, status, &s->success_status)) - f = SERVICE_SUCCESS; - else if (code == CLD_EXITED) - f = SERVICE_FAILURE_EXIT_CODE; - else if (code == CLD_KILLED) - f = SERVICE_FAILURE_SIGNAL; - else if (code == CLD_DUMPED) - f = SERVICE_FAILURE_CORE_DUMP; - else - assert_not_reached("Unknown code"); - - if (s->main_pid == pid) { - /* Forking services may occasionally move to a new PID. - * As long as they update the PID file before exiting the old - * PID, they're fine. */ - if (service_load_pid_file(s, false) == 0) - return; - - s->main_pid = 0; - exec_status_exit(&s->main_exec_status, &s->exec_context, pid, code, status); - - if (s->main_command) { - /* If this is not a forking service than the - * main process got started and hence we copy - * the exit status so that it is recorded both - * as main and as control process exit - * status */ - - s->main_command->exec_status = s->main_exec_status; - - if (s->main_command->ignore) - f = SERVICE_SUCCESS; - } else if (s->exec_command[SERVICE_EXEC_START]) { - - /* If this is a forked process, then we should - * ignore the return value if this was - * configured for the starter process */ - - if (s->exec_command[SERVICE_EXEC_START]->ignore) - f = SERVICE_SUCCESS; - } - - log_struct(f == SERVICE_SUCCESS ? LOG_DEBUG : LOG_NOTICE, - LOG_UNIT_ID(u), - LOG_UNIT_MESSAGE(u, "Main process exited, code=%s, status=%i/%s", - sigchld_code_to_string(code), status, - strna(code == CLD_EXITED - ? exit_status_to_string(status, EXIT_STATUS_FULL) - : signal_to_string(status))), - "EXIT_CODE=%s", sigchld_code_to_string(code), - "EXIT_STATUS=%i", status, - NULL); - - if (f != SERVICE_SUCCESS) - s->result = f; - - if (s->main_command && - s->main_command->command_next && - f == SERVICE_SUCCESS) { - - /* There is another command to * - * execute, so let's do that. */ - - log_unit_debug(u, "Running next main command for state %s.", service_state_to_string(s->state)); - service_run_next_main(s); - - } else { - - /* The service exited, so the service is officially - * gone. */ - s->main_command = NULL; - - switch (s->state) { - - case SERVICE_START_POST: - case SERVICE_RELOAD: - case SERVICE_STOP: - /* Need to wait until the operation is - * done */ - break; - - case SERVICE_START: - if (s->type == SERVICE_ONESHOT) { - /* This was our main goal, so let's go on */ - if (f == SERVICE_SUCCESS) - service_enter_start_post(s); - else - service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); - break; - } - - /* Fall through */ - - case SERVICE_RUNNING: - service_enter_running(s, f); - break; - - case SERVICE_STOP_SIGABRT: - case SERVICE_STOP_SIGTERM: - case SERVICE_STOP_SIGKILL: - - if (!control_pid_good(s)) - service_enter_stop_post(s, f); - - /* If there is still a control process, wait for that first */ - break; - - case SERVICE_STOP_POST: - case SERVICE_FINAL_SIGTERM: - case SERVICE_FINAL_SIGKILL: - - if (!control_pid_good(s)) - service_enter_dead(s, f, true); - break; - - default: - assert_not_reached("Uh, main process died at wrong time."); - } - } - - } else if (s->control_pid == pid) { - s->control_pid = 0; - - if (s->control_command) { - exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status); - - if (s->control_command->ignore) - f = SERVICE_SUCCESS; - } - - log_unit_full(u, f == SERVICE_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, - "Control process exited, code=%s status=%i", - sigchld_code_to_string(code), status); - - if (f != SERVICE_SUCCESS) - s->result = f; - - /* Immediately get rid of the cgroup, so that the - * kernel doesn't delay the cgroup empty messages for - * the service cgroup any longer than necessary */ - service_kill_control_processes(s); - - if (s->control_command && - s->control_command->command_next && - f == SERVICE_SUCCESS) { - - /* There is another command to * - * execute, so let's do that. */ - - log_unit_debug(u, "Running next control command for state %s.", service_state_to_string(s->state)); - service_run_next_control(s); - - } else { - /* No further commands for this step, so let's - * figure out what to do next */ - - s->control_command = NULL; - s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; - - log_unit_debug(u, "Got final SIGCHLD for state %s.", service_state_to_string(s->state)); - - switch (s->state) { - - case SERVICE_START_PRE: - if (f == SERVICE_SUCCESS) - service_enter_start(s); - else - service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); - break; - - case SERVICE_START: - if (s->type != SERVICE_FORKING) - /* Maybe spurious event due to a reload that changed the type? */ - break; - - if (f != SERVICE_SUCCESS) { - service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); - break; - } - - if (s->pid_file) { - bool has_start_post; - int r; - - /* Let's try to load the pid file here if we can. - * The PID file might actually be created by a START_POST - * script. In that case don't worry if the loading fails. */ - - has_start_post = !!s->exec_command[SERVICE_EXEC_START_POST]; - r = service_load_pid_file(s, !has_start_post); - if (!has_start_post && r < 0) { - r = service_demand_pid_file(s); - if (r < 0 || !cgroup_good(s)) - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); - break; - } - } else - service_search_main_pid(s); - - service_enter_start_post(s); - break; - - case SERVICE_START_POST: - if (f != SERVICE_SUCCESS) { - service_enter_signal(s, SERVICE_STOP_SIGTERM, f); - break; - } - - if (s->pid_file) { - int r; - - r = service_load_pid_file(s, true); - if (r < 0) { - r = service_demand_pid_file(s); - if (r < 0 || !cgroup_good(s)) - service_enter_stop(s, SERVICE_FAILURE_RESOURCES); - break; - } - } else - service_search_main_pid(s); - - service_enter_running(s, SERVICE_SUCCESS); - break; - - case SERVICE_RELOAD: - if (f == SERVICE_SUCCESS) - if (service_load_pid_file(s, true) < 0) - service_search_main_pid(s); - - s->reload_result = f; - service_enter_running(s, SERVICE_SUCCESS); - break; - - case SERVICE_STOP: - service_enter_signal(s, SERVICE_STOP_SIGTERM, f); - break; - - case SERVICE_STOP_SIGABRT: - case SERVICE_STOP_SIGTERM: - case SERVICE_STOP_SIGKILL: - if (main_pid_good(s) <= 0) - service_enter_stop_post(s, f); - - /* If there is still a service - * process around, wait until - * that one quit, too */ - break; - - case SERVICE_STOP_POST: - case SERVICE_FINAL_SIGTERM: - case SERVICE_FINAL_SIGKILL: - if (main_pid_good(s) <= 0) - service_enter_dead(s, f, true); - break; - - default: - assert_not_reached("Uh, control process died at wrong time."); - } - } - } - - /* Notify clients about changed exit status */ - unit_add_to_dbus_queue(u); - - /* We got one SIGCHLD for the service, let's watch all - * processes that are now running of the service, and watch - * that. Among the PIDs we then watch will be children - * reassigned to us, which hopefully allows us to identify - * when all children are gone */ - unit_tidy_watch_pids(u, s->main_pid, s->control_pid); - unit_watch_all_pids(u); - - /* If the PID set is empty now, then let's finish this off */ - if (set_isempty(u->pids)) - service_notify_cgroup_empty_event(u); -} - -static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { - Service *s = SERVICE(userdata); - - assert(s); - assert(source == s->timer_event_source); - - switch (s->state) { - - case SERVICE_START_PRE: - case SERVICE_START: - log_unit_warning(UNIT(s), "%s operation timed out. Terminating.", s->state == SERVICE_START ? "Start" : "Start-pre"); - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_TIMEOUT); - break; - - case SERVICE_START_POST: - log_unit_warning(UNIT(s), "Start-post operation timed out. Stopping."); - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT); - break; - - case SERVICE_RUNNING: - log_unit_warning(UNIT(s), "Service reached runtime time limit. Stopping."); - service_enter_stop(s, SERVICE_FAILURE_TIMEOUT); - break; - - case SERVICE_RELOAD: - log_unit_warning(UNIT(s), "Reload operation timed out. Killing reload process."); - service_kill_control_processes(s); - s->reload_result = SERVICE_FAILURE_TIMEOUT; - service_enter_running(s, SERVICE_SUCCESS); - break; - - case SERVICE_STOP: - log_unit_warning(UNIT(s), "Stopping timed out. Terminating."); - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT); - break; - - case SERVICE_STOP_SIGABRT: - log_unit_warning(UNIT(s), "State 'stop-sigabrt' timed out. Terminating."); - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT); - break; - - case SERVICE_STOP_SIGTERM: - if (s->kill_context.send_sigkill) { - log_unit_warning(UNIT(s), "State 'stop-sigterm' timed out. Killing."); - service_enter_signal(s, SERVICE_STOP_SIGKILL, SERVICE_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(s), "State 'stop-sigterm' timed out. Skipping SIGKILL."); - service_enter_stop_post(s, SERVICE_FAILURE_TIMEOUT); - } - - break; - - case SERVICE_STOP_SIGKILL: - /* Uh, we sent a SIGKILL and it is still not gone? - * Must be something we cannot kill, so let's just be - * weirded out and continue */ - - log_unit_warning(UNIT(s), "Processes still around after SIGKILL. Ignoring."); - service_enter_stop_post(s, SERVICE_FAILURE_TIMEOUT); - break; - - case SERVICE_STOP_POST: - log_unit_warning(UNIT(s), "State 'stop-post' timed out. Terminating."); - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_TIMEOUT); - break; - - case SERVICE_FINAL_SIGTERM: - if (s->kill_context.send_sigkill) { - log_unit_warning(UNIT(s), "State 'stop-final-sigterm' timed out. Killing."); - service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(s), "State 'stop-final-sigterm' timed out. Skipping SIGKILL. Entering failed mode."); - service_enter_dead(s, SERVICE_FAILURE_TIMEOUT, false); - } - - break; - - case SERVICE_FINAL_SIGKILL: - log_unit_warning(UNIT(s), "Processes still around after final SIGKILL. Entering failed mode."); - service_enter_dead(s, SERVICE_FAILURE_TIMEOUT, true); - break; - - case SERVICE_AUTO_RESTART: - log_unit_info(UNIT(s), - s->restart_usec > 0 ? - "Service hold-off time over, scheduling restart." : - "Service has no hold-off time, scheduling restart."); - service_enter_restart(s); - break; - - default: - assert_not_reached("Timeout at wrong time."); - } - - return 0; -} - -static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void *userdata) { - Service *s = SERVICE(userdata); - char t[FORMAT_TIMESPAN_MAX]; - - assert(s); - assert(source == s->watchdog_event_source); - - log_unit_error(UNIT(s), "Watchdog timeout (limit %s)!", - format_timespan(t, sizeof(t), s->watchdog_usec, 1)); - - service_enter_signal(s, SERVICE_STOP_SIGABRT, SERVICE_FAILURE_WATCHDOG); - - return 0; -} - -static void service_notify_message(Unit *u, pid_t pid, char **tags, FDSet *fds) { - Service *s = SERVICE(u); - _cleanup_free_ char *cc = NULL; - bool notify_dbus = false; - const char *e; - - assert(u); - - cc = strv_join(tags, ", "); - - if (s->notify_access == NOTIFY_NONE) { - log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception is disabled.", pid); - return; - } else if (s->notify_access == NOTIFY_MAIN && pid != s->main_pid) { - if (s->main_pid != 0) - log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT, pid, s->main_pid); - else - log_unit_debug(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID which is currently not known", pid); - return; - } else - log_unit_debug(u, "Got notification message from PID "PID_FMT" (%s)", pid, isempty(cc) ? "n/a" : cc); - - /* Interpret MAINPID= */ - e = strv_find_startswith(tags, "MAINPID="); - if (e && IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) { - if (parse_pid(e, &pid) < 0) - log_unit_warning(u, "Failed to parse MAINPID= field in notification message: %s", e); - else { - service_set_main_pid(s, pid); - unit_watch_pid(UNIT(s), pid); - notify_dbus = true; - } - } - - /* Interpret RELOADING= */ - if (strv_find(tags, "RELOADING=1")) { - - s->notify_state = NOTIFY_RELOADING; - - if (s->state == SERVICE_RUNNING) - service_enter_reload_by_notify(s); - - notify_dbus = true; - } - - /* Interpret READY= */ - if (strv_find(tags, "READY=1")) { - - s->notify_state = NOTIFY_READY; - - /* Type=notify services inform us about completed - * initialization with READY=1 */ - if (s->type == SERVICE_NOTIFY && s->state == SERVICE_START) - service_enter_start_post(s); - - /* Sending READY=1 while we are reloading informs us - * that the reloading is complete */ - if (s->state == SERVICE_RELOAD && s->control_pid == 0) - service_enter_running(s, SERVICE_SUCCESS); - - notify_dbus = true; - } - - /* Interpret STOPPING= */ - if (strv_find(tags, "STOPPING=1")) { - - s->notify_state = NOTIFY_STOPPING; - - if (s->state == SERVICE_RUNNING) - service_enter_stop_by_notify(s); - - notify_dbus = true; - } - - /* Interpret STATUS= */ - e = strv_find_startswith(tags, "STATUS="); - if (e) { - _cleanup_free_ char *t = NULL; - - if (!isempty(e)) { - if (!utf8_is_valid(e)) - log_unit_warning(u, "Status message in notification message is not UTF-8 clean."); - else { - t = strdup(e); - if (!t) - log_oom(); - } - } - - if (!streq_ptr(s->status_text, t)) { - - free(s->status_text); - s->status_text = t; - t = NULL; - - notify_dbus = true; - } - } - - /* Interpret ERRNO= */ - e = strv_find_startswith(tags, "ERRNO="); - if (e) { - int status_errno; - - if (safe_atoi(e, &status_errno) < 0 || status_errno < 0) - log_unit_warning(u, "Failed to parse ERRNO= field in notification message: %s", e); - else { - if (s->status_errno != status_errno) { - s->status_errno = status_errno; - notify_dbus = true; - } - } - } - - /* Interpret WATCHDOG= */ - if (strv_find(tags, "WATCHDOG=1")) - service_reset_watchdog(s); - - if (strv_find(tags, "FDSTORE=1")) { - const char *name; - - name = strv_find_startswith(tags, "FDNAME="); - if (name && !fdname_is_valid(name)) { - log_unit_warning(u, "Passed FDNAME= name is invalid, ignoring."); - name = NULL; - } - - service_add_fd_store_set(s, fds, name); - } - - /* Notify clients about changed status or main pid */ - if (notify_dbus) - unit_add_to_dbus_queue(u); -} - -static int service_get_timeout(Unit *u, usec_t *timeout) { - Service *s = SERVICE(u); - uint64_t t; - int r; - - if (!s->timer_event_source) - return 0; - - r = sd_event_source_get_time(s->timer_event_source, &t); - if (r < 0) - return r; - if (t == USEC_INFINITY) - return 0; - - *timeout = t; - return 1; -} - -static void service_bus_name_owner_change( - Unit *u, - const char *name, - const char *old_owner, - const char *new_owner) { - - Service *s = SERVICE(u); - int r; - - assert(s); - assert(name); - - assert(streq(s->bus_name, name)); - assert(old_owner || new_owner); - - if (old_owner && new_owner) - log_unit_debug(u, "D-Bus name %s changed owner from %s to %s", name, old_owner, new_owner); - else if (old_owner) - log_unit_debug(u, "D-Bus name %s no longer registered by %s", name, old_owner); - else - log_unit_debug(u, "D-Bus name %s now registered by %s", name, new_owner); - - s->bus_name_good = !!new_owner; - - /* Track the current owner, so we can reconstruct changes after a daemon reload */ - r = free_and_strdup(&s->bus_name_owner, new_owner); - if (r < 0) { - log_unit_error_errno(u, r, "Unable to set new bus name owner %s: %m", new_owner); - return; - } - - if (s->type == SERVICE_DBUS) { - - /* service_enter_running() will figure out what to - * do */ - if (s->state == SERVICE_RUNNING) - service_enter_running(s, SERVICE_SUCCESS); - else if (s->state == SERVICE_START && new_owner) - service_enter_start_post(s); - - } else if (new_owner && - s->main_pid <= 0 && - (s->state == SERVICE_START || - s->state == SERVICE_START_POST || - s->state == SERVICE_RUNNING || - s->state == SERVICE_RELOAD)) { - - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - pid_t pid; - - /* Try to acquire PID from bus service */ - - r = sd_bus_get_name_creds(u->manager->api_bus, name, SD_BUS_CREDS_PID, &creds); - if (r >= 0) - r = sd_bus_creds_get_pid(creds, &pid); - if (r >= 0) { - log_unit_debug(u, "D-Bus name %s is now owned by process %u", name, (unsigned) pid); - - service_set_main_pid(s, pid); - unit_watch_pid(UNIT(s), pid); - } - } -} - -int service_set_socket_fd(Service *s, int fd, Socket *sock, bool selinux_context_net) { - _cleanup_free_ char *peer = NULL; - int r; - - assert(s); - assert(fd >= 0); - - /* This is called by the socket code when instantiating a new service for a stream socket and the socket needs - * to be configured. We take ownership of the passed fd on success. */ - - if (UNIT(s)->load_state != UNIT_LOADED) - return -EINVAL; - - if (s->socket_fd >= 0) - return -EBUSY; - - if (s->state != SERVICE_DEAD) - return -EAGAIN; - - if (getpeername_pretty(fd, true, &peer) >= 0) { - - if (UNIT(s)->description) { - _cleanup_free_ char *a; - - a = strjoin(UNIT(s)->description, " (", peer, ")", NULL); - if (!a) - return -ENOMEM; - - r = unit_set_description(UNIT(s), a); - } else - r = unit_set_description(UNIT(s), peer); - - if (r < 0) - return r; - } - - r = unit_add_two_dependencies(UNIT(sock), UNIT_BEFORE, UNIT_TRIGGERS, UNIT(s), false); - if (r < 0) - return r; - - s->socket_fd = fd; - s->socket_fd_selinux_context_net = selinux_context_net; - - unit_ref_set(&s->accept_socket, UNIT(sock)); - return 0; -} - -static void service_reset_failed(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - if (s->state == SERVICE_FAILED) - service_set_state(s, SERVICE_DEAD); - - s->result = SERVICE_SUCCESS; - s->reload_result = SERVICE_SUCCESS; -} - -static int service_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { - Service *s = SERVICE(u); - - return unit_kill_common(u, who, signo, s->main_pid, s->control_pid, error); -} - -static int service_main_pid(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - return s->main_pid; -} - -static int service_control_pid(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - return s->control_pid; -} - -static const char* const service_restart_table[_SERVICE_RESTART_MAX] = { - [SERVICE_RESTART_NO] = "no", - [SERVICE_RESTART_ON_SUCCESS] = "on-success", - [SERVICE_RESTART_ON_FAILURE] = "on-failure", - [SERVICE_RESTART_ON_ABNORMAL] = "on-abnormal", - [SERVICE_RESTART_ON_WATCHDOG] = "on-watchdog", - [SERVICE_RESTART_ON_ABORT] = "on-abort", - [SERVICE_RESTART_ALWAYS] = "always", -}; - -DEFINE_STRING_TABLE_LOOKUP(service_restart, ServiceRestart); - -static const char* const service_type_table[_SERVICE_TYPE_MAX] = { - [SERVICE_SIMPLE] = "simple", - [SERVICE_FORKING] = "forking", - [SERVICE_ONESHOT] = "oneshot", - [SERVICE_DBUS] = "dbus", - [SERVICE_NOTIFY] = "notify", - [SERVICE_IDLE] = "idle" -}; - -DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType); - -static const char* const service_exec_command_table[_SERVICE_EXEC_COMMAND_MAX] = { - [SERVICE_EXEC_START_PRE] = "ExecStartPre", - [SERVICE_EXEC_START] = "ExecStart", - [SERVICE_EXEC_START_POST] = "ExecStartPost", - [SERVICE_EXEC_RELOAD] = "ExecReload", - [SERVICE_EXEC_STOP] = "ExecStop", - [SERVICE_EXEC_STOP_POST] = "ExecStopPost", -}; - -DEFINE_STRING_TABLE_LOOKUP(service_exec_command, ServiceExecCommand); - -static const char* const notify_access_table[_NOTIFY_ACCESS_MAX] = { - [NOTIFY_NONE] = "none", - [NOTIFY_MAIN] = "main", - [NOTIFY_ALL] = "all" -}; - -DEFINE_STRING_TABLE_LOOKUP(notify_access, NotifyAccess); - -static const char* const notify_state_table[_NOTIFY_STATE_MAX] = { - [NOTIFY_UNKNOWN] = "unknown", - [NOTIFY_READY] = "ready", - [NOTIFY_RELOADING] = "reloading", - [NOTIFY_STOPPING] = "stopping", -}; - -DEFINE_STRING_TABLE_LOOKUP(notify_state, NotifyState); - -static const char* const service_result_table[_SERVICE_RESULT_MAX] = { - [SERVICE_SUCCESS] = "success", - [SERVICE_FAILURE_RESOURCES] = "resources", - [SERVICE_FAILURE_TIMEOUT] = "timeout", - [SERVICE_FAILURE_EXIT_CODE] = "exit-code", - [SERVICE_FAILURE_SIGNAL] = "signal", - [SERVICE_FAILURE_CORE_DUMP] = "core-dump", - [SERVICE_FAILURE_WATCHDOG] = "watchdog", - [SERVICE_FAILURE_START_LIMIT_HIT] = "start-limit-hit", -}; - -DEFINE_STRING_TABLE_LOOKUP(service_result, ServiceResult); - -const UnitVTable service_vtable = { - .object_size = sizeof(Service), - .exec_context_offset = offsetof(Service, exec_context), - .cgroup_context_offset = offsetof(Service, cgroup_context), - .kill_context_offset = offsetof(Service, kill_context), - .exec_runtime_offset = offsetof(Service, exec_runtime), - - .sections = - "Unit\0" - "Service\0" - "Install\0", - .private_section = "Service", - - .init = service_init, - .done = service_done, - .load = service_load, - .release_resources = service_release_resources, - - .coldplug = service_coldplug, - - .dump = service_dump, - - .start = service_start, - .stop = service_stop, - .reload = service_reload, - - .can_reload = service_can_reload, - - .kill = service_kill, - - .serialize = service_serialize, - .deserialize_item = service_deserialize_item, - - .active_state = service_active_state, - .sub_state_to_string = service_sub_state_to_string, - - .check_gc = service_check_gc, - - .sigchld_event = service_sigchld_event, - - .reset_failed = service_reset_failed, - - .notify_cgroup_empty = service_notify_cgroup_empty_event, - .notify_message = service_notify_message, - - .main_pid = service_main_pid, - .control_pid = service_control_pid, - - .bus_name_owner_change = service_bus_name_owner_change, - - .bus_vtable = bus_service_vtable, - .bus_set_property = bus_service_set_property, - .bus_commit_properties = bus_service_commit_properties, - - .get_timeout = service_get_timeout, - .can_transient = true, - - .status_message_formats = { - .starting_stopping = { - [0] = "Starting %s...", - [1] = "Stopping %s...", - }, - .finished_start_job = { - [JOB_DONE] = "Started %s.", - [JOB_FAILED] = "Failed to start %s.", - }, - .finished_stop_job = { - [JOB_DONE] = "Stopped %s.", - [JOB_FAILED] = "Stopped (with error) %s.", - }, - }, -}; diff --git a/src/core/service.h b/src/core/service.h deleted file mode 100644 index 4af3d40439..0000000000 --- a/src/core/service.h +++ /dev/null @@ -1,220 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct Service Service; -typedef struct ServiceFDStore ServiceFDStore; - -#include "exit-status.h" -#include "kill.h" -#include "path.h" -#include "ratelimit.h" - -typedef enum ServiceRestart { - SERVICE_RESTART_NO, - SERVICE_RESTART_ON_SUCCESS, - SERVICE_RESTART_ON_FAILURE, - SERVICE_RESTART_ON_ABNORMAL, - SERVICE_RESTART_ON_WATCHDOG, - SERVICE_RESTART_ON_ABORT, - SERVICE_RESTART_ALWAYS, - _SERVICE_RESTART_MAX, - _SERVICE_RESTART_INVALID = -1 -} ServiceRestart; - -typedef enum ServiceType { - SERVICE_SIMPLE, /* we fork and go on right-away (i.e. modern socket activated daemons) */ - SERVICE_FORKING, /* forks by itself (i.e. traditional daemons) */ - SERVICE_ONESHOT, /* we fork and wait until the program finishes (i.e. programs like fsck which run and need to finish before we continue) */ - SERVICE_DBUS, /* we fork and wait until a specific D-Bus name appears on the bus */ - SERVICE_NOTIFY, /* we fork and wait until a daemon sends us a ready message with sd_notify() */ - SERVICE_IDLE, /* much like simple, but delay exec() until all jobs are dispatched. */ - _SERVICE_TYPE_MAX, - _SERVICE_TYPE_INVALID = -1 -} ServiceType; - -typedef enum ServiceExecCommand { - SERVICE_EXEC_START_PRE, - SERVICE_EXEC_START, - SERVICE_EXEC_START_POST, - SERVICE_EXEC_RELOAD, - SERVICE_EXEC_STOP, - SERVICE_EXEC_STOP_POST, - _SERVICE_EXEC_COMMAND_MAX, - _SERVICE_EXEC_COMMAND_INVALID = -1 -} ServiceExecCommand; - -typedef enum NotifyAccess { - NOTIFY_NONE, - NOTIFY_ALL, - NOTIFY_MAIN, - _NOTIFY_ACCESS_MAX, - _NOTIFY_ACCESS_INVALID = -1 -} NotifyAccess; - -typedef enum NotifyState { - NOTIFY_UNKNOWN, - NOTIFY_READY, - NOTIFY_RELOADING, - NOTIFY_STOPPING, - _NOTIFY_STATE_MAX, - _NOTIFY_STATE_INVALID = -1 -} NotifyState; - -typedef enum ServiceResult { - SERVICE_SUCCESS, - SERVICE_FAILURE_RESOURCES, /* a bit of a misnomer, just our catch-all error for errnos we didn't expect */ - SERVICE_FAILURE_TIMEOUT, - SERVICE_FAILURE_EXIT_CODE, - SERVICE_FAILURE_SIGNAL, - SERVICE_FAILURE_CORE_DUMP, - SERVICE_FAILURE_WATCHDOG, - SERVICE_FAILURE_START_LIMIT_HIT, - _SERVICE_RESULT_MAX, - _SERVICE_RESULT_INVALID = -1 -} ServiceResult; - -struct ServiceFDStore { - Service *service; - - int fd; - char *fdname; - sd_event_source *event_source; - - LIST_FIELDS(ServiceFDStore, fd_store); -}; - -struct Service { - Unit meta; - - ServiceType type; - ServiceRestart restart; - ExitStatusSet restart_prevent_status; - ExitStatusSet restart_force_status; - ExitStatusSet success_status; - - /* If set we'll read the main daemon PID from this file */ - char *pid_file; - - usec_t restart_usec; - usec_t timeout_start_usec; - usec_t timeout_stop_usec; - usec_t runtime_max_usec; - - dual_timestamp watchdog_timestamp; - usec_t watchdog_usec; - sd_event_source *watchdog_event_source; - - ExecCommand* exec_command[_SERVICE_EXEC_COMMAND_MAX]; - - ExecContext exec_context; - KillContext kill_context; - CGroupContext cgroup_context; - - ServiceState state, deserialized_state; - - /* The exit status of the real main process */ - ExecStatus main_exec_status; - - /* The currently executed control process */ - ExecCommand *control_command; - - /* The currently executed main process, which may be NULL if - * the main process got started via forking mode and not by - * us */ - ExecCommand *main_command; - - /* The ID of the control command currently being executed */ - ServiceExecCommand control_command_id; - - /* Runtime data of the execution context */ - ExecRuntime *exec_runtime; - - pid_t main_pid, control_pid; - int socket_fd; - bool socket_fd_selinux_context_net; - - bool permissions_start_only; - bool root_directory_start_only; - bool remain_after_exit; - bool guess_main_pid; - - /* If we shut down, remember why */ - ServiceResult result; - ServiceResult reload_result; - - bool main_pid_known:1; - bool main_pid_alien:1; - bool bus_name_good:1; - bool forbid_restart:1; - bool start_timeout_defined:1; - - bool reset_cpu_usage:1; - - char *bus_name; - char *bus_name_owner; /* unique name of the current owner */ - - char *status_text; - int status_errno; - - FailureAction failure_action; - - UnitRef accept_socket; - - sd_event_source *timer_event_source; - PathSpec *pid_file_pathspec; - - NotifyAccess notify_access; - NotifyState notify_state; - - ServiceFDStore *fd_store; - unsigned n_fd_store; - unsigned n_fd_store_max; - - char *usb_function_descriptors; - char *usb_function_strings; - - int stdin_fd; - int stdout_fd; - int stderr_fd; -}; - -extern const UnitVTable service_vtable; - -int service_set_socket_fd(Service *s, int fd, struct Socket *socket, bool selinux_context_net); -void service_close_socket_fd(Service *s); - -const char* service_restart_to_string(ServiceRestart i) _const_; -ServiceRestart service_restart_from_string(const char *s) _pure_; - -const char* service_type_to_string(ServiceType i) _const_; -ServiceType service_type_from_string(const char *s) _pure_; - -const char* service_exec_command_to_string(ServiceExecCommand i) _const_; -ServiceExecCommand service_exec_command_from_string(const char *s) _pure_; - -const char* notify_access_to_string(NotifyAccess i) _const_; -NotifyAccess notify_access_from_string(const char *s) _pure_; - -const char* notify_state_to_string(NotifyState i) _const_; -NotifyState notify_state_from_string(const char *s) _pure_; - -const char* service_result_to_string(ServiceResult i) _const_; -ServiceResult service_result_from_string(const char *s) _pure_; diff --git a/src/core/show-status.c b/src/core/show-status.c deleted file mode 100644 index 59ebdc7219..0000000000 --- a/src/core/show-status.c +++ /dev/null @@ -1,124 +0,0 @@ -/*** - 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 "alloc-util.h" -#include "fd-util.h" -#include "io-util.h" -#include "parse-util.h" -#include "show-status.h" -#include "string-util.h" -#include "terminal-util.h" -#include "util.h" - -int parse_show_status(const char *v, ShowStatus *ret) { - int r; - - assert(v); - assert(ret); - - if (streq(v, "auto")) { - *ret = SHOW_STATUS_AUTO; - return 0; - } - - r = parse_boolean(v); - if (r < 0) - return r; - - *ret = r ? SHOW_STATUS_YES : SHOW_STATUS_NO; - return 0; -} - -int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) { - static const char status_indent[] = " "; /* "[" STATUS "] " */ - _cleanup_free_ char *s = NULL; - _cleanup_close_ int fd = -1; - struct iovec iovec[6] = {}; - int n = 0; - static bool prev_ephemeral; - - assert(format); - - /* This is independent of logging, as status messages are - * optional and go exclusively to the console. */ - - if (vasprintf(&s, format, ap) < 0) - return log_oom(); - - fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return fd; - - if (ellipse) { - char *e; - size_t emax, sl; - int c; - - c = fd_columns(fd); - if (c <= 0) - c = 80; - - sl = status ? sizeof(status_indent)-1 : 0; - - emax = c - sl - 1; - if (emax < 3) - emax = 3; - - e = ellipsize(s, emax, 50); - if (e) { - free(s); - s = e; - } - } - - if (prev_ephemeral) - IOVEC_SET_STRING(iovec[n++], "\r" ANSI_ERASE_TO_END_OF_LINE); - prev_ephemeral = ephemeral; - - if (status) { - if (!isempty(status)) { - IOVEC_SET_STRING(iovec[n++], "["); - IOVEC_SET_STRING(iovec[n++], status); - IOVEC_SET_STRING(iovec[n++], "] "); - } else - IOVEC_SET_STRING(iovec[n++], status_indent); - } - - IOVEC_SET_STRING(iovec[n++], s); - if (!ephemeral) - IOVEC_SET_STRING(iovec[n++], "\n"); - - if (writev(fd, iovec, n) < 0) - return -errno; - - return 0; -} - -int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) { - va_list ap; - int r; - - assert(format); - - va_start(ap, format); - r = status_vprintf(status, ellipse, ephemeral, format, ap); - va_end(ap); - - return r; -} diff --git a/src/core/show-status.h b/src/core/show-status.h deleted file mode 100644 index 9a29e72645..0000000000 --- a/src/core/show-status.h +++ /dev/null @@ -1,39 +0,0 @@ -#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 - -#include "macro.h" - -/* Manager status */ - -typedef enum ShowStatus { - _SHOW_STATUS_UNSET = -2, - SHOW_STATUS_AUTO = -1, - SHOW_STATUS_NO = 0, - SHOW_STATUS_YES = 1, - SHOW_STATUS_TEMPORARY = 2, -} ShowStatus; - -int parse_show_status(const char *v, ShowStatus *ret); - -int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) _printf_(4,0); -int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) _printf_(4,5); diff --git a/src/core/shutdown.c b/src/core/shutdown.c deleted file mode 100644 index e14755d84e..0000000000 --- a/src/core/shutdown.c +++ /dev/null @@ -1,440 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 ProFUSION embedded systems - - 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "cgroup-util.h" -#include "def.h" -#include "fileio.h" -#include "killall.h" -#include "log.h" -#include "missing.h" -#include "parse-util.h" -#include "process-util.h" -#include "string-util.h" -#include "switch-root.h" -#include "terminal-util.h" -#include "umount.h" -#include "util.h" -#include "virt.h" -#include "watchdog.h" - -#define FINALIZE_ATTEMPTS 50 - -static char* arg_verb; -static uint8_t arg_exit_code; - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_LOG_LEVEL = 0x100, - ARG_LOG_TARGET, - ARG_LOG_COLOR, - ARG_LOG_LOCATION, - ARG_EXIT_CODE, - }; - - static const struct option options[] = { - { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, - { "log-target", required_argument, NULL, ARG_LOG_TARGET }, - { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, - { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, - { "exit-code", required_argument, NULL, ARG_EXIT_CODE }, - {} - }; - - int c, r; - - assert(argc >= 1); - assert(argv); - - /* "-" prevents getopt from permuting argv[] and moving the verb away - * from argv[1]. Our interface to initrd promises it'll be there. */ - while ((c = getopt_long(argc, argv, "-", options, NULL)) >= 0) - switch (c) { - - case ARG_LOG_LEVEL: - r = log_set_max_level_from_string(optarg); - if (r < 0) - log_error("Failed to parse log level %s, ignoring.", optarg); - - break; - - case ARG_LOG_TARGET: - r = log_set_target_from_string(optarg); - if (r < 0) - log_error("Failed to parse log target %s, ignoring", optarg); - - break; - - case ARG_LOG_COLOR: - - if (optarg) { - r = log_show_color_from_string(optarg); - if (r < 0) - log_error("Failed to parse log color setting %s, ignoring", optarg); - } else - log_show_color(true); - - break; - - case ARG_LOG_LOCATION: - if (optarg) { - r = log_show_location_from_string(optarg); - if (r < 0) - log_error("Failed to parse log location setting %s, ignoring", optarg); - } else - log_show_location(true); - - break; - - case ARG_EXIT_CODE: - r = safe_atou8(optarg, &arg_exit_code); - if (r < 0) - log_error("Failed to parse exit code %s, ignoring", optarg); - - break; - - case '\001': - if (!arg_verb) - arg_verb = optarg; - else - log_error("Excess arguments, ignoring"); - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option code."); - } - - if (!arg_verb) { - log_error("Verb argument missing."); - return -EINVAL; - } - - return 0; -} - -static int switch_root_initramfs(void) { - if (mount("/run/initramfs", "/run/initramfs", NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Failed to mount bind /run/initramfs on /run/initramfs: %m"); - - if (mount(NULL, "/run/initramfs", NULL, MS_PRIVATE, NULL) < 0) - return log_error_errno(errno, "Failed to make /run/initramfs private mount: %m"); - - /* switch_root with MS_BIND, because there might still be processes lurking around, which have open file descriptors. - * /run/initramfs/shutdown will take care of these. - * Also do not detach the old root, because /run/initramfs/shutdown needs to access it. - */ - return switch_root("/run/initramfs", "/oldroot", false, MS_BIND); -} - - -int main(int argc, char *argv[]) { - bool need_umount, need_swapoff, need_loop_detach, need_dm_detach; - bool in_container, use_watchdog = false; - _cleanup_free_ char *cgroup = NULL; - char *arguments[3]; - unsigned retries; - int cmd, r; - static const char* const dirs[] = {SYSTEM_SHUTDOWN_PATH, NULL}; - - log_parse_environment(); - r = parse_argv(argc, argv); - if (r < 0) - goto error; - - /* journald will die if not gone yet. The log target defaults - * to console, but may have been changed by command line options. */ - - log_close_console(); /* force reopen of /dev/console */ - log_open(); - - umask(0022); - - if (getpid() != 1) { - log_error("Not executed by init (PID 1)."); - r = -EPERM; - goto error; - } - - if (streq(arg_verb, "reboot")) - cmd = RB_AUTOBOOT; - else if (streq(arg_verb, "poweroff")) - cmd = RB_POWER_OFF; - else if (streq(arg_verb, "halt")) - cmd = RB_HALT_SYSTEM; - else if (streq(arg_verb, "kexec")) - cmd = LINUX_REBOOT_CMD_KEXEC; - else if (streq(arg_verb, "exit")) - cmd = 0; /* ignored, just checking that arg_verb is valid */ - else { - r = -EINVAL; - log_error("Unknown action '%s'.", arg_verb); - goto error; - } - - (void) cg_get_root_path(&cgroup); - - use_watchdog = !!getenv("WATCHDOG_USEC"); - - /* lock us into memory */ - mlockall(MCL_CURRENT|MCL_FUTURE); - - log_info("Sending SIGTERM to remaining processes..."); - broadcast_signal(SIGTERM, true, true); - - log_info("Sending SIGKILL to remaining processes..."); - broadcast_signal(SIGKILL, true, false); - - in_container = detect_container() > 0; - - need_umount = !in_container; - need_swapoff = !in_container; - need_loop_detach = !in_container; - need_dm_detach = !in_container; - - /* Unmount all mountpoints, swaps, and loopback devices */ - for (retries = 0; retries < FINALIZE_ATTEMPTS; retries++) { - bool changed = false; - - if (use_watchdog) - watchdog_ping(); - - /* Let's trim the cgroup tree on each iteration so - that we leave an empty cgroup tree around, so that - container managers get a nice notify event when we - are down */ - if (cgroup) - cg_trim(SYSTEMD_CGROUP_CONTROLLER, cgroup, false); - - if (need_umount) { - log_info("Unmounting file systems."); - r = umount_all(&changed); - if (r == 0) { - need_umount = false; - log_info("All filesystems unmounted."); - } else if (r > 0) - log_info("Not all file systems unmounted, %d left.", r); - else - log_error_errno(r, "Failed to unmount file systems: %m"); - } - - if (need_swapoff) { - log_info("Deactivating swaps."); - r = swapoff_all(&changed); - if (r == 0) { - need_swapoff = false; - log_info("All swaps deactivated."); - } else if (r > 0) - log_info("Not all swaps deactivated, %d left.", r); - else - log_error_errno(r, "Failed to deactivate swaps: %m"); - } - - if (need_loop_detach) { - log_info("Detaching loop devices."); - r = loopback_detach_all(&changed); - if (r == 0) { - need_loop_detach = false; - log_info("All loop devices detached."); - } else if (r > 0) - log_info("Not all loop devices detached, %d left.", r); - else - log_error_errno(r, "Failed to detach loop devices: %m"); - } - - if (need_dm_detach) { - log_info("Detaching DM devices."); - r = dm_detach_all(&changed); - if (r == 0) { - need_dm_detach = false; - log_info("All DM devices detached."); - } else if (r > 0) - log_info("Not all DM devices detached, %d left.", r); - else - log_error_errno(r, "Failed to detach DM devices: %m"); - } - - if (!need_umount && !need_swapoff && !need_loop_detach && !need_dm_detach) { - if (retries > 0) - log_info("All filesystems, swaps, loop devices, DM devices detached."); - /* Yay, done */ - goto initrd_jump; - } - - /* If in this iteration we didn't manage to - * unmount/deactivate anything, we simply give up */ - if (!changed) { - log_info("Cannot finalize remaining%s%s%s%s continuing.", - need_umount ? " file systems," : "", - need_swapoff ? " swap devices," : "", - need_loop_detach ? " loop devices," : "", - need_dm_detach ? " DM devices," : ""); - goto initrd_jump; - } - - log_debug("After %u retries, couldn't finalize remaining %s%s%s%s trying again.", - retries + 1, - need_umount ? " file systems," : "", - need_swapoff ? " swap devices," : "", - need_loop_detach ? " loop devices," : "", - need_dm_detach ? " DM devices," : ""); - } - - log_error("Too many iterations, giving up."); - - initrd_jump: - - arguments[0] = NULL; - arguments[1] = arg_verb; - arguments[2] = NULL; - execute_directories(dirs, DEFAULT_TIMEOUT_USEC, arguments); - - if (!in_container && !in_initrd() && - access("/run/initramfs/shutdown", X_OK) == 0) { - r = switch_root_initramfs(); - if (r >= 0) { - argv[0] = (char*) "/shutdown"; - - setsid(); - make_console_stdio(); - - log_info("Successfully changed into root pivot.\n" - "Returning to initrd..."); - - execv("/shutdown", argv); - log_error_errno(errno, "Failed to execute shutdown binary: %m"); - } else - log_error_errno(r, "Failed to switch root to \"/run/initramfs\": %m"); - - } - - if (need_umount || need_swapoff || need_loop_detach || need_dm_detach) - log_error("Failed to finalize %s%s%s%s ignoring", - need_umount ? " file systems," : "", - need_swapoff ? " swap devices," : "", - need_loop_detach ? " loop devices," : "", - need_dm_detach ? " DM devices," : ""); - - /* The kernel will automaticall flush ATA disks and suchlike - * on reboot(), but the file systems need to be synce'd - * explicitly in advance. So let's do this here, but not - * needlessly slow down containers. */ - if (!in_container) - sync(); - - if (streq(arg_verb, "exit")) { - if (in_container) - exit(arg_exit_code); - else { - /* We cannot exit() on the host, fallback on another - * method. */ - cmd = RB_POWER_OFF; - } - } - - switch (cmd) { - - case LINUX_REBOOT_CMD_KEXEC: - - if (!in_container) { - /* We cheat and exec kexec to avoid doing all its work */ - pid_t pid; - - log_info("Rebooting with kexec."); - - pid = fork(); - if (pid < 0) - log_error_errno(errno, "Failed to fork: %m"); - else if (pid == 0) { - - const char * const args[] = { - KEXEC, "-e", NULL - }; - - /* Child */ - - execv(args[0], (char * const *) args); - _exit(EXIT_FAILURE); - } else - wait_for_terminate_and_warn("kexec", pid, true); - } - - cmd = RB_AUTOBOOT; - /* Fall through */ - - case RB_AUTOBOOT: - - if (!in_container) { - _cleanup_free_ char *param = NULL; - - r = read_one_line_file("/run/systemd/reboot-param", ¶m); - if (r < 0) - log_warning_errno(r, "Failed to read reboot parameter file: %m"); - - if (!isempty(param)) { - log_info("Rebooting with argument '%s'.", param); - syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, param); - log_warning_errno(errno, "Failed to reboot with parameter, retrying without: %m"); - } - } - - log_info("Rebooting."); - break; - - case RB_POWER_OFF: - log_info("Powering off."); - break; - - case RB_HALT_SYSTEM: - log_info("Halting system."); - break; - - default: - assert_not_reached("Unknown magic"); - } - - reboot(cmd); - if (errno == EPERM && in_container) { - /* If we are in a container, and we lacked - * CAP_SYS_BOOT just exit, this will kill our - * container for good. */ - log_info("Exiting container."); - exit(0); - } - - r = log_error_errno(errno, "Failed to invoke reboot(): %m"); - - error: - log_emergency_errno(r, "Critical error while doing system shutdown: %m"); - freeze(); -} diff --git a/src/core/slice.c b/src/core/slice.c deleted file mode 100644 index c7700b8857..0000000000 --- a/src/core/slice.c +++ /dev/null @@ -1,346 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "alloc-util.h" -#include "dbus-slice.h" -#include "log.h" -#include "slice.h" -#include "special.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "unit.h" - -static const UnitActiveState state_translation_table[_SLICE_STATE_MAX] = { - [SLICE_DEAD] = UNIT_INACTIVE, - [SLICE_ACTIVE] = UNIT_ACTIVE -}; - -static void slice_init(Unit *u) { - assert(u); - assert(u->load_state == UNIT_STUB); - - u->ignore_on_isolate = true; -} - -static void slice_set_state(Slice *t, SliceState state) { - SliceState old_state; - assert(t); - - old_state = t->state; - t->state = state; - - if (state != old_state) - log_debug("%s changed %s -> %s", - UNIT(t)->id, - slice_state_to_string(old_state), - slice_state_to_string(state)); - - unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], true); -} - -static int slice_add_parent_slice(Slice *s) { - char *a, *dash; - Unit *parent; - int r; - - assert(s); - - if (UNIT_ISSET(UNIT(s)->slice)) - return 0; - - if (unit_has_name(UNIT(s), SPECIAL_ROOT_SLICE)) - return 0; - - a = strdupa(UNIT(s)->id); - dash = strrchr(a, '-'); - if (dash) - strcpy(dash, ".slice"); - else - a = (char*) SPECIAL_ROOT_SLICE; - - r = manager_load_unit(UNIT(s)->manager, a, NULL, NULL, &parent); - if (r < 0) - return r; - - unit_ref_set(&UNIT(s)->slice, parent); - return 0; -} - -static int slice_add_default_dependencies(Slice *s) { - int r; - - assert(s); - - if (!UNIT(s)->default_dependencies) - return 0; - - /* Make sure slices are unloaded on shutdown */ - r = unit_add_two_dependencies_by_name( - UNIT(s), - UNIT_BEFORE, UNIT_CONFLICTS, - SPECIAL_SHUTDOWN_TARGET, NULL, true); - if (r < 0) - return r; - - return 0; -} - -static int slice_verify(Slice *s) { - _cleanup_free_ char *parent = NULL; - int r; - - assert(s); - - if (UNIT(s)->load_state != UNIT_LOADED) - return 0; - - if (!slice_name_is_valid(UNIT(s)->id)) { - log_unit_error(UNIT(s), "Slice name %s is not valid. Refusing.", UNIT(s)->id); - return -EINVAL; - } - - r = slice_build_parent_slice(UNIT(s)->id, &parent); - if (r < 0) - return log_unit_error_errno(UNIT(s), r, "Failed to determine parent slice: %m"); - - if (parent ? !unit_has_name(UNIT_DEREF(UNIT(s)->slice), parent) : UNIT_ISSET(UNIT(s)->slice)) { - log_unit_error(UNIT(s), "Located outside of parent slice. Refusing."); - return -EINVAL; - } - - return 0; -} - -static int slice_load(Unit *u) { - Slice *s = SLICE(u); - int r; - - assert(s); - assert(u->load_state == UNIT_STUB); - - r = unit_load_fragment_and_dropin_optional(u); - if (r < 0) - return r; - - /* This is a new unit? Then let's add in some extras */ - if (u->load_state == UNIT_LOADED) { - - r = unit_patch_contexts(u); - if (r < 0) - return r; - - r = slice_add_parent_slice(s); - if (r < 0) - return r; - - r = slice_add_default_dependencies(s); - if (r < 0) - return r; - } - - return slice_verify(s); -} - -static int slice_coldplug(Unit *u) { - Slice *t = SLICE(u); - - assert(t); - assert(t->state == SLICE_DEAD); - - if (t->deserialized_state != t->state) - slice_set_state(t, t->deserialized_state); - - return 0; -} - -static void slice_dump(Unit *u, FILE *f, const char *prefix) { - Slice *t = SLICE(u); - - assert(t); - assert(f); - - fprintf(f, - "%sSlice State: %s\n", - prefix, slice_state_to_string(t->state)); - - cgroup_context_dump(&t->cgroup_context, f, prefix); -} - -static int slice_start(Unit *u) { - Slice *t = SLICE(u); - - assert(t); - assert(t->state == SLICE_DEAD); - - (void) unit_realize_cgroup(u); - (void) unit_reset_cpu_usage(u); - - slice_set_state(t, SLICE_ACTIVE); - return 1; -} - -static int slice_stop(Unit *u) { - Slice *t = SLICE(u); - - assert(t); - assert(t->state == SLICE_ACTIVE); - - /* We do not need to destroy the cgroup explicitly, - * unit_notify() will do that for us anyway. */ - - slice_set_state(t, SLICE_DEAD); - return 1; -} - -static int slice_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { - return unit_kill_common(u, who, signo, -1, -1, error); -} - -static int slice_serialize(Unit *u, FILE *f, FDSet *fds) { - Slice *s = SLICE(u); - - assert(s); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", slice_state_to_string(s->state)); - return 0; -} - -static int slice_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Slice *s = SLICE(u); - - assert(u); - assert(key); - assert(value); - assert(fds); - - if (streq(key, "state")) { - SliceState state; - - state = slice_state_from_string(value); - if (state < 0) - log_debug("Failed to parse state value %s", value); - else - s->deserialized_state = state; - - } else - log_debug("Unknown serialization key '%s'", key); - - return 0; -} - -_pure_ static UnitActiveState slice_active_state(Unit *u) { - assert(u); - - return state_translation_table[SLICE(u)->state]; -} - -_pure_ static const char *slice_sub_state_to_string(Unit *u) { - assert(u); - - return slice_state_to_string(SLICE(u)->state); -} - -static void slice_enumerate(Manager *m) { - Unit *u; - int r; - - assert(m); - - u = manager_get_unit(m, SPECIAL_ROOT_SLICE); - if (!u) { - u = unit_new(m, sizeof(Slice)); - if (!u) { - log_oom(); - return; - } - - r = unit_add_name(u, SPECIAL_ROOT_SLICE); - if (r < 0) { - unit_free(u); - log_error_errno(r, "Failed to add -.slice name"); - return; - } - } - - u->default_dependencies = false; - u->no_gc = true; - u->ignore_on_isolate = true; - u->refuse_manual_start = true; - u->refuse_manual_stop = true; - SLICE(u)->deserialized_state = SLICE_ACTIVE; - - if (!u->description) - u->description = strdup("Root Slice"); - if (!u->documentation) - (void) strv_extend(&u->documentation, "man:systemd.special(7)"); - - unit_add_to_load_queue(u); - unit_add_to_dbus_queue(u); -} - -const UnitVTable slice_vtable = { - .object_size = sizeof(Slice), - .cgroup_context_offset = offsetof(Slice, cgroup_context), - - .sections = - "Unit\0" - "Slice\0" - "Install\0", - .private_section = "Slice", - - .can_transient = true, - - .init = slice_init, - .load = slice_load, - - .coldplug = slice_coldplug, - - .dump = slice_dump, - - .start = slice_start, - .stop = slice_stop, - - .kill = slice_kill, - - .serialize = slice_serialize, - .deserialize_item = slice_deserialize_item, - - .active_state = slice_active_state, - .sub_state_to_string = slice_sub_state_to_string, - - .bus_vtable = bus_slice_vtable, - .bus_set_property = bus_slice_set_property, - .bus_commit_properties = bus_slice_commit_properties, - - .enumerate = slice_enumerate, - - .status_message_formats = { - .finished_start_job = { - [JOB_DONE] = "Created slice %s.", - }, - .finished_stop_job = { - [JOB_DONE] = "Removed slice %s.", - }, - }, -}; diff --git a/src/core/slice.h b/src/core/slice.h deleted file mode 100644 index c9f3f61067..0000000000 --- a/src/core/slice.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -typedef struct Slice Slice; - -struct Slice { - Unit meta; - - SliceState state, deserialized_state; - - CGroupContext cgroup_context; -}; - -extern const UnitVTable slice_vtable; diff --git a/src/core/smack-setup.c b/src/core/smack-setup.c deleted file mode 100644 index 5a6d11cfa1..0000000000 --- a/src/core/smack-setup.c +++ /dev/null @@ -1,346 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2013 Intel Corporation - Authors: - Nathaniel Chen - - 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 -#include -#include -#include -#include - -#include "alloc-util.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "log.h" -#include "macro.h" -#include "smack-setup.h" -#include "string-util.h" -#include "util.h" - -#ifdef HAVE_SMACK - -static int write_access2_rules(const char* srcdir) { - _cleanup_close_ int load2_fd = -1, change_fd = -1; - _cleanup_closedir_ DIR *dir = NULL; - struct dirent *entry; - char buf[NAME_MAX]; - int dfd = -1; - int r = 0; - - load2_fd = open("/sys/fs/smackfs/load2", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); - if (load2_fd < 0) { - if (errno != ENOENT) - log_warning_errno(errno, "Failed to open '/sys/fs/smackfs/load2': %m"); - return -errno; /* negative error */ - } - - change_fd = open("/sys/fs/smackfs/change-rule", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); - if (change_fd < 0) { - if (errno != ENOENT) - log_warning_errno(errno, "Failed to open '/sys/fs/smackfs/change-rule': %m"); - return -errno; /* negative error */ - } - - /* write rules to load2 or change-rule from every file in the directory */ - dir = opendir(srcdir); - if (!dir) { - if (errno != ENOENT) - log_warning_errno(errno, "Failed to opendir '%s': %m", srcdir); - return errno; /* positive on purpose */ - } - - dfd = dirfd(dir); - assert(dfd >= 0); - - FOREACH_DIRENT(entry, dir, return 0) { - int fd; - _cleanup_fclose_ FILE *policy = NULL; - - if (!dirent_is_file(entry)) - continue; - - fd = openat(dfd, entry->d_name, O_RDONLY|O_CLOEXEC); - if (fd < 0) { - if (r == 0) - r = -errno; - log_warning_errno(errno, "Failed to open '%s': %m", entry->d_name); - continue; - } - - policy = fdopen(fd, "re"); - if (!policy) { - if (r == 0) - r = -errno; - safe_close(fd); - log_error_errno(errno, "Failed to open '%s': %m", entry->d_name); - continue; - } - - /* load2 write rules in the kernel require a line buffered stream */ - FOREACH_LINE(buf, policy, - log_error_errno(errno, "Failed to read line from '%s': %m", - entry->d_name)) { - - _cleanup_free_ char *sbj = NULL, *obj = NULL, *acc1 = NULL, *acc2 = NULL; - - if (isempty(truncate_nl(buf))) - continue; - - /* if 3 args -> load rule : subject object access1 */ - /* if 4 args -> change rule : subject object access1 access2 */ - if (sscanf(buf, "%ms %ms %ms %ms", &sbj, &obj, &acc1, &acc2) < 3) { - log_error_errno(errno, "Failed to parse rule '%s' in '%s', ignoring.", buf, entry->d_name); - continue; - } - - if (write(isempty(acc2) ? load2_fd : change_fd, buf, strlen(buf)) < 0) { - if (r == 0) - r = -errno; - log_error_errno(errno, "Failed to write '%s' to '%s' in '%s'", - buf, isempty(acc2) ? "/sys/fs/smackfs/load2" : "/sys/fs/smackfs/change-rule", entry->d_name); - } - } - } - - return r; -} - -static int write_cipso2_rules(const char* srcdir) { - _cleanup_close_ int cipso2_fd = -1; - _cleanup_closedir_ DIR *dir = NULL; - struct dirent *entry; - char buf[NAME_MAX]; - int dfd = -1; - int r = 0; - - cipso2_fd = open("/sys/fs/smackfs/cipso2", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); - if (cipso2_fd < 0) { - if (errno != ENOENT) - log_warning_errno(errno, "Failed to open '/sys/fs/smackfs/cipso2': %m"); - return -errno; /* negative error */ - } - - /* write rules to cipso2 from every file in the directory */ - dir = opendir(srcdir); - if (!dir) { - if (errno != ENOENT) - log_warning_errno(errno, "Failed to opendir '%s': %m", srcdir); - return errno; /* positive on purpose */ - } - - dfd = dirfd(dir); - assert(dfd >= 0); - - FOREACH_DIRENT(entry, dir, return 0) { - int fd; - _cleanup_fclose_ FILE *policy = NULL; - - if (!dirent_is_file(entry)) - continue; - - fd = openat(dfd, entry->d_name, O_RDONLY|O_CLOEXEC); - if (fd < 0) { - if (r == 0) - r = -errno; - log_error_errno(errno, "Failed to open '%s': %m", entry->d_name); - continue; - } - - policy = fdopen(fd, "re"); - if (!policy) { - if (r == 0) - r = -errno; - safe_close(fd); - log_error_errno(errno, "Failed to open '%s': %m", entry->d_name); - continue; - } - - /* cipso2 write rules in the kernel require a line buffered stream */ - FOREACH_LINE(buf, policy, - log_error_errno(errno, "Failed to read line from '%s': %m", - entry->d_name)) { - - if (isempty(truncate_nl(buf))) - continue; - - if (write(cipso2_fd, buf, strlen(buf)) < 0) { - if (r == 0) - r = -errno; - log_error_errno(errno, "Failed to write '%s' to '/sys/fs/smackfs/cipso2' in '%s'", - buf, entry->d_name); - break; - } - } - } - - return r; -} - -static int write_netlabel_rules(const char* srcdir) { - _cleanup_fclose_ FILE *dst = NULL; - _cleanup_closedir_ DIR *dir = NULL; - struct dirent *entry; - char buf[NAME_MAX]; - int dfd = -1; - int r = 0; - - dst = fopen("/sys/fs/smackfs/netlabel", "we"); - if (!dst) { - if (errno != ENOENT) - log_warning_errno(errno, "Failed to open /sys/fs/smackfs/netlabel: %m"); - return -errno; /* negative error */ - } - - /* write rules to dst from every file in the directory */ - dir = opendir(srcdir); - if (!dir) { - if (errno != ENOENT) - log_warning_errno(errno, "Failed to opendir %s: %m", srcdir); - return errno; /* positive on purpose */ - } - - dfd = dirfd(dir); - assert(dfd >= 0); - - FOREACH_DIRENT(entry, dir, return 0) { - int fd; - _cleanup_fclose_ FILE *policy = NULL; - - fd = openat(dfd, entry->d_name, O_RDONLY|O_CLOEXEC); - if (fd < 0) { - if (r == 0) - r = -errno; - log_warning_errno(errno, "Failed to open %s: %m", entry->d_name); - continue; - } - - policy = fdopen(fd, "re"); - if (!policy) { - if (r == 0) - r = -errno; - safe_close(fd); - log_error_errno(errno, "Failed to open %s: %m", entry->d_name); - continue; - } - - /* load2 write rules in the kernel require a line buffered stream */ - FOREACH_LINE(buf, policy, - log_error_errno(errno, "Failed to read line from %s: %m", - entry->d_name)) { - if (!fputs(buf, dst)) { - if (r == 0) - r = -EINVAL; - log_error_errno(errno, "Failed to write line to /sys/fs/smackfs/netlabel"); - break; - } - if (fflush(dst)) { - if (r == 0) - r = -errno; - log_error_errno(errno, "Failed to flush writes to /sys/fs/smackfs/netlabel: %m"); - break; - } - } - } - - return r; -} - -#endif - -int mac_smack_setup(bool *loaded_policy) { - -#ifdef HAVE_SMACK - - int r; - - assert(loaded_policy); - - r = write_access2_rules("/etc/smack/accesses.d/"); - switch(r) { - case -ENOENT: - log_debug("Smack is not enabled in the kernel."); - return 0; - case ENOENT: - log_debug("Smack access rules directory '/etc/smack/accesses.d/' not found"); - return 0; - case 0: - log_info("Successfully loaded Smack policies."); - break; - default: - log_warning_errno(r, "Failed to load Smack access rules, ignoring: %m"); - return 0; - } - -#ifdef SMACK_RUN_LABEL - r = write_string_file("/proc/self/attr/current", SMACK_RUN_LABEL, 0); - if (r < 0) - log_warning_errno(r, "Failed to set SMACK label \"" SMACK_RUN_LABEL "\" on self: %m"); - r = write_string_file("/sys/fs/smackfs/ambient", SMACK_RUN_LABEL, 0); - if (r < 0) - log_warning_errno(r, "Failed to set SMACK ambient label \"" SMACK_RUN_LABEL "\": %m"); - r = write_string_file("/sys/fs/smackfs/netlabel", - "0.0.0.0/0 " SMACK_RUN_LABEL, 0); - if (r < 0) - log_warning_errno(r, "Failed to set SMACK netlabel rule \"0.0.0.0/0 " SMACK_RUN_LABEL "\": %m"); - r = write_string_file("/sys/fs/smackfs/netlabel", "127.0.0.1 -CIPSO", 0); - if (r < 0) - log_warning_errno(r, "Failed to set SMACK netlabel rule \"127.0.0.1 -CIPSO\": %m"); -#endif - - r = write_cipso2_rules("/etc/smack/cipso.d/"); - switch(r) { - case -ENOENT: - log_debug("Smack/CIPSO is not enabled in the kernel."); - return 0; - case ENOENT: - log_debug("Smack/CIPSO access rules directory '/etc/smack/cipso.d/' not found"); - break; - case 0: - log_info("Successfully loaded Smack/CIPSO policies."); - break; - default: - log_warning_errno(r, "Failed to load Smack/CIPSO access rules, ignoring: %m"); - break; - } - - r = write_netlabel_rules("/etc/smack/netlabel.d/"); - switch(r) { - case -ENOENT: - log_debug("Smack/CIPSO is not enabled in the kernel."); - return 0; - case ENOENT: - log_debug("Smack network host rules directory '/etc/smack/netlabel.d/' not found"); - break; - case 0: - log_info("Successfully loaded Smack network host rules."); - break; - default: - log_warning_errno(r, "Failed to load Smack network host rules: %m, ignoring."); - break; - } - - *loaded_policy = true; - -#endif - - return 0; -} diff --git a/src/core/smack-setup.h b/src/core/smack-setup.h deleted file mode 100644 index 78164c85e6..0000000000 --- a/src/core/smack-setup.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright (C) 2013 Intel Corporation - Authors: - Nathaniel Chen - - 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 . -***/ - -int mac_smack_setup(bool *loaded_policy); diff --git a/src/core/socket.c b/src/core/socket.c deleted file mode 100644 index f6204d04bf..0000000000 --- a/src/core/socket.c +++ /dev/null @@ -1,2992 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "copy.h" -#include "dbus-socket.h" -#include "def.h" -#include "exit-status.h" -#include "fd-util.h" -#include "formats-util.h" -#include "io-util.h" -#include "label.h" -#include "log.h" -#include "missing.h" -#include "mkdir.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "selinux-util.h" -#include "signal-util.h" -#include "smack-util.h" -#include "socket.h" -#include "special.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "unit-printf.h" -#include "unit.h" -#include "user-util.h" - -static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = { - [SOCKET_DEAD] = UNIT_INACTIVE, - [SOCKET_START_PRE] = UNIT_ACTIVATING, - [SOCKET_START_CHOWN] = UNIT_ACTIVATING, - [SOCKET_START_POST] = UNIT_ACTIVATING, - [SOCKET_LISTENING] = UNIT_ACTIVE, - [SOCKET_RUNNING] = UNIT_ACTIVE, - [SOCKET_STOP_PRE] = UNIT_DEACTIVATING, - [SOCKET_STOP_PRE_SIGTERM] = UNIT_DEACTIVATING, - [SOCKET_STOP_PRE_SIGKILL] = UNIT_DEACTIVATING, - [SOCKET_STOP_POST] = UNIT_DEACTIVATING, - [SOCKET_FINAL_SIGTERM] = UNIT_DEACTIVATING, - [SOCKET_FINAL_SIGKILL] = UNIT_DEACTIVATING, - [SOCKET_FAILED] = UNIT_FAILED -}; - -static int socket_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); -static int socket_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); - -static void socket_init(Unit *u) { - Socket *s = SOCKET(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - s->backlog = SOMAXCONN; - s->timeout_usec = u->manager->default_timeout_start_usec; - s->directory_mode = 0755; - s->socket_mode = 0666; - - s->max_connections = 64; - - s->priority = -1; - s->ip_tos = -1; - s->ip_ttl = -1; - s->mark = -1; - - s->exec_context.std_output = u->manager->default_std_output; - s->exec_context.std_error = u->manager->default_std_error; - - s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; - - s->trigger_limit.interval = USEC_INFINITY; - s->trigger_limit.burst = (unsigned) -1; -} - -static void socket_unwatch_control_pid(Socket *s) { - assert(s); - - if (s->control_pid <= 0) - return; - - unit_unwatch_pid(UNIT(s), s->control_pid); - s->control_pid = 0; -} - -static void socket_cleanup_fd_list(SocketPort *p) { - assert(p); - - close_many(p->auxiliary_fds, p->n_auxiliary_fds); - p->auxiliary_fds = mfree(p->auxiliary_fds); - p->n_auxiliary_fds = 0; -} - -void socket_free_ports(Socket *s) { - SocketPort *p; - - assert(s); - - while ((p = s->ports)) { - LIST_REMOVE(port, s->ports, p); - - sd_event_source_unref(p->event_source); - - socket_cleanup_fd_list(p); - safe_close(p->fd); - free(p->path); - free(p); - } -} - -static void socket_done(Unit *u) { - Socket *s = SOCKET(u); - - assert(s); - - socket_free_ports(s); - - s->exec_runtime = exec_runtime_unref(s->exec_runtime); - exec_command_free_array(s->exec_command, _SOCKET_EXEC_COMMAND_MAX); - s->control_command = NULL; - - socket_unwatch_control_pid(s); - - unit_ref_unset(&s->service); - - s->tcp_congestion = mfree(s->tcp_congestion); - s->bind_to_device = mfree(s->bind_to_device); - - s->smack = mfree(s->smack); - s->smack_ip_in = mfree(s->smack_ip_in); - s->smack_ip_out = mfree(s->smack_ip_out); - - strv_free(s->symlinks); - - s->user = mfree(s->user); - s->group = mfree(s->group); - - s->fdname = mfree(s->fdname); - - s->timer_event_source = sd_event_source_unref(s->timer_event_source); -} - -static int socket_arm_timer(Socket *s, usec_t usec) { - int r; - - assert(s); - - if (s->timer_event_source) { - r = sd_event_source_set_time(s->timer_event_source, usec); - if (r < 0) - return r; - - return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); - } - - if (usec == USEC_INFINITY) - return 0; - - r = sd_event_add_time( - UNIT(s)->manager->event, - &s->timer_event_source, - CLOCK_MONOTONIC, - usec, 0, - socket_dispatch_timer, s); - if (r < 0) - return r; - - (void) sd_event_source_set_description(s->timer_event_source, "socket-timer"); - - return 0; -} - -int socket_instantiate_service(Socket *s) { - _cleanup_free_ char *prefix = NULL, *name = NULL; - int r; - Unit *u; - - assert(s); - - /* This fills in s->service if it isn't filled in yet. For - * Accept=yes sockets we create the next connection service - * here. For Accept=no this is mostly a NOP since the service - * is figured out at load time anyway. */ - - if (UNIT_DEREF(s->service)) - return 0; - - if (!s->accept) - return 0; - - r = unit_name_to_prefix(UNIT(s)->id, &prefix); - if (r < 0) - return r; - - if (asprintf(&name, "%s@%u.service", prefix, s->n_accepted) < 0) - return -ENOMEM; - - r = manager_load_unit(UNIT(s)->manager, name, NULL, NULL, &u); - if (r < 0) - return r; - - unit_ref_set(&s->service, u); - - return unit_add_two_dependencies(UNIT(s), UNIT_BEFORE, UNIT_TRIGGERS, u, false); -} - -static bool have_non_accept_socket(Socket *s) { - SocketPort *p; - - assert(s); - - if (!s->accept) - return true; - - LIST_FOREACH(port, p, s->ports) { - - if (p->type != SOCKET_SOCKET) - return true; - - if (!socket_address_can_accept(&p->address)) - return true; - } - - return false; -} - -static int socket_add_mount_links(Socket *s) { - SocketPort *p; - int r; - - assert(s); - - LIST_FOREACH(port, p, s->ports) { - const char *path = NULL; - - if (p->type == SOCKET_SOCKET) - path = socket_address_get_path(&p->address); - else if (IN_SET(p->type, SOCKET_FIFO, SOCKET_SPECIAL, SOCKET_USB_FUNCTION)) - path = p->path; - - if (!path) - continue; - - r = unit_require_mounts_for(UNIT(s), path); - if (r < 0) - return r; - } - - return 0; -} - -static int socket_add_device_link(Socket *s) { - char *t; - - assert(s); - - if (!s->bind_to_device || streq(s->bind_to_device, "lo")) - return 0; - - t = strjoina("/sys/subsystem/net/devices/", s->bind_to_device); - return unit_add_node_link(UNIT(s), t, false, UNIT_BINDS_TO); -} - -static int socket_add_default_dependencies(Socket *s) { - int r; - assert(s); - - if (!UNIT(s)->default_dependencies) - return 0; - - r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_SOCKETS_TARGET, NULL, true); - if (r < 0) - return r; - - if (MANAGER_IS_SYSTEM(UNIT(s)->manager)) { - r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); - if (r < 0) - return r; - } - - return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); -} - -_pure_ static bool socket_has_exec(Socket *s) { - unsigned i; - assert(s); - - for (i = 0; i < _SOCKET_EXEC_COMMAND_MAX; i++) - if (s->exec_command[i]) - return true; - - return false; -} - -static int socket_add_extras(Socket *s) { - Unit *u = UNIT(s); - int r; - - assert(s); - - /* Pick defaults for the trigger limit, if nothing was explicitly configured. We pick a relatively high limit - * in Accept=yes mode, and a lower limit for Accept=no. Reason: in Accept=yes mode we are invoking accept() - * ourselves before the trigger limit can hit, thus incoming connections are taken off the socket queue quickly - * and reliably. This is different for Accept=no, where the spawned service has to take the incoming traffic - * off the queues, which it might not necessarily do. Moreover, while Accept=no services are supposed to - * process whatever is queued in one go, and thus should normally never have to be started frequently. This is - * different for Accept=yes where each connection is processed by a new service instance, and thus frequent - * service starts are typical. */ - - if (s->trigger_limit.interval == USEC_INFINITY) - s->trigger_limit.interval = 2 * USEC_PER_SEC; - - if (s->trigger_limit.burst == (unsigned) -1) { - if (s->accept) - s->trigger_limit.burst = 200; - else - s->trigger_limit.burst = 20; - } - - if (have_non_accept_socket(s)) { - - if (!UNIT_DEREF(s->service)) { - Unit *x; - - r = unit_load_related_unit(u, ".service", &x); - if (r < 0) - return r; - - unit_ref_set(&s->service, x); - } - - r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(s->service), true); - if (r < 0) - return r; - } - - r = socket_add_mount_links(s); - if (r < 0) - return r; - - r = socket_add_device_link(s); - if (r < 0) - return r; - - r = unit_patch_contexts(u); - if (r < 0) - return r; - - if (socket_has_exec(s)) { - r = unit_add_exec_dependencies(u, &s->exec_context); - if (r < 0) - return r; - - r = unit_set_default_slice(u); - if (r < 0) - return r; - } - - r = socket_add_default_dependencies(s); - if (r < 0) - return r; - - return 0; -} - -static const char *socket_find_symlink_target(Socket *s) { - const char *found = NULL; - SocketPort *p; - - LIST_FOREACH(port, p, s->ports) { - const char *f = NULL; - - switch (p->type) { - - case SOCKET_FIFO: - f = p->path; - break; - - case SOCKET_SOCKET: - if (p->address.sockaddr.un.sun_path[0] != 0) - f = p->address.sockaddr.un.sun_path; - break; - - default: - break; - } - - if (f) { - if (found) - return NULL; - - found = f; - } - } - - return found; -} - -static int socket_verify(Socket *s) { - assert(s); - - if (UNIT(s)->load_state != UNIT_LOADED) - return 0; - - if (!s->ports) { - log_unit_error(UNIT(s), "Unit lacks Listen setting. Refusing."); - return -EINVAL; - } - - if (s->accept && have_non_accept_socket(s)) { - log_unit_error(UNIT(s), "Unit configured for accepting sockets, but sockets are non-accepting. Refusing."); - return -EINVAL; - } - - if (s->accept && s->max_connections <= 0) { - log_unit_error(UNIT(s), "MaxConnection= setting too small. Refusing."); - return -EINVAL; - } - - if (s->accept && UNIT_DEREF(s->service)) { - log_unit_error(UNIT(s), "Explicit service configuration for accepting socket units not supported. Refusing."); - return -EINVAL; - } - - if (s->exec_context.pam_name && s->kill_context.kill_mode != KILL_CONTROL_GROUP) { - log_unit_error(UNIT(s), "Unit has PAM enabled. Kill mode must be set to 'control-group'. Refusing."); - return -EINVAL; - } - - if (!strv_isempty(s->symlinks) && !socket_find_symlink_target(s)) { - log_unit_error(UNIT(s), "Unit has symlinks set but none or more than one node in the file system. Refusing."); - return -EINVAL; - } - - return 0; -} - -static int socket_load(Unit *u) { - Socket *s = SOCKET(u); - int r; - - assert(u); - assert(u->load_state == UNIT_STUB); - - r = unit_load_fragment_and_dropin(u); - if (r < 0) - return r; - - if (u->load_state == UNIT_LOADED) { - /* This is a new unit? Then let's add in some extras */ - r = socket_add_extras(s); - if (r < 0) - return r; - } - - return socket_verify(s); -} - -_const_ static const char* listen_lookup(int family, int type) { - - if (family == AF_NETLINK) - return "ListenNetlink"; - - if (type == SOCK_STREAM) - return "ListenStream"; - else if (type == SOCK_DGRAM) - return "ListenDatagram"; - else if (type == SOCK_SEQPACKET) - return "ListenSequentialPacket"; - - assert_not_reached("Unknown socket type"); - return NULL; -} - -static void socket_dump(Unit *u, FILE *f, const char *prefix) { - char time_string[FORMAT_TIMESPAN_MAX]; - SocketExecCommand c; - Socket *s = SOCKET(u); - SocketPort *p; - const char *prefix2; - - assert(s); - assert(f); - - prefix = strempty(prefix); - prefix2 = strjoina(prefix, "\t"); - - fprintf(f, - "%sSocket State: %s\n" - "%sResult: %s\n" - "%sBindIPv6Only: %s\n" - "%sBacklog: %u\n" - "%sSocketMode: %04o\n" - "%sDirectoryMode: %04o\n" - "%sKeepAlive: %s\n" - "%sNoDelay: %s\n" - "%sFreeBind: %s\n" - "%sTransparent: %s\n" - "%sBroadcast: %s\n" - "%sPassCredentials: %s\n" - "%sPassSecurity: %s\n" - "%sTCPCongestion: %s\n" - "%sRemoveOnStop: %s\n" - "%sWritable: %s\n" - "%sFDName: %s\n" - "%sSELinuxContextFromNet: %s\n", - prefix, socket_state_to_string(s->state), - prefix, socket_result_to_string(s->result), - prefix, socket_address_bind_ipv6_only_to_string(s->bind_ipv6_only), - prefix, s->backlog, - prefix, s->socket_mode, - prefix, s->directory_mode, - prefix, yes_no(s->keep_alive), - prefix, yes_no(s->no_delay), - prefix, yes_no(s->free_bind), - prefix, yes_no(s->transparent), - prefix, yes_no(s->broadcast), - prefix, yes_no(s->pass_cred), - prefix, yes_no(s->pass_sec), - prefix, strna(s->tcp_congestion), - prefix, yes_no(s->remove_on_stop), - prefix, yes_no(s->writable), - prefix, socket_fdname(s), - prefix, yes_no(s->selinux_context_from_net)); - - if (s->control_pid > 0) - fprintf(f, - "%sControl PID: "PID_FMT"\n", - prefix, s->control_pid); - - if (s->bind_to_device) - fprintf(f, - "%sBindToDevice: %s\n", - prefix, s->bind_to_device); - - if (s->accept) - fprintf(f, - "%sAccepted: %u\n" - "%sNConnections: %u\n" - "%sMaxConnections: %u\n", - prefix, s->n_accepted, - prefix, s->n_connections, - prefix, s->max_connections); - - if (s->priority >= 0) - fprintf(f, - "%sPriority: %i\n", - prefix, s->priority); - - if (s->receive_buffer > 0) - fprintf(f, - "%sReceiveBuffer: %zu\n", - prefix, s->receive_buffer); - - if (s->send_buffer > 0) - fprintf(f, - "%sSendBuffer: %zu\n", - prefix, s->send_buffer); - - if (s->ip_tos >= 0) - fprintf(f, - "%sIPTOS: %i\n", - prefix, s->ip_tos); - - if (s->ip_ttl >= 0) - fprintf(f, - "%sIPTTL: %i\n", - prefix, s->ip_ttl); - - if (s->pipe_size > 0) - fprintf(f, - "%sPipeSize: %zu\n", - prefix, s->pipe_size); - - if (s->mark >= 0) - fprintf(f, - "%sMark: %i\n", - prefix, s->mark); - - if (s->mq_maxmsg > 0) - fprintf(f, - "%sMessageQueueMaxMessages: %li\n", - prefix, s->mq_maxmsg); - - if (s->mq_msgsize > 0) - fprintf(f, - "%sMessageQueueMessageSize: %li\n", - prefix, s->mq_msgsize); - - if (s->reuse_port) - fprintf(f, - "%sReusePort: %s\n", - prefix, yes_no(s->reuse_port)); - - if (s->smack) - fprintf(f, - "%sSmackLabel: %s\n", - prefix, s->smack); - - if (s->smack_ip_in) - fprintf(f, - "%sSmackLabelIPIn: %s\n", - prefix, s->smack_ip_in); - - if (s->smack_ip_out) - fprintf(f, - "%sSmackLabelIPOut: %s\n", - prefix, s->smack_ip_out); - - if (!isempty(s->user) || !isempty(s->group)) - fprintf(f, - "%sSocketUser: %s\n" - "%sSocketGroup: %s\n", - prefix, strna(s->user), - prefix, strna(s->group)); - - if (s->keep_alive_time > 0) - fprintf(f, - "%sKeepAliveTimeSec: %s\n", - prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, s->keep_alive_time, USEC_PER_SEC)); - - if (s->keep_alive_interval) - fprintf(f, - "%sKeepAliveIntervalSec: %s\n", - prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, s->keep_alive_interval, USEC_PER_SEC)); - - if (s->keep_alive_cnt) - fprintf(f, - "%sKeepAliveProbes: %u\n", - prefix, s->keep_alive_cnt); - - if (s->defer_accept) - fprintf(f, - "%sDeferAcceptSec: %s\n", - prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, s->defer_accept, USEC_PER_SEC)); - - LIST_FOREACH(port, p, s->ports) { - - if (p->type == SOCKET_SOCKET) { - const char *t; - int r; - char *k = NULL; - - r = socket_address_print(&p->address, &k); - if (r < 0) - t = strerror(-r); - else - t = k; - - fprintf(f, "%s%s: %s\n", prefix, listen_lookup(socket_address_family(&p->address), p->address.type), t); - free(k); - } else if (p->type == SOCKET_SPECIAL) - fprintf(f, "%sListenSpecial: %s\n", prefix, p->path); - else if (p->type == SOCKET_USB_FUNCTION) - fprintf(f, "%sListenUSBFunction: %s\n", prefix, p->path); - else if (p->type == SOCKET_MQUEUE) - fprintf(f, "%sListenMessageQueue: %s\n", prefix, p->path); - else - fprintf(f, "%sListenFIFO: %s\n", prefix, p->path); - } - - fprintf(f, - "%sTriggerLimitIntervalSec: %s\n" - "%sTriggerLimitBurst: %u\n", - prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, s->trigger_limit.interval, USEC_PER_SEC), - prefix, s->trigger_limit.burst); - - exec_context_dump(&s->exec_context, f, prefix); - kill_context_dump(&s->kill_context, f, prefix); - - for (c = 0; c < _SOCKET_EXEC_COMMAND_MAX; c++) { - if (!s->exec_command[c]) - continue; - - fprintf(f, "%s-> %s:\n", - prefix, socket_exec_command_to_string(c)); - - exec_command_dump_list(s->exec_command[c], f, prefix2); - } -} - -static int instance_from_socket(int fd, unsigned nr, char **instance) { - socklen_t l; - char *r; - union sockaddr_union local, remote; - - assert(fd >= 0); - assert(instance); - - l = sizeof(local); - if (getsockname(fd, &local.sa, &l) < 0) - return -errno; - - l = sizeof(remote); - if (getpeername(fd, &remote.sa, &l) < 0) - return -errno; - - switch (local.sa.sa_family) { - - case AF_INET: { - uint32_t - a = ntohl(local.in.sin_addr.s_addr), - b = ntohl(remote.in.sin_addr.s_addr); - - if (asprintf(&r, - "%u-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u", - nr, - a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF, - ntohs(local.in.sin_port), - b >> 24, (b >> 16) & 0xFF, (b >> 8) & 0xFF, b & 0xFF, - ntohs(remote.in.sin_port)) < 0) - return -ENOMEM; - - break; - } - - case AF_INET6: { - static const unsigned char ipv4_prefix[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF - }; - - if (memcmp(&local.in6.sin6_addr, ipv4_prefix, sizeof(ipv4_prefix)) == 0 && - memcmp(&remote.in6.sin6_addr, ipv4_prefix, sizeof(ipv4_prefix)) == 0) { - const uint8_t - *a = local.in6.sin6_addr.s6_addr+12, - *b = remote.in6.sin6_addr.s6_addr+12; - - if (asprintf(&r, - "%u-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u", - nr, - a[0], a[1], a[2], a[3], - ntohs(local.in6.sin6_port), - b[0], b[1], b[2], b[3], - ntohs(remote.in6.sin6_port)) < 0) - return -ENOMEM; - } else { - char a[INET6_ADDRSTRLEN], b[INET6_ADDRSTRLEN]; - - if (asprintf(&r, - "%u-%s:%u-%s:%u", - nr, - inet_ntop(AF_INET6, &local.in6.sin6_addr, a, sizeof(a)), - ntohs(local.in6.sin6_port), - inet_ntop(AF_INET6, &remote.in6.sin6_addr, b, sizeof(b)), - ntohs(remote.in6.sin6_port)) < 0) - return -ENOMEM; - } - - break; - } - - case AF_UNIX: { - struct ucred ucred; - int k; - - k = getpeercred(fd, &ucred); - if (k >= 0) { - if (asprintf(&r, - "%u-"PID_FMT"-"UID_FMT, - nr, ucred.pid, ucred.uid) < 0) - return -ENOMEM; - } else if (k == -ENODATA) { - /* This handles the case where somebody is - * connecting from another pid/uid namespace - * (e.g. from outside of our container). */ - if (asprintf(&r, - "%u-unknown", - nr) < 0) - return -ENOMEM; - } else - return k; - - break; - } - - default: - assert_not_reached("Unhandled socket type."); - } - - *instance = r; - return 0; -} - -static void socket_close_fds(Socket *s) { - SocketPort *p; - char **i; - - assert(s); - - LIST_FOREACH(port, p, s->ports) { - bool was_open; - - was_open = p->fd >= 0; - - p->event_source = sd_event_source_unref(p->event_source); - p->fd = safe_close(p->fd); - socket_cleanup_fd_list(p); - - /* One little note: we should normally not delete any sockets in the file system here! After all some - * other process we spawned might still have a reference of this fd and wants to continue to use - * it. Therefore we normally delete sockets in the file system before we create a new one, not after we - * stopped using one! That all said, if the user explicitly requested this, we'll delete them here - * anyway, but only then. */ - - if (!was_open || !s->remove_on_stop) - continue; - - switch (p->type) { - - case SOCKET_FIFO: - (void) unlink(p->path); - break; - - case SOCKET_MQUEUE: - (void) mq_unlink(p->path); - break; - - case SOCKET_SOCKET: - (void) socket_address_unlink(&p->address); - break; - - default: - break; - } - } - - if (s->remove_on_stop) - STRV_FOREACH(i, s->symlinks) - (void) unlink(*i); -} - -static void socket_apply_socket_options(Socket *s, int fd) { - int r; - - assert(s); - assert(fd >= 0); - - if (s->keep_alive) { - int b = s->keep_alive; - if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &b, sizeof(b)) < 0) - log_unit_warning_errno(UNIT(s), errno, "SO_KEEPALIVE failed: %m"); - } - - if (s->keep_alive_time) { - int value = s->keep_alive_time / USEC_PER_SEC; - if (setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &value, sizeof(value)) < 0) - log_unit_warning_errno(UNIT(s), errno, "TCP_KEEPIDLE failed: %m"); - } - - if (s->keep_alive_interval) { - int value = s->keep_alive_interval / USEC_PER_SEC; - if (setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &value, sizeof(value)) < 0) - log_unit_warning_errno(UNIT(s), errno, "TCP_KEEPINTVL failed: %m"); - } - - if (s->keep_alive_cnt) { - int value = s->keep_alive_cnt; - if (setsockopt(fd, SOL_TCP, TCP_KEEPCNT, &value, sizeof(value)) < 0) - log_unit_warning_errno(UNIT(s), errno, "TCP_KEEPCNT failed: %m"); - } - - if (s->defer_accept) { - int value = s->defer_accept / USEC_PER_SEC; - if (setsockopt(fd, SOL_TCP, TCP_DEFER_ACCEPT, &value, sizeof(value)) < 0) - log_unit_warning_errno(UNIT(s), errno, "TCP_DEFER_ACCEPT failed: %m"); - } - - if (s->no_delay) { - int b = s->no_delay; - - if (s->socket_protocol == IPPROTO_SCTP) { - if (setsockopt(fd, SOL_SCTP, SCTP_NODELAY, &b, sizeof(b)) < 0) - log_unit_warning_errno(UNIT(s), errno, "SCTP_NODELAY failed: %m"); - } else { - if (setsockopt(fd, SOL_TCP, TCP_NODELAY, &b, sizeof(b)) < 0) - log_unit_warning_errno(UNIT(s), errno, "TCP_NODELAY failed: %m"); - } - } - - if (s->broadcast) { - int one = 1; - if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one)) < 0) - log_unit_warning_errno(UNIT(s), errno, "SO_BROADCAST failed: %m"); - } - - if (s->pass_cred) { - int one = 1; - if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) - log_unit_warning_errno(UNIT(s), errno, "SO_PASSCRED failed: %m"); - } - - if (s->pass_sec) { - int one = 1; - if (setsockopt(fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one)) < 0) - log_unit_warning_errno(UNIT(s), errno, "SO_PASSSEC failed: %m"); - } - - if (s->priority >= 0) - if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &s->priority, sizeof(s->priority)) < 0) - log_unit_warning_errno(UNIT(s), errno, "SO_PRIORITY failed: %m"); - - if (s->receive_buffer > 0) { - int value = (int) s->receive_buffer; - - /* We first try with SO_RCVBUFFORCE, in case we have the perms for that */ - - if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &value, sizeof(value)) < 0) - if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)) < 0) - log_unit_warning_errno(UNIT(s), errno, "SO_RCVBUF failed: %m"); - } - - if (s->send_buffer > 0) { - int value = (int) s->send_buffer; - if (setsockopt(fd, SOL_SOCKET, SO_SNDBUFFORCE, &value, sizeof(value)) < 0) - if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)) < 0) - log_unit_warning_errno(UNIT(s), errno, "SO_SNDBUF failed: %m"); - } - - if (s->mark >= 0) - if (setsockopt(fd, SOL_SOCKET, SO_MARK, &s->mark, sizeof(s->mark)) < 0) - log_unit_warning_errno(UNIT(s), errno, "SO_MARK failed: %m"); - - if (s->ip_tos >= 0) - if (setsockopt(fd, IPPROTO_IP, IP_TOS, &s->ip_tos, sizeof(s->ip_tos)) < 0) - log_unit_warning_errno(UNIT(s), errno, "IP_TOS failed: %m"); - - if (s->ip_ttl >= 0) { - int x; - - r = setsockopt(fd, IPPROTO_IP, IP_TTL, &s->ip_ttl, sizeof(s->ip_ttl)); - - if (socket_ipv6_is_supported()) - x = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &s->ip_ttl, sizeof(s->ip_ttl)); - else { - x = -1; - errno = EAFNOSUPPORT; - } - - if (r < 0 && x < 0) - log_unit_warning_errno(UNIT(s), errno, "IP_TTL/IPV6_UNICAST_HOPS failed: %m"); - } - - if (s->tcp_congestion) - if (setsockopt(fd, SOL_TCP, TCP_CONGESTION, s->tcp_congestion, strlen(s->tcp_congestion)+1) < 0) - log_unit_warning_errno(UNIT(s), errno, "TCP_CONGESTION failed: %m"); - - if (s->smack_ip_in) { - r = mac_smack_apply_fd(fd, SMACK_ATTR_IPIN, s->smack_ip_in); - if (r < 0) - log_unit_error_errno(UNIT(s), r, "mac_smack_apply_ip_in_fd: %m"); - } - - if (s->smack_ip_out) { - r = mac_smack_apply_fd(fd, SMACK_ATTR_IPOUT, s->smack_ip_out); - if (r < 0) - log_unit_error_errno(UNIT(s), r, "mac_smack_apply_ip_out_fd: %m"); - } -} - -static void socket_apply_fifo_options(Socket *s, int fd) { - int r; - - assert(s); - assert(fd >= 0); - - if (s->pipe_size > 0) - if (fcntl(fd, F_SETPIPE_SZ, s->pipe_size) < 0) - log_unit_warning_errno(UNIT(s), errno, "Setting pipe size failed, ignoring: %m"); - - if (s->smack) { - r = mac_smack_apply_fd(fd, SMACK_ATTR_ACCESS, s->smack); - if (r < 0) - log_unit_error_errno(UNIT(s), r, "SMACK relabelling failed, ignoring: %m"); - } -} - -static int fifo_address_create( - const char *path, - mode_t directory_mode, - mode_t socket_mode) { - - _cleanup_close_ int fd = -1; - mode_t old_mask; - struct stat st; - int r; - - assert(path); - - mkdir_parents_label(path, directory_mode); - - r = mac_selinux_create_file_prepare(path, S_IFIFO); - if (r < 0) - return r; - - /* Enforce the right access mode for the fifo */ - old_mask = umask(~ socket_mode); - - /* Include the original umask in our mask */ - (void) umask(~socket_mode | old_mask); - - r = mkfifo(path, socket_mode); - (void) umask(old_mask); - - if (r < 0 && errno != EEXIST) { - r = -errno; - goto fail; - } - - fd = open(path, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK | O_NOFOLLOW); - if (fd < 0) { - r = -errno; - goto fail; - } - - mac_selinux_create_file_clear(); - - if (fstat(fd, &st) < 0) { - r = -errno; - goto fail; - } - - if (!S_ISFIFO(st.st_mode) || - (st.st_mode & 0777) != (socket_mode & ~old_mask) || - st.st_uid != getuid() || - st.st_gid != getgid()) { - r = -EEXIST; - goto fail; - } - - r = fd; - fd = -1; - - return r; - -fail: - mac_selinux_create_file_clear(); - return r; -} - -static int special_address_create(const char *path, bool writable) { - _cleanup_close_ int fd = -1; - struct stat st; - int r; - - assert(path); - - fd = open(path, (writable ? O_RDWR : O_RDONLY)|O_CLOEXEC|O_NOCTTY|O_NONBLOCK|O_NOFOLLOW); - if (fd < 0) - return -errno; - - if (fstat(fd, &st) < 0) - return -errno; - - /* Check whether this is a /proc, /sys or /dev file or char device */ - if (!S_ISREG(st.st_mode) && !S_ISCHR(st.st_mode)) - return -EEXIST; - - r = fd; - fd = -1; - - return r; -} - -static int usbffs_address_create(const char *path) { - _cleanup_close_ int fd = -1; - struct stat st; - int r; - - assert(path); - - fd = open(path, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK|O_NOFOLLOW); - if (fd < 0) - return -errno; - - if (fstat(fd, &st) < 0) - return -errno; - - /* Check whether this is a regular file (ffs endpoint)*/ - if (!S_ISREG(st.st_mode)) - return -EEXIST; - - r = fd; - fd = -1; - - return r; -} - -static int mq_address_create( - const char *path, - mode_t mq_mode, - long maxmsg, - long msgsize) { - - _cleanup_close_ int fd = -1; - struct stat st; - mode_t old_mask; - struct mq_attr _attr, *attr = NULL; - int r; - - assert(path); - - if (maxmsg > 0 && msgsize > 0) { - _attr = (struct mq_attr) { - .mq_flags = O_NONBLOCK, - .mq_maxmsg = maxmsg, - .mq_msgsize = msgsize, - }; - attr = &_attr; - } - - /* Enforce the right access mode for the mq */ - old_mask = umask(~ mq_mode); - - /* Include the original umask in our mask */ - (void) umask(~mq_mode | old_mask); - fd = mq_open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_CREAT, mq_mode, attr); - (void) umask(old_mask); - - if (fd < 0) - return -errno; - - if (fstat(fd, &st) < 0) - return -errno; - - if ((st.st_mode & 0777) != (mq_mode & ~old_mask) || - st.st_uid != getuid() || - st.st_gid != getgid()) - return -EEXIST; - - r = fd; - fd = -1; - - return r; -} - -static int socket_symlink(Socket *s) { - const char *p; - char **i; - - assert(s); - - p = socket_find_symlink_target(s); - if (!p) - return 0; - - STRV_FOREACH(i, s->symlinks) - symlink_label(p, *i); - - return 0; -} - -static int usbffs_write_descs(int fd, Service *s) { - int r; - - if (!s->usb_function_descriptors || !s->usb_function_strings) - return -EINVAL; - - r = copy_file_fd(s->usb_function_descriptors, fd, false); - if (r < 0) - return r; - - return copy_file_fd(s->usb_function_strings, fd, false); -} - -static int usbffs_select_ep(const struct dirent *d) { - return d->d_name[0] != '.' && !streq(d->d_name, "ep0"); -} - -static int usbffs_dispatch_eps(SocketPort *p) { - _cleanup_free_ struct dirent **ent = NULL; - _cleanup_free_ char *path = NULL; - int r, i, n, k; - - path = dirname_malloc(p->path); - if (!path) - return -ENOMEM; - - r = scandir(path, &ent, usbffs_select_ep, alphasort); - if (r < 0) - return -errno; - - n = r; - p->auxiliary_fds = new(int, n); - if (!p->auxiliary_fds) - return -ENOMEM; - - p->n_auxiliary_fds = n; - - k = 0; - for (i = 0; i < n; ++i) { - _cleanup_free_ char *ep = NULL; - - ep = path_make_absolute(ent[i]->d_name, path); - if (!ep) - return -ENOMEM; - - path_kill_slashes(ep); - - r = usbffs_address_create(ep); - if (r < 0) - goto fail; - - p->auxiliary_fds[k] = r; - - ++k; - free(ent[i]); - } - - return r; - -fail: - close_many(p->auxiliary_fds, k); - p->auxiliary_fds = mfree(p->auxiliary_fds); - p->n_auxiliary_fds = 0; - - return r; -} - -static int socket_determine_selinux_label(Socket *s, char **ret) { - ExecCommand *c; - int r; - - assert(s); - assert(ret); - - if (s->selinux_context_from_net) { - /* If this is requested, get label from the network label */ - - r = mac_selinux_get_our_label(ret); - if (r == -EOPNOTSUPP) - goto no_label; - - } else { - /* Otherwise, get it from the executable we are about to start */ - r = socket_instantiate_service(s); - if (r < 0) - return r; - - if (!UNIT_ISSET(s->service)) - goto no_label; - - c = SERVICE(UNIT_DEREF(s->service))->exec_command[SERVICE_EXEC_START]; - if (!c) - goto no_label; - - r = mac_selinux_get_create_label_from_exe(c->path, ret); - if (r == -EPERM || r == -EOPNOTSUPP) - goto no_label; - } - - return r; - -no_label: - *ret = NULL; - return 0; -} - -static int socket_open_fds(Socket *s) { - _cleanup_(mac_selinux_freep) char *label = NULL; - bool know_label = false; - SocketPort *p; - int r; - - assert(s); - - LIST_FOREACH(port, p, s->ports) { - - if (p->fd >= 0) - continue; - - switch (p->type) { - - case SOCKET_SOCKET: - - if (!know_label) { - /* Figure out label, if we don't it know yet. We do it once, for the first socket where - * we need this and remember it for the rest. */ - - r = socket_determine_selinux_label(s, &label); - if (r < 0) - goto rollback; - - know_label = true; - } - - /* Apply the socket protocol */ - switch (p->address.type) { - - case SOCK_STREAM: - case SOCK_SEQPACKET: - if (s->socket_protocol == IPPROTO_SCTP) - p->address.protocol = s->socket_protocol; - break; - - case SOCK_DGRAM: - if (s->socket_protocol == IPPROTO_UDPLITE) - p->address.protocol = s->socket_protocol; - break; - } - - r = socket_address_listen( - &p->address, - SOCK_CLOEXEC|SOCK_NONBLOCK, - s->backlog, - s->bind_ipv6_only, - s->bind_to_device, - s->reuse_port, - s->free_bind, - s->transparent, - s->directory_mode, - s->socket_mode, - label); - if (r < 0) - goto rollback; - - p->fd = r; - socket_apply_socket_options(s, p->fd); - socket_symlink(s); - break; - - case SOCKET_SPECIAL: - - p->fd = special_address_create(p->path, s->writable); - if (p->fd < 0) { - r = p->fd; - goto rollback; - } - break; - - case SOCKET_FIFO: - - p->fd = fifo_address_create( - p->path, - s->directory_mode, - s->socket_mode); - if (p->fd < 0) { - r = p->fd; - goto rollback; - } - - socket_apply_fifo_options(s, p->fd); - socket_symlink(s); - break; - - case SOCKET_MQUEUE: - - p->fd = mq_address_create( - p->path, - s->socket_mode, - s->mq_maxmsg, - s->mq_msgsize); - if (p->fd < 0) { - r = p->fd; - goto rollback; - } - break; - - case SOCKET_USB_FUNCTION: { - _cleanup_free_ char *ep = NULL; - - ep = path_make_absolute("ep0", p->path); - - p->fd = usbffs_address_create(ep); - if (p->fd < 0) { - r = p->fd; - goto rollback; - } - - r = usbffs_write_descs(p->fd, SERVICE(UNIT_DEREF(s->service))); - if (r < 0) - goto rollback; - - r = usbffs_dispatch_eps(p); - if (r < 0) - goto rollback; - - break; - } - default: - assert_not_reached("Unknown port type"); - } - } - - return 0; - -rollback: - socket_close_fds(s); - return r; -} - -static void socket_unwatch_fds(Socket *s) { - SocketPort *p; - int r; - - assert(s); - - LIST_FOREACH(port, p, s->ports) { - if (p->fd < 0) - continue; - - if (!p->event_source) - continue; - - r = sd_event_source_set_enabled(p->event_source, SD_EVENT_OFF); - if (r < 0) - log_unit_debug_errno(UNIT(s), r, "Failed to disable event source: %m"); - } -} - -static int socket_watch_fds(Socket *s) { - SocketPort *p; - int r; - - assert(s); - - LIST_FOREACH(port, p, s->ports) { - if (p->fd < 0) - continue; - - if (p->event_source) { - r = sd_event_source_set_enabled(p->event_source, SD_EVENT_ON); - if (r < 0) - goto fail; - } else { - r = sd_event_add_io(UNIT(s)->manager->event, &p->event_source, p->fd, EPOLLIN, socket_dispatch_io, p); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(p->event_source, "socket-port-io"); - } - } - - return 0; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to watch listening fds: %m"); - socket_unwatch_fds(s); - return r; -} - -enum { - SOCKET_OPEN_NONE, - SOCKET_OPEN_SOME, - SOCKET_OPEN_ALL, -}; - -static int socket_check_open(Socket *s) { - bool have_open = false, have_closed = false; - SocketPort *p; - - assert(s); - - LIST_FOREACH(port, p, s->ports) { - if (p->fd < 0) - have_closed = true; - else - have_open = true; - - if (have_open && have_closed) - return SOCKET_OPEN_SOME; - } - - if (have_open) - return SOCKET_OPEN_ALL; - - return SOCKET_OPEN_NONE; -} - -static void socket_set_state(Socket *s, SocketState state) { - SocketState old_state; - assert(s); - - old_state = s->state; - s->state = state; - - if (!IN_SET(state, - SOCKET_START_PRE, - SOCKET_START_CHOWN, - SOCKET_START_POST, - SOCKET_STOP_PRE, - SOCKET_STOP_PRE_SIGTERM, - SOCKET_STOP_PRE_SIGKILL, - SOCKET_STOP_POST, - SOCKET_FINAL_SIGTERM, - SOCKET_FINAL_SIGKILL)) { - - s->timer_event_source = sd_event_source_unref(s->timer_event_source); - socket_unwatch_control_pid(s); - s->control_command = NULL; - s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; - } - - if (state != SOCKET_LISTENING) - socket_unwatch_fds(s); - - if (!IN_SET(state, - SOCKET_START_CHOWN, - SOCKET_START_POST, - SOCKET_LISTENING, - SOCKET_RUNNING, - SOCKET_STOP_PRE, - SOCKET_STOP_PRE_SIGTERM, - SOCKET_STOP_PRE_SIGKILL)) - socket_close_fds(s); - - if (state != old_state) - log_unit_debug(UNIT(s), "Changed %s -> %s", socket_state_to_string(old_state), socket_state_to_string(state)); - - unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); -} - -static int socket_coldplug(Unit *u) { - Socket *s = SOCKET(u); - int r; - - assert(s); - assert(s->state == SOCKET_DEAD); - - if (s->deserialized_state == s->state) - return 0; - - if (s->control_pid > 0 && - pid_is_unwaited(s->control_pid) && - IN_SET(s->deserialized_state, - SOCKET_START_PRE, - SOCKET_START_CHOWN, - SOCKET_START_POST, - SOCKET_STOP_PRE, - SOCKET_STOP_PRE_SIGTERM, - SOCKET_STOP_PRE_SIGKILL, - SOCKET_STOP_POST, - SOCKET_FINAL_SIGTERM, - SOCKET_FINAL_SIGKILL)) { - - r = unit_watch_pid(UNIT(s), s->control_pid); - if (r < 0) - return r; - - r = socket_arm_timer(s, usec_add(u->state_change_timestamp.monotonic, s->timeout_usec)); - if (r < 0) - return r; - } - - if (IN_SET(s->deserialized_state, - SOCKET_START_CHOWN, - SOCKET_START_POST, - SOCKET_LISTENING, - SOCKET_RUNNING)) { - - /* Originally, we used to simply reopen all sockets here that we didn't have file descriptors - * for. However, this is problematic, as we won't traverse throught the SOCKET_START_CHOWN state for - * them, and thus the UID/GID wouldn't be right. Hence, instead simply check if we have all fds open, - * and if there's a mismatch, warn loudly. */ - - r = socket_check_open(s); - if (r == SOCKET_OPEN_NONE) - log_unit_warning(UNIT(s), - "Socket unit configuration has changed while unit has been running, " - "no open socket file descriptor left. " - "The socket unit is not functional until restarted."); - else if (r == SOCKET_OPEN_SOME) - log_unit_warning(UNIT(s), - "Socket unit configuration has changed while unit has been running, " - "and some socket file descriptors have not been opened yet. " - "The socket unit is not fully functional until restarted."); - } - - if (s->deserialized_state == SOCKET_LISTENING) { - r = socket_watch_fds(s); - if (r < 0) - return r; - } - - socket_set_state(s, s->deserialized_state); - return 0; -} - -static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { - _cleanup_free_ char **argv = NULL; - pid_t pid; - int r; - ExecParameters exec_params = { - .apply_permissions = true, - .apply_chroot = true, - .apply_tty_stdin = true, - .stdin_fd = -1, - .stdout_fd = -1, - .stderr_fd = -1, - }; - - assert(s); - assert(c); - assert(_pid); - - (void) unit_realize_cgroup(UNIT(s)); - if (s->reset_cpu_usage) { - (void) unit_reset_cpu_usage(UNIT(s)); - s->reset_cpu_usage = false; - } - - r = unit_setup_exec_runtime(UNIT(s)); - if (r < 0) - return r; - - r = socket_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); - if (r < 0) - return r; - - r = unit_full_printf_strv(UNIT(s), c->argv, &argv); - if (r < 0) - return r; - - exec_params.argv = argv; - exec_params.environment = UNIT(s)->manager->environment; - exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn; - exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; - exec_params.cgroup_path = UNIT(s)->cgroup_path; - exec_params.cgroup_delegate = s->cgroup_context.delegate; - exec_params.runtime_prefix = manager_get_runtime_prefix(UNIT(s)->manager); - - r = exec_spawn(UNIT(s), - c, - &s->exec_context, - &exec_params, - s->exec_runtime, - &pid); - if (r < 0) - return r; - - r = unit_watch_pid(UNIT(s), pid); - if (r < 0) - /* FIXME: we need to do something here */ - return r; - - *_pid = pid; - return 0; -} - -static int socket_chown(Socket *s, pid_t *_pid) { - pid_t pid; - int r; - - r = socket_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); - if (r < 0) - goto fail; - - /* We have to resolve the user names out-of-process, hence - * let's fork here. It's messy, but well, what can we do? */ - - pid = fork(); - if (pid < 0) - return -errno; - - if (pid == 0) { - SocketPort *p; - uid_t uid = UID_INVALID; - gid_t gid = GID_INVALID; - int ret; - - (void) default_signals(SIGNALS_CRASH_HANDLER, SIGNALS_IGNORE, -1); - (void) ignore_signals(SIGPIPE, -1); - log_forget_fds(); - - if (!isempty(s->user)) { - const char *user = s->user; - - r = get_user_creds(&user, &uid, &gid, NULL, NULL); - if (r < 0) { - ret = EXIT_USER; - goto fail_child; - } - } - - if (!isempty(s->group)) { - const char *group = s->group; - - r = get_group_creds(&group, &gid); - if (r < 0) { - ret = EXIT_GROUP; - goto fail_child; - } - } - - LIST_FOREACH(port, p, s->ports) { - const char *path = NULL; - - if (p->type == SOCKET_SOCKET) - path = socket_address_get_path(&p->address); - else if (p->type == SOCKET_FIFO) - path = p->path; - - if (!path) - continue; - - if (chown(path, uid, gid) < 0) { - r = -errno; - ret = EXIT_CHOWN; - goto fail_child; - } - } - - _exit(0); - - fail_child: - log_open(); - log_error_errno(r, "Failed to chown socket at step %s: %m", exit_status_to_string(ret, EXIT_STATUS_SYSTEMD)); - - _exit(ret); - } - - r = unit_watch_pid(UNIT(s), pid); - if (r < 0) - goto fail; - - *_pid = pid; - return 0; - -fail: - s->timer_event_source = sd_event_source_unref(s->timer_event_source); - return r; -} - -static void socket_enter_dead(Socket *s, SocketResult f) { - assert(s); - - if (f != SOCKET_SUCCESS) - s->result = f; - - exec_runtime_destroy(s->exec_runtime); - s->exec_runtime = exec_runtime_unref(s->exec_runtime); - - exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager)); - - socket_set_state(s, s->result != SOCKET_SUCCESS ? SOCKET_FAILED : SOCKET_DEAD); -} - -static void socket_enter_signal(Socket *s, SocketState state, SocketResult f); - -static void socket_enter_stop_post(Socket *s, SocketResult f) { - int r; - assert(s); - - if (f != SOCKET_SUCCESS) - s->result = f; - - socket_unwatch_control_pid(s); - s->control_command_id = SOCKET_EXEC_STOP_POST; - s->control_command = s->exec_command[SOCKET_EXEC_STOP_POST]; - - if (s->control_command) { - r = socket_spawn(s, s->control_command, &s->control_pid); - if (r < 0) - goto fail; - - socket_set_state(s, SOCKET_STOP_POST); - } else - socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'stop-post' task: %m"); - socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_RESOURCES); -} - -static void socket_enter_signal(Socket *s, SocketState state, SocketResult f) { - int r; - - assert(s); - - if (f != SOCKET_SUCCESS) - s->result = f; - - r = unit_kill_context( - UNIT(s), - &s->kill_context, - (state != SOCKET_STOP_PRE_SIGTERM && state != SOCKET_FINAL_SIGTERM) ? - KILL_KILL : KILL_TERMINATE, - -1, - s->control_pid, - false); - if (r < 0) - goto fail; - - if (r > 0) { - r = socket_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); - if (r < 0) - goto fail; - - socket_set_state(s, state); - } else if (state == SOCKET_STOP_PRE_SIGTERM) - socket_enter_signal(s, SOCKET_STOP_PRE_SIGKILL, SOCKET_SUCCESS); - else if (state == SOCKET_STOP_PRE_SIGKILL) - socket_enter_stop_post(s, SOCKET_SUCCESS); - else if (state == SOCKET_FINAL_SIGTERM) - socket_enter_signal(s, SOCKET_FINAL_SIGKILL, SOCKET_SUCCESS); - else - socket_enter_dead(s, SOCKET_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); - - if (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_STOP_PRE_SIGKILL) - socket_enter_stop_post(s, SOCKET_FAILURE_RESOURCES); - else - socket_enter_dead(s, SOCKET_FAILURE_RESOURCES); -} - -static void socket_enter_stop_pre(Socket *s, SocketResult f) { - int r; - assert(s); - - if (f != SOCKET_SUCCESS) - s->result = f; - - socket_unwatch_control_pid(s); - s->control_command_id = SOCKET_EXEC_STOP_PRE; - s->control_command = s->exec_command[SOCKET_EXEC_STOP_PRE]; - - if (s->control_command) { - r = socket_spawn(s, s->control_command, &s->control_pid); - if (r < 0) - goto fail; - - socket_set_state(s, SOCKET_STOP_PRE); - } else - socket_enter_stop_post(s, SOCKET_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'stop-pre' task: %m"); - socket_enter_stop_post(s, SOCKET_FAILURE_RESOURCES); -} - -static void socket_enter_listening(Socket *s) { - int r; - assert(s); - - r = socket_watch_fds(s); - if (r < 0) { - log_unit_warning_errno(UNIT(s), r, "Failed to watch sockets: %m"); - goto fail; - } - - socket_set_state(s, SOCKET_LISTENING); - return; - -fail: - socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); -} - -static void socket_enter_start_post(Socket *s) { - int r; - assert(s); - - socket_unwatch_control_pid(s); - s->control_command_id = SOCKET_EXEC_START_POST; - s->control_command = s->exec_command[SOCKET_EXEC_START_POST]; - - if (s->control_command) { - r = socket_spawn(s, s->control_command, &s->control_pid); - if (r < 0) { - log_unit_warning_errno(UNIT(s), r, "Failed to run 'start-post' task: %m"); - goto fail; - } - - socket_set_state(s, SOCKET_START_POST); - } else - socket_enter_listening(s); - - return; - -fail: - socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); -} - -static void socket_enter_start_chown(Socket *s) { - int r; - - assert(s); - - r = socket_open_fds(s); - if (r < 0) { - log_unit_warning_errno(UNIT(s), r, "Failed to listen on sockets: %m"); - goto fail; - } - - if (!isempty(s->user) || !isempty(s->group)) { - - socket_unwatch_control_pid(s); - s->control_command_id = SOCKET_EXEC_START_CHOWN; - s->control_command = NULL; - - r = socket_chown(s, &s->control_pid); - if (r < 0) { - log_unit_warning_errno(UNIT(s), r, "Failed to fork 'start-chown' task: %m"); - goto fail; - } - - socket_set_state(s, SOCKET_START_CHOWN); - } else - socket_enter_start_post(s); - - return; - -fail: - socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); -} - -static void socket_enter_start_pre(Socket *s) { - int r; - assert(s); - - socket_unwatch_control_pid(s); - s->control_command_id = SOCKET_EXEC_START_PRE; - s->control_command = s->exec_command[SOCKET_EXEC_START_PRE]; - - if (s->control_command) { - r = socket_spawn(s, s->control_command, &s->control_pid); - if (r < 0) { - log_unit_warning_errno(UNIT(s), r, "Failed to run 'start-pre' task: %m"); - goto fail; - } - - socket_set_state(s, SOCKET_START_PRE); - } else - socket_enter_start_chown(s); - - return; - -fail: - socket_enter_dead(s, SOCKET_FAILURE_RESOURCES); -} - -static void flush_ports(Socket *s) { - SocketPort *p; - - /* Flush all incoming traffic, regardless if actual bytes or new connections, so that this socket isn't busy - * anymore */ - - LIST_FOREACH(port, p, s->ports) { - if (p->fd < 0) - continue; - - (void) flush_accept(p->fd); - (void) flush_fd(p->fd); - } -} - -static void socket_enter_running(Socket *s, int cfd) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - /* Note that this call takes possession of the connection fd passed. It either has to assign it somewhere or - * close it. */ - - assert(s); - - /* We don't take connections anymore if we are supposed to shut down anyway */ - if (unit_stop_pending(UNIT(s))) { - - log_unit_debug(UNIT(s), "Suppressing connection request since unit stop is scheduled."); - - if (cfd >= 0) - cfd = safe_close(cfd); - else - flush_ports(s); - - return; - } - - if (!ratelimit_test(&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; - } - - if (cfd < 0) { - Iterator i; - Unit *other; - bool pending = false; - - /* If there's already a start pending don't bother to - * do anything */ - SET_FOREACH(other, UNIT(s)->dependencies[UNIT_TRIGGERS], i) - if (unit_active_or_pending(other)) { - pending = true; - break; - } - - if (!pending) { - if (!UNIT_ISSET(s->service)) { - log_unit_error(UNIT(s), "Service to activate vanished, refusing activation."); - r = -ENOENT; - goto fail; - } - - r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT_DEREF(s->service), JOB_REPLACE, &error, NULL); - if (r < 0) - goto fail; - } - - socket_set_state(s, SOCKET_RUNNING); - } else { - _cleanup_free_ char *prefix = NULL, *instance = NULL, *name = NULL; - Service *service; - - if (s->n_connections >= s->max_connections) { - log_unit_warning(UNIT(s), "Too many incoming connections (%u), refusing connection attempt.", s->n_connections); - safe_close(cfd); - return; - } - - r = socket_instantiate_service(s); - if (r < 0) - goto fail; - - r = instance_from_socket(cfd, s->n_accepted, &instance); - if (r < 0) { - if (r != -ENOTCONN) - goto fail; - - /* 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; - } - - r = unit_name_to_prefix(UNIT(s)->id, &prefix); - if (r < 0) - goto fail; - - r = unit_name_build(prefix, instance, ".service", &name); - if (r < 0) - goto fail; - - r = unit_add_name(UNIT_DEREF(s->service), name); - if (r < 0) - goto fail; - - service = SERVICE(UNIT_DEREF(s->service)); - unit_ref_unset(&s->service); - - s->n_accepted++; - unit_choose_id(UNIT(service), name); - - r = service_set_socket_fd(service, cfd, s, s->selinux_context_from_net); - if (r < 0) - goto fail; - - cfd = -1; /* We passed ownership of the fd to the service now. Forget it here. */ - s->n_connections++; - - r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT(service), JOB_REPLACE, &error, NULL); - if (r < 0) { - /* We failed to activate the new service, but it still exists. Let's make sure the service - * closes and forgets the connection fd again, immediately. */ - service_close_socket_fd(service); - goto fail; - } - - /* Notify clients about changed counters */ - unit_add_to_dbus_queue(UNIT(s)); - } - - 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", - bus_error_message(&error, r)); - - socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); - safe_close(cfd); -} - -static void socket_run_next(Socket *s) { - int r; - - assert(s); - assert(s->control_command); - assert(s->control_command->command_next); - - socket_unwatch_control_pid(s); - - s->control_command = s->control_command->command_next; - - r = socket_spawn(s, s->control_command, &s->control_pid); - if (r < 0) - goto fail; - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run next task: %m"); - - if (s->state == SOCKET_START_POST) - socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); - else if (s->state == SOCKET_STOP_POST) - socket_enter_dead(s, SOCKET_FAILURE_RESOURCES); - else - socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_RESOURCES); -} - -static int socket_start(Unit *u) { - Socket *s = SOCKET(u); - int r; - - assert(s); - - /* We cannot fulfill this request right now, try again later - * please! */ - if (IN_SET(s->state, - SOCKET_STOP_PRE, - SOCKET_STOP_PRE_SIGKILL, - SOCKET_STOP_PRE_SIGTERM, - SOCKET_STOP_POST, - SOCKET_FINAL_SIGTERM, - SOCKET_FINAL_SIGKILL)) - return -EAGAIN; - - /* Already on it! */ - if (IN_SET(s->state, - SOCKET_START_PRE, - SOCKET_START_CHOWN, - SOCKET_START_POST)) - return 0; - - /* Cannot run this without the service being around */ - if (UNIT_ISSET(s->service)) { - Service *service; - - service = SERVICE(UNIT_DEREF(s->service)); - - if (UNIT(service)->load_state != UNIT_LOADED) { - log_unit_error(u, "Socket service %s not loaded, refusing.", UNIT(service)->id); - return -ENOENT; - } - - /* If the service is already active we cannot start the - * socket */ - if (service->state != SERVICE_DEAD && - service->state != SERVICE_FAILED && - service->state != SERVICE_AUTO_RESTART) { - log_unit_error(u, "Socket service %s already active, refusing.", UNIT(service)->id); - return -EBUSY; - } - } - - assert(s->state == SOCKET_DEAD || s->state == SOCKET_FAILED); - - r = unit_start_limit_test(u); - if (r < 0) { - socket_enter_dead(s, SOCKET_FAILURE_START_LIMIT_HIT); - return r; - } - - s->result = SOCKET_SUCCESS; - s->reset_cpu_usage = true; - - socket_enter_start_pre(s); - - return 1; -} - -static int socket_stop(Unit *u) { - Socket *s = SOCKET(u); - - assert(s); - - /* Already on it */ - if (IN_SET(s->state, - SOCKET_STOP_PRE, - SOCKET_STOP_PRE_SIGTERM, - SOCKET_STOP_PRE_SIGKILL, - SOCKET_STOP_POST, - SOCKET_FINAL_SIGTERM, - SOCKET_FINAL_SIGKILL)) - return 0; - - /* If there's already something running we go directly into - * kill mode. */ - if (IN_SET(s->state, - SOCKET_START_PRE, - SOCKET_START_CHOWN, - SOCKET_START_POST)) { - socket_enter_signal(s, SOCKET_STOP_PRE_SIGTERM, SOCKET_SUCCESS); - return -EAGAIN; - } - - assert(s->state == SOCKET_LISTENING || s->state == SOCKET_RUNNING); - - socket_enter_stop_pre(s, SOCKET_SUCCESS); - return 1; -} - -static int socket_serialize(Unit *u, FILE *f, FDSet *fds) { - Socket *s = SOCKET(u); - SocketPort *p; - int r; - - assert(u); - assert(f); - assert(fds); - - 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); - - if (s->control_pid > 0) - unit_serialize_item_format(u, f, "control-pid", PID_FMT, s->control_pid); - - if (s->control_command_id >= 0) - unit_serialize_item(u, f, "control-command", socket_exec_command_to_string(s->control_command_id)); - - LIST_FOREACH(port, p, s->ports) { - int copy; - - if (p->fd < 0) - continue; - - copy = fdset_put_dup(fds, p->fd); - if (copy < 0) - return copy; - - if (p->type == SOCKET_SOCKET) { - _cleanup_free_ char *t = NULL; - - r = socket_address_print(&p->address, &t); - if (r < 0) - return r; - - if (socket_address_family(&p->address) == AF_NETLINK) - unit_serialize_item_format(u, f, "netlink", "%i %s", copy, t); - else - unit_serialize_item_format(u, f, "socket", "%i %i %s", copy, p->address.type, t); - - } else if (p->type == SOCKET_SPECIAL) - unit_serialize_item_format(u, f, "special", "%i %s", copy, p->path); - else if (p->type == SOCKET_MQUEUE) - unit_serialize_item_format(u, f, "mqueue", "%i %s", copy, p->path); - else if (p->type == SOCKET_USB_FUNCTION) - unit_serialize_item_format(u, f, "ffs", "%i %s", copy, p->path); - else { - assert(p->type == SOCKET_FIFO); - unit_serialize_item_format(u, f, "fifo", "%i %s", copy, p->path); - } - } - - return 0; -} - -static int socket_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Socket *s = SOCKET(u); - - assert(u); - assert(key); - assert(value); - - if (streq(key, "state")) { - SocketState state; - - state = socket_state_from_string(value); - if (state < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - s->deserialized_state = state; - } else if (streq(key, "result")) { - SocketResult f; - - f = socket_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse result value: %s", value); - else if (f != SOCKET_SUCCESS) - s->result = f; - - } else if (streq(key, "n-accepted")) { - unsigned k; - - if (safe_atou(value, &k) < 0) - log_unit_debug(u, "Failed to parse n-accepted value: %s", value); - else - s->n_accepted += k; - } else if (streq(key, "control-pid")) { - pid_t pid; - - if (parse_pid(value, &pid) < 0) - log_unit_debug(u, "Failed to parse control-pid value: %s", value); - else - s->control_pid = pid; - } else if (streq(key, "control-command")) { - SocketExecCommand id; - - id = socket_exec_command_from_string(value); - if (id < 0) - log_unit_debug(u, "Failed to parse exec-command value: %s", value); - else { - s->control_command_id = id; - s->control_command = s->exec_command[id]; - } - } else if (streq(key, "fifo")) { - int fd, skip = 0; - SocketPort *p; - - if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse fifo value: %s", value); - else { - - LIST_FOREACH(port, p, s->ports) - if (p->type == SOCKET_FIFO && - path_equal_or_files_same(p->path, value+skip)) - break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } - - } else if (streq(key, "special")) { - int fd, skip = 0; - SocketPort *p; - - if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse special value: %s", value); - else { - - LIST_FOREACH(port, p, s->ports) - if (p->type == SOCKET_SPECIAL && - path_equal_or_files_same(p->path, value+skip)) - break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } - - } else if (streq(key, "mqueue")) { - int fd, skip = 0; - SocketPort *p; - - if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse mqueue value: %s", value); - else { - - LIST_FOREACH(port, p, s->ports) - if (p->type == SOCKET_MQUEUE && - streq(p->path, value+skip)) - break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } - - } else if (streq(key, "socket")) { - int fd, type, skip = 0; - SocketPort *p; - - if (sscanf(value, "%i %i %n", &fd, &type, &skip) < 2 || fd < 0 || type < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse socket value: %s", value); - else { - - LIST_FOREACH(port, p, s->ports) - if (socket_address_is(&p->address, value+skip, type)) - break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } - - } else if (streq(key, "netlink")) { - int fd, skip = 0; - SocketPort *p; - - if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse socket value: %s", value); - else { - - LIST_FOREACH(port, p, s->ports) - if (socket_address_is_netlink(&p->address, value+skip)) - break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } - - } else if (streq(key, "ffs")) { - int fd, skip = 0; - SocketPort *p; - - if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse ffs value: %s", value); - else { - - LIST_FOREACH(port, p, s->ports) - if (p->type == SOCKET_USB_FUNCTION && - path_equal_or_files_same(p->path, value+skip)) - break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } - - } else - log_unit_debug(UNIT(s), "Unknown serialization key: %s", key); - - return 0; -} - -static void socket_distribute_fds(Unit *u, FDSet *fds) { - Socket *s = SOCKET(u); - SocketPort *p; - - assert(u); - - LIST_FOREACH(port, p, s->ports) { - Iterator i; - int fd; - - if (p->type != SOCKET_SOCKET) - continue; - - if (p->fd >= 0) - continue; - - FDSET_FOREACH(fd, fds, i) { - if (socket_address_matches_fd(&p->address, fd)) { - p->fd = fdset_remove(fds, fd); - s->deserialized_state = SOCKET_LISTENING; - break; - } - } - } -} - -_pure_ static UnitActiveState socket_active_state(Unit *u) { - assert(u); - - return state_translation_table[SOCKET(u)->state]; -} - -_pure_ static const char *socket_sub_state_to_string(Unit *u) { - assert(u); - - return socket_state_to_string(SOCKET(u)->state); -} - -const char* socket_port_type_to_string(SocketPort *p) { - - assert(p); - - switch (p->type) { - - case SOCKET_SOCKET: - - switch (p->address.type) { - - case SOCK_STREAM: - return "Stream"; - - case SOCK_DGRAM: - return "Datagram"; - - case SOCK_SEQPACKET: - return "SequentialPacket"; - - case SOCK_RAW: - if (socket_address_family(&p->address) == AF_NETLINK) - return "Netlink"; - - default: - return NULL; - } - - case SOCKET_SPECIAL: - return "Special"; - - case SOCKET_MQUEUE: - return "MessageQueue"; - - case SOCKET_FIFO: - return "FIFO"; - - case SOCKET_USB_FUNCTION: - return "USBFunction"; - - default: - return NULL; - } -} - -_pure_ static bool socket_check_gc(Unit *u) { - Socket *s = SOCKET(u); - - assert(u); - - return s->n_connections > 0; -} - -static int socket_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - SocketPort *p = userdata; - int cfd = -1; - - assert(p); - assert(fd >= 0); - - if (p->socket->state != SOCKET_LISTENING) - return 0; - - log_unit_debug(UNIT(p->socket), "Incoming traffic"); - - if (revents != EPOLLIN) { - - if (revents & EPOLLHUP) - log_unit_error(UNIT(p->socket), "Got POLLHUP on a listening socket. The service probably invoked shutdown() on it, and should better not do that."); - else - log_unit_error(UNIT(p->socket), "Got unexpected poll event (0x%x) on socket.", revents); - goto fail; - } - - if (p->socket->accept && - p->type == SOCKET_SOCKET && - socket_address_can_accept(&p->address)) { - - for (;;) { - - cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK); - if (cfd < 0) { - - if (errno == EINTR) - continue; - - log_unit_error_errno(UNIT(p->socket), errno, "Failed to accept socket: %m"); - goto fail; - } - - break; - } - - socket_apply_socket_options(p->socket, cfd); - } - - socket_enter_running(p->socket, cfd); - return 0; - -fail: - socket_enter_stop_pre(p->socket, SOCKET_FAILURE_RESOURCES); - return 0; -} - -static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) { - Socket *s = SOCKET(u); - SocketResult f; - - assert(s); - assert(pid >= 0); - - if (pid != s->control_pid) - return; - - s->control_pid = 0; - - if (is_clean_exit(code, status, NULL)) - f = SOCKET_SUCCESS; - else if (code == CLD_EXITED) - f = SOCKET_FAILURE_EXIT_CODE; - else if (code == CLD_KILLED) - f = SOCKET_FAILURE_SIGNAL; - else if (code == CLD_DUMPED) - f = SOCKET_FAILURE_CORE_DUMP; - else - assert_not_reached("Unknown sigchld code"); - - if (s->control_command) { - exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status); - - if (s->control_command->ignore) - f = SOCKET_SUCCESS; - } - - log_unit_full(u, f == SOCKET_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, - "Control process exited, code=%s status=%i", - sigchld_code_to_string(code), status); - - if (f != SOCKET_SUCCESS) - s->result = f; - - if (s->control_command && - s->control_command->command_next && - f == SOCKET_SUCCESS) { - - log_unit_debug(u, "Running next command for state %s", socket_state_to_string(s->state)); - socket_run_next(s); - } else { - s->control_command = NULL; - s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; - - /* No further commands for this step, so let's figure - * out what to do next */ - - log_unit_debug(u, "Got final SIGCHLD for state %s", socket_state_to_string(s->state)); - - switch (s->state) { - - case SOCKET_START_PRE: - if (f == SOCKET_SUCCESS) - socket_enter_start_chown(s); - else - socket_enter_signal(s, SOCKET_FINAL_SIGTERM, f); - break; - - case SOCKET_START_CHOWN: - if (f == SOCKET_SUCCESS) - socket_enter_start_post(s); - else - socket_enter_stop_pre(s, f); - break; - - case SOCKET_START_POST: - if (f == SOCKET_SUCCESS) - socket_enter_listening(s); - else - socket_enter_stop_pre(s, f); - break; - - case SOCKET_STOP_PRE: - case SOCKET_STOP_PRE_SIGTERM: - case SOCKET_STOP_PRE_SIGKILL: - socket_enter_stop_post(s, f); - break; - - case SOCKET_STOP_POST: - case SOCKET_FINAL_SIGTERM: - case SOCKET_FINAL_SIGKILL: - socket_enter_dead(s, f); - break; - - default: - assert_not_reached("Uh, control process died at wrong time."); - } - } - - /* Notify clients about changed exit status */ - unit_add_to_dbus_queue(u); -} - -static int socket_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { - Socket *s = SOCKET(userdata); - - assert(s); - assert(s->timer_event_source == source); - - switch (s->state) { - - case SOCKET_START_PRE: - log_unit_warning(UNIT(s), "Starting timed out. Terminating."); - socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_TIMEOUT); - break; - - case SOCKET_START_CHOWN: - case SOCKET_START_POST: - log_unit_warning(UNIT(s), "Starting timed out. Stopping."); - socket_enter_stop_pre(s, SOCKET_FAILURE_TIMEOUT); - break; - - case SOCKET_STOP_PRE: - log_unit_warning(UNIT(s), "Stopping timed out. Terminating."); - socket_enter_signal(s, SOCKET_STOP_PRE_SIGTERM, SOCKET_FAILURE_TIMEOUT); - break; - - case SOCKET_STOP_PRE_SIGTERM: - if (s->kill_context.send_sigkill) { - log_unit_warning(UNIT(s), "Stopping timed out. Killing."); - socket_enter_signal(s, SOCKET_STOP_PRE_SIGKILL, SOCKET_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(s), "Stopping timed out. Skipping SIGKILL. Ignoring."); - socket_enter_stop_post(s, SOCKET_FAILURE_TIMEOUT); - } - break; - - case SOCKET_STOP_PRE_SIGKILL: - log_unit_warning(UNIT(s), "Processes still around after SIGKILL. Ignoring."); - socket_enter_stop_post(s, SOCKET_FAILURE_TIMEOUT); - break; - - case SOCKET_STOP_POST: - log_unit_warning(UNIT(s), "Stopping timed out (2). Terminating."); - socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_TIMEOUT); - break; - - case SOCKET_FINAL_SIGTERM: - if (s->kill_context.send_sigkill) { - log_unit_warning(UNIT(s), "Stopping timed out (2). Killing."); - socket_enter_signal(s, SOCKET_FINAL_SIGKILL, SOCKET_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(s), "Stopping timed out (2). Skipping SIGKILL. Ignoring."); - socket_enter_dead(s, SOCKET_FAILURE_TIMEOUT); - } - break; - - case SOCKET_FINAL_SIGKILL: - log_unit_warning(UNIT(s), "Still around after SIGKILL (2). Entering failed mode."); - socket_enter_dead(s, SOCKET_FAILURE_TIMEOUT); - break; - - default: - assert_not_reached("Timeout at wrong time."); - } - - return 0; -} - -int socket_collect_fds(Socket *s, int **fds) { - int *rfds, k = 0, n = 0; - SocketPort *p; - - assert(s); - assert(fds); - - /* Called from the service code for requesting our fds */ - - LIST_FOREACH(port, p, s->ports) { - if (p->fd >= 0) - n++; - n += p->n_auxiliary_fds; - } - - if (n <= 0) { - *fds = NULL; - return 0; - } - - rfds = new(int, n); - if (!rfds) - return -ENOMEM; - - LIST_FOREACH(port, p, s->ports) { - int i; - - if (p->fd >= 0) - rfds[k++] = p->fd; - for (i = 0; i < p->n_auxiliary_fds; ++i) - rfds[k++] = p->auxiliary_fds[i]; - } - - assert(k == n); - - *fds = rfds; - return n; -} - -static void socket_reset_failed(Unit *u) { - Socket *s = SOCKET(u); - - assert(s); - - if (s->state == SOCKET_FAILED) - socket_set_state(s, SOCKET_DEAD); - - s->result = SOCKET_SUCCESS; -} - -void socket_connection_unref(Socket *s) { - assert(s); - - /* The service is dead. Yay! - * - * This is strictly for one-instance-per-connection - * services. */ - - assert(s->n_connections > 0); - s->n_connections--; - - log_unit_debug(UNIT(s), "One connection closed, %u left.", s->n_connections); -} - -static void socket_trigger_notify(Unit *u, Unit *other) { - Socket *s = SOCKET(u); - - assert(u); - assert(other); - - /* Filter out invocations with bogus state */ - if (other->load_state != UNIT_LOADED || other->type != UNIT_SERVICE) - return; - - /* Don't propagate state changes from the service if we are already down */ - if (!IN_SET(s->state, SOCKET_RUNNING, SOCKET_LISTENING)) - return; - - /* We don't care for the service state if we are in Accept=yes mode */ - if (s->accept) - return; - - /* Propagate start limit hit state */ - if (other->start_limit_hit) { - socket_enter_stop_pre(s, SOCKET_FAILURE_SERVICE_START_LIMIT_HIT); - return; - } - - /* Don't propagate anything if there's still a job queued */ - if (other->job) - return; - - if (IN_SET(SERVICE(other)->state, - SERVICE_DEAD, SERVICE_FAILED, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, - SERVICE_AUTO_RESTART)) - socket_enter_listening(s); - - if (SERVICE(other)->state == SERVICE_RUNNING) - socket_set_state(s, SOCKET_RUNNING); -} - -static int socket_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { - return unit_kill_common(u, who, signo, -1, SOCKET(u)->control_pid, error); -} - -static int socket_get_timeout(Unit *u, usec_t *timeout) { - Socket *s = SOCKET(u); - usec_t t; - int r; - - if (!s->timer_event_source) - return 0; - - r = sd_event_source_get_time(s->timer_event_source, &t); - if (r < 0) - return r; - if (t == USEC_INFINITY) - return 0; - - *timeout = t; - return 1; -} - -char *socket_fdname(Socket *s) { - assert(s); - - /* Returns the name to use for $LISTEN_NAMES. If the user - * didn't specify anything specifically, use the socket unit's - * name as fallback. */ - - if (s->fdname) - return s->fdname; - - return UNIT(s)->id; -} - -static int socket_control_pid(Unit *u) { - Socket *s = SOCKET(u); - - assert(s); - - return s->control_pid; -} - -static const char* const socket_exec_command_table[_SOCKET_EXEC_COMMAND_MAX] = { - [SOCKET_EXEC_START_PRE] = "StartPre", - [SOCKET_EXEC_START_CHOWN] = "StartChown", - [SOCKET_EXEC_START_POST] = "StartPost", - [SOCKET_EXEC_STOP_PRE] = "StopPre", - [SOCKET_EXEC_STOP_POST] = "StopPost" -}; - -DEFINE_STRING_TABLE_LOOKUP(socket_exec_command, SocketExecCommand); - -static const char* const socket_result_table[_SOCKET_RESULT_MAX] = { - [SOCKET_SUCCESS] = "success", - [SOCKET_FAILURE_RESOURCES] = "resources", - [SOCKET_FAILURE_TIMEOUT] = "timeout", - [SOCKET_FAILURE_EXIT_CODE] = "exit-code", - [SOCKET_FAILURE_SIGNAL] = "signal", - [SOCKET_FAILURE_CORE_DUMP] = "core-dump", - [SOCKET_FAILURE_START_LIMIT_HIT] = "start-limit-hit", - [SOCKET_FAILURE_TRIGGER_LIMIT_HIT] = "trigger-limit-hit", - [SOCKET_FAILURE_SERVICE_START_LIMIT_HIT] = "service-start-limit-hit" -}; - -DEFINE_STRING_TABLE_LOOKUP(socket_result, SocketResult); - -const UnitVTable socket_vtable = { - .object_size = sizeof(Socket), - .exec_context_offset = offsetof(Socket, exec_context), - .cgroup_context_offset = offsetof(Socket, cgroup_context), - .kill_context_offset = offsetof(Socket, kill_context), - .exec_runtime_offset = offsetof(Socket, exec_runtime), - - .sections = - "Unit\0" - "Socket\0" - "Install\0", - .private_section = "Socket", - - .init = socket_init, - .done = socket_done, - .load = socket_load, - - .coldplug = socket_coldplug, - - .dump = socket_dump, - - .start = socket_start, - .stop = socket_stop, - - .kill = socket_kill, - - .get_timeout = socket_get_timeout, - - .serialize = socket_serialize, - .deserialize_item = socket_deserialize_item, - .distribute_fds = socket_distribute_fds, - - .active_state = socket_active_state, - .sub_state_to_string = socket_sub_state_to_string, - - .check_gc = socket_check_gc, - - .sigchld_event = socket_sigchld_event, - - .trigger_notify = socket_trigger_notify, - - .reset_failed = socket_reset_failed, - - .control_pid = socket_control_pid, - - .bus_vtable = bus_socket_vtable, - .bus_set_property = bus_socket_set_property, - .bus_commit_properties = bus_socket_commit_properties, - - .status_message_formats = { - /*.starting_stopping = { - [0] = "Starting socket %s...", - [1] = "Stopping socket %s...", - },*/ - .finished_start_job = { - [JOB_DONE] = "Listening on %s.", - [JOB_FAILED] = "Failed to listen on %s.", - [JOB_TIMEOUT] = "Timed out starting %s.", - }, - .finished_stop_job = { - [JOB_DONE] = "Closed %s.", - [JOB_FAILED] = "Failed stopping %s.", - [JOB_TIMEOUT] = "Timed out stopping %s.", - }, - }, -}; diff --git a/src/core/socket.h b/src/core/socket.h deleted file mode 100644 index 0f1ac69c6f..0000000000 --- a/src/core/socket.h +++ /dev/null @@ -1,185 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct Socket Socket; - -#include "mount.h" -#include "service.h" -#include "socket-util.h" - -typedef enum SocketExecCommand { - SOCKET_EXEC_START_PRE, - SOCKET_EXEC_START_CHOWN, - SOCKET_EXEC_START_POST, - SOCKET_EXEC_STOP_PRE, - SOCKET_EXEC_STOP_POST, - _SOCKET_EXEC_COMMAND_MAX, - _SOCKET_EXEC_COMMAND_INVALID = -1 -} SocketExecCommand; - -typedef enum SocketType { - SOCKET_SOCKET, - SOCKET_FIFO, - SOCKET_SPECIAL, - SOCKET_MQUEUE, - SOCKET_USB_FUNCTION, - _SOCKET_FIFO_MAX, - _SOCKET_FIFO_INVALID = -1 -} SocketType; - -typedef enum SocketResult { - SOCKET_SUCCESS, - SOCKET_FAILURE_RESOURCES, - SOCKET_FAILURE_TIMEOUT, - SOCKET_FAILURE_EXIT_CODE, - SOCKET_FAILURE_SIGNAL, - SOCKET_FAILURE_CORE_DUMP, - SOCKET_FAILURE_START_LIMIT_HIT, - SOCKET_FAILURE_TRIGGER_LIMIT_HIT, - SOCKET_FAILURE_SERVICE_START_LIMIT_HIT, - _SOCKET_RESULT_MAX, - _SOCKET_RESULT_INVALID = -1 -} SocketResult; - -typedef struct SocketPort { - Socket *socket; - - SocketType type; - int fd; - int *auxiliary_fds; - int n_auxiliary_fds; - - SocketAddress address; - char *path; - sd_event_source *event_source; - - LIST_FIELDS(struct SocketPort, port); -} SocketPort; - -struct Socket { - Unit meta; - - LIST_HEAD(SocketPort, ports); - - unsigned n_accepted; - unsigned n_connections; - unsigned max_connections; - - unsigned backlog; - unsigned keep_alive_cnt; - usec_t timeout_usec; - usec_t keep_alive_time; - usec_t keep_alive_interval; - usec_t defer_accept; - - ExecCommand* exec_command[_SOCKET_EXEC_COMMAND_MAX]; - ExecContext exec_context; - KillContext kill_context; - CGroupContext cgroup_context; - ExecRuntime *exec_runtime; - - /* For Accept=no sockets refers to the one service we'll - activate. For Accept=yes sockets is either NULL, or filled - when the next service we spawn. */ - UnitRef service; - - SocketState state, deserialized_state; - - sd_event_source *timer_event_source; - - ExecCommand* control_command; - SocketExecCommand control_command_id; - pid_t control_pid; - - mode_t directory_mode; - mode_t socket_mode; - - SocketResult result; - - char **symlinks; - - bool accept; - bool remove_on_stop; - bool writable; - - int socket_protocol; - - /* Socket options */ - bool keep_alive; - bool no_delay; - bool free_bind; - bool transparent; - bool broadcast; - bool pass_cred; - bool pass_sec; - - /* Only for INET6 sockets: issue IPV6_V6ONLY sockopt */ - SocketAddressBindIPv6Only bind_ipv6_only; - - int priority; - int mark; - size_t receive_buffer; - size_t send_buffer; - int ip_tos; - int ip_ttl; - size_t pipe_size; - char *bind_to_device; - char *tcp_congestion; - bool reuse_port; - long mq_maxmsg; - long mq_msgsize; - - char *smack; - char *smack_ip_in; - char *smack_ip_out; - - bool selinux_context_from_net; - - char *user, *group; - - bool reset_cpu_usage:1; - - char *fdname; - - RateLimit trigger_limit; -}; - -/* Called from the service code when collecting fds */ -int socket_collect_fds(Socket *s, int **fds); - -/* Called from the service code when a per-connection service ended */ -void socket_connection_unref(Socket *s); - -void socket_free_ports(Socket *s); - -int socket_instantiate_service(Socket *s); - -char *socket_fdname(Socket *s); - -extern const UnitVTable socket_vtable; - -const char* socket_exec_command_to_string(SocketExecCommand i) _const_; -SocketExecCommand socket_exec_command_from_string(const char *s) _pure_; - -const char* socket_result_to_string(SocketResult i) _const_; -SocketResult socket_result_from_string(const char *s) _pure_; - -const char* socket_port_type_to_string(SocketPort *p) _pure_; diff --git a/src/core/swap.c b/src/core/swap.c deleted file mode 100644 index a532b15be8..0000000000 --- a/src/core/swap.c +++ /dev/null @@ -1,1532 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include - -#include "libudev.h" - -#include "alloc-util.h" -#include "dbus-swap.h" -#include "escape.h" -#include "exit-status.h" -#include "fd-util.h" -#include "formats-util.h" -#include "fstab-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "special.h" -#include "string-table.h" -#include "string-util.h" -#include "swap.h" -#include "udev-util.h" -#include "unit-name.h" -#include "unit.h" -#include "virt.h" - -static const UnitActiveState state_translation_table[_SWAP_STATE_MAX] = { - [SWAP_DEAD] = UNIT_INACTIVE, - [SWAP_ACTIVATING] = UNIT_ACTIVATING, - [SWAP_ACTIVATING_DONE] = UNIT_ACTIVE, - [SWAP_ACTIVE] = UNIT_ACTIVE, - [SWAP_DEACTIVATING] = UNIT_DEACTIVATING, - [SWAP_ACTIVATING_SIGTERM] = UNIT_DEACTIVATING, - [SWAP_ACTIVATING_SIGKILL] = UNIT_DEACTIVATING, - [SWAP_DEACTIVATING_SIGTERM] = UNIT_DEACTIVATING, - [SWAP_DEACTIVATING_SIGKILL] = UNIT_DEACTIVATING, - [SWAP_FAILED] = UNIT_FAILED -}; - -static int swap_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); -static int swap_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); - -static void swap_unset_proc_swaps(Swap *s) { - assert(s); - - if (!s->from_proc_swaps) - return; - - s->parameters_proc_swaps.what = mfree(s->parameters_proc_swaps.what); - - s->from_proc_swaps = false; -} - -static int swap_set_devnode(Swap *s, const char *devnode) { - Hashmap *swaps; - Swap *first; - int r; - - assert(s); - - r = hashmap_ensure_allocated(&UNIT(s)->manager->swaps_by_devnode, &string_hash_ops); - if (r < 0) - return r; - - swaps = UNIT(s)->manager->swaps_by_devnode; - - if (s->devnode) { - first = hashmap_get(swaps, s->devnode); - - LIST_REMOVE(same_devnode, first, s); - if (first) - hashmap_replace(swaps, first->devnode, first); - else - hashmap_remove(swaps, s->devnode); - - s->devnode = mfree(s->devnode); - } - - if (devnode) { - s->devnode = strdup(devnode); - if (!s->devnode) - return -ENOMEM; - - first = hashmap_get(swaps, s->devnode); - LIST_PREPEND(same_devnode, first, s); - - return hashmap_replace(swaps, first->devnode, first); - } - - return 0; -} - -static void swap_init(Unit *u) { - Swap *s = SWAP(u); - - assert(s); - assert(UNIT(s)->load_state == UNIT_STUB); - - s->timeout_usec = u->manager->default_timeout_start_usec; - - s->exec_context.std_output = u->manager->default_std_output; - s->exec_context.std_error = u->manager->default_std_error; - - s->parameters_proc_swaps.priority = s->parameters_fragment.priority = -1; - - s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; - - u->ignore_on_isolate = true; -} - -static void swap_unwatch_control_pid(Swap *s) { - assert(s); - - if (s->control_pid <= 0) - return; - - unit_unwatch_pid(UNIT(s), s->control_pid); - s->control_pid = 0; -} - -static void swap_done(Unit *u) { - Swap *s = SWAP(u); - - assert(s); - - swap_unset_proc_swaps(s); - swap_set_devnode(s, NULL); - - s->what = mfree(s->what); - s->parameters_fragment.what = mfree(s->parameters_fragment.what); - s->parameters_fragment.options = mfree(s->parameters_fragment.options); - - s->exec_runtime = exec_runtime_unref(s->exec_runtime); - exec_command_done_array(s->exec_command, _SWAP_EXEC_COMMAND_MAX); - s->control_command = NULL; - - swap_unwatch_control_pid(s); - - s->timer_event_source = sd_event_source_unref(s->timer_event_source); -} - -static int swap_arm_timer(Swap *s, usec_t usec) { - int r; - - assert(s); - - if (s->timer_event_source) { - r = sd_event_source_set_time(s->timer_event_source, usec); - if (r < 0) - return r; - - return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); - } - - if (usec == USEC_INFINITY) - return 0; - - r = sd_event_add_time( - UNIT(s)->manager->event, - &s->timer_event_source, - CLOCK_MONOTONIC, - usec, 0, - swap_dispatch_timer, s); - if (r < 0) - return r; - - (void) sd_event_source_set_description(s->timer_event_source, "swap-timer"); - - return 0; -} - -static int swap_add_device_links(Swap *s) { - assert(s); - - if (!s->what) - return 0; - - if (!s->from_fragment) - return 0; - - if (is_device_path(s->what)) - return unit_add_node_link(UNIT(s), s->what, MANAGER_IS_SYSTEM(UNIT(s)->manager), UNIT_BINDS_TO); - else - /* File based swap devices need to be ordered after - * systemd-remount-fs.service, since they might need a - * writable file system. */ - return unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_REMOUNT_FS_SERVICE, NULL, true); -} - -static int swap_add_default_dependencies(Swap *s) { - int r; - - assert(s); - - if (!UNIT(s)->default_dependencies) - return 0; - - if (!MANAGER_IS_SYSTEM(UNIT(s)->manager)) - return 0; - - if (detect_container() > 0) - return 0; - - /* swap units generated for the swap dev links are missing the - * ordering dep against the swap target. */ - r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_SWAP_TARGET, NULL, true); - if (r < 0) - return r; - - return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); -} - -static int swap_verify(Swap *s) { - _cleanup_free_ char *e = NULL; - int r; - - if (UNIT(s)->load_state != UNIT_LOADED) - return 0; - - r = unit_name_from_path(s->what, ".swap", &e); - if (r < 0) - return log_unit_error_errno(UNIT(s), r, "Failed to generate unit name from path: %m"); - - if (!unit_has_name(UNIT(s), e)) { - log_unit_error(UNIT(s), "Value of What= and unit name do not match, not loading."); - return -EINVAL; - } - - if (s->exec_context.pam_name && s->kill_context.kill_mode != KILL_CONTROL_GROUP) { - log_unit_error(UNIT(s), "Unit has PAM enabled. Kill mode must be set to 'control-group'. Refusing to load."); - return -EINVAL; - } - - return 0; -} - -static int swap_load_devnode(Swap *s) { - _cleanup_udev_device_unref_ struct udev_device *d = NULL; - struct stat st; - const char *p; - - assert(s); - - if (stat(s->what, &st) < 0 || !S_ISBLK(st.st_mode)) - return 0; - - d = udev_device_new_from_devnum(UNIT(s)->manager->udev, 'b', st.st_rdev); - if (!d) - return 0; - - p = udev_device_get_devnode(d); - if (!p) - return 0; - - return swap_set_devnode(s, p); -} - -static int swap_load(Unit *u) { - int r; - Swap *s = SWAP(u); - - assert(s); - assert(u->load_state == UNIT_STUB); - - /* Load a .swap file */ - r = unit_load_fragment_and_dropin_optional(u); - if (r < 0) - return r; - - if (u->load_state == UNIT_LOADED) { - - if (UNIT(s)->fragment_path) - s->from_fragment = true; - - if (!s->what) { - if (s->parameters_fragment.what) - s->what = strdup(s->parameters_fragment.what); - else if (s->parameters_proc_swaps.what) - s->what = strdup(s->parameters_proc_swaps.what); - else { - r = unit_name_to_path(u->id, &s->what); - if (r < 0) - return r; - } - - if (!s->what) - return -ENOMEM; - } - - path_kill_slashes(s->what); - - if (!UNIT(s)->description) { - r = unit_set_description(u, s->what); - if (r < 0) - return r; - } - - r = unit_require_mounts_for(UNIT(s), s->what); - if (r < 0) - return r; - - r = swap_add_device_links(s); - if (r < 0) - return r; - - r = swap_load_devnode(s); - if (r < 0) - return r; - - r = unit_patch_contexts(u); - if (r < 0) - return r; - - r = unit_add_exec_dependencies(u, &s->exec_context); - if (r < 0) - return r; - - r = unit_set_default_slice(u); - if (r < 0) - return r; - - r = swap_add_default_dependencies(s); - if (r < 0) - return r; - } - - return swap_verify(s); -} - -static int swap_setup_unit( - Manager *m, - const char *what, - const char *what_proc_swaps, - int priority, - bool set_flags) { - - _cleanup_free_ char *e = NULL; - bool delete = false; - Unit *u = NULL; - int r; - SwapParameters *p; - - assert(m); - assert(what); - assert(what_proc_swaps); - - r = unit_name_from_path(what, ".swap", &e); - if (r < 0) - return log_unit_error_errno(u, r, "Failed to generate unit name from path: %m"); - - u = manager_get_unit(m, e); - - if (u && - SWAP(u)->from_proc_swaps && - !path_equal(SWAP(u)->parameters_proc_swaps.what, what_proc_swaps)) { - log_error("Swap %s appeared twice with different device paths %s and %s", e, SWAP(u)->parameters_proc_swaps.what, what_proc_swaps); - return -EEXIST; - } - - if (!u) { - delete = true; - - u = unit_new(m, sizeof(Swap)); - if (!u) - return log_oom(); - - r = unit_add_name(u, e); - if (r < 0) - goto fail; - - SWAP(u)->what = strdup(what); - if (!SWAP(u)->what) { - r = -ENOMEM; - goto fail; - } - - unit_add_to_load_queue(u); - } else - delete = false; - - p = &SWAP(u)->parameters_proc_swaps; - - if (!p->what) { - p->what = strdup(what_proc_swaps); - if (!p->what) { - r = -ENOMEM; - goto fail; - } - } - - if (set_flags) { - SWAP(u)->is_active = true; - SWAP(u)->just_activated = !SWAP(u)->from_proc_swaps; - } - - SWAP(u)->from_proc_swaps = true; - - p->priority = priority; - - unit_add_to_dbus_queue(u); - return 0; - -fail: - log_unit_warning_errno(u, r, "Failed to load swap unit: %m"); - - if (delete && u) - unit_free(u); - - return r; -} - -static int swap_process_new(Manager *m, const char *device, int prio, bool set_flags) { - _cleanup_udev_device_unref_ struct udev_device *d = NULL; - struct udev_list_entry *item = NULL, *first = NULL; - const char *dn; - struct stat st; - int r; - - assert(m); - - r = swap_setup_unit(m, device, device, prio, set_flags); - if (r < 0) - return r; - - /* If this is a block device, then let's add duplicates for - * all other names of this block device */ - if (stat(device, &st) < 0 || !S_ISBLK(st.st_mode)) - return 0; - - d = udev_device_new_from_devnum(m->udev, 'b', st.st_rdev); - if (!d) - return 0; - - /* Add the main device node */ - dn = udev_device_get_devnode(d); - if (dn && !streq(dn, device)) - swap_setup_unit(m, dn, device, prio, set_flags); - - /* Add additional units for all symlinks */ - first = udev_device_get_devlinks_list_entry(d); - udev_list_entry_foreach(item, first) { - const char *p; - - /* Don't bother with the /dev/block links */ - p = udev_list_entry_get_name(item); - - if (streq(p, device)) - continue; - - if (path_startswith(p, "/dev/block/")) - continue; - - if (stat(p, &st) >= 0) - if (!S_ISBLK(st.st_mode) || - st.st_rdev != udev_device_get_devnum(d)) - continue; - - swap_setup_unit(m, p, device, prio, set_flags); - } - - return r; -} - -static void swap_set_state(Swap *s, SwapState state) { - SwapState old_state; - Swap *other; - - assert(s); - - old_state = s->state; - s->state = state; - - if (state != SWAP_ACTIVATING && - state != SWAP_ACTIVATING_SIGTERM && - state != SWAP_ACTIVATING_SIGKILL && - state != SWAP_ACTIVATING_DONE && - state != SWAP_DEACTIVATING && - state != SWAP_DEACTIVATING_SIGTERM && - state != SWAP_DEACTIVATING_SIGKILL) { - s->timer_event_source = sd_event_source_unref(s->timer_event_source); - swap_unwatch_control_pid(s); - s->control_command = NULL; - s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; - } - - if (state != old_state) - log_unit_debug(UNIT(s), "Changed %s -> %s", swap_state_to_string(old_state), swap_state_to_string(state)); - - unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); - - /* If there other units for the same device node have a job - queued it might be worth checking again if it is runnable - now. This is necessary, since swap_start() refuses - operation with EAGAIN if there's already another job for - the same device node queued. */ - LIST_FOREACH_OTHERS(same_devnode, other, s) - if (UNIT(other)->job) - job_add_to_run_queue(UNIT(other)->job); -} - -static int swap_coldplug(Unit *u) { - Swap *s = SWAP(u); - SwapState new_state = SWAP_DEAD; - int r; - - assert(s); - assert(s->state == SWAP_DEAD); - - if (s->deserialized_state != s->state) - new_state = s->deserialized_state; - else if (s->from_proc_swaps) - new_state = SWAP_ACTIVE; - - if (new_state == s->state) - return 0; - - if (s->control_pid > 0 && - pid_is_unwaited(s->control_pid) && - IN_SET(new_state, - SWAP_ACTIVATING, - SWAP_ACTIVATING_SIGTERM, - SWAP_ACTIVATING_SIGKILL, - SWAP_ACTIVATING_DONE, - SWAP_DEACTIVATING, - SWAP_DEACTIVATING_SIGTERM, - SWAP_DEACTIVATING_SIGKILL)) { - - r = unit_watch_pid(UNIT(s), s->control_pid); - if (r < 0) - return r; - - r = swap_arm_timer(s, usec_add(u->state_change_timestamp.monotonic, s->timeout_usec)); - if (r < 0) - return r; - } - - swap_set_state(s, new_state); - return 0; -} - -static void swap_dump(Unit *u, FILE *f, const char *prefix) { - Swap *s = SWAP(u); - SwapParameters *p; - - assert(s); - assert(f); - - if (s->from_proc_swaps) - p = &s->parameters_proc_swaps; - else if (s->from_fragment) - p = &s->parameters_fragment; - else - p = NULL; - - fprintf(f, - "%sSwap State: %s\n" - "%sResult: %s\n" - "%sWhat: %s\n" - "%sFrom /proc/swaps: %s\n" - "%sFrom fragment: %s\n", - prefix, swap_state_to_string(s->state), - prefix, swap_result_to_string(s->result), - prefix, s->what, - prefix, yes_no(s->from_proc_swaps), - prefix, yes_no(s->from_fragment)); - - if (s->devnode) - fprintf(f, "%sDevice Node: %s\n", prefix, s->devnode); - - if (p) - fprintf(f, - "%sPriority: %i\n" - "%sOptions: %s\n", - prefix, p->priority, - prefix, strempty(p->options)); - - if (s->control_pid > 0) - fprintf(f, - "%sControl PID: "PID_FMT"\n", - prefix, s->control_pid); - - exec_context_dump(&s->exec_context, f, prefix); - kill_context_dump(&s->kill_context, f, prefix); -} - -static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) { - pid_t pid; - int r; - ExecParameters exec_params = { - .apply_permissions = true, - .apply_chroot = true, - .apply_tty_stdin = true, - .stdin_fd = -1, - .stdout_fd = -1, - .stderr_fd = -1, - }; - - assert(s); - assert(c); - assert(_pid); - - (void) unit_realize_cgroup(UNIT(s)); - if (s->reset_cpu_usage) { - (void) unit_reset_cpu_usage(UNIT(s)); - s->reset_cpu_usage = false; - } - - r = unit_setup_exec_runtime(UNIT(s)); - if (r < 0) - goto fail; - - r = swap_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); - if (r < 0) - goto fail; - - exec_params.environment = UNIT(s)->manager->environment; - exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn; - exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; - exec_params.cgroup_path = UNIT(s)->cgroup_path; - exec_params.cgroup_delegate = s->cgroup_context.delegate; - exec_params.runtime_prefix = manager_get_runtime_prefix(UNIT(s)->manager); - - r = exec_spawn(UNIT(s), - c, - &s->exec_context, - &exec_params, - s->exec_runtime, - &pid); - if (r < 0) - goto fail; - - r = unit_watch_pid(UNIT(s), pid); - if (r < 0) - /* FIXME: we need to do something here */ - goto fail; - - *_pid = pid; - - return 0; - -fail: - s->timer_event_source = sd_event_source_unref(s->timer_event_source); - return r; -} - -static void swap_enter_dead(Swap *s, SwapResult f) { - assert(s); - - if (f != SWAP_SUCCESS) - s->result = f; - - exec_runtime_destroy(s->exec_runtime); - s->exec_runtime = exec_runtime_unref(s->exec_runtime); - - exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager)); - - swap_set_state(s, s->result != SWAP_SUCCESS ? SWAP_FAILED : SWAP_DEAD); -} - -static void swap_enter_active(Swap *s, SwapResult f) { - assert(s); - - if (f != SWAP_SUCCESS) - s->result = f; - - swap_set_state(s, SWAP_ACTIVE); -} - -static void swap_enter_signal(Swap *s, SwapState state, SwapResult f) { - int r; - - assert(s); - - if (f != SWAP_SUCCESS) - s->result = f; - - r = unit_kill_context( - UNIT(s), - &s->kill_context, - (state != SWAP_ACTIVATING_SIGTERM && state != SWAP_DEACTIVATING_SIGTERM) ? - KILL_KILL : KILL_TERMINATE, - -1, - s->control_pid, - false); - if (r < 0) - goto fail; - - if (r > 0) { - r = swap_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); - if (r < 0) - goto fail; - - swap_set_state(s, state); - } else if (state == SWAP_ACTIVATING_SIGTERM) - swap_enter_signal(s, SWAP_ACTIVATING_SIGKILL, SWAP_SUCCESS); - else if (state == SWAP_DEACTIVATING_SIGTERM) - swap_enter_signal(s, SWAP_DEACTIVATING_SIGKILL, SWAP_SUCCESS); - else - swap_enter_dead(s, SWAP_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); - swap_enter_dead(s, SWAP_FAILURE_RESOURCES); -} - -static void swap_enter_activating(Swap *s) { - _cleanup_free_ char *opts = NULL; - int r; - - assert(s); - - s->control_command_id = SWAP_EXEC_ACTIVATE; - s->control_command = s->exec_command + SWAP_EXEC_ACTIVATE; - - if (s->from_fragment) { - int priority = -1; - - r = fstab_find_pri(s->parameters_fragment.options, &priority); - if (r < 0) - log_warning_errno(r, "Failed to parse swap priority \"%s\", ignoring: %m", s->parameters_fragment.options); - else if (r == 1 && s->parameters_fragment.priority >= 0) - log_warning("Duplicate swap priority configuration by Priority and Options fields."); - - if (r <= 0 && s->parameters_fragment.priority >= 0) { - if (s->parameters_fragment.options) - r = asprintf(&opts, "%s,pri=%i", s->parameters_fragment.options, s->parameters_fragment.priority); - else - r = asprintf(&opts, "pri=%i", s->parameters_fragment.priority); - if (r < 0) - goto fail; - } - } - - r = exec_command_set(s->control_command, "/sbin/swapon", NULL); - if (r < 0) - goto fail; - - if (s->parameters_fragment.options || opts) { - r = exec_command_append(s->control_command, "-o", - opts ? : s->parameters_fragment.options, NULL); - if (r < 0) - goto fail; - } - - r = exec_command_append(s->control_command, s->what, NULL); - if (r < 0) - goto fail; - - swap_unwatch_control_pid(s); - - r = swap_spawn(s, s->control_command, &s->control_pid); - if (r < 0) - goto fail; - - swap_set_state(s, SWAP_ACTIVATING); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'swapon' task: %m"); - swap_enter_dead(s, SWAP_FAILURE_RESOURCES); -} - -static void swap_enter_deactivating(Swap *s) { - int r; - - assert(s); - - s->control_command_id = SWAP_EXEC_DEACTIVATE; - s->control_command = s->exec_command + SWAP_EXEC_DEACTIVATE; - - r = exec_command_set(s->control_command, - "/sbin/swapoff", - s->what, - NULL); - if (r < 0) - goto fail; - - swap_unwatch_control_pid(s); - - r = swap_spawn(s, s->control_command, &s->control_pid); - if (r < 0) - goto fail; - - swap_set_state(s, SWAP_DEACTIVATING); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'swapoff' task: %m"); - swap_enter_active(s, SWAP_FAILURE_RESOURCES); -} - -static int swap_start(Unit *u) { - Swap *s = SWAP(u), *other; - int r; - - assert(s); - - /* We cannot fulfill this request right now, try again later - * please! */ - - if (s->state == SWAP_DEACTIVATING || - s->state == SWAP_DEACTIVATING_SIGTERM || - s->state == SWAP_DEACTIVATING_SIGKILL || - s->state == SWAP_ACTIVATING_SIGTERM || - s->state == SWAP_ACTIVATING_SIGKILL) - return -EAGAIN; - - if (s->state == SWAP_ACTIVATING) - return 0; - - assert(s->state == SWAP_DEAD || s->state == SWAP_FAILED); - - if (detect_container() > 0) - return -EPERM; - - /* If there's a job for another swap unit for the same node - * running, then let's not dispatch this one for now, and wait - * until that other job has finished. */ - LIST_FOREACH_OTHERS(same_devnode, other, s) - if (UNIT(other)->job && UNIT(other)->job->state == JOB_RUNNING) - return -EAGAIN; - - r = unit_start_limit_test(u); - if (r < 0) { - swap_enter_dead(s, SWAP_FAILURE_START_LIMIT_HIT); - return r; - } - - s->result = SWAP_SUCCESS; - s->reset_cpu_usage = true; - - swap_enter_activating(s); - return 1; -} - -static int swap_stop(Unit *u) { - Swap *s = SWAP(u); - - assert(s); - - if (s->state == SWAP_DEACTIVATING || - s->state == SWAP_DEACTIVATING_SIGTERM || - s->state == SWAP_DEACTIVATING_SIGKILL || - s->state == SWAP_ACTIVATING_SIGTERM || - s->state == SWAP_ACTIVATING_SIGKILL) - return 0; - - assert(s->state == SWAP_ACTIVATING || - s->state == SWAP_ACTIVATING_DONE || - s->state == SWAP_ACTIVE); - - if (detect_container() > 0) - return -EPERM; - - swap_enter_deactivating(s); - return 1; -} - -static int swap_serialize(Unit *u, FILE *f, FDSet *fds) { - Swap *s = SWAP(u); - - assert(s); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", swap_state_to_string(s->state)); - unit_serialize_item(u, f, "result", swap_result_to_string(s->result)); - - if (s->control_pid > 0) - unit_serialize_item_format(u, f, "control-pid", PID_FMT, s->control_pid); - - if (s->control_command_id >= 0) - unit_serialize_item(u, f, "control-command", swap_exec_command_to_string(s->control_command_id)); - - return 0; -} - -static int swap_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Swap *s = SWAP(u); - - assert(s); - assert(fds); - - if (streq(key, "state")) { - SwapState state; - - state = swap_state_from_string(value); - if (state < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - s->deserialized_state = state; - } else if (streq(key, "result")) { - SwapResult f; - - f = swap_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse result value: %s", value); - else if (f != SWAP_SUCCESS) - s->result = f; - } else if (streq(key, "control-pid")) { - pid_t pid; - - if (parse_pid(value, &pid) < 0) - log_unit_debug(u, "Failed to parse control-pid value: %s", value); - else - s->control_pid = pid; - - } else if (streq(key, "control-command")) { - SwapExecCommand id; - - id = swap_exec_command_from_string(value); - if (id < 0) - log_unit_debug(u, "Failed to parse exec-command value: %s", value); - else { - s->control_command_id = id; - s->control_command = s->exec_command + id; - } - } else - log_unit_debug(u, "Unknown serialization key: %s", key); - - return 0; -} - -_pure_ static UnitActiveState swap_active_state(Unit *u) { - assert(u); - - return state_translation_table[SWAP(u)->state]; -} - -_pure_ static const char *swap_sub_state_to_string(Unit *u) { - assert(u); - - return swap_state_to_string(SWAP(u)->state); -} - -_pure_ static bool swap_check_gc(Unit *u) { - Swap *s = SWAP(u); - - assert(s); - - return s->from_proc_swaps; -} - -static void swap_sigchld_event(Unit *u, pid_t pid, int code, int status) { - Swap *s = SWAP(u); - SwapResult f; - - assert(s); - assert(pid >= 0); - - if (pid != s->control_pid) - return; - - s->control_pid = 0; - - if (is_clean_exit(code, status, NULL)) - f = SWAP_SUCCESS; - else if (code == CLD_EXITED) - f = SWAP_FAILURE_EXIT_CODE; - else if (code == CLD_KILLED) - f = SWAP_FAILURE_SIGNAL; - else if (code == CLD_DUMPED) - f = SWAP_FAILURE_CORE_DUMP; - else - assert_not_reached("Unknown code"); - - if (f != SWAP_SUCCESS) - s->result = f; - - if (s->control_command) { - exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status); - - s->control_command = NULL; - s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; - } - - log_unit_full(u, f == SWAP_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, - "Swap process exited, code=%s status=%i", sigchld_code_to_string(code), status); - - switch (s->state) { - - case SWAP_ACTIVATING: - case SWAP_ACTIVATING_DONE: - case SWAP_ACTIVATING_SIGTERM: - case SWAP_ACTIVATING_SIGKILL: - - if (f == SWAP_SUCCESS) - swap_enter_active(s, f); - else - swap_enter_dead(s, f); - break; - - case SWAP_DEACTIVATING: - case SWAP_DEACTIVATING_SIGKILL: - case SWAP_DEACTIVATING_SIGTERM: - - swap_enter_dead(s, f); - break; - - default: - assert_not_reached("Uh, control process died at wrong time."); - } - - /* Notify clients about changed exit status */ - unit_add_to_dbus_queue(u); -} - -static int swap_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { - Swap *s = SWAP(userdata); - - assert(s); - assert(s->timer_event_source == source); - - switch (s->state) { - - case SWAP_ACTIVATING: - case SWAP_ACTIVATING_DONE: - log_unit_warning(UNIT(s), "Activation timed out. Stopping."); - swap_enter_signal(s, SWAP_ACTIVATING_SIGTERM, SWAP_FAILURE_TIMEOUT); - break; - - case SWAP_DEACTIVATING: - log_unit_warning(UNIT(s), "Deactivation timed out. Stopping."); - swap_enter_signal(s, SWAP_DEACTIVATING_SIGTERM, SWAP_FAILURE_TIMEOUT); - break; - - case SWAP_ACTIVATING_SIGTERM: - if (s->kill_context.send_sigkill) { - log_unit_warning(UNIT(s), "Activation timed out. Killing."); - swap_enter_signal(s, SWAP_ACTIVATING_SIGKILL, SWAP_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(s), "Activation timed out. Skipping SIGKILL. Ignoring."); - swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); - } - break; - - case SWAP_DEACTIVATING_SIGTERM: - if (s->kill_context.send_sigkill) { - log_unit_warning(UNIT(s), "Deactivation timed out. Killing."); - swap_enter_signal(s, SWAP_DEACTIVATING_SIGKILL, SWAP_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(s), "Deactivation timed out. Skipping SIGKILL. Ignoring."); - swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); - } - break; - - case SWAP_ACTIVATING_SIGKILL: - case SWAP_DEACTIVATING_SIGKILL: - log_unit_warning(UNIT(s), "Swap process still around after SIGKILL. Ignoring."); - swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); - break; - - default: - assert_not_reached("Timeout at wrong time."); - } - - return 0; -} - -static int swap_load_proc_swaps(Manager *m, bool set_flags) { - unsigned i; - int r = 0; - - assert(m); - - rewind(m->proc_swaps); - - (void) fscanf(m->proc_swaps, "%*s %*s %*s %*s %*s\n"); - - for (i = 1;; i++) { - _cleanup_free_ char *dev = NULL, *d = NULL; - int prio = 0, k; - - k = fscanf(m->proc_swaps, - "%ms " /* device/file */ - "%*s " /* type of swap */ - "%*s " /* swap size */ - "%*s " /* used */ - "%i\n", /* priority */ - &dev, &prio); - if (k != 2) { - if (k == EOF) - break; - - log_warning("Failed to parse /proc/swaps:%u.", i); - continue; - } - - if (cunescape(dev, UNESCAPE_RELAX, &d) < 0) - return log_oom(); - - device_found_node(m, d, true, DEVICE_FOUND_SWAP, set_flags); - - k = swap_process_new(m, d, prio, set_flags); - if (k < 0) - r = k; - } - - return r; -} - -static int swap_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - Unit *u; - int r; - - assert(m); - assert(revents & EPOLLPRI); - - r = swap_load_proc_swaps(m, true); - if (r < 0) { - log_error_errno(r, "Failed to reread /proc/swaps: %m"); - - /* Reset flags, just in case, for late calls */ - LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_SWAP]) { - Swap *swap = SWAP(u); - - swap->is_active = swap->just_activated = false; - } - - return 0; - } - - manager_dispatch_load_queue(m); - - LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_SWAP]) { - Swap *swap = SWAP(u); - - if (!swap->is_active) { - /* This has just been deactivated */ - - swap_unset_proc_swaps(swap); - - switch (swap->state) { - - case SWAP_ACTIVE: - swap_enter_dead(swap, SWAP_SUCCESS); - break; - - default: - /* Fire again */ - swap_set_state(swap, swap->state); - break; - } - - if (swap->what) - device_found_node(m, swap->what, false, DEVICE_FOUND_SWAP, true); - - } else if (swap->just_activated) { - - /* New swap entry */ - - switch (swap->state) { - - case SWAP_DEAD: - case SWAP_FAILED: - swap_enter_active(swap, SWAP_SUCCESS); - break; - - case SWAP_ACTIVATING: - swap_set_state(swap, SWAP_ACTIVATING_DONE); - break; - - default: - /* Nothing really changed, but let's - * issue an notification call - * nonetheless, in case somebody is - * waiting for this. */ - swap_set_state(swap, swap->state); - break; - } - } - - /* Reset the flags for later calls */ - swap->is_active = swap->just_activated = false; - } - - return 1; -} - -static Unit *swap_following(Unit *u) { - Swap *s = SWAP(u); - Swap *other, *first = NULL; - - assert(s); - - /* If the user configured the swap through /etc/fstab or - * a device unit, follow that. */ - - if (s->from_fragment) - return NULL; - - LIST_FOREACH_OTHERS(same_devnode, other, s) - if (other->from_fragment) - return UNIT(other); - - /* Otherwise, make everybody follow the unit that's named after - * the swap device in the kernel */ - - if (streq_ptr(s->what, s->devnode)) - return NULL; - - LIST_FOREACH_AFTER(same_devnode, other, s) - if (streq_ptr(other->what, other->devnode)) - return UNIT(other); - - LIST_FOREACH_BEFORE(same_devnode, other, s) { - if (streq_ptr(other->what, other->devnode)) - return UNIT(other); - - first = other; - } - - /* Fall back to the first on the list */ - return UNIT(first); -} - -static int swap_following_set(Unit *u, Set **_set) { - Swap *s = SWAP(u), *other; - Set *set; - int r; - - assert(s); - assert(_set); - - if (LIST_JUST_US(same_devnode, s)) { - *_set = NULL; - return 0; - } - - set = set_new(NULL); - if (!set) - return -ENOMEM; - - LIST_FOREACH_OTHERS(same_devnode, other, s) { - r = set_put(set, other); - if (r < 0) - goto fail; - } - - *_set = set; - return 1; - -fail: - set_free(set); - return r; -} - -static void swap_shutdown(Manager *m) { - assert(m); - - m->swap_event_source = sd_event_source_unref(m->swap_event_source); - - m->proc_swaps = safe_fclose(m->proc_swaps); - - m->swaps_by_devnode = hashmap_free(m->swaps_by_devnode); -} - -static void swap_enumerate(Manager *m) { - int r; - - assert(m); - - if (!m->proc_swaps) { - m->proc_swaps = fopen("/proc/swaps", "re"); - if (!m->proc_swaps) { - if (errno == ENOENT) - log_debug("Not swap enabled, skipping enumeration"); - else - log_error_errno(errno, "Failed to open /proc/swaps: %m"); - - return; - } - - r = sd_event_add_io(m->event, &m->swap_event_source, fileno(m->proc_swaps), EPOLLPRI, swap_dispatch_io, m); - if (r < 0) { - log_error_errno(r, "Failed to watch /proc/swaps: %m"); - goto fail; - } - - /* Dispatch this before we dispatch SIGCHLD, so that - * we always get the events from /proc/swaps before - * the SIGCHLD of /sbin/swapon. */ - r = sd_event_source_set_priority(m->swap_event_source, -10); - if (r < 0) { - log_error_errno(r, "Failed to change /proc/swaps priority: %m"); - goto fail; - } - - (void) sd_event_source_set_description(m->swap_event_source, "swap-proc"); - } - - r = swap_load_proc_swaps(m, false); - if (r < 0) - goto fail; - - return; - -fail: - swap_shutdown(m); -} - -int swap_process_device_new(Manager *m, struct udev_device *dev) { - struct udev_list_entry *item = NULL, *first = NULL; - _cleanup_free_ char *e = NULL; - const char *dn; - Swap *s; - int r = 0; - - assert(m); - assert(dev); - - dn = udev_device_get_devnode(dev); - if (!dn) - return 0; - - r = unit_name_from_path(dn, ".swap", &e); - if (r < 0) - return r; - - s = hashmap_get(m->units, e); - if (s) - r = swap_set_devnode(s, dn); - - first = udev_device_get_devlinks_list_entry(dev); - udev_list_entry_foreach(item, first) { - _cleanup_free_ char *n = NULL; - int q; - - q = unit_name_from_path(udev_list_entry_get_name(item), ".swap", &n); - if (q < 0) - return q; - - s = hashmap_get(m->units, n); - if (s) { - q = swap_set_devnode(s, dn); - if (q < 0) - r = q; - } - } - - return r; -} - -int swap_process_device_remove(Manager *m, struct udev_device *dev) { - const char *dn; - int r = 0; - Swap *s; - - dn = udev_device_get_devnode(dev); - if (!dn) - return 0; - - while ((s = hashmap_get(m->swaps_by_devnode, dn))) { - int q; - - q = swap_set_devnode(s, NULL); - if (q < 0) - r = q; - } - - return r; -} - -static void swap_reset_failed(Unit *u) { - Swap *s = SWAP(u); - - assert(s); - - if (s->state == SWAP_FAILED) - swap_set_state(s, SWAP_DEAD); - - s->result = SWAP_SUCCESS; -} - -static int swap_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { - return unit_kill_common(u, who, signo, -1, SWAP(u)->control_pid, error); -} - -static int swap_get_timeout(Unit *u, usec_t *timeout) { - Swap *s = SWAP(u); - usec_t t; - int r; - - if (!s->timer_event_source) - return 0; - - r = sd_event_source_get_time(s->timer_event_source, &t); - if (r < 0) - return r; - if (t == USEC_INFINITY) - return 0; - - *timeout = t; - return 1; -} - -static bool swap_supported(void) { - static int supported = -1; - - /* If swap support is not available in the kernel, or we are - * running in a container we don't support swap units, and any - * attempts to starting one should fail immediately. */ - - if (supported < 0) - supported = - access("/proc/swaps", F_OK) >= 0 && - detect_container() <= 0; - - return supported; -} - -static int swap_control_pid(Unit *u) { - Swap *s = SWAP(u); - - assert(s); - - return s->control_pid; -} - -static const char* const swap_exec_command_table[_SWAP_EXEC_COMMAND_MAX] = { - [SWAP_EXEC_ACTIVATE] = "ExecActivate", - [SWAP_EXEC_DEACTIVATE] = "ExecDeactivate", -}; - -DEFINE_STRING_TABLE_LOOKUP(swap_exec_command, SwapExecCommand); - -static const char* const swap_result_table[_SWAP_RESULT_MAX] = { - [SWAP_SUCCESS] = "success", - [SWAP_FAILURE_RESOURCES] = "resources", - [SWAP_FAILURE_TIMEOUT] = "timeout", - [SWAP_FAILURE_EXIT_CODE] = "exit-code", - [SWAP_FAILURE_SIGNAL] = "signal", - [SWAP_FAILURE_CORE_DUMP] = "core-dump", - [SWAP_FAILURE_START_LIMIT_HIT] = "start-limit-hit", -}; - -DEFINE_STRING_TABLE_LOOKUP(swap_result, SwapResult); - -const UnitVTable swap_vtable = { - .object_size = sizeof(Swap), - .exec_context_offset = offsetof(Swap, exec_context), - .cgroup_context_offset = offsetof(Swap, cgroup_context), - .kill_context_offset = offsetof(Swap, kill_context), - .exec_runtime_offset = offsetof(Swap, exec_runtime), - - .sections = - "Unit\0" - "Swap\0" - "Install\0", - .private_section = "Swap", - - .init = swap_init, - .load = swap_load, - .done = swap_done, - - .coldplug = swap_coldplug, - - .dump = swap_dump, - - .start = swap_start, - .stop = swap_stop, - - .kill = swap_kill, - - .get_timeout = swap_get_timeout, - - .serialize = swap_serialize, - .deserialize_item = swap_deserialize_item, - - .active_state = swap_active_state, - .sub_state_to_string = swap_sub_state_to_string, - - .check_gc = swap_check_gc, - - .sigchld_event = swap_sigchld_event, - - .reset_failed = swap_reset_failed, - - .control_pid = swap_control_pid, - - .bus_vtable = bus_swap_vtable, - .bus_set_property = bus_swap_set_property, - .bus_commit_properties = bus_swap_commit_properties, - - .following = swap_following, - .following_set = swap_following_set, - - .enumerate = swap_enumerate, - .shutdown = swap_shutdown, - .supported = swap_supported, - - .status_message_formats = { - .starting_stopping = { - [0] = "Activating swap %s...", - [1] = "Deactivating swap %s...", - }, - .finished_start_job = { - [JOB_DONE] = "Activated swap %s.", - [JOB_FAILED] = "Failed to activate swap %s.", - [JOB_TIMEOUT] = "Timed out activating swap %s.", - }, - .finished_stop_job = { - [JOB_DONE] = "Deactivated swap %s.", - [JOB_FAILED] = "Failed deactivating swap %s.", - [JOB_TIMEOUT] = "Timed out deactivating swap %s.", - }, - }, -}; diff --git a/src/core/swap.h b/src/core/swap.h deleted file mode 100644 index fbf66debdc..0000000000 --- a/src/core/swap.h +++ /dev/null @@ -1,110 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2010 Maarten Lankhorst - - 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 "libudev.h" - -typedef struct Swap Swap; - -typedef enum SwapExecCommand { - SWAP_EXEC_ACTIVATE, - SWAP_EXEC_DEACTIVATE, - _SWAP_EXEC_COMMAND_MAX, - _SWAP_EXEC_COMMAND_INVALID = -1 -} SwapExecCommand; - -typedef enum SwapResult { - SWAP_SUCCESS, - SWAP_FAILURE_RESOURCES, - SWAP_FAILURE_TIMEOUT, - SWAP_FAILURE_EXIT_CODE, - SWAP_FAILURE_SIGNAL, - SWAP_FAILURE_CORE_DUMP, - SWAP_FAILURE_START_LIMIT_HIT, - _SWAP_RESULT_MAX, - _SWAP_RESULT_INVALID = -1 -} SwapResult; - -typedef struct SwapParameters { - char *what; - char *options; - int priority; -} SwapParameters; - -struct Swap { - Unit meta; - - char *what; - - /* If the device has already shown up, this is the device - * node, which might be different from what, due to - * symlinks */ - char *devnode; - - SwapParameters parameters_proc_swaps; - SwapParameters parameters_fragment; - - bool from_proc_swaps:1; - bool from_fragment:1; - - /* Used while looking for swaps that vanished or got added - * from/to /proc/swaps */ - bool is_active:1; - bool just_activated:1; - - bool reset_cpu_usage:1; - - SwapResult result; - - usec_t timeout_usec; - - ExecCommand exec_command[_SWAP_EXEC_COMMAND_MAX]; - ExecContext exec_context; - KillContext kill_context; - CGroupContext cgroup_context; - - ExecRuntime *exec_runtime; - - SwapState state, deserialized_state; - - ExecCommand* control_command; - SwapExecCommand control_command_id; - pid_t control_pid; - - sd_event_source *timer_event_source; - - /* In order to be able to distinguish dependencies on - different device nodes we might end up creating multiple - devices for the same swap. We chain them up here. */ - - LIST_FIELDS(struct Swap, same_devnode); -}; - -extern const UnitVTable swap_vtable; - -int swap_process_device_new(Manager *m, struct udev_device *dev); -int swap_process_device_remove(Manager *m, struct udev_device *dev); - -const char* swap_exec_command_to_string(SwapExecCommand i) _const_; -SwapExecCommand swap_exec_command_from_string(const char *s) _pure_; - -const char* swap_result_to_string(SwapResult i) _const_; -SwapResult swap_result_from_string(const char *s) _pure_; diff --git a/src/core/system.conf b/src/core/system.conf deleted file mode 100644 index db8b7acd78..0000000000 --- a/src/core/system.conf +++ /dev/null @@ -1,61 +0,0 @@ -# This file is part of systemd. -# -# 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. -# -# Entries in this file show the compile time defaults. -# You can change settings by editing this file. -# Defaults can be restored by simply deleting this file. -# -# See systemd-system.conf(5) for details. - -[Manager] -#LogLevel=info -#LogTarget=journal-or-kmsg -#LogColor=yes -#LogLocation=no -#DumpCore=yes -#ShowStatus=yes -#CrashChangeVT=no -#CrashShell=no -#CrashReboot=no -#CPUAffinity=1 2 -#JoinControllers=cpu,cpuacct net_cls,net_prio -#RuntimeWatchdogSec=0 -#ShutdownWatchdogSec=10min -#CapabilityBoundingSet= -#SystemCallArchitectures= -#TimerSlackNSec= -#DefaultTimerAccuracySec=1min -#DefaultStandardOutput=journal -#DefaultStandardError=inherit -#DefaultTimeoutStartSec=90s -#DefaultTimeoutStopSec=90s -#DefaultRestartSec=100ms -#DefaultStartLimitIntervalSec=10s -#DefaultStartLimitBurst=5 -#DefaultEnvironment= -#DefaultCPUAccounting=no -#DefaultIOAccounting=no -#DefaultBlockIOAccounting=no -#DefaultMemoryAccounting=no -#DefaultTasksAccounting=yes -#DefaultTasksMax=512 -#DefaultLimitCPU= -#DefaultLimitFSIZE= -#DefaultLimitDATA= -#DefaultLimitSTACK= -#DefaultLimitCORE= -#DefaultLimitRSS= -#DefaultLimitNOFILE= -#DefaultLimitAS= -#DefaultLimitNPROC= -#DefaultLimitMEMLOCK= -#DefaultLimitLOCKS= -#DefaultLimitSIGPENDING= -#DefaultLimitMSGQUEUE= -#DefaultLimitNICE= -#DefaultLimitRTPRIO= -#DefaultLimitRTTIME= diff --git a/src/core/systemd.pc.in b/src/core/systemd.pc.in deleted file mode 100644 index ac52b30dd3..0000000000 --- a/src/core/systemd.pc.in +++ /dev/null @@ -1,34 +0,0 @@ -# This file is part of systemd. -# -# 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. - -prefix=@prefix@ -systemdutildir=@rootlibexecdir@ -systemdsystemunitdir=@systemunitdir@ -systemdsystempresetdir=@systempresetdir@ -systemduserunitdir=@userunitdir@ -systemduserpresetdir=@userpresetdir@ -systemdsystemconfdir=@pkgsysconfdir@/system -systemduserconfdir=@pkgsysconfdir@/user -systemdsystemunitpath=${systemdsystemconfdir}:/etc/systemd/system:/run/systemd/system:/usr/local/lib/systemd/system:${systemdsystemunitdir}:/usr/lib/systemd/system:/lib/systemd/system -systemduserunitpath=${systemduserconfdir}:/etc/systemd/user:/run/systemd/user:/usr/local/lib/systemd/user:/usr/local/share/systemd/user:${systemduserunitdir}:/usr/lib/systemd/user:/usr/share/systemd/user -systemdsystemgeneratordir=@systemgeneratordir@ -systemdusergeneratordir=@usergeneratordir@ -systemdsleepdir=@systemsleepdir@ -systemdshutdowndir=@systemshutdowndir@ -tmpfilesdir=@tmpfilesdir@ -sysusersdir=@sysusersdir@ -sysctldir=@sysctldir@ -binfmtdir=@binfmtdir@ -modulesloaddir=@modulesloaddir@ -catalogdir=@catalogdir@ -systemuidmax=@systemuidmax@ -systemgidmax=@systemgidmax@ - -Name: systemd -Description: systemd System and Service Manager -URL: @PACKAGE_URL@ -Version: @PACKAGE_VERSION@ diff --git a/src/core/target.c b/src/core/target.c deleted file mode 100644 index 61a91aad07..0000000000 --- a/src/core/target.c +++ /dev/null @@ -1,223 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 "dbus-target.h" -#include "log.h" -#include "special.h" -#include "string-util.h" -#include "unit-name.h" -#include "unit.h" -#include "target.h" - -static const UnitActiveState state_translation_table[_TARGET_STATE_MAX] = { - [TARGET_DEAD] = UNIT_INACTIVE, - [TARGET_ACTIVE] = UNIT_ACTIVE -}; - -static void target_set_state(Target *t, TargetState state) { - TargetState old_state; - assert(t); - - old_state = t->state; - t->state = state; - - if (state != old_state) - log_debug("%s changed %s -> %s", - UNIT(t)->id, - target_state_to_string(old_state), - target_state_to_string(state)); - - unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], true); -} - -static int target_add_default_dependencies(Target *t) { - - static const UnitDependency deps[] = { - UNIT_REQUIRES, - UNIT_REQUISITE, - UNIT_WANTS, - UNIT_BINDS_TO, - UNIT_PART_OF - }; - - Iterator i; - Unit *other; - int r; - unsigned k; - - assert(t); - - /* Imply ordering for requirement dependencies on target - * units. Note that when the user created a contradicting - * ordering manually we won't add anything in here to make - * sure we don't create a loop. */ - - for (k = 0; k < ELEMENTSOF(deps); k++) - SET_FOREACH(other, UNIT(t)->dependencies[deps[k]], i) { - r = unit_add_default_target_dependency(other, UNIT(t)); - if (r < 0) - return r; - } - - /* Make sure targets are unloaded on shutdown */ - return unit_add_dependency_by_name(UNIT(t), UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); -} - -static int target_load(Unit *u) { - Target *t = TARGET(u); - int r; - - assert(t); - - r = unit_load_fragment_and_dropin(u); - if (r < 0) - return r; - - /* This is a new unit? Then let's add in some extras */ - if (u->load_state == UNIT_LOADED && u->default_dependencies) { - r = target_add_default_dependencies(t); - if (r < 0) - return r; - } - - return 0; -} - -static int target_coldplug(Unit *u) { - Target *t = TARGET(u); - - assert(t); - assert(t->state == TARGET_DEAD); - - if (t->deserialized_state != t->state) - target_set_state(t, t->deserialized_state); - - return 0; -} - -static void target_dump(Unit *u, FILE *f, const char *prefix) { - Target *t = TARGET(u); - - assert(t); - assert(f); - - fprintf(f, - "%sTarget State: %s\n", - prefix, target_state_to_string(t->state)); -} - -static int target_start(Unit *u) { - Target *t = TARGET(u); - - assert(t); - assert(t->state == TARGET_DEAD); - - target_set_state(t, TARGET_ACTIVE); - return 1; -} - -static int target_stop(Unit *u) { - Target *t = TARGET(u); - - assert(t); - assert(t->state == TARGET_ACTIVE); - - target_set_state(t, TARGET_DEAD); - return 1; -} - -static int target_serialize(Unit *u, FILE *f, FDSet *fds) { - Target *s = TARGET(u); - - assert(s); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", target_state_to_string(s->state)); - return 0; -} - -static int target_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Target *s = TARGET(u); - - assert(u); - assert(key); - assert(value); - assert(fds); - - if (streq(key, "state")) { - TargetState state; - - state = target_state_from_string(value); - if (state < 0) - log_debug("Failed to parse state value %s", value); - else - s->deserialized_state = state; - - } else - log_debug("Unknown serialization key '%s'", key); - - return 0; -} - -_pure_ static UnitActiveState target_active_state(Unit *u) { - assert(u); - - return state_translation_table[TARGET(u)->state]; -} - -_pure_ static const char *target_sub_state_to_string(Unit *u) { - assert(u); - - return target_state_to_string(TARGET(u)->state); -} - -const UnitVTable target_vtable = { - .object_size = sizeof(Target), - - .sections = - "Unit\0" - "Target\0" - "Install\0", - - .load = target_load, - .coldplug = target_coldplug, - - .dump = target_dump, - - .start = target_start, - .stop = target_stop, - - .serialize = target_serialize, - .deserialize_item = target_deserialize_item, - - .active_state = target_active_state, - .sub_state_to_string = target_sub_state_to_string, - - .bus_vtable = bus_target_vtable, - - .status_message_formats = { - .finished_start_job = { - [JOB_DONE] = "Reached target %s.", - }, - .finished_stop_job = { - [JOB_DONE] = "Stopped target %s.", - }, - }, -}; diff --git a/src/core/target.h b/src/core/target.h deleted file mode 100644 index 339aea154e..0000000000 --- a/src/core/target.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct Target Target; - -struct Target { - Unit meta; - - TargetState state, deserialized_state; -}; - -extern const UnitVTable target_vtable; diff --git a/src/core/timer.c b/src/core/timer.c deleted file mode 100644 index 3206296f09..0000000000 --- a/src/core/timer.c +++ /dev/null @@ -1,859 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "dbus-timer.h" -#include "fs-util.h" -#include "parse-util.h" -#include "random-util.h" -#include "special.h" -#include "string-table.h" -#include "string-util.h" -#include "timer.h" -#include "unit-name.h" -#include "unit.h" -#include "user-util.h" -#include "virt.h" - -static const UnitActiveState state_translation_table[_TIMER_STATE_MAX] = { - [TIMER_DEAD] = UNIT_INACTIVE, - [TIMER_WAITING] = UNIT_ACTIVE, - [TIMER_RUNNING] = UNIT_ACTIVE, - [TIMER_ELAPSED] = UNIT_ACTIVE, - [TIMER_FAILED] = UNIT_FAILED -}; - -static int timer_dispatch(sd_event_source *s, uint64_t usec, void *userdata); - -static void timer_init(Unit *u) { - Timer *t = TIMER(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - t->next_elapse_monotonic_or_boottime = USEC_INFINITY; - t->next_elapse_realtime = USEC_INFINITY; - t->accuracy_usec = u->manager->default_timer_accuracy_usec; - t->remain_after_elapse = true; -} - -void timer_free_values(Timer *t) { - TimerValue *v; - - assert(t); - - while ((v = t->values)) { - LIST_REMOVE(value, t->values, v); - calendar_spec_free(v->calendar_spec); - free(v); - } -} - -static void timer_done(Unit *u) { - Timer *t = TIMER(u); - - assert(t); - - timer_free_values(t); - - t->monotonic_event_source = sd_event_source_unref(t->monotonic_event_source); - t->realtime_event_source = sd_event_source_unref(t->realtime_event_source); - - free(t->stamp_path); -} - -static int timer_verify(Timer *t) { - assert(t); - - if (UNIT(t)->load_state != UNIT_LOADED) - return 0; - - if (!t->values) { - log_unit_error(UNIT(t), "Timer unit lacks value setting. Refusing."); - return -EINVAL; - } - - return 0; -} - -static int timer_add_default_dependencies(Timer *t) { - int r; - TimerValue *v; - - assert(t); - - if (!UNIT(t)->default_dependencies) - return 0; - - r = unit_add_dependency_by_name(UNIT(t), UNIT_BEFORE, SPECIAL_TIMERS_TARGET, NULL, true); - if (r < 0) - return r; - - if (MANAGER_IS_SYSTEM(UNIT(t)->manager)) { - r = unit_add_two_dependencies_by_name(UNIT(t), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); - if (r < 0) - return r; - - LIST_FOREACH(value, v, t->values) { - if (v->base == TIMER_CALENDAR) { - r = unit_add_dependency_by_name(UNIT(t), UNIT_AFTER, SPECIAL_TIME_SYNC_TARGET, NULL, true); - if (r < 0) - return r; - break; - } - } - } - - return unit_add_two_dependencies_by_name(UNIT(t), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); -} - -static int timer_setup_persistent(Timer *t) { - int r; - - assert(t); - - if (!t->persistent) - return 0; - - if (MANAGER_IS_SYSTEM(UNIT(t)->manager)) { - - r = unit_require_mounts_for(UNIT(t), "/var/lib/systemd/timers"); - if (r < 0) - return r; - - t->stamp_path = strappend("/var/lib/systemd/timers/stamp-", UNIT(t)->id); - } else { - const char *e; - - e = getenv("XDG_DATA_HOME"); - if (e) - t->stamp_path = strjoin(e, "/systemd/timers/stamp-", UNIT(t)->id, NULL); - else { - - _cleanup_free_ char *h = NULL; - - r = get_home_dir(&h); - if (r < 0) - return log_unit_error_errno(UNIT(t), r, "Failed to determine home directory: %m"); - - t->stamp_path = strjoin(h, "/.local/share/systemd/timers/stamp-", UNIT(t)->id, NULL); - } - } - - if (!t->stamp_path) - return log_oom(); - - return 0; -} - -static int timer_load(Unit *u) { - Timer *t = TIMER(u); - int r; - - assert(u); - assert(u->load_state == UNIT_STUB); - - r = unit_load_fragment_and_dropin(u); - if (r < 0) - return r; - - if (u->load_state == UNIT_LOADED) { - - if (set_isempty(u->dependencies[UNIT_TRIGGERS])) { - Unit *x; - - r = unit_load_related_unit(u, ".service", &x); - if (r < 0) - return r; - - r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, x, true); - if (r < 0) - return r; - } - - r = timer_setup_persistent(t); - if (r < 0) - return r; - - r = timer_add_default_dependencies(t); - if (r < 0) - return r; - } - - return timer_verify(t); -} - -static void timer_dump(Unit *u, FILE *f, const char *prefix) { - char buf[FORMAT_TIMESPAN_MAX]; - Timer *t = TIMER(u); - Unit *trigger; - TimerValue *v; - - trigger = UNIT_TRIGGER(u); - - fprintf(f, - "%sTimer State: %s\n" - "%sResult: %s\n" - "%sUnit: %s\n" - "%sPersistent: %s\n" - "%sWakeSystem: %s\n" - "%sAccuracy: %s\n" - "%sRemainAfterElapse: %s\n", - prefix, timer_state_to_string(t->state), - prefix, timer_result_to_string(t->result), - prefix, trigger ? trigger->id : "n/a", - prefix, yes_no(t->persistent), - prefix, yes_no(t->wake_system), - prefix, format_timespan(buf, sizeof(buf), t->accuracy_usec, 1), - prefix, yes_no(t->remain_after_elapse)); - - LIST_FOREACH(value, v, t->values) { - - if (v->base == TIMER_CALENDAR) { - _cleanup_free_ char *p = NULL; - - calendar_spec_to_string(v->calendar_spec, &p); - - fprintf(f, - "%s%s: %s\n", - prefix, - timer_base_to_string(v->base), - strna(p)); - } else { - char timespan1[FORMAT_TIMESPAN_MAX]; - - fprintf(f, - "%s%s: %s\n", - prefix, - timer_base_to_string(v->base), - format_timespan(timespan1, sizeof(timespan1), v->value, 0)); - } - } -} - -static void timer_set_state(Timer *t, TimerState state) { - TimerState old_state; - assert(t); - - old_state = t->state; - t->state = state; - - if (state != TIMER_WAITING) { - t->monotonic_event_source = sd_event_source_unref(t->monotonic_event_source); - t->realtime_event_source = sd_event_source_unref(t->realtime_event_source); - } - - if (state != old_state) - log_unit_debug(UNIT(t), "Changed %s -> %s", timer_state_to_string(old_state), timer_state_to_string(state)); - - unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], true); -} - -static void timer_enter_waiting(Timer *t, bool initial); - -static int timer_coldplug(Unit *u) { - Timer *t = TIMER(u); - - assert(t); - assert(t->state == TIMER_DEAD); - - if (t->deserialized_state == t->state) - return 0; - - if (t->deserialized_state == TIMER_WAITING) - timer_enter_waiting(t, false); - else - timer_set_state(t, t->deserialized_state); - - return 0; -} - -static void timer_enter_dead(Timer *t, TimerResult f) { - assert(t); - - if (f != TIMER_SUCCESS) - t->result = f; - - timer_set_state(t, t->result != TIMER_SUCCESS ? TIMER_FAILED : TIMER_DEAD); -} - -static void timer_enter_elapsed(Timer *t, bool leave_around) { - assert(t); - - /* If a unit is marked with RemainAfterElapse=yes we leave it - * around even after it elapsed once, so that starting it - * later again does not necessarily mean immediate - * retriggering. We unconditionally leave units with - * TIMER_UNIT_ACTIVE or TIMER_UNIT_INACTIVE triggers around, - * since they might be restarted automatically at any time - * later on. */ - - if (t->remain_after_elapse || leave_around) - timer_set_state(t, TIMER_ELAPSED); - else - timer_enter_dead(t, TIMER_SUCCESS); -} - -static usec_t monotonic_to_boottime(usec_t t) { - usec_t a, b; - - if (t <= 0) - return 0; - - a = now(clock_boottime_or_monotonic()); - b = now(CLOCK_MONOTONIC); - - if (t + a > b) - return t + a - b; - else - return 0; -} - -static void add_random(Timer *t, usec_t *v) { - char s[FORMAT_TIMESPAN_MAX]; - usec_t add; - - assert(t); - assert(v); - - if (t->random_usec == 0) - return; - if (*v == USEC_INFINITY) - return; - - add = random_u64() % t->random_usec; - - if (*v + add < *v) /* overflow */ - *v = (usec_t) -2; /* Highest possible value, that is not USEC_INFINITY */ - else - *v += add; - - log_unit_info(UNIT(t), "Adding %s random time.", format_timespan(s, sizeof(s), add, 0)); -} - -static void timer_enter_waiting(Timer *t, bool initial) { - bool found_monotonic = false, found_realtime = false; - usec_t ts_realtime, ts_monotonic; - usec_t base = 0; - bool leave_around = false; - TimerValue *v; - Unit *trigger; - int r; - - assert(t); - - trigger = UNIT_TRIGGER(UNIT(t)); - if (!trigger) { - log_unit_error(UNIT(t), "Unit to trigger vanished."); - timer_enter_dead(t, TIMER_FAILURE_RESOURCES); - return; - } - - /* If we shall wake the system we use the boottime clock - * rather than the monotonic clock. */ - - ts_realtime = now(CLOCK_REALTIME); - ts_monotonic = now(t->wake_system ? clock_boottime_or_monotonic() : CLOCK_MONOTONIC); - t->next_elapse_monotonic_or_boottime = t->next_elapse_realtime = 0; - - LIST_FOREACH(value, v, t->values) { - - if (v->disabled) - continue; - - if (v->base == TIMER_CALENDAR) { - usec_t b; - - /* If we know the last time this was - * triggered, schedule the job based relative - * to that. If we don't just start from - * now. */ - - b = t->last_trigger.realtime > 0 ? t->last_trigger.realtime : ts_realtime; - - r = calendar_spec_next_usec(v->calendar_spec, b, &v->next_elapse); - if (r < 0) - continue; - - if (!found_realtime) - t->next_elapse_realtime = v->next_elapse; - else - t->next_elapse_realtime = MIN(t->next_elapse_realtime, v->next_elapse); - - found_realtime = true; - - } else { - switch (v->base) { - - case TIMER_ACTIVE: - if (state_translation_table[t->state] == UNIT_ACTIVE) - base = UNIT(t)->inactive_exit_timestamp.monotonic; - else - base = ts_monotonic; - break; - - case TIMER_BOOT: - if (detect_container() <= 0) { - /* CLOCK_MONOTONIC equals the uptime on Linux */ - base = 0; - break; - } - /* In a container we don't want to include the time the host - * was already up when the container started, so count from - * our own startup. Fall through. */ - case TIMER_STARTUP: - base = UNIT(t)->manager->userspace_timestamp.monotonic; - break; - - case TIMER_UNIT_ACTIVE: - leave_around = true; - base = trigger->inactive_exit_timestamp.monotonic; - - if (base <= 0) - base = t->last_trigger.monotonic; - - if (base <= 0) - continue; - - break; - - case TIMER_UNIT_INACTIVE: - leave_around = true; - base = trigger->inactive_enter_timestamp.monotonic; - - if (base <= 0) - base = t->last_trigger.monotonic; - - if (base <= 0) - continue; - - break; - - default: - assert_not_reached("Unknown timer base"); - } - - if (t->wake_system) - base = monotonic_to_boottime(base); - - v->next_elapse = base + v->value; - - if (!initial && v->next_elapse < ts_monotonic && IN_SET(v->base, TIMER_ACTIVE, TIMER_BOOT, TIMER_STARTUP)) { - /* This is a one time trigger, disable it now */ - v->disabled = true; - continue; - } - - if (!found_monotonic) - t->next_elapse_monotonic_or_boottime = v->next_elapse; - else - t->next_elapse_monotonic_or_boottime = MIN(t->next_elapse_monotonic_or_boottime, v->next_elapse); - - found_monotonic = true; - } - } - - if (!found_monotonic && !found_realtime) { - log_unit_debug(UNIT(t), "Timer is elapsed."); - timer_enter_elapsed(t, leave_around); - return; - } - - if (found_monotonic) { - char buf[FORMAT_TIMESPAN_MAX]; - usec_t left; - - add_random(t, &t->next_elapse_monotonic_or_boottime); - - left = t->next_elapse_monotonic_or_boottime > ts_monotonic ? t->next_elapse_monotonic_or_boottime - ts_monotonic : 0; - log_unit_debug(UNIT(t), "Monotonic timer elapses in %s.", format_timespan(buf, sizeof(buf), left, 0)); - - if (t->monotonic_event_source) { - r = sd_event_source_set_time(t->monotonic_event_source, t->next_elapse_monotonic_or_boottime); - if (r < 0) - goto fail; - - r = sd_event_source_set_enabled(t->monotonic_event_source, SD_EVENT_ONESHOT); - if (r < 0) - goto fail; - } else { - - r = sd_event_add_time( - UNIT(t)->manager->event, - &t->monotonic_event_source, - t->wake_system ? CLOCK_BOOTTIME_ALARM : CLOCK_MONOTONIC, - t->next_elapse_monotonic_or_boottime, t->accuracy_usec, - timer_dispatch, t); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(t->monotonic_event_source, "timer-monotonic"); - } - - } else if (t->monotonic_event_source) { - - r = sd_event_source_set_enabled(t->monotonic_event_source, SD_EVENT_OFF); - if (r < 0) - goto fail; - } - - if (found_realtime) { - char buf[FORMAT_TIMESTAMP_MAX]; - - add_random(t, &t->next_elapse_realtime); - - log_unit_debug(UNIT(t), "Realtime timer elapses at %s.", format_timestamp(buf, sizeof(buf), t->next_elapse_realtime)); - - if (t->realtime_event_source) { - r = sd_event_source_set_time(t->realtime_event_source, t->next_elapse_realtime); - if (r < 0) - goto fail; - - r = sd_event_source_set_enabled(t->realtime_event_source, SD_EVENT_ONESHOT); - if (r < 0) - goto fail; - } else { - r = sd_event_add_time( - UNIT(t)->manager->event, - &t->realtime_event_source, - t->wake_system ? CLOCK_REALTIME_ALARM : CLOCK_REALTIME, - t->next_elapse_realtime, t->accuracy_usec, - timer_dispatch, t); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(t->realtime_event_source, "timer-realtime"); - } - - } else if (t->realtime_event_source) { - - r = sd_event_source_set_enabled(t->realtime_event_source, SD_EVENT_OFF); - if (r < 0) - goto fail; - } - - timer_set_state(t, TIMER_WAITING); - return; - -fail: - log_unit_warning_errno(UNIT(t), r, "Failed to enter waiting state: %m"); - timer_enter_dead(t, TIMER_FAILURE_RESOURCES); -} - -static void timer_enter_running(Timer *t) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - Unit *trigger; - int r; - - assert(t); - - /* Don't start job if we are supposed to go down */ - if (unit_stop_pending(UNIT(t))) - return; - - trigger = UNIT_TRIGGER(UNIT(t)); - if (!trigger) { - log_unit_error(UNIT(t), "Unit to trigger vanished."); - timer_enter_dead(t, TIMER_FAILURE_RESOURCES); - return; - } - - r = manager_add_job(UNIT(t)->manager, JOB_START, trigger, JOB_REPLACE, &error, NULL); - if (r < 0) - goto fail; - - dual_timestamp_get(&t->last_trigger); - - if (t->stamp_path) - touch_file(t->stamp_path, true, t->last_trigger.realtime, UID_INVALID, GID_INVALID, MODE_INVALID); - - timer_set_state(t, TIMER_RUNNING); - return; - -fail: - log_unit_warning(UNIT(t), "Failed to queue unit startup job: %s", bus_error_message(&error, r)); - timer_enter_dead(t, TIMER_FAILURE_RESOURCES); -} - -static int timer_start(Unit *u) { - Timer *t = TIMER(u); - TimerValue *v; - Unit *trigger; - int r; - - assert(t); - assert(t->state == TIMER_DEAD || t->state == TIMER_FAILED); - - trigger = UNIT_TRIGGER(u); - if (!trigger || trigger->load_state != UNIT_LOADED) { - log_unit_error(u, "Refusing to start, unit to trigger not loaded."); - return -ENOENT; - } - - r = unit_start_limit_test(u); - if (r < 0) { - timer_enter_dead(t, TIMER_FAILURE_START_LIMIT_HIT); - return r; - } - - t->last_trigger = DUAL_TIMESTAMP_NULL; - - /* Reenable all timers that depend on unit activation time */ - LIST_FOREACH(value, v, t->values) - if (v->base == TIMER_ACTIVE) - v->disabled = false; - - if (t->stamp_path) { - struct stat st; - - if (stat(t->stamp_path, &st) >= 0) - t->last_trigger.realtime = timespec_load(&st.st_atim); - else if (errno == ENOENT) - /* The timer has never run before, - * make sure a stamp file exists. - */ - touch_file(t->stamp_path, true, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID); - } - - t->result = TIMER_SUCCESS; - timer_enter_waiting(t, true); - return 1; -} - -static int timer_stop(Unit *u) { - Timer *t = TIMER(u); - - assert(t); - assert(t->state == TIMER_WAITING || t->state == TIMER_RUNNING || t->state == TIMER_ELAPSED); - - timer_enter_dead(t, TIMER_SUCCESS); - return 1; -} - -static int timer_serialize(Unit *u, FILE *f, FDSet *fds) { - Timer *t = TIMER(u); - - assert(u); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", timer_state_to_string(t->state)); - unit_serialize_item(u, f, "result", timer_result_to_string(t->result)); - - if (t->last_trigger.realtime > 0) - unit_serialize_item_format(u, f, "last-trigger-realtime", "%" PRIu64, t->last_trigger.realtime); - - if (t->last_trigger.monotonic > 0) - unit_serialize_item_format(u, f, "last-trigger-monotonic", "%" PRIu64, t->last_trigger.monotonic); - - return 0; -} - -static int timer_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Timer *t = TIMER(u); - int r; - - assert(u); - assert(key); - assert(value); - assert(fds); - - if (streq(key, "state")) { - TimerState state; - - state = timer_state_from_string(value); - if (state < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - t->deserialized_state = state; - } else if (streq(key, "result")) { - TimerResult f; - - f = timer_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse result value: %s", value); - else if (f != TIMER_SUCCESS) - t->result = f; - } else if (streq(key, "last-trigger-realtime")) { - - r = safe_atou64(value, &t->last_trigger.realtime); - if (r < 0) - log_unit_debug(u, "Failed to parse last-trigger-realtime value: %s", value); - - } else if (streq(key, "last-trigger-monotonic")) { - - r = safe_atou64(value, &t->last_trigger.monotonic); - if (r < 0) - log_unit_debug(u, "Failed to parse last-trigger-monotonic value: %s", value); - - } else - log_unit_debug(u, "Unknown serialization key: %s", key); - - return 0; -} - -_pure_ static UnitActiveState timer_active_state(Unit *u) { - assert(u); - - return state_translation_table[TIMER(u)->state]; -} - -_pure_ static const char *timer_sub_state_to_string(Unit *u) { - assert(u); - - return timer_state_to_string(TIMER(u)->state); -} - -static int timer_dispatch(sd_event_source *s, uint64_t usec, void *userdata) { - Timer *t = TIMER(userdata); - - assert(t); - - if (t->state != TIMER_WAITING) - return 0; - - log_unit_debug(UNIT(t), "Timer elapsed."); - timer_enter_running(t); - return 0; -} - -static void timer_trigger_notify(Unit *u, Unit *other) { - Timer *t = TIMER(u); - TimerValue *v; - - assert(u); - assert(other); - - if (other->load_state != UNIT_LOADED) - return; - - /* Reenable all timers that depend on unit state */ - LIST_FOREACH(value, v, t->values) - if (v->base == TIMER_UNIT_ACTIVE || - v->base == TIMER_UNIT_INACTIVE) - v->disabled = false; - - switch (t->state) { - - case TIMER_WAITING: - case TIMER_ELAPSED: - - /* Recalculate sleep time */ - timer_enter_waiting(t, false); - break; - - case TIMER_RUNNING: - - if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) { - log_unit_debug(UNIT(t), "Got notified about unit deactivation."); - timer_enter_waiting(t, false); - } - break; - - case TIMER_DEAD: - case TIMER_FAILED: - break; - - default: - assert_not_reached("Unknown timer state"); - } -} - -static void timer_reset_failed(Unit *u) { - Timer *t = TIMER(u); - - assert(t); - - if (t->state == TIMER_FAILED) - timer_set_state(t, TIMER_DEAD); - - t->result = TIMER_SUCCESS; -} - -static void timer_time_change(Unit *u) { - Timer *t = TIMER(u); - - assert(u); - - if (t->state != TIMER_WAITING) - return; - - log_unit_debug(u, "Time change, recalculating next elapse."); - timer_enter_waiting(t, false); -} - -static const char* const timer_base_table[_TIMER_BASE_MAX] = { - [TIMER_ACTIVE] = "OnActiveSec", - [TIMER_BOOT] = "OnBootSec", - [TIMER_STARTUP] = "OnStartupSec", - [TIMER_UNIT_ACTIVE] = "OnUnitActiveSec", - [TIMER_UNIT_INACTIVE] = "OnUnitInactiveSec", - [TIMER_CALENDAR] = "OnCalendar" -}; - -DEFINE_STRING_TABLE_LOOKUP(timer_base, TimerBase); - -static const char* const timer_result_table[_TIMER_RESULT_MAX] = { - [TIMER_SUCCESS] = "success", - [TIMER_FAILURE_RESOURCES] = "resources", - [TIMER_FAILURE_START_LIMIT_HIT] = "start-limit-hit", -}; - -DEFINE_STRING_TABLE_LOOKUP(timer_result, TimerResult); - -const UnitVTable timer_vtable = { - .object_size = sizeof(Timer), - - .sections = - "Unit\0" - "Timer\0" - "Install\0", - .private_section = "Timer", - - .init = timer_init, - .done = timer_done, - .load = timer_load, - - .coldplug = timer_coldplug, - - .dump = timer_dump, - - .start = timer_start, - .stop = timer_stop, - - .serialize = timer_serialize, - .deserialize_item = timer_deserialize_item, - - .active_state = timer_active_state, - .sub_state_to_string = timer_sub_state_to_string, - - .trigger_notify = timer_trigger_notify, - - .reset_failed = timer_reset_failed, - .time_change = timer_time_change, - - .bus_vtable = bus_timer_vtable, - .bus_set_property = bus_timer_set_property, - - .can_transient = true, -}; diff --git a/src/core/timer.h b/src/core/timer.h deleted file mode 100644 index 9c4b64f898..0000000000 --- a/src/core/timer.h +++ /dev/null @@ -1,89 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct Timer Timer; - -#include "calendarspec.h" - -typedef enum TimerBase { - TIMER_ACTIVE, - TIMER_BOOT, - TIMER_STARTUP, - TIMER_UNIT_ACTIVE, - TIMER_UNIT_INACTIVE, - TIMER_CALENDAR, - _TIMER_BASE_MAX, - _TIMER_BASE_INVALID = -1 -} TimerBase; - -typedef struct TimerValue { - TimerBase base; - bool disabled; - - usec_t value; /* only for monotonic events */ - CalendarSpec *calendar_spec; /* only for calendar events */ - usec_t next_elapse; - - LIST_FIELDS(struct TimerValue, value); -} TimerValue; - -typedef enum TimerResult { - TIMER_SUCCESS, - TIMER_FAILURE_RESOURCES, - TIMER_FAILURE_START_LIMIT_HIT, - _TIMER_RESULT_MAX, - _TIMER_RESULT_INVALID = -1 -} TimerResult; - -struct Timer { - Unit meta; - - usec_t accuracy_usec; - usec_t random_usec; - - LIST_HEAD(TimerValue, values); - usec_t next_elapse_realtime; - usec_t next_elapse_monotonic_or_boottime; - dual_timestamp last_trigger; - - TimerState state, deserialized_state; - - sd_event_source *monotonic_event_source; - sd_event_source *realtime_event_source; - - TimerResult result; - - bool persistent; - bool wake_system; - bool remain_after_elapse; - - char *stamp_path; -}; - -void timer_free_values(Timer *t); - -extern const UnitVTable timer_vtable; - -const char *timer_base_to_string(TimerBase i) _const_; -TimerBase timer_base_from_string(const char *s) _pure_; - -const char* timer_result_to_string(TimerResult i) _const_; -TimerResult timer_result_from_string(const char *s) _pure_; diff --git a/src/core/transaction.c b/src/core/transaction.c deleted file mode 100644 index e06a48a2f1..0000000000 --- a/src/core/transaction.c +++ /dev/null @@ -1,1099 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-error.h" -#include "terminal-util.h" -#include "transaction.h" -#include "dbus-unit.h" - -static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies); - -static void transaction_delete_job(Transaction *tr, Job *j, bool delete_dependencies) { - assert(tr); - assert(j); - - /* Deletes one job from the transaction */ - - transaction_unlink_job(tr, j, delete_dependencies); - - job_free(j); -} - -static void transaction_delete_unit(Transaction *tr, Unit *u) { - Job *j; - - /* Deletes all jobs associated with a certain unit from the - * transaction */ - - while ((j = hashmap_get(tr->jobs, u))) - transaction_delete_job(tr, j, true); -} - -void transaction_abort(Transaction *tr) { - Job *j; - - assert(tr); - - while ((j = hashmap_first(tr->jobs))) - transaction_delete_job(tr, j, false); - - assert(hashmap_isempty(tr->jobs)); -} - -static void transaction_find_jobs_that_matter_to_anchor(Job *j, unsigned generation) { - JobDependency *l; - - /* A recursive sweep through the graph that marks all units - * that matter to the anchor job, i.e. are directly or - * indirectly a dependency of the anchor job via paths that - * are fully marked as mattering. */ - - j->matters_to_anchor = true; - j->generation = generation; - - LIST_FOREACH(subject, l, j->subject_list) { - - /* This link does not matter */ - if (!l->matters) - continue; - - /* This unit has already been marked */ - if (l->object->generation == generation) - continue; - - transaction_find_jobs_that_matter_to_anchor(l->object, generation); - } -} - -static void transaction_merge_and_delete_job(Transaction *tr, Job *j, Job *other, JobType t) { - JobDependency *l, *last; - - assert(j); - assert(other); - assert(j->unit == other->unit); - assert(!j->installed); - - /* Merges 'other' into 'j' and then deletes 'other'. */ - - j->type = t; - j->state = JOB_WAITING; - j->irreversible = j->irreversible || other->irreversible; - j->matters_to_anchor = j->matters_to_anchor || other->matters_to_anchor; - - /* Patch us in as new owner of the JobDependency objects */ - last = NULL; - LIST_FOREACH(subject, l, other->subject_list) { - assert(l->subject == other); - l->subject = j; - last = l; - } - - /* Merge both lists */ - if (last) { - last->subject_next = j->subject_list; - if (j->subject_list) - j->subject_list->subject_prev = last; - j->subject_list = other->subject_list; - } - - /* Patch us in as new owner of the JobDependency objects */ - last = NULL; - LIST_FOREACH(object, l, other->object_list) { - assert(l->object == other); - l->object = j; - last = l; - } - - /* Merge both lists */ - if (last) { - last->object_next = j->object_list; - if (j->object_list) - j->object_list->object_prev = last; - j->object_list = other->object_list; - } - - /* Kill the other job */ - other->subject_list = NULL; - other->object_list = NULL; - transaction_delete_job(tr, other, true); -} - -_pure_ static bool job_is_conflicted_by(Job *j) { - JobDependency *l; - - assert(j); - - /* Returns true if this job is pulled in by a least one - * ConflictedBy dependency. */ - - LIST_FOREACH(object, l, j->object_list) - if (l->conflicts) - return true; - - return false; -} - -static int delete_one_unmergeable_job(Transaction *tr, Job *j) { - Job *k; - - assert(j); - - /* Tries to delete one item in the linked list - * j->transaction_next->transaction_next->... that conflicts - * with another one, in an attempt to make an inconsistent - * transaction work. */ - - /* We rely here on the fact that if a merged with b does not - * merge with c, either a or b merge with c neither */ - LIST_FOREACH(transaction, j, j) - LIST_FOREACH(transaction, k, j->transaction_next) { - Job *d; - - /* Is this one mergeable? Then skip it */ - if (job_type_is_mergeable(j->type, k->type)) - continue; - - /* Ok, we found two that conflict, let's see if we can - * drop one of them */ - if (!j->matters_to_anchor && !k->matters_to_anchor) { - - /* Both jobs don't matter, so let's - * find the one that is smarter to - * remove. Let's think positive and - * rather remove stops then starts -- - * except if something is being - * stopped because it is conflicted by - * another unit in which case we - * rather remove the start. */ - - log_unit_debug(j->unit, - "Looking at job %s/%s conflicted_by=%s", - j->unit->id, job_type_to_string(j->type), - yes_no(j->type == JOB_STOP && job_is_conflicted_by(j))); - log_unit_debug(k->unit, - "Looking at job %s/%s conflicted_by=%s", - k->unit->id, job_type_to_string(k->type), - yes_no(k->type == JOB_STOP && job_is_conflicted_by(k))); - - if (j->type == JOB_STOP) { - - if (job_is_conflicted_by(j)) - d = k; - else - d = j; - - } else if (k->type == JOB_STOP) { - - if (job_is_conflicted_by(k)) - d = j; - else - d = k; - } else - d = j; - - } else if (!j->matters_to_anchor) - d = j; - else if (!k->matters_to_anchor) - d = k; - else - return -ENOEXEC; - - /* Ok, we can drop one, so let's do so. */ - log_unit_debug(d->unit, - "Fixing conflicting jobs %s/%s,%s/%s by deleting job %s/%s", - j->unit->id, job_type_to_string(j->type), - k->unit->id, job_type_to_string(k->type), - d->unit->id, job_type_to_string(d->type)); - transaction_delete_job(tr, d, true); - return 0; - } - - return -EINVAL; -} - -static int transaction_merge_jobs(Transaction *tr, sd_bus_error *e) { - Job *j; - Iterator i; - int r; - - assert(tr); - - /* First step, check whether any of the jobs for one specific - * task conflict. If so, try to drop one of them. */ - HASHMAP_FOREACH(j, tr->jobs, i) { - JobType t; - Job *k; - - t = j->type; - LIST_FOREACH(transaction, k, j->transaction_next) { - if (job_type_merge_and_collapse(&t, k->type, j->unit) >= 0) - continue; - - /* OK, we could not merge all jobs for this - * action. Let's see if we can get rid of one - * of them */ - - r = delete_one_unmergeable_job(tr, j); - if (r >= 0) - /* Ok, we managed to drop one, now - * let's ask our callers to call us - * again after garbage collecting */ - return -EAGAIN; - - /* We couldn't merge anything. Failure */ - return sd_bus_error_setf(e, BUS_ERROR_TRANSACTION_JOBS_CONFLICTING, - "Transaction contains conflicting jobs '%s' and '%s' for %s. " - "Probably contradicting requirement dependencies configured.", - job_type_to_string(t), - job_type_to_string(k->type), - k->unit->id); - } - } - - /* Second step, merge the jobs. */ - HASHMAP_FOREACH(j, tr->jobs, i) { - JobType t = j->type; - Job *k; - - /* Merge all transaction jobs for j->unit */ - LIST_FOREACH(transaction, k, j->transaction_next) - assert_se(job_type_merge_and_collapse(&t, k->type, j->unit) == 0); - - while ((k = j->transaction_next)) { - if (tr->anchor_job == k) { - transaction_merge_and_delete_job(tr, k, j, t); - j = k; - } else - transaction_merge_and_delete_job(tr, j, k, t); - } - - assert(!j->transaction_next); - assert(!j->transaction_prev); - } - - return 0; -} - -static void transaction_drop_redundant(Transaction *tr) { - Job *j; - Iterator i; - - /* Goes through the transaction and removes all jobs of the units - * whose jobs are all noops. If not all of a unit's jobs are - * redundant, they are kept. */ - - assert(tr); - -rescan: - HASHMAP_FOREACH(j, tr->jobs, i) { - Job *k; - - LIST_FOREACH(transaction, k, j) { - - if (tr->anchor_job == k || - !job_type_is_redundant(k->type, unit_active_state(k->unit)) || - (k->unit->job && job_type_is_conflicting(k->type, k->unit->job->type))) - goto next_unit; - } - - /* log_debug("Found redundant job %s/%s, dropping.", j->unit->id, job_type_to_string(j->type)); */ - transaction_delete_job(tr, j, false); - goto rescan; - next_unit:; - } -} - -_pure_ static bool unit_matters_to_anchor(Unit *u, Job *j) { - assert(u); - assert(!j->transaction_prev); - - /* Checks whether at least one of the jobs for this unit - * matters to the anchor. */ - - LIST_FOREACH(transaction, j, j) - if (j->matters_to_anchor) - return true; - - return false; -} - -static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsigned generation, sd_bus_error *e) { - Iterator i; - Unit *u; - int r; - - assert(tr); - assert(j); - assert(!j->transaction_prev); - - /* Does a recursive sweep through the ordering graph, looking - * for a cycle. If we find a cycle we try to break it. */ - - /* Have we seen this before? */ - if (j->generation == generation) { - Job *k, *delete; - - /* If the marker is NULL we have been here already and - * decided the job was loop-free from here. Hence - * shortcut things and return right-away. */ - if (!j->marker) - return 0; - - /* So, the marker is not NULL and we already have been - * here. We have a cycle. Let's try to break it. We go - * backwards in our path and try to find a suitable - * job to remove. We use the marker to find our way - * back, since smart how we are we stored our way back - * in there. */ - log_unit_warning(j->unit, - "Found ordering cycle on %s/%s", - j->unit->id, job_type_to_string(j->type)); - - delete = NULL; - for (k = from; k; k = ((k->generation == generation && k->marker != k) ? k->marker : NULL)) { - - /* logging for j not k here here to provide consistent narrative */ - log_unit_warning(j->unit, - "Found dependency on %s/%s", - k->unit->id, job_type_to_string(k->type)); - - if (!delete && hashmap_get(tr->jobs, k->unit) && !unit_matters_to_anchor(k->unit, k)) - /* Ok, we can drop this one, so let's - * do so. */ - delete = k; - - /* Check if this in fact was the beginning of - * the cycle */ - if (k == j) - break; - } - - - if (delete) { - const char *status; - /* logging for j not k here here to provide consistent narrative */ - log_unit_warning(j->unit, - "Breaking ordering cycle by deleting job %s/%s", - delete->unit->id, job_type_to_string(delete->type)); - log_unit_error(delete->unit, - "Job %s/%s deleted to break ordering cycle starting with %s/%s", - delete->unit->id, job_type_to_string(delete->type), - j->unit->id, job_type_to_string(j->type)); - - if (log_get_show_color()) - status = ANSI_HIGHLIGHT_RED " SKIP " ANSI_NORMAL; - else - status = " SKIP "; - - unit_status_printf(delete->unit, status, - "Ordering cycle found, skipping %s"); - transaction_delete_unit(tr, delete->unit); - return -EAGAIN; - } - - log_error("Unable to break cycle"); - - return sd_bus_error_setf(e, BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC, - "Transaction order is cyclic. See system logs for details."); - } - - /* Make the marker point to where we come from, so that we can - * find our way backwards if we want to break a cycle. We use - * a special marker for the beginning: we point to - * ourselves. */ - j->marker = from ? from : j; - j->generation = generation; - - /* We assume that the dependencies are bidirectional, and - * hence can ignore UNIT_AFTER */ - SET_FOREACH(u, j->unit->dependencies[UNIT_BEFORE], i) { - Job *o; - - /* Is there a job for this unit? */ - o = hashmap_get(tr->jobs, u); - if (!o) { - /* Ok, there is no job for this in the - * transaction, but maybe there is already one - * running? */ - o = u->job; - if (!o) - continue; - } - - r = transaction_verify_order_one(tr, o, j, generation, e); - if (r < 0) - return r; - } - - /* Ok, let's backtrack, and remember that this entry is not on - * our path anymore. */ - j->marker = NULL; - - return 0; -} - -static int transaction_verify_order(Transaction *tr, unsigned *generation, sd_bus_error *e) { - Job *j; - int r; - Iterator i; - unsigned g; - - assert(tr); - assert(generation); - - /* Check if the ordering graph is cyclic. If it is, try to fix - * that up by dropping one of the jobs. */ - - g = (*generation)++; - - HASHMAP_FOREACH(j, tr->jobs, i) { - r = transaction_verify_order_one(tr, j, NULL, g, e); - if (r < 0) - return r; - } - - return 0; -} - -static void transaction_collect_garbage(Transaction *tr) { - Iterator i; - Job *j; - - assert(tr); - - /* Drop jobs that are not required by any other job */ - -rescan: - HASHMAP_FOREACH(j, tr->jobs, i) { - if (tr->anchor_job == j || j->object_list) { - /* log_debug("Keeping job %s/%s because of %s/%s", */ - /* j->unit->id, job_type_to_string(j->type), */ - /* j->object_list->subject ? j->object_list->subject->unit->id : "root", */ - /* j->object_list->subject ? job_type_to_string(j->object_list->subject->type) : "root"); */ - continue; - } - - /* log_debug("Garbage collecting job %s/%s", j->unit->id, job_type_to_string(j->type)); */ - transaction_delete_job(tr, j, true); - goto rescan; - } -} - -static int transaction_is_destructive(Transaction *tr, JobMode mode, sd_bus_error *e) { - Iterator i; - Job *j; - - assert(tr); - - /* Checks whether applying this transaction means that - * existing jobs would be replaced */ - - HASHMAP_FOREACH(j, tr->jobs, i) { - - /* Assume merged */ - assert(!j->transaction_prev); - assert(!j->transaction_next); - - if (j->unit->job && (mode == JOB_FAIL || j->unit->job->irreversible) && - job_type_is_conflicting(j->unit->job->type, j->type)) - return sd_bus_error_setf(e, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE, - "Transaction is destructive."); - } - - return 0; -} - -static void transaction_minimize_impact(Transaction *tr) { - Job *j; - Iterator i; - - assert(tr); - - /* Drops all unnecessary jobs that reverse already active jobs - * or that stop a running service. */ - -rescan: - HASHMAP_FOREACH(j, tr->jobs, i) { - LIST_FOREACH(transaction, j, j) { - bool stops_running_service, changes_existing_job; - - /* If it matters, we shouldn't drop it */ - if (j->matters_to_anchor) - continue; - - /* Would this stop a running service? - * Would this change an existing job? - * If so, let's drop this entry */ - - stops_running_service = - j->type == JOB_STOP && UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(j->unit)); - - changes_existing_job = - j->unit->job && - job_type_is_conflicting(j->type, j->unit->job->type); - - if (!stops_running_service && !changes_existing_job) - continue; - - if (stops_running_service) - log_unit_debug(j->unit, - "%s/%s would stop a running service.", - j->unit->id, job_type_to_string(j->type)); - - if (changes_existing_job) - log_unit_debug(j->unit, - "%s/%s would change existing job.", - j->unit->id, job_type_to_string(j->type)); - - /* Ok, let's get rid of this */ - log_unit_debug(j->unit, - "Deleting %s/%s to minimize impact.", - j->unit->id, job_type_to_string(j->type)); - - transaction_delete_job(tr, j, true); - goto rescan; - } - } -} - -static int transaction_apply(Transaction *tr, Manager *m, JobMode mode) { - Iterator i; - Job *j; - int r; - - /* Moves the transaction jobs to the set of active jobs */ - - if (mode == JOB_ISOLATE || mode == JOB_FLUSH) { - - /* When isolating first kill all installed jobs which - * aren't part of the new transaction */ - HASHMAP_FOREACH(j, m->jobs, i) { - assert(j->installed); - - if (hashmap_get(tr->jobs, j->unit)) - continue; - - /* Not invalidating recursively. Avoids triggering - * OnFailure= actions of dependent jobs. Also avoids - * invalidating our iterator. */ - job_finish_and_invalidate(j, JOB_CANCELED, false, false); - } - } - - HASHMAP_FOREACH(j, tr->jobs, i) { - /* Assume merged */ - assert(!j->transaction_prev); - assert(!j->transaction_next); - - r = hashmap_put(m->jobs, UINT32_TO_PTR(j->id), j); - if (r < 0) - goto rollback; - } - - while ((j = hashmap_steal_first(tr->jobs))) { - Job *installed_job; - - /* Clean the job dependencies */ - transaction_unlink_job(tr, j, false); - - installed_job = job_install(j); - if (installed_job != j) { - /* j has been merged into a previously installed job */ - if (tr->anchor_job == j) - tr->anchor_job = installed_job; - hashmap_remove(m->jobs, UINT32_TO_PTR(j->id)); - job_free(j); - j = installed_job; - } - - job_add_to_run_queue(j); - job_add_to_dbus_queue(j); - job_start_timer(j); - job_shutdown_magic(j); - } - - return 0; - -rollback: - - HASHMAP_FOREACH(j, tr->jobs, i) - hashmap_remove(m->jobs, UINT32_TO_PTR(j->id)); - - return r; -} - -int transaction_activate(Transaction *tr, Manager *m, JobMode mode, sd_bus_error *e) { - Iterator i; - Job *j; - int r; - unsigned generation = 1; - - assert(tr); - - /* This applies the changes recorded in tr->jobs to - * the actual list of jobs, if possible. */ - - /* Reset the generation counter of all installed jobs. The detection of cycles - * looks at installed jobs. If they had a non-zero generation from some previous - * walk of the graph, the algorithm would break. */ - HASHMAP_FOREACH(j, m->jobs, i) - j->generation = 0; - - /* First step: figure out which jobs matter */ - transaction_find_jobs_that_matter_to_anchor(tr->anchor_job, generation++); - - /* Second step: Try not to stop any running services if - * we don't have to. Don't try to reverse running - * jobs if we don't have to. */ - if (mode == JOB_FAIL) - transaction_minimize_impact(tr); - - /* Third step: Drop redundant jobs */ - transaction_drop_redundant(tr); - - for (;;) { - /* Fourth step: Let's remove unneeded jobs that might - * be lurking. */ - if (mode != JOB_ISOLATE) - transaction_collect_garbage(tr); - - /* Fifth step: verify order makes sense and correct - * cycles if necessary and possible */ - r = transaction_verify_order(tr, &generation, e); - if (r >= 0) - break; - - if (r != -EAGAIN) { - log_warning("Requested transaction contains an unfixable cyclic ordering dependency: %s", bus_error_message(e, r)); - return r; - } - - /* Let's see if the resulting transaction ordering - * graph is still cyclic... */ - } - - for (;;) { - /* Sixth step: let's drop unmergeable entries if - * necessary and possible, merge entries we can - * merge */ - r = transaction_merge_jobs(tr, e); - if (r >= 0) - break; - - if (r != -EAGAIN) { - log_warning("Requested transaction contains unmergeable jobs: %s", bus_error_message(e, r)); - return r; - } - - /* Seventh step: an entry got dropped, let's garbage - * collect its dependencies. */ - if (mode != JOB_ISOLATE) - transaction_collect_garbage(tr); - - /* Let's see if the resulting transaction still has - * unmergeable entries ... */ - } - - /* Eights step: Drop redundant jobs again, if the merging now allows us to drop more. */ - transaction_drop_redundant(tr); - - /* Ninth step: check whether we can actually apply this */ - r = transaction_is_destructive(tr, mode, e); - if (r < 0) { - log_notice("Requested transaction contradicts existing jobs: %s", bus_error_message(e, r)); - return r; - } - - /* Tenth step: apply changes */ - r = transaction_apply(tr, m, mode); - if (r < 0) - return log_warning_errno(r, "Failed to apply transaction: %m"); - - assert(hashmap_isempty(tr->jobs)); - - if (!hashmap_isempty(m->jobs)) { - /* Are there any jobs now? Then make sure we have the - * idle pipe around. We don't really care too much - * whether this works or not, as the idle pipe is a - * feature for cosmetics, not actually useful for - * anything beyond that. */ - - if (m->idle_pipe[0] < 0 && m->idle_pipe[1] < 0 && - m->idle_pipe[2] < 0 && m->idle_pipe[3] < 0) { - (void) pipe2(m->idle_pipe, O_NONBLOCK|O_CLOEXEC); - (void) pipe2(m->idle_pipe + 2, O_NONBLOCK|O_CLOEXEC); - } - } - - return 0; -} - -static Job* transaction_add_one_job(Transaction *tr, JobType type, Unit *unit, bool *is_new) { - Job *j, *f; - - assert(tr); - assert(unit); - - /* Looks for an existing prospective job and returns that. If - * it doesn't exist it is created and added to the prospective - * jobs list. */ - - f = hashmap_get(tr->jobs, unit); - - LIST_FOREACH(transaction, j, f) { - assert(j->unit == unit); - - if (j->type == type) { - if (is_new) - *is_new = false; - return j; - } - } - - j = job_new(unit, type); - if (!j) - return NULL; - - j->generation = 0; - j->marker = NULL; - j->matters_to_anchor = false; - j->irreversible = tr->irreversible; - - LIST_PREPEND(transaction, f, j); - - if (hashmap_replace(tr->jobs, unit, f) < 0) { - LIST_REMOVE(transaction, f, j); - job_free(j); - return NULL; - } - - if (is_new) - *is_new = true; - - /* log_debug("Added job %s/%s to transaction.", unit->id, job_type_to_string(type)); */ - - return j; -} - -static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies) { - assert(tr); - assert(j); - - if (j->transaction_prev) - j->transaction_prev->transaction_next = j->transaction_next; - else if (j->transaction_next) - hashmap_replace(tr->jobs, j->unit, j->transaction_next); - else - hashmap_remove_value(tr->jobs, j->unit, j); - - if (j->transaction_next) - j->transaction_next->transaction_prev = j->transaction_prev; - - j->transaction_prev = j->transaction_next = NULL; - - while (j->subject_list) - job_dependency_free(j->subject_list); - - while (j->object_list) { - Job *other = j->object_list->matters ? j->object_list->subject : NULL; - - job_dependency_free(j->object_list); - - if (other && delete_dependencies) { - log_unit_debug(other->unit, - "Deleting job %s/%s as dependency of job %s/%s", - other->unit->id, job_type_to_string(other->type), - j->unit->id, job_type_to_string(j->type)); - transaction_delete_job(tr, other, delete_dependencies); - } - } -} - -int transaction_add_job_and_dependencies( - Transaction *tr, - JobType type, - Unit *unit, - Job *by, - bool matters, - bool conflicts, - bool ignore_requirements, - bool ignore_order, - sd_bus_error *e) { - Job *ret; - Iterator i; - Unit *dep; - int r; - bool is_new; - - assert(tr); - assert(type < _JOB_TYPE_MAX); - assert(type < _JOB_TYPE_MAX_IN_TRANSACTION); - assert(unit); - - /* Before adding jobs for this unit, let's ensure that its state has been loaded - * This matters when jobs are spawned as part of coldplugging itself (see e. g. path_coldplug()). - * This way, we "recursively" coldplug units, ensuring that we do not look at state of - * not-yet-coldplugged units. */ - if (MANAGER_IS_RELOADING(unit->manager)) - unit_coldplug(unit); - - /* log_debug("Pulling in %s/%s from %s/%s", */ - /* unit->id, job_type_to_string(type), */ - /* by ? by->unit->id : "NA", */ - /* by ? job_type_to_string(by->type) : "NA"); */ - - if (!IN_SET(unit->load_state, UNIT_LOADED, UNIT_ERROR, UNIT_NOT_FOUND, UNIT_MASKED)) - return sd_bus_error_setf(e, BUS_ERROR_LOAD_FAILED, "Unit %s is not loaded properly.", unit->id); - - if (type != JOB_STOP) { - r = bus_unit_check_load_state(unit, e); - if (r < 0) - return r; - } - - if (!unit_job_is_applicable(unit, type)) - return sd_bus_error_setf(e, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE, - "Job type %s is not applicable for unit %s.", - job_type_to_string(type), unit->id); - - - /* First add the job. */ - ret = transaction_add_one_job(tr, type, unit, &is_new); - if (!ret) - return -ENOMEM; - - ret->ignore_order = ret->ignore_order || ignore_order; - - /* Then, add a link to the job. */ - if (by) { - if (!job_dependency_new(by, ret, matters, conflicts)) - return -ENOMEM; - } else { - /* If the job has no parent job, it is the anchor job. */ - assert(!tr->anchor_job); - tr->anchor_job = ret; - } - - if (is_new && !ignore_requirements && type != JOB_NOP) { - Set *following; - - /* If we are following some other unit, make sure we - * add all dependencies of everybody following. */ - if (unit_following_set(ret->unit, &following) > 0) { - SET_FOREACH(dep, following, i) { - r = transaction_add_job_and_dependencies(tr, type, dep, ret, false, false, false, ignore_order, e); - if (r < 0) { - log_unit_warning(dep, "Cannot add dependency job for, ignoring: %s", bus_error_message(e, r)); - sd_bus_error_free(e); - } - } - - set_free(following); - } - - /* Finally, recursively add in all dependencies. */ - if (type == JOB_START || type == JOB_RESTART) { - SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES], i) { - r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, false, false, ignore_order, e); - if (r < 0) { - if (r != -EBADR) /* job type not applicable */ - goto fail; - - sd_bus_error_free(e); - } - } - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_BINDS_TO], i) { - r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, false, false, ignore_order, e); - if (r < 0) { - if (r != -EBADR) /* job type not applicable */ - goto fail; - - sd_bus_error_free(e); - } - } - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_WANTS], i) { - r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, false, false, false, ignore_order, e); - if (r < 0) { - /* unit masked, job type not applicable and unit not found are not considered as errors. */ - log_unit_full(dep, - IN_SET(r, -ERFKILL, -EBADR, -ENOENT) ? LOG_DEBUG : LOG_WARNING, - r, "Cannot add dependency job, ignoring: %s", - bus_error_message(e, r)); - sd_bus_error_free(e); - } - } - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE], i) { - r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, true, false, false, ignore_order, e); - if (r < 0) { - if (r != -EBADR) /* job type not applicable */ - goto fail; - - sd_bus_error_free(e); - } - } - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTS], i) { - r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, true, true, false, ignore_order, e); - if (r < 0) { - if (r != -EBADR) /* job type not applicable */ - goto fail; - - sd_bus_error_free(e); - } - } - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTED_BY], i) { - r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, false, false, false, ignore_order, e); - if (r < 0) { - log_unit_warning(dep, - "Cannot add dependency job, ignoring: %s", - bus_error_message(e, r)); - sd_bus_error_free(e); - } - } - - } - - if (type == JOB_STOP || type == JOB_RESTART) { - static const UnitDependency propagate_deps[] = { - UNIT_REQUIRED_BY, - UNIT_REQUISITE_OF, - UNIT_BOUND_BY, - UNIT_CONSISTS_OF, - }; - - JobType ptype; - unsigned j; - - /* We propagate STOP as STOP, but RESTART only - * as TRY_RESTART, in order not to start - * dependencies that are not around. */ - ptype = type == JOB_RESTART ? JOB_TRY_RESTART : type; - - for (j = 0; j < ELEMENTSOF(propagate_deps); j++) - SET_FOREACH(dep, ret->unit->dependencies[propagate_deps[j]], i) { - JobType nt; - - nt = job_type_collapse(ptype, dep); - if (nt == JOB_NOP) - continue; - - r = transaction_add_job_and_dependencies(tr, nt, dep, ret, true, false, false, ignore_order, e); - if (r < 0) { - if (r != -EBADR) /* job type not applicable */ - goto fail; - - sd_bus_error_free(e); - } - } - } - - if (type == JOB_RELOAD) { - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_PROPAGATES_RELOAD_TO], i) { - JobType nt; - - nt = job_type_collapse(JOB_TRY_RELOAD, dep); - if (nt == JOB_NOP) - continue; - - r = transaction_add_job_and_dependencies(tr, nt, dep, ret, false, false, false, ignore_order, e); - if (r < 0) { - log_unit_warning(dep, - "Cannot add dependency reload job, ignoring: %s", - bus_error_message(e, r)); - sd_bus_error_free(e); - } - } - } - - /* JOB_VERIFY_STARTED require no dependency handling */ - } - - return 0; - -fail: - return r; -} - -int transaction_add_isolate_jobs(Transaction *tr, Manager *m) { - Iterator i; - Unit *u; - char *k; - int r; - - assert(tr); - assert(m); - - HASHMAP_FOREACH_KEY(u, k, m->units, i) { - - /* ignore aliases */ - if (u->id != k) - continue; - - if (u->ignore_on_isolate) - continue; - - /* No need to stop inactive jobs */ - if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(u)) && !u->job) - continue; - - /* Is there already something listed for this? */ - if (hashmap_get(tr->jobs, u)) - continue; - - r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, tr->anchor_job, true, false, false, false, NULL); - if (r < 0) - log_unit_warning_errno(u, r, "Cannot add isolate job, ignoring: %m"); - } - - return 0; -} - -Transaction *transaction_new(bool irreversible) { - Transaction *tr; - - tr = new0(Transaction, 1); - if (!tr) - return NULL; - - tr->jobs = hashmap_new(NULL); - if (!tr->jobs) { - free(tr); - return NULL; - } - - tr->irreversible = irreversible; - - return tr; -} - -void transaction_free(Transaction *tr) { - assert(hashmap_isempty(tr->jobs)); - hashmap_free(tr->jobs); - free(tr); -} diff --git a/src/core/transaction.h b/src/core/transaction.h deleted file mode 100644 index 6a3f927b0f..0000000000 --- a/src/core/transaction.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct Transaction Transaction; - -#include "hashmap.h" -#include "job.h" -#include "manager.h" -#include "unit.h" - -struct Transaction { - /* Jobs to be added */ - Hashmap *jobs; /* Unit object => Job object list 1:1 */ - Job *anchor_job; /* the job the user asked for */ - bool irreversible; -}; - -Transaction *transaction_new(bool irreversible); -void transaction_free(Transaction *tr); - -int transaction_add_job_and_dependencies( - Transaction *tr, - JobType type, - Unit *unit, - Job *by, - bool matters, - bool conflicts, - bool ignore_requirements, - bool ignore_order, - sd_bus_error *e); -int transaction_activate(Transaction *tr, Manager *m, JobMode mode, sd_bus_error *e); -int transaction_add_isolate_jobs(Transaction *tr, Manager *m); -void transaction_abort(Transaction *tr); diff --git a/src/core/triggers.systemd.in b/src/core/triggers.systemd.in deleted file mode 100644 index 0d8c303136..0000000000 --- a/src/core/triggers.systemd.in +++ /dev/null @@ -1,66 +0,0 @@ -# -*- Mode: rpm-spec; indent-tabs-mode: nil -*- */ -# -# This file is part of systemd. -# -# Copyright 2015 Zbigniew Jędrzejewski-Szmek -# -# 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 . - -# The contents of this are an example to be copied into systemd.spec. -# -# Minimum rpm version supported: 4.13.0 - -%transfiletriggerin -P 900900 -p -- @systemunitdir@ /etc/systemd/system --- This script will run after any package is initially installed or --- upgraded. We care about the case where a package is initially --- installed, because other cases are covered by the *un scriptlets, --- so sometimes we will reload needlessly. - -pid = posix.fork() -if pid == 0 then - assert(posix.exec("%{_bindir}/systemctl", "daemon-reload")) -elseif pid > 0 then - posix.wait(pid) -end - -%transfiletriggerun -p -- @systemunitdir@ /etc/systemd/system --- On removal, we need to run daemon-reload after any units have been --- removed. %transfiletriggerpostun would be ideal, but it does not get --- executed for some reason. --- On upgrade, we need to run daemon-reload after any new unit files --- have been installed, but before %postun scripts in packages get --- executed. %transfiletriggerun gets the right list of files --- but it is invoked too early (before changes happen). --- %filetriggerpostun happens at the right time, but it fires for --- every package. --- To execute the reload at the right time, we create a state --- file in %transfiletriggerun and execute the daemon-reload in --- the first %filetriggerpostun. - -posix.mkdir("%{_localstatedir}/lib") -posix.mkdir("%{_localstatedir}/lib/rpm-state") -posix.mkdir("%{_localstatedir}/lib/rpm-state/systemd") -io.open("%{_localstatedir}/lib/rpm-state/systemd/needs-reload", "w") - -%filetriggerpostun -P 1000100 -p -- @systemunitdir@ /etc/systemd/system -if posix.access("%{_localstatedir}/lib/rpm-state/systemd/needs-reload") then - posix.unlink("%{_localstatedir}/lib/rpm-state/systemd/needs-reload") - posix.rmdir("%{_localstatedir}/lib/rpm-state/systemd") - pid = posix.fork() - if pid == 0 then - assert(posix.exec("%{_bindir}/systemctl", "daemon-reload")) - elseif pid > 0 then - posix.wait(pid) - end -end diff --git a/src/core/umount.c b/src/core/umount.c deleted file mode 100644 index c21a2be54e..0000000000 --- a/src/core/umount.c +++ /dev/null @@ -1,614 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 ProFUSION embedded systems - - 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 -#include -#include -#include -#include -#include - -#include "libudev.h" - -#include "alloc-util.h" -#include "escape.h" -#include "fd-util.h" -#include "fstab-util.h" -#include "list.h" -#include "mount-setup.h" -#include "path-util.h" -#include "string-util.h" -#include "udev-util.h" -#include "umount.h" -#include "util.h" -#include "virt.h" - -typedef struct MountPoint { - char *path; - char *options; - dev_t devnum; - LIST_FIELDS(struct MountPoint, mount_point); -} MountPoint; - -static void mount_point_free(MountPoint **head, MountPoint *m) { - assert(head); - assert(m); - - LIST_REMOVE(mount_point, *head, m); - - free(m->path); - free(m); -} - -static void mount_points_list_free(MountPoint **head) { - assert(head); - - while (*head) - mount_point_free(head, *head); -} - -static int mount_points_list_get(MountPoint **head) { - _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; - unsigned int i; - int r; - - assert(head); - - proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); - if (!proc_self_mountinfo) - return -errno; - - for (i = 1;; i++) { - _cleanup_free_ char *path = NULL, *options = NULL; - char *p = NULL; - MountPoint *m; - int k; - - k = fscanf(proc_self_mountinfo, - "%*s " /* (1) mount id */ - "%*s " /* (2) parent id */ - "%*s " /* (3) major:minor */ - "%*s " /* (4) root */ - "%ms " /* (5) mount point */ - "%*s" /* (6) mount flags */ - "%*[^-]" /* (7) optional fields */ - "- " /* (8) separator */ - "%*s " /* (9) file system type */ - "%*s" /* (10) mount source */ - "%ms" /* (11) mount options */ - "%*[^\n]", /* some rubbish at the end */ - &path, &options); - if (k != 2) { - if (k == EOF) - break; - - log_warning("Failed to parse /proc/self/mountinfo:%u.", i); - continue; - } - - r = cunescape(path, UNESCAPE_RELAX, &p); - if (r < 0) - return r; - - /* Ignore mount points we can't unmount because they - * are API or because we are keeping them open (like - * /dev/console). Also, ignore all mounts below API - * file systems, since they are likely virtual too, - * and hence not worth spending time on. Also, in - * unprivileged containers we might lack the rights to - * unmount these things, hence don't bother. */ - if (mount_point_is_api(p) || - mount_point_ignore(p) || - path_startswith(p, "/dev") || - path_startswith(p, "/sys") || - path_startswith(p, "/proc")) { - free(p); - continue; - } - - m = new0(MountPoint, 1); - if (!m) { - free(p); - return -ENOMEM; - } - - m->path = p; - m->options = options; - options = NULL; - - LIST_PREPEND(mount_point, *head, m); - } - - return 0; -} - -static int swap_list_get(MountPoint **head) { - _cleanup_fclose_ FILE *proc_swaps = NULL; - unsigned int i; - int r; - - assert(head); - - proc_swaps = fopen("/proc/swaps", "re"); - if (!proc_swaps) - return (errno == ENOENT) ? 0 : -errno; - - (void) fscanf(proc_swaps, "%*s %*s %*s %*s %*s\n"); - - for (i = 2;; i++) { - MountPoint *swap; - char *dev = NULL, *d; - int k; - - k = fscanf(proc_swaps, - "%ms " /* device/file */ - "%*s " /* type of swap */ - "%*s " /* swap size */ - "%*s " /* used */ - "%*s\n", /* priority */ - &dev); - - if (k != 1) { - if (k == EOF) - break; - - log_warning("Failed to parse /proc/swaps:%u.", i); - free(dev); - continue; - } - - if (endswith(dev, " (deleted)")) { - free(dev); - continue; - } - - r = cunescape(dev, UNESCAPE_RELAX, &d); - free(dev); - if (r < 0) - return r; - - swap = new0(MountPoint, 1); - if (!swap) { - free(d); - return -ENOMEM; - } - - swap->path = d; - LIST_PREPEND(mount_point, *head, swap); - } - - return 0; -} - -static int loopback_list_get(MountPoint **head) { - _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; - struct udev_list_entry *item = NULL, *first = NULL; - _cleanup_udev_unref_ struct udev *udev = NULL; - int r; - - assert(head); - - udev = udev_new(); - if (!udev) - return -ENOMEM; - - e = udev_enumerate_new(udev); - if (!e) - return -ENOMEM; - - r = udev_enumerate_add_match_subsystem(e, "block"); - if (r < 0) - return r; - - r = udev_enumerate_add_match_sysname(e, "loop*"); - if (r < 0) - return r; - - r = udev_enumerate_add_match_sysattr(e, "loop/backing_file", NULL); - if (r < 0) - return r; - - r = udev_enumerate_scan_devices(e); - if (r < 0) - return r; - - first = udev_enumerate_get_list_entry(e); - udev_list_entry_foreach(item, first) { - MountPoint *lb; - _cleanup_udev_device_unref_ struct udev_device *d; - char *loop; - const char *dn; - - d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); - if (!d) - return -ENOMEM; - - dn = udev_device_get_devnode(d); - if (!dn) - continue; - - loop = strdup(dn); - if (!loop) - return -ENOMEM; - - lb = new0(MountPoint, 1); - if (!lb) { - free(loop); - return -ENOMEM; - } - - lb->path = loop; - LIST_PREPEND(mount_point, *head, lb); - } - - return 0; -} - -static int dm_list_get(MountPoint **head) { - _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; - struct udev_list_entry *item = NULL, *first = NULL; - _cleanup_udev_unref_ struct udev *udev = NULL; - int r; - - assert(head); - - udev = udev_new(); - if (!udev) - return -ENOMEM; - - e = udev_enumerate_new(udev); - if (!e) - return -ENOMEM; - - r = udev_enumerate_add_match_subsystem(e, "block"); - if (r < 0) - return r; - - r = udev_enumerate_add_match_sysname(e, "dm-*"); - if (r < 0) - return r; - - r = udev_enumerate_scan_devices(e); - if (r < 0) - return r; - - first = udev_enumerate_get_list_entry(e); - udev_list_entry_foreach(item, first) { - MountPoint *m; - _cleanup_udev_device_unref_ struct udev_device *d; - dev_t devnum; - char *node; - const char *dn; - - d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); - if (!d) - return -ENOMEM; - - devnum = udev_device_get_devnum(d); - dn = udev_device_get_devnode(d); - if (major(devnum) == 0 || !dn) - continue; - - node = strdup(dn); - if (!node) - return -ENOMEM; - - m = new(MountPoint, 1); - if (!m) { - free(node); - return -ENOMEM; - } - - m->path = node; - m->devnum = devnum; - LIST_PREPEND(mount_point, *head, m); - } - - return 0; -} - -static int delete_loopback(const char *device) { - _cleanup_close_ int fd = -1; - int r; - - fd = open(device, O_RDONLY|O_CLOEXEC); - if (fd < 0) - return errno == ENOENT ? 0 : -errno; - - r = ioctl(fd, LOOP_CLR_FD, 0); - if (r >= 0) - return 1; - - /* ENXIO: not bound, so no error */ - if (errno == ENXIO) - return 0; - - return -errno; -} - -static int delete_dm(dev_t devnum) { - _cleanup_close_ int fd = -1; - int r; - struct dm_ioctl dm = { - .version = {DM_VERSION_MAJOR, - DM_VERSION_MINOR, - DM_VERSION_PATCHLEVEL}, - .data_size = sizeof(dm), - .dev = devnum, - }; - - assert(major(devnum) != 0); - - fd = open("/dev/mapper/control", O_RDWR|O_CLOEXEC); - if (fd < 0) - return -errno; - - r = ioctl(fd, DM_DEV_REMOVE, &dm); - return r >= 0 ? 0 : -errno; -} - -static int mount_points_list_umount(MountPoint **head, bool *changed, bool log_error) { - MountPoint *m, *n; - int n_failed = 0; - - assert(head); - - LIST_FOREACH_SAFE(mount_point, m, n, *head) { - - /* If we are in a container, don't attempt to - read-only mount anything as that brings no real - benefits, but might confuse the host, as we remount - the superblock here, not the bind mound. */ - if (detect_container() <= 0) { - _cleanup_free_ char *options = NULL; - /* MS_REMOUNT requires that the data parameter - * should be the same from the original mount - * except for the desired changes. Since we want - * to remount read-only, we should filter out - * rw (and ro too, because it confuses the kernel) */ - (void) fstab_filter_options(m->options, "rw\0ro\0", NULL, NULL, &options); - - /* We always try to remount directories - * read-only first, before we go on and umount - * them. - * - * Mount points can be stacked. If a mount - * point is stacked below / or /usr, we - * cannot umount or remount it directly, - * since there is no way to refer to the - * underlying mount. There's nothing we can do - * about it for the general case, but we can - * do something about it if it is aliased - * somehwere else via a bind mount. If we - * explicitly remount the super block of that - * alias read-only we hence should be - * relatively safe regarding keeping the fs we - * can otherwise not see dirty. */ - log_info("Remounting '%s' read-only with options '%s'.", m->path, options); - (void) mount(NULL, m->path, NULL, MS_REMOUNT|MS_RDONLY, options); - } - - /* Skip / and /usr since we cannot unmount that - * anyway, since we are running from it. They have - * already been remounted ro. */ - if (path_equal(m->path, "/") -#ifndef HAVE_SPLIT_USR - || path_equal(m->path, "/usr") -#endif - || path_startswith(m->path, "/run/initramfs") - ) - continue; - - /* Trying to umount. We don't force here since we rely - * on busy NFS and FUSE file systems to return EBUSY - * until we closed everything on top of them. */ - log_info("Unmounting %s.", m->path); - if (umount2(m->path, 0) == 0) { - if (changed) - *changed = true; - - mount_point_free(head, m); - } else if (log_error) { - log_warning_errno(errno, "Could not unmount %s: %m", m->path); - n_failed++; - } - } - - return n_failed; -} - -static int swap_points_list_off(MountPoint **head, bool *changed) { - MountPoint *m, *n; - int n_failed = 0; - - assert(head); - - LIST_FOREACH_SAFE(mount_point, m, n, *head) { - log_info("Deactivating swap %s.", m->path); - if (swapoff(m->path) == 0) { - if (changed) - *changed = true; - - mount_point_free(head, m); - } else { - log_warning_errno(errno, "Could not deactivate swap %s: %m", m->path); - n_failed++; - } - } - - return n_failed; -} - -static int loopback_points_list_detach(MountPoint **head, bool *changed) { - MountPoint *m, *n; - int n_failed = 0, k; - struct stat root_st; - - assert(head); - - k = lstat("/", &root_st); - - LIST_FOREACH_SAFE(mount_point, m, n, *head) { - int r; - struct stat loopback_st; - - if (k >= 0 && - major(root_st.st_dev) != 0 && - lstat(m->path, &loopback_st) >= 0 && - root_st.st_dev == loopback_st.st_rdev) { - n_failed++; - continue; - } - - log_info("Detaching loopback %s.", m->path); - r = delete_loopback(m->path); - if (r >= 0) { - if (r > 0 && changed) - *changed = true; - - mount_point_free(head, m); - } else { - log_warning_errno(errno, "Could not detach loopback %s: %m", m->path); - n_failed++; - } - } - - return n_failed; -} - -static int dm_points_list_detach(MountPoint **head, bool *changed) { - MountPoint *m, *n; - int n_failed = 0, k; - struct stat root_st; - - assert(head); - - k = lstat("/", &root_st); - - LIST_FOREACH_SAFE(mount_point, m, n, *head) { - int r; - - if (k >= 0 && - major(root_st.st_dev) != 0 && - root_st.st_dev == m->devnum) { - n_failed++; - continue; - } - - log_info("Detaching DM %u:%u.", major(m->devnum), minor(m->devnum)); - r = delete_dm(m->devnum); - if (r >= 0) { - if (changed) - *changed = true; - - mount_point_free(head, m); - } else { - log_warning_errno(errno, "Could not detach DM %s: %m", m->path); - n_failed++; - } - } - - return n_failed; -} - -int umount_all(bool *changed) { - int r; - bool umount_changed; - LIST_HEAD(MountPoint, mp_list_head); - - LIST_HEAD_INIT(mp_list_head); - r = mount_points_list_get(&mp_list_head); - if (r < 0) - goto end; - - /* retry umount, until nothing can be umounted anymore */ - do { - umount_changed = false; - - mount_points_list_umount(&mp_list_head, &umount_changed, false); - if (umount_changed) - *changed = true; - - } while (umount_changed); - - /* umount one more time with logging enabled */ - r = mount_points_list_umount(&mp_list_head, &umount_changed, true); - if (r <= 0) - goto end; - - end: - mount_points_list_free(&mp_list_head); - - return r; -} - -int swapoff_all(bool *changed) { - int r; - LIST_HEAD(MountPoint, swap_list_head); - - LIST_HEAD_INIT(swap_list_head); - - r = swap_list_get(&swap_list_head); - if (r < 0) - goto end; - - r = swap_points_list_off(&swap_list_head, changed); - - end: - mount_points_list_free(&swap_list_head); - - return r; -} - -int loopback_detach_all(bool *changed) { - int r; - LIST_HEAD(MountPoint, loopback_list_head); - - LIST_HEAD_INIT(loopback_list_head); - - r = loopback_list_get(&loopback_list_head); - if (r < 0) - goto end; - - r = loopback_points_list_detach(&loopback_list_head, changed); - - end: - mount_points_list_free(&loopback_list_head); - - return r; -} - -int dm_detach_all(bool *changed) { - int r; - LIST_HEAD(MountPoint, dm_list_head); - - LIST_HEAD_INIT(dm_list_head); - - r = dm_list_get(&dm_list_head); - if (r < 0) - goto end; - - r = dm_points_list_detach(&dm_list_head, changed); - - end: - mount_points_list_free(&dm_list_head); - - return r; -} diff --git a/src/core/umount.h b/src/core/umount.h deleted file mode 100644 index 4e2215a47d..0000000000 --- a/src/core/umount.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 ProFUSION embedded systems - - 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 . -***/ - -int umount_all(bool *changed); - -int swapoff_all(bool *changed); - -int loopback_detach_all(bool *changed); - -int dm_detach_all(bool *changed); diff --git a/src/core/unit-printf.c b/src/core/unit-printf.c deleted file mode 100644 index f11df42af3..0000000000 --- a/src/core/unit-printf.c +++ /dev/null @@ -1,304 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 "alloc-util.h" -#include "cgroup-util.h" -#include "formats-util.h" -#include "macro.h" -#include "specifier.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "unit-printf.h" -#include "unit.h" -#include "user-util.h" - -static int specifier_prefix_and_instance(char specifier, void *data, void *userdata, char **ret) { - Unit *u = userdata; - - assert(u); - - return unit_name_to_prefix_and_instance(u->id, ret); -} - -static int specifier_prefix(char specifier, void *data, void *userdata, char **ret) { - Unit *u = userdata; - - assert(u); - - return unit_name_to_prefix(u->id, ret); -} - -static int specifier_prefix_unescaped(char specifier, void *data, void *userdata, char **ret) { - _cleanup_free_ char *p = NULL; - Unit *u = userdata; - int r; - - assert(u); - - r = unit_name_to_prefix(u->id, &p); - if (r < 0) - return r; - - return unit_name_unescape(p, ret); -} - -static int specifier_instance_unescaped(char specifier, void *data, void *userdata, char **ret) { - Unit *u = userdata; - - assert(u); - - return unit_name_unescape(strempty(u->instance), ret); -} - -static int specifier_filename(char specifier, void *data, void *userdata, char **ret) { - Unit *u = userdata; - - assert(u); - - if (u->instance) - return unit_name_path_unescape(u->instance, ret); - else - return unit_name_to_path(u->id, ret); -} - -static int specifier_cgroup(char specifier, void *data, void *userdata, char **ret) { - Unit *u = userdata; - char *n; - - assert(u); - - if (u->cgroup_path) - n = strdup(u->cgroup_path); - else - n = unit_default_cgroup_path(u); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; -} - -static int specifier_cgroup_root(char specifier, void *data, void *userdata, char **ret) { - Unit *u = userdata; - char *n; - - assert(u); - - n = strdup(u->manager->cgroup_root); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; -} - -static int specifier_cgroup_slice(char specifier, void *data, void *userdata, char **ret) { - Unit *u = userdata; - char *n; - - assert(u); - - if (UNIT_ISSET(u->slice)) { - Unit *slice; - - slice = UNIT_DEREF(u->slice); - - if (slice->cgroup_path) - n = strdup(slice->cgroup_path); - else - n = unit_default_cgroup_path(slice); - } else - n = strdup(u->manager->cgroup_root); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; -} - -static int specifier_runtime(char specifier, void *data, void *userdata, char **ret) { - Unit *u = userdata; - const char *e; - char *n = NULL; - - assert(u); - - e = manager_get_runtime_prefix(u->manager); - if (!e) - return -EOPNOTSUPP; - n = strdup(e); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; -} - -static int specifier_user_name(char specifier, void *data, void *userdata, char **ret) { - char *t; - - /* If we are UID 0 (root), this will not result in NSS, - * otherwise it might. This is good, as we want to be able to - * run this in PID 1, where our user ID is 0, but where NSS - * lookups are not allowed. */ - - t = getusername_malloc(); - if (!t) - return -ENOMEM; - - *ret = t; - return 0; -} - -static int specifier_user_id(char specifier, void *data, void *userdata, char **ret) { - - if (asprintf(ret, UID_FMT, getuid()) < 0) - return -ENOMEM; - - return 0; -} - -static int specifier_user_home(char specifier, void *data, void *userdata, char **ret) { - - /* On PID 1 (which runs as root) this will not result in NSS, - * which is good. See above */ - - return get_home_dir(ret); -} - -static int specifier_user_shell(char specifier, void *data, void *userdata, char **ret) { - - /* On PID 1 (which runs as root) this will not result in NSS, - * which is good. See above */ - - return get_shell(ret); -} - -int unit_name_printf(Unit *u, const char* format, char **ret) { - - /* - * This will use the passed string as format string and - * replace the following specifiers: - * - * %n: the full id of the unit (foo@bar.waldo) - * %N: the id of the unit without the suffix (foo@bar) - * %p: the prefix (foo) - * %i: the instance (bar) - */ - - const Specifier table[] = { - { 'n', specifier_string, u->id }, - { 'N', specifier_prefix_and_instance, NULL }, - { 'p', specifier_prefix, NULL }, - { 'i', specifier_string, u->instance }, - { 0, NULL, NULL } - }; - - assert(u); - assert(format); - assert(ret); - - return specifier_printf(format, table, u, ret); -} - -int unit_full_printf(Unit *u, const char *format, char **ret) { - - /* This is similar to unit_name_printf() but also supports - * unescaping. Also, adds a couple of additional codes: - * - * %f the instance if set, otherwise the id - * %c cgroup path of unit - * %r where units in this slice are placed in the cgroup tree - * %R the root of this systemd's instance tree - * %t the runtime directory to place sockets in (e.g. "/run" or $XDG_RUNTIME_DIR) - * %U the UID of the running user - * %u the username of the running user - * %h the homedir of the running user - * %s the shell of the running user - * %m the machine ID of the running system - * %H the host name of the running system - * %b the boot ID of the running system - * %v `uname -r` of the running system - */ - - const Specifier table[] = { - { 'n', specifier_string, u->id }, - { 'N', specifier_prefix_and_instance, NULL }, - { 'p', specifier_prefix, NULL }, - { 'P', specifier_prefix_unescaped, NULL }, - { 'i', specifier_string, u->instance }, - { 'I', specifier_instance_unescaped, NULL }, - - { 'f', specifier_filename, NULL }, - { 'c', specifier_cgroup, NULL }, - { 'r', specifier_cgroup_slice, NULL }, - { 'R', specifier_cgroup_root, NULL }, - { 't', specifier_runtime, NULL }, - - { 'U', specifier_user_id, NULL }, - { 'u', specifier_user_name, NULL }, - { 'h', specifier_user_home, NULL }, - { 's', specifier_user_shell, NULL }, - - { 'm', specifier_machine_id, NULL }, - { 'H', specifier_host_name, NULL }, - { 'b', specifier_boot_id, NULL }, - { 'v', specifier_kernel_release, NULL }, - {} - }; - - assert(u); - assert(format); - assert(ret); - - return specifier_printf(format, table, u, ret); -} - -int unit_full_printf_strv(Unit *u, char **l, char ***ret) { - size_t n; - char **r, **i, **j; - int q; - - /* Applies unit_full_printf to every entry in l */ - - assert(u); - - n = strv_length(l); - r = new(char*, n+1); - if (!r) - return -ENOMEM; - - for (i = l, j = r; *i; i++, j++) { - q = unit_full_printf(u, *i, j); - if (q < 0) - goto fail; - } - - *j = NULL; - *ret = r; - return 0; - -fail: - for (j--; j >= r; j--) - free(*j); - - free(r); - return q; -} diff --git a/src/core/unit-printf.h b/src/core/unit-printf.h deleted file mode 100644 index 4fc8531228..0000000000 --- a/src/core/unit-printf.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "unit.h" - -int unit_name_printf(Unit *u, const char* text, char **ret); -int unit_full_printf(Unit *u, const char *text, char **ret); -int unit_full_printf_strv(Unit *u, char **l, char ***ret); diff --git a/src/core/unit.c b/src/core/unit.c deleted file mode 100644 index 2fff3f2d8b..0000000000 --- a/src/core/unit.c +++ /dev/null @@ -1,3818 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include - -#include "sd-id128.h" -#include "sd-messages.h" - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-util.h" -#include "cgroup-util.h" -#include "dbus-unit.h" -#include "dbus.h" -#include "dropin.h" -#include "escape.h" -#include "execute.h" -#include "fileio-label.h" -#include "formats-util.h" -#include "load-dropin.h" -#include "load-fragment.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "set.h" -#include "signal-util.h" -#include "special.h" -#include "stat-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "strv.h" -#include "umask-util.h" -#include "unit-name.h" -#include "unit.h" -#include "user-util.h" -#include "virt.h" - -const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { - [UNIT_SERVICE] = &service_vtable, - [UNIT_SOCKET] = &socket_vtable, - [UNIT_BUSNAME] = &busname_vtable, - [UNIT_TARGET] = &target_vtable, - [UNIT_DEVICE] = &device_vtable, - [UNIT_MOUNT] = &mount_vtable, - [UNIT_AUTOMOUNT] = &automount_vtable, - [UNIT_SWAP] = &swap_vtable, - [UNIT_TIMER] = &timer_vtable, - [UNIT_PATH] = &path_vtable, - [UNIT_SLICE] = &slice_vtable, - [UNIT_SCOPE] = &scope_vtable -}; - -static void maybe_warn_about_dependency(Unit *u, const char *other, UnitDependency dependency); - -Unit *unit_new(Manager *m, size_t size) { - Unit *u; - - assert(m); - assert(size >= sizeof(Unit)); - - u = malloc0(size); - if (!u) - return NULL; - - u->names = set_new(&string_hash_ops); - if (!u->names) { - free(u); - return NULL; - } - - u->manager = m; - u->type = _UNIT_TYPE_INVALID; - u->default_dependencies = true; - u->unit_file_state = _UNIT_FILE_STATE_INVALID; - u->unit_file_preset = -1; - u->on_failure_job_mode = JOB_REPLACE; - u->cgroup_inotify_wd = -1; - u->job_timeout = USEC_INFINITY; - - RATELIMIT_INIT(u->start_limit, m->default_start_limit_interval, m->default_start_limit_burst); - RATELIMIT_INIT(u->auto_stop_ratelimit, 10 * USEC_PER_SEC, 16); - - return u; -} - -bool unit_has_name(Unit *u, const char *name) { - assert(u); - assert(name); - - return !!set_get(u->names, (char*) name); -} - -static void unit_init(Unit *u) { - CGroupContext *cc; - ExecContext *ec; - KillContext *kc; - - assert(u); - assert(u->manager); - assert(u->type >= 0); - - cc = unit_get_cgroup_context(u); - if (cc) { - cgroup_context_init(cc); - - /* Copy in the manager defaults into the cgroup - * context, _before_ the rest of the settings have - * been initialized */ - - cc->cpu_accounting = u->manager->default_cpu_accounting; - cc->io_accounting = u->manager->default_io_accounting; - cc->blockio_accounting = u->manager->default_blockio_accounting; - cc->memory_accounting = u->manager->default_memory_accounting; - cc->tasks_accounting = u->manager->default_tasks_accounting; - - if (u->type != UNIT_SLICE) - cc->tasks_max = u->manager->default_tasks_max; - } - - ec = unit_get_exec_context(u); - if (ec) - exec_context_init(ec); - - kc = unit_get_kill_context(u); - if (kc) - kill_context_init(kc); - - if (UNIT_VTABLE(u)->init) - UNIT_VTABLE(u)->init(u); -} - -int unit_add_name(Unit *u, const char *text) { - _cleanup_free_ char *s = NULL, *i = NULL; - UnitType t; - int r; - - assert(u); - assert(text); - - if (unit_name_is_valid(text, UNIT_NAME_TEMPLATE)) { - - if (!u->instance) - return -EINVAL; - - r = unit_name_replace_instance(text, u->instance, &s); - if (r < 0) - return r; - } else { - s = strdup(text); - if (!s) - return -ENOMEM; - } - - if (set_contains(u->names, s)) - return 0; - if (hashmap_contains(u->manager->units, s)) - return -EEXIST; - - if (!unit_name_is_valid(s, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) - return -EINVAL; - - t = unit_name_to_type(s); - if (t < 0) - return -EINVAL; - - if (u->type != _UNIT_TYPE_INVALID && t != u->type) - return -EINVAL; - - r = unit_name_to_instance(s, &i); - if (r < 0) - return r; - - if (i && !unit_type_may_template(t)) - return -EINVAL; - - /* Ensure that this unit is either instanced or not instanced, - * but not both. Note that we do allow names with different - * instance names however! */ - if (u->type != _UNIT_TYPE_INVALID && !u->instance != !i) - return -EINVAL; - - if (!unit_type_may_alias(t) && !set_isempty(u->names)) - return -EEXIST; - - if (hashmap_size(u->manager->units) >= MANAGER_MAX_NAMES) - return -E2BIG; - - r = set_put(u->names, s); - if (r < 0) - return r; - assert(r > 0); - - r = hashmap_put(u->manager->units, s, u); - if (r < 0) { - (void) set_remove(u->names, s); - return r; - } - - if (u->type == _UNIT_TYPE_INVALID) { - u->type = t; - u->id = s; - u->instance = i; - - LIST_PREPEND(units_by_type, u->manager->units_by_type[t], u); - - unit_init(u); - - i = NULL; - } - - s = NULL; - - unit_add_to_dbus_queue(u); - return 0; -} - -int unit_choose_id(Unit *u, const char *name) { - _cleanup_free_ char *t = NULL; - char *s, *i; - int r; - - assert(u); - assert(name); - - if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { - - if (!u->instance) - return -EINVAL; - - r = unit_name_replace_instance(name, u->instance, &t); - if (r < 0) - return r; - - name = t; - } - - /* Selects one of the names of this unit as the id */ - s = set_get(u->names, (char*) name); - if (!s) - return -ENOENT; - - /* Determine the new instance from the new id */ - r = unit_name_to_instance(s, &i); - if (r < 0) - return r; - - u->id = s; - - free(u->instance); - u->instance = i; - - unit_add_to_dbus_queue(u); - - return 0; -} - -int unit_set_description(Unit *u, const char *description) { - char *s; - - assert(u); - - if (isempty(description)) - s = NULL; - else { - s = strdup(description); - if (!s) - return -ENOMEM; - } - - free(u->description); - u->description = s; - - unit_add_to_dbus_queue(u); - return 0; -} - -bool unit_check_gc(Unit *u) { - UnitActiveState state; - assert(u); - - if (u->job) - return true; - - if (u->nop_job) - return true; - - state = unit_active_state(u); - - /* If the unit is inactive and failed and no job is queued for - * it, then release its runtime resources */ - if (UNIT_IS_INACTIVE_OR_FAILED(state) && - UNIT_VTABLE(u)->release_resources) - UNIT_VTABLE(u)->release_resources(u); - - /* But we keep the unit object around for longer when it is - * referenced or configured to not be gc'ed */ - if (state != UNIT_INACTIVE) - return true; - - if (u->no_gc) - return true; - - if (u->refs) - return true; - - if (UNIT_VTABLE(u)->check_gc) - if (UNIT_VTABLE(u)->check_gc(u)) - return true; - - return false; -} - -void unit_add_to_load_queue(Unit *u) { - assert(u); - assert(u->type != _UNIT_TYPE_INVALID); - - if (u->load_state != UNIT_STUB || u->in_load_queue) - return; - - LIST_PREPEND(load_queue, u->manager->load_queue, u); - u->in_load_queue = true; -} - -void unit_add_to_cleanup_queue(Unit *u) { - assert(u); - - if (u->in_cleanup_queue) - return; - - LIST_PREPEND(cleanup_queue, u->manager->cleanup_queue, u); - u->in_cleanup_queue = true; -} - -void unit_add_to_gc_queue(Unit *u) { - assert(u); - - if (u->in_gc_queue || u->in_cleanup_queue) - return; - - if (unit_check_gc(u)) - return; - - LIST_PREPEND(gc_queue, u->manager->gc_queue, u); - u->in_gc_queue = true; - - u->manager->n_in_gc_queue++; -} - -void unit_add_to_dbus_queue(Unit *u) { - assert(u); - assert(u->type != _UNIT_TYPE_INVALID); - - if (u->load_state == UNIT_STUB || u->in_dbus_queue) - return; - - /* Shortcut things if nobody cares */ - if (sd_bus_track_count(u->manager->subscribed) <= 0 && - set_isempty(u->manager->private_buses)) { - u->sent_dbus_new_signal = true; - return; - } - - LIST_PREPEND(dbus_queue, u->manager->dbus_unit_queue, u); - u->in_dbus_queue = true; -} - -static void bidi_set_free(Unit *u, Set *s) { - Iterator i; - Unit *other; - - assert(u); - - /* Frees the set and makes sure we are dropped from the - * inverse pointers */ - - SET_FOREACH(other, s, i) { - UnitDependency d; - - for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) - set_remove(other->dependencies[d], u); - - unit_add_to_gc_queue(other); - } - - set_free(s); -} - -static void unit_remove_transient(Unit *u) { - char **i; - - assert(u); - - if (!u->transient) - return; - - if (u->fragment_path) - (void) unlink(u->fragment_path); - - STRV_FOREACH(i, u->dropin_paths) { - _cleanup_free_ char *p = NULL, *pp = NULL; - - p = dirname_malloc(*i); /* Get the drop-in directory from the drop-in file */ - if (!p) - continue; - - pp = dirname_malloc(p); /* Get the config directory from the drop-in directory */ - if (!pp) - continue; - - /* Only drop transient drop-ins */ - if (!path_equal(u->manager->lookup_paths.transient, pp)) - continue; - - (void) unlink(*i); - (void) rmdir(p); - } -} - -static void unit_free_requires_mounts_for(Unit *u) { - char **j; - - STRV_FOREACH(j, u->requires_mounts_for) { - char s[strlen(*j) + 1]; - - PATH_FOREACH_PREFIX_MORE(s, *j) { - char *y; - Set *x; - - x = hashmap_get2(u->manager->units_requiring_mounts_for, s, (void**) &y); - if (!x) - continue; - - set_remove(x, u); - - if (set_isempty(x)) { - hashmap_remove(u->manager->units_requiring_mounts_for, y); - free(y); - set_free(x); - } - } - } - - u->requires_mounts_for = strv_free(u->requires_mounts_for); -} - -static void unit_done(Unit *u) { - ExecContext *ec; - CGroupContext *cc; - - assert(u); - - if (u->type < 0) - return; - - if (UNIT_VTABLE(u)->done) - UNIT_VTABLE(u)->done(u); - - ec = unit_get_exec_context(u); - if (ec) - exec_context_done(ec); - - cc = unit_get_cgroup_context(u); - if (cc) - cgroup_context_done(cc); -} - -void unit_free(Unit *u) { - UnitDependency d; - Iterator i; - char *t; - - assert(u); - - if (u->transient_file) - fclose(u->transient_file); - - if (!MANAGER_IS_RELOADING(u->manager)) - unit_remove_transient(u); - - bus_unit_send_removed_signal(u); - - unit_done(u); - - sd_bus_slot_unref(u->match_bus_slot); - - unit_free_requires_mounts_for(u); - - SET_FOREACH(t, u->names, i) - hashmap_remove_value(u->manager->units, t, u); - - if (u->job) { - Job *j = u->job; - job_uninstall(j); - job_free(j); - } - - if (u->nop_job) { - Job *j = u->nop_job; - job_uninstall(j); - job_free(j); - } - - for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) - bidi_set_free(u, u->dependencies[d]); - - if (u->type != _UNIT_TYPE_INVALID) - LIST_REMOVE(units_by_type, u->manager->units_by_type[u->type], u); - - if (u->in_load_queue) - LIST_REMOVE(load_queue, u->manager->load_queue, u); - - if (u->in_dbus_queue) - LIST_REMOVE(dbus_queue, u->manager->dbus_unit_queue, u); - - if (u->in_cleanup_queue) - LIST_REMOVE(cleanup_queue, u->manager->cleanup_queue, u); - - if (u->in_gc_queue) { - LIST_REMOVE(gc_queue, u->manager->gc_queue, u); - u->manager->n_in_gc_queue--; - } - - if (u->in_cgroup_queue) - LIST_REMOVE(cgroup_queue, u->manager->cgroup_queue, u); - - unit_release_cgroup(u); - - (void) manager_update_failed_units(u->manager, u, false); - set_remove(u->manager->startup_units, u); - - free(u->description); - strv_free(u->documentation); - free(u->fragment_path); - free(u->source_path); - strv_free(u->dropin_paths); - free(u->instance); - - free(u->job_timeout_reboot_arg); - - set_free_free(u->names); - - unit_unwatch_all_pids(u); - - condition_free_list(u->conditions); - condition_free_list(u->asserts); - - free(u->reboot_arg); - - unit_ref_unset(&u->slice); - - while (u->refs) - unit_ref_unset(u->refs); - - free(u); -} - -UnitActiveState unit_active_state(Unit *u) { - assert(u); - - if (u->load_state == UNIT_MERGED) - return unit_active_state(unit_follow_merge(u)); - - /* After a reload it might happen that a unit is not correctly - * loaded but still has a process around. That's why we won't - * shortcut failed loading to UNIT_INACTIVE_FAILED. */ - - return UNIT_VTABLE(u)->active_state(u); -} - -const char* unit_sub_state_to_string(Unit *u) { - assert(u); - - return UNIT_VTABLE(u)->sub_state_to_string(u); -} - -static int complete_move(Set **s, Set **other) { - int r; - - assert(s); - assert(other); - - if (!*other) - return 0; - - if (*s) { - r = set_move(*s, *other); - if (r < 0) - return r; - } else { - *s = *other; - *other = NULL; - } - - return 0; -} - -static int merge_names(Unit *u, Unit *other) { - char *t; - Iterator i; - int r; - - assert(u); - assert(other); - - r = complete_move(&u->names, &other->names); - if (r < 0) - return r; - - set_free_free(other->names); - other->names = NULL; - other->id = NULL; - - SET_FOREACH(t, u->names, i) - assert_se(hashmap_replace(u->manager->units, t, u) == 0); - - return 0; -} - -static int reserve_dependencies(Unit *u, Unit *other, UnitDependency d) { - unsigned n_reserve; - - assert(u); - assert(other); - assert(d < _UNIT_DEPENDENCY_MAX); - - /* - * If u does not have this dependency set allocated, there is no need - * to reserve anything. In that case other's set will be transferred - * as a whole to u by complete_move(). - */ - if (!u->dependencies[d]) - return 0; - - /* merge_dependencies() will skip a u-on-u dependency */ - n_reserve = set_size(other->dependencies[d]) - !!set_get(other->dependencies[d], u); - - return set_reserve(u->dependencies[d], n_reserve); -} - -static void merge_dependencies(Unit *u, Unit *other, const char *other_id, UnitDependency d) { - Iterator i; - Unit *back; - int r; - - assert(u); - assert(other); - assert(d < _UNIT_DEPENDENCY_MAX); - - /* Fix backwards pointers */ - SET_FOREACH(back, other->dependencies[d], i) { - UnitDependency k; - - for (k = 0; k < _UNIT_DEPENDENCY_MAX; k++) { - /* Do not add dependencies between u and itself */ - if (back == u) { - if (set_remove(back->dependencies[k], other)) - maybe_warn_about_dependency(u, other_id, k); - } else { - r = set_remove_and_put(back->dependencies[k], other, u); - if (r == -EEXIST) - set_remove(back->dependencies[k], other); - else - assert(r >= 0 || r == -ENOENT); - } - } - } - - /* Also do not move dependencies on u to itself */ - back = set_remove(other->dependencies[d], u); - if (back) - maybe_warn_about_dependency(u, other_id, d); - - /* The move cannot fail. The caller must have performed a reservation. */ - assert_se(complete_move(&u->dependencies[d], &other->dependencies[d]) == 0); - - other->dependencies[d] = set_free(other->dependencies[d]); -} - -int unit_merge(Unit *u, Unit *other) { - UnitDependency d; - const char *other_id = NULL; - int r; - - assert(u); - assert(other); - assert(u->manager == other->manager); - assert(u->type != _UNIT_TYPE_INVALID); - - other = unit_follow_merge(other); - - if (other == u) - return 0; - - if (u->type != other->type) - return -EINVAL; - - if (!u->instance != !other->instance) - return -EINVAL; - - if (!unit_type_may_alias(u->type)) /* Merging only applies to unit names that support aliases */ - return -EEXIST; - - if (other->load_state != UNIT_STUB && - other->load_state != UNIT_NOT_FOUND) - return -EEXIST; - - if (other->job) - return -EEXIST; - - if (other->nop_job) - return -EEXIST; - - if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) - return -EEXIST; - - if (other->id) - other_id = strdupa(other->id); - - /* Make reservations to ensure merge_dependencies() won't fail */ - for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) { - r = reserve_dependencies(u, other, d); - /* - * We don't rollback reservations if we fail. We don't have - * a way to undo reservations. A reservation is not a leak. - */ - if (r < 0) - return r; - } - - /* Merge names */ - r = merge_names(u, other); - if (r < 0) - return r; - - /* Redirect all references */ - while (other->refs) - unit_ref_set(other->refs, u); - - /* Merge dependencies */ - for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) - merge_dependencies(u, other, other_id, d); - - other->load_state = UNIT_MERGED; - other->merged_into = u; - - /* If there is still some data attached to the other node, we - * don't need it anymore, and can free it. */ - if (other->load_state != UNIT_STUB) - if (UNIT_VTABLE(other)->done) - UNIT_VTABLE(other)->done(other); - - unit_add_to_dbus_queue(u); - unit_add_to_cleanup_queue(other); - - return 0; -} - -int unit_merge_by_name(Unit *u, const char *name) { - _cleanup_free_ char *s = NULL; - Unit *other; - int r; - - assert(u); - assert(name); - - if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { - if (!u->instance) - return -EINVAL; - - r = unit_name_replace_instance(name, u->instance, &s); - if (r < 0) - return r; - - name = s; - } - - other = manager_get_unit(u->manager, name); - if (other) - return unit_merge(u, other); - - return unit_add_name(u, name); -} - -Unit* unit_follow_merge(Unit *u) { - assert(u); - - while (u->load_state == UNIT_MERGED) - assert_se(u = u->merged_into); - - return u; -} - -int unit_add_exec_dependencies(Unit *u, ExecContext *c) { - int r; - - assert(u); - assert(c); - - if (c->working_directory) { - r = unit_require_mounts_for(u, c->working_directory); - if (r < 0) - return r; - } - - if (c->root_directory) { - r = unit_require_mounts_for(u, c->root_directory); - if (r < 0) - return r; - } - - if (!MANAGER_IS_SYSTEM(u->manager)) - return 0; - - if (c->private_tmp) { - r = unit_require_mounts_for(u, "/tmp"); - if (r < 0) - return r; - - r = unit_require_mounts_for(u, "/var/tmp"); - if (r < 0) - return r; - } - - if (c->std_output != EXEC_OUTPUT_KMSG && - c->std_output != EXEC_OUTPUT_SYSLOG && - c->std_output != EXEC_OUTPUT_JOURNAL && - c->std_output != EXEC_OUTPUT_KMSG_AND_CONSOLE && - c->std_output != EXEC_OUTPUT_SYSLOG_AND_CONSOLE && - c->std_output != EXEC_OUTPUT_JOURNAL_AND_CONSOLE && - c->std_error != EXEC_OUTPUT_KMSG && - c->std_error != EXEC_OUTPUT_SYSLOG && - c->std_error != EXEC_OUTPUT_JOURNAL && - c->std_error != EXEC_OUTPUT_KMSG_AND_CONSOLE && - c->std_error != EXEC_OUTPUT_JOURNAL_AND_CONSOLE && - c->std_error != EXEC_OUTPUT_SYSLOG_AND_CONSOLE) - return 0; - - /* If syslog or kernel logging is requested, make sure our own - * logging daemon is run first. */ - - r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, NULL, true); - if (r < 0) - return r; - - return 0; -} - -const char *unit_description(Unit *u) { - assert(u); - - if (u->description) - return u->description; - - return strna(u->id); -} - -void unit_dump(Unit *u, FILE *f, const char *prefix) { - char *t, **j; - UnitDependency d; - Iterator i; - const char *prefix2; - char - timestamp0[FORMAT_TIMESTAMP_MAX], - timestamp1[FORMAT_TIMESTAMP_MAX], - timestamp2[FORMAT_TIMESTAMP_MAX], - timestamp3[FORMAT_TIMESTAMP_MAX], - timestamp4[FORMAT_TIMESTAMP_MAX], - timespan[FORMAT_TIMESPAN_MAX]; - Unit *following; - _cleanup_set_free_ Set *following_set = NULL; - int r; - - assert(u); - assert(u->type >= 0); - - prefix = strempty(prefix); - prefix2 = strjoina(prefix, "\t"); - - fprintf(f, - "%s-> Unit %s:\n" - "%s\tDescription: %s\n" - "%s\tInstance: %s\n" - "%s\tUnit Load State: %s\n" - "%s\tUnit Active State: %s\n" - "%s\tState Change Timestamp: %s\n" - "%s\tInactive Exit Timestamp: %s\n" - "%s\tActive Enter Timestamp: %s\n" - "%s\tActive Exit Timestamp: %s\n" - "%s\tInactive Enter Timestamp: %s\n" - "%s\tGC Check Good: %s\n" - "%s\tNeed Daemon Reload: %s\n" - "%s\tTransient: %s\n" - "%s\tSlice: %s\n" - "%s\tCGroup: %s\n" - "%s\tCGroup realized: %s\n" - "%s\tCGroup mask: 0x%x\n" - "%s\tCGroup members mask: 0x%x\n", - prefix, u->id, - prefix, unit_description(u), - prefix, strna(u->instance), - prefix, unit_load_state_to_string(u->load_state), - prefix, unit_active_state_to_string(unit_active_state(u)), - prefix, strna(format_timestamp(timestamp0, sizeof(timestamp0), u->state_change_timestamp.realtime)), - prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->inactive_exit_timestamp.realtime)), - prefix, strna(format_timestamp(timestamp2, sizeof(timestamp2), u->active_enter_timestamp.realtime)), - prefix, strna(format_timestamp(timestamp3, sizeof(timestamp3), u->active_exit_timestamp.realtime)), - prefix, strna(format_timestamp(timestamp4, sizeof(timestamp4), u->inactive_enter_timestamp.realtime)), - prefix, yes_no(unit_check_gc(u)), - prefix, yes_no(unit_need_daemon_reload(u)), - prefix, yes_no(u->transient), - prefix, strna(unit_slice_name(u)), - prefix, strna(u->cgroup_path), - prefix, yes_no(u->cgroup_realized), - prefix, u->cgroup_realized_mask, - prefix, u->cgroup_members_mask); - - SET_FOREACH(t, u->names, i) - fprintf(f, "%s\tName: %s\n", prefix, t); - - STRV_FOREACH(j, u->documentation) - fprintf(f, "%s\tDocumentation: %s\n", prefix, *j); - - following = unit_following(u); - if (following) - fprintf(f, "%s\tFollowing: %s\n", prefix, following->id); - - r = unit_following_set(u, &following_set); - if (r >= 0) { - Unit *other; - - SET_FOREACH(other, following_set, i) - fprintf(f, "%s\tFollowing Set Member: %s\n", prefix, other->id); - } - - if (u->fragment_path) - fprintf(f, "%s\tFragment Path: %s\n", prefix, u->fragment_path); - - if (u->source_path) - fprintf(f, "%s\tSource Path: %s\n", prefix, u->source_path); - - STRV_FOREACH(j, u->dropin_paths) - fprintf(f, "%s\tDropIn Path: %s\n", prefix, *j); - - if (u->job_timeout != USEC_INFINITY) - fprintf(f, "%s\tJob Timeout: %s\n", prefix, format_timespan(timespan, sizeof(timespan), u->job_timeout, 0)); - - if (u->job_timeout_action != FAILURE_ACTION_NONE) - fprintf(f, "%s\tJob Timeout Action: %s\n", prefix, failure_action_to_string(u->job_timeout_action)); - - if (u->job_timeout_reboot_arg) - fprintf(f, "%s\tJob Timeout Reboot Argument: %s\n", prefix, u->job_timeout_reboot_arg); - - condition_dump_list(u->conditions, f, prefix, condition_type_to_string); - condition_dump_list(u->asserts, f, prefix, assert_type_to_string); - - if (dual_timestamp_is_set(&u->condition_timestamp)) - fprintf(f, - "%s\tCondition Timestamp: %s\n" - "%s\tCondition Result: %s\n", - prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->condition_timestamp.realtime)), - prefix, yes_no(u->condition_result)); - - if (dual_timestamp_is_set(&u->assert_timestamp)) - fprintf(f, - "%s\tAssert Timestamp: %s\n" - "%s\tAssert Result: %s\n", - prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->assert_timestamp.realtime)), - prefix, yes_no(u->assert_result)); - - for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) { - Unit *other; - - SET_FOREACH(other, u->dependencies[d], i) - fprintf(f, "%s\t%s: %s\n", prefix, unit_dependency_to_string(d), other->id); - } - - if (!strv_isempty(u->requires_mounts_for)) { - fprintf(f, - "%s\tRequiresMountsFor:", prefix); - - STRV_FOREACH(j, u->requires_mounts_for) - fprintf(f, " %s", *j); - - fputs("\n", f); - } - - if (u->load_state == UNIT_LOADED) { - - fprintf(f, - "%s\tStopWhenUnneeded: %s\n" - "%s\tRefuseManualStart: %s\n" - "%s\tRefuseManualStop: %s\n" - "%s\tDefaultDependencies: %s\n" - "%s\tOnFailureJobMode: %s\n" - "%s\tIgnoreOnIsolate: %s\n", - prefix, yes_no(u->stop_when_unneeded), - prefix, yes_no(u->refuse_manual_start), - prefix, yes_no(u->refuse_manual_stop), - prefix, yes_no(u->default_dependencies), - prefix, job_mode_to_string(u->on_failure_job_mode), - prefix, yes_no(u->ignore_on_isolate)); - - if (UNIT_VTABLE(u)->dump) - UNIT_VTABLE(u)->dump(u, f, prefix2); - - } else if (u->load_state == UNIT_MERGED) - fprintf(f, - "%s\tMerged into: %s\n", - prefix, u->merged_into->id); - else if (u->load_state == UNIT_ERROR) - fprintf(f, "%s\tLoad Error Code: %s\n", prefix, strerror(-u->load_error)); - - - if (u->job) - job_dump(u->job, f, prefix2); - - if (u->nop_job) - job_dump(u->nop_job, f, prefix2); - -} - -/* Common implementation for multiple backends */ -int unit_load_fragment_and_dropin(Unit *u) { - int r; - - assert(u); - - /* Load a .{service,socket,...} file */ - r = unit_load_fragment(u); - if (r < 0) - return r; - - if (u->load_state == UNIT_STUB) - return -ENOENT; - - /* Load drop-in directory data */ - r = unit_load_dropin(unit_follow_merge(u)); - if (r < 0) - return r; - - return 0; -} - -/* Common implementation for multiple backends */ -int unit_load_fragment_and_dropin_optional(Unit *u) { - int r; - - assert(u); - - /* Same as unit_load_fragment_and_dropin(), but whether - * something can be loaded or not doesn't matter. */ - - /* Load a .service file */ - r = unit_load_fragment(u); - if (r < 0) - return r; - - if (u->load_state == UNIT_STUB) - u->load_state = UNIT_LOADED; - - /* Load drop-in directory data */ - r = unit_load_dropin(unit_follow_merge(u)); - if (r < 0) - return r; - - return 0; -} - -int unit_add_default_target_dependency(Unit *u, Unit *target) { - assert(u); - assert(target); - - if (target->type != UNIT_TARGET) - return 0; - - /* Only add the dependency if both units are loaded, so that - * that loop check below is reliable */ - if (u->load_state != UNIT_LOADED || - target->load_state != UNIT_LOADED) - return 0; - - /* If either side wants no automatic dependencies, then let's - * skip this */ - if (!u->default_dependencies || - !target->default_dependencies) - return 0; - - /* Don't create loops */ - if (set_get(target->dependencies[UNIT_BEFORE], u)) - return 0; - - return unit_add_dependency(target, UNIT_AFTER, u, true); -} - -static int unit_add_target_dependencies(Unit *u) { - - static const UnitDependency deps[] = { - UNIT_REQUIRED_BY, - UNIT_REQUISITE_OF, - UNIT_WANTED_BY, - UNIT_BOUND_BY - }; - - Unit *target; - Iterator i; - unsigned k; - int r = 0; - - assert(u); - - for (k = 0; k < ELEMENTSOF(deps); k++) - SET_FOREACH(target, u->dependencies[deps[k]], i) { - r = unit_add_default_target_dependency(u, target); - if (r < 0) - return r; - } - - return r; -} - -static int unit_add_slice_dependencies(Unit *u) { - assert(u); - - if (!UNIT_HAS_CGROUP_CONTEXT(u)) - return 0; - - if (UNIT_ISSET(u->slice)) - return unit_add_two_dependencies(u, UNIT_AFTER, UNIT_REQUIRES, UNIT_DEREF(u->slice), true); - - if (unit_has_name(u, SPECIAL_ROOT_SLICE)) - return 0; - - return unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_REQUIRES, SPECIAL_ROOT_SLICE, NULL, true); -} - -static int unit_add_mount_dependencies(Unit *u) { - char **i; - int r; - - assert(u); - - STRV_FOREACH(i, u->requires_mounts_for) { - char prefix[strlen(*i) + 1]; - - PATH_FOREACH_PREFIX_MORE(prefix, *i) { - _cleanup_free_ char *p = NULL; - Unit *m; - - r = unit_name_from_path(prefix, ".mount", &p); - if (r < 0) - return r; - - m = manager_get_unit(u->manager, p); - if (!m) { - /* Make sure to load the mount unit if - * it exists. If so the dependencies - * on this unit will be added later - * during the loading of the mount - * unit. */ - (void) manager_load_unit_prepare(u->manager, p, NULL, NULL, &m); - continue; - } - if (m == u) - continue; - - if (m->load_state != UNIT_LOADED) - continue; - - r = unit_add_dependency(u, UNIT_AFTER, m, true); - if (r < 0) - return r; - - if (m->fragment_path) { - r = unit_add_dependency(u, UNIT_REQUIRES, m, true); - if (r < 0) - return r; - } - } - } - - return 0; -} - -static int unit_add_startup_units(Unit *u) { - CGroupContext *c; - int r; - - c = unit_get_cgroup_context(u); - if (!c) - return 0; - - if (c->startup_cpu_shares == CGROUP_CPU_SHARES_INVALID && - c->startup_io_weight == CGROUP_WEIGHT_INVALID && - c->startup_blockio_weight == CGROUP_BLKIO_WEIGHT_INVALID) - return 0; - - r = set_ensure_allocated(&u->manager->startup_units, NULL); - if (r < 0) - return r; - - return set_put(u->manager->startup_units, u); -} - -int unit_load(Unit *u) { - int r; - - assert(u); - - if (u->in_load_queue) { - LIST_REMOVE(load_queue, u->manager->load_queue, u); - u->in_load_queue = false; - } - - if (u->type == _UNIT_TYPE_INVALID) - return -EINVAL; - - if (u->load_state != UNIT_STUB) - return 0; - - if (u->transient_file) { - r = fflush_and_check(u->transient_file); - if (r < 0) - goto fail; - - fclose(u->transient_file); - u->transient_file = NULL; - - u->fragment_mtime = now(CLOCK_REALTIME); - } - - if (UNIT_VTABLE(u)->load) { - r = UNIT_VTABLE(u)->load(u); - if (r < 0) - goto fail; - } - - if (u->load_state == UNIT_STUB) { - r = -ENOENT; - goto fail; - } - - if (u->load_state == UNIT_LOADED) { - - r = unit_add_target_dependencies(u); - if (r < 0) - goto fail; - - r = unit_add_slice_dependencies(u); - if (r < 0) - goto fail; - - r = unit_add_mount_dependencies(u); - if (r < 0) - goto fail; - - r = unit_add_startup_units(u); - if (r < 0) - goto fail; - - if (u->on_failure_job_mode == JOB_ISOLATE && set_size(u->dependencies[UNIT_ON_FAILURE]) > 1) { - log_unit_error(u, "More than one OnFailure= dependencies specified but OnFailureJobMode=isolate set. Refusing."); - r = -EINVAL; - goto fail; - } - - unit_update_cgroup_members_masks(u); - } - - assert((u->load_state != UNIT_MERGED) == !u->merged_into); - - unit_add_to_dbus_queue(unit_follow_merge(u)); - unit_add_to_gc_queue(u); - - return 0; - -fail: - u->load_state = u->load_state == UNIT_STUB ? UNIT_NOT_FOUND : UNIT_ERROR; - u->load_error = r; - unit_add_to_dbus_queue(u); - unit_add_to_gc_queue(u); - - log_unit_debug_errno(u, r, "Failed to load configuration: %m"); - - return r; -} - -static bool unit_condition_test_list(Unit *u, Condition *first, const char *(*to_string)(ConditionType t)) { - Condition *c; - int triggered = -1; - - assert(u); - assert(to_string); - - /* If the condition list is empty, then it is true */ - if (!first) - return true; - - /* Otherwise, if all of the non-trigger conditions apply and - * if any of the trigger conditions apply (unless there are - * none) we return true */ - LIST_FOREACH(conditions, c, first) { - int r; - - r = condition_test(c); - if (r < 0) - log_unit_warning(u, - "Couldn't determine result for %s=%s%s%s, assuming failed: %m", - to_string(c->type), - c->trigger ? "|" : "", - c->negate ? "!" : "", - c->parameter); - else - log_unit_debug(u, - "%s=%s%s%s %s.", - to_string(c->type), - c->trigger ? "|" : "", - c->negate ? "!" : "", - c->parameter, - condition_result_to_string(c->result)); - - if (!c->trigger && r <= 0) - return false; - - if (c->trigger && triggered <= 0) - triggered = r > 0; - } - - return triggered != 0; -} - -static bool unit_condition_test(Unit *u) { - assert(u); - - dual_timestamp_get(&u->condition_timestamp); - u->condition_result = unit_condition_test_list(u, u->conditions, condition_type_to_string); - - return u->condition_result; -} - -static bool unit_assert_test(Unit *u) { - assert(u); - - dual_timestamp_get(&u->assert_timestamp); - u->assert_result = unit_condition_test_list(u, u->asserts, assert_type_to_string); - - return u->assert_result; -} - -void unit_status_printf(Unit *u, const char *status, const char *unit_status_msg_format) { - DISABLE_WARNING_FORMAT_NONLITERAL; - manager_status_printf(u->manager, STATUS_TYPE_NORMAL, status, unit_status_msg_format, unit_description(u)); - REENABLE_WARNING; -} - -_pure_ static const char* unit_get_status_message_format(Unit *u, JobType t) { - const char *format; - const UnitStatusMessageFormats *format_table; - - assert(u); - assert(IN_SET(t, JOB_START, JOB_STOP, JOB_RELOAD)); - - if (t != JOB_RELOAD) { - format_table = &UNIT_VTABLE(u)->status_message_formats; - if (format_table) { - format = format_table->starting_stopping[t == JOB_STOP]; - if (format) - return format; - } - } - - /* Return generic strings */ - if (t == JOB_START) - return "Starting %s."; - else if (t == JOB_STOP) - return "Stopping %s."; - else - return "Reloading %s."; -} - -static void unit_status_print_starting_stopping(Unit *u, JobType t) { - const char *format; - - assert(u); - - /* Reload status messages have traditionally not been printed to console. */ - if (!IN_SET(t, JOB_START, JOB_STOP)) - return; - - format = unit_get_status_message_format(u, t); - - DISABLE_WARNING_FORMAT_NONLITERAL; - unit_status_printf(u, "", format); - REENABLE_WARNING; -} - -static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) { - const char *format; - char buf[LINE_MAX]; - sd_id128_t mid; - - assert(u); - - if (!IN_SET(t, JOB_START, JOB_STOP, JOB_RELOAD)) - return; - - if (log_on_console()) - return; - - /* We log status messages for all units and all operations. */ - - format = unit_get_status_message_format(u, t); - - DISABLE_WARNING_FORMAT_NONLITERAL; - xsprintf(buf, format, unit_description(u)); - REENABLE_WARNING; - - mid = t == JOB_START ? SD_MESSAGE_UNIT_STARTING : - t == JOB_STOP ? SD_MESSAGE_UNIT_STOPPING : - SD_MESSAGE_UNIT_RELOADING; - - /* Note that we deliberately use LOG_MESSAGE() instead of - * LOG_UNIT_MESSAGE() here, since this is supposed to mimic - * closely what is written to screen using the status output, - * which is supposed the highest level, friendliest output - * possible, which means we should avoid the low-level unit - * name. */ - log_struct(LOG_INFO, - LOG_MESSAGE_ID(mid), - LOG_UNIT_ID(u), - LOG_MESSAGE("%s", buf), - NULL); -} - -void unit_status_emit_starting_stopping_reloading(Unit *u, JobType t) { - assert(u); - assert(t >= 0); - assert(t < _JOB_TYPE_MAX); - - unit_status_log_starting_stopping_reloading(u, t); - unit_status_print_starting_stopping(u, t); -} - -int unit_start_limit_test(Unit *u) { - assert(u); - - if (ratelimit_test(&u->start_limit)) { - u->start_limit_hit = false; - return 0; - } - - log_unit_warning(u, "Start request repeated too quickly."); - u->start_limit_hit = true; - - return failure_action(u->manager, u->start_limit_action, u->reboot_arg); -} - -/* Errors: - * -EBADR: This unit type does not support starting. - * -EALREADY: Unit is already started. - * -EAGAIN: An operation is already in progress. Retry later. - * -ECANCELED: Too many requests for now. - * -EPROTO: Assert failed - * -EINVAL: Unit not loaded - * -EOPNOTSUPP: Unit type not supported - */ -int unit_start(Unit *u) { - UnitActiveState state; - Unit *following; - - assert(u); - - /* If this is already started, then this will succeed. Note - * that this will even succeed if this unit is not startable - * by the user. This is relied on to detect when we need to - * wait for units and when waiting is finished. */ - state = unit_active_state(u); - if (UNIT_IS_ACTIVE_OR_RELOADING(state)) - return -EALREADY; - - /* Units that aren't loaded cannot be started */ - if (u->load_state != UNIT_LOADED) - return -EINVAL; - - /* If the conditions failed, don't do anything at all. If we - * already are activating this call might still be useful to - * speed up activation in case there is some hold-off time, - * but we don't want to recheck the condition in that case. */ - if (state != UNIT_ACTIVATING && - !unit_condition_test(u)) { - log_unit_debug(u, "Starting requested but condition failed. Not starting unit."); - return -EALREADY; - } - - /* If the asserts failed, fail the entire job */ - if (state != UNIT_ACTIVATING && - !unit_assert_test(u)) { - log_unit_notice(u, "Starting requested but asserts failed."); - return -EPROTO; - } - - /* Units of types that aren't supported cannot be - * started. Note that we do this test only after the condition - * checks, so that we rather return condition check errors - * (which are usually not considered a true failure) than "not - * supported" errors (which are considered a failure). - */ - if (!unit_supported(u)) - return -EOPNOTSUPP; - - /* Forward to the main object, if we aren't it. */ - following = unit_following(u); - if (following) { - log_unit_debug(u, "Redirecting start request from %s to %s.", u->id, following->id); - return unit_start(following); - } - - /* If it is stopped, but we cannot start it, then fail */ - if (!UNIT_VTABLE(u)->start) - return -EBADR; - - /* We don't suppress calls to ->start() here when we are - * already starting, to allow this request to be used as a - * "hurry up" call, for example when the unit is in some "auto - * restart" state where it waits for a holdoff timer to elapse - * before it will start again. */ - - unit_add_to_dbus_queue(u); - - return UNIT_VTABLE(u)->start(u); -} - -bool unit_can_start(Unit *u) { - assert(u); - - if (u->load_state != UNIT_LOADED) - return false; - - if (!unit_supported(u)) - return false; - - return !!UNIT_VTABLE(u)->start; -} - -bool unit_can_isolate(Unit *u) { - assert(u); - - return unit_can_start(u) && - u->allow_isolate; -} - -/* Errors: - * -EBADR: This unit type does not support stopping. - * -EALREADY: Unit is already stopped. - * -EAGAIN: An operation is already in progress. Retry later. - */ -int unit_stop(Unit *u) { - UnitActiveState state; - Unit *following; - - assert(u); - - state = unit_active_state(u); - if (UNIT_IS_INACTIVE_OR_FAILED(state)) - return -EALREADY; - - following = unit_following(u); - if (following) { - log_unit_debug(u, "Redirecting stop request from %s to %s.", u->id, following->id); - return unit_stop(following); - } - - if (!UNIT_VTABLE(u)->stop) - return -EBADR; - - unit_add_to_dbus_queue(u); - - return UNIT_VTABLE(u)->stop(u); -} - -/* Errors: - * -EBADR: This unit type does not support reloading. - * -ENOEXEC: Unit is not started. - * -EAGAIN: An operation is already in progress. Retry later. - */ -int unit_reload(Unit *u) { - UnitActiveState state; - Unit *following; - - assert(u); - - if (u->load_state != UNIT_LOADED) - return -EINVAL; - - if (!unit_can_reload(u)) - return -EBADR; - - state = unit_active_state(u); - if (state == UNIT_RELOADING) - return -EALREADY; - - if (state != UNIT_ACTIVE) { - log_unit_warning(u, "Unit cannot be reloaded because it is inactive."); - return -ENOEXEC; - } - - following = unit_following(u); - if (following) { - log_unit_debug(u, "Redirecting reload request from %s to %s.", u->id, following->id); - return unit_reload(following); - } - - unit_add_to_dbus_queue(u); - - return UNIT_VTABLE(u)->reload(u); -} - -bool unit_can_reload(Unit *u) { - assert(u); - - if (!UNIT_VTABLE(u)->reload) - return false; - - if (!UNIT_VTABLE(u)->can_reload) - return true; - - return UNIT_VTABLE(u)->can_reload(u); -} - -static void unit_check_unneeded(Unit *u) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - - static const UnitDependency needed_dependencies[] = { - UNIT_REQUIRED_BY, - UNIT_REQUISITE_OF, - UNIT_WANTED_BY, - UNIT_BOUND_BY, - }; - - Unit *other; - Iterator i; - unsigned j; - int r; - - assert(u); - - /* If this service shall be shut down when unneeded then do - * so. */ - - if (!u->stop_when_unneeded) - return; - - if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) - return; - - for (j = 0; j < ELEMENTSOF(needed_dependencies); j++) - SET_FOREACH(other, u->dependencies[needed_dependencies[j]], i) - if (unit_active_or_pending(other)) - return; - - /* If stopping a unit fails continously we might enter a stop - * loop here, hence stop acting on the service being - * unnecessary after a while. */ - if (!ratelimit_test(&u->auto_stop_ratelimit)) { - log_unit_warning(u, "Unit not needed anymore, but not stopping since we tried this too often recently."); - return; - } - - log_unit_info(u, "Unit not needed anymore. Stopping."); - - /* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */ - r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, &error, NULL); - if (r < 0) - log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r)); -} - -static void unit_check_binds_to(Unit *u) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - bool stop = false; - Unit *other; - Iterator i; - int r; - - assert(u); - - if (u->job) - return; - - if (unit_active_state(u) != UNIT_ACTIVE) - return; - - SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i) { - if (other->job) - continue; - - if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) - continue; - - stop = true; - break; - } - - if (!stop) - return; - - /* If stopping a unit fails continously we might enter a stop - * loop here, hence stop acting on the service being - * unnecessary after a while. */ - if (!ratelimit_test(&u->auto_stop_ratelimit)) { - log_unit_warning(u, "Unit is bound to inactive unit %s, but not stopping since we tried this too often recently.", other->id); - return; - } - - assert(other); - log_unit_info(u, "Unit is bound to inactive unit %s. Stopping, too.", other->id); - - /* A unit we need to run is gone. Sniff. Let's stop this. */ - r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, &error, NULL); - if (r < 0) - log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r)); -} - -static void retroactively_start_dependencies(Unit *u) { - Iterator i; - Unit *other; - - assert(u); - assert(UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))); - - SET_FOREACH(other, u->dependencies[UNIT_REQUIRES], i) - if (!set_get(u->dependencies[UNIT_AFTER], other) && - !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, NULL, NULL); - - SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i) - if (!set_get(u->dependencies[UNIT_AFTER], other) && - !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, NULL, NULL); - - SET_FOREACH(other, u->dependencies[UNIT_WANTS], i) - if (!set_get(u->dependencies[UNIT_AFTER], other) && - !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_START, other, JOB_FAIL, NULL, NULL); - - SET_FOREACH(other, u->dependencies[UNIT_CONFLICTS], i) - if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL); - - SET_FOREACH(other, u->dependencies[UNIT_CONFLICTED_BY], i) - if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL); -} - -static void retroactively_stop_dependencies(Unit *u) { - Iterator i; - Unit *other; - - assert(u); - assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))); - - /* Pull down units which are bound to us recursively if enabled */ - SET_FOREACH(other, u->dependencies[UNIT_BOUND_BY], i) - if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL); -} - -static void check_unneeded_dependencies(Unit *u) { - Iterator i; - Unit *other; - - assert(u); - assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))); - - /* Garbage collect services that might not be needed anymore, if enabled */ - SET_FOREACH(other, u->dependencies[UNIT_REQUIRES], i) - if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - unit_check_unneeded(other); - SET_FOREACH(other, u->dependencies[UNIT_WANTS], i) - if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - unit_check_unneeded(other); - SET_FOREACH(other, u->dependencies[UNIT_REQUISITE], i) - if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - unit_check_unneeded(other); - SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i) - if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - unit_check_unneeded(other); -} - -void unit_start_on_failure(Unit *u) { - Unit *other; - Iterator i; - - assert(u); - - if (set_size(u->dependencies[UNIT_ON_FAILURE]) <= 0) - return; - - log_unit_info(u, "Triggering OnFailure= dependencies."); - - SET_FOREACH(other, u->dependencies[UNIT_ON_FAILURE], i) { - int r; - - r = manager_add_job(u->manager, JOB_START, other, u->on_failure_job_mode, NULL, NULL); - if (r < 0) - log_unit_error_errno(u, r, "Failed to enqueue OnFailure= job: %m"); - } -} - -void unit_trigger_notify(Unit *u) { - Unit *other; - Iterator i; - - assert(u); - - SET_FOREACH(other, u->dependencies[UNIT_TRIGGERED_BY], i) - if (UNIT_VTABLE(other)->trigger_notify) - UNIT_VTABLE(other)->trigger_notify(other, u); -} - -void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success) { - Manager *m; - bool unexpected; - - assert(u); - assert(os < _UNIT_ACTIVE_STATE_MAX); - assert(ns < _UNIT_ACTIVE_STATE_MAX); - - /* Note that this is called for all low-level state changes, - * even if they might map to the same high-level - * UnitActiveState! That means that ns == os is an expected - * behavior here. For example: if a mount point is remounted - * this function will be called too! */ - - m = u->manager; - - /* Update timestamps for state changes */ - if (!MANAGER_IS_RELOADING(m)) { - dual_timestamp_get(&u->state_change_timestamp); - - if (UNIT_IS_INACTIVE_OR_FAILED(os) && !UNIT_IS_INACTIVE_OR_FAILED(ns)) - u->inactive_exit_timestamp = u->state_change_timestamp; - else if (!UNIT_IS_INACTIVE_OR_FAILED(os) && UNIT_IS_INACTIVE_OR_FAILED(ns)) - u->inactive_enter_timestamp = u->state_change_timestamp; - - if (!UNIT_IS_ACTIVE_OR_RELOADING(os) && UNIT_IS_ACTIVE_OR_RELOADING(ns)) - u->active_enter_timestamp = u->state_change_timestamp; - else if (UNIT_IS_ACTIVE_OR_RELOADING(os) && !UNIT_IS_ACTIVE_OR_RELOADING(ns)) - u->active_exit_timestamp = u->state_change_timestamp; - } - - /* Keep track of failed units */ - (void) manager_update_failed_units(u->manager, u, ns == UNIT_FAILED); - - /* Make sure the cgroup is always removed when we become inactive */ - if (UNIT_IS_INACTIVE_OR_FAILED(ns)) - unit_prune_cgroup(u); - - /* Note that this doesn't apply to RemainAfterExit services exiting - * successfully, since there's no change of state in that case. Which is - * why it is handled in service_set_state() */ - if (UNIT_IS_INACTIVE_OR_FAILED(os) != UNIT_IS_INACTIVE_OR_FAILED(ns)) { - ExecContext *ec; - - ec = unit_get_exec_context(u); - if (ec && exec_context_may_touch_console(ec)) { - if (UNIT_IS_INACTIVE_OR_FAILED(ns)) { - m->n_on_console--; - - if (m->n_on_console == 0) - /* unset no_console_output flag, since the console is free */ - m->no_console_output = false; - } else - m->n_on_console++; - } - } - - if (u->job) { - unexpected = false; - - if (u->job->state == JOB_WAITING) - - /* So we reached a different state for this - * job. Let's see if we can run it now if it - * failed previously due to EAGAIN. */ - job_add_to_run_queue(u->job); - - /* Let's check whether this state change constitutes a - * finished job, or maybe contradicts a running job and - * hence needs to invalidate jobs. */ - - switch (u->job->type) { - - case JOB_START: - case JOB_VERIFY_ACTIVE: - - if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) - job_finish_and_invalidate(u->job, JOB_DONE, true, false); - else if (u->job->state == JOB_RUNNING && ns != UNIT_ACTIVATING) { - unexpected = true; - - if (UNIT_IS_INACTIVE_OR_FAILED(ns)) - job_finish_and_invalidate(u->job, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true, false); - } - - break; - - case JOB_RELOAD: - case JOB_RELOAD_OR_START: - case JOB_TRY_RELOAD: - - if (u->job->state == JOB_RUNNING) { - if (ns == UNIT_ACTIVE) - job_finish_and_invalidate(u->job, reload_success ? JOB_DONE : JOB_FAILED, true, false); - else if (ns != UNIT_ACTIVATING && ns != UNIT_RELOADING) { - unexpected = true; - - if (UNIT_IS_INACTIVE_OR_FAILED(ns)) - job_finish_and_invalidate(u->job, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true, false); - } - } - - break; - - case JOB_STOP: - case JOB_RESTART: - case JOB_TRY_RESTART: - - if (UNIT_IS_INACTIVE_OR_FAILED(ns)) - job_finish_and_invalidate(u->job, JOB_DONE, true, false); - else if (u->job->state == JOB_RUNNING && ns != UNIT_DEACTIVATING) { - unexpected = true; - job_finish_and_invalidate(u->job, JOB_FAILED, true, false); - } - - break; - - default: - assert_not_reached("Job type unknown"); - } - - } else - unexpected = true; - - if (!MANAGER_IS_RELOADING(m)) { - - /* If this state change happened without being - * requested by a job, then let's retroactively start - * or stop dependencies. We skip that step when - * deserializing, since we don't want to create any - * additional jobs just because something is already - * activated. */ - - if (unexpected) { - if (UNIT_IS_INACTIVE_OR_FAILED(os) && UNIT_IS_ACTIVE_OR_ACTIVATING(ns)) - retroactively_start_dependencies(u); - else if (UNIT_IS_ACTIVE_OR_ACTIVATING(os) && UNIT_IS_INACTIVE_OR_DEACTIVATING(ns)) - retroactively_stop_dependencies(u); - } - - /* stop unneeded units regardless if going down was expected or not */ - if (UNIT_IS_INACTIVE_OR_DEACTIVATING(ns)) - check_unneeded_dependencies(u); - - if (ns != os && ns == UNIT_FAILED) { - log_unit_notice(u, "Unit entered failed state."); - unit_start_on_failure(u); - } - } - - /* Some names are special */ - if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) { - - if (unit_has_name(u, SPECIAL_DBUS_SERVICE)) - /* The bus might have just become available, - * hence try to connect to it, if we aren't - * yet connected. */ - bus_init(m, true); - - if (u->type == UNIT_SERVICE && - !UNIT_IS_ACTIVE_OR_RELOADING(os) && - !MANAGER_IS_RELOADING(m)) { - /* Write audit record if we have just finished starting up */ - manager_send_unit_audit(m, u, AUDIT_SERVICE_START, true); - u->in_audit = true; - } - - if (!UNIT_IS_ACTIVE_OR_RELOADING(os)) - manager_send_unit_plymouth(m, u); - - } else { - - /* We don't care about D-Bus here, since we'll get an - * asynchronous notification for it anyway. */ - - if (u->type == UNIT_SERVICE && - UNIT_IS_INACTIVE_OR_FAILED(ns) && - !UNIT_IS_INACTIVE_OR_FAILED(os) && - !MANAGER_IS_RELOADING(m)) { - - /* Hmm, if there was no start record written - * write it now, so that we always have a nice - * pair */ - if (!u->in_audit) { - manager_send_unit_audit(m, u, AUDIT_SERVICE_START, ns == UNIT_INACTIVE); - - if (ns == UNIT_INACTIVE) - manager_send_unit_audit(m, u, AUDIT_SERVICE_STOP, true); - } else - /* Write audit record if we have just finished shutting down */ - manager_send_unit_audit(m, u, AUDIT_SERVICE_STOP, ns == UNIT_INACTIVE); - - u->in_audit = false; - } - } - - manager_recheck_journal(m); - unit_trigger_notify(u); - - if (!MANAGER_IS_RELOADING(u->manager)) { - /* Maybe we finished startup and are now ready for - * being stopped because unneeded? */ - unit_check_unneeded(u); - - /* Maybe we finished startup, but something we needed - * has vanished? Let's die then. (This happens when - * something BindsTo= to a Type=oneshot unit, as these - * units go directly from starting to inactive, - * without ever entering started.) */ - unit_check_binds_to(u); - } - - unit_add_to_dbus_queue(u); - unit_add_to_gc_queue(u); -} - -int unit_watch_pid(Unit *u, pid_t pid) { - int q, r; - - assert(u); - assert(pid >= 1); - - /* Watch a specific PID. We only support one or two units - * watching each PID for now, not more. */ - - r = set_ensure_allocated(&u->pids, NULL); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&u->manager->watch_pids1, NULL); - if (r < 0) - return r; - - r = hashmap_put(u->manager->watch_pids1, PID_TO_PTR(pid), u); - if (r == -EEXIST) { - r = hashmap_ensure_allocated(&u->manager->watch_pids2, NULL); - if (r < 0) - return r; - - r = hashmap_put(u->manager->watch_pids2, PID_TO_PTR(pid), u); - } - - q = set_put(u->pids, PID_TO_PTR(pid)); - if (q < 0) - return q; - - return r; -} - -void unit_unwatch_pid(Unit *u, pid_t pid) { - assert(u); - assert(pid >= 1); - - (void) hashmap_remove_value(u->manager->watch_pids1, PID_TO_PTR(pid), u); - (void) hashmap_remove_value(u->manager->watch_pids2, PID_TO_PTR(pid), u); - (void) set_remove(u->pids, PID_TO_PTR(pid)); -} - -void unit_unwatch_all_pids(Unit *u) { - assert(u); - - while (!set_isempty(u->pids)) - unit_unwatch_pid(u, PTR_TO_PID(set_first(u->pids))); - - u->pids = set_free(u->pids); -} - -void unit_tidy_watch_pids(Unit *u, pid_t except1, pid_t except2) { - Iterator i; - void *e; - - assert(u); - - /* Cleans dead PIDs from our list */ - - SET_FOREACH(e, u->pids, i) { - pid_t pid = PTR_TO_PID(e); - - if (pid == except1 || pid == except2) - continue; - - if (!pid_is_unwaited(pid)) - unit_unwatch_pid(u, pid); - } -} - -bool unit_job_is_applicable(Unit *u, JobType j) { - assert(u); - assert(j >= 0 && j < _JOB_TYPE_MAX); - - switch (j) { - - case JOB_VERIFY_ACTIVE: - case JOB_START: - case JOB_STOP: - case JOB_NOP: - return true; - - case JOB_RESTART: - case JOB_TRY_RESTART: - return unit_can_start(u); - - case JOB_RELOAD: - case JOB_TRY_RELOAD: - return unit_can_reload(u); - - case JOB_RELOAD_OR_START: - return unit_can_reload(u) && unit_can_start(u); - - default: - assert_not_reached("Invalid job type"); - } -} - -static void maybe_warn_about_dependency(Unit *u, const char *other, UnitDependency dependency) { - assert(u); - - /* Only warn about some unit types */ - if (!IN_SET(dependency, UNIT_CONFLICTS, UNIT_CONFLICTED_BY, UNIT_BEFORE, UNIT_AFTER, UNIT_ON_FAILURE, UNIT_TRIGGERS, UNIT_TRIGGERED_BY)) - return; - - if (streq_ptr(u->id, other)) - log_unit_warning(u, "Dependency %s=%s dropped", unit_dependency_to_string(dependency), u->id); - else - log_unit_warning(u, "Dependency %s=%s dropped, merged into %s", unit_dependency_to_string(dependency), strna(other), u->id); -} - -int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_reference) { - - static const UnitDependency inverse_table[_UNIT_DEPENDENCY_MAX] = { - [UNIT_REQUIRES] = UNIT_REQUIRED_BY, - [UNIT_WANTS] = UNIT_WANTED_BY, - [UNIT_REQUISITE] = UNIT_REQUISITE_OF, - [UNIT_BINDS_TO] = UNIT_BOUND_BY, - [UNIT_PART_OF] = UNIT_CONSISTS_OF, - [UNIT_REQUIRED_BY] = UNIT_REQUIRES, - [UNIT_REQUISITE_OF] = UNIT_REQUISITE, - [UNIT_WANTED_BY] = UNIT_WANTS, - [UNIT_BOUND_BY] = UNIT_BINDS_TO, - [UNIT_CONSISTS_OF] = UNIT_PART_OF, - [UNIT_CONFLICTS] = UNIT_CONFLICTED_BY, - [UNIT_CONFLICTED_BY] = UNIT_CONFLICTS, - [UNIT_BEFORE] = UNIT_AFTER, - [UNIT_AFTER] = UNIT_BEFORE, - [UNIT_ON_FAILURE] = _UNIT_DEPENDENCY_INVALID, - [UNIT_REFERENCES] = UNIT_REFERENCED_BY, - [UNIT_REFERENCED_BY] = UNIT_REFERENCES, - [UNIT_TRIGGERS] = UNIT_TRIGGERED_BY, - [UNIT_TRIGGERED_BY] = UNIT_TRIGGERS, - [UNIT_PROPAGATES_RELOAD_TO] = UNIT_RELOAD_PROPAGATED_FROM, - [UNIT_RELOAD_PROPAGATED_FROM] = UNIT_PROPAGATES_RELOAD_TO, - [UNIT_JOINS_NAMESPACE_OF] = UNIT_JOINS_NAMESPACE_OF, - }; - int r, q = 0, v = 0, w = 0; - Unit *orig_u = u, *orig_other = other; - - assert(u); - assert(d >= 0 && d < _UNIT_DEPENDENCY_MAX); - assert(other); - - u = unit_follow_merge(u); - other = unit_follow_merge(other); - - /* We won't allow dependencies on ourselves. We will not - * consider them an error however. */ - if (u == other) { - maybe_warn_about_dependency(orig_u, orig_other->id, d); - return 0; - } - - r = set_ensure_allocated(&u->dependencies[d], NULL); - if (r < 0) - return r; - - if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID) { - r = set_ensure_allocated(&other->dependencies[inverse_table[d]], NULL); - if (r < 0) - return r; - } - - if (add_reference) { - r = set_ensure_allocated(&u->dependencies[UNIT_REFERENCES], NULL); - if (r < 0) - return r; - - r = set_ensure_allocated(&other->dependencies[UNIT_REFERENCED_BY], NULL); - if (r < 0) - return r; - } - - q = set_put(u->dependencies[d], other); - if (q < 0) - return q; - - if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID && inverse_table[d] != d) { - v = set_put(other->dependencies[inverse_table[d]], u); - if (v < 0) { - r = v; - goto fail; - } - } - - if (add_reference) { - w = set_put(u->dependencies[UNIT_REFERENCES], other); - if (w < 0) { - r = w; - goto fail; - } - - r = set_put(other->dependencies[UNIT_REFERENCED_BY], u); - if (r < 0) - goto fail; - } - - unit_add_to_dbus_queue(u); - return 0; - -fail: - if (q > 0) - set_remove(u->dependencies[d], other); - - if (v > 0) - set_remove(other->dependencies[inverse_table[d]], u); - - if (w > 0) - set_remove(u->dependencies[UNIT_REFERENCES], other); - - return r; -} - -int unit_add_two_dependencies(Unit *u, UnitDependency d, UnitDependency e, Unit *other, bool add_reference) { - int r; - - assert(u); - - r = unit_add_dependency(u, d, other, add_reference); - if (r < 0) - return r; - - return unit_add_dependency(u, e, other, add_reference); -} - -static int resolve_template(Unit *u, const char *name, const char*path, char **buf, const char **ret) { - int r; - - assert(u); - assert(name || path); - assert(buf); - assert(ret); - - if (!name) - name = basename(path); - - if (!unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { - *buf = NULL; - *ret = name; - return 0; - } - - if (u->instance) - r = unit_name_replace_instance(name, u->instance, buf); - else { - _cleanup_free_ char *i = NULL; - - r = unit_name_to_prefix(u->id, &i); - if (r < 0) - return r; - - r = unit_name_replace_instance(name, i, buf); - } - if (r < 0) - return r; - - *ret = *buf; - return 0; -} - -int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, const char *path, bool add_reference) { - _cleanup_free_ char *buf = NULL; - Unit *other; - int r; - - assert(u); - assert(name || path); - - r = resolve_template(u, name, path, &buf, &name); - if (r < 0) - return r; - - r = manager_load_unit(u->manager, name, path, NULL, &other); - if (r < 0) - return r; - - return unit_add_dependency(u, d, other, add_reference); -} - -int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference) { - _cleanup_free_ char *buf = NULL; - Unit *other; - int r; - - assert(u); - assert(name || path); - - r = resolve_template(u, name, path, &buf, &name); - if (r < 0) - return r; - - r = manager_load_unit(u->manager, name, path, NULL, &other); - if (r < 0) - return r; - - return unit_add_two_dependencies(u, d, e, other, add_reference); -} - -int set_unit_path(const char *p) { - /* This is mostly for debug purposes */ - if (setenv("SYSTEMD_UNIT_PATH", p, 1) < 0) - return -errno; - - return 0; -} - -char *unit_dbus_path(Unit *u) { - assert(u); - - if (!u->id) - return NULL; - - return unit_dbus_path_from_name(u->id); -} - -int unit_set_slice(Unit *u, Unit *slice) { - assert(u); - assert(slice); - - /* Sets the unit slice if it has not been set before. Is extra - * careful, to only allow this for units that actually have a - * cgroup context. Also, we don't allow to set this for slices - * (since the parent slice is derived from the name). Make - * sure the unit we set is actually a slice. */ - - if (!UNIT_HAS_CGROUP_CONTEXT(u)) - return -EOPNOTSUPP; - - if (u->type == UNIT_SLICE) - return -EINVAL; - - if (unit_active_state(u) != UNIT_INACTIVE) - return -EBUSY; - - if (slice->type != UNIT_SLICE) - return -EINVAL; - - if (unit_has_name(u, SPECIAL_INIT_SCOPE) && - !unit_has_name(slice, SPECIAL_ROOT_SLICE)) - return -EPERM; - - if (UNIT_DEREF(u->slice) == slice) - return 0; - - /* Disallow slice changes if @u is already bound to cgroups */ - if (UNIT_ISSET(u->slice) && u->cgroup_realized) - return -EBUSY; - - unit_ref_unset(&u->slice); - unit_ref_set(&u->slice, slice); - return 1; -} - -int unit_set_default_slice(Unit *u) { - _cleanup_free_ char *b = NULL; - const char *slice_name; - Unit *slice; - int r; - - assert(u); - - if (UNIT_ISSET(u->slice)) - return 0; - - if (u->instance) { - _cleanup_free_ char *prefix = NULL, *escaped = NULL; - - /* Implicitly place all instantiated units in their - * own per-template slice */ - - r = unit_name_to_prefix(u->id, &prefix); - if (r < 0) - return r; - - /* The prefix is already escaped, but it might include - * "-" which has a special meaning for slice units, - * hence escape it here extra. */ - escaped = unit_name_escape(prefix); - if (!escaped) - return -ENOMEM; - - if (MANAGER_IS_SYSTEM(u->manager)) - b = strjoin("system-", escaped, ".slice", NULL); - else - b = strappend(escaped, ".slice"); - if (!b) - return -ENOMEM; - - slice_name = b; - } else - slice_name = - MANAGER_IS_SYSTEM(u->manager) && !unit_has_name(u, SPECIAL_INIT_SCOPE) - ? SPECIAL_SYSTEM_SLICE - : SPECIAL_ROOT_SLICE; - - r = manager_load_unit(u->manager, slice_name, NULL, NULL, &slice); - if (r < 0) - return r; - - return unit_set_slice(u, slice); -} - -const char *unit_slice_name(Unit *u) { - assert(u); - - if (!UNIT_ISSET(u->slice)) - return NULL; - - return UNIT_DEREF(u->slice)->id; -} - -int unit_load_related_unit(Unit *u, const char *type, Unit **_found) { - _cleanup_free_ char *t = NULL; - int r; - - assert(u); - assert(type); - assert(_found); - - r = unit_name_change_suffix(u->id, type, &t); - if (r < 0) - return r; - if (unit_has_name(u, t)) - return -EINVAL; - - r = manager_load_unit(u->manager, t, NULL, NULL, _found); - assert(r < 0 || *_found != u); - return r; -} - -static int signal_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) { - const char *name, *old_owner, *new_owner; - Unit *u = userdata; - int r; - - assert(message); - assert(u); - - r = sd_bus_message_read(message, "sss", &name, &old_owner, &new_owner); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - if (UNIT_VTABLE(u)->bus_name_owner_change) - UNIT_VTABLE(u)->bus_name_owner_change(u, name, old_owner, new_owner); - - return 0; -} - -int unit_install_bus_match(Unit *u, sd_bus *bus, const char *name) { - const char *match; - - assert(u); - assert(bus); - assert(name); - - if (u->match_bus_slot) - return -EBUSY; - - match = strjoina("type='signal'," - "sender='org.freedesktop.DBus'," - "path='/org/freedesktop/DBus'," - "interface='org.freedesktop.DBus'," - "member='NameOwnerChanged'," - "arg0='", name, "'"); - - return sd_bus_add_match(bus, &u->match_bus_slot, match, signal_name_owner_changed, u); -} - -int unit_watch_bus_name(Unit *u, const char *name) { - int r; - - assert(u); - assert(name); - - /* Watch a specific name on the bus. We only support one unit - * watching each name for now. */ - - if (u->manager->api_bus) { - /* If the bus is already available, install the match directly. - * Otherwise, just put the name in the list. bus_setup_api() will take care later. */ - r = unit_install_bus_match(u, u->manager->api_bus, name); - if (r < 0) - return log_warning_errno(r, "Failed to subscribe to NameOwnerChanged signal for '%s': %m", name); - } - - r = hashmap_put(u->manager->watch_bus, name, u); - if (r < 0) { - u->match_bus_slot = sd_bus_slot_unref(u->match_bus_slot); - return log_warning_errno(r, "Failed to put bus name to hashmap: %m"); - } - - return 0; -} - -void unit_unwatch_bus_name(Unit *u, const char *name) { - assert(u); - assert(name); - - hashmap_remove_value(u->manager->watch_bus, name, u); - u->match_bus_slot = sd_bus_slot_unref(u->match_bus_slot); -} - -bool unit_can_serialize(Unit *u) { - assert(u); - - return UNIT_VTABLE(u)->serialize && UNIT_VTABLE(u)->deserialize_item; -} - -int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { - int r; - - assert(u); - assert(f); - assert(fds); - - if (unit_can_serialize(u)) { - ExecRuntime *rt; - - r = UNIT_VTABLE(u)->serialize(u, f, fds); - if (r < 0) - return r; - - rt = unit_get_exec_runtime(u); - if (rt) { - r = exec_runtime_serialize(u, rt, f, fds); - if (r < 0) - return r; - } - } - - dual_timestamp_serialize(f, "state-change-timestamp", &u->state_change_timestamp); - - dual_timestamp_serialize(f, "inactive-exit-timestamp", &u->inactive_exit_timestamp); - dual_timestamp_serialize(f, "active-enter-timestamp", &u->active_enter_timestamp); - dual_timestamp_serialize(f, "active-exit-timestamp", &u->active_exit_timestamp); - dual_timestamp_serialize(f, "inactive-enter-timestamp", &u->inactive_enter_timestamp); - - dual_timestamp_serialize(f, "condition-timestamp", &u->condition_timestamp); - dual_timestamp_serialize(f, "assert-timestamp", &u->assert_timestamp); - - if (dual_timestamp_is_set(&u->condition_timestamp)) - unit_serialize_item(u, f, "condition-result", yes_no(u->condition_result)); - - if (dual_timestamp_is_set(&u->assert_timestamp)) - unit_serialize_item(u, f, "assert-result", yes_no(u->assert_result)); - - unit_serialize_item(u, f, "transient", yes_no(u->transient)); - unit_serialize_item_format(u, f, "cpuacct-usage-base", "%" PRIu64, u->cpuacct_usage_base); - - if (u->cgroup_path) - unit_serialize_item(u, f, "cgroup", u->cgroup_path); - unit_serialize_item(u, f, "cgroup-realized", yes_no(u->cgroup_realized)); - - if (serialize_jobs) { - if (u->job) { - fprintf(f, "job\n"); - job_serialize(u->job, f, fds); - } - - if (u->nop_job) { - fprintf(f, "job\n"); - job_serialize(u->nop_job, f, fds); - } - } - - /* End marker */ - fputc('\n', f); - return 0; -} - -int unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value) { - assert(u); - assert(f); - assert(key); - - if (!value) - return 0; - - fputs(key, f); - fputc('=', f); - fputs(value, f); - fputc('\n', f); - - return 1; -} - -int unit_serialize_item_escaped(Unit *u, FILE *f, const char *key, const char *value) { - _cleanup_free_ char *c = NULL; - - assert(u); - assert(f); - assert(key); - - if (!value) - return 0; - - c = cescape(value); - if (!c) - return -ENOMEM; - - fputs(key, f); - fputc('=', f); - fputs(c, f); - fputc('\n', f); - - return 1; -} - -int unit_serialize_item_fd(Unit *u, FILE *f, FDSet *fds, const char *key, int fd) { - int copy; - - assert(u); - assert(f); - assert(key); - - if (fd < 0) - return 0; - - copy = fdset_put_dup(fds, fd); - if (copy < 0) - return copy; - - fprintf(f, "%s=%i\n", key, copy); - return 1; -} - -void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *format, ...) { - va_list ap; - - assert(u); - assert(f); - assert(key); - assert(format); - - fputs(key, f); - fputc('=', f); - - va_start(ap, format); - vfprintf(f, format, ap); - va_end(ap); - - fputc('\n', f); -} - -int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { - ExecRuntime **rt = NULL; - size_t offset; - int r; - - assert(u); - assert(f); - assert(fds); - - offset = UNIT_VTABLE(u)->exec_runtime_offset; - if (offset > 0) - rt = (ExecRuntime**) ((uint8_t*) u + offset); - - for (;;) { - char line[LINE_MAX], *l, *v; - size_t k; - - if (!fgets(line, sizeof(line), f)) { - if (feof(f)) - return 0; - return -errno; - } - - char_array_0(line); - l = strstrip(line); - - /* End marker */ - if (isempty(l)) - break; - - k = strcspn(l, "="); - - if (l[k] == '=') { - l[k] = 0; - v = l+k+1; - } else - v = l+k; - - if (streq(l, "job")) { - if (v[0] == '\0') { - /* new-style serialized job */ - Job *j; - - j = job_new_raw(u); - if (!j) - return log_oom(); - - r = job_deserialize(j, f, fds); - if (r < 0) { - job_free(j); - return r; - } - - r = hashmap_put(u->manager->jobs, UINT32_TO_PTR(j->id), j); - if (r < 0) { - job_free(j); - return r; - } - - r = job_install_deserialized(j); - if (r < 0) { - hashmap_remove(u->manager->jobs, UINT32_TO_PTR(j->id)); - job_free(j); - return r; - } - } else /* legacy for pre-44 */ - log_unit_warning(u, "Update from too old systemd versions are unsupported, cannot deserialize job: %s", v); - continue; - } else if (streq(l, "state-change-timestamp")) { - dual_timestamp_deserialize(v, &u->state_change_timestamp); - continue; - } else if (streq(l, "inactive-exit-timestamp")) { - dual_timestamp_deserialize(v, &u->inactive_exit_timestamp); - continue; - } else if (streq(l, "active-enter-timestamp")) { - dual_timestamp_deserialize(v, &u->active_enter_timestamp); - continue; - } else if (streq(l, "active-exit-timestamp")) { - dual_timestamp_deserialize(v, &u->active_exit_timestamp); - continue; - } else if (streq(l, "inactive-enter-timestamp")) { - dual_timestamp_deserialize(v, &u->inactive_enter_timestamp); - continue; - } else if (streq(l, "condition-timestamp")) { - dual_timestamp_deserialize(v, &u->condition_timestamp); - continue; - } else if (streq(l, "assert-timestamp")) { - dual_timestamp_deserialize(v, &u->assert_timestamp); - continue; - } else if (streq(l, "condition-result")) { - - r = parse_boolean(v); - if (r < 0) - log_unit_debug(u, "Failed to parse condition result value %s, ignoring.", v); - else - u->condition_result = r; - - continue; - - } else if (streq(l, "assert-result")) { - - r = parse_boolean(v); - if (r < 0) - log_unit_debug(u, "Failed to parse assert result value %s, ignoring.", v); - else - u->assert_result = r; - - continue; - - } else if (streq(l, "transient")) { - - r = parse_boolean(v); - if (r < 0) - log_unit_debug(u, "Failed to parse transient bool %s, ignoring.", v); - else - u->transient = r; - - continue; - - } else if (streq(l, "cpuacct-usage-base")) { - - r = safe_atou64(v, &u->cpuacct_usage_base); - if (r < 0) - log_unit_debug(u, "Failed to parse CPU usage %s, ignoring.", v); - - continue; - - } else if (streq(l, "cgroup")) { - - r = unit_set_cgroup_path(u, v); - if (r < 0) - log_unit_debug_errno(u, r, "Failed to set cgroup path %s, ignoring: %m", v); - - (void) unit_watch_cgroup(u); - - continue; - } else if (streq(l, "cgroup-realized")) { - int b; - - b = parse_boolean(v); - if (b < 0) - log_unit_debug(u, "Failed to parse cgroup-realized bool %s, ignoring.", v); - else - u->cgroup_realized = b; - - continue; - } - - if (unit_can_serialize(u)) { - if (rt) { - r = exec_runtime_deserialize_item(u, rt, l, v, fds); - if (r < 0) { - log_unit_warning(u, "Failed to deserialize runtime parameter '%s', ignoring.", l); - continue; - } - - /* Returns positive if key was handled by the call */ - if (r > 0) - continue; - } - - r = UNIT_VTABLE(u)->deserialize_item(u, l, v, fds); - if (r < 0) - log_unit_warning(u, "Failed to deserialize unit parameter '%s', ignoring.", l); - } - } - - /* Versions before 228 did not carry a state change timestamp. In this case, take the current time. This is - * useful, so that timeouts based on this timestamp don't trigger too early, and is in-line with the logic from - * before 228 where the base for timeouts was not persistent across reboots. */ - - if (!dual_timestamp_is_set(&u->state_change_timestamp)) - dual_timestamp_get(&u->state_change_timestamp); - - return 0; -} - -int unit_add_node_link(Unit *u, const char *what, bool wants, UnitDependency dep) { - Unit *device; - _cleanup_free_ char *e = NULL; - int r; - - assert(u); - - /* Adds in links to the device node that this unit is based on */ - if (isempty(what)) - return 0; - - if (!is_device_path(what)) - return 0; - - /* When device units aren't supported (such as in a - * container), don't create dependencies on them. */ - if (!unit_type_supported(UNIT_DEVICE)) - return 0; - - r = unit_name_from_path(what, ".device", &e); - if (r < 0) - return r; - - r = manager_load_unit(u->manager, e, NULL, NULL, &device); - if (r < 0) - return r; - - r = unit_add_two_dependencies(u, UNIT_AFTER, - MANAGER_IS_SYSTEM(u->manager) ? dep : UNIT_WANTS, - device, true); - if (r < 0) - return r; - - if (wants) { - r = unit_add_dependency(device, UNIT_WANTS, u, false); - if (r < 0) - return r; - } - - return 0; -} - -int unit_coldplug(Unit *u) { - int r = 0, q = 0; - - assert(u); - - /* Make sure we don't enter a loop, when coldplugging - * recursively. */ - if (u->coldplugged) - return 0; - - u->coldplugged = true; - - if (UNIT_VTABLE(u)->coldplug) - r = UNIT_VTABLE(u)->coldplug(u); - - if (u->job) - q = job_coldplug(u->job); - - if (r < 0) - return r; - if (q < 0) - return q; - - return 0; -} - -static bool fragment_mtime_newer(const char *path, usec_t mtime) { - struct stat st; - - if (!path) - return false; - - if (stat(path, &st) < 0) - /* What, cannot access this anymore? */ - return true; - - if (mtime > 0) - /* For non-empty files check the mtime */ - return timespec_load(&st.st_mtim) > mtime; - else if (!null_or_empty(&st)) - /* For masked files check if they are still so */ - return true; - - return false; -} - -bool unit_need_daemon_reload(Unit *u) { - _cleanup_strv_free_ char **t = NULL; - char **path; - - assert(u); - - if (fragment_mtime_newer(u->fragment_path, u->fragment_mtime)) - return true; - - if (fragment_mtime_newer(u->source_path, u->source_mtime)) - return true; - - (void) unit_find_dropin_paths(u, &t); - if (!strv_equal(u->dropin_paths, t)) - return true; - - STRV_FOREACH(path, u->dropin_paths) - if (fragment_mtime_newer(*path, u->dropin_mtime)) - return true; - - return false; -} - -void unit_reset_failed(Unit *u) { - assert(u); - - if (UNIT_VTABLE(u)->reset_failed) - UNIT_VTABLE(u)->reset_failed(u); - - RATELIMIT_RESET(u->start_limit); - u->start_limit_hit = false; -} - -Unit *unit_following(Unit *u) { - assert(u); - - if (UNIT_VTABLE(u)->following) - return UNIT_VTABLE(u)->following(u); - - return NULL; -} - -bool unit_stop_pending(Unit *u) { - assert(u); - - /* This call does check the current state of the unit. It's - * hence useful to be called from state change calls of the - * unit itself, where the state isn't updated yet. This is - * different from unit_inactive_or_pending() which checks both - * the current state and for a queued job. */ - - return u->job && u->job->type == JOB_STOP; -} - -bool unit_inactive_or_pending(Unit *u) { - assert(u); - - /* Returns true if the unit is inactive or going down */ - - if (UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))) - return true; - - if (unit_stop_pending(u)) - return true; - - return false; -} - -bool unit_active_or_pending(Unit *u) { - assert(u); - - /* Returns true if the unit is active or going up */ - - if (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) - return true; - - if (u->job && - (u->job->type == JOB_START || - u->job->type == JOB_RELOAD_OR_START || - u->job->type == JOB_RESTART)) - return true; - - return false; -} - -int unit_kill(Unit *u, KillWho w, int signo, sd_bus_error *error) { - assert(u); - assert(w >= 0 && w < _KILL_WHO_MAX); - assert(SIGNAL_VALID(signo)); - - if (!UNIT_VTABLE(u)->kill) - return -EOPNOTSUPP; - - return UNIT_VTABLE(u)->kill(u, w, signo, error); -} - -static Set *unit_pid_set(pid_t main_pid, pid_t control_pid) { - Set *pid_set; - int r; - - pid_set = set_new(NULL); - if (!pid_set) - return NULL; - - /* Exclude the main/control pids from being killed via the cgroup */ - if (main_pid > 0) { - r = set_put(pid_set, PID_TO_PTR(main_pid)); - if (r < 0) - goto fail; - } - - if (control_pid > 0) { - r = set_put(pid_set, PID_TO_PTR(control_pid)); - if (r < 0) - goto fail; - } - - return pid_set; - -fail: - set_free(pid_set); - return NULL; -} - -int unit_kill_common( - Unit *u, - KillWho who, - int signo, - pid_t main_pid, - pid_t control_pid, - sd_bus_error *error) { - - int r = 0; - bool killed = false; - - if (IN_SET(who, KILL_MAIN, KILL_MAIN_FAIL)) { - if (main_pid < 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no main processes", unit_type_to_string(u->type)); - else if (main_pid == 0) - return sd_bus_error_set_const(error, BUS_ERROR_NO_SUCH_PROCESS, "No main process to kill"); - } - - if (IN_SET(who, KILL_CONTROL, KILL_CONTROL_FAIL)) { - if (control_pid < 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no control processes", unit_type_to_string(u->type)); - else if (control_pid == 0) - return sd_bus_error_set_const(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); - } - - if (IN_SET(who, KILL_CONTROL, KILL_CONTROL_FAIL, KILL_ALL, KILL_ALL_FAIL)) - if (control_pid > 0) { - if (kill(control_pid, signo) < 0) - r = -errno; - else - killed = true; - } - - if (IN_SET(who, KILL_MAIN, KILL_MAIN_FAIL, KILL_ALL, KILL_ALL_FAIL)) - if (main_pid > 0) { - if (kill(main_pid, signo) < 0) - r = -errno; - else - killed = true; - } - - if (IN_SET(who, KILL_ALL, KILL_ALL_FAIL) && u->cgroup_path) { - _cleanup_set_free_ Set *pid_set = NULL; - int q; - - /* Exclude the main/control pids from being killed via the cgroup */ - pid_set = unit_pid_set(main_pid, control_pid); - if (!pid_set) - return -ENOMEM; - - q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, signo, false, false, false, pid_set); - if (q < 0 && q != -EAGAIN && q != -ESRCH && q != -ENOENT) - r = q; - else - killed = true; - } - - if (r == 0 && !killed && IN_SET(who, KILL_ALL_FAIL, KILL_CONTROL_FAIL)) - return -ESRCH; - - return r; -} - -int unit_following_set(Unit *u, Set **s) { - assert(u); - assert(s); - - if (UNIT_VTABLE(u)->following_set) - return UNIT_VTABLE(u)->following_set(u, s); - - *s = NULL; - return 0; -} - -UnitFileState unit_get_unit_file_state(Unit *u) { - int r; - - assert(u); - - if (u->unit_file_state < 0 && u->fragment_path) { - r = unit_file_get_state( - u->manager->unit_file_scope, - NULL, - basename(u->fragment_path), - &u->unit_file_state); - if (r < 0) - u->unit_file_state = UNIT_FILE_BAD; - } - - return u->unit_file_state; -} - -int unit_get_unit_file_preset(Unit *u) { - assert(u); - - if (u->unit_file_preset < 0 && u->fragment_path) - u->unit_file_preset = unit_file_query_preset( - u->manager->unit_file_scope, - NULL, - basename(u->fragment_path)); - - return u->unit_file_preset; -} - -Unit* unit_ref_set(UnitRef *ref, Unit *u) { - assert(ref); - assert(u); - - if (ref->unit) - unit_ref_unset(ref); - - ref->unit = u; - LIST_PREPEND(refs, u->refs, ref); - return u; -} - -void unit_ref_unset(UnitRef *ref) { - assert(ref); - - if (!ref->unit) - return; - - /* We are about to drop a reference to the unit, make sure the garbage collection has a look at it as it might - * be unreferenced now. */ - unit_add_to_gc_queue(ref->unit); - - LIST_REMOVE(refs, ref->unit->refs, ref); - ref->unit = NULL; -} - -int unit_patch_contexts(Unit *u) { - CGroupContext *cc; - ExecContext *ec; - unsigned i; - int r; - - assert(u); - - /* Patch in the manager defaults into the exec and cgroup - * contexts, _after_ the rest of the settings have been - * initialized */ - - ec = unit_get_exec_context(u); - if (ec) { - /* This only copies in the ones that need memory */ - for (i = 0; i < _RLIMIT_MAX; i++) - if (u->manager->rlimit[i] && !ec->rlimit[i]) { - ec->rlimit[i] = newdup(struct rlimit, u->manager->rlimit[i], 1); - if (!ec->rlimit[i]) - return -ENOMEM; - } - - if (MANAGER_IS_USER(u->manager) && - !ec->working_directory) { - - r = get_home_dir(&ec->working_directory); - if (r < 0) - return r; - - /* Allow user services to run, even if the - * home directory is missing */ - ec->working_directory_missing_ok = true; - } - - if (MANAGER_IS_USER(u->manager) && - (ec->syscall_whitelist || - !set_isempty(ec->syscall_filter) || - !set_isempty(ec->syscall_archs) || - ec->address_families_whitelist || - !set_isempty(ec->address_families))) - ec->no_new_privileges = true; - - if (ec->private_devices) - ec->capability_bounding_set &= ~(UINT64_C(1) << CAP_MKNOD); - } - - cc = unit_get_cgroup_context(u); - if (cc) { - - if (ec && - ec->private_devices && - cc->device_policy == CGROUP_AUTO) - cc->device_policy = CGROUP_CLOSED; - } - - return 0; -} - -ExecContext *unit_get_exec_context(Unit *u) { - size_t offset; - assert(u); - - if (u->type < 0) - return NULL; - - offset = UNIT_VTABLE(u)->exec_context_offset; - if (offset <= 0) - return NULL; - - return (ExecContext*) ((uint8_t*) u + offset); -} - -KillContext *unit_get_kill_context(Unit *u) { - size_t offset; - assert(u); - - if (u->type < 0) - return NULL; - - offset = UNIT_VTABLE(u)->kill_context_offset; - if (offset <= 0) - return NULL; - - return (KillContext*) ((uint8_t*) u + offset); -} - -CGroupContext *unit_get_cgroup_context(Unit *u) { - size_t offset; - - if (u->type < 0) - return NULL; - - offset = UNIT_VTABLE(u)->cgroup_context_offset; - if (offset <= 0) - return NULL; - - return (CGroupContext*) ((uint8_t*) u + offset); -} - -ExecRuntime *unit_get_exec_runtime(Unit *u) { - size_t offset; - - if (u->type < 0) - return NULL; - - offset = UNIT_VTABLE(u)->exec_runtime_offset; - if (offset <= 0) - return NULL; - - return *(ExecRuntime**) ((uint8_t*) u + offset); -} - -static const char* unit_drop_in_dir(Unit *u, UnitSetPropertiesMode mode) { - assert(u); - - if (!IN_SET(mode, UNIT_RUNTIME, UNIT_PERSISTENT)) - return NULL; - - if (u->transient) /* Redirect drop-ins for transient units always into the transient directory. */ - return u->manager->lookup_paths.transient; - - if (mode == UNIT_RUNTIME) - return u->manager->lookup_paths.runtime_control; - - if (mode == UNIT_PERSISTENT) - return u->manager->lookup_paths.persistent_control; - - return NULL; -} - -int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) { - _cleanup_free_ char *p = NULL, *q = NULL; - const char *dir, *prefixed; - int r; - - assert(u); - - if (u->transient_file) { - /* When this is a transient unit file in creation, then let's not create a new drop-in but instead - * write to the transient unit file. */ - fputs(data, u->transient_file); - return 0; - } - - if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) - return 0; - - dir = unit_drop_in_dir(u, mode); - if (!dir) - return -EINVAL; - - prefixed = strjoina("# This is a drop-in unit file extension, created via \"systemctl set-property\" or an equivalent operation. Do not edit.\n", - data); - - r = drop_in_file(dir, u->id, 50, name, &p, &q); - if (r < 0) - return r; - - (void) mkdir_p(p, 0755); - r = write_string_file_atomic_label(q, prefixed); - if (r < 0) - return r; - - r = strv_push(&u->dropin_paths, q); - if (r < 0) - return r; - q = NULL; - - strv_uniq(u->dropin_paths); - - u->dropin_mtime = now(CLOCK_REALTIME); - - return 0; -} - -int unit_write_drop_in_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) { - _cleanup_free_ char *p = NULL; - va_list ap; - int r; - - assert(u); - assert(name); - assert(format); - - if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) - return 0; - - va_start(ap, format); - r = vasprintf(&p, format, ap); - va_end(ap); - - if (r < 0) - return -ENOMEM; - - return unit_write_drop_in(u, mode, name, p); -} - -int unit_write_drop_in_private(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) { - const char *ndata; - - assert(u); - assert(name); - assert(data); - - if (!UNIT_VTABLE(u)->private_section) - return -EINVAL; - - if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) - return 0; - - ndata = strjoina("[", UNIT_VTABLE(u)->private_section, "]\n", data); - - return unit_write_drop_in(u, mode, name, ndata); -} - -int unit_write_drop_in_private_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) { - _cleanup_free_ char *p = NULL; - va_list ap; - int r; - - assert(u); - assert(name); - assert(format); - - if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) - return 0; - - va_start(ap, format); - r = vasprintf(&p, format, ap); - va_end(ap); - - if (r < 0) - return -ENOMEM; - - return unit_write_drop_in_private(u, mode, name, p); -} - -int unit_make_transient(Unit *u) { - FILE *f; - char *path; - - assert(u); - - if (!UNIT_VTABLE(u)->can_transient) - return -EOPNOTSUPP; - - path = strjoin(u->manager->lookup_paths.transient, "/", u->id, NULL); - if (!path) - return -ENOMEM; - - /* Let's open the file we'll write the transient settings into. This file is kept open as long as we are - * creating the transient, and is closed in unit_load(), as soon as we start loading the file. */ - - RUN_WITH_UMASK(0022) { - f = fopen(path, "we"); - if (!f) { - free(path); - return -errno; - } - } - - if (u->transient_file) - fclose(u->transient_file); - u->transient_file = f; - - free(u->fragment_path); - u->fragment_path = path; - - u->source_path = mfree(u->source_path); - u->dropin_paths = strv_free(u->dropin_paths); - u->fragment_mtime = u->source_mtime = u->dropin_mtime = 0; - - u->load_state = UNIT_STUB; - u->load_error = 0; - u->transient = true; - - unit_add_to_dbus_queue(u); - unit_add_to_gc_queue(u); - unit_add_to_load_queue(u); - - fputs("# This is a transient unit file, created programmatically via the systemd API. Do not edit.\n", - u->transient_file); - - return 0; -} - -int unit_kill_context( - Unit *u, - KillContext *c, - KillOperation k, - pid_t main_pid, - pid_t control_pid, - bool main_pid_alien) { - - bool wait_for_exit = false; - int sig, r; - - assert(u); - assert(c); - - if (c->kill_mode == KILL_NONE) - return 0; - - switch (k) { - case KILL_KILL: - sig = SIGKILL; - break; - case KILL_ABORT: - sig = SIGABRT; - break; - case KILL_TERMINATE: - sig = c->kill_signal; - break; - default: - assert_not_reached("KillOperation unknown"); - } - - if (main_pid > 0) { - r = kill_and_sigcont(main_pid, sig); - - if (r < 0 && r != -ESRCH) { - _cleanup_free_ char *comm = NULL; - get_process_comm(main_pid, &comm); - - log_unit_warning_errno(u, r, "Failed to kill main process " PID_FMT " (%s), ignoring: %m", main_pid, strna(comm)); - } else { - if (!main_pid_alien) - wait_for_exit = true; - - if (c->send_sighup && k == KILL_TERMINATE) - (void) kill(main_pid, SIGHUP); - } - } - - if (control_pid > 0) { - r = kill_and_sigcont(control_pid, sig); - - if (r < 0 && r != -ESRCH) { - _cleanup_free_ char *comm = NULL; - get_process_comm(control_pid, &comm); - - log_unit_warning_errno(u, r, "Failed to kill control process " PID_FMT " (%s), ignoring: %m", control_pid, strna(comm)); - } else { - wait_for_exit = true; - - if (c->send_sighup && k == KILL_TERMINATE) - (void) kill(control_pid, SIGHUP); - } - } - - if (u->cgroup_path && - (c->kill_mode == KILL_CONTROL_GROUP || (c->kill_mode == KILL_MIXED && k == KILL_KILL))) { - _cleanup_set_free_ Set *pid_set = NULL; - - /* Exclude the main/control pids from being killed via the cgroup */ - pid_set = unit_pid_set(main_pid, control_pid); - if (!pid_set) - return -ENOMEM; - - r = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, sig, true, k != KILL_TERMINATE, false, pid_set); - if (r < 0) { - if (r != -EAGAIN && r != -ESRCH && r != -ENOENT) - log_unit_warning_errno(u, r, "Failed to kill control group %s, ignoring: %m", u->cgroup_path); - - } else if (r > 0) { - - /* FIXME: For now, on the legacy hierarchy, we - * will not wait for the cgroup members to die - * if we are running in a container or if this - * is a delegation unit, simply because cgroup - * notification is unreliable in these - * cases. It doesn't work at all in - * containers, and outside of containers it - * can be confused easily by left-over - * directories in the cgroup — which however - * should not exist in non-delegated units. On - * the unified hierarchy that's different, - * there we get proper events. Hence rely on - * them.*/ - - if (cg_unified() > 0 || - (detect_container() == 0 && !unit_cgroup_delegate(u))) - wait_for_exit = true; - - if (c->send_sighup && k != KILL_KILL) { - set_free(pid_set); - - pid_set = unit_pid_set(main_pid, control_pid); - if (!pid_set) - return -ENOMEM; - - cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, SIGHUP, false, true, false, pid_set); - } - } - } - - return wait_for_exit; -} - -int unit_require_mounts_for(Unit *u, const char *path) { - char prefix[strlen(path) + 1], *p; - int r; - - assert(u); - assert(path); - - /* Registers a unit for requiring a certain path and all its - * prefixes. We keep a simple array of these paths in the - * unit, since its usually short. However, we build a prefix - * table for all possible prefixes so that new appearing mount - * units can easily determine which units to make themselves a - * dependency of. */ - - if (!path_is_absolute(path)) - return -EINVAL; - - p = strdup(path); - if (!p) - return -ENOMEM; - - path_kill_slashes(p); - - if (!path_is_safe(p)) { - free(p); - return -EPERM; - } - - if (strv_contains(u->requires_mounts_for, p)) { - free(p); - return 0; - } - - r = strv_consume(&u->requires_mounts_for, p); - if (r < 0) - return r; - - PATH_FOREACH_PREFIX_MORE(prefix, p) { - Set *x; - - x = hashmap_get(u->manager->units_requiring_mounts_for, prefix); - if (!x) { - char *q; - - r = hashmap_ensure_allocated(&u->manager->units_requiring_mounts_for, &string_hash_ops); - if (r < 0) - return r; - - q = strdup(prefix); - if (!q) - return -ENOMEM; - - x = set_new(NULL); - if (!x) { - free(q); - return -ENOMEM; - } - - r = hashmap_put(u->manager->units_requiring_mounts_for, q, x); - if (r < 0) { - free(q); - set_free(x); - return r; - } - } - - r = set_put(x, u); - if (r < 0) - return r; - } - - return 0; -} - -int unit_setup_exec_runtime(Unit *u) { - ExecRuntime **rt; - size_t offset; - Iterator i; - Unit *other; - - offset = UNIT_VTABLE(u)->exec_runtime_offset; - assert(offset > 0); - - /* Check if there already is an ExecRuntime for this unit? */ - rt = (ExecRuntime**) ((uint8_t*) u + offset); - if (*rt) - return 0; - - /* Try to get it from somebody else */ - SET_FOREACH(other, u->dependencies[UNIT_JOINS_NAMESPACE_OF], i) { - - *rt = unit_get_exec_runtime(other); - if (*rt) { - exec_runtime_ref(*rt); - return 0; - } - } - - return exec_runtime_make(rt, unit_get_exec_context(u), u->id); -} - -bool unit_type_supported(UnitType t) { - if (_unlikely_(t < 0)) - return false; - if (_unlikely_(t >= _UNIT_TYPE_MAX)) - return false; - - if (!unit_vtable[t]->supported) - return true; - - return unit_vtable[t]->supported(); -} - -void unit_warn_if_dir_nonempty(Unit *u, const char* where) { - int r; - - assert(u); - assert(where); - - r = dir_is_empty(where); - if (r > 0) - return; - if (r < 0) { - log_unit_warning_errno(u, r, "Failed to check directory %s: %m", where); - return; - } - - log_struct(LOG_NOTICE, - LOG_MESSAGE_ID(SD_MESSAGE_OVERMOUNTING), - LOG_UNIT_ID(u), - LOG_UNIT_MESSAGE(u, "Directory %s to mount over is not empty, mounting anyway.", where), - "WHERE=%s", where, - NULL); -} - -int unit_fail_if_symlink(Unit *u, const char* where) { - int r; - - assert(u); - assert(where); - - r = is_symlink(where); - if (r < 0) { - log_unit_debug_errno(u, r, "Failed to check symlink %s, ignoring: %m", where); - return 0; - } - if (r == 0) - return 0; - - log_struct(LOG_ERR, - LOG_MESSAGE_ID(SD_MESSAGE_OVERMOUNTING), - LOG_UNIT_ID(u), - LOG_UNIT_MESSAGE(u, "Mount on symlink %s not allowed.", where), - "WHERE=%s", where, - NULL); - - return -ELOOP; -} - -bool unit_is_pristine(Unit *u) { - assert(u); - - /* Check if the unit already exists or is already around, - * in a number of different ways. Note that to cater for unit - * types such as slice, we are generally fine with units that - * are marked UNIT_LOADED even even though nothing was - * actually loaded, as those unit types don't require a file - * on disk to validly load. */ - - return !(!IN_SET(u->load_state, UNIT_NOT_FOUND, UNIT_LOADED) || - u->fragment_path || - u->source_path || - !strv_isempty(u->dropin_paths) || - u->job || - u->merged_into); -} - -pid_t unit_control_pid(Unit *u) { - assert(u); - - if (UNIT_VTABLE(u)->control_pid) - return UNIT_VTABLE(u)->control_pid(u); - - return 0; -} - -pid_t unit_main_pid(Unit *u) { - assert(u); - - if (UNIT_VTABLE(u)->main_pid) - return UNIT_VTABLE(u)->main_pid(u); - - return 0; -} diff --git a/src/core/unit.h b/src/core/unit.h deleted file mode 100644 index 08a927962d..0000000000 --- a/src/core/unit.h +++ /dev/null @@ -1,639 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include - -typedef struct Unit Unit; -typedef struct UnitVTable UnitVTable; -typedef struct UnitRef UnitRef; -typedef struct UnitStatusMessageFormats UnitStatusMessageFormats; - -#include "condition.h" -#include "failure-action.h" -#include "install.h" -#include "list.h" -#include "unit-name.h" - -typedef enum KillOperation { - KILL_TERMINATE, - KILL_KILL, - KILL_ABORT, - _KILL_OPERATION_MAX, - _KILL_OPERATION_INVALID = -1 -} KillOperation; - -static inline bool UNIT_IS_ACTIVE_OR_RELOADING(UnitActiveState t) { - return t == UNIT_ACTIVE || t == UNIT_RELOADING; -} - -static inline bool UNIT_IS_ACTIVE_OR_ACTIVATING(UnitActiveState t) { - return t == UNIT_ACTIVE || t == UNIT_ACTIVATING || t == UNIT_RELOADING; -} - -static inline bool UNIT_IS_INACTIVE_OR_DEACTIVATING(UnitActiveState t) { - return t == UNIT_INACTIVE || t == UNIT_FAILED || t == UNIT_DEACTIVATING; -} - -static inline bool UNIT_IS_INACTIVE_OR_FAILED(UnitActiveState t) { - return t == UNIT_INACTIVE || t == UNIT_FAILED; -} - -#include "job.h" - -struct UnitRef { - /* Keeps tracks of references to a unit. This is useful so - * that we can merge two units if necessary and correct all - * references to them */ - - Unit* unit; - LIST_FIELDS(UnitRef, refs); -}; - -struct Unit { - Manager *manager; - - UnitType type; - UnitLoadState load_state; - Unit *merged_into; - - char *id; /* One name is special because we use it for identification. Points to an entry in the names set */ - char *instance; - - Set *names; - Set *dependencies[_UNIT_DEPENDENCY_MAX]; - - char **requires_mounts_for; - - char *description; - char **documentation; - - char *fragment_path; /* if loaded from a config file this is the primary path to it */ - char *source_path; /* if converted, the source file */ - char **dropin_paths; - - usec_t fragment_mtime; - usec_t source_mtime; - usec_t dropin_mtime; - - /* If this is a transient unit we are currently writing, this is where we are writing it to */ - FILE *transient_file; - - /* If there is something to do with this unit, then this is the installed job for it */ - Job *job; - - /* JOB_NOP jobs are special and can be installed without disturbing the real job. */ - Job *nop_job; - - /* The slot used for watching NameOwnerChanged signals */ - sd_bus_slot *match_bus_slot; - - /* Job timeout and action to take */ - usec_t job_timeout; - FailureAction job_timeout_action; - char *job_timeout_reboot_arg; - - /* References to this */ - LIST_HEAD(UnitRef, refs); - - /* Conditions to check */ - LIST_HEAD(Condition, conditions); - LIST_HEAD(Condition, asserts); - - dual_timestamp condition_timestamp; - dual_timestamp assert_timestamp; - - /* Updated whenever the low-level state changes */ - dual_timestamp state_change_timestamp; - - /* Updated whenever the (high-level) active state enters or leaves the active or inactive states */ - dual_timestamp inactive_exit_timestamp; - dual_timestamp active_enter_timestamp; - dual_timestamp active_exit_timestamp; - dual_timestamp inactive_enter_timestamp; - - UnitRef slice; - - /* Per type list */ - LIST_FIELDS(Unit, units_by_type); - - /* All units which have requires_mounts_for set */ - LIST_FIELDS(Unit, has_requires_mounts_for); - - /* Load queue */ - LIST_FIELDS(Unit, load_queue); - - /* D-Bus queue */ - LIST_FIELDS(Unit, dbus_queue); - - /* Cleanup queue */ - LIST_FIELDS(Unit, cleanup_queue); - - /* GC queue */ - LIST_FIELDS(Unit, gc_queue); - - /* CGroup realize members queue */ - LIST_FIELDS(Unit, cgroup_queue); - - /* Units with the same CGroup netclass */ - LIST_FIELDS(Unit, cgroup_netclass); - - /* PIDs we keep an eye on. Note that a unit might have many - * more, but these are the ones we care enough about to - * process SIGCHLD for */ - Set *pids; - - /* Used during GC sweeps */ - unsigned gc_marker; - - /* Error code when we didn't manage to load the unit (negative) */ - int load_error; - - /* Put a ratelimit on unit starting */ - RateLimit start_limit; - FailureAction start_limit_action; - char *reboot_arg; - - /* Make sure we never enter endless loops with the check unneeded logic, or the BindsTo= logic */ - RateLimit auto_stop_ratelimit; - - /* Cached unit file state and preset */ - UnitFileState unit_file_state; - int unit_file_preset; - - /* Where the cpuacct.usage cgroup counter was at the time the unit was started */ - nsec_t cpuacct_usage_base; - - /* Counterparts in the cgroup filesystem */ - char *cgroup_path; - CGroupMask cgroup_realized_mask; - CGroupMask cgroup_enabled_mask; - CGroupMask cgroup_subtree_mask; - CGroupMask cgroup_members_mask; - int cgroup_inotify_wd; - - uint32_t cgroup_netclass_id; - - /* How to start OnFailure units */ - JobMode on_failure_job_mode; - - /* Garbage collect us we nobody wants or requires us anymore */ - bool stop_when_unneeded; - - /* Create default dependencies */ - bool default_dependencies; - - /* Refuse manual starting, allow starting only indirectly via dependency. */ - bool refuse_manual_start; - - /* Don't allow the user to stop this unit manually, allow stopping only indirectly via dependency. */ - bool refuse_manual_stop; - - /* Allow isolation requests */ - bool allow_isolate; - - /* Ignore this unit when isolating */ - bool ignore_on_isolate; - - /* Did the last condition check succeed? */ - bool condition_result; - bool assert_result; - - /* Is this a transient unit? */ - bool transient; - - bool in_load_queue:1; - bool in_dbus_queue:1; - bool in_cleanup_queue:1; - bool in_gc_queue:1; - bool in_cgroup_queue:1; - - bool sent_dbus_new_signal:1; - - bool no_gc:1; - - bool in_audit:1; - - bool cgroup_realized:1; - bool cgroup_members_mask_valid:1; - bool cgroup_subtree_mask_valid:1; - - bool start_limit_hit:1; - - /* Did we already invoke unit_coldplug() for this unit? */ - bool coldplugged:1; -}; - -struct UnitStatusMessageFormats { - const char *starting_stopping[2]; - const char *finished_start_job[_JOB_RESULT_MAX]; - const char *finished_stop_job[_JOB_RESULT_MAX]; -}; - -typedef enum UnitSetPropertiesMode { - UNIT_CHECK = 0, - UNIT_RUNTIME = 1, - UNIT_PERSISTENT = 2, -} UnitSetPropertiesMode; - -#include "automount.h" -#include "busname.h" -#include "device.h" -#include "path.h" -#include "scope.h" -#include "slice.h" -#include "socket.h" -#include "swap.h" -#include "target.h" -#include "timer.h" - -struct UnitVTable { - /* How much memory does an object of this unit type need */ - size_t object_size; - - /* If greater than 0, the offset into the object where - * ExecContext is found, if the unit type has that */ - size_t exec_context_offset; - - /* If greater than 0, the offset into the object where - * CGroupContext is found, if the unit type has that */ - size_t cgroup_context_offset; - - /* If greater than 0, the offset into the object where - * KillContext is found, if the unit type has that */ - size_t kill_context_offset; - - /* If greater than 0, the offset into the object where the - * pointer to ExecRuntime is found, if the unit type has - * that */ - size_t exec_runtime_offset; - - /* The name of the configuration file section with the private settings of this unit */ - const char *private_section; - - /* Config file sections this unit type understands, separated - * by NUL chars */ - const char *sections; - - /* This should reset all type-specific variables. This should - * not allocate memory, and is called with zero-initialized - * data. It should hence only initialize variables that need - * to be set != 0. */ - void (*init)(Unit *u); - - /* This should free all type-specific variables. It should be - * idempotent. */ - void (*done)(Unit *u); - - /* Actually load data from disk. This may fail, and should set - * load_state to UNIT_LOADED, UNIT_MERGED or leave it at - * UNIT_STUB if no configuration could be found. */ - int (*load)(Unit *u); - - /* If a lot of units got created via enumerate(), this is - * where to actually set the state and call unit_notify(). */ - int (*coldplug)(Unit *u); - - void (*dump)(Unit *u, FILE *f, const char *prefix); - - int (*start)(Unit *u); - int (*stop)(Unit *u); - int (*reload)(Unit *u); - - int (*kill)(Unit *u, KillWho w, int signo, sd_bus_error *error); - - bool (*can_reload)(Unit *u); - - /* Write all data that cannot be restored from other sources - * away using unit_serialize_item() */ - int (*serialize)(Unit *u, FILE *f, FDSet *fds); - - /* Restore one item from the serialization */ - int (*deserialize_item)(Unit *u, const char *key, const char *data, FDSet *fds); - - /* Try to match up fds with what we need for this unit */ - void (*distribute_fds)(Unit *u, FDSet *fds); - - /* Boils down the more complex internal state of this unit to - * a simpler one that the engine can understand */ - UnitActiveState (*active_state)(Unit *u); - - /* Returns the substate specific to this unit type as - * string. This is purely information so that we can give the - * user a more fine grained explanation in which actual state a - * unit is in. */ - const char* (*sub_state_to_string)(Unit *u); - - /* Return true when there is reason to keep this entry around - * even nothing references it and it isn't active in any - * way */ - bool (*check_gc)(Unit *u); - - /* When the unit is not running and no job for it queued we - * shall release its runtime resources */ - void (*release_resources)(Unit *u); - - /* Invoked on every child that died */ - void (*sigchld_event)(Unit *u, pid_t pid, int code, int status); - - /* Reset failed state if we are in failed state */ - void (*reset_failed)(Unit *u); - - /* Called whenever any of the cgroups this unit watches for - * ran empty */ - void (*notify_cgroup_empty)(Unit *u); - - /* Called whenever a process of this unit sends us a message */ - void (*notify_message)(Unit *u, pid_t pid, char **tags, FDSet *fds); - - /* Called whenever a name this Unit registered for comes or - * goes away. */ - void (*bus_name_owner_change)(Unit *u, const char *name, const char *old_owner, const char *new_owner); - - /* Called for each property that is being set */ - int (*bus_set_property)(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); - - /* Called after at least one property got changed to apply the necessary change */ - int (*bus_commit_properties)(Unit *u); - - /* Return the unit this unit is following */ - Unit *(*following)(Unit *u); - - /* Return the set of units that are following each other */ - int (*following_set)(Unit *u, Set **s); - - /* Invoked each time a unit this unit is triggering changes - * state or gains/loses a job */ - void (*trigger_notify)(Unit *u, Unit *trigger); - - /* Called whenever CLOCK_REALTIME made a jump */ - void (*time_change)(Unit *u); - - /* Returns the next timeout of a unit */ - int (*get_timeout)(Unit *u, usec_t *timeout); - - /* Returns the main PID if there is any defined, or 0. */ - pid_t (*main_pid)(Unit *u); - - /* Returns the main PID if there is any defined, or 0. */ - pid_t (*control_pid)(Unit *u); - - /* This is called for each unit type and should be used to - * enumerate existing devices and load them. However, - * everything that is loaded here should still stay in - * inactive state. It is the job of the coldplug() call above - * to put the units into the initial state. */ - void (*enumerate)(Manager *m); - - /* Type specific cleanups. */ - void (*shutdown)(Manager *m); - - /* If this function is set and return false all jobs for units - * of this type will immediately fail. */ - bool (*supported)(void); - - /* The bus vtable */ - const sd_bus_vtable *bus_vtable; - - /* The strings to print in status messages */ - UnitStatusMessageFormats status_message_formats; - - /* True if transient units of this type are OK */ - bool can_transient:1; -}; - -extern const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX]; - -#define UNIT_VTABLE(u) unit_vtable[(u)->type] - -/* For casting a unit into the various unit types */ -#define DEFINE_CAST(UPPERCASE, MixedCase) \ - static inline MixedCase* UPPERCASE(Unit *u) { \ - if (_unlikely_(!u || u->type != UNIT_##UPPERCASE)) \ - return NULL; \ - \ - return (MixedCase*) u; \ - } - -/* For casting the various unit types into a unit */ -#define UNIT(u) (&(u)->meta) - -#define UNIT_HAS_EXEC_CONTEXT(u) (UNIT_VTABLE(u)->exec_context_offset > 0) -#define UNIT_HAS_CGROUP_CONTEXT(u) (UNIT_VTABLE(u)->cgroup_context_offset > 0) -#define UNIT_HAS_KILL_CONTEXT(u) (UNIT_VTABLE(u)->kill_context_offset > 0) - -#define UNIT_TRIGGER(u) ((Unit*) set_first((u)->dependencies[UNIT_TRIGGERS])) - -DEFINE_CAST(SERVICE, Service); -DEFINE_CAST(SOCKET, Socket); -DEFINE_CAST(BUSNAME, BusName); -DEFINE_CAST(TARGET, Target); -DEFINE_CAST(DEVICE, Device); -DEFINE_CAST(MOUNT, Mount); -DEFINE_CAST(AUTOMOUNT, Automount); -DEFINE_CAST(SWAP, Swap); -DEFINE_CAST(TIMER, Timer); -DEFINE_CAST(PATH, Path); -DEFINE_CAST(SLICE, Slice); -DEFINE_CAST(SCOPE, Scope); - -Unit *unit_new(Manager *m, size_t size); -void unit_free(Unit *u); - -int unit_add_name(Unit *u, const char *name); - -int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_reference); -int unit_add_two_dependencies(Unit *u, UnitDependency d, UnitDependency e, Unit *other, bool add_reference); - -int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, const char *filename, bool add_reference); -int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference); - -int unit_add_exec_dependencies(Unit *u, ExecContext *c); - -int unit_choose_id(Unit *u, const char *name); -int unit_set_description(Unit *u, const char *description); - -bool unit_check_gc(Unit *u); - -void unit_add_to_load_queue(Unit *u); -void unit_add_to_dbus_queue(Unit *u); -void unit_add_to_cleanup_queue(Unit *u); -void unit_add_to_gc_queue(Unit *u); - -int unit_merge(Unit *u, Unit *other); -int unit_merge_by_name(Unit *u, const char *other); - -Unit *unit_follow_merge(Unit *u) _pure_; - -int unit_load_fragment_and_dropin(Unit *u); -int unit_load_fragment_and_dropin_optional(Unit *u); -int unit_load(Unit *unit); - -int unit_set_slice(Unit *u, Unit *slice); -int unit_set_default_slice(Unit *u); - -const char *unit_description(Unit *u) _pure_; - -bool unit_has_name(Unit *u, const char *name); - -UnitActiveState unit_active_state(Unit *u); - -const char* unit_sub_state_to_string(Unit *u); - -void unit_dump(Unit *u, FILE *f, const char *prefix); - -bool unit_can_reload(Unit *u) _pure_; -bool unit_can_start(Unit *u) _pure_; -bool unit_can_isolate(Unit *u) _pure_; - -int unit_start(Unit *u); -int unit_stop(Unit *u); -int unit_reload(Unit *u); - -int unit_kill(Unit *u, KillWho w, int signo, sd_bus_error *error); -int unit_kill_common(Unit *u, KillWho who, int signo, pid_t main_pid, pid_t control_pid, sd_bus_error *error); - -void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success); - -int unit_watch_pid(Unit *u, pid_t pid); -void unit_unwatch_pid(Unit *u, pid_t pid); -void unit_unwatch_all_pids(Unit *u); - -void unit_tidy_watch_pids(Unit *u, pid_t except1, pid_t except2); - -int unit_install_bus_match(Unit *u, sd_bus *bus, const char *name); -int unit_watch_bus_name(Unit *u, const char *name); -void unit_unwatch_bus_name(Unit *u, const char *name); - -bool unit_job_is_applicable(Unit *u, JobType j); - -int set_unit_path(const char *p); - -char *unit_dbus_path(Unit *u); - -int unit_load_related_unit(Unit *u, const char *type, Unit **_found); - -bool unit_can_serialize(Unit *u) _pure_; - -int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs); -int unit_deserialize(Unit *u, FILE *f, FDSet *fds); - -int unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value); -int unit_serialize_item_escaped(Unit *u, FILE *f, const char *key, const char *value); -int unit_serialize_item_fd(Unit *u, FILE *f, FDSet *fds, const char *key, int fd); -void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *value, ...) _printf_(4,5); - -int unit_add_node_link(Unit *u, const char *what, bool wants, UnitDependency d); - -int unit_coldplug(Unit *u); - -void unit_status_printf(Unit *u, const char *status, const char *unit_status_msg_format) _printf_(3, 0); -void unit_status_emit_starting_stopping_reloading(Unit *u, JobType t); - -bool unit_need_daemon_reload(Unit *u); - -void unit_reset_failed(Unit *u); - -Unit *unit_following(Unit *u); -int unit_following_set(Unit *u, Set **s); - -const char *unit_slice_name(Unit *u); - -bool unit_stop_pending(Unit *u) _pure_; -bool unit_inactive_or_pending(Unit *u) _pure_; -bool unit_active_or_pending(Unit *u); - -int unit_add_default_target_dependency(Unit *u, Unit *target); - -void unit_start_on_failure(Unit *u); -void unit_trigger_notify(Unit *u); - -UnitFileState unit_get_unit_file_state(Unit *u); -int unit_get_unit_file_preset(Unit *u); - -Unit* unit_ref_set(UnitRef *ref, Unit *u); -void unit_ref_unset(UnitRef *ref); - -#define UNIT_DEREF(ref) ((ref).unit) -#define UNIT_ISSET(ref) (!!(ref).unit) - -int unit_patch_contexts(Unit *u); - -ExecContext *unit_get_exec_context(Unit *u) _pure_; -KillContext *unit_get_kill_context(Unit *u) _pure_; -CGroupContext *unit_get_cgroup_context(Unit *u) _pure_; - -ExecRuntime *unit_get_exec_runtime(Unit *u) _pure_; - -int unit_setup_exec_runtime(Unit *u); - -int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data); -int unit_write_drop_in_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) _printf_(4,5); - -int unit_write_drop_in_private(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data); -int unit_write_drop_in_private_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) _printf_(4,5); - -int unit_kill_context(Unit *u, KillContext *c, KillOperation k, pid_t main_pid, pid_t control_pid, bool main_pid_alien); - -int unit_make_transient(Unit *u); - -int unit_require_mounts_for(Unit *u, const char *path); - -bool unit_type_supported(UnitType t); - -bool unit_is_pristine(Unit *u); - -pid_t unit_control_pid(Unit *u); -pid_t unit_main_pid(Unit *u); - -static inline bool unit_supported(Unit *u) { - return unit_type_supported(u->type); -} - -void unit_warn_if_dir_nonempty(Unit *u, const char* where); -int unit_fail_if_symlink(Unit *u, const char* where); - -int unit_start_limit_test(Unit *u); - -/* Macros which append UNIT= or USER_UNIT= to the message */ - -#define log_unit_full(unit, level, error, ...) \ - ({ \ - Unit *_u = (unit); \ - _u ? log_object_internal(level, error, __FILE__, __LINE__, __func__, _u->manager->unit_log_field, _u->id, ##__VA_ARGS__) : \ - log_internal(level, error, __FILE__, __LINE__, __func__, ##__VA_ARGS__); \ - }) - -#define log_unit_debug(unit, ...) log_unit_full(unit, LOG_DEBUG, 0, ##__VA_ARGS__) -#define log_unit_info(unit, ...) log_unit_full(unit, LOG_INFO, 0, ##__VA_ARGS__) -#define log_unit_notice(unit, ...) log_unit_full(unit, LOG_NOTICE, 0, ##__VA_ARGS__) -#define log_unit_warning(unit, ...) log_unit_full(unit, LOG_WARNING, 0, ##__VA_ARGS__) -#define log_unit_error(unit, ...) log_unit_full(unit, LOG_ERR, 0, ##__VA_ARGS__) - -#define log_unit_debug_errno(unit, error, ...) log_unit_full(unit, LOG_DEBUG, error, ##__VA_ARGS__) -#define log_unit_info_errno(unit, error, ...) log_unit_full(unit, LOG_INFO, error, ##__VA_ARGS__) -#define log_unit_notice_errno(unit, error, ...) log_unit_full(unit, LOG_NOTICE, error, ##__VA_ARGS__) -#define log_unit_warning_errno(unit, error, ...) log_unit_full(unit, LOG_WARNING, error, ##__VA_ARGS__) -#define log_unit_error_errno(unit, error, ...) log_unit_full(unit, LOG_ERR, error, ##__VA_ARGS__) - -#define LOG_UNIT_MESSAGE(unit, fmt, ...) "MESSAGE=%s: " fmt, (unit)->id, ##__VA_ARGS__ -#define LOG_UNIT_ID(unit) (unit)->manager->unit_log_format_string, (unit)->id diff --git a/src/core/user.conf b/src/core/user.conf deleted file mode 100644 index b427f1ef6d..0000000000 --- a/src/core/user.conf +++ /dev/null @@ -1,44 +0,0 @@ -# This file is part of systemd. -# -# 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. -# -# You can override the directives in this file by creating files in -# /etc/systemd/user.conf.d/*.conf. -# -# See systemd-user.conf(5) for details - -[Manager] -#LogLevel=info -#LogTarget=console -#LogColor=yes -#LogLocation=no -#SystemCallArchitectures= -#TimerSlackNSec= -#DefaultTimerAccuracySec=1min -#DefaultStandardOutput=inherit -#DefaultStandardError=inherit -#DefaultTimeoutStartSec=90s -#DefaultTimeoutStopSec=90s -#DefaultRestartSec=100ms -#DefaultStartLimitIntervalSec=10s -#DefaultStartLimitBurst=5 -#DefaultEnvironment= -#DefaultLimitCPU= -#DefaultLimitFSIZE= -#DefaultLimitDATA= -#DefaultLimitSTACK= -#DefaultLimitCORE= -#DefaultLimitRSS= -#DefaultLimitNOFILE= -#DefaultLimitAS= -#DefaultLimitNPROC= -#DefaultLimitMEMLOCK= -#DefaultLimitLOCKS= -#DefaultLimitSIGPENDING= -#DefaultLimitMSGQUEUE= -#DefaultLimitNICE= -#DefaultLimitRTPRIO= -#DefaultLimitRTTIME= diff --git a/src/coredump/Makefile b/src/coredump/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/coredump/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/coredump/coredump-vacuum.c b/src/coredump/coredump-vacuum.c deleted file mode 100644 index f02b6dbd87..0000000000 --- a/src/coredump/coredump-vacuum.c +++ /dev/null @@ -1,268 +0,0 @@ -/*** - 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 "alloc-util.h" -#include "coredump-vacuum.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "hashmap.h" -#include "macro.h" -#include "string-util.h" -#include "time-util.h" -#include "user-util.h" -#include "util.h" - -#define DEFAULT_MAX_USE_LOWER (uint64_t) (1ULL*1024ULL*1024ULL) /* 1 MiB */ -#define DEFAULT_MAX_USE_UPPER (uint64_t) (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */ -#define DEFAULT_KEEP_FREE_UPPER (uint64_t) (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */ -#define DEFAULT_KEEP_FREE (uint64_t) (1024ULL*1024ULL) /* 1 MB */ - -struct vacuum_candidate { - unsigned n_files; - char *oldest_file; - usec_t oldest_mtime; -}; - -static void vacuum_candidate_free(struct vacuum_candidate *c) { - if (!c) - return; - - free(c->oldest_file); - free(c); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct vacuum_candidate*, vacuum_candidate_free); - -static void vacuum_candidate_hasmap_free(Hashmap *h) { - struct vacuum_candidate *c; - - while ((c = hashmap_steal_first(h))) - vacuum_candidate_free(c); - - hashmap_free(h); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, vacuum_candidate_hasmap_free); - -static int uid_from_file_name(const char *filename, uid_t *uid) { - const char *p, *e, *u; - - p = startswith(filename, "core."); - if (!p) - return -EINVAL; - - /* Skip the comm field */ - p = strchr(p, '.'); - if (!p) - return -EINVAL; - p++; - - /* Find end up UID */ - e = strchr(p, '.'); - if (!e) - return -EINVAL; - - u = strndupa(p, e-p); - return parse_uid(u, uid); -} - -static bool vacuum_necessary(int fd, uint64_t sum, uint64_t keep_free, uint64_t max_use) { - uint64_t fs_size = 0, fs_free = (uint64_t) -1; - struct statvfs sv; - - assert(fd >= 0); - - if (fstatvfs(fd, &sv) >= 0) { - fs_size = sv.f_frsize * sv.f_blocks; - fs_free = sv.f_frsize * sv.f_bfree; - } - - if (max_use == (uint64_t) -1) { - - if (fs_size > 0) { - max_use = PAGE_ALIGN(fs_size / 10); /* 10% */ - - if (max_use > DEFAULT_MAX_USE_UPPER) - max_use = DEFAULT_MAX_USE_UPPER; - - if (max_use < DEFAULT_MAX_USE_LOWER) - max_use = DEFAULT_MAX_USE_LOWER; - } else - max_use = DEFAULT_MAX_USE_LOWER; - } else - max_use = PAGE_ALIGN(max_use); - - if (max_use > 0 && sum > max_use) - return true; - - if (keep_free == (uint64_t) -1) { - - if (fs_size > 0) { - keep_free = PAGE_ALIGN((fs_size * 3) / 20); /* 15% */ - - if (keep_free > DEFAULT_KEEP_FREE_UPPER) - keep_free = DEFAULT_KEEP_FREE_UPPER; - } else - keep_free = DEFAULT_KEEP_FREE; - } else - keep_free = PAGE_ALIGN(keep_free); - - if (keep_free > 0 && fs_free < keep_free) - return true; - - return false; -} - -int coredump_vacuum(int exclude_fd, uint64_t keep_free, uint64_t max_use) { - _cleanup_closedir_ DIR *d = NULL; - struct stat exclude_st; - int r; - - if (keep_free == 0 && max_use == 0) - return 0; - - if (exclude_fd >= 0) { - if (fstat(exclude_fd, &exclude_st) < 0) - return log_error_errno(errno, "Failed to fstat(): %m"); - } - - /* This algorithm will keep deleting the oldest file of the - * user with the most coredumps until we are back in the size - * limits. Note that vacuuming for journal files is different, - * because we rely on rate-limiting of the messages there, - * to avoid being flooded. */ - - d = opendir("/var/lib/systemd/coredump"); - if (!d) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Can't open coredump directory: %m"); - } - - for (;;) { - _cleanup_(vacuum_candidate_hasmap_freep) Hashmap *h = NULL; - struct vacuum_candidate *worst = NULL; - struct dirent *de; - uint64_t sum = 0; - - rewinddir(d); - - FOREACH_DIRENT(de, d, goto fail) { - struct vacuum_candidate *c; - struct stat st; - uid_t uid; - usec_t t; - - r = uid_from_file_name(de->d_name, &uid); - if (r < 0) - continue; - - if (fstatat(dirfd(d), de->d_name, &st, AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW) < 0) { - if (errno == ENOENT) - continue; - - log_warning_errno(errno, "Failed to stat /var/lib/systemd/coredump/%s: %m", de->d_name); - continue; - } - - if (!S_ISREG(st.st_mode)) - continue; - - if (exclude_fd >= 0 && - exclude_st.st_dev == st.st_dev && - exclude_st.st_ino == st.st_ino) - continue; - - r = hashmap_ensure_allocated(&h, NULL); - if (r < 0) - return log_oom(); - - t = timespec_load(&st.st_mtim); - - c = hashmap_get(h, UID_TO_PTR(uid)); - if (c) { - - if (t < c->oldest_mtime) { - char *n; - - n = strdup(de->d_name); - if (!n) - return log_oom(); - - free(c->oldest_file); - c->oldest_file = n; - c->oldest_mtime = t; - } - - } else { - _cleanup_(vacuum_candidate_freep) struct vacuum_candidate *n = NULL; - - n = new0(struct vacuum_candidate, 1); - if (!n) - return log_oom(); - - n->oldest_file = strdup(de->d_name); - if (!n->oldest_file) - return log_oom(); - - n->oldest_mtime = t; - - r = hashmap_put(h, UID_TO_PTR(uid), n); - if (r < 0) - return log_oom(); - - c = n; - n = NULL; - } - - c->n_files++; - - if (!worst || - worst->n_files < c->n_files || - (worst->n_files == c->n_files && c->oldest_mtime < worst->oldest_mtime)) - worst = c; - - sum += st.st_blocks * 512; - } - - if (!worst) - break; - - r = vacuum_necessary(dirfd(d), sum, keep_free, max_use); - if (r <= 0) - return r; - - if (unlinkat(dirfd(d), worst->oldest_file, 0) < 0) { - - if (errno == ENOENT) - continue; - - return log_error_errno(errno, "Failed to remove file %s: %m", worst->oldest_file); - } else - log_info("Removed old coredump %s.", worst->oldest_file); - } - - return 0; - -fail: - return log_error_errno(errno, "Failed to read directory: %m"); -} diff --git a/src/coredump/coredump-vacuum.h b/src/coredump/coredump-vacuum.h deleted file mode 100644 index 4b7b9f2d98..0000000000 --- a/src/coredump/coredump-vacuum.h +++ /dev/null @@ -1,25 +0,0 @@ -#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 -#include - -int coredump_vacuum(int exclude_fd, uint64_t keep_free, uint64_t max_use); diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c deleted file mode 100644 index 999de63900..0000000000 --- a/src/coredump/coredump.c +++ /dev/null @@ -1,1149 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 -#include -#include -#include - -#ifdef HAVE_ELFUTILS -#include -#include -#endif - -#include "sd-journal.h" -#include "sd-login.h" -#include "sd-daemon.h" - -#include "acl-util.h" -#include "alloc-util.h" -#include "capability-util.h" -#include "cgroup-util.h" -#include "compress.h" -#include "conf-parser.h" -#include "copy.h" -#include "coredump-vacuum.h" -#include "dirent-util.h" -#include "escape.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "io-util.h" -#include "journald-native.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "parse-util.h" -#include "process-util.h" -#include "socket-util.h" -#include "special.h" -#include "stacktrace.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "user-util.h" -#include "util.h" - -/* The maximum size up to which we process coredumps */ -#define PROCESS_SIZE_MAX ((uint64_t) (2LLU*1024LLU*1024LLU*1024LLU)) - -/* The maximum size up to which we leave the coredump around on disk */ -#define EXTERNAL_SIZE_MAX PROCESS_SIZE_MAX - -/* The maximum size up to which we store the coredump in the journal */ -#define JOURNAL_SIZE_MAX ((size_t) (767LU*1024LU*1024LU)) - -/* Make sure to not make this larger than the maximum journal entry - * size. See DATA_SIZE_MAX in journald-native.c. */ -assert_cc(JOURNAL_SIZE_MAX <= DATA_SIZE_MAX); - -enum { - /* We use this as array indexes for a couple of special fields we use for naming coredumping files, and - * attaching xattrs */ - CONTEXT_PID, - CONTEXT_UID, - CONTEXT_GID, - CONTEXT_SIGNAL, - CONTEXT_TIMESTAMP, - CONTEXT_RLIMIT, - CONTEXT_COMM, - CONTEXT_EXE, - _CONTEXT_MAX -}; - -typedef enum CoredumpStorage { - COREDUMP_STORAGE_NONE, - COREDUMP_STORAGE_EXTERNAL, - COREDUMP_STORAGE_JOURNAL, - COREDUMP_STORAGE_BOTH, - _COREDUMP_STORAGE_MAX, - _COREDUMP_STORAGE_INVALID = -1 -} CoredumpStorage; - -static const char* const coredump_storage_table[_COREDUMP_STORAGE_MAX] = { - [COREDUMP_STORAGE_NONE] = "none", - [COREDUMP_STORAGE_EXTERNAL] = "external", - [COREDUMP_STORAGE_JOURNAL] = "journal", - [COREDUMP_STORAGE_BOTH] = "both", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP(coredump_storage, CoredumpStorage); -static DEFINE_CONFIG_PARSE_ENUM(config_parse_coredump_storage, coredump_storage, CoredumpStorage, "Failed to parse storage setting"); - -static CoredumpStorage arg_storage = COREDUMP_STORAGE_EXTERNAL; -static bool arg_compress = true; -static uint64_t arg_process_size_max = PROCESS_SIZE_MAX; -static uint64_t arg_external_size_max = EXTERNAL_SIZE_MAX; -static size_t arg_journal_size_max = JOURNAL_SIZE_MAX; -static uint64_t arg_keep_free = (uint64_t) -1; -static uint64_t arg_max_use = (uint64_t) -1; - -static int parse_config(void) { - static const ConfigTableItem items[] = { - { "Coredump", "Storage", config_parse_coredump_storage, 0, &arg_storage }, - { "Coredump", "Compress", config_parse_bool, 0, &arg_compress }, - { "Coredump", "ProcessSizeMax", config_parse_iec_uint64, 0, &arg_process_size_max }, - { "Coredump", "ExternalSizeMax", config_parse_iec_uint64, 0, &arg_external_size_max }, - { "Coredump", "JournalSizeMax", config_parse_iec_size, 0, &arg_journal_size_max }, - { "Coredump", "KeepFree", config_parse_iec_uint64, 0, &arg_keep_free }, - { "Coredump", "MaxUse", config_parse_iec_uint64, 0, &arg_max_use }, - {} - }; - - return config_parse_many(PKGSYSCONFDIR "/coredump.conf", - CONF_PATHS_NULSTR("systemd/coredump.conf.d"), - "Coredump\0", - config_item_table_lookup, items, - false, NULL); -} - -static int fix_acl(int fd, uid_t uid) { - -#ifdef HAVE_ACL - _cleanup_(acl_freep) acl_t acl = NULL; - acl_entry_t entry; - acl_permset_t permset; - int r; - - assert(fd >= 0); - - if (uid <= SYSTEM_UID_MAX) - return 0; - - /* Make sure normal users can read (but not write or delete) - * their own coredumps */ - - acl = acl_get_fd(fd); - if (!acl) - return log_error_errno(errno, "Failed to get ACL: %m"); - - if (acl_create_entry(&acl, &entry) < 0 || - acl_set_tag_type(entry, ACL_USER) < 0 || - acl_set_qualifier(entry, &uid) < 0) { - log_error_errno(errno, "Failed to patch ACL: %m"); - return -errno; - } - - if (acl_get_permset(entry, &permset) < 0 || - acl_add_perm(permset, ACL_READ) < 0) - return log_warning_errno(errno, "Failed to patch ACL: %m"); - - r = calc_acl_mask_if_needed(&acl); - if (r < 0) - return log_warning_errno(r, "Failed to patch ACL: %m"); - - if (acl_set_fd(fd, acl) < 0) - return log_error_errno(errno, "Failed to apply ACL: %m"); -#endif - - return 0; -} - -static int fix_xattr(int fd, const char *context[_CONTEXT_MAX]) { - - static const char * const xattrs[_CONTEXT_MAX] = { - [CONTEXT_PID] = "user.coredump.pid", - [CONTEXT_UID] = "user.coredump.uid", - [CONTEXT_GID] = "user.coredump.gid", - [CONTEXT_SIGNAL] = "user.coredump.signal", - [CONTEXT_TIMESTAMP] = "user.coredump.timestamp", - [CONTEXT_COMM] = "user.coredump.comm", - [CONTEXT_EXE] = "user.coredump.exe", - }; - - int r = 0; - unsigned i; - - assert(fd >= 0); - - /* Attach some metadata to coredumps via extended - * attributes. Just because we can. */ - - for (i = 0; i < _CONTEXT_MAX; i++) { - int k; - - if (isempty(context[i]) || !xattrs[i]) - continue; - - k = fsetxattr(fd, xattrs[i], context[i], strlen(context[i]), XATTR_CREATE); - if (k < 0 && r == 0) - r = -errno; - } - - return r; -} - -#define filename_escape(s) xescape((s), "./ ") - -static inline const char *coredump_tmpfile_name(const char *s) { - return s ? s : "(unnamed temporary file)"; -} - -static int fix_permissions( - int fd, - const char *filename, - const char *target, - const char *context[_CONTEXT_MAX], - uid_t uid) { - - int r; - - assert(fd >= 0); - assert(target); - assert(context); - - /* Ignore errors on these */ - (void) fchmod(fd, 0640); - (void) fix_acl(fd, uid); - (void) fix_xattr(fd, context); - - if (fsync(fd) < 0) - return log_error_errno(errno, "Failed to sync coredump %s: %m", coredump_tmpfile_name(filename)); - - r = link_tmpfile(fd, filename, target); - if (r < 0) - return log_error_errno(r, "Failed to move coredump %s into place: %m", target); - - return 0; -} - -static int maybe_remove_external_coredump(const char *filename, uint64_t size) { - - /* Returns 1 if might remove, 0 if will not remove, < 0 on error. */ - - if (IN_SET(arg_storage, COREDUMP_STORAGE_EXTERNAL, COREDUMP_STORAGE_BOTH) && - size <= arg_external_size_max) - return 0; - - if (!filename) - return 1; - - if (unlink(filename) < 0 && errno != ENOENT) - return log_error_errno(errno, "Failed to unlink %s: %m", filename); - - return 1; -} - -static int make_filename(const char *context[_CONTEXT_MAX], char **ret) { - _cleanup_free_ char *c = NULL, *u = NULL, *p = NULL, *t = NULL; - sd_id128_t boot = {}; - int r; - - assert(context); - - c = filename_escape(context[CONTEXT_COMM]); - if (!c) - return -ENOMEM; - - u = filename_escape(context[CONTEXT_UID]); - if (!u) - return -ENOMEM; - - r = sd_id128_get_boot(&boot); - if (r < 0) - return r; - - p = filename_escape(context[CONTEXT_PID]); - if (!p) - return -ENOMEM; - - t = filename_escape(context[CONTEXT_TIMESTAMP]); - if (!t) - return -ENOMEM; - - if (asprintf(ret, - "/var/lib/systemd/coredump/core.%s.%s." SD_ID128_FORMAT_STR ".%s.%s000000", - c, - u, - SD_ID128_FORMAT_VAL(boot), - p, - t) < 0) - return -ENOMEM; - - return 0; -} - -static int save_external_coredump( - const char *context[_CONTEXT_MAX], - int input_fd, - char **ret_filename, - int *ret_node_fd, - int *ret_data_fd, - uint64_t *ret_size) { - - _cleanup_free_ char *fn = NULL, *tmp = NULL; - _cleanup_close_ int fd = -1; - uint64_t rlimit, max_size; - struct stat st; - uid_t uid; - int r; - - assert(context); - assert(ret_filename); - assert(ret_node_fd); - assert(ret_data_fd); - assert(ret_size); - - r = parse_uid(context[CONTEXT_UID], &uid); - if (r < 0) - return log_error_errno(r, "Failed to parse UID: %m"); - - r = safe_atou64(context[CONTEXT_RLIMIT], &rlimit); - if (r < 0) - return log_error_errno(r, "Failed to parse resource limit: %s", context[CONTEXT_RLIMIT]); - if (rlimit <= 0) { - /* Is coredumping disabled? Then don't bother saving/processing the coredump */ - log_info("Core Dumping has been disabled for process %s (%s).", context[CONTEXT_PID], context[CONTEXT_COMM]); - return -EBADSLT; - } - - /* Never store more than the process configured, or than we actually shall keep or process */ - max_size = MIN(rlimit, MAX(arg_process_size_max, arg_external_size_max)); - - r = make_filename(context, &fn); - if (r < 0) - return log_error_errno(r, "Failed to determine coredump file name: %m"); - - mkdir_p_label("/var/lib/systemd/coredump", 0755); - - fd = open_tmpfile_linkable(fn, O_RDWR|O_CLOEXEC, &tmp); - if (fd < 0) - return log_error_errno(fd, "Failed to create temporary file for coredump %s: %m", fn); - - r = copy_bytes(input_fd, fd, max_size, false); - if (r == -EFBIG) { - log_error("Coredump of %s (%s) is larger than configured processing limit, refusing.", context[CONTEXT_PID], context[CONTEXT_COMM]); - goto fail; - } else if (IN_SET(r, -EDQUOT, -ENOSPC)) { - log_error("Not enough disk space for coredump of %s (%s), refusing.", context[CONTEXT_PID], context[CONTEXT_COMM]); - goto fail; - } else if (r < 0) { - log_error_errno(r, "Failed to dump coredump to file: %m"); - goto fail; - } - - if (fstat(fd, &st) < 0) { - log_error_errno(errno, "Failed to fstat coredump %s: %m", coredump_tmpfile_name(tmp)); - goto fail; - } - - if (lseek(fd, 0, SEEK_SET) == (off_t) -1) { - log_error_errno(errno, "Failed to seek on %s: %m", coredump_tmpfile_name(tmp)); - goto fail; - } - -#if defined(HAVE_XZ) || defined(HAVE_LZ4) - /* If we will remove the coredump anyway, do not compress. */ - if (maybe_remove_external_coredump(NULL, st.st_size) == 0 - && arg_compress) { - - _cleanup_free_ char *fn_compressed = NULL, *tmp_compressed = NULL; - _cleanup_close_ int fd_compressed = -1; - - fn_compressed = strappend(fn, COMPRESSED_EXT); - if (!fn_compressed) { - log_oom(); - goto uncompressed; - } - - fd_compressed = open_tmpfile_linkable(fn_compressed, O_RDWR|O_CLOEXEC, &tmp_compressed); - if (fd_compressed < 0) { - log_error_errno(fd_compressed, "Failed to create temporary file for coredump %s: %m", fn_compressed); - goto uncompressed; - } - - r = compress_stream(fd, fd_compressed, -1); - if (r < 0) { - log_error_errno(r, "Failed to compress %s: %m", coredump_tmpfile_name(tmp_compressed)); - goto fail_compressed; - } - - r = fix_permissions(fd_compressed, tmp_compressed, fn_compressed, context, uid); - if (r < 0) - goto fail_compressed; - - /* OK, this worked, we can get rid of the uncompressed version now */ - if (tmp) - unlink_noerrno(tmp); - - *ret_filename = fn_compressed; /* compressed */ - *ret_node_fd = fd_compressed; /* compressed */ - *ret_data_fd = fd; /* uncompressed */ - *ret_size = (uint64_t) st.st_size; /* uncompressed */ - - fn_compressed = NULL; - fd = fd_compressed = -1; - - return 0; - - fail_compressed: - if (tmp_compressed) - (void) unlink(tmp_compressed); - } - -uncompressed: -#endif - - r = fix_permissions(fd, tmp, fn, context, uid); - if (r < 0) - goto fail; - - *ret_filename = fn; - *ret_data_fd = fd; - *ret_node_fd = -1; - *ret_size = (uint64_t) st.st_size; - - fn = NULL; - fd = -1; - - return 0; - -fail: - if (tmp) - (void) unlink(tmp); - return r; -} - -static int allocate_journal_field(int fd, size_t size, char **ret, size_t *ret_size) { - _cleanup_free_ char *field = NULL; - ssize_t n; - - assert(fd >= 0); - assert(ret); - assert(ret_size); - - if (lseek(fd, 0, SEEK_SET) == (off_t) -1) - return log_warning_errno(errno, "Failed to seek: %m"); - - field = malloc(9 + size); - if (!field) { - log_warning("Failed to allocate memory for coredump, coredump will not be stored."); - return -ENOMEM; - } - - memcpy(field, "COREDUMP=", 9); - - n = read(fd, field + 9, size); - if (n < 0) - return log_error_errno((int) n, "Failed to read core data: %m"); - if ((size_t) n < size) { - log_error("Core data too short."); - return -EIO; - } - - *ret = field; - *ret_size = size + 9; - - field = NULL; - - return 0; -} - -/* Joins /proc/[pid]/fd/ and /proc/[pid]/fdinfo/ into the following lines: - * 0:/dev/pts/23 - * pos: 0 - * flags: 0100002 - * - * 1:/dev/pts/23 - * pos: 0 - * flags: 0100002 - * - * 2:/dev/pts/23 - * pos: 0 - * flags: 0100002 - * EOF - */ -static int compose_open_fds(pid_t pid, char **open_fds) { - _cleanup_closedir_ DIR *proc_fd_dir = NULL; - _cleanup_close_ int proc_fdinfo_fd = -1; - _cleanup_free_ char *buffer = NULL; - _cleanup_fclose_ FILE *stream = NULL; - const char *fddelim = "", *path; - struct dirent *dent = NULL; - size_t size = 0; - int r = 0; - - assert(pid >= 0); - assert(open_fds != NULL); - - path = procfs_file_alloca(pid, "fd"); - proc_fd_dir = opendir(path); - if (!proc_fd_dir) - return -errno; - - proc_fdinfo_fd = openat(dirfd(proc_fd_dir), "../fdinfo", O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC|O_PATH); - if (proc_fdinfo_fd < 0) - return -errno; - - stream = open_memstream(&buffer, &size); - if (!stream) - return -ENOMEM; - - FOREACH_DIRENT(dent, proc_fd_dir, return -errno) { - _cleanup_fclose_ FILE *fdinfo = NULL; - _cleanup_free_ char *fdname = NULL; - char line[LINE_MAX]; - int fd; - - r = readlinkat_malloc(dirfd(proc_fd_dir), dent->d_name, &fdname); - if (r < 0) - return r; - - fprintf(stream, "%s%s:%s\n", fddelim, dent->d_name, fdname); - fddelim = "\n"; - - /* Use the directory entry from /proc/[pid]/fd with /proc/[pid]/fdinfo */ - fd = openat(proc_fdinfo_fd, dent->d_name, O_NOFOLLOW|O_CLOEXEC|O_RDONLY); - if (fd < 0) - continue; - - fdinfo = fdopen(fd, "re"); - if (fdinfo == NULL) { - close(fd); - continue; - } - - FOREACH_LINE(line, fdinfo, break) { - fputs(line, stream); - if (!endswith(line, "\n")) - fputc('\n', stream); - } - } - - errno = 0; - stream = safe_fclose(stream); - - if (errno > 0) - return -errno; - - *open_fds = buffer; - buffer = NULL; - - return 0; -} - -static int change_uid_gid(const char *context[]) { - uid_t uid; - gid_t gid; - int r; - - r = parse_uid(context[CONTEXT_UID], &uid); - if (r < 0) - return r; - - if (uid <= SYSTEM_UID_MAX) { - const char *user = "systemd-coredump"; - - r = get_user_creds(&user, &uid, &gid, NULL, NULL); - if (r < 0) { - log_warning_errno(r, "Cannot resolve %s user. Proceeding to dump core as root: %m", user); - uid = gid = 0; - } - } else { - r = parse_gid(context[CONTEXT_GID], &gid); - if (r < 0) - return r; - } - - return drop_privileges(uid, gid, 0); -} - -static int submit_coredump( - const char *context[_CONTEXT_MAX], - struct iovec *iovec, - size_t n_iovec_allocated, - size_t n_iovec, - int input_fd) { - - _cleanup_close_ int coredump_fd = -1, coredump_node_fd = -1; - _cleanup_free_ char *core_message = NULL, *filename = NULL, *coredump_data = NULL; - uint64_t coredump_size; - int r; - - assert(context); - assert(iovec); - assert(n_iovec_allocated >= n_iovec + 3); - assert(input_fd >= 0); - - /* Vacuum before we write anything again */ - (void) coredump_vacuum(-1, arg_keep_free, arg_max_use); - - /* Always stream the coredump to disk, if that's possible */ - r = save_external_coredump(context, input_fd, &filename, &coredump_node_fd, &coredump_fd, &coredump_size); - if (r < 0) - /* Skip whole core dumping part */ - goto log; - - /* If we don't want to keep the coredump on disk, remove it now, as later on we will lack the privileges for - * it. However, we keep the fd to it, so that we can still process it and log it. */ - r = maybe_remove_external_coredump(filename, coredump_size); - if (r < 0) - return r; - if (r == 0) { - const char *coredump_filename; - - coredump_filename = strjoina("COREDUMP_FILENAME=", filename); - IOVEC_SET_STRING(iovec[n_iovec++], coredump_filename); - } - - /* Vacuum again, but exclude the coredump we just created */ - (void) coredump_vacuum(coredump_node_fd >= 0 ? coredump_node_fd : coredump_fd, arg_keep_free, arg_max_use); - - /* Now, let's drop privileges to become the user who owns the segfaulted process and allocate the coredump - * memory under the user's uid. This also ensures that the credentials journald will see are the ones of the - * coredumping user, thus making sure the user gets access to the core dump. Let's also get rid of all - * capabilities, if we run as root, we won't need them anymore. */ - r = change_uid_gid(context); - if (r < 0) - return log_error_errno(r, "Failed to drop privileges: %m"); - -#ifdef HAVE_ELFUTILS - /* Try to get a strack trace if we can */ - if (coredump_size <= arg_process_size_max) { - _cleanup_free_ char *stacktrace = NULL; - - r = coredump_make_stack_trace(coredump_fd, context[CONTEXT_EXE], &stacktrace); - if (r >= 0) - core_message = strjoin("MESSAGE=Process ", context[CONTEXT_PID], " (", context[CONTEXT_COMM], ") of user ", context[CONTEXT_UID], " dumped core.\n\n", stacktrace, NULL); - else if (r == -EINVAL) - log_warning("Failed to generate stack trace: %s", dwfl_errmsg(dwfl_errno())); - else - log_warning_errno(r, "Failed to generate stack trace: %m"); - } - - if (!core_message) -#endif -log: - core_message = strjoin("MESSAGE=Process ", context[CONTEXT_PID], " (", context[CONTEXT_COMM], ") of user ", context[CONTEXT_UID], " dumped core.", NULL); - if (core_message) - IOVEC_SET_STRING(iovec[n_iovec++], core_message); - - /* Optionally store the entire coredump in the journal */ - if (IN_SET(arg_storage, COREDUMP_STORAGE_JOURNAL, COREDUMP_STORAGE_BOTH) && - coredump_size <= arg_journal_size_max) { - size_t sz = 0; - - /* Store the coredump itself in the journal */ - - r = allocate_journal_field(coredump_fd, (size_t) coredump_size, &coredump_data, &sz); - if (r >= 0) { - iovec[n_iovec].iov_base = coredump_data; - iovec[n_iovec].iov_len = sz; - n_iovec++; - } - } - - assert(n_iovec <= n_iovec_allocated); - - r = sd_journal_sendv(iovec, n_iovec); - if (r < 0) - return log_error_errno(r, "Failed to log coredump: %m"); - - return 0; -} - -static void map_context_fields(const struct iovec *iovec, const char *context[]) { - - static const char * const context_field_names[_CONTEXT_MAX] = { - [CONTEXT_PID] = "COREDUMP_PID=", - [CONTEXT_UID] = "COREDUMP_UID=", - [CONTEXT_GID] = "COREDUMP_GID=", - [CONTEXT_SIGNAL] = "COREDUMP_SIGNAL=", - [CONTEXT_TIMESTAMP] = "COREDUMP_TIMESTAMP=", - [CONTEXT_COMM] = "COREDUMP_COMM=", - [CONTEXT_EXE] = "COREDUMP_EXE=", - [CONTEXT_RLIMIT] = "COREDUMP_RLIMIT=", - }; - - unsigned i; - - assert(iovec); - assert(context); - - for (i = 0; i < _CONTEXT_MAX; i++) { - size_t l; - - l = strlen(context_field_names[i]); - if (iovec->iov_len < l) - continue; - - if (memcmp(iovec->iov_base, context_field_names[i], l) != 0) - continue; - - /* Note that these strings are NUL terminated, because we made sure that a trailing NUL byte is in the - * buffer, though not included in the iov_len count. (see below) */ - context[i] = (char*) iovec->iov_base + l; - break; - } -} - -static int process_socket(int fd) { - _cleanup_close_ int coredump_fd = -1; - struct iovec *iovec = NULL; - size_t n_iovec = 0, n_iovec_allocated = 0, i; - const char *context[_CONTEXT_MAX] = {}; - int r; - - assert(fd >= 0); - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - for (;;) { - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(int))]; - } control = {}; - struct msghdr mh = { - .msg_control = &control, - .msg_controllen = sizeof(control), - .msg_iovlen = 1, - }; - ssize_t n; - ssize_t l; - - if (!GREEDY_REALLOC(iovec, n_iovec_allocated, n_iovec + 3)) { - r = log_oom(); - goto finish; - } - - l = next_datagram_size_fd(fd); - if (l < 0) { - r = log_error_errno(l, "Failed to determine datagram size to read: %m"); - goto finish; - } - - assert(l >= 0); - - iovec[n_iovec].iov_len = l; - iovec[n_iovec].iov_base = malloc(l + 1); - - if (!iovec[n_iovec].iov_base) { - r = log_oom(); - goto finish; - } - - mh.msg_iov = iovec + n_iovec; - - n = recvmsg(fd, &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); - if (n < 0) { - free(iovec[n_iovec].iov_base); - r = log_error_errno(errno, "Failed to receive datagram: %m"); - goto finish; - } - - if (n == 0) { - struct cmsghdr *cmsg, *found = NULL; - /* The final zero-length datagram carries the file descriptor and tells us that we're done. */ - - free(iovec[n_iovec].iov_base); - - CMSG_FOREACH(cmsg, &mh) { - if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SCM_RIGHTS && - cmsg->cmsg_len == CMSG_LEN(sizeof(int))) { - assert(!found); - found = cmsg; - } - } - - if (!found) { - log_error("Coredump file descriptor missing."); - r = -EBADMSG; - goto finish; - } - - assert(coredump_fd < 0); - coredump_fd = *(int*) CMSG_DATA(found); - break; - } - - /* Add trailing NUL byte, in case these are strings */ - ((char*) iovec[n_iovec].iov_base)[n] = 0; - iovec[n_iovec].iov_len = (size_t) n; - - cmsg_close_all(&mh); - map_context_fields(iovec + n_iovec, context); - n_iovec++; - } - - if (!GREEDY_REALLOC(iovec, n_iovec_allocated, n_iovec + 3)) { - r = log_oom(); - goto finish; - } - - /* Make sure we we got all data we really need */ - assert(context[CONTEXT_PID]); - assert(context[CONTEXT_UID]); - assert(context[CONTEXT_GID]); - assert(context[CONTEXT_SIGNAL]); - assert(context[CONTEXT_TIMESTAMP]); - assert(context[CONTEXT_RLIMIT]); - assert(context[CONTEXT_COMM]); - assert(coredump_fd >= 0); - - r = submit_coredump(context, iovec, n_iovec_allocated, n_iovec, coredump_fd); - -finish: - for (i = 0; i < n_iovec; i++) - free(iovec[i].iov_base); - free(iovec); - - return r; -} - -static int send_iovec(const struct iovec iovec[], size_t n_iovec, int input_fd) { - - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/coredump", - }; - _cleanup_close_ int fd = -1; - size_t i; - int r; - - assert(iovec || n_iovec <= 0); - assert(input_fd >= 0); - - fd = socket(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0); - if (fd < 0) - return log_error_errno(errno, "Failed to create coredump socket: %m"); - - if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) - return log_error_errno(errno, "Failed to connect to coredump service: %m"); - - for (i = 0; i < n_iovec; i++) { - ssize_t n; - assert(iovec[i].iov_len > 0); - - n = send(fd, iovec[i].iov_base, iovec[i].iov_len, MSG_NOSIGNAL); - if (n < 0) - return log_error_errno(errno, "Failed to send coredump datagram: %m"); - } - - r = send_one_fd(fd, input_fd, 0); - if (r < 0) - return log_error_errno(r, "Failed to send coredump fd: %m"); - - return 0; -} - -static int process_journald_crash(const char *context[], int input_fd) { - _cleanup_close_ int coredump_fd = -1, coredump_node_fd = -1; - _cleanup_free_ char *filename = NULL; - uint64_t coredump_size; - int r; - - assert(context); - assert(input_fd >= 0); - - /* If we are journald, we cut things short, don't write to the journal, but still create a coredump. */ - - if (arg_storage != COREDUMP_STORAGE_NONE) - arg_storage = COREDUMP_STORAGE_EXTERNAL; - - r = save_external_coredump(context, input_fd, &filename, &coredump_node_fd, &coredump_fd, &coredump_size); - if (r < 0) - return r; - - r = maybe_remove_external_coredump(filename, coredump_size); - if (r < 0) - return r; - - log_info("Detected coredump of the journal daemon itself, diverted to %s.", filename); - return 0; -} - -static int process_kernel(int argc, char* argv[]) { - - /* The small core field we allocate on the stack, to keep things simple */ - char - *core_pid = NULL, *core_uid = NULL, *core_gid = NULL, *core_signal = NULL, - *core_session = NULL, *core_exe = NULL, *core_comm = NULL, *core_cmdline = NULL, - *core_cgroup = NULL, *core_cwd = NULL, *core_root = NULL, *core_unit = NULL, - *core_user_unit = NULL, *core_slice = NULL, *core_timestamp = NULL, *core_rlimit = NULL; - - /* The larger ones we allocate on the heap */ - _cleanup_free_ char - *core_owner_uid = NULL, *core_open_fds = NULL, *core_proc_status = NULL, - *core_proc_maps = NULL, *core_proc_limits = NULL, *core_proc_cgroup = NULL, *core_environ = NULL; - - _cleanup_free_ char *exe = NULL, *comm = NULL; - const char *context[_CONTEXT_MAX]; - struct iovec iovec[25]; - size_t n_iovec = 0; - uid_t owner_uid; - const char *p; - pid_t pid; - char *t; - int r; - - if (argc < CONTEXT_COMM + 1) { - log_error("Not enough arguments passed from kernel (%i, expected %i).", argc - 1, CONTEXT_COMM + 1 - 1); - return -EINVAL; - } - - r = parse_pid(argv[CONTEXT_PID + 1], &pid); - if (r < 0) - return log_error_errno(r, "Failed to parse PID."); - - r = get_process_comm(pid, &comm); - if (r < 0) { - log_warning_errno(r, "Failed to get COMM, falling back to the command line: %m"); - comm = strv_join(argv + CONTEXT_COMM + 1, " "); - if (!comm) - return log_oom(); - } - - r = get_process_exe(pid, &exe); - if (r < 0) - log_warning_errno(r, "Failed to get EXE, ignoring: %m"); - - context[CONTEXT_PID] = argv[CONTEXT_PID + 1]; - context[CONTEXT_UID] = argv[CONTEXT_UID + 1]; - context[CONTEXT_GID] = argv[CONTEXT_GID + 1]; - context[CONTEXT_SIGNAL] = argv[CONTEXT_SIGNAL + 1]; - context[CONTEXT_TIMESTAMP] = argv[CONTEXT_TIMESTAMP + 1]; - context[CONTEXT_RLIMIT] = argv[CONTEXT_RLIMIT + 1]; - context[CONTEXT_COMM] = comm; - context[CONTEXT_EXE] = exe; - - if (cg_pid_get_unit(pid, &t) >= 0) { - - if (streq(t, SPECIAL_JOURNALD_SERVICE)) { - free(t); - return process_journald_crash(context, STDIN_FILENO); - } - - core_unit = strjoina("COREDUMP_UNIT=", t); - free(t); - - IOVEC_SET_STRING(iovec[n_iovec++], core_unit); - } - - /* OK, now we know it's not the journal, hence we can make use of it now. */ - log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); - log_open(); - - if (cg_pid_get_user_unit(pid, &t) >= 0) { - core_user_unit = strjoina("COREDUMP_USER_UNIT=", t); - free(t); - - IOVEC_SET_STRING(iovec[n_iovec++], core_user_unit); - } - - core_pid = strjoina("COREDUMP_PID=", context[CONTEXT_PID]); - IOVEC_SET_STRING(iovec[n_iovec++], core_pid); - - core_uid = strjoina("COREDUMP_UID=", context[CONTEXT_UID]); - IOVEC_SET_STRING(iovec[n_iovec++], core_uid); - - core_gid = strjoina("COREDUMP_GID=", context[CONTEXT_GID]); - IOVEC_SET_STRING(iovec[n_iovec++], core_gid); - - core_signal = strjoina("COREDUMP_SIGNAL=", context[CONTEXT_SIGNAL]); - IOVEC_SET_STRING(iovec[n_iovec++], core_signal); - - core_rlimit = strjoina("COREDUMP_RLIMIT=", context[CONTEXT_RLIMIT]); - IOVEC_SET_STRING(iovec[n_iovec++], core_rlimit); - - if (sd_pid_get_session(pid, &t) >= 0) { - core_session = strjoina("COREDUMP_SESSION=", t); - free(t); - - IOVEC_SET_STRING(iovec[n_iovec++], core_session); - } - - if (sd_pid_get_owner_uid(pid, &owner_uid) >= 0) { - r = asprintf(&core_owner_uid, "COREDUMP_OWNER_UID=" UID_FMT, owner_uid); - if (r > 0) - IOVEC_SET_STRING(iovec[n_iovec++], core_owner_uid); - } - - if (sd_pid_get_slice(pid, &t) >= 0) { - core_slice = strjoina("COREDUMP_SLICE=", t); - free(t); - - IOVEC_SET_STRING(iovec[n_iovec++], core_slice); - } - - if (comm) { - core_comm = strjoina("COREDUMP_COMM=", comm); - IOVEC_SET_STRING(iovec[n_iovec++], core_comm); - } - - if (exe) { - core_exe = strjoina("COREDUMP_EXE=", exe); - IOVEC_SET_STRING(iovec[n_iovec++], core_exe); - } - - if (get_process_cmdline(pid, 0, false, &t) >= 0) { - core_cmdline = strjoina("COREDUMP_CMDLINE=", t); - free(t); - - IOVEC_SET_STRING(iovec[n_iovec++], core_cmdline); - } - - if (cg_pid_get_path_shifted(pid, NULL, &t) >= 0) { - core_cgroup = strjoina("COREDUMP_CGROUP=", t); - free(t); - - IOVEC_SET_STRING(iovec[n_iovec++], core_cgroup); - } - - if (compose_open_fds(pid, &t) >= 0) { - core_open_fds = strappend("COREDUMP_OPEN_FDS=", t); - free(t); - - if (core_open_fds) - IOVEC_SET_STRING(iovec[n_iovec++], core_open_fds); - } - - p = procfs_file_alloca(pid, "status"); - if (read_full_file(p, &t, NULL) >= 0) { - core_proc_status = strappend("COREDUMP_PROC_STATUS=", t); - free(t); - - if (core_proc_status) - IOVEC_SET_STRING(iovec[n_iovec++], core_proc_status); - } - - p = procfs_file_alloca(pid, "maps"); - if (read_full_file(p, &t, NULL) >= 0) { - core_proc_maps = strappend("COREDUMP_PROC_MAPS=", t); - free(t); - - if (core_proc_maps) - IOVEC_SET_STRING(iovec[n_iovec++], core_proc_maps); - } - - p = procfs_file_alloca(pid, "limits"); - if (read_full_file(p, &t, NULL) >= 0) { - core_proc_limits = strappend("COREDUMP_PROC_LIMITS=", t); - free(t); - - if (core_proc_limits) - IOVEC_SET_STRING(iovec[n_iovec++], core_proc_limits); - } - - p = procfs_file_alloca(pid, "cgroup"); - if (read_full_file(p, &t, NULL) >=0) { - core_proc_cgroup = strappend("COREDUMP_PROC_CGROUP=", t); - free(t); - - if (core_proc_cgroup) - IOVEC_SET_STRING(iovec[n_iovec++], core_proc_cgroup); - } - - if (get_process_cwd(pid, &t) >= 0) { - core_cwd = strjoina("COREDUMP_CWD=", t); - free(t); - - IOVEC_SET_STRING(iovec[n_iovec++], core_cwd); - } - - if (get_process_root(pid, &t) >= 0) { - core_root = strjoina("COREDUMP_ROOT=", t); - free(t); - - IOVEC_SET_STRING(iovec[n_iovec++], core_root); - } - - if (get_process_environ(pid, &t) >= 0) { - core_environ = strappend("COREDUMP_ENVIRON=", t); - free(t); - - if (core_environ) - IOVEC_SET_STRING(iovec[n_iovec++], core_environ); - } - - core_timestamp = strjoina("COREDUMP_TIMESTAMP=", context[CONTEXT_TIMESTAMP], "000000"); - IOVEC_SET_STRING(iovec[n_iovec++], core_timestamp); - - IOVEC_SET_STRING(iovec[n_iovec++], "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1"); - - assert_cc(2 == LOG_CRIT); - IOVEC_SET_STRING(iovec[n_iovec++], "PRIORITY=2"); - - assert(n_iovec <= ELEMENTSOF(iovec)); - - return send_iovec(iovec, n_iovec, STDIN_FILENO); -} - -int main(int argc, char *argv[]) { - int r; - - /* First, log to a safe place, since we don't know what crashed and it might be journald which we'd rather not - * log to then. */ - - log_set_target(LOG_TARGET_KMSG); - log_open(); - - /* Make sure we never enter a loop */ - (void) prctl(PR_SET_DUMPABLE, 0); - - /* Ignore all parse errors */ - (void) parse_config(); - - log_debug("Selected storage '%s'.", coredump_storage_to_string(arg_storage)); - log_debug("Selected compression %s.", yes_no(arg_compress)); - - r = sd_listen_fds(false); - if (r < 0) { - log_error_errno(r, "Failed to determine number of file descriptor: %m"); - goto finish; - } - - /* If we got an fd passed, we are running in coredumpd mode. Otherwise we are invoked from the kernel as - * coredump handler */ - if (r == 0) - r = process_kernel(argc, argv); - else if (r == 1) - r = process_socket(SD_LISTEN_FDS_START); - else { - log_error("Received unexpected number of file descriptors."); - r = -EINVAL; - } - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/coredump/coredump.conf b/src/coredump/coredump.conf deleted file mode 100644 index c2f0643e03..0000000000 --- a/src/coredump/coredump.conf +++ /dev/null @@ -1,21 +0,0 @@ -# This file is part of systemd. -# -# 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. -# -# Entries in this file show the compile time defaults. -# You can change settings by editing this file. -# Defaults can be restored by simply deleting this file. -# -# See coredump.conf(5) for details. - -[Coredump] -#Storage=external -#Compress=yes -#ProcessSizeMax=2G -#ExternalSizeMax=2G -#JournalSizeMax=767M -#MaxUse= -#KeepFree= diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c deleted file mode 100644 index 27b1e0fb3f..0000000000 --- a/src/coredump/coredumpctl.c +++ /dev/null @@ -1,878 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 Zbigniew Jędrzejewski-Szmek - - 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 -#include -#include -#include -#include - -#include "sd-journal.h" - -#include "alloc-util.h" -#include "compress.h" -#include "fd-util.h" -#include "fileio.h" -#include "journal-internal.h" -#include "log.h" -#include "macro.h" -#include "pager.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "set.h" -#include "sigbus.h" -#include "signal-util.h" -#include "string-util.h" -#include "terminal-util.h" -#include "user-util.h" -#include "util.h" - -static enum { - ACTION_NONE, - ACTION_INFO, - ACTION_LIST, - ACTION_DUMP, - ACTION_GDB, -} arg_action = ACTION_LIST; -static const char* arg_field = NULL; -static const char *arg_directory = NULL; -static bool arg_no_pager = false; -static int arg_no_legend = false; -static int arg_one = false; -static FILE* arg_output = NULL; - -static Set *new_matches(void) { - Set *set; - char *tmp; - int r; - - set = set_new(NULL); - if (!set) { - log_oom(); - return NULL; - } - - tmp = strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1"); - if (!tmp) { - log_oom(); - set_free(set); - return NULL; - } - - r = set_consume(set, tmp); - if (r < 0) { - log_error_errno(r, "failed to add to set: %m"); - set_free(set); - return NULL; - } - - return set; -} - -static int add_match(Set *set, const char *match) { - _cleanup_free_ char *p = NULL; - char *pattern = NULL; - const char* prefix; - pid_t pid; - int r; - - if (strchr(match, '=')) - prefix = ""; - else if (strchr(match, '/')) { - r = path_make_absolute_cwd(match, &p); - if (r < 0) - goto fail; - match = p; - prefix = "COREDUMP_EXE="; - } else if (parse_pid(match, &pid) >= 0) - prefix = "COREDUMP_PID="; - else - prefix = "COREDUMP_COMM="; - - pattern = strjoin(prefix, match, NULL); - if (!pattern) { - r = -ENOMEM; - goto fail; - } - - log_debug("Adding pattern: %s", pattern); - r = set_consume(set, pattern); - if (r < 0) - goto fail; - - return 0; -fail: - return log_error_errno(r, "Failed to add match: %m"); -} - -static void help(void) { - printf("%s [OPTIONS...]\n\n" - "List or retrieve coredumps from the journal.\n\n" - "Flags:\n" - " -h --help Show this help\n" - " --version Print version string\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not print the column headers.\n" - " -1 Show information about most recent entry only\n" - " -F --field=FIELD List all values a certain field takes\n" - " -o --output=FILE Write output to FILE\n\n" - " -D --directory=DIR Use journal files from directory\n\n" - - "Commands:\n" - " list [MATCHES...] List available coredumps (default)\n" - " info [MATCHES...] Show detailed information about one or more coredumps\n" - " dump [MATCHES...] Print first matching coredump to stdout\n" - " gdb [MATCHES...] Start gdb for the first matching coredump\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[], Set *matches) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - }; - - int r, c; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version" , no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "output", required_argument, NULL, 'o' }, - { "field", required_argument, NULL, 'F' }, - { "directory", required_argument, NULL, 'D' }, - {} - }; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "ho:F:1D:", options, NULL)) >= 0) - switch(c) { - - case 'h': - arg_action = ACTION_NONE; - help(); - return 0; - - case ARG_VERSION: - arg_action = ACTION_NONE; - return version(); - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case ARG_NO_LEGEND: - arg_no_legend = true; - break; - - case 'o': - if (arg_output) { - log_error("cannot set output more than once"); - return -EINVAL; - } - - arg_output = fopen(optarg, "we"); - if (!arg_output) - return log_error_errno(errno, "writing to '%s': %m", optarg); - - break; - - case 'F': - if (arg_field) { - log_error("cannot use --field/-F more than once"); - return -EINVAL; - } - arg_field = optarg; - break; - - case '1': - arg_one = true; - break; - - case 'D': - arg_directory = optarg; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (optind < argc) { - const char *cmd = argv[optind++]; - if (streq(cmd, "list")) - arg_action = ACTION_LIST; - else if (streq(cmd, "dump")) - arg_action = ACTION_DUMP; - else if (streq(cmd, "gdb")) - arg_action = ACTION_GDB; - else if (streq(cmd, "info")) - arg_action = ACTION_INFO; - else { - log_error("Unknown action '%s'", cmd); - return -EINVAL; - } - } - - if (arg_field && arg_action != ACTION_LIST) { - log_error("Option --field/-F only makes sense with list"); - return -EINVAL; - } - - while (optind < argc) { - r = add_match(matches, argv[optind]); - if (r != 0) - return r; - optind++; - } - - return 0; -} - -static int retrieve(const void *data, - size_t len, - const char *name, - char **var) { - - size_t ident; - char *v; - - ident = strlen(name) + 1; /* name + "=" */ - - if (len < ident) - return 0; - - if (memcmp(data, name, ident - 1) != 0) - return 0; - - if (((const char*) data)[ident - 1] != '=') - return 0; - - v = strndup((const char*)data + ident, len - ident); - if (!v) - return log_oom(); - - free(*var); - *var = v; - - return 0; -} - -static void print_field(FILE* file, sd_journal *j) { - _cleanup_free_ char *value = NULL; - const void *d; - size_t l; - - assert(file); - assert(j); - - assert(arg_field); - - SD_JOURNAL_FOREACH_DATA(j, d, l) - retrieve(d, l, arg_field, &value); - - if (value) - fprintf(file, "%s\n", value); -} - -static int print_list(FILE* file, sd_journal *j, int had_legend) { - _cleanup_free_ char - *pid = NULL, *uid = NULL, *gid = NULL, - *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL, - *filename = NULL; - const void *d; - size_t l; - usec_t t; - char buf[FORMAT_TIMESTAMP_MAX]; - int r; - bool present; - - assert(file); - assert(j); - - SD_JOURNAL_FOREACH_DATA(j, d, l) { - retrieve(d, l, "COREDUMP_PID", &pid); - retrieve(d, l, "COREDUMP_UID", &uid); - retrieve(d, l, "COREDUMP_GID", &gid); - retrieve(d, l, "COREDUMP_SIGNAL", &sgnl); - retrieve(d, l, "COREDUMP_EXE", &exe); - retrieve(d, l, "COREDUMP_COMM", &comm); - retrieve(d, l, "COREDUMP_CMDLINE", &cmdline); - retrieve(d, l, "COREDUMP_FILENAME", &filename); - } - - if (!pid && !uid && !gid && !sgnl && !exe && !comm && !cmdline && !filename) { - log_warning("Empty coredump log entry"); - return -EINVAL; - } - - r = sd_journal_get_realtime_usec(j, &t); - if (r < 0) - return log_error_errno(r, "Failed to get realtime timestamp: %m"); - - format_timestamp(buf, sizeof(buf), t); - present = filename && access(filename, F_OK) == 0; - - if (!had_legend && !arg_no_legend) - fprintf(file, "%-*s %*s %*s %*s %*s %*s %s\n", - FORMAT_TIMESTAMP_WIDTH, "TIME", - 6, "PID", - 5, "UID", - 5, "GID", - 3, "SIG", - 1, "PRESENT", - "EXE"); - - fprintf(file, "%-*s %*s %*s %*s %*s %*s %s\n", - FORMAT_TIMESTAMP_WIDTH, buf, - 6, strna(pid), - 5, strna(uid), - 5, strna(gid), - 3, strna(sgnl), - 1, present ? "*" : "", - strna(exe ?: (comm ?: cmdline))); - - return 0; -} - -static int print_info(FILE *file, sd_journal *j, bool need_space) { - _cleanup_free_ char - *pid = NULL, *uid = NULL, *gid = NULL, - *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL, - *unit = NULL, *user_unit = NULL, *session = NULL, - *boot_id = NULL, *machine_id = NULL, *hostname = NULL, - *slice = NULL, *cgroup = NULL, *owner_uid = NULL, - *message = NULL, *timestamp = NULL, *filename = NULL; - const void *d; - size_t l; - int r; - - assert(file); - assert(j); - - SD_JOURNAL_FOREACH_DATA(j, d, l) { - retrieve(d, l, "COREDUMP_PID", &pid); - retrieve(d, l, "COREDUMP_UID", &uid); - retrieve(d, l, "COREDUMP_GID", &gid); - retrieve(d, l, "COREDUMP_SIGNAL", &sgnl); - retrieve(d, l, "COREDUMP_EXE", &exe); - retrieve(d, l, "COREDUMP_COMM", &comm); - retrieve(d, l, "COREDUMP_CMDLINE", &cmdline); - retrieve(d, l, "COREDUMP_UNIT", &unit); - retrieve(d, l, "COREDUMP_USER_UNIT", &user_unit); - retrieve(d, l, "COREDUMP_SESSION", &session); - retrieve(d, l, "COREDUMP_OWNER_UID", &owner_uid); - retrieve(d, l, "COREDUMP_SLICE", &slice); - retrieve(d, l, "COREDUMP_CGROUP", &cgroup); - retrieve(d, l, "COREDUMP_TIMESTAMP", ×tamp); - retrieve(d, l, "COREDUMP_FILENAME", &filename); - retrieve(d, l, "_BOOT_ID", &boot_id); - retrieve(d, l, "_MACHINE_ID", &machine_id); - retrieve(d, l, "_HOSTNAME", &hostname); - retrieve(d, l, "MESSAGE", &message); - } - - if (need_space) - fputs("\n", file); - - if (comm) - fprintf(file, - " PID: %s%s%s (%s)\n", - ansi_highlight(), strna(pid), ansi_normal(), comm); - else - fprintf(file, - " PID: %s%s%s\n", - ansi_highlight(), strna(pid), ansi_normal()); - - if (uid) { - uid_t n; - - if (parse_uid(uid, &n) >= 0) { - _cleanup_free_ char *u = NULL; - - u = uid_to_name(n); - fprintf(file, - " UID: %s (%s)\n", - uid, u); - } else { - fprintf(file, - " UID: %s\n", - uid); - } - } - - if (gid) { - gid_t n; - - if (parse_gid(gid, &n) >= 0) { - _cleanup_free_ char *g = NULL; - - g = gid_to_name(n); - fprintf(file, - " GID: %s (%s)\n", - gid, g); - } else { - fprintf(file, - " GID: %s\n", - gid); - } - } - - if (sgnl) { - int sig; - - if (safe_atoi(sgnl, &sig) >= 0) - fprintf(file, " Signal: %s (%s)\n", sgnl, signal_to_string(sig)); - else - fprintf(file, " Signal: %s\n", sgnl); - } - - if (timestamp) { - usec_t u; - - r = safe_atou64(timestamp, &u); - if (r >= 0) { - char absolute[FORMAT_TIMESTAMP_MAX], relative[FORMAT_TIMESPAN_MAX]; - - fprintf(file, - " Timestamp: %s (%s)\n", - format_timestamp(absolute, sizeof(absolute), u), - format_timestamp_relative(relative, sizeof(relative), u)); - - } else - fprintf(file, " Timestamp: %s\n", timestamp); - } - - if (cmdline) - fprintf(file, " Command Line: %s\n", cmdline); - if (exe) - fprintf(file, " Executable: %s%s%s\n", ansi_highlight(), exe, ansi_normal()); - if (cgroup) - fprintf(file, " Control Group: %s\n", cgroup); - if (unit) - fprintf(file, " Unit: %s\n", unit); - if (user_unit) - fprintf(file, " User Unit: %s\n", unit); - if (slice) - fprintf(file, " Slice: %s\n", slice); - if (session) - fprintf(file, " Session: %s\n", session); - if (owner_uid) { - uid_t n; - - if (parse_uid(owner_uid, &n) >= 0) { - _cleanup_free_ char *u = NULL; - - u = uid_to_name(n); - fprintf(file, - " Owner UID: %s (%s)\n", - owner_uid, u); - } else { - fprintf(file, - " Owner UID: %s\n", - owner_uid); - } - } - if (boot_id) - fprintf(file, " Boot ID: %s\n", boot_id); - if (machine_id) - fprintf(file, " Machine ID: %s\n", machine_id); - if (hostname) - fprintf(file, " Hostname: %s\n", hostname); - - if (filename && access(filename, F_OK) == 0) - fprintf(file, " Coredump: %s\n", filename); - - if (message) { - _cleanup_free_ char *m = NULL; - - m = strreplace(message, "\n", "\n "); - - fprintf(file, " Message: %s\n", strstrip(m ?: message)); - } - - return 0; -} - -static int focus(sd_journal *j) { - int r; - - r = sd_journal_seek_tail(j); - if (r == 0) - r = sd_journal_previous(j); - if (r < 0) - return log_error_errno(r, "Failed to search journal: %m"); - if (r == 0) { - log_error("No match found."); - return -ESRCH; - } - return r; -} - -static void print_entry(sd_journal *j, unsigned n_found) { - assert(j); - - if (arg_action == ACTION_INFO) - print_info(stdout, j, n_found); - else if (arg_field) - print_field(stdout, j); - else - print_list(stdout, j, n_found); -} - -static int dump_list(sd_journal *j) { - unsigned n_found = 0; - int r; - - assert(j); - - /* The coredumps are likely to compressed, and for just - * listing them we don't need to decompress them, so let's - * pick a fairly low data threshold here */ - sd_journal_set_data_threshold(j, 4096); - - if (arg_one) { - r = focus(j); - if (r < 0) - return r; - - print_entry(j, 0); - } else { - SD_JOURNAL_FOREACH(j) - print_entry(j, n_found++); - - if (!arg_field && n_found <= 0) { - log_notice("No coredumps found."); - return -ESRCH; - } - } - - return 0; -} - -static int save_core(sd_journal *j, int fd, char **path, bool *unlink_temp) { - const char *data; - _cleanup_free_ char *filename = NULL; - size_t len; - int r; - - assert((fd >= 0) != !!path); - assert(!!path == !!unlink_temp); - - /* Prefer uncompressed file to journal (probably cached) to - * compressed file (probably uncached). */ - r = sd_journal_get_data(j, "COREDUMP_FILENAME", (const void**) &data, &len); - if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to retrieve COREDUMP_FILENAME: %m"); - else if (r == 0) - retrieve(data, len, "COREDUMP_FILENAME", &filename); - - if (filename && access(filename, R_OK) < 0) { - log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, - "File %s is not readable: %m", filename); - filename = mfree(filename); - } - - if (filename && !endswith(filename, ".xz") && !endswith(filename, ".lz4")) { - if (path) { - *path = filename; - filename = NULL; - } - - return 0; - } else { - _cleanup_close_ int fdt = -1; - char *temp = NULL; - - if (fd < 0) { - temp = strdup("/var/tmp/coredump-XXXXXX"); - if (!temp) - return log_oom(); - - fdt = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC); - if (fdt < 0) - return log_error_errno(fdt, "Failed to create temporary file: %m"); - log_debug("Created temporary file %s", temp); - - fd = fdt; - } - - r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len); - if (r == 0) { - ssize_t sz; - - assert(len >= 9); - data += 9; - len -= 9; - - sz = write(fdt, data, len); - if (sz < 0) { - r = log_error_errno(errno, - "Failed to write temporary file: %m"); - goto error; - } - if (sz != (ssize_t) len) { - log_error("Short write to temporary file."); - r = -EIO; - goto error; - } - } else if (filename) { -#if defined(HAVE_XZ) || defined(HAVE_LZ4) - _cleanup_close_ int fdf; - - fdf = open(filename, O_RDONLY | O_CLOEXEC); - if (fdf < 0) { - r = log_error_errno(errno, - "Failed to open %s: %m", - filename); - goto error; - } - - r = decompress_stream(filename, fdf, fd, -1); - if (r < 0) { - log_error_errno(r, "Failed to decompress %s: %m", filename); - goto error; - } -#else - log_error("Cannot decompress file. Compiled without compression support."); - r = -EOPNOTSUPP; - goto error; -#endif - } else { - if (r == -ENOENT) - log_error("Cannot retrieve coredump from journal or disk."); - else - log_error_errno(r, "Failed to retrieve COREDUMP field: %m"); - goto error; - } - - if (temp) { - *path = temp; - *unlink_temp = true; - } - - return 0; - -error: - if (temp) { - unlink(temp); - log_debug("Removed temporary file %s", temp); - } - return r; - } -} - -static int dump_core(sd_journal* j) { - int r; - - assert(j); - - r = focus(j); - if (r < 0) - return r; - - print_info(arg_output ? stdout : stderr, j, false); - - if (on_tty() && !arg_output) { - log_error("Refusing to dump core to tty."); - return -ENOTTY; - } - - r = save_core(j, arg_output ? fileno(arg_output) : STDOUT_FILENO, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Coredump retrieval failed: %m"); - - r = sd_journal_previous(j); - if (r >= 0) - log_warning("More than one entry matches, ignoring rest."); - - return 0; -} - -static int run_gdb(sd_journal *j) { - _cleanup_free_ char *exe = NULL, *path = NULL; - bool unlink_path = false; - const char *data; - siginfo_t st; - size_t len; - pid_t pid; - int r; - - assert(j); - - r = focus(j); - if (r < 0) - return r; - - print_info(stdout, j, false); - fputs("\n", stdout); - - r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len); - if (r < 0) - return log_error_errno(r, "Failed to retrieve COREDUMP_EXE field: %m"); - - assert(len > strlen("COREDUMP_EXE=")); - data += strlen("COREDUMP_EXE="); - len -= strlen("COREDUMP_EXE="); - - exe = strndup(data, len); - if (!exe) - return log_oom(); - - if (endswith(exe, " (deleted)")) { - log_error("Binary already deleted."); - return -ENOENT; - } - - if (!path_is_absolute(exe)) { - log_error("Binary is not an absolute path."); - return -ENOENT; - } - - r = save_core(j, -1, &path, &unlink_path); - if (r < 0) - return log_error_errno(r, "Failed to retrieve core: %m"); - - pid = fork(); - if (pid < 0) { - r = log_error_errno(errno, "Failed to fork(): %m"); - goto finish; - } - if (pid == 0) { - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - execlp("gdb", "gdb", exe, path, NULL); - - log_error_errno(errno, "Failed to invoke gdb: %m"); - _exit(1); - } - - r = wait_for_terminate(pid, &st); - if (r < 0) { - log_error_errno(r, "Failed to wait for gdb: %m"); - goto finish; - } - - r = st.si_code == CLD_EXITED ? st.si_status : 255; - -finish: - if (unlink_path) { - log_debug("Removed temporary file %s", path); - unlink(path); - } - - return r; -} - -int main(int argc, char *argv[]) { - _cleanup_(sd_journal_closep) sd_journal*j = NULL; - const char* match; - Iterator it; - int r = 0; - _cleanup_set_free_free_ Set *matches = NULL; - - setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); - - matches = new_matches(); - if (!matches) { - r = -ENOMEM; - goto end; - } - - r = parse_argv(argc, argv, matches); - if (r < 0) - goto end; - - if (arg_action == ACTION_NONE) - goto end; - - sigbus_install(); - - if (arg_directory) { - r = sd_journal_open_directory(&j, arg_directory, 0); - if (r < 0) { - log_error_errno(r, "Failed to open journals in directory: %s: %m", arg_directory); - goto end; - } - } else { - r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); - if (r < 0) { - log_error_errno(r, "Failed to open journal: %m"); - goto end; - } - } - - /* We want full data, nothing truncated. */ - sd_journal_set_data_threshold(j, 0); - - SET_FOREACH(match, matches, it) { - r = sd_journal_add_match(j, match, strlen(match)); - if (r != 0) { - log_error_errno(r, "Failed to add match '%s': %m", - match); - goto end; - } - } - - if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) { - _cleanup_free_ char *filter; - - filter = journal_make_match_string(j); - log_debug("Journal filter: %s", filter); - } - - switch(arg_action) { - - case ACTION_LIST: - case ACTION_INFO: - pager_open(arg_no_pager, false); - r = dump_list(j); - break; - - case ACTION_DUMP: - r = dump_core(j); - break; - - case ACTION_GDB: - r = run_gdb(j); - break; - - default: - assert_not_reached("Shouldn't be here"); - } - -end: - pager_close(); - - if (arg_output) - fclose(arg_output); - - return r >= 0 ? r : EXIT_FAILURE; -} diff --git a/src/coredump/stacktrace.c b/src/coredump/stacktrace.c deleted file mode 100644 index cc4dad9465..0000000000 --- a/src/coredump/stacktrace.c +++ /dev/null @@ -1,200 +0,0 @@ -/*** - 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 - -#include "alloc-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "macro.h" -#include "stacktrace.h" -#include "string-util.h" -#include "util.h" - -#define FRAMES_MAX 64 -#define THREADS_MAX 64 - -struct stack_context { - FILE *f; - Dwfl *dwfl; - Elf *elf; - unsigned n_thread; - unsigned n_frame; -}; - -static int frame_callback(Dwfl_Frame *frame, void *userdata) { - struct stack_context *c = userdata; - Dwarf_Addr pc, pc_adjusted, bias = 0; - _cleanup_free_ Dwarf_Die *scopes = NULL; - const char *fname = NULL, *symbol = NULL; - Dwfl_Module *module; - bool is_activation; - - assert(frame); - assert(c); - - if (c->n_frame >= FRAMES_MAX) - return DWARF_CB_ABORT; - - if (!dwfl_frame_pc(frame, &pc, &is_activation)) - return DWARF_CB_ABORT; - - pc_adjusted = pc - (is_activation ? 0 : 1); - - module = dwfl_addrmodule(c->dwfl, pc_adjusted); - if (module) { - Dwarf_Die *s, *cudie; - int n; - - cudie = dwfl_module_addrdie(module, pc_adjusted, &bias); - if (cudie) { - n = dwarf_getscopes(cudie, pc_adjusted - bias, &scopes); - for (s = scopes; s < scopes + n; s++) { - if (IN_SET(dwarf_tag(s), DW_TAG_subprogram, DW_TAG_inlined_subroutine, DW_TAG_entry_point)) { - Dwarf_Attribute *a, space; - - a = dwarf_attr_integrate(s, DW_AT_MIPS_linkage_name, &space); - if (!a) - a = dwarf_attr_integrate(s, DW_AT_linkage_name, &space); - if (a) - symbol = dwarf_formstring(a); - if (!symbol) - symbol = dwarf_diename(s); - - if (symbol) - break; - } - } - } - - if (!symbol) - symbol = dwfl_module_addrname(module, pc_adjusted); - - fname = dwfl_module_info(module, NULL, NULL, NULL, NULL, NULL, NULL, NULL); - } - - fprintf(c->f, "#%-2u 0x%016" PRIx64 " %s (%s)\n", c->n_frame, (uint64_t) pc, strna(symbol), strna(fname)); - c->n_frame++; - - return DWARF_CB_OK; -} - -static int thread_callback(Dwfl_Thread *thread, void *userdata) { - struct stack_context *c = userdata; - pid_t tid; - - assert(thread); - assert(c); - - if (c->n_thread >= THREADS_MAX) - return DWARF_CB_ABORT; - - if (c->n_thread != 0) - fputc('\n', c->f); - - c->n_frame = 0; - - tid = dwfl_thread_tid(thread); - fprintf(c->f, "Stack trace of thread " PID_FMT ":\n", tid); - - if (dwfl_thread_getframes(thread, frame_callback, c) < 0) - return DWARF_CB_ABORT; - - c->n_thread++; - - return DWARF_CB_OK; -} - -int coredump_make_stack_trace(int fd, const char *executable, char **ret) { - - static const Dwfl_Callbacks callbacks = { - .find_elf = dwfl_build_id_find_elf, - .find_debuginfo = dwfl_standard_find_debuginfo, - }; - - struct stack_context c = {}; - char *buf = NULL; - size_t sz = 0; - int r; - - assert(fd >= 0); - assert(ret); - - if (lseek(fd, 0, SEEK_SET) == (off_t) -1) - return -errno; - - c.f = open_memstream(&buf, &sz); - if (!c.f) - return -ENOMEM; - - elf_version(EV_CURRENT); - - c.elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); - if (!c.elf) { - r = -EINVAL; - goto finish; - } - - c.dwfl = dwfl_begin(&callbacks); - if (!c.dwfl) { - r = -EINVAL; - goto finish; - } - - if (dwfl_core_file_report(c.dwfl, c.elf, executable) < 0) { - r = -EINVAL; - goto finish; - } - - if (dwfl_report_end(c.dwfl, NULL, NULL) != 0) { - r = -EINVAL; - goto finish; - } - - if (dwfl_core_file_attach(c.dwfl, c.elf) < 0) { - r = -EINVAL; - goto finish; - } - - if (dwfl_getthreads(c.dwfl, thread_callback, &c) < 0) { - r = -EINVAL; - goto finish; - } - - c.f = safe_fclose(c.f); - - *ret = buf; - buf = NULL; - - r = 0; - -finish: - if (c.dwfl) - dwfl_end(c.dwfl); - - if (c.elf) - elf_end(c.elf); - - safe_fclose(c.f); - - free(buf); - - return r; -} diff --git a/src/coredump/stacktrace.h b/src/coredump/stacktrace.h deleted file mode 100644 index 15e9c04465..0000000000 --- a/src/coredump/stacktrace.h +++ /dev/null @@ -1,22 +0,0 @@ -#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 . -***/ - -int coredump_make_stack_trace(int fd, const char *executable, char **ret); diff --git a/src/coredump/test-coredump-vacuum.c b/src/coredump/test-coredump-vacuum.c deleted file mode 100644 index 70a57f183f..0000000000 --- a/src/coredump/test-coredump-vacuum.c +++ /dev/null @@ -1,30 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 Zbigniew Jędrzejewski-Szmek - - 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 "coredump-vacuum.h" - -int main(int argc, char *argv[]) { - - if (coredump_vacuum(-1, (uint64_t) -1, 70 * 1024) < 0) - return EXIT_FAILURE; - - return EXIT_SUCCESS; -} diff --git a/src/cryptsetup/Makefile b/src/cryptsetup/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/cryptsetup/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c deleted file mode 100644 index 8ac5ab730a..0000000000 --- a/src/cryptsetup/cryptsetup-generator.c +++ /dev/null @@ -1,509 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 "alloc-util.h" -#include "dropin.h" -#include "fd-util.h" -#include "fileio.h" -#include "fstab-util.h" -#include "generator.h" -#include "hashmap.h" -#include "log.h" -#include "mkdir.h" -#include "parse-util.h" -#include "path-util.h" -#include "proc-cmdline.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "util.h" - -typedef struct crypto_device { - char *uuid; - char *keyfile; - char *name; - char *options; - bool create; -} crypto_device; - -static const char *arg_dest = "/tmp"; -static bool arg_enabled = true; -static bool arg_read_crypttab = true; -static bool arg_whitelist = false; -static Hashmap *arg_disks = NULL; -static char *arg_default_options = NULL; -static char *arg_default_keyfile = NULL; - -static int create_disk( - const char *name, - const char *device, - const char *password, - const char *options) { - - _cleanup_free_ char *p = NULL, *n = NULL, *d = NULL, *u = NULL, *to = NULL, *e = NULL, - *filtered = NULL; - _cleanup_fclose_ FILE *f = NULL; - bool noauto, nofail, tmp, swap; - char *from; - int r; - - assert(name); - assert(device); - - noauto = fstab_test_yes_no_option(options, "noauto\0" "auto\0"); - nofail = fstab_test_yes_no_option(options, "nofail\0" "fail\0"); - tmp = fstab_test_option(options, "tmp\0"); - swap = fstab_test_option(options, "swap\0"); - - if (tmp && swap) { - log_error("Device '%s' cannot be both 'tmp' and 'swap'. Ignoring.", name); - return -EINVAL; - } - - e = unit_name_escape(name); - if (!e) - return log_oom(); - - r = unit_name_build("systemd-cryptsetup", e, ".service", &n); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - - p = strjoin(arg_dest, "/", n, NULL); - if (!p) - return log_oom(); - - u = fstab_node_to_udev_node(device); - if (!u) - return log_oom(); - - r = unit_name_from_path(u, ".device", &d); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - - f = fopen(p, "wxe"); - if (!f) - return log_error_errno(errno, "Failed to create unit file %s: %m", p); - - fputs( - "# Automatically generated by systemd-cryptsetup-generator\n\n" - "[Unit]\n" - "Description=Cryptography Setup for %I\n" - "Documentation=man:crypttab(5) man:systemd-cryptsetup-generator(8) man:systemd-cryptsetup@.service(8)\n" - "SourcePath=/etc/crypttab\n" - "DefaultDependencies=no\n" - "Conflicts=umount.target\n" - "BindsTo=dev-mapper-%i.device\n" - "IgnoreOnIsolate=true\n" - "After=cryptsetup-pre.target\n", - f); - - if (!nofail) - fprintf(f, - "Before=cryptsetup.target\n"); - - if (password) { - if (STR_IN_SET(password, "/dev/urandom", "/dev/random", "/dev/hw_random")) - fputs("After=systemd-random-seed.service\n", f); - else if (!streq(password, "-") && !streq(password, "none")) { - _cleanup_free_ char *uu; - - uu = fstab_node_to_udev_node(password); - if (!uu) - return log_oom(); - - if (!path_equal(uu, "/dev/null")) { - - if (is_device_path(uu)) { - _cleanup_free_ char *dd = NULL; - - r = unit_name_from_path(uu, ".device", &dd); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - - fprintf(f, "After=%1$s\nRequires=%1$s\n", dd); - } else - fprintf(f, "RequiresMountsFor=%s\n", password); - } - } - } - - if (is_device_path(u)) - fprintf(f, - "BindsTo=%s\n" - "After=%s\n" - "Before=umount.target\n", - d, d); - else - fprintf(f, - "RequiresMountsFor=%s\n", - u); - - r = generator_write_timeouts(arg_dest, device, name, options, &filtered); - if (r < 0) - return r; - - fprintf(f, - "\n[Service]\n" - "Type=oneshot\n" - "RemainAfterExit=yes\n" - "TimeoutSec=0\n" /* the binary handles timeouts anyway */ - "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n" - "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n", - name, u, strempty(password), strempty(filtered), - name); - - if (tmp) - fprintf(f, - "ExecStartPost=/sbin/mke2fs '/dev/mapper/%s'\n", - name); - - if (swap) - fprintf(f, - "ExecStartPost=/sbin/mkswap '/dev/mapper/%s'\n", - name); - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write file %s: %m", p); - - from = strjoina("../", n); - - if (!noauto) { - - to = strjoin(arg_dest, "/", d, ".wants/", n, NULL); - if (!to) - return log_oom(); - - mkdir_parents_label(to, 0755); - if (symlink(from, to) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", to); - - free(to); - if (!nofail) - to = strjoin(arg_dest, "/cryptsetup.target.requires/", n, NULL); - else - to = strjoin(arg_dest, "/cryptsetup.target.wants/", n, NULL); - if (!to) - return log_oom(); - - mkdir_parents_label(to, 0755); - if (symlink(from, to) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", to); - } - - free(to); - to = strjoin(arg_dest, "/dev-mapper-", e, ".device.requires/", n, NULL); - if (!to) - return log_oom(); - - mkdir_parents_label(to, 0755); - if (symlink(from, to) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", to); - - if (!noauto && !nofail) { - _cleanup_free_ char *dmname; - dmname = strjoin("dev-mapper-", e, ".device", NULL); - if (!dmname) - return log_oom(); - - r = write_drop_in(arg_dest, dmname, 90, "device-timeout", - "# Automatically generated by systemd-cryptsetup-generator \n\n" - "[Unit]\nJobTimeoutSec=0"); - if (r < 0) - return log_error_errno(r, "Failed to write device drop-in: %m"); - } - - return 0; -} - -static void free_arg_disks(void) { - crypto_device *d; - - while ((d = hashmap_steal_first(arg_disks))) { - free(d->uuid); - free(d->keyfile); - free(d->name); - free(d->options); - free(d); - } - - hashmap_free(arg_disks); -} - -static crypto_device *get_crypto_device(const char *uuid) { - int r; - crypto_device *d; - - assert(uuid); - - d = hashmap_get(arg_disks, uuid); - if (!d) { - d = new0(struct crypto_device, 1); - if (!d) - return NULL; - - d->create = false; - d->keyfile = d->options = d->name = NULL; - - d->uuid = strdup(uuid); - if (!d->uuid) { - free(d); - return NULL; - } - - r = hashmap_put(arg_disks, d->uuid, d); - if (r < 0) { - free(d->uuid); - free(d); - return NULL; - } - } - - return d; -} - -static int parse_proc_cmdline_item(const char *key, const char *value) { - int r; - crypto_device *d; - _cleanup_free_ char *uuid = NULL, *uuid_value = NULL; - - if (STR_IN_SET(key, "luks", "rd.luks") && value) { - - r = parse_boolean(value); - if (r < 0) - log_warning("Failed to parse luks switch %s. Ignoring.", value); - else - arg_enabled = r; - - } else if (STR_IN_SET(key, "luks.crypttab", "rd.luks.crypttab") && value) { - - r = parse_boolean(value); - if (r < 0) - log_warning("Failed to parse luks crypttab switch %s. Ignoring.", value); - else - arg_read_crypttab = r; - - } else if (STR_IN_SET(key, "luks.uuid", "rd.luks.uuid") && value) { - - d = get_crypto_device(startswith(value, "luks-") ? value+5 : value); - if (!d) - return log_oom(); - - d->create = arg_whitelist = true; - - } else if (STR_IN_SET(key, "luks.options", "rd.luks.options") && value) { - - r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); - if (r == 2) { - d = get_crypto_device(uuid); - if (!d) - return log_oom(); - - free(d->options); - d->options = uuid_value; - uuid_value = NULL; - } else if (free_and_strdup(&arg_default_options, value) < 0) - return log_oom(); - - } else if (STR_IN_SET(key, "luks.key", "rd.luks.key") && value) { - - r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); - if (r == 2) { - d = get_crypto_device(uuid); - if (!d) - return log_oom(); - - free(d->keyfile); - d->keyfile = uuid_value; - uuid_value = NULL; - } else if (free_and_strdup(&arg_default_keyfile, value) < 0) - return log_oom(); - - } else if (STR_IN_SET(key, "luks.name", "rd.luks.name") && value) { - - r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); - if (r == 2) { - d = get_crypto_device(uuid); - if (!d) - return log_oom(); - - d->create = arg_whitelist = true; - - free(d->name); - d->name = uuid_value; - uuid_value = NULL; - } else - log_warning("Failed to parse luks name switch %s. Ignoring.", value); - - } - - return 0; -} - -static int add_crypttab_devices(void) { - struct stat st; - unsigned crypttab_line = 0; - _cleanup_fclose_ FILE *f = NULL; - - if (!arg_read_crypttab) - return 0; - - f = fopen("/etc/crypttab", "re"); - if (!f) { - if (errno != ENOENT) - log_error_errno(errno, "Failed to open /etc/crypttab: %m"); - return 0; - } - - if (fstat(fileno(f), &st) < 0) { - log_error_errno(errno, "Failed to stat /etc/crypttab: %m"); - return 0; - } - - for (;;) { - int r, k; - char line[LINE_MAX], *l, *uuid; - crypto_device *d = NULL; - _cleanup_free_ char *name = NULL, *device = NULL, *keyfile = NULL, *options = NULL; - - if (!fgets(line, sizeof(line), f)) - break; - - crypttab_line++; - - l = strstrip(line); - if (*l == '#' || *l == 0) - continue; - - k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &keyfile, &options); - if (k < 2 || k > 4) { - log_error("Failed to parse /etc/crypttab:%u, ignoring.", crypttab_line); - continue; - } - - uuid = startswith(device, "UUID="); - if (!uuid) - uuid = path_startswith(device, "/dev/disk/by-uuid/"); - if (!uuid) - uuid = startswith(name, "luks-"); - if (uuid) - d = hashmap_get(arg_disks, uuid); - - if (arg_whitelist && !d) { - log_info("Not creating device '%s' because it was not specified on the kernel command line.", name); - continue; - } - - r = create_disk(name, device, keyfile, (d && d->options) ? d->options : options); - if (r < 0) - return r; - - if (d) - d->create = false; - } - - return 0; -} - -static int add_proc_cmdline_devices(void) { - int r; - Iterator i; - crypto_device *d; - - HASHMAP_FOREACH(d, arg_disks, i) { - const char *options; - _cleanup_free_ char *device = NULL; - - if (!d->create) - continue; - - if (!d->name) { - d->name = strappend("luks-", d->uuid); - if (!d->name) - return log_oom(); - } - - device = strappend("UUID=", d->uuid); - if (!device) - return log_oom(); - - if (d->options) - options = d->options; - else if (arg_default_options) - options = arg_default_options; - else - options = "timeout=0"; - - r = create_disk(d->name, device, d->keyfile ?: arg_default_keyfile, options); - if (r < 0) - return r; - } - - return 0; -} - -int main(int argc, char *argv[]) { - int r = EXIT_FAILURE; - - if (argc > 1 && argc != 4) { - log_error("This program takes three or no arguments."); - return EXIT_FAILURE; - } - - if (argc > 1) - arg_dest = argv[1]; - - log_set_target(LOG_TARGET_SAFE); - log_parse_environment(); - log_open(); - - umask(0022); - - arg_disks = hashmap_new(&string_hash_ops); - if (!arg_disks) - goto cleanup; - - r = parse_proc_cmdline(parse_proc_cmdline_item); - if (r < 0) { - log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - r = EXIT_FAILURE; - } - - if (!arg_enabled) { - r = EXIT_SUCCESS; - goto cleanup; - } - - if (add_crypttab_devices() < 0) - goto cleanup; - - if (add_proc_cmdline_devices() < 0) - goto cleanup; - - r = EXIT_SUCCESS; - -cleanup: - free_arg_disks(); - free(arg_default_options); - free(arg_default_keyfile); - - return r; -} diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c deleted file mode 100644 index 9927621ea0..0000000000 --- a/src/cryptsetup/cryptsetup.c +++ /dev/null @@ -1,757 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include - -#include "sd-device.h" - -#include "alloc-util.h" -#include "ask-password-api.h" -#include "device-util.h" -#include "escape.h" -#include "fileio.h" -#include "log.h" -#include "mount-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -static const char *arg_type = NULL; /* CRYPT_LUKS1, CRYPT_TCRYPT or CRYPT_PLAIN */ -static char *arg_cipher = NULL; -static unsigned arg_key_size = 0; -static int arg_key_slot = CRYPT_ANY_SLOT; -static unsigned arg_keyfile_size = 0; -static unsigned arg_keyfile_offset = 0; -static char *arg_hash = NULL; -static char *arg_header = NULL; -static unsigned arg_tries = 3; -static bool arg_readonly = false; -static bool arg_verify = false; -static bool arg_discards = false; -static bool arg_tcrypt_hidden = false; -static bool arg_tcrypt_system = false; -static char **arg_tcrypt_keyfiles = NULL; -static uint64_t arg_offset = 0; -static uint64_t arg_skip = 0; -static usec_t arg_timeout = 0; - -/* Options Debian's crypttab knows we don't: - - precheck= - check= - checkargs= - noearly= - loud= - keyscript= -*/ - -static int parse_one_option(const char *option) { - assert(option); - - /* Handled outside of this tool */ - if (STR_IN_SET(option, "noauto", "auto", "nofail", "fail")) - return 0; - - if (startswith(option, "cipher=")) { - char *t; - - t = strdup(option+7); - if (!t) - return log_oom(); - - free(arg_cipher); - arg_cipher = t; - - } else if (startswith(option, "size=")) { - - if (safe_atou(option+5, &arg_key_size) < 0) { - log_error("size= parse failure, ignoring."); - return 0; - } - - if (arg_key_size % 8) { - log_error("size= not a multiple of 8, ignoring."); - return 0; - } - - arg_key_size /= 8; - - } else if (startswith(option, "key-slot=")) { - - arg_type = CRYPT_LUKS1; - if (safe_atoi(option+9, &arg_key_slot) < 0) { - log_error("key-slot= parse failure, ignoring."); - return 0; - } - - } else if (startswith(option, "tcrypt-keyfile=")) { - - arg_type = CRYPT_TCRYPT; - if (path_is_absolute(option+15)) { - if (strv_extend(&arg_tcrypt_keyfiles, option + 15) < 0) - return log_oom(); - } else - log_error("Key file path '%s' is not absolute. Ignoring.", option+15); - - } else if (startswith(option, "keyfile-size=")) { - - if (safe_atou(option+13, &arg_keyfile_size) < 0) { - log_error("keyfile-size= parse failure, ignoring."); - return 0; - } - - } else if (startswith(option, "keyfile-offset=")) { - - if (safe_atou(option+15, &arg_keyfile_offset) < 0) { - log_error("keyfile-offset= parse failure, ignoring."); - return 0; - } - - } else if (startswith(option, "hash=")) { - char *t; - - t = strdup(option+5); - if (!t) - return log_oom(); - - free(arg_hash); - arg_hash = t; - - } else if (startswith(option, "header=")) { - arg_type = CRYPT_LUKS1; - - if (!path_is_absolute(option+7)) { - log_error("Header path '%s' is not absolute, refusing.", option+7); - return -EINVAL; - } - - if (arg_header) { - log_error("Duplicate header= options, refusing."); - return -EINVAL; - } - - arg_header = strdup(option+7); - if (!arg_header) - return log_oom(); - - } else if (startswith(option, "tries=")) { - - if (safe_atou(option+6, &arg_tries) < 0) { - log_error("tries= parse failure, ignoring."); - return 0; - } - - } else if (STR_IN_SET(option, "readonly", "read-only")) - arg_readonly = true; - else if (streq(option, "verify")) - arg_verify = true; - else if (STR_IN_SET(option, "allow-discards", "discard")) - arg_discards = true; - else if (streq(option, "luks")) - arg_type = CRYPT_LUKS1; - else if (streq(option, "tcrypt")) - arg_type = CRYPT_TCRYPT; - else if (streq(option, "tcrypt-hidden")) { - arg_type = CRYPT_TCRYPT; - arg_tcrypt_hidden = true; - } else if (streq(option, "tcrypt-system")) { - arg_type = CRYPT_TCRYPT; - arg_tcrypt_system = true; - } else if (STR_IN_SET(option, "plain", "swap", "tmp")) - arg_type = CRYPT_PLAIN; - else if (startswith(option, "timeout=")) { - - if (parse_sec(option+8, &arg_timeout) < 0) { - log_error("timeout= parse failure, ignoring."); - return 0; - } - - } else if (startswith(option, "offset=")) { - - if (safe_atou64(option+7, &arg_offset) < 0) { - log_error("offset= parse failure, refusing."); - return -EINVAL; - } - - } else if (startswith(option, "skip=")) { - - if (safe_atou64(option+5, &arg_skip) < 0) { - log_error("skip= parse failure, refusing."); - return -EINVAL; - } - - } else if (!streq(option, "none")) - log_error("Encountered unknown /etc/crypttab option '%s', ignoring.", option); - - return 0; -} - -static int parse_options(const char *options) { - const char *word, *state; - size_t l; - int r; - - assert(options); - - FOREACH_WORD_SEPARATOR(word, l, options, ",", state) { - _cleanup_free_ char *o; - - o = strndup(word, l); - if (!o) - return -ENOMEM; - r = parse_one_option(o); - if (r < 0) - return r; - } - - /* sanity-check options */ - if (arg_type != NULL && !streq(arg_type, CRYPT_PLAIN)) { - if (arg_offset) - log_warning("offset= ignored with type %s", arg_type); - if (arg_skip) - log_warning("skip= ignored with type %s", arg_type); - } - - return 0; -} - -static void log_glue(int level, const char *msg, void *usrptr) { - log_debug("%s", msg); -} - -static int disk_major_minor(const char *path, char **ret) { - struct stat st; - - assert(path); - - if (stat(path, &st) < 0) - return -errno; - - if (!S_ISBLK(st.st_mode)) - return -EINVAL; - - if (asprintf(ret, "/dev/block/%d:%d", major(st.st_rdev), minor(st.st_rdev)) < 0) - return -errno; - - return 0; -} - -static char* disk_description(const char *path) { - - static const char name_fields[] = - "ID_PART_ENTRY_NAME\0" - "DM_NAME\0" - "ID_MODEL_FROM_DATABASE\0" - "ID_MODEL\0"; - - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - struct stat st; - const char *i; - int r; - - assert(path); - - if (stat(path, &st) < 0) - return NULL; - - if (!S_ISBLK(st.st_mode)) - return NULL; - - r = sd_device_new_from_devnum(&device, 'b', st.st_rdev); - if (r < 0) - return NULL; - - NULSTR_FOREACH(i, name_fields) { - const char *name; - - r = sd_device_get_property_value(device, i, &name); - if (r >= 0 && !isempty(name)) - return strdup(name); - } - - return NULL; -} - -static char *disk_mount_point(const char *label) { - _cleanup_free_ char *device = NULL; - _cleanup_endmntent_ FILE *f = NULL; - struct mntent *m; - - /* Yeah, we don't support native systemd unit files here for now */ - - if (asprintf(&device, "/dev/mapper/%s", label) < 0) - return NULL; - - f = setmntent("/etc/fstab", "r"); - if (!f) - return NULL; - - while ((m = getmntent(f))) - if (path_equal(m->mnt_fsname, device)) - return strdup(m->mnt_dir); - - return NULL; -} - -static int get_password(const char *vol, const char *src, usec_t until, bool accept_cached, char ***ret) { - _cleanup_free_ char *description = NULL, *name_buffer = NULL, *mount_point = NULL, *maj_min = NULL, *text = NULL, *escaped_name = NULL; - _cleanup_strv_free_erase_ char **passwords = NULL; - const char *name = NULL; - char **p, *id; - int r = 0; - - assert(vol); - assert(src); - assert(ret); - - description = disk_description(src); - mount_point = disk_mount_point(vol); - - if (description && streq(vol, description)) - /* If the description string is simply the - * volume name, then let's not show this - * twice */ - description = mfree(description); - - if (mount_point && description) - r = asprintf(&name_buffer, "%s (%s) on %s", description, vol, mount_point); - else if (mount_point) - r = asprintf(&name_buffer, "%s on %s", vol, mount_point); - else if (description) - r = asprintf(&name_buffer, "%s (%s)", description, vol); - - if (r < 0) - return log_oom(); - - name = name_buffer ? name_buffer : vol; - - if (asprintf(&text, "Please enter passphrase for disk %s!", name) < 0) - return log_oom(); - - if (src) - (void) disk_major_minor(src, &maj_min); - - if (maj_min) { - escaped_name = maj_min; - maj_min = NULL; - } else - escaped_name = cescape(name); - - if (!escaped_name) - return log_oom(); - - id = strjoina("cryptsetup:", escaped_name); - - r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", until, - ASK_PASSWORD_PUSH_CACHE | (accept_cached*ASK_PASSWORD_ACCEPT_CACHED), - &passwords); - if (r < 0) - return log_error_errno(r, "Failed to query password: %m"); - - if (arg_verify) { - _cleanup_strv_free_erase_ char **passwords2 = NULL; - - assert(strv_length(passwords) == 1); - - if (asprintf(&text, "Please enter passphrase for disk %s! (verification)", name) < 0) - return log_oom(); - - id = strjoina("cryptsetup-verification:", escaped_name); - - r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", until, ASK_PASSWORD_PUSH_CACHE, &passwords2); - if (r < 0) - return log_error_errno(r, "Failed to query verification password: %m"); - - assert(strv_length(passwords2) == 1); - - if (!streq(passwords[0], passwords2[0])) { - log_warning("Passwords did not match, retrying."); - return -EAGAIN; - } - } - - strv_uniq(passwords); - - STRV_FOREACH(p, passwords) { - char *c; - - if (strlen(*p)+1 >= arg_key_size) - continue; - - /* Pad password if necessary */ - c = new(char, arg_key_size); - if (!c) - return log_oom(); - - strncpy(c, *p, arg_key_size); - free(*p); - *p = c; - } - - *ret = passwords; - passwords = NULL; - - return 0; -} - -static int attach_tcrypt( - struct crypt_device *cd, - const char *name, - const char *key_file, - char **passwords, - uint32_t flags) { - - int r = 0; - _cleanup_free_ char *passphrase = NULL; - struct crypt_params_tcrypt params = { - .flags = CRYPT_TCRYPT_LEGACY_MODES, - .keyfiles = (const char **)arg_tcrypt_keyfiles, - .keyfiles_count = strv_length(arg_tcrypt_keyfiles) - }; - - assert(cd); - assert(name); - assert(key_file || (passwords && passwords[0])); - - if (arg_tcrypt_hidden) - params.flags |= CRYPT_TCRYPT_HIDDEN_HEADER; - - if (arg_tcrypt_system) - params.flags |= CRYPT_TCRYPT_SYSTEM_HEADER; - - if (key_file) { - r = read_one_line_file(key_file, &passphrase); - if (r < 0) { - log_error_errno(r, "Failed to read password file '%s': %m", key_file); - return -EAGAIN; - } - - params.passphrase = passphrase; - } else - params.passphrase = passwords[0]; - params.passphrase_size = strlen(params.passphrase); - - r = crypt_load(cd, CRYPT_TCRYPT, ¶ms); - if (r < 0) { - if (key_file && r == -EPERM) { - log_error("Failed to activate using password file '%s'.", key_file); - return -EAGAIN; - } - return r; - } - - return crypt_activate_by_volume_key(cd, name, NULL, 0, flags); -} - -static int attach_luks_or_plain(struct crypt_device *cd, - const char *name, - const char *key_file, - const char *data_device, - char **passwords, - uint32_t flags) { - int r = 0; - bool pass_volume_key = false; - - assert(cd); - assert(name); - assert(key_file || passwords); - - if (!arg_type || streq(arg_type, CRYPT_LUKS1)) { - r = crypt_load(cd, CRYPT_LUKS1, NULL); - if (r < 0) { - log_error("crypt_load() failed on device %s.\n", crypt_get_device_name(cd)); - return r; - } - - if (data_device) - r = crypt_set_data_device(cd, data_device); - } - - if ((!arg_type && r < 0) || streq_ptr(arg_type, CRYPT_PLAIN)) { - struct crypt_params_plain params = { - .offset = arg_offset, - .skip = arg_skip, - }; - const char *cipher, *cipher_mode; - _cleanup_free_ char *truncated_cipher = NULL; - - if (arg_hash) { - /* plain isn't a real hash type. it just means "use no hash" */ - if (!streq(arg_hash, "plain")) - params.hash = arg_hash; - } else if (!key_file) - /* for CRYPT_PLAIN, the behaviour of cryptsetup - * package is to not hash when a key file is provided */ - params.hash = "ripemd160"; - - if (arg_cipher) { - size_t l; - - l = strcspn(arg_cipher, "-"); - truncated_cipher = strndup(arg_cipher, l); - if (!truncated_cipher) - return log_oom(); - - cipher = truncated_cipher; - cipher_mode = arg_cipher[l] ? arg_cipher+l+1 : "plain"; - } else { - cipher = "aes"; - cipher_mode = "cbc-essiv:sha256"; - } - - /* for CRYPT_PLAIN limit reads - * from keyfile to key length, and - * ignore keyfile-size */ - arg_keyfile_size = arg_key_size; - - /* In contrast to what the name - * crypt_setup() might suggest this - * doesn't actually format anything, - * it just configures encryption - * parameters when used for plain - * mode. */ - r = crypt_format(cd, CRYPT_PLAIN, cipher, cipher_mode, NULL, NULL, arg_keyfile_size, ¶ms); - - /* hash == NULL implies the user passed "plain" */ - pass_volume_key = (params.hash == NULL); - } - - if (r < 0) - return log_error_errno(r, "Loading of cryptographic parameters failed: %m"); - - log_info("Set cipher %s, mode %s, key size %i bits for device %s.", - crypt_get_cipher(cd), - crypt_get_cipher_mode(cd), - crypt_get_volume_key_size(cd)*8, - crypt_get_device_name(cd)); - - if (key_file) { - r = crypt_activate_by_keyfile_offset(cd, name, arg_key_slot, key_file, arg_keyfile_size, arg_keyfile_offset, flags); - if (r < 0) { - log_error_errno(r, "Failed to activate with key file '%s': %m", key_file); - return -EAGAIN; - } - } else { - char **p; - - STRV_FOREACH(p, passwords) { - if (pass_volume_key) - r = crypt_activate_by_volume_key(cd, name, *p, arg_key_size, flags); - else - r = crypt_activate_by_passphrase(cd, name, arg_key_slot, *p, strlen(*p), flags); - - if (r >= 0) - break; - } - } - - return r; -} - -static int help(void) { - - printf("%s attach VOLUME SOURCEDEVICE [PASSWORD] [OPTIONS]\n" - "%s detach VOLUME\n\n" - "Attaches or detaches an encrypted block device.\n", - program_invocation_short_name, - program_invocation_short_name); - - return 0; -} - -int main(int argc, char *argv[]) { - int r = EXIT_FAILURE; - struct crypt_device *cd = NULL; - - if (argc <= 1) { - help(); - return EXIT_SUCCESS; - } - - if (argc < 3) { - log_error("This program requires at least two arguments."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - if (streq(argv[1], "attach")) { - uint32_t flags = 0; - int k; - unsigned tries; - usec_t until; - crypt_status_info status; - const char *key_file = NULL; - - /* Arguments: systemd-cryptsetup attach VOLUME SOURCE-DEVICE [PASSWORD] [OPTIONS] */ - - if (argc < 4) { - log_error("attach requires at least two arguments."); - goto finish; - } - - if (argc >= 5 && - argv[4][0] && - !streq(argv[4], "-") && - !streq(argv[4], "none")) { - - if (!path_is_absolute(argv[4])) - log_error("Password file path '%s' is not absolute. Ignoring.", argv[4]); - else - key_file = argv[4]; - } - - if (argc >= 6 && argv[5][0] && !streq(argv[5], "-")) { - if (parse_options(argv[5]) < 0) - goto finish; - } - - /* A delicious drop of snake oil */ - mlockall(MCL_FUTURE); - - if (arg_header) { - log_debug("LUKS header: %s", arg_header); - k = crypt_init(&cd, arg_header); - } else - k = crypt_init(&cd, argv[3]); - if (k) { - log_error_errno(k, "crypt_init() failed: %m"); - goto finish; - } - - crypt_set_log_callback(cd, log_glue, NULL); - - status = crypt_status(cd, argv[2]); - if (status == CRYPT_ACTIVE || status == CRYPT_BUSY) { - log_info("Volume %s already active.", argv[2]); - r = EXIT_SUCCESS; - goto finish; - } - - if (arg_readonly) - flags |= CRYPT_ACTIVATE_READONLY; - - if (arg_discards) - flags |= CRYPT_ACTIVATE_ALLOW_DISCARDS; - - if (arg_timeout > 0) - until = now(CLOCK_MONOTONIC) + arg_timeout; - else - until = 0; - - arg_key_size = (arg_key_size > 0 ? arg_key_size : (256 / 8)); - - if (key_file) { - struct stat st; - - /* Ideally we'd do this on the open fd, but since this is just a - * warning it's OK to do this in two steps. */ - if (stat(key_file, &st) >= 0 && S_ISREG(st.st_mode) && (st.st_mode & 0005)) - log_warning("Key file %s is world-readable. This is not a good idea!", key_file); - } - - for (tries = 0; arg_tries == 0 || tries < arg_tries; tries++) { - _cleanup_strv_free_erase_ char **passwords = NULL; - - if (!key_file) { - k = get_password(argv[2], argv[3], until, tries == 0 && !arg_verify, &passwords); - if (k == -EAGAIN) - continue; - else if (k < 0) - goto finish; - } - - if (streq_ptr(arg_type, CRYPT_TCRYPT)) - k = attach_tcrypt(cd, argv[2], key_file, passwords, flags); - else - k = attach_luks_or_plain(cd, - argv[2], - key_file, - arg_header ? argv[3] : NULL, - passwords, - flags); - if (k >= 0) - break; - else if (k == -EAGAIN) { - key_file = NULL; - continue; - } else if (k != -EPERM) { - log_error_errno(k, "Failed to activate: %m"); - goto finish; - } - - log_warning("Invalid passphrase."); - } - - if (arg_tries != 0 && tries >= arg_tries) { - log_error("Too many attempts; giving up."); - r = EXIT_FAILURE; - goto finish; - } - - } else if (streq(argv[1], "detach")) { - int k; - - k = crypt_init_by_name(&cd, argv[2]); - if (k == -ENODEV) { - log_info("Volume %s already inactive.", argv[2]); - r = EXIT_SUCCESS; - goto finish; - } else if (k) { - log_error_errno(k, "crypt_init_by_name() failed: %m"); - goto finish; - } - - crypt_set_log_callback(cd, log_glue, NULL); - - k = crypt_deactivate(cd, argv[2]); - if (k < 0) { - log_error_errno(k, "Failed to deactivate: %m"); - goto finish; - } - - } else { - log_error("Unknown verb %s.", argv[1]); - goto finish; - } - - r = EXIT_SUCCESS; - -finish: - - if (cd) - crypt_free(cd); - - free(arg_cipher); - free(arg_hash); - free(arg_header); - strv_free(arg_tcrypt_keyfiles); - - return r; -} diff --git a/src/dbus1-generator/Makefile b/src/dbus1-generator/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/dbus1-generator/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/dbus1-generator/dbus1-generator.c b/src/dbus1-generator/dbus1-generator.c deleted file mode 100644 index 717cb9558e..0000000000 --- a/src/dbus1-generator/dbus1-generator.c +++ /dev/null @@ -1,331 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "alloc-util.h" -#include "bus-internal.h" -#include "bus-util.h" -#include "cgroup-util.h" -#include "conf-parser.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "mkdir.h" -#include "special.h" -#include "unit-name.h" -#include "util.h" - -static const char *arg_dest_late = "/tmp", *arg_dest = "/tmp"; - -static int create_dbus_files( - const char *path, - const char *name, - const char *service, - const char *exec, - const char *user, - const char *type) { - - _cleanup_free_ char *b = NULL, *s = NULL, *lnk = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(path); - assert(name); - assert(service || exec); - - if (!service) { - _cleanup_free_ char *a = NULL; - - s = strjoin("dbus-", name, ".service", NULL); - if (!s) - return log_oom(); - - a = strjoin(arg_dest_late, "/", s, NULL); - if (!a) - return log_oom(); - - f = fopen(a, "wxe"); - if (!f) - return log_error_errno(errno, "Failed to create %s: %m", a); - - fprintf(f, - "# Automatically generated by systemd-dbus1-generator\n\n" - "[Unit]\n" - "SourcePath=%s\n" - "Description=DBUS1: %s\n" - "Documentation=man:systemd-dbus1-generator(8)\n\n" - "[Service]\n" - "ExecStart=%s\n" - "Type=dbus\n" - "BusName=%s\n", - path, - name, - exec, - name); - - if (user) - fprintf(f, "User=%s\n", user); - - - if (type) { - fprintf(f, "Environment=DBUS_STARTER_BUS_TYPE=%s\n", type); - - if (streq(type, "system")) - fprintf(f, "Environment=DBUS_STARTER_ADDRESS=" DEFAULT_SYSTEM_BUS_ADDRESS "\n"); - else if (streq(type, "session")) { - char *run; - - run = getenv("XDG_RUNTIME_DIR"); - if (!run) { - log_error("XDG_RUNTIME_DIR not set."); - return -EINVAL; - } - - fprintf(f, "Environment=DBUS_STARTER_ADDRESS="KERNEL_USER_BUS_ADDRESS_FMT ";" UNIX_USER_BUS_ADDRESS_FMT "\n", - getuid(), run); - } - } - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write %s: %m", a); - - f = safe_fclose(f); - - service = s; - } - - b = strjoin(arg_dest_late, "/", name, ".busname", NULL); - if (!b) - return log_oom(); - - f = fopen(b, "wxe"); - if (!f) - return log_error_errno(errno, "Failed to create %s: %m", b); - - fprintf(f, - "# Automatically generated by systemd-dbus1-generator\n\n" - "[Unit]\n" - "SourcePath=%s\n" - "Description=DBUS1: %s\n" - "Documentation=man:systemd-dbus1-generator(8)\n\n" - "[BusName]\n" - "Name=%s\n" - "Service=%s\n" - "AllowWorld=talk\n", - path, - name, - name, - service); - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write %s: %m", b); - - lnk = strjoin(arg_dest_late, "/" SPECIAL_BUSNAMES_TARGET ".wants/", name, ".busname", NULL); - if (!lnk) - return log_oom(); - - mkdir_parents_label(lnk, 0755); - if (symlink(b, lnk)) - return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); - - return 0; -} - -static int add_dbus(const char *path, const char *fname, const char *type) { - _cleanup_free_ char *name = NULL, *exec = NULL, *user = NULL, *service = NULL; - - const ConfigTableItem table[] = { - { "D-BUS Service", "Name", config_parse_string, 0, &name }, - { "D-BUS Service", "Exec", config_parse_string, 0, &exec }, - { "D-BUS Service", "User", config_parse_string, 0, &user }, - { "D-BUS Service", "SystemdService", config_parse_string, 0, &service }, - { }, - }; - - char *p; - int r; - - assert(path); - assert(fname); - - p = strjoina(path, "/", fname); - r = config_parse(NULL, p, NULL, - "D-BUS Service\0", - config_item_table_lookup, table, - true, false, true, NULL); - if (r < 0) - return r; - - if (!name) { - log_warning("Activation file %s lacks name setting, ignoring.", p); - return 0; - } - - if (!service_name_is_valid(name)) { - log_warning("Bus service name %s is not valid, ignoring.", name); - return 0; - } - - if (streq(name, "org.freedesktop.systemd1")) { - log_debug("Skipping %s, identified as systemd.", p); - return 0; - } - - if (service) { - if (!unit_name_is_valid(service, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) { - log_warning("Unit name %s is not valid, ignoring.", service); - return 0; - } - if (!endswith(service, ".service")) { - log_warning("Bus names can only activate services, ignoring %s.", p); - return 0; - } - } else { - if (streq(exec, "/bin/false") || !exec) { - log_warning("Neither service name nor binary path specified, ignoring %s.", p); - return 0; - } - - if (exec[0] != '/') { - log_warning("Exec= in %s does not start with an absolute path, ignoring.", p); - return 0; - } - } - - return create_dbus_files(p, name, service, exec, user, type); -} - -static int parse_dbus_fragments(const char *path, const char *type) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - int r; - - assert(path); - assert(type); - - d = opendir(path); - if (!d) { - if (errno == -ENOENT) - return 0; - - return log_error_errno(errno, "Failed to enumerate D-Bus activated services: %m"); - } - - r = 0; - FOREACH_DIRENT(de, d, goto fail) { - int q; - - if (!endswith(de->d_name, ".service")) - continue; - - q = add_dbus(path, de->d_name, type); - if (q < 0) - r = q; - } - - return r; - -fail: - return log_error_errno(errno, "Failed to read D-Bus services directory: %m"); -} - -static int link_busnames_target(const char *units) { - const char *f, *t; - - f = strjoina(units, "/" SPECIAL_BUSNAMES_TARGET); - t = strjoina(arg_dest, "/" SPECIAL_BASIC_TARGET ".wants/" SPECIAL_BUSNAMES_TARGET); - - mkdir_parents_label(t, 0755); - if (symlink(f, t) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", t); - - return 0; -} - -static int link_compatibility(const char *units) { - const char *f, *t; - - f = strjoina(units, "/systemd-bus-proxyd.socket"); - t = strjoina(arg_dest, "/" SPECIAL_DBUS_SOCKET); - mkdir_parents_label(t, 0755); - if (symlink(f, t) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", t); - - f = strjoina(units, "/systemd-bus-proxyd.socket"); - t = strjoina(arg_dest, "/" SPECIAL_SOCKETS_TARGET ".wants/systemd-bus-proxyd.socket"); - mkdir_parents_label(t, 0755); - if (symlink(f, t) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", t); - - t = strjoina(arg_dest, "/" SPECIAL_DBUS_SERVICE); - if (symlink("/dev/null", t) < 0) - return log_error_errno(errno, "Failed to mask %s: %m", t); - - return 0; -} - -int main(int argc, char *argv[]) { - const char *path, *type, *units; - int r, q; - - if (argc > 1 && argc != 4) { - log_error("This program takes three or no arguments."); - return EXIT_FAILURE; - } - - if (argc > 1) { - arg_dest = argv[1]; - arg_dest_late = argv[3]; - } - - log_set_target(LOG_TARGET_SAFE); - log_parse_environment(); - log_open(); - - umask(0022); - - if (!is_kdbus_available()) - return 0; - - r = cg_pid_get_owner_uid(0, NULL); - if (r >= 0) { - path = "/usr/share/dbus-1/services"; - type = "session"; - units = USER_DATA_UNIT_PATH; - } else if (r == -ENXIO) { - path = "/usr/share/dbus-1/system-services"; - type = "system"; - units = SYSTEM_DATA_UNIT_PATH; - } else - return log_error_errno(r, "Failed to determine whether we are running as user or system instance: %m"); - - r = parse_dbus_fragments(path, type); - - /* FIXME: One day this should just be pulled in statically from basic.target */ - q = link_busnames_target(units); - if (q < 0) - r = q; - - q = link_compatibility(units); - if (q < 0) - r = q; - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/debug-generator/Makefile b/src/debug-generator/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/debug-generator/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/debug-generator/debug-generator.c b/src/debug-generator/debug-generator.c deleted file mode 100644 index 7e80af78e7..0000000000 --- a/src/debug-generator/debug-generator.c +++ /dev/null @@ -1,202 +0,0 @@ -/*** - 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 "alloc-util.h" -#include "mkdir.h" -#include "parse-util.h" -#include "proc-cmdline.h" -#include "special.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "util.h" - -static char *arg_default_unit = NULL; -static const char *arg_dest = "/tmp"; -static char **arg_mask = NULL; -static char **arg_wants = NULL; -static bool arg_debug_shell = false; - -static int parse_proc_cmdline_item(const char *key, const char *value) { - int r; - - assert(key); - - if (streq(key, "systemd.mask")) { - - if (!value) - log_error("Missing argument for systemd.mask= kernel command line parameter."); - else { - char *n; - - r = unit_name_mangle(value, UNIT_NAME_NOGLOB, &n); - if (r < 0) - return log_error_errno(r, "Failed to glob unit name: %m"); - - r = strv_consume(&arg_mask, n); - if (r < 0) - return log_oom(); - } - - } else if (streq(key, "systemd.wants")) { - - if (!value) - log_error("Missing argument for systemd.want= kernel command line parameter."); - else { - char *n; - - r = unit_name_mangle(value, UNIT_NAME_NOGLOB, &n); - if (r < 0) - return log_error_errno(r, "Failed to glob unit name: %m"); - - r = strv_consume(&arg_wants, n); - if (r < 0) - return log_oom(); - } - - } else if (streq(key, "systemd.debug-shell")) { - - if (value) { - r = parse_boolean(value); - if (r < 0) - log_error("Failed to parse systemd.debug-shell= argument '%s', ignoring.", value); - else - arg_debug_shell = r; - } else - arg_debug_shell = true; - } else if (streq(key, "systemd.unit")) { - - if (!value) - log_error("Missing argument for systemd.unit= kernel command line parameter."); - else { - r = free_and_strdup(&arg_default_unit, value); - if (r < 0) - return log_error_errno(r, "Failed to set default unit %s: %m", value); - } - } else if (!value) { - const char *target; - - target = runlevel_to_target(key); - if (target) { - r = free_and_strdup(&arg_default_unit, target); - if (r < 0) - return log_error_errno(r, "Failed to set default unit %s: %m", target); - } - } - - return 0; -} - -static int generate_mask_symlinks(void) { - char **u; - int r = 0; - - if (strv_isempty(arg_mask)) - return 0; - - STRV_FOREACH(u, arg_mask) { - _cleanup_free_ char *p = NULL; - - p = strjoin(arg_dest, "/", *u, NULL); - if (!p) - return log_oom(); - - if (symlink("/dev/null", p) < 0) - r = log_error_errno(errno, - "Failed to create mask symlink %s: %m", - p); - } - - return r; -} - -static int generate_wants_symlinks(void) { - char **u; - int r = 0; - - if (strv_isempty(arg_wants)) - return 0; - - STRV_FOREACH(u, arg_wants) { - _cleanup_free_ char *p = NULL, *f = NULL; - - p = strjoin(arg_dest, "/", arg_default_unit, ".wants/", *u, NULL); - if (!p) - return log_oom(); - - f = strappend(SYSTEM_DATA_UNIT_PATH "/", *u); - if (!f) - return log_oom(); - - mkdir_parents_label(p, 0755); - - if (symlink(f, p) < 0) - r = log_error_errno(errno, - "Failed to create wants symlink %s: %m", - p); - } - - return r; -} - -int main(int argc, char *argv[]) { - int r, q; - - if (argc > 1 && argc != 4) { - log_error("This program takes three or no arguments."); - return EXIT_FAILURE; - } - - if (argc > 1) - arg_dest = argv[2]; - - log_set_target(LOG_TARGET_SAFE); - log_parse_environment(); - log_open(); - - umask(0022); - - r = free_and_strdup(&arg_default_unit, SPECIAL_DEFAULT_TARGET); - if (r < 0) { - log_error_errno(r, "Failed to set default unit %s: %m", SPECIAL_DEFAULT_TARGET); - goto finish; - } - - r = parse_proc_cmdline(parse_proc_cmdline_item); - if (r < 0) - log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - - if (arg_debug_shell) { - r = strv_extend(&arg_wants, "debug-shell.service"); - if (r < 0) { - r = log_oom(); - goto finish; - } - } - - r = generate_mask_symlinks(); - - q = generate_wants_symlinks(); - if (q < 0) - r = q; - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; - -} diff --git a/src/delta/Makefile b/src/delta/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/delta/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/delta/delta.c b/src/delta/delta.c deleted file mode 100644 index f32744def2..0000000000 --- a/src/delta/delta.c +++ /dev/null @@ -1,635 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 Lennart Poettering - Copyright 2013 Zbigniew Jędrzejewski-Szmek - - 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 -#include -#include -#include - -#include "alloc-util.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fs-util.h" -#include "hashmap.h" -#include "locale-util.h" -#include "log.h" -#include "pager.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "signal-util.h" -#include "stat-util.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "util.h" - -static const char prefixes[] = - "/etc\0" - "/run\0" - "/usr/local/lib\0" - "/usr/local/share\0" - "/usr/lib\0" - "/usr/share\0" -#ifdef HAVE_SPLIT_USR - "/lib\0" -#endif - ; - -static const char suffixes[] = - "sysctl.d\0" - "tmpfiles.d\0" - "modules-load.d\0" - "binfmt.d\0" - "systemd/system\0" - "systemd/user\0" - "systemd/system-preset\0" - "systemd/user-preset\0" - "udev/rules.d\0" - "modprobe.d\0"; - -static const char have_dropins[] = - "systemd/system\0" - "systemd/user\0"; - -static bool arg_no_pager = false; -static int arg_diff = -1; - -static enum { - SHOW_MASKED = 1 << 0, - SHOW_EQUIVALENT = 1 << 1, - SHOW_REDIRECTED = 1 << 2, - SHOW_OVERRIDDEN = 1 << 3, - SHOW_UNCHANGED = 1 << 4, - SHOW_EXTENDED = 1 << 5, - - SHOW_DEFAULTS = - (SHOW_MASKED | SHOW_EQUIVALENT | SHOW_REDIRECTED | SHOW_OVERRIDDEN | SHOW_EXTENDED) -} arg_flags = 0; - -static int equivalent(const char *a, const char *b) { - _cleanup_free_ char *x = NULL, *y = NULL; - - x = canonicalize_file_name(a); - if (!x) - return -errno; - - y = canonicalize_file_name(b); - if (!y) - return -errno; - - return path_equal(x, y); -} - -static int notify_override_masked(const char *top, const char *bottom) { - if (!(arg_flags & SHOW_MASKED)) - return 0; - - printf("%s%s%s %s %s %s\n", - ansi_highlight_red(), "[MASKED]", ansi_normal(), - top, special_glyph(ARROW), bottom); - return 1; -} - -static int notify_override_equivalent(const char *top, const char *bottom) { - if (!(arg_flags & SHOW_EQUIVALENT)) - return 0; - - printf("%s%s%s %s %s %s\n", - ansi_highlight_green(), "[EQUIVALENT]", ansi_normal(), - top, special_glyph(ARROW), bottom); - return 1; -} - -static int notify_override_redirected(const char *top, const char *bottom) { - if (!(arg_flags & SHOW_REDIRECTED)) - return 0; - - printf("%s%s%s %s %s %s\n", - ansi_highlight(), "[REDIRECTED]", ansi_normal(), - top, special_glyph(ARROW), bottom); - return 1; -} - -static int notify_override_overridden(const char *top, const char *bottom) { - if (!(arg_flags & SHOW_OVERRIDDEN)) - return 0; - - printf("%s%s%s %s %s %s\n", - ansi_highlight(), "[OVERRIDDEN]", ansi_normal(), - top, special_glyph(ARROW), bottom); - return 1; -} - -static int notify_override_extended(const char *top, const char *bottom) { - if (!(arg_flags & SHOW_EXTENDED)) - return 0; - - printf("%s%s%s %s %s %s\n", - ansi_highlight(), "[EXTENDED]", ansi_normal(), - top, special_glyph(ARROW), bottom); - return 1; -} - -static int notify_override_unchanged(const char *f) { - if (!(arg_flags & SHOW_UNCHANGED)) - return 0; - - printf("[UNCHANGED] %s\n", f); - return 1; -} - -static int found_override(const char *top, const char *bottom) { - _cleanup_free_ char *dest = NULL; - int k; - pid_t pid; - - assert(top); - assert(bottom); - - if (null_or_empty_path(top) > 0) - return notify_override_masked(top, bottom); - - k = readlink_malloc(top, &dest); - if (k >= 0) { - if (equivalent(dest, bottom) > 0) - return notify_override_equivalent(top, bottom); - else - return notify_override_redirected(top, bottom); - } - - k = notify_override_overridden(top, bottom); - if (!arg_diff) - return k; - - putchar('\n'); - - fflush(stdout); - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork off diff: %m"); - else if (pid == 0) { - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - execlp("diff", "diff", "-us", "--", bottom, top, NULL); - log_error_errno(errno, "Failed to execute diff: %m"); - _exit(EXIT_FAILURE); - } - - wait_for_terminate_and_warn("diff", pid, false); - putchar('\n'); - - return k; -} - -static int enumerate_dir_d(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *toppath, const char *drop) { - _cleanup_free_ char *unit = NULL; - _cleanup_free_ char *path = NULL; - _cleanup_strv_free_ char **list = NULL; - char **file; - char *c; - int r; - - assert(!endswith(drop, "/")); - - path = strjoin(toppath, "/", drop, NULL); - if (!path) - return -ENOMEM; - - log_debug("Looking at %s", path); - - unit = strdup(drop); - if (!unit) - return -ENOMEM; - - c = strrchr(unit, '.'); - if (!c) - return -EINVAL; - *c = 0; - - r = get_files_in_directory(path, &list); - if (r < 0) - return log_error_errno(r, "Failed to enumerate %s: %m", path); - - STRV_FOREACH(file, list) { - Hashmap *h; - int k; - char *p; - char *d; - - if (!endswith(*file, ".conf")) - continue; - - p = strjoin(path, "/", *file, NULL); - if (!p) - return -ENOMEM; - d = p + strlen(toppath) + 1; - - log_debug("Adding at top: %s %s %s", d, special_glyph(ARROW), p); - k = hashmap_put(top, d, p); - if (k >= 0) { - p = strdup(p); - if (!p) - return -ENOMEM; - d = p + strlen(toppath) + 1; - } else if (k != -EEXIST) { - free(p); - return k; - } - - log_debug("Adding at bottom: %s %s %s", d, special_glyph(ARROW), p); - free(hashmap_remove(bottom, d)); - k = hashmap_put(bottom, d, p); - if (k < 0) { - free(p); - return k; - } - - h = hashmap_get(drops, unit); - if (!h) { - h = hashmap_new(&string_hash_ops); - if (!h) - return -ENOMEM; - hashmap_put(drops, unit, h); - unit = strdup(unit); - if (!unit) - return -ENOMEM; - } - - p = strdup(p); - if (!p) - return -ENOMEM; - - log_debug("Adding to drops: %s %s %s %s %s", - unit, special_glyph(ARROW), basename(p), special_glyph(ARROW), p); - k = hashmap_put(h, basename(p), p); - if (k < 0) { - free(p); - if (k != -EEXIST) - return k; - } - } - return 0; -} - -static int enumerate_dir(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *path, bool dropins) { - _cleanup_closedir_ DIR *d; - - assert(top); - assert(bottom); - assert(drops); - assert(path); - - log_debug("Looking at %s", path); - - d = opendir(path); - if (!d) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open %s: %m", path); - } - - for (;;) { - struct dirent *de; - int k; - char *p; - - errno = 0; - de = readdir(d); - if (!de) - return -errno; - - dirent_ensure_type(d, de); - - if (dropins && de->d_type == DT_DIR && endswith(de->d_name, ".d")) - enumerate_dir_d(top, bottom, drops, path, de->d_name); - - if (!dirent_is_file(de)) - continue; - - p = strjoin(path, "/", de->d_name, NULL); - if (!p) - return -ENOMEM; - - log_debug("Adding at top: %s %s %s", basename(p), special_glyph(ARROW), p); - k = hashmap_put(top, basename(p), p); - if (k >= 0) { - p = strdup(p); - if (!p) - return -ENOMEM; - } else if (k != -EEXIST) { - free(p); - return k; - } - - log_debug("Adding at bottom: %s %s %s", basename(p), special_glyph(ARROW), p); - free(hashmap_remove(bottom, basename(p))); - k = hashmap_put(bottom, basename(p), p); - if (k < 0) { - free(p); - return k; - } - } -} - -static int process_suffix(const char *suffix, const char *onlyprefix) { - const char *p; - char *f; - Hashmap *top, *bottom, *drops; - Hashmap *h; - char *key; - int r = 0, k; - Iterator i, j; - int n_found = 0; - bool dropins; - - assert(suffix); - assert(!startswith(suffix, "/")); - assert(!strstr(suffix, "//")); - - dropins = nulstr_contains(have_dropins, suffix); - - top = hashmap_new(&string_hash_ops); - bottom = hashmap_new(&string_hash_ops); - drops = hashmap_new(&string_hash_ops); - if (!top || !bottom || !drops) { - r = -ENOMEM; - goto finish; - } - - NULSTR_FOREACH(p, prefixes) { - _cleanup_free_ char *t = NULL; - - t = strjoin(p, "/", suffix, NULL); - if (!t) { - r = -ENOMEM; - goto finish; - } - - k = enumerate_dir(top, bottom, drops, t, dropins); - if (r == 0) - r = k; - } - - HASHMAP_FOREACH_KEY(f, key, top, i) { - char *o; - - o = hashmap_get(bottom, key); - assert(o); - - if (!onlyprefix || startswith(o, onlyprefix)) { - if (path_equal(o, f)) { - notify_override_unchanged(f); - } else { - k = found_override(f, o); - if (k < 0) - r = k; - else - n_found += k; - } - } - - h = hashmap_get(drops, key); - if (h) - HASHMAP_FOREACH(o, h, j) - if (!onlyprefix || startswith(o, onlyprefix)) - n_found += notify_override_extended(f, o); - } - -finish: - hashmap_free_free(top); - hashmap_free_free(bottom); - - HASHMAP_FOREACH_KEY(h, key, drops, i) { - hashmap_free_free(hashmap_remove(drops, key)); - hashmap_remove(drops, key); - free(key); - } - hashmap_free(drops); - - return r < 0 ? r : n_found; -} - -static int process_suffixes(const char *onlyprefix) { - const char *n; - int n_found = 0, r; - - NULSTR_FOREACH(n, suffixes) { - r = process_suffix(n, onlyprefix); - if (r < 0) - return r; - - n_found += r; - } - - return n_found; -} - -static int process_suffix_chop(const char *arg) { - const char *p; - - assert(arg); - - if (!path_is_absolute(arg)) - return process_suffix(arg, NULL); - - /* Strip prefix from the suffix */ - NULSTR_FOREACH(p, prefixes) { - const char *suffix; - - suffix = startswith(arg, p); - if (suffix) { - suffix += strspn(suffix, "/"); - if (*suffix) - return process_suffix(suffix, NULL); - else - return process_suffixes(arg); - } - } - - log_error("Invalid suffix specification %s.", arg); - return -EINVAL; -} - -static void help(void) { - printf("%s [OPTIONS...] [SUFFIX...]\n\n" - "Find overridden configuration files.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --diff[=1|0] Show a diff when overridden files differ\n" - " -t --type=LIST... Only display a selected set of override types\n" - , program_invocation_short_name); -} - -static int parse_flags(const char *flag_str, int flags) { - const char *word, *state; - size_t l; - - FOREACH_WORD_SEPARATOR(word, l, flag_str, ",", state) { - if (strneq("masked", word, l)) - flags |= SHOW_MASKED; - else if (strneq ("equivalent", word, l)) - flags |= SHOW_EQUIVALENT; - else if (strneq("redirected", word, l)) - flags |= SHOW_REDIRECTED; - else if (strneq("overridden", word, l)) - flags |= SHOW_OVERRIDDEN; - else if (strneq("unchanged", word, l)) - flags |= SHOW_UNCHANGED; - else if (strneq("extended", word, l)) - flags |= SHOW_EXTENDED; - else if (strneq("default", word, l)) - flags |= SHOW_DEFAULTS; - else - return -EINVAL; - } - return flags; -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_NO_PAGER = 0x100, - ARG_DIFF, - ARG_VERSION - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "diff", optional_argument, NULL, ARG_DIFF }, - { "type", required_argument, NULL, 't' }, - {} - }; - - int c; - - assert(argc >= 1); - assert(argv); - - while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case 't': { - int f; - f = parse_flags(optarg, arg_flags); - if (f < 0) { - log_error("Failed to parse flags field."); - return -EINVAL; - } - arg_flags = f; - break; - } - - case ARG_DIFF: - if (!optarg) - arg_diff = 1; - else { - int b; - - b = parse_boolean(optarg); - if (b < 0) { - log_error("Failed to parse diff boolean."); - return -EINVAL; - } - - arg_diff = b; - } - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -int main(int argc, char *argv[]) { - int r, k, n_found = 0; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (arg_flags == 0) - arg_flags = SHOW_DEFAULTS; - - if (arg_diff < 0) - arg_diff = !!(arg_flags & SHOW_OVERRIDDEN); - else if (arg_diff) - arg_flags |= SHOW_OVERRIDDEN; - - pager_open(arg_no_pager, false); - - if (optind < argc) { - int i; - - for (i = optind; i < argc; i++) { - path_kill_slashes(argv[i]); - - k = process_suffix_chop(argv[i]); - if (k < 0) - r = k; - else - n_found += k; - } - - } else { - k = process_suffixes(NULL); - if (k < 0) - r = k; - else - n_found += k; - } - - if (r >= 0) - printf("%s%i overridden configuration files found.\n", n_found ? "\n" : "", n_found); - -finish: - pager_close(); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/detect-virt/Makefile b/src/detect-virt/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/detect-virt/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/detect-virt/detect-virt.c b/src/detect-virt/detect-virt.c deleted file mode 100644 index 5d51589a31..0000000000 --- a/src/detect-virt/detect-virt.c +++ /dev/null @@ -1,169 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include - -#include "util.h" -#include "virt.h" - -static bool arg_quiet = false; -static enum { - ANY_VIRTUALIZATION, - ONLY_VM, - ONLY_CONTAINER, - ONLY_CHROOT, -} arg_mode = ANY_VIRTUALIZATION; - -static void help(void) { - printf("%s [OPTIONS...]\n\n" - "Detect execution in a virtualized environment.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -c --container Only detect whether we are run in a container\n" - " -v --vm Only detect whether we are run in a VM\n" - " -r --chroot Detect whether we are run in a chroot() environment\n" - " -q --quiet Don't output anything, just set return value\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100 - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "container", no_argument, NULL, 'c' }, - { "vm", no_argument, NULL, 'v' }, - { "chroot", no_argument, NULL, 'r' }, - { "quiet", no_argument, NULL, 'q' }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "hqcvr", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case 'q': - arg_quiet = true; - break; - - case 'c': - arg_mode = ONLY_CONTAINER; - break; - - case 'v': - arg_mode = ONLY_VM; - break; - - case 'r': - arg_mode = ONLY_CHROOT; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (optind < argc) { - log_error("%s takes no arguments.", program_invocation_short_name); - return -EINVAL; - } - - return 1; -} - -int main(int argc, char *argv[]) { - int r; - - /* This is mostly intended to be used for scripts which want - * to detect whether we are being run in a virtualized - * environment or not */ - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; - - switch (arg_mode) { - - case ONLY_VM: - r = detect_vm(); - if (r < 0) { - log_error_errno(r, "Failed to check for VM: %m"); - return EXIT_FAILURE; - } - - break; - - case ONLY_CONTAINER: - r = detect_container(); - if (r < 0) { - log_error_errno(r, "Failed to check for container: %m"); - return EXIT_FAILURE; - } - - break; - - case ONLY_CHROOT: - r = running_in_chroot(); - if (r < 0) { - log_error_errno(r, "Failed to check for chroot() environment: %m"); - return EXIT_FAILURE; - } - - return r ? EXIT_SUCCESS : EXIT_FAILURE; - - case ANY_VIRTUALIZATION: - default: - r = detect_virtualization(); - if (r < 0) { - log_error_errno(r, "Failed to check for virtualization: %m"); - return EXIT_FAILURE; - } - - break; - } - - if (!arg_quiet) - puts(virtualization_to_string(r)); - - return r != VIRTUALIZATION_NONE ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/src/escape/Makefile b/src/escape/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/escape/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/escape/escape.c b/src/escape/escape.c deleted file mode 100644 index 9f39049577..0000000000 --- a/src/escape/escape.c +++ /dev/null @@ -1,237 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Michael Biebl - - 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 -#include - -#include "alloc-util.h" -#include "log.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" - -static enum { - ACTION_ESCAPE, - ACTION_UNESCAPE, - ACTION_MANGLE -} arg_action = ACTION_ESCAPE; -static const char *arg_suffix = NULL; -static const char *arg_template = NULL; -static bool arg_path = false; - -static void help(void) { - printf("%s [OPTIONS...] [NAME...]\n\n" - "Show system and user paths.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --suffix=SUFFIX Unit suffix to append to escaped strings\n" - " --template=TEMPLATE Insert strings as instance into template\n" - " -u --unescape Unescape strings\n" - " -m --mangle Mangle strings\n" - " -p --path When escaping/unescaping assume the string is a path\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_SUFFIX, - ARG_TEMPLATE - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "suffix", required_argument, NULL, ARG_SUFFIX }, - { "template", required_argument, NULL, ARG_TEMPLATE }, - { "unescape", no_argument, NULL, 'u' }, - { "mangle", no_argument, NULL, 'm' }, - { "path", no_argument, NULL, 'p' }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "hump", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_SUFFIX: - - if (unit_type_from_string(optarg) < 0) { - log_error("Invalid unit suffix type %s.", optarg); - return -EINVAL; - } - - arg_suffix = optarg; - break; - - case ARG_TEMPLATE: - - if (!unit_name_is_valid(optarg, UNIT_NAME_TEMPLATE)) { - log_error("Template name %s is not valid.", optarg); - return -EINVAL; - } - - arg_template = optarg; - break; - - case 'u': - arg_action = ACTION_UNESCAPE; - break; - - case 'm': - arg_action = ACTION_MANGLE; - break; - - case 'p': - arg_path = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (optind >= argc) { - log_error("Not enough arguments."); - return -EINVAL; - } - - if (arg_template && arg_suffix) { - log_error("--suffix= and --template= may not be combined."); - return -EINVAL; - } - - if ((arg_template || arg_suffix) && arg_action != ACTION_ESCAPE) { - log_error("--suffix= and --template= are not compatible with --unescape or --mangle."); - return -EINVAL; - } - - if (arg_path && !IN_SET(arg_action, ACTION_ESCAPE, ACTION_UNESCAPE)) { - log_error("--path may not be combined with --mangle."); - return -EINVAL; - } - - return 1; -} - -int main(int argc, char *argv[]) { - char **i; - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - STRV_FOREACH(i, argv + optind) { - _cleanup_free_ char *e = NULL; - - switch (arg_action) { - - case ACTION_ESCAPE: - if (arg_path) { - r = unit_name_path_escape(*i, &e); - if (r < 0) { - log_error_errno(r, "Failed to escape string: %m"); - goto finish; - } - } else { - e = unit_name_escape(*i); - if (!e) { - r = log_oom(); - goto finish; - } - } - - if (arg_template) { - char *x; - - r = unit_name_replace_instance(arg_template, e, &x); - if (r < 0) { - log_error_errno(r, "Failed to replace instance: %m"); - goto finish; - } - - free(e); - e = x; - } else if (arg_suffix) { - char *x; - - x = strjoin(e, ".", arg_suffix, NULL); - if (!x) { - r = log_oom(); - goto finish; - } - - free(e); - e = x; - } - - break; - - case ACTION_UNESCAPE: - if (arg_path) - r = unit_name_path_unescape(*i, &e); - else - r = unit_name_unescape(*i, &e); - - if (r < 0) { - log_error_errno(r, "Failed to unescape string: %m"); - goto finish; - } - break; - - case ACTION_MANGLE: - r = unit_name_mangle(*i, UNIT_NAME_NOGLOB, &e); - if (r < 0) { - log_error_errno(r, "Failed to mangle name: %m"); - goto finish; - } - break; - } - - if (i != argv+optind) - fputc(' ', stdout); - - fputs(e, stdout); - } - - fputc('\n', stdout); - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/firstboot/Makefile b/src/firstboot/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/firstboot/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c deleted file mode 100644 index 1e1a592b7c..0000000000 --- a/src/firstboot/firstboot.c +++ /dev/null @@ -1,870 +0,0 @@ -/*** - 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 -#include -#include - -#include "alloc-util.h" -#include "ask-password-api.h" -#include "copy.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "hostname-util.h" -#include "locale-util.h" -#include "mkdir.h" -#include "parse-util.h" -#include "path-util.h" -#include "random-util.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "time-util.h" -#include "umask-util.h" -#include "user-util.h" - -static char *arg_root = NULL; -static char *arg_locale = NULL; /* $LANG */ -static char *arg_locale_messages = NULL; /* $LC_MESSAGES */ -static char *arg_timezone = NULL; -static char *arg_hostname = NULL; -static sd_id128_t arg_machine_id = {}; -static char *arg_root_password = NULL; -static bool arg_prompt_locale = false; -static bool arg_prompt_timezone = false; -static bool arg_prompt_hostname = false; -static bool arg_prompt_root_password = false; -static bool arg_copy_locale = false; -static bool arg_copy_timezone = false; -static bool arg_copy_root_password = false; - -static bool press_any_key(void) { - char k = 0; - bool need_nl = true; - - printf("-- Press any key to proceed --"); - fflush(stdout); - - (void) read_one_char(stdin, &k, USEC_INFINITY, &need_nl); - - if (need_nl) - putchar('\n'); - - return k != 'q'; -} - -static void print_welcome(void) { - _cleanup_free_ char *pretty_name = NULL; - const char *os_release = NULL; - static bool done = false; - int r; - - if (done) - return; - - os_release = prefix_roota(arg_root, "/etc/os-release"); - r = parse_env_file(os_release, NEWLINE, - "PRETTY_NAME", &pretty_name, - NULL); - if (r == -ENOENT) { - - os_release = prefix_roota(arg_root, "/usr/lib/os-release"); - r = parse_env_file(os_release, NEWLINE, - "PRETTY_NAME", &pretty_name, - NULL); - } - - if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to read os-release file: %m"); - - printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n", - isempty(pretty_name) ? "GNU/Linux" : pretty_name); - - press_any_key(); - - done = true; -} - -static int show_menu(char **x, unsigned n_columns, unsigned width, unsigned percentage) { - unsigned n, per_column, i, j; - unsigned break_lines, break_modulo; - - assert(n_columns > 0); - - n = strv_length(x); - per_column = (n + n_columns - 1) / n_columns; - - break_lines = lines(); - if (break_lines > 2) - break_lines--; - - /* The first page gets two extra lines, since we want to show - * a title */ - break_modulo = break_lines; - if (break_modulo > 3) - break_modulo -= 3; - - for (i = 0; i < per_column; i++) { - - for (j = 0; j < n_columns; j ++) { - _cleanup_free_ char *e = NULL; - - if (j * per_column + i >= n) - break; - - e = ellipsize(x[j * per_column + i], width, percentage); - if (!e) - return log_oom(); - - printf("%4u) %-*s", j * per_column + i + 1, width, e); - } - - putchar('\n'); - - /* on the first screen we reserve 2 extra lines for the title */ - if (i % break_lines == break_modulo) { - if (!press_any_key()) - return 0; - } - } - - return 0; -} - -static int prompt_loop(const char *text, char **l, bool (*is_valid)(const char *name), char **ret) { - int r; - - assert(text); - assert(is_valid); - assert(ret); - - for (;;) { - _cleanup_free_ char *p = NULL; - unsigned u; - - r = ask_string(&p, "%s %s (empty to skip): ", special_glyph(TRIANGULAR_BULLET), text); - if (r < 0) - return log_error_errno(r, "Failed to query user: %m"); - - if (isempty(p)) { - log_warning("No data entered, skipping."); - return 0; - } - - r = safe_atou(p, &u); - if (r >= 0) { - char *c; - - if (u <= 0 || u > strv_length(l)) { - log_error("Specified entry number out of range."); - continue; - } - - log_info("Selected '%s'.", l[u-1]); - - c = strdup(l[u-1]); - if (!c) - return log_oom(); - - free(*ret); - *ret = c; - return 0; - } - - if (!is_valid(p)) { - log_error("Entered data invalid."); - continue; - } - - free(*ret); - *ret = p; - p = 0; - return 0; - } -} - -static int prompt_locale(void) { - _cleanup_strv_free_ char **locales = NULL; - int r; - - if (arg_locale || arg_locale_messages) - return 0; - - if (!arg_prompt_locale) - return 0; - - r = get_locales(&locales); - if (r < 0) - return log_error_errno(r, "Cannot query locales list: %m"); - - print_welcome(); - - printf("\nAvailable Locales:\n\n"); - r = show_menu(locales, 3, 22, 60); - if (r < 0) - return r; - - putchar('\n'); - - r = prompt_loop("Please enter system locale name or number", locales, locale_is_valid, &arg_locale); - if (r < 0) - return r; - - if (isempty(arg_locale)) - return 0; - - r = prompt_loop("Please enter system message locale name or number", locales, locale_is_valid, &arg_locale_messages); - if (r < 0) - return r; - - return 0; -} - -static int process_locale(void) { - const char *etc_localeconf; - char* locales[3]; - unsigned i = 0; - int r; - - etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf"); - if (laccess(etc_localeconf, F_OK) >= 0) - return 0; - - if (arg_copy_locale && arg_root) { - - mkdir_parents(etc_localeconf, 0755); - r = copy_file("/etc/locale.conf", etc_localeconf, 0, 0644, 0); - if (r != -ENOENT) { - if (r < 0) - return log_error_errno(r, "Failed to copy %s: %m", etc_localeconf); - - log_info("%s copied.", etc_localeconf); - return 0; - } - } - - r = prompt_locale(); - if (r < 0) - return r; - - if (!isempty(arg_locale)) - locales[i++] = strjoina("LANG=", arg_locale); - if (!isempty(arg_locale_messages) && !streq(arg_locale_messages, arg_locale)) - locales[i++] = strjoina("LC_MESSAGES=", arg_locale_messages); - - if (i == 0) - return 0; - - locales[i] = NULL; - - mkdir_parents(etc_localeconf, 0755); - r = write_env_file(etc_localeconf, locales); - if (r < 0) - return log_error_errno(r, "Failed to write %s: %m", etc_localeconf); - - log_info("%s written.", etc_localeconf); - return 0; -} - -static int prompt_timezone(void) { - _cleanup_strv_free_ char **zones = NULL; - int r; - - if (arg_timezone) - return 0; - - if (!arg_prompt_timezone) - return 0; - - r = get_timezones(&zones); - if (r < 0) - return log_error_errno(r, "Cannot query timezone list: %m"); - - print_welcome(); - - printf("\nAvailable Time Zones:\n\n"); - r = show_menu(zones, 3, 22, 30); - if (r < 0) - return r; - - putchar('\n'); - - r = prompt_loop("Please enter timezone name or number", zones, timezone_is_valid, &arg_timezone); - if (r < 0) - return r; - - return 0; -} - -static int process_timezone(void) { - const char *etc_localtime, *e; - int r; - - etc_localtime = prefix_roota(arg_root, "/etc/localtime"); - if (laccess(etc_localtime, F_OK) >= 0) - return 0; - - if (arg_copy_timezone && arg_root) { - _cleanup_free_ char *p = NULL; - - r = readlink_malloc("/etc/localtime", &p); - if (r != -ENOENT) { - if (r < 0) - return log_error_errno(r, "Failed to read host timezone: %m"); - - mkdir_parents(etc_localtime, 0755); - if (symlink(p, etc_localtime) < 0) - return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime); - - log_info("%s copied.", etc_localtime); - return 0; - } - } - - r = prompt_timezone(); - if (r < 0) - return r; - - if (isempty(arg_timezone)) - return 0; - - e = strjoina("../usr/share/zoneinfo/", arg_timezone); - - mkdir_parents(etc_localtime, 0755); - if (symlink(e, etc_localtime) < 0) - return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime); - - log_info("%s written", etc_localtime); - return 0; -} - -static int prompt_hostname(void) { - int r; - - if (arg_hostname) - return 0; - - if (!arg_prompt_hostname) - return 0; - - print_welcome(); - putchar('\n'); - - for (;;) { - _cleanup_free_ char *h = NULL; - - r = ask_string(&h, "%s Please enter hostname for new system (empty to skip): ", special_glyph(TRIANGULAR_BULLET)); - if (r < 0) - return log_error_errno(r, "Failed to query hostname: %m"); - - if (isempty(h)) { - log_warning("No hostname entered, skipping."); - break; - } - - if (!hostname_is_valid(h, true)) { - log_error("Specified hostname invalid."); - continue; - } - - /* Get rid of the trailing dot that we allow, but don't want to see */ - arg_hostname = hostname_cleanup(h); - h = NULL; - break; - } - - return 0; -} - -static int process_hostname(void) { - const char *etc_hostname; - int r; - - etc_hostname = prefix_roota(arg_root, "/etc/hostname"); - if (laccess(etc_hostname, F_OK) >= 0) - return 0; - - r = prompt_hostname(); - if (r < 0) - return r; - - if (isempty(arg_hostname)) - return 0; - - mkdir_parents(etc_hostname, 0755); - r = write_string_file(etc_hostname, arg_hostname, WRITE_STRING_FILE_CREATE); - if (r < 0) - return log_error_errno(r, "Failed to write %s: %m", etc_hostname); - - log_info("%s written.", etc_hostname); - return 0; -} - -static int process_machine_id(void) { - const char *etc_machine_id; - char id[SD_ID128_STRING_MAX]; - int r; - - etc_machine_id = prefix_roota(arg_root, "/etc/machine-id"); - if (laccess(etc_machine_id, F_OK) >= 0) - return 0; - - if (sd_id128_equal(arg_machine_id, SD_ID128_NULL)) - return 0; - - mkdir_parents(etc_machine_id, 0755); - r = write_string_file(etc_machine_id, sd_id128_to_string(arg_machine_id, id), WRITE_STRING_FILE_CREATE); - if (r < 0) - return log_error_errno(r, "Failed to write machine id: %m"); - - log_info("%s written.", etc_machine_id); - return 0; -} - -static int prompt_root_password(void) { - const char *msg1, *msg2, *etc_shadow; - int r; - - if (arg_root_password) - return 0; - - if (!arg_prompt_root_password) - return 0; - - etc_shadow = prefix_roota(arg_root, "/etc/shadow"); - if (laccess(etc_shadow, F_OK) >= 0) - return 0; - - print_welcome(); - putchar('\n'); - - msg1 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter a new root password (empty to skip): "); - msg2 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter new root password again: "); - - for (;;) { - _cleanup_string_free_erase_ char *a = NULL, *b = NULL; - - r = ask_password_tty(msg1, NULL, 0, 0, NULL, &a); - if (r < 0) - return log_error_errno(r, "Failed to query root password: %m"); - - if (isempty(a)) { - log_warning("No password entered, skipping."); - break; - } - - r = ask_password_tty(msg2, NULL, 0, 0, NULL, &b); - if (r < 0) - return log_error_errno(r, "Failed to query root password: %m"); - - if (!streq(a, b)) { - log_error("Entered passwords did not match, please try again."); - continue; - } - - arg_root_password = a; - a = NULL; - break; - } - - return 0; -} - -static int write_root_shadow(const char *path, const struct spwd *p) { - _cleanup_fclose_ FILE *f = NULL; - assert(path); - assert(p); - - RUN_WITH_UMASK(0777) - f = fopen(path, "wex"); - if (!f) - return -errno; - - errno = 0; - if (putspent(p, f) != 0) - return errno > 0 ? -errno : -EIO; - - return fflush_and_check(f); -} - -static int process_root_password(void) { - - static const char table[] = - "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "0123456789" - "./"; - - struct spwd item = { - .sp_namp = (char*) "root", - .sp_min = -1, - .sp_max = -1, - .sp_warn = -1, - .sp_inact = -1, - .sp_expire = -1, - .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */ - }; - - _cleanup_close_ int lock = -1; - char salt[3+16+1+1]; - uint8_t raw[16]; - unsigned i; - char *j; - - const char *etc_shadow; - int r; - - etc_shadow = prefix_roota(arg_root, "/etc/shadow"); - if (laccess(etc_shadow, F_OK) >= 0) - return 0; - - mkdir_parents(etc_shadow, 0755); - - lock = take_etc_passwd_lock(arg_root); - if (lock < 0) - return log_error_errno(lock, "Failed to take a lock: %m"); - - if (arg_copy_root_password && arg_root) { - struct spwd *p; - - errno = 0; - p = getspnam("root"); - if (p || errno != ENOENT) { - if (!p) { - if (!errno) - errno = EIO; - - return log_error_errno(errno, "Failed to find shadow entry for root: %m"); - } - - r = write_root_shadow(etc_shadow, p); - if (r < 0) - return log_error_errno(r, "Failed to write %s: %m", etc_shadow); - - log_info("%s copied.", etc_shadow); - return 0; - } - } - - r = prompt_root_password(); - if (r < 0) - return r; - - if (!arg_root_password) - return 0; - - r = dev_urandom(raw, 16); - if (r < 0) - return log_error_errno(r, "Failed to get salt: %m"); - - /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */ - assert_cc(sizeof(table) == 64 + 1); - j = stpcpy(salt, "$6$"); - for (i = 0; i < 16; i++) - j[i] = table[raw[i] & 63]; - j[i++] = '$'; - j[i] = 0; - - errno = 0; - item.sp_pwdp = crypt(arg_root_password, salt); - if (!item.sp_pwdp) { - if (!errno) - errno = EINVAL; - - return log_error_errno(errno, "Failed to encrypt password: %m"); - } - - item.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY); - - r = write_root_shadow(etc_shadow, &item); - if (r < 0) - return log_error_errno(r, "Failed to write %s: %m", etc_shadow); - - log_info("%s written.", etc_shadow); - return 0; -} - -static void help(void) { - printf("%s [OPTIONS...]\n\n" - "Configures basic settings of the system.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --locale=LOCALE Set primary locale (LANG=)\n" - " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n" - " --timezone=TIMEZONE Set timezone\n" - " --hostname=NAME Set host name\n" - " --machine-ID=ID Set machine ID\n" - " --root-password=PASSWORD Set root password\n" - " --root-password-file=FILE Set root password from file\n" - " --prompt-locale Prompt the user for locale settings\n" - " --prompt-timezone Prompt the user for timezone\n" - " --prompt-hostname Prompt the user for hostname\n" - " --prompt-root-password Prompt the user for root password\n" - " --prompt Prompt for all of the above\n" - " --copy-locale Copy locale from host\n" - " --copy-timezone Copy timezone from host\n" - " --copy-root-password Copy root password from host\n" - " --copy Copy locale, timezone, root password\n" - " --setup-machine-id Generate a new random machine ID\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_ROOT, - ARG_LOCALE, - ARG_LOCALE_MESSAGES, - ARG_TIMEZONE, - ARG_HOSTNAME, - ARG_MACHINE_ID, - ARG_ROOT_PASSWORD, - ARG_ROOT_PASSWORD_FILE, - ARG_PROMPT, - ARG_PROMPT_LOCALE, - ARG_PROMPT_TIMEZONE, - ARG_PROMPT_HOSTNAME, - ARG_PROMPT_ROOT_PASSWORD, - ARG_COPY, - ARG_COPY_LOCALE, - ARG_COPY_TIMEZONE, - ARG_COPY_ROOT_PASSWORD, - ARG_SETUP_MACHINE_ID, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "root", required_argument, NULL, ARG_ROOT }, - { "locale", required_argument, NULL, ARG_LOCALE }, - { "locale-messages", required_argument, NULL, ARG_LOCALE_MESSAGES }, - { "timezone", required_argument, NULL, ARG_TIMEZONE }, - { "hostname", required_argument, NULL, ARG_HOSTNAME }, - { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, - { "root-password", required_argument, NULL, ARG_ROOT_PASSWORD }, - { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE }, - { "prompt", no_argument, NULL, ARG_PROMPT }, - { "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE }, - { "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE }, - { "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME }, - { "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD }, - { "copy", no_argument, NULL, ARG_COPY }, - { "copy-locale", no_argument, NULL, ARG_COPY_LOCALE }, - { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE }, - { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD }, - { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID }, - {} - }; - - int r, c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_ROOT: - r = parse_path_argument_and_warn(optarg, true, &arg_root); - if (r < 0) - return r; - break; - - case ARG_LOCALE: - if (!locale_is_valid(optarg)) { - log_error("Locale %s is not valid.", optarg); - return -EINVAL; - } - - r = free_and_strdup(&arg_locale, optarg); - if (r < 0) - return log_oom(); - - break; - - case ARG_LOCALE_MESSAGES: - if (!locale_is_valid(optarg)) { - log_error("Locale %s is not valid.", optarg); - return -EINVAL; - } - - r = free_and_strdup(&arg_locale_messages, optarg); - if (r < 0) - return log_oom(); - - break; - - case ARG_TIMEZONE: - if (!timezone_is_valid(optarg)) { - log_error("Timezone %s is not valid.", optarg); - return -EINVAL; - } - - r = free_and_strdup(&arg_timezone, optarg); - if (r < 0) - return log_oom(); - - break; - - case ARG_ROOT_PASSWORD: - r = free_and_strdup(&arg_root_password, optarg); - if (r < 0) - return log_oom(); - break; - - case ARG_ROOT_PASSWORD_FILE: - arg_root_password = mfree(arg_root_password); - - r = read_one_line_file(optarg, &arg_root_password); - if (r < 0) - return log_error_errno(r, "Failed to read %s: %m", optarg); - - break; - - case ARG_HOSTNAME: - if (!hostname_is_valid(optarg, true)) { - log_error("Host name %s is not valid.", optarg); - return -EINVAL; - } - - hostname_cleanup(optarg); - r = free_and_strdup(&arg_hostname, optarg); - if (r < 0) - return log_oom(); - - break; - - case ARG_MACHINE_ID: - if (sd_id128_from_string(optarg, &arg_machine_id) < 0) { - log_error("Failed to parse machine id %s.", optarg); - return -EINVAL; - } - - break; - - case ARG_PROMPT: - arg_prompt_locale = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true; - break; - - case ARG_PROMPT_LOCALE: - arg_prompt_locale = true; - break; - - case ARG_PROMPT_TIMEZONE: - arg_prompt_timezone = true; - break; - - case ARG_PROMPT_HOSTNAME: - arg_prompt_hostname = true; - break; - - case ARG_PROMPT_ROOT_PASSWORD: - arg_prompt_root_password = true; - break; - - case ARG_COPY: - arg_copy_locale = arg_copy_timezone = arg_copy_root_password = true; - break; - - case ARG_COPY_LOCALE: - arg_copy_locale = true; - break; - - case ARG_COPY_TIMEZONE: - arg_copy_timezone = true; - break; - - case ARG_COPY_ROOT_PASSWORD: - arg_copy_root_password = true; - break; - - case ARG_SETUP_MACHINE_ID: - - r = sd_id128_randomize(&arg_machine_id); - if (r < 0) - return log_error_errno(r, "Failed to generate randomized machine ID: %m"); - - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -int main(int argc, char *argv[]) { - int r; - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - r = process_locale(); - if (r < 0) - goto finish; - - r = process_timezone(); - if (r < 0) - goto finish; - - r = process_hostname(); - if (r < 0) - goto finish; - - r = process_machine_id(); - if (r < 0) - goto finish; - - r = process_root_password(); - if (r < 0) - goto finish; - -finish: - free(arg_root); - free(arg_locale); - free(arg_locale_messages); - free(arg_timezone); - free(arg_hostname); - string_erase(arg_root_password); - free(arg_root_password); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/fsck/Makefile b/src/fsck/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/fsck/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/fsck/fsck.c b/src/fsck/fsck.c deleted file mode 100644 index d32e1d923e..0000000000 --- a/src/fsck/fsck.c +++ /dev/null @@ -1,487 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2014 Holger Hans Peter Freyther - - 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 -#include -#include -#include -#include -#include -#include - -#include "sd-bus.h" -#include "sd-device.h" - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-error.h" -#include "bus-util.h" -#include "device-util.h" -#include "fd-util.h" -#include "fs-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "proc-cmdline.h" -#include "process-util.h" -#include "signal-util.h" -#include "socket-util.h" -#include "special.h" -#include "stdio-util.h" -#include "util.h" - -/* exit codes as defined in fsck(8) */ -enum { - FSCK_SUCCESS = 0, - FSCK_ERROR_CORRECTED = 1, - FSCK_SYSTEM_SHOULD_REBOOT = 2, - FSCK_ERRORS_LEFT_UNCORRECTED = 4, - FSCK_OPERATIONAL_ERROR = 8, - FSCK_USAGE_OR_SYNTAX_ERROR = 16, - FSCK_USER_CANCELLED = 32, - FSCK_SHARED_LIB_ERROR = 128, -}; - -static bool arg_skip = false; -static bool arg_force = false; -static bool arg_show_progress = false; -static const char *arg_repair = "-a"; - -static void start_target(const char *target, const char *mode) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - assert(target); - - r = bus_connect_system_systemd(&bus); - if (r < 0) { - log_error_errno(r, "Failed to get D-Bus connection: %m"); - return; - } - - log_info("Running request %s/start/replace", target); - - /* Start these units only if we can replace base.target with it */ - r = sd_bus_call_method(bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartUnitReplace", - &error, - NULL, - "sss", "basic.target", target, mode); - - /* Don't print a warning if we aren't called during startup */ - if (r < 0 && !sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_JOB)) - log_error("Failed to start unit: %s", bus_error_message(&error, r)); -} - -static int parse_proc_cmdline_item(const char *key, const char *value) { - int r; - - assert(key); - - if (streq(key, "fsck.mode") && value) { - - if (streq(value, "auto")) - arg_force = arg_skip = false; - else if (streq(value, "force")) - arg_force = true; - else if (streq(value, "skip")) - arg_skip = true; - else - log_warning("Invalid fsck.mode= parameter '%s'. Ignoring.", value); - - } else if (streq(key, "fsck.repair") && value) { - - if (streq(value, "preen")) - arg_repair = "-a"; - else { - r = parse_boolean(value); - if (r > 0) - arg_repair = "-y"; - else if (r == 0) - arg_repair = "-n"; - else - log_warning("Invalid fsck.repair= parameter '%s'. Ignoring.", value); - } - } - -#ifdef HAVE_SYSV_COMPAT - else if (streq(key, "fastboot") && !value) { - log_warning("Please pass 'fsck.mode=skip' rather than 'fastboot' on the kernel command line."); - arg_skip = true; - - } else if (streq(key, "forcefsck") && !value) { - log_warning("Please pass 'fsck.mode=force' rather than 'forcefsck' on the kernel command line."); - arg_force = true; - } -#endif - - return 0; -} - -static void test_files(void) { - -#ifdef HAVE_SYSV_COMPAT - if (access("/fastboot", F_OK) >= 0) { - log_error("Please pass 'fsck.mode=skip' on the kernel command line rather than creating /fastboot on the root file system."); - arg_skip = true; - } - - if (access("/forcefsck", F_OK) >= 0) { - log_error("Please pass 'fsck.mode=force' on the kernel command line rather than creating /forcefsck on the root file system."); - arg_force = true; - } -#endif - - arg_show_progress = access("/run/systemd/show-status", F_OK) >= 0; -} - -static double percent(int pass, unsigned long cur, unsigned long max) { - /* Values stolen from e2fsck */ - - static const int pass_table[] = { - 0, 70, 90, 92, 95, 100 - }; - - if (pass <= 0) - return 0.0; - - if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0) - return 100.0; - - return (double) pass_table[pass-1] + - ((double) pass_table[pass] - (double) pass_table[pass-1]) * - (double) cur / (double) max; -} - -static int process_progress(int fd) { - _cleanup_fclose_ FILE *console = NULL, *f = NULL; - usec_t last = 0; - bool locked = false; - int clear = 0, r; - - /* No progress pipe to process? Then we are a NOP. */ - if (fd < 0) - return 0; - - f = fdopen(fd, "re"); - if (!f) { - safe_close(fd); - return -errno; - } - - console = fopen("/dev/console", "we"); - if (!console) - return -ENOMEM; - - for (;;) { - int pass, m; - unsigned long cur, max; - _cleanup_free_ char *device = NULL; - double p; - usec_t t; - - if (fscanf(f, "%i %lu %lu %ms", &pass, &cur, &max, &device) != 4) { - - if (ferror(f)) - r = log_warning_errno(errno, "Failed to read from progress pipe: %m"); - else if (feof(f)) - r = 0; - else { - log_warning("Failed to parse progress pipe data"); - r = -EBADMSG; - } - break; - } - - /* Only show one progress counter at max */ - if (!locked) { - if (flock(fileno(console), LOCK_EX|LOCK_NB) < 0) - continue; - - locked = true; - } - - /* Only update once every 50ms */ - t = now(CLOCK_MONOTONIC); - if (last + 50 * USEC_PER_MSEC > t) - continue; - - last = t; - - p = percent(pass, cur, max); - fprintf(console, "\r%s: fsck %3.1f%% complete...\r%n", device, p, &m); - fflush(console); - - if (m > clear) - clear = m; - } - - if (clear > 0) { - unsigned j; - - fputc('\r', console); - for (j = 0; j < (unsigned) clear; j++) - fputc(' ', console); - fputc('\r', console); - fflush(console); - } - - return r; -} - -static int fsck_progress_socket(void) { - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/fsck.progress", - }; - - int fd, r; - - fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (fd < 0) - return log_warning_errno(errno, "socket(): %m"); - - if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) { - r = log_full_errno(errno == ECONNREFUSED || errno == ENOENT ? LOG_DEBUG : LOG_WARNING, - errno, "Failed to connect to progress socket %s, ignoring: %m", sa.un.sun_path); - safe_close(fd); - return r; - } - - return fd; -} - -int main(int argc, char *argv[]) { - _cleanup_close_pair_ int progress_pipe[2] = { -1, -1 }; - _cleanup_(sd_device_unrefp) sd_device *dev = NULL; - const char *device, *type; - bool root_directory; - siginfo_t status; - struct stat st; - int r; - pid_t pid; - - if (argc > 2) { - log_error("This program expects one or no arguments."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - r = parse_proc_cmdline(parse_proc_cmdline_item); - if (r < 0) - log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - - test_files(); - - if (!arg_force && arg_skip) { - r = 0; - goto finish; - } - - if (argc > 1) { - device = argv[1]; - - if (stat(device, &st) < 0) { - r = log_error_errno(errno, "Failed to stat %s: %m", device); - goto finish; - } - - if (!S_ISBLK(st.st_mode)) { - log_error("%s is not a block device.", device); - r = -EINVAL; - goto finish; - } - - r = sd_device_new_from_devnum(&dev, 'b', st.st_rdev); - if (r < 0) { - log_error_errno(r, "Failed to detect device %s: %m", device); - goto finish; - } - - root_directory = false; - } else { - struct timespec times[2]; - - /* Find root device */ - - if (stat("/", &st) < 0) { - r = log_error_errno(errno, "Failed to stat() the root directory: %m"); - goto finish; - } - - /* Virtual root devices don't need an fsck */ - if (major(st.st_dev) == 0) { - log_debug("Root directory is virtual or btrfs, skipping check."); - r = 0; - goto finish; - } - - /* check if we are already writable */ - times[0] = st.st_atim; - times[1] = st.st_mtim; - - if (utimensat(AT_FDCWD, "/", times, 0) == 0) { - log_info("Root directory is writable, skipping check."); - r = 0; - goto finish; - } - - r = sd_device_new_from_devnum(&dev, 'b', st.st_dev); - if (r < 0) { - log_error_errno(r, "Failed to detect root device: %m"); - goto finish; - } - - r = sd_device_get_devname(dev, &device); - if (r < 0) { - log_error_errno(r, "Failed to detect device node of root directory: %m"); - goto finish; - } - - root_directory = true; - } - - r = sd_device_get_property_value(dev, "ID_FS_TYPE", &type); - if (r >= 0) { - r = fsck_exists(type); - if (r < 0) - log_warning_errno(r, "Couldn't detect if fsck.%s may be used for %s, proceeding: %m", type, device); - else if (r == 0) { - log_info("fsck.%s doesn't exist, not checking file system on %s.", type, device); - goto finish; - } - } - - if (arg_show_progress) { - if (pipe(progress_pipe) < 0) { - r = log_error_errno(errno, "pipe(): %m"); - goto finish; - } - } - - pid = fork(); - if (pid < 0) { - r = log_error_errno(errno, "fork(): %m"); - goto finish; - } - if (pid == 0) { - char dash_c[sizeof("-C")-1 + DECIMAL_STR_MAX(int) + 1]; - int progress_socket = -1; - const char *cmdline[9]; - int i = 0; - - /* Child */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - /* Close the reading side of the progress pipe */ - progress_pipe[0] = safe_close(progress_pipe[0]); - - /* Try to connect to a progress management daemon, if there is one */ - progress_socket = fsck_progress_socket(); - if (progress_socket >= 0) { - /* If this worked we close the progress pipe early, and just use the socket */ - progress_pipe[1] = safe_close(progress_pipe[1]); - xsprintf(dash_c, "-C%i", progress_socket); - } else if (progress_pipe[1] >= 0) { - /* Otherwise if we have the progress pipe to our own local handle, we use it */ - xsprintf(dash_c, "-C%i", progress_pipe[1]); - } else - dash_c[0] = 0; - - cmdline[i++] = "/sbin/fsck"; - cmdline[i++] = arg_repair; - cmdline[i++] = "-T"; - - /* - * Since util-linux v2.25 fsck uses /run/fsck/.lock files. - * The previous versions use flock for the device and conflict with - * udevd, see https://bugs.freedesktop.org/show_bug.cgi?id=79576#c5 - */ - cmdline[i++] = "-l"; - - if (!root_directory) - cmdline[i++] = "-M"; - - if (arg_force) - cmdline[i++] = "-f"; - - if (!isempty(dash_c)) - cmdline[i++] = dash_c; - - cmdline[i++] = device; - cmdline[i++] = NULL; - - execv(cmdline[0], (char**) cmdline); - _exit(FSCK_OPERATIONAL_ERROR); - } - - progress_pipe[1] = safe_close(progress_pipe[1]); - (void) process_progress(progress_pipe[0]); - progress_pipe[0] = -1; - - r = wait_for_terminate(pid, &status); - if (r < 0) { - log_error_errno(r, "waitid(): %m"); - goto finish; - } - - if (status.si_code != CLD_EXITED || (status.si_status & ~1)) { - - if (status.si_code == CLD_KILLED || status.si_code == CLD_DUMPED) - log_error("fsck terminated by signal %s.", signal_to_string(status.si_status)); - else if (status.si_code == CLD_EXITED) - log_error("fsck failed with error code %i.", status.si_status); - else - log_error("fsck failed due to unknown reason."); - - r = -EINVAL; - - if (status.si_code == CLD_EXITED && (status.si_status & FSCK_SYSTEM_SHOULD_REBOOT) && root_directory) - /* System should be rebooted. */ - start_target(SPECIAL_REBOOT_TARGET, "replace-irreversibly"); - else if (status.si_code == CLD_EXITED && (status.si_status & (FSCK_SYSTEM_SHOULD_REBOOT | FSCK_ERRORS_LEFT_UNCORRECTED))) - /* Some other problem */ - start_target(SPECIAL_EMERGENCY_TARGET, "replace"); - else { - log_warning("Ignoring error."); - r = 0; - } - - } else - r = 0; - - if (status.si_code == CLD_EXITED && (status.si_status & FSCK_ERROR_CORRECTED)) - (void) touch("/run/systemd/quotacheck"); - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/fstab-generator/Makefile b/src/fstab-generator/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/fstab-generator/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c deleted file mode 100644 index 108522873e..0000000000 --- a/src/fstab-generator/fstab-generator.c +++ /dev/null @@ -1,713 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fstab-util.h" -#include "generator.h" -#include "log.h" -#include "mkdir.h" -#include "mount-setup.h" -#include "mount-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "proc-cmdline.h" -#include "special.h" -#include "stat-util.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "util.h" -#include "virt.h" - -static const char *arg_dest = "/tmp"; -static bool arg_fstab_enabled = true; -static char *arg_root_what = NULL; -static char *arg_root_fstype = NULL; -static char *arg_root_options = NULL; -static int arg_root_rw = -1; -static char *arg_usr_what = NULL; -static char *arg_usr_fstype = NULL; -static char *arg_usr_options = NULL; - -static int add_swap( - const char *what, - struct mntent *me, - bool noauto, - bool nofail) { - - _cleanup_free_ char *name = NULL, *unit = NULL, *lnk = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(what); - assert(me); - - if (access("/proc/swaps", F_OK) < 0) { - log_info("Swap not supported, ignoring fstab swap entry for %s.", what); - return 0; - } - - if (detect_container() > 0) { - log_info("Running in a container, ignoring fstab swap entry for %s.", what); - return 0; - } - - r = unit_name_from_path(what, ".swap", &name); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - - unit = strjoin(arg_dest, "/", name, NULL); - if (!unit) - return log_oom(); - - f = fopen(unit, "wxe"); - if (!f) { - if (errno == EEXIST) - log_error("Failed to create swap unit file %s, as it already exists. Duplicate entry in /etc/fstab?", unit); - else - log_error_errno(errno, "Failed to create unit file %s: %m", unit); - return -errno; - } - - fprintf(f, - "# Automatically generated by systemd-fstab-generator\n\n" - "[Unit]\n" - "SourcePath=/etc/fstab\n" - "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n\n" - "[Swap]\n" - "What=%s\n", - what); - - if (!isempty(me->mnt_opts) && !streq(me->mnt_opts, "defaults")) - fprintf(f, "Options=%s\n", me->mnt_opts); - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write unit file %s: %m", unit); - - /* use what as where, to have a nicer error message */ - r = generator_write_timeouts(arg_dest, what, what, me->mnt_opts, NULL); - if (r < 0) - return r; - - if (!noauto) { - lnk = strjoin(arg_dest, "/" SPECIAL_SWAP_TARGET, - nofail ? ".wants/" : ".requires/", name, NULL); - if (!lnk) - return log_oom(); - - mkdir_parents_label(lnk, 0755); - if (symlink(unit, lnk) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); - } - - return 0; -} - -static bool mount_is_network(struct mntent *me) { - assert(me); - - return fstab_test_option(me->mnt_opts, "_netdev\0") || - fstype_is_network(me->mnt_type); -} - -static bool mount_in_initrd(struct mntent *me) { - assert(me); - - return fstab_test_option(me->mnt_opts, "x-initrd.mount\0") || - streq(me->mnt_dir, "/usr"); -} - -static int write_idle_timeout(FILE *f, const char *where, const char *opts) { - _cleanup_free_ char *timeout = NULL; - char timespan[FORMAT_TIMESPAN_MAX]; - usec_t u; - int r; - - r = fstab_filter_options(opts, "x-systemd.idle-timeout\0", NULL, &timeout, NULL); - if (r < 0) - return log_warning_errno(r, "Failed to parse options: %m"); - if (r == 0) - return 0; - - r = parse_sec(timeout, &u); - if (r < 0) { - log_warning("Failed to parse timeout for %s, ignoring: %s", where, timeout); - return 0; - } - - fprintf(f, "TimeoutIdleSec=%s\n", format_timespan(timespan, sizeof(timespan), u, 0)); - - return 0; -} - -static int write_requires_after(FILE *f, const char *opts) { - _cleanup_strv_free_ char **names = NULL, **units = NULL; - _cleanup_free_ char *res = NULL; - char **s; - int r; - - assert(f); - assert(opts); - - r = fstab_extract_values(opts, "x-systemd.requires", &names); - if (r < 0) - return log_warning_errno(r, "Failed to parse options: %m"); - if (r == 0) - return 0; - - STRV_FOREACH(s, names) { - char *x; - - r = unit_name_mangle_with_suffix(*s, UNIT_NAME_NOGLOB, ".mount", &x); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - r = strv_consume(&units, x); - if (r < 0) - return log_oom(); - } - - if (units) { - res = strv_join(units, " "); - if (!res) - return log_oom(); - fprintf(f, "After=%1$s\nRequires=%1$s\n", res); - } - - return 0; -} - -static int write_requires_mounts_for(FILE *f, const char *opts) { - _cleanup_strv_free_ char **paths = NULL; - _cleanup_free_ char *res = NULL; - int r; - - assert(f); - assert(opts); - - r = fstab_extract_values(opts, "x-systemd.requires-mounts-for", &paths); - if (r < 0) - return log_warning_errno(r, "Failed to parse options: %m"); - if (r == 0) - return 0; - - res = strv_join(paths, " "); - if (!res) - return log_oom(); - - fprintf(f, "RequiresMountsFor=%s\n", res); - - return 0; -} - -static int add_mount( - const char *what, - const char *where, - const char *fstype, - const char *opts, - int passno, - bool noauto, - bool nofail, - bool automount, - const char *post, - const char *source) { - - _cleanup_free_ char - *name = NULL, *unit = NULL, *lnk = NULL, - *automount_name = NULL, *automount_unit = NULL, - *filtered = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(what); - assert(where); - assert(opts); - assert(post); - assert(source); - - if (streq_ptr(fstype, "autofs")) - return 0; - - if (!is_path(where)) { - log_warning("Mount point %s is not a valid path, ignoring.", where); - return 0; - } - - if (mount_point_is_api(where) || - mount_point_ignore(where)) - return 0; - - if (path_equal(where, "/")) { - if (noauto) - log_warning("Ignoring \"noauto\" for root device"); - if (nofail) - log_warning("Ignoring \"nofail\" for root device"); - if (automount) - log_warning("Ignoring automount option for root device"); - - noauto = nofail = automount = false; - } - - r = unit_name_from_path(where, ".mount", &name); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - - unit = strjoin(arg_dest, "/", name, NULL); - if (!unit) - return log_oom(); - - f = fopen(unit, "wxe"); - if (!f) { - if (errno == EEXIST) - log_error("Failed to create mount unit file %s, as it already exists. Duplicate entry in /etc/fstab?", unit); - else - log_error_errno(errno, "Failed to create unit file %s: %m", unit); - return -errno; - } - - fprintf(f, - "# Automatically generated by systemd-fstab-generator\n\n" - "[Unit]\n" - "SourcePath=%s\n" - "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n", - source); - - if (!noauto && !nofail && !automount) - fprintf(f, "Before=%s\n", post); - - if (!automount && opts) { - r = write_requires_after(f, opts); - if (r < 0) - return r; - r = write_requires_mounts_for(f, opts); - if (r < 0) - return r; - } - - if (passno != 0) { - r = generator_write_fsck_deps(f, arg_dest, what, where, fstype); - if (r < 0) - return r; - } - - fprintf(f, - "\n" - "[Mount]\n" - "What=%s\n" - "Where=%s\n", - what, - where); - - if (!isempty(fstype) && !streq(fstype, "auto")) - fprintf(f, "Type=%s\n", fstype); - - r = generator_write_timeouts(arg_dest, what, where, opts, &filtered); - if (r < 0) - return r; - - if (!isempty(filtered) && !streq(filtered, "defaults")) - fprintf(f, "Options=%s\n", filtered); - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write unit file %s: %m", unit); - - if (!noauto && !automount) { - lnk = strjoin(arg_dest, "/", post, nofail ? ".wants/" : ".requires/", name, NULL); - if (!lnk) - return log_oom(); - - mkdir_parents_label(lnk, 0755); - if (symlink(unit, lnk) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); - } - - if (automount) { - r = unit_name_from_path(where, ".automount", &automount_name); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - - automount_unit = strjoin(arg_dest, "/", automount_name, NULL); - if (!automount_unit) - return log_oom(); - - fclose(f); - f = fopen(automount_unit, "wxe"); - if (!f) - return log_error_errno(errno, "Failed to create unit file %s: %m", automount_unit); - - fprintf(f, - "# Automatically generated by systemd-fstab-generator\n\n" - "[Unit]\n" - "SourcePath=%s\n" - "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n", - source); - - fprintf(f, "Before=%s\n", post); - - if (opts) { - r = write_requires_after(f, opts); - if (r < 0) - return r; - r = write_requires_mounts_for(f, opts); - if (r < 0) - return r; - } - - fprintf(f, - "\n" - "[Automount]\n" - "Where=%s\n", - where); - - r = write_idle_timeout(f, where, opts); - if (r < 0) - return r; - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write unit file %s: %m", automount_unit); - - free(lnk); - lnk = strjoin(arg_dest, "/", post, nofail ? ".wants/" : ".requires/", automount_name, NULL); - if (!lnk) - return log_oom(); - - mkdir_parents_label(lnk, 0755); - if (symlink(automount_unit, lnk) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); - } - - return 0; -} - -static int parse_fstab(bool initrd) { - _cleanup_endmntent_ FILE *f = NULL; - const char *fstab_path; - struct mntent *me; - int r = 0; - - fstab_path = initrd ? "/sysroot/etc/fstab" : "/etc/fstab"; - f = setmntent(fstab_path, "re"); - if (!f) { - if (errno == ENOENT) - return 0; - - log_error_errno(errno, "Failed to open %s: %m", fstab_path); - return -errno; - } - - while ((me = getmntent(f))) { - _cleanup_free_ char *where = NULL, *what = NULL; - bool noauto, nofail; - int k; - - if (initrd && !mount_in_initrd(me)) - continue; - - what = fstab_node_to_udev_node(me->mnt_fsname); - if (!what) - return log_oom(); - - if (is_device_path(what) && path_is_read_only_fs("sys") > 0) { - log_info("Running in a container, ignoring fstab device entry for %s.", what); - continue; - } - - where = initrd ? strappend("/sysroot/", me->mnt_dir) : strdup(me->mnt_dir); - if (!where) - return log_oom(); - - if (is_path(where)) - path_kill_slashes(where); - - noauto = fstab_test_yes_no_option(me->mnt_opts, "noauto\0" "auto\0"); - nofail = fstab_test_yes_no_option(me->mnt_opts, "nofail\0" "fail\0"); - log_debug("Found entry what=%s where=%s type=%s nofail=%s noauto=%s", - what, where, me->mnt_type, - yes_no(noauto), yes_no(nofail)); - - if (streq(me->mnt_type, "swap")) - k = add_swap(what, me, noauto, nofail); - else { - bool automount; - const char *post; - - automount = fstab_test_option(me->mnt_opts, - "comment=systemd.automount\0" - "x-systemd.automount\0"); - if (initrd) - post = SPECIAL_INITRD_FS_TARGET; - else if (mount_is_network(me)) - post = SPECIAL_REMOTE_FS_TARGET; - else - post = SPECIAL_LOCAL_FS_TARGET; - - k = add_mount(what, - where, - me->mnt_type, - me->mnt_opts, - me->mnt_passno, - noauto, - nofail, - automount, - post, - fstab_path); - } - - if (k < 0) - r = k; - } - - return r; -} - -static int add_sysroot_mount(void) { - _cleanup_free_ char *what = NULL; - const char *opts; - int r; - - if (isempty(arg_root_what)) { - log_debug("Could not find a root= entry on the kernel command line."); - return 0; - } - - what = fstab_node_to_udev_node(arg_root_what); - if (!what) - return log_oom(); - - if (!arg_root_options) - opts = arg_root_rw > 0 ? "rw" : "ro"; - else if (arg_root_rw >= 0 || - !fstab_test_option(arg_root_options, "ro\0" "rw\0")) - opts = strjoina(arg_root_options, ",", arg_root_rw > 0 ? "rw" : "ro"); - else - opts = arg_root_options; - - log_debug("Found entry what=%s where=/sysroot type=%s", what, strna(arg_root_fstype)); - - if (is_device_path(what)) { - r = generator_write_initrd_root_device_deps(arg_dest, what); - if (r < 0) - return r; - } - - return add_mount(what, - "/sysroot", - arg_root_fstype, - opts, - is_device_path(what) ? 1 : 0, - false, - false, - false, - SPECIAL_INITRD_ROOT_FS_TARGET, - "/proc/cmdline"); -} - -static int add_sysroot_usr_mount(void) { - _cleanup_free_ char *what = NULL; - const char *opts; - - if (!arg_usr_what && !arg_usr_fstype && !arg_usr_options) - return 0; - - if (arg_root_what && !arg_usr_what) { - arg_usr_what = strdup(arg_root_what); - - if (!arg_usr_what) - return log_oom(); - } - - if (arg_root_fstype && !arg_usr_fstype) { - arg_usr_fstype = strdup(arg_root_fstype); - - if (!arg_usr_fstype) - return log_oom(); - } - - if (arg_root_options && !arg_usr_options) { - arg_usr_options = strdup(arg_root_options); - - if (!arg_usr_options) - return log_oom(); - } - - if (!arg_usr_what) - return 0; - - what = fstab_node_to_udev_node(arg_usr_what); - if (!path_is_absolute(what)) { - log_debug("Skipping entry what=%s where=/sysroot/usr type=%s", what, strna(arg_usr_fstype)); - return -1; - } - - if (!arg_usr_options) - opts = arg_root_rw > 0 ? "rw" : "ro"; - else if (!fstab_test_option(arg_usr_options, "ro\0" "rw\0")) - opts = strjoina(arg_usr_options, ",", arg_root_rw > 0 ? "rw" : "ro"); - else - opts = arg_usr_options; - - log_debug("Found entry what=%s where=/sysroot/usr type=%s", what, strna(arg_usr_fstype)); - return add_mount(what, - "/sysroot/usr", - arg_usr_fstype, - opts, - 1, - false, - false, - false, - SPECIAL_INITRD_FS_TARGET, - "/proc/cmdline"); -} - -static int parse_proc_cmdline_item(const char *key, const char *value) { - int r; - - /* root=, usr=, usrfstype= and roofstype= may occur more than once, the last - * instance should take precedence. In the case of multiple rootflags= - * or usrflags= the arguments should be concatenated */ - - if (STR_IN_SET(key, "fstab", "rd.fstab") && value) { - - r = parse_boolean(value); - if (r < 0) - log_warning("Failed to parse fstab switch %s. Ignoring.", value); - else - arg_fstab_enabled = r; - - } else if (streq(key, "root") && value) { - - if (free_and_strdup(&arg_root_what, value) < 0) - return log_oom(); - - } else if (streq(key, "rootfstype") && value) { - - if (free_and_strdup(&arg_root_fstype, value) < 0) - return log_oom(); - - } else if (streq(key, "rootflags") && value) { - char *o; - - o = arg_root_options ? - strjoin(arg_root_options, ",", value, NULL) : - strdup(value); - if (!o) - return log_oom(); - - free(arg_root_options); - arg_root_options = o; - - } else if (streq(key, "mount.usr") && value) { - - if (free_and_strdup(&arg_usr_what, value) < 0) - return log_oom(); - - } else if (streq(key, "mount.usrfstype") && value) { - - if (free_and_strdup(&arg_usr_fstype, value) < 0) - return log_oom(); - - } else if (streq(key, "mount.usrflags") && value) { - char *o; - - o = arg_usr_options ? - strjoin(arg_usr_options, ",", value, NULL) : - strdup(value); - if (!o) - return log_oom(); - - free(arg_usr_options); - arg_usr_options = o; - - } else if (streq(key, "rw") && !value) - arg_root_rw = true; - else if (streq(key, "ro") && !value) - arg_root_rw = false; - - return 0; -} - -int main(int argc, char *argv[]) { - int r = 0; - - if (argc > 1 && argc != 4) { - log_error("This program takes three or no arguments."); - return EXIT_FAILURE; - } - - if (argc > 1) - arg_dest = argv[1]; - - log_set_target(LOG_TARGET_SAFE); - log_parse_environment(); - log_open(); - - umask(0022); - - r = parse_proc_cmdline(parse_proc_cmdline_item); - if (r < 0) - log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - - /* Always honour root= and usr= in the kernel command line if we are in an initrd */ - if (in_initrd()) { - r = add_sysroot_mount(); - if (r == 0) - r = add_sysroot_usr_mount(); - } - - /* Honour /etc/fstab only when that's enabled */ - if (arg_fstab_enabled) { - int k; - - log_debug("Parsing /etc/fstab"); - - /* Parse the local /etc/fstab, possibly from the initrd */ - k = parse_fstab(false); - if (k < 0) - r = k; - - /* If running in the initrd also parse the /etc/fstab from the host */ - if (in_initrd()) { - log_debug("Parsing /sysroot/etc/fstab"); - - k = parse_fstab(true); - if (k < 0) - r = k; - } - } - - free(arg_root_what); - free(arg_root_fstype); - free(arg_root_options); - - free(arg_usr_what); - free(arg_usr_fstype); - free(arg_usr_options); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/getty-generator/Makefile b/src/getty-generator/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/getty-generator/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/getty-generator/getty-generator.c b/src/getty-generator/getty-generator.c deleted file mode 100644 index b15c76b5b8..0000000000 --- a/src/getty-generator/getty-generator.c +++ /dev/null @@ -1,233 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "log.h" -#include "mkdir.h" -#include "path-util.h" -#include "process-util.h" -#include "string-util.h" -#include "terminal-util.h" -#include "unit-name.h" -#include "util.h" -#include "virt.h" - -static const char *arg_dest = "/tmp"; - -static int add_symlink(const char *fservice, const char *tservice) { - char *from, *to; - int r; - - assert(fservice); - assert(tservice); - - from = strjoina(SYSTEM_DATA_UNIT_PATH "/", fservice); - to = strjoina(arg_dest, "/getty.target.wants/", tservice); - - mkdir_parents_label(to, 0755); - - r = symlink(from, to); - if (r < 0) { - /* In case console=hvc0 is passed this will very likely result in EEXIST */ - if (errno == EEXIST) - return 0; - - return log_error_errno(errno, "Failed to create symlink %s: %m", to); - } - - return 0; -} - -static int add_serial_getty(const char *tty) { - _cleanup_free_ char *n = NULL; - int r; - - assert(tty); - - log_debug("Automatically adding serial getty for /dev/%s.", tty); - - r = unit_name_from_path_instance("serial-getty", tty, ".service", &n); - if (r < 0) - return log_error_errno(r, "Failed to generate service name: %m"); - - return add_symlink("serial-getty@.service", n); -} - -static int add_container_getty(const char *tty) { - _cleanup_free_ char *n = NULL; - int r; - - assert(tty); - - log_debug("Automatically adding container getty for /dev/pts/%s.", tty); - - r = unit_name_from_path_instance("container-getty", tty, ".service", &n); - if (r < 0) - return log_error_errno(r, "Failed to generate service name: %m"); - - return add_symlink("container-getty@.service", n); -} - -static int verify_tty(const char *name) { - _cleanup_close_ int fd = -1; - const char *p; - - /* Some TTYs are weird and have been enumerated but don't work - * when you try to use them, such as classic ttyS0 and - * friends. Let's check that and open the device and run - * isatty() on it. */ - - p = strjoina("/dev/", name); - - /* O_NONBLOCK is essential here, to make sure we don't wait - * for DCD */ - fd = open(p, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC|O_NOFOLLOW); - if (fd < 0) - return -errno; - - errno = 0; - if (isatty(fd) <= 0) - return errno > 0 ? -errno : -EIO; - - return 0; -} - -int main(int argc, char *argv[]) { - - static const char virtualization_consoles[] = - "hvc0\0" - "xvc0\0" - "hvsi0\0" - "sclp_line0\0" - "ttysclp0\0" - "3270!tty1\0"; - - _cleanup_free_ char *active = NULL; - const char *j; - int r; - - if (argc > 1 && argc != 4) { - log_error("This program takes three or no arguments."); - return EXIT_FAILURE; - } - - if (argc > 1) - arg_dest = argv[1]; - - log_set_target(LOG_TARGET_SAFE); - log_parse_environment(); - log_open(); - - umask(0022); - - if (detect_container() > 0) { - _cleanup_free_ char *container_ttys = NULL; - - log_debug("Automatically adding console shell."); - - if (add_symlink("console-getty.service", "console-getty.service") < 0) - return EXIT_FAILURE; - - /* When $container_ttys is set for PID 1, spawn - * gettys on all ptys named therein. Note that despite - * the variable name we only support ptys here. */ - - r = getenv_for_pid(1, "container_ttys", &container_ttys); - if (r > 0) { - const char *word, *state; - size_t l; - - FOREACH_WORD(word, l, container_ttys, state) { - const char *t; - char tty[l + 1]; - - memcpy(tty, word, l); - tty[l] = 0; - - /* First strip off /dev/ if it is specified */ - t = path_startswith(tty, "/dev/"); - if (!t) - t = tty; - - /* Then, make sure it's actually a pty */ - t = path_startswith(t, "pts/"); - if (!t) - continue; - - if (add_container_getty(t) < 0) - return EXIT_FAILURE; - } - } - - /* Don't add any further magic if we are in a container */ - return EXIT_SUCCESS; - } - - if (read_one_line_file("/sys/class/tty/console/active", &active) >= 0) { - const char *word, *state; - size_t l; - - /* Automatically add in a serial getty on all active - * kernel consoles */ - FOREACH_WORD(word, l, active, state) { - _cleanup_free_ char *tty = NULL; - - tty = strndup(word, l); - if (!tty) { - log_oom(); - return EXIT_FAILURE; - } - - if (isempty(tty) || tty_is_vc(tty)) - continue; - - if (verify_tty(tty) < 0) - continue; - - /* We assume that gettys on virtual terminals are - * started via manual configuration and do this magic - * only for non-VC terminals. */ - - if (add_serial_getty(tty) < 0) - return EXIT_FAILURE; - } - } - - /* Automatically add in a serial getty on the first - * virtualizer console */ - NULSTR_FOREACH(j, virtualization_consoles) { - char *p; - - p = strjoina("/sys/class/tty/", j); - if (access(p, F_OK) < 0) - continue; - - if (add_serial_getty(j) < 0) - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} diff --git a/src/gpt-auto-generator/Makefile b/src/gpt-auto-generator/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/gpt-auto-generator/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c deleted file mode 100644 index a4938a7c3a..0000000000 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ /dev/null @@ -1,1042 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 -#include -#include - -#include "libudev.h" -#include "sd-id128.h" - -#include "alloc-util.h" -#include "blkid-util.h" -#include "btrfs-util.h" -#include "dirent-util.h" -#include "efivars.h" -#include "fd-util.h" -#include "fileio.h" -#include "fstab-util.h" -#include "generator.h" -#include "gpt.h" -#include "missing.h" -#include "mkdir.h" -#include "mount-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "proc-cmdline.h" -#include "special.h" -#include "stat-util.h" -#include "string-util.h" -#include "udev-util.h" -#include "unit-name.h" -#include "util.h" -#include "virt.h" - -static const char *arg_dest = "/tmp"; -static bool arg_enabled = true; -static bool arg_root_enabled = true; -static bool arg_root_rw = false; - -static int add_cryptsetup(const char *id, const char *what, bool rw, char **device) { - _cleanup_free_ char *e = NULL, *n = NULL, *p = NULL, *d = NULL, *to = NULL; - _cleanup_fclose_ FILE *f = NULL; - char *from, *ret; - int r; - - assert(id); - assert(what); - assert(device); - - r = unit_name_from_path(what, ".device", &d); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - - e = unit_name_escape(id); - if (!e) - return log_oom(); - - r = unit_name_build("systemd-cryptsetup", e, ".service", &n); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - - p = strjoin(arg_dest, "/", n, NULL); - if (!p) - return log_oom(); - - f = fopen(p, "wxe"); - if (!f) - return log_error_errno(errno, "Failed to create unit file %s: %m", p); - - fprintf(f, - "# Automatically generated by systemd-gpt-auto-generator\n\n" - "[Unit]\n" - "Description=Cryptography Setup for %%I\n" - "Documentation=man:systemd-gpt-auto-generator(8) man:systemd-cryptsetup@.service(8)\n" - "DefaultDependencies=no\n" - "Conflicts=umount.target\n" - "BindsTo=dev-mapper-%%i.device %s\n" - "Before=umount.target cryptsetup.target\n" - "After=%s\n" - "IgnoreOnIsolate=true\n" - "[Service]\n" - "Type=oneshot\n" - "RemainAfterExit=yes\n" - "TimeoutSec=0\n" /* the binary handles timeouts anyway */ - "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '' '%s'\n" - "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n", - d, d, - id, what, rw ? "" : "read-only", - id); - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write file %s: %m", p); - - from = strjoina("../", n); - - to = strjoin(arg_dest, "/", d, ".wants/", n, NULL); - if (!to) - return log_oom(); - - mkdir_parents_label(to, 0755); - if (symlink(from, to) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", to); - - free(to); - to = strjoin(arg_dest, "/cryptsetup.target.requires/", n, NULL); - if (!to) - return log_oom(); - - mkdir_parents_label(to, 0755); - if (symlink(from, to) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", to); - - free(to); - to = strjoin(arg_dest, "/dev-mapper-", e, ".device.requires/", n, NULL); - if (!to) - return log_oom(); - - mkdir_parents_label(to, 0755); - if (symlink(from, to) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", to); - - free(p); - p = strjoin(arg_dest, "/dev-mapper-", e, ".device.d/50-job-timeout-sec-0.conf", NULL); - if (!p) - return log_oom(); - - mkdir_parents_label(p, 0755); - r = write_string_file(p, - "# Automatically generated by systemd-gpt-auto-generator\n\n" - "[Unit]\n" - "JobTimeoutSec=0\n", - WRITE_STRING_FILE_CREATE); /* the binary handles timeouts anyway */ - if (r < 0) - return log_error_errno(r, "Failed to write device drop-in: %m"); - - ret = strappend("/dev/mapper/", id); - if (!ret) - return log_oom(); - - *device = ret; - return 0; -} - -static int add_mount( - const char *id, - const char *what, - const char *where, - const char *fstype, - bool rw, - const char *options, - const char *description, - const char *post) { - - _cleanup_free_ char *unit = NULL, *lnk = NULL, *crypto_what = NULL, *p = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(id); - assert(what); - assert(where); - assert(description); - - log_debug("Adding %s: %s %s", where, what, strna(fstype)); - - if (streq_ptr(fstype, "crypto_LUKS")) { - - r = add_cryptsetup(id, what, rw, &crypto_what); - if (r < 0) - return r; - - what = crypto_what; - fstype = NULL; - } - - r = unit_name_from_path(where, ".mount", &unit); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - - p = strjoin(arg_dest, "/", unit, NULL); - if (!p) - return log_oom(); - - f = fopen(p, "wxe"); - if (!f) - return log_error_errno(errno, "Failed to create unit file %s: %m", unit); - - fprintf(f, - "# Automatically generated by systemd-gpt-auto-generator\n\n" - "[Unit]\n" - "Description=%s\n" - "Documentation=man:systemd-gpt-auto-generator(8)\n", - description); - - if (post) - fprintf(f, "Before=%s\n", post); - - r = generator_write_fsck_deps(f, arg_dest, what, where, fstype); - if (r < 0) - return r; - - fprintf(f, - "\n" - "[Mount]\n" - "What=%s\n" - "Where=%s\n", - what, where); - - if (fstype) - fprintf(f, "Type=%s\n", fstype); - - if (options) - fprintf(f, "Options=%s,%s\n", options, rw ? "rw" : "ro"); - else - fprintf(f, "Options=%s\n", rw ? "rw" : "ro"); - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write unit file %s: %m", p); - - if (post) { - lnk = strjoin(arg_dest, "/", post, ".requires/", unit, NULL); - if (!lnk) - return log_oom(); - - mkdir_parents_label(lnk, 0755); - if (symlink(p, lnk) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); - } - - return 0; -} - -static bool path_is_busy(const char *where) { - int r; - - /* already a mountpoint; generators run during reload */ - r = path_is_mount_point(where, AT_SYMLINK_FOLLOW); - if (r > 0) - return false; - - /* the directory might not exist on a stateless system */ - if (r == -ENOENT) - return false; - - if (r < 0) - return true; - - /* not a mountpoint but it contains files */ - if (dir_is_empty(where) <= 0) - return true; - - return false; -} - -static int probe_and_add_mount( - const char *id, - const char *what, - const char *where, - bool rw, - const char *description, - const char *post) { - - _cleanup_blkid_free_probe_ blkid_probe b = NULL; - const char *fstype = NULL; - int r; - - assert(id); - assert(what); - assert(where); - assert(description); - - if (path_is_busy(where)) { - log_debug("%s already populated, ignoring.", where); - return 0; - } - - /* Let's check the partition type here, so that we know - * whether to do LUKS magic. */ - - errno = 0; - b = blkid_new_probe_from_filename(what); - if (!b) { - if (errno == 0) - return log_oom(); - return log_error_errno(errno, "Failed to allocate prober: %m"); - } - - blkid_probe_enable_superblocks(b, 1); - blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE); - - errno = 0; - r = blkid_do_safeprobe(b); - if (r == -2 || r == 1) /* no result or uncertain */ - return 0; - else if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe %s: %m", what); - - /* add_mount is OK with fstype being NULL. */ - (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); - - return add_mount( - id, - what, - where, - fstype, - rw, - NULL, - description, - post); -} - -static int add_swap(const char *path) { - _cleanup_free_ char *name = NULL, *unit = NULL, *lnk = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(path); - - log_debug("Adding swap: %s", path); - - r = unit_name_from_path(path, ".swap", &name); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - - unit = strjoin(arg_dest, "/", name, NULL); - if (!unit) - return log_oom(); - - f = fopen(unit, "wxe"); - if (!f) - return log_error_errno(errno, "Failed to create unit file %s: %m", unit); - - fprintf(f, - "# Automatically generated by systemd-gpt-auto-generator\n\n" - "[Unit]\n" - "Description=Swap Partition\n" - "Documentation=man:systemd-gpt-auto-generator(8)\n\n" - "[Swap]\n" - "What=%s\n", - path); - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write unit file %s: %m", unit); - - lnk = strjoin(arg_dest, "/" SPECIAL_SWAP_TARGET ".wants/", name, NULL); - if (!lnk) - return log_oom(); - - mkdir_parents_label(lnk, 0755); - if (symlink(unit, lnk) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); - - return 0; -} - -#ifdef ENABLE_EFI -static int add_automount( - const char *id, - const char *what, - const char *where, - const char *fstype, - bool rw, - const char *options, - const char *description, - usec_t timeout) { - - _cleanup_free_ char *unit = NULL, *lnk = NULL; - _cleanup_free_ char *opt, *p = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(id); - assert(where); - assert(description); - - if (options) - opt = strjoin(options, ",noauto", NULL); - else - opt = strdup("noauto"); - if (!opt) - return log_oom(); - - r = add_mount(id, - what, - where, - fstype, - rw, - opt, - description, - NULL); - if (r < 0) - return r; - - r = unit_name_from_path(where, ".automount", &unit); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - - p = strjoin(arg_dest, "/", unit, NULL); - if (!p) - return log_oom(); - - f = fopen(p, "wxe"); - if (!f) - return log_error_errno(errno, "Failed to create unit file %s: %m", unit); - - fprintf(f, - "# Automatically generated by systemd-gpt-auto-generator\n\n" - "[Unit]\n" - "Description=%s\n" - "Documentation=man:systemd-gpt-auto-generator(8)\n" - "[Automount]\n" - "Where=%s\n" - "TimeoutIdleSec=%lld\n", - description, - where, - (unsigned long long)timeout / USEC_PER_SEC); - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write unit file %s: %m", p); - - lnk = strjoin(arg_dest, "/" SPECIAL_LOCAL_FS_TARGET ".wants/", unit, NULL); - if (!lnk) - return log_oom(); - mkdir_parents_label(lnk, 0755); - - if (symlink(p, lnk) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); - - return 0; -} - -static int add_boot(const char *what) { - _cleanup_blkid_free_probe_ blkid_probe b = NULL; - const char *fstype = NULL, *uuid = NULL; - sd_id128_t id, type_id; - int r; - - assert(what); - - if (!is_efi_boot()) { - log_debug("Not an EFI boot, ignoring /boot."); - return 0; - } - - if (in_initrd()) { - log_debug("In initrd, ignoring /boot."); - return 0; - } - - if (detect_container() > 0) { - log_debug("In a container, ignoring /boot."); - return 0; - } - - /* We create an .automount which is not overridden by the .mount from the fstab generator. */ - if (fstab_is_mount_point("/boot")) { - log_debug("/boot specified in fstab, ignoring."); - return 0; - } - - if (path_is_busy("/boot")) { - log_debug("/boot already populated, ignoring."); - return 0; - } - - r = efi_loader_get_device_part_uuid(&id); - if (r == -ENOENT) { - log_debug("EFI loader partition unknown."); - return 0; - } - - if (r < 0) { - log_error_errno(r, "Failed to read ESP partition UUID: %m"); - return r; - } - - errno = 0; - b = blkid_new_probe_from_filename(what); - if (!b) { - if (errno == 0) - return log_oom(); - return log_error_errno(errno, "Failed to allocate prober: %m"); - } - - blkid_probe_enable_partitions(b, 1); - blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); - - errno = 0; - r = blkid_do_safeprobe(b); - if (r == -2 || r == 1) /* no result or uncertain */ - return 0; - else if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe %s: %m", what); - - (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); - if (!streq_ptr(fstype, "vfat")) { - log_debug("Partition for /boot is not a FAT filesystem, ignoring."); - return 0; - } - - errno = 0; - r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &uuid, NULL); - if (r != 0) { - log_debug_errno(errno, "Partition for /boot does not have a UUID, ignoring."); - return 0; - } - - if (sd_id128_from_string(uuid, &type_id) < 0) { - log_debug("Partition for /boot does not have a valid UUID, ignoring."); - return 0; - } - - if (!sd_id128_equal(type_id, id)) { - log_debug("Partition for /boot does not appear to be the partition we are booted from."); - return 0; - } - - r = add_automount("boot", - what, - "/boot", - "vfat", - true, - "umask=0077", - "EFI System Partition Automount", - 120 * USEC_PER_SEC); - - return r; -} -#else -static int add_boot(const char *what) { - return 0; -} -#endif - -static int enumerate_partitions(dev_t devnum) { - - _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; - _cleanup_udev_device_unref_ struct udev_device *d = NULL; - _cleanup_blkid_free_probe_ blkid_probe b = NULL; - _cleanup_udev_unref_ struct udev *udev = NULL; - _cleanup_free_ char *boot = NULL, *home = NULL, *srv = NULL; - struct udev_list_entry *first, *item; - struct udev_device *parent = NULL; - const char *name, *node, *pttype, *devtype; - int boot_nr = -1, home_nr = -1, srv_nr = -1; - bool home_rw = true, srv_rw = true; - blkid_partlist pl; - int r, k; - dev_t pn; - - udev = udev_new(); - if (!udev) - return log_oom(); - - d = udev_device_new_from_devnum(udev, 'b', devnum); - if (!d) - return log_oom(); - - name = udev_device_get_devnode(d); - if (!name) - name = udev_device_get_syspath(d); - if (!name) { - log_debug("Device %u:%u does not have a name, ignoring.", - major(devnum), minor(devnum)); - return 0; - } - - parent = udev_device_get_parent(d); - if (!parent) { - log_debug("%s: not a partitioned device, ignoring.", name); - return 0; - } - - /* Does it have a devtype? */ - devtype = udev_device_get_devtype(parent); - if (!devtype) { - log_debug("%s: parent doesn't have a device type, ignoring.", name); - return 0; - } - - /* Is this a disk or a partition? We only care for disks... */ - if (!streq(devtype, "disk")) { - log_debug("%s: parent isn't a raw disk, ignoring.", name); - return 0; - } - - /* Does it have a device node? */ - node = udev_device_get_devnode(parent); - if (!node) { - log_debug("%s: parent device does not have device node, ignoring.", name); - return 0; - } - - log_debug("%s: root device %s.", name, node); - - pn = udev_device_get_devnum(parent); - if (major(pn) == 0) - return 0; - - errno = 0; - b = blkid_new_probe_from_filename(node); - if (!b) { - if (errno == 0) - return log_oom(); - - return log_error_errno(errno, "%s: failed to allocate prober: %m", node); - } - - blkid_probe_enable_partitions(b, 1); - blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); - - errno = 0; - r = blkid_do_safeprobe(b); - if (r == 1) - return 0; /* no results */ - else if (r == -2) { - log_warning("%s: probe gave ambiguous results, ignoring.", node); - return 0; - } else if (r != 0) - return log_error_errno(errno ?: EIO, "%s: failed to probe: %m", node); - - errno = 0; - r = blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL); - if (r != 0) { - if (errno == 0) - return 0; /* No partition table found. */ - - return log_error_errno(errno, "%s: failed to determine partition table type: %m", node); - } - - /* We only do this all for GPT... */ - if (!streq_ptr(pttype, "gpt")) { - log_debug("%s: not a GPT partition table, ignoring.", node); - return 0; - } - - errno = 0; - pl = blkid_probe_get_partitions(b); - if (!pl) { - if (errno == 0) - return log_oom(); - - return log_error_errno(errno, "%s: failed to list partitions: %m", node); - } - - e = udev_enumerate_new(udev); - if (!e) - return log_oom(); - - r = udev_enumerate_add_match_parent(e, parent); - if (r < 0) - return log_oom(); - - r = udev_enumerate_add_match_subsystem(e, "block"); - if (r < 0) - return log_oom(); - - r = udev_enumerate_scan_devices(e); - if (r < 0) - return log_error_errno(r, "%s: failed to enumerate partitions: %m", node); - - first = udev_enumerate_get_list_entry(e); - udev_list_entry_foreach(item, first) { - _cleanup_udev_device_unref_ struct udev_device *q; - unsigned long long flags; - const char *stype, *subnode; - sd_id128_t type_id; - blkid_partition pp; - dev_t qn; - int nr; - - q = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); - if (!q) - continue; - - qn = udev_device_get_devnum(q); - if (major(qn) == 0) - continue; - - if (qn == devnum) - continue; - - if (qn == pn) - continue; - - subnode = udev_device_get_devnode(q); - if (!subnode) - continue; - - pp = blkid_partlist_devno_to_partition(pl, qn); - if (!pp) - continue; - - nr = blkid_partition_get_partno(pp); - if (nr < 0) - continue; - - stype = blkid_partition_get_type_string(pp); - if (!stype) - continue; - - if (sd_id128_from_string(stype, &type_id) < 0) - continue; - - flags = blkid_partition_get_flags(pp); - - if (sd_id128_equal(type_id, GPT_SWAP)) { - - if (flags & GPT_FLAG_NO_AUTO) - continue; - - if (flags & GPT_FLAG_READ_ONLY) { - log_debug("%s marked as read-only swap partition, which is bogus. Ignoring.", subnode); - continue; - } - - k = add_swap(subnode); - if (k < 0) - r = k; - - } else if (sd_id128_equal(type_id, GPT_ESP)) { - - /* We only care for the first /boot partition */ - if (boot && nr >= boot_nr) - continue; - - /* Note that we do not honour the "no-auto" - * flag for the ESP, as it is often unset, to - * hide it from Windows. */ - - boot_nr = nr; - - r = free_and_strdup(&boot, subnode); - if (r < 0) - return log_oom(); - - } else if (sd_id128_equal(type_id, GPT_HOME)) { - - if (flags & GPT_FLAG_NO_AUTO) - continue; - - /* We only care for the first /home partition */ - if (home && nr >= home_nr) - continue; - - home_nr = nr; - home_rw = !(flags & GPT_FLAG_READ_ONLY), - - r = free_and_strdup(&home, subnode); - if (r < 0) - return log_oom(); - - } else if (sd_id128_equal(type_id, GPT_SRV)) { - - if (flags & GPT_FLAG_NO_AUTO) - continue; - - /* We only care for the first /srv partition */ - if (srv && nr >= srv_nr) - continue; - - srv_nr = nr; - srv_rw = !(flags & GPT_FLAG_READ_ONLY), - - r = free_and_strdup(&srv, subnode); - if (r < 0) - return log_oom(); - } - } - - if (boot) { - k = add_boot(boot); - if (k < 0) - r = k; - } - - if (home) { - k = probe_and_add_mount("home", home, "/home", home_rw, "Home Partition", SPECIAL_LOCAL_FS_TARGET); - if (k < 0) - r = k; - } - - if (srv) { - k = probe_and_add_mount("srv", srv, "/srv", srv_rw, "Server Data Partition", SPECIAL_LOCAL_FS_TARGET); - if (k < 0) - r = k; - } - - return r; -} - -static int get_block_device(const char *path, dev_t *dev) { - struct stat st; - struct statfs sfs; - - assert(path); - assert(dev); - - /* Get's the block device directly backing a file system. If - * the block device is encrypted, returns the device mapper - * block device. */ - - if (lstat(path, &st)) - return -errno; - - if (major(st.st_dev) != 0) { - *dev = st.st_dev; - return 1; - } - - if (statfs(path, &sfs) < 0) - return -errno; - - if (F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC)) - return btrfs_get_block_device(path, dev); - - return 0; -} - -static int get_block_device_harder(const char *path, dev_t *dev) { - _cleanup_closedir_ DIR *d = NULL; - _cleanup_free_ char *p = NULL, *t = NULL; - struct dirent *de, *found = NULL; - const char *q; - unsigned maj, min; - dev_t dt; - int r; - - assert(path); - assert(dev); - - /* Gets the backing block device for a file system, and - * handles LUKS encrypted file systems, looking for its - * immediate parent, if there is one. */ - - r = get_block_device(path, &dt); - if (r <= 0) - return r; - - if (asprintf(&p, "/sys/dev/block/%u:%u/slaves", major(dt), minor(dt)) < 0) - return -ENOMEM; - - d = opendir(p); - if (!d) { - if (errno == ENOENT) - goto fallback; - - return -errno; - } - - FOREACH_DIRENT_ALL(de, d, return -errno) { - - if (STR_IN_SET(de->d_name, ".", "..")) - continue; - - if (!IN_SET(de->d_type, DT_LNK, DT_UNKNOWN)) - continue; - - if (found) /* Don't try to support multiple backing block devices */ - goto fallback; - - found = de; - } - - if (!found) - goto fallback; - - q = strjoina(p, "/", found->d_name, "/dev"); - - r = read_one_line_file(q, &t); - if (r == -ENOENT) - goto fallback; - if (r < 0) - return r; - - if (sscanf(t, "%u:%u", &maj, &min) != 2) - return -EINVAL; - - if (maj == 0) - goto fallback; - - *dev = makedev(maj, min); - return 1; - -fallback: - *dev = dt; - return 1; -} - -static int parse_proc_cmdline_item(const char *key, const char *value) { - int r; - - assert(key); - - if (STR_IN_SET(key, "systemd.gpt_auto", "rd.systemd.gpt_auto") && value) { - - r = parse_boolean(value); - if (r < 0) - log_warning("Failed to parse gpt-auto switch \"%s\". Ignoring.", value); - else - arg_enabled = r; - - } else if (streq(key, "root") && value) { - - /* Disable root disk logic if there's a root= value - * specified (unless it happens to be "gpt-auto") */ - - arg_root_enabled = streq(value, "gpt-auto"); - - } else if (streq(key, "rw") && !value) - arg_root_rw = true; - else if (streq(key, "ro") && !value) - arg_root_rw = false; - - return 0; -} - -static int add_root_mount(void) { - -#ifdef ENABLE_EFI - int r; - - if (!is_efi_boot()) { - log_debug("Not a EFI boot, not creating root mount."); - return 0; - } - - r = efi_loader_get_device_part_uuid(NULL); - if (r == -ENOENT) { - log_debug("EFI loader partition unknown, exiting."); - return 0; - } else if (r < 0) - return log_error_errno(r, "Failed to read ESP partition UUID: %m"); - - /* OK, we have an ESP partition, this is fantastic, so let's - * wait for a root device to show up. A udev rule will create - * the link for us under the right name. */ - - if (in_initrd()) { - r = generator_write_initrd_root_device_deps(arg_dest, "/dev/gpt-auto-root"); - if (r < 0) - return 0; - } - - return add_mount( - "root", - "/dev/gpt-auto-root", - in_initrd() ? "/sysroot" : "/", - NULL, - arg_root_rw, - NULL, - "Root Partition", - in_initrd() ? SPECIAL_INITRD_ROOT_FS_TARGET : SPECIAL_LOCAL_FS_TARGET); -#else - return 0; -#endif -} - -static int add_mounts(void) { - dev_t devno; - int r; - - r = get_block_device_harder("/", &devno); - if (r < 0) - return log_error_errno(r, "Failed to determine block device of root file system: %m"); - else if (r == 0) { - r = get_block_device_harder("/usr", &devno); - if (r < 0) - return log_error_errno(r, "Failed to determine block device of /usr file system: %m"); - else if (r == 0) { - log_debug("Neither root nor /usr file system are on a (single) block device."); - return 0; - } - } - - return enumerate_partitions(devno); -} - -int main(int argc, char *argv[]) { - int r = 0; - - if (argc > 1 && argc != 4) { - log_error("This program takes three or no arguments."); - return EXIT_FAILURE; - } - - if (argc > 1) - arg_dest = argv[3]; - - log_set_target(LOG_TARGET_SAFE); - log_parse_environment(); - log_open(); - - umask(0022); - - if (detect_container() > 0) { - log_debug("In a container, exiting."); - return EXIT_SUCCESS; - } - - r = parse_proc_cmdline(parse_proc_cmdline_item); - if (r < 0) - log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - - if (!arg_enabled) { - log_debug("Disabled, exiting."); - return EXIT_SUCCESS; - } - - if (arg_root_enabled) - r = add_root_mount(); - - if (!in_initrd()) { - int k; - - k = add_mounts(); - if (k < 0) - r = k; - } - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/grp-boot/Makefile b/src/grp-boot/Makefile new file mode 100644 index 0000000000..922d8a32d3 --- /dev/null +++ b/src/grp-boot/Makefile @@ -0,0 +1,28 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +at.subdirs += bootctl systemd-boot + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-boot/bootctl/Makefile b/src/grp-boot/bootctl/Makefile new file mode 100644 index 0000000000..84253d2387 --- /dev/null +++ b/src/grp-boot/bootctl/Makefile @@ -0,0 +1,54 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_EFI),) +ifneq ($(HAVE_BLKID),) +bootctl_SOURCES = \ + src/boot/bootctl.c + +bootctl_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -DEFI_MACHINE_TYPE_NAME=\"$(EFI_MACHINE_TYPE_NAME)\" \ + -DBOOTLIBDIR=\"$(bootlibdir)\" + +bootctl_CFLAGS = \ + $(AM_CFLAGS) \ + $(BLKID_CFLAGS) + +bootctl_LDADD = \ + libshared.la \ + $(BLKID_LIBS) + +bin_PROGRAMS += \ + bootctl + +dist_bashcompletion_data += \ + shell-completion/bash/bootctl + +dist_zshcompletion_data += \ + shell-completion/zsh/_bootctl +endif # HAVE_BLKID +endif # ENABLE_EFI +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-boot/bootctl/bootctl.c b/src/grp-boot/bootctl/bootctl.c new file mode 100644 index 0000000000..4a356d25d1 --- /dev/null +++ b/src/grp-boot/bootctl/bootctl.c @@ -0,0 +1,1143 @@ +/*** + This file is part of systemd. + + Copyright 2013-2015 Kay Sievers + Copyright 2013 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "blkid-util.h" +#include "dirent-util.h" +#include "efivars.h" +#include "fd-util.h" +#include "fileio.h" +#include "locale-util.h" +#include "rm-rf.h" +#include "string-util.h" +#include "util.h" + +static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t *psize, sd_id128_t *uuid) { + struct statfs sfs; + struct stat st, st2; + _cleanup_free_ char *t = NULL; + _cleanup_blkid_free_probe_ blkid_probe b = NULL; + int r; + const char *v, *t2; + + if (statfs(p, &sfs) < 0) + return log_error_errno(errno, "Failed to check file system type of \"%s\": %m", p); + + if (sfs.f_type != 0x4d44) { + log_error("File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p); + return -ENODEV; + } + + if (stat(p, &st) < 0) + return log_error_errno(errno, "Failed to determine block device node of \"%s\": %m", p); + + if (major(st.st_dev) == 0) { + log_error("Block device node of %p is invalid.", p); + return -ENODEV; + } + + t2 = strjoina(p, "/.."); + r = stat(t2, &st2); + if (r < 0) + return log_error_errno(errno, "Failed to determine block device node of parent of \"%s\": %m", p); + + if (st.st_dev == st2.st_dev) { + log_error("Directory \"%s\" is not the root of the EFI System Partition (ESP) file system.", p); + return -ENODEV; + } + + r = asprintf(&t, "/dev/block/%u:%u", major(st.st_dev), minor(st.st_dev)); + if (r < 0) + return log_oom(); + + errno = 0; + b = blkid_new_probe_from_filename(t); + if (!b) { + if (errno == 0) + return log_oom(); + + return log_error_errno(errno, "Failed to open file system \"%s\": %m", p); + } + + blkid_probe_enable_superblocks(b, 1); + blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE); + blkid_probe_enable_partitions(b, 1); + blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); + + errno = 0; + r = blkid_do_safeprobe(b); + if (r == -2) { + log_error("File system \"%s\" is ambigious.", p); + return -ENODEV; + } else if (r == 1) { + log_error("File system \"%s\" does not contain a label.", p); + return -ENODEV; + } else if (r != 0) { + r = errno ? -errno : -EIO; + return log_error_errno(r, "Failed to probe file system \"%s\": %m", p); + } + + errno = 0; + r = blkid_probe_lookup_value(b, "TYPE", &v, NULL); + if (r != 0) { + r = errno ? -errno : -EIO; + return log_error_errno(r, "Failed to probe file system type \"%s\": %m", p); + } + + if (!streq(v, "vfat")) { + log_error("File system \"%s\" is not FAT.", p); + return -ENODEV; + } + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL); + if (r != 0) { + r = errno ? -errno : -EIO; + return log_error_errno(r, "Failed to probe partition scheme \"%s\": %m", p); + } + + if (!streq(v, "gpt")) { + log_error("File system \"%s\" is not on a GPT partition table.", p); + return -ENODEV; + } + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL); + if (r != 0) { + r = errno ? -errno : -EIO; + return log_error_errno(r, "Failed to probe partition type UUID \"%s\": %m", p); + } + + if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b")) { + log_error("File system \"%s\" has wrong type for an EFI System Partition (ESP).", p); + return -ENODEV; + } + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL); + if (r != 0) { + r = errno ? -errno : -EIO; + return log_error_errno(r, "Failed to probe partition entry UUID \"%s\": %m", p); + } + + r = sd_id128_from_string(v, uuid); + if (r < 0) { + log_error("Partition \"%s\" has invalid UUID \"%s\".", p, v); + return -EIO; + } + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_NUMBER", &v, NULL); + if (r != 0) { + r = errno ? -errno : -EIO; + return log_error_errno(r, "Failed to probe partition number \"%s\": m", p); + } + *part = strtoul(v, NULL, 10); + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL); + if (r != 0) { + r = errno ? -errno : -EIO; + return log_error_errno(r, "Failed to probe partition offset \"%s\": %m", p); + } + *pstart = strtoul(v, NULL, 10); + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL); + if (r != 0) { + r = errno ? -errno : -EIO; + return log_error_errno(r, "Failed to probe partition size \"%s\": %m", p); + } + *psize = strtoul(v, NULL, 10); + + return 0; +} + +/* search for "#### LoaderInfo: systemd-boot 218 ####" string inside the binary */ +static int get_file_version(int fd, char **v) { + struct stat st; + char *buf; + const char *s, *e; + char *x = NULL; + int r = 0; + + assert(fd >= 0); + assert(v); + + if (fstat(fd, &st) < 0) + return -errno; + + if (st.st_size < 27) + return 0; + + buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (buf == MAP_FAILED) + return -errno; + + s = memmem(buf, st.st_size - 8, "#### LoaderInfo: ", 17); + if (!s) + goto finish; + s += 17; + + e = memmem(s, st.st_size - (s - buf), " ####", 5); + if (!e || e - s < 3) { + log_error("Malformed version string."); + r = -EINVAL; + goto finish; + } + + x = strndup(s, e - s); + if (!x) { + r = log_oom(); + goto finish; + } + r = 1; + +finish: + munmap(buf, st.st_size); + *v = x; + return r; +} + +static int enumerate_binaries(const char *esp_path, const char *path, const char *prefix) { + char *p; + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r = 0, c = 0; + + p = strjoina(esp_path, "/", path); + d = opendir(p); + if (!d) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to read \"%s\": %m", p); + } + + FOREACH_DIRENT(de, d, break) { + _cleanup_close_ int fd = -1; + _cleanup_free_ char *v = NULL; + + if (!endswith_no_case(de->d_name, ".efi")) + continue; + + if (prefix && !startswith_no_case(de->d_name, prefix)) + continue; + + fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open \"%s/%s\" for reading: %m", p, de->d_name); + + r = get_file_version(fd, &v); + if (r < 0) + return r; + if (r > 0) + printf(" File: %s/%s/%s (%s)\n", special_glyph(TREE_RIGHT), path, de->d_name, v); + else + printf(" File: %s/%s/%s\n", special_glyph(TREE_RIGHT), path, de->d_name); + c++; + } + + return c; +} + +static int status_binaries(const char *esp_path, sd_id128_t partition) { + int r; + + printf("Boot Loader Binaries:\n"); + + printf(" ESP: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", SD_ID128_FORMAT_VAL(partition)); + + r = enumerate_binaries(esp_path, "EFI/systemd", NULL); + if (r == 0) + log_error("systemd-boot not installed in ESP."); + else if (r < 0) + return r; + + r = enumerate_binaries(esp_path, "EFI/Boot", "boot"); + if (r == 0) + log_error("No default/fallback boot loader installed in ESP."); + else if (r < 0) + return r; + + printf("\n"); + + return 0; +} + +static int print_efi_option(uint16_t id, bool in_order) { + _cleanup_free_ char *title = NULL; + _cleanup_free_ char *path = NULL; + sd_id128_t partition; + bool active; + int r = 0; + + r = efi_get_boot_option(id, &title, &partition, &path, &active); + if (r < 0) + return r; + + /* print only configured entries with partition information */ + if (!path || sd_id128_equal(partition, SD_ID128_NULL)) + return 0; + + efi_tilt_backslashes(path); + + printf(" Title: %s\n", strna(title)); + printf(" ID: 0x%04X\n", id); + printf(" Status: %sactive%s\n", active ? "" : "in", in_order ? ", boot-order" : ""); + printf(" Partition: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", SD_ID128_FORMAT_VAL(partition)); + printf(" File: %s%s\n", special_glyph(TREE_RIGHT), path); + printf("\n"); + + return 0; +} + +static int status_variables(void) { + int n_options, n_order; + _cleanup_free_ uint16_t *options = NULL, *order = NULL; + int i; + + if (!is_efi_boot()) { + log_notice("Not booted with EFI, not showing EFI variables."); + return 0; + } + + n_options = efi_get_boot_options(&options); + if (n_options == -ENOENT) + return log_error_errno(ENOENT, "Failed to access EFI variables, efivarfs" + " needs to be available at /sys/firmware/efi/efivars/."); + else if (n_options < 0) + return log_error_errno(n_options, "Failed to read EFI boot entries: %m"); + + n_order = efi_get_boot_order(&order); + if (n_order == -ENOENT) + n_order = 0; + else if (n_order < 0) + return log_error_errno(n_order, "Failed to read EFI boot order."); + + /* print entries in BootOrder first */ + printf("Boot Loader Entries in EFI Variables:\n"); + for (i = 0; i < n_order; i++) + print_efi_option(order[i], true); + + /* print remaining entries */ + for (i = 0; i < n_options; i++) { + int j; + + for (j = 0; j < n_order; j++) + if (options[i] == order[j]) + goto next; + + print_efi_option(options[i], false); + next: + continue; + } + + return 0; +} + +static int compare_product(const char *a, const char *b) { + size_t x, y; + + assert(a); + assert(b); + + x = strcspn(a, " "); + y = strcspn(b, " "); + if (x != y) + return x < y ? -1 : x > y ? 1 : 0; + + return strncmp(a, b, x); +} + +static int compare_version(const char *a, const char *b) { + assert(a); + assert(b); + + a += strcspn(a, " "); + a += strspn(a, " "); + b += strcspn(b, " "); + b += strspn(b, " "); + + return strverscmp(a, b); +} + +static int version_check(int fd, const char *from, const char *to) { + _cleanup_free_ char *a = NULL, *b = NULL; + _cleanup_close_ int fd2 = -1; + int r; + + assert(fd >= 0); + assert(from); + assert(to); + + r = get_file_version(fd, &a); + if (r < 0) + return r; + if (r == 0) { + log_error("Source file \"%s\" does not carry version information!", from); + return -EINVAL; + } + + fd2 = open(to, O_RDONLY|O_CLOEXEC); + if (fd2 < 0) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open \"%s\" for reading: %m", to); + } + + r = get_file_version(fd2, &b); + if (r < 0) + return r; + if (r == 0 || compare_product(a, b) != 0) { + log_notice("Skipping \"%s\", since it's owned by another boot loader.", to); + return -EEXIST; + } + + if (compare_version(a, b) < 0) { + log_warning("Skipping \"%s\", since a newer boot loader version exists already.", to); + return -ESTALE; + } + + return 0; +} + +static int copy_file(const char *from, const char *to, bool force) { + _cleanup_fclose_ FILE *f = NULL, *g = NULL; + char *p; + int r; + struct timespec t[2]; + struct stat st; + + assert(from); + assert(to); + + f = fopen(from, "re"); + if (!f) + return log_error_errno(errno, "Failed to open \"%s\" for reading: %m", from); + + if (!force) { + /* If this is an update, then let's compare versions first */ + r = version_check(fileno(f), from, to); + if (r < 0) + return r; + } + + p = strjoina(to, "~"); + g = fopen(p, "wxe"); + if (!g) { + /* Directory doesn't exist yet? Then let's skip this... */ + if (!force && errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open \"%s\" for writing: %m", to); + } + + rewind(f); + do { + size_t k; + uint8_t buf[32*1024]; + + k = fread(buf, 1, sizeof(buf), f); + if (ferror(f)) { + r = log_error_errno(EIO, "Failed to read \"%s\": %m", from); + goto error; + } + + if (k == 0) + break; + + fwrite(buf, 1, k, g); + if (ferror(g)) { + r = log_error_errno(EIO, "Failed to write \"%s\": %m", to); + goto error; + } + } while (!feof(f)); + + r = fflush_and_check(g); + if (r < 0) { + log_error_errno(r, "Failed to write \"%s\": %m", to); + goto error; + } + + r = fstat(fileno(f), &st); + if (r < 0) { + r = log_error_errno(errno, "Failed to get file timestamps of \"%s\": %m", from); + goto error; + } + + t[0] = st.st_atim; + t[1] = st.st_mtim; + + r = futimens(fileno(g), t); + if (r < 0) { + r = log_error_errno(errno, "Failed to set file timestamps on \"%s\": %m", p); + goto error; + } + + if (rename(p, to) < 0) { + r = log_error_errno(errno, "Failed to rename \"%s\" to \"%s\": %m", p, to); + goto error; + } + + log_info("Copied \"%s\" to \"%s\".", from, to); + return 0; + +error: + (void) unlink(p); + return r; +} + +static char* strupper(char *s) { + char *p; + + for (p = s; *p; p++) + *p = toupper(*p); + + return s; +} + +static int mkdir_one(const char *prefix, const char *suffix) { + char *p; + + p = strjoina(prefix, "/", suffix); + if (mkdir(p, 0700) < 0) { + if (errno != EEXIST) + return log_error_errno(errno, "Failed to create \"%s\": %m", p); + } else + log_info("Created \"%s\".", p); + + return 0; +} + +static const char *efi_subdirs[] = { + "EFI", + "EFI/systemd", + "EFI/Boot", + "loader", + "loader/entries" +}; + +static int create_dirs(const char *esp_path) { + int r; + unsigned i; + + for (i = 0; i < ELEMENTSOF(efi_subdirs); i++) { + r = mkdir_one(esp_path, efi_subdirs[i]); + if (r < 0) + return r; + } + + return 0; +} + +static int copy_one_file(const char *esp_path, const char *name, bool force) { + char *p, *q; + int r; + + p = strjoina(BOOTLIBDIR "/", name); + q = strjoina(esp_path, "/EFI/systemd/", name); + r = copy_file(p, q, force); + + if (startswith(name, "systemd-boot")) { + int k; + char *v; + + /* Create the EFI default boot loader name (specified for removable devices) */ + v = strjoina(esp_path, "/EFI/Boot/BOOT", name + strlen("systemd-boot")); + strupper(strrchr(v, '/') + 1); + + k = copy_file(p, v, force); + if (k < 0 && r == 0) + r = k; + } + + return r; +} + +static int install_binaries(const char *esp_path, bool force) { + struct dirent *de; + _cleanup_closedir_ DIR *d = NULL; + int r = 0; + + if (force) { + /* Don't create any of these directories when we are + * just updating. When we update we'll drop-in our + * files (unless there are newer ones already), but we + * won't create the directories for them in the first + * place. */ + r = create_dirs(esp_path); + if (r < 0) + return r; + } + + d = opendir(BOOTLIBDIR); + if (!d) + return log_error_errno(errno, "Failed to open \""BOOTLIBDIR"\": %m"); + + FOREACH_DIRENT(de, d, break) { + int k; + + if (!endswith_no_case(de->d_name, ".efi")) + continue; + + k = copy_one_file(esp_path, de->d_name, force); + if (k < 0 && r == 0) + r = k; + } + + return r; +} + +static bool same_entry(uint16_t id, const sd_id128_t uuid, const char *path) { + _cleanup_free_ char *opath = NULL; + sd_id128_t ouuid; + int r; + + r = efi_get_boot_option(id, NULL, &ouuid, &opath, NULL); + if (r < 0) + return false; + if (!sd_id128_equal(uuid, ouuid)) + return false; + if (!streq_ptr(path, opath)) + return false; + + return true; +} + +static int find_slot(sd_id128_t uuid, const char *path, uint16_t *id) { + _cleanup_free_ uint16_t *options = NULL; + int n, i; + + n = efi_get_boot_options(&options); + if (n < 0) + return n; + + /* find already existing systemd-boot entry */ + for (i = 0; i < n; i++) + if (same_entry(options[i], uuid, path)) { + *id = options[i]; + return 1; + } + + /* find free slot in the sorted BootXXXX variable list */ + for (i = 0; i < n; i++) + if (i != options[i]) { + *id = i; + return 1; + } + + /* use the next one */ + if (i == 0xffff) + return -ENOSPC; + *id = i; + return 0; +} + +static int insert_into_order(uint16_t slot, bool first) { + _cleanup_free_ uint16_t *order = NULL; + uint16_t *t; + int n, i; + + n = efi_get_boot_order(&order); + if (n <= 0) + /* no entry, add us */ + return efi_set_boot_order(&slot, 1); + + /* are we the first and only one? */ + if (n == 1 && order[0] == slot) + return 0; + + /* are we already in the boot order? */ + for (i = 0; i < n; i++) { + if (order[i] != slot) + continue; + + /* we do not require to be the first one, all is fine */ + if (!first) + return 0; + + /* move us to the first slot */ + memmove(order + 1, order, i * sizeof(uint16_t)); + order[0] = slot; + return efi_set_boot_order(order, n); + } + + /* extend array */ + t = realloc(order, (n + 1) * sizeof(uint16_t)); + if (!t) + return -ENOMEM; + order = t; + + /* add us to the top or end of the list */ + if (first) { + memmove(order + 1, order, n * sizeof(uint16_t)); + order[0] = slot; + } else + order[n] = slot; + + return efi_set_boot_order(order, n + 1); +} + +static int remove_from_order(uint16_t slot) { + _cleanup_free_ uint16_t *order = NULL; + int n, i; + + n = efi_get_boot_order(&order); + if (n <= 0) + return n; + + for (i = 0; i < n; i++) { + if (order[i] != slot) + continue; + + if (i + 1 < n) + memmove(order + i, order + i+1, (n - i) * sizeof(uint16_t)); + return efi_set_boot_order(order, n - 1); + } + + return 0; +} + +static int install_variables(const char *esp_path, + uint32_t part, uint64_t pstart, uint64_t psize, + sd_id128_t uuid, const char *path, + bool first) { + char *p; + uint16_t slot; + int r; + + if (!is_efi_boot()) { + log_warning("Not booted with EFI, skipping EFI variable setup."); + return 0; + } + + p = strjoina(esp_path, path); + if (access(p, F_OK) < 0) { + if (errno == ENOENT) + return 0; + else + return log_error_errno(errno, "Cannot access \"%s\": %m", p); + } + + r = find_slot(uuid, path, &slot); + if (r < 0) + return log_error_errno(r, + r == -ENOENT ? + "Failed to access EFI variables. Is the \"efivarfs\" filesystem mounted?" : + "Failed to determine current boot order: %m"); + + if (first || r == false) { + r = efi_add_boot_option(slot, "Systemd Boot Manager", + part, pstart, psize, + uuid, path); + if (r < 0) + return log_error_errno(r, "Failed to create EFI Boot variable entry: %m"); + + log_info("Created EFI boot entry \"Systemd Boot Manager\"."); + } + + return insert_into_order(slot, first); +} + +static int remove_boot_efi(const char *esp_path) { + char *p; + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r, c = 0; + + p = strjoina(esp_path, "/EFI/Boot"); + d = opendir(p); + if (!d) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open directory \"%s\": %m", p); + } + + FOREACH_DIRENT(de, d, break) { + _cleanup_close_ int fd = -1; + _cleanup_free_ char *v = NULL; + + if (!endswith_no_case(de->d_name, ".efi")) + continue; + + if (!startswith_no_case(de->d_name, "Boot")) + continue; + + fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open \"%s/%s\" for reading: %m", p, de->d_name); + + r = get_file_version(fd, &v); + if (r < 0) + return r; + if (r > 0 && startswith(v, "systemd-boot ")) { + r = unlinkat(dirfd(d), de->d_name, 0); + if (r < 0) + return log_error_errno(errno, "Failed to remove \"%s/%s\": %m", p, de->d_name); + + log_info("Removed \"%s/%s\".", p, de->d_name); + } + + c++; + } + + return c; +} + +static int rmdir_one(const char *prefix, const char *suffix) { + char *p; + + p = strjoina(prefix, "/", suffix); + if (rmdir(p) < 0) { + if (!IN_SET(errno, ENOENT, ENOTEMPTY)) + return log_error_errno(errno, "Failed to remove \"%s\": %m", p); + } else + log_info("Removed \"%s\".", p); + + return 0; +} + +static int remove_binaries(const char *esp_path) { + char *p; + int r, q; + unsigned i; + + p = strjoina(esp_path, "/EFI/systemd"); + r = rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); + + q = remove_boot_efi(esp_path); + if (q < 0 && r == 0) + r = q; + + for (i = ELEMENTSOF(efi_subdirs); i > 0; i--) { + q = rmdir_one(esp_path, efi_subdirs[i-1]); + if (q < 0 && r == 0) + r = q; + } + + return r; +} + +static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) { + uint16_t slot; + int r; + + if (!is_efi_boot()) + return 0; + + r = find_slot(uuid, path, &slot); + if (r != 1) + return 0; + + r = efi_remove_boot_option(slot); + if (r < 0) + return r; + + if (in_order) + return remove_from_order(slot); + else + return 0; +} + +static int install_loader_config(const char *esp_path) { + char *p; + char line[64]; + char *machine = NULL; + _cleanup_fclose_ FILE *f = NULL, *g = NULL; + + f = fopen("/etc/machine-id", "re"); + if (!f) + return errno == ENOENT ? 0 : -errno; + + if (fgets(line, sizeof(line), f) != NULL) { + char *s; + + s = strchr(line, '\n'); + if (s) + s[0] = '\0'; + if (strlen(line) == 32) + machine = line; + } + + if (!machine) + return -ESRCH; + + p = strjoina(esp_path, "/loader/loader.conf"); + g = fopen(p, "wxe"); + if (g) { + fprintf(g, "#timeout 3\n"); + fprintf(g, "default %s-*\n", machine); + if (ferror(g)) + return log_error_errno(EIO, "Failed to write \"%s\": %m", p); + } + + return 0; +} + +static int help(void) { + printf("%s [COMMAND] [OPTIONS...]\n" + "\n" + "Install, update or remove the systemd-boot EFI boot manager.\n\n" + " -h --help Show this help\n" + " --version Print version\n" + " --path=PATH Path to the EFI System Partition (ESP)\n" + " --no-variables Don't touch EFI variables\n" + "\n" + "Commands:\n" + " status Show status of installed systemd-boot and EFI variables\n" + " install Install systemd-boot to the ESP and EFI variables\n" + " update Update systemd-boot in the ESP and EFI variables\n" + " remove Remove systemd-boot from the ESP and EFI variables\n", + program_invocation_short_name); + + return 0; +} + +static const char *arg_path = "/boot"; +static bool arg_touch_variables = true; + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_PATH = 0x100, + ARG_VERSION, + ARG_NO_VARIABLES, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "path", required_argument, NULL, ARG_PATH }, + { "no-variables", no_argument, NULL, ARG_NO_VARIABLES }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_PATH: + arg_path = optarg; + break; + + case ARG_NO_VARIABLES: + arg_touch_variables = false; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unknown option"); + } + + return 1; +} + +static void read_loader_efi_var(const char *name, char **var) { + int r; + + r = efi_get_variable_string(EFI_VENDOR_LOADER, name, var); + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to read EFI variable %s: %m", name); +} + +static int bootctl_main(int argc, char*argv[]) { + enum action { + ACTION_STATUS, + ACTION_INSTALL, + ACTION_UPDATE, + ACTION_REMOVE + } arg_action = ACTION_STATUS; + static const struct { + const char* verb; + enum action action; + } verbs[] = { + { "status", ACTION_STATUS }, + { "install", ACTION_INSTALL }, + { "update", ACTION_UPDATE }, + { "remove", ACTION_REMOVE }, + }; + + sd_id128_t uuid = {}; + uint32_t part = 0; + uint64_t pstart = 0, psize = 0; + int r, q; + + if (argv[optind]) { + unsigned i; + + for (i = 0; i < ELEMENTSOF(verbs); i++) { + if (!streq(argv[optind], verbs[i].verb)) + continue; + arg_action = verbs[i].action; + break; + } + if (i >= ELEMENTSOF(verbs)) { + log_error("Unknown operation \"%s\"", argv[optind]); + return -EINVAL; + } + } + + if (geteuid() != 0) + return log_error_errno(EPERM, "Need to be root."); + + r = verify_esp(arg_path, &part, &pstart, &psize, &uuid); + if (r == -ENODEV && !arg_path) + log_notice("You might want to use --path= to indicate the path to your ESP, in case it is not mounted on /boot."); + if (r < 0) + return r; + + switch (arg_action) { + case ACTION_STATUS: { + _cleanup_free_ char *fw_type = NULL; + _cleanup_free_ char *fw_info = NULL; + _cleanup_free_ char *loader = NULL; + _cleanup_free_ char *loader_path = NULL; + sd_id128_t loader_part_uuid = {}; + + if (is_efi_boot()) { + read_loader_efi_var("LoaderFirmwareType", &fw_type); + read_loader_efi_var("LoaderFirmwareInfo", &fw_info); + read_loader_efi_var("LoaderInfo", &loader); + read_loader_efi_var("LoaderImageIdentifier", &loader_path); + if (loader_path) + efi_tilt_backslashes(loader_path); + r = efi_loader_get_device_part_uuid(&loader_part_uuid); + if (r < 0 && r == -ENOENT) + log_warning_errno(r, "Failed to read EFI variable LoaderDevicePartUUID: %m"); + + printf("System:\n"); + printf(" Firmware: %s (%s)\n", strna(fw_type), strna(fw_info)); + + r = is_efi_secure_boot(); + if (r < 0) + log_warning_errno(r, "Failed to query secure boot status: %m"); + else + printf(" Secure Boot: %s\n", r ? "enabled" : "disabled"); + + r = is_efi_secure_boot_setup_mode(); + if (r < 0) + log_warning_errno(r, "Failed to query secure boot mode: %m"); + else + printf(" Setup Mode: %s\n", r ? "setup" : "user"); + printf("\n"); + + printf("Loader:\n"); + printf(" Product: %s\n", strna(loader)); + if (!sd_id128_equal(loader_part_uuid, SD_ID128_NULL)) + printf(" Partition: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", + SD_ID128_FORMAT_VAL(loader_part_uuid)); + else + printf(" Partition: n/a\n"); + printf(" File: %s%s\n", special_glyph(TREE_RIGHT), strna(loader_path)); + printf("\n"); + } else + printf("System:\n Not booted with EFI\n"); + + r = status_binaries(arg_path, uuid); + if (r < 0) + return r; + + if (arg_touch_variables) + r = status_variables(); + break; + } + + case ACTION_INSTALL: + case ACTION_UPDATE: + umask(0002); + + r = install_binaries(arg_path, arg_action == ACTION_INSTALL); + if (r < 0) + return r; + + if (arg_action == ACTION_INSTALL) { + r = install_loader_config(arg_path); + if (r < 0) + return r; + } + + if (arg_touch_variables) + r = install_variables(arg_path, + part, pstart, psize, uuid, + "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", + arg_action == ACTION_INSTALL); + break; + + case ACTION_REMOVE: + r = remove_binaries(arg_path); + + if (arg_touch_variables) { + q = remove_variables(uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", true); + if (q < 0 && r == 0) + r = q; + } + break; + } + + return r; +} + +int main(int argc, char *argv[]) { + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = bootctl_main(argc, argv); + + finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-boot/systemd-boot/.gitignore b/src/grp-boot/systemd-boot/.gitignore new file mode 100644 index 0000000000..e193acbe12 --- /dev/null +++ b/src/grp-boot/systemd-boot/.gitignore @@ -0,0 +1,2 @@ +/systemd_boot.so +/stub.so diff --git a/src/grp-boot/systemd-boot/Makefile b/src/grp-boot/systemd-boot/Makefile new file mode 100644 index 0000000000..5b53fdc7e4 --- /dev/null +++ b/src/grp-boot/systemd-boot/Makefile @@ -0,0 +1,194 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_EFI),) +ifneq ($(HAVE_GNUEFI),) +efi_cppflags = \ + $(EFI_CPPFLAGS) \ + -I$(top_builddir) -include config.h \ + -I$(EFI_INC_DIR)/efi \ + -I$(EFI_INC_DIR)/efi/$(EFI_ARCH) \ + -DEFI_MACHINE_TYPE_NAME=\"$(EFI_MACHINE_TYPE_NAME)\" + +efi_cflags = \ + $(EFI_CFLAGS) \ + -Wall \ + -Wextra \ + -std=gnu90 \ + -nostdinc \ + -ggdb -O0 \ + -fpic \ + -fshort-wchar \ + -nostdinc \ + -ffreestanding \ + -fno-strict-aliasing \ + -fno-stack-protector \ + -Wsign-compare \ + -Wno-missing-field-initializers + +ifneq ($(ARCH_X86_64),) +efi_cflags += \ + -mno-red-zone \ + -mno-sse \ + -mno-mmx \ + -DEFI_FUNCTION_WRAPPER \ + -DGNU_EFI_USE_MS_ABI +endif # ARCH_X86_64 + +ifneq ($(ARCH_IA32),) +efi_cflags += \ + -mno-sse \ + -mno-mmx +endif # ARCH_IA32 + +efi_ldflags = \ + $(EFI_LDFLAGS) \ + -T $(EFI_LDS_DIR)/elf_$(EFI_ARCH)_efi.lds \ + -shared \ + -Bsymbolic \ + -nostdlib \ + -znocombreloc \ + -L $(EFI_LIB_DIR) \ + $(EFI_LDS_DIR)/crt0-efi-$(EFI_ARCH).o + +# Aarch64 and ARM32 don't have an EFI capable objcopy. Use 'binary' instead, +# and add required symbols manually. +ifneq ($(ARCH_AARCH64),) +efi_ldflags += --defsym=EFI_SUBSYSTEM=0xa +EFI_FORMAT = -O binary +else +EFI_FORMAT = --target=efi-app-$(EFI_ARCH) +endif # ARCH_AARCH64 +endif # HAVE_GNUEFI +endif # ENABLE_EFI + +# ------------------------------------------------------------------------------ +systemd_boot_headers = \ + src/boot/efi/util.h \ + src/boot/efi/console.h \ + src/boot/efi/graphics.h \ + src/boot/efi/pefile.h \ + src/boot/efi/measure.h \ + src/boot/efi/disk.h + +systemd_boot_sources = \ + src/boot/efi/util.c \ + src/boot/efi/console.c \ + src/boot/efi/graphics.c \ + src/boot/efi/pefile.c \ + src/boot/efi/disk.c \ + src/boot/efi/measure.c \ + src/boot/efi/boot.c + +EXTRA_DIST += $(systemd_boot_sources) $(systemd_boot_headers) + +systemd_boot_objects = $(addprefix $(top_builddir)/,$(systemd_boot_sources:.c=.o)) +systemd_boot_solib = $(top_builddir)/src/boot/efi/systemd_boot.so +systemd_boot = systemd-boot$(EFI_MACHINE_TYPE_NAME).efi + +ifneq ($(ENABLE_EFI),) +ifneq ($(HAVE_GNUEFI),) +bootlib_DATA = $(systemd_boot) + +$(outdir)/%.o: $(top_srcdir)/src/boot/efi/%.c $(addprefix $(top_srcdir)/,$(systemd_boot_headers)) + @$(MKDIR_P) $(top_builddir)/src/boot/efi/ + $(AM_V_CC)$(EFI_CC) $(efi_cppflags) $(efi_cflags) -c $< -o $@ + +$(systemd_boot_solib): $(systemd_boot_objects) + $(AM_V_CCLD)$(LD) $(efi_ldflags) $(systemd_boot_objects) \ + -o $@ -lefi -lgnuefi $(shell $(CC) -print-libgcc-file-name); \ + nm -D -u $@ | grep ' U ' && exit 1 || : + +$(systemd_boot): $(systemd_boot_solib) + $(AM_V_GEN)$(OBJCOPY) -j .text -j .sdata -j .data -j .dynamic \ + -j .dynsym -j .rel -j .rela -j .reloc $(EFI_FORMAT) $< $@ +endif # HAVE_GNUEFI +endif # ENABLE_EFI + +CLEANFILES += $(systemd_boot_objects) $(systemd_boot_solib) $(systemd_boot) + +# ------------------------------------------------------------------------------ +stub_headers = \ + src/boot/efi/util.h \ + src/boot/efi/pefile.h \ + src/boot/efi/disk.h \ + src/boot/efi/graphics.h \ + src/boot/efi/splash.h \ + src/boot/efi/measure.h \ + src/boot/efi/linux.h + +stub_sources = \ + src/boot/efi/util.c \ + src/boot/efi/pefile.c \ + src/boot/efi/disk.c \ + src/boot/efi/graphics.c \ + src/boot/efi/splash.c \ + src/boot/efi/linux.c \ + src/boot/efi/measure.c \ + src/boot/efi/stub.c + +EXTRA_DIST += \ + $(stub_sources) \ + $(stub_headers) \ + test/splash.bmp + +stub_objects = $(addprefix $(top_builddir)/,$(stub_sources:.c=.o)) +stub_solib = $(top_builddir)/src/boot/efi/stub.so +stub = linux$(EFI_MACHINE_TYPE_NAME).efi.stub + +ifneq ($(ENABLE_EFI),) +ifneq ($(HAVE_GNUEFI),) +bootlib_DATA += $(stub) + +$(outdir)/%.o: $(top_srcdir)/src/boot/efi/%.c $(addprefix $(top_srcdir)/,$(stub_headers)) + @$(MKDIR_P) $(top_builddir)/src/boot/efi/ + $(AM_V_CC)$(EFI_CC) $(efi_cppflags) $(efi_cflags) -c $< -o $@ + +$(stub_solib): $(stub_objects) + $(AM_V_CCLD)$(LD) $(efi_ldflags) $(stub_objects) \ + -o $@ -lefi -lgnuefi $(shell $(CC) -print-libgcc-file-name); \ + nm -D -u $@ | grep ' U ' && exit 1 || : + +$(stub): $(stub_solib) + $(AM_V_GEN)$(OBJCOPY) -j .text -j .sdata -j .data -j .dynamic \ + -j .dynsym -j .rel -j .rela -j .reloc $(EFI_FORMAT) $< $@ +endif # HAVE_GNUEFI +endif # ENABLE_EFI + +CLEANFILES += $(stub_objects) $(stub_solib) $(stub) + + +# ------------------------------------------------------------------------------ +CLEANFILES += test-efi-disk.img + +test-efi-disk.img: $(systemd_boot) $(stub) test/test-efi-create-disk.sh + $(AM_V_GEN)test/test-efi-create-disk.sh + +test-efi: test-efi-disk.img + $(QEMU) -machine accel=kvm -m 1024 -bios $(QEMU_BIOS) -snapshot test-efi-disk.img + +EXTRA_DIST += test/test-efi-create-disk.sh + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-boot/systemd-boot/boot.c b/src/grp-boot/systemd-boot/boot.c new file mode 100644 index 0000000000..30c1ead1aa --- /dev/null +++ b/src/grp-boot/systemd-boot/boot.c @@ -0,0 +1,1857 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2012-2015 Kay Sievers + * Copyright (C) 2012-2015 Harald Hoyer + */ + +#include +#include + +#include "console.h" +#include "disk.h" +#include "graphics.h" +#include "linux.h" +#include "pefile.h" +#include "util.h" +#include "measure.h" + +#ifndef EFI_OS_INDICATIONS_BOOT_TO_FW_UI +#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001ULL +#endif + +/* magic string to find in the binary image */ +static const char __attribute__((used)) magic[] = "#### LoaderInfo: systemd-boot " VERSION " ####"; + +static const EFI_GUID global_guid = EFI_GLOBAL_VARIABLE; + +enum loader_type { + LOADER_UNDEFINED, + LOADER_EFI, + LOADER_LINUX +}; + +typedef struct { + CHAR16 *file; + CHAR16 *title_show; + CHAR16 *title; + CHAR16 *version; + CHAR16 *machine_id; + EFI_HANDLE *device; + enum loader_type type; + CHAR16 *loader; + CHAR16 *options; + CHAR16 key; + EFI_STATUS (*call)(VOID); + BOOLEAN no_autoselect; + BOOLEAN non_unique; +} ConfigEntry; + +typedef struct { + ConfigEntry **entries; + UINTN entry_count; + INTN idx_default; + INTN idx_default_efivar; + UINTN timeout_sec; + UINTN timeout_sec_config; + INTN timeout_sec_efivar; + CHAR16 *entry_default_pattern; + CHAR16 *entry_oneshot; + CHAR16 *options_edit; + BOOLEAN no_editor; +} Config; + +static VOID cursor_left(UINTN *cursor, UINTN *first) { + if ((*cursor) > 0) + (*cursor)--; + else if ((*first) > 0) + (*first)--; +} + +static VOID cursor_right(UINTN *cursor, UINTN *first, UINTN x_max, UINTN len) { + if ((*cursor)+1 < x_max) + (*cursor)++; + else if ((*first) + (*cursor) < len) + (*first)++; +} + +static BOOLEAN line_edit(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN y_pos) { + CHAR16 *line; + UINTN size; + UINTN len; + UINTN first; + CHAR16 *print; + UINTN cursor; + UINTN clear; + BOOLEAN exit; + BOOLEAN enter; + + if (!line_in) + line_in = L""; + size = StrLen(line_in) + 1024; + line = AllocatePool(size * sizeof(CHAR16)); + StrCpy(line, line_in); + len = StrLen(line); + print = AllocatePool((x_max+1) * sizeof(CHAR16)); + + uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, TRUE); + + first = 0; + cursor = 0; + clear = 0; + enter = FALSE; + exit = FALSE; + while (!exit) { + EFI_STATUS err; + UINT64 key; + UINTN i; + + i = len - first; + if (i >= x_max-1) + i = x_max-1; + CopyMem(print, line + first, i * sizeof(CHAR16)); + while (clear > 0 && i < x_max-1) { + clear--; + print[i++] = ' '; + } + print[i] = '\0'; + + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_pos); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, print); + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos); + + err = console_key_read(&key, TRUE); + if (EFI_ERROR(err)) + continue; + + switch (key) { + case KEYPRESS(0, SCAN_ESC, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'c'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'g'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('c')): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('g')): + exit = TRUE; + break; + + case KEYPRESS(0, SCAN_HOME, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'a'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('a')): + /* beginning-of-line */ + cursor = 0; + first = 0; + continue; + + case KEYPRESS(0, SCAN_END, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'e'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('e')): + /* end-of-line */ + cursor = len - first; + if (cursor+1 >= x_max) { + cursor = x_max-1; + first = len - (x_max-1); + } + continue; + + case KEYPRESS(0, SCAN_DOWN, 0): + case KEYPRESS(EFI_ALT_PRESSED, 0, 'f'): + case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_RIGHT, 0): + /* forward-word */ + while (line[first + cursor] && line[first + cursor] == ' ') + cursor_right(&cursor, &first, x_max, len); + while (line[first + cursor] && line[first + cursor] != ' ') + cursor_right(&cursor, &first, x_max, len); + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos); + continue; + + case KEYPRESS(0, SCAN_UP, 0): + case KEYPRESS(EFI_ALT_PRESSED, 0, 'b'): + case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_LEFT, 0): + /* backward-word */ + if ((first + cursor) > 0 && line[first + cursor-1] == ' ') { + cursor_left(&cursor, &first); + while ((first + cursor) > 0 && line[first + cursor] == ' ') + cursor_left(&cursor, &first); + } + while ((first + cursor) > 0 && line[first + cursor-1] != ' ') + cursor_left(&cursor, &first); + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos); + continue; + + case KEYPRESS(0, SCAN_RIGHT, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'f'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('f')): + /* forward-char */ + if (first + cursor == len) + continue; + cursor_right(&cursor, &first, x_max, len); + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos); + continue; + + case KEYPRESS(0, SCAN_LEFT, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'b'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('b')): + /* backward-char */ + cursor_left(&cursor, &first); + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos); + continue; + + case KEYPRESS(EFI_ALT_PRESSED, 0, 'd'): + /* kill-word */ + clear = 0; + for (i = first + cursor; i < len && line[i] == ' '; i++) + clear++; + for (; i < len && line[i] != ' '; i++) + clear++; + + for (i = first + cursor; i + clear < len; i++) + line[i] = line[i + clear]; + len -= clear; + line[len] = '\0'; + continue; + + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'w'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('w')): + case KEYPRESS(EFI_ALT_PRESSED, 0, CHAR_BACKSPACE): + /* backward-kill-word */ + clear = 0; + if ((first + cursor) > 0 && line[first + cursor-1] == ' ') { + cursor_left(&cursor, &first); + clear++; + while ((first + cursor) > 0 && line[first + cursor] == ' ') { + cursor_left(&cursor, &first); + clear++; + } + } + while ((first + cursor) > 0 && line[first + cursor-1] != ' ') { + cursor_left(&cursor, &first); + clear++; + } + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos); + + for (i = first + cursor; i + clear < len; i++) + line[i] = line[i + clear]; + len -= clear; + line[len] = '\0'; + continue; + + case KEYPRESS(0, SCAN_DELETE, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'd'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('d')): + if (len == 0) + continue; + if (first + cursor == len) + continue; + for (i = first + cursor; i < len; i++) + line[i] = line[i+1]; + clear = 1; + len--; + continue; + + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'k'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('k')): + /* kill-line */ + line[first + cursor] = '\0'; + clear = len - (first + cursor); + len = first + cursor; + continue; + + case KEYPRESS(0, 0, CHAR_LINEFEED): + case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN): + if (StrCmp(line, line_in) != 0) { + *line_out = line; + line = NULL; + } + enter = TRUE; + exit = TRUE; + break; + + case KEYPRESS(0, 0, CHAR_BACKSPACE): + if (len == 0) + continue; + if (first == 0 && cursor == 0) + continue; + for (i = first + cursor-1; i < len; i++) + line[i] = line[i+1]; + clear = 1; + len--; + if (cursor > 0) + cursor--; + if (cursor > 0 || first == 0) + continue; + /* show full line if it fits */ + if (len < x_max) { + cursor = first; + first = 0; + continue; + } + /* jump left to see what we delete */ + if (first > 10) { + first -= 10; + cursor = 10; + } else { + cursor = first; + first = 0; + } + continue; + + case KEYPRESS(0, 0, ' ') ... KEYPRESS(0, 0, '~'): + case KEYPRESS(0, 0, 0x80) ... KEYPRESS(0, 0, 0xffff): + if (len+1 == size) + continue; + for (i = len; i > first + cursor; i--) + line[i] = line[i-1]; + line[first + cursor] = KEYCHAR(key); + len++; + line[len] = '\0'; + if (cursor+1 < x_max) + cursor++; + else if (first + cursor < len) + first++; + continue; + } + } + + uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE); + FreePool(print); + FreePool(line); + return enter; +} + +static UINTN entry_lookup_key(Config *config, UINTN start, CHAR16 key) { + UINTN i; + + if (key == 0) + return -1; + + /* select entry by number key */ + if (key >= '1' && key <= '9') { + i = key - '0'; + if (i > config->entry_count) + i = config->entry_count; + return i-1; + } + + /* find matching key in config entries */ + for (i = start; i < config->entry_count; i++) + if (config->entries[i]->key == key) + return i; + + for (i = 0; i < start; i++) + if (config->entries[i]->key == key) + return i; + + return -1; +} + +static VOID print_status(Config *config, CHAR16 *loaded_image_path) { + UINT64 key; + UINTN i; + CHAR16 *s; + CHAR8 *b; + UINTN x; + UINTN y; + UINTN size; + + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + + Print(L"systemd-boot version: " VERSION "\n"); + Print(L"architecture: " EFI_MACHINE_TYPE_NAME "\n"); + Print(L"loaded image: %s\n", loaded_image_path); + Print(L"UEFI specification: %d.%02d\n", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff); + Print(L"firmware vendor: %s\n", ST->FirmwareVendor); + Print(L"firmware version: %d.%02d\n", ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); + + if (uefi_call_wrapper(ST->ConOut->QueryMode, 4, ST->ConOut, ST->ConOut->Mode->Mode, &x, &y) == EFI_SUCCESS) + Print(L"console size: %d x %d\n", x, y); + + if (efivar_get_raw(&global_guid, L"SecureBoot", &b, &size) == EFI_SUCCESS) { + Print(L"SecureBoot: %s\n", yes_no(*b > 0)); + FreePool(b); + } + + if (efivar_get_raw(&global_guid, L"SetupMode", &b, &size) == EFI_SUCCESS) { + Print(L"SetupMode: %s\n", *b > 0 ? L"setup" : L"user"); + FreePool(b); + } + + if (efivar_get_raw(&global_guid, L"OsIndicationsSupported", &b, &size) == EFI_SUCCESS) { + Print(L"OsIndicationsSupported: %d\n", (UINT64)*b); + FreePool(b); + } + Print(L"\n"); + + Print(L"timeout: %d\n", config->timeout_sec); + if (config->timeout_sec_efivar >= 0) + Print(L"timeout (EFI var): %d\n", config->timeout_sec_efivar); + Print(L"timeout (config): %d\n", config->timeout_sec_config); + if (config->entry_default_pattern) + Print(L"default pattern: '%s'\n", config->entry_default_pattern); + Print(L"editor: %s\n", yes_no(!config->no_editor)); + Print(L"\n"); + + Print(L"config entry count: %d\n", config->entry_count); + Print(L"entry selected idx: %d\n", config->idx_default); + if (config->idx_default_efivar >= 0) + Print(L"entry EFI var idx: %d\n", config->idx_default_efivar); + Print(L"\n"); + + if (efivar_get_int(L"LoaderConfigTimeout", &i) == EFI_SUCCESS) + Print(L"LoaderConfigTimeout: %d\n", i); + if (config->entry_oneshot) + Print(L"LoaderEntryOneShot: %s\n", config->entry_oneshot); + if (efivar_get(L"LoaderDevicePartUUID", &s) == EFI_SUCCESS) { + Print(L"LoaderDevicePartUUID: %s\n", s); + FreePool(s); + } + if (efivar_get(L"LoaderEntryDefault", &s) == EFI_SUCCESS) { + Print(L"LoaderEntryDefault: %s\n", s); + FreePool(s); + } + + Print(L"\n--- press key ---\n\n"); + console_key_read(&key, TRUE); + + for (i = 0; i < config->entry_count; i++) { + ConfigEntry *entry; + + if (key == KEYPRESS(0, SCAN_ESC, 0) || key == KEYPRESS(0, 0, 'q')) + break; + + entry = config->entries[i]; + Print(L"config entry: %d/%d\n", i+1, config->entry_count); + if (entry->file) + Print(L"file '%s'\n", entry->file); + Print(L"title show '%s'\n", entry->title_show); + if (entry->title) + Print(L"title '%s'\n", entry->title); + if (entry->version) + Print(L"version '%s'\n", entry->version); + if (entry->machine_id) + Print(L"machine-id '%s'\n", entry->machine_id); + if (entry->device) { + EFI_DEVICE_PATH *device_path; + CHAR16 *str; + + device_path = DevicePathFromHandle(entry->device); + if (device_path) { + str = DevicePathToStr(device_path); + Print(L"device handle '%s'\n", str); + FreePool(str); + } + } + if (entry->loader) + Print(L"loader '%s'\n", entry->loader); + if (entry->options) + Print(L"options '%s'\n", entry->options); + Print(L"auto-select %s\n", yes_no(!entry->no_autoselect)); + if (entry->call) + Print(L"internal call yes\n"); + + Print(L"\n--- press key ---\n\n"); + console_key_read(&key, TRUE); + } + + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); +} + +static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *loaded_image_path) { + EFI_STATUS err; + UINTN visible_max; + UINTN idx_highlight; + UINTN idx_highlight_prev; + UINTN idx_first; + UINTN idx_last; + BOOLEAN refresh; + BOOLEAN highlight; + UINTN i; + UINTN line_width; + CHAR16 **lines; + UINTN x_start; + UINTN y_start; + UINTN x_max; + UINTN y_max; + CHAR16 *status; + CHAR16 *clearline; + INTN timeout_remain; + INT16 idx; + BOOLEAN exit = FALSE; + BOOLEAN run = TRUE; + BOOLEAN wait = FALSE; + + graphics_mode(FALSE); + uefi_call_wrapper(ST->ConIn->Reset, 2, ST->ConIn, FALSE); + uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE); + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); + + /* draw a single character to make ClearScreen work on some firmware */ + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L" "); + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + + err = uefi_call_wrapper(ST->ConOut->QueryMode, 4, ST->ConOut, ST->ConOut->Mode->Mode, &x_max, &y_max); + if (EFI_ERROR(err)) { + x_max = 80; + y_max = 25; + } + + /* we check 10 times per second for a keystroke */ + if (config->timeout_sec > 0) + timeout_remain = config->timeout_sec * 10; + else + timeout_remain = -1; + + idx_highlight = config->idx_default; + idx_highlight_prev = 0; + + visible_max = y_max - 2; + + if ((UINTN)config->idx_default >= visible_max) + idx_first = config->idx_default-1; + else + idx_first = 0; + + idx_last = idx_first + visible_max-1; + + refresh = TRUE; + highlight = FALSE; + + /* length of the longest entry */ + line_width = 5; + for (i = 0; i < config->entry_count; i++) { + UINTN entry_len; + + entry_len = StrLen(config->entries[i]->title_show); + if (line_width < entry_len) + line_width = entry_len; + } + if (line_width > x_max-6) + line_width = x_max-6; + + /* offsets to center the entries on the screen */ + x_start = (x_max - (line_width)) / 2; + if (config->entry_count < visible_max) + y_start = ((visible_max - config->entry_count) / 2) + 1; + else + y_start = 0; + + /* menu entries title lines */ + lines = AllocatePool(sizeof(CHAR16 *) * config->entry_count); + for (i = 0; i < config->entry_count; i++) { + UINTN j, k; + + lines[i] = AllocatePool(((x_max+1) * sizeof(CHAR16))); + for (j = 0; j < x_start; j++) + lines[i][j] = ' '; + + for (k = 0; config->entries[i]->title_show[k] != '\0' && j < x_max; j++, k++) + lines[i][j] = config->entries[i]->title_show[k]; + + for (; j < x_max; j++) + lines[i][j] = ' '; + lines[i][x_max] = '\0'; + } + + status = NULL; + clearline = AllocatePool((x_max+1) * sizeof(CHAR16)); + for (i = 0; i < x_max; i++) + clearline[i] = ' '; + clearline[i] = 0; + + while (!exit) { + UINT64 key; + + if (refresh) { + for (i = 0; i < config->entry_count; i++) { + if (i < idx_first || i > idx_last) + continue; + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_start + i - idx_first); + if (i == idx_highlight) + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, + EFI_BLACK|EFI_BACKGROUND_LIGHTGRAY); + else + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, + EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[i]); + if ((INTN)i == config->idx_default_efivar) { + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, x_start-3, y_start + i - idx_first); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"=>"); + } + } + refresh = FALSE; + } else if (highlight) { + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_start + idx_highlight_prev - idx_first); + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[idx_highlight_prev]); + if ((INTN)idx_highlight_prev == config->idx_default_efivar) { + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, x_start-3, y_start + idx_highlight_prev - idx_first); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"=>"); + } + + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_start + idx_highlight - idx_first); + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_BLACK|EFI_BACKGROUND_LIGHTGRAY); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[idx_highlight]); + if ((INTN)idx_highlight == config->idx_default_efivar) { + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, x_start-3, y_start + idx_highlight - idx_first); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"=>"); + } + highlight = FALSE; + } + + if (timeout_remain > 0) { + FreePool(status); + status = PoolPrint(L"Boot in %d sec.", (timeout_remain + 5) / 10); + } + + /* print status at last line of screen */ + if (status) { + UINTN len; + UINTN x; + + /* center line */ + len = StrLen(status); + if (len < x_max) + x = (x_max - len) / 2; + else + x = 0; + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline + (x_max - x)); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, status); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1 + x + len); + } + + err = console_key_read(&key, wait); + if (EFI_ERROR(err)) { + /* timeout reached */ + if (timeout_remain == 0) { + exit = TRUE; + break; + } + + /* sleep and update status */ + if (timeout_remain > 0) { + uefi_call_wrapper(BS->Stall, 1, 100 * 1000); + timeout_remain--; + continue; + } + + /* timeout disabled, wait for next key */ + wait = TRUE; + continue; + } + + timeout_remain = -1; + + /* clear status after keystroke */ + if (status) { + FreePool(status); + status = NULL; + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1); + } + + idx_highlight_prev = idx_highlight; + + switch (key) { + case KEYPRESS(0, SCAN_UP, 0): + case KEYPRESS(0, 0, 'k'): + if (idx_highlight > 0) + idx_highlight--; + break; + + case KEYPRESS(0, SCAN_DOWN, 0): + case KEYPRESS(0, 0, 'j'): + if (idx_highlight < config->entry_count-1) + idx_highlight++; + break; + + case KEYPRESS(0, SCAN_HOME, 0): + case KEYPRESS(EFI_ALT_PRESSED, 0, '<'): + if (idx_highlight > 0) { + refresh = TRUE; + idx_highlight = 0; + } + break; + + case KEYPRESS(0, SCAN_END, 0): + case KEYPRESS(EFI_ALT_PRESSED, 0, '>'): + if (idx_highlight < config->entry_count-1) { + refresh = TRUE; + idx_highlight = config->entry_count-1; + } + break; + + case KEYPRESS(0, SCAN_PAGE_UP, 0): + if (idx_highlight > visible_max) + idx_highlight -= visible_max; + else + idx_highlight = 0; + break; + + case KEYPRESS(0, SCAN_PAGE_DOWN, 0): + idx_highlight += visible_max; + if (idx_highlight > config->entry_count-1) + idx_highlight = config->entry_count-1; + break; + + case KEYPRESS(0, 0, CHAR_LINEFEED): + case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN): + exit = TRUE; + break; + + case KEYPRESS(0, SCAN_F1, 0): + case KEYPRESS(0, 0, 'h'): + case KEYPRESS(0, 0, '?'): + status = StrDuplicate(L"(d)efault, (t/T)timeout, (e)dit, (v)ersion (Q)uit (P)rint (h)elp"); + break; + + case KEYPRESS(0, 0, 'Q'): + exit = TRUE; + run = FALSE; + break; + + case KEYPRESS(0, 0, 'd'): + if (config->idx_default_efivar != (INTN)idx_highlight) { + /* store the selected entry in a persistent EFI variable */ + efivar_set(L"LoaderEntryDefault", config->entries[idx_highlight]->file, TRUE); + config->idx_default_efivar = idx_highlight; + status = StrDuplicate(L"Default boot entry selected."); + } else { + /* clear the default entry EFI variable */ + efivar_set(L"LoaderEntryDefault", NULL, TRUE); + config->idx_default_efivar = -1; + status = StrDuplicate(L"Default boot entry cleared."); + } + refresh = TRUE; + break; + + case KEYPRESS(0, 0, '-'): + case KEYPRESS(0, 0, 'T'): + if (config->timeout_sec_efivar > 0) { + config->timeout_sec_efivar--; + efivar_set_int(L"LoaderConfigTimeout", config->timeout_sec_efivar, TRUE); + if (config->timeout_sec_efivar > 0) + status = PoolPrint(L"Menu timeout set to %d sec.", config->timeout_sec_efivar); + else + status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu."); + } else if (config->timeout_sec_efivar <= 0){ + config->timeout_sec_efivar = -1; + efivar_set(L"LoaderConfigTimeout", NULL, TRUE); + if (config->timeout_sec_config > 0) + status = PoolPrint(L"Menu timeout of %d sec is defined by configuration file.", + config->timeout_sec_config); + else + status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu."); + } + break; + + case KEYPRESS(0, 0, '+'): + case KEYPRESS(0, 0, 't'): + if (config->timeout_sec_efivar == -1 && config->timeout_sec_config == 0) + config->timeout_sec_efivar++; + config->timeout_sec_efivar++; + efivar_set_int(L"LoaderConfigTimeout", config->timeout_sec_efivar, TRUE); + if (config->timeout_sec_efivar > 0) + status = PoolPrint(L"Menu timeout set to %d sec.", + config->timeout_sec_efivar); + else + status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu."); + break; + + case KEYPRESS(0, 0, 'e'): + /* only the options of configured entries can be edited */ + if (config->no_editor || config->entries[idx_highlight]->type == LOADER_UNDEFINED) + break; + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1); + if (line_edit(config->entries[idx_highlight]->options, &config->options_edit, x_max-1, y_max-1)) + exit = TRUE; + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1); + break; + + case KEYPRESS(0, 0, 'v'): + status = PoolPrint(L"systemd-boot " VERSION " (" EFI_MACHINE_TYPE_NAME "), UEFI Specification %d.%02d, Vendor %s %d.%02d", + ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff, + ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); + break; + + case KEYPRESS(0, 0, 'P'): + print_status(config, loaded_image_path); + refresh = TRUE; + break; + + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'l'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('l')): + refresh = TRUE; + break; + + default: + /* jump with a hotkey directly to a matching entry */ + idx = entry_lookup_key(config, idx_highlight+1, KEYCHAR(key)); + if (idx < 0) + break; + idx_highlight = idx; + refresh = TRUE; + } + + if (idx_highlight > idx_last) { + idx_last = idx_highlight; + idx_first = 1 + idx_highlight - visible_max; + refresh = TRUE; + } else if (idx_highlight < idx_first) { + idx_first = idx_highlight; + idx_last = idx_highlight + visible_max-1; + refresh = TRUE; + } + + if (!refresh && idx_highlight != idx_highlight_prev) + highlight = TRUE; + } + + *chosen_entry = config->entries[idx_highlight]; + + for (i = 0; i < config->entry_count; i++) + FreePool(lines[i]); + FreePool(lines); + FreePool(clearline); + + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_WHITE|EFI_BACKGROUND_BLACK); + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + return run; +} + +static VOID config_add_entry(Config *config, ConfigEntry *entry) { + if ((config->entry_count & 15) == 0) { + UINTN i; + + i = config->entry_count + 16; + if (config->entry_count == 0) + config->entries = AllocatePool(sizeof(VOID *) * i); + else + config->entries = ReallocatePool(config->entries, + sizeof(VOID *) * config->entry_count, sizeof(VOID *) * i); + } + config->entries[config->entry_count++] = entry; +} + +static VOID config_entry_free(ConfigEntry *entry) { + FreePool(entry->title_show); + FreePool(entry->title); + FreePool(entry->machine_id); + FreePool(entry->loader); + FreePool(entry->options); +} + +static BOOLEAN is_digit(CHAR16 c) { + return (c >= '0') && (c <= '9'); +} + +static UINTN c_order(CHAR16 c) { + if (c == '\0') + return 0; + if (is_digit(c)) + return 0; + else if ((c >= 'a') && (c <= 'z')) + return c; + else + return c + 0x10000; +} + +static INTN str_verscmp(CHAR16 *s1, CHAR16 *s2) { + CHAR16 *os1 = s1; + CHAR16 *os2 = s2; + + while (*s1 || *s2) { + INTN first; + + while ((*s1 && !is_digit(*s1)) || (*s2 && !is_digit(*s2))) { + INTN order; + + order = c_order(*s1) - c_order(*s2); + if (order) + return order; + s1++; + s2++; + } + + while (*s1 == '0') + s1++; + while (*s2 == '0') + s2++; + + first = 0; + while (is_digit(*s1) && is_digit(*s2)) { + if (first == 0) + first = *s1 - *s2; + s1++; + s2++; + } + + if (is_digit(*s1)) + return 1; + if (is_digit(*s2)) + return -1; + + if (first) + return first; + } + + return StrCmp(os1, os2); +} + +static CHAR8 *line_get_key_value(CHAR8 *content, CHAR8 *sep, UINTN *pos, CHAR8 **key_ret, CHAR8 **value_ret) { + CHAR8 *line; + UINTN linelen; + CHAR8 *value; + +skip: + line = content + *pos; + if (*line == '\0') + return NULL; + + linelen = 0; + while (line[linelen] && !strchra((CHAR8 *)"\n\r", line[linelen])) + linelen++; + + /* move pos to next line */ + *pos += linelen; + if (content[*pos]) + (*pos)++; + + /* empty line */ + if (linelen == 0) + goto skip; + + /* terminate line */ + line[linelen] = '\0'; + + /* remove leading whitespace */ + while (strchra((CHAR8 *)" \t", *line)) { + line++; + linelen--; + } + + /* remove trailing whitespace */ + while (linelen > 0 && strchra(sep, line[linelen-1])) + linelen--; + line[linelen] = '\0'; + + if (*line == '#') + goto skip; + + /* split key/value */ + value = line; + while (*value && !strchra(sep, *value)) + value++; + if (*value == '\0') + goto skip; + *value = '\0'; + value++; + while (*value && strchra(sep, *value)) + value++; + + /* unquote */ + if (value[0] == '\"' && line[linelen-1] == '\"') { + value++; + line[linelen-1] = '\0'; + } + + *key_ret = line; + *value_ret = value; + return line; +} + +static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) { + CHAR8 *line; + UINTN pos = 0; + CHAR8 *key, *value; + + line = content; + while ((line = line_get_key_value(content, (CHAR8 *)" \t", &pos, &key, &value))) { + if (strcmpa((CHAR8 *)"timeout", key) == 0) { + CHAR16 *s; + + s = stra_to_str(value); + config->timeout_sec_config = Atoi(s); + config->timeout_sec = config->timeout_sec_config; + FreePool(s); + continue; + } + + if (strcmpa((CHAR8 *)"default", key) == 0) { + FreePool(config->entry_default_pattern); + config->entry_default_pattern = stra_to_str(value); + StrLwr(config->entry_default_pattern); + continue; + } + + if (strcmpa((CHAR8 *)"editor", key) == 0) { + BOOLEAN on; + + if (EFI_ERROR(parse_boolean(value, &on))) + continue; + config->no_editor = !on; + } + } +} + +static VOID config_entry_add_from_file(Config *config, EFI_HANDLE *device, CHAR16 *file, CHAR8 *content, CHAR16 *loaded_image_path) { + ConfigEntry *entry; + CHAR8 *line; + UINTN pos = 0; + CHAR8 *key, *value; + UINTN len; + CHAR16 *initrd = NULL; + + entry = AllocateZeroPool(sizeof(ConfigEntry)); + + line = content; + while ((line = line_get_key_value(content, (CHAR8 *)" \t", &pos, &key, &value))) { + if (strcmpa((CHAR8 *)"title", key) == 0) { + FreePool(entry->title); + entry->title = stra_to_str(value); + continue; + } + + if (strcmpa((CHAR8 *)"version", key) == 0) { + FreePool(entry->version); + entry->version = stra_to_str(value); + continue; + } + + if (strcmpa((CHAR8 *)"machine-id", key) == 0) { + FreePool(entry->machine_id); + entry->machine_id = stra_to_str(value); + continue; + } + + if (strcmpa((CHAR8 *)"linux", key) == 0) { + FreePool(entry->loader); + entry->type = LOADER_LINUX; + entry->loader = stra_to_path(value); + entry->key = 'l'; + continue; + } + + if (strcmpa((CHAR8 *)"efi", key) == 0) { + entry->type = LOADER_EFI; + FreePool(entry->loader); + entry->loader = stra_to_path(value); + + /* do not add an entry for ourselves */ + if (StriCmp(entry->loader, loaded_image_path) == 0) { + entry->type = LOADER_UNDEFINED; + break; + } + continue; + } + + if (strcmpa((CHAR8 *)"architecture", key) == 0) { + /* do not add an entry for an EFI image of architecture not matching with that of the image */ + if (strcmpa((CHAR8 *)EFI_MACHINE_TYPE_NAME, value) != 0) { + entry->type = LOADER_UNDEFINED; + break; + } + continue; + } + + if (strcmpa((CHAR8 *)"initrd", key) == 0) { + CHAR16 *new; + + new = stra_to_path(value); + if (initrd) { + CHAR16 *s; + + s = PoolPrint(L"%s initrd=%s", initrd, new); + FreePool(initrd); + initrd = s; + } else + initrd = PoolPrint(L"initrd=%s", new); + FreePool(new); + continue; + } + + if (strcmpa((CHAR8 *)"options", key) == 0) { + CHAR16 *new; + + new = stra_to_str(value); + if (entry->options) { + CHAR16 *s; + + s = PoolPrint(L"%s %s", entry->options, new); + FreePool(entry->options); + entry->options = s; + } else { + entry->options = new; + new = NULL; + } + FreePool(new); + continue; + } + } + + if (entry->type == LOADER_UNDEFINED) { + config_entry_free(entry); + FreePool(initrd); + FreePool(entry); + return; + } + + /* add initrd= to options */ + if (entry->type == LOADER_LINUX && initrd) { + if (entry->options) { + CHAR16 *s; + + s = PoolPrint(L"%s %s", initrd, entry->options); + FreePool(entry->options); + entry->options = s; + } else { + entry->options = initrd; + initrd = NULL; + } + } + FreePool(initrd); + + entry->device = device; + entry->file = StrDuplicate(file); + len = StrLen(entry->file); + /* remove ".conf" */ + if (len > 5) + entry->file[len - 5] = '\0'; + StrLwr(entry->file); + + config_add_entry(config, entry); +} + +static VOID config_load_defaults(Config *config, EFI_FILE *root_dir) { + CHAR8 *content = NULL; + UINTN sec; + UINTN len; + EFI_STATUS err; + + len = file_read(root_dir, L"\\loader\\loader.conf", 0, 0, &content); + if (len > 0) + config_defaults_load_from_file(config, content); + FreePool(content); + + err = efivar_get_int(L"LoaderConfigTimeout", &sec); + if (!EFI_ERROR(err)) { + config->timeout_sec_efivar = sec; + config->timeout_sec = sec; + } else + config->timeout_sec_efivar = -1; +} + +static VOID config_load_entries(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path) { + EFI_FILE_HANDLE entries_dir; + EFI_STATUS err; + + err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &entries_dir, L"\\loader\\entries", EFI_FILE_MODE_READ, 0ULL); + if (!EFI_ERROR(err)) { + for (;;) { + CHAR16 buf[256]; + UINTN bufsize; + EFI_FILE_INFO *f; + CHAR8 *content = NULL; + UINTN len; + + bufsize = sizeof(buf); + err = uefi_call_wrapper(entries_dir->Read, 3, entries_dir, &bufsize, buf); + if (bufsize == 0 || EFI_ERROR(err)) + break; + + f = (EFI_FILE_INFO *) buf; + if (f->FileName[0] == '.') + continue; + if (f->Attribute & EFI_FILE_DIRECTORY) + continue; + + len = StrLen(f->FileName); + if (len < 6) + continue; + if (StriCmp(f->FileName + len - 5, L".conf") != 0) + continue; + if (StrnCmp(f->FileName, L"auto-", 5) == 0) + continue; + + len = file_read(entries_dir, f->FileName, 0, 0, &content); + if (len > 0) + config_entry_add_from_file(config, device, f->FileName, content, loaded_image_path); + FreePool(content); + } + uefi_call_wrapper(entries_dir->Close, 1, entries_dir); + } +} + +static VOID config_sort_entries(Config *config) { + UINTN i; + + for (i = 1; i < config->entry_count; i++) { + BOOLEAN more; + UINTN k; + + more = FALSE; + for (k = 0; k < config->entry_count - i; k++) { + ConfigEntry *entry; + + if (str_verscmp(config->entries[k]->file, config->entries[k+1]->file) <= 0) + continue; + entry = config->entries[k]; + config->entries[k] = config->entries[k+1]; + config->entries[k+1] = entry; + more = TRUE; + } + if (!more) + break; + } +} + +static VOID config_default_entry_select(Config *config) { + CHAR16 *var; + EFI_STATUS err; + UINTN i; + + /* + * The EFI variable to specify a boot entry for the next, and only the + * next reboot. The variable is always cleared directly after it is read. + */ + err = efivar_get(L"LoaderEntryOneShot", &var); + if (!EFI_ERROR(err)) { + BOOLEAN found = FALSE; + + for (i = 0; i < config->entry_count; i++) { + if (StrCmp(config->entries[i]->file, var) == 0) { + config->idx_default = i; + found = TRUE; + break; + } + } + + config->entry_oneshot = StrDuplicate(var); + efivar_set(L"LoaderEntryOneShot", NULL, TRUE); + FreePool(var); + if (found) + return; + } + + /* + * The EFI variable to select the default boot entry overrides the + * configured pattern. The variable can be set and cleared by pressing + * the 'd' key in the loader selection menu, the entry is marked with + * an '*'. + */ + err = efivar_get(L"LoaderEntryDefault", &var); + if (!EFI_ERROR(err)) { + BOOLEAN found = FALSE; + + for (i = 0; i < config->entry_count; i++) { + if (StrCmp(config->entries[i]->file, var) == 0) { + config->idx_default = i; + config->idx_default_efivar = i; + found = TRUE; + break; + } + } + FreePool(var); + if (found) + return; + } + config->idx_default_efivar = -1; + + if (config->entry_count == 0) + return; + + /* + * Match the pattern from the end of the list to the start, find last + * entry (largest number) matching the given pattern. + */ + if (config->entry_default_pattern) { + i = config->entry_count; + while (i--) { + if (config->entries[i]->no_autoselect) + continue; + if (MetaiMatch(config->entries[i]->file, config->entry_default_pattern)) { + config->idx_default = i; + return; + } + } + } + + /* select the last suitable entry */ + i = config->entry_count; + while (i--) { + if (config->entries[i]->no_autoselect) + continue; + config->idx_default = i; + return; + } + + /* no entry found */ + config->idx_default = -1; +} + +/* generate a unique title, avoiding non-distinguishable menu entries */ +static VOID config_title_generate(Config *config) { + UINTN i, k; + BOOLEAN unique; + + /* set title */ + for (i = 0; i < config->entry_count; i++) { + CHAR16 *title; + + FreePool(config->entries[i]->title_show); + title = config->entries[i]->title; + if (!title) + title = config->entries[i]->file; + config->entries[i]->title_show = StrDuplicate(title); + } + + unique = TRUE; + for (i = 0; i < config->entry_count; i++) { + for (k = 0; k < config->entry_count; k++) { + if (i == k) + continue; + if (StrCmp(config->entries[i]->title_show, config->entries[k]->title_show) != 0) + continue; + + unique = FALSE; + config->entries[i]->non_unique = TRUE; + config->entries[k]->non_unique = TRUE; + } + } + if (unique) + return; + + /* add version to non-unique titles */ + for (i = 0; i < config->entry_count; i++) { + CHAR16 *s; + + if (!config->entries[i]->non_unique) + continue; + if (!config->entries[i]->version) + continue; + + s = PoolPrint(L"%s (%s)", config->entries[i]->title_show, config->entries[i]->version); + FreePool(config->entries[i]->title_show); + config->entries[i]->title_show = s; + config->entries[i]->non_unique = FALSE; + } + + unique = TRUE; + for (i = 0; i < config->entry_count; i++) { + for (k = 0; k < config->entry_count; k++) { + if (i == k) + continue; + if (StrCmp(config->entries[i]->title_show, config->entries[k]->title_show) != 0) + continue; + + unique = FALSE; + config->entries[i]->non_unique = TRUE; + config->entries[k]->non_unique = TRUE; + } + } + if (unique) + return; + + /* add machine-id to non-unique titles */ + for (i = 0; i < config->entry_count; i++) { + CHAR16 *s; + CHAR16 *m; + + if (!config->entries[i]->non_unique) + continue; + if (!config->entries[i]->machine_id) + continue; + + m = StrDuplicate(config->entries[i]->machine_id); + m[8] = '\0'; + s = PoolPrint(L"%s (%s)", config->entries[i]->title_show, m); + FreePool(config->entries[i]->title_show); + config->entries[i]->title_show = s; + config->entries[i]->non_unique = FALSE; + FreePool(m); + } + + unique = TRUE; + for (i = 0; i < config->entry_count; i++) { + for (k = 0; k < config->entry_count; k++) { + if (i == k) + continue; + if (StrCmp(config->entries[i]->title_show, config->entries[k]->title_show) != 0) + continue; + + unique = FALSE; + config->entries[i]->non_unique = TRUE; + config->entries[k]->non_unique = TRUE; + } + } + if (unique) + return; + + /* add file name to non-unique titles */ + for (i = 0; i < config->entry_count; i++) { + CHAR16 *s; + + if (!config->entries[i]->non_unique) + continue; + s = PoolPrint(L"%s (%s)", config->entries[i]->title_show, config->entries[i]->file); + FreePool(config->entries[i]->title_show); + config->entries[i]->title_show = s; + config->entries[i]->non_unique = FALSE; + } +} + +static BOOLEAN config_entry_add_call(Config *config, CHAR16 *title, EFI_STATUS (*call)(VOID)) { + ConfigEntry *entry; + + entry = AllocateZeroPool(sizeof(ConfigEntry)); + entry->title = StrDuplicate(title); + entry->call = call; + entry->no_autoselect = TRUE; + config_add_entry(config, entry); + return TRUE; +} + +static ConfigEntry *config_entry_add_loader(Config *config, EFI_HANDLE *device, + enum loader_type type,CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) { + ConfigEntry *entry; + + entry = AllocateZeroPool(sizeof(ConfigEntry)); + entry->type = type; + entry->title = StrDuplicate(title); + entry->device = device; + entry->loader = StrDuplicate(loader); + entry->file = StrDuplicate(file); + StrLwr(entry->file); + entry->key = key; + config_add_entry(config, entry); + + return entry; +} + +static BOOLEAN config_entry_add_loader_auto(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path, + CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) { + EFI_FILE_HANDLE handle; + ConfigEntry *entry; + EFI_STATUS err; + + /* do not add an entry for ourselves */ + if (loaded_image_path && StriCmp(loader, loaded_image_path) == 0) + return FALSE; + + /* check existence */ + err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &handle, loader, EFI_FILE_MODE_READ, 0ULL); + if (EFI_ERROR(err)) + return FALSE; + uefi_call_wrapper(handle->Close, 1, handle); + + entry = config_entry_add_loader(config, device, LOADER_UNDEFINED, file, key, title, loader); + if (!entry) + return FALSE; + + /* do not boot right away into auto-detected entries */ + entry->no_autoselect = TRUE; + + return TRUE; +} + +static VOID config_entry_add_osx(Config *config) { + EFI_STATUS err; + UINTN handle_count = 0; + EFI_HANDLE *handles = NULL; + + err = LibLocateHandle(ByProtocol, &FileSystemProtocol, NULL, &handle_count, &handles); + if (!EFI_ERROR(err)) { + UINTN i; + + for (i = 0; i < handle_count; i++) { + EFI_FILE *root; + BOOLEAN found; + + root = LibOpenRoot(handles[i]); + if (!root) + continue; + found = config_entry_add_loader_auto(config, handles[i], root, NULL, L"auto-osx", 'a', L"OS X", + L"\\System\\Library\\CoreServices\\boot.efi"); + uefi_call_wrapper(root->Close, 1, root); + if (found) + break; + } + + FreePool(handles); + } +} + +static VOID config_entry_add_linux( Config *config, EFI_LOADED_IMAGE *loaded_image, EFI_FILE *root_dir) { + EFI_FILE_HANDLE linux_dir; + EFI_STATUS err; + ConfigEntry *entry; + + err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &linux_dir, L"\\EFI\\Linux", EFI_FILE_MODE_READ, 0ULL); + if (!EFI_ERROR(err)) { + for (;;) { + CHAR16 buf[256]; + UINTN bufsize; + EFI_FILE_INFO *f; + CHAR8 *sections[] = { + (UINT8 *)".osrel", + (UINT8 *)".cmdline", + NULL + }; + UINTN offs[ELEMENTSOF(sections)-1] = {}; + UINTN szs[ELEMENTSOF(sections)-1] = {}; + UINTN addrs[ELEMENTSOF(sections)-1] = {}; + CHAR8 *content = NULL; + UINTN len; + CHAR8 *line; + UINTN pos = 0; + CHAR8 *key, *value; + CHAR16 *os_name = NULL; + CHAR16 *os_id = NULL; + CHAR16 *os_version = NULL; + CHAR16 *os_build = NULL; + + bufsize = sizeof(buf); + err = uefi_call_wrapper(linux_dir->Read, 3, linux_dir, &bufsize, buf); + if (bufsize == 0 || EFI_ERROR(err)) + break; + + f = (EFI_FILE_INFO *) buf; + if (f->FileName[0] == '.') + continue; + if (f->Attribute & EFI_FILE_DIRECTORY) + continue; + len = StrLen(f->FileName); + if (len < 5) + continue; + if (StriCmp(f->FileName + len - 4, L".efi") != 0) + continue; + + /* look for .osrel and .cmdline sections in the .efi binary */ + err = pefile_locate_sections(linux_dir, f->FileName, sections, addrs, offs, szs); + if (EFI_ERROR(err)) + continue; + + len = file_read(linux_dir, f->FileName, offs[0], szs[0], &content); + if (len <= 0) + continue; + + /* read properties from the embedded os-release file */ + line = content; + while ((line = line_get_key_value(content, (CHAR8 *)"=", &pos, &key, &value))) { + if (strcmpa((CHAR8 *)"PRETTY_NAME", key) == 0) { + FreePool(os_name); + os_name = stra_to_str(value); + continue; + } + + if (strcmpa((CHAR8 *)"ID", key) == 0) { + FreePool(os_id); + os_id = stra_to_str(value); + continue; + } + + if (strcmpa((CHAR8 *)"VERSION_ID", key) == 0) { + FreePool(os_version); + os_version = stra_to_str(value); + continue; + } + + if (strcmpa((CHAR8 *)"BUILD_ID", key) == 0) { + FreePool(os_build); + os_build = stra_to_str(value); + continue; + } + } + + if (os_name && os_id && (os_version || os_build)) { + CHAR16 *conf; + CHAR16 *path; + CHAR16 *cmdline; + + conf = PoolPrint(L"%s-%s", os_id, os_version ? : os_build); + path = PoolPrint(L"\\EFI\\Linux\\%s", f->FileName); + entry = config_entry_add_loader(config, loaded_image->DeviceHandle, LOADER_LINUX, conf, 'l', os_name, path); + + FreePool(content); + /* read the embedded cmdline file */ + len = file_read(linux_dir, f->FileName, offs[1], szs[1] - 1 , &content); + if (len > 0) { + cmdline = stra_to_str(content); + entry->options = cmdline; + cmdline = NULL; + } + FreePool(cmdline); + FreePool(conf); + FreePool(path); + } + + FreePool(os_name); + FreePool(os_id); + FreePool(os_version); + FreePool(os_build); + FreePool(content); + } + uefi_call_wrapper(linux_dir->Close, 1, linux_dir); + } +} + +static EFI_STATUS image_start(EFI_HANDLE parent_image, const Config *config, const ConfigEntry *entry) { + EFI_HANDLE image; + EFI_DEVICE_PATH *path; + CHAR16 *options; + EFI_STATUS err; + + path = FileDevicePath(entry->device, entry->loader); + if (!path) { + Print(L"Error getting device path."); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return EFI_INVALID_PARAMETER; + } + + err = uefi_call_wrapper(BS->LoadImage, 6, FALSE, parent_image, path, NULL, 0, &image); + if (EFI_ERROR(err)) { + Print(L"Error loading %s: %r", entry->loader, err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + goto out; + } + + if (config->options_edit) + options = config->options_edit; + else if (entry->options) + options = entry->options; + else + options = NULL; + if (options) { + EFI_LOADED_IMAGE *loaded_image; + + err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, (VOID **)&loaded_image, + parent_image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (EFI_ERROR(err)) { + Print(L"Error getting LoadedImageProtocol handle: %r", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + goto out_unload; + } + loaded_image->LoadOptions = options; + loaded_image->LoadOptionsSize = (StrLen(loaded_image->LoadOptions)+1) * sizeof(CHAR16); + +#ifdef SD_BOOT_LOG_TPM + /* Try to log any options to the TPM, escpecially to catch manually edited options */ + err = tpm_log_event(SD_TPM_PCR, + (EFI_PHYSICAL_ADDRESS) loaded_image->LoadOptions, + loaded_image->LoadOptionsSize, loaded_image->LoadOptions); + if (EFI_ERROR(err)) { + Print(L"Unable to add image options measurement: %r", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return err; + } +#endif + } + + efivar_set_time_usec(L"LoaderTimeExecUSec", 0); + err = uefi_call_wrapper(BS->StartImage, 3, image, NULL, NULL); +out_unload: + uefi_call_wrapper(BS->UnloadImage, 1, image); +out: + FreePool(path); + return err; +} + +static EFI_STATUS reboot_into_firmware(VOID) { + CHAR8 *b; + UINTN size; + UINT64 osind; + EFI_STATUS err; + + osind = EFI_OS_INDICATIONS_BOOT_TO_FW_UI; + + err = efivar_get_raw(&global_guid, L"OsIndications", &b, &size); + if (!EFI_ERROR(err)) + osind |= (UINT64)*b; + FreePool(b); + + err = efivar_set_raw(&global_guid, L"OsIndications", (CHAR8 *)&osind, sizeof(UINT64), TRUE); + if (EFI_ERROR(err)) + return err; + + err = uefi_call_wrapper(RT->ResetSystem, 4, EfiResetCold, EFI_SUCCESS, 0, NULL); + Print(L"Error calling ResetSystem: %r", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return err; +} + +static VOID config_free(Config *config) { + UINTN i; + + for (i = 0; i < config->entry_count; i++) + config_entry_free(config->entries[i]); + FreePool(config->entries); + FreePool(config->entry_default_pattern); + FreePool(config->options_edit); + FreePool(config->entry_oneshot); +} + +EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { + CHAR16 *s; + CHAR8 *b; + UINTN size; + EFI_LOADED_IMAGE *loaded_image; + EFI_FILE *root_dir; + CHAR16 *loaded_image_path; + EFI_STATUS err; + Config config; + UINT64 init_usec; + BOOLEAN menu = FALSE; + CHAR16 uuid[37]; + + InitializeLib(image, sys_table); + init_usec = time_usec(); + efivar_set_time_usec(L"LoaderTimeInitUSec", init_usec); + efivar_set(L"LoaderInfo", L"systemd-boot " VERSION, FALSE); + s = PoolPrint(L"%s %d.%02d", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); + efivar_set(L"LoaderFirmwareInfo", s, FALSE); + FreePool(s); + s = PoolPrint(L"UEFI %d.%02d", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff); + efivar_set(L"LoaderFirmwareType", s, FALSE); + FreePool(s); + + err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, (VOID **)&loaded_image, + image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (EFI_ERROR(err)) { + Print(L"Error getting a LoadedImageProtocol handle: %r ", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return err; + } + + /* export the device path this image is started from */ + if (disk_get_part_uuid(loaded_image->DeviceHandle, uuid) == EFI_SUCCESS) + efivar_set(L"LoaderDevicePartUUID", uuid, FALSE); + + root_dir = LibOpenRoot(loaded_image->DeviceHandle); + if (!root_dir) { + Print(L"Unable to open root directory: %r ", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return EFI_LOAD_ERROR; + } + + + /* the filesystem path to this image, to prevent adding ourselves to the menu */ + loaded_image_path = DevicePathToStr(loaded_image->FilePath); + efivar_set(L"LoaderImageIdentifier", loaded_image_path, FALSE); + + ZeroMem(&config, sizeof(Config)); + config_load_defaults(&config, root_dir); + + /* scan /EFI/Linux/ directory */ + config_entry_add_linux(&config, loaded_image, root_dir); + + /* scan /loader/entries/\*.conf files */ + config_load_entries(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path); + + /* sort entries after version number */ + config_sort_entries(&config); + + /* if we find some well-known loaders, add them to the end of the list */ + config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path, + L"auto-windows", 'w', L"Windows Boot Manager", L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi"); + config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path, + L"auto-efi-shell", 's', L"EFI Shell", L"\\shell" EFI_MACHINE_TYPE_NAME ".efi"); + config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path, + L"auto-efi-default", '\0', L"EFI Default Loader", L"\\EFI\\Boot\\boot" EFI_MACHINE_TYPE_NAME ".efi"); + config_entry_add_osx(&config); + + if (efivar_get_raw(&global_guid, L"OsIndicationsSupported", &b, &size) == EFI_SUCCESS) { + UINT64 osind = (UINT64)*b; + + if (osind & EFI_OS_INDICATIONS_BOOT_TO_FW_UI) + config_entry_add_call(&config, L"Reboot Into Firmware Interface", reboot_into_firmware); + FreePool(b); + } + + if (config.entry_count == 0) { + Print(L"No loader found. Configuration files in \\loader\\entries\\*.conf are needed."); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + goto out; + } + + config_title_generate(&config); + + /* select entry by configured pattern or EFI LoaderDefaultEntry= variable*/ + config_default_entry_select(&config); + + /* if no configured entry to select from was found, enable the menu */ + if (config.idx_default == -1) { + config.idx_default = 0; + if (config.timeout_sec == 0) + config.timeout_sec = 10; + } + + /* select entry or show menu when key is pressed or timeout is set */ + if (config.timeout_sec == 0) { + UINT64 key; + + err = console_key_read(&key, FALSE); + if (!EFI_ERROR(err)) { + INT16 idx; + + /* find matching key in config entries */ + idx = entry_lookup_key(&config, config.idx_default, KEYCHAR(key)); + if (idx >= 0) + config.idx_default = idx; + else + menu = TRUE; + } + } else + menu = TRUE; + + for (;;) { + ConfigEntry *entry; + + entry = config.entries[config.idx_default]; + if (menu) { + efivar_set_time_usec(L"LoaderTimeMenuUSec", 0); + uefi_call_wrapper(BS->SetWatchdogTimer, 4, 0, 0x10000, 0, NULL); + if (!menu_run(&config, &entry, loaded_image_path)) + break; + + /* run special entry like "reboot" */ + if (entry->call) { + entry->call(); + continue; + } + } + + /* export the selected boot entry to the system */ + efivar_set(L"LoaderEntrySelected", entry->file, FALSE); + + uefi_call_wrapper(BS->SetWatchdogTimer, 4, 5 * 60, 0x10000, 0, NULL); + err = image_start(image, &config, entry); + if (EFI_ERROR(err)) { + graphics_mode(FALSE); + Print(L"\nFailed to execute %s (%s): %r\n", entry->title, entry->loader, err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + goto out; + } + + menu = TRUE; + config.timeout_sec = 0; + } + err = EFI_SUCCESS; +out: + FreePool(loaded_image_path); + config_free(&config); + uefi_call_wrapper(root_dir->Close, 1, root_dir); + uefi_call_wrapper(BS->CloseProtocol, 4, image, &LoadedImageProtocol, image, NULL); + return err; +} diff --git a/src/grp-boot/systemd-boot/console.c b/src/grp-boot/systemd-boot/console.c new file mode 100644 index 0000000000..c436f8b476 --- /dev/null +++ b/src/grp-boot/systemd-boot/console.c @@ -0,0 +1,139 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2012-2013 Kay Sievers + * Copyright (C) 2012 Harald Hoyer + */ + +#include +#include + +#include "console.h" +#include "util.h" + +#define EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID \ + { 0xdd9e7534, 0x7762, 0x4698, { 0x8c, 0x14, 0xf5, 0x85, 0x17, 0xa6, 0x25, 0xaa } } + +struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL; + +typedef EFI_STATUS (EFIAPI *EFI_INPUT_RESET_EX)( + struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, + BOOLEAN ExtendedVerification +); + +typedef UINT8 EFI_KEY_TOGGLE_STATE; + +typedef struct { + UINT32 KeyShiftState; + EFI_KEY_TOGGLE_STATE KeyToggleState; +} EFI_KEY_STATE; + +typedef struct { + EFI_INPUT_KEY Key; + EFI_KEY_STATE KeyState; +} EFI_KEY_DATA; + +typedef EFI_STATUS (EFIAPI *EFI_INPUT_READ_KEY_EX)( + struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, + EFI_KEY_DATA *KeyData +); + +typedef EFI_STATUS (EFIAPI *EFI_SET_STATE)( + struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, + EFI_KEY_TOGGLE_STATE *KeyToggleState +); + +typedef EFI_STATUS (EFIAPI *EFI_KEY_NOTIFY_FUNCTION)( + EFI_KEY_DATA *KeyData +); + +typedef EFI_STATUS (EFIAPI *EFI_REGISTER_KEYSTROKE_NOTIFY)( + struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, + EFI_KEY_DATA KeyData, + EFI_KEY_NOTIFY_FUNCTION KeyNotificationFunction, + VOID **NotifyHandle +); + +typedef EFI_STATUS (EFIAPI *EFI_UNREGISTER_KEYSTROKE_NOTIFY)( + struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, + VOID *NotificationHandle +); + +typedef struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL { + EFI_INPUT_RESET_EX Reset; + EFI_INPUT_READ_KEY_EX ReadKeyStrokeEx; + EFI_EVENT WaitForKeyEx; + EFI_SET_STATE SetState; + EFI_REGISTER_KEYSTROKE_NOTIFY RegisterKeyNotify; + EFI_UNREGISTER_KEYSTROKE_NOTIFY UnregisterKeyNotify; +} EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL; + +EFI_STATUS console_key_read(UINT64 *key, BOOLEAN wait) { + EFI_GUID EfiSimpleTextInputExProtocolGuid = EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID; + static EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *TextInputEx; + static BOOLEAN checked; + UINTN index; + EFI_INPUT_KEY k; + EFI_STATUS err; + + if (!checked) { + err = LibLocateProtocol(&EfiSimpleTextInputExProtocolGuid, (VOID **)&TextInputEx); + if (EFI_ERROR(err)) + TextInputEx = NULL; + + checked = TRUE; + } + + /* wait until key is pressed */ + if (wait) { + if (TextInputEx) + uefi_call_wrapper(BS->WaitForEvent, 3, 1, &TextInputEx->WaitForKeyEx, &index); + else + uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index); + } + + if (TextInputEx) { + EFI_KEY_DATA keydata; + UINT64 keypress; + + err = uefi_call_wrapper(TextInputEx->ReadKeyStrokeEx, 2, TextInputEx, &keydata); + if (!EFI_ERROR(err)) { + UINT32 shift = 0; + + /* do not distinguish between left and right keys */ + if (keydata.KeyState.KeyShiftState & EFI_SHIFT_STATE_VALID) { + if (keydata.KeyState.KeyShiftState & (EFI_RIGHT_CONTROL_PRESSED|EFI_LEFT_CONTROL_PRESSED)) + shift |= EFI_CONTROL_PRESSED; + if (keydata.KeyState.KeyShiftState & (EFI_RIGHT_ALT_PRESSED|EFI_LEFT_ALT_PRESSED)) + shift |= EFI_ALT_PRESSED; + }; + + /* 32 bit modifier keys + 16 bit scan code + 16 bit unicode */ + keypress = KEYPRESS(shift, keydata.Key.ScanCode, keydata.Key.UnicodeChar); + if (keypress > 0) { + *key = keypress; + return 0; + } + } + } + + /* fallback for firmware which does not support SimpleTextInputExProtocol + * + * This is also called in case ReadKeyStrokeEx did not return a key, because + * some broken firmwares offer SimpleTextInputExProtocol, but never acually + * handle any key. */ + err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &k); + if (EFI_ERROR(err)) + return err; + + *key = KEYPRESS(0, k.ScanCode, k.UnicodeChar); + return 0; +} diff --git a/src/grp-boot/systemd-boot/console.h b/src/grp-boot/systemd-boot/console.h new file mode 100644 index 0000000000..3fe0ce5ec4 --- /dev/null +++ b/src/grp-boot/systemd-boot/console.h @@ -0,0 +1,32 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2012-2013 Kay Sievers + * Copyright (C) 2012 Harald Hoyer + */ + +#ifndef __SDBOOT_CONSOLE_H +#define __SDBOOT_CONSOLE_H + +#define EFI_SHIFT_STATE_VALID 0x80000000 +#define EFI_RIGHT_CONTROL_PRESSED 0x00000004 +#define EFI_LEFT_CONTROL_PRESSED 0x00000008 +#define EFI_RIGHT_ALT_PRESSED 0x00000010 +#define EFI_LEFT_ALT_PRESSED 0x00000020 + +#define EFI_CONTROL_PRESSED (EFI_RIGHT_CONTROL_PRESSED|EFI_LEFT_CONTROL_PRESSED) +#define EFI_ALT_PRESSED (EFI_RIGHT_ALT_PRESSED|EFI_LEFT_ALT_PRESSED) +#define KEYPRESS(keys, scan, uni) ((((UINT64)keys) << 32) | ((scan) << 16) | (uni)) +#define KEYCHAR(k) ((k) & 0xffff) +#define CHAR_CTRL(c) ((c) - 'a' + 1) + +EFI_STATUS console_key_read(UINT64 *key, BOOLEAN wait); +#endif diff --git a/src/grp-boot/systemd-boot/disk.c b/src/grp-boot/systemd-boot/disk.c new file mode 100644 index 0000000000..3e3b5b224a --- /dev/null +++ b/src/grp-boot/systemd-boot/disk.c @@ -0,0 +1,49 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2015 Kay Sievers + */ + +#include +#include + +#include "util.h" + +EFI_STATUS disk_get_part_uuid(EFI_HANDLE *handle, CHAR16 uuid[37]) { + EFI_DEVICE_PATH *device_path; + EFI_STATUS r = EFI_NOT_FOUND; + + /* export the device path this image is started from */ + device_path = DevicePathFromHandle(handle); + if (device_path) { + EFI_DEVICE_PATH *path, *paths; + + paths = UnpackDevicePath(device_path); + for (path = paths; !IsDevicePathEnd(path); path = NextDevicePathNode(path)) { + HARDDRIVE_DEVICE_PATH *drive; + + if (DevicePathType(path) != MEDIA_DEVICE_PATH) + continue; + if (DevicePathSubType(path) != MEDIA_HARDDRIVE_DP) + continue; + drive = (HARDDRIVE_DEVICE_PATH *)path; + if (drive->SignatureType != SIGNATURE_TYPE_GUID) + continue; + + GuidToString(uuid, (EFI_GUID *)&drive->Signature); + r = EFI_SUCCESS; + break; + } + FreePool(paths); + } + + return r; +} diff --git a/src/grp-boot/systemd-boot/disk.h b/src/grp-boot/systemd-boot/disk.h new file mode 100644 index 0000000000..af91a9c674 --- /dev/null +++ b/src/grp-boot/systemd-boot/disk.h @@ -0,0 +1,19 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2015 Kay Sievers + */ + +#ifndef __SDBOOT_DISK_H +#define __SDBOOT_DISK_H + +EFI_STATUS disk_get_part_uuid(EFI_HANDLE *handle, CHAR16 uuid[37]); +#endif diff --git a/src/grp-boot/systemd-boot/graphics.c b/src/grp-boot/systemd-boot/graphics.c new file mode 100644 index 0000000000..4854baf874 --- /dev/null +++ b/src/grp-boot/systemd-boot/graphics.c @@ -0,0 +1,88 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2012-2013 Kay Sievers + * Copyright (C) 2012 Harald Hoyer + * Copyright (C) 2013 Intel Corporation + * Authored by Joonas Lahtinen + */ + +#include +#include + +#include "graphics.h" +#include "util.h" + +EFI_STATUS graphics_mode(BOOLEAN on) { + #define EFI_CONSOLE_CONTROL_PROTOCOL_GUID \ + { 0xf42f7782, 0x12e, 0x4c12, { 0x99, 0x56, 0x49, 0xf9, 0x43, 0x4, 0xf7, 0x21 } }; + + struct _EFI_CONSOLE_CONTROL_PROTOCOL; + + typedef enum { + EfiConsoleControlScreenText, + EfiConsoleControlScreenGraphics, + EfiConsoleControlScreenMaxValue, + } EFI_CONSOLE_CONTROL_SCREEN_MODE; + + typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_GET_MODE)( + struct _EFI_CONSOLE_CONTROL_PROTOCOL *This, + EFI_CONSOLE_CONTROL_SCREEN_MODE *Mode, + BOOLEAN *UgaExists, + BOOLEAN *StdInLocked + ); + + typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE)( + struct _EFI_CONSOLE_CONTROL_PROTOCOL *This, + EFI_CONSOLE_CONTROL_SCREEN_MODE Mode + ); + + typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_LOCK_STD_IN)( + struct _EFI_CONSOLE_CONTROL_PROTOCOL *This, + CHAR16 *Password + ); + + typedef struct _EFI_CONSOLE_CONTROL_PROTOCOL { + EFI_CONSOLE_CONTROL_PROTOCOL_GET_MODE GetMode; + EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE SetMode; + EFI_CONSOLE_CONTROL_PROTOCOL_LOCK_STD_IN LockStdIn; + } EFI_CONSOLE_CONTROL_PROTOCOL; + + EFI_GUID ConsoleControlProtocolGuid = EFI_CONSOLE_CONTROL_PROTOCOL_GUID; + EFI_CONSOLE_CONTROL_PROTOCOL *ConsoleControl = NULL; + EFI_CONSOLE_CONTROL_SCREEN_MODE new; + EFI_CONSOLE_CONTROL_SCREEN_MODE current; + BOOLEAN uga_exists; + BOOLEAN stdin_locked; + EFI_STATUS err; + + err = LibLocateProtocol(&ConsoleControlProtocolGuid, (VOID **)&ConsoleControl); + if (EFI_ERROR(err)) + /* console control protocol is nonstandard and might not exist. */ + return err == EFI_NOT_FOUND ? EFI_SUCCESS : err; + + /* check current mode */ + err = uefi_call_wrapper(ConsoleControl->GetMode, 4, ConsoleControl, ¤t, &uga_exists, &stdin_locked); + if (EFI_ERROR(err)) + return err; + + /* do not touch the mode */ + new = on ? EfiConsoleControlScreenGraphics : EfiConsoleControlScreenText; + if (new == current) + return EFI_SUCCESS; + + err = uefi_call_wrapper(ConsoleControl->SetMode, 2, ConsoleControl, new); + + /* some firmware enables the cursor when switching modes */ + uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE); + + return err; +} diff --git a/src/grp-boot/systemd-boot/graphics.h b/src/grp-boot/systemd-boot/graphics.h new file mode 100644 index 0000000000..cf48e647e7 --- /dev/null +++ b/src/grp-boot/systemd-boot/graphics.h @@ -0,0 +1,22 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2012-2013 Kay Sievers + * Copyright (C) 2012 Harald Hoyer + * Copyright (C) 2013 Intel Corporation + * Authored by Joonas Lahtinen + */ + +#ifndef __SDBOOT_GRAPHICS_H +#define __SDBOOT_GRAPHICS_H + +EFI_STATUS graphics_mode(BOOLEAN on); +#endif diff --git a/src/grp-boot/systemd-boot/linux.c b/src/grp-boot/systemd-boot/linux.c new file mode 100644 index 0000000000..0dc99a6c53 --- /dev/null +++ b/src/grp-boot/systemd-boot/linux.c @@ -0,0 +1,128 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2015 Kay Sievers + */ + +#include +#include + +#include "linux.h" +#include "util.h" + +#define SETUP_MAGIC 0x53726448 /* "HdrS" */ +struct SetupHeader { + UINT8 boot_sector[0x01f1]; + UINT8 setup_secs; + UINT16 root_flags; + UINT32 sys_size; + UINT16 ram_size; + UINT16 video_mode; + UINT16 root_dev; + UINT16 signature; + UINT16 jump; + UINT32 header; + UINT16 version; + UINT16 su_switch; + UINT16 setup_seg; + UINT16 start_sys; + UINT16 kernel_ver; + UINT8 loader_id; + UINT8 load_flags; + UINT16 movesize; + UINT32 code32_start; + UINT32 ramdisk_start; + UINT32 ramdisk_len; + UINT32 bootsect_kludge; + UINT16 heap_end; + UINT8 ext_loader_ver; + UINT8 ext_loader_type; + UINT32 cmd_line_ptr; + UINT32 ramdisk_max; + UINT32 kernel_alignment; + UINT8 relocatable_kernel; + UINT8 min_alignment; + UINT16 xloadflags; + UINT32 cmdline_size; + UINT32 hardware_subarch; + UINT64 hardware_subarch_data; + UINT32 payload_offset; + UINT32 payload_length; + UINT64 setup_data; + UINT64 pref_address; + UINT32 init_size; + UINT32 handover_offset; +} __attribute__((packed)); + +#ifdef __x86_64__ +typedef VOID(*handover_f)(VOID *image, EFI_SYSTEM_TABLE *table, struct SetupHeader *setup); +static inline VOID linux_efi_handover(EFI_HANDLE image, struct SetupHeader *setup) { + handover_f handover; + + asm volatile ("cli"); + handover = (handover_f)((UINTN)setup->code32_start + 512 + setup->handover_offset); + handover(image, ST, setup); +} +#else +typedef VOID(*handover_f)(VOID *image, EFI_SYSTEM_TABLE *table, struct SetupHeader *setup) __attribute__((regparm(0))); +static inline VOID linux_efi_handover(EFI_HANDLE image, struct SetupHeader *setup) { + handover_f handover; + + handover = (handover_f)((UINTN)setup->code32_start + setup->handover_offset); + handover(image, ST, setup); +} +#endif + +EFI_STATUS linux_exec(EFI_HANDLE *image, + CHAR8 *cmdline, UINTN cmdline_len, + UINTN linux_addr, + UINTN initrd_addr, UINTN initrd_size) { + struct SetupHeader *image_setup; + struct SetupHeader *boot_setup; + EFI_PHYSICAL_ADDRESS addr; + EFI_STATUS err; + + image_setup = (struct SetupHeader *)(linux_addr); + if (image_setup->signature != 0xAA55 || image_setup->header != SETUP_MAGIC) + return EFI_LOAD_ERROR; + + if (image_setup->version < 0x20b || !image_setup->relocatable_kernel) + return EFI_LOAD_ERROR; + + addr = 0x3fffffff; + err = uefi_call_wrapper(BS->AllocatePages, 4, AllocateMaxAddress, EfiLoaderData, + EFI_SIZE_TO_PAGES(0x4000), &addr); + if (EFI_ERROR(err)) + return err; + boot_setup = (struct SetupHeader *)(UINTN)addr; + ZeroMem(boot_setup, 0x4000); + CopyMem(boot_setup, image_setup, sizeof(struct SetupHeader)); + boot_setup->loader_id = 0xff; + + boot_setup->code32_start = (UINT32)linux_addr + (image_setup->setup_secs+1) * 512; + + if (cmdline) { + addr = 0xA0000; + err = uefi_call_wrapper(BS->AllocatePages, 4, AllocateMaxAddress, EfiLoaderData, + EFI_SIZE_TO_PAGES(cmdline_len + 1), &addr); + if (EFI_ERROR(err)) + return err; + CopyMem((VOID *)(UINTN)addr, cmdline, cmdline_len); + ((CHAR8 *)addr)[cmdline_len] = 0; + boot_setup->cmd_line_ptr = (UINT32)addr; + } + + boot_setup->ramdisk_start = (UINT32)initrd_addr; + boot_setup->ramdisk_len = (UINT32)initrd_size; + + linux_efi_handover(image, boot_setup); + return EFI_LOAD_ERROR; +} diff --git a/src/grp-boot/systemd-boot/linux.h b/src/grp-boot/systemd-boot/linux.h new file mode 100644 index 0000000000..d9e6ed7955 --- /dev/null +++ b/src/grp-boot/systemd-boot/linux.h @@ -0,0 +1,22 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2015 Kay Sievers + */ + +#ifndef __SDBOOT_kernel_H +#define __SDBOOT_kernel_H + +EFI_STATUS linux_exec(EFI_HANDLE *image, + CHAR8 *cmdline, UINTN cmdline_size, + UINTN linux_addr, + UINTN initrd_addr, UINTN initrd_size); +#endif diff --git a/src/grp-boot/systemd-boot/measure.c b/src/grp-boot/systemd-boot/measure.c new file mode 100644 index 0000000000..7c016387c1 --- /dev/null +++ b/src/grp-boot/systemd-boot/measure.c @@ -0,0 +1,312 @@ +/* + * This program 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. + * + * This program 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. + * + */ + +#ifdef SD_BOOT_LOG_TPM + +#include +#include +#include "measure.h" + +#define EFI_TCG_PROTOCOL_GUID { 0xf541796d, 0xa62e, 0x4954, {0xa7, 0x75, 0x95, 0x84, 0xf6, 0x1b, 0x9c, 0xdd} } + +typedef struct _TCG_VERSION { + UINT8 Major; + UINT8 Minor; + UINT8 RevMajor; + UINT8 RevMinor; +} TCG_VERSION; + +typedef struct _TCG_BOOT_SERVICE_CAPABILITY { + UINT8 Size; + struct _TCG_VERSION StructureVersion; + struct _TCG_VERSION ProtocolSpecVersion; + UINT8 HashAlgorithmBitmap; + BOOLEAN TPMPresentFlag; + BOOLEAN TPMDeactivatedFlag; +} TCG_BOOT_SERVICE_CAPABILITY; + +typedef UINT32 TCG_ALGORITHM_ID; +#define TCG_ALG_SHA 0x00000004 // The SHA1 algorithm + +#define SHA1_DIGEST_SIZE 20 + +typedef struct _TCG_DIGEST { + UINT8 Digest[SHA1_DIGEST_SIZE]; +} TCG_DIGEST; + +#define EV_IPL 13 + +typedef struct _TCG_PCR_EVENT { + UINT32 PCRIndex; + UINT32 EventType; + struct _TCG_DIGEST digest; + UINT32 EventSize; + UINT8 Event[1]; +} TCG_PCR_EVENT; + +INTERFACE_DECL(_EFI_TCG); + +typedef EFI_STATUS(EFIAPI * EFI_TCG_STATUS_CHECK) (IN struct _EFI_TCG * This, + OUT struct _TCG_BOOT_SERVICE_CAPABILITY * ProtocolCapability, + OUT UINT32 * TCGFeatureFlags, + OUT EFI_PHYSICAL_ADDRESS * EventLogLocation, + OUT EFI_PHYSICAL_ADDRESS * EventLogLastEntry); + +typedef EFI_STATUS(EFIAPI * EFI_TCG_HASH_ALL) (IN struct _EFI_TCG * This, + IN UINT8 * HashData, + IN UINT64 HashDataLen, + IN TCG_ALGORITHM_ID AlgorithmId, + IN OUT UINT64 * HashedDataLen, IN OUT UINT8 ** HashedDataResult); + +typedef EFI_STATUS(EFIAPI * EFI_TCG_LOG_EVENT) (IN struct _EFI_TCG * This, + IN struct _TCG_PCR_EVENT * TCGLogData, + IN OUT UINT32 * EventNumber, IN UINT32 Flags); + +typedef EFI_STATUS(EFIAPI * EFI_TCG_PASS_THROUGH_TO_TPM) (IN struct _EFI_TCG * This, + IN UINT32 TpmInputParameterBlockSize, + IN UINT8 * TpmInputParameterBlock, + IN UINT32 TpmOutputParameterBlockSize, + IN UINT8 * TpmOutputParameterBlock); + +typedef EFI_STATUS(EFIAPI * EFI_TCG_HASH_LOG_EXTEND_EVENT) (IN struct _EFI_TCG * This, + IN EFI_PHYSICAL_ADDRESS HashData, + IN UINT64 HashDataLen, + IN TCG_ALGORITHM_ID AlgorithmId, + IN struct _TCG_PCR_EVENT * TCGLogData, + IN OUT UINT32 * EventNumber, + OUT EFI_PHYSICAL_ADDRESS * EventLogLastEntry); + +typedef struct _EFI_TCG { + EFI_TCG_STATUS_CHECK StatusCheck; + EFI_TCG_HASH_ALL HashAll; + EFI_TCG_LOG_EVENT LogEvent; + EFI_TCG_PASS_THROUGH_TO_TPM PassThroughToTPM; + EFI_TCG_HASH_LOG_EXTEND_EVENT HashLogExtendEvent; +} EFI_TCG; + +#define EFI_TCG2_PROTOCOL_GUID {0x607f766c, 0x7455, 0x42be, { 0x93, 0x0b, 0xe4, 0xd7, 0x6d, 0xb2, 0x72, 0x0f }} + +typedef struct tdEFI_TCG2_PROTOCOL EFI_TCG2_PROTOCOL; + +typedef struct tdEFI_TCG2_VERSION { + UINT8 Major; + UINT8 Minor; +} EFI_TCG2_VERSION; + +typedef UINT32 EFI_TCG2_EVENT_LOG_BITMAP; +typedef UINT32 EFI_TCG2_EVENT_LOG_FORMAT; +typedef UINT32 EFI_TCG2_EVENT_ALGORITHM_BITMAP; + +#define EFI_TCG2_EVENT_LOG_FORMAT_TCG_1_2 0x00000001 +#define EFI_TCG2_EVENT_LOG_FORMAT_TCG_2 0x00000002 + +typedef struct tdEFI_TCG2_BOOT_SERVICE_CAPABILITY { + UINT8 Size; + EFI_TCG2_VERSION StructureVersion; + EFI_TCG2_VERSION ProtocolVersion; + EFI_TCG2_EVENT_ALGORITHM_BITMAP HashAlgorithmBitmap; + EFI_TCG2_EVENT_LOG_BITMAP SupportedEventLogs; + BOOLEAN TPMPresentFlag; + UINT16 MaxCommandSize; + UINT16 MaxResponseSize; + UINT32 ManufacturerID; + UINT32 NumberOfPCRBanks; + EFI_TCG2_EVENT_ALGORITHM_BITMAP ActivePcrBanks; +} EFI_TCG2_BOOT_SERVICE_CAPABILITY; + +#define EFI_TCG2_EVENT_HEADER_VERSION 1 + +typedef struct { + UINT32 HeaderSize; + UINT16 HeaderVersion; + UINT32 PCRIndex; + UINT32 EventType; +} EFI_TCG2_EVENT_HEADER; + +typedef struct tdEFI_TCG2_EVENT { + UINT32 Size; + EFI_TCG2_EVENT_HEADER Header; + UINT8 Event[1]; +} EFI_TCG2_EVENT; + +typedef EFI_STATUS(EFIAPI * EFI_TCG2_GET_CAPABILITY) (IN EFI_TCG2_PROTOCOL * This, + IN OUT EFI_TCG2_BOOT_SERVICE_CAPABILITY * ProtocolCapability); + +typedef EFI_STATUS(EFIAPI * EFI_TCG2_GET_EVENT_LOG) (IN EFI_TCG2_PROTOCOL * This, + IN EFI_TCG2_EVENT_LOG_FORMAT EventLogFormat, + OUT EFI_PHYSICAL_ADDRESS * EventLogLocation, + OUT EFI_PHYSICAL_ADDRESS * EventLogLastEntry, + OUT BOOLEAN * EventLogTruncated); + +typedef EFI_STATUS(EFIAPI * EFI_TCG2_HASH_LOG_EXTEND_EVENT) (IN EFI_TCG2_PROTOCOL * This, + IN UINT64 Flags, + IN EFI_PHYSICAL_ADDRESS DataToHash, + IN UINT64 DataToHashLen, IN EFI_TCG2_EVENT * EfiTcgEvent); + +typedef EFI_STATUS(EFIAPI * EFI_TCG2_SUBMIT_COMMAND) (IN EFI_TCG2_PROTOCOL * This, + IN UINT32 InputParameterBlockSize, + IN UINT8 * InputParameterBlock, + IN UINT32 OutputParameterBlockSize, IN UINT8 * OutputParameterBlock); + +typedef EFI_STATUS(EFIAPI * EFI_TCG2_GET_ACTIVE_PCR_BANKS) (IN EFI_TCG2_PROTOCOL * This, OUT UINT32 * ActivePcrBanks); + +typedef EFI_STATUS(EFIAPI * EFI_TCG2_SET_ACTIVE_PCR_BANKS) (IN EFI_TCG2_PROTOCOL * This, IN UINT32 ActivePcrBanks); + +typedef EFI_STATUS(EFIAPI * EFI_TCG2_GET_RESULT_OF_SET_ACTIVE_PCR_BANKS) (IN EFI_TCG2_PROTOCOL * This, + OUT UINT32 * OperationPresent, OUT UINT32 * Response); + +typedef struct tdEFI_TCG2_PROTOCOL { + EFI_TCG2_GET_CAPABILITY GetCapability; + EFI_TCG2_GET_EVENT_LOG GetEventLog; + EFI_TCG2_HASH_LOG_EXTEND_EVENT HashLogExtendEvent; + EFI_TCG2_SUBMIT_COMMAND SubmitCommand; + EFI_TCG2_GET_ACTIVE_PCR_BANKS GetActivePcrBanks; + EFI_TCG2_SET_ACTIVE_PCR_BANKS SetActivePcrBanks; + EFI_TCG2_GET_RESULT_OF_SET_ACTIVE_PCR_BANKS GetResultOfSetActivePcrBanks; +} EFI_TCG2; + + +static EFI_STATUS tpm1_measure_to_pcr_and_event_log(const EFI_TCG *tcg, UINT32 pcrindex, const EFI_PHYSICAL_ADDRESS buffer, + UINTN buffer_size, const CHAR16 *description) { + EFI_STATUS status; + TCG_PCR_EVENT *tcg_event; + UINT32 event_number; + EFI_PHYSICAL_ADDRESS event_log_last; + UINTN desc_len; + + desc_len = (StrLen(description) + 1) * sizeof(CHAR16); + + tcg_event = AllocateZeroPool(desc_len + sizeof(TCG_PCR_EVENT)); + + if (tcg_event == NULL) + return EFI_OUT_OF_RESOURCES; + + tcg_event->EventSize = desc_len; + CopyMem((VOID *) & tcg_event->Event[0], (VOID *) description, desc_len); + + tcg_event->PCRIndex = pcrindex; + tcg_event->EventType = EV_IPL; + + event_number = 1; + status = uefi_call_wrapper(tcg->HashLogExtendEvent, 7, + tcg, buffer, buffer_size, TCG_ALG_SHA, tcg_event, &event_number, &event_log_last); + + if (EFI_ERROR(status)) + return status; + + uefi_call_wrapper(BS->FreePool, 1, tcg_event); + + return EFI_SUCCESS; +} + + +static EFI_STATUS tpm2_measure_to_pcr_and_event_log(const EFI_TCG2 *tcg, UINT32 pcrindex, const EFI_PHYSICAL_ADDRESS buffer, + UINT64 buffer_size, const CHAR16 *description) { + EFI_STATUS status; + EFI_TCG2_EVENT *tcg_event; + UINTN desc_len; + + desc_len = StrLen(description) * sizeof(CHAR16); + + tcg_event = AllocateZeroPool(sizeof(*tcg_event) - sizeof(tcg_event->Event) + desc_len + 1); + + if (tcg_event == NULL) + return EFI_OUT_OF_RESOURCES; + + tcg_event->Size = sizeof(EFI_TCG2_EVENT) - sizeof(tcg_event->Event) + desc_len + 1; + tcg_event->Header.HeaderSize = sizeof(EFI_TCG2_EVENT_HEADER); + tcg_event->Header.HeaderVersion = EFI_TCG2_EVENT_HEADER_VERSION; + tcg_event->Header.PCRIndex = pcrindex; + tcg_event->Header.EventType = EV_IPL; + + CopyMem((VOID *) tcg_event->Event, (VOID *) description, desc_len); + + status = uefi_call_wrapper(tcg->HashLogExtendEvent, 5, tcg, 0, buffer, buffer_size, tcg_event); + + uefi_call_wrapper(BS->FreePool, 1, tcg_event); + + if (EFI_ERROR(status)) + return status; + + return EFI_SUCCESS; +} + +static EFI_TCG * tcg1_interface_check(void) { + EFI_GUID tpm_guid = EFI_TCG_PROTOCOL_GUID; + EFI_STATUS status; + EFI_TCG *tcg; + TCG_BOOT_SERVICE_CAPABILITY capability; + UINT32 features; + EFI_PHYSICAL_ADDRESS event_log_location; + EFI_PHYSICAL_ADDRESS event_log_last_entry; + + status = LibLocateProtocol(&tpm_guid, (void **) &tcg); + + if (EFI_ERROR(status)) + return NULL; + + capability.Size = (UINT8) sizeof(capability); + status = uefi_call_wrapper(tcg->StatusCheck, 5, tcg, &capability, &features, &event_log_location, &event_log_last_entry); + + if (EFI_ERROR(status)) + return NULL; + + if (capability.TPMDeactivatedFlag) + return NULL; + + if (!capability.TPMPresentFlag) + return NULL; + + return tcg; +} + +static EFI_TCG2 * tcg2_interface_check(void) { + EFI_GUID tpm2_guid = EFI_TCG2_PROTOCOL_GUID; + EFI_STATUS status; + EFI_TCG2 *tcg; + EFI_TCG2_BOOT_SERVICE_CAPABILITY capability; + + status = LibLocateProtocol(&tpm2_guid, (void **) &tcg); + + if (EFI_ERROR(status)) + return NULL; + + capability.Size = (UINT8) sizeof(capability); + status = uefi_call_wrapper(tcg->GetCapability, 2, tcg, &capability); + + if (EFI_ERROR(status)) + return NULL; + + if (!capability.TPMPresentFlag) + return NULL; + + return tcg; +} + +EFI_STATUS tpm_log_event(UINT32 pcrindex, const EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const CHAR16 *description) { + EFI_TCG *tpm1; + EFI_TCG2 *tpm2; + + tpm2 = tcg2_interface_check(); + if (tpm2) + return tpm2_measure_to_pcr_and_event_log(tpm2, pcrindex, buffer, buffer_size, description); + + tpm1 = tcg1_interface_check(); + if (tpm1) + return tpm1_measure_to_pcr_and_event_log(tpm1, pcrindex, buffer, buffer_size, description); + + /* No active TPM found, so don't return an error */ + return EFI_SUCCESS; +} + +#endif diff --git a/src/grp-boot/systemd-boot/measure.h b/src/grp-boot/systemd-boot/measure.h new file mode 100644 index 0000000000..a2cfe817d0 --- /dev/null +++ b/src/grp-boot/systemd-boot/measure.h @@ -0,0 +1,21 @@ +/* + * This program 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. + * + * This program 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. + * + */ +#ifndef __SDBOOT_MEASURE_H +#define __SDBOOT_MEASURE_H + +#ifndef SD_TPM_PCR +#define SD_TPM_PCR 8 +#endif + +EFI_STATUS tpm_log_event(UINT32 pcrindex, const EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const CHAR16 *description); +#endif diff --git a/src/grp-boot/systemd-boot/pefile.c b/src/grp-boot/systemd-boot/pefile.c new file mode 100644 index 0000000000..77fff77b69 --- /dev/null +++ b/src/grp-boot/systemd-boot/pefile.c @@ -0,0 +1,170 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2015 Kay Sievers + */ + +#include +#include + +#include "pefile.h" +#include "util.h" + +struct DosFileHeader { + UINT8 Magic[2]; + UINT16 LastSize; + UINT16 nBlocks; + UINT16 nReloc; + UINT16 HdrSize; + UINT16 MinAlloc; + UINT16 MaxAlloc; + UINT16 ss; + UINT16 sp; + UINT16 Checksum; + UINT16 ip; + UINT16 cs; + UINT16 RelocPos; + UINT16 nOverlay; + UINT16 reserved[4]; + UINT16 OEMId; + UINT16 OEMInfo; + UINT16 reserved2[10]; + UINT32 ExeHeader; +} __attribute__((packed)); + +#define PE_HEADER_MACHINE_I386 0x014c +#define PE_HEADER_MACHINE_X64 0x8664 +struct PeFileHeader { + UINT16 Machine; + UINT16 NumberOfSections; + UINT32 TimeDateStamp; + UINT32 PointerToSymbolTable; + UINT32 NumberOfSymbols; + UINT16 SizeOfOptionalHeader; + UINT16 Characteristics; +} __attribute__((packed)); + +struct PeSectionHeader { + UINT8 Name[8]; + UINT32 VirtualSize; + UINT32 VirtualAddress; + UINT32 SizeOfRawData; + UINT32 PointerToRawData; + UINT32 PointerToRelocations; + UINT32 PointerToLinenumbers; + UINT16 NumberOfRelocations; + UINT16 NumberOfLinenumbers; + UINT32 Characteristics; +} __attribute__((packed)); + + +EFI_STATUS pefile_locate_sections(EFI_FILE *dir, CHAR16 *path, CHAR8 **sections, UINTN *addrs, UINTN *offsets, UINTN *sizes) { + EFI_FILE_HANDLE handle; + struct DosFileHeader dos; + uint8_t magic[4]; + struct PeFileHeader pe; + UINTN len; + UINTN i; + EFI_STATUS err; + + err = uefi_call_wrapper(dir->Open, 5, dir, &handle, path, EFI_FILE_MODE_READ, 0ULL); + if (EFI_ERROR(err)) + return err; + + /* MS-DOS stub */ + len = sizeof(dos); + err = uefi_call_wrapper(handle->Read, 3, handle, &len, &dos); + if (EFI_ERROR(err)) + goto out; + if (len != sizeof(dos)) { + err = EFI_LOAD_ERROR; + goto out; + } + + if (CompareMem(dos.Magic, "MZ", 2) != 0) { + err = EFI_LOAD_ERROR; + goto out; + } + + err = uefi_call_wrapper(handle->SetPosition, 2, handle, dos.ExeHeader); + if (EFI_ERROR(err)) + goto out; + + /* PE header */ + len = sizeof(magic); + err = uefi_call_wrapper(handle->Read, 3, handle, &len, &magic); + if (EFI_ERROR(err)) + goto out; + if (len != sizeof(magic)) { + err = EFI_LOAD_ERROR; + goto out; + } + + if (CompareMem(magic, "PE\0\0", 2) != 0) { + err = EFI_LOAD_ERROR; + goto out; + } + + len = sizeof(pe); + err = uefi_call_wrapper(handle->Read, 3, handle, &len, &pe); + if (EFI_ERROR(err)) + goto out; + if (len != sizeof(pe)) { + err = EFI_LOAD_ERROR; + goto out; + } + + /* PE32+ Subsystem type */ + if (pe.Machine != PE_HEADER_MACHINE_X64 && + pe.Machine != PE_HEADER_MACHINE_I386) { + err = EFI_LOAD_ERROR; + goto out; + } + + if (pe.NumberOfSections > 96) { + err = EFI_LOAD_ERROR; + goto out; + } + + /* the sections start directly after the headers */ + err = uefi_call_wrapper(handle->SetPosition, 2, handle, dos.ExeHeader + sizeof(magic) + sizeof(pe) + pe.SizeOfOptionalHeader); + if (EFI_ERROR(err)) + goto out; + + for (i = 0; i < pe.NumberOfSections; i++) { + struct PeSectionHeader sect; + UINTN j; + + len = sizeof(sect); + err = uefi_call_wrapper(handle->Read, 3, handle, &len, §); + if (EFI_ERROR(err)) + goto out; + if (len != sizeof(sect)) { + err = EFI_LOAD_ERROR; + goto out; + } + for (j = 0; sections[j]; j++) { + if (CompareMem(sect.Name, sections[j], strlena(sections[j])) != 0) + continue; + + if (addrs) + addrs[j] = (UINTN)sect.VirtualAddress; + if (offsets) + offsets[j] = (UINTN)sect.PointerToRawData; + if (sizes) + sizes[j] = (UINTN)sect.VirtualSize; + } + } + +out: + uefi_call_wrapper(handle->Close, 1, handle); + return err; +} diff --git a/src/grp-boot/systemd-boot/pefile.h b/src/grp-boot/systemd-boot/pefile.h new file mode 100644 index 0000000000..2e445ede17 --- /dev/null +++ b/src/grp-boot/systemd-boot/pefile.h @@ -0,0 +1,20 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2015 Kay Sievers + */ + +#ifndef __SDBOOT_PEFILE_H +#define __SDBOOT_PEFILE_H + +EFI_STATUS pefile_locate_sections(EFI_FILE *dir, CHAR16 *path, + CHAR8 **sections, UINTN *addrs, UINTN *offsets, UINTN *sizes); +#endif diff --git a/src/grp-boot/systemd-boot/splash.c b/src/grp-boot/systemd-boot/splash.c new file mode 100644 index 0000000000..c0ef7f64fe --- /dev/null +++ b/src/grp-boot/systemd-boot/splash.c @@ -0,0 +1,321 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2012-2013 Kay Sievers + * Copyright (C) 2012 Harald Hoyer + */ + +#include +#include + +#include "graphics.h" +#include "splash.h" +#include "util.h" + +struct bmp_file { + CHAR8 signature[2]; + UINT32 size; + UINT16 reserved[2]; + UINT32 offset; +} __attribute__((packed)); + +/* we require at least BITMAPINFOHEADER, later versions are + accepted, but their features ignored */ +struct bmp_dib { + UINT32 size; + UINT32 x; + UINT32 y; + UINT16 planes; + UINT16 depth; + UINT32 compression; + UINT32 image_size; + INT32 x_pixel_meter; + INT32 y_pixel_meter; + UINT32 colors_used; + UINT32 colors_important; +} __attribute__((packed)); + +struct bmp_map { + UINT8 blue; + UINT8 green; + UINT8 red; + UINT8 reserved; +} __attribute__((packed)); + +EFI_STATUS bmp_parse_header(UINT8 *bmp, UINTN size, struct bmp_dib **ret_dib, + struct bmp_map **ret_map, UINT8 **pixmap) { + struct bmp_file *file; + struct bmp_dib *dib; + struct bmp_map *map; + UINTN row_size; + + if (size < sizeof(struct bmp_file) + sizeof(struct bmp_dib)) + return EFI_INVALID_PARAMETER; + + /* check file header */ + file = (struct bmp_file *)bmp; + if (file->signature[0] != 'B' || file->signature[1] != 'M') + return EFI_INVALID_PARAMETER; + if (file->size != size) + return EFI_INVALID_PARAMETER; + if (file->size < file->offset) + return EFI_INVALID_PARAMETER; + + /* check device-independent bitmap */ + dib = (struct bmp_dib *)(bmp + sizeof(struct bmp_file)); + if (dib->size < sizeof(struct bmp_dib)) + return EFI_UNSUPPORTED; + + switch (dib->depth) { + case 1: + case 4: + case 8: + case 24: + if (dib->compression != 0) + return EFI_UNSUPPORTED; + + break; + + case 16: + case 32: + if (dib->compression != 0 && dib->compression != 3) + return EFI_UNSUPPORTED; + + break; + + default: + return EFI_UNSUPPORTED; + } + + row_size = ((UINTN) dib->depth * dib->x + 31) / 32 * 4; + if (file->size - file->offset < dib->y * row_size) + return EFI_INVALID_PARAMETER; + if (row_size * dib->y > 64 * 1024 * 1024) + return EFI_INVALID_PARAMETER; + + /* check color table */ + map = (struct bmp_map *)(bmp + sizeof(struct bmp_file) + dib->size); + if (file->offset < sizeof(struct bmp_file) + dib->size) + return EFI_INVALID_PARAMETER; + + if (file->offset > sizeof(struct bmp_file) + dib->size) { + UINT32 map_count; + UINTN map_size; + + if (dib->colors_used) + map_count = dib->colors_used; + else { + switch (dib->depth) { + case 1: + case 4: + case 8: + map_count = 1 << dib->depth; + break; + + default: + map_count = 0; + break; + } + } + + map_size = file->offset - (sizeof(struct bmp_file) + dib->size); + if (map_size != sizeof(struct bmp_map) * map_count) + return EFI_INVALID_PARAMETER; + } + + *ret_map = map; + *ret_dib = dib; + *pixmap = bmp + file->offset; + + return EFI_SUCCESS; +} + +static VOID pixel_blend(UINT32 *dst, const UINT32 source) { + UINT32 alpha, src, src_rb, src_g, dst_rb, dst_g, rb, g; + + alpha = (source & 0xff); + + /* convert src from RGBA to XRGB */ + src = source >> 8; + + /* decompose into RB and G components */ + src_rb = (src & 0xff00ff); + src_g = (src & 0x00ff00); + + dst_rb = (*dst & 0xff00ff); + dst_g = (*dst & 0x00ff00); + + /* blend */ + rb = ((((src_rb - dst_rb) * alpha + 0x800080) >> 8) + dst_rb) & 0xff00ff; + g = ((((src_g - dst_g) * alpha + 0x008000) >> 8) + dst_g) & 0x00ff00; + + *dst = (rb | g); +} + +EFI_STATUS bmp_to_blt(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *buf, + struct bmp_dib *dib, struct bmp_map *map, + UINT8 *pixmap) { + UINT8 *in; + UINTN y; + + /* transform and copy pixels */ + in = pixmap; + for (y = 0; y < dib->y; y++) { + EFI_GRAPHICS_OUTPUT_BLT_PIXEL *out; + UINTN row_size; + UINTN x; + + out = &buf[(dib->y - y - 1) * dib->x]; + for (x = 0; x < dib->x; x++, in++, out++) { + switch (dib->depth) { + case 1: { + UINTN i; + + for (i = 0; i < 8 && x < dib->x; i++) { + out->Red = map[((*in) >> (7 - i)) & 1].red; + out->Green = map[((*in) >> (7 - i)) & 1].green; + out->Blue = map[((*in) >> (7 - i)) & 1].blue; + out++; + x++; + } + out--; + x--; + break; + } + + case 4: { + UINTN i; + + i = (*in) >> 4; + out->Red = map[i].red; + out->Green = map[i].green; + out->Blue = map[i].blue; + if (x < (dib->x - 1)) { + out++; + x++; + i = (*in) & 0x0f; + out->Red = map[i].red; + out->Green = map[i].green; + out->Blue = map[i].blue; + } + break; + } + + case 8: + out->Red = map[*in].red; + out->Green = map[*in].green; + out->Blue = map[*in].blue; + break; + + case 16: { + UINT16 i = *(UINT16 *) in; + + out->Red = (i & 0x7c00) >> 7; + out->Green = (i & 0x3e0) >> 2; + out->Blue = (i & 0x1f) << 3; + in += 1; + break; + } + + case 24: + out->Red = in[2]; + out->Green = in[1]; + out->Blue = in[0]; + in += 2; + break; + + case 32: { + UINT32 i = *(UINT32 *) in; + + pixel_blend((UINT32 *)out, i); + + in += 3; + break; + } + } + } + + /* add row padding; new lines always start at 32 bit boundary */ + row_size = in - pixmap; + in += ((row_size + 3) & ~3) - row_size; + } + + return EFI_SUCCESS; +} + +EFI_STATUS graphics_splash(UINT8 *content, UINTN len, const EFI_GRAPHICS_OUTPUT_BLT_PIXEL *background) { + EFI_GRAPHICS_OUTPUT_BLT_PIXEL pixel = {}; + EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; + EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput = NULL; + struct bmp_dib *dib; + struct bmp_map *map; + UINT8 *pixmap; + UINT64 blt_size; + VOID *blt = NULL; + UINTN x_pos = 0; + UINTN y_pos = 0; + EFI_STATUS err; + + if (!background) { + if (StriCmp(L"Apple", ST->FirmwareVendor) == 0) { + pixel.Red = 0xc0; + pixel.Green = 0xc0; + pixel.Blue = 0xc0; + } + background = &pixel; + } + + err = LibLocateProtocol(&GraphicsOutputProtocolGuid, (VOID **)&GraphicsOutput); + if (EFI_ERROR(err)) + return err; + + err = bmp_parse_header(content, len, &dib, &map, &pixmap); + if (EFI_ERROR(err)) + goto err; + + if (dib->x < GraphicsOutput->Mode->Info->HorizontalResolution) + x_pos = (GraphicsOutput->Mode->Info->HorizontalResolution - dib->x) / 2; + if (dib->y < GraphicsOutput->Mode->Info->VerticalResolution) + y_pos = (GraphicsOutput->Mode->Info->VerticalResolution - dib->y) / 2; + + uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput, + (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)background, + EfiBltVideoFill, 0, 0, 0, 0, + GraphicsOutput->Mode->Info->HorizontalResolution, + GraphicsOutput->Mode->Info->VerticalResolution, 0); + + /* EFI buffer */ + blt_size = dib->x * dib->y * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL); + blt = AllocatePool(blt_size); + if (!blt) + return EFI_OUT_OF_RESOURCES; + + err = uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput, + blt, EfiBltVideoToBltBuffer, x_pos, y_pos, 0, 0, + dib->x, dib->y, 0); + if (EFI_ERROR(err)) + goto err; + + err = bmp_to_blt(blt, dib, map, pixmap); + if (EFI_ERROR(err)) + goto err; + + err = graphics_mode(TRUE); + if (EFI_ERROR(err)) + goto err; + + err = uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput, + blt, EfiBltBufferToVideo, 0, 0, x_pos, y_pos, + dib->x, dib->y, 0); +err: + FreePool(blt); + return err; +} diff --git a/src/grp-boot/systemd-boot/splash.h b/src/grp-boot/systemd-boot/splash.h new file mode 100644 index 0000000000..09b543fb47 --- /dev/null +++ b/src/grp-boot/systemd-boot/splash.h @@ -0,0 +1,20 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2012-2013 Kay Sievers + * Copyright (C) 2012 Harald Hoyer + */ + +#ifndef __SDBOOT_SPLASH_H +#define __SDBOOT_SPLASH_H + +EFI_STATUS graphics_splash(UINT8 *content, UINTN len, const EFI_GRAPHICS_OUTPUT_BLT_PIXEL *background); +#endif diff --git a/src/grp-boot/systemd-boot/stub.c b/src/grp-boot/systemd-boot/stub.c new file mode 100644 index 0000000000..1e250f34f4 --- /dev/null +++ b/src/grp-boot/systemd-boot/stub.c @@ -0,0 +1,130 @@ +/* This program 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. + * + * This program 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. + * + * Copyright (C) 2015 Kay Sievers + */ + +#include +#include + +#include "disk.h" +#include "graphics.h" +#include "linux.h" +#include "pefile.h" +#include "splash.h" +#include "util.h" +#include "measure.h" + +/* magic string to find in the binary image */ +static const char __attribute__((used)) magic[] = "#### LoaderInfo: systemd-stub " VERSION " ####"; + +static const EFI_GUID global_guid = EFI_GLOBAL_VARIABLE; + +EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { + EFI_LOADED_IMAGE *loaded_image; + EFI_FILE *root_dir; + CHAR16 *loaded_image_path; + CHAR8 *b; + UINTN size; + BOOLEAN secure = FALSE; + CHAR8 *sections[] = { + (UINT8 *)".cmdline", + (UINT8 *)".linux", + (UINT8 *)".initrd", + (UINT8 *)".splash", + NULL + }; + UINTN addrs[ELEMENTSOF(sections)-1] = {}; + UINTN offs[ELEMENTSOF(sections)-1] = {}; + UINTN szs[ELEMENTSOF(sections)-1] = {}; + CHAR8 *cmdline = NULL; + UINTN cmdline_len; + CHAR16 uuid[37]; + EFI_STATUS err; + + InitializeLib(image, sys_table); + + err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, (VOID **)&loaded_image, + image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (EFI_ERROR(err)) { + Print(L"Error getting a LoadedImageProtocol handle: %r ", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return err; + } + + root_dir = LibOpenRoot(loaded_image->DeviceHandle); + if (!root_dir) { + Print(L"Unable to open root directory: %r ", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return EFI_LOAD_ERROR; + } + + loaded_image_path = DevicePathToStr(loaded_image->FilePath); + + if (efivar_get_raw(&global_guid, L"SecureBoot", &b, &size) == EFI_SUCCESS) { + if (*b > 0) + secure = TRUE; + FreePool(b); + } + + err = pefile_locate_sections(root_dir, loaded_image_path, sections, addrs, offs, szs); + if (EFI_ERROR(err)) { + Print(L"Unable to locate embedded .linux section: %r ", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return err; + } + + if (szs[0] > 0) + cmdline = (CHAR8 *)(loaded_image->ImageBase + addrs[0]); + + cmdline_len = szs[0]; + + /* if we are not in secure boot mode, accept a custom command line and replace the built-in one */ + if (!secure && loaded_image->LoadOptionsSize > 0) { + CHAR16 *options; + CHAR8 *line; + UINTN i; + + options = (CHAR16 *)loaded_image->LoadOptions; + cmdline_len = (loaded_image->LoadOptionsSize / sizeof(CHAR16)) * sizeof(CHAR8); + line = AllocatePool(cmdline_len); + for (i = 0; i < cmdline_len; i++) + line[i] = options[i]; + cmdline = line; + +#ifdef SD_BOOT_LOG_TPM + /* Try to log any options to the TPM, escpecially manually edited options */ + err = tpm_log_event(SD_TPM_PCR, + (EFI_PHYSICAL_ADDRESS) loaded_image->LoadOptions, + loaded_image->LoadOptionsSize, loaded_image->LoadOptions); + if (EFI_ERROR(err)) { + Print(L"Unable to add image options measurement: %r", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return err; + } +#endif + } + + /* export the device path this image is started from */ + if (disk_get_part_uuid(loaded_image->DeviceHandle, uuid) == EFI_SUCCESS) + efivar_set(L"LoaderDevicePartUUID", uuid, FALSE); + + if (szs[3] > 0) + graphics_splash((UINT8 *)((UINTN)loaded_image->ImageBase + addrs[3]), szs[3], NULL); + + err = linux_exec(image, cmdline, cmdline_len, + (UINTN)loaded_image->ImageBase + addrs[1], + (UINTN)loaded_image->ImageBase + addrs[2], szs[2]); + + graphics_mode(FALSE); + Print(L"Execution of embedded linux image failed: %r\n", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return err; +} diff --git a/src/grp-boot/systemd-boot/test-efi-create-disk.sh b/src/grp-boot/systemd-boot/test-efi-create-disk.sh new file mode 100755 index 0000000000..56dd09abd7 --- /dev/null +++ b/src/grp-boot/systemd-boot/test-efi-create-disk.sh @@ -0,0 +1,42 @@ +#!/bin/bash -e + +# create GPT table with EFI System Partition +rm -f test-efi-disk.img +dd if=/dev/null of=test-efi-disk.img bs=1M seek=512 count=1 +parted --script test-efi-disk.img "mklabel gpt" "mkpart ESP fat32 1MiB 511MiB" "set 1 boot on" + +# create FAT32 file system +LOOP=$(losetup --show -f -P test-efi-disk.img) +mkfs.vfat -F32 ${LOOP}p1 +mkdir -p mnt +mount ${LOOP}p1 mnt + +mkdir -p mnt/EFI/{Boot,systemd} +cp systemd-bootx64.efi mnt/EFI/Boot/bootx64.efi + +[ -e /boot/shellx64.efi ] && cp /boot/shellx64.efi mnt/ + +mkdir mnt/EFI/Linux +echo -n "foo=yes bar=no root=/dev/fakeroot debug rd.break=initqueue" > mnt/cmdline.txt +objcopy \ + --add-section .osrel=/etc/os-release --change-section-vma .osrel=0x20000 \ + --add-section .cmdline=mnt/cmdline.txt --change-section-vma .cmdline=0x30000 \ + --add-section .splash=test/splash.bmp --change-section-vma .splash=0x40000 \ + --add-section .linux=/boot/$(cat /etc/machine-id)/$(uname -r)/linux --change-section-vma .linux=0x2000000 \ + --add-section .initrd=/boot/$(cat /etc/machine-id)/$(uname -r)/initrd --change-section-vma .initrd=0x3000000 \ + linuxx64.efi.stub mnt/EFI/Linux/linux-test.efi + +# install entries +mkdir -p mnt/loader/entries +echo -e "timeout 3\n" > mnt/loader/loader.conf +echo -e "title Test\nefi /test\n" > mnt/loader/entries/test.conf +echo -e "title Test2\nlinux /test2\noptions option=yes word number=1000 more\n" > mnt/loader/entries/test2.conf +echo -e "title Test3\nlinux /test3\n" > mnt/loader/entries/test3.conf +echo -e "title Test4\nlinux /test4\n" > mnt/loader/entries/test4.conf +echo -e "title Test5\nefi /test5\n" > mnt/loader/entries/test5.conf +echo -e "title Test6\nlinux /test6\n" > mnt/loader/entries/test6.conf + +sync +umount mnt +rmdir mnt +losetup -d $LOOP diff --git a/src/grp-boot/systemd-boot/util.c b/src/grp-boot/systemd-boot/util.c new file mode 100644 index 0000000000..98c5be74ce --- /dev/null +++ b/src/grp-boot/systemd-boot/util.c @@ -0,0 +1,345 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2012-2013 Kay Sievers + * Copyright (C) 2012 Harald Hoyer + */ + +#include +#include + +#include "util.h" + +/* + * Allocated random UUID, intended to be shared across tools that implement + * the (ESP)\loader\entries\-.conf convention and the + * associated EFI variables. + */ +static const EFI_GUID loader_guid = { 0x4a67b082, 0x0a4c, 0x41cf, {0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f} }; + +#ifdef __x86_64__ +UINT64 ticks_read(VOID) { + UINT64 a, d; + __asm__ volatile ("rdtsc" : "=a" (a), "=d" (d)); + return (d << 32) | a; +} +#elif defined(__i386__) +UINT64 ticks_read(VOID) { + UINT64 val; + __asm__ volatile ("rdtsc" : "=A" (val)); + return val; +} +#else +UINT64 ticks_read(VOID) { + UINT64 val = 1; + return val; +} +#endif + +/* count TSC ticks during a millisecond delay */ +UINT64 ticks_freq(VOID) { + UINT64 ticks_start, ticks_end; + + ticks_start = ticks_read(); + uefi_call_wrapper(BS->Stall, 1, 1000); + ticks_end = ticks_read(); + + return (ticks_end - ticks_start) * 1000; +} + +UINT64 time_usec(VOID) { + UINT64 ticks; + static UINT64 freq; + + ticks = ticks_read(); + if (ticks == 0) + return 0; + + if (freq == 0) { + freq = ticks_freq(); + if (freq == 0) + return 0; + } + + return 1000 * 1000 * ticks / freq; +} + +EFI_STATUS parse_boolean(CHAR8 *v, BOOLEAN *b) { + if (strcmpa(v, (CHAR8 *)"1") == 0 || + strcmpa(v, (CHAR8 *)"yes") == 0 || + strcmpa(v, (CHAR8 *)"y") == 0 || + strcmpa(v, (CHAR8 *)"true") == 0) { + *b = TRUE; + return EFI_SUCCESS; + } + + if (strcmpa(v, (CHAR8 *)"0") == 0 || + strcmpa(v, (CHAR8 *)"no") == 0 || + strcmpa(v, (CHAR8 *)"n") == 0 || + strcmpa(v, (CHAR8 *)"false") == 0) { + *b = FALSE; + return EFI_SUCCESS; + } + + return EFI_INVALID_PARAMETER; +} + +EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 *buf, UINTN size, BOOLEAN persistent) { + UINT32 flags; + + flags = EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS; + if (persistent) + flags |= EFI_VARIABLE_NON_VOLATILE; + + return uefi_call_wrapper(RT->SetVariable, 5, name, (EFI_GUID *)vendor, flags, size, buf); +} + +EFI_STATUS efivar_set(CHAR16 *name, CHAR16 *value, BOOLEAN persistent) { + return efivar_set_raw(&loader_guid, name, (CHAR8 *)value, value ? (StrLen(value)+1) * sizeof(CHAR16) : 0, persistent); +} + +EFI_STATUS efivar_set_int(CHAR16 *name, UINTN i, BOOLEAN persistent) { + CHAR16 str[32]; + + SPrint(str, 32, L"%d", i); + return efivar_set(name, str, persistent); +} + +EFI_STATUS efivar_get(CHAR16 *name, CHAR16 **value) { + CHAR8 *buf; + CHAR16 *val; + UINTN size; + EFI_STATUS err; + + err = efivar_get_raw(&loader_guid, name, &buf, &size); + if (EFI_ERROR(err)) + return err; + + val = StrDuplicate((CHAR16 *)buf); + if (!val) { + FreePool(buf); + return EFI_OUT_OF_RESOURCES; + } + + *value = val; + return EFI_SUCCESS; +} + +EFI_STATUS efivar_get_int(CHAR16 *name, UINTN *i) { + CHAR16 *val; + EFI_STATUS err; + + err = efivar_get(name, &val); + if (!EFI_ERROR(err)) { + *i = Atoi(val); + FreePool(val); + } + return err; +} + +EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 **buffer, UINTN *size) { + CHAR8 *buf; + UINTN l; + EFI_STATUS err; + + l = sizeof(CHAR16 *) * EFI_MAXIMUM_VARIABLE_SIZE; + buf = AllocatePool(l); + if (!buf) + return EFI_OUT_OF_RESOURCES; + + err = uefi_call_wrapper(RT->GetVariable, 5, name, (EFI_GUID *)vendor, NULL, &l, buf); + if (!EFI_ERROR(err)) { + *buffer = buf; + if (size) + *size = l; + } else + FreePool(buf); + return err; + +} + +VOID efivar_set_time_usec(CHAR16 *name, UINT64 usec) { + CHAR16 str[32]; + + if (usec == 0) + usec = time_usec(); + if (usec == 0) + return; + + SPrint(str, 32, L"%ld", usec); + efivar_set(name, str, FALSE); +} + +static INTN utf8_to_16(CHAR8 *stra, CHAR16 *c) { + CHAR16 unichar; + UINTN len; + UINTN i; + + if (stra[0] < 0x80) + len = 1; + else if ((stra[0] & 0xe0) == 0xc0) + len = 2; + else if ((stra[0] & 0xf0) == 0xe0) + len = 3; + else if ((stra[0] & 0xf8) == 0xf0) + len = 4; + else if ((stra[0] & 0xfc) == 0xf8) + len = 5; + else if ((stra[0] & 0xfe) == 0xfc) + len = 6; + else + return -1; + + switch (len) { + case 1: + unichar = stra[0]; + break; + case 2: + unichar = stra[0] & 0x1f; + break; + case 3: + unichar = stra[0] & 0x0f; + break; + case 4: + unichar = stra[0] & 0x07; + break; + case 5: + unichar = stra[0] & 0x03; + break; + case 6: + unichar = stra[0] & 0x01; + break; + } + + for (i = 1; i < len; i++) { + if ((stra[i] & 0xc0) != 0x80) + return -1; + unichar <<= 6; + unichar |= stra[i] & 0x3f; + } + + *c = unichar; + return len; +} + +CHAR16 *stra_to_str(CHAR8 *stra) { + UINTN strlen; + UINTN len; + UINTN i; + CHAR16 *str; + + len = strlena(stra); + str = AllocatePool((len + 1) * sizeof(CHAR16)); + + strlen = 0; + i = 0; + while (i < len) { + INTN utf8len; + + utf8len = utf8_to_16(stra + i, str + strlen); + if (utf8len <= 0) { + /* invalid utf8 sequence, skip the garbage */ + i++; + continue; + } + + strlen++; + i += utf8len; + } + str[strlen] = '\0'; + return str; +} + +CHAR16 *stra_to_path(CHAR8 *stra) { + CHAR16 *str; + UINTN strlen; + UINTN len; + UINTN i; + + len = strlena(stra); + str = AllocatePool((len + 2) * sizeof(CHAR16)); + + str[0] = '\\'; + strlen = 1; + i = 0; + while (i < len) { + INTN utf8len; + + utf8len = utf8_to_16(stra + i, str + strlen); + if (utf8len <= 0) { + /* invalid utf8 sequence, skip the garbage */ + i++; + continue; + } + + if (str[strlen] == '/') + str[strlen] = '\\'; + if (str[strlen] == '\\' && str[strlen-1] == '\\') { + /* skip double slashes */ + i += utf8len; + continue; + } + + strlen++; + i += utf8len; + } + str[strlen] = '\0'; + return str; +} + +CHAR8 *strchra(CHAR8 *s, CHAR8 c) { + do { + if (*s == c) + return s; + } while (*s++); + return NULL; +} + +INTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, UINTN off, UINTN size, CHAR8 **content) { + EFI_FILE_HANDLE handle; + CHAR8 *buf; + UINTN buflen; + EFI_STATUS err; + UINTN len; + + err = uefi_call_wrapper(dir->Open, 5, dir, &handle, name, EFI_FILE_MODE_READ, 0ULL); + if (EFI_ERROR(err)) + return err; + + if (size == 0) { + EFI_FILE_INFO *info; + + info = LibFileInfo(handle); + buflen = info->FileSize+1; + FreePool(info); + } else + buflen = size; + + if (off > 0) { + err = uefi_call_wrapper(handle->SetPosition, 2, handle, off); + if (EFI_ERROR(err)) + return err; + } + + buf = AllocatePool(buflen); + err = uefi_call_wrapper(handle->Read, 3, handle, &buflen, buf); + if (!EFI_ERROR(err)) { + buf[buflen] = '\0'; + *content = buf; + len = buflen; + } else { + len = err; + FreePool(buf); + } + + uefi_call_wrapper(handle->Close, 1, handle); + return len; +} diff --git a/src/grp-boot/systemd-boot/util.h b/src/grp-boot/systemd-boot/util.h new file mode 100644 index 0000000000..e673cdf9a0 --- /dev/null +++ b/src/grp-boot/systemd-boot/util.h @@ -0,0 +1,48 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2012-2013 Kay Sievers + * Copyright (C) 2012 Harald Hoyer + */ + +#ifndef __SDBOOT_UTIL_H +#define __SDBOOT_UTIL_H + +#include +#include + +#define ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0])) + +static inline const CHAR16 *yes_no(BOOLEAN b) { + return b ? L"yes" : L"no"; +} + +EFI_STATUS parse_boolean(CHAR8 *v, BOOLEAN *b); + +UINT64 ticks_read(void); +UINT64 ticks_freq(void); +UINT64 time_usec(void); + +EFI_STATUS efivar_set(CHAR16 *name, CHAR16 *value, BOOLEAN persistent); +EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 *buf, UINTN size, BOOLEAN persistent); +EFI_STATUS efivar_set_int(CHAR16 *name, UINTN i, BOOLEAN persistent); +VOID efivar_set_time_usec(CHAR16 *name, UINT64 usec); + +EFI_STATUS efivar_get(CHAR16 *name, CHAR16 **value); +EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 **buffer, UINTN *size); +EFI_STATUS efivar_get_int(CHAR16 *name, UINTN *i); + +CHAR8 *strchra(CHAR8 *s, CHAR8 c); +CHAR16 *stra_to_path(CHAR8 *stra); +CHAR16 *stra_to_str(CHAR8 *stra); + +INTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, UINTN off, UINTN size, CHAR8 **content); +#endif diff --git a/src/grp-coredump/Makefile b/src/grp-coredump/Makefile new file mode 100644 index 0000000000..2e604d7b86 --- /dev/null +++ b/src/grp-coredump/Makefile @@ -0,0 +1,28 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +at.subdirs += coredumpctl systemd-coredump + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-coredump/coredumpctl/Makefile b/src/grp-coredump/coredumpctl/Makefile new file mode 100644 index 0000000000..47a4397fa4 --- /dev/null +++ b/src/grp-coredump/coredumpctl/Makefile @@ -0,0 +1,41 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +coredumpctl_SOURCES = \ + src/coredump/coredumpctl.c + +coredumpctl_LDADD = \ + libshared.la + +bin_PROGRAMS += \ + coredumpctl + +dist_bashcompletion_data += \ + shell-completion/bash/coredumpctl + +dist_zshcompletion_data += \ + shell-completion/zsh/_coredumpctl + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-coredump/coredumpctl/coredumpctl.c b/src/grp-coredump/coredumpctl/coredumpctl.c new file mode 100644 index 0000000000..dc95da3613 --- /dev/null +++ b/src/grp-coredump/coredumpctl/coredumpctl.c @@ -0,0 +1,878 @@ +/*** + This file is part of systemd. + + Copyright 2012 Zbigniew Jędrzejewski-Szmek + + 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 +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "compress.h" +#include "fd-util.h" +#include "fileio.h" +#include "journal-internal.h" +#include "log.h" +#include "macro.h" +#include "pager.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "set.h" +#include "sigbus.h" +#include "signal-util.h" +#include "string-util.h" +#include "terminal-util.h" +#include "user-util.h" +#include "util.h" + +static enum { + ACTION_NONE, + ACTION_INFO, + ACTION_LIST, + ACTION_DUMP, + ACTION_GDB, +} arg_action = ACTION_LIST; +static const char* arg_field = NULL; +static const char *arg_directory = NULL; +static bool arg_no_pager = false; +static int arg_no_legend = false; +static int arg_one = false; +static FILE* arg_output = NULL; + +static Set *new_matches(void) { + Set *set; + char *tmp; + int r; + + set = set_new(NULL); + if (!set) { + log_oom(); + return NULL; + } + + tmp = strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1"); + if (!tmp) { + log_oom(); + set_free(set); + return NULL; + } + + r = set_consume(set, tmp); + if (r < 0) { + log_error_errno(r, "failed to add to set: %m"); + set_free(set); + return NULL; + } + + return set; +} + +static int add_match(Set *set, const char *match) { + _cleanup_free_ char *p = NULL; + char *pattern = NULL; + const char* prefix; + pid_t pid; + int r; + + if (strchr(match, '=')) + prefix = ""; + else if (strchr(match, '/')) { + r = path_make_absolute_cwd(match, &p); + if (r < 0) + goto fail; + match = p; + prefix = "COREDUMP_EXE="; + } else if (parse_pid(match, &pid) >= 0) + prefix = "COREDUMP_PID="; + else + prefix = "COREDUMP_COMM="; + + pattern = strjoin(prefix, match, NULL); + if (!pattern) { + r = -ENOMEM; + goto fail; + } + + log_debug("Adding pattern: %s", pattern); + r = set_consume(set, pattern); + if (r < 0) + goto fail; + + return 0; +fail: + return log_error_errno(r, "Failed to add match: %m"); +} + +static void help(void) { + printf("%s [OPTIONS...]\n\n" + "List or retrieve coredumps from the journal.\n\n" + "Flags:\n" + " -h --help Show this help\n" + " --version Print version string\n" + " --no-pager Do not pipe output into a pager\n" + " --no-legend Do not print the column headers.\n" + " -1 Show information about most recent entry only\n" + " -F --field=FIELD List all values a certain field takes\n" + " -o --output=FILE Write output to FILE\n\n" + " -D --directory=DIR Use journal files from directory\n\n" + + "Commands:\n" + " list [MATCHES...] List available coredumps (default)\n" + " info [MATCHES...] Show detailed information about one or more coredumps\n" + " dump [MATCHES...] Print first matching coredump to stdout\n" + " gdb [MATCHES...] Start gdb for the first matching coredump\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[], Set *matches) { + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_NO_LEGEND, + }; + + int r, c; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version" , no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "output", required_argument, NULL, 'o' }, + { "field", required_argument, NULL, 'F' }, + { "directory", required_argument, NULL, 'D' }, + {} + }; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "ho:F:1D:", options, NULL)) >= 0) + switch(c) { + + case 'h': + arg_action = ACTION_NONE; + help(); + return 0; + + case ARG_VERSION: + arg_action = ACTION_NONE; + return version(); + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case ARG_NO_LEGEND: + arg_no_legend = true; + break; + + case 'o': + if (arg_output) { + log_error("cannot set output more than once"); + return -EINVAL; + } + + arg_output = fopen(optarg, "we"); + if (!arg_output) + return log_error_errno(errno, "writing to '%s': %m", optarg); + + break; + + case 'F': + if (arg_field) { + log_error("cannot use --field/-F more than once"); + return -EINVAL; + } + arg_field = optarg; + break; + + case '1': + arg_one = true; + break; + + case 'D': + arg_directory = optarg; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (optind < argc) { + const char *cmd = argv[optind++]; + if (streq(cmd, "list")) + arg_action = ACTION_LIST; + else if (streq(cmd, "dump")) + arg_action = ACTION_DUMP; + else if (streq(cmd, "gdb")) + arg_action = ACTION_GDB; + else if (streq(cmd, "info")) + arg_action = ACTION_INFO; + else { + log_error("Unknown action '%s'", cmd); + return -EINVAL; + } + } + + if (arg_field && arg_action != ACTION_LIST) { + log_error("Option --field/-F only makes sense with list"); + return -EINVAL; + } + + while (optind < argc) { + r = add_match(matches, argv[optind]); + if (r != 0) + return r; + optind++; + } + + return 0; +} + +static int retrieve(const void *data, + size_t len, + const char *name, + char **var) { + + size_t ident; + char *v; + + ident = strlen(name) + 1; /* name + "=" */ + + if (len < ident) + return 0; + + if (memcmp(data, name, ident - 1) != 0) + return 0; + + if (((const char*) data)[ident - 1] != '=') + return 0; + + v = strndup((const char*)data + ident, len - ident); + if (!v) + return log_oom(); + + free(*var); + *var = v; + + return 0; +} + +static void print_field(FILE* file, sd_journal *j) { + _cleanup_free_ char *value = NULL; + const void *d; + size_t l; + + assert(file); + assert(j); + + assert(arg_field); + + SD_JOURNAL_FOREACH_DATA(j, d, l) + retrieve(d, l, arg_field, &value); + + if (value) + fprintf(file, "%s\n", value); +} + +static int print_list(FILE* file, sd_journal *j, int had_legend) { + _cleanup_free_ char + *pid = NULL, *uid = NULL, *gid = NULL, + *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL, + *filename = NULL; + const void *d; + size_t l; + usec_t t; + char buf[FORMAT_TIMESTAMP_MAX]; + int r; + bool present; + + assert(file); + assert(j); + + SD_JOURNAL_FOREACH_DATA(j, d, l) { + retrieve(d, l, "COREDUMP_PID", &pid); + retrieve(d, l, "COREDUMP_UID", &uid); + retrieve(d, l, "COREDUMP_GID", &gid); + retrieve(d, l, "COREDUMP_SIGNAL", &sgnl); + retrieve(d, l, "COREDUMP_EXE", &exe); + retrieve(d, l, "COREDUMP_COMM", &comm); + retrieve(d, l, "COREDUMP_CMDLINE", &cmdline); + retrieve(d, l, "COREDUMP_FILENAME", &filename); + } + + if (!pid && !uid && !gid && !sgnl && !exe && !comm && !cmdline && !filename) { + log_warning("Empty coredump log entry"); + return -EINVAL; + } + + r = sd_journal_get_realtime_usec(j, &t); + if (r < 0) + return log_error_errno(r, "Failed to get realtime timestamp: %m"); + + format_timestamp(buf, sizeof(buf), t); + present = filename && access(filename, F_OK) == 0; + + if (!had_legend && !arg_no_legend) + fprintf(file, "%-*s %*s %*s %*s %*s %*s %s\n", + FORMAT_TIMESTAMP_WIDTH, "TIME", + 6, "PID", + 5, "UID", + 5, "GID", + 3, "SIG", + 1, "PRESENT", + "EXE"); + + fprintf(file, "%-*s %*s %*s %*s %*s %*s %s\n", + FORMAT_TIMESTAMP_WIDTH, buf, + 6, strna(pid), + 5, strna(uid), + 5, strna(gid), + 3, strna(sgnl), + 1, present ? "*" : "", + strna(exe ?: (comm ?: cmdline))); + + return 0; +} + +static int print_info(FILE *file, sd_journal *j, bool need_space) { + _cleanup_free_ char + *pid = NULL, *uid = NULL, *gid = NULL, + *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL, + *unit = NULL, *user_unit = NULL, *session = NULL, + *boot_id = NULL, *machine_id = NULL, *hostname = NULL, + *slice = NULL, *cgroup = NULL, *owner_uid = NULL, + *message = NULL, *timestamp = NULL, *filename = NULL; + const void *d; + size_t l; + int r; + + assert(file); + assert(j); + + SD_JOURNAL_FOREACH_DATA(j, d, l) { + retrieve(d, l, "COREDUMP_PID", &pid); + retrieve(d, l, "COREDUMP_UID", &uid); + retrieve(d, l, "COREDUMP_GID", &gid); + retrieve(d, l, "COREDUMP_SIGNAL", &sgnl); + retrieve(d, l, "COREDUMP_EXE", &exe); + retrieve(d, l, "COREDUMP_COMM", &comm); + retrieve(d, l, "COREDUMP_CMDLINE", &cmdline); + retrieve(d, l, "COREDUMP_UNIT", &unit); + retrieve(d, l, "COREDUMP_USER_UNIT", &user_unit); + retrieve(d, l, "COREDUMP_SESSION", &session); + retrieve(d, l, "COREDUMP_OWNER_UID", &owner_uid); + retrieve(d, l, "COREDUMP_SLICE", &slice); + retrieve(d, l, "COREDUMP_CGROUP", &cgroup); + retrieve(d, l, "COREDUMP_TIMESTAMP", ×tamp); + retrieve(d, l, "COREDUMP_FILENAME", &filename); + retrieve(d, l, "_BOOT_ID", &boot_id); + retrieve(d, l, "_MACHINE_ID", &machine_id); + retrieve(d, l, "_HOSTNAME", &hostname); + retrieve(d, l, "MESSAGE", &message); + } + + if (need_space) + fputs("\n", file); + + if (comm) + fprintf(file, + " PID: %s%s%s (%s)\n", + ansi_highlight(), strna(pid), ansi_normal(), comm); + else + fprintf(file, + " PID: %s%s%s\n", + ansi_highlight(), strna(pid), ansi_normal()); + + if (uid) { + uid_t n; + + if (parse_uid(uid, &n) >= 0) { + _cleanup_free_ char *u = NULL; + + u = uid_to_name(n); + fprintf(file, + " UID: %s (%s)\n", + uid, u); + } else { + fprintf(file, + " UID: %s\n", + uid); + } + } + + if (gid) { + gid_t n; + + if (parse_gid(gid, &n) >= 0) { + _cleanup_free_ char *g = NULL; + + g = gid_to_name(n); + fprintf(file, + " GID: %s (%s)\n", + gid, g); + } else { + fprintf(file, + " GID: %s\n", + gid); + } + } + + if (sgnl) { + int sig; + + if (safe_atoi(sgnl, &sig) >= 0) + fprintf(file, " Signal: %s (%s)\n", sgnl, signal_to_string(sig)); + else + fprintf(file, " Signal: %s\n", sgnl); + } + + if (timestamp) { + usec_t u; + + r = safe_atou64(timestamp, &u); + if (r >= 0) { + char absolute[FORMAT_TIMESTAMP_MAX], relative[FORMAT_TIMESPAN_MAX]; + + fprintf(file, + " Timestamp: %s (%s)\n", + format_timestamp(absolute, sizeof(absolute), u), + format_timestamp_relative(relative, sizeof(relative), u)); + + } else + fprintf(file, " Timestamp: %s\n", timestamp); + } + + if (cmdline) + fprintf(file, " Command Line: %s\n", cmdline); + if (exe) + fprintf(file, " Executable: %s%s%s\n", ansi_highlight(), exe, ansi_normal()); + if (cgroup) + fprintf(file, " Control Group: %s\n", cgroup); + if (unit) + fprintf(file, " Unit: %s\n", unit); + if (user_unit) + fprintf(file, " User Unit: %s\n", unit); + if (slice) + fprintf(file, " Slice: %s\n", slice); + if (session) + fprintf(file, " Session: %s\n", session); + if (owner_uid) { + uid_t n; + + if (parse_uid(owner_uid, &n) >= 0) { + _cleanup_free_ char *u = NULL; + + u = uid_to_name(n); + fprintf(file, + " Owner UID: %s (%s)\n", + owner_uid, u); + } else { + fprintf(file, + " Owner UID: %s\n", + owner_uid); + } + } + if (boot_id) + fprintf(file, " Boot ID: %s\n", boot_id); + if (machine_id) + fprintf(file, " Machine ID: %s\n", machine_id); + if (hostname) + fprintf(file, " Hostname: %s\n", hostname); + + if (filename && access(filename, F_OK) == 0) + fprintf(file, " Coredump: %s\n", filename); + + if (message) { + _cleanup_free_ char *m = NULL; + + m = strreplace(message, "\n", "\n "); + + fprintf(file, " Message: %s\n", strstrip(m ?: message)); + } + + return 0; +} + +static int focus(sd_journal *j) { + int r; + + r = sd_journal_seek_tail(j); + if (r == 0) + r = sd_journal_previous(j); + if (r < 0) + return log_error_errno(r, "Failed to search journal: %m"); + if (r == 0) { + log_error("No match found."); + return -ESRCH; + } + return r; +} + +static void print_entry(sd_journal *j, unsigned n_found) { + assert(j); + + if (arg_action == ACTION_INFO) + print_info(stdout, j, n_found); + else if (arg_field) + print_field(stdout, j); + else + print_list(stdout, j, n_found); +} + +static int dump_list(sd_journal *j) { + unsigned n_found = 0; + int r; + + assert(j); + + /* The coredumps are likely to compressed, and for just + * listing them we don't need to decompress them, so let's + * pick a fairly low data threshold here */ + sd_journal_set_data_threshold(j, 4096); + + if (arg_one) { + r = focus(j); + if (r < 0) + return r; + + print_entry(j, 0); + } else { + SD_JOURNAL_FOREACH(j) + print_entry(j, n_found++); + + if (!arg_field && n_found <= 0) { + log_notice("No coredumps found."); + return -ESRCH; + } + } + + return 0; +} + +static int save_core(sd_journal *j, int fd, char **path, bool *unlink_temp) { + const char *data; + _cleanup_free_ char *filename = NULL; + size_t len; + int r; + + assert((fd >= 0) != !!path); + assert(!!path == !!unlink_temp); + + /* Prefer uncompressed file to journal (probably cached) to + * compressed file (probably uncached). */ + r = sd_journal_get_data(j, "COREDUMP_FILENAME", (const void**) &data, &len); + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to retrieve COREDUMP_FILENAME: %m"); + else if (r == 0) + retrieve(data, len, "COREDUMP_FILENAME", &filename); + + if (filename && access(filename, R_OK) < 0) { + log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, + "File %s is not readable: %m", filename); + filename = mfree(filename); + } + + if (filename && !endswith(filename, ".xz") && !endswith(filename, ".lz4")) { + if (path) { + *path = filename; + filename = NULL; + } + + return 0; + } else { + _cleanup_close_ int fdt = -1; + char *temp = NULL; + + if (fd < 0) { + temp = strdup("/var/tmp/coredump-XXXXXX"); + if (!temp) + return log_oom(); + + fdt = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC); + if (fdt < 0) + return log_error_errno(fdt, "Failed to create temporary file: %m"); + log_debug("Created temporary file %s", temp); + + fd = fdt; + } + + r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len); + if (r == 0) { + ssize_t sz; + + assert(len >= 9); + data += 9; + len -= 9; + + sz = write(fdt, data, len); + if (sz < 0) { + r = log_error_errno(errno, + "Failed to write temporary file: %m"); + goto error; + } + if (sz != (ssize_t) len) { + log_error("Short write to temporary file."); + r = -EIO; + goto error; + } + } else if (filename) { +#if defined(HAVE_XZ) || defined(HAVE_LZ4) + _cleanup_close_ int fdf; + + fdf = open(filename, O_RDONLY | O_CLOEXEC); + if (fdf < 0) { + r = log_error_errno(errno, + "Failed to open %s: %m", + filename); + goto error; + } + + r = decompress_stream(filename, fdf, fd, -1); + if (r < 0) { + log_error_errno(r, "Failed to decompress %s: %m", filename); + goto error; + } +#else + log_error("Cannot decompress file. Compiled without compression support."); + r = -EOPNOTSUPP; + goto error; +#endif + } else { + if (r == -ENOENT) + log_error("Cannot retrieve coredump from journal or disk."); + else + log_error_errno(r, "Failed to retrieve COREDUMP field: %m"); + goto error; + } + + if (temp) { + *path = temp; + *unlink_temp = true; + } + + return 0; + +error: + if (temp) { + unlink(temp); + log_debug("Removed temporary file %s", temp); + } + return r; + } +} + +static int dump_core(sd_journal* j) { + int r; + + assert(j); + + r = focus(j); + if (r < 0) + return r; + + print_info(arg_output ? stdout : stderr, j, false); + + if (on_tty() && !arg_output) { + log_error("Refusing to dump core to tty."); + return -ENOTTY; + } + + r = save_core(j, arg_output ? fileno(arg_output) : STDOUT_FILENO, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Coredump retrieval failed: %m"); + + r = sd_journal_previous(j); + if (r >= 0) + log_warning("More than one entry matches, ignoring rest."); + + return 0; +} + +static int run_gdb(sd_journal *j) { + _cleanup_free_ char *exe = NULL, *path = NULL; + bool unlink_path = false; + const char *data; + siginfo_t st; + size_t len; + pid_t pid; + int r; + + assert(j); + + r = focus(j); + if (r < 0) + return r; + + print_info(stdout, j, false); + fputs("\n", stdout); + + r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len); + if (r < 0) + return log_error_errno(r, "Failed to retrieve COREDUMP_EXE field: %m"); + + assert(len > strlen("COREDUMP_EXE=")); + data += strlen("COREDUMP_EXE="); + len -= strlen("COREDUMP_EXE="); + + exe = strndup(data, len); + if (!exe) + return log_oom(); + + if (endswith(exe, " (deleted)")) { + log_error("Binary already deleted."); + return -ENOENT; + } + + if (!path_is_absolute(exe)) { + log_error("Binary is not an absolute path."); + return -ENOENT; + } + + r = save_core(j, -1, &path, &unlink_path); + if (r < 0) + return log_error_errno(r, "Failed to retrieve core: %m"); + + pid = fork(); + if (pid < 0) { + r = log_error_errno(errno, "Failed to fork(): %m"); + goto finish; + } + if (pid == 0) { + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + execlp("gdb", "gdb", exe, path, NULL); + + log_error_errno(errno, "Failed to invoke gdb: %m"); + _exit(1); + } + + r = wait_for_terminate(pid, &st); + if (r < 0) { + log_error_errno(r, "Failed to wait for gdb: %m"); + goto finish; + } + + r = st.si_code == CLD_EXITED ? st.si_status : 255; + +finish: + if (unlink_path) { + log_debug("Removed temporary file %s", path); + unlink(path); + } + + return r; +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_journal_closep) sd_journal*j = NULL; + const char* match; + Iterator it; + int r = 0; + _cleanup_set_free_free_ Set *matches = NULL; + + setlocale(LC_ALL, ""); + log_parse_environment(); + log_open(); + + matches = new_matches(); + if (!matches) { + r = -ENOMEM; + goto end; + } + + r = parse_argv(argc, argv, matches); + if (r < 0) + goto end; + + if (arg_action == ACTION_NONE) + goto end; + + sigbus_install(); + + if (arg_directory) { + r = sd_journal_open_directory(&j, arg_directory, 0); + if (r < 0) { + log_error_errno(r, "Failed to open journals in directory: %s: %m", arg_directory); + goto end; + } + } else { + r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); + if (r < 0) { + log_error_errno(r, "Failed to open journal: %m"); + goto end; + } + } + + /* We want full data, nothing truncated. */ + sd_journal_set_data_threshold(j, 0); + + SET_FOREACH(match, matches, it) { + r = sd_journal_add_match(j, match, strlen(match)); + if (r != 0) { + log_error_errno(r, "Failed to add match '%s': %m", + match); + goto end; + } + } + + if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) { + _cleanup_free_ char *filter; + + filter = journal_make_match_string(j); + log_debug("Journal filter: %s", filter); + } + + switch(arg_action) { + + case ACTION_LIST: + case ACTION_INFO: + pager_open(arg_no_pager, false); + r = dump_list(j); + break; + + case ACTION_DUMP: + r = dump_core(j); + break; + + case ACTION_GDB: + r = run_gdb(j); + break; + + default: + assert_not_reached("Shouldn't be here"); + } + +end: + pager_close(); + + if (arg_output) + fclose(arg_output); + + return r >= 0 ? r : EXIT_FAILURE; +} diff --git a/src/grp-coredump/systemd-coredump/Makefile b/src/grp-coredump/systemd-coredump/Makefile new file mode 100644 index 0000000000..87ec1799c0 --- /dev/null +++ b/src/grp-coredump/systemd-coredump/Makefile @@ -0,0 +1,81 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_COREDUMP),) +systemd_coredump_SOURCES = \ + src/coredump/coredump.c \ + src/coredump/coredump-vacuum.c \ + src/coredump/coredump-vacuum.h + +systemd_coredump_LDADD = \ + libshared.la + +ifneq ($(HAVE_ELFUTILS),) +systemd_coredump_SOURCES += \ + src/coredump/stacktrace.c \ + src/coredump/stacktrace.h + +systemd_coredump_LDADD += \ + $(ELFUTILS_LIBS) +endif # HAVE_ELFUTILS + +nodist_systemunit_DATA += \ + units/systemd-coredump@.service + +dist_systemunit_DATA += \ + units/systemd-coredump.socket + +SOCKETS_TARGET_WANTS += \ + systemd-coredump.socket + +libexec_PROGRAMS += \ + systemd-coredump + +dist_pkgsysconf_DATA += \ + src/coredump/coredump.conf + +manual_tests += \ + test-coredump-vacuum + +test_coredump_vacuum_SOURCES = \ + src/coredump/test-coredump-vacuum.c \ + src/coredump/coredump-vacuum.c \ + src/coredump/coredump-vacuum.h + +test_coredump_vacuum_LDADD = \ + libshared.la + +nodist_sysctl_DATA = \ + sysctl.d/50-coredump.conf + +CLEANFILES += \ + sysctl.d/50-coredump.conf +endif # ENABLE_COREDUMP + +EXTRA_DIST += \ + sysctl.d/50-coredump.conf.in \ + units/systemd-coredump@.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-coredump/systemd-coredump/coredump-vacuum.c b/src/grp-coredump/systemd-coredump/coredump-vacuum.c new file mode 100644 index 0000000000..f02b6dbd87 --- /dev/null +++ b/src/grp-coredump/systemd-coredump/coredump-vacuum.c @@ -0,0 +1,268 @@ +/*** + 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 "alloc-util.h" +#include "coredump-vacuum.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "macro.h" +#include "string-util.h" +#include "time-util.h" +#include "user-util.h" +#include "util.h" + +#define DEFAULT_MAX_USE_LOWER (uint64_t) (1ULL*1024ULL*1024ULL) /* 1 MiB */ +#define DEFAULT_MAX_USE_UPPER (uint64_t) (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */ +#define DEFAULT_KEEP_FREE_UPPER (uint64_t) (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */ +#define DEFAULT_KEEP_FREE (uint64_t) (1024ULL*1024ULL) /* 1 MB */ + +struct vacuum_candidate { + unsigned n_files; + char *oldest_file; + usec_t oldest_mtime; +}; + +static void vacuum_candidate_free(struct vacuum_candidate *c) { + if (!c) + return; + + free(c->oldest_file); + free(c); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct vacuum_candidate*, vacuum_candidate_free); + +static void vacuum_candidate_hasmap_free(Hashmap *h) { + struct vacuum_candidate *c; + + while ((c = hashmap_steal_first(h))) + vacuum_candidate_free(c); + + hashmap_free(h); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, vacuum_candidate_hasmap_free); + +static int uid_from_file_name(const char *filename, uid_t *uid) { + const char *p, *e, *u; + + p = startswith(filename, "core."); + if (!p) + return -EINVAL; + + /* Skip the comm field */ + p = strchr(p, '.'); + if (!p) + return -EINVAL; + p++; + + /* Find end up UID */ + e = strchr(p, '.'); + if (!e) + return -EINVAL; + + u = strndupa(p, e-p); + return parse_uid(u, uid); +} + +static bool vacuum_necessary(int fd, uint64_t sum, uint64_t keep_free, uint64_t max_use) { + uint64_t fs_size = 0, fs_free = (uint64_t) -1; + struct statvfs sv; + + assert(fd >= 0); + + if (fstatvfs(fd, &sv) >= 0) { + fs_size = sv.f_frsize * sv.f_blocks; + fs_free = sv.f_frsize * sv.f_bfree; + } + + if (max_use == (uint64_t) -1) { + + if (fs_size > 0) { + max_use = PAGE_ALIGN(fs_size / 10); /* 10% */ + + if (max_use > DEFAULT_MAX_USE_UPPER) + max_use = DEFAULT_MAX_USE_UPPER; + + if (max_use < DEFAULT_MAX_USE_LOWER) + max_use = DEFAULT_MAX_USE_LOWER; + } else + max_use = DEFAULT_MAX_USE_LOWER; + } else + max_use = PAGE_ALIGN(max_use); + + if (max_use > 0 && sum > max_use) + return true; + + if (keep_free == (uint64_t) -1) { + + if (fs_size > 0) { + keep_free = PAGE_ALIGN((fs_size * 3) / 20); /* 15% */ + + if (keep_free > DEFAULT_KEEP_FREE_UPPER) + keep_free = DEFAULT_KEEP_FREE_UPPER; + } else + keep_free = DEFAULT_KEEP_FREE; + } else + keep_free = PAGE_ALIGN(keep_free); + + if (keep_free > 0 && fs_free < keep_free) + return true; + + return false; +} + +int coredump_vacuum(int exclude_fd, uint64_t keep_free, uint64_t max_use) { + _cleanup_closedir_ DIR *d = NULL; + struct stat exclude_st; + int r; + + if (keep_free == 0 && max_use == 0) + return 0; + + if (exclude_fd >= 0) { + if (fstat(exclude_fd, &exclude_st) < 0) + return log_error_errno(errno, "Failed to fstat(): %m"); + } + + /* This algorithm will keep deleting the oldest file of the + * user with the most coredumps until we are back in the size + * limits. Note that vacuuming for journal files is different, + * because we rely on rate-limiting of the messages there, + * to avoid being flooded. */ + + d = opendir("/var/lib/systemd/coredump"); + if (!d) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Can't open coredump directory: %m"); + } + + for (;;) { + _cleanup_(vacuum_candidate_hasmap_freep) Hashmap *h = NULL; + struct vacuum_candidate *worst = NULL; + struct dirent *de; + uint64_t sum = 0; + + rewinddir(d); + + FOREACH_DIRENT(de, d, goto fail) { + struct vacuum_candidate *c; + struct stat st; + uid_t uid; + usec_t t; + + r = uid_from_file_name(de->d_name, &uid); + if (r < 0) + continue; + + if (fstatat(dirfd(d), de->d_name, &st, AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW) < 0) { + if (errno == ENOENT) + continue; + + log_warning_errno(errno, "Failed to stat /var/lib/systemd/coredump/%s: %m", de->d_name); + continue; + } + + if (!S_ISREG(st.st_mode)) + continue; + + if (exclude_fd >= 0 && + exclude_st.st_dev == st.st_dev && + exclude_st.st_ino == st.st_ino) + continue; + + r = hashmap_ensure_allocated(&h, NULL); + if (r < 0) + return log_oom(); + + t = timespec_load(&st.st_mtim); + + c = hashmap_get(h, UID_TO_PTR(uid)); + if (c) { + + if (t < c->oldest_mtime) { + char *n; + + n = strdup(de->d_name); + if (!n) + return log_oom(); + + free(c->oldest_file); + c->oldest_file = n; + c->oldest_mtime = t; + } + + } else { + _cleanup_(vacuum_candidate_freep) struct vacuum_candidate *n = NULL; + + n = new0(struct vacuum_candidate, 1); + if (!n) + return log_oom(); + + n->oldest_file = strdup(de->d_name); + if (!n->oldest_file) + return log_oom(); + + n->oldest_mtime = t; + + r = hashmap_put(h, UID_TO_PTR(uid), n); + if (r < 0) + return log_oom(); + + c = n; + n = NULL; + } + + c->n_files++; + + if (!worst || + worst->n_files < c->n_files || + (worst->n_files == c->n_files && c->oldest_mtime < worst->oldest_mtime)) + worst = c; + + sum += st.st_blocks * 512; + } + + if (!worst) + break; + + r = vacuum_necessary(dirfd(d), sum, keep_free, max_use); + if (r <= 0) + return r; + + if (unlinkat(dirfd(d), worst->oldest_file, 0) < 0) { + + if (errno == ENOENT) + continue; + + return log_error_errno(errno, "Failed to remove file %s: %m", worst->oldest_file); + } else + log_info("Removed old coredump %s.", worst->oldest_file); + } + + return 0; + +fail: + return log_error_errno(errno, "Failed to read directory: %m"); +} diff --git a/src/grp-coredump/systemd-coredump/coredump-vacuum.h b/src/grp-coredump/systemd-coredump/coredump-vacuum.h new file mode 100644 index 0000000000..4b7b9f2d98 --- /dev/null +++ b/src/grp-coredump/systemd-coredump/coredump-vacuum.h @@ -0,0 +1,25 @@ +#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 +#include + +int coredump_vacuum(int exclude_fd, uint64_t keep_free, uint64_t max_use); diff --git a/src/grp-coredump/systemd-coredump/coredump.c b/src/grp-coredump/systemd-coredump/coredump.c new file mode 100644 index 0000000000..bf0e0a038b --- /dev/null +++ b/src/grp-coredump/systemd-coredump/coredump.c @@ -0,0 +1,1149 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 +#include +#include +#include + +#ifdef HAVE_ELFUTILS +#include +#include +#endif + +#include +#include +#include + +#include "acl-util.h" +#include "alloc-util.h" +#include "capability-util.h" +#include "cgroup-util.h" +#include "compress.h" +#include "conf-parser.h" +#include "copy.h" +#include "coredump-vacuum.h" +#include "dirent-util.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "io-util.h" +#include "journald-native.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "parse-util.h" +#include "process-util.h" +#include "socket-util.h" +#include "special.h" +#include "stacktrace.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" +#include "util.h" + +/* The maximum size up to which we process coredumps */ +#define PROCESS_SIZE_MAX ((uint64_t) (2LLU*1024LLU*1024LLU*1024LLU)) + +/* The maximum size up to which we leave the coredump around on disk */ +#define EXTERNAL_SIZE_MAX PROCESS_SIZE_MAX + +/* The maximum size up to which we store the coredump in the journal */ +#define JOURNAL_SIZE_MAX ((size_t) (767LU*1024LU*1024LU)) + +/* Make sure to not make this larger than the maximum journal entry + * size. See DATA_SIZE_MAX in journald-native.c. */ +assert_cc(JOURNAL_SIZE_MAX <= DATA_SIZE_MAX); + +enum { + /* We use this as array indexes for a couple of special fields we use for naming coredumping files, and + * attaching xattrs */ + CONTEXT_PID, + CONTEXT_UID, + CONTEXT_GID, + CONTEXT_SIGNAL, + CONTEXT_TIMESTAMP, + CONTEXT_RLIMIT, + CONTEXT_COMM, + CONTEXT_EXE, + _CONTEXT_MAX +}; + +typedef enum CoredumpStorage { + COREDUMP_STORAGE_NONE, + COREDUMP_STORAGE_EXTERNAL, + COREDUMP_STORAGE_JOURNAL, + COREDUMP_STORAGE_BOTH, + _COREDUMP_STORAGE_MAX, + _COREDUMP_STORAGE_INVALID = -1 +} CoredumpStorage; + +static const char* const coredump_storage_table[_COREDUMP_STORAGE_MAX] = { + [COREDUMP_STORAGE_NONE] = "none", + [COREDUMP_STORAGE_EXTERNAL] = "external", + [COREDUMP_STORAGE_JOURNAL] = "journal", + [COREDUMP_STORAGE_BOTH] = "both", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(coredump_storage, CoredumpStorage); +static DEFINE_CONFIG_PARSE_ENUM(config_parse_coredump_storage, coredump_storage, CoredumpStorage, "Failed to parse storage setting"); + +static CoredumpStorage arg_storage = COREDUMP_STORAGE_EXTERNAL; +static bool arg_compress = true; +static uint64_t arg_process_size_max = PROCESS_SIZE_MAX; +static uint64_t arg_external_size_max = EXTERNAL_SIZE_MAX; +static size_t arg_journal_size_max = JOURNAL_SIZE_MAX; +static uint64_t arg_keep_free = (uint64_t) -1; +static uint64_t arg_max_use = (uint64_t) -1; + +static int parse_config(void) { + static const ConfigTableItem items[] = { + { "Coredump", "Storage", config_parse_coredump_storage, 0, &arg_storage }, + { "Coredump", "Compress", config_parse_bool, 0, &arg_compress }, + { "Coredump", "ProcessSizeMax", config_parse_iec_uint64, 0, &arg_process_size_max }, + { "Coredump", "ExternalSizeMax", config_parse_iec_uint64, 0, &arg_external_size_max }, + { "Coredump", "JournalSizeMax", config_parse_iec_size, 0, &arg_journal_size_max }, + { "Coredump", "KeepFree", config_parse_iec_uint64, 0, &arg_keep_free }, + { "Coredump", "MaxUse", config_parse_iec_uint64, 0, &arg_max_use }, + {} + }; + + return config_parse_many(PKGSYSCONFDIR "/coredump.conf", + CONF_PATHS_NULSTR("systemd/coredump.conf.d"), + "Coredump\0", + config_item_table_lookup, items, + false, NULL); +} + +static int fix_acl(int fd, uid_t uid) { + +#ifdef HAVE_ACL + _cleanup_(acl_freep) acl_t acl = NULL; + acl_entry_t entry; + acl_permset_t permset; + int r; + + assert(fd >= 0); + + if (uid <= SYSTEM_UID_MAX) + return 0; + + /* Make sure normal users can read (but not write or delete) + * their own coredumps */ + + acl = acl_get_fd(fd); + if (!acl) + return log_error_errno(errno, "Failed to get ACL: %m"); + + if (acl_create_entry(&acl, &entry) < 0 || + acl_set_tag_type(entry, ACL_USER) < 0 || + acl_set_qualifier(entry, &uid) < 0) { + log_error_errno(errno, "Failed to patch ACL: %m"); + return -errno; + } + + if (acl_get_permset(entry, &permset) < 0 || + acl_add_perm(permset, ACL_READ) < 0) + return log_warning_errno(errno, "Failed to patch ACL: %m"); + + r = calc_acl_mask_if_needed(&acl); + if (r < 0) + return log_warning_errno(r, "Failed to patch ACL: %m"); + + if (acl_set_fd(fd, acl) < 0) + return log_error_errno(errno, "Failed to apply ACL: %m"); +#endif + + return 0; +} + +static int fix_xattr(int fd, const char *context[_CONTEXT_MAX]) { + + static const char * const xattrs[_CONTEXT_MAX] = { + [CONTEXT_PID] = "user.coredump.pid", + [CONTEXT_UID] = "user.coredump.uid", + [CONTEXT_GID] = "user.coredump.gid", + [CONTEXT_SIGNAL] = "user.coredump.signal", + [CONTEXT_TIMESTAMP] = "user.coredump.timestamp", + [CONTEXT_COMM] = "user.coredump.comm", + [CONTEXT_EXE] = "user.coredump.exe", + }; + + int r = 0; + unsigned i; + + assert(fd >= 0); + + /* Attach some metadata to coredumps via extended + * attributes. Just because we can. */ + + for (i = 0; i < _CONTEXT_MAX; i++) { + int k; + + if (isempty(context[i]) || !xattrs[i]) + continue; + + k = fsetxattr(fd, xattrs[i], context[i], strlen(context[i]), XATTR_CREATE); + if (k < 0 && r == 0) + r = -errno; + } + + return r; +} + +#define filename_escape(s) xescape((s), "./ ") + +static inline const char *coredump_tmpfile_name(const char *s) { + return s ? s : "(unnamed temporary file)"; +} + +static int fix_permissions( + int fd, + const char *filename, + const char *target, + const char *context[_CONTEXT_MAX], + uid_t uid) { + + int r; + + assert(fd >= 0); + assert(target); + assert(context); + + /* Ignore errors on these */ + (void) fchmod(fd, 0640); + (void) fix_acl(fd, uid); + (void) fix_xattr(fd, context); + + if (fsync(fd) < 0) + return log_error_errno(errno, "Failed to sync coredump %s: %m", coredump_tmpfile_name(filename)); + + r = link_tmpfile(fd, filename, target); + if (r < 0) + return log_error_errno(r, "Failed to move coredump %s into place: %m", target); + + return 0; +} + +static int maybe_remove_external_coredump(const char *filename, uint64_t size) { + + /* Returns 1 if might remove, 0 if will not remove, < 0 on error. */ + + if (IN_SET(arg_storage, COREDUMP_STORAGE_EXTERNAL, COREDUMP_STORAGE_BOTH) && + size <= arg_external_size_max) + return 0; + + if (!filename) + return 1; + + if (unlink(filename) < 0 && errno != ENOENT) + return log_error_errno(errno, "Failed to unlink %s: %m", filename); + + return 1; +} + +static int make_filename(const char *context[_CONTEXT_MAX], char **ret) { + _cleanup_free_ char *c = NULL, *u = NULL, *p = NULL, *t = NULL; + sd_id128_t boot = {}; + int r; + + assert(context); + + c = filename_escape(context[CONTEXT_COMM]); + if (!c) + return -ENOMEM; + + u = filename_escape(context[CONTEXT_UID]); + if (!u) + return -ENOMEM; + + r = sd_id128_get_boot(&boot); + if (r < 0) + return r; + + p = filename_escape(context[CONTEXT_PID]); + if (!p) + return -ENOMEM; + + t = filename_escape(context[CONTEXT_TIMESTAMP]); + if (!t) + return -ENOMEM; + + if (asprintf(ret, + "/var/lib/systemd/coredump/core.%s.%s." SD_ID128_FORMAT_STR ".%s.%s000000", + c, + u, + SD_ID128_FORMAT_VAL(boot), + p, + t) < 0) + return -ENOMEM; + + return 0; +} + +static int save_external_coredump( + const char *context[_CONTEXT_MAX], + int input_fd, + char **ret_filename, + int *ret_node_fd, + int *ret_data_fd, + uint64_t *ret_size) { + + _cleanup_free_ char *fn = NULL, *tmp = NULL; + _cleanup_close_ int fd = -1; + uint64_t rlimit, max_size; + struct stat st; + uid_t uid; + int r; + + assert(context); + assert(ret_filename); + assert(ret_node_fd); + assert(ret_data_fd); + assert(ret_size); + + r = parse_uid(context[CONTEXT_UID], &uid); + if (r < 0) + return log_error_errno(r, "Failed to parse UID: %m"); + + r = safe_atou64(context[CONTEXT_RLIMIT], &rlimit); + if (r < 0) + return log_error_errno(r, "Failed to parse resource limit: %s", context[CONTEXT_RLIMIT]); + if (rlimit <= 0) { + /* Is coredumping disabled? Then don't bother saving/processing the coredump */ + log_info("Core Dumping has been disabled for process %s (%s).", context[CONTEXT_PID], context[CONTEXT_COMM]); + return -EBADSLT; + } + + /* Never store more than the process configured, or than we actually shall keep or process */ + max_size = MIN(rlimit, MAX(arg_process_size_max, arg_external_size_max)); + + r = make_filename(context, &fn); + if (r < 0) + return log_error_errno(r, "Failed to determine coredump file name: %m"); + + mkdir_p_label("/var/lib/systemd/coredump", 0755); + + fd = open_tmpfile_linkable(fn, O_RDWR|O_CLOEXEC, &tmp); + if (fd < 0) + return log_error_errno(fd, "Failed to create temporary file for coredump %s: %m", fn); + + r = copy_bytes(input_fd, fd, max_size, false); + if (r == -EFBIG) { + log_error("Coredump of %s (%s) is larger than configured processing limit, refusing.", context[CONTEXT_PID], context[CONTEXT_COMM]); + goto fail; + } else if (IN_SET(r, -EDQUOT, -ENOSPC)) { + log_error("Not enough disk space for coredump of %s (%s), refusing.", context[CONTEXT_PID], context[CONTEXT_COMM]); + goto fail; + } else if (r < 0) { + log_error_errno(r, "Failed to dump coredump to file: %m"); + goto fail; + } + + if (fstat(fd, &st) < 0) { + log_error_errno(errno, "Failed to fstat coredump %s: %m", coredump_tmpfile_name(tmp)); + goto fail; + } + + if (lseek(fd, 0, SEEK_SET) == (off_t) -1) { + log_error_errno(errno, "Failed to seek on %s: %m", coredump_tmpfile_name(tmp)); + goto fail; + } + +#if defined(HAVE_XZ) || defined(HAVE_LZ4) + /* If we will remove the coredump anyway, do not compress. */ + if (maybe_remove_external_coredump(NULL, st.st_size) == 0 + && arg_compress) { + + _cleanup_free_ char *fn_compressed = NULL, *tmp_compressed = NULL; + _cleanup_close_ int fd_compressed = -1; + + fn_compressed = strappend(fn, COMPRESSED_EXT); + if (!fn_compressed) { + log_oom(); + goto uncompressed; + } + + fd_compressed = open_tmpfile_linkable(fn_compressed, O_RDWR|O_CLOEXEC, &tmp_compressed); + if (fd_compressed < 0) { + log_error_errno(fd_compressed, "Failed to create temporary file for coredump %s: %m", fn_compressed); + goto uncompressed; + } + + r = compress_stream(fd, fd_compressed, -1); + if (r < 0) { + log_error_errno(r, "Failed to compress %s: %m", coredump_tmpfile_name(tmp_compressed)); + goto fail_compressed; + } + + r = fix_permissions(fd_compressed, tmp_compressed, fn_compressed, context, uid); + if (r < 0) + goto fail_compressed; + + /* OK, this worked, we can get rid of the uncompressed version now */ + if (tmp) + unlink_noerrno(tmp); + + *ret_filename = fn_compressed; /* compressed */ + *ret_node_fd = fd_compressed; /* compressed */ + *ret_data_fd = fd; /* uncompressed */ + *ret_size = (uint64_t) st.st_size; /* uncompressed */ + + fn_compressed = NULL; + fd = fd_compressed = -1; + + return 0; + + fail_compressed: + if (tmp_compressed) + (void) unlink(tmp_compressed); + } + +uncompressed: +#endif + + r = fix_permissions(fd, tmp, fn, context, uid); + if (r < 0) + goto fail; + + *ret_filename = fn; + *ret_data_fd = fd; + *ret_node_fd = -1; + *ret_size = (uint64_t) st.st_size; + + fn = NULL; + fd = -1; + + return 0; + +fail: + if (tmp) + (void) unlink(tmp); + return r; +} + +static int allocate_journal_field(int fd, size_t size, char **ret, size_t *ret_size) { + _cleanup_free_ char *field = NULL; + ssize_t n; + + assert(fd >= 0); + assert(ret); + assert(ret_size); + + if (lseek(fd, 0, SEEK_SET) == (off_t) -1) + return log_warning_errno(errno, "Failed to seek: %m"); + + field = malloc(9 + size); + if (!field) { + log_warning("Failed to allocate memory for coredump, coredump will not be stored."); + return -ENOMEM; + } + + memcpy(field, "COREDUMP=", 9); + + n = read(fd, field + 9, size); + if (n < 0) + return log_error_errno((int) n, "Failed to read core data: %m"); + if ((size_t) n < size) { + log_error("Core data too short."); + return -EIO; + } + + *ret = field; + *ret_size = size + 9; + + field = NULL; + + return 0; +} + +/* Joins /proc/[pid]/fd/ and /proc/[pid]/fdinfo/ into the following lines: + * 0:/dev/pts/23 + * pos: 0 + * flags: 0100002 + * + * 1:/dev/pts/23 + * pos: 0 + * flags: 0100002 + * + * 2:/dev/pts/23 + * pos: 0 + * flags: 0100002 + * EOF + */ +static int compose_open_fds(pid_t pid, char **open_fds) { + _cleanup_closedir_ DIR *proc_fd_dir = NULL; + _cleanup_close_ int proc_fdinfo_fd = -1; + _cleanup_free_ char *buffer = NULL; + _cleanup_fclose_ FILE *stream = NULL; + const char *fddelim = "", *path; + struct dirent *dent = NULL; + size_t size = 0; + int r = 0; + + assert(pid >= 0); + assert(open_fds != NULL); + + path = procfs_file_alloca(pid, "fd"); + proc_fd_dir = opendir(path); + if (!proc_fd_dir) + return -errno; + + proc_fdinfo_fd = openat(dirfd(proc_fd_dir), "../fdinfo", O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC|O_PATH); + if (proc_fdinfo_fd < 0) + return -errno; + + stream = open_memstream(&buffer, &size); + if (!stream) + return -ENOMEM; + + FOREACH_DIRENT(dent, proc_fd_dir, return -errno) { + _cleanup_fclose_ FILE *fdinfo = NULL; + _cleanup_free_ char *fdname = NULL; + char line[LINE_MAX]; + int fd; + + r = readlinkat_malloc(dirfd(proc_fd_dir), dent->d_name, &fdname); + if (r < 0) + return r; + + fprintf(stream, "%s%s:%s\n", fddelim, dent->d_name, fdname); + fddelim = "\n"; + + /* Use the directory entry from /proc/[pid]/fd with /proc/[pid]/fdinfo */ + fd = openat(proc_fdinfo_fd, dent->d_name, O_NOFOLLOW|O_CLOEXEC|O_RDONLY); + if (fd < 0) + continue; + + fdinfo = fdopen(fd, "re"); + if (fdinfo == NULL) { + close(fd); + continue; + } + + FOREACH_LINE(line, fdinfo, break) { + fputs(line, stream); + if (!endswith(line, "\n")) + fputc('\n', stream); + } + } + + errno = 0; + stream = safe_fclose(stream); + + if (errno > 0) + return -errno; + + *open_fds = buffer; + buffer = NULL; + + return 0; +} + +static int change_uid_gid(const char *context[]) { + uid_t uid; + gid_t gid; + int r; + + r = parse_uid(context[CONTEXT_UID], &uid); + if (r < 0) + return r; + + if (uid <= SYSTEM_UID_MAX) { + const char *user = "systemd-coredump"; + + r = get_user_creds(&user, &uid, &gid, NULL, NULL); + if (r < 0) { + log_warning_errno(r, "Cannot resolve %s user. Proceeding to dump core as root: %m", user); + uid = gid = 0; + } + } else { + r = parse_gid(context[CONTEXT_GID], &gid); + if (r < 0) + return r; + } + + return drop_privileges(uid, gid, 0); +} + +static int submit_coredump( + const char *context[_CONTEXT_MAX], + struct iovec *iovec, + size_t n_iovec_allocated, + size_t n_iovec, + int input_fd) { + + _cleanup_close_ int coredump_fd = -1, coredump_node_fd = -1; + _cleanup_free_ char *core_message = NULL, *filename = NULL, *coredump_data = NULL; + uint64_t coredump_size; + int r; + + assert(context); + assert(iovec); + assert(n_iovec_allocated >= n_iovec + 3); + assert(input_fd >= 0); + + /* Vacuum before we write anything again */ + (void) coredump_vacuum(-1, arg_keep_free, arg_max_use); + + /* Always stream the coredump to disk, if that's possible */ + r = save_external_coredump(context, input_fd, &filename, &coredump_node_fd, &coredump_fd, &coredump_size); + if (r < 0) + /* Skip whole core dumping part */ + goto log; + + /* If we don't want to keep the coredump on disk, remove it now, as later on we will lack the privileges for + * it. However, we keep the fd to it, so that we can still process it and log it. */ + r = maybe_remove_external_coredump(filename, coredump_size); + if (r < 0) + return r; + if (r == 0) { + const char *coredump_filename; + + coredump_filename = strjoina("COREDUMP_FILENAME=", filename); + IOVEC_SET_STRING(iovec[n_iovec++], coredump_filename); + } + + /* Vacuum again, but exclude the coredump we just created */ + (void) coredump_vacuum(coredump_node_fd >= 0 ? coredump_node_fd : coredump_fd, arg_keep_free, arg_max_use); + + /* Now, let's drop privileges to become the user who owns the segfaulted process and allocate the coredump + * memory under the user's uid. This also ensures that the credentials journald will see are the ones of the + * coredumping user, thus making sure the user gets access to the core dump. Let's also get rid of all + * capabilities, if we run as root, we won't need them anymore. */ + r = change_uid_gid(context); + if (r < 0) + return log_error_errno(r, "Failed to drop privileges: %m"); + +#ifdef HAVE_ELFUTILS + /* Try to get a strack trace if we can */ + if (coredump_size <= arg_process_size_max) { + _cleanup_free_ char *stacktrace = NULL; + + r = coredump_make_stack_trace(coredump_fd, context[CONTEXT_EXE], &stacktrace); + if (r >= 0) + core_message = strjoin("MESSAGE=Process ", context[CONTEXT_PID], " (", context[CONTEXT_COMM], ") of user ", context[CONTEXT_UID], " dumped core.\n\n", stacktrace, NULL); + else if (r == -EINVAL) + log_warning("Failed to generate stack trace: %s", dwfl_errmsg(dwfl_errno())); + else + log_warning_errno(r, "Failed to generate stack trace: %m"); + } + + if (!core_message) +#endif +log: + core_message = strjoin("MESSAGE=Process ", context[CONTEXT_PID], " (", context[CONTEXT_COMM], ") of user ", context[CONTEXT_UID], " dumped core.", NULL); + if (core_message) + IOVEC_SET_STRING(iovec[n_iovec++], core_message); + + /* Optionally store the entire coredump in the journal */ + if (IN_SET(arg_storage, COREDUMP_STORAGE_JOURNAL, COREDUMP_STORAGE_BOTH) && + coredump_size <= arg_journal_size_max) { + size_t sz = 0; + + /* Store the coredump itself in the journal */ + + r = allocate_journal_field(coredump_fd, (size_t) coredump_size, &coredump_data, &sz); + if (r >= 0) { + iovec[n_iovec].iov_base = coredump_data; + iovec[n_iovec].iov_len = sz; + n_iovec++; + } + } + + assert(n_iovec <= n_iovec_allocated); + + r = sd_journal_sendv(iovec, n_iovec); + if (r < 0) + return log_error_errno(r, "Failed to log coredump: %m"); + + return 0; +} + +static void map_context_fields(const struct iovec *iovec, const char *context[]) { + + static const char * const context_field_names[_CONTEXT_MAX] = { + [CONTEXT_PID] = "COREDUMP_PID=", + [CONTEXT_UID] = "COREDUMP_UID=", + [CONTEXT_GID] = "COREDUMP_GID=", + [CONTEXT_SIGNAL] = "COREDUMP_SIGNAL=", + [CONTEXT_TIMESTAMP] = "COREDUMP_TIMESTAMP=", + [CONTEXT_COMM] = "COREDUMP_COMM=", + [CONTEXT_EXE] = "COREDUMP_EXE=", + [CONTEXT_RLIMIT] = "COREDUMP_RLIMIT=", + }; + + unsigned i; + + assert(iovec); + assert(context); + + for (i = 0; i < _CONTEXT_MAX; i++) { + size_t l; + + l = strlen(context_field_names[i]); + if (iovec->iov_len < l) + continue; + + if (memcmp(iovec->iov_base, context_field_names[i], l) != 0) + continue; + + /* Note that these strings are NUL terminated, because we made sure that a trailing NUL byte is in the + * buffer, though not included in the iov_len count. (see below) */ + context[i] = (char*) iovec->iov_base + l; + break; + } +} + +static int process_socket(int fd) { + _cleanup_close_ int coredump_fd = -1; + struct iovec *iovec = NULL; + size_t n_iovec = 0, n_iovec_allocated = 0, i; + const char *context[_CONTEXT_MAX] = {}; + int r; + + assert(fd >= 0); + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + for (;;) { + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + struct msghdr mh = { + .msg_control = &control, + .msg_controllen = sizeof(control), + .msg_iovlen = 1, + }; + ssize_t n; + ssize_t l; + + if (!GREEDY_REALLOC(iovec, n_iovec_allocated, n_iovec + 3)) { + r = log_oom(); + goto finish; + } + + l = next_datagram_size_fd(fd); + if (l < 0) { + r = log_error_errno(l, "Failed to determine datagram size to read: %m"); + goto finish; + } + + assert(l >= 0); + + iovec[n_iovec].iov_len = l; + iovec[n_iovec].iov_base = malloc(l + 1); + + if (!iovec[n_iovec].iov_base) { + r = log_oom(); + goto finish; + } + + mh.msg_iov = iovec + n_iovec; + + n = recvmsg(fd, &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); + if (n < 0) { + free(iovec[n_iovec].iov_base); + r = log_error_errno(errno, "Failed to receive datagram: %m"); + goto finish; + } + + if (n == 0) { + struct cmsghdr *cmsg, *found = NULL; + /* The final zero-length datagram carries the file descriptor and tells us that we're done. */ + + free(iovec[n_iovec].iov_base); + + CMSG_FOREACH(cmsg, &mh) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS && + cmsg->cmsg_len == CMSG_LEN(sizeof(int))) { + assert(!found); + found = cmsg; + } + } + + if (!found) { + log_error("Coredump file descriptor missing."); + r = -EBADMSG; + goto finish; + } + + assert(coredump_fd < 0); + coredump_fd = *(int*) CMSG_DATA(found); + break; + } + + /* Add trailing NUL byte, in case these are strings */ + ((char*) iovec[n_iovec].iov_base)[n] = 0; + iovec[n_iovec].iov_len = (size_t) n; + + cmsg_close_all(&mh); + map_context_fields(iovec + n_iovec, context); + n_iovec++; + } + + if (!GREEDY_REALLOC(iovec, n_iovec_allocated, n_iovec + 3)) { + r = log_oom(); + goto finish; + } + + /* Make sure we we got all data we really need */ + assert(context[CONTEXT_PID]); + assert(context[CONTEXT_UID]); + assert(context[CONTEXT_GID]); + assert(context[CONTEXT_SIGNAL]); + assert(context[CONTEXT_TIMESTAMP]); + assert(context[CONTEXT_RLIMIT]); + assert(context[CONTEXT_COMM]); + assert(coredump_fd >= 0); + + r = submit_coredump(context, iovec, n_iovec_allocated, n_iovec, coredump_fd); + +finish: + for (i = 0; i < n_iovec; i++) + free(iovec[i].iov_base); + free(iovec); + + return r; +} + +static int send_iovec(const struct iovec iovec[], size_t n_iovec, int input_fd) { + + static const union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/coredump", + }; + _cleanup_close_ int fd = -1; + size_t i; + int r; + + assert(iovec || n_iovec <= 0); + assert(input_fd >= 0); + + fd = socket(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0); + if (fd < 0) + return log_error_errno(errno, "Failed to create coredump socket: %m"); + + if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) + return log_error_errno(errno, "Failed to connect to coredump service: %m"); + + for (i = 0; i < n_iovec; i++) { + ssize_t n; + assert(iovec[i].iov_len > 0); + + n = send(fd, iovec[i].iov_base, iovec[i].iov_len, MSG_NOSIGNAL); + if (n < 0) + return log_error_errno(errno, "Failed to send coredump datagram: %m"); + } + + r = send_one_fd(fd, input_fd, 0); + if (r < 0) + return log_error_errno(r, "Failed to send coredump fd: %m"); + + return 0; +} + +static int process_journald_crash(const char *context[], int input_fd) { + _cleanup_close_ int coredump_fd = -1, coredump_node_fd = -1; + _cleanup_free_ char *filename = NULL; + uint64_t coredump_size; + int r; + + assert(context); + assert(input_fd >= 0); + + /* If we are journald, we cut things short, don't write to the journal, but still create a coredump. */ + + if (arg_storage != COREDUMP_STORAGE_NONE) + arg_storage = COREDUMP_STORAGE_EXTERNAL; + + r = save_external_coredump(context, input_fd, &filename, &coredump_node_fd, &coredump_fd, &coredump_size); + if (r < 0) + return r; + + r = maybe_remove_external_coredump(filename, coredump_size); + if (r < 0) + return r; + + log_info("Detected coredump of the journal daemon itself, diverted to %s.", filename); + return 0; +} + +static int process_kernel(int argc, char* argv[]) { + + /* The small core field we allocate on the stack, to keep things simple */ + char + *core_pid = NULL, *core_uid = NULL, *core_gid = NULL, *core_signal = NULL, + *core_session = NULL, *core_exe = NULL, *core_comm = NULL, *core_cmdline = NULL, + *core_cgroup = NULL, *core_cwd = NULL, *core_root = NULL, *core_unit = NULL, + *core_user_unit = NULL, *core_slice = NULL, *core_timestamp = NULL, *core_rlimit = NULL; + + /* The larger ones we allocate on the heap */ + _cleanup_free_ char + *core_owner_uid = NULL, *core_open_fds = NULL, *core_proc_status = NULL, + *core_proc_maps = NULL, *core_proc_limits = NULL, *core_proc_cgroup = NULL, *core_environ = NULL; + + _cleanup_free_ char *exe = NULL, *comm = NULL; + const char *context[_CONTEXT_MAX]; + struct iovec iovec[25]; + size_t n_iovec = 0; + uid_t owner_uid; + const char *p; + pid_t pid; + char *t; + int r; + + if (argc < CONTEXT_COMM + 1) { + log_error("Not enough arguments passed from kernel (%i, expected %i).", argc - 1, CONTEXT_COMM + 1 - 1); + return -EINVAL; + } + + r = parse_pid(argv[CONTEXT_PID + 1], &pid); + if (r < 0) + return log_error_errno(r, "Failed to parse PID."); + + r = get_process_comm(pid, &comm); + if (r < 0) { + log_warning_errno(r, "Failed to get COMM, falling back to the command line: %m"); + comm = strv_join(argv + CONTEXT_COMM + 1, " "); + if (!comm) + return log_oom(); + } + + r = get_process_exe(pid, &exe); + if (r < 0) + log_warning_errno(r, "Failed to get EXE, ignoring: %m"); + + context[CONTEXT_PID] = argv[CONTEXT_PID + 1]; + context[CONTEXT_UID] = argv[CONTEXT_UID + 1]; + context[CONTEXT_GID] = argv[CONTEXT_GID + 1]; + context[CONTEXT_SIGNAL] = argv[CONTEXT_SIGNAL + 1]; + context[CONTEXT_TIMESTAMP] = argv[CONTEXT_TIMESTAMP + 1]; + context[CONTEXT_RLIMIT] = argv[CONTEXT_RLIMIT + 1]; + context[CONTEXT_COMM] = comm; + context[CONTEXT_EXE] = exe; + + if (cg_pid_get_unit(pid, &t) >= 0) { + + if (streq(t, SPECIAL_JOURNALD_SERVICE)) { + free(t); + return process_journald_crash(context, STDIN_FILENO); + } + + core_unit = strjoina("COREDUMP_UNIT=", t); + free(t); + + IOVEC_SET_STRING(iovec[n_iovec++], core_unit); + } + + /* OK, now we know it's not the journal, hence we can make use of it now. */ + log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); + log_open(); + + if (cg_pid_get_user_unit(pid, &t) >= 0) { + core_user_unit = strjoina("COREDUMP_USER_UNIT=", t); + free(t); + + IOVEC_SET_STRING(iovec[n_iovec++], core_user_unit); + } + + core_pid = strjoina("COREDUMP_PID=", context[CONTEXT_PID]); + IOVEC_SET_STRING(iovec[n_iovec++], core_pid); + + core_uid = strjoina("COREDUMP_UID=", context[CONTEXT_UID]); + IOVEC_SET_STRING(iovec[n_iovec++], core_uid); + + core_gid = strjoina("COREDUMP_GID=", context[CONTEXT_GID]); + IOVEC_SET_STRING(iovec[n_iovec++], core_gid); + + core_signal = strjoina("COREDUMP_SIGNAL=", context[CONTEXT_SIGNAL]); + IOVEC_SET_STRING(iovec[n_iovec++], core_signal); + + core_rlimit = strjoina("COREDUMP_RLIMIT=", context[CONTEXT_RLIMIT]); + IOVEC_SET_STRING(iovec[n_iovec++], core_rlimit); + + if (sd_pid_get_session(pid, &t) >= 0) { + core_session = strjoina("COREDUMP_SESSION=", t); + free(t); + + IOVEC_SET_STRING(iovec[n_iovec++], core_session); + } + + if (sd_pid_get_owner_uid(pid, &owner_uid) >= 0) { + r = asprintf(&core_owner_uid, "COREDUMP_OWNER_UID=" UID_FMT, owner_uid); + if (r > 0) + IOVEC_SET_STRING(iovec[n_iovec++], core_owner_uid); + } + + if (sd_pid_get_slice(pid, &t) >= 0) { + core_slice = strjoina("COREDUMP_SLICE=", t); + free(t); + + IOVEC_SET_STRING(iovec[n_iovec++], core_slice); + } + + if (comm) { + core_comm = strjoina("COREDUMP_COMM=", comm); + IOVEC_SET_STRING(iovec[n_iovec++], core_comm); + } + + if (exe) { + core_exe = strjoina("COREDUMP_EXE=", exe); + IOVEC_SET_STRING(iovec[n_iovec++], core_exe); + } + + if (get_process_cmdline(pid, 0, false, &t) >= 0) { + core_cmdline = strjoina("COREDUMP_CMDLINE=", t); + free(t); + + IOVEC_SET_STRING(iovec[n_iovec++], core_cmdline); + } + + if (cg_pid_get_path_shifted(pid, NULL, &t) >= 0) { + core_cgroup = strjoina("COREDUMP_CGROUP=", t); + free(t); + + IOVEC_SET_STRING(iovec[n_iovec++], core_cgroup); + } + + if (compose_open_fds(pid, &t) >= 0) { + core_open_fds = strappend("COREDUMP_OPEN_FDS=", t); + free(t); + + if (core_open_fds) + IOVEC_SET_STRING(iovec[n_iovec++], core_open_fds); + } + + p = procfs_file_alloca(pid, "status"); + if (read_full_file(p, &t, NULL) >= 0) { + core_proc_status = strappend("COREDUMP_PROC_STATUS=", t); + free(t); + + if (core_proc_status) + IOVEC_SET_STRING(iovec[n_iovec++], core_proc_status); + } + + p = procfs_file_alloca(pid, "maps"); + if (read_full_file(p, &t, NULL) >= 0) { + core_proc_maps = strappend("COREDUMP_PROC_MAPS=", t); + free(t); + + if (core_proc_maps) + IOVEC_SET_STRING(iovec[n_iovec++], core_proc_maps); + } + + p = procfs_file_alloca(pid, "limits"); + if (read_full_file(p, &t, NULL) >= 0) { + core_proc_limits = strappend("COREDUMP_PROC_LIMITS=", t); + free(t); + + if (core_proc_limits) + IOVEC_SET_STRING(iovec[n_iovec++], core_proc_limits); + } + + p = procfs_file_alloca(pid, "cgroup"); + if (read_full_file(p, &t, NULL) >=0) { + core_proc_cgroup = strappend("COREDUMP_PROC_CGROUP=", t); + free(t); + + if (core_proc_cgroup) + IOVEC_SET_STRING(iovec[n_iovec++], core_proc_cgroup); + } + + if (get_process_cwd(pid, &t) >= 0) { + core_cwd = strjoina("COREDUMP_CWD=", t); + free(t); + + IOVEC_SET_STRING(iovec[n_iovec++], core_cwd); + } + + if (get_process_root(pid, &t) >= 0) { + core_root = strjoina("COREDUMP_ROOT=", t); + free(t); + + IOVEC_SET_STRING(iovec[n_iovec++], core_root); + } + + if (get_process_environ(pid, &t) >= 0) { + core_environ = strappend("COREDUMP_ENVIRON=", t); + free(t); + + if (core_environ) + IOVEC_SET_STRING(iovec[n_iovec++], core_environ); + } + + core_timestamp = strjoina("COREDUMP_TIMESTAMP=", context[CONTEXT_TIMESTAMP], "000000"); + IOVEC_SET_STRING(iovec[n_iovec++], core_timestamp); + + IOVEC_SET_STRING(iovec[n_iovec++], "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1"); + + assert_cc(2 == LOG_CRIT); + IOVEC_SET_STRING(iovec[n_iovec++], "PRIORITY=2"); + + assert(n_iovec <= ELEMENTSOF(iovec)); + + return send_iovec(iovec, n_iovec, STDIN_FILENO); +} + +int main(int argc, char *argv[]) { + int r; + + /* First, log to a safe place, since we don't know what crashed and it might be journald which we'd rather not + * log to then. */ + + log_set_target(LOG_TARGET_KMSG); + log_open(); + + /* Make sure we never enter a loop */ + (void) prctl(PR_SET_DUMPABLE, 0); + + /* Ignore all parse errors */ + (void) parse_config(); + + log_debug("Selected storage '%s'.", coredump_storage_to_string(arg_storage)); + log_debug("Selected compression %s.", yes_no(arg_compress)); + + r = sd_listen_fds(false); + if (r < 0) { + log_error_errno(r, "Failed to determine number of file descriptor: %m"); + goto finish; + } + + /* If we got an fd passed, we are running in coredumpd mode. Otherwise we are invoked from the kernel as + * coredump handler */ + if (r == 0) + r = process_kernel(argc, argv); + else if (r == 1) + r = process_socket(SD_LISTEN_FDS_START); + else { + log_error("Received unexpected number of file descriptors."); + r = -EINVAL; + } + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-coredump/systemd-coredump/coredump.conf b/src/grp-coredump/systemd-coredump/coredump.conf new file mode 100644 index 0000000000..c2f0643e03 --- /dev/null +++ b/src/grp-coredump/systemd-coredump/coredump.conf @@ -0,0 +1,21 @@ +# This file is part of systemd. +# +# 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. +# +# Entries in this file show the compile time defaults. +# You can change settings by editing this file. +# Defaults can be restored by simply deleting this file. +# +# See coredump.conf(5) for details. + +[Coredump] +#Storage=external +#Compress=yes +#ProcessSizeMax=2G +#ExternalSizeMax=2G +#JournalSizeMax=767M +#MaxUse= +#KeepFree= diff --git a/src/grp-coredump/systemd-coredump/stacktrace.c b/src/grp-coredump/systemd-coredump/stacktrace.c new file mode 100644 index 0000000000..cc4dad9465 --- /dev/null +++ b/src/grp-coredump/systemd-coredump/stacktrace.c @@ -0,0 +1,200 @@ +/*** + 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 + +#include "alloc-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "macro.h" +#include "stacktrace.h" +#include "string-util.h" +#include "util.h" + +#define FRAMES_MAX 64 +#define THREADS_MAX 64 + +struct stack_context { + FILE *f; + Dwfl *dwfl; + Elf *elf; + unsigned n_thread; + unsigned n_frame; +}; + +static int frame_callback(Dwfl_Frame *frame, void *userdata) { + struct stack_context *c = userdata; + Dwarf_Addr pc, pc_adjusted, bias = 0; + _cleanup_free_ Dwarf_Die *scopes = NULL; + const char *fname = NULL, *symbol = NULL; + Dwfl_Module *module; + bool is_activation; + + assert(frame); + assert(c); + + if (c->n_frame >= FRAMES_MAX) + return DWARF_CB_ABORT; + + if (!dwfl_frame_pc(frame, &pc, &is_activation)) + return DWARF_CB_ABORT; + + pc_adjusted = pc - (is_activation ? 0 : 1); + + module = dwfl_addrmodule(c->dwfl, pc_adjusted); + if (module) { + Dwarf_Die *s, *cudie; + int n; + + cudie = dwfl_module_addrdie(module, pc_adjusted, &bias); + if (cudie) { + n = dwarf_getscopes(cudie, pc_adjusted - bias, &scopes); + for (s = scopes; s < scopes + n; s++) { + if (IN_SET(dwarf_tag(s), DW_TAG_subprogram, DW_TAG_inlined_subroutine, DW_TAG_entry_point)) { + Dwarf_Attribute *a, space; + + a = dwarf_attr_integrate(s, DW_AT_MIPS_linkage_name, &space); + if (!a) + a = dwarf_attr_integrate(s, DW_AT_linkage_name, &space); + if (a) + symbol = dwarf_formstring(a); + if (!symbol) + symbol = dwarf_diename(s); + + if (symbol) + break; + } + } + } + + if (!symbol) + symbol = dwfl_module_addrname(module, pc_adjusted); + + fname = dwfl_module_info(module, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + } + + fprintf(c->f, "#%-2u 0x%016" PRIx64 " %s (%s)\n", c->n_frame, (uint64_t) pc, strna(symbol), strna(fname)); + c->n_frame++; + + return DWARF_CB_OK; +} + +static int thread_callback(Dwfl_Thread *thread, void *userdata) { + struct stack_context *c = userdata; + pid_t tid; + + assert(thread); + assert(c); + + if (c->n_thread >= THREADS_MAX) + return DWARF_CB_ABORT; + + if (c->n_thread != 0) + fputc('\n', c->f); + + c->n_frame = 0; + + tid = dwfl_thread_tid(thread); + fprintf(c->f, "Stack trace of thread " PID_FMT ":\n", tid); + + if (dwfl_thread_getframes(thread, frame_callback, c) < 0) + return DWARF_CB_ABORT; + + c->n_thread++; + + return DWARF_CB_OK; +} + +int coredump_make_stack_trace(int fd, const char *executable, char **ret) { + + static const Dwfl_Callbacks callbacks = { + .find_elf = dwfl_build_id_find_elf, + .find_debuginfo = dwfl_standard_find_debuginfo, + }; + + struct stack_context c = {}; + char *buf = NULL; + size_t sz = 0; + int r; + + assert(fd >= 0); + assert(ret); + + if (lseek(fd, 0, SEEK_SET) == (off_t) -1) + return -errno; + + c.f = open_memstream(&buf, &sz); + if (!c.f) + return -ENOMEM; + + elf_version(EV_CURRENT); + + c.elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); + if (!c.elf) { + r = -EINVAL; + goto finish; + } + + c.dwfl = dwfl_begin(&callbacks); + if (!c.dwfl) { + r = -EINVAL; + goto finish; + } + + if (dwfl_core_file_report(c.dwfl, c.elf, executable) < 0) { + r = -EINVAL; + goto finish; + } + + if (dwfl_report_end(c.dwfl, NULL, NULL) != 0) { + r = -EINVAL; + goto finish; + } + + if (dwfl_core_file_attach(c.dwfl, c.elf) < 0) { + r = -EINVAL; + goto finish; + } + + if (dwfl_getthreads(c.dwfl, thread_callback, &c) < 0) { + r = -EINVAL; + goto finish; + } + + c.f = safe_fclose(c.f); + + *ret = buf; + buf = NULL; + + r = 0; + +finish: + if (c.dwfl) + dwfl_end(c.dwfl); + + if (c.elf) + elf_end(c.elf); + + safe_fclose(c.f); + + free(buf); + + return r; +} diff --git a/src/grp-coredump/systemd-coredump/stacktrace.h b/src/grp-coredump/systemd-coredump/stacktrace.h new file mode 100644 index 0000000000..15e9c04465 --- /dev/null +++ b/src/grp-coredump/systemd-coredump/stacktrace.h @@ -0,0 +1,22 @@ +#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 . +***/ + +int coredump_make_stack_trace(int fd, const char *executable, char **ret); diff --git a/src/grp-coredump/systemd-coredump/test-coredump-vacuum.c b/src/grp-coredump/systemd-coredump/test-coredump-vacuum.c new file mode 100644 index 0000000000..70a57f183f --- /dev/null +++ b/src/grp-coredump/systemd-coredump/test-coredump-vacuum.c @@ -0,0 +1,30 @@ +/*** + This file is part of systemd. + + Copyright 2012 Zbigniew Jędrzejewski-Szmek + + 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 "coredump-vacuum.h" + +int main(int argc, char *argv[]) { + + if (coredump_vacuum(-1, (uint64_t) -1, 70 * 1024) < 0) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} diff --git a/src/grp-journal-remote/.gitignore b/src/grp-journal-remote/.gitignore new file mode 100644 index 0000000000..06847b65d4 --- /dev/null +++ b/src/grp-journal-remote/.gitignore @@ -0,0 +1,2 @@ +/journal-remote.conf +/journal-upload.conf diff --git a/src/grp-journal-remote/browse.html b/src/grp-journal-remote/browse.html new file mode 100644 index 0000000000..32848c7673 --- /dev/null +++ b/src/grp-journal-remote/browse.html @@ -0,0 +1,544 @@ + + + + Journal + + + + + + + +

+ +
+
+
+
+
+
+ +
+ +      + Only current boot +
+ +
+ +
+ +
+ + + + +      + + +
+ +
+ g: First Page      + ←, k, BACKSPACE: Previous Page      + →, j, SPACE: Next Page      + G: Last Page      + +: More entries      + -: Fewer entries +
+ + + + diff --git a/src/grp-journal-remote/log-generator.py b/src/grp-journal-remote/log-generator.py new file mode 100755 index 0000000000..fd6964e758 --- /dev/null +++ b/src/grp-journal-remote/log-generator.py @@ -0,0 +1,76 @@ +#!/usr/bin/python +from __future__ import print_function +import sys +import argparse + +PARSER = argparse.ArgumentParser() +PARSER.add_argument('n', type=int) +PARSER.add_argument('--dots', action='store_true') +PARSER.add_argument('--data-size', type=int, default=4000) +PARSER.add_argument('--data-type', choices={'random', 'simple'}) +OPTIONS = PARSER.parse_args() + +template = """\ +__CURSOR=s=6863c726210b4560b7048889d8ada5c5;i=3e931;b=f446871715504074bf7049ef0718fa93;m={m:x};t=4fd05c +__REALTIME_TIMESTAMP={realtime_ts} +__MONOTONIC_TIMESTAMP={monotonic_ts} +_BOOT_ID=f446871715504074bf7049ef0718fa93 +_TRANSPORT=syslog +PRIORITY={priority} +SYSLOG_FACILITY={facility} +SYSLOG_IDENTIFIER=/USR/SBIN/CRON +MESSAGE={message} +_UID=0 +_GID=0 +_MACHINE_ID=69121ca41d12c1b69a7960174c27b618 +_HOSTNAME=hostname +SYSLOG_PID=25721 +_PID=25721 +_SOURCE_REALTIME_TIMESTAMP={source_realtime_ts} +DATA={data} +""" + +m = 0x198603b12d7 +realtime_ts = 1404101101501873 +monotonic_ts = 1753961140951 +source_realtime_ts = 1404101101483516 +priority = 3 +facility = 6 + +src = open('/dev/urandom', 'rb') + +bytes = 0 +counter = 0 + +for i in range(OPTIONS.n): + message = repr(src.read(2000)) + if OPTIONS.data_type == 'random': + data = repr(src.read(OPTIONS.data_size)) + else: + # keep the pattern non-repeating so we get a different blob every time + data = '{:0{}}'.format(counter, OPTIONS.data_size) + counter += 1 + + entry = template.format(m=m, + realtime_ts=realtime_ts, + monotonic_ts=monotonic_ts, + source_realtime_ts=source_realtime_ts, + priority=priority, + facility=facility, + message=message, + data=data) + m += 1 + realtime_ts += 1 + monotonic_ts += 1 + source_realtime_ts += 1 + + bytes += len(entry) + + print(entry) + + if OPTIONS.dots: + print('.', file=sys.stderr, end='', flush=True) + +if OPTIONS.dots: + print(file=sys.stderr) +print('Wrote {} bytes'.format(bytes), file=sys.stderr) diff --git a/src/grp-journal-remote/microhttpd-util.c b/src/grp-journal-remote/microhttpd-util.c new file mode 100644 index 0000000000..c65c43186f --- /dev/null +++ b/src/grp-journal-remote/microhttpd-util.c @@ -0,0 +1,327 @@ +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + Copyright 2012 Zbigniew Jędrzejewski-Szmek + + 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 +#include + +#ifdef HAVE_GNUTLS +#include +#include +#endif + +#include "alloc-util.h" +#include "log.h" +#include "macro.h" +#include "microhttpd-util.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +void microhttpd_logger(void *arg, const char *fmt, va_list ap) { + char *f; + + f = strjoina("microhttpd: ", fmt); + + DISABLE_WARNING_FORMAT_NONLITERAL; + log_internalv(LOG_INFO, 0, NULL, 0, NULL, f, ap); + REENABLE_WARNING; +} + + +static int mhd_respond_internal(struct MHD_Connection *connection, + enum MHD_RequestTerminationCode code, + char *buffer, + size_t size, + enum MHD_ResponseMemoryMode mode) { + struct MHD_Response *response; + int r; + + assert(connection); + + response = MHD_create_response_from_buffer(size, buffer, mode); + if (!response) + return MHD_NO; + + log_debug("Queing response %u: %s", code, buffer); + MHD_add_response_header(response, "Content-Type", "text/plain"); + r = MHD_queue_response(connection, code, response); + MHD_destroy_response(response); + + return r; +} + +int mhd_respond(struct MHD_Connection *connection, + enum MHD_RequestTerminationCode code, + const char *message) { + + return mhd_respond_internal(connection, code, + (char*) message, strlen(message), + MHD_RESPMEM_PERSISTENT); +} + +int mhd_respond_oom(struct MHD_Connection *connection) { + return mhd_respond(connection, MHD_HTTP_SERVICE_UNAVAILABLE, "Out of memory.\n"); +} + +int mhd_respondf(struct MHD_Connection *connection, + enum MHD_RequestTerminationCode code, + const char *format, ...) { + + char *m; + int r; + va_list ap; + + assert(connection); + assert(format); + + va_start(ap, format); + r = vasprintf(&m, format, ap); + va_end(ap); + + if (r < 0) + return respond_oom(connection); + + return mhd_respond_internal(connection, code, m, r, MHD_RESPMEM_MUST_FREE); +} + +#ifdef HAVE_GNUTLS + +static struct { + const char *const names[4]; + int level; + bool enabled; +} gnutls_log_map[] = { + { {"0"}, LOG_DEBUG }, + { {"1", "audit"}, LOG_WARNING, true}, /* gnutls session audit */ + { {"2", "assert"}, LOG_DEBUG }, /* gnutls assert log */ + { {"3", "hsk", "ext"}, LOG_DEBUG }, /* gnutls handshake log */ + { {"4", "rec"}, LOG_DEBUG }, /* gnutls record log */ + { {"5", "dtls"}, LOG_DEBUG }, /* gnutls DTLS log */ + { {"6", "buf"}, LOG_DEBUG }, + { {"7", "write", "read"}, LOG_DEBUG }, + { {"8"}, LOG_DEBUG }, + { {"9", "enc", "int"}, LOG_DEBUG }, +}; + +static void log_func_gnutls(int level, const char *message) { + assert_se(message); + + if (0 <= level && level < (int) ELEMENTSOF(gnutls_log_map)) { + if (gnutls_log_map[level].enabled) + log_internal(gnutls_log_map[level].level, 0, NULL, 0, NULL, "gnutls %d/%s: %s", level, gnutls_log_map[level].names[1], message); + } else { + log_debug("Received GNUTLS message with unknown level %d.", level); + log_internal(LOG_DEBUG, 0, NULL, 0, NULL, "gnutls: %s", message); + } +} + +static void log_reset_gnutls_level(void) { + int i; + + for (i = ELEMENTSOF(gnutls_log_map) - 1; i >= 0; i--) + if (gnutls_log_map[i].enabled) { + log_debug("Setting gnutls log level to %d", i); + gnutls_global_set_log_level(i); + break; + } +} + +static int log_enable_gnutls_category(const char *cat) { + unsigned i; + + if (streq(cat, "all")) { + for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++) + gnutls_log_map[i].enabled = true; + log_reset_gnutls_level(); + return 0; + } else + for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++) + if (strv_contains((char**)gnutls_log_map[i].names, cat)) { + gnutls_log_map[i].enabled = true; + log_reset_gnutls_level(); + return 0; + } + log_error("No such log category: %s", cat); + return -EINVAL; +} + +int setup_gnutls_logger(char **categories) { + char **cat; + int r; + + gnutls_global_set_log_function(log_func_gnutls); + + if (categories) { + STRV_FOREACH(cat, categories) { + r = log_enable_gnutls_category(*cat); + if (r < 0) + return r; + } + } else + log_reset_gnutls_level(); + + return 0; +} + +static int verify_cert_authorized(gnutls_session_t session) { + unsigned status; + gnutls_certificate_type_t type; + gnutls_datum_t out; + int r; + + r = gnutls_certificate_verify_peers2(session, &status); + if (r < 0) + return log_error_errno(r, "gnutls_certificate_verify_peers2 failed: %m"); + + type = gnutls_certificate_type_get(session); + r = gnutls_certificate_verification_status_print(status, type, &out, 0); + if (r < 0) + return log_error_errno(r, "gnutls_certificate_verification_status_print failed: %m"); + + log_debug("Certificate status: %s", out.data); + gnutls_free(out.data); + + return status == 0 ? 0 : -EPERM; +} + +static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_cert) { + const gnutls_datum_t *pcert; + unsigned listsize; + gnutls_x509_crt_t cert; + int r; + + assert(session); + assert(client_cert); + + pcert = gnutls_certificate_get_peers(session, &listsize); + if (!pcert || !listsize) { + log_error("Failed to retrieve certificate chain"); + return -EINVAL; + } + + r = gnutls_x509_crt_init(&cert); + if (r < 0) { + log_error("Failed to initialize client certificate"); + return r; + } + + /* Note that by passing values between 0 and listsize here, you + can get access to the CA's certs */ + r = gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER); + if (r < 0) { + log_error("Failed to import client certificate"); + gnutls_x509_crt_deinit(cert); + return r; + } + + *client_cert = cert; + return 0; +} + +static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) { + size_t len = 0; + int r; + + assert(buf); + assert(*buf == NULL); + + r = gnutls_x509_crt_get_dn(client_cert, NULL, &len); + if (r != GNUTLS_E_SHORT_MEMORY_BUFFER) { + log_error("gnutls_x509_crt_get_dn failed"); + return r; + } + + *buf = malloc(len); + if (!*buf) + return log_oom(); + + gnutls_x509_crt_get_dn(client_cert, *buf, &len); + return 0; +} + +static inline void gnutls_x509_crt_deinitp(gnutls_x509_crt_t *p) { + gnutls_x509_crt_deinit(*p); +} + +int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) { + const union MHD_ConnectionInfo *ci; + gnutls_session_t session; + _cleanup_(gnutls_x509_crt_deinitp) gnutls_x509_crt_t client_cert = NULL; + _cleanup_free_ char *buf = NULL; + int r; + + assert(connection); + assert(code); + + *code = 0; + + ci = MHD_get_connection_info(connection, + MHD_CONNECTION_INFO_GNUTLS_SESSION); + if (!ci) { + log_error("MHD_get_connection_info failed: session is unencrypted"); + *code = mhd_respond(connection, MHD_HTTP_FORBIDDEN, + "Encrypted connection is required"); + return -EPERM; + } + session = ci->tls_session; + assert(session); + + r = get_client_cert(session, &client_cert); + if (r < 0) { + *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED, + "Authorization through certificate is required"); + return -EPERM; + } + + r = get_auth_dn(client_cert, &buf); + if (r < 0) { + *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED, + "Failed to determine distinguished name from certificate"); + return -EPERM; + } + + log_debug("Connection from %s", buf); + + if (hostname) { + *hostname = buf; + buf = NULL; + } + + r = verify_cert_authorized(session); + if (r < 0) { + log_warning("Client is not authorized"); + *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED, + "Client certificate not signed by recognized authority"); + } + return r; +} + +#else +int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) { + return -EPERM; +} + +int setup_gnutls_logger(char **categories) { + if (categories) + log_notice("Ignoring specified gnutls logging categories — gnutls not available."); + return 0; +} +#endif diff --git a/src/grp-journal-remote/microhttpd-util.h b/src/grp-journal-remote/microhttpd-util.h new file mode 100644 index 0000000000..ea160f212b --- /dev/null +++ b/src/grp-journal-remote/microhttpd-util.h @@ -0,0 +1,60 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 Zbigniew Jędrzejewski-Szmek + + 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 + +#include "macro.h" + +/* Compatiblity with libmicrohttpd < 0.9.38 */ +#ifndef MHD_HTTP_NOT_ACCEPTABLE +#define MHD_HTTP_NOT_ACCEPTABLE MHD_HTTP_METHOD_NOT_ACCEPTABLE +#endif + +#if MHD_VERSION < 0x00094203 +#define MHD_create_response_from_fd_at_offset64 MHD_create_response_from_fd_at_offset +#endif + +void microhttpd_logger(void *arg, const char *fmt, va_list ap) _printf_(2, 0); + +/* respond_oom() must be usable with return, hence this form. */ +#define respond_oom(connection) log_oom(), mhd_respond_oom(connection) + +int mhd_respondf(struct MHD_Connection *connection, + unsigned code, + const char *format, ...) _printf_(3,4); + +int mhd_respond(struct MHD_Connection *connection, + unsigned code, + const char *message); + +int mhd_respond_oom(struct MHD_Connection *connection); + +int check_permissions(struct MHD_Connection *connection, int *code, char **hostname); + +/* Set gnutls internal logging function to a callback which uses our + * own logging framework. + * + * gnutls categories are additionally filtered by our internal log + * level, so it should be set fairly high to capture all potentially + * interesting events without overwhelming detail. + */ +int setup_gnutls_logger(char **categories); diff --git a/src/grp-journal-remote/systemd-journal-gatewayd/Makefile b/src/grp-journal-remote/systemd-journal-gatewayd/Makefile new file mode 100644 index 0000000000..ed1dfbf301 --- /dev/null +++ b/src/grp-journal-remote/systemd-journal-gatewayd/Makefile @@ -0,0 +1,68 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(HAVE_MICROHTTPD),) +gatewayddocumentrootdir=$(pkgdatadir)/gatewayd + +libexec_PROGRAMS += \ + systemd-journal-gatewayd + +systemd_journal_gatewayd_SOURCES = \ + src/journal-remote/journal-gatewayd.c \ + src/journal-remote/microhttpd-util.h \ + src/journal-remote/microhttpd-util.c + +systemd_journal_gatewayd_LDADD = \ + libshared.la \ + $(MICROHTTPD_LIBS) + +ifneq ($(HAVE_GNUTLS),) +systemd_journal_gatewayd_LDADD += \ + $(GNUTLS_LIBS) +endif # HAVE_GNUTLS + +systemd_journal_gatewayd_CFLAGS = \ + $(AM_CFLAGS) \ + $(MICROHTTPD_CFLAGS) + +systemd_journal_gatewayd_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -DDOCUMENT_ROOT=\"$(gatewayddocumentrootdir)\" + +dist_systemunit_DATA += \ + units/systemd-journal-gatewayd.socket + +nodist_systemunit_DATA += \ + units/systemd-journal-gatewayd.service + +dist_gatewayddocumentroot_DATA = \ + src/journal-remote/browse.html + +endif # HAVE_MICROHTTPD + +EXTRA_DIST += \ + units/systemd-journal-gatewayd.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-journal-remote/systemd-journal-gatewayd/journal-gatewayd.c b/src/grp-journal-remote/systemd-journal-gatewayd/journal-gatewayd.c new file mode 100644 index 0000000000..1cfb5e2c9c --- /dev/null +++ b/src/grp-journal-remote/systemd-journal-gatewayd/journal-gatewayd.c @@ -0,0 +1,1077 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 +#ifdef HAVE_GNUTLS +#include +#endif +#include +#include +#include +#include + +#include +#include +#include + +#include "alloc-util.h" +#include "bus-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "hostname-util.h" +#include "log.h" +#include "logs-show.h" +#include "microhttpd-util.h" +#include "parse-util.h" +#include "sigbus.h" +#include "util.h" + +#define JOURNAL_WAIT_TIMEOUT (10*USEC_PER_SEC) + +static char *arg_key_pem = NULL; +static char *arg_cert_pem = NULL; +static char *arg_trust_pem = NULL; + +typedef struct RequestMeta { + sd_journal *journal; + + OutputMode mode; + + char *cursor; + int64_t n_skip; + uint64_t n_entries; + bool n_entries_set; + + FILE *tmp; + uint64_t delta, size; + + int argument_parse_error; + + bool follow; + bool discrete; + + uint64_t n_fields; + bool n_fields_set; +} RequestMeta; + +static const char* const mime_types[_OUTPUT_MODE_MAX] = { + [OUTPUT_SHORT] = "text/plain", + [OUTPUT_JSON] = "application/json", + [OUTPUT_JSON_SSE] = "text/event-stream", + [OUTPUT_EXPORT] = "application/vnd.fdo.journal", +}; + +static RequestMeta *request_meta(void **connection_cls) { + RequestMeta *m; + + assert(connection_cls); + if (*connection_cls) + return *connection_cls; + + m = new0(RequestMeta, 1); + if (!m) + return NULL; + + *connection_cls = m; + return m; +} + +static void request_meta_free( + void *cls, + struct MHD_Connection *connection, + void **connection_cls, + enum MHD_RequestTerminationCode toe) { + + RequestMeta *m = *connection_cls; + + if (!m) + return; + + sd_journal_close(m->journal); + + safe_fclose(m->tmp); + + free(m->cursor); + free(m); +} + +static int open_journal(RequestMeta *m) { + assert(m); + + if (m->journal) + return 0; + + return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM); +} + +static int request_meta_ensure_tmp(RequestMeta *m) { + assert(m); + + if (m->tmp) + rewind(m->tmp); + else { + int fd; + + fd = open_tmpfile_unlinkable("/tmp", O_RDWR|O_CLOEXEC); + if (fd < 0) + return fd; + + m->tmp = fdopen(fd, "w+"); + if (!m->tmp) { + safe_close(fd); + return -errno; + } + } + + return 0; +} + +static ssize_t request_reader_entries( + void *cls, + uint64_t pos, + char *buf, + size_t max) { + + RequestMeta *m = cls; + int r; + size_t n, k; + + assert(m); + assert(buf); + assert(max > 0); + assert(pos >= m->delta); + + pos -= m->delta; + + while (pos >= m->size) { + off_t sz; + + /* End of this entry, so let's serialize the next + * one */ + + if (m->n_entries_set && + m->n_entries <= 0) + return MHD_CONTENT_READER_END_OF_STREAM; + + if (m->n_skip < 0) + r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1); + else if (m->n_skip > 0) + r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1); + else + r = sd_journal_next(m->journal); + + if (r < 0) { + log_error_errno(r, "Failed to advance journal pointer: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } else if (r == 0) { + + if (m->follow) { + r = sd_journal_wait(m->journal, (uint64_t) JOURNAL_WAIT_TIMEOUT); + if (r < 0) { + log_error_errno(r, "Couldn't wait for journal event: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + if (r == SD_JOURNAL_NOP) + break; + + continue; + } + + return MHD_CONTENT_READER_END_OF_STREAM; + } + + if (m->discrete) { + assert(m->cursor); + + r = sd_journal_test_cursor(m->journal, m->cursor); + if (r < 0) { + log_error_errno(r, "Failed to test cursor: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + if (r == 0) + return MHD_CONTENT_READER_END_OF_STREAM; + } + + pos -= m->size; + m->delta += m->size; + + if (m->n_entries_set) + m->n_entries -= 1; + + m->n_skip = 0; + + r = request_meta_ensure_tmp(m); + if (r < 0) { + log_error_errno(r, "Failed to create temporary file: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL); + if (r < 0) { + log_error_errno(r, "Failed to serialize item: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + sz = ftello(m->tmp); + if (sz == (off_t) -1) { + log_error_errno(errno, "Failed to retrieve file position: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + m->size = (uint64_t) sz; + } + + if (fseeko(m->tmp, pos, SEEK_SET) < 0) { + log_error_errno(errno, "Failed to seek to position: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + n = m->size - pos; + if (n < 1) + return 0; + if (n > max) + n = max; + + errno = 0; + k = fread(buf, 1, n, m->tmp); + if (k != n) { + log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + return (ssize_t) k; +} + +static int request_parse_accept( + RequestMeta *m, + struct MHD_Connection *connection) { + + const char *header; + + assert(m); + assert(connection); + + header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept"); + if (!header) + return 0; + + if (streq(header, mime_types[OUTPUT_JSON])) + m->mode = OUTPUT_JSON; + else if (streq(header, mime_types[OUTPUT_JSON_SSE])) + m->mode = OUTPUT_JSON_SSE; + else if (streq(header, mime_types[OUTPUT_EXPORT])) + m->mode = OUTPUT_EXPORT; + else + m->mode = OUTPUT_SHORT; + + return 0; +} + +static int request_parse_range( + RequestMeta *m, + struct MHD_Connection *connection) { + + const char *range, *colon, *colon2; + int r; + + assert(m); + assert(connection); + + range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range"); + if (!range) + return 0; + + if (!startswith(range, "entries=")) + return 0; + + range += 8; + range += strspn(range, WHITESPACE); + + colon = strchr(range, ':'); + if (!colon) + m->cursor = strdup(range); + else { + const char *p; + + colon2 = strchr(colon + 1, ':'); + if (colon2) { + _cleanup_free_ char *t; + + t = strndup(colon + 1, colon2 - colon - 1); + if (!t) + return -ENOMEM; + + r = safe_atoi64(t, &m->n_skip); + if (r < 0) + return r; + } + + p = (colon2 ? colon2 : colon) + 1; + if (*p) { + r = safe_atou64(p, &m->n_entries); + if (r < 0) + return r; + + if (m->n_entries <= 0) + return -EINVAL; + + m->n_entries_set = true; + } + + m->cursor = strndup(range, colon - range); + } + + if (!m->cursor) + return -ENOMEM; + + m->cursor[strcspn(m->cursor, WHITESPACE)] = 0; + if (isempty(m->cursor)) + m->cursor = mfree(m->cursor); + + return 0; +} + +static int request_parse_arguments_iterator( + void *cls, + enum MHD_ValueKind kind, + const char *key, + const char *value) { + + RequestMeta *m = cls; + _cleanup_free_ char *p = NULL; + int r; + + assert(m); + + if (isempty(key)) { + m->argument_parse_error = -EINVAL; + return MHD_NO; + } + + if (streq(key, "follow")) { + if (isempty(value)) { + m->follow = true; + return MHD_YES; + } + + r = parse_boolean(value); + if (r < 0) { + m->argument_parse_error = r; + return MHD_NO; + } + + m->follow = r; + return MHD_YES; + } + + if (streq(key, "discrete")) { + if (isempty(value)) { + m->discrete = true; + return MHD_YES; + } + + r = parse_boolean(value); + if (r < 0) { + m->argument_parse_error = r; + return MHD_NO; + } + + m->discrete = r; + return MHD_YES; + } + + if (streq(key, "boot")) { + if (isempty(value)) + r = true; + else { + r = parse_boolean(value); + if (r < 0) { + m->argument_parse_error = r; + return MHD_NO; + } + } + + if (r) { + char match[9 + 32 + 1] = "_BOOT_ID="; + sd_id128_t bid; + + r = sd_id128_get_boot(&bid); + if (r < 0) { + log_error_errno(r, "Failed to get boot ID: %m"); + return MHD_NO; + } + + sd_id128_to_string(bid, match + 9); + r = sd_journal_add_match(m->journal, match, sizeof(match)-1); + if (r < 0) { + m->argument_parse_error = r; + return MHD_NO; + } + } + + return MHD_YES; + } + + p = strjoin(key, "=", strempty(value), NULL); + if (!p) { + m->argument_parse_error = log_oom(); + return MHD_NO; + } + + r = sd_journal_add_match(m->journal, p, 0); + if (r < 0) { + m->argument_parse_error = r; + return MHD_NO; + } + + return MHD_YES; +} + +static int request_parse_arguments( + RequestMeta *m, + struct MHD_Connection *connection) { + + assert(m); + assert(connection); + + m->argument_parse_error = 0; + MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m); + + return m->argument_parse_error; +} + +static int request_handler_entries( + struct MHD_Connection *connection, + void *connection_cls) { + + struct MHD_Response *response; + RequestMeta *m = connection_cls; + int r; + + assert(connection); + assert(m); + + r = open_journal(m); + if (r < 0) + return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r)); + + if (request_parse_accept(m, connection) < 0) + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n"); + + if (request_parse_range(m, connection) < 0) + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n"); + + if (request_parse_arguments(m, connection) < 0) + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n"); + + if (m->discrete) { + if (!m->cursor) + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n"); + + m->n_entries = 1; + m->n_entries_set = true; + } + + if (m->cursor) + r = sd_journal_seek_cursor(m->journal, m->cursor); + else if (m->n_skip >= 0) + r = sd_journal_seek_head(m->journal); + else if (m->n_skip < 0) + r = sd_journal_seek_tail(m->journal); + if (r < 0) + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n"); + + response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL); + if (!response) + return respond_oom(connection); + + MHD_add_response_header(response, "Content-Type", mime_types[m->mode]); + + r = MHD_queue_response(connection, MHD_HTTP_OK, response); + MHD_destroy_response(response); + + return r; +} + +static int output_field(FILE *f, OutputMode m, const char *d, size_t l) { + const char *eq; + size_t j; + + eq = memchr(d, '=', l); + if (!eq) + return -EINVAL; + + j = l - (eq - d + 1); + + if (m == OUTPUT_JSON) { + fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d); + json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH); + fputs(" }\n", f); + } else { + fwrite(eq+1, 1, j, f); + fputc('\n', f); + } + + return 0; +} + +static ssize_t request_reader_fields( + void *cls, + uint64_t pos, + char *buf, + size_t max) { + + RequestMeta *m = cls; + int r; + size_t n, k; + + assert(m); + assert(buf); + assert(max > 0); + assert(pos >= m->delta); + + pos -= m->delta; + + while (pos >= m->size) { + off_t sz; + const void *d; + size_t l; + + /* End of this field, so let's serialize the next + * one */ + + if (m->n_fields_set && + m->n_fields <= 0) + return MHD_CONTENT_READER_END_OF_STREAM; + + r = sd_journal_enumerate_unique(m->journal, &d, &l); + if (r < 0) { + log_error_errno(r, "Failed to advance field index: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } else if (r == 0) + return MHD_CONTENT_READER_END_OF_STREAM; + + pos -= m->size; + m->delta += m->size; + + if (m->n_fields_set) + m->n_fields -= 1; + + r = request_meta_ensure_tmp(m); + if (r < 0) { + log_error_errno(r, "Failed to create temporary file: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + r = output_field(m->tmp, m->mode, d, l); + if (r < 0) { + log_error_errno(r, "Failed to serialize item: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + sz = ftello(m->tmp); + if (sz == (off_t) -1) { + log_error_errno(errno, "Failed to retrieve file position: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + m->size = (uint64_t) sz; + } + + if (fseeko(m->tmp, pos, SEEK_SET) < 0) { + log_error_errno(errno, "Failed to seek to position: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + n = m->size - pos; + if (n > max) + n = max; + + errno = 0; + k = fread(buf, 1, n, m->tmp); + if (k != n) { + log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + return (ssize_t) k; +} + +static int request_handler_fields( + struct MHD_Connection *connection, + const char *field, + void *connection_cls) { + + struct MHD_Response *response; + RequestMeta *m = connection_cls; + int r; + + assert(connection); + assert(m); + + r = open_journal(m); + if (r < 0) + return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r)); + + if (request_parse_accept(m, connection) < 0) + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n"); + + r = sd_journal_query_unique(m->journal, field); + if (r < 0) + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n"); + + response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL); + if (!response) + return respond_oom(connection); + + MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]); + + r = MHD_queue_response(connection, MHD_HTTP_OK, response); + MHD_destroy_response(response); + + return r; +} + +static int request_handler_redirect( + struct MHD_Connection *connection, + const char *target) { + + char *page; + struct MHD_Response *response; + int ret; + + assert(connection); + assert(target); + + if (asprintf(&page, "Please continue to the journal browser.", target) < 0) + return respond_oom(connection); + + response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE); + if (!response) { + free(page); + return respond_oom(connection); + } + + MHD_add_response_header(response, "Content-Type", "text/html"); + MHD_add_response_header(response, "Location", target); + + ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response); + MHD_destroy_response(response); + + return ret; +} + +static int request_handler_file( + struct MHD_Connection *connection, + const char *path, + const char *mime_type) { + + struct MHD_Response *response; + int ret; + _cleanup_close_ int fd = -1; + struct stat st; + + assert(connection); + assert(path); + assert(mime_type); + + fd = open(path, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return mhd_respondf(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path); + + if (fstat(fd, &st) < 0) + return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n"); + + response = MHD_create_response_from_fd_at_offset64(st.st_size, fd, 0); + if (!response) + return respond_oom(connection); + + fd = -1; + + MHD_add_response_header(response, "Content-Type", mime_type); + + ret = MHD_queue_response(connection, MHD_HTTP_OK, response); + MHD_destroy_response(response); + + return ret; +} + +static int get_virtualization(char **v) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + char *b = NULL; + int r; + + r = sd_bus_default_system(&bus); + if (r < 0) + return r; + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Virtualization", + NULL, + &b); + if (r < 0) + return r; + + if (isempty(b)) { + free(b); + *v = NULL; + return 0; + } + + *v = b; + return 1; +} + +static int request_handler_machine( + struct MHD_Connection *connection, + void *connection_cls) { + + struct MHD_Response *response; + RequestMeta *m = connection_cls; + int r; + _cleanup_free_ char* hostname = NULL, *os_name = NULL; + uint64_t cutoff_from = 0, cutoff_to = 0, usage = 0; + char *json; + sd_id128_t mid, bid; + _cleanup_free_ char *v = NULL; + + assert(connection); + assert(m); + + r = open_journal(m); + if (r < 0) + return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r)); + + r = sd_id128_get_machine(&mid); + if (r < 0) + return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r)); + + r = sd_id128_get_boot(&bid); + if (r < 0) + return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r)); + + hostname = gethostname_malloc(); + if (!hostname) + return respond_oom(connection); + + r = sd_journal_get_usage(m->journal, &usage); + if (r < 0) + return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r)); + + r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to); + if (r < 0) + return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r)); + + if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL) == -ENOENT) + (void) parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL); + + get_virtualization(&v); + + r = asprintf(&json, + "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\"," + "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\"," + "\"hostname\" : \"%s\"," + "\"os_pretty_name\" : \"%s\"," + "\"virtualization\" : \"%s\"," + "\"usage\" : \"%"PRIu64"\"," + "\"cutoff_from_realtime\" : \"%"PRIu64"\"," + "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n", + SD_ID128_FORMAT_VAL(mid), + SD_ID128_FORMAT_VAL(bid), + hostname_cleanup(hostname), + os_name ? os_name : "GNU/Linux", + v ? v : "bare", + usage, + cutoff_from, + cutoff_to); + + if (r < 0) + return respond_oom(connection); + + response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE); + if (!response) { + free(json); + return respond_oom(connection); + } + + MHD_add_response_header(response, "Content-Type", "application/json"); + r = MHD_queue_response(connection, MHD_HTTP_OK, response); + MHD_destroy_response(response); + + return r; +} + +static int request_handler( + void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t *upload_data_size, + void **connection_cls) { + int r, code; + + assert(connection); + assert(connection_cls); + assert(url); + assert(method); + + if (!streq(method, "GET")) + return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, + "Unsupported method.\n"); + + + if (!*connection_cls) { + if (!request_meta(connection_cls)) + return respond_oom(connection); + return MHD_YES; + } + + if (arg_trust_pem) { + r = check_permissions(connection, &code, NULL); + if (r < 0) + return code; + } + + if (streq(url, "/")) + return request_handler_redirect(connection, "/browse"); + + if (streq(url, "/entries")) + return request_handler_entries(connection, *connection_cls); + + if (startswith(url, "/fields/")) + return request_handler_fields(connection, url + 8, *connection_cls); + + if (streq(url, "/browse")) + return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html"); + + if (streq(url, "/machine")) + return request_handler_machine(connection, *connection_cls); + + return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.\n"); +} + +static void help(void) { + printf("%s [OPTIONS...] ...\n\n" + "HTTP server for journal events.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --cert=CERT.PEM Server certificate in PEM format\n" + " --key=KEY.PEM Server key in PEM format\n" + " --trust=CERT.PEM Certificat authority certificate in PEM format\n", + program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_KEY, + ARG_CERT, + ARG_TRUST, + }; + + int r, c; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "key", required_argument, NULL, ARG_KEY }, + { "cert", required_argument, NULL, ARG_CERT }, + { "trust", required_argument, NULL, ARG_TRUST }, + {} + }; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch(c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_KEY: + if (arg_key_pem) { + log_error("Key file specified twice"); + return -EINVAL; + } + r = read_full_file(optarg, &arg_key_pem, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read key file: %m"); + assert(arg_key_pem); + break; + + case ARG_CERT: + if (arg_cert_pem) { + log_error("Certificate file specified twice"); + return -EINVAL; + } + r = read_full_file(optarg, &arg_cert_pem, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read certificate file: %m"); + assert(arg_cert_pem); + break; + + case ARG_TRUST: +#ifdef HAVE_GNUTLS + if (arg_trust_pem) { + log_error("CA certificate file specified twice"); + return -EINVAL; + } + r = read_full_file(optarg, &arg_trust_pem, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read CA certificate file: %m"); + assert(arg_trust_pem); + break; +#else + log_error("Option --trust is not available."); +#endif + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (optind < argc) { + log_error("This program does not take arguments."); + return -EINVAL; + } + + if (!!arg_key_pem != !!arg_cert_pem) { + log_error("Certificate and key files must be specified together"); + return -EINVAL; + } + + if (arg_trust_pem && !arg_key_pem) { + log_error("CA certificate can only be used with certificate file"); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char *argv[]) { + struct MHD_Daemon *d = NULL; + int r, n; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r < 0) + return EXIT_FAILURE; + if (r == 0) + return EXIT_SUCCESS; + + sigbus_install(); + + r = setup_gnutls_logger(NULL); + if (r < 0) + return EXIT_FAILURE; + + n = sd_listen_fds(1); + if (n < 0) { + log_error_errno(n, "Failed to determine passed sockets: %m"); + goto finish; + } else if (n > 1) { + log_error("Can't listen on more than one socket."); + goto finish; + } else { + struct MHD_OptionItem opts[] = { + { MHD_OPTION_NOTIFY_COMPLETED, + (intptr_t) request_meta_free, NULL }, + { MHD_OPTION_EXTERNAL_LOGGER, + (intptr_t) microhttpd_logger, NULL }, + { MHD_OPTION_END, 0, NULL }, + { MHD_OPTION_END, 0, NULL }, + { MHD_OPTION_END, 0, NULL }, + { MHD_OPTION_END, 0, NULL }, + { MHD_OPTION_END, 0, NULL }}; + int opts_pos = 2; + + /* We force MHD_USE_PIPE_FOR_SHUTDOWN here, in order + * to make sure libmicrohttpd doesn't use shutdown() + * on our listening socket, which would break socket + * re-activation. See + * + * https://lists.gnu.org/archive/html/libmicrohttpd/2015-09/msg00014.html + * https://github.com/systemd/systemd/pull/1286 + */ + + int flags = + MHD_USE_DEBUG | + MHD_USE_DUAL_STACK | + MHD_USE_PIPE_FOR_SHUTDOWN | + MHD_USE_POLL | + MHD_USE_THREAD_PER_CONNECTION; + + if (n > 0) + opts[opts_pos++] = (struct MHD_OptionItem) + {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START}; + if (arg_key_pem) { + assert(arg_cert_pem); + opts[opts_pos++] = (struct MHD_OptionItem) + {MHD_OPTION_HTTPS_MEM_KEY, 0, arg_key_pem}; + opts[opts_pos++] = (struct MHD_OptionItem) + {MHD_OPTION_HTTPS_MEM_CERT, 0, arg_cert_pem}; + flags |= MHD_USE_SSL; + } + if (arg_trust_pem) { + assert(flags & MHD_USE_SSL); + opts[opts_pos++] = (struct MHD_OptionItem) + {MHD_OPTION_HTTPS_MEM_TRUST, 0, arg_trust_pem}; + } + + d = MHD_start_daemon(flags, 19531, + NULL, NULL, + request_handler, NULL, + MHD_OPTION_ARRAY, opts, + MHD_OPTION_END); + } + + if (!d) { + log_error("Failed to start daemon!"); + goto finish; + } + + pause(); + + r = EXIT_SUCCESS; + +finish: + if (d) + MHD_stop_daemon(d); + + return r; +} diff --git a/src/grp-journal-remote/systemd-journal-remote/Makefile b/src/grp-journal-remote/systemd-journal-remote/Makefile new file mode 100644 index 0000000000..5e730f9fa6 --- /dev/null +++ b/src/grp-journal-remote/systemd-journal-remote/Makefile @@ -0,0 +1,85 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(HAVE_MICROHTTPD),) +libexec_PROGRAMS += \ + systemd-journal-remote + +systemd_journal_remote_SOURCES = \ + src/journal-remote/journal-remote-parse.h \ + src/journal-remote/journal-remote-parse.c \ + src/journal-remote/journal-remote-write.h \ + src/journal-remote/journal-remote-write.c \ + src/journal-remote/journal-remote.h \ + src/journal-remote/journal-remote.c + +systemd_journal_remote_LDADD = \ + libjournal-core.la + +systemd_journal_remote_SOURCES += \ + src/journal-remote/microhttpd-util.h \ + src/journal-remote/microhttpd-util.c + +systemd_journal_remote_CFLAGS = \ + $(AM_CFLAGS) \ + $(MICROHTTPD_CFLAGS) + +systemd_journal_remote_LDADD += \ + $(MICROHTTPD_LIBS) + +ifneq ($(ENABLE_TMPFILES),) +dist_tmpfiles_DATA += \ + tmpfiles.d/systemd-remote.conf +endif # ENABLE_TMPFILES + +ifneq ($(HAVE_GNUTLS),) +systemd_journal_remote_LDADD += \ + $(GNUTLS_LIBS) +endif # HAVE_GNUTLS + +# systemd-journal-remote make sense mostly with full crypto stack +dist_systemunit_DATA += \ + units/systemd-journal-remote.socket + +nodist_systemunit_DATA += \ + units/systemd-journal-remote.service + +journal-remote-install-hook: journal-install-hook + -$(MKDIR_P) $(DESTDIR)/var/log/journal/remote + -chown 0:0 $(DESTDIR)/var/log/journal/remote + -chmod 755 $(DESTDIR)/var/log/journal/remote + +INSTALL_EXEC_HOOKS += journal-remote-install-hook + +nodist_pkgsysconf_DATA += \ + src/journal-remote/journal-remote.conf + +EXTRA_DIST += \ + units/systemd-journal-remote.service.in \ + src/journal-remote/journal-remote.conf.in \ + src/journal-remote/log-generator.py +endif # HAVE_MICROHTTPD + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-journal-remote/systemd-journal-remote/journal-remote-parse.c b/src/grp-journal-remote/systemd-journal-remote/journal-remote-parse.c new file mode 100644 index 0000000000..9ba9ee3fc0 --- /dev/null +++ b/src/grp-journal-remote/systemd-journal-remote/journal-remote-parse.c @@ -0,0 +1,506 @@ +/*** + This file is part of systemd. + + Copyright 2014 Zbigniew Jędrzejewski-Szmek + + 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 "alloc-util.h" +#include "fd-util.h" +#include "journal-remote-parse.h" +#include "journald-native.h" +#include "parse-util.h" +#include "string-util.h" + +#define LINE_CHUNK 8*1024u + +void source_free(RemoteSource *source) { + if (!source) + return; + + if (source->fd >= 0 && !source->passive_fd) { + log_debug("Closing fd:%d (%s)", source->fd, source->name); + safe_close(source->fd); + } + + free(source->name); + free(source->buf); + iovw_free_contents(&source->iovw); + + log_debug("Writer ref count %i", source->writer->n_ref); + writer_unref(source->writer); + + sd_event_source_unref(source->event); + sd_event_source_unref(source->buffer_event); + + free(source); +} + +/** + * Initialize zero-filled source with given values. On success, takes + * ownerhship of fd and writer, otherwise does not touch them. + */ +RemoteSource* source_new(int fd, bool passive_fd, char *name, Writer *writer) { + + RemoteSource *source; + + log_debug("Creating source for %sfd:%d (%s)", + passive_fd ? "passive " : "", fd, name); + + assert(fd >= 0); + + source = new0(RemoteSource, 1); + if (!source) + return NULL; + + source->fd = fd; + source->passive_fd = passive_fd; + source->name = name; + source->writer = writer; + + return source; +} + +static char* realloc_buffer(RemoteSource *source, size_t size) { + char *b, *old = source->buf; + + b = GREEDY_REALLOC(source->buf, source->size, size); + if (!b) + return NULL; + + iovw_rebase(&source->iovw, old, source->buf); + + return b; +} + +static int get_line(RemoteSource *source, char **line, size_t *size) { + ssize_t n; + char *c = NULL; + + assert(source); + assert(source->state == STATE_LINE); + assert(source->offset <= source->filled); + assert(source->filled <= source->size); + assert(source->buf == NULL || source->size > 0); + assert(source->fd >= 0); + + for (;;) { + if (source->buf) { + size_t start = MAX(source->scanned, source->offset); + + c = memchr(source->buf + start, '\n', + source->filled - start); + if (c != NULL) + break; + } + + source->scanned = source->filled; + if (source->scanned >= DATA_SIZE_MAX) { + log_error("Entry is bigger than %u bytes.", DATA_SIZE_MAX); + return -E2BIG; + } + + if (source->passive_fd) + /* we have to wait for some data to come to us */ + return -EAGAIN; + + /* We know that source->filled is at most DATA_SIZE_MAX, so if + we reallocate it, we'll increase the size at least a bit. */ + assert_cc(DATA_SIZE_MAX < ENTRY_SIZE_MAX); + if (source->size - source->filled < LINE_CHUNK && + !realloc_buffer(source, MIN(source->filled + LINE_CHUNK, ENTRY_SIZE_MAX))) + return log_oom(); + + assert(source->buf); + assert(source->size - source->filled >= LINE_CHUNK || + source->size == ENTRY_SIZE_MAX); + + n = read(source->fd, + source->buf + source->filled, + source->size - source->filled); + if (n < 0) { + if (errno != EAGAIN) + log_error_errno(errno, "read(%d, ..., %zu): %m", + source->fd, + source->size - source->filled); + return -errno; + } else if (n == 0) + return 0; + + source->filled += n; + } + + *line = source->buf + source->offset; + *size = c + 1 - source->buf - source->offset; + source->offset += *size; + + return 1; +} + +int push_data(RemoteSource *source, const char *data, size_t size) { + assert(source); + assert(source->state != STATE_EOF); + + if (!realloc_buffer(source, source->filled + size)) { + log_error("Failed to store received data of size %zu " + "(in addition to existing %zu bytes with %zu filled): %s", + size, source->size, source->filled, strerror(ENOMEM)); + return -ENOMEM; + } + + memcpy(source->buf + source->filled, data, size); + source->filled += size; + + return 0; +} + +static int fill_fixed_size(RemoteSource *source, void **data, size_t size) { + + assert(source); + assert(source->state == STATE_DATA_START || + source->state == STATE_DATA || + source->state == STATE_DATA_FINISH); + assert(size <= DATA_SIZE_MAX); + assert(source->offset <= source->filled); + assert(source->filled <= source->size); + assert(source->buf != NULL || source->size == 0); + assert(source->buf == NULL || source->size > 0); + assert(source->fd >= 0); + assert(data); + + while (source->filled - source->offset < size) { + int n; + + if (source->passive_fd) + /* we have to wait for some data to come to us */ + return -EAGAIN; + + if (!realloc_buffer(source, source->offset + size)) + return log_oom(); + + n = read(source->fd, source->buf + source->filled, + source->size - source->filled); + if (n < 0) { + if (errno != EAGAIN) + log_error_errno(errno, "read(%d, ..., %zu): %m", source->fd, + source->size - source->filled); + return -errno; + } else if (n == 0) + return 0; + + source->filled += n; + } + + *data = source->buf + source->offset; + source->offset += size; + + return 1; +} + +static int get_data_size(RemoteSource *source) { + int r; + void *data; + + assert(source); + assert(source->state == STATE_DATA_START); + assert(source->data_size == 0); + + r = fill_fixed_size(source, &data, sizeof(uint64_t)); + if (r <= 0) + return r; + + source->data_size = le64toh( *(uint64_t *) data ); + if (source->data_size > DATA_SIZE_MAX) { + log_error("Stream declares field with size %zu > DATA_SIZE_MAX = %u", + source->data_size, DATA_SIZE_MAX); + return -EINVAL; + } + if (source->data_size == 0) + log_warning("Binary field with zero length"); + + return 1; +} + +static int get_data_data(RemoteSource *source, void **data) { + int r; + + assert(source); + assert(data); + assert(source->state == STATE_DATA); + + r = fill_fixed_size(source, data, source->data_size); + if (r <= 0) + return r; + + return 1; +} + +static int get_data_newline(RemoteSource *source) { + int r; + char *data; + + assert(source); + assert(source->state == STATE_DATA_FINISH); + + r = fill_fixed_size(source, (void**) &data, 1); + if (r <= 0) + return r; + + assert(data); + if (*data != '\n') { + log_error("expected newline, got '%c'", *data); + return -EINVAL; + } + + return 1; +} + +static int process_dunder(RemoteSource *source, char *line, size_t n) { + const char *timestamp; + int r; + + assert(line); + assert(n > 0); + assert(line[n-1] == '\n'); + + /* XXX: is it worth to support timestamps in extended format? + * We don't produce them, but who knows... */ + + timestamp = startswith(line, "__CURSOR="); + if (timestamp) + /* ignore __CURSOR */ + return 1; + + timestamp = startswith(line, "__REALTIME_TIMESTAMP="); + if (timestamp) { + long long unsigned x; + line[n-1] = '\0'; + r = safe_atollu(timestamp, &x); + if (r < 0) + log_warning("Failed to parse __REALTIME_TIMESTAMP: '%s'", timestamp); + else + source->ts.realtime = x; + return r < 0 ? r : 1; + } + + timestamp = startswith(line, "__MONOTONIC_TIMESTAMP="); + if (timestamp) { + long long unsigned x; + line[n-1] = '\0'; + r = safe_atollu(timestamp, &x); + if (r < 0) + log_warning("Failed to parse __MONOTONIC_TIMESTAMP: '%s'", timestamp); + else + source->ts.monotonic = x; + return r < 0 ? r : 1; + } + + timestamp = startswith(line, "__"); + if (timestamp) { + log_notice("Unknown dunder line %s", line); + return 1; + } + + /* no dunder */ + return 0; +} + +static int process_data(RemoteSource *source) { + int r; + + switch(source->state) { + case STATE_LINE: { + char *line, *sep; + size_t n = 0; + + assert(source->data_size == 0); + + r = get_line(source, &line, &n); + if (r < 0) + return r; + if (r == 0) { + source->state = STATE_EOF; + return r; + } + assert(n > 0); + assert(line[n-1] == '\n'); + + if (n == 1) { + log_trace("Received empty line, event is ready"); + return 1; + } + + r = process_dunder(source, line, n); + if (r != 0) + return r < 0 ? r : 0; + + /* MESSAGE=xxx\n + or + COREDUMP\n + LLLLLLLL0011223344...\n + */ + sep = memchr(line, '=', n); + if (sep) { + /* chomp newline */ + n--; + + r = iovw_put(&source->iovw, line, n); + if (r < 0) + return r; + } else { + /* replace \n with = */ + line[n-1] = '='; + + source->field_len = n; + source->state = STATE_DATA_START; + + /* we cannot put the field in iovec until we have all data */ + } + + log_trace("Received: %.*s (%s)", (int) n, line, sep ? "text" : "binary"); + + return 0; /* continue */ + } + + case STATE_DATA_START: + assert(source->data_size == 0); + + r = get_data_size(source); + // log_debug("get_data_size() -> %d", r); + if (r < 0) + return r; + if (r == 0) { + source->state = STATE_EOF; + return 0; + } + + source->state = source->data_size > 0 ? + STATE_DATA : STATE_DATA_FINISH; + + return 0; /* continue */ + + case STATE_DATA: { + void *data; + char *field; + + assert(source->data_size > 0); + + r = get_data_data(source, &data); + // log_debug("get_data_data() -> %d", r); + if (r < 0) + return r; + if (r == 0) { + source->state = STATE_EOF; + return 0; + } + + assert(data); + + field = (char*) data - sizeof(uint64_t) - source->field_len; + memmove(field + sizeof(uint64_t), field, source->field_len); + + r = iovw_put(&source->iovw, field + sizeof(uint64_t), source->field_len + source->data_size); + if (r < 0) + return r; + + source->state = STATE_DATA_FINISH; + + return 0; /* continue */ + } + + case STATE_DATA_FINISH: + r = get_data_newline(source); + // log_debug("get_data_newline() -> %d", r); + if (r < 0) + return r; + if (r == 0) { + source->state = STATE_EOF; + return 0; + } + + source->data_size = 0; + source->state = STATE_LINE; + + return 0; /* continue */ + default: + assert_not_reached("wtf?"); + } +} + +int process_source(RemoteSource *source, bool compress, bool seal) { + size_t remain, target; + int r; + + assert(source); + assert(source->writer); + + r = process_data(source); + if (r <= 0) + return r; + + /* We have a full event */ + log_trace("Received full event from source@%p fd:%d (%s)", + source, source->fd, source->name); + + if (!source->iovw.count) { + log_warning("Entry with no payload, skipping"); + goto freeing; + } + + assert(source->iovw.iovec); + assert(source->iovw.count); + + r = writer_write(source->writer, &source->iovw, &source->ts, compress, seal); + if (r < 0) + log_error_errno(r, "Failed to write entry of %zu bytes: %m", + iovw_size(&source->iovw)); + else + r = 1; + + freeing: + iovw_free_contents(&source->iovw); + + /* possibly reset buffer position */ + remain = source->filled - source->offset; + + if (remain == 0) /* no brainer */ + source->offset = source->scanned = source->filled = 0; + else if (source->offset > source->size - source->filled && + source->offset > remain) { + memcpy(source->buf, source->buf + source->offset, remain); + source->offset = source->scanned = 0; + source->filled = remain; + } + + target = source->size; + while (target > 16 * LINE_CHUNK && source->filled < target / 2) + target /= 2; + if (target < source->size) { + char *tmp; + + tmp = realloc(source->buf, target); + if (!tmp) + log_warning("Failed to reallocate buffer to (smaller) size %zu", + target); + else { + log_debug("Reallocated buffer from %zu to %zu bytes", + source->size, target); + source->buf = tmp; + source->size = target; + } + } + + return r; +} diff --git a/src/grp-journal-remote/systemd-journal-remote/journal-remote-parse.h b/src/grp-journal-remote/systemd-journal-remote/journal-remote-parse.h new file mode 100644 index 0000000000..4f47ea89d6 --- /dev/null +++ b/src/grp-journal-remote/systemd-journal-remote/journal-remote-parse.h @@ -0,0 +1,69 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Zbigniew Jędrzejewski-Szmek + + 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 "journal-remote-write.h" + +typedef enum { + STATE_LINE = 0, /* waiting to read, or reading line */ + STATE_DATA_START, /* reading binary data header */ + STATE_DATA, /* reading binary data */ + STATE_DATA_FINISH, /* expecting newline */ + STATE_EOF, /* done */ +} source_state; + +typedef struct RemoteSource { + char *name; + int fd; + bool passive_fd; + + char *buf; + size_t size; /* total size of the buffer */ + size_t offset; /* offset to the beginning of live data in the buffer */ + size_t scanned; /* number of bytes since the beginning of data without a newline */ + size_t filled; /* total number of bytes in the buffer */ + + size_t field_len; /* used for binary fields: the field name length */ + size_t data_size; /* and the size of the binary data chunk being processed */ + + struct iovec_wrapper iovw; + + source_state state; + dual_timestamp ts; + + Writer *writer; + + sd_event_source *event; + sd_event_source *buffer_event; +} RemoteSource; + +RemoteSource* source_new(int fd, bool passive_fd, char *name, Writer *writer); + +static inline size_t source_non_empty(RemoteSource *source) { + assert(source); + + return source->filled; +} + +void source_free(RemoteSource *source); +int push_data(RemoteSource *source, const char *data, size_t size); +int process_source(RemoteSource *source, bool compress, bool seal); diff --git a/src/grp-journal-remote/systemd-journal-remote/journal-remote-write.c b/src/grp-journal-remote/systemd-journal-remote/journal-remote-write.c new file mode 100644 index 0000000000..7bba52566e --- /dev/null +++ b/src/grp-journal-remote/systemd-journal-remote/journal-remote-write.c @@ -0,0 +1,168 @@ +/*** + This file is part of systemd. + + Copyright 2012 Zbigniew Jędrzejewski-Szmek + + 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 "alloc-util.h" +#include "journal-remote.h" + +int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len) { + if (!GREEDY_REALLOC(iovw->iovec, iovw->size_bytes, iovw->count + 1)) + return log_oom(); + + iovw->iovec[iovw->count++] = (struct iovec) {data, len}; + return 0; +} + +void iovw_free_contents(struct iovec_wrapper *iovw) { + iovw->iovec = mfree(iovw->iovec); + iovw->size_bytes = iovw->count = 0; +} + +size_t iovw_size(struct iovec_wrapper *iovw) { + size_t n = 0, i; + + for (i = 0; i < iovw->count; i++) + n += iovw->iovec[i].iov_len; + + return n; +} + +void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new) { + size_t i; + + for (i = 0; i < iovw->count; i++) + iovw->iovec[i].iov_base = (char*) iovw->iovec[i].iov_base - old + new; +} + +/********************************************************************** + ********************************************************************** + **********************************************************************/ + +static int do_rotate(JournalFile **f, bool compress, bool seal) { + int r = journal_file_rotate(f, compress, seal, NULL); + if (r < 0) { + if (*f) + log_error_errno(r, "Failed to rotate %s: %m", (*f)->path); + else + log_error_errno(r, "Failed to create rotated journal: %m"); + } + + return r; +} + +Writer* writer_new(RemoteServer *server) { + Writer *w; + + w = new0(Writer, 1); + if (!w) + return NULL; + + memset(&w->metrics, 0xFF, sizeof(w->metrics)); + + w->mmap = mmap_cache_new(); + if (!w->mmap) { + free(w); + return NULL; + } + + w->n_ref = 1; + w->server = server; + + return w; +} + +Writer* writer_free(Writer *w) { + if (!w) + return NULL; + + if (w->journal) { + log_debug("Closing journal file %s.", w->journal->path); + journal_file_close(w->journal); + } + + if (w->server && w->hashmap_key) + hashmap_remove(w->server->writers, w->hashmap_key); + + free(w->hashmap_key); + + if (w->mmap) + mmap_cache_unref(w->mmap); + + free(w); + + return NULL; +} + +Writer* writer_unref(Writer *w) { + if (w && (-- w->n_ref <= 0)) + writer_free(w); + + return NULL; +} + +Writer* writer_ref(Writer *w) { + if (w) + assert_se(++ w->n_ref >= 2); + + return w; +} + +int writer_write(Writer *w, + struct iovec_wrapper *iovw, + dual_timestamp *ts, + bool compress, + bool seal) { + int r; + + assert(w); + assert(iovw); + assert(iovw->count > 0); + + if (journal_file_rotate_suggested(w->journal, 0)) { + log_info("%s: Journal header limits reached or header out-of-date, rotating", + w->journal->path); + r = do_rotate(&w->journal, compress, seal); + if (r < 0) + return r; + } + + r = journal_file_append_entry(w->journal, ts, iovw->iovec, iovw->count, + &w->seqnum, NULL, NULL); + if (r >= 0) { + if (w->server) + w->server->event_count += 1; + return 1; + } + + log_debug_errno(r, "%s: Write failed, rotating: %m", w->journal->path); + r = do_rotate(&w->journal, compress, seal); + if (r < 0) + return r; + else + log_debug("%s: Successfully rotated journal", w->journal->path); + + log_debug("Retrying write."); + r = journal_file_append_entry(w->journal, ts, iovw->iovec, iovw->count, + &w->seqnum, NULL, NULL); + if (r < 0) + return r; + + if (w->server) + w->server->event_count += 1; + return 1; +} diff --git a/src/grp-journal-remote/systemd-journal-remote/journal-remote-write.h b/src/grp-journal-remote/systemd-journal-remote/journal-remote-write.h new file mode 100644 index 0000000000..53ba45fc04 --- /dev/null +++ b/src/grp-journal-remote/systemd-journal-remote/journal-remote-write.h @@ -0,0 +1,70 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Zbigniew Jędrzejewski-Szmek + + 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 "journal-file.h" + +typedef struct RemoteServer RemoteServer; + +struct iovec_wrapper { + struct iovec *iovec; + size_t size_bytes; + size_t count; +}; + +int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len); +void iovw_free_contents(struct iovec_wrapper *iovw); +size_t iovw_size(struct iovec_wrapper *iovw); +void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new); + +typedef struct Writer { + JournalFile *journal; + JournalMetrics metrics; + + MMapCache *mmap; + RemoteServer *server; + char *hashmap_key; + + uint64_t seqnum; + + int n_ref; +} Writer; + +Writer* writer_new(RemoteServer* server); +Writer* writer_free(Writer *w); + +Writer* writer_ref(Writer *w); +Writer* writer_unref(Writer *w); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Writer*, writer_unref); +#define _cleanup_writer_unref_ _cleanup_(writer_unrefp) + +int writer_write(Writer *s, + struct iovec_wrapper *iovw, + dual_timestamp *ts, + bool compress, + bool seal); + +typedef enum JournalWriteSplitMode { + JOURNAL_WRITE_SPLIT_NONE, + JOURNAL_WRITE_SPLIT_HOST, + _JOURNAL_WRITE_SPLIT_MAX, + _JOURNAL_WRITE_SPLIT_INVALID = -1 +} JournalWriteSplitMode; diff --git a/src/grp-journal-remote/systemd-journal-remote/journal-remote.c b/src/grp-journal-remote/systemd-journal-remote/journal-remote.c new file mode 100644 index 0000000000..9b4d12d336 --- /dev/null +++ b/src/grp-journal-remote/systemd-journal-remote/journal-remote.c @@ -0,0 +1,1601 @@ +/*** + This file is part of systemd. + + Copyright 2012 Zbigniew Jędrzejewski-Szmek + + 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 +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_GNUTLS +#include +#endif + +#include + +#include "alloc-util.h" +#include "conf-parser.h" +#include "def.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "journal-file.h" +#include "journal-remote-write.h" +#include "journal-remote.h" +#include "journald-native.h" +#include "macro.h" +#include "parse-util.h" +#include "signal-util.h" +#include "socket-util.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" + +#define REMOTE_JOURNAL_PATH "/var/log/journal/remote" + +#define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-remote.pem" +#define CERT_FILE CERTIFICATE_ROOT "/certs/journal-remote.pem" +#define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem" + +static char* arg_url = NULL; +static char* arg_getter = NULL; +static char* arg_listen_raw = NULL; +static char* arg_listen_http = NULL; +static char* arg_listen_https = NULL; +static char** arg_files = NULL; +static int arg_compress = true; +static int arg_seal = false; +static int http_socket = -1, https_socket = -1; +static char** arg_gnutls_log = NULL; + +static JournalWriteSplitMode arg_split_mode = JOURNAL_WRITE_SPLIT_HOST; +static char* arg_output = NULL; + +static char *arg_key = NULL; +static char *arg_cert = NULL; +static char *arg_trust = NULL; +static bool arg_trust_all = false; + +/********************************************************************** + ********************************************************************** + **********************************************************************/ + +static int spawn_child(const char* child, char** argv) { + int fd[2]; + pid_t parent_pid, child_pid; + int r; + + if (pipe(fd) < 0) + return log_error_errno(errno, "Failed to create pager pipe: %m"); + + parent_pid = getpid(); + + child_pid = fork(); + if (child_pid < 0) { + r = log_error_errno(errno, "Failed to fork: %m"); + safe_close_pair(fd); + return r; + } + + /* In the child */ + if (child_pid == 0) { + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + r = dup2(fd[1], STDOUT_FILENO); + if (r < 0) { + log_error_errno(errno, "Failed to dup pipe to stdout: %m"); + _exit(EXIT_FAILURE); + } + + safe_close_pair(fd); + + /* Make sure the child goes away when the parent dies */ + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + _exit(EXIT_FAILURE); + + /* Check whether our parent died before we were able + * to set the death signal */ + if (getppid() != parent_pid) + _exit(EXIT_SUCCESS); + + execvp(child, argv); + log_error_errno(errno, "Failed to exec child %s: %m", child); + _exit(EXIT_FAILURE); + } + + r = close(fd[1]); + if (r < 0) + log_warning_errno(errno, "Failed to close write end of pipe: %m"); + + return fd[0]; +} + +static int spawn_curl(const char* url) { + char **argv = STRV_MAKE("curl", + "-HAccept: application/vnd.fdo.journal", + "--silent", + "--show-error", + url); + int r; + + r = spawn_child("curl", argv); + if (r < 0) + log_error_errno(r, "Failed to spawn curl: %m"); + return r; +} + +static int spawn_getter(const char *getter) { + int r; + _cleanup_strv_free_ char **words = NULL; + + assert(getter); + r = strv_split_extract(&words, getter, WHITESPACE, EXTRACT_QUOTES); + if (r < 0) + return log_error_errno(r, "Failed to split getter option: %m"); + + r = spawn_child(words[0], words); + if (r < 0) + log_error_errno(r, "Failed to spawn getter %s: %m", getter); + + return r; +} + +#define filename_escape(s) xescape((s), "/ ") + +static int open_output(Writer *w, const char* host) { + _cleanup_free_ char *_output = NULL; + const char *output; + int r; + + switch (arg_split_mode) { + case JOURNAL_WRITE_SPLIT_NONE: + output = arg_output ?: REMOTE_JOURNAL_PATH "/remote.journal"; + break; + + case JOURNAL_WRITE_SPLIT_HOST: { + _cleanup_free_ char *name; + + assert(host); + + name = filename_escape(host); + if (!name) + return log_oom(); + + r = asprintf(&_output, "%s/remote-%s.journal", + arg_output ?: REMOTE_JOURNAL_PATH, + name); + if (r < 0) + return log_oom(); + + output = _output; + break; + } + + default: + assert_not_reached("what?"); + } + + r = journal_file_open_reliably(output, + O_RDWR|O_CREAT, 0640, + arg_compress, arg_seal, + &w->metrics, + w->mmap, NULL, + NULL, &w->journal); + if (r < 0) + log_error_errno(r, "Failed to open output journal %s: %m", + output); + else + log_debug("Opened output file %s", w->journal->path); + return r; +} + +/********************************************************************** + ********************************************************************** + **********************************************************************/ + +static int init_writer_hashmap(RemoteServer *s) { + static const struct hash_ops *hash_ops[] = { + [JOURNAL_WRITE_SPLIT_NONE] = NULL, + [JOURNAL_WRITE_SPLIT_HOST] = &string_hash_ops, + }; + + assert(arg_split_mode >= 0 && arg_split_mode < (int) ELEMENTSOF(hash_ops)); + + s->writers = hashmap_new(hash_ops[arg_split_mode]); + if (!s->writers) + return log_oom(); + + return 0; +} + +static int get_writer(RemoteServer *s, const char *host, + Writer **writer) { + const void *key; + _cleanup_writer_unref_ Writer *w = NULL; + int r; + + switch(arg_split_mode) { + case JOURNAL_WRITE_SPLIT_NONE: + key = "one and only"; + break; + + case JOURNAL_WRITE_SPLIT_HOST: + assert(host); + key = host; + break; + + default: + assert_not_reached("what split mode?"); + } + + w = hashmap_get(s->writers, key); + if (w) + writer_ref(w); + else { + w = writer_new(s); + if (!w) + return log_oom(); + + if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST) { + w->hashmap_key = strdup(key); + if (!w->hashmap_key) + return log_oom(); + } + + r = open_output(w, host); + if (r < 0) + return r; + + r = hashmap_put(s->writers, w->hashmap_key ?: key, w); + if (r < 0) + return r; + } + + *writer = w; + w = NULL; + return 0; +} + +/********************************************************************** + ********************************************************************** + **********************************************************************/ + +/* This should go away as soon as µhttpd allows state to be passed around. */ +static RemoteServer *server; + +static int dispatch_raw_source_event(sd_event_source *event, + int fd, + uint32_t revents, + void *userdata); +static int dispatch_raw_source_until_block(sd_event_source *event, + void *userdata); +static int dispatch_blocking_source_event(sd_event_source *event, + void *userdata); +static int dispatch_raw_connection_event(sd_event_source *event, + int fd, + uint32_t revents, + void *userdata); +static int dispatch_http_event(sd_event_source *event, + int fd, + uint32_t revents, + void *userdata); + +static int get_source_for_fd(RemoteServer *s, + int fd, char *name, RemoteSource **source) { + Writer *writer; + int r; + + /* This takes ownership of name, but only on success. */ + + assert(fd >= 0); + assert(source); + + if (!GREEDY_REALLOC0(s->sources, s->sources_size, fd + 1)) + return log_oom(); + + r = get_writer(s, name, &writer); + if (r < 0) + return log_warning_errno(r, "Failed to get writer for source %s: %m", + name); + + if (s->sources[fd] == NULL) { + s->sources[fd] = source_new(fd, false, name, writer); + if (!s->sources[fd]) { + writer_unref(writer); + return log_oom(); + } + + s->active++; + } + + *source = s->sources[fd]; + return 0; +} + +static int remove_source(RemoteServer *s, int fd) { + RemoteSource *source; + + assert(s); + assert(fd >= 0 && fd < (ssize_t) s->sources_size); + + source = s->sources[fd]; + if (source) { + /* this closes fd too */ + source_free(source); + s->sources[fd] = NULL; + s->active--; + } + + return 0; +} + +static int add_source(RemoteServer *s, int fd, char* name, bool own_name) { + + RemoteSource *source = NULL; + int r; + + /* This takes ownership of name, even on failure, if own_name is true. */ + + assert(s); + assert(fd >= 0); + assert(name); + + if (!own_name) { + name = strdup(name); + if (!name) + return log_oom(); + } + + r = get_source_for_fd(s, fd, name, &source); + if (r < 0) { + log_error_errno(r, "Failed to create source for fd:%d (%s): %m", + fd, name); + free(name); + return r; + } + + r = sd_event_add_io(s->events, &source->event, + fd, EPOLLIN|EPOLLRDHUP|EPOLLPRI, + dispatch_raw_source_event, source); + if (r == 0) { + /* Add additional source for buffer processing. It will be + * enabled later. */ + r = sd_event_add_defer(s->events, &source->buffer_event, + dispatch_raw_source_until_block, source); + if (r == 0) + sd_event_source_set_enabled(source->buffer_event, SD_EVENT_OFF); + } else if (r == -EPERM) { + log_debug("Falling back to sd_event_add_defer for fd:%d (%s)", fd, name); + r = sd_event_add_defer(s->events, &source->event, + dispatch_blocking_source_event, source); + if (r == 0) + sd_event_source_set_enabled(source->event, SD_EVENT_ON); + } + if (r < 0) { + log_error_errno(r, "Failed to register event source for fd:%d: %m", + fd); + goto error; + } + + r = sd_event_source_set_description(source->event, name); + if (r < 0) { + log_error_errno(r, "Failed to set source name for fd:%d: %m", fd); + goto error; + } + + return 1; /* work to do */ + + error: + remove_source(s, fd); + return r; +} + +static int add_raw_socket(RemoteServer *s, int fd) { + int r; + _cleanup_close_ int fd_ = fd; + char name[sizeof("raw-socket-")-1 + DECIMAL_STR_MAX(int) + 1]; + + assert(fd >= 0); + + r = sd_event_add_io(s->events, &s->listen_event, + fd, EPOLLIN, + dispatch_raw_connection_event, s); + if (r < 0) + return r; + + xsprintf(name, "raw-socket-%d", fd); + + r = sd_event_source_set_description(s->listen_event, name); + if (r < 0) + return r; + + fd_ = -1; + s->active++; + return 0; +} + +static int setup_raw_socket(RemoteServer *s, const char *address) { + int fd; + + fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM, SOCK_CLOEXEC); + if (fd < 0) + return fd; + + return add_raw_socket(s, fd); +} + +/********************************************************************** + ********************************************************************** + **********************************************************************/ + +static int request_meta(void **connection_cls, int fd, char *hostname) { + RemoteSource *source; + Writer *writer; + int r; + + assert(connection_cls); + if (*connection_cls) + return 0; + + r = get_writer(server, hostname, &writer); + if (r < 0) + return log_warning_errno(r, "Failed to get writer for source %s: %m", + hostname); + + source = source_new(fd, true, hostname, writer); + if (!source) { + writer_unref(writer); + return log_oom(); + } + + log_debug("Added RemoteSource as connection metadata %p", source); + + *connection_cls = source; + return 0; +} + +static void request_meta_free(void *cls, + struct MHD_Connection *connection, + void **connection_cls, + enum MHD_RequestTerminationCode toe) { + RemoteSource *s; + + assert(connection_cls); + s = *connection_cls; + + if (s) { + log_debug("Cleaning up connection metadata %p", s); + source_free(s); + *connection_cls = NULL; + } +} + +static int process_http_upload( + struct MHD_Connection *connection, + const char *upload_data, + size_t *upload_data_size, + RemoteSource *source) { + + bool finished = false; + size_t remaining; + int r; + + assert(source); + + log_trace("%s: connection %p, %zu bytes", + __func__, connection, *upload_data_size); + + if (*upload_data_size) { + log_trace("Received %zu bytes", *upload_data_size); + + r = push_data(source, upload_data, *upload_data_size); + if (r < 0) + return mhd_respond_oom(connection); + + *upload_data_size = 0; + } else + finished = true; + + for (;;) { + r = process_source(source, arg_compress, arg_seal); + if (r == -EAGAIN) + break; + else if (r < 0) { + log_warning("Failed to process data for connection %p", connection); + if (r == -E2BIG) + return mhd_respondf(connection, + MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, + "Entry is too large, maximum is %u bytes.\n", + DATA_SIZE_MAX); + else + return mhd_respondf(connection, + MHD_HTTP_UNPROCESSABLE_ENTITY, + "Processing failed: %s.", strerror(-r)); + } + } + + if (!finished) + return MHD_YES; + + /* The upload is finished */ + + remaining = source_non_empty(source); + if (remaining > 0) { + log_warning("Premature EOFbyte. %zu bytes lost.", remaining); + return mhd_respondf(connection, MHD_HTTP_EXPECTATION_FAILED, + "Premature EOF. %zu bytes of trailing data not processed.", + remaining); + } + + return mhd_respond(connection, MHD_HTTP_ACCEPTED, "OK.\n"); +}; + +static int request_handler( + void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t *upload_data_size, + void **connection_cls) { + + const char *header; + int r, code, fd; + _cleanup_free_ char *hostname = NULL; + + assert(connection); + assert(connection_cls); + assert(url); + assert(method); + + log_trace("Handling a connection %s %s %s", method, url, version); + + if (*connection_cls) + return process_http_upload(connection, + upload_data, upload_data_size, + *connection_cls); + + if (!streq(method, "POST")) + return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, + "Unsupported method.\n"); + + if (!streq(url, "/upload")) + return mhd_respond(connection, MHD_HTTP_NOT_FOUND, + "Not found.\n"); + + header = MHD_lookup_connection_value(connection, + MHD_HEADER_KIND, "Content-Type"); + if (!header || !streq(header, "application/vnd.fdo.journal")) + return mhd_respond(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE, + "Content-Type: application/vnd.fdo.journal" + " is required.\n"); + + { + const union MHD_ConnectionInfo *ci; + + ci = MHD_get_connection_info(connection, + MHD_CONNECTION_INFO_CONNECTION_FD); + if (!ci) { + log_error("MHD_get_connection_info failed: cannot get remote fd"); + return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, + "Cannot check remote address"); + } + + fd = ci->connect_fd; + assert(fd >= 0); + } + + if (server->check_trust) { + r = check_permissions(connection, &code, &hostname); + if (r < 0) + return code; + } else { + r = getpeername_pretty(fd, false, &hostname); + if (r < 0) + return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, + "Cannot check remote hostname"); + } + + assert(hostname); + + r = request_meta(connection_cls, fd, hostname); + if (r == -ENOMEM) + return respond_oom(connection); + else if (r < 0) + return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, + strerror(-r)); + + hostname = NULL; + return MHD_YES; +} + +static int setup_microhttpd_server(RemoteServer *s, + int fd, + const char *key, + const char *cert, + const char *trust) { + struct MHD_OptionItem opts[] = { + { MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) request_meta_free}, + { MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) microhttpd_logger}, + { MHD_OPTION_LISTEN_SOCKET, fd}, + { MHD_OPTION_CONNECTION_MEMORY_LIMIT, 128*1024}, + { MHD_OPTION_END}, + { MHD_OPTION_END}, + { MHD_OPTION_END}, + { MHD_OPTION_END}}; + int opts_pos = 4; + int flags = + MHD_USE_DEBUG | + MHD_USE_DUAL_STACK | + MHD_USE_EPOLL_LINUX_ONLY | + MHD_USE_PEDANTIC_CHECKS | + MHD_USE_PIPE_FOR_SHUTDOWN; + + const union MHD_DaemonInfo *info; + int r, epoll_fd; + MHDDaemonWrapper *d; + + assert(fd >= 0); + + r = fd_nonblock(fd, true); + if (r < 0) + return log_error_errno(r, "Failed to make fd:%d nonblocking: %m", fd); + + if (key) { + assert(cert); + + opts[opts_pos++] = (struct MHD_OptionItem) + {MHD_OPTION_HTTPS_MEM_KEY, 0, (char*) key}; + opts[opts_pos++] = (struct MHD_OptionItem) + {MHD_OPTION_HTTPS_MEM_CERT, 0, (char*) cert}; + + flags |= MHD_USE_SSL; + + if (trust) + opts[opts_pos++] = (struct MHD_OptionItem) + {MHD_OPTION_HTTPS_MEM_TRUST, 0, (char*) trust}; + } + + d = new(MHDDaemonWrapper, 1); + if (!d) + return log_oom(); + + d->fd = (uint64_t) fd; + + d->daemon = MHD_start_daemon(flags, 0, + NULL, NULL, + request_handler, NULL, + MHD_OPTION_ARRAY, opts, + MHD_OPTION_END); + if (!d->daemon) { + log_error("Failed to start µhttp daemon"); + r = -EINVAL; + goto error; + } + + log_debug("Started MHD %s daemon on fd:%d (wrapper @ %p)", + key ? "HTTPS" : "HTTP", fd, d); + + + info = MHD_get_daemon_info(d->daemon, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY); + if (!info) { + log_error("µhttp returned NULL daemon info"); + r = -EOPNOTSUPP; + goto error; + } + + epoll_fd = info->listen_fd; + if (epoll_fd < 0) { + log_error("µhttp epoll fd is invalid"); + r = -EUCLEAN; + goto error; + } + + r = sd_event_add_io(s->events, &d->event, + epoll_fd, EPOLLIN, + dispatch_http_event, d); + if (r < 0) { + log_error_errno(r, "Failed to add event callback: %m"); + goto error; + } + + r = sd_event_source_set_description(d->event, "epoll-fd"); + if (r < 0) { + log_error_errno(r, "Failed to set source name: %m"); + goto error; + } + + r = hashmap_ensure_allocated(&s->daemons, &uint64_hash_ops); + if (r < 0) { + log_oom(); + goto error; + } + + r = hashmap_put(s->daemons, &d->fd, d); + if (r < 0) { + log_error_errno(r, "Failed to add daemon to hashmap: %m"); + goto error; + } + + s->active++; + return 0; + +error: + MHD_stop_daemon(d->daemon); + free(d->daemon); + free(d); + return r; +} + +static int setup_microhttpd_socket(RemoteServer *s, + const char *address, + const char *key, + const char *cert, + const char *trust) { + int fd; + + fd = make_socket_fd(LOG_DEBUG, address, SOCK_STREAM, SOCK_CLOEXEC); + if (fd < 0) + return fd; + + return setup_microhttpd_server(s, fd, key, cert, trust); +} + +static int dispatch_http_event(sd_event_source *event, + int fd, + uint32_t revents, + void *userdata) { + MHDDaemonWrapper *d = userdata; + int r; + + assert(d); + + r = MHD_run(d->daemon); + if (r == MHD_NO) { + log_error("MHD_run failed!"); + // XXX: unregister daemon + return -EINVAL; + } + + return 1; /* work to do */ +} + +/********************************************************************** + ********************************************************************** + **********************************************************************/ + +static int setup_signals(RemoteServer *s) { + int r; + + assert(s); + + assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, -1) >= 0); + + r = sd_event_add_signal(s->events, &s->sigterm_event, SIGTERM, NULL, s); + if (r < 0) + return r; + + r = sd_event_add_signal(s->events, &s->sigint_event, SIGINT, NULL, s); + if (r < 0) + return r; + + return 0; +} + +static int negative_fd(const char *spec) { + /* Return a non-positive number as its inverse, -EINVAL otherwise. */ + + int fd, r; + + r = safe_atoi(spec, &fd); + if (r < 0) + return r; + + if (fd > 0) + return -EINVAL; + else + return -fd; +} + +static int remoteserver_init(RemoteServer *s, + const char* key, + const char* cert, + const char* trust) { + int r, n, fd; + char **file; + + assert(s); + + if ((arg_listen_raw || arg_listen_http) && trust) { + log_error("Option --trust makes all non-HTTPS connections untrusted."); + return -EINVAL; + } + + r = sd_event_default(&s->events); + if (r < 0) + return log_error_errno(r, "Failed to allocate event loop: %m"); + + setup_signals(s); + + assert(server == NULL); + server = s; + + r = init_writer_hashmap(s); + if (r < 0) + return r; + + n = sd_listen_fds(true); + if (n < 0) + return log_error_errno(n, "Failed to read listening file descriptors from environment: %m"); + else + log_debug("Received %d descriptors", n); + + if (MAX(http_socket, https_socket) >= SD_LISTEN_FDS_START + n) { + log_error("Received fewer sockets than expected"); + return -EBADFD; + } + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { + if (sd_is_socket(fd, AF_UNSPEC, 0, true)) { + log_debug("Received a listening socket (fd:%d)", fd); + + if (fd == http_socket) + r = setup_microhttpd_server(s, fd, NULL, NULL, NULL); + else if (fd == https_socket) + r = setup_microhttpd_server(s, fd, key, cert, trust); + else + r = add_raw_socket(s, fd); + } else if (sd_is_socket(fd, AF_UNSPEC, 0, false)) { + char *hostname; + + r = getpeername_pretty(fd, false, &hostname); + if (r < 0) + return log_error_errno(r, "Failed to retrieve remote name: %m"); + + log_debug("Received a connection socket (fd:%d) from %s", fd, hostname); + + r = add_source(s, fd, hostname, true); + } else { + log_error("Unknown socket passed on fd:%d", fd); + + return -EINVAL; + } + + if (r < 0) + return log_error_errno(r, "Failed to register socket (fd:%d): %m", + fd); + } + + if (arg_getter) { + log_info("Spawning getter %s...", arg_getter); + fd = spawn_getter(arg_getter); + if (fd < 0) + return fd; + + r = add_source(s, fd, (char*) arg_output, false); + if (r < 0) + return r; + } + + if (arg_url) { + const char *url; + char *hostname, *p; + + if (!strstr(arg_url, "/entries")) { + if (endswith(arg_url, "/")) + url = strjoina(arg_url, "entries"); + else + url = strjoina(arg_url, "/entries"); + } + else + url = strdupa(arg_url); + + log_info("Spawning curl %s...", url); + fd = spawn_curl(url); + if (fd < 0) + return fd; + + hostname = + startswith(arg_url, "https://") ?: + startswith(arg_url, "http://") ?: + arg_url; + + hostname = strdupa(hostname); + if ((p = strchr(hostname, '/'))) + *p = '\0'; + if ((p = strchr(hostname, ':'))) + *p = '\0'; + + r = add_source(s, fd, hostname, false); + if (r < 0) + return r; + } + + if (arg_listen_raw) { + log_debug("Listening on a socket..."); + r = setup_raw_socket(s, arg_listen_raw); + if (r < 0) + return r; + } + + if (arg_listen_http) { + r = setup_microhttpd_socket(s, arg_listen_http, NULL, NULL, NULL); + if (r < 0) + return r; + } + + if (arg_listen_https) { + r = setup_microhttpd_socket(s, arg_listen_https, key, cert, trust); + if (r < 0) + return r; + } + + STRV_FOREACH(file, arg_files) { + const char *output_name; + + if (streq(*file, "-")) { + log_debug("Using standard input as source."); + + fd = STDIN_FILENO; + output_name = "stdin"; + } else { + log_debug("Reading file %s...", *file); + + fd = open(*file, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", *file); + output_name = *file; + } + + r = add_source(s, fd, (char*) output_name, false); + if (r < 0) + return r; + } + + if (s->active == 0) { + log_error("Zero sources specified"); + return -EINVAL; + } + + if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE) { + /* In this case we know what the writer will be + called, so we can create it and verify that we can + create output as expected. */ + r = get_writer(s, NULL, &s->_single_writer); + if (r < 0) + return r; + } + + return 0; +} + +static void server_destroy(RemoteServer *s) { + size_t i; + MHDDaemonWrapper *d; + + while ((d = hashmap_steal_first(s->daemons))) { + MHD_stop_daemon(d->daemon); + sd_event_source_unref(d->event); + free(d); + } + + hashmap_free(s->daemons); + + assert(s->sources_size == 0 || s->sources); + for (i = 0; i < s->sources_size; i++) + remove_source(s, i); + free(s->sources); + + writer_unref(s->_single_writer); + hashmap_free(s->writers); + + sd_event_source_unref(s->sigterm_event); + sd_event_source_unref(s->sigint_event); + sd_event_source_unref(s->listen_event); + sd_event_unref(s->events); + + /* fds that we're listening on remain open... */ +} + +/********************************************************************** + ********************************************************************** + **********************************************************************/ + +static int handle_raw_source(sd_event_source *event, + int fd, + uint32_t revents, + RemoteServer *s) { + + RemoteSource *source; + int r; + + /* Returns 1 if there might be more data pending, + * 0 if data is currently exhausted, negative on error. + */ + + assert(fd >= 0 && fd < (ssize_t) s->sources_size); + source = s->sources[fd]; + assert(source->fd == fd); + + r = process_source(source, arg_compress, arg_seal); + if (source->state == STATE_EOF) { + size_t remaining; + + log_debug("EOF reached with source fd:%d (%s)", + source->fd, source->name); + + remaining = source_non_empty(source); + if (remaining > 0) + log_notice("Premature EOF. %zu bytes lost.", remaining); + remove_source(s, source->fd); + log_debug("%zu active sources remaining", s->active); + return 0; + } else if (r == -E2BIG) { + log_notice_errno(E2BIG, "Entry too big, skipped"); + return 1; + } else if (r == -EAGAIN) { + return 0; + } else if (r < 0) { + log_debug_errno(r, "Closing connection: %m"); + remove_source(server, fd); + return 0; + } else + return 1; +} + +static int dispatch_raw_source_until_block(sd_event_source *event, + void *userdata) { + RemoteSource *source = userdata; + int r; + + /* Make sure event stays around even if source is destroyed */ + sd_event_source_ref(event); + + r = handle_raw_source(event, source->fd, EPOLLIN, server); + if (r != 1) + /* No more data for now */ + sd_event_source_set_enabled(event, SD_EVENT_OFF); + + sd_event_source_unref(event); + + return r; +} + +static int dispatch_raw_source_event(sd_event_source *event, + int fd, + uint32_t revents, + void *userdata) { + RemoteSource *source = userdata; + int r; + + assert(source->event); + assert(source->buffer_event); + + r = handle_raw_source(event, fd, EPOLLIN, server); + if (r == 1) + /* Might have more data. We need to rerun the handler + * until we are sure the buffer is exhausted. */ + sd_event_source_set_enabled(source->buffer_event, SD_EVENT_ON); + + return r; +} + +static int dispatch_blocking_source_event(sd_event_source *event, + void *userdata) { + RemoteSource *source = userdata; + + return handle_raw_source(event, source->fd, EPOLLIN, server); +} + +static int accept_connection(const char* type, int fd, + SocketAddress *addr, char **hostname) { + int fd2, r; + + log_debug("Accepting new %s connection on fd:%d", type, fd); + fd2 = accept4(fd, &addr->sockaddr.sa, &addr->size, SOCK_NONBLOCK|SOCK_CLOEXEC); + if (fd2 < 0) + return log_error_errno(errno, "accept() on fd:%d failed: %m", fd); + + switch(socket_address_family(addr)) { + case AF_INET: + case AF_INET6: { + _cleanup_free_ char *a = NULL; + char *b; + + r = socket_address_print(addr, &a); + if (r < 0) { + log_error_errno(r, "socket_address_print(): %m"); + close(fd2); + return r; + } + + r = socknameinfo_pretty(&addr->sockaddr, addr->size, &b); + if (r < 0) { + log_error_errno(r, "Resolving hostname failed: %m"); + close(fd2); + return r; + } + + log_debug("Accepted %s %s connection from %s", + type, + socket_address_family(addr) == AF_INET ? "IP" : "IPv6", + a); + + *hostname = b; + + return fd2; + }; + default: + log_error("Rejected %s connection with unsupported family %d", + type, socket_address_family(addr)); + close(fd2); + + return -EINVAL; + } +} + +static int dispatch_raw_connection_event(sd_event_source *event, + int fd, + uint32_t revents, + void *userdata) { + RemoteServer *s = userdata; + int fd2; + SocketAddress addr = { + .size = sizeof(union sockaddr_union), + .type = SOCK_STREAM, + }; + char *hostname = NULL; + + fd2 = accept_connection("raw", fd, &addr, &hostname); + if (fd2 < 0) + return fd2; + + return add_source(s, fd2, hostname, true); +} + +/********************************************************************** + ********************************************************************** + **********************************************************************/ + +static const char* const journal_write_split_mode_table[_JOURNAL_WRITE_SPLIT_MAX] = { + [JOURNAL_WRITE_SPLIT_NONE] = "none", + [JOURNAL_WRITE_SPLIT_HOST] = "host", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(journal_write_split_mode, JournalWriteSplitMode); +static DEFINE_CONFIG_PARSE_ENUM(config_parse_write_split_mode, + journal_write_split_mode, + JournalWriteSplitMode, + "Failed to parse split mode setting"); + +static int parse_config(void) { + const ConfigTableItem items[] = { + { "Remote", "Seal", config_parse_bool, 0, &arg_seal }, + { "Remote", "SplitMode", config_parse_write_split_mode, 0, &arg_split_mode }, + { "Remote", "ServerKeyFile", config_parse_path, 0, &arg_key }, + { "Remote", "ServerCertificateFile", config_parse_path, 0, &arg_cert }, + { "Remote", "TrustedCertificateFile", config_parse_path, 0, &arg_trust }, + {}}; + + return config_parse_many(PKGSYSCONFDIR "/journal-remote.conf", + CONF_PATHS_NULSTR("systemd/journal-remote.conf.d"), + "Remote\0", config_item_table_lookup, items, + false, NULL); +} + +static void help(void) { + printf("%s [OPTIONS...] {FILE|-}...\n\n" + "Write external journal events to journal file(s).\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --url=URL Read events from systemd-journal-gatewayd at URL\n" + " --getter=COMMAND Read events from the output of COMMAND\n" + " --listen-raw=ADDR Listen for connections at ADDR\n" + " --listen-http=ADDR Listen for HTTP connections at ADDR\n" + " --listen-https=ADDR Listen for HTTPS connections at ADDR\n" + " -o --output=FILE|DIR Write output to FILE or DIR/external-*.journal\n" + " --compress[=BOOL] XZ-compress the output journal (default: yes)\n" + " --seal[=BOOL] Use event sealing (default: no)\n" + " --key=FILENAME SSL key in PEM format (default:\n" + " \"" PRIV_KEY_FILE "\")\n" + " --cert=FILENAME SSL certificate in PEM format (default:\n" + " \"" CERT_FILE "\")\n" + " --trust=FILENAME|all SSL CA certificate or disable checking (default:\n" + " \"" TRUST_FILE "\")\n" + " --gnutls-log=CATEGORY...\n" + " Specify a list of gnutls logging categories\n" + " --split-mode=none|host How many output files to create\n" + "\n" + "Note: file descriptors from sd_listen_fds() will be consumed, too.\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_URL, + ARG_LISTEN_RAW, + ARG_LISTEN_HTTP, + ARG_LISTEN_HTTPS, + ARG_GETTER, + ARG_SPLIT_MODE, + ARG_COMPRESS, + ARG_SEAL, + ARG_KEY, + ARG_CERT, + ARG_TRUST, + ARG_GNUTLS_LOG, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "url", required_argument, NULL, ARG_URL }, + { "getter", required_argument, NULL, ARG_GETTER }, + { "listen-raw", required_argument, NULL, ARG_LISTEN_RAW }, + { "listen-http", required_argument, NULL, ARG_LISTEN_HTTP }, + { "listen-https", required_argument, NULL, ARG_LISTEN_HTTPS }, + { "output", required_argument, NULL, 'o' }, + { "split-mode", required_argument, NULL, ARG_SPLIT_MODE }, + { "compress", optional_argument, NULL, ARG_COMPRESS }, + { "seal", optional_argument, NULL, ARG_SEAL }, + { "key", required_argument, NULL, ARG_KEY }, + { "cert", required_argument, NULL, ARG_CERT }, + { "trust", required_argument, NULL, ARG_TRUST }, + { "gnutls-log", required_argument, NULL, ARG_GNUTLS_LOG }, + {} + }; + + int c, r; + bool type_a, type_b; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "ho:", options, NULL)) >= 0) + switch(c) { + case 'h': + help(); + return 0 /* done */; + + case ARG_VERSION: + return version(); + + case ARG_URL: + if (arg_url) { + log_error("cannot currently set more than one --url"); + return -EINVAL; + } + + arg_url = optarg; + break; + + case ARG_GETTER: + if (arg_getter) { + log_error("cannot currently use --getter more than once"); + return -EINVAL; + } + + arg_getter = optarg; + break; + + case ARG_LISTEN_RAW: + if (arg_listen_raw) { + log_error("cannot currently use --listen-raw more than once"); + return -EINVAL; + } + + arg_listen_raw = optarg; + break; + + case ARG_LISTEN_HTTP: + if (arg_listen_http || http_socket >= 0) { + log_error("cannot currently use --listen-http more than once"); + return -EINVAL; + } + + r = negative_fd(optarg); + if (r >= 0) + http_socket = r; + else + arg_listen_http = optarg; + break; + + case ARG_LISTEN_HTTPS: + if (arg_listen_https || https_socket >= 0) { + log_error("cannot currently use --listen-https more than once"); + return -EINVAL; + } + + r = negative_fd(optarg); + if (r >= 0) + https_socket = r; + else + arg_listen_https = optarg; + + break; + + case ARG_KEY: + if (arg_key) { + log_error("Key file specified twice"); + return -EINVAL; + } + + arg_key = strdup(optarg); + if (!arg_key) + return log_oom(); + + break; + + case ARG_CERT: + if (arg_cert) { + log_error("Certificate file specified twice"); + return -EINVAL; + } + + arg_cert = strdup(optarg); + if (!arg_cert) + return log_oom(); + + break; + + case ARG_TRUST: + if (arg_trust || arg_trust_all) { + log_error("Confusing trusted CA configuration"); + return -EINVAL; + } + + if (streq(optarg, "all")) + arg_trust_all = true; + else { +#ifdef HAVE_GNUTLS + arg_trust = strdup(optarg); + if (!arg_trust) + return log_oom(); +#else + log_error("Option --trust is not available."); + return -EINVAL; +#endif + } + + break; + + case 'o': + if (arg_output) { + log_error("cannot use --output/-o more than once"); + return -EINVAL; + } + + arg_output = optarg; + break; + + case ARG_SPLIT_MODE: + arg_split_mode = journal_write_split_mode_from_string(optarg); + if (arg_split_mode == _JOURNAL_WRITE_SPLIT_INVALID) { + log_error("Invalid split mode: %s", optarg); + return -EINVAL; + } + break; + + case ARG_COMPRESS: + if (optarg) { + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --compress= parameter."); + return -EINVAL; + } + + arg_compress = !!r; + } else + arg_compress = true; + + break; + + case ARG_SEAL: + if (optarg) { + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --seal= parameter."); + return -EINVAL; + } + + arg_seal = !!r; + } else + arg_seal = true; + + break; + + case ARG_GNUTLS_LOG: { +#ifdef HAVE_GNUTLS + const char* p = optarg; + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to parse --gnutls-log= argument: %m"); + + if (r == 0) + break; + + if (strv_push(&arg_gnutls_log, word) < 0) + return log_oom(); + + word = NULL; + } + break; +#else + log_error("Option --gnutls-log is not available."); + return -EINVAL; +#endif + } + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unknown option code."); + } + + if (optind < argc) + arg_files = argv + optind; + + type_a = arg_getter || !strv_isempty(arg_files); + type_b = arg_url + || arg_listen_raw + || arg_listen_http || arg_listen_https + || sd_listen_fds(false) > 0; + if (type_a && type_b) { + log_error("Cannot use file input or --getter with " + "--arg-listen-... or socket activation."); + return -EINVAL; + } + if (type_a) { + if (!arg_output) { + log_error("Option --output must be specified with file input or --getter."); + return -EINVAL; + } + + arg_split_mode = JOURNAL_WRITE_SPLIT_NONE; + } + + if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE + && arg_output && is_dir(arg_output, true) > 0) { + log_error("For SplitMode=none, output must be a file."); + return -EINVAL; + } + + if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST + && arg_output && is_dir(arg_output, true) <= 0) { + log_error("For SplitMode=host, output must be a directory."); + return -EINVAL; + } + + log_debug("Full config: SplitMode=%s Key=%s Cert=%s Trust=%s", + journal_write_split_mode_to_string(arg_split_mode), + strna(arg_key), + strna(arg_cert), + strna(arg_trust)); + + return 1 /* work to do */; +} + +static int load_certificates(char **key, char **cert, char **trust) { + int r; + + r = read_full_file(arg_key ?: PRIV_KEY_FILE, key, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read key from file '%s': %m", + arg_key ?: PRIV_KEY_FILE); + + r = read_full_file(arg_cert ?: CERT_FILE, cert, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read certificate from file '%s': %m", + arg_cert ?: CERT_FILE); + + if (arg_trust_all) + log_info("Certificate checking disabled."); + else { + r = read_full_file(arg_trust ?: TRUST_FILE, trust, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read CA certificate file '%s': %m", + arg_trust ?: TRUST_FILE); + } + + return 0; +} + +int main(int argc, char **argv) { + RemoteServer s = {}; + int r; + _cleanup_free_ char *key = NULL, *cert = NULL, *trust = NULL; + + log_show_color(true); + log_parse_environment(); + + r = parse_config(); + if (r < 0) + return EXIT_FAILURE; + + r = parse_argv(argc, argv); + if (r <= 0) + return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; + + + if (arg_listen_http || arg_listen_https) { + r = setup_gnutls_logger(arg_gnutls_log); + if (r < 0) + return EXIT_FAILURE; + } + + if (arg_listen_https || https_socket >= 0) + if (load_certificates(&key, &cert, &trust) < 0) + return EXIT_FAILURE; + + if (remoteserver_init(&s, key, cert, trust) < 0) + return EXIT_FAILURE; + + r = sd_event_set_watchdog(s.events, true); + if (r < 0) + log_error_errno(r, "Failed to enable watchdog: %m"); + else + log_debug("Watchdog is %s.", r > 0 ? "enabled" : "disabled"); + + log_debug("%s running as pid "PID_FMT, + program_invocation_short_name, getpid()); + sd_notify(false, + "READY=1\n" + "STATUS=Processing requests..."); + + while (s.active) { + r = sd_event_get_state(s.events); + if (r < 0) + break; + if (r == SD_EVENT_FINISHED) + break; + + r = sd_event_run(s.events, -1); + if (r < 0) { + log_error_errno(r, "Failed to run event loop: %m"); + break; + } + } + + sd_notifyf(false, + "STOPPING=1\n" + "STATUS=Shutting down after writing %" PRIu64 " entries...", s.event_count); + log_info("Finishing after writing %" PRIu64 " entries", s.event_count); + + server_destroy(&s); + + free(arg_key); + free(arg_cert); + free(arg_trust); + + return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/grp-journal-remote/systemd-journal-remote/journal-remote.conf.in b/src/grp-journal-remote/systemd-journal-remote/journal-remote.conf.in new file mode 100644 index 0000000000..7122d63362 --- /dev/null +++ b/src/grp-journal-remote/systemd-journal-remote/journal-remote.conf.in @@ -0,0 +1,6 @@ +[Remote] +# Seal=false +# SplitMode=host +# ServerKeyFile=@CERTIFICATEROOT@/private/journal-remote.pem +# ServerCertificateFile=@CERTIFICATEROOT@/certs/journal-remote.pem +# TrustedCertificateFile=@CERTIFICATEROOT@/ca/trusted.pem diff --git a/src/grp-journal-remote/systemd-journal-remote/journal-remote.h b/src/grp-journal-remote/systemd-journal-remote/journal-remote.h new file mode 100644 index 0000000000..58487e498a --- /dev/null +++ b/src/grp-journal-remote/systemd-journal-remote/journal-remote.h @@ -0,0 +1,52 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Zbigniew Jędrzejewski-Szmek + + 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 "hashmap.h" +#include "journal-remote-parse.h" +#include "journal-remote-write.h" +#include "microhttpd-util.h" + +typedef struct MHDDaemonWrapper MHDDaemonWrapper; + +struct MHDDaemonWrapper { + uint64_t fd; + struct MHD_Daemon *daemon; + + sd_event_source *event; +}; + +struct RemoteServer { + RemoteSource **sources; + size_t sources_size; + size_t active; + + sd_event *events; + sd_event_source *sigterm_event, *sigint_event, *listen_event; + + Hashmap *writers; + Writer *_single_writer; + uint64_t event_count; + + bool check_trust; + Hashmap *daemons; +}; diff --git a/src/grp-journal-remote/systemd-journal-upload/Makefile b/src/grp-journal-remote/systemd-journal-upload/Makefile new file mode 100644 index 0000000000..4d8fba8fd0 --- /dev/null +++ b/src/grp-journal-remote/systemd-journal-upload/Makefile @@ -0,0 +1,54 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(HAVE_LIBCURL),) +libexec_PROGRAMS += \ + systemd-journal-upload + +systemd_journal_upload_SOURCES = \ + src/journal-remote/journal-upload.h \ + src/journal-remote/journal-upload.c \ + src/journal-remote/journal-upload-journal.c + +systemd_journal_upload_CFLAGS = \ + $(AM_CFLAGS) \ + $(LIBCURL_CFLAGS) + +systemd_journal_upload_LDADD = \ + libshared.la \ + $(LIBCURL_LIBS) + +nodist_systemunit_DATA += \ + units/systemd-journal-upload.service + +nodist_pkgsysconf_DATA += \ + src/journal-remote/journal-upload.conf +endif + +EXTRA_DIST += \ + units/systemd-journal-upload.service.in \ + src/journal-remote/journal-upload.conf.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-journal-remote/systemd-journal-upload/journal-upload-journal.c b/src/grp-journal-remote/systemd-journal-upload/journal-upload-journal.c new file mode 100644 index 0000000000..aef095c8c9 --- /dev/null +++ b/src/grp-journal-remote/systemd-journal-upload/journal-upload-journal.c @@ -0,0 +1,422 @@ +/*** + This file is part of systemd. + + Copyright 2014 Zbigniew Jędrzejewski-Szmek + + 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 + +#include "alloc-util.h" +#include "journal-upload.h" +#include "log.h" +#include "utf8.h" +#include "util.h" +#include + +/** + * Write up to size bytes to buf. Return negative on error, and number of + * bytes written otherwise. The last case is a kind of an error too. + */ +static ssize_t write_entry(char *buf, size_t size, Uploader *u) { + int r; + size_t pos = 0; + + assert(size <= SSIZE_MAX); + + for (;;) { + + switch(u->entry_state) { + case ENTRY_CURSOR: { + u->current_cursor = mfree(u->current_cursor); + + r = sd_journal_get_cursor(u->journal, &u->current_cursor); + if (r < 0) + return log_error_errno(r, "Failed to get cursor: %m"); + + r = snprintf(buf + pos, size - pos, + "__CURSOR=%s\n", u->current_cursor); + if (pos + r > size) + /* not enough space */ + return pos; + + u->entry_state++; + + if (pos + r == size) { + /* exactly one character short, but we don't need it */ + buf[size - 1] = '\n'; + return size; + } + + pos += r; + } /* fall through */ + + case ENTRY_REALTIME: { + usec_t realtime; + + r = sd_journal_get_realtime_usec(u->journal, &realtime); + if (r < 0) + return log_error_errno(r, "Failed to get realtime timestamp: %m"); + + r = snprintf(buf + pos, size - pos, + "__REALTIME_TIMESTAMP="USEC_FMT"\n", realtime); + if (r + pos > size) + /* not enough space */ + return pos; + + u->entry_state++; + + if (r + pos == size) { + /* exactly one character short, but we don't need it */ + buf[size - 1] = '\n'; + return size; + } + + pos += r; + } /* fall through */ + + case ENTRY_MONOTONIC: { + usec_t monotonic; + sd_id128_t boot_id; + + r = sd_journal_get_monotonic_usec(u->journal, &monotonic, &boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get monotonic timestamp: %m"); + + r = snprintf(buf + pos, size - pos, + "__MONOTONIC_TIMESTAMP="USEC_FMT"\n", monotonic); + if (r + pos > size) + /* not enough space */ + return pos; + + u->entry_state++; + + if (r + pos == size) { + /* exactly one character short, but we don't need it */ + buf[size - 1] = '\n'; + return size; + } + + pos += r; + } /* fall through */ + + case ENTRY_BOOT_ID: { + sd_id128_t boot_id; + char sid[33]; + + r = sd_journal_get_monotonic_usec(u->journal, NULL, &boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get monotonic timestamp: %m"); + + r = snprintf(buf + pos, size - pos, + "_BOOT_ID=%s\n", sd_id128_to_string(boot_id, sid)); + if (r + pos > size) + /* not enough space */ + return pos; + + u->entry_state++; + + if (r + pos == size) { + /* exactly one character short, but we don't need it */ + buf[size - 1] = '\n'; + return size; + } + + pos += r; + } /* fall through */ + + case ENTRY_NEW_FIELD: { + u->field_pos = 0; + + r = sd_journal_enumerate_data(u->journal, + &u->field_data, + &u->field_length); + if (r < 0) + return log_error_errno(r, "Failed to move to next field in entry: %m"); + else if (r == 0) { + u->entry_state = ENTRY_OUTRO; + continue; + } + + if (!utf8_is_printable_newline(u->field_data, + u->field_length, false)) { + u->entry_state = ENTRY_BINARY_FIELD_START; + continue; + } + + u->entry_state++; + } /* fall through */ + + case ENTRY_TEXT_FIELD: + case ENTRY_BINARY_FIELD: { + bool done; + size_t tocopy; + + done = size - pos > u->field_length - u->field_pos; + if (done) + tocopy = u->field_length - u->field_pos; + else + tocopy = size - pos; + + memcpy(buf + pos, + (char*) u->field_data + u->field_pos, + tocopy); + + if (done) { + buf[pos + tocopy] = '\n'; + pos += tocopy + 1; + u->entry_state = ENTRY_NEW_FIELD; + continue; + } else { + u->field_pos += tocopy; + return size; + } + } + + case ENTRY_BINARY_FIELD_START: { + const char *c; + size_t len; + + c = memchr(u->field_data, '=', u->field_length); + if (!c || c == u->field_data) { + log_error("Invalid field."); + return -EINVAL; + } + + len = c - (const char*)u->field_data; + + /* need space for label + '\n' */ + if (size - pos < len + 1) + return pos; + + memcpy(buf + pos, u->field_data, len); + buf[pos + len] = '\n'; + pos += len + 1; + + u->field_pos = len + 1; + u->entry_state++; + } /* fall through */ + + case ENTRY_BINARY_FIELD_SIZE: { + uint64_t le64; + + /* need space for uint64_t */ + if (size - pos < 8) + return pos; + + le64 = htole64(u->field_length - u->field_pos); + memcpy(buf + pos, &le64, 8); + pos += 8; + + u->entry_state++; + continue; + } + + case ENTRY_OUTRO: + /* need space for '\n' */ + if (size - pos < 1) + return pos; + + buf[pos++] = '\n'; + u->entry_state++; + u->entries_sent++; + + return pos; + + default: + assert_not_reached("WTF?"); + } + } + assert_not_reached("WTF?"); +} + +static inline void check_update_watchdog(Uploader *u) { + usec_t after; + usec_t elapsed_time; + + if (u->watchdog_usec <= 0) + return; + + after = now(CLOCK_MONOTONIC); + elapsed_time = usec_sub(after, u->watchdog_timestamp); + if (elapsed_time > u->watchdog_usec / 2) { + log_debug("Update watchdog timer"); + sd_notify(false, "WATCHDOG=1"); + u->watchdog_timestamp = after; + } +} + +static size_t journal_input_callback(void *buf, size_t size, size_t nmemb, void *userp) { + Uploader *u = userp; + int r; + sd_journal *j; + size_t filled = 0; + ssize_t w; + + assert(u); + assert(nmemb <= SSIZE_MAX / size); + + check_update_watchdog(u); + + j = u->journal; + + while (j && filled < size * nmemb) { + if (u->entry_state == ENTRY_DONE) { + r = sd_journal_next(j); + if (r < 0) { + log_error_errno(r, "Failed to move to next entry in journal: %m"); + return CURL_READFUNC_ABORT; + } else if (r == 0) { + if (u->input_event) + log_debug("No more entries, waiting for journal."); + else { + log_info("No more entries, closing journal."); + close_journal_input(u); + } + + u->uploading = false; + + break; + } + + u->entry_state = ENTRY_CURSOR; + } + + w = write_entry((char*)buf + filled, size * nmemb - filled, u); + if (w < 0) + return CURL_READFUNC_ABORT; + filled += w; + + if (filled == 0) { + log_error("Buffer space is too small to write entry."); + return CURL_READFUNC_ABORT; + } else if (u->entry_state != ENTRY_DONE) + /* This means that all available space was used up */ + break; + + log_debug("Entry %zu (%s) has been uploaded.", + u->entries_sent, u->current_cursor); + } + + return filled; +} + +void close_journal_input(Uploader *u) { + assert(u); + + if (u->journal) { + log_debug("Closing journal input."); + + sd_journal_close(u->journal); + u->journal = NULL; + } + u->timeout = 0; +} + +static int process_journal_input(Uploader *u, int skip) { + int r; + + if (u->uploading) + return 0; + + r = sd_journal_next_skip(u->journal, skip); + if (r < 0) + return log_error_errno(r, "Failed to skip to next entry: %m"); + else if (r < skip) + return 0; + + /* have data */ + u->entry_state = ENTRY_CURSOR; + return start_upload(u, journal_input_callback, u); +} + +int check_journal_input(Uploader *u) { + if (u->input_event) { + int r; + + r = sd_journal_process(u->journal); + if (r < 0) { + log_error_errno(r, "Failed to process journal: %m"); + close_journal_input(u); + return r; + } + + if (r == SD_JOURNAL_NOP) + return 0; + } + + return process_journal_input(u, 1); +} + +static int dispatch_journal_input(sd_event_source *event, + int fd, + uint32_t revents, + void *userp) { + Uploader *u = userp; + + assert(u); + + if (u->uploading) + return 0; + + log_debug("Detected journal input, checking for new data."); + return check_journal_input(u); +} + +int open_journal_for_upload(Uploader *u, + sd_journal *j, + const char *cursor, + bool after_cursor, + bool follow) { + int fd, r, events; + + u->journal = j; + + sd_journal_set_data_threshold(j, 0); + + if (follow) { + fd = sd_journal_get_fd(j); + if (fd < 0) + return log_error_errno(fd, "sd_journal_get_fd failed: %m"); + + events = sd_journal_get_events(j); + + r = sd_journal_reliable_fd(j); + assert(r >= 0); + if (r > 0) + u->timeout = -1; + else + u->timeout = JOURNAL_UPLOAD_POLL_TIMEOUT; + + r = sd_event_add_io(u->events, &u->input_event, + fd, events, dispatch_journal_input, u); + if (r < 0) + return log_error_errno(r, "Failed to register input event: %m"); + + log_debug("Listening for journal events on fd:%d, timeout %d", + fd, u->timeout == (uint64_t) -1 ? -1 : (int) u->timeout); + } else + log_debug("Not listening for journal events."); + + if (cursor) { + r = sd_journal_seek_cursor(j, cursor); + if (r < 0) + return log_error_errno(r, "Failed to seek to cursor %s: %m", + cursor); + } + + return process_journal_input(u, 1 + !!after_cursor); +} diff --git a/src/grp-journal-remote/systemd-journal-upload/journal-upload.c b/src/grp-journal-remote/systemd-journal-upload/journal-upload.c new file mode 100644 index 0000000000..e622f6c1e1 --- /dev/null +++ b/src/grp-journal-remote/systemd-journal-upload/journal-upload.c @@ -0,0 +1,880 @@ +/*** + This file is part of systemd. + + Copyright 2014 Zbigniew Jędrzejewski-Szmek + + 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 +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "conf-parser.h" +#include "def.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "glob-util.h" +#include "journal-upload.h" +#include "log.h" +#include "mkdir.h" +#include "parse-util.h" +#include "sigbus.h" +#include "signal-util.h" +#include "string-util.h" +#include "util.h" + +#define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-upload.pem" +#define CERT_FILE CERTIFICATE_ROOT "/certs/journal-upload.pem" +#define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem" +#define DEFAULT_PORT 19532 + +static const char* arg_url = NULL; +static const char *arg_key = NULL; +static const char *arg_cert = NULL; +static const char *arg_trust = NULL; +static const char *arg_directory = NULL; +static char **arg_file = NULL; +static const char *arg_cursor = NULL; +static bool arg_after_cursor = false; +static int arg_journal_type = 0; +static const char *arg_machine = NULL; +static bool arg_merge = false; +static int arg_follow = -1; +static const char *arg_save_state = NULL; + +static void close_fd_input(Uploader *u); + +#define SERVER_ANSWER_KEEP 2048 + +#define STATE_FILE "/var/lib/systemd/journal-upload/state" + +#define easy_setopt(curl, opt, value, level, cmd) \ + do { \ + code = curl_easy_setopt(curl, opt, value); \ + if (code) { \ + log_full(level, \ + "curl_easy_setopt " #opt " failed: %s", \ + curl_easy_strerror(code)); \ + cmd; \ + } \ + } while (0) + +static size_t output_callback(char *buf, + size_t size, + size_t nmemb, + void *userp) { + Uploader *u = userp; + + assert(u); + + log_debug("The server answers (%zu bytes): %.*s", + size*nmemb, (int)(size*nmemb), buf); + + if (nmemb && !u->answer) { + u->answer = strndup(buf, size*nmemb); + if (!u->answer) + log_warning_errno(ENOMEM, "Failed to store server answer (%zu bytes): %m", + size*nmemb); + } + + return size * nmemb; +} + +static int check_cursor_updating(Uploader *u) { + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + if (!u->state_file) + return 0; + + r = mkdir_parents(u->state_file, 0755); + if (r < 0) + return log_error_errno(r, "Cannot create parent directory of state file %s: %m", + u->state_file); + + r = fopen_temporary(u->state_file, &f, &temp_path); + if (r < 0) + return log_error_errno(r, "Cannot save state to %s: %m", + u->state_file); + unlink(temp_path); + + return 0; +} + +static int update_cursor_state(Uploader *u) { + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + if (!u->state_file || !u->last_cursor) + return 0; + + r = fopen_temporary(u->state_file, &f, &temp_path); + if (r < 0) + goto fail; + + fprintf(f, + "# This is private data. Do not parse.\n" + "LAST_CURSOR=%s\n", + u->last_cursor); + + r = fflush_and_check(f); + if (r < 0) + goto fail; + + if (rename(temp_path, u->state_file) < 0) { + r = -errno; + goto fail; + } + + return 0; + +fail: + if (temp_path) + (void) unlink(temp_path); + + (void) unlink(u->state_file); + + return log_error_errno(r, "Failed to save state %s: %m", u->state_file); +} + +static int load_cursor_state(Uploader *u) { + int r; + + if (!u->state_file) + return 0; + + r = parse_env_file(u->state_file, NEWLINE, + "LAST_CURSOR", &u->last_cursor, + NULL); + + if (r == -ENOENT) + log_debug("State file %s is not present.", u->state_file); + else if (r < 0) + return log_error_errno(r, "Failed to read state file %s: %m", + u->state_file); + else + log_debug("Last cursor was %s", u->last_cursor); + + return 0; +} + + + +int start_upload(Uploader *u, + size_t (*input_callback)(void *ptr, + size_t size, + size_t nmemb, + void *userdata), + void *data) { + CURLcode code; + + assert(u); + assert(input_callback); + + if (!u->header) { + struct curl_slist *h; + + h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal"); + if (!h) + return log_oom(); + + h = curl_slist_append(h, "Transfer-Encoding: chunked"); + if (!h) { + curl_slist_free_all(h); + return log_oom(); + } + + h = curl_slist_append(h, "Accept: text/plain"); + if (!h) { + curl_slist_free_all(h); + return log_oom(); + } + + u->header = h; + } + + if (!u->easy) { + CURL *curl; + + curl = curl_easy_init(); + if (!curl) { + log_error("Call to curl_easy_init failed."); + return -ENOSR; + } + + /* tell it to POST to the URL */ + easy_setopt(curl, CURLOPT_POST, 1L, + LOG_ERR, return -EXFULL); + + easy_setopt(curl, CURLOPT_ERRORBUFFER, u->error, + LOG_ERR, return -EXFULL); + + /* set where to write to */ + easy_setopt(curl, CURLOPT_WRITEFUNCTION, output_callback, + LOG_ERR, return -EXFULL); + + easy_setopt(curl, CURLOPT_WRITEDATA, data, + LOG_ERR, return -EXFULL); + + /* set where to read from */ + easy_setopt(curl, CURLOPT_READFUNCTION, input_callback, + LOG_ERR, return -EXFULL); + + easy_setopt(curl, CURLOPT_READDATA, data, + LOG_ERR, return -EXFULL); + + /* use our special own mime type and chunked transfer */ + easy_setopt(curl, CURLOPT_HTTPHEADER, u->header, + LOG_ERR, return -EXFULL); + + if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) + /* enable verbose for easier tracing */ + easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, ); + + easy_setopt(curl, CURLOPT_USERAGENT, + "systemd-journal-upload " PACKAGE_STRING, + LOG_WARNING, ); + + if (arg_key || startswith(u->url, "https://")) { + easy_setopt(curl, CURLOPT_SSLKEY, arg_key ?: PRIV_KEY_FILE, + LOG_ERR, return -EXFULL); + easy_setopt(curl, CURLOPT_SSLCERT, arg_cert ?: CERT_FILE, + LOG_ERR, return -EXFULL); + } + + if (streq_ptr(arg_trust, "all")) + easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0, + LOG_ERR, return -EUCLEAN); + else if (arg_trust || startswith(u->url, "https://")) + easy_setopt(curl, CURLOPT_CAINFO, arg_trust ?: TRUST_FILE, + LOG_ERR, return -EXFULL); + + if (arg_key || arg_trust) + easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1, + LOG_WARNING, ); + + u->easy = curl; + } else { + /* truncate the potential old error message */ + u->error[0] = '\0'; + + free(u->answer); + u->answer = 0; + } + + /* upload to this place */ + code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url); + if (code) { + log_error("curl_easy_setopt CURLOPT_URL failed: %s", + curl_easy_strerror(code)); + return -EXFULL; + } + + u->uploading = true; + + return 0; +} + +static size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *userp) { + Uploader *u = userp; + + ssize_t r; + + assert(u); + assert(nmemb <= SSIZE_MAX / size); + + if (u->input < 0) + return 0; + + r = read(u->input, buf, size * nmemb); + log_debug("%s: allowed %zu, read %zd", __func__, size*nmemb, r); + + if (r > 0) + return r; + + u->uploading = false; + if (r == 0) { + log_debug("Reached EOF"); + close_fd_input(u); + return 0; + } else { + log_error_errno(errno, "Aborting transfer after read error on input: %m."); + return CURL_READFUNC_ABORT; + } +} + +static void close_fd_input(Uploader *u) { + assert(u); + + if (u->input >= 0) + close_nointr(u->input); + u->input = -1; + u->timeout = 0; +} + +static int dispatch_fd_input(sd_event_source *event, + int fd, + uint32_t revents, + void *userp) { + Uploader *u = userp; + + assert(u); + assert(fd >= 0); + + if (revents & EPOLLHUP) { + log_debug("Received HUP"); + close_fd_input(u); + return 0; + } + + if (!(revents & EPOLLIN)) { + log_warning("Unexpected poll event %"PRIu32".", revents); + return -EINVAL; + } + + if (u->uploading) { + log_warning("dispatch_fd_input called when uploading, ignoring."); + return 0; + } + + return start_upload(u, fd_input_callback, u); +} + +static int open_file_for_upload(Uploader *u, const char *filename) { + int fd, r = 0; + + if (streq(filename, "-")) + fd = STDIN_FILENO; + else { + fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", filename); + } + + u->input = fd; + + if (arg_follow) { + r = sd_event_add_io(u->events, &u->input_event, + fd, EPOLLIN, dispatch_fd_input, u); + if (r < 0) { + if (r != -EPERM || arg_follow > 0) + return log_error_errno(r, "Failed to register input event: %m"); + + /* Normal files should just be consumed without polling. */ + r = start_upload(u, fd_input_callback, u); + } + } + + return r; +} + +static int dispatch_sigterm(sd_event_source *event, + const struct signalfd_siginfo *si, + void *userdata) { + Uploader *u = userdata; + + assert(u); + + log_received_signal(LOG_INFO, si); + + close_fd_input(u); + close_journal_input(u); + + sd_event_exit(u->events, 0); + return 0; +} + +static int setup_signals(Uploader *u) { + int r; + + assert(u); + + assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, -1) >= 0); + + r = sd_event_add_signal(u->events, &u->sigterm_event, SIGTERM, dispatch_sigterm, u); + if (r < 0) + return r; + + r = sd_event_add_signal(u->events, &u->sigint_event, SIGINT, dispatch_sigterm, u); + if (r < 0) + return r; + + return 0; +} + +static int setup_uploader(Uploader *u, const char *url, const char *state_file) { + int r; + const char *host, *proto = ""; + + assert(u); + assert(url); + + memzero(u, sizeof(Uploader)); + u->input = -1; + + if (!(host = startswith(url, "http://")) && !(host = startswith(url, "https://"))) { + host = url; + proto = "https://"; + } + + if (strchr(host, ':')) + u->url = strjoin(proto, url, "/upload", NULL); + else { + char *t; + size_t x; + + t = strdupa(url); + x = strlen(t); + while (x > 0 && t[x - 1] == '/') + t[x - 1] = '\0'; + + u->url = strjoin(proto, t, ":" STRINGIFY(DEFAULT_PORT), "/upload", NULL); + } + if (!u->url) + return log_oom(); + + u->state_file = state_file; + + r = sd_event_default(&u->events); + if (r < 0) + return log_error_errno(r, "sd_event_default failed: %m"); + + r = setup_signals(u); + if (r < 0) + return log_error_errno(r, "Failed to set up signals: %m"); + + (void) sd_watchdog_enabled(false, &u->watchdog_usec); + + return load_cursor_state(u); +} + +static void destroy_uploader(Uploader *u) { + assert(u); + + curl_easy_cleanup(u->easy); + curl_slist_free_all(u->header); + free(u->answer); + + free(u->last_cursor); + free(u->current_cursor); + + free(u->url); + + u->input_event = sd_event_source_unref(u->input_event); + + close_fd_input(u); + close_journal_input(u); + + sd_event_source_unref(u->sigterm_event); + sd_event_source_unref(u->sigint_event); + sd_event_unref(u->events); +} + +static int perform_upload(Uploader *u) { + CURLcode code; + long status; + + assert(u); + + u->watchdog_timestamp = now(CLOCK_MONOTONIC); + code = curl_easy_perform(u->easy); + if (code) { + if (u->error[0]) + log_error("Upload to %s failed: %.*s", + u->url, (int) sizeof(u->error), u->error); + else + log_error("Upload to %s failed: %s", + u->url, curl_easy_strerror(code)); + return -EIO; + } + + code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status); + if (code) { + log_error("Failed to retrieve response code: %s", + curl_easy_strerror(code)); + return -EUCLEAN; + } + + if (status >= 300) { + log_error("Upload to %s failed with code %ld: %s", + u->url, status, strna(u->answer)); + return -EIO; + } else if (status < 200) { + log_error("Upload to %s finished with unexpected code %ld: %s", + u->url, status, strna(u->answer)); + return -EIO; + } else + log_debug("Upload finished successfully with code %ld: %s", + status, strna(u->answer)); + + free(u->last_cursor); + u->last_cursor = u->current_cursor; + u->current_cursor = NULL; + + return update_cursor_state(u); +} + +static int parse_config(void) { + const ConfigTableItem items[] = { + { "Upload", "URL", config_parse_string, 0, &arg_url }, + { "Upload", "ServerKeyFile", config_parse_path, 0, &arg_key }, + { "Upload", "ServerCertificateFile", config_parse_path, 0, &arg_cert }, + { "Upload", "TrustedCertificateFile", config_parse_path, 0, &arg_trust }, + {}}; + + return config_parse_many(PKGSYSCONFDIR "/journal-upload.conf", + CONF_PATHS_NULSTR("systemd/journal-upload.conf.d"), + "Upload\0", config_item_table_lookup, items, + false, NULL); +} + +static void help(void) { + printf("%s -u URL {FILE|-}...\n\n" + "Upload journal events to a remote server.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -u --url=URL Upload to this address (default port " + STRINGIFY(DEFAULT_PORT) ")\n" + " --key=FILENAME Specify key in PEM format (default:\n" + " \"" PRIV_KEY_FILE "\")\n" + " --cert=FILENAME Specify certificate in PEM format (default:\n" + " \"" CERT_FILE "\")\n" + " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n" + " \"" TRUST_FILE "\")\n" + " --system Use the system journal\n" + " --user Use the user journal for the current user\n" + " -m --merge Use all available journals\n" + " -M --machine=CONTAINER Operate on local container\n" + " -D --directory=PATH Use journal files from directory\n" + " --file=PATH Use this journal file\n" + " --cursor=CURSOR Start at the specified cursor\n" + " --after-cursor=CURSOR Start after the specified cursor\n" + " --follow[=BOOL] Do [not] wait for input\n" + " --save-state[=FILE] Save uploaded cursors (default \n" + " " STATE_FILE ")\n" + " -h --help Show this help and exit\n" + " --version Print version string and exit\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_KEY, + ARG_CERT, + ARG_TRUST, + ARG_USER, + ARG_SYSTEM, + ARG_FILE, + ARG_CURSOR, + ARG_AFTER_CURSOR, + ARG_FOLLOW, + ARG_SAVE_STATE, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "url", required_argument, NULL, 'u' }, + { "key", required_argument, NULL, ARG_KEY }, + { "cert", required_argument, NULL, ARG_CERT }, + { "trust", required_argument, NULL, ARG_TRUST }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "user", no_argument, NULL, ARG_USER }, + { "merge", no_argument, NULL, 'm' }, + { "machine", required_argument, NULL, 'M' }, + { "directory", required_argument, NULL, 'D' }, + { "file", required_argument, NULL, ARG_FILE }, + { "cursor", required_argument, NULL, ARG_CURSOR }, + { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR }, + { "follow", optional_argument, NULL, ARG_FOLLOW }, + { "save-state", optional_argument, NULL, ARG_SAVE_STATE }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + opterr = 0; + + while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0) + switch(c) { + case 'h': + help(); + return 0 /* done */; + + case ARG_VERSION: + return version(); + + case 'u': + if (arg_url) { + log_error("cannot use more than one --url"); + return -EINVAL; + } + + arg_url = optarg; + break; + + case ARG_KEY: + if (arg_key) { + log_error("cannot use more than one --key"); + return -EINVAL; + } + + arg_key = optarg; + break; + + case ARG_CERT: + if (arg_cert) { + log_error("cannot use more than one --cert"); + return -EINVAL; + } + + arg_cert = optarg; + break; + + case ARG_TRUST: + if (arg_trust) { + log_error("cannot use more than one --trust"); + return -EINVAL; + } + + arg_trust = optarg; + break; + + case ARG_SYSTEM: + arg_journal_type |= SD_JOURNAL_SYSTEM; + break; + + case ARG_USER: + arg_journal_type |= SD_JOURNAL_CURRENT_USER; + break; + + case 'm': + arg_merge = true; + break; + + case 'M': + if (arg_machine) { + log_error("cannot use more than one --machine/-M"); + return -EINVAL; + } + + arg_machine = optarg; + break; + + case 'D': + if (arg_directory) { + log_error("cannot use more than one --directory/-D"); + return -EINVAL; + } + + arg_directory = optarg; + break; + + case ARG_FILE: + r = glob_extend(&arg_file, optarg); + if (r < 0) + return log_error_errno(r, "Failed to add paths: %m"); + break; + + case ARG_CURSOR: + if (arg_cursor) { + log_error("cannot use more than one --cursor/--after-cursor"); + return -EINVAL; + } + + arg_cursor = optarg; + break; + + case ARG_AFTER_CURSOR: + if (arg_cursor) { + log_error("cannot use more than one --cursor/--after-cursor"); + return -EINVAL; + } + + arg_cursor = optarg; + arg_after_cursor = true; + break; + + case ARG_FOLLOW: + if (optarg) { + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --follow= parameter."); + return -EINVAL; + } + + arg_follow = !!r; + } else + arg_follow = true; + + break; + + case ARG_SAVE_STATE: + arg_save_state = optarg ?: STATE_FILE; + break; + + case '?': + log_error("Unknown option %s.", argv[optind-1]); + return -EINVAL; + + case ':': + log_error("Missing argument to %s.", argv[optind-1]); + return -EINVAL; + + default: + assert_not_reached("Unhandled option code."); + } + + if (!arg_url) { + log_error("Required --url/-u option missing."); + return -EINVAL; + } + + if (!!arg_key != !!arg_cert) { + log_error("Options --key and --cert must be used together."); + return -EINVAL; + } + + if (optind < argc && (arg_directory || arg_file || arg_machine || arg_journal_type)) { + log_error("Input arguments make no sense with journal input."); + return -EINVAL; + } + + return 1; +} + +static int open_journal(sd_journal **j) { + int r; + + if (arg_directory) + r = sd_journal_open_directory(j, arg_directory, arg_journal_type); + else if (arg_file) + r = sd_journal_open_files(j, (const char**) arg_file, 0); + else if (arg_machine) + r = sd_journal_open_container(j, arg_machine, 0); + else + r = sd_journal_open(j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type); + if (r < 0) + log_error_errno(r, "Failed to open %s: %m", + arg_directory ? arg_directory : arg_file ? "files" : "journal"); + return r; +} + +int main(int argc, char **argv) { + Uploader u; + int r; + bool use_journal; + + log_show_color(true); + log_parse_environment(); + + r = parse_config(); + if (r < 0) + goto finish; + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + sigbus_install(); + + r = setup_uploader(&u, arg_url, arg_save_state); + if (r < 0) + goto cleanup; + + sd_event_set_watchdog(u.events, true); + + r = check_cursor_updating(&u); + if (r < 0) + goto cleanup; + + log_debug("%s running as pid "PID_FMT, + program_invocation_short_name, getpid()); + + use_journal = optind >= argc; + if (use_journal) { + sd_journal *j; + r = open_journal(&j); + if (r < 0) + goto finish; + r = open_journal_for_upload(&u, j, + arg_cursor ?: u.last_cursor, + arg_cursor ? arg_after_cursor : true, + !!arg_follow); + if (r < 0) + goto finish; + } + + sd_notify(false, + "READY=1\n" + "STATUS=Processing input..."); + + for (;;) { + r = sd_event_get_state(u.events); + if (r < 0) + break; + if (r == SD_EVENT_FINISHED) + break; + + if (use_journal) { + if (!u.journal) + break; + + r = check_journal_input(&u); + } else if (u.input < 0 && !use_journal) { + if (optind >= argc) + break; + + log_debug("Using %s as input.", argv[optind]); + r = open_file_for_upload(&u, argv[optind++]); + } + if (r < 0) + goto cleanup; + + if (u.uploading) { + r = perform_upload(&u); + if (r < 0) + break; + } + + r = sd_event_run(u.events, u.timeout); + if (r < 0) { + log_error_errno(r, "Failed to run event loop: %m"); + break; + } + } + +cleanup: + sd_notify(false, + "STOPPING=1\n" + "STATUS=Shutting down..."); + + destroy_uploader(&u); + +finish: + return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/grp-journal-remote/systemd-journal-upload/journal-upload.conf.in b/src/grp-journal-remote/systemd-journal-upload/journal-upload.conf.in new file mode 100644 index 0000000000..c5670682e8 --- /dev/null +++ b/src/grp-journal-remote/systemd-journal-upload/journal-upload.conf.in @@ -0,0 +1,5 @@ +[Upload] +# URL= +# ServerKeyFile=@CERTIFICATEROOT@/private/journal-upload.pem +# ServerCertificateFile=@CERTIFICATEROOT@/certs/journal-upload.pem +# TrustedCertificateFile=@CERTIFICATEROOT@/ca/trusted.pem diff --git a/src/grp-journal-remote/systemd-journal-upload/journal-upload.h b/src/grp-journal-remote/systemd-journal-upload/journal-upload.h new file mode 100644 index 0000000000..4a521bf78f --- /dev/null +++ b/src/grp-journal-remote/systemd-journal-upload/journal-upload.h @@ -0,0 +1,71 @@ +#pragma once + +#include + +#include +#include +#include "time-util.h" + +typedef enum { + ENTRY_CURSOR = 0, /* Nothing actually written yet. */ + ENTRY_REALTIME, + ENTRY_MONOTONIC, + ENTRY_BOOT_ID, + ENTRY_NEW_FIELD, /* In between fields. */ + ENTRY_TEXT_FIELD, /* In the middle of a text field. */ + ENTRY_BINARY_FIELD_START, /* Writing the name of a binary field. */ + ENTRY_BINARY_FIELD_SIZE, /* Writing the size of a binary field. */ + ENTRY_BINARY_FIELD, /* In the middle of a binary field. */ + ENTRY_OUTRO, /* Writing '\n' */ + ENTRY_DONE, /* Need to move to a new field. */ +} entry_state; + +typedef struct Uploader { + sd_event *events; + sd_event_source *sigint_event, *sigterm_event; + + char *url; + CURL *easy; + bool uploading; + char error[CURL_ERROR_SIZE]; + struct curl_slist *header; + char *answer; + + sd_event_source *input_event; + uint64_t timeout; + + /* fd stuff */ + int input; + + /* journal stuff */ + sd_journal* journal; + + entry_state entry_state; + const void *field_data; + size_t field_pos, field_length; + + /* general metrics */ + const char *state_file; + + size_t entries_sent; + char *last_cursor, *current_cursor; + usec_t watchdog_timestamp; + usec_t watchdog_usec; +} Uploader; + +#define JOURNAL_UPLOAD_POLL_TIMEOUT (10 * USEC_PER_SEC) + +int start_upload(Uploader *u, + size_t (*input_callback)(void *ptr, + size_t size, + size_t nmemb, + void *userdata), + void *data); + +int open_journal_for_upload(Uploader *u, + sd_journal *j, + const char *cursor, + bool after_cursor, + bool follow); +void close_journal_input(Uploader *u); +int check_journal_input(Uploader *u); diff --git a/src/grp-journal/.gitignore b/src/grp-journal/.gitignore new file mode 100644 index 0000000000..04d5852547 --- /dev/null +++ b/src/grp-journal/.gitignore @@ -0,0 +1,4 @@ +/journald-gperf.c +/libsystemd-journal.pc +/audit_type-list.txt +/audit_type-*-name.* diff --git a/src/grp-journal/Makefile b/src/grp-journal/Makefile new file mode 100644 index 0000000000..188a3487e4 --- /dev/null +++ b/src/grp-journal/Makefile @@ -0,0 +1,170 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +test_journal_SOURCES = \ + src/journal/test-journal.c + +test_journal_LDADD = \ + libjournal-core.la + +test_journal_send_SOURCES = \ + src/journal/test-journal-send.c + +test_journal_send_LDADD = \ + libjournal-core.la + +test_journal_syslog_SOURCES = \ + src/journal/test-journal-syslog.c + +test_journal_syslog_LDADD = \ + libjournal-core.la + +test_journal_match_SOURCES = \ + src/journal/test-journal-match.c + +test_journal_match_LDADD = \ + libjournal-core.la + +test_journal_enum_SOURCES = \ + src/journal/test-journal-enum.c + +test_journal_enum_LDADD = \ + libjournal-core.la + +test_journal_stream_SOURCES = \ + src/journal/test-journal-stream.c + +test_journal_stream_LDADD = \ + libjournal-core.la + +test_journal_flush_SOURCES = \ + src/journal/test-journal-flush.c + +test_journal_flush_LDADD = \ + libjournal-core.la + +test_journal_init_SOURCES = \ + src/journal/test-journal-init.c + +test_journal_init_LDADD = \ + libjournal-core.la + +test_journal_verify_SOURCES = \ + src/journal/test-journal-verify.c + +test_journal_verify_LDADD = \ + libjournal-core.la + +test_journal_interleaving_SOURCES = \ + src/journal/test-journal-interleaving.c + +test_journal_interleaving_LDADD = \ + libjournal-core.la + +test_mmap_cache_SOURCES = \ + src/journal/test-mmap-cache.c + +test_mmap_cache_LDADD = \ + libjournal-core.la + +test_catalog_SOURCES = \ + src/journal/test-catalog.c + +test_catalog_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -DCATALOG_DIR=\"$(abs_top_srcdir)/catalog\" + +test_catalog_LDADD = \ + libjournal-core.la + +test_compress_SOURCES = \ + src/journal/test-compress.c + +test_compress_LDADD = \ + libshared.la + +test_compress_benchmark_SOURCES = \ + src/journal/test-compress-benchmark.c + +test_compress_benchmark_LDADD = \ + libshared.la + +test_audit_type_SOURCES = \ + src/journal/test-audit-type.c + +test_audit_type_LDADD = \ + libjournal-core.la + +journal-install-hook: + -$(MKDIR_P) $(DESTDIR)/var/log/journal + -chown 0:0 $(DESTDIR)/var/log/journal + -chmod 755 $(DESTDIR)/var/log/journal + -setfacl -nm g:adm:rx,d:g:adm:rx $(DESTDIR)/var/log/journal/ + -setfacl -nm g:wheel:rx,d:g:wheel:rx $(DESTDIR)/var/log/journal/ + +journal-uninstall-hook: + -rmdir $(DESTDIR)/var/log/journal/remote + -rmdir $(DESTDIR)/var/log/journal/ + +INSTALL_EXEC_HOOKS += journal-install-hook +UNINSTALL_EXEC_HOOKS += journal-uninstall-hook + +# ------------------------------------------------------------------------------ +# Update catalog on installation. Do not bother if installing +# in DESTDIR, since this is likely for packaging purposes. +catalog-update-hook: + -test -n "$(DESTDIR)" || $(bindir)/journalctl --update-catalog + +INSTALL_DATA_HOOKS += \ + catalog-update-hook + +catalog-remove-hook: + -test -n "$(DESTDIR)" || rm -f $(catalogstatedir)/database + +UNINSTALL_DATA_HOOKS += \ + catalog-remove-hook + +tests += \ + test-journal \ + test-journal-enum \ + test-journal-send \ + test-journal-syslog \ + test-journal-match \ + test-journal-stream \ + test-journal-init \ + test-journal-verify \ + test-journal-interleaving \ + test-journal-flush \ + test-mmap-cache \ + test-catalog \ + test-audit-type + +ifneq ($(HAVE_COMPRESSION),) +tests += \ + test-compress \ + test-compress-benchmark +endif # HAVE_COMPRESSION + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-journal/catalog/systemd.be.catalog b/src/grp-journal/catalog/systemd.be.catalog new file mode 100644 index 0000000000..051f49492f --- /dev/null +++ b/src/grp-journal/catalog/systemd.be.catalog @@ -0,0 +1,260 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2015 Viktar Vaŭčkievič +# +# 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 . + +# Message catalog for systemd's own messages +# Belarusian translation + +# The catalog format is documented on +# Фармат каталога апісаны на старонцы +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# For an explanation why we do all this, see https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: Сэрвіс журналявання запусціўся +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Працэс сістэмнага журналявання запусціўся, адкрыў файлы для +запісу і гатовы апрацоўваць запыты. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: Сэрвіс журналявання спыніўся +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Працэс сістэмнага журналявання спыніўся і закрыў усе файлы. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Паведамленні з сэрвісу адкінуты +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Сэрвіс адправіў занадта штат паведамленняў за кароткі прамежак часу. +Частка паведамленняў была адкінута. + +Майце на ўвазе, што былі адкінуты паведамлення толькі гэтага сэрвісу. +Паведамленні іншых сэрвісаў засталіся. + +Мяжа, пасля якой паведамленні будуць адкінуты, наладжваецца з +дапамогай RateLimitIntervalSec= і RateLimitBurst= у файле +/etc/systemd/journald.conf. Глядзіце journald.conf(5) для дэталей. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Паведамленні страчаны +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Паведамленні ядра былі страчаны, так як сістэма журналявання не паспела +іх апрацаваць. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Працэс @COREDUMP_PID@ (@COREDUMP_COMM@) скінуў дамп памяці +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Працэс @COREDUMP_PID@ (@COREDUMP_COMM@) разбіўся і скінуў дамп памяці. + +Звычайна гэта сведчыць аб памылцы ў праграмным кодзе. +Рэкамендуецца паведаміць аб гэтым распрацоўнікам. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: Новая сесія № @SESSION_ID@ створана для карыстальніка @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Новая сесія з № @SESSION_ID@ створана для карыстальніка @USER_ID@. + +Лідар гэтай сесіі пад № @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: Сесія № @SESSION_ID@ спынена +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Сесія № @SESSION_ID@ спынена. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: Даступна новае працоўнае месца № @SEAT_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Новае працоўнае месца № @SEAT_ID@ наладжана і даступна для выкарыстання. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Працоўнае месца № @SEAT_ID@ выдалена +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Працоўнае месца № @SEAT_ID@ выдалена і больш не даступна. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Час зменены +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Сістэмны гадзіннік зменены на @REALTIME@ мікрасекунд ад 1 студзеня 1970. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Часавы пояс зменены на @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Сістэмны часавы пояс зменены на @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: Запуск сістэмы завяршыўся +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Усе сістэмныя сэрвісы, неабходныя для загрузкі сістэмы, паспяхова +запусціліся. Майце на ўвазе, што гэта не значыць, што машына нічога не +робіць. Магчыма, некаторыя сэрвісы яшчэ ініцыялізіруюцца. + +На запуск ядра спатрэбілася @KERNEL_USEC@ мікрасекунд. + +На запуск пачатковага RAM-дыска спатрэбілася @INITRD_USEC@ мікрасекунд. + +На запуск сістэмных сэрвісаў спатрэбілася @USERSPACE_USEC@ мікрасекунд. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: Сістэма перайшла ў стан сну @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Цяпер сістэма перайшла у стан сну @SLEEP@. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: Сістэма выйшла са стана сну @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Цяпер сістэма выйшла са стана сну @SLEEP@. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Сістэма завяршае работу +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Пачаўся працэс выключэння сістэмы. +Спыняюцца ўсе сістэмныя сэрвісы і дэмантуюцца файлавыя сістэмы. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: Юніт @UNIT@ запускаецца +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Пачаўся працэс запуску юніта @UNIT@. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: Юніт @UNIT@ запусціўся +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Працэс запуску юніта @UNIT@ завершаны. + +Вынік: @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: Юніт @UNIT@ спыняецца +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Пачаўся працэс спынення юніта @UNIT@. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: Юніт @UNIT@ спынены +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Працэс спынення юніта @UNIT@ завершаны. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: Збой юніта @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Збой юніта @UNIT@. + +Вынік: @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: Юніт @UNIT@ перачытвае сваю канфігурацыю +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Юніт @UNIT@ пачаў перачытваць сваю канфігурацыю. + +-- 7b05ebc668384222baa8881179cfda54 +Subject: Юніт @UNIT@ перачытаў сваю канфігурацыю +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Юніт @UNIT@ перачытаў сваю канфігурацыю. + +Вынік: @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Працэс @EXECUTABLE@ не можа быць выкананы +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Працэс @EXECUTABLE@ не можа быць выкананы ў выніку збою. + +Ён вярнуў памылку нумар @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Sibject: Адно ці больш паведамленняў не былі накіраваны ў syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Адно ці больш паведамленняў не былі накіраваны ў syslog сэрвіс, які +выконваецца паралельна з journald. Звычайна гэта значыць, што +рэалізацыя syslog не паспявае апрацаваць паведамленні з неабходнай +хуткасцю. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Кропка мантавання не пустая +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Каталог @WHERE@ указаны як кропка мантавання (другое поле ў /etc/fstab +ці Where= поле ў файле юніта systemd) і не пусты. Гэта не перашкаджае +мантаванню, але існуючыя ў ім файлы будуць недаступны. Для доступу да +іх, калі ласка, змантуйце гэтую файлавую сістэму ў іншае месца. + +-- 24d8d4452573402496068381a6312df2 +Subject: Віртуальная машына або кантэйнер запусціўся +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Віртуальная машына @NAME@ з лідарам № @LEADER@ запусцілася і +гатова для выкарыстання. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: Віртуальная машына або кантэйнер спынены +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Віртуальная машына @NAME@ з лідарам № @LEADER@ спынена. diff --git a/src/grp-journal/catalog/systemd.be@latin.catalog b/src/grp-journal/catalog/systemd.be@latin.catalog new file mode 100644 index 0000000000..6ab361aafb --- /dev/null +++ b/src/grp-journal/catalog/systemd.be@latin.catalog @@ -0,0 +1,260 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2015 Viktar Vaŭčkievič +# +# 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 . + +# Message catalog for systemd's own messages +# Belarusian Latin translation + +# The catalog format is documented on +# Farmat kataloha apisany na staroncy +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# For an explanation why we do all this, see https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: Servis žurnaliavannia zapusciŭsia +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Praces sistemnaha žurnaliavannia zapusciŭsia, adkryŭ fajly dlia +zapisu i hatovy apracoŭvać zapyty. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: Servis žurnaliavannia spyniŭsia +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Praces sistemnaha žurnaliavannia spyniŭsia i zakryŭ usie fajly. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Paviedamlienni z servisu adkinuty +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Servis adpraviŭ zanadta štat paviedamlienniaŭ za karotki pramiežak času. +Častka paviedamlienniaŭ byla adkinuta. + +Majcie na ŭvazie, što byli adkinuty paviedamliennia toĺki hetaha servisu. +Paviedamlienni inšych servisaŭ zastalisia. + +Miaža, paslia jakoj paviedamlienni buduć adkinuty, naladžvajecca z +dapamohaj RateLimitIntervalSec= i RateLimitBurst= u fajlie +/etc/systemd/journald.conf. Hliadzicie journald.conf(5) dlia detaliej. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Paviedamlienni stračany +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Paviedamlienni jadra byli stračany, tak jak sistema žurnaliavannia nie paspiela +ich apracavać. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Praces @COREDUMP_PID@ (@COREDUMP_COMM@) skinuŭ damp pamiaci +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Praces @COREDUMP_PID@ (@COREDUMP_COMM@) razbiŭsia i skinuŭ damp pamiaci. + +Zvyčajna heta sviedčyć ab pamylcy ŭ prahramnym kodzie. +Rekamiendujecca paviedamić ab hetym raspracoŭnikam. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: Novaja siesija № @SESSION_ID@ stvorana dlia karystaĺnika @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Novaja siesija z № @SESSION_ID@ stvorana dlia karystaĺnika @USER_ID@. + +Lidar hetaj siesii pad № @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: Siesija № @SESSION_ID@ spyniena +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Siesija № @SESSION_ID@ spyniena. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: Dastupna novaje pracoŭnaje miesca № @SEAT_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Novaje pracoŭnaje miesca № @SEAT_ID@ naladžana i dastupna dlia vykarystannia. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Pracoŭnaje miesca № @SEAT_ID@ vydaliena +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Pracoŭnaje miesca № @SEAT_ID@ vydaliena i boĺš nie dastupna. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Čas zmienieny +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Sistemny hadzinnik zmienieny na @REALTIME@ mikrasiekund ad 1 studzienia 1970. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Časavy pojas zmienieny na @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Sistemny časavy pojas zmienieny na @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: Zapusk sistemy zaviaršyŭsia +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Usie sistemnyja servisy, nieabchodnyja dlia zahruzki sistemy, paspiachova +zapuscilisia. Majcie na ŭvazie, što heta nie značyć, što mašyna ničoha nie +robić. Mahčyma, niekatoryja servisy jašče inicyjalizirujucca. + +Na zapusk jadra spatrebilasia @KERNEL_USEC@ mikrasiekund. + +Na zapusk pačatkovaha RAM-dyska spatrebilasia @INITRD_USEC@ mikrasiekund. + +Na zapusk sistemnych servisaŭ spatrebilasia @USERSPACE_USEC@ mikrasiekund. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: Sistema pierajšla ŭ stan snu @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Ciapier sistema pierajšla u stan snu @SLEEP@. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: Sistema vyjšla sa stana snu @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Ciapier sistema vyjšla sa stana snu @SLEEP@. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Sistema zaviaršaje rabotu +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Pačaŭsia praces vykliučennia sistemy. +Spyniajucca ŭsie sistemnyja servisy i demantujucca fajlavyja sistemy. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: Junit @UNIT@ zapuskajecca +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Pačaŭsia praces zapusku junita @UNIT@. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: Junit @UNIT@ zapusciŭsia +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Praces zapusku junita @UNIT@ zavieršany. + +Vynik: @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: Junit @UNIT@ spyniajecca +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Pačaŭsia praces spyniennia junita @UNIT@. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: Junit @UNIT@ spynieny +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Praces spyniennia junita @UNIT@ zavieršany. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: Zboj junita @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Zboj junita @UNIT@. + +Vynik: @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: Junit @UNIT@ pieračytvaje svaju kanfihuracyju +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Junit @UNIT@ pačaŭ pieračytvać svaju kanfihuracyju. + +-- 7b05ebc668384222baa8881179cfda54 +Subject: Junit @UNIT@ pieračytaŭ svaju kanfihuracyju +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Junit @UNIT@ pieračytaŭ svaju kanfihuracyju. + +Vynik: @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Praces @EXECUTABLE@ nie moža być vykanany +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Praces @EXECUTABLE@ nie moža być vykanany ŭ vyniku zboju. + +Jon viarnuŭ pamylku numar @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Sibject: Adno ci boĺš paviedamlienniaŭ nie byli nakiravany ŭ syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Adno ci boĺš paviedamlienniaŭ nie byli nakiravany ŭ syslog servis, jaki +vykonvajecca paralieĺna z journald. Zvyčajna heta značyć, što +realizacyja syslog nie paspiavaje apracavać paviedamlienni z nieabchodnaj +chutkasciu. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Kropka mantavannia nie pustaja +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Kataloh @WHERE@ ukazany jak kropka mantavannia (druhoje polie ŭ /etc/fstab +ci Where= polie ŭ fajlie junita systemd) i nie pusty. Heta nie pieraškadžaje +mantavanniu, alie isnujučyja ŭ im fajly buduć niedastupny. Dlia dostupu da +ich, kali laska, zmantujcie hetuju fajlavuju sistemu ŭ inšaje miesca. + +-- 24d8d4452573402496068381a6312df2 +Subject: Virtuaĺnaja mašyna abo kantejnier zapusciŭsia +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Virtuaĺnaja mašyna @NAME@ z lidaram № @LEADER@ zapuscilasia i +hatova dlia vykarystannia. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: Virtuaĺnaja mašyna abo kantejnier spynieny +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Virtuaĺnaja mašyna @NAME@ z lidaram № @LEADER@ spyniena. diff --git a/src/grp-journal/catalog/systemd.bg.catalog b/src/grp-journal/catalog/systemd.bg.catalog new file mode 100644 index 0000000000..30246c0bbe --- /dev/null +++ b/src/grp-journal/catalog/systemd.bg.catalog @@ -0,0 +1,324 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2016 Alexander Shopov +# +# 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 . + +# Message catalog for systemd's own messages + +# The catalog format is documented on +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# For an explanation why we do all this, see https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: Журналният процес е пуснат +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Журналният процес на системата е стартирал, отворил е журналните файлове +за запис и може да приема заявки. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: Журналният процес е спрян +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Журналният процес на системата е спрян, затворени са всички отворени +журнални файлове. + +-- ec387f577b844b8fa948f33cad9a75e6 +Subject: Пространството върху диска заето от журналните файлове +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@JOURNAL_NAME@ (@JOURNAL_PATH@) в момента заема @CURRENT_USE_PRETTY@. +Максималният зададен размер е @MAX_USE_PRETTY@. +Свободни се оставят поне @DISK_KEEP_FREE_PRETTY@ (от текущо наличните @DISK_AVAILABLE_PRETTY@). +Максималният наложен размер е @LIMIT_PRETTY@, от който @AVAILABLE_PRETTY@ са свободни. + +Настройките за максималния размер на журнала върху диска се +управляват чрез директивите „SystemMaxUse=“, „SystemKeepFree=“, +„SystemMaxFileSize=“, „RuntimeMaxUse=“, „RuntimeKeepFree=“ и +„RuntimeMaxFileSize=“ във файла „/etc/systemd/journald.conf“. +За повече информация прегледайте „journald.conf(5)“ от ръководството. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Съобщенията от някоя услуга не са допуснати +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Някоя услуга генерира прекалено много съобщения за кратък период. +Част само от нейните съобщения са отхвърляни. + +Съобщенията от другите услуги не са засегнати. + +Настройките за максималния брой съобщения, които ще се обработят, се +управляват чрез директивите „RateLimitInterval=“ и „RateLimitBurst=“ във +файла „/etc/systemd/journald.conf“. За повече информация прегледайте +„journald.conf(5)“ от ръководството. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Пропуснати журнални съобщения +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Някои от съобщенията на ядрото може и да са пропуснати, защото системата не +смогваше да ги обработи достатъчно бързо. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Процес № @COREDUMP_PID@ (@COREDUMP_COMM@) запази освободената памет +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Процес № @COREDUMP_PID@ (@COREDUMP_COMM@) заби, представянето му в паметта +бе запазено. + +Най-често това се дължи на грешка в забилата програма и следва да я +докладвате на създателите на програмата. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: Създадена е нова сесия № @SESSION_ID@ за потребителя „@USER_ID@“ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +За потребителя „@USER_ID@“ е създадена нова сесия № @SESSION_ID@. + +Водещият процес на сесията е: @LEADER@ + +-- 3354939424b4456d9802ca8333ed424a +Subject: Сесия № @SESSION_ID@ приключи +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Сесия № @SESSION_ID@ приключи работа. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: Налично е ново работно място № @SEAT_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Новото работно място № @SEAT_ID@ е настроено и готово за работа. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Работното място № @SEAT_ID@ е премахнато +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Работното място № @SEAT_ID@ вече не е налично. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Смяна на системното време +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Часовникът на системата е сверен да сочи @REALTIME@ микросекунди след +1 януари 1970. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Смяна на часовия пояс да е „@TIMEZONE@“ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Часовият пояс на системата е сменен на „@TIMEZONE@“. + +-- b07a249cd024414a82dd00cd181378ff +Subject: Стартирането на системата завърши +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Успешно са стартирали всички услуги, които са посочени за задействане при +стартиране на системата. Това не означава, че системата бездейства, защото +някои от услугите може да извършват специфични действия при стартиране. + +Стартирането на ядрото отне @KERNEL_USEC@ микросекунди. + +Стартирането на RAM диска за първоначално зареждане отне @INITRD_USEC@ +микросекунди. + +Стартирането на потребителските програми отне @USERSPACE_USEC@ микросекунди. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: Системата е приспана на ниво „@SLEEP@“ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Системата премина в състояние на приспиване „@SLEEP@“. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: Системата се събуди след приспиване на ниво„@SLEEP@“ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Системата се събуди от състояние на приспиване „@SLEEP@“. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Започна процедура на спиране на системата +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Започна процедурата на Systemd за спиране на системата. Всички процеси и +услуги се спират, всички файлови системи се демонтират. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: Модул „@UNIT@“ се стартира +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Модулът „@UNIT@“ се стартира в момента + +-- 39f53479d3a045ac8e11786248231fbf +Subject: Модул „@UNIT@“ вече е стартиран +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Стартирането на модул „@UNIT@“ завърши. + +Резултатът е: @RESULT@ + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: Модул „@UNIT@“ се спира +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Модулът „@UNIT@“ се спира в момента. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: Модул „@UNIT@“ вече е спрян +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Спирането на модул „@UNIT@“ завърши. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: Модулът „@UNIT@“ не успя да стартира +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Модулът „@UNIT@“ не успя да стартира. + +Резултатът е: @RESULT@ + +-- d34d037fff1847e6ae669a370e694725 +Subject: Модулът „@UNIT@“ започна презареждане на настройките си +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Модулът „@UNIT@“ започна презареждане на настройките си. + +-- 7b05ebc668384222baa8881179cfda54 +Subject: Модулът „@UNIT@“ завърши презареждането на настройките си +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Модулът „@UNIT@“ завърши презареждането на настройките си. + +Резултатът e: @RESULT@ + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Програмата „@EXECUTABLE@“ не успя да се стартира +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Програмата „@EXECUTABLE@“ не успя да се стартира. + +Върнатият номер на грешка е: @ERRNO@ + +-- 0027229ca0644181a76c4e92458afa2e +Subject: Поне едно съобщение не бе препратено към syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Поне едно съобщение не бе препратено към журналната услуга syslog, която +работи успоредно с journald. + +Най-често това указва, че тази реализация на syslog не може да поеме текущия +обем съобщения. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Точката за монтиране не е празна +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Директорията „@WHERE@“ не е празна. + +Тя е указана като точка за монтиране — или като второ поле във файла +„/etc/fstab“, или чрез директивата „Where=“ в някой от файловете за +модул на Systemd. + +Това не пречи на самото монтиране, но вече съществуващите там файлове и +директории няма да се виждат повече, освен ако ръчно не монтирате тази +непразна директория някъде другаде. + +-- 24d8d4452573402496068381a6312df2 +Subject: Стартирана е виртуална машина или контейнер +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Виртуалната машина „@NAME@“ с идентификатор на водещия процес @LEADER@ +е стартирана и готова за работа. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: Спряна е виртуална машина или контейнер +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Виртуалната машина „@NAME@“ с идентификатор на водещия процес @LEADER@ +е спряна. + +-- 36db2dfa5a9045e1bd4af5f93e1cf057 +Subject: Режимът DNSSEC е изключен, защото сървърът не го поддържа +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) resolved.conf(5) + +Локалната услуга за имена (systemd-resolved.service) установи, че +настроения сървър за DNS не поддържа DNSSEC, затова този режим е изключен. + +Това се случва, когато директивата „DNSSEC=allow-downgrade“ е включена във +файла „resolved.conf“ и зададеният сървър за DNS не е съвместим с DNSSEC. + +Внимавайте, защото това може да позволи атака, при която трета страна ви +връща отговори, които да предизвикат понижаването на сигурността от DNSSEC +до DNS. + +Такова събитие означава, че или сървърът за DNS не е съвместим с DNSSEC, +или някой успешно ви е атакувал за понижаване на сигурността на имената. + +-- 1675d7f172174098b1108bf8c7dc8f5d +Subject: Неуспешна проверка на DNSSEC +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +Заявка или запис в DNS не издържа проверка с DNSSEC. + +Това обикновено показва вмешателство на трета страна в канала ви за връзка. + +-- 4d4408cfd0d144859184d1e65d7c8a65 +Subject: Анулирана доверена котва в DNSSEC +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +Анулирана е доверена котва за DNSSEC и трябва да настроите нова. + +Понякога новата идва с обновяване на системата. diff --git a/src/grp-journal/catalog/systemd.catalog b/src/grp-journal/catalog/systemd.catalog new file mode 100644 index 0000000000..90929bca6d --- /dev/null +++ b/src/grp-journal/catalog/systemd.catalog @@ -0,0 +1,334 @@ +# This file is part of systemd. +# +# Copyright 2012 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 . + +# Message catalog for systemd's own messages + +# The catalog format is documented on +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# For an explanation why we do all this, see https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: The journal has been started +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The system journal process has started up, opened the journal +files for writing and is now ready to process requests. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: The journal has been stopped +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The system journal process has shut down and closed all currently +active journal files. + +-- ec387f577b844b8fa948f33cad9a75e6 +Subject: Disk space used by the journal +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@JOURNAL_NAME@ (@JOURNAL_PATH@) is currently using @CURRENT_USE_PRETTY@. +Maximum allowed usage is set to @MAX_USE_PRETTY@. +Leaving at least @DISK_KEEP_FREE_PRETTY@ free (of currently available @DISK_AVAILABLE_PRETTY@ of disk space). +Enforced usage limit is thus @LIMIT_PRETTY@, of which @AVAILABLE_PRETTY@ are still available. + +The limits controlling how much disk space is used by the journal may +be configured with SystemMaxUse=, SystemKeepFree=, SystemMaxFileSize=, +RuntimeMaxUse=, RuntimeKeepFree=, RuntimeMaxFileSize= settings in +/etc/systemd/journald.conf. See journald.conf(5) for details. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Messages from a service have been suppressed +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +A service has logged too many messages within a time period. Messages +from the service have been dropped. + +Note that only messages from the service in question have been +dropped, other services' messages are unaffected. + +The limits controlling when messages are dropped may be configured +with RateLimitIntervalSec= and RateLimitBurst= in +/etc/systemd/journald.conf. See journald.conf(5) for details. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Journal messages have been missed +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Kernel messages have been lost as the journal system has been unable +to process them quickly enough. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Process @COREDUMP_PID@ (@COREDUMP_COMM@) dumped core +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Process @COREDUMP_PID@ (@COREDUMP_COMM@) crashed and dumped core. + +This usually indicates a programming error in the crashing program and +should be reported to its vendor as a bug. + +-- fc2e22bc6ee647b6b90729ab34a250b1 de +Subject: Speicherabbild für Prozess @COREDUMP_PID@ (@COREDUMP_COMM) generiert +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Prozess @COREDUMP_PID@ (@COREDUMP_COMM@) ist abgebrochen worden und +ein Speicherabbild wurde generiert. + +Üblicherweise ist dies ein Hinweis auf einen Programmfehler und sollte +als Fehler dem jeweiligen Hersteller gemeldet werden. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: A new session @SESSION_ID@ has been created for user @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +A new session with the ID @SESSION_ID@ has been created for the user @USER_ID@. + +The leading process of the session is @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: Session @SESSION_ID@ has been terminated +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +A session with the ID @SESSION_ID@ has been terminated. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: A new seat @SEAT_ID@ is now available +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +A new seat @SEAT_ID@ has been configured and is now available. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Seat @SEAT_ID@ has now been removed +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +A seat @SEAT_ID@ has been removed and is no longer available. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Time change +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The system clock has been changed to @REALTIME@ microseconds after January 1st, 1970. + +-- c7a787079b354eaaa9e77b371893cd27 de +Subject: Zeitänderung +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Die System-Zeit wurde geändert auf @REALTIME@ Mikrosekunden nach dem 1. Januar 1970. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Time zone change to @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The system timezone has been changed to @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: System start-up is now complete +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +All system services necessary queued for starting at boot have been +successfully started. Note that this does not mean that the machine is +now idle as services might still be busy with completing start-up. + +Kernel start-up required @KERNEL_USEC@ microseconds. + +Initial RAM disk start-up required @INITRD_USEC@ microseconds. + +Userspace start-up required @USERSPACE_USEC@ microseconds. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: System sleep state @SLEEP@ entered +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The system has now entered the @SLEEP@ sleep state. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: System sleep state @SLEEP@ left +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The system has now left the @SLEEP@ sleep state. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: System shutdown initiated +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Systemd shutdown has been initiated. The shutdown has now begun and +all system services are terminated and all file systems unmounted. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: Unit @UNIT@ has begun start-up +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Unit @UNIT@ has begun starting up. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: Unit @UNIT@ has finished start-up +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Unit @UNIT@ has finished starting up. + +The start-up result is @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: Unit @UNIT@ has begun shutting down +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Unit @UNIT@ has begun shutting down. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: Unit @UNIT@ has finished shutting down +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Unit @UNIT@ has finished shutting down. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: Unit @UNIT@ has failed +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Unit @UNIT@ has failed. + +The result is @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: Unit @UNIT@ has begun reloading its configuration +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Unit @UNIT@ has begun reloading its configuration + +-- 7b05ebc668384222baa8881179cfda54 +Subject: Unit @UNIT@ has finished reloading its configuration +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Unit @UNIT@ has finished reloading its configuration + +The result is @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Process @EXECUTABLE@ could not be executed +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The process @EXECUTABLE@ could not be executed and failed. + +The error number returned by this process is @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: One or more messages could not be forwarded to syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +One or more messages could not be forwarded to the syslog service +running side-by-side with journald. This usually indicates that the +syslog implementation has not been able to keep up with the speed of +messages queued. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Mount point is not empty +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The directory @WHERE@ is specified as the mount point (second field in +/etc/fstab or Where= field in systemd unit file) and is not empty. +This does not interfere with mounting, but the pre-exisiting files in +this directory become inaccessible. To see those over-mounted files, +please manually mount the underlying file system to a secondary +location. + +-- 24d8d4452573402496068381a6312df2 +Subject: A virtual machine or container has been started +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The virtual machine @NAME@ with its leader PID @LEADER@ has been +started is now ready to use. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: A virtual machine or container has been terminated +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The virtual machine @NAME@ with its leader PID @LEADER@ has been +shut down. + +-- 36db2dfa5a9045e1bd4af5f93e1cf057 +Subject: DNSSEC mode has been turned off, as server doesn't support it +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) resolved.conf(5) + +The resolver service (systemd-resolved.service) has detected that the +configured DNS server does not support DNSSEC, and DNSSEC validation has been +turned off as result. + +This event will take place if DNSSEC=allow-downgrade is configured in +resolved.conf and the configured DNS server is incompatible with DNSSEC. Note +that using this mode permits DNSSEC downgrade attacks, as an attacker might be +able turn off DNSSEC validation on the system by inserting DNS replies in the +communication channel that result in a downgrade like this. + +This event might be indication that the DNS server is indeed incompatible with +DNSSEC or that an attacker has successfully managed to stage such a downgrade +attack. + +-- 1675d7f172174098b1108bf8c7dc8f5d +Subject: DNSSEC validation failed +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +A DNS query or resource record set failed DNSSEC validation. This is usually +indication that the communication channel used was tampered with. + +-- 4d4408cfd0d144859184d1e65d7c8a65 +Subject: A DNSSEC trust anchor has been revoked +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +A DNSSEC trust anchor has been revoked. A new trust anchor has to be +configured, or the operating system needs to be updated, to provide an updated +DNSSEC trust anchor. diff --git a/src/grp-journal/catalog/systemd.da.catalog b/src/grp-journal/catalog/systemd.da.catalog new file mode 100644 index 0000000000..093e8139da --- /dev/null +++ b/src/grp-journal/catalog/systemd.da.catalog @@ -0,0 +1,261 @@ +# This file is part of systemd. +# +# Copyright 2012 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 . + +# Message catalog for systemd's own messages +# Danish translation + +# The catalog format is documented on +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# For an explanation why we do all this, see https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: Journalen er blevet startet +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +System-journal processen har startet op, åbnet journal filerne for +tilskrivning og er nu klar til at modtage anmodninger. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: Journalen er blevet stoppet +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +System-journal processen er stoppet og har lukket alle aktive journal +filer. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Beskeder fra en service er blevet undertrykt +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +En service har logget for mange beskeder inden for en given tidsperiode. +Beskeder fra omtalte service er blevet smidt væk. + +Kun beskeder fra omtalte service er smidt væk. Beskeder fra andre +services er ikke påvirket. + +Grænsen for hvornår beskeder bliver smidt væk kan konfigureres +med RateLimitIntervalSec= og RateLimitBurst= i +/etc/systemd/journald.conf. Se journald.conf(5) for detaljer herom. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Journal beskeder er gået tabt +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Kernel beskeder er gået tabt da journal systemet ikke har været i stand +til at håndtere dem hurtigt nok. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Fejl-fil genereret for process @COREDUMP_PID@ (@COREDUMP_COMM@) +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Process @COREDUMP_PID@ (@COREDUMP_COMM@) har lukket ned og genereret en +fejl-fil. + +Dette indikerer som regel en programmeringsfejl i det nedlukkede program +og burde blive reporteret som en bug til folkene bag + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: En ny session @SESSION_ID@ er blevet lavet for bruger @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +En ny session med ID @SESSION_ID@ er blevet lavet for brugeren @USER_ID@. + +Den ledende process for sessionen er @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: Session @SESSION_ID@ er blevet lukket ned +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +En session med ID @SESSION_ID@ er blevet lukket ned. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: En ny arbejdsstation $SEAT_ID@ er nu tilgængelig +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +En ny arbejdsstation @SEAT_ID@ er blevet konfigureret og er nu tilgængelig. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Arbejdsstation @SEAT_ID@ er nu blevet fjernet +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +En arbejdsstation @SEAT_ID@ er blevet fjernet og er ikke længere tilgængelig. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Tidsændring +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Systemtiden er blevet ændret til @REALTIME@ mikrosekunder efter d. 1. Januar 1970. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Tidszoneændring til @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Tidszonen for systemet er blevet ændret til @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: Opstart af systemet er nu fuldført +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Alle system services i kø til at køre ved opstart, er blevet startet +med success. Bemærk at dette ikke betyder at maskinen er i dvale, da +services stadig kan være i gang med at færdiggøre deres opstart. + +Opstart af kernel tog @KERNEL_USEC@ mikrosekunder. + +Opstart af initrd tog @INITRD_USEC@ mikrosekunder. + +Opstart af userspace tog @USERSPACE_USEC@ mikrosekunder. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: System slumretilstand @SLEEP@ trådt i kraft +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +System er nu gået i @SLEEP@ slumretilstand. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: System slumretilstand @SLEEP@ forladt +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Systemet har nu forladt @SLEEP@ slumretilstand. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Systemnedlukning påbegyndt +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Systemnedlukning er blevet påbegyndt. Nedlukningen er nu begyndt og +alle system services er blevet afbrudt og alle filsystemer afmonteret. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: Enhed @UNIT@ har påbegyndt opstart +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Enhed @UNIT@ er begyndt at starte op. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: Enhed @UNIT har færdiggjort opstart +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Enhed @UNIT@ er færdig med at starte op. + +Resultat for opstart er @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: Enhed @UNIT@ har påbegyndt nedlukning +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Enhed @UNIT@ har påbegyndt nedlukning. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: Enhed @UNIT@ har færdiggjort nedlukning +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Enhed @UNIT@ har færdiggjort nedlukning. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: Enhed @UNIT@ har fejlet +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Enhed @UNIT@ har fejlet. + +Resultatet er @RESULT@ + +-- d34d037fff1847e6ae669a370e694725 +Subject: Enhed @UNIT@ har påbegyndt genindlæsning af sin konfiguration +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Enhed @UNIT@ er begyndt at genindlæse sin konfiguration + +-- 7b05ebc668384222baa8881179cfda54 +Subject: Enhed @UNIT@ har færdiggjort genindlæsning af sin konfiguration +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Enhed @UNIT@ er færdig med at genindlæse sin konfiguration + +Resultatet er: @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Process @EXECUTABLE@ kunne ikke eksekveres +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Processen @EXECUTABLE@ kunne ikke eksekveres og fejlede. + +Processens returnerede fejlkode er @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: Èn eller flere beskeder kunne ikke videresendes til syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Èn eller flere beskeder kunne ikke videresendes til syslog servicen +der kører side-om-side med journald. Dette indikerer typisk at syslog +implementationen ikke har kunnet følge med mængden af ventende beskeder. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Monteringspunkt er ikke tomt +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Folderen @WHERE@ er specificeret som monteringspunkt (andet felt i +/etc/fstab eller Where= feltet i systemd enhedsfil) men er ikke tom. +Dette forstyrrer ikke monteringen, men de pre-eksisterende filer i folderen +bliver utilgængelige. For at se de over-monterede filer; montér det +underlæggende filsystem til en anden lokation. + +-- 24d8d4452573402496068381a6312df2 +Subject: En virtuel maskine eller container er blevet startet +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Den virtuelle maskine @NAME@ med dens leder PID @LEADER@ er blevet +startet og er klar til brug. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: En virtuel maskine eller container er blevet afbrudt +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Den virtuelle maskine @NAME@ med dens leder PID @LEADER@ er blevet +nedlukket. diff --git a/src/grp-journal/catalog/systemd.fr.catalog b/src/grp-journal/catalog/systemd.fr.catalog new file mode 100644 index 0000000000..0cea629c31 --- /dev/null +++ b/src/grp-journal/catalog/systemd.fr.catalog @@ -0,0 +1,320 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2013-2015 Sylvain Plantefève +# +# 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 . + +# Message catalog for systemd's own messages +# French translation + +# Le format du catalogue de messages est décrit (en anglais) içi : +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +-- f77379a8490b408bbe5f6940505a777b +Subject: Le journal a été démarré +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Le processus du journal système a démarré, ouvert ses fichiers en écriture +et est prêt à traiter les requêtes. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: Le journal a été arrêté +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Le processus du journal système a été arrêté et tous ses fichiers actifs +ont été fermés. + +-- ec387f577b844b8fa948f33cad9a75e6 +Subject: Espace disque utilisé par le journal +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +@JOURNAL_NAME@ (@JOURNAL_PATH@) utilise actuellement @CURRENT_USE_PRETTY@. +Le maximum autorisé est défini à @MAX_USE_PRETTY@. +Au moins @DISK_KEEP_FREE_PRETTY@ doivent être laissés libres +(sur @DISK_AVAILABLE_PRETTY@ d'espace disque actuellement libre). +La limite appliquée est donc @LIMIT_PRETTY@, dont @AVAILABLE_PRETTY@ +sont toujours disponibles. + +Les limites définissant la quantité d'espace disque que peut utiliser le +journal peuvent être configurées avec les paramètres SystemMaxUse=, +SystemKeepFree=, SystemMaxFileSize=, RuntimeMaxUse=, RuntimeKeepFree=, +RuntimeMaxFileSize= dans le fichier /etc/systemd/journald.conf. +Voir journald.conf(5) pour plus de détails. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Des messages d'un service ont été supprimés +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Un service a essayé d'enregistrer un trop grand nombre de messages sur un +intervalle de temps donné. Des messages de ce service ont été évincés. + +Notez que seuls des messages de ce service ont été évincés, les messages des +autres services ne sont pas affectés. + +Les limites définissant ce comportement peuvent être configurées avec les +paramètres RateLimitIntervalSec= et RateLimitBurst= dans le fichier +/etc/systemd/journald.conf. Voir journald.conf(5) pour plus de détails. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Des messages du journal ont été manqués +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Des messages du noyau ont été manqués car le journal système n'a pas été +capable de les traiter suffisamment vite. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Le processus @COREDUMP_PID@ (@COREDUMP_COMM@) a généré un fichier « core » +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Le processus @COREDUMP_PID@ (@COREDUMP_COMM@) a planté et généré un fichier « core ». + +Cela indique généralement une erreur de programmation dans le programme +incriminé, et cela devrait être notifié à son concepteur comme un défaut (bug). + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: Une nouvelle session @SESSION_ID@ a été créée pour l'utilisateur @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Une nouvelle session a été créée pour l'utilisateur @USER_ID@ avec +l'identifiant (ID) @SESSION_ID@. + +Le processus maître de la session est @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: La session @SESSION_ID@ s'est terminée +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +La session d'identifiant (ID) @SESSION_ID@ s'est terminée. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: Un nouveau poste (seat) @SEAT_ID@ est disponible +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Un nouveau poste (seat) @SEAT_ID@ a été configuré et est maintenant +disponible. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Le poste (seat) @SEAT_ID@ a été retiré +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Le poste (seat) @SEAT_ID@ a été retiré et n'est plus disponible. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Changement d'heure +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'horloge système a été modifiée et positionnée à @REALTIME@ microsecondes +après le 1er janvier 1970. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Fuseau horaire modifié en @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Le fuseau horaire du système a été modifié et positionné à @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: Le démarrage du système est terminé +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Tous les services nécessaires au démarrage du système ont été lancés avec +succès. Notez que cela ne signifie pas que le système est maintenant au +repos, car des services peuvent encore être en train de terminer leur +démarrage. + +Le chargement du noyau a nécessité @KERNEL_USEC@ microsecondes. + +Le chargement du « RAM disk » initial a nécessité @INITRD_USEC@ microsecondes. + +Le chargement de l'espace utilisateur a nécessité @USERSPACE_USEC@ microsecondes. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: Le système entre dans l'état de repos (sleep state) @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Le système est maintenant à l'état de repos (sleep state) @SLEEP@. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: Le système sorti de l'état de repos (sleep state) @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Le système est maintenant sorti de l'état de repos (sleep state) @SLEEP@. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Arrêt du système amorcé +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'arrêt du système a été amorcé. L'arrêt a maintenant commencé, tous les +services du système sont terminés et tous les systèmes de fichiers sont +démontés. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: L'unité (unit) @UNIT@ a commencé à démarrer +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unité (unit) @UNIT@ a commencé à démarrer. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: L'unité (unit) @UNIT@ a terminé son démarrage +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unité (unit) @UNIT@ a terminé son démarrage, avec le résultat @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: L'unité (unit) @UNIT@ a commencé à s'arrêter +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unité (unit) @UNIT@ a commencé à s'arrêter. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: L'unité (unit) @UNIT@ a terminé son arrêt +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unité (unit) @UNIT@ a terminé son arrêt. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: L'unité (unit) @UNIT@ a échoué +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unité (unit) @UNIT@ a échoué, avec le résultat @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: L'unité (unit) @UNIT@ a commencé à recharger sa configuration +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unité (unit) @UNIT@ a commencé à recharger sa configuration. + +-- 7b05ebc668384222baa8881179cfda54 +Subject: L'unité (unit) @UNIT@ a terminé de recharger configuration +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unité (unit) @UNIT@ a terminé de recharger configuration, +avec le résultat @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Le processus @EXECUTABLE@ n'a pas pu être exécuté +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Le processus @EXECUTABLE@ n'a pas pu être exécuté, et a donc échoué. + +Le code d'erreur renvoyé est @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: Un ou plusieurs messages n'ont pas pu être transmis à syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Un ou plusieurs messages n'ont pas pu être transmis au service syslog +s'exécutant conjointement avec journald. Cela indique généralement que +l'implémentation de syslog utilisée n'a pas été capable de suivre +la cadence du flux de messages. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Le point de montage n'est pas vide +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Le répertoire @WHERE@ est spécifié comme point de montage (second champ du +fichier /etc/fstab, ou champ Where= dans une unité (unit) systemd) et n'est +pas vide. +Cela ne perturbe pas le montage du système de fichiers, mais les fichiers +préalablement présents dans ce répertoire sont devenus inaccessibles. +Pour atteindre ces fichiers, veuillez monter manuellement le système de +fichiers sous-jacent à un autre emplacement. + +-- 24d8d4452573402496068381a6312df2 +Subject: Une machine virtuelle ou un conteneur (container) a été démarré +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +La machine virtuelle @NAME@ a été démarrée avec le PID maître @LEADER@, +et est maintenant prête à l'emploi. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: Une machine virtuelle ou un conteneur (container) a été arrêté +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +La machine virtuelle @NAME@ avec le PID maître @LEADER@ a été arrêtée. + +-- 36db2dfa5a9045e1bd4af5f93e1cf057 +Subject: Le mode DNSSEC a été désactivé, car il n'est pas supporté par le serveur +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) resolved.conf(5) + +Le service de résolution (systemd-resolved.service) a détecté que le serveur +DNS configuré ne supporte pas DNSSEC, et la validation DNSSEC a donc été +désactivée. + +Cet évènement se produit si DNSSEC=allow-downgrade est configuré dans +resolved.conf et que le serveur DNS configuré n'est pas compatible avec +DNSSEC. +Veuillez noter que ce mode permet des attaques de rétrogradation DNSSEC, +car un attaquant peut être capable de désactiver la validation DNSSEC sur +le système en injectant des réponses DNS dans le canal de communication. + +Cet évènement indique que le serveur DNS est effectivement incompatible avec +DNSSEC, ou qu'un attaquant a peut-être conduit une telle attaque avec succès. + +-- 1675d7f172174098b1108bf8c7dc8f5d +Subject: La validation DNSSEC a échoué +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +Une requête ou une ressource DNS n'a pas passé la validation DNSSEC. +Ceci est généralement une indication que le canal de communication a été +altéré. + +-- 4d4408cfd0d144859184d1e65d7c8a65 +Subject: Une ancre de confiance DNSSEC a été révoquée +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +Une ancre de confiance DNSSEC a été révoquée. Une nouvelle ancre de +confiance doit être configurée, ou le système d'exploitation a besoin +d'être mis à jour, pour fournir une version à jour de l'ancre de confiance. diff --git a/src/grp-journal/catalog/systemd.hr.catalog b/src/grp-journal/catalog/systemd.hr.catalog new file mode 100644 index 0000000000..350988dd87 --- /dev/null +++ b/src/grp-journal/catalog/systemd.hr.catalog @@ -0,0 +1,314 @@ +# This file is part of systemd. +# +# Copyright 2012 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 . + +# Message catalog for systemd's own messages +# Croatian translation + +# Format kataloga je dokumentiran na +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# Za pojašnjenje zašto ovo radimo, posjetite https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: journal je pokrenut +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Journal proces sustava se pokrenuo, otvorio je journal + datoteke za upis i spreman je za obradu zahtjeva. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: journal je zaustavljen +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Journal proces sustava je isključio i zatvorio sve trenutno +aktivne journal datoteke. + +-- ec387f577b844b8fa948f33cad9a75e6 +Subject: Diskovni prostor koji koristi journal +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@JOURNAL_NAME@ (@JOURNAL_PATH@) trenutno koristi @CURRENT_USE_PRETTY@. +Najveća dopuštena upotreba je postavljena na @MAX_USE_PRETTY@. +Ostavljam najmanje @DISK_KEEP_FREE_PRETTY@ slobodno (trenutno dostupno @DISK_AVAILABLE_PRETTY@ diskovnog prostora). +Prisilno ograničenje upotrebe je @LIMIT_PRETTY@, od kojeg je @AVAILABLE_PRETTY@ još dostupno. + +Ograničenja kontroliraju koliko diskovnog prostora koristi journal mogu +se podesiti sa SystemMaxUse=, SystemKeepFree=, SystemMaxFileSize=, +RuntimeMaxUse=, RuntimeKeepFree=, RuntimeMaxFileSize= settings u +/etc/systemd/journald.conf. Pogledajte journald.conf(5) za više pojedinosti. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Poruka iz usluge je potisnuta +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Usluga je prijavila previše poruka u određenom vremenskom razdoblju. Poruke +iz usluge su odbačene. + +Zapamtite da samo poruke iz usluge u upitu su +odbačene, ostale poruke usluga nisu zahvaćene. + +Ograničenja koja kontroliraju kada je poruka odbačena mogu se podesiti +sa RateLimitIntervalSec= i RateLimitBurst= u +/etc/systemd/journald.conf. Pogledajte journald.conf(5) za više pojedinosti. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Journal poruka je propuštena +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Kernel poruka je izgubljena zato jer ih journal sustav nije mogao +dovoljno brzo obraditi. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Proces @COREDUMP_PID@ (@COREDUMP_COMM@) je izbacio jezgru +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Proces @COREDUMP_PID@ (@COREDUMP_COMM@) se srušio i izbacio jezgru. + +Rušenje programa je uobičajeno uzrokovano greškom u programiranju i +trebalo bi se prijaviti razvijatelju kao greška. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: Nova sesija @SESSION_ID@ je stvorena za korisnika @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Nova sesija sa ID @SESSION_ID@ je stvorena za korisnika @USER_ID@. + +Glavni proces sesije je @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: Sesija @SESSION_ID@ je prekinuta +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Sesija sa ID @SESSION_ID@ je prekinuta. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: Novo sjedište @SEAT_ID@ je sada dostupno +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Novo sjedište @SEAT_ID@ je podešeno i sada je dostupno. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Sjedište @SEAT_ID@ je sada uklonjeno +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Sjedište @SEAT_ID@ je uklonjeno i više nije dostupno. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Vrijeme promjene +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Sat sustava je promijenjen na @REALTIME@ microsekundi nakon 1. Siječnja, 1970 godine. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Vremenska zona je promijenjena u @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Vremenska zona je promijenjena u @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: Pokretanje sustava je sada završeno +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Sve usluge sustava koje su zadane za pokretanje pri pokretanju sustava +su uspješno pokrenute. Zapamtite da ovo ne znači da sada računalo +miruje zato jer se neke usluge još uvijek mogu pokretati. + +Pokretanje kernela zahtijeva @KERNEL_USEC@ mikrosekundi. + +Pokretanje početnog RAM diska zahtijeva @INITRD_USEC@ mikrosekundi. + +Pokretanje prostora korisnika zahtijeva @USERSPACE_USEC@ mikrosekundi. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: Pokrenuto je stanje spavanja @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Sustav je sada pokrenuo stanje spavanja @SLEEP@ + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: Završeno je stanje spavanja @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Sustav je sada završio stanje spavanja @SLEEP@ + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Pokrenuto je isključivanje sustava +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Pokrenuto je isključivanje sustava. Isključivanje je sada pokrenuto, +sve usluge sustava su prekinute i svi datotečni sustavi su odmontirani. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: Jedinica @UNIT@ je započela pokretanje +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jedinica @UNIT@ je započela pokretanje. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: Jedinica @UNIT@ je završila pokretanje +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jedinica @UNIT@ je završila pokretanje. + +Rezultat pokretanja je @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: Jedinica @UNIT@ je započela isključivanje +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jedinica @UNIT@ je započela isključivanje. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: Jedinica @UNIT@ je završila isključivanje +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jedinica @UNIT@ je završila isključivanje. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: Jedinica @UNIT@ nije uspjela +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jedinica @UNIT@ nije uspjela. + +Rezultat je @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: Jedinica @UNIT@ je započela ponovno učitavati podešavanja +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jedinica @UNIT@ je započela ponovno učitavati podešavanja + +-- 7b05ebc668384222baa8881179cfda54 +Subject: Jedinica @UNIT@ je završila ponovno učitavati podešavanja +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jedinica @UNIT@ je završila ponovno učitavati podešavanja + +Rezultat je @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Proces @EXECUTABLE@ se ne može pokrenuti +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Proces @EXECUTABLE@ se ne može pokrenuti i nije uspio. + +Broj greške vraćen ovim procesom je @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: Jedna ili više poruka se ne mogu proslijediti u dnevnik sustava +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jedna ili više poruka se ne mogu proslijediti u dnevnik sustava, usluge +su pokrenute istovremeno s journalom. Ovo uobičajeno označava da +implementacija dnevnika sustava ne može slijediti brzinu +zahtjeva poruka. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Točka montiranja nije prazna +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Direktorij @WHERE@ je određen za točku montiranja (drugi redak u +/etc/fstab ili Where= redak u datoteci systemd jedinice) i nije prazan. +To ne utječe na montiranje, ali postojeće datoteke u ovom direktoriju +postaju nedostupne. Kako bi vidjeli datoteke preko kojih je montirano, +ručno montirajte osnovni datotečni sustav na drugu lokaciju. + +-- 24d8d4452573402496068381a6312df2 +Subject: Virtualni stroj ili spremnik su pokrenuti +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Virtualni stroj @NAME@ sa vodećim @LEADER@ PID-om je +pokrenut i spreman je za korištenje. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: Virtualni stroj ili spremnik su isključeni +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Virtualni stroj @NAME@ sa vodećim PID-om @LEADER@ je +isključen. + +-- 36db2dfa5a9045e1bd4af5f93e1cf057 +Subject: DNSSEC način je isključen, jer ga poslužitelj ne podržava +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) resolved.conf(5) + +Usluga razrješavanja (systemd-resolved.service) je otkrila da +podešeni DNS poslužitelj ne podržava DNSSEC, i DNSSEC, kao rezultat +provjera je isključena. + +Ovaj događaj će zauzeti mjesto ako je DNSSEC=allow-downgrade podešen u +resolved.conf i podešeni DNS poslužitelj je nekompatibilan s DNSSEC. Zapamtite +da korištenje ovog načina dopušta povećanje DNSSEC napada, napadač bi mogao +isključiti DNSSEC provjeru na sustavu umetanjem DNS odgovora u +komunikacijski kanal što rezultira povećanjem napada poput ovog. + +Ovaj događaj bi mogao označavati da je DNS poslužitelj uistinu nekompatibilan s +DNSSEC ili da je napadač uspješno izvršio takav napad. + +-- 1675d7f172174098b1108bf8c7dc8f5d +Subject: DNSSEC provjera neuspješna +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +DNS zahtjev ili snimak resursa nije prošao DNSSEC provjeru. To uobičajeno +označava da je komunikacijski kanal mijenjan. + +-- 4d4408cfd0d144859184d1e65d7c8a65 +Subject: DNSSEC pouzdano sidro je opozvano +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +A DNSSEC trust anchor has been revoked. A new trust anchor has to be +configured, or the operating system needs to be updated, to provide an updated +DNSSEC trust anchor. diff --git a/src/grp-journal/catalog/systemd.hu.catalog b/src/grp-journal/catalog/systemd.hu.catalog new file mode 100644 index 0000000000..68e8c2572e --- /dev/null +++ b/src/grp-journal/catalog/systemd.hu.catalog @@ -0,0 +1,262 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2016 Gabor Kelemen +# +# 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 . + +# Message catalog for systemd's own messages + +# The catalog format is documented on +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# For an explanation why we do all this, see https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: A napló elindult +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszernapló folyamat elindult, megnyitotta írásra a naplófájlokat, +és most készen áll kérések feldolgozására. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: A napló leállt +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszernapló folyamat leállt, és bezárt minden jelenleg aktív naplófájlt. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Egy szolgáltatás üzenetei elnémítva +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Egy szolgáltatás túl sok üzenetet naplózott adott idő alatt. A +szolgáltatástól származó üzenetek eldobásra kerültek. + +Ne feledje, hogy csak a kérdéses szolgáltatás üzenetei kerültek eldobásra, + más szolgáltatások üzeneteit ez nem befolyásolja. + +Az üzenetek eldobását vezérlő korlátok az /etc/systemd/journald.conf +RateLimitIntervalSec= és RateLimitBurst= beállításaival adhatók meg. +Részletekért lásd a journald.conf(5) man oldalt. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Naplóüzenetek vesztek el +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Kernelüzenetek vesztek el, mert a naplózó rendszer nem tudta elég gyorsan +feldolgozni azokat. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Egy folyamat összeomlott: @COREDUMP_PID@ (@COREDUMP_COMM@) +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Ez a folyamat: @COREDUMP_PID@ (@COREDUMP_COMM@) összeomlott, és core fájlt + írt ki. + +Ez általában programozási hibát jelez az összeomló programban, és +a szállítója felé kell bejelenteni. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: Új munkamenet (@SESSION_ID@) létrehozva, felhasználója: @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Létrejött egy új munkamenet @SESSION_ID@ azonosítóval ezen felhasználóhoz: +@USER_ID@. + +A munkamenet vezető folyamata: @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: Munkamenet (@SESSION_ID@) befejezve +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +A következő azonosítójú munkamenet befejeződött: @SESSION_ID@. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: Elérhető egy új munkaállomás: @SEAT_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Beállításra kerül és használható egy új munkaállomás: @SEAT_ID@. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: A munkaállomás eltávolítva: @SEAT_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +A munkaállomás el lett távolítva, és már nem érhető el: @SEAT_ID@ + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Időmódosítás +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszeróra beállítva @REALTIME@ ezredmásodpercre 1970. január 1. után. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Időzóna-módosítás erre: @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszer időzónája módosítva lett erre: @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: A rendszer indítása kész +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszerindításkor szükséges indításhoz sorba állított összes +rendszerszolgáltatás elindult. Ne feledje, hogy ez nem jelenti, hogy a +gép üresjáratban van, mivel egyes szolgáltatások még az indítás +befejezésével lehetnek elfoglalva. + +A kernel indítása @KERNEL_USEC@ ezredmásodpercet igényelt. + +A kiinduló RAM lemez indítása @INITRD_USEC@ ezredmásodpercet igényelt. + +A felhasználói programok indítása @USERSPACE_USEC@ ezredmásodpercet igényelt. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: A rendszer „@SLEEP@” alvási állapotba lépett +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszer belépett ebbe az alvási állapotba: @SLEEP@. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: A rendszer „@SLEEP@” alvási állapotból kilépett +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszer kilépett ebből az alvási állapotból: @SLEEP@. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Rendszer leállítása kezdeményezve +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A systemd leállítása kezdeményezve. A leállítás megkezdődött, minden +rendszerszolgáltatás befejeződik, minden fájlrendszer leválasztásra kerül. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: A(z) @UNIT@ egység indítása megkezdődött +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység megkezdte az indulást. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: A(z) @UNIT@ egység befejezte az indulást +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység befejezte az indulást + +Az indítás eredménye: @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: A(z) @UNIT@ egység megkezdte a leállást +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység megkezdte a leállást. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: A(z) @UNIT@ egység befejezte a leállást +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység befejezte a leállást. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: A(z) @UNIT@ egység hibát jelzett +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység hibát jelzett. + +Az eredmény: @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: A(z) @UNIT@ egység megkezdte a beállításainak újratöltését +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység megkezdte a beállításainak újratöltését. + +-- 7b05ebc668384222baa8881179cfda54 +Subject: A(z) @UNIT@ egység befejezte a beállításainak újratöltését +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység befejezte a beállításainak újratöltését. + +Az eredmény: @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: A folyamat végrehajtása sikertelen: @EXECUTABLE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A folyamat végrehajtása sikertelen volt, és hibát jelzett: @EXECUTABLE@. + +A folyamat által visszaadott hibaszám: @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: Legalább egy üzenet nem továbbítható a rendszernaplónak +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Legalább egy üzenet nem volt továbbítható a journald-vel párhuzamosan futó +syslog szolgáltatásnak. Ez általában azt jelenti, hogy a syslog +megvalósítás nem volt képes lépést tartani a sorba állított +üzenetek sebességével. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: A csatolási pont nem üres +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A csatolási pontként megadott @WHERE@ könyvtár (második mező az /etc/fstab +fájlban, vagy a Where= sor a systemd egységfájlban) nem üres. Ez nem +akadályozza meg a csatolást, de a könyvtárban már meglévő fájlok +elérhetetlenné válnak. A fájlok láthatóvá tételéhez csatolja +az azokat tartalmazó fájlrendszert egy másodlagos helyre. + +-- 24d8d4452573402496068381a6312df2 +Subject: Egy virtuális gép vagy konténer elindult +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @NAME@ nevű virtuális gép (vezető PID: @LEADER@) elindult, és +használatra kész. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: Egy virtuális gép vagy konténer befejeződött +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @NAME@ nevű virtuális gép (vezető PID: @LEADER@) leállt. diff --git a/src/grp-journal/catalog/systemd.it.catalog b/src/grp-journal/catalog/systemd.it.catalog new file mode 100644 index 0000000000..b6fca48221 --- /dev/null +++ b/src/grp-journal/catalog/systemd.it.catalog @@ -0,0 +1,254 @@ +# This file is part of systemd. +# +# Copyright 2013 Daniele Medri +# +# 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 . + +# Message catalog for systemd's own messages + +-- f77379a8490b408bbe5f6940505a777b +Subject: Il registro è stato avviato +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Il processo relativo al registro di sistema è stato avviato, ha aperto i +file in scrittura ed è ora pronto a gestire richieste. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: Il registro è stato terminato +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Il processo relativo al registro di sistema è stato terminato e ha chiuso +tutti i file attivi. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: I messaggi di un servizio sono stati soppressi +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Un servizio ha registrato troppi messaggi in un dato periodo di tempo. +I messaggi del servizio sono stati eliminati. + +Solo i messaggi del servizio indicato sono stati +eliminati, i messaggi degli altri servizi rimangono invariati. + +I limiti oltre i quali i messaggi si eliminano si configurano +con RateLimitIntervalSec= e RateLimitBurst= in +/etc/systemd/journald.conf. Vedi journald.conf(5) per maggiori informazioni. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: I messaggi di un servizio sono stati perduti +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +I messaggi del kernel sono stati perduti perché, il registro di sistema +non è stato in grado di gestirli abbastanza velocemente. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Il processo @COREDUMP_PID@ (@COREDUMP_COMM@) ha generato un dump. +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Il processo @COREDUMP_PID@ (@COREDUMP_COMM@) si è bloccato generando un dump. + +Questo di solito capita per un errore di programmazione nell'applicazione e +dovrebbe essere segnalato al vendor come un bug. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: La nuova sessione @SESSION_ID@ è stata creata per l'utente @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Una nuova sessione con ID @SESSION_ID@ è stata creata per l'utente @USER_ID@. + +Il processo primario della sessione è @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: La sessione @SESSION_ID@ è terminata +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +La sessione con ID @SESSION_ID@ è terminata. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: La nuova postazione @SEAT_ID@ è ora disponibile +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +La nuova postazione @SEAT_ID@ è stata configurata ed è ora disponibile. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: La postazione @SEAT_ID@ è stata rimossa +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +La postazione @SEAT_ID@ è stata rimossa e non è più disponibile. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Cambio d'orario +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'orologio di sistema è cambiato in @REALTIME@ microsecondi dal 1 gennaio, 1970. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Il fuso orario è cambiato in @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Il fuso orario di sistema è cambiato in @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: Avvio del sistema completato. +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Tutti i servizi di sistema richiesti per la fase di avvio sono stati eseguiti +con successo. Nota che la macchina potrebbe non essere ancora pronta in quanto +i servizi attivati sono in fase di completamento. + +L'avvio del kernel ha richiesto @KERNEL_USEC@ microsecondi. + +L'avvio del disco RAM ha richiesto @INITRD_USEC@ microsecondi. + +L'avvio dello userspace ha richiesto @USERSPACE_USEC@ microsecondi. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: Il sistema è entrato in fase di pausa @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Il sistema è entrato nello stato di pausa @SLEEP@. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: Il sistema è uscito dalla fase di pausa @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Il sistema è uscito dallo stato di pausa @SLEEP@. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Il sistema è in fase di spegnimento +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Systemd è in fase di spegnimento. Tutti i servizi di sistema +saranno terminati e tutti i file systems smontati. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: L'unità @UNIT@ inizia la fase di avvio +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unità @UNIT@ ha iniziato la fase di avvio. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: L'unità @UNIT@ termina la fase di avvio +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unità @UNIT@ ha terminato la fase di avvio. + +La fase di avvio è @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: L'unità @UNIT@ inizia la fase di spegnimento +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unità @UNIT@ ha iniziato la fase di spegnimento. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: L'unità @UNIT@ termina la fase di spegnimento +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unità @UNIT@ ha terminato la fase di spegnimento. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: L'unità @UNIT@ è fallita +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unità @UNIT@ è fallita. + +Il risultato è @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: L'unità @UNIT@ inizia a caricare la propria configurazione +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unità @UNIT@ è iniziata ricaricando la propria configurazione + +-- 7b05ebc668384222baa8881179cfda54 +Subject: L'unità @UNIT@ termina il caricamento della propria configurazione +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unità @UNIT@ è terminata ricaricando la propria configurazione + +Il risultato è @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Il processo @EXECUTABLE@ non può essere eseguito +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Il processo @EXECUTABLE@ non può essere eseguito e termina. + +Il numero di errore restituito durante l'esecuzione del processo è @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: Uno o più messaggi non possono essere inoltrati a syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Uno o più messaggi non possono essere inviati al servizio syslog +eseguito in parallelo a journald. Questo di solito capita perché, +l'implementazione di syslog non sta al passo con la +velocità dei messaggi accodati. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Il punto di montaggio non è vuoto +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +La directory @WHERE@ è specificata come punto di montaggio (secondo campo +in /etc/fstab o nel campo Where= del file unità di systemd) e non è vuoto. +Questo non interferisce con il montaggio, ma i file pre-esistenti in questa +directory diventano inaccessibili. Per visualizzare i file, si suggerisce +di montare manualmente il file system indicato in una posizione secondaria. + +-- 24d8d4452573402496068381a6312df2 +Subject: Avviata macchina virtuale o container +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +La macchina virtuale @NAME@ con PID primario @LEADER@ è stata +avviata ed è pronta all'uso. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: Terminata macchina virtuale o container +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +La macchina virtuale @NAME@ con PID primario @LEADER@ è stata spenta. diff --git a/src/grp-journal/catalog/systemd.ko.catalog b/src/grp-journal/catalog/systemd.ko.catalog new file mode 100644 index 0000000000..2fc6b60b1b --- /dev/null +++ b/src/grp-journal/catalog/systemd.ko.catalog @@ -0,0 +1,264 @@ +# This file is part of systemd. +# +# Copyright 2012 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 . + +# Message catalog for systemd's own messages +# Korean translation + +# The catalog format is documented on +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# For an explanation why we do all this, see https://xkcd.com/1024/ +# +# Translator : +# Seong-ho Cho , 2015. + +-- f77379a8490b408bbe5f6940505a777b +Subject: 저널 시작 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +시스템 저널 프로세스를 시작했고 기록목적으로 저널 파일을 열었으며, +프로세스 요청을 기다리고 있습니다. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: 저널 멈춤 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +시스템 저널 프로세스를 껐고 현재 활성화 중인 저널 파일을 모두 +닫았습니다. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: 서비스의 메시지를 거절함 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +일정 시간동안 서비스에서 너무 많은 메시지를 기록했습니다. +서비스에서 오는 메시지를 거절했습니다. + +의문점이 있는 서비스로부터 오는 메시지만 거절했음을 참고하십시오 +다른 서비스의 메시지에는 영향을 주지 않습니다. + +메시지 거절 제어 제한 값은 /etc/systemd/journald.conf 의 +RateLimitIntervalSec= 변수와 RateLimitBurst= 변수로 설정합니다. +자세한 내용은 ournald.conf(5)를 살펴보십시오. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: 저널 메시지 놓침 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +저널 시스템에서 커널 메시지를 충분히 빠르게 처리할 수 없어 커널 + 메시지를 잃었습니다. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: 프로세스 @COREDUMP_PID@번 코어 덤프(@COREDUMP_COMM@) 생성함 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +프로세스 @COREDUMP_PID@번 (@COREDUMP_COMM@)이 비정상적으로 끝나 +코어 덤프를 생성했습니다. + +보통 비정상 종료 관리 프로그램에서 프로그래밍 오류를 나타내며, +제작자에게 버그로 보고해야합니다. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: @USER_ID@ 사용자의 새 @SESSION_ID@ 세션 만듦 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +@USER_ID@ 사용자의 새 @SESSION_ID@ 세션을 만들었습니다. + +이 세션의 관리 프로세스는 @LEADER@ 입니다. + +-- 3354939424b4456d9802ca8333ed424a +Subject: @SESSION_ID@ 세션 마침 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +@SESSION_ID@ 세션을 끝냈습니다. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: 새 @SEAT_ID@ 시트 사용할 수 있음 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +새 @SEAT_ID@ 시트를 설정했고 사용할 수 있습니다. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: @SEAT_ID@ 시트 제거함 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +@SEAT_ID@ 시트를 제거했으며 더이상 사용할 수 없습니다. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: 시간 바꿈 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +시스템 시계를 1970년 1월 1일 이후로 @REALTIME@ 마이크로초 지난 값으로 +설정했습니다. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: @TIMEZONE@ 시간대로 시간대 바꿈 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +시스템 시간대를 @TIMEZONE@ 시간대로 바꾸었습니다. + +-- b07a249cd024414a82dd00cd181378ff +Subject: 시스템 시동 마침 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +부팅 과정에 시작하려고 준비한 모든 시스템 서비스를 성공적으로 + 시작했습니다. 머신이 서비스처럼 대기중이라는 의미는 아니며 +지동을 완전히 마칠 때까지 사용중일 수도 있는 점 참고하십시오. + +커널 시동에 @KERNEL_USEC@ 마이크로초가 걸립니다. + +초기 램 디스크 시동에 @INITRD_USEC@ 마이크로초가 걸립니다. + +사용자 영역 시동에 @USERSPACE_USEC@ 마이크로초가 걸립니다. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: @SLEEP@ 대기 상태 진입 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@SLEEP@ 대기 상태로 진입했습니다. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: @SLEEP@ 대기 상태 마침 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@SLEEP@ 대기 상태를 마쳤습니다. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: 컴퓨터 끄기 시작 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +컴퓨터 끄기 동작을 시작했습니다. 모든 시스템 동작을 멈추고 +모든 파일 시스템의 마운트를 해제합니다. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: @UNIT@ 유닛 시작 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 유닛을 시작했습니다. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: @UNIT@ 유닛 시동 마침 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 유닛 시동을 마쳤습니다. + +시동 결과는 @RESULT@ 입니다. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: @UNIT@ 유닛 끝내기 동작 시작 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 유닛 끝내기 동작을 시작했습니다. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: @UNIT@ 유닛 끝내기 동작 마침 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 유닛 끝내기 동작을 마쳤습니다. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: @UNIT@ 유닛 동작 실패 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 유닛 동작에 실패했습니다. + +결과는 @RESULT@ 입니다. + +-- d34d037fff1847e6ae669a370e694725 +Subject: @UNIT@ 유닛 설정 다시 읽기 시작 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 유닛의 설정 다시 읽기를 시작했습니다 + +-- 7b05ebc668384222baa8881179cfda54 +Subject: @UNIT@ 유닛 설정 다시 읽기 완료 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 유닛의 설정 다시 읽기 동작을 끝냈습니다. + +결과는 @RESULT@ 입니다. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: @EXECUTABLE@ 프로세스 시작할 수 없음 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@EXECUTABLE@ 프로세스를 시작할 수 없어 실행에 실패했습니다. + +이 프로세스에서 반환한 오류 번호는 @ERRNO@번 입니다. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: 하나 이상의 메시지를 syslog에 전달할 수 없음 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +journald 서비스와 동시에 실행중인 syslog 서비스에 하나 이상의 메시지를 +전달할 수 없습니다. 보통 순차적으로 오는 메시지의 속도를 syslog 구현체가 +따라가지 못함을 의미합니다. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: 마운트 지점 비어있지 않음 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@WHERE@ 디렉터리를 마운트 지점으로 지정했으며 (/etc/fstab 파일의 + 두번째 필드 또는 systemd 유닛 파일의 Where= 필드) 비어있지 않습니다. +마운트 과정에 방해가 되진 않지만 이전에 이 디렉터리에 존재하는 파일에 + 접근할 수 없게 됩니다. 중복으로 마운트한 파일을 보려면, 근본 파일 +시스템의 다음 위치에 직접 마운트하십시오. + +-- 24d8d4452573402496068381a6312df2 +Subject: 가상 머신 또는 컨테이너 시작 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@LEADER@ 프로세스 ID로 동작하는 @NAME@ 가상 머신을 시작했으며, +이제부터 사용할 수 있습니다. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: 가상 머신 또는 컨테이너 마침 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@LEADER@ 프로세스 ID로 동작하는 @NAME@ 가상 머신을 껐습니다. diff --git a/src/grp-journal/catalog/systemd.pl.catalog b/src/grp-journal/catalog/systemd.pl.catalog new file mode 100644 index 0000000000..d8059e93cd --- /dev/null +++ b/src/grp-journal/catalog/systemd.pl.catalog @@ -0,0 +1,315 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2014, 2015, 2016 Piotr Drąg +# +# 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 . + +# Message catalog for systemd's own messages +# Polish translation + +# The catalog format is documented on +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# For an explanation why we do all this, see https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: Uruchomiono dziennik +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Systemowy proces dziennika został uruchomiony, otworzył pliki dziennika do +zapisu i jest gotowy do przetwarzania żądań. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: Zatrzymano dziennik +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Systemowy proces dziennika został wyłączony i zamknął wszystkie obecnie +aktywne pliki dziennika. + +-- ec387f577b844b8fa948f33cad9a75e6 +Subject: Miejsce na dysku używane przez dziennik +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@JOURNAL_NAME@ (@JOURNAL_PATH@) obecnie używa @CURRENT_USE_PRETTY@. +Maksymalnie może używać @MAX_USE_PRETTY@. +Zostawianie co najmniej @DISK_KEEP_FREE_PRETTY@ wolnego (z obecnie dostępnego @DISK_AVAILABLE_PRETTY@ miejsca na dysku). +Wymuszone ograniczenie użycia wynosi więc @LIMIT_PRETTY@, z czego @AVAILABLE_PRETTY@ jest nadal dostępne. + +Ograniczenia kontrolujące ilość miejsca na dysku używanego przez dziennik +można konfigurować za pomocą ustawień SystemMaxUse=, SystemKeepFree=, +SystemMaxFileSize=, RuntimeMaxUse=, RuntimeKeepFree=, RuntimeMaxFileSize= +w pliku /etc/systemd/journald.conf. Strona journald.conf(5) zawiera więcej +informacji. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Ograniczono komunikaty z usługi +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Usługa zapisała za dużo komunikatów w określonym czasie. Komunikaty z usługi +zostały pominięte. + +Proszę zauważyć, że tylko komunikaty z danej usługi zostały pominięte. Nie ma +to wpływu na komunikaty innych usług. + +Ograniczenia kontrolujące pomijanie komunikatów mogą być konfigurowane +za pomocą opcji RateLimitIntervalSec= i RateLimitBurst= w pliku +/etc/systemd/journald.conf. Strona journald.conf(5) zawiera więcej informacji. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Utracono komunikaty dziennika +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Komunikaty jądra zostały utracone, ponieważ system dziennika nie mógł +przetworzyć ich odpowiednio szybko. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Proces @COREDUMP_PID@ (@COREDUMP_COMM@) zrzucił plik core +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Proces @COREDUMP_PID@ (@COREDUMP_COMM@) uległ awarii i zrzucił plik core. + +Zwykle wskazuje to na błąd programistyczny w danym programie i powinno zostać +zgłoszone jego producentowi jako błąd. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: Utworzono nową sesję @SESSION_ID@ dla użytkownika @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Nowa sesja o identyfikatorze @SESSION_ID@ została utworzona dla użytkownika +@USER_ID@. + +Proces prowadzący sesji: @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: Zakończono sesję @SESSION_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Sesja o identyfikatorze @SESSION_ID@ została zakończona. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: Dostępne jest nowe stanowisko @SEAT_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Nowe stanowisko @SEAT_ID@ zostało skonfigurowane i jest teraz dostępne. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Usunięto stanowisko @SEAT_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Stanowisko @SEAT_ID@ zostało usunięte i nie jest już dostępne. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Zmiana czasu +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Zegar systemowy został zmieniony na @REALTIME@ μs po 1 stycznia 1970. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Zmiana strefy czasowej na @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Systemowa strefa czasowa została zmieniona na @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: Ukończono uruchamianie systemu +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Wszystkie usługi systemowe obowiązkowo zakolejkowane do włączenia podczas +uruchamiania systemu zostały pomyślnie uruchomione. Proszę zauważyć, że nie +oznacza to, że komputer jest bezczynny, jako że usługi mogą wciąż kończyć +proces uruchamiania. + +Uruchamianie jądra zajęło @KERNEL_USEC@ μs. + +Uruchamianie początkowego dysku RAM zajęło @INITRD_USEC@ μs. + +Uruchamianie przestrzeni użytkownika zajęło @USERSPACE_USEC@ μs. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: Przejście do stanu uśpienia @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +System przeszedł do stanu uśpienia @SLEEP@. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: Wyjście ze stanu uśpienia @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +System wyszedł ze stanu uśpienia @SLEEP@. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Zainicjowano wyłączenie systemu +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Zainicjowano wyłączenie systemd. Wyłączenie zostało rozpoczęte i wszystkie +usługi systemowe zostały zakończone, a wszystkie systemy plików odmontowane. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: Rozpoczęto uruchamianie jednostki @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jednostka @UNIT@ rozpoczęła uruchamianie. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: Ukończono uruchamianie jednostki @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jednostka @UNIT@ ukończyła uruchamianie. + +Wynik uruchamiania: @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: Rozpoczęto wyłączanie jednostki @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jednostka @UNIT@ rozpoczęła wyłączanie. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: Ukończono wyłączanie jednostki @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jednostka @UNIT@ ukończyła wyłączanie. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: Jednostka @UNIT@ się nie powiodła +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jednostka @UNIT@ się nie powiodła. + +Wynik: @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: Rozpoczęto ponowne wczytywanie konfiguracji jednostki @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jednostka @UNIT@ rozpoczęła ponowne wczytywanie swojej konfiguracji. + +-- 7b05ebc668384222baa8881179cfda54 +Subject: Ukończono ponowne wczytywanie konfiguracji jednostki @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jednostka @UNIT@ ukończyła ponowne wczytywanie swojej konfiguracji. + +Wynik: @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Nie można wykonać procesu @EXECUTABLE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Proces @EXECUTABLE@ nie mógł zostać wykonany i się nie powiódł. + +Numer błędu zwrócony przez ten proces: @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: Nie można przekazać jednego lub więcej komunikatów do syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jeden lub więcej komunikatów nie może zostać przekazanych do usługi syslog +uruchomionej obok journald. Zwykle oznacza to, że implementacja syslog nie +jest w stanie nadążyć za prędkością kolejki komunikatów. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Punkt montowania nie jest pusty +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Katalog @WHERE@ został podany jako punkt montowania (drugie pole w pliku +/etc/fstab lub pole Where= w pliku jednostki systemd) i nie jest pusty. Nie +wpływa to na montowanie, ale wcześniej istniejące pliki w tym katalogu stają +się niedostępne. Aby zobaczyć te pliki, proszę ręcznie zamontować system +plików w innym położeniu. + +-- 24d8d4452573402496068381a6312df2 +Subject: Uruchomiono maszynę wirtualną lub kontener +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Maszyna wirtualna @NAME@ (PID prowadzący @LEADER@) została uruchomiona i jest +gotowa do użycia. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: Zakończono maszynę wirtualną lub kontener +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Maszyna wirtualna @NAME@ (PID prowadzący @LEADER@) została wyłączona. + +-- 36db2dfa5a9045e1bd4af5f93e1cf057 +Subject: Wyłączono tryb DNSSEC, ponieważ serwer go nie obsługuje +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) resolved.conf(5) + +Usługa resolver (systemd-resolved.service) wykryła, że skonfigurowany serwer +DNS nie obsługuje DNSSEC, w wyniku czego walidacja DNSSEC została wyłączona. + +To zdarzenie będzie miało miejsce, jeśli skonfigurowano DNSSEC=allow-downgrade +w pliku resolved.conf, a skonfigurowany serwer DNS jest niezgodny z DNSSEC. +Proszę zauważyć, że używanie tego trybu umożliwia ataki wyłączające DNSSEC, +ponieważ atakujący będzie mógł wyłączyć walidację DNSSEC na komputerze przez +umieszczenie odpowiednich odpowiedzi DNS w kanale komunikacji. + +To zdarzenie może wskazywać, że serwer DNS jest faktycznie niezgodny z DNSSEC, +albo że atakującemu udało się upozorować atak tego typu. + +-- 1675d7f172174098b1108bf8c7dc8f5d +Subject: Walidacja DNSSEC się nie powiodła +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +Zapytanie DNS lub ustawiony wpis zasobu nie przeszedł walidacji DNSSEC. +Zwykle wskazuje to, że ktoś manipulował używanym kanałem komunikacji. + +-- 4d4408cfd0d144859184d1e65d7c8a65 +Subject: Unieważniono kotwicę zaufania DNSSEC +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +Kotwica zaufania DNSSEC została unieważniona. Należy skonfigurować nową, albo +system operacyjny musi zostać zaktualizowany, aby dostarczyć zaktualizowaną +kotwicę zaufania DNSSEC. diff --git a/src/grp-journal/catalog/systemd.pt_BR.catalog b/src/grp-journal/catalog/systemd.pt_BR.catalog new file mode 100644 index 0000000000..8b856e8355 --- /dev/null +++ b/src/grp-journal/catalog/systemd.pt_BR.catalog @@ -0,0 +1,264 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2015 Rafael Ferreira (translation) +# +# 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 . + +# Catálogo de mensagens para as mensagens do próprio systemd + +# O formato do catálogo está documentado em +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# Para uma explicação do porquê de fazermos tudo isso, veja +# https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: O jornal foi inciado +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +O processo jornal do sistema foi iniciado, arquivos foram abertos e está +pronto para processar requisições. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: O jornal foi interrompido +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +O processo do jornal do sistema foi desligado e todos os arquivos de jornal +do sistema foram fechados. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Mensagens de um serviço foram suprimidas +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Um serviço registrou no log um número excessivo de mensagens dentro de um +período de tempo. Mensagens do serviço foram descartadas. + +Note que apenas mensagens de um serviço em questão foram descartadas; outras +mensagens dos serviços não foram afetadas. + +Os controles de limites de quando as mensagens são descartadas pode ser +configurado com RateLimitIntervalSec= e RateLimitBurst= no +/etc/systemd/journald.conf. Veja journald.conf(5) para detalhes. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Mensagens do jornal foram perdidas +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Mensagens do kernel foram perdidas pois o sistema do jornal não pôde +processá-las em velocidade suficiente para a demanda. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Processo @COREDUMP_PID@ (@COREDUMP_COMM@) despejou núcleo +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Processo @COREDUMP_PID@ (@COREDUMP_COMM@) travou e despejou o núcleo. + +Isso normalmente indica um erro de programação no programa que travou e +deveria ser relatado para seu fabricante como um erro. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: A nova sessão @SESSION_ID@ foi criada para usuário o @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Uma nova sessão com o ID @SESSION_ID@ foi criada para o usuário @USER_ID@. + +O processo originador da sessão é @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: Sessão @SESSION_ID@ foi terminada +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Um sessão com o ID @SESSION_ID@ foi terminada. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: Um novo seat @SEAT_ID@ está disponível +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Um novo seat @SEAT_ID@ foi configurado e está disponível. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Seat @SEAT_ID@ foi removido agora +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Um seat @SEAT_ID@ foi removido e não está mais disponível. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Time change +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +O relógio do sistema foi alterado para @REALTIME@ microssegundos após 1º de +janeiro de 1970. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Fuso horário alterado para @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +O fuso horário do sistema foi alterado para @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: Inicialização do sistema foi concluída +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Todos os serviços do sistema necessários que estão enfileirados para +executar na inicialização do sistema, foram iniciados com sucesso. Note +que isso não significa que a máquina está ociosa, pois os serviços podem +ainda estar ocupados com a inicialização completa. + +Inicialização do kernel precisou @KERNEL_USEC@ microssegundos. + +Disco de RAM inicial precisou de @INITRD_USEC@ microssegundos. + +Inicialização do espaço do usuário precisou de @USERSPACE_USEC@ microssegundos. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: Estado de suspensão do sistema @SLEEP@ iniciado +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +O sistema entrou agora no estado de suspensão @SLEEP@. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: Estado de suspensão do sistema @SLEEP@ finalizado +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +O sistema saiu agora do estado de suspensão @SLEEP@. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Desligamento do sistema iniciado +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Desligamento do sistema foi inicializado. O desligamento se iniciou e todos +os serviços do sistema foram terminados e todos os sistemas desmontados. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: Unidade @UNIT@ sendo iniciado +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A unidade @UNIT@ está sendo iniciada. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: Unidade @UNIT@ concluiu a inicialização +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A unidade @UNIT@ concluiu a inicialização. + +The start-up result is @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: Unidade @UNIT@ sendo desligado +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A unidade @UNIT@ está sendo desligada. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: A unidade @UNIT@ concluiu o desligamento +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A unidade @UNIT@ concluiu o desligamento. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: A unidade @UNIT@ falhou +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A unidade @UNIT@ falhou. + +O resultado é @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: Unidade @UNIT@ iniciou recarregamento de sua configuração +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A unidade @UNIT@ iniciou o recarregamento de sua configuração. + +-- 7b05ebc668384222baa8881179cfda54 +Subject: Unidade @UNIT@ concluiu recarregamento de sua configuração +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A unidade @UNIT@ concluiu o recarregamento de sua configuração. + +O resultado é @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Processo @EXECUTABLE@ não pôde ser executado +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +O processo @EXECUTABLE@ não pôde ser executado e falhou. + +O número de erro retornado por este processo é @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: Uma ou mais mensagens não puderam ser encaminhadas para o syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Uma ou mais mensagens não puderam ser encaminhadas para o serviço do syslog +em execução paralela ao journald. Isso normalmente indica que a implementação +do syslog não foi capaz de se manter com a velocidade das mensagens +enfileiradas. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Ponto de montagem não está vazio +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +O diretório @WHERE@ está especificado como ponto de montagem (o segundo campo +no /etc/fstab ou campo Where= no arquivo de unidade do systemd) e não está +vazio. Isso não interfere com a montagem, mas os arquivos pré-existentes +neste diretório se tornaram inacessívels. Para ver aqueles arquivos, sobre os +quais foi realizada a montagem, por favor monte manualmente o sistema de +arquivos subjacente para uma localização secundária. + +-- 24d8d4452573402496068381a6312df2 +Subject: Uma máquina virtual ou contêiner foi iniciado +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A máquina virtual @NAME@ com seu PID @LEADER@ incial foi iniciada e está +pronto para ser usad. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: Uma máquina virtual ou contêiner foi terminado +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A máquina virtual @NAME@ com seu PID @LEADER@ incial foi desligada. diff --git a/src/grp-journal/catalog/systemd.ru.catalog b/src/grp-journal/catalog/systemd.ru.catalog new file mode 100644 index 0000000000..e56dbe3acc --- /dev/null +++ b/src/grp-journal/catalog/systemd.ru.catalog @@ -0,0 +1,354 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2013-2016 Sergey Ptashnick +# +# 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 . + +# Message catalog for systemd's own messages +# Russian translation + +# Формат каталога сообщений описан по ссылке +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# Перед каждым элементом в комментарии указан Subject исходного +# сообщения (на английском). + +# Subject: The Journal has been started +-- f77379a8490b408bbe5f6940505a777b +Subject: Запущена служба журналирования +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Процесс, отвечающий за журналирование системных событий, успешно запустился, +открыл для записи файлы журнала, и готов обрабатывать запросы. + +# Subject: The Journal has been stopped +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: Служба журналирования остановлена +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Процесс, отвечающий за журналирование системных событий, завершил работу и +закрыл все свои файлы. + +# Subject: Disk space used by the journal +-- ec387f577b844b8fa948f33cad9a75e6 +Subject: Место на диске, занятое журналом +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@JOURNAL_NAME@ (@JOURNAL_PATH@) сейчас занимает @CURRENT_USE_PRETTY@. +Максимальный разрешенный размер составляет @MAX_USE_PRETTY@. +Оставляем свободными как минимум @DISK_KEEP_FREE_PRETTY@ (сейчас на диске +свободно @DISK_AVAILABLE_PRETTY@). +Таким образом, предел использования составляет @LIMIT_PRETTY@, из которых +@AVAILABLE_PRETTY@ пока свободно. + +Ограничения на размер журнала настраиваются при помощи параметров +SystemMaxUse=, SystemKeepFree=, SystemMaxFileSize=, RuntimeMaxUse=, +RuntimeKeepFree=, RuntimeMaxFileSize= в файле /etc/systemd/journald.conf. +Более подробные сведения вы можете получить на справочной странице +journald.conf(5). + +# Subject: Messages from a service have been suppressed +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Часть сообщений от службы пропущена +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Служба отправила слишком много сообщений за короткий промежуток времени. +Часть сообщений была пропущена. + +Обратите внимание, что были пропущены сообщения только от этой службы, +сообщения других служб не затронуты. + +Предел, после которого служба журнала начинает игнорировать сообщения, +настраивается параметрами RateLimitIntervalSec= и RateLimitBurst= в файле +/etc/systemd/journald.conf. Подробности смотрите на странице руководства +journald.conf(5). + +# Subject: Journal messages have been missed +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Часть сообщений ядра пропущена +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Часть сообщений, поступивших от ядра, была потеряна, так как служба +журналирования не успела их обработать. + +# Subject: Process @COREDUMP_PID@ (@COREDUMP_COMM@) dumped core +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Процесс @COREDUMP_PID@ (@COREDUMP_COMM@) сбросил дамп памяти +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Процесс @COREDUMP_PID@ (@COREDUMP_COMM@) завершился из-за критической ошибки. +Записан дамп памяти. + +Вероятно, это произошло из-за ошибки, допущенной в коде программы. +Рекомендуется сообщить её разработчикам о возникшей проблеме. + +# Subject: A new session @SESSION_ID@ has been created for user @USER_ID@ +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: Для пользователя @USER_ID@ создан новый сеанс @SESSION_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Для пользователя @USER_ID@ создан новый сеанс с идентификатором @SESSION_ID@. + +Главный процесс нового сеанса имеет индентификатор @LEADER@. + +# Subject: A session @SESSION_ID@ has been terminated +-- 3354939424b4456d9802ca8333ed424a +Subject: Сеанс @SESSION_ID@ завершен +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Сеанс с идентификатором @SESSION_ID@ завершился. + +# Subject: A new seat @SEAT_ID@ is now available +-- fcbefc5da23d428093f97c82a9290f7b +Subject: Добавлено новое рабочее место @SEAT_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Новое рабочее место (seat) @SEAT_ID@ полностью настроено и готово к +использованию. + +# Subject: A seat @SEAT_ID@ has now been removed +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Рабочее место @SEAT_ID@ отключено +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Рабочее место (seat) @SEAT_ID@ было отключено. + +# Subject: Time change +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Переведены системные часы +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Системные часы были переведены. Сейчас они показывают @REALTIME@ микросекунд +с момента 00:00:00 1 января 1970 года. + +# Subject: Time zone change to @TIMEZONE@ +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Часовой пояс изменен на @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Системный часовой пояс был изменен. Новое значение: @TIMEZONE@. + +# Subject: System start-up is now complete +-- b07a249cd024414a82dd00cd181378ff +Subject: Запуск системы завершен +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Все системные службы, запуск которых предписан настройками, были запущены. +Впрочем, это ещё не означает, что система в данный момент ничем не занята, +так как некоторые службы могут продолжать инициализацию даже после того, как +отчитались о своем запуске. + +Запуск ядра занял @KERNEL_USEC@ микросекунд. + +Процессы начального RAM-диска (initrd) отработали за @INITRD_USEC@ микросекунд. + +Запуск системных служб занял @USERSPACE_USEC@ микросекунд. + +# Subject: System sleep state @SLEEP@ entered +-- 6bbd95ee977941e497c48be27c254128 +Subject: Система перешла в состояние сна (@SLEEP@) +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Система была переведена в состояние сна (@SLEEP@). + +# Subject: System sleep state @SLEEP@ left +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: Система вышла из состояния сна (@SLEEP@) +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Система была выведена из состояния сна (@SLEEP@). + +# Subject: System shutdown initiated +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Подготовка системы к выключению +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Начат процесс подготовки к выключению компьютера. Останавливаются все системные +службы, отмонтируются все файловые системы. + +# Subject: Unit @UNIT@ has begun with start-up +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: Начинается запуск юнита @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Начат процесс запуска юнита @UNIT@. + +# Subject: Unit @UNIT@ has finished start-up +-- 39f53479d3a045ac8e11786248231fbf +Subject: Запуск юнита @UNIT@ завершен +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Процесс запуска юнита @UNIT@ был завершен. + +Результат: @RESULT@. + +# Subject: Unit @UNIT@ has begun shutting down +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: Начинается остановка юнита @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Начат процесс остановки юнита @UNIT@. + +# Subject: Unit @UNIT@ has finished shutting down +-- 9d1aaa27d60140bd96365438aad20286 +Subject: Завершена остановка юнита @UNIT@. +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Процесс остановки юнита @UNIT@ был завершен. + +# Subject: Unit @UNIT@ has failed +-- be02cf6855d2428ba40df7e9d022f03d +Subject: Ошибка юнита @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Произошел сбой юнита @UNIT@. + +Результат: @RESULT@. + +# Subject: Unit @UNIT@ has begun with reloading its configuration +-- d34d037fff1847e6ae669a370e694725 +Subject: Юнит @UNIT@ начал перечитывать свои настройки +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Юнит @UNIT@ начал процесс перечитывания своей конфигурации. + +# Subject: Unit @UNIT@ has finished reloading its configuration +-- 7b05ebc668384222baa8881179cfda54 +Subject: Юнит @UNIT@ завершил перечитывание своих настроек +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Юнит @UNIT@ завершил процесс перечитывания своей конфигурации. + +Результат: @RESULT@. + +# Subject: Process @EXECUTABLE@ could not be executed +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Не удалось запустить процесс @EXECUTABLE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Сбой: не удалось запустить процесс @EXECUTABLE@. + +Код ошибки: @ERRNO@. + +# Subject: One or more messages could not be forwarded to syslog +-- 0027229ca0644181a76c4e92458afa2e +Subject: Часть сообщений не удалось передать процессу syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Не удалось передать некоторые сообщения демону системного лога (syslog), +дублирующему работу службы системного журнала. Скорее всего, причина в том, что +используемая реализация syslog не успевает обрабатывать сообщения с достаточной +скоростью. + +# Subject: Mount point is not empty +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Каталог, являющийся точкой монтирования, не пуст +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Каталог @WHERE@, который был указан в качестве точки монтирования (во втором +столбце файла /etc/fstab, либо в параметре Where= файла конфигурации юнита), +не является пустым. Это никак не мешает монтированию, однако ранее находившиеся +в нем файлы будут недоступны. Чтобы получить к ним доступ, вы можете вручную +перемонтировать эту файловую систему в другую точку. + +# Subject: A virtual machine or container has been started +-- 24d8d4452573402496068381a6312df2 +Subject: Запущена виртуальная машина/контейнер +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Виртуальная машина @NAME@ (идентификатор главного процесса: @LEADER@) запущена и +готова к работе. + +# Subject: A virtual machine or container has been terminated +-- 58432bd3bace477cb514b56381b8a758 +Subject: Остановлена виртуальная машина/контейнер +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Виртуальная машина @NAME@ (идентификатор главного процесса: @LEADER@) выключена. + +# Subject: DNSSEC mode has been turned off, as server doesn't support it +-- 36db2dfa5a9045e1bd4af5f93e1cf057 +Subject: Механизм DNSSEC был отключен, так как DNS-сервер его не поддерживает +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) resolved.conf(5) + +Служба разрешения имен хостов (systemd-resolved.service) определила, что +указанный в настойках DNS-сервер не поддерживает технологию DNSSEC, и +автоматически отключила DNSSEC-проверки. + +Данное событие возникает, если в файле resolved.conf указан параметр +DNSSEC=allow-downgrade, и вышестоящий DNS-сервер не поддерживает DNSSEC. +Обратите внимание, что режим allow-downgrade допускает возможность атаки +"DNSSEC downgrade", в ходе которой атакующий хакер блокирует проверки DNSSEC +путем отправки ложных сообщений от имени DNS-сервера. + +Возникновение данного события может свидетельствовать как о том, что ваш +DNS-сервер не поддерживает DNSSEC, так и о том, что некий хакер успешно провел +против вас атаку, направленную на блокировку DNSSEC-проверок. + +# Subject: DNSSEC validation failed +-- 1675d7f172174098b1108bf8c7dc8f5d +Subject: Проверка DNSSEC провалена +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +DNS-запрос или отдельная ресурсная запись не прошла проверку DNSSEC. +Как правило, это свидетельствует о постороннем вмешательстве в канал связи. + +# Subject: A DNSSEC trust anchor has been revoked +-- 4d4408cfd0d144859184d1e65d7c8a65 +Subject: Открытый ключ DNSSEC был отозван +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +Открытый ключ (trust ahcnor) DNSSEC был отозван. Необходимо настроить новый +открытый ключ, либо обновить систему, чтобы получить обновленный открытый ключ. diff --git a/src/grp-journal/catalog/systemd.sr.catalog b/src/grp-journal/catalog/systemd.sr.catalog new file mode 100644 index 0000000000..cc689b7956 --- /dev/null +++ b/src/grp-journal/catalog/systemd.sr.catalog @@ -0,0 +1,262 @@ +# This file is part of systemd. +# +# Copyright 2012 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 . + +# Message catalog for systemd's own messages +# Serbian translation + +# Формат каталога је документован на +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# Да бисте видели зашто ово радимо, погледајте https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: Журнал је покренут +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Системски журналски процес се покренуо, отворио журналске +датотеке за упис и спреман је за обраду захтева. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: Журнал је заустављен +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Системски журналски процес се зауставио и затворио све тренутно +отворене журналске датотеке. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Поруке од услуге су утишане +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Услуга је уписала сувише порука за једно време. Поруке +од услуге су одбачене. + +Знајте да су само поруке од ове услуге одбачене, друге +услуге нису захваћене овим. + +Ограничења која подешавају начин на који се поруке одбацују се могу подесити +помоћу „RateLimitIntervalSec=“ и „RateLimitBurst=“ параметара унутар датотеке +/etc/systemd/journald.conf. Погледајте journald.conf(5) за појединости. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Журналске поруке су изгубљене +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Поруке кернела су изгубљене јер журналски систем није могао да их +обради довољно брзо. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Процес @COREDUMP_PID@ (@COREDUMP_COMM@) је избацио своје језгро +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Процес @COREDUMP_PID@ (@COREDUMP_COMM@) је пао и избацио своје језгро. + +Ово обично значи да постоји грешка у програму који је пао и ова +грешка треба да се пријави продавцу. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: Нова сесија @SESSION_ID@ је направљена за корисника @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Нова сесија са ИБ-ом @SESSION_ID@ је направљена за корисника @USER_ID@. + +Водећи процес сесије је @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: Сесија @SESSION_ID@ је окончана +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Сесија са ИБ-ом @SESSION_ID@ је окончана. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: Ново седиште @SEAT_ID@ је сада доступно +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Ново седиште @SEAT_ID@ је исподешавано и сада је доступно. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Седиште @SEAT_ID@ је сада уклоњено +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Седиште @SEAT_ID@ је сада уклоњено и више није доступно. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Време је промењено +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Системски сат је сада подешен на @REALTIME@ микросекунде након 1. јануара 1970. године. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Временска зона је промењена на @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Временска зона је промењена на @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: Подизање система је сада готово +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Све системске услуге које су заказане за подизање су успешно покренуте. +Знајте да ово не значи да је машина сада беспослена јер услуге могу +и даље бити заузете завршавањем покретања система. + +Подизање кернела је трајало @KERNEL_USEC@ микросекунде. + +Подизање почетног РАМ диска је трајало @INITRD_USEC@ микросекунде. + +Подизање корисничких програма је трајало @USERSPACE_USEC@ микросекунде. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: Системско стање спавања @SLEEP@ започето +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Систем је сада ушао у @SLEEP@ стање спавања. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: Системско стање спавања @SLEEP@ напуштено +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Систем је изашао из @SLEEP@ стања спавања. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Гашење система започето +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Систем-де гашење је започето. Гашење је сада почело и све +системске услуге су окончане и сви системи датотека откачени. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: Јединица @UNIT@ је почела са покретањем +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Јединица @UNIT@ је почела са покретањем. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: Јединица @UNIT@ је завршила са покретањем +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Јединица @UNIT@ је завршила са покретањем. + +Исход покретања је @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: Јединица @UNIT@ је почела са гашењем +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Јединица @UNIT@ је почела са гашењем. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: Јединица @UNIT@ је завршила са гашењем +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Јединица @UNIT@ је завршила са гашењем. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: Јединица @UNIT@ је пукла +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Јединица @UNIT@ је пукла. + +Исход је @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: Јединица @UNIT@ је почела са поновним учитавањем свог подешавања +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Јединица @UNIT@ је почела са поновним учитавањем свог подешавања + +-- 7b05ebc668384222baa8881179cfda54 +Subject: Јединица @UNIT@ је завршила са поновним учитавањем свог подешавања +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Јединица @UNIT@ је завршила са поновним учитавањем свог подешавања + +Исход је @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Процес @EXECUTABLE@ није могао бити извршен +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Процес @EXECUTABLE@ није могао бити извршен и пукао је. + +Овај процес је вратио број грешке @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: Једна или више порука није могло бити прослеђено системском записнику +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Једна или више порука није могло бити прослеђено „syslog“ услузи +која ради упоредно са журнал-деом. Ово обично значи да спроведена +„syslog“ услуга није могла да издржи брзину свих надолазећих +порука у реду. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Тачка качења није празна +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Директоријум @WHERE@ је наведен као тачка качења (друго поље у +/etc/fstab датотеци или у „Where=“ пољу систем-де јединичне датотеке) +и он није празан. Ово не утиче на качење али ће већ постојеће датотеке у +овом директоријуму постати недоступне. Да бисте видели ове недоступне +датотеке, ручно прикачите основни систем датотека у другу +путању. + +-- 24d8d4452573402496068381a6312df2 +Subject: Виртуелна машина или контејнер је покренут(а) +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Виртуелна машина @NAME@ са водећим ПИБ-ом @LEADER@ је +покренута и сада је спремна за коришћење. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: Виртуелна машина или контејнер је окончан(а) +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Виртуелна машина @NAME@ са водећим ПИБ-ом @LEADER@ је +угашена. diff --git a/src/grp-journal/catalog/systemd.zh_CN.catalog b/src/grp-journal/catalog/systemd.zh_CN.catalog new file mode 100644 index 0000000000..ed59fc9250 --- /dev/null +++ b/src/grp-journal/catalog/systemd.zh_CN.catalog @@ -0,0 +1,253 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2015 Boyuan Yang +# +# 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 . + +# Message catalog for systemd's own messages +# Simplified Chinese translation + +# 本 catalog 文档格式被记载在 +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# 如需了解我们为什么做这些工作,请见 https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: 日志已开始 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系统日志进程已启动,已打开供写入的日志文件并准备好处理请求。 + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: 日志已停止 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系统日志进程已终止,并已关闭所有当前活动的日志文件。 + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: 由某个服务而来的消息已被抑制 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +某个服务在一个时间周期内记录了太多消息。 +从该服务而来的消息已被丢弃。 + +请注意只有由有问题的服务传来的消息被丢弃, +其它服务的消息不受影响。 + +可以在 /etc/systemd/journald.conf 中设定 RateLimitIntervalSec= +以及 RateLimitBurst = 的值以控制丢弃信息的限制。 +请参见 journald.conf(5) 以了解详情。 + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: 日志消息已遗失 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +因日志系统对内核消息的处理速度不够快, +部分信息已经遗失。 + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: 进程 @COREDUMP_PID@ (@COREDUMP_COMM@) 核心已转储 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +进程 @COREDUMP_PID@ (@COREDUMP_COMM@) 已崩溃并进行核心转储。 + +这通常意味着崩溃程序中存在编程错误,并应当将此错误向其开发者报告。 + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: 一个新会话 @SESSION_ID@ 已为用户 @USER_ID@ 建立 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +一个 ID 为 @SESSION_ID@ 的新会话已为用户 @USER_ID@ 建立。 + +该会话的首进程为 @LEADER@。 + +-- 3354939424b4456d9802ca8333ed424a +Subject: 会话 @SESSION_ID@ 已终止 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +一个 ID 为 @SESSION_ID@ 的会话已终止。 + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: 一个新的座位 @SEAT_ID@ 可用 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +一个新的座位 @SEAT_ID@ 已被配置并已可用。 + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: 座位 @SEAT_ID@ 已被移除 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +座位 @SEAT_ID@ 已被移除并不再可用。 + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: 时间已变更 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系统时钟已变更为1970年1月1日后 @REALTIME@ 微秒。 + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: 时区变更为 @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系统时区已变更为 @TIMEZONE@。 + +-- b07a249cd024414a82dd00cd181378ff +Subject: 系统启动已完成 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +所有系统启动时需要的系统服务均已成功启动。 +请注意这并不代表现在机器已经空闲,因为某些服务可能仍处于完成启动的过程中。 + +内核启动使用了 @KERNEL_USEC@ 毫秒。 + +初始内存盘启动使用了 @INITRD_USEC@ 毫秒。 + +用户空间启动使用了 @USERSPACE_USEC@ 毫秒。 + +-- 6bbd95ee977941e497c48be27c254128 +Subject: 系统已进入 @SLEEP@ 睡眠状态 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-deve + +系统现已进入 @SLEEP@ 睡眠状态。 + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: 系统已离开 @SLEEP@ 睡眠状态 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系统现已离开 @SLEEP@ 睡眠状态。 + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: 系统关机已开始 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系统关机操作已初始化。 +关机已开始,所有系统服务均已结束,所有文件系统已卸载。 + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: @UNIT@ 单元已开始启动 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 单元已开始启动。 + +-- 39f53479d3a045ac8e11786248231fbf +Subject: @UNIT@ 单元已结束启动 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 单元已结束启动。 + +启动结果为“@RESULT@”。 + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: @UNIT@ 单元已开始停止操作 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 单元已开始停止操作。 + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: @UNIT@ 单元已结束停止操作 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 单元已结束停止操作。 + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: @UNIT@ 单元已失败 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 单元已失败。 + +结果为“@RESULT@”。 + +-- d34d037fff1847e6ae669a370e694725 +Subject: @UNIT@ 单元已开始重新载入其配置 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 单元已开始重新载入其配置。 + +-- 7b05ebc668384222baa8881179cfda54 +Subject: @UNIT@ 单元已结束配置重载入 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 单元已结束配置重载入操作。 + +结果为“@RESULT@”。 + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: 进程 @EXECUTABLE@ 无法执行 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +进程 @EXECUTABLE@ 无法被执行并已失败。 + +该进程返回的错误代码为 @ERRNO@。 + +-- 0027229ca0644181a76c4e92458afa2e +Subject: 一个或更多消息无法被转发至 syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +有一条或更多的消息无法被转发至与 journald 同时运行的 syslog 服务。 +这通常意味着 syslog 实现无法跟上队列中消息进入的速度。 + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: 挂载点不为空 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +目录 @WHERE@ 被指定为挂载点(即 /etc/fstab 文件的第二栏,或 systemd 单元 +文件的 Where= 字段),且该目录非空。 +这并不会影响挂载行为,但该目录中先前已存在的文件将无法被访问。 +如需查看这些文件,请手动将其下的文件系统挂载到另一个位置。 + +-- 24d8d4452573402496068381a6312df2 +Subject: 一个虚拟机或容器已启动 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +虚拟机 @NAME@,以及其首进程 PID @LEADER@,已被启动并可被使用。 + +-- 58432bd3bace477cb514b56381b8a758 +Subject: 一个虚拟机或容器已被终止 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +虚拟机 @NAME@,以及其首进程 PID @LEADER@,已被关闭并停止。 diff --git a/src/grp-journal/catalog/systemd.zh_TW.catalog b/src/grp-journal/catalog/systemd.zh_TW.catalog new file mode 100644 index 0000000000..aa5004db08 --- /dev/null +++ b/src/grp-journal/catalog/systemd.zh_TW.catalog @@ -0,0 +1,263 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2015 Jeff Huang +# +# 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 . + +# Message catalog for systemd's own messages +# Traditional Chinese translation + +# Catalog 的格式記錄於 +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# For an explanation why we do all this, see https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: 日誌已開始 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系統日誌行程已啟動,已開啟日誌 +檔案供寫入並準備好對行程的要求做出回應。 + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: 日誌已停止 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系統日誌行程已關閉,且關閉所有目前 +活躍的日誌檔案。 + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: 從服務而來的訊息已被抑制 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +有一個服務在一個時間週期內記錄了太多訊息。 +從該服務而來的訊息已被丟棄。 + +注意,只有有問題的服務之訊息被丟棄, +其他服務的訊息則不受影響。 + +可以在 /etc/systemd/journald.conf 中設定 +RateLimitIntervalSec= 以及 RateLimitBurst= +來控制當訊息要開始被丟棄時的限制。參見 journald.conf(5) 以獲得更多資訊。 + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: 日誌訊息已遺失 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +因日誌系統對核心訊息的處理不夠快速, +部份訊息已遺失。 + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: 行程 @COREDUMP_PID@ (@COREDUMP_COMM@) 核心傾印 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +行程 @COREDUMP_PID@ (@COREDUMP_COMM@) 當掉並核心傾印。 + +這通常代表了在當掉的程式中的一個程式錯誤 +並需要回報錯誤給其開發者。 + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: 新的工作階段 @SESSION_ID@ 已為使用者 @USER_ID@ 建立 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +一個新的工作階段,ID @SESSION_ID@ 已為使用者 @USER_ID@ 建立。 + +這個工作階段的領導行程為 @LEADER@。 + +-- 3354939424b4456d9802ca8333ed424a +Subject: 工作階段 @SESSION_ID@ 已結束 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +一個工作階段,ID @SESSION_ID@ 已結束。 + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: 新的座位 @SEAT_ID@ 可用 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +一個新的座位 @SEAT_ID@ 已被設定且現在可用。 + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: 座位 @SEAT_ID@ 已被移除 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +座位 @SEAT_ID@ 已被移除且不再可用。 + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: 時間變更 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系統時間已變更為1970年1月1日後 @REALTIME@ 微秒。 + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: 時區變更為 @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系統時區已變更為 @TIMEZONE@。 + +-- b07a249cd024414a82dd00cd181378ff +Subject: 系統啟動已完成 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +所有開機所必要的系統服務都已成功啟動。 +注意這並不代表這臺機器有空閒的時間 +可以服務,可能仍忙於完成啟動。 + +核心啟動需要 @KERNEL_USEC@ 微秒。 + +初始 RAM 磁碟啟動需要 @INITRD_USEC@ 微秒。 + +使用者空間啟動需要 @USERSPACE_USEC@ 微秒。 + +-- 6bbd95ee977941e497c48be27c254128 +Subject: 系統進入 @SLEEP@ 睡眠狀態 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系統現在已進入 @SLEEP@ 睡眠狀態。 + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: 系統離開 @SLEEP@ 睡眠狀態 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系統現在已離開 @SLEEP@ 睡眠狀態。 + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: 系統關機開始 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Systemd 關閉已經開始。關閉已開始且所有系統服務 +都已結束,所有的檔案系統也都已被卸載。 + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: 單位 @UNIT@ 已開始啟動 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +單位 @UNIT@ 已開始啟動。 + +-- 39f53479d3a045ac8e11786248231fbf +Subject: 單位 @UNIT@ 啟動已結束 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +單位 @UNIT@ 啟動已結束。 + +啟動結果為 @RESULT@。 + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: 單位 @UNIT@ 已開始關閉 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +單位 @UNIT@ 已開始關閉。 + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: 單位 @UNIT@ 已關閉結束 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +單位 @UNIT@ 已關閉結束。 + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: 單位 @UNIT@ 已失敗 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +單位 @UNIT@ 已失敗。 + +結果為 @RESULT@。 + +-- d34d037fff1847e6ae669a370e694725 +Subject: 單位 @UNIT@ 已開始重新載入其設定 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +單位 @UNIT@ 已開始重新載入其設定 + +-- 7b05ebc668384222baa8881179cfda54 +Subject: 單位 @UNIT@ 已結束重新載入其設定 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +單位 @UNIT@ 已結束重新載入其設定 + +結果為 @RESULT@。 + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: 行程 @EXECUTABLE@ 無法執行 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +行程 @EXECUTABLE@ 無法執行且失敗。 + +由該行程所回傳的錯誤碼為 @ERRNO@。 + +-- 0027229ca0644181a76c4e92458afa2e +Subject: 一個或更多訊息無法被轉發到 syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +一個或更多訊息無法被轉發到 syslog 服務 +以及並行執行的 journald。這通常代表著 +syslog 實作並無未跟上佇列中訊息 +的速度。 + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: 掛載點不為空 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +目錄 @WHERE@ 被指定為掛載點(在 /etc/fstab 中的 +第二欄或是在 systemd 單位檔案中的 Where= 欄位)且其不為空。 +這並不會干擾掛載,但在此目錄中已存在的檔案 +會變成無法存取的狀態。要檢視這些 over-mounted 的檔案, +請手動掛載下面的檔案系統到次要 +位置。 + +-- 24d8d4452573402496068381a6312df2 +Subject: 虛擬機器或容器已啟動 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +虛擬機器 @NAME@ 包含它的領導 PID @LEADER@ 現在 +已經開始並已經可以使用。 + +-- 58432bd3bace477cb514b56381b8a758 +Subject: 虛擬機器或容器已結束 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +虛擬機器 @NAME@ 包含它的領導 PID @LEADER@ 已經 +關閉。 diff --git a/src/grp-journal/journalctl/Makefile b/src/grp-journal/journalctl/Makefile new file mode 100644 index 0000000000..c3cdb6b27a --- /dev/null +++ b/src/grp-journal/journalctl/Makefile @@ -0,0 +1,49 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +# using _CFLAGS = in the conditional below would suppress AM_CFLAGS +journalctl_CFLAGS = \ + $(AM_CFLAGS) + +journalctl_SOURCES = \ + src/journal/journalctl.c + +journalctl_LDADD = \ + libshared.la \ + libudev-core.la + +ifneq ($(HAVE_QRENCODE),) +journalctl_SOURCES += \ + src/journal/journal-qrcode.c \ + src/journal/journal-qrcode.h + +journalctl_CFLAGS += \ + $(QRENCODE_CFLAGS) + +journalctl_LDADD += \ + $(QRENCODE_LIBS) +endif # HAVE_QRENCODE + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-journal/journalctl/journalctl.c b/src/grp-journal/journalctl/journalctl.c new file mode 100644 index 0000000000..3602bd0556 --- /dev/null +++ b/src/grp-journal/journalctl/journalctl.c @@ -0,0 +1,2600 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "acl-util.h" +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "catalog.h" +#include "chattr-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "fsprg.h" +#include "glob-util.h" +#include "hostname-util.h" +#include "io-util.h" +#include "journal-def.h" +#include "journal-internal.h" +#include "journal-qrcode.h" +#include "journal-vacuum.h" +#include "journal-verify.h" +#include "locale-util.h" +#include "log.h" +#include "logs-show.h" +#include "mkdir.h" +#include "pager.h" +#include "parse-util.h" +#include "path-util.h" +#include "rlimit-util.h" +#include "set.h" +#include "sigbus.h" +#include "strv.h" +#include "syslog-util.h" +#include "terminal-util.h" +#include "udev.h" +#include "udev-util.h" +#include "unit-name.h" +#include "user-util.h" + +#define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE) + +enum { + /* Special values for arg_lines */ + ARG_LINES_DEFAULT = -2, + ARG_LINES_ALL = -1, +}; + +static OutputMode arg_output = OUTPUT_SHORT; +static bool arg_utc = false; +static bool arg_pager_end = false; +static bool arg_follow = false; +static bool arg_full = true; +static bool arg_all = false; +static bool arg_no_pager = false; +static int arg_lines = ARG_LINES_DEFAULT; +static bool arg_no_tail = false; +static bool arg_quiet = false; +static bool arg_merge = false; +static bool arg_boot = false; +static sd_id128_t arg_boot_id = {}; +static int arg_boot_offset = 0; +static bool arg_dmesg = false; +static bool arg_no_hostname = false; +static const char *arg_cursor = NULL; +static const char *arg_after_cursor = NULL; +static bool arg_show_cursor = false; +static const char *arg_directory = NULL; +static char **arg_file = NULL; +static bool arg_file_stdin = false; +static int arg_priorities = 0xFF; +static const char *arg_verify_key = NULL; +#ifdef HAVE_GCRYPT +static usec_t arg_interval = DEFAULT_FSS_INTERVAL_USEC; +static bool arg_force = false; +#endif +static usec_t arg_since, arg_until; +static bool arg_since_set = false, arg_until_set = false; +static char **arg_syslog_identifier = NULL; +static char **arg_system_units = NULL; +static char **arg_user_units = NULL; +static const char *arg_field = NULL; +static bool arg_catalog = false; +static bool arg_reverse = false; +static int arg_journal_type = 0; +static char *arg_root = NULL; +static const char *arg_machine = NULL; +static uint64_t arg_vacuum_size = 0; +static uint64_t arg_vacuum_n_files = 0; +static usec_t arg_vacuum_time = 0; + +static enum { + ACTION_SHOW, + ACTION_NEW_ID128, + ACTION_PRINT_HEADER, + ACTION_SETUP_KEYS, + ACTION_VERIFY, + ACTION_DISK_USAGE, + ACTION_LIST_CATALOG, + ACTION_DUMP_CATALOG, + ACTION_UPDATE_CATALOG, + ACTION_LIST_BOOTS, + ACTION_FLUSH, + ACTION_SYNC, + ACTION_ROTATE, + ACTION_VACUUM, + ACTION_LIST_FIELDS, + ACTION_LIST_FIELD_NAMES, +} arg_action = ACTION_SHOW; + +typedef struct BootId { + sd_id128_t id; + uint64_t first; + uint64_t last; + LIST_FIELDS(struct BootId, boot_list); +} BootId; + +static int add_matches_for_device(sd_journal *j, const char *devpath) { + int r; + _cleanup_udev_unref_ struct udev *udev = NULL; + _cleanup_udev_device_unref_ struct udev_device *device = NULL; + struct udev_device *d = NULL; + struct stat st; + + assert(j); + assert(devpath); + + if (!path_startswith(devpath, "/dev/")) { + log_error("Devpath does not start with /dev/"); + return -EINVAL; + } + + udev = udev_new(); + if (!udev) + return log_oom(); + + r = stat(devpath, &st); + if (r < 0) + log_error_errno(errno, "Couldn't stat file: %m"); + + d = device = udev_device_new_from_devnum(udev, S_ISBLK(st.st_mode) ? 'b' : 'c', st.st_rdev); + if (!device) + return log_error_errno(errno, "Failed to get udev device from devnum %u:%u: %m", major(st.st_rdev), minor(st.st_rdev)); + + while (d) { + _cleanup_free_ char *match = NULL; + const char *subsys, *sysname, *devnode; + + subsys = udev_device_get_subsystem(d); + if (!subsys) { + d = udev_device_get_parent(d); + continue; + } + + sysname = udev_device_get_sysname(d); + if (!sysname) { + d = udev_device_get_parent(d); + continue; + } + + match = strjoin("_KERNEL_DEVICE=+", subsys, ":", sysname, NULL); + if (!match) + return log_oom(); + + r = sd_journal_add_match(j, match, 0); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + + devnode = udev_device_get_devnode(d); + if (devnode) { + _cleanup_free_ char *match1 = NULL; + + r = stat(devnode, &st); + if (r < 0) + return log_error_errno(r, "Failed to stat() device node \"%s\": %m", devnode); + + r = asprintf(&match1, "_KERNEL_DEVICE=%c%u:%u", S_ISBLK(st.st_mode) ? 'b' : 'c', major(st.st_rdev), minor(st.st_rdev)); + if (r < 0) + return log_oom(); + + r = sd_journal_add_match(j, match1, 0); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + } + + d = udev_device_get_parent(d); + } + + r = add_match_this_boot(j, arg_machine); + if (r < 0) + return log_error_errno(r, "Failed to add match for the current boot: %m"); + + return 0; +} + +static char *format_timestamp_maybe_utc(char *buf, size_t l, usec_t t) { + + if (arg_utc) + return format_timestamp_utc(buf, l, t); + + return format_timestamp(buf, l, t); +} + +static int parse_boot_descriptor(const char *x, sd_id128_t *boot_id, int *offset) { + sd_id128_t id = SD_ID128_NULL; + int off = 0, r; + + if (strlen(x) >= 32) { + char *t; + + t = strndupa(x, 32); + r = sd_id128_from_string(t, &id); + if (r >= 0) + x += 32; + + if (*x != '-' && *x != '+' && *x != 0) + return -EINVAL; + + if (*x != 0) { + r = safe_atoi(x, &off); + if (r < 0) + return r; + } + } else { + r = safe_atoi(x, &off); + if (r < 0) + return r; + } + + if (boot_id) + *boot_id = id; + + if (offset) + *offset = off; + + return 0; +} + +static void help(void) { + + pager_open(arg_no_pager, arg_pager_end); + + printf("%s [OPTIONS...] [MATCHES...]\n\n" + "Query the journal.\n\n" + "Options:\n" + " --system Show the system journal\n" + " --user Show the user journal for the current user\n" + " -M --machine=CONTAINER Operate on local container\n" + " -S --since=DATE Show entries not older than the specified date\n" + " -U --until=DATE Show entries not newer than the specified date\n" + " -c --cursor=CURSOR Show entries starting at the specified cursor\n" + " --after-cursor=CURSOR Show entries after the specified cursor\n" + " --show-cursor Print the cursor after all the entries\n" + " -b --boot[=ID] Show current boot or the specified boot\n" + " --list-boots Show terse information about recorded boots\n" + " -k --dmesg Show kernel message log from the current boot\n" + " -u --unit=UNIT Show logs from the specified unit\n" + " --user-unit=UNIT Show logs from the specified user unit\n" + " -t --identifier=STRING Show entries with the specified syslog identifier\n" + " -p --priority=RANGE Show entries with the specified priority\n" + " -e --pager-end Immediately jump to the end in the pager\n" + " -f --follow Follow the journal\n" + " -n --lines[=INTEGER] Number of journal entries to show\n" + " --no-tail Show all lines, even in follow mode\n" + " -r --reverse Show the newest entries first\n" + " -o --output=STRING Change journal output mode (short, short-iso,\n" + " short-precise, short-monotonic, verbose,\n" + " export, json, json-pretty, json-sse, cat)\n" + " --utc Express time in Coordinated Universal Time (UTC)\n" + " -x --catalog Add message explanations where available\n" + " --no-full Ellipsize fields\n" + " -a --all Show all fields, including long and unprintable\n" + " -q --quiet Do not show info messages and privilege warning\n" + " --no-pager Do not pipe output into a pager\n" + " --no-hostname Suppress output of hostname field\n" + " -m --merge Show entries from all available journals\n" + " -D --directory=PATH Show journal files from directory\n" + " --file=PATH Show journal file\n" + " --root=ROOT Operate on catalog files below a root directory\n" +#ifdef HAVE_GCRYPT + " --interval=TIME Time interval for changing the FSS sealing key\n" + " --verify-key=KEY Specify FSS verification key\n" + " --force Override of the FSS key pair with --setup-keys\n" +#endif + "\nCommands:\n" + " -h --help Show this help text\n" + " --version Show package version\n" + " -N --fields List all field names currently used\n" + " -F --field=FIELD List all values that a specified field takes\n" + " --disk-usage Show total disk usage of all journal files\n" + " --vacuum-size=BYTES Reduce disk usage below specified size\n" + " --vacuum-files=INT Leave only the specified number of journal files\n" + " --vacuum-time=TIME Remove journal files older than specified time\n" + " --verify Verify journal file consistency\n" + " --sync Synchronize unwritten journal messages to disk\n" + " --flush Flush all journal data from /run into /var\n" + " --rotate Request immediate rotation of the journal files\n" + " --header Show journal header information\n" + " --list-catalog Show all message IDs in the catalog\n" + " --dump-catalog Show entries in the message catalog\n" + " --update-catalog Update the message catalog database\n" + " --new-id128 Generate a new 128-bit ID\n" +#ifdef HAVE_GCRYPT + " --setup-keys Generate a new FSS key pair\n" +#endif + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_NO_FULL, + ARG_NO_TAIL, + ARG_NEW_ID128, + ARG_LIST_BOOTS, + ARG_USER, + ARG_SYSTEM, + ARG_ROOT, + ARG_HEADER, + ARG_SETUP_KEYS, + ARG_FILE, + ARG_INTERVAL, + ARG_VERIFY, + ARG_VERIFY_KEY, + ARG_DISK_USAGE, + ARG_AFTER_CURSOR, + ARG_SHOW_CURSOR, + ARG_USER_UNIT, + ARG_LIST_CATALOG, + ARG_DUMP_CATALOG, + ARG_UPDATE_CATALOG, + ARG_FORCE, + ARG_UTC, + ARG_SYNC, + ARG_FLUSH, + ARG_ROTATE, + ARG_VACUUM_SIZE, + ARG_VACUUM_FILES, + ARG_VACUUM_TIME, + ARG_NO_HOSTNAME, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version" , no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "pager-end", no_argument, NULL, 'e' }, + { "follow", no_argument, NULL, 'f' }, + { "force", no_argument, NULL, ARG_FORCE }, + { "output", required_argument, NULL, 'o' }, + { "all", no_argument, NULL, 'a' }, + { "full", no_argument, NULL, 'l' }, + { "no-full", no_argument, NULL, ARG_NO_FULL }, + { "lines", optional_argument, NULL, 'n' }, + { "no-tail", no_argument, NULL, ARG_NO_TAIL }, + { "new-id128", no_argument, NULL, ARG_NEW_ID128 }, + { "quiet", no_argument, NULL, 'q' }, + { "merge", no_argument, NULL, 'm' }, + { "boot", optional_argument, NULL, 'b' }, + { "list-boots", no_argument, NULL, ARG_LIST_BOOTS }, + { "this-boot", optional_argument, NULL, 'b' }, /* deprecated */ + { "dmesg", no_argument, NULL, 'k' }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "user", no_argument, NULL, ARG_USER }, + { "directory", required_argument, NULL, 'D' }, + { "file", required_argument, NULL, ARG_FILE }, + { "root", required_argument, NULL, ARG_ROOT }, + { "header", no_argument, NULL, ARG_HEADER }, + { "identifier", required_argument, NULL, 't' }, + { "priority", required_argument, NULL, 'p' }, + { "setup-keys", no_argument, NULL, ARG_SETUP_KEYS }, + { "interval", required_argument, NULL, ARG_INTERVAL }, + { "verify", no_argument, NULL, ARG_VERIFY }, + { "verify-key", required_argument, NULL, ARG_VERIFY_KEY }, + { "disk-usage", no_argument, NULL, ARG_DISK_USAGE }, + { "cursor", required_argument, NULL, 'c' }, + { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR }, + { "show-cursor", no_argument, NULL, ARG_SHOW_CURSOR }, + { "since", required_argument, NULL, 'S' }, + { "until", required_argument, NULL, 'U' }, + { "unit", required_argument, NULL, 'u' }, + { "user-unit", required_argument, NULL, ARG_USER_UNIT }, + { "field", required_argument, NULL, 'F' }, + { "fields", no_argument, NULL, 'N' }, + { "catalog", no_argument, NULL, 'x' }, + { "list-catalog", no_argument, NULL, ARG_LIST_CATALOG }, + { "dump-catalog", no_argument, NULL, ARG_DUMP_CATALOG }, + { "update-catalog", no_argument, NULL, ARG_UPDATE_CATALOG }, + { "reverse", no_argument, NULL, 'r' }, + { "machine", required_argument, NULL, 'M' }, + { "utc", no_argument, NULL, ARG_UTC }, + { "flush", no_argument, NULL, ARG_FLUSH }, + { "sync", no_argument, NULL, ARG_SYNC }, + { "rotate", no_argument, NULL, ARG_ROTATE }, + { "vacuum-size", required_argument, NULL, ARG_VACUUM_SIZE }, + { "vacuum-files", required_argument, NULL, ARG_VACUUM_FILES }, + { "vacuum-time", required_argument, NULL, ARG_VACUUM_TIME }, + { "no-hostname", no_argument, NULL, ARG_NO_HOSTNAME }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:c:S:U:t:u:NF:xrM:", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case 'e': + arg_pager_end = true; + + if (arg_lines == ARG_LINES_DEFAULT) + arg_lines = 1000; + + break; + + case 'f': + arg_follow = true; + break; + + case 'o': + arg_output = output_mode_from_string(optarg); + if (arg_output < 0) { + log_error("Unknown output format '%s'.", optarg); + return -EINVAL; + } + + if (arg_output == OUTPUT_EXPORT || + arg_output == OUTPUT_JSON || + arg_output == OUTPUT_JSON_PRETTY || + arg_output == OUTPUT_JSON_SSE || + arg_output == OUTPUT_CAT) + arg_quiet = true; + + break; + + case 'l': + arg_full = true; + break; + + case ARG_NO_FULL: + arg_full = false; + break; + + case 'a': + arg_all = true; + break; + + case 'n': + if (optarg) { + if (streq(optarg, "all")) + arg_lines = ARG_LINES_ALL; + else { + r = safe_atoi(optarg, &arg_lines); + if (r < 0 || arg_lines < 0) { + log_error("Failed to parse lines '%s'", optarg); + return -EINVAL; + } + } + } else { + arg_lines = 10; + + /* Hmm, no argument? Maybe the next + * word on the command line is + * supposed to be the argument? Let's + * see if there is one, and is + * parsable. */ + if (optind < argc) { + int n; + if (streq(argv[optind], "all")) { + arg_lines = ARG_LINES_ALL; + optind++; + } else if (safe_atoi(argv[optind], &n) >= 0 && n >= 0) { + arg_lines = n; + optind++; + } + } + } + + break; + + case ARG_NO_TAIL: + arg_no_tail = true; + break; + + case ARG_NEW_ID128: + arg_action = ACTION_NEW_ID128; + break; + + case 'q': + arg_quiet = true; + break; + + case 'm': + arg_merge = true; + break; + + case 'b': + arg_boot = true; + + if (optarg) { + r = parse_boot_descriptor(optarg, &arg_boot_id, &arg_boot_offset); + if (r < 0) { + log_error("Failed to parse boot descriptor '%s'", optarg); + return -EINVAL; + } + } else { + + /* Hmm, no argument? Maybe the next + * word on the command line is + * supposed to be the argument? Let's + * see if there is one and is parsable + * as a boot descriptor... */ + + if (optind < argc && + parse_boot_descriptor(argv[optind], &arg_boot_id, &arg_boot_offset) >= 0) + optind++; + } + + break; + + case ARG_LIST_BOOTS: + arg_action = ACTION_LIST_BOOTS; + break; + + case 'k': + arg_boot = arg_dmesg = true; + break; + + case ARG_SYSTEM: + arg_journal_type |= SD_JOURNAL_SYSTEM; + break; + + case ARG_USER: + arg_journal_type |= SD_JOURNAL_CURRENT_USER; + break; + + case 'M': + arg_machine = optarg; + break; + + case 'D': + arg_directory = optarg; + break; + + case ARG_FILE: + if (streq(optarg, "-")) + /* An undocumented feature: we can read journal files from STDIN. We don't document + * this though, since after all we only support this for mmap-able, seekable files, and + * not for example pipes which are probably the primary usecase for reading things from + * STDIN. To avoid confusion we hence don't document this feature. */ + arg_file_stdin = true; + else { + r = glob_extend(&arg_file, optarg); + if (r < 0) + return log_error_errno(r, "Failed to add paths: %m"); + } + break; + + case ARG_ROOT: + r = parse_path_argument_and_warn(optarg, true, &arg_root); + if (r < 0) + return r; + break; + + case 'c': + arg_cursor = optarg; + break; + + case ARG_AFTER_CURSOR: + arg_after_cursor = optarg; + break; + + case ARG_SHOW_CURSOR: + arg_show_cursor = true; + break; + + case ARG_HEADER: + arg_action = ACTION_PRINT_HEADER; + break; + + case ARG_VERIFY: + arg_action = ACTION_VERIFY; + break; + + case ARG_DISK_USAGE: + arg_action = ACTION_DISK_USAGE; + break; + + case ARG_VACUUM_SIZE: + r = parse_size(optarg, 1024, &arg_vacuum_size); + if (r < 0) { + log_error("Failed to parse vacuum size: %s", optarg); + return r; + } + + arg_action = ACTION_VACUUM; + break; + + case ARG_VACUUM_FILES: + r = safe_atou64(optarg, &arg_vacuum_n_files); + if (r < 0) { + log_error("Failed to parse vacuum files: %s", optarg); + return r; + } + + arg_action = ACTION_VACUUM; + break; + + case ARG_VACUUM_TIME: + r = parse_sec(optarg, &arg_vacuum_time); + if (r < 0) { + log_error("Failed to parse vacuum time: %s", optarg); + return r; + } + + arg_action = ACTION_VACUUM; + break; + +#ifdef HAVE_GCRYPT + case ARG_FORCE: + arg_force = true; + break; + + case ARG_SETUP_KEYS: + arg_action = ACTION_SETUP_KEYS; + break; + + + case ARG_VERIFY_KEY: + arg_action = ACTION_VERIFY; + arg_verify_key = optarg; + arg_merge = false; + break; + + case ARG_INTERVAL: + r = parse_sec(optarg, &arg_interval); + if (r < 0 || arg_interval <= 0) { + log_error("Failed to parse sealing key change interval: %s", optarg); + return -EINVAL; + } + break; +#else + case ARG_SETUP_KEYS: + case ARG_VERIFY_KEY: + case ARG_INTERVAL: + case ARG_FORCE: + log_error("Forward-secure sealing not available."); + return -EOPNOTSUPP; +#endif + + case 'p': { + const char *dots; + + dots = strstr(optarg, ".."); + if (dots) { + char *a; + int from, to, i; + + /* a range */ + a = strndup(optarg, dots - optarg); + if (!a) + return log_oom(); + + from = log_level_from_string(a); + to = log_level_from_string(dots + 2); + free(a); + + if (from < 0 || to < 0) { + log_error("Failed to parse log level range %s", optarg); + return -EINVAL; + } + + arg_priorities = 0; + + if (from < to) { + for (i = from; i <= to; i++) + arg_priorities |= 1 << i; + } else { + for (i = to; i <= from; i++) + arg_priorities |= 1 << i; + } + + } else { + int p, i; + + p = log_level_from_string(optarg); + if (p < 0) { + log_error("Unknown log level %s", optarg); + return -EINVAL; + } + + arg_priorities = 0; + + for (i = 0; i <= p; i++) + arg_priorities |= 1 << i; + } + + break; + } + + case 'S': + r = parse_timestamp(optarg, &arg_since); + if (r < 0) { + log_error("Failed to parse timestamp: %s", optarg); + return -EINVAL; + } + arg_since_set = true; + break; + + case 'U': + r = parse_timestamp(optarg, &arg_until); + if (r < 0) { + log_error("Failed to parse timestamp: %s", optarg); + return -EINVAL; + } + arg_until_set = true; + break; + + case 't': + r = strv_extend(&arg_syslog_identifier, optarg); + if (r < 0) + return log_oom(); + break; + + case 'u': + r = strv_extend(&arg_system_units, optarg); + if (r < 0) + return log_oom(); + break; + + case ARG_USER_UNIT: + r = strv_extend(&arg_user_units, optarg); + if (r < 0) + return log_oom(); + break; + + case 'F': + arg_action = ACTION_LIST_FIELDS; + arg_field = optarg; + break; + + case 'N': + arg_action = ACTION_LIST_FIELD_NAMES; + break; + + case ARG_NO_HOSTNAME: + arg_no_hostname = true; + break; + + case 'x': + arg_catalog = true; + break; + + case ARG_LIST_CATALOG: + arg_action = ACTION_LIST_CATALOG; + break; + + case ARG_DUMP_CATALOG: + arg_action = ACTION_DUMP_CATALOG; + break; + + case ARG_UPDATE_CATALOG: + arg_action = ACTION_UPDATE_CATALOG; + break; + + case 'r': + arg_reverse = true; + break; + + case ARG_UTC: + arg_utc = true; + break; + + case ARG_FLUSH: + arg_action = ACTION_FLUSH; + break; + + case ARG_ROTATE: + arg_action = ACTION_ROTATE; + break; + + case ARG_SYNC: + arg_action = ACTION_SYNC; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (arg_follow && !arg_no_tail && !arg_since && arg_lines == ARG_LINES_DEFAULT) + arg_lines = 10; + + if (!!arg_directory + !!arg_file + !!arg_machine > 1) { + log_error("Please specify either -D/--directory= or --file= or -M/--machine=, not more than one."); + return -EINVAL; + } + + if (arg_since_set && arg_until_set && arg_since > arg_until) { + log_error("--since= must be before --until=."); + return -EINVAL; + } + + if (!!arg_cursor + !!arg_after_cursor + !!arg_since_set > 1) { + log_error("Please specify only one of --since=, --cursor=, and --after-cursor."); + return -EINVAL; + } + + if (arg_follow && arg_reverse) { + log_error("Please specify either --reverse= or --follow=, not both."); + return -EINVAL; + } + + if (!IN_SET(arg_action, ACTION_SHOW, ACTION_DUMP_CATALOG, ACTION_LIST_CATALOG) && optind < argc) { + log_error("Extraneous arguments starting with '%s'", argv[optind]); + return -EINVAL; + } + + if ((arg_boot || arg_action == ACTION_LIST_BOOTS) && (arg_file || arg_directory || arg_merge)) { + log_error("Using --boot or --list-boots with --file, --directory or --merge is not supported."); + return -EINVAL; + } + + if (!strv_isempty(arg_system_units) && (arg_journal_type == SD_JOURNAL_CURRENT_USER)) { + + /* Specifying --user and --unit= at the same time makes no sense (as the former excludes the user + * journal, but the latter excludes the system journal, thus resulting in empty output). Let's be nice + * to users, and automatically turn --unit= into --user-unit= if combined with --user. */ + r = strv_extend_strv(&arg_user_units, arg_system_units, true); + if (r < 0) + return -ENOMEM; + + arg_system_units = strv_free(arg_system_units); + } + + return 1; +} + +static int generate_new_id128(void) { + sd_id128_t id; + int r; + unsigned i; + + r = sd_id128_randomize(&id); + if (r < 0) + return log_error_errno(r, "Failed to generate ID: %m"); + + printf("As string:\n" + SD_ID128_FORMAT_STR "\n\n" + "As UUID:\n" + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n\n" + "As macro:\n" + "#define MESSAGE_XYZ SD_ID128_MAKE(", + SD_ID128_FORMAT_VAL(id), + SD_ID128_FORMAT_VAL(id)); + for (i = 0; i < 16; i++) + printf("%02x%s", id.bytes[i], i != 15 ? "," : ""); + fputs(")\n\n", stdout); + + printf("As Python constant:\n" + ">>> import uuid\n" + ">>> MESSAGE_XYZ = uuid.UUID('" SD_ID128_FORMAT_STR "')\n", + SD_ID128_FORMAT_VAL(id)); + + return 0; +} + +static int add_matches(sd_journal *j, char **args) { + char **i; + bool have_term = false; + + assert(j); + + STRV_FOREACH(i, args) { + int r; + + if (streq(*i, "+")) { + if (!have_term) + break; + r = sd_journal_add_disjunction(j); + have_term = false; + + } else if (path_is_absolute(*i)) { + _cleanup_free_ char *p, *t = NULL, *t2 = NULL, *interpreter = NULL; + const char *path; + struct stat st; + + p = canonicalize_file_name(*i); + path = p ?: *i; + + if (lstat(path, &st) < 0) + return log_error_errno(errno, "Couldn't stat file: %m"); + + if (S_ISREG(st.st_mode) && (0111 & st.st_mode)) { + if (executable_is_script(path, &interpreter) > 0) { + _cleanup_free_ char *comm; + + comm = strndup(basename(path), 15); + if (!comm) + return log_oom(); + + t = strappend("_COMM=", comm); + if (!t) + return log_oom(); + + /* Append _EXE only if the interpreter is not a link. + Otherwise, it might be outdated often. */ + if (lstat(interpreter, &st) == 0 && !S_ISLNK(st.st_mode)) { + t2 = strappend("_EXE=", interpreter); + if (!t2) + return log_oom(); + } + } else { + t = strappend("_EXE=", path); + if (!t) + return log_oom(); + } + + r = sd_journal_add_match(j, t, 0); + + if (r >=0 && t2) + r = sd_journal_add_match(j, t2, 0); + + } else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) { + r = add_matches_for_device(j, path); + if (r < 0) + return r; + } else { + log_error("File is neither a device node, nor regular file, nor executable: %s", *i); + return -EINVAL; + } + + have_term = true; + } else { + r = sd_journal_add_match(j, *i, 0); + have_term = true; + } + + if (r < 0) + return log_error_errno(r, "Failed to add match '%s': %m", *i); + } + + if (!strv_isempty(args) && !have_term) { + log_error("\"+\" can only be used between terms"); + return -EINVAL; + } + + return 0; +} + +static void boot_id_free_all(BootId *l) { + + while (l) { + BootId *i = l; + LIST_REMOVE(boot_list, l, i); + free(i); + } +} + +static int discover_next_boot(sd_journal *j, + sd_id128_t previous_boot_id, + bool advance_older, + BootId **ret) { + + _cleanup_free_ BootId *next_boot = NULL; + char match[9+32+1] = "_BOOT_ID="; + sd_id128_t boot_id; + int r; + + assert(j); + assert(ret); + + /* We expect the journal to be on the last position of a boot + * (in relation to the direction we are going), so that the next + * invocation of sd_journal_next/previous will be from a different + * boot. We then collect any information we desire and then jump + * to the last location of the new boot by using a _BOOT_ID match + * coming from the other journal direction. */ + + /* Make sure we aren't restricted by any _BOOT_ID matches, so that + * we can actually advance to a *different* boot. */ + sd_journal_flush_matches(j); + + do { + if (advance_older) + r = sd_journal_previous(j); + else + r = sd_journal_next(j); + if (r < 0) + return r; + else if (r == 0) + return 0; /* End of journal, yay. */ + + r = sd_journal_get_monotonic_usec(j, NULL, &boot_id); + if (r < 0) + return r; + + /* We iterate through this in a loop, until the boot ID differs from the previous one. Note that + * normally, this will only require a single iteration, as we seeked to the last entry of the previous + * boot entry already. However, it might happen that the per-journal-field entry arrays are less + * complete than the main entry array, and hence might reference an entry that's not actually the last + * one of the boot ID as last one. Let's hence use the per-field array is initial seek position to + * speed things up, but let's not trust that it is complete, and hence, manually advance as + * necessary. */ + + } while (sd_id128_equal(boot_id, previous_boot_id)); + + next_boot = new0(BootId, 1); + if (!next_boot) + return -ENOMEM; + + next_boot->id = boot_id; + + r = sd_journal_get_realtime_usec(j, &next_boot->first); + if (r < 0) + return r; + + /* Now seek to the last occurrence of this boot ID. */ + sd_id128_to_string(next_boot->id, match + 9); + r = sd_journal_add_match(j, match, sizeof(match) - 1); + if (r < 0) + return r; + + if (advance_older) + r = sd_journal_seek_head(j); + else + r = sd_journal_seek_tail(j); + if (r < 0) + return r; + + if (advance_older) + r = sd_journal_next(j); + else + r = sd_journal_previous(j); + if (r < 0) + return r; + else if (r == 0) + return -ENODATA; /* This shouldn't happen. We just came from this very boot ID. */ + + r = sd_journal_get_realtime_usec(j, &next_boot->last); + if (r < 0) + return r; + + *ret = next_boot; + next_boot = NULL; + + return 0; +} + +static int get_boots( + sd_journal *j, + BootId **boots, + sd_id128_t *query_ref_boot, + int ref_boot_offset) { + + bool skip_once; + int r, count = 0; + BootId *head = NULL, *tail = NULL; + const bool advance_older = query_ref_boot && ref_boot_offset <= 0; + sd_id128_t previous_boot_id; + + assert(j); + + /* Adjust for the asymmetry that offset 0 is + * the last (and current) boot, while 1 is considered the + * (chronological) first boot in the journal. */ + skip_once = query_ref_boot && sd_id128_is_null(*query_ref_boot) && ref_boot_offset < 0; + + /* Advance to the earliest/latest occurrence of our reference + * boot ID (taking our lookup direction into account), so that + * discover_next_boot() can do its job. + * If no reference is given, the journal head/tail will do, + * they're "virtual" boots after all. */ + if (query_ref_boot && !sd_id128_is_null(*query_ref_boot)) { + char match[9+32+1] = "_BOOT_ID="; + + sd_journal_flush_matches(j); + + sd_id128_to_string(*query_ref_boot, match + 9); + r = sd_journal_add_match(j, match, sizeof(match) - 1); + if (r < 0) + return r; + + if (advance_older) + r = sd_journal_seek_head(j); /* seek to oldest */ + else + r = sd_journal_seek_tail(j); /* seek to newest */ + if (r < 0) + return r; + + if (advance_older) + r = sd_journal_next(j); /* read the oldest entry */ + else + r = sd_journal_previous(j); /* read the most recently added entry */ + if (r < 0) + return r; + else if (r == 0) + goto finish; + else if (ref_boot_offset == 0) { + count = 1; + goto finish; + } + + /* At this point the read pointer is positioned at the oldest/newest occurence of the reference boot + * ID. After flushing the matches, one more invocation of _previous()/_next() will hence place us at + * the following entry, which must then have an older/newer boot ID */ + } else { + + if (advance_older) + r = sd_journal_seek_tail(j); /* seek to newest */ + else + r = sd_journal_seek_head(j); /* seek to oldest */ + if (r < 0) + return r; + + /* No sd_journal_next()/_previous() here. + * + * At this point the read pointer is positioned after the newest/before the oldest entry in the whole + * journal. The next invocation of _previous()/_next() will hence position us at the newest/oldest + * entry we have. */ + } + + previous_boot_id = SD_ID128_NULL; + for (;;) { + _cleanup_free_ BootId *current = NULL; + + r = discover_next_boot(j, previous_boot_id, advance_older, ¤t); + if (r < 0) { + boot_id_free_all(head); + return r; + } + + if (!current) + break; + + previous_boot_id = current->id; + + if (query_ref_boot) { + if (!skip_once) + ref_boot_offset += advance_older ? 1 : -1; + skip_once = false; + + if (ref_boot_offset == 0) { + count = 1; + *query_ref_boot = current->id; + break; + } + } else { + LIST_INSERT_AFTER(boot_list, head, tail, current); + tail = current; + current = NULL; + count++; + } + } + +finish: + if (boots) + *boots = head; + + sd_journal_flush_matches(j); + + return count; +} + +static int list_boots(sd_journal *j) { + int w, i, count; + BootId *id, *all_ids; + + assert(j); + + count = get_boots(j, &all_ids, NULL, 0); + if (count < 0) + return log_error_errno(count, "Failed to determine boots: %m"); + if (count == 0) + return count; + + pager_open(arg_no_pager, arg_pager_end); + + /* numbers are one less, but we need an extra char for the sign */ + w = DECIMAL_STR_WIDTH(count - 1) + 1; + + i = 0; + LIST_FOREACH(boot_list, id, all_ids) { + char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX]; + + printf("% *i " SD_ID128_FORMAT_STR " %s—%s\n", + w, i - count + 1, + SD_ID128_FORMAT_VAL(id->id), + format_timestamp_maybe_utc(a, sizeof(a), id->first), + format_timestamp_maybe_utc(b, sizeof(b), id->last)); + i++; + } + + boot_id_free_all(all_ids); + + return 0; +} + +static int add_boot(sd_journal *j) { + char match[9+32+1] = "_BOOT_ID="; + sd_id128_t ref_boot_id; + int r; + + assert(j); + + if (!arg_boot) + return 0; + + if (arg_boot_offset == 0 && sd_id128_equal(arg_boot_id, SD_ID128_NULL)) + return add_match_this_boot(j, arg_machine); + + ref_boot_id = arg_boot_id; + r = get_boots(j, NULL, &ref_boot_id, arg_boot_offset); + assert(r <= 1); + if (r <= 0) { + const char *reason = (r == 0) ? "No such boot ID in journal" : strerror(-r); + + if (sd_id128_is_null(arg_boot_id)) + log_error("Data from the specified boot (%+i) is not available: %s", + arg_boot_offset, reason); + else + log_error("Data from the specified boot ("SD_ID128_FORMAT_STR") is not available: %s", + SD_ID128_FORMAT_VAL(arg_boot_id), reason); + + return r == 0 ? -ENODATA : r; + } + + sd_id128_to_string(ref_boot_id, match + 9); + + r = sd_journal_add_match(j, match, sizeof(match) - 1); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + + r = sd_journal_add_conjunction(j); + if (r < 0) + return log_error_errno(r, "Failed to add conjunction: %m"); + + return 0; +} + +static int add_dmesg(sd_journal *j) { + int r; + assert(j); + + if (!arg_dmesg) + return 0; + + r = sd_journal_add_match(j, "_TRANSPORT=kernel", strlen("_TRANSPORT=kernel")); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + + r = sd_journal_add_conjunction(j); + if (r < 0) + return log_error_errno(r, "Failed to add conjunction: %m"); + + return 0; +} + +static int get_possible_units( + sd_journal *j, + const char *fields, + char **patterns, + Set **units) { + + _cleanup_set_free_free_ Set *found; + const char *field; + int r; + + found = set_new(&string_hash_ops); + if (!found) + return -ENOMEM; + + NULSTR_FOREACH(field, fields) { + const void *data; + size_t size; + + r = sd_journal_query_unique(j, field); + if (r < 0) + return r; + + SD_JOURNAL_FOREACH_UNIQUE(j, data, size) { + char **pattern, *eq; + size_t prefix; + _cleanup_free_ char *u = NULL; + + eq = memchr(data, '=', size); + if (eq) + prefix = eq - (char*) data + 1; + else + prefix = 0; + + u = strndup((char*) data + prefix, size - prefix); + if (!u) + return -ENOMEM; + + STRV_FOREACH(pattern, patterns) + if (fnmatch(*pattern, u, FNM_NOESCAPE) == 0) { + log_debug("Matched %s with pattern %s=%s", u, field, *pattern); + + r = set_consume(found, u); + u = NULL; + if (r < 0 && r != -EEXIST) + return r; + + break; + } + } + } + + *units = found; + found = NULL; + return 0; +} + +/* This list is supposed to return the superset of unit names + * possibly matched by rules added with add_matches_for_unit... */ +#define SYSTEM_UNITS \ + "_SYSTEMD_UNIT\0" \ + "COREDUMP_UNIT\0" \ + "UNIT\0" \ + "OBJECT_SYSTEMD_UNIT\0" \ + "_SYSTEMD_SLICE\0" + +/* ... and add_matches_for_user_unit */ +#define USER_UNITS \ + "_SYSTEMD_USER_UNIT\0" \ + "USER_UNIT\0" \ + "COREDUMP_USER_UNIT\0" \ + "OBJECT_SYSTEMD_USER_UNIT\0" + +static int add_units(sd_journal *j) { + _cleanup_strv_free_ char **patterns = NULL; + int r, count = 0; + char **i; + + assert(j); + + STRV_FOREACH(i, arg_system_units) { + _cleanup_free_ char *u = NULL; + + r = unit_name_mangle(*i, UNIT_NAME_GLOB, &u); + if (r < 0) + return r; + + if (string_is_glob(u)) { + r = strv_push(&patterns, u); + if (r < 0) + return r; + u = NULL; + } else { + r = add_matches_for_unit(j, u); + if (r < 0) + return r; + r = sd_journal_add_disjunction(j); + if (r < 0) + return r; + count++; + } + } + + if (!strv_isempty(patterns)) { + _cleanup_set_free_free_ Set *units = NULL; + Iterator it; + char *u; + + r = get_possible_units(j, SYSTEM_UNITS, patterns, &units); + if (r < 0) + return r; + + SET_FOREACH(u, units, it) { + r = add_matches_for_unit(j, u); + if (r < 0) + return r; + r = sd_journal_add_disjunction(j); + if (r < 0) + return r; + count++; + } + } + + patterns = strv_free(patterns); + + STRV_FOREACH(i, arg_user_units) { + _cleanup_free_ char *u = NULL; + + r = unit_name_mangle(*i, UNIT_NAME_GLOB, &u); + if (r < 0) + return r; + + if (string_is_glob(u)) { + r = strv_push(&patterns, u); + if (r < 0) + return r; + u = NULL; + } else { + r = add_matches_for_user_unit(j, u, getuid()); + if (r < 0) + return r; + r = sd_journal_add_disjunction(j); + if (r < 0) + return r; + count++; + } + } + + if (!strv_isempty(patterns)) { + _cleanup_set_free_free_ Set *units = NULL; + Iterator it; + char *u; + + r = get_possible_units(j, USER_UNITS, patterns, &units); + if (r < 0) + return r; + + SET_FOREACH(u, units, it) { + r = add_matches_for_user_unit(j, u, getuid()); + if (r < 0) + return r; + r = sd_journal_add_disjunction(j); + if (r < 0) + return r; + count++; + } + } + + /* Complain if the user request matches but nothing whatsoever was + * found, since otherwise everything would be matched. */ + if (!(strv_isempty(arg_system_units) && strv_isempty(arg_user_units)) && count == 0) + return -ENODATA; + + r = sd_journal_add_conjunction(j); + if (r < 0) + return r; + + return 0; +} + +static int add_priorities(sd_journal *j) { + char match[] = "PRIORITY=0"; + int i, r; + assert(j); + + if (arg_priorities == 0xFF) + return 0; + + for (i = LOG_EMERG; i <= LOG_DEBUG; i++) + if (arg_priorities & (1 << i)) { + match[sizeof(match)-2] = '0' + i; + + r = sd_journal_add_match(j, match, strlen(match)); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + } + + r = sd_journal_add_conjunction(j); + if (r < 0) + return log_error_errno(r, "Failed to add conjunction: %m"); + + return 0; +} + + +static int add_syslog_identifier(sd_journal *j) { + int r; + char **i; + + assert(j); + + STRV_FOREACH(i, arg_syslog_identifier) { + char *u; + + u = strjoina("SYSLOG_IDENTIFIER=", *i); + r = sd_journal_add_match(j, u, 0); + if (r < 0) + return r; + r = sd_journal_add_disjunction(j); + if (r < 0) + return r; + } + + r = sd_journal_add_conjunction(j); + if (r < 0) + return r; + + return 0; +} + +static int setup_keys(void) { +#ifdef HAVE_GCRYPT + size_t mpk_size, seed_size, state_size, i; + uint8_t *mpk, *seed, *state; + int fd = -1, r; + sd_id128_t machine, boot; + char *p = NULL, *k = NULL; + struct FSSHeader h; + uint64_t n; + struct stat st; + + r = stat("/var/log/journal", &st); + if (r < 0 && errno != ENOENT && errno != ENOTDIR) + return log_error_errno(errno, "stat(\"%s\") failed: %m", "/var/log/journal"); + + if (r < 0 || !S_ISDIR(st.st_mode)) { + log_error("%s is not a directory, must be using persistent logging for FSS.", + "/var/log/journal"); + return r < 0 ? -errno : -ENOTDIR; + } + + r = sd_id128_get_machine(&machine); + if (r < 0) + return log_error_errno(r, "Failed to get machine ID: %m"); + + r = sd_id128_get_boot(&boot); + if (r < 0) + return log_error_errno(r, "Failed to get boot ID: %m"); + + if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss", + SD_ID128_FORMAT_VAL(machine)) < 0) + return log_oom(); + + if (arg_force) { + r = unlink(p); + if (r < 0 && errno != ENOENT) { + r = log_error_errno(errno, "unlink(\"%s\") failed: %m", p); + goto finish; + } + } else if (access(p, F_OK) >= 0) { + log_error("Sealing key file %s exists already. Use --force to recreate.", p); + r = -EEXIST; + goto finish; + } + + if (asprintf(&k, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss.tmp.XXXXXX", + SD_ID128_FORMAT_VAL(machine)) < 0) { + r = log_oom(); + goto finish; + } + + mpk_size = FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR); + mpk = alloca(mpk_size); + + seed_size = FSPRG_RECOMMENDED_SEEDLEN; + seed = alloca(seed_size); + + state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR); + state = alloca(state_size); + + fd = open("/dev/random", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + r = log_error_errno(errno, "Failed to open /dev/random: %m"); + goto finish; + } + + log_info("Generating seed..."); + r = loop_read_exact(fd, seed, seed_size, true); + if (r < 0) { + log_error_errno(r, "Failed to read random seed: %m"); + goto finish; + } + + log_info("Generating key pair..."); + FSPRG_GenMK(NULL, mpk, seed, seed_size, FSPRG_RECOMMENDED_SECPAR); + + log_info("Generating sealing key..."); + FSPRG_GenState0(state, mpk, seed, seed_size); + + assert(arg_interval > 0); + + n = now(CLOCK_REALTIME); + n /= arg_interval; + + safe_close(fd); + fd = mkostemp_safe(k, O_WRONLY|O_CLOEXEC); + if (fd < 0) { + r = log_error_errno(fd, "Failed to open %s: %m", k); + goto finish; + } + + /* Enable secure remove, exclusion from dump, synchronous + * writing and in-place updating */ + r = chattr_fd(fd, FS_SECRM_FL|FS_NODUMP_FL|FS_SYNC_FL|FS_NOCOW_FL, FS_SECRM_FL|FS_NODUMP_FL|FS_SYNC_FL|FS_NOCOW_FL); + if (r < 0) + log_warning_errno(r, "Failed to set file attributes: %m"); + + zero(h); + memcpy(h.signature, "KSHHRHLP", 8); + h.machine_id = machine; + h.boot_id = boot; + h.header_size = htole64(sizeof(h)); + h.start_usec = htole64(n * arg_interval); + h.interval_usec = htole64(arg_interval); + h.fsprg_secpar = htole16(FSPRG_RECOMMENDED_SECPAR); + h.fsprg_state_size = htole64(state_size); + + r = loop_write(fd, &h, sizeof(h), false); + if (r < 0) { + log_error_errno(r, "Failed to write header: %m"); + goto finish; + } + + r = loop_write(fd, state, state_size, false); + if (r < 0) { + log_error_errno(r, "Failed to write state: %m"); + goto finish; + } + + if (link(k, p) < 0) { + r = log_error_errno(errno, "Failed to link file: %m"); + goto finish; + } + + if (on_tty()) { + fprintf(stderr, + "\n" + "The new key pair has been generated. The " ANSI_HIGHLIGHT "secret sealing key" ANSI_NORMAL " has been written to\n" + "the following local file. This key file is automatically updated when the\n" + "sealing key is advanced. It should not be used on multiple hosts.\n" + "\n" + "\t%s\n" + "\n" + "Please write down the following " ANSI_HIGHLIGHT "secret verification key" ANSI_NORMAL ". It should be stored\n" + "at a safe location and should not be saved locally on disk.\n" + "\n\t" ANSI_HIGHLIGHT_RED, p); + fflush(stderr); + } + for (i = 0; i < seed_size; i++) { + if (i > 0 && i % 3 == 0) + putchar('-'); + printf("%02x", ((uint8_t*) seed)[i]); + } + + printf("/%llx-%llx\n", (unsigned long long) n, (unsigned long long) arg_interval); + + if (on_tty()) { + char tsb[FORMAT_TIMESPAN_MAX], *hn; + + fprintf(stderr, + ANSI_NORMAL "\n" + "The sealing key is automatically changed every %s.\n", + format_timespan(tsb, sizeof(tsb), arg_interval, 0)); + + hn = gethostname_malloc(); + + if (hn) { + hostname_cleanup(hn); + fprintf(stderr, "\nThe keys have been generated for host %s/" SD_ID128_FORMAT_STR ".\n", hn, SD_ID128_FORMAT_VAL(machine)); + } else + fprintf(stderr, "\nThe keys have been generated for host " SD_ID128_FORMAT_STR ".\n", SD_ID128_FORMAT_VAL(machine)); + +#ifdef HAVE_QRENCODE + /* If this is not an UTF-8 system don't print any QR codes */ + if (is_locale_utf8()) { + fputs("\nTo transfer the verification key to your phone please scan the QR code below:\n\n", stderr); + print_qr_code(stderr, seed, seed_size, n, arg_interval, hn, machine); + } +#endif + free(hn); + } + + r = 0; + +finish: + safe_close(fd); + + if (k) { + unlink(k); + free(k); + } + + free(p); + + return r; +#else + log_error("Forward-secure sealing not available."); + return -EOPNOTSUPP; +#endif +} + +static int verify(sd_journal *j) { + int r = 0; + Iterator i; + JournalFile *f; + + assert(j); + + log_show_color(true); + + ORDERED_HASHMAP_FOREACH(f, j->files, i) { + int k; + usec_t first = 0, validated = 0, last = 0; + +#ifdef HAVE_GCRYPT + if (!arg_verify_key && JOURNAL_HEADER_SEALED(f->header)) + log_notice("Journal file %s has sealing enabled but verification key has not been passed using --verify-key=.", f->path); +#endif + + k = journal_file_verify(f, arg_verify_key, &first, &validated, &last, true); + if (k == -EINVAL) { + /* If the key was invalid give up right-away. */ + return k; + } else if (k < 0) { + log_warning_errno(k, "FAIL: %s (%m)", f->path); + r = k; + } else { + char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX], c[FORMAT_TIMESPAN_MAX]; + log_info("PASS: %s", f->path); + + if (arg_verify_key && JOURNAL_HEADER_SEALED(f->header)) { + if (validated > 0) { + log_info("=> Validated from %s to %s, final %s entries not sealed.", + format_timestamp_maybe_utc(a, sizeof(a), first), + format_timestamp_maybe_utc(b, sizeof(b), validated), + format_timespan(c, sizeof(c), last > validated ? last - validated : 0, 0)); + } else if (last > 0) + log_info("=> No sealing yet, %s of entries not sealed.", + format_timespan(c, sizeof(c), last - first, 0)); + else + log_info("=> No sealing yet, no entries in file."); + } + } + } + + return r; +} + +static int access_check_var_log_journal(sd_journal *j) { +#ifdef HAVE_ACL + _cleanup_strv_free_ char **g = NULL; + const char* dir; +#endif + int r; + + assert(j); + + if (arg_quiet) + return 0; + + /* If we are root, we should have access, don't warn. */ + if (getuid() == 0) + return 0; + + /* If we are in the 'systemd-journal' group, we should have + * access too. */ + r = in_group("systemd-journal"); + if (r < 0) + return log_error_errno(r, "Failed to check if we are in the 'systemd-journal' group: %m"); + if (r > 0) + return 0; + +#ifdef HAVE_ACL + if (laccess("/run/log/journal", F_OK) >= 0) + dir = "/run/log/journal"; + else + dir = "/var/log/journal"; + + /* If we are in any of the groups listed in the journal ACLs, + * then all is good, too. Let's enumerate all groups from the + * default ACL of the directory, which generally should allow + * access to most journal files too. */ + r = acl_search_groups(dir, &g); + if (r < 0) + return log_error_errno(r, "Failed to search journal ACL: %m"); + if (r > 0) + return 0; + + /* Print a pretty list, if there were ACLs set. */ + if (!strv_isempty(g)) { + _cleanup_free_ char *s = NULL; + + /* Thre are groups in the ACL, let's list them */ + r = strv_extend(&g, "systemd-journal"); + if (r < 0) + return log_oom(); + + strv_sort(g); + strv_uniq(g); + + s = strv_join(g, "', '"); + if (!s) + return log_oom(); + + log_notice("Hint: You are currently not seeing messages from other users and the system.\n" + " Users in groups '%s' can see all messages.\n" + " Pass -q to turn off this notice.", s); + return 1; + } +#endif + + /* If no ACLs were found, print a short version of the message. */ + log_notice("Hint: You are currently not seeing messages from other users and the system.\n" + " Users in the 'systemd-journal' group can see all messages. Pass -q to\n" + " turn off this notice."); + + return 1; +} + +static int access_check(sd_journal *j) { + Iterator it; + void *code; + char *path; + int r = 0; + + assert(j); + + if (hashmap_isempty(j->errors)) { + if (ordered_hashmap_isempty(j->files)) + log_notice("No journal files were found."); + + return 0; + } + + if (hashmap_contains(j->errors, INT_TO_PTR(-EACCES))) { + (void) access_check_var_log_journal(j); + + if (ordered_hashmap_isempty(j->files)) + r = log_error_errno(EACCES, "No journal files were opened due to insufficient permissions."); + } + + HASHMAP_FOREACH_KEY(path, code, j->errors, it) { + int err; + + err = abs(PTR_TO_INT(code)); + + switch (err) { + case EACCES: + continue; + + case ENODATA: + log_warning_errno(err, "Journal file %s is truncated, ignoring file.", path); + break; + + case EPROTONOSUPPORT: + log_warning_errno(err, "Journal file %s uses an unsupported feature, ignoring file.", path); + break; + + case EBADMSG: + log_warning_errno(err, "Journal file %s corrupted, ignoring file.", path); + break; + + default: + log_warning_errno(err, "An error was encountered while opening journal file or directory %s, ignoring file: %m", path); + break; + } + } + + return r; +} + +static int flush_to_var(void) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_close_ int watch_fd = -1; + int r; + + if (arg_machine) { + log_error("--flush is not supported in conjunction with --machine=."); + return -EOPNOTSUPP; + } + + /* Quick exit */ + if (access("/run/systemd/journal/flushed", F_OK) >= 0) + return 0; + + /* OK, let's actually do the full logic, send SIGUSR1 to the + * daemon and set up inotify to wait for the flushed file to appear */ + r = bus_connect_system_systemd(&bus); + if (r < 0) + return log_error_errno(r, "Failed to get D-Bus connection: %m"); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "KillUnit", + &error, + NULL, + "ssi", "systemd-journald.service", "main", SIGUSR1); + if (r < 0) + return log_error_errno(r, "Failed to kill journal service: %s", bus_error_message(&error, r)); + + mkdir_p("/run/systemd/journal", 0755); + + watch_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); + if (watch_fd < 0) + return log_error_errno(errno, "Failed to create inotify watch: %m"); + + r = inotify_add_watch(watch_fd, "/run/systemd/journal", IN_CREATE|IN_DONT_FOLLOW|IN_ONLYDIR); + if (r < 0) + return log_error_errno(errno, "Failed to watch journal directory: %m"); + + for (;;) { + if (access("/run/systemd/journal/flushed", F_OK) >= 0) + break; + + if (errno != ENOENT) + return log_error_errno(errno, "Failed to check for existence of /run/systemd/journal/flushed: %m"); + + r = fd_wait_for_event(watch_fd, POLLIN, USEC_INFINITY); + if (r < 0) + return log_error_errno(r, "Failed to wait for event: %m"); + + r = flush_fd(watch_fd); + if (r < 0) + return log_error_errno(r, "Failed to flush inotify events: %m"); + } + + return 0; +} + +static int send_signal_and_wait(int sig, const char *watch_path) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_close_ int watch_fd = -1; + usec_t start; + int r; + + if (arg_machine) { + log_error("--sync and --rotate are not supported in conjunction with --machine=."); + return -EOPNOTSUPP; + } + + start = now(CLOCK_MONOTONIC); + + /* This call sends the specified signal to journald, and waits + * for acknowledgment by watching the mtime of the specified + * flag file. This is used to trigger syncing or rotation and + * then wait for the operation to complete. */ + + for (;;) { + usec_t tstamp; + + /* See if a sync happened by now. */ + r = read_timestamp_file(watch_path, &tstamp); + if (r < 0 && r != -ENOENT) + return log_error_errno(errno, "Failed to read %s: %m", watch_path); + if (r >= 0 && tstamp >= start) + return 0; + + /* Let's ask for a sync, but only once. */ + if (!bus) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + r = bus_connect_system_systemd(&bus); + if (r < 0) + return log_error_errno(r, "Failed to get D-Bus connection: %m"); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "KillUnit", + &error, + NULL, + "ssi", "systemd-journald.service", "main", sig); + if (r < 0) + return log_error_errno(r, "Failed to kill journal service: %s", bus_error_message(&error, r)); + + continue; + } + + /* Let's install the inotify watch, if we didn't do that yet. */ + if (watch_fd < 0) { + + mkdir_p("/run/systemd/journal", 0755); + + watch_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); + if (watch_fd < 0) + return log_error_errno(errno, "Failed to create inotify watch: %m"); + + r = inotify_add_watch(watch_fd, "/run/systemd/journal", IN_MOVED_TO|IN_DONT_FOLLOW|IN_ONLYDIR); + if (r < 0) + return log_error_errno(errno, "Failed to watch journal directory: %m"); + + /* Recheck the flag file immediately, so that we don't miss any event since the last check. */ + continue; + } + + /* OK, all preparatory steps done, let's wait until + * inotify reports an event. */ + + r = fd_wait_for_event(watch_fd, POLLIN, USEC_INFINITY); + if (r < 0) + return log_error_errno(r, "Failed to wait for event: %m"); + + r = flush_fd(watch_fd); + if (r < 0) + return log_error_errno(r, "Failed to flush inotify events: %m"); + } + + return 0; +} + +static int rotate(void) { + return send_signal_and_wait(SIGUSR2, "/run/systemd/journal/rotated"); +} + +static int sync_journal(void) { + return send_signal_and_wait(SIGRTMIN+1, "/run/systemd/journal/synced"); +} + +int main(int argc, char *argv[]) { + int r; + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + bool need_seek = false; + sd_id128_t previous_boot_id; + bool previous_boot_id_valid = false, first_line = true; + int n_shown = 0; + bool ellipsized = false; + + setlocale(LC_ALL, ""); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + signal(SIGWINCH, columns_lines_cache_reset); + sigbus_install(); + + /* Increase max number of open files to 16K if we can, we + * might needs this when browsing journal files, which might + * be split up into many files. */ + setrlimit_closest(RLIMIT_NOFILE, &RLIMIT_MAKE_CONST(16384)); + + switch (arg_action) { + + case ACTION_NEW_ID128: + r = generate_new_id128(); + goto finish; + + case ACTION_SETUP_KEYS: + r = setup_keys(); + goto finish; + + case ACTION_LIST_CATALOG: + case ACTION_DUMP_CATALOG: + case ACTION_UPDATE_CATALOG: { + _cleanup_free_ char *database; + + database = path_join(arg_root, CATALOG_DATABASE, NULL); + if (!database) { + r = log_oom(); + goto finish; + } + + if (arg_action == ACTION_UPDATE_CATALOG) { + r = catalog_update(database, arg_root, catalog_file_dirs); + if (r < 0) + log_error_errno(r, "Failed to list catalog: %m"); + } else { + bool oneline = arg_action == ACTION_LIST_CATALOG; + + pager_open(arg_no_pager, arg_pager_end); + + if (optind < argc) + r = catalog_list_items(stdout, database, oneline, argv + optind); + else + r = catalog_list(stdout, database, oneline); + if (r < 0) + log_error_errno(r, "Failed to list catalog: %m"); + } + + goto finish; + } + + case ACTION_FLUSH: + r = flush_to_var(); + goto finish; + + case ACTION_SYNC: + r = sync_journal(); + goto finish; + + case ACTION_ROTATE: + r = rotate(); + goto finish; + + case ACTION_SHOW: + case ACTION_PRINT_HEADER: + case ACTION_VERIFY: + case ACTION_DISK_USAGE: + case ACTION_LIST_BOOTS: + case ACTION_VACUUM: + case ACTION_LIST_FIELDS: + case ACTION_LIST_FIELD_NAMES: + /* These ones require access to the journal files, continue below. */ + break; + + default: + assert_not_reached("Unknown action"); + } + + if (arg_directory) + r = sd_journal_open_directory(&j, arg_directory, arg_journal_type); + else if (arg_file_stdin) { + int ifd = STDIN_FILENO; + r = sd_journal_open_files_fd(&j, &ifd, 1, 0); + } else if (arg_file) + r = sd_journal_open_files(&j, (const char**) arg_file, 0); + else if (arg_machine) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int fd; + + if (geteuid() != 0) { + /* The file descriptor returned by OpenMachineRootDirectory() will be owned by users/groups of + * the container, thus we need root privileges to override them. */ + log_error("Using the --machine= switch requires root privileges."); + r = -EPERM; + goto finish; + } + + r = sd_bus_open_system(&bus); + if (r < 0) { + log_error_errno(r, "Failed to open system bus: %m"); + goto finish; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "OpenMachineRootDirectory", + &error, + &reply, + "s", arg_machine); + if (r < 0) { + log_error_errno(r, "Failed to open root directory: %s", bus_error_message(&error, r)); + goto finish; + } + + r = sd_bus_message_read(reply, "h", &fd); + if (r < 0) { + bus_log_parse_error(r); + goto finish; + } + + fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (fd < 0) { + r = log_error_errno(errno, "Failed to duplicate file descriptor: %m"); + goto finish; + } + + r = sd_journal_open_directory_fd(&j, fd, SD_JOURNAL_OS_ROOT); + if (r < 0) + safe_close(fd); + } else + r = sd_journal_open(&j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type); + if (r < 0) { + log_error_errno(r, "Failed to open %s: %m", arg_directory ?: arg_file ? "files" : "journal"); + goto finish; + } + + r = access_check(j); + if (r < 0) + goto finish; + + switch (arg_action) { + + case ACTION_NEW_ID128: + case ACTION_SETUP_KEYS: + case ACTION_LIST_CATALOG: + case ACTION_DUMP_CATALOG: + case ACTION_UPDATE_CATALOG: + case ACTION_FLUSH: + case ACTION_SYNC: + case ACTION_ROTATE: + assert_not_reached("Unexpected action."); + + case ACTION_PRINT_HEADER: + journal_print_header(j); + r = 0; + goto finish; + + case ACTION_VERIFY: + r = verify(j); + goto finish; + + case ACTION_DISK_USAGE: { + uint64_t bytes = 0; + char sbytes[FORMAT_BYTES_MAX]; + + r = sd_journal_get_usage(j, &bytes); + if (r < 0) + goto finish; + + printf("Archived and active journals take up %s on disk.\n", + format_bytes(sbytes, sizeof(sbytes), bytes)); + goto finish; + } + + case ACTION_LIST_BOOTS: + r = list_boots(j); + goto finish; + + case ACTION_VACUUM: { + Directory *d; + Iterator i; + + HASHMAP_FOREACH(d, j->directories_by_path, i) { + int q; + + if (d->is_root) + continue; + + q = journal_directory_vacuum(d->path, arg_vacuum_size, arg_vacuum_n_files, arg_vacuum_time, NULL, true); + if (q < 0) { + log_error_errno(q, "Failed to vacuum %s: %m", d->path); + r = q; + } + } + + goto finish; + } + + case ACTION_LIST_FIELD_NAMES: { + const char *field; + + SD_JOURNAL_FOREACH_FIELD(j, field) { + printf("%s\n", field); + n_shown++; + } + + r = 0; + goto finish; + } + + case ACTION_SHOW: + case ACTION_LIST_FIELDS: + break; + + default: + assert_not_reached("Unknown action"); + } + + if (arg_boot_offset != 0 && + sd_journal_has_runtime_files(j) > 0 && + sd_journal_has_persistent_files(j) == 0) { + log_info("Specifying boot ID has no effect, no persistent journal was found"); + r = 0; + goto finish; + } + /* add_boot() must be called first! + * It may need to seek the journal to find parent boot IDs. */ + r = add_boot(j); + if (r < 0) + goto finish; + + r = add_dmesg(j); + if (r < 0) + goto finish; + + r = add_units(j); + if (r < 0) { + log_error_errno(r, "Failed to add filter for units: %m"); + goto finish; + } + + r = add_syslog_identifier(j); + if (r < 0) { + log_error_errno(r, "Failed to add filter for syslog identifiers: %m"); + goto finish; + } + + r = add_priorities(j); + if (r < 0) + goto finish; + + r = add_matches(j, argv + optind); + if (r < 0) + goto finish; + + if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) { + _cleanup_free_ char *filter; + + filter = journal_make_match_string(j); + if (!filter) + return log_oom(); + + log_debug("Journal filter: %s", filter); + } + + if (arg_action == ACTION_LIST_FIELDS) { + const void *data; + size_t size; + + assert(arg_field); + + r = sd_journal_set_data_threshold(j, 0); + if (r < 0) { + log_error_errno(r, "Failed to unset data size threshold: %m"); + goto finish; + } + + r = sd_journal_query_unique(j, arg_field); + if (r < 0) { + log_error_errno(r, "Failed to query unique data objects: %m"); + goto finish; + } + + SD_JOURNAL_FOREACH_UNIQUE(j, data, size) { + const void *eq; + + if (arg_lines >= 0 && n_shown >= arg_lines) + break; + + eq = memchr(data, '=', size); + if (eq) + printf("%.*s\n", (int) (size - ((const uint8_t*) eq - (const uint8_t*) data + 1)), (const char*) eq + 1); + else + printf("%.*s\n", (int) size, (const char*) data); + + n_shown++; + } + + r = 0; + goto finish; + } + + /* Opening the fd now means the first sd_journal_wait() will actually wait */ + if (arg_follow) { + r = sd_journal_get_fd(j); + if (r == -EMEDIUMTYPE) { + log_error_errno(r, "The --follow switch is not supported in conjunction with reading from STDIN."); + goto finish; + } + if (r < 0) { + log_error_errno(r, "Failed to get journal fd: %m"); + goto finish; + } + } + + if (arg_cursor || arg_after_cursor) { + r = sd_journal_seek_cursor(j, arg_cursor ?: arg_after_cursor); + if (r < 0) { + log_error_errno(r, "Failed to seek to cursor: %m"); + goto finish; + } + + if (!arg_reverse) + r = sd_journal_next_skip(j, 1 + !!arg_after_cursor); + else + r = sd_journal_previous_skip(j, 1 + !!arg_after_cursor); + + if (arg_after_cursor && r < 2) { + /* We couldn't find the next entry after the cursor. */ + if (arg_follow) + need_seek = true; + else + arg_lines = 0; + } + + } else if (arg_since_set && !arg_reverse) { + r = sd_journal_seek_realtime_usec(j, arg_since); + if (r < 0) { + log_error_errno(r, "Failed to seek to date: %m"); + goto finish; + } + r = sd_journal_next(j); + + } else if (arg_until_set && arg_reverse) { + r = sd_journal_seek_realtime_usec(j, arg_until); + if (r < 0) { + log_error_errno(r, "Failed to seek to date: %m"); + goto finish; + } + r = sd_journal_previous(j); + + } else if (arg_lines >= 0) { + r = sd_journal_seek_tail(j); + if (r < 0) { + log_error_errno(r, "Failed to seek to tail: %m"); + goto finish; + } + + r = sd_journal_previous_skip(j, arg_lines); + + } else if (arg_reverse) { + r = sd_journal_seek_tail(j); + if (r < 0) { + log_error_errno(r, "Failed to seek to tail: %m"); + goto finish; + } + + r = sd_journal_previous(j); + + } else { + r = sd_journal_seek_head(j); + if (r < 0) { + log_error_errno(r, "Failed to seek to head: %m"); + goto finish; + } + + r = sd_journal_next(j); + } + + if (r < 0) { + log_error_errno(r, "Failed to iterate through journal: %m"); + goto finish; + } + if (r == 0) { + if (arg_follow) + need_seek = true; + else { + if (!arg_quiet) + printf("-- No entries --\n"); + goto finish; + } + } + + if (!arg_follow) + pager_open(arg_no_pager, arg_pager_end); + + if (!arg_quiet) { + usec_t start, end; + char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX]; + + r = sd_journal_get_cutoff_realtime_usec(j, &start, &end); + if (r < 0) { + log_error_errno(r, "Failed to get cutoff: %m"); + goto finish; + } + + if (r > 0) { + if (arg_follow) + printf("-- Logs begin at %s. --\n", + format_timestamp_maybe_utc(start_buf, sizeof(start_buf), start)); + else + printf("-- Logs begin at %s, end at %s. --\n", + format_timestamp_maybe_utc(start_buf, sizeof(start_buf), start), + format_timestamp_maybe_utc(end_buf, sizeof(end_buf), end)); + } + } + + for (;;) { + while (arg_lines < 0 || n_shown < arg_lines || (arg_follow && !first_line)) { + int flags; + + if (need_seek) { + if (!arg_reverse) + r = sd_journal_next(j); + else + r = sd_journal_previous(j); + if (r < 0) { + log_error_errno(r, "Failed to iterate through journal: %m"); + goto finish; + } + if (r == 0) + break; + } + + if (arg_until_set && !arg_reverse) { + usec_t usec; + + r = sd_journal_get_realtime_usec(j, &usec); + if (r < 0) { + log_error_errno(r, "Failed to determine timestamp: %m"); + goto finish; + } + if (usec > arg_until) + goto finish; + } + + if (arg_since_set && arg_reverse) { + usec_t usec; + + r = sd_journal_get_realtime_usec(j, &usec); + if (r < 0) { + log_error_errno(r, "Failed to determine timestamp: %m"); + goto finish; + } + if (usec < arg_since) + goto finish; + } + + if (!arg_merge && !arg_quiet) { + sd_id128_t boot_id; + + r = sd_journal_get_monotonic_usec(j, NULL, &boot_id); + if (r >= 0) { + if (previous_boot_id_valid && + !sd_id128_equal(boot_id, previous_boot_id)) + printf("%s-- Reboot --%s\n", + ansi_highlight(), ansi_normal()); + + previous_boot_id = boot_id; + previous_boot_id_valid = true; + } + } + + flags = + arg_all * OUTPUT_SHOW_ALL | + arg_full * OUTPUT_FULL_WIDTH | + colors_enabled() * OUTPUT_COLOR | + arg_catalog * OUTPUT_CATALOG | + arg_utc * OUTPUT_UTC | + arg_no_hostname * OUTPUT_NO_HOSTNAME; + + r = output_journal(stdout, j, arg_output, 0, flags, &ellipsized); + need_seek = true; + if (r == -EADDRNOTAVAIL) + break; + else if (r < 0 || ferror(stdout)) + goto finish; + + n_shown++; + } + + if (!arg_follow) { + if (arg_show_cursor) { + _cleanup_free_ char *cursor = NULL; + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0 && r != -EADDRNOTAVAIL) + log_error_errno(r, "Failed to get cursor: %m"); + else if (r >= 0) + printf("-- cursor: %s\n", cursor); + } + + break; + } + + r = sd_journal_wait(j, (uint64_t) -1); + if (r < 0) { + log_error_errno(r, "Couldn't wait for journal event: %m"); + goto finish; + } + + first_line = false; + } + +finish: + pager_close(); + + strv_free(arg_file); + + strv_free(arg_syslog_identifier); + strv_free(arg_system_units); + strv_free(arg_user_units); + + free(arg_root); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-journal/libjournal-core/Makefile b/src/grp-journal/libjournal-core/Makefile new file mode 100644 index 0000000000..d55aebfb49 --- /dev/null +++ b/src/grp-journal/libjournal-core/Makefile @@ -0,0 +1,56 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +libjournal_core_la_SOURCES = \ + src/journal/journald-kmsg.c \ + src/journal/journald-kmsg.h \ + src/journal/journald-syslog.c \ + src/journal/journald-syslog.h \ + src/journal/journald-stream.c \ + src/journal/journald-stream.h \ + src/journal/journald-server.c \ + src/journal/journald-server.h \ + src/journal/journald-console.c \ + src/journal/journald-console.h \ + src/journal/journald-wall.c \ + src/journal/journald-wall.h \ + src/journal/journald-native.c \ + src/journal/journald-native.h \ + src/journal/journald-audit.c \ + src/journal/journald-audit.h \ + src/journal/journald-rate-limit.c \ + src/journal/journald-rate-limit.h \ + src/journal/journal-internal.h + +nodist_libjournal_core_la_SOURCES = \ + src/journal/journald-gperf.c + +libjournal_core_la_LIBADD = \ + libshared.la + +noinst_LTLIBRARIES += \ + libjournal-core.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-journal/libjournal-core/cat.c b/src/grp-journal/libjournal-core/cat.c new file mode 100644 index 0000000000..93ab6e7f96 --- /dev/null +++ b/src/grp-journal/libjournal-core/cat.c @@ -0,0 +1,161 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 +#include +#include +#include +#include + +#include + +#include "fd-util.h" +#include "parse-util.h" +#include "string-util.h" +#include "syslog-util.h" +#include "util.h" + +static const char *arg_identifier = NULL; +static int arg_priority = LOG_INFO; +static bool arg_level_prefix = true; + +static void help(void) { + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Execute process with stdout/stderr connected to the journal.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -t --identifier=STRING Set syslog identifier\n" + " -p --priority=PRIORITY Set priority value (0..7)\n" + " --level-prefix=BOOL Control whether level prefix shall be parsed\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_LEVEL_PREFIX + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "identifier", required_argument, NULL, 't' }, + { "priority", required_argument, NULL, 'p' }, + { "level-prefix", required_argument, NULL, ARG_LEVEL_PREFIX }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "+ht:p:", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case 't': + if (isempty(optarg)) + arg_identifier = NULL; + else + arg_identifier = optarg; + break; + + case 'p': + arg_priority = log_level_from_string(optarg); + if (arg_priority < 0) { + log_error("Failed to parse priority value."); + return -EINVAL; + } + break; + + case ARG_LEVEL_PREFIX: { + int k; + + k = parse_boolean(optarg); + if (k < 0) + return log_error_errno(k, "Failed to parse level prefix value."); + + arg_level_prefix = k; + break; + } + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +int main(int argc, char *argv[]) { + _cleanup_close_ int fd = -1, saved_stderr = -1; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + fd = sd_journal_stream_fd(arg_identifier, arg_priority, arg_level_prefix); + if (fd < 0) { + r = log_error_errno(fd, "Failed to create stream fd: %m"); + goto finish; + } + + saved_stderr = fcntl(STDERR_FILENO, F_DUPFD_CLOEXEC, 3); + + if (dup3(fd, STDOUT_FILENO, 0) < 0 || + dup3(fd, STDERR_FILENO, 0) < 0) { + r = log_error_errno(errno, "Failed to duplicate fd: %m"); + goto finish; + } + + if (fd >= 3) + safe_close(fd); + fd = -1; + + if (argc <= optind) + (void) execl("/bin/cat", "/bin/cat", NULL); + else + (void) execvp(argv[optind], argv + optind); + r = -errno; + + /* Let's try to restore a working stderr, so we can print the error message */ + if (saved_stderr >= 0) + (void) dup3(saved_stderr, STDERR_FILENO, 0); + + log_error_errno(r, "Failed to execute process: %m"); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-journal/libjournal-core/journal-qrcode.c b/src/grp-journal/libjournal-core/journal-qrcode.c new file mode 100644 index 0000000000..e38730d65c --- /dev/null +++ b/src/grp-journal/libjournal-core/journal-qrcode.c @@ -0,0 +1,135 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 +#include +#include +#include +#include + +#include "journal-qrcode.h" + +#define WHITE_ON_BLACK "\033[40;37;1m" +#define NORMAL "\033[0m" + +static void print_border(FILE *output, unsigned width) { + unsigned x, y; + + /* Four rows of border */ + for (y = 0; y < 4; y += 2) { + fputs(WHITE_ON_BLACK, output); + + for (x = 0; x < 4 + width + 4; x++) + fputs("\342\226\210", output); + + fputs(NORMAL "\n", output); + } +} + +int print_qr_code( + FILE *output, + const void *seed, + size_t seed_size, + uint64_t start, + uint64_t interval, + const char *hn, + sd_id128_t machine) { + + FILE *f; + char *url = NULL; + size_t url_size = 0, i; + QRcode* qr; + unsigned x, y; + + assert(seed); + assert(seed_size > 0); + + f = open_memstream(&url, &url_size); + if (!f) + return -ENOMEM; + + fputs("fss://", f); + + for (i = 0; i < seed_size; i++) { + if (i > 0 && i % 3 == 0) + fputc('-', f); + fprintf(f, "%02x", ((uint8_t*) seed)[i]); + } + + fprintf(f, "/%"PRIx64"-%"PRIx64"?machine=" SD_ID128_FORMAT_STR, + start, + interval, + SD_ID128_FORMAT_VAL(machine)); + + if (hn) + fprintf(f, ";hostname=%s", hn); + + if (ferror(f)) { + fclose(f); + free(url); + return -ENOMEM; + } + + fclose(f); + + qr = QRcode_encodeString(url, 0, QR_ECLEVEL_L, QR_MODE_8, 1); + free(url); + + if (!qr) + return -ENOMEM; + + print_border(output, qr->width); + + for (y = 0; y < (unsigned) qr->width; y += 2) { + const uint8_t *row1, *row2; + + row1 = qr->data + qr->width * y; + row2 = row1 + qr->width; + + fputs(WHITE_ON_BLACK, output); + for (x = 0; x < 4; x++) + fputs("\342\226\210", output); + + for (x = 0; x < (unsigned) qr->width; x ++) { + bool a, b; + + a = row1[x] & 1; + b = (y+1) < (unsigned) qr->width ? (row2[x] & 1) : false; + + if (a && b) + fputc(' ', output); + else if (a) + fputs("\342\226\204", output); + else if (b) + fputs("\342\226\200", output); + else + fputs("\342\226\210", output); + } + + for (x = 0; x < 4; x++) + fputs("\342\226\210", output); + fputs(NORMAL "\n", output); + } + + print_border(output, qr->width); + + QRcode_free(qr); + return 0; +} diff --git a/src/grp-journal/libjournal-core/journal-qrcode.h b/src/grp-journal/libjournal-core/journal-qrcode.h new file mode 100644 index 0000000000..34a779d5be --- /dev/null +++ b/src/grp-journal/libjournal-core/journal-qrcode.h @@ -0,0 +1,27 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 + +#include + +int print_qr_code(FILE *f, const void *seed, size_t seed_size, uint64_t start, uint64_t interval, const char *hn, sd_id128_t machine); diff --git a/src/grp-journal/libjournal-core/journald-audit.c b/src/grp-journal/libjournal-core/journald-audit.c new file mode 100644 index 0000000000..a433c91c54 --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-audit.c @@ -0,0 +1,564 @@ +/*** + 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 "alloc-util.h" +#include "audit-type.h" +#include "fd-util.h" +#include "hexdecoct.h" +#include "io-util.h" +#include "journald-audit.h" +#include "missing.h" +#include "string-util.h" + +typedef struct MapField { + const char *audit_field; + const char *journal_field; + int (*map)(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov); +} MapField; + +static int map_simple_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) { + _cleanup_free_ char *c = NULL; + size_t l = 0, allocated = 0; + const char *e; + + assert(field); + assert(p); + assert(iov); + assert(n_iov); + + l = strlen(field); + allocated = l + 1; + c = malloc(allocated); + if (!c) + return -ENOMEM; + + memcpy(c, field, l); + for (e = *p; *e != ' ' && *e != 0; e++) { + if (!GREEDY_REALLOC(c, allocated, l+2)) + return -ENOMEM; + + c[l++] = *e; + } + + c[l] = 0; + + if (!GREEDY_REALLOC(*iov, *n_iov_allocated, *n_iov + 1)) + return -ENOMEM; + + (*iov)[*n_iov].iov_base = c; + (*iov)[*n_iov].iov_len = l; + (*n_iov)++; + + *p = e; + c = NULL; + + return 1; +} + +static int map_string_field_internal(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov, bool filter_printable) { + _cleanup_free_ char *c = NULL; + const char *s, *e; + size_t l; + + assert(field); + assert(p); + assert(iov); + assert(n_iov); + + /* The kernel formats string fields in one of two formats. */ + + if (**p == '"') { + /* Normal quoted syntax */ + s = *p + 1; + e = strchr(s, '"'); + if (!e) + return 0; + + l = strlen(field) + (e - s); + c = malloc(l+1); + if (!c) + return -ENOMEM; + + *((char*) mempcpy(stpcpy(c, field), s, e - s)) = 0; + + e += 1; + + } else if (unhexchar(**p) >= 0) { + /* Hexadecimal escaping */ + size_t allocated = 0; + + l = strlen(field); + allocated = l + 2; + c = malloc(allocated); + if (!c) + return -ENOMEM; + + memcpy(c, field, l); + for (e = *p; *e != ' ' && *e != 0; e += 2) { + int a, b; + uint8_t x; + + a = unhexchar(e[0]); + if (a < 0) + return 0; + + b = unhexchar(e[1]); + if (b < 0) + return 0; + + x = ((uint8_t) a << 4 | (uint8_t) b); + + if (filter_printable && x < (uint8_t) ' ') + x = (uint8_t) ' '; + + if (!GREEDY_REALLOC(c, allocated, l+2)) + return -ENOMEM; + + c[l++] = (char) x; + } + + c[l] = 0; + } else + return 0; + + if (!GREEDY_REALLOC(*iov, *n_iov_allocated, *n_iov + 1)) + return -ENOMEM; + + (*iov)[*n_iov].iov_base = c; + (*iov)[*n_iov].iov_len = l; + (*n_iov)++; + + *p = e; + c = NULL; + + return 1; +} + +static int map_string_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) { + return map_string_field_internal(field, p, iov, n_iov_allocated, n_iov, false); +} + +static int map_string_field_printable(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) { + return map_string_field_internal(field, p, iov, n_iov_allocated, n_iov, true); +} + +static int map_generic_field(const char *prefix, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) { + const char *e, *f; + char *c, *t; + int r; + + /* Implements fallback mappings for all fields we don't know */ + + for (e = *p; e < *p + 16; e++) { + + if (*e == 0 || *e == ' ') + return 0; + + if (*e == '=') + break; + + if (!((*e >= 'a' && *e <= 'z') || + (*e >= 'A' && *e <= 'Z') || + (*e >= '0' && *e <= '9') || + *e == '_' || *e == '-')) + return 0; + } + + if (e <= *p || e >= *p + 16) + return 0; + + c = alloca(strlen(prefix) + (e - *p) + 2); + + t = stpcpy(c, prefix); + for (f = *p; f < e; f++) { + char x; + + if (*f >= 'a' && *f <= 'z') + x = (*f - 'a') + 'A'; /* uppercase */ + else if (*f == '-') + x = '_'; /* dashes → underscores */ + else + x = *f; + + *(t++) = x; + } + strcpy(t, "="); + + e++; + + r = map_simple_field(c, &e, iov, n_iov_allocated, n_iov); + if (r < 0) + return r; + + *p = e; + return r; +} + +/* Kernel fields are those occurring in the audit string before + * msg='. All of these fields are trusted, hence carry the "_" prefix. + * We try to translate the fields we know into our native names. The + * other's are generically mapped to _AUDIT_FIELD_XYZ= */ +static const MapField map_fields_kernel[] = { + + /* First, we map certain well-known audit fields into native + * well-known fields */ + { "pid=", "_PID=", map_simple_field }, + { "ppid=", "_PPID=", map_simple_field }, + { "uid=", "_UID=", map_simple_field }, + { "euid=", "_EUID=", map_simple_field }, + { "fsuid=", "_FSUID=", map_simple_field }, + { "gid=", "_GID=", map_simple_field }, + { "egid=", "_EGID=", map_simple_field }, + { "fsgid=", "_FSGID=", map_simple_field }, + { "tty=", "_TTY=", map_simple_field }, + { "ses=", "_AUDIT_SESSION=", map_simple_field }, + { "auid=", "_AUDIT_LOGINUID=", map_simple_field }, + { "subj=", "_SELINUX_CONTEXT=", map_simple_field }, + { "comm=", "_COMM=", map_string_field }, + { "exe=", "_EXE=", map_string_field }, + { "proctitle=", "_CMDLINE=", map_string_field_printable }, + + /* Some fields don't map to native well-known fields. However, + * we know that they are string fields, hence let's undo + * string field escaping for them, though we stick to the + * generic field names. */ + { "path=", "_AUDIT_FIELD_PATH=", map_string_field }, + { "dev=", "_AUDIT_FIELD_DEV=", map_string_field }, + { "name=", "_AUDIT_FIELD_NAME=", map_string_field }, + {} +}; + +/* Userspace fields are those occurring in the audit string after + * msg='. All of these fields are untrusted, hence carry no "_" + * prefix. We map the fields we don't know to AUDIT_FIELD_XYZ= */ +static const MapField map_fields_userspace[] = { + { "cwd=", "AUDIT_FIELD_CWD=", map_string_field }, + { "cmd=", "AUDIT_FIELD_CMD=", map_string_field }, + { "acct=", "AUDIT_FIELD_ACCT=", map_string_field }, + { "exe=", "AUDIT_FIELD_EXE=", map_string_field }, + { "comm=", "AUDIT_FIELD_COMM=", map_string_field }, + {} +}; + +static int map_all_fields( + const char *p, + const MapField map_fields[], + const char *prefix, + bool handle_msg, + struct iovec **iov, + size_t *n_iov_allocated, + unsigned *n_iov) { + + int r; + + assert(p); + assert(iov); + assert(n_iov_allocated); + assert(n_iov); + + for (;;) { + bool mapped = false; + const MapField *m; + const char *v; + + p += strspn(p, WHITESPACE); + + if (*p == 0) + return 0; + + if (handle_msg) { + v = startswith(p, "msg='"); + if (v) { + const char *e; + char *c; + + /* Userspace message. It's enclosed in + simple quotation marks, is not + escaped, but the last field in the + line, hence let's remove the + quotation mark, and apply the + userspace mapping instead of the + kernel mapping. */ + + e = endswith(v, "'"); + if (!e) + return 0; /* don't continue splitting up if the final quotation mark is missing */ + + c = strndupa(v, e - v); + return map_all_fields(c, map_fields_userspace, "AUDIT_FIELD_", false, iov, n_iov_allocated, n_iov); + } + } + + /* Try to map the kernel fields to our own names */ + for (m = map_fields; m->audit_field; m++) { + v = startswith(p, m->audit_field); + if (!v) + continue; + + r = m->map(m->journal_field, &v, iov, n_iov_allocated, n_iov); + if (r < 0) + return log_debug_errno(r, "Failed to parse audit array: %m"); + + if (r > 0) { + mapped = true; + p = v; + break; + } + } + + if (!mapped) { + r = map_generic_field(prefix, &p, iov, n_iov_allocated, n_iov); + if (r < 0) + return log_debug_errno(r, "Failed to parse audit array: %m"); + + if (r == 0) + /* Couldn't process as generic field, let's just skip over it */ + p += strcspn(p, WHITESPACE); + } + } +} + +static void process_audit_string(Server *s, int type, const char *data, size_t size) { + _cleanup_free_ struct iovec *iov = NULL; + size_t n_iov_allocated = 0; + unsigned n_iov = 0, k; + uint64_t seconds, msec, id; + const char *p, *type_name; + unsigned z; + char id_field[sizeof("_AUDIT_ID=") + DECIMAL_STR_MAX(uint64_t)], + type_field[sizeof("_AUDIT_TYPE=") + DECIMAL_STR_MAX(int)], + source_time_field[sizeof("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)]; + char *m; + + assert(s); + + if (size <= 0) + return; + + if (!data) + return; + + /* Note that the input buffer is NUL terminated, but let's + * check whether there is a spurious NUL byte */ + if (memchr(data, 0, size)) + return; + + p = startswith(data, "audit"); + if (!p) + return; + + if (sscanf(p, "(%" PRIu64 ".%" PRIu64 ":%" PRIu64 "):%n", + &seconds, + &msec, + &id, + &k) != 3) + return; + + p += k; + p += strspn(p, WHITESPACE); + + if (isempty(p)) + return; + + n_iov_allocated = N_IOVEC_META_FIELDS + 7; + iov = new(struct iovec, n_iov_allocated); + if (!iov) { + log_oom(); + return; + } + + IOVEC_SET_STRING(iov[n_iov++], "_TRANSPORT=audit"); + + sprintf(source_time_field, "_SOURCE_REALTIME_TIMESTAMP=%" PRIu64, + (usec_t) seconds * USEC_PER_SEC + (usec_t) msec * USEC_PER_MSEC); + IOVEC_SET_STRING(iov[n_iov++], source_time_field); + + sprintf(type_field, "_AUDIT_TYPE=%i", type); + IOVEC_SET_STRING(iov[n_iov++], type_field); + + sprintf(id_field, "_AUDIT_ID=%" PRIu64, id); + IOVEC_SET_STRING(iov[n_iov++], id_field); + + assert_cc(4 == LOG_FAC(LOG_AUTH)); + IOVEC_SET_STRING(iov[n_iov++], "SYSLOG_FACILITY=4"); + IOVEC_SET_STRING(iov[n_iov++], "SYSLOG_IDENTIFIER=audit"); + + type_name = audit_type_name_alloca(type); + + m = strjoina("MESSAGE=", type_name, " ", p); + IOVEC_SET_STRING(iov[n_iov++], m); + + z = n_iov; + + map_all_fields(p, map_fields_kernel, "_AUDIT_FIELD_", true, &iov, &n_iov_allocated, &n_iov); + + if (!GREEDY_REALLOC(iov, n_iov_allocated, n_iov + N_IOVEC_META_FIELDS)) { + log_oom(); + goto finish; + } + + server_dispatch_message(s, iov, n_iov, n_iov_allocated, NULL, NULL, NULL, 0, NULL, LOG_NOTICE, 0); + +finish: + /* free() all entries that map_all_fields() added. All others + * are allocated on the stack or are constant. */ + + for (; z < n_iov; z++) + free(iov[z].iov_base); +} + +void server_process_audit_message( + Server *s, + const void *buffer, + size_t buffer_size, + const struct ucred *ucred, + const union sockaddr_union *sa, + socklen_t salen) { + + const struct nlmsghdr *nl = buffer; + + assert(s); + + if (buffer_size < ALIGN(sizeof(struct nlmsghdr))) + return; + + assert(buffer); + + /* Filter out fake data */ + if (!sa || + salen != sizeof(struct sockaddr_nl) || + sa->nl.nl_family != AF_NETLINK || + sa->nl.nl_pid != 0) { + log_debug("Audit netlink message from invalid sender."); + return; + } + + if (!ucred || ucred->pid != 0) { + log_debug("Audit netlink message with invalid credentials."); + return; + } + + if (!NLMSG_OK(nl, buffer_size)) { + log_error("Audit netlink message truncated."); + return; + } + + /* Ignore special Netlink messages */ + if (IN_SET(nl->nlmsg_type, NLMSG_NOOP, NLMSG_ERROR)) + return; + + /* Below AUDIT_FIRST_USER_MSG theer are only control messages, let's ignore those */ + if (nl->nlmsg_type < AUDIT_FIRST_USER_MSG) + return; + + process_audit_string(s, nl->nlmsg_type, NLMSG_DATA(nl), nl->nlmsg_len - ALIGN(sizeof(struct nlmsghdr))); +} + +static int enable_audit(int fd, bool b) { + struct { + union { + struct nlmsghdr header; + uint8_t header_space[NLMSG_HDRLEN]; + }; + struct audit_status body; + } _packed_ request = { + .header.nlmsg_len = NLMSG_LENGTH(sizeof(struct audit_status)), + .header.nlmsg_type = AUDIT_SET, + .header.nlmsg_flags = NLM_F_REQUEST, + .header.nlmsg_seq = 1, + .header.nlmsg_pid = 0, + .body.mask = AUDIT_STATUS_ENABLED, + .body.enabled = b, + }; + union sockaddr_union sa = { + .nl.nl_family = AF_NETLINK, + .nl.nl_pid = 0, + }; + struct iovec iovec = { + .iov_base = &request, + .iov_len = NLMSG_LENGTH(sizeof(struct audit_status)), + }; + struct msghdr mh = { + .msg_iov = &iovec, + .msg_iovlen = 1, + .msg_name = &sa.sa, + .msg_namelen = sizeof(sa.nl), + }; + + ssize_t n; + + n = sendmsg(fd, &mh, MSG_NOSIGNAL); + if (n < 0) + return -errno; + if (n != NLMSG_LENGTH(sizeof(struct audit_status))) + return -EIO; + + /* We don't wait for the result here, we can't do anything + * about it anyway */ + + return 0; +} + +int server_open_audit(Server *s) { + static const int one = 1; + int r; + + if (s->audit_fd < 0) { + static const union sockaddr_union sa = { + .nl.nl_family = AF_NETLINK, + .nl.nl_pid = 0, + .nl.nl_groups = AUDIT_NLGRP_READLOG, + }; + + s->audit_fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_AUDIT); + if (s->audit_fd < 0) { + if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) + log_debug("Audit not supported in the kernel."); + else + log_warning_errno(errno, "Failed to create audit socket, ignoring: %m"); + + return 0; + } + + if (bind(s->audit_fd, &sa.sa, sizeof(sa.nl)) < 0) { + log_warning_errno(errno, + "Failed to join audit multicast group. " + "The kernel is probably too old or multicast reading is not supported. " + "Ignoring: %m"); + s->audit_fd = safe_close(s->audit_fd); + return 0; + } + } else + fd_nonblock(s->audit_fd, 1); + + r = setsockopt(s->audit_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); + if (r < 0) + return log_error_errno(errno, "Failed to set SO_PASSCRED on audit socket: %m"); + + r = sd_event_add_io(s->event, &s->audit_event_source, s->audit_fd, EPOLLIN, server_process_datagram, s); + if (r < 0) + return log_error_errno(r, "Failed to add audit fd to event loop: %m"); + + /* We are listening now, try to enable audit */ + r = enable_audit(s->audit_fd, true); + if (r < 0) + log_warning_errno(r, "Failed to issue audit enable call: %m"); + + return 0; +} diff --git a/src/grp-journal/libjournal-core/journald-audit.h b/src/grp-journal/libjournal-core/journald-audit.h new file mode 100644 index 0000000000..8c7457778c --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-audit.h @@ -0,0 +1,27 @@ +#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 "journald-server.h" +#include "socket-util.h" + +void server_process_audit_message(Server *s, const void *buffer, size_t buffer_size, const struct ucred *ucred, const union sockaddr_union *sa, socklen_t salen); + +int server_open_audit(Server*s); diff --git a/src/grp-journal/libjournal-core/journald-console.c b/src/grp-journal/libjournal-core/journald-console.c new file mode 100644 index 0000000000..fcc9f25814 --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-console.c @@ -0,0 +1,115 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "io-util.h" +#include "journald-console.h" +#include "journald-server.h" +#include "parse-util.h" +#include "process-util.h" +#include "stdio-util.h" +#include "terminal-util.h" + +static bool prefix_timestamp(void) { + + static int cached_printk_time = -1; + + if (_unlikely_(cached_printk_time < 0)) { + _cleanup_free_ char *p = NULL; + + cached_printk_time = + read_one_line_file("/sys/module/printk/parameters/time", &p) >= 0 + && parse_boolean(p) > 0; + } + + return cached_printk_time; +} + +void server_forward_console( + Server *s, + int priority, + const char *identifier, + const char *message, + const struct ucred *ucred) { + + struct iovec iovec[5]; + struct timespec ts; + char tbuf[sizeof("[] ")-1 + DECIMAL_STR_MAX(ts.tv_sec) + DECIMAL_STR_MAX(ts.tv_nsec)-3 + 1]; + char header_pid[sizeof("[]: ")-1 + DECIMAL_STR_MAX(pid_t)]; + int n = 0, fd; + _cleanup_free_ char *ident_buf = NULL; + const char *tty; + + assert(s); + assert(message); + + if (LOG_PRI(priority) > s->max_level_console) + return; + + /* First: timestamp */ + if (prefix_timestamp()) { + assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0); + xsprintf(tbuf, "[%5"PRI_TIME".%06ld] ", + ts.tv_sec, + ts.tv_nsec / 1000); + IOVEC_SET_STRING(iovec[n++], tbuf); + } + + /* Second: identifier and PID */ + if (ucred) { + if (!identifier) { + get_process_comm(ucred->pid, &ident_buf); + identifier = ident_buf; + } + + xsprintf(header_pid, "["PID_FMT"]: ", ucred->pid); + + if (identifier) + IOVEC_SET_STRING(iovec[n++], identifier); + + IOVEC_SET_STRING(iovec[n++], header_pid); + } else if (identifier) { + IOVEC_SET_STRING(iovec[n++], identifier); + IOVEC_SET_STRING(iovec[n++], ": "); + } + + /* Fourth: message */ + IOVEC_SET_STRING(iovec[n++], message); + IOVEC_SET_STRING(iovec[n++], "\n"); + + tty = s->tty_path ? s->tty_path : "/dev/console"; + + fd = open_terminal(tty, O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) { + log_debug_errno(fd, "Failed to open %s for logging: %m", tty); + return; + } + + if (writev(fd, iovec, n) < 0) + log_debug_errno(errno, "Failed to write to %s for logging: %m", tty); + + safe_close(fd); +} diff --git a/src/grp-journal/libjournal-core/journald-console.h b/src/grp-journal/libjournal-core/journald-console.h new file mode 100644 index 0000000000..dda07e2c28 --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-console.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 "journald-server.h" + +void server_forward_console(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred); diff --git a/src/grp-journal/libjournal-core/journald-gperf.gperf b/src/grp-journal/libjournal-core/journald-gperf.gperf new file mode 100644 index 0000000000..7fecd7a964 --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-gperf.gperf @@ -0,0 +1,46 @@ +%{ +#include +#include +#include "conf-parser.h" +#include "journald-server.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name journald_gperf_hash +%define lookup-function-name journald_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +Journal.Storage, config_parse_storage, 0, offsetof(Server, storage) +Journal.Compress, config_parse_bool, 0, offsetof(Server, compress) +Journal.Seal, config_parse_bool, 0, offsetof(Server, seal) +Journal.SyncIntervalSec, config_parse_sec, 0, offsetof(Server, sync_interval_usec) +# The following is a legacy name for compatibility +Journal.RateLimitInterval, config_parse_sec, 0, offsetof(Server, rate_limit_interval) +Journal.RateLimitIntervalSec,config_parse_sec, 0, offsetof(Server, rate_limit_interval) +Journal.RateLimitBurst, config_parse_unsigned, 0, offsetof(Server, rate_limit_burst) +Journal.SystemMaxUse, config_parse_iec_uint64, 0, offsetof(Server, system_metrics.max_use) +Journal.SystemMaxFileSize, config_parse_iec_uint64, 0, offsetof(Server, system_metrics.max_size) +Journal.SystemKeepFree, config_parse_iec_uint64, 0, offsetof(Server, system_metrics.keep_free) +Journal.SystemMaxFiles, config_parse_uint64, 0, offsetof(Server, system_metrics.n_max_files) +Journal.RuntimeMaxUse, config_parse_iec_uint64, 0, offsetof(Server, runtime_metrics.max_use) +Journal.RuntimeMaxFileSize, config_parse_iec_uint64, 0, offsetof(Server, runtime_metrics.max_size) +Journal.RuntimeKeepFree, config_parse_iec_uint64, 0, offsetof(Server, runtime_metrics.keep_free) +Journal.RuntimeMaxFiles, config_parse_uint64, 0, offsetof(Server, runtime_metrics.n_max_files) +Journal.MaxRetentionSec, config_parse_sec, 0, offsetof(Server, max_retention_usec) +Journal.MaxFileSec, config_parse_sec, 0, offsetof(Server, max_file_usec) +Journal.ForwardToSyslog, config_parse_bool, 0, offsetof(Server, forward_to_syslog) +Journal.ForwardToKMsg, config_parse_bool, 0, offsetof(Server, forward_to_kmsg) +Journal.ForwardToConsole, config_parse_bool, 0, offsetof(Server, forward_to_console) +Journal.ForwardToWall, config_parse_bool, 0, offsetof(Server, forward_to_wall) +Journal.TTYPath, config_parse_path, 0, offsetof(Server, tty_path) +Journal.MaxLevelStore, config_parse_log_level, 0, offsetof(Server, max_level_store) +Journal.MaxLevelSyslog, config_parse_log_level, 0, offsetof(Server, max_level_syslog) +Journal.MaxLevelKMsg, config_parse_log_level, 0, offsetof(Server, max_level_kmsg) +Journal.MaxLevelConsole, config_parse_log_level, 0, offsetof(Server, max_level_console) +Journal.MaxLevelWall, config_parse_log_level, 0, offsetof(Server, max_level_wall) +Journal.SplitMode, config_parse_split_mode, 0, offsetof(Server, split_mode) diff --git a/src/grp-journal/libjournal-core/journald-kmsg.c b/src/grp-journal/libjournal-core/journald-kmsg.c new file mode 100644 index 0000000000..3712636de2 --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-kmsg.c @@ -0,0 +1,473 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include +#include +#include + +#include "libudev.h" +#include + +#include "escape.h" +#include "fd-util.h" +#include "formats-util.h" +#include "io-util.h" +#include "journald-kmsg.h" +#include "journald-server.h" +#include "journald-syslog.h" +#include "parse-util.h" +#include "process-util.h" +#include "stdio-util.h" +#include "string-util.h" + +void server_forward_kmsg( + Server *s, + int priority, + const char *identifier, + const char *message, + const struct ucred *ucred) { + + struct iovec iovec[5]; + char header_priority[DECIMAL_STR_MAX(priority) + 3], + header_pid[sizeof("[]: ")-1 + DECIMAL_STR_MAX(pid_t) + 1]; + int n = 0; + char *ident_buf = NULL; + + assert(s); + assert(priority >= 0); + assert(priority <= 999); + assert(message); + + if (_unlikely_(LOG_PRI(priority) > s->max_level_kmsg)) + return; + + if (_unlikely_(s->dev_kmsg_fd < 0)) + return; + + /* Never allow messages with kernel facility to be written to + * kmsg, regardless where the data comes from. */ + priority = syslog_fixup_facility(priority); + + /* First: priority field */ + xsprintf(header_priority, "<%i>", priority); + IOVEC_SET_STRING(iovec[n++], header_priority); + + /* Second: identifier and PID */ + if (ucred) { + if (!identifier) { + get_process_comm(ucred->pid, &ident_buf); + identifier = ident_buf; + } + + xsprintf(header_pid, "["PID_FMT"]: ", ucred->pid); + + if (identifier) + IOVEC_SET_STRING(iovec[n++], identifier); + + IOVEC_SET_STRING(iovec[n++], header_pid); + } else if (identifier) { + IOVEC_SET_STRING(iovec[n++], identifier); + IOVEC_SET_STRING(iovec[n++], ": "); + } + + /* Fourth: message */ + IOVEC_SET_STRING(iovec[n++], message); + IOVEC_SET_STRING(iovec[n++], "\n"); + + if (writev(s->dev_kmsg_fd, iovec, n) < 0) + log_debug_errno(errno, "Failed to write to /dev/kmsg for logging: %m"); + + free(ident_buf); +} + +static bool is_us(const char *pid) { + pid_t t; + + assert(pid); + + if (parse_pid(pid, &t) < 0) + return false; + + return t == getpid(); +} + +static void dev_kmsg_record(Server *s, const char *p, size_t l) { + struct iovec iovec[N_IOVEC_META_FIELDS + 7 + N_IOVEC_KERNEL_FIELDS + 2 + N_IOVEC_UDEV_FIELDS]; + char *message = NULL, *syslog_priority = NULL, *syslog_pid = NULL, *syslog_facility = NULL, *syslog_identifier = NULL, *source_time = NULL; + int priority, r; + unsigned n = 0, z = 0, j; + unsigned long long usec; + char *identifier = NULL, *pid = NULL, *e, *f, *k; + uint64_t serial; + size_t pl; + char *kernel_device = NULL; + + assert(s); + assert(p); + + if (l <= 0) + return; + + e = memchr(p, ',', l); + if (!e) + return; + *e = 0; + + r = safe_atoi(p, &priority); + if (r < 0 || priority < 0 || priority > 999) + return; + + if (s->forward_to_kmsg && (priority & LOG_FACMASK) != LOG_KERN) + return; + + l -= (e - p) + 1; + p = e + 1; + e = memchr(p, ',', l); + if (!e) + return; + *e = 0; + + r = safe_atou64(p, &serial); + if (r < 0) + return; + + if (s->kernel_seqnum) { + /* We already read this one? */ + if (serial < *s->kernel_seqnum) + return; + + /* Did we lose any? */ + if (serial > *s->kernel_seqnum) + server_driver_message(s, SD_MESSAGE_JOURNAL_MISSED, + LOG_MESSAGE("Missed %"PRIu64" kernel messages", + serial - *s->kernel_seqnum), + NULL); + + /* Make sure we never read this one again. Note that + * we always store the next message serial we expect + * here, simply because this makes handling the first + * message with serial 0 easy. */ + *s->kernel_seqnum = serial + 1; + } + + l -= (e - p) + 1; + p = e + 1; + f = memchr(p, ';', l); + if (!f) + return; + /* Kernel 3.6 has the flags field, kernel 3.5 lacks that */ + e = memchr(p, ',', l); + if (!e || f < e) + e = f; + *e = 0; + + r = safe_atollu(p, &usec); + if (r < 0) + return; + + l -= (f - p) + 1; + p = f + 1; + e = memchr(p, '\n', l); + if (!e) + return; + *e = 0; + + pl = e - p; + l -= (e - p) + 1; + k = e + 1; + + for (j = 0; l > 0 && j < N_IOVEC_KERNEL_FIELDS; j++) { + char *m; + /* Metadata fields attached */ + + if (*k != ' ') + break; + + k++, l--; + + e = memchr(k, '\n', l); + if (!e) + return; + + *e = 0; + + if (cunescape_length_with_prefix(k, e - k, "_KERNEL_", UNESCAPE_RELAX, &m) < 0) + break; + + if (startswith(m, "_KERNEL_DEVICE=")) + kernel_device = m + 15; + + IOVEC_SET_STRING(iovec[n++], m); + z++; + + l -= (e - k) + 1; + k = e + 1; + } + + if (kernel_device) { + struct udev_device *ud; + + ud = udev_device_new_from_device_id(s->udev, kernel_device); + if (ud) { + const char *g; + struct udev_list_entry *ll; + char *b; + + g = udev_device_get_devnode(ud); + if (g) { + b = strappend("_UDEV_DEVNODE=", g); + if (b) { + IOVEC_SET_STRING(iovec[n++], b); + z++; + } + } + + g = udev_device_get_sysname(ud); + if (g) { + b = strappend("_UDEV_SYSNAME=", g); + if (b) { + IOVEC_SET_STRING(iovec[n++], b); + z++; + } + } + + j = 0; + ll = udev_device_get_devlinks_list_entry(ud); + udev_list_entry_foreach(ll, ll) { + + if (j > N_IOVEC_UDEV_FIELDS) + break; + + g = udev_list_entry_get_name(ll); + if (g) { + b = strappend("_UDEV_DEVLINK=", g); + if (b) { + IOVEC_SET_STRING(iovec[n++], b); + z++; + } + } + + j++; + } + + udev_device_unref(ud); + } + } + + if (asprintf(&source_time, "_SOURCE_MONOTONIC_TIMESTAMP=%llu", usec) >= 0) + IOVEC_SET_STRING(iovec[n++], source_time); + + IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=kernel"); + + if (asprintf(&syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK) >= 0) + IOVEC_SET_STRING(iovec[n++], syslog_priority); + + if (asprintf(&syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)) >= 0) + IOVEC_SET_STRING(iovec[n++], syslog_facility); + + if ((priority & LOG_FACMASK) == LOG_KERN) + IOVEC_SET_STRING(iovec[n++], "SYSLOG_IDENTIFIER=kernel"); + else { + pl -= syslog_parse_identifier((const char**) &p, &identifier, &pid); + + /* Avoid any messages we generated ourselves via + * log_info() and friends. */ + if (pid && is_us(pid)) + goto finish; + + if (identifier) { + syslog_identifier = strappend("SYSLOG_IDENTIFIER=", identifier); + if (syslog_identifier) + IOVEC_SET_STRING(iovec[n++], syslog_identifier); + } + + if (pid) { + syslog_pid = strappend("SYSLOG_PID=", pid); + if (syslog_pid) + IOVEC_SET_STRING(iovec[n++], syslog_pid); + } + } + + if (cunescape_length_with_prefix(p, pl, "MESSAGE=", UNESCAPE_RELAX, &message) >= 0) + IOVEC_SET_STRING(iovec[n++], message); + + server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), NULL, NULL, NULL, 0, NULL, priority, 0); + +finish: + for (j = 0; j < z; j++) + free(iovec[j].iov_base); + + free(message); + free(syslog_priority); + free(syslog_identifier); + free(syslog_pid); + free(syslog_facility); + free(source_time); + free(identifier); + free(pid); +} + +static int server_read_dev_kmsg(Server *s) { + char buffer[8192+1]; /* the kernel-side limit per record is 8K currently */ + ssize_t l; + + assert(s); + assert(s->dev_kmsg_fd >= 0); + + l = read(s->dev_kmsg_fd, buffer, sizeof(buffer) - 1); + if (l == 0) + return 0; + if (l < 0) { + /* Old kernels who don't allow reading from /dev/kmsg + * return EINVAL when we try. So handle this cleanly, + * but don' try to ever read from it again. */ + if (errno == EINVAL) { + s->dev_kmsg_event_source = sd_event_source_unref(s->dev_kmsg_event_source); + return 0; + } + + if (errno == EAGAIN || errno == EINTR || errno == EPIPE) + return 0; + + return log_error_errno(errno, "Failed to read from kernel: %m"); + } + + dev_kmsg_record(s, buffer, l); + return 1; +} + +int server_flush_dev_kmsg(Server *s) { + int r; + + assert(s); + + if (s->dev_kmsg_fd < 0) + return 0; + + if (!s->dev_kmsg_readable) + return 0; + + log_debug("Flushing /dev/kmsg..."); + + for (;;) { + r = server_read_dev_kmsg(s); + if (r < 0) + return r; + + if (r == 0) + break; + } + + return 0; +} + +static int dispatch_dev_kmsg(sd_event_source *es, int fd, uint32_t revents, void *userdata) { + Server *s = userdata; + + assert(es); + assert(fd == s->dev_kmsg_fd); + assert(s); + + if (revents & EPOLLERR) + log_warning("/dev/kmsg buffer overrun, some messages lost."); + + if (!(revents & EPOLLIN)) + log_error("Got invalid event from epoll for /dev/kmsg: %"PRIx32, revents); + + return server_read_dev_kmsg(s); +} + +int server_open_dev_kmsg(Server *s) { + int r; + + assert(s); + + s->dev_kmsg_fd = open("/dev/kmsg", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (s->dev_kmsg_fd < 0) { + log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, + "Failed to open /dev/kmsg, ignoring: %m"); + return 0; + } + + r = sd_event_add_io(s->event, &s->dev_kmsg_event_source, s->dev_kmsg_fd, EPOLLIN, dispatch_dev_kmsg, s); + if (r < 0) { + + /* This will fail with EPERM on older kernels where + * /dev/kmsg is not readable. */ + if (r == -EPERM) { + r = 0; + goto fail; + } + + log_error_errno(r, "Failed to add /dev/kmsg fd to event loop: %m"); + goto fail; + } + + r = sd_event_source_set_priority(s->dev_kmsg_event_source, SD_EVENT_PRIORITY_IMPORTANT+10); + if (r < 0) { + log_error_errno(r, "Failed to adjust priority of kmsg event source: %m"); + goto fail; + } + + s->dev_kmsg_readable = true; + + return 0; + +fail: + s->dev_kmsg_event_source = sd_event_source_unref(s->dev_kmsg_event_source); + s->dev_kmsg_fd = safe_close(s->dev_kmsg_fd); + + return r; +} + +int server_open_kernel_seqnum(Server *s) { + _cleanup_close_ int fd; + uint64_t *p; + int r; + + assert(s); + + /* We store the seqnum we last read in an mmaped file. That + * way we can just use it like a variable, but it is + * persistent and automatically flushed at reboot. */ + + fd = open("/run/systemd/journal/kernel-seqnum", O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0644); + if (fd < 0) { + log_error_errno(errno, "Failed to open /run/systemd/journal/kernel-seqnum, ignoring: %m"); + return 0; + } + + r = posix_fallocate(fd, 0, sizeof(uint64_t)); + if (r != 0) { + log_error_errno(r, "Failed to allocate sequential number file, ignoring: %m"); + return 0; + } + + p = mmap(NULL, sizeof(uint64_t), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + if (p == MAP_FAILED) { + log_error_errno(errno, "Failed to map sequential number file, ignoring: %m"); + return 0; + } + + s->kernel_seqnum = p; + + return 0; +} diff --git a/src/grp-journal/libjournal-core/journald-kmsg.h b/src/grp-journal/libjournal-core/journald-kmsg.h new file mode 100644 index 0000000000..dab49f1e8c --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-kmsg.h @@ -0,0 +1,29 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 "journald-server.h" + +int server_open_dev_kmsg(Server *s); +int server_flush_dev_kmsg(Server *s); + +void server_forward_kmsg(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred); + +int server_open_kernel_seqnum(Server *s); diff --git a/src/grp-journal/libjournal-core/journald-native.c b/src/grp-journal/libjournal-core/journald-native.c new file mode 100644 index 0000000000..0a1ce205c2 --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-native.c @@ -0,0 +1,501 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "io-util.h" +#include "journald-console.h" +#include "journald-kmsg.h" +#include "journald-native.h" +#include "journald-server.h" +#include "journald-syslog.h" +#include "journald-wall.h" +#include "memfd-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "selinux-util.h" +#include "socket-util.h" +#include "string-util.h" + +bool valid_user_field(const char *p, size_t l, bool allow_protected) { + const char *a; + + /* We kinda enforce POSIX syntax recommendations for + environment variables here, but make a couple of additional + requirements. + + http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html */ + + /* No empty field names */ + if (l <= 0) + return false; + + /* Don't allow names longer than 64 chars */ + if (l > 64) + return false; + + /* Variables starting with an underscore are protected */ + if (!allow_protected && p[0] == '_') + return false; + + /* Don't allow digits as first character */ + if (p[0] >= '0' && p[0] <= '9') + return false; + + /* Only allow A-Z0-9 and '_' */ + for (a = p; a < p + l; a++) + if ((*a < 'A' || *a > 'Z') && + (*a < '0' || *a > '9') && + *a != '_') + return false; + + return true; +} + +static bool allow_object_pid(const struct ucred *ucred) { + return ucred && ucred->uid == 0; +} + +void server_process_native_message( + Server *s, + const void *buffer, size_t buffer_size, + const struct ucred *ucred, + const struct timeval *tv, + const char *label, size_t label_len) { + + struct iovec *iovec = NULL; + unsigned n = 0, j, tn = (unsigned) -1; + const char *p; + size_t remaining, m = 0, entry_size = 0; + int priority = LOG_INFO; + char *identifier = NULL, *message = NULL; + pid_t object_pid = 0; + + assert(s); + assert(buffer || buffer_size == 0); + + p = buffer; + remaining = buffer_size; + + while (remaining > 0) { + const char *e, *q; + + e = memchr(p, '\n', remaining); + + if (!e) { + /* Trailing noise, let's ignore it, and flush what we collected */ + log_debug("Received message with trailing noise, ignoring."); + break; + } + + if (e == p) { + /* Entry separator */ + + if (entry_size + n + 1 > ENTRY_SIZE_MAX) { /* data + separators + trailer */ + log_debug("Entry is too big with %u properties and %zu bytes, ignoring.", n, entry_size); + continue; + } + + server_dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, NULL, priority, object_pid); + n = 0; + priority = LOG_INFO; + entry_size = 0; + + p++; + remaining--; + continue; + } + + if (*p == '.' || *p == '#') { + /* Ignore control commands for now, and + * comments too. */ + remaining -= (e - p) + 1; + p = e + 1; + continue; + } + + /* A property follows */ + + /* n existing properties, 1 new, +1 for _TRANSPORT */ + if (!GREEDY_REALLOC(iovec, m, n + 2 + N_IOVEC_META_FIELDS + N_IOVEC_OBJECT_FIELDS)) { + log_oom(); + break; + } + + q = memchr(p, '=', e - p); + if (q) { + if (valid_user_field(p, q - p, false)) { + size_t l; + + l = e - p; + + /* If the field name starts with an + * underscore, skip the variable, + * since that indidates a trusted + * field */ + iovec[n].iov_base = (char*) p; + iovec[n].iov_len = l; + entry_size += iovec[n].iov_len; + n++; + + /* We need to determine the priority + * of this entry for the rate limiting + * logic */ + if (l == 10 && + startswith(p, "PRIORITY=") && + p[9] >= '0' && p[9] <= '9') + priority = (priority & LOG_FACMASK) | (p[9] - '0'); + + else if (l == 17 && + startswith(p, "SYSLOG_FACILITY=") && + p[16] >= '0' && p[16] <= '9') + priority = (priority & LOG_PRIMASK) | ((p[16] - '0') << 3); + + else if (l == 18 && + startswith(p, "SYSLOG_FACILITY=") && + p[16] >= '0' && p[16] <= '9' && + p[17] >= '0' && p[17] <= '9') + priority = (priority & LOG_PRIMASK) | (((p[16] - '0')*10 + (p[17] - '0')) << 3); + + else if (l >= 19 && + startswith(p, "SYSLOG_IDENTIFIER=")) { + char *t; + + t = strndup(p + 18, l - 18); + if (t) { + free(identifier); + identifier = t; + } + + } else if (l >= 8 && + startswith(p, "MESSAGE=")) { + char *t; + + t = strndup(p + 8, l - 8); + if (t) { + free(message); + message = t; + } + + } else if (l > strlen("OBJECT_PID=") && + l < strlen("OBJECT_PID=") + DECIMAL_STR_MAX(pid_t) && + startswith(p, "OBJECT_PID=") && + allow_object_pid(ucred)) { + char buf[DECIMAL_STR_MAX(pid_t)]; + memcpy(buf, p + strlen("OBJECT_PID="), l - strlen("OBJECT_PID=")); + buf[l-strlen("OBJECT_PID=")] = '\0'; + + /* ignore error */ + parse_pid(buf, &object_pid); + } + } + + remaining -= (e - p) + 1; + p = e + 1; + continue; + } else { + le64_t l_le; + uint64_t l; + char *k; + + if (remaining < e - p + 1 + sizeof(uint64_t) + 1) { + log_debug("Failed to parse message, ignoring."); + break; + } + + memcpy(&l_le, e + 1, sizeof(uint64_t)); + l = le64toh(l_le); + + if (l > DATA_SIZE_MAX) { + log_debug("Received binary data block of %"PRIu64" bytes is too large, ignoring.", l); + break; + } + + if ((uint64_t) remaining < e - p + 1 + sizeof(uint64_t) + l + 1 || + e[1+sizeof(uint64_t)+l] != '\n') { + log_debug("Failed to parse message, ignoring."); + break; + } + + k = malloc((e - p) + 1 + l); + if (!k) { + log_oom(); + break; + } + + memcpy(k, p, e - p); + k[e - p] = '='; + memcpy(k + (e - p) + 1, e + 1 + sizeof(uint64_t), l); + + if (valid_user_field(p, e - p, false)) { + iovec[n].iov_base = k; + iovec[n].iov_len = (e - p) + 1 + l; + entry_size += iovec[n].iov_len; + n++; + } else + free(k); + + remaining -= (e - p) + 1 + sizeof(uint64_t) + l + 1; + p = e + 1 + sizeof(uint64_t) + l + 1; + } + } + + if (n <= 0) + goto finish; + + tn = n++; + IOVEC_SET_STRING(iovec[tn], "_TRANSPORT=journal"); + entry_size += strlen("_TRANSPORT=journal"); + + if (entry_size + n + 1 > ENTRY_SIZE_MAX) { /* data + separators + trailer */ + log_debug("Entry is too big with %u properties and %zu bytes, ignoring.", + n, entry_size); + goto finish; + } + + if (message) { + if (s->forward_to_syslog) + server_forward_syslog(s, priority, identifier, message, ucred, tv); + + if (s->forward_to_kmsg) + server_forward_kmsg(s, priority, identifier, message, ucred); + + if (s->forward_to_console) + server_forward_console(s, priority, identifier, message, ucred); + + if (s->forward_to_wall) + server_forward_wall(s, priority, identifier, message, ucred); + } + + server_dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, NULL, priority, object_pid); + +finish: + for (j = 0; j < n; j++) { + if (j == tn) + continue; + + if (iovec[j].iov_base < buffer || + (const uint8_t*) iovec[j].iov_base >= (const uint8_t*) buffer + buffer_size) + free(iovec[j].iov_base); + } + + free(iovec); + free(identifier); + free(message); +} + +void server_process_native_file( + Server *s, + int fd, + const struct ucred *ucred, + const struct timeval *tv, + const char *label, size_t label_len) { + + struct stat st; + bool sealed; + int r; + + /* Data is in the passed fd, since it didn't fit in a + * datagram. */ + + assert(s); + assert(fd >= 0); + + /* If it's a memfd, check if it is sealed. If so, we can just + * use map it and use it, and do not need to copy the data + * out. */ + sealed = memfd_get_sealed(fd) > 0; + + if (!sealed && (!ucred || ucred->uid != 0)) { + _cleanup_free_ char *sl = NULL, *k = NULL; + const char *e; + + /* If this is not a sealed memfd, and the peer is unknown or + * unprivileged, then verify the path. */ + + if (asprintf(&sl, "/proc/self/fd/%i", fd) < 0) { + log_oom(); + return; + } + + r = readlink_malloc(sl, &k); + if (r < 0) { + log_error_errno(r, "readlink(%s) failed: %m", sl); + return; + } + + e = path_startswith(k, "/dev/shm/"); + if (!e) + e = path_startswith(k, "/tmp/"); + if (!e) + e = path_startswith(k, "/var/tmp/"); + if (!e) { + log_error("Received file outside of allowed directories. Refusing."); + return; + } + + if (!filename_is_valid(e)) { + log_error("Received file in subdirectory of allowed directories. Refusing."); + return; + } + } + + if (fstat(fd, &st) < 0) { + log_error_errno(errno, "Failed to stat passed file, ignoring: %m"); + return; + } + + if (!S_ISREG(st.st_mode)) { + log_error("File passed is not regular. Ignoring."); + return; + } + + if (st.st_size <= 0) + return; + + if (st.st_size > ENTRY_SIZE_MAX) { + log_error("File passed too large. Ignoring."); + return; + } + + if (sealed) { + void *p; + size_t ps; + + /* The file is sealed, we can just map it and use it. */ + + ps = PAGE_ALIGN(st.st_size); + p = mmap(NULL, ps, PROT_READ, MAP_PRIVATE, fd, 0); + if (p == MAP_FAILED) { + log_error_errno(errno, "Failed to map memfd, ignoring: %m"); + return; + } + + server_process_native_message(s, p, st.st_size, ucred, tv, label, label_len); + assert_se(munmap(p, ps) >= 0); + } else { + _cleanup_free_ void *p = NULL; + struct statvfs vfs; + ssize_t n; + + if (fstatvfs(fd, &vfs) < 0) { + log_error_errno(errno, "Failed to stat file system of passed file, ignoring: %m"); + return; + } + + /* Refuse operating on file systems that have + * mandatory locking enabled, see: + * + * https://github.com/systemd/systemd/issues/1822 + */ + if (vfs.f_flag & ST_MANDLOCK) { + log_error("Received file descriptor from file system with mandatory locking enable, refusing."); + return; + } + + /* Make the fd non-blocking. On regular files this has + * the effect of bypassing mandatory locking. Of + * course, this should normally not be necessary given + * the check above, but let's better be safe than + * sorry, after all NFS is pretty confusing regarding + * file system flags, and we better don't trust it, + * and so is SMB. */ + r = fd_nonblock(fd, true); + if (r < 0) { + log_error_errno(r, "Failed to make fd non-blocking, ignoring: %m"); + return; + } + + /* The file is not sealed, we can't map the file here, since + * clients might then truncate it and trigger a SIGBUS for + * us. So let's stupidly read it */ + + p = malloc(st.st_size); + if (!p) { + log_oom(); + return; + } + + n = pread(fd, p, st.st_size, 0); + if (n < 0) + log_error_errno(errno, "Failed to read file, ignoring: %m"); + else if (n > 0) + server_process_native_message(s, p, n, ucred, tv, label, label_len); + } +} + +int server_open_native_socket(Server*s) { + + static const union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/journal/socket", + }; + static const int one = 1; + int r; + + assert(s); + + if (s->native_fd < 0) { + s->native_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (s->native_fd < 0) + return log_error_errno(errno, "socket() failed: %m"); + + (void) unlink(sa.un.sun_path); + + r = bind(s->native_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); + if (r < 0) + return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path); + + (void) chmod(sa.un.sun_path, 0666); + } else + fd_nonblock(s->native_fd, 1); + + r = setsockopt(s->native_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); + if (r < 0) + return log_error_errno(errno, "SO_PASSCRED failed: %m"); + +#ifdef HAVE_SELINUX + if (mac_selinux_have()) { + r = setsockopt(s->native_fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one)); + if (r < 0) + log_warning_errno(errno, "SO_PASSSEC failed: %m"); + } +#endif + + r = setsockopt(s->native_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)); + if (r < 0) + return log_error_errno(errno, "SO_TIMESTAMP failed: %m"); + + r = sd_event_add_io(s->event, &s->native_event_source, s->native_fd, EPOLLIN, server_process_datagram, s); + if (r < 0) + return log_error_errno(r, "Failed to add native server fd to event loop: %m"); + + r = sd_event_source_set_priority(s->native_event_source, SD_EVENT_PRIORITY_NORMAL+5); + if (r < 0) + return log_error_errno(r, "Failed to adjust native event source priority: %m"); + + return 0; +} diff --git a/src/grp-journal/libjournal-core/journald-native.h b/src/grp-journal/libjournal-core/journald-native.h new file mode 100644 index 0000000000..c13b80aa4f --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-native.h @@ -0,0 +1,35 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 "journald-server.h" + +/* Make sure not to make this smaller than the maximum coredump + * size. See COREDUMP_MAX in coredump.c */ +#define ENTRY_SIZE_MAX (1024*1024*770u) +#define DATA_SIZE_MAX (1024*1024*768u) + +bool valid_user_field(const char *p, size_t l, bool allow_protected); + +void server_process_native_message(Server *s, const void *buffer, size_t buffer_size, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len); + +void server_process_native_file(Server *s, int fd, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len); + +int server_open_native_socket(Server*s); diff --git a/src/grp-journal/libjournal-core/journald-rate-limit.c b/src/grp-journal/libjournal-core/journald-rate-limit.c new file mode 100644 index 0000000000..fce799a6ce --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-rate-limit.c @@ -0,0 +1,271 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 + +#include "alloc-util.h" +#include "hashmap.h" +#include "journald-rate-limit.h" +#include "list.h" +#include "random-util.h" +#include "string-util.h" +#include "util.h" + +#define POOLS_MAX 5 +#define BUCKETS_MAX 127 +#define GROUPS_MAX 2047 + +static const int priority_map[] = { + [LOG_EMERG] = 0, + [LOG_ALERT] = 0, + [LOG_CRIT] = 0, + [LOG_ERR] = 1, + [LOG_WARNING] = 2, + [LOG_NOTICE] = 3, + [LOG_INFO] = 3, + [LOG_DEBUG] = 4 +}; + +typedef struct JournalRateLimitPool JournalRateLimitPool; +typedef struct JournalRateLimitGroup JournalRateLimitGroup; + +struct JournalRateLimitPool { + usec_t begin; + unsigned num; + unsigned suppressed; +}; + +struct JournalRateLimitGroup { + JournalRateLimit *parent; + + char *id; + JournalRateLimitPool pools[POOLS_MAX]; + uint64_t hash; + + LIST_FIELDS(JournalRateLimitGroup, bucket); + LIST_FIELDS(JournalRateLimitGroup, lru); +}; + +struct JournalRateLimit { + usec_t interval; + unsigned burst; + + JournalRateLimitGroup* buckets[BUCKETS_MAX]; + JournalRateLimitGroup *lru, *lru_tail; + + unsigned n_groups; + + uint8_t hash_key[16]; +}; + +JournalRateLimit *journal_rate_limit_new(usec_t interval, unsigned burst) { + JournalRateLimit *r; + + assert(interval > 0 || burst == 0); + + r = new0(JournalRateLimit, 1); + if (!r) + return NULL; + + r->interval = interval; + r->burst = burst; + + random_bytes(r->hash_key, sizeof(r->hash_key)); + + return r; +} + +static void journal_rate_limit_group_free(JournalRateLimitGroup *g) { + assert(g); + + if (g->parent) { + assert(g->parent->n_groups > 0); + + if (g->parent->lru_tail == g) + g->parent->lru_tail = g->lru_prev; + + LIST_REMOVE(lru, g->parent->lru, g); + LIST_REMOVE(bucket, g->parent->buckets[g->hash % BUCKETS_MAX], g); + + g->parent->n_groups--; + } + + free(g->id); + free(g); +} + +void journal_rate_limit_free(JournalRateLimit *r) { + assert(r); + + while (r->lru) + journal_rate_limit_group_free(r->lru); + + free(r); +} + +_pure_ static bool journal_rate_limit_group_expired(JournalRateLimitGroup *g, usec_t ts) { + unsigned i; + + assert(g); + + for (i = 0; i < POOLS_MAX; i++) + if (g->pools[i].begin + g->parent->interval >= ts) + return false; + + return true; +} + +static void journal_rate_limit_vacuum(JournalRateLimit *r, usec_t ts) { + assert(r); + + /* Makes room for at least one new item, but drop all + * expored items too. */ + + while (r->n_groups >= GROUPS_MAX || + (r->lru_tail && journal_rate_limit_group_expired(r->lru_tail, ts))) + journal_rate_limit_group_free(r->lru_tail); +} + +static JournalRateLimitGroup* journal_rate_limit_group_new(JournalRateLimit *r, const char *id, usec_t ts) { + JournalRateLimitGroup *g; + struct siphash state; + + assert(r); + assert(id); + + g = new0(JournalRateLimitGroup, 1); + if (!g) + return NULL; + + g->id = strdup(id); + if (!g->id) + goto fail; + + siphash24_init(&state, r->hash_key); + string_hash_func(g->id, &state); + g->hash = siphash24_finalize(&state); + + journal_rate_limit_vacuum(r, ts); + + LIST_PREPEND(bucket, r->buckets[g->hash % BUCKETS_MAX], g); + LIST_PREPEND(lru, r->lru, g); + if (!g->lru_next) + r->lru_tail = g; + r->n_groups++; + + g->parent = r; + return g; + +fail: + journal_rate_limit_group_free(g); + return NULL; +} + +static unsigned burst_modulate(unsigned burst, uint64_t available) { + unsigned k; + + /* Modulates the burst rate a bit with the amount of available + * disk space */ + + k = u64log2(available); + + /* 1MB */ + if (k <= 20) + return burst; + + burst = (burst * (k-20)) / 4; + + /* + * Example: + * + * <= 1MB = rate * 1 + * 16MB = rate * 2 + * 256MB = rate * 3 + * 4GB = rate * 4 + * 64GB = rate * 5 + * 1TB = rate * 6 + */ + + return burst; +} + +int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, uint64_t available) { + uint64_t h; + JournalRateLimitGroup *g; + JournalRateLimitPool *p; + struct siphash state; + unsigned burst; + usec_t ts; + + assert(id); + + if (!r) + return 1; + + if (r->interval == 0 || r->burst == 0) + return 1; + + burst = burst_modulate(r->burst, available); + + ts = now(CLOCK_MONOTONIC); + + siphash24_init(&state, r->hash_key); + string_hash_func(id, &state); + h = siphash24_finalize(&state); + g = r->buckets[h % BUCKETS_MAX]; + + LIST_FOREACH(bucket, g, g) + if (streq(g->id, id)) + break; + + if (!g) { + g = journal_rate_limit_group_new(r, id, ts); + if (!g) + return -ENOMEM; + } + + p = &g->pools[priority_map[priority]]; + + if (p->begin <= 0) { + p->suppressed = 0; + p->num = 1; + p->begin = ts; + return 1; + } + + if (p->begin + r->interval < ts) { + unsigned s; + + s = p->suppressed; + p->suppressed = 0; + p->num = 1; + p->begin = ts; + + return 1 + s; + } + + if (p->num <= burst) { + p->num++; + return 1; + } + + p->suppressed++; + return 0; +} diff --git a/src/grp-journal/libjournal-core/journald-rate-limit.h b/src/grp-journal/libjournal-core/journald-rate-limit.h new file mode 100644 index 0000000000..bb0abb7ee9 --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-rate-limit.h @@ -0,0 +1,28 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 struct JournalRateLimit JournalRateLimit; + +JournalRateLimit *journal_rate_limit_new(usec_t interval, unsigned burst); +void journal_rate_limit_free(JournalRateLimit *r); +int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, uint64_t available); diff --git a/src/grp-journal/libjournal-core/journald-server.c b/src/grp-journal/libjournal-core/journald-server.c new file mode 100644 index 0000000000..cc29443e66 --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-server.c @@ -0,0 +1,2007 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#ifdef HAVE_SELINUX +#include +#endif +#include +#include +#include +#include +#include + +#include "libudev.h" +#include +#include +#include + +#include "acl-util.h" +#include "alloc-util.h" +#include "audit-util.h" +#include "cgroup-util.h" +#include "conf-parser.h" +#include "dirent-util.h" +#include "extract-word.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "hashmap.h" +#include "hostname-util.h" +#include "io-util.h" +#include "journal-authenticate.h" +#include "journal-file.h" +#include "journal-internal.h" +#include "journal-vacuum.h" +#include "journald-audit.h" +#include "journald-kmsg.h" +#include "journald-native.h" +#include "journald-rate-limit.h" +#include "journald-server.h" +#include "journald-stream.h" +#include "journald-syslog.h" +#include "missing.h" +#include "mkdir.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "process-util.h" +#include "rm-rf.h" +#include "selinux-util.h" +#include "signal-util.h" +#include "socket-util.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" +#include "user-util.h" +#include "log.h" + +#define USER_JOURNALS_MAX 1024 + +#define DEFAULT_SYNC_INTERVAL_USEC (5*USEC_PER_MINUTE) +#define DEFAULT_RATE_LIMIT_INTERVAL (30*USEC_PER_SEC) +#define DEFAULT_RATE_LIMIT_BURST 1000 +#define DEFAULT_MAX_FILE_USEC USEC_PER_MONTH + +#define RECHECK_SPACE_USEC (30*USEC_PER_SEC) + +#define NOTIFY_SNDBUF_SIZE (8*1024*1024) + +/* The period to insert between posting changes for coalescing */ +#define POST_CHANGE_TIMER_INTERVAL_USEC (250*USEC_PER_MSEC) + +static int determine_space_for( + Server *s, + JournalMetrics *metrics, + const char *path, + const char *name, + bool verbose, + bool patch_min_use, + uint64_t *available, + uint64_t *limit) { + + uint64_t sum = 0, ss_avail, avail; + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + struct statvfs ss; + const char *p; + usec_t ts; + + assert(s); + assert(metrics); + assert(path); + assert(name); + + ts = now(CLOCK_MONOTONIC); + + if (!verbose && s->cached_space_timestamp + RECHECK_SPACE_USEC > ts) { + + if (available) + *available = s->cached_space_available; + if (limit) + *limit = s->cached_space_limit; + + return 0; + } + + p = strjoina(path, SERVER_MACHINE_ID(s)); + d = opendir(p); + if (!d) + return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open %s: %m", p); + + if (fstatvfs(dirfd(d), &ss) < 0) + return log_error_errno(errno, "Failed to fstatvfs(%s): %m", p); + + FOREACH_DIRENT_ALL(de, d, break) { + struct stat st; + + if (!endswith(de->d_name, ".journal") && + !endswith(de->d_name, ".journal~")) + continue; + + if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { + log_debug_errno(errno, "Failed to stat %s/%s, ignoring: %m", p, de->d_name); + continue; + } + + if (!S_ISREG(st.st_mode)) + continue; + + sum += (uint64_t) st.st_blocks * 512UL; + } + + /* If requested, then let's bump the min_use limit to the + * current usage on disk. We do this when starting up and + * first opening the journal files. This way sudden spikes in + * disk usage will not cause journald to vacuum files without + * bounds. Note that this means that only a restart of + * journald will make it reset this value. */ + + if (patch_min_use) + metrics->min_use = MAX(metrics->min_use, sum); + + ss_avail = ss.f_bsize * ss.f_bavail; + avail = LESS_BY(ss_avail, metrics->keep_free); + + s->cached_space_limit = MIN(MAX(sum + avail, metrics->min_use), metrics->max_use); + s->cached_space_available = LESS_BY(s->cached_space_limit, sum); + s->cached_space_timestamp = ts; + + if (verbose) { + char fb1[FORMAT_BYTES_MAX], fb2[FORMAT_BYTES_MAX], fb3[FORMAT_BYTES_MAX], + fb4[FORMAT_BYTES_MAX], fb5[FORMAT_BYTES_MAX], fb6[FORMAT_BYTES_MAX]; + format_bytes(fb1, sizeof(fb1), sum); + format_bytes(fb2, sizeof(fb2), metrics->max_use); + format_bytes(fb3, sizeof(fb3), metrics->keep_free); + format_bytes(fb4, sizeof(fb4), ss_avail); + format_bytes(fb5, sizeof(fb5), s->cached_space_limit); + format_bytes(fb6, sizeof(fb6), s->cached_space_available); + + server_driver_message(s, SD_MESSAGE_JOURNAL_USAGE, + LOG_MESSAGE("%s (%s) is %s, max %s, %s free.", + name, path, fb1, fb5, fb6), + "JOURNAL_NAME=%s", name, + "JOURNAL_PATH=%s", path, + "CURRENT_USE=%"PRIu64, sum, + "CURRENT_USE_PRETTY=%s", fb1, + "MAX_USE=%"PRIu64, metrics->max_use, + "MAX_USE_PRETTY=%s", fb2, + "DISK_KEEP_FREE=%"PRIu64, metrics->keep_free, + "DISK_KEEP_FREE_PRETTY=%s", fb3, + "DISK_AVAILABLE=%"PRIu64, ss_avail, + "DISK_AVAILABLE_PRETTY=%s", fb4, + "LIMIT=%"PRIu64, s->cached_space_limit, + "LIMIT_PRETTY=%s", fb5, + "AVAILABLE=%"PRIu64, s->cached_space_available, + "AVAILABLE_PRETTY=%s", fb6, + NULL); + } + + if (available) + *available = s->cached_space_available; + if (limit) + *limit = s->cached_space_limit; + + return 1; +} + +static int determine_space(Server *s, bool verbose, bool patch_min_use, uint64_t *available, uint64_t *limit) { + JournalMetrics *metrics; + const char *path, *name; + + assert(s); + + if (s->system_journal) { + path = "/var/log/journal/"; + metrics = &s->system_metrics; + name = "System journal"; + } else { + path = "/run/log/journal/"; + metrics = &s->runtime_metrics; + name = "Runtime journal"; + } + + return determine_space_for(s, metrics, path, name, verbose, patch_min_use, available, limit); +} + +static void server_add_acls(JournalFile *f, uid_t uid) { +#ifdef HAVE_ACL + int r; +#endif + assert(f); + +#ifdef HAVE_ACL + if (uid <= SYSTEM_UID_MAX) + return; + + r = add_acls_for_user(f->fd, uid); + if (r < 0) + log_warning_errno(r, "Failed to set ACL on %s, ignoring: %m", f->path); +#endif +} + +static int open_journal( + Server *s, + bool reliably, + const char *fname, + int flags, + bool seal, + JournalMetrics *metrics, + JournalFile **ret) { + int r; + JournalFile *f; + + assert(s); + assert(fname); + assert(ret); + + if (reliably) + r = journal_file_open_reliably(fname, flags, 0640, s->compress, seal, metrics, s->mmap, s->deferred_closes, NULL, &f); + else + r = journal_file_open(-1, fname, flags, 0640, s->compress, seal, metrics, s->mmap, s->deferred_closes, NULL, &f); + if (r < 0) + return r; + + r = journal_file_enable_post_change_timer(f, s->event, POST_CHANGE_TIMER_INTERVAL_USEC); + if (r < 0) { + (void) journal_file_close(f); + return r; + } + + *ret = f; + return r; +} + +static JournalFile* find_journal(Server *s, uid_t uid) { + _cleanup_free_ char *p = NULL; + int r; + JournalFile *f; + sd_id128_t machine; + + assert(s); + + /* We split up user logs only on /var, not on /run. If the + * runtime file is open, we write to it exclusively, in order + * to guarantee proper order as soon as we flush /run to + * /var and close the runtime file. */ + + if (s->runtime_journal) + return s->runtime_journal; + + if (uid <= SYSTEM_UID_MAX) + return s->system_journal; + + r = sd_id128_get_machine(&machine); + if (r < 0) + return s->system_journal; + + f = ordered_hashmap_get(s->user_journals, UID_TO_PTR(uid)); + if (f) + return f; + + if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/user-"UID_FMT".journal", + SD_ID128_FORMAT_VAL(machine), uid) < 0) + return s->system_journal; + + while (ordered_hashmap_size(s->user_journals) >= USER_JOURNALS_MAX) { + /* Too many open? Then let's close one */ + f = ordered_hashmap_steal_first(s->user_journals); + assert(f); + (void) journal_file_close(f); + } + + r = open_journal(s, true, p, O_RDWR|O_CREAT, s->seal, &s->system_metrics, &f); + if (r < 0) + return s->system_journal; + + server_add_acls(f, uid); + + r = ordered_hashmap_put(s->user_journals, UID_TO_PTR(uid), f); + if (r < 0) { + (void) journal_file_close(f); + return s->system_journal; + } + + return f; +} + +static int do_rotate( + Server *s, + JournalFile **f, + const char* name, + bool seal, + uint32_t uid) { + + int r; + assert(s); + + if (!*f) + return -EINVAL; + + r = journal_file_rotate(f, s->compress, seal, s->deferred_closes); + if (r < 0) + if (*f) + log_error_errno(r, "Failed to rotate %s: %m", (*f)->path); + else + log_error_errno(r, "Failed to create new %s journal: %m", name); + else + server_add_acls(*f, uid); + + return r; +} + +void server_rotate(Server *s) { + JournalFile *f; + void *k; + Iterator i; + int r; + + log_debug("Rotating..."); + + (void) do_rotate(s, &s->runtime_journal, "runtime", false, 0); + (void) do_rotate(s, &s->system_journal, "system", s->seal, 0); + + ORDERED_HASHMAP_FOREACH_KEY(f, k, s->user_journals, i) { + r = do_rotate(s, &f, "user", s->seal, PTR_TO_UID(k)); + if (r >= 0) + ordered_hashmap_replace(s->user_journals, k, f); + else if (!f) + /* Old file has been closed and deallocated */ + ordered_hashmap_remove(s->user_journals, k); + } + + /* Perform any deferred closes which aren't still offlining. */ + SET_FOREACH(f, s->deferred_closes, i) + if (!journal_file_is_offlining(f)) { + (void) set_remove(s->deferred_closes, f); + (void) journal_file_close(f); + } +} + +void server_sync(Server *s) { + JournalFile *f; + Iterator i; + int r; + + if (s->system_journal) { + r = journal_file_set_offline(s->system_journal, false); + if (r < 0) + log_warning_errno(r, "Failed to sync system journal, ignoring: %m"); + } + + ORDERED_HASHMAP_FOREACH(f, s->user_journals, i) { + r = journal_file_set_offline(f, false); + if (r < 0) + log_warning_errno(r, "Failed to sync user journal, ignoring: %m"); + } + + if (s->sync_event_source) { + r = sd_event_source_set_enabled(s->sync_event_source, SD_EVENT_OFF); + if (r < 0) + log_error_errno(r, "Failed to disable sync timer source: %m"); + } + + s->sync_scheduled = false; +} + +static void do_vacuum( + Server *s, + JournalFile *f, + JournalMetrics *metrics, + const char *path, + const char *name, + bool verbose, + bool patch_min_use) { + + const char *p; + uint64_t limit; + int r; + + assert(s); + assert(metrics); + assert(path); + assert(name); + + if (!f) + return; + + p = strjoina(path, SERVER_MACHINE_ID(s)); + + limit = metrics->max_use; + (void) determine_space_for(s, metrics, path, name, verbose, patch_min_use, NULL, &limit); + + r = journal_directory_vacuum(p, limit, metrics->n_max_files, s->max_retention_usec, &s->oldest_file_usec, verbose); + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to vacuum %s, ignoring: %m", p); +} + +int server_vacuum(Server *s, bool verbose, bool patch_min_use) { + assert(s); + + log_debug("Vacuuming..."); + + s->oldest_file_usec = 0; + + do_vacuum(s, s->system_journal, &s->system_metrics, "/var/log/journal/", "System journal", verbose, patch_min_use); + do_vacuum(s, s->runtime_journal, &s->runtime_metrics, "/run/log/journal/", "Runtime journal", verbose, patch_min_use); + + s->cached_space_limit = 0; + s->cached_space_available = 0; + s->cached_space_timestamp = 0; + + return 0; +} + +static void server_cache_machine_id(Server *s) { + sd_id128_t id; + int r; + + assert(s); + + r = sd_id128_get_machine(&id); + if (r < 0) + return; + + sd_id128_to_string(id, stpcpy(s->machine_id_field, "_MACHINE_ID=")); +} + +static void server_cache_boot_id(Server *s) { + sd_id128_t id; + int r; + + assert(s); + + r = sd_id128_get_boot(&id); + if (r < 0) + return; + + sd_id128_to_string(id, stpcpy(s->boot_id_field, "_BOOT_ID=")); +} + +static void server_cache_hostname(Server *s) { + _cleanup_free_ char *t = NULL; + char *x; + + assert(s); + + t = gethostname_malloc(); + if (!t) + return; + + x = strappend("_HOSTNAME=", t); + if (!x) + return; + + free(s->hostname_field); + s->hostname_field = x; +} + +static bool shall_try_append_again(JournalFile *f, int r) { + switch(r) { + case -E2BIG: /* Hit configured limit */ + case -EFBIG: /* Hit fs limit */ + case -EDQUOT: /* Quota limit hit */ + case -ENOSPC: /* Disk full */ + log_debug("%s: Allocation limit reached, rotating.", f->path); + return true; + case -EIO: /* I/O error of some kind (mmap) */ + log_warning("%s: IO error, rotating.", f->path); + return true; + case -EHOSTDOWN: /* Other machine */ + log_info("%s: Journal file from other machine, rotating.", f->path); + return true; + case -EBUSY: /* Unclean shutdown */ + log_info("%s: Unclean shutdown, rotating.", f->path); + return true; + case -EPROTONOSUPPORT: /* Unsupported feature */ + log_info("%s: Unsupported feature, rotating.", f->path); + return true; + case -EBADMSG: /* Corrupted */ + case -ENODATA: /* Truncated */ + case -ESHUTDOWN: /* Already archived */ + log_warning("%s: Journal file corrupted, rotating.", f->path); + return true; + case -EIDRM: /* Journal file has been deleted */ + log_warning("%s: Journal file has been deleted, rotating.", f->path); + return true; + default: + return false; + } +} + +static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, unsigned n, int priority) { + JournalFile *f; + bool vacuumed = false; + int r; + + assert(s); + assert(iovec); + assert(n > 0); + + f = find_journal(s, uid); + if (!f) + return; + + if (journal_file_rotate_suggested(f, s->max_file_usec)) { + log_debug("%s: Journal header limits reached or header out-of-date, rotating.", f->path); + server_rotate(s); + server_vacuum(s, false, false); + vacuumed = true; + + f = find_journal(s, uid); + if (!f) + return; + } + + r = journal_file_append_entry(f, NULL, iovec, n, &s->seqnum, NULL, NULL); + if (r >= 0) { + server_schedule_sync(s, priority); + return; + } + + if (vacuumed || !shall_try_append_again(f, r)) { + log_error_errno(r, "Failed to write entry (%d items, %zu bytes), ignoring: %m", n, IOVEC_TOTAL_SIZE(iovec, n)); + return; + } + + server_rotate(s); + server_vacuum(s, false, false); + + f = find_journal(s, uid); + if (!f) + return; + + log_debug("Retrying write."); + r = journal_file_append_entry(f, NULL, iovec, n, &s->seqnum, NULL, NULL); + if (r < 0) + log_error_errno(r, "Failed to write entry (%d items, %zu bytes) despite vacuuming, ignoring: %m", n, IOVEC_TOTAL_SIZE(iovec, n)); + else + server_schedule_sync(s, priority); +} + +static void dispatch_message_real( + Server *s, + struct iovec *iovec, unsigned n, unsigned m, + const struct ucred *ucred, + const struct timeval *tv, + const char *label, size_t label_len, + const char *unit_id, + int priority, + pid_t object_pid) { + + char pid[sizeof("_PID=") + DECIMAL_STR_MAX(pid_t)], + uid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)], + gid[sizeof("_GID=") + DECIMAL_STR_MAX(gid_t)], + owner_uid[sizeof("_SYSTEMD_OWNER_UID=") + DECIMAL_STR_MAX(uid_t)], + source_time[sizeof("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)], + o_uid[sizeof("OBJECT_UID=") + DECIMAL_STR_MAX(uid_t)], + o_gid[sizeof("OBJECT_GID=") + DECIMAL_STR_MAX(gid_t)], + o_owner_uid[sizeof("OBJECT_SYSTEMD_OWNER_UID=") + DECIMAL_STR_MAX(uid_t)]; + uid_t object_uid; + gid_t object_gid; + char *x; + int r; + char *t, *c; + uid_t realuid = 0, owner = 0, journal_uid; + bool owner_valid = false; +#ifdef HAVE_AUDIT + char audit_session[sizeof("_AUDIT_SESSION=") + DECIMAL_STR_MAX(uint32_t)], + audit_loginuid[sizeof("_AUDIT_LOGINUID=") + DECIMAL_STR_MAX(uid_t)], + o_audit_session[sizeof("OBJECT_AUDIT_SESSION=") + DECIMAL_STR_MAX(uint32_t)], + o_audit_loginuid[sizeof("OBJECT_AUDIT_LOGINUID=") + DECIMAL_STR_MAX(uid_t)]; + + uint32_t audit; + uid_t loginuid; +#endif + + assert(s); + assert(iovec); + assert(n > 0); + assert(n + N_IOVEC_META_FIELDS + (object_pid ? N_IOVEC_OBJECT_FIELDS : 0) <= m); + + if (ucred) { + realuid = ucred->uid; + + sprintf(pid, "_PID="PID_FMT, ucred->pid); + IOVEC_SET_STRING(iovec[n++], pid); + + sprintf(uid, "_UID="UID_FMT, ucred->uid); + IOVEC_SET_STRING(iovec[n++], uid); + + sprintf(gid, "_GID="GID_FMT, ucred->gid); + IOVEC_SET_STRING(iovec[n++], gid); + + r = get_process_comm(ucred->pid, &t); + if (r >= 0) { + x = strjoina("_COMM=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + + r = get_process_exe(ucred->pid, &t); + if (r >= 0) { + x = strjoina("_EXE=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + + r = get_process_cmdline(ucred->pid, 0, false, &t); + if (r >= 0) { + x = strjoina("_CMDLINE=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + + r = get_process_capeff(ucred->pid, &t); + if (r >= 0) { + x = strjoina("_CAP_EFFECTIVE=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + +#ifdef HAVE_AUDIT + r = audit_session_from_pid(ucred->pid, &audit); + if (r >= 0) { + sprintf(audit_session, "_AUDIT_SESSION=%"PRIu32, audit); + IOVEC_SET_STRING(iovec[n++], audit_session); + } + + r = audit_loginuid_from_pid(ucred->pid, &loginuid); + if (r >= 0) { + sprintf(audit_loginuid, "_AUDIT_LOGINUID="UID_FMT, loginuid); + IOVEC_SET_STRING(iovec[n++], audit_loginuid); + } +#endif + + r = cg_pid_get_path_shifted(ucred->pid, s->cgroup_root, &c); + if (r >= 0) { + char *session = NULL; + + x = strjoina("_SYSTEMD_CGROUP=", c); + IOVEC_SET_STRING(iovec[n++], x); + + r = cg_path_get_session(c, &t); + if (r >= 0) { + session = strjoina("_SYSTEMD_SESSION=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], session); + } + + if (cg_path_get_owner_uid(c, &owner) >= 0) { + owner_valid = true; + + sprintf(owner_uid, "_SYSTEMD_OWNER_UID="UID_FMT, owner); + IOVEC_SET_STRING(iovec[n++], owner_uid); + } + + if (cg_path_get_unit(c, &t) >= 0) { + x = strjoina("_SYSTEMD_UNIT=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } else if (unit_id && !session) { + x = strjoina("_SYSTEMD_UNIT=", unit_id); + IOVEC_SET_STRING(iovec[n++], x); + } + + if (cg_path_get_user_unit(c, &t) >= 0) { + x = strjoina("_SYSTEMD_USER_UNIT=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } else if (unit_id && session) { + x = strjoina("_SYSTEMD_USER_UNIT=", unit_id); + IOVEC_SET_STRING(iovec[n++], x); + } + + if (cg_path_get_slice(c, &t) >= 0) { + x = strjoina("_SYSTEMD_SLICE=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + + free(c); + } else if (unit_id) { + x = strjoina("_SYSTEMD_UNIT=", unit_id); + IOVEC_SET_STRING(iovec[n++], x); + } + +#ifdef HAVE_SELINUX + if (mac_selinux_have()) { + if (label) { + x = alloca(strlen("_SELINUX_CONTEXT=") + label_len + 1); + + *((char*) mempcpy(stpcpy(x, "_SELINUX_CONTEXT="), label, label_len)) = 0; + IOVEC_SET_STRING(iovec[n++], x); + } else { + security_context_t con; + + if (getpidcon(ucred->pid, &con) >= 0) { + x = strjoina("_SELINUX_CONTEXT=", con); + + freecon(con); + IOVEC_SET_STRING(iovec[n++], x); + } + } + } +#endif + } + assert(n <= m); + + if (object_pid) { + r = get_process_uid(object_pid, &object_uid); + if (r >= 0) { + sprintf(o_uid, "OBJECT_UID="UID_FMT, object_uid); + IOVEC_SET_STRING(iovec[n++], o_uid); + } + + r = get_process_gid(object_pid, &object_gid); + if (r >= 0) { + sprintf(o_gid, "OBJECT_GID="GID_FMT, object_gid); + IOVEC_SET_STRING(iovec[n++], o_gid); + } + + r = get_process_comm(object_pid, &t); + if (r >= 0) { + x = strjoina("OBJECT_COMM=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + + r = get_process_exe(object_pid, &t); + if (r >= 0) { + x = strjoina("OBJECT_EXE=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + + r = get_process_cmdline(object_pid, 0, false, &t); + if (r >= 0) { + x = strjoina("OBJECT_CMDLINE=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + +#ifdef HAVE_AUDIT + r = audit_session_from_pid(object_pid, &audit); + if (r >= 0) { + sprintf(o_audit_session, "OBJECT_AUDIT_SESSION=%"PRIu32, audit); + IOVEC_SET_STRING(iovec[n++], o_audit_session); + } + + r = audit_loginuid_from_pid(object_pid, &loginuid); + if (r >= 0) { + sprintf(o_audit_loginuid, "OBJECT_AUDIT_LOGINUID="UID_FMT, loginuid); + IOVEC_SET_STRING(iovec[n++], o_audit_loginuid); + } +#endif + + r = cg_pid_get_path_shifted(object_pid, s->cgroup_root, &c); + if (r >= 0) { + x = strjoina("OBJECT_SYSTEMD_CGROUP=", c); + IOVEC_SET_STRING(iovec[n++], x); + + r = cg_path_get_session(c, &t); + if (r >= 0) { + x = strjoina("OBJECT_SYSTEMD_SESSION=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + + if (cg_path_get_owner_uid(c, &owner) >= 0) { + sprintf(o_owner_uid, "OBJECT_SYSTEMD_OWNER_UID="UID_FMT, owner); + IOVEC_SET_STRING(iovec[n++], o_owner_uid); + } + + if (cg_path_get_unit(c, &t) >= 0) { + x = strjoina("OBJECT_SYSTEMD_UNIT=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + + if (cg_path_get_user_unit(c, &t) >= 0) { + x = strjoina("OBJECT_SYSTEMD_USER_UNIT=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + + free(c); + } + } + assert(n <= m); + + if (tv) { + sprintf(source_time, "_SOURCE_REALTIME_TIMESTAMP=%llu", (unsigned long long) timeval_load(tv)); + IOVEC_SET_STRING(iovec[n++], source_time); + } + + /* Note that strictly speaking storing the boot id here is + * redundant since the entry includes this in-line + * anyway. However, we need this indexed, too. */ + if (!isempty(s->boot_id_field)) + IOVEC_SET_STRING(iovec[n++], s->boot_id_field); + + if (!isempty(s->machine_id_field)) + IOVEC_SET_STRING(iovec[n++], s->machine_id_field); + + if (!isempty(s->hostname_field)) + IOVEC_SET_STRING(iovec[n++], s->hostname_field); + + assert(n <= m); + + if (s->split_mode == SPLIT_UID && realuid > 0) + /* Split up strictly by any UID */ + journal_uid = realuid; + else if (s->split_mode == SPLIT_LOGIN && realuid > 0 && owner_valid && owner > 0) + /* Split up by login UIDs. We do this only if the + * realuid is not root, in order not to accidentally + * leak privileged information to the user that is + * logged by a privileged process that is part of an + * unprivileged session. */ + journal_uid = owner; + else + journal_uid = 0; + + write_to_journal(s, journal_uid, iovec, n, priority); +} + +void server_driver_message(Server *s, sd_id128_t message_id, const char *format, ...) { + char mid[11 + 32 + 1]; + struct iovec iovec[N_IOVEC_META_FIELDS + 5 + N_IOVEC_PAYLOAD_FIELDS]; + unsigned n = 0, m; + int r; + va_list ap; + struct ucred ucred = {}; + + assert(s); + assert(format); + + assert_cc(3 == LOG_FAC(LOG_DAEMON)); + IOVEC_SET_STRING(iovec[n++], "SYSLOG_FACILITY=3"); + IOVEC_SET_STRING(iovec[n++], "SYSLOG_IDENTIFIER=systemd-journald"); + + IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=driver"); + assert_cc(6 == LOG_INFO); + IOVEC_SET_STRING(iovec[n++], "PRIORITY=6"); + + if (!sd_id128_equal(message_id, SD_ID128_NULL)) { + snprintf(mid, sizeof(mid), LOG_MESSAGE_ID(message_id)); + IOVEC_SET_STRING(iovec[n++], mid); + } + + m = n; + + va_start(ap, format); + r = log_format_iovec(iovec, ELEMENTSOF(iovec), &n, false, 0, format, ap); + /* Error handling below */ + va_end(ap); + + ucred.pid = getpid(); + ucred.uid = getuid(); + ucred.gid = getgid(); + + if (r >= 0) + dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL, LOG_INFO, 0); + + while (m < n) + free(iovec[m++].iov_base); + + if (r < 0) { + /* We failed to format the message. Emit a warning instead. */ + char buf[LINE_MAX]; + + xsprintf(buf, "MESSAGE=Entry printing failed: %s", strerror(-r)); + + n = 3; + IOVEC_SET_STRING(iovec[n++], "PRIORITY=4"); + IOVEC_SET_STRING(iovec[n++], buf); + dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL, LOG_INFO, 0); + } +} + +void server_dispatch_message( + Server *s, + struct iovec *iovec, unsigned n, unsigned m, + const struct ucred *ucred, + const struct timeval *tv, + const char *label, size_t label_len, + const char *unit_id, + int priority, + pid_t object_pid) { + + int rl, r; + _cleanup_free_ char *path = NULL; + uint64_t available = 0; + char *c; + + assert(s); + assert(iovec || n == 0); + + if (n == 0) + return; + + if (LOG_PRI(priority) > s->max_level_store) + return; + + /* Stop early in case the information will not be stored + * in a journal. */ + if (s->storage == STORAGE_NONE) + return; + + if (!ucred) + goto finish; + + r = cg_pid_get_path_shifted(ucred->pid, s->cgroup_root, &path); + if (r < 0) + goto finish; + + /* example: /user/lennart/3/foobar + * /system/dbus.service/foobar + * + * So let's cut of everything past the third /, since that is + * where user directories start */ + + c = strchr(path, '/'); + if (c) { + c = strchr(c+1, '/'); + if (c) { + c = strchr(c+1, '/'); + if (c) + *c = 0; + } + } + + (void) determine_space(s, false, false, &available, NULL); + rl = journal_rate_limit_test(s->rate_limit, path, priority & LOG_PRIMASK, available); + if (rl == 0) + return; + + /* Write a suppression message if we suppressed something */ + if (rl > 1) + server_driver_message(s, SD_MESSAGE_JOURNAL_DROPPED, + LOG_MESSAGE("Suppressed %u messages from %s", rl - 1, path), + NULL); + +finish: + dispatch_message_real(s, iovec, n, m, ucred, tv, label, label_len, unit_id, priority, object_pid); +} + + +static int system_journal_open(Server *s, bool flush_requested) { + const char *fn; + int r = 0; + + if (!s->system_journal && + (s->storage == STORAGE_PERSISTENT || s->storage == STORAGE_AUTO) && + (flush_requested + || access("/run/systemd/journal/flushed", F_OK) >= 0)) { + + /* If in auto mode: first try to create the machine + * path, but not the prefix. + * + * If in persistent mode: create /var/log/journal and + * the machine path */ + + if (s->storage == STORAGE_PERSISTENT) + (void) mkdir_p("/var/log/journal/", 0755); + + fn = strjoina("/var/log/journal/", SERVER_MACHINE_ID(s)); + (void) mkdir(fn, 0755); + + fn = strjoina(fn, "/system.journal"); + r = open_journal(s, true, fn, O_RDWR|O_CREAT, s->seal, &s->system_metrics, &s->system_journal); + if (r >= 0) { + server_add_acls(s->system_journal, 0); + (void) determine_space_for(s, &s->system_metrics, "/var/log/journal/", "System journal", true, true, NULL, NULL); + } else if (r < 0) { + if (r != -ENOENT && r != -EROFS) + log_warning_errno(r, "Failed to open system journal: %m"); + + r = 0; + } + } + + if (!s->runtime_journal && + (s->storage != STORAGE_NONE)) { + + fn = strjoina("/run/log/journal/", SERVER_MACHINE_ID(s), "/system.journal"); + + if (s->system_journal) { + + /* Try to open the runtime journal, but only + * if it already exists, so that we can flush + * it into the system journal */ + + r = open_journal(s, false, fn, O_RDWR, false, &s->runtime_metrics, &s->runtime_journal); + if (r < 0) { + if (r != -ENOENT) + log_warning_errno(r, "Failed to open runtime journal: %m"); + + r = 0; + } + + } else { + + /* OK, we really need the runtime journal, so create + * it if necessary. */ + + (void) mkdir("/run/log", 0755); + (void) mkdir("/run/log/journal", 0755); + (void) mkdir_parents(fn, 0750); + + r = open_journal(s, true, fn, O_RDWR|O_CREAT, false, &s->runtime_metrics, &s->runtime_journal); + if (r < 0) + return log_error_errno(r, "Failed to open runtime journal: %m"); + } + + if (s->runtime_journal) { + server_add_acls(s->runtime_journal, 0); + (void) determine_space_for(s, &s->runtime_metrics, "/run/log/journal/", "Runtime journal", true, true, NULL, NULL); + } + } + + return r; +} + +int server_flush_to_var(Server *s) { + sd_id128_t machine; + sd_journal *j = NULL; + char ts[FORMAT_TIMESPAN_MAX]; + usec_t start; + unsigned n = 0; + int r; + + assert(s); + + if (s->storage != STORAGE_AUTO && + s->storage != STORAGE_PERSISTENT) + return 0; + + if (!s->runtime_journal) + return 0; + + (void) system_journal_open(s, true); + + if (!s->system_journal) + return 0; + + log_debug("Flushing to /var..."); + + start = now(CLOCK_MONOTONIC); + + r = sd_id128_get_machine(&machine); + if (r < 0) + return r; + + r = sd_journal_open(&j, SD_JOURNAL_RUNTIME_ONLY); + if (r < 0) + return log_error_errno(r, "Failed to read runtime journal: %m"); + + sd_journal_set_data_threshold(j, 0); + + SD_JOURNAL_FOREACH(j) { + Object *o = NULL; + JournalFile *f; + + f = j->current_file; + assert(f && f->current_offset > 0); + + n++; + + r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); + if (r < 0) { + log_error_errno(r, "Can't read entry: %m"); + goto finish; + } + + r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset, NULL, NULL, NULL); + if (r >= 0) + continue; + + if (!shall_try_append_again(s->system_journal, r)) { + log_error_errno(r, "Can't write entry: %m"); + goto finish; + } + + server_rotate(s); + server_vacuum(s, false, false); + + if (!s->system_journal) { + log_notice("Didn't flush runtime journal since rotation of system journal wasn't successful."); + r = -EIO; + goto finish; + } + + log_debug("Retrying write."); + r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset, NULL, NULL, NULL); + if (r < 0) { + log_error_errno(r, "Can't write entry: %m"); + goto finish; + } + } + + r = 0; + +finish: + journal_file_post_change(s->system_journal); + + s->runtime_journal = journal_file_close(s->runtime_journal); + + if (r >= 0) + (void) rm_rf("/run/log/journal", REMOVE_ROOT); + + sd_journal_close(j); + + server_driver_message(s, SD_ID128_NULL, + LOG_MESSAGE("Time spent on flushing to /var is %s for %u entries.", + format_timespan(ts, sizeof(ts), now(CLOCK_MONOTONIC) - start, 0), + n), + NULL); + + return r; +} + +int server_process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userdata) { + Server *s = userdata; + struct ucred *ucred = NULL; + struct timeval *tv = NULL; + struct cmsghdr *cmsg; + char *label = NULL; + size_t label_len = 0, m; + struct iovec iovec; + ssize_t n; + int *fds = NULL, v = 0; + unsigned n_fds = 0; + + union { + struct cmsghdr cmsghdr; + + /* We use NAME_MAX space for the SELinux label + * here. The kernel currently enforces no + * limit, but according to suggestions from + * the SELinux people this will change and it + * will probably be identical to NAME_MAX. For + * now we use that, but this should be updated + * one day when the final limit is known. */ + uint8_t buf[CMSG_SPACE(sizeof(struct ucred)) + + CMSG_SPACE(sizeof(struct timeval)) + + CMSG_SPACE(sizeof(int)) + /* fd */ + CMSG_SPACE(NAME_MAX)]; /* selinux label */ + } control = {}; + + union sockaddr_union sa = {}; + + struct msghdr msghdr = { + .msg_iov = &iovec, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + .msg_name = &sa, + .msg_namelen = sizeof(sa), + }; + + assert(s); + assert(fd == s->native_fd || fd == s->syslog_fd || fd == s->audit_fd); + + if (revents != EPOLLIN) { + log_error("Got invalid event from epoll for datagram fd: %"PRIx32, revents); + return -EIO; + } + + /* Try to get the right size, if we can. (Not all + * sockets support SIOCINQ, hence we just try, but + * don't rely on it. */ + (void) ioctl(fd, SIOCINQ, &v); + + /* Fix it up, if it is too small. We use the same fixed value as auditd here. Awful! */ + m = PAGE_ALIGN(MAX3((size_t) v + 1, + (size_t) LINE_MAX, + ALIGN(sizeof(struct nlmsghdr)) + ALIGN((size_t) MAX_AUDIT_MESSAGE_LENGTH)) + 1); + + if (!GREEDY_REALLOC(s->buffer, s->buffer_size, m)) + return log_oom(); + + iovec.iov_base = s->buffer; + iovec.iov_len = s->buffer_size - 1; /* Leave room for trailing NUL we add later */ + + n = recvmsg(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); + if (n < 0) { + if (errno == EINTR || errno == EAGAIN) + return 0; + + return log_error_errno(errno, "recvmsg() failed: %m"); + } + + CMSG_FOREACH(cmsg, &msghdr) { + + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_CREDENTIALS && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) + ucred = (struct ucred*) CMSG_DATA(cmsg); + else if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_SECURITY) { + label = (char*) CMSG_DATA(cmsg); + label_len = cmsg->cmsg_len - CMSG_LEN(0); + } else if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SO_TIMESTAMP && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) + tv = (struct timeval*) CMSG_DATA(cmsg); + else if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + fds = (int*) CMSG_DATA(cmsg); + n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + } + } + + /* And a trailing NUL, just in case */ + s->buffer[n] = 0; + + if (fd == s->syslog_fd) { + if (n > 0 && n_fds == 0) + server_process_syslog_message(s, strstrip(s->buffer), ucred, tv, label, label_len); + else if (n_fds > 0) + log_warning("Got file descriptors via syslog socket. Ignoring."); + + } else if (fd == s->native_fd) { + if (n > 0 && n_fds == 0) + server_process_native_message(s, s->buffer, n, ucred, tv, label, label_len); + else if (n == 0 && n_fds == 1) + server_process_native_file(s, fds[0], ucred, tv, label, label_len); + else if (n_fds > 0) + log_warning("Got too many file descriptors via native socket. Ignoring."); + + } else { + assert(fd == s->audit_fd); + + if (n > 0 && n_fds == 0) + server_process_audit_message(s, s->buffer, n, ucred, &sa, msghdr.msg_namelen); + else if (n_fds > 0) + log_warning("Got file descriptors via audit socket. Ignoring."); + } + + close_many(fds, n_fds); + return 0; +} + +static int dispatch_sigusr1(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) { + Server *s = userdata; + int r; + + assert(s); + + log_info("Received request to flush runtime journal from PID " PID_FMT, si->ssi_pid); + + server_flush_to_var(s); + server_sync(s); + server_vacuum(s, false, false); + + r = touch("/run/systemd/journal/flushed"); + if (r < 0) + log_warning_errno(r, "Failed to touch /run/systemd/journal/flushed, ignoring: %m"); + + return 0; +} + +static int dispatch_sigusr2(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) { + Server *s = userdata; + int r; + + assert(s); + + log_info("Received request to rotate journal from PID " PID_FMT, si->ssi_pid); + server_rotate(s); + server_vacuum(s, true, true); + + /* Let clients know when the most recent rotation happened. */ + r = write_timestamp_file_atomic("/run/systemd/journal/rotated", now(CLOCK_MONOTONIC)); + if (r < 0) + log_warning_errno(r, "Failed to write /run/systemd/journal/rotated, ignoring: %m"); + + return 0; +} + +static int dispatch_sigterm(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) { + Server *s = userdata; + + assert(s); + + log_received_signal(LOG_INFO, si); + + sd_event_exit(s->event, 0); + return 0; +} + +static int dispatch_sigrtmin1(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) { + Server *s = userdata; + int r; + + assert(s); + + log_debug("Received request to sync from PID " PID_FMT, si->ssi_pid); + + server_sync(s); + + /* Let clients know when the most recent sync happened. */ + r = write_timestamp_file_atomic("/run/systemd/journal/synced", now(CLOCK_MONOTONIC)); + if (r < 0) + log_warning_errno(r, "Failed to write /run/systemd/journal/synced, ignoring: %m"); + + return 0; +} + +static int setup_signals(Server *s) { + int r; + + assert(s); + + assert(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, SIGUSR1, SIGUSR2, SIGRTMIN+1, -1) >= 0); + + r = sd_event_add_signal(s->event, &s->sigusr1_event_source, SIGUSR1, dispatch_sigusr1, s); + if (r < 0) + return r; + + r = sd_event_add_signal(s->event, &s->sigusr2_event_source, SIGUSR2, dispatch_sigusr2, s); + if (r < 0) + return r; + + r = sd_event_add_signal(s->event, &s->sigterm_event_source, SIGTERM, dispatch_sigterm, s); + if (r < 0) + return r; + + /* Let's process SIGTERM late, so that we flush all queued + * messages to disk before we exit */ + r = sd_event_source_set_priority(s->sigterm_event_source, SD_EVENT_PRIORITY_NORMAL+20); + if (r < 0) + return r; + + /* When journald is invoked on the terminal (when debugging), + * it's useful if C-c is handled equivalent to SIGTERM. */ + r = sd_event_add_signal(s->event, &s->sigint_event_source, SIGINT, dispatch_sigterm, s); + if (r < 0) + return r; + + r = sd_event_source_set_priority(s->sigint_event_source, SD_EVENT_PRIORITY_NORMAL+20); + if (r < 0) + return r; + + /* SIGRTMIN+1 causes an immediate sync. We process this very + * late, so that everything else queued at this point is + * really written to disk. Clients can watch + * /run/systemd/journal/synced with inotify until its mtime + * changes to see when a sync happened. */ + r = sd_event_add_signal(s->event, &s->sigrtmin1_event_source, SIGRTMIN+1, dispatch_sigrtmin1, s); + if (r < 0) + return r; + + r = sd_event_source_set_priority(s->sigrtmin1_event_source, SD_EVENT_PRIORITY_NORMAL+15); + if (r < 0) + return r; + + return 0; +} + +static int server_parse_proc_cmdline(Server *s) { + _cleanup_free_ char *line = NULL; + const char *p; + int r; + + r = proc_cmdline(&line); + if (r < 0) { + log_warning_errno(r, "Failed to read /proc/cmdline, ignoring: %m"); + return 0; + } + + p = line; + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, NULL, 0); + if (r < 0) + return log_error_errno(r, "Failed to parse journald syntax \"%s\": %m", line); + + if (r == 0) + break; + + if (startswith(word, "systemd.journald.forward_to_syslog=")) { + r = parse_boolean(word + 35); + if (r < 0) + log_warning("Failed to parse forward to syslog switch %s. Ignoring.", word + 35); + else + s->forward_to_syslog = r; + } else if (startswith(word, "systemd.journald.forward_to_kmsg=")) { + r = parse_boolean(word + 33); + if (r < 0) + log_warning("Failed to parse forward to kmsg switch %s. Ignoring.", word + 33); + else + s->forward_to_kmsg = r; + } else if (startswith(word, "systemd.journald.forward_to_console=")) { + r = parse_boolean(word + 36); + if (r < 0) + log_warning("Failed to parse forward to console switch %s. Ignoring.", word + 36); + else + s->forward_to_console = r; + } else if (startswith(word, "systemd.journald.forward_to_wall=")) { + r = parse_boolean(word + 33); + if (r < 0) + log_warning("Failed to parse forward to wall switch %s. Ignoring.", word + 33); + else + s->forward_to_wall = r; + } else if (startswith(word, "systemd.journald")) + log_warning("Invalid systemd.journald parameter. Ignoring."); + } + + /* do not warn about state here, since probably systemd already did */ + return 0; +} + +static int server_parse_config_file(Server *s) { + assert(s); + + return config_parse_many(PKGSYSCONFDIR "/journald.conf", + CONF_PATHS_NULSTR("systemd/journald.conf.d"), + "Journal\0", + config_item_perf_lookup, journald_gperf_lookup, + false, s); +} + +static int server_dispatch_sync(sd_event_source *es, usec_t t, void *userdata) { + Server *s = userdata; + + assert(s); + + server_sync(s); + return 0; +} + +int server_schedule_sync(Server *s, int priority) { + int r; + + assert(s); + + if (priority <= LOG_CRIT) { + /* Immediately sync to disk when this is of priority CRIT, ALERT, EMERG */ + server_sync(s); + return 0; + } + + if (s->sync_scheduled) + return 0; + + if (s->sync_interval_usec > 0) { + usec_t when; + + r = sd_event_now(s->event, CLOCK_MONOTONIC, &when); + if (r < 0) + return r; + + when += s->sync_interval_usec; + + if (!s->sync_event_source) { + r = sd_event_add_time( + s->event, + &s->sync_event_source, + CLOCK_MONOTONIC, + when, 0, + server_dispatch_sync, s); + if (r < 0) + return r; + + r = sd_event_source_set_priority(s->sync_event_source, SD_EVENT_PRIORITY_IMPORTANT); + } else { + r = sd_event_source_set_time(s->sync_event_source, when); + if (r < 0) + return r; + + r = sd_event_source_set_enabled(s->sync_event_source, SD_EVENT_ONESHOT); + } + if (r < 0) + return r; + + s->sync_scheduled = true; + } + + return 0; +} + +static int dispatch_hostname_change(sd_event_source *es, int fd, uint32_t revents, void *userdata) { + Server *s = userdata; + + assert(s); + + server_cache_hostname(s); + return 0; +} + +static int server_open_hostname(Server *s) { + int r; + + assert(s); + + s->hostname_fd = open("/proc/sys/kernel/hostname", O_RDONLY|O_CLOEXEC|O_NDELAY|O_NOCTTY); + if (s->hostname_fd < 0) + return log_error_errno(errno, "Failed to open /proc/sys/kernel/hostname: %m"); + + r = sd_event_add_io(s->event, &s->hostname_event_source, s->hostname_fd, 0, dispatch_hostname_change, s); + if (r < 0) { + /* kernels prior to 3.2 don't support polling this file. Ignore + * the failure. */ + if (r == -EPERM) { + log_warning_errno(r, "Failed to register hostname fd in event loop, ignoring: %m"); + s->hostname_fd = safe_close(s->hostname_fd); + return 0; + } + + return log_error_errno(r, "Failed to register hostname fd in event loop: %m"); + } + + r = sd_event_source_set_priority(s->hostname_event_source, SD_EVENT_PRIORITY_IMPORTANT-10); + if (r < 0) + return log_error_errno(r, "Failed to adjust priority of host name event source: %m"); + + return 0; +} + +static int dispatch_notify_event(sd_event_source *es, int fd, uint32_t revents, void *userdata) { + Server *s = userdata; + int r; + + assert(s); + assert(s->notify_event_source == es); + assert(s->notify_fd == fd); + + /* The $NOTIFY_SOCKET is writable again, now send exactly one + * message on it. Either it's the wtachdog event, the initial + * READY=1 event or an stdout stream event. If there's nothing + * to write anymore, turn our event source off. The next time + * there's something to send it will be turned on again. */ + + if (!s->sent_notify_ready) { + static const char p[] = + "READY=1\n" + "STATUS=Processing requests..."; + ssize_t l; + + l = send(s->notify_fd, p, strlen(p), MSG_DONTWAIT); + if (l < 0) { + if (errno == EAGAIN) + return 0; + + return log_error_errno(errno, "Failed to send READY=1 notification message: %m"); + } + + s->sent_notify_ready = true; + log_debug("Sent READY=1 notification."); + + } else if (s->send_watchdog) { + + static const char p[] = + "WATCHDOG=1"; + + ssize_t l; + + l = send(s->notify_fd, p, strlen(p), MSG_DONTWAIT); + if (l < 0) { + if (errno == EAGAIN) + return 0; + + return log_error_errno(errno, "Failed to send WATCHDOG=1 notification message: %m"); + } + + s->send_watchdog = false; + log_debug("Sent WATCHDOG=1 notification."); + + } else if (s->stdout_streams_notify_queue) + /* Dispatch one stream notification event */ + stdout_stream_send_notify(s->stdout_streams_notify_queue); + + /* Leave us enabled if there's still more to to do. */ + if (s->send_watchdog || s->stdout_streams_notify_queue) + return 0; + + /* There was nothing to do anymore, let's turn ourselves off. */ + r = sd_event_source_set_enabled(es, SD_EVENT_OFF); + if (r < 0) + return log_error_errno(r, "Failed to turn off notify event source: %m"); + + return 0; +} + +static int dispatch_watchdog(sd_event_source *es, uint64_t usec, void *userdata) { + Server *s = userdata; + int r; + + assert(s); + + s->send_watchdog = true; + + r = sd_event_source_set_enabled(s->notify_event_source, SD_EVENT_ON); + if (r < 0) + log_warning_errno(r, "Failed to turn on notify event source: %m"); + + r = sd_event_source_set_time(s->watchdog_event_source, usec + s->watchdog_usec / 2); + if (r < 0) + return log_error_errno(r, "Failed to restart watchdog event source: %m"); + + r = sd_event_source_set_enabled(s->watchdog_event_source, SD_EVENT_ON); + if (r < 0) + return log_error_errno(r, "Failed to enable watchdog event source: %m"); + + return 0; +} + +static int server_connect_notify(Server *s) { + union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + }; + const char *e; + int r; + + assert(s); + assert(s->notify_fd < 0); + assert(!s->notify_event_source); + + /* + So here's the problem: we'd like to send notification + messages to PID 1, but we cannot do that via sd_notify(), + since that's synchronous, and we might end up blocking on + it. Specifically: given that PID 1 might block on + dbus-daemon during IPC, and dbus-daemon is logging to us, + and might hence block on us, we might end up in a deadlock + if we block on sending PID 1 notification messages — by + generating a full blocking circle. To avoid this, let's + create a non-blocking socket, and connect it to the + notification socket, and then wait for POLLOUT before we + send anything. This should efficiently avoid any deadlocks, + as we'll never block on PID 1, hence PID 1 can safely block + on dbus-daemon which can safely block on us again. + + Don't think that this issue is real? It is, see: + https://github.com/systemd/systemd/issues/1505 + */ + + e = getenv("NOTIFY_SOCKET"); + if (!e) + return 0; + + if ((e[0] != '@' && e[0] != '/') || e[1] == 0) { + log_error("NOTIFY_SOCKET set to an invalid value: %s", e); + return -EINVAL; + } + + if (strlen(e) > sizeof(sa.un.sun_path)) { + log_error("NOTIFY_SOCKET path too long: %s", e); + return -EINVAL; + } + + s->notify_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (s->notify_fd < 0) + return log_error_errno(errno, "Failed to create notify socket: %m"); + + (void) fd_inc_sndbuf(s->notify_fd, NOTIFY_SNDBUF_SIZE); + + strncpy(sa.un.sun_path, e, sizeof(sa.un.sun_path)); + if (sa.un.sun_path[0] == '@') + sa.un.sun_path[0] = 0; + + r = connect(s->notify_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); + if (r < 0) + return log_error_errno(errno, "Failed to connect to notify socket: %m"); + + r = sd_event_add_io(s->event, &s->notify_event_source, s->notify_fd, EPOLLOUT, dispatch_notify_event, s); + if (r < 0) + return log_error_errno(r, "Failed to watch notification socket: %m"); + + if (sd_watchdog_enabled(false, &s->watchdog_usec) > 0) { + s->send_watchdog = true; + + r = sd_event_add_time(s->event, &s->watchdog_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + s->watchdog_usec/2, s->watchdog_usec/4, dispatch_watchdog, s); + if (r < 0) + return log_error_errno(r, "Failed to add watchdog time event: %m"); + } + + /* This should fire pretty soon, which we'll use to send the + * READY=1 event. */ + + return 0; +} + +int server_init(Server *s) { + _cleanup_fdset_free_ FDSet *fds = NULL; + int n, r, fd; + bool no_sockets; + + assert(s); + + zero(*s); + s->syslog_fd = s->native_fd = s->stdout_fd = s->dev_kmsg_fd = s->audit_fd = s->hostname_fd = s->notify_fd = -1; + s->compress = true; + s->seal = true; + + s->watchdog_usec = USEC_INFINITY; + + s->sync_interval_usec = DEFAULT_SYNC_INTERVAL_USEC; + s->sync_scheduled = false; + + s->rate_limit_interval = DEFAULT_RATE_LIMIT_INTERVAL; + s->rate_limit_burst = DEFAULT_RATE_LIMIT_BURST; + + s->forward_to_wall = true; + + s->max_file_usec = DEFAULT_MAX_FILE_USEC; + + s->max_level_store = LOG_DEBUG; + s->max_level_syslog = LOG_DEBUG; + s->max_level_kmsg = LOG_NOTICE; + s->max_level_console = LOG_INFO; + s->max_level_wall = LOG_EMERG; + + journal_reset_metrics(&s->system_metrics); + journal_reset_metrics(&s->runtime_metrics); + + server_parse_config_file(s); + server_parse_proc_cmdline(s); + + if (!!s->rate_limit_interval ^ !!s->rate_limit_burst) { + log_debug("Setting both rate limit interval and burst from "USEC_FMT",%u to 0,0", + s->rate_limit_interval, s->rate_limit_burst); + s->rate_limit_interval = s->rate_limit_burst = 0; + } + + (void) mkdir_p("/run/systemd/journal", 0755); + + s->user_journals = ordered_hashmap_new(NULL); + if (!s->user_journals) + return log_oom(); + + s->mmap = mmap_cache_new(); + if (!s->mmap) + return log_oom(); + + s->deferred_closes = set_new(NULL); + if (!s->deferred_closes) + return log_oom(); + + r = sd_event_default(&s->event); + if (r < 0) + return log_error_errno(r, "Failed to create event loop: %m"); + + n = sd_listen_fds(true); + if (n < 0) + return log_error_errno(n, "Failed to read listening file descriptors from environment: %m"); + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { + + if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/run/systemd/journal/socket", 0) > 0) { + + if (s->native_fd >= 0) { + log_error("Too many native sockets passed."); + return -EINVAL; + } + + s->native_fd = fd; + + } else if (sd_is_socket_unix(fd, SOCK_STREAM, 1, "/run/systemd/journal/stdout", 0) > 0) { + + if (s->stdout_fd >= 0) { + log_error("Too many stdout sockets passed."); + return -EINVAL; + } + + s->stdout_fd = fd; + + } else if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/dev/log", 0) > 0 || + sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/run/systemd/journal/dev-log", 0) > 0) { + + if (s->syslog_fd >= 0) { + log_error("Too many /dev/log sockets passed."); + return -EINVAL; + } + + s->syslog_fd = fd; + + } else if (sd_is_socket(fd, AF_NETLINK, SOCK_RAW, -1) > 0) { + + if (s->audit_fd >= 0) { + log_error("Too many audit sockets passed."); + return -EINVAL; + } + + s->audit_fd = fd; + + } else { + + if (!fds) { + fds = fdset_new(); + if (!fds) + return log_oom(); + } + + r = fdset_put(fds, fd); + if (r < 0) + return log_oom(); + } + } + + /* Try to restore streams, but don't bother if this fails */ + (void) server_restore_streams(s, fds); + + if (fdset_size(fds) > 0) { + log_warning("%u unknown file descriptors passed, closing.", fdset_size(fds)); + fds = fdset_free(fds); + } + + no_sockets = s->native_fd < 0 && s->stdout_fd < 0 && s->syslog_fd < 0 && s->audit_fd < 0; + + /* always open stdout, syslog, native, and kmsg sockets */ + + /* systemd-journald.socket: /run/systemd/journal/stdout */ + r = server_open_stdout_socket(s); + if (r < 0) + return r; + + /* systemd-journald-dev-log.socket: /run/systemd/journal/dev-log */ + r = server_open_syslog_socket(s); + if (r < 0) + return r; + + /* systemd-journald.socket: /run/systemd/journal/socket */ + r = server_open_native_socket(s); + if (r < 0) + return r; + + /* /dev/ksmg */ + r = server_open_dev_kmsg(s); + if (r < 0) + return r; + + /* Unless we got *some* sockets and not audit, open audit socket */ + if (s->audit_fd >= 0 || no_sockets) { + r = server_open_audit(s); + if (r < 0) + return r; + } + + r = server_open_kernel_seqnum(s); + if (r < 0) + return r; + + r = server_open_hostname(s); + if (r < 0) + return r; + + r = setup_signals(s); + if (r < 0) + return r; + + s->udev = udev_new(); + if (!s->udev) + return -ENOMEM; + + s->rate_limit = journal_rate_limit_new(s->rate_limit_interval, s->rate_limit_burst); + if (!s->rate_limit) + return -ENOMEM; + + r = cg_get_root_path(&s->cgroup_root); + if (r < 0) + return r; + + server_cache_hostname(s); + server_cache_boot_id(s); + server_cache_machine_id(s); + + (void) server_connect_notify(s); + + return system_journal_open(s, false); +} + +void server_maybe_append_tags(Server *s) { +#ifdef HAVE_GCRYPT + JournalFile *f; + Iterator i; + usec_t n; + + n = now(CLOCK_REALTIME); + + if (s->system_journal) + journal_file_maybe_append_tag(s->system_journal, n); + + ORDERED_HASHMAP_FOREACH(f, s->user_journals, i) + journal_file_maybe_append_tag(f, n); +#endif +} + +void server_done(Server *s) { + JournalFile *f; + assert(s); + + if (s->deferred_closes) { + journal_file_close_set(s->deferred_closes); + set_free(s->deferred_closes); + } + + while (s->stdout_streams) + stdout_stream_free(s->stdout_streams); + + if (s->system_journal) + (void) journal_file_close(s->system_journal); + + if (s->runtime_journal) + (void) journal_file_close(s->runtime_journal); + + while ((f = ordered_hashmap_steal_first(s->user_journals))) + (void) journal_file_close(f); + + ordered_hashmap_free(s->user_journals); + + sd_event_source_unref(s->syslog_event_source); + sd_event_source_unref(s->native_event_source); + sd_event_source_unref(s->stdout_event_source); + sd_event_source_unref(s->dev_kmsg_event_source); + sd_event_source_unref(s->audit_event_source); + sd_event_source_unref(s->sync_event_source); + sd_event_source_unref(s->sigusr1_event_source); + sd_event_source_unref(s->sigusr2_event_source); + sd_event_source_unref(s->sigterm_event_source); + sd_event_source_unref(s->sigint_event_source); + sd_event_source_unref(s->sigrtmin1_event_source); + sd_event_source_unref(s->hostname_event_source); + sd_event_source_unref(s->notify_event_source); + sd_event_source_unref(s->watchdog_event_source); + sd_event_unref(s->event); + + safe_close(s->syslog_fd); + safe_close(s->native_fd); + safe_close(s->stdout_fd); + safe_close(s->dev_kmsg_fd); + safe_close(s->audit_fd); + safe_close(s->hostname_fd); + safe_close(s->notify_fd); + + if (s->rate_limit) + journal_rate_limit_free(s->rate_limit); + + if (s->kernel_seqnum) + munmap(s->kernel_seqnum, sizeof(uint64_t)); + + free(s->buffer); + free(s->tty_path); + free(s->cgroup_root); + free(s->hostname_field); + + if (s->mmap) + mmap_cache_unref(s->mmap); + + udev_unref(s->udev); +} + +static const char* const storage_table[_STORAGE_MAX] = { + [STORAGE_AUTO] = "auto", + [STORAGE_VOLATILE] = "volatile", + [STORAGE_PERSISTENT] = "persistent", + [STORAGE_NONE] = "none" +}; + +DEFINE_STRING_TABLE_LOOKUP(storage, Storage); +DEFINE_CONFIG_PARSE_ENUM(config_parse_storage, storage, Storage, "Failed to parse storage setting"); + +static const char* const split_mode_table[_SPLIT_MAX] = { + [SPLIT_LOGIN] = "login", + [SPLIT_UID] = "uid", + [SPLIT_NONE] = "none", +}; + +DEFINE_STRING_TABLE_LOOKUP(split_mode, SplitMode); +DEFINE_CONFIG_PARSE_ENUM(config_parse_split_mode, split_mode, SplitMode, "Failed to parse split mode setting"); diff --git a/src/grp-journal/libjournal-core/journald-server.h b/src/grp-journal/libjournal-core/journald-server.h new file mode 100644 index 0000000000..bebb056aa7 --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-server.h @@ -0,0 +1,186 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 + +#include + +typedef struct Server Server; + +#include "hashmap.h" +#include "journal-file.h" +#include "journald-rate-limit.h" +#include "journald-stream.h" +#include "list.h" + +typedef enum Storage { + STORAGE_AUTO, + STORAGE_VOLATILE, + STORAGE_PERSISTENT, + STORAGE_NONE, + _STORAGE_MAX, + _STORAGE_INVALID = -1 +} Storage; + +typedef enum SplitMode { + SPLIT_UID, + SPLIT_LOGIN, + SPLIT_NONE, + _SPLIT_MAX, + _SPLIT_INVALID = -1 +} SplitMode; + +struct Server { + int syslog_fd; + int native_fd; + int stdout_fd; + int dev_kmsg_fd; + int audit_fd; + int hostname_fd; + int notify_fd; + + sd_event *event; + + sd_event_source *syslog_event_source; + sd_event_source *native_event_source; + sd_event_source *stdout_event_source; + sd_event_source *dev_kmsg_event_source; + sd_event_source *audit_event_source; + sd_event_source *sync_event_source; + sd_event_source *sigusr1_event_source; + sd_event_source *sigusr2_event_source; + sd_event_source *sigterm_event_source; + sd_event_source *sigint_event_source; + sd_event_source *sigrtmin1_event_source; + sd_event_source *hostname_event_source; + sd_event_source *notify_event_source; + sd_event_source *watchdog_event_source; + + JournalFile *runtime_journal; + JournalFile *system_journal; + OrderedHashmap *user_journals; + + uint64_t seqnum; + + char *buffer; + size_t buffer_size; + + JournalRateLimit *rate_limit; + usec_t sync_interval_usec; + usec_t rate_limit_interval; + unsigned rate_limit_burst; + + JournalMetrics runtime_metrics; + JournalMetrics system_metrics; + + bool compress; + bool seal; + + bool forward_to_kmsg; + bool forward_to_syslog; + bool forward_to_console; + bool forward_to_wall; + + unsigned n_forward_syslog_missed; + usec_t last_warn_forward_syslog_missed; + + uint64_t cached_space_available; + uint64_t cached_space_limit; + usec_t cached_space_timestamp; + + uint64_t var_available_timestamp; + + usec_t max_retention_usec; + usec_t max_file_usec; + usec_t oldest_file_usec; + + LIST_HEAD(StdoutStream, stdout_streams); + LIST_HEAD(StdoutStream, stdout_streams_notify_queue); + unsigned n_stdout_streams; + + char *tty_path; + + int max_level_store; + int max_level_syslog; + int max_level_kmsg; + int max_level_console; + int max_level_wall; + + Storage storage; + SplitMode split_mode; + + MMapCache *mmap; + + Set *deferred_closes; + + struct udev *udev; + + uint64_t *kernel_seqnum; + bool dev_kmsg_readable:1; + + bool send_watchdog:1; + bool sent_notify_ready:1; + bool sync_scheduled:1; + + char machine_id_field[sizeof("_MACHINE_ID=") + 32]; + char boot_id_field[sizeof("_BOOT_ID=") + 32]; + char *hostname_field; + + /* Cached cgroup root, so that we don't have to query that all the time */ + char *cgroup_root; + + usec_t watchdog_usec; +}; + +#define SERVER_MACHINE_ID(s) ((s)->machine_id_field + strlen("_MACHINE_ID=")) + +#define N_IOVEC_META_FIELDS 20 +#define N_IOVEC_KERNEL_FIELDS 64 +#define N_IOVEC_UDEV_FIELDS 32 +#define N_IOVEC_OBJECT_FIELDS 12 +#define N_IOVEC_PAYLOAD_FIELDS 15 + +void server_dispatch_message(Server *s, struct iovec *iovec, unsigned n, unsigned m, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len, const char *unit_id, int priority, pid_t object_pid); +void server_driver_message(Server *s, sd_id128_t message_id, const char *format, ...) _printf_(3,0) _sentinel_; + +/* gperf lookup function */ +const struct ConfigPerfItem* journald_gperf_lookup(const char *key, unsigned length); + +int config_parse_storage(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); + +const char *storage_to_string(Storage s) _const_; +Storage storage_from_string(const char *s) _pure_; + +int config_parse_split_mode(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); + +const char *split_mode_to_string(SplitMode s) _const_; +SplitMode split_mode_from_string(const char *s) _pure_; + +int server_init(Server *s); +void server_done(Server *s); +void server_sync(Server *s); +int server_vacuum(Server *s, bool verbose, bool patch_min_use); +void server_rotate(Server *s); +int server_schedule_sync(Server *s, int priority); +int server_flush_to_var(Server *s); +void server_maybe_append_tags(Server *s); +int server_process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userdata); diff --git a/src/grp-journal/libjournal-core/journald-stream.c b/src/grp-journal/libjournal-core/journald-stream.c new file mode 100644 index 0000000000..99d856301c --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-stream.c @@ -0,0 +1,785 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 + +#ifdef HAVE_SELINUX +#include +#endif + +#include +#include + +#include "alloc-util.h" +#include "dirent-util.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "io-util.h" +#include "journald-console.h" +#include "journald-kmsg.h" +#include "journald-server.h" +#include "journald-stream.h" +#include "journald-syslog.h" +#include "journald-wall.h" +#include "mkdir.h" +#include "parse-util.h" +#include "selinux-util.h" +#include "socket-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "syslog-util.h" + +#define STDOUT_STREAMS_MAX 4096 + +typedef enum StdoutStreamState { + STDOUT_STREAM_IDENTIFIER, + STDOUT_STREAM_UNIT_ID, + STDOUT_STREAM_PRIORITY, + STDOUT_STREAM_LEVEL_PREFIX, + STDOUT_STREAM_FORWARD_TO_SYSLOG, + STDOUT_STREAM_FORWARD_TO_KMSG, + STDOUT_STREAM_FORWARD_TO_CONSOLE, + STDOUT_STREAM_RUNNING +} StdoutStreamState; + +struct StdoutStream { + Server *server; + StdoutStreamState state; + + int fd; + + struct ucred ucred; + char *label; + char *identifier; + char *unit_id; + int priority; + bool level_prefix:1; + bool forward_to_syslog:1; + bool forward_to_kmsg:1; + bool forward_to_console:1; + + bool fdstore:1; + bool in_notify_queue:1; + + char buffer[LINE_MAX+1]; + size_t length; + + sd_event_source *event_source; + + char *state_file; + + LIST_FIELDS(StdoutStream, stdout_stream); + LIST_FIELDS(StdoutStream, stdout_stream_notify_queue); +}; + +void stdout_stream_free(StdoutStream *s) { + if (!s) + return; + + if (s->server) { + assert(s->server->n_stdout_streams > 0); + s->server->n_stdout_streams--; + LIST_REMOVE(stdout_stream, s->server->stdout_streams, s); + + if (s->in_notify_queue) + LIST_REMOVE(stdout_stream_notify_queue, s->server->stdout_streams_notify_queue, s); + } + + if (s->event_source) { + sd_event_source_set_enabled(s->event_source, SD_EVENT_OFF); + s->event_source = sd_event_source_unref(s->event_source); + } + + safe_close(s->fd); + free(s->label); + free(s->identifier); + free(s->unit_id); + free(s->state_file); + + free(s); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(StdoutStream*, stdout_stream_free); + +static void stdout_stream_destroy(StdoutStream *s) { + if (!s) + return; + + if (s->state_file) + (void) unlink(s->state_file); + + stdout_stream_free(s); +} + +static int stdout_stream_save(StdoutStream *s) { + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(s); + + if (s->state != STDOUT_STREAM_RUNNING) + return 0; + + if (!s->state_file) { + struct stat st; + + r = fstat(s->fd, &st); + if (r < 0) + return log_warning_errno(errno, "Failed to stat connected stream: %m"); + + /* We use device and inode numbers as identifier for the stream */ + if (asprintf(&s->state_file, "/run/systemd/journal/streams/%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino) < 0) + return log_oom(); + } + + mkdir_p("/run/systemd/journal/streams", 0755); + + r = fopen_temporary(s->state_file, &f, &temp_path); + if (r < 0) + goto fail; + + fprintf(f, + "# This is private data. Do not parse\n" + "PRIORITY=%i\n" + "LEVEL_PREFIX=%i\n" + "FORWARD_TO_SYSLOG=%i\n" + "FORWARD_TO_KMSG=%i\n" + "FORWARD_TO_CONSOLE=%i\n", + s->priority, + s->level_prefix, + s->forward_to_syslog, + s->forward_to_kmsg, + s->forward_to_console); + + if (!isempty(s->identifier)) { + _cleanup_free_ char *escaped; + + escaped = cescape(s->identifier); + if (!escaped) { + r = -ENOMEM; + goto fail; + } + + fprintf(f, "IDENTIFIER=%s\n", escaped); + } + + if (!isempty(s->unit_id)) { + _cleanup_free_ char *escaped; + + escaped = cescape(s->unit_id); + if (!escaped) { + r = -ENOMEM; + goto fail; + } + + fprintf(f, "UNIT=%s\n", escaped); + } + + r = fflush_and_check(f); + if (r < 0) + goto fail; + + if (rename(temp_path, s->state_file) < 0) { + r = -errno; + goto fail; + } + + if (!s->fdstore && !s->in_notify_queue) { + LIST_PREPEND(stdout_stream_notify_queue, s->server->stdout_streams_notify_queue, s); + s->in_notify_queue = true; + + if (s->server->notify_event_source) { + r = sd_event_source_set_enabled(s->server->notify_event_source, SD_EVENT_ON); + if (r < 0) + log_warning_errno(r, "Failed to enable notify event source: %m"); + } + } + + return 0; + +fail: + (void) unlink(s->state_file); + + if (temp_path) + (void) unlink(temp_path); + + return log_error_errno(r, "Failed to save stream data %s: %m", s->state_file); +} + +static int stdout_stream_log(StdoutStream *s, const char *p) { + struct iovec iovec[N_IOVEC_META_FIELDS + 5]; + int priority; + char syslog_priority[] = "PRIORITY=\0"; + char syslog_facility[sizeof("SYSLOG_FACILITY=")-1 + DECIMAL_STR_MAX(int) + 1]; + _cleanup_free_ char *message = NULL, *syslog_identifier = NULL; + unsigned n = 0; + size_t label_len; + + assert(s); + assert(p); + + priority = s->priority; + + if (s->level_prefix) + syslog_parse_priority(&p, &priority, false); + + if (isempty(p)) + return 0; + + if (s->forward_to_syslog || s->server->forward_to_syslog) + server_forward_syslog(s->server, syslog_fixup_facility(priority), s->identifier, p, &s->ucred, NULL); + + if (s->forward_to_kmsg || s->server->forward_to_kmsg) + server_forward_kmsg(s->server, priority, s->identifier, p, &s->ucred); + + if (s->forward_to_console || s->server->forward_to_console) + server_forward_console(s->server, priority, s->identifier, p, &s->ucred); + + if (s->server->forward_to_wall) + server_forward_wall(s->server, priority, s->identifier, p, &s->ucred); + + IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=stdout"); + + syslog_priority[strlen("PRIORITY=")] = '0' + LOG_PRI(priority); + IOVEC_SET_STRING(iovec[n++], syslog_priority); + + if (priority & LOG_FACMASK) { + xsprintf(syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)); + IOVEC_SET_STRING(iovec[n++], syslog_facility); + } + + if (s->identifier) { + syslog_identifier = strappend("SYSLOG_IDENTIFIER=", s->identifier); + if (syslog_identifier) + IOVEC_SET_STRING(iovec[n++], syslog_identifier); + } + + message = strappend("MESSAGE=", p); + if (message) + IOVEC_SET_STRING(iovec[n++], message); + + label_len = s->label ? strlen(s->label) : 0; + server_dispatch_message(s->server, iovec, n, ELEMENTSOF(iovec), &s->ucred, NULL, s->label, label_len, s->unit_id, priority, 0); + return 0; +} + +static int stdout_stream_line(StdoutStream *s, char *p) { + int r; + char *orig; + + assert(s); + assert(p); + + orig = p; + p = strstrip(p); + + switch (s->state) { + + case STDOUT_STREAM_IDENTIFIER: + if (isempty(p)) + s->identifier = NULL; + else { + s->identifier = strdup(p); + if (!s->identifier) + return log_oom(); + } + + s->state = STDOUT_STREAM_UNIT_ID; + return 0; + + case STDOUT_STREAM_UNIT_ID: + if (s->ucred.uid == 0) { + if (isempty(p)) + s->unit_id = NULL; + else { + s->unit_id = strdup(p); + if (!s->unit_id) + return log_oom(); + } + } + + s->state = STDOUT_STREAM_PRIORITY; + return 0; + + case STDOUT_STREAM_PRIORITY: + r = safe_atoi(p, &s->priority); + if (r < 0 || s->priority < 0 || s->priority > 999) { + log_warning("Failed to parse log priority line."); + return -EINVAL; + } + + s->state = STDOUT_STREAM_LEVEL_PREFIX; + return 0; + + case STDOUT_STREAM_LEVEL_PREFIX: + r = parse_boolean(p); + if (r < 0) { + log_warning("Failed to parse level prefix line."); + return -EINVAL; + } + + s->level_prefix = !!r; + s->state = STDOUT_STREAM_FORWARD_TO_SYSLOG; + return 0; + + case STDOUT_STREAM_FORWARD_TO_SYSLOG: + r = parse_boolean(p); + if (r < 0) { + log_warning("Failed to parse forward to syslog line."); + return -EINVAL; + } + + s->forward_to_syslog = !!r; + s->state = STDOUT_STREAM_FORWARD_TO_KMSG; + return 0; + + case STDOUT_STREAM_FORWARD_TO_KMSG: + r = parse_boolean(p); + if (r < 0) { + log_warning("Failed to parse copy to kmsg line."); + return -EINVAL; + } + + s->forward_to_kmsg = !!r; + s->state = STDOUT_STREAM_FORWARD_TO_CONSOLE; + return 0; + + case STDOUT_STREAM_FORWARD_TO_CONSOLE: + r = parse_boolean(p); + if (r < 0) { + log_warning("Failed to parse copy to console line."); + return -EINVAL; + } + + s->forward_to_console = !!r; + s->state = STDOUT_STREAM_RUNNING; + + /* Try to save the stream, so that journald can be restarted and we can recover */ + (void) stdout_stream_save(s); + return 0; + + case STDOUT_STREAM_RUNNING: + return stdout_stream_log(s, orig); + } + + assert_not_reached("Unknown stream state"); +} + +static int stdout_stream_scan(StdoutStream *s, bool force_flush) { + char *p; + size_t remaining; + int r; + + assert(s); + + p = s->buffer; + remaining = s->length; + for (;;) { + char *end; + size_t skip; + + end = memchr(p, '\n', remaining); + if (end) + skip = end - p + 1; + else if (remaining >= sizeof(s->buffer) - 1) { + end = p + sizeof(s->buffer) - 1; + skip = remaining; + } else + break; + + *end = 0; + + r = stdout_stream_line(s, p); + if (r < 0) + return r; + + remaining -= skip; + p += skip; + } + + if (force_flush && remaining > 0) { + p[remaining] = 0; + r = stdout_stream_line(s, p); + if (r < 0) + return r; + + p += remaining; + remaining = 0; + } + + if (p > s->buffer) { + memmove(s->buffer, p, remaining); + s->length = remaining; + } + + return 0; +} + +static int stdout_stream_process(sd_event_source *es, int fd, uint32_t revents, void *userdata) { + StdoutStream *s = userdata; + ssize_t l; + int r; + + assert(s); + + if ((revents|EPOLLIN|EPOLLHUP) != (EPOLLIN|EPOLLHUP)) { + log_error("Got invalid event from epoll for stdout stream: %"PRIx32, revents); + goto terminate; + } + + l = read(s->fd, s->buffer+s->length, sizeof(s->buffer)-1-s->length); + if (l < 0) { + + if (errno == EAGAIN) + return 0; + + log_warning_errno(errno, "Failed to read from stream: %m"); + goto terminate; + } + + if (l == 0) { + stdout_stream_scan(s, true); + goto terminate; + } + + s->length += l; + r = stdout_stream_scan(s, false); + if (r < 0) + goto terminate; + + return 1; + +terminate: + stdout_stream_destroy(s); + return 0; +} + +static int stdout_stream_install(Server *s, int fd, StdoutStream **ret) { + _cleanup_(stdout_stream_freep) StdoutStream *stream = NULL; + int r; + + assert(s); + assert(fd >= 0); + + stream = new0(StdoutStream, 1); + if (!stream) + return log_oom(); + + stream->fd = -1; + stream->priority = LOG_INFO; + + r = getpeercred(fd, &stream->ucred); + if (r < 0) + return log_error_errno(r, "Failed to determine peer credentials: %m"); + + if (mac_selinux_have()) { + r = getpeersec(fd, &stream->label); + if (r < 0 && r != -EOPNOTSUPP) + (void) log_warning_errno(r, "Failed to determine peer security context: %m"); + } + + (void) shutdown(fd, SHUT_WR); + + r = sd_event_add_io(s->event, &stream->event_source, fd, EPOLLIN, stdout_stream_process, stream); + if (r < 0) + return log_error_errno(r, "Failed to add stream to event loop: %m"); + + r = sd_event_source_set_priority(stream->event_source, SD_EVENT_PRIORITY_NORMAL+5); + if (r < 0) + return log_error_errno(r, "Failed to adjust stdout event source priority: %m"); + + stream->fd = fd; + + stream->server = s; + LIST_PREPEND(stdout_stream, s->stdout_streams, stream); + s->n_stdout_streams++; + + if (ret) + *ret = stream; + + stream = NULL; + + return 0; +} + +static int stdout_stream_new(sd_event_source *es, int listen_fd, uint32_t revents, void *userdata) { + _cleanup_close_ int fd = -1; + Server *s = userdata; + int r; + + assert(s); + + if (revents != EPOLLIN) { + log_error("Got invalid event from epoll for stdout server fd: %"PRIx32, revents); + return -EIO; + } + + fd = accept4(s->stdout_fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); + if (fd < 0) { + if (errno == EAGAIN) + return 0; + + return log_error_errno(errno, "Failed to accept stdout connection: %m"); + } + + if (s->n_stdout_streams >= STDOUT_STREAMS_MAX) { + log_warning("Too many stdout streams, refusing connection."); + return 0; + } + + r = stdout_stream_install(s, fd, NULL); + if (r < 0) + return r; + + fd = -1; + return 0; +} + +static int stdout_stream_load(StdoutStream *stream, const char *fname) { + _cleanup_free_ char + *priority = NULL, + *level_prefix = NULL, + *forward_to_syslog = NULL, + *forward_to_kmsg = NULL, + *forward_to_console = NULL; + int r; + + assert(stream); + assert(fname); + + if (!stream->state_file) { + stream->state_file = strappend("/run/systemd/journal/streams/", fname); + if (!stream->state_file) + return log_oom(); + } + + r = parse_env_file(stream->state_file, NEWLINE, + "PRIORITY", &priority, + "LEVEL_PREFIX", &level_prefix, + "FORWARD_TO_SYSLOG", &forward_to_syslog, + "FORWARD_TO_KMSG", &forward_to_kmsg, + "FORWARD_TO_CONSOLE", &forward_to_console, + "IDENTIFIER", &stream->identifier, + "UNIT", &stream->unit_id, + NULL); + if (r < 0) + return log_error_errno(r, "Failed to read: %s", stream->state_file); + + if (priority) { + int p; + + p = log_level_from_string(priority); + if (p >= 0) + stream->priority = p; + } + + if (level_prefix) { + r = parse_boolean(level_prefix); + if (r >= 0) + stream->level_prefix = r; + } + + if (forward_to_syslog) { + r = parse_boolean(forward_to_syslog); + if (r >= 0) + stream->forward_to_syslog = r; + } + + if (forward_to_kmsg) { + r = parse_boolean(forward_to_kmsg); + if (r >= 0) + stream->forward_to_kmsg = r; + } + + if (forward_to_console) { + r = parse_boolean(forward_to_console); + if (r >= 0) + stream->forward_to_console = r; + } + + return 0; +} + +static int stdout_stream_restore(Server *s, const char *fname, int fd) { + StdoutStream *stream; + int r; + + assert(s); + assert(fname); + assert(fd >= 0); + + if (s->n_stdout_streams >= STDOUT_STREAMS_MAX) { + log_warning("Too many stdout streams, refusing restoring of stream."); + return -ENOBUFS; + } + + r = stdout_stream_install(s, fd, &stream); + if (r < 0) + return r; + + stream->state = STDOUT_STREAM_RUNNING; + stream->fdstore = true; + + /* Ignore all parsing errors */ + (void) stdout_stream_load(stream, fname); + + return 0; +} + +int server_restore_streams(Server *s, FDSet *fds) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r; + + d = opendir("/run/systemd/journal/streams"); + if (!d) { + if (errno == ENOENT) + return 0; + + return log_warning_errno(errno, "Failed to enumerate /run/systemd/journal/streams: %m"); + } + + FOREACH_DIRENT(de, d, goto fail) { + unsigned long st_dev, st_ino; + bool found = false; + Iterator i; + int fd; + + if (sscanf(de->d_name, "%lu:%lu", &st_dev, &st_ino) != 2) + continue; + + FDSET_FOREACH(fd, fds, i) { + struct stat st; + + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to stat %s: %m", de->d_name); + + if (S_ISSOCK(st.st_mode) && st.st_dev == st_dev && st.st_ino == st_ino) { + found = true; + break; + } + } + + if (!found) { + /* No file descriptor? Then let's delete the state file */ + log_debug("Cannot restore stream file %s", de->d_name); + unlinkat(dirfd(d), de->d_name, 0); + continue; + } + + fdset_remove(fds, fd); + + r = stdout_stream_restore(s, de->d_name, fd); + if (r < 0) + safe_close(fd); + } + + return 0; + +fail: + return log_error_errno(errno, "Failed to read streams directory: %m"); +} + +int server_open_stdout_socket(Server *s) { + static const union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/journal/stdout", + }; + int r; + + assert(s); + + if (s->stdout_fd < 0) { + s->stdout_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (s->stdout_fd < 0) + return log_error_errno(errno, "socket() failed: %m"); + + (void) unlink(sa.un.sun_path); + + r = bind(s->stdout_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); + if (r < 0) + return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path); + + (void) chmod(sa.un.sun_path, 0666); + + if (listen(s->stdout_fd, SOMAXCONN) < 0) + return log_error_errno(errno, "listen(%s) failed: %m", sa.un.sun_path); + } else + fd_nonblock(s->stdout_fd, 1); + + r = sd_event_add_io(s->event, &s->stdout_event_source, s->stdout_fd, EPOLLIN, stdout_stream_new, s); + if (r < 0) + return log_error_errno(r, "Failed to add stdout server fd to event source: %m"); + + r = sd_event_source_set_priority(s->stdout_event_source, SD_EVENT_PRIORITY_NORMAL+5); + if (r < 0) + return log_error_errno(r, "Failed to adjust priority of stdout server event source: %m"); + + return 0; +} + +void stdout_stream_send_notify(StdoutStream *s) { + struct iovec iovec = { + .iov_base = (char*) "FDSTORE=1", + .iov_len = strlen("FDSTORE=1"), + }; + struct msghdr msghdr = { + .msg_iov = &iovec, + .msg_iovlen = 1, + }; + struct cmsghdr *cmsg; + ssize_t l; + + assert(s); + assert(!s->fdstore); + assert(s->in_notify_queue); + assert(s->server); + assert(s->server->notify_fd >= 0); + + /* Store the connection fd in PID 1, so that we get it passed + * in again on next start */ + + msghdr.msg_controllen = CMSG_SPACE(sizeof(int)); + msghdr.msg_control = alloca0(msghdr.msg_controllen); + + cmsg = CMSG_FIRSTHDR(&msghdr); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + + memcpy(CMSG_DATA(cmsg), &s->fd, sizeof(int)); + + l = sendmsg(s->server->notify_fd, &msghdr, MSG_DONTWAIT|MSG_NOSIGNAL); + if (l < 0) { + if (errno == EAGAIN) + return; + + log_error_errno(errno, "Failed to send stream file descriptor to service manager: %m"); + } else { + log_debug("Successfully sent stream file descriptor to service manager."); + s->fdstore = 1; + } + + LIST_REMOVE(stdout_stream_notify_queue, s->server->stdout_streams_notify_queue, s); + s->in_notify_queue = false; + +} diff --git a/src/grp-journal/libjournal-core/journald-stream.h b/src/grp-journal/libjournal-core/journald-stream.h new file mode 100644 index 0000000000..db4c67fae3 --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-stream.h @@ -0,0 +1,31 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +typedef struct StdoutStream StdoutStream; + +#include "fdset.h" +#include "journald-server.h" + +int server_open_stdout_socket(Server *s); +int server_restore_streams(Server *s, FDSet *fds); + +void stdout_stream_free(StdoutStream *s); +void stdout_stream_send_notify(StdoutStream *s); diff --git a/src/grp-journal/libjournal-core/journald-syslog.c b/src/grp-journal/libjournal-core/journald-syslog.c new file mode 100644 index 0000000000..86fe81d179 --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-syslog.c @@ -0,0 +1,454 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "io-util.h" +#include "journald-console.h" +#include "journald-kmsg.h" +#include "journald-server.h" +#include "journald-syslog.h" +#include "journald-wall.h" +#include "process-util.h" +#include "selinux-util.h" +#include "socket-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "syslog-util.h" + +/* Warn once every 30s if we missed syslog message */ +#define WARN_FORWARD_SYSLOG_MISSED_USEC (30 * USEC_PER_SEC) + +static void forward_syslog_iovec(Server *s, const struct iovec *iovec, unsigned n_iovec, const struct ucred *ucred, const struct timeval *tv) { + + static const union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/journal/syslog", + }; + struct msghdr msghdr = { + .msg_iov = (struct iovec *) iovec, + .msg_iovlen = n_iovec, + .msg_name = (struct sockaddr*) &sa.sa, + .msg_namelen = SOCKADDR_UN_LEN(sa.un), + }; + struct cmsghdr *cmsg; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; + } control; + + assert(s); + assert(iovec); + assert(n_iovec > 0); + + if (ucred) { + zero(control); + msghdr.msg_control = &control; + msghdr.msg_controllen = sizeof(control); + + cmsg = CMSG_FIRSTHDR(&msghdr); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_CREDENTIALS; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred)); + memcpy(CMSG_DATA(cmsg), ucred, sizeof(struct ucred)); + msghdr.msg_controllen = cmsg->cmsg_len; + } + + /* Forward the syslog message we received via /dev/log to + * /run/systemd/syslog. Unfortunately we currently can't set + * the SO_TIMESTAMP auxiliary data, and hence we don't. */ + + if (sendmsg(s->syslog_fd, &msghdr, MSG_NOSIGNAL) >= 0) + return; + + /* The socket is full? I guess the syslog implementation is + * too slow, and we shouldn't wait for that... */ + if (errno == EAGAIN) { + s->n_forward_syslog_missed++; + return; + } + + if (ucred && (errno == ESRCH || errno == EPERM)) { + struct ucred u; + + /* Hmm, presumably the sender process vanished + * by now, or we don't have CAP_SYS_AMDIN, so + * let's fix it as good as we can, and retry */ + + u = *ucred; + u.pid = getpid(); + memcpy(CMSG_DATA(cmsg), &u, sizeof(struct ucred)); + + if (sendmsg(s->syslog_fd, &msghdr, MSG_NOSIGNAL) >= 0) + return; + + if (errno == EAGAIN) { + s->n_forward_syslog_missed++; + return; + } + } + + if (errno != ENOENT) + log_debug_errno(errno, "Failed to forward syslog message: %m"); +} + +static void forward_syslog_raw(Server *s, int priority, const char *buffer, const struct ucred *ucred, const struct timeval *tv) { + struct iovec iovec; + + assert(s); + assert(buffer); + + if (LOG_PRI(priority) > s->max_level_syslog) + return; + + IOVEC_SET_STRING(iovec, buffer); + forward_syslog_iovec(s, &iovec, 1, ucred, tv); +} + +void server_forward_syslog(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred, const struct timeval *tv) { + struct iovec iovec[5]; + char header_priority[DECIMAL_STR_MAX(priority) + 3], header_time[64], + header_pid[sizeof("[]: ")-1 + DECIMAL_STR_MAX(pid_t) + 1]; + int n = 0; + time_t t; + struct tm *tm; + char *ident_buf = NULL; + + assert(s); + assert(priority >= 0); + assert(priority <= 999); + assert(message); + + if (LOG_PRI(priority) > s->max_level_syslog) + return; + + /* First: priority field */ + xsprintf(header_priority, "<%i>", priority); + IOVEC_SET_STRING(iovec[n++], header_priority); + + /* Second: timestamp */ + t = tv ? tv->tv_sec : ((time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC)); + tm = localtime(&t); + if (!tm) + return; + if (strftime(header_time, sizeof(header_time), "%h %e %T ", tm) <= 0) + return; + IOVEC_SET_STRING(iovec[n++], header_time); + + /* Third: identifier and PID */ + if (ucred) { + if (!identifier) { + get_process_comm(ucred->pid, &ident_buf); + identifier = ident_buf; + } + + xsprintf(header_pid, "["PID_FMT"]: ", ucred->pid); + + if (identifier) + IOVEC_SET_STRING(iovec[n++], identifier); + + IOVEC_SET_STRING(iovec[n++], header_pid); + } else if (identifier) { + IOVEC_SET_STRING(iovec[n++], identifier); + IOVEC_SET_STRING(iovec[n++], ": "); + } + + /* Fourth: message */ + IOVEC_SET_STRING(iovec[n++], message); + + forward_syslog_iovec(s, iovec, n, ucred, tv); + + free(ident_buf); +} + +int syslog_fixup_facility(int priority) { + + if ((priority & LOG_FACMASK) == 0) + return (priority & LOG_PRIMASK) | LOG_USER; + + return priority; +} + +size_t syslog_parse_identifier(const char **buf, char **identifier, char **pid) { + const char *p; + char *t; + size_t l, e; + + assert(buf); + assert(identifier); + assert(pid); + + p = *buf; + + p += strspn(p, WHITESPACE); + l = strcspn(p, WHITESPACE); + + if (l <= 0 || + p[l-1] != ':') + return 0; + + e = l; + l--; + + if (p[l-1] == ']') { + size_t k = l-1; + + for (;;) { + + if (p[k] == '[') { + t = strndup(p+k+1, l-k-2); + if (t) + *pid = t; + + l = k; + break; + } + + if (k == 0) + break; + + k--; + } + } + + t = strndup(p, l); + if (t) + *identifier = t; + + if (strchr(WHITESPACE, p[e])) + e++; + *buf = p + e; + return e; +} + +static void syslog_skip_date(char **buf) { + enum { + LETTER, + SPACE, + NUMBER, + SPACE_OR_NUMBER, + COLON + } sequence[] = { + LETTER, LETTER, LETTER, + SPACE, + SPACE_OR_NUMBER, NUMBER, + SPACE, + SPACE_OR_NUMBER, NUMBER, + COLON, + SPACE_OR_NUMBER, NUMBER, + COLON, + SPACE_OR_NUMBER, NUMBER, + SPACE + }; + + char *p; + unsigned i; + + assert(buf); + assert(*buf); + + p = *buf; + + for (i = 0; i < ELEMENTSOF(sequence); i++, p++) { + + if (!*p) + return; + + switch (sequence[i]) { + + case SPACE: + if (*p != ' ') + return; + break; + + case SPACE_OR_NUMBER: + if (*p == ' ') + break; + + /* fall through */ + + case NUMBER: + if (*p < '0' || *p > '9') + return; + + break; + + case LETTER: + if (!(*p >= 'A' && *p <= 'Z') && + !(*p >= 'a' && *p <= 'z')) + return; + + break; + + case COLON: + if (*p != ':') + return; + break; + + } + } + + *buf = p; +} + +void server_process_syslog_message( + Server *s, + const char *buf, + const struct ucred *ucred, + const struct timeval *tv, + const char *label, + size_t label_len) { + + char syslog_priority[sizeof("PRIORITY=") + DECIMAL_STR_MAX(int)], + syslog_facility[sizeof("SYSLOG_FACILITY=") + DECIMAL_STR_MAX(int)]; + const char *message = NULL, *syslog_identifier = NULL, *syslog_pid = NULL; + struct iovec iovec[N_IOVEC_META_FIELDS + 6]; + unsigned n = 0; + int priority = LOG_USER | LOG_INFO; + _cleanup_free_ char *identifier = NULL, *pid = NULL; + const char *orig; + + assert(s); + assert(buf); + + orig = buf; + syslog_parse_priority(&buf, &priority, true); + + if (s->forward_to_syslog) + forward_syslog_raw(s, priority, orig, ucred, tv); + + syslog_skip_date((char**) &buf); + syslog_parse_identifier(&buf, &identifier, &pid); + + if (s->forward_to_kmsg) + server_forward_kmsg(s, priority, identifier, buf, ucred); + + if (s->forward_to_console) + server_forward_console(s, priority, identifier, buf, ucred); + + if (s->forward_to_wall) + server_forward_wall(s, priority, identifier, buf, ucred); + + IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=syslog"); + + xsprintf(syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK); + IOVEC_SET_STRING(iovec[n++], syslog_priority); + + if (priority & LOG_FACMASK) { + xsprintf(syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)); + IOVEC_SET_STRING(iovec[n++], syslog_facility); + } + + if (identifier) { + syslog_identifier = strjoina("SYSLOG_IDENTIFIER=", identifier); + IOVEC_SET_STRING(iovec[n++], syslog_identifier); + } + + if (pid) { + syslog_pid = strjoina("SYSLOG_PID=", pid); + IOVEC_SET_STRING(iovec[n++], syslog_pid); + } + + message = strjoina("MESSAGE=", buf); + if (message) + IOVEC_SET_STRING(iovec[n++], message); + + server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), ucred, tv, label, label_len, NULL, priority, 0); +} + +int server_open_syslog_socket(Server *s) { + + static const union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/journal/dev-log", + }; + static const int one = 1; + int r; + + assert(s); + + if (s->syslog_fd < 0) { + s->syslog_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (s->syslog_fd < 0) + return log_error_errno(errno, "socket() failed: %m"); + + (void) unlink(sa.un.sun_path); + + r = bind(s->syslog_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); + if (r < 0) + return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path); + + (void) chmod(sa.un.sun_path, 0666); + } else + fd_nonblock(s->syslog_fd, 1); + + r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); + if (r < 0) + return log_error_errno(errno, "SO_PASSCRED failed: %m"); + +#ifdef HAVE_SELINUX + if (mac_selinux_have()) { + r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one)); + if (r < 0) + log_warning_errno(errno, "SO_PASSSEC failed: %m"); + } +#endif + + r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)); + if (r < 0) + return log_error_errno(errno, "SO_TIMESTAMP failed: %m"); + + r = sd_event_add_io(s->event, &s->syslog_event_source, s->syslog_fd, EPOLLIN, server_process_datagram, s); + if (r < 0) + return log_error_errno(r, "Failed to add syslog server fd to event loop: %m"); + + r = sd_event_source_set_priority(s->syslog_event_source, SD_EVENT_PRIORITY_NORMAL+5); + if (r < 0) + return log_error_errno(r, "Failed to adjust syslog event source priority: %m"); + + return 0; +} + +void server_maybe_warn_forward_syslog_missed(Server *s) { + usec_t n; + + assert(s); + + if (s->n_forward_syslog_missed <= 0) + return; + + n = now(CLOCK_MONOTONIC); + if (s->last_warn_forward_syslog_missed + WARN_FORWARD_SYSLOG_MISSED_USEC > n) + return; + + server_driver_message(s, SD_MESSAGE_FORWARD_SYSLOG_MISSED, + LOG_MESSAGE("Forwarding to syslog missed %u messages.", + s->n_forward_syslog_missed), + NULL); + + s->n_forward_syslog_missed = 0; + s->last_warn_forward_syslog_missed = n; +} diff --git a/src/grp-journal/libjournal-core/journald-syslog.h b/src/grp-journal/libjournal-core/journald-syslog.h new file mode 100644 index 0000000000..46ad715314 --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-syslog.h @@ -0,0 +1,33 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 "journald-server.h" + +int syslog_fixup_facility(int priority) _const_; + +size_t syslog_parse_identifier(const char **buf, char **identifier, char **pid); + +void server_forward_syslog(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred, const struct timeval *tv); + +void server_process_syslog_message(Server *s, const char *buf, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len); +int server_open_syslog_socket(Server *s); + +void server_maybe_warn_forward_syslog_missed(Server *s); diff --git a/src/grp-journal/libjournal-core/journald-wall.c b/src/grp-journal/libjournal-core/journald-wall.c new file mode 100644 index 0000000000..4d91fafffe --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-wall.c @@ -0,0 +1,71 @@ +/*** + This file is part of systemd. + + Copyright 2014 Sebastian Thorarensen + + 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 "alloc-util.h" +#include "formats-util.h" +#include "journald-server.h" +#include "journald-wall.h" +#include "process-util.h" +#include "string-util.h" +#include "utmp-wtmp.h" + +void server_forward_wall( + Server *s, + int priority, + const char *identifier, + const char *message, + const struct ucred *ucred) { + + _cleanup_free_ char *ident_buf = NULL, *l_buf = NULL; + const char *l; + int r; + + assert(s); + assert(message); + + if (LOG_PRI(priority) > s->max_level_wall) + return; + + if (ucred) { + if (!identifier) { + get_process_comm(ucred->pid, &ident_buf); + identifier = ident_buf; + } + + if (asprintf(&l_buf, "%s["PID_FMT"]: %s", strempty(identifier), ucred->pid, message) < 0) { + log_oom(); + return; + } + + l = l_buf; + + } else if (identifier) { + + l = l_buf = strjoin(identifier, ": ", message, NULL); + if (!l_buf) { + log_oom(); + return; + } + } else + l = message; + + r = utmp_wall(l, "systemd-journald", NULL, NULL, NULL); + if (r < 0) + log_debug_errno(r, "Failed to send wall message: %m"); +} diff --git a/src/grp-journal/libjournal-core/journald-wall.h b/src/grp-journal/libjournal-core/journald-wall.h new file mode 100644 index 0000000000..ebc2b89fa8 --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-wall.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Sebastian Thorarensen + + 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 "journald-server.h" + +void server_forward_wall(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred); diff --git a/src/grp-journal/libjournal-core/journald.conf b/src/grp-journal/libjournal-core/journald.conf new file mode 100644 index 0000000000..2541b949be --- /dev/null +++ b/src/grp-journal/libjournal-core/journald.conf @@ -0,0 +1,41 @@ +# This file is part of systemd. +# +# 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. +# +# Entries in this file show the compile time defaults. +# You can change settings by editing this file. +# Defaults can be restored by simply deleting this file. +# +# See journald.conf(5) for details. + +[Journal] +#Storage=auto +#Compress=yes +#Seal=yes +#SplitMode=uid +#SyncIntervalSec=5m +#RateLimitIntervalSec=30s +#RateLimitBurst=1000 +#SystemMaxUse= +#SystemKeepFree= +#SystemMaxFileSize= +#SystemMaxFiles=100 +#RuntimeMaxUse= +#RuntimeKeepFree= +#RuntimeMaxFileSize= +#RuntimeMaxFiles=100 +#MaxRetentionSec= +#MaxFileSec=1month +#ForwardToSyslog=no +#ForwardToKMsg=no +#ForwardToConsole=no +#ForwardToWall=yes +#TTYPath=/dev/console +#MaxLevelStore=debug +#MaxLevelSyslog=debug +#MaxLevelKMsg=notice +#MaxLevelConsole=info +#MaxLevelWall=emerg diff --git a/src/grp-journal/libjournal-core/test-audit-type.c b/src/grp-journal/libjournal-core/test-audit-type.c new file mode 100644 index 0000000000..88a2e6d9d9 --- /dev/null +++ b/src/grp-journal/libjournal-core/test-audit-type.c @@ -0,0 +1,42 @@ +/*** + This file is part of systemd. + + Copyright 2015 Zbigniew Jędrzejewski-Szmek + + 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 + +#include "audit-type.h" + +static void print_audit_label(int i) { + const char *name; + + name = audit_type_name_alloca(i); + /* This is a separate function only because of alloca */ + printf("%i → %s → %s\n", i, audit_type_to_string(i), name); +} + +static void test_audit_type(void) { + int i; + + for (i = 0; i <= AUDIT_KERNEL; i++) + print_audit_label(i); +} + +int main(int argc, char **argv) { + test_audit_type(); +} diff --git a/src/grp-journal/libjournal-core/test-catalog.c b/src/grp-journal/libjournal-core/test-catalog.c new file mode 100644 index 0000000000..f939fcdc2a --- /dev/null +++ b/src/grp-journal/libjournal-core/test-catalog.c @@ -0,0 +1,264 @@ +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + 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 +#include +#include + +#include + +#include "alloc-util.h" +#include "catalog.h" +#include "fd-util.h" +#include "fileio.h" +#include "log.h" +#include "macro.h" +#include "string-util.h" +#include "util.h" + +static const char *catalog_dirs[] = { + CATALOG_DIR, + NULL, +}; + +static const char *no_catalog_dirs[] = { + "/bin/hopefully/with/no/catalog", + NULL +}; + +static Hashmap * test_import(const char* contents, ssize_t size, int code) { + int r; + char name[] = "/tmp/test-catalog.XXXXXX"; + _cleanup_close_ int fd; + Hashmap *h; + + if (size < 0) + size = strlen(contents); + + assert_se(h = hashmap_new(&catalog_hash_ops)); + + fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + assert_se(fd >= 0); + assert_se(write(fd, contents, size) == size); + + r = catalog_import_file(h, name); + assert_se(r == code); + + unlink(name); + + return h; +} + +static void test_catalog_import_invalid(void) { + _cleanup_hashmap_free_free_free_ Hashmap *h = NULL; + + h = test_import("xxx", -1, -EINVAL); + assert_se(hashmap_isempty(h)); +} + +static void test_catalog_import_badid(void) { + _cleanup_hashmap_free_free_free_ Hashmap *h = NULL; + const char *input = +"-- 0027229ca0644181a76c4e92458afaff dededededededededededededededede\n" \ +"Subject: message\n" \ +"\n" \ +"payload\n"; + h = test_import(input, -1, -EINVAL); +} + +static void test_catalog_import_one(void) { + _cleanup_hashmap_free_free_free_ Hashmap *h = NULL; + char *payload; + Iterator j; + + const char *input = +"-- 0027229ca0644181a76c4e92458afaff dededededededededededededededed\n" \ +"Subject: message\n" \ +"\n" \ +"payload\n"; + const char *expect = +"Subject: message\n" \ +"\n" \ +"payload\n"; + + h = test_import(input, -1, 0); + assert_se(hashmap_size(h) == 1); + + HASHMAP_FOREACH(payload, h, j) { + printf("expect: %s\n", expect); + printf("actual: %s\n", payload); + assert_se(streq(expect, payload)); + } +} + +static void test_catalog_import_merge(void) { + _cleanup_hashmap_free_free_free_ Hashmap *h = NULL; + char *payload; + Iterator j; + + const char *input = +"-- 0027229ca0644181a76c4e92458afaff dededededededededededededededed\n" \ +"Subject: message\n" \ +"Defined-By: me\n" \ +"\n" \ +"payload\n" \ +"\n" \ +"-- 0027229ca0644181a76c4e92458afaff dededededededededededededededed\n" \ +"Subject: override subject\n" \ +"X-Header: hello\n" \ +"\n" \ +"override payload\n"; + + const char *combined = +"Subject: override subject\n" \ +"X-Header: hello\n" \ +"Subject: message\n" \ +"Defined-By: me\n" \ +"\n" \ +"override payload\n"; + + h = test_import(input, -1, 0); + assert_se(hashmap_size(h) == 1); + + HASHMAP_FOREACH(payload, h, j) { + assert_se(streq(combined, payload)); + } +} + +static void test_catalog_import_merge_no_body(void) { + _cleanup_hashmap_free_free_free_ Hashmap *h = NULL; + char *payload; + Iterator j; + + const char *input = +"-- 0027229ca0644181a76c4e92458afaff dededededededededededededededed\n" \ +"Subject: message\n" \ +"Defined-By: me\n" \ +"\n" \ +"payload\n" \ +"\n" \ +"-- 0027229ca0644181a76c4e92458afaff dededededededededededededededed\n" \ +"Subject: override subject\n" \ +"X-Header: hello\n" \ +"\n"; + + const char *combined = +"Subject: override subject\n" \ +"X-Header: hello\n" \ +"Subject: message\n" \ +"Defined-By: me\n" \ +"\n" \ +"payload\n"; + + h = test_import(input, -1, 0); + assert_se(hashmap_size(h) == 1); + + HASHMAP_FOREACH(payload, h, j) { + assert_se(streq(combined, payload)); + } +} + +static const char* database = NULL; + +static void test_catalog_update(void) { + static char name[] = "/tmp/test-catalog.XXXXXX"; + int r; + + r = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + assert_se(r >= 0); + + database = name; + + /* Test what happens if there are no files. */ + r = catalog_update(database, NULL, NULL); + assert_se(r >= 0); + + /* Test what happens if there are no files in the directory. */ + r = catalog_update(database, NULL, no_catalog_dirs); + assert_se(r >= 0); + + /* Make sure that we at least have some files loaded or the + catalog_list below will fail. */ + r = catalog_update(database, NULL, catalog_dirs); + assert_se(r >= 0); +} + +static void test_catalog_file_lang(void) { + _cleanup_free_ char *lang = NULL, *lang2 = NULL, *lang3 = NULL, *lang4 = NULL; + + assert_se(catalog_file_lang("systemd.de_DE.catalog", &lang) == 1); + assert_se(streq(lang, "de_DE")); + + assert_se(catalog_file_lang("systemd..catalog", &lang2) == 0); + assert_se(lang2 == NULL); + + assert_se(catalog_file_lang("systemd.fr.catalog", &lang2) == 1); + assert_se(streq(lang2, "fr")); + + assert_se(catalog_file_lang("systemd.fr.catalog.gz", &lang3) == 0); + assert_se(lang3 == NULL); + + assert_se(catalog_file_lang("systemd.01234567890123456789012345678901.catalog", &lang3) == 0); + assert_se(lang3 == NULL); + + assert_se(catalog_file_lang("systemd.0123456789012345678901234567890.catalog", &lang3) == 1); + assert_se(streq(lang3, "0123456789012345678901234567890")); + + assert_se(catalog_file_lang("/x/y/systemd.catalog", &lang4) == 0); + assert_se(lang4 == NULL); + + assert_se(catalog_file_lang("/x/y/systemd.ru_RU.catalog", &lang4) == 1); + assert_se(streq(lang4, "ru_RU")); +} + +int main(int argc, char *argv[]) { + _cleanup_free_ char *text = NULL; + int r; + + setlocale(LC_ALL, "de_DE.UTF-8"); + + log_parse_environment(); + log_open(); + + test_catalog_file_lang(); + + test_catalog_import_invalid(); + test_catalog_import_badid(); + test_catalog_import_one(); + test_catalog_import_merge(); + test_catalog_import_merge_no_body(); + + test_catalog_update(); + + r = catalog_list(stdout, database, true); + assert_se(r >= 0); + + r = catalog_list(stdout, database, false); + assert_se(r >= 0); + + assert_se(catalog_get(database, SD_MESSAGE_COREDUMP, &text) >= 0); + printf(">>>%s<<<\n", text); + + if (database) + unlink(database); + + return 0; +} diff --git a/src/grp-journal/libjournal-core/test-compress-benchmark.c b/src/grp-journal/libjournal-core/test-compress-benchmark.c new file mode 100644 index 0000000000..6f6d71435d --- /dev/null +++ b/src/grp-journal/libjournal-core/test-compress-benchmark.c @@ -0,0 +1,180 @@ +/*** + This file is part of systemd + + Copyright 2014 Zbigniew Jędrzejewski-Szmek + + 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 "alloc-util.h" +#include "compress.h" +#include "macro.h" +#include "parse-util.h" +#include "random-util.h" +#include "string-util.h" +#include "util.h" + +typedef int (compress_t)(const void *src, uint64_t src_size, void *dst, + size_t dst_alloc_size, size_t *dst_size); +typedef int (decompress_t)(const void *src, uint64_t src_size, + void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max); + +static usec_t arg_duration = 2 * USEC_PER_SEC; +static size_t arg_start; + +#define MAX_SIZE (1024*1024LU) +#define PRIME 1048571 /* A prime close enough to one megabyte that mod 4 == 3 */ + +static size_t _permute(size_t x) { + size_t residue; + + if (x >= PRIME) + return x; + + residue = x*x % PRIME; + if (x <= PRIME / 2) + return residue; + else + return PRIME - residue; +} + +static size_t permute(size_t x) { + return _permute((_permute(x) + arg_start) % MAX_SIZE ^ 0xFF345); +} + +static char* make_buf(size_t count, const char *type) { + char *buf; + size_t i; + + buf = malloc(count); + assert_se(buf); + + if (streq(type, "zeros")) + memzero(buf, count); + else if (streq(type, "simple")) + for (i = 0; i < count; i++) + buf[i] = 'a' + i % ('z' - 'a' + 1); + else if (streq(type, "random")) { + size_t step = count / 10; + + random_bytes(buf, step); + memzero(buf + 1*step, step); + random_bytes(buf + 2*step, step); + memzero(buf + 3*step, step); + random_bytes(buf + 4*step, step); + memzero(buf + 5*step, step); + random_bytes(buf + 6*step, step); + memzero(buf + 7*step, step); + random_bytes(buf + 8*step, step); + memzero(buf + 9*step, step); + } else + assert_not_reached("here"); + + return buf; +} + +static void test_compress_decompress(const char* label, const char* type, + compress_t compress, decompress_t decompress) { + usec_t n, n2 = 0; + float dt; + + _cleanup_free_ char *text, *buf; + _cleanup_free_ void *buf2 = NULL; + size_t buf2_allocated = 0; + size_t skipped = 0, compressed = 0, total = 0; + + text = make_buf(MAX_SIZE, type); + buf = calloc(MAX_SIZE + 1, 1); + assert_se(text && buf); + + n = now(CLOCK_MONOTONIC); + + for (size_t i = 0; i <= MAX_SIZE; i++) { + size_t j = 0, k = 0, size; + int r; + + size = permute(i); + if (size == 0) + continue; + + log_debug("%s %zu %zu", type, i, size); + + memzero(buf, MIN(size + 1000, MAX_SIZE)); + + r = compress(text, size, buf, size, &j); + /* assume compression must be successful except for small or random inputs */ + assert_se(r == 0 || (size < 2048 && r == -ENOBUFS) || streq(type, "random")); + + /* check for overwrites */ + assert_se(buf[size] == 0); + if (r != 0) { + skipped += size; + continue; + } + + assert_se(j > 0); + if (j >= size) + log_error("%s \"compressed\" %zu -> %zu", label, size, j); + + r = decompress(buf, j, &buf2, &buf2_allocated, &k, 0); + assert_se(r == 0); + assert_se(buf2_allocated >= k); + assert_se(k == size); + + assert_se(memcmp(text, buf2, size) == 0); + + total += size; + compressed += j; + + n2 = now(CLOCK_MONOTONIC); + if (n2 - n > arg_duration) + break; + } + + dt = (n2-n) / 1e6; + + log_info("%s/%s: compressed & decompressed %zu bytes in %.2fs (%.2fMiB/s), " + "mean compresion %.2f%%, skipped %zu bytes", + label, type, total, dt, + total / 1024. / 1024 / dt, + 100 - compressed * 100. / total, + skipped); +} + +int main(int argc, char *argv[]) { + const char *i; + + log_set_max_level(LOG_INFO); + + if (argc >= 2) { + unsigned x; + + assert_se(safe_atou(argv[1], &x) >= 0); + arg_duration = x * USEC_PER_SEC; + } + if (argc == 3) + (void) safe_atozu(argv[2], &arg_start); + else + arg_start = getpid(); + + NULSTR_FOREACH(i, "zeros\0simple\0random\0") { +#ifdef HAVE_XZ + test_compress_decompress("XZ", i, compress_blob_xz, decompress_blob_xz); +#endif +#ifdef HAVE_LZ4 + test_compress_decompress("LZ4", i, compress_blob_lz4, decompress_blob_lz4); +#endif + } + return 0; +} diff --git a/src/grp-journal/libjournal-core/test-compress.c b/src/grp-journal/libjournal-core/test-compress.c new file mode 100644 index 0000000000..68c9a4d76c --- /dev/null +++ b/src/grp-journal/libjournal-core/test-compress.c @@ -0,0 +1,308 @@ +/*** + This file is part of systemd + + Copyright 2014 Ronny Chevalier + + 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 . +***/ + +#ifdef HAVE_LZ4 +#include +#endif + +#include "alloc-util.h" +#include "compress.h" +#include "fd-util.h" +#include "fileio.h" +#include "macro.h" +#include "random-util.h" +#include "util.h" + +#ifdef HAVE_XZ +# define XZ_OK 0 +#else +# define XZ_OK -EPROTONOSUPPORT +#endif + +#ifdef HAVE_LZ4 +# define LZ4_OK 0 +#else +# define LZ4_OK -EPROTONOSUPPORT +#endif + +typedef int (compress_blob_t)(const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size); +typedef int (decompress_blob_t)(const void *src, uint64_t src_size, + void **dst, size_t *dst_alloc_size, + size_t* dst_size, size_t dst_max); +typedef int (decompress_sw_t)(const void *src, uint64_t src_size, + void **buffer, size_t *buffer_size, + const void *prefix, size_t prefix_len, + uint8_t extra); + +typedef int (compress_stream_t)(int fdf, int fdt, uint64_t max_bytes); +typedef int (decompress_stream_t)(int fdf, int fdt, uint64_t max_size); + +static void test_compress_decompress(int compression, + compress_blob_t compress, + decompress_blob_t decompress, + const char *data, + size_t data_len, + bool may_fail) { + char compressed[512]; + size_t csize, usize = 0; + _cleanup_free_ char *decompressed = NULL; + int r; + + log_info("/* testing %s %s blob compression/decompression */", + object_compressed_to_string(compression), data); + + r = compress(data, data_len, compressed, sizeof(compressed), &csize); + if (r == -ENOBUFS) { + log_info_errno(r, "compression failed: %m"); + assert_se(may_fail); + } else { + assert_se(r == 0); + r = decompress(compressed, csize, + (void **) &decompressed, &usize, &csize, 0); + assert_se(r == 0); + assert_se(decompressed); + assert_se(memcmp(decompressed, data, data_len) == 0); + } + + r = decompress("garbage", 7, + (void **) &decompressed, &usize, &csize, 0); + assert_se(r < 0); + + /* make sure to have the minimal lz4 compressed size */ + r = decompress("00000000\1g", 9, + (void **) &decompressed, &usize, &csize, 0); + assert_se(r < 0); + + r = decompress("\100000000g", 9, + (void **) &decompressed, &usize, &csize, 0); + assert_se(r < 0); + + memzero(decompressed, usize); +} + +static void test_decompress_startswith(int compression, + compress_blob_t compress, + decompress_sw_t decompress_sw, + const char *data, + size_t data_len, + bool may_fail) { + + char *compressed; + _cleanup_free_ char *compressed1 = NULL, *compressed2 = NULL, *decompressed = NULL; + size_t csize, usize = 0, len; + int r; + + log_info("/* testing decompress_startswith with %s on %.20s text*/", + object_compressed_to_string(compression), data); + +#define BUFSIZE_1 512 +#define BUFSIZE_2 20000 + + compressed = compressed1 = malloc(BUFSIZE_1); + assert_se(compressed1); + r = compress(data, data_len, compressed, BUFSIZE_1, &csize); + if (r == -ENOBUFS) { + log_info_errno(r, "compression failed: %m"); + assert_se(may_fail); + + compressed = compressed2 = malloc(BUFSIZE_2); + assert_se(compressed2); + r = compress(data, data_len, compressed, BUFSIZE_2, &csize); + assert(r == 0); + } + assert_se(r == 0); + + len = strlen(data); + + r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len, '\0'); + assert_se(r > 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len, 'w'); + assert_se(r == 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, "barbarbar", 9, ' '); + assert_se(r == 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len - 1, data[len-1]); + assert_se(r > 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len - 1, 'w'); + assert_se(r == 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len, '\0'); + assert_se(r > 0); +} + +static void test_compress_stream(int compression, + const char* cat, + compress_stream_t compress, + decompress_stream_t decompress, + const char *srcfile) { + + _cleanup_close_ int src = -1, dst = -1, dst2 = -1; + char pattern[] = "/tmp/systemd-test.compressed.XXXXXX", + pattern2[] = "/tmp/systemd-test.compressed.XXXXXX"; + int r; + _cleanup_free_ char *cmd = NULL, *cmd2; + struct stat st = {}; + + log_debug("/* testing %s compression */", + object_compressed_to_string(compression)); + + log_debug("/* create source from %s */", srcfile); + + assert_se((src = open(srcfile, O_RDONLY|O_CLOEXEC)) >= 0); + + log_debug("/* test compression */"); + + assert_se((dst = mkostemp_safe(pattern, O_RDWR|O_CLOEXEC)) >= 0); + + assert_se(compress(src, dst, -1) == 0); + + if (cat) { + assert_se(asprintf(&cmd, "%s %s | diff %s -", cat, pattern, srcfile) > 0); + assert_se(system(cmd) == 0); + } + + log_debug("/* test decompression */"); + + assert_se((dst2 = mkostemp_safe(pattern2, O_RDWR|O_CLOEXEC)) >= 0); + + assert_se(stat(srcfile, &st) == 0); + + assert_se(lseek(dst, 0, SEEK_SET) == 0); + r = decompress(dst, dst2, st.st_size); + assert_se(r == 0); + + assert_se(asprintf(&cmd2, "diff %s %s", srcfile, pattern2) > 0); + assert_se(system(cmd2) == 0); + + log_debug("/* test faulty decompression */"); + + assert_se(lseek(dst, 1, SEEK_SET) == 1); + r = decompress(dst, dst2, st.st_size); + assert_se(r == -EBADMSG || r == 0); + + assert_se(lseek(dst, 0, SEEK_SET) == 0); + assert_se(lseek(dst2, 0, SEEK_SET) == 0); + r = decompress(dst, dst2, st.st_size - 1); + assert_se(r == -EFBIG); + + assert_se(unlink(pattern) == 0); + assert_se(unlink(pattern2) == 0); +} + +#ifdef HAVE_LZ4 +static void test_lz4_decompress_partial(void) { + char buf[20000]; + size_t buf_size = sizeof(buf), compressed; + int r; + _cleanup_free_ char *huge = NULL; + +#define HUGE_SIZE (4096*1024) + huge = malloc(HUGE_SIZE); + memset(huge, 'x', HUGE_SIZE); + memcpy(huge, "HUGE=", 5); + + r = LZ4_compress_limitedOutput(huge, buf, HUGE_SIZE, buf_size); + assert_se(r >= 0); + compressed = r; + log_info("Compressed %i → %zu", HUGE_SIZE, compressed); + + r = LZ4_decompress_safe(buf, huge, r, HUGE_SIZE); + assert_se(r >= 0); + log_info("Decompressed → %i", r); + + r = LZ4_decompress_safe_partial(buf, huge, + compressed, + 12, HUGE_SIZE); + assert_se(r >= 0); + log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE, r); + + /* We expect this to fail, because that's how current lz4 works. If this + * call succeeds, then lz4 has been fixed, and we need to change our code. + */ + r = LZ4_decompress_safe_partial(buf, huge, + compressed, + 12, HUGE_SIZE-1); + assert_se(r < 0); + log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE-1, r); +} +#endif + +int main(int argc, char *argv[]) { + const char text[] = + "text\0foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF" + "foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF"; + + char data[512] = "random\0"; + + char huge[4096*1024]; + memset(huge, 'x', sizeof(huge)); + memcpy(huge, "HUGE=", 5); + char_array_0(huge); + + log_set_max_level(LOG_DEBUG); + + random_bytes(data + 7, sizeof(data) - 7); + +#ifdef HAVE_XZ + test_compress_decompress(OBJECT_COMPRESSED_XZ, compress_blob_xz, decompress_blob_xz, + text, sizeof(text), false); + test_compress_decompress(OBJECT_COMPRESSED_XZ, compress_blob_xz, decompress_blob_xz, + data, sizeof(data), true); + + test_decompress_startswith(OBJECT_COMPRESSED_XZ, + compress_blob_xz, decompress_startswith_xz, + text, sizeof(text), false); + test_decompress_startswith(OBJECT_COMPRESSED_XZ, + compress_blob_xz, decompress_startswith_xz, + data, sizeof(data), true); + test_decompress_startswith(OBJECT_COMPRESSED_XZ, + compress_blob_xz, decompress_startswith_xz, + huge, sizeof(huge), true); + + test_compress_stream(OBJECT_COMPRESSED_XZ, "xzcat", + compress_stream_xz, decompress_stream_xz, argv[0]); +#else + log_info("/* XZ test skipped */"); +#endif + +#ifdef HAVE_LZ4 + test_compress_decompress(OBJECT_COMPRESSED_LZ4, compress_blob_lz4, decompress_blob_lz4, + text, sizeof(text), false); + test_compress_decompress(OBJECT_COMPRESSED_LZ4, compress_blob_lz4, decompress_blob_lz4, + data, sizeof(data), true); + + test_decompress_startswith(OBJECT_COMPRESSED_LZ4, + compress_blob_lz4, decompress_startswith_lz4, + text, sizeof(text), false); + test_decompress_startswith(OBJECT_COMPRESSED_LZ4, + compress_blob_lz4, decompress_startswith_lz4, + data, sizeof(data), true); + test_decompress_startswith(OBJECT_COMPRESSED_LZ4, + compress_blob_lz4, decompress_startswith_lz4, + huge, sizeof(huge), true); + + test_compress_stream(OBJECT_COMPRESSED_LZ4, "lz4cat", + compress_stream_lz4, decompress_stream_lz4, argv[0]); + + test_lz4_decompress_partial(); +#else + log_info("/* LZ4 test skipped */"); +#endif + + return 0; +} diff --git a/src/grp-journal/libjournal-core/test-journal-enum.c b/src/grp-journal/libjournal-core/test-journal-enum.c new file mode 100644 index 0000000000..54df59f477 --- /dev/null +++ b/src/grp-journal/libjournal-core/test-journal-enum.c @@ -0,0 +1,53 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 + +#include "journal-internal.h" +#include "log.h" +#include "macro.h" + +int main(int argc, char *argv[]) { + unsigned n = 0; + _cleanup_(sd_journal_closep) sd_journal*j = NULL; + + log_set_max_level(LOG_DEBUG); + + assert_se(sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY) >= 0); + + assert_se(sd_journal_add_match(j, "_TRANSPORT=syslog", 0) >= 0); + assert_se(sd_journal_add_match(j, "_UID=0", 0) >= 0); + + SD_JOURNAL_FOREACH_BACKWARDS(j) { + const void *d; + size_t l; + + assert_se(sd_journal_get_data(j, "MESSAGE", &d, &l) >= 0); + + printf("%.*s\n", (int) l, (char*) d); + + n++; + if (n >= 10) + break; + } + + return 0; +} diff --git a/src/grp-journal/libjournal-core/test-journal-flush.c b/src/grp-journal/libjournal-core/test-journal-flush.c new file mode 100644 index 0000000000..7e9814f8fa --- /dev/null +++ b/src/grp-journal/libjournal-core/test-journal-flush.c @@ -0,0 +1,75 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include "alloc-util.h" +#include "journal-file.h" +#include "journal-internal.h" +#include "macro.h" +#include "string-util.h" + +int main(int argc, char *argv[]) { + _cleanup_free_ char *fn = NULL; + char dn[] = "/var/tmp/test-journal-flush.XXXXXX"; + JournalFile *new_journal = NULL; + sd_journal *j = NULL; + unsigned n = 0; + int r; + + assert_se(mkdtemp(dn)); + fn = strappend(dn, "/test.journal"); + + r = journal_file_open(-1, fn, O_CREAT|O_RDWR, 0644, false, false, NULL, NULL, NULL, NULL, &new_journal); + assert_se(r >= 0); + + r = sd_journal_open(&j, 0); + assert_se(r >= 0); + + sd_journal_set_data_threshold(j, 0); + + SD_JOURNAL_FOREACH(j) { + Object *o; + JournalFile *f; + + f = j->current_file; + assert_se(f && f->current_offset > 0); + + r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); + assert_se(r >= 0); + + r = journal_file_copy_entry(f, new_journal, o, f->current_offset, NULL, NULL, NULL); + assert_se(r >= 0); + + n++; + if (n > 10000) + break; + } + + sd_journal_close(j); + + (void) journal_file_close(new_journal); + + unlink(fn); + assert_se(rmdir(dn) == 0); + + return 0; +} diff --git a/src/grp-journal/libjournal-core/test-journal-init.c b/src/grp-journal/libjournal-core/test-journal-init.c new file mode 100644 index 0000000000..e6713034dd --- /dev/null +++ b/src/grp-journal/libjournal-core/test-journal-init.c @@ -0,0 +1,64 @@ +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + 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 "log.h" +#include "parse-util.h" +#include "rm-rf.h" +#include "util.h" + +int main(int argc, char *argv[]) { + sd_journal *j; + int r, i, I = 100; + char t[] = "/tmp/journal-stream-XXXXXX"; + + log_set_max_level(LOG_DEBUG); + + if (argc >= 2) { + r = safe_atoi(argv[1], &I); + if (r < 0) + log_info("Could not parse loop count argument. Using default."); + } + + log_info("Running %d loops", I); + + assert_se(mkdtemp(t)); + + for (i = 0; i < I; i++) { + r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); + assert_se(r == 0); + + sd_journal_close(j); + + r = sd_journal_open_directory(&j, t, 0); + assert_se(r == 0); + + sd_journal_close(j); + + j = NULL; + r = sd_journal_open_directory(&j, t, SD_JOURNAL_LOCAL_ONLY); + assert_se(r == -EINVAL); + assert_se(j == NULL); + } + + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + + return 0; +} diff --git a/src/grp-journal/libjournal-core/test-journal-interleaving.c b/src/grp-journal/libjournal-core/test-journal-interleaving.c new file mode 100644 index 0000000000..d09ef011a6 --- /dev/null +++ b/src/grp-journal/libjournal-core/test-journal-interleaving.c @@ -0,0 +1,307 @@ +/*** + This file is part of systemd. + + Copyright 2013 Marius Vollmer + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + 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 + +#include + +#include "alloc-util.h" +#include "journal-file.h" +#include "journal-vacuum.h" +#include "log.h" +#include "parse-util.h" +#include "rm-rf.h" +#include "util.h" + +/* This program tests skipping around in a multi-file journal. + */ + +static bool arg_keep = false; + +noreturn static void log_assert_errno(const char *text, int eno, const char *file, int line, const char *func) { + log_internal(LOG_CRIT, 0, file, line, func, + "'%s' failed at %s:%u (%s): %s.", + text, file, line, func, strerror(eno)); + abort(); +} + +#define assert_ret(expr) \ + do { \ + int _r_ = (expr); \ + if (_unlikely_(_r_ < 0)) \ + log_assert_errno(#expr, -_r_, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ + } while (false) + +static JournalFile *test_open(const char *name) { + JournalFile *f; + assert_ret(journal_file_open(-1, name, O_RDWR|O_CREAT, 0644, true, false, NULL, NULL, NULL, NULL, &f)); + return f; +} + +static void test_close(JournalFile *f) { + (void) journal_file_close (f); +} + +static void append_number(JournalFile *f, int n, uint64_t *seqnum) { + char *p; + dual_timestamp ts; + static dual_timestamp previous_ts = {}; + struct iovec iovec[1]; + + dual_timestamp_get(&ts); + + if (ts.monotonic <= previous_ts.monotonic) + ts.monotonic = previous_ts.monotonic + 1; + + if (ts.realtime <= previous_ts.realtime) + ts.realtime = previous_ts.realtime + 1; + + previous_ts = ts; + + assert_se(asprintf(&p, "NUMBER=%d", n) >= 0); + iovec[0].iov_base = p; + iovec[0].iov_len = strlen(p); + assert_ret(journal_file_append_entry(f, &ts, iovec, 1, seqnum, NULL, NULL)); + free(p); +} + +static void test_check_number (sd_journal *j, int n) { + const void *d; + _cleanup_free_ char *k; + size_t l; + int x; + + assert_ret(sd_journal_get_data(j, "NUMBER", &d, &l)); + assert_se(k = strndup(d, l)); + printf("%s\n", k); + + assert_se(safe_atoi(k + 7, &x) >= 0); + assert_se(n == x); +} + +static void test_check_numbers_down (sd_journal *j, int count) { + int i; + + for (i = 1; i <= count; i++) { + int r; + test_check_number(j, i); + assert_ret(r = sd_journal_next(j)); + if (i == count) + assert_se(r == 0); + else + assert_se(r == 1); + } + +} + +static void test_check_numbers_up (sd_journal *j, int count) { + for (int i = count; i >= 1; i--) { + int r; + test_check_number(j, i); + assert_ret(r = sd_journal_previous(j)); + if (i == 1) + assert_se(r == 0); + else + assert_se(r == 1); + } + +} + +static void setup_sequential(void) { + JournalFile *one, *two; + one = test_open("one.journal"); + two = test_open("two.journal"); + append_number(one, 1, NULL); + append_number(one, 2, NULL); + append_number(two, 3, NULL); + append_number(two, 4, NULL); + test_close(one); + test_close(two); +} + +static void setup_interleaved(void) { + JournalFile *one, *two; + one = test_open("one.journal"); + two = test_open("two.journal"); + append_number(one, 1, NULL); + append_number(two, 2, NULL); + append_number(one, 3, NULL); + append_number(two, 4, NULL); + test_close(one); + test_close(two); +} + +static void test_skip(void (*setup)(void)) { + char t[] = "/tmp/journal-skip-XXXXXX"; + sd_journal *j; + int r; + + assert_se(mkdtemp(t)); + assert_se(chdir(t) >= 0); + + setup(); + + /* Seek to head, iterate down. + */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_head(j)); + assert_ret(sd_journal_next(j)); + test_check_numbers_down(j, 4); + sd_journal_close(j); + + /* Seek to tail, iterate up. + */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_tail(j)); + assert_ret(sd_journal_previous(j)); + test_check_numbers_up(j, 4); + sd_journal_close(j); + + /* Seek to tail, skip to head, iterate down. + */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_tail(j)); + assert_ret(r = sd_journal_previous_skip(j, 4)); + assert_se(r == 4); + test_check_numbers_down(j, 4); + sd_journal_close(j); + + /* Seek to head, skip to tail, iterate up. + */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_head(j)); + assert_ret(r = sd_journal_next_skip(j, 4)); + assert_se(r == 4); + test_check_numbers_up(j, 4); + sd_journal_close(j); + + log_info("Done..."); + + if (arg_keep) + log_info("Not removing %s", t); + else { + journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); + + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + } + + puts("------------------------------------------------------------"); +} + +static void test_sequence_numbers(void) { + + char t[] = "/tmp/journal-seq-XXXXXX"; + JournalFile *one, *two; + uint64_t seqnum = 0; + sd_id128_t seqnum_id; + + assert_se(mkdtemp(t)); + assert_se(chdir(t) >= 0); + + assert_se(journal_file_open(-1, "one.journal", O_RDWR|O_CREAT, 0644, + true, false, NULL, NULL, NULL, NULL, &one) == 0); + + append_number(one, 1, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert_se(seqnum == 1); + append_number(one, 2, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert_se(seqnum == 2); + + assert_se(one->header->state == STATE_ONLINE); + assert_se(!sd_id128_equal(one->header->file_id, one->header->machine_id)); + assert_se(!sd_id128_equal(one->header->file_id, one->header->boot_id)); + assert_se(sd_id128_equal(one->header->file_id, one->header->seqnum_id)); + + memcpy(&seqnum_id, &one->header->seqnum_id, sizeof(sd_id128_t)); + + assert_se(journal_file_open(-1, "two.journal", O_RDWR|O_CREAT, 0644, + true, false, NULL, NULL, NULL, one, &two) == 0); + + assert_se(two->header->state == STATE_ONLINE); + assert_se(!sd_id128_equal(two->header->file_id, one->header->file_id)); + assert_se(sd_id128_equal(one->header->machine_id, one->header->machine_id)); + assert_se(sd_id128_equal(one->header->boot_id, one->header->boot_id)); + assert_se(sd_id128_equal(one->header->seqnum_id, one->header->seqnum_id)); + + append_number(two, 3, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert_se(seqnum == 3); + append_number(two, 4, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert_se(seqnum == 4); + + test_close(two); + + append_number(one, 5, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert_se(seqnum == 5); + + append_number(one, 6, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert_se(seqnum == 6); + + test_close(one); + + /* restart server */ + seqnum = 0; + + assert_se(journal_file_open(-1, "two.journal", O_RDWR, 0, + true, false, NULL, NULL, NULL, NULL, &two) == 0); + + assert_se(sd_id128_equal(two->header->seqnum_id, seqnum_id)); + + append_number(two, 7, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert_se(seqnum == 5); + + /* So..., here we have the same seqnum in two files with the + * same seqnum_id. */ + + test_close(two); + + log_info("Done..."); + + if (arg_keep) + log_info("Not removing %s", t); + else { + journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); + + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + } +} + +int main(int argc, char *argv[]) { + log_set_max_level(LOG_DEBUG); + + /* journal_file_open requires a valid machine id */ + if (access("/etc/machine-id", F_OK) != 0) + return EXIT_TEST_SKIP; + + arg_keep = argc > 1; + + test_skip(setup_sequential); + test_skip(setup_interleaved); + + test_sequence_numbers(); + + return 0; +} diff --git a/src/grp-journal/libjournal-core/test-journal-match.c b/src/grp-journal/libjournal-core/test-journal-match.c new file mode 100644 index 0000000000..5ee2adb827 --- /dev/null +++ b/src/grp-journal/libjournal-core/test-journal-match.c @@ -0,0 +1,76 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 + +#include "alloc-util.h" +#include "journal-internal.h" +#include "log.h" +#include "string-util.h" +#include "util.h" + +int main(int argc, char *argv[]) { + _cleanup_(sd_journal_closep) sd_journal*j = NULL; + _cleanup_free_ char *t; + + log_set_max_level(LOG_DEBUG); + + assert_se(sd_journal_open(&j, 0) >= 0); + + assert_se(sd_journal_add_match(j, "foobar", 0) < 0); + assert_se(sd_journal_add_match(j, "foobar=waldo", 0) < 0); + assert_se(sd_journal_add_match(j, "", 0) < 0); + assert_se(sd_journal_add_match(j, "=", 0) < 0); + assert_se(sd_journal_add_match(j, "=xxxxx", 0) < 0); + assert_se(sd_journal_add_match(j, "HALLO=WALDO", 0) >= 0); + assert_se(sd_journal_add_match(j, "QUUX=mmmm", 0) >= 0); + assert_se(sd_journal_add_match(j, "QUUX=xxxxx", 0) >= 0); + assert_se(sd_journal_add_match(j, "HALLO=", 0) >= 0); + assert_se(sd_journal_add_match(j, "QUUX=xxxxx", 0) >= 0); + assert_se(sd_journal_add_match(j, "QUUX=yyyyy", 0) >= 0); + assert_se(sd_journal_add_match(j, "PIFF=paff", 0) >= 0); + + assert_se(sd_journal_add_disjunction(j) >= 0); + + assert_se(sd_journal_add_match(j, "ONE=one", 0) >= 0); + assert_se(sd_journal_add_match(j, "ONE=two", 0) >= 0); + assert_se(sd_journal_add_match(j, "TWO=two", 0) >= 0); + + assert_se(sd_journal_add_conjunction(j) >= 0); + + assert_se(sd_journal_add_match(j, "L4_1=yes", 0) >= 0); + assert_se(sd_journal_add_match(j, "L4_1=ok", 0) >= 0); + assert_se(sd_journal_add_match(j, "L4_2=yes", 0) >= 0); + assert_se(sd_journal_add_match(j, "L4_2=ok", 0) >= 0); + + assert_se(sd_journal_add_disjunction(j) >= 0); + + assert_se(sd_journal_add_match(j, "L3=yes", 0) >= 0); + assert_se(sd_journal_add_match(j, "L3=ok", 0) >= 0); + + assert_se(t = journal_make_match_string(j)); + + printf("resulting match expression is: %s\n", t); + + assert_se(streq(t, "(((L3=ok OR L3=yes) OR ((L4_2=ok OR L4_2=yes) AND (L4_1=ok OR L4_1=yes))) AND ((TWO=two AND (ONE=two OR ONE=one)) OR (PIFF=paff AND (QUUX=yyyyy OR QUUX=xxxxx OR QUUX=mmmm) AND (HALLO= OR HALLO=WALDO))))")); + + return 0; +} diff --git a/src/grp-journal/libjournal-core/test-journal-send.c b/src/grp-journal/libjournal-core/test-journal-send.c new file mode 100644 index 0000000000..169082f9a4 --- /dev/null +++ b/src/grp-journal/libjournal-core/test-journal-send.c @@ -0,0 +1,102 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include + +#include + +#include "macro.h" + +int main(int argc, char *argv[]) { + char huge[4096*1024]; + + /* utf-8 and non-utf-8, message-less and message-ful iovecs */ + struct iovec graph1[] = { + {(char*) "GRAPH=graph", strlen("GRAPH=graph")} + }; + struct iovec graph2[] = { + {(char*) "GRAPH=graph\n", strlen("GRAPH=graph\n")} + }; + struct iovec message1[] = { + {(char*) "MESSAGE=graph", strlen("MESSAGE=graph")} + }; + struct iovec message2[] = { + {(char*) "MESSAGE=graph\n", strlen("MESSAGE=graph\n")} + }; + + assert_se(sd_journal_print(LOG_INFO, "piepapo") == 0); + + assert_se(sd_journal_send("MESSAGE=foobar", + "VALUE=%i", 7, + NULL) == 0); + + errno = ENOENT; + assert_se(sd_journal_perror("Foobar") == 0); + + assert_se(sd_journal_perror("") == 0); + + memset(huge, 'x', sizeof(huge)); + memcpy(huge, "HUGE=", 5); + char_array_0(huge); + + assert_se(sd_journal_send("MESSAGE=Huge field attached", + huge, + NULL) == 0); + + assert_se(sd_journal_send("MESSAGE=uiui", + "VALUE=A", + "VALUE=B", + "VALUE=C", + "SINGLETON=1", + "OTHERVALUE=X", + "OTHERVALUE=Y", + "WITH_BINARY=this is a binary value \a", + NULL) == 0); + + syslog(LOG_NOTICE, "Hello World!"); + + assert_se(sd_journal_print(LOG_NOTICE, "Hello World") == 0); + + assert_se(sd_journal_send("MESSAGE=Hello World!", + "MESSAGE_ID=52fb62f99e2c49d89cfbf9d6de5e3555", + "PRIORITY=5", + "HOME=%s", getenv("HOME"), + "TERM=%s", getenv("TERM"), + "PAGE_SIZE=%li", sysconf(_SC_PAGESIZE), + "N_CPUS=%li", sysconf(_SC_NPROCESSORS_ONLN), + NULL) == 0); + + assert_se(sd_journal_sendv(graph1, 1) == 0); + assert_se(sd_journal_sendv(graph2, 1) == 0); + assert_se(sd_journal_sendv(message1, 1) == 0); + assert_se(sd_journal_sendv(message2, 1) == 0); + + /* test without location fields */ +#undef sd_journal_sendv + assert_se(sd_journal_sendv(graph1, 1) == 0); + assert_se(sd_journal_sendv(graph2, 1) == 0); + assert_se(sd_journal_sendv(message1, 1) == 0); + assert_se(sd_journal_sendv(message2, 1) == 0); + + sleep(1); + + return 0; +} diff --git a/src/grp-journal/libjournal-core/test-journal-stream.c b/src/grp-journal/libjournal-core/test-journal-stream.c new file mode 100644 index 0000000000..0a1da47861 --- /dev/null +++ b/src/grp-journal/libjournal-core/test-journal-stream.c @@ -0,0 +1,196 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 + +#include + +#include "alloc-util.h" +#include "journal-file.h" +#include "journal-internal.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "rm-rf.h" +#include "util.h" + +#define N_ENTRIES 200 + +static void verify_contents(sd_journal *j, unsigned skip) { + unsigned i; + + assert_se(j); + + i = 0; + SD_JOURNAL_FOREACH(j) { + const void *d; + char *k, *c; + size_t l; + unsigned u = 0; + + assert_se(sd_journal_get_cursor(j, &k) >= 0); + printf("cursor: %s\n", k); + free(k); + + assert_se(sd_journal_get_data(j, "MAGIC", &d, &l) >= 0); + printf("\t%.*s\n", (int) l, (const char*) d); + + assert_se(sd_journal_get_data(j, "NUMBER", &d, &l) >= 0); + assert_se(k = strndup(d, l)); + printf("\t%s\n", k); + + if (skip > 0) { + assert_se(safe_atou(k + 7, &u) >= 0); + assert_se(i == u); + i += skip; + } + + free(k); + + assert_se(sd_journal_get_cursor(j, &c) >= 0); + assert_se(sd_journal_test_cursor(j, c) > 0); + free(c); + } + + if (skip > 0) + assert_se(i == N_ENTRIES); +} + +int main(int argc, char *argv[]) { + JournalFile *one, *two, *three; + char t[] = "/tmp/journal-stream-XXXXXX"; + unsigned i; + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + char *z; + const void *data; + size_t l; + dual_timestamp previous_ts = DUAL_TIMESTAMP_NULL; + + /* journal_file_open requires a valid machine id */ + if (access("/etc/machine-id", F_OK) != 0) + return EXIT_TEST_SKIP; + + log_set_max_level(LOG_DEBUG); + + assert_se(mkdtemp(t)); + assert_se(chdir(t) >= 0); + + assert_se(journal_file_open(-1, "one.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &one) == 0); + assert_se(journal_file_open(-1, "two.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &two) == 0); + assert_se(journal_file_open(-1, "three.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &three) == 0); + + for (i = 0; i < N_ENTRIES; i++) { + char *p, *q; + dual_timestamp ts; + struct iovec iovec[2]; + + dual_timestamp_get(&ts); + + if (ts.monotonic <= previous_ts.monotonic) + ts.monotonic = previous_ts.monotonic + 1; + + if (ts.realtime <= previous_ts.realtime) + ts.realtime = previous_ts.realtime + 1; + + previous_ts = ts; + + assert_se(asprintf(&p, "NUMBER=%u", i) >= 0); + iovec[0].iov_base = p; + iovec[0].iov_len = strlen(p); + + assert_se(asprintf(&q, "MAGIC=%s", i % 5 == 0 ? "quux" : "waldo") >= 0); + + iovec[1].iov_base = q; + iovec[1].iov_len = strlen(q); + + if (i % 10 == 0) + assert_se(journal_file_append_entry(three, &ts, iovec, 2, NULL, NULL, NULL) == 0); + else { + if (i % 3 == 0) + assert_se(journal_file_append_entry(two, &ts, iovec, 2, NULL, NULL, NULL) == 0); + + assert_se(journal_file_append_entry(one, &ts, iovec, 2, NULL, NULL, NULL) == 0); + } + + free(p); + free(q); + } + + (void) journal_file_close(one); + (void) journal_file_close(two); + (void) journal_file_close(three); + + assert_se(sd_journal_open_directory(&j, t, 0) >= 0); + + assert_se(sd_journal_add_match(j, "MAGIC=quux", 0) >= 0); + SD_JOURNAL_FOREACH_BACKWARDS(j) { + _cleanup_free_ char *c; + + assert_se(sd_journal_get_data(j, "NUMBER", &data, &l) >= 0); + printf("\t%.*s\n", (int) l, (const char*) data); + + assert_se(sd_journal_get_cursor(j, &c) >= 0); + assert_se(sd_journal_test_cursor(j, c) > 0); + } + + SD_JOURNAL_FOREACH(j) { + _cleanup_free_ char *c; + + assert_se(sd_journal_get_data(j, "NUMBER", &data, &l) >= 0); + printf("\t%.*s\n", (int) l, (const char*) data); + + assert_se(sd_journal_get_cursor(j, &c) >= 0); + assert_se(sd_journal_test_cursor(j, c) > 0); + } + + sd_journal_flush_matches(j); + + verify_contents(j, 1); + + printf("NEXT TEST\n"); + assert_se(sd_journal_add_match(j, "MAGIC=quux", 0) >= 0); + + assert_se(z = journal_make_match_string(j)); + printf("resulting match expression is: %s\n", z); + free(z); + + verify_contents(j, 5); + + printf("NEXT TEST\n"); + sd_journal_flush_matches(j); + assert_se(sd_journal_add_match(j, "MAGIC=waldo", 0) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=10", 0) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=11", 0) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=12", 0) >= 0); + + assert_se(z = journal_make_match_string(j)); + printf("resulting match expression is: %s\n", z); + free(z); + + verify_contents(j, 0); + + assert_se(sd_journal_query_unique(j, "NUMBER") >= 0); + SD_JOURNAL_FOREACH_UNIQUE(j, data, l) + printf("%.*s\n", (int) l, (const char*) data); + + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + + return 0; +} diff --git a/src/grp-journal/libjournal-core/test-journal-syslog.c b/src/grp-journal/libjournal-core/test-journal-syslog.c new file mode 100644 index 0000000000..4ff7f3ec2e --- /dev/null +++ b/src/grp-journal/libjournal-core/test-journal-syslog.c @@ -0,0 +1,44 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 "alloc-util.h" +#include "journald-syslog.h" +#include "macro.h" +#include "string-util.h" + +static void test_syslog_parse_identifier(const char* str, + const char *ident, const char*pid, int ret) { + const char *buf = str; + _cleanup_free_ char *ident2 = NULL, *pid2 = NULL; + int ret2; + + ret2 = syslog_parse_identifier(&buf, &ident2, &pid2); + + assert_se(ret == ret2); + assert_se(ident == ident2 || streq_ptr(ident, ident2)); + assert_se(pid == pid2 || streq_ptr(pid, pid2)); +} + +int main(void) { + test_syslog_parse_identifier("pidu[111]: xxx", "pidu", "111", 11); + test_syslog_parse_identifier("pidu: xxx", "pidu", NULL, 6); + test_syslog_parse_identifier("pidu xxx", NULL, NULL, 0); + + return 0; +} diff --git a/src/grp-journal/libjournal-core/test-journal-verify.c b/src/grp-journal/libjournal-core/test-journal-verify.c new file mode 100644 index 0000000000..3d2312fc55 --- /dev/null +++ b/src/grp-journal/libjournal-core/test-journal-verify.c @@ -0,0 +1,150 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 +#include + +#include "fd-util.h" +#include "journal-file.h" +#include "journal-verify.h" +#include "log.h" +#include "rm-rf.h" +#include "terminal-util.h" +#include "util.h" + +#define N_ENTRIES 6000 +#define RANDOM_RANGE 77 + +static void bit_toggle(const char *fn, uint64_t p) { + uint8_t b; + ssize_t r; + int fd; + + fd = open(fn, O_RDWR|O_CLOEXEC); + assert_se(fd >= 0); + + r = pread(fd, &b, 1, p/8); + assert_se(r == 1); + + b ^= 1 << (p % 8); + + r = pwrite(fd, &b, 1, p/8); + assert_se(r == 1); + + safe_close(fd); +} + +static int raw_verify(const char *fn, const char *verification_key) { + JournalFile *f; + int r; + + r = journal_file_open(-1, fn, O_RDONLY, 0666, true, !!verification_key, NULL, NULL, NULL, NULL, &f); + if (r < 0) + return r; + + r = journal_file_verify(f, verification_key, NULL, NULL, NULL, false); + (void) journal_file_close(f); + + return r; +} + +int main(int argc, char *argv[]) { + char t[] = "/tmp/journal-XXXXXX"; + unsigned n; + JournalFile *f; + const char *verification_key = argv[1]; + usec_t from = 0, to = 0, total = 0; + char a[FORMAT_TIMESTAMP_MAX]; + char b[FORMAT_TIMESTAMP_MAX]; + char c[FORMAT_TIMESPAN_MAX]; + struct stat st; + uint64_t p; + + /* journal_file_open requires a valid machine id */ + if (access("/etc/machine-id", F_OK) != 0) + return EXIT_TEST_SKIP; + + log_set_max_level(LOG_DEBUG); + + assert_se(mkdtemp(t)); + assert_se(chdir(t) >= 0); + + log_info("Generating..."); + + assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, 0666, true, !!verification_key, NULL, NULL, NULL, NULL, &f) == 0); + + for (n = 0; n < N_ENTRIES; n++) { + struct iovec iovec; + struct dual_timestamp ts; + char *test; + + dual_timestamp_get(&ts); + + assert_se(asprintf(&test, "RANDOM=%lu", random() % RANDOM_RANGE)); + + iovec.iov_base = (void*) test; + iovec.iov_len = strlen(test); + + assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0); + + free(test); + } + + (void) journal_file_close(f); + + log_info("Verifying..."); + + assert_se(journal_file_open(-1, "test.journal", O_RDONLY, 0666, true, !!verification_key, NULL, NULL, NULL, NULL, &f) == 0); + /* journal_file_print_header(f); */ + journal_file_dump(f); + + assert_se(journal_file_verify(f, verification_key, &from, &to, &total, true) >= 0); + + if (verification_key && JOURNAL_HEADER_SEALED(f->header)) + log_info("=> Validated from %s to %s, %s missing", + format_timestamp(a, sizeof(a), from), + format_timestamp(b, sizeof(b), to), + format_timespan(c, sizeof(c), total > to ? total - to : 0, 0)); + + (void) journal_file_close(f); + + if (verification_key) { + log_info("Toggling bits..."); + + assert_se(stat("test.journal", &st) >= 0); + + for (p = 38448*8+0; p < ((uint64_t) st.st_size * 8); p ++) { + bit_toggle("test.journal", p); + + log_info("[ %"PRIu64"+%"PRIu64"]", p / 8, p % 8); + + if (raw_verify("test.journal", verification_key) >= 0) + log_notice(ANSI_HIGHLIGHT_RED ">>>> %"PRIu64" (bit %"PRIu64") can be toggled without detection." ANSI_NORMAL, p / 8, p % 8); + + bit_toggle("test.journal", p); + } + } + + log_info("Exiting..."); + + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + + return 0; +} diff --git a/src/grp-journal/libjournal-core/test-journal.c b/src/grp-journal/libjournal-core/test-journal.c new file mode 100644 index 0000000000..2543d64b5b --- /dev/null +++ b/src/grp-journal/libjournal-core/test-journal.c @@ -0,0 +1,178 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 + +#include "journal-authenticate.h" +#include "journal-file.h" +#include "journal-vacuum.h" +#include "log.h" +#include "rm-rf.h" + +static bool arg_keep = false; + +static void test_non_empty(void) { + dual_timestamp ts; + JournalFile *f; + struct iovec iovec; + static const char test[] = "TEST1=1", test2[] = "TEST2=2"; + Object *o; + uint64_t p; + char t[] = "/tmp/journal-XXXXXX"; + + log_set_max_level(LOG_DEBUG); + + assert_se(mkdtemp(t)); + assert_se(chdir(t) >= 0); + + assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, 0666, true, true, NULL, NULL, NULL, NULL, &f) == 0); + + dual_timestamp_get(&ts); + + iovec.iov_base = (void*) test; + iovec.iov_len = strlen(test); + assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0); + + iovec.iov_base = (void*) test2; + iovec.iov_len = strlen(test2); + assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0); + + iovec.iov_base = (void*) test; + iovec.iov_len = strlen(test); + assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0); + +#ifdef HAVE_GCRYPT + journal_file_append_tag(f); +#endif + journal_file_dump(f); + + assert_se(journal_file_next_entry(f, 0, DIRECTION_DOWN, &o, &p) == 1); + assert_se(le64toh(o->entry.seqnum) == 1); + + assert_se(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 1); + assert_se(le64toh(o->entry.seqnum) == 2); + + assert_se(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 1); + assert_se(le64toh(o->entry.seqnum) == 3); + + assert_se(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 0); + + assert_se(journal_file_next_entry(f, 0, DIRECTION_DOWN, &o, &p) == 1); + assert_se(le64toh(o->entry.seqnum) == 1); + + assert_se(journal_file_find_data_object(f, test, strlen(test), NULL, &p) == 1); + assert_se(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_DOWN, &o, NULL) == 1); + assert_se(le64toh(o->entry.seqnum) == 1); + + assert_se(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_UP, &o, NULL) == 1); + assert_se(le64toh(o->entry.seqnum) == 3); + + assert_se(journal_file_find_data_object(f, test2, strlen(test2), NULL, &p) == 1); + assert_se(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_UP, &o, NULL) == 1); + assert_se(le64toh(o->entry.seqnum) == 2); + + assert_se(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_DOWN, &o, NULL) == 1); + assert_se(le64toh(o->entry.seqnum) == 2); + + assert_se(journal_file_find_data_object(f, "quux", 4, NULL, &p) == 0); + + assert_se(journal_file_move_to_entry_by_seqnum(f, 1, DIRECTION_DOWN, &o, NULL) == 1); + assert_se(le64toh(o->entry.seqnum) == 1); + + assert_se(journal_file_move_to_entry_by_seqnum(f, 3, DIRECTION_DOWN, &o, NULL) == 1); + assert_se(le64toh(o->entry.seqnum) == 3); + + assert_se(journal_file_move_to_entry_by_seqnum(f, 2, DIRECTION_DOWN, &o, NULL) == 1); + assert_se(le64toh(o->entry.seqnum) == 2); + + assert_se(journal_file_move_to_entry_by_seqnum(f, 10, DIRECTION_DOWN, &o, NULL) == 0); + + journal_file_rotate(&f, true, true, NULL); + journal_file_rotate(&f, true, true, NULL); + + (void) journal_file_close(f); + + log_info("Done..."); + + if (arg_keep) + log_info("Not removing %s", t); + else { + journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); + + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + } + + puts("------------------------------------------------------------"); +} + +static void test_empty(void) { + JournalFile *f1, *f2, *f3, *f4; + char t[] = "/tmp/journal-XXXXXX"; + + log_set_max_level(LOG_DEBUG); + + assert_se(mkdtemp(t)); + assert_se(chdir(t) >= 0); + + assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, 0666, false, false, NULL, NULL, NULL, NULL, &f1) == 0); + + assert_se(journal_file_open(-1, "test-compress.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &f2) == 0); + + assert_se(journal_file_open(-1, "test-seal.journal", O_RDWR|O_CREAT, 0666, false, true, NULL, NULL, NULL, NULL, &f3) == 0); + + assert_se(journal_file_open(-1, "test-seal-compress.journal", O_RDWR|O_CREAT, 0666, true, true, NULL, NULL, NULL, NULL, &f4) == 0); + + journal_file_print_header(f1); + puts(""); + journal_file_print_header(f2); + puts(""); + journal_file_print_header(f3); + puts(""); + journal_file_print_header(f4); + puts(""); + + log_info("Done..."); + + if (arg_keep) + log_info("Not removing %s", t); + else { + journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); + + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + } + + (void) journal_file_close(f1); + (void) journal_file_close(f2); + (void) journal_file_close(f3); + (void) journal_file_close(f4); +} + +int main(int argc, char *argv[]) { + arg_keep = argc > 1; + + /* journal_file_open requires a valid machine id */ + if (access("/etc/machine-id", F_OK) != 0) + return EXIT_TEST_SKIP; + + test_non_empty(); + test_empty(); + + return 0; +} diff --git a/src/grp-journal/libjournal-core/test-mmap-cache.c b/src/grp-journal/libjournal-core/test-mmap-cache.c new file mode 100644 index 0000000000..009aabf55e --- /dev/null +++ b/src/grp-journal/libjournal-core/test-mmap-cache.c @@ -0,0 +1,79 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 +#include +#include + +#include "fd-util.h" +#include "fileio.h" +#include "macro.h" +#include "mmap-cache.h" +#include "util.h" + +int main(int argc, char *argv[]) { + int x, y, z, r; + char px[] = "/tmp/testmmapXXXXXXX", py[] = "/tmp/testmmapYXXXXXX", pz[] = "/tmp/testmmapZXXXXXX"; + MMapCache *m; + void *p, *q; + + assert_se(m = mmap_cache_new()); + + x = mkostemp_safe(px, O_RDWR|O_CLOEXEC); + assert_se(x >= 0); + unlink(px); + + y = mkostemp_safe(py, O_RDWR|O_CLOEXEC); + assert_se(y >= 0); + unlink(py); + + z = mkostemp_safe(pz, O_RDWR|O_CLOEXEC); + assert_se(z >= 0); + unlink(pz); + + r = mmap_cache_get(m, x, PROT_READ, 0, false, 1, 2, NULL, &p); + assert_se(r >= 0); + + r = mmap_cache_get(m, x, PROT_READ, 0, false, 2, 2, NULL, &q); + assert_se(r >= 0); + + assert_se((uint8_t*) p + 1 == (uint8_t*) q); + + r = mmap_cache_get(m, x, PROT_READ, 1, false, 3, 2, NULL, &q); + assert_se(r >= 0); + + assert_se((uint8_t*) p + 2 == (uint8_t*) q); + + r = mmap_cache_get(m, x, PROT_READ, 0, false, 16ULL*1024ULL*1024ULL, 2, NULL, &p); + assert_se(r >= 0); + + r = mmap_cache_get(m, x, PROT_READ, 1, false, 16ULL*1024ULL*1024ULL+1, 2, NULL, &q); + assert_se(r >= 0); + + assert_se((uint8_t*) p + 1 == (uint8_t*) q); + + mmap_cache_unref(m); + + safe_close(x); + safe_close(y); + safe_close(z); + + return 0; +} diff --git a/src/grp-journal/systemd-journald/Makefile b/src/grp-journal/systemd-journald/Makefile new file mode 100644 index 0000000000..53046d1ce3 --- /dev/null +++ b/src/grp-journal/systemd-journald/Makefile @@ -0,0 +1,94 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_journald_SOURCES = \ + src/journal/journald.c \ + src/journal/journald-server.h + +systemd_journald_LDADD = \ + libjournal-core.la \ + libshared.la + +systemd_cat_SOURCES = \ + src/journal/cat.c + +systemd_cat_LDADD = \ + libjournal-core.la + + +libexec_PROGRAMS += \ + systemd-journald + +bin_PROGRAMS += \ + journalctl + +bin_PROGRAMS += \ + systemd-cat + +dist_systemunit_DATA += \ + units/systemd-journald.socket \ + units/systemd-journald-dev-log.socket \ + units/systemd-journald-audit.socket + +nodist_systemunit_DATA += \ + units/systemd-journald.service \ + units/systemd-journal-flush.service \ + units/systemd-journal-catalog-update.service + +dist_pkgsysconf_DATA += \ + src/journal/journald.conf + +dist_catalog_DATA = \ + catalog/systemd.bg.catalog \ + catalog/systemd.be.catalog \ + catalog/systemd.be@latin.catalog \ + catalog/systemd.fr.catalog \ + catalog/systemd.it.catalog \ + catalog/systemd.pl.catalog \ + catalog/systemd.pt_BR.catalog \ + catalog/systemd.ru.catalog \ + catalog/systemd.zh_CN.catalog \ + catalog/systemd.zh_TW.catalog \ + catalog/systemd.catalog + +SOCKETS_TARGET_WANTS += \ + systemd-journald.socket \ + systemd-journald-dev-log.socket \ + systemd-journald-audit.socket + +SYSINIT_TARGET_WANTS += \ + systemd-journald.service \ + systemd-journal-flush.service \ + systemd-journal-catalog-update.service + +EXTRA_DIST += \ + units/systemd-journald.service.in \ + units/systemd-journal-flush.service.in \ + units/systemd-journal-catalog-update.service.in + +gperf_gperf_sources += \ + src/journal/journald-gperf.gperf + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-journal/systemd-journald/journald.c b/src/grp-journal/systemd-journald/journald.c new file mode 100644 index 0000000000..1afe44fa8e --- /dev/null +++ b/src/grp-journal/systemd-journald/journald.c @@ -0,0 +1,120 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include + +#include "formats-util.h" +#include "journal-authenticate.h" +#include "journald-kmsg.h" +#include "journald-server.h" +#include "journald-syslog.h" +#include "sigbus.h" + +int main(int argc, char *argv[]) { + Server server; + int r; + + if (argc > 1) { + log_error("This program does not take arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_SAFE); + log_set_facility(LOG_SYSLOG); + log_parse_environment(); + log_open(); + + umask(0022); + + sigbus_install(); + + r = server_init(&server); + if (r < 0) + goto finish; + + server_vacuum(&server, false, false); + server_flush_to_var(&server); + server_flush_dev_kmsg(&server); + + log_debug("systemd-journald running as pid "PID_FMT, getpid()); + server_driver_message(&server, SD_MESSAGE_JOURNAL_START, + LOG_MESSAGE("Journal started"), + NULL); + + for (;;) { + usec_t t = USEC_INFINITY, n; + + r = sd_event_get_state(server.event); + if (r < 0) + goto finish; + if (r == SD_EVENT_FINISHED) + break; + + n = now(CLOCK_REALTIME); + + if (server.max_retention_usec > 0 && server.oldest_file_usec > 0) { + + /* The retention time is reached, so let's vacuum! */ + if (server.oldest_file_usec + server.max_retention_usec < n) { + log_info("Retention time reached."); + server_rotate(&server); + server_vacuum(&server, false, false); + continue; + } + + /* Calculate when to rotate the next time */ + t = server.oldest_file_usec + server.max_retention_usec - n; + } + +#ifdef HAVE_GCRYPT + if (server.system_journal) { + usec_t u; + + if (journal_file_next_evolve_usec(server.system_journal, &u)) { + if (n >= u) + t = 0; + else + t = MIN(t, u - n); + } + } +#endif + + r = sd_event_run(server.event, t); + if (r < 0) { + log_error_errno(r, "Failed to run event loop: %m"); + goto finish; + } + + server_maybe_append_tags(&server); + server_maybe_warn_forward_syslog_missed(&server); + } + + log_debug("systemd-journald stopped as pid "PID_FMT, getpid()); + server_driver_message(&server, SD_MESSAGE_JOURNAL_STOP, + LOG_MESSAGE("Journal stopped"), + NULL); + +finish: + server_done(&server); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-machine/Makefile b/src/grp-machine/Makefile new file mode 100644 index 0000000000..7412341233 --- /dev/null +++ b/src/grp-machine/Makefile @@ -0,0 +1,29 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +at.subdirs += machinectl systemd-machined +at.subdirs += nss-mymachines + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-machine/libmachine-core/.gitignore b/src/grp-machine/libmachine-core/.gitignore new file mode 100644 index 0000000000..e1065b5894 --- /dev/null +++ b/src/grp-machine/libmachine-core/.gitignore @@ -0,0 +1 @@ +/org.freedesktop.machine1.policy diff --git a/src/grp-machine/libmachine-core/Makefile b/src/grp-machine/libmachine-core/Makefile new file mode 100644 index 0000000000..3881224746 --- /dev/null +++ b/src/grp-machine/libmachine-core/Makefile @@ -0,0 +1,52 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +libmachine_core_la_SOURCES = \ + src/machine/machine.c \ + src/machine/machine.h \ + src/machine/machined-dbus.c \ + src/machine/machine-dbus.c \ + src/machine/machine-dbus.h \ + src/machine/image-dbus.c \ + src/machine/image-dbus.h \ + src/machine/operation.c \ + src/machine/operation.h + +libmachine_core_la_LIBADD = \ + libshared.la + +noinst_LTLIBRARIES += \ + libmachine-core.la + +test_machine_tables_SOURCES = \ + src/machine/test-machine-tables.c + +test_machine_tables_LDADD = \ + libmachine-core.la + +tests += \ + test-machine-tables + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-machine/libmachine-core/image-dbus.c b/src/grp-machine/libmachine-core/image-dbus.c new file mode 100644 index 0000000000..0eed9b81bb --- /dev/null +++ b/src/grp-machine/libmachine-core/image-dbus.c @@ -0,0 +1,422 @@ +/*** + 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 "alloc-util.h" +#include "bus-label.h" +#include "bus-util.h" +#include "fd-util.h" +#include "image-dbus.h" +#include "io-util.h" +#include "machine-image.h" +#include "process-util.h" +#include "strv.h" +#include "user-util.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, image_type, ImageType); + +int bus_image_method_remove( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 }; + Image *image = userdata; + Manager *m = image->userdata; + pid_t child; + int r; + + assert(message); + assert(image); + + if (m->n_operations >= OPERATIONS_MAX) + return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing operations."); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-images", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m"); + + child = fork(); + if (child < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); + if (child == 0) { + errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); + + r = image_remove(image); + if (r < 0) { + (void) write(errno_pipe_fd[1], &r, sizeof(r)); + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); + } + + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + r = operation_new(m, NULL, child, message, errno_pipe_fd[0]); + if (r < 0) { + (void) sigkill_wait(child); + return r; + } + + errno_pipe_fd[0] = -1; + + return 1; +} + +int bus_image_method_rename( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + Image *image = userdata; + Manager *m = image->userdata; + const char *new_name; + int r; + + assert(message); + assert(image); + + r = sd_bus_message_read(message, "s", &new_name); + if (r < 0) + return r; + + if (!image_name_is_valid(new_name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-images", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = image_rename(image, new_name); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_image_method_clone( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 }; + Image *image = userdata; + Manager *m = image->userdata; + const char *new_name; + int r, read_only; + pid_t child; + + assert(message); + assert(image); + assert(m); + + if (m->n_operations >= OPERATIONS_MAX) + return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing operations."); + + r = sd_bus_message_read(message, "sb", &new_name, &read_only); + if (r < 0) + return r; + + if (!image_name_is_valid(new_name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-images", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m"); + + child = fork(); + if (child < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); + if (child == 0) { + errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); + + r = image_clone(image, new_name, read_only); + if (r < 0) { + (void) write(errno_pipe_fd[1], &r, sizeof(r)); + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); + } + + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + r = operation_new(m, NULL, child, message, errno_pipe_fd[0]); + if (r < 0) { + (void) sigkill_wait(child); + return r; + } + + errno_pipe_fd[0] = -1; + + return 1; +} + +int bus_image_method_mark_read_only( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + Image *image = userdata; + Manager *m = image->userdata; + int r, read_only; + + assert(message); + + r = sd_bus_message_read(message, "b", &read_only); + if (r < 0) + return r; + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-images", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = image_read_only(image, read_only); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_image_method_set_limit( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + Image *image = userdata; + Manager *m = image->userdata; + uint64_t limit; + int r; + + assert(message); + + r = sd_bus_message_read(message, "t", &limit); + if (r < 0) + return r; + if (!FILE_SIZE_VALID_OR_INFINITY(limit)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range"); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-images", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = image_set_limit(image, limit); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +const sd_bus_vtable image_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Image, name), 0), + SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Image, path), 0), + SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Image, type), 0), + SD_BUS_PROPERTY("ReadOnly", "b", bus_property_get_bool, offsetof(Image, read_only), 0), + SD_BUS_PROPERTY("CreationTimestamp", "t", NULL, offsetof(Image, crtime), 0), + SD_BUS_PROPERTY("ModificationTimestamp", "t", NULL, offsetof(Image, mtime), 0), + SD_BUS_PROPERTY("Usage", "t", NULL, offsetof(Image, usage), 0), + SD_BUS_PROPERTY("Limit", "t", NULL, offsetof(Image, limit), 0), + SD_BUS_PROPERTY("UsageExclusive", "t", NULL, offsetof(Image, usage_exclusive), 0), + SD_BUS_PROPERTY("LimitExclusive", "t", NULL, offsetof(Image, limit_exclusive), 0), + SD_BUS_METHOD("Remove", NULL, NULL, bus_image_method_remove, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Rename", "s", NULL, bus_image_method_rename, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Clone", "sb", NULL, bus_image_method_clone, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MarkReadOnly", "b", NULL, bus_image_method_mark_read_only, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetLimit", "t", NULL, bus_image_method_set_limit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END +}; + +static int image_flush_cache(sd_event_source *s, void *userdata) { + Manager *m = userdata; + Image *i; + + assert(s); + assert(m); + + while ((i = hashmap_steal_first(m->image_cache))) + image_unref(i); + + return 0; +} + +int image_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + _cleanup_free_ char *e = NULL; + Manager *m = userdata; + Image *image = NULL; + const char *p; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + + p = startswith(path, "/org/freedesktop/machine1/image/"); + if (!p) + return 0; + + e = bus_label_unescape(p); + if (!e) + return -ENOMEM; + + image = hashmap_get(m->image_cache, e); + if (image) { + *found = image; + return 1; + } + + r = hashmap_ensure_allocated(&m->image_cache, &string_hash_ops); + if (r < 0) + return r; + + if (!m->image_cache_defer_event) { + r = sd_event_add_defer(m->event, &m->image_cache_defer_event, image_flush_cache, m); + if (r < 0) + return r; + + r = sd_event_source_set_priority(m->image_cache_defer_event, SD_EVENT_PRIORITY_IDLE); + if (r < 0) + return r; + } + + r = sd_event_source_set_enabled(m->image_cache_defer_event, SD_EVENT_ONESHOT); + if (r < 0) + return r; + + r = image_find(e, &image); + if (r <= 0) + return r; + + image->userdata = m; + + r = hashmap_put(m->image_cache, image->name, image); + if (r < 0) { + image_unref(image); + return r; + } + + *found = image; + return 1; +} + +char *image_bus_path(const char *name) { + _cleanup_free_ char *e = NULL; + + assert(name); + + e = bus_label_escape(name); + if (!e) + return NULL; + + return strappend("/org/freedesktop/machine1/image/", e); +} + +int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + _cleanup_(image_hashmap_freep) Hashmap *images = NULL; + _cleanup_strv_free_ char **l = NULL; + Image *image; + Iterator i; + int r; + + assert(bus); + assert(path); + assert(nodes); + + images = hashmap_new(&string_hash_ops); + if (!images) + return -ENOMEM; + + r = image_discover(images); + if (r < 0) + return r; + + HASHMAP_FOREACH(image, images, i) { + char *p; + + p = image_bus_path(image->name); + if (!p) + return -ENOMEM; + + r = strv_consume(&l, p); + if (r < 0) + return r; + } + + *nodes = l; + l = NULL; + + return 1; +} diff --git a/src/grp-machine/libmachine-core/image-dbus.h b/src/grp-machine/libmachine-core/image-dbus.h new file mode 100644 index 0000000000..b62da996c6 --- /dev/null +++ b/src/grp-machine/libmachine-core/image-dbus.h @@ -0,0 +1,35 @@ +#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 "machined.h" + +extern const sd_bus_vtable image_vtable[]; + +char *image_bus_path(const char *name); + +int image_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); +int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); + +int bus_image_method_remove(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_image_method_rename(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_image_method_clone(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_image_method_mark_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_image_method_set_limit(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/grp-machine/libmachine-core/machine-dbus.c b/src/grp-machine/libmachine-core/machine-dbus.c new file mode 100644 index 0000000000..7b9aa66d63 --- /dev/null +++ b/src/grp-machine/libmachine-core/machine-dbus.c @@ -0,0 +1,1475 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include +#include + +/* When we include libgen.h because we need dirname() we immediately + * undefine basename() since libgen.h defines it as a macro to the POSIX + * version which is really broken. We prefer GNU basename(). */ +#include +#undef basename + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-internal.h" +#include "bus-label.h" +#include "bus-util.h" +#include "copy.h" +#include "env-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "in-addr-util.h" +#include "local-addresses.h" +#include "machine-dbus.h" +#include "machine.h" +#include "mkdir.h" +#include "path-util.h" +#include "process-util.h" +#include "signal-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "user-util.h" + +static int property_get_id( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Machine *m = userdata; + + assert(bus); + assert(reply); + assert(m); + + return sd_bus_message_append_array(reply, 'y', &m->id, 16); +} + +static int property_get_state( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Machine *m = userdata; + const char *state; + int r; + + assert(bus); + assert(reply); + assert(m); + + state = machine_state_to_string(machine_get_state(m)); + + r = sd_bus_message_append_basic(reply, 's', state); + if (r < 0) + return r; + + return 1; +} + +static int property_get_netif( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Machine *m = userdata; + + assert(bus); + assert(reply); + assert(m); + + assert_cc(sizeof(int) == sizeof(int32_t)); + + return sd_bus_message_append_array(reply, 'i', m->netif, m->n_netif * sizeof(int)); +} + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_class, machine_class, MachineClass); + +int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Machine *m = userdata; + int r; + + assert(message); + assert(m); + + r = bus_verify_polkit_async( + message, + CAP_KILL, + "org.freedesktop.machine1.manage-machines", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = machine_stop(m); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Machine *m = userdata; + const char *swho; + int32_t signo; + KillWho who; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "si", &swho, &signo); + if (r < 0) + return r; + + if (isempty(swho)) + who = KILL_ALL; + else { + who = kill_who_from_string(swho); + if (who < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid kill parameter '%s'", swho); + } + + if (!SIGNAL_VALID(signo)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid signal %i", signo); + + r = bus_verify_polkit_async( + message, + CAP_KILL, + "org.freedesktop.machine1.manage-machines", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = machine_kill(m, who, signo); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Machine *m = userdata; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(iay)"); + if (r < 0) + return r; + + switch (m->class) { + + case MACHINE_HOST: { + _cleanup_free_ struct local_address *addresses = NULL; + struct local_address *a; + int n, i; + + n = local_addresses(NULL, 0, AF_UNSPEC, &addresses); + if (n < 0) + return n; + + for (a = addresses, i = 0; i < n; a++, i++) { + + r = sd_bus_message_open_container(reply, 'r', "iay"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "i", addresses[i].family); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', &addresses[i].address, FAMILY_ADDRESS_SIZE(addresses[i].family)); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + } + + break; + } + + case MACHINE_CONTAINER: { + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + _cleanup_free_ char *us = NULL, *them = NULL; + _cleanup_close_ int netns_fd = -1; + const char *p; + siginfo_t si; + pid_t child; + + r = readlink_malloc("/proc/self/ns/net", &us); + if (r < 0) + return r; + + p = procfs_file_alloca(m->leader, "ns/net"); + r = readlink_malloc(p, &them); + if (r < 0) + return r; + + if (streq(us, them)) + return sd_bus_error_setf(error, BUS_ERROR_NO_PRIVATE_NETWORKING, "Machine %s does not use private networking", m->name); + + r = namespace_open(m->leader, NULL, NULL, &netns_fd, NULL, NULL); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) + return -errno; + + child = fork(); + if (child < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); + + if (child == 0) { + _cleanup_free_ struct local_address *addresses = NULL; + struct local_address *a; + int i, n; + + pair[0] = safe_close(pair[0]); + + r = namespace_enter(-1, -1, netns_fd, -1, -1); + if (r < 0) + _exit(EXIT_FAILURE); + + n = local_addresses(NULL, 0, AF_UNSPEC, &addresses); + if (n < 0) + _exit(EXIT_FAILURE); + + for (a = addresses, i = 0; i < n; a++, i++) { + struct iovec iov[2] = { + { .iov_base = &a->family, .iov_len = sizeof(a->family) }, + { .iov_base = &a->address, .iov_len = FAMILY_ADDRESS_SIZE(a->family) }, + }; + + r = writev(pair[1], iov, 2); + if (r < 0) + _exit(EXIT_FAILURE); + } + + pair[1] = safe_close(pair[1]); + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + + for (;;) { + int family; + ssize_t n; + union in_addr_union in_addr; + struct iovec iov[2]; + struct msghdr mh = { + .msg_iov = iov, + .msg_iovlen = 2, + }; + + iov[0] = (struct iovec) { .iov_base = &family, .iov_len = sizeof(family) }; + iov[1] = (struct iovec) { .iov_base = &in_addr, .iov_len = sizeof(in_addr) }; + + n = recvmsg(pair[0], &mh, 0); + if (n < 0) + return -errno; + if ((size_t) n < sizeof(family)) + break; + + r = sd_bus_message_open_container(reply, 'r', "iay"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "i", family); + if (r < 0) + return r; + + switch (family) { + + case AF_INET: + if (n != sizeof(struct in_addr) + sizeof(family)) + return -EIO; + + r = sd_bus_message_append_array(reply, 'y', &in_addr.in, sizeof(in_addr.in)); + break; + + case AF_INET6: + if (n != sizeof(struct in6_addr) + sizeof(family)) + return -EIO; + + r = sd_bus_message_append_array(reply, 'y', &in_addr.in6, sizeof(in_addr.in6)); + break; + } + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + } + + r = wait_for_terminate(child, &si); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); + if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) + return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); + break; + } + + default: + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting IP address data is only supported on container machines."); + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_strv_free_ char **l = NULL; + Machine *m = userdata; + char **k, **v; + int r; + + assert(message); + assert(m); + + switch (m->class) { + + case MACHINE_HOST: + r = load_env_file_pairs(NULL, "/etc/os-release", NULL, &l); + if (r < 0) + return r; + + break; + + case MACHINE_CONTAINER: { + _cleanup_close_ int mntns_fd = -1, root_fd = -1; + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + _cleanup_fclose_ FILE *f = NULL; + siginfo_t si; + pid_t child; + + r = namespace_open(m->leader, NULL, &mntns_fd, NULL, NULL, &root_fd); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) + return -errno; + + child = fork(); + if (child < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); + + if (child == 0) { + _cleanup_close_ int fd = -1; + + pair[0] = safe_close(pair[0]); + + r = namespace_enter(-1, mntns_fd, -1, -1, root_fd); + if (r < 0) + _exit(EXIT_FAILURE); + + fd = open("/etc/os-release", O_RDONLY|O_CLOEXEC); + if (fd < 0) { + fd = open("/usr/lib/os-release", O_RDONLY|O_CLOEXEC); + if (fd < 0) + _exit(EXIT_FAILURE); + } + + r = copy_bytes(fd, pair[1], (uint64_t) -1, false); + if (r < 0) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + + f = fdopen(pair[0], "re"); + if (!f) + return -errno; + + pair[0] = -1; + + r = load_env_file_pairs(f, "/etc/os-release", NULL, &l); + if (r < 0) + return r; + + r = wait_for_terminate(child, &si); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); + if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) + return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); + + break; + } + + default: + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting OS release data is only supported on container machines."); + } + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "{ss}"); + if (r < 0) + return r; + + STRV_FOREACH_PAIR(k, v, l) { + r = sd_bus_message_append(reply, "{ss}", *k, *v); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *pty_name = NULL; + _cleanup_close_ int master = -1; + Machine *m = userdata; + int r; + + assert(message); + assert(m); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-open-pty" : "org.freedesktop.machine1.open-pty", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (master < 0) + return master; + + r = ptsname_namespace(master, &pty_name); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "hs", master, pty_name); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +static int container_bus_new(Machine *m, sd_bus_error *error, sd_bus **ret) { + int r; + + assert(m); + assert(ret); + + switch (m->class) { + + case MACHINE_HOST: + *ret = NULL; + break; + + case MACHINE_CONTAINER: { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + char *address; + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + if (asprintf(&address, "x-machine-kernel:pid=%1$" PID_PRI ";x-machine-unix:pid=%1$" PID_PRI, m->leader) < 0) + return -ENOMEM; + + bus->address = address; + bus->bus_client = true; + bus->trusted = false; + bus->is_system = true; + + r = sd_bus_start(bus); + if (r == -ENOENT) + return sd_bus_error_set_errnof(error, r, "There is no system bus in container %s.", m->name); + if (r < 0) + return r; + + *ret = bus; + bus = NULL; + break; + } + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *pty_name = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL; + _cleanup_close_ int master = -1; + sd_bus *container_bus = NULL; + Machine *m = userdata; + const char *p, *getty; + int r; + + assert(message); + assert(m); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-login" : "org.freedesktop.machine1.login", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (master < 0) + return master; + + r = ptsname_namespace(master, &pty_name); + if (r < 0) + return r; + + p = path_startswith(pty_name, "/dev/pts/"); + if (!p) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "PTS name %s is invalid", pty_name); + + r = container_bus_new(m, error, &allocated_bus); + if (r < 0) + return r; + + container_bus = allocated_bus ?: m->manager->bus; + + getty = strjoina("container-getty@", p, ".service"); + + r = sd_bus_call_method( + container_bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartUnit", + error, NULL, + "ss", getty, "replace"); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "hs", master, pty_name); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *tm = NULL; + _cleanup_free_ char *pty_name = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL; + sd_bus *container_bus = NULL; + _cleanup_close_ int master = -1, slave = -1; + _cleanup_strv_free_ char **env = NULL, **args = NULL; + Machine *m = userdata; + const char *p, *unit, *user, *path, *description, *utmp_id; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "ss", &user, &path); + if (r < 0) + return r; + if (isempty(user)) + user = NULL; + if (isempty(path)) + path = "/bin/sh"; + if (!path_is_absolute(path)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified path '%s' is not absolute", path); + + r = sd_bus_message_read_strv(message, &args); + if (r < 0) + return r; + if (strv_isempty(args)) { + args = strv_free(args); + + args = strv_new(path, NULL); + if (!args) + return -ENOMEM; + + args[0][0] = '-'; /* Tell /bin/sh that this shall be a login shell */ + } + + r = sd_bus_message_read_strv(message, &env); + if (r < 0) + return r; + if (!strv_env_is_valid(env)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments"); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-shell" : "org.freedesktop.machine1.shell", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (master < 0) + return master; + + r = ptsname_namespace(master, &pty_name); + if (r < 0) + return r; + + p = path_startswith(pty_name, "/dev/pts/"); + assert(p); + + slave = machine_open_terminal(m, pty_name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (slave < 0) + return slave; + + utmp_id = path_startswith(pty_name, "/dev/"); + assert(utmp_id); + + r = container_bus_new(m, error, &allocated_bus); + if (r < 0) + return r; + + container_bus = allocated_bus ?: m->manager->bus; + + r = sd_bus_message_new_method_call( + container_bus, + &tm, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) + return r; + + /* Name and mode */ + unit = strjoina("container-shell@", p, ".service"); + r = sd_bus_message_append(tm, "ss", unit, "fail"); + if (r < 0) + return r; + + /* Properties */ + r = sd_bus_message_open_container(tm, 'a', "(sv)"); + if (r < 0) + return r; + + description = strjoina("Shell for User ", isempty(user) ? "root" : user); + r = sd_bus_message_append(tm, + "(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)", + "Description", "s", description, + "StandardInputFileDescriptor", "h", slave, + "StandardOutputFileDescriptor", "h", slave, + "StandardErrorFileDescriptor", "h", slave, + "SendSIGHUP", "b", true, + "IgnoreSIGPIPE", "b", false, + "KillMode", "s", "mixed", + "TTYReset", "b", true, + "UtmpIdentifier", "s", utmp_id, + "UtmpMode", "s", "user", + "PAMName", "s", "login", + "WorkingDirectory", "s", "-~"); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "(sv)", "User", "s", isempty(user) ? "root" : user); + if (r < 0) + return r; + + if (!strv_isempty(env)) { + r = sd_bus_message_open_container(tm, 'r', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "s", "Environment"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(tm, 'v', "as"); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(tm, env); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + } + + /* Exec container */ + r = sd_bus_message_open_container(tm, 'r', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "s", "ExecStart"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(tm, 'v', "a(sasb)"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(tm, 'a', "(sasb)"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(tm, 'r', "sasb"); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "s", path); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(tm, args); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "b", true); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + /* Auxiliary units */ + r = sd_bus_message_append(tm, "a(sa(sv))", 0); + if (r < 0) + return r; + + r = sd_bus_call(container_bus, tm, 0, error, NULL); + if (r < 0) + return r; + + slave = safe_close(slave); + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "hs", master, pty_name); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 }; + char mount_slave[] = "/tmp/propagate.XXXXXX", *mount_tmp, *mount_outside, *p; + bool mount_slave_created = false, mount_slave_mounted = false, + mount_tmp_created = false, mount_tmp_mounted = false, + mount_outside_created = false, mount_outside_mounted = false; + const char *dest, *src; + Machine *m = userdata; + int read_only, make_directory; + pid_t child; + siginfo_t si; + int r; + + assert(message); + assert(m); + + if (m->class != MACHINE_CONTAINER) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Bind mounting is only supported on container machines."); + + r = sd_bus_message_read(message, "ssbb", &src, &dest, &read_only, &make_directory); + if (r < 0) + return r; + + if (!path_is_absolute(src) || !path_is_safe(src)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute and not contain ../."); + + if (isempty(dest)) + dest = src; + else if (!path_is_absolute(dest) || !path_is_safe(dest)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute and not contain ../."); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-machines", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + /* One day, when bind mounting /proc/self/fd/n works across + * namespace boundaries we should rework this logic to make + * use of it... */ + + p = strjoina("/run/systemd/nspawn/propagate/", m->name, "/"); + if (laccess(p, F_OK) < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Container does not allow propagation of mount points."); + + /* Our goal is to install a new bind mount into the container, + possibly read-only. This is irritatingly complex + unfortunately, currently. + + First, we start by creating a private playground in /tmp, + that we can mount MS_SLAVE. (Which is necessary, since + MS_MOVE cannot be applied to mounts with MS_SHARED parent + mounts.) */ + + if (!mkdtemp(mount_slave)) + return sd_bus_error_set_errnof(error, errno, "Failed to create playground %s: %m", mount_slave); + + mount_slave_created = true; + + if (mount(mount_slave, mount_slave, NULL, MS_BIND, NULL) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to make bind mount %s: %m", mount_slave); + goto finish; + } + + mount_slave_mounted = true; + + if (mount(NULL, mount_slave, NULL, MS_SLAVE, NULL) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to remount slave %s: %m", mount_slave); + goto finish; + } + + /* Second, we mount the source directory to a directory inside + of our MS_SLAVE playground. */ + mount_tmp = strjoina(mount_slave, "/mount"); + if (mkdir(mount_tmp, 0700) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount point %s: %m", mount_tmp); + goto finish; + } + + mount_tmp_created = true; + + if (mount(src, mount_tmp, NULL, MS_BIND, NULL) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to overmount %s: %m", mount_tmp); + goto finish; + } + + mount_tmp_mounted = true; + + /* Third, we remount the new bind mount read-only if requested. */ + if (read_only) + if (mount(NULL, mount_tmp, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to remount read-only %s: %m", mount_tmp); + goto finish; + } + + /* Fourth, we move the new bind mount into the propagation + * directory. This way it will appear there read-only + * right-away. */ + + mount_outside = strjoina("/run/systemd/nspawn/propagate/", m->name, "/XXXXXX"); + if (!mkdtemp(mount_outside)) { + r = sd_bus_error_set_errnof(error, errno, "Cannot create propagation directory %s: %m", mount_outside); + goto finish; + } + + mount_outside_created = true; + + if (mount(mount_tmp, mount_outside, NULL, MS_MOVE, NULL) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to move %s to %s: %m", mount_tmp, mount_outside); + goto finish; + } + + mount_outside_mounted = true; + mount_tmp_mounted = false; + + (void) rmdir(mount_tmp); + mount_tmp_created = false; + + (void) umount(mount_slave); + mount_slave_mounted = false; + + (void) rmdir(mount_slave); + mount_slave_created = false; + + if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m"); + goto finish; + } + + child = fork(); + if (child < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); + goto finish; + } + + if (child == 0) { + const char *mount_inside; + int mntfd; + const char *q; + + errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); + + q = procfs_file_alloca(m->leader, "ns/mnt"); + mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (mntfd < 0) { + r = log_error_errno(errno, "Failed to open mount namespace of leader: %m"); + goto child_fail; + } + + if (setns(mntfd, CLONE_NEWNS) < 0) { + r = log_error_errno(errno, "Failed to join namespace of leader: %m"); + goto child_fail; + } + + if (make_directory) + (void) mkdir_p(dest, 0755); + + /* Fifth, move the mount to the right place inside */ + mount_inside = strjoina("/run/systemd/nspawn/incoming/", basename(mount_outside)); + if (mount(mount_inside, dest, NULL, MS_MOVE, NULL) < 0) { + r = log_error_errno(errno, "Failed to mount: %m"); + goto child_fail; + } + + _exit(EXIT_SUCCESS); + + child_fail: + (void) write(errno_pipe_fd[1], &r, sizeof(r)); + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + _exit(EXIT_FAILURE); + } + + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + r = wait_for_terminate(child, &si); + if (r < 0) { + r = sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); + goto finish; + } + if (si.si_code != CLD_EXITED) { + r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); + goto finish; + } + if (si.si_status != EXIT_SUCCESS) { + + if (read(errno_pipe_fd[0], &r, sizeof(r)) == sizeof(r)) + r = sd_bus_error_set_errnof(error, r, "Failed to mount: %m"); + else + r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child failed."); + goto finish; + } + + r = sd_bus_reply_method_return(message, NULL); + +finish: + if (mount_outside_mounted) + umount(mount_outside); + if (mount_outside_created) + rmdir(mount_outside); + + if (mount_tmp_mounted) + umount(mount_tmp); + if (mount_tmp_created) + rmdir(mount_tmp); + + if (mount_slave_mounted) + umount(mount_slave); + if (mount_slave_created) + rmdir(mount_slave); + + return r; +} + +int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error) { + const char *src, *dest, *host_path, *container_path, *host_basename, *host_dirname, *container_basename, *container_dirname; + _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 }; + _cleanup_close_ int hostfd = -1; + Machine *m = userdata; + bool copy_from; + pid_t child; + char *t; + int r; + + assert(message); + assert(m); + + if (m->manager->n_operations >= OPERATIONS_MAX) + return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing copies."); + + if (m->class != MACHINE_CONTAINER) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Copying files is only supported on container machines."); + + r = sd_bus_message_read(message, "ss", &src, &dest); + if (r < 0) + return r; + + if (!path_is_absolute(src)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute."); + + if (isempty(dest)) + dest = src; + else if (!path_is_absolute(dest)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute."); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-machines", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + copy_from = strstr(sd_bus_message_get_member(message), "CopyFrom"); + + if (copy_from) { + container_path = src; + host_path = dest; + } else { + host_path = src; + container_path = dest; + } + + host_basename = basename(host_path); + t = strdupa(host_path); + host_dirname = dirname(t); + + container_basename = basename(container_path); + t = strdupa(container_path); + container_dirname = dirname(t); + + hostfd = open(host_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY); + if (hostfd < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to open host directory %s: %m", host_dirname); + + if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m"); + + child = fork(); + if (child < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); + + if (child == 0) { + int containerfd; + const char *q; + int mntfd; + + errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); + + q = procfs_file_alloca(m->leader, "ns/mnt"); + mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (mntfd < 0) { + r = log_error_errno(errno, "Failed to open mount namespace of leader: %m"); + goto child_fail; + } + + if (setns(mntfd, CLONE_NEWNS) < 0) { + r = log_error_errno(errno, "Failed to join namespace of leader: %m"); + goto child_fail; + } + + containerfd = open(container_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY); + if (containerfd < 0) { + r = log_error_errno(errno, "Failed top open destination directory: %m"); + goto child_fail; + } + + if (copy_from) + r = copy_tree_at(containerfd, container_basename, hostfd, host_basename, true); + else + r = copy_tree_at(hostfd, host_basename, containerfd, container_basename, true); + + hostfd = safe_close(hostfd); + containerfd = safe_close(containerfd); + + if (r < 0) { + r = log_error_errno(r, "Failed to copy tree: %m"); + goto child_fail; + } + + _exit(EXIT_SUCCESS); + + child_fail: + (void) write(errno_pipe_fd[1], &r, sizeof(r)); + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + _exit(EXIT_FAILURE); + } + + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + /* Copying might take a while, hence install a watch on the child, and return */ + + r = operation_new(m->manager, m, child, message, errno_pipe_fd[0]); + if (r < 0) { + (void) sigkill_wait(child); + return r; + } + errno_pipe_fd[0] = -1; + + return 1; +} + +int bus_machine_method_open_root_directory(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_close_ int fd = -1; + Machine *m = userdata; + int r; + + assert(message); + assert(m); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-machines", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + switch (m->class) { + + case MACHINE_HOST: + fd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return -errno; + + break; + + case MACHINE_CONTAINER: { + _cleanup_close_ int mntns_fd = -1, root_fd = -1; + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + siginfo_t si; + pid_t child; + + r = namespace_open(m->leader, NULL, &mntns_fd, NULL, NULL, &root_fd); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) + return -errno; + + child = fork(); + if (child < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); + + if (child == 0) { + _cleanup_close_ int dfd = -1; + + pair[0] = safe_close(pair[0]); + + r = namespace_enter(-1, mntns_fd, -1, -1, root_fd); + if (r < 0) + _exit(EXIT_FAILURE); + + dfd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (dfd < 0) + _exit(EXIT_FAILURE); + + r = send_one_fd(pair[1], dfd, 0); + dfd = safe_close(dfd); + if (r < 0) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + + r = wait_for_terminate(child, &si); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); + if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) + return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); + + fd = receive_one_fd(pair[0], MSG_DONTWAIT); + if (fd < 0) + return fd; + + break; + } + + default: + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Opening the root directory is only supported on container machines."); + } + + return sd_bus_reply_method_return(message, "h", fd); +} + +const sd_bus_vtable machine_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Machine, name), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Id", "ay", property_get_id, 0, SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("Timestamp", offsetof(Machine, timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Service", "s", NULL, offsetof(Machine, service), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Unit", "s", NULL, offsetof(Machine, unit), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Scope", "s", NULL, offsetof(Machine, unit), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("Leader", "u", NULL, offsetof(Machine, leader), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Class", "s", property_get_class, offsetof(Machine, class), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RootDirectory", "s", NULL, offsetof(Machine, root_directory), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("NetworkInterfaces", "ai", property_get_netif, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("State", "s", property_get_state, 0, 0), + SD_BUS_METHOD("Terminate", NULL, NULL, bus_machine_method_terminate, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Kill", "si", NULL, bus_machine_method_kill, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetAddresses", NULL, "a(iay)", bus_machine_method_get_addresses, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetOSRelease", NULL, "a{ss}", bus_machine_method_get_os_release, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("OpenPTY", NULL, "hs", bus_machine_method_open_pty, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("OpenLogin", NULL, "hs", bus_machine_method_open_login, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("OpenShell", "ssasas", "hs", bus_machine_method_open_shell, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("BindMount", "ssbb", NULL, bus_machine_method_bind_mount, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CopyFrom", "ss", NULL, bus_machine_method_copy, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CopyTo", "ss", NULL, bus_machine_method_copy, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("OpenRootDirectory", NULL, "h", bus_machine_method_open_root_directory, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END +}; + +int machine_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + if (streq(path, "/org/freedesktop/machine1/machine/self")) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + sd_bus_message *message; + pid_t pid; + + message = sd_bus_get_current_message(bus); + if (!message) + return 0; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r < 0) + return r; + + r = manager_get_machine_by_pid(m, pid, &machine); + if (r <= 0) + return 0; + } else { + _cleanup_free_ char *e = NULL; + const char *p; + + p = startswith(path, "/org/freedesktop/machine1/machine/"); + if (!p) + return 0; + + e = bus_label_unescape(p); + if (!e) + return -ENOMEM; + + machine = hashmap_get(m->machines, e); + if (!machine) + return 0; + } + + *found = machine; + return 1; +} + +char *machine_bus_path(Machine *m) { + _cleanup_free_ char *e = NULL; + + assert(m); + + e = bus_label_escape(m->name); + if (!e) + return NULL; + + return strappend("/org/freedesktop/machine1/machine/", e); +} + +int machine_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + _cleanup_strv_free_ char **l = NULL; + Machine *machine = NULL; + Manager *m = userdata; + Iterator i; + int r; + + assert(bus); + assert(path); + assert(nodes); + + HASHMAP_FOREACH(machine, m->machines, i) { + char *p; + + p = machine_bus_path(machine); + if (!p) + return -ENOMEM; + + r = strv_consume(&l, p); + if (r < 0) + return r; + } + + *nodes = l; + l = NULL; + + return 1; +} + +int machine_send_signal(Machine *m, bool new_machine) { + _cleanup_free_ char *p = NULL; + + assert(m); + + p = machine_bus_path(m); + if (!p) + return -ENOMEM; + + return sd_bus_emit_signal( + m->manager->bus, + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + new_machine ? "MachineNew" : "MachineRemoved", + "so", m->name, p); +} + +int machine_send_create_reply(Machine *m, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL; + _cleanup_free_ char *p = NULL; + + assert(m); + + if (!m->create_message) + return 0; + + c = m->create_message; + m->create_message = NULL; + + if (error) + return sd_bus_reply_method_error(c, error); + + /* Update the machine state file before we notify the client + * about the result. */ + machine_save(m); + + p = machine_bus_path(m); + if (!p) + return -ENOMEM; + + return sd_bus_reply_method_return(c, "o", p); +} diff --git a/src/grp-machine/libmachine-core/machine-dbus.h b/src/grp-machine/libmachine-core/machine-dbus.h new file mode 100644 index 0000000000..d3faf5cb07 --- /dev/null +++ b/src/grp-machine/libmachine-core/machine-dbus.h @@ -0,0 +1,44 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 "machine.h" + +extern const sd_bus_vtable machine_vtable[]; + +char *machine_bus_path(Machine *s); +int machine_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); +int machine_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); + +int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_open_root_directory(sd_bus_message *message, void *userdata, sd_bus_error *error); + +int machine_send_signal(Machine *m, bool new_machine); +int machine_send_create_reply(Machine *m, sd_bus_error *error); diff --git a/src/grp-machine/libmachine-core/machine.c b/src/grp-machine/libmachine-core/machine.c new file mode 100644 index 0000000000..135f47dafc --- /dev/null +++ b/src/grp-machine/libmachine-core/machine.c @@ -0,0 +1,630 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include + +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "escape.h" +#include "extract-word.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "hashmap.h" +#include "machine-dbus.h" +#include "machine.h" +#include "mkdir.h" +#include "parse-util.h" +#include "process-util.h" +#include "special.h" +#include "string-table.h" +#include "terminal-util.h" +#include "unit-name.h" +#include "util.h" + +Machine* machine_new(Manager *manager, MachineClass class, const char *name) { + Machine *m; + + assert(manager); + assert(class < _MACHINE_CLASS_MAX); + assert(name); + + /* Passing class == _MACHINE_CLASS_INVALID here is fine. It + * means as much as "we don't know yet", and that we'll figure + * it out later when loading the state file. */ + + m = new0(Machine, 1); + if (!m) + return NULL; + + m->name = strdup(name); + if (!m->name) + goto fail; + + if (class != MACHINE_HOST) { + m->state_file = strappend("/run/systemd/machines/", m->name); + if (!m->state_file) + goto fail; + } + + m->class = class; + + if (hashmap_put(manager->machines, m->name, m) < 0) + goto fail; + + m->manager = manager; + + return m; + +fail: + free(m->state_file); + free(m->name); + free(m); + + return NULL; +} + +void machine_free(Machine *m) { + assert(m); + + while (m->operations) + operation_free(m->operations); + + if (m->in_gc_queue) + LIST_REMOVE(gc_queue, m->manager->machine_gc_queue, m); + + machine_release_unit(m); + + free(m->scope_job); + + (void) hashmap_remove(m->manager->machines, m->name); + + if (m->manager->host_machine == m) + m->manager->host_machine = NULL; + + if (m->leader > 0) + (void) hashmap_remove_value(m->manager->machine_leaders, PID_TO_PTR(m->leader), m); + + sd_bus_message_unref(m->create_message); + + free(m->name); + free(m->state_file); + free(m->service); + free(m->root_directory); + free(m->netif); + free(m); +} + +int machine_save(Machine *m) { + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(m); + + if (!m->state_file) + return 0; + + if (!m->started) + return 0; + + r = mkdir_safe_label("/run/systemd/machines", 0755, 0, 0); + if (r < 0) + goto fail; + + r = fopen_temporary(m->state_file, &f, &temp_path); + if (r < 0) + goto fail; + + (void) fchmod(fileno(f), 0644); + + fprintf(f, + "# This is private data. Do not parse.\n" + "NAME=%s\n", + m->name); + + if (m->unit) { + _cleanup_free_ char *escaped; + + escaped = cescape(m->unit); + if (!escaped) { + r = -ENOMEM; + goto fail; + } + + fprintf(f, "SCOPE=%s\n", escaped); /* We continue to call this "SCOPE=" because it is internal only, and we want to stay compatible with old files */ + } + + if (m->scope_job) + fprintf(f, "SCOPE_JOB=%s\n", m->scope_job); + + if (m->service) { + _cleanup_free_ char *escaped; + + escaped = cescape(m->service); + if (!escaped) { + r = -ENOMEM; + goto fail; + } + fprintf(f, "SERVICE=%s\n", escaped); + } + + if (m->root_directory) { + _cleanup_free_ char *escaped; + + escaped = cescape(m->root_directory); + if (!escaped) { + r = -ENOMEM; + goto fail; + } + fprintf(f, "ROOT=%s\n", escaped); + } + + if (!sd_id128_equal(m->id, SD_ID128_NULL)) + fprintf(f, "ID=" SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->id)); + + if (m->leader != 0) + fprintf(f, "LEADER="PID_FMT"\n", m->leader); + + if (m->class != _MACHINE_CLASS_INVALID) + fprintf(f, "CLASS=%s\n", machine_class_to_string(m->class)); + + if (dual_timestamp_is_set(&m->timestamp)) + fprintf(f, + "REALTIME="USEC_FMT"\n" + "MONOTONIC="USEC_FMT"\n", + m->timestamp.realtime, + m->timestamp.monotonic); + + if (m->n_netif > 0) { + unsigned i; + + fputs("NETIF=", f); + + for (i = 0; i < m->n_netif; i++) { + if (i != 0) + fputc(' ', f); + + fprintf(f, "%i", m->netif[i]); + } + + fputc('\n', f); + } + + r = fflush_and_check(f); + if (r < 0) + goto fail; + + if (rename(temp_path, m->state_file) < 0) { + r = -errno; + goto fail; + } + + if (m->unit) { + char *sl; + + /* Create a symlink from the unit name to the machine + * name, so that we can quickly find the machine for + * each given unit. Ignore error. */ + sl = strjoina("/run/systemd/machines/unit:", m->unit); + (void) symlink(m->name, sl); + } + + return 0; + +fail: + (void) unlink(m->state_file); + + if (temp_path) + (void) unlink(temp_path); + + return log_error_errno(r, "Failed to save machine data %s: %m", m->state_file); +} + +static void machine_unlink(Machine *m) { + assert(m); + + if (m->unit) { + + char *sl; + + sl = strjoina("/run/systemd/machines/unit:", m->unit); + (void) unlink(sl); + } + + if (m->state_file) + (void) unlink(m->state_file); +} + +int machine_load(Machine *m) { + _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *id = NULL, *leader = NULL, *class = NULL, *netif = NULL; + int r; + + assert(m); + + if (!m->state_file) + return 0; + + r = parse_env_file(m->state_file, NEWLINE, + "SCOPE", &m->unit, + "SCOPE_JOB", &m->scope_job, + "SERVICE", &m->service, + "ROOT", &m->root_directory, + "ID", &id, + "LEADER", &leader, + "CLASS", &class, + "REALTIME", &realtime, + "MONOTONIC", &monotonic, + "NETIF", &netif, + NULL); + if (r < 0) { + if (r == -ENOENT) + return 0; + + return log_error_errno(r, "Failed to read %s: %m", m->state_file); + } + + if (id) + sd_id128_from_string(id, &m->id); + + if (leader) + parse_pid(leader, &m->leader); + + if (class) { + MachineClass c; + + c = machine_class_from_string(class); + if (c >= 0) + m->class = c; + } + + if (realtime) + timestamp_deserialize(realtime, &m->timestamp.realtime); + if (monotonic) + timestamp_deserialize(monotonic, &m->timestamp.monotonic); + + if (netif) { + size_t allocated = 0, nr = 0; + const char *p; + int *ni = NULL; + + p = netif; + for (;;) { + _cleanup_free_ char *word = NULL; + int ifi; + + r = extract_first_word(&p, &word, NULL, 0); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_warning_errno(r, "Failed to parse NETIF: %s", netif); + break; + } + + if (parse_ifindex(word, &ifi) < 0) + continue; + + if (!GREEDY_REALLOC(ni, allocated, nr+1)) { + free(ni); + return log_oom(); + } + + ni[nr++] = ifi; + } + + free(m->netif); + m->netif = ni; + m->n_netif = nr; + } + + return r; +} + +static int machine_start_scope(Machine *m, sd_bus_message *properties, sd_bus_error *error) { + int r = 0; + + assert(m); + assert(m->class != MACHINE_HOST); + + if (!m->unit) { + _cleanup_free_ char *escaped = NULL; + char *scope, *description, *job = NULL; + + escaped = unit_name_escape(m->name); + if (!escaped) + return log_oom(); + + scope = strjoin("machine-", escaped, ".scope", NULL); + if (!scope) + return log_oom(); + + description = strjoina(m->class == MACHINE_VM ? "Virtual Machine " : "Container ", m->name); + + r = manager_start_scope(m->manager, scope, m->leader, SPECIAL_MACHINE_SLICE, description, properties, error, &job); + if (r < 0) { + log_error("Failed to start machine scope: %s", bus_error_message(error, r)); + free(scope); + return r; + } else { + m->unit = scope; + + free(m->scope_job); + m->scope_job = job; + } + } + + if (m->unit) + hashmap_put(m->manager->machine_units, m->unit, m); + + return r; +} + +int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error) { + int r; + + assert(m); + + if (!IN_SET(m->class, MACHINE_CONTAINER, MACHINE_VM)) + return -EOPNOTSUPP; + + if (m->started) + return 0; + + r = hashmap_put(m->manager->machine_leaders, PID_TO_PTR(m->leader), m); + if (r < 0) + return r; + + /* Create cgroup */ + r = machine_start_scope(m, properties, error); + if (r < 0) + return r; + + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_MACHINE_START), + "NAME=%s", m->name, + "LEADER="PID_FMT, m->leader, + LOG_MESSAGE("New machine %s.", m->name), + NULL); + + if (!dual_timestamp_is_set(&m->timestamp)) + dual_timestamp_get(&m->timestamp); + + m->started = true; + + /* Save new machine data */ + machine_save(m); + + machine_send_signal(m, true); + + return 0; +} + +static int machine_stop_scope(Machine *m) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char *job = NULL; + int r; + + assert(m); + assert(m->class != MACHINE_HOST); + + if (!m->unit) + return 0; + + r = manager_stop_unit(m->manager, m->unit, &error, &job); + if (r < 0) { + log_error("Failed to stop machine scope: %s", bus_error_message(&error, r)); + return r; + } + + free(m->scope_job); + m->scope_job = job; + + return 0; +} + +int machine_stop(Machine *m) { + int r; + assert(m); + + if (!IN_SET(m->class, MACHINE_CONTAINER, MACHINE_VM)) + return -EOPNOTSUPP; + + r = machine_stop_scope(m); + + m->stopping = true; + + machine_save(m); + + return r; +} + +int machine_finalize(Machine *m) { + assert(m); + + if (m->started) + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_MACHINE_STOP), + "NAME=%s", m->name, + "LEADER="PID_FMT, m->leader, + LOG_MESSAGE("Machine %s terminated.", m->name), + NULL); + + machine_unlink(m); + machine_add_to_gc_queue(m); + + if (m->started) { + machine_send_signal(m, false); + m->started = false; + } + + return 0; +} + +bool machine_check_gc(Machine *m, bool drop_not_started) { + assert(m); + + if (m->class == MACHINE_HOST) + return true; + + if (drop_not_started && !m->started) + return false; + + if (m->scope_job && manager_job_is_active(m->manager, m->scope_job)) + return true; + + if (m->unit && manager_unit_is_active(m->manager, m->unit)) + return true; + + return false; +} + +void machine_add_to_gc_queue(Machine *m) { + assert(m); + + if (m->in_gc_queue) + return; + + LIST_PREPEND(gc_queue, m->manager->machine_gc_queue, m); + m->in_gc_queue = true; +} + +MachineState machine_get_state(Machine *s) { + assert(s); + + if (s->class == MACHINE_HOST) + return MACHINE_RUNNING; + + if (s->stopping) + return MACHINE_CLOSING; + + if (s->scope_job) + return MACHINE_OPENING; + + return MACHINE_RUNNING; +} + +int machine_kill(Machine *m, KillWho who, int signo) { + assert(m); + + if (!IN_SET(m->class, MACHINE_VM, MACHINE_CONTAINER)) + return -EOPNOTSUPP; + + if (!m->unit) + return -ESRCH; + + if (who == KILL_LEADER) { + /* If we shall simply kill the leader, do so directly */ + + if (kill(m->leader, signo) < 0) + return -errno; + + return 0; + } + + /* Otherwise, make PID 1 do it for us, for the entire cgroup */ + return manager_kill_unit(m->manager, m->unit, signo, NULL); +} + +int machine_openpt(Machine *m, int flags) { + assert(m); + + switch (m->class) { + + case MACHINE_HOST: { + int fd; + + fd = posix_openpt(flags); + if (fd < 0) + return -errno; + + if (unlockpt(fd) < 0) + return -errno; + + return fd; + } + + case MACHINE_CONTAINER: + if (m->leader <= 0) + return -EINVAL; + + return openpt_in_namespace(m->leader, flags); + + default: + return -EOPNOTSUPP; + } +} + +int machine_open_terminal(Machine *m, const char *path, int mode) { + assert(m); + + switch (m->class) { + + case MACHINE_HOST: + return open_terminal(path, mode); + + case MACHINE_CONTAINER: + if (m->leader <= 0) + return -EINVAL; + + return open_terminal_in_namespace(m->leader, path, mode); + + default: + return -EOPNOTSUPP; + } +} + +void machine_release_unit(Machine *m) { + assert(m); + + if (!m->unit) + return; + + (void) hashmap_remove(m->manager->machine_units, m->unit); + m->unit = mfree(m->unit); +} + +static const char* const machine_class_table[_MACHINE_CLASS_MAX] = { + [MACHINE_CONTAINER] = "container", + [MACHINE_VM] = "vm", + [MACHINE_HOST] = "host", +}; + +DEFINE_STRING_TABLE_LOOKUP(machine_class, MachineClass); + +static const char* const machine_state_table[_MACHINE_STATE_MAX] = { + [MACHINE_OPENING] = "opening", + [MACHINE_RUNNING] = "running", + [MACHINE_CLOSING] = "closing" +}; + +DEFINE_STRING_TABLE_LOOKUP(machine_state, MachineState); + +static const char* const kill_who_table[_KILL_WHO_MAX] = { + [KILL_LEADER] = "leader", + [KILL_ALL] = "all" +}; + +DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho); diff --git a/src/grp-machine/libmachine-core/machine.h b/src/grp-machine/libmachine-core/machine.h new file mode 100644 index 0000000000..e5d75361a9 --- /dev/null +++ b/src/grp-machine/libmachine-core/machine.h @@ -0,0 +1,110 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +typedef struct Machine Machine; +typedef enum KillWho KillWho; + +#include "list.h" +#include "machined.h" +#include "operation.h" + +typedef enum MachineState { + MACHINE_OPENING, /* Machine is being registered */ + MACHINE_RUNNING, /* Machine is running */ + MACHINE_CLOSING, /* Machine is terminating */ + _MACHINE_STATE_MAX, + _MACHINE_STATE_INVALID = -1 +} MachineState; + +typedef enum MachineClass { + MACHINE_CONTAINER, + MACHINE_VM, + MACHINE_HOST, + _MACHINE_CLASS_MAX, + _MACHINE_CLASS_INVALID = -1 +} MachineClass; + +enum KillWho { + KILL_LEADER, + KILL_ALL, + _KILL_WHO_MAX, + _KILL_WHO_INVALID = -1 +}; + +struct Machine { + Manager *manager; + + char *name; + sd_id128_t id; + + MachineClass class; + + char *state_file; + char *service; + char *root_directory; + + char *unit; + char *scope_job; + + pid_t leader; + + dual_timestamp timestamp; + + bool in_gc_queue:1; + bool started:1; + bool stopping:1; + + sd_bus_message *create_message; + + int *netif; + unsigned n_netif; + + LIST_HEAD(Operation, operations); + + LIST_FIELDS(Machine, gc_queue); +}; + +Machine* machine_new(Manager *manager, MachineClass class, const char *name); +void machine_free(Machine *m); +bool machine_check_gc(Machine *m, bool drop_not_started); +void machine_add_to_gc_queue(Machine *m); +int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error); +int machine_stop(Machine *m); +int machine_finalize(Machine *m); +int machine_save(Machine *m); +int machine_load(Machine *m); +int machine_kill(Machine *m, KillWho who, int signo); + +void machine_release_unit(Machine *m); + +MachineState machine_get_state(Machine *u); + +const char* machine_class_to_string(MachineClass t) _const_; +MachineClass machine_class_from_string(const char *s) _pure_; + +const char* machine_state_to_string(MachineState t) _const_; +MachineState machine_state_from_string(const char *s) _pure_; + +const char *kill_who_to_string(KillWho k) _const_; +KillWho kill_who_from_string(const char *s) _pure_; + +int machine_openpt(Machine *m, int flags); +int machine_open_terminal(Machine *m, const char *path, int mode); diff --git a/src/grp-machine/libmachine-core/machined-dbus.c b/src/grp-machine/libmachine-core/machined-dbus.c new file mode 100644 index 0000000000..41f138882b --- /dev/null +++ b/src/grp-machine/libmachine-core/machined-dbus.c @@ -0,0 +1,1660 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include + +#include + +#include "alloc-util.h" +#include "btrfs-util.h" +#include "bus-common-errors.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "hostname-util.h" +#include "image-dbus.h" +#include "io-util.h" +#include "machine-dbus.h" +#include "machine-image.h" +#include "machine-pool.h" +#include "machined.h" +#include "path-util.h" +#include "process-util.h" +#include "stdio-util.h" +#include "strv.h" +#include "unit-name.h" +#include "user-util.h" + +static int property_get_pool_path( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + assert(bus); + assert(reply); + + return sd_bus_message_append(reply, "s", "/var/lib/machines"); +} + +static int property_get_pool_usage( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + _cleanup_close_ int fd = -1; + uint64_t usage = (uint64_t) -1; + struct stat st; + + assert(bus); + assert(reply); + + /* We try to read the quota info from /var/lib/machines, as + * well as the usage of the loopback file + * /var/lib/machines.raw, and pick the larger value. */ + + fd = open("/var/lib/machines", O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (fd >= 0) { + BtrfsQuotaInfo q; + + if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0) + usage = q.referenced; + } + + if (stat("/var/lib/machines.raw", &st) >= 0) { + if (usage == (uint64_t) -1 || st.st_blocks * 512ULL > usage) + usage = st.st_blocks * 512ULL; + } + + return sd_bus_message_append(reply, "t", usage); +} + +static int property_get_pool_limit( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + _cleanup_close_ int fd = -1; + uint64_t size = (uint64_t) -1; + struct stat st; + + assert(bus); + assert(reply); + + /* We try to read the quota limit from /var/lib/machines, as + * well as the size of the loopback file + * /var/lib/machines.raw, and pick the smaller value. */ + + fd = open("/var/lib/machines", O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (fd >= 0) { + BtrfsQuotaInfo q; + + if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0) + size = q.referenced_max; + } + + if (stat("/var/lib/machines.raw", &st) >= 0) { + if (size == (uint64_t) -1 || (uint64_t) st.st_size < size) + size = st.st_size; + } + + return sd_bus_message_append(reply, "t", size); +} + +static int method_get_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *p = NULL; + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + p = machine_bus_path(machine); + if (!p) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", p); +} + +static int method_get_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *p = NULL; + Manager *m = userdata; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + r = image_find(name, NULL); + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); + if (r < 0) + return r; + + p = image_bus_path(name); + if (!p) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", p); +} + +static int method_get_machine_by_pid(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *p = NULL; + Manager *m = userdata; + Machine *machine = NULL; + pid_t pid; + int r; + + assert(message); + assert(m); + + assert_cc(sizeof(pid_t) == sizeof(uint32_t)); + + r = sd_bus_message_read(message, "u", &pid); + if (r < 0) + return r; + + if (pid < 0) + return -EINVAL; + + if (pid == 0) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r < 0) + return r; + } + + r = manager_get_machine_by_pid(m, pid, &machine); + if (r < 0) + return r; + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_MACHINE_FOR_PID, "PID "PID_FMT" does not belong to any known machine", pid); + + p = machine_bus_path(machine); + if (!p) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", p); +} + +static int method_list_machines(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Manager *m = userdata; + Machine *machine; + Iterator i; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + r = sd_bus_message_open_container(reply, 'a', "(ssso)"); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + HASHMAP_FOREACH(machine, m->machines, i) { + _cleanup_free_ char *p = NULL; + + p = machine_bus_path(machine); + if (!p) + return -ENOMEM; + + r = sd_bus_message_append(reply, "(ssso)", + machine->name, + strempty(machine_class_to_string(machine->class)), + machine->service, + p); + if (r < 0) + return sd_bus_error_set_errno(error, r); + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + return sd_bus_send(NULL, reply, NULL); +} + +static int method_create_or_register_machine(Manager *manager, sd_bus_message *message, bool read_network, Machine **_m, sd_bus_error *error) { + const char *name, *service, *class, *root_directory; + const int32_t *netif = NULL; + MachineClass c; + uint32_t leader; + sd_id128_t id; + const void *v; + Machine *m; + size_t n, n_netif = 0; + int r; + + assert(manager); + assert(message); + assert(_m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + if (!machine_name_is_valid(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine name"); + + r = sd_bus_message_read_array(message, 'y', &v, &n); + if (r < 0) + return r; + if (n == 0) + id = SD_ID128_NULL; + else if (n == 16) + memcpy(&id, v, n); + else + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine ID parameter"); + + r = sd_bus_message_read(message, "ssus", &service, &class, &leader, &root_directory); + if (r < 0) + return r; + + if (read_network) { + size_t i; + + r = sd_bus_message_read_array(message, 'i', (const void**) &netif, &n_netif); + if (r < 0) + return r; + + n_netif /= sizeof(int32_t); + + for (i = 0; i < n_netif; i++) { + if (netif[i] <= 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid network interface index %i", netif[i]); + } + } + + if (isempty(class)) + c = _MACHINE_CLASS_INVALID; + else { + c = machine_class_from_string(class); + if (c < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine class parameter"); + } + + if (leader == 1) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid leader PID"); + + if (!isempty(root_directory) && !path_is_absolute(root_directory)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Root directory must be empty or an absolute path"); + + if (leader == 0) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return r; + + assert_cc(sizeof(uint32_t) == sizeof(pid_t)); + + r = sd_bus_creds_get_pid(creds, (pid_t*) &leader); + if (r < 0) + return r; + } + + if (hashmap_get(manager->machines, name)) + return sd_bus_error_setf(error, BUS_ERROR_MACHINE_EXISTS, "Machine '%s' already exists", name); + + r = manager_add_machine(manager, name, &m); + if (r < 0) + return r; + + m->leader = leader; + m->class = c; + m->id = id; + + if (!isempty(service)) { + m->service = strdup(service); + if (!m->service) { + r = -ENOMEM; + goto fail; + } + } + + if (!isempty(root_directory)) { + m->root_directory = strdup(root_directory); + if (!m->root_directory) { + r = -ENOMEM; + goto fail; + } + } + + if (n_netif > 0) { + assert_cc(sizeof(int32_t) == sizeof(int)); + m->netif = memdup(netif, sizeof(int32_t) * n_netif); + if (!m->netif) { + r = -ENOMEM; + goto fail; + } + + m->n_netif = n_netif; + } + + *_m = m; + + return 1; + +fail: + machine_add_to_gc_queue(m); + return r; +} + +static int method_create_machine_internal(sd_bus_message *message, bool read_network, void *userdata, sd_bus_error *error) { + Manager *manager = userdata; + Machine *m = NULL; + int r; + + assert(message); + assert(manager); + + r = method_create_or_register_machine(manager, message, read_network, &m, error); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(message, 'a', "(sv)"); + if (r < 0) + goto fail; + + r = machine_start(m, message, error); + if (r < 0) + goto fail; + + m->create_message = sd_bus_message_ref(message); + return 1; + +fail: + machine_add_to_gc_queue(m); + return r; +} + +static int method_create_machine_with_network(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_create_machine_internal(message, true, userdata, error); +} + +static int method_create_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_create_machine_internal(message, false, userdata, error); +} + +static int method_register_machine_internal(sd_bus_message *message, bool read_network, void *userdata, sd_bus_error *error) { + Manager *manager = userdata; + _cleanup_free_ char *p = NULL; + Machine *m = NULL; + int r; + + assert(message); + assert(manager); + + r = method_create_or_register_machine(manager, message, read_network, &m, error); + if (r < 0) + return r; + + r = cg_pid_get_unit(m->leader, &m->unit); + if (r < 0) { + r = sd_bus_error_set_errnof(error, r, "Failed to determine unit of process "PID_FMT" : %s", m->leader, strerror(-r)); + goto fail; + } + + r = machine_start(m, NULL, error); + if (r < 0) + goto fail; + + p = machine_bus_path(m); + if (!p) { + r = -ENOMEM; + goto fail; + } + + return sd_bus_reply_method_return(message, "o", p); + +fail: + machine_add_to_gc_queue(m); + return r; +} + +static int method_register_machine_with_network(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_register_machine_internal(message, true, userdata, error); +} + +static int method_register_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_register_machine_internal(message, false, userdata, error); +} + +static int method_terminate_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_terminate(message, machine, error); +} + +static int method_kill_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_kill(message, machine, error); +} + +static int method_get_machine_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_get_addresses(message, machine, error); +} + +static int method_get_machine_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_get_os_release(message, machine, error); +} + +static int method_list_images(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(image_hashmap_freep) Hashmap *images = NULL; + Manager *m = userdata; + Image *image; + Iterator i; + int r; + + assert(message); + assert(m); + + images = hashmap_new(&string_hash_ops); + if (!images) + return -ENOMEM; + + r = image_discover(images); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(ssbttto)"); + if (r < 0) + return r; + + HASHMAP_FOREACH(image, images, i) { + _cleanup_free_ char *p = NULL; + + p = image_bus_path(image->name); + if (!p) + return -ENOMEM; + + r = sd_bus_message_append(reply, "(ssbttto)", + image->name, + image_type_to_string(image->type), + image->read_only, + image->crtime, + image->mtime, + image->usage, + p); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +static int method_open_machine_pty(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_open_pty(message, machine, error); +} + +static int method_open_machine_login(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_open_login(message, machine, error); +} + +static int method_open_machine_shell(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_open_shell(message, machine, error); +} + +static int method_bind_mount_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_bind_mount(message, machine, error); +} + +static int method_copy_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_copy(message, machine, error); +} + +static int method_open_machine_root_directory(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_open_root_directory(message, machine, error); +} + +static int method_remove_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(image_unrefp) Image* i = NULL; + const char *name; + int r; + + assert(message); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + if (!image_name_is_valid(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); + + r = image_find(name, &i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); + + i->userdata = userdata; + return bus_image_method_remove(message, i, error); +} + +static int method_rename_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(image_unrefp) Image* i = NULL; + const char *old_name; + int r; + + assert(message); + + r = sd_bus_message_read(message, "s", &old_name); + if (r < 0) + return r; + + if (!image_name_is_valid(old_name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", old_name); + + r = image_find(old_name, &i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", old_name); + + i->userdata = userdata; + return bus_image_method_rename(message, i, error); +} + +static int method_clone_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(image_unrefp) Image *i = NULL; + const char *old_name; + int r; + + assert(message); + + r = sd_bus_message_read(message, "s", &old_name); + if (r < 0) + return r; + + if (!image_name_is_valid(old_name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", old_name); + + r = image_find(old_name, &i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", old_name); + + i->userdata = userdata; + return bus_image_method_clone(message, i, error); +} + +static int method_mark_image_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(image_unrefp) Image *i = NULL; + const char *name; + int r; + + assert(message); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + if (!image_name_is_valid(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); + + r = image_find(name, &i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); + + i->userdata = userdata; + return bus_image_method_mark_read_only(message, i, error); +} + +static int method_clean_pool(sd_bus_message *message, void *userdata, sd_bus_error *error) { + enum { + REMOVE_ALL, + REMOVE_HIDDEN, + } mode; + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(image_hashmap_freep) Hashmap *images = NULL; + Manager *m = userdata; + Image *image; + const char *mm; + Iterator i; + int r; + + assert(message); + + r = sd_bus_message_read(message, "s", &mm); + if (r < 0) + return r; + + if (streq(mm, "all")) + mode = REMOVE_ALL; + else if (streq(mm, "hidden")) + mode = REMOVE_HIDDEN; + else + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown mode '%s'.", mm); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-machines", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + images = hashmap_new(&string_hash_ops); + if (!images) + return -ENOMEM; + + r = image_discover(images); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(st)"); + if (r < 0) + return r; + + HASHMAP_FOREACH(image, images, i) { + + /* We can't remove vendor images (i.e. those in /usr) */ + if (IMAGE_IS_VENDOR(image)) + continue; + + if (IMAGE_IS_HOST(image)) + continue; + + if (mode == REMOVE_HIDDEN && !IMAGE_IS_HIDDEN(image)) + continue; + + r = image_remove(image); + if (r == -EBUSY) /* keep images that are currently being used. */ + continue; + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to remove image %s: %m", image->name); + + r = sd_bus_message_append(reply, "(st)", image->name, image->usage_exclusive); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + uint64_t limit; + int r; + + assert(message); + + r = sd_bus_message_read(message, "t", &limit); + if (r < 0) + return r; + if (!FILE_SIZE_VALID_OR_INFINITY(limit)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range"); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-machines", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + /* Set up the machine directory if necessary */ + r = setup_machine_directory(limit, error); + if (r < 0) + return r; + + /* Resize the backing loopback device, if there is one, except if we asked to drop any limit */ + if (limit != (uint64_t) -1) { + r = btrfs_resize_loopback("/var/lib/machines", limit, false); + if (r == -ENOTTY) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Quota is only supported on btrfs."); + if (r < 0 && r != -ENODEV) /* ignore ENODEV, as that's what is returned if the file system is not on loopback */ + return sd_bus_error_set_errnof(error, r, "Failed to adjust loopback limit: %m"); + } + + (void) btrfs_qgroup_set_limit("/var/lib/machines", 0, limit); + + r = btrfs_subvol_set_subtree_quota_limit("/var/lib/machines", 0, limit); + if (r == -ENOTTY) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Quota is only supported on btrfs."); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to adjust quota limit: %m"); + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_set_image_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(image_unrefp) Image *i = NULL; + const char *name; + int r; + + assert(message); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + if (!image_name_is_valid(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); + + r = image_find(name, &i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); + + i->userdata = userdata; + return bus_image_method_set_limit(message, i, error); +} + +static int method_map_from_machine_user(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_fclose_ FILE *f = NULL; + Manager *m = userdata; + const char *name, *p; + Machine *machine; + uint32_t uid; + int r; + + r = sd_bus_message_read(message, "su", &name, &uid); + if (r < 0) + return r; + + if (!uid_is_valid(uid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + if (machine->class != MACHINE_CONTAINER) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Not supported for non-container machines."); + + p = procfs_file_alloca(machine->leader, "uid_map"); + f = fopen(p, "re"); + if (!f) + return -errno; + + for (;;) { + uid_t uid_base, uid_shift, uid_range, converted; + int k; + + errno = 0; + k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT, &uid_base, &uid_shift, &uid_range); + if (k < 0 && feof(f)) + break; + if (k != 3) { + if (ferror(f) && errno > 0) + return -errno; + + return -EIO; + } + + if (uid < uid_base || uid >= uid_base + uid_range) + continue; + + converted = uid - uid_base + uid_shift; + if (!uid_is_valid(converted)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); + + return sd_bus_reply_method_return(message, "u", (uint32_t) converted); + } + + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "Machine '%s' has no matching user mappings.", name); +} + +static int method_map_to_machine_user(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + uid_t uid; + Iterator i; + int r; + + r = sd_bus_message_read(message, "u", &uid); + if (r < 0) + return r; + if (!uid_is_valid(uid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); + if (uid < 0x10000) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "User " UID_FMT " belongs to host UID range", uid); + + HASHMAP_FOREACH(machine, m->machines, i) { + _cleanup_fclose_ FILE *f = NULL; + char p[strlen("/proc//uid_map") + DECIMAL_STR_MAX(pid_t) + 1]; + + if (machine->class != MACHINE_CONTAINER) + continue; + + xsprintf(p, "/proc/" UID_FMT "/uid_map", machine->leader); + f = fopen(p, "re"); + if (!f) { + log_warning_errno(errno, "Failed top open %s, ignoring,", p); + continue; + } + + for (;;) { + _cleanup_free_ char *o = NULL; + uid_t uid_base, uid_shift, uid_range, converted; + int k; + + errno = 0; + k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT, &uid_base, &uid_shift, &uid_range); + if (k < 0 && feof(f)) + break; + if (k != 3) { + if (ferror(f) && errno > 0) + return -errno; + + return -EIO; + } + + if (uid < uid_shift || uid >= uid_shift + uid_range) + continue; + + converted = (uid - uid_shift + uid_base); + if (!uid_is_valid(converted)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); + + o = machine_bus_path(machine); + if (!o) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "sou", machine->name, o, (uint32_t) converted); + } + } + + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "No matching user mapping for " UID_FMT ".", uid); +} + +static int method_map_from_machine_group(sd_bus_message *message, void *groupdata, sd_bus_error *error) { + _cleanup_fclose_ FILE *f = NULL; + Manager *m = groupdata; + const char *name, *p; + Machine *machine; + uint32_t gid; + int r; + + r = sd_bus_message_read(message, "su", &name, &gid); + if (r < 0) + return r; + + if (!gid_is_valid(gid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + if (machine->class != MACHINE_CONTAINER) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Not supported for non-container machines."); + + p = procfs_file_alloca(machine->leader, "gid_map"); + f = fopen(p, "re"); + if (!f) + return -errno; + + for (;;) { + gid_t gid_base, gid_shift, gid_range, converted; + int k; + + errno = 0; + k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT, &gid_base, &gid_shift, &gid_range); + if (k < 0 && feof(f)) + break; + if (k != 3) { + if (ferror(f) && errno > 0) + return -errno; + + return -EIO; + } + + if (gid < gid_base || gid >= gid_base + gid_range) + continue; + + converted = gid - gid_base + gid_shift; + if (!gid_is_valid(converted)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); + + return sd_bus_reply_method_return(message, "u", (uint32_t) converted); + } + + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "Machine '%s' has no matching group mappings.", name); +} + +static int method_map_to_machine_group(sd_bus_message *message, void *groupdata, sd_bus_error *error) { + Manager *m = groupdata; + Machine *machine; + gid_t gid; + Iterator i; + int r; + + r = sd_bus_message_read(message, "u", &gid); + if (r < 0) + return r; + if (!gid_is_valid(gid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); + if (gid < 0x10000) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "Group " GID_FMT " belongs to host GID range", gid); + + HASHMAP_FOREACH(machine, m->machines, i) { + _cleanup_fclose_ FILE *f = NULL; + char p[strlen("/proc//gid_map") + DECIMAL_STR_MAX(pid_t) + 1]; + + if (machine->class != MACHINE_CONTAINER) + continue; + + xsprintf(p, "/proc/" GID_FMT "/gid_map", machine->leader); + f = fopen(p, "re"); + if (!f) { + log_warning_errno(errno, "Failed top open %s, ignoring,", p); + continue; + } + + for (;;) { + _cleanup_free_ char *o = NULL; + gid_t gid_base, gid_shift, gid_range, converted; + int k; + + errno = 0; + k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT, &gid_base, &gid_shift, &gid_range); + if (k < 0 && feof(f)) + break; + if (k != 3) { + if (ferror(f) && errno > 0) + return -errno; + + return -EIO; + } + + if (gid < gid_shift || gid >= gid_shift + gid_range) + continue; + + converted = (gid - gid_shift + gid_base); + if (!gid_is_valid(converted)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); + + o = machine_bus_path(machine); + if (!o) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "sou", machine->name, o, (uint32_t) converted); + } + } + + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "No matching group mapping for " GID_FMT ".", gid); +} + +const sd_bus_vtable manager_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("PoolPath", "s", property_get_pool_path, 0, 0), + SD_BUS_PROPERTY("PoolUsage", "t", property_get_pool_usage, 0, 0), + SD_BUS_PROPERTY("PoolLimit", "t", property_get_pool_limit, 0, 0), + SD_BUS_METHOD("GetMachine", "s", "o", method_get_machine, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetImage", "s", "o", method_get_image, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetMachineByPID", "u", "o", method_get_machine_by_pid, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListMachines", NULL, "a(ssso)", method_list_machines, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListImages", NULL, "a(ssbttto)", method_list_images, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CreateMachine", "sayssusa(sv)", "o", method_create_machine, 0), + SD_BUS_METHOD("CreateMachineWithNetwork", "sayssusaia(sv)", "o", method_create_machine_with_network, 0), + SD_BUS_METHOD("RegisterMachine", "sayssus", "o", method_register_machine, 0), + SD_BUS_METHOD("RegisterMachineWithNetwork", "sayssusai", "o", method_register_machine_with_network, 0), + SD_BUS_METHOD("TerminateMachine", "s", NULL, method_terminate_machine, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("KillMachine", "ssi", NULL, method_kill_machine, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetMachineAddresses", "s", "a(iay)", method_get_machine_addresses, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetMachineOSRelease", "s", "a{ss}", method_get_machine_os_release, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("OpenMachinePTY", "s", "hs", method_open_machine_pty, 0), + SD_BUS_METHOD("OpenMachineLogin", "s", "hs", method_open_machine_login, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("OpenMachineShell", "sssasas", "hs", method_open_machine_shell, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("BindMountMachine", "sssbb", NULL, method_bind_mount_machine, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CopyFromMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CopyToMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("OpenMachineRootDirectory", "s", "h", method_open_machine_root_directory, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RemoveImage", "s", NULL, method_remove_image, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RenameImage", "ss", NULL, method_rename_image, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CloneImage", "ssb", NULL, method_clone_image, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MarkImageReadOnly", "sb", NULL, method_mark_image_read_only, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetPoolLimit", "t", NULL, method_set_pool_limit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetImageLimit", "st", NULL, method_set_image_limit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CleanPool", "s", "a(st)", method_clean_pool, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MapFromMachineUser", "su", "u", method_map_from_machine_user, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MapToMachineUser", "u", "sou", method_map_to_machine_user, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MapFromMachineGroup", "su", "u", method_map_from_machine_group, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MapToMachineGroup", "u", "sou", method_map_to_machine_group, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_SIGNAL("MachineNew", "so", 0), + SD_BUS_SIGNAL("MachineRemoved", "so", 0), + SD_BUS_VTABLE_END +}; + +int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) { + const char *path, *result, *unit; + Manager *m = userdata; + Machine *machine; + uint32_t id; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "uoss", &id, &path, &unit, &result); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + machine = hashmap_get(m->machine_units, unit); + if (!machine) + return 0; + + if (streq_ptr(path, machine->scope_job)) { + machine->scope_job = mfree(machine->scope_job); + + if (machine->started) { + if (streq(result, "done")) + machine_send_create_reply(machine, NULL); + else { + _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL; + + sd_bus_error_setf(&e, BUS_ERROR_JOB_FAILED, "Start job for unit %s failed with '%s'", unit, result); + + machine_send_create_reply(machine, &e); + } + } + + machine_save(machine); + } + + machine_add_to_gc_queue(machine); + return 0; +} + +int match_properties_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *unit = NULL; + const char *path; + Manager *m = userdata; + Machine *machine; + int r; + + assert(message); + assert(m); + + path = sd_bus_message_get_path(message); + if (!path) + return 0; + + r = unit_name_from_dbus_path(path, &unit); + if (r == -EINVAL) /* not for a unit */ + return 0; + if (r < 0) { + log_oom(); + return 0; + } + + machine = hashmap_get(m->machine_units, unit); + if (!machine) + return 0; + + machine_add_to_gc_queue(machine); + return 0; +} + +int match_unit_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) { + const char *path, *unit; + Manager *m = userdata; + Machine *machine; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "so", &unit, &path); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + machine = hashmap_get(m->machine_units, unit); + if (!machine) + return 0; + + machine_add_to_gc_queue(machine); + return 0; +} + +int match_reloading(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + Iterator i; + int b, r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + if (b) + return 0; + + /* systemd finished reloading, let's recheck all our machines */ + log_debug("System manager has been reloaded, rechecking machines..."); + + HASHMAP_FOREACH(machine, m->machines, i) + machine_add_to_gc_queue(machine); + + return 0; +} + +int manager_start_scope( + Manager *manager, + const char *scope, + pid_t pid, + const char *slice, + const char *description, + sd_bus_message *more_properties, + sd_bus_error *error, + char **job) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + int r; + + assert(manager); + assert(scope); + assert(pid > 1); + + r = sd_bus_message_new_method_call( + manager->bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "ss", strempty(scope), "fail"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return r; + + if (!isempty(slice)) { + r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); + if (r < 0) + return r; + } + + if (!isempty(description)) { + r = sd_bus_message_append(m, "(sv)", "Description", "s", description); + if (r < 0) + return r; + } + + r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, pid); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "(sv)", "Delegate", "b", 1); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", UINT64_C(16384)); + if (r < 0) + return bus_log_create_error(r); + + if (more_properties) { + r = sd_bus_message_copy(m, more_properties, true); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "a(sa(sv))", 0); + if (r < 0) + return r; + + r = sd_bus_call(manager->bus, m, 0, error, &reply); + if (r < 0) + return r; + + if (job) { + const char *j; + char *copy; + + r = sd_bus_message_read(reply, "o", &j); + if (r < 0) + return r; + + copy = strdup(j); + if (!copy) + return -ENOMEM; + + *job = copy; + } + + return 1; +} + +int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + assert(manager); + assert(unit); + + r = sd_bus_call_method( + manager->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StopUnit", + error, + &reply, + "ss", unit, "fail"); + if (r < 0) { + if (sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) || + sd_bus_error_has_name(error, BUS_ERROR_LOAD_FAILED)) { + + if (job) + *job = NULL; + + sd_bus_error_free(error); + return 0; + } + + return r; + } + + if (job) { + const char *j; + char *copy; + + r = sd_bus_message_read(reply, "o", &j); + if (r < 0) + return r; + + copy = strdup(j); + if (!copy) + return -ENOMEM; + + *job = copy; + } + + return 1; +} + +int manager_kill_unit(Manager *manager, const char *unit, int signo, sd_bus_error *error) { + assert(manager); + assert(unit); + + return sd_bus_call_method( + manager->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "KillUnit", + error, + NULL, + "ssi", unit, "all", signo); +} + +int manager_unit_is_active(Manager *manager, const char *unit) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *path = NULL; + const char *state; + int r; + + assert(manager); + assert(unit); + + path = unit_dbus_path_from_name(unit); + if (!path) + return -ENOMEM; + + r = sd_bus_get_property( + manager->bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "ActiveState", + &error, + &reply, + "s"); + if (r < 0) { + if (sd_bus_error_has_name(&error, SD_BUS_ERROR_NO_REPLY) || + sd_bus_error_has_name(&error, SD_BUS_ERROR_DISCONNECTED)) + return true; + + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT) || + sd_bus_error_has_name(&error, BUS_ERROR_LOAD_FAILED)) + return false; + + return r; + } + + r = sd_bus_message_read(reply, "s", &state); + if (r < 0) + return -EINVAL; + + return !STR_IN_SET(state, "inactive", "failed"); +} + +int manager_job_is_active(Manager *manager, const char *path) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + assert(manager); + assert(path); + + r = sd_bus_get_property( + manager->bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Job", + "State", + &error, + &reply, + "s"); + if (r < 0) { + if (sd_bus_error_has_name(&error, SD_BUS_ERROR_NO_REPLY) || + sd_bus_error_has_name(&error, SD_BUS_ERROR_DISCONNECTED)) + return true; + + if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_OBJECT)) + return false; + + return r; + } + + /* We don't actually care about the state really. The fact + * that we could read the job state is enough for us */ + + return true; +} + +int manager_get_machine_by_pid(Manager *m, pid_t pid, Machine **machine) { + Machine *mm; + int r; + + assert(m); + assert(pid >= 1); + assert(machine); + + mm = hashmap_get(m->machine_leaders, PID_TO_PTR(pid)); + if (!mm) { + _cleanup_free_ char *unit = NULL; + + r = cg_pid_get_unit(pid, &unit); + if (r >= 0) + mm = hashmap_get(m->machine_units, unit); + } + if (!mm) + return 0; + + *machine = mm; + return 1; +} + +int manager_add_machine(Manager *m, const char *name, Machine **_machine) { + Machine *machine; + + assert(m); + assert(name); + + machine = hashmap_get(m->machines, name); + if (!machine) { + machine = machine_new(m, _MACHINE_CLASS_INVALID, name); + if (!machine) + return -ENOMEM; + } + + if (_machine) + *_machine = machine; + + return 0; +} diff --git a/src/grp-machine/libmachine-core/machined.h b/src/grp-machine/libmachine-core/machined.h new file mode 100644 index 0000000000..777571ebc0 --- /dev/null +++ b/src/grp-machine/libmachine-core/machined.h @@ -0,0 +1,82 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 +#include + +#include "hashmap.h" +#include "list.h" + +typedef struct Manager Manager; + +#include "image-dbus.h" +#include "machine-dbus.h" +#include "machine.h" +#include "operation.h" + +struct Manager { + sd_event *event; + sd_bus *bus; + + Hashmap *machines; + Hashmap *machine_units; + Hashmap *machine_leaders; + + Hashmap *polkit_registry; + + Hashmap *image_cache; + sd_event_source *image_cache_defer_event; + + LIST_HEAD(Machine, machine_gc_queue); + + Machine *host_machine; + + LIST_HEAD(Operation, operations); + unsigned n_operations; +}; + +Manager *manager_new(void); +void manager_free(Manager *m); + +int manager_add_machine(Manager *m, const char *name, Machine **_machine); +int manager_enumerate_machines(Manager *m); + +int manager_startup(Manager *m); +int manager_run(Manager *m); + +void manager_gc(Manager *m, bool drop_not_started); + +int manager_get_machine_by_pid(Manager *m, pid_t pid, Machine **machine); + +extern const sd_bus_vtable manager_vtable[]; + +int match_reloading(sd_bus_message *message, void *userdata, sd_bus_error *error); +int match_unit_removed(sd_bus_message *message, void *userdata, sd_bus_error *error); +int match_properties_changed(sd_bus_message *message, void *userdata, sd_bus_error *error); +int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error); + +int manager_start_scope(Manager *manager, const char *scope, pid_t pid, const char *slice, const char *description, sd_bus_message *more_properties, sd_bus_error *error, char **job); +int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job); +int manager_kill_unit(Manager *manager, const char *unit, int signo, sd_bus_error *error); +int manager_unit_is_active(Manager *manager, const char *unit); +int manager_job_is_active(Manager *manager, const char *path); diff --git a/src/grp-machine/libmachine-core/operation.c b/src/grp-machine/libmachine-core/operation.c new file mode 100644 index 0000000000..e6ddc41a55 --- /dev/null +++ b/src/grp-machine/libmachine-core/operation.c @@ -0,0 +1,131 @@ +/*** + This file is part of systemd. + + Copyright 2016 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 "alloc-util.h" +#include "fd-util.h" +#include "operation.h" +#include "process-util.h" + +static int operation_done(sd_event_source *s, const siginfo_t *si, void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + Operation *o = userdata; + int r; + + assert(o); + assert(si); + + log_debug("Operating " PID_FMT " is now complete with with code=%s status=%i", + o->pid, + sigchld_code_to_string(si->si_code), si->si_status); + + o->pid = 0; + + if (si->si_code != CLD_EXITED) { + r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child died abnormally."); + goto fail; + } + + if (si->si_status != EXIT_SUCCESS) { + if (read(o->errno_fd, &r, sizeof(r)) == sizeof(r)) + r = sd_bus_error_set_errnof(&error, r, "%m"); + else + r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child failed."); + + goto fail; + } + + r = sd_bus_reply_method_return(o->message, NULL); + if (r < 0) + log_error_errno(r, "Failed to reply to message: %m"); + + operation_free(o); + return 0; + +fail: + r = sd_bus_reply_method_error(o->message, &error); + if (r < 0) + log_error_errno(r, "Failed to reply to message: %m"); + + operation_free(o); + return 0; +} + +int operation_new(Manager *manager, Machine *machine, pid_t child, sd_bus_message *message, int errno_fd) { + Operation *o; + int r; + + assert(manager); + assert(child > 1); + assert(message); + assert(errno_fd >= 0); + + o = new0(Operation, 1); + if (!o) + return -ENOMEM; + + r = sd_event_add_child(manager->event, &o->event_source, child, WEXITED, operation_done, o); + if (r < 0) { + free(o); + return r; + } + + o->pid = child; + o->message = sd_bus_message_ref(message); + o->errno_fd = errno_fd; + + LIST_PREPEND(operations, manager->operations, o); + manager->n_operations++; + o->manager = manager; + + if (machine) { + LIST_PREPEND(operations_by_machine, machine->operations, o); + o->machine = machine; + } + + log_debug("Started new operation " PID_FMT ".", child); + + /* At this point we took ownership of both the child and the errno file descriptor! */ + + return 0; +} + +Operation *operation_free(Operation *o) { + if (!o) + return NULL; + + sd_event_source_unref(o->event_source); + + safe_close(o->errno_fd); + + if (o->pid > 1) + (void) sigkill_wait(o->pid); + + sd_bus_message_unref(o->message); + + if (o->manager) { + LIST_REMOVE(operations, o->manager->operations, o); + o->manager->n_operations--; + } + + if (o->machine) + LIST_REMOVE(operations_by_machine, o->machine->operations, o); + + free(o); + return NULL; +} diff --git a/src/grp-machine/libmachine-core/operation.h b/src/grp-machine/libmachine-core/operation.h new file mode 100644 index 0000000000..9397cd5f6d --- /dev/null +++ b/src/grp-machine/libmachine-core/operation.h @@ -0,0 +1,47 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 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 +#include + +#include "list.h" + +typedef struct Operation Operation; + +#include "machined.h" + +#define OPERATIONS_MAX 64 + +struct Operation { + Manager *manager; + Machine *machine; + pid_t pid; + sd_bus_message *message; + int errno_fd; + sd_event_source *event_source; + LIST_FIELDS(Operation, operations); + LIST_FIELDS(Operation, operations_by_machine); +}; + +int operation_new(Manager *manager, Machine *machine, pid_t child, sd_bus_message *message, int errno_fd); +Operation *operation_free(Operation *o); diff --git a/src/grp-machine/libmachine-core/org.freedesktop.machine1.conf b/src/grp-machine/libmachine-core/org.freedesktop.machine1.conf new file mode 100644 index 0000000000..9d40b90151 --- /dev/null +++ b/src/grp-machine/libmachine-core/org.freedesktop.machine1.conf @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/grp-machine/libmachine-core/org.freedesktop.machine1.policy.in b/src/grp-machine/libmachine-core/org.freedesktop.machine1.policy.in new file mode 100644 index 0000000000..69f78a5c25 --- /dev/null +++ b/src/grp-machine/libmachine-core/org.freedesktop.machine1.policy.in @@ -0,0 +1,102 @@ + + + + + + + + The systemd Project + http://www.freedesktop.org/wiki/Software/systemd + + + <_description>Log into a local container + <_message>Authentication is required to log into a local container. + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Log into the local host + <_message>Authentication is required to log into the local host. + + auth_admin + auth_admin + yes + + + + + <_description>Acquire a shell in a local container + <_message>Authentication is required to acquire a shell in a local container. + + auth_admin + auth_admin + auth_admin_keep + + org.freedesktop.login1.login + + + + <_description>Acquire a shell on the local host + <_message>Authentication is required to acquire a shell on the local host. + + auth_admin + auth_admin + auth_admin_keep + + org.freedesktop.login1.host-login + + + + <_description>Acquire a pseudo TTY in a local container + <_message>Authentication is required to acquire a pseudo TTY in a local container. + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Acquire a pseudo TTY on the local host + <_message>Authentication is required to acquire a pseudo TTY on the local host. + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Manage local virtual machines and containers + <_message>Authentication is required to manage local virtual machines and containers. + + auth_admin + auth_admin + auth_admin_keep + + org.freedesktop.login1.shell org.freedesktop.login1.login + + + + <_description>Manage local virtual machine and container images + <_message>Authentication is required to manage local virtual machine and container images. + + auth_admin + auth_admin + auth_admin_keep + + + + diff --git a/src/grp-machine/libmachine-core/org.freedesktop.machine1.service b/src/grp-machine/libmachine-core/org.freedesktop.machine1.service new file mode 100644 index 0000000000..d3dc99852b --- /dev/null +++ b/src/grp-machine/libmachine-core/org.freedesktop.machine1.service @@ -0,0 +1,12 @@ +# This file is part of systemd. +# +# 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. + +[D-BUS Service] +Name=org.freedesktop.machine1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.machine1.service diff --git a/src/grp-machine/libmachine-core/test-machine-tables.c b/src/grp-machine/libmachine-core/test-machine-tables.c new file mode 100644 index 0000000000..f851a4d37d --- /dev/null +++ b/src/grp-machine/libmachine-core/test-machine-tables.c @@ -0,0 +1,29 @@ +/*** + This file is part of systemd + + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + 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 "machine.h" +#include "test-tables.h" + +int main(int argc, char **argv) { + test_table(machine_class, MACHINE_CLASS); + test_table(machine_state, MACHINE_STATE); + test_table(kill_who, KILL_WHO); + + return EXIT_SUCCESS; +} diff --git a/src/grp-machine/machinectl/Makefile b/src/grp-machine/machinectl/Makefile new file mode 100644 index 0000000000..556256eada --- /dev/null +++ b/src/grp-machine/machinectl/Makefile @@ -0,0 +1,42 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +machinectl_SOURCES = \ + src/machine/machinectl.c + +machinectl_LDADD = \ + libshared.la + +bin_PROGRAMS += \ + machinectl + +dist_bashcompletion_data += \ + shell-completion/bash/machinectl + +dist_zshcompletion_data += \ + shell-completion/zsh/_machinectl \ + shell-completion/zsh/_sd_machines + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-machine/machinectl/machinectl.c b/src/grp-machine/machinectl/machinectl.c new file mode 100644 index 0000000000..92ffa8b83d --- /dev/null +++ b/src/grp-machine/machinectl/machinectl.c @@ -0,0 +1,2782 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-unit-util.h" +#include "bus-util.h" +#include "cgroup-show.h" +#include "cgroup-util.h" +#include "copy.h" +#include "env-util.h" +#include "fd-util.h" +#include "hostname-util.h" +#include "import-util.h" +#include "log.h" +#include "logs-show.h" +#include "macro.h" +#include "mkdir.h" +#include "pager.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "ptyfwd.h" +#include "signal-util.h" +#include "spawn-polkit-agent.h" +#include "strv.h" +#include "terminal-util.h" +#include "unit-name.h" +#include "util.h" +#include "verbs.h" +#include "web-util.h" + +static char **arg_property = NULL; +static bool arg_all = false; +static bool arg_value = false; +static bool arg_full = false; +static bool arg_no_pager = false; +static bool arg_legend = true; +static const char *arg_kill_who = NULL; +static int arg_signal = SIGTERM; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static char *arg_host = NULL; +static bool arg_read_only = false; +static bool arg_mkdir = false; +static bool arg_quiet = false; +static bool arg_ask_password = true; +static unsigned arg_lines = 10; +static OutputMode arg_output = OUTPUT_SHORT; +static bool arg_force = false; +static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; +static const char* arg_format = NULL; +static const char *arg_uid = NULL; +static char **arg_setenv = NULL; + +static void polkit_agent_open_if_enabled(void) { + + /* Open the polkit agent as a child process if necessary */ + + if (!arg_ask_password) + return; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return; + + polkit_agent_open(); +} + +static OutputFlags get_output_flags(void) { + return + arg_all * OUTPUT_SHOW_ALL | + arg_full * OUTPUT_FULL_WIDTH | + (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH | + colors_enabled() * OUTPUT_COLOR | + !arg_quiet * OUTPUT_WARN_CUTOFF; +} + +typedef struct MachineInfo { + const char *name; + const char *class; + const char *service; +} MachineInfo; + +static int compare_machine_info(const void *a, const void *b) { + const MachineInfo *x = a, *y = b; + + return strcmp(x->name, y->name); +} + +static int list_machines(int argc, char *argv[], void *userdata) { + + size_t max_name = strlen("MACHINE"), max_class = strlen("CLASS"), max_service = strlen("SERVICE"); + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ MachineInfo *machines = NULL; + const char *name, *class, *service, *object; + size_t n_machines = 0, n_allocated = 0, j; + sd_bus *bus = userdata; + int r; + + assert(bus); + + pager_open(arg_no_pager, false); + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "ListMachines", + &error, + &reply, + NULL); + if (r < 0) { + log_error("Could not get machines: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_enter_container(reply, 'a', "(ssso)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) { + size_t l; + + if (name[0] == '.' && !arg_all) + continue; + + if (!GREEDY_REALLOC(machines, n_allocated, n_machines + 1)) + return log_oom(); + + machines[n_machines].name = name; + machines[n_machines].class = class; + machines[n_machines].service = service; + + l = strlen(name); + if (l > max_name) + max_name = l; + + l = strlen(class); + if (l > max_class) + max_class = l; + + l = strlen(service); + if (l > max_service) + max_service = l; + + n_machines++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + qsort_safe(machines, n_machines, sizeof(MachineInfo), compare_machine_info); + + if (arg_legend) + printf("%-*s %-*s %-*s\n", + (int) max_name, "MACHINE", + (int) max_class, "CLASS", + (int) max_service, "SERVICE"); + + for (j = 0; j < n_machines; j++) + printf("%-*s %-*s %-*s\n", + (int) max_name, machines[j].name, + (int) max_class, machines[j].class, + (int) max_service, machines[j].service); + + if (arg_legend) + printf("\n%zu machines listed.\n", n_machines); + + return 0; +} + +typedef struct ImageInfo { + const char *name; + const char *type; + bool read_only; + usec_t crtime; + usec_t mtime; + uint64_t size; +} ImageInfo; + +static int compare_image_info(const void *a, const void *b) { + const ImageInfo *x = a, *y = b; + + return strcmp(x->name, y->name); +} + +static int list_images(int argc, char *argv[], void *userdata) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + size_t max_name = strlen("NAME"), max_type = strlen("TYPE"), max_size = strlen("USAGE"), max_crtime = strlen("CREATED"), max_mtime = strlen("MODIFIED"); + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ ImageInfo *images = NULL; + size_t n_images = 0, n_allocated = 0, j; + const char *name, *type, *object; + sd_bus *bus = userdata; + uint64_t crtime, mtime, size; + int read_only, r; + + assert(bus); + + pager_open(arg_no_pager, false); + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "ListImages", + &error, + &reply, + ""); + if (r < 0) { + log_error("Could not get images: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbttto)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(ssbttto)", &name, &type, &read_only, &crtime, &mtime, &size, &object)) > 0) { + char buf[MAX(FORMAT_TIMESTAMP_MAX, FORMAT_BYTES_MAX)]; + size_t l; + + if (name[0] == '.' && !arg_all) + continue; + + if (!GREEDY_REALLOC(images, n_allocated, n_images + 1)) + return log_oom(); + + images[n_images].name = name; + images[n_images].type = type; + images[n_images].read_only = read_only; + images[n_images].crtime = crtime; + images[n_images].mtime = mtime; + images[n_images].size = size; + + l = strlen(name); + if (l > max_name) + max_name = l; + + l = strlen(type); + if (l > max_type) + max_type = l; + + if (crtime != 0) { + l = strlen(strna(format_timestamp(buf, sizeof(buf), crtime))); + if (l > max_crtime) + max_crtime = l; + } + + if (mtime != 0) { + l = strlen(strna(format_timestamp(buf, sizeof(buf), mtime))); + if (l > max_mtime) + max_mtime = l; + } + + if (size != (uint64_t) -1) { + l = strlen(strna(format_bytes(buf, sizeof(buf), size))); + if (l > max_size) + max_size = l; + } + + n_images++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + qsort_safe(images, n_images, sizeof(ImageInfo), compare_image_info); + + if (arg_legend) + printf("%-*s %-*s %-3s %-*s %-*s %-*s\n", + (int) max_name, "NAME", + (int) max_type, "TYPE", + "RO", + (int) max_size, "USAGE", + (int) max_crtime, "CREATED", + (int) max_mtime, "MODIFIED"); + + for (j = 0; j < n_images; j++) { + char crtime_buf[FORMAT_TIMESTAMP_MAX], mtime_buf[FORMAT_TIMESTAMP_MAX], size_buf[FORMAT_BYTES_MAX]; + + printf("%-*s %-*s %s%-3s%s %-*s %-*s %-*s\n", + (int) max_name, images[j].name, + (int) max_type, images[j].type, + images[j].read_only ? ansi_highlight_red() : "", yes_no(images[j].read_only), images[j].read_only ? ansi_normal() : "", + (int) max_size, strna(format_bytes(size_buf, sizeof(size_buf), images[j].size)), + (int) max_crtime, strna(format_timestamp(crtime_buf, sizeof(crtime_buf), images[j].crtime)), + (int) max_mtime, strna(format_timestamp(mtime_buf, sizeof(mtime_buf), images[j].mtime))); + } + + if (arg_legend) + printf("\n%zu images listed.\n", n_images); + + return 0; +} + +static int show_unit_cgroup(sd_bus *bus, const char *unit, pid_t leader) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *path = NULL; + const char *cgroup; + int r; + unsigned c; + + assert(bus); + assert(unit); + + path = unit_dbus_path_from_name(unit); + if (!path) + return log_oom(); + + r = sd_bus_get_property( + bus, + "org.freedesktop.systemd1", + path, + unit_dbus_interface_from_name(unit), + "ControlGroup", + &error, + &reply, + "s"); + if (r < 0) + return log_error_errno(r, "Failed to query ControlGroup: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "s", &cgroup); + if (r < 0) + return bus_log_parse_error(r); + + if (isempty(cgroup)) + return 0; + + c = columns(); + if (c > 18) + c -= 18; + else + c = 0; + + r = unit_show_processes(bus, unit, cgroup, "\t\t ", c, get_output_flags(), &error); + if (r == -EBADR) { + + if (arg_transport == BUS_TRANSPORT_REMOTE) + return 0; + + /* Fallback for older systemd versions where the GetUnitProcesses() call is not yet available */ + + if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup) != 0 && leader <= 0) + return 0; + + show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, &leader, leader > 0, get_output_flags()); + } else if (r < 0) + return log_error_errno(r, "Failed to dump process list: %s", bus_error_message(&error, r)); + + return 0; +} + +static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *prefix, const char *prefix2) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + assert(bus); + assert(name); + assert(prefix); + assert(prefix2); + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachineAddresses", + NULL, + &reply, + "s", name); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'a', "(iay)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) { + int family; + const void *a; + size_t sz; + char buffer[MAX(INET6_ADDRSTRLEN, INET_ADDRSTRLEN)]; + + r = sd_bus_message_read(reply, "i", &family); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + return bus_log_parse_error(r); + + fputs(prefix, stdout); + fputs(inet_ntop(family, a, buffer, sizeof(buffer)), stdout); + if (family == AF_INET6 && ifi > 0) + printf("%%%i", ifi); + fputc('\n', stdout); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (prefix != prefix2) + prefix = prefix2; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + return 0; +} + +static int print_os_release(sd_bus *bus, const char *name, const char *prefix) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *k, *v, *pretty = NULL; + int r; + + assert(bus); + assert(name); + assert(prefix); + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachineOSRelease", + NULL, + &reply, + "s", name); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'a', "{ss}"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "{ss}", &k, &v)) > 0) { + if (streq(k, "PRETTY_NAME")) + pretty = v; + + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (pretty) + printf("%s%s\n", prefix, pretty); + + return 0; +} + +typedef struct MachineStatusInfo { + char *name; + sd_id128_t id; + char *class; + char *service; + char *unit; + char *root_directory; + pid_t leader; + struct dual_timestamp timestamp; + int *netif; + unsigned n_netif; +} MachineStatusInfo; + +static void machine_status_info_clear(MachineStatusInfo *info) { + if (info) { + free(info->name); + free(info->class); + free(info->service); + free(info->unit); + free(info->root_directory); + free(info->netif); + zero(*info); + } +} + +static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) { + char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; + char since2[FORMAT_TIMESTAMP_MAX], *s2; + int ifi = -1; + + assert(bus); + assert(i); + + fputs(strna(i->name), stdout); + + if (!sd_id128_equal(i->id, SD_ID128_NULL)) + printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id)); + else + putchar('\n'); + + s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp.realtime); + s2 = format_timestamp(since2, sizeof(since2), i->timestamp.realtime); + + if (s1) + printf("\t Since: %s; %s\n", s2, s1); + else if (s2) + printf("\t Since: %s\n", s2); + + if (i->leader > 0) { + _cleanup_free_ char *t = NULL; + + printf("\t Leader: %u", (unsigned) i->leader); + + get_process_comm(i->leader, &t); + if (t) + printf(" (%s)", t); + + putchar('\n'); + } + + if (i->service) { + printf("\t Service: %s", i->service); + + if (i->class) + printf("; class %s", i->class); + + putchar('\n'); + } else if (i->class) + printf("\t Class: %s\n", i->class); + + if (i->root_directory) + printf("\t Root: %s\n", i->root_directory); + + if (i->n_netif > 0) { + unsigned c; + + fputs("\t Iface:", stdout); + + for (c = 0; c < i->n_netif; c++) { + char name[IF_NAMESIZE+1] = ""; + + if (if_indextoname(i->netif[c], name)) { + fputc(' ', stdout); + fputs(name, stdout); + + if (ifi < 0) + ifi = i->netif[c]; + else + ifi = 0; + } else + printf(" %i", i->netif[c]); + } + + fputc('\n', stdout); + } + + print_addresses(bus, i->name, ifi, + "\t Address: ", + "\t "); + + print_os_release(bus, i->name, "\t OS: "); + + if (i->unit) { + printf("\t Unit: %s\n", i->unit); + show_unit_cgroup(bus, i->unit, i->leader); + + if (arg_transport == BUS_TRANSPORT_LOCAL) + + show_journal_by_unit( + stdout, + i->unit, + arg_output, + 0, + i->timestamp.monotonic, + arg_lines, + 0, + get_output_flags() | OUTPUT_BEGIN_NEWLINE, + SD_JOURNAL_LOCAL_ONLY, + true, + NULL); + } +} + +static int map_netif(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { + MachineStatusInfo *i = userdata; + size_t l; + const void *v; + int r; + + assert_cc(sizeof(int32_t) == sizeof(int)); + r = sd_bus_message_read_array(m, SD_BUS_TYPE_INT32, &v, &l); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + + i->n_netif = l / sizeof(int32_t); + i->netif = memdup(v, l); + if (!i->netif) + return -ENOMEM; + + return 0; +} + +static int show_machine_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) { + + static const struct bus_properties_map map[] = { + { "Name", "s", NULL, offsetof(MachineStatusInfo, name) }, + { "Class", "s", NULL, offsetof(MachineStatusInfo, class) }, + { "Service", "s", NULL, offsetof(MachineStatusInfo, service) }, + { "Unit", "s", NULL, offsetof(MachineStatusInfo, unit) }, + { "RootDirectory", "s", NULL, offsetof(MachineStatusInfo, root_directory) }, + { "Leader", "u", NULL, offsetof(MachineStatusInfo, leader) }, + { "Timestamp", "t", NULL, offsetof(MachineStatusInfo, timestamp.realtime) }, + { "TimestampMonotonic", "t", NULL, offsetof(MachineStatusInfo, timestamp.monotonic) }, + { "Id", "ay", bus_map_id128, offsetof(MachineStatusInfo, id) }, + { "NetworkInterfaces", "ai", map_netif, 0 }, + {} + }; + + _cleanup_(machine_status_info_clear) MachineStatusInfo info = {}; + int r; + + assert(verb); + assert(bus); + assert(path); + assert(new_line); + + r = bus_map_all_properties(bus, + "org.freedesktop.machine1", + path, + map, + &info); + if (r < 0) + return log_error_errno(r, "Could not get properties: %m"); + + if (*new_line) + printf("\n"); + *new_line = true; + + print_machine_status_info(bus, &info); + + return r; +} + +static int show_machine_properties(sd_bus *bus, const char *path, bool *new_line) { + int r; + + assert(bus); + assert(path); + assert(new_line); + + if (*new_line) + printf("\n"); + + *new_line = true; + + r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_value, arg_all); + if (r < 0) + log_error_errno(r, "Could not get properties: %m"); + + return r; +} + +static int show_machine(int argc, char *argv[], void *userdata) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + bool properties, new_line = false; + sd_bus *bus = userdata; + int r = 0, i; + + assert(bus); + + properties = !strstr(argv[0], "status"); + + pager_open(arg_no_pager, false); + + if (properties && argc <= 1) { + + /* If no argument is specified, inspect the manager + * itself */ + r = show_machine_properties(bus, "/org/freedesktop/machine1", &new_line); + if (r < 0) + return r; + } + + for (i = 1; i < argc; i++) { + const char *path = NULL; + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachine", + &error, + &reply, + "s", argv[i]); + if (r < 0) { + log_error("Could not get path to machine: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); + + if (properties) + r = show_machine_properties(bus, path, &new_line); + else + r = show_machine_info(argv[0], bus, path, &new_line); + } + + return r; +} + +typedef struct ImageStatusInfo { + char *name; + char *path; + char *type; + int read_only; + usec_t crtime; + usec_t mtime; + uint64_t usage; + uint64_t limit; + uint64_t usage_exclusive; + uint64_t limit_exclusive; +} ImageStatusInfo; + +static void image_status_info_clear(ImageStatusInfo *info) { + if (info) { + free(info->name); + free(info->path); + free(info->type); + zero(*info); + } +} + +static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) { + char ts_relative[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; + char ts_absolute[FORMAT_TIMESTAMP_MAX], *s2; + char bs[FORMAT_BYTES_MAX], *s3; + char bs_exclusive[FORMAT_BYTES_MAX], *s4; + + assert(bus); + assert(i); + + if (i->name) { + fputs(i->name, stdout); + putchar('\n'); + } + + if (i->type) + printf("\t Type: %s\n", i->type); + + if (i->path) + printf("\t Path: %s\n", i->path); + + printf("\t RO: %s%s%s\n", + i->read_only ? ansi_highlight_red() : "", + i->read_only ? "read-only" : "writable", + i->read_only ? ansi_normal() : ""); + + s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->crtime); + s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->crtime); + if (s1 && s2) + printf("\t Created: %s; %s\n", s2, s1); + else if (s2) + printf("\t Created: %s\n", s2); + + s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->mtime); + s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->mtime); + if (s1 && s2) + printf("\tModified: %s; %s\n", s2, s1); + else if (s2) + printf("\tModified: %s\n", s2); + + s3 = format_bytes(bs, sizeof(bs), i->usage); + s4 = i->usage_exclusive != i->usage ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->usage_exclusive) : NULL; + if (s3 && s4) + printf("\t Usage: %s (exclusive: %s)\n", s3, s4); + else if (s3) + printf("\t Usage: %s\n", s3); + + s3 = format_bytes(bs, sizeof(bs), i->limit); + s4 = i->limit_exclusive != i->limit ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->limit_exclusive) : NULL; + if (s3 && s4) + printf("\t Limit: %s (exclusive: %s)\n", s3, s4); + else if (s3) + printf("\t Limit: %s\n", s3); +} + +static int show_image_info(sd_bus *bus, const char *path, bool *new_line) { + + static const struct bus_properties_map map[] = { + { "Name", "s", NULL, offsetof(ImageStatusInfo, name) }, + { "Path", "s", NULL, offsetof(ImageStatusInfo, path) }, + { "Type", "s", NULL, offsetof(ImageStatusInfo, type) }, + { "ReadOnly", "b", NULL, offsetof(ImageStatusInfo, read_only) }, + { "CreationTimestamp", "t", NULL, offsetof(ImageStatusInfo, crtime) }, + { "ModificationTimestamp", "t", NULL, offsetof(ImageStatusInfo, mtime) }, + { "Usage", "t", NULL, offsetof(ImageStatusInfo, usage) }, + { "Limit", "t", NULL, offsetof(ImageStatusInfo, limit) }, + { "UsageExclusive", "t", NULL, offsetof(ImageStatusInfo, usage_exclusive) }, + { "LimitExclusive", "t", NULL, offsetof(ImageStatusInfo, limit_exclusive) }, + {} + }; + + _cleanup_(image_status_info_clear) ImageStatusInfo info = {}; + int r; + + assert(bus); + assert(path); + assert(new_line); + + r = bus_map_all_properties(bus, + "org.freedesktop.machine1", + path, + map, + &info); + if (r < 0) + return log_error_errno(r, "Could not get properties: %m"); + + if (*new_line) + printf("\n"); + *new_line = true; + + print_image_status_info(bus, &info); + + return r; +} + +typedef struct PoolStatusInfo { + char *path; + uint64_t usage; + uint64_t limit; +} PoolStatusInfo; + +static void pool_status_info_clear(PoolStatusInfo *info) { + if (info) { + free(info->path); + zero(*info); + info->usage = -1; + info->limit = -1; + } +} + +static void print_pool_status_info(sd_bus *bus, PoolStatusInfo *i) { + char bs[FORMAT_BYTES_MAX], *s; + + if (i->path) + printf("\t Path: %s\n", i->path); + + s = format_bytes(bs, sizeof(bs), i->usage); + if (s) + printf("\t Usage: %s\n", s); + + s = format_bytes(bs, sizeof(bs), i->limit); + if (s) + printf("\t Limit: %s\n", s); +} + +static int show_pool_info(sd_bus *bus) { + + static const struct bus_properties_map map[] = { + { "PoolPath", "s", NULL, offsetof(PoolStatusInfo, path) }, + { "PoolUsage", "t", NULL, offsetof(PoolStatusInfo, usage) }, + { "PoolLimit", "t", NULL, offsetof(PoolStatusInfo, limit) }, + {} + }; + + _cleanup_(pool_status_info_clear) PoolStatusInfo info = { + .usage = (uint64_t) -1, + .limit = (uint64_t) -1, + }; + int r; + + assert(bus); + + r = bus_map_all_properties(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + map, + &info); + if (r < 0) + return log_error_errno(r, "Could not get properties: %m"); + + print_pool_status_info(bus, &info); + + return 0; +} + + +static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) { + int r; + + assert(bus); + assert(path); + assert(new_line); + + if (*new_line) + printf("\n"); + + *new_line = true; + + r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_value, arg_all); + if (r < 0) + log_error_errno(r, "Could not get properties: %m"); + + return r; +} + +static int show_image(int argc, char *argv[], void *userdata) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + bool properties, new_line = false; + sd_bus *bus = userdata; + int r = 0, i; + + assert(bus); + + properties = !strstr(argv[0], "status"); + + pager_open(arg_no_pager, false); + + if (argc <= 1) { + + /* If no argument is specified, inspect the manager + * itself */ + + if (properties) + r = show_image_properties(bus, "/org/freedesktop/machine1", &new_line); + else + r = show_pool_info(bus); + if (r < 0) + return r; + } + + for (i = 1; i < argc; i++) { + const char *path = NULL; + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetImage", + &error, + &reply, + "s", argv[i]); + if (r < 0) { + log_error("Could not get path to image: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); + + if (properties) + r = show_image_properties(bus, path, &new_line); + else + r = show_image_info(bus, path, &new_line); + } + + return r; +} + +static int kill_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + if (!arg_kill_who) + arg_kill_who = "all"; + + for (i = 1; i < argc; i++) { + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "KillMachine", + &error, + NULL, + "ssi", argv[i], arg_kill_who, arg_signal); + if (r < 0) { + log_error("Could not kill machine: %s", bus_error_message(&error, -r)); + return r; + } + } + + return 0; +} + +static int reboot_machine(int argc, char *argv[], void *userdata) { + arg_kill_who = "leader"; + arg_signal = SIGINT; /* sysvinit + systemd */ + + return kill_machine(argc, argv, userdata); +} + +static int poweroff_machine(int argc, char *argv[], void *userdata) { + arg_kill_who = "leader"; + arg_signal = SIGRTMIN+4; /* only systemd */ + + return kill_machine(argc, argv, userdata); +} + +static int terminate_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + for (i = 1; i < argc; i++) { + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "TerminateMachine", + &error, + NULL, + "s", argv[i]); + if (r < 0) { + log_error("Could not terminate machine: %s", bus_error_message(&error, -r)); + return r; + } + } + + return 0; +} + +static int copy_files(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *abs_host_path = NULL; + char *dest, *host_path, *container_path; + sd_bus *bus = userdata; + bool copy_from; + int r; + + assert(bus); + + polkit_agent_open_if_enabled(); + + copy_from = streq(argv[0], "copy-from"); + dest = argv[3] ?: argv[2]; + host_path = copy_from ? dest : argv[2]; + container_path = copy_from ? argv[2] : dest; + + if (!path_is_absolute(host_path)) { + r = path_make_absolute_cwd(host_path, &abs_host_path); + if (r < 0) + return log_error_errno(r, "Failed to make path absolute: %m"); + + host_path = abs_host_path; + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + copy_from ? "CopyFromMachine" : "CopyToMachine"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sss", + argv[1], + copy_from ? container_path : host_path, + copy_from ? host_path : container_path); + if (r < 0) + return bus_log_create_error(r); + + /* This is a slow operation, hence turn off any method call timeouts */ + r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL); + if (r < 0) + return log_error_errno(r, "Failed to copy: %s", bus_error_message(&error, r)); + + return 0; +} + +static int bind_mount(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + polkit_agent_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "BindMountMachine", + &error, + NULL, + "sssbb", + argv[1], + argv[2], + argv[3], + arg_read_only, + arg_mkdir); + if (r < 0) { + log_error("Failed to bind mount: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int on_machine_removed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + PTYForward ** forward = (PTYForward**) userdata; + int r; + + assert(m); + assert(forward); + + if (*forward) { + /* If the forwarder is already initialized, tell it to + * exit on the next vhangup(), so that we still flush + * out what might be queued and exit then. */ + + r = pty_forward_set_ignore_vhangup(*forward, false); + if (r >= 0) + return 0; + + log_error_errno(r, "Failed to set ignore_vhangup flag: %m"); + } + + /* On error, or when the forwarder is not initialized yet, quit immediately */ + sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), EXIT_FAILURE); + return 0; +} + +static int process_forward(sd_event *event, PTYForward **forward, int master, PTYForwardFlags flags, const char *name) { + char last_char = 0; + bool machine_died; + int ret = 0, r; + + assert(event); + assert(master >= 0); + assert(name); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); + + if (streq(name, ".host")) + log_info("Connected to the local host. Press ^] three times within 1s to exit session."); + else + log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name); + + sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); + sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); + + r = pty_forward_new(event, master, flags, forward); + if (r < 0) + return log_error_errno(r, "Failed to create PTY forwarder: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + pty_forward_get_last_char(*forward, &last_char); + + machine_died = + (flags & PTY_FORWARD_IGNORE_VHANGUP) && + pty_forward_get_ignore_vhangup(*forward) == 0; + + *forward = pty_forward_free(*forward); + + if (last_char != '\n') + fputc('\n', stdout); + + if (machine_died) + log_info("Machine %s terminated.", name); + else if (streq(name, ".host")) + log_info("Connection to the local host terminated."); + else + log_info("Connection to machine %s terminated.", name); + + sd_event_get_exit_code(event, &ret); + return ret; +} + +static int login_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(pty_forward_freep) PTYForward *forward = NULL; + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + int master = -1, r; + sd_bus *bus = userdata; + const char *pty, *match, *machine; + + assert(bus); + + if (!strv_isempty(arg_setenv) || arg_uid) { + log_error("--setenv= and --uid= are not supported for 'login'. Use 'shell' instead."); + return -EINVAL; + } + + if (arg_transport != BUS_TRANSPORT_LOCAL && + arg_transport != BUS_TRANSPORT_MACHINE) { + log_error("Login only supported on local machines."); + return -EOPNOTSUPP; + } + + polkit_agent_open_if_enabled(); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + machine = argc < 2 || isempty(argv[1]) ? ".host" : argv[1]; + + match = strjoina("type='signal'," + "sender='org.freedesktop.machine1'," + "path='/org/freedesktop/machine1',", + "interface='org.freedesktop.machine1.Manager'," + "member='MachineRemoved'," + "arg0='", machine, "'"); + + r = sd_bus_add_match(bus, &slot, match, on_machine_removed, &forward); + if (r < 0) + return log_error_errno(r, "Failed to add machine removal match: %m"); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "OpenMachineLogin", + &error, + &reply, + "s", machine); + if (r < 0) { + log_error("Failed to get login PTY: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "hs", &master, &pty); + if (r < 0) + return bus_log_parse_error(r); + + return process_forward(event, &forward, master, PTY_FORWARD_IGNORE_VHANGUP, machine); +} + +static int shell_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(pty_forward_freep) PTYForward *forward = NULL; + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + int master = -1, r; + sd_bus *bus = userdata; + const char *pty, *match, *machine, *path, *uid = NULL; + + assert(bus); + + if (arg_transport != BUS_TRANSPORT_LOCAL && + arg_transport != BUS_TRANSPORT_MACHINE) { + log_error("Shell only supported on local machines."); + return -EOPNOTSUPP; + } + + /* Pass $TERM to shell session, if not explicitly specified. */ + if (!strv_find_prefix(arg_setenv, "TERM=")) { + const char *t; + + t = strv_find_prefix(environ, "TERM="); + if (t) { + if (strv_extend(&arg_setenv, t) < 0) + return log_oom(); + } + } + + polkit_agent_open_if_enabled(); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + machine = argc < 2 || isempty(argv[1]) ? NULL : argv[1]; + + if (arg_uid) + uid = arg_uid; + else if (machine) { + const char *at; + + at = strchr(machine, '@'); + if (at) { + uid = strndupa(machine, at - machine); + machine = at + 1; + } + } + + if (isempty(machine)) + machine = ".host"; + + match = strjoina("type='signal'," + "sender='org.freedesktop.machine1'," + "path='/org/freedesktop/machine1',", + "interface='org.freedesktop.machine1.Manager'," + "member='MachineRemoved'," + "arg0='", machine, "'"); + + r = sd_bus_add_match(bus, &slot, match, on_machine_removed, &forward); + if (r < 0) + return log_error_errno(r, "Failed to add machine removal match: %m"); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "OpenMachineShell"); + if (r < 0) + return bus_log_create_error(r); + + path = argc < 3 || isempty(argv[2]) ? NULL : argv[2]; + + r = sd_bus_message_append(m, "sss", machine, uid, path); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, strv_length(argv) <= 3 ? NULL : argv + 2); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, arg_setenv); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to get shell PTY: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "hs", &master, &pty); + if (r < 0) + return bus_log_parse_error(r); + + return process_forward(event, &forward, master, 0, machine); +} + +static int remove_image(int argc, char *argv[], void *userdata) { + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + for (i = 1; i < argc; i++) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "RemoveImage"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", argv[i]); + if (r < 0) + return bus_log_create_error(r); + + /* This is a slow operation, hence turn off any method call timeouts */ + r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL); + if (r < 0) + return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r)); + } + + return 0; +} + +static int rename_image(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r; + + polkit_agent_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "RenameImage", + &error, + NULL, + "ss", argv[1], argv[2]); + if (r < 0) { + log_error("Could not rename image: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int clone_image(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + sd_bus *bus = userdata; + int r; + + polkit_agent_open_if_enabled(); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "CloneImage"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "ssb", argv[1], argv[2], arg_read_only); + if (r < 0) + return bus_log_create_error(r); + + /* This is a slow operation, hence turn off any method call timeouts */ + r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL); + if (r < 0) + return log_error_errno(r, "Could not clone image: %s", bus_error_message(&error, r)); + + return 0; +} + +static int read_only_image(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int b = true, r; + + if (argc > 2) { + b = parse_boolean(argv[2]); + if (b < 0) { + log_error("Failed to parse boolean argument: %s", argv[2]); + return -EINVAL; + } + } + + polkit_agent_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "MarkImageReadOnly", + &error, + NULL, + "sb", argv[1], b); + if (r < 0) { + log_error("Could not mark image read-only: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int make_service_name(const char *name, char **ret) { + _cleanup_free_ char *e = NULL; + int r; + + assert(name); + assert(ret); + + if (!machine_name_is_valid(name)) { + log_error("Invalid machine name %s.", name); + return -EINVAL; + } + + e = unit_name_escape(name); + if (!e) + return log_oom(); + + r = unit_name_build("systemd-nspawn", e, ".service", ret); + if (r < 0) + return log_error_errno(r, "Failed to build unit name: %m"); + + return 0; +} + +static int start_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_oom(); + + for (i = 1; i < argc; i++) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *unit = NULL; + const char *object; + + r = make_service_name(argv[i], &unit); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartUnit", + &error, + &reply, + "ss", unit, "fail"); + if (r < 0) { + log_error("Failed to start unit: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_add(w, object); + if (r < 0) + return log_oom(); + } + + r = bus_wait_for_jobs(w, arg_quiet, NULL); + if (r < 0) + return r; + + return 0; +} + +static int enable_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + int carries_install_info = 0; + const char *method = NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + method = streq(argv[0], "enable") ? "EnableUnitFiles" : "DisableUnitFiles"; + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + method); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return bus_log_create_error(r); + + for (i = 1; i < argc; i++) { + _cleanup_free_ char *unit = NULL; + + r = make_service_name(argv[i], &unit); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", unit); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + if (streq(argv[0], "enable")) + r = sd_bus_message_append(m, "bb", false, false); + else + r = sd_bus_message_append(m, "b", false); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to enable or disable unit: %s", bus_error_message(&error, -r)); + return r; + } + + if (streq(argv[0], "enable")) { + r = sd_bus_message_read(reply, "b", carries_install_info); + if (r < 0) + return bus_log_parse_error(r); + } + + r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes); + if (r < 0) + goto finish; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Reload", + &error, + NULL, + NULL); + if (r < 0) { + log_error("Failed to reload daemon: %s", bus_error_message(&error, -r)); + goto finish; + } + + r = 0; + +finish: + unit_file_changes_free(changes, n_changes); + + return r; +} + +static int match_log_message(sd_bus_message *m, void *userdata, sd_bus_error *error) { + const char **our_path = userdata, *line; + unsigned priority; + int r; + + assert(m); + assert(our_path); + + r = sd_bus_message_read(m, "us", &priority, &line); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (!streq_ptr(*our_path, sd_bus_message_get_path(m))) + return 0; + + if (arg_quiet && LOG_PRI(priority) >= LOG_INFO) + return 0; + + log_full(priority, "%s", line); + return 0; +} + +static int match_transfer_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { + const char **our_path = userdata, *path, *result; + uint32_t id; + int r; + + assert(m); + assert(our_path); + + r = sd_bus_message_read(m, "uos", &id, &path, &result); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (!streq_ptr(*our_path, path)) + return 0; + + sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), !streq_ptr(result, "done")); + return 0; +} + +static int transfer_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + assert(s); + assert(si); + + if (!arg_quiet) + log_info("Continuing download in the background. Use \"machinectl cancel-transfer %" PRIu32 "\" to abort transfer.", PTR_TO_UINT32(userdata)); + + sd_event_exit(sd_event_source_get_event(s), EINTR); + return 0; +} + +static int transfer_image_common(sd_bus *bus, sd_bus_message *m) { + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot_job_removed = NULL, *slot_log_message = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_event_unrefp) sd_event* event = NULL; + const char *path = NULL; + uint32_t id; + int r; + + assert(bus); + assert(m); + + polkit_agent_open_if_enabled(); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + r = sd_bus_add_match( + bus, + &slot_job_removed, + "type='signal'," + "sender='org.freedesktop.import1'," + "interface='org.freedesktop.import1.Manager'," + "member='TransferRemoved'," + "path='/org/freedesktop/import1'", + match_transfer_removed, &path); + if (r < 0) + return log_error_errno(r, "Failed to install match: %m"); + + r = sd_bus_add_match( + bus, + &slot_log_message, + "type='signal'," + "sender='org.freedesktop.import1'," + "interface='org.freedesktop.import1.Transfer'," + "member='LogMessage'", + match_log_message, &path); + if (r < 0) + return log_error_errno(r, "Failed to install match: %m"); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to transfer image: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "uo", &id, &path); + if (r < 0) + return bus_log_parse_error(r); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + + if (!arg_quiet) + log_info("Enqueued transfer job %u. Press C-c to continue download in background.", id); + + sd_event_add_signal(event, NULL, SIGINT, transfer_signal_handler, UINT32_TO_PTR(id)); + sd_event_add_signal(event, NULL, SIGTERM, transfer_signal_handler, UINT32_TO_PTR(id)); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + return -r; +} + +static int import_tar(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *ll = NULL; + _cleanup_close_ int fd = -1; + const char *local = NULL, *path = NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + if (argc >= 2) + path = argv[1]; + if (isempty(path) || streq(path, "-")) + path = NULL; + + if (argc >= 3) + local = argv[2]; + else if (path) + local = basename(path); + if (isempty(local) || streq(local, "-")) + local = NULL; + + if (!local) { + log_error("Need either path or local name."); + return -EINVAL; + } + + r = tar_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local name %s is not a suitable machine name.", local); + return -EINVAL; + } + + if (path) { + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ImportTar"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "hsbb", + fd >= 0 ? fd : STDIN_FILENO, + local, + arg_force, + arg_read_only); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int import_raw(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *ll = NULL; + _cleanup_close_ int fd = -1; + const char *local = NULL, *path = NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + if (argc >= 2) + path = argv[1]; + if (isempty(path) || streq(path, "-")) + path = NULL; + + if (argc >= 3) + local = argv[2]; + else if (path) + local = basename(path); + if (isempty(local) || streq(local, "-")) + local = NULL; + + if (!local) { + log_error("Need either path or local name."); + return -EINVAL; + } + + r = raw_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local name %s is not a suitable machine name.", local); + return -EINVAL; + } + + if (path) { + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ImportRaw"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "hsbb", + fd >= 0 ? fd : STDIN_FILENO, + local, + arg_force, + arg_read_only); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static void determine_compression_from_filename(const char *p) { + if (arg_format) + return; + + if (!p) + return; + + if (endswith(p, ".xz")) + arg_format = "xz"; + else if (endswith(p, ".gz")) + arg_format = "gzip"; + else if (endswith(p, ".bz2")) + arg_format = "bzip2"; +} + +static int export_tar(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_close_ int fd = -1; + const char *local = NULL, *path = NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + local = argv[1]; + if (!machine_name_is_valid(local)) { + log_error("Machine name %s is not valid.", local); + return -EINVAL; + } + + if (argc >= 3) + path = argv[2]; + if (isempty(path) || streq(path, "-")) + path = NULL; + + if (path) { + determine_compression_from_filename(path); + + fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ExportTar"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "shs", + local, + fd >= 0 ? fd : STDOUT_FILENO, + arg_format); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int export_raw(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_close_ int fd = -1; + const char *local = NULL, *path = NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + local = argv[1]; + if (!machine_name_is_valid(local)) { + log_error("Machine name %s is not valid.", local); + return -EINVAL; + } + + if (argc >= 3) + path = argv[2]; + if (isempty(path) || streq(path, "-")) + path = NULL; + + if (path) { + determine_compression_from_filename(path); + + fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ExportRaw"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "shs", + local, + fd >= 0 ? fd : STDOUT_FILENO, + arg_format); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int pull_tar(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *l = NULL, *ll = NULL; + const char *local, *remote; + sd_bus *bus = userdata; + int r; + + assert(bus); + + remote = argv[1]; + if (!http_url_is_valid(remote)) { + log_error("URL '%s' is not valid.", remote); + return -EINVAL; + } + + if (argc >= 3) + local = argv[2]; + else { + r = import_url_last_component(remote, &l); + if (r < 0) + return log_error_errno(r, "Failed to get final component of URL: %m"); + + local = l; + } + + if (isempty(local) || streq(local, "-")) + local = NULL; + + if (local) { + r = tar_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local name %s is not a suitable machine name.", local); + return -EINVAL; + } + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "PullTar"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssb", + remote, + local, + import_verify_to_string(arg_verify), + arg_force); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int pull_raw(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *l = NULL, *ll = NULL; + const char *local, *remote; + sd_bus *bus = userdata; + int r; + + assert(bus); + + remote = argv[1]; + if (!http_url_is_valid(remote)) { + log_error("URL '%s' is not valid.", remote); + return -EINVAL; + } + + if (argc >= 3) + local = argv[2]; + else { + r = import_url_last_component(remote, &l); + if (r < 0) + return log_error_errno(r, "Failed to get final component of URL: %m"); + + local = l; + } + + if (isempty(local) || streq(local, "-")) + local = NULL; + + if (local) { + r = raw_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local name %s is not a suitable machine name.", local); + return -EINVAL; + } + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "PullRaw"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssb", + remote, + local, + import_verify_to_string(arg_verify), + arg_force); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +typedef struct TransferInfo { + uint32_t id; + const char *type; + const char *remote; + const char *local; + double progress; +} TransferInfo; + +static int compare_transfer_info(const void *a, const void *b) { + const TransferInfo *x = a, *y = b; + + return strcmp(x->local, y->local); +} + +static int list_transfers(int argc, char *argv[], void *userdata) { + size_t max_type = strlen("TYPE"), max_local = strlen("LOCAL"), max_remote = strlen("REMOTE"); + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ TransferInfo *transfers = NULL; + size_t n_transfers = 0, n_allocated = 0, j; + const char *type, *remote, *local, *object; + sd_bus *bus = userdata; + uint32_t id, max_id = 0; + double progress; + int r; + + pager_open(arg_no_pager, false); + + r = sd_bus_call_method(bus, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ListTransfers", + &error, + &reply, + NULL); + if (r < 0) { + log_error("Could not get transfers: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_enter_container(reply, 'a', "(usssdo)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(usssdo)", &id, &type, &remote, &local, &progress, &object)) > 0) { + size_t l; + + if (!GREEDY_REALLOC(transfers, n_allocated, n_transfers + 1)) + return log_oom(); + + transfers[n_transfers].id = id; + transfers[n_transfers].type = type; + transfers[n_transfers].remote = remote; + transfers[n_transfers].local = local; + transfers[n_transfers].progress = progress; + + l = strlen(type); + if (l > max_type) + max_type = l; + + l = strlen(remote); + if (l > max_remote) + max_remote = l; + + l = strlen(local); + if (l > max_local) + max_local = l; + + if (id > max_id) + max_id = id; + + n_transfers++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + qsort_safe(transfers, n_transfers, sizeof(TransferInfo), compare_transfer_info); + + if (arg_legend) + printf("%-*s %-*s %-*s %-*s %-*s\n", + (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), "ID", + (int) 7, "PERCENT", + (int) max_type, "TYPE", + (int) max_local, "LOCAL", + (int) max_remote, "REMOTE"); + + for (j = 0; j < n_transfers; j++) + printf("%*" PRIu32 " %*u%% %-*s %-*s %-*s\n", + (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), transfers[j].id, + (int) 6, (unsigned) (transfers[j].progress * 100), + (int) max_type, transfers[j].type, + (int) max_local, transfers[j].local, + (int) max_remote, transfers[j].remote); + + if (arg_legend) + printf("\n%zu transfers listed.\n", n_transfers); + + return 0; +} + +static int cancel_transfer(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + for (i = 1; i < argc; i++) { + uint32_t id; + + r = safe_atou32(argv[i], &id); + if (r < 0) + return log_error_errno(r, "Failed to parse transfer id: %s", argv[i]); + + r = sd_bus_call_method( + bus, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "CancelTransfer", + &error, + NULL, + "u", id); + if (r < 0) { + log_error("Could not cancel transfer: %s", bus_error_message(&error, -r)); + return r; + } + } + + return 0; +} + +static int set_limit(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + uint64_t limit; + int r; + + if (STR_IN_SET(argv[argc-1], "-", "none", "infinity")) + limit = (uint64_t) -1; + else { + r = parse_size(argv[argc-1], 1024, &limit); + if (r < 0) + return log_error("Failed to parse size: %s", argv[argc-1]); + } + + if (argc > 2) + /* With two arguments changes the quota limit of the + * specified image */ + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "SetImageLimit", + &error, + NULL, + "st", argv[1], limit); + else + /* With one argument changes the pool quota limit */ + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "SetPoolLimit", + &error, + NULL, + "t", limit); + + if (r < 0) { + log_error("Could not set limit: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int clean_images(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + uint64_t usage, total = 0; + char fb[FORMAT_BYTES_MAX]; + sd_bus *bus = userdata; + const char *name; + unsigned c = 0; + int r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "CleanPool", + &error, + &reply, + "s", arg_all ? "all" : "hidden"); + if (r < 0) + return log_error_errno(r, "Could not clean pool: %s", bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, 'a', "(st)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(st)", &name, &usage)) > 0) { + log_info("Removed image '%s'. Freed exclusive disk space: %s", + name, format_bytes(fb, sizeof(fb), usage)); + + total += usage; + c++; + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + log_info("Removed %u images in total. Total freed exclusive disk space %s.", + c, format_bytes(fb, sizeof(fb), total)); + + return 0; +} + +static int help(int argc, char *argv[], void *userdata) { + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Send control commands to or query the virtual machine and container\n" + "registration manager.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --no-legend Do not show the headers and footers\n" + " --no-ask-password Do not ask for system passwords\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " -p --property=NAME Show only properties by this name\n" + " -q --quiet Suppress output\n" + " -a --all Show all properties, including empty ones\n" + " --value When showing properties, only print the value\n" + " -l --full Do not ellipsize output\n" + " --kill-who=WHO Who to send signal to\n" + " -s --signal=SIGNAL Which signal to send\n" + " --uid=USER Specify user ID to invoke shell as\n" + " -E --setenv=VAR=VALUE Add an environment variable for shell\n" + " --read-only Create read-only bind mount\n" + " --mkdir Create directory before bind mounting, if missing\n" + " -n --lines=INTEGER Number of journal entries to show\n" + " -o --output=STRING Change journal output mode (short,\n" + " short-monotonic, verbose, export, json,\n" + " json-pretty, json-sse, cat)\n" + " --verify=MODE Verification mode for downloaded images (no,\n" + " checksum, signature)\n" + " --force Download image even if already exists\n\n" + "Machine Commands:\n" + " list List running VMs and containers\n" + " status NAME... Show VM/container details\n" + " show [NAME...] Show properties of one or more VMs/containers\n" + " start NAME... Start container as a service\n" + " login [NAME] Get a login prompt in a container or on the\n" + " local host\n" + " shell [[USER@]NAME [COMMAND...]]\n" + " Invoke a shell (or other command) in a container\n" + " or on the local host\n" + " enable NAME... Enable automatic container start at boot\n" + " disable NAME... Disable automatic container start at boot\n" + " poweroff NAME... Power off one or more containers\n" + " reboot NAME... Reboot one or more containers\n" + " terminate NAME... Terminate one or more VMs/containers\n" + " kill NAME... Send signal to processes of a VM/container\n" + " copy-to NAME PATH [PATH] Copy files from the host to a container\n" + " copy-from NAME PATH [PATH] Copy files from a container to the host\n" + " bind NAME PATH [PATH] Bind mount a path from the host into a container\n\n" + "Image Commands:\n" + " list-images Show available container and VM images\n" + " image-status [NAME...] Show image details\n" + " show-image [NAME...] Show properties of image\n" + " clone NAME NAME Clone an image\n" + " rename NAME NAME Rename an image\n" + " read-only NAME [BOOL] Mark or unmark image read-only\n" + " remove NAME... Remove an image\n" + " set-limit [NAME] BYTES Set image or pool size limit (disk quota)\n" + " clean Remove hidden (or all) images\n\n" + "Image Transfer Commands:\n" + " pull-tar URL [NAME] Download a TAR container image\n" + " pull-raw URL [NAME] Download a RAW container or VM image\n" + " import-tar FILE [NAME] Import a local TAR container image\n" + " import-raw FILE [NAME] Import a local RAW container or VM image\n" + " export-tar NAME [FILE] Export a TAR container image locally\n" + " export-raw NAME [FILE] Export a RAW container or VM image locally\n" + " list-transfers Show list of downloads in progress\n" + " cancel-transfer Cancel a download\n" + , program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_NO_LEGEND, + ARG_VALUE, + ARG_KILL_WHO, + ARG_READ_ONLY, + ARG_MKDIR, + ARG_NO_ASK_PASSWORD, + ARG_VERIFY, + ARG_FORCE, + ARG_FORMAT, + ARG_UID, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "property", required_argument, NULL, 'p' }, + { "all", no_argument, NULL, 'a' }, + { "value", no_argument, NULL, ARG_VALUE }, + { "full", no_argument, NULL, 'l' }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "kill-who", required_argument, NULL, ARG_KILL_WHO }, + { "signal", required_argument, NULL, 's' }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "read-only", no_argument, NULL, ARG_READ_ONLY }, + { "mkdir", no_argument, NULL, ARG_MKDIR }, + { "quiet", no_argument, NULL, 'q' }, + { "lines", required_argument, NULL, 'n' }, + { "output", required_argument, NULL, 'o' }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "verify", required_argument, NULL, ARG_VERIFY }, + { "force", no_argument, NULL, ARG_FORCE }, + { "format", required_argument, NULL, ARG_FORMAT }, + { "uid", required_argument, NULL, ARG_UID }, + { "setenv", required_argument, NULL, 'E' }, + {} + }; + + bool reorder = false; + int c, r; + + assert(argc >= 0); + assert(argv); + + for (;;) { + const char * const option_string = "+hp:als:H:M:qn:o:"; + + c = getopt_long(argc, argv, option_string + reorder, options, NULL); + if (c < 0) { + /* We generally are fine with the fact that getopt_long() reorders the command line, and looks + * for switches after the main verb. However, for "shell" we really don't want that, since we + * want that switches passed after that are passed to the program to execute, and not processed + * by us. To make this possible, we'll first invoke getopt_long() with reordering disabled + * (i.e. with the "+" prefix in the option string), and as soon as we hit the end (i.e. the + * verb) we check if that's "shell". If it is, we exit the loop, since we don't want any + * further options processed. However, if it is anything else, we process the same argument + * again, but this time allow reordering. */ + + if (!reorder && optind < argc && !streq(argv[optind], "shell")) { + reorder = true; + optind--; + continue; + } + + break; + } + + switch (c) { + + case 'h': + return help(0, NULL, NULL); + + case ARG_VERSION: + return version(); + + case 'p': + r = strv_extend(&arg_property, optarg); + if (r < 0) + return log_oom(); + + /* If the user asked for a particular + * property, show it to him, even if it is + * empty. */ + arg_all = true; + break; + + case 'a': + arg_all = true; + break; + + case ARG_VALUE: + arg_value = true; + break; + + case 'l': + arg_full = true; + break; + + case 'n': + if (safe_atou(optarg, &arg_lines) < 0) { + log_error("Failed to parse lines '%s'", optarg); + return -EINVAL; + } + break; + + case 'o': + arg_output = output_mode_from_string(optarg); + if (arg_output < 0) { + log_error("Unknown output '%s'.", optarg); + return -EINVAL; + } + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case ARG_NO_LEGEND: + arg_legend = false; + break; + + case ARG_KILL_WHO: + arg_kill_who = optarg; + break; + + case 's': + arg_signal = signal_from_string_try_harder(optarg); + if (arg_signal < 0) { + log_error("Failed to parse signal string %s.", optarg); + return -EINVAL; + } + break; + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_READ_ONLY: + arg_read_only = true; + break; + + case ARG_MKDIR: + arg_mkdir = true; + break; + + case 'q': + arg_quiet = true; + break; + + case ARG_VERIFY: + arg_verify = import_verify_from_string(optarg); + if (arg_verify < 0) { + log_error("Failed to parse --verify= setting: %s", optarg); + return -EINVAL; + } + break; + + case ARG_FORCE: + arg_force = true; + break; + + case ARG_FORMAT: + if (!STR_IN_SET(optarg, "uncompressed", "xz", "gzip", "bzip2")) { + log_error("Unknown format: %s", optarg); + return -EINVAL; + } + + arg_format = optarg; + break; + + case ARG_UID: + arg_uid = optarg; + break; + + case 'E': + if (!env_assignment_is_valid(optarg)) { + log_error("Environment assignment invalid: %s", optarg); + return -EINVAL; + } + + r = strv_extend(&arg_setenv, optarg); + if (r < 0) + return log_oom(); + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + } + + return 1; +} + +static int machinectl_main(int argc, char *argv[], sd_bus *bus) { + + static const Verb verbs[] = { + { "help", VERB_ANY, VERB_ANY, 0, help }, + { "list", VERB_ANY, 1, VERB_DEFAULT, list_machines }, + { "list-images", VERB_ANY, 1, 0, list_images }, + { "status", 2, VERB_ANY, 0, show_machine }, + { "image-status", VERB_ANY, VERB_ANY, 0, show_image }, + { "show", VERB_ANY, VERB_ANY, 0, show_machine }, + { "show-image", VERB_ANY, VERB_ANY, 0, show_image }, + { "terminate", 2, VERB_ANY, 0, terminate_machine }, + { "reboot", 2, VERB_ANY, 0, reboot_machine }, + { "poweroff", 2, VERB_ANY, 0, poweroff_machine }, + { "kill", 2, VERB_ANY, 0, kill_machine }, + { "login", VERB_ANY, 2, 0, login_machine }, + { "shell", VERB_ANY, VERB_ANY, 0, shell_machine }, + { "bind", 3, 4, 0, bind_mount }, + { "copy-to", 3, 4, 0, copy_files }, + { "copy-from", 3, 4, 0, copy_files }, + { "remove", 2, VERB_ANY, 0, remove_image }, + { "rename", 3, 3, 0, rename_image }, + { "clone", 3, 3, 0, clone_image }, + { "read-only", 2, 3, 0, read_only_image }, + { "start", 2, VERB_ANY, 0, start_machine }, + { "enable", 2, VERB_ANY, 0, enable_machine }, + { "disable", 2, VERB_ANY, 0, enable_machine }, + { "import-tar", 2, 3, 0, import_tar }, + { "import-raw", 2, 3, 0, import_raw }, + { "export-tar", 2, 3, 0, export_tar }, + { "export-raw", 2, 3, 0, export_raw }, + { "pull-tar", 2, 3, 0, pull_tar }, + { "pull-raw", 2, 3, 0, pull_raw }, + { "list-transfers", VERB_ANY, 1, 0, list_transfers }, + { "cancel-transfer", 2, VERB_ANY, 0, cancel_transfer }, + { "set-limit", 2, 3, 0, set_limit }, + { "clean", VERB_ANY, 1, 0, clean_images }, + {} + }; + + return dispatch_verb(argc, argv, verbs, bus); +} + +int main(int argc, char*argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + setlocale(LC_ALL, ""); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = bus_connect_transport(arg_transport, arg_host, false, &bus); + if (r < 0) { + log_error_errno(r, "Failed to create bus connection: %m"); + goto finish; + } + + sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); + + r = machinectl_main(argc, argv, bus); + +finish: + pager_close(); + polkit_agent_close(); + + strv_free(arg_property); + strv_free(arg_setenv); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-machine/nss-mymachines/Makefile b/src/grp-machine/nss-mymachines/Makefile new file mode 100644 index 0000000000..c7c0d76907 --- /dev/null +++ b/src/grp-machine/nss-mymachines/Makefile @@ -0,0 +1,46 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + + +libnss_mymachines_la_SOURCES = \ + src/nss-mymachines/nss-mymachines.sym \ + src/nss-mymachines/nss-mymachines.c + +libnss_mymachines_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -module \ + -export-dynamic \ + -avoid-version \ + -shared \ + -shrext .so.2 \ + -Wl,--version-script=$(srcdir)/nss-mymachines.sym + +libnss_mymachines_la_LIBADD = \ + libsystemd-internal.la + +lib_LTLIBRARIES += \ + libnss_mymachines.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-machine/nss-mymachines/nss-mymachines.c b/src/grp-machine/nss-mymachines/nss-mymachines.c new file mode 100644 index 0000000000..da09035fe7 --- /dev/null +++ b/src/grp-machine/nss-mymachines/nss-mymachines.c @@ -0,0 +1,738 @@ +/*** + 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 + +#include +#include + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "hostname-util.h" +#include "in-addr-util.h" +#include "macro.h" +#include "nss-util.h" +#include "signal-util.h" +#include "string-util.h" +#include "user-util.h" +#include "util.h" + +NSS_GETHOSTBYNAME_PROTOTYPES(mymachines); +NSS_GETPW_PROTOTYPES(mymachines); +NSS_GETGR_PROTOTYPES(mymachines); + +#define HOST_UID_LIMIT ((uid_t) UINT32_C(0x10000)) +#define HOST_GID_LIMIT ((gid_t) UINT32_C(0x10000)) + +static int count_addresses(sd_bus_message *m, int af, unsigned *ret) { + unsigned c = 0; + int r; + + assert(m); + assert(ret); + + while ((r = sd_bus_message_enter_container(m, 'r', "iay")) > 0) { + int family; + + r = sd_bus_message_read(m, "i", &family); + if (r < 0) + return r; + + r = sd_bus_message_skip(m, "ay"); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + if (af != AF_UNSPEC && family != af) + continue; + + c++; + } + if (r < 0) + return r; + + r = sd_bus_message_rewind(m, false); + if (r < 0) + return r; + + *ret = c; + return 0; +} + +enum nss_status _nss_mymachines_gethostbyname4_r( + const char *name, + struct gaih_addrtuple **pat, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp) { + + struct gaih_addrtuple *r_tuple, *r_tuple_first = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ int *ifindices = NULL; + _cleanup_free_ char *class = NULL; + size_t l, ms, idx; + unsigned i = 0, c = 0; + char *r_name; + int n_ifindices, r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + assert(name); + assert(pat); + assert(buffer); + assert(errnop); + assert(h_errnop); + + r = sd_machine_get_class(name, &class); + if (r < 0) + goto fail; + if (!streq(class, "container")) { + r = -ENOTTY; + goto fail; + } + + n_ifindices = sd_machine_get_ifindices(name, &ifindices); + if (n_ifindices < 0) { + r = n_ifindices; + goto fail; + } + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachineAddresses", + NULL, + &reply, + "s", name); + if (r < 0) + goto fail; + + r = sd_bus_message_enter_container(reply, 'a', "(iay)"); + if (r < 0) + goto fail; + + r = count_addresses(reply, AF_UNSPEC, &c); + if (r < 0) + goto fail; + + if (c <= 0) { + *errnop = ESRCH; + *h_errnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; + } + + l = strlen(name); + ms = ALIGN(l+1) + ALIGN(sizeof(struct gaih_addrtuple)) * c; + if (buflen < ms) { + *errnop = ENOMEM; + *h_errnop = TRY_AGAIN; + return NSS_STATUS_TRYAGAIN; + } + + /* First, append name */ + r_name = buffer; + memcpy(r_name, name, l+1); + idx = ALIGN(l+1); + + /* Second, append addresses */ + r_tuple_first = (struct gaih_addrtuple*) (buffer + idx); + while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) { + int family; + const void *a; + size_t sz; + + r = sd_bus_message_read(reply, "i", &family); + if (r < 0) + goto fail; + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + goto fail; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + goto fail; + + if (!IN_SET(family, AF_INET, AF_INET6)) { + r = -EAFNOSUPPORT; + goto fail; + } + + if (sz != FAMILY_ADDRESS_SIZE(family)) { + r = -EINVAL; + goto fail; + } + + r_tuple = (struct gaih_addrtuple*) (buffer + idx); + r_tuple->next = i == c-1 ? NULL : (struct gaih_addrtuple*) ((char*) r_tuple + ALIGN(sizeof(struct gaih_addrtuple))); + r_tuple->name = r_name; + r_tuple->family = family; + r_tuple->scopeid = n_ifindices == 1 ? ifindices[0] : 0; + memcpy(r_tuple->addr, a, sz); + + idx += ALIGN(sizeof(struct gaih_addrtuple)); + i++; + } + + assert(i == c); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + goto fail; + + assert(idx == ms); + + if (*pat) + **pat = *r_tuple_first; + else + *pat = r_tuple_first; + + if (ttlp) + *ttlp = 0; + + /* Explicitly reset all error variables */ + *errnop = 0; + *h_errnop = NETDB_SUCCESS; + h_errno = 0; + + return NSS_STATUS_SUCCESS; + +fail: + *errnop = -r; + *h_errnop = NO_DATA; + return NSS_STATUS_UNAVAIL; +} + +enum nss_status _nss_mymachines_gethostbyname3_r( + const char *name, + int af, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp, + char **canonp) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ char *class = NULL; + unsigned c = 0, i = 0; + char *r_name, *r_aliases, *r_addr, *r_addr_list; + size_t l, idx, ms, alen; + int r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + assert(name); + assert(result); + assert(buffer); + assert(errnop); + assert(h_errnop); + + if (af == AF_UNSPEC) + af = AF_INET; + + if (af != AF_INET && af != AF_INET6) { + r = -EAFNOSUPPORT; + goto fail; + } + + r = sd_machine_get_class(name, &class); + if (r < 0) + goto fail; + if (!streq(class, "container")) { + r = -ENOTTY; + goto fail; + } + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachineAddresses", + NULL, + &reply, + "s", name); + if (r < 0) + goto fail; + + r = sd_bus_message_enter_container(reply, 'a', "(iay)"); + if (r < 0) + goto fail; + + r = count_addresses(reply, af, &c); + if (r < 0) + goto fail; + + if (c <= 0) { + *errnop = ENOENT; + *h_errnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; + } + + alen = FAMILY_ADDRESS_SIZE(af); + l = strlen(name); + + ms = ALIGN(l+1) + c * ALIGN(alen) + (c+2) * sizeof(char*); + + if (buflen < ms) { + *errnop = ENOMEM; + *h_errnop = NO_RECOVERY; + return NSS_STATUS_TRYAGAIN; + } + + /* First, append name */ + r_name = buffer; + memcpy(r_name, name, l+1); + idx = ALIGN(l+1); + + /* Second, create aliases array */ + r_aliases = buffer + idx; + ((char**) r_aliases)[0] = NULL; + idx += sizeof(char*); + + /* Third, append addresses */ + r_addr = buffer + idx; + while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) { + int family; + const void *a; + size_t sz; + + r = sd_bus_message_read(reply, "i", &family); + if (r < 0) + goto fail; + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + goto fail; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + goto fail; + + if (family != af) + continue; + + if (sz != alen) { + r = -EINVAL; + goto fail; + } + + memcpy(r_addr + i*ALIGN(alen), a, alen); + i++; + } + + assert(i == c); + idx += c * ALIGN(alen); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + goto fail; + + /* Third, append address pointer array */ + r_addr_list = buffer + idx; + for (i = 0; i < c; i++) + ((char**) r_addr_list)[i] = r_addr + i*ALIGN(alen); + + ((char**) r_addr_list)[i] = NULL; + idx += (c+1) * sizeof(char*); + + assert(idx == ms); + + result->h_name = r_name; + result->h_aliases = (char**) r_aliases; + result->h_addrtype = af; + result->h_length = alen; + result->h_addr_list = (char**) r_addr_list; + + if (ttlp) + *ttlp = 0; + + if (canonp) + *canonp = r_name; + + /* Explicitly reset all error variables */ + *errnop = 0; + *h_errnop = NETDB_SUCCESS; + h_errno = 0; + + return NSS_STATUS_SUCCESS; + +fail: + *errnop = -r; + *h_errnop = NO_DATA; + return NSS_STATUS_UNAVAIL; +} + +NSS_GETHOSTBYNAME_FALLBACKS(mymachines); + +enum nss_status _nss_mymachines_getpwnam_r( + const char *name, + struct passwd *pwd, + char *buffer, size_t buflen, + int *errnop) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + const char *p, *e, *machine; + uint32_t mapped; + uid_t uid; + size_t l; + int r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + assert(name); + assert(pwd); + + p = startswith(name, "vu-"); + if (!p) + goto not_found; + + e = strrchr(p, '-'); + if (!e || e == p) + goto not_found; + + if (e - p > HOST_NAME_MAX - 1) /* -1 for the last dash */ + goto not_found; + + r = parse_uid(e + 1, &uid); + if (r < 0) + goto not_found; + + machine = strndupa(p, e - p); + if (!machine_name_is_valid(machine)) + goto not_found; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "MapFromMachineUser", + &error, + &reply, + "su", + machine, (uint32_t) uid); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_USER_MAPPING)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "u", &mapped); + if (r < 0) + goto fail; + + /* Refuse to work if the mapped address is in the host UID range, or if there was no mapping at all. */ + if (mapped < HOST_UID_LIMIT || mapped == uid) + goto not_found; + + l = strlen(name); + if (buflen < l+1) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + memcpy(buffer, name, l+1); + + pwd->pw_name = buffer; + pwd->pw_uid = mapped; + pwd->pw_gid = 65534; /* nobody */ + pwd->pw_gecos = buffer; + pwd->pw_passwd = (char*) "*"; /* locked */ + pwd->pw_dir = (char*) "/"; + pwd->pw_shell = (char*) "/sbin/nologin"; + + *errnop = 0; + return NSS_STATUS_SUCCESS; + +not_found: + *errnop = 0; + return NSS_STATUS_NOTFOUND; + +fail: + *errnop = -r; + return NSS_STATUS_UNAVAIL; +} + +enum nss_status _nss_mymachines_getpwuid_r( + uid_t uid, + struct passwd *pwd, + char *buffer, size_t buflen, + int *errnop) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + const char *machine, *object; + uint32_t mapped; + int r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + if (!uid_is_valid(uid)) { + r = -EINVAL; + goto fail; + } + + /* We consider all uids < 65536 host uids */ + if (uid < HOST_UID_LIMIT) + goto not_found; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "MapToMachineUser", + &error, + &reply, + "u", + (uint32_t) uid); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_USER_MAPPING)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "sou", &machine, &object, &mapped); + if (r < 0) + goto fail; + + if (mapped == uid) + goto not_found; + + if (snprintf(buffer, buflen, "vu-%s-" UID_FMT, machine, (uid_t) mapped) >= (int) buflen) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + pwd->pw_name = buffer; + pwd->pw_uid = uid; + pwd->pw_gid = 65534; /* nobody */ + pwd->pw_gecos = buffer; + pwd->pw_passwd = (char*) "*"; /* locked */ + pwd->pw_dir = (char*) "/"; + pwd->pw_shell = (char*) "/sbin/nologin"; + + *errnop = 0; + return NSS_STATUS_SUCCESS; + +not_found: + *errnop = 0; + return NSS_STATUS_NOTFOUND; + +fail: + *errnop = -r; + return NSS_STATUS_UNAVAIL; +} + +enum nss_status _nss_mymachines_getgrnam_r( + const char *name, + struct group *gr, + char *buffer, size_t buflen, + int *errnop) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + const char *p, *e, *machine; + uint32_t mapped; + uid_t gid; + size_t l; + int r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + assert(name); + assert(gr); + + p = startswith(name, "vg-"); + if (!p) + goto not_found; + + e = strrchr(p, '-'); + if (!e || e == p) + goto not_found; + + if (e - p > HOST_NAME_MAX - 1) /* -1 for the last dash */ + goto not_found; + + r = parse_gid(e + 1, &gid); + if (r < 0) + goto not_found; + + machine = strndupa(p, e - p); + if (!machine_name_is_valid(machine)) + goto not_found; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "MapFromMachineGroup", + &error, + &reply, + "su", + machine, (uint32_t) gid); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_GROUP_MAPPING)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "u", &mapped); + if (r < 0) + goto fail; + + if (mapped < HOST_GID_LIMIT || mapped == gid) + goto not_found; + + l = sizeof(char*) + strlen(name) + 1; + if (buflen < l) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + memzero(buffer, sizeof(char*)); + strcpy(buffer + sizeof(char*), name); + + gr->gr_name = buffer + sizeof(char*); + gr->gr_gid = gid; + gr->gr_passwd = (char*) "*"; /* locked */ + gr->gr_mem = (char**) buffer; + + *errnop = 0; + return NSS_STATUS_SUCCESS; + +not_found: + *errnop = 0; + return NSS_STATUS_NOTFOUND; + +fail: + *errnop = -r; + return NSS_STATUS_UNAVAIL; +} + +enum nss_status _nss_mymachines_getgrgid_r( + gid_t gid, + struct group *gr, + char *buffer, size_t buflen, + int *errnop) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + const char *machine, *object; + uint32_t mapped; + int r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + if (!gid_is_valid(gid)) { + r = -EINVAL; + goto fail; + } + + /* We consider all gids < 65536 host gids */ + if (gid < HOST_GID_LIMIT) + goto not_found; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "MapToMachineGroup", + &error, + &reply, + "u", + (uint32_t) gid); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_GROUP_MAPPING)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "sou", &machine, &object, &mapped); + if (r < 0) + goto fail; + + if (mapped == gid) + goto not_found; + + if (buflen < sizeof(char*) + 1) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + memzero(buffer, sizeof(char*)); + if (snprintf(buffer + sizeof(char*), buflen - sizeof(char*), "vg-%s-" GID_FMT, machine, (gid_t) mapped) >= (int) buflen) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + gr->gr_name = buffer + sizeof(char*); + gr->gr_gid = gid; + gr->gr_passwd = (char*) "*"; /* locked */ + gr->gr_mem = (char**) buffer; + + *errnop = 0; + return NSS_STATUS_SUCCESS; + +not_found: + *errnop = 0; + return NSS_STATUS_NOTFOUND; + +fail: + *errnop = -r; + return NSS_STATUS_UNAVAIL; +} diff --git a/src/grp-machine/nss-mymachines/nss-mymachines.sym b/src/grp-machine/nss-mymachines/nss-mymachines.sym new file mode 100644 index 0000000000..0728ac3ba7 --- /dev/null +++ b/src/grp-machine/nss-mymachines/nss-mymachines.sym @@ -0,0 +1,21 @@ +/*** + This file is part of systemd. + + 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. +***/ + +{ +global: + _nss_mymachines_gethostbyname_r; + _nss_mymachines_gethostbyname2_r; + _nss_mymachines_gethostbyname3_r; + _nss_mymachines_gethostbyname4_r; + _nss_mymachines_getpwnam_r; + _nss_mymachines_getpwuid_r; + _nss_mymachines_getgrnam_r; + _nss_mymachines_getgrgid_r; +local: *; +}; diff --git a/src/grp-machine/systemd-machined/Makefile b/src/grp-machine/systemd-machined/Makefile new file mode 100644 index 0000000000..59540c18c4 --- /dev/null +++ b/src/grp-machine/systemd-machined/Makefile @@ -0,0 +1,67 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_machined_SOURCES = \ + src/machine/machined.c \ + src/machine/machined.h + +systemd_machined_LDADD = \ + libmachine-core.la + +libexec_PROGRAMS += \ + systemd-machined + +nodist_systemunit_DATA += \ + units/systemd-machined.service + +dist_systemunit_DATA += \ + units/machine.slice + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.machine1.busname + +dist_dbussystemservice_DATA += \ + src/machine/org.freedesktop.machine1.service + +dist_dbuspolicy_DATA += \ + src/machine/org.freedesktop.machine1.conf + +polkitpolicy_files += \ + src/machine/org.freedesktop.machine1.policy + +SYSTEM_UNIT_ALIASES += \ + systemd-machined.service dbus-org.freedesktop.machine1.service + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.machine1.busname + + +polkitpolicy_in_files += \ + src/machine/org.freedesktop.machine1.policy.in + +EXTRA_DIST += \ + units/systemd-machined.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-machine/systemd-machined/machined.c b/src/grp-machine/systemd-machined/machined.c new file mode 100644 index 0000000000..151f0983ec --- /dev/null +++ b/src/grp-machine/systemd-machined/machined.c @@ -0,0 +1,415 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 +#include + +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "hostname-util.h" +#include "label.h" +#include "machine-image.h" +#include "machined.h" +#include "signal-util.h" + +Manager *manager_new(void) { + Manager *m; + int r; + + m = new0(Manager, 1); + if (!m) + return NULL; + + m->machines = hashmap_new(&string_hash_ops); + m->machine_units = hashmap_new(&string_hash_ops); + m->machine_leaders = hashmap_new(NULL); + + if (!m->machines || !m->machine_units || !m->machine_leaders) { + manager_free(m); + return NULL; + } + + r = sd_event_default(&m->event); + if (r < 0) { + manager_free(m); + return NULL; + } + + sd_event_set_watchdog(m->event, true); + + return m; +} + +void manager_free(Manager *m) { + Machine *machine; + Image *i; + + assert(m); + + while (m->operations) + operation_free(m->operations); + + assert(m->n_operations == 0); + + while ((machine = hashmap_first(m->machines))) + machine_free(machine); + + hashmap_free(m->machines); + hashmap_free(m->machine_units); + hashmap_free(m->machine_leaders); + + while ((i = hashmap_steal_first(m->image_cache))) + image_unref(i); + + hashmap_free(m->image_cache); + + sd_event_source_unref(m->image_cache_defer_event); + + bus_verify_polkit_async_registry_free(m->polkit_registry); + + sd_bus_unref(m->bus); + sd_event_unref(m->event); + + free(m); +} + +static int manager_add_host_machine(Manager *m) { + _cleanup_free_ char *rd = NULL, *unit = NULL; + sd_id128_t mid; + Machine *t; + int r; + + if (m->host_machine) + return 0; + + r = sd_id128_get_machine(&mid); + if (r < 0) + return log_error_errno(r, "Failed to get machine ID: %m"); + + rd = strdup("/"); + if (!rd) + return log_oom(); + + unit = strdup("-.slice"); + if (!unit) + return log_oom(); + + t = machine_new(m, MACHINE_HOST, ".host"); + if (!t) + return log_oom(); + + t->leader = 1; + t->id = mid; + + t->root_directory = rd; + t->unit = unit; + rd = unit = NULL; + + dual_timestamp_from_boottime_or_monotonic(&t->timestamp, 0); + + m->host_machine = t; + + return 0; +} + +int manager_enumerate_machines(Manager *m) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r = 0; + + assert(m); + + r = manager_add_host_machine(m); + if (r < 0) + return r; + + /* Read in machine data stored on disk */ + d = opendir("/run/systemd/machines"); + if (!d) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open /run/systemd/machines: %m"); + } + + FOREACH_DIRENT(de, d, return -errno) { + struct Machine *machine; + int k; + + if (!dirent_is_file(de)) + continue; + + /* Ignore symlinks that map the unit name to the machine */ + if (startswith(de->d_name, "unit:")) + continue; + + if (!machine_name_is_valid(de->d_name)) + continue; + + k = manager_add_machine(m, de->d_name, &machine); + if (k < 0) { + r = log_error_errno(k, "Failed to add machine by file name %s: %m", de->d_name); + continue; + } + + machine_add_to_gc_queue(machine); + + k = machine_load(machine); + if (k < 0) + r = k; + } + + return r; +} + +static int manager_connect_bus(Manager *m) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(m); + assert(!m->bus); + + r = sd_bus_default_system(&m->bus); + if (r < 0) + return log_error_errno(r, "Failed to connect to system bus: %m"); + + r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/machine1", "org.freedesktop.machine1.Manager", manager_vtable, m); + if (r < 0) + return log_error_errno(r, "Failed to add manager object vtable: %m"); + + r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/machine1/machine", "org.freedesktop.machine1.Machine", machine_vtable, machine_object_find, m); + if (r < 0) + return log_error_errno(r, "Failed to add machine object vtable: %m"); + + r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/machine1/machine", machine_node_enumerator, m); + if (r < 0) + return log_error_errno(r, "Failed to add machine enumerator: %m"); + + r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/machine1/image", "org.freedesktop.machine1.Image", image_vtable, image_object_find, m); + if (r < 0) + return log_error_errno(r, "Failed to add image object vtable: %m"); + + r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/machine1/image", image_node_enumerator, m); + if (r < 0) + return log_error_errno(r, "Failed to add image enumerator: %m"); + + r = sd_bus_add_match(m->bus, + NULL, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='JobRemoved'," + "path='/org/freedesktop/systemd1'", + match_job_removed, + m); + if (r < 0) + return log_error_errno(r, "Failed to add match for JobRemoved: %m"); + + r = sd_bus_add_match(m->bus, + NULL, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='UnitRemoved'," + "path='/org/freedesktop/systemd1'", + match_unit_removed, + m); + if (r < 0) + return log_error_errno(r, "Failed to add match for UnitRemoved: %m"); + + r = sd_bus_add_match(m->bus, + NULL, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.DBus.Properties'," + "member='PropertiesChanged'," + "arg0='org.freedesktop.systemd1.Unit'", + match_properties_changed, + m); + if (r < 0) + return log_error_errno(r, "Failed to add match for PropertiesChanged: %m"); + + r = sd_bus_add_match(m->bus, + NULL, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='Reloading'," + "path='/org/freedesktop/systemd1'", + match_reloading, + m); + if (r < 0) + return log_error_errno(r, "Failed to add match for Reloading: %m"); + + r = sd_bus_call_method( + m->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Subscribe", + &error, + NULL, NULL); + if (r < 0) { + log_error("Failed to enable subscription: %s", bus_error_message(&error, r)); + return r; + } + + r = sd_bus_request_name(m->bus, "org.freedesktop.machine1", 0); + if (r < 0) + return log_error_errno(r, "Failed to register name: %m"); + + r = sd_bus_attach_event(m->bus, m->event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + return 0; +} + +void manager_gc(Manager *m, bool drop_not_started) { + Machine *machine; + + assert(m); + + while ((machine = m->machine_gc_queue)) { + LIST_REMOVE(gc_queue, m->machine_gc_queue, machine); + machine->in_gc_queue = false; + + /* First, if we are not closing yet, initiate stopping */ + if (!machine_check_gc(machine, drop_not_started) && + machine_get_state(machine) != MACHINE_CLOSING) + machine_stop(machine); + + /* Now, the stop stop probably made this referenced + * again, but if it didn't, then it's time to let it + * go entirely. */ + if (!machine_check_gc(machine, drop_not_started)) { + machine_finalize(machine); + machine_free(machine); + } + } +} + +int manager_startup(Manager *m) { + Machine *machine; + Iterator i; + int r; + + assert(m); + + /* Connect to the bus */ + r = manager_connect_bus(m); + if (r < 0) + return r; + + /* Deserialize state */ + manager_enumerate_machines(m); + + /* Remove stale objects before we start them */ + manager_gc(m, false); + + /* And start everything */ + HASHMAP_FOREACH(machine, m->machines, i) + machine_start(machine, NULL, NULL); + + return 0; +} + +static bool check_idle(void *userdata) { + Manager *m = userdata; + + if (m->operations) + return false; + + manager_gc(m, true); + + return hashmap_isempty(m->machines); +} + +int manager_run(Manager *m) { + assert(m); + + return bus_event_loop_with_idle( + m->event, + m->bus, + "org.freedesktop.machine1", + DEFAULT_EXIT_USEC, + check_idle, m); +} + +int main(int argc, char *argv[]) { + Manager *m = NULL; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_set_facility(LOG_AUTH); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc != 1) { + log_error("This program takes no arguments."); + r = -EINVAL; + goto finish; + } + + /* Always create the directories people can create inotify + * watches in. Note that some applications might check for the + * existence of /run/systemd/machines/ to determine whether + * machined is available, so please always make sure this + * check stays in. */ + mkdir_label("/run/systemd/machines", 0755); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); + + m = manager_new(); + if (!m) { + r = log_oom(); + goto finish; + } + + r = manager_startup(m); + if (r < 0) { + log_error_errno(r, "Failed to fully start up daemon: %m"); + goto finish; + } + + log_debug("systemd-machined running as pid "PID_FMT, getpid()); + + sd_notify(false, + "READY=1\n" + "STATUS=Processing requests..."); + + r = manager_run(m); + + log_debug("systemd-machined stopped as pid "PID_FMT, getpid()); + +finish: + manager_free(m); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-resolve/nss-resolve/nss-resolve.c b/src/grp-resolve/nss-resolve/nss-resolve.c new file mode 100644 index 0000000000..4c2101d856 --- /dev/null +++ b/src/grp-resolve/nss-resolve/nss-resolve.c @@ -0,0 +1,675 @@ +/*** + 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 +#include +#include +#include +#include + +#include + +#include "bus-common-errors.h" +#include "in-addr-util.h" +#include "macro.h" +#include "nss-util.h" +#include "string-util.h" +#include "util.h" +#include "signal-util.h" + +NSS_GETHOSTBYNAME_PROTOTYPES(resolve); +NSS_GETHOSTBYADDR_PROTOTYPES(resolve); + +#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) + +typedef void (*voidfunc_t)(void); + +static voidfunc_t find_fallback(const char *module, const char *symbol) { + void *dl; + + /* Try to find a fallback NSS module symbol */ + + dl = dlopen(module, RTLD_LAZY|RTLD_NODELETE); + if (!dl) + return NULL; + + return dlsym(dl, symbol); +} + +static bool bus_error_shall_fallback(sd_bus_error *e) { + return sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN) || + sd_bus_error_has_name(e, SD_BUS_ERROR_NAME_HAS_NO_OWNER) || + sd_bus_error_has_name(e, SD_BUS_ERROR_NO_REPLY) || + sd_bus_error_has_name(e, SD_BUS_ERROR_ACCESS_DENIED); +} + +static int count_addresses(sd_bus_message *m, int af, const char **canonical) { + int c = 0, r; + + assert(m); + assert(canonical); + + r = sd_bus_message_enter_container(m, 'a', "(iiay)"); + if (r < 0) + return r; + + while ((r = sd_bus_message_enter_container(m, 'r', "iiay")) > 0) { + int family, ifindex; + + assert_cc(sizeof(int32_t) == sizeof(int)); + + r = sd_bus_message_read(m, "ii", &ifindex, &family); + if (r < 0) + return r; + + r = sd_bus_message_skip(m, "ay"); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + if (af != AF_UNSPEC && family != af) + continue; + + c++; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + r = sd_bus_message_read(m, "s", canonical); + if (r < 0) + return r; + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + return c; +} + +enum nss_status _nss_resolve_gethostbyname4_r( + const char *name, + struct gaih_addrtuple **pat, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + struct gaih_addrtuple *r_tuple, *r_tuple_first = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + const char *canonical = NULL; + size_t l, ms, idx; + char *r_name; + int c, r, i = 0; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + assert(name); + assert(pat); + assert(buffer); + assert(errnop); + assert(h_errnop); + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fallback; + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveHostname"); + if (r < 0) + goto fail; + + r = sd_bus_message_set_auto_start(req, false); + if (r < 0) + goto fail; + + r = sd_bus_message_append(req, "isit", 0, name, AF_UNSPEC, (uint64_t) 0); + if (r < 0) + goto fail; + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) { + if (sd_bus_error_has_name(&error, _BUS_ERROR_DNS "NXDOMAIN")) { + *errnop = ESRCH; + *h_errnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; + } + + if (bus_error_shall_fallback(&error)) + goto fallback; + + goto fail; + } + + c = count_addresses(reply, AF_UNSPEC, &canonical); + if (c < 0) { + r = c; + goto fail; + } + if (c == 0) { + *errnop = ESRCH; + *h_errnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; + } + + if (isempty(canonical)) + canonical = name; + + l = strlen(canonical); + ms = ALIGN(l+1) + ALIGN(sizeof(struct gaih_addrtuple)) * c; + if (buflen < ms) { + *errnop = ENOMEM; + *h_errnop = TRY_AGAIN; + return NSS_STATUS_TRYAGAIN; + } + + /* First, append name */ + r_name = buffer; + memcpy(r_name, canonical, l+1); + idx = ALIGN(l+1); + + /* Second, append addresses */ + r_tuple_first = (struct gaih_addrtuple*) (buffer + idx); + + r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); + if (r < 0) + goto fail; + + while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { + int family, ifindex; + const void *a; + size_t sz; + + assert_cc(sizeof(int32_t) == sizeof(int)); + + r = sd_bus_message_read(reply, "ii", &ifindex, &family); + if (r < 0) + goto fail; + + if (ifindex < 0) { + r = -EINVAL; + goto fail; + } + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + goto fail; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + goto fail; + + if (!IN_SET(family, AF_INET, AF_INET6)) + continue; + + if (sz != FAMILY_ADDRESS_SIZE(family)) { + r = -EINVAL; + goto fail; + } + + r_tuple = (struct gaih_addrtuple*) (buffer + idx); + r_tuple->next = i == c-1 ? NULL : (struct gaih_addrtuple*) ((char*) r_tuple + ALIGN(sizeof(struct gaih_addrtuple))); + r_tuple->name = r_name; + r_tuple->family = family; + r_tuple->scopeid = ifindex; + memcpy(r_tuple->addr, a, sz); + + idx += ALIGN(sizeof(struct gaih_addrtuple)); + i++; + } + if (r < 0) + goto fail; + + assert(i == c); + assert(idx == ms); + + if (*pat) + **pat = *r_tuple_first; + else + *pat = r_tuple_first; + + if (ttlp) + *ttlp = 0; + + /* Explicitly reset all error variables */ + *errnop = 0; + *h_errnop = NETDB_SUCCESS; + h_errno = 0; + + return NSS_STATUS_SUCCESS; + +fallback: + { + _nss_gethostbyname4_r_t fallback; + + fallback = (_nss_gethostbyname4_r_t) + find_fallback("libnss_dns.so.2", "_nss_dns_gethostbyname4_r"); + + if (fallback) + return fallback(name, pat, buffer, buflen, errnop, h_errnop, ttlp); + } + +fail: + *errnop = -r; + *h_errnop = NO_RECOVERY; + return NSS_STATUS_UNAVAIL; +} + +enum nss_status _nss_resolve_gethostbyname3_r( + const char *name, + int af, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp, + char **canonp) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char *r_name, *r_aliases, *r_addr, *r_addr_list; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + size_t l, idx, ms, alen; + const char *canonical; + int c, r, i = 0; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + assert(name); + assert(result); + assert(buffer); + assert(errnop); + assert(h_errnop); + + if (af == AF_UNSPEC) + af = AF_INET; + + if (af != AF_INET && af != AF_INET6) { + r = -EAFNOSUPPORT; + goto fail; + } + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fallback; + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveHostname"); + if (r < 0) + goto fail; + + r = sd_bus_message_set_auto_start(req, false); + if (r < 0) + goto fail; + + r = sd_bus_message_append(req, "isit", 0, name, af, (uint64_t) 0); + if (r < 0) + goto fail; + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) { + if (sd_bus_error_has_name(&error, _BUS_ERROR_DNS "NXDOMAIN")) { + *errnop = ESRCH; + *h_errnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; + } + + if (bus_error_shall_fallback(&error)) + goto fallback; + + goto fail; + } + + c = count_addresses(reply, af, &canonical); + if (c < 0) { + r = c; + goto fail; + } + if (c == 0) { + *errnop = ESRCH; + *h_errnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; + } + + if (isempty(canonical)) + canonical = name; + + alen = FAMILY_ADDRESS_SIZE(af); + l = strlen(canonical); + + ms = ALIGN(l+1) + c * ALIGN(alen) + (c+2) * sizeof(char*); + + if (buflen < ms) { + *errnop = ENOMEM; + *h_errnop = TRY_AGAIN; + return NSS_STATUS_TRYAGAIN; + } + + /* First, append name */ + r_name = buffer; + memcpy(r_name, canonical, l+1); + idx = ALIGN(l+1); + + /* Second, create empty aliases array */ + r_aliases = buffer + idx; + ((char**) r_aliases)[0] = NULL; + idx += sizeof(char*); + + /* Third, append addresses */ + r_addr = buffer + idx; + + r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); + if (r < 0) + goto fail; + + while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { + int ifindex, family; + const void *a; + size_t sz; + + r = sd_bus_message_read(reply, "ii", &ifindex, &family); + if (r < 0) + goto fail; + + if (ifindex < 0) { + r = -EINVAL; + goto fail; + } + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + goto fail; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + goto fail; + + if (family != af) + continue; + + if (sz != alen) { + r = -EINVAL; + goto fail; + } + + memcpy(r_addr + i*ALIGN(alen), a, alen); + i++; + } + if (r < 0) + goto fail; + + assert(i == c); + idx += c * ALIGN(alen); + + /* Fourth, append address pointer array */ + r_addr_list = buffer + idx; + for (i = 0; i < c; i++) + ((char**) r_addr_list)[i] = r_addr + i*ALIGN(alen); + + ((char**) r_addr_list)[i] = NULL; + idx += (c+1) * sizeof(char*); + + assert(idx == ms); + + result->h_name = r_name; + result->h_aliases = (char**) r_aliases; + result->h_addrtype = af; + result->h_length = alen; + result->h_addr_list = (char**) r_addr_list; + + /* Explicitly reset all error variables */ + *errnop = 0; + *h_errnop = NETDB_SUCCESS; + h_errno = 0; + + if (ttlp) + *ttlp = 0; + + if (canonp) + *canonp = r_name; + + return NSS_STATUS_SUCCESS; + +fallback: + { + _nss_gethostbyname3_r_t fallback; + + fallback = (_nss_gethostbyname3_r_t) + find_fallback("libnss_dns.so.2", "_nss_dns_gethostbyname3_r"); + if (fallback) + return fallback(name, af, result, buffer, buflen, errnop, h_errnop, ttlp, canonp); + } + +fail: + *errnop = -r; + *h_errnop = NO_RECOVERY; + return NSS_STATUS_UNAVAIL; +} + +enum nss_status _nss_resolve_gethostbyaddr2_r( + const void* addr, socklen_t len, + int af, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char *r_name, *r_aliases, *r_addr, *r_addr_list; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + unsigned c = 0, i = 0; + size_t ms = 0, idx; + const char *n; + int r, ifindex; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + assert(addr); + assert(result); + assert(buffer); + assert(errnop); + assert(h_errnop); + + if (!IN_SET(af, AF_INET, AF_INET6)) { + *errnop = EAFNOSUPPORT; + *h_errnop = NO_DATA; + return NSS_STATUS_UNAVAIL; + } + + if (len != FAMILY_ADDRESS_SIZE(af)) { + *errnop = EINVAL; + *h_errnop = NO_RECOVERY; + return NSS_STATUS_UNAVAIL; + } + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fallback; + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveAddress"); + if (r < 0) + goto fail; + + r = sd_bus_message_set_auto_start(req, false); + if (r < 0) + goto fail; + + r = sd_bus_message_append(req, "ii", 0, af); + if (r < 0) + goto fail; + + r = sd_bus_message_append_array(req, 'y', addr, len); + if (r < 0) + goto fail; + + r = sd_bus_message_append(req, "t", (uint64_t) 0); + if (r < 0) + goto fail; + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) { + if (sd_bus_error_has_name(&error, _BUS_ERROR_DNS "NXDOMAIN")) { + *errnop = ESRCH; + *h_errnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; + } + + if (bus_error_shall_fallback(&error)) + goto fallback; + + + *errnop = -r; + *h_errnop = NO_RECOVERY; + return NSS_STATUS_UNAVAIL; + } + + r = sd_bus_message_enter_container(reply, 'a', "(is)"); + if (r < 0) + goto fail; + + while ((r = sd_bus_message_read(reply, "(is)", &ifindex, &n)) > 0) { + + if (ifindex < 0) { + r = -EINVAL; + goto fail; + } + + c++; + ms += ALIGN(strlen(n) + 1); + } + if (r < 0) + goto fail; + + r = sd_bus_message_rewind(reply, false); + if (r < 0) + return r; + + if (c <= 0) { + *errnop = ESRCH; + *h_errnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; + } + + ms += ALIGN(len) + /* the address */ + 2 * sizeof(char*) + /* pointers to the address, plus trailing NULL */ + c * sizeof(char*); /* pointers to aliases, plus trailing NULL */ + + if (buflen < ms) { + *errnop = ENOMEM; + *h_errnop = TRY_AGAIN; + return NSS_STATUS_TRYAGAIN; + } + + /* First, place address */ + r_addr = buffer; + memcpy(r_addr, addr, len); + idx = ALIGN(len); + + /* Second, place address list */ + r_addr_list = buffer + idx; + ((char**) r_addr_list)[0] = r_addr; + ((char**) r_addr_list)[1] = NULL; + idx += sizeof(char*) * 2; + + /* Third, reserve space for the aliases array */ + r_aliases = buffer + idx; + idx += sizeof(char*) * c; + + /* Fourth, place aliases */ + i = 0; + r_name = buffer + idx; + while ((r = sd_bus_message_read(reply, "(is)", &ifindex, &n)) > 0) { + char *p; + size_t l; + + l = strlen(n); + p = buffer + idx; + memcpy(p, n, l+1); + + if (i > 0) + ((char**) r_aliases)[i-1] = p; + i++; + + idx += ALIGN(l+1); + } + if (r < 0) + goto fail; + + ((char**) r_aliases)[c-1] = NULL; + assert(idx == ms); + + result->h_name = r_name; + result->h_aliases = (char**) r_aliases; + result->h_addrtype = af; + result->h_length = len; + result->h_addr_list = (char**) r_addr_list; + + if (ttlp) + *ttlp = 0; + + /* Explicitly reset all error variables */ + *errnop = 0; + *h_errnop = NETDB_SUCCESS; + h_errno = 0; + + return NSS_STATUS_SUCCESS; + +fallback: + { + _nss_gethostbyaddr2_r_t fallback; + + fallback = (_nss_gethostbyaddr2_r_t) + find_fallback("libnss_dns.so.2", "_nss_dns_gethostbyaddr2_r"); + + if (fallback) + return fallback(addr, len, af, result, buffer, buflen, errnop, h_errnop, ttlp); + } + +fail: + *errnop = -r; + *h_errnop = NO_RECOVERY; + return NSS_STATUS_UNAVAIL; +} + +NSS_GETHOSTBYNAME_FALLBACKS(resolve); +NSS_GETHOSTBYADDR_FALLBACKS(resolve); diff --git a/src/grp-resolve/nss-resolve/nss-resolve.sym b/src/grp-resolve/nss-resolve/nss-resolve.sym new file mode 100644 index 0000000000..df8dff2a20 --- /dev/null +++ b/src/grp-resolve/nss-resolve/nss-resolve.sym @@ -0,0 +1,19 @@ +/*** + This file is part of systemd. + + 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. +***/ + +{ +global: + _nss_resolve_gethostbyname_r; + _nss_resolve_gethostbyname2_r; + _nss_resolve_gethostbyname3_r; + _nss_resolve_gethostbyname4_r; + _nss_resolve_gethostbyaddr_r; + _nss_resolve_gethostbyaddr2_r; +local: *; +}; diff --git a/src/grp-resolve/systemd-resolved/.gitignore b/src/grp-resolve/systemd-resolved/.gitignore new file mode 100644 index 0000000000..f0835923b7 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/.gitignore @@ -0,0 +1,6 @@ +/resolved-gperf.c +/resolved.conf +/dns_type-from-name.gperf +/dns_type-from-name.h +/dns_type-list.txt +/dns_type-to-name.h diff --git a/src/grp-resolve/systemd-resolved/Makefile b/src/grp-resolve/systemd-resolved/Makefile new file mode 100644 index 0000000000..f025608eab --- /dev/null +++ b/src/grp-resolve/systemd-resolved/Makefile @@ -0,0 +1,247 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + + +$(outdir)/dns_type-list.txt: src/resolve/dns-type.h + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)$(SED) -n -r 's/.* DNS_TYPE_(\w+).*/\1/p' <$< >$@ + +$(outdir)/dns_type-to-name.h: src/resolve/dns_type-list.txt + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)$(AWK) 'BEGIN{ print "const char *dns_type_to_string(int type) {\n\tswitch(type) {" } {printf " case DNS_TYPE_%s: return ", $$1; sub(/_/, "-"); printf "\"%s\";\n", $$1 } END{ print " default: return NULL;\n\t}\n}\n" }' <$< >$@ + +$(outdir)/dns_type-from-name.gperf: src/resolve/dns_type-list.txt + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)$(AWK) 'BEGIN{ print "struct dns_type_name { const char* name; int id; };"; print "%null-strings"; print "%%";} { s=$$1; sub(/_/, "-", s); printf "%s, ", $$s; printf "DNS_TYPE_%s\n", $$1 }' <$< >$@ + +ifneq ($(ENABLE_RESOLVED),) + +basic_dns_sources = \ + src/resolve/resolved-dns-dnssec.c \ + src/resolve/resolved-dns-dnssec.h \ + src/resolve/resolved-dns-packet.c \ + src/resolve/resolved-dns-packet.h \ + src/resolve/resolved-dns-rr.c \ + src/resolve/resolved-dns-rr.h \ + src/resolve/resolved-dns-answer.c \ + src/resolve/resolved-dns-answer.h \ + src/resolve/resolved-dns-question.c \ + src/resolve/resolved-dns-question.h \ + src/resolve/dns-type.c \ + src/resolve/dns-type.h + +systemd_resolved_SOURCES = \ + src/resolve/resolved.c \ + src/resolve/resolved-manager.c \ + src/resolve/resolved-manager.h \ + src/resolve/resolved-conf.c \ + src/resolve/resolved-conf.h \ + src/resolve/resolved-resolv-conf.c \ + src/resolve/resolved-resolv-conf.h \ + src/resolve/resolved-bus.c \ + src/resolve/resolved-bus.h \ + src/resolve/resolved-link.h \ + src/resolve/resolved-link.c \ + src/resolve/resolved-link-bus.c \ + src/resolve/resolved-link-bus.h \ + src/resolve/resolved-llmnr.h \ + src/resolve/resolved-llmnr.c \ + src/resolve/resolved-mdns.h \ + src/resolve/resolved-mdns.c \ + src/resolve/resolved-def.h \ + $(basic_dns_sources) \ + src/resolve/resolved-dns-query.h \ + src/resolve/resolved-dns-query.c \ + src/resolve/resolved-dns-synthesize.h \ + src/resolve/resolved-dns-synthesize.c \ + src/resolve/resolved-dns-transaction.h \ + src/resolve/resolved-dns-transaction.c \ + src/resolve/resolved-dns-scope.h \ + src/resolve/resolved-dns-scope.c \ + src/resolve/resolved-dns-server.h \ + src/resolve/resolved-dns-server.c \ + src/resolve/resolved-dns-search-domain.h \ + src/resolve/resolved-dns-search-domain.c \ + src/resolve/resolved-dns-cache.h \ + src/resolve/resolved-dns-cache.c \ + src/resolve/resolved-dns-zone.h \ + src/resolve/resolved-dns-zone.c \ + src/resolve/resolved-dns-stream.h \ + src/resolve/resolved-dns-stream.c \ + src/resolve/resolved-dns-trust-anchor.h \ + src/resolve/resolved-dns-trust-anchor.c \ + src/resolve/resolved-etc-hosts.h \ + src/resolve/resolved-etc-hosts.c \ + src/shared/gcrypt-util.c \ + src/shared/gcrypt-util.h + +nodist_systemd_resolved_SOURCES = \ + src/resolve/dns_type-from-name.h \ + src/resolve/dns_type-to-name.h \ + src/resolve/resolved-gperf.c + +systemd_resolved_LDADD = \ + libsystemd-network.la \ + libshared.la + +libexec_PROGRAMS += \ + systemd-resolved + +nodist_systemunit_DATA += \ + units/systemd-resolved.service + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.resolve1.busname + +dist_dbuspolicy_DATA += \ + src/resolve/org.freedesktop.resolve1.conf + +dist_dbussystemservice_DATA += \ + src/resolve/org.freedesktop.resolve1.service + +SYSTEM_UNIT_ALIASES += \ + systemd-resolved.service dbus-org.freedesktop.resolve1.service + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.resolve1.busname + +GENERAL_ALIASES += \ + $(systemunitdir)/systemd-resolved.service $(pkgsysconfdir)/system/multi-user.target.wants/systemd-resolved.service + +nodist_pkgsysconf_DATA += \ + src/resolve/resolved.conf + +libnss_resolve_la_SOURCES = \ + src/nss-resolve/nss-resolve.sym \ + src/nss-resolve/nss-resolve.c + +libnss_resolve_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -module \ + -export-dynamic \ + -avoid-version \ + -shared \ + -shrext .so.2 \ + -Wl,--version-script=$(srcdir)/nss-resolve.sym + +libnss_resolve_la_LIBADD = \ + libsystemd-internal.la \ + -ldl + +lib_LTLIBRARIES += \ + libnss_resolve.la + +systemd_resolve_SOURCES = \ + src/resolve/resolve-tool.c \ + $(basic_dns_sources) \ + src/shared/gcrypt-util.c \ + src/shared/gcrypt-util.h + +nodist_systemd_resolve_SOURCES = \ + src/resolve/dns_type-from-name.h \ + src/resolve/dns_type-to-name.h + +systemd_resolve_LDADD = \ + libshared.la + +bin_PROGRAMS += \ + systemd-resolve + +dist_bashcompletion_data += \ + shell-completion/bash/systemd-resolve + +dist_zshcompletion_data += \ + shell-completion/zsh/_systemd-resolve + +tests += \ + test-dns-packet \ + test-resolve-tables \ + test-dnssec + +manual_tests += \ + test-dnssec-complex + +test_resolve_tables_SOURCES = \ + src/resolve/test-resolve-tables.c \ + src/resolve/dns_type-from-name.h \ + src/resolve/dns_type-to-name.h \ + $(basic_dns_sources) \ + src/shared/test-tables.h + +test_resolve_tables_LDADD = \ + libshared.la + +test_dns_packet_SOURCES = \ + src/resolve/test-dns-packet.c \ + $(basic_dns_sources) + +test_dns_packet_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -DRESOLVE_TEST_DIR=\"$(abs_top_srcdir)/src/resolve/test-data\" + +test_dns_packet_LDADD = \ + libshared.la + +EXTRA_DIST += \ + src/resolve/test-data/_openpgpkey.fedoraproject.org.pkts \ + src/resolve/test-data/fedoraproject.org.pkts \ + src/resolve/test-data/gandi.net.pkts \ + src/resolve/test-data/google.com.pkts \ + src/resolve/test-data/root.pkts \ + src/resolve/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts \ + src/resolve/test-data/teamits.com.pkts \ + src/resolve/test-data/zbyszek@fedoraproject.org.pkts \ + src/resolve/test-data/_443._tcp.fedoraproject.org.pkts \ + src/resolve/test-data/kyhwana.org.pkts \ + src/resolve/test-data/fake-caa.pkts + +test_dnssec_SOURCES = \ + src/resolve/test-dnssec.c \ + $(basic_dns_sources) + +test_dnssec_LDADD = \ + libshared.la + +test_dnssec_complex_SOURCES = \ + src/resolve/test-dnssec-complex.c \ + src/resolve/dns-type.c \ + src/resolve/dns-type.h + +test_dnssec_complex_LDADD = \ + libshared.la + +endif # ENABLE_RESOLVED + +gperf_txt_sources += \ + src/resolve/dns_type-list.txt + +gperf_gperf_sources += \ + src/resolve/resolved-gperf.gperf + +EXTRA_DIST += \ + units/systemd-resolved.service.m4.in \ + src/resolve/resolved.conf.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-resolve/systemd-resolved/RFCs b/src/grp-resolve/systemd-resolved/RFCs new file mode 100644 index 0000000000..09c85f9518 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/RFCs @@ -0,0 +1,59 @@ +Y = Comprehensively Implemented, to the point appropriate for resolved +D = Comprehensively Implemented, by a dependency of resolved +! = Missing and something we might want to implement +~ = Needs no explicit support or doesn't apply +? = Is this relevant today? + = We are working on this + +Y https://tools.ietf.org/html/rfc1034 → DOMAIN NAMES - CONCEPTS AND FACILITIES +Y https://tools.ietf.org/html/rfc1035 → DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION +? https://tools.ietf.org/html/rfc1101 → DNS Encoding of Network Names and Other Types +Y https://tools.ietf.org/html/rfc1123 → Requirements for Internet Hosts — Application and Support +~ https://tools.ietf.org/html/rfc1464 → Using the Domain Name System To Store Arbitrary String Attributes +Y https://tools.ietf.org/html/rfc1536 → Common DNS Implementation Errors and Suggested Fixes +Y https://tools.ietf.org/html/rfc1876 → A Means for Expressing Location Information in the Domain Name System +Y https://tools.ietf.org/html/rfc2181 → Clarifications to the DNS Specification +Y https://tools.ietf.org/html/rfc2308 → Negative Caching of DNS Queries (DNS NCACHE) +Y https://tools.ietf.org/html/rfc2782 → A DNS RR for specifying the location of services (DNS SRV) +D https://tools.ietf.org/html/rfc3492 → Punycode: A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA) +Y https://tools.ietf.org/html/rfc3596 → DNS Extensions to Support IP Version 6 +Y https://tools.ietf.org/html/rfc3597 → Handling of Unknown DNS Resource Record (RR) Types +Y https://tools.ietf.org/html/rfc4033 → DNS Security Introduction and Requirements +Y https://tools.ietf.org/html/rfc4034 → Resource Records for the DNS Security Extensions +Y https://tools.ietf.org/html/rfc4035 → Protocol Modifications for the DNS Security Extensions +! https://tools.ietf.org/html/rfc4183 → A Suggested Scheme for DNS Resolution of Networks and Gateways +Y https://tools.ietf.org/html/rfc4255 → Using DNS to Securely Publish Secure Shell (SSH) Key Fingerprints +Y https://tools.ietf.org/html/rfc4343 → Domain Name System (DNS) Case Insensitivity Clarification +~ https://tools.ietf.org/html/rfc4470 → Minimally Covering NSEC Records and DNSSEC On-line Signing +Y https://tools.ietf.org/html/rfc4501 → Domain Name System Uniform Resource Identifiers +Y https://tools.ietf.org/html/rfc4509 → Use of SHA-256 in DNSSEC Delegation Signer (DS) Resource Records (RRs) +~ https://tools.ietf.org/html/rfc4592 → The Role of Wildcards in the Domain Name System +~ https://tools.ietf.org/html/rfc4697 → Observed DNS Resolution Misbehavior +Y https://tools.ietf.org/html/rfc4795 → Link-Local Multicast Name Resolution (LLMNR) +Y https://tools.ietf.org/html/rfc5011 → Automated Updates of DNS Security (DNSSEC) Trust Anchors +Y https://tools.ietf.org/html/rfc5155 → DNS Security (DNSSEC) Hashed Authenticated Denial of Existence +Y https://tools.ietf.org/html/rfc5452 → Measures for Making DNS More Resilient against Forged Answers +Y https://tools.ietf.org/html/rfc5702 → Use of SHA-2 Algorithms with RSA in DNSKEY and RRSIG Resource Records for DNSSEC +Y https://tools.ietf.org/html/rfc5890 → Internationalized Domain Names for Applications (IDNA): Definitions and Document Framework +Y https://tools.ietf.org/html/rfc5891 → Internationalized Domain Names in Applications (IDNA): Protocol +Y https://tools.ietf.org/html/rfc5966 → DNS Transport over TCP - Implementation Requirements +Y https://tools.ietf.org/html/rfc6303 → Locally Served DNS Zones +Y https://tools.ietf.org/html/rfc6604 → xNAME RCODE and Status Bits Clarification +Y https://tools.ietf.org/html/rfc6605 → Elliptic Curve Digital Signature Algorithm (DSA) for DNSSEC + https://tools.ietf.org/html/rfc6672 → DNAME Redirection in the DNS +! https://tools.ietf.org/html/rfc6731 → Improved Recursive DNS Server Selection for Multi-Interfaced Nodes +Y https://tools.ietf.org/html/rfc6761 → Special-Use Domain Names + https://tools.ietf.org/html/rfc6762 → Multicast DNS + https://tools.ietf.org/html/rfc6763 → DNS-Based Service Discovery +~ https://tools.ietf.org/html/rfc6781 → DNSSEC Operational Practices, Version 2 +Y https://tools.ietf.org/html/rfc6840 → Clarifications and Implementation Notes for DNS Security (DNSSEC) +Y https://tools.ietf.org/html/rfc6891 → Extension Mechanisms for DNS (EDNS(0)) +Y https://tools.ietf.org/html/rfc6944 → Applicability Statement: DNS Security (DNSSEC) DNSKEY Algorithm Implementation Status +Y https://tools.ietf.org/html/rfc6975 → Signaling Cryptographic Algorithm Understanding in DNS Security Extensions (DNSSEC) +Y https://tools.ietf.org/html/rfc7129 → Authenticated Denial of Existence in the DNS +Y https://tools.ietf.org/html/rfc7646 → Definition and Use of DNSSEC Negative Trust Anchors +~ https://tools.ietf.org/html/rfc7719 → DNS Terminology + +Also relevant: + + https://www.iab.org/documents/correspondence-reports-documents/2013-2/iab-statement-dotless-domains-considered-harmful/ diff --git a/src/grp-resolve/systemd-resolved/dns-type.c b/src/grp-resolve/systemd-resolved/dns-type.c new file mode 100644 index 0000000000..78d9d5733f --- /dev/null +++ b/src/grp-resolve/systemd-resolved/dns-type.c @@ -0,0 +1,323 @@ +/*** + This file is part of systemd. + + Copyright 2014 Zbigniew Jędrzejewski-Szmek + + 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 "dns-type.h" +#include "parse-util.h" +#include "string-util.h" + +typedef const struct { + uint16_t type; + const char *name; +} dns_type; + +static const struct dns_type_name * +lookup_dns_type (register const char *str, register unsigned int len); + +#include "dns_type-from-name.h" +#include "dns_type-to-name.h" + +int dns_type_from_string(const char *s) { + const struct dns_type_name *sc; + + assert(s); + + sc = lookup_dns_type(s, strlen(s)); + if (sc) + return sc->id; + + s = startswith_no_case(s, "TYPE"); + if (s) { + unsigned x; + + if (safe_atou(s, &x) >= 0 && + x <= UINT16_MAX) + return (int) x; + } + + return _DNS_TYPE_INVALID; +} + +bool dns_type_is_pseudo(uint16_t type) { + + /* Checks whether the specified type is a "pseudo-type". What + * a "pseudo-type" precisely is, is defined only very weakly, + * but apparently entails all RR types that are not actually + * stored as RRs on the server and should hence also not be + * cached. We use this list primarily to validate NSEC type + * bitfields, and to verify what to cache. */ + + return IN_SET(type, + 0, /* A Pseudo RR type, according to RFC 2931 */ + DNS_TYPE_ANY, + DNS_TYPE_AXFR, + DNS_TYPE_IXFR, + DNS_TYPE_OPT, + DNS_TYPE_TSIG, + DNS_TYPE_TKEY + ); +} + +bool dns_class_is_pseudo(uint16_t class) { + return class == DNS_TYPE_ANY; +} + +bool dns_type_is_valid_query(uint16_t type) { + + /* The types valid as questions in packets */ + + return !IN_SET(type, + 0, + DNS_TYPE_OPT, + DNS_TYPE_TSIG, + DNS_TYPE_TKEY, + + /* RRSIG are technically valid as questions, but we refuse doing explicit queries for them, as + * they aren't really payload, but signatures for payload, and cannot be validated on their + * own. After all they are the signatures, and have no signatures of their own validating + * them. */ + DNS_TYPE_RRSIG); +} + +bool dns_type_is_valid_rr(uint16_t type) { + + /* The types valid as RR in packets (but not necessarily + * stored on servers). */ + + return !IN_SET(type, + DNS_TYPE_ANY, + DNS_TYPE_AXFR, + DNS_TYPE_IXFR); +} + +bool dns_class_is_valid_rr(uint16_t class) { + return class != DNS_CLASS_ANY; +} + +bool dns_type_may_redirect(uint16_t type) { + /* The following record types should never be redirected using + * CNAME/DNAME RRs. See + * . */ + + if (dns_type_is_pseudo(type)) + return false; + + return !IN_SET(type, + DNS_TYPE_CNAME, + DNS_TYPE_DNAME, + DNS_TYPE_NSEC3, + DNS_TYPE_NSEC, + DNS_TYPE_RRSIG, + DNS_TYPE_NXT, + DNS_TYPE_SIG, + DNS_TYPE_KEY); +} + +bool dns_type_may_wildcard(uint16_t type) { + + /* The following records may not be expanded from wildcard RRsets */ + + if (dns_type_is_pseudo(type)) + return false; + + return !IN_SET(type, + DNS_TYPE_NSEC3, + DNS_TYPE_SOA, + + /* Prohibited by https://tools.ietf.org/html/rfc4592#section-4.4 */ + DNS_TYPE_DNAME); +} + +bool dns_type_apex_only(uint16_t type) { + + /* Returns true for all RR types that may only appear signed in a zone apex */ + + return IN_SET(type, + DNS_TYPE_SOA, + DNS_TYPE_NS, /* this one can appear elsewhere, too, but not signed */ + DNS_TYPE_DNSKEY, + DNS_TYPE_NSEC3PARAM); +} + +bool dns_type_is_dnssec(uint16_t type) { + return IN_SET(type, + DNS_TYPE_DS, + DNS_TYPE_DNSKEY, + DNS_TYPE_RRSIG, + DNS_TYPE_NSEC, + DNS_TYPE_NSEC3, + DNS_TYPE_NSEC3PARAM); +} + +bool dns_type_is_obsolete(uint16_t type) { + return IN_SET(type, + /* Obsoleted by RFC 973 */ + DNS_TYPE_MD, + DNS_TYPE_MF, + DNS_TYPE_MAILA, + + /* Kinda obsoleted by RFC 2505 */ + DNS_TYPE_MB, + DNS_TYPE_MG, + DNS_TYPE_MR, + DNS_TYPE_MINFO, + DNS_TYPE_MAILB, + + /* RFC1127 kinda obsoleted this by recommending against its use */ + DNS_TYPE_WKS, + + /* Declared historical by RFC 6563 */ + DNS_TYPE_A6, + + /* Obsoleted by DNSSEC-bis */ + DNS_TYPE_NXT, + + /* RFC 1035 removed support for concepts that needed this from RFC 883 */ + DNS_TYPE_NULL); +} + +bool dns_type_needs_authentication(uint16_t type) { + + /* Returns true for all (non-obsolete) RR types where records are not useful if they aren't + * authenticated. I.e. everything that contains crypto keys. */ + + return IN_SET(type, + DNS_TYPE_CERT, + DNS_TYPE_SSHFP, + DNS_TYPE_IPSECKEY, + DNS_TYPE_DS, + DNS_TYPE_DNSKEY, + DNS_TYPE_TLSA, + DNS_TYPE_CDNSKEY, + DNS_TYPE_OPENPGPKEY, + DNS_TYPE_CAA); +} + +int dns_type_to_af(uint16_t t) { + switch (t) { + + case DNS_TYPE_A: + return AF_INET; + + case DNS_TYPE_AAAA: + return AF_INET6; + + case DNS_TYPE_ANY: + return AF_UNSPEC; + + default: + return -EINVAL; + } +} + +const char *dns_class_to_string(uint16_t class) { + + switch (class) { + + case DNS_CLASS_IN: + return "IN"; + + case DNS_CLASS_ANY: + return "ANY"; + } + + return NULL; +} + +int dns_class_from_string(const char *s) { + + if (!s) + return _DNS_CLASS_INVALID; + + if (strcaseeq(s, "IN")) + return DNS_CLASS_IN; + else if (strcaseeq(s, "ANY")) + return DNS_CLASS_ANY; + + return _DNS_CLASS_INVALID; +} + +const char* tlsa_cert_usage_to_string(uint8_t cert_usage) { + + switch (cert_usage) { + + case 0: + return "CA constraint"; + + case 1: + return "Service certificate constraint"; + + case 2: + return "Trust anchor assertion"; + + case 3: + return "Domain-issued certificate"; + + case 4 ... 254: + return "Unassigned"; + + case 255: + return "Private use"; + } + + return NULL; /* clang cannot count that we covered everything */ +} + +const char* tlsa_selector_to_string(uint8_t selector) { + switch (selector) { + + case 0: + return "Full Certificate"; + + case 1: + return "SubjectPublicKeyInfo"; + + case 2 ... 254: + return "Unassigned"; + + case 255: + return "Private use"; + } + + return NULL; +} + +const char* tlsa_matching_type_to_string(uint8_t selector) { + + switch (selector) { + + case 0: + return "No hash used"; + + case 1: + return "SHA-256"; + + case 2: + return "SHA-512"; + + case 3 ... 254: + return "Unassigned"; + + case 255: + return "Private use"; + } + + return NULL; +} diff --git a/src/grp-resolve/systemd-resolved/dns-type.h b/src/grp-resolve/systemd-resolved/dns-type.h new file mode 100644 index 0000000000..7b79d29d7e --- /dev/null +++ b/src/grp-resolve/systemd-resolved/dns-type.h @@ -0,0 +1,161 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Zbigniew Jędrzejewski-Szmek + + 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 "macro.h" + +/* DNS record types, taken from + * http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml. + */ +enum { + /* Normal records */ + DNS_TYPE_A = 0x01, + DNS_TYPE_NS, + DNS_TYPE_MD, + DNS_TYPE_MF, + DNS_TYPE_CNAME, + DNS_TYPE_SOA, + DNS_TYPE_MB, + DNS_TYPE_MG, + DNS_TYPE_MR, + DNS_TYPE_NULL, + DNS_TYPE_WKS, + DNS_TYPE_PTR, + DNS_TYPE_HINFO, + DNS_TYPE_MINFO, + DNS_TYPE_MX, + DNS_TYPE_TXT, + DNS_TYPE_RP, + DNS_TYPE_AFSDB, + DNS_TYPE_X25, + DNS_TYPE_ISDN, + DNS_TYPE_RT, + DNS_TYPE_NSAP, + DNS_TYPE_NSAP_PTR, + DNS_TYPE_SIG, + DNS_TYPE_KEY, + DNS_TYPE_PX, + DNS_TYPE_GPOS, + DNS_TYPE_AAAA, + DNS_TYPE_LOC, + DNS_TYPE_NXT, + DNS_TYPE_EID, + DNS_TYPE_NIMLOC, + DNS_TYPE_SRV, + DNS_TYPE_ATMA, + DNS_TYPE_NAPTR, + DNS_TYPE_KX, + DNS_TYPE_CERT, + DNS_TYPE_A6, + DNS_TYPE_DNAME, + DNS_TYPE_SINK, + DNS_TYPE_OPT, /* EDNS0 option */ + DNS_TYPE_APL, + DNS_TYPE_DS, + DNS_TYPE_SSHFP, + DNS_TYPE_IPSECKEY, + DNS_TYPE_RRSIG, + DNS_TYPE_NSEC, + DNS_TYPE_DNSKEY, + DNS_TYPE_DHCID, + DNS_TYPE_NSEC3, + DNS_TYPE_NSEC3PARAM, + DNS_TYPE_TLSA, + + DNS_TYPE_HIP = 0x37, + DNS_TYPE_NINFO, + DNS_TYPE_RKEY, + DNS_TYPE_TALINK, + DNS_TYPE_CDS, + DNS_TYPE_CDNSKEY, + DNS_TYPE_OPENPGPKEY, + + DNS_TYPE_SPF = 0x63, + DNS_TYPE_NID, + DNS_TYPE_L32, + DNS_TYPE_L64, + DNS_TYPE_LP, + DNS_TYPE_EUI48, + DNS_TYPE_EUI64, + + DNS_TYPE_TKEY = 0xF9, + DNS_TYPE_TSIG, + DNS_TYPE_IXFR, + DNS_TYPE_AXFR, + DNS_TYPE_MAILB, + DNS_TYPE_MAILA, + DNS_TYPE_ANY, + DNS_TYPE_URI, + DNS_TYPE_CAA, + DNS_TYPE_TA = 0x8000, + DNS_TYPE_DLV, + + _DNS_TYPE_MAX, + _DNS_TYPE_INVALID = -1 +}; + +assert_cc(DNS_TYPE_SSHFP == 44); +assert_cc(DNS_TYPE_TLSA == 52); +assert_cc(DNS_TYPE_ANY == 255); + +/* DNS record classes, see RFC 1035 */ +enum { + DNS_CLASS_IN = 0x01, + DNS_CLASS_ANY = 0xFF, + + _DNS_CLASS_MAX, + _DNS_CLASS_INVALID = -1 +}; + +#define _DNS_CLASS_STRING_MAX (sizeof "CLASS" + DECIMAL_STR_MAX(uint16_t)) +#define _DNS_TYPE_STRING_MAX (sizeof "CLASS" + DECIMAL_STR_MAX(uint16_t)) + +bool dns_type_is_pseudo(uint16_t type); +bool dns_type_is_valid_query(uint16_t type); +bool dns_type_is_valid_rr(uint16_t type); +bool dns_type_may_redirect(uint16_t type); +bool dns_type_is_dnssec(uint16_t type); +bool dns_type_is_obsolete(uint16_t type); +bool dns_type_may_wildcard(uint16_t type); +bool dns_type_apex_only(uint16_t type); +bool dns_type_needs_authentication(uint16_t type); +int dns_type_to_af(uint16_t type); + +bool dns_class_is_pseudo(uint16_t class); +bool dns_class_is_valid_rr(uint16_t class); + +/* TYPE?? follows http://tools.ietf.org/html/rfc3597#section-5 */ +const char *dns_type_to_string(int type); +int dns_type_from_string(const char *s); + +const char *dns_class_to_string(uint16_t class); +int dns_class_from_string(const char *name); + +/* https://tools.ietf.org/html/draft-ietf-dane-protocol-23#section-7.2 */ +const char *tlsa_cert_usage_to_string(uint8_t cert_usage); + +/* https://tools.ietf.org/html/draft-ietf-dane-protocol-23#section-7.3 */ +const char *tlsa_selector_to_string(uint8_t selector); + +/* https://tools.ietf.org/html/draft-ietf-dane-protocol-23#section-7.4 */ +const char *tlsa_matching_type_to_string(uint8_t selector); + +/* https://tools.ietf.org/html/rfc6844#section-5.1 */ +#define CAA_FLAG_CRITICAL (1u << 7) diff --git a/src/grp-resolve/systemd-resolved/org.freedesktop.resolve1.conf b/src/grp-resolve/systemd-resolved/org.freedesktop.resolve1.conf new file mode 100644 index 0000000000..25b09774e5 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/org.freedesktop.resolve1.conf @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/grp-resolve/systemd-resolved/org.freedesktop.resolve1.service b/src/grp-resolve/systemd-resolved/org.freedesktop.resolve1.service new file mode 100644 index 0000000000..7ac5c323f0 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/org.freedesktop.resolve1.service @@ -0,0 +1,12 @@ +# This file is part of systemd. +# +# 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. + +[D-BUS Service] +Name=org.freedesktop.resolve1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.resolve1.service diff --git a/src/grp-resolve/systemd-resolved/resolve-tool.c b/src/grp-resolve/systemd-resolved/resolve-tool.c new file mode 100644 index 0000000000..fbf7b0e4f6 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolve-tool.c @@ -0,0 +1,1484 @@ +/*** + This file is part of systemd. + + Copyright 2014 Zbigniew Jędrzejewski-Szmek + + 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 + +#include + +#include "af-list.h" +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "escape.h" +#include "in-addr-util.h" +#include "gcrypt-util.h" +#include "parse-util.h" +#include "resolved-def.h" +#include "resolved-dns-packet.h" +#include "terminal-util.h" + +#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) + +static int arg_family = AF_UNSPEC; +static int arg_ifindex = 0; +static uint16_t arg_type = 0; +static uint16_t arg_class = 0; +static bool arg_legend = true; +static uint64_t arg_flags = 0; + +typedef enum ServiceFamily { + SERVICE_FAMILY_TCP, + SERVICE_FAMILY_UDP, + SERVICE_FAMILY_SCTP, + _SERVICE_FAMILY_INVALID = -1, +} ServiceFamily; +static ServiceFamily arg_service_family = SERVICE_FAMILY_TCP; + +typedef enum RawType { + RAW_NONE, + RAW_PAYLOAD, + RAW_PACKET, +} RawType; +static RawType arg_raw = RAW_NONE; + +static enum { + MODE_RESOLVE_HOST, + MODE_RESOLVE_RECORD, + MODE_RESOLVE_SERVICE, + MODE_RESOLVE_OPENPGP, + MODE_RESOLVE_TLSA, + MODE_STATISTICS, + MODE_RESET_STATISTICS, +} arg_mode = MODE_RESOLVE_HOST; + +static ServiceFamily service_family_from_string(const char *s) { + if (s == NULL || streq(s, "tcp")) + return SERVICE_FAMILY_TCP; + if (streq(s, "udp")) + return SERVICE_FAMILY_UDP; + if (streq(s, "sctp")) + return SERVICE_FAMILY_SCTP; + return _SERVICE_FAMILY_INVALID; +} + +static const char* service_family_to_string(ServiceFamily service) { + switch(service) { + case SERVICE_FAMILY_TCP: + return "_tcp"; + case SERVICE_FAMILY_UDP: + return "_udp"; + case SERVICE_FAMILY_SCTP: + return "_sctp"; + default: + assert_not_reached("invalid service"); + } +} + +static void print_source(uint64_t flags, usec_t rtt) { + char rtt_str[FORMAT_TIMESTAMP_MAX]; + + if (!arg_legend) + return; + + if (flags == 0) + return; + + fputs("\n-- Information acquired via", stdout); + + if (flags != 0) + printf(" protocol%s%s%s%s%s", + flags & SD_RESOLVED_DNS ? " DNS" :"", + flags & SD_RESOLVED_LLMNR_IPV4 ? " LLMNR/IPv4" : "", + flags & SD_RESOLVED_LLMNR_IPV6 ? " LLMNR/IPv6" : "", + flags & SD_RESOLVED_MDNS_IPV4 ? "mDNS/IPv4" : "", + flags & SD_RESOLVED_MDNS_IPV6 ? "mDNS/IPv6" : ""); + + assert_se(format_timespan(rtt_str, sizeof(rtt_str), rtt, 100)); + + printf(" in %s", rtt_str); + + fputc('.', stdout); + fputc('\n', stdout); + + printf("-- Data is authenticated: %s\n", yes_no(flags & SD_RESOLVED_AUTHENTICATED)); +} + +static int resolve_host(sd_bus *bus, const char *name) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *canonical = NULL; + char ifname[IF_NAMESIZE] = ""; + unsigned c = 0; + int r; + uint64_t flags; + usec_t ts; + + assert(name); + + if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) + return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); + + log_debug("Resolving %s (family %s, interface %s).", name, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveHostname"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "isit", arg_ifindex, name, arg_family, arg_flags); + if (r < 0) + return bus_log_create_error(r); + + ts = now(CLOCK_MONOTONIC); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) + return log_error_errno(r, "%s: resolve call failed: %s", name, bus_error_message(&error, r)); + + ts = now(CLOCK_MONOTONIC) - ts; + + r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { + _cleanup_free_ char *pretty = NULL; + int ifindex, family; + const void *a; + size_t sz; + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(reply, "ii", &ifindex, &family); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (!IN_SET(family, AF_INET, AF_INET6)) { + log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown"); + continue; + } + + if (sz != FAMILY_ADDRESS_SIZE(family)) { + log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown"); + return -EINVAL; + } + + ifname[0] = 0; + if (ifindex > 0 && !if_indextoname(ifindex, ifname)) + log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); + + r = in_addr_to_string(family, a, &pretty); + if (r < 0) + return log_error_errno(r, "Failed to print address for %s: %m", name); + + printf("%*s%s %s%s%s\n", + (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", + pretty, + isempty(ifname) ? "" : "%", ifname); + + c++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "st", &canonical, &flags); + if (r < 0) + return bus_log_parse_error(r); + + if (!streq(name, canonical)) + printf("%*s%s (%s)\n", + (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", + canonical); + + if (c == 0) { + log_error("%s: no addresses found", name); + return -ESRCH; + } + + print_source(flags, ts); + + return 0; +} + +static int resolve_address(sd_bus *bus, int family, const union in_addr_union *address, int ifindex) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *pretty = NULL; + char ifname[IF_NAMESIZE] = ""; + uint64_t flags; + unsigned c = 0; + usec_t ts; + int r; + + assert(bus); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(address); + + if (ifindex <= 0) + ifindex = arg_ifindex; + + r = in_addr_to_string(family, address, &pretty); + if (r < 0) + return log_oom(); + + if (ifindex > 0 && !if_indextoname(ifindex, ifname)) + return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); + + log_debug("Resolving %s%s%s.", pretty, isempty(ifname) ? "" : "%", ifname); + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveAddress"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "ii", ifindex, family); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_array(req, 'y', address, FAMILY_ADDRESS_SIZE(family)); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "t", arg_flags); + if (r < 0) + return bus_log_create_error(r); + + ts = now(CLOCK_MONOTONIC); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) { + log_error("%s: resolve call failed: %s", pretty, bus_error_message(&error, r)); + return r; + } + + ts = now(CLOCK_MONOTONIC) - ts; + + r = sd_bus_message_enter_container(reply, 'a', "(is)"); + if (r < 0) + return bus_log_create_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "is")) > 0) { + const char *n; + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(reply, "is", &ifindex, &n); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + + ifname[0] = 0; + if (ifindex > 0 && !if_indextoname(ifindex, ifname)) + log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); + + printf("%*s%*s%*s%s %s\n", + (int) strlen(pretty), c == 0 ? pretty : "", + isempty(ifname) ? 0 : 1, c > 0 || isempty(ifname) ? "" : "%", + (int) strlen(ifname), c == 0 ? ifname : "", + c == 0 ? ":" : " ", + n); + + c++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "t", &flags); + if (r < 0) + return bus_log_parse_error(r); + + if (c == 0) { + log_error("%s: no names found", pretty); + return -ESRCH; + } + + print_source(flags, ts); + + return 0; +} + +static int parse_address(const char *s, int *family, union in_addr_union *address, int *ifindex) { + const char *percent, *a; + int ifi = 0; + int r; + + percent = strchr(s, '%'); + if (percent) { + if (parse_ifindex(percent+1, &ifi) < 0) { + ifi = if_nametoindex(percent+1); + if (ifi <= 0) + return -EINVAL; + } + + a = strndupa(s, percent - s); + } else + a = s; + + r = in_addr_from_string_auto(a, family, address); + if (r < 0) + return r; + + *ifindex = ifi; + return 0; +} + +static int output_rr_packet(const void *d, size_t l, int ifindex) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + int r; + char ifname[IF_NAMESIZE] = ""; + + r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0); + if (r < 0) + return log_oom(); + + p->refuse_compression = true; + + r = dns_packet_append_blob(p, d, l, NULL); + if (r < 0) + return log_oom(); + + r = dns_packet_read_rr(p, &rr, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse RR: %m"); + + if (arg_raw == RAW_PAYLOAD) { + void *data; + ssize_t k; + + k = dns_resource_record_payload(rr, &data); + if (k < 0) + return log_error_errno(k, "Cannot dump RR: %m"); + fwrite(data, 1, k, stdout); + } else { + const char *s; + + s = dns_resource_record_to_string(rr); + if (!s) + return log_oom(); + + if (ifindex > 0 && !if_indextoname(ifindex, ifname)) + log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); + + printf("%s%s%s\n", s, isempty(ifname) ? "" : " # interface ", ifname); + } + + return 0; +} + +static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_t type) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char ifname[IF_NAMESIZE] = ""; + unsigned n = 0; + uint64_t flags; + int r; + usec_t ts; + bool needs_authentication = false; + + assert(name); + + if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) + return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); + + log_debug("Resolving %s %s %s (interface %s).", name, dns_class_to_string(class), dns_type_to_string(type), isempty(ifname) ? "*" : ifname); + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveRecord"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, class, type, arg_flags); + if (r < 0) + return bus_log_create_error(r); + + ts = now(CLOCK_MONOTONIC); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) { + log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r)); + return r; + } + + ts = now(CLOCK_MONOTONIC) - ts; + + r = sd_bus_message_enter_container(reply, 'a', "(iqqay)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "iqqay")) > 0) { + uint16_t c, t; + int ifindex; + const void *d; + size_t l; + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(reply, "iqq", &ifindex, &c, &t); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &d, &l); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (arg_raw == RAW_PACKET) { + uint64_t u64 = htole64(l); + + fwrite(&u64, sizeof(u64), 1, stdout); + fwrite(d, 1, l, stdout); + } else { + r = output_rr_packet(d, l, ifindex); + if (r < 0) + return r; + } + + if (dns_type_needs_authentication(t)) + needs_authentication = true; + + n++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "t", &flags); + if (r < 0) + return bus_log_parse_error(r); + + if (n == 0) { + log_error("%s: no records found", name); + return -ESRCH; + } + + print_source(flags, ts); + + if ((flags & SD_RESOLVED_AUTHENTICATED) == 0 && needs_authentication) { + fflush(stdout); + + fprintf(stderr, "\n%s" + "WARNING: The resources shown contain cryptographic key data which could not be\n" + " authenticated. It is not suitable to authenticate any communication.\n" + " This is usually indication that DNSSEC authentication was not enabled\n" + " or is not available for the selected protocol or DNS servers.%s\n", + ansi_highlight_red(), + ansi_normal()); + } + + return 0; +} + +static int resolve_rfc4501(sd_bus *bus, const char *name) { + uint16_t type = 0, class = 0; + const char *p, *q, *n; + int r; + + assert(bus); + assert(name); + assert(startswith(name, "dns:")); + + /* Parse RFC 4501 dns: URIs */ + + p = name + 4; + + if (p[0] == '/') { + const char *e; + + if (p[1] != '/') + goto invalid; + + e = strchr(p + 2, '/'); + if (!e) + goto invalid; + + if (e != p + 2) + log_warning("DNS authority specification not supported; ignoring specified authority."); + + p = e + 1; + } + + q = strchr(p, '?'); + if (q) { + n = strndupa(p, q - p); + q++; + + for (;;) { + const char *f; + + f = startswith_no_case(q, "class="); + if (f) { + _cleanup_free_ char *t = NULL; + const char *e; + + if (class != 0) { + log_error("DNS class specified twice."); + return -EINVAL; + } + + e = strchrnul(f, ';'); + t = strndup(f, e - f); + if (!t) + return log_oom(); + + r = dns_class_from_string(t); + if (r < 0) { + log_error("Unknown DNS class %s.", t); + return -EINVAL; + } + + class = r; + + if (*e == ';') { + q = e + 1; + continue; + } + + break; + } + + f = startswith_no_case(q, "type="); + if (f) { + _cleanup_free_ char *t = NULL; + const char *e; + + if (type != 0) { + log_error("DNS type specified twice."); + return -EINVAL; + } + + e = strchrnul(f, ';'); + t = strndup(f, e - f); + if (!t) + return log_oom(); + + r = dns_type_from_string(t); + if (r < 0) { + log_error("Unknown DNS type %s.", t); + return -EINVAL; + } + + type = r; + + if (*e == ';') { + q = e + 1; + continue; + } + + break; + } + + goto invalid; + } + } else + n = p; + + if (class == 0) + class = arg_class ?: DNS_CLASS_IN; + if (type == 0) + type = arg_type ?: DNS_TYPE_A; + + return resolve_record(bus, n, class, type); + +invalid: + log_error("Invalid DNS URI: %s", name); + return -EINVAL; +} + +static int resolve_service(sd_bus *bus, const char *name, const char *type, const char *domain) { + const char *canonical_name, *canonical_type, *canonical_domain; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char ifname[IF_NAMESIZE] = ""; + size_t indent, sz; + uint64_t flags; + const char *p; + unsigned c; + usec_t ts; + int r; + + assert(bus); + assert(domain); + + if (isempty(name)) + name = NULL; + if (isempty(type)) + type = NULL; + + if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) + return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); + + if (name) + log_debug("Resolving service \"%s\" of type %s in %s (family %s, interface %s).", name, type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); + else if (type) + log_debug("Resolving service type %s of %s (family %s, interface %s).", type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); + else + log_debug("Resolving service type %s (family %s, interface %s).", domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveService"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "isssit", arg_ifindex, name, type, domain, arg_family, arg_flags); + if (r < 0) + return bus_log_create_error(r); + + ts = now(CLOCK_MONOTONIC); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) + return log_error_errno(r, "Resolve call failed: %s", bus_error_message(&error, r)); + + ts = now(CLOCK_MONOTONIC) - ts; + + r = sd_bus_message_enter_container(reply, 'a', "(qqqsa(iiay)s)"); + if (r < 0) + return bus_log_parse_error(r); + + indent = + (name ? strlen(name) + 1 : 0) + + (type ? strlen(type) + 1 : 0) + + strlen(domain) + 2; + + c = 0; + while ((r = sd_bus_message_enter_container(reply, 'r', "qqqsa(iiay)s")) > 0) { + uint16_t priority, weight, port; + const char *hostname, *canonical; + + r = sd_bus_message_read(reply, "qqqs", &priority, &weight, &port, &hostname); + if (r < 0) + return bus_log_parse_error(r); + + if (name) + printf("%*s%s", (int) strlen(name), c == 0 ? name : "", c == 0 ? "/" : " "); + if (type) + printf("%*s%s", (int) strlen(type), c == 0 ? type : "", c == 0 ? "/" : " "); + + printf("%*s%s %s:%u [priority=%u, weight=%u]\n", + (int) strlen(domain), c == 0 ? domain : "", + c == 0 ? ":" : " ", + hostname, port, + priority, weight); + + r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { + _cleanup_free_ char *pretty = NULL; + int ifindex, family; + const void *a; + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(reply, "ii", &ifindex, &family); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (!IN_SET(family, AF_INET, AF_INET6)) { + log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown"); + continue; + } + + if (sz != FAMILY_ADDRESS_SIZE(family)) { + log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown"); + return -EINVAL; + } + + ifname[0] = 0; + if (ifindex > 0 && !if_indextoname(ifindex, ifname)) + log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); + + r = in_addr_to_string(family, a, &pretty); + if (r < 0) + return log_error_errno(r, "Failed to print address for %s: %m", name); + + printf("%*s%s%s%s\n", (int) indent, "", pretty, isempty(ifname) ? "" : "%s", ifname); + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "s", &canonical); + if (r < 0) + return bus_log_parse_error(r); + + if (!streq(hostname, canonical)) + printf("%*s(%s)\n", (int) indent, "", canonical); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + c++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_enter_container(reply, 'a', "ay"); + if (r < 0) + return bus_log_parse_error(r); + + c = 0; + while ((r = sd_bus_message_read_array(reply, 'y', (const void**) &p, &sz)) > 0) { + _cleanup_free_ char *escaped = NULL; + + escaped = cescape_length(p, sz); + if (!escaped) + return log_oom(); + + printf("%*s%s\n", (int) indent, "", escaped); + c++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "ssst", &canonical_name, &canonical_type, &canonical_domain, &flags); + if (r < 0) + return bus_log_parse_error(r); + + if (isempty(canonical_name)) + canonical_name = NULL; + if (isempty(canonical_type)) + canonical_type = NULL; + + if (!streq_ptr(name, canonical_name) || + !streq_ptr(type, canonical_type) || + !streq_ptr(domain, canonical_domain)) { + + printf("%*s(", (int) indent, ""); + + if (canonical_name) + printf("%s/", canonical_name); + if (canonical_type) + printf("%s/", canonical_type); + + printf("%s)\n", canonical_domain); + } + + print_source(flags, ts); + + return 0; +} + +static int resolve_openpgp(sd_bus *bus, const char *address) { + const char *domain, *full; + int r; + _cleanup_free_ char *hashed = NULL; + + assert(bus); + assert(address); + + domain = strrchr(address, '@'); + if (!domain) { + log_error("Address does not contain '@': \"%s\"", address); + return -EINVAL; + } else if (domain == address || domain[1] == '\0') { + log_error("Address starts or ends with '@': \"%s\"", address); + return -EINVAL; + } + domain++; + + r = string_hashsum_sha224(address, domain - 1 - address, &hashed); + if (r < 0) + return log_error_errno(r, "Hashing failed: %m"); + + full = strjoina(hashed, "._openpgpkey.", domain); + log_debug("Looking up \"%s\".", full); + + return resolve_record(bus, full, + arg_class ?: DNS_CLASS_IN, + arg_type ?: DNS_TYPE_OPENPGPKEY); +} + +static int resolve_tlsa(sd_bus *bus, const char *address) { + const char *port; + uint16_t port_num = 443; + _cleanup_free_ char *full = NULL; + int r; + + assert(bus); + assert(address); + + port = strrchr(address, ':'); + if (port) { + r = safe_atou16(port + 1, &port_num); + if (r < 0 || port_num == 0) + return log_error_errno(r, "Invalid port \"%s\".", port + 1); + + address = strndupa(address, port - address); + } + + r = asprintf(&full, "_%u.%s.%s", + port_num, + service_family_to_string(arg_service_family), + address); + if (r < 0) + return log_oom(); + + log_debug("Looking up \"%s\".", full); + + return resolve_record(bus, full, + arg_class ?: DNS_CLASS_IN, + arg_type ?: DNS_TYPE_TLSA); +} + +static int show_statistics(sd_bus *bus) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + uint64_t n_current_transactions, n_total_transactions, + cache_size, n_cache_hit, n_cache_miss, + n_dnssec_secure, n_dnssec_insecure, n_dnssec_bogus, n_dnssec_indeterminate; + int r, dnssec_supported; + + assert(bus); + + r = sd_bus_get_property_trivial(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "DNSSECSupported", + &error, + 'b', + &dnssec_supported); + if (r < 0) + return log_error_errno(r, "Failed to get DNSSEC supported state: %s", bus_error_message(&error, r)); + + printf("DNSSEC supported by current servers: %s%s%s\n\n", + ansi_highlight(), + yes_no(dnssec_supported), + ansi_normal()); + + r = sd_bus_get_property(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "TransactionStatistics", + &error, + &reply, + "(tt)"); + if (r < 0) + return log_error_errno(r, "Failed to get transaction statistics: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "(tt)", + &n_current_transactions, + &n_total_transactions); + if (r < 0) + return bus_log_parse_error(r); + + printf("%sTransactions%s\n" + "Current Transactions: %" PRIu64 "\n" + " Total Transactions: %" PRIu64 "\n", + ansi_highlight(), + ansi_normal(), + n_current_transactions, + n_total_transactions); + + reply = sd_bus_message_unref(reply); + + r = sd_bus_get_property(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "CacheStatistics", + &error, + &reply, + "(ttt)"); + if (r < 0) + return log_error_errno(r, "Failed to get cache statistics: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "(ttt)", + &cache_size, + &n_cache_hit, + &n_cache_miss); + if (r < 0) + return bus_log_parse_error(r); + + printf("\n%sCache%s\n" + " Current Cache Size: %" PRIu64 "\n" + " Cache Hits: %" PRIu64 "\n" + " Cache Misses: %" PRIu64 "\n", + ansi_highlight(), + ansi_normal(), + cache_size, + n_cache_hit, + n_cache_miss); + + reply = sd_bus_message_unref(reply); + + r = sd_bus_get_property(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "DNSSECStatistics", + &error, + &reply, + "(tttt)"); + if (r < 0) + return log_error_errno(r, "Failed to get DNSSEC statistics: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "(tttt)", + &n_dnssec_secure, + &n_dnssec_insecure, + &n_dnssec_bogus, + &n_dnssec_indeterminate); + if (r < 0) + return bus_log_parse_error(r); + + printf("\n%sDNSSEC Verdicts%s\n" + " Secure: %" PRIu64 "\n" + " Insecure: %" PRIu64 "\n" + " Bogus: %" PRIu64 "\n" + " Indeterminate: %" PRIu64 "\n", + ansi_highlight(), + ansi_normal(), + n_dnssec_secure, + n_dnssec_insecure, + n_dnssec_bogus, + n_dnssec_indeterminate); + + return 0; +} + +static int reset_statistics(sd_bus *bus) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + r = sd_bus_call_method(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResetStatistics", + &error, + NULL, + NULL); + if (r < 0) + return log_error_errno(r, "Failed to reset statistics: %s", bus_error_message(&error, r)); + + return 0; +} + +static void help_protocol_types(void) { + if (arg_legend) + puts("Known protocol types:"); + puts("dns\nllmnr\nllmnr-ipv4\nllmnr-ipv6"); +} + +static void help_dns_types(void) { + int i; + const char *t; + + if (arg_legend) + puts("Known DNS RR types:"); + for (i = 0; i < _DNS_TYPE_MAX; i++) { + t = dns_type_to_string(i); + if (t) + puts(t); + } +} + +static void help_dns_classes(void) { + int i; + const char *t; + + if (arg_legend) + puts("Known DNS RR classes:"); + for (i = 0; i < _DNS_CLASS_MAX; i++) { + t = dns_class_to_string(i); + if (t) + puts(t); + } +} + +static void help(void) { + printf("%1$s [OPTIONS...] HOSTNAME|ADDRESS...\n" + "%1$s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n" + "%1$s [OPTIONS...] --openpgp EMAIL@DOMAIN...\n" + "%1$s [OPTIONS...] --statistics\n" + "%1$s [OPTIONS...] --reset-statistics\n" + "\n" + "Resolve domain names, IPv4 and IPv6 addresses, DNS resource records, and services.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -4 Resolve IPv4 addresses\n" + " -6 Resolve IPv6 addresses\n" + " -i --interface=INTERFACE Look on interface\n" + " -p --protocol=PROTO|help Look via protocol\n" + " -t --type=TYPE|help Query RR with DNS type\n" + " -c --class=CLASS|help Query RR with DNS class\n" + " --service Resolve service (SRV)\n" + " --service-address=BOOL Resolve address for services (default: yes)\n" + " --service-txt=BOOL Resolve TXT records for services (default: yes)\n" + " --openpgp Query OpenPGP public key\n" + " --tlsa Query TLS public key\n" + " --cname=BOOL Follow CNAME redirects (default: yes)\n" + " --search=BOOL Use search domains for single-label names\n" + " (default: yes)\n" + " --raw[=payload|packet] Dump the answer as binary data\n" + " --legend=BOOL Print headers and additional info (default: yes)\n" + " --statistics Show resolver statistics\n" + " --reset-statistics Reset resolver statistics\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_LEGEND, + ARG_SERVICE, + ARG_CNAME, + ARG_SERVICE_ADDRESS, + ARG_SERVICE_TXT, + ARG_OPENPGP, + ARG_TLSA, + ARG_RAW, + ARG_SEARCH, + ARG_STATISTICS, + ARG_RESET_STATISTICS, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "type", required_argument, NULL, 't' }, + { "class", required_argument, NULL, 'c' }, + { "legend", required_argument, NULL, ARG_LEGEND }, + { "interface", required_argument, NULL, 'i' }, + { "protocol", required_argument, NULL, 'p' }, + { "cname", required_argument, NULL, ARG_CNAME }, + { "service", no_argument, NULL, ARG_SERVICE }, + { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS }, + { "service-txt", required_argument, NULL, ARG_SERVICE_TXT }, + { "openpgp", no_argument, NULL, ARG_OPENPGP }, + { "tlsa", optional_argument, NULL, ARG_TLSA }, + { "raw", optional_argument, NULL, ARG_RAW }, + { "search", required_argument, NULL, ARG_SEARCH }, + { "statistics", no_argument, NULL, ARG_STATISTICS, }, + { "reset-statistics", no_argument, NULL, ARG_RESET_STATISTICS }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h46i:t:c:p:", options, NULL)) >= 0) + switch(c) { + + case 'h': + help(); + return 0; /* done */; + + case ARG_VERSION: + return version(); + + case '4': + arg_family = AF_INET; + break; + + case '6': + arg_family = AF_INET6; + break; + + case 'i': { + int ifi; + + if (parse_ifindex(optarg, &ifi) >= 0) + arg_ifindex = ifi; + else { + ifi = if_nametoindex(optarg); + if (ifi <= 0) + return log_error_errno(errno, "Unknown interface %s: %m", optarg); + + arg_ifindex = ifi; + } + + break; + } + + case 't': + if (streq(optarg, "help")) { + help_dns_types(); + return 0; + } + + r = dns_type_from_string(optarg); + if (r < 0) { + log_error("Failed to parse RR record type %s", optarg); + return r; + } + arg_type = (uint16_t) r; + assert((int) arg_type == r); + + arg_mode = MODE_RESOLVE_RECORD; + break; + + case 'c': + if (streq(optarg, "help")) { + help_dns_classes(); + return 0; + } + + r = dns_class_from_string(optarg); + if (r < 0) { + log_error("Failed to parse RR record class %s", optarg); + return r; + } + arg_class = (uint16_t) r; + assert((int) arg_class == r); + + break; + + case ARG_LEGEND: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --legend= argument"); + + arg_legend = r; + break; + + case 'p': + if (streq(optarg, "help")) { + help_protocol_types(); + return 0; + } else if (streq(optarg, "dns")) + arg_flags |= SD_RESOLVED_DNS; + else if (streq(optarg, "llmnr")) + arg_flags |= SD_RESOLVED_LLMNR; + else if (streq(optarg, "llmnr-ipv4")) + arg_flags |= SD_RESOLVED_LLMNR_IPV4; + else if (streq(optarg, "llmnr-ipv6")) + arg_flags |= SD_RESOLVED_LLMNR_IPV6; + else { + log_error("Unknown protocol specifier: %s", optarg); + return -EINVAL; + } + + break; + + case ARG_SERVICE: + arg_mode = MODE_RESOLVE_SERVICE; + break; + + case ARG_OPENPGP: + arg_mode = MODE_RESOLVE_OPENPGP; + break; + + case ARG_TLSA: + arg_mode = MODE_RESOLVE_TLSA; + arg_service_family = service_family_from_string(optarg); + if (arg_service_family < 0) { + log_error("Unknown service family \"%s\".", optarg); + return -EINVAL; + } + break; + + case ARG_RAW: + if (on_tty()) { + log_error("Refusing to write binary data to tty."); + return -ENOTTY; + } + + if (optarg == NULL || streq(optarg, "payload")) + arg_raw = RAW_PAYLOAD; + else if (streq(optarg, "packet")) + arg_raw = RAW_PACKET; + else { + log_error("Unknown --raw specifier \"%s\".", optarg); + return -EINVAL; + } + + arg_legend = false; + break; + + case ARG_CNAME: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --cname= argument."); + SET_FLAG(arg_flags, SD_RESOLVED_NO_CNAME, r == 0); + break; + + case ARG_SERVICE_ADDRESS: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --service-address= argument."); + SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0); + break; + + case ARG_SERVICE_TXT: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --service-txt= argument."); + SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); + break; + + case ARG_SEARCH: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --search argument."); + SET_FLAG(arg_flags, SD_RESOLVED_NO_SEARCH, r == 0); + break; + + case ARG_STATISTICS: + arg_mode = MODE_STATISTICS; + break; + + case ARG_RESET_STATISTICS: + arg_mode = MODE_RESET_STATISTICS; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (arg_type == 0 && arg_class != 0) { + log_error("--class= may only be used in conjunction with --type=."); + return -EINVAL; + } + + if (arg_type != 0 && arg_mode == MODE_RESOLVE_SERVICE) { + log_error("--service and --type= may not be combined."); + return -EINVAL; + } + + if (arg_type != 0 && arg_class == 0) + arg_class = DNS_CLASS_IN; + + if (arg_class != 0 && arg_type == 0) + arg_type = DNS_TYPE_A; + + return 1 /* work to do */; +} + +int main(int argc, char **argv) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = sd_bus_open_system(&bus); + if (r < 0) { + log_error_errno(r, "sd_bus_open_system: %m"); + goto finish; + } + + switch (arg_mode) { + + case MODE_RESOLVE_HOST: + if (optind >= argc) { + log_error("No arguments passed."); + r = -EINVAL; + goto finish; + } + + while (argv[optind]) { + int family, ifindex, k; + union in_addr_union a; + + if (startswith(argv[optind], "dns:")) + k = resolve_rfc4501(bus, argv[optind]); + else { + k = parse_address(argv[optind], &family, &a, &ifindex); + if (k >= 0) + k = resolve_address(bus, family, &a, ifindex); + else + k = resolve_host(bus, argv[optind]); + } + + if (r == 0) + r = k; + + optind++; + } + break; + + case MODE_RESOLVE_RECORD: + if (optind >= argc) { + log_error("No arguments passed."); + r = -EINVAL; + goto finish; + } + + while (argv[optind]) { + int k; + + k = resolve_record(bus, argv[optind], arg_class, arg_type); + if (r == 0) + r = k; + + optind++; + } + break; + + case MODE_RESOLVE_SERVICE: + if (argc < optind + 1) { + log_error("Domain specification required."); + r = -EINVAL; + goto finish; + + } else if (argc == optind + 1) + r = resolve_service(bus, NULL, NULL, argv[optind]); + else if (argc == optind + 2) + r = resolve_service(bus, NULL, argv[optind], argv[optind+1]); + else if (argc == optind + 3) + r = resolve_service(bus, argv[optind], argv[optind+1], argv[optind+2]); + else { + log_error("Too many arguments."); + r = -EINVAL; + goto finish; + } + + break; + + case MODE_RESOLVE_OPENPGP: + if (argc < optind + 1) { + log_error("E-mail address required."); + r = -EINVAL; + goto finish; + + } + + r = 0; + while (optind < argc) { + int k; + + k = resolve_openpgp(bus, argv[optind++]); + if (k < 0) + r = k; + } + break; + + case MODE_RESOLVE_TLSA: + if (argc < optind + 1) { + log_error("Domain name required."); + r = -EINVAL; + goto finish; + + } + + r = 0; + while (optind < argc) { + int k; + + k = resolve_tlsa(bus, argv[optind++]); + if (k < 0) + r = k; + } + break; + + case MODE_STATISTICS: + if (argc > optind) { + log_error("Too many arguments."); + r = -EINVAL; + goto finish; + } + + r = show_statistics(bus); + break; + + case MODE_RESET_STATISTICS: + if (argc > optind) { + log_error("Too many arguments."); + r = -EINVAL; + goto finish; + } + + r = reset_statistics(bus); + break; + } + +finish: + return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/grp-resolve/systemd-resolved/resolved-bus.c b/src/grp-resolve/systemd-resolved/resolved-bus.c new file mode 100644 index 0000000000..33f7c61557 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-bus.c @@ -0,0 +1,1655 @@ +/*** + 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 "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-util.h" +#include "dns-domain.h" +#include "resolved-bus.h" +#include "resolved-def.h" +#include "resolved-dns-synthesize.h" +#include "resolved-link-bus.h" + +static int reply_query_state(DnsQuery *q) { + + switch (q->state) { + + case DNS_TRANSACTION_NO_SERVERS: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_NAME_SERVERS, "No appropriate name servers or networks for name found"); + + case DNS_TRANSACTION_TIMEOUT: + return sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "Query timed out"); + + case DNS_TRANSACTION_ATTEMPTS_MAX_REACHED: + return sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "All attempts to contact name servers or networks failed"); + + case DNS_TRANSACTION_INVALID_REPLY: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply"); + + case DNS_TRANSACTION_ERRNO: + return sd_bus_reply_method_errnof(q->request, q->answer_errno, "Lookup failed due to system error: %m"); + + case DNS_TRANSACTION_ABORTED: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "Query aborted"); + + case DNS_TRANSACTION_DNSSEC_FAILED: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s", + dnssec_result_to_string(q->answer_dnssec_result)); + + case DNS_TRANSACTION_NO_TRUST_ANCHOR: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_TRUST_ANCHOR, "No suitable trust anchor known"); + + case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_RR_TYPE_UNSUPPORTED, "Server does not support requested resource record type"); + + case DNS_TRANSACTION_NETWORK_DOWN: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NETWORK_DOWN, "Network is down"); + + case DNS_TRANSACTION_NOT_FOUND: + /* We return this as NXDOMAIN. This is only generated when a host doesn't implement LLMNR/TCP, and we + * thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */ + return sd_bus_reply_method_errorf(q->request, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q)); + + case DNS_TRANSACTION_RCODE_FAILURE: { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + if (q->answer_rcode == DNS_RCODE_NXDOMAIN) + sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q)); + else { + const char *rc, *n; + char p[DECIMAL_STR_MAX(q->answer_rcode)]; + + rc = dns_rcode_to_string(q->answer_rcode); + if (!rc) { + sprintf(p, "%i", q->answer_rcode); + rc = p; + } + + n = strjoina(_BUS_ERROR_DNS, rc); + sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error %s", dns_query_string(q), rc); + } + + return sd_bus_reply_method_error(q->request, &error); + } + + case DNS_TRANSACTION_NULL: + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: + case DNS_TRANSACTION_SUCCESS: + default: + assert_not_reached("Impossible state"); + } +} + +static int append_address(sd_bus_message *reply, DnsResourceRecord *rr, int ifindex) { + int r; + + assert(reply); + assert(rr); + + r = sd_bus_message_open_container(reply, 'r', "iiay"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "i", ifindex); + if (r < 0) + return r; + + if (rr->key->type == DNS_TYPE_A) { + r = sd_bus_message_append(reply, "i", AF_INET); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', &rr->a.in_addr, sizeof(struct in_addr)); + + } else if (rr->key->type == DNS_TYPE_AAAA) { + r = sd_bus_message_append(reply, "i", AF_INET6); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', &rr->aaaa.in6_addr, sizeof(struct in6_addr)); + } else + return -EAFNOSUPPORT; + + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return 0; +} + +static void bus_method_resolve_hostname_complete(DnsQuery *q) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *normalized = NULL; + DnsResourceRecord *rr; + unsigned added = 0; + int ifindex, r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + r = dns_query_process_cname(q); + if (r == -ELOOP) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); + goto finish; + } + if (r < 0) + goto finish; + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ + return; + + r = sd_bus_message_new_method_return(q->request, &reply); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "(iiay)"); + if (r < 0) + goto finish; + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + DnsQuestion *question; + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = append_address(reply, rr, ifindex); + if (r < 0) + goto finish; + + if (!canonical) + canonical = dns_resource_record_ref(rr); + + added++; + } + + if (added <= 0) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q)); + goto finish; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + /* The key names are not necessarily normalized, make sure that they are when we return them to our bus + * clients. */ + r = dns_name_normalize(dns_resource_key_name(canonical->key), &normalized); + if (r < 0) + goto finish; + + /* Return the precise spelling and uppercasing and CNAME target reported by the server */ + assert(canonical); + r = sd_bus_message_append( + reply, "st", + normalized, + SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); + if (r < 0) + goto finish; + + r = sd_bus_send(q->manager->bus, reply, NULL); + +finish: + if (r < 0) { + log_error_errno(r, "Failed to send hostname reply: %m"); + sd_bus_reply_method_errno(q->request, r, NULL); + } + + dns_query_free(q); +} + +static int check_ifindex_flags(int ifindex, uint64_t *flags, uint64_t ok, sd_bus_error *error) { + assert(flags); + + if (ifindex < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); + + if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_CNAME|ok)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter"); + + if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */ + *flags |= SD_RESOLVED_PROTOCOLS_ALL; + + return 0; +} + +static int parse_as_address(sd_bus_message *m, int ifindex, const char *hostname, int family, uint64_t flags) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *canonical = NULL; + union in_addr_union parsed; + int r, ff; + + /* Check if the hostname is actually already an IP address formatted as string. In that case just parse it, + * let's not attempt to look it up. */ + + r = in_addr_from_string_auto(hostname, &ff, &parsed); + if (r < 0) /* not an address */ + return 0; + + if (family != AF_UNSPEC && ff != family) + return sd_bus_reply_method_errorf(m, BUS_ERROR_NO_SUCH_RR, "The specified address is not of the requested family."); + + r = sd_bus_message_new_method_return(m, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(iiay)"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'r', "iiay"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "ii", ifindex, ff); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', &parsed, FAMILY_ADDRESS_SIZE(ff)); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + /* When an IP address is specified we just return it as canonical name, in order to avoid a DNS + * look-up. However, we reformat it to make sure it's in a truly canonical form (i.e. on IPv6 the inner + * omissions are always done the same way). */ + r = in_addr_to_string(ff, &parsed, &canonical); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "st", canonical, + SD_RESOLVED_FLAGS_MAKE(dns_synthesize_protocol(flags), ff, true)); + if (r < 0) + return r; + + return sd_bus_send(sd_bus_message_get_bus(m), reply, NULL); +} + +static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL; + Manager *m = userdata; + const char *hostname; + int family, ifindex; + uint64_t flags; + DnsQuery *q; + int r; + + assert(message); + assert(m); + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(message, "isit", &ifindex, &hostname, &family, &flags); + if (r < 0) + return r; + + if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); + + r = check_ifindex_flags(ifindex, &flags, SD_RESOLVED_NO_SEARCH, error); + if (r < 0) + return r; + + r = parse_as_address(message, ifindex, hostname, family, flags); + if (r != 0) + return r; + + r = dns_name_is_valid(hostname); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", hostname); + + r = dns_question_new_address(&question_utf8, family, hostname, false); + if (r < 0) + return r; + + r = dns_question_new_address(&question_idna, family, hostname, true); + if (r < 0) + return r; + + r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags); + if (r < 0) + return r; + + q->request = sd_bus_message_ref(message); + q->request_family = family; + q->complete = bus_method_resolve_hostname_complete; + q->suppress_unroutable_family = family == AF_UNSPEC; + + r = dns_query_bus_track(q, message); + if (r < 0) + goto fail; + + r = dns_query_go(q); + if (r < 0) + goto fail; + + return 1; + +fail: + dns_query_free(q); + return r; +} + +static void bus_method_resolve_address_complete(DnsQuery *q) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + DnsQuestion *question; + DnsResourceRecord *rr; + unsigned added = 0; + int ifindex, r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + r = dns_query_process_cname(q); + if (r == -ELOOP) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); + goto finish; + } + if (r < 0) + goto finish; + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ + return; + + r = sd_bus_message_new_method_return(q->request, &reply); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "(is)"); + if (r < 0) + goto finish; + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + _cleanup_free_ char *normalized = NULL; + + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = dns_name_normalize(rr->ptr.name, &normalized); + if (r < 0) + goto finish; + + r = sd_bus_message_append(reply, "(is)", ifindex, normalized); + if (r < 0) + goto finish; + + added++; + } + + if (added <= 0) { + _cleanup_free_ char *ip = NULL; + + (void) in_addr_to_string(q->request_family, &q->request_address, &ip); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, + "Address '%s' does not have any RR of requested type", strnull(ip)); + goto finish; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); + if (r < 0) + goto finish; + + r = sd_bus_send(q->manager->bus, reply, NULL); + +finish: + if (r < 0) { + log_error_errno(r, "Failed to send address reply: %m"); + sd_bus_reply_method_errno(q->request, r, NULL); + } + + dns_query_free(q); +} + +static int bus_method_resolve_address(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + Manager *m = userdata; + int family, ifindex; + uint64_t flags; + const void *d; + DnsQuery *q; + size_t sz; + int r; + + assert(message); + assert(m); + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(message, "ii", &ifindex, &family); + if (r < 0) + return r; + + if (!IN_SET(family, AF_INET, AF_INET6)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); + + r = sd_bus_message_read_array(message, 'y', &d, &sz); + if (r < 0) + return r; + + if (sz != FAMILY_ADDRESS_SIZE(family)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid address size"); + + r = sd_bus_message_read(message, "t", &flags); + if (r < 0) + return r; + + r = check_ifindex_flags(ifindex, &flags, 0, error); + if (r < 0) + return r; + + r = dns_question_new_reverse(&question, family, d); + if (r < 0) + return r; + + r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); + if (r < 0) + return r; + + q->request = sd_bus_message_ref(message); + q->request_family = family; + memcpy(&q->request_address, d, sz); + q->complete = bus_method_resolve_address_complete; + + r = dns_query_bus_track(q, message); + if (r < 0) + goto fail; + + r = dns_query_go(q); + if (r < 0) + goto fail; + + return 1; + +fail: + dns_query_free(q); + return r; +} + +static int bus_message_append_rr(sd_bus_message *m, DnsResourceRecord *rr, int ifindex) { + int r; + + assert(m); + assert(rr); + + r = sd_bus_message_open_container(m, 'r', "iqqay"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "iqq", + ifindex, + rr->key->class, + rr->key->type); + if (r < 0) + return r; + + r = dns_resource_record_to_wire_format(rr, false); + if (r < 0) + return r; + + r = sd_bus_message_append_array(m, 'y', rr->wire_format, rr->wire_format_size); + if (r < 0) + return r; + + return sd_bus_message_close_container(m); +} + +static void bus_method_resolve_record_complete(DnsQuery *q) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + DnsResourceRecord *rr; + DnsQuestion *question; + unsigned added = 0; + int ifindex; + int r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + r = dns_query_process_cname(q); + if (r == -ELOOP) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); + goto finish; + } + if (r < 0) + goto finish; + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ + return; + + r = sd_bus_message_new_method_return(q->request, &reply); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "(iqqay)"); + if (r < 0) + goto finish; + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = bus_message_append_rr(reply, rr, ifindex); + if (r < 0) + goto finish; + + added++; + } + + if (added <= 0) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Name '%s' does not have any RR of the requested type", dns_query_string(q)); + goto finish; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); + if (r < 0) + goto finish; + + r = sd_bus_send(q->manager->bus, reply, NULL); + +finish: + if (r < 0) { + log_error_errno(r, "Failed to send record reply: %m"); + sd_bus_reply_method_errno(q->request, r, NULL); + } + + dns_query_free(q); +} + +static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + Manager *m = userdata; + uint16_t class, type; + const char *name; + int r, ifindex; + uint64_t flags; + DnsQuery *q; + + assert(message); + assert(m); + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(message, "isqqt", &ifindex, &name, &class, &type, &flags); + if (r < 0) + return r; + + r = dns_name_is_valid(name); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid name '%s'", name); + + if (!dns_type_is_valid_query(type)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified resource record type %" PRIu16 " may not be used in a query.", type); + if (dns_type_is_obsolete(type)) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Specified DNS resource record type %" PRIu16 " is obsolete.", type); + + r = check_ifindex_flags(ifindex, &flags, 0, error); + if (r < 0) + return r; + + question = dns_question_new(1); + if (!question) + return -ENOMEM; + + key = dns_resource_key_new(class, type, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(question, key); + if (r < 0) + return r; + + r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); + if (r < 0) + return r; + + q->request = sd_bus_message_ref(message); + q->complete = bus_method_resolve_record_complete; + + r = dns_query_bus_track(q, message); + if (r < 0) + goto fail; + + r = dns_query_go(q); + if (r < 0) + goto fail; + + return 1; + +fail: + dns_query_free(q); + return r; +} + +static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; + _cleanup_free_ char *normalized = NULL; + DnsQuery *aux; + int r; + + assert(q); + assert(reply); + assert(rr); + assert(rr->key); + + if (rr->key->type != DNS_TYPE_SRV) + return 0; + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + /* First, let's see if we could find an appropriate A or AAAA + * record for the SRV record */ + LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { + DnsResourceRecord *zz; + DnsQuestion *question; + + if (aux->state != DNS_TRANSACTION_SUCCESS) + continue; + if (aux->auxiliary_result != 0) + continue; + + question = dns_query_question_for_protocol(aux, aux->answer_protocol); + + r = dns_name_equal(dns_question_first_name(question), rr->srv.name); + if (r < 0) + return r; + if (r == 0) + continue; + + DNS_ANSWER_FOREACH(zz, aux->answer) { + + r = dns_question_matches_rr(question, zz, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + + canonical = dns_resource_record_ref(zz); + break; + } + + if (canonical) + break; + } + + /* Is there are successful A/AAAA lookup for this SRV RR? If not, don't add it */ + if (!canonical) + return 0; + } + + r = sd_bus_message_open_container(reply, 'r', "qqqsa(iiay)s"); + if (r < 0) + return r; + + r = dns_name_normalize(rr->srv.name, &normalized); + if (r < 0) + return r; + + r = sd_bus_message_append( + reply, + "qqqs", + rr->srv.priority, rr->srv.weight, rr->srv.port, normalized); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(iiay)"); + if (r < 0) + return r; + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { + DnsResourceRecord *zz; + DnsQuestion *question; + int ifindex; + + if (aux->state != DNS_TRANSACTION_SUCCESS) + continue; + if (aux->auxiliary_result != 0) + continue; + + question = dns_query_question_for_protocol(aux, aux->answer_protocol); + + r = dns_name_equal(dns_question_first_name(question), rr->srv.name); + if (r < 0) + return r; + if (r == 0) + continue; + + DNS_ANSWER_FOREACH_IFINDEX(zz, ifindex, aux->answer) { + + r = dns_question_matches_rr(question, zz, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + + r = append_address(reply, zz, ifindex); + if (r < 0) + return r; + } + } + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + if (canonical) { + normalized = mfree(normalized); + + r = dns_name_normalize(dns_resource_key_name(canonical->key), &normalized); + if (r < 0) + return r; + } + + /* Note that above we appended the hostname as encoded in the + * SRV, and here the canonical hostname this maps to. */ + r = sd_bus_message_append(reply, "s", normalized); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return 1; +} + +static int append_txt(sd_bus_message *reply, DnsResourceRecord *rr) { + DnsTxtItem *i; + int r; + + assert(reply); + assert(rr); + assert(rr->key); + + if (rr->key->type != DNS_TYPE_TXT) + return 0; + + LIST_FOREACH(items, i, rr->txt.items) { + + if (i->length <= 0) + continue; + + r = sd_bus_message_append_array(reply, 'y', i->data, i->length); + if (r < 0) + return r; + } + + return 1; +} + +static void resolve_service_all_complete(DnsQuery *q) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL; + DnsQuestion *question; + DnsResourceRecord *rr; + unsigned added = 0; + DnsQuery *aux; + int r; + + assert(q); + + if (q->block_all_complete > 0) + return; + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + DnsQuery *bad = NULL; + bool have_success = false; + + LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { + + switch (aux->state) { + + case DNS_TRANSACTION_PENDING: + /* If an auxiliary query is still pending, let's wait */ + return; + + case DNS_TRANSACTION_SUCCESS: + if (aux->auxiliary_result == 0) + have_success = true; + else + bad = aux; + break; + + default: + bad = aux; + break; + } + } + + if (!have_success) { + /* We can only return one error, hence pick the last error we encountered */ + + assert(bad); + + if (bad->state == DNS_TRANSACTION_SUCCESS) { + assert(bad->auxiliary_result != 0); + + if (bad->auxiliary_result == -ELOOP) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(bad)); + goto finish; + } + + r = bad->auxiliary_result; + goto finish; + } + + r = reply_query_state(bad); + goto finish; + } + } + + r = sd_bus_message_new_method_return(q->request, &reply); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "(qqqsa(iiay)s)"); + if (r < 0) + goto finish; + + question = dns_query_question_for_protocol(q, q->answer_protocol); + DNS_ANSWER_FOREACH(rr, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = append_srv(q, reply, rr); + if (r < 0) + goto finish; + if (r == 0) /* not an SRV record */ + continue; + + if (!canonical) + canonical = dns_resource_record_ref(rr); + + added++; + } + + if (added <= 0) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q)); + goto finish; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "ay"); + if (r < 0) + goto finish; + + DNS_ANSWER_FOREACH(rr, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = append_txt(reply, rr); + if (r < 0) + goto finish; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + assert(canonical); + r = dns_service_split(dns_resource_key_name(canonical->key), &name, &type, &domain); + if (r < 0) + goto finish; + + r = sd_bus_message_append( + reply, + "ssst", + name, type, domain, + SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); + if (r < 0) + goto finish; + + r = sd_bus_send(q->manager->bus, reply, NULL); + +finish: + if (r < 0) { + log_error_errno(r, "Failed to send service reply: %m"); + sd_bus_reply_method_errno(q->request, r, NULL); + } + + dns_query_free(q); +} + +static void resolve_service_hostname_complete(DnsQuery *q) { + int r; + + assert(q); + assert(q->auxiliary_for); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + resolve_service_all_complete(q->auxiliary_for); + return; + } + + r = dns_query_process_cname(q); + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ + return; + + /* This auxiliary lookup is finished or failed, let's see if all are finished now. */ + q->auxiliary_result = r; + resolve_service_all_complete(q->auxiliary_for); +} + +static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifindex) { + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + DnsQuery *aux; + int r; + + assert(q); + assert(rr); + assert(rr->key); + assert(rr->key->type == DNS_TYPE_SRV); + + /* OK, we found an SRV record for the service. Let's resolve + * the hostname included in it */ + + r = dns_question_new_address(&question, q->request_family, rr->srv.name, false); + if (r < 0) + return r; + + r = dns_query_new(q->manager, &aux, question, question, ifindex, q->flags|SD_RESOLVED_NO_SEARCH); + if (r < 0) + return r; + + aux->request_family = q->request_family; + aux->complete = resolve_service_hostname_complete; + + r = dns_query_make_auxiliary(aux, q); + if (r == -EAGAIN) { + /* Too many auxiliary lookups? If so, don't complain, + * let's just not add this one, we already have more + * than enough */ + + dns_query_free(aux); + return 0; + } + if (r < 0) + goto fail; + + /* Note that auxiliary queries do not track the original bus + * client, only the primary request does that. */ + + r = dns_query_go(aux); + if (r < 0) + goto fail; + + return 1; + +fail: + dns_query_free(aux); + return r; +} + +static void bus_method_resolve_service_complete(DnsQuery *q) { + bool has_root_domain = false; + DnsResourceRecord *rr; + DnsQuestion *question; + unsigned found = 0; + int ifindex, r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + r = dns_query_process_cname(q); + if (r == -ELOOP) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); + goto finish; + } + if (r < 0) + goto finish; + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ + return; + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; + + if (rr->key->type != DNS_TYPE_SRV) + continue; + + if (dns_name_is_root(rr->srv.name)) { + has_root_domain = true; + continue; + } + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + q->block_all_complete++; + r = resolve_service_hostname(q, rr, ifindex); + q->block_all_complete--; + + if (r < 0) + goto finish; + } + + found++; + } + + if (has_root_domain && found <= 0) { + /* If there's exactly one SRV RR and it uses + * the root domain as host name, then the + * service is explicitly not offered on the + * domain. Report this as a recognizable + * error. See RFC 2782, Section "Usage + * Rules". */ + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_SERVICE, "'%s' does not provide the requested service", dns_query_string(q)); + goto finish; + } + + if (found <= 0) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q)); + goto finish; + } + + /* Maybe we are already finished? check now... */ + resolve_service_all_complete(q); + return; + +finish: + if (r < 0) { + log_error_errno(r, "Failed to send service reply: %m"); + sd_bus_reply_method_errno(q->request, r, NULL); + } + + dns_query_free(q); +} + +static int bus_method_resolve_service(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL; + const char *name, *type, *domain; + Manager *m = userdata; + int family, ifindex; + uint64_t flags; + DnsQuery *q; + int r; + + assert(message); + assert(m); + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(message, "isssit", &ifindex, &name, &type, &domain, &family, &flags); + if (r < 0) + return r; + + if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); + + if (isempty(name)) + name = NULL; + else if (!dns_service_name_is_valid(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid service name '%s'", name); + + if (isempty(type)) + type = NULL; + else if (!dns_srv_type_is_valid(type)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid SRV service type '%s'", type); + + r = dns_name_is_valid(domain); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid domain '%s'", domain); + + if (name && !type) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Service name cannot be specified without service type."); + + r = check_ifindex_flags(ifindex, &flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS, error); + if (r < 0) + return r; + + r = dns_question_new_service(&question_utf8, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), false); + if (r < 0) + return r; + + r = dns_question_new_service(&question_idna, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), true); + if (r < 0) + return r; + + r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags|SD_RESOLVED_NO_SEARCH); + if (r < 0) + return r; + + q->request = sd_bus_message_ref(message); + q->request_family = family; + q->complete = bus_method_resolve_service_complete; + + r = dns_query_bus_track(q, message); + if (r < 0) + goto fail; + + r = dns_query_go(q); + if (r < 0) + goto fail; + + return 1; + +fail: + dns_query_free(q); + return r; +} + +int bus_dns_server_append(sd_bus_message *reply, DnsServer *s, bool with_ifindex) { + int r; + + assert(reply); + assert(s); + + r = sd_bus_message_open_container(reply, 'r', with_ifindex ? "iiay" : "iay"); + if (r < 0) + return r; + + if (with_ifindex) { + r = sd_bus_message_append(reply, "i", s->link ? s->link->ifindex : 0); + if (r < 0) + return r; + } + + r = sd_bus_message_append(reply, "i", s->family); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', &s->address, FAMILY_ADDRESS_SIZE(s->family)); + if (r < 0) + return r; + + return sd_bus_message_close_container(reply); +} + +static int bus_property_get_dns_servers( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Manager *m = userdata; + unsigned c = 0; + DnsServer *s; + Iterator i; + Link *l; + int r; + + assert(reply); + assert(m); + + r = sd_bus_message_open_container(reply, 'a', "(iiay)"); + if (r < 0) + return r; + + LIST_FOREACH(servers, s, m->dns_servers) { + r = bus_dns_server_append(reply, s, true); + if (r < 0) + return r; + + c++; + } + + HASHMAP_FOREACH(l, m->links, i) { + LIST_FOREACH(servers, s, l->dns_servers) { + r = bus_dns_server_append(reply, s, true); + if (r < 0) + return r; + c++; + } + } + + if (c == 0) { + LIST_FOREACH(servers, s, m->fallback_dns_servers) { + r = bus_dns_server_append(reply, s, true); + if (r < 0) + return r; + } + } + + return sd_bus_message_close_container(reply); +} + +static int bus_property_get_domains( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Manager *m = userdata; + DnsSearchDomain *d; + Iterator i; + Link *l; + int r; + + assert(reply); + assert(m); + + r = sd_bus_message_open_container(reply, 'a', "(isb)"); + if (r < 0) + return r; + + LIST_FOREACH(domains, d, m->search_domains) { + r = sd_bus_message_append(reply, "(isb)", 0, d->name, d->route_only); + if (r < 0) + return r; + } + + HASHMAP_FOREACH(l, m->links, i) { + LIST_FOREACH(domains, d, l->search_domains) { + r = sd_bus_message_append(reply, "(isb)", l->ifindex, d->name, d->route_only); + if (r < 0) + return r; + } + } + + return sd_bus_message_close_container(reply); +} + +static int bus_property_get_transaction_statistics( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Manager *m = userdata; + + assert(reply); + assert(m); + + return sd_bus_message_append(reply, "(tt)", + (uint64_t) hashmap_size(m->dns_transactions), + (uint64_t) m->n_transactions_total); +} + +static int bus_property_get_cache_statistics( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + uint64_t size = 0, hit = 0, miss = 0; + Manager *m = userdata; + DnsScope *s; + + assert(reply); + assert(m); + + LIST_FOREACH(scopes, s, m->dns_scopes) { + size += dns_cache_size(&s->cache); + hit += s->cache.n_hit; + miss += s->cache.n_miss; + } + + return sd_bus_message_append(reply, "(ttt)", size, hit, miss); +} + +static int bus_property_get_dnssec_statistics( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Manager *m = userdata; + + assert(reply); + assert(m); + + return sd_bus_message_append(reply, "(tttt)", + (uint64_t) m->n_dnssec_verdict[DNSSEC_SECURE], + (uint64_t) m->n_dnssec_verdict[DNSSEC_INSECURE], + (uint64_t) m->n_dnssec_verdict[DNSSEC_BOGUS], + (uint64_t) m->n_dnssec_verdict[DNSSEC_INDETERMINATE]); +} + +static int bus_property_get_dnssec_supported( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Manager *m = userdata; + + assert(reply); + assert(m); + + return sd_bus_message_append(reply, "b", manager_dnssec_supported(m)); +} + +static int bus_method_reset_statistics(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + DnsScope *s; + + assert(message); + assert(m); + + LIST_FOREACH(scopes, s, m->dns_scopes) + s->cache.n_hit = s->cache.n_miss = 0; + + m->n_transactions_total = 0; + zero(m->n_dnssec_verdict); + + return sd_bus_reply_method_return(message, NULL); +} + +static int get_any_link(Manager *m, int ifindex, Link **ret, sd_bus_error *error) { + Link *l; + + assert(m); + assert(ret); + + if (ifindex <= 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); + + l = hashmap_get(m->links, INT_TO_PTR(ifindex)); + if (!l) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_LINK, "Link %i not known", ifindex); + + *ret = l; + return 0; +} + +static int get_unmanaged_link(Manager *m, int ifindex, Link **ret, sd_bus_error *error) { + Link *l; + int r; + + assert(m); + assert(ret); + + r = get_any_link(m, ifindex, &l, error); + if (r < 0) + return r; + + if (l->flags & IFF_LOOPBACK) + return sd_bus_error_setf(error, BUS_ERROR_LINK_BUSY, "Link %s is loopback device.", l->name); + if (l->is_managed) + return sd_bus_error_setf(error, BUS_ERROR_LINK_BUSY, "Link %s is managed.", l->name); + + *ret = l; + return 0; +} + +static int call_link_method(Manager *m, sd_bus_message *message, sd_bus_message_handler_t handler, sd_bus_error *error) { + int ifindex, r; + Link *l; + + assert(m); + assert(message); + assert(handler); + + assert_cc(sizeof(int) == sizeof(int32_t)); + r = sd_bus_message_read(message, "i", &ifindex); + if (r < 0) + return r; + + r = get_unmanaged_link(m, ifindex, &l, error); + if (r < 0) + return r; + + return handler(message, l, error); +} + +static int bus_method_set_link_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_dns_servers, error); +} + +static int bus_method_set_link_domains(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_domains, error); +} + +static int bus_method_set_link_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_llmnr, error); +} + +static int bus_method_set_link_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_mdns, error); +} + +static int bus_method_set_link_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_dnssec, error); +} + +static int bus_method_set_link_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_dnssec_negative_trust_anchors, error); +} + +static int bus_method_revert_link(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_revert, error); +} + +static int bus_method_get_link(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *p = NULL; + Manager *m = userdata; + int r, ifindex; + Link *l; + + assert(message); + assert(m); + + assert_cc(sizeof(int) == sizeof(int32_t)); + r = sd_bus_message_read(message, "i", &ifindex); + if (r < 0) + return r; + + r = get_any_link(m, ifindex, &l, error); + if (r < 0) + return r; + + p = link_bus_path(l); + if (!p) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", p); +} + +static const sd_bus_vtable resolve_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("LLMNRHostname", "s", NULL, offsetof(Manager, llmnr_hostname), 0), + SD_BUS_PROPERTY("DNS", "a(iiay)", bus_property_get_dns_servers, 0, 0), + SD_BUS_PROPERTY("Domains", "a(isb)", bus_property_get_domains, 0, 0), + SD_BUS_PROPERTY("TransactionStatistics", "(tt)", bus_property_get_transaction_statistics, 0, 0), + SD_BUS_PROPERTY("CacheStatistics", "(ttt)", bus_property_get_cache_statistics, 0, 0), + SD_BUS_PROPERTY("DNSSECStatistics", "(tttt)", bus_property_get_dnssec_statistics, 0, 0), + SD_BUS_PROPERTY("DNSSECSupported", "b", bus_property_get_dnssec_supported, 0, 0), + + SD_BUS_METHOD("ResolveHostname", "isit", "a(iiay)st", bus_method_resolve_hostname, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ResolveAddress", "iiayt", "a(is)t", bus_method_resolve_address, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ResolveRecord", "isqqt", "a(iqqay)t", bus_method_resolve_record, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ResolveService", "isssit", "a(qqqsa(iiay)s)aayssst", bus_method_resolve_service, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ResetStatistics", NULL, NULL, bus_method_reset_statistics, 0), + SD_BUS_METHOD("GetLink", "i", "o", bus_method_get_link, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetLinkDNS", "ia(iay)", NULL, bus_method_set_link_dns_servers, 0), + SD_BUS_METHOD("SetLinkDomains", "ia(sb)", NULL, bus_method_set_link_domains, 0), + SD_BUS_METHOD("SetLinkLLMNR", "is", NULL, bus_method_set_link_llmnr, 0), + SD_BUS_METHOD("SetLinkMulticastDNS", "is", NULL, bus_method_set_link_mdns, 0), + SD_BUS_METHOD("SetLinkDNSSEC", "is", NULL, bus_method_set_link_dnssec, 0), + SD_BUS_METHOD("SetLinkDNSSECNegativeTrustAnchors", "ias", NULL, bus_method_set_link_dnssec_negative_trust_anchors, 0), + SD_BUS_METHOD("RevertLink", "i", NULL, bus_method_revert_link, 0), + + SD_BUS_VTABLE_END, +}; + +static int on_bus_retry(sd_event_source *s, usec_t usec, void *userdata) { + Manager *m = userdata; + + assert(s); + assert(m); + + m->bus_retry_event_source = sd_event_source_unref(m->bus_retry_event_source); + + manager_connect_bus(m); + return 0; +} + +static int match_prepare_for_sleep(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) { + Manager *m = userdata; + int b, r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) { + log_debug_errno(r, "Failed to parse PrepareForSleep signal: %m"); + return 0; + } + + if (b) + return 0; + + log_debug("Coming back from suspend, verifying all RRs..."); + + manager_verify_all(m); + return 0; +} + +int manager_connect_bus(Manager *m) { + int r; + + assert(m); + + if (m->bus) + return 0; + + r = sd_bus_default_system(&m->bus); + if (r < 0) { + /* We failed to connect? Yuck, we must be in early + * boot. Let's try in 5s again. As soon as we have + * kdbus we can stop doing this... */ + + log_debug_errno(r, "Failed to connect to bus, trying again in 5s: %m"); + + r = sd_event_add_time(m->event, &m->bus_retry_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 5*USEC_PER_SEC, 0, on_bus_retry, m); + if (r < 0) + return log_error_errno(r, "Failed to install bus reconnect time event: %m"); + + (void) sd_event_source_set_description(m->bus_retry_event_source, "bus-retry"); + return 0; + } + + r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/resolve1", "org.freedesktop.resolve1.Manager", resolve_vtable, m); + if (r < 0) + return log_error_errno(r, "Failed to register object: %m"); + + r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/resolve1/link", "org.freedesktop.resolve1.Link", link_vtable, link_object_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register link objects: %m"); + + r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/resolve1/link", link_node_enumerator, m); + if (r < 0) + return log_error_errno(r, "Failed to register link enumerator: %m"); + + r = sd_bus_request_name(m->bus, "org.freedesktop.resolve1", 0); + if (r < 0) + return log_error_errno(r, "Failed to register name: %m"); + + r = sd_bus_attach_event(m->bus, m->event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + r = sd_bus_add_match(m->bus, &m->prepare_for_sleep_slot, + "type='signal'," + "sender='org.freedesktop.login1'," + "interface='org.freedesktop.login1.Manager'," + "member='PrepareForSleep'," + "path='/org/freedesktop/login1'", + match_prepare_for_sleep, + m); + if (r < 0) + log_error_errno(r, "Failed to add match for PrepareForSleep: %m"); + + return 0; +} diff --git a/src/grp-resolve/systemd-resolved/resolved-bus.h b/src/grp-resolve/systemd-resolved/resolved-bus.h new file mode 100644 index 0000000000..f49e1337d2 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-bus.h @@ -0,0 +1,25 @@ +#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 "resolved-manager.h" + +int manager_connect_bus(Manager *m); +int bus_dns_server_append(sd_bus_message *reply, DnsServer *s, bool with_ifindex); diff --git a/src/grp-resolve/systemd-resolved/resolved-conf.c b/src/grp-resolve/systemd-resolved/resolved-conf.c new file mode 100644 index 0000000000..990dc03b60 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-conf.c @@ -0,0 +1,236 @@ +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 "alloc-util.h" +#include "conf-parser.h" +#include "def.h" +#include "extract-word.h" +#include "parse-util.h" +#include "resolved-conf.h" +#include "string-util.h" + +int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word) { + union in_addr_union address; + int family, r; + DnsServer *s; + + assert(m); + assert(word); + + r = in_addr_from_string_auto(word, &family, &address); + if (r < 0) + return r; + + /* Filter out duplicates */ + s = dns_server_find(manager_get_first_dns_server(m, type), family, &address); + if (s) { + /* + * Drop the marker. This is used to find the servers + * that ceased to exist, see + * manager_mark_dns_servers() and + * manager_flush_marked_dns_servers(). + */ + dns_server_move_back_and_unmark(s); + return 0; + } + + return dns_server_new(m, NULL, type, NULL, family, &address); +} + +int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string) { + int r; + + assert(m); + assert(string); + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + r = manager_add_dns_server_by_string(m, type, word); + if (r < 0) + log_warning_errno(r, "Failed to add DNS server address '%s', ignoring.", word); + } + + return 0; +} + +int manager_add_search_domain_by_string(Manager *m, const char *domain) { + DnsSearchDomain *d; + bool route_only; + int r; + + assert(m); + assert(domain); + + route_only = *domain == '~'; + if (route_only) + domain++; + + if (dns_name_is_root(domain) || streq(domain, "*")) { + route_only = true; + domain = "."; + } + + r = dns_search_domain_find(m->search_domains, domain, &d); + if (r < 0) + return r; + if (r > 0) + dns_search_domain_move_back_and_unmark(d); + else { + r = dns_search_domain_new(m, &d, DNS_SEARCH_DOMAIN_SYSTEM, NULL, domain); + if (r < 0) + return r; + } + + d->route_only = route_only; + return 0; +} + +int manager_parse_search_domains_and_warn(Manager *m, const char *string) { + int r; + + assert(m); + assert(string); + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&string, &word, NULL, EXTRACT_QUOTES); + if (r < 0) + return r; + if (r == 0) + break; + + r = manager_add_search_domain_by_string(m, word); + if (r < 0) + log_warning_errno(r, "Failed to add search domain '%s', ignoring.", word); + } + + return 0; +} + +int config_parse_dns_servers( + 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) { + + Manager *m = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(m); + + if (isempty(rvalue)) + /* Empty assignment means clear the list */ + dns_server_unlink_all(manager_get_first_dns_server(m, ltype)); + else { + /* Otherwise, add to the list */ + r = manager_parse_dns_server_string_and_warn(m, ltype, rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse DNS server string '%s'. Ignoring.", rvalue); + return 0; + } + } + + /* If we have a manual setting, then we stop reading + * /etc/resolv.conf */ + if (ltype == DNS_SERVER_SYSTEM) + m->read_resolv_conf = false; + if (ltype == DNS_SERVER_FALLBACK) + m->need_builtin_fallbacks = false; + + return 0; +} + +int config_parse_search_domains( + 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) { + + Manager *m = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(m); + + if (isempty(rvalue)) + /* Empty assignment means clear the list */ + dns_search_domain_unlink_all(m->search_domains); + else { + /* Otherwise, add to the list */ + r = manager_parse_search_domains_and_warn(m, rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse search domains string '%s'. Ignoring.", rvalue); + return 0; + } + } + + /* If we have a manual setting, then we stop reading + * /etc/resolv.conf */ + m->read_resolv_conf = false; + + return 0; +} + +int manager_parse_config_file(Manager *m) { + int r; + + assert(m); + + r = config_parse_many(PKGSYSCONFDIR "/resolved.conf", + CONF_PATHS_NULSTR("systemd/resolved.conf.d"), + "Resolve\0", + config_item_perf_lookup, resolved_gperf_lookup, + false, m); + if (r < 0) + return r; + + if (m->need_builtin_fallbacks) { + r = manager_parse_dns_server_string_and_warn(m, DNS_SERVER_FALLBACK, DNS_SERVERS); + if (r < 0) + return r; + } + + return 0; + +} diff --git a/src/grp-resolve/systemd-resolved/resolved-conf.h b/src/grp-resolve/systemd-resolved/resolved-conf.h new file mode 100644 index 0000000000..e1fd2cceec --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-conf.h @@ -0,0 +1,36 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 "resolved-manager.h" + +int manager_parse_config_file(Manager *m); + +int manager_add_search_domain_by_string(Manager *m, const char *domain); +int manager_parse_search_domains_and_warn(Manager *m, const char *string); + +int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word); +int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string); + +const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, unsigned length); + +int config_parse_dns_servers(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); +int config_parse_search_domains(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); +int config_parse_dnssec(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); diff --git a/src/grp-resolve/systemd-resolved/resolved-def.h b/src/grp-resolve/systemd-resolved/resolved-def.h new file mode 100644 index 0000000000..c4c1915b18 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-def.h @@ -0,0 +1,38 @@ +#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 + +#define SD_RESOLVED_DNS (UINT64_C(1) << 0) +#define SD_RESOLVED_LLMNR_IPV4 (UINT64_C(1) << 1) +#define SD_RESOLVED_LLMNR_IPV6 (UINT64_C(1) << 2) +#define SD_RESOLVED_MDNS_IPV4 (UINT64_C(1) << 3) +#define SD_RESOLVED_MDNS_IPV6 (UINT64_C(1) << 4) +#define SD_RESOLVED_NO_CNAME (UINT64_C(1) << 5) +#define SD_RESOLVED_NO_TXT (UINT64_C(1) << 6) +#define SD_RESOLVED_NO_ADDRESS (UINT64_C(1) << 7) +#define SD_RESOLVED_NO_SEARCH (UINT64_C(1) << 8) +#define SD_RESOLVED_AUTHENTICATED (UINT64_C(1) << 9) + +#define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6) +#define SD_RESOLVED_MDNS (SD_RESOLVED_MDNS_IPV4|SD_RESOLVED_MDNS_IPV6) + +#define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS) diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-answer.c b/src/grp-resolve/systemd-resolved/resolved-dns-answer.c new file mode 100644 index 0000000000..0dadf8b1dd --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-answer.c @@ -0,0 +1,858 @@ +/*** + 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 "alloc-util.h" +#include "dns-domain.h" +#include "resolved-dns-answer.h" +#include "resolved-dns-dnssec.h" +#include "string-util.h" + +DnsAnswer *dns_answer_new(unsigned n) { + DnsAnswer *a; + + a = malloc0(offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * n); + if (!a) + return NULL; + + a->n_ref = 1; + a->n_allocated = n; + + return a; +} + +DnsAnswer *dns_answer_ref(DnsAnswer *a) { + if (!a) + return NULL; + + assert(a->n_ref > 0); + a->n_ref++; + return a; +} + +static void dns_answer_flush(DnsAnswer *a) { + DnsResourceRecord *rr; + + if (!a) + return; + + DNS_ANSWER_FOREACH(rr, a) + dns_resource_record_unref(rr); + + a->n_rrs = 0; +} + +DnsAnswer *dns_answer_unref(DnsAnswer *a) { + if (!a) + return NULL; + + assert(a->n_ref > 0); + + if (a->n_ref == 1) { + dns_answer_flush(a); + free(a); + } else + a->n_ref--; + + return NULL; +} + +static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { + assert(rr); + + if (!a) + return -ENOSPC; + + if (a->n_rrs >= a->n_allocated) + return -ENOSPC; + + a->items[a->n_rrs++] = (DnsAnswerItem) { + .rr = dns_resource_record_ref(rr), + .ifindex = ifindex, + .flags = flags, + }; + + return 1; +} + +static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) { + DnsResourceRecord *rr; + DnsAnswerFlags flags; + int ifindex, r; + + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, source) { + r = dns_answer_add_raw(a, rr, ifindex, flags); + if (r < 0) + return r; + } + + return 0; +} + +int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { + unsigned i; + int r; + + assert(rr); + + if (!a) + return -ENOSPC; + if (a->n_ref > 1) + return -EBUSY; + + for (i = 0; i < a->n_rrs; i++) { + if (a->items[i].ifindex != ifindex) + continue; + + r = dns_resource_record_equal(a->items[i].rr, rr); + if (r < 0) + return r; + if (r > 0) { + /* Don't mix contradicting TTLs (see below) */ + if ((rr->ttl == 0) != (a->items[i].rr->ttl == 0)) + return -EINVAL; + + /* Entry already exists, keep the entry with + * the higher RR. */ + if (rr->ttl > a->items[i].rr->ttl) { + dns_resource_record_ref(rr); + dns_resource_record_unref(a->items[i].rr); + a->items[i].rr = rr; + } + + a->items[i].flags |= flags; + return 0; + } + + r = dns_resource_key_equal(a->items[i].rr->key, rr->key); + if (r < 0) + return r; + if (r > 0) { + /* There's already an RR of the same RRset in + * place! Let's see if the TTLs more or less + * match. We don't really care if they match + * precisely, but we do care whether one is 0 + * and the other is not. See RFC 2181, Section + * 5.2.*/ + + if ((rr->ttl == 0) != (a->items[i].rr->ttl == 0)) + return -EINVAL; + } + } + + return dns_answer_add_raw(a, rr, ifindex, flags); +} + +static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) { + DnsResourceRecord *rr; + DnsAnswerFlags flags; + int ifindex, r; + + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, b) { + r = dns_answer_add(a, rr, ifindex, flags); + if (r < 0) + return r; + } + + return 0; +} + +int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { + int r; + + assert(a); + assert(rr); + + r = dns_answer_reserve_or_clone(a, 1); + if (r < 0) + return r; + + return dns_answer_add(*a, rr, ifindex, flags); +} + +int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *soa = NULL; + + soa = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_SOA, name); + if (!soa) + return -ENOMEM; + + soa->ttl = ttl; + + soa->soa.mname = strdup(name); + if (!soa->soa.mname) + return -ENOMEM; + + soa->soa.rname = strappend("root.", name); + if (!soa->soa.rname) + return -ENOMEM; + + soa->soa.serial = 1; + soa->soa.refresh = 1; + soa->soa.retry = 1; + soa->soa.expire = 1; + soa->soa.minimum = ttl; + + return dns_answer_add(a, soa, 0, DNS_ANSWER_AUTHENTICATED); +} + +int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) { + DnsAnswerFlags flags = 0, i_flags; + DnsResourceRecord *i; + bool found = false; + int r; + + assert(key); + + DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { + r = dns_resource_key_match_rr(key, i, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + + if (!ret_flags) + return 1; + + if (found) + flags &= i_flags; + else { + flags = i_flags; + found = true; + } + } + + if (ret_flags) + *ret_flags = flags; + + return found; +} + +int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr, DnsAnswerFlags *ret_flags) { + DnsAnswerFlags flags = 0, i_flags; + DnsResourceRecord *i; + bool found = false; + int r; + + assert(rr); + + DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { + r = dns_resource_record_equal(i, rr); + if (r < 0) + return r; + if (r == 0) + continue; + + if (!ret_flags) + return 1; + + if (found) + flags &= i_flags; + else { + flags = i_flags; + found = true; + } + } + + if (ret_flags) + *ret_flags = flags; + + return found; +} + +int dns_answer_contains_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) { + DnsAnswerFlags flags = 0, i_flags; + DnsResourceRecord *i; + bool found = false; + int r; + + assert(key); + + DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { + r = dns_resource_key_equal(i->key, key); + if (r < 0) + return r; + if (r == 0) + continue; + + if (!ret_flags) + return true; + + if (found) + flags &= i_flags; + else { + flags = i_flags; + found = true; + } + } + + if (ret_flags) + *ret_flags = flags; + + return found; +} + +int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a) { + DnsResourceRecord *i; + + DNS_ANSWER_FOREACH(i, a) { + if (IN_SET(i->key->type, DNS_TYPE_NSEC, DNS_TYPE_NSEC3)) + return true; + } + + return false; +} + +int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone) { + DnsResourceRecord *rr; + int r; + + /* Checks whether the specified answer contains at least one NSEC3 RR in the specified zone */ + + DNS_ANSWER_FOREACH(rr, answer) { + const char *p; + + if (rr->key->type != DNS_TYPE_NSEC3) + continue; + + p = dns_resource_key_name(rr->key); + r = dns_name_parent(&p); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dns_name_equal(p, zone); + if (r != 0) + return r; + } + + return false; +} + +int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) { + DnsResourceRecord *rr, *soa = NULL; + DnsAnswerFlags rr_flags, soa_flags = 0; + int r; + + assert(key); + + /* For a SOA record we can never find a matching SOA record */ + if (key->type == DNS_TYPE_SOA) + return 0; + + DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) { + r = dns_resource_key_match_soa(key, rr->key); + if (r < 0) + return r; + if (r > 0) { + + if (soa) { + r = dns_name_endswith(dns_resource_key_name(rr->key), dns_resource_key_name(soa->key)); + if (r < 0) + return r; + if (r > 0) + continue; + } + + soa = rr; + soa_flags = rr_flags; + } + } + + if (!soa) + return 0; + + if (ret) + *ret = soa; + if (flags) + *flags = soa_flags; + + return 1; +} + +int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) { + DnsResourceRecord *rr; + DnsAnswerFlags rr_flags; + int r; + + assert(key); + + /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */ + if (!dns_type_may_redirect(key->type)) + return 0; + + DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) { + r = dns_resource_key_match_cname_or_dname(key, rr->key, NULL); + if (r < 0) + return r; + if (r > 0) { + if (ret) + *ret = rr; + if (flags) + *flags = rr_flags; + return 1; + } + } + + return 0; +} + +int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret) { + _cleanup_(dns_answer_unrefp) DnsAnswer *k = NULL; + int r; + + assert(ret); + + if (dns_answer_size(a) <= 0) { + *ret = dns_answer_ref(b); + return 0; + } + + if (dns_answer_size(b) <= 0) { + *ret = dns_answer_ref(a); + return 0; + } + + k = dns_answer_new(a->n_rrs + b->n_rrs); + if (!k) + return -ENOMEM; + + r = dns_answer_add_raw_all(k, a); + if (r < 0) + return r; + + r = dns_answer_add_all(k, b); + if (r < 0) + return r; + + *ret = k; + k = NULL; + + return 0; +} + +int dns_answer_extend(DnsAnswer **a, DnsAnswer *b) { + DnsAnswer *merged; + int r; + + assert(a); + + r = dns_answer_merge(*a, b, &merged); + if (r < 0) + return r; + + dns_answer_unref(*a); + *a = merged; + + return 0; +} + +int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) { + bool found = false, other = false; + DnsResourceRecord *rr; + unsigned i; + int r; + + assert(a); + assert(key); + + /* Remove all entries matching the specified key from *a */ + + DNS_ANSWER_FOREACH(rr, *a) { + r = dns_resource_key_equal(rr->key, key); + if (r < 0) + return r; + if (r > 0) + found = true; + else + other = true; + + if (found && other) + break; + } + + if (!found) + return 0; + + if (!other) { + *a = dns_answer_unref(*a); /* Return NULL for the empty answer */ + return 1; + } + + if ((*a)->n_ref > 1) { + _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; + DnsAnswerFlags flags; + int ifindex; + + copy = dns_answer_new((*a)->n_rrs); + if (!copy) + return -ENOMEM; + + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) { + r = dns_resource_key_equal(rr->key, key); + if (r < 0) + return r; + if (r > 0) + continue; + + r = dns_answer_add_raw(copy, rr, ifindex, flags); + if (r < 0) + return r; + } + + dns_answer_unref(*a); + *a = copy; + copy = NULL; + + return 1; + } + + /* Only a single reference, edit in-place */ + + i = 0; + for (;;) { + if (i >= (*a)->n_rrs) + break; + + r = dns_resource_key_equal((*a)->items[i].rr->key, key); + if (r < 0) + return r; + if (r > 0) { + /* Kill this entry */ + + dns_resource_record_unref((*a)->items[i].rr); + memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1)); + (*a)->n_rrs--; + continue; + + } else + /* Keep this entry */ + i++; + } + + return 1; +} + +int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rm) { + bool found = false, other = false; + DnsResourceRecord *rr; + unsigned i; + int r; + + assert(a); + assert(rm); + + /* Remove all entries matching the specified RR from *a */ + + DNS_ANSWER_FOREACH(rr, *a) { + r = dns_resource_record_equal(rr, rm); + if (r < 0) + return r; + if (r > 0) + found = true; + else + other = true; + + if (found && other) + break; + } + + if (!found) + return 0; + + if (!other) { + *a = dns_answer_unref(*a); /* Return NULL for the empty answer */ + return 1; + } + + if ((*a)->n_ref > 1) { + _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; + DnsAnswerFlags flags; + int ifindex; + + copy = dns_answer_new((*a)->n_rrs); + if (!copy) + return -ENOMEM; + + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) { + r = dns_resource_record_equal(rr, rm); + if (r < 0) + return r; + if (r > 0) + continue; + + r = dns_answer_add_raw(copy, rr, ifindex, flags); + if (r < 0) + return r; + } + + dns_answer_unref(*a); + *a = copy; + copy = NULL; + + return 1; + } + + /* Only a single reference, edit in-place */ + + i = 0; + for (;;) { + if (i >= (*a)->n_rrs) + break; + + r = dns_resource_record_equal((*a)->items[i].rr, rm); + if (r < 0) + return r; + if (r > 0) { + /* Kill this entry */ + + dns_resource_record_unref((*a)->items[i].rr); + memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1)); + (*a)->n_rrs--; + continue; + + } else + /* Keep this entry */ + i++; + } + + return 1; +} + +int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags) { + DnsResourceRecord *rr_source; + int ifindex_source, r; + DnsAnswerFlags flags_source; + + assert(a); + assert(key); + + /* Copy all RRs matching the specified key from source into *a */ + + DNS_ANSWER_FOREACH_FULL(rr_source, ifindex_source, flags_source, source) { + + r = dns_resource_key_equal(rr_source->key, key); + if (r < 0) + return r; + if (r == 0) + continue; + + /* Make space for at least one entry */ + r = dns_answer_reserve_or_clone(a, 1); + if (r < 0) + return r; + + r = dns_answer_add(*a, rr_source, ifindex_source, flags_source|or_flags); + if (r < 0) + return r; + } + + return 0; +} + +int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags) { + int r; + + assert(to); + assert(from); + assert(key); + + r = dns_answer_copy_by_key(to, *from, key, or_flags); + if (r < 0) + return r; + + return dns_answer_remove_by_key(from, key); +} + +void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local) { + DnsAnswerItem *items; + unsigned i, start, end; + + if (!a) + return; + + if (a->n_rrs <= 1) + return; + + start = 0; + end = a->n_rrs-1; + + /* RFC 4795, Section 2.6 suggests we should order entries + * depending on whether the sender is a link-local address. */ + + items = newa(DnsAnswerItem, a->n_rrs); + for (i = 0; i < a->n_rrs; i++) { + + if (a->items[i].rr->key->class == DNS_CLASS_IN && + ((a->items[i].rr->key->type == DNS_TYPE_A && in_addr_is_link_local(AF_INET, (union in_addr_union*) &a->items[i].rr->a.in_addr) != prefer_link_local) || + (a->items[i].rr->key->type == DNS_TYPE_AAAA && in_addr_is_link_local(AF_INET6, (union in_addr_union*) &a->items[i].rr->aaaa.in6_addr) != prefer_link_local))) + /* Order address records that are are not preferred to the end of the array */ + items[end--] = a->items[i]; + else + /* Order all other records to the beginning of the array */ + items[start++] = a->items[i]; + } + + assert(start == end+1); + memcpy(a->items, items, sizeof(DnsAnswerItem) * a->n_rrs); +} + +int dns_answer_reserve(DnsAnswer **a, unsigned n_free) { + DnsAnswer *n; + + assert(a); + + if (n_free <= 0) + return 0; + + if (*a) { + unsigned ns; + + if ((*a)->n_ref > 1) + return -EBUSY; + + ns = (*a)->n_rrs + n_free; + + if ((*a)->n_allocated >= ns) + return 0; + + /* Allocate more than we need */ + ns *= 2; + + n = realloc(*a, offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * ns); + if (!n) + return -ENOMEM; + + n->n_allocated = ns; + } else { + n = dns_answer_new(n_free); + if (!n) + return -ENOMEM; + } + + *a = n; + return 0; +} + +int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free) { + _cleanup_(dns_answer_unrefp) DnsAnswer *n = NULL; + int r; + + assert(a); + + /* Tries to extend the DnsAnswer object. And if that's not + * possible, since we are not the sole owner, then allocate a + * new, appropriately sized one. Either way, after this call + * the object will only have a single reference, and has room + * for at least the specified number of RRs. */ + + r = dns_answer_reserve(a, n_free); + if (r != -EBUSY) + return r; + + assert(*a); + + n = dns_answer_new(((*a)->n_rrs + n_free) * 2); + if (!n) + return -ENOMEM; + + r = dns_answer_add_raw_all(n, *a); + if (r < 0) + return r; + + dns_answer_unref(*a); + *a = n; + n = NULL; + + return 0; +} + +void dns_answer_dump(DnsAnswer *answer, FILE *f) { + DnsResourceRecord *rr; + DnsAnswerFlags flags; + int ifindex; + + if (!f) + f = stdout; + + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) { + const char *t; + + fputc('\t', f); + + t = dns_resource_record_to_string(rr); + if (!t) { + log_oom(); + continue; + } + + fputs(t, f); + + if (ifindex != 0 || flags & (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE|DNS_ANSWER_SHARED_OWNER)) + fputs("\t;", f); + + if (ifindex != 0) + printf(" ifindex=%i", ifindex); + if (flags & DNS_ANSWER_AUTHENTICATED) + fputs(" authenticated", f); + if (flags & DNS_ANSWER_CACHEABLE) + fputs(" cachable", f); + if (flags & DNS_ANSWER_SHARED_OWNER) + fputs(" shared-owner", f); + + fputc('\n', f); + } +} + +bool dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname) { + DnsResourceRecord *rr; + int r; + + assert(cname); + + /* Checks whether the answer contains a DNAME record that indicates that the specified CNAME record is + * synthesized from it */ + + if (cname->key->type != DNS_TYPE_CNAME) + return 0; + + DNS_ANSWER_FOREACH(rr, a) { + _cleanup_free_ char *n = NULL; + + if (rr->key->type != DNS_TYPE_DNAME) + continue; + if (rr->key->class != cname->key->class) + continue; + + r = dns_name_change_suffix(cname->cname.name, rr->dname.name, dns_resource_key_name(rr->key), &n); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dns_name_equal(n, dns_resource_key_name(cname->key)); + if (r < 0) + return r; + if (r > 0) + return 1; + + } + + return 0; +} diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-answer.h b/src/grp-resolve/systemd-resolved/resolved-dns-answer.h new file mode 100644 index 0000000000..0679c610f5 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-answer.h @@ -0,0 +1,143 @@ +#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 . +***/ + +typedef struct DnsAnswer DnsAnswer; +typedef struct DnsAnswerItem DnsAnswerItem; + +#include "macro.h" +#include "resolved-dns-rr.h" + +/* A simple array of resource records. We keep track of the + * originating ifindex for each RR where that makes sense, so that we + * can qualify A and AAAA RRs referring to a local link with the + * right ifindex. + * + * Note that we usually encode the empty DnsAnswer object as a simple NULL. */ + +typedef enum DnsAnswerFlags { + DNS_ANSWER_AUTHENTICATED = 1, /* Item has been authenticated */ + DNS_ANSWER_CACHEABLE = 2, /* Item is subject to caching */ + DNS_ANSWER_SHARED_OWNER = 4, /* For mDNS: RRset may be owner by multiple peers */ +} DnsAnswerFlags; + +struct DnsAnswerItem { + DnsResourceRecord *rr; + int ifindex; + DnsAnswerFlags flags; +}; + +struct DnsAnswer { + unsigned n_ref; + unsigned n_rrs, n_allocated; + DnsAnswerItem items[0]; +}; + +DnsAnswer *dns_answer_new(unsigned n); +DnsAnswer *dns_answer_ref(DnsAnswer *a); +DnsAnswer *dns_answer_unref(DnsAnswer *a); + +int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags); +int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags); +int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl); + +int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags); +int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr, DnsAnswerFlags *combined_flags); +int dns_answer_contains_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags); +int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a); +int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone); + +int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); +int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); + +int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret); +int dns_answer_extend(DnsAnswer **a, DnsAnswer *b); + +void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local); + +int dns_answer_reserve(DnsAnswer **a, unsigned n_free); +int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free); + +int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key); +int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rr); + +int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags); +int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags); + +bool dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname); + +static inline unsigned dns_answer_size(DnsAnswer *a) { + return a ? a->n_rrs : 0; +} + +void dns_answer_dump(DnsAnswer *answer, FILE *f); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); + +#define _DNS_ANSWER_FOREACH(q, kk, a) \ + for (unsigned UNIQ_T(i, q) = ({ \ + (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ + 0; \ + }); \ + (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ + UNIQ_T(i, q)++, (kk) = (UNIQ_T(i, q) < (a)->n_rrs ? (a)->items[UNIQ_T(i, q)].rr : NULL)) + +#define DNS_ANSWER_FOREACH(kk, a) _DNS_ANSWER_FOREACH(UNIQ, kk, a) + +#define _DNS_ANSWER_FOREACH_IFINDEX(q, kk, ifi, a) \ + for (unsigned UNIQ_T(i, q) = ({ \ + (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ + (ifi) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \ + 0; \ + }); \ + (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ + UNIQ_T(i, q)++, \ + (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ + (ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0)) + +#define DNS_ANSWER_FOREACH_IFINDEX(kk, ifindex, a) _DNS_ANSWER_FOREACH_IFINDEX(UNIQ, kk, ifindex, a) + +#define _DNS_ANSWER_FOREACH_FLAGS(q, kk, fl, a) \ + for (unsigned UNIQ_T(i, q) = ({ \ + (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ + (fl) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].flags : 0; \ + 0; \ + }); \ + (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ + UNIQ_T(i, q)++, \ + (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ + (fl) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].flags : 0)) + +#define DNS_ANSWER_FOREACH_FLAGS(kk, flags, a) _DNS_ANSWER_FOREACH_FLAGS(UNIQ, kk, flags, a) + +#define _DNS_ANSWER_FOREACH_FULL(q, kk, ifi, fl, a) \ + for (unsigned UNIQ_T(i, q) = ({ \ + (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ + (ifi) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \ + (fl) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].flags : 0; \ + 0; \ + }); \ + (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ + UNIQ_T(i, q)++, \ + (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ + (ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0), \ + (fl) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].flags : 0)) + +#define DNS_ANSWER_FOREACH_FULL(kk, ifindex, flags, a) _DNS_ANSWER_FOREACH_FULL(UNIQ, kk, ifindex, flags, a) diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-cache.c b/src/grp-resolve/systemd-resolved/resolved-dns-cache.c new file mode 100644 index 0000000000..77c42d7aad --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-cache.c @@ -0,0 +1,1050 @@ +/*** + 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 "af-list.h" +#include "alloc-util.h" +#include "dns-domain.h" +#include "resolved-dns-answer.h" +#include "resolved-dns-cache.h" +#include "resolved-dns-packet.h" +#include "string-util.h" + +/* Never cache more than 4K entries. RFC 1536, Section 5 suggests to + * leave DNS caches unbounded, but that's crazy. */ +#define CACHE_MAX 4096 + +/* We never keep any item longer than 2h in our cache */ +#define CACHE_TTL_MAX_USEC (2 * USEC_PER_HOUR) + +typedef enum DnsCacheItemType DnsCacheItemType; +typedef struct DnsCacheItem DnsCacheItem; + +enum DnsCacheItemType { + DNS_CACHE_POSITIVE, + DNS_CACHE_NODATA, + DNS_CACHE_NXDOMAIN, +}; + +struct DnsCacheItem { + DnsCacheItemType type; + DnsResourceKey *key; + DnsResourceRecord *rr; + + usec_t until; + bool authenticated:1; + bool shared_owner:1; + + int ifindex; + int owner_family; + union in_addr_union owner_address; + + unsigned prioq_idx; + LIST_FIELDS(DnsCacheItem, by_key); +}; + +static void dns_cache_item_free(DnsCacheItem *i) { + if (!i) + return; + + dns_resource_record_unref(i->rr); + dns_resource_key_unref(i->key); + free(i); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem*, dns_cache_item_free); + +static void dns_cache_item_unlink_and_free(DnsCache *c, DnsCacheItem *i) { + DnsCacheItem *first; + + assert(c); + + if (!i) + return; + + first = hashmap_get(c->by_key, i->key); + LIST_REMOVE(by_key, first, i); + + if (first) + assert_se(hashmap_replace(c->by_key, first->key, first) >= 0); + else + hashmap_remove(c->by_key, i->key); + + prioq_remove(c->by_expiry, i, &i->prioq_idx); + + dns_cache_item_free(i); +} + +static bool dns_cache_remove_by_rr(DnsCache *c, DnsResourceRecord *rr) { + DnsCacheItem *first, *i; + int r; + + first = hashmap_get(c->by_key, rr->key); + LIST_FOREACH(by_key, i, first) { + r = dns_resource_record_equal(i->rr, rr); + if (r < 0) + return r; + if (r > 0) { + dns_cache_item_unlink_and_free(c, i); + return true; + } + } + + return false; +} + +static bool dns_cache_remove_by_key(DnsCache *c, DnsResourceKey *key) { + DnsCacheItem *first, *i, *n; + + assert(c); + assert(key); + + first = hashmap_remove(c->by_key, key); + if (!first) + return false; + + LIST_FOREACH_SAFE(by_key, i, n, first) { + prioq_remove(c->by_expiry, i, &i->prioq_idx); + dns_cache_item_free(i); + } + + return true; +} + +void dns_cache_flush(DnsCache *c) { + DnsResourceKey *key; + + assert(c); + + while ((key = hashmap_first_key(c->by_key))) + dns_cache_remove_by_key(c, key); + + assert(hashmap_size(c->by_key) == 0); + assert(prioq_size(c->by_expiry) == 0); + + c->by_key = hashmap_free(c->by_key); + c->by_expiry = prioq_free(c->by_expiry); +} + +static void dns_cache_make_space(DnsCache *c, unsigned add) { + assert(c); + + if (add <= 0) + return; + + /* Makes space for n new entries. Note that we actually allow + * the cache to grow beyond CACHE_MAX, but only when we shall + * add more RRs to the cache than CACHE_MAX at once. In that + * case the cache will be emptied completely otherwise. */ + + for (;;) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + DnsCacheItem *i; + + if (prioq_size(c->by_expiry) <= 0) + break; + + if (prioq_size(c->by_expiry) + add < CACHE_MAX) + break; + + i = prioq_peek(c->by_expiry); + assert(i); + + /* Take an extra reference to the key so that it + * doesn't go away in the middle of the remove call */ + key = dns_resource_key_ref(i->key); + dns_cache_remove_by_key(c, key); + } +} + +void dns_cache_prune(DnsCache *c) { + usec_t t = 0; + + assert(c); + + /* Remove all entries that are past their TTL */ + + for (;;) { + DnsCacheItem *i; + char key_str[DNS_RESOURCE_KEY_STRING_MAX]; + + i = prioq_peek(c->by_expiry); + if (!i) + break; + + if (t <= 0) + t = now(clock_boottime_or_monotonic()); + + if (i->until > t) + break; + + /* Depending whether this is an mDNS shared entry + * either remove only this one RR or the whole RRset */ + log_debug("Removing %scache entry for %s (expired "USEC_FMT"s ago)", + i->shared_owner ? "shared " : "", + dns_resource_key_to_string(i->key, key_str, sizeof key_str), + (t - i->until) / USEC_PER_SEC); + + if (i->shared_owner) + dns_cache_item_unlink_and_free(c, i); + else { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + /* Take an extra reference to the key so that it + * doesn't go away in the middle of the remove call */ + key = dns_resource_key_ref(i->key); + dns_cache_remove_by_key(c, key); + } + } +} + +static int dns_cache_item_prioq_compare_func(const void *a, const void *b) { + const DnsCacheItem *x = a, *y = b; + + if (x->until < y->until) + return -1; + if (x->until > y->until) + return 1; + return 0; +} + +static int dns_cache_init(DnsCache *c) { + int r; + + assert(c); + + r = prioq_ensure_allocated(&c->by_expiry, dns_cache_item_prioq_compare_func); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&c->by_key, &dns_resource_key_hash_ops); + if (r < 0) + return r; + + return r; +} + +static int dns_cache_link_item(DnsCache *c, DnsCacheItem *i) { + DnsCacheItem *first; + int r; + + assert(c); + assert(i); + + r = prioq_put(c->by_expiry, i, &i->prioq_idx); + if (r < 0) + return r; + + first = hashmap_get(c->by_key, i->key); + if (first) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; + + /* Keep a reference to the original key, while we manipulate the list. */ + k = dns_resource_key_ref(first->key); + + /* Now, try to reduce the number of keys we keep */ + dns_resource_key_reduce(&first->key, &i->key); + + if (first->rr) + dns_resource_key_reduce(&first->rr->key, &i->key); + if (i->rr) + dns_resource_key_reduce(&i->rr->key, &i->key); + + LIST_PREPEND(by_key, first, i); + assert_se(hashmap_replace(c->by_key, first->key, first) >= 0); + } else { + r = hashmap_put(c->by_key, i->key, i); + if (r < 0) { + prioq_remove(c->by_expiry, i, &i->prioq_idx); + return r; + } + } + + return 0; +} + +static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) { + DnsCacheItem *i; + + assert(c); + assert(rr); + + LIST_FOREACH(by_key, i, hashmap_get(c->by_key, rr->key)) + if (i->rr && dns_resource_record_equal(i->rr, rr) > 0) + return i; + + return NULL; +} + +static usec_t calculate_until(DnsResourceRecord *rr, uint32_t nsec_ttl, usec_t timestamp, bool use_soa_minimum) { + uint32_t ttl; + usec_t u; + + assert(rr); + + ttl = MIN(rr->ttl, nsec_ttl); + if (rr->key->type == DNS_TYPE_SOA && use_soa_minimum) { + /* If this is a SOA RR, and it is requested, clamp to + * the SOA's minimum field. This is used when we do + * negative caching, to determine the TTL for the + * negative caching entry. See RFC 2308, Section + * 5. */ + + if (ttl > rr->soa.minimum) + ttl = rr->soa.minimum; + } + + u = ttl * USEC_PER_SEC; + if (u > CACHE_TTL_MAX_USEC) + u = CACHE_TTL_MAX_USEC; + + if (rr->expiry != USEC_INFINITY) { + usec_t left; + + /* Make use of the DNSSEC RRSIG expiry info, if we + * have it */ + + left = LESS_BY(rr->expiry, now(CLOCK_REALTIME)); + if (u > left) + u = left; + } + + return timestamp + u; +} + +static void dns_cache_item_update_positive( + DnsCache *c, + DnsCacheItem *i, + DnsResourceRecord *rr, + bool authenticated, + bool shared_owner, + usec_t timestamp, + int ifindex, + int owner_family, + const union in_addr_union *owner_address) { + + assert(c); + assert(i); + assert(rr); + assert(owner_address); + + i->type = DNS_CACHE_POSITIVE; + + if (!i->by_key_prev) + /* We are the first item in the list, we need to + * update the key used in the hashmap */ + + assert_se(hashmap_replace(c->by_key, rr->key, i) >= 0); + + dns_resource_record_ref(rr); + dns_resource_record_unref(i->rr); + i->rr = rr; + + dns_resource_key_unref(i->key); + i->key = dns_resource_key_ref(rr->key); + + i->until = calculate_until(rr, (uint32_t) -1, timestamp, false); + i->authenticated = authenticated; + i->shared_owner = shared_owner; + + i->ifindex = ifindex; + + i->owner_family = owner_family; + i->owner_address = *owner_address; + + prioq_reshuffle(c->by_expiry, i, &i->prioq_idx); +} + +static int dns_cache_put_positive( + DnsCache *c, + DnsResourceRecord *rr, + bool authenticated, + bool shared_owner, + usec_t timestamp, + int ifindex, + int owner_family, + const union in_addr_union *owner_address) { + + _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; + DnsCacheItem *existing; + char key_str[DNS_RESOURCE_KEY_STRING_MAX], ifname[IF_NAMESIZE]; + int r, k; + + assert(c); + assert(rr); + assert(owner_address); + + /* Never cache pseudo RRs */ + if (dns_class_is_pseudo(rr->key->class)) + return 0; + if (dns_type_is_pseudo(rr->key->type)) + return 0; + + /* New TTL is 0? Delete this specific entry... */ + if (rr->ttl <= 0) { + k = dns_cache_remove_by_rr(c, rr); + log_debug("%s: %s", + k > 0 ? "Removed zero TTL entry from cache" : "Not caching zero TTL cache entry", + dns_resource_key_to_string(rr->key, key_str, sizeof key_str)); + return 0; + } + + /* Entry exists already? Update TTL, timestamp and owner*/ + existing = dns_cache_get(c, rr); + if (existing) { + dns_cache_item_update_positive( + c, + existing, + rr, + authenticated, + shared_owner, + timestamp, + ifindex, + owner_family, + owner_address); + return 0; + } + + /* Otherwise, add the new RR */ + r = dns_cache_init(c); + if (r < 0) + return r; + + dns_cache_make_space(c, 1); + + i = new0(DnsCacheItem, 1); + if (!i) + return -ENOMEM; + + i->type = DNS_CACHE_POSITIVE; + i->key = dns_resource_key_ref(rr->key); + i->rr = dns_resource_record_ref(rr); + i->until = calculate_until(rr, (uint32_t) -1, timestamp, false); + i->authenticated = authenticated; + i->shared_owner = shared_owner; + i->ifindex = ifindex; + i->owner_family = owner_family; + i->owner_address = *owner_address; + i->prioq_idx = PRIOQ_IDX_NULL; + + r = dns_cache_link_item(c, i); + if (r < 0) + return r; + + if (log_get_max_level() >= LOG_DEBUG) { + _cleanup_free_ char *t = NULL; + + (void) in_addr_to_string(i->owner_family, &i->owner_address, &t); + + log_debug("Added positive %s%s cache entry for %s "USEC_FMT"s on %s/%s/%s", + i->authenticated ? "authenticated" : "unauthenticated", + i->shared_owner ? " shared" : "", + dns_resource_key_to_string(i->key, key_str, sizeof key_str), + (i->until - timestamp) / USEC_PER_SEC, + i->ifindex == 0 ? "*" : strna(if_indextoname(i->ifindex, ifname)), + af_to_name_short(i->owner_family), + strna(t)); + } + + i = NULL; + return 0; +} + +static int dns_cache_put_negative( + DnsCache *c, + DnsResourceKey *key, + int rcode, + bool authenticated, + uint32_t nsec_ttl, + usec_t timestamp, + DnsResourceRecord *soa, + int owner_family, + const union in_addr_union *owner_address) { + + _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; + char key_str[DNS_RESOURCE_KEY_STRING_MAX]; + int r; + + assert(c); + assert(key); + assert(soa); + assert(owner_address); + + /* Never cache pseudo RR keys. DNS_TYPE_ANY is particularly + * important to filter out as we use this as a pseudo-type for + * NXDOMAIN entries */ + if (dns_class_is_pseudo(key->class)) + return 0; + if (dns_type_is_pseudo(key->type)) + return 0; + + if (nsec_ttl <= 0 || soa->soa.minimum <= 0 || soa->ttl <= 0) { + log_debug("Not caching negative entry with zero SOA/NSEC/NSEC3 TTL: %s", + dns_resource_key_to_string(key, key_str, sizeof key_str)); + return 0; + } + + if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) + return 0; + + r = dns_cache_init(c); + if (r < 0) + return r; + + dns_cache_make_space(c, 1); + + i = new0(DnsCacheItem, 1); + if (!i) + return -ENOMEM; + + i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN; + i->until = calculate_until(soa, nsec_ttl, timestamp, true); + i->authenticated = authenticated; + i->owner_family = owner_family; + i->owner_address = *owner_address; + i->prioq_idx = PRIOQ_IDX_NULL; + + if (i->type == DNS_CACHE_NXDOMAIN) { + /* NXDOMAIN entries should apply equally to all types, so we use ANY as + * a pseudo type for this purpose here. */ + i->key = dns_resource_key_new(key->class, DNS_TYPE_ANY, dns_resource_key_name(key)); + if (!i->key) + return -ENOMEM; + + /* Make sure to remove any previous entry for this + * specific ANY key. (For non-ANY keys the cache data + * is already cleared by the caller.) Note that we + * don't bother removing positive or NODATA cache + * items in this case, because it would either be slow + * or require explicit indexing by name */ + dns_cache_remove_by_key(c, key); + } else + i->key = dns_resource_key_ref(key); + + r = dns_cache_link_item(c, i); + if (r < 0) + return r; + + log_debug("Added %s cache entry for %s "USEC_FMT"s", + i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", + dns_resource_key_to_string(i->key, key_str, sizeof key_str), + (i->until - timestamp) / USEC_PER_SEC); + + i = NULL; + return 0; +} + +static void dns_cache_remove_previous( + DnsCache *c, + DnsResourceKey *key, + DnsAnswer *answer) { + + DnsResourceRecord *rr; + DnsAnswerFlags flags; + + assert(c); + + /* First, if we were passed a key (i.e. on LLMNR/DNS, but + * not on mDNS), delete all matching old RRs, so that we only + * keep complete by_key in place. */ + if (key) + dns_cache_remove_by_key(c, key); + + /* Second, flush all entries matching the answer, unless this + * is an RR that is explicitly marked to be "shared" between + * peers (i.e. mDNS RRs without the flush-cache bit set). */ + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + if ((flags & DNS_ANSWER_CACHEABLE) == 0) + continue; + + if (flags & DNS_ANSWER_SHARED_OWNER) + continue; + + dns_cache_remove_by_key(c, rr->key); + } +} + +static bool rr_eligible(DnsResourceRecord *rr) { + assert(rr); + + /* When we see an NSEC/NSEC3 RR, we'll only cache it if it is from the lower zone, not the upper zone, since + * that's where the interesting bits are (with exception of DS RRs). Of course, this way we cannot derive DS + * existence from any cached NSEC/NSEC3, but that should be fine. */ + + switch (rr->key->type) { + + case DNS_TYPE_NSEC: + return !bitmap_isset(rr->nsec.types, DNS_TYPE_NS) || + bitmap_isset(rr->nsec.types, DNS_TYPE_SOA); + + case DNS_TYPE_NSEC3: + return !bitmap_isset(rr->nsec3.types, DNS_TYPE_NS) || + bitmap_isset(rr->nsec3.types, DNS_TYPE_SOA); + + default: + return true; + } +} + +int dns_cache_put( + DnsCache *c, + DnsResourceKey *key, + int rcode, + DnsAnswer *answer, + bool authenticated, + uint32_t nsec_ttl, + usec_t timestamp, + int owner_family, + const union in_addr_union *owner_address) { + + DnsResourceRecord *soa = NULL, *rr; + DnsAnswerFlags flags; + unsigned cache_keys; + int r, ifindex; + + assert(c); + assert(owner_address); + + dns_cache_remove_previous(c, key, answer); + + if (dns_answer_size(answer) <= 0) { + char key_str[DNS_RESOURCE_KEY_STRING_MAX]; + + log_debug("Not caching negative entry without a SOA record: %s", + dns_resource_key_to_string(key, key_str, sizeof key_str)); + return 0; + } + + /* We only care for positive replies and NXDOMAINs, on all + * other replies we will simply flush the respective entries, + * and that's it */ + if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) + return 0; + + cache_keys = dns_answer_size(answer); + if (key) + cache_keys++; + + /* Make some space for our new entries */ + dns_cache_make_space(c, cache_keys); + + if (timestamp <= 0) + timestamp = now(clock_boottime_or_monotonic()); + + /* Second, add in positive entries for all contained RRs */ + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) { + if ((flags & DNS_ANSWER_CACHEABLE) == 0) + continue; + + r = rr_eligible(rr); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dns_cache_put_positive( + c, + rr, + flags & DNS_ANSWER_AUTHENTICATED, + flags & DNS_ANSWER_SHARED_OWNER, + timestamp, + ifindex, + owner_family, owner_address); + if (r < 0) + goto fail; + } + + if (!key) /* mDNS doesn't know negative caching, really */ + return 0; + + /* Third, add in negative entries if the key has no RR */ + r = dns_answer_match_key(answer, key, NULL); + if (r < 0) + goto fail; + if (r > 0) + return 0; + + /* But not if it has a matching CNAME/DNAME (the negative + * caching will be done on the canonical name, not on the + * alias) */ + r = dns_answer_find_cname_or_dname(answer, key, NULL, NULL); + if (r < 0) + goto fail; + if (r > 0) + return 0; + + /* See https://tools.ietf.org/html/rfc2308, which say that a + * matching SOA record in the packet is used to to enable + * negative caching. */ + r = dns_answer_find_soa(answer, key, &soa, &flags); + if (r < 0) + goto fail; + if (r == 0) + return 0; + + /* Refuse using the SOA data if it is unsigned, but the key is + * signed */ + if (authenticated && (flags & DNS_ANSWER_AUTHENTICATED) == 0) + return 0; + + r = dns_cache_put_negative( + c, + key, + rcode, + authenticated, + nsec_ttl, + timestamp, + soa, + owner_family, owner_address); + if (r < 0) + goto fail; + + return 0; + +fail: + /* Adding all RRs failed. Let's clean up what we already + * added, just in case */ + + if (key) + dns_cache_remove_by_key(c, key); + + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + if ((flags & DNS_ANSWER_CACHEABLE) == 0) + continue; + + dns_cache_remove_by_key(c, rr->key); + } + + return r; +} + +static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, DnsResourceKey *k) { + DnsCacheItem *i; + const char *n; + int r; + + assert(c); + assert(k); + + /* If we hit some OOM error, or suchlike, we don't care too + * much, after all this is just a cache */ + + i = hashmap_get(c->by_key, k); + if (i) + return i; + + n = dns_resource_key_name(k); + + /* Check if we have an NXDOMAIN cache item for the name, notice that we use + * the pseudo-type ANY for NXDOMAIN cache items. */ + i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_ANY, n)); + if (i && i->type == DNS_CACHE_NXDOMAIN) + return i; + + if (dns_type_may_redirect(k->type)) { + /* Check if we have a CNAME record instead */ + i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_CNAME, n)); + if (i) + return i; + + /* OK, let's look for cached DNAME records. */ + for (;;) { + if (isempty(n)) + return NULL; + + i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_DNAME, n)); + if (i) + return i; + + /* Jump one label ahead */ + r = dns_name_parent(&n); + if (r <= 0) + return NULL; + } + } + + if (k->type != DNS_TYPE_NSEC) { + /* Check if we have an NSEC record instead for the name. */ + i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_NSEC, n)); + if (i) + return i; + } + + return NULL; +} + +int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **ret, bool *authenticated) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + char key_str[DNS_RESOURCE_KEY_STRING_MAX]; + unsigned n = 0; + int r; + bool nxdomain = false; + DnsCacheItem *j, *first, *nsec = NULL; + bool have_authenticated = false, have_non_authenticated = false; + + assert(c); + assert(key); + assert(rcode); + assert(ret); + assert(authenticated); + + if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { + /* If we have ANY lookups we don't use the cache, so + * that the caller refreshes via the network. */ + + log_debug("Ignoring cache for ANY lookup: %s", + dns_resource_key_to_string(key, key_str, sizeof key_str)); + + c->n_miss++; + + *ret = NULL; + *rcode = DNS_RCODE_SUCCESS; + return 0; + } + + first = dns_cache_get_by_key_follow_cname_dname_nsec(c, key); + if (!first) { + /* If one question cannot be answered we need to refresh */ + + log_debug("Cache miss for %s", + dns_resource_key_to_string(key, key_str, sizeof key_str)); + + c->n_miss++; + + *ret = NULL; + *rcode = DNS_RCODE_SUCCESS; + return 0; + } + + LIST_FOREACH(by_key, j, first) { + if (j->rr) { + if (j->rr->key->type == DNS_TYPE_NSEC) + nsec = j; + + n++; + } else if (j->type == DNS_CACHE_NXDOMAIN) + nxdomain = true; + + if (j->authenticated) + have_authenticated = true; + else + have_non_authenticated = true; + } + + if (nsec && !IN_SET(key->type, DNS_TYPE_NSEC, DNS_TYPE_DS)) { + /* Note that we won't derive information for DS RRs from an NSEC, because we only cache NSEC RRs from + * the lower-zone of a zone cut, but the DS RRs are on the upper zone. */ + + log_debug("NSEC NODATA cache hit for %s", + dns_resource_key_to_string(key, key_str, sizeof key_str)); + + /* We only found an NSEC record that matches our name. + * If it says the type doesn't exist report + * NODATA. Otherwise report a cache miss. */ + + *ret = NULL; + *rcode = DNS_RCODE_SUCCESS; + *authenticated = nsec->authenticated; + + if (!bitmap_isset(nsec->rr->nsec.types, key->type) && + !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) && + !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_DNAME)) { + c->n_hit++; + return 1; + } + + c->n_miss++; + return 0; + } + + log_debug("%s cache hit for %s", + n > 0 ? "Positive" : + nxdomain ? "NXDOMAIN" : "NODATA", + dns_resource_key_to_string(key, key_str, sizeof key_str)); + + if (n <= 0) { + c->n_hit++; + + *ret = NULL; + *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS; + *authenticated = have_authenticated && !have_non_authenticated; + return 1; + } + + answer = dns_answer_new(n); + if (!answer) + return -ENOMEM; + + LIST_FOREACH(by_key, j, first) { + if (!j->rr) + continue; + + r = dns_answer_add(answer, j->rr, j->ifindex, j->authenticated ? DNS_ANSWER_AUTHENTICATED : 0); + if (r < 0) + return r; + } + + c->n_hit++; + + *ret = answer; + *rcode = DNS_RCODE_SUCCESS; + *authenticated = have_authenticated && !have_non_authenticated; + answer = NULL; + + return n; +} + +int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) { + DnsCacheItem *i, *first; + bool same_owner = true; + + assert(cache); + assert(rr); + + dns_cache_prune(cache); + + /* See if there's a cache entry for the same key. If there + * isn't there's no conflict */ + first = hashmap_get(cache->by_key, rr->key); + if (!first) + return 0; + + /* See if the RR key is owned by the same owner, if so, there + * isn't a conflict either */ + LIST_FOREACH(by_key, i, first) { + if (i->owner_family != owner_family || + !in_addr_equal(owner_family, &i->owner_address, owner_address)) { + same_owner = false; + break; + } + } + if (same_owner) + return 0; + + /* See if there's the exact same RR in the cache. If yes, then + * there's no conflict. */ + if (dns_cache_get(cache, rr)) + return 0; + + /* There's a conflict */ + return 1; +} + +int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p) { + unsigned ancount = 0; + Iterator iterator; + DnsCacheItem *i; + int r; + + assert(cache); + assert(p); + + HASHMAP_FOREACH(i, cache->by_key, iterator) { + DnsCacheItem *j; + + LIST_FOREACH(by_key, j, i) { + if (!j->rr) + continue; + + if (!j->shared_owner) + continue; + + r = dns_packet_append_rr(p, j->rr, NULL, NULL); + if (r == -EMSGSIZE && p->protocol == DNS_PROTOCOL_MDNS) { + /* For mDNS, if we're unable to stuff all known answers into the given packet, + * allocate a new one, push the RR into that one and link it to the current one. + */ + + DNS_PACKET_HEADER(p)->ancount = htobe16(ancount); + ancount = 0; + + r = dns_packet_new_query(&p->more, p->protocol, 0, true); + if (r < 0) + return r; + + /* continue with new packet */ + p = p->more; + r = dns_packet_append_rr(p, j->rr, NULL, NULL); + } + + if (r < 0) + return r; + + ancount++; + } + } + + DNS_PACKET_HEADER(p)->ancount = htobe16(ancount); + + return 0; +} + +void dns_cache_dump(DnsCache *cache, FILE *f) { + Iterator iterator; + DnsCacheItem *i; + + if (!cache) + return; + + if (!f) + f = stdout; + + HASHMAP_FOREACH(i, cache->by_key, iterator) { + DnsCacheItem *j; + + LIST_FOREACH(by_key, j, i) { + + fputc('\t', f); + + if (j->rr) { + const char *t; + t = dns_resource_record_to_string(j->rr); + if (!t) { + log_oom(); + continue; + } + + fputs(t, f); + fputc('\n', f); + } else { + char key_str[DNS_RESOURCE_KEY_STRING_MAX]; + + fputs(dns_resource_key_to_string(j->key, key_str, sizeof key_str), f); + fputs(" -- ", f); + fputs(j->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", f); + fputc('\n', f); + } + } + } +} + +bool dns_cache_is_empty(DnsCache *cache) { + if (!cache) + return true; + + return hashmap_isempty(cache->by_key); +} + +unsigned dns_cache_size(DnsCache *cache) { + if (!cache) + return 0; + + return hashmap_size(cache->by_key); +} diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-cache.h b/src/grp-resolve/systemd-resolved/resolved-dns-cache.h new file mode 100644 index 0000000000..2293718e86 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-cache.h @@ -0,0 +1,52 @@ +#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 "hashmap.h" +#include "list.h" +#include "prioq.h" +#include "time-util.h" + +typedef struct DnsCache { + Hashmap *by_key; + Prioq *by_expiry; + unsigned n_hit; + unsigned n_miss; +} DnsCache; + +#include "resolved-dns-answer.h" +#include "resolved-dns-packet.h" +#include "resolved-dns-question.h" +#include "resolved-dns-rr.h" + +void dns_cache_flush(DnsCache *c); +void dns_cache_prune(DnsCache *c); + +int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, bool authenticated, uint32_t nsec_ttl, usec_t timestamp, int owner_family, const union in_addr_union *owner_address); +int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **answer, bool *authenticated); + +int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address); + +void dns_cache_dump(DnsCache *cache, FILE *f); +bool dns_cache_is_empty(DnsCache *cache); + +unsigned dns_cache_size(DnsCache *cache); + +int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p); diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-dnssec.c b/src/grp-resolve/systemd-resolved/resolved-dns-dnssec.c new file mode 100644 index 0000000000..a54aed3a63 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-dnssec.c @@ -0,0 +1,2199 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#ifdef HAVE_GCRYPT +#include +#endif + +#include "alloc-util.h" +#include "dns-domain.h" +#include "gcrypt-util.h" +#include "hexdecoct.h" +#include "resolved-dns-dnssec.h" +#include "resolved-dns-packet.h" +#include "string-table.h" + +#define VERIFY_RRS_MAX 256 +#define MAX_KEY_SIZE (32*1024) + +/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */ +#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE) + +/* Maximum number of NSEC3 iterations we'll do. RFC5155 says 2500 shall be the maximum useful value */ +#define NSEC3_ITERATIONS_MAX 2500 + +/* + * The DNSSEC Chain of trust: + * + * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone + * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree + * DS RRs are protected like normal RRs + * + * Example chain: + * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS + */ + +uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) { + const uint8_t *p; + uint32_t sum, f; + size_t i; + + /* The algorithm from RFC 4034, Appendix B. */ + + assert(dnskey); + assert(dnskey->key->type == DNS_TYPE_DNSKEY); + + f = (uint32_t) dnskey->dnskey.flags; + + if (mask_revoke) + f &= ~DNSKEY_FLAG_REVOKE; + + sum = f + ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm); + + p = dnskey->dnskey.key; + + for (i = 0; i < dnskey->dnskey.key_size; i++) + sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i]; + + sum += (sum >> 16) & UINT32_C(0xFFFF); + + return sum & UINT32_C(0xFFFF); +} + +int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) { + size_t c = 0; + int r; + + /* Converts the specified hostname into DNSSEC canonicalized + * form. */ + + if (buffer_max < 2) + return -ENOBUFS; + + for (;;) { + r = dns_label_unescape(&n, buffer, buffer_max); + if (r < 0) + return r; + if (r == 0) + break; + + if (buffer_max < (size_t) r + 2) + return -ENOBUFS; + + /* The DNSSEC canonical form is not clear on what to + * do with dots appearing in labels, the way DNS-SD + * does it. Refuse it for now. */ + + if (memchr(buffer, '.', r)) + return -EINVAL; + + ascii_strlower_n(buffer, (size_t) r); + buffer[r] = '.'; + + buffer += r + 1; + c += r + 1; + + buffer_max -= r + 1; + } + + if (c <= 0) { + /* Not even a single label: this is the root domain name */ + + assert(buffer_max > 2); + buffer[0] = '.'; + buffer[1] = 0; + + return 1; + } + + return (int) c; +} + +#ifdef HAVE_GCRYPT + +static int rr_compare(const void *a, const void *b) { + DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b; + size_t m; + int r; + + /* Let's order the RRs according to RFC 4034, Section 6.3 */ + + assert(x); + assert(*x); + assert((*x)->wire_format); + assert(y); + assert(*y); + assert((*y)->wire_format); + + m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y)); + + r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m); + if (r != 0) + return r; + + if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) + return -1; + else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) + return 1; + + return 0; +} + +static int dnssec_rsa_verify_raw( + const char *hash_algorithm, + const void *signature, size_t signature_size, + const void *data, size_t data_size, + const void *exponent, size_t exponent_size, + const void *modulus, size_t modulus_size) { + + gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL; + gcry_mpi_t n = NULL, e = NULL, s = NULL; + gcry_error_t ge; + int r; + + assert(hash_algorithm); + + ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&signature_sexp, + NULL, + "(sig-val (rsa (s %m)))", + s); + + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&data_sexp, + NULL, + "(data (flags pkcs1) (hash %s %b))", + hash_algorithm, + (int) data_size, + data); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&public_key_sexp, + NULL, + "(public-key (rsa (n %m) (e %m)))", + n, + e); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp); + if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE) + r = 0; + else if (ge != 0) { + log_debug("RSA signature check failed: %s", gpg_strerror(ge)); + r = -EIO; + } else + r = 1; + +finish: + if (e) + gcry_mpi_release(e); + if (n) + gcry_mpi_release(n); + if (s) + gcry_mpi_release(s); + + if (public_key_sexp) + gcry_sexp_release(public_key_sexp); + if (signature_sexp) + gcry_sexp_release(signature_sexp); + if (data_sexp) + gcry_sexp_release(data_sexp); + + return r; +} + +static int dnssec_rsa_verify( + const char *hash_algorithm, + const void *hash, size_t hash_size, + DnsResourceRecord *rrsig, + DnsResourceRecord *dnskey) { + + size_t exponent_size, modulus_size; + void *exponent, *modulus; + + assert(hash_algorithm); + assert(hash); + assert(hash_size > 0); + assert(rrsig); + assert(dnskey); + + if (*(uint8_t*) dnskey->dnskey.key == 0) { + /* exponent is > 255 bytes long */ + + exponent = (uint8_t*) dnskey->dnskey.key + 3; + exponent_size = + ((size_t) (((uint8_t*) dnskey->dnskey.key)[1]) << 8) | + ((size_t) ((uint8_t*) dnskey->dnskey.key)[2]); + + if (exponent_size < 256) + return -EINVAL; + + if (3 + exponent_size >= dnskey->dnskey.key_size) + return -EINVAL; + + modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size; + modulus_size = dnskey->dnskey.key_size - 3 - exponent_size; + + } else { + /* exponent is <= 255 bytes long */ + + exponent = (uint8_t*) dnskey->dnskey.key + 1; + exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0]; + + if (exponent_size <= 0) + return -EINVAL; + + if (1 + exponent_size >= dnskey->dnskey.key_size) + return -EINVAL; + + modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size; + modulus_size = dnskey->dnskey.key_size - 1 - exponent_size; + } + + return dnssec_rsa_verify_raw( + hash_algorithm, + rrsig->rrsig.signature, rrsig->rrsig.signature_size, + hash, hash_size, + exponent, exponent_size, + modulus, modulus_size); +} + +static int dnssec_ecdsa_verify_raw( + const char *hash_algorithm, + const char *curve, + const void *signature_r, size_t signature_r_size, + const void *signature_s, size_t signature_s_size, + const void *data, size_t data_size, + const void *key, size_t key_size) { + + gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL; + gcry_mpi_t q = NULL, r = NULL, s = NULL; + gcry_error_t ge; + int k; + + assert(hash_algorithm); + + ge = gcry_mpi_scan(&r, GCRYMPI_FMT_USG, signature_r, signature_r_size, NULL); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature_s, signature_s_size, NULL); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_mpi_scan(&q, GCRYMPI_FMT_USG, key, key_size, NULL); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&signature_sexp, + NULL, + "(sig-val (ecdsa (r %m) (s %m)))", + r, + s); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&data_sexp, + NULL, + "(data (flags rfc6979) (hash %s %b))", + hash_algorithm, + (int) data_size, + data); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&public_key_sexp, + NULL, + "(public-key (ecc (curve %s) (q %m)))", + curve, + q); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp); + if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE) + k = 0; + else if (ge != 0) { + log_debug("ECDSA signature check failed: %s", gpg_strerror(ge)); + k = -EIO; + } else + k = 1; +finish: + if (r) + gcry_mpi_release(r); + if (s) + gcry_mpi_release(s); + if (q) + gcry_mpi_release(q); + + if (public_key_sexp) + gcry_sexp_release(public_key_sexp); + if (signature_sexp) + gcry_sexp_release(signature_sexp); + if (data_sexp) + gcry_sexp_release(data_sexp); + + return k; +} + +static int dnssec_ecdsa_verify( + const char *hash_algorithm, + int algorithm, + const void *hash, size_t hash_size, + DnsResourceRecord *rrsig, + DnsResourceRecord *dnskey) { + + const char *curve; + size_t key_size; + uint8_t *q; + + assert(hash); + assert(hash_size); + assert(rrsig); + assert(dnskey); + + if (algorithm == DNSSEC_ALGORITHM_ECDSAP256SHA256) { + key_size = 32; + curve = "NIST P-256"; + } else if (algorithm == DNSSEC_ALGORITHM_ECDSAP384SHA384) { + key_size = 48; + curve = "NIST P-384"; + } else + return -EOPNOTSUPP; + + if (dnskey->dnskey.key_size != key_size * 2) + return -EINVAL; + + if (rrsig->rrsig.signature_size != key_size * 2) + return -EINVAL; + + q = alloca(key_size*2 + 1); + q[0] = 0x04; /* Prepend 0x04 to indicate an uncompressed key */ + memcpy(q+1, dnskey->dnskey.key, key_size*2); + + return dnssec_ecdsa_verify_raw( + hash_algorithm, + curve, + rrsig->rrsig.signature, key_size, + (uint8_t*) rrsig->rrsig.signature + key_size, key_size, + hash, hash_size, + q, key_size*2+1); +} + +static void md_add_uint8(gcry_md_hd_t md, uint8_t v) { + gcry_md_write(md, &v, sizeof(v)); +} + +static void md_add_uint16(gcry_md_hd_t md, uint16_t v) { + v = htobe16(v); + gcry_md_write(md, &v, sizeof(v)); +} + +static void md_add_uint32(gcry_md_hd_t md, uint32_t v) { + v = htobe32(v); + gcry_md_write(md, &v, sizeof(v)); +} + +static int dnssec_rrsig_prepare(DnsResourceRecord *rrsig) { + int n_key_labels, n_signer_labels; + const char *name; + int r; + + /* Checks whether the specified RRSIG RR is somewhat valid, and initializes the .n_skip_labels_source and + * .n_skip_labels_signer fields so that we can use them later on. */ + + assert(rrsig); + assert(rrsig->key->type == DNS_TYPE_RRSIG); + + /* Check if this RRSIG RR is already prepared */ + if (rrsig->n_skip_labels_source != (unsigned) -1) + return 0; + + if (rrsig->rrsig.inception > rrsig->rrsig.expiration) + return -EINVAL; + + name = dns_resource_key_name(rrsig->key); + + n_key_labels = dns_name_count_labels(name); + if (n_key_labels < 0) + return n_key_labels; + if (rrsig->rrsig.labels > n_key_labels) + return -EINVAL; + + n_signer_labels = dns_name_count_labels(rrsig->rrsig.signer); + if (n_signer_labels < 0) + return n_signer_labels; + if (n_signer_labels > rrsig->rrsig.labels) + return -EINVAL; + + r = dns_name_skip(name, n_key_labels - n_signer_labels, &name); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + /* Check if the signer is really a suffix of us */ + r = dns_name_equal(name, rrsig->rrsig.signer); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + rrsig->n_skip_labels_source = n_key_labels - rrsig->rrsig.labels; + rrsig->n_skip_labels_signer = n_key_labels - n_signer_labels; + + return 0; +} + +static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) { + usec_t expiration, inception, skew; + + assert(rrsig); + assert(rrsig->key->type == DNS_TYPE_RRSIG); + + if (realtime == USEC_INFINITY) + realtime = now(CLOCK_REALTIME); + + expiration = rrsig->rrsig.expiration * USEC_PER_SEC; + inception = rrsig->rrsig.inception * USEC_PER_SEC; + + /* Consider inverted validity intervals as expired */ + if (inception > expiration) + return true; + + /* Permit a certain amount of clock skew of 10% of the valid + * time range. This takes inspiration from unbound's + * resolver. */ + skew = (expiration - inception) / 10; + if (skew > SKEW_MAX) + skew = SKEW_MAX; + + if (inception < skew) + inception = 0; + else + inception -= skew; + + if (expiration + skew < expiration) + expiration = USEC_INFINITY; + else + expiration += skew; + + return realtime < inception || realtime > expiration; +} + +static int algorithm_to_gcrypt_md(uint8_t algorithm) { + + /* Translates a DNSSEC signature algorithm into a gcrypt + * digest identifier. + * + * Note that we implement all algorithms listed as "Must + * implement" and "Recommended to Implement" in RFC6944. We + * don't implement any algorithms that are listed as + * "Optional" or "Must Not Implement". Specifically, we do not + * implement RSAMD5, DSASHA1, DH, DSA-NSEC3-SHA1, and + * GOST-ECC. */ + + switch (algorithm) { + + case DNSSEC_ALGORITHM_RSASHA1: + case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: + return GCRY_MD_SHA1; + + case DNSSEC_ALGORITHM_RSASHA256: + case DNSSEC_ALGORITHM_ECDSAP256SHA256: + return GCRY_MD_SHA256; + + case DNSSEC_ALGORITHM_ECDSAP384SHA384: + return GCRY_MD_SHA384; + + case DNSSEC_ALGORITHM_RSASHA512: + return GCRY_MD_SHA512; + + default: + return -EOPNOTSUPP; + } +} + +static void dnssec_fix_rrset_ttl( + DnsResourceRecord *list[], + unsigned n, + DnsResourceRecord *rrsig, + usec_t realtime) { + + unsigned k; + + assert(list); + assert(n > 0); + assert(rrsig); + + for (k = 0; k < n; k++) { + DnsResourceRecord *rr = list[k]; + + /* Pick the TTL as the minimum of the RR's TTL, the + * RR's original TTL according to the RRSIG and the + * RRSIG's own TTL, see RFC 4035, Section 5.3.3 */ + rr->ttl = MIN3(rr->ttl, rrsig->rrsig.original_ttl, rrsig->ttl); + rr->expiry = rrsig->rrsig.expiration * USEC_PER_SEC; + + /* Copy over information about the signer and wildcard source of synthesis */ + rr->n_skip_labels_source = rrsig->n_skip_labels_source; + rr->n_skip_labels_signer = rrsig->n_skip_labels_signer; + } + + rrsig->expiry = rrsig->rrsig.expiration * USEC_PER_SEC; +} + +int dnssec_verify_rrset( + DnsAnswer *a, + const DnsResourceKey *key, + DnsResourceRecord *rrsig, + DnsResourceRecord *dnskey, + usec_t realtime, + DnssecResult *result) { + + uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX]; + DnsResourceRecord **list, *rr; + const char *source, *name; + gcry_md_hd_t md = NULL; + int r, md_algorithm; + size_t k, n = 0; + size_t hash_size; + void *hash; + bool wildcard; + + assert(key); + assert(rrsig); + assert(dnskey); + assert(result); + assert(rrsig->key->type == DNS_TYPE_RRSIG); + assert(dnskey->key->type == DNS_TYPE_DNSKEY); + + /* Verifies that the RRSet matches the specified "key" in "a", + * using the signature "rrsig" and the key "dnskey". It's + * assumed that RRSIG and DNSKEY match. */ + + md_algorithm = algorithm_to_gcrypt_md(rrsig->rrsig.algorithm); + if (md_algorithm == -EOPNOTSUPP) { + *result = DNSSEC_UNSUPPORTED_ALGORITHM; + return 0; + } + if (md_algorithm < 0) + return md_algorithm; + + r = dnssec_rrsig_prepare(rrsig); + if (r == -EINVAL) { + *result = DNSSEC_INVALID; + return r; + } + if (r < 0) + return r; + + r = dnssec_rrsig_expired(rrsig, realtime); + if (r < 0) + return r; + if (r > 0) { + *result = DNSSEC_SIGNATURE_EXPIRED; + return 0; + } + + name = dns_resource_key_name(key); + + /* Some keys may only appear signed in the zone apex, and are invalid anywhere else. (SOA, NS...) */ + if (dns_type_apex_only(rrsig->rrsig.type_covered)) { + r = dns_name_equal(rrsig->rrsig.signer, name); + if (r < 0) + return r; + if (r == 0) { + *result = DNSSEC_INVALID; + return 0; + } + } + + /* OTOH DS RRs may not appear in the zone apex, but are valid everywhere else. */ + if (rrsig->rrsig.type_covered == DNS_TYPE_DS) { + r = dns_name_equal(rrsig->rrsig.signer, name); + if (r < 0) + return r; + if (r > 0) { + *result = DNSSEC_INVALID; + return 0; + } + } + + /* Determine the "Source of Synthesis" and whether this is a wildcard RRSIG */ + r = dns_name_suffix(name, rrsig->rrsig.labels, &source); + if (r < 0) + return r; + if (r > 0 && !dns_type_may_wildcard(rrsig->rrsig.type_covered)) { + /* We refuse to validate NSEC3 or SOA RRs that are synthesized from wildcards */ + *result = DNSSEC_INVALID; + return 0; + } + if (r == 1) { + /* If we stripped a single label, then let's see if that maybe was "*". If so, we are not really + * synthesized from a wildcard, we are the wildcard itself. Treat that like a normal name. */ + r = dns_name_startswith(name, "*"); + if (r < 0) + return r; + if (r > 0) + source = name; + + wildcard = r == 0; + } else + wildcard = r > 0; + + /* Collect all relevant RRs in a single array, so that we can look at the RRset */ + list = newa(DnsResourceRecord *, dns_answer_size(a)); + + DNS_ANSWER_FOREACH(rr, a) { + r = dns_resource_key_equal(key, rr->key); + if (r < 0) + return r; + if (r == 0) + continue; + + /* We need the wire format for ordering, and digest calculation */ + r = dns_resource_record_to_wire_format(rr, true); + if (r < 0) + return r; + + list[n++] = rr; + + if (n > VERIFY_RRS_MAX) + return -E2BIG; + } + + if (n <= 0) + return -ENODATA; + + /* Bring the RRs into canonical order */ + qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare); + + /* OK, the RRs are now in canonical order. Let's calculate the digest */ + initialize_libgcrypt(false); + + hash_size = gcry_md_get_algo_dlen(md_algorithm); + assert(hash_size > 0); + + gcry_md_open(&md, md_algorithm, 0); + if (!md) + return -EIO; + + md_add_uint16(md, rrsig->rrsig.type_covered); + md_add_uint8(md, rrsig->rrsig.algorithm); + md_add_uint8(md, rrsig->rrsig.labels); + md_add_uint32(md, rrsig->rrsig.original_ttl); + md_add_uint32(md, rrsig->rrsig.expiration); + md_add_uint32(md, rrsig->rrsig.inception); + md_add_uint16(md, rrsig->rrsig.key_tag); + + r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true); + if (r < 0) + goto finish; + gcry_md_write(md, wire_format_name, r); + + /* Convert the source of synthesis into wire format */ + r = dns_name_to_wire_format(source, wire_format_name, sizeof(wire_format_name), true); + if (r < 0) + goto finish; + + for (k = 0; k < n; k++) { + size_t l; + + rr = list[k]; + + /* Hash the source of synthesis. If this is a wildcard, then prefix it with the *. label */ + if (wildcard) + gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2); + gcry_md_write(md, wire_format_name, r); + + md_add_uint16(md, rr->key->type); + md_add_uint16(md, rr->key->class); + md_add_uint32(md, rrsig->rrsig.original_ttl); + + l = DNS_RESOURCE_RECORD_RDATA_SIZE(rr); + assert(l <= 0xFFFF); + + md_add_uint16(md, (uint16_t) l); + gcry_md_write(md, DNS_RESOURCE_RECORD_RDATA(rr), l); + } + + hash = gcry_md_read(md, 0); + if (!hash) { + r = -EIO; + goto finish; + } + + switch (rrsig->rrsig.algorithm) { + + case DNSSEC_ALGORITHM_RSASHA1: + case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: + case DNSSEC_ALGORITHM_RSASHA256: + case DNSSEC_ALGORITHM_RSASHA512: + r = dnssec_rsa_verify( + gcry_md_algo_name(md_algorithm), + hash, hash_size, + rrsig, + dnskey); + break; + + case DNSSEC_ALGORITHM_ECDSAP256SHA256: + case DNSSEC_ALGORITHM_ECDSAP384SHA384: + r = dnssec_ecdsa_verify( + gcry_md_algo_name(md_algorithm), + rrsig->rrsig.algorithm, + hash, hash_size, + rrsig, + dnskey); + break; + } + + if (r < 0) + goto finish; + + /* Now, fix the ttl, expiry, and remember the synthesizing source and the signer */ + if (r > 0) + dnssec_fix_rrset_ttl(list, n, rrsig, realtime); + + if (r == 0) + *result = DNSSEC_INVALID; + else if (wildcard) + *result = DNSSEC_VALIDATED_WILDCARD; + else + *result = DNSSEC_VALIDATED; + + r = 0; + +finish: + gcry_md_close(md); + return r; +} + +int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) { + + assert(rrsig); + assert(dnskey); + + /* Checks if the specified DNSKEY RR matches the key used for + * the signature in the specified RRSIG RR */ + + if (rrsig->key->type != DNS_TYPE_RRSIG) + return -EINVAL; + + if (dnskey->key->type != DNS_TYPE_DNSKEY) + return 0; + if (dnskey->key->class != rrsig->key->class) + return 0; + if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0) + return 0; + if (!revoked_ok && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE)) + return 0; + if (dnskey->dnskey.protocol != 3) + return 0; + if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm) + return 0; + + if (dnssec_keytag(dnskey, false) != rrsig->rrsig.key_tag) + return 0; + + return dns_name_equal(dns_resource_key_name(dnskey->key), rrsig->rrsig.signer); +} + +int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) { + assert(key); + assert(rrsig); + + /* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */ + + if (rrsig->key->type != DNS_TYPE_RRSIG) + return 0; + if (rrsig->key->class != key->class) + return 0; + if (rrsig->rrsig.type_covered != key->type) + return 0; + + return dns_name_equal(dns_resource_key_name(rrsig->key), dns_resource_key_name(key)); +} + +int dnssec_verify_rrset_search( + DnsAnswer *a, + const DnsResourceKey *key, + DnsAnswer *validated_dnskeys, + usec_t realtime, + DnssecResult *result, + DnsResourceRecord **ret_rrsig) { + + bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false; + DnsResourceRecord *rrsig; + int r; + + assert(key); + assert(result); + + /* Verifies all RRs from "a" that match the key "key" against DNSKEYs in "validated_dnskeys" */ + + if (!a || a->n_rrs <= 0) + return -ENODATA; + + /* Iterate through each RRSIG RR. */ + DNS_ANSWER_FOREACH(rrsig, a) { + DnsResourceRecord *dnskey; + DnsAnswerFlags flags; + + /* Is this an RRSIG RR that applies to RRs matching our key? */ + r = dnssec_key_match_rrsig(key, rrsig); + if (r < 0) + return r; + if (r == 0) + continue; + + found_rrsig = true; + + /* Look for a matching key */ + DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) { + DnssecResult one_result; + + if ((flags & DNS_ANSWER_AUTHENTICATED) == 0) + continue; + + /* Is this a DNSKEY RR that matches they key of our RRSIG? */ + r = dnssec_rrsig_match_dnskey(rrsig, dnskey, false); + if (r < 0) + return r; + if (r == 0) + continue; + + /* Take the time here, if it isn't set yet, so + * that we do all validations with the same + * time. */ + if (realtime == USEC_INFINITY) + realtime = now(CLOCK_REALTIME); + + /* Yay, we found a matching RRSIG with a matching + * DNSKEY, awesome. Now let's verify all entries of + * the RRSet against the RRSIG and DNSKEY + * combination. */ + + r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime, &one_result); + if (r < 0) + return r; + + switch (one_result) { + + case DNSSEC_VALIDATED: + case DNSSEC_VALIDATED_WILDCARD: + /* Yay, the RR has been validated, + * return immediately, but fix up the expiry */ + if (ret_rrsig) + *ret_rrsig = rrsig; + + *result = one_result; + return 0; + + case DNSSEC_INVALID: + /* If the signature is invalid, let's try another + key and/or signature. After all they + key_tags and stuff are not unique, and + might be shared by multiple keys. */ + found_invalid = true; + continue; + + case DNSSEC_UNSUPPORTED_ALGORITHM: + /* If the key algorithm is + unsupported, try another + RRSIG/DNSKEY pair, but remember we + encountered this, so that we can + return a proper error when we + encounter nothing better. */ + found_unsupported_algorithm = true; + continue; + + case DNSSEC_SIGNATURE_EXPIRED: + /* If the signature is expired, try + another one, but remember it, so + that we can return this */ + found_expired_rrsig = true; + continue; + + default: + assert_not_reached("Unexpected DNSSEC validation result"); + } + } + } + + if (found_expired_rrsig) + *result = DNSSEC_SIGNATURE_EXPIRED; + else if (found_unsupported_algorithm) + *result = DNSSEC_UNSUPPORTED_ALGORITHM; + else if (found_invalid) + *result = DNSSEC_INVALID; + else if (found_rrsig) + *result = DNSSEC_MISSING_KEY; + else + *result = DNSSEC_NO_SIGNATURE; + + if (ret_rrsig) + *ret_rrsig = NULL; + + return 0; +} + +int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) { + DnsResourceRecord *rr; + int r; + + /* Checks whether there's at least one RRSIG in 'a' that proctects RRs of the specified key */ + + DNS_ANSWER_FOREACH(rr, a) { + r = dnssec_key_match_rrsig(key, rr); + if (r < 0) + return r; + if (r > 0) + return 1; + } + + return 0; +} + +static int digest_to_gcrypt_md(uint8_t algorithm) { + + /* Translates a DNSSEC digest algorithm into a gcrypt digest identifier */ + + switch (algorithm) { + + case DNSSEC_DIGEST_SHA1: + return GCRY_MD_SHA1; + + case DNSSEC_DIGEST_SHA256: + return GCRY_MD_SHA256; + + case DNSSEC_DIGEST_SHA384: + return GCRY_MD_SHA384; + + default: + return -EOPNOTSUPP; + } +} + +int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) { + char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX]; + gcry_md_hd_t md = NULL; + size_t hash_size; + int md_algorithm, r; + void *result; + + assert(dnskey); + assert(ds); + + /* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */ + + if (dnskey->key->type != DNS_TYPE_DNSKEY) + return -EINVAL; + if (ds->key->type != DNS_TYPE_DS) + return -EINVAL; + if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0) + return -EKEYREJECTED; + if (!mask_revoke && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE)) + return -EKEYREJECTED; + if (dnskey->dnskey.protocol != 3) + return -EKEYREJECTED; + + if (dnskey->dnskey.algorithm != ds->ds.algorithm) + return 0; + if (dnssec_keytag(dnskey, mask_revoke) != ds->ds.key_tag) + return 0; + + initialize_libgcrypt(false); + + md_algorithm = digest_to_gcrypt_md(ds->ds.digest_type); + if (md_algorithm < 0) + return md_algorithm; + + hash_size = gcry_md_get_algo_dlen(md_algorithm); + assert(hash_size > 0); + + if (ds->ds.digest_size != hash_size) + return 0; + + r = dnssec_canonicalize(dns_resource_key_name(dnskey->key), owner_name, sizeof(owner_name)); + if (r < 0) + return r; + + gcry_md_open(&md, md_algorithm, 0); + if (!md) + return -EIO; + + gcry_md_write(md, owner_name, r); + if (mask_revoke) + md_add_uint16(md, dnskey->dnskey.flags & ~DNSKEY_FLAG_REVOKE); + else + md_add_uint16(md, dnskey->dnskey.flags); + md_add_uint8(md, dnskey->dnskey.protocol); + md_add_uint8(md, dnskey->dnskey.algorithm); + gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size); + + result = gcry_md_read(md, 0); + if (!result) { + r = -EIO; + goto finish; + } + + r = memcmp(result, ds->ds.digest, ds->ds.digest_size) != 0; + +finish: + gcry_md_close(md); + return r; +} + +int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) { + DnsResourceRecord *ds; + DnsAnswerFlags flags; + int r; + + assert(dnskey); + + if (dnskey->key->type != DNS_TYPE_DNSKEY) + return 0; + + DNS_ANSWER_FOREACH_FLAGS(ds, flags, validated_ds) { + + if ((flags & DNS_ANSWER_AUTHENTICATED) == 0) + continue; + + if (ds->key->type != DNS_TYPE_DS) + continue; + if (ds->key->class != dnskey->key->class) + continue; + + r = dns_name_equal(dns_resource_key_name(dnskey->key), dns_resource_key_name(ds->key)); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_verify_dnskey_by_ds(dnskey, ds, false); + if (IN_SET(r, -EKEYREJECTED, -EOPNOTSUPP)) + return 0; /* The DNSKEY is revoked or otherwise invalid, or we don't support the digest algorithm */ + if (r < 0) + return r; + if (r > 0) + return 1; + } + + return 0; +} + +static int nsec3_hash_to_gcrypt_md(uint8_t algorithm) { + + /* Translates a DNSSEC NSEC3 hash algorithm into a gcrypt digest identifier */ + + switch (algorithm) { + + case NSEC3_ALGORITHM_SHA1: + return GCRY_MD_SHA1; + + default: + return -EOPNOTSUPP; + } +} + +int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { + uint8_t wire_format[DNS_WIRE_FOMAT_HOSTNAME_MAX]; + gcry_md_hd_t md = NULL; + size_t hash_size; + int algorithm; + void *result; + unsigned k; + int r; + + assert(nsec3); + assert(name); + assert(ret); + + if (nsec3->key->type != DNS_TYPE_NSEC3) + return -EINVAL; + + if (nsec3->nsec3.iterations > NSEC3_ITERATIONS_MAX) { + log_debug("Ignoring NSEC3 RR %s with excessive number of iterations.", dns_resource_record_to_string(nsec3)); + return -EOPNOTSUPP; + } + + algorithm = nsec3_hash_to_gcrypt_md(nsec3->nsec3.algorithm); + if (algorithm < 0) + return algorithm; + + initialize_libgcrypt(false); + + hash_size = gcry_md_get_algo_dlen(algorithm); + assert(hash_size > 0); + + if (nsec3->nsec3.next_hashed_name_size != hash_size) + return -EINVAL; + + r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true); + if (r < 0) + return r; + + gcry_md_open(&md, algorithm, 0); + if (!md) + return -EIO; + + gcry_md_write(md, wire_format, r); + gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size); + + result = gcry_md_read(md, 0); + if (!result) { + r = -EIO; + goto finish; + } + + for (k = 0; k < nsec3->nsec3.iterations; k++) { + uint8_t tmp[hash_size]; + memcpy(tmp, result, hash_size); + + gcry_md_reset(md); + gcry_md_write(md, tmp, hash_size); + gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size); + + result = gcry_md_read(md, 0); + if (!result) { + r = -EIO; + goto finish; + } + } + + memcpy(ret, result, hash_size); + r = (int) hash_size; + +finish: + gcry_md_close(md); + return r; +} + +static int nsec3_is_good(DnsResourceRecord *rr, DnsResourceRecord *nsec3) { + const char *a, *b; + int r; + + assert(rr); + + if (rr->key->type != DNS_TYPE_NSEC3) + return 0; + + /* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */ + if (!IN_SET(rr->nsec3.flags, 0, 1)) + return 0; + + /* Ignore NSEC3 RRs whose algorithm we don't know */ + if (nsec3_hash_to_gcrypt_md(rr->nsec3.algorithm) < 0) + return 0; + /* Ignore NSEC3 RRs with an excessive number of required iterations */ + if (rr->nsec3.iterations > NSEC3_ITERATIONS_MAX) + return 0; + + /* Ignore NSEC3 RRs generated from wildcards. If these NSEC3 RRs weren't correctly signed we can't make this + * check (since rr->n_skip_labels_source is -1), but that's OK, as we won't trust them anyway in that case. */ + if (rr->n_skip_labels_source != 0 && rr->n_skip_labels_source != (unsigned) -1) + return 0; + /* Ignore NSEC3 RRs that are located anywhere else than one label below the zone */ + if (rr->n_skip_labels_signer != 1 && rr->n_skip_labels_signer != (unsigned) -1) + return 0; + + if (!nsec3) + return 1; + + /* If a second NSEC3 RR is specified, also check if they are from the same zone. */ + + if (nsec3 == rr) /* Shortcut */ + return 1; + + if (rr->key->class != nsec3->key->class) + return 0; + if (rr->nsec3.algorithm != nsec3->nsec3.algorithm) + return 0; + if (rr->nsec3.iterations != nsec3->nsec3.iterations) + return 0; + if (rr->nsec3.salt_size != nsec3->nsec3.salt_size) + return 0; + if (memcmp(rr->nsec3.salt, nsec3->nsec3.salt, rr->nsec3.salt_size) != 0) + return 0; + + a = dns_resource_key_name(rr->key); + r = dns_name_parent(&a); /* strip off hash */ + if (r < 0) + return r; + if (r == 0) + return 0; + + b = dns_resource_key_name(nsec3->key); + r = dns_name_parent(&b); /* strip off hash */ + if (r < 0) + return r; + if (r == 0) + return 0; + + /* Make sure both have the same parent */ + return dns_name_equal(a, b); +} + +static int nsec3_hashed_domain_format(const uint8_t *hashed, size_t hashed_size, const char *zone, char **ret) { + _cleanup_free_ char *l = NULL; + char *j; + + assert(hashed); + assert(hashed_size > 0); + assert(zone); + assert(ret); + + l = base32hexmem(hashed, hashed_size, false); + if (!l) + return -ENOMEM; + + j = strjoin(l, ".", zone, NULL); + if (!j) + return -ENOMEM; + + *ret = j; + return (int) hashed_size; +} + +static int nsec3_hashed_domain_make(DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) { + uint8_t hashed[DNSSEC_HASH_SIZE_MAX]; + int hashed_size; + + assert(nsec3); + assert(domain); + assert(zone); + assert(ret); + + hashed_size = dnssec_nsec3_hash(nsec3, domain, hashed); + if (hashed_size < 0) + return hashed_size; + + return nsec3_hashed_domain_format(hashed, (size_t) hashed_size, zone, ret); +} + +/* See RFC 5155, Section 8 + * First try to find a NSEC3 record that matches our query precisely, if that fails, find the closest + * enclosure. Secondly, find a proof that there is no closer enclosure and either a proof that there + * is no wildcard domain as a direct descendant of the closest enclosure, or find an NSEC3 record that + * matches the wildcard domain. + * + * Based on this we can prove either the existence of the record in @key, or NXDOMAIN or NODATA, or + * that there is no proof either way. The latter is the case if a the proof of non-existence of a given + * name uses an NSEC3 record with the opt-out bit set. Lastly, if we are given insufficient NSEC3 records + * to conclude anything we indicate this by returning NO_RR. */ +static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { + _cleanup_free_ char *next_closer_domain = NULL, *wildcard_domain = NULL; + const char *zone, *p, *pp = NULL, *wildcard; + DnsResourceRecord *rr, *enclosure_rr, *zone_rr, *wildcard_rr = NULL; + DnsAnswerFlags flags; + int hashed_size, r; + bool a, no_closer = false, no_wildcard = false, optout = false; + + assert(key); + assert(result); + + /* First step, find the zone name and the NSEC3 parameters of the zone. + * it is sufficient to look for the longest common suffix we find with + * any NSEC3 RR in the response. Any NSEC3 record will do as all NSEC3 + * records from a given zone in a response must use the same + * parameters. */ + zone = dns_resource_key_name(key); + for (;;) { + DNS_ANSWER_FOREACH_FLAGS(zone_rr, flags, answer) { + r = nsec3_is_good(zone_rr, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dns_name_equal_skip(dns_resource_key_name(zone_rr->key), 1, zone); + if (r < 0) + return r; + if (r > 0) + goto found_zone; + } + + /* Strip one label from the front */ + r = dns_name_parent(&zone); + if (r < 0) + return r; + if (r == 0) + break; + } + + *result = DNSSEC_NSEC_NO_RR; + return 0; + +found_zone: + /* Second step, find the closest encloser NSEC3 RR in 'answer' that matches 'key' */ + p = dns_resource_key_name(key); + for (;;) { + _cleanup_free_ char *hashed_domain = NULL; + + hashed_size = nsec3_hashed_domain_make(zone_rr, p, zone, &hashed_domain); + if (hashed_size == -EOPNOTSUPP) { + *result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM; + return 0; + } + if (hashed_size < 0) + return hashed_size; + + DNS_ANSWER_FOREACH_FLAGS(enclosure_rr, flags, answer) { + + r = nsec3_is_good(enclosure_rr, zone_rr); + if (r < 0) + return r; + if (r == 0) + continue; + + if (enclosure_rr->nsec3.next_hashed_name_size != (size_t) hashed_size) + continue; + + r = dns_name_equal(dns_resource_key_name(enclosure_rr->key), hashed_domain); + if (r < 0) + return r; + if (r > 0) { + a = flags & DNS_ANSWER_AUTHENTICATED; + goto found_closest_encloser; + } + } + + /* We didn't find the closest encloser with this name, + * but let's remember this domain name, it might be + * the next closer name */ + + pp = p; + + /* Strip one label from the front */ + r = dns_name_parent(&p); + if (r < 0) + return r; + if (r == 0) + break; + } + + *result = DNSSEC_NSEC_NO_RR; + return 0; + +found_closest_encloser: + /* We found a closest encloser in 'p'; next closer is 'pp' */ + + if (!pp) { + /* We have an exact match! If we area looking for a DS RR, then we must insist that we got the NSEC3 RR + * from the parent. Otherwise the one from the child. Do so, by checking whether SOA and NS are + * appropriately set. */ + + if (key->type == DNS_TYPE_DS) { + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) + return -EBADMSG; + } else { + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_NS) && + !bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) + return -EBADMSG; + } + + /* No next closer NSEC3 RR. That means there's a direct NSEC3 RR for our key. */ + if (bitmap_isset(enclosure_rr->nsec3.types, key->type)) + *result = DNSSEC_NSEC_FOUND; + else if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_CNAME)) + *result = DNSSEC_NSEC_CNAME; + else + *result = DNSSEC_NSEC_NODATA; + + if (authenticated) + *authenticated = a; + if (ttl) + *ttl = enclosure_rr->ttl; + + return 0; + } + + /* Ensure this is not a DNAME domain, see RFC5155, section 8.3. */ + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_DNAME)) + return -EBADMSG; + + /* Ensure that this data is from the delegated domain + * (i.e. originates from the "lower" DNS server), and isn't + * just glue records (i.e. doesn't originate from the "upper" + * DNS server). */ + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_NS) && + !bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) + return -EBADMSG; + + /* Prove that there is no next closer and whether or not there is a wildcard domain. */ + + wildcard = strjoina("*.", p); + r = nsec3_hashed_domain_make(enclosure_rr, wildcard, zone, &wildcard_domain); + if (r < 0) + return r; + if (r != hashed_size) + return -EBADMSG; + + r = nsec3_hashed_domain_make(enclosure_rr, pp, zone, &next_closer_domain); + if (r < 0) + return r; + if (r != hashed_size) + return -EBADMSG; + + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + _cleanup_free_ char *next_hashed_domain = NULL; + + r = nsec3_is_good(rr, zone_rr); + if (r < 0) + return r; + if (r == 0) + continue; + + r = nsec3_hashed_domain_format(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, zone, &next_hashed_domain); + if (r < 0) + return r; + + r = dns_name_between(dns_resource_key_name(rr->key), next_closer_domain, next_hashed_domain); + if (r < 0) + return r; + if (r > 0) { + if (rr->nsec3.flags & 1) + optout = true; + + a = a && (flags & DNS_ANSWER_AUTHENTICATED); + + no_closer = true; + } + + r = dns_name_equal(dns_resource_key_name(rr->key), wildcard_domain); + if (r < 0) + return r; + if (r > 0) { + a = a && (flags & DNS_ANSWER_AUTHENTICATED); + + wildcard_rr = rr; + } + + r = dns_name_between(dns_resource_key_name(rr->key), wildcard_domain, next_hashed_domain); + if (r < 0) + return r; + if (r > 0) { + if (rr->nsec3.flags & 1) + /* This only makes sense if we have a wildcard delegation, which is + * very unlikely, see RFC 4592, Section 4.2, but we cannot rely on + * this not happening, so hence cannot simply conclude NXDOMAIN as + * we would wish */ + optout = true; + + a = a && (flags & DNS_ANSWER_AUTHENTICATED); + + no_wildcard = true; + } + } + + if (wildcard_rr && no_wildcard) + return -EBADMSG; + + if (!no_closer) { + *result = DNSSEC_NSEC_NO_RR; + return 0; + } + + if (wildcard_rr) { + /* A wildcard exists that matches our query. */ + if (optout) + /* This is not specified in any RFC to the best of my knowledge, but + * if the next closer enclosure is covered by an opt-out NSEC3 RR + * it means that we cannot prove that the source of synthesis is + * correct, as there may be a closer match. */ + *result = DNSSEC_NSEC_OPTOUT; + else if (bitmap_isset(wildcard_rr->nsec3.types, key->type)) + *result = DNSSEC_NSEC_FOUND; + else if (bitmap_isset(wildcard_rr->nsec3.types, DNS_TYPE_CNAME)) + *result = DNSSEC_NSEC_CNAME; + else + *result = DNSSEC_NSEC_NODATA; + } else { + if (optout) + /* The RFC only specifies that we have to care for optout for NODATA for + * DS records. However, children of an insecure opt-out delegation should + * also be considered opt-out, rather than verified NXDOMAIN. + * Note that we do not require a proof of wildcard non-existence if the + * next closer domain is covered by an opt-out, as that would not provide + * any additional information. */ + *result = DNSSEC_NSEC_OPTOUT; + else if (no_wildcard) + *result = DNSSEC_NSEC_NXDOMAIN; + else { + *result = DNSSEC_NSEC_NO_RR; + + return 0; + } + } + + if (authenticated) + *authenticated = a; + + if (ttl) + *ttl = enclosure_rr->ttl; + + return 0; +} + +static int dnssec_nsec_wildcard_equal(DnsResourceRecord *rr, const char *name) { + char label[DNS_LABEL_MAX]; + const char *n; + int r; + + assert(rr); + assert(rr->key->type == DNS_TYPE_NSEC); + + /* Checks whether the specified RR has a name beginning in "*.", and if the rest is a suffix of our name */ + + if (rr->n_skip_labels_source != 1) + return 0; + + n = dns_resource_key_name(rr->key); + r = dns_label_unescape(&n, label, sizeof(label)); + if (r <= 0) + return r; + if (r != 1 || label[0] != '*') + return 0; + + return dns_name_endswith(name, n); +} + +static int dnssec_nsec_in_path(DnsResourceRecord *rr, const char *name) { + const char *nn, *common_suffix; + int r; + + assert(rr); + assert(rr->key->type == DNS_TYPE_NSEC); + + /* Checks whether the specified nsec RR indicates that name is an empty non-terminal (ENT) + * + * A couple of examples: + * + * NSEC bar → waldo.foo.bar: indicates that foo.bar exists and is an ENT + * NSEC waldo.foo.bar → yyy.zzz.xoo.bar: indicates that xoo.bar and zzz.xoo.bar exist and are ENTs + * NSEC yyy.zzz.xoo.bar → bar: indicates pretty much nothing about ENTs + */ + + /* First, determine parent of next domain. */ + nn = rr->nsec.next_domain_name; + r = dns_name_parent(&nn); + if (r <= 0) + return r; + + /* If the name we just determined is not equal or child of the name we are interested in, then we can't say + * anything at all. */ + r = dns_name_endswith(nn, name); + if (r <= 0) + return r; + + /* If the name we we are interested in is not a prefix of the common suffix of the NSEC RR's owner and next domain names, then we can't say anything either. */ + r = dns_name_common_suffix(dns_resource_key_name(rr->key), rr->nsec.next_domain_name, &common_suffix); + if (r < 0) + return r; + + return dns_name_endswith(name, common_suffix); +} + +static int dnssec_nsec_from_parent_zone(DnsResourceRecord *rr, const char *name) { + int r; + + assert(rr); + assert(rr->key->type == DNS_TYPE_NSEC); + + /* Checks whether this NSEC originates to the parent zone or the child zone. */ + + r = dns_name_parent(&name); + if (r <= 0) + return r; + + r = dns_name_equal(name, dns_resource_key_name(rr->key)); + if (r <= 0) + return r; + + /* DNAME, and NS without SOA is an indication for a delegation. */ + if (bitmap_isset(rr->nsec.types, DNS_TYPE_DNAME)) + return 1; + + if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) && !bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) + return 1; + + return 0; +} + +static int dnssec_nsec_covers(DnsResourceRecord *rr, const char *name) { + const char *common_suffix, *p; + int r; + + assert(rr); + assert(rr->key->type == DNS_TYPE_NSEC); + + /* Checks whether the "Next Closer" is witin the space covered by the specified RR. */ + + r = dns_name_common_suffix(dns_resource_key_name(rr->key), rr->nsec.next_domain_name, &common_suffix); + if (r < 0) + return r; + + for (;;) { + p = name; + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r == 0) + return 0; + + r = dns_name_equal(name, common_suffix); + if (r < 0) + return r; + if (r > 0) + break; + } + + /* p is now the "Next Closer". */ + + return dns_name_between(dns_resource_key_name(rr->key), p, rr->nsec.next_domain_name); +} + +static int dnssec_nsec_covers_wildcard(DnsResourceRecord *rr, const char *name) { + const char *common_suffix, *wc; + int r; + + assert(rr); + assert(rr->key->type == DNS_TYPE_NSEC); + + /* Checks whether the "Wildcard at the Closest Encloser" is within the space covered by the specified + * RR. Specifically, checks whether 'name' has the common suffix of the NSEC RR's owner and next names as + * suffix, and whether the NSEC covers the name generated by that suffix prepended with an asterisk label. + * + * NSEC bar → waldo.foo.bar: indicates that *.bar and *.foo.bar do not exist + * NSEC waldo.foo.bar → yyy.zzz.xoo.bar: indicates that *.xoo.bar and *.zzz.xoo.bar do not exist (and more ...) + * NSEC yyy.zzz.xoo.bar → bar: indicates that a number of wildcards don#t exist either... + */ + + r = dns_name_common_suffix(dns_resource_key_name(rr->key), rr->nsec.next_domain_name, &common_suffix); + if (r < 0) + return r; + + /* If the common suffix is not shared by the name we are interested in, it has nothing to say for us. */ + r = dns_name_endswith(name, common_suffix); + if (r <= 0) + return r; + + wc = strjoina("*.", common_suffix); + return dns_name_between(dns_resource_key_name(rr->key), wc, rr->nsec.next_domain_name); +} + +int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { + bool have_nsec3 = false, covering_rr_authenticated = false, wildcard_rr_authenticated = false; + DnsResourceRecord *rr, *covering_rr = NULL, *wildcard_rr = NULL; + DnsAnswerFlags flags; + const char *name; + int r; + + assert(key); + assert(result); + + /* Look for any NSEC/NSEC3 RRs that say something about the specified key. */ + + name = dns_resource_key_name(key); + + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + + if (rr->key->class != key->class) + continue; + + have_nsec3 = have_nsec3 || (rr->key->type == DNS_TYPE_NSEC3); + + if (rr->key->type != DNS_TYPE_NSEC) + continue; + + /* The following checks only make sense for NSEC RRs that are not expanded from a wildcard */ + r = dns_resource_record_is_synthetic(rr); + if (r < 0) + return r; + if (r > 0) + continue; + + /* Check if this is a direct match. If so, we have encountered a NODATA case */ + r = dns_name_equal(dns_resource_key_name(rr->key), name); + if (r < 0) + return r; + if (r == 0) { + /* If it's not a direct match, maybe it's a wild card match? */ + r = dnssec_nsec_wildcard_equal(rr, name); + if (r < 0) + return r; + } + if (r > 0) { + if (key->type == DNS_TYPE_DS) { + /* If we look for a DS RR and the server sent us the NSEC RR of the child zone + * we have a problem. For DS RRs we want the NSEC RR from the parent */ + if (bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) + continue; + } else { + /* For all RR types, ensure that if NS is set SOA is set too, so that we know + * we got the child's NSEC. */ + if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) && + !bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) + continue; + } + + if (bitmap_isset(rr->nsec.types, key->type)) + *result = DNSSEC_NSEC_FOUND; + else if (bitmap_isset(rr->nsec.types, DNS_TYPE_CNAME)) + *result = DNSSEC_NSEC_CNAME; + else + *result = DNSSEC_NSEC_NODATA; + + if (authenticated) + *authenticated = flags & DNS_ANSWER_AUTHENTICATED; + if (ttl) + *ttl = rr->ttl; + + return 0; + } + + /* Check if the name we are looking for is an empty non-terminal within the owner or next name + * of the NSEC RR. */ + r = dnssec_nsec_in_path(rr, name); + if (r < 0) + return r; + if (r > 0) { + *result = DNSSEC_NSEC_NODATA; + + if (authenticated) + *authenticated = flags & DNS_ANSWER_AUTHENTICATED; + if (ttl) + *ttl = rr->ttl; + + return 0; + } + + /* The following two "covering" checks, are not useful if the NSEC is from the parent */ + r = dnssec_nsec_from_parent_zone(rr, name); + if (r < 0) + return r; + if (r > 0) + continue; + + /* Check if this NSEC RR proves the absence of an explicit RR under this name */ + r = dnssec_nsec_covers(rr, name); + if (r < 0) + return r; + if (r > 0 && (!covering_rr || !covering_rr_authenticated)) { + covering_rr = rr; + covering_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED; + } + + /* Check if this NSEC RR proves the absence of a wildcard RR under this name */ + r = dnssec_nsec_covers_wildcard(rr, name); + if (r < 0) + return r; + if (r > 0 && (!wildcard_rr || !wildcard_rr_authenticated)) { + wildcard_rr = rr; + wildcard_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED; + } + } + + if (covering_rr && wildcard_rr) { + /* If we could prove that neither the name itself, nor the wildcard at the closest encloser exists, we + * proved the NXDOMAIN case. */ + *result = DNSSEC_NSEC_NXDOMAIN; + + if (authenticated) + *authenticated = covering_rr_authenticated && wildcard_rr_authenticated; + if (ttl) + *ttl = MIN(covering_rr->ttl, wildcard_rr->ttl); + + return 0; + } + + /* OK, this was not sufficient. Let's see if NSEC3 can help. */ + if (have_nsec3) + return dnssec_test_nsec3(answer, key, result, authenticated, ttl); + + /* No approproate NSEC RR found, report this. */ + *result = DNSSEC_NSEC_NO_RR; + return 0; +} + +static int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, const char *name, const char *zone, bool *authenticated) { + DnsResourceRecord *rr; + DnsAnswerFlags flags; + int r; + + assert(name); + assert(zone); + + /* Checks whether there's an NSEC/NSEC3 that proves that the specified 'name' is non-existing in the specified + * 'zone'. The 'zone' must be a suffix of the 'name'. */ + + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + bool found = false; + + if (rr->key->type != type && type != DNS_TYPE_ANY) + continue; + + switch (rr->key->type) { + + case DNS_TYPE_NSEC: + + /* We only care for NSEC RRs from the indicated zone */ + r = dns_resource_record_is_signer(rr, zone); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dns_name_between(dns_resource_key_name(rr->key), name, rr->nsec.next_domain_name); + if (r < 0) + return r; + + found = r > 0; + break; + + case DNS_TYPE_NSEC3: { + _cleanup_free_ char *hashed_domain = NULL, *next_hashed_domain = NULL; + + /* We only care for NSEC3 RRs from the indicated zone */ + r = dns_resource_record_is_signer(rr, zone); + if (r < 0) + return r; + if (r == 0) + continue; + + r = nsec3_is_good(rr, NULL); + if (r < 0) + return r; + if (r == 0) + break; + + /* Format the domain we are testing with the NSEC3 RR's hash function */ + r = nsec3_hashed_domain_make( + rr, + name, + zone, + &hashed_domain); + if (r < 0) + return r; + if ((size_t) r != rr->nsec3.next_hashed_name_size) + break; + + /* Format the NSEC3's next hashed name as proper domain name */ + r = nsec3_hashed_domain_format( + rr->nsec3.next_hashed_name, + rr->nsec3.next_hashed_name_size, + zone, + &next_hashed_domain); + if (r < 0) + return r; + + r = dns_name_between(dns_resource_key_name(rr->key), hashed_domain, next_hashed_domain); + if (r < 0) + return r; + + found = r > 0; + break; + } + + default: + continue; + } + + if (found) { + if (authenticated) + *authenticated = flags & DNS_ANSWER_AUTHENTICATED; + return 1; + } + } + + return 0; +} + +static int dnssec_test_positive_wildcard_nsec3( + DnsAnswer *answer, + const char *name, + const char *source, + const char *zone, + bool *authenticated) { + + const char *next_closer = NULL; + int r; + + /* Run a positive NSEC3 wildcard proof. Specifically: + * + * A proof that the "next closer" of the generating wildcard does not exist. + * + * Note a key difference between the NSEC3 and NSEC versions of the proof. NSEC RRs don't have to exist for + * empty non-transients. NSEC3 RRs however have to. This means it's sufficient to check if the next closer name + * exists for the NSEC3 RR and we are done. + * + * To prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f all we have to check is that + * c.d.e.f does not exist. */ + + for (;;) { + next_closer = name; + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r == 0) + return 0; + + r = dns_name_equal(name, source); + if (r < 0) + return r; + if (r > 0) + break; + } + + return dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC3, next_closer, zone, authenticated); +} + +static int dnssec_test_positive_wildcard_nsec( + DnsAnswer *answer, + const char *name, + const char *source, + const char *zone, + bool *_authenticated) { + + bool authenticated = true; + int r; + + /* Run a positive NSEC wildcard proof. Specifically: + * + * A proof that there's neither a wildcard name nor a non-wildcard name that is a suffix of the name "name" and + * a prefix of the synthesizing source "source" in the zone "zone". + * + * See RFC 5155, Section 8.8 and RFC 4035, Section 5.3.4 + * + * Note that if we want to prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f, then we + * have to prove that none of the following exist: + * + * 1) a.b.c.d.e.f + * 2) *.b.c.d.e.f + * 3) b.c.d.e.f + * 4) *.c.d.e.f + * 5) c.d.e.f + * + */ + + for (;;) { + _cleanup_free_ char *wc = NULL; + bool a = false; + + /* Check if there's an NSEC or NSEC3 RR that proves that the mame we determined is really non-existing, + * i.e between the owner name and the next name of an NSEC RR. */ + r = dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC, name, zone, &a); + if (r <= 0) + return r; + + authenticated = authenticated && a; + + /* Strip one label off */ + r = dns_name_parent(&name); + if (r <= 0) + return r; + + /* Did we reach the source of synthesis? */ + r = dns_name_equal(name, source); + if (r < 0) + return r; + if (r > 0) { + /* Successful exit */ + *_authenticated = authenticated; + return 1; + } + + /* Safety check, that the source of synthesis is still our suffix */ + r = dns_name_endswith(name, source); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + + /* Replace the label we stripped off with an asterisk */ + wc = strappend("*.", name); + if (!wc) + return -ENOMEM; + + /* And check if the proof holds for the asterisk name, too */ + r = dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC, wc, zone, &a); + if (r <= 0) + return r; + + authenticated = authenticated && a; + /* In the next iteration we'll check the non-asterisk-prefixed version */ + } +} + +int dnssec_test_positive_wildcard( + DnsAnswer *answer, + const char *name, + const char *source, + const char *zone, + bool *authenticated) { + + int r; + + assert(name); + assert(source); + assert(zone); + assert(authenticated); + + r = dns_answer_contains_zone_nsec3(answer, zone); + if (r < 0) + return r; + if (r > 0) + return dnssec_test_positive_wildcard_nsec3(answer, name, source, zone, authenticated); + else + return dnssec_test_positive_wildcard_nsec(answer, name, source, zone, authenticated); +} + +#else + +int dnssec_verify_rrset( + DnsAnswer *a, + const DnsResourceKey *key, + DnsResourceRecord *rrsig, + DnsResourceRecord *dnskey, + usec_t realtime, + DnssecResult *result) { + + return -EOPNOTSUPP; +} + +int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) { + + return -EOPNOTSUPP; +} + +int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) { + + return -EOPNOTSUPP; +} + +int dnssec_verify_rrset_search( + DnsAnswer *a, + const DnsResourceKey *key, + DnsAnswer *validated_dnskeys, + usec_t realtime, + DnssecResult *result, + DnsResourceRecord **ret_rrsig) { + + return -EOPNOTSUPP; +} + +int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) { + + return -EOPNOTSUPP; +} + +int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) { + + return -EOPNOTSUPP; +} + +int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) { + + return -EOPNOTSUPP; +} + +int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { + + return -EOPNOTSUPP; +} + +int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { + + return -EOPNOTSUPP; +} + +int dnssec_test_positive_wildcard( + DnsAnswer *answer, + const char *name, + const char *source, + const char *zone, + bool *authenticated) { + + return -EOPNOTSUPP; +} + +#endif + +static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = { + [DNSSEC_VALIDATED] = "validated", + [DNSSEC_VALIDATED_WILDCARD] = "validated-wildcard", + [DNSSEC_INVALID] = "invalid", + [DNSSEC_SIGNATURE_EXPIRED] = "signature-expired", + [DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm", + [DNSSEC_NO_SIGNATURE] = "no-signature", + [DNSSEC_MISSING_KEY] = "missing-key", + [DNSSEC_UNSIGNED] = "unsigned", + [DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary", + [DNSSEC_NSEC_MISMATCH] = "nsec-mismatch", + [DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server", +}; +DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult); + +static const char* const dnssec_verdict_table[_DNSSEC_VERDICT_MAX] = { + [DNSSEC_SECURE] = "secure", + [DNSSEC_INSECURE] = "insecure", + [DNSSEC_BOGUS] = "bogus", + [DNSSEC_INDETERMINATE] = "indeterminate", +}; +DEFINE_STRING_TABLE_LOOKUP(dnssec_verdict, DnssecVerdict); diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-dnssec.h b/src/grp-resolve/systemd-resolved/resolved-dns-dnssec.h new file mode 100644 index 0000000000..77bd4d71bf --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-dnssec.h @@ -0,0 +1,102 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +typedef enum DnssecResult DnssecResult; +typedef enum DnssecVerdict DnssecVerdict; + +#include "dns-domain.h" +#include "resolved-dns-answer.h" +#include "resolved-dns-rr.h" + +enum DnssecResult { + /* These five are returned by dnssec_verify_rrset() */ + DNSSEC_VALIDATED, + DNSSEC_VALIDATED_WILDCARD, /* Validated via a wildcard RRSIG, further NSEC/NSEC3 checks necessary */ + DNSSEC_INVALID, + DNSSEC_SIGNATURE_EXPIRED, + DNSSEC_UNSUPPORTED_ALGORITHM, + + /* These two are added by dnssec_verify_rrset_search() */ + DNSSEC_NO_SIGNATURE, + DNSSEC_MISSING_KEY, + + /* These two are added by the DnsTransaction logic */ + DNSSEC_UNSIGNED, + DNSSEC_FAILED_AUXILIARY, + DNSSEC_NSEC_MISMATCH, + DNSSEC_INCOMPATIBLE_SERVER, + + _DNSSEC_RESULT_MAX, + _DNSSEC_RESULT_INVALID = -1 +}; + +enum DnssecVerdict { + DNSSEC_SECURE, + DNSSEC_INSECURE, + DNSSEC_BOGUS, + DNSSEC_INDETERMINATE, + + _DNSSEC_VERDICT_MAX, + _DNSSEC_VERDICT_INVALID = -1 +}; + +#define DNSSEC_CANONICAL_HOSTNAME_MAX (DNS_HOSTNAME_MAX + 2) + +/* The longest digest we'll ever generate, of all digest algorithms we support */ +#define DNSSEC_HASH_SIZE_MAX (MAX(20, 32)) + +int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok); +int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig); + +int dnssec_verify_rrset(DnsAnswer *answer, const DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime, DnssecResult *result); +int dnssec_verify_rrset_search(DnsAnswer *answer, const DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime, DnssecResult *result, DnsResourceRecord **rrsig); + +int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke); +int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds); + +int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key); + +uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke); + +int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max); + +int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret); + +typedef enum DnssecNsecResult { + DNSSEC_NSEC_NO_RR, /* No suitable NSEC/NSEC3 RR found */ + DNSSEC_NSEC_CNAME, /* Didn't find what was asked for, but did find CNAME */ + DNSSEC_NSEC_UNSUPPORTED_ALGORITHM, + DNSSEC_NSEC_NXDOMAIN, + DNSSEC_NSEC_NODATA, + DNSSEC_NSEC_FOUND, + DNSSEC_NSEC_OPTOUT, +} DnssecNsecResult; + +int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl); + + +int dnssec_test_positive_wildcard(DnsAnswer *a, const char *name, const char *source, const char *zone, bool *authenticated); + +const char* dnssec_result_to_string(DnssecResult m) _const_; +DnssecResult dnssec_result_from_string(const char *s) _pure_; + +const char* dnssec_verdict_to_string(DnssecVerdict m) _const_; +DnssecVerdict dnssec_verdict_from_string(const char *s) _pure_; diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-packet.c b/src/grp-resolve/systemd-resolved/resolved-dns-packet.c new file mode 100644 index 0000000000..b7907bb511 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-packet.c @@ -0,0 +1,2255 @@ +/*** + 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 "alloc-util.h" +#include "dns-domain.h" +#include "resolved-dns-packet.h" +#include "string-table.h" +#include "strv.h" +#include "unaligned.h" +#include "utf8.h" +#include "util.h" + +#define EDNS0_OPT_DO (1<<15) + +typedef struct DnsPacketRewinder { + DnsPacket *packet; + size_t saved_rindex; +} DnsPacketRewinder; + +static void rewind_dns_packet(DnsPacketRewinder *rewinder) { + if (rewinder->packet) + dns_packet_rewind(rewinder->packet, rewinder->saved_rindex); +} + +#define INIT_REWINDER(rewinder, p) do { rewinder.packet = p; rewinder.saved_rindex = p->rindex; } while (0) +#define CANCEL_REWINDER(rewinder) do { rewinder.packet = NULL; } while (0) + +int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { + DnsPacket *p; + size_t a; + + assert(ret); + + if (mtu <= UDP_PACKET_HEADER_SIZE) + a = DNS_PACKET_SIZE_START; + else + a = mtu - UDP_PACKET_HEADER_SIZE; + + if (a < DNS_PACKET_HEADER_SIZE) + a = DNS_PACKET_HEADER_SIZE; + + /* round up to next page size */ + a = PAGE_ALIGN(ALIGN(sizeof(DnsPacket)) + a) - ALIGN(sizeof(DnsPacket)); + + /* make sure we never allocate more than useful */ + if (a > DNS_PACKET_SIZE_MAX) + a = DNS_PACKET_SIZE_MAX; + + p = malloc0(ALIGN(sizeof(DnsPacket)) + a); + if (!p) + return -ENOMEM; + + p->size = p->rindex = DNS_PACKET_HEADER_SIZE; + p->allocated = a; + p->protocol = protocol; + p->opt_start = p->opt_size = (size_t) -1; + p->n_ref = 1; + + *ret = p; + + return 0; +} + +void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated) { + + DnsPacketHeader *h; + + assert(p); + + h = DNS_PACKET_HEADER(p); + + switch(p->protocol) { + case DNS_PROTOCOL_LLMNR: + assert(!truncated); + + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, + 0 /* opcode */, + 0 /* c */, + 0 /* tc */, + 0 /* t */, + 0 /* ra */, + 0 /* ad */, + 0 /* cd */, + 0 /* rcode */)); + break; + + case DNS_PROTOCOL_MDNS: + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, + 0 /* opcode */, + 0 /* aa */, + truncated /* tc */, + 0 /* rd (ask for recursion) */, + 0 /* ra */, + 0 /* ad */, + 0 /* cd */, + 0 /* rcode */)); + break; + + default: + assert(!truncated); + + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, + 0 /* opcode */, + 0 /* aa */, + 0 /* tc */, + 1 /* rd (ask for recursion) */, + 0 /* ra */, + 0 /* ad */, + dnssec_checking_disabled /* cd */, + 0 /* rcode */)); + } +} + +int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled) { + DnsPacket *p; + int r; + + assert(ret); + + r = dns_packet_new(&p, protocol, mtu); + if (r < 0) + return r; + + /* Always set the TC bit to 0 initially. + * If there are multiple packets later, we'll update the bit shortly before sending. + */ + dns_packet_set_flags(p, dnssec_checking_disabled, false); + + *ret = p; + return 0; +} + +DnsPacket *dns_packet_ref(DnsPacket *p) { + + if (!p) + return NULL; + + assert(!p->on_stack); + + assert(p->n_ref > 0); + p->n_ref++; + return p; +} + +static void dns_packet_free(DnsPacket *p) { + char *s; + + assert(p); + + dns_question_unref(p->question); + dns_answer_unref(p->answer); + dns_resource_record_unref(p->opt); + + while ((s = hashmap_steal_first_key(p->names))) + free(s); + hashmap_free(p->names); + + free(p->_data); + + if (!p->on_stack) + free(p); +} + +DnsPacket *dns_packet_unref(DnsPacket *p) { + if (!p) + return NULL; + + assert(p->n_ref > 0); + + dns_packet_unref(p->more); + + if (p->n_ref == 1) + dns_packet_free(p); + else + p->n_ref--; + + return NULL; +} + +int dns_packet_validate(DnsPacket *p) { + assert(p); + + if (p->size < DNS_PACKET_HEADER_SIZE) + return -EBADMSG; + + if (p->size > DNS_PACKET_SIZE_MAX) + return -EBADMSG; + + return 1; +} + +int dns_packet_validate_reply(DnsPacket *p) { + int r; + + assert(p); + + r = dns_packet_validate(p); + if (r < 0) + return r; + + if (DNS_PACKET_QR(p) != 1) + return 0; + + if (DNS_PACKET_OPCODE(p) != 0) + return -EBADMSG; + + switch (p->protocol) { + + case DNS_PROTOCOL_LLMNR: + /* RFC 4795, Section 2.1.1. says to discard all replies with QDCOUNT != 1 */ + if (DNS_PACKET_QDCOUNT(p) != 1) + return -EBADMSG; + + break; + + case DNS_PROTOCOL_MDNS: + /* RFC 6762, Section 18 */ + if (DNS_PACKET_RCODE(p) != 0) + return -EBADMSG; + + break; + + default: + break; + } + + return 1; +} + +int dns_packet_validate_query(DnsPacket *p) { + int r; + + assert(p); + + r = dns_packet_validate(p); + if (r < 0) + return r; + + if (DNS_PACKET_QR(p) != 0) + return 0; + + if (DNS_PACKET_OPCODE(p) != 0) + return -EBADMSG; + + if (DNS_PACKET_TC(p)) + return -EBADMSG; + + switch (p->protocol) { + + case DNS_PROTOCOL_LLMNR: + /* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */ + if (DNS_PACKET_QDCOUNT(p) != 1) + return -EBADMSG; + + /* RFC 4795, Section 2.1.1. says to discard all queries with ANCOUNT != 0 */ + if (DNS_PACKET_ANCOUNT(p) > 0) + return -EBADMSG; + + /* RFC 4795, Section 2.1.1. says to discard all queries with NSCOUNT != 0 */ + if (DNS_PACKET_NSCOUNT(p) > 0) + return -EBADMSG; + + break; + + case DNS_PROTOCOL_MDNS: + /* RFC 6762, Section 18 */ + if (DNS_PACKET_AA(p) != 0 || + DNS_PACKET_RD(p) != 0 || + DNS_PACKET_RA(p) != 0 || + DNS_PACKET_AD(p) != 0 || + DNS_PACKET_CD(p) != 0 || + DNS_PACKET_RCODE(p) != 0) + return -EBADMSG; + + break; + + default: + break; + } + + return 1; +} + +static int dns_packet_extend(DnsPacket *p, size_t add, void **ret, size_t *start) { + assert(p); + + if (p->size + add > p->allocated) { + size_t a; + + a = PAGE_ALIGN((p->size + add) * 2); + if (a > DNS_PACKET_SIZE_MAX) + a = DNS_PACKET_SIZE_MAX; + + if (p->size + add > a) + return -EMSGSIZE; + + if (p->_data) { + void *d; + + d = realloc(p->_data, a); + if (!d) + return -ENOMEM; + + p->_data = d; + } else { + p->_data = malloc(a); + if (!p->_data) + return -ENOMEM; + + memcpy(p->_data, (uint8_t*) p + ALIGN(sizeof(DnsPacket)), p->size); + memzero((uint8_t*) p->_data + p->size, a - p->size); + } + + p->allocated = a; + } + + if (start) + *start = p->size; + + if (ret) + *ret = (uint8_t*) DNS_PACKET_DATA(p) + p->size; + + p->size += add; + return 0; +} + +void dns_packet_truncate(DnsPacket *p, size_t sz) { + Iterator i; + char *s; + void *n; + + assert(p); + + if (p->size <= sz) + return; + + HASHMAP_FOREACH_KEY(n, s, p->names, i) { + + if (PTR_TO_SIZE(n) < sz) + continue; + + hashmap_remove(p->names, s); + free(s); + } + + p->size = sz; +} + +int dns_packet_append_blob(DnsPacket *p, const void *d, size_t l, size_t *start) { + void *q; + int r; + + assert(p); + + r = dns_packet_extend(p, l, &q, start); + if (r < 0) + return r; + + memcpy(q, d, l); + return 0; +} + +int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start) { + void *d; + int r; + + assert(p); + + r = dns_packet_extend(p, sizeof(uint8_t), &d, start); + if (r < 0) + return r; + + ((uint8_t*) d)[0] = v; + + return 0; +} + +int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start) { + void *d; + int r; + + assert(p); + + r = dns_packet_extend(p, sizeof(uint16_t), &d, start); + if (r < 0) + return r; + + unaligned_write_be16(d, v); + + return 0; +} + +int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start) { + void *d; + int r; + + assert(p); + + r = dns_packet_extend(p, sizeof(uint32_t), &d, start); + if (r < 0) + return r; + + unaligned_write_be32(d, v); + + return 0; +} + +int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start) { + assert(p); + assert(s); + + return dns_packet_append_raw_string(p, s, strlen(s), start); +} + +int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start) { + void *d; + int r; + + assert(p); + assert(s || size == 0); + + if (size > 255) + return -E2BIG; + + r = dns_packet_extend(p, 1 + size, &d, start); + if (r < 0) + return r; + + ((uint8_t*) d)[0] = (uint8_t) size; + + memcpy_safe(((uint8_t*) d) + 1, s, size); + + return 0; +} + +int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, bool canonical_candidate, size_t *start) { + uint8_t *w; + int r; + + /* Append a label to a packet. Optionally, does this in DNSSEC + * canonical form, if this label is marked as a candidate for + * it, and the canonical form logic is enabled for the + * packet */ + + assert(p); + assert(d); + + if (l > DNS_LABEL_MAX) + return -E2BIG; + + r = dns_packet_extend(p, 1 + l, (void**) &w, start); + if (r < 0) + return r; + + *(w++) = (uint8_t) l; + + if (p->canonical_form && canonical_candidate) { + size_t i; + + /* Generate in canonical form, as defined by DNSSEC + * RFC 4034, Section 6.2, i.e. all lower-case. */ + + for (i = 0; i < l; i++) + w[i] = (uint8_t) ascii_tolower(d[i]); + } else + /* Otherwise, just copy the string unaltered. This is + * essential for DNS-SD, where the casing of labels + * matters and needs to be retained. */ + memcpy(w, d, l); + + return 0; +} + +int dns_packet_append_name( + DnsPacket *p, + const char *name, + bool allow_compression, + bool canonical_candidate, + size_t *start) { + + size_t saved_size; + int r; + + assert(p); + assert(name); + + if (p->refuse_compression) + allow_compression = false; + + saved_size = p->size; + + while (!dns_name_is_root(name)) { + const char *z = name; + char label[DNS_LABEL_MAX]; + size_t n = 0; + + if (allow_compression) + n = PTR_TO_SIZE(hashmap_get(p->names, name)); + if (n > 0) { + assert(n < p->size); + + if (n < 0x4000) { + r = dns_packet_append_uint16(p, 0xC000 | n, NULL); + if (r < 0) + goto fail; + + goto done; + } + } + + r = dns_label_unescape(&name, label, sizeof(label)); + if (r < 0) + goto fail; + + r = dns_packet_append_label(p, label, r, canonical_candidate, &n); + if (r < 0) + goto fail; + + if (allow_compression) { + _cleanup_free_ char *s = NULL; + + s = strdup(z); + if (!s) { + r = -ENOMEM; + goto fail; + } + + r = hashmap_ensure_allocated(&p->names, &dns_name_hash_ops); + if (r < 0) + goto fail; + + r = hashmap_put(p->names, s, SIZE_TO_PTR(n)); + if (r < 0) + goto fail; + + s = NULL; + } + } + + r = dns_packet_append_uint8(p, 0, NULL); + if (r < 0) + return r; + +done: + if (start) + *start = saved_size; + + return 0; + +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start) { + size_t saved_size; + int r; + + assert(p); + assert(k); + + saved_size = p->size; + + r = dns_packet_append_name(p, dns_resource_key_name(k), true, true, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, k->type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, k->class, NULL); + if (r < 0) + goto fail; + + if (start) + *start = saved_size; + + return 0; + +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, const uint8_t *types, size_t *start) { + size_t saved_size; + int r; + + assert(p); + assert(types); + assert(length > 0); + + saved_size = p->size; + + r = dns_packet_append_uint8(p, window, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, length, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, types, length, NULL); + if (r < 0) + goto fail; + + if (start) + *start = saved_size; + + return 0; +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) { + Iterator i; + uint8_t window = 0; + uint8_t entry = 0; + uint8_t bitmaps[32] = {}; + unsigned n; + size_t saved_size; + int r; + + assert(p); + + saved_size = p->size; + + BITMAP_FOREACH(n, types, i) { + assert(n <= 0xffff); + + if ((n >> 8) != window && bitmaps[entry / 8] != 0) { + r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); + if (r < 0) + goto fail; + + zero(bitmaps); + } + + window = n >> 8; + entry = n & 255; + + bitmaps[entry / 8] |= 1 << (7 - (entry % 8)); + } + + if (bitmaps[entry / 8] != 0) { + r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); + if (r < 0) + goto fail; + } + + if (start) + *start = saved_size; + + return 0; +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +/* Append the OPT pseudo-RR described in RFC6891 */ +int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start) { + size_t saved_size; + int r; + + assert(p); + /* we must never advertise supported packet size smaller than the legacy max */ + assert(max_udp_size >= DNS_PACKET_UNICAST_SIZE_MAX); + + if (p->opt_start != (size_t) -1) + return -EBUSY; + + assert(p->opt_size == (size_t) -1); + + saved_size = p->size; + + /* empty name */ + r = dns_packet_append_uint8(p, 0, NULL); + if (r < 0) + return r; + + /* type */ + r = dns_packet_append_uint16(p, DNS_TYPE_OPT, NULL); + if (r < 0) + goto fail; + + /* maximum udp packet that can be received */ + r = dns_packet_append_uint16(p, max_udp_size, NULL); + if (r < 0) + goto fail; + + /* extended RCODE and VERSION */ + r = dns_packet_append_uint16(p, 0, NULL); + if (r < 0) + goto fail; + + /* flags: DNSSEC OK (DO), see RFC3225 */ + r = dns_packet_append_uint16(p, edns0_do ? EDNS0_OPT_DO : 0, NULL); + if (r < 0) + goto fail; + + /* RDLENGTH */ + + if (edns0_do) { + /* If DO is on, also append RFC6975 Algorithm data */ + + static const uint8_t rfc6975[] = { + + 0, 5, /* OPTION_CODE: DAU */ + 0, 6, /* LIST_LENGTH */ + DNSSEC_ALGORITHM_RSASHA1, + DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, + DNSSEC_ALGORITHM_RSASHA256, + DNSSEC_ALGORITHM_RSASHA512, + DNSSEC_ALGORITHM_ECDSAP256SHA256, + DNSSEC_ALGORITHM_ECDSAP384SHA384, + + 0, 6, /* OPTION_CODE: DHU */ + 0, 3, /* LIST_LENGTH */ + DNSSEC_DIGEST_SHA1, + DNSSEC_DIGEST_SHA256, + DNSSEC_DIGEST_SHA384, + + 0, 7, /* OPTION_CODE: N3U */ + 0, 1, /* LIST_LENGTH */ + NSEC3_ALGORITHM_SHA1, + }; + + r = dns_packet_append_uint16(p, sizeof(rfc6975), NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rfc6975, sizeof(rfc6975), NULL); + } else + r = dns_packet_append_uint16(p, 0, NULL); + + if (r < 0) + goto fail; + + DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) + 1); + + p->opt_start = saved_size; + p->opt_size = p->size - saved_size; + + if (start) + *start = saved_size; + + return 0; + +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +int dns_packet_truncate_opt(DnsPacket *p) { + assert(p); + + if (p->opt_start == (size_t) -1) { + assert(p->opt_size == (size_t) -1); + return 0; + } + + assert(p->opt_size != (size_t) -1); + assert(DNS_PACKET_ARCOUNT(p) > 0); + + if (p->opt_start + p->opt_size != p->size) + return -EBUSY; + + dns_packet_truncate(p, p->opt_start); + DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) - 1); + p->opt_start = p->opt_size = (size_t) -1; + + return 1; +} + +int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start) { + size_t saved_size, rdlength_offset, end, rdlength, rds; + int r; + + assert(p); + assert(rr); + + saved_size = p->size; + + r = dns_packet_append_key(p, rr->key, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->ttl, NULL); + if (r < 0) + goto fail; + + /* Initially we write 0 here */ + r = dns_packet_append_uint16(p, 0, &rdlength_offset); + if (r < 0) + goto fail; + + rds = p->size - saved_size; + + switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { + + case DNS_TYPE_SRV: + r = dns_packet_append_uint16(p, rr->srv.priority, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->srv.weight, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->srv.port, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->srv.name, true, false, NULL); + break; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + r = dns_packet_append_name(p, rr->ptr.name, true, false, NULL); + break; + + case DNS_TYPE_HINFO: + r = dns_packet_append_string(p, rr->hinfo.cpu, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_string(p, rr->hinfo.os, NULL); + break; + + case DNS_TYPE_SPF: /* exactly the same as TXT */ + case DNS_TYPE_TXT: + + if (!rr->txt.items) { + /* RFC 6763, section 6.1 suggests to generate + * single empty string for an empty array. */ + + r = dns_packet_append_raw_string(p, NULL, 0, NULL); + if (r < 0) + goto fail; + } else { + DnsTxtItem *i; + + LIST_FOREACH(items, i, rr->txt.items) { + r = dns_packet_append_raw_string(p, i->data, i->length, NULL); + if (r < 0) + goto fail; + } + } + + r = 0; + break; + + case DNS_TYPE_A: + r = dns_packet_append_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL); + break; + + case DNS_TYPE_AAAA: + r = dns_packet_append_blob(p, &rr->aaaa.in6_addr, sizeof(struct in6_addr), NULL); + break; + + case DNS_TYPE_SOA: + r = dns_packet_append_name(p, rr->soa.mname, true, false, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->soa.rname, true, false, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.serial, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.refresh, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.retry, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.expire, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.minimum, NULL); + break; + + case DNS_TYPE_MX: + r = dns_packet_append_uint16(p, rr->mx.priority, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->mx.exchange, true, false, NULL); + break; + + case DNS_TYPE_LOC: + r = dns_packet_append_uint8(p, rr->loc.version, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->loc.size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->loc.horiz_pre, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->loc.vert_pre, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->loc.latitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->loc.longitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->loc.altitude, NULL); + break; + + case DNS_TYPE_DS: + r = dns_packet_append_uint16(p, rr->ds.key_tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->ds.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->ds.digest_type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->ds.digest, rr->ds.digest_size, NULL); + break; + + case DNS_TYPE_SSHFP: + r = dns_packet_append_uint8(p, rr->sshfp.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->sshfp.fptype, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, NULL); + break; + + case DNS_TYPE_DNSKEY: + r = dns_packet_append_uint16(p, rr->dnskey.flags, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->dnskey.protocol, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->dnskey.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->dnskey.key, rr->dnskey.key_size, NULL); + break; + + case DNS_TYPE_RRSIG: + r = dns_packet_append_uint16(p, rr->rrsig.type_covered, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->rrsig.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->rrsig.labels, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->rrsig.original_ttl, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->rrsig.expiration, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->rrsig.inception, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->rrsig.key_tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->rrsig.signer, false, true, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->rrsig.signature, rr->rrsig.signature_size, NULL); + break; + + case DNS_TYPE_NSEC: + r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, false, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_types(p, rr->nsec.types, NULL); + if (r < 0) + goto fail; + + break; + + case DNS_TYPE_NSEC3: + r = dns_packet_append_uint8(p, rr->nsec3.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->nsec3.flags, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->nsec3.iterations, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->nsec3.salt_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->nsec3.salt, rr->nsec3.salt_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->nsec3.next_hashed_name_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_types(p, rr->nsec3.types, NULL); + if (r < 0) + goto fail; + + break; + + case DNS_TYPE_TLSA: + r = dns_packet_append_uint8(p, rr->tlsa.cert_usage, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->tlsa.selector, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->tlsa.matching_type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->tlsa.data, rr->tlsa.data_size, NULL); + break; + + case DNS_TYPE_CAA: + r = dns_packet_append_uint8(p, rr->caa.flags, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_string(p, rr->caa.tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->caa.value, rr->caa.value_size, NULL); + break; + + case DNS_TYPE_OPT: + case DNS_TYPE_OPENPGPKEY: + case _DNS_TYPE_INVALID: /* unparseable */ + default: + + r = dns_packet_append_blob(p, rr->generic.data, rr->generic.data_size, NULL); + break; + } + if (r < 0) + goto fail; + + /* Let's calculate the actual data size and update the field */ + rdlength = p->size - rdlength_offset - sizeof(uint16_t); + if (rdlength > 0xFFFF) { + r = -ENOSPC; + goto fail; + } + + end = p->size; + p->size = rdlength_offset; + r = dns_packet_append_uint16(p, rdlength, NULL); + if (r < 0) + goto fail; + p->size = end; + + if (start) + *start = saved_size; + + if (rdata_start) + *rdata_start = rds; + + return 0; + +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) { + assert(p); + + if (p->rindex + sz > p->size) + return -EMSGSIZE; + + if (ret) + *ret = (uint8_t*) DNS_PACKET_DATA(p) + p->rindex; + + if (start) + *start = p->rindex; + + p->rindex += sz; + return 0; +} + +void dns_packet_rewind(DnsPacket *p, size_t idx) { + assert(p); + assert(idx <= p->size); + assert(idx >= DNS_PACKET_HEADER_SIZE); + + p->rindex = idx; +} + +int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start) { + const void *q; + int r; + + assert(p); + assert(d); + + r = dns_packet_read(p, sz, &q, start); + if (r < 0) + return r; + + memcpy(d, q, sz); + return 0; +} + +static int dns_packet_read_memdup( + DnsPacket *p, size_t size, + void **ret, size_t *ret_size, + size_t *ret_start) { + + const void *src; + size_t start; + int r; + + assert(p); + assert(ret); + + r = dns_packet_read(p, size, &src, &start); + if (r < 0) + return r; + + if (size <= 0) + *ret = NULL; + else { + void *copy; + + copy = memdup(src, size); + if (!copy) + return -ENOMEM; + + *ret = copy; + } + + if (ret_size) + *ret_size = size; + if (ret_start) + *ret_start = start; + + return 0; +} + +int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start) { + const void *d; + int r; + + assert(p); + + r = dns_packet_read(p, sizeof(uint8_t), &d, start); + if (r < 0) + return r; + + *ret = ((uint8_t*) d)[0]; + return 0; +} + +int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start) { + const void *d; + int r; + + assert(p); + + r = dns_packet_read(p, sizeof(uint16_t), &d, start); + if (r < 0) + return r; + + *ret = unaligned_read_be16(d); + + return 0; +} + +int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start) { + const void *d; + int r; + + assert(p); + + r = dns_packet_read(p, sizeof(uint32_t), &d, start); + if (r < 0) + return r; + + *ret = unaligned_read_be32(d); + + return 0; +} + +int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start) { + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; + const void *d; + char *t; + uint8_t c; + int r; + + assert(p); + INIT_REWINDER(rewinder, p); + + r = dns_packet_read_uint8(p, &c, NULL); + if (r < 0) + return r; + + r = dns_packet_read(p, c, &d, NULL); + if (r < 0) + return r; + + if (memchr(d, 0, c)) + return -EBADMSG; + + t = strndup(d, c); + if (!t) + return -ENOMEM; + + if (!utf8_is_valid(t)) { + free(t); + return -EBADMSG; + } + + *ret = t; + + if (start) + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); + + return 0; +} + +int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start) { + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; + uint8_t c; + int r; + + assert(p); + INIT_REWINDER(rewinder, p); + + r = dns_packet_read_uint8(p, &c, NULL); + if (r < 0) + return r; + + r = dns_packet_read(p, c, ret, NULL); + if (r < 0) + return r; + + if (size) + *size = c; + if (start) + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); + + return 0; +} + +int dns_packet_read_name( + DnsPacket *p, + char **_ret, + bool allow_compression, + size_t *start) { + + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; + size_t after_rindex = 0, jump_barrier; + _cleanup_free_ char *ret = NULL; + size_t n = 0, allocated = 0; + bool first = true; + int r; + + assert(p); + assert(_ret); + INIT_REWINDER(rewinder, p); + jump_barrier = p->rindex; + + if (p->refuse_compression) + allow_compression = false; + + for (;;) { + uint8_t c, d; + + r = dns_packet_read_uint8(p, &c, NULL); + if (r < 0) + return r; + + if (c == 0) + /* End of name */ + break; + else if (c <= 63) { + const char *label; + + /* Literal label */ + r = dns_packet_read(p, c, (const void**) &label, NULL); + if (r < 0) + return r; + + if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + if (first) + first = false; + else + ret[n++] = '.'; + + r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + n += r; + continue; + } else if (allow_compression && (c & 0xc0) == 0xc0) { + uint16_t ptr; + + /* Pointer */ + r = dns_packet_read_uint8(p, &d, NULL); + if (r < 0) + return r; + + ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d; + if (ptr < DNS_PACKET_HEADER_SIZE || ptr >= jump_barrier) + return -EBADMSG; + + if (after_rindex == 0) + after_rindex = p->rindex; + + /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */ + jump_barrier = ptr; + p->rindex = ptr; + } else + return -EBADMSG; + } + + if (!GREEDY_REALLOC(ret, allocated, n + 1)) + return -ENOMEM; + + ret[n] = 0; + + if (after_rindex != 0) + p->rindex= after_rindex; + + *_ret = ret; + ret = NULL; + + if (start) + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); + + return 0; +} + +static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *start) { + uint8_t window; + uint8_t length; + const uint8_t *bitmap; + uint8_t bit = 0; + unsigned i; + bool found = false; + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; + int r; + + assert(p); + assert(types); + INIT_REWINDER(rewinder, p); + + r = bitmap_ensure_allocated(types); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &window, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &length, NULL); + if (r < 0) + return r; + + if (length == 0 || length > 32) + return -EBADMSG; + + r = dns_packet_read(p, length, (const void **)&bitmap, NULL); + if (r < 0) + return r; + + for (i = 0; i < length; i++) { + uint8_t bitmask = 1 << 7; + + if (!bitmap[i]) { + found = false; + bit += 8; + continue; + } + + found = true; + + while (bitmask) { + if (bitmap[i] & bitmask) { + uint16_t n; + + n = (uint16_t) window << 8 | (uint16_t) bit; + + /* Ignore pseudo-types. see RFC4034 section 4.1.2 */ + if (dns_type_is_pseudo(n)) + continue; + + r = bitmap_set(*types, n); + if (r < 0) + return r; + } + + bit++; + bitmask >>= 1; + } + } + + if (!found) + return -EBADMSG; + + if (start) + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); + + return 0; +} + +static int dns_packet_read_type_windows(DnsPacket *p, Bitmap **types, size_t size, size_t *start) { + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; + int r; + + INIT_REWINDER(rewinder, p); + + while (p->rindex < rewinder.saved_rindex + size) { + r = dns_packet_read_type_window(p, types, NULL); + if (r < 0) + return r; + + /* don't read past end of current RR */ + if (p->rindex > rewinder.saved_rindex + size) + return -EBADMSG; + } + + if (p->rindex != rewinder.saved_rindex + size) + return -EBADMSG; + + if (start) + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); + + return 0; +} + +int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start) { + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; + _cleanup_free_ char *name = NULL; + bool cache_flush = false; + uint16_t class, type; + DnsResourceKey *key; + int r; + + assert(p); + assert(ret); + INIT_REWINDER(rewinder, p); + + r = dns_packet_read_name(p, &name, true, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint16(p, &type, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint16(p, &class, NULL); + if (r < 0) + return r; + + if (p->protocol == DNS_PROTOCOL_MDNS) { + /* See RFC6762, Section 10.2 */ + + if (type != DNS_TYPE_OPT && (class & MDNS_RR_CACHE_FLUSH)) { + class &= ~MDNS_RR_CACHE_FLUSH; + cache_flush = true; + } + } + + key = dns_resource_key_new_consume(class, type, name); + if (!key) + return -ENOMEM; + + name = NULL; + *ret = key; + + if (ret_cache_flush) + *ret_cache_flush = cache_flush; + if (start) + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); + + return 0; +} + +static bool loc_size_ok(uint8_t size) { + uint8_t m = size >> 4, e = size & 0xF; + + return m <= 9 && e <= 9 && (m > 0 || e == 0); +} + +int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; + size_t offset; + uint16_t rdlength; + bool cache_flush; + int r; + + assert(p); + assert(ret); + + INIT_REWINDER(rewinder, p); + + r = dns_packet_read_key(p, &key, &cache_flush, NULL); + if (r < 0) + return r; + + if (!dns_class_is_valid_rr(key->class) || !dns_type_is_valid_rr(key->type)) + return -EBADMSG; + + rr = dns_resource_record_new(key); + if (!rr) + return -ENOMEM; + + r = dns_packet_read_uint32(p, &rr->ttl, NULL); + if (r < 0) + return r; + + /* RFC 2181, Section 8, suggests to + * treat a TTL with the MSB set as a zero TTL. */ + if (rr->ttl & UINT32_C(0x80000000)) + rr->ttl = 0; + + r = dns_packet_read_uint16(p, &rdlength, NULL); + if (r < 0) + return r; + + if (p->rindex + rdlength > p->size) + return -EBADMSG; + + offset = p->rindex; + + switch (rr->key->type) { + + case DNS_TYPE_SRV: + r = dns_packet_read_uint16(p, &rr->srv.priority, NULL); + if (r < 0) + return r; + r = dns_packet_read_uint16(p, &rr->srv.weight, NULL); + if (r < 0) + return r; + r = dns_packet_read_uint16(p, &rr->srv.port, NULL); + if (r < 0) + return r; + r = dns_packet_read_name(p, &rr->srv.name, true, NULL); + break; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + r = dns_packet_read_name(p, &rr->ptr.name, true, NULL); + break; + + case DNS_TYPE_HINFO: + r = dns_packet_read_string(p, &rr->hinfo.cpu, NULL); + if (r < 0) + return r; + + r = dns_packet_read_string(p, &rr->hinfo.os, NULL); + break; + + case DNS_TYPE_SPF: /* exactly the same as TXT */ + case DNS_TYPE_TXT: + if (rdlength <= 0) { + DnsTxtItem *i; + /* RFC 6763, section 6.1 suggests to treat + * empty TXT RRs as equivalent to a TXT record + * with a single empty string. */ + + i = malloc0(offsetof(DnsTxtItem, data) + 1); /* for safety reasons we add an extra NUL byte */ + if (!i) + return -ENOMEM; + + rr->txt.items = i; + } else { + DnsTxtItem *last = NULL; + + while (p->rindex < offset + rdlength) { + DnsTxtItem *i; + const void *data; + size_t sz; + + r = dns_packet_read_raw_string(p, &data, &sz, NULL); + if (r < 0) + return r; + + i = malloc0(offsetof(DnsTxtItem, data) + sz + 1); /* extra NUL byte at the end */ + if (!i) + return -ENOMEM; + + memcpy(i->data, data, sz); + i->length = sz; + + LIST_INSERT_AFTER(items, rr->txt.items, last, i); + last = i; + } + } + + r = 0; + break; + + case DNS_TYPE_A: + r = dns_packet_read_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL); + break; + + case DNS_TYPE_AAAA: + r = dns_packet_read_blob(p, &rr->aaaa.in6_addr, sizeof(struct in6_addr), NULL); + break; + + case DNS_TYPE_SOA: + r = dns_packet_read_name(p, &rr->soa.mname, true, NULL); + if (r < 0) + return r; + + r = dns_packet_read_name(p, &rr->soa.rname, true, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &rr->soa.serial, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &rr->soa.refresh, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &rr->soa.retry, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &rr->soa.expire, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &rr->soa.minimum, NULL); + break; + + case DNS_TYPE_MX: + r = dns_packet_read_uint16(p, &rr->mx.priority, NULL); + if (r < 0) + return r; + + r = dns_packet_read_name(p, &rr->mx.exchange, true, NULL); + break; + + case DNS_TYPE_LOC: { + uint8_t t; + size_t pos; + + r = dns_packet_read_uint8(p, &t, &pos); + if (r < 0) + return r; + + if (t == 0) { + rr->loc.version = t; + + r = dns_packet_read_uint8(p, &rr->loc.size, NULL); + if (r < 0) + return r; + + if (!loc_size_ok(rr->loc.size)) + return -EBADMSG; + + r = dns_packet_read_uint8(p, &rr->loc.horiz_pre, NULL); + if (r < 0) + return r; + + if (!loc_size_ok(rr->loc.horiz_pre)) + return -EBADMSG; + + r = dns_packet_read_uint8(p, &rr->loc.vert_pre, NULL); + if (r < 0) + return r; + + if (!loc_size_ok(rr->loc.vert_pre)) + return -EBADMSG; + + r = dns_packet_read_uint32(p, &rr->loc.latitude, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &rr->loc.longitude, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &rr->loc.altitude, NULL); + if (r < 0) + return r; + + break; + } else { + dns_packet_rewind(p, pos); + rr->unparseable = true; + goto unparseable; + } + } + + case DNS_TYPE_DS: + r = dns_packet_read_uint16(p, &rr->ds.key_tag, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &rr->ds.algorithm, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &rr->ds.digest_type, NULL); + if (r < 0) + return r; + + r = dns_packet_read_memdup(p, rdlength - 4, + &rr->ds.digest, &rr->ds.digest_size, + NULL); + if (r < 0) + return r; + + if (rr->ds.digest_size <= 0) + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + return -EBADMSG; + + break; + + case DNS_TYPE_SSHFP: + r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &rr->sshfp.fptype, NULL); + if (r < 0) + return r; + + r = dns_packet_read_memdup(p, rdlength - 2, + &rr->sshfp.fingerprint, &rr->sshfp.fingerprint_size, + NULL); + + if (rr->sshfp.fingerprint_size <= 0) + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + return -EBADMSG; + + break; + + case DNS_TYPE_DNSKEY: + r = dns_packet_read_uint16(p, &rr->dnskey.flags, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &rr->dnskey.protocol, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &rr->dnskey.algorithm, NULL); + if (r < 0) + return r; + + r = dns_packet_read_memdup(p, rdlength - 4, + &rr->dnskey.key, &rr->dnskey.key_size, + NULL); + + if (rr->dnskey.key_size <= 0) + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + return -EBADMSG; + + break; + + case DNS_TYPE_RRSIG: + r = dns_packet_read_uint16(p, &rr->rrsig.type_covered, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &rr->rrsig.algorithm, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &rr->rrsig.labels, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &rr->rrsig.original_ttl, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &rr->rrsig.expiration, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &rr->rrsig.inception, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint16(p, &rr->rrsig.key_tag, NULL); + if (r < 0) + return r; + + r = dns_packet_read_name(p, &rr->rrsig.signer, false, NULL); + if (r < 0) + return r; + + r = dns_packet_read_memdup(p, offset + rdlength - p->rindex, + &rr->rrsig.signature, &rr->rrsig.signature_size, + NULL); + + if (rr->rrsig.signature_size <= 0) + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + return -EBADMSG; + + break; + + case DNS_TYPE_NSEC: { + + /* + * RFC6762, section 18.14 explictly states mDNS should use name compression. + * This contradicts RFC3845, section 2.1.1 + */ + + bool allow_compressed = p->protocol == DNS_PROTOCOL_MDNS; + + r = dns_packet_read_name(p, &rr->nsec.next_domain_name, allow_compressed, NULL); + if (r < 0) + return r; + + r = dns_packet_read_type_windows(p, &rr->nsec.types, offset + rdlength - p->rindex, NULL); + + /* We accept empty NSEC bitmaps. The bit indicating the presence of the NSEC record itself + * is redundant and in e.g., RFC4956 this fact is used to define a use for NSEC records + * without the NSEC bit set. */ + + break; + } + case DNS_TYPE_NSEC3: { + uint8_t size; + + r = dns_packet_read_uint8(p, &rr->nsec3.algorithm, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &rr->nsec3.flags, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint16(p, &rr->nsec3.iterations, NULL); + if (r < 0) + return r; + + /* this may be zero */ + r = dns_packet_read_uint8(p, &size, NULL); + if (r < 0) + return r; + + r = dns_packet_read_memdup(p, size, &rr->nsec3.salt, &rr->nsec3.salt_size, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &size, NULL); + if (r < 0) + return r; + + if (size <= 0) + return -EBADMSG; + + r = dns_packet_read_memdup(p, size, + &rr->nsec3.next_hashed_name, &rr->nsec3.next_hashed_name_size, + NULL); + if (r < 0) + return r; + + r = dns_packet_read_type_windows(p, &rr->nsec3.types, offset + rdlength - p->rindex, NULL); + + /* empty non-terminals can have NSEC3 records, so empty bitmaps are allowed */ + + break; + } + + case DNS_TYPE_TLSA: + r = dns_packet_read_uint8(p, &rr->tlsa.cert_usage, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &rr->tlsa.selector, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &rr->tlsa.matching_type, NULL); + if (r < 0) + return r; + + r = dns_packet_read_memdup(p, rdlength - 3, + &rr->tlsa.data, &rr->tlsa.data_size, + NULL); + + if (rr->tlsa.data_size <= 0) + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + return -EBADMSG; + + break; + + case DNS_TYPE_CAA: + r = dns_packet_read_uint8(p, &rr->caa.flags, NULL); + if (r < 0) + return r; + + r = dns_packet_read_string(p, &rr->caa.tag, NULL); + if (r < 0) + return r; + + r = dns_packet_read_memdup(p, + rdlength + offset - p->rindex, + &rr->caa.value, &rr->caa.value_size, NULL); + + break; + + case DNS_TYPE_OPT: /* we only care about the header of OPT for now. */ + case DNS_TYPE_OPENPGPKEY: + default: + unparseable: + r = dns_packet_read_memdup(p, rdlength, &rr->generic.data, &rr->generic.data_size, NULL); + + break; + } + if (r < 0) + return r; + if (p->rindex != offset + rdlength) + return -EBADMSG; + + *ret = rr; + rr = NULL; + + if (ret_cache_flush) + *ret_cache_flush = cache_flush; + if (start) + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); + + return 0; +} + +static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) { + const uint8_t* p; + bool found_dau_dhu_n3u = false; + size_t l; + + /* Checks whether the specified OPT RR is well-formed and whether it contains RFC6975 data (which is not OK in + * a reply). */ + + assert(rr); + assert(rr->key->type == DNS_TYPE_OPT); + + /* Check that the version is 0 */ + if (((rr->ttl >> 16) & UINT32_C(0xFF)) != 0) + return false; + + p = rr->opt.data; + l = rr->opt.data_size; + while (l > 0) { + uint16_t option_code, option_length; + + /* At least four bytes for OPTION-CODE and OPTION-LENGTH are required */ + if (l < 4U) + return false; + + option_code = unaligned_read_be16(p); + option_length = unaligned_read_be16(p + 2); + + if (l < option_length + 4U) + return false; + + /* RFC 6975 DAU, DHU or N3U fields found. */ + if (IN_SET(option_code, 5, 6, 7)) + found_dau_dhu_n3u = true; + + p += option_length + 4U; + l -= option_length + 4U; + } + + *rfc6975 = found_dau_dhu_n3u; + return true; +} + +int dns_packet_extract(DnsPacket *p) { + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = {}; + unsigned n, i; + int r; + + if (p->extracted) + return 0; + + INIT_REWINDER(rewinder, p); + dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE); + + n = DNS_PACKET_QDCOUNT(p); + if (n > 0) { + question = dns_question_new(n); + if (!question) + return -ENOMEM; + + for (i = 0; i < n; i++) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + bool cache_flush; + + r = dns_packet_read_key(p, &key, &cache_flush, NULL); + if (r < 0) + return r; + + if (cache_flush) + return -EBADMSG; + + if (!dns_type_is_valid_query(key->type)) + return -EBADMSG; + + r = dns_question_add(question, key); + if (r < 0) + return r; + } + } + + n = DNS_PACKET_RRCOUNT(p); + if (n > 0) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *previous = NULL; + bool bad_opt = false; + + answer = dns_answer_new(n); + if (!answer) + return -ENOMEM; + + for (i = 0; i < n; i++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + bool cache_flush; + + r = dns_packet_read_rr(p, &rr, &cache_flush, NULL); + if (r < 0) + return r; + + /* Try to reduce memory usage a bit */ + if (previous) + dns_resource_key_reduce(&rr->key, &previous->key); + + if (rr->key->type == DNS_TYPE_OPT) { + bool has_rfc6975; + + if (p->opt || bad_opt) { + /* Multiple OPT RRs? if so, let's ignore all, because there's something wrong + * with the server, and if one is valid we wouldn't know which one. */ + log_debug("Multiple OPT RRs detected, ignoring all."); + bad_opt = true; + continue; + } + + if (!dns_name_is_root(dns_resource_key_name(rr->key))) { + /* If the OPT RR is not owned by the root domain, then it is bad, let's ignore + * it. */ + log_debug("OPT RR is not owned by root domain, ignoring."); + bad_opt = true; + continue; + } + + if (i < DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)) { + /* OPT RR is in the wrong section? Some Belkin routers do this. This is a hint + * the EDNS implementation is borked, like the Belkin one is, hence ignore + * it. */ + log_debug("OPT RR in wrong section, ignoring."); + bad_opt = true; + continue; + } + + if (!opt_is_good(rr, &has_rfc6975)) { + log_debug("Malformed OPT RR, ignoring."); + bad_opt = true; + continue; + } + + if (has_rfc6975) { + /* If the OPT RR contains RFC6975 algorithm data, then this is indication that + * the server just copied the OPT it got from us (which contained that data) + * back into the reply. If so, then it doesn't properly support EDNS, as + * RFC6975 makes it very clear that the algorithm data should only be contained + * in questions, never in replies. Crappy Belkin routers copy the OPT data for + * example, hence let's detect this so that we downgrade early. */ + log_debug("OPT RR contained RFC6975 data, ignoring."); + bad_opt = true; + continue; + } + + p->opt = dns_resource_record_ref(rr); + } else { + + /* According to RFC 4795, section 2.9. only the RRs from the Answer section shall be + * cached. Hence mark only those RRs as cacheable by default, but not the ones from the + * Additional or Authority sections. */ + + r = dns_answer_add(answer, rr, p->ifindex, + (i < DNS_PACKET_ANCOUNT(p) ? DNS_ANSWER_CACHEABLE : 0) | + (p->protocol == DNS_PROTOCOL_MDNS && !cache_flush ? DNS_ANSWER_SHARED_OWNER : 0)); + if (r < 0) + return r; + } + + /* Remember this RR, so that we potentically can merge it's ->key object with the next RR. Note + * that we only do this if we actually decided to keep the RR around. */ + dns_resource_record_unref(previous); + previous = dns_resource_record_ref(rr); + } + + if (bad_opt) + p->opt = dns_resource_record_unref(p->opt); + } + + p->question = question; + question = NULL; + + p->answer = answer; + answer = NULL; + + p->extracted = true; + + /* no CANCEL, always rewind */ + return 0; +} + +int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key) { + int r; + + assert(p); + assert(key); + + /* Checks if the specified packet is a reply for the specified + * key and the specified key is the only one in the question + * section. */ + + if (DNS_PACKET_QR(p) != 1) + return 0; + + /* Let's unpack the packet, if that hasn't happened yet. */ + r = dns_packet_extract(p); + if (r < 0) + return r; + + if (p->question->n_keys != 1) + return 0; + + return dns_resource_key_equal(p->question->keys[0], key); +} + +static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = { + [DNS_RCODE_SUCCESS] = "SUCCESS", + [DNS_RCODE_FORMERR] = "FORMERR", + [DNS_RCODE_SERVFAIL] = "SERVFAIL", + [DNS_RCODE_NXDOMAIN] = "NXDOMAIN", + [DNS_RCODE_NOTIMP] = "NOTIMP", + [DNS_RCODE_REFUSED] = "REFUSED", + [DNS_RCODE_YXDOMAIN] = "YXDOMAIN", + [DNS_RCODE_YXRRSET] = "YRRSET", + [DNS_RCODE_NXRRSET] = "NXRRSET", + [DNS_RCODE_NOTAUTH] = "NOTAUTH", + [DNS_RCODE_NOTZONE] = "NOTZONE", + [DNS_RCODE_BADVERS] = "BADVERS", + [DNS_RCODE_BADKEY] = "BADKEY", + [DNS_RCODE_BADTIME] = "BADTIME", + [DNS_RCODE_BADMODE] = "BADMODE", + [DNS_RCODE_BADNAME] = "BADNAME", + [DNS_RCODE_BADALG] = "BADALG", + [DNS_RCODE_BADTRUNC] = "BADTRUNC", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_rcode, int); + +static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = { + [DNS_PROTOCOL_DNS] = "dns", + [DNS_PROTOCOL_MDNS] = "mdns", + [DNS_PROTOCOL_LLMNR] = "llmnr", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_protocol, DnsProtocol); diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-packet.h b/src/grp-resolve/systemd-resolved/resolved-dns-packet.h new file mode 100644 index 0000000000..416335d0a2 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-packet.h @@ -0,0 +1,270 @@ +#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 +#include + +#include "hashmap.h" +#include "in-addr-util.h" +#include "macro.h" +#include "sparse-endian.h" + +typedef struct DnsPacketHeader DnsPacketHeader; +typedef struct DnsPacket DnsPacket; + +#include "resolved-def.h" +#include "resolved-dns-answer.h" +#include "resolved-dns-question.h" +#include "resolved-dns-rr.h" + +typedef enum DnsProtocol { + DNS_PROTOCOL_DNS, + DNS_PROTOCOL_MDNS, + DNS_PROTOCOL_LLMNR, + _DNS_PROTOCOL_MAX, + _DNS_PROTOCOL_INVALID = -1 +} DnsProtocol; + +struct DnsPacketHeader { + uint16_t id; + be16_t flags; + be16_t qdcount; + be16_t ancount; + be16_t nscount; + be16_t arcount; +}; + +#define DNS_PACKET_HEADER_SIZE sizeof(DnsPacketHeader) +#define UDP_PACKET_HEADER_SIZE (sizeof(struct iphdr) + sizeof(struct udphdr)) + +/* The various DNS protocols deviate in how large a packet can grow, + but the TCP transport has a 16bit size field, hence that appears to + be the absolute maximum. */ +#define DNS_PACKET_SIZE_MAX 0xFFFF + +/* RFC 1035 say 512 is the maximum, for classic unicast DNS */ +#define DNS_PACKET_UNICAST_SIZE_MAX 512 + +/* With EDNS0 we can use larger packets, default to 4096, which is what is commonly used */ +#define DNS_PACKET_UNICAST_SIZE_LARGE_MAX 4096 + +#define DNS_PACKET_SIZE_START 512 + +struct DnsPacket { + int n_ref; + DnsProtocol protocol; + size_t size, allocated, rindex; + void *_data; /* don't access directly, use DNS_PACKET_DATA()! */ + Hashmap *names; /* For name compression */ + size_t opt_start, opt_size; + + /* Parsed data */ + DnsQuestion *question; + DnsAnswer *answer; + DnsResourceRecord *opt; + + /* Packet reception metadata */ + int ifindex; + int family, ipproto; + union in_addr_union sender, destination; + uint16_t sender_port, destination_port; + uint32_t ttl; + + /* For support of truncated packets */ + DnsPacket *more; + + bool on_stack:1; + bool extracted:1; + bool refuse_compression:1; + bool canonical_form:1; +}; + +static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) { + if (_unlikely_(!p)) + return NULL; + + if (p->_data) + return p->_data; + + return ((uint8_t*) p) + ALIGN(sizeof(DnsPacket)); +} + +#define DNS_PACKET_HEADER(p) ((DnsPacketHeader*) DNS_PACKET_DATA(p)) +#define DNS_PACKET_ID(p) DNS_PACKET_HEADER(p)->id +#define DNS_PACKET_QR(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 15) & 1) +#define DNS_PACKET_OPCODE(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 11) & 15) +#define DNS_PACKET_AA(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 10) & 1) +#define DNS_PACKET_TC(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 9) & 1) +#define DNS_PACKET_RD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 8) & 1) +#define DNS_PACKET_RA(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 7) & 1) +#define DNS_PACKET_AD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 5) & 1) +#define DNS_PACKET_CD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 4) & 1) + +static inline uint16_t DNS_PACKET_RCODE(DnsPacket *p) { + uint16_t rcode; + + if (p->opt) + rcode = (uint16_t) (p->opt->ttl >> 24); + else + rcode = 0; + + return rcode | (be16toh(DNS_PACKET_HEADER(p)->flags) & 15); +} + +/* LLMNR defines some bits differently */ +#define DNS_PACKET_LLMNR_C(p) DNS_PACKET_AA(p) +#define DNS_PACKET_LLMNR_T(p) DNS_PACKET_RD(p) + +#define DNS_PACKET_QDCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->qdcount) +#define DNS_PACKET_ANCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->ancount) +#define DNS_PACKET_NSCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->nscount) +#define DNS_PACKET_ARCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->arcount) + +#define DNS_PACKET_MAKE_FLAGS(qr, opcode, aa, tc, rd, ra, ad, cd, rcode) \ + (((uint16_t) !!(qr) << 15) | \ + ((uint16_t) ((opcode) & 15) << 11) | \ + ((uint16_t) !!(aa) << 10) | /* on LLMNR: c */ \ + ((uint16_t) !!(tc) << 9) | \ + ((uint16_t) !!(rd) << 8) | /* on LLMNR: t */ \ + ((uint16_t) !!(ra) << 7) | \ + ((uint16_t) !!(ad) << 5) | \ + ((uint16_t) !!(cd) << 4) | \ + ((uint16_t) ((rcode) & 15))) + +static inline unsigned DNS_PACKET_RRCOUNT(DnsPacket *p) { + return + (unsigned) DNS_PACKET_ANCOUNT(p) + + (unsigned) DNS_PACKET_NSCOUNT(p) + + (unsigned) DNS_PACKET_ARCOUNT(p); +} + +int dns_packet_new(DnsPacket **p, DnsProtocol protocol, size_t mtu); +int dns_packet_new_query(DnsPacket **p, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled); + +void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated); + +DnsPacket *dns_packet_ref(DnsPacket *p); +DnsPacket *dns_packet_unref(DnsPacket *p); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsPacket*, dns_packet_unref); + +int dns_packet_validate(DnsPacket *p); +int dns_packet_validate_reply(DnsPacket *p); +int dns_packet_validate_query(DnsPacket *p); + +int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key); + +int dns_packet_append_blob(DnsPacket *p, const void *d, size_t sz, size_t *start); +int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start); +int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start); +int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start); +int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start); +int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start); +int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, bool canonical_candidate, size_t *start); +int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, bool canonical_candidate, size_t *start); +int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, size_t *start); +int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start); +int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start); + +void dns_packet_truncate(DnsPacket *p, size_t sz); +int dns_packet_truncate_opt(DnsPacket *p); + +int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start); +int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start); +int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start); +int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start); +int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start); +int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start); +int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start); +int dns_packet_read_name(DnsPacket *p, char **ret, bool allow_compression, size_t *start); +int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start); +int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start); + +void dns_packet_rewind(DnsPacket *p, size_t idx); + +int dns_packet_skip_question(DnsPacket *p); +int dns_packet_extract(DnsPacket *p); + +static inline bool DNS_PACKET_SHALL_CACHE(DnsPacket *p) { + /* Never cache data originating from localhost, under the + * assumption, that it's coming from a locally DNS forwarder + * or server, that is caching on its own. */ + + return in_addr_is_localhost(p->family, &p->sender) == 0; +} + +/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 */ +enum { + DNS_RCODE_SUCCESS = 0, + DNS_RCODE_FORMERR = 1, + DNS_RCODE_SERVFAIL = 2, + DNS_RCODE_NXDOMAIN = 3, + DNS_RCODE_NOTIMP = 4, + DNS_RCODE_REFUSED = 5, + DNS_RCODE_YXDOMAIN = 6, + DNS_RCODE_YXRRSET = 7, + DNS_RCODE_NXRRSET = 8, + DNS_RCODE_NOTAUTH = 9, + DNS_RCODE_NOTZONE = 10, + DNS_RCODE_BADVERS = 16, + DNS_RCODE_BADSIG = 16, /* duplicate value! */ + DNS_RCODE_BADKEY = 17, + DNS_RCODE_BADTIME = 18, + DNS_RCODE_BADMODE = 19, + DNS_RCODE_BADNAME = 20, + DNS_RCODE_BADALG = 21, + DNS_RCODE_BADTRUNC = 22, + _DNS_RCODE_MAX_DEFINED +}; + +const char* dns_rcode_to_string(int i) _const_; +int dns_rcode_from_string(const char *s) _pure_; + +const char* dns_protocol_to_string(DnsProtocol p) _const_; +DnsProtocol dns_protocol_from_string(const char *s) _pure_; + +#define LLMNR_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 252U) }) +#define LLMNR_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03 } }) + +#define MDNS_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 251U) }) +#define MDNS_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb } }) + +static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family, bool authenticated) { + uint64_t f; + + /* Converts a protocol + family into a flags field as used in queries and responses */ + + f = authenticated ? SD_RESOLVED_AUTHENTICATED : 0; + + switch (protocol) { + case DNS_PROTOCOL_DNS: + return f|SD_RESOLVED_DNS; + + case DNS_PROTOCOL_LLMNR: + return f|(family == AF_INET6 ? SD_RESOLVED_LLMNR_IPV6 : SD_RESOLVED_LLMNR_IPV4); + + case DNS_PROTOCOL_MDNS: + return f|(family == AF_INET6 ? SD_RESOLVED_MDNS_IPV6 : SD_RESOLVED_MDNS_IPV4); + + default: + return f; + } +} diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-query.c b/src/grp-resolve/systemd-resolved/resolved-dns-query.c new file mode 100644 index 0000000000..ea04e58d61 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-query.c @@ -0,0 +1,1109 @@ +/*** + 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 "alloc-util.h" +#include "dns-domain.h" +#include "dns-type.h" +#include "hostname-util.h" +#include "local-addresses.h" +#include "resolved-dns-query.h" +#include "resolved-dns-synthesize.h" +#include "resolved-etc-hosts.h" +#include "string-util.h" + +/* How long to wait for the query in total */ +#define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC) + +#define CNAME_MAX 8 +#define QUERIES_MAX 2048 +#define AUXILIARY_QUERIES_MAX 64 + +static int dns_query_candidate_new(DnsQueryCandidate **ret, DnsQuery *q, DnsScope *s) { + DnsQueryCandidate *c; + + assert(ret); + assert(q); + assert(s); + + c = new0(DnsQueryCandidate, 1); + if (!c) + return -ENOMEM; + + c->query = q; + c->scope = s; + + LIST_PREPEND(candidates_by_query, q->candidates, c); + LIST_PREPEND(candidates_by_scope, s->query_candidates, c); + + *ret = c; + return 0; +} + +static void dns_query_candidate_stop(DnsQueryCandidate *c) { + DnsTransaction *t; + + assert(c); + + while ((t = set_steal_first(c->transactions))) { + set_remove(t->notify_query_candidates, c); + set_remove(t->notify_query_candidates_done, c); + dns_transaction_gc(t); + } +} + +DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c) { + + if (!c) + return NULL; + + dns_query_candidate_stop(c); + + set_free(c->transactions); + dns_search_domain_unref(c->search_domain); + + if (c->query) + LIST_REMOVE(candidates_by_query, c->query->candidates, c); + + if (c->scope) + LIST_REMOVE(candidates_by_scope, c->scope->query_candidates, c); + + free(c); + + return NULL; +} + +static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) { + DnsSearchDomain *next = NULL; + + assert(c); + + if (c->search_domain && c->search_domain->linked) + next = c->search_domain->domains_next; + else + next = dns_scope_get_search_domains(c->scope); + + for (;;) { + if (!next) /* We hit the end of the list */ + return 0; + + if (!next->route_only) + break; + + /* Skip over route-only domains */ + next = next->domains_next; + } + + dns_search_domain_unref(c->search_domain); + c->search_domain = dns_search_domain_ref(next); + + return 1; +} + +static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResourceKey *key) { + DnsTransaction *t; + int r; + + assert(c); + assert(key); + + t = dns_scope_find_transaction(c->scope, key, true); + if (!t) { + r = dns_transaction_new(&t, c->scope, key); + if (r < 0) + return r; + } else { + if (set_contains(c->transactions, t)) + return 0; + } + + r = set_ensure_allocated(&c->transactions, NULL); + if (r < 0) + goto gc; + + r = set_ensure_allocated(&t->notify_query_candidates, NULL); + if (r < 0) + goto gc; + + r = set_ensure_allocated(&t->notify_query_candidates_done, NULL); + if (r < 0) + goto gc; + + r = set_put(t->notify_query_candidates, c); + if (r < 0) + goto gc; + + r = set_put(c->transactions, t); + if (r < 0) { + (void) set_remove(t->notify_query_candidates, c); + goto gc; + } + + return 1; + +gc: + dns_transaction_gc(t); + return r; +} + +static int dns_query_candidate_go(DnsQueryCandidate *c) { + DnsTransaction *t; + Iterator i; + int r; + unsigned n = 0; + + assert(c); + + /* Start the transactions that are not started yet */ + SET_FOREACH(t, c->transactions, i) { + if (t->state != DNS_TRANSACTION_NULL) + continue; + + r = dns_transaction_go(t); + if (r < 0) + return r; + + n++; + } + + /* If there was nothing to start, then let's proceed immediately */ + if (n == 0) + dns_query_candidate_notify(c); + + return 0; +} + +static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { + DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; + DnsTransaction *t; + Iterator i; + + assert(c); + + if (c->error_code != 0) + return DNS_TRANSACTION_ERRNO; + + SET_FOREACH(t, c->transactions, i) { + + switch (t->state) { + + case DNS_TRANSACTION_NULL: + /* If there's a NULL transaction pending, then + * this means not all transactions where + * started yet, and we were called from within + * the stackframe that is supposed to start + * remaining transactions. In this case, + * simply claim the candidate is pending. */ + + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: + /* If there's one transaction currently in + * VALIDATING state, then this means there's + * also one in PENDING state, hence we can + * return PENDING immediately. */ + return DNS_TRANSACTION_PENDING; + + case DNS_TRANSACTION_SUCCESS: + state = t->state; + break; + + default: + if (state != DNS_TRANSACTION_SUCCESS) + state = t->state; + + break; + } + } + + return state; +} + +static bool dns_query_candidate_is_routable(DnsQueryCandidate *c, uint16_t type) { + int family; + + assert(c); + + /* Checks whether the specified RR type matches an address family that is routable on the link(s) the scope of + * this candidate belongs to. Specifically, whether there's a routable IPv4 address on it if we query an A RR, + * or a routable IPv6 address if we query an AAAA RR. */ + + if (!c->query->suppress_unroutable_family) + return true; + + if (c->scope->protocol != DNS_PROTOCOL_DNS) + return true; + + family = dns_type_to_af(type); + if (family < 0) + return true; + + if (c->scope->link) + return link_relevant(c->scope->link, family, false); + else + return manager_routable(c->scope->manager, family); +} + +static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) { + DnsQuestion *question; + DnsResourceKey *key; + int n = 0, r; + + assert(c); + + dns_query_candidate_stop(c); + + question = dns_query_question_for_protocol(c->query, c->scope->protocol); + + /* Create one transaction per question key */ + DNS_QUESTION_FOREACH(key, question) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *new_key = NULL; + DnsResourceKey *qkey; + + if (!dns_query_candidate_is_routable(c, key->type)) + continue; + + if (c->search_domain) { + r = dns_resource_key_new_append_suffix(&new_key, key, c->search_domain->name); + if (r < 0) + goto fail; + + qkey = new_key; + } else + qkey = key; + + if (!dns_scope_good_key(c->scope, qkey)) + continue; + + r = dns_query_candidate_add_transaction(c, qkey); + if (r < 0) + goto fail; + + n++; + } + + return n; + +fail: + dns_query_candidate_stop(c); + return r; +} + +void dns_query_candidate_notify(DnsQueryCandidate *c) { + DnsTransactionState state; + int r; + + assert(c); + + state = dns_query_candidate_state(c); + + if (DNS_TRANSACTION_IS_LIVE(state)) + return; + + if (state != DNS_TRANSACTION_SUCCESS && c->search_domain) { + + r = dns_query_candidate_next_search_domain(c); + if (r < 0) + goto fail; + + if (r > 0) { + /* OK, there's another search domain to try, let's do so. */ + + r = dns_query_candidate_setup_transactions(c); + if (r < 0) + goto fail; + + if (r > 0) { + /* New transactions where queued. Start them and wait */ + + r = dns_query_candidate_go(c); + if (r < 0) + goto fail; + + return; + } + } + + } + + dns_query_ready(c->query); + return; + +fail: + log_warning_errno(r, "Failed to follow search domains: %m"); + c->error_code = r; + dns_query_ready(c->query); +} + +static void dns_query_stop(DnsQuery *q) { + DnsQueryCandidate *c; + + assert(q); + + q->timeout_event_source = sd_event_source_unref(q->timeout_event_source); + + LIST_FOREACH(candidates_by_query, c, q->candidates) + dns_query_candidate_stop(c); +} + +static void dns_query_free_candidates(DnsQuery *q) { + assert(q); + + while (q->candidates) + dns_query_candidate_free(q->candidates); +} + +static void dns_query_reset_answer(DnsQuery *q) { + assert(q); + + q->answer = dns_answer_unref(q->answer); + q->answer_rcode = 0; + q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + q->answer_errno = 0; + q->answer_authenticated = false; + q->answer_protocol = _DNS_PROTOCOL_INVALID; + q->answer_family = AF_UNSPEC; + q->answer_search_domain = dns_search_domain_unref(q->answer_search_domain); +} + +DnsQuery *dns_query_free(DnsQuery *q) { + if (!q) + return NULL; + + while (q->auxiliary_queries) + dns_query_free(q->auxiliary_queries); + + if (q->auxiliary_for) { + assert(q->auxiliary_for->n_auxiliary_queries > 0); + q->auxiliary_for->n_auxiliary_queries--; + LIST_REMOVE(auxiliary_queries, q->auxiliary_for->auxiliary_queries, q); + } + + dns_query_free_candidates(q); + + dns_question_unref(q->question_idna); + dns_question_unref(q->question_utf8); + + dns_query_reset_answer(q); + + sd_bus_message_unref(q->request); + sd_bus_track_unref(q->bus_track); + + free(q->request_address_string); + + if (q->manager) { + LIST_REMOVE(queries, q->manager->dns_queries, q); + q->manager->n_dns_queries--; + } + + free(q); + + return NULL; +} + +int dns_query_new( + Manager *m, + DnsQuery **ret, + DnsQuestion *question_utf8, + DnsQuestion *question_idna, + int ifindex, uint64_t flags) { + + _cleanup_(dns_query_freep) DnsQuery *q = NULL; + DnsResourceKey *key; + bool good = false; + int r; + char key_str[DNS_RESOURCE_KEY_STRING_MAX]; + + assert(m); + + if (dns_question_size(question_utf8) > 0) { + r = dns_question_is_valid_for_query(question_utf8); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + good = true; + } + + /* If the IDNA and UTF8 questions are the same, merge their references */ + r = dns_question_is_equal(question_idna, question_utf8); + if (r < 0) + return r; + if (r > 0) + question_idna = question_utf8; + else { + if (dns_question_size(question_idna) > 0) { + r = dns_question_is_valid_for_query(question_idna); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + good = true; + } + } + + if (!good) /* don't allow empty queries */ + return -EINVAL; + + if (m->n_dns_queries >= QUERIES_MAX) + return -EBUSY; + + q = new0(DnsQuery, 1); + if (!q) + return -ENOMEM; + + q->question_utf8 = dns_question_ref(question_utf8); + q->question_idna = dns_question_ref(question_idna); + q->ifindex = ifindex; + q->flags = flags; + q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + q->answer_protocol = _DNS_PROTOCOL_INVALID; + q->answer_family = AF_UNSPEC; + + /* First dump UTF8 question */ + DNS_QUESTION_FOREACH(key, question_utf8) + log_debug("Looking up RR for %s.", + dns_resource_key_to_string(key, key_str, sizeof key_str)); + + /* And then dump the IDNA question, but only what hasn't been dumped already through the UTF8 question. */ + DNS_QUESTION_FOREACH(key, question_idna) { + r = dns_question_contains(question_utf8, key); + if (r < 0) + return r; + if (r > 0) + continue; + + log_debug("Looking up IDNA RR for %s.", + dns_resource_key_to_string(key, key_str, sizeof key_str)); + } + + LIST_PREPEND(queries, m->dns_queries, q); + m->n_dns_queries++; + q->manager = m; + + if (ret) + *ret = q; + q = NULL; + + return 0; +} + +int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for) { + assert(q); + assert(auxiliary_for); + + /* Ensure that that the query is not auxiliary yet, and + * nothing else is auxiliary to it either */ + assert(!q->auxiliary_for); + assert(!q->auxiliary_queries); + + /* Ensure that the unit we shall be made auxiliary for isn't + * auxiliary itself */ + assert(!auxiliary_for->auxiliary_for); + + if (auxiliary_for->n_auxiliary_queries >= AUXILIARY_QUERIES_MAX) + return -EAGAIN; + + LIST_PREPEND(auxiliary_queries, auxiliary_for->auxiliary_queries, q); + q->auxiliary_for = auxiliary_for; + + auxiliary_for->n_auxiliary_queries++; + return 0; +} + +static void dns_query_complete(DnsQuery *q, DnsTransactionState state) { + assert(q); + assert(!DNS_TRANSACTION_IS_LIVE(state)); + assert(DNS_TRANSACTION_IS_LIVE(q->state)); + + /* Note that this call might invalidate the query. Callers + * should hence not attempt to access the query or transaction + * after calling this function. */ + + q->state = state; + + dns_query_stop(q); + if (q->complete) + q->complete(q); +} + +static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) { + DnsQuery *q = userdata; + + assert(s); + assert(q); + + dns_query_complete(q, DNS_TRANSACTION_TIMEOUT); + return 0; +} + +static int dns_query_add_candidate(DnsQuery *q, DnsScope *s) { + DnsQueryCandidate *c; + int r; + + assert(q); + assert(s); + + r = dns_query_candidate_new(&c, q, s); + if (r < 0) + return r; + + /* If this a single-label domain on DNS, we might append a suitable search domain first. */ + if ((q->flags & SD_RESOLVED_NO_SEARCH) == 0) { + r = dns_scope_name_needs_search_domain(s, dns_question_first_name(q->question_idna)); + if (r < 0) + goto fail; + if (r > 0) { + /* OK, we need a search domain now. Let's find one for this scope */ + + r = dns_query_candidate_next_search_domain(c); + if (r <= 0) /* if there's no search domain, then we won't add any transaction. */ + goto fail; + } + } + + r = dns_query_candidate_setup_transactions(c); + if (r < 0) + goto fail; + + return 0; + +fail: + dns_query_candidate_free(c); + return r; +} + +static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + int r; + + assert(q); + assert(state); + + /* Tries to synthesize localhost RR replies (and others) where appropriate. Note that this is done *after* the + * the normal lookup finished. The data from the network hence takes precedence over the data we + * synthesize. (But note that many scopes refuse to resolve certain domain names) */ + + if (!IN_SET(*state, + DNS_TRANSACTION_RCODE_FAILURE, + DNS_TRANSACTION_NO_SERVERS, + DNS_TRANSACTION_TIMEOUT, + DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, + DNS_TRANSACTION_NETWORK_DOWN, + DNS_TRANSACTION_NOT_FOUND)) + return 0; + + r = dns_synthesize_answer( + q->manager, + q->question_utf8, + q->ifindex, + &answer); + + if (r <= 0) + return r; + + dns_query_reset_answer(q); + + q->answer = answer; + answer = NULL; + q->answer_rcode = DNS_RCODE_SUCCESS; + q->answer_protocol = dns_synthesize_protocol(q->flags); + q->answer_family = dns_synthesize_family(q->flags); + q->answer_authenticated = true; + + *state = DNS_TRANSACTION_SUCCESS; + + return 1; +} + +static int dns_query_try_etc_hosts(DnsQuery *q) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + int r; + + assert(q); + + /* Looks in /etc/hosts for matching entries. Note that this is done *before* the normal lookup is done. The + * data from /etc/hosts hence takes precedence over the network. */ + + r = manager_etc_hosts_lookup( + q->manager, + q->question_utf8, + &answer); + if (r <= 0) + return r; + + dns_query_reset_answer(q); + + q->answer = answer; + answer = NULL; + q->answer_rcode = DNS_RCODE_SUCCESS; + q->answer_protocol = dns_synthesize_protocol(q->flags); + q->answer_family = dns_synthesize_family(q->flags); + q->answer_authenticated = true; + + return 1; +} + +int dns_query_go(DnsQuery *q) { + DnsScopeMatch found = DNS_SCOPE_NO; + DnsScope *s, *first = NULL; + DnsQueryCandidate *c; + int r; + + assert(q); + + if (q->state != DNS_TRANSACTION_NULL) + return 0; + + r = dns_query_try_etc_hosts(q); + if (r < 0) + return r; + if (r > 0) { + dns_query_complete(q, DNS_TRANSACTION_SUCCESS); + return 1; + } + + LIST_FOREACH(scopes, s, q->manager->dns_scopes) { + DnsScopeMatch match; + const char *name; + + name = dns_question_first_name(dns_query_question_for_protocol(q, s->protocol)); + if (!name) + continue; + + match = dns_scope_good_domain(s, q->ifindex, q->flags, name); + if (match < 0) + return match; + + if (match == DNS_SCOPE_NO) + continue; + + found = match; + + if (match == DNS_SCOPE_YES) { + first = s; + break; + } else { + assert(match == DNS_SCOPE_MAYBE); + + if (!first) + first = s; + } + } + + if (found == DNS_SCOPE_NO) { + DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; + + r = dns_query_synthesize_reply(q, &state); + if (r < 0) + return r; + + dns_query_complete(q, state); + return 1; + } + + r = dns_query_add_candidate(q, first); + if (r < 0) + goto fail; + + LIST_FOREACH(scopes, s, first->scopes_next) { + DnsScopeMatch match; + const char *name; + + name = dns_question_first_name(dns_query_question_for_protocol(q, s->protocol)); + if (!name) + continue; + + match = dns_scope_good_domain(s, q->ifindex, q->flags, name); + if (match < 0) + goto fail; + + if (match != found) + continue; + + r = dns_query_add_candidate(q, s); + if (r < 0) + goto fail; + } + + dns_query_reset_answer(q); + + r = sd_event_add_time( + q->manager->event, + &q->timeout_event_source, + clock_boottime_or_monotonic(), + now(clock_boottime_or_monotonic()) + QUERY_TIMEOUT_USEC, 0, + on_query_timeout, q); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(q->timeout_event_source, "query-timeout"); + + q->state = DNS_TRANSACTION_PENDING; + q->block_ready++; + + /* Start the transactions */ + LIST_FOREACH(candidates_by_query, c, q->candidates) { + r = dns_query_candidate_go(c); + if (r < 0) { + q->block_ready--; + goto fail; + } + } + + q->block_ready--; + dns_query_ready(q); + + return 1; + +fail: + dns_query_stop(q); + return r; +} + +static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { + DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; + bool has_authenticated = false, has_non_authenticated = false; + DnssecResult dnssec_result_authenticated = _DNSSEC_RESULT_INVALID, dnssec_result_non_authenticated = _DNSSEC_RESULT_INVALID; + DnsTransaction *t; + Iterator i; + int r; + + assert(q); + + if (!c) { + r = dns_query_synthesize_reply(q, &state); + if (r < 0) + goto fail; + + dns_query_complete(q, state); + return; + } + + if (c->error_code != 0) { + /* If the candidate had an error condition of its own, start with that. */ + state = DNS_TRANSACTION_ERRNO; + q->answer = dns_answer_unref(q->answer); + q->answer_rcode = 0; + q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + q->answer_errno = c->error_code; + } + + SET_FOREACH(t, c->transactions, i) { + + switch (t->state) { + + case DNS_TRANSACTION_SUCCESS: { + /* We found a successfully reply, merge it into the answer */ + r = dns_answer_extend(&q->answer, t->answer); + if (r < 0) + goto fail; + + q->answer_rcode = t->answer_rcode; + q->answer_errno = 0; + + if (t->answer_authenticated) { + has_authenticated = true; + dnssec_result_authenticated = t->answer_dnssec_result; + } else { + has_non_authenticated = true; + dnssec_result_non_authenticated = t->answer_dnssec_result; + } + + state = DNS_TRANSACTION_SUCCESS; + break; + } + + case DNS_TRANSACTION_NULL: + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: + case DNS_TRANSACTION_ABORTED: + /* Ignore transactions that didn't complete */ + continue; + + default: + /* Any kind of failure? Store the data away, + * if there's nothing stored yet. */ + + if (state == DNS_TRANSACTION_SUCCESS) + continue; + + q->answer = dns_answer_unref(q->answer); + q->answer_rcode = t->answer_rcode; + q->answer_dnssec_result = t->answer_dnssec_result; + q->answer_errno = t->answer_errno; + + state = t->state; + break; + } + } + + if (state == DNS_TRANSACTION_SUCCESS) { + q->answer_authenticated = has_authenticated && !has_non_authenticated; + q->answer_dnssec_result = q->answer_authenticated ? dnssec_result_authenticated : dnssec_result_non_authenticated; + } + + q->answer_protocol = c->scope->protocol; + q->answer_family = c->scope->family; + + dns_search_domain_unref(q->answer_search_domain); + q->answer_search_domain = dns_search_domain_ref(c->search_domain); + + r = dns_query_synthesize_reply(q, &state); + if (r < 0) + goto fail; + + dns_query_complete(q, state); + return; + +fail: + q->answer_errno = -r; + dns_query_complete(q, DNS_TRANSACTION_ERRNO); +} + +void dns_query_ready(DnsQuery *q) { + + DnsQueryCandidate *bad = NULL, *c; + bool pending = false; + + assert(q); + assert(DNS_TRANSACTION_IS_LIVE(q->state)); + + /* Note that this call might invalidate the query. Callers + * should hence not attempt to access the query or transaction + * after calling this function, unless the block_ready + * counter was explicitly bumped before doing so. */ + + if (q->block_ready > 0) + return; + + LIST_FOREACH(candidates_by_query, c, q->candidates) { + DnsTransactionState state; + + state = dns_query_candidate_state(c); + switch (state) { + + case DNS_TRANSACTION_SUCCESS: + /* One of the candidates is successful, + * let's use it, and copy its data out */ + dns_query_accept(q, c); + return; + + case DNS_TRANSACTION_NULL: + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: + /* One of the candidates is still going on, + * let's maybe wait for it */ + pending = true; + break; + + default: + /* Any kind of failure */ + bad = c; + break; + } + } + + if (pending) + return; + + dns_query_accept(q, bad); +} + +static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) { + _cleanup_(dns_question_unrefp) DnsQuestion *nq_idna = NULL, *nq_utf8 = NULL; + int r, k; + + assert(q); + + q->n_cname_redirects++; + if (q->n_cname_redirects > CNAME_MAX) + return -ELOOP; + + r = dns_question_cname_redirect(q->question_idna, cname, &nq_idna); + if (r < 0) + return r; + else if (r > 0) + log_debug("Following CNAME/DNAME %s → %s.", dns_question_first_name(q->question_idna), dns_question_first_name(nq_idna)); + + k = dns_question_is_equal(q->question_idna, q->question_utf8); + if (k < 0) + return r; + if (k > 0) { + /* Same question? Shortcut new question generation */ + nq_utf8 = dns_question_ref(nq_idna); + k = r; + } else { + k = dns_question_cname_redirect(q->question_utf8, cname, &nq_utf8); + if (k < 0) + return k; + else if (k > 0) + log_debug("Following UTF8 CNAME/DNAME %s → %s.", dns_question_first_name(q->question_utf8), dns_question_first_name(nq_utf8)); + } + + if (r == 0 && k == 0) /* No actual cname happened? */ + return -ELOOP; + + if (q->answer_protocol == DNS_PROTOCOL_DNS) { + /* Don't permit CNAME redirects from unicast DNS to LLMNR or MulticastDNS, so that global resources + * cannot invade the local namespace. The opposite way we permit: local names may redirect to global + * ones. */ + + q->flags &= ~(SD_RESOLVED_LLMNR|SD_RESOLVED_MDNS); /* mask away the local protocols */ + } + + /* Turn off searching for the new name */ + q->flags |= SD_RESOLVED_NO_SEARCH; + + dns_question_unref(q->question_idna); + q->question_idna = nq_idna; + nq_idna = NULL; + + dns_question_unref(q->question_utf8); + q->question_utf8 = nq_utf8; + nq_utf8 = NULL; + + dns_query_free_candidates(q); + dns_query_reset_answer(q); + + q->state = DNS_TRANSACTION_NULL; + + return 0; +} + +int dns_query_process_cname(DnsQuery *q) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *cname = NULL; + DnsQuestion *question; + DnsResourceRecord *rr; + int r; + + assert(q); + + if (!IN_SET(q->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_NULL)) + return DNS_QUERY_NOMATCH; + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + DNS_ANSWER_FOREACH(rr, q->answer) { + r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + if (r < 0) + return r; + if (r > 0) + return DNS_QUERY_MATCH; /* The answer matches directly, no need to follow cnames */ + + r = dns_question_matches_cname_or_dname(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + if (r < 0) + return r; + if (r > 0 && !cname) + cname = dns_resource_record_ref(rr); + } + + if (!cname) + return DNS_QUERY_NOMATCH; /* No match and no cname to follow */ + + if (q->flags & SD_RESOLVED_NO_CNAME) + return -ELOOP; + + /* OK, let's actually follow the CNAME */ + r = dns_query_cname_redirect(q, cname); + if (r < 0) + return r; + + /* Let's see if the answer can already answer the new + * redirected question */ + r = dns_query_process_cname(q); + if (r != DNS_QUERY_NOMATCH) + return r; + + /* OK, it cannot, let's begin with the new query */ + r = dns_query_go(q); + if (r < 0) + return r; + + return DNS_QUERY_RESTARTED; /* We restarted the query for a new cname */ +} + +static int on_bus_track(sd_bus_track *t, void *userdata) { + DnsQuery *q = userdata; + + assert(t); + assert(q); + + log_debug("Client of active query vanished, aborting query."); + dns_query_complete(q, DNS_TRANSACTION_ABORTED); + return 0; +} + +int dns_query_bus_track(DnsQuery *q, sd_bus_message *m) { + int r; + + assert(q); + assert(m); + + if (!q->bus_track) { + r = sd_bus_track_new(sd_bus_message_get_bus(m), &q->bus_track, on_bus_track, q); + if (r < 0) + return r; + } + + r = sd_bus_track_add_sender(q->bus_track, m); + if (r < 0) + return r; + + return 0; +} + +DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol) { + assert(q); + + switch (protocol) { + + case DNS_PROTOCOL_DNS: + return q->question_idna; + + case DNS_PROTOCOL_MDNS: + case DNS_PROTOCOL_LLMNR: + return q->question_utf8; + + default: + return NULL; + } +} + +const char *dns_query_string(DnsQuery *q) { + const char *name; + int r; + + /* Returns a somewhat useful human-readable lookup key string for this query */ + + if (q->request_address_string) + return q->request_address_string; + + if (q->request_address_valid) { + r = in_addr_to_string(q->request_family, &q->request_address, &q->request_address_string); + if (r >= 0) + return q->request_address_string; + } + + name = dns_question_first_name(q->question_utf8); + if (name) + return name; + + return dns_question_first_name(q->question_idna); +} diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-query.h b/src/grp-resolve/systemd-resolved/resolved-dns-query.h new file mode 100644 index 0000000000..7f7c76ff20 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-query.h @@ -0,0 +1,133 @@ +#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 + +#include "set.h" + +typedef struct DnsQueryCandidate DnsQueryCandidate; +typedef struct DnsQuery DnsQuery; + +#include "resolved-dns-answer.h" +#include "resolved-dns-question.h" +#include "resolved-dns-stream.h" +#include "resolved-dns-search-domain.h" + +struct DnsQueryCandidate { + DnsQuery *query; + DnsScope *scope; + + DnsSearchDomain *search_domain; + + int error_code; + Set *transactions; + + LIST_FIELDS(DnsQueryCandidate, candidates_by_query); + LIST_FIELDS(DnsQueryCandidate, candidates_by_scope); +}; + +struct DnsQuery { + Manager *manager; + + /* When resolving a service, we first create a TXT+SRV query, + * and then for the hostnames we discover auxiliary A+AAAA + * queries. This pointer always points from the auxiliary + * queries back to the TXT+SRV query. */ + DnsQuery *auxiliary_for; + LIST_HEAD(DnsQuery, auxiliary_queries); + unsigned n_auxiliary_queries; + int auxiliary_result; + + /* The question, formatted in IDNA for use on classic DNS, and as UTF8 for use in LLMNR or mDNS. Note that even + * on classic DNS some labels might use UTF8 encoding. Specifically, DNS-SD service names (in contrast to their + * domain suffixes) use UTF-8 encoding even on DNS. Thus, the difference between these two fields is mostly + * relevant only for explicit *hostname* lookups as well as the domain suffixes of service lookups. */ + DnsQuestion *question_idna; + DnsQuestion *question_utf8; + + uint64_t flags; + int ifindex; + + /* If true, A or AAAA RR lookups will be suppressed on links with no routable address of the matching address + * family */ + bool suppress_unroutable_family; + + DnsTransactionState state; + unsigned n_cname_redirects; + + LIST_HEAD(DnsQueryCandidate, candidates); + sd_event_source *timeout_event_source; + + /* Discovered data */ + DnsAnswer *answer; + int answer_rcode; + DnssecResult answer_dnssec_result; + bool answer_authenticated; + DnsProtocol answer_protocol; + int answer_family; + DnsSearchDomain *answer_search_domain; + int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */ + + /* Bus client information */ + sd_bus_message *request; + int request_family; + bool request_address_valid; + union in_addr_union request_address; + unsigned block_all_complete; + char *request_address_string; + + /* Completion callback */ + void (*complete)(DnsQuery* q); + unsigned block_ready; + + sd_bus_track *bus_track; + + LIST_FIELDS(DnsQuery, queries); + LIST_FIELDS(DnsQuery, auxiliary_queries); +}; + +enum { + DNS_QUERY_MATCH, + DNS_QUERY_NOMATCH, + DNS_QUERY_RESTARTED, +}; + +DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c); +void dns_query_candidate_notify(DnsQueryCandidate *c); + +int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question_utf8, DnsQuestion *question_idna, int family, uint64_t flags); +DnsQuery *dns_query_free(DnsQuery *q); + +int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for); + +int dns_query_go(DnsQuery *q); +void dns_query_ready(DnsQuery *q); + +int dns_query_process_cname(DnsQuery *q); + +int dns_query_bus_track(DnsQuery *q, sd_bus_message *m); + +DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol); + +const char *dns_query_string(DnsQuery *q); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuery*, dns_query_free); diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-question.c b/src/grp-resolve/systemd-resolved/resolved-dns-question.c new file mode 100644 index 0000000000..c8b502d1cd --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-question.c @@ -0,0 +1,468 @@ +/*** + 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 "alloc-util.h" +#include "dns-domain.h" +#include "dns-type.h" +#include "resolved-dns-question.h" + +DnsQuestion *dns_question_new(unsigned n) { + DnsQuestion *q; + + assert(n > 0); + + q = malloc0(offsetof(DnsQuestion, keys) + sizeof(DnsResourceKey*) * n); + if (!q) + return NULL; + + q->n_ref = 1; + q->n_allocated = n; + + return q; +} + +DnsQuestion *dns_question_ref(DnsQuestion *q) { + if (!q) + return NULL; + + assert(q->n_ref > 0); + q->n_ref++; + return q; +} + +DnsQuestion *dns_question_unref(DnsQuestion *q) { + if (!q) + return NULL; + + assert(q->n_ref > 0); + + if (q->n_ref == 1) { + unsigned i; + + for (i = 0; i < q->n_keys; i++) + dns_resource_key_unref(q->keys[i]); + free(q); + } else + q->n_ref--; + + return NULL; +} + +int dns_question_add(DnsQuestion *q, DnsResourceKey *key) { + unsigned i; + int r; + + assert(key); + + if (!q) + return -ENOSPC; + + for (i = 0; i < q->n_keys; i++) { + r = dns_resource_key_equal(q->keys[i], key); + if (r < 0) + return r; + if (r > 0) + return 0; + } + + if (q->n_keys >= q->n_allocated) + return -ENOSPC; + + q->keys[q->n_keys++] = dns_resource_key_ref(key); + return 0; +} + +int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { + unsigned i; + int r; + + assert(rr); + + if (!q) + return 0; + + for (i = 0; i < q->n_keys; i++) { + r = dns_resource_key_match_rr(q->keys[i], rr, search_domain); + if (r != 0) + return r; + } + + return 0; +} + +int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { + unsigned i; + int r; + + assert(rr); + + if (!q) + return 0; + + if (!IN_SET(rr->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)) + return 0; + + for (i = 0; i < q->n_keys; i++) { + /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */ + if (!dns_type_may_redirect(q->keys[i]->type)) + return 0; + + r = dns_resource_key_match_cname_or_dname(q->keys[i], rr->key, search_domain); + if (r != 0) + return r; + } + + return 0; +} + +int dns_question_is_valid_for_query(DnsQuestion *q) { + const char *name; + unsigned i; + int r; + + if (!q) + return 0; + + if (q->n_keys <= 0) + return 0; + + if (q->n_keys > 65535) + return 0; + + name = dns_resource_key_name(q->keys[0]); + if (!name) + return 0; + + /* Check that all keys in this question bear the same name */ + for (i = 0; i < q->n_keys; i++) { + assert(q->keys[i]); + + if (i > 0) { + r = dns_name_equal(dns_resource_key_name(q->keys[i]), name); + if (r <= 0) + return r; + } + + if (!dns_type_is_valid_query(q->keys[i]->type)) + return 0; + } + + return 1; +} + +int dns_question_contains(DnsQuestion *a, const DnsResourceKey *k) { + unsigned j; + int r; + + assert(k); + + if (!a) + return 0; + + for (j = 0; j < a->n_keys; j++) { + r = dns_resource_key_equal(a->keys[j], k); + if (r != 0) + return r; + } + + return 0; +} + +int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b) { + unsigned j; + int r; + + if (a == b) + return 1; + + if (!a) + return !b || b->n_keys == 0; + if (!b) + return a->n_keys == 0; + + /* Checks if all keys in a are also contained b, and vice versa */ + + for (j = 0; j < a->n_keys; j++) { + r = dns_question_contains(b, a->keys[j]); + if (r <= 0) + return r; + } + + for (j = 0; j < b->n_keys; j++) { + r = dns_question_contains(a, b->keys[j]); + if (r <= 0) + return r; + } + + return 1; +} + +int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret) { + _cleanup_(dns_question_unrefp) DnsQuestion *n = NULL; + DnsResourceKey *key; + bool same = true; + int r; + + assert(cname); + assert(ret); + assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)); + + if (dns_question_size(q) <= 0) { + *ret = NULL; + return 0; + } + + DNS_QUESTION_FOREACH(key, q) { + _cleanup_free_ char *destination = NULL; + const char *d; + + if (cname->key->type == DNS_TYPE_CNAME) + d = cname->cname.name; + else { + r = dns_name_change_suffix(dns_resource_key_name(key), dns_resource_key_name(cname->key), cname->dname.name, &destination); + if (r < 0) + return r; + if (r == 0) + continue; + + d = destination; + } + + r = dns_name_equal(dns_resource_key_name(key), d); + if (r < 0) + return r; + + if (r == 0) { + same = false; + break; + } + } + + /* Fully the same, indicate we didn't do a thing */ + if (same) { + *ret = NULL; + return 0; + } + + n = dns_question_new(q->n_keys); + if (!n) + return -ENOMEM; + + /* Create a new question, and patch in the new name */ + DNS_QUESTION_FOREACH(key, q) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; + + k = dns_resource_key_new_redirect(key, cname); + if (!k) + return -ENOMEM; + + r = dns_question_add(n, k); + if (r < 0) + return r; + } + + *ret = n; + n = NULL; + + return 1; +} + +const char *dns_question_first_name(DnsQuestion *q) { + + if (!q) + return NULL; + + if (q->n_keys < 1) + return NULL; + + return dns_resource_key_name(q->keys[0]); +} + +int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna) { + _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; + _cleanup_free_ char *buf = NULL; + int r; + + assert(ret); + assert(name); + + if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) + return -EAFNOSUPPORT; + + if (convert_idna) { + r = dns_name_apply_idna(name, &buf); + if (r < 0) + return r; + + name = buf; + } + + q = dns_question_new(family == AF_UNSPEC ? 2 : 1); + if (!q) + return -ENOMEM; + + if (family != AF_INET6) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(q, key); + if (r < 0) + return r; + } + + if (family != AF_INET) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(q, key); + if (r < 0) + return r; + } + + *ret = q; + q = NULL; + + return 0; +} + +int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; + _cleanup_free_ char *reverse = NULL; + int r; + + assert(ret); + assert(a); + + if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) + return -EAFNOSUPPORT; + + r = dns_name_reverse(family, a, &reverse); + if (r < 0) + return r; + + q = dns_question_new(1); + if (!q) + return -ENOMEM; + + key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, reverse); + if (!key) + return -ENOMEM; + + reverse = NULL; + + r = dns_question_add(q, key); + if (r < 0) + return r; + + *ret = q; + q = NULL; + + return 0; +} + +int dns_question_new_service( + DnsQuestion **ret, + const char *service, + const char *type, + const char *domain, + bool with_txt, + bool convert_idna) { + + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; + _cleanup_free_ char *buf = NULL, *joined = NULL; + const char *name; + int r; + + assert(ret); + + /* We support three modes of invocation: + * + * 1. Only a domain is specified, in which case we assume a properly encoded SRV RR name, including service + * type and possibly a service name. If specified in this way we assume it's already IDNA converted if + * that's necessary. + * + * 2. Both service type and a domain specified, in which case a normal SRV RR is assumed, without a DNS-SD + * style prefix. In this case we'll IDNA convert the domain, if that's requested. + * + * 3. All three of service name, type and domain are specified, in which case a DNS-SD service is put + * together. The service name is never IDNA converted, and the domain is if requested. + * + * It's not supported to specify a service name without a type, or no domain name. + */ + + if (!domain) + return -EINVAL; + + if (type) { + if (convert_idna) { + r = dns_name_apply_idna(domain, &buf); + if (r < 0) + return r; + + domain = buf; + } + + r = dns_service_join(service, type, domain, &joined); + if (r < 0) + return r; + + name = joined; + } else { + if (service) + return -EINVAL; + + name = domain; + } + + q = dns_question_new(1 + with_txt); + if (!q) + return -ENOMEM; + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_SRV, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(q, key); + if (r < 0) + return r; + + if (with_txt) { + dns_resource_key_unref(key); + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_TXT, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(q, key); + if (r < 0) + return r; + } + + *ret = q; + q = NULL; + + return 0; +} diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-question.h b/src/grp-resolve/systemd-resolved/resolved-dns-question.h new file mode 100644 index 0000000000..ea41478975 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-question.h @@ -0,0 +1,69 @@ +#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 . +***/ + +typedef struct DnsQuestion DnsQuestion; + +#include "macro.h" +#include "resolved-dns-rr.h" + +/* A simple array of resource keys */ + +struct DnsQuestion { + unsigned n_ref; + unsigned n_keys, n_allocated; + DnsResourceKey* keys[0]; +}; + +DnsQuestion *dns_question_new(unsigned n); +DnsQuestion *dns_question_ref(DnsQuestion *q); +DnsQuestion *dns_question_unref(DnsQuestion *q); + +int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna); +int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a); +int dns_question_new_service(DnsQuestion **ret, const char *service, const char *type, const char *domain, bool with_txt, bool convert_idna); + +int dns_question_add(DnsQuestion *q, DnsResourceKey *key); + +int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain); +int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char* search_domain); +int dns_question_is_valid_for_query(DnsQuestion *q); +int dns_question_contains(DnsQuestion *a, const DnsResourceKey *k); +int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b); + +int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret); + +const char *dns_question_first_name(DnsQuestion *q); + +static inline unsigned dns_question_size(DnsQuestion *q) { + return q ? q->n_keys : 0; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuestion*, dns_question_unref); + +#define _DNS_QUESTION_FOREACH(u, key, q) \ + for (unsigned UNIQ_T(i, u) = ({ \ + (key) = ((q) && (q)->n_keys > 0) ? (q)->keys[0] : NULL; \ + 0; \ + }); \ + (q) && (UNIQ_T(i, u) < (q)->n_keys); \ + UNIQ_T(i, u)++, (key) = (UNIQ_T(i, u) < (q)->n_keys ? (q)->keys[UNIQ_T(i, u)] : NULL)) + +#define DNS_QUESTION_FOREACH(key, q) _DNS_QUESTION_FOREACH(UNIQ, key, q) diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-rr.c b/src/grp-resolve/systemd-resolved/resolved-dns-rr.c new file mode 100644 index 0000000000..6a29a93a26 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-rr.c @@ -0,0 +1,1594 @@ +/*** + 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 "alloc-util.h" +#include "dns-domain.h" +#include "dns-type.h" +#include "escape.h" +#include "hexdecoct.h" +#include "resolved-dns-dnssec.h" +#include "resolved-dns-packet.h" +#include "resolved-dns-rr.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" + +DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name) { + DnsResourceKey *k; + size_t l; + + assert(name); + + l = strlen(name); + k = malloc0(sizeof(DnsResourceKey) + l + 1); + if (!k) + return NULL; + + k->n_ref = 1; + k->class = class; + k->type = type; + + strcpy((char*) k + sizeof(DnsResourceKey), name); + + return k; +} + +DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname) { + int r; + + assert(key); + assert(cname); + + assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)); + + if (cname->key->type == DNS_TYPE_CNAME) + return dns_resource_key_new(key->class, key->type, cname->cname.name); + else { + DnsResourceKey *k; + char *destination = NULL; + + r = dns_name_change_suffix(dns_resource_key_name(key), dns_resource_key_name(cname->key), cname->dname.name, &destination); + if (r < 0) + return NULL; + if (r == 0) + return dns_resource_key_ref((DnsResourceKey*) key); + + k = dns_resource_key_new_consume(key->class, key->type, destination); + if (!k) { + free(destination); + return NULL; + } + + return k; + } +} + +int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name) { + DnsResourceKey *new_key; + char *joined; + int r; + + assert(ret); + assert(key); + assert(name); + + if (dns_name_is_root(name)) { + *ret = dns_resource_key_ref(key); + return 0; + } + + r = dns_name_concat(dns_resource_key_name(key), name, &joined); + if (r < 0) + return r; + + new_key = dns_resource_key_new_consume(key->class, key->type, joined); + if (!new_key) { + free(joined); + return -ENOMEM; + } + + *ret = new_key; + return 0; +} + +DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name) { + DnsResourceKey *k; + + assert(name); + + k = new0(DnsResourceKey, 1); + if (!k) + return NULL; + + k->n_ref = 1; + k->class = class; + k->type = type; + k->_name = name; + + return k; +} + +DnsResourceKey* dns_resource_key_ref(DnsResourceKey *k) { + + if (!k) + return NULL; + + /* Static/const keys created with DNS_RESOURCE_KEY_CONST will + * set this to -1, they should not be reffed/unreffed */ + assert(k->n_ref != (unsigned) -1); + + assert(k->n_ref > 0); + k->n_ref++; + + return k; +} + +DnsResourceKey* dns_resource_key_unref(DnsResourceKey *k) { + if (!k) + return NULL; + + assert(k->n_ref != (unsigned) -1); + assert(k->n_ref > 0); + + if (k->n_ref == 1) { + free(k->_name); + free(k); + } else + k->n_ref--; + + return NULL; +} + +const char* dns_resource_key_name(const DnsResourceKey *key) { + const char *name; + + if (!key) + return NULL; + + if (key->_name) + name = key->_name; + else + name = (char*) key + sizeof(DnsResourceKey); + + if (dns_name_is_root(name)) + return "."; + else + return name; +} + +bool dns_resource_key_is_address(const DnsResourceKey *key) { + assert(key); + + /* Check if this is an A or AAAA resource key */ + + return key->class == DNS_CLASS_IN && IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_AAAA); +} + +int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) { + int r; + + if (a == b) + return 1; + + r = dns_name_equal(dns_resource_key_name(a), dns_resource_key_name(b)); + if (r <= 0) + return r; + + if (a->class != b->class) + return 0; + + if (a->type != b->type) + return 0; + + return 1; +} + +int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain) { + int r; + + assert(key); + assert(rr); + + if (key == rr->key) + return 1; + + /* Checks if an rr matches the specified key. If a search + * domain is specified, it will also be checked if the key + * with the search domain suffixed might match the RR. */ + + if (rr->key->class != key->class && key->class != DNS_CLASS_ANY) + return 0; + + if (rr->key->type != key->type && key->type != DNS_TYPE_ANY) + return 0; + + r = dns_name_equal(dns_resource_key_name(rr->key), dns_resource_key_name(key)); + if (r != 0) + return r; + + if (search_domain) { + _cleanup_free_ char *joined = NULL; + + r = dns_name_concat(dns_resource_key_name(key), search_domain, &joined); + if (r < 0) + return r; + + return dns_name_equal(dns_resource_key_name(rr->key), joined); + } + + return 0; +} + +int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain) { + int r; + + assert(key); + assert(cname); + + if (cname->class != key->class && key->class != DNS_CLASS_ANY) + return 0; + + if (cname->type == DNS_TYPE_CNAME) + r = dns_name_equal(dns_resource_key_name(key), dns_resource_key_name(cname)); + else if (cname->type == DNS_TYPE_DNAME) + r = dns_name_endswith(dns_resource_key_name(key), dns_resource_key_name(cname)); + else + return 0; + + if (r != 0) + return r; + + if (search_domain) { + _cleanup_free_ char *joined = NULL; + + r = dns_name_concat(dns_resource_key_name(key), search_domain, &joined); + if (r < 0) + return r; + + if (cname->type == DNS_TYPE_CNAME) + return dns_name_equal(joined, dns_resource_key_name(cname)); + else if (cname->type == DNS_TYPE_DNAME) + return dns_name_endswith(joined, dns_resource_key_name(cname)); + } + + return 0; +} + +int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa) { + assert(soa); + assert(key); + + /* Checks whether 'soa' is a SOA record for the specified key. */ + + if (soa->class != key->class) + return 0; + + if (soa->type != DNS_TYPE_SOA) + return 0; + + return dns_name_endswith(dns_resource_key_name(key), dns_resource_key_name(soa)); +} + +static void dns_resource_key_hash_func(const void *i, struct siphash *state) { + const DnsResourceKey *k = i; + + assert(k); + + dns_name_hash_func(dns_resource_key_name(k), state); + siphash24_compress(&k->class, sizeof(k->class), state); + siphash24_compress(&k->type, sizeof(k->type), state); +} + +static int dns_resource_key_compare_func(const void *a, const void *b) { + const DnsResourceKey *x = a, *y = b; + int ret; + + ret = dns_name_compare_func(dns_resource_key_name(x), dns_resource_key_name(y)); + if (ret != 0) + return ret; + + if (x->type < y->type) + return -1; + if (x->type > y->type) + return 1; + + if (x->class < y->class) + return -1; + if (x->class > y->class) + return 1; + + return 0; +} + +const struct hash_ops dns_resource_key_hash_ops = { + .hash = dns_resource_key_hash_func, + .compare = dns_resource_key_compare_func +}; + +char* dns_resource_key_to_string(const DnsResourceKey *key, char *buf, size_t buf_size) { + const char *c, *t; + char *ans = buf; + + /* If we cannot convert the CLASS/TYPE into a known string, + use the format recommended by RFC 3597, Section 5. */ + + c = dns_class_to_string(key->class); + t = dns_type_to_string(key->type); + + snprintf(buf, buf_size, "%s %s%s%.0u %s%s%.0u", + dns_resource_key_name(key), + c ?: "", c ? "" : "CLASS", c ? 0 : key->class, + t ?: "", t ? "" : "TYPE", t ? 0 : key->class); + + return ans; +} + +bool dns_resource_key_reduce(DnsResourceKey **a, DnsResourceKey **b) { + assert(a); + assert(b); + + /* Try to replace one RR key by another if they are identical, thus saving a bit of memory. Note that we do + * this only for RR keys, not for RRs themselves, as they carry a lot of additional metadata (where they come + * from, validity data, and suchlike), and cannot be replaced so easily by other RRs that have the same + * superficial data. */ + + if (!*a) + return false; + if (!*b) + return false; + + /* We refuse merging const keys */ + if ((*a)->n_ref == (unsigned) -1) + return false; + if ((*b)->n_ref == (unsigned) -1) + return false; + + /* Already the same? */ + if (*a == *b) + return true; + + /* Are they really identical? */ + if (dns_resource_key_equal(*a, *b) <= 0) + return false; + + /* Keep the one which already has more references. */ + if ((*a)->n_ref > (*b)->n_ref) { + dns_resource_key_unref(*b); + *b = dns_resource_key_ref(*a); + } else { + dns_resource_key_unref(*a); + *a = dns_resource_key_ref(*b); + } + + return true; +} + +DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key) { + DnsResourceRecord *rr; + + rr = new0(DnsResourceRecord, 1); + if (!rr) + return NULL; + + rr->n_ref = 1; + rr->key = dns_resource_key_ref(key); + rr->expiry = USEC_INFINITY; + rr->n_skip_labels_signer = rr->n_skip_labels_source = (unsigned) -1; + + return rr; +} + +DnsResourceRecord* dns_resource_record_new_full(uint16_t class, uint16_t type, const char *name) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + key = dns_resource_key_new(class, type, name); + if (!key) + return NULL; + + return dns_resource_record_new(key); +} + +DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr) { + if (!rr) + return NULL; + + assert(rr->n_ref > 0); + rr->n_ref++; + + return rr; +} + +DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) { + if (!rr) + return NULL; + + assert(rr->n_ref > 0); + + if (rr->n_ref > 1) { + rr->n_ref--; + return NULL; + } + + if (rr->key) { + switch(rr->key->type) { + + case DNS_TYPE_SRV: + free(rr->srv.name); + break; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + free(rr->ptr.name); + break; + + case DNS_TYPE_HINFO: + free(rr->hinfo.cpu); + free(rr->hinfo.os); + break; + + case DNS_TYPE_TXT: + case DNS_TYPE_SPF: + dns_txt_item_free_all(rr->txt.items); + break; + + case DNS_TYPE_SOA: + free(rr->soa.mname); + free(rr->soa.rname); + break; + + case DNS_TYPE_MX: + free(rr->mx.exchange); + break; + + case DNS_TYPE_DS: + free(rr->ds.digest); + break; + + case DNS_TYPE_SSHFP: + free(rr->sshfp.fingerprint); + break; + + case DNS_TYPE_DNSKEY: + free(rr->dnskey.key); + break; + + case DNS_TYPE_RRSIG: + free(rr->rrsig.signer); + free(rr->rrsig.signature); + break; + + case DNS_TYPE_NSEC: + free(rr->nsec.next_domain_name); + bitmap_free(rr->nsec.types); + break; + + case DNS_TYPE_NSEC3: + free(rr->nsec3.next_hashed_name); + free(rr->nsec3.salt); + bitmap_free(rr->nsec3.types); + break; + + case DNS_TYPE_LOC: + case DNS_TYPE_A: + case DNS_TYPE_AAAA: + break; + + case DNS_TYPE_TLSA: + free(rr->tlsa.data); + break; + + case DNS_TYPE_CAA: + free(rr->caa.tag); + free(rr->caa.value); + break; + + case DNS_TYPE_OPENPGPKEY: + default: + free(rr->generic.data); + } + + free(rr->wire_format); + dns_resource_key_unref(rr->key); + } + + free(rr->to_string); + free(rr); + + return NULL; +} + +int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *hostname) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_free_ char *ptr = NULL; + int r; + + assert(ret); + assert(address); + assert(hostname); + + r = dns_name_reverse(family, address, &ptr); + if (r < 0) + return r; + + key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, ptr); + if (!key) + return -ENOMEM; + + ptr = NULL; + + rr = dns_resource_record_new(key); + if (!rr) + return -ENOMEM; + + rr->ptr.name = strdup(hostname); + if (!rr->ptr.name) + return -ENOMEM; + + *ret = rr; + rr = NULL; + + return 0; +} + +int dns_resource_record_new_address(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name) { + DnsResourceRecord *rr; + + assert(ret); + assert(address); + assert(family); + + if (family == AF_INET) { + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, name); + if (!rr) + return -ENOMEM; + + rr->a.in_addr = address->in; + + } else if (family == AF_INET6) { + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, name); + if (!rr) + return -ENOMEM; + + rr->aaaa.in6_addr = address->in6; + } else + return -EAFNOSUPPORT; + + *ret = rr; + + return 0; +} + +#define FIELD_EQUAL(a, b, field) \ + ((a).field ## _size == (b).field ## _size && \ + memcmp((a).field, (b).field, (a).field ## _size) == 0) + +int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b) { + int r; + + assert(a); + assert(b); + + if (a == b) + return 1; + + r = dns_resource_key_equal(a->key, b->key); + if (r <= 0) + return r; + + if (a->unparseable != b->unparseable) + return 0; + + switch (a->unparseable ? _DNS_TYPE_INVALID : a->key->type) { + + case DNS_TYPE_SRV: + r = dns_name_equal(a->srv.name, b->srv.name); + if (r <= 0) + return r; + + return a->srv.priority == b->srv.priority && + a->srv.weight == b->srv.weight && + a->srv.port == b->srv.port; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + return dns_name_equal(a->ptr.name, b->ptr.name); + + case DNS_TYPE_HINFO: + return strcaseeq(a->hinfo.cpu, b->hinfo.cpu) && + strcaseeq(a->hinfo.os, b->hinfo.os); + + case DNS_TYPE_SPF: /* exactly the same as TXT */ + case DNS_TYPE_TXT: + return dns_txt_item_equal(a->txt.items, b->txt.items); + + case DNS_TYPE_A: + return memcmp(&a->a.in_addr, &b->a.in_addr, sizeof(struct in_addr)) == 0; + + case DNS_TYPE_AAAA: + return memcmp(&a->aaaa.in6_addr, &b->aaaa.in6_addr, sizeof(struct in6_addr)) == 0; + + case DNS_TYPE_SOA: + r = dns_name_equal(a->soa.mname, b->soa.mname); + if (r <= 0) + return r; + r = dns_name_equal(a->soa.rname, b->soa.rname); + if (r <= 0) + return r; + + return a->soa.serial == b->soa.serial && + a->soa.refresh == b->soa.refresh && + a->soa.retry == b->soa.retry && + a->soa.expire == b->soa.expire && + a->soa.minimum == b->soa.minimum; + + case DNS_TYPE_MX: + if (a->mx.priority != b->mx.priority) + return 0; + + return dns_name_equal(a->mx.exchange, b->mx.exchange); + + case DNS_TYPE_LOC: + assert(a->loc.version == b->loc.version); + + return a->loc.size == b->loc.size && + a->loc.horiz_pre == b->loc.horiz_pre && + a->loc.vert_pre == b->loc.vert_pre && + a->loc.latitude == b->loc.latitude && + a->loc.longitude == b->loc.longitude && + a->loc.altitude == b->loc.altitude; + + case DNS_TYPE_DS: + return a->ds.key_tag == b->ds.key_tag && + a->ds.algorithm == b->ds.algorithm && + a->ds.digest_type == b->ds.digest_type && + FIELD_EQUAL(a->ds, b->ds, digest); + + case DNS_TYPE_SSHFP: + return a->sshfp.algorithm == b->sshfp.algorithm && + a->sshfp.fptype == b->sshfp.fptype && + FIELD_EQUAL(a->sshfp, b->sshfp, fingerprint); + + case DNS_TYPE_DNSKEY: + return a->dnskey.flags == b->dnskey.flags && + a->dnskey.protocol == b->dnskey.protocol && + a->dnskey.algorithm == b->dnskey.algorithm && + FIELD_EQUAL(a->dnskey, b->dnskey, key); + + case DNS_TYPE_RRSIG: + /* do the fast comparisons first */ + return a->rrsig.type_covered == b->rrsig.type_covered && + a->rrsig.algorithm == b->rrsig.algorithm && + a->rrsig.labels == b->rrsig.labels && + a->rrsig.original_ttl == b->rrsig.original_ttl && + a->rrsig.expiration == b->rrsig.expiration && + a->rrsig.inception == b->rrsig.inception && + a->rrsig.key_tag == b->rrsig.key_tag && + FIELD_EQUAL(a->rrsig, b->rrsig, signature) && + dns_name_equal(a->rrsig.signer, b->rrsig.signer); + + case DNS_TYPE_NSEC: + return dns_name_equal(a->nsec.next_domain_name, b->nsec.next_domain_name) && + bitmap_equal(a->nsec.types, b->nsec.types); + + case DNS_TYPE_NSEC3: + return a->nsec3.algorithm == b->nsec3.algorithm && + a->nsec3.flags == b->nsec3.flags && + a->nsec3.iterations == b->nsec3.iterations && + FIELD_EQUAL(a->nsec3, b->nsec3, salt) && + FIELD_EQUAL(a->nsec3, b->nsec3, next_hashed_name) && + bitmap_equal(a->nsec3.types, b->nsec3.types); + + case DNS_TYPE_TLSA: + return a->tlsa.cert_usage == b->tlsa.cert_usage && + a->tlsa.selector == b->tlsa.selector && + a->tlsa.matching_type == b->tlsa.matching_type && + FIELD_EQUAL(a->tlsa, b->tlsa, data); + + case DNS_TYPE_CAA: + return a->caa.flags == b->caa.flags && + streq(a->caa.tag, b->caa.tag) && + FIELD_EQUAL(a->caa, b->caa, value); + + case DNS_TYPE_OPENPGPKEY: + default: + return FIELD_EQUAL(a->generic, b->generic, data); + } +} + +static char* format_location(uint32_t latitude, uint32_t longitude, uint32_t altitude, + uint8_t size, uint8_t horiz_pre, uint8_t vert_pre) { + char *s; + char NS = latitude >= 1U<<31 ? 'N' : 'S'; + char EW = longitude >= 1U<<31 ? 'E' : 'W'; + + int lat = latitude >= 1U<<31 ? (int) (latitude - (1U<<31)) : (int) ((1U<<31) - latitude); + int lon = longitude >= 1U<<31 ? (int) (longitude - (1U<<31)) : (int) ((1U<<31) - longitude); + double alt = altitude >= 10000000u ? altitude - 10000000u : -(double)(10000000u - altitude); + double siz = (size >> 4) * exp10((double) (size & 0xF)); + double hor = (horiz_pre >> 4) * exp10((double) (horiz_pre & 0xF)); + double ver = (vert_pre >> 4) * exp10((double) (vert_pre & 0xF)); + + if (asprintf(&s, "%d %d %.3f %c %d %d %.3f %c %.2fm %.2fm %.2fm %.2fm", + (lat / 60000 / 60), + (lat / 60000) % 60, + (lat % 60000) / 1000., + NS, + (lon / 60000 / 60), + (lon / 60000) % 60, + (lon % 60000) / 1000., + EW, + alt / 100., + siz / 100., + hor / 100., + ver / 100.) < 0) + return NULL; + + return s; +} + +static int format_timestamp_dns(char *buf, size_t l, time_t sec) { + struct tm tm; + + assert(buf); + assert(l > strlen("YYYYMMDDHHmmSS")); + + if (!gmtime_r(&sec, &tm)) + return -EINVAL; + + if (strftime(buf, l, "%Y%m%d%H%M%S", &tm) <= 0) + return -EINVAL; + + return 0; +} + +static char *format_types(Bitmap *types) { + _cleanup_strv_free_ char **strv = NULL; + _cleanup_free_ char *str = NULL; + Iterator i; + unsigned type; + int r; + + BITMAP_FOREACH(type, types, i) { + if (dns_type_to_string(type)) { + r = strv_extend(&strv, dns_type_to_string(type)); + if (r < 0) + return NULL; + } else { + char *t; + + r = asprintf(&t, "TYPE%u", type); + if (r < 0) + return NULL; + + r = strv_consume(&strv, t); + if (r < 0) + return NULL; + } + } + + str = strv_join(strv, " "); + if (!str) + return NULL; + + return strjoin("( ", str, " )", NULL); +} + +static char *format_txt(DnsTxtItem *first) { + DnsTxtItem *i; + size_t c = 1; + char *p, *s; + + LIST_FOREACH(items, i, first) + c += i->length * 4 + 3; + + p = s = new(char, c); + if (!s) + return NULL; + + LIST_FOREACH(items, i, first) { + size_t j; + + if (i != first) + *(p++) = ' '; + + *(p++) = '"'; + + for (j = 0; j < i->length; j++) { + if (i->data[j] < ' ' || i->data[j] == '"' || i->data[j] >= 127) { + *(p++) = '\\'; + *(p++) = '0' + (i->data[j] / 100); + *(p++) = '0' + ((i->data[j] / 10) % 10); + *(p++) = '0' + (i->data[j] % 10); + } else + *(p++) = i->data[j]; + } + + *(p++) = '"'; + } + + *p = 0; + return s; +} + +const char *dns_resource_record_to_string(DnsResourceRecord *rr) { + _cleanup_free_ char *t = NULL; + char *s, k[DNS_RESOURCE_KEY_STRING_MAX]; + int r; + + assert(rr); + + if (rr->to_string) + return rr->to_string; + + dns_resource_key_to_string(rr->key, k, sizeof(k)); + + switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { + + case DNS_TYPE_SRV: + r = asprintf(&s, "%s %u %u %u %s", + k, + rr->srv.priority, + rr->srv.weight, + rr->srv.port, + strna(rr->srv.name)); + if (r < 0) + return NULL; + break; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + s = strjoin(k, " ", rr->ptr.name, NULL); + if (!s) + return NULL; + + break; + + case DNS_TYPE_HINFO: + s = strjoin(k, " ", rr->hinfo.cpu, " ", rr->hinfo.os, NULL); + if (!s) + return NULL; + break; + + case DNS_TYPE_SPF: /* exactly the same as TXT */ + case DNS_TYPE_TXT: + t = format_txt(rr->txt.items); + if (!t) + return NULL; + + s = strjoin(k, " ", t, NULL); + if (!s) + return NULL; + break; + + case DNS_TYPE_A: { + _cleanup_free_ char *x = NULL; + + r = in_addr_to_string(AF_INET, (const union in_addr_union*) &rr->a.in_addr, &x); + if (r < 0) + return NULL; + + s = strjoin(k, " ", x, NULL); + if (!s) + return NULL; + break; + } + + case DNS_TYPE_AAAA: + r = in_addr_to_string(AF_INET6, (const union in_addr_union*) &rr->aaaa.in6_addr, &t); + if (r < 0) + return NULL; + + s = strjoin(k, " ", t, NULL); + if (!s) + return NULL; + break; + + case DNS_TYPE_SOA: + r = asprintf(&s, "%s %s %s %u %u %u %u %u", + k, + strna(rr->soa.mname), + strna(rr->soa.rname), + rr->soa.serial, + rr->soa.refresh, + rr->soa.retry, + rr->soa.expire, + rr->soa.minimum); + if (r < 0) + return NULL; + break; + + case DNS_TYPE_MX: + r = asprintf(&s, "%s %u %s", + k, + rr->mx.priority, + rr->mx.exchange); + if (r < 0) + return NULL; + break; + + case DNS_TYPE_LOC: + assert(rr->loc.version == 0); + + t = format_location(rr->loc.latitude, + rr->loc.longitude, + rr->loc.altitude, + rr->loc.size, + rr->loc.horiz_pre, + rr->loc.vert_pre); + if (!t) + return NULL; + + s = strjoin(k, " ", t, NULL); + if (!s) + return NULL; + break; + + case DNS_TYPE_DS: + t = hexmem(rr->ds.digest, rr->ds.digest_size); + if (!t) + return NULL; + + r = asprintf(&s, "%s %u %u %u %s", + k, + rr->ds.key_tag, + rr->ds.algorithm, + rr->ds.digest_type, + t); + if (r < 0) + return NULL; + break; + + case DNS_TYPE_SSHFP: + t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); + if (!t) + return NULL; + + r = asprintf(&s, "%s %u %u %s", + k, + rr->sshfp.algorithm, + rr->sshfp.fptype, + t); + if (r < 0) + return NULL; + break; + + case DNS_TYPE_DNSKEY: { + _cleanup_free_ char *alg = NULL; + char *ss; + int n; + uint16_t key_tag; + + key_tag = dnssec_keytag(rr, true); + + r = dnssec_algorithm_to_string_alloc(rr->dnskey.algorithm, &alg); + if (r < 0) + return NULL; + + r = asprintf(&s, "%s %u %u %s %n", + k, + rr->dnskey.flags, + rr->dnskey.protocol, + alg, + &n); + if (r < 0) + return NULL; + + r = base64_append(&s, n, + rr->dnskey.key, rr->dnskey.key_size, + 8, columns()); + if (r < 0) + return NULL; + + r = asprintf(&ss, "%s\n" + " -- Flags:%s%s%s\n" + " -- Key tag: %u", + s, + rr->dnskey.flags & DNSKEY_FLAG_SEP ? " SEP" : "", + rr->dnskey.flags & DNSKEY_FLAG_REVOKE ? " REVOKE" : "", + rr->dnskey.flags & DNSKEY_FLAG_ZONE_KEY ? " ZONE_KEY" : "", + key_tag); + if (r < 0) + return NULL; + free(s); + s = ss; + + break; + } + + case DNS_TYPE_RRSIG: { + _cleanup_free_ char *alg = NULL; + char expiration[strlen("YYYYMMDDHHmmSS") + 1], inception[strlen("YYYYMMDDHHmmSS") + 1]; + const char *type; + int n; + + type = dns_type_to_string(rr->rrsig.type_covered); + + r = dnssec_algorithm_to_string_alloc(rr->rrsig.algorithm, &alg); + if (r < 0) + return NULL; + + r = format_timestamp_dns(expiration, sizeof(expiration), rr->rrsig.expiration); + if (r < 0) + return NULL; + + r = format_timestamp_dns(inception, sizeof(inception), rr->rrsig.inception); + if (r < 0) + return NULL; + + /* TYPE?? follows + * http://tools.ietf.org/html/rfc3597#section-5 */ + + r = asprintf(&s, "%s %s%.*u %s %u %u %s %s %u %s %n", + k, + type ?: "TYPE", + type ? 0 : 1, type ? 0u : (unsigned) rr->rrsig.type_covered, + alg, + rr->rrsig.labels, + rr->rrsig.original_ttl, + expiration, + inception, + rr->rrsig.key_tag, + rr->rrsig.signer, + &n); + if (r < 0) + return NULL; + + r = base64_append(&s, n, + rr->rrsig.signature, rr->rrsig.signature_size, + 8, columns()); + if (r < 0) + return NULL; + + break; + } + + case DNS_TYPE_NSEC: + t = format_types(rr->nsec.types); + if (!t) + return NULL; + + r = asprintf(&s, "%s %s %s", + k, + rr->nsec.next_domain_name, + t); + if (r < 0) + return NULL; + break; + + case DNS_TYPE_NSEC3: { + _cleanup_free_ char *salt = NULL, *hash = NULL; + + if (rr->nsec3.salt_size > 0) { + salt = hexmem(rr->nsec3.salt, rr->nsec3.salt_size); + if (!salt) + return NULL; + } + + hash = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false); + if (!hash) + return NULL; + + t = format_types(rr->nsec3.types); + if (!t) + return NULL; + + r = asprintf(&s, "%s %"PRIu8" %"PRIu8" %"PRIu16" %s %s %s", + k, + rr->nsec3.algorithm, + rr->nsec3.flags, + rr->nsec3.iterations, + rr->nsec3.salt_size > 0 ? salt : "-", + hash, + t); + if (r < 0) + return NULL; + + break; + } + + case DNS_TYPE_TLSA: { + const char *cert_usage, *selector, *matching_type; + + cert_usage = tlsa_cert_usage_to_string(rr->tlsa.cert_usage); + selector = tlsa_selector_to_string(rr->tlsa.selector); + matching_type = tlsa_matching_type_to_string(rr->tlsa.matching_type); + + t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); + if (!t) + return NULL; + + r = asprintf(&s, + "%s %u %u %u %s\n" + " -- Cert. usage: %s\n" + " -- Selector: %s\n" + " -- Matching type: %s", + k, + rr->tlsa.cert_usage, + rr->tlsa.selector, + rr->tlsa.matching_type, + t, + cert_usage, + selector, + matching_type); + if (r < 0) + return NULL; + + break; + } + + case DNS_TYPE_CAA: { + _cleanup_free_ char *value; + + value = octescape(rr->caa.value, rr->caa.value_size); + if (!value) + return NULL; + + r = asprintf(&s, "%s %u %s \"%s\"%s%s%s%.0u", + k, + rr->caa.flags, + rr->caa.tag, + value, + rr->caa.flags ? "\n -- Flags:" : "", + rr->caa.flags & CAA_FLAG_CRITICAL ? " critical" : "", + rr->caa.flags & ~CAA_FLAG_CRITICAL ? " " : "", + rr->caa.flags & ~CAA_FLAG_CRITICAL); + if (r < 0) + return NULL; + + break; + } + + case DNS_TYPE_OPENPGPKEY: { + int n; + + r = asprintf(&s, "%s %n", + k, + &n); + if (r < 0) + return NULL; + + r = base64_append(&s, n, + rr->generic.data, rr->generic.data_size, + 8, columns()); + if (r < 0) + return NULL; + break; + } + + default: + t = hexmem(rr->generic.data, rr->generic.data_size); + if (!t) + return NULL; + + /* Format as documented in RFC 3597, Section 5 */ + r = asprintf(&s, "%s \\# %zu %s", k, rr->generic.data_size, t); + if (r < 0) + return NULL; + break; + } + + rr->to_string = s; + return s; +} + +ssize_t dns_resource_record_payload(DnsResourceRecord *rr, void **out) { + assert(rr); + assert(out); + + switch(rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { + case DNS_TYPE_SRV: + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + case DNS_TYPE_HINFO: + case DNS_TYPE_SPF: + case DNS_TYPE_TXT: + case DNS_TYPE_A: + case DNS_TYPE_AAAA: + case DNS_TYPE_SOA: + case DNS_TYPE_MX: + case DNS_TYPE_LOC: + case DNS_TYPE_DS: + case DNS_TYPE_DNSKEY: + case DNS_TYPE_RRSIG: + case DNS_TYPE_NSEC: + case DNS_TYPE_NSEC3: + return -EINVAL; + + case DNS_TYPE_SSHFP: + *out = rr->sshfp.fingerprint; + return rr->sshfp.fingerprint_size; + + case DNS_TYPE_TLSA: + *out = rr->tlsa.data; + return rr->tlsa.data_size; + + + case DNS_TYPE_OPENPGPKEY: + default: + *out = rr->generic.data; + return rr->generic.data_size; + } +} + +int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) { + + DnsPacket packet = { + .n_ref = 1, + .protocol = DNS_PROTOCOL_DNS, + .on_stack = true, + .refuse_compression = true, + .canonical_form = canonical, + }; + + size_t start, rds; + int r; + + assert(rr); + + /* Generates the RR in wire-format, optionally in the + * canonical form as discussed in the DNSSEC RFC 4034, Section + * 6.2. We allocate a throw-away DnsPacket object on the stack + * here, because we need some book-keeping for memory + * management, and can reuse the DnsPacket serializer, that + * can generate the canonical form, too, but also knows label + * compression and suchlike. */ + + if (rr->wire_format && rr->wire_format_canonical == canonical) + return 0; + + r = dns_packet_append_rr(&packet, rr, &start, &rds); + if (r < 0) + return r; + + assert(start == 0); + assert(packet._data); + + free(rr->wire_format); + rr->wire_format = packet._data; + rr->wire_format_size = packet.size; + rr->wire_format_rdata_offset = rds; + rr->wire_format_canonical = canonical; + + packet._data = NULL; + dns_packet_unref(&packet); + + return 0; +} + +int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret) { + const char *n; + int r; + + assert(rr); + assert(ret); + + /* Returns the RRset's signer, if it is known. */ + + if (rr->n_skip_labels_signer == (unsigned) -1) + return -ENODATA; + + n = dns_resource_key_name(rr->key); + r = dns_name_skip(n, rr->n_skip_labels_signer, &n); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + *ret = n; + return 0; +} + +int dns_resource_record_source(DnsResourceRecord *rr, const char **ret) { + const char *n; + int r; + + assert(rr); + assert(ret); + + /* Returns the RRset's synthesizing source, if it is known. */ + + if (rr->n_skip_labels_source == (unsigned) -1) + return -ENODATA; + + n = dns_resource_key_name(rr->key); + r = dns_name_skip(n, rr->n_skip_labels_source, &n); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + *ret = n; + return 0; +} + +int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone) { + const char *signer; + int r; + + assert(rr); + + r = dns_resource_record_signer(rr, &signer); + if (r < 0) + return r; + + return dns_name_equal(zone, signer); +} + +int dns_resource_record_is_synthetic(DnsResourceRecord *rr) { + int r; + + assert(rr); + + /* Returns > 0 if the RR is generated from a wildcard, and is not the asterisk name itself */ + + if (rr->n_skip_labels_source == (unsigned) -1) + return -ENODATA; + + if (rr->n_skip_labels_source == 0) + return 0; + + if (rr->n_skip_labels_source > 1) + return 1; + + r = dns_name_startswith(dns_resource_key_name(rr->key), "*"); + if (r < 0) + return r; + + return !r; +} + +void dns_resource_record_hash_func(const void *i, struct siphash *state) { + const DnsResourceRecord *rr = i; + + assert(rr); + + dns_resource_key_hash_func(rr->key, state); + + switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { + + case DNS_TYPE_SRV: + siphash24_compress(&rr->srv.priority, sizeof(rr->srv.priority), state); + siphash24_compress(&rr->srv.weight, sizeof(rr->srv.weight), state); + siphash24_compress(&rr->srv.port, sizeof(rr->srv.port), state); + dns_name_hash_func(rr->srv.name, state); + break; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + dns_name_hash_func(rr->ptr.name, state); + break; + + case DNS_TYPE_HINFO: + string_hash_func(rr->hinfo.cpu, state); + string_hash_func(rr->hinfo.os, state); + break; + + case DNS_TYPE_TXT: + case DNS_TYPE_SPF: { + DnsTxtItem *j; + + LIST_FOREACH(items, j, rr->txt.items) { + siphash24_compress(j->data, j->length, state); + + /* Add an extra NUL byte, so that "a" followed by "b" doesn't result in the same hash as "ab" + * followed by "". */ + siphash24_compress_byte(0, state); + } + break; + } + + case DNS_TYPE_A: + siphash24_compress(&rr->a.in_addr, sizeof(rr->a.in_addr), state); + break; + + case DNS_TYPE_AAAA: + siphash24_compress(&rr->aaaa.in6_addr, sizeof(rr->aaaa.in6_addr), state); + break; + + case DNS_TYPE_SOA: + dns_name_hash_func(rr->soa.mname, state); + dns_name_hash_func(rr->soa.rname, state); + siphash24_compress(&rr->soa.serial, sizeof(rr->soa.serial), state); + siphash24_compress(&rr->soa.refresh, sizeof(rr->soa.refresh), state); + siphash24_compress(&rr->soa.retry, sizeof(rr->soa.retry), state); + siphash24_compress(&rr->soa.expire, sizeof(rr->soa.expire), state); + siphash24_compress(&rr->soa.minimum, sizeof(rr->soa.minimum), state); + break; + + case DNS_TYPE_MX: + siphash24_compress(&rr->mx.priority, sizeof(rr->mx.priority), state); + dns_name_hash_func(rr->mx.exchange, state); + break; + + case DNS_TYPE_LOC: + siphash24_compress(&rr->loc.version, sizeof(rr->loc.version), state); + siphash24_compress(&rr->loc.size, sizeof(rr->loc.size), state); + siphash24_compress(&rr->loc.horiz_pre, sizeof(rr->loc.horiz_pre), state); + siphash24_compress(&rr->loc.vert_pre, sizeof(rr->loc.vert_pre), state); + siphash24_compress(&rr->loc.latitude, sizeof(rr->loc.latitude), state); + siphash24_compress(&rr->loc.longitude, sizeof(rr->loc.longitude), state); + siphash24_compress(&rr->loc.altitude, sizeof(rr->loc.altitude), state); + break; + + case DNS_TYPE_SSHFP: + siphash24_compress(&rr->sshfp.algorithm, sizeof(rr->sshfp.algorithm), state); + siphash24_compress(&rr->sshfp.fptype, sizeof(rr->sshfp.fptype), state); + siphash24_compress(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, state); + break; + + case DNS_TYPE_DNSKEY: + siphash24_compress(&rr->dnskey.flags, sizeof(rr->dnskey.flags), state); + siphash24_compress(&rr->dnskey.protocol, sizeof(rr->dnskey.protocol), state); + siphash24_compress(&rr->dnskey.algorithm, sizeof(rr->dnskey.algorithm), state); + siphash24_compress(rr->dnskey.key, rr->dnskey.key_size, state); + break; + + case DNS_TYPE_RRSIG: + siphash24_compress(&rr->rrsig.type_covered, sizeof(rr->rrsig.type_covered), state); + siphash24_compress(&rr->rrsig.algorithm, sizeof(rr->rrsig.algorithm), state); + siphash24_compress(&rr->rrsig.labels, sizeof(rr->rrsig.labels), state); + siphash24_compress(&rr->rrsig.original_ttl, sizeof(rr->rrsig.original_ttl), state); + siphash24_compress(&rr->rrsig.expiration, sizeof(rr->rrsig.expiration), state); + siphash24_compress(&rr->rrsig.inception, sizeof(rr->rrsig.inception), state); + siphash24_compress(&rr->rrsig.key_tag, sizeof(rr->rrsig.key_tag), state); + dns_name_hash_func(rr->rrsig.signer, state); + siphash24_compress(rr->rrsig.signature, rr->rrsig.signature_size, state); + break; + + case DNS_TYPE_NSEC: + dns_name_hash_func(rr->nsec.next_domain_name, state); + /* FIXME: we leave out the type bitmap here. Hash + * would be better if we'd take it into account + * too. */ + break; + + case DNS_TYPE_DS: + siphash24_compress(&rr->ds.key_tag, sizeof(rr->ds.key_tag), state); + siphash24_compress(&rr->ds.algorithm, sizeof(rr->ds.algorithm), state); + siphash24_compress(&rr->ds.digest_type, sizeof(rr->ds.digest_type), state); + siphash24_compress(rr->ds.digest, rr->ds.digest_size, state); + break; + + case DNS_TYPE_NSEC3: + siphash24_compress(&rr->nsec3.algorithm, sizeof(rr->nsec3.algorithm), state); + siphash24_compress(&rr->nsec3.flags, sizeof(rr->nsec3.flags), state); + siphash24_compress(&rr->nsec3.iterations, sizeof(rr->nsec3.iterations), state); + siphash24_compress(rr->nsec3.salt, rr->nsec3.salt_size, state); + siphash24_compress(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, state); + /* FIXME: We leave the bitmaps out */ + break; + + case DNS_TYPE_TLSA: + siphash24_compress(&rr->tlsa.cert_usage, sizeof(rr->tlsa.cert_usage), state); + siphash24_compress(&rr->tlsa.selector, sizeof(rr->tlsa.selector), state); + siphash24_compress(&rr->tlsa.matching_type, sizeof(rr->tlsa.matching_type), state); + siphash24_compress(rr->tlsa.data, rr->tlsa.data_size, state); + break; + + case DNS_TYPE_CAA: + siphash24_compress(&rr->caa.flags, sizeof(rr->caa.flags), state); + string_hash_func(rr->caa.tag, state); + siphash24_compress(rr->caa.value, rr->caa.value_size, state); + break; + + case DNS_TYPE_OPENPGPKEY: + default: + siphash24_compress(rr->generic.data, rr->generic.data_size, state); + break; + } +} + +static int dns_resource_record_compare_func(const void *a, const void *b) { + const DnsResourceRecord *x = a, *y = b; + int ret; + + ret = dns_resource_key_compare_func(x->key, y->key); + if (ret != 0) + return ret; + + if (dns_resource_record_equal(x, y)) + return 0; + + /* This is a bit dirty, we don't implement proper ordering, but + * the hashtable doesn't need ordering anyway, hence we don't + * care. */ + return x < y ? -1 : 1; +} + +const struct hash_ops dns_resource_record_hash_ops = { + .hash = dns_resource_record_hash_func, + .compare = dns_resource_record_compare_func, +}; + +DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i) { + DnsTxtItem *n; + + if (!i) + return NULL; + + n = i->items_next; + + free(i); + return dns_txt_item_free_all(n); +} + +bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) { + + if (a == b) + return true; + + if (!a != !b) + return false; + + if (!a) + return true; + + if (a->length != b->length) + return false; + + if (memcmp(a->data, b->data, a->length) != 0) + return false; + + return dns_txt_item_equal(a->items_next, b->items_next); +} + +static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = { + /* Mnemonics as listed on https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ + [DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5", + [DNSSEC_ALGORITHM_DH] = "DH", + [DNSSEC_ALGORITHM_DSA] = "DSA", + [DNSSEC_ALGORITHM_ECC] = "ECC", + [DNSSEC_ALGORITHM_RSASHA1] = "RSASHA1", + [DNSSEC_ALGORITHM_DSA_NSEC3_SHA1] = "DSA-NSEC3-SHA1", + [DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1] = "RSASHA1-NSEC3-SHA1", + [DNSSEC_ALGORITHM_RSASHA256] = "RSASHA256", + [DNSSEC_ALGORITHM_RSASHA512] = "RSASHA512", + [DNSSEC_ALGORITHM_ECC_GOST] = "ECC-GOST", + [DNSSEC_ALGORITHM_ECDSAP256SHA256] = "ECDSAP256SHA256", + [DNSSEC_ALGORITHM_ECDSAP384SHA384] = "ECDSAP384SHA384", + [DNSSEC_ALGORITHM_INDIRECT] = "INDIRECT", + [DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS", + [DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID", +}; +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(dnssec_algorithm, int, 255); + +static const char* const dnssec_digest_table[_DNSSEC_DIGEST_MAX_DEFINED] = { + /* Names as listed on https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ + [DNSSEC_DIGEST_SHA1] = "SHA-1", + [DNSSEC_DIGEST_SHA256] = "SHA-256", + [DNSSEC_DIGEST_GOST_R_34_11_94] = "GOST_R_34.11-94", + [DNSSEC_DIGEST_SHA384] = "SHA-384", +}; +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(dnssec_digest, int, 255); diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-rr.h b/src/grp-resolve/systemd-resolved/resolved-dns-rr.h new file mode 100644 index 0000000000..020a2abd77 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-rr.h @@ -0,0 +1,342 @@ +#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 + +#include "bitmap.h" +#include "dns-type.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "list.h" +#include "string-util.h" + +typedef struct DnsResourceKey DnsResourceKey; +typedef struct DnsResourceRecord DnsResourceRecord; +typedef struct DnsTxtItem DnsTxtItem; + +/* DNSKEY RR flags */ +#define DNSKEY_FLAG_SEP (UINT16_C(1) << 0) +#define DNSKEY_FLAG_REVOKE (UINT16_C(1) << 7) +#define DNSKEY_FLAG_ZONE_KEY (UINT16_C(1) << 8) + +/* mDNS RR flags */ +#define MDNS_RR_CACHE_FLUSH (UINT16_C(1) << 15) + +/* DNSSEC algorithm identifiers, see + * http://tools.ietf.org/html/rfc4034#appendix-A.1 and + * https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ +enum { + DNSSEC_ALGORITHM_RSAMD5 = 1, + DNSSEC_ALGORITHM_DH, + DNSSEC_ALGORITHM_DSA, + DNSSEC_ALGORITHM_ECC, + DNSSEC_ALGORITHM_RSASHA1, + DNSSEC_ALGORITHM_DSA_NSEC3_SHA1, + DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, + DNSSEC_ALGORITHM_RSASHA256 = 8, /* RFC 5702 */ + DNSSEC_ALGORITHM_RSASHA512 = 10, /* RFC 5702 */ + DNSSEC_ALGORITHM_ECC_GOST = 12, /* RFC 5933 */ + DNSSEC_ALGORITHM_ECDSAP256SHA256 = 13, /* RFC 6605 */ + DNSSEC_ALGORITHM_ECDSAP384SHA384 = 14, /* RFC 6605 */ + DNSSEC_ALGORITHM_INDIRECT = 252, + DNSSEC_ALGORITHM_PRIVATEDNS, + DNSSEC_ALGORITHM_PRIVATEOID, + _DNSSEC_ALGORITHM_MAX_DEFINED +}; + +/* DNSSEC digest identifiers, see + * https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ +enum { + DNSSEC_DIGEST_SHA1 = 1, + DNSSEC_DIGEST_SHA256 = 2, /* RFC 4509 */ + DNSSEC_DIGEST_GOST_R_34_11_94 = 3, /* RFC 5933 */ + DNSSEC_DIGEST_SHA384 = 4, /* RFC 6605 */ + _DNSSEC_DIGEST_MAX_DEFINED +}; + +/* DNSSEC NSEC3 hash algorithms, see + * https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml */ +enum { + NSEC3_ALGORITHM_SHA1 = 1, + _NSEC3_ALGORITHM_MAX_DEFINED +}; + +struct DnsResourceKey { + unsigned n_ref; /* (unsigned -1) for const keys, see below */ + uint16_t class, type; + char *_name; /* don't access directly, use dns_resource_key_name()! */ +}; + +/* Creates a temporary resource key. This is only useful to quickly + * look up something, without allocating a full DnsResourceKey object + * for it. Note that it is not OK to take references to this kind of + * resource key object. */ +#define DNS_RESOURCE_KEY_CONST(c, t, n) \ + ((DnsResourceKey) { \ + .n_ref = (unsigned) -1, \ + .class = c, \ + .type = t, \ + ._name = (char*) n, \ + }) + + +struct DnsTxtItem { + size_t length; + LIST_FIELDS(DnsTxtItem, items); + uint8_t data[]; +}; + +struct DnsResourceRecord { + unsigned n_ref; + DnsResourceKey *key; + + char *to_string; + + uint32_t ttl; + usec_t expiry; /* RRSIG signature expiry */ + + /* How many labels to strip to determine "signer" of the RRSIG (aka, the zone). -1 if not signed. */ + unsigned n_skip_labels_signer; + /* How many labels to strip to determine "synthesizing source" of this RR, i.e. the wildcard's immediate parent. -1 if not signed. */ + unsigned n_skip_labels_source; + + bool unparseable:1; + + bool wire_format_canonical:1; + void *wire_format; + size_t wire_format_size; + size_t wire_format_rdata_offset; + + union { + struct { + void *data; + size_t data_size; + } generic, opt; + + struct { + uint16_t priority; + uint16_t weight; + uint16_t port; + char *name; + } srv; + + struct { + char *name; + } ptr, ns, cname, dname; + + struct { + char *cpu; + char *os; + } hinfo; + + struct { + DnsTxtItem *items; + } txt, spf; + + struct { + struct in_addr in_addr; + } a; + + struct { + struct in6_addr in6_addr; + } aaaa; + + struct { + char *mname; + char *rname; + uint32_t serial; + uint32_t refresh; + uint32_t retry; + uint32_t expire; + uint32_t minimum; + } soa; + + struct { + uint16_t priority; + char *exchange; + } mx; + + /* https://tools.ietf.org/html/rfc1876 */ + struct { + uint8_t version; + uint8_t size; + uint8_t horiz_pre; + uint8_t vert_pre; + uint32_t latitude; + uint32_t longitude; + uint32_t altitude; + } loc; + + /* https://tools.ietf.org/html/rfc4255#section-3.1 */ + struct { + uint8_t algorithm; + uint8_t fptype; + void *fingerprint; + size_t fingerprint_size; + } sshfp; + + /* http://tools.ietf.org/html/rfc4034#section-2.1 */ + struct { + uint16_t flags; + uint8_t protocol; + uint8_t algorithm; + void* key; + size_t key_size; + } dnskey; + + /* http://tools.ietf.org/html/rfc4034#section-3.1 */ + struct { + uint16_t type_covered; + uint8_t algorithm; + uint8_t labels; + uint32_t original_ttl; + uint32_t expiration; + uint32_t inception; + uint16_t key_tag; + char *signer; + void *signature; + size_t signature_size; + } rrsig; + + /* https://tools.ietf.org/html/rfc4034#section-4.1 */ + struct { + char *next_domain_name; + Bitmap *types; + } nsec; + + /* https://tools.ietf.org/html/rfc4034#section-5.1 */ + struct { + uint16_t key_tag; + uint8_t algorithm; + uint8_t digest_type; + void *digest; + size_t digest_size; + } ds; + + struct { + uint8_t algorithm; + uint8_t flags; + uint16_t iterations; + void *salt; + size_t salt_size; + void *next_hashed_name; + size_t next_hashed_name_size; + Bitmap *types; + } nsec3; + + /* https://tools.ietf.org/html/draft-ietf-dane-protocol-23 */ + struct { + uint8_t cert_usage; + uint8_t selector; + uint8_t matching_type; + void *data; + size_t data_size; + } tlsa; + + /* https://tools.ietf.org/html/rfc6844 */ + struct { + uint8_t flags; + char *tag; + void *value; + size_t value_size; + } caa; + }; +}; + +static inline const void* DNS_RESOURCE_RECORD_RDATA(DnsResourceRecord *rr) { + if (!rr) + return NULL; + + if (!rr->wire_format) + return NULL; + + assert(rr->wire_format_rdata_offset <= rr->wire_format_size); + return (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset; +} + +static inline size_t DNS_RESOURCE_RECORD_RDATA_SIZE(DnsResourceRecord *rr) { + if (!rr) + return 0; + if (!rr->wire_format) + return 0; + + assert(rr->wire_format_rdata_offset <= rr->wire_format_size); + return rr->wire_format_size - rr->wire_format_rdata_offset; +} + +DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name); +DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname); +int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name); +DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name); +DnsResourceKey* dns_resource_key_ref(DnsResourceKey *key); +DnsResourceKey* dns_resource_key_unref(DnsResourceKey *key); +const char* dns_resource_key_name(const DnsResourceKey *key); +bool dns_resource_key_is_address(const DnsResourceKey *key); +int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b); +int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain); +int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain); +int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa); + +/* _DNS_{CLASS,TYPE}_STRING_MAX include one byte for NUL, which we use for space instead below. + * DNS_HOSTNAME_MAX does not include the NUL byte, so we need to add 1. */ +#define DNS_RESOURCE_KEY_STRING_MAX (_DNS_CLASS_STRING_MAX + _DNS_TYPE_STRING_MAX + DNS_HOSTNAME_MAX + 1) + +char* dns_resource_key_to_string(const DnsResourceKey *key, char *buf, size_t buf_size); +ssize_t dns_resource_record_payload(DnsResourceRecord *rr, void **out); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceKey*, dns_resource_key_unref); + +static inline bool dns_key_is_shared(const DnsResourceKey *key) { + return IN_SET(key->type, DNS_TYPE_PTR); +} + +bool dns_resource_key_reduce(DnsResourceKey **a, DnsResourceKey **b); + +DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key); +DnsResourceRecord* dns_resource_record_new_full(uint16_t class, uint16_t type, const char *name); +DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr); +DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr); +int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name); +int dns_resource_record_new_address(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name); +int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b); +const char* dns_resource_record_to_string(DnsResourceRecord *rr); +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref); + +int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical); + +int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret); +int dns_resource_record_source(DnsResourceRecord *rr, const char **ret); +int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone); +int dns_resource_record_is_synthetic(DnsResourceRecord *rr); + +DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i); +bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b); + +void dns_resource_record_hash_func(const void *i, struct siphash *state); + +extern const struct hash_ops dns_resource_key_hash_ops; +extern const struct hash_ops dns_resource_record_hash_ops; + +int dnssec_algorithm_to_string_alloc(int i, char **ret); +int dnssec_algorithm_from_string(const char *s) _pure_; + +int dnssec_digest_to_string_alloc(int i, char **ret); +int dnssec_digest_from_string(const char *s) _pure_; diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-scope.c b/src/grp-resolve/systemd-resolved/resolved-dns-scope.c new file mode 100644 index 0000000000..66e4585c18 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-scope.c @@ -0,0 +1,1028 @@ +/*** + 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 "af-list.h" +#include "alloc-util.h" +#include "dns-domain.h" +#include "fd-util.h" +#include "hostname-util.h" +#include "missing.h" +#include "random-util.h" +#include "resolved-dns-scope.h" +#include "resolved-llmnr.h" +#include "resolved-mdns.h" +#include "socket-util.h" +#include "strv.h" + +#define MULTICAST_RATELIMIT_INTERVAL_USEC (1*USEC_PER_SEC) +#define MULTICAST_RATELIMIT_BURST 1000 + +/* After how much time to repeat LLMNR requests, see RFC 4795 Section 7 */ +#define MULTICAST_RESEND_TIMEOUT_MIN_USEC (100 * USEC_PER_MSEC) +#define MULTICAST_RESEND_TIMEOUT_MAX_USEC (1 * USEC_PER_SEC) + +int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int family) { + DnsScope *s; + + assert(m); + assert(ret); + + s = new0(DnsScope, 1); + if (!s) + return -ENOMEM; + + s->manager = m; + s->link = l; + s->protocol = protocol; + s->family = family; + s->resend_timeout = MULTICAST_RESEND_TIMEOUT_MIN_USEC; + + if (protocol == DNS_PROTOCOL_DNS) { + /* Copy DNSSEC mode from the link if it is set there, + * otherwise take the manager's DNSSEC mode. Note that + * we copy this only at scope creation time, and do + * not update it from the on, even if the setting + * changes. */ + + if (l) + s->dnssec_mode = link_get_dnssec_mode(l); + else + s->dnssec_mode = manager_get_dnssec_mode(m); + } else + s->dnssec_mode = DNSSEC_NO; + + LIST_PREPEND(scopes, m->dns_scopes, s); + + dns_scope_llmnr_membership(s, true); + dns_scope_mdns_membership(s, true); + + log_debug("New scope on link %s, protocol %s, family %s", l ? l->name : "*", dns_protocol_to_string(protocol), family == AF_UNSPEC ? "*" : af_to_name(family)); + + /* Enforce ratelimiting for the multicast protocols */ + RATELIMIT_INIT(s->ratelimit, MULTICAST_RATELIMIT_INTERVAL_USEC, MULTICAST_RATELIMIT_BURST); + + *ret = s; + return 0; +} + +static void dns_scope_abort_transactions(DnsScope *s) { + assert(s); + + while (s->transactions) { + DnsTransaction *t = s->transactions; + + /* Abort the transaction, but make sure it is not + * freed while we still look at it */ + + t->block_gc++; + if (DNS_TRANSACTION_IS_LIVE(t->state)) + dns_transaction_complete(t, DNS_TRANSACTION_ABORTED); + t->block_gc--; + + dns_transaction_free(t); + } +} + +DnsScope* dns_scope_free(DnsScope *s) { + DnsResourceRecord *rr; + + if (!s) + return NULL; + + log_debug("Removing scope on link %s, protocol %s, family %s", s->link ? s->link->name : "*", dns_protocol_to_string(s->protocol), s->family == AF_UNSPEC ? "*" : af_to_name(s->family)); + + dns_scope_llmnr_membership(s, false); + dns_scope_mdns_membership(s, false); + dns_scope_abort_transactions(s); + + while (s->query_candidates) + dns_query_candidate_free(s->query_candidates); + + hashmap_free(s->transactions_by_key); + + while ((rr = ordered_hashmap_steal_first(s->conflict_queue))) + dns_resource_record_unref(rr); + + ordered_hashmap_free(s->conflict_queue); + sd_event_source_unref(s->conflict_event_source); + + dns_cache_flush(&s->cache); + dns_zone_flush(&s->zone); + + LIST_REMOVE(scopes, s->manager->dns_scopes, s); + free(s); + + return NULL; +} + +DnsServer *dns_scope_get_dns_server(DnsScope *s) { + assert(s); + + if (s->protocol != DNS_PROTOCOL_DNS) + return NULL; + + if (s->link) + return link_get_dns_server(s->link); + else + return manager_get_dns_server(s->manager); +} + +void dns_scope_next_dns_server(DnsScope *s) { + assert(s); + + if (s->protocol != DNS_PROTOCOL_DNS) + return; + + if (s->link) + link_next_dns_server(s->link); + else + manager_next_dns_server(s->manager); +} + +void dns_scope_packet_received(DnsScope *s, usec_t rtt) { + assert(s); + + if (rtt <= s->max_rtt) + return; + + s->max_rtt = rtt; + s->resend_timeout = MIN(MAX(MULTICAST_RESEND_TIMEOUT_MIN_USEC, s->max_rtt * 2), MULTICAST_RESEND_TIMEOUT_MAX_USEC); +} + +void dns_scope_packet_lost(DnsScope *s, usec_t usec) { + assert(s); + + if (s->resend_timeout <= usec) + s->resend_timeout = MIN(s->resend_timeout * 2, MULTICAST_RESEND_TIMEOUT_MAX_USEC); +} + +static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) { + union in_addr_union addr; + int ifindex = 0, r; + int family; + uint32_t mtu; + + assert(s); + assert(p); + assert(p->protocol == s->protocol); + + if (s->link) { + mtu = s->link->mtu; + ifindex = s->link->ifindex; + } else + mtu = manager_find_mtu(s->manager); + + switch (s->protocol) { + + case DNS_PROTOCOL_DNS: + assert(fd >= 0); + + if (DNS_PACKET_QDCOUNT(p) > 1) + return -EOPNOTSUPP; + + if (p->size > DNS_PACKET_UNICAST_SIZE_MAX) + return -EMSGSIZE; + + if (p->size + UDP_PACKET_HEADER_SIZE > mtu) + return -EMSGSIZE; + + r = manager_write(s->manager, fd, p); + if (r < 0) + return r; + + break; + + case DNS_PROTOCOL_LLMNR: + assert(fd < 0); + + if (DNS_PACKET_QDCOUNT(p) > 1) + return -EOPNOTSUPP; + + if (!ratelimit_test(&s->ratelimit)) + return -EBUSY; + + family = s->family; + + if (family == AF_INET) { + addr.in = LLMNR_MULTICAST_IPV4_ADDRESS; + fd = manager_llmnr_ipv4_udp_fd(s->manager); + } else if (family == AF_INET6) { + addr.in6 = LLMNR_MULTICAST_IPV6_ADDRESS; + fd = manager_llmnr_ipv6_udp_fd(s->manager); + } else + return -EAFNOSUPPORT; + if (fd < 0) + return fd; + + r = manager_send(s->manager, fd, ifindex, family, &addr, LLMNR_PORT, p); + if (r < 0) + return r; + + break; + + case DNS_PROTOCOL_MDNS: + assert(fd < 0); + + if (!ratelimit_test(&s->ratelimit)) + return -EBUSY; + + family = s->family; + + if (family == AF_INET) { + addr.in = MDNS_MULTICAST_IPV4_ADDRESS; + fd = manager_mdns_ipv4_fd(s->manager); + } else if (family == AF_INET6) { + addr.in6 = MDNS_MULTICAST_IPV6_ADDRESS; + fd = manager_mdns_ipv6_fd(s->manager); + } else + return -EAFNOSUPPORT; + if (fd < 0) + return fd; + + r = manager_send(s->manager, fd, ifindex, family, &addr, MDNS_PORT, p); + if (r < 0) + return r; + + break; + + default: + return -EAFNOSUPPORT; + } + + return 1; +} + +int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p) { + int r; + + assert(s); + assert(p); + assert(p->protocol == s->protocol); + assert((s->protocol == DNS_PROTOCOL_DNS) == (fd >= 0)); + + do { + /* If there are multiple linked packets, set the TC bit in all but the last of them */ + if (p->more) { + assert(p->protocol == DNS_PROTOCOL_MDNS); + dns_packet_set_flags(p, true, true); + } + + r = dns_scope_emit_one(s, fd, p); + if (r < 0) + return r; + + p = p->more; + } while (p); + + return 0; +} + +static int dns_scope_socket( + DnsScope *s, + int type, + int family, + const union in_addr_union *address, + DnsServer *server, + uint16_t port) { + + _cleanup_close_ int fd = -1; + union sockaddr_union sa = {}; + socklen_t salen; + static const int one = 1; + int ret, r; + + assert(s); + + if (server) { + assert(family == AF_UNSPEC); + assert(!address); + + sa.sa.sa_family = server->family; + if (server->family == AF_INET) { + sa.in.sin_port = htobe16(port); + sa.in.sin_addr = server->address.in; + salen = sizeof(sa.in); + } else if (server->family == AF_INET6) { + sa.in6.sin6_port = htobe16(port); + sa.in6.sin6_addr = server->address.in6; + sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0; + salen = sizeof(sa.in6); + } else + return -EAFNOSUPPORT; + } else { + assert(family != AF_UNSPEC); + assert(address); + + sa.sa.sa_family = family; + + if (family == AF_INET) { + sa.in.sin_port = htobe16(port); + sa.in.sin_addr = address->in; + salen = sizeof(sa.in); + } else if (family == AF_INET6) { + sa.in6.sin6_port = htobe16(port); + sa.in6.sin6_addr = address->in6; + sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0; + salen = sizeof(sa.in6); + } else + return -EAFNOSUPPORT; + } + + fd = socket(sa.sa.sa_family, type|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) + return -errno; + + if (type == SOCK_STREAM) { + r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); + if (r < 0) + return -errno; + } + + if (s->link) { + uint32_t ifindex = htobe32(s->link->ifindex); + + if (sa.sa.sa_family == AF_INET) { + r = setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex)); + if (r < 0) + return -errno; + } else if (sa.sa.sa_family == AF_INET6) { + r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex)); + if (r < 0) + return -errno; + } + } + + if (s->protocol == DNS_PROTOCOL_LLMNR) { + /* RFC 4795, section 2.5 requires the TTL to be set to 1 */ + + if (sa.sa.sa_family == AF_INET) { + r = setsockopt(fd, IPPROTO_IP, IP_TTL, &one, sizeof(one)); + if (r < 0) + return -errno; + } else if (sa.sa.sa_family == AF_INET6) { + r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one)); + if (r < 0) + return -errno; + } + } + + r = connect(fd, &sa.sa, salen); + if (r < 0 && errno != EINPROGRESS) + return -errno; + + ret = fd; + fd = -1; + + return ret; +} + +int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port) { + return dns_scope_socket(s, SOCK_DGRAM, AF_UNSPEC, NULL, server, port); +} + +int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port) { + return dns_scope_socket(s, SOCK_STREAM, family, address, server, port); +} + +DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain) { + DnsSearchDomain *d; + + assert(s); + assert(domain); + + /* Checks if the specified domain is something to look up on + * this scope. Note that this accepts non-qualified hostnames, + * i.e. those without any search path prefixed yet. */ + + if (ifindex != 0 && (!s->link || s->link->ifindex != ifindex)) + return DNS_SCOPE_NO; + + if ((SD_RESOLVED_FLAGS_MAKE(s->protocol, s->family, 0) & flags) == 0) + return DNS_SCOPE_NO; + + /* Never resolve any loopback hostname or IP address via DNS, + * LLMNR or mDNS. Instead, always rely on synthesized RRs for + * these. */ + if (is_localhost(domain) || + dns_name_endswith(domain, "127.in-addr.arpa") > 0 || + dns_name_equal(domain, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) + return DNS_SCOPE_NO; + + /* Never respond to some of the domains listed in RFC6303 */ + if (dns_name_endswith(domain, "0.in-addr.arpa") > 0 || + dns_name_equal(domain, "255.255.255.255.in-addr.arpa") > 0 || + dns_name_equal(domain, "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) + return DNS_SCOPE_NO; + + /* Never respond to some of the domains listed in RFC6761 */ + if (dns_name_endswith(domain, "invalid") > 0) + return DNS_SCOPE_NO; + + /* Always honour search domains for routing queries. Note that + * we return DNS_SCOPE_YES here, rather than just + * DNS_SCOPE_MAYBE, which means wildcard scopes won't be + * considered anymore. */ + LIST_FOREACH(domains, d, dns_scope_get_search_domains(s)) + if (dns_name_endswith(domain, d->name) > 0) + return DNS_SCOPE_YES; + + switch (s->protocol) { + + case DNS_PROTOCOL_DNS: + + /* Exclude link-local IP ranges */ + if (dns_name_endswith(domain, "254.169.in-addr.arpa") == 0 && + dns_name_endswith(domain, "8.e.f.ip6.arpa") == 0 && + dns_name_endswith(domain, "9.e.f.ip6.arpa") == 0 && + dns_name_endswith(domain, "a.e.f.ip6.arpa") == 0 && + dns_name_endswith(domain, "b.e.f.ip6.arpa") == 0 && + /* If networks use .local in their private setups, they are supposed to also add .local to their search + * domains, which we already checked above. Otherwise, we consider .local specific to mDNS and won't + * send such queries ordinary DNS servers. */ + dns_name_endswith(domain, "local") == 0) + return DNS_SCOPE_MAYBE; + + return DNS_SCOPE_NO; + + case DNS_PROTOCOL_MDNS: + if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || + (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) || + (dns_name_endswith(domain, "local") > 0 && /* only resolve names ending in .local via mDNS */ + dns_name_equal(domain, "local") == 0 && /* but not the single-label "local" name itself */ + manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via mDNS */ + return DNS_SCOPE_MAYBE; + + return DNS_SCOPE_NO; + + case DNS_PROTOCOL_LLMNR: + if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || + (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) || + (dns_name_is_single_label(domain) && /* only resolve single label names via LLMNR */ + !is_gateway_hostname(domain) && /* don't resolve "gateway" with LLMNR, let nss-myhostname handle this */ + manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via LLMNR */ + return DNS_SCOPE_MAYBE; + + return DNS_SCOPE_NO; + + default: + assert_not_reached("Unknown scope protocol"); + } +} + +bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key) { + int key_family; + + assert(s); + assert(key); + + /* Check if it makes sense to resolve the specified key on + * this scope. Note that this call assumes as fully qualified + * name, i.e. the search suffixes already appended. */ + + if (key->class != DNS_CLASS_IN) + return false; + + if (s->protocol == DNS_PROTOCOL_DNS) { + + /* On classic DNS, looking up non-address RRs is always + * fine. (Specifically, we want to permit looking up + * DNSKEY and DS records on the root and top-level + * domains.) */ + if (!dns_resource_key_is_address(key)) + return true; + + /* However, we refuse to look up A and AAAA RRs on the + * root and single-label domains, under the assumption + * that those should be resolved via LLMNR or search + * path only, and should not be leaked onto the + * internet. */ + return !(dns_name_is_single_label(dns_resource_key_name(key)) || + dns_name_is_root(dns_resource_key_name(key))); + } + + /* On mDNS and LLMNR, send A and AAAA queries only on the + * respective scopes */ + + key_family = dns_type_to_af(key->type); + if (key_family < 0) + return true; + + return key_family == s->family; +} + +static int dns_scope_multicast_membership(DnsScope *s, bool b, struct in_addr in, struct in6_addr in6) { + int fd; + + assert(s); + assert(s->link); + + if (s->family == AF_INET) { + struct ip_mreqn mreqn = { + .imr_multiaddr = in, + .imr_ifindex = s->link->ifindex, + }; + + fd = manager_llmnr_ipv4_udp_fd(s->manager); + if (fd < 0) + return fd; + + /* Always first try to drop membership before we add + * one. This is necessary on some devices, such as + * veth. */ + if (b) + (void) setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn)); + + if (setsockopt(fd, IPPROTO_IP, b ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn)) < 0) + return -errno; + + } else if (s->family == AF_INET6) { + struct ipv6_mreq mreq = { + .ipv6mr_multiaddr = in6, + .ipv6mr_interface = s->link->ifindex, + }; + + fd = manager_llmnr_ipv6_udp_fd(s->manager); + if (fd < 0) + return fd; + + if (b) + (void) setsockopt(fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); + + if (setsockopt(fd, IPPROTO_IPV6, b ? IPV6_ADD_MEMBERSHIP : IPV6_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) + return -errno; + } else + return -EAFNOSUPPORT; + + return 0; +} + +int dns_scope_llmnr_membership(DnsScope *s, bool b) { + + if (s->protocol != DNS_PROTOCOL_LLMNR) + return 0; + + return dns_scope_multicast_membership(s, b, LLMNR_MULTICAST_IPV4_ADDRESS, LLMNR_MULTICAST_IPV6_ADDRESS); +} + +int dns_scope_mdns_membership(DnsScope *s, bool b) { + + if (s->protocol != DNS_PROTOCOL_MDNS) + return 0; + + return dns_scope_multicast_membership(s, b, MDNS_MULTICAST_IPV4_ADDRESS, MDNS_MULTICAST_IPV6_ADDRESS); +} + +static int dns_scope_make_reply_packet( + DnsScope *s, + uint16_t id, + int rcode, + DnsQuestion *q, + DnsAnswer *answer, + DnsAnswer *soa, + bool tentative, + DnsPacket **ret) { + + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + unsigned i; + int r; + + assert(s); + assert(ret); + + if ((!q || q->n_keys <= 0) + && (!answer || answer->n_rrs <= 0) + && (!soa || soa->n_rrs <= 0)) + return -EINVAL; + + r = dns_packet_new(&p, s->protocol, 0); + if (r < 0) + return r; + + DNS_PACKET_HEADER(p)->id = id; + DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( + 1 /* qr */, + 0 /* opcode */, + 0 /* c */, + 0 /* tc */, + tentative, + 0 /* (ra) */, + 0 /* (ad) */, + 0 /* (cd) */, + rcode)); + + if (q) { + for (i = 0; i < q->n_keys; i++) { + r = dns_packet_append_key(p, q->keys[i], NULL); + if (r < 0) + return r; + } + + DNS_PACKET_HEADER(p)->qdcount = htobe16(q->n_keys); + } + + if (answer) { + for (i = 0; i < answer->n_rrs; i++) { + r = dns_packet_append_rr(p, answer->items[i].rr, NULL, NULL); + if (r < 0) + return r; + } + + DNS_PACKET_HEADER(p)->ancount = htobe16(answer->n_rrs); + } + + if (soa) { + for (i = 0; i < soa->n_rrs; i++) { + r = dns_packet_append_rr(p, soa->items[i].rr, NULL, NULL); + if (r < 0) + return r; + } + + DNS_PACKET_HEADER(p)->arcount = htobe16(soa->n_rrs); + } + + *ret = p; + p = NULL; + + return 0; +} + +static void dns_scope_verify_conflicts(DnsScope *s, DnsPacket *p) { + unsigned n; + + assert(s); + assert(p); + + if (p->question) + for (n = 0; n < p->question->n_keys; n++) + dns_zone_verify_conflicts(&s->zone, p->question->keys[n]); + if (p->answer) + for (n = 0; n < p->answer->n_rrs; n++) + dns_zone_verify_conflicts(&s->zone, p->answer->items[n].rr->key); +} + +void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) { + _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL; + DnsResourceKey *key = NULL; + bool tentative = false; + int r, fd; + + assert(s); + assert(p); + + if (p->protocol != DNS_PROTOCOL_LLMNR) + return; + + if (p->ipproto == IPPROTO_UDP) { + /* Don't accept UDP queries directed to anything but + * the LLMNR multicast addresses. See RFC 4795, + * section 2.5. */ + + if (p->family == AF_INET && !in_addr_equal(AF_INET, &p->destination, (union in_addr_union*) &LLMNR_MULTICAST_IPV4_ADDRESS)) + return; + + if (p->family == AF_INET6 && !in_addr_equal(AF_INET6, &p->destination, (union in_addr_union*) &LLMNR_MULTICAST_IPV6_ADDRESS)) + return; + } + + r = dns_packet_extract(p); + if (r < 0) { + log_debug_errno(r, "Failed to extract resources from incoming packet: %m"); + return; + } + + if (DNS_PACKET_LLMNR_C(p)) { + /* Somebody notified us about a possible conflict */ + dns_scope_verify_conflicts(s, p); + return; + } + + assert(p->question->n_keys == 1); + key = p->question->keys[0]; + + r = dns_zone_lookup(&s->zone, key, &answer, &soa, &tentative); + if (r < 0) { + log_debug_errno(r, "Failed to lookup key: %m"); + return; + } + if (r == 0) + return; + + if (answer) + dns_answer_order_by_scope(answer, in_addr_is_link_local(p->family, &p->sender) > 0); + + r = dns_scope_make_reply_packet(s, DNS_PACKET_ID(p), DNS_RCODE_SUCCESS, p->question, answer, soa, tentative, &reply); + if (r < 0) { + log_debug_errno(r, "Failed to build reply packet: %m"); + return; + } + + if (stream) + r = dns_stream_write_packet(stream, reply); + else { + if (!ratelimit_test(&s->ratelimit)) + return; + + if (p->family == AF_INET) + fd = manager_llmnr_ipv4_udp_fd(s->manager); + else if (p->family == AF_INET6) + fd = manager_llmnr_ipv6_udp_fd(s->manager); + else { + log_debug("Unknown protocol"); + return; + } + if (fd < 0) { + log_debug_errno(fd, "Failed to get reply socket: %m"); + return; + } + + /* Note that we always immediately reply to all LLMNR + * requests, and do not wait any time, since we + * verified uniqueness for all records. Also see RFC + * 4795, Section 2.7 */ + + r = manager_send(s->manager, fd, p->ifindex, p->family, &p->sender, p->sender_port, reply); + } + + if (r < 0) { + log_debug_errno(r, "Failed to send reply packet: %m"); + return; + } +} + +DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok) { + DnsTransaction *t; + + assert(scope); + assert(key); + + /* Try to find an ongoing transaction that is a equal to the + * specified question */ + t = hashmap_get(scope->transactions_by_key, key); + if (!t) + return NULL; + + /* Refuse reusing transactions that completed based on cached + * data instead of a real packet, if that's requested. */ + if (!cache_ok && + IN_SET(t->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_RCODE_FAILURE) && + t->answer_source != DNS_TRANSACTION_NETWORK) + return NULL; + + return t; +} + +static int dns_scope_make_conflict_packet( + DnsScope *s, + DnsResourceRecord *rr, + DnsPacket **ret) { + + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + int r; + + assert(s); + assert(rr); + assert(ret); + + r = dns_packet_new(&p, s->protocol, 0); + if (r < 0) + return r; + + DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( + 0 /* qr */, + 0 /* opcode */, + 1 /* conflict */, + 0 /* tc */, + 0 /* t */, + 0 /* (ra) */, + 0 /* (ad) */, + 0 /* (cd) */, + 0)); + + /* For mDNS, the transaction ID should always be 0 */ + if (s->protocol != DNS_PROTOCOL_MDNS) + random_bytes(&DNS_PACKET_HEADER(p)->id, sizeof(uint16_t)); + + DNS_PACKET_HEADER(p)->qdcount = htobe16(1); + DNS_PACKET_HEADER(p)->arcount = htobe16(1); + + r = dns_packet_append_key(p, rr->key, NULL); + if (r < 0) + return r; + + r = dns_packet_append_rr(p, rr, NULL, NULL); + if (r < 0) + return r; + + *ret = p; + p = NULL; + + return 0; +} + +static int on_conflict_dispatch(sd_event_source *es, usec_t usec, void *userdata) { + DnsScope *scope = userdata; + int r; + + assert(es); + assert(scope); + + scope->conflict_event_source = sd_event_source_unref(scope->conflict_event_source); + + for (;;) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + + rr = ordered_hashmap_steal_first(scope->conflict_queue); + if (!rr) + break; + + r = dns_scope_make_conflict_packet(scope, rr, &p); + if (r < 0) { + log_error_errno(r, "Failed to make conflict packet: %m"); + return 0; + } + + r = dns_scope_emit_udp(scope, -1, p); + if (r < 0) + log_debug_errno(r, "Failed to send conflict packet: %m"); + } + + return 0; +} + +int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr) { + usec_t jitter; + int r; + + assert(scope); + assert(rr); + + /* We don't send these queries immediately. Instead, we queue + * them, and send them after some jitter delay. */ + r = ordered_hashmap_ensure_allocated(&scope->conflict_queue, &dns_resource_key_hash_ops); + if (r < 0) { + log_oom(); + return r; + } + + /* We only place one RR per key in the conflict + * messages, not all of them. That should be enough to + * indicate where there might be a conflict */ + r = ordered_hashmap_put(scope->conflict_queue, rr->key, rr); + if (r == -EEXIST || r == 0) + return 0; + if (r < 0) + return log_debug_errno(r, "Failed to queue conflicting RR: %m"); + + dns_resource_record_ref(rr); + + if (scope->conflict_event_source) + return 0; + + random_bytes(&jitter, sizeof(jitter)); + jitter %= LLMNR_JITTER_INTERVAL_USEC; + + r = sd_event_add_time(scope->manager->event, + &scope->conflict_event_source, + clock_boottime_or_monotonic(), + now(clock_boottime_or_monotonic()) + jitter, + LLMNR_JITTER_INTERVAL_USEC, + on_conflict_dispatch, scope); + if (r < 0) + return log_debug_errno(r, "Failed to add conflict dispatch event: %m"); + + (void) sd_event_source_set_description(scope->conflict_event_source, "scope-conflict"); + + return 0; +} + +void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p) { + unsigned i; + int r; + + assert(scope); + assert(p); + + if (p->protocol != DNS_PROTOCOL_LLMNR) + return; + + if (DNS_PACKET_RRCOUNT(p) <= 0) + return; + + if (DNS_PACKET_LLMNR_C(p) != 0) + return; + + if (DNS_PACKET_LLMNR_T(p) != 0) + return; + + if (manager_our_packet(scope->manager, p)) + return; + + r = dns_packet_extract(p); + if (r < 0) { + log_debug_errno(r, "Failed to extract packet: %m"); + return; + } + + log_debug("Checking for conflicts..."); + + for (i = 0; i < p->answer->n_rrs; i++) { + + /* Check for conflicts against the local zone. If we + * found one, we won't check any further */ + r = dns_zone_check_conflicts(&scope->zone, p->answer->items[i].rr); + if (r != 0) + continue; + + /* Check for conflicts against the local cache. If so, + * send out an advisory query, to inform everybody */ + r = dns_cache_check_conflicts(&scope->cache, p->answer->items[i].rr, p->family, &p->sender); + if (r <= 0) + continue; + + dns_scope_notify_conflict(scope, p->answer->items[i].rr); + } +} + +void dns_scope_dump(DnsScope *s, FILE *f) { + assert(s); + + if (!f) + f = stdout; + + fputs("[Scope protocol=", f); + fputs(dns_protocol_to_string(s->protocol), f); + + if (s->link) { + fputs(" interface=", f); + fputs(s->link->name, f); + } + + if (s->family != AF_UNSPEC) { + fputs(" family=", f); + fputs(af_to_name(s->family), f); + } + + fputs("]\n", f); + + if (!dns_zone_is_empty(&s->zone)) { + fputs("ZONE:\n", f); + dns_zone_dump(&s->zone, f); + } + + if (!dns_cache_is_empty(&s->cache)) { + fputs("CACHE:\n", f); + dns_cache_dump(&s->cache, f); + } +} + +DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s) { + assert(s); + + if (s->protocol != DNS_PROTOCOL_DNS) + return NULL; + + if (s->link) + return s->link->search_domains; + + return s->manager->search_domains; +} + +bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name) { + assert(s); + + if (s->protocol != DNS_PROTOCOL_DNS) + return false; + + return dns_name_is_single_label(name); +} + +bool dns_scope_network_good(DnsScope *s) { + /* Checks whether the network is in good state for lookups on this scope. For mDNS/LLMNR/Classic DNS scopes + * bound to links this is easy, as they don't even exist if the link isn't in a suitable state. For the global + * DNS scope we check whether there are any links that are up and have an address. */ + + if (s->link) + return true; + + return manager_routable(s->manager, AF_UNSPEC); +} diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-scope.h b/src/grp-resolve/systemd-resolved/resolved-dns-scope.h new file mode 100644 index 0000000000..291e5817d0 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-scope.h @@ -0,0 +1,109 @@ +#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 "list.h" + +typedef struct DnsScope DnsScope; + +#include "resolved-dns-cache.h" +#include "resolved-dns-dnssec.h" +#include "resolved-dns-packet.h" +#include "resolved-dns-server.h" +#include "resolved-dns-zone.h" +#include "resolved-link.h" + +typedef enum DnsScopeMatch { + DNS_SCOPE_NO, + DNS_SCOPE_MAYBE, + DNS_SCOPE_YES, + _DNS_SCOPE_MATCH_MAX, + _DNS_SCOPE_INVALID = -1 +} DnsScopeMatch; + +struct DnsScope { + Manager *manager; + + DnsProtocol protocol; + int family; + DnssecMode dnssec_mode; + + Link *link; + + DnsCache cache; + DnsZone zone; + + OrderedHashmap *conflict_queue; + sd_event_source *conflict_event_source; + + RateLimit ratelimit; + + usec_t resend_timeout; + usec_t max_rtt; + + LIST_HEAD(DnsQueryCandidate, query_candidates); + + /* Note that we keep track of ongoing transactions in two + * ways: once in a hashmap, indexed by the rr key, and once in + * a linked list. We use the hashmap to quickly find + * transactions we can reuse for a key. But note that there + * might be multiple transactions for the same key (because + * the zone probing can't reuse a transaction answered from + * the zone or the cache), and the hashmap only tracks the + * most recent entry. */ + Hashmap *transactions_by_key; + LIST_HEAD(DnsTransaction, transactions); + + LIST_FIELDS(DnsScope, scopes); +}; + +int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol p, int family); +DnsScope* dns_scope_free(DnsScope *s); + +void dns_scope_packet_received(DnsScope *s, usec_t rtt); +void dns_scope_packet_lost(DnsScope *s, usec_t usec); + +int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p); +int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port); +int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port); + +DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain); +bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key); + +DnsServer *dns_scope_get_dns_server(DnsScope *s); +void dns_scope_next_dns_server(DnsScope *s); + +int dns_scope_llmnr_membership(DnsScope *s, bool b); +int dns_scope_mdns_membership(DnsScope *s, bool b); + +void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p); + +DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok); + +int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr); +void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p); + +void dns_scope_dump(DnsScope *s, FILE *f); + +DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s); + +bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name); + +bool dns_scope_network_good(DnsScope *s); diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-search-domain.c b/src/grp-resolve/systemd-resolved/resolved-dns-search-domain.c new file mode 100644 index 0000000000..732471027b --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-search-domain.c @@ -0,0 +1,227 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 "alloc-util.h" +#include "dns-domain.h" +#include "resolved-dns-search-domain.h" + +int dns_search_domain_new( + Manager *m, + DnsSearchDomain **ret, + DnsSearchDomainType type, + Link *l, + const char *name) { + + _cleanup_free_ char *normalized = NULL; + DnsSearchDomain *d; + int r; + + assert(m); + assert((type == DNS_SEARCH_DOMAIN_LINK) == !!l); + assert(name); + + r = dns_name_normalize(name, &normalized); + if (r < 0) + return r; + + if (l) { + if (l->n_search_domains >= LINK_SEARCH_DOMAINS_MAX) + return -E2BIG; + } else { + if (m->n_search_domains >= MANAGER_SEARCH_DOMAINS_MAX) + return -E2BIG; + } + + d = new0(DnsSearchDomain, 1); + if (!d) + return -ENOMEM; + + d->n_ref = 1; + d->manager = m; + d->type = type; + d->name = normalized; + normalized = NULL; + + switch (type) { + + case DNS_SEARCH_DOMAIN_LINK: + d->link = l; + LIST_APPEND(domains, l->search_domains, d); + l->n_search_domains++; + break; + + case DNS_SERVER_SYSTEM: + LIST_APPEND(domains, m->search_domains, d); + m->n_search_domains++; + break; + + default: + assert_not_reached("Unknown search domain type"); + } + + d->linked = true; + + if (ret) + *ret = d; + + return 0; +} + +DnsSearchDomain* dns_search_domain_ref(DnsSearchDomain *d) { + if (!d) + return NULL; + + assert(d->n_ref > 0); + d->n_ref++; + + return d; +} + +DnsSearchDomain* dns_search_domain_unref(DnsSearchDomain *d) { + if (!d) + return NULL; + + assert(d->n_ref > 0); + d->n_ref--; + + if (d->n_ref > 0) + return NULL; + + free(d->name); + free(d); + + return NULL; +} + +void dns_search_domain_unlink(DnsSearchDomain *d) { + assert(d); + assert(d->manager); + + if (!d->linked) + return; + + switch (d->type) { + + case DNS_SEARCH_DOMAIN_LINK: + assert(d->link); + assert(d->link->n_search_domains > 0); + LIST_REMOVE(domains, d->link->search_domains, d); + d->link->n_search_domains--; + break; + + case DNS_SEARCH_DOMAIN_SYSTEM: + assert(d->manager->n_search_domains > 0); + LIST_REMOVE(domains, d->manager->search_domains, d); + d->manager->n_search_domains--; + break; + } + + d->linked = false; + + dns_search_domain_unref(d); +} + +void dns_search_domain_move_back_and_unmark(DnsSearchDomain *d) { + DnsSearchDomain *tail; + + assert(d); + + if (!d->marked) + return; + + d->marked = false; + + if (!d->linked || !d->domains_next) + return; + + switch (d->type) { + + case DNS_SEARCH_DOMAIN_LINK: + assert(d->link); + LIST_FIND_TAIL(domains, d, tail); + LIST_REMOVE(domains, d->link->search_domains, d); + LIST_INSERT_AFTER(domains, d->link->search_domains, tail, d); + break; + + case DNS_SEARCH_DOMAIN_SYSTEM: + LIST_FIND_TAIL(domains, d, tail); + LIST_REMOVE(domains, d->manager->search_domains, d); + LIST_INSERT_AFTER(domains, d->manager->search_domains, tail, d); + break; + + default: + assert_not_reached("Unknown search domain type"); + } +} + +void dns_search_domain_unlink_all(DnsSearchDomain *first) { + DnsSearchDomain *next; + + if (!first) + return; + + next = first->domains_next; + dns_search_domain_unlink(first); + + dns_search_domain_unlink_all(next); +} + +void dns_search_domain_unlink_marked(DnsSearchDomain *first) { + DnsSearchDomain *next; + + if (!first) + return; + + next = first->domains_next; + + if (first->marked) + dns_search_domain_unlink(first); + + dns_search_domain_unlink_marked(next); +} + +void dns_search_domain_mark_all(DnsSearchDomain *first) { + if (!first) + return; + + first->marked = true; + dns_search_domain_mark_all(first->domains_next); +} + +int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDomain **ret) { + DnsSearchDomain *d; + int r; + + assert(name); + assert(ret); + + LIST_FOREACH(domains, d, first) { + + r = dns_name_equal(name, d->name); + if (r < 0) + return r; + if (r > 0) { + *ret = d; + return 1; + } + } + + *ret = NULL; + return 0; +} diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-search-domain.h b/src/grp-resolve/systemd-resolved/resolved-dns-search-domain.h new file mode 100644 index 0000000000..eaacef4edc --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-search-domain.h @@ -0,0 +1,74 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 "macro.h" + +typedef struct DnsSearchDomain DnsSearchDomain; + +typedef enum DnsSearchDomainType { + DNS_SEARCH_DOMAIN_SYSTEM, + DNS_SEARCH_DOMAIN_LINK, +} DnsSearchDomainType; + +#include "resolved-link.h" +#include "resolved-manager.h" + +struct DnsSearchDomain { + Manager *manager; + + unsigned n_ref; + + DnsSearchDomainType type; + Link *link; + + char *name; + + bool marked:1; + bool route_only:1; + + bool linked:1; + LIST_FIELDS(DnsSearchDomain, domains); +}; + +int dns_search_domain_new( + Manager *m, + DnsSearchDomain **ret, + DnsSearchDomainType type, + Link *link, + const char *name); + +DnsSearchDomain* dns_search_domain_ref(DnsSearchDomain *d); +DnsSearchDomain* dns_search_domain_unref(DnsSearchDomain *d); + +void dns_search_domain_unlink(DnsSearchDomain *d); +void dns_search_domain_move_back_and_unmark(DnsSearchDomain *d); + +void dns_search_domain_unlink_all(DnsSearchDomain *first); +void dns_search_domain_unlink_marked(DnsSearchDomain *first); +void dns_search_domain_mark_all(DnsSearchDomain *first); + +int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDomain **ret); + +static inline const char* DNS_SEARCH_DOMAIN_NAME(DnsSearchDomain *d) { + return d ? d->name : NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsSearchDomain*, dns_search_domain_unref); diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-server.c b/src/grp-resolve/systemd-resolved/resolved-dns-server.c new file mode 100644 index 0000000000..3095c042db --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-server.c @@ -0,0 +1,741 @@ +/*** + 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 "alloc-util.h" +#include "resolved-dns-server.h" +#include "resolved-resolv-conf.h" +#include "siphash24.h" +#include "string-table.h" +#include "string-util.h" + +/* After how much time to repeat classic DNS requests */ +#define DNS_TIMEOUT_MIN_USEC (500 * USEC_PER_MSEC) +#define DNS_TIMEOUT_MAX_USEC (5 * USEC_PER_SEC) + +/* The amount of time to wait before retrying with a full feature set */ +#define DNS_SERVER_FEATURE_GRACE_PERIOD_MAX_USEC (6 * USEC_PER_HOUR) +#define DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC (5 * USEC_PER_MINUTE) + +/* The number of times we will attempt a certain feature set before degrading */ +#define DNS_SERVER_FEATURE_RETRY_ATTEMPTS 3 + +int dns_server_new( + Manager *m, + DnsServer **ret, + DnsServerType type, + Link *l, + int family, + const union in_addr_union *in_addr) { + + DnsServer *s; + + assert(m); + assert((type == DNS_SERVER_LINK) == !!l); + assert(in_addr); + + if (!IN_SET(family, AF_INET, AF_INET6)) + return -EAFNOSUPPORT; + + if (l) { + if (l->n_dns_servers >= LINK_DNS_SERVERS_MAX) + return -E2BIG; + } else { + if (m->n_dns_servers >= MANAGER_DNS_SERVERS_MAX) + return -E2BIG; + } + + s = new0(DnsServer, 1); + if (!s) + return -ENOMEM; + + s->n_ref = 1; + s->manager = m; + s->verified_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID; + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST; + s->features_grace_period_usec = DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC; + s->received_udp_packet_max = DNS_PACKET_UNICAST_SIZE_MAX; + s->type = type; + s->family = family; + s->address = *in_addr; + s->resend_timeout = DNS_TIMEOUT_MIN_USEC; + + switch (type) { + + case DNS_SERVER_LINK: + s->link = l; + LIST_APPEND(servers, l->dns_servers, s); + l->n_dns_servers++; + break; + + case DNS_SERVER_SYSTEM: + LIST_APPEND(servers, m->dns_servers, s); + m->n_dns_servers++; + break; + + case DNS_SERVER_FALLBACK: + LIST_APPEND(servers, m->fallback_dns_servers, s); + m->n_dns_servers++; + break; + + default: + assert_not_reached("Unknown server type"); + } + + s->linked = true; + + /* A new DNS server that isn't fallback is added and the one + * we used so far was a fallback one? Then let's try to pick + * the new one */ + if (type != DNS_SERVER_FALLBACK && + m->current_dns_server && + m->current_dns_server->type == DNS_SERVER_FALLBACK) + manager_set_dns_server(m, NULL); + + if (ret) + *ret = s; + + return 0; +} + +DnsServer* dns_server_ref(DnsServer *s) { + if (!s) + return NULL; + + assert(s->n_ref > 0); + s->n_ref++; + + return s; +} + +DnsServer* dns_server_unref(DnsServer *s) { + if (!s) + return NULL; + + assert(s->n_ref > 0); + s->n_ref--; + + if (s->n_ref > 0) + return NULL; + + free(s->server_string); + free(s); + return NULL; +} + +void dns_server_unlink(DnsServer *s) { + assert(s); + assert(s->manager); + + /* This removes the specified server from the linked list of + * servers, but any server might still stay around if it has + * refs, for example from an ongoing transaction. */ + + if (!s->linked) + return; + + switch (s->type) { + + case DNS_SERVER_LINK: + assert(s->link); + assert(s->link->n_dns_servers > 0); + LIST_REMOVE(servers, s->link->dns_servers, s); + s->link->n_dns_servers--; + break; + + case DNS_SERVER_SYSTEM: + assert(s->manager->n_dns_servers > 0); + LIST_REMOVE(servers, s->manager->dns_servers, s); + s->manager->n_dns_servers--; + break; + + case DNS_SERVER_FALLBACK: + assert(s->manager->n_dns_servers > 0); + LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); + s->manager->n_dns_servers--; + break; + } + + s->linked = false; + + if (s->link && s->link->current_dns_server == s) + link_set_dns_server(s->link, NULL); + + if (s->manager->current_dns_server == s) + manager_set_dns_server(s->manager, NULL); + + dns_server_unref(s); +} + +void dns_server_move_back_and_unmark(DnsServer *s) { + DnsServer *tail; + + assert(s); + + if (!s->marked) + return; + + s->marked = false; + + if (!s->linked || !s->servers_next) + return; + + /* Move us to the end of the list, so that the order is + * strictly kept, if we are not at the end anyway. */ + + switch (s->type) { + + case DNS_SERVER_LINK: + assert(s->link); + LIST_FIND_TAIL(servers, s, tail); + LIST_REMOVE(servers, s->link->dns_servers, s); + LIST_INSERT_AFTER(servers, s->link->dns_servers, tail, s); + break; + + case DNS_SERVER_SYSTEM: + LIST_FIND_TAIL(servers, s, tail); + LIST_REMOVE(servers, s->manager->dns_servers, s); + LIST_INSERT_AFTER(servers, s->manager->dns_servers, tail, s); + break; + + case DNS_SERVER_FALLBACK: + LIST_FIND_TAIL(servers, s, tail); + LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); + LIST_INSERT_AFTER(servers, s->manager->fallback_dns_servers, tail, s); + break; + + default: + assert_not_reached("Unknown server type"); + } +} + +static void dns_server_verified(DnsServer *s, DnsServerFeatureLevel level) { + assert(s); + + if (s->verified_feature_level > level) + return; + + if (s->verified_feature_level != level) { + log_debug("Verified we get a response at feature level %s from DNS server %s.", + dns_server_feature_level_to_string(level), + dns_server_string(s)); + s->verified_feature_level = level; + } + + assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0); +} + +void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t rtt, size_t size) { + assert(s); + + if (protocol == IPPROTO_UDP) { + if (s->possible_feature_level == level) + s->n_failed_udp = 0; + + /* If the RRSIG data is missing, then we can only validate EDNS0 at max */ + if (s->packet_rrsig_missing && level >= DNS_SERVER_FEATURE_LEVEL_DO) + level = DNS_SERVER_FEATURE_LEVEL_DO - 1; + + /* If the OPT RR got lost, then we can only validate UDP at max */ + if (s->packet_bad_opt && level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) + level = DNS_SERVER_FEATURE_LEVEL_EDNS0 - 1; + + /* Even if we successfully receive a reply to a request announcing support for large packets, + that does not mean we can necessarily receive large packets. */ + if (level == DNS_SERVER_FEATURE_LEVEL_LARGE) + level = DNS_SERVER_FEATURE_LEVEL_LARGE - 1; + + } else if (protocol == IPPROTO_TCP) { + + if (s->possible_feature_level == level) + s->n_failed_tcp = 0; + + /* Successful TCP connections are only useful to verify the TCP feature level. */ + level = DNS_SERVER_FEATURE_LEVEL_TCP; + } + + dns_server_verified(s, level); + + /* Remember the size of the largest UDP packet we received from a server, + we know that we can always announce support for packets with at least + this size. */ + if (protocol == IPPROTO_UDP && s->received_udp_packet_max < size) + s->received_udp_packet_max = size; + + if (s->max_rtt < rtt) { + s->max_rtt = rtt; + s->resend_timeout = CLAMP(s->max_rtt * 2, DNS_TIMEOUT_MIN_USEC, DNS_TIMEOUT_MAX_USEC); + } +} + +void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t usec) { + assert(s); + assert(s->manager); + + if (s->possible_feature_level == level) { + if (protocol == IPPROTO_UDP) + s->n_failed_udp++; + else if (protocol == IPPROTO_TCP) + s->n_failed_tcp++; + } + + if (s->resend_timeout > usec) + return; + + s->resend_timeout = MIN(s->resend_timeout * 2, DNS_TIMEOUT_MAX_USEC); +} + +void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level) { + assert(s); + + /* Invoked whenever we get a FORMERR, SERVFAIL or NOTIMP rcode from a server. */ + + if (s->possible_feature_level != level) + return; + + s->packet_failed = true; +} + +void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level) { + assert(s); + + /* Invoked whenever we get a packet with TC bit set. */ + + if (s->possible_feature_level != level) + return; + + s->packet_truncated = true; +} + +void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level) { + assert(s); + + if (level < DNS_SERVER_FEATURE_LEVEL_DO) + return; + + /* If the RRSIG RRs are missing, we have to downgrade what we previously verified */ + if (s->verified_feature_level >= DNS_SERVER_FEATURE_LEVEL_DO) + s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_DO-1; + + s->packet_rrsig_missing = true; +} + +void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level) { + assert(s); + + if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0) + return; + + /* If the OPT RR got lost, we have to downgrade what we previously verified */ + if (s->verified_feature_level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) + s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_EDNS0-1; + + s->packet_bad_opt = true; +} + +static bool dns_server_grace_period_expired(DnsServer *s) { + usec_t ts; + + assert(s); + assert(s->manager); + + if (s->verified_usec == 0) + return false; + + assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); + + if (s->verified_usec + s->features_grace_period_usec > ts) + return false; + + s->features_grace_period_usec = MIN(s->features_grace_period_usec * 2, DNS_SERVER_FEATURE_GRACE_PERIOD_MAX_USEC); + + return true; +} + +static void dns_server_reset_counters(DnsServer *s) { + assert(s); + + s->n_failed_udp = 0; + s->n_failed_tcp = 0; + s->packet_failed = false; + s->packet_truncated = false; + s->verified_usec = 0; + + /* Note that we do not reset s->packet_bad_opt and s->packet_rrsig_missing here. We reset them only when the + * grace period ends, but not when lowering the possible feature level, as a lower level feature level should + * not make RRSIGs appear or OPT appear, but rather make them disappear. If the reappear anyway, then that's + * indication for a differently broken OPT/RRSIG implementation, and we really don't want to support that + * either. + * + * This is particularly important to deal with certain Belkin routers which break OPT for certain lookups (A), + * but pass traffic through for others (AAAA). If we detect the broken behaviour on one lookup we should not + * reenable it for another, because we cannot validate things anyway, given that the RRSIG/OPT data will be + * incomplete. */ +} + +DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) { + assert(s); + + if (s->possible_feature_level != DNS_SERVER_FEATURE_LEVEL_BEST && + dns_server_grace_period_expired(s)) { + + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST; + + dns_server_reset_counters(s); + + s->packet_bad_opt = false; + s->packet_rrsig_missing = false; + + log_info("Grace period over, resuming full feature set (%s) for DNS server %s.", + dns_server_feature_level_to_string(s->possible_feature_level), + dns_server_string(s)); + + } else if (s->possible_feature_level <= s->verified_feature_level) + s->possible_feature_level = s->verified_feature_level; + else { + DnsServerFeatureLevel p = s->possible_feature_level; + + if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && + s->possible_feature_level == DNS_SERVER_FEATURE_LEVEL_TCP) { + + /* We are at the TCP (lowest) level, and we tried a couple of TCP connections, and it didn't + * work. Upgrade back to UDP again. */ + log_debug("Reached maximum number of failed TCP connection attempts, trying UDP again..."); + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP; + + } else if (s->packet_bad_opt && + s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) { + + /* A reply to one of our EDNS0 queries didn't carry a valid OPT RR, then downgrade to below + * EDNS0 levels. After all, some records generate different responses with and without OPT RR + * in the request. Example: + * https://open.nlnetlabs.nl/pipermail/dnssec-trigger/2014-November/000376.html */ + + log_debug("Server doesn't support EDNS(0) properly, downgrading feature level..."); + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP; + + } else if (s->packet_rrsig_missing && + s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_DO) { + + /* RRSIG data was missing on a EDNS0 packet with DO bit set. This means the server doesn't + * augment responses with DNSSEC RRs. If so, let's better not ask the server for it anymore, + * after all some servers generate different replies depending if an OPT RR is in the query or + * not. */ + + log_debug("Detected server responses lack RRSIG records, downgrading feature level..."); + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_EDNS0; + + } else if (s->n_failed_udp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && + s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_UDP) { + + /* We lost too many UDP packets in a row, and are on a feature level of UDP or higher. If the + * packets are lost, maybe the server cannot parse them, hence downgrading sounds like a good + * idea. We might downgrade all the way down to TCP this way. */ + + log_debug("Lost too many UDP packets, downgrading feature level..."); + s->possible_feature_level--; + + } else if (s->packet_failed && + s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) { + + /* We got a failure packet, and are at a feature level above UDP. Note that in this case we + * downgrade no further than UDP, under the assumption that a failure packet indicates an + * incompatible packet contents, but not a problem with the transport. */ + + log_debug("Got server failure, downgrading feature level..."); + s->possible_feature_level--; + + } else if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && + s->packet_truncated && + s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) { + + /* We got too many TCP connection failures in a row, we had at least one truncated packet, and + * are on a feature level above UDP. By downgrading things and getting rid of DNSSEC or EDNS0 + * data we hope to make the packet smaller, so that it still works via UDP given that TCP + * appears not to be a fallback. Note that if we are already at the lowest UDP level, we don't + * go further down, since that's TCP, and TCP failed too often after all. */ + + log_debug("Got too many failed TCP connection failures and truncated UDP packets, downgrading feature level..."); + s->possible_feature_level--; + } + + if (p != s->possible_feature_level) { + + /* We changed the feature level, reset the counting */ + dns_server_reset_counters(s); + + log_warning("Using degraded feature set (%s) for DNS server %s.", + dns_server_feature_level_to_string(s->possible_feature_level), + dns_server_string(s)); + } + } + + return s->possible_feature_level; +} + +int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level) { + size_t packet_size; + bool edns_do; + int r; + + assert(server); + assert(packet); + assert(packet->protocol == DNS_PROTOCOL_DNS); + + /* Fix the OPT field in the packet to match our current feature level. */ + + r = dns_packet_truncate_opt(packet); + if (r < 0) + return r; + + if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0) + return 0; + + edns_do = level >= DNS_SERVER_FEATURE_LEVEL_DO; + + if (level >= DNS_SERVER_FEATURE_LEVEL_LARGE) + packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX; + else + packet_size = server->received_udp_packet_max; + + return dns_packet_append_opt(packet, packet_size, edns_do, NULL); +} + +const char *dns_server_string(DnsServer *server) { + assert(server); + + if (!server->server_string) + (void) in_addr_to_string(server->family, &server->address, &server->server_string); + + return strna(server->server_string); +} + +bool dns_server_dnssec_supported(DnsServer *server) { + assert(server); + + /* Returns whether the server supports DNSSEC according to what we know about it */ + + if (server->possible_feature_level < DNS_SERVER_FEATURE_LEVEL_DO) + return false; + + if (server->packet_bad_opt) + return false; + + if (server->packet_rrsig_missing) + return false; + + /* DNSSEC servers need to support TCP properly (see RFC5966), if they don't, we assume DNSSEC is borked too */ + if (server->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS) + return false; + + return true; +} + +void dns_server_warn_downgrade(DnsServer *server) { + assert(server); + + if (server->warned_downgrade) + return; + + log_struct(LOG_NOTICE, + LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_DOWNGRADE), + LOG_MESSAGE("Server %s does not support DNSSEC, downgrading to non-DNSSEC mode.", dns_server_string(server)), + "DNS_SERVER=%s", dns_server_string(server), + "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(server->possible_feature_level), + NULL); + + server->warned_downgrade = true; +} + +static void dns_server_hash_func(const void *p, struct siphash *state) { + const DnsServer *s = p; + + assert(s); + + siphash24_compress(&s->family, sizeof(s->family), state); + siphash24_compress(&s->address, FAMILY_ADDRESS_SIZE(s->family), state); +} + +static int dns_server_compare_func(const void *a, const void *b) { + const DnsServer *x = a, *y = b; + + if (x->family < y->family) + return -1; + if (x->family > y->family) + return 1; + + return memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family)); +} + +const struct hash_ops dns_server_hash_ops = { + .hash = dns_server_hash_func, + .compare = dns_server_compare_func +}; + +void dns_server_unlink_all(DnsServer *first) { + DnsServer *next; + + if (!first) + return; + + next = first->servers_next; + dns_server_unlink(first); + + dns_server_unlink_all(next); +} + +void dns_server_unlink_marked(DnsServer *first) { + DnsServer *next; + + if (!first) + return; + + next = first->servers_next; + + if (first->marked) + dns_server_unlink(first); + + dns_server_unlink_marked(next); +} + +void dns_server_mark_all(DnsServer *first) { + if (!first) + return; + + first->marked = true; + dns_server_mark_all(first->servers_next); +} + +DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr) { + DnsServer *s; + + LIST_FOREACH(servers, s, first) + if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0) + return s; + + return NULL; +} + +DnsServer *manager_get_first_dns_server(Manager *m, DnsServerType t) { + assert(m); + + switch (t) { + + case DNS_SERVER_SYSTEM: + return m->dns_servers; + + case DNS_SERVER_FALLBACK: + return m->fallback_dns_servers; + + default: + return NULL; + } +} + +DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) { + assert(m); + + if (m->current_dns_server == s) + return s; + + if (s) + log_info("Switching to %s DNS server %s.", + dns_server_type_to_string(s->type), + dns_server_string(s)); + + dns_server_unref(m->current_dns_server); + m->current_dns_server = dns_server_ref(s); + + if (m->unicast_scope) + dns_cache_flush(&m->unicast_scope->cache); + + return s; +} + +DnsServer *manager_get_dns_server(Manager *m) { + Link *l; + assert(m); + + /* Try to read updates resolv.conf */ + manager_read_resolv_conf(m); + + /* If no DNS server was chosen so far, pick the first one */ + if (!m->current_dns_server) + manager_set_dns_server(m, m->dns_servers); + + if (!m->current_dns_server) { + bool found = false; + Iterator i; + + /* No DNS servers configured, let's see if there are + * any on any links. If not, we use the fallback + * servers */ + + HASHMAP_FOREACH(l, m->links, i) + if (l->dns_servers) { + found = true; + break; + } + + if (!found) + manager_set_dns_server(m, m->fallback_dns_servers); + } + + return m->current_dns_server; +} + +void manager_next_dns_server(Manager *m) { + assert(m); + + /* If there's currently no DNS server set, then the next + * manager_get_dns_server() will find one */ + if (!m->current_dns_server) + return; + + /* Change to the next one, but make sure to follow the linked + * list only if the server is still linked. */ + if (m->current_dns_server->linked && m->current_dns_server->servers_next) { + manager_set_dns_server(m, m->current_dns_server->servers_next); + return; + } + + /* If there was no next one, then start from the beginning of + * the list */ + if (m->current_dns_server->type == DNS_SERVER_FALLBACK) + manager_set_dns_server(m, m->fallback_dns_servers); + else + manager_set_dns_server(m, m->dns_servers); +} + +static const char* const dns_server_type_table[_DNS_SERVER_TYPE_MAX] = { + [DNS_SERVER_SYSTEM] = "system", + [DNS_SERVER_FALLBACK] = "fallback", + [DNS_SERVER_LINK] = "link", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_server_type, DnsServerType); + +static const char* const dns_server_feature_level_table[_DNS_SERVER_FEATURE_LEVEL_MAX] = { + [DNS_SERVER_FEATURE_LEVEL_TCP] = "TCP", + [DNS_SERVER_FEATURE_LEVEL_UDP] = "UDP", + [DNS_SERVER_FEATURE_LEVEL_EDNS0] = "UDP+EDNS0", + [DNS_SERVER_FEATURE_LEVEL_DO] = "UDP+EDNS0+DO", + [DNS_SERVER_FEATURE_LEVEL_LARGE] = "UDP+EDNS0+DO+LARGE", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_server_feature_level, DnsServerFeatureLevel); diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-server.h b/src/grp-resolve/systemd-resolved/resolved-dns-server.h new file mode 100644 index 0000000000..9f4a69c37a --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-server.h @@ -0,0 +1,143 @@ +#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 "in-addr-util.h" + +typedef struct DnsServer DnsServer; + +typedef enum DnsServerType { + DNS_SERVER_SYSTEM, + DNS_SERVER_FALLBACK, + DNS_SERVER_LINK, +} DnsServerType; +#define _DNS_SERVER_TYPE_MAX (DNS_SERVER_LINK + 1) + +const char* dns_server_type_to_string(DnsServerType i) _const_; +DnsServerType dns_server_type_from_string(const char *s) _pure_; + +typedef enum DnsServerFeatureLevel { + DNS_SERVER_FEATURE_LEVEL_TCP, + DNS_SERVER_FEATURE_LEVEL_UDP, + DNS_SERVER_FEATURE_LEVEL_EDNS0, + DNS_SERVER_FEATURE_LEVEL_DO, + DNS_SERVER_FEATURE_LEVEL_LARGE, + _DNS_SERVER_FEATURE_LEVEL_MAX, + _DNS_SERVER_FEATURE_LEVEL_INVALID = -1 +} DnsServerFeatureLevel; + +#define DNS_SERVER_FEATURE_LEVEL_WORST 0 +#define DNS_SERVER_FEATURE_LEVEL_BEST (_DNS_SERVER_FEATURE_LEVEL_MAX - 1) + +const char* dns_server_feature_level_to_string(int i) _const_; +int dns_server_feature_level_from_string(const char *s) _pure_; + +#include "resolved-link.h" +#include "resolved-manager.h" + +struct DnsServer { + Manager *manager; + + unsigned n_ref; + + DnsServerType type; + Link *link; + + int family; + union in_addr_union address; + + char *server_string; + + usec_t resend_timeout; + usec_t max_rtt; + + DnsServerFeatureLevel verified_feature_level; + DnsServerFeatureLevel possible_feature_level; + + size_t received_udp_packet_max; + + unsigned n_failed_udp; + unsigned n_failed_tcp; + + bool packet_failed:1; + bool packet_truncated:1; + bool packet_bad_opt:1; + bool packet_rrsig_missing:1; + + usec_t verified_usec; + usec_t features_grace_period_usec; + + /* Whether we already warned about downgrading to non-DNSSEC mode for this server */ + bool warned_downgrade:1; + + /* Used when GC'ing old DNS servers when configuration changes. */ + bool marked:1; + + /* If linked is set, then this server appears in the servers linked list */ + bool linked:1; + LIST_FIELDS(DnsServer, servers); +}; + +int dns_server_new( + Manager *m, + DnsServer **ret, + DnsServerType type, + Link *link, + int family, + const union in_addr_union *address); + +DnsServer* dns_server_ref(DnsServer *s); +DnsServer* dns_server_unref(DnsServer *s); + +void dns_server_unlink(DnsServer *s); +void dns_server_move_back_and_unmark(DnsServer *s); + +void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t rtt, size_t size); +void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t usec); +void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level); +void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level); +void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level); +void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level); + +DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s); + +int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level); + +const char *dns_server_string(DnsServer *server); + +bool dns_server_dnssec_supported(DnsServer *server); + +void dns_server_warn_downgrade(DnsServer *server); + +DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr); + +void dns_server_unlink_all(DnsServer *first); +void dns_server_unlink_marked(DnsServer *first); +void dns_server_mark_all(DnsServer *first); + +DnsServer *manager_get_first_dns_server(Manager *m, DnsServerType t); + +DnsServer *manager_set_dns_server(Manager *m, DnsServer *s); +DnsServer *manager_get_dns_server(Manager *m); +void manager_next_dns_server(Manager *m); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref); + +extern const struct hash_ops dns_server_hash_ops; diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-stream.c b/src/grp-resolve/systemd-resolved/resolved-dns-stream.c new file mode 100644 index 0000000000..a1040aeff4 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-stream.c @@ -0,0 +1,403 @@ +/*** + 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 "alloc-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "missing.h" +#include "resolved-dns-stream.h" + +#define DNS_STREAM_TIMEOUT_USEC (10 * USEC_PER_SEC) +#define DNS_STREAMS_MAX 128 + +static void dns_stream_stop(DnsStream *s) { + assert(s); + + s->io_event_source = sd_event_source_unref(s->io_event_source); + s->timeout_event_source = sd_event_source_unref(s->timeout_event_source); + s->fd = safe_close(s->fd); +} + +static int dns_stream_update_io(DnsStream *s) { + int f = 0; + + assert(s); + + if (s->write_packet && s->n_written < sizeof(s->write_size) + s->write_packet->size) + f |= EPOLLOUT; + if (!s->read_packet || s->n_read < sizeof(s->read_size) + s->read_packet->size) + f |= EPOLLIN; + + return sd_event_source_set_io_events(s->io_event_source, f); +} + +static int dns_stream_complete(DnsStream *s, int error) { + assert(s); + + dns_stream_stop(s); + + if (s->complete) + s->complete(s, error); + else + dns_stream_free(s); + + return 0; +} + +static int dns_stream_identify(DnsStream *s) { + union { + struct cmsghdr header; /* For alignment */ + uint8_t buffer[CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo)) + + EXTRA_CMSG_SPACE /* kernel appears to require extra space */]; + } control; + struct msghdr mh = {}; + struct cmsghdr *cmsg; + socklen_t sl; + int r; + + assert(s); + + if (s->identified) + return 0; + + /* Query the local side */ + s->local_salen = sizeof(s->local); + r = getsockname(s->fd, &s->local.sa, &s->local_salen); + if (r < 0) + return -errno; + if (s->local.sa.sa_family == AF_INET6 && s->ifindex <= 0) + s->ifindex = s->local.in6.sin6_scope_id; + + /* Query the remote side */ + s->peer_salen = sizeof(s->peer); + r = getpeername(s->fd, &s->peer.sa, &s->peer_salen); + if (r < 0) + return -errno; + if (s->peer.sa.sa_family == AF_INET6 && s->ifindex <= 0) + s->ifindex = s->peer.in6.sin6_scope_id; + + /* Check consistency */ + assert(s->peer.sa.sa_family == s->local.sa.sa_family); + assert(IN_SET(s->peer.sa.sa_family, AF_INET, AF_INET6)); + + /* Query connection meta information */ + sl = sizeof(control); + if (s->peer.sa.sa_family == AF_INET) { + r = getsockopt(s->fd, IPPROTO_IP, IP_PKTOPTIONS, &control, &sl); + if (r < 0) + return -errno; + } else if (s->peer.sa.sa_family == AF_INET6) { + + r = getsockopt(s->fd, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, &control, &sl); + if (r < 0) + return -errno; + } else + return -EAFNOSUPPORT; + + mh.msg_control = &control; + mh.msg_controllen = sl; + + CMSG_FOREACH(cmsg, &mh) { + + if (cmsg->cmsg_level == IPPROTO_IPV6) { + assert(s->peer.sa.sa_family == AF_INET6); + + switch (cmsg->cmsg_type) { + + case IPV6_PKTINFO: { + struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg); + + if (s->ifindex <= 0) + s->ifindex = i->ipi6_ifindex; + break; + } + + case IPV6_HOPLIMIT: + s->ttl = *(int *) CMSG_DATA(cmsg); + break; + } + + } else if (cmsg->cmsg_level == IPPROTO_IP) { + assert(s->peer.sa.sa_family == AF_INET); + + switch (cmsg->cmsg_type) { + + case IP_PKTINFO: { + struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg); + + if (s->ifindex <= 0) + s->ifindex = i->ipi_ifindex; + break; + } + + case IP_TTL: + s->ttl = *(int *) CMSG_DATA(cmsg); + break; + } + } + } + + /* The Linux kernel sets the interface index to the loopback + * device if the connection came from the local host since it + * avoids the routing table in such a case. Let's unset the + * interface index in such a case. */ + if (s->ifindex == LOOPBACK_IFINDEX) + s->ifindex = 0; + + /* If we don't know the interface index still, we look for the + * first local interface with a matching address. Yuck! */ + if (s->ifindex <= 0) + s->ifindex = manager_find_ifindex(s->manager, s->local.sa.sa_family, s->local.sa.sa_family == AF_INET ? (union in_addr_union*) &s->local.in.sin_addr : (union in_addr_union*) &s->local.in6.sin6_addr); + + if (s->protocol == DNS_PROTOCOL_LLMNR && s->ifindex > 0) { + uint32_t ifindex = htobe32(s->ifindex); + + /* Make sure all packets for this connection are sent on the same interface */ + if (s->local.sa.sa_family == AF_INET) { + r = setsockopt(s->fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex)); + if (r < 0) + log_debug_errno(errno, "Failed to invoke IP_UNICAST_IF: %m"); + } else if (s->local.sa.sa_family == AF_INET6) { + r = setsockopt(s->fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex)); + if (r < 0) + log_debug_errno(errno, "Failed to invoke IPV6_UNICAST_IF: %m"); + } + } + + s->identified = true; + + return 0; +} + +static int on_stream_timeout(sd_event_source *es, usec_t usec, void *userdata) { + DnsStream *s = userdata; + + assert(s); + + return dns_stream_complete(s, ETIMEDOUT); +} + +static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *userdata) { + DnsStream *s = userdata; + int r; + + assert(s); + + r = dns_stream_identify(s); + if (r < 0) + return dns_stream_complete(s, -r); + + if ((revents & EPOLLOUT) && + s->write_packet && + s->n_written < sizeof(s->write_size) + s->write_packet->size) { + + struct iovec iov[2]; + ssize_t ss; + + iov[0].iov_base = &s->write_size; + iov[0].iov_len = sizeof(s->write_size); + iov[1].iov_base = DNS_PACKET_DATA(s->write_packet); + iov[1].iov_len = s->write_packet->size; + + IOVEC_INCREMENT(iov, 2, s->n_written); + + ss = writev(fd, iov, 2); + if (ss < 0) { + if (errno != EINTR && errno != EAGAIN) + return dns_stream_complete(s, errno); + } else + s->n_written += ss; + + /* Are we done? If so, disable the event source for EPOLLOUT */ + if (s->n_written >= sizeof(s->write_size) + s->write_packet->size) { + r = dns_stream_update_io(s); + if (r < 0) + return dns_stream_complete(s, -r); + } + } + + if ((revents & (EPOLLIN|EPOLLHUP|EPOLLRDHUP)) && + (!s->read_packet || + s->n_read < sizeof(s->read_size) + s->read_packet->size)) { + + if (s->n_read < sizeof(s->read_size)) { + ssize_t ss; + + ss = read(fd, (uint8_t*) &s->read_size + s->n_read, sizeof(s->read_size) - s->n_read); + if (ss < 0) { + if (errno != EINTR && errno != EAGAIN) + return dns_stream_complete(s, errno); + } else if (ss == 0) + return dns_stream_complete(s, ECONNRESET); + else + s->n_read += ss; + } + + if (s->n_read >= sizeof(s->read_size)) { + + if (be16toh(s->read_size) < DNS_PACKET_HEADER_SIZE) + return dns_stream_complete(s, EBADMSG); + + if (s->n_read < sizeof(s->read_size) + be16toh(s->read_size)) { + ssize_t ss; + + if (!s->read_packet) { + r = dns_packet_new(&s->read_packet, s->protocol, be16toh(s->read_size)); + if (r < 0) + return dns_stream_complete(s, -r); + + s->read_packet->size = be16toh(s->read_size); + s->read_packet->ipproto = IPPROTO_TCP; + s->read_packet->family = s->peer.sa.sa_family; + s->read_packet->ttl = s->ttl; + s->read_packet->ifindex = s->ifindex; + + if (s->read_packet->family == AF_INET) { + s->read_packet->sender.in = s->peer.in.sin_addr; + s->read_packet->sender_port = be16toh(s->peer.in.sin_port); + s->read_packet->destination.in = s->local.in.sin_addr; + s->read_packet->destination_port = be16toh(s->local.in.sin_port); + } else { + assert(s->read_packet->family == AF_INET6); + s->read_packet->sender.in6 = s->peer.in6.sin6_addr; + s->read_packet->sender_port = be16toh(s->peer.in6.sin6_port); + s->read_packet->destination.in6 = s->local.in6.sin6_addr; + s->read_packet->destination_port = be16toh(s->local.in6.sin6_port); + + if (s->read_packet->ifindex == 0) + s->read_packet->ifindex = s->peer.in6.sin6_scope_id; + if (s->read_packet->ifindex == 0) + s->read_packet->ifindex = s->local.in6.sin6_scope_id; + } + } + + ss = read(fd, + (uint8_t*) DNS_PACKET_DATA(s->read_packet) + s->n_read - sizeof(s->read_size), + sizeof(s->read_size) + be16toh(s->read_size) - s->n_read); + if (ss < 0) { + if (errno != EINTR && errno != EAGAIN) + return dns_stream_complete(s, errno); + } else if (ss == 0) + return dns_stream_complete(s, ECONNRESET); + else + s->n_read += ss; + } + + /* Are we done? If so, disable the event source for EPOLLIN */ + if (s->n_read >= sizeof(s->read_size) + be16toh(s->read_size)) { + r = dns_stream_update_io(s); + if (r < 0) + return dns_stream_complete(s, -r); + + /* If there's a packet handler + * installed, call that. Note that + * this is optional... */ + if (s->on_packet) + return s->on_packet(s); + } + } + } + + if ((s->write_packet && s->n_written >= sizeof(s->write_size) + s->write_packet->size) && + (s->read_packet && s->n_read >= sizeof(s->read_size) + s->read_packet->size)) + return dns_stream_complete(s, 0); + + return 0; +} + +DnsStream *dns_stream_free(DnsStream *s) { + if (!s) + return NULL; + + dns_stream_stop(s); + + if (s->manager) { + LIST_REMOVE(streams, s->manager->dns_streams, s); + s->manager->n_dns_streams--; + } + + dns_packet_unref(s->write_packet); + dns_packet_unref(s->read_packet); + + free(s); + + return 0; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_free); + +int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { + _cleanup_(dns_stream_freep) DnsStream *s = NULL; + int r; + + assert(m); + assert(fd >= 0); + + if (m->n_dns_streams > DNS_STREAMS_MAX) + return -EBUSY; + + s = new0(DnsStream, 1); + if (!s) + return -ENOMEM; + + s->fd = -1; + s->protocol = protocol; + + r = sd_event_add_io(m->event, &s->io_event_source, fd, EPOLLIN, on_stream_io, s); + if (r < 0) + return r; + + (void) sd_event_source_set_description(s->io_event_source, "dns-stream-io"); + + r = sd_event_add_time( + m->event, + &s->timeout_event_source, + clock_boottime_or_monotonic(), + now(clock_boottime_or_monotonic()) + DNS_STREAM_TIMEOUT_USEC, 0, + on_stream_timeout, s); + if (r < 0) + return r; + + (void) sd_event_source_set_description(s->timeout_event_source, "dns-stream-timeout"); + + LIST_PREPEND(streams, m->dns_streams, s); + s->manager = m; + s->fd = fd; + m->n_dns_streams++; + + *ret = s; + s = NULL; + + return 0; +} + +int dns_stream_write_packet(DnsStream *s, DnsPacket *p) { + assert(s); + + if (s->write_packet) + return -EBUSY; + + s->write_packet = dns_packet_ref(p); + s->write_size = htobe16(p->size); + s->n_written = 0; + + return dns_stream_update_io(s); +} diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-stream.h b/src/grp-resolve/systemd-resolved/resolved-dns-stream.h new file mode 100644 index 0000000000..5ccc842249 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-stream.h @@ -0,0 +1,61 @@ +#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 "socket-util.h" + +typedef struct DnsStream DnsStream; + +#include "resolved-dns-packet.h" +#include "resolved-dns-transaction.h" + +struct DnsStream { + Manager *manager; + + DnsProtocol protocol; + + int fd; + union sockaddr_union peer; + socklen_t peer_salen; + union sockaddr_union local; + socklen_t local_salen; + int ifindex; + uint32_t ttl; + bool identified; + + sd_event_source *io_event_source; + sd_event_source *timeout_event_source; + + be16_t write_size, read_size; + DnsPacket *write_packet, *read_packet; + size_t n_written, n_read; + + int (*on_packet)(DnsStream *s); + int (*complete)(DnsStream *s, int error); + + DnsTransaction *transaction; + + LIST_FIELDS(DnsStream, streams); +}; + +int dns_stream_new(Manager *m, DnsStream **s, DnsProtocol protocol, int fd); +DnsStream *dns_stream_free(DnsStream *s); + +int dns_stream_write_packet(DnsStream *s, DnsPacket *p); diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-synthesize.c b/src/grp-resolve/systemd-resolved/resolved-dns-synthesize.c new file mode 100644 index 0000000000..e3003411f7 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-synthesize.c @@ -0,0 +1,413 @@ +/*** + 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 "alloc-util.h" +#include "hostname-util.h" +#include "local-addresses.h" +#include "resolved-dns-synthesize.h" + +int dns_synthesize_ifindex(int ifindex) { + + /* When the caller asked for resolving on a specific + * interface, we synthesize the answer for that + * interface. However, if nothing specific was claimed and we + * only return localhost RRs, we synthesize the answer for + * localhost. */ + + if (ifindex > 0) + return ifindex; + + return LOOPBACK_IFINDEX; +} + +int dns_synthesize_family(uint64_t flags) { + + /* Picks an address family depending on set flags. This is + * purely for synthesized answers, where the family we return + * for the reply should match what was requested in the + * question, even though we are synthesizing the answer + * here. */ + + if (!(flags & SD_RESOLVED_DNS)) { + if (flags & (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_MDNS_IPV4)) + return AF_INET; + if (flags & (SD_RESOLVED_LLMNR_IPV6|SD_RESOLVED_MDNS_IPV6)) + return AF_INET6; + } + + return AF_UNSPEC; +} + +DnsProtocol dns_synthesize_protocol(uint64_t flags) { + + /* Similar as dns_synthesize_family() but does this for the + * protocol. If resolving via DNS was requested, we claim it + * was DNS. Similar, if nothing specific was + * requested. However, if only resolving via LLMNR was + * requested we return that. */ + + if (flags & SD_RESOLVED_DNS) + return DNS_PROTOCOL_DNS; + if (flags & SD_RESOLVED_LLMNR) + return DNS_PROTOCOL_LLMNR; + if (flags & SD_RESOLVED_MDNS) + return DNS_PROTOCOL_MDNS; + + return DNS_PROTOCOL_DNS; +} + +static int synthesize_localhost_rr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { + int r; + + assert(m); + assert(key); + assert(answer); + + r = dns_answer_reserve(answer, 2); + if (r < 0) + return r; + + if (IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_ANY)) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, dns_resource_key_name(key)); + if (!rr) + return -ENOMEM; + + rr->a.in_addr.s_addr = htobe32(INADDR_LOOPBACK); + + r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + if (IN_SET(key->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, dns_resource_key_name(key)); + if (!rr) + return -ENOMEM; + + rr->aaaa.in6_addr = in6addr_loopback; + + r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to, int ifindex, DnsAnswerFlags flags) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, from); + if (!rr) + return -ENOMEM; + + rr->ptr.name = strdup(to); + if (!rr->ptr.name) + return -ENOMEM; + + return dns_answer_add(*answer, rr, ifindex, flags); +} + +static int synthesize_localhost_ptr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { + int r; + + assert(m); + assert(key); + assert(answer); + + if (IN_SET(key->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) { + r = dns_answer_reserve(answer, 1); + if (r < 0) + return r; + + r = answer_add_ptr(answer, dns_resource_key_name(key), "localhost", dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +static int answer_add_addresses_rr( + DnsAnswer **answer, + const char *name, + struct local_address *addresses, + unsigned n_addresses) { + + unsigned j; + int r; + + assert(answer); + assert(name); + + r = dns_answer_reserve(answer, n_addresses); + if (r < 0) + return r; + + for (j = 0; j < n_addresses; j++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + r = dns_resource_record_new_address(&rr, addresses[j].family, &addresses[j].address, name); + if (r < 0) + return r; + + r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +static int answer_add_addresses_ptr( + DnsAnswer **answer, + const char *name, + struct local_address *addresses, + unsigned n_addresses, + int af, const union in_addr_union *match) { + + unsigned j; + int r; + + assert(answer); + assert(name); + + for (j = 0; j < n_addresses; j++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + if (af != AF_UNSPEC) { + + if (addresses[j].family != af) + continue; + + if (match && !in_addr_equal(af, match, &addresses[j].address)) + continue; + } + + r = dns_answer_reserve(answer, 1); + if (r < 0) + return r; + + r = dns_resource_record_new_reverse(&rr, addresses[j].family, &addresses[j].address, name); + if (r < 0) + return r; + + r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +static int synthesize_system_hostname_rr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n = 0, af; + + assert(m); + assert(key); + assert(answer); + + af = dns_type_to_af(key->type); + if (af >= 0) { + n = local_addresses(m->rtnl, ifindex, af, &addresses); + if (n < 0) + return n; + + if (n == 0) { + struct local_address buffer[2]; + + /* If we have no local addresses then use ::1 + * and 127.0.0.2 as local ones. */ + + if (af == AF_INET || af == AF_UNSPEC) + buffer[n++] = (struct local_address) { + .family = AF_INET, + .ifindex = dns_synthesize_ifindex(ifindex), + .address.in.s_addr = htobe32(0x7F000002), + }; + + if (af == AF_INET6 || af == AF_UNSPEC) + buffer[n++] = (struct local_address) { + .family = AF_INET6, + .ifindex = dns_synthesize_ifindex(ifindex), + .address.in6 = in6addr_loopback, + }; + + return answer_add_addresses_rr(answer, dns_resource_key_name(key), buffer, n); + } + } + + return answer_add_addresses_rr(answer, dns_resource_key_name(key), addresses, n); +} + +static int synthesize_system_hostname_ptr(Manager *m, int af, const union in_addr_union *address, int ifindex, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n, r; + + assert(m); + assert(address); + assert(answer); + + if (af == AF_INET && address->in.s_addr == htobe32(0x7F000002)) { + + /* Always map the IPv4 address 127.0.0.2 to the local + * hostname, in addition to "localhost": */ + + r = dns_answer_reserve(answer, 3); + if (r < 0) + return r; + + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->llmnr_hostname, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->mdns_hostname, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", "localhost", dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + return 0; + } + + n = local_addresses(m->rtnl, ifindex, af, &addresses); + if (n < 0) + return n; + + r = answer_add_addresses_ptr(answer, m->llmnr_hostname, addresses, n, af, address); + if (r < 0) + return r; + + return answer_add_addresses_ptr(answer, m->mdns_hostname, addresses, n, af, address); +} + +static int synthesize_gateway_rr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n = 0, af; + + assert(m); + assert(key); + assert(answer); + + af = dns_type_to_af(key->type); + if (af >= 0) { + n = local_gateways(m->rtnl, ifindex, af, &addresses); + if (n < 0) + return n; + } + + return answer_add_addresses_rr(answer, dns_resource_key_name(key), addresses, n); +} + +static int synthesize_gateway_ptr(Manager *m, int af, const union in_addr_union *address, int ifindex, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n; + + assert(m); + assert(address); + assert(answer); + + n = local_gateways(m->rtnl, ifindex, af, &addresses); + if (n < 0) + return n; + + return answer_add_addresses_ptr(answer, "gateway", addresses, n, af, address); +} + +int dns_synthesize_answer( + Manager *m, + DnsQuestion *q, + int ifindex, + DnsAnswer **ret) { + + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + DnsResourceKey *key; + bool found = false; + int r; + + assert(m); + assert(q); + + DNS_QUESTION_FOREACH(key, q) { + union in_addr_union address; + const char *name; + int af; + + if (key->class != DNS_CLASS_IN && + key->class != DNS_CLASS_ANY) + continue; + + name = dns_resource_key_name(key); + + if (is_localhost(name)) { + + r = synthesize_localhost_rr(m, key, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize localhost RRs: %m"); + + } else if (manager_is_own_hostname(m, name)) { + + r = synthesize_system_hostname_rr(m, key, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize system hostname RRs: %m"); + + } else if (is_gateway_hostname(name)) { + + r = synthesize_gateway_rr(m, key, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize gateway RRs: %m"); + + } else if ((dns_name_endswith(name, "127.in-addr.arpa") > 0 && dns_name_equal(name, "2.0.0.127.in-addr.arpa") == 0) || + dns_name_equal(name, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) { + + r = synthesize_localhost_ptr(m, key, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize localhost PTR RRs: %m"); + + } else if (dns_name_address(name, &af, &address) > 0) { + + r = synthesize_system_hostname_ptr(m, af, &address, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize system hostname PTR RR: %m"); + + r = synthesize_gateway_ptr(m, af, &address, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize gateway hostname PTR RR: %m"); + } else + continue; + + found = true; + } + + r = found; + + if (ret) { + *ret = answer; + answer = NULL; + } + + return r; +} diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-synthesize.h b/src/grp-resolve/systemd-resolved/resolved-dns-synthesize.h new file mode 100644 index 0000000000..5d829bb2e7 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-synthesize.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 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 "resolved-dns-answer.h" +#include "resolved-dns-question.h" +#include "resolved-manager.h" + +int dns_synthesize_ifindex(int ifindex); +int dns_synthesize_family(uint64_t flags); +DnsProtocol dns_synthesize_protocol(uint64_t flags); + +int dns_synthesize_answer(Manager *m, DnsQuestion *q, int ifindex, DnsAnswer **ret); diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-transaction.c b/src/grp-resolve/systemd-resolved/resolved-dns-transaction.c new file mode 100644 index 0000000000..a4a67623e7 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-transaction.c @@ -0,0 +1,3048 @@ +/*** + 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 "af-list.h" +#include "alloc-util.h" +#include "dns-domain.h" +#include "errno-list.h" +#include "fd-util.h" +#include "random-util.h" +#include "resolved-dns-cache.h" +#include "resolved-dns-transaction.h" +#include "resolved-llmnr.h" +#include "string-table.h" + +#define TRANSACTIONS_MAX 4096 + +static void dns_transaction_reset_answer(DnsTransaction *t) { + assert(t); + + t->received = dns_packet_unref(t->received); + t->answer = dns_answer_unref(t->answer); + t->answer_rcode = 0; + t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; + t->answer_authenticated = false; + t->answer_nsec_ttl = (uint32_t) -1; + t->answer_errno = 0; +} + +static void dns_transaction_flush_dnssec_transactions(DnsTransaction *t) { + DnsTransaction *z; + + assert(t); + + while ((z = set_steal_first(t->dnssec_transactions))) { + set_remove(z->notify_transactions, t); + set_remove(z->notify_transactions_done, t); + dns_transaction_gc(z); + } +} + +static void dns_transaction_close_connection(DnsTransaction *t) { + assert(t); + + t->stream = dns_stream_free(t->stream); + t->dns_udp_event_source = sd_event_source_unref(t->dns_udp_event_source); + t->dns_udp_fd = safe_close(t->dns_udp_fd); +} + +static void dns_transaction_stop_timeout(DnsTransaction *t) { + assert(t); + + t->timeout_event_source = sd_event_source_unref(t->timeout_event_source); +} + +DnsTransaction* dns_transaction_free(DnsTransaction *t) { + DnsQueryCandidate *c; + DnsZoneItem *i; + DnsTransaction *z; + + if (!t) + return NULL; + + log_debug("Freeing transaction %" PRIu16 ".", t->id); + + dns_transaction_close_connection(t); + dns_transaction_stop_timeout(t); + + dns_packet_unref(t->sent); + dns_transaction_reset_answer(t); + + dns_server_unref(t->server); + + if (t->scope) { + hashmap_remove_value(t->scope->transactions_by_key, t->key, t); + LIST_REMOVE(transactions_by_scope, t->scope->transactions, t); + + if (t->id != 0) + hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id)); + } + + while ((c = set_steal_first(t->notify_query_candidates))) + set_remove(c->transactions, t); + set_free(t->notify_query_candidates); + + while ((c = set_steal_first(t->notify_query_candidates_done))) + set_remove(c->transactions, t); + set_free(t->notify_query_candidates_done); + + while ((i = set_steal_first(t->notify_zone_items))) + i->probe_transaction = NULL; + set_free(t->notify_zone_items); + + while ((i = set_steal_first(t->notify_zone_items_done))) + i->probe_transaction = NULL; + set_free(t->notify_zone_items_done); + + while ((z = set_steal_first(t->notify_transactions))) + set_remove(z->dnssec_transactions, t); + set_free(t->notify_transactions); + + while ((z = set_steal_first(t->notify_transactions_done))) + set_remove(z->dnssec_transactions, t); + set_free(t->notify_transactions_done); + + dns_transaction_flush_dnssec_transactions(t); + set_free(t->dnssec_transactions); + + dns_answer_unref(t->validated_keys); + dns_resource_key_unref(t->key); + + free(t); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsTransaction*, dns_transaction_free); + +bool dns_transaction_gc(DnsTransaction *t) { + assert(t); + + if (t->block_gc > 0) + return true; + + if (set_isempty(t->notify_query_candidates) && + set_isempty(t->notify_query_candidates_done) && + set_isempty(t->notify_zone_items) && + set_isempty(t->notify_zone_items_done) && + set_isempty(t->notify_transactions) && + set_isempty(t->notify_transactions_done)) { + dns_transaction_free(t); + return false; + } + + return true; +} + +static uint16_t pick_new_id(Manager *m) { + uint16_t new_id; + + /* Find a fresh, unused transaction id. Note that this loop is bounded because there's a limit on the number of + * transactions, and it's much lower than the space of IDs. */ + + assert_cc(TRANSACTIONS_MAX < 0xFFFF); + + do + random_bytes(&new_id, sizeof(new_id)); + while (new_id == 0 || + hashmap_get(m->dns_transactions, UINT_TO_PTR(new_id))); + + return new_id; +} + +int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) { + _cleanup_(dns_transaction_freep) DnsTransaction *t = NULL; + int r; + + assert(ret); + assert(s); + assert(key); + + /* Don't allow looking up invalid or pseudo RRs */ + if (!dns_type_is_valid_query(key->type)) + return -EINVAL; + if (dns_type_is_obsolete(key->type)) + return -EOPNOTSUPP; + + /* We only support the IN class */ + if (key->class != DNS_CLASS_IN && key->class != DNS_CLASS_ANY) + return -EOPNOTSUPP; + + if (hashmap_size(s->manager->dns_transactions) >= TRANSACTIONS_MAX) + return -EBUSY; + + r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&s->transactions_by_key, &dns_resource_key_hash_ops); + if (r < 0) + return r; + + t = new0(DnsTransaction, 1); + if (!t) + return -ENOMEM; + + t->dns_udp_fd = -1; + t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; + t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + t->answer_nsec_ttl = (uint32_t) -1; + t->key = dns_resource_key_ref(key); + t->current_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID; + + t->id = pick_new_id(s->manager); + + r = hashmap_put(s->manager->dns_transactions, UINT_TO_PTR(t->id), t); + if (r < 0) { + t->id = 0; + return r; + } + + r = hashmap_replace(s->transactions_by_key, t->key, t); + if (r < 0) { + hashmap_remove(s->manager->dns_transactions, UINT_TO_PTR(t->id)); + return r; + } + + LIST_PREPEND(transactions_by_scope, s->transactions, t); + t->scope = s; + + s->manager->n_transactions_total++; + + if (ret) + *ret = t; + + t = NULL; + + return 0; +} + +static void dns_transaction_shuffle_id(DnsTransaction *t) { + uint16_t new_id; + assert(t); + + /* Pick a new ID for this transaction. */ + + new_id = pick_new_id(t->scope->manager); + assert_se(hashmap_remove_and_put(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id), UINT_TO_PTR(new_id), t) >= 0); + + log_debug("Transaction %" PRIu16 " is now %" PRIu16 ".", t->id, new_id); + t->id = new_id; + + /* Make sure we generate a new packet with the new ID */ + t->sent = dns_packet_unref(t->sent); +} + +static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) { + _cleanup_free_ char *pretty = NULL; + char key_str[DNS_RESOURCE_KEY_STRING_MAX]; + DnsZoneItem *z; + + assert(t); + assert(p); + + if (manager_our_packet(t->scope->manager, p) != 0) + return; + + (void) in_addr_to_string(p->family, &p->sender, &pretty); + + log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s got tentative packet from %s.", + t->id, + dns_resource_key_to_string(t->key, key_str, sizeof key_str), + dns_protocol_to_string(t->scope->protocol), + t->scope->link ? t->scope->link->name : "*", + af_to_name_short(t->scope->family), + strnull(pretty)); + + /* RFC 4795, Section 4.1 says that the peer with the + * lexicographically smaller IP address loses */ + if (memcmp(&p->sender, &p->destination, FAMILY_ADDRESS_SIZE(p->family)) >= 0) { + log_debug("Peer has lexicographically larger IP address and thus lost in the conflict."); + return; + } + + log_debug("We have the lexicographically larger IP address and thus lost in the conflict."); + + t->block_gc++; + + while ((z = set_first(t->notify_zone_items))) { + /* First, make sure the zone item drops the reference + * to us */ + dns_zone_item_probe_stop(z); + + /* Secondly, report this as conflict, so that we might + * look for a different hostname */ + dns_zone_item_conflict(z); + } + t->block_gc--; + + dns_transaction_gc(t); +} + +void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { + DnsQueryCandidate *c; + DnsZoneItem *z; + DnsTransaction *d; + const char *st; + char key_str[DNS_RESOURCE_KEY_STRING_MAX]; + + assert(t); + assert(!DNS_TRANSACTION_IS_LIVE(state)); + + if (state == DNS_TRANSACTION_DNSSEC_FAILED) { + dns_resource_key_to_string(t->key, key_str, sizeof key_str); + + log_struct(LOG_NOTICE, + LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_FAILURE), + LOG_MESSAGE("DNSSEC validation failed for question %s: %s", key_str, dnssec_result_to_string(t->answer_dnssec_result)), + "DNS_TRANSACTION=%" PRIu16, t->id, + "DNS_QUESTION=%s", key_str, + "DNSSEC_RESULT=%s", dnssec_result_to_string(t->answer_dnssec_result), + "DNS_SERVER=%s", dns_server_string(t->server), + "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(t->server->possible_feature_level), + NULL); + } + + /* Note that this call might invalidate the query. Callers + * should hence not attempt to access the query or transaction + * after calling this function. */ + + if (state == DNS_TRANSACTION_ERRNO) + st = errno_to_name(t->answer_errno); + else + st = dns_transaction_state_to_string(state); + + log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s now complete with <%s> from %s (%s).", + t->id, + dns_resource_key_to_string(t->key, key_str, sizeof key_str), + dns_protocol_to_string(t->scope->protocol), + t->scope->link ? t->scope->link->name : "*", + af_to_name_short(t->scope->family), + st, + t->answer_source < 0 ? "none" : dns_transaction_source_to_string(t->answer_source), + t->answer_authenticated ? "authenticated" : "unsigned"); + + t->state = state; + + dns_transaction_close_connection(t); + dns_transaction_stop_timeout(t); + + /* Notify all queries that are interested, but make sure the + * transaction isn't freed while we are still looking at it */ + t->block_gc++; + + SET_FOREACH_MOVE(c, t->notify_query_candidates_done, t->notify_query_candidates) + dns_query_candidate_notify(c); + SWAP_TWO(t->notify_query_candidates, t->notify_query_candidates_done); + + SET_FOREACH_MOVE(z, t->notify_zone_items_done, t->notify_zone_items) + dns_zone_item_notify(z); + SWAP_TWO(t->notify_zone_items, t->notify_zone_items_done); + + SET_FOREACH_MOVE(d, t->notify_transactions_done, t->notify_transactions) + dns_transaction_notify(d, t); + SWAP_TWO(t->notify_transactions, t->notify_transactions_done); + + t->block_gc--; + dns_transaction_gc(t); +} + +static int dns_transaction_pick_server(DnsTransaction *t) { + DnsServer *server; + + assert(t); + assert(t->scope->protocol == DNS_PROTOCOL_DNS); + + server = dns_scope_get_dns_server(t->scope); + if (!server) + return -ESRCH; + + t->current_feature_level = dns_server_possible_feature_level(server); + + if (server == t->server) + return 0; + + dns_server_unref(t->server); + t->server = dns_server_ref(server); + + return 1; +} + +static void dns_transaction_retry(DnsTransaction *t) { + int r; + + assert(t); + + log_debug("Retrying transaction %" PRIu16 ".", t->id); + + /* Before we try again, switch to a new server. */ + dns_scope_next_dns_server(t->scope); + + r = dns_transaction_go(t); + if (r < 0) { + t->answer_errno = -r; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); + } +} + +static int dns_transaction_maybe_restart(DnsTransaction *t) { + assert(t); + + if (!t->server) + return 0; + + if (t->current_feature_level <= dns_server_possible_feature_level(t->server)) + return 0; + + /* The server's current feature level is lower than when we sent the original query. We learnt something from + the response or possibly an auxiliary DNSSEC response that we didn't know before. We take that as reason to + restart the whole transaction. This is a good idea to deal with servers that respond rubbish if we include + OPT RR or DO bit. One of these cases is documented here, for example: + https://open.nlnetlabs.nl/pipermail/dnssec-trigger/2014-November/000376.html */ + + log_debug("Server feature level is now lower than when we began our transaction. Restarting with new ID."); + dns_transaction_shuffle_id(t); + return dns_transaction_go(t); +} + +static int on_stream_complete(DnsStream *s, int error) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + DnsTransaction *t; + + assert(s); + assert(s->transaction); + + /* Copy the data we care about out of the stream before we + * destroy it. */ + t = s->transaction; + p = dns_packet_ref(s->read_packet); + + t->stream = dns_stream_free(t->stream); + + if (ERRNO_IS_DISCONNECT(error)) { + usec_t usec; + + if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { + /* If the LLMNR/TCP connection failed, the host doesn't support LLMNR, and we cannot answer the + * question on this scope. */ + dns_transaction_complete(t, DNS_TRANSACTION_NOT_FOUND); + return 0; + } + + log_debug_errno(error, "Connection failure for DNS TCP stream: %m"); + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &usec) >= 0); + dns_server_packet_lost(t->server, IPPROTO_TCP, t->current_feature_level, usec - t->start_usec); + + dns_transaction_retry(t); + return 0; + } + if (error != 0) { + t->answer_errno = error; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); + return 0; + } + + if (dns_packet_validate_reply(p) <= 0) { + log_debug("Invalid TCP reply packet."); + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return 0; + } + + dns_scope_check_conflicts(t->scope, p); + + t->block_gc++; + dns_transaction_process_reply(t, p); + t->block_gc--; + + /* If the response wasn't useful, then complete the transition + * now. After all, we are the worst feature set now with TCP + * sockets, and there's really no point in retrying. */ + if (t->state == DNS_TRANSACTION_PENDING) + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + else + dns_transaction_gc(t); + + return 0; +} + +static int dns_transaction_open_tcp(DnsTransaction *t) { + _cleanup_close_ int fd = -1; + int r; + + assert(t); + + dns_transaction_close_connection(t); + + switch (t->scope->protocol) { + + case DNS_PROTOCOL_DNS: + r = dns_transaction_pick_server(t); + if (r < 0) + return r; + + if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) + return -EOPNOTSUPP; + + r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level); + if (r < 0) + return r; + + fd = dns_scope_socket_tcp(t->scope, AF_UNSPEC, NULL, t->server, 53); + break; + + case DNS_PROTOCOL_LLMNR: + /* When we already received a reply to this (but it was truncated), send to its sender address */ + if (t->received) + fd = dns_scope_socket_tcp(t->scope, t->received->family, &t->received->sender, NULL, t->received->sender_port); + else { + union in_addr_union address; + int family = AF_UNSPEC; + + /* Otherwise, try to talk to the owner of a + * the IP address, in case this is a reverse + * PTR lookup */ + + r = dns_name_address(dns_resource_key_name(t->key), &family, &address); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + if (family != t->scope->family) + return -ESRCH; + + fd = dns_scope_socket_tcp(t->scope, family, &address, NULL, LLMNR_PORT); + } + + break; + + default: + return -EAFNOSUPPORT; + } + + if (fd < 0) + return fd; + + r = dns_stream_new(t->scope->manager, &t->stream, t->scope->protocol, fd); + if (r < 0) + return r; + fd = -1; + + r = dns_stream_write_packet(t->stream, t->sent); + if (r < 0) { + t->stream = dns_stream_free(t->stream); + return r; + } + + t->stream->complete = on_stream_complete; + t->stream->transaction = t; + + /* The interface index is difficult to determine if we are + * connecting to the local host, hence fill this in right away + * instead of determining it from the socket */ + if (t->scope->link) + t->stream->ifindex = t->scope->link->ifindex; + + dns_transaction_reset_answer(t); + + t->tried_stream = true; + + return 0; +} + +static void dns_transaction_cache_answer(DnsTransaction *t) { + assert(t); + + /* For mDNS we cache whenever we get the packet, rather than + * in each transaction. */ + if (!IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) + return; + + /* We never cache if this packet is from the local host, under + * the assumption that a locally running DNS server would + * cache this anyway, and probably knows better when to flush + * the cache then we could. */ + if (!DNS_PACKET_SHALL_CACHE(t->received)) + return; + + dns_cache_put(&t->scope->cache, + t->key, + t->answer_rcode, + t->answer, + t->answer_authenticated, + t->answer_nsec_ttl, + 0, + t->received->family, + &t->received->sender); +} + +static bool dns_transaction_dnssec_is_live(DnsTransaction *t) { + DnsTransaction *dt; + Iterator i; + + assert(t); + + SET_FOREACH(dt, t->dnssec_transactions, i) + if (DNS_TRANSACTION_IS_LIVE(dt->state)) + return true; + + return false; +} + +static int dns_transaction_dnssec_ready(DnsTransaction *t) { + DnsTransaction *dt; + Iterator i; + + assert(t); + + /* Checks whether the auxiliary DNSSEC transactions of our transaction have completed, or are still + * ongoing. Returns 0, if we aren't ready for the DNSSEC validation, positive if we are. */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + switch (dt->state) { + + case DNS_TRANSACTION_NULL: + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: + /* Still ongoing */ + return 0; + + case DNS_TRANSACTION_RCODE_FAILURE: + if (dt->answer_rcode != DNS_RCODE_NXDOMAIN) { + log_debug("Auxiliary DNSSEC RR query failed with rcode=%s.", dns_rcode_to_string(dt->answer_rcode)); + goto fail; + } + + /* Fall-through: NXDOMAIN is good enough for us. This is because some DNS servers erronously + * return NXDOMAIN for empty non-terminals (Akamai...), and we need to handle that nicely, when + * asking for parent SOA or similar RRs to make unsigned proofs. */ + + case DNS_TRANSACTION_SUCCESS: + /* All good. */ + break; + + case DNS_TRANSACTION_DNSSEC_FAILED: + /* We handle DNSSEC failures different from other errors, as we care about the DNSSEC + * validationr result */ + + log_debug("Auxiliary DNSSEC RR query failed validation: %s", dnssec_result_to_string(dt->answer_dnssec_result)); + t->answer_dnssec_result = dt->answer_dnssec_result; /* Copy error code over */ + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); + return 0; + + + default: + log_debug("Auxiliary DNSSEC RR query failed with %s", dns_transaction_state_to_string(dt->state)); + goto fail; + } + } + + /* All is ready, we can go and validate */ + return 1; + +fail: + t->answer_dnssec_result = DNSSEC_FAILED_AUXILIARY; + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); + return 0; +} + +static void dns_transaction_process_dnssec(DnsTransaction *t) { + int r; + + assert(t); + + /* Are there ongoing DNSSEC transactions? If so, let's wait for them. */ + r = dns_transaction_dnssec_ready(t); + if (r < 0) + goto fail; + if (r == 0) /* We aren't ready yet (or one of our auxiliary transactions failed, and we shouldn't validate now */ + return; + + /* See if we learnt things from the additional DNSSEC transactions, that we didn't know before, and better + * restart the lookup immediately. */ + r = dns_transaction_maybe_restart(t); + if (r < 0) + goto fail; + if (r > 0) /* Transaction got restarted... */ + return; + + /* All our auxiliary DNSSEC transactions are complete now. Try + * to validate our RRset now. */ + r = dns_transaction_validate_dnssec(t); + if (r == -EBADMSG) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + if (r < 0) + goto fail; + + if (t->answer_dnssec_result == DNSSEC_INCOMPATIBLE_SERVER && + t->scope->dnssec_mode == DNSSEC_YES) { + /* We are not in automatic downgrade mode, and the + * server is bad, refuse operation. */ + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); + return; + } + + if (!IN_SET(t->answer_dnssec_result, + _DNSSEC_RESULT_INVALID, /* No DNSSEC validation enabled */ + DNSSEC_VALIDATED, /* Answer is signed and validated successfully */ + DNSSEC_UNSIGNED, /* Answer is right-fully unsigned */ + DNSSEC_INCOMPATIBLE_SERVER)) { /* Server does not do DNSSEC (Yay, we are downgrade attack vulnerable!) */ + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); + return; + } + + if (t->answer_dnssec_result == DNSSEC_INCOMPATIBLE_SERVER) + dns_server_warn_downgrade(t->server); + + dns_transaction_cache_answer(t); + + if (t->answer_rcode == DNS_RCODE_SUCCESS) + dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); + else + dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); + + return; + +fail: + t->answer_errno = -r; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); +} + +static int dns_transaction_has_positive_answer(DnsTransaction *t, DnsAnswerFlags *flags) { + int r; + + assert(t); + + /* Checks whether the answer is positive, i.e. either a direct + * answer to the question, or a CNAME/DNAME for it */ + + r = dns_answer_match_key(t->answer, t->key, flags); + if (r != 0) + return r; + + r = dns_answer_find_cname_or_dname(t->answer, t->key, NULL, flags); + if (r != 0) + return r; + + return false; +} + +static int dns_transaction_fix_rcode(DnsTransaction *t) { + int r; + + assert(t); + + /* Fix up the RCODE to SUCCESS if we get at least one matching RR in a response. Note that this contradicts the + * DNS RFCs a bit. Specifically, RFC 6604 Section 3 clarifies that the RCODE shall say something about a + * CNAME/DNAME chain element coming after the last chain element contained in the message, and not the first + * one included. However, it also indicates that not all DNS servers implement this correctly. Moreover, when + * using DNSSEC we usually only can prove the first element of a CNAME/DNAME chain anyway, hence let's settle + * on always processing the RCODE as referring to the immediate look-up we do, i.e. the first element of a + * CNAME/DNAME chain. This way, we uniformly handle CNAME/DNAME chains, regardless if the DNS server + * incorrectly implements RCODE, whether DNSSEC is in use, or whether the DNS server only supplied us with an + * incomplete CNAME/DNAME chain. + * + * Or in other words: if we get at least one positive reply in a message we patch NXDOMAIN to become SUCCESS, + * and then rely on the CNAME chasing logic to figure out that there's actually a CNAME error with a new + * lookup. */ + + if (t->answer_rcode != DNS_RCODE_NXDOMAIN) + return 0; + + r = dns_transaction_has_positive_answer(t, NULL); + if (r <= 0) + return r; + + t->answer_rcode = DNS_RCODE_SUCCESS; + return 0; +} + +void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { + usec_t ts; + int r; + + assert(t); + assert(p); + assert(t->scope); + assert(t->scope->manager); + + if (t->state != DNS_TRANSACTION_PENDING) + return; + + /* Note that this call might invalidate the query. Callers + * should hence not attempt to access the query or transaction + * after calling this function. */ + + log_debug("Processing incoming packet on transaction %" PRIu16".", t->id); + + switch (t->scope->protocol) { + + case DNS_PROTOCOL_LLMNR: + assert(t->scope->link); + + /* For LLMNR we will not accept any packets from other + * interfaces */ + + if (p->ifindex != t->scope->link->ifindex) + return; + + if (p->family != t->scope->family) + return; + + /* Tentative packets are not full responses but still + * useful for identifying uniqueness conflicts during + * probing. */ + if (DNS_PACKET_LLMNR_T(p)) { + dns_transaction_tentative(t, p); + return; + } + + break; + + case DNS_PROTOCOL_MDNS: + assert(t->scope->link); + + /* For mDNS we will not accept any packets from other interfaces */ + if (p->ifindex != t->scope->link->ifindex) + return; + + if (p->family != t->scope->family) + return; + + break; + + case DNS_PROTOCOL_DNS: + /* Note that we do not need to verify the + * addresses/port numbers of incoming traffic, as we + * invoked connect() on our UDP socket in which case + * the kernel already does the needed verification for + * us. */ + break; + + default: + assert_not_reached("Invalid DNS protocol."); + } + + if (t->received != p) { + dns_packet_unref(t->received); + t->received = dns_packet_ref(p); + } + + t->answer_source = DNS_TRANSACTION_NETWORK; + + if (p->ipproto == IPPROTO_TCP) { + if (DNS_PACKET_TC(p)) { + /* Truncated via TCP? Somebody must be fucking with us */ + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + + if (DNS_PACKET_ID(p) != t->id) { + /* Not the reply to our query? Somebody must be fucking with us */ + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + } + + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); + + switch (t->scope->protocol) { + + case DNS_PROTOCOL_DNS: + assert(t->server); + + if (IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) { + + /* Request failed, immediately try again with reduced features */ + log_debug("Server returned error: %s", dns_rcode_to_string(DNS_PACKET_RCODE(p))); + + dns_server_packet_failed(t->server, t->current_feature_level); + dns_transaction_retry(t); + return; + } else if (DNS_PACKET_TC(p)) + dns_server_packet_truncated(t->server, t->current_feature_level); + + break; + + case DNS_PROTOCOL_LLMNR: + case DNS_PROTOCOL_MDNS: + dns_scope_packet_received(t->scope, ts - t->start_usec); + break; + + default: + assert_not_reached("Invalid DNS protocol."); + } + + if (DNS_PACKET_TC(p)) { + + /* Truncated packets for mDNS are not allowed. Give up immediately. */ + if (t->scope->protocol == DNS_PROTOCOL_MDNS) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + + log_debug("Reply truncated, retrying via TCP."); + + /* Response was truncated, let's try again with good old TCP */ + r = dns_transaction_open_tcp(t); + if (r == -ESRCH) { + /* No servers found? Damn! */ + dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); + return; + } + if (r == -EOPNOTSUPP) { + /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */ + dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); + return; + } + if (r < 0) { + /* On LLMNR, if we cannot connect to the host, + * we immediately give up */ + if (t->scope->protocol != DNS_PROTOCOL_DNS) + goto fail; + + /* On DNS, couldn't send? Try immediately again, with a new server */ + dns_transaction_retry(t); + } + + return; + } + + /* After the superficial checks, actually parse the message. */ + r = dns_packet_extract(p); + if (r < 0) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + + /* Report that the OPT RR was missing */ + if (t->server) { + if (!p->opt) + dns_server_packet_bad_opt(t->server, t->current_feature_level); + + dns_server_packet_received(t->server, p->ipproto, t->current_feature_level, ts - t->start_usec, p->size); + } + + /* See if we know things we didn't know before that indicate we better restart the lookup immediately. */ + r = dns_transaction_maybe_restart(t); + if (r < 0) + goto fail; + if (r > 0) /* Transaction got restarted... */ + return; + + if (IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) { + + /* Only consider responses with equivalent query section to the request */ + r = dns_packet_is_reply_for(p, t->key); + if (r < 0) + goto fail; + if (r == 0) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + + /* Install the answer as answer to the transaction */ + dns_answer_unref(t->answer); + t->answer = dns_answer_ref(p->answer); + t->answer_rcode = DNS_PACKET_RCODE(p); + t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + t->answer_authenticated = false; + + r = dns_transaction_fix_rcode(t); + if (r < 0) + goto fail; + + /* Block GC while starting requests for additional DNSSEC RRs */ + t->block_gc++; + r = dns_transaction_request_dnssec_keys(t); + t->block_gc--; + + /* Maybe the transaction is ready for GC'ing now? If so, free it and return. */ + if (!dns_transaction_gc(t)) + return; + + /* Requesting additional keys might have resulted in + * this transaction to fail, since the auxiliary + * request failed for some reason. If so, we are not + * in pending state anymore, and we should exit + * quickly. */ + if (t->state != DNS_TRANSACTION_PENDING) + return; + if (r < 0) + goto fail; + if (r > 0) { + /* There are DNSSEC transactions pending now. Update the state accordingly. */ + t->state = DNS_TRANSACTION_VALIDATING; + dns_transaction_close_connection(t); + dns_transaction_stop_timeout(t); + return; + } + } + + dns_transaction_process_dnssec(t); + return; + +fail: + t->answer_errno = -r; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); +} + +static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + DnsTransaction *t = userdata; + int r; + + assert(t); + assert(t->scope); + + r = manager_recv(t->scope->manager, fd, DNS_PROTOCOL_DNS, &p); + if (ERRNO_IS_DISCONNECT(-r)) { + usec_t usec; + + /* UDP connection failure get reported via ICMP and then are possible delivered to us on the next + * recvmsg(). Treat this like a lost packet. */ + + log_debug_errno(r, "Connection failure for DNS UDP packet: %m"); + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &usec) >= 0); + dns_server_packet_lost(t->server, IPPROTO_UDP, t->current_feature_level, usec - t->start_usec); + + dns_transaction_retry(t); + return 0; + } + if (r < 0) { + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); + t->answer_errno = -r; + return 0; + } + + r = dns_packet_validate_reply(p); + if (r < 0) { + log_debug_errno(r, "Received invalid DNS packet as response, ignoring: %m"); + return 0; + } + if (r == 0) { + log_debug("Received inappropriate DNS packet as response, ignoring."); + return 0; + } + + if (DNS_PACKET_ID(p) != t->id) { + log_debug("Received packet with incorrect transaction ID, ignoring."); + return 0; + } + + dns_transaction_process_reply(t, p); + return 0; +} + +static int dns_transaction_emit_udp(DnsTransaction *t) { + int r; + + assert(t); + + if (t->scope->protocol == DNS_PROTOCOL_DNS) { + + r = dns_transaction_pick_server(t); + if (r < 0) + return r; + + if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_UDP) + return -EAGAIN; + + if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) + return -EOPNOTSUPP; + + if (r > 0 || t->dns_udp_fd < 0) { /* Server changed, or no connection yet. */ + int fd; + + dns_transaction_close_connection(t); + + fd = dns_scope_socket_udp(t->scope, t->server, 53); + if (fd < 0) + return fd; + + r = sd_event_add_io(t->scope->manager->event, &t->dns_udp_event_source, fd, EPOLLIN, on_dns_packet, t); + if (r < 0) { + safe_close(fd); + return r; + } + + (void) sd_event_source_set_description(t->dns_udp_event_source, "dns-transaction-udp"); + t->dns_udp_fd = fd; + } + + r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level); + if (r < 0) + return r; + } else + dns_transaction_close_connection(t); + + r = dns_scope_emit_udp(t->scope, t->dns_udp_fd, t->sent); + if (r < 0) + return r; + + dns_transaction_reset_answer(t); + + return 0; +} + +static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) { + DnsTransaction *t = userdata; + + assert(s); + assert(t); + + if (!t->initial_jitter_scheduled || t->initial_jitter_elapsed) { + /* Timeout reached? Increase the timeout for the server used */ + switch (t->scope->protocol) { + + case DNS_PROTOCOL_DNS: + assert(t->server); + dns_server_packet_lost(t->server, t->stream ? IPPROTO_TCP : IPPROTO_UDP, t->current_feature_level, usec - t->start_usec); + break; + + case DNS_PROTOCOL_LLMNR: + case DNS_PROTOCOL_MDNS: + dns_scope_packet_lost(t->scope, usec - t->start_usec); + break; + + default: + assert_not_reached("Invalid DNS protocol."); + } + + if (t->initial_jitter_scheduled) + t->initial_jitter_elapsed = true; + } + + log_debug("Timeout reached on transaction %" PRIu16 ".", t->id); + + dns_transaction_retry(t); + return 0; +} + +static usec_t transaction_get_resend_timeout(DnsTransaction *t) { + assert(t); + assert(t->scope); + + switch (t->scope->protocol) { + + case DNS_PROTOCOL_DNS: + assert(t->server); + return t->server->resend_timeout; + + case DNS_PROTOCOL_MDNS: + assert(t->n_attempts > 0); + return (1 << (t->n_attempts - 1)) * USEC_PER_SEC; + + case DNS_PROTOCOL_LLMNR: + return t->scope->resend_timeout; + + default: + assert_not_reached("Invalid DNS protocol."); + } +} + +static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { + int r; + + assert(t); + + dns_transaction_stop_timeout(t); + + r = dns_scope_network_good(t->scope); + if (r < 0) + return r; + if (r == 0) { + dns_transaction_complete(t, DNS_TRANSACTION_NETWORK_DOWN); + return 0; + } + + if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t->scope->protocol)) { + dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); + return 0; + } + + if (t->scope->protocol == DNS_PROTOCOL_LLMNR && t->tried_stream) { + /* If we already tried via a stream, then we don't + * retry on LLMNR. See RFC 4795, Section 2.7. */ + dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); + return 0; + } + + t->n_attempts++; + t->start_usec = ts; + + dns_transaction_reset_answer(t); + dns_transaction_flush_dnssec_transactions(t); + + /* Check the trust anchor. Do so only on classic DNS, since DNSSEC does not apply otherwise. */ + if (t->scope->protocol == DNS_PROTOCOL_DNS) { + r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, t->key, &t->answer); + if (r < 0) + return r; + if (r > 0) { + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR; + t->answer_authenticated = true; + dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); + return 0; + } + + if (dns_name_is_root(dns_resource_key_name(t->key)) && + t->key->type == DNS_TYPE_DS) { + + /* Hmm, this is a request for the root DS? A + * DS RR doesn't exist in the root zone, and + * if our trust anchor didn't know it either, + * this means we cannot do any DNSSEC logic + * anymore. */ + + if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) { + /* We are in downgrade mode. In this + * case, synthesize an unsigned empty + * response, so that the any lookup + * depending on this one can continue + * assuming there was no DS, and hence + * the root zone was unsigned. */ + + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR; + t->answer_authenticated = false; + dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); + } else + /* If we are not in downgrade mode, + * then fail the lookup, because we + * cannot reasonably answer it. There + * might be DS RRs, but we don't know + * them, and the DNS server won't tell + * them to us (and even if it would, + * we couldn't validate and trust them. */ + dns_transaction_complete(t, DNS_TRANSACTION_NO_TRUST_ANCHOR); + + return 0; + } + } + + /* Check the zone, but only if this transaction is not used + * for probing or verifying a zone item. */ + if (set_isempty(t->notify_zone_items)) { + + r = dns_zone_lookup(&t->scope->zone, t->key, &t->answer, NULL, NULL); + if (r < 0) + return r; + if (r > 0) { + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_source = DNS_TRANSACTION_ZONE; + t->answer_authenticated = true; + dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); + return 0; + } + } + + /* Check the cache, but only if this transaction is not used + * for probing or verifying a zone item. */ + if (set_isempty(t->notify_zone_items)) { + + /* Before trying the cache, let's make sure we figured out a + * server to use. Should this cause a change of server this + * might flush the cache. */ + dns_scope_get_dns_server(t->scope); + + /* Let's then prune all outdated entries */ + dns_cache_prune(&t->scope->cache); + + r = dns_cache_lookup(&t->scope->cache, t->key, &t->answer_rcode, &t->answer, &t->answer_authenticated); + if (r < 0) + return r; + if (r > 0) { + t->answer_source = DNS_TRANSACTION_CACHE; + if (t->answer_rcode == DNS_RCODE_SUCCESS) + dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); + else + dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); + return 0; + } + } + + return 1; +} + +static int dns_transaction_make_packet_mdns(DnsTransaction *t) { + + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + bool add_known_answers = false; + DnsTransaction *other; + unsigned qdcount; + usec_t ts; + int r; + + assert(t); + assert(t->scope->protocol == DNS_PROTOCOL_MDNS); + + /* Discard any previously prepared packet, so we can start over and coalesce again */ + t->sent = dns_packet_unref(t->sent); + + r = dns_packet_new_query(&p, t->scope->protocol, 0, false); + if (r < 0) + return r; + + r = dns_packet_append_key(p, t->key, NULL); + if (r < 0) + return r; + + qdcount = 1; + + if (dns_key_is_shared(t->key)) + add_known_answers = true; + + /* + * For mDNS, we want to coalesce as many open queries in pending transactions into one single + * query packet on the wire as possible. To achieve that, we iterate through all pending transactions + * in our current scope, and see whether their timing contraints allow them to be sent. + */ + + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); + + LIST_FOREACH(transactions_by_scope, other, t->scope->transactions) { + + /* Skip ourselves */ + if (other == t) + continue; + + if (other->state != DNS_TRANSACTION_PENDING) + continue; + + if (other->next_attempt_after > ts) + continue; + + if (qdcount >= UINT16_MAX) + break; + + r = dns_packet_append_key(p, other->key, NULL); + + /* + * If we can't stuff more questions into the packet, just give up. + * One of the 'other' transactions will fire later and take care of the rest. + */ + if (r == -EMSGSIZE) + break; + + if (r < 0) + return r; + + r = dns_transaction_prepare(other, ts); + if (r <= 0) + continue; + + ts += transaction_get_resend_timeout(other); + + r = sd_event_add_time( + other->scope->manager->event, + &other->timeout_event_source, + clock_boottime_or_monotonic(), + ts, 0, + on_transaction_timeout, other); + if (r < 0) + return r; + + (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); + + other->state = DNS_TRANSACTION_PENDING; + other->next_attempt_after = ts; + + qdcount++; + + if (dns_key_is_shared(other->key)) + add_known_answers = true; + } + + DNS_PACKET_HEADER(p)->qdcount = htobe16(qdcount); + + /* Append known answer section if we're asking for any shared record */ + if (add_known_answers) { + r = dns_cache_export_shared_to_packet(&t->scope->cache, p); + if (r < 0) + return r; + } + + t->sent = p; + p = NULL; + + return 0; +} + +static int dns_transaction_make_packet(DnsTransaction *t) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + int r; + + assert(t); + + if (t->scope->protocol == DNS_PROTOCOL_MDNS) + return dns_transaction_make_packet_mdns(t); + + if (t->sent) + return 0; + + r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode != DNSSEC_NO); + if (r < 0) + return r; + + r = dns_packet_append_key(p, t->key, NULL); + if (r < 0) + return r; + + DNS_PACKET_HEADER(p)->qdcount = htobe16(1); + DNS_PACKET_HEADER(p)->id = t->id; + + t->sent = p; + p = NULL; + + return 0; +} + +int dns_transaction_go(DnsTransaction *t) { + usec_t ts; + int r; + char key_str[DNS_RESOURCE_KEY_STRING_MAX]; + + assert(t); + + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); + + r = dns_transaction_prepare(t, ts); + if (r <= 0) + return r; + + log_debug("Transaction %" PRIu16 " for <%s> scope %s on %s/%s.", + t->id, + dns_resource_key_to_string(t->key, key_str, sizeof key_str), + dns_protocol_to_string(t->scope->protocol), + t->scope->link ? t->scope->link->name : "*", + af_to_name_short(t->scope->family)); + + if (!t->initial_jitter_scheduled && + (t->scope->protocol == DNS_PROTOCOL_LLMNR || + t->scope->protocol == DNS_PROTOCOL_MDNS)) { + usec_t jitter, accuracy; + + /* RFC 4795 Section 2.7 suggests all queries should be + * delayed by a random time from 0 to JITTER_INTERVAL. */ + + t->initial_jitter_scheduled = true; + + random_bytes(&jitter, sizeof(jitter)); + + switch (t->scope->protocol) { + + case DNS_PROTOCOL_LLMNR: + jitter %= LLMNR_JITTER_INTERVAL_USEC; + accuracy = LLMNR_JITTER_INTERVAL_USEC; + break; + + case DNS_PROTOCOL_MDNS: + jitter %= MDNS_JITTER_RANGE_USEC; + jitter += MDNS_JITTER_MIN_USEC; + accuracy = MDNS_JITTER_RANGE_USEC; + break; + default: + assert_not_reached("bad protocol"); + } + + r = sd_event_add_time( + t->scope->manager->event, + &t->timeout_event_source, + clock_boottime_or_monotonic(), + ts + jitter, accuracy, + on_transaction_timeout, t); + if (r < 0) + return r; + + (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); + + t->n_attempts = 0; + t->next_attempt_after = ts; + t->state = DNS_TRANSACTION_PENDING; + + log_debug("Delaying %s transaction for " USEC_FMT "us.", dns_protocol_to_string(t->scope->protocol), jitter); + return 0; + } + + /* Otherwise, we need to ask the network */ + r = dns_transaction_make_packet(t); + if (r < 0) + return r; + + if (t->scope->protocol == DNS_PROTOCOL_LLMNR && + (dns_name_endswith(dns_resource_key_name(t->key), "in-addr.arpa") > 0 || + dns_name_endswith(dns_resource_key_name(t->key), "ip6.arpa") > 0)) { + + /* RFC 4795, Section 2.4. says reverse lookups shall + * always be made via TCP on LLMNR */ + r = dns_transaction_open_tcp(t); + } else { + /* Try via UDP, and if that fails due to large size or lack of + * support try via TCP */ + r = dns_transaction_emit_udp(t); + if (r == -EMSGSIZE) + log_debug("Sending query via TCP since it is too large."); + if (r == -EAGAIN) + log_debug("Sending query via TCP since server doesn't support UDP."); + if (r == -EMSGSIZE || r == -EAGAIN) + r = dns_transaction_open_tcp(t); + } + + if (r == -ESRCH) { + /* No servers to send this to? */ + dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); + return 0; + } + if (r == -EOPNOTSUPP) { + /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */ + dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); + return 0; + } + if (t->scope->protocol == DNS_PROTOCOL_LLMNR && ERRNO_IS_DISCONNECT(-r)) { + /* On LLMNR, if we cannot connect to a host via TCP when doing reverse lookups. This means we cannot + * answer this request with this protocol. */ + dns_transaction_complete(t, DNS_TRANSACTION_NOT_FOUND); + return 0; + } + if (r < 0) { + if (t->scope->protocol != DNS_PROTOCOL_DNS) + return r; + + /* Couldn't send? Try immediately again, with a new server */ + dns_scope_next_dns_server(t->scope); + + return dns_transaction_go(t); + } + + ts += transaction_get_resend_timeout(t); + + r = sd_event_add_time( + t->scope->manager->event, + &t->timeout_event_source, + clock_boottime_or_monotonic(), + ts, 0, + on_transaction_timeout, t); + if (r < 0) + return r; + + (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); + + t->state = DNS_TRANSACTION_PENDING; + t->next_attempt_after = ts; + + return 1; +} + +static int dns_transaction_find_cyclic(DnsTransaction *t, DnsTransaction *aux) { + DnsTransaction *n; + Iterator i; + int r; + + assert(t); + assert(aux); + + /* Try to find cyclic dependencies between transaction objects */ + + if (t == aux) + return 1; + + SET_FOREACH(n, aux->dnssec_transactions, i) { + r = dns_transaction_find_cyclic(t, n); + if (r != 0) + return r; + } + + return 0; +} + +static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResourceKey *key, DnsTransaction **ret) { + DnsTransaction *aux; + int r; + + assert(t); + assert(ret); + assert(key); + + aux = dns_scope_find_transaction(t->scope, key, true); + if (!aux) { + r = dns_transaction_new(&aux, t->scope, key); + if (r < 0) + return r; + } else { + if (set_contains(t->dnssec_transactions, aux)) { + *ret = aux; + return 0; + } + + r = dns_transaction_find_cyclic(t, aux); + if (r < 0) + return r; + if (r > 0) { + char s[DNS_RESOURCE_KEY_STRING_MAX], saux[DNS_RESOURCE_KEY_STRING_MAX]; + + log_debug("Potential cyclic dependency, refusing to add transaction %" PRIu16 " (%s) as dependency for %" PRIu16 " (%s).", + aux->id, + dns_resource_key_to_string(t->key, s, sizeof s), + t->id, + dns_resource_key_to_string(aux->key, saux, sizeof saux)); + + return -ELOOP; + } + } + + r = set_ensure_allocated(&t->dnssec_transactions, NULL); + if (r < 0) + goto gc; + + r = set_ensure_allocated(&aux->notify_transactions, NULL); + if (r < 0) + goto gc; + + r = set_ensure_allocated(&aux->notify_transactions_done, NULL); + if (r < 0) + goto gc; + + r = set_put(t->dnssec_transactions, aux); + if (r < 0) + goto gc; + + r = set_put(aux->notify_transactions, t); + if (r < 0) { + (void) set_remove(t->dnssec_transactions, aux); + goto gc; + } + + *ret = aux; + return 1; + +gc: + dns_transaction_gc(aux); + return r; +} + +static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey *key) { + _cleanup_(dns_answer_unrefp) DnsAnswer *a = NULL; + DnsTransaction *aux; + int r; + + assert(t); + assert(key); + + /* Try to get the data from the trust anchor */ + r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, key, &a); + if (r < 0) + return r; + if (r > 0) { + r = dns_answer_extend(&t->validated_keys, a); + if (r < 0) + return r; + + return 0; + } + + /* This didn't work, ask for it via the network/cache then. */ + r = dns_transaction_add_dnssec_transaction(t, key, &aux); + if (r == -ELOOP) /* This would result in a cyclic dependency */ + return 0; + if (r < 0) + return r; + + if (aux->state == DNS_TRANSACTION_NULL) { + r = dns_transaction_go(aux); + if (r < 0) + return r; + } + + return 1; +} + +static int dns_transaction_negative_trust_anchor_lookup(DnsTransaction *t, const char *name) { + int r; + + assert(t); + + /* Check whether the specified name is in the NTA + * database, either in the global one, or the link-local + * one. */ + + r = dns_trust_anchor_lookup_negative(&t->scope->manager->trust_anchor, name); + if (r != 0) + return r; + + if (!t->scope->link) + return 0; + + return set_contains(t->scope->link->dnssec_negative_trust_anchors, name); +} + +static int dns_transaction_has_unsigned_negative_answer(DnsTransaction *t) { + int r; + + assert(t); + + /* Checks whether the answer is negative, and lacks NSEC/NSEC3 + * RRs to prove it */ + + r = dns_transaction_has_positive_answer(t, NULL); + if (r < 0) + return r; + if (r > 0) + return false; + + /* Is this key explicitly listed as a negative trust anchor? + * If so, it's nothing we need to care about */ + r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(t->key)); + if (r < 0) + return r; + if (r > 0) + return false; + + /* The answer does not contain any RRs that match to the + * question. If so, let's see if there are any NSEC/NSEC3 RRs + * included. If not, the answer is unsigned. */ + + r = dns_answer_contains_nsec_or_nsec3(t->answer); + if (r < 0) + return r; + if (r > 0) + return false; + + return true; +} + +static int dns_transaction_is_primary_response(DnsTransaction *t, DnsResourceRecord *rr) { + int r; + + assert(t); + assert(rr); + + /* Check if the specified RR is the "primary" response, + * i.e. either matches the question precisely or is a + * CNAME/DNAME for it. */ + + r = dns_resource_key_match_rr(t->key, rr, NULL); + if (r != 0) + return r; + + return dns_resource_key_match_cname_or_dname(t->key, rr->key, NULL); +} + +static bool dns_transaction_dnssec_supported(DnsTransaction *t) { + assert(t); + + /* Checks whether our transaction's DNS server is assumed to be compatible with DNSSEC. Returns false as soon + * as we changed our mind about a server, and now believe it is incompatible with DNSSEC. */ + + if (t->scope->protocol != DNS_PROTOCOL_DNS) + return false; + + /* If we have picked no server, then we are working from the cache or some other source, and DNSSEC might well + * be supported, hence return true. */ + if (!t->server) + return true; + + if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_DO) + return false; + + return dns_server_dnssec_supported(t->server); +} + +static bool dns_transaction_dnssec_supported_full(DnsTransaction *t) { + DnsTransaction *dt; + Iterator i; + + assert(t); + + /* Checks whether our transaction our any of the auxiliary transactions couldn't do DNSSEC. */ + + if (!dns_transaction_dnssec_supported(t)) + return false; + + SET_FOREACH(dt, t->dnssec_transactions, i) + if (!dns_transaction_dnssec_supported(dt)) + return false; + + return true; +} + +int dns_transaction_request_dnssec_keys(DnsTransaction *t) { + DnsResourceRecord *rr; + + int r; + + assert(t); + + /* + * Retrieve all auxiliary RRs for the answer we got, so that + * we can verify signatures or prove that RRs are rightfully + * unsigned. Specifically: + * + * - For RRSIG we get the matching DNSKEY + * - For DNSKEY we get the matching DS + * - For unsigned SOA/NS we get the matching DS + * - For unsigned CNAME/DNAME/DS we get the parent SOA RR + * - For other unsigned RRs we get the matching SOA RR + * - For SOA/NS queries with no matching response RR, and no NSEC/NSEC3, the DS RR + * - For DS queries with no matching response RRs, and no NSEC/NSEC3, the parent's SOA RR + * - For other queries with no matching response RRs, and no NSEC/NSEC3, the SOA RR + */ + + if (t->scope->dnssec_mode == DNSSEC_NO) + return 0; + if (t->answer_source != DNS_TRANSACTION_NETWORK) + return 0; /* We only need to validate stuff from the network */ + if (!dns_transaction_dnssec_supported(t)) + return 0; /* If we can't do DNSSEC anyway there's no point in geting the auxiliary RRs */ + + DNS_ANSWER_FOREACH(rr, t->answer) { + + if (dns_type_is_pseudo(rr->key->type)) + continue; + + /* If this RR is in the negative trust anchor, we don't need to validate it. */ + r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(rr->key)); + if (r < 0) + return r; + if (r > 0) + continue; + + switch (rr->key->type) { + + case DNS_TYPE_RRSIG: { + /* For each RRSIG we request the matching DNSKEY */ + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *dnskey = NULL; + + /* If this RRSIG is about a DNSKEY RR and the + * signer is the same as the owner, then we + * already have the DNSKEY, and we don't have + * to look for more. */ + if (rr->rrsig.type_covered == DNS_TYPE_DNSKEY) { + r = dns_name_equal(rr->rrsig.signer, dns_resource_key_name(rr->key)); + if (r < 0) + return r; + if (r > 0) + continue; + } + + /* If the signer is not a parent of our + * original query, then this is about an + * auxiliary RRset, but not anything we asked + * for. In this case we aren't interested, + * because we don't want to request additional + * RRs for stuff we didn't really ask for, and + * also to avoid request loops, where + * additional RRs from one transaction result + * in another transaction whose additonal RRs + * point back to the original transaction, and + * we deadlock. */ + r = dns_name_endswith(dns_resource_key_name(t->key), rr->rrsig.signer); + if (r < 0) + return r; + if (r == 0) + continue; + + dnskey = dns_resource_key_new(rr->key->class, DNS_TYPE_DNSKEY, rr->rrsig.signer); + if (!dnskey) + return -ENOMEM; + + log_debug("Requesting DNSKEY to validate transaction %" PRIu16" (%s, RRSIG with key tag: %" PRIu16 ").", + t->id, dns_resource_key_name(rr->key), rr->rrsig.key_tag); + r = dns_transaction_request_dnssec_rr(t, dnskey); + if (r < 0) + return r; + break; + } + + case DNS_TYPE_DNSKEY: { + /* For each DNSKEY we request the matching DS */ + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; + + /* If the DNSKEY we are looking at is not for + * zone we are interested in, nor any of its + * parents, we aren't interested, and don't + * request it. After all, we don't want to end + * up in request loops, and want to keep + * additional traffic down. */ + + r = dns_name_endswith(dns_resource_key_name(t->key), dns_resource_key_name(rr->key)); + if (r < 0) + return r; + if (r == 0) + continue; + + ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, dns_resource_key_name(rr->key)); + if (!ds) + return -ENOMEM; + + log_debug("Requesting DS to validate transaction %" PRIu16" (%s, DNSKEY with key tag: %" PRIu16 ").", + t->id, dns_resource_key_name(rr->key), dnssec_keytag(rr, false)); + r = dns_transaction_request_dnssec_rr(t, ds); + if (r < 0) + return r; + + break; + } + + case DNS_TYPE_SOA: + case DNS_TYPE_NS: { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; + + /* For an unsigned SOA or NS, try to acquire + * the matching DS RR, as we are at a zone cut + * then, and whether a DS exists tells us + * whether the zone is signed. Do so only if + * this RR matches our original question, + * however. */ + + r = dns_resource_key_match_rr(t->key, rr, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_has_rrsig(t->answer, rr->key); + if (r < 0) + return r; + if (r > 0) + continue; + + ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, dns_resource_key_name(rr->key)); + if (!ds) + return -ENOMEM; + + log_debug("Requesting DS to validate transaction %" PRIu16 " (%s, unsigned SOA/NS RRset).", + t->id, dns_resource_key_name(rr->key)); + r = dns_transaction_request_dnssec_rr(t, ds); + if (r < 0) + return r; + + break; + } + + case DNS_TYPE_DS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + const char *name; + + /* CNAMEs and DNAMEs cannot be located at a + * zone apex, hence ask for the parent SOA for + * unsigned CNAME/DNAME RRs, maybe that's the + * apex. But do all that only if this is + * actually a response to our original + * question. + * + * Similar for DS RRs, which are signed when + * the parent SOA is signed. */ + + r = dns_transaction_is_primary_response(t, rr); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_has_rrsig(t->answer, rr->key); + if (r < 0) + return r; + if (r > 0) + continue; + + r = dns_answer_has_dname_for_cname(t->answer, rr); + if (r < 0) + return r; + if (r > 0) + continue; + + name = dns_resource_key_name(rr->key); + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r == 0) + continue; + + soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, name); + if (!soa) + return -ENOMEM; + + log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned CNAME/DNAME/DS RRset).", + t->id, dns_resource_key_name(rr->key)); + r = dns_transaction_request_dnssec_rr(t, soa); + if (r < 0) + return r; + + break; + } + + default: { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + + /* For other unsigned RRsets (including + * NSEC/NSEC3!), look for proof the zone is + * unsigned, by requesting the SOA RR of the + * zone. However, do so only if they are + * directly relevant to our original + * question. */ + + r = dns_transaction_is_primary_response(t, rr); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_has_rrsig(t->answer, rr->key); + if (r < 0) + return r; + if (r > 0) + continue; + + soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, dns_resource_key_name(rr->key)); + if (!soa) + return -ENOMEM; + + log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned non-SOA/NS RRset <%s>).", + t->id, dns_resource_key_name(rr->key), dns_resource_record_to_string(rr)); + r = dns_transaction_request_dnssec_rr(t, soa); + if (r < 0) + return r; + break; + }} + } + + /* Above, we requested everything necessary to validate what + * we got. Now, let's request what we need to validate what we + * didn't get... */ + + r = dns_transaction_has_unsigned_negative_answer(t); + if (r < 0) + return r; + if (r > 0) { + const char *name; + uint16_t type = 0; + + name = dns_resource_key_name(t->key); + + /* If this was a SOA or NS request, then check if there's a DS RR for the same domain. Note that this + * could also be used as indication that we are not at a zone apex, but in real world setups there are + * too many broken DNS servers (Hello, incapdns.net!) where non-terminal zones return NXDOMAIN even + * though they have further children. If this was a DS request, then it's signed when the parent zone + * is signed, hence ask the parent SOA in that case. If this was any other RR then ask for the SOA RR, + * to see if that is signed. */ + + if (t->key->type == DNS_TYPE_DS) { + r = dns_name_parent(&name); + if (r > 0) { + type = DNS_TYPE_SOA; + log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned empty DS response).", + t->id, dns_resource_key_name(t->key)); + } else + name = NULL; + + } else if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS)) { + + type = DNS_TYPE_DS; + log_debug("Requesting DS to validate transaction %" PRIu16 " (%s, unsigned empty SOA/NS response).", + t->id, dns_resource_key_name(t->key)); + + } else { + type = DNS_TYPE_SOA; + log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned empty non-SOA/NS/DS response).", + t->id, dns_resource_key_name(t->key)); + } + + if (name) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + + soa = dns_resource_key_new(t->key->class, type, name); + if (!soa) + return -ENOMEM; + + r = dns_transaction_request_dnssec_rr(t, soa); + if (r < 0) + return r; + } + } + + return dns_transaction_dnssec_is_live(t); +} + +void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source) { + assert(t); + assert(source); + + /* Invoked whenever any of our auxiliary DNSSEC transactions completed its work. If the state is still PENDING, + we are still in the loop that adds further DNSSEC transactions, hence don't check if we are ready yet. If + the state is VALIDATING however, we should check if we are complete now. */ + + if (t->state == DNS_TRANSACTION_VALIDATING) + dns_transaction_process_dnssec(t); +} + +static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) { + DnsResourceRecord *rr; + int ifindex, r; + + assert(t); + + /* Add all DNSKEY RRs from the answer that are validated by DS + * RRs from the list of validated keys to the list of + * validated keys. */ + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, t->answer) { + + r = dnssec_verify_dnskey_by_ds_search(rr, t->validated_keys); + if (r < 0) + return r; + if (r == 0) + continue; + + /* If so, the DNSKEY is validated too. */ + r = dns_answer_add_extend(&t->validated_keys, rr, ifindex, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *rr) { + int r; + + assert(t); + assert(rr); + + /* Checks if the RR we are looking for must be signed with an + * RRSIG. This is used for positive responses. */ + + if (t->scope->dnssec_mode == DNSSEC_NO) + return false; + + if (dns_type_is_pseudo(rr->key->type)) + return -EINVAL; + + r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(rr->key)); + if (r < 0) + return r; + if (r > 0) + return false; + + switch (rr->key->type) { + + case DNS_TYPE_RRSIG: + /* RRSIGs are the signatures themselves, they need no signing. */ + return false; + + case DNS_TYPE_SOA: + case DNS_TYPE_NS: { + DnsTransaction *dt; + Iterator i; + + /* For SOA or NS RRs we look for a matching DS transaction */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != rr->key->class) + continue; + if (dt->key->type != DNS_TYPE_DS) + continue; + + r = dns_name_equal(dns_resource_key_name(dt->key), dns_resource_key_name(rr->key)); + if (r < 0) + return r; + if (r == 0) + continue; + + /* We found a DS transactions for the SOA/NS + * RRs we are looking at. If it discovered signed DS + * RRs, then we need to be signed, too. */ + + if (!dt->answer_authenticated) + return false; + + return dns_answer_match_key(dt->answer, dt->key, NULL); + } + + /* We found nothing that proves this is safe to leave + * this unauthenticated, hence ask inist on + * authentication. */ + return true; + } + + case DNS_TYPE_DS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: { + const char *parent = NULL; + DnsTransaction *dt; + Iterator i; + + /* + * CNAME/DNAME RRs cannot be located at a zone apex, hence look directly for the parent SOA. + * + * DS RRs are signed if the parent is signed, hence also look at the parent SOA + */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != rr->key->class) + continue; + if (dt->key->type != DNS_TYPE_SOA) + continue; + + if (!parent) { + parent = dns_resource_key_name(rr->key); + r = dns_name_parent(&parent); + if (r < 0) + return r; + if (r == 0) { + if (rr->key->type == DNS_TYPE_DS) + return true; + + /* A CNAME/DNAME without a parent? That's sooo weird. */ + log_debug("Transaction %" PRIu16 " claims CNAME/DNAME at root. Refusing.", t->id); + return -EBADMSG; + } + } + + r = dns_name_equal(dns_resource_key_name(dt->key), parent); + if (r < 0) + return r; + if (r == 0) + continue; + + return t->answer_authenticated; + } + + return true; + } + + default: { + DnsTransaction *dt; + Iterator i; + + /* Any other kind of RR (including DNSKEY/NSEC/NSEC3). Let's see if our SOA lookup was authenticated */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != rr->key->class) + continue; + if (dt->key->type != DNS_TYPE_SOA) + continue; + + r = dns_name_equal(dns_resource_key_name(dt->key), dns_resource_key_name(rr->key)); + if (r < 0) + return r; + if (r == 0) + continue; + + /* We found the transaction that was supposed to find + * the SOA RR for us. It was successful, but found no + * RR for us. This means we are not at a zone cut. In + * this case, we require authentication if the SOA + * lookup was authenticated too. */ + return t->answer_authenticated; + } + + return true; + }} +} + +static int dns_transaction_in_private_tld(DnsTransaction *t, const DnsResourceKey *key) { + DnsTransaction *dt; + const char *tld; + Iterator i; + int r; + + /* If DNSSEC downgrade mode is on, checks whether the + * specified RR is one level below a TLD we have proven not to + * exist. In such a case we assume that this is a private + * domain, and permit it. + * + * This detects cases like the Fritz!Box router networks. Each + * Fritz!Box router serves a private "fritz.box" zone, in the + * non-existing TLD "box". Requests for the "fritz.box" domain + * are served by the router itself, while requests for the + * "box" domain will result in NXDOMAIN. + * + * Note that this logic is unable to detect cases where a + * router serves a private DNS zone directly under + * non-existing TLD. In such a case we cannot detect whether + * the TLD is supposed to exist or not, as all requests we + * make for it will be answered by the router's zone, and not + * by the root zone. */ + + assert(t); + + if (t->scope->dnssec_mode != DNSSEC_ALLOW_DOWNGRADE) + return false; /* In strict DNSSEC mode what doesn't exist, doesn't exist */ + + tld = dns_resource_key_name(key); + r = dns_name_parent(&tld); + if (r < 0) + return r; + if (r == 0) + return false; /* Already the root domain */ + + if (!dns_name_is_single_label(tld)) + return false; + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != key->class) + continue; + + r = dns_name_equal(dns_resource_key_name(dt->key), tld); + if (r < 0) + return r; + if (r == 0) + continue; + + /* We found an auxiliary lookup we did for the TLD. If + * that returned with NXDOMAIN, we know the TLD didn't + * exist, and hence this might be a private zone. */ + + return dt->answer_rcode == DNS_RCODE_NXDOMAIN; + } + + return false; +} + +static int dns_transaction_requires_nsec(DnsTransaction *t) { + char key_str[DNS_RESOURCE_KEY_STRING_MAX]; + DnsTransaction *dt; + const char *name; + uint16_t type = 0; + Iterator i; + int r; + + assert(t); + + /* Checks if we need to insist on NSEC/NSEC3 RRs for proving + * this negative reply */ + + if (t->scope->dnssec_mode == DNSSEC_NO) + return false; + + if (dns_type_is_pseudo(t->key->type)) + return -EINVAL; + + r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(t->key)); + if (r < 0) + return r; + if (r > 0) + return false; + + r = dns_transaction_in_private_tld(t, t->key); + if (r < 0) + return r; + if (r > 0) { + /* The lookup is from a TLD that is proven not to + * exist, and we are in downgrade mode, hence ignore + * that fact that we didn't get any NSEC RRs.*/ + + log_info("Detected a negative query %s in a private DNS zone, permitting unsigned response.", + dns_resource_key_to_string(t->key, key_str, sizeof key_str)); + return false; + } + + name = dns_resource_key_name(t->key); + + if (t->key->type == DNS_TYPE_DS) { + + /* We got a negative reply for this DS lookup? DS RRs are signed when their parent zone is signed, + * hence check the parent SOA in this case. */ + + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r == 0) + return true; + + type = DNS_TYPE_SOA; + + } else if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS)) + /* We got a negative reply for this SOA/NS lookup? If so, check if there's a DS RR for this */ + type = DNS_TYPE_DS; + else + /* For all other negative replies, check for the SOA lookup */ + type = DNS_TYPE_SOA; + + /* For all other RRs we check the SOA on the same level to see + * if it's signed. */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != t->key->class) + continue; + if (dt->key->type != type) + continue; + + r = dns_name_equal(dns_resource_key_name(dt->key), name); + if (r < 0) + return r; + if (r == 0) + continue; + + return dt->answer_authenticated; + } + + /* If in doubt, require NSEC/NSEC3 */ + return true; +} + +static int dns_transaction_dnskey_authenticated(DnsTransaction *t, DnsResourceRecord *rr) { + DnsResourceRecord *rrsig; + bool found = false; + int r; + + /* Checks whether any of the DNSKEYs used for the RRSIGs for + * the specified RRset is authenticated (i.e. has a matching + * DS RR). */ + + r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(rr->key)); + if (r < 0) + return r; + if (r > 0) + return false; + + DNS_ANSWER_FOREACH(rrsig, t->answer) { + DnsTransaction *dt; + Iterator i; + + r = dnssec_key_match_rrsig(rr->key, rrsig); + if (r < 0) + return r; + if (r == 0) + continue; + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != rr->key->class) + continue; + + if (dt->key->type == DNS_TYPE_DNSKEY) { + + r = dns_name_equal(dns_resource_key_name(dt->key), rrsig->rrsig.signer); + if (r < 0) + return r; + if (r == 0) + continue; + + /* OK, we found an auxiliary DNSKEY + * lookup. If that lookup is + * authenticated, report this. */ + + if (dt->answer_authenticated) + return true; + + found = true; + + } else if (dt->key->type == DNS_TYPE_DS) { + + r = dns_name_equal(dns_resource_key_name(dt->key), rrsig->rrsig.signer); + if (r < 0) + return r; + if (r == 0) + continue; + + /* OK, we found an auxiliary DS + * lookup. If that lookup is + * authenticated and non-zero, we + * won! */ + + if (!dt->answer_authenticated) + return false; + + return dns_answer_match_key(dt->answer, dt->key, NULL); + } + } + } + + return found ? false : -ENXIO; +} + +static int dns_transaction_known_signed(DnsTransaction *t, DnsResourceRecord *rr) { + assert(t); + assert(rr); + + /* We know that the root domain is signed, hence if it appears + * not to be signed, there's a problem with the DNS server */ + + return rr->key->class == DNS_CLASS_IN && + dns_name_is_root(dns_resource_key_name(rr->key)); +} + +static int dns_transaction_check_revoked_trust_anchors(DnsTransaction *t) { + DnsResourceRecord *rr; + int r; + + assert(t); + + /* Maybe warn the user that we encountered a revoked DNSKEY + * for a key from our trust anchor. Note that we don't care + * whether the DNSKEY can be authenticated or not. It's + * sufficient if it is self-signed. */ + + DNS_ANSWER_FOREACH(rr, t->answer) { + r = dns_trust_anchor_check_revoked(&t->scope->manager->trust_anchor, rr, t->answer); + if (r < 0) + return r; + } + + return 0; +} + +static int dns_transaction_invalidate_revoked_keys(DnsTransaction *t) { + bool changed; + int r; + + assert(t); + + /* Removes all DNSKEY/DS objects from t->validated_keys that + * our trust anchors database considers revoked. */ + + do { + DnsResourceRecord *rr; + + changed = false; + + DNS_ANSWER_FOREACH(rr, t->validated_keys) { + r = dns_trust_anchor_is_revoked(&t->scope->manager->trust_anchor, rr); + if (r < 0) + return r; + if (r > 0) { + r = dns_answer_remove_by_rr(&t->validated_keys, rr); + if (r < 0) + return r; + + assert(r > 0); + changed = true; + break; + } + } + } while (changed); + + return 0; +} + +static int dns_transaction_copy_validated(DnsTransaction *t) { + DnsTransaction *dt; + Iterator i; + int r; + + assert(t); + + /* Copy all validated RRs from the auxiliary DNSSEC transactions into our set of validated RRs */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (DNS_TRANSACTION_IS_LIVE(dt->state)) + continue; + + if (!dt->answer_authenticated) + continue; + + r = dns_answer_extend(&t->validated_keys, dt->answer); + if (r < 0) + return r; + } + + return 0; +} + +typedef enum { + DNSSEC_PHASE_DNSKEY, /* Phase #1, only validate DNSKEYs */ + DNSSEC_PHASE_NSEC, /* Phase #2, only validate NSEC+NSEC3 */ + DNSSEC_PHASE_ALL, /* Phase #3, validate everything else */ +} Phase; + +static int dnssec_validate_records( + DnsTransaction *t, + Phase phase, + bool *have_nsec, + DnsAnswer **validated) { + + DnsResourceRecord *rr; + int r; + + /* Returns negative on error, 0 if validation failed, 1 to restart validation, 2 when finished. */ + + DNS_ANSWER_FOREACH(rr, t->answer) { + DnsResourceRecord *rrsig = NULL; + DnssecResult result; + + switch (rr->key->type) { + case DNS_TYPE_RRSIG: + continue; + + case DNS_TYPE_DNSKEY: + /* We validate DNSKEYs only in the DNSKEY and ALL phases */ + if (phase == DNSSEC_PHASE_NSEC) + continue; + break; + + case DNS_TYPE_NSEC: + case DNS_TYPE_NSEC3: + *have_nsec = true; + + /* We validate NSEC/NSEC3 only in the NSEC and ALL phases */ + if (phase == DNSSEC_PHASE_DNSKEY) + continue; + break; + + default: + /* We validate all other RRs only in the ALL phases */ + if (phase != DNSSEC_PHASE_ALL) + continue; + } + + r = dnssec_verify_rrset_search(t->answer, rr->key, t->validated_keys, USEC_INFINITY, &result, &rrsig); + if (r < 0) + return r; + + log_debug("Looking at %s: %s", strna(dns_resource_record_to_string(rr)), dnssec_result_to_string(result)); + + if (result == DNSSEC_VALIDATED) { + + if (rr->key->type == DNS_TYPE_DNSKEY) { + /* If we just validated a DNSKEY RRset, then let's add these keys to + * the set of validated keys for this transaction. */ + + r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + /* Some of the DNSKEYs we just added might already have been revoked, + * remove them again in that case. */ + r = dns_transaction_invalidate_revoked_keys(t); + if (r < 0) + return r; + } + + /* Add the validated RRset to the new list of validated + * RRsets, and remove it from the unvalidated RRsets. + * We mark the RRset as authenticated and cacheable. */ + r = dns_answer_move_by_key(validated, &t->answer, rr->key, DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE); + if (r < 0) + return r; + + manager_dnssec_verdict(t->scope->manager, DNSSEC_SECURE, rr->key); + + /* Exit the loop, we dropped something from the answer, start from the beginning */ + return 1; + } + + /* If we haven't read all DNSKEYs yet a negative result of the validation is irrelevant, as + * there might be more DNSKEYs coming. Similar, if we haven't read all NSEC/NSEC3 RRs yet, + * we cannot do positive wildcard proofs yet, as those require the NSEC/NSEC3 RRs. */ + if (phase != DNSSEC_PHASE_ALL) + continue; + + if (result == DNSSEC_VALIDATED_WILDCARD) { + bool authenticated = false; + const char *source; + + /* This RRset validated, but as a wildcard. This means we need + * to prove via NSEC/NSEC3 that no matching non-wildcard RR exists.*/ + + /* First step, determine the source of synthesis */ + r = dns_resource_record_source(rrsig, &source); + if (r < 0) + return r; + + r = dnssec_test_positive_wildcard(*validated, + dns_resource_key_name(rr->key), + source, + rrsig->rrsig.signer, + &authenticated); + + /* Unless the NSEC proof showed that the key really doesn't exist something is off. */ + if (r == 0) + result = DNSSEC_INVALID; + else { + r = dns_answer_move_by_key(validated, &t->answer, rr->key, + authenticated ? (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE) : 0); + if (r < 0) + return r; + + manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, rr->key); + + /* Exit the loop, we dropped something from the answer, start from the beginning */ + return 1; + } + } + + if (result == DNSSEC_NO_SIGNATURE) { + r = dns_transaction_requires_rrsig(t, rr); + if (r < 0) + return r; + if (r == 0) { + /* Data does not require signing. In that case, just copy it over, + * but remember that this is by no means authenticated.*/ + r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0); + if (r < 0) + return r; + + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); + return 1; + } + + r = dns_transaction_known_signed(t, rr); + if (r < 0) + return r; + if (r > 0) { + /* This is an RR we know has to be signed. If it isn't this means + * the server is not attaching RRSIGs, hence complain. */ + + dns_server_packet_rrsig_missing(t->server, t->current_feature_level); + + if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) { + + /* Downgrading is OK? If so, just consider the information unsigned */ + + r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0); + if (r < 0) + return r; + + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); + return 1; + } + + /* Otherwise, fail */ + t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER; + return 0; + } + + r = dns_transaction_in_private_tld(t, rr->key); + if (r < 0) + return r; + if (r > 0) { + char s[DNS_RESOURCE_KEY_STRING_MAX]; + + /* The data is from a TLD that is proven not to exist, and we are in downgrade + * mode, hence ignore the fact that this was not signed. */ + + log_info("Detected RRset %s is in a private DNS zone, permitting unsigned RRs.", + dns_resource_key_to_string(rr->key, s, sizeof s)); + + r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0); + if (r < 0) + return r; + + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); + return 1; + } + } + + if (IN_SET(result, + DNSSEC_MISSING_KEY, + DNSSEC_SIGNATURE_EXPIRED, + DNSSEC_UNSUPPORTED_ALGORITHM)) { + + r = dns_transaction_dnskey_authenticated(t, rr); + if (r < 0 && r != -ENXIO) + return r; + if (r == 0) { + /* The DNSKEY transaction was not authenticated, this means there's + * no DS for this, which means it's OK if no keys are found for this signature. */ + + r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0); + if (r < 0) + return r; + + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); + return 1; + } + } + + r = dns_transaction_is_primary_response(t, rr); + if (r < 0) + return r; + if (r > 0) { + /* Look for a matching DNAME for this CNAME */ + r = dns_answer_has_dname_for_cname(t->answer, rr); + if (r < 0) + return r; + if (r == 0) { + /* Also look among the stuff we already validated */ + r = dns_answer_has_dname_for_cname(*validated, rr); + if (r < 0) + return r; + } + + if (r == 0) { + if (IN_SET(result, + DNSSEC_INVALID, + DNSSEC_SIGNATURE_EXPIRED, + DNSSEC_NO_SIGNATURE)) + manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, rr->key); + else /* DNSSEC_MISSING_KEY or DNSSEC_UNSUPPORTED_ALGORITHM */ + manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, rr->key); + + /* This is a primary response to our question, and it failed validation. + * That's fatal. */ + t->answer_dnssec_result = result; + return 0; + } + + /* This is a primary response, but we do have a DNAME RR + * in the RR that can replay this CNAME, hence rely on + * that, and we can remove the CNAME in favour of it. */ + } + + /* This is just some auxiliary data. Just remove the RRset and continue. */ + r = dns_answer_remove_by_key(&t->answer, rr->key); + if (r < 0) + return r; + + /* We dropped something from the answer, start from the beginning. */ + return 1; + } + + return 2; /* Finito. */ +} + +int dns_transaction_validate_dnssec(DnsTransaction *t) { + _cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL; + Phase phase; + DnsAnswerFlags flags; + int r; + char key_str[DNS_RESOURCE_KEY_STRING_MAX]; + + assert(t); + + /* We have now collected all DS and DNSKEY RRs in + * t->validated_keys, let's see which RRs we can now + * authenticate with that. */ + + if (t->scope->dnssec_mode == DNSSEC_NO) + return 0; + + /* Already validated */ + if (t->answer_dnssec_result != _DNSSEC_RESULT_INVALID) + return 0; + + /* Our own stuff needs no validation */ + if (IN_SET(t->answer_source, DNS_TRANSACTION_ZONE, DNS_TRANSACTION_TRUST_ANCHOR)) { + t->answer_dnssec_result = DNSSEC_VALIDATED; + t->answer_authenticated = true; + return 0; + } + + /* Cached stuff is not affected by validation. */ + if (t->answer_source != DNS_TRANSACTION_NETWORK) + return 0; + + if (!dns_transaction_dnssec_supported_full(t)) { + /* The server does not support DNSSEC, or doesn't augment responses with RRSIGs. */ + t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER; + log_debug("Not validating response for %" PRIu16 ", server lacks DNSSEC support.", t->id); + return 0; + } + + log_debug("Validating response from transaction %" PRIu16 " (%s).", + t->id, + dns_resource_key_to_string(t->key, key_str, sizeof key_str)); + + /* First, see if this response contains any revoked trust + * anchors we care about */ + r = dns_transaction_check_revoked_trust_anchors(t); + if (r < 0) + return r; + + /* Third, copy all RRs we acquired successfully from auxiliary RRs over. */ + r = dns_transaction_copy_validated(t); + if (r < 0) + return r; + + /* Second, see if there are DNSKEYs we already know a + * validated DS for. */ + r = dns_transaction_validate_dnskey_by_ds(t); + if (r < 0) + return r; + + /* Fourth, remove all DNSKEY and DS RRs again that our trust + * anchor says are revoked. After all we might have marked + * some keys revoked above, but they might still be lingering + * in our validated_keys list. */ + r = dns_transaction_invalidate_revoked_keys(t); + if (r < 0) + return r; + + phase = DNSSEC_PHASE_DNSKEY; + for (;;) { + bool have_nsec = false; + + r = dnssec_validate_records(t, phase, &have_nsec, &validated); + if (r <= 0) + return r; + + /* Try again as long as we managed to achieve something */ + if (r == 1) + continue; + + if (phase == DNSSEC_PHASE_DNSKEY && have_nsec) { + /* OK, we processed all DNSKEYs, and there are NSEC/NSEC3 RRs, look at those now. */ + phase = DNSSEC_PHASE_NSEC; + continue; + } + + if (phase != DNSSEC_PHASE_ALL) { + /* OK, we processed all DNSKEYs and NSEC/NSEC3 RRs, look at all the rest now. + * Note that in this third phase we start to remove RRs we couldn't validate. */ + phase = DNSSEC_PHASE_ALL; + continue; + } + + /* We're done */ + break; + } + + dns_answer_unref(t->answer); + t->answer = validated; + validated = NULL; + + /* At this point the answer only contains validated + * RRsets. Now, let's see if it actually answers the question + * we asked. If so, great! If it doesn't, then see if + * NSEC/NSEC3 can prove this. */ + r = dns_transaction_has_positive_answer(t, &flags); + if (r > 0) { + /* Yes, it answers the question! */ + + if (flags & DNS_ANSWER_AUTHENTICATED) { + /* The answer is fully authenticated, yay. */ + t->answer_dnssec_result = DNSSEC_VALIDATED; + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_authenticated = true; + } else { + /* The answer is not fully authenticated. */ + t->answer_dnssec_result = DNSSEC_UNSIGNED; + t->answer_authenticated = false; + } + + } else if (r == 0) { + DnssecNsecResult nr; + bool authenticated = false; + + /* Bummer! Let's check NSEC/NSEC3 */ + r = dnssec_nsec_test(t->answer, t->key, &nr, &authenticated, &t->answer_nsec_ttl); + if (r < 0) + return r; + + switch (nr) { + + case DNSSEC_NSEC_NXDOMAIN: + /* NSEC proves the domain doesn't exist. Very good. */ + log_debug("Proved NXDOMAIN via NSEC/NSEC3 for transaction %u (%s)", t->id, key_str); + t->answer_dnssec_result = DNSSEC_VALIDATED; + t->answer_rcode = DNS_RCODE_NXDOMAIN; + t->answer_authenticated = authenticated; + + manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, t->key); + break; + + case DNSSEC_NSEC_NODATA: + /* NSEC proves that there's no data here, very good. */ + log_debug("Proved NODATA via NSEC/NSEC3 for transaction %u (%s)", t->id, key_str); + t->answer_dnssec_result = DNSSEC_VALIDATED; + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_authenticated = authenticated; + + manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, t->key); + break; + + case DNSSEC_NSEC_OPTOUT: + /* NSEC3 says the data might not be signed */ + log_debug("Data is NSEC3 opt-out via NSEC/NSEC3 for transaction %u (%s)", t->id, key_str); + t->answer_dnssec_result = DNSSEC_UNSIGNED; + t->answer_authenticated = false; + + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, t->key); + break; + + case DNSSEC_NSEC_NO_RR: + /* No NSEC data? Bummer! */ + + r = dns_transaction_requires_nsec(t); + if (r < 0) + return r; + if (r > 0) { + t->answer_dnssec_result = DNSSEC_NO_SIGNATURE; + manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, t->key); + } else { + t->answer_dnssec_result = DNSSEC_UNSIGNED; + t->answer_authenticated = false; + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, t->key); + } + + break; + + case DNSSEC_NSEC_UNSUPPORTED_ALGORITHM: + /* We don't know the NSEC3 algorithm used? */ + t->answer_dnssec_result = DNSSEC_UNSUPPORTED_ALGORITHM; + manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, t->key); + break; + + case DNSSEC_NSEC_FOUND: + case DNSSEC_NSEC_CNAME: + /* NSEC says it needs to be there, but we couldn't find it? Bummer! */ + t->answer_dnssec_result = DNSSEC_NSEC_MISMATCH; + manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, t->key); + break; + + default: + assert_not_reached("Unexpected NSEC result."); + } + } + + return 1; +} + +static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] = { + [DNS_TRANSACTION_NULL] = "null", + [DNS_TRANSACTION_PENDING] = "pending", + [DNS_TRANSACTION_VALIDATING] = "validating", + [DNS_TRANSACTION_RCODE_FAILURE] = "rcode-failure", + [DNS_TRANSACTION_SUCCESS] = "success", + [DNS_TRANSACTION_NO_SERVERS] = "no-servers", + [DNS_TRANSACTION_TIMEOUT] = "timeout", + [DNS_TRANSACTION_ATTEMPTS_MAX_REACHED] = "attempts-max-reached", + [DNS_TRANSACTION_INVALID_REPLY] = "invalid-reply", + [DNS_TRANSACTION_ERRNO] = "errno", + [DNS_TRANSACTION_ABORTED] = "aborted", + [DNS_TRANSACTION_DNSSEC_FAILED] = "dnssec-failed", + [DNS_TRANSACTION_NO_TRUST_ANCHOR] = "no-trust-anchor", + [DNS_TRANSACTION_RR_TYPE_UNSUPPORTED] = "rr-type-unsupported", + [DNS_TRANSACTION_NETWORK_DOWN] = "network-down", + [DNS_TRANSACTION_NOT_FOUND] = "not-found", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_transaction_state, DnsTransactionState); + +static const char* const dns_transaction_source_table[_DNS_TRANSACTION_SOURCE_MAX] = { + [DNS_TRANSACTION_NETWORK] = "network", + [DNS_TRANSACTION_CACHE] = "cache", + [DNS_TRANSACTION_ZONE] = "zone", + [DNS_TRANSACTION_TRUST_ANCHOR] = "trust-anchor", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_transaction_source, DnsTransactionSource); diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-transaction.h b/src/grp-resolve/systemd-resolved/resolved-dns-transaction.h new file mode 100644 index 0000000000..eaece91533 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-transaction.h @@ -0,0 +1,174 @@ +#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 . +***/ + +typedef struct DnsTransaction DnsTransaction; +typedef enum DnsTransactionState DnsTransactionState; +typedef enum DnsTransactionSource DnsTransactionSource; + +enum DnsTransactionState { + DNS_TRANSACTION_NULL, + DNS_TRANSACTION_PENDING, + DNS_TRANSACTION_VALIDATING, + DNS_TRANSACTION_RCODE_FAILURE, + DNS_TRANSACTION_SUCCESS, + DNS_TRANSACTION_NO_SERVERS, + DNS_TRANSACTION_TIMEOUT, + DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, + DNS_TRANSACTION_INVALID_REPLY, + DNS_TRANSACTION_ERRNO, + DNS_TRANSACTION_ABORTED, + DNS_TRANSACTION_DNSSEC_FAILED, + DNS_TRANSACTION_NO_TRUST_ANCHOR, + DNS_TRANSACTION_RR_TYPE_UNSUPPORTED, + DNS_TRANSACTION_NETWORK_DOWN, + DNS_TRANSACTION_NOT_FOUND, /* like NXDOMAIN, but when LLMNR/TCP connections fail */ + _DNS_TRANSACTION_STATE_MAX, + _DNS_TRANSACTION_STATE_INVALID = -1 +}; + +#define DNS_TRANSACTION_IS_LIVE(state) IN_SET((state), DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING) + +enum DnsTransactionSource { + DNS_TRANSACTION_NETWORK, + DNS_TRANSACTION_CACHE, + DNS_TRANSACTION_ZONE, + DNS_TRANSACTION_TRUST_ANCHOR, + _DNS_TRANSACTION_SOURCE_MAX, + _DNS_TRANSACTION_SOURCE_INVALID = -1 +}; + +#include "resolved-dns-answer.h" +#include "resolved-dns-packet.h" +#include "resolved-dns-question.h" +#include "resolved-dns-scope.h" + +struct DnsTransaction { + DnsScope *scope; + + DnsResourceKey *key; + + DnsTransactionState state; + + uint16_t id; + + bool tried_stream:1; + + bool initial_jitter_scheduled:1; + bool initial_jitter_elapsed:1; + + DnsPacket *sent, *received; + + DnsAnswer *answer; + int answer_rcode; + DnssecResult answer_dnssec_result; + DnsTransactionSource answer_source; + uint32_t answer_nsec_ttl; + int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */ + + /* Indicates whether the primary answer is authenticated, + * i.e. whether the RRs from answer which directly match the + * question are authenticated, or, if there are none, whether + * the NODATA or NXDOMAIN case is. It says nothing about + * additional RRs listed in the answer, however they have + * their own DNS_ANSWER_AUTHORIZED FLAGS. Note that this bit + * is defined different than the AD bit in DNS packets, as + * that covers more than just the actual primary answer. */ + bool answer_authenticated; + + /* Contains DNSKEY, DS, SOA RRs we already verified and need + * to authenticate this reply */ + DnsAnswer *validated_keys; + + usec_t start_usec; + usec_t next_attempt_after; + sd_event_source *timeout_event_source; + unsigned n_attempts; + + /* UDP connection logic, if we need it */ + int dns_udp_fd; + sd_event_source *dns_udp_event_source; + + /* TCP connection logic, if we need it */ + DnsStream *stream; + + /* The active server */ + DnsServer *server; + + /* The features of the DNS server at time of transaction start */ + DnsServerFeatureLevel current_feature_level; + + /* Query candidates this transaction is referenced by and that + * shall be notified about this specific transaction + * completing. */ + Set *notify_query_candidates, *notify_query_candidates_done; + + /* Zone items this transaction is referenced by and that shall + * be notified about completion. */ + Set *notify_zone_items, *notify_zone_items_done; + + /* Other transactions that this transactions is referenced by + * and that shall be notified about completion. This is used + * when transactions want to validate their RRsets, but need + * another DNSKEY or DS RR to do so. */ + Set *notify_transactions, *notify_transactions_done; + + /* The opposite direction: the transactions this transaction + * created in order to request DNSKEY or DS RRs. */ + Set *dnssec_transactions; + + unsigned block_gc; + + LIST_FIELDS(DnsTransaction, transactions_by_scope); +}; + +int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key); +DnsTransaction* dns_transaction_free(DnsTransaction *t); + +bool dns_transaction_gc(DnsTransaction *t); +int dns_transaction_go(DnsTransaction *t); + +void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p); +void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state); + +void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source); +int dns_transaction_validate_dnssec(DnsTransaction *t); +int dns_transaction_request_dnssec_keys(DnsTransaction *t); + +const char* dns_transaction_state_to_string(DnsTransactionState p) _const_; +DnsTransactionState dns_transaction_state_from_string(const char *s) _pure_; + +const char* dns_transaction_source_to_string(DnsTransactionSource p) _const_; +DnsTransactionSource dns_transaction_source_from_string(const char *s) _pure_; + +/* LLMNR Jitter interval, see RFC 4795 Section 7 */ +#define LLMNR_JITTER_INTERVAL_USEC (100 * USEC_PER_MSEC) + +/* mDNS Jitter interval, see RFC 6762 Section 5.2 */ +#define MDNS_JITTER_MIN_USEC (20 * USEC_PER_MSEC) +#define MDNS_JITTER_RANGE_USEC (100 * USEC_PER_MSEC) + +/* Maximum attempts to send DNS requests, across all DNS servers */ +#define DNS_TRANSACTION_ATTEMPTS_MAX 16 + +/* Maximum attempts to send LLMNR requests, see RFC 4795 Section 2.7 */ +#define LLMNR_TRANSACTION_ATTEMPTS_MAX 3 + +#define TRANSACTION_ATTEMPTS_MAX(p) ((p) == DNS_PROTOCOL_LLMNR ? LLMNR_TRANSACTION_ATTEMPTS_MAX : DNS_TRANSACTION_ATTEMPTS_MAX) diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-trust-anchor.c b/src/grp-resolve/systemd-resolved/resolved-dns-trust-anchor.c new file mode 100644 index 0000000000..77370e7dd5 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-trust-anchor.c @@ -0,0 +1,743 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 "alloc-util.h" +#include "conf-files.h" +#include "def.h" +#include "dns-domain.h" +#include "fd-util.h" +#include "fileio.h" +#include "hexdecoct.h" +#include "parse-util.h" +#include "resolved-dns-trust-anchor.h" +#include "resolved-dns-dnssec.h" +#include "set.h" +#include "string-util.h" +#include "strv.h" + +static const char trust_anchor_dirs[] = CONF_PATHS_NULSTR("dnssec-trust-anchors.d"); + +/* The DS RR from https://data.iana.org/root-anchors/root-anchors.xml, retrieved December 2015 */ +static const uint8_t root_digest[] = + { 0x49, 0xAA, 0xC1, 0x1D, 0x7B, 0x6F, 0x64, 0x46, 0x70, 0x2E, 0x54, 0xA1, 0x60, 0x73, 0x71, 0x60, + 0x7A, 0x1A, 0x41, 0x85, 0x52, 0x00, 0xFD, 0x2C, 0xE1, 0xCD, 0xDE, 0x32, 0xF2, 0x4E, 0x8F, 0xB5 }; + +static bool dns_trust_anchor_knows_domain_positive(DnsTrustAnchor *d, const char *name) { + assert(d); + + /* Returns true if there's an entry for the specified domain + * name in our trust anchor */ + + return + hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DNSKEY, name)) || + hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, name)); +} + +static int dns_trust_anchor_add_builtin_positive(DnsTrustAnchor *d) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + int r; + + assert(d); + + r = hashmap_ensure_allocated(&d->positive_by_key, &dns_resource_key_hash_ops); + if (r < 0) + return r; + + /* Only add the built-in trust anchor if there's neither a DS + * nor a DNSKEY defined for the root domain. That way users + * have an easy way to override the root domain DS/DNSKEY + * data. */ + if (dns_trust_anchor_knows_domain_positive(d, ".")) + return 0; + + /* Add the RR from https://data.iana.org/root-anchors/root-anchors.xml */ + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, ""); + if (!rr) + return -ENOMEM; + + rr->ds.key_tag = 19036; + rr->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; + rr->ds.digest_type = DNSSEC_DIGEST_SHA256; + rr->ds.digest_size = sizeof(root_digest); + rr->ds.digest = memdup(root_digest, rr->ds.digest_size); + if (!rr->ds.digest) + return -ENOMEM; + + answer = dns_answer_new(1); + if (!answer) + return -ENOMEM; + + r = dns_answer_add(answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + r = hashmap_put(d->positive_by_key, rr->key, answer); + if (r < 0) + return r; + + answer = NULL; + return 0; +} + +static int dns_trust_anchor_add_builtin_negative(DnsTrustAnchor *d) { + + static const char private_domains[] = + /* RFC 6761 says that .test is a special domain for + * testing and not to be installed in the root zone */ + "test\0" + + /* RFC 6761 says that these reverse IP lookup ranges + * are for private addresses, and hence should not + * show up in the root zone */ + "10.in-addr.arpa\0" + "16.172.in-addr.arpa\0" + "17.172.in-addr.arpa\0" + "18.172.in-addr.arpa\0" + "19.172.in-addr.arpa\0" + "20.172.in-addr.arpa\0" + "21.172.in-addr.arpa\0" + "22.172.in-addr.arpa\0" + "23.172.in-addr.arpa\0" + "24.172.in-addr.arpa\0" + "25.172.in-addr.arpa\0" + "26.172.in-addr.arpa\0" + "27.172.in-addr.arpa\0" + "28.172.in-addr.arpa\0" + "29.172.in-addr.arpa\0" + "30.172.in-addr.arpa\0" + "31.172.in-addr.arpa\0" + "168.192.in-addr.arpa\0" + + /* RFC 6762 reserves the .local domain for Multicast + * DNS, it hence cannot appear in the root zone. (Note + * that we by default do not route .local traffic to + * DNS anyway, except when a configured search domain + * suggests so.) */ + "local\0" + + /* These two are well known, popular private zone + * TLDs, that are blocked from delegation, according + * to: + * http://icannwiki.com/Name_Collision#NGPC_Resolution + * + * There's also ongoing work on making this official + * in an RRC: + * https://www.ietf.org/archive/id/draft-chapin-additional-reserved-tlds-02.txt */ + "home\0" + "corp\0" + + /* The following four TLDs are suggested for private + * zones in RFC 6762, Appendix G, and are hence very + * unlikely to be made official TLDs any day soon */ + "lan\0" + "intranet\0" + "internal\0" + "private\0"; + + const char *name; + int r; + + assert(d); + + /* Only add the built-in trust anchor if there's no negative + * trust anchor defined at all. This enables easy overriding + * of negative trust anchors. */ + + if (set_size(d->negative_by_name) > 0) + return 0; + + r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops); + if (r < 0) + return r; + + /* We add a couple of domains as default negative trust + * anchors, where it's very unlikely they will be installed in + * the root zone. If they exist they must be private, and thus + * unsigned. */ + + NULSTR_FOREACH(name, private_domains) { + + if (dns_trust_anchor_knows_domain_positive(d, name)) + continue; + + r = set_put_strdup(d->negative_by_name, name); + if (r < 0) + return r; + } + + return 0; +} + +static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, unsigned line, const char *s) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_free_ char *domain = NULL, *class = NULL, *type = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + DnsAnswer *old_answer = NULL; + const char *p = s; + int r; + + assert(d); + assert(line); + + r = extract_first_word(&p, &domain, NULL, EXTRACT_QUOTES); + if (r < 0) + return log_warning_errno(r, "Unable to parse domain in line %s:%u: %m", path, line); + + if (!dns_name_is_valid(domain)) { + log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain, path, line); + return -EINVAL; + } + + r = extract_many_words(&p, NULL, 0, &class, &type, NULL); + if (r < 0) + return log_warning_errno(r, "Unable to parse class and type in line %s:%u: %m", path, line); + if (r != 2) { + log_warning("Missing class or type in line %s:%u", path, line); + return -EINVAL; + } + + if (!strcaseeq(class, "IN")) { + log_warning("RR class %s is not supported, ignoring line %s:%u.", class, path, line); + return -EINVAL; + } + + if (strcaseeq(type, "DS")) { + _cleanup_free_ char *key_tag = NULL, *algorithm = NULL, *digest_type = NULL, *digest = NULL; + _cleanup_free_ void *dd = NULL; + uint16_t kt; + int a, dt; + size_t l; + + r = extract_many_words(&p, NULL, 0, &key_tag, &algorithm, &digest_type, &digest, NULL); + if (r < 0) { + log_warning_errno(r, "Failed to parse DS parameters on line %s:%u: %m", path, line); + return -EINVAL; + } + if (r != 4) { + log_warning("Missing DS parameters on line %s:%u", path, line); + return -EINVAL; + } + + r = safe_atou16(key_tag, &kt); + if (r < 0) + return log_warning_errno(r, "Failed to parse DS key tag %s on line %s:%u: %m", key_tag, path, line); + + a = dnssec_algorithm_from_string(algorithm); + if (a < 0) { + log_warning("Failed to parse DS algorithm %s on line %s:%u", algorithm, path, line); + return -EINVAL; + } + + dt = dnssec_digest_from_string(digest_type); + if (dt < 0) { + log_warning("Failed to parse DS digest type %s on line %s:%u", digest_type, path, line); + return -EINVAL; + } + + r = unhexmem(digest, strlen(digest), &dd, &l); + if (r < 0) { + log_warning("Failed to parse DS digest %s on line %s:%u", digest, path, line); + return -EINVAL; + } + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, domain); + if (!rr) + return log_oom(); + + rr->ds.key_tag = kt; + rr->ds.algorithm = a; + rr->ds.digest_type = dt; + rr->ds.digest_size = l; + rr->ds.digest = dd; + dd = NULL; + + } else if (strcaseeq(type, "DNSKEY")) { + _cleanup_free_ char *flags = NULL, *protocol = NULL, *algorithm = NULL, *key = NULL; + _cleanup_free_ void *k = NULL; + uint16_t f; + size_t l; + int a; + + r = extract_many_words(&p, NULL, 0, &flags, &protocol, &algorithm, &key, NULL); + if (r < 0) + return log_warning_errno(r, "Failed to parse DNSKEY parameters on line %s:%u: %m", path, line); + if (r != 4) { + log_warning("Missing DNSKEY parameters on line %s:%u", path, line); + return -EINVAL; + } + + if (!streq(protocol, "3")) { + log_warning("DNSKEY Protocol is not 3 on line %s:%u", path, line); + return -EINVAL; + } + + r = safe_atou16(flags, &f); + if (r < 0) + return log_warning_errno(r, "Failed to parse DNSKEY flags field %s on line %s:%u", flags, path, line); + if ((f & DNSKEY_FLAG_ZONE_KEY) == 0) { + log_warning("DNSKEY lacks zone key bit set on line %s:%u", path, line); + return -EINVAL; + } + if ((f & DNSKEY_FLAG_REVOKE)) { + log_warning("DNSKEY is already revoked on line %s:%u", path, line); + return -EINVAL; + } + + a = dnssec_algorithm_from_string(algorithm); + if (a < 0) { + log_warning("Failed to parse DNSKEY algorithm %s on line %s:%u", algorithm, path, line); + return -EINVAL; + } + + r = unbase64mem(key, strlen(key), &k, &l); + if (r < 0) + return log_warning_errno(r, "Failed to parse DNSKEY key data %s on line %s:%u", key, path, line); + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, domain); + if (!rr) + return log_oom(); + + rr->dnskey.flags = f; + rr->dnskey.protocol = 3; + rr->dnskey.algorithm = a; + rr->dnskey.key_size = l; + rr->dnskey.key = k; + k = NULL; + + } else { + log_warning("RR type %s is not supported, ignoring line %s:%u.", type, path, line); + return -EINVAL; + } + + if (!isempty(p)) { + log_warning("Trailing garbage on line %s:%u, ignoring line.", path, line); + return -EINVAL; + } + + r = hashmap_ensure_allocated(&d->positive_by_key, &dns_resource_key_hash_ops); + if (r < 0) + return log_oom(); + + old_answer = hashmap_get(d->positive_by_key, rr->key); + answer = dns_answer_ref(old_answer); + + r = dns_answer_add_extend(&answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return log_error_errno(r, "Failed to add trust anchor RR: %m"); + + r = hashmap_replace(d->positive_by_key, rr->key, answer); + if (r < 0) + return log_error_errno(r, "Failed to add answer to trust anchor: %m"); + + old_answer = dns_answer_unref(old_answer); + answer = NULL; + + return 0; +} + +static int dns_trust_anchor_load_negative(DnsTrustAnchor *d, const char *path, unsigned line, const char *s) { + _cleanup_free_ char *domain = NULL; + const char *p = s; + int r; + + assert(d); + assert(line); + + r = extract_first_word(&p, &domain, NULL, EXTRACT_QUOTES); + if (r < 0) + return log_warning_errno(r, "Unable to parse line %s:%u: %m", path, line); + + if (!dns_name_is_valid(domain)) { + log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain, path, line); + return -EINVAL; + } + + if (!isempty(p)) { + log_warning("Trailing garbage at line %s:%u, ignoring line.", path, line); + return -EINVAL; + } + + r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops); + if (r < 0) + return log_oom(); + + r = set_put(d->negative_by_name, domain); + if (r < 0) + return log_oom(); + if (r > 0) + domain = NULL; + + return 0; +} + +static int dns_trust_anchor_load_files( + DnsTrustAnchor *d, + const char *suffix, + int (*loader)(DnsTrustAnchor *d, const char *path, unsigned n, const char *line)) { + + _cleanup_strv_free_ char **files = NULL; + char **f; + int r; + + assert(d); + assert(suffix); + assert(loader); + + r = conf_files_list_nulstr(&files, suffix, NULL, trust_anchor_dirs); + if (r < 0) + return log_error_errno(r, "Failed to enumerate %s trust anchor files: %m", suffix); + + STRV_FOREACH(f, files) { + _cleanup_fclose_ FILE *g = NULL; + char line[LINE_MAX]; + unsigned n = 0; + + g = fopen(*f, "r"); + if (!g) { + if (errno == ENOENT) + continue; + + log_warning_errno(errno, "Failed to open %s: %m", *f); + continue; + } + + FOREACH_LINE(line, g, log_warning_errno(errno, "Failed to read %s, ignoring: %m", *f)) { + char *l; + + n++; + + l = strstrip(line); + if (isempty(l)) + continue; + + if (*l == ';') + continue; + + (void) loader(d, *f, n, l); + } + } + + return 0; +} + +static int domain_name_cmp(const void *a, const void *b) { + char **x = (char**) a, **y = (char**) b; + + return dns_name_compare_func(*x, *y); +} + +static int dns_trust_anchor_dump(DnsTrustAnchor *d) { + DnsAnswer *a; + Iterator i; + + assert(d); + + if (hashmap_isempty(d->positive_by_key)) + log_info("No positive trust anchors defined."); + else { + log_info("Positive Trust Anchors:"); + HASHMAP_FOREACH(a, d->positive_by_key, i) { + DnsResourceRecord *rr; + + DNS_ANSWER_FOREACH(rr, a) + log_info("%s", dns_resource_record_to_string(rr)); + } + } + + if (set_isempty(d->negative_by_name)) + log_info("No negative trust anchors defined."); + else { + _cleanup_free_ char **l = NULL, *j = NULL; + + l = set_get_strv(d->negative_by_name); + if (!l) + return log_oom(); + + qsort_safe(l, set_size(d->negative_by_name), sizeof(char*), domain_name_cmp); + + j = strv_join(l, " "); + if (!j) + return log_oom(); + + log_info("Negative trust anchors: %s", j); + } + + return 0; +} + +int dns_trust_anchor_load(DnsTrustAnchor *d) { + int r; + + assert(d); + + /* If loading things from disk fails, we don't consider this fatal */ + (void) dns_trust_anchor_load_files(d, ".positive", dns_trust_anchor_load_positive); + (void) dns_trust_anchor_load_files(d, ".negative", dns_trust_anchor_load_negative); + + /* However, if the built-in DS fails, then we have a problem. */ + r = dns_trust_anchor_add_builtin_positive(d); + if (r < 0) + return log_error_errno(r, "Failed to add built-in positive trust anchor: %m"); + + r = dns_trust_anchor_add_builtin_negative(d); + if (r < 0) + return log_error_errno(r, "Failed to add built-in negative trust anchor: %m"); + + dns_trust_anchor_dump(d); + + return 0; +} + +void dns_trust_anchor_flush(DnsTrustAnchor *d) { + DnsAnswer *a; + DnsResourceRecord *rr; + + assert(d); + + while ((a = hashmap_steal_first(d->positive_by_key))) + dns_answer_unref(a); + d->positive_by_key = hashmap_free(d->positive_by_key); + + while ((rr = set_steal_first(d->revoked_by_rr))) + dns_resource_record_unref(rr); + d->revoked_by_rr = set_free(d->revoked_by_rr); + + d->negative_by_name = set_free_free(d->negative_by_name); +} + +int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey *key, DnsAnswer **ret) { + DnsAnswer *a; + + assert(d); + assert(key); + assert(ret); + + /* We only serve DS and DNSKEY RRs. */ + if (!IN_SET(key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY)) + return 0; + + a = hashmap_get(d->positive_by_key, key); + if (!a) + return 0; + + *ret = dns_answer_ref(a); + return 1; +} + +int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name) { + assert(d); + assert(name); + + return set_contains(d->negative_by_name, name); +} + +static int dns_trust_anchor_revoked_put(DnsTrustAnchor *d, DnsResourceRecord *rr) { + int r; + + assert(d); + + r = set_ensure_allocated(&d->revoked_by_rr, &dns_resource_record_hash_ops); + if (r < 0) + return r; + + r = set_put(d->revoked_by_rr, rr); + if (r < 0) + return r; + if (r > 0) + dns_resource_record_ref(rr); + + return r; +} + +static int dns_trust_anchor_remove_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr) { + _cleanup_(dns_answer_unrefp) DnsAnswer *new_answer = NULL; + DnsAnswer *old_answer; + int r; + + /* Remember that this is a revoked trust anchor RR */ + r = dns_trust_anchor_revoked_put(d, rr); + if (r < 0) + return r; + + /* Remove this from the positive trust anchor */ + old_answer = hashmap_get(d->positive_by_key, rr->key); + if (!old_answer) + return 0; + + new_answer = dns_answer_ref(old_answer); + + r = dns_answer_remove_by_rr(&new_answer, rr); + if (r <= 0) + return r; + + /* We found the key! Warn the user */ + log_struct(LOG_WARNING, + LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED), + LOG_MESSAGE("DNSSEC Trust anchor %s has been revoked. Please update the trust anchor, or upgrade your operating system."), strna(dns_resource_record_to_string(rr)), + "TRUST_ANCHOR=%s", dns_resource_record_to_string(rr), + NULL); + + if (dns_answer_size(new_answer) <= 0) { + assert_se(hashmap_remove(d->positive_by_key, rr->key) == old_answer); + dns_answer_unref(old_answer); + return 1; + } + + r = hashmap_replace(d->positive_by_key, new_answer->items[0].rr->key, new_answer); + if (r < 0) + return r; + + new_answer = NULL; + dns_answer_unref(old_answer); + return 1; +} + +static int dns_trust_anchor_check_revoked_one(DnsTrustAnchor *d, DnsResourceRecord *revoked_dnskey) { + DnsAnswer *a; + int r; + + assert(d); + assert(revoked_dnskey); + assert(revoked_dnskey->key->type == DNS_TYPE_DNSKEY); + assert(revoked_dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE); + + a = hashmap_get(d->positive_by_key, revoked_dnskey->key); + if (a) { + DnsResourceRecord *anchor; + + /* First, look for the precise DNSKEY in our trust anchor database */ + + DNS_ANSWER_FOREACH(anchor, a) { + + if (anchor->dnskey.protocol != revoked_dnskey->dnskey.protocol) + continue; + + if (anchor->dnskey.algorithm != revoked_dnskey->dnskey.algorithm) + continue; + + if (anchor->dnskey.key_size != revoked_dnskey->dnskey.key_size) + continue; + + /* Note that we allow the REVOKE bit to be + * different! It will be set in the revoked + * key, but unset in our version of it */ + if (((anchor->dnskey.flags ^ revoked_dnskey->dnskey.flags) | DNSKEY_FLAG_REVOKE) != DNSKEY_FLAG_REVOKE) + continue; + + if (memcmp(anchor->dnskey.key, revoked_dnskey->dnskey.key, anchor->dnskey.key_size) != 0) + continue; + + dns_trust_anchor_remove_revoked(d, anchor); + break; + } + } + + a = hashmap_get(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(revoked_dnskey->key->class, DNS_TYPE_DS, dns_resource_key_name(revoked_dnskey->key))); + if (a) { + DnsResourceRecord *anchor; + + /* Second, look for DS RRs matching this DNSKEY in our trust anchor database */ + + DNS_ANSWER_FOREACH(anchor, a) { + + /* We set mask_revoke to true here, since our + * DS fingerprint will be the one of the + * unrevoked DNSKEY, but the one we got passed + * here has the bit set. */ + r = dnssec_verify_dnskey_by_ds(revoked_dnskey, anchor, true); + if (r < 0) + return r; + if (r == 0) + continue; + + dns_trust_anchor_remove_revoked(d, anchor); + break; + } + } + + return 0; +} + +int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsResourceRecord *dnskey, DnsAnswer *rrs) { + DnsResourceRecord *rrsig; + int r; + + assert(d); + assert(dnskey); + + /* Looks if "dnskey" is a self-signed RR that has been revoked + * and matches one of our trust anchor entries. If so, removes + * it from the trust anchor and returns > 0. */ + + if (dnskey->key->type != DNS_TYPE_DNSKEY) + return 0; + + /* Is this DNSKEY revoked? */ + if ((dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE) == 0) + return 0; + + /* Could this be interesting to us at all? If not, + * there's no point in looking for and verifying a + * self-signed RRSIG. */ + if (!dns_trust_anchor_knows_domain_positive(d, dns_resource_key_name(dnskey->key))) + return 0; + + /* Look for a self-signed RRSIG in the other rrs belonging to this DNSKEY */ + DNS_ANSWER_FOREACH(rrsig, rrs) { + DnssecResult result; + + if (rrsig->key->type != DNS_TYPE_RRSIG) + continue; + + r = dnssec_rrsig_match_dnskey(rrsig, dnskey, true); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_verify_rrset(rrs, dnskey->key, rrsig, dnskey, USEC_INFINITY, &result); + if (r < 0) + return r; + if (result != DNSSEC_VALIDATED) + continue; + + /* Bingo! This is a revoked self-signed DNSKEY. Let's + * see if this precise one exists in our trust anchor + * database, too. */ + r = dns_trust_anchor_check_revoked_one(d, dnskey); + if (r < 0) + return r; + + return 1; + } + + return 0; +} + +int dns_trust_anchor_is_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr) { + assert(d); + + if (!IN_SET(rr->key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY)) + return 0; + + return set_contains(d->revoked_by_rr, rr); +} diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-trust-anchor.h b/src/grp-resolve/systemd-resolved/resolved-dns-trust-anchor.h new file mode 100644 index 0000000000..635c75fde5 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-trust-anchor.h @@ -0,0 +1,43 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +typedef struct DnsTrustAnchor DnsTrustAnchor; + +#include "hashmap.h" +#include "resolved-dns-answer.h" +#include "resolved-dns-rr.h" + +/* This contains a fixed database mapping domain names to DS or DNSKEY records. */ + +struct DnsTrustAnchor { + Hashmap *positive_by_key; + Set *negative_by_name; + Set *revoked_by_rr; +}; + +int dns_trust_anchor_load(DnsTrustAnchor *d); +void dns_trust_anchor_flush(DnsTrustAnchor *d); + +int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey* key, DnsAnswer **answer); +int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name); + +int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsResourceRecord *dnskey, DnsAnswer *rrs); +int dns_trust_anchor_is_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr); diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-zone.c b/src/grp-resolve/systemd-resolved/resolved-dns-zone.c new file mode 100644 index 0000000000..850eed8cb8 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-zone.c @@ -0,0 +1,661 @@ +/*** + 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 "alloc-util.h" +#include "dns-domain.h" +#include "list.h" +#include "resolved-dns-packet.h" +#include "resolved-dns-zone.h" +#include "string-util.h" + +/* Never allow more than 1K entries */ +#define ZONE_MAX 1024 + +void dns_zone_item_probe_stop(DnsZoneItem *i) { + DnsTransaction *t; + assert(i); + + if (!i->probe_transaction) + return; + + t = i->probe_transaction; + i->probe_transaction = NULL; + + set_remove(t->notify_zone_items, i); + set_remove(t->notify_zone_items_done, i); + dns_transaction_gc(t); +} + +static void dns_zone_item_free(DnsZoneItem *i) { + if (!i) + return; + + dns_zone_item_probe_stop(i); + dns_resource_record_unref(i->rr); + + free(i); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free); + +static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) { + DnsZoneItem *first; + + assert(z); + + if (!i) + return; + + first = hashmap_get(z->by_key, i->rr->key); + LIST_REMOVE(by_key, first, i); + if (first) + assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0); + else + hashmap_remove(z->by_key, i->rr->key); + + first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key)); + LIST_REMOVE(by_name, first, i); + if (first) + assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0); + else + hashmap_remove(z->by_name, dns_resource_key_name(i->rr->key)); + + dns_zone_item_free(i); +} + +void dns_zone_flush(DnsZone *z) { + DnsZoneItem *i; + + assert(z); + + while ((i = hashmap_first(z->by_key))) + dns_zone_item_remove_and_free(z, i); + + assert(hashmap_size(z->by_key) == 0); + assert(hashmap_size(z->by_name) == 0); + + z->by_key = hashmap_free(z->by_key); + z->by_name = hashmap_free(z->by_name); +} + +static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) { + DnsZoneItem *i; + + assert(z); + assert(rr); + + LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key)) + if (dns_resource_record_equal(i->rr, rr) > 0) + return i; + + return NULL; +} + +void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) { + DnsZoneItem *i; + + assert(z); + assert(rr); + + i = dns_zone_get(z, rr); + if (i) + dns_zone_item_remove_and_free(z, i); +} + +static int dns_zone_init(DnsZone *z) { + int r; + + assert(z); + + r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops); + if (r < 0) + return r; + + return 0; +} + +static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) { + DnsZoneItem *first; + int r; + + first = hashmap_get(z->by_key, i->rr->key); + if (first) { + LIST_PREPEND(by_key, first, i); + assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0); + } else { + r = hashmap_put(z->by_key, i->rr->key, i); + if (r < 0) + return r; + } + + first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key)); + if (first) { + LIST_PREPEND(by_name, first, i); + assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0); + } else { + r = hashmap_put(z->by_name, dns_resource_key_name(i->rr->key), i); + if (r < 0) + return r; + } + + return 0; +} + +static int dns_zone_item_probe_start(DnsZoneItem *i) { + DnsTransaction *t; + int r; + + assert(i); + + if (i->probe_transaction) + return 0; + + t = dns_scope_find_transaction(i->scope, &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key)), false); + if (!t) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key)); + if (!key) + return -ENOMEM; + + r = dns_transaction_new(&t, i->scope, key); + if (r < 0) + return r; + } + + r = set_ensure_allocated(&t->notify_zone_items, NULL); + if (r < 0) + goto gc; + + r = set_ensure_allocated(&t->notify_zone_items_done, NULL); + if (r < 0) + goto gc; + + r = set_put(t->notify_zone_items, i); + if (r < 0) + goto gc; + + i->probe_transaction = t; + + if (t->state == DNS_TRANSACTION_NULL) { + + i->block_ready++; + r = dns_transaction_go(t); + i->block_ready--; + + if (r < 0) { + dns_zone_item_probe_stop(i); + return r; + } + } + + dns_zone_item_notify(i); + return 0; + +gc: + dns_transaction_gc(t); + return r; +} + +int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) { + _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL; + DnsZoneItem *existing; + int r; + + assert(z); + assert(s); + assert(rr); + + if (dns_class_is_pseudo(rr->key->class)) + return -EINVAL; + if (dns_type_is_pseudo(rr->key->type)) + return -EINVAL; + + existing = dns_zone_get(z, rr); + if (existing) + return 0; + + r = dns_zone_init(z); + if (r < 0) + return r; + + i = new0(DnsZoneItem, 1); + if (!i) + return -ENOMEM; + + i->scope = s; + i->rr = dns_resource_record_ref(rr); + i->probing_enabled = probe; + + r = dns_zone_link_item(z, i); + if (r < 0) + return r; + + if (probe) { + DnsZoneItem *first, *j; + bool established = false; + + /* Check if there's already an RR with the same name + * established. If so, it has been probed already, and + * we don't ned to probe again. */ + + LIST_FIND_HEAD(by_name, i, first); + LIST_FOREACH(by_name, j, first) { + if (i == j) + continue; + + if (j->state == DNS_ZONE_ITEM_ESTABLISHED) + established = true; + } + + if (established) + i->state = DNS_ZONE_ITEM_ESTABLISHED; + else { + i->state = DNS_ZONE_ITEM_PROBING; + + r = dns_zone_item_probe_start(i); + if (r < 0) { + dns_zone_item_remove_and_free(z, i); + i = NULL; + return r; + } + } + } else + i->state = DNS_ZONE_ITEM_ESTABLISHED; + + i = NULL; + return 0; +} + +int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL; + unsigned n_answer = 0; + DnsZoneItem *j, *first; + bool tentative = true, need_soa = false; + int r; + + assert(z); + assert(key); + assert(ret_answer); + + /* First iteration, count what we have */ + + if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { + bool found = false, added = false; + int k; + + /* If this is a generic match, then we have to + * go through the list by the name and look + * for everything manually */ + + first = hashmap_get(z->by_name, dns_resource_key_name(key)); + LIST_FOREACH(by_name, j, first) { + if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + found = true; + + k = dns_resource_key_match_rr(key, j->rr, NULL); + if (k < 0) + return k; + if (k > 0) { + n_answer++; + added = true; + } + + } + + if (found && !added) + need_soa = true; + + } else { + bool found = false; + + /* If this is a specific match, then look for + * the right key immediately */ + + first = hashmap_get(z->by_key, key); + LIST_FOREACH(by_key, j, first) { + if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + found = true; + n_answer++; + } + + if (!found) { + first = hashmap_get(z->by_name, dns_resource_key_name(key)); + LIST_FOREACH(by_name, j, first) { + if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + need_soa = true; + break; + } + } + } + + if (n_answer <= 0 && !need_soa) + goto return_empty; + + if (n_answer > 0) { + answer = dns_answer_new(n_answer); + if (!answer) + return -ENOMEM; + } + + if (need_soa) { + soa = dns_answer_new(1); + if (!soa) + return -ENOMEM; + } + + /* Second iteration, actually add the RRs to the answers */ + if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { + bool found = false, added = false; + int k; + + first = hashmap_get(z->by_name, dns_resource_key_name(key)); + LIST_FOREACH(by_name, j, first) { + if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + found = true; + + if (j->state != DNS_ZONE_ITEM_PROBING) + tentative = false; + + k = dns_resource_key_match_rr(key, j->rr, NULL); + if (k < 0) + return k; + if (k > 0) { + r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + added = true; + } + } + + if (found && !added) { + r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL); + if (r < 0) + return r; + } + } else { + bool found = false; + + first = hashmap_get(z->by_key, key); + LIST_FOREACH(by_key, j, first) { + if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + found = true; + + if (j->state != DNS_ZONE_ITEM_PROBING) + tentative = false; + + r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + if (!found) { + bool add_soa = false; + + first = hashmap_get(z->by_name, dns_resource_key_name(key)); + LIST_FOREACH(by_name, j, first) { + if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + if (j->state != DNS_ZONE_ITEM_PROBING) + tentative = false; + + add_soa = true; + } + + if (add_soa) { + r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL); + if (r < 0) + return r; + } + } + } + + /* If the caller sets ret_tentative to NULL, then use this as + * indication to not return tentative entries */ + + if (!ret_tentative && tentative) + goto return_empty; + + *ret_answer = answer; + answer = NULL; + + if (ret_soa) { + *ret_soa = soa; + soa = NULL; + } + + if (ret_tentative) + *ret_tentative = tentative; + + return 1; + +return_empty: + *ret_answer = NULL; + + if (ret_soa) + *ret_soa = NULL; + + if (ret_tentative) + *ret_tentative = false; + + return 0; +} + +void dns_zone_item_conflict(DnsZoneItem *i) { + assert(i); + + if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED)) + return; + + log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr))); + + dns_zone_item_probe_stop(i); + + /* Withdraw the conflict item */ + i->state = DNS_ZONE_ITEM_WITHDRAWN; + + /* Maybe change the hostname */ + if (manager_is_own_hostname(i->scope->manager, dns_resource_key_name(i->rr->key)) > 0) + manager_next_hostname(i->scope->manager); +} + +void dns_zone_item_notify(DnsZoneItem *i) { + assert(i); + assert(i->probe_transaction); + + if (i->block_ready > 0) + return; + + if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING)) + return; + + if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) { + bool we_lost = false; + + /* The probe got a successful reply. If we so far + * weren't established we just give up. If we already + * were established, and the peer has the + * lexicographically larger IP address we continue + * and defend it. */ + + if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) { + log_debug("Got a successful probe for not yet established RR, we lost."); + we_lost = true; + } else { + assert(i->probe_transaction->received); + we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0; + if (we_lost) + log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost."); + } + + if (we_lost) { + dns_zone_item_conflict(i); + return; + } + + log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost."); + } + + log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr))); + + dns_zone_item_probe_stop(i); + i->state = DNS_ZONE_ITEM_ESTABLISHED; +} + +static int dns_zone_item_verify(DnsZoneItem *i) { + int r; + + assert(i); + + if (i->state != DNS_ZONE_ITEM_ESTABLISHED) + return 0; + + log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr))); + + i->state = DNS_ZONE_ITEM_VERIFYING; + r = dns_zone_item_probe_start(i); + if (r < 0) { + log_error_errno(r, "Failed to start probing for verifying RR: %m"); + i->state = DNS_ZONE_ITEM_ESTABLISHED; + return r; + } + + return 0; +} + +int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) { + DnsZoneItem *i, *first; + int c = 0; + + assert(zone); + assert(rr); + + /* This checks whether a response RR we received from somebody + * else is one that we actually thought was uniquely ours. If + * so, we'll verify our RRs. */ + + /* No conflict if we don't have the name at all. */ + first = hashmap_get(zone->by_name, dns_resource_key_name(rr->key)); + if (!first) + return 0; + + /* No conflict if we have the exact same RR */ + if (dns_zone_get(zone, rr)) + return 0; + + /* OK, somebody else has RRs for the same name. Yuck! Let's + * start probing again */ + + LIST_FOREACH(by_name, i, first) { + if (dns_resource_record_equal(i->rr, rr)) + continue; + + dns_zone_item_verify(i); + c++; + } + + return c; +} + +int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) { + DnsZoneItem *i, *first; + int c = 0; + + assert(zone); + + /* Somebody else notified us about a possible conflict. Let's + * verify if that's true. */ + + first = hashmap_get(zone->by_name, dns_resource_key_name(key)); + if (!first) + return 0; + + LIST_FOREACH(by_name, i, first) { + dns_zone_item_verify(i); + c++; + } + + return c; +} + +void dns_zone_verify_all(DnsZone *zone) { + DnsZoneItem *i; + Iterator iterator; + + assert(zone); + + HASHMAP_FOREACH(i, zone->by_key, iterator) { + DnsZoneItem *j; + + LIST_FOREACH(by_key, j, i) + dns_zone_item_verify(j); + } +} + +void dns_zone_dump(DnsZone *zone, FILE *f) { + Iterator iterator; + DnsZoneItem *i; + + if (!zone) + return; + + if (!f) + f = stdout; + + HASHMAP_FOREACH(i, zone->by_key, iterator) { + DnsZoneItem *j; + + LIST_FOREACH(by_key, j, i) { + const char *t; + + t = dns_resource_record_to_string(j->rr); + if (!t) { + log_oom(); + continue; + } + + fputc('\t', f); + fputs(t, f); + fputc('\n', f); + } + } +} + +bool dns_zone_is_empty(DnsZone *zone) { + if (!zone) + return true; + + return hashmap_isempty(zone->by_key); +} diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-zone.h b/src/grp-resolve/systemd-resolved/resolved-dns-zone.h new file mode 100644 index 0000000000..408833c359 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-dns-zone.h @@ -0,0 +1,81 @@ +#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 "hashmap.h" + +typedef struct DnsZone { + Hashmap *by_key; + Hashmap *by_name; +} DnsZone; + +typedef struct DnsZoneItem DnsZoneItem; +typedef enum DnsZoneItemState DnsZoneItemState; + +#include "resolved-dns-answer.h" +#include "resolved-dns-question.h" +#include "resolved-dns-rr.h" +#include "resolved-dns-transaction.h" + +/* RFC 4795 Section 2.8. suggests a TTL of 30s by default */ +#define LLMNR_DEFAULT_TTL (30) + +enum DnsZoneItemState { + DNS_ZONE_ITEM_PROBING, + DNS_ZONE_ITEM_ESTABLISHED, + DNS_ZONE_ITEM_VERIFYING, + DNS_ZONE_ITEM_WITHDRAWN, +}; + +struct DnsZoneItem { + DnsScope *scope; + DnsResourceRecord *rr; + + DnsZoneItemState state; + + unsigned block_ready; + + bool probing_enabled; + + LIST_FIELDS(DnsZoneItem, by_key); + LIST_FIELDS(DnsZoneItem, by_name); + + DnsTransaction *probe_transaction; +}; + +void dns_zone_flush(DnsZone *z); + +int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe); +void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr); + +int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **answer, DnsAnswer **soa, bool *tentative); + +void dns_zone_item_conflict(DnsZoneItem *i); +void dns_zone_item_notify(DnsZoneItem *i); + +int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr); +int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key); + +void dns_zone_verify_all(DnsZone *zone); + +void dns_zone_item_probe_stop(DnsZoneItem *i); + +void dns_zone_dump(DnsZone *zone, FILE *f); +bool dns_zone_is_empty(DnsZone *zone); diff --git a/src/grp-resolve/systemd-resolved/resolved-etc-hosts.c b/src/grp-resolve/systemd-resolved/resolved-etc-hosts.c new file mode 100644 index 0000000000..40d650949d --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-etc-hosts.c @@ -0,0 +1,448 @@ +/*** + This file is part of systemd. + + Copyright 2016 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 "fd-util.h" +#include "fileio.h" +#include "hostname-util.h" +#include "resolved-etc-hosts.h" +#include "resolved-dns-synthesize.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" + +/* Recheck /etc/hosts at most once every 2s */ +#define ETC_HOSTS_RECHECK_USEC (2*USEC_PER_SEC) + +typedef struct EtcHostsItem { + int family; + union in_addr_union address; + + char **names; +} EtcHostsItem; + +typedef struct EtcHostsItemByName { + char *name; + + EtcHostsItem **items; + size_t n_items, n_allocated; +} EtcHostsItemByName; + +void manager_etc_hosts_flush(Manager *m) { + EtcHostsItem *item; + EtcHostsItemByName *bn; + + while ((item = set_steal_first(m->etc_hosts_by_address))) { + strv_free(item->names); + free(item); + } + + while ((bn = hashmap_steal_first(m->etc_hosts_by_name))) { + free(bn->name); + free(bn->items); + free(bn); + } + + m->etc_hosts_by_address = set_free(m->etc_hosts_by_address); + m->etc_hosts_by_name = hashmap_free(m->etc_hosts_by_name); + + m->etc_hosts_mtime = USEC_INFINITY; +} + +static void etc_hosts_item_hash_func(const void *p, struct siphash *state) { + const EtcHostsItem *item = p; + + siphash24_compress(&item->family, sizeof(item->family), state); + + if (item->family == AF_INET) + siphash24_compress(&item->address.in, sizeof(item->address.in), state); + else if (item->family == AF_INET6) + siphash24_compress(&item->address.in6, sizeof(item->address.in6), state); +} + +static int etc_hosts_item_compare_func(const void *a, const void *b) { + const EtcHostsItem *x = a, *y = b; + + if (x->family != y->family) + return x->family - y->family; + + if (x->family == AF_INET) + return memcmp(&x->address.in.s_addr, &y->address.in.s_addr, sizeof(struct in_addr)); + + if (x->family == AF_INET6) + return memcmp(&x->address.in6.s6_addr, &y->address.in6.s6_addr, sizeof(struct in6_addr)); + + return trivial_compare_func(a, b); +} + +static const struct hash_ops etc_hosts_item_ops = { + .hash = etc_hosts_item_hash_func, + .compare = etc_hosts_item_compare_func, +}; + +static int add_item(Manager *m, int family, const union in_addr_union *address, char **names) { + + EtcHostsItem key = { + .family = family, + .address = *address, + }; + EtcHostsItem *item; + char **n; + int r; + + assert(m); + assert(address); + + r = in_addr_is_null(family, address); + if (r < 0) + return r; + if (r > 0) + /* This is an 0.0.0.0 or :: item, which we assume means that we shall map the specified hostname to + * nothing. */ + item = NULL; + else { + /* If this is a normal address, then, simply add entry mapping it to the specified names */ + + item = set_get(m->etc_hosts_by_address, &key); + if (item) { + r = strv_extend_strv(&item->names, names, true); + if (r < 0) + return log_oom(); + } else { + + r = set_ensure_allocated(&m->etc_hosts_by_address, &etc_hosts_item_ops); + if (r < 0) + return log_oom(); + + item = new0(EtcHostsItem, 1); + if (!item) + return log_oom(); + + item->family = family; + item->address = *address; + item->names = names; + + r = set_put(m->etc_hosts_by_address, item); + if (r < 0) { + free(item); + return log_oom(); + } + } + } + + STRV_FOREACH(n, names) { + EtcHostsItemByName *bn; + + bn = hashmap_get(m->etc_hosts_by_name, *n); + if (!bn) { + r = hashmap_ensure_allocated(&m->etc_hosts_by_name, &dns_name_hash_ops); + if (r < 0) + return log_oom(); + + bn = new0(EtcHostsItemByName, 1); + if (!bn) + return log_oom(); + + bn->name = strdup(*n); + if (!bn->name) { + free(bn); + return log_oom(); + } + + r = hashmap_put(m->etc_hosts_by_name, bn->name, bn); + if (r < 0) { + free(bn->name); + free(bn); + return log_oom(); + } + } + + if (item) { + if (!GREEDY_REALLOC(bn->items, bn->n_allocated, bn->n_items+1)) + return log_oom(); + + bn->items[bn->n_items++] = item; + } + } + + return 0; +} + +static int parse_line(Manager *m, unsigned nr, const char *line) { + _cleanup_free_ char *address = NULL; + _cleanup_strv_free_ char **names = NULL; + union in_addr_union in; + bool suppressed = false; + int family, r; + + assert(m); + assert(line); + + r = extract_first_word(&line, &address, NULL, EXTRACT_RELAX); + if (r < 0) + return log_error_errno(r, "Couldn't extract address, in line /etc/hosts:%u.", nr); + if (r == 0) { + log_error("Premature end of line, in line /etc/hosts:%u.", nr); + return -EINVAL; + } + + r = in_addr_from_string_auto(address, &family, &in); + if (r < 0) + return log_error_errno(r, "Address '%s' is invalid, in line /etc/hosts:%u.", address, nr); + + for (;;) { + _cleanup_free_ char *name = NULL; + + r = extract_first_word(&line, &name, NULL, EXTRACT_RELAX); + if (r < 0) + return log_error_errno(r, "Couldn't extract host name, in line /etc/hosts:%u.", nr); + if (r == 0) + break; + + r = dns_name_is_valid(name); + if (r <= 0) + return log_error_errno(r, "Hostname %s is not valid, ignoring, in line /etc/hosts:%u.", name, nr); + + if (is_localhost(name)) { + /* Suppress the "localhost" line that is often seen */ + suppressed = true; + continue; + } + + r = strv_push(&names, name); + if (r < 0) + return log_oom(); + + name = NULL; + } + + if (strv_isempty(names)) { + + if (suppressed) + return 0; + + log_error("Line is missing any host names, in line /etc/hosts:%u.", nr); + return -EINVAL; + } + + /* Takes possession of the names strv */ + r = add_item(m, family, &in, names); + if (r < 0) + return r; + + names = NULL; + return r; +} + +int manager_etc_hosts_read(Manager *m) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + struct stat st; + usec_t ts; + unsigned nr = 0; + int r; + + assert_se(sd_event_now(m->event, clock_boottime_or_monotonic(), &ts) >= 0); + + /* See if we checked /etc/hosts recently already */ + if (m->etc_hosts_last != USEC_INFINITY && m->etc_hosts_last + ETC_HOSTS_RECHECK_USEC > ts) + return 0; + + m->etc_hosts_last = ts; + + if (m->etc_hosts_mtime != USEC_INFINITY) { + if (stat("/etc/hosts", &st) < 0) { + if (errno == ENOENT) { + r = 0; + goto clear; + } + + return log_error_errno(errno, "Failed to stat /etc/hosts: %m"); + } + + /* Did the mtime change? If not, there's no point in re-reading the file. */ + if (timespec_load(&st.st_mtim) == m->etc_hosts_mtime) + return 0; + } + + f = fopen("/etc/hosts", "re"); + if (!f) { + if (errno == ENOENT) { + r = 0; + goto clear; + } + + return log_error_errno(errno, "Failed to open /etc/hosts: %m"); + } + + /* Take the timestamp at the beginning of processing, so that any changes made later are read on the next + * invocation */ + r = fstat(fileno(f), &st); + if (r < 0) + return log_error_errno(errno, "Failed to fstat() /etc/hosts: %m"); + + manager_etc_hosts_flush(m); + + FOREACH_LINE(line, f, return log_error_errno(errno, "Failed to read /etc/hosts: %m")) { + char *l; + + nr++; + + l = strstrip(line); + if (isempty(l)) + continue; + if (l[0] == '#') + continue; + + r = parse_line(m, nr, l); + if (r == -ENOMEM) /* On OOM we abandon the half-built-up structure. All other errors we ignore and proceed */ + goto clear; + } + + m->etc_hosts_mtime = timespec_load(&st.st_mtim); + m->etc_hosts_last = ts; + + return 1; + +clear: + manager_etc_hosts_flush(m); + return r; +} + +int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer) { + bool found_a = false, found_aaaa = false; + EtcHostsItemByName *bn; + EtcHostsItem k = {}; + DnsResourceKey *t; + const char *name; + unsigned i; + int r; + + assert(m); + assert(q); + assert(answer); + + r = manager_etc_hosts_read(m); + if (r < 0) + return r; + + name = dns_question_first_name(q); + if (!name) + return 0; + + r = dns_name_address(name, &k.family, &k.address); + if (r > 0) { + EtcHostsItem *item; + DnsResourceKey *found_ptr = NULL; + + item = set_get(m->etc_hosts_by_address, &k); + if (!item) + return 0; + + /* We have an address in /etc/hosts that matches the queried name. Let's return successful. Actual data + * we'll only return if the request was for PTR. */ + + DNS_QUESTION_FOREACH(t, q) { + if (!IN_SET(t->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) + continue; + if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY)) + continue; + + r = dns_name_equal(dns_resource_key_name(t), name); + if (r < 0) + return r; + if (r > 0) { + found_ptr = t; + break; + } + } + + if (found_ptr) { + char **n; + + r = dns_answer_reserve(answer, strv_length(item->names)); + if (r < 0) + return r; + + STRV_FOREACH(n, item->names) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new(found_ptr); + if (!rr) + return -ENOMEM; + + rr->ptr.name = strdup(*n); + if (!rr->ptr.name) + return -ENOMEM; + + r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + } + + return 1; + } + + bn = hashmap_get(m->etc_hosts_by_name, name); + if (!bn) + return 0; + + r = dns_answer_reserve(answer, bn->n_items); + if (r < 0) + return r; + + DNS_QUESTION_FOREACH(t, q) { + if (!IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_TYPE_ANY)) + continue; + if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY)) + continue; + + r = dns_name_equal(dns_resource_key_name(t), name); + if (r < 0) + return r; + if (r == 0) + continue; + + if (IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_ANY)) + found_a = true; + if (IN_SET(t->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) + found_aaaa = true; + + if (found_a && found_aaaa) + break; + } + + for (i = 0; i < bn->n_items; i++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + if ((found_a && bn->items[i]->family != AF_INET) && + (found_aaaa && bn->items[i]->family != AF_INET6)) + continue; + + r = dns_resource_record_new_address(&rr, bn->items[i]->family, &bn->items[i]->address, bn->name); + if (r < 0) + return r; + + r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 1; +} diff --git a/src/grp-resolve/systemd-resolved/resolved-etc-hosts.h b/src/grp-resolve/systemd-resolved/resolved-etc-hosts.h new file mode 100644 index 0000000000..9d5a175f18 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-etc-hosts.h @@ -0,0 +1,28 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 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 "resolved-manager.h" +#include "resolved-dns-question.h" +#include "resolved-dns-answer.h" + +void manager_etc_hosts_flush(Manager *m); +int manager_etc_hosts_read(Manager *m); +int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer); diff --git a/src/grp-resolve/systemd-resolved/resolved-gperf.gperf b/src/grp-resolve/systemd-resolved/resolved-gperf.gperf new file mode 100644 index 0000000000..82f26215df --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-gperf.gperf @@ -0,0 +1,21 @@ +%{ +#include +#include "conf-parser.h" +#include "resolved-conf.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name resolved_gperf_hash +%define lookup-function-name resolved_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0 +Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0 +Resolve.Domains, config_parse_search_domains, 0, 0 +Resolve.LLMNR, config_parse_resolve_support, 0, offsetof(Manager, llmnr_support) +Resolve.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode) diff --git a/src/grp-resolve/systemd-resolved/resolved-link-bus.c b/src/grp-resolve/systemd-resolved/resolved-link-bus.c new file mode 100644 index 0000000000..7f21891819 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-link-bus.c @@ -0,0 +1,550 @@ +/*** + This file is part of systemd. + + Copyright 2016 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 "alloc-util.h" +#include "bus-util.h" +#include "parse-util.h" +#include "resolve-util.h" +#include "resolved-bus.h" +#include "resolved-link-bus.h" +#include "strv.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_resolve_support, resolve_support, ResolveSupport); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_dnssec_mode, dnssec_mode, DnssecMode); + +static int property_get_dns( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Link *l = userdata; + DnsServer *s; + int r; + + assert(reply); + assert(l); + + r = sd_bus_message_open_container(reply, 'a', "(iay)"); + if (r < 0) + return r; + + LIST_FOREACH(servers, s, l->dns_servers) { + r = bus_dns_server_append(reply, s, false); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_domains( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Link *l = userdata; + DnsSearchDomain *d; + int r; + + assert(reply); + assert(l); + + r = sd_bus_message_open_container(reply, 'a', "(sb)"); + if (r < 0) + return r; + + LIST_FOREACH(domains, d, l->search_domains) { + r = sd_bus_message_append(reply, "(sb)", d->name, d->route_only); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_scopes_mask( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Link *l = userdata; + uint64_t mask; + + assert(reply); + assert(l); + + mask = (l->unicast_scope ? SD_RESOLVED_DNS : 0) | + (l->llmnr_ipv4_scope ? SD_RESOLVED_LLMNR_IPV4 : 0) | + (l->llmnr_ipv6_scope ? SD_RESOLVED_LLMNR_IPV6 : 0) | + (l->mdns_ipv4_scope ? SD_RESOLVED_MDNS_IPV4 : 0) | + (l->mdns_ipv6_scope ? SD_RESOLVED_MDNS_IPV6 : 0); + + return sd_bus_message_append(reply, "t", mask); +} + +static int property_get_ntas( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Link *l = userdata; + const char *name; + Iterator i; + int r; + + assert(reply); + assert(l); + + r = sd_bus_message_open_container(reply, 'a', "s"); + if (r < 0) + return r; + + SET_FOREACH(name, l->dnssec_negative_trust_anchors, i) { + r = sd_bus_message_append(reply, "s", name); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_dnssec_supported( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Link *l = userdata; + + assert(reply); + assert(l); + + return sd_bus_message_append(reply, "b", link_dnssec_supported(l)); +} + +int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ struct in_addr_data *dns = NULL; + size_t allocated = 0, n = 0; + Link *l = userdata; + unsigned i; + int r; + + assert(message); + assert(l); + + r = sd_bus_message_enter_container(message, 'a', "(iay)"); + if (r < 0) + return r; + + for (;;) { + int family; + size_t sz; + const void *d; + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_enter_container(message, 'r', "iay"); + if (r < 0) + return r; + if (r == 0) + break; + + r = sd_bus_message_read(message, "i", &family); + if (r < 0) + return r; + + if (!IN_SET(family, AF_INET, AF_INET6)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); + + r = sd_bus_message_read_array(message, 'y', &d, &sz); + if (r < 0) + return r; + if (sz != FAMILY_ADDRESS_SIZE(family)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid address size"); + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (!GREEDY_REALLOC(dns, allocated, n+1)) + return -ENOMEM; + + dns[n].family = family; + memcpy(&dns[n].address, d, sz); + n++; + } + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + dns_server_mark_all(l->dns_servers); + + for (i = 0; i < n; i++) { + DnsServer *s; + + s = dns_server_find(l->dns_servers, dns[i].family, &dns[i].address); + if (s) + dns_server_move_back_and_unmark(s); + else { + r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, dns[i].family, &dns[i].address); + if (r < 0) + goto clear; + } + + } + + dns_server_unlink_marked(l->dns_servers); + link_allocate_scopes(l); + + return sd_bus_reply_method_return(message, NULL); + +clear: + dns_server_unlink_all(l->dns_servers); + return r; +} + +int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + int r; + + assert(message); + assert(l); + + r = sd_bus_message_enter_container(message, 'a', "(sb)"); + if (r < 0) + return r; + + for (;;) { + const char *name; + int route_only; + + r = sd_bus_message_read(message, "(sb)", &name, &route_only); + if (r < 0) + return r; + if (r == 0) + break; + + r = dns_name_is_valid(name); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid search domain %s", name); + if (!route_only && dns_name_is_root(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Root domain is not suitable as search domain"); + } + + dns_search_domain_mark_all(l->search_domains); + + r = sd_bus_message_rewind(message, false); + if (r < 0) + return r; + + for (;;) { + DnsSearchDomain *d; + const char *name; + int route_only; + + r = sd_bus_message_read(message, "(sb)", &name, &route_only); + if (r < 0) + goto clear; + if (r == 0) + break; + + r = dns_search_domain_find(l->search_domains, name, &d); + if (r < 0) + goto clear; + + if (r > 0) + dns_search_domain_move_back_and_unmark(d); + else { + r = dns_search_domain_new(l->manager, &d, DNS_SEARCH_DOMAIN_LINK, l, name); + if (r < 0) + goto clear; + } + + d->route_only = route_only; + } + + r = sd_bus_message_exit_container(message); + if (r < 0) + goto clear; + + dns_search_domain_unlink_marked(l->search_domains); + return sd_bus_reply_method_return(message, NULL); + +clear: + dns_search_domain_unlink_all(l->search_domains); + return r; +} + +int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + ResolveSupport mode; + const char *llmnr; + int r; + + assert(message); + assert(l); + + r = sd_bus_message_read(message, "s", &llmnr); + if (r < 0) + return r; + + if (isempty(llmnr)) + mode = RESOLVE_SUPPORT_YES; + else { + mode = resolve_support_from_string(llmnr); + if (mode < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid LLMNR setting: %s", llmnr); + } + + l->llmnr_support = mode; + link_allocate_scopes(l); + link_add_rrs(l, false); + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + ResolveSupport mode; + const char *mdns; + int r; + + assert(message); + assert(l); + + r = sd_bus_message_read(message, "s", &mdns); + if (r < 0) + return r; + + if (isempty(mdns)) + mode = RESOLVE_SUPPORT_NO; + else { + mode = resolve_support_from_string(mdns); + if (mode < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid MulticastDNS setting: %s", mdns); + } + + l->mdns_support = mode; + link_allocate_scopes(l); + link_add_rrs(l, false); + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + const char *dnssec; + DnssecMode mode; + int r; + + assert(message); + assert(l); + + r = sd_bus_message_read(message, "s", &dnssec); + if (r < 0) + return r; + + if (isempty(dnssec)) + mode = _DNSSEC_MODE_INVALID; + else { + mode = dnssec_mode_from_string(dnssec); + if (mode < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNSSEC setting: %s", dnssec); + } + + link_set_dnssec_mode(l, mode); + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_set_free_free_ Set *ns = NULL; + _cleanup_free_ char **ntas = NULL; + Link *l = userdata; + int r; + char **i; + + assert(message); + assert(l); + + r = sd_bus_message_read_strv(message, &ntas); + if (r < 0) + return r; + + STRV_FOREACH(i, ntas) { + r = dns_name_is_valid(*i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid negative trust anchor domain: %s", *i); + } + + ns = set_new(&dns_name_hash_ops); + if (!ns) + return -ENOMEM; + + STRV_FOREACH(i, ntas) { + r = set_put_strdup(ns, *i); + if (r < 0) + return r; + } + + set_free_free(l->dnssec_negative_trust_anchors); + l->dnssec_negative_trust_anchors = ns; + ns = NULL; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_revert(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + + assert(message); + assert(l); + + link_flush_settings(l); + link_allocate_scopes(l); + link_add_rrs(l, false); + + return sd_bus_reply_method_return(message, NULL); +} + +const sd_bus_vtable link_vtable[] = { + SD_BUS_VTABLE_START(0), + + SD_BUS_PROPERTY("ScopesMask", "t", property_get_scopes_mask, 0, 0), + SD_BUS_PROPERTY("DNS", "a(iay)", property_get_dns, 0, 0), + SD_BUS_PROPERTY("Domains", "a(sb)", property_get_domains, 0, 0), + SD_BUS_PROPERTY("LLMNR", "s", property_get_resolve_support, offsetof(Link, llmnr_support), 0), + SD_BUS_PROPERTY("MulticastDNS", "s", property_get_resolve_support, offsetof(Link, mdns_support), 0), + SD_BUS_PROPERTY("DNSSEC", "s", property_get_dnssec_mode, offsetof(Link, dnssec_mode), 0), + SD_BUS_PROPERTY("DNSSECNegativeTrustAnchors", "as", property_get_ntas, 0, 0), + SD_BUS_PROPERTY("DNSSECSupported", "b", property_get_dnssec_supported, 0, 0), + + SD_BUS_METHOD("SetDNS", "a(iay)", NULL, bus_link_method_set_dns_servers, 0), + SD_BUS_METHOD("SetDomains", "a(sb)", NULL, bus_link_method_set_domains, 0), + SD_BUS_METHOD("SetLLMNR", "s", NULL, bus_link_method_set_llmnr, 0), + SD_BUS_METHOD("SetMulticastDNS", "s", NULL, bus_link_method_set_mdns, 0), + SD_BUS_METHOD("SetDNSSEC", "s", NULL, bus_link_method_set_dnssec, 0), + SD_BUS_METHOD("SetDNSSECNegativeTrustAnchors", "as", NULL, bus_link_method_set_dnssec_negative_trust_anchors, 0), + SD_BUS_METHOD("Revert", NULL, NULL, bus_link_method_revert, 0), + + SD_BUS_VTABLE_END +}; + +int link_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + _cleanup_free_ char *e = NULL; + Manager *m = userdata; + int ifindex; + Link *link; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + r = sd_bus_path_decode(path, "/org/freedesktop/resolve1/link", &e); + if (r <= 0) + return 0; + + r = parse_ifindex(e, &ifindex); + if (r < 0) + return 0; + + link = hashmap_get(m->links, INT_TO_PTR(ifindex)); + if (!link) + return 0; + + *found = link; + return 1; +} + +char *link_bus_path(Link *link) { + _cleanup_free_ char *ifindex = NULL; + char *p; + int r; + + assert(link); + + if (asprintf(&ifindex, "%i", link->ifindex) < 0) + return NULL; + + r = sd_bus_path_encode("/org/freedesktop/resolve1/link", ifindex, &p); + if (r < 0) + return NULL; + + return p; +} + +int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + _cleanup_strv_free_ char **l = NULL; + Manager *m = userdata; + Link *link; + Iterator i; + unsigned c = 0; + + assert(bus); + assert(path); + assert(m); + assert(nodes); + + l = new0(char*, hashmap_size(m->links) + 1); + if (!l) + return -ENOMEM; + + HASHMAP_FOREACH(link, m->links, i) { + char *p; + + p = link_bus_path(link); + if (!p) + return -ENOMEM; + + l[c++] = p; + } + + l[c] = NULL; + *nodes = l; + l = NULL; + + return 1; +} diff --git a/src/grp-resolve/systemd-resolved/resolved-link-bus.h b/src/grp-resolve/systemd-resolved/resolved-link-bus.h new file mode 100644 index 0000000000..b1ac57961d --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-link-bus.h @@ -0,0 +1,38 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 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 "resolved-link.h" + +extern const sd_bus_vtable link_vtable[]; + +int link_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); +char *link_bus_path(Link *link); +int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); + +int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_revert(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/grp-resolve/systemd-resolved/resolved-link.c b/src/grp-resolve/systemd-resolved/resolved-link.c new file mode 100644 index 0000000000..4eef20599a --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-link.c @@ -0,0 +1,840 @@ +/*** + 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 + +#include "alloc-util.h" +#include "missing.h" +#include "parse-util.h" +#include "resolved-link.h" +#include "string-util.h" +#include "strv.h" + +int link_new(Manager *m, Link **ret, int ifindex) { + _cleanup_(link_freep) Link *l = NULL; + int r; + + assert(m); + assert(ifindex > 0); + + r = hashmap_ensure_allocated(&m->links, NULL); + if (r < 0) + return r; + + l = new0(Link, 1); + if (!l) + return -ENOMEM; + + l->ifindex = ifindex; + l->llmnr_support = RESOLVE_SUPPORT_YES; + l->mdns_support = RESOLVE_SUPPORT_NO; + l->dnssec_mode = _DNSSEC_MODE_INVALID; + l->operstate = IF_OPER_UNKNOWN; + + r = hashmap_put(m->links, INT_TO_PTR(ifindex), l); + if (r < 0) + return r; + + l->manager = m; + + if (ret) + *ret = l; + l = NULL; + + return 0; +} + +void link_flush_settings(Link *l) { + assert(l); + + l->llmnr_support = RESOLVE_SUPPORT_YES; + l->mdns_support = RESOLVE_SUPPORT_NO; + l->dnssec_mode = _DNSSEC_MODE_INVALID; + + dns_server_unlink_all(l->dns_servers); + dns_search_domain_unlink_all(l->search_domains); + + l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors); +} + +Link *link_free(Link *l) { + if (!l) + return NULL; + + link_flush_settings(l); + + while (l->addresses) + (void) link_address_free(l->addresses); + + if (l->manager) + hashmap_remove(l->manager->links, INT_TO_PTR(l->ifindex)); + + dns_scope_free(l->unicast_scope); + dns_scope_free(l->llmnr_ipv4_scope); + dns_scope_free(l->llmnr_ipv6_scope); + dns_scope_free(l->mdns_ipv4_scope); + dns_scope_free(l->mdns_ipv6_scope); + + free(l); + return NULL; +} + +void link_allocate_scopes(Link *l) { + int r; + + assert(l); + + if (link_relevant(l, AF_UNSPEC, false) && + l->dns_servers) { + if (!l->unicast_scope) { + r = dns_scope_new(l->manager, &l->unicast_scope, l, DNS_PROTOCOL_DNS, AF_UNSPEC); + if (r < 0) + log_warning_errno(r, "Failed to allocate DNS scope: %m"); + } + } else + l->unicast_scope = dns_scope_free(l->unicast_scope); + + if (link_relevant(l, AF_INET, true) && + l->llmnr_support != RESOLVE_SUPPORT_NO && + l->manager->llmnr_support != RESOLVE_SUPPORT_NO) { + if (!l->llmnr_ipv4_scope) { + r = dns_scope_new(l->manager, &l->llmnr_ipv4_scope, l, DNS_PROTOCOL_LLMNR, AF_INET); + if (r < 0) + log_warning_errno(r, "Failed to allocate LLMNR IPv4 scope: %m"); + } + } else + l->llmnr_ipv4_scope = dns_scope_free(l->llmnr_ipv4_scope); + + if (link_relevant(l, AF_INET6, true) && + l->llmnr_support != RESOLVE_SUPPORT_NO && + l->manager->llmnr_support != RESOLVE_SUPPORT_NO && + socket_ipv6_is_supported()) { + if (!l->llmnr_ipv6_scope) { + r = dns_scope_new(l->manager, &l->llmnr_ipv6_scope, l, DNS_PROTOCOL_LLMNR, AF_INET6); + if (r < 0) + log_warning_errno(r, "Failed to allocate LLMNR IPv6 scope: %m"); + } + } else + l->llmnr_ipv6_scope = dns_scope_free(l->llmnr_ipv6_scope); + + if (link_relevant(l, AF_INET, true) && + l->mdns_support != RESOLVE_SUPPORT_NO && + l->manager->mdns_support != RESOLVE_SUPPORT_NO) { + if (!l->mdns_ipv4_scope) { + r = dns_scope_new(l->manager, &l->mdns_ipv4_scope, l, DNS_PROTOCOL_MDNS, AF_INET); + if (r < 0) + log_warning_errno(r, "Failed to allocate mDNS IPv4 scope: %m"); + } + } else + l->mdns_ipv4_scope = dns_scope_free(l->mdns_ipv4_scope); + + if (link_relevant(l, AF_INET6, true) && + l->mdns_support != RESOLVE_SUPPORT_NO && + l->manager->mdns_support != RESOLVE_SUPPORT_NO) { + if (!l->mdns_ipv6_scope) { + r = dns_scope_new(l->manager, &l->mdns_ipv6_scope, l, DNS_PROTOCOL_MDNS, AF_INET6); + if (r < 0) + log_warning_errno(r, "Failed to allocate mDNS IPv6 scope: %m"); + } + } else + l->mdns_ipv6_scope = dns_scope_free(l->mdns_ipv6_scope); +} + +void link_add_rrs(Link *l, bool force_remove) { + LinkAddress *a; + + LIST_FOREACH(addresses, a, l->addresses) + link_address_add_rrs(a, force_remove); +} + +int link_update_rtnl(Link *l, sd_netlink_message *m) { + const char *n = NULL; + int r; + + assert(l); + assert(m); + + r = sd_rtnl_message_link_get_flags(m, &l->flags); + if (r < 0) + return r; + + (void) sd_netlink_message_read_u32(m, IFLA_MTU, &l->mtu); + (void) sd_netlink_message_read_u8(m, IFLA_OPERSTATE, &l->operstate); + + if (sd_netlink_message_read_string(m, IFLA_IFNAME, &n) >= 0) { + strncpy(l->name, n, sizeof(l->name)-1); + char_array_0(l->name); + } + + link_allocate_scopes(l); + link_add_rrs(l, false); + + return 0; +} + +static int link_update_dns_servers(Link *l) { + _cleanup_strv_free_ char **nameservers = NULL; + char **nameserver; + int r; + + assert(l); + + r = sd_network_link_get_dns(l->ifindex, &nameservers); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + dns_server_mark_all(l->dns_servers); + + STRV_FOREACH(nameserver, nameservers) { + union in_addr_union a; + DnsServer *s; + int family; + + r = in_addr_from_string_auto(*nameserver, &family, &a); + if (r < 0) + goto clear; + + s = dns_server_find(l->dns_servers, family, &a); + if (s) + dns_server_move_back_and_unmark(s); + else { + r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, family, &a); + if (r < 0) + goto clear; + } + } + + dns_server_unlink_marked(l->dns_servers); + return 0; + +clear: + dns_server_unlink_all(l->dns_servers); + return r; +} + +static int link_update_llmnr_support(Link *l) { + _cleanup_free_ char *b = NULL; + int r; + + assert(l); + + r = sd_network_link_get_llmnr(l->ifindex, &b); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + l->llmnr_support = resolve_support_from_string(b); + if (l->llmnr_support < 0) { + r = -EINVAL; + goto clear; + } + + return 0; + +clear: + l->llmnr_support = RESOLVE_SUPPORT_YES; + return r; +} + +static int link_update_mdns_support(Link *l) { + _cleanup_free_ char *b = NULL; + int r; + + assert(l); + + r = sd_network_link_get_mdns(l->ifindex, &b); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + l->mdns_support = resolve_support_from_string(b); + if (l->mdns_support < 0) { + r = -EINVAL; + goto clear; + } + + return 0; + +clear: + l->mdns_support = RESOLVE_SUPPORT_NO; + return r; +} + +void link_set_dnssec_mode(Link *l, DnssecMode mode) { + + assert(l); + + if (l->dnssec_mode == mode) + return; + + if ((l->dnssec_mode == _DNSSEC_MODE_INVALID) || + (l->dnssec_mode == DNSSEC_NO && mode != DNSSEC_NO) || + (l->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE && mode == DNSSEC_YES)) { + + /* When switching from non-DNSSEC mode to DNSSEC mode, flush the cache. Also when switching from the + * allow-downgrade mode to full DNSSEC mode, flush it too. */ + if (l->unicast_scope) + dns_cache_flush(&l->unicast_scope->cache); + } + + l->dnssec_mode = mode; +} + +static int link_update_dnssec_mode(Link *l) { + _cleanup_free_ char *m = NULL; + DnssecMode mode; + int r; + + assert(l); + + r = sd_network_link_get_dnssec(l->ifindex, &m); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + mode = dnssec_mode_from_string(m); + if (mode < 0) { + r = -EINVAL; + goto clear; + } + + link_set_dnssec_mode(l, mode); + + return 0; + +clear: + l->dnssec_mode = _DNSSEC_MODE_INVALID; + return r; +} + +static int link_update_dnssec_negative_trust_anchors(Link *l) { + _cleanup_strv_free_ char **ntas = NULL; + _cleanup_set_free_free_ Set *ns = NULL; + char **i; + int r; + + assert(l); + + r = sd_network_link_get_dnssec_negative_trust_anchors(l->ifindex, &ntas); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + ns = set_new(&dns_name_hash_ops); + if (!ns) + return -ENOMEM; + + STRV_FOREACH(i, ntas) { + r = set_put_strdup(ns, *i); + if (r < 0) + return r; + } + + set_free_free(l->dnssec_negative_trust_anchors); + l->dnssec_negative_trust_anchors = ns; + ns = NULL; + + return 0; + +clear: + l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors); + return r; +} + +static int link_update_search_domain_one(Link *l, const char *name, bool route_only) { + DnsSearchDomain *d; + int r; + + r = dns_search_domain_find(l->search_domains, name, &d); + if (r < 0) + return r; + if (r > 0) + dns_search_domain_move_back_and_unmark(d); + else { + r = dns_search_domain_new(l->manager, &d, DNS_SEARCH_DOMAIN_LINK, l, name); + if (r < 0) + return r; + } + + d->route_only = route_only; + return 0; +} + +static int link_update_search_domains(Link *l) { + _cleanup_strv_free_ char **sdomains = NULL, **rdomains = NULL; + char **i; + int r, q; + + assert(l); + + r = sd_network_link_get_search_domains(l->ifindex, &sdomains); + if (r < 0 && r != -ENODATA) + goto clear; + + q = sd_network_link_get_route_domains(l->ifindex, &rdomains); + if (q < 0 && q != -ENODATA) { + r = q; + goto clear; + } + + if (r == -ENODATA && q == -ENODATA) { + /* networkd knows nothing about this interface, and that's fine. */ + r = 0; + goto clear; + } + + dns_search_domain_mark_all(l->search_domains); + + STRV_FOREACH(i, sdomains) { + r = link_update_search_domain_one(l, *i, false); + if (r < 0) + goto clear; + } + + STRV_FOREACH(i, rdomains) { + r = link_update_search_domain_one(l, *i, true); + if (r < 0) + goto clear; + } + + dns_search_domain_unlink_marked(l->search_domains); + return 0; + +clear: + dns_search_domain_unlink_all(l->search_domains); + return r; +} + +static int link_is_unmanaged(Link *l) { + _cleanup_free_ char *state = NULL; + int r; + + assert(l); + + r = sd_network_link_get_setup_state(l->ifindex, &state); + if (r == -ENODATA) + return 1; + if (r < 0) + return r; + + return STR_IN_SET(state, "pending", "unmanaged"); +} + +static void link_read_settings(Link *l) { + int r; + + assert(l); + + /* Read settings from networkd, except when networkd is not managing this interface. */ + + r = link_is_unmanaged(l); + if (r < 0) { + log_warning_errno(r, "Failed to determine whether interface %s is managed: %m", l->name); + return; + } + if (r > 0) { + + /* If this link used to be managed, but is now unmanaged, flush all our settings — but only once. */ + if (l->is_managed) + link_flush_settings(l); + + l->is_managed = false; + return; + } + + l->is_managed = true; + + r = link_update_dns_servers(l); + if (r < 0) + log_warning_errno(r, "Failed to read DNS servers for interface %s, ignoring: %m", l->name); + + r = link_update_llmnr_support(l); + if (r < 0) + log_warning_errno(r, "Failed to read LLMNR support for interface %s, ignoring: %m", l->name); + + r = link_update_mdns_support(l); + if (r < 0) + log_warning_errno(r, "Failed to read mDNS support for interface %s, ignoring: %m", l->name); + + r = link_update_dnssec_mode(l); + if (r < 0) + log_warning_errno(r, "Failed to read DNSSEC mode for interface %s, ignoring: %m", l->name); + + r = link_update_dnssec_negative_trust_anchors(l); + if (r < 0) + log_warning_errno(r, "Failed to read DNSSEC negative trust anchors for interface %s, ignoring: %m", l->name); + + r = link_update_search_domains(l); + if (r < 0) + log_warning_errno(r, "Failed to read search domains for interface %s, ignoring: %m", l->name); +} + +int link_update_monitor(Link *l) { + assert(l); + + link_read_settings(l); + link_allocate_scopes(l); + link_add_rrs(l, false); + + return 0; +} + +bool link_relevant(Link *l, int family, bool local_multicast) { + _cleanup_free_ char *state = NULL; + LinkAddress *a; + + assert(l); + + /* A link is relevant for local multicast traffic if it isn't a loopback or pointopoint device, has a link + * beat, can do multicast and has at least one link-local (or better) IP address. + * + * A link is relevant for non-multicast traffic if it isn't a loopback device, has a link beat, and has at + * least one routable address.*/ + + if (l->flags & (IFF_LOOPBACK|IFF_DORMANT)) + return false; + + if ((l->flags & (IFF_UP|IFF_LOWER_UP)) != (IFF_UP|IFF_LOWER_UP)) + return false; + + if (local_multicast) { + if (l->flags & IFF_POINTOPOINT) + return false; + + if ((l->flags & IFF_MULTICAST) != IFF_MULTICAST) + return false; + } + + /* Check kernel operstate + * https://www.kernel.org/doc/Documentation/networking/operstates.txt */ + if (!IN_SET(l->operstate, IF_OPER_UNKNOWN, IF_OPER_UP)) + return false; + + (void) sd_network_link_get_operational_state(l->ifindex, &state); + if (state && !STR_IN_SET(state, "unknown", "degraded", "routable")) + return false; + + LIST_FOREACH(addresses, a, l->addresses) + if ((family == AF_UNSPEC || a->family == family) && link_address_relevant(a, local_multicast)) + return true; + + return false; +} + +LinkAddress *link_find_address(Link *l, int family, const union in_addr_union *in_addr) { + LinkAddress *a; + + assert(l); + + LIST_FOREACH(addresses, a, l->addresses) + if (a->family == family && in_addr_equal(family, &a->in_addr, in_addr)) + return a; + + return NULL; +} + +DnsServer* link_set_dns_server(Link *l, DnsServer *s) { + assert(l); + + if (l->current_dns_server == s) + return s; + + if (s) + log_info("Switching to DNS server %s for interface %s.", dns_server_string(s), l->name); + + dns_server_unref(l->current_dns_server); + l->current_dns_server = dns_server_ref(s); + + if (l->unicast_scope) + dns_cache_flush(&l->unicast_scope->cache); + + return s; +} + +DnsServer *link_get_dns_server(Link *l) { + assert(l); + + if (!l->current_dns_server) + link_set_dns_server(l, l->dns_servers); + + return l->current_dns_server; +} + +void link_next_dns_server(Link *l) { + assert(l); + + if (!l->current_dns_server) + return; + + /* Change to the next one, but make sure to follow the linked + * list only if this server is actually still linked. */ + if (l->current_dns_server->linked && l->current_dns_server->servers_next) { + link_set_dns_server(l, l->current_dns_server->servers_next); + return; + } + + link_set_dns_server(l, l->dns_servers); +} + +DnssecMode link_get_dnssec_mode(Link *l) { + assert(l); + + if (l->dnssec_mode != _DNSSEC_MODE_INVALID) + return l->dnssec_mode; + + return manager_get_dnssec_mode(l->manager); +} + +bool link_dnssec_supported(Link *l) { + DnsServer *server; + + assert(l); + + if (link_get_dnssec_mode(l) == DNSSEC_NO) + return false; + + server = link_get_dns_server(l); + if (server) + return dns_server_dnssec_supported(server); + + return true; +} + +int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr) { + LinkAddress *a; + + assert(l); + assert(in_addr); + + a = new0(LinkAddress, 1); + if (!a) + return -ENOMEM; + + a->family = family; + a->in_addr = *in_addr; + + a->link = l; + LIST_PREPEND(addresses, l->addresses, a); + + if (ret) + *ret = a; + + return 0; +} + +LinkAddress *link_address_free(LinkAddress *a) { + if (!a) + return NULL; + + if (a->link) { + LIST_REMOVE(addresses, a->link->addresses, a); + + if (a->llmnr_address_rr) { + if (a->family == AF_INET && a->link->llmnr_ipv4_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_address_rr); + else if (a->family == AF_INET6 && a->link->llmnr_ipv6_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_address_rr); + } + + if (a->llmnr_ptr_rr) { + if (a->family == AF_INET && a->link->llmnr_ipv4_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_ptr_rr); + else if (a->family == AF_INET6 && a->link->llmnr_ipv6_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_ptr_rr); + } + } + + dns_resource_record_unref(a->llmnr_address_rr); + dns_resource_record_unref(a->llmnr_ptr_rr); + + free(a); + return NULL; +} + +void link_address_add_rrs(LinkAddress *a, bool force_remove) { + int r; + + assert(a); + + if (a->family == AF_INET) { + + if (!force_remove && + link_address_relevant(a, true) && + a->link->llmnr_ipv4_scope && + a->link->llmnr_support == RESOLVE_SUPPORT_YES && + a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) { + + if (!a->link->manager->llmnr_host_ipv4_key) { + a->link->manager->llmnr_host_ipv4_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, a->link->manager->llmnr_hostname); + if (!a->link->manager->llmnr_host_ipv4_key) { + r = -ENOMEM; + goto fail; + } + } + + if (!a->llmnr_address_rr) { + a->llmnr_address_rr = dns_resource_record_new(a->link->manager->llmnr_host_ipv4_key); + if (!a->llmnr_address_rr) { + r = -ENOMEM; + goto fail; + } + + a->llmnr_address_rr->a.in_addr = a->in_addr.in; + a->llmnr_address_rr->ttl = LLMNR_DEFAULT_TTL; + } + + if (!a->llmnr_ptr_rr) { + r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->llmnr_hostname); + if (r < 0) + goto fail; + + a->llmnr_ptr_rr->ttl = LLMNR_DEFAULT_TTL; + } + + r = dns_zone_put(&a->link->llmnr_ipv4_scope->zone, a->link->llmnr_ipv4_scope, a->llmnr_address_rr, true); + if (r < 0) + log_warning_errno(r, "Failed to add A record to LLMNR zone: %m"); + + r = dns_zone_put(&a->link->llmnr_ipv4_scope->zone, a->link->llmnr_ipv4_scope, a->llmnr_ptr_rr, false); + if (r < 0) + log_warning_errno(r, "Failed to add IPv6 PTR record to LLMNR zone: %m"); + } else { + if (a->llmnr_address_rr) { + if (a->link->llmnr_ipv4_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_address_rr); + a->llmnr_address_rr = dns_resource_record_unref(a->llmnr_address_rr); + } + + if (a->llmnr_ptr_rr) { + if (a->link->llmnr_ipv4_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_ptr_rr); + a->llmnr_ptr_rr = dns_resource_record_unref(a->llmnr_ptr_rr); + } + } + } + + if (a->family == AF_INET6) { + + if (!force_remove && + link_address_relevant(a, true) && + a->link->llmnr_ipv6_scope && + a->link->llmnr_support == RESOLVE_SUPPORT_YES && + a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) { + + if (!a->link->manager->llmnr_host_ipv6_key) { + a->link->manager->llmnr_host_ipv6_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, a->link->manager->llmnr_hostname); + if (!a->link->manager->llmnr_host_ipv6_key) { + r = -ENOMEM; + goto fail; + } + } + + if (!a->llmnr_address_rr) { + a->llmnr_address_rr = dns_resource_record_new(a->link->manager->llmnr_host_ipv6_key); + if (!a->llmnr_address_rr) { + r = -ENOMEM; + goto fail; + } + + a->llmnr_address_rr->aaaa.in6_addr = a->in_addr.in6; + a->llmnr_address_rr->ttl = LLMNR_DEFAULT_TTL; + } + + if (!a->llmnr_ptr_rr) { + r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->llmnr_hostname); + if (r < 0) + goto fail; + + a->llmnr_ptr_rr->ttl = LLMNR_DEFAULT_TTL; + } + + r = dns_zone_put(&a->link->llmnr_ipv6_scope->zone, a->link->llmnr_ipv6_scope, a->llmnr_address_rr, true); + if (r < 0) + log_warning_errno(r, "Failed to add AAAA record to LLMNR zone: %m"); + + r = dns_zone_put(&a->link->llmnr_ipv6_scope->zone, a->link->llmnr_ipv6_scope, a->llmnr_ptr_rr, false); + if (r < 0) + log_warning_errno(r, "Failed to add IPv6 PTR record to LLMNR zone: %m"); + } else { + if (a->llmnr_address_rr) { + if (a->link->llmnr_ipv6_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_address_rr); + a->llmnr_address_rr = dns_resource_record_unref(a->llmnr_address_rr); + } + + if (a->llmnr_ptr_rr) { + if (a->link->llmnr_ipv6_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_ptr_rr); + a->llmnr_ptr_rr = dns_resource_record_unref(a->llmnr_ptr_rr); + } + } + } + + return; + +fail: + log_debug_errno(r, "Failed to update address RRs: %m"); +} + +int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m) { + int r; + assert(a); + assert(m); + + r = sd_rtnl_message_addr_get_flags(m, &a->flags); + if (r < 0) + return r; + + sd_rtnl_message_addr_get_scope(m, &a->scope); + + link_allocate_scopes(a->link); + link_add_rrs(a->link, false); + + return 0; +} + +bool link_address_relevant(LinkAddress *a, bool local_multicast) { + assert(a); + + if (a->flags & (IFA_F_DEPRECATED|IFA_F_TENTATIVE)) + return false; + + if (a->scope >= (local_multicast ? RT_SCOPE_HOST : RT_SCOPE_LINK)) + return false; + + return true; +} diff --git a/src/grp-resolve/systemd-resolved/resolved-link.h b/src/grp-resolve/systemd-resolved/resolved-link.h new file mode 100644 index 0000000000..f534c12824 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-link.h @@ -0,0 +1,111 @@ +#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 + +#include "in-addr-util.h" +#include "ratelimit.h" +#include "resolve-util.h" + +typedef struct Link Link; +typedef struct LinkAddress LinkAddress; + +#include "resolved-dns-rr.h" +#include "resolved-dns-search-domain.h" +#include "resolved-dns-server.h" +#include "resolved-manager.h" + +#define LINK_SEARCH_DOMAINS_MAX 32 +#define LINK_DNS_SERVERS_MAX 32 + +struct LinkAddress { + Link *link; + + int family; + union in_addr_union in_addr; + + unsigned char flags, scope; + + DnsResourceRecord *llmnr_address_rr; + DnsResourceRecord *llmnr_ptr_rr; + + LIST_FIELDS(LinkAddress, addresses); +}; + +struct Link { + Manager *manager; + + int ifindex; + unsigned flags; + + LIST_HEAD(LinkAddress, addresses); + + LIST_HEAD(DnsServer, dns_servers); + DnsServer *current_dns_server; + unsigned n_dns_servers; + + LIST_HEAD(DnsSearchDomain, search_domains); + unsigned n_search_domains; + + ResolveSupport llmnr_support; + ResolveSupport mdns_support; + DnssecMode dnssec_mode; + Set *dnssec_negative_trust_anchors; + + DnsScope *unicast_scope; + DnsScope *llmnr_ipv4_scope; + DnsScope *llmnr_ipv6_scope; + DnsScope *mdns_ipv4_scope; + DnsScope *mdns_ipv6_scope; + + bool is_managed; + + char name[IF_NAMESIZE]; + uint32_t mtu; + uint8_t operstate; +}; + +int link_new(Manager *m, Link **ret, int ifindex); +Link *link_free(Link *l); +int link_update_rtnl(Link *l, sd_netlink_message *m); +int link_update_monitor(Link *l); +bool link_relevant(Link *l, int family, bool local_multicast); +LinkAddress* link_find_address(Link *l, int family, const union in_addr_union *in_addr); +void link_add_rrs(Link *l, bool force_remove); + +void link_flush_settings(Link *l); +void link_set_dnssec_mode(Link *l, DnssecMode mode); +void link_allocate_scopes(Link *l); + +DnsServer* link_set_dns_server(Link *l, DnsServer *s); +DnsServer* link_get_dns_server(Link *l); +void link_next_dns_server(Link *l); + +DnssecMode link_get_dnssec_mode(Link *l); +bool link_dnssec_supported(Link *l); + +int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr); +LinkAddress *link_address_free(LinkAddress *a); +int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m); +bool link_address_relevant(LinkAddress *l, bool local_multicast); +void link_address_add_rrs(LinkAddress *a, bool force_remove); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_free); diff --git a/src/grp-resolve/systemd-resolved/resolved-llmnr.c b/src/grp-resolve/systemd-resolved/resolved-llmnr.c new file mode 100644 index 0000000000..8b1d71a3eb --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-llmnr.c @@ -0,0 +1,476 @@ +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 + +#include "fd-util.h" +#include "resolved-llmnr.h" +#include "resolved-manager.h" + +void manager_llmnr_stop(Manager *m) { + assert(m); + + m->llmnr_ipv4_udp_event_source = sd_event_source_unref(m->llmnr_ipv4_udp_event_source); + m->llmnr_ipv4_udp_fd = safe_close(m->llmnr_ipv4_udp_fd); + + m->llmnr_ipv6_udp_event_source = sd_event_source_unref(m->llmnr_ipv6_udp_event_source); + m->llmnr_ipv6_udp_fd = safe_close(m->llmnr_ipv6_udp_fd); + + m->llmnr_ipv4_tcp_event_source = sd_event_source_unref(m->llmnr_ipv4_tcp_event_source); + m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd); + + m->llmnr_ipv6_tcp_event_source = sd_event_source_unref(m->llmnr_ipv6_tcp_event_source); + m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); +} + +int manager_llmnr_start(Manager *m) { + int r; + + assert(m); + + if (m->llmnr_support == RESOLVE_SUPPORT_NO) + return 0; + + r = manager_llmnr_ipv4_udp_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + + r = manager_llmnr_ipv4_tcp_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + + if (socket_ipv6_is_supported()) { + r = manager_llmnr_ipv6_udp_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + + r = manager_llmnr_ipv6_tcp_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + } + + return 0; + +eaddrinuse: + log_warning("There appears to be another LLMNR responder running. Turning off LLMNR support."); + m->llmnr_support = RESOLVE_SUPPORT_NO; + manager_llmnr_stop(m); + + return 0; +} + +static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + DnsTransaction *t = NULL; + Manager *m = userdata; + DnsScope *scope; + int r; + + r = manager_recv(m, fd, DNS_PROTOCOL_LLMNR, &p); + if (r <= 0) + return r; + + scope = manager_find_scope(m, p); + if (!scope) { + log_warning("Got LLMNR UDP packet on unknown scope. Ignoring."); + return 0; + } + + if (dns_packet_validate_reply(p) > 0) { + log_debug("Got LLMNR reply packet for id %u", DNS_PACKET_ID(p)); + + dns_scope_check_conflicts(scope, p); + + t = hashmap_get(m->dns_transactions, UINT_TO_PTR(DNS_PACKET_ID(p))); + if (t) + dns_transaction_process_reply(t, p); + + } else if (dns_packet_validate_query(p) > 0) { + log_debug("Got LLMNR query packet for id %u", DNS_PACKET_ID(p)); + + dns_scope_process_query(scope, NULL, p); + } else + log_debug("Invalid LLMNR UDP packet, ignoring."); + + return 0; +} + +int manager_llmnr_ipv4_udp_fd(Manager *m) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(LLMNR_PORT), + }; + static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; + int r; + + assert(m); + + if (m->llmnr_ipv4_udp_fd >= 0) + return m->llmnr_ipv4_udp_fd; + + m->llmnr_ipv4_udp_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->llmnr_ipv4_udp_fd < 0) + return -errno; + + /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* Disable Don't-Fragment bit in the IP header */ + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->llmnr_ipv4_udp_fd, &sa.sa, sizeof(sa.in)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->llmnr_ipv4_udp_event_source, m->llmnr_ipv4_udp_fd, EPOLLIN, on_llmnr_packet, m); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(m->llmnr_ipv4_udp_event_source, "llmnr-ipv4-udp"); + + return m->llmnr_ipv4_udp_fd; + +fail: + m->llmnr_ipv4_udp_fd = safe_close(m->llmnr_ipv4_udp_fd); + return r; +} + +int manager_llmnr_ipv6_udp_fd(Manager *m) { + union sockaddr_union sa = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(LLMNR_PORT), + }; + static const int one = 1, ttl = 255; + int r; + + assert(m); + + if (m->llmnr_ipv6_udp_fd >= 0) + return m->llmnr_ipv6_udp_fd; + + m->llmnr_ipv6_udp_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->llmnr_ipv6_udp_fd < 0) + return -errno; + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->llmnr_ipv6_udp_fd, &sa.sa, sizeof(sa.in6)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->llmnr_ipv6_udp_event_source, m->llmnr_ipv6_udp_fd, EPOLLIN, on_llmnr_packet, m); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(m->llmnr_ipv6_udp_event_source, "llmnr-ipv6-udp"); + + return m->llmnr_ipv6_udp_fd; + +fail: + m->llmnr_ipv6_udp_fd = safe_close(m->llmnr_ipv6_udp_fd); + return r; +} + +static int on_llmnr_stream_packet(DnsStream *s) { + DnsScope *scope; + + assert(s); + + scope = manager_find_scope(s->manager, s->read_packet); + if (!scope) { + log_warning("Got LLMNR TCP packet on unknown scope. Ignoring."); + return 0; + } + + if (dns_packet_validate_query(s->read_packet) > 0) { + log_debug("Got query packet for id %u", DNS_PACKET_ID(s->read_packet)); + + dns_scope_process_query(scope, s, s->read_packet); + + /* If no reply packet was set, we free the stream */ + if (s->write_packet) + return 0; + } else + log_debug("Invalid LLMNR TCP packet."); + + dns_stream_free(s); + return 0; +} + +static int on_llmnr_stream(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + DnsStream *stream; + Manager *m = userdata; + int cfd, r; + + cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); + if (cfd < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return -errno; + } + + r = dns_stream_new(m, &stream, DNS_PROTOCOL_LLMNR, cfd); + if (r < 0) { + safe_close(cfd); + return r; + } + + stream->on_packet = on_llmnr_stream_packet; + return 0; +} + +int manager_llmnr_ipv4_tcp_fd(Manager *m) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(LLMNR_PORT), + }; + static const int one = 1, pmtu = IP_PMTUDISC_DONT; + int r; + + assert(m); + + if (m->llmnr_ipv4_tcp_fd >= 0) + return m->llmnr_ipv4_tcp_fd; + + m->llmnr_ipv4_tcp_fd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->llmnr_ipv4_tcp_fd < 0) + return -errno; + + /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */ + r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_TTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* Disable Don't-Fragment bit in the IP header */ + r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->llmnr_ipv4_tcp_fd, &sa.sa, sizeof(sa.in)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = listen(m->llmnr_ipv4_tcp_fd, SOMAXCONN); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->llmnr_ipv4_tcp_event_source, m->llmnr_ipv4_tcp_fd, EPOLLIN, on_llmnr_stream, m); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(m->llmnr_ipv4_tcp_event_source, "llmnr-ipv4-tcp"); + + return m->llmnr_ipv4_tcp_fd; + +fail: + m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd); + return r; +} + +int manager_llmnr_ipv6_tcp_fd(Manager *m) { + union sockaddr_union sa = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(LLMNR_PORT), + }; + static const int one = 1; + int r; + + assert(m); + + if (m->llmnr_ipv6_tcp_fd >= 0) + return m->llmnr_ipv6_tcp_fd; + + m->llmnr_ipv6_tcp_fd = socket(AF_INET6, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->llmnr_ipv6_tcp_fd < 0) + return -errno; + + /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */ + r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->llmnr_ipv6_tcp_fd, &sa.sa, sizeof(sa.in6)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = listen(m->llmnr_ipv6_tcp_fd, SOMAXCONN); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->llmnr_ipv6_tcp_event_source, m->llmnr_ipv6_tcp_fd, EPOLLIN, on_llmnr_stream, m); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(m->llmnr_ipv6_tcp_event_source, "llmnr-ipv6-tcp"); + + return m->llmnr_ipv6_tcp_fd; + +fail: + m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); + return r; +} diff --git a/src/grp-resolve/systemd-resolved/resolved-llmnr.h b/src/grp-resolve/systemd-resolved/resolved-llmnr.h new file mode 100644 index 0000000000..8133582fa7 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-llmnr.h @@ -0,0 +1,32 @@ +#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 "resolved-manager.h" + +#define LLMNR_PORT 5355 + +int manager_llmnr_ipv4_udp_fd(Manager *m); +int manager_llmnr_ipv6_udp_fd(Manager *m); +int manager_llmnr_ipv4_tcp_fd(Manager *m); +int manager_llmnr_ipv6_tcp_fd(Manager *m); + +void manager_llmnr_stop(Manager *m); +int manager_llmnr_start(Manager *m); diff --git a/src/grp-resolve/systemd-resolved/resolved-manager.c b/src/grp-resolve/systemd-resolved/resolved-manager.c new file mode 100644 index 0000000000..7166b94d71 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-manager.c @@ -0,0 +1,1239 @@ +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 +#include + +#include "af-list.h" +#include "alloc-util.h" +#include "dns-domain.h" +#include "fd-util.h" +#include "fileio-label.h" +#include "hostname-util.h" +#include "io-util.h" +#include "netlink-util.h" +#include "network-internal.h" +#include "ordered-set.h" +#include "parse-util.h" +#include "random-util.h" +#include "resolved-bus.h" +#include "resolved-conf.h" +#include "resolved-etc-hosts.h" +#include "resolved-llmnr.h" +#include "resolved-manager.h" +#include "resolved-mdns.h" +#include "resolved-resolv-conf.h" +#include "socket-util.h" +#include "string-table.h" +#include "string-util.h" +#include "utf8.h" + +#define SEND_TIMEOUT_USEC (200 * USEC_PER_MSEC) + +static int manager_process_link(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) { + Manager *m = userdata; + uint16_t type; + Link *l; + int ifindex, r; + + assert(rtnl); + assert(m); + assert(mm); + + r = sd_netlink_message_get_type(mm, &type); + if (r < 0) + goto fail; + + r = sd_rtnl_message_link_get_ifindex(mm, &ifindex); + if (r < 0) + goto fail; + + l = hashmap_get(m->links, INT_TO_PTR(ifindex)); + + switch (type) { + + case RTM_NEWLINK:{ + bool is_new = !l; + + if (!l) { + r = link_new(m, &l, ifindex); + if (r < 0) + goto fail; + } + + r = link_update_rtnl(l, mm); + if (r < 0) + goto fail; + + r = link_update_monitor(l); + if (r < 0) + goto fail; + + if (is_new) + log_debug("Found new link %i/%s", ifindex, l->name); + + break; + } + + case RTM_DELLINK: + if (l) { + log_debug("Removing link %i/%s", l->ifindex, l->name); + link_free(l); + } + + break; + } + + return 0; + +fail: + log_warning_errno(r, "Failed to process RTNL link message: %m"); + return 0; +} + +static int manager_process_address(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) { + Manager *m = userdata; + union in_addr_union address; + uint16_t type; + int r, ifindex, family; + LinkAddress *a; + Link *l; + + assert(rtnl); + assert(mm); + assert(m); + + r = sd_netlink_message_get_type(mm, &type); + if (r < 0) + goto fail; + + r = sd_rtnl_message_addr_get_ifindex(mm, &ifindex); + if (r < 0) + goto fail; + + l = hashmap_get(m->links, INT_TO_PTR(ifindex)); + if (!l) + return 0; + + r = sd_rtnl_message_addr_get_family(mm, &family); + if (r < 0) + goto fail; + + switch (family) { + + case AF_INET: + r = sd_netlink_message_read_in_addr(mm, IFA_LOCAL, &address.in); + if (r < 0) { + r = sd_netlink_message_read_in_addr(mm, IFA_ADDRESS, &address.in); + if (r < 0) + goto fail; + } + + break; + + case AF_INET6: + r = sd_netlink_message_read_in6_addr(mm, IFA_LOCAL, &address.in6); + if (r < 0) { + r = sd_netlink_message_read_in6_addr(mm, IFA_ADDRESS, &address.in6); + if (r < 0) + goto fail; + } + + break; + + default: + return 0; + } + + a = link_find_address(l, family, &address); + + switch (type) { + + case RTM_NEWADDR: + + if (!a) { + r = link_address_new(l, &a, family, &address); + if (r < 0) + return r; + } + + r = link_address_update_rtnl(a, mm); + if (r < 0) + return r; + + break; + + case RTM_DELADDR: + link_address_free(a); + break; + } + + return 0; + +fail: + log_warning_errno(r, "Failed to process RTNL address message: %m"); + return 0; +} + +static int manager_rtnl_listen(Manager *m) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + sd_netlink_message *i; + int r; + + assert(m); + + /* First, subscribe to interfaces coming and going */ + r = sd_netlink_open(&m->rtnl); + if (r < 0) + return r; + + r = sd_netlink_attach_event(m->rtnl, m->event, SD_EVENT_PRIORITY_IMPORTANT); + if (r < 0) + return r; + + r = sd_netlink_add_match(m->rtnl, RTM_NEWLINK, manager_process_link, m); + if (r < 0) + return r; + + r = sd_netlink_add_match(m->rtnl, RTM_DELLINK, manager_process_link, m); + if (r < 0) + return r; + + r = sd_netlink_add_match(m->rtnl, RTM_NEWADDR, manager_process_address, m); + if (r < 0) + return r; + + r = sd_netlink_add_match(m->rtnl, RTM_DELADDR, manager_process_address, m); + if (r < 0) + return r; + + /* Then, enumerate all links */ + r = sd_rtnl_message_new_link(m->rtnl, &req, RTM_GETLINK, 0); + if (r < 0) + return r; + + r = sd_netlink_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_netlink_call(m->rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (i = reply; i; i = sd_netlink_message_next(i)) { + r = manager_process_link(m->rtnl, i, m); + if (r < 0) + return r; + } + + req = sd_netlink_message_unref(req); + reply = sd_netlink_message_unref(reply); + + /* Finally, enumerate all addresses, too */ + r = sd_rtnl_message_new_addr(m->rtnl, &req, RTM_GETADDR, 0, AF_UNSPEC); + if (r < 0) + return r; + + r = sd_netlink_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_netlink_call(m->rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (i = reply; i; i = sd_netlink_message_next(i)) { + r = manager_process_address(m->rtnl, i, m); + if (r < 0) + return r; + } + + return r; +} + +static int on_network_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + Iterator i; + Link *l; + int r; + + assert(m); + + sd_network_monitor_flush(m->network_monitor); + + HASHMAP_FOREACH(l, m->links, i) { + r = link_update_monitor(l); + if (r < 0) + log_warning_errno(r, "Failed to update monitor information for %i: %m", l->ifindex); + } + + r = manager_write_resolv_conf(m); + if (r < 0) + log_warning_errno(r, "Could not update "PRIVATE_RESOLV_CONF": %m"); + + return 0; +} + +static int manager_network_monitor_listen(Manager *m) { + int r, fd, events; + + assert(m); + + r = sd_network_monitor_new(&m->network_monitor, NULL); + if (r < 0) + return r; + + fd = sd_network_monitor_get_fd(m->network_monitor); + if (fd < 0) + return fd; + + events = sd_network_monitor_get_events(m->network_monitor); + if (events < 0) + return events; + + r = sd_event_add_io(m->event, &m->network_event_source, fd, events, &on_network_event, m); + if (r < 0) + return r; + + r = sd_event_source_set_priority(m->network_event_source, SD_EVENT_PRIORITY_IMPORTANT+5); + if (r < 0) + return r; + + (void) sd_event_source_set_description(m->network_event_source, "network-monitor"); + + return 0; +} + +static int determine_hostname(char **llmnr_hostname, char **mdns_hostname) { + _cleanup_free_ char *h = NULL, *n = NULL; + char label[DNS_LABEL_MAX]; + const char *p; + int r, k; + + assert(llmnr_hostname); + assert(mdns_hostname); + + /* Extract and normalize the first label of the locally + * configured hostname, and check it's not "localhost". */ + + h = gethostname_malloc(); + if (!h) + return log_oom(); + + p = h; + r = dns_label_unescape(&p, label, sizeof(label)); + if (r < 0) + return log_error_errno(r, "Failed to unescape host name: %m"); + if (r == 0) { + log_error("Couldn't find a single label in hosntame."); + return -EINVAL; + } + + k = dns_label_undo_idna(label, r, label, sizeof(label)); + if (k < 0) + return log_error_errno(k, "Failed to undo IDNA: %m"); + if (k > 0) + r = k; + + if (!utf8_is_valid(label)) { + log_error("System hostname is not UTF-8 clean."); + return -EINVAL; + } + + r = dns_label_escape_new(label, r, &n); + if (r < 0) + return log_error_errno(r, "Failed to escape host name: %m"); + + if (is_localhost(n)) { + log_debug("System hostname is 'localhost', ignoring."); + return -EINVAL; + } + + r = dns_name_concat(n, "local", mdns_hostname); + if (r < 0) + return log_error_errno(r, "Failed to determine mDNS hostname: %m"); + + *llmnr_hostname = n; + n = NULL; + + return 0; +} + +static int on_hostname_change(sd_event_source *es, int fd, uint32_t revents, void *userdata) { + _cleanup_free_ char *llmnr_hostname = NULL, *mdns_hostname = NULL; + Manager *m = userdata; + int r; + + assert(m); + + r = determine_hostname(&llmnr_hostname, &mdns_hostname); + if (r < 0) + return 0; /* ignore invalid hostnames */ + + if (streq(llmnr_hostname, m->llmnr_hostname) && streq(mdns_hostname, m->mdns_hostname)) + return 0; + + log_info("System hostname changed to '%s'.", llmnr_hostname); + + free(m->llmnr_hostname); + free(m->mdns_hostname); + + m->llmnr_hostname = llmnr_hostname; + m->mdns_hostname = mdns_hostname; + + llmnr_hostname = mdns_hostname = NULL; + + manager_refresh_rrs(m); + + return 0; +} + +static int manager_watch_hostname(Manager *m) { + int r; + + assert(m); + + m->hostname_fd = open("/proc/sys/kernel/hostname", O_RDONLY|O_CLOEXEC|O_NDELAY|O_NOCTTY); + if (m->hostname_fd < 0) { + log_warning_errno(errno, "Failed to watch hostname: %m"); + return 0; + } + + r = sd_event_add_io(m->event, &m->hostname_event_source, m->hostname_fd, 0, on_hostname_change, m); + if (r < 0) { + if (r == -EPERM) + /* kernels prior to 3.2 don't support polling this file. Ignore the failure. */ + m->hostname_fd = safe_close(m->hostname_fd); + else + return log_error_errno(r, "Failed to add hostname event source: %m"); + } + + (void) sd_event_source_set_description(m->hostname_event_source, "hostname"); + + r = determine_hostname(&m->llmnr_hostname, &m->mdns_hostname); + if (r < 0) { + log_info("Defaulting to hostname 'gnu-linux'."); + m->llmnr_hostname = strdup("gnu-linux"); + if (!m->llmnr_hostname) + return log_oom(); + + m->mdns_hostname = strdup("gnu-linux.local"); + if (!m->mdns_hostname) + return log_oom(); + } else + log_info("Using system hostname '%s'.", m->llmnr_hostname); + + return 0; +} + +static int manager_sigusr1(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + _cleanup_free_ char *buffer = NULL; + _cleanup_fclose_ FILE *f = NULL; + Manager *m = userdata; + size_t size = 0; + DnsScope *scope; + + assert(s); + assert(si); + assert(m); + + f = open_memstream(&buffer, &size); + if (!f) + return log_oom(); + + LIST_FOREACH(scopes, scope, m->dns_scopes) + dns_scope_dump(scope, f); + + if (fflush_and_check(f) < 0) + return log_oom(); + + log_dump(LOG_INFO, buffer); + return 0; +} + +int manager_new(Manager **ret) { + _cleanup_(manager_freep) Manager *m = NULL; + int r; + + assert(ret); + + m = new0(Manager, 1); + if (!m) + return -ENOMEM; + + m->llmnr_ipv4_udp_fd = m->llmnr_ipv6_udp_fd = -1; + m->llmnr_ipv4_tcp_fd = m->llmnr_ipv6_tcp_fd = -1; + m->mdns_ipv4_fd = m->mdns_ipv6_fd = -1; + m->hostname_fd = -1; + + m->llmnr_support = RESOLVE_SUPPORT_YES; + m->mdns_support = RESOLVE_SUPPORT_NO; + m->dnssec_mode = DEFAULT_DNSSEC_MODE; + m->read_resolv_conf = true; + m->need_builtin_fallbacks = true; + m->etc_hosts_last = m->etc_hosts_mtime = USEC_INFINITY; + + r = dns_trust_anchor_load(&m->trust_anchor); + if (r < 0) + return r; + + r = manager_parse_config_file(m); + if (r < 0) + return r; + + r = sd_event_default(&m->event); + if (r < 0) + return r; + + sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); + sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); + + sd_event_set_watchdog(m->event, true); + + r = manager_watch_hostname(m); + if (r < 0) + return r; + + r = dns_scope_new(m, &m->unicast_scope, NULL, DNS_PROTOCOL_DNS, AF_UNSPEC); + if (r < 0) + return r; + + r = manager_network_monitor_listen(m); + if (r < 0) + return r; + + r = manager_rtnl_listen(m); + if (r < 0) + return r; + + r = manager_connect_bus(m); + if (r < 0) + return r; + + (void) sd_event_add_signal(m->event, &m->sigusr1_event_source, SIGUSR1, manager_sigusr1, m); + + *ret = m; + m = NULL; + + return 0; +} + +int manager_start(Manager *m) { + int r; + + assert(m); + + r = manager_llmnr_start(m); + if (r < 0) + return r; + + r = manager_mdns_start(m); + if (r < 0) + return r; + + return 0; +} + +Manager *manager_free(Manager *m) { + Link *l; + + if (!m) + return NULL; + + dns_server_unlink_all(m->dns_servers); + dns_server_unlink_all(m->fallback_dns_servers); + dns_search_domain_unlink_all(m->search_domains); + + while ((l = hashmap_first(m->links))) + link_free(l); + + while (m->dns_queries) + dns_query_free(m->dns_queries); + + dns_scope_free(m->unicast_scope); + + hashmap_free(m->links); + hashmap_free(m->dns_transactions); + + sd_event_source_unref(m->network_event_source); + sd_network_monitor_unref(m->network_monitor); + + sd_netlink_unref(m->rtnl); + sd_event_source_unref(m->rtnl_event_source); + + manager_llmnr_stop(m); + manager_mdns_stop(m); + + sd_bus_slot_unref(m->prepare_for_sleep_slot); + sd_event_source_unref(m->bus_retry_event_source); + sd_bus_unref(m->bus); + + sd_event_source_unref(m->sigusr1_event_source); + + sd_event_unref(m->event); + + dns_resource_key_unref(m->llmnr_host_ipv4_key); + dns_resource_key_unref(m->llmnr_host_ipv6_key); + + sd_event_source_unref(m->hostname_event_source); + safe_close(m->hostname_fd); + free(m->llmnr_hostname); + free(m->mdns_hostname); + + dns_trust_anchor_flush(&m->trust_anchor); + manager_etc_hosts_flush(m); + + free(m); + + return NULL; +} + +int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + union { + struct cmsghdr header; /* For alignment */ + uint8_t buffer[CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo)) + + CMSG_SPACE(int) /* ttl/hoplimit */ + + EXTRA_CMSG_SPACE /* kernel appears to require extra buffer space */]; + } control; + union sockaddr_union sa; + struct msghdr mh = {}; + struct cmsghdr *cmsg; + struct iovec iov; + ssize_t ms, l; + int r; + + assert(m); + assert(fd >= 0); + assert(ret); + + ms = next_datagram_size_fd(fd); + if (ms < 0) + return ms; + + r = dns_packet_new(&p, protocol, ms); + if (r < 0) + return r; + + iov.iov_base = DNS_PACKET_DATA(p); + iov.iov_len = p->allocated; + + mh.msg_name = &sa.sa; + mh.msg_namelen = sizeof(sa); + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_control = &control; + mh.msg_controllen = sizeof(control); + + l = recvmsg(fd, &mh, 0); + if (l < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return -errno; + } + + if (l <= 0) + return -EIO; + + assert(!(mh.msg_flags & MSG_CTRUNC)); + assert(!(mh.msg_flags & MSG_TRUNC)); + + p->size = (size_t) l; + + p->family = sa.sa.sa_family; + p->ipproto = IPPROTO_UDP; + if (p->family == AF_INET) { + p->sender.in = sa.in.sin_addr; + p->sender_port = be16toh(sa.in.sin_port); + } else if (p->family == AF_INET6) { + p->sender.in6 = sa.in6.sin6_addr; + p->sender_port = be16toh(sa.in6.sin6_port); + p->ifindex = sa.in6.sin6_scope_id; + } else + return -EAFNOSUPPORT; + + CMSG_FOREACH(cmsg, &mh) { + + if (cmsg->cmsg_level == IPPROTO_IPV6) { + assert(p->family == AF_INET6); + + switch (cmsg->cmsg_type) { + + case IPV6_PKTINFO: { + struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg); + + if (p->ifindex <= 0) + p->ifindex = i->ipi6_ifindex; + + p->destination.in6 = i->ipi6_addr; + break; + } + + case IPV6_HOPLIMIT: + p->ttl = *(int *) CMSG_DATA(cmsg); + break; + + } + } else if (cmsg->cmsg_level == IPPROTO_IP) { + assert(p->family == AF_INET); + + switch (cmsg->cmsg_type) { + + case IP_PKTINFO: { + struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg); + + if (p->ifindex <= 0) + p->ifindex = i->ipi_ifindex; + + p->destination.in = i->ipi_addr; + break; + } + + case IP_TTL: + p->ttl = *(int *) CMSG_DATA(cmsg); + break; + } + } + } + + /* The Linux kernel sets the interface index to the loopback + * device if the packet came from the local host since it + * avoids the routing table in such a case. Let's unset the + * interface index in such a case. */ + if (p->ifindex == LOOPBACK_IFINDEX) + p->ifindex = 0; + + if (protocol != DNS_PROTOCOL_DNS) { + /* If we don't know the interface index still, we look for the + * first local interface with a matching address. Yuck! */ + if (p->ifindex <= 0) + p->ifindex = manager_find_ifindex(m, p->family, &p->destination); + } + + *ret = p; + p = NULL; + + return 1; +} + +static int sendmsg_loop(int fd, struct msghdr *mh, int flags) { + int r; + + assert(fd >= 0); + assert(mh); + + for (;;) { + if (sendmsg(fd, mh, flags) >= 0) + return 0; + + if (errno == EINTR) + continue; + + if (errno != EAGAIN) + return -errno; + + r = fd_wait_for_event(fd, POLLOUT, SEND_TIMEOUT_USEC); + if (r < 0) + return r; + if (r == 0) + return -ETIMEDOUT; + } +} + +static int write_loop(int fd, void *message, size_t length) { + int r; + + assert(fd >= 0); + assert(message); + + for (;;) { + if (write(fd, message, length) >= 0) + return 0; + + if (errno == EINTR) + continue; + + if (errno != EAGAIN) + return -errno; + + r = fd_wait_for_event(fd, POLLOUT, SEND_TIMEOUT_USEC); + if (r < 0) + return r; + if (r == 0) + return -ETIMEDOUT; + } +} + +int manager_write(Manager *m, int fd, DnsPacket *p) { + int r; + + log_debug("Sending %s packet with id %" PRIu16 ".", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p)); + + r = write_loop(fd, DNS_PACKET_DATA(p), p->size); + if (r < 0) + return r; + + return 0; +} + +static int manager_ipv4_send(Manager *m, int fd, int ifindex, const struct in_addr *addr, uint16_t port, DnsPacket *p) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + }; + union { + struct cmsghdr header; /* For alignment */ + uint8_t buffer[CMSG_SPACE(sizeof(struct in_pktinfo))]; + } control; + struct msghdr mh = {}; + struct iovec iov; + + assert(m); + assert(fd >= 0); + assert(addr); + assert(port > 0); + assert(p); + + iov.iov_base = DNS_PACKET_DATA(p); + iov.iov_len = p->size; + + sa.in.sin_addr = *addr; + sa.in.sin_port = htobe16(port), + + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_name = &sa.sa; + mh.msg_namelen = sizeof(sa.in); + + if (ifindex > 0) { + struct cmsghdr *cmsg; + struct in_pktinfo *pi; + + zero(control); + + mh.msg_control = &control; + mh.msg_controllen = CMSG_LEN(sizeof(struct in_pktinfo)); + + cmsg = CMSG_FIRSTHDR(&mh); + cmsg->cmsg_len = mh.msg_controllen; + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + + pi = (struct in_pktinfo*) CMSG_DATA(cmsg); + pi->ipi_ifindex = ifindex; + } + + return sendmsg_loop(fd, &mh, 0); +} + +static int manager_ipv6_send(Manager *m, int fd, int ifindex, const struct in6_addr *addr, uint16_t port, DnsPacket *p) { + union sockaddr_union sa = { + .in6.sin6_family = AF_INET6, + }; + union { + struct cmsghdr header; /* For alignment */ + uint8_t buffer[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } control; + struct msghdr mh = {}; + struct iovec iov; + + assert(m); + assert(fd >= 0); + assert(addr); + assert(port > 0); + assert(p); + + iov.iov_base = DNS_PACKET_DATA(p); + iov.iov_len = p->size; + + sa.in6.sin6_addr = *addr; + sa.in6.sin6_port = htobe16(port), + sa.in6.sin6_scope_id = ifindex; + + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_name = &sa.sa; + mh.msg_namelen = sizeof(sa.in6); + + if (ifindex > 0) { + struct cmsghdr *cmsg; + struct in6_pktinfo *pi; + + zero(control); + + mh.msg_control = &control; + mh.msg_controllen = CMSG_LEN(sizeof(struct in6_pktinfo)); + + cmsg = CMSG_FIRSTHDR(&mh); + cmsg->cmsg_len = mh.msg_controllen; + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + + pi = (struct in6_pktinfo*) CMSG_DATA(cmsg); + pi->ipi6_ifindex = ifindex; + } + + return sendmsg_loop(fd, &mh, 0); +} + +int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *addr, uint16_t port, DnsPacket *p) { + assert(m); + assert(fd >= 0); + assert(addr); + assert(port > 0); + assert(p); + + log_debug("Sending %s packet with id %" PRIu16 " on interface %i/%s.", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p), ifindex, af_to_name(family)); + + if (family == AF_INET) + return manager_ipv4_send(m, fd, ifindex, &addr->in, port, p); + else if (family == AF_INET6) + return manager_ipv6_send(m, fd, ifindex, &addr->in6, port, p); + + return -EAFNOSUPPORT; +} + +uint32_t manager_find_mtu(Manager *m) { + uint32_t mtu = 0; + Link *l; + Iterator i; + + /* If we don't know on which link a DNS packet would be + * delivered, let's find the largest MTU that works on all + * interfaces we know of */ + + HASHMAP_FOREACH(l, m->links, i) { + if (l->mtu <= 0) + continue; + + if (mtu <= 0 || l->mtu < mtu) + mtu = l->mtu; + } + + return mtu; +} + +int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr) { + LinkAddress *a; + + assert(m); + + a = manager_find_link_address(m, family, in_addr); + if (a) + return a->link->ifindex; + + return 0; +} + +void manager_refresh_rrs(Manager *m) { + Iterator i; + Link *l; + + assert(m); + + m->llmnr_host_ipv4_key = dns_resource_key_unref(m->llmnr_host_ipv4_key); + m->llmnr_host_ipv6_key = dns_resource_key_unref(m->llmnr_host_ipv6_key); + + HASHMAP_FOREACH(l, m->links, i) { + link_add_rrs(l, true); + link_add_rrs(l, false); + } +} + +int manager_next_hostname(Manager *m) { + const char *p; + uint64_t u, a; + char *h, *k; + int r; + + assert(m); + + p = strchr(m->llmnr_hostname, 0); + assert(p); + + while (p > m->llmnr_hostname) { + if (!strchr("0123456789", p[-1])) + break; + + p--; + } + + if (*p == 0 || safe_atou64(p, &u) < 0 || u <= 0) + u = 1; + + /* Add a random number to the old value. This way we can avoid + * that two hosts pick the same hostname, win on IPv4 and lose + * on IPv6 (or vice versa), and pick the same hostname + * replacement hostname, ad infinitum. We still want the + * numbers to go up monotonically, hence we just add a random + * value 1..10 */ + + random_bytes(&a, sizeof(a)); + u += 1 + a % 10; + + if (asprintf(&h, "%.*s%" PRIu64, (int) (p - m->llmnr_hostname), m->llmnr_hostname, u) < 0) + return -ENOMEM; + + r = dns_name_concat(h, "local", &k); + if (r < 0) { + free(h); + return r; + } + + log_info("Hostname conflict, changing published hostname from '%s' to '%s'.", m->llmnr_hostname, h); + + free(m->llmnr_hostname); + m->llmnr_hostname = h; + + free(m->mdns_hostname); + m->mdns_hostname = k; + + manager_refresh_rrs(m); + + return 0; +} + +LinkAddress* manager_find_link_address(Manager *m, int family, const union in_addr_union *in_addr) { + Iterator i; + Link *l; + + assert(m); + + HASHMAP_FOREACH(l, m->links, i) { + LinkAddress *a; + + a = link_find_address(l, family, in_addr); + if (a) + return a; + } + + return NULL; +} + +bool manager_our_packet(Manager *m, DnsPacket *p) { + assert(m); + assert(p); + + return !!manager_find_link_address(m, p->family, &p->sender); +} + +DnsScope* manager_find_scope(Manager *m, DnsPacket *p) { + Link *l; + + assert(m); + assert(p); + + l = hashmap_get(m->links, INT_TO_PTR(p->ifindex)); + if (!l) + return NULL; + + switch (p->protocol) { + case DNS_PROTOCOL_LLMNR: + if (p->family == AF_INET) + return l->llmnr_ipv4_scope; + else if (p->family == AF_INET6) + return l->llmnr_ipv6_scope; + + break; + + case DNS_PROTOCOL_MDNS: + if (p->family == AF_INET) + return l->mdns_ipv4_scope; + else if (p->family == AF_INET6) + return l->mdns_ipv6_scope; + + break; + + default: + break; + } + + return NULL; +} + +void manager_verify_all(Manager *m) { + DnsScope *s; + + assert(m); + + LIST_FOREACH(scopes, s, m->dns_scopes) + dns_zone_verify_all(&s->zone); +} + +int manager_is_own_hostname(Manager *m, const char *name) { + int r; + + assert(m); + assert(name); + + if (m->llmnr_hostname) { + r = dns_name_equal(name, m->llmnr_hostname); + if (r != 0) + return r; + } + + if (m->mdns_hostname) + return dns_name_equal(name, m->mdns_hostname); + + return 0; +} + +int manager_compile_dns_servers(Manager *m, OrderedSet **dns) { + DnsServer *s; + Iterator i; + Link *l; + int r; + + assert(m); + assert(dns); + + r = ordered_set_ensure_allocated(dns, &dns_server_hash_ops); + if (r < 0) + return r; + + /* First add the system-wide servers and domains */ + LIST_FOREACH(servers, s, m->dns_servers) { + r = ordered_set_put(*dns, s); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + + /* Then, add the per-link servers */ + HASHMAP_FOREACH(l, m->links, i) { + LIST_FOREACH(servers, s, l->dns_servers) { + r = ordered_set_put(*dns, s); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + } + + /* If we found nothing, add the fallback servers */ + if (ordered_set_isempty(*dns)) { + LIST_FOREACH(servers, s, m->fallback_dns_servers) { + r = ordered_set_put(*dns, s); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + } + + return 0; +} + +int manager_compile_search_domains(Manager *m, OrderedSet **domains) { + DnsSearchDomain *d; + Iterator i; + Link *l; + int r; + + assert(m); + assert(domains); + + r = ordered_set_ensure_allocated(domains, &dns_name_hash_ops); + if (r < 0) + return r; + + LIST_FOREACH(domains, d, m->search_domains) { + r = ordered_set_put(*domains, d->name); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + + HASHMAP_FOREACH(l, m->links, i) { + + LIST_FOREACH(domains, d, l->search_domains) { + r = ordered_set_put(*domains, d->name); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + } + + return 0; +} + +DnssecMode manager_get_dnssec_mode(Manager *m) { + assert(m); + + if (m->dnssec_mode != _DNSSEC_MODE_INVALID) + return m->dnssec_mode; + + return DNSSEC_NO; +} + +bool manager_dnssec_supported(Manager *m) { + DnsServer *server; + Iterator i; + Link *l; + + assert(m); + + if (manager_get_dnssec_mode(m) == DNSSEC_NO) + return false; + + server = manager_get_dns_server(m); + if (server && !dns_server_dnssec_supported(server)) + return false; + + HASHMAP_FOREACH(l, m->links, i) + if (!link_dnssec_supported(l)) + return false; + + return true; +} + +void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key) { + + assert(verdict >= 0); + assert(verdict < _DNSSEC_VERDICT_MAX); + + if (log_get_max_level() >= LOG_DEBUG) { + char s[DNS_RESOURCE_KEY_STRING_MAX]; + + log_debug("Found verdict for lookup %s: %s", + dns_resource_key_to_string(key, s, sizeof s), + dnssec_verdict_to_string(verdict)); + } + + m->n_dnssec_verdict[verdict]++; +} + +bool manager_routable(Manager *m, int family) { + Iterator i; + Link *l; + + assert(m); + + /* Returns true if the host has at least one interface with a routable address of the specified type */ + + HASHMAP_FOREACH(l, m->links, i) + if (link_relevant(l, family, false)) + return true; + + return false; +} diff --git a/src/grp-resolve/systemd-resolved/resolved-manager.h b/src/grp-resolve/systemd-resolved/resolved-manager.h new file mode 100644 index 0000000000..8bef2d2b28 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-manager.h @@ -0,0 +1,171 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 +#include + +#include "hashmap.h" +#include "list.h" +#include "ordered-set.h" +#include "resolve-util.h" + +typedef struct Manager Manager; + +#include "resolved-dns-query.h" +#include "resolved-dns-search-domain.h" +#include "resolved-dns-server.h" +#include "resolved-dns-stream.h" +#include "resolved-dns-trust-anchor.h" +#include "resolved-link.h" + +#define MANAGER_SEARCH_DOMAINS_MAX 32 +#define MANAGER_DNS_SERVERS_MAX 32 + +struct Manager { + sd_event *event; + + ResolveSupport llmnr_support; + ResolveSupport mdns_support; + DnssecMode dnssec_mode; + + /* Network */ + Hashmap *links; + + sd_netlink *rtnl; + sd_event_source *rtnl_event_source; + + sd_network_monitor *network_monitor; + sd_event_source *network_event_source; + + /* DNS query management */ + Hashmap *dns_transactions; + LIST_HEAD(DnsQuery, dns_queries); + unsigned n_dns_queries; + + LIST_HEAD(DnsStream, dns_streams); + unsigned n_dns_streams; + + /* Unicast dns */ + LIST_HEAD(DnsServer, dns_servers); + LIST_HEAD(DnsServer, fallback_dns_servers); + unsigned n_dns_servers; /* counts both main and fallback */ + DnsServer *current_dns_server; + + LIST_HEAD(DnsSearchDomain, search_domains); + unsigned n_search_domains; + bool permit_domain_search; + + bool need_builtin_fallbacks:1; + + bool read_resolv_conf:1; + usec_t resolv_conf_mtime; + + DnsTrustAnchor trust_anchor; + + LIST_HEAD(DnsScope, dns_scopes); + DnsScope *unicast_scope; + + /* LLMNR */ + int llmnr_ipv4_udp_fd; + int llmnr_ipv6_udp_fd; + int llmnr_ipv4_tcp_fd; + int llmnr_ipv6_tcp_fd; + + sd_event_source *llmnr_ipv4_udp_event_source; + sd_event_source *llmnr_ipv6_udp_event_source; + sd_event_source *llmnr_ipv4_tcp_event_source; + sd_event_source *llmnr_ipv6_tcp_event_source; + + /* mDNS */ + int mdns_ipv4_fd; + int mdns_ipv6_fd; + + sd_event_source *mdns_ipv4_event_source; + sd_event_source *mdns_ipv6_event_source; + + /* dbus */ + sd_bus *bus; + sd_event_source *bus_retry_event_source; + + /* The hostname we publish on LLMNR and mDNS */ + char *llmnr_hostname; + char *mdns_hostname; + DnsResourceKey *llmnr_host_ipv4_key; + DnsResourceKey *llmnr_host_ipv6_key; + + /* Watch the system hostname */ + int hostname_fd; + sd_event_source *hostname_event_source; + + /* Watch for system suspends */ + sd_bus_slot *prepare_for_sleep_slot; + + sd_event_source *sigusr1_event_source; + + unsigned n_transactions_total; + unsigned n_dnssec_verdict[_DNSSEC_VERDICT_MAX]; + + /* Data from /etc/hosts */ + Set* etc_hosts_by_address; + Hashmap* etc_hosts_by_name; + usec_t etc_hosts_last, etc_hosts_mtime; +}; + +/* Manager */ + +int manager_new(Manager **ret); +Manager* manager_free(Manager *m); + +int manager_start(Manager *m); + +uint32_t manager_find_mtu(Manager *m); + +int manager_write(Manager *m, int fd, DnsPacket *p); +int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *addr, uint16_t port, DnsPacket *p); +int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret); + +int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr); +LinkAddress* manager_find_link_address(Manager *m, int family, const union in_addr_union *in_addr); + +void manager_refresh_rrs(Manager *m); +int manager_next_hostname(Manager *m); + +bool manager_our_packet(Manager *m, DnsPacket *p); +DnsScope* manager_find_scope(Manager *m, DnsPacket *p); + +void manager_verify_all(Manager *m); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); + +#define EXTRA_CMSG_SPACE 1024 + +int manager_is_own_hostname(Manager *m, const char *name); + +int manager_compile_dns_servers(Manager *m, OrderedSet **servers); +int manager_compile_search_domains(Manager *m, OrderedSet **domains); + +DnssecMode manager_get_dnssec_mode(Manager *m); +bool manager_dnssec_supported(Manager *m); + +void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key); + +bool manager_routable(Manager *m, int family); diff --git a/src/grp-resolve/systemd-resolved/resolved-mdns.c b/src/grp-resolve/systemd-resolved/resolved-mdns.c new file mode 100644 index 0000000000..b13b1d0144 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-mdns.c @@ -0,0 +1,287 @@ +/*** + This file is part of systemd. + + Copyright 2015 Daniel Mack + + 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 +#include + +#include "fd-util.h" +#include "resolved-manager.h" +#include "resolved-mdns.h" + +void manager_mdns_stop(Manager *m) { + assert(m); + + m->mdns_ipv4_event_source = sd_event_source_unref(m->mdns_ipv4_event_source); + m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); + + m->mdns_ipv6_event_source = sd_event_source_unref(m->mdns_ipv6_event_source); + m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); +} + +int manager_mdns_start(Manager *m) { + int r; + + assert(m); + + if (m->mdns_support == RESOLVE_SUPPORT_NO) + return 0; + + r = manager_mdns_ipv4_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + + if (socket_ipv6_is_supported()) { + r = manager_mdns_ipv6_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + } + + return 0; + +eaddrinuse: + log_warning("There appears to be another mDNS responder running. Turning off mDNS support."); + m->mdns_support = RESOLVE_SUPPORT_NO; + manager_mdns_stop(m); + + return 0; +} + +static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + Manager *m = userdata; + DnsScope *scope; + int r; + + r = manager_recv(m, fd, DNS_PROTOCOL_MDNS, &p); + if (r <= 0) + return r; + + scope = manager_find_scope(m, p); + if (!scope) { + log_warning("Got mDNS UDP packet on unknown scope. Ignoring."); + return 0; + } + + if (dns_packet_validate_reply(p) > 0) { + DnsResourceRecord *rr; + + log_debug("Got mDNS reply packet"); + + /* + * mDNS is different from regular DNS and LLMNR with regard to handling responses. + * While on other protocols, we can ignore every answer that doesn't match a question + * we broadcast earlier, RFC6762, section 18.1 recommends looking at and caching all + * incoming information, regardless of the DNS packet ID. + * + * Hence, extract the packet here, and try to find a transaction for answer the we got + * and complete it. Also store the new information in scope's cache. + */ + r = dns_packet_extract(p); + if (r < 0) { + log_debug("mDNS packet extraction failed."); + return 0; + } + + dns_scope_check_conflicts(scope, p); + + DNS_ANSWER_FOREACH(rr, p->answer) { + const char *name = dns_resource_key_name(rr->key); + DnsTransaction *t; + + /* If the received reply packet contains ANY record that is not .local or .in-addr.arpa, + * we assume someone's playing tricks on us and discard the packet completely. */ + if (!(dns_name_endswith(name, "in-addr.arpa") > 0 || + dns_name_endswith(name, "local") > 0)) + return 0; + + t = dns_scope_find_transaction(scope, rr->key, false); + if (t) + dns_transaction_process_reply(t, p); + } + + dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, (uint32_t) -1, 0, p->family, &p->sender); + + } else if (dns_packet_validate_query(p) > 0) { + log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p)); + + dns_scope_process_query(scope, NULL, p); + } else + log_debug("Invalid mDNS UDP packet."); + + return 0; +} + +int manager_mdns_ipv4_fd(Manager *m) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(MDNS_PORT), + }; + static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; + int r; + + assert(m); + + if (m->mdns_ipv4_fd >= 0) + return m->mdns_ipv4_fd; + + m->mdns_ipv4_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->mdns_ipv4_fd < 0) + return -errno; + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* Disable Don't-Fragment bit in the IP header */ + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->mdns_ipv4_fd, &sa.sa, sizeof(sa.in)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->mdns_ipv4_event_source, m->mdns_ipv4_fd, EPOLLIN, on_mdns_packet, m); + if (r < 0) + goto fail; + + return m->mdns_ipv4_fd; + +fail: + m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); + return r; +} + +int manager_mdns_ipv6_fd(Manager *m) { + union sockaddr_union sa = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(MDNS_PORT), + }; + static const int one = 1, ttl = 255; + int r; + + assert(m); + + if (m->mdns_ipv6_fd >= 0) + return m->mdns_ipv6_fd; + + m->mdns_ipv6_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->mdns_ipv6_fd < 0) + return -errno; + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->mdns_ipv6_fd, &sa.sa, sizeof(sa.in6)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->mdns_ipv6_event_source, m->mdns_ipv6_fd, EPOLLIN, on_mdns_packet, m); + if (r < 0) + goto fail; + + return m->mdns_ipv6_fd; + +fail: + m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); + return r; +} diff --git a/src/grp-resolve/systemd-resolved/resolved-mdns.h b/src/grp-resolve/systemd-resolved/resolved-mdns.h new file mode 100644 index 0000000000..5d274648f4 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-mdns.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Daniel Mack + + 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 "resolved-manager.h" + +#define MDNS_PORT 5353 + +int manager_mdns_ipv4_fd(Manager *m); +int manager_mdns_ipv6_fd(Manager *m); + +void manager_mdns_stop(Manager *m); +int manager_mdns_start(Manager *m); diff --git a/src/grp-resolve/systemd-resolved/resolved-resolv-conf.c b/src/grp-resolve/systemd-resolved/resolved-resolv-conf.c new file mode 100644 index 0000000000..ff03acc772 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-resolv-conf.c @@ -0,0 +1,267 @@ +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 "alloc-util.h" +#include "dns-domain.h" +#include "fd-util.h" +#include "fileio-label.h" +#include "fileio.h" +#include "ordered-set.h" +#include "resolved-conf.h" +#include "resolved-resolv-conf.h" +#include "string-util.h" +#include "strv.h" + +int manager_read_resolv_conf(Manager *m) { + _cleanup_fclose_ FILE *f = NULL; + struct stat st, own; + char line[LINE_MAX]; + usec_t t; + int r; + + assert(m); + + /* Reads the system /etc/resolv.conf, if it exists and is not + * symlinked to our own resolv.conf instance */ + + if (!m->read_resolv_conf) + return 0; + + r = stat("/etc/resolv.conf", &st); + if (r < 0) { + if (errno == ENOENT) + return 0; + + r = log_warning_errno(errno, "Failed to stat /etc/resolv.conf: %m"); + goto clear; + } + + /* Have we already seen the file? */ + t = timespec_load(&st.st_mtim); + if (t == m->resolv_conf_mtime) + return 0; + + /* Is it symlinked to our own file? */ + if (stat("/run/systemd/resolve/resolv.conf", &own) >= 0 && + st.st_dev == own.st_dev && + st.st_ino == own.st_ino) + return 0; + + f = fopen("/etc/resolv.conf", "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + r = log_warning_errno(errno, "Failed to open /etc/resolv.conf: %m"); + goto clear; + } + + if (fstat(fileno(f), &st) < 0) { + r = log_error_errno(errno, "Failed to stat open file: %m"); + goto clear; + } + + dns_server_mark_all(m->dns_servers); + dns_search_domain_mark_all(m->search_domains); + + FOREACH_LINE(line, f, r = -errno; goto clear) { + const char *a; + char *l; + + l = strstrip(line); + if (*l == '#' || *l == ';') + continue; + + a = first_word(l, "nameserver"); + if (a) { + r = manager_add_dns_server_by_string(m, DNS_SERVER_SYSTEM, a); + if (r < 0) + log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring.", a); + + continue; + } + + a = first_word(l, "domain"); + if (!a) /* We treat "domain" lines, and "search" lines as equivalent, and add both to our list. */ + a = first_word(l, "search"); + if (a) { + r = manager_parse_search_domains_and_warn(m, a); + if (r < 0) + log_warning_errno(r, "Failed to parse search domain string '%s', ignoring.", a); + } + } + + m->resolv_conf_mtime = t; + + /* Flush out all servers and search domains that are still + * marked. Those are then ones that didn't appear in the new + * /etc/resolv.conf */ + dns_server_unlink_marked(m->dns_servers); + dns_search_domain_unlink_marked(m->search_domains); + + /* Whenever /etc/resolv.conf changes, start using the first + * DNS server of it. This is useful to deal with broken + * network managing implementations (like NetworkManager), + * that when connecting to a VPN place both the VPN DNS + * servers and the local ones in /etc/resolv.conf. Without + * resetting the DNS server to use back to the first entry we + * will continue to use the local one thus being unable to + * resolve VPN domains. */ + manager_set_dns_server(m, m->dns_servers); + + /* Unconditionally flush the cache when /etc/resolv.conf is + * modified, even if the data it contained was completely + * identical to the previous version we used. We do this + * because altering /etc/resolv.conf is typically done when + * the network configuration changes, and that should be + * enough to flush the global unicast DNS cache. */ + if (m->unicast_scope) + dns_cache_flush(&m->unicast_scope->cache); + + return 0; + +clear: + dns_server_unlink_all(m->dns_servers); + dns_search_domain_unlink_all(m->search_domains); + return r; +} + +static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) { + assert(s); + assert(f); + assert(count); + + (void) dns_server_string(s); + + if (!s->server_string) { + log_warning("Our of memory, or invalid DNS address. Ignoring server."); + return; + } + + if (*count == MAXNS) + fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f); + (*count)++; + + fprintf(f, "nameserver %s\n", s->server_string); +} + +static void write_resolv_conf_search( + const char *domain, + FILE *f, + unsigned *count, + unsigned *length) { + + assert(domain); + assert(f); + assert(length); + + if (*count >= MAXDNSRCH || + *length + strlen(domain) > 256) { + if (*count == MAXDNSRCH) + fputs(" # Too many search domains configured, remaining ones ignored.", f); + if (*length <= 256) + fputs(" # Total length of all search domains is too long, remaining ones ignored.", f); + + return; + } + + (*length) += strlen(domain); + (*count)++; + + fputc(' ', f); + fputs(domain, f); +} + +static int write_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) { + Iterator i; + + fputs("# This file is managed by systemd-resolved(8). Do not edit.\n#\n" + "# Third party programs must not access this file directly, but\n" + "# only through the symlink at /etc/resolv.conf. To manage\n" + "# resolv.conf(5) in a different way, replace the symlink by a\n" + "# static file or a different symlink.\n\n", f); + + if (ordered_set_isempty(dns)) + fputs("# No DNS servers known.\n", f); + else { + unsigned count = 0; + DnsServer *s; + + ORDERED_SET_FOREACH(s, dns, i) + write_resolv_conf_server(s, f, &count); + } + + if (!ordered_set_isempty(domains)) { + unsigned length = 0, count = 0; + char *domain; + + fputs("search", f); + ORDERED_SET_FOREACH(domain, domains, i) + write_resolv_conf_search(domain, f, &count, &length); + fputs("\n", f); + } + + return fflush_and_check(f); +} + +int manager_write_resolv_conf(Manager *m) { + + _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *domains = NULL; + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(m); + + /* Read the system /etc/resolv.conf first */ + manager_read_resolv_conf(m); + + /* Add the full list to a set, to filter out duplicates */ + r = manager_compile_dns_servers(m, &dns); + if (r < 0) + return r; + + r = manager_compile_search_domains(m, &domains); + if (r < 0) + return r; + + r = fopen_temporary_label(PRIVATE_RESOLV_CONF, PRIVATE_RESOLV_CONF, &f, &temp_path); + if (r < 0) + return r; + + fchmod(fileno(f), 0644); + + r = write_resolv_conf_contents(f, dns, domains); + if (r < 0) + goto fail; + + if (rename(temp_path, PRIVATE_RESOLV_CONF) < 0) { + r = -errno; + goto fail; + } + + return 0; + +fail: + (void) unlink(PRIVATE_RESOLV_CONF); + (void) unlink(temp_path); + return r; +} diff --git a/src/grp-resolve/systemd-resolved/resolved-resolv-conf.h b/src/grp-resolve/systemd-resolved/resolved-resolv-conf.h new file mode 100644 index 0000000000..75fa080e4c --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved-resolv-conf.h @@ -0,0 +1,27 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 "resolved-manager.h" + +#define PRIVATE_RESOLV_CONF "/run/systemd/resolve/resolv.conf" + +int manager_read_resolv_conf(Manager *m); +int manager_write_resolv_conf(Manager *m); diff --git a/src/grp-resolve/systemd-resolved/resolved.c b/src/grp-resolve/systemd-resolved/resolved.c new file mode 100644 index 0000000000..086a2fcac7 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved.c @@ -0,0 +1,112 @@ +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 + +#include "capability-util.h" +#include "mkdir.h" +#include "resolved-conf.h" +#include "resolved-manager.h" +#include "resolved-resolv-conf.h" +#include "selinux-util.h" +#include "signal-util.h" +#include "user-util.h" + +int main(int argc, char *argv[]) { + _cleanup_(manager_freep) Manager *m = NULL; + const char *user = "systemd-resolve"; + uid_t uid; + gid_t gid; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + if (argc != 1) { + log_error("This program takes no arguments."); + r = -EINVAL; + goto finish; + } + + umask(0022); + + r = mac_selinux_init(); + if (r < 0) { + log_error_errno(r, "SELinux setup failed: %m"); + goto finish; + } + + r = get_user_creds(&user, &uid, &gid, NULL, NULL); + if (r < 0) { + log_error_errno(r, "Cannot resolve user name %s: %m", user); + goto finish; + } + + /* Always create the directory where resolv.conf will live */ + r = mkdir_safe_label("/run/systemd/resolve", 0755, uid, gid); + if (r < 0) { + log_error_errno(r, "Could not create runtime directory: %m"); + goto finish; + } + + r = drop_privileges(uid, gid, 0); + if (r < 0) + goto finish; + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGUSR1, -1) >= 0); + + r = manager_new(&m); + if (r < 0) { + log_error_errno(r, "Could not create manager: %m"); + goto finish; + } + + r = manager_start(m); + if (r < 0) { + log_error_errno(r, "Failed to start manager: %m"); + goto finish; + } + + /* Write finish default resolv.conf to avoid a dangling + * symlink */ + r = manager_write_resolv_conf(m); + if (r < 0) + log_warning_errno(r, "Could not create "PRIVATE_RESOLV_CONF": %m"); + + sd_notify(false, + "READY=1\n" + "STATUS=Processing requests..."); + + r = sd_event_loop(m->event); + if (r < 0) { + log_error_errno(r, "Event loop failed: %m"); + goto finish; + } + + sd_event_get_exit_code(m->event, &r); + +finish: + sd_notify(false, + "STOPPING=1\n" + "STATUS=Shutting down..."); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-resolve/systemd-resolved/resolved.conf.in b/src/grp-resolve/systemd-resolved/resolved.conf.in new file mode 100644 index 0000000000..a288588924 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/resolved.conf.in @@ -0,0 +1,19 @@ +# This file is part of systemd. +# +# 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. +# +# Entries in this file show the compile time defaults. +# You can change settings by editing this file. +# Defaults can be restored by simply deleting this file. +# +# See resolved.conf(5) for details + +[Resolve] +#DNS= +#FallbackDNS=@DNS_SERVERS@ +#Domains= +#LLMNR=yes +#DNSSEC=@DEFAULT_DNSSEC_MODE@ diff --git a/src/grp-resolve/systemd-resolved/test-data/_443._tcp.fedoraproject.org.pkts b/src/grp-resolve/systemd-resolved/test-data/_443._tcp.fedoraproject.org.pkts new file mode 100644 index 0000000000..a383c6286d Binary files /dev/null and b/src/grp-resolve/systemd-resolved/test-data/_443._tcp.fedoraproject.org.pkts differ diff --git a/src/grp-resolve/systemd-resolved/test-data/_openpgpkey.fedoraproject.org.pkts b/src/grp-resolve/systemd-resolved/test-data/_openpgpkey.fedoraproject.org.pkts new file mode 100644 index 0000000000..15de02e997 Binary files /dev/null and b/src/grp-resolve/systemd-resolved/test-data/_openpgpkey.fedoraproject.org.pkts differ diff --git a/src/grp-resolve/systemd-resolved/test-data/fake-caa.pkts b/src/grp-resolve/systemd-resolved/test-data/fake-caa.pkts new file mode 100644 index 0000000000..1c3ecc5491 Binary files /dev/null and b/src/grp-resolve/systemd-resolved/test-data/fake-caa.pkts differ diff --git a/src/grp-resolve/systemd-resolved/test-data/fedoraproject.org.pkts b/src/grp-resolve/systemd-resolved/test-data/fedoraproject.org.pkts new file mode 100644 index 0000000000..17874844d9 Binary files /dev/null and b/src/grp-resolve/systemd-resolved/test-data/fedoraproject.org.pkts differ diff --git a/src/grp-resolve/systemd-resolved/test-data/gandi.net.pkts b/src/grp-resolve/systemd-resolved/test-data/gandi.net.pkts new file mode 100644 index 0000000000..5ef51e0c8e Binary files /dev/null and b/src/grp-resolve/systemd-resolved/test-data/gandi.net.pkts differ diff --git a/src/grp-resolve/systemd-resolved/test-data/google.com.pkts b/src/grp-resolve/systemd-resolved/test-data/google.com.pkts new file mode 100644 index 0000000000..f98c4cd855 Binary files /dev/null and b/src/grp-resolve/systemd-resolved/test-data/google.com.pkts differ diff --git a/src/grp-resolve/systemd-resolved/test-data/kyhwana.org.pkts b/src/grp-resolve/systemd-resolved/test-data/kyhwana.org.pkts new file mode 100644 index 0000000000..e28a725c9a Binary files /dev/null and b/src/grp-resolve/systemd-resolved/test-data/kyhwana.org.pkts differ diff --git a/src/grp-resolve/systemd-resolved/test-data/root.pkts b/src/grp-resolve/systemd-resolved/test-data/root.pkts new file mode 100644 index 0000000000..54ba668c75 Binary files /dev/null and b/src/grp-resolve/systemd-resolved/test-data/root.pkts differ diff --git a/src/grp-resolve/systemd-resolved/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts b/src/grp-resolve/systemd-resolved/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts new file mode 100644 index 0000000000..a854249532 Binary files /dev/null and b/src/grp-resolve/systemd-resolved/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts differ diff --git a/src/grp-resolve/systemd-resolved/test-data/teamits.com.pkts b/src/grp-resolve/systemd-resolved/test-data/teamits.com.pkts new file mode 100644 index 0000000000..11deb39677 Binary files /dev/null and b/src/grp-resolve/systemd-resolved/test-data/teamits.com.pkts differ diff --git a/src/grp-resolve/systemd-resolved/test-data/zbyszek@fedoraproject.org.pkts b/src/grp-resolve/systemd-resolved/test-data/zbyszek@fedoraproject.org.pkts new file mode 100644 index 0000000000..f0a6f982df Binary files /dev/null and b/src/grp-resolve/systemd-resolved/test-data/zbyszek@fedoraproject.org.pkts differ diff --git a/src/grp-resolve/systemd-resolved/test-dns-packet.c b/src/grp-resolve/systemd-resolved/test-dns-packet.c new file mode 100644 index 0000000000..c232a69ce1 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/test-dns-packet.c @@ -0,0 +1,114 @@ +/*** + This file is part of systemd + + Copyright 2016 Zbigniew Jędrzejewski-Szmek + + 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 + +#include "alloc-util.h" +#include "fileio.h" +#include "glob-util.h" +#include "log.h" +#include "macro.h" +#include "resolved-dns-packet.h" +#include "resolved-dns-rr.h" +#include "string-util.h" +#include "strv.h" + +#define HASH_KEY SD_ID128_MAKE(d3,1e,48,90,4b,fa,4c,fe,af,9d,d5,a1,d7,2e,8a,b1) + +static uint64_t hash(DnsResourceRecord *rr) { + struct siphash state; + + siphash24_init(&state, HASH_KEY.bytes); + dns_resource_record_hash_func(rr, &state); + return siphash24_finalize(&state); +} + +static void test_packet_from_file(const char* filename, bool canonical) { + _cleanup_free_ char *data = NULL; + size_t data_size, packet_size, offset; + + assert_se(read_full_file(filename, &data, &data_size) >= 0); + assert_se(data); + assert_se(data_size > 8); + + log_info("============== %s %s==============", filename, canonical ? "canonical " : ""); + + for (offset = 0; offset < data_size; offset += 8 + packet_size) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL, *p2 = NULL; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL, *rr2 = NULL; + const char *s, *s2; + uint64_t hash1, hash2; + + packet_size = le64toh( *(uint64_t*)(data + offset) ); + assert_se(packet_size > 0); + assert_se(offset + 8 + packet_size <= data_size); + + assert_se(dns_packet_new(&p, DNS_PROTOCOL_DNS, 0) >= 0); + + assert_se(dns_packet_append_blob(p, data + offset + 8, packet_size, NULL) >= 0); + assert_se(dns_packet_read_rr(p, &rr, NULL, NULL) >= 0); + + s = dns_resource_record_to_string(rr); + assert_se(s); + puts(s); + + hash1 = hash(rr); + + assert_se(dns_resource_record_to_wire_format(rr, canonical) >= 0); + + assert_se(dns_packet_new(&p2, DNS_PROTOCOL_DNS, 0) >= 0); + assert_se(dns_packet_append_blob(p2, rr->wire_format, rr->wire_format_size, NULL) >= 0); + assert_se(dns_packet_read_rr(p2, &rr2, NULL, NULL) >= 0); + + s2 = dns_resource_record_to_string(rr); + assert_se(s2); + assert_se(streq(s, s2)); + + hash2 = hash(rr); + assert_se(hash1 == hash2); + } +} + +int main(int argc, char **argv) { + int i, N; + _cleanup_globfree_ glob_t g = {}; + char **fnames; + + log_parse_environment(); + + if (argc >= 2) { + N = argc - 1; + fnames = argv + 1; + } else { + assert_se(glob(RESOLVE_TEST_DIR "/*.pkts", GLOB_NOSORT, NULL, &g) == 0); + N = g.gl_pathc; + fnames = g.gl_pathv; + } + + for (i = 0; i < N; i++) { + test_packet_from_file(fnames[i], false); + puts(""); + test_packet_from_file(fnames[i], true); + if (i + 1 < N) + puts(""); + } + + return EXIT_SUCCESS; +} diff --git a/src/grp-resolve/systemd-resolved/test-dnssec-complex.c b/src/grp-resolve/systemd-resolved/test-dnssec-complex.c new file mode 100644 index 0000000000..568400ac77 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/test-dnssec-complex.c @@ -0,0 +1,236 @@ +/*** + This file is part of systemd. + + Copyright 2016 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 + +#include "af-list.h" +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "dns-type.h" +#include "random-util.h" +#include "string-util.h" +#include "time-util.h" + +#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) + +static void prefix_random(const char *name, char **ret) { + uint64_t i, u; + char *m = NULL; + + u = 1 + (random_u64() & 3); + + for (i = 0; i < u; i++) { + _cleanup_free_ char *b = NULL; + char *x; + + assert_se(asprintf(&b, "x%" PRIu64 "x", random_u64())); + x = strjoin(b, ".", name, NULL); + assert_se(x); + + free(m); + m = x; + } + + *ret = m; + } + +static void test_rr_lookup(sd_bus *bus, const char *name, uint16_t type, const char *result) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *m = NULL; + int r; + + /* If the name starts with a dot, we prefix one to three random labels */ + if (startswith(name, ".")) { + prefix_random(name + 1, &m); + name = m; + } + + assert_se(sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveRecord") >= 0); + + assert_se(sd_bus_message_append(req, "isqqt", 0, name, DNS_CLASS_IN, type, UINT64_C(0)) >= 0); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + + if (r < 0) { + assert_se(result); + assert_se(sd_bus_error_has_name(&error, result)); + log_info("[OK] %s/%s resulted in <%s>.", name, dns_type_to_string(type), error.name); + } else { + assert_se(!result); + log_info("[OK] %s/%s succeeded.", name, dns_type_to_string(type)); + } +} + +static void test_hostname_lookup(sd_bus *bus, const char *name, int family, const char *result) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *m = NULL; + const char *af; + int r; + + af = family == AF_UNSPEC ? "AF_UNSPEC" : af_to_name(family); + + /* If the name starts with a dot, we prefix one to three random labels */ + if (startswith(name, ".")) { + prefix_random(name + 1, &m); + name = m; + } + + assert_se(sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveHostname") >= 0); + + assert_se(sd_bus_message_append(req, "isit", 0, name, family, UINT64_C(0)) >= 0); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + + if (r < 0) { + assert_se(result); + assert_se(sd_bus_error_has_name(&error, result)); + log_info("[OK] %s/%s resulted in <%s>.", name, af, error.name); + } else { + assert_se(!result); + log_info("[OK] %s/%s succeeded.", name, af); + } + +} + +int main(int argc, char* argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + + /* Note that this is a manual test as it requires: + * + * Full network access + * A DNSSEC capable DNS server + * That zones contacted are still set up as they were when I wrote this. + */ + + assert_se(sd_bus_open_system(&bus) >= 0); + + /* Normally signed */ + test_rr_lookup(bus, "www.eurid.eu", DNS_TYPE_A, NULL); + test_hostname_lookup(bus, "www.eurid.eu", AF_UNSPEC, NULL); + + test_rr_lookup(bus, "sigok.verteiltesysteme.net", DNS_TYPE_A, NULL); + test_hostname_lookup(bus, "sigok.verteiltesysteme.net", AF_UNSPEC, NULL); + + /* Normally signed, NODATA */ + test_rr_lookup(bus, "www.eurid.eu", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); + test_rr_lookup(bus, "sigok.verteiltesysteme.net", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); + + /* Invalid signature */ + test_rr_lookup(bus, "sigfail.verteiltesysteme.net", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED); + test_hostname_lookup(bus, "sigfail.verteiltesysteme.net", AF_INET, BUS_ERROR_DNSSEC_FAILED); + + /* Invalid signature, RSA, wildcard */ + test_rr_lookup(bus, ".wilda.rhybar.0skar.cz", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED); + test_hostname_lookup(bus, ".wilda.rhybar.0skar.cz", AF_INET, BUS_ERROR_DNSSEC_FAILED); + + /* Invalid signature, ECDSA, wildcard */ + test_rr_lookup(bus, ".wilda.rhybar.ecdsa.0skar.cz", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED); + test_hostname_lookup(bus, ".wilda.rhybar.ecdsa.0skar.cz", AF_INET, BUS_ERROR_DNSSEC_FAILED); + + /* NXDOMAIN in NSEC domain */ + test_rr_lookup(bus, "hhh.nasa.gov", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "hhh.nasa.gov", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); + + /* wildcard, NSEC zone */ + test_rr_lookup(bus, ".wilda.nsec.0skar.cz", DNS_TYPE_A, NULL); + test_hostname_lookup(bus, ".wilda.nsec.0skar.cz", AF_INET, NULL); + + /* wildcard, NSEC zone, NODATA */ + test_rr_lookup(bus, ".wilda.nsec.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); + + /* wildcard, NSEC3 zone */ + test_rr_lookup(bus, ".wilda.0skar.cz", DNS_TYPE_A, NULL); + test_hostname_lookup(bus, ".wilda.0skar.cz", AF_INET, NULL); + + /* wildcard, NSEC3 zone, NODATA */ + test_rr_lookup(bus, ".wilda.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); + + /* wildcard, NSEC zone, CNAME */ + test_rr_lookup(bus, ".wild.nsec.0skar.cz", DNS_TYPE_A, NULL); + test_hostname_lookup(bus, ".wild.nsec.0skar.cz", AF_UNSPEC, NULL); + test_hostname_lookup(bus, ".wild.nsec.0skar.cz", AF_INET, NULL); + + /* wildcard, NSEC zone, NODATA, CNAME */ + test_rr_lookup(bus, ".wild.nsec.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); + + /* wildcard, NSEC3 zone, CNAME */ + test_rr_lookup(bus, ".wild.0skar.cz", DNS_TYPE_A, NULL); + test_hostname_lookup(bus, ".wild.0skar.cz", AF_UNSPEC, NULL); + test_hostname_lookup(bus, ".wild.0skar.cz", AF_INET, NULL); + + /* wildcard, NSEC3 zone, NODATA, CNAME */ + test_rr_lookup(bus, ".wild.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); + + /* NODATA due to empty non-terminal in NSEC domain */ + test_rr_lookup(bus, "herndon.nasa.gov", DNS_TYPE_A, BUS_ERROR_NO_SUCH_RR); + test_hostname_lookup(bus, "herndon.nasa.gov", AF_UNSPEC, BUS_ERROR_NO_SUCH_RR); + test_hostname_lookup(bus, "herndon.nasa.gov", AF_INET, BUS_ERROR_NO_SUCH_RR); + test_hostname_lookup(bus, "herndon.nasa.gov", AF_INET6, BUS_ERROR_NO_SUCH_RR); + + /* NXDOMAIN in NSEC root zone: */ + test_rr_lookup(bus, "jasdhjas.kjkfgjhfjg", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_INET, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN"); + + /* NXDOMAIN in NSEC3 .com zone: */ + test_rr_lookup(bus, "kjkfgjhfjgsdfdsfd.com", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_INET, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); + + /* Unsigned A */ + test_rr_lookup(bus, "poettering.de", DNS_TYPE_A, NULL); + test_rr_lookup(bus, "poettering.de", DNS_TYPE_AAAA, NULL); + test_hostname_lookup(bus, "poettering.de", AF_UNSPEC, NULL); + test_hostname_lookup(bus, "poettering.de", AF_INET, NULL); + test_hostname_lookup(bus, "poettering.de", AF_INET6, NULL); + +#ifdef HAVE_LIBIDN + /* Unsigned A with IDNA conversion necessary */ + test_hostname_lookup(bus, "pöttering.de", AF_UNSPEC, NULL); + test_hostname_lookup(bus, "pöttering.de", AF_INET, NULL); + test_hostname_lookup(bus, "pöttering.de", AF_INET6, NULL); +#endif + + /* DNAME, pointing to NXDOMAIN */ + test_rr_lookup(bus, ".ireallyhpoethisdoesnexist.xn--kprw13d.", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); + test_rr_lookup(bus, ".ireallyhpoethisdoesnexist.xn--kprw13d.", DNS_TYPE_RP, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_INET, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN"); + + return 0; +} diff --git a/src/grp-resolve/systemd-resolved/test-dnssec.c b/src/grp-resolve/systemd-resolved/test-dnssec.c new file mode 100644 index 0000000000..b3018e8239 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/test-dnssec.c @@ -0,0 +1,343 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 +#include + +#include "alloc-util.h" +#include "resolved-dns-dnssec.h" +#include "resolved-dns-rr.h" +#include "string-util.h" +#include "hexdecoct.h" + +static void test_dnssec_canonicalize_one(const char *original, const char *canonical, int r) { + char canonicalized[DNSSEC_CANONICAL_HOSTNAME_MAX]; + + assert_se(dnssec_canonicalize(original, canonicalized, sizeof(canonicalized)) == r); + if (r < 0) + return; + + assert_se(streq(canonicalized, canonical)); +} + +static void test_dnssec_canonicalize(void) { + test_dnssec_canonicalize_one("", ".", 1); + test_dnssec_canonicalize_one(".", ".", 1); + test_dnssec_canonicalize_one("foo", "foo.", 4); + test_dnssec_canonicalize_one("foo.", "foo.", 4); + test_dnssec_canonicalize_one("FOO.", "foo.", 4); + test_dnssec_canonicalize_one("FOO.bar.", "foo.bar.", 8); + test_dnssec_canonicalize_one("FOO..bar.", NULL, -EINVAL); +} + +#ifdef HAVE_GCRYPT + +static void test_dnssec_verify_dns_key(void) { + + static const uint8_t ds1_fprint[] = { + 0x46, 0x8B, 0xC8, 0xDD, 0xC7, 0xE8, 0x27, 0x03, 0x40, 0xBB, 0x8A, 0x1F, 0x3B, 0x2E, 0x45, 0x9D, + 0x80, 0x67, 0x14, 0x01, + }; + static const uint8_t ds2_fprint[] = { + 0x8A, 0xEE, 0x80, 0x47, 0x05, 0x5F, 0x83, 0xD1, 0x48, 0xBA, 0x8F, 0xF6, 0xDD, 0xA7, 0x60, 0xCE, + 0x94, 0xF7, 0xC7, 0x5E, 0x52, 0x4C, 0xF2, 0xE9, 0x50, 0xB9, 0x2E, 0xCB, 0xEF, 0x96, 0xB9, 0x98, + }; + static const uint8_t dnskey_blob[] = { + 0x03, 0x01, 0x00, 0x01, 0xa8, 0x12, 0xda, 0x4f, 0xd2, 0x7d, 0x54, 0x14, 0x0e, 0xcc, 0x5b, 0x5e, + 0x45, 0x9c, 0x96, 0x98, 0xc0, 0xc0, 0x85, 0x81, 0xb1, 0x47, 0x8c, 0x7d, 0xe8, 0x39, 0x50, 0xcc, + 0xc5, 0xd0, 0xf2, 0x00, 0x81, 0x67, 0x79, 0xf6, 0xcc, 0x9d, 0xad, 0x6c, 0xbb, 0x7b, 0x6f, 0x48, + 0x97, 0x15, 0x1c, 0xfd, 0x0b, 0xfe, 0xd3, 0xd7, 0x7d, 0x9f, 0x81, 0x26, 0xd3, 0xc5, 0x65, 0x49, + 0xcf, 0x46, 0x62, 0xb0, 0x55, 0x6e, 0x47, 0xc7, 0x30, 0xef, 0x51, 0xfb, 0x3e, 0xc6, 0xef, 0xde, + 0x27, 0x3f, 0xfa, 0x57, 0x2d, 0xa7, 0x1d, 0x80, 0x46, 0x9a, 0x5f, 0x14, 0xb3, 0xb0, 0x2c, 0xbe, + 0x72, 0xca, 0xdf, 0xb2, 0xff, 0x36, 0x5b, 0x4f, 0xec, 0x58, 0x8e, 0x8d, 0x01, 0xe9, 0xa9, 0xdf, + 0xb5, 0x60, 0xad, 0x52, 0x4d, 0xfc, 0xa9, 0x3e, 0x8d, 0x35, 0x95, 0xb3, 0x4e, 0x0f, 0xca, 0x45, + 0x1b, 0xf7, 0xef, 0x3a, 0x88, 0x25, 0x08, 0xc7, 0x4e, 0x06, 0xc1, 0x62, 0x1a, 0xce, 0xd8, 0x77, + 0xbd, 0x02, 0x65, 0xf8, 0x49, 0xfb, 0xce, 0xf6, 0xa8, 0x09, 0xfc, 0xde, 0xb2, 0x09, 0x9d, 0x39, + 0xf8, 0x63, 0x9c, 0x32, 0x42, 0x7c, 0xa0, 0x30, 0x86, 0x72, 0x7a, 0x4a, 0xc6, 0xd4, 0xb3, 0x2d, + 0x24, 0xef, 0x96, 0x3f, 0xc2, 0xda, 0xd3, 0xf2, 0x15, 0x6f, 0xda, 0x65, 0x4b, 0x81, 0x28, 0x68, + 0xf4, 0xfe, 0x3e, 0x71, 0x4f, 0x50, 0x96, 0x72, 0x58, 0xa1, 0x89, 0xdd, 0x01, 0x61, 0x39, 0x39, + 0xc6, 0x76, 0xa4, 0xda, 0x02, 0x70, 0x3d, 0xc0, 0xdc, 0x8d, 0x70, 0x72, 0x04, 0x90, 0x79, 0xd4, + 0xec, 0x65, 0xcf, 0x49, 0x35, 0x25, 0x3a, 0x14, 0x1a, 0x45, 0x20, 0xeb, 0x31, 0xaf, 0x92, 0xba, + 0x20, 0xd3, 0xcd, 0xa7, 0x13, 0x44, 0xdc, 0xcf, 0xf0, 0x27, 0x34, 0xb9, 0xe7, 0x24, 0x6f, 0x73, + 0xe7, 0xea, 0x77, 0x03, + }; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *dnskey = NULL, *ds1 = NULL, *ds2 = NULL; + + /* The two DS RRs in effect for nasa.gov on 2015-12-01. */ + ds1 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "nasa.gov"); + assert_se(ds1); + + ds1->ds.key_tag = 47857; + ds1->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; + ds1->ds.digest_type = DNSSEC_DIGEST_SHA1; + ds1->ds.digest_size = sizeof(ds1_fprint); + ds1->ds.digest = memdup(ds1_fprint, ds1->ds.digest_size); + assert_se(ds1->ds.digest); + + log_info("DS1: %s", strna(dns_resource_record_to_string(ds1))); + + ds2 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "NASA.GOV"); + assert_se(ds2); + + ds2->ds.key_tag = 47857; + ds2->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; + ds2->ds.digest_type = DNSSEC_DIGEST_SHA256; + ds2->ds.digest_size = sizeof(ds2_fprint); + ds2->ds.digest = memdup(ds2_fprint, ds2->ds.digest_size); + assert_se(ds2->ds.digest); + + log_info("DS2: %s", strna(dns_resource_record_to_string(ds2))); + + dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nasa.GOV"); + assert_se(dnskey); + + dnskey->dnskey.flags = 257; + dnskey->dnskey.protocol = 3; + dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; + dnskey->dnskey.key_size = sizeof(dnskey_blob); + dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); + assert_se(dnskey->dnskey.key); + + log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); + log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); + + assert_se(dnssec_verify_dnskey_by_ds(dnskey, ds1, false) > 0); + assert_se(dnssec_verify_dnskey_by_ds(dnskey, ds2, false) > 0); +} + +static void test_dnssec_verify_rrset(void) { + + static const uint8_t signature_blob[] = { + 0x7f, 0x79, 0xdd, 0x5e, 0x89, 0x79, 0x18, 0xd0, 0x34, 0x86, 0x8c, 0x72, 0x77, 0x75, 0x48, 0x4d, + 0xc3, 0x7d, 0x38, 0x04, 0xab, 0xcd, 0x9e, 0x4c, 0x82, 0xb0, 0x92, 0xca, 0xe9, 0x66, 0xe9, 0x6e, + 0x47, 0xc7, 0x68, 0x8c, 0x94, 0xf6, 0x69, 0xcb, 0x75, 0x94, 0xe6, 0x30, 0xa6, 0xfb, 0x68, 0x64, + 0x96, 0x1a, 0x84, 0xe1, 0xdc, 0x16, 0x4c, 0x83, 0x6c, 0x44, 0xf2, 0x74, 0x4d, 0x74, 0x79, 0x8f, + 0xf3, 0xf4, 0x63, 0x0d, 0xef, 0x5a, 0xe7, 0xe2, 0xfd, 0xf2, 0x2b, 0x38, 0x7c, 0x28, 0x96, 0x9d, + 0xb6, 0xcd, 0x5c, 0x3b, 0x57, 0xe2, 0x24, 0x78, 0x65, 0xd0, 0x9e, 0x77, 0x83, 0x09, 0x6c, 0xff, + 0x3d, 0x52, 0x3f, 0x6e, 0xd1, 0xed, 0x2e, 0xf9, 0xee, 0x8e, 0xa6, 0xbe, 0x9a, 0xa8, 0x87, 0x76, + 0xd8, 0x77, 0xcc, 0x96, 0xa0, 0x98, 0xa1, 0xd1, 0x68, 0x09, 0x43, 0xcf, 0x56, 0xd9, 0xd1, 0x66, + }; + + static const uint8_t dnskey_blob[] = { + 0x03, 0x01, 0x00, 0x01, 0x9b, 0x49, 0x9b, 0xc1, 0xf9, 0x9a, 0xe0, 0x4e, 0xcf, 0xcb, 0x14, 0x45, + 0x2e, 0xc9, 0xf9, 0x74, 0xa7, 0x18, 0xb5, 0xf3, 0xde, 0x39, 0x49, 0xdf, 0x63, 0x33, 0x97, 0x52, + 0xe0, 0x8e, 0xac, 0x50, 0x30, 0x8e, 0x09, 0xd5, 0x24, 0x3d, 0x26, 0xa4, 0x49, 0x37, 0x2b, 0xb0, + 0x6b, 0x1b, 0xdf, 0xde, 0x85, 0x83, 0xcb, 0x22, 0x4e, 0x60, 0x0a, 0x91, 0x1a, 0x1f, 0xc5, 0x40, + 0xb1, 0xc3, 0x15, 0xc1, 0x54, 0x77, 0x86, 0x65, 0x53, 0xec, 0x10, 0x90, 0x0c, 0x91, 0x00, 0x5e, + 0x15, 0xdc, 0x08, 0x02, 0x4c, 0x8c, 0x0d, 0xc0, 0xac, 0x6e, 0xc4, 0x3e, 0x1b, 0x80, 0x19, 0xe4, + 0xf7, 0x5f, 0x77, 0x51, 0x06, 0x87, 0x61, 0xde, 0xa2, 0x18, 0x0f, 0x40, 0x8b, 0x79, 0x72, 0xfa, + 0x8d, 0x1a, 0x44, 0x47, 0x0d, 0x8e, 0x3a, 0x2d, 0xc7, 0x39, 0xbf, 0x56, 0x28, 0x97, 0xd9, 0x20, + 0x4f, 0x00, 0x51, 0x3b, + }; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *a = NULL, *rrsig = NULL, *dnskey = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + DnssecResult result; + + a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "nAsA.gov"); + assert_se(a); + + a->a.in_addr.s_addr = inet_addr("52.0.14.116"); + + log_info("A: %s", strna(dns_resource_record_to_string(a))); + + rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV."); + assert_se(rrsig); + + rrsig->rrsig.type_covered = DNS_TYPE_A; + rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256; + rrsig->rrsig.labels = 2; + rrsig->rrsig.original_ttl = 600; + rrsig->rrsig.expiration = 0x5683135c; + rrsig->rrsig.inception = 0x565b7da8; + rrsig->rrsig.key_tag = 63876; + rrsig->rrsig.signer = strdup("Nasa.Gov."); + assert_se(rrsig->rrsig.signer); + rrsig->rrsig.signature_size = sizeof(signature_blob); + rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size); + assert_se(rrsig->rrsig.signature); + + log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig))); + + dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV"); + assert_se(dnskey); + + dnskey->dnskey.flags = 256; + dnskey->dnskey.protocol = 3; + dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; + dnskey->dnskey.key_size = sizeof(dnskey_blob); + dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); + assert_se(dnskey->dnskey.key); + + log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); + log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); + + assert_se(dnssec_key_match_rrsig(a->key, rrsig) > 0); + assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0); + + answer = dns_answer_new(1); + assert_se(answer); + assert_se(dns_answer_add(answer, a, 0, DNS_ANSWER_AUTHENTICATED) >= 0); + + /* Validate the RR as it if was 2015-12-2 today */ + assert_se(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, 1449092754*USEC_PER_SEC, &result) >= 0); + assert_se(result == DNSSEC_VALIDATED); +} + +static void test_dnssec_verify_rrset2(void) { + + static const uint8_t signature_blob[] = { + 0x48, 0x45, 0xc8, 0x8b, 0xc0, 0x14, 0x92, 0xf5, 0x15, 0xc6, 0x84, 0x9d, 0x2f, 0xe3, 0x32, 0x11, + 0x7d, 0xf1, 0xe6, 0x87, 0xb9, 0x42, 0xd3, 0x8b, 0x9e, 0xaf, 0x92, 0x31, 0x0a, 0x53, 0xad, 0x8b, + 0xa7, 0x5c, 0x83, 0x39, 0x8c, 0x28, 0xac, 0xce, 0x6e, 0x9c, 0x18, 0xe3, 0x31, 0x16, 0x6e, 0xca, + 0x38, 0x31, 0xaf, 0xd9, 0x94, 0xf1, 0x84, 0xb1, 0xdf, 0x5a, 0xc2, 0x73, 0x22, 0xf6, 0xcb, 0xa2, + 0xe7, 0x8c, 0x77, 0x0c, 0x74, 0x2f, 0xc2, 0x13, 0xb0, 0x93, 0x51, 0xa9, 0x4f, 0xae, 0x0a, 0xda, + 0x45, 0xcc, 0xfd, 0x43, 0x99, 0x36, 0x9a, 0x0d, 0x21, 0xe0, 0xeb, 0x30, 0x65, 0xd4, 0xa0, 0x27, + 0x37, 0x3b, 0xe4, 0xc1, 0xc5, 0xa1, 0x2a, 0xd1, 0x76, 0xc4, 0x7e, 0x64, 0x0e, 0x5a, 0xa6, 0x50, + 0x24, 0xd5, 0x2c, 0xcc, 0x6d, 0xe5, 0x37, 0xea, 0xbd, 0x09, 0x34, 0xed, 0x24, 0x06, 0xa1, 0x22, + }; + + static const uint8_t dnskey_blob[] = { + 0x03, 0x01, 0x00, 0x01, 0xc3, 0x7f, 0x1d, 0xd1, 0x1c, 0x97, 0xb1, 0x13, 0x34, 0x3a, 0x9a, 0xea, + 0xee, 0xd9, 0x5a, 0x11, 0x1b, 0x17, 0xc7, 0xe3, 0xd4, 0xda, 0x20, 0xbc, 0x5d, 0xba, 0x74, 0xe3, + 0x37, 0x99, 0xec, 0x25, 0xce, 0x93, 0x7f, 0xbd, 0x22, 0x73, 0x7e, 0x14, 0x71, 0xe0, 0x60, 0x07, + 0xd4, 0x39, 0x8b, 0x5e, 0xe9, 0xba, 0x25, 0xe8, 0x49, 0xe9, 0x34, 0xef, 0xfe, 0x04, 0x5c, 0xa5, + 0x27, 0xcd, 0xa9, 0xda, 0x70, 0x05, 0x21, 0xab, 0x15, 0x82, 0x24, 0xc3, 0x94, 0xf5, 0xd7, 0xb7, + 0xc4, 0x66, 0xcb, 0x32, 0x6e, 0x60, 0x2b, 0x55, 0x59, 0x28, 0x89, 0x8a, 0x72, 0xde, 0x88, 0x56, + 0x27, 0x95, 0xd9, 0xac, 0x88, 0x4f, 0x65, 0x2b, 0x68, 0xfc, 0xe6, 0x41, 0xc1, 0x1b, 0xef, 0x4e, + 0xd6, 0xc2, 0x0f, 0x64, 0x88, 0x95, 0x5e, 0xdd, 0x3a, 0x02, 0x07, 0x50, 0xa9, 0xda, 0xa4, 0x49, + 0x74, 0x62, 0xfe, 0xd7, + }; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *nsec = NULL, *rrsig = NULL, *dnskey = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + DnssecResult result; + + nsec = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC, "nasa.gov"); + assert_se(nsec); + + nsec->nsec.next_domain_name = strdup("3D-Printing.nasa.gov"); + assert_se(nsec->nsec.next_domain_name); + + nsec->nsec.types = bitmap_new(); + assert_se(nsec->nsec.types); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_A) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NS) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_SOA) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_MX) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_TXT) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_RRSIG) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NSEC) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_DNSKEY) >= 0); + assert_se(bitmap_set(nsec->nsec.types, 65534) >= 0); + + log_info("NSEC: %s", strna(dns_resource_record_to_string(nsec))); + + rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV."); + assert_se(rrsig); + + rrsig->rrsig.type_covered = DNS_TYPE_NSEC; + rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256; + rrsig->rrsig.labels = 2; + rrsig->rrsig.original_ttl = 300; + rrsig->rrsig.expiration = 0x5689002f; + rrsig->rrsig.inception = 0x56617230; + rrsig->rrsig.key_tag = 30390; + rrsig->rrsig.signer = strdup("Nasa.Gov."); + assert_se(rrsig->rrsig.signer); + rrsig->rrsig.signature_size = sizeof(signature_blob); + rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size); + assert_se(rrsig->rrsig.signature); + + log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig))); + + dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV"); + assert_se(dnskey); + + dnskey->dnskey.flags = 256; + dnskey->dnskey.protocol = 3; + dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; + dnskey->dnskey.key_size = sizeof(dnskey_blob); + dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); + assert_se(dnskey->dnskey.key); + + log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); + log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); + + assert_se(dnssec_key_match_rrsig(nsec->key, rrsig) > 0); + assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0); + + answer = dns_answer_new(1); + assert_se(answer); + assert_se(dns_answer_add(answer, nsec, 0, DNS_ANSWER_AUTHENTICATED) >= 0); + + /* Validate the RR as it if was 2015-12-11 today */ + assert_se(dnssec_verify_rrset(answer, nsec->key, rrsig, dnskey, 1449849318*USEC_PER_SEC, &result) >= 0); + assert_se(result == DNSSEC_VALIDATED); +} + +static void test_dnssec_nsec3_hash(void) { + static const uint8_t salt[] = { 0xB0, 0x1D, 0xFA, 0xCE }; + static const uint8_t next_hashed_name[] = { 0x84, 0x10, 0x26, 0x53, 0xc9, 0xfa, 0x4d, 0x85, 0x6c, 0x97, 0x82, 0xe2, 0x8f, 0xdf, 0x2d, 0x5e, 0x87, 0x69, 0xc4, 0x52 }; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + uint8_t h[DNSSEC_HASH_SIZE_MAX]; + _cleanup_free_ char *b = NULL; + int k; + + /* The NSEC3 RR for eurid.eu on 2015-12-14. */ + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC3, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM.eurid.eu."); + assert_se(rr); + + rr->nsec3.algorithm = DNSSEC_DIGEST_SHA1; + rr->nsec3.flags = 1; + rr->nsec3.iterations = 1; + rr->nsec3.salt = memdup(salt, sizeof(salt)); + assert_se(rr->nsec3.salt); + rr->nsec3.salt_size = sizeof(salt); + rr->nsec3.next_hashed_name = memdup(next_hashed_name, sizeof(next_hashed_name)); + assert_se(rr->nsec3.next_hashed_name); + rr->nsec3.next_hashed_name_size = sizeof(next_hashed_name); + + log_info("NSEC3: %s", strna(dns_resource_record_to_string(rr))); + + k = dnssec_nsec3_hash(rr, "eurid.eu", &h); + assert_se(k >= 0); + + b = base32hexmem(h, k, false); + assert_se(b); + assert_se(strcasecmp(b, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM") == 0); +} + +#endif + +int main(int argc, char*argv[]) { + + test_dnssec_canonicalize(); + +#ifdef HAVE_GCRYPT + test_dnssec_verify_dns_key(); + test_dnssec_verify_rrset(); + test_dnssec_verify_rrset2(); + test_dnssec_nsec3_hash(); +#endif + + return 0; +} diff --git a/src/grp-resolve/systemd-resolved/test-resolve-tables.c b/src/grp-resolve/systemd-resolved/test-resolve-tables.c new file mode 100644 index 0000000000..2d615130e1 --- /dev/null +++ b/src/grp-resolve/systemd-resolved/test-resolve-tables.c @@ -0,0 +1,64 @@ +/*** + This file is part of systemd + + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + 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 "dns-type.h" +#include "test-tables.h" + +int main(int argc, char **argv) { + uint16_t i; + + test_table_sparse(dns_type, DNS_TYPE); + + log_info("/* DNS_TYPE */"); + for (i = 0; i < _DNS_TYPE_MAX; i++) { + const char *s; + + s = dns_type_to_string(i); + assert_se(s == NULL || strlen(s) < _DNS_TYPE_STRING_MAX); + + if (s) + log_info("%-*s %s%s%s%s%s%s%s%s%s", + (int) _DNS_TYPE_STRING_MAX - 1, s, + dns_type_is_pseudo(i) ? "pseudo " : "", + dns_type_is_valid_query(i) ? "valid_query " : "", + dns_type_is_valid_rr(i) ? "is_valid_rr " : "", + dns_type_may_redirect(i) ? "may_redirect " : "", + dns_type_is_dnssec(i) ? "dnssec " : "", + dns_type_is_obsolete(i) ? "obsolete " : "", + dns_type_may_wildcard(i) ? "wildcard " : "", + dns_type_apex_only(i) ? "apex_only " : "", + dns_type_needs_authentication(i) ? "needs_authentication" : ""); + } + + log_info("/* DNS_CLASS */"); + for (i = 0; i < _DNS_CLASS_MAX; i++) { + const char *s; + + s = dns_class_to_string(i); + assert_se(s == NULL || strlen(s) < _DNS_CLASS_STRING_MAX); + + if (s) + log_info("%-*s %s%s", + (int) _DNS_CLASS_STRING_MAX - 1, s, + dns_class_is_pseudo(i) ? "is_pseudo " : "", + dns_class_is_valid_rr(i) ? "is_valid_rr " : ""); + } + + return EXIT_SUCCESS; +} diff --git a/src/grp-system/systemctl/Makefile b/src/grp-system/systemctl/Makefile new file mode 100644 index 0000000000..20bd4f069c --- /dev/null +++ b/src/grp-system/systemctl/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemctl_SOURCES = \ + src/systemctl/systemctl.c + +systemctl_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-system/systemctl/systemctl.c b/src/grp-system/systemctl/systemctl.c new file mode 100644 index 0000000000..c7c00bbbc3 --- /dev/null +++ b/src/grp-system/systemctl/systemctl.c @@ -0,0 +1,7815 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2013 Marc-Antoine Perennou + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-error.h" +#include "bus-message.h" +#include "bus-unit-util.h" +#include "bus-util.h" +#include "cgroup-show.h" +#include "cgroup-util.h" +#include "copy.h" +#include "dropin.h" +#include "efivars.h" +#include "env-util.h" +#include "exit-status.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "glob-util.h" +#include "hostname-util.h" +#include "initreq.h" +#include "install.h" +#include "io-util.h" +#include "list.h" +#include "locale-util.h" +#include "log.h" +#include "logs-show.h" +#include "macro.h" +#include "mkdir.h" +#include "pager.h" +#include "parse-util.h" +#include "path-lookup.h" +#include "path-util.h" +#include "process-util.h" +#include "rlimit-util.h" +#include "set.h" +#include "signal-util.h" +#include "socket-util.h" +#include "spawn-ask-password-agent.h" +#include "spawn-polkit-agent.h" +#include "special.h" +#include "stat-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "unit-name.h" +#include "user-util.h" +#include "util.h" +#include "utmp-wtmp.h" +#include "verbs.h" +#include "virt.h" + +static char **arg_types = NULL; +static char **arg_states = NULL; +static char **arg_properties = NULL; +static bool arg_all = false; +static enum dependency { + DEPENDENCY_FORWARD, + DEPENDENCY_REVERSE, + DEPENDENCY_AFTER, + DEPENDENCY_BEFORE, + _DEPENDENCY_MAX +} arg_dependency = DEPENDENCY_FORWARD; +static const char *arg_job_mode = "replace"; +static UnitFileScope arg_scope = UNIT_FILE_SYSTEM; +static bool arg_no_block = false; +static bool arg_no_legend = false; +static bool arg_no_pager = false; +static bool arg_no_wtmp = false; +static bool arg_no_sync = false; +static bool arg_no_wall = false; +static bool arg_no_reload = false; +static bool arg_value = false; +static bool arg_show_types = false; +static bool arg_ignore_inhibitors = false; +static bool arg_dry = false; +static bool arg_quiet = false; +static bool arg_full = false; +static bool arg_recursive = false; +static int arg_force = 0; +static bool arg_ask_password = false; +static bool arg_runtime = false; +static UnitFilePresetMode arg_preset_mode = UNIT_FILE_PRESET_FULL; +static char **arg_wall = NULL; +static const char *arg_kill_who = NULL; +static int arg_signal = SIGTERM; +static char *arg_root = NULL; +static usec_t arg_when = 0; +static enum action { + _ACTION_INVALID, + ACTION_SYSTEMCTL, + ACTION_HALT, + ACTION_POWEROFF, + ACTION_REBOOT, + ACTION_KEXEC, + ACTION_EXIT, + ACTION_SUSPEND, + ACTION_HIBERNATE, + ACTION_HYBRID_SLEEP, + ACTION_RUNLEVEL2, + ACTION_RUNLEVEL3, + ACTION_RUNLEVEL4, + ACTION_RUNLEVEL5, + ACTION_RESCUE, + ACTION_EMERGENCY, + ACTION_DEFAULT, + ACTION_RELOAD, + ACTION_REEXEC, + ACTION_RUNLEVEL, + ACTION_CANCEL_SHUTDOWN, + _ACTION_MAX +} arg_action = ACTION_SYSTEMCTL; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static const char *arg_host = NULL; +static unsigned arg_lines = 10; +static OutputMode arg_output = OUTPUT_SHORT; +static bool arg_plain = false; +static bool arg_firmware_setup = false; +static bool arg_now = false; + +static int daemon_reload(int argc, char *argv[], void* userdata); +static int halt_now(enum action a); +static int get_state_one_unit(sd_bus *bus, const char *name, UnitActiveState *active_state); + +static bool original_stdout_is_tty; + +typedef enum BusFocus { + BUS_FULL, /* The full bus indicated via --system or --user */ + BUS_MANAGER, /* The manager itself, possibly directly, possibly via the bus */ + _BUS_FOCUS_MAX +} BusFocus; + +static sd_bus *busses[_BUS_FOCUS_MAX] = {}; + +static int acquire_bus(BusFocus focus, sd_bus **ret) { + int r; + + assert(focus < _BUS_FOCUS_MAX); + assert(ret); + + /* We only go directly to the manager, if we are using a local transport */ + if (arg_transport != BUS_TRANSPORT_LOCAL) + focus = BUS_FULL; + + if (!busses[focus]) { + bool user; + + user = arg_scope != UNIT_FILE_SYSTEM; + + if (focus == BUS_MANAGER) + r = bus_connect_transport_systemd(arg_transport, arg_host, user, &busses[focus]); + else + r = bus_connect_transport(arg_transport, arg_host, user, &busses[focus]); + if (r < 0) + return log_error_errno(r, "Failed to connect to bus: %m"); + + (void) sd_bus_set_allow_interactive_authorization(busses[focus], arg_ask_password); + } + + *ret = busses[focus]; + return 0; +} + +static void release_busses(void) { + BusFocus w; + + for (w = 0; w < _BUS_FOCUS_MAX; w++) + busses[w] = sd_bus_flush_close_unref(busses[w]); +} + +static void ask_password_agent_open_if_enabled(void) { + + /* Open the password agent as a child process if necessary */ + + if (!arg_ask_password) + return; + + if (arg_scope != UNIT_FILE_SYSTEM) + return; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return; + + ask_password_agent_open(); +} + +static void polkit_agent_open_if_enabled(void) { + + /* Open the polkit agent as a child process if necessary */ + + if (!arg_ask_password) + return; + + if (arg_scope != UNIT_FILE_SYSTEM) + return; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return; + + polkit_agent_open(); +} + +static OutputFlags get_output_flags(void) { + return + arg_all * OUTPUT_SHOW_ALL | + arg_full * OUTPUT_FULL_WIDTH | + (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH | + colors_enabled() * OUTPUT_COLOR | + !arg_quiet * OUTPUT_WARN_CUTOFF; +} + +static int translate_bus_error_to_exit_status(int r, const sd_bus_error *error) { + assert(error); + + if (!sd_bus_error_is_set(error)) + return r; + + if (sd_bus_error_has_name(error, SD_BUS_ERROR_ACCESS_DENIED) || + sd_bus_error_has_name(error, BUS_ERROR_ONLY_BY_DEPENDENCY) || + sd_bus_error_has_name(error, BUS_ERROR_NO_ISOLATION) || + sd_bus_error_has_name(error, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE)) + return EXIT_NOPERMISSION; + + if (sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT)) + return EXIT_NOTINSTALLED; + + if (sd_bus_error_has_name(error, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE) || + sd_bus_error_has_name(error, SD_BUS_ERROR_NOT_SUPPORTED)) + return EXIT_NOTIMPLEMENTED; + + if (sd_bus_error_has_name(error, BUS_ERROR_LOAD_FAILED)) + return EXIT_NOTCONFIGURED; + + if (r != 0) + return r; + + return EXIT_FAILURE; +} + +static bool install_client_side(void) { + + /* Decides when to execute enable/disable/... operations + * client-side rather than server-side. */ + + if (running_in_chroot() > 0) + return true; + + if (sd_booted() <= 0) + return true; + + if (!isempty(arg_root)) + return true; + + if (arg_scope == UNIT_FILE_GLOBAL) + return true; + + /* Unsupported environment variable, mostly for debugging purposes */ + if (getenv_bool("SYSTEMCTL_INSTALL_CLIENT_SIDE") > 0) + return true; + + return false; +} + +static int compare_unit_info(const void *a, const void *b) { + const UnitInfo *u = a, *v = b; + const char *d1, *d2; + int r; + + /* First, order by machine */ + if (!u->machine && v->machine) + return -1; + if (u->machine && !v->machine) + return 1; + if (u->machine && v->machine) { + r = strcasecmp(u->machine, v->machine); + if (r != 0) + return r; + } + + /* Second, order by unit type */ + d1 = strrchr(u->id, '.'); + d2 = strrchr(v->id, '.'); + if (d1 && d2) { + r = strcasecmp(d1, d2); + if (r != 0) + return r; + } + + /* Third, order by name */ + return strcasecmp(u->id, v->id); +} + +static bool output_show_unit(const UnitInfo *u, char **patterns) { + assert(u); + + if (!strv_fnmatch_or_empty(patterns, u->id, FNM_NOESCAPE)) + return false; + + if (arg_types) { + const char *dot; + + dot = strrchr(u->id, '.'); + if (!dot) + return false; + + if (!strv_find(arg_types, dot+1)) + return false; + } + + if (arg_all) + return true; + + /* Note that '--all' is not purely a state filter, but also a + * filter that hides units that "follow" other units (which is + * used for device units that appear under different names). */ + if (!isempty(u->following)) + return false; + + if (!strv_isempty(arg_states)) + return true; + + /* By default show all units except the ones in inactive + * state and with no pending job */ + if (u->job_id > 0) + return true; + + if (streq(u->active_state, "inactive")) + return false; + + return true; +} + +static int output_units_list(const UnitInfo *unit_infos, unsigned c) { + unsigned circle_len = 0, id_len, max_id_len, load_len, active_len, sub_len, job_len, desc_len; + const UnitInfo *u; + unsigned n_shown = 0; + int job_count = 0; + + max_id_len = strlen("UNIT"); + load_len = strlen("LOAD"); + active_len = strlen("ACTIVE"); + sub_len = strlen("SUB"); + job_len = strlen("JOB"); + desc_len = 0; + + for (u = unit_infos; u < unit_infos + c; u++) { + max_id_len = MAX(max_id_len, strlen(u->id) + (u->machine ? strlen(u->machine)+1 : 0)); + load_len = MAX(load_len, strlen(u->load_state)); + active_len = MAX(active_len, strlen(u->active_state)); + sub_len = MAX(sub_len, strlen(u->sub_state)); + + if (u->job_id != 0) { + job_len = MAX(job_len, strlen(u->job_type)); + job_count++; + } + + if (!arg_no_legend && + (streq(u->active_state, "failed") || + STR_IN_SET(u->load_state, "error", "not-found", "masked"))) + circle_len = 2; + } + + if (!arg_full && original_stdout_is_tty) { + unsigned basic_len; + + id_len = MIN(max_id_len, 25u); + basic_len = circle_len + 5 + id_len + 5 + active_len + sub_len; + + if (job_count) + basic_len += job_len + 1; + + if (basic_len < (unsigned) columns()) { + unsigned extra_len, incr; + extra_len = columns() - basic_len; + + /* Either UNIT already got 25, or is fully satisfied. + * Grant up to 25 to DESC now. */ + incr = MIN(extra_len, 25u); + desc_len += incr; + extra_len -= incr; + + /* split the remaining space between UNIT and DESC, + * but do not give UNIT more than it needs. */ + if (extra_len > 0) { + incr = MIN(extra_len / 2, max_id_len - id_len); + id_len += incr; + desc_len += extra_len - incr; + } + } + } else + id_len = max_id_len; + + for (u = unit_infos; u < unit_infos + c; u++) { + _cleanup_free_ char *e = NULL, *j = NULL; + const char *on_loaded = "", *off_loaded = ""; + const char *on_active = "", *off_active = ""; + const char *on_circle = "", *off_circle = ""; + const char *id; + bool circle = false; + + if (!n_shown && !arg_no_legend) { + + if (circle_len > 0) + fputs(" ", stdout); + + printf("%-*s %-*s %-*s %-*s ", + id_len, "UNIT", + load_len, "LOAD", + active_len, "ACTIVE", + sub_len, "SUB"); + + if (job_count) + printf("%-*s ", job_len, "JOB"); + + if (!arg_full && arg_no_pager) + printf("%.*s\n", desc_len, "DESCRIPTION"); + else + printf("%s\n", "DESCRIPTION"); + } + + n_shown++; + + if (STR_IN_SET(u->load_state, "error", "not-found", "masked") && !arg_plain) { + on_loaded = ansi_highlight_red(); + on_circle = ansi_highlight_yellow(); + off_loaded = off_circle = ansi_normal(); + circle = true; + } else if (streq(u->active_state, "failed") && !arg_plain) { + on_circle = on_active = ansi_highlight_red(); + off_circle = off_active = ansi_normal(); + circle = true; + } + + if (u->machine) { + j = strjoin(u->machine, ":", u->id, NULL); + if (!j) + return log_oom(); + + id = j; + } else + id = u->id; + + if (arg_full) { + e = ellipsize(id, id_len, 33); + if (!e) + return log_oom(); + + id = e; + } + + if (circle_len > 0) + printf("%s%s%s ", on_circle, circle ? special_glyph(BLACK_CIRCLE) : " ", off_circle); + + printf("%s%-*s%s %s%-*s%s %s%-*s %-*s%s %-*s", + on_active, id_len, id, off_active, + on_loaded, load_len, u->load_state, off_loaded, + on_active, active_len, u->active_state, + sub_len, u->sub_state, off_active, + job_count ? job_len + 1 : 0, u->job_id ? u->job_type : ""); + + if (desc_len > 0) + printf("%.*s\n", desc_len, u->description); + else + printf("%s\n", u->description); + } + + if (!arg_no_legend) { + const char *on, *off; + + if (n_shown) { + puts("\n" + "LOAD = Reflects whether the unit definition was properly loaded.\n" + "ACTIVE = The high-level unit activation state, i.e. generalization of SUB.\n" + "SUB = The low-level unit activation state, values depend on unit type."); + puts(job_count ? "JOB = Pending job for the unit.\n" : ""); + on = ansi_highlight(); + off = ansi_normal(); + } else { + on = ansi_highlight_red(); + off = ansi_normal(); + } + + if (arg_all) + printf("%s%u loaded units listed.%s\n" + "To show all installed unit files use 'systemctl list-unit-files'.\n", + on, n_shown, off); + else + printf("%s%u loaded units listed.%s Pass --all to see loaded but inactive units, too.\n" + "To show all installed unit files use 'systemctl list-unit-files'.\n", + on, n_shown, off); + } + + return 0; +} + +static int get_unit_list( + sd_bus *bus, + const char *machine, + char **patterns, + UnitInfo **unit_infos, + int c, + sd_bus_message **_reply) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + size_t size = c; + int r; + UnitInfo u; + bool fallback = false; + + assert(bus); + assert(unit_infos); + assert(_reply); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnitsByPatterns"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, arg_states); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, patterns); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) { + /* Fallback to legacy ListUnitsFiltered method */ + fallback = true; + log_debug_errno(r, "Failed to list units: %s Falling back to ListUnitsFiltered method.", bus_error_message(&error, r)); + m = sd_bus_message_unref(m); + sd_bus_error_free(&error); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnitsFiltered"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, arg_states); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + } + if (r < 0) + return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = bus_parse_unit_info(reply, &u)) > 0) { + u.machine = machine; + + if (!output_show_unit(&u, fallback ? patterns : NULL)) + continue; + + if (!GREEDY_REALLOC(*unit_infos, size, c+1)) + return log_oom(); + + (*unit_infos)[c++] = u; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + *_reply = reply; + reply = NULL; + + return c; +} + +static void message_set_freep(Set **set) { + sd_bus_message *m; + + while ((m = set_steal_first(*set))) + sd_bus_message_unref(m); + + set_free(*set); +} + +static int get_unit_list_recursive( + sd_bus *bus, + char **patterns, + UnitInfo **_unit_infos, + Set **_replies, + char ***_machines) { + + _cleanup_free_ UnitInfo *unit_infos = NULL; + _cleanup_(message_set_freep) Set *replies; + sd_bus_message *reply; + int c, r; + + assert(bus); + assert(_replies); + assert(_unit_infos); + assert(_machines); + + replies = set_new(NULL); + if (!replies) + return log_oom(); + + c = get_unit_list(bus, NULL, patterns, &unit_infos, 0, &reply); + if (c < 0) + return c; + + r = set_put(replies, reply); + if (r < 0) { + sd_bus_message_unref(reply); + return log_oom(); + } + + if (arg_recursive) { + _cleanup_strv_free_ char **machines = NULL; + char **i; + + r = sd_get_machine_names(&machines); + if (r < 0) + return log_error_errno(r, "Failed to get machine names: %m"); + + STRV_FOREACH(i, machines) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *container = NULL; + int k; + + r = sd_bus_open_system_machine(&container, *i); + if (r < 0) { + log_warning_errno(r, "Failed to connect to container %s, ignoring: %m", *i); + continue; + } + + k = get_unit_list(container, *i, patterns, &unit_infos, c, &reply); + if (k < 0) + return k; + + c = k; + + r = set_put(replies, reply); + if (r < 0) { + sd_bus_message_unref(reply); + return log_oom(); + } + } + + *_machines = machines; + machines = NULL; + } else + *_machines = NULL; + + *_unit_infos = unit_infos; + unit_infos = NULL; + + *_replies = replies; + replies = NULL; + + return c; +} + +static int list_units(int argc, char *argv[], void *userdata) { + _cleanup_free_ UnitInfo *unit_infos = NULL; + _cleanup_(message_set_freep) Set *replies = NULL; + _cleanup_strv_free_ char **machines = NULL; + sd_bus *bus; + int r; + + pager_open(arg_no_pager, false); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines); + if (r < 0) + return r; + + qsort_safe(unit_infos, r, sizeof(UnitInfo), compare_unit_info); + return output_units_list(unit_infos, r); +} + +static int get_triggered_units( + sd_bus *bus, + const char* path, + char*** ret) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(path); + assert(ret); + + r = sd_bus_get_property_strv( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "Triggers", + &error, + ret); + if (r < 0) + return log_error_errno(r, "Failed to determine triggers: %s", bus_error_message(&error, r)); + + return 0; +} + +static int get_listening( + sd_bus *bus, + const char* unit_path, + char*** listening) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *type, *path; + int r, n = 0; + + r = sd_bus_get_property( + bus, + "org.freedesktop.systemd1", + unit_path, + "org.freedesktop.systemd1.Socket", + "Listen", + &error, + &reply, + "a(ss)"); + if (r < 0) + return log_error_errno(r, "Failed to get list of listening sockets: %s", bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(ss)", &type, &path)) > 0) { + + r = strv_extend(listening, type); + if (r < 0) + return log_oom(); + + r = strv_extend(listening, path); + if (r < 0) + return log_oom(); + + n++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + return n; +} + +struct socket_info { + const char *machine; + const char* id; + + char* type; + char* path; + + /* Note: triggered is a list here, although it almost certainly + * will always be one unit. Nevertheless, dbus API allows for multiple + * values, so let's follow that. */ + char** triggered; + + /* The strv above is shared. free is set only in the first one. */ + bool own_triggered; +}; + +static int socket_info_compare(const struct socket_info *a, const struct socket_info *b) { + int o; + + assert(a); + assert(b); + + if (!a->machine && b->machine) + return -1; + if (a->machine && !b->machine) + return 1; + if (a->machine && b->machine) { + o = strcasecmp(a->machine, b->machine); + if (o != 0) + return o; + } + + o = strcmp(a->path, b->path); + if (o == 0) + o = strcmp(a->type, b->type); + + return o; +} + +static int output_sockets_list(struct socket_info *socket_infos, unsigned cs) { + struct socket_info *s; + unsigned pathlen = strlen("LISTEN"), + typelen = strlen("TYPE") * arg_show_types, + socklen = strlen("UNIT"), + servlen = strlen("ACTIVATES"); + const char *on, *off; + + for (s = socket_infos; s < socket_infos + cs; s++) { + unsigned tmp = 0; + char **a; + + socklen = MAX(socklen, strlen(s->id)); + if (arg_show_types) + typelen = MAX(typelen, strlen(s->type)); + pathlen = MAX(pathlen, strlen(s->path) + (s->machine ? strlen(s->machine)+1 : 0)); + + STRV_FOREACH(a, s->triggered) + tmp += strlen(*a) + 2*(a != s->triggered); + servlen = MAX(servlen, tmp); + } + + if (cs) { + if (!arg_no_legend) + printf("%-*s %-*.*s%-*s %s\n", + pathlen, "LISTEN", + typelen + arg_show_types, typelen + arg_show_types, "TYPE ", + socklen, "UNIT", + "ACTIVATES"); + + for (s = socket_infos; s < socket_infos + cs; s++) { + _cleanup_free_ char *j = NULL; + const char *path; + char **a; + + if (s->machine) { + j = strjoin(s->machine, ":", s->path, NULL); + if (!j) + return log_oom(); + path = j; + } else + path = s->path; + + if (arg_show_types) + printf("%-*s %-*s %-*s", + pathlen, path, typelen, s->type, socklen, s->id); + else + printf("%-*s %-*s", + pathlen, path, socklen, s->id); + STRV_FOREACH(a, s->triggered) + printf("%s %s", + a == s->triggered ? "" : ",", *a); + printf("\n"); + } + + on = ansi_highlight(); + off = ansi_normal(); + if (!arg_no_legend) + printf("\n"); + } else { + on = ansi_highlight_red(); + off = ansi_normal(); + } + + if (!arg_no_legend) { + printf("%s%u sockets listed.%s\n", on, cs, off); + if (!arg_all) + printf("Pass --all to see loaded but inactive sockets, too.\n"); + } + + return 0; +} + +static int list_sockets(int argc, char *argv[], void *userdata) { + _cleanup_(message_set_freep) Set *replies = NULL; + _cleanup_strv_free_ char **machines = NULL; + _cleanup_free_ UnitInfo *unit_infos = NULL; + _cleanup_free_ struct socket_info *socket_infos = NULL; + const UnitInfo *u; + struct socket_info *s; + unsigned cs = 0; + size_t size = 0; + int r = 0, n; + sd_bus *bus; + + pager_open(arg_no_pager, false); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + n = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines); + if (n < 0) + return n; + + for (u = unit_infos; u < unit_infos + n; u++) { + _cleanup_strv_free_ char **listening = NULL, **triggered = NULL; + int i, c; + + if (!endswith(u->id, ".socket")) + continue; + + r = get_triggered_units(bus, u->unit_path, &triggered); + if (r < 0) + goto cleanup; + + c = get_listening(bus, u->unit_path, &listening); + if (c < 0) { + r = c; + goto cleanup; + } + + if (!GREEDY_REALLOC(socket_infos, size, cs + c)) { + r = log_oom(); + goto cleanup; + } + + for (i = 0; i < c; i++) + socket_infos[cs + i] = (struct socket_info) { + .machine = u->machine, + .id = u->id, + .type = listening[i*2], + .path = listening[i*2 + 1], + .triggered = triggered, + .own_triggered = i==0, + }; + + /* from this point on we will cleanup those socket_infos */ + cs += c; + free(listening); + listening = triggered = NULL; /* avoid cleanup */ + } + + qsort_safe(socket_infos, cs, sizeof(struct socket_info), + (__compar_fn_t) socket_info_compare); + + output_sockets_list(socket_infos, cs); + + cleanup: + assert(cs == 0 || socket_infos); + for (s = socket_infos; s < socket_infos + cs; s++) { + free(s->type); + free(s->path); + if (s->own_triggered) + strv_free(s->triggered); + } + + return r; +} + +static int get_next_elapse( + sd_bus *bus, + const char *path, + dual_timestamp *next) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + dual_timestamp t; + int r; + + assert(bus); + assert(path); + assert(next); + + r = sd_bus_get_property_trivial( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Timer", + "NextElapseUSecMonotonic", + &error, + 't', + &t.monotonic); + if (r < 0) + return log_error_errno(r, "Failed to get next elapsation time: %s", bus_error_message(&error, r)); + + r = sd_bus_get_property_trivial( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Timer", + "NextElapseUSecRealtime", + &error, + 't', + &t.realtime); + if (r < 0) + return log_error_errno(r, "Failed to get next elapsation time: %s", bus_error_message(&error, r)); + + *next = t; + return 0; +} + +static int get_last_trigger( + sd_bus *bus, + const char *path, + usec_t *last) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(path); + assert(last); + + r = sd_bus_get_property_trivial( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Timer", + "LastTriggerUSec", + &error, + 't', + last); + if (r < 0) + return log_error_errno(r, "Failed to get last trigger time: %s", bus_error_message(&error, r)); + + return 0; +} + +struct timer_info { + const char* machine; + const char* id; + usec_t next_elapse; + usec_t last_trigger; + char** triggered; +}; + +static int timer_info_compare(const struct timer_info *a, const struct timer_info *b) { + int o; + + assert(a); + assert(b); + + if (!a->machine && b->machine) + return -1; + if (a->machine && !b->machine) + return 1; + if (a->machine && b->machine) { + o = strcasecmp(a->machine, b->machine); + if (o != 0) + return o; + } + + if (a->next_elapse < b->next_elapse) + return -1; + if (a->next_elapse > b->next_elapse) + return 1; + + return strcmp(a->id, b->id); +} + +static int output_timers_list(struct timer_info *timer_infos, unsigned n) { + struct timer_info *t; + unsigned + nextlen = strlen("NEXT"), + leftlen = strlen("LEFT"), + lastlen = strlen("LAST"), + passedlen = strlen("PASSED"), + unitlen = strlen("UNIT"), + activatelen = strlen("ACTIVATES"); + + const char *on, *off; + + assert(timer_infos || n == 0); + + for (t = timer_infos; t < timer_infos + n; t++) { + unsigned ul = 0; + char **a; + + if (t->next_elapse > 0) { + char tstamp[FORMAT_TIMESTAMP_MAX] = "", trel[FORMAT_TIMESTAMP_RELATIVE_MAX] = ""; + + format_timestamp(tstamp, sizeof(tstamp), t->next_elapse); + nextlen = MAX(nextlen, strlen(tstamp) + 1); + + format_timestamp_relative(trel, sizeof(trel), t->next_elapse); + leftlen = MAX(leftlen, strlen(trel)); + } + + if (t->last_trigger > 0) { + char tstamp[FORMAT_TIMESTAMP_MAX] = "", trel[FORMAT_TIMESTAMP_RELATIVE_MAX] = ""; + + format_timestamp(tstamp, sizeof(tstamp), t->last_trigger); + lastlen = MAX(lastlen, strlen(tstamp) + 1); + + format_timestamp_relative(trel, sizeof(trel), t->last_trigger); + passedlen = MAX(passedlen, strlen(trel)); + } + + unitlen = MAX(unitlen, strlen(t->id) + (t->machine ? strlen(t->machine)+1 : 0)); + + STRV_FOREACH(a, t->triggered) + ul += strlen(*a) + 2*(a != t->triggered); + + activatelen = MAX(activatelen, ul); + } + + if (n > 0) { + if (!arg_no_legend) + printf("%-*s %-*s %-*s %-*s %-*s %s\n", + nextlen, "NEXT", + leftlen, "LEFT", + lastlen, "LAST", + passedlen, "PASSED", + unitlen, "UNIT", + "ACTIVATES"); + + for (t = timer_infos; t < timer_infos + n; t++) { + _cleanup_free_ char *j = NULL; + const char *unit; + char tstamp1[FORMAT_TIMESTAMP_MAX] = "n/a", trel1[FORMAT_TIMESTAMP_RELATIVE_MAX] = "n/a"; + char tstamp2[FORMAT_TIMESTAMP_MAX] = "n/a", trel2[FORMAT_TIMESTAMP_RELATIVE_MAX] = "n/a"; + char **a; + + format_timestamp(tstamp1, sizeof(tstamp1), t->next_elapse); + format_timestamp_relative(trel1, sizeof(trel1), t->next_elapse); + + format_timestamp(tstamp2, sizeof(tstamp2), t->last_trigger); + format_timestamp_relative(trel2, sizeof(trel2), t->last_trigger); + + if (t->machine) { + j = strjoin(t->machine, ":", t->id, NULL); + if (!j) + return log_oom(); + unit = j; + } else + unit = t->id; + + printf("%-*s %-*s %-*s %-*s %-*s", + nextlen, tstamp1, leftlen, trel1, lastlen, tstamp2, passedlen, trel2, unitlen, unit); + + STRV_FOREACH(a, t->triggered) + printf("%s %s", + a == t->triggered ? "" : ",", *a); + printf("\n"); + } + + on = ansi_highlight(); + off = ansi_normal(); + if (!arg_no_legend) + printf("\n"); + } else { + on = ansi_highlight_red(); + off = ansi_normal(); + } + + if (!arg_no_legend) { + printf("%s%u timers listed.%s\n", on, n, off); + if (!arg_all) + printf("Pass --all to see loaded but inactive timers, too.\n"); + } + + return 0; +} + +static usec_t calc_next_elapse(dual_timestamp *nw, dual_timestamp *next) { + usec_t next_elapse; + + assert(nw); + assert(next); + + if (next->monotonic != USEC_INFINITY && next->monotonic > 0) { + usec_t converted; + + if (next->monotonic > nw->monotonic) + converted = nw->realtime + (next->monotonic - nw->monotonic); + else + converted = nw->realtime - (nw->monotonic - next->monotonic); + + if (next->realtime != USEC_INFINITY && next->realtime > 0) + next_elapse = MIN(converted, next->realtime); + else + next_elapse = converted; + + } else + next_elapse = next->realtime; + + return next_elapse; +} + +static int list_timers(int argc, char *argv[], void *userdata) { + _cleanup_(message_set_freep) Set *replies = NULL; + _cleanup_strv_free_ char **machines = NULL; + _cleanup_free_ struct timer_info *timer_infos = NULL; + _cleanup_free_ UnitInfo *unit_infos = NULL; + struct timer_info *t; + const UnitInfo *u; + size_t size = 0; + int n, c = 0; + dual_timestamp nw; + sd_bus *bus; + int r = 0; + + pager_open(arg_no_pager, false); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + n = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines); + if (n < 0) + return n; + + dual_timestamp_get(&nw); + + for (u = unit_infos; u < unit_infos + n; u++) { + _cleanup_strv_free_ char **triggered = NULL; + dual_timestamp next = DUAL_TIMESTAMP_NULL; + usec_t m, last = 0; + + if (!endswith(u->id, ".timer")) + continue; + + r = get_triggered_units(bus, u->unit_path, &triggered); + if (r < 0) + goto cleanup; + + r = get_next_elapse(bus, u->unit_path, &next); + if (r < 0) + goto cleanup; + + get_last_trigger(bus, u->unit_path, &last); + + if (!GREEDY_REALLOC(timer_infos, size, c+1)) { + r = log_oom(); + goto cleanup; + } + + m = calc_next_elapse(&nw, &next); + + timer_infos[c++] = (struct timer_info) { + .machine = u->machine, + .id = u->id, + .next_elapse = m, + .last_trigger = last, + .triggered = triggered, + }; + + triggered = NULL; /* avoid cleanup */ + } + + qsort_safe(timer_infos, c, sizeof(struct timer_info), + (__compar_fn_t) timer_info_compare); + + output_timers_list(timer_infos, c); + + cleanup: + for (t = timer_infos; t < timer_infos + c; t++) + strv_free(t->triggered); + + return r; +} + +static int compare_unit_file_list(const void *a, const void *b) { + const char *d1, *d2; + const UnitFileList *u = a, *v = b; + + d1 = strrchr(u->path, '.'); + d2 = strrchr(v->path, '.'); + + if (d1 && d2) { + int r; + + r = strcasecmp(d1, d2); + if (r != 0) + return r; + } + + return strcasecmp(basename(u->path), basename(v->path)); +} + +static bool output_show_unit_file(const UnitFileList *u, char **states, char **patterns) { + assert(u); + + if (!strv_fnmatch_or_empty(patterns, basename(u->path), FNM_NOESCAPE)) + return false; + + if (!strv_isempty(arg_types)) { + const char *dot; + + dot = strrchr(u->path, '.'); + if (!dot) + return false; + + if (!strv_find(arg_types, dot+1)) + return false; + } + + if (!strv_isempty(states) && + !strv_find(states, unit_file_state_to_string(u->state))) + return false; + + return true; +} + +static void output_unit_file_list(const UnitFileList *units, unsigned c) { + unsigned max_id_len, id_cols, state_cols; + const UnitFileList *u; + + max_id_len = strlen("UNIT FILE"); + state_cols = strlen("STATE"); + + for (u = units; u < units + c; u++) { + max_id_len = MAX(max_id_len, strlen(basename(u->path))); + state_cols = MAX(state_cols, strlen(unit_file_state_to_string(u->state))); + } + + if (!arg_full) { + unsigned basic_cols; + + id_cols = MIN(max_id_len, 25u); + basic_cols = 1 + id_cols + state_cols; + if (basic_cols < (unsigned) columns()) + id_cols += MIN(columns() - basic_cols, max_id_len - id_cols); + } else + id_cols = max_id_len; + + if (!arg_no_legend && c > 0) + printf("%-*s %-*s\n", + id_cols, "UNIT FILE", + state_cols, "STATE"); + + for (u = units; u < units + c; u++) { + _cleanup_free_ char *e = NULL; + const char *on, *off; + const char *id; + + if (IN_SET(u->state, + UNIT_FILE_MASKED, + UNIT_FILE_MASKED_RUNTIME, + UNIT_FILE_DISABLED, + UNIT_FILE_BAD)) { + on = ansi_highlight_red(); + off = ansi_normal(); + } else if (u->state == UNIT_FILE_ENABLED) { + on = ansi_highlight_green(); + off = ansi_normal(); + } else + on = off = ""; + + id = basename(u->path); + + e = arg_full ? NULL : ellipsize(id, id_cols, 33); + + printf("%-*s %s%-*s%s\n", + id_cols, e ? e : id, + on, state_cols, unit_file_state_to_string(u->state), off); + } + + if (!arg_no_legend) + printf("\n%u unit files listed.\n", c); +} + +static int list_unit_files(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ UnitFileList *units = NULL; + UnitFileList *unit; + size_t size = 0; + unsigned c = 0; + const char *state; + char *path; + int r; + bool fallback = false; + + pager_open(arg_no_pager, false); + + if (install_client_side()) { + Hashmap *h; + UnitFileList *u; + Iterator i; + unsigned n_units; + + h = hashmap_new(&string_hash_ops); + if (!h) + return log_oom(); + + r = unit_file_get_list(arg_scope, arg_root, h, arg_states, strv_skip(argv, 1)); + if (r < 0) { + unit_file_list_free(h); + return log_error_errno(r, "Failed to get unit file list: %m"); + } + + n_units = hashmap_size(h); + + units = new(UnitFileList, n_units ?: 1); /* avoid malloc(0) */ + if (!units) { + unit_file_list_free(h); + return log_oom(); + } + + HASHMAP_FOREACH(u, h, i) { + if (!output_show_unit_file(u, NULL, NULL)) + continue; + + units[c++] = *u; + free(u); + } + + assert(c <= n_units); + hashmap_free(h); + + r = 0; + } else { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnitFilesByPatterns"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, arg_states); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, strv_skip(argv, 1)); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) { + /* Fallback to legacy ListUnitFiles method */ + fallback = true; + log_debug_errno(r, "Failed to list unit files: %s Falling back to ListUnitsFiles method.", bus_error_message(&error, r)); + m = sd_bus_message_unref(m); + sd_bus_error_free(&error); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnitFiles"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + } + if (r < 0) + return log_error_errno(r, "Failed to list unit files: %s", bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(ss)", &path, &state)) > 0) { + + if (!GREEDY_REALLOC(units, size, c + 1)) + return log_oom(); + + units[c] = (struct UnitFileList) { + path, + unit_file_state_from_string(state) + }; + + if (output_show_unit_file(&units[c], + fallback ? arg_states : NULL, + fallback ? strv_skip(argv, 1) : NULL)) + c++; + + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + } + + qsort_safe(units, c, sizeof(UnitFileList), compare_unit_file_list); + output_unit_file_list(units, c); + + if (install_client_side()) + for (unit = units; unit < units + c; unit++) + free(unit->path); + + return 0; +} + +static int list_dependencies_print(const char *name, int level, unsigned int branches, bool last) { + _cleanup_free_ char *n = NULL; + size_t max_len = MAX(columns(),20u); + size_t len = 0; + int i; + + if (!arg_plain) { + + for (i = level - 1; i >= 0; i--) { + len += 2; + if (len > max_len - 3 && !arg_full) { + printf("%s...\n",max_len % 2 ? "" : " "); + return 0; + } + printf("%s", special_glyph(branches & (1 << i) ? TREE_VERTICAL : TREE_SPACE)); + } + len += 2; + + if (len > max_len - 3 && !arg_full) { + printf("%s...\n",max_len % 2 ? "" : " "); + return 0; + } + + printf("%s", special_glyph(last ? TREE_RIGHT : TREE_BRANCH)); + } + + if (arg_full) { + printf("%s\n", name); + return 0; + } + + n = ellipsize(name, max_len-len, 100); + if (!n) + return log_oom(); + + printf("%s\n", n); + return 0; +} + +static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) { + + static const char *dependencies[_DEPENDENCY_MAX] = { + [DEPENDENCY_FORWARD] = "Requires\0" + "Requisite\0" + "Wants\0" + "ConsistsOf\0" + "BindsTo\0", + [DEPENDENCY_REVERSE] = "RequiredBy\0" + "RequisiteOf\0" + "WantedBy\0" + "PartOf\0" + "BoundBy\0", + [DEPENDENCY_AFTER] = "After\0", + [DEPENDENCY_BEFORE] = "Before\0", + }; + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_strv_free_ char **ret = NULL; + _cleanup_free_ char *path = NULL; + int r; + + assert(bus); + assert(name); + assert(deps); + assert_cc(ELEMENTSOF(dependencies) == _DEPENDENCY_MAX); + + path = unit_dbus_path_from_name(name); + if (!path) + return log_oom(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.DBus.Properties", + "GetAll", + &error, + &reply, + "s", "org.freedesktop.systemd1.Unit"); + if (r < 0) + return log_error_errno(r, "Failed to get properties of %s: %s", name, bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sv}"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { + const char *prop; + + r = sd_bus_message_read(reply, "s", &prop); + if (r < 0) + return bus_log_parse_error(r); + + if (!nulstr_contains(dependencies[arg_dependency], prop)) { + r = sd_bus_message_skip(reply, "v"); + if (r < 0) + return bus_log_parse_error(r); + } else { + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_VARIANT, "as"); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_message_read_strv_extend(reply, &ret); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + *deps = ret; + ret = NULL; + + return 0; +} + +static int list_dependencies_compare(const void *_a, const void *_b) { + const char **a = (const char**) _a, **b = (const char**) _b; + + if (unit_name_to_type(*a) == UNIT_TARGET && unit_name_to_type(*b) != UNIT_TARGET) + return 1; + if (unit_name_to_type(*a) != UNIT_TARGET && unit_name_to_type(*b) == UNIT_TARGET) + return -1; + + return strcasecmp(*a, *b); +} + +static int list_dependencies_one( + sd_bus *bus, + const char *name, + int level, + char ***units, + unsigned int branches) { + + _cleanup_strv_free_ char **deps = NULL; + char **c; + int r = 0; + + assert(bus); + assert(name); + assert(units); + + r = strv_extend(units, name); + if (r < 0) + return log_oom(); + + r = list_dependencies_get_dependencies(bus, name, &deps); + if (r < 0) + return r; + + qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare); + + STRV_FOREACH(c, deps) { + if (strv_contains(*units, *c)) { + if (!arg_plain) { + r = list_dependencies_print("...", level + 1, (branches << 1) | (c[1] == NULL ? 0 : 1), 1); + if (r < 0) + return r; + } + continue; + } + + if (arg_plain) + printf(" "); + else { + UnitActiveState active_state = _UNIT_ACTIVE_STATE_INVALID; + const char *on; + + (void) get_state_one_unit(bus, *c, &active_state); + + switch (active_state) { + case UNIT_ACTIVE: + case UNIT_RELOADING: + case UNIT_ACTIVATING: + on = ansi_highlight_green(); + break; + + case UNIT_INACTIVE: + case UNIT_DEACTIVATING: + on = ansi_normal(); + break; + + default: + on = ansi_highlight_red(); + break; + } + + printf("%s%s%s ", on, special_glyph(BLACK_CIRCLE), ansi_normal()); + } + + r = list_dependencies_print(*c, level, branches, c[1] == NULL); + if (r < 0) + return r; + + if (arg_all || unit_name_to_type(*c) == UNIT_TARGET) { + r = list_dependencies_one(bus, *c, level + 1, units, (branches << 1) | (c[1] == NULL ? 0 : 1)); + if (r < 0) + return r; + } + } + + if (!arg_plain) + strv_remove(*units, name); + + return 0; +} + +static int list_dependencies(int argc, char *argv[], void *userdata) { + _cleanup_strv_free_ char **units = NULL; + _cleanup_free_ char *unit = NULL; + const char *u; + sd_bus *bus; + int r; + + if (argv[1]) { + r = unit_name_mangle(argv[1], UNIT_NAME_NOGLOB, &unit); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + + u = unit; + } else + u = SPECIAL_DEFAULT_TARGET; + + pager_open(arg_no_pager, false); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + puts(u); + + return list_dependencies_one(bus, u, 0, &units, 0); +} + +struct machine_info { + bool is_host; + char *name; + char *state; + char *control_group; + uint32_t n_failed_units; + uint32_t n_jobs; + usec_t timestamp; +}; + +static const struct bus_properties_map machine_info_property_map[] = { + { "SystemState", "s", NULL, offsetof(struct machine_info, state) }, + { "NJobs", "u", NULL, offsetof(struct machine_info, n_jobs) }, + { "NFailedUnits", "u", NULL, offsetof(struct machine_info, n_failed_units) }, + { "ControlGroup", "s", NULL, offsetof(struct machine_info, control_group) }, + { "UserspaceTimestamp", "t", NULL, offsetof(struct machine_info, timestamp) }, + {} +}; + +static void machine_info_clear(struct machine_info *info) { + if (info) { + free(info->name); + free(info->state); + free(info->control_group); + zero(*info); + } +} + +static void free_machines_list(struct machine_info *machine_infos, int n) { + int i; + + if (!machine_infos) + return; + + for (i = 0; i < n; i++) + machine_info_clear(&machine_infos[i]); + + free(machine_infos); +} + +static int compare_machine_info(const void *a, const void *b) { + const struct machine_info *u = a, *v = b; + + if (u->is_host != v->is_host) + return u->is_host > v->is_host ? -1 : 1; + + return strcasecmp(u->name, v->name); +} + +static int get_machine_properties(sd_bus *bus, struct machine_info *mi) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *container = NULL; + int r; + + assert(mi); + + if (!bus) { + r = sd_bus_open_system_machine(&container, mi->name); + if (r < 0) + return r; + + bus = container; + } + + r = bus_map_all_properties(bus, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", machine_info_property_map, mi); + if (r < 0) + return r; + + return 0; +} + +static bool output_show_machine(const char *name, char **patterns) { + return strv_fnmatch_or_empty(patterns, name, FNM_NOESCAPE); +} + +static int get_machine_list( + sd_bus *bus, + struct machine_info **_machine_infos, + char **patterns) { + + struct machine_info *machine_infos = NULL; + _cleanup_strv_free_ char **m = NULL; + _cleanup_free_ char *hn = NULL; + size_t sz = 0; + char **i; + int c = 0, r; + + hn = gethostname_malloc(); + if (!hn) + return log_oom(); + + if (output_show_machine(hn, patterns)) { + if (!GREEDY_REALLOC0(machine_infos, sz, c+1)) + return log_oom(); + + machine_infos[c].is_host = true; + machine_infos[c].name = hn; + hn = NULL; + + get_machine_properties(bus, &machine_infos[c]); + c++; + } + + r = sd_get_machine_names(&m); + if (r < 0) + return log_error_errno(r, "Failed to get machine list: %m"); + + STRV_FOREACH(i, m) { + _cleanup_free_ char *class = NULL; + + if (!output_show_machine(*i, patterns)) + continue; + + sd_machine_get_class(*i, &class); + if (!streq_ptr(class, "container")) + continue; + + if (!GREEDY_REALLOC0(machine_infos, sz, c+1)) { + free_machines_list(machine_infos, c); + return log_oom(); + } + + machine_infos[c].is_host = false; + machine_infos[c].name = strdup(*i); + if (!machine_infos[c].name) { + free_machines_list(machine_infos, c); + return log_oom(); + } + + get_machine_properties(NULL, &machine_infos[c]); + c++; + } + + *_machine_infos = machine_infos; + return c; +} + +static void output_machines_list(struct machine_info *machine_infos, unsigned n) { + struct machine_info *m; + unsigned + circle_len = 0, + namelen = sizeof("NAME") - 1, + statelen = sizeof("STATE") - 1, + failedlen = sizeof("FAILED") - 1, + jobslen = sizeof("JOBS") - 1; + + assert(machine_infos || n == 0); + + for (m = machine_infos; m < machine_infos + n; m++) { + namelen = MAX(namelen, strlen(m->name) + (m->is_host ? sizeof(" (host)") - 1 : 0)); + statelen = MAX(statelen, m->state ? strlen(m->state) : 0); + failedlen = MAX(failedlen, DECIMAL_STR_WIDTH(m->n_failed_units)); + jobslen = MAX(jobslen, DECIMAL_STR_WIDTH(m->n_jobs)); + + if (!arg_plain && !streq_ptr(m->state, "running")) + circle_len = 2; + } + + if (!arg_no_legend) { + if (circle_len > 0) + fputs(" ", stdout); + + printf("%-*s %-*s %-*s %-*s\n", + namelen, "NAME", + statelen, "STATE", + failedlen, "FAILED", + jobslen, "JOBS"); + } + + for (m = machine_infos; m < machine_infos + n; m++) { + const char *on_state = "", *off_state = ""; + const char *on_failed = "", *off_failed = ""; + bool circle = false; + + if (streq_ptr(m->state, "degraded")) { + on_state = ansi_highlight_red(); + off_state = ansi_normal(); + circle = true; + } else if (!streq_ptr(m->state, "running")) { + on_state = ansi_highlight_yellow(); + off_state = ansi_normal(); + circle = true; + } + + if (m->n_failed_units > 0) { + on_failed = ansi_highlight_red(); + off_failed = ansi_normal(); + } else + on_failed = off_failed = ""; + + if (circle_len > 0) + printf("%s%s%s ", on_state, circle ? special_glyph(BLACK_CIRCLE) : " ", off_state); + + if (m->is_host) + printf("%-*s (host) %s%-*s%s %s%*" PRIu32 "%s %*" PRIu32 "\n", + (int) (namelen - (sizeof(" (host)")-1)), strna(m->name), + on_state, statelen, strna(m->state), off_state, + on_failed, failedlen, m->n_failed_units, off_failed, + jobslen, m->n_jobs); + else + printf("%-*s %s%-*s%s %s%*" PRIu32 "%s %*" PRIu32 "\n", + namelen, strna(m->name), + on_state, statelen, strna(m->state), off_state, + on_failed, failedlen, m->n_failed_units, off_failed, + jobslen, m->n_jobs); + } + + if (!arg_no_legend) + printf("\n%u machines listed.\n", n); +} + +static int list_machines(int argc, char *argv[], void *userdata) { + struct machine_info *machine_infos = NULL; + sd_bus *bus; + int r; + + if (geteuid() != 0) { + log_error("Must be root."); + return -EPERM; + } + + pager_open(arg_no_pager, false); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = get_machine_list(bus, &machine_infos, strv_skip(argv, 1)); + if (r < 0) + return r; + + qsort_safe(machine_infos, r, sizeof(struct machine_info), compare_machine_info); + output_machines_list(machine_infos, r); + free_machines_list(machine_infos, r); + + return 0; +} + +static int get_default(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *_path = NULL; + const char *path; + int r; + + if (install_client_side()) { + r = unit_file_get_default(arg_scope, arg_root, &_path); + if (r < 0) + return log_error_errno(r, "Failed to get default target: %m"); + path = _path; + + r = 0; + } else { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetDefaultTarget", + &error, + &reply, + NULL); + if (r < 0) + return log_error_errno(r, "Failed to get default target: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "s", &path); + if (r < 0) + return bus_log_parse_error(r); + } + + if (path) + printf("%s\n", path); + + return 0; +} + +static int set_default(int argc, char *argv[], void *userdata) { + _cleanup_free_ char *unit = NULL; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + int r; + + assert(argc >= 2); + assert(argv); + + r = unit_name_mangle_with_suffix(argv[1], UNIT_NAME_NOGLOB, ".target", &unit); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + + if (install_client_side()) { + r = unit_file_set_default(arg_scope, arg_root, unit, true, &changes, &n_changes); + unit_file_dump_changes(r, "set default", changes, n_changes, arg_quiet); + + if (r > 0) + r = 0; + } else { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + sd_bus *bus; + + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SetDefaultTarget", + &error, + &reply, + "sb", unit, 1); + if (r < 0) + return log_error_errno(r, "Failed to set default target: %s", bus_error_message(&error, r)); + + r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes); + if (r < 0) + goto finish; + + /* Try to reload if enabled */ + if (!arg_no_reload) + r = daemon_reload(argc, argv, userdata); + else + r = 0; + } + +finish: + unit_file_changes_free(changes, n_changes); + + return r; +} + +struct job_info { + uint32_t id; + const char *name, *type, *state; +}; + +static void output_jobs_list(const struct job_info* jobs, unsigned n, bool skipped) { + unsigned id_len, unit_len, type_len, state_len; + const struct job_info *j; + const char *on, *off; + bool shorten = false; + + assert(n == 0 || jobs); + + if (n == 0) { + if (!arg_no_legend) { + on = ansi_highlight_green(); + off = ansi_normal(); + + printf("%sNo jobs %s.%s\n", on, skipped ? "listed" : "running", off); + } + return; + } + + pager_open(arg_no_pager, false); + + id_len = strlen("JOB"); + unit_len = strlen("UNIT"); + type_len = strlen("TYPE"); + state_len = strlen("STATE"); + + for (j = jobs; j < jobs + n; j++) { + uint32_t id = j->id; + assert(j->name && j->type && j->state); + + id_len = MAX(id_len, DECIMAL_STR_WIDTH(id)); + unit_len = MAX(unit_len, strlen(j->name)); + type_len = MAX(type_len, strlen(j->type)); + state_len = MAX(state_len, strlen(j->state)); + } + + if (!arg_full && id_len + 1 + unit_len + type_len + 1 + state_len > columns()) { + unit_len = MAX(33u, columns() - id_len - type_len - state_len - 3); + shorten = true; + } + + if (!arg_no_legend) + printf("%*s %-*s %-*s %-*s\n", + id_len, "JOB", + unit_len, "UNIT", + type_len, "TYPE", + state_len, "STATE"); + + for (j = jobs; j < jobs + n; j++) { + _cleanup_free_ char *e = NULL; + + if (streq(j->state, "running")) { + on = ansi_highlight(); + off = ansi_normal(); + } else + on = off = ""; + + e = shorten ? ellipsize(j->name, unit_len, 33) : NULL; + printf("%*u %s%-*s%s %-*s %s%-*s%s\n", + id_len, j->id, + on, unit_len, e ? e : j->name, off, + type_len, j->type, + on, state_len, j->state, off); + } + + if (!arg_no_legend) { + on = ansi_highlight(); + off = ansi_normal(); + + printf("\n%s%u jobs listed%s.\n", on, n, off); + } +} + +static bool output_show_job(struct job_info *job, char **patterns) { + return strv_fnmatch_or_empty(patterns, job->name, FNM_NOESCAPE); +} + +static int list_jobs(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *name, *type, *state, *job_path, *unit_path; + _cleanup_free_ struct job_info *jobs = NULL; + size_t size = 0; + unsigned c = 0; + sd_bus *bus; + uint32_t id; + int r; + bool skipped = false; + + pager_open(arg_no_pager, false); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListJobs", + &error, + &reply, + NULL); + if (r < 0) + return log_error_errno(r, "Failed to list jobs: %s", bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, 'a', "(usssoo)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(usssoo)", &id, &name, &type, &state, &job_path, &unit_path)) > 0) { + struct job_info job = { id, name, type, state }; + + if (!output_show_job(&job, strv_skip(argv, 1))) { + skipped = true; + continue; + } + + if (!GREEDY_REALLOC(jobs, size, c + 1)) + return log_oom(); + + jobs[c++] = job; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + output_jobs_list(jobs, c, skipped); + return 0; +} + +static int cancel_job(int argc, char *argv[], void *userdata) { + sd_bus *bus; + char **name; + int r = 0; + + if (argc <= 1) + return daemon_reload(argc, argv, userdata); + + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + STRV_FOREACH(name, strv_skip(argv, 1)) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + uint32_t id; + int q; + + q = safe_atou32(*name, &id); + if (q < 0) + return log_error_errno(q, "Failed to parse job id \"%s\": %m", *name); + + q = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "CancelJob", + &error, + NULL, + "u", id); + if (q < 0) { + log_error_errno(q, "Failed to cancel job %"PRIu32": %s", id, bus_error_message(&error, q)); + if (r == 0) + r = q; + } + } + + return r; +} + +static int need_daemon_reload(sd_bus *bus, const char *unit) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *path; + int b, r; + + /* We ignore all errors here, since this is used to show a + * warning only */ + + /* We don't use unit_dbus_path_from_name() directly since we + * don't want to load the unit if it isn't loaded. */ + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnit", + NULL, + &reply, + "s", unit); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return r; + + r = sd_bus_get_property_trivial( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "NeedDaemonReload", + NULL, + 'b', &b); + if (r < 0) + return r; + + return b; +} + +static void warn_unit_file_changed(const char *name) { + assert(name); + + log_warning("%sWarning:%s %s changed on disk. Run 'systemctl%s daemon-reload' to reload units.", + ansi_highlight_red(), + ansi_normal(), + name, + arg_scope == UNIT_FILE_SYSTEM ? "" : " --user"); +} + +static int unit_file_find_path(LookupPaths *lp, const char *unit_name, char **unit_path) { + char **p; + + assert(lp); + assert(unit_name); + assert(unit_path); + + STRV_FOREACH(p, lp->search_path) { + _cleanup_free_ char *path; + + path = path_join(arg_root, *p, unit_name); + if (!path) + return log_oom(); + + if (access(path, F_OK) == 0) { + *unit_path = path; + path = NULL; + return 1; + } + } + + return 0; +} + +static int unit_find_paths( + sd_bus *bus, + const char *unit_name, + LookupPaths *lp, + char **fragment_path, + char ***dropin_paths) { + + _cleanup_free_ char *path = NULL; + _cleanup_strv_free_ char **dropins = NULL; + int r; + + /** + * Finds where the unit is defined on disk. Returns 0 if the unit + * is not found. Returns 1 if it is found, and sets + * - the path to the unit in *path, if it exists on disk, + * - and a strv of existing drop-ins in *dropins, + * if the arg is not NULL and any dropins were found. + */ + + assert(unit_name); + assert(fragment_path); + assert(lp); + + if (!install_client_side() && !unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *unit = NULL; + + unit = unit_dbus_path_from_name(unit_name); + if (!unit) + return log_oom(); + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + unit, + "org.freedesktop.systemd1.Unit", + "FragmentPath", + &error, + &path); + if (r < 0) + return log_error_errno(r, "Failed to get FragmentPath: %s", bus_error_message(&error, r)); + + if (dropin_paths) { + r = sd_bus_get_property_strv( + bus, + "org.freedesktop.systemd1", + unit, + "org.freedesktop.systemd1.Unit", + "DropInPaths", + &error, + &dropins); + if (r < 0) + return log_error_errno(r, "Failed to get DropInPaths: %s", bus_error_message(&error, r)); + } + } else { + _cleanup_set_free_ Set *names; + + names = set_new(NULL); + if (!names) + return log_oom(); + + r = set_put(names, unit_name); + if (r < 0) + return log_error_errno(r, "Failed to add unit name: %m"); + + r = unit_file_find_path(lp, unit_name, &path); + if (r < 0) + return r; + + if (r == 0) { + _cleanup_free_ char *template = NULL; + + r = unit_name_template(unit_name, &template); + if (r < 0 && r != -EINVAL) + return log_error_errno(r, "Failed to determine template name: %m"); + if (r >= 0) { + r = unit_file_find_path(lp, template, &path); + if (r < 0) + return r; + } + } + + if (dropin_paths) { + r = unit_file_find_dropin_paths(lp->search_path, NULL, names, &dropins); + if (r < 0) + return r; + } + } + + r = 0; + + if (!isempty(path)) { + *fragment_path = path; + path = NULL; + r = 1; + } + + if (dropin_paths && !strv_isempty(dropins)) { + *dropin_paths = dropins; + dropins = NULL; + r = 1; + } + + if (r == 0) + log_error("No files found for %s.", unit_name); + + return r; +} + +static int get_state_one_unit(sd_bus *bus, const char *name, UnitActiveState *active_state) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *buf = NULL; + UnitActiveState state; + const char *path; + int r; + + assert(name); + assert(active_state); + + /* We don't use unit_dbus_path_from_name() directly since we don't want to load the unit unnecessarily, if it + * isn't loaded. */ + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnit", + &error, + &reply, + "s", name); + if (r < 0) { + if (!sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT)) + return log_error_errno(r, "Failed to retrieve unit: %s", bus_error_message(&error, r)); + + /* The unit is currently not loaded, hence say it's "inactive", since all units that aren't loaded are + * considered inactive. */ + state = UNIT_INACTIVE; + + } else { + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "ActiveState", + &error, + &buf); + if (r < 0) + return log_error_errno(r, "Failed to retrieve unit state: %s", bus_error_message(&error, r)); + + state = unit_active_state_from_string(buf); + if (state == _UNIT_ACTIVE_STATE_INVALID) { + log_error("Invalid unit state '%s' for: %s", buf, name); + return -EINVAL; + } + } + + *active_state = state; + return 0; +} + +static int check_triggering_units( + sd_bus *bus, + const char *name) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *path = NULL, *n = NULL, *load_state = NULL; + _cleanup_strv_free_ char **triggered_by = NULL; + bool print_warning_label = true; + UnitActiveState active_state; + char **i; + int r; + + r = unit_name_mangle(name, UNIT_NAME_NOGLOB, &n); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + + path = unit_dbus_path_from_name(n); + if (!path) + return log_oom(); + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "LoadState", + &error, + &load_state); + if (r < 0) + return log_error_errno(r, "Failed to get load state of %s: %s", n, bus_error_message(&error, r)); + + if (streq(load_state, "masked")) + return 0; + + r = sd_bus_get_property_strv( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "TriggeredBy", + &error, + &triggered_by); + if (r < 0) + return log_error_errno(r, "Failed to get triggered by array of %s: %s", n, bus_error_message(&error, r)); + + STRV_FOREACH(i, triggered_by) { + r = get_state_one_unit(bus, *i, &active_state); + if (r < 0) + return r; + + if (!IN_SET(active_state, UNIT_ACTIVE, UNIT_RELOADING)) + continue; + + if (print_warning_label) { + log_warning("Warning: Stopping %s, but it can still be activated by:", n); + print_warning_label = false; + } + + log_warning(" %s", *i); + } + + return 0; +} + +static const struct { + const char *verb; + const char *method; +} unit_actions[] = { + { "start", "StartUnit" }, + { "stop", "StopUnit" }, + { "condstop", "StopUnit" }, + { "reload", "ReloadUnit" }, + { "restart", "RestartUnit" }, + { "try-restart", "TryRestartUnit" }, + { "condrestart", "TryRestartUnit" }, + { "reload-or-restart", "ReloadOrRestartUnit" }, + { "try-reload-or-restart", "ReloadOrTryRestartUnit" }, + { "reload-or-try-restart", "ReloadOrTryRestartUnit" }, + { "condreload", "ReloadOrTryRestartUnit" }, + { "force-reload", "ReloadOrTryRestartUnit" } +}; + +static const char *verb_to_method(const char *verb) { + uint i; + + for (i = 0; i < ELEMENTSOF(unit_actions); i++) + if (streq_ptr(unit_actions[i].verb, verb)) + return unit_actions[i].method; + + return "StartUnit"; +} + +static const char *method_to_verb(const char *method) { + uint i; + + for (i = 0; i < ELEMENTSOF(unit_actions); i++) + if (streq_ptr(unit_actions[i].method, method)) + return unit_actions[i].verb; + + return "n/a"; +} + +static int start_unit_one( + sd_bus *bus, + const char *method, + const char *name, + const char *mode, + sd_bus_error *error, + BusWaitForJobs *w) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *path; + int r; + + assert(method); + assert(name); + assert(mode); + assert(error); + + log_debug("Calling manager for %s on %s, %s", method, name, mode); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + method, + error, + &reply, + "ss", name, mode); + if (r < 0) { + const char *verb; + + if (r == -ENOENT && arg_action != ACTION_SYSTEMCTL) + /* There's always a fallback possible for + * legacy actions. */ + return -EADDRNOTAVAIL; + + verb = method_to_verb(method); + + log_error("Failed to %s %s: %s", verb, name, bus_error_message(error, r)); + + if (!sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) && + !sd_bus_error_has_name(error, BUS_ERROR_UNIT_MASKED)) + log_error("See %s logs and 'systemctl%s status %s' for details.", + arg_scope == UNIT_FILE_SYSTEM ? "system" : "user", + arg_scope == UNIT_FILE_SYSTEM ? "" : " --user", + name); + + return r; + } + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); + + if (need_daemon_reload(bus, name) > 0) + warn_unit_file_changed(name); + + if (w) { + log_debug("Adding %s to the set", path); + r = bus_wait_for_jobs_add(w, path); + if (r < 0) + return log_oom(); + } + + return 0; +} + +static int expand_names(sd_bus *bus, char **names, const char* suffix, char ***ret) { + _cleanup_strv_free_ char **mangled = NULL, **globs = NULL; + char **name; + int r, i; + + assert(bus); + assert(ret); + + STRV_FOREACH(name, names) { + char *t; + + if (suffix) + r = unit_name_mangle_with_suffix(*name, UNIT_NAME_GLOB, suffix, &t); + else + r = unit_name_mangle(*name, UNIT_NAME_GLOB, &t); + if (r < 0) + return log_error_errno(r, "Failed to mangle name: %m"); + + if (string_is_glob(t)) + r = strv_consume(&globs, t); + else + r = strv_consume(&mangled, t); + if (r < 0) + return log_oom(); + } + + /* Query the manager only if any of the names are a glob, since + * this is fairly expensive */ + if (!strv_isempty(globs)) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ UnitInfo *unit_infos = NULL; + size_t allocated, n; + + r = get_unit_list(bus, NULL, globs, &unit_infos, 0, &reply); + if (r < 0) + return r; + + n = strv_length(mangled); + allocated = n + 1; + + for (i = 0; i < r; i++) { + if (!GREEDY_REALLOC(mangled, allocated, n+2)) + return log_oom(); + + mangled[n] = strdup(unit_infos[i].id); + if (!mangled[n]) + return log_oom(); + + mangled[++n] = NULL; + } + } + + *ret = mangled; + mangled = NULL; /* do not free */ + + return 0; +} + +static const struct { + const char *target; + const char *verb; + const char *mode; +} action_table[_ACTION_MAX] = { + [ACTION_HALT] = { SPECIAL_HALT_TARGET, "halt", "replace-irreversibly" }, + [ACTION_POWEROFF] = { SPECIAL_POWEROFF_TARGET, "poweroff", "replace-irreversibly" }, + [ACTION_REBOOT] = { SPECIAL_REBOOT_TARGET, "reboot", "replace-irreversibly" }, + [ACTION_KEXEC] = { SPECIAL_KEXEC_TARGET, "kexec", "replace-irreversibly" }, + [ACTION_RUNLEVEL2] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, + [ACTION_RUNLEVEL3] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, + [ACTION_RUNLEVEL4] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, + [ACTION_RUNLEVEL5] = { SPECIAL_GRAPHICAL_TARGET, NULL, "isolate" }, + [ACTION_RESCUE] = { SPECIAL_RESCUE_TARGET, "rescue", "isolate" }, + [ACTION_EMERGENCY] = { SPECIAL_EMERGENCY_TARGET, "emergency", "isolate" }, + [ACTION_DEFAULT] = { SPECIAL_DEFAULT_TARGET, "default", "isolate" }, + [ACTION_EXIT] = { SPECIAL_EXIT_TARGET, "exit", "replace-irreversibly" }, + [ACTION_SUSPEND] = { SPECIAL_SUSPEND_TARGET, "suspend", "replace-irreversibly" }, + [ACTION_HIBERNATE] = { SPECIAL_HIBERNATE_TARGET, "hibernate", "replace-irreversibly" }, + [ACTION_HYBRID_SLEEP] = { SPECIAL_HYBRID_SLEEP_TARGET, "hybrid-sleep", "replace-irreversibly" }, +}; + +static enum action verb_to_action(const char *verb) { + enum action i; + + for (i = _ACTION_INVALID; i < _ACTION_MAX; i++) + if (streq_ptr(action_table[i].verb, verb)) + return i; + + return _ACTION_INVALID; +} + +static int start_unit(int argc, char *argv[], void *userdata) { + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + const char *method, *mode, *one_name, *suffix = NULL; + _cleanup_strv_free_ char **names = NULL; + sd_bus *bus; + char **name; + int r = 0; + + ask_password_agent_open_if_enabled(); + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + if (arg_action == ACTION_SYSTEMCTL) { + enum action action; + + method = verb_to_method(argv[0]); + action = verb_to_action(argv[0]); + + if (streq(argv[0], "isolate")) { + mode = "isolate"; + suffix = ".target"; + } else + mode = action_table[action].mode ?: arg_job_mode; + + one_name = action_table[action].target; + } else { + assert(arg_action < ELEMENTSOF(action_table)); + assert(action_table[arg_action].target); + + method = "StartUnit"; + + mode = action_table[arg_action].mode; + one_name = action_table[arg_action].target; + } + + if (one_name) + names = strv_new(one_name, NULL); + else { + r = expand_names(bus, strv_skip(argv, 1), suffix, &names); + if (r < 0) + return log_error_errno(r, "Failed to expand names: %m"); + } + + if (!arg_no_block) { + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch jobs: %m"); + } + + STRV_FOREACH(name, names) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int q; + + q = start_unit_one(bus, method, *name, mode, &error, w); + if (r >= 0 && q < 0) + r = translate_bus_error_to_exit_status(q, &error); + } + + if (!arg_no_block) { + int q, arg_count = 0; + const char* extra_args[4] = {}; + + if (arg_scope != UNIT_FILE_SYSTEM) + extra_args[arg_count++] = "--user"; + + assert(IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_REMOTE, BUS_TRANSPORT_MACHINE)); + if (arg_transport == BUS_TRANSPORT_REMOTE) { + extra_args[arg_count++] = "-H"; + extra_args[arg_count++] = arg_host; + } else if (arg_transport == BUS_TRANSPORT_MACHINE) { + extra_args[arg_count++] = "-M"; + extra_args[arg_count++] = arg_host; + } + + q = bus_wait_for_jobs(w, arg_quiet, extra_args); + if (q < 0) + return q; + + /* When stopping units, warn if they can still be triggered by + * another active unit (socket, path, timer) */ + if (!arg_quiet && streq(method, "StopUnit")) + STRV_FOREACH(name, names) + check_triggering_units(bus, *name); + } + + return r; +} + +static int logind_set_wall_message(void) { +#ifdef HAVE_LOGIND + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + _cleanup_free_ char *m = NULL; + int r; + + r = acquire_bus(BUS_FULL, &bus); + if (r < 0) + return r; + + m = strv_join(arg_wall, " "); + if (!m) + return log_oom(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "SetWallMessage", + &error, + NULL, + "sb", + m, + !arg_no_wall); + + if (r < 0) + return log_warning_errno(r, "Failed to set wall message, ignoring: %s", bus_error_message(&error, r)); + +#endif + return 0; +} + +/* Ask systemd-logind, which might grant access to unprivileged users + * through PolicyKit */ +static int logind_reboot(enum action a) { +#ifdef HAVE_LOGIND + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *method, *description; + sd_bus *bus; + int r; + + polkit_agent_open_if_enabled(); + (void) logind_set_wall_message(); + + r = acquire_bus(BUS_FULL, &bus); + if (r < 0) + return r; + + switch (a) { + + case ACTION_REBOOT: + method = "Reboot"; + description = "reboot system"; + break; + + case ACTION_POWEROFF: + method = "PowerOff"; + description = "power off system"; + break; + + case ACTION_SUSPEND: + method = "Suspend"; + description = "suspend system"; + break; + + case ACTION_HIBERNATE: + method = "Hibernate"; + description = "hibernate system"; + break; + + case ACTION_HYBRID_SLEEP: + method = "HybridSleep"; + description = "put system into hybrid sleep"; + break; + + default: + return -EINVAL; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + method, + &error, + NULL, + "b", arg_ask_password); + if (r < 0) + return log_error_errno(r, "Failed to %s via logind: %s", description, bus_error_message(&error, r)); + + return 0; +#else + return -ENOSYS; +#endif +} + +static int logind_check_inhibitors(enum action a) { +#ifdef HAVE_LOGIND + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_strv_free_ char **sessions = NULL; + const char *what, *who, *why, *mode; + uint32_t uid, pid; + sd_bus *bus; + unsigned c = 0; + char **s; + int r; + + if (arg_ignore_inhibitors || arg_force > 0) + return 0; + + if (arg_when > 0) + return 0; + + if (geteuid() == 0) + return 0; + + if (!on_tty()) + return 0; + + r = acquire_bus(BUS_FULL, &bus); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "ListInhibitors", + NULL, + &reply, + NULL); + if (r < 0) + /* If logind is not around, then there are no inhibitors... */ + return 0; + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssuu)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(ssssuu)", &what, &who, &why, &mode, &uid, &pid)) > 0) { + _cleanup_free_ char *comm = NULL, *user = NULL; + _cleanup_strv_free_ char **sv = NULL; + + if (!streq(mode, "block")) + continue; + + sv = strv_split(what, ":"); + if (!sv) + return log_oom(); + + if ((pid_t) pid < 0) + return log_error_errno(ERANGE, "Bad PID %"PRIu32": %m", pid); + + if (!strv_contains(sv, + IN_SET(a, + ACTION_HALT, + ACTION_POWEROFF, + ACTION_REBOOT, + ACTION_KEXEC) ? "shutdown" : "sleep")) + continue; + + get_process_comm(pid, &comm); + user = uid_to_name(uid); + + log_warning("Operation inhibited by \"%s\" (PID "PID_FMT" \"%s\", user %s), reason is \"%s\".", + who, (pid_t) pid, strna(comm), strna(user), why); + + c++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + /* Check for current sessions */ + sd_get_sessions(&sessions); + STRV_FOREACH(s, sessions) { + _cleanup_free_ char *type = NULL, *tty = NULL, *seat = NULL, *user = NULL, *service = NULL, *class = NULL; + + if (sd_session_get_uid(*s, &uid) < 0 || uid == getuid()) + continue; + + if (sd_session_get_class(*s, &class) < 0 || !streq(class, "user")) + continue; + + if (sd_session_get_type(*s, &type) < 0 || (!streq(type, "x11") && !streq(type, "tty"))) + continue; + + sd_session_get_tty(*s, &tty); + sd_session_get_seat(*s, &seat); + sd_session_get_service(*s, &service); + user = uid_to_name(uid); + + log_warning("User %s is logged in on %s.", strna(user), isempty(tty) ? (isempty(seat) ? strna(service) : seat) : tty); + c++; + } + + if (c <= 0) + return 0; + + log_error("Please retry operation after closing inhibitors and logging out other users.\nAlternatively, ignore inhibitors and users with 'systemctl %s -i'.", + action_table[a].verb); + + return -EPERM; +#else + return 0; +#endif +} + +static int logind_prepare_firmware_setup(void) { +#ifdef HAVE_LOGIND + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + int r; + + r = acquire_bus(BUS_FULL, &bus); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "SetRebootToFirmwareSetup", + &error, + NULL, + "b", true); + if (r < 0) + return log_error_errno(r, "Cannot indicate to EFI to boot into setup mode: %s", bus_error_message(&error, r)); + + return 0; +#else + log_error("Cannot remotely indicate to EFI to boot into setup mode."); + return -ENOSYS; +#endif +} + +static int prepare_firmware_setup(void) { + int r; + + if (!arg_firmware_setup) + return 0; + + if (arg_transport == BUS_TRANSPORT_LOCAL) { + + r = efi_set_reboot_to_firmware(true); + if (r < 0) + log_debug_errno(r, "Cannot indicate to EFI to boot into setup mode, will retry via logind: %m"); + else + return r; + } + + return logind_prepare_firmware_setup(); +} + +static int set_exit_code(uint8_t code) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + int r; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SetExitCode", + &error, + NULL, + "y", code); + if (r < 0) + return log_error_errno(r, "Failed to set exit code: %s", bus_error_message(&error, r)); + + return 0; +} + +static int start_special(int argc, char *argv[], void *userdata) { + enum action a; + int r; + + assert(argv); + + a = verb_to_action(argv[0]); + + r = logind_check_inhibitors(a); + if (r < 0) + return r; + + if (arg_force >= 2 && geteuid() != 0) { + log_error("Must be root."); + return -EPERM; + } + + r = prepare_firmware_setup(); + if (r < 0) + return r; + + if (a == ACTION_REBOOT && argc > 1) { + r = update_reboot_parameter_and_warn(argv[1]); + if (r < 0) + return r; + + } else if (a == ACTION_EXIT && argc > 1) { + uint8_t code; + + /* If the exit code is not given on the command line, + * don't reset it to zero: just keep it as it might + * have been set previously. */ + + r = safe_atou8(argv[1], &code); + if (r < 0) + return log_error_errno(r, "Invalid exit code."); + + r = set_exit_code(code); + if (r < 0) + return r; + } + + if (arg_force >= 2 && + IN_SET(a, + ACTION_HALT, + ACTION_POWEROFF, + ACTION_REBOOT)) + return halt_now(a); + + if (arg_force >= 1 && + IN_SET(a, + ACTION_HALT, + ACTION_POWEROFF, + ACTION_REBOOT, + ACTION_KEXEC, + ACTION_EXIT)) + return daemon_reload(argc, argv, userdata); + + /* First try logind, to allow authentication with polkit */ + if (IN_SET(a, + ACTION_POWEROFF, + ACTION_REBOOT, + ACTION_SUSPEND, + ACTION_HIBERNATE, + ACTION_HYBRID_SLEEP)) { + r = logind_reboot(a); + if (r >= 0) + return r; + if (IN_SET(r, -EOPNOTSUPP, -EINPROGRESS)) + /* requested operation is not supported or already in progress */ + return r; + + /* On all other errors, try low-level operation */ + } + + return start_unit(argc, argv, userdata); +} + +static int check_unit_generic(int code, const UnitActiveState good_states[], int nb_states, char **args) { + _cleanup_strv_free_ char **names = NULL; + UnitActiveState active_state; + sd_bus *bus; + char **name; + int r, i; + bool found = false; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = expand_names(bus, args, NULL, &names); + if (r < 0) + return log_error_errno(r, "Failed to expand names: %m"); + + STRV_FOREACH(name, names) { + r = get_state_one_unit(bus, *name, &active_state); + if (r < 0) + return r; + + if (!arg_quiet) + puts(unit_active_state_to_string(active_state)); + + for (i = 0; i < nb_states; ++i) + if (good_states[i] == active_state) + found = true; + } + + /* use the given return code for the case that we won't find + * any unit which matches the list */ + return found ? 0 : code; +} + +static int check_unit_active(int argc, char *argv[], void *userdata) { + const UnitActiveState states[] = { UNIT_ACTIVE, UNIT_RELOADING }; + /* According to LSB: 3, "program is not running" */ + return check_unit_generic(3, states, ELEMENTSOF(states), strv_skip(argv, 1)); +} + +static int check_unit_failed(int argc, char *argv[], void *userdata) { + const UnitActiveState states[] = { UNIT_FAILED }; + return check_unit_generic(1, states, ELEMENTSOF(states), strv_skip(argv, 1)); +} + +static int kill_unit(int argc, char *argv[], void *userdata) { + _cleanup_strv_free_ char **names = NULL; + char *kill_who = NULL, **name; + sd_bus *bus; + int r, q; + + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + if (!arg_kill_who) + arg_kill_who = "all"; + + /* --fail was specified */ + if (streq(arg_job_mode, "fail")) + kill_who = strjoina(arg_kill_who, "-fail"); + + r = expand_names(bus, strv_skip(argv, 1), NULL, &names); + if (r < 0) + return log_error_errno(r, "Failed to expand names: %m"); + + STRV_FOREACH(name, names) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + q = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "KillUnit", + &error, + NULL, + "ssi", *names, kill_who ? kill_who : arg_kill_who, arg_signal); + if (q < 0) { + log_error_errno(q, "Failed to kill unit %s: %s", *names, bus_error_message(&error, q)); + if (r == 0) + r = q; + } + } + + return r; +} + +typedef struct ExecStatusInfo { + char *name; + + char *path; + char **argv; + + bool ignore; + + usec_t start_timestamp; + usec_t exit_timestamp; + pid_t pid; + int code; + int status; + + LIST_FIELDS(struct ExecStatusInfo, exec); +} ExecStatusInfo; + +static void exec_status_info_free(ExecStatusInfo *i) { + assert(i); + + free(i->name); + free(i->path); + strv_free(i->argv); + free(i); +} + +static int exec_status_info_deserialize(sd_bus_message *m, ExecStatusInfo *i) { + uint64_t start_timestamp, exit_timestamp, start_timestamp_monotonic, exit_timestamp_monotonic; + const char *path; + uint32_t pid; + int32_t code, status; + int ignore, r; + + assert(m); + assert(i); + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_STRUCT, "sasbttttuii"); + if (r < 0) + return bus_log_parse_error(r); + else if (r == 0) + return 0; + + r = sd_bus_message_read(m, "s", &path); + if (r < 0) + return bus_log_parse_error(r); + + i->path = strdup(path); + if (!i->path) + return log_oom(); + + r = sd_bus_message_read_strv(m, &i->argv); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(m, + "bttttuii", + &ignore, + &start_timestamp, &start_timestamp_monotonic, + &exit_timestamp, &exit_timestamp_monotonic, + &pid, + &code, &status); + if (r < 0) + return bus_log_parse_error(r); + + i->ignore = ignore; + i->start_timestamp = (usec_t) start_timestamp; + i->exit_timestamp = (usec_t) exit_timestamp; + i->pid = (pid_t) pid; + i->code = code; + i->status = status; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 1; +} + +typedef struct UnitStatusInfo { + const char *id; + const char *load_state; + const char *active_state; + const char *sub_state; + const char *unit_file_state; + const char *unit_file_preset; + + const char *description; + const char *following; + + char **documentation; + + const char *fragment_path; + const char *source_path; + const char *control_group; + + char **dropin_paths; + + const char *load_error; + const char *result; + + usec_t inactive_exit_timestamp; + usec_t inactive_exit_timestamp_monotonic; + usec_t active_enter_timestamp; + usec_t active_exit_timestamp; + usec_t inactive_enter_timestamp; + + bool need_daemon_reload; + bool transient; + + /* Service */ + pid_t main_pid; + pid_t control_pid; + const char *status_text; + const char *pid_file; + bool running:1; + int status_errno; + + usec_t start_timestamp; + usec_t exit_timestamp; + + int exit_code, exit_status; + + usec_t condition_timestamp; + bool condition_result; + bool failed_condition_trigger; + bool failed_condition_negate; + const char *failed_condition; + const char *failed_condition_parameter; + + usec_t assert_timestamp; + bool assert_result; + bool failed_assert_trigger; + bool failed_assert_negate; + const char *failed_assert; + const char *failed_assert_parameter; + + /* Socket */ + unsigned n_accepted; + unsigned n_connections; + bool accept; + + /* Pairs of type, path */ + char **listen; + + /* Device */ + const char *sysfs_path; + + /* Mount, Automount */ + const char *where; + + /* Swap */ + const char *what; + + /* CGroup */ + uint64_t memory_current; + uint64_t memory_limit; + uint64_t cpu_usage_nsec; + uint64_t tasks_current; + uint64_t tasks_max; + + LIST_HEAD(ExecStatusInfo, exec); +} UnitStatusInfo; + +static void print_status_info( + sd_bus *bus, + UnitStatusInfo *i, + bool *ellipsized) { + + ExecStatusInfo *p; + const char *active_on, *active_off, *on, *off, *ss; + usec_t timestamp; + char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; + char since2[FORMAT_TIMESTAMP_MAX], *s2; + const char *path; + char **t, **t2; + int r; + + assert(i); + + /* This shows pretty information about a unit. See + * print_property() for a low-level property printer */ + + if (streq_ptr(i->active_state, "failed")) { + active_on = ansi_highlight_red(); + active_off = ansi_normal(); + } else if (streq_ptr(i->active_state, "active") || streq_ptr(i->active_state, "reloading")) { + active_on = ansi_highlight_green(); + active_off = ansi_normal(); + } else + active_on = active_off = ""; + + printf("%s%s%s %s", active_on, special_glyph(BLACK_CIRCLE), active_off, strna(i->id)); + + if (i->description && !streq_ptr(i->id, i->description)) + printf(" - %s", i->description); + + printf("\n"); + + if (i->following) + printf(" Follow: unit currently follows state of %s\n", i->following); + + if (streq_ptr(i->load_state, "error")) { + on = ansi_highlight_red(); + off = ansi_normal(); + } else + on = off = ""; + + path = i->source_path ? i->source_path : i->fragment_path; + + if (i->load_error != 0) + printf(" Loaded: %s%s%s (Reason: %s)\n", + on, strna(i->load_state), off, i->load_error); + else if (path && !isempty(i->unit_file_state) && !isempty(i->unit_file_preset)) + printf(" Loaded: %s%s%s (%s; %s; vendor preset: %s)\n", + on, strna(i->load_state), off, path, i->unit_file_state, i->unit_file_preset); + else if (path && !isempty(i->unit_file_state)) + printf(" Loaded: %s%s%s (%s; %s)\n", + on, strna(i->load_state), off, path, i->unit_file_state); + else if (path) + printf(" Loaded: %s%s%s (%s)\n", + on, strna(i->load_state), off, path); + else + printf(" Loaded: %s%s%s\n", + on, strna(i->load_state), off); + + if (i->transient) + printf("Transient: yes\n"); + + if (!strv_isempty(i->dropin_paths)) { + _cleanup_free_ char *dir = NULL; + bool last = false; + char ** dropin; + + STRV_FOREACH(dropin, i->dropin_paths) { + if (! dir || last) { + printf(dir ? " " : " Drop-In: "); + + dir = mfree(dir); + + dir = dirname_malloc(*dropin); + if (!dir) { + log_oom(); + return; + } + + printf("%s\n %s", dir, + special_glyph(TREE_RIGHT)); + } + + last = ! (*(dropin + 1) && startswith(*(dropin + 1), dir)); + + printf("%s%s", basename(*dropin), last ? "\n" : ", "); + } + } + + ss = streq_ptr(i->active_state, i->sub_state) ? NULL : i->sub_state; + if (ss) + printf(" Active: %s%s (%s)%s", + active_on, strna(i->active_state), ss, active_off); + else + printf(" Active: %s%s%s", + active_on, strna(i->active_state), active_off); + + if (!isempty(i->result) && !streq(i->result, "success")) + printf(" (Result: %s)", i->result); + + timestamp = (streq_ptr(i->active_state, "active") || + streq_ptr(i->active_state, "reloading")) ? i->active_enter_timestamp : + (streq_ptr(i->active_state, "inactive") || + streq_ptr(i->active_state, "failed")) ? i->inactive_enter_timestamp : + streq_ptr(i->active_state, "activating") ? i->inactive_exit_timestamp : + i->active_exit_timestamp; + + s1 = format_timestamp_relative(since1, sizeof(since1), timestamp); + s2 = format_timestamp(since2, sizeof(since2), timestamp); + + if (s1) + printf(" since %s; %s\n", s2, s1); + else if (s2) + printf(" since %s\n", s2); + else + printf("\n"); + + if (!i->condition_result && i->condition_timestamp > 0) { + s1 = format_timestamp_relative(since1, sizeof(since1), i->condition_timestamp); + s2 = format_timestamp(since2, sizeof(since2), i->condition_timestamp); + + printf("Condition: start %scondition failed%s at %s%s%s\n", + ansi_highlight_yellow(), ansi_normal(), + s2, s1 ? "; " : "", strempty(s1)); + if (i->failed_condition_trigger) + printf(" none of the trigger conditions were met\n"); + else if (i->failed_condition) + printf(" %s=%s%s was not met\n", + i->failed_condition, + i->failed_condition_negate ? "!" : "", + i->failed_condition_parameter); + } + + if (!i->assert_result && i->assert_timestamp > 0) { + s1 = format_timestamp_relative(since1, sizeof(since1), i->assert_timestamp); + s2 = format_timestamp(since2, sizeof(since2), i->assert_timestamp); + + printf(" Assert: start %sassertion failed%s at %s%s%s\n", + ansi_highlight_red(), ansi_normal(), + s2, s1 ? "; " : "", strempty(s1)); + if (i->failed_assert_trigger) + printf(" none of the trigger assertions were met\n"); + else if (i->failed_assert) + printf(" %s=%s%s was not met\n", + i->failed_assert, + i->failed_assert_negate ? "!" : "", + i->failed_assert_parameter); + } + + if (i->sysfs_path) + printf(" Device: %s\n", i->sysfs_path); + if (i->where) + printf(" Where: %s\n", i->where); + if (i->what) + printf(" What: %s\n", i->what); + + STRV_FOREACH(t, i->documentation) + printf(" %*s %s\n", 9, t == i->documentation ? "Docs:" : "", *t); + + 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); + + LIST_FOREACH(exec, p, i->exec) { + _cleanup_free_ char *argv = NULL; + bool good; + + /* Only show exited processes here */ + if (p->code == 0) + continue; + + argv = strv_join(p->argv, " "); + printf(" Process: "PID_FMT" %s=%s ", p->pid, p->name, strna(argv)); + + good = is_clean_exit_lsb(p->code, p->status, NULL); + if (!good) { + on = ansi_highlight_red(); + off = ansi_normal(); + } else + on = off = ""; + + printf("%s(code=%s, ", on, sigchld_code_to_string(p->code)); + + if (p->code == CLD_EXITED) { + const char *c; + + printf("status=%i", p->status); + + c = exit_status_to_string(p->status, EXIT_STATUS_SYSTEMD); + if (c) + printf("/%s", c); + + } else + printf("signal=%s", signal_to_string(p->status)); + + printf(")%s\n", off); + + if (i->main_pid == p->pid && + i->start_timestamp == p->start_timestamp && + i->exit_timestamp == p->start_timestamp) + /* Let's not show this twice */ + i->main_pid = 0; + + if (p->pid == i->control_pid) + i->control_pid = 0; + } + + if (i->main_pid > 0 || i->control_pid > 0) { + if (i->main_pid > 0) { + printf(" Main PID: "PID_FMT, i->main_pid); + + if (i->running) { + _cleanup_free_ char *comm = NULL; + get_process_comm(i->main_pid, &comm); + if (comm) + printf(" (%s)", comm); + } else if (i->exit_code > 0) { + printf(" (code=%s, ", sigchld_code_to_string(i->exit_code)); + + if (i->exit_code == CLD_EXITED) { + const char *c; + + printf("status=%i", i->exit_status); + + c = exit_status_to_string(i->exit_status, EXIT_STATUS_SYSTEMD); + if (c) + printf("/%s", c); + + } else + printf("signal=%s", signal_to_string(i->exit_status)); + printf(")"); + } + + if (i->control_pid > 0) + printf(";"); + } + + if (i->control_pid > 0) { + _cleanup_free_ char *c = NULL; + + printf(" %8s: "PID_FMT, i->main_pid ? "" : " Control", i->control_pid); + + get_process_comm(i->control_pid, &c); + if (c) + printf(" (%s)", c); + } + + printf("\n"); + } + + if (i->status_text) + printf(" Status: \"%s\"\n", i->status_text); + if (i->status_errno > 0) + printf(" Error: %i (%s)\n", i->status_errno, strerror(i->status_errno)); + + if (i->tasks_current != (uint64_t) -1) { + printf(" Tasks: %" PRIu64, i->tasks_current); + + if (i->tasks_max != (uint64_t) -1) + printf(" (limit: %" PRIi64 ")\n", i->tasks_max); + else + printf("\n"); + } + + if (i->memory_current != (uint64_t) -1) { + char buf[FORMAT_BYTES_MAX]; + + printf(" Memory: %s", format_bytes(buf, sizeof(buf), i->memory_current)); + + if (i->memory_limit != (uint64_t) -1) + printf(" (limit: %s)\n", format_bytes(buf, sizeof(buf), i->memory_limit)); + else + printf("\n"); + } + + if (i->cpu_usage_nsec != (uint64_t) -1) { + char buf[FORMAT_TIMESPAN_MAX]; + printf(" CPU: %s\n", format_timespan(buf, sizeof(buf), i->cpu_usage_nsec / NSEC_PER_USEC, USEC_PER_MSEC)); + } + + if (i->control_group) + printf(" CGroup: %s\n", i->control_group); + + { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + static const char prefix[] = " "; + unsigned c; + + c = columns(); + if (c > sizeof(prefix) - 1) + c -= sizeof(prefix) - 1; + else + c = 0; + + r = unit_show_processes(bus, i->id, i->control_group, prefix, c, get_output_flags(), &error); + if (r == -EBADR) { + unsigned k = 0; + pid_t extra[2]; + + /* Fallback for older systemd versions where the GetUnitProcesses() call is not yet available */ + + if (i->main_pid > 0) + extra[k++] = i->main_pid; + + if (i->control_pid > 0) + extra[k++] = i->control_pid; + + show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, i->control_group, prefix, c, extra, k, get_output_flags()); + } else if (r < 0) + log_warning_errno(r, "Failed to dump process list, ignoring: %s", bus_error_message(&error, r)); + } + + if (i->id && arg_transport == BUS_TRANSPORT_LOCAL) + show_journal_by_unit( + stdout, + i->id, + arg_output, + 0, + i->inactive_exit_timestamp_monotonic, + arg_lines, + getuid(), + get_output_flags() | OUTPUT_BEGIN_NEWLINE, + SD_JOURNAL_LOCAL_ONLY, + arg_scope == UNIT_FILE_SYSTEM, + ellipsized); + + if (i->need_daemon_reload) + warn_unit_file_changed(i->id); +} + +static void show_unit_help(UnitStatusInfo *i) { + char **p; + + assert(i); + + if (!i->documentation) { + log_info("Documentation for %s not known.", i->id); + return; + } + + STRV_FOREACH(p, i->documentation) + if (startswith(*p, "man:")) + show_man_page(*p + 4, false); + else + log_info("Can't show: %s", *p); +} + +static int status_property(const char *name, sd_bus_message *m, UnitStatusInfo *i, const char *contents) { + int r; + + assert(name); + assert(m); + assert(i); + + switch (contents[0]) { + + case SD_BUS_TYPE_STRING: { + const char *s; + + r = sd_bus_message_read(m, "s", &s); + if (r < 0) + return bus_log_parse_error(r); + + if (!isempty(s)) { + if (streq(name, "Id")) + i->id = s; + else if (streq(name, "LoadState")) + i->load_state = s; + else if (streq(name, "ActiveState")) + i->active_state = s; + else if (streq(name, "SubState")) + i->sub_state = s; + else if (streq(name, "Description")) + i->description = s; + else if (streq(name, "FragmentPath")) + i->fragment_path = s; + else if (streq(name, "SourcePath")) + i->source_path = s; +#ifndef NOLEGACY + else if (streq(name, "DefaultControlGroup")) { + const char *e; + e = startswith(s, SYSTEMD_CGROUP_CONTROLLER ":"); + if (e) + i->control_group = e; + } +#endif + else if (streq(name, "ControlGroup")) + i->control_group = s; + else if (streq(name, "StatusText")) + i->status_text = s; + else if (streq(name, "PIDFile")) + i->pid_file = s; + else if (streq(name, "SysFSPath")) + i->sysfs_path = s; + else if (streq(name, "Where")) + i->where = s; + else if (streq(name, "What")) + i->what = s; + else if (streq(name, "Following")) + i->following = s; + else if (streq(name, "UnitFileState")) + i->unit_file_state = s; + else if (streq(name, "UnitFilePreset")) + i->unit_file_preset = s; + else if (streq(name, "Result")) + i->result = s; + } + + break; + } + + case SD_BUS_TYPE_BOOLEAN: { + int b; + + r = sd_bus_message_read(m, "b", &b); + if (r < 0) + return bus_log_parse_error(r); + + if (streq(name, "Accept")) + i->accept = b; + else if (streq(name, "NeedDaemonReload")) + i->need_daemon_reload = b; + else if (streq(name, "ConditionResult")) + i->condition_result = b; + else if (streq(name, "AssertResult")) + i->assert_result = b; + else if (streq(name, "Transient")) + i->transient = b; + + break; + } + + case SD_BUS_TYPE_UINT32: { + uint32_t u; + + r = sd_bus_message_read(m, "u", &u); + if (r < 0) + return bus_log_parse_error(r); + + if (streq(name, "MainPID")) { + if (u > 0) { + i->main_pid = (pid_t) u; + i->running = true; + } + } else if (streq(name, "ControlPID")) + i->control_pid = (pid_t) u; + else if (streq(name, "ExecMainPID")) { + if (u > 0) + i->main_pid = (pid_t) u; + } else if (streq(name, "NAccepted")) + i->n_accepted = u; + else if (streq(name, "NConnections")) + i->n_connections = u; + + break; + } + + case SD_BUS_TYPE_INT32: { + int32_t j; + + r = sd_bus_message_read(m, "i", &j); + if (r < 0) + return bus_log_parse_error(r); + + if (streq(name, "ExecMainCode")) + i->exit_code = (int) j; + else if (streq(name, "ExecMainStatus")) + i->exit_status = (int) j; + else if (streq(name, "StatusErrno")) + i->status_errno = (int) j; + + break; + } + + case SD_BUS_TYPE_UINT64: { + uint64_t u; + + r = sd_bus_message_read(m, "t", &u); + if (r < 0) + return bus_log_parse_error(r); + + if (streq(name, "ExecMainStartTimestamp")) + i->start_timestamp = (usec_t) u; + else if (streq(name, "ExecMainExitTimestamp")) + i->exit_timestamp = (usec_t) u; + else if (streq(name, "ActiveEnterTimestamp")) + i->active_enter_timestamp = (usec_t) u; + else if (streq(name, "InactiveEnterTimestamp")) + i->inactive_enter_timestamp = (usec_t) u; + else if (streq(name, "InactiveExitTimestamp")) + i->inactive_exit_timestamp = (usec_t) u; + else if (streq(name, "InactiveExitTimestampMonotonic")) + i->inactive_exit_timestamp_monotonic = (usec_t) u; + else if (streq(name, "ActiveExitTimestamp")) + i->active_exit_timestamp = (usec_t) u; + else if (streq(name, "ConditionTimestamp")) + i->condition_timestamp = (usec_t) u; + else if (streq(name, "AssertTimestamp")) + i->assert_timestamp = (usec_t) u; + else if (streq(name, "MemoryCurrent")) + i->memory_current = u; + else if (streq(name, "MemoryLimit")) + i->memory_limit = u; + else if (streq(name, "TasksCurrent")) + i->tasks_current = u; + else if (streq(name, "TasksMax")) + i->tasks_max = u; + else if (streq(name, "CPUUsageNSec")) + i->cpu_usage_nsec = u; + + break; + } + + case SD_BUS_TYPE_ARRAY: + + if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && startswith(name, "Exec")) { + _cleanup_free_ ExecStatusInfo *info = NULL; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sasbttttuii)"); + if (r < 0) + return bus_log_parse_error(r); + + info = new0(ExecStatusInfo, 1); + if (!info) + return log_oom(); + + while ((r = exec_status_info_deserialize(m, info)) > 0) { + + info->name = strdup(name); + if (!info->name) + return log_oom(); + + LIST_PREPEND(exec, i->exec, info); + + info = new0(ExecStatusInfo, 1); + if (!info) + return log_oom(); + } + + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Listen")) { + const char *type, *path; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(ss)", &type, &path)) > 0) { + + r = strv_extend(&i->listen, type); + if (r < 0) + return r; + + r = strv_extend(&i->listen, path); + if (r < 0) + return r; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRING && streq(name, "DropInPaths")) { + + r = sd_bus_message_read_strv(m, &i->dropin_paths); + if (r < 0) + return bus_log_parse_error(r); + + } else if (contents[1] == SD_BUS_TYPE_STRING && streq(name, "Documentation")) { + + r = sd_bus_message_read_strv(m, &i->documentation); + if (r < 0) + return bus_log_parse_error(r); + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Conditions")) { + const char *cond, *param; + int trigger, negate; + int32_t state; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sbbsi)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(sbbsi)", &cond, &trigger, &negate, ¶m, &state)) > 0) { + log_debug("%s %d %d %s %d", cond, trigger, negate, param, state); + if (state < 0 && (!trigger || !i->failed_condition)) { + i->failed_condition = cond; + i->failed_condition_trigger = trigger; + i->failed_condition_negate = negate; + i->failed_condition_parameter = param; + } + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Asserts")) { + const char *cond, *param; + int trigger, negate; + int32_t state; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sbbsi)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(sbbsi)", &cond, &trigger, &negate, ¶m, &state)) > 0) { + log_debug("%s %d %d %s %d", cond, trigger, negate, param, state); + if (state < 0 && (!trigger || !i->failed_assert)) { + i->failed_assert = cond; + i->failed_assert_trigger = trigger; + i->failed_assert_negate = negate; + i->failed_assert_parameter = param; + } + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + } else + goto skip; + + break; + + case SD_BUS_TYPE_STRUCT_BEGIN: + + if (streq(name, "LoadError")) { + const char *n, *message; + + r = sd_bus_message_read(m, "(ss)", &n, &message); + if (r < 0) + return bus_log_parse_error(r); + + if (!isempty(message)) + i->load_error = message; + } else + goto skip; + + break; + + default: + goto skip; + } + + return 0; + +skip: + r = sd_bus_message_skip(m, contents); + if (r < 0) + return bus_log_parse_error(r); + + return 0; +} + +#define print_prop(name, fmt, ...) \ + do { \ + if (arg_value) \ + printf(fmt "\n", __VA_ARGS__); \ + else \ + printf("%s=" fmt "\n", name, __VA_ARGS__); \ + } while(0) + +static int print_property(const char *name, sd_bus_message *m, const char *contents) { + int r; + + assert(name); + assert(m); + + /* This is a low-level property printer, see + * print_status_info() for the nicer output */ + + if (arg_properties && !strv_find(arg_properties, name)) { + /* skip what we didn't read */ + r = sd_bus_message_skip(m, contents); + return r; + } + + switch (contents[0]) { + + case SD_BUS_TYPE_STRUCT_BEGIN: + + if (contents[1] == SD_BUS_TYPE_UINT32 && streq(name, "Job")) { + uint32_t u; + + r = sd_bus_message_read(m, "(uo)", &u, NULL); + if (r < 0) + return bus_log_parse_error(r); + + if (u > 0) + print_prop(name, "%"PRIu32, u); + else if (arg_all) + print_prop(name, "%s", ""); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRING && streq(name, "Unit")) { + const char *s; + + r = sd_bus_message_read(m, "(so)", &s, NULL); + if (r < 0) + return bus_log_parse_error(r); + + if (arg_all || !isempty(s)) + print_prop(name, "%s", s); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRING && streq(name, "LoadError")) { + const char *a = NULL, *b = NULL; + + r = sd_bus_message_read(m, "(ss)", &a, &b); + if (r < 0) + return bus_log_parse_error(r); + + if (arg_all || !isempty(a) || !isempty(b)) + print_prop(name, "%s \"%s\"", strempty(a), strempty(b)); + + return 0; + } else if (streq_ptr(name, "SystemCallFilter")) { + _cleanup_strv_free_ char **l = NULL; + int whitelist; + + r = sd_bus_message_enter_container(m, 'r', "bas"); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(m, "b", &whitelist); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_strv(m, &l); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + if (arg_all || whitelist || !strv_isempty(l)) { + bool first = true; + char **i; + + if (!arg_value) { + fputs(name, stdout); + fputc('=', stdout); + } + + if (!whitelist) + fputc('~', stdout); + + STRV_FOREACH(i, l) { + if (first) + first = false; + else + fputc(' ', stdout); + + fputs(*i, stdout); + } + fputc('\n', stdout); + } + + return 0; + } + + break; + + case SD_BUS_TYPE_ARRAY: + + if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "EnvironmentFiles")) { + const char *path; + int ignore; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sb)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(sb)", &path, &ignore)) > 0) + print_prop("EnvironmentFile", "%s (ignore_errors=%s)\n", path, yes_no(ignore)); + + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Paths")) { + const char *type, *path; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(ss)", &type, &path)) > 0) + print_prop(type, "%s", path); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Listen")) { + const char *type, *path; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(ss)", &type, &path)) > 0) + if (arg_value) + puts(path); + else + printf("Listen%s=%s\n", type, path); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Timers")) { + const char *base; + uint64_t value, next_elapse; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(stt)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(stt)", &base, &value, &next_elapse)) > 0) { + char timespan1[FORMAT_TIMESPAN_MAX], timespan2[FORMAT_TIMESPAN_MAX]; + + print_prop(base, "{ value=%s ; next_elapse=%s }", + format_timespan(timespan1, sizeof(timespan1), value, 0), + format_timespan(timespan2, sizeof(timespan2), next_elapse, 0)); + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && startswith(name, "Exec")) { + ExecStatusInfo info = {}; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sasbttttuii)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = exec_status_info_deserialize(m, &info)) > 0) { + char timestamp1[FORMAT_TIMESTAMP_MAX], timestamp2[FORMAT_TIMESTAMP_MAX]; + _cleanup_free_ char *tt; + + tt = strv_join(info.argv, " "); + + print_prop(name, + "{ path=%s ; argv[]=%s ; ignore_errors=%s ; start_time=[%s] ; stop_time=[%s] ; pid="PID_FMT" ; code=%s ; status=%i%s%s }", + strna(info.path), + strna(tt), + yes_no(info.ignore), + strna(format_timestamp(timestamp1, sizeof(timestamp1), info.start_timestamp)), + strna(format_timestamp(timestamp2, sizeof(timestamp2), info.exit_timestamp)), + info.pid, + sigchld_code_to_string(info.code), + info.status, + info.code == CLD_EXITED ? "" : "/", + strempty(info.code == CLD_EXITED ? NULL : signal_to_string(info.status))); + + free(info.path); + strv_free(info.argv); + zero(info); + } + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "DeviceAllow")) { + const char *path, *rwm; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(ss)", &path, &rwm)) > 0) + print_prop(name, "%s %s", strna(path), strna(rwm)); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && (streq(name, "IODeviceWeight") || streq(name, "BlockIODeviceWeight"))) { + const char *path; + uint64_t weight; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(st)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(st)", &path, &weight)) > 0) + print_prop(name, "%s %"PRIu64, strna(path), weight); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && (cgroup_io_limit_type_from_string(name) >= 0 || + streq(name, "BlockIOReadBandwidth") || streq(name, "BlockIOWriteBandwidth"))) { + const char *path; + uint64_t bandwidth; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(st)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(st)", &path, &bandwidth)) > 0) + print_prop(name, "%s %"PRIu64, strna(path), bandwidth); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + } + + break; + } + + r = bus_print_property(name, m, arg_value, arg_all); + if (r < 0) + return bus_log_parse_error(r); + + if (r == 0) { + r = sd_bus_message_skip(m, contents); + if (r < 0) + return bus_log_parse_error(r); + + if (arg_all) + printf("%s=[unprintable]\n", name); + } + + return 0; +} + +static int show_one( + const char *verb, + sd_bus *bus, + const char *path, + bool show_properties, + bool *new_line, + bool *ellipsized) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + UnitStatusInfo info = { + .memory_current = (uint64_t) -1, + .memory_limit = (uint64_t) -1, + .cpu_usage_nsec = (uint64_t) -1, + .tasks_current = (uint64_t) -1, + .tasks_max = (uint64_t) -1, + }; + ExecStatusInfo *p; + int r; + + assert(path); + assert(new_line); + + log_debug("Showing one %s", path); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.DBus.Properties", + "GetAll", + &error, + &reply, + "s", ""); + if (r < 0) + return log_error_errno(r, "Failed to get properties: %s", bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sv}"); + if (r < 0) + return bus_log_parse_error(r); + + if (*new_line) + printf("\n"); + + *new_line = true; + + while ((r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { + const char *name, *contents; + + r = sd_bus_message_read(reply, "s", &name); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_peek_type(reply, NULL, &contents); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_VARIANT, contents); + if (r < 0) + return bus_log_parse_error(r); + + if (show_properties) + r = print_property(name, reply, contents); + else + r = status_property(name, reply, &info, contents); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = 0; + + if (!show_properties) { + if (streq(verb, "help")) + show_unit_help(&info); + else + print_status_info(bus, &info, ellipsized); + } + + strv_free(info.documentation); + strv_free(info.dropin_paths); + strv_free(info.listen); + + if (!streq_ptr(info.active_state, "active") && + !streq_ptr(info.active_state, "reloading") && + streq(verb, "status")) { + /* According to LSB: "program not running" */ + /* 0: program is running or service is OK + * 1: program is dead and /run PID file exists + * 2: program is dead and /run/lock lock file exists + * 3: program is not running + * 4: program or service status is unknown + */ + if (info.pid_file && access(info.pid_file, F_OK) == 0) + r = 1; + else + r = 3; + } + + while ((p = info.exec)) { + LIST_REMOVE(exec, info.exec, p); + exec_status_info_free(p); + } + + return r; +} + +static int get_unit_dbus_path_by_pid( + sd_bus *bus, + uint32_t pid, + char **unit) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + char *u; + int r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnitByPID", + &error, + &reply, + "u", pid); + if (r < 0) + return log_error_errno(r, "Failed to get unit for PID %"PRIu32": %s", pid, bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "o", &u); + if (r < 0) + return bus_log_parse_error(r); + + u = strdup(u); + if (!u) + return log_oom(); + + *unit = u; + return 0; +} + +static int show_all( + const char* verb, + sd_bus *bus, + bool show_properties, + bool *new_line, + bool *ellipsized) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ UnitInfo *unit_infos = NULL; + const UnitInfo *u; + unsigned c; + int r, ret = 0; + + r = get_unit_list(bus, NULL, NULL, &unit_infos, 0, &reply); + if (r < 0) + return r; + + pager_open(arg_no_pager, false); + + c = (unsigned) r; + + qsort_safe(unit_infos, c, sizeof(UnitInfo), compare_unit_info); + + for (u = unit_infos; u < unit_infos + c; u++) { + _cleanup_free_ char *p = NULL; + + p = unit_dbus_path_from_name(u->id); + if (!p) + return log_oom(); + + r = show_one(verb, bus, p, show_properties, new_line, ellipsized); + if (r < 0) + return r; + else if (r > 0 && ret == 0) + ret = r; + } + + return ret; +} + +static int show_system_status(sd_bus *bus) { + char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], since2[FORMAT_TIMESTAMP_MAX]; + _cleanup_free_ char *hn = NULL; + _cleanup_(machine_info_clear) struct machine_info mi = {}; + const char *on, *off; + int r; + + hn = gethostname_malloc(); + if (!hn) + return log_oom(); + + r = bus_map_all_properties(bus, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", machine_info_property_map, &mi); + if (r < 0) + return log_error_errno(r, "Failed to read server status: %m"); + + if (streq_ptr(mi.state, "degraded")) { + on = ansi_highlight_red(); + off = ansi_normal(); + } else if (!streq_ptr(mi.state, "running")) { + on = ansi_highlight_yellow(); + off = ansi_normal(); + } else + on = off = ""; + + printf("%s%s%s %s\n", on, special_glyph(BLACK_CIRCLE), off, arg_host ? arg_host : hn); + + printf(" State: %s%s%s\n", + on, strna(mi.state), off); + + printf(" Jobs: %" PRIu32 " queued\n", mi.n_jobs); + printf(" Failed: %" PRIu32 " units\n", mi.n_failed_units); + + printf(" Since: %s; %s\n", + format_timestamp(since2, sizeof(since2), mi.timestamp), + format_timestamp_relative(since1, sizeof(since1), mi.timestamp)); + + printf(" CGroup: %s\n", mi.control_group ?: "/"); + if (IN_SET(arg_transport, + BUS_TRANSPORT_LOCAL, + BUS_TRANSPORT_MACHINE)) { + static const char prefix[] = " "; + unsigned c; + + c = columns(); + if (c > sizeof(prefix) - 1) + c -= sizeof(prefix) - 1; + else + c = 0; + + show_cgroup(SYSTEMD_CGROUP_CONTROLLER, strempty(mi.control_group), prefix, c, get_output_flags()); + } + + return 0; +} + +static int show(int argc, char *argv[], void *userdata) { + bool show_properties, show_status, show_help, new_line = false; + bool ellipsized = false; + int r, ret = 0; + sd_bus *bus; + + assert(argv); + + show_properties = streq(argv[0], "show"); + show_status = streq(argv[0], "status"); + show_help = streq(argv[0], "help"); + + if (show_help && argc <= 1) { + log_error("This command expects one or more unit names. Did you mean --help?"); + return -EINVAL; + } + + pager_open(arg_no_pager, false); + + if (show_status) + /* Increase max number of open files to 16K if we can, we + * might needs this when browsing journal files, which might + * be split up into many files. */ + setrlimit_closest(RLIMIT_NOFILE, &RLIMIT_MAKE_CONST(16384)); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + /* If no argument is specified inspect the manager itself */ + if (show_properties && argc <= 1) + return show_one(argv[0], bus, "/org/freedesktop/systemd1", show_properties, &new_line, &ellipsized); + + if (show_status && argc <= 1) { + + pager_open(arg_no_pager, false); + show_system_status(bus); + new_line = true; + + if (arg_all) + ret = show_all(argv[0], bus, false, &new_line, &ellipsized); + } else { + _cleanup_free_ char **patterns = NULL; + char **name; + + STRV_FOREACH(name, strv_skip(argv, 1)) { + _cleanup_free_ char *unit = NULL; + uint32_t id; + + if (safe_atou32(*name, &id) < 0) { + if (strv_push(&patterns, *name) < 0) + return log_oom(); + + continue; + } else if (show_properties) { + /* Interpret as job id */ + if (asprintf(&unit, "/org/freedesktop/systemd1/job/%u", id) < 0) + return log_oom(); + + } else { + /* Interpret as PID */ + r = get_unit_dbus_path_by_pid(bus, id, &unit); + if (r < 0) { + ret = r; + continue; + } + } + + r = show_one(argv[0], bus, unit, show_properties, &new_line, &ellipsized); + if (r < 0) + return r; + else if (r > 0 && ret == 0) + ret = r; + } + + if (!strv_isempty(patterns)) { + _cleanup_strv_free_ char **names = NULL; + + r = expand_names(bus, patterns, NULL, &names); + if (r < 0) + return log_error_errno(r, "Failed to expand names: %m"); + + STRV_FOREACH(name, names) { + _cleanup_free_ char *unit; + + unit = unit_dbus_path_from_name(*name); + if (!unit) + return log_oom(); + + r = show_one(argv[0], bus, unit, show_properties, &new_line, &ellipsized); + if (r < 0) + return r; + else if (r > 0 && ret == 0) + ret = r; + } + } + } + + if (ellipsized && !arg_quiet) + printf("Hint: Some lines were ellipsized, use -l to show in full.\n"); + + return ret; +} + +static int cat_file(const char *filename, bool newline) { + _cleanup_close_ int fd; + + fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return -errno; + + printf("%s%s# %s%s\n", + newline ? "\n" : "", + ansi_highlight_blue(), + filename, + ansi_normal()); + fflush(stdout); + + return copy_bytes(fd, STDOUT_FILENO, (uint64_t) -1, false); +} + +static int cat(int argc, char *argv[], void *userdata) { + _cleanup_lookup_paths_free_ LookupPaths lp = {}; + _cleanup_strv_free_ char **names = NULL; + char **name; + sd_bus *bus; + bool first = true; + int r; + + if (arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Cannot remotely cat units."); + return -EINVAL; + } + + r = lookup_paths_init(&lp, arg_scope, 0, arg_root); + if (r < 0) + return log_error_errno(r, "Failed to determine unit paths: %m"); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = expand_names(bus, strv_skip(argv, 1), NULL, &names); + if (r < 0) + return log_error_errno(r, "Failed to expand names: %m"); + + pager_open(arg_no_pager, false); + + STRV_FOREACH(name, names) { + _cleanup_free_ char *fragment_path = NULL; + _cleanup_strv_free_ char **dropin_paths = NULL; + char **path; + + r = unit_find_paths(bus, *name, &lp, &fragment_path, &dropin_paths); + if (r < 0) + return r; + else if (r == 0) + return -ENOENT; + + if (first) + first = false; + else + puts(""); + + if (fragment_path) { + r = cat_file(fragment_path, false); + if (r < 0) + return log_warning_errno(r, "Failed to cat %s: %m", fragment_path); + } + + STRV_FOREACH(path, dropin_paths) { + r = cat_file(*path, path == dropin_paths); + if (r < 0) + return log_warning_errno(r, "Failed to cat %s: %m", *path); + } + } + + return 0; +} + +static int set_property(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *n = NULL; + sd_bus *bus; + char **i; + int r; + + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SetUnitProperties"); + if (r < 0) + return bus_log_create_error(r); + + r = unit_name_mangle(argv[1], UNIT_NAME_NOGLOB, &n); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + + r = sd_bus_message_append(m, "sb", n, arg_runtime); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + STRV_FOREACH(i, strv_skip(argv, 2)) { + r = bus_append_unit_property_assignment(m, *i); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, NULL); + if (r < 0) + return log_error_errno(r, "Failed to set unit properties on %s: %s", n, bus_error_message(&error, r)); + + return 0; +} + +static int daemon_reload(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *method; + sd_bus *bus; + int r; + + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + if (arg_action == ACTION_RELOAD) + method = "Reload"; + else if (arg_action == ACTION_REEXEC) + method = "Reexecute"; + else { + assert(arg_action == ACTION_SYSTEMCTL); + + method = + streq(argv[0], "clear-jobs") || + streq(argv[0], "cancel") ? "ClearJobs" : + streq(argv[0], "daemon-reexec") ? "Reexecute" : + streq(argv[0], "reset-failed") ? "ResetFailed" : + streq(argv[0], "halt") ? "Halt" : + streq(argv[0], "poweroff") ? "PowerOff" : + streq(argv[0], "reboot") ? "Reboot" : + streq(argv[0], "kexec") ? "KExec" : + streq(argv[0], "exit") ? "Exit" : + /* "daemon-reload" */ "Reload"; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + method, + &error, + NULL, + NULL); + if (r == -ENOENT && arg_action != ACTION_SYSTEMCTL) + /* There's always a fallback possible for + * legacy actions. */ + r = -EADDRNOTAVAIL; + else if ((r == -ETIMEDOUT || r == -ECONNRESET) && streq(method, "Reexecute")) + /* On reexecution, we expect a disconnect, not a + * reply */ + r = 0; + else if (r < 0) + return log_error_errno(r, "Failed to reload daemon: %s", bus_error_message(&error, r)); + + return r < 0 ? r : 0; +} + +static int reset_failed(int argc, char *argv[], void *userdata) { + _cleanup_strv_free_ char **names = NULL; + sd_bus *bus; + char **name; + int r, q; + + if (argc <= 1) + return daemon_reload(argc, argv, userdata); + + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = expand_names(bus, strv_skip(argv, 1), NULL, &names); + if (r < 0) + return log_error_errno(r, "Failed to expand names: %m"); + + STRV_FOREACH(name, names) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + q = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ResetFailedUnit", + &error, + NULL, + "s", *name); + if (q < 0) { + log_error_errno(q, "Failed to reset failed state of unit %s: %s", *name, bus_error_message(&error, q)); + if (r == 0) + r = q; + } + } + + return r; +} + +static int show_environment(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *text; + sd_bus *bus; + int r; + + pager_open(arg_no_pager, false); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_get_property( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Environment", + &error, + &reply, + "as"); + if (r < 0) + return log_error_errno(r, "Failed to get environment: %s", bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "s"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_STRING, &text)) > 0) + puts(text); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + return 0; +} + +static int switch_root(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *cmdline_init = NULL; + const char *root, *init; + sd_bus *bus; + int r; + + if (arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Cannot switch root remotely."); + return -EINVAL; + } + + if (argc < 2 || argc > 3) { + log_error("Wrong number of arguments."); + return -EINVAL; + } + + root = argv[1]; + + if (argc >= 3) + init = argv[2]; + else { + r = parse_env_file("/proc/cmdline", WHITESPACE, + "init", &cmdline_init, + NULL); + if (r < 0) + log_debug_errno(r, "Failed to parse /proc/cmdline: %m"); + + init = cmdline_init; + } + + if (isempty(init)) + init = NULL; + + if (init) { + const char *root_systemd_path = NULL, *root_init_path = NULL; + + root_systemd_path = strjoina(root, "/" SYSTEMD_BINARY_PATH); + root_init_path = strjoina(root, "/", init); + + /* If the passed init is actually the same as the + * systemd binary, then let's suppress it. */ + if (files_same(root_init_path, root_systemd_path) > 0) + init = NULL; + } + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + log_debug("Switching root - root: %s; init: %s", root, strna(init)); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SwitchRoot", + &error, + NULL, + "ss", root, init); + if (r < 0) + return log_error_errno(r, "Failed to switch root: %s", bus_error_message(&error, r)); + + return 0; +} + +static int set_environment(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + const char *method; + sd_bus *bus; + int r; + + assert(argc > 1); + assert(argv); + + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + method = streq(argv[0], "set-environment") + ? "SetEnvironment" + : "UnsetEnvironment"; + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + method); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, strv_skip(argv, 1)); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, NULL); + if (r < 0) + return log_error_errno(r, "Failed to set environment: %s", bus_error_message(&error, r)); + + return 0; +} + +static int import_environment(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + sd_bus *bus; + int r; + + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SetEnvironment"); + if (r < 0) + return bus_log_create_error(r); + + if (argc < 2) + r = sd_bus_message_append_strv(m, environ); + else { + char **a, **b; + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return bus_log_create_error(r); + + STRV_FOREACH(a, strv_skip(argv, 1)) { + + if (!env_name_is_valid(*a)) { + log_error("Not a valid environment variable name: %s", *a); + return -EINVAL; + } + + STRV_FOREACH(b, environ) { + const char *eq; + + eq = startswith(*b, *a); + if (eq && *eq == '=') { + + r = sd_bus_message_append(m, "s", *b); + if (r < 0) + return bus_log_create_error(r); + + break; + } + } + } + + r = sd_bus_message_close_container(m); + } + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, NULL); + if (r < 0) + return log_error_errno(r, "Failed to import environment: %s", bus_error_message(&error, r)); + + return 0; +} + +static int enable_sysv_units(const char *verb, char **args) { + int r = 0; + +#if defined(HAVE_SYSV_COMPAT) + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + unsigned f = 0; + + /* Processes all SysV units, and reshuffles the array so that afterwards only the native units remain */ + + if (arg_scope != UNIT_FILE_SYSTEM) + return 0; + + if (getenv_bool("SYSTEMCTL_SKIP_SYSV") > 0) + return 0; + + if (!STR_IN_SET(verb, + "enable", + "disable", + "is-enabled")) + return 0; + + r = lookup_paths_init(&paths, arg_scope, LOOKUP_PATHS_EXCLUDE_GENERATED, arg_root); + if (r < 0) + return r; + + r = 0; + while (args[f]) { + + const char *argv[] = { + ROOTLIBEXECDIR "/systemd-sysv-install", + NULL, + NULL, + NULL, + NULL, + }; + + _cleanup_free_ char *p = NULL, *q = NULL, *l = NULL; + bool found_native = false, found_sysv; + siginfo_t status; + const char *name; + unsigned c = 1; + pid_t pid; + int j; + + name = args[f++]; + + if (!endswith(name, ".service")) + continue; + + if (path_is_absolute(name)) + continue; + + j = unit_file_exists(arg_scope, &paths, name); + if (j < 0 && !IN_SET(j, -ELOOP, -ERFKILL, -EADDRNOTAVAIL)) + return log_error_errno(j, "Failed to lookup unit file state: %m"); + found_native = j != 0; + + /* If we have both a native unit and a SysV script, enable/disable them both (below); for is-enabled, + * prefer the native unit */ + if (found_native && streq(verb, "is-enabled")) + continue; + + p = path_join(arg_root, SYSTEM_SYSVINIT_PATH, name); + if (!p) + return log_oom(); + + p[strlen(p) - strlen(".service")] = 0; + found_sysv = access(p, F_OK) >= 0; + if (!found_sysv) + continue; + + if (found_native) + log_info("Synchronizing state of %s with SysV service script with %s.", name, argv[0]); + else + log_info("%s is not a native service, redirecting to systemd-sysv-install.", name); + + if (!isempty(arg_root)) + argv[c++] = q = strappend("--root=", arg_root); + + argv[c++] = verb; + argv[c++] = basename(p); + argv[c] = NULL; + + l = strv_join((char**)argv, " "); + if (!l) + return log_oom(); + + log_info("Executing: %s", l); + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + else if (pid == 0) { + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + execv(argv[0], (char**) argv); + log_error_errno(errno, "Failed to execute %s: %m", argv[0]); + _exit(EXIT_FAILURE); + } + + j = wait_for_terminate(pid, &status); + if (j < 0) { + log_error_errno(j, "Failed to wait for child: %m"); + return j; + } + + if (status.si_code == CLD_EXITED) { + if (streq(verb, "is-enabled")) { + if (status.si_status == 0) { + if (!arg_quiet) + puts("enabled"); + r = 1; + } else { + if (!arg_quiet) + puts("disabled"); + } + + } else if (status.si_status != 0) + return -EBADE; /* We don't warn here, under the assumption the script already showed an explanation */ + } else { + log_error("Unexpected waitid() result."); + return -EPROTO; + } + + if (found_native) + continue; + + /* Remove this entry, so that we don't try enabling it as native unit */ + assert(f > 0); + f--; + assert(args[f] == name); + strv_remove(args, name); + } + +#endif + return r; +} + +static int mangle_names(char **original_names, char ***mangled_names) { + char **i, **l, **name; + int r; + + l = i = new(char*, strv_length(original_names) + 1); + if (!l) + return log_oom(); + + STRV_FOREACH(name, original_names) { + + /* When enabling units qualified path names are OK, + * too, hence allow them explicitly. */ + + if (is_path(*name)) { + *i = strdup(*name); + if (!*i) { + strv_free(l); + return log_oom(); + } + } else { + r = unit_name_mangle(*name, UNIT_NAME_NOGLOB, i); + if (r < 0) { + strv_free(l); + return log_error_errno(r, "Failed to mangle unit name: %m"); + } + } + + i++; + } + + *i = NULL; + *mangled_names = l; + + return 0; +} + +static int enable_unit(int argc, char *argv[], void *userdata) { + _cleanup_strv_free_ char **names = NULL; + const char *verb = argv[0]; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + int carries_install_info = -1; + bool ignore_carries_install_info = arg_quiet; + int r; + + if (!argv[1]) + return 0; + + r = mangle_names(strv_skip(argv, 1), &names); + if (r < 0) + return r; + + r = enable_sysv_units(verb, names); + if (r < 0) + return r; + + /* If the operation was fully executed by the SysV compat, let's finish early */ + if (strv_isempty(names)) { + if (arg_no_reload || install_client_side()) + return 0; + return daemon_reload(argc, argv, userdata); + } + + if (install_client_side()) { + if (streq(verb, "enable")) { + r = unit_file_enable(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); + carries_install_info = r; + } else if (streq(verb, "disable")) + r = unit_file_disable(arg_scope, arg_runtime, arg_root, names, &changes, &n_changes); + else if (streq(verb, "reenable")) { + r = unit_file_reenable(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); + carries_install_info = r; + } else if (streq(verb, "link")) + r = unit_file_link(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); + else if (streq(verb, "preset")) { + r = unit_file_preset(arg_scope, arg_runtime, arg_root, names, arg_preset_mode, arg_force, &changes, &n_changes); + } else if (streq(verb, "mask")) + r = unit_file_mask(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); + else if (streq(verb, "unmask")) + r = unit_file_unmask(arg_scope, arg_runtime, arg_root, names, &changes, &n_changes); + else if (streq(verb, "revert")) + r = unit_file_revert(arg_scope, arg_root, names, &changes, &n_changes); + else + assert_not_reached("Unknown verb"); + + unit_file_dump_changes(r, verb, changes, n_changes, arg_quiet); + if (r < 0) + return r; + r = 0; + } else { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + bool expect_carries_install_info = false; + bool send_runtime = true, send_force = true, send_preset_mode = false; + const char *method; + sd_bus *bus; + + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + if (streq(verb, "enable")) { + method = "EnableUnitFiles"; + expect_carries_install_info = true; + } else if (streq(verb, "disable")) { + method = "DisableUnitFiles"; + send_force = false; + } else if (streq(verb, "reenable")) { + method = "ReenableUnitFiles"; + expect_carries_install_info = true; + } else if (streq(verb, "link")) + method = "LinkUnitFiles"; + else if (streq(verb, "preset")) { + + if (arg_preset_mode != UNIT_FILE_PRESET_FULL) { + method = "PresetUnitFilesWithMode"; + send_preset_mode = true; + } else + method = "PresetUnitFiles"; + + expect_carries_install_info = true; + ignore_carries_install_info = true; + } else if (streq(verb, "mask")) + method = "MaskUnitFiles"; + else if (streq(verb, "unmask")) { + method = "UnmaskUnitFiles"; + send_force = false; + } else if (streq(verb, "revert")) { + method = "RevertUnitFiles"; + send_runtime = send_force = false; + } else + assert_not_reached("Unknown verb"); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + method); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, names); + if (r < 0) + return bus_log_create_error(r); + + if (send_preset_mode) { + r = sd_bus_message_append(m, "s", unit_file_preset_mode_to_string(arg_preset_mode)); + if (r < 0) + return bus_log_create_error(r); + } + + if (send_runtime) { + r = sd_bus_message_append(m, "b", arg_runtime); + if (r < 0) + return bus_log_create_error(r); + } + + if (send_force) { + r = sd_bus_message_append(m, "b", arg_force); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to %s unit: %s", verb, bus_error_message(&error, r)); + + if (expect_carries_install_info) { + r = sd_bus_message_read(reply, "b", &carries_install_info); + if (r < 0) + return bus_log_parse_error(r); + } + + r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes); + if (r < 0) + return r; + + /* Try to reload if enabled */ + if (!arg_no_reload) + r = daemon_reload(argc, argv, userdata); + else + r = 0; + } + + if (carries_install_info == 0 && !ignore_carries_install_info) + log_warning("The unit files have no installation config (WantedBy, RequiredBy, Also, Alias\n" + "settings in the [Install] section, and DefaultInstance for template units).\n" + "This means they are not meant to be enabled using systemctl.\n" + "Possible reasons for having this kind of units are:\n" + "1) A unit may be statically enabled by being symlinked from another unit's\n" + " .wants/ or .requires/ directory.\n" + "2) A unit's purpose may be to act as a helper for some other unit which has\n" + " a requirement dependency on it.\n" + "3) A unit may be started when needed via activation (socket, path, timer,\n" + " D-Bus, udev, scripted systemctl call, ...).\n" + "4) In case of template units, the unit is meant to be enabled with some\n" + " instance name specified."); + + if (arg_now && n_changes > 0 && STR_IN_SET(argv[0], "enable", "disable", "mask")) { + char *new_args[n_changes + 2]; + sd_bus *bus; + unsigned i; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + goto finish; + + new_args[0] = (char*) (streq(argv[0], "enable") ? "start" : "stop"); + for (i = 0; i < n_changes; i++) + new_args[i + 1] = basename(changes[i].path); + new_args[i + 1] = NULL; + + r = start_unit(strv_length(new_args), new_args, userdata); + } + +finish: + unit_file_changes_free(changes, n_changes); + + return r; +} + +static int add_dependency(int argc, char *argv[], void *userdata) { + _cleanup_strv_free_ char **names = NULL; + _cleanup_free_ char *target = NULL; + const char *verb = argv[0]; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + UnitDependency dep; + int r = 0; + + if (!argv[1]) + return 0; + + r = unit_name_mangle_with_suffix(argv[1], UNIT_NAME_NOGLOB, ".target", &target); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + + r = mangle_names(strv_skip(argv, 2), &names); + if (r < 0) + return r; + + if (streq(verb, "add-wants")) + dep = UNIT_WANTS; + else if (streq(verb, "add-requires")) + dep = UNIT_REQUIRES; + else + assert_not_reached("Unknown verb"); + + if (install_client_side()) { + r = unit_file_add_dependency(arg_scope, arg_runtime, arg_root, names, target, dep, arg_force, &changes, &n_changes); + unit_file_dump_changes(r, "add dependency on", changes, n_changes, arg_quiet); + + if (r > 0) + r = 0; + } else { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "AddDependencyUnitFiles"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, names); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "ssbb", target, unit_dependency_to_string(dep), arg_runtime, arg_force); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to add dependency: %s", bus_error_message(&error, r)); + + r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes); + if (r < 0) + goto finish; + + if (arg_no_reload) { + r = 0; + goto finish; + } + + r = daemon_reload(argc, argv, userdata); + } + +finish: + unit_file_changes_free(changes, n_changes); + + return r; +} + +static int preset_all(int argc, char *argv[], void *userdata) { + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + int r; + + if (install_client_side()) { + r = unit_file_preset_all(arg_scope, arg_runtime, arg_root, arg_preset_mode, arg_force, &changes, &n_changes); + unit_file_dump_changes(r, "preset", changes, n_changes, arg_quiet); + + if (r > 0) + r = 0; + } else { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + sd_bus *bus; + + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "PresetAllUnitFiles", + &error, + &reply, + "sbb", + unit_file_preset_mode_to_string(arg_preset_mode), + arg_runtime, + arg_force); + if (r < 0) + return log_error_errno(r, "Failed to preset all units: %s", bus_error_message(&error, r)); + + r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes); + if (r < 0) + goto finish; + + if (arg_no_reload) { + r = 0; + goto finish; + } + + r = daemon_reload(argc, argv, userdata); + } + +finish: + unit_file_changes_free(changes, n_changes); + + return r; +} + +static int unit_is_enabled(int argc, char *argv[], void *userdata) { + + _cleanup_strv_free_ char **names = NULL; + bool enabled; + char **name; + int r; + + r = mangle_names(strv_skip(argv, 1), &names); + if (r < 0) + return r; + + r = enable_sysv_units(argv[0], names); + if (r < 0) + return r; + + enabled = r > 0; + + if (install_client_side()) { + + STRV_FOREACH(name, names) { + UnitFileState state; + + r = unit_file_get_state(arg_scope, arg_root, *name, &state); + if (r < 0) + return log_error_errno(state, "Failed to get unit file state for %s: %m", *name); + + if (IN_SET(state, + UNIT_FILE_ENABLED, + UNIT_FILE_ENABLED_RUNTIME, + UNIT_FILE_STATIC, + UNIT_FILE_INDIRECT, + UNIT_FILE_GENERATED)) + enabled = true; + + if (!arg_quiet) + puts(unit_file_state_to_string(state)); + } + + r = 0; + } else { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + STRV_FOREACH(name, names) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *s; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnitFileState", + &error, + &reply, + "s", *name); + if (r < 0) + return log_error_errno(r, "Failed to get unit file state for %s: %s", *name, bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "s", &s); + if (r < 0) + return bus_log_parse_error(r); + + if (STR_IN_SET(s, "enabled", "enabled-runtime", "static", "indirect", "generated")) + enabled = true; + + if (!arg_quiet) + puts(s); + } + } + + return enabled ? EXIT_SUCCESS : EXIT_FAILURE; +} + +static int is_system_running(int argc, char *argv[], void *userdata) { + _cleanup_free_ char *state = NULL; + sd_bus *bus; + int r; + + if (running_in_chroot() > 0 || (arg_transport == BUS_TRANSPORT_LOCAL && !sd_booted())) { + if (!arg_quiet) + puts("offline"); + return EXIT_FAILURE; + } + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SystemState", + NULL, + &state); + if (r < 0) { + if (!arg_quiet) + puts("unknown"); + return 0; + } + + if (!arg_quiet) + puts(state); + + return streq(state, "running") ? EXIT_SUCCESS : EXIT_FAILURE; +} + +static int create_edit_temp_file(const char *new_path, const char *original_path, char **ret_tmp_fn) { + _cleanup_free_ char *t = NULL; + int r; + + assert(new_path); + assert(original_path); + assert(ret_tmp_fn); + + r = tempfn_random(new_path, NULL, &t); + if (r < 0) + return log_error_errno(r, "Failed to determine temporary filename for \"%s\": %m", new_path); + + r = mkdir_parents(new_path, 0755); + if (r < 0) + return log_error_errno(r, "Failed to create directories for \"%s\": %m", new_path); + + r = copy_file(original_path, t, 0, 0644, 0); + if (r == -ENOENT) { + + r = touch(t); + if (r < 0) + return log_error_errno(r, "Failed to create temporary file \"%s\": %m", t); + + } else if (r < 0) + return log_error_errno(r, "Failed to copy \"%s\" to \"%s\": %m", original_path, t); + + *ret_tmp_fn = t; + t = NULL; + + return 0; +} + +static int get_file_to_edit( + const LookupPaths *paths, + const char *name, + char **ret_path) { + + _cleanup_free_ char *path = NULL, *run = NULL; + + assert(name); + assert(ret_path); + + path = strjoin(paths->persistent_config, "/", name, NULL); + if (!path) + return log_oom(); + + if (arg_runtime) { + run = strjoin(paths->runtime_config, name, NULL); + if (!run) + return log_oom(); + } + + if (arg_runtime) { + if (access(path, F_OK) >= 0) { + log_error("Refusing to create \"%s\" because it would be overridden by \"%s\" anyway.", run, path); + return -EEXIST; + } + + *ret_path = run; + run = NULL; + } else { + *ret_path = path; + path = NULL; + } + + return 0; +} + +static int unit_file_create_dropin( + const LookupPaths *paths, + const char *unit_name, + char **ret_new_path, + char **ret_tmp_path) { + + char *tmp_new_path, *tmp_tmp_path, *ending; + int r; + + assert(unit_name); + assert(ret_new_path); + assert(ret_tmp_path); + + ending = strjoina(unit_name, ".d/override.conf"); + r = get_file_to_edit(paths, ending, &tmp_new_path); + if (r < 0) + return r; + + r = create_edit_temp_file(tmp_new_path, tmp_new_path, &tmp_tmp_path); + if (r < 0) { + free(tmp_new_path); + return r; + } + + *ret_new_path = tmp_new_path; + *ret_tmp_path = tmp_tmp_path; + + return 0; +} + +static int unit_file_create_copy( + const LookupPaths *paths, + const char *unit_name, + const char *fragment_path, + char **ret_new_path, + char **ret_tmp_path) { + + char *tmp_new_path, *tmp_tmp_path; + int r; + + assert(fragment_path); + assert(unit_name); + assert(ret_new_path); + assert(ret_tmp_path); + + r = get_file_to_edit(paths, unit_name, &tmp_new_path); + if (r < 0) + return r; + + if (!path_equal(fragment_path, tmp_new_path) && access(tmp_new_path, F_OK) == 0) { + char response; + + r = ask_char(&response, "yn", "\"%s\" already exists. Overwrite with \"%s\"? [(y)es, (n)o] ", tmp_new_path, fragment_path); + if (r < 0) { + free(tmp_new_path); + return r; + } + if (response != 'y') { + log_warning("%s ignored", unit_name); + free(tmp_new_path); + return -1; + } + } + + r = create_edit_temp_file(tmp_new_path, fragment_path, &tmp_tmp_path); + if (r < 0) { + log_error_errno(r, "Failed to create temporary file for \"%s\": %m", tmp_new_path); + free(tmp_new_path); + return r; + } + + *ret_new_path = tmp_new_path; + *ret_tmp_path = tmp_tmp_path; + + return 0; +} + +static int run_editor(char **paths) { + pid_t pid; + int r; + + assert(paths); + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + + if (pid == 0) { + const char **args; + char *editor, **editor_args = NULL; + char **tmp_path, **original_path, *p; + unsigned n_editor_args = 0, i = 1; + size_t argc; + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + argc = strv_length(paths)/2 + 1; + + /* SYSTEMD_EDITOR takes precedence over EDITOR which takes precedence over VISUAL + * If neither SYSTEMD_EDITOR nor EDITOR nor VISUAL are present, + * we try to execute well known editors + */ + editor = getenv("SYSTEMD_EDITOR"); + if (!editor) + editor = getenv("EDITOR"); + if (!editor) + editor = getenv("VISUAL"); + + if (!isempty(editor)) { + editor_args = strv_split(editor, WHITESPACE); + if (!editor_args) { + (void) log_oom(); + _exit(EXIT_FAILURE); + } + n_editor_args = strv_length(editor_args); + argc += n_editor_args - 1; + } + args = newa(const char*, argc + 1); + + if (n_editor_args > 0) { + args[0] = editor_args[0]; + for (; i < n_editor_args; i++) + args[i] = editor_args[i]; + } + + STRV_FOREACH_PAIR(original_path, tmp_path, paths) { + args[i] = *tmp_path; + i++; + } + args[i] = NULL; + + if (n_editor_args > 0) + execvp(args[0], (char* const*) args); + + FOREACH_STRING(p, "editor", "nano", "vim", "vi") { + args[0] = p; + execvp(p, (char* const*) args); + /* We do not fail if the editor doesn't exist + * because we want to try each one of them before + * failing. + */ + if (errno != ENOENT) { + log_error_errno(errno, "Failed to execute %s: %m", editor); + _exit(EXIT_FAILURE); + } + } + + log_error("Cannot edit unit(s), no editor available. Please set either $SYSTEMD_EDITOR, $EDITOR or $VISUAL."); + _exit(EXIT_FAILURE); + } + + r = wait_for_terminate_and_warn("editor", pid, true); + if (r < 0) + return log_error_errno(r, "Failed to wait for child: %m"); + + return 0; +} + +static int find_paths_to_edit(sd_bus *bus, char **names, char ***paths) { + _cleanup_lookup_paths_free_ LookupPaths lp = {}; + char **name; + int r; + + assert(names); + assert(paths); + + r = lookup_paths_init(&lp, arg_scope, 0, arg_root); + if (r < 0) + return r; + + STRV_FOREACH(name, names) { + _cleanup_free_ char *path = NULL, *new_path = NULL, *tmp_path = NULL; + + r = unit_find_paths(bus, *name, &lp, &path, NULL); + if (r < 0) + return r; + else if (r == 0) + return -ENOENT; + else if (!path) { + // FIXME: support units with path==NULL (no FragmentPath) + log_error("No fragment exists for %s.", *name); + return -ENOENT; + } + + if (arg_full) + r = unit_file_create_copy(&lp, *name, path, &new_path, &tmp_path); + else + r = unit_file_create_dropin(&lp, *name, &new_path, &tmp_path); + if (r < 0) + return r; + + r = strv_push_pair(paths, new_path, tmp_path); + if (r < 0) + return log_oom(); + new_path = tmp_path = NULL; + } + + return 0; +} + +static int edit(int argc, char *argv[], void *userdata) { + _cleanup_strv_free_ char **names = NULL; + _cleanup_strv_free_ char **paths = NULL; + char **original, **tmp; + sd_bus *bus; + int r; + + if (!on_tty()) { + log_error("Cannot edit units if not on a tty."); + return -EINVAL; + } + + if (arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Cannot edit units remotely."); + return -EINVAL; + } + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = expand_names(bus, strv_skip(argv, 1), NULL, &names); + if (r < 0) + return log_error_errno(r, "Failed to expand names: %m"); + + r = find_paths_to_edit(bus, names, &paths); + if (r < 0) + return r; + + if (strv_isempty(paths)) + return -ENOENT; + + r = run_editor(paths); + if (r < 0) + goto end; + + STRV_FOREACH_PAIR(original, tmp, paths) { + /* If the temporary file is empty we ignore it. It's + * useful if the user wants to cancel its modification + */ + if (null_or_empty_path(*tmp)) { + log_warning("Editing \"%s\" canceled: temporary file is empty.", *original); + continue; + } + + r = rename(*tmp, *original); + if (r < 0) { + r = log_error_errno(errno, "Failed to rename \"%s\" to \"%s\": %m", *tmp, *original); + goto end; + } + } + + r = 0; + + if (!arg_no_reload && !install_client_side()) + r = daemon_reload(argc, argv, userdata); + +end: + STRV_FOREACH_PAIR(original, tmp, paths) { + (void) unlink(*tmp); + + /* Removing empty dropin dirs */ + if (!arg_full) { + _cleanup_free_ char *dir; + + dir = dirname_malloc(*original); + if (!dir) + return log_oom(); + + /* no need to check if the dir is empty, rmdir + * does nothing if it is not the case. + */ + (void) rmdir(dir); + } + } + + return r; +} + +static void systemctl_help(void) { + + pager_open(arg_no_pager, false); + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Query or send control commands to the systemd manager.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --system Connect to system manager\n" + " --user Connect to user service manager\n" + " -H --host=[USER@]HOST\n" + " Operate on remote host\n" + " -M --machine=CONTAINER\n" + " Operate on local container\n" + " -t --type=TYPE List units of a particular type\n" + " --state=STATE List units with particular LOAD or SUB or ACTIVE state\n" + " -p --property=NAME Show only properties by this name\n" + " -a --all Show all loaded units/properties, including dead/empty\n" + " ones. To list all units installed on the system, use\n" + " the 'list-unit-files' command instead.\n" + " -l --full Don't ellipsize unit names on output\n" + " -r --recursive Show unit list of host and local containers\n" + " --reverse Show reverse dependencies with 'list-dependencies'\n" + " --job-mode=MODE Specify how to deal with already queued jobs, when\n" + " queueing a new job\n" + " --show-types When showing sockets, explicitly show their type\n" + " --value When showing properties, only print the value\n" + " -i --ignore-inhibitors\n" + " When shutting down or sleeping, ignore inhibitors\n" + " --kill-who=WHO Who to send signal to\n" + " -s --signal=SIGNAL Which signal to send\n" + " --now Start or stop unit in addition to enabling or disabling it\n" + " -q --quiet Suppress output\n" + " --no-block Do not wait until operation finished\n" + " --no-wall Don't send wall message before halt/power-off/reboot\n" + " --no-reload Don't reload daemon after en-/dis-abling unit files\n" + " --no-legend Do not print a legend (column headers and hints)\n" + " --no-pager Do not pipe output into a pager\n" + " --no-ask-password\n" + " Do not ask for system passwords\n" + " --global Enable/disable unit files globally\n" + " --runtime Enable unit files only temporarily until next reboot\n" + " -f --force When enabling unit files, override existing symlinks\n" + " When shutting down, execute action immediately\n" + " --preset-mode= Apply only enable, only disable, or all presets\n" + " --root=PATH Enable unit files in the specified root directory\n" + " -n --lines=INTEGER Number of journal entries to show\n" + " -o --output=STRING Change journal output mode (short, short-iso,\n" + " short-precise, short-monotonic, verbose,\n" + " export, json, json-pretty, json-sse, cat)\n" + " --firmware-setup Tell the firmware to show the setup menu on next boot\n" + " --plain Print unit dependencies as a list instead of a tree\n\n" + "Unit Commands:\n" + " list-units [PATTERN...] List loaded units\n" + " list-sockets [PATTERN...] List loaded sockets ordered by address\n" + " list-timers [PATTERN...] List loaded timers ordered by next elapse\n" + " start NAME... Start (activate) one or more units\n" + " stop NAME... Stop (deactivate) one or more units\n" + " reload NAME... Reload one or more units\n" + " restart NAME... Start or restart one or more units\n" + " try-restart NAME... Restart one or more units if active\n" + " reload-or-restart NAME... Reload one or more units if possible,\n" + " otherwise start or restart\n" + " try-reload-or-restart NAME... If active, reload one or more units,\n" + " if supported, otherwise restart\n" + " isolate NAME Start one unit and stop all others\n" + " kill NAME... Send signal to processes of a unit\n" + " is-active PATTERN... Check whether units are active\n" + " is-failed PATTERN... Check whether units are failed\n" + " status [PATTERN...|PID...] Show runtime status of one or more units\n" + " show [PATTERN...|JOB...] Show properties of one or more\n" + " units/jobs or the manager\n" + " cat PATTERN... Show files and drop-ins of one or more units\n" + " set-property NAME ASSIGNMENT... Sets one or more properties of a unit\n" + " help PATTERN...|PID... Show manual for one or more units\n" + " reset-failed [PATTERN...] Reset failed state for all, one, or more\n" + " units\n" + " list-dependencies [NAME] Recursively show units which are required\n" + " or wanted by this unit or by which this\n" + " unit is required or wanted\n\n" + "Unit File Commands:\n" + " list-unit-files [PATTERN...] List installed unit files\n" + " enable NAME... Enable one or more unit files\n" + " disable NAME... Disable one or more unit files\n" + " reenable NAME... Reenable one or more unit files\n" + " preset NAME... Enable/disable one or more unit files\n" + " based on preset configuration\n" + " preset-all Enable/disable all unit files based on\n" + " preset configuration\n" + " is-enabled NAME... Check whether unit files are enabled\n" + " mask NAME... Mask one or more units\n" + " unmask NAME... Unmask one or more units\n" + " link PATH... Link one or more units files into\n" + " the search path\n" + " revert NAME... Revert one or more unit files to vendor\n" + " version\n" + " add-wants TARGET NAME... Add 'Wants' dependency for the target\n" + " on specified one or more units\n" + " add-requires TARGET NAME... Add 'Requires' dependency for the target\n" + " on specified one or more units\n" + " edit NAME... Edit one or more unit files\n" + " get-default Get the name of the default target\n" + " set-default NAME Set the default target\n\n" + "Machine Commands:\n" + " list-machines [PATTERN...] List local containers and host\n\n" + "Job Commands:\n" + " list-jobs [PATTERN...] List jobs\n" + " cancel [JOB...] Cancel all, one, or more jobs\n\n" + "Environment Commands:\n" + " show-environment Dump environment\n" + " set-environment NAME=VALUE... Set one or more environment variables\n" + " unset-environment NAME... Unset one or more environment variables\n" + " import-environment [NAME...] Import all or some environment variables\n\n" + "Manager Lifecycle Commands:\n" + " daemon-reload Reload systemd manager configuration\n" + " daemon-reexec Reexecute systemd manager\n\n" + "System Commands:\n" + " is-system-running Check whether system is fully running\n" + " default Enter system default mode\n" + " rescue Enter system rescue mode\n" + " emergency Enter system emergency mode\n" + " halt Shut down and halt the system\n" + " poweroff Shut down and power-off the system\n" + " reboot [ARG] Shut down and reboot the system\n" + " kexec Shut down and reboot the system with kexec\n" + " exit [EXIT_CODE] Request user instance or container exit\n" + " switch-root ROOT [INIT] Change to a different root file system\n" + " suspend Suspend the system\n" + " hibernate Hibernate the system\n" + " hybrid-sleep Hibernate and suspend the system\n", + program_invocation_short_name); +} + +static void halt_help(void) { + printf("%s [OPTIONS...]%s\n\n" + "%s the system.\n\n" + " --help Show this help\n" + " --halt Halt the machine\n" + " -p --poweroff Switch off the machine\n" + " --reboot Reboot the machine\n" + " -f --force Force immediate halt/power-off/reboot\n" + " -w --wtmp-only Don't halt/power-off/reboot, just write wtmp record\n" + " -d --no-wtmp Don't write wtmp record\n" + " --no-wall Don't send wall message before halt/power-off/reboot\n", + program_invocation_short_name, + arg_action == ACTION_REBOOT ? " [ARG]" : "", + arg_action == ACTION_REBOOT ? "Reboot" : + arg_action == ACTION_POWEROFF ? "Power off" : + "Halt"); +} + +static void shutdown_help(void) { + printf("%s [OPTIONS...] [TIME] [WALL...]\n\n" + "Shut down the system.\n\n" + " --help Show this help\n" + " -H --halt Halt the machine\n" + " -P --poweroff Power-off the machine\n" + " -r --reboot Reboot the machine\n" + " -h Equivalent to --poweroff, overridden by --halt\n" + " -k Don't halt/power-off/reboot, just send warnings\n" + " --no-wall Don't send wall message before halt/power-off/reboot\n" + " -c Cancel a pending shutdown\n", + program_invocation_short_name); +} + +static void telinit_help(void) { + printf("%s [OPTIONS...] {COMMAND}\n\n" + "Send control commands to the init daemon.\n\n" + " --help Show this help\n" + " --no-wall Don't send wall message before halt/power-off/reboot\n\n" + "Commands:\n" + " 0 Power-off the machine\n" + " 6 Reboot the machine\n" + " 2, 3, 4, 5 Start runlevelX.target unit\n" + " 1, s, S Enter rescue mode\n" + " q, Q Reload init daemon configuration\n" + " u, U Reexecute init daemon\n", + program_invocation_short_name); +} + +static void runlevel_help(void) { + printf("%s [OPTIONS...]\n\n" + "Prints the previous and current runlevel of the init system.\n\n" + " --help Show this help\n", + program_invocation_short_name); +} + +static void help_types(void) { + int i; + + if (!arg_no_legend) + puts("Available unit types:"); + for (i = 0; i < _UNIT_TYPE_MAX; i++) + puts(unit_type_to_string(i)); +} + +static void help_states(void) { + int i; + + if (!arg_no_legend) + puts("Available unit load states:"); + for (i = 0; i < _UNIT_LOAD_STATE_MAX; i++) + puts(unit_load_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable unit active states:"); + for (i = 0; i < _UNIT_ACTIVE_STATE_MAX; i++) + puts(unit_active_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable automount unit substates:"); + for (i = 0; i < _AUTOMOUNT_STATE_MAX; i++) + puts(automount_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable busname unit substates:"); + for (i = 0; i < _BUSNAME_STATE_MAX; i++) + puts(busname_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable device unit substates:"); + for (i = 0; i < _DEVICE_STATE_MAX; i++) + puts(device_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable mount unit substates:"); + for (i = 0; i < _MOUNT_STATE_MAX; i++) + puts(mount_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable path unit substates:"); + for (i = 0; i < _PATH_STATE_MAX; i++) + puts(path_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable scope unit substates:"); + for (i = 0; i < _SCOPE_STATE_MAX; i++) + puts(scope_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable service unit substates:"); + for (i = 0; i < _SERVICE_STATE_MAX; i++) + puts(service_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable slice unit substates:"); + for (i = 0; i < _SLICE_STATE_MAX; i++) + puts(slice_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable socket unit substates:"); + for (i = 0; i < _SOCKET_STATE_MAX; i++) + puts(socket_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable swap unit substates:"); + for (i = 0; i < _SWAP_STATE_MAX; i++) + puts(swap_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable target unit substates:"); + for (i = 0; i < _TARGET_STATE_MAX; i++) + puts(target_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable timer unit substates:"); + for (i = 0; i < _TIMER_STATE_MAX; i++) + puts(timer_state_to_string(i)); +} + +static int systemctl_parse_argv(int argc, char *argv[]) { + + enum { + ARG_FAIL = 0x100, + ARG_REVERSE, + ARG_AFTER, + ARG_BEFORE, + ARG_SHOW_TYPES, + ARG_IRREVERSIBLE, + ARG_IGNORE_DEPENDENCIES, + ARG_VALUE, + ARG_VERSION, + ARG_USER, + ARG_SYSTEM, + ARG_GLOBAL, + ARG_NO_BLOCK, + ARG_NO_LEGEND, + ARG_NO_PAGER, + ARG_NO_WALL, + ARG_ROOT, + ARG_NO_RELOAD, + ARG_KILL_WHO, + ARG_NO_ASK_PASSWORD, + ARG_FAILED, + ARG_RUNTIME, + ARG_FORCE, + ARG_PLAIN, + ARG_STATE, + ARG_JOB_MODE, + ARG_PRESET_MODE, + ARG_FIRMWARE_SETUP, + ARG_NOW, + ARG_MESSAGE, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "type", required_argument, NULL, 't' }, + { "property", required_argument, NULL, 'p' }, + { "all", no_argument, NULL, 'a' }, + { "reverse", no_argument, NULL, ARG_REVERSE }, + { "after", no_argument, NULL, ARG_AFTER }, + { "before", no_argument, NULL, ARG_BEFORE }, + { "show-types", no_argument, NULL, ARG_SHOW_TYPES }, + { "failed", no_argument, NULL, ARG_FAILED }, /* compatibility only */ + { "full", no_argument, NULL, 'l' }, + { "job-mode", required_argument, NULL, ARG_JOB_MODE }, + { "fail", no_argument, NULL, ARG_FAIL }, /* compatibility only */ + { "irreversible", no_argument, NULL, ARG_IRREVERSIBLE }, /* compatibility only */ + { "ignore-dependencies", no_argument, NULL, ARG_IGNORE_DEPENDENCIES }, /* compatibility only */ + { "ignore-inhibitors", no_argument, NULL, 'i' }, + { "value", no_argument, NULL, ARG_VALUE }, + { "user", no_argument, NULL, ARG_USER }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "global", no_argument, NULL, ARG_GLOBAL }, + { "no-block", no_argument, NULL, ARG_NO_BLOCK }, + { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-wall", no_argument, NULL, ARG_NO_WALL }, + { "quiet", no_argument, NULL, 'q' }, + { "root", required_argument, NULL, ARG_ROOT }, + { "force", no_argument, NULL, ARG_FORCE }, + { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, + { "kill-who", required_argument, NULL, ARG_KILL_WHO }, + { "signal", required_argument, NULL, 's' }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "runtime", no_argument, NULL, ARG_RUNTIME }, + { "lines", required_argument, NULL, 'n' }, + { "output", required_argument, NULL, 'o' }, + { "plain", no_argument, NULL, ARG_PLAIN }, + { "state", required_argument, NULL, ARG_STATE }, + { "recursive", no_argument, NULL, 'r' }, + { "preset-mode", required_argument, NULL, ARG_PRESET_MODE }, + { "firmware-setup", no_argument, NULL, ARG_FIRMWARE_SETUP }, + { "now", no_argument, NULL, ARG_NOW }, + { "message", required_argument, NULL, ARG_MESSAGE }, + {} + }; + + const char *p; + int c, r; + + assert(argc >= 0); + assert(argv); + + /* we default to allowing interactive authorization only in systemctl (not in the legacy commands) */ + arg_ask_password = true; + + while ((c = getopt_long(argc, argv, "ht:p:alqfs:H:M:n:o:ir", options, NULL)) >= 0) + + switch (c) { + + case 'h': + systemctl_help(); + return 0; + + case ARG_VERSION: + return version(); + + case 't': { + if (isempty(optarg)) { + log_error("--type requires arguments."); + return -EINVAL; + } + + p = optarg; + for (;;) { + _cleanup_free_ char *type = NULL; + + r = extract_first_word(&p, &type, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to parse type: %s", optarg); + + if (r == 0) + break; + + if (streq(type, "help")) { + help_types(); + return 0; + } + + if (unit_type_from_string(type) >= 0) { + if (strv_push(&arg_types, type) < 0) + return log_oom(); + type = NULL; + continue; + } + + /* It's much nicer to use --state= for + * load states, but let's support this + * in --types= too for compatibility + * with old versions */ + if (unit_load_state_from_string(type) >= 0) { + if (strv_push(&arg_states, type) < 0) + return log_oom(); + type = NULL; + continue; + } + + log_error("Unknown unit type or load state '%s'.", type); + log_info("Use -t help to see a list of allowed values."); + return -EINVAL; + } + + break; + } + + case 'p': { + /* Make sure that if the empty property list + was specified, we won't show any properties. */ + if (isempty(optarg) && !arg_properties) { + arg_properties = new0(char*, 1); + if (!arg_properties) + return log_oom(); + } else { + p = optarg; + for (;;) { + _cleanup_free_ char *prop = NULL; + + r = extract_first_word(&p, &prop, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to parse property: %s", optarg); + + if (r == 0) + break; + + if (strv_push(&arg_properties, prop) < 0) + return log_oom(); + + prop = NULL; + } + } + + /* If the user asked for a particular + * property, show it to him, even if it is + * empty. */ + arg_all = true; + + break; + } + + case 'a': + arg_all = true; + break; + + case ARG_REVERSE: + arg_dependency = DEPENDENCY_REVERSE; + break; + + case ARG_AFTER: + arg_dependency = DEPENDENCY_AFTER; + break; + + case ARG_BEFORE: + arg_dependency = DEPENDENCY_BEFORE; + break; + + case ARG_SHOW_TYPES: + arg_show_types = true; + break; + + case ARG_VALUE: + arg_value = true; + break; + + case ARG_JOB_MODE: + arg_job_mode = optarg; + break; + + case ARG_FAIL: + arg_job_mode = "fail"; + break; + + case ARG_IRREVERSIBLE: + arg_job_mode = "replace-irreversibly"; + break; + + case ARG_IGNORE_DEPENDENCIES: + arg_job_mode = "ignore-dependencies"; + break; + + case ARG_USER: + arg_scope = UNIT_FILE_USER; + break; + + case ARG_SYSTEM: + arg_scope = UNIT_FILE_SYSTEM; + break; + + case ARG_GLOBAL: + arg_scope = UNIT_FILE_GLOBAL; + break; + + case ARG_NO_BLOCK: + arg_no_block = true; + break; + + case ARG_NO_LEGEND: + arg_no_legend = true; + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case ARG_NO_WALL: + arg_no_wall = true; + break; + + case ARG_ROOT: + r = parse_path_argument_and_warn(optarg, false, &arg_root); + if (r < 0) + return r; + break; + + case 'l': + arg_full = true; + break; + + case ARG_FAILED: + if (strv_extend(&arg_states, "failed") < 0) + return log_oom(); + + break; + + case 'q': + arg_quiet = true; + break; + + case ARG_FORCE: + arg_force++; + break; + + case 'f': + arg_force++; + break; + + case ARG_NO_RELOAD: + arg_no_reload = true; + break; + + case ARG_KILL_WHO: + arg_kill_who = optarg; + break; + + case 's': + arg_signal = signal_from_string_try_harder(optarg); + if (arg_signal < 0) { + log_error("Failed to parse signal string %s.", optarg); + return -EINVAL; + } + break; + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_RUNTIME: + arg_runtime = true; + break; + + case 'n': + if (safe_atou(optarg, &arg_lines) < 0) { + log_error("Failed to parse lines '%s'", optarg); + return -EINVAL; + } + break; + + case 'o': + arg_output = output_mode_from_string(optarg); + if (arg_output < 0) { + log_error("Unknown output '%s'.", optarg); + return -EINVAL; + } + break; + + case 'i': + arg_ignore_inhibitors = true; + break; + + case ARG_PLAIN: + arg_plain = true; + break; + + case ARG_FIRMWARE_SETUP: + arg_firmware_setup = true; + break; + + case ARG_STATE: { + if (isempty(optarg)) { + log_error("--signal requires arguments."); + return -EINVAL; + } + + p = optarg; + for (;;) { + _cleanup_free_ char *s = NULL; + + r = extract_first_word(&p, &s, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to parse signal: %s", optarg); + + if (r == 0) + break; + + if (streq(s, "help")) { + help_states(); + return 0; + } + + if (strv_push(&arg_states, s) < 0) + return log_oom(); + + s = NULL; + } + break; + } + + case 'r': + if (geteuid() != 0) { + log_error("--recursive requires root privileges."); + return -EPERM; + } + + arg_recursive = true; + break; + + case ARG_PRESET_MODE: + + arg_preset_mode = unit_file_preset_mode_from_string(optarg); + if (arg_preset_mode < 0) { + log_error("Failed to parse preset mode: %s.", optarg); + return -EINVAL; + } + + break; + + case ARG_NOW: + arg_now = true; + break; + + case ARG_MESSAGE: + if (strv_extend(&arg_wall, optarg) < 0) + return log_oom(); + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (arg_transport != BUS_TRANSPORT_LOCAL && arg_scope != UNIT_FILE_SYSTEM) { + log_error("Cannot access user instance remotely."); + return -EINVAL; + } + + return 1; +} + +static int halt_parse_argv(int argc, char *argv[]) { + + enum { + ARG_HELP = 0x100, + ARG_HALT, + ARG_REBOOT, + ARG_NO_WALL + }; + + static const struct option options[] = { + { "help", no_argument, NULL, ARG_HELP }, + { "halt", no_argument, NULL, ARG_HALT }, + { "poweroff", no_argument, NULL, 'p' }, + { "reboot", no_argument, NULL, ARG_REBOOT }, + { "force", no_argument, NULL, 'f' }, + { "wtmp-only", no_argument, NULL, 'w' }, + { "no-wtmp", no_argument, NULL, 'd' }, + { "no-sync", no_argument, NULL, 'n' }, + { "no-wall", no_argument, NULL, ARG_NO_WALL }, + {} + }; + + int c, r, runlevel; + + assert(argc >= 0); + assert(argv); + + if (utmp_get_runlevel(&runlevel, NULL) >= 0) + if (runlevel == '0' || runlevel == '6') + arg_force = 2; + + while ((c = getopt_long(argc, argv, "pfwdnih", options, NULL)) >= 0) + switch (c) { + + case ARG_HELP: + halt_help(); + return 0; + + case ARG_HALT: + arg_action = ACTION_HALT; + break; + + case 'p': + if (arg_action != ACTION_REBOOT) + arg_action = ACTION_POWEROFF; + break; + + case ARG_REBOOT: + arg_action = ACTION_REBOOT; + break; + + case 'f': + arg_force = 2; + break; + + case 'w': + arg_dry = true; + break; + + case 'd': + arg_no_wtmp = true; + break; + + case 'n': + arg_no_sync = true; + break; + + case ARG_NO_WALL: + arg_no_wall = true; + break; + + case 'i': + case 'h': + /* Compatibility nops */ + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (arg_action == ACTION_REBOOT && (argc == optind || argc == optind + 1)) { + r = update_reboot_parameter_and_warn(argc == optind + 1 ? argv[optind] : NULL); + if (r < 0) + return r; + } else if (optind < argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + return 1; +} + +static int parse_shutdown_time_spec(const char *t, usec_t *_u) { + assert(t); + assert(_u); + + if (streq(t, "now")) + *_u = 0; + else if (!strchr(t, ':')) { + uint64_t u; + + if (safe_atou64(t, &u) < 0) + return -EINVAL; + + *_u = now(CLOCK_REALTIME) + USEC_PER_MINUTE * u; + } else { + char *e = NULL; + long hour, minute; + struct tm tm = {}; + time_t s; + usec_t n; + + errno = 0; + hour = strtol(t, &e, 10); + if (errno > 0 || *e != ':' || hour < 0 || hour > 23) + return -EINVAL; + + minute = strtol(e+1, &e, 10); + if (errno > 0 || *e != 0 || minute < 0 || minute > 59) + return -EINVAL; + + n = now(CLOCK_REALTIME); + s = (time_t) (n / USEC_PER_SEC); + + assert_se(localtime_r(&s, &tm)); + + tm.tm_hour = (int) hour; + tm.tm_min = (int) minute; + tm.tm_sec = 0; + + assert_se(s = mktime(&tm)); + + *_u = (usec_t) s * USEC_PER_SEC; + + while (*_u <= n) + *_u += USEC_PER_DAY; + } + + return 0; +} + +static int shutdown_parse_argv(int argc, char *argv[]) { + + enum { + ARG_HELP = 0x100, + ARG_NO_WALL + }; + + static const struct option options[] = { + { "help", no_argument, NULL, ARG_HELP }, + { "halt", no_argument, NULL, 'H' }, + { "poweroff", no_argument, NULL, 'P' }, + { "reboot", no_argument, NULL, 'r' }, + { "kexec", no_argument, NULL, 'K' }, /* not documented extension */ + { "no-wall", no_argument, NULL, ARG_NO_WALL }, + {} + }; + + char **wall = NULL; + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "HPrhkKtafFc", options, NULL)) >= 0) + switch (c) { + + case ARG_HELP: + shutdown_help(); + return 0; + + case 'H': + arg_action = ACTION_HALT; + break; + + case 'P': + arg_action = ACTION_POWEROFF; + break; + + case 'r': + if (kexec_loaded()) + arg_action = ACTION_KEXEC; + else + arg_action = ACTION_REBOOT; + break; + + case 'K': + arg_action = ACTION_KEXEC; + break; + + case 'h': + if (arg_action != ACTION_HALT) + arg_action = ACTION_POWEROFF; + break; + + case 'k': + arg_dry = true; + break; + + case ARG_NO_WALL: + arg_no_wall = true; + break; + + case 't': + case 'a': + case 'f': + case 'F': + /* Compatibility nops */ + break; + + case 'c': + arg_action = ACTION_CANCEL_SHUTDOWN; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (argc > optind && arg_action != ACTION_CANCEL_SHUTDOWN) { + r = parse_shutdown_time_spec(argv[optind], &arg_when); + if (r < 0) { + log_error("Failed to parse time specification: %s", argv[optind]); + return r; + } + } else + arg_when = now(CLOCK_REALTIME) + USEC_PER_MINUTE; + + if (argc > optind && arg_action == ACTION_CANCEL_SHUTDOWN) + /* No time argument for shutdown cancel */ + wall = argv + optind; + else if (argc > optind + 1) + /* We skip the time argument */ + wall = argv + optind + 1; + + if (wall) { + arg_wall = strv_copy(wall); + if (!arg_wall) + return log_oom(); + } + + optind = argc; + + return 1; +} + +static int telinit_parse_argv(int argc, char *argv[]) { + + enum { + ARG_HELP = 0x100, + ARG_NO_WALL + }; + + static const struct option options[] = { + { "help", no_argument, NULL, ARG_HELP }, + { "no-wall", no_argument, NULL, ARG_NO_WALL }, + {} + }; + + static const struct { + char from; + enum action to; + } table[] = { + { '0', ACTION_POWEROFF }, + { '6', ACTION_REBOOT }, + { '1', ACTION_RESCUE }, + { '2', ACTION_RUNLEVEL2 }, + { '3', ACTION_RUNLEVEL3 }, + { '4', ACTION_RUNLEVEL4 }, + { '5', ACTION_RUNLEVEL5 }, + { 's', ACTION_RESCUE }, + { 'S', ACTION_RESCUE }, + { 'q', ACTION_RELOAD }, + { 'Q', ACTION_RELOAD }, + { 'u', ACTION_REEXEC }, + { 'U', ACTION_REEXEC } + }; + + unsigned i; + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0) + switch (c) { + + case ARG_HELP: + telinit_help(); + return 0; + + case ARG_NO_WALL: + arg_no_wall = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (optind >= argc) { + log_error("%s: required argument missing.", program_invocation_short_name); + return -EINVAL; + } + + if (optind + 1 < argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + if (strlen(argv[optind]) != 1) { + log_error("Expected single character argument."); + return -EINVAL; + } + + for (i = 0; i < ELEMENTSOF(table); i++) + if (table[i].from == argv[optind][0]) + break; + + if (i >= ELEMENTSOF(table)) { + log_error("Unknown command '%s'.", argv[optind]); + return -EINVAL; + } + + arg_action = table[i].to; + + optind++; + + return 1; +} + +static int runlevel_parse_argv(int argc, char *argv[]) { + + enum { + ARG_HELP = 0x100, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, ARG_HELP }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0) + switch (c) { + + case ARG_HELP: + runlevel_help(); + return 0; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (optind < argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + return 1; +} + +static int parse_argv(int argc, char *argv[]) { + assert(argc >= 0); + assert(argv); + + if (program_invocation_short_name) { + + if (strstr(program_invocation_short_name, "halt")) { + arg_action = ACTION_HALT; + return halt_parse_argv(argc, argv); + } else if (strstr(program_invocation_short_name, "poweroff")) { + arg_action = ACTION_POWEROFF; + return halt_parse_argv(argc, argv); + } else if (strstr(program_invocation_short_name, "reboot")) { + if (kexec_loaded()) + arg_action = ACTION_KEXEC; + else + arg_action = ACTION_REBOOT; + return halt_parse_argv(argc, argv); + } else if (strstr(program_invocation_short_name, "shutdown")) { + arg_action = ACTION_POWEROFF; + return shutdown_parse_argv(argc, argv); + } else if (strstr(program_invocation_short_name, "init")) { + + if (sd_booted() > 0) { + arg_action = _ACTION_INVALID; + return telinit_parse_argv(argc, argv); + } else { + /* Hmm, so some other init system is + * running, we need to forward this + * request to it. For now we simply + * guess that it is Upstart. */ + + execv(TELINIT, argv); + + log_error("Couldn't find an alternative telinit implementation to spawn."); + return -EIO; + } + + } else if (strstr(program_invocation_short_name, "runlevel")) { + arg_action = ACTION_RUNLEVEL; + return runlevel_parse_argv(argc, argv); + } + } + + arg_action = ACTION_SYSTEMCTL; + return systemctl_parse_argv(argc, argv); +} + +#ifdef HAVE_SYSV_COMPAT +_pure_ static int action_to_runlevel(void) { + + static const char table[_ACTION_MAX] = { + [ACTION_HALT] = '0', + [ACTION_POWEROFF] = '0', + [ACTION_REBOOT] = '6', + [ACTION_RUNLEVEL2] = '2', + [ACTION_RUNLEVEL3] = '3', + [ACTION_RUNLEVEL4] = '4', + [ACTION_RUNLEVEL5] = '5', + [ACTION_RESCUE] = '1' + }; + + assert(arg_action < _ACTION_MAX); + + return table[arg_action]; +} +#endif + +static int talk_initctl(void) { +#ifdef HAVE_SYSV_COMPAT + struct init_request request = { + .magic = INIT_MAGIC, + .sleeptime = 0, + .cmd = INIT_CMD_RUNLVL + }; + + _cleanup_close_ int fd = -1; + char rl; + int r; + + rl = action_to_runlevel(); + if (!rl) + return 0; + + request.runlevel = rl; + + fd = open(INIT_FIFO, O_WRONLY|O_NDELAY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open "INIT_FIFO": %m"); + } + + r = loop_write(fd, &request, sizeof(request), false); + if (r < 0) + return log_error_errno(r, "Failed to write to "INIT_FIFO": %m"); + + return 1; +#else + return 0; +#endif +} + +static int systemctl_main(int argc, char *argv[]) { + + static const Verb verbs[] = { + { "list-units", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_NOCHROOT, list_units }, + { "list-unit-files", VERB_ANY, VERB_ANY, 0, list_unit_files }, + { "list-sockets", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_sockets }, + { "list-timers", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_timers }, + { "list-jobs", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_jobs }, + { "list-machines", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_machines }, + { "clear-jobs", VERB_ANY, 1, VERB_NOCHROOT, daemon_reload }, + { "cancel", VERB_ANY, VERB_ANY, VERB_NOCHROOT, cancel_job }, + { "start", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "stop", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "condstop", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with ALTLinux */ + { "reload", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "try-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "reload-or-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "reload-or-try-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatbility with old systemctl <= 228 */ + { "try-reload-or-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "force-reload", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with SysV */ + { "condreload", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with ALTLinux */ + { "condrestart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with RH */ + { "isolate", 2, 2, VERB_NOCHROOT, start_unit }, + { "kill", 2, VERB_ANY, VERB_NOCHROOT, kill_unit }, + { "is-active", 2, VERB_ANY, VERB_NOCHROOT, check_unit_active }, + { "check", 2, VERB_ANY, VERB_NOCHROOT, check_unit_active }, + { "is-failed", 2, VERB_ANY, VERB_NOCHROOT, check_unit_failed }, + { "show", VERB_ANY, VERB_ANY, VERB_NOCHROOT, show }, + { "cat", 2, VERB_ANY, VERB_NOCHROOT, cat }, + { "status", VERB_ANY, VERB_ANY, VERB_NOCHROOT, show }, + { "help", VERB_ANY, VERB_ANY, VERB_NOCHROOT, show }, + { "daemon-reload", VERB_ANY, 1, VERB_NOCHROOT, daemon_reload }, + { "daemon-reexec", VERB_ANY, 1, VERB_NOCHROOT, daemon_reload }, + { "show-environment", VERB_ANY, 1, VERB_NOCHROOT, show_environment }, + { "set-environment", 2, VERB_ANY, VERB_NOCHROOT, set_environment }, + { "unset-environment", 2, VERB_ANY, VERB_NOCHROOT, set_environment }, + { "import-environment", VERB_ANY, VERB_ANY, VERB_NOCHROOT, import_environment}, + { "halt", VERB_ANY, 1, VERB_NOCHROOT, start_special }, + { "poweroff", VERB_ANY, 1, VERB_NOCHROOT, start_special }, + { "reboot", VERB_ANY, 2, VERB_NOCHROOT, start_special }, + { "kexec", VERB_ANY, 1, VERB_NOCHROOT, start_special }, + { "suspend", VERB_ANY, 1, VERB_NOCHROOT, start_special }, + { "hibernate", VERB_ANY, 1, VERB_NOCHROOT, start_special }, + { "hybrid-sleep", VERB_ANY, 1, VERB_NOCHROOT, start_special }, + { "default", VERB_ANY, 1, VERB_NOCHROOT, start_special }, + { "rescue", VERB_ANY, 1, VERB_NOCHROOT, start_special }, + { "emergency", VERB_ANY, 1, VERB_NOCHROOT, start_special }, + { "exit", VERB_ANY, 2, VERB_NOCHROOT, start_special }, + { "reset-failed", VERB_ANY, VERB_ANY, VERB_NOCHROOT, reset_failed }, + { "enable", 2, VERB_ANY, 0, enable_unit }, + { "disable", 2, VERB_ANY, 0, enable_unit }, + { "is-enabled", 2, VERB_ANY, 0, unit_is_enabled }, + { "reenable", 2, VERB_ANY, 0, enable_unit }, + { "preset", 2, VERB_ANY, 0, enable_unit }, + { "preset-all", VERB_ANY, 1, 0, preset_all }, + { "mask", 2, VERB_ANY, 0, enable_unit }, + { "unmask", 2, VERB_ANY, 0, enable_unit }, + { "link", 2, VERB_ANY, 0, enable_unit }, + { "revert", 2, VERB_ANY, 0, enable_unit }, + { "switch-root", 2, VERB_ANY, VERB_NOCHROOT, switch_root }, + { "list-dependencies", VERB_ANY, 2, VERB_NOCHROOT, list_dependencies }, + { "set-default", 2, 2, 0, set_default }, + { "get-default", VERB_ANY, 1, 0, get_default, }, + { "set-property", 3, VERB_ANY, VERB_NOCHROOT, set_property }, + { "is-system-running", VERB_ANY, 1, 0, is_system_running }, + { "add-wants", 3, VERB_ANY, 0, add_dependency }, + { "add-requires", 3, VERB_ANY, 0, add_dependency }, + { "edit", 2, VERB_ANY, VERB_NOCHROOT, edit }, + {} + }; + + return dispatch_verb(argc, argv, verbs, NULL); +} + +static int reload_with_fallback(void) { + + /* First, try systemd via D-Bus. */ + if (daemon_reload(0, NULL, NULL) >= 0) + return 0; + + /* Nothing else worked, so let's try signals */ + assert(arg_action == ACTION_RELOAD || arg_action == ACTION_REEXEC); + + if (kill(1, arg_action == ACTION_RELOAD ? SIGHUP : SIGTERM) < 0) + return log_error_errno(errno, "kill() failed: %m"); + + return 0; +} + +static int start_with_fallback(void) { + + /* First, try systemd via D-Bus. */ + if (start_unit(0, NULL, NULL) >= 0) + return 0; + + /* Nothing else worked, so let's try + * /dev/initctl */ + if (talk_initctl() > 0) + return 0; + + log_error("Failed to talk to init daemon."); + return -EIO; +} + +static int halt_now(enum action a) { + int r; + + /* The kernel will automaticall flush ATA disks and suchlike + * on reboot(), but the file systems need to be synce'd + * explicitly in advance. */ + if (!arg_no_sync) + (void) sync(); + + /* Make sure C-A-D is handled by the kernel from this point + * on... */ + (void) reboot(RB_ENABLE_CAD); + + switch (a) { + + case ACTION_HALT: + log_info("Halting."); + (void) reboot(RB_HALT_SYSTEM); + return -errno; + + case ACTION_POWEROFF: + log_info("Powering off."); + (void) reboot(RB_POWER_OFF); + return -errno; + + case ACTION_KEXEC: + case ACTION_REBOOT: { + _cleanup_free_ char *param = NULL; + + r = read_one_line_file("/run/systemd/reboot-param", ¶m); + if (r < 0) + log_warning_errno(r, "Failed to read reboot parameter file: %m"); + + if (!isempty(param)) { + log_info("Rebooting with argument '%s'.", param); + (void) syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, param); + log_warning_errno(errno, "Failed to reboot with parameter, retrying without: %m"); + } + + log_info("Rebooting."); + (void) reboot(RB_AUTOBOOT); + return -errno; + } + + default: + assert_not_reached("Unknown action."); + } +} + +static int logind_schedule_shutdown(void) { + +#ifdef HAVE_LOGIND + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char date[FORMAT_TIMESTAMP_MAX]; + const char *action; + sd_bus *bus; + int r; + + (void) logind_set_wall_message(); + + r = acquire_bus(BUS_FULL, &bus); + if (r < 0) + return r; + + switch (arg_action) { + case ACTION_HALT: + action = "halt"; + break; + case ACTION_POWEROFF: + action = "poweroff"; + break; + case ACTION_KEXEC: + action = "kexec"; + break; + case ACTION_EXIT: + action = "exit"; + break; + case ACTION_REBOOT: + default: + action = "reboot"; + break; + } + + if (arg_dry) + action = strjoina("dry-", action); + + r = sd_bus_call_method( + bus, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "ScheduleShutdown", + &error, + NULL, + "st", + action, + arg_when); + if (r < 0) + return log_warning_errno(r, "Failed to call ScheduleShutdown in logind, proceeding with immediate shutdown: %s", bus_error_message(&error, r)); + + log_info("Shutdown scheduled for %s, use 'shutdown -c' to cancel.", format_timestamp(date, sizeof(date), arg_when)); + return 0; +#else + log_error("Cannot schedule shutdown without logind support, proceeding with immediate shutdown."); + return -ENOSYS; +#endif +} + +static int halt_main(void) { + int r; + + r = logind_check_inhibitors(arg_action); + if (r < 0) + return r; + + if (arg_when > 0) + return logind_schedule_shutdown(); + + if (geteuid() != 0) { + if (arg_dry || arg_force > 0) { + log_error("Must be root."); + return -EPERM; + } + + /* Try logind if we are a normal user and no special + * mode applies. Maybe PolicyKit allows us to shutdown + * the machine. */ + if (IN_SET(arg_action, ACTION_POWEROFF, ACTION_REBOOT)) { + r = logind_reboot(arg_action); + if (r >= 0) + return r; + if (IN_SET(r, -EOPNOTSUPP, -EINPROGRESS)) + /* requested operation is not + * supported on the local system or + * already in progress */ + return r; + /* on all other errors, try low-level operation */ + } + } + + if (!arg_dry && !arg_force) + return start_with_fallback(); + + assert(geteuid() == 0); + + if (!arg_no_wtmp) { + if (sd_booted() > 0) + log_debug("Not writing utmp record, assuming that systemd-update-utmp is used."); + else { + r = utmp_put_shutdown(); + if (r < 0) + log_warning_errno(r, "Failed to write utmp record: %m"); + } + } + + if (arg_dry) + return 0; + + r = halt_now(arg_action); + return log_error_errno(r, "Failed to reboot: %m"); +} + +static int runlevel_main(void) { + int r, runlevel, previous; + + r = utmp_get_runlevel(&runlevel, &previous); + if (r < 0) { + puts("unknown"); + return r; + } + + printf("%c %c\n", + previous <= 0 ? 'N' : previous, + runlevel <= 0 ? 'N' : runlevel); + + return 0; +} + +static int logind_cancel_shutdown(void) { +#ifdef HAVE_LOGIND + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + int r; + + r = acquire_bus(BUS_FULL, &bus); + if (r < 0) + return r; + + (void) logind_set_wall_message(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "CancelScheduledShutdown", + &error, + NULL, NULL); + if (r < 0) + return log_warning_errno(r, "Failed to talk to logind, shutdown hasn't been cancelled: %s", bus_error_message(&error, r)); + + return 0; +#else + log_error("Not compiled with logind support, cannot cancel scheduled shutdowns."); + return -ENOSYS; +#endif +} + +int main(int argc, char*argv[]) { + int r; + + setlocale(LC_ALL, ""); + log_parse_environment(); + log_open(); + + /* Explicitly not on_tty() to avoid setting cached value. + * This becomes relevant for piping output which might be + * ellipsized. */ + original_stdout_is_tty = isatty(STDOUT_FILENO); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (arg_action != ACTION_SYSTEMCTL && running_in_chroot() > 0) { + log_info("Running in chroot, ignoring request."); + r = 0; + goto finish; + } + + /* systemctl_main() will print an error message for the bus + * connection, but only if it needs to */ + + switch (arg_action) { + + case ACTION_SYSTEMCTL: + r = systemctl_main(argc, argv); + break; + + case ACTION_HALT: + case ACTION_POWEROFF: + case ACTION_REBOOT: + case ACTION_KEXEC: + r = halt_main(); + break; + + case ACTION_RUNLEVEL2: + case ACTION_RUNLEVEL3: + case ACTION_RUNLEVEL4: + case ACTION_RUNLEVEL5: + case ACTION_RESCUE: + case ACTION_EMERGENCY: + case ACTION_DEFAULT: + r = start_with_fallback(); + break; + + case ACTION_RELOAD: + case ACTION_REEXEC: + r = reload_with_fallback(); + break; + + case ACTION_CANCEL_SHUTDOWN: + r = logind_cancel_shutdown(); + break; + + case ACTION_RUNLEVEL: + r = runlevel_main(); + break; + + case _ACTION_INVALID: + default: + assert_not_reached("Unknown action"); + } + +finish: + pager_close(); + ask_password_agent_close(); + polkit_agent_close(); + + strv_free(arg_types); + strv_free(arg_states); + strv_free(arg_properties); + + strv_free(arg_wall); + free(arg_root); + + release_busses(); + + /* Note that we return r here, not EXIT_SUCCESS, so that we can implement the LSB-like return codes */ + + return r < 0 ? EXIT_FAILURE : r; +} diff --git a/src/grp-system/systemctl/systemd-sysv-install.SKELETON b/src/grp-system/systemctl/systemd-sysv-install.SKELETON new file mode 100755 index 0000000000..a53a3e6221 --- /dev/null +++ b/src/grp-system/systemctl/systemd-sysv-install.SKELETON @@ -0,0 +1,47 @@ +#!/bin/sh +# This script is called by "systemctl enable/disable" when the given unit is a +# SysV init.d script. It needs to call the distribution's mechanism for +# enabling/disabling those, such as chkconfig, update-rc.d, or similar. This +# can optionally take a --root argument for enabling a SysV init script +# in a chroot or similar. +set -e + +usage() { + echo "Usage: $0 [--root=path] enable|disable|is-enabled " >&2 + exit 1 +} + +# parse options +eval set -- "$(getopt -o r: --long root: -- "$@")" +while true; do + case "$1" in + -r|--root) + ROOT="$2" + shift 2 ;; + --) shift ; break ;; + *) usage ;; + esac +done + +NAME="$2" +[ -n "$NAME" ] || usage + +case "$1" in + enable) + # call the command to enable SysV init script $NAME here + # (consider optional $ROOT) + echo "IMPLEMENT ME: enabling SysV init.d script $NAME" + ;; + disable) + # call the command to disable SysV init script $NAME here + # (consider optional $ROOT) + echo "IMPLEMENT ME: disabling SysV init.d script $NAME" + ;; + is-enabled) + # exit with 0 if $NAME is enabled, non-zero if it is disabled + # (consider optional $ROOT) + echo "IMPLEMENT ME: checking SysV init.d script $NAME" + ;; + *) + usage ;; +esac diff --git a/src/grp-system/systemd/Makefile b/src/grp-system/systemd/Makefile new file mode 100644 index 0000000000..2e6d9bbf7f --- /dev/null +++ b/src/grp-system/systemd/Makefile @@ -0,0 +1,70 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_SOURCES = \ + src/core/main.c + +systemd_CFLAGS = \ + $(AM_CFLAGS) \ + $(SECCOMP_CFLAGS) \ + $(MOUNT_CFLAGS) + +systemd_LDADD = \ + libcore.la + +dist_pkgsysconf_DATA += \ + src/core/system.conf \ + src/core/user.conf + +dist_dbuspolicy_DATA += \ + src/core/org.freedesktop.systemd1.conf + +dist_dbussystemservice_DATA += \ + src/core/org.freedesktop.systemd1.service + +polkitpolicy_in_in_files += \ + src/core/org.freedesktop.systemd1.policy.in.in + +pkgconfigdata_DATA += \ + src/core/systemd.pc + +nodist_rpmmacros_DATA = \ + src/core/macros.systemd + +BUILT_SOURCES += \ + src/core/triggers.systemd + +EXTRA_DIST += \ + src/core/systemd.pc.in \ + src/core/macros.systemd.in \ + src/core/triggers.systemd.in + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.systemd1.busname + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.systemd1.busname + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-system/systemd/macros.systemd.in b/src/grp-system/systemd/macros.systemd.in new file mode 100644 index 0000000000..2cace3d3ba --- /dev/null +++ b/src/grp-system/systemd/macros.systemd.in @@ -0,0 +1,105 @@ +# -*- Mode: rpm-spec; indent-tabs-mode: nil -*- */ +# +# This file is part of systemd. +# +# Copyright 2012 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 . + +# RPM macros for packages installing systemd unit files + +%_unitdir @systemunitdir@ +%_userunitdir @userunitdir@ +%_presetdir @systempresetdir@ +%_udevhwdbdir @udevhwdbdir@ +%_udevrulesdir @udevrulesdir@ +%_journalcatalogdir @catalogdir@ +%_tmpfilesdir @tmpfilesdir@ +%_sysusersdir @sysusersdir@ +%_sysctldir @sysctldir@ +%_binfmtdir @binfmtdir@ + +%systemd_requires \ +Requires(post): systemd \ +Requires(preun): systemd \ +Requires(postun): systemd \ +%{nil} + +%systemd_post() \ +if [ $1 -eq 1 ] ; then \ + # Initial installation \ + systemctl --no-reload preset %{?*} >/dev/null 2>&1 || : \ +fi \ +%{nil} + +%systemd_user_post() %{expand:%systemd_post \\--user \\--global %%{?*}} + +%systemd_preun() \ +if [ $1 -eq 0 ] ; then \ + # Package removal, not upgrade \ + systemctl --no-reload disable --now %{?*} > /dev/null 2>&1 || : \ +fi \ +%{nil} + +%systemd_user_preun() \ +if [ $1 -eq 0 ] ; then \ + # Package removal, not upgrade \ + systemctl --no-reload --user --global disable %{?*} > /dev/null 2>&1 || : \ +fi \ +%{nil} + +%systemd_postun() %{nil} + +%systemd_user_postun() %{nil} + +%systemd_postun_with_restart() \ +if [ $1 -ge 1 ] ; then \ + # Package upgrade, not uninstall \ + systemctl try-restart %{?*} >/dev/null 2>&1 || : \ +fi \ +%{nil} + +%systemd_user_postun_with_restart() %{nil} + +%udev_hwdb_update() \ +udevadm hwdb --update >/dev/null 2>&1 || : \ +%{nil} + +%udev_rules_update() \ +udevadm control --reload >/dev/null 2>&1 || : \ +%{nil} + +%journal_catalog_update() \ +journalctl --update-catalog >/dev/null 2>&1 || : \ +%{nil} + +%tmpfiles_create() \ +systemd-tmpfiles --create %{?*} >/dev/null 2>&1 || : \ +%{nil} + +%sysusers_create() \ +systemd-sysusers %{?*} >/dev/null 2>&1 || : \ +%{nil} + +%sysusers_create_inline() \ +echo %{?*} | systemd-sysusers - >/dev/null 2>&1 || : \ +%{nil} + +%sysctl_apply() \ +@rootlibexecdir@/systemd-sysctl %{?*} >/dev/null 2>&1 || : \ +%{nil} + +%binfmt_apply() \ +@rootlibexecdir@/systemd-binfmt %{?*} >/dev/null 2>&1 || : \ +%{nil} diff --git a/src/grp-system/systemd/main.c b/src/grp-system/systemd/main.c new file mode 100644 index 0000000000..042cce49dd --- /dev/null +++ b/src/grp-system/systemd/main.c @@ -0,0 +1,2149 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SECCOMP +#include +#endif +#ifdef HAVE_VALGRIND_VALGRIND_H +#include +#endif + +#include +#include + +#include "alloc-util.h" +#include "architecture.h" +#include "build.h" +#include "bus-error.h" +#include "bus-util.h" +#include "capability-util.h" +#include "clock-util.h" +#include "conf-parser.h" +#include "cpu-set-util.h" +#include "dbus-manager.h" +#include "def.h" +#include "env-util.h" +#include "fd-util.h" +#include "fdset.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "hostname-setup.h" +#include "ima-setup.h" +#include "killall.h" +#include "kmod-setup.h" +#include "load-fragment.h" +#include "log.h" +#include "loopback-setup.h" +#include "machine-id-setup.h" +#include "manager.h" +#include "missing.h" +#include "mount-setup.h" +#include "pager.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "process-util.h" +#include "rlimit-util.h" +#include "selinux-setup.h" +#include "selinux-util.h" +#include "signal-util.h" +#include "smack-setup.h" +#include "special.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "strv.h" +#include "switch-root.h" +#include "terminal-util.h" +#include "umask-util.h" +#include "user-util.h" +#include "virt.h" +#include "watchdog.h" + +static enum { + ACTION_RUN, + ACTION_HELP, + ACTION_VERSION, + ACTION_TEST, + ACTION_DUMP_CONFIGURATION_ITEMS, + ACTION_DONE +} arg_action = ACTION_RUN; +static char *arg_default_unit = NULL; +static bool arg_system = false; +static bool arg_dump_core = true; +static int arg_crash_chvt = -1; +static bool arg_crash_shell = false; +static bool arg_crash_reboot = false; +static bool arg_confirm_spawn = false; +static ShowStatus arg_show_status = _SHOW_STATUS_UNSET; +static bool arg_switched_root = false; +static bool arg_no_pager = false; +static char ***arg_join_controllers = NULL; +static ExecOutput arg_default_std_output = EXEC_OUTPUT_JOURNAL; +static ExecOutput arg_default_std_error = EXEC_OUTPUT_INHERIT; +static usec_t arg_default_restart_usec = DEFAULT_RESTART_USEC; +static usec_t arg_default_timeout_start_usec = DEFAULT_TIMEOUT_USEC; +static usec_t arg_default_timeout_stop_usec = DEFAULT_TIMEOUT_USEC; +static usec_t arg_default_start_limit_interval = DEFAULT_START_LIMIT_INTERVAL; +static unsigned arg_default_start_limit_burst = DEFAULT_START_LIMIT_BURST; +static usec_t arg_runtime_watchdog = 0; +static usec_t arg_shutdown_watchdog = 10 * USEC_PER_MINUTE; +static char **arg_default_environment = NULL; +static struct rlimit *arg_default_rlimit[_RLIMIT_MAX] = {}; +static uint64_t arg_capability_bounding_set = CAP_ALL; +static nsec_t arg_timer_slack_nsec = NSEC_INFINITY; +static usec_t arg_default_timer_accuracy_usec = 1 * USEC_PER_MINUTE; +static Set* arg_syscall_archs = NULL; +static FILE* arg_serialization = NULL; +static bool arg_default_cpu_accounting = false; +static bool arg_default_io_accounting = false; +static bool arg_default_blockio_accounting = false; +static bool arg_default_memory_accounting = false; +static bool arg_default_tasks_accounting = true; +static uint64_t arg_default_tasks_max = UINT64_C(512); +static sd_id128_t arg_machine_id = {}; + +noreturn static void freeze_or_reboot(void) { + + if (arg_crash_reboot) { + log_notice("Rebooting in 10s..."); + (void) sleep(10); + + log_notice("Rebooting now..."); + (void) reboot(RB_AUTOBOOT); + log_emergency_errno(errno, "Failed to reboot: %m"); + } + + log_emergency("Freezing execution."); + freeze(); +} + +noreturn static void crash(int sig) { + struct sigaction sa; + pid_t pid; + + if (getpid() != 1) + /* Pass this on immediately, if this is not PID 1 */ + (void) raise(sig); + else if (!arg_dump_core) + log_emergency("Caught <%s>, not dumping core.", signal_to_string(sig)); + else { + sa = (struct sigaction) { + .sa_handler = nop_signal_handler, + .sa_flags = SA_NOCLDSTOP|SA_RESTART, + }; + + /* We want to wait for the core process, hence let's enable SIGCHLD */ + (void) sigaction(SIGCHLD, &sa, NULL); + + pid = raw_clone(SIGCHLD, NULL); + if (pid < 0) + log_emergency_errno(errno, "Caught <%s>, cannot fork for core dump: %m", signal_to_string(sig)); + else if (pid == 0) { + /* Enable default signal handler for core dump */ + + sa = (struct sigaction) { + .sa_handler = SIG_DFL, + }; + (void) sigaction(sig, &sa, NULL); + + /* Don't limit the coredump size */ + (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(RLIM_INFINITY)); + + /* Just to be sure... */ + (void) chdir("/"); + + /* Raise the signal again */ + pid = raw_getpid(); + (void) kill(pid, sig); /* raise() would kill the parent */ + + assert_not_reached("We shouldn't be here..."); + _exit(EXIT_FAILURE); + } else { + siginfo_t status; + int r; + + /* Order things nicely. */ + r = wait_for_terminate(pid, &status); + if (r < 0) + log_emergency_errno(r, "Caught <%s>, waitpid() failed: %m", signal_to_string(sig)); + else if (status.si_code != CLD_DUMPED) + log_emergency("Caught <%s>, core dump failed (child "PID_FMT", code=%s, status=%i/%s).", + signal_to_string(sig), + pid, sigchld_code_to_string(status.si_code), + status.si_status, + strna(status.si_code == CLD_EXITED + ? exit_status_to_string(status.si_status, EXIT_STATUS_FULL) + : signal_to_string(status.si_status))); + else + log_emergency("Caught <%s>, dumped core as pid "PID_FMT".", signal_to_string(sig), pid); + } + } + + if (arg_crash_chvt >= 0) + (void) chvt(arg_crash_chvt); + + sa = (struct sigaction) { + .sa_handler = SIG_IGN, + .sa_flags = SA_NOCLDSTOP|SA_NOCLDWAIT|SA_RESTART, + }; + + /* Let the kernel reap children for us */ + (void) sigaction(SIGCHLD, &sa, NULL); + + if (arg_crash_shell) { + log_notice("Executing crash shell in 10s..."); + (void) sleep(10); + + pid = raw_clone(SIGCHLD, NULL); + if (pid < 0) + log_emergency_errno(errno, "Failed to fork off crash shell: %m"); + else if (pid == 0) { + (void) setsid(); + (void) make_console_stdio(); + (void) execle("/bin/sh", "/bin/sh", NULL, environ); + + log_emergency_errno(errno, "execle() failed: %m"); + _exit(EXIT_FAILURE); + } else { + log_info("Spawned crash shell as PID "PID_FMT".", pid); + (void) wait_for_terminate(pid, NULL); + } + } + + freeze_or_reboot(); +} + +static void install_crash_handler(void) { + static const struct sigaction sa = { + .sa_handler = crash, + .sa_flags = SA_NODEFER, /* So that we can raise the signal again from the signal handler */ + }; + int r; + + /* We ignore the return value here, since, we don't mind if we + * cannot set up a crash handler */ + r = sigaction_many(&sa, SIGNALS_CRASH_HANDLER, -1); + if (r < 0) + log_debug_errno(r, "I had trouble setting up the crash handler, ignoring: %m"); +} + +static int console_setup(void) { + _cleanup_close_ int tty_fd = -1; + int r; + + tty_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (tty_fd < 0) + return log_error_errno(tty_fd, "Failed to open /dev/console: %m"); + + /* We don't want to force text mode. plymouth may be showing + * pictures already from initrd. */ + r = reset_terminal_fd(tty_fd, false); + if (r < 0) + return log_error_errno(r, "Failed to reset /dev/console: %m"); + + return 0; +} + +static int parse_crash_chvt(const char *value) { + int b; + + if (safe_atoi(value, &arg_crash_chvt) >= 0) + return 0; + + b = parse_boolean(value); + if (b < 0) + return b; + + if (b > 0) + arg_crash_chvt = 0; /* switch to where kmsg goes */ + else + arg_crash_chvt = -1; /* turn off switching */ + + return 0; +} + +static int set_machine_id(const char *m) { + assert(m); + + if (sd_id128_from_string(m, &arg_machine_id) < 0) + return -EINVAL; + + if (sd_id128_is_null(arg_machine_id)) + return -EINVAL; + + return 0; +} + +static int parse_proc_cmdline_item(const char *key, const char *value) { + + int r; + + assert(key); + + if (streq(key, "systemd.unit") && value) { + + if (!in_initrd()) + return free_and_strdup(&arg_default_unit, value); + + } else if (streq(key, "rd.systemd.unit") && value) { + + if (in_initrd()) + return free_and_strdup(&arg_default_unit, value); + + } else if (streq(key, "systemd.dump_core") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse dump core switch %s. Ignoring.", value); + else + arg_dump_core = r; + + } else if (streq(key, "systemd.crash_chvt") && value) { + + if (parse_crash_chvt(value) < 0) + log_warning("Failed to parse crash chvt switch %s. Ignoring.", value); + + } else if (streq(key, "systemd.crash_shell") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse crash shell switch %s. Ignoring.", value); + else + arg_crash_shell = r; + + } else if (streq(key, "systemd.crash_reboot") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse crash reboot switch %s. Ignoring.", value); + else + arg_crash_reboot = r; + + } else if (streq(key, "systemd.confirm_spawn") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse confirm spawn switch %s. Ignoring.", value); + else + arg_confirm_spawn = r; + + } else if (streq(key, "systemd.show_status") && value) { + + r = parse_show_status(value, &arg_show_status); + if (r < 0) + log_warning("Failed to parse show status switch %s. Ignoring.", value); + + } else if (streq(key, "systemd.default_standard_output") && value) { + + r = exec_output_from_string(value); + if (r < 0) + log_warning("Failed to parse default standard output switch %s. Ignoring.", value); + else + arg_default_std_output = r; + + } else if (streq(key, "systemd.default_standard_error") && value) { + + r = exec_output_from_string(value); + if (r < 0) + log_warning("Failed to parse default standard error switch %s. Ignoring.", value); + else + arg_default_std_error = r; + + } else if (streq(key, "systemd.setenv") && value) { + + if (env_assignment_is_valid(value)) { + char **env; + + env = strv_env_set(arg_default_environment, value); + if (env) + arg_default_environment = env; + else + log_warning_errno(ENOMEM, "Setting environment variable '%s' failed, ignoring: %m", value); + } else + log_warning("Environment variable name '%s' is not valid. Ignoring.", value); + + } else if (streq(key, "systemd.machine_id") && value) { + + r = set_machine_id(value); + if (r < 0) + log_warning("MachineID '%s' is not valid. Ignoring.", value); + + } else if (streq(key, "quiet") && !value) { + + if (arg_show_status == _SHOW_STATUS_UNSET) + arg_show_status = SHOW_STATUS_AUTO; + + } else if (streq(key, "debug") && !value) { + + /* Note that log_parse_environment() handles 'debug' + * too, and sets the log level to LOG_DEBUG. */ + + if (detect_container() > 0) + log_set_target(LOG_TARGET_CONSOLE); + + } else if (!in_initrd() && !value) { + const char *target; + + /* SysV compatibility */ + target = runlevel_to_target(key); + if (target) + return free_and_strdup(&arg_default_unit, target); + + } else if (streq(key, "systemd.default_timeout_start_sec") && value) { + + r = parse_sec(value, &arg_default_timeout_start_usec); + if (r < 0) + log_warning_errno(r, "Failed to parse default start timeout: %s, ignoring.", value); + + if (arg_default_timeout_start_usec <= 0) + arg_default_timeout_start_usec = USEC_INFINITY; + } + + return 0; +} + +#define DEFINE_SETTER(name, func, descr) \ + static int name(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) { \ + \ + int r; \ + \ + assert(filename); \ + assert(lvalue); \ + assert(rvalue); \ + \ + r = func(rvalue); \ + if (r < 0) \ + log_syntax(unit, LOG_ERR, filename, line, r, \ + "Invalid " descr "'%s': %m", \ + rvalue); \ + \ + return 0; \ + } + +DEFINE_SETTER(config_parse_level2, log_set_max_level_from_string, "log level") +DEFINE_SETTER(config_parse_target, log_set_target_from_string, "target") +DEFINE_SETTER(config_parse_color, log_show_color_from_string, "color" ) +DEFINE_SETTER(config_parse_location, log_show_location_from_string, "location") + +static int config_parse_cpu_affinity2( + 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) { + + _cleanup_cpu_free_ cpu_set_t *c = NULL; + int ncpus; + + ncpus = parse_cpu_set_and_warn(rvalue, &c, unit, filename, line, lvalue); + if (ncpus < 0) + return ncpus; + + if (sched_setaffinity(0, CPU_ALLOC_SIZE(ncpus), c) < 0) + log_warning("Failed to set CPU affinity: %m"); + + return 0; +} + +static int config_parse_show_status( + 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) { + + int k; + ShowStatus *b = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + k = parse_show_status(rvalue, b); + if (k < 0) { + log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse show status setting, ignoring: %s", rvalue); + return 0; + } + + return 0; +} + +static int config_parse_crash_chvt( + 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) { + + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = parse_crash_chvt(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CrashChangeVT= setting, ignoring: %s", rvalue); + return 0; + } + + return 0; +} + +static int config_parse_join_controllers(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) { + + const char *whole_rvalue = rvalue; + unsigned n = 0; + + assert(filename); + assert(lvalue); + assert(rvalue); + + arg_join_controllers = strv_free_free(arg_join_controllers); + + for (;;) { + _cleanup_free_ char *word = NULL; + char **l; + int r; + + r = extract_first_word(&rvalue, &word, WHITESPACE, EXTRACT_QUOTES); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Invalid value for %s: %s", lvalue, whole_rvalue); + return r; + } + if (r == 0) + break; + + l = strv_split(word, ","); + if (!l) + return log_oom(); + strv_uniq(l); + + if (strv_length(l) <= 1) { + strv_free(l); + continue; + } + + if (!arg_join_controllers) { + arg_join_controllers = new(char**, 2); + if (!arg_join_controllers) { + strv_free(l); + return log_oom(); + } + + arg_join_controllers[0] = l; + arg_join_controllers[1] = NULL; + + n = 1; + } else { + char ***a; + char ***t; + + t = new0(char**, n+2); + if (!t) { + strv_free(l); + return log_oom(); + } + + n = 0; + + for (a = arg_join_controllers; *a; a++) { + + if (strv_overlap(*a, l)) { + if (strv_extend_strv(&l, *a, false) < 0) { + strv_free(l); + strv_free_free(t); + return log_oom(); + } + + } else { + char **c; + + c = strv_copy(*a); + if (!c) { + strv_free(l); + strv_free_free(t); + return log_oom(); + } + + t[n++] = c; + } + } + + t[n++] = strv_uniq(l); + + strv_free_free(arg_join_controllers); + arg_join_controllers = t; + } + } + if (!isempty(rvalue)) + log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); + + return 0; +} + +static int parse_config_file(void) { + + const ConfigTableItem items[] = { + { "Manager", "LogLevel", config_parse_level2, 0, NULL }, + { "Manager", "LogTarget", config_parse_target, 0, NULL }, + { "Manager", "LogColor", config_parse_color, 0, NULL }, + { "Manager", "LogLocation", config_parse_location, 0, NULL }, + { "Manager", "DumpCore", config_parse_bool, 0, &arg_dump_core }, + { "Manager", "CrashChVT", /* legacy */ config_parse_crash_chvt, 0, NULL }, + { "Manager", "CrashChangeVT", config_parse_crash_chvt, 0, NULL }, + { "Manager", "CrashShell", config_parse_bool, 0, &arg_crash_shell }, + { "Manager", "CrashReboot", config_parse_bool, 0, &arg_crash_reboot }, + { "Manager", "ShowStatus", config_parse_show_status, 0, &arg_show_status }, + { "Manager", "CPUAffinity", config_parse_cpu_affinity2, 0, NULL }, + { "Manager", "JoinControllers", config_parse_join_controllers, 0, &arg_join_controllers }, + { "Manager", "RuntimeWatchdogSec", config_parse_sec, 0, &arg_runtime_watchdog }, + { "Manager", "ShutdownWatchdogSec", config_parse_sec, 0, &arg_shutdown_watchdog }, + { "Manager", "CapabilityBoundingSet", config_parse_capability_set, 0, &arg_capability_bounding_set }, +#ifdef HAVE_SECCOMP + { "Manager", "SystemCallArchitectures", config_parse_syscall_archs, 0, &arg_syscall_archs }, +#endif + { "Manager", "TimerSlackNSec", config_parse_nsec, 0, &arg_timer_slack_nsec }, + { "Manager", "DefaultTimerAccuracySec", config_parse_sec, 0, &arg_default_timer_accuracy_usec }, + { "Manager", "DefaultStandardOutput", config_parse_output, 0, &arg_default_std_output }, + { "Manager", "DefaultStandardError", config_parse_output, 0, &arg_default_std_error }, + { "Manager", "DefaultTimeoutStartSec", config_parse_sec, 0, &arg_default_timeout_start_usec }, + { "Manager", "DefaultTimeoutStopSec", config_parse_sec, 0, &arg_default_timeout_stop_usec }, + { "Manager", "DefaultRestartSec", config_parse_sec, 0, &arg_default_restart_usec }, + { "Manager", "DefaultStartLimitInterval", config_parse_sec, 0, &arg_default_start_limit_interval }, /* obsolete alias */ + { "Manager", "DefaultStartLimitIntervalSec",config_parse_sec, 0, &arg_default_start_limit_interval }, + { "Manager", "DefaultStartLimitBurst", config_parse_unsigned, 0, &arg_default_start_limit_burst }, + { "Manager", "DefaultEnvironment", config_parse_environ, 0, &arg_default_environment }, + { "Manager", "DefaultLimitCPU", config_parse_limit, RLIMIT_CPU, arg_default_rlimit }, + { "Manager", "DefaultLimitFSIZE", config_parse_limit, RLIMIT_FSIZE, arg_default_rlimit }, + { "Manager", "DefaultLimitDATA", config_parse_limit, RLIMIT_DATA, arg_default_rlimit }, + { "Manager", "DefaultLimitSTACK", config_parse_limit, RLIMIT_STACK, arg_default_rlimit }, + { "Manager", "DefaultLimitCORE", config_parse_limit, RLIMIT_CORE, arg_default_rlimit }, + { "Manager", "DefaultLimitRSS", config_parse_limit, RLIMIT_RSS, arg_default_rlimit }, + { "Manager", "DefaultLimitNOFILE", config_parse_limit, RLIMIT_NOFILE, arg_default_rlimit }, + { "Manager", "DefaultLimitAS", config_parse_limit, RLIMIT_AS, arg_default_rlimit }, + { "Manager", "DefaultLimitNPROC", config_parse_limit, RLIMIT_NPROC, arg_default_rlimit }, + { "Manager", "DefaultLimitMEMLOCK", config_parse_limit, RLIMIT_MEMLOCK, arg_default_rlimit }, + { "Manager", "DefaultLimitLOCKS", config_parse_limit, RLIMIT_LOCKS, arg_default_rlimit }, + { "Manager", "DefaultLimitSIGPENDING", config_parse_limit, RLIMIT_SIGPENDING, arg_default_rlimit }, + { "Manager", "DefaultLimitMSGQUEUE", config_parse_limit, RLIMIT_MSGQUEUE, arg_default_rlimit }, + { "Manager", "DefaultLimitNICE", config_parse_limit, RLIMIT_NICE, arg_default_rlimit }, + { "Manager", "DefaultLimitRTPRIO", config_parse_limit, RLIMIT_RTPRIO, arg_default_rlimit }, + { "Manager", "DefaultLimitRTTIME", config_parse_limit, RLIMIT_RTTIME, arg_default_rlimit }, + { "Manager", "DefaultCPUAccounting", config_parse_bool, 0, &arg_default_cpu_accounting }, + { "Manager", "DefaultIOAccounting", config_parse_bool, 0, &arg_default_io_accounting }, + { "Manager", "DefaultBlockIOAccounting", config_parse_bool, 0, &arg_default_blockio_accounting }, + { "Manager", "DefaultMemoryAccounting", config_parse_bool, 0, &arg_default_memory_accounting }, + { "Manager", "DefaultTasksAccounting", config_parse_bool, 0, &arg_default_tasks_accounting }, + { "Manager", "DefaultTasksMax", config_parse_tasks_max, 0, &arg_default_tasks_max }, + {} + }; + + const char *fn, *conf_dirs_nulstr; + + fn = arg_system ? + PKGSYSCONFDIR "/system.conf" : + PKGSYSCONFDIR "/user.conf"; + + conf_dirs_nulstr = arg_system ? + CONF_PATHS_NULSTR("systemd/system.conf.d") : + CONF_PATHS_NULSTR("systemd/user.conf.d"); + + config_parse_many(fn, conf_dirs_nulstr, "Manager\0", config_item_table_lookup, items, false, NULL); + + /* Traditionally "0" was used to turn off the default unit timeouts. Fix this up so that we used USEC_INFINITY + * like everywhere else. */ + if (arg_default_timeout_start_usec <= 0) + arg_default_timeout_start_usec = USEC_INFINITY; + if (arg_default_timeout_stop_usec <= 0) + arg_default_timeout_stop_usec = USEC_INFINITY; + + return 0; +} + +static void manager_set_defaults(Manager *m) { + + assert(m); + + m->default_timer_accuracy_usec = arg_default_timer_accuracy_usec; + m->default_std_output = arg_default_std_output; + m->default_std_error = arg_default_std_error; + m->default_timeout_start_usec = arg_default_timeout_start_usec; + m->default_timeout_stop_usec = arg_default_timeout_stop_usec; + m->default_restart_usec = arg_default_restart_usec; + m->default_start_limit_interval = arg_default_start_limit_interval; + m->default_start_limit_burst = arg_default_start_limit_burst; + m->default_cpu_accounting = arg_default_cpu_accounting; + m->default_io_accounting = arg_default_io_accounting; + m->default_blockio_accounting = arg_default_blockio_accounting; + m->default_memory_accounting = arg_default_memory_accounting; + m->default_tasks_accounting = arg_default_tasks_accounting; + m->default_tasks_max = arg_default_tasks_max; + + manager_set_default_rlimits(m, arg_default_rlimit); + manager_environment_add(m, NULL, arg_default_environment); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_LOG_LEVEL = 0x100, + ARG_LOG_TARGET, + ARG_LOG_COLOR, + ARG_LOG_LOCATION, + ARG_UNIT, + ARG_SYSTEM, + ARG_USER, + ARG_TEST, + ARG_NO_PAGER, + ARG_VERSION, + ARG_DUMP_CONFIGURATION_ITEMS, + ARG_DUMP_CORE, + ARG_CRASH_CHVT, + ARG_CRASH_SHELL, + ARG_CRASH_REBOOT, + ARG_CONFIRM_SPAWN, + ARG_SHOW_STATUS, + ARG_DESERIALIZE, + ARG_SWITCHED_ROOT, + ARG_DEFAULT_STD_OUTPUT, + ARG_DEFAULT_STD_ERROR, + ARG_MACHINE_ID + }; + + static const struct option options[] = { + { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + { "log-target", required_argument, NULL, ARG_LOG_TARGET }, + { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, + { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, + { "unit", required_argument, NULL, ARG_UNIT }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "user", no_argument, NULL, ARG_USER }, + { "test", no_argument, NULL, ARG_TEST }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "dump-configuration-items", no_argument, NULL, ARG_DUMP_CONFIGURATION_ITEMS }, + { "dump-core", optional_argument, NULL, ARG_DUMP_CORE }, + { "crash-chvt", required_argument, NULL, ARG_CRASH_CHVT }, + { "crash-shell", optional_argument, NULL, ARG_CRASH_SHELL }, + { "crash-reboot", optional_argument, NULL, ARG_CRASH_REBOOT }, + { "confirm-spawn", optional_argument, NULL, ARG_CONFIRM_SPAWN }, + { "show-status", optional_argument, NULL, ARG_SHOW_STATUS }, + { "deserialize", required_argument, NULL, ARG_DESERIALIZE }, + { "switched-root", no_argument, NULL, ARG_SWITCHED_ROOT }, + { "default-standard-output", required_argument, NULL, ARG_DEFAULT_STD_OUTPUT, }, + { "default-standard-error", required_argument, NULL, ARG_DEFAULT_STD_ERROR, }, + { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, + {} + }; + + int c, r; + + assert(argc >= 1); + assert(argv); + + if (getpid() == 1) + opterr = 0; + + while ((c = getopt_long(argc, argv, "hDbsz:", options, NULL)) >= 0) + + switch (c) { + + case ARG_LOG_LEVEL: + r = log_set_max_level_from_string(optarg); + if (r < 0) { + log_error("Failed to parse log level %s.", optarg); + return r; + } + + break; + + case ARG_LOG_TARGET: + r = log_set_target_from_string(optarg); + if (r < 0) { + log_error("Failed to parse log target %s.", optarg); + return r; + } + + break; + + case ARG_LOG_COLOR: + + if (optarg) { + r = log_show_color_from_string(optarg); + if (r < 0) { + log_error("Failed to parse log color setting %s.", optarg); + return r; + } + } else + log_show_color(true); + + break; + + case ARG_LOG_LOCATION: + if (optarg) { + r = log_show_location_from_string(optarg); + if (r < 0) { + log_error("Failed to parse log location setting %s.", optarg); + return r; + } + } else + log_show_location(true); + + break; + + case ARG_DEFAULT_STD_OUTPUT: + r = exec_output_from_string(optarg); + if (r < 0) { + log_error("Failed to parse default standard output setting %s.", optarg); + return r; + } else + arg_default_std_output = r; + break; + + case ARG_DEFAULT_STD_ERROR: + r = exec_output_from_string(optarg); + if (r < 0) { + log_error("Failed to parse default standard error output setting %s.", optarg); + return r; + } else + arg_default_std_error = r; + break; + + case ARG_UNIT: + + r = free_and_strdup(&arg_default_unit, optarg); + if (r < 0) + return log_error_errno(r, "Failed to set default unit %s: %m", optarg); + + break; + + case ARG_SYSTEM: + arg_system = true; + break; + + case ARG_USER: + arg_system = false; + break; + + case ARG_TEST: + arg_action = ACTION_TEST; + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case ARG_VERSION: + arg_action = ACTION_VERSION; + break; + + case ARG_DUMP_CONFIGURATION_ITEMS: + arg_action = ACTION_DUMP_CONFIGURATION_ITEMS; + break; + + case ARG_DUMP_CORE: + if (!optarg) + arg_dump_core = true; + else { + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse dump core boolean: %s", optarg); + arg_dump_core = r; + } + break; + + case ARG_CRASH_CHVT: + r = parse_crash_chvt(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse crash virtual terminal index: %s", optarg); + break; + + case ARG_CRASH_SHELL: + if (!optarg) + arg_crash_shell = true; + else { + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse crash shell boolean: %s", optarg); + arg_crash_shell = r; + } + break; + + case ARG_CRASH_REBOOT: + if (!optarg) + arg_crash_reboot = true; + else { + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse crash shell boolean: %s", optarg); + arg_crash_reboot = r; + } + break; + + case ARG_CONFIRM_SPAWN: + r = optarg ? parse_boolean(optarg) : 1; + if (r < 0) { + log_error("Failed to parse confirm spawn boolean %s.", optarg); + return r; + } + arg_confirm_spawn = r; + break; + + case ARG_SHOW_STATUS: + if (optarg) { + r = parse_show_status(optarg, &arg_show_status); + if (r < 0) { + log_error("Failed to parse show status boolean %s.", optarg); + return r; + } + } else + arg_show_status = SHOW_STATUS_YES; + break; + + case ARG_DESERIALIZE: { + int fd; + FILE *f; + + r = safe_atoi(optarg, &fd); + if (r < 0 || fd < 0) { + log_error("Failed to parse deserialize option %s.", optarg); + return -EINVAL; + } + + (void) fd_cloexec(fd, true); + + f = fdopen(fd, "r"); + if (!f) + return log_error_errno(errno, "Failed to open serialization fd: %m"); + + safe_fclose(arg_serialization); + arg_serialization = f; + + break; + } + + case ARG_SWITCHED_ROOT: + arg_switched_root = true; + break; + + case ARG_MACHINE_ID: + r = set_machine_id(optarg); + if (r < 0) { + log_error("MachineID '%s' is not valid.", optarg); + return r; + } + break; + + case 'h': + arg_action = ACTION_HELP; + break; + + case 'D': + log_set_max_level(LOG_DEBUG); + break; + + case 'b': + case 's': + case 'z': + /* Just to eat away the sysvinit kernel + * cmdline args without getopt() error + * messages that we'll parse in + * parse_proc_cmdline_word() or ignore. */ + + case '?': + if (getpid() != 1) + return -EINVAL; + else + return 0; + + default: + assert_not_reached("Unhandled option code."); + } + + if (optind < argc && getpid() != 1) { + /* Hmm, when we aren't run as init system + * let's complain about excess arguments */ + + log_error("Excess arguments."); + return -EINVAL; + } + + return 0; +} + +static int help(void) { + + printf("%s [OPTIONS...]\n\n" + "Starts up and maintains the system or user services.\n\n" + " -h --help Show this help\n" + " --test Determine startup sequence, dump it and exit\n" + " --no-pager Do not pipe output into a pager\n" + " --dump-configuration-items Dump understood unit configuration items\n" + " --unit=UNIT Set default unit\n" + " --system Run a system instance, even if PID != 1\n" + " --user Run a user instance\n" + " --dump-core[=BOOL] Dump core on crash\n" + " --crash-vt=NR Change to specified VT on crash\n" + " --crash-reboot[=BOOL] Reboot on crash\n" + " --crash-shell[=BOOL] Run shell on crash\n" + " --confirm-spawn[=BOOL] Ask for confirmation when spawning processes\n" + " --show-status[=BOOL] Show status updates on the console during bootup\n" + " --log-target=TARGET Set log target (console, journal, kmsg, journal-or-kmsg, null)\n" + " --log-level=LEVEL Set log level (debug, info, notice, warning, err, crit, alert, emerg)\n" + " --log-color[=BOOL] Highlight important log messages\n" + " --log-location[=BOOL] Include code location in log messages\n" + " --default-standard-output= Set default standard output for services\n" + " --default-standard-error= Set default standard error output for services\n", + program_invocation_short_name); + + return 0; +} + +static int prepare_reexecute(Manager *m, FILE **_f, FDSet **_fds, bool switching_root) { + _cleanup_fdset_free_ FDSet *fds = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(m); + assert(_f); + assert(_fds); + + r = manager_open_serialization(m, &f); + if (r < 0) + return log_error_errno(r, "Failed to create serialization file: %m"); + + /* Make sure nothing is really destructed when we shut down */ + m->n_reloading++; + bus_manager_send_reloading(m, true); + + fds = fdset_new(); + if (!fds) + return log_oom(); + + r = manager_serialize(m, f, fds, switching_root); + if (r < 0) + return log_error_errno(r, "Failed to serialize state: %m"); + + if (fseeko(f, 0, SEEK_SET) == (off_t) -1) + return log_error_errno(errno, "Failed to rewind serialization fd: %m"); + + r = fd_cloexec(fileno(f), false); + if (r < 0) + return log_error_errno(r, "Failed to disable O_CLOEXEC for serialization: %m"); + + r = fdset_cloexec(fds, false); + if (r < 0) + return log_error_errno(r, "Failed to disable O_CLOEXEC for serialization fds: %m"); + + *_f = f; + *_fds = fds; + + f = NULL; + fds = NULL; + + return 0; +} + +static int bump_rlimit_nofile(struct rlimit *saved_rlimit) { + struct rlimit nl; + int r; + + assert(saved_rlimit); + + /* Save the original RLIMIT_NOFILE so that we can reset it + * later when transitioning from the initrd to the main + * systemd or suchlike. */ + if (getrlimit(RLIMIT_NOFILE, saved_rlimit) < 0) + return log_error_errno(errno, "Reading RLIMIT_NOFILE failed: %m"); + + /* Make sure forked processes get the default kernel setting */ + if (!arg_default_rlimit[RLIMIT_NOFILE]) { + struct rlimit *rl; + + rl = newdup(struct rlimit, saved_rlimit, 1); + if (!rl) + return log_oom(); + + arg_default_rlimit[RLIMIT_NOFILE] = rl; + } + + /* Bump up the resource limit for ourselves substantially */ + nl.rlim_cur = nl.rlim_max = 64*1024; + r = setrlimit_closest(RLIMIT_NOFILE, &nl); + if (r < 0) + return log_error_errno(r, "Setting RLIMIT_NOFILE failed: %m"); + + return 0; +} + +static void test_usr(void) { + + /* Check that /usr is not a separate fs */ + + if (dir_is_empty("/usr") <= 0) + return; + + log_warning("/usr appears to be on its own filesystem and is not already mounted. This is not a supported setup. " + "Some things will probably break (sometimes even silently) in mysterious ways. " + "Consult http://freedesktop.org/wiki/Software/systemd/separate-usr-is-broken for more information."); +} + +static int initialize_join_controllers(void) { + /* By default, mount "cpu" + "cpuacct" together, and "net_cls" + * + "net_prio". We'd like to add "cpuset" to the mix, but + * "cpuset" doesn't really work for groups with no initialized + * attributes. */ + + arg_join_controllers = new(char**, 3); + if (!arg_join_controllers) + return -ENOMEM; + + arg_join_controllers[0] = strv_new("cpu", "cpuacct", NULL); + if (!arg_join_controllers[0]) + goto oom; + + arg_join_controllers[1] = strv_new("net_cls", "net_prio", NULL); + if (!arg_join_controllers[1]) + goto oom; + + arg_join_controllers[2] = NULL; + return 0; + +oom: + arg_join_controllers = strv_free_free(arg_join_controllers); + return -ENOMEM; +} + +static int enforce_syscall_archs(Set *archs) { +#ifdef HAVE_SECCOMP + scmp_filter_ctx *seccomp; + Iterator i; + void *id; + int r; + + seccomp = seccomp_init(SCMP_ACT_ALLOW); + if (!seccomp) + return log_oom(); + + SET_FOREACH(id, arg_syscall_archs, i) { + r = seccomp_arch_add(seccomp, PTR_TO_UINT32(id) - 1); + if (r == -EEXIST) + continue; + if (r < 0) { + log_error_errno(r, "Failed to add architecture to seccomp: %m"); + goto finish; + } + } + + r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); + if (r < 0) { + log_error_errno(r, "Failed to unset NO_NEW_PRIVS: %m"); + goto finish; + } + + r = seccomp_load(seccomp); + if (r < 0) + log_error_errno(r, "Failed to add install architecture seccomp: %m"); + +finish: + seccomp_release(seccomp); + return r; +#else + return 0; +#endif +} + +static int status_welcome(void) { + _cleanup_free_ char *pretty_name = NULL, *ansi_color = NULL; + int r; + + r = parse_env_file("/etc/os-release", NEWLINE, + "PRETTY_NAME", &pretty_name, + "ANSI_COLOR", &ansi_color, + NULL); + if (r == -ENOENT) + r = parse_env_file("/usr/lib/os-release", NEWLINE, + "PRETTY_NAME", &pretty_name, + "ANSI_COLOR", &ansi_color, + NULL); + + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to read os-release file: %m"); + + if (log_get_show_color()) + return status_printf(NULL, false, false, + "\nWelcome to \x1B[%sm%s\x1B[0m!\n", + isempty(ansi_color) ? "1" : ansi_color, + isempty(pretty_name) ? "GNU/Linux" : pretty_name); + else + return status_printf(NULL, false, false, + "\nWelcome to %s!\n", + isempty(pretty_name) ? "GNU/Linux" : pretty_name); +} + +static int write_container_id(void) { + const char *c; + int r; + + c = getenv("container"); + if (isempty(c)) + return 0; + + RUN_WITH_UMASK(0022) + r = write_string_file("/run/systemd/container", c, WRITE_STRING_FILE_CREATE); + if (r < 0) + return log_warning_errno(r, "Failed to write /run/systemd/container, ignoring: %m"); + + return 1; +} + +static int bump_unix_max_dgram_qlen(void) { + _cleanup_free_ char *qlen = NULL; + unsigned long v; + int r; + + /* Let's bump the net.unix.max_dgram_qlen sysctl. The kernel + * default of 16 is simply too low. We set the value really + * really early during boot, so that it is actually applied to + * all our sockets, including the $NOTIFY_SOCKET one. */ + + r = read_one_line_file("/proc/sys/net/unix/max_dgram_qlen", &qlen); + if (r < 0) + return log_warning_errno(r, "Failed to read AF_UNIX datagram queue length, ignoring: %m"); + + r = safe_atolu(qlen, &v); + if (r < 0) + return log_warning_errno(r, "Failed to parse AF_UNIX datagram queue length, ignoring: %m"); + + if (v >= DEFAULT_UNIX_MAX_DGRAM_QLEN) + return 0; + + qlen = mfree(qlen); + if (asprintf(&qlen, "%lu\n", DEFAULT_UNIX_MAX_DGRAM_QLEN) < 0) + return log_oom(); + + r = write_string_file("/proc/sys/net/unix/max_dgram_qlen", qlen, 0); + if (r < 0) + return log_full_errno(IN_SET(r, -EROFS, -EPERM, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to bump AF_UNIX datagram queue length, ignoring: %m"); + + return 1; +} + +int main(int argc, char *argv[]) { + Manager *m = NULL; + int r, retval = EXIT_FAILURE; + usec_t before_startup, after_startup; + char timespan[FORMAT_TIMESPAN_MAX]; + FDSet *fds = NULL; + bool reexecute = false; + const char *shutdown_verb = NULL; + dual_timestamp initrd_timestamp = DUAL_TIMESTAMP_NULL; + dual_timestamp userspace_timestamp = DUAL_TIMESTAMP_NULL; + dual_timestamp kernel_timestamp = DUAL_TIMESTAMP_NULL; + dual_timestamp security_start_timestamp = DUAL_TIMESTAMP_NULL; + dual_timestamp security_finish_timestamp = DUAL_TIMESTAMP_NULL; + static char systemd[] = "systemd"; + bool skip_setup = false; + unsigned j; + bool loaded_policy = false; + bool arm_reboot_watchdog = false; + bool queue_default_job = false; + bool empty_etc = false; + char *switch_root_dir = NULL, *switch_root_init = NULL; + struct rlimit saved_rlimit_nofile = RLIMIT_MAKE_CONST(0); + const char *error_message = NULL; + +#ifdef HAVE_SYSV_COMPAT + if (getpid() != 1 && strstr(program_invocation_short_name, "init")) { + /* This is compatibility support for SysV, where + * calling init as a user is identical to telinit. */ + + execv(SYSTEMCTL_BINARY_PATH, argv); + log_error_errno(errno, "Failed to exec " SYSTEMCTL_BINARY_PATH ": %m"); + return 1; + } +#endif + + dual_timestamp_from_monotonic(&kernel_timestamp, 0); + dual_timestamp_get(&userspace_timestamp); + + /* Determine if this is a reexecution or normal bootup. We do + * the full command line parsing much later, so let's just + * have a quick peek here. */ + if (strv_find(argv+1, "--deserialize")) + skip_setup = true; + + /* If we have switched root, do all the special setup + * things */ + if (strv_find(argv+1, "--switched-root")) + skip_setup = false; + + /* If we get started via the /sbin/init symlink then we are + called 'init'. After a subsequent reexecution we are then + called 'systemd'. That is confusing, hence let's call us + systemd right-away. */ + program_invocation_short_name = systemd; + prctl(PR_SET_NAME, systemd); + + saved_argv = argv; + saved_argc = argc; + + log_show_color(colors_enabled()); + log_set_upgrade_syslog_to_journal(true); + + /* Disable the umask logic */ + if (getpid() == 1) + umask(0); + + if (getpid() == 1 && detect_container() <= 0) { + + /* Running outside of a container as PID 1 */ + arg_system = true; + make_null_stdio(); + log_set_target(LOG_TARGET_KMSG); + log_open(); + + if (in_initrd()) + initrd_timestamp = userspace_timestamp; + + if (!skip_setup) { + r = mount_setup_early(); + if (r < 0) { + error_message = "Failed to early mount API filesystems"; + goto finish; + } + dual_timestamp_get(&security_start_timestamp); + if (mac_selinux_setup(&loaded_policy) < 0) { + error_message = "Failed to load SELinux policy"; + goto finish; + } else if (ima_setup() < 0) { + error_message = "Failed to load IMA policy"; + goto finish; + } else if (mac_smack_setup(&loaded_policy) < 0) { + error_message = "Failed to load SMACK policy"; + goto finish; + } + dual_timestamp_get(&security_finish_timestamp); + } + + if (mac_selinux_init() < 0) { + error_message = "Failed to initialize SELinux policy"; + goto finish; + } + + if (!skip_setup) { + if (clock_is_localtime(NULL) > 0) { + int min; + + /* + * The very first call of settimeofday() also does a time warp in the kernel. + * + * In the rtc-in-local time mode, we set the kernel's timezone, and rely on + * external tools to take care of maintaining the RTC and do all adjustments. + * This matches the behavior of Windows, which leaves the RTC alone if the + * registry tells that the RTC runs in UTC. + */ + r = clock_set_timezone(&min); + if (r < 0) + log_error_errno(r, "Failed to apply local time delta, ignoring: %m"); + else + log_info("RTC configured in localtime, applying delta of %i minutes to system time.", min); + } else if (!in_initrd()) { + /* + * Do a dummy very first call to seal the kernel's time warp magic. + * + * Do not call this this from inside the initrd. The initrd might not + * carry /etc/adjtime with LOCAL, but the real system could be set up + * that way. In such case, we need to delay the time-warp or the sealing + * until we reach the real system. + * + * Do no set the kernel's timezone. The concept of local time cannot + * be supported reliably, the time will jump or be incorrect at every daylight + * saving time change. All kernel local time concepts will be treated + * as UTC that way. + */ + (void) clock_reset_timewarp(); + } + + r = clock_apply_epoch(); + if (r < 0) + log_error_errno(r, "Current system time is before build time, but cannot correct: %m"); + else if (r > 0) + log_info("System time before build time, advancing clock."); + } + + /* Set the default for later on, but don't actually + * open the logs like this for now. Note that if we + * are transitioning from the initrd there might still + * be journal fd open, and we shouldn't attempt + * opening that before we parsed /proc/cmdline which + * might redirect output elsewhere. */ + log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); + + } else if (getpid() == 1) { + /* Running inside a container, as PID 1 */ + arg_system = true; + log_set_target(LOG_TARGET_CONSOLE); + log_close_console(); /* force reopen of /dev/console */ + log_open(); + + /* For the later on, see above... */ + log_set_target(LOG_TARGET_JOURNAL); + + /* clear the kernel timestamp, + * because we are in a container */ + kernel_timestamp = DUAL_TIMESTAMP_NULL; + } else { + /* Running as user instance */ + arg_system = false; + log_set_target(LOG_TARGET_AUTO); + log_open(); + + /* clear the kernel timestamp, + * because we are not PID 1 */ + kernel_timestamp = DUAL_TIMESTAMP_NULL; + } + + if (getpid() == 1) { + /* Don't limit the core dump size, so that coredump handlers such as systemd-coredump (which honour the limit) + * will process core dumps for system services by default. */ + (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(RLIM_INFINITY)); + + /* But at the same time, turn off the core_pattern logic by default, so that no coredumps are stored + * until the systemd-coredump tool is enabled via sysctl. */ + if (!skip_setup) + (void) write_string_file("/proc/sys/kernel/core_pattern", "|/bin/false", 0); + } + + /* Initialize default unit */ + r = free_and_strdup(&arg_default_unit, SPECIAL_DEFAULT_TARGET); + if (r < 0) { + log_emergency_errno(r, "Failed to set default unit %s: %m", SPECIAL_DEFAULT_TARGET); + error_message = "Failed to set default unit"; + goto finish; + } + + r = initialize_join_controllers(); + if (r < 0) { + error_message = "Failed to initialize cgroup controllers"; + goto finish; + } + + /* Mount /proc, /sys and friends, so that /proc/cmdline and + * /proc/$PID/fd is available. */ + if (getpid() == 1) { + + /* Load the kernel modules early, so that we kdbus.ko is loaded before kdbusfs shall be mounted */ + if (!skip_setup) + kmod_setup(); + + r = mount_setup(loaded_policy); + if (r < 0) { + error_message = "Failed to mount API filesystems"; + goto finish; + } + } + + /* Reset all signal handlers. */ + (void) reset_all_signal_handlers(); + (void) ignore_signals(SIGNALS_IGNORE, -1); + + if (parse_config_file() < 0) { + error_message = "Failed to parse config file"; + goto finish; + } + + if (arg_system) { + r = parse_proc_cmdline(parse_proc_cmdline_item); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + } + + /* Note that this also parses bits from the kernel command + * line, including "debug". */ + log_parse_environment(); + + if (parse_argv(argc, argv) < 0) { + error_message = "Failed to parse commandline arguments"; + goto finish; + } + + if (arg_action == ACTION_TEST && + geteuid() == 0) { + log_error("Don't run test mode as root."); + goto finish; + } + + if (!arg_system && + arg_action == ACTION_RUN && + sd_booted() <= 0) { + log_error("Trying to run as user instance, but the system has not been booted with systemd."); + goto finish; + } + + if (arg_system && + arg_action == ACTION_RUN && + running_in_chroot() > 0) { + log_error("Cannot be run in a chroot() environment."); + goto finish; + } + + if (arg_action == ACTION_TEST) + skip_setup = true; + + if (arg_action == ACTION_TEST || arg_action == ACTION_HELP) + pager_open(arg_no_pager, false); + + if (arg_action == ACTION_HELP) { + retval = help(); + goto finish; + } else if (arg_action == ACTION_VERSION) { + retval = version(); + goto finish; + } else if (arg_action == ACTION_DUMP_CONFIGURATION_ITEMS) { + unit_dump_config_items(stdout); + retval = EXIT_SUCCESS; + goto finish; + } else if (arg_action == ACTION_DONE) { + retval = EXIT_SUCCESS; + goto finish; + } + + if (!arg_system && + !getenv("XDG_RUNTIME_DIR")) { + log_error("Trying to run as user instance, but $XDG_RUNTIME_DIR is not set."); + goto finish; + } + + assert_se(arg_action == ACTION_RUN || arg_action == ACTION_TEST); + + /* Close logging fds, in order not to confuse fdset below */ + log_close(); + + /* Remember open file descriptors for later deserialization */ + r = fdset_new_fill(&fds); + if (r < 0) { + log_emergency_errno(r, "Failed to allocate fd set: %m"); + error_message = "Failed to allocate fd set"; + goto finish; + } else + fdset_cloexec(fds, true); + + if (arg_serialization) + assert_se(fdset_remove(fds, fileno(arg_serialization)) >= 0); + + if (arg_system) + /* Become a session leader if we aren't one yet. */ + setsid(); + + /* Move out of the way, so that we won't block unmounts */ + assert_se(chdir("/") == 0); + + /* Reset the console, but only if this is really init and we + * are freshly booted */ + if (arg_system && arg_action == ACTION_RUN) { + + /* If we are init, we connect stdin/stdout/stderr to + * /dev/null and make sure we don't have a controlling + * tty. */ + release_terminal(); + + if (getpid() == 1 && !skip_setup) + console_setup(); + } + + /* Open the logging devices, if possible and necessary */ + log_open(); + + if (arg_show_status == _SHOW_STATUS_UNSET) + arg_show_status = SHOW_STATUS_YES; + + /* Make sure we leave a core dump without panicing the + * kernel. */ + if (getpid() == 1) { + install_crash_handler(); + + r = mount_cgroup_controllers(arg_join_controllers); + if (r < 0) + goto finish; + } + + if (arg_system) { + int v; + + log_info(PACKAGE_STRING " running in %ssystem mode. (" SYSTEMD_FEATURES ")", + arg_action == ACTION_TEST ? "test " : "" ); + + v = detect_virtualization(); + if (v > 0) + log_info("Detected virtualization %s.", virtualization_to_string(v)); + + write_container_id(); + + log_info("Detected architecture %s.", architecture_to_string(uname_architecture())); + + if (in_initrd()) + log_info("Running in initial RAM disk."); + + /* Let's check whether /etc is already populated. We + * don't actually really check for that, but use + * /etc/machine-id as flag file. This allows container + * managers and installers to provision a couple of + * files already. If the container manager wants to + * provision the machine ID itself it should pass + * $container_uuid to PID 1. */ + + empty_etc = access("/etc/machine-id", F_OK) < 0; + if (empty_etc) + log_info("Running with unpopulated /etc."); + } else { + _cleanup_free_ char *t; + + t = uid_to_name(getuid()); + log_debug(PACKAGE_STRING " running in %suser mode for user "UID_FMT"/%s. (" SYSTEMD_FEATURES ")", + arg_action == ACTION_TEST ? " test" : "", getuid(), t); + } + + if (arg_system && !skip_setup) { + if (arg_show_status > 0) + status_welcome(); + + hostname_setup(); + machine_id_setup(NULL, arg_machine_id); + loopback_setup(); + bump_unix_max_dgram_qlen(); + + test_usr(); + } + + if (arg_system && arg_runtime_watchdog > 0 && arg_runtime_watchdog != USEC_INFINITY) + watchdog_set_timeout(&arg_runtime_watchdog); + + if (arg_timer_slack_nsec != NSEC_INFINITY) + if (prctl(PR_SET_TIMERSLACK, arg_timer_slack_nsec) < 0) + log_error_errno(errno, "Failed to adjust timer slack: %m"); + + if (!cap_test_all(arg_capability_bounding_set)) { + r = capability_bounding_set_drop_usermode(arg_capability_bounding_set); + if (r < 0) { + log_emergency_errno(r, "Failed to drop capability bounding set of usermode helpers: %m"); + error_message = "Failed to drop capability bounding set of usermode helpers"; + goto finish; + } + r = capability_bounding_set_drop(arg_capability_bounding_set, true); + if (r < 0) { + log_emergency_errno(r, "Failed to drop capability bounding set: %m"); + error_message = "Failed to drop capability bounding set"; + goto finish; + } + } + + if (arg_syscall_archs) { + r = enforce_syscall_archs(arg_syscall_archs); + if (r < 0) { + error_message = "Failed to set syscall architectures"; + goto finish; + } + } + + if (!arg_system) + /* Become reaper of our children */ + if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) + log_warning_errno(errno, "Failed to make us a subreaper: %m"); + + if (arg_system) { + bump_rlimit_nofile(&saved_rlimit_nofile); + + if (empty_etc) { + r = unit_file_preset_all(UNIT_FILE_SYSTEM, false, NULL, UNIT_FILE_PRESET_ENABLE_ONLY, false, NULL, 0); + if (r < 0) + log_full_errno(r == -EEXIST ? LOG_NOTICE : LOG_WARNING, r, "Failed to populate /etc with preset unit settings, ignoring: %m"); + else + log_info("Populated /etc with preset unit settings."); + } + } + + r = manager_new(arg_system ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, arg_action == ACTION_TEST, &m); + if (r < 0) { + log_emergency_errno(r, "Failed to allocate manager object: %m"); + error_message = "Failed to allocate manager object"; + goto finish; + } + + m->confirm_spawn = arg_confirm_spawn; + m->runtime_watchdog = arg_runtime_watchdog; + m->shutdown_watchdog = arg_shutdown_watchdog; + m->userspace_timestamp = userspace_timestamp; + m->kernel_timestamp = kernel_timestamp; + m->initrd_timestamp = initrd_timestamp; + m->security_start_timestamp = security_start_timestamp; + m->security_finish_timestamp = security_finish_timestamp; + + manager_set_defaults(m); + manager_set_show_status(m, arg_show_status); + manager_set_first_boot(m, empty_etc); + + /* Remember whether we should queue the default job */ + queue_default_job = !arg_serialization || arg_switched_root; + + before_startup = now(CLOCK_MONOTONIC); + + r = manager_startup(m, arg_serialization, fds); + if (r < 0) + log_error_errno(r, "Failed to fully start up daemon: %m"); + + /* This will close all file descriptors that were opened, but + * not claimed by any unit. */ + fds = fdset_free(fds); + + arg_serialization = safe_fclose(arg_serialization); + + if (queue_default_job) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + Unit *target = NULL; + Job *default_unit_job; + + log_debug("Activating default unit: %s", arg_default_unit); + + r = manager_load_unit(m, arg_default_unit, NULL, &error, &target); + if (r < 0) + log_error("Failed to load default target: %s", bus_error_message(&error, r)); + else if (target->load_state == UNIT_ERROR || target->load_state == UNIT_NOT_FOUND) + log_error_errno(target->load_error, "Failed to load default target: %m"); + else if (target->load_state == UNIT_MASKED) + log_error("Default target masked."); + + if (!target || target->load_state != UNIT_LOADED) { + log_info("Trying to load rescue target..."); + + r = manager_load_unit(m, SPECIAL_RESCUE_TARGET, NULL, &error, &target); + if (r < 0) { + log_emergency("Failed to load rescue target: %s", bus_error_message(&error, r)); + error_message = "Failed to load rescue target"; + goto finish; + } else if (target->load_state == UNIT_ERROR || target->load_state == UNIT_NOT_FOUND) { + log_emergency_errno(target->load_error, "Failed to load rescue target: %m"); + error_message = "Failed to load rescue target"; + goto finish; + } else if (target->load_state == UNIT_MASKED) { + log_emergency("Rescue target masked."); + error_message = "Rescue target masked"; + goto finish; + } + } + + assert(target->load_state == UNIT_LOADED); + + if (arg_action == ACTION_TEST) { + printf("-> By units:\n"); + manager_dump_units(m, stdout, "\t"); + } + + r = manager_add_job(m, JOB_START, target, JOB_ISOLATE, &error, &default_unit_job); + if (r == -EPERM) { + log_debug("Default target could not be isolated, starting instead: %s", bus_error_message(&error, r)); + + sd_bus_error_free(&error); + + r = manager_add_job(m, JOB_START, target, JOB_REPLACE, &error, &default_unit_job); + if (r < 0) { + log_emergency("Failed to start default target: %s", bus_error_message(&error, r)); + error_message = "Failed to start default target"; + goto finish; + } + } else if (r < 0) { + log_emergency("Failed to isolate default target: %s", bus_error_message(&error, r)); + error_message = "Failed to isolate default target"; + goto finish; + } + + m->default_unit_job_id = default_unit_job->id; + + after_startup = now(CLOCK_MONOTONIC); + log_full(arg_action == ACTION_TEST ? LOG_INFO : LOG_DEBUG, + "Loaded units and determined initial transaction in %s.", + format_timespan(timespan, sizeof(timespan), after_startup - before_startup, 100 * USEC_PER_MSEC)); + + if (arg_action == ACTION_TEST) { + printf("-> By jobs:\n"); + manager_dump_jobs(m, stdout, "\t"); + retval = EXIT_SUCCESS; + goto finish; + } + } + + for (;;) { + r = manager_loop(m); + if (r < 0) { + log_emergency_errno(r, "Failed to run main loop: %m"); + error_message = "Failed to run main loop"; + goto finish; + } + + switch (m->exit_code) { + + case MANAGER_RELOAD: + log_info("Reloading."); + + r = parse_config_file(); + if (r < 0) + log_error("Failed to parse config file."); + + manager_set_defaults(m); + + r = manager_reload(m); + if (r < 0) + log_error_errno(r, "Failed to reload: %m"); + break; + + case MANAGER_REEXECUTE: + + if (prepare_reexecute(m, &arg_serialization, &fds, false) < 0) { + error_message = "Failed to prepare for reexecution"; + goto finish; + } + + reexecute = true; + log_notice("Reexecuting."); + goto finish; + + case MANAGER_SWITCH_ROOT: + /* Steal the switch root parameters */ + switch_root_dir = m->switch_root; + switch_root_init = m->switch_root_init; + m->switch_root = m->switch_root_init = NULL; + + if (!switch_root_init) + if (prepare_reexecute(m, &arg_serialization, &fds, true) < 0) { + error_message = "Failed to prepare for reexecution"; + goto finish; + } + + reexecute = true; + log_notice("Switching root."); + goto finish; + + case MANAGER_EXIT: + retval = m->return_value; + + if (MANAGER_IS_USER(m)) { + log_debug("Exit."); + goto finish; + } + + /* fallthrough */ + case MANAGER_REBOOT: + case MANAGER_POWEROFF: + case MANAGER_HALT: + case MANAGER_KEXEC: { + static const char * const table[_MANAGER_EXIT_CODE_MAX] = { + [MANAGER_EXIT] = "exit", + [MANAGER_REBOOT] = "reboot", + [MANAGER_POWEROFF] = "poweroff", + [MANAGER_HALT] = "halt", + [MANAGER_KEXEC] = "kexec" + }; + + assert_se(shutdown_verb = table[m->exit_code]); + arm_reboot_watchdog = m->exit_code == MANAGER_REBOOT; + + log_notice("Shutting down."); + goto finish; + } + + default: + assert_not_reached("Unknown exit code."); + } + } + +finish: + pager_close(); + + if (m) + arg_shutdown_watchdog = m->shutdown_watchdog; + + m = manager_free(m); + + for (j = 0; j < ELEMENTSOF(arg_default_rlimit); j++) + arg_default_rlimit[j] = mfree(arg_default_rlimit[j]); + + arg_default_unit = mfree(arg_default_unit); + arg_join_controllers = strv_free_free(arg_join_controllers); + arg_default_environment = strv_free(arg_default_environment); + arg_syscall_archs = set_free(arg_syscall_archs); + + mac_selinux_finish(); + + if (reexecute) { + const char **args; + unsigned i, args_size; + + /* Close and disarm the watchdog, so that the new + * instance can reinitialize it, but doesn't get + * rebooted while we do that */ + watchdog_close(true); + + /* Reset the RLIMIT_NOFILE to the kernel default, so + * that the new systemd can pass the kernel default to + * its child processes */ + if (saved_rlimit_nofile.rlim_cur > 0) + (void) setrlimit(RLIMIT_NOFILE, &saved_rlimit_nofile); + + if (switch_root_dir) { + /* Kill all remaining processes from the + * initrd, but don't wait for them, so that we + * can handle the SIGCHLD for them after + * deserializing. */ + broadcast_signal(SIGTERM, false, true); + + /* And switch root with MS_MOVE, because we remove the old directory afterwards and detach it. */ + r = switch_root(switch_root_dir, "/mnt", true, MS_MOVE); + if (r < 0) + log_error_errno(r, "Failed to switch root, trying to continue: %m"); + } + + args_size = MAX(6, argc+1); + args = newa(const char*, args_size); + + if (!switch_root_init) { + char sfd[DECIMAL_STR_MAX(int) + 1]; + + /* First try to spawn ourselves with the right + * path, and with full serialization. We do + * this only if the user didn't specify an + * explicit init to spawn. */ + + assert(arg_serialization); + assert(fds); + + xsprintf(sfd, "%i", fileno(arg_serialization)); + + i = 0; + args[i++] = SYSTEMD_BINARY_PATH; + if (switch_root_dir) + args[i++] = "--switched-root"; + args[i++] = arg_system ? "--system" : "--user"; + args[i++] = "--deserialize"; + args[i++] = sfd; + args[i++] = NULL; + + /* do not pass along the environment we inherit from the kernel or initrd */ + if (switch_root_dir) + (void) clearenv(); + + assert(i <= args_size); + + /* + * We want valgrind to print its memory usage summary before reexecution. + * Valgrind won't do this is on its own on exec(), but it will do it on exit(). + * Hence, to ensure we get a summary here, fork() off a child, let it exit() cleanly, + * so that it prints the summary, and wait() for it in the parent, before proceeding into the exec(). + */ + valgrind_summary_hack(); + + (void) execv(args[0], (char* const*) args); + } + + /* Try the fallback, if there is any, without any + * serialization. We pass the original argv[] and + * envp[]. (Well, modulo the ordering changes due to + * getopt() in argv[], and some cleanups in envp[], + * but let's hope that doesn't matter.) */ + + arg_serialization = safe_fclose(arg_serialization); + fds = fdset_free(fds); + + /* Reopen the console */ + (void) make_console_stdio(); + + for (j = 1, i = 1; j < (unsigned) argc; j++) + args[i++] = argv[j]; + args[i++] = NULL; + assert(i <= args_size); + + /* Reenable any blocked signals, especially important + * if we switch from initial ramdisk to init=... */ + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + if (switch_root_init) { + args[0] = switch_root_init; + (void) execv(args[0], (char* const*) args); + log_warning_errno(errno, "Failed to execute configured init, trying fallback: %m"); + } + + args[0] = "/sbin/init"; + (void) execv(args[0], (char* const*) args); + + if (errno == ENOENT) { + log_warning("No /sbin/init, trying fallback"); + + args[0] = "/bin/sh"; + args[1] = NULL; + (void) execv(args[0], (char* const*) args); + log_error_errno(errno, "Failed to execute /bin/sh, giving up: %m"); + } else + log_warning_errno(errno, "Failed to execute /sbin/init, giving up: %m"); + } + + arg_serialization = safe_fclose(arg_serialization); + fds = fdset_free(fds); + +#ifdef HAVE_VALGRIND_VALGRIND_H + /* If we are PID 1 and running under valgrind, then let's exit + * here explicitly. valgrind will only generate nice output on + * exit(), not on exec(), hence let's do the former not the + * latter here. */ + if (getpid() == 1 && RUNNING_ON_VALGRIND) + return 0; +#endif + + if (shutdown_verb) { + char log_level[DECIMAL_STR_MAX(int) + 1]; + char exit_code[DECIMAL_STR_MAX(uint8_t) + 1]; + const char* command_line[11] = { + SYSTEMD_SHUTDOWN_BINARY_PATH, + shutdown_verb, + "--log-level", log_level, + "--log-target", + }; + unsigned pos = 5; + _cleanup_strv_free_ char **env_block = NULL; + + assert(command_line[pos] == NULL); + env_block = strv_copy(environ); + + xsprintf(log_level, "%d", log_get_max_level()); + + switch (log_get_target()) { + + case LOG_TARGET_KMSG: + case LOG_TARGET_JOURNAL_OR_KMSG: + case LOG_TARGET_SYSLOG_OR_KMSG: + command_line[pos++] = "kmsg"; + break; + + case LOG_TARGET_NULL: + command_line[pos++] = "null"; + break; + + case LOG_TARGET_CONSOLE: + default: + command_line[pos++] = "console"; + break; + }; + + if (log_get_show_color()) + command_line[pos++] = "--log-color"; + + if (log_get_show_location()) + command_line[pos++] = "--log-location"; + + if (streq(shutdown_verb, "exit")) { + command_line[pos++] = "--exit-code"; + command_line[pos++] = exit_code; + xsprintf(exit_code, "%d", retval); + } + + assert(pos < ELEMENTSOF(command_line)); + + if (arm_reboot_watchdog && arg_shutdown_watchdog > 0 && arg_shutdown_watchdog != USEC_INFINITY) { + char *e; + + /* If we reboot let's set the shutdown + * watchdog and tell the shutdown binary to + * repeatedly ping it */ + r = watchdog_set_timeout(&arg_shutdown_watchdog); + watchdog_close(r < 0); + + /* Tell the binary how often to ping, ignore failure */ + if (asprintf(&e, "WATCHDOG_USEC="USEC_FMT, arg_shutdown_watchdog) > 0) + (void) strv_push(&env_block, e); + } else + watchdog_close(true); + + /* Avoid the creation of new processes forked by the + * kernel; at this point, we will not listen to the + * signals anyway */ + if (detect_container() <= 0) + (void) cg_uninstall_release_agent(SYSTEMD_CGROUP_CONTROLLER); + + execve(SYSTEMD_SHUTDOWN_BINARY_PATH, (char **) command_line, env_block); + log_error_errno(errno, "Failed to execute shutdown binary, %s: %m", + getpid() == 1 ? "freezing" : "quitting"); + } + + if (getpid() == 1) { + if (error_message) + manager_status_printf(NULL, STATUS_TYPE_EMERGENCY, + ANSI_HIGHLIGHT_RED "!!!!!!" ANSI_NORMAL, + "%s, freezing.", error_message); + freeze_or_reboot(); + } + + return retval; +} diff --git a/src/grp-system/systemd/org.freedesktop.systemd1.conf b/src/grp-system/systemd/org.freedesktop.systemd1.conf new file mode 100644 index 0000000000..3c64f20872 --- /dev/null +++ b/src/grp-system/systemd/org.freedesktop.systemd1.conf @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/grp-system/systemd/org.freedesktop.systemd1.policy.in.in b/src/grp-system/systemd/org.freedesktop.systemd1.policy.in.in new file mode 100644 index 0000000000..cc39a9e1c3 --- /dev/null +++ b/src/grp-system/systemd/org.freedesktop.systemd1.policy.in.in @@ -0,0 +1,70 @@ + + + + + + + + The systemd Project + http://www.freedesktop.org/wiki/Software/systemd + + + <_description>Send passphrase back to system + <_message>Authentication is required to send the entered passphrase back to the system. + + no + no + auth_admin_keep + + @rootlibexecdir@/systemd-reply-password + + + + <_description>Manage system services or other units + <_message>Authentication is required to manage system services or other units. + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Manage system service or unit files + <_message>Authentication is required to manage system service or unit files. + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Set or unset system and service manager environment variables + <_message>Authentication is required to set or unset system and service manager environment variables. + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Reload the systemd state + <_message>Authentication is required to reload the systemd state. + + auth_admin + auth_admin + auth_admin_keep + + + + diff --git a/src/grp-system/systemd/org.freedesktop.systemd1.service b/src/grp-system/systemd/org.freedesktop.systemd1.service new file mode 100644 index 0000000000..d4df3e93a2 --- /dev/null +++ b/src/grp-system/systemd/org.freedesktop.systemd1.service @@ -0,0 +1,11 @@ +# This file is part of systemd. +# +# 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. + +[D-BUS Service] +Name=org.freedesktop.systemd1 +Exec=/bin/false +User=root diff --git a/src/grp-system/systemd/system.conf b/src/grp-system/systemd/system.conf new file mode 100644 index 0000000000..db8b7acd78 --- /dev/null +++ b/src/grp-system/systemd/system.conf @@ -0,0 +1,61 @@ +# This file is part of systemd. +# +# 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. +# +# Entries in this file show the compile time defaults. +# You can change settings by editing this file. +# Defaults can be restored by simply deleting this file. +# +# See systemd-system.conf(5) for details. + +[Manager] +#LogLevel=info +#LogTarget=journal-or-kmsg +#LogColor=yes +#LogLocation=no +#DumpCore=yes +#ShowStatus=yes +#CrashChangeVT=no +#CrashShell=no +#CrashReboot=no +#CPUAffinity=1 2 +#JoinControllers=cpu,cpuacct net_cls,net_prio +#RuntimeWatchdogSec=0 +#ShutdownWatchdogSec=10min +#CapabilityBoundingSet= +#SystemCallArchitectures= +#TimerSlackNSec= +#DefaultTimerAccuracySec=1min +#DefaultStandardOutput=journal +#DefaultStandardError=inherit +#DefaultTimeoutStartSec=90s +#DefaultTimeoutStopSec=90s +#DefaultRestartSec=100ms +#DefaultStartLimitIntervalSec=10s +#DefaultStartLimitBurst=5 +#DefaultEnvironment= +#DefaultCPUAccounting=no +#DefaultIOAccounting=no +#DefaultBlockIOAccounting=no +#DefaultMemoryAccounting=no +#DefaultTasksAccounting=yes +#DefaultTasksMax=512 +#DefaultLimitCPU= +#DefaultLimitFSIZE= +#DefaultLimitDATA= +#DefaultLimitSTACK= +#DefaultLimitCORE= +#DefaultLimitRSS= +#DefaultLimitNOFILE= +#DefaultLimitAS= +#DefaultLimitNPROC= +#DefaultLimitMEMLOCK= +#DefaultLimitLOCKS= +#DefaultLimitSIGPENDING= +#DefaultLimitMSGQUEUE= +#DefaultLimitNICE= +#DefaultLimitRTPRIO= +#DefaultLimitRTTIME= diff --git a/src/grp-system/systemd/systemd.pc.in b/src/grp-system/systemd/systemd.pc.in new file mode 100644 index 0000000000..ac52b30dd3 --- /dev/null +++ b/src/grp-system/systemd/systemd.pc.in @@ -0,0 +1,34 @@ +# This file is part of systemd. +# +# 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. + +prefix=@prefix@ +systemdutildir=@rootlibexecdir@ +systemdsystemunitdir=@systemunitdir@ +systemdsystempresetdir=@systempresetdir@ +systemduserunitdir=@userunitdir@ +systemduserpresetdir=@userpresetdir@ +systemdsystemconfdir=@pkgsysconfdir@/system +systemduserconfdir=@pkgsysconfdir@/user +systemdsystemunitpath=${systemdsystemconfdir}:/etc/systemd/system:/run/systemd/system:/usr/local/lib/systemd/system:${systemdsystemunitdir}:/usr/lib/systemd/system:/lib/systemd/system +systemduserunitpath=${systemduserconfdir}:/etc/systemd/user:/run/systemd/user:/usr/local/lib/systemd/user:/usr/local/share/systemd/user:${systemduserunitdir}:/usr/lib/systemd/user:/usr/share/systemd/user +systemdsystemgeneratordir=@systemgeneratordir@ +systemdusergeneratordir=@usergeneratordir@ +systemdsleepdir=@systemsleepdir@ +systemdshutdowndir=@systemshutdowndir@ +tmpfilesdir=@tmpfilesdir@ +sysusersdir=@sysusersdir@ +sysctldir=@sysctldir@ +binfmtdir=@binfmtdir@ +modulesloaddir=@modulesloaddir@ +catalogdir=@catalogdir@ +systemuidmax=@systemuidmax@ +systemgidmax=@systemgidmax@ + +Name: systemd +Description: systemd System and Service Manager +URL: @PACKAGE_URL@ +Version: @PACKAGE_VERSION@ diff --git a/src/grp-system/systemd/triggers.systemd.in b/src/grp-system/systemd/triggers.systemd.in new file mode 100644 index 0000000000..0d8c303136 --- /dev/null +++ b/src/grp-system/systemd/triggers.systemd.in @@ -0,0 +1,66 @@ +# -*- Mode: rpm-spec; indent-tabs-mode: nil -*- */ +# +# This file is part of systemd. +# +# Copyright 2015 Zbigniew Jędrzejewski-Szmek +# +# 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 . + +# The contents of this are an example to be copied into systemd.spec. +# +# Minimum rpm version supported: 4.13.0 + +%transfiletriggerin -P 900900 -p -- @systemunitdir@ /etc/systemd/system +-- This script will run after any package is initially installed or +-- upgraded. We care about the case where a package is initially +-- installed, because other cases are covered by the *un scriptlets, +-- so sometimes we will reload needlessly. + +pid = posix.fork() +if pid == 0 then + assert(posix.exec("%{_bindir}/systemctl", "daemon-reload")) +elseif pid > 0 then + posix.wait(pid) +end + +%transfiletriggerun -p -- @systemunitdir@ /etc/systemd/system +-- On removal, we need to run daemon-reload after any units have been +-- removed. %transfiletriggerpostun would be ideal, but it does not get +-- executed for some reason. +-- On upgrade, we need to run daemon-reload after any new unit files +-- have been installed, but before %postun scripts in packages get +-- executed. %transfiletriggerun gets the right list of files +-- but it is invoked too early (before changes happen). +-- %filetriggerpostun happens at the right time, but it fires for +-- every package. +-- To execute the reload at the right time, we create a state +-- file in %transfiletriggerun and execute the daemon-reload in +-- the first %filetriggerpostun. + +posix.mkdir("%{_localstatedir}/lib") +posix.mkdir("%{_localstatedir}/lib/rpm-state") +posix.mkdir("%{_localstatedir}/lib/rpm-state/systemd") +io.open("%{_localstatedir}/lib/rpm-state/systemd/needs-reload", "w") + +%filetriggerpostun -P 1000100 -p -- @systemunitdir@ /etc/systemd/system +if posix.access("%{_localstatedir}/lib/rpm-state/systemd/needs-reload") then + posix.unlink("%{_localstatedir}/lib/rpm-state/systemd/needs-reload") + posix.rmdir("%{_localstatedir}/lib/rpm-state/systemd") + pid = posix.fork() + if pid == 0 then + assert(posix.exec("%{_bindir}/systemctl", "daemon-reload")) + elseif pid > 0 then + posix.wait(pid) + end +end diff --git a/src/grp-system/systemd/user.conf b/src/grp-system/systemd/user.conf new file mode 100644 index 0000000000..b427f1ef6d --- /dev/null +++ b/src/grp-system/systemd/user.conf @@ -0,0 +1,44 @@ +# This file is part of systemd. +# +# 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. +# +# You can override the directives in this file by creating files in +# /etc/systemd/user.conf.d/*.conf. +# +# See systemd-user.conf(5) for details + +[Manager] +#LogLevel=info +#LogTarget=console +#LogColor=yes +#LogLocation=no +#SystemCallArchitectures= +#TimerSlackNSec= +#DefaultTimerAccuracySec=1min +#DefaultStandardOutput=inherit +#DefaultStandardError=inherit +#DefaultTimeoutStartSec=90s +#DefaultTimeoutStopSec=90s +#DefaultRestartSec=100ms +#DefaultStartLimitIntervalSec=10s +#DefaultStartLimitBurst=5 +#DefaultEnvironment= +#DefaultLimitCPU= +#DefaultLimitFSIZE= +#DefaultLimitDATA= +#DefaultLimitSTACK= +#DefaultLimitCORE= +#DefaultLimitRSS= +#DefaultLimitNOFILE= +#DefaultLimitAS= +#DefaultLimitNPROC= +#DefaultLimitMEMLOCK= +#DefaultLimitLOCKS= +#DefaultLimitSIGPENDING= +#DefaultLimitMSGQUEUE= +#DefaultLimitNICE= +#DefaultLimitRTPRIO= +#DefaultLimitRTTIME= diff --git a/src/grp-timedate/systemd-timedated/.gitignore b/src/grp-timedate/systemd-timedated/.gitignore new file mode 100644 index 0000000000..48757f0968 --- /dev/null +++ b/src/grp-timedate/systemd-timedated/.gitignore @@ -0,0 +1 @@ +org.freedesktop.timedate1.policy diff --git a/src/grp-timedate/systemd-timedated/Makefile b/src/grp-timedate/systemd-timedated/Makefile new file mode 100644 index 0000000000..b3288b22b1 --- /dev/null +++ b/src/grp-timedate/systemd-timedated/Makefile @@ -0,0 +1,65 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_TIMEDATED),) +systemd_timedated_SOURCES = \ + src/timedate/timedated.c + +systemd_timedated_LDADD = \ + libshared.la + +libexec_PROGRAMS += \ + systemd-timedated + +dist_dbussystemservice_DATA += \ + src/timedate/org.freedesktop.timedate1.service + +dist_dbuspolicy_DATA += \ + src/timedate/org.freedesktop.timedate1.conf + +nodist_systemunit_DATA += \ + units/systemd-timedated.service + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.timedate1.busname + +polkitpolicy_files += \ + src/timedate/org.freedesktop.timedate1.policy + +SYSTEM_UNIT_ALIASES += \ + systemd-timedated.service dbus-org.freedesktop.timedate1.service + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.timedate1.busname + +endif # ENABLE_TIMEDATED + +polkitpolicy_in_files += \ + src/timedate/org.freedesktop.timedate1.policy.in + +EXTRA_DIST += \ + units/systemd-timedated.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-timedate/systemd-timedated/org.freedesktop.timedate1.conf b/src/grp-timedate/systemd-timedated/org.freedesktop.timedate1.conf new file mode 100644 index 0000000000..36557d5841 --- /dev/null +++ b/src/grp-timedate/systemd-timedated/org.freedesktop.timedate1.conf @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/grp-timedate/systemd-timedated/org.freedesktop.timedate1.policy.in b/src/grp-timedate/systemd-timedated/org.freedesktop.timedate1.policy.in new file mode 100644 index 0000000000..aa30b70831 --- /dev/null +++ b/src/grp-timedate/systemd-timedated/org.freedesktop.timedate1.policy.in @@ -0,0 +1,62 @@ + + + + + + + + The systemd Project + http://www.freedesktop.org/wiki/Software/systemd + + + <_description>Set system time + <_message>Authentication is required to set the system time. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + org.freedesktop.timedate1.set-timezone org.freedesktop.timedate1.set-ntp + + + + <_description>Set system timezone + <_message>Authentication is required to set the system timezone. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + + <_description>Set RTC to local timezone or UTC + <_message>Authentication is required to control whether + the RTC stores the local or UTC time. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + + <_description>Turn network time synchronization on or off + <_message>Authentication is required to control whether + network time synchronization shall be enabled. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + diff --git a/src/grp-timedate/systemd-timedated/org.freedesktop.timedate1.service b/src/grp-timedate/systemd-timedated/org.freedesktop.timedate1.service new file mode 100644 index 0000000000..875f4bec78 --- /dev/null +++ b/src/grp-timedate/systemd-timedated/org.freedesktop.timedate1.service @@ -0,0 +1,12 @@ +# This file is part of systemd. +# +# 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. + +[D-BUS Service] +Name=org.freedesktop.timedate1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.timedate1.service diff --git a/src/grp-timedate/systemd-timedated/timedated.c b/src/grp-timedate/systemd-timedated/timedated.c new file mode 100644 index 0000000000..51a13fcf49 --- /dev/null +++ b/src/grp-timedate/systemd-timedated/timedated.c @@ -0,0 +1,747 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include + +#include +#include +#include + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-error.h" +#include "bus-util.h" +#include "clock-util.h" +#include "def.h" +#include "fileio-label.h" +#include "fs-util.h" +#include "path-util.h" +#include "selinux-util.h" +#include "strv.h" +#include "user-util.h" +#include "util.h" + +#define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n" +#define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n" + +static BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map timedated_errors[] = { + SD_BUS_ERROR_MAP("org.freedesktop.timedate1.NoNTPSupport", EOPNOTSUPP), + SD_BUS_ERROR_MAP_END +}; + +typedef struct Context { + char *zone; + bool local_rtc; + bool can_ntp; + bool use_ntp; + Hashmap *polkit_registry; +} Context; + +static void context_free(Context *c) { + assert(c); + + free(c->zone); + bus_verify_polkit_async_registry_free(c->polkit_registry); +} + +static int context_read_data(Context *c) { + _cleanup_free_ char *t = NULL; + int r; + + assert(c); + + r = get_timezone(&t); + if (r == -EINVAL) + log_warning_errno(r, "/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/."); + else if (r < 0) + log_warning_errno(r, "Failed to get target of /etc/localtime: %m"); + + free(c->zone); + c->zone = t; + t = NULL; + + c->local_rtc = clock_is_localtime(NULL) > 0; + + return 0; +} + +static int context_write_data_timezone(Context *c) { + _cleanup_free_ char *p = NULL; + int r = 0; + + assert(c); + + if (isempty(c->zone)) { + if (unlink("/etc/localtime") < 0 && errno != ENOENT) + r = -errno; + + return r; + } + + p = strappend("../usr/share/zoneinfo/", c->zone); + if (!p) + return log_oom(); + + r = symlink_atomic(p, "/etc/localtime"); + if (r < 0) + return r; + + return 0; +} + +static int context_write_data_local_rtc(Context *c) { + int r; + _cleanup_free_ char *s = NULL, *w = NULL; + + assert(c); + + r = read_full_file("/etc/adjtime", &s, NULL); + if (r < 0) { + if (r != -ENOENT) + return r; + + if (!c->local_rtc) + return 0; + + w = strdup(NULL_ADJTIME_LOCAL); + if (!w) + return -ENOMEM; + } else { + char *p; + const char *e = "\n"; /* default if there is less than 3 lines */ + const char *prepend = ""; + size_t a, b; + + p = strchrnul(s, '\n'); + if (*p == '\0') + /* only one line, no \n terminator */ + prepend = "\n0\n"; + else if (p[1] == '\0') { + /* only one line, with \n terminator */ + ++p; + prepend = "0\n"; + } else { + p = strchr(p+1, '\n'); + if (!p) { + /* only two lines, no \n terminator */ + prepend = "\n"; + p = s + strlen(s); + } else { + char *end; + /* third line might have a \n terminator or not */ + p++; + end = strchr(p, '\n'); + /* if we actually have a fourth line, use that as suffix "e", otherwise the default \n */ + if (end) + e = end; + } + } + + a = p - s; + b = strlen(e); + + w = new(char, a + (c->local_rtc ? 5 : 3) + strlen(prepend) + b + 1); + if (!w) + return -ENOMEM; + + *(char*) mempcpy(stpcpy(stpcpy(mempcpy(w, s, a), prepend), c->local_rtc ? "LOCAL" : "UTC"), e, b) = 0; + + if (streq(w, NULL_ADJTIME_UTC)) { + if (unlink("/etc/adjtime") < 0) + if (errno != ENOENT) + return -errno; + + return 0; + } + } + + mac_selinux_init(); + return write_string_file_atomic_label("/etc/adjtime", w); +} + +static int context_read_ntp(Context *c, sd_bus *bus) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *s; + int r; + + assert(c); + assert(bus); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnitFileState", + &error, + &reply, + "s", + "systemd-timesyncd.service"); + + if (r < 0) { + if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND) || + sd_bus_error_has_name(&error, "org.freedesktop.systemd1.LoadFailed") || + sd_bus_error_has_name(&error, "org.freedesktop.systemd1.NoSuchUnit")) + return 0; + + return r; + } + + r = sd_bus_message_read(reply, "s", &s); + if (r < 0) + return r; + + c->can_ntp = true; + c->use_ntp = STR_IN_SET(s, "enabled", "enabled-runtime"); + + return 0; +} + +static int context_start_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) { + int r; + + assert(bus); + assert(error); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + enabled ? "StartUnit" : "StopUnit", + error, + NULL, + "ss", + "systemd-timesyncd.service", + "replace"); + if (r < 0) { + if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) || + sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") || + sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit")) + return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported."); + + return r; + } + + return 0; +} + +static int context_enable_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) { + int r; + + assert(bus); + assert(error); + + if (enabled) + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "EnableUnitFiles", + error, + NULL, + "asbb", 1, + "systemd-timesyncd.service", + false, true); + else + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "DisableUnitFiles", + error, + NULL, + "asb", 1, + "systemd-timesyncd.service", + false); + + if (r < 0) { + if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND)) + return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported."); + + return r; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Reload", + error, + NULL, + NULL); + if (r < 0) + return r; + + return 0; +} + +static int property_get_rtc_time( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + struct tm tm; + usec_t t; + int r; + + zero(tm); + r = clock_get_hwclock(&tm); + if (r == -EBUSY) { + log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp."); + t = 0; + } else if (r == -ENOENT) { + log_debug("/dev/rtc not found."); + t = 0; /* no RTC found */ + } else if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %m"); + else + t = (usec_t) timegm(&tm) * USEC_PER_SEC; + + return sd_bus_message_append(reply, "t", t); +} + +static int property_get_time( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME)); +} + +static int property_get_ntp_sync( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + return sd_bus_message_append(reply, "b", ntp_synced()); +} + +static int method_set_timezone(sd_bus_message *m, void *userdata, sd_bus_error *error) { + Context *c = userdata; + const char *z; + int interactive; + char *t; + int r; + + assert(m); + assert(c); + + r = sd_bus_message_read(m, "sb", &z, &interactive); + if (r < 0) + return r; + + if (!timezone_is_valid(z)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z); + + if (streq_ptr(z, c->zone)) + return sd_bus_reply_method_return(m, NULL); + + r = bus_verify_polkit_async( + m, + CAP_SYS_TIME, + "org.freedesktop.timedate1.set-timezone", + NULL, + interactive, + UID_INVALID, + &c->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + t = strdup(z); + if (!t) + return -ENOMEM; + + free(c->zone); + c->zone = t; + + /* 1. Write new configuration file */ + r = context_write_data_timezone(c); + if (r < 0) { + log_error_errno(r, "Failed to set time zone: %m"); + return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %m"); + } + + /* 2. Tell the kernel our timezone */ + clock_set_timezone(NULL); + + if (c->local_rtc) { + struct timespec ts; + struct tm *tm; + + /* 3. Sync RTC from system clock, with the new delta */ + assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); + assert_se(tm = localtime(&ts.tv_sec)); + clock_set_hwclock(tm); + } + + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE), + "TIMEZONE=%s", c->zone, + LOG_MESSAGE("Changed time zone to '%s'.", c->zone), + NULL); + + (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL); + + return sd_bus_reply_method_return(m, NULL); +} + +static int method_set_local_rtc(sd_bus_message *m, void *userdata, sd_bus_error *error) { + int lrtc, fix_system, interactive; + Context *c = userdata; + struct timespec ts; + int r; + + assert(m); + assert(c); + + r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive); + if (r < 0) + return r; + + if (lrtc == c->local_rtc) + return sd_bus_reply_method_return(m, NULL); + + r = bus_verify_polkit_async( + m, + CAP_SYS_TIME, + "org.freedesktop.timedate1.set-local-rtc", + NULL, + interactive, + UID_INVALID, + &c->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; + + c->local_rtc = lrtc; + + /* 1. Write new configuration file */ + r = context_write_data_local_rtc(c); + if (r < 0) { + log_error_errno(r, "Failed to set RTC to local/UTC: %m"); + return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %m"); + } + + /* 2. Tell the kernel our timezone */ + clock_set_timezone(NULL); + + /* 3. Synchronize clocks */ + assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); + + if (fix_system) { + struct tm tm; + + /* Sync system clock from RTC; first, + * initialize the timezone fields of + * struct tm. */ + if (c->local_rtc) + tm = *localtime(&ts.tv_sec); + else + tm = *gmtime(&ts.tv_sec); + + /* Override the main fields of + * struct tm, but not the timezone + * fields */ + if (clock_get_hwclock(&tm) >= 0) { + + /* And set the system clock + * with this */ + if (c->local_rtc) + ts.tv_sec = mktime(&tm); + else + ts.tv_sec = timegm(&tm); + + clock_settime(CLOCK_REALTIME, &ts); + } + + } else { + struct tm *tm; + + /* Sync RTC from system clock */ + if (c->local_rtc) + tm = localtime(&ts.tv_sec); + else + tm = gmtime(&ts.tv_sec); + + clock_set_hwclock(tm); + } + + log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC"); + + (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL); + + return sd_bus_reply_method_return(m, NULL); +} + +static int method_set_time(sd_bus_message *m, void *userdata, sd_bus_error *error) { + int relative, interactive; + Context *c = userdata; + int64_t utc; + struct timespec ts; + usec_t start; + struct tm* tm; + int r; + + assert(m); + assert(c); + + if (c->use_ntp) + return sd_bus_error_setf(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled"); + + /* this only gets used if dbus does not provide a timestamp */ + start = now(CLOCK_MONOTONIC); + + r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive); + if (r < 0) + return r; + + if (!relative && utc <= 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time"); + + if (relative && utc == 0) + return sd_bus_reply_method_return(m, NULL); + + if (relative) { + usec_t n, x; + + n = now(CLOCK_REALTIME); + x = n + utc; + + if ((utc > 0 && x < n) || + (utc < 0 && x > n)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow"); + + timespec_store(&ts, x); + } else + timespec_store(&ts, (usec_t) utc); + + r = bus_verify_polkit_async( + m, + CAP_SYS_TIME, + "org.freedesktop.timedate1.set-time", + NULL, + interactive, + UID_INVALID, + &c->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; + + /* adjust ts for time spent in program */ + r = sd_bus_message_get_monotonic_usec(m, &start); + /* when sd_bus_message_get_monotonic_usec() returns -ENODATA it does not modify &start */ + if (r < 0 && r != -ENODATA) + return r; + + timespec_store(&ts, timespec_load(&ts) + (now(CLOCK_MONOTONIC) - start)); + + /* Set system clock */ + if (clock_settime(CLOCK_REALTIME, &ts) < 0) { + log_error_errno(errno, "Failed to set local time: %m"); + return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m"); + } + + /* Sync down to RTC */ + if (c->local_rtc) + tm = localtime(&ts.tv_sec); + else + tm = gmtime(&ts.tv_sec); + clock_set_hwclock(tm); + + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_TIME_CHANGE), + "REALTIME="USEC_FMT, timespec_load(&ts), + LOG_MESSAGE("Changed local time to %s", ctime(&ts.tv_sec)), + NULL); + + return sd_bus_reply_method_return(m, NULL); +} + +static int method_set_ntp(sd_bus_message *m, void *userdata, sd_bus_error *error) { + int enabled, interactive; + Context *c = userdata; + int r; + + assert(m); + assert(c); + + r = sd_bus_message_read(m, "bb", &enabled, &interactive); + if (r < 0) + return r; + + if ((bool)enabled == c->use_ntp) + return sd_bus_reply_method_return(m, NULL); + + r = bus_verify_polkit_async( + m, + CAP_SYS_TIME, + "org.freedesktop.timedate1.set-ntp", + NULL, + interactive, + UID_INVALID, + &c->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; + + r = context_enable_ntp(sd_bus_message_get_bus(m), error, enabled); + if (r < 0) + return r; + + r = context_start_ntp(sd_bus_message_get_bus(m), error, enabled); + if (r < 0) + return r; + + c->use_ntp = enabled; + log_info("Set NTP to %s", enabled ? "enabled" : "disabled"); + + (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL); + + return sd_bus_reply_method_return(m, NULL); +} + +static const sd_bus_vtable timedate_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool, offsetof(Context, can_ntp), 0), + SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0), + SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0), + SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0), + SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END, +}; + +static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + assert(c); + assert(event); + assert(_bus); + + r = sd_bus_default_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to get system bus connection: %m"); + + r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c); + if (r < 0) + return log_error_errno(r, "Failed to register object: %m"); + + r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0); + if (r < 0) + return log_error_errno(r, "Failed to register name: %m"); + + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + *_bus = bus; + bus = NULL; + + return 0; +} + +int main(int argc, char *argv[]) { + Context context = {}; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc != 1) { + log_error("This program takes no arguments."); + r = -EINVAL; + goto finish; + } + + r = sd_event_default(&event); + if (r < 0) { + log_error_errno(r, "Failed to allocate event loop: %m"); + goto finish; + } + + sd_event_set_watchdog(event, true); + + r = connect_bus(&context, event, &bus); + if (r < 0) + goto finish; + + (void) sd_bus_negotiate_timestamp(bus, true); + + r = context_read_data(&context); + if (r < 0) { + log_error_errno(r, "Failed to read time zone data: %m"); + goto finish; + } + + r = context_read_ntp(&context, bus); + if (r < 0) { + log_error_errno(r, "Failed to determine whether NTP is enabled: %m"); + goto finish; + } + + r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL); + if (r < 0) { + log_error_errno(r, "Failed to run event loop: %m"); + goto finish; + } + +finish: + context_free(&context); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-timedate/timedatectl/Makefile b/src/grp-timedate/timedatectl/Makefile new file mode 100644 index 0000000000..c1be945443 --- /dev/null +++ b/src/grp-timedate/timedatectl/Makefile @@ -0,0 +1,42 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_TIMEDATED),) +timedatectl_SOURCES = \ + src/timedate/timedatectl.c + +timedatectl_LDADD = \ + libshared.la + +bin_PROGRAMS += \ + timedatectl + +dist_bashcompletion_data += \ + shell-completion/bash/timedatectl + +dist_zshcompletion_data += \ + shell-completion/zsh/_timedatectl +endif # ENABLE_TIMEDATED +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-timedate/timedatectl/timedatectl.c b/src/grp-timedate/timedatectl/timedatectl.c new file mode 100644 index 0000000000..1fd542fb49 --- /dev/null +++ b/src/grp-timedate/timedatectl/timedatectl.c @@ -0,0 +1,506 @@ +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + Copyright 2013 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 +#include +#include +#include + +#include + +#include "bus-error.h" +#include "bus-util.h" +#include "pager.h" +#include "parse-util.h" +#include "spawn-polkit-agent.h" +#include "strv.h" +#include "terminal-util.h" +#include "util.h" + +static bool arg_no_pager = false; +static bool arg_ask_password = true; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static char *arg_host = NULL; +static bool arg_adjust_system_clock = false; + +static void polkit_agent_open_if_enabled(void) { + + /* Open the polkit agent as a child process if necessary */ + if (!arg_ask_password) + return; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return; + + polkit_agent_open(); +} + +typedef struct StatusInfo { + usec_t time; + char *timezone; + + usec_t rtc_time; + bool rtc_local; + + bool ntp_enabled; + bool ntp_capable; + bool ntp_synced; +} StatusInfo; + +static void status_info_clear(StatusInfo *info) { + if (info) { + free(info->timezone); + zero(*info); + } +} + +static void print_status_info(const StatusInfo *i) { + char a[FORMAT_TIMESTAMP_MAX]; + struct tm tm; + time_t sec; + bool have_time = false; + const char *old_tz = NULL, *tz; + int r; + + assert(i); + + /* Save the old $TZ */ + tz = getenv("TZ"); + if (tz) + old_tz = strdupa(tz); + + /* Set the new $TZ */ + if (setenv("TZ", isempty(i->timezone) ? "UTC" : i->timezone, true) < 0) + log_warning_errno(errno, "Failed to set TZ environment variable, ignoring: %m"); + else + tzset(); + + if (i->time != 0) { + sec = (time_t) (i->time / USEC_PER_SEC); + have_time = true; + } else if (IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE)) { + sec = time(NULL); + have_time = true; + } else + log_warning("Could not get time from timedated and not operating locally, ignoring."); + + if (have_time) { + xstrftime(a, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)); + printf(" Local time: %.*s\n", (int) sizeof(a), a); + + xstrftime(a, "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)); + printf(" Universal time: %.*s\n", (int) sizeof(a), a); + } else { + printf(" Local time: %s\n", "n/a"); + printf(" Universal time: %s\n", "n/a"); + } + + if (i->rtc_time > 0) { + time_t rtc_sec; + + rtc_sec = (time_t) (i->rtc_time / USEC_PER_SEC); + xstrftime(a, "%a %Y-%m-%d %H:%M:%S", gmtime_r(&rtc_sec, &tm)); + printf(" RTC time: %.*s\n", (int) sizeof(a), a); + } else + printf(" RTC time: %s\n", "n/a"); + + if (have_time) + xstrftime(a, "%Z, %z", localtime_r(&sec, &tm)); + + /* Restore the $TZ */ + if (old_tz) + r = setenv("TZ", old_tz, true); + else + r = unsetenv("TZ"); + if (r < 0) + log_warning_errno(errno, "Failed to set TZ environment variable, ignoring: %m"); + else + tzset(); + + printf(" Time zone: %s (%.*s)\n" + " Network time on: %s\n" + "NTP synchronized: %s\n" + " RTC in local TZ: %s\n", + strna(i->timezone), (int) sizeof(a), have_time ? a : "n/a", + i->ntp_capable ? yes_no(i->ntp_enabled) : "n/a", + yes_no(i->ntp_synced), + yes_no(i->rtc_local)); + + if (i->rtc_local) + fputs("\n" ANSI_HIGHLIGHT + "Warning: The system is configured to read the RTC time in the local time zone.\n" + " This mode can not be fully supported. It will create various problems\n" + " with time zone changes and daylight saving time adjustments. The RTC\n" + " time is never updated, it relies on external facilities to maintain it.\n" + " If at all possible, use RTC in UTC by calling\n" + " 'timedatectl set-local-rtc 0'." ANSI_NORMAL "\n", stdout); +} + +static int show_status(sd_bus *bus, char **args, unsigned n) { + _cleanup_(status_info_clear) StatusInfo info = {}; + static const struct bus_properties_map map[] = { + { "Timezone", "s", NULL, offsetof(StatusInfo, timezone) }, + { "LocalRTC", "b", NULL, offsetof(StatusInfo, rtc_local) }, + { "NTP", "b", NULL, offsetof(StatusInfo, ntp_enabled) }, + { "CanNTP", "b", NULL, offsetof(StatusInfo, ntp_capable) }, + { "NTPSynchronized", "b", NULL, offsetof(StatusInfo, ntp_synced) }, + { "TimeUSec", "t", NULL, offsetof(StatusInfo, time) }, + { "RTCTimeUSec", "t", NULL, offsetof(StatusInfo, rtc_time) }, + {} + }; + int r; + + assert(bus); + + r = bus_map_all_properties(bus, + "org.freedesktop.timedate1", + "/org/freedesktop/timedate1", + map, + &info); + if (r < 0) + return log_error_errno(r, "Failed to query server: %m"); + + print_status_info(&info); + + return r; +} + +static int set_time(sd_bus *bus, char **args, unsigned n) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + bool relative = false, interactive = arg_ask_password; + usec_t t; + int r; + + assert(args); + assert(n == 2); + + polkit_agent_open_if_enabled(); + + r = parse_timestamp(args[1], &t); + if (r < 0) { + log_error("Failed to parse time specification: %s", args[1]); + return r; + } + + r = sd_bus_call_method(bus, + "org.freedesktop.timedate1", + "/org/freedesktop/timedate1", + "org.freedesktop.timedate1", + "SetTime", + &error, + NULL, + "xbb", (int64_t)t, relative, interactive); + if (r < 0) + log_error("Failed to set time: %s", bus_error_message(&error, -r)); + + return r; +} + +static int set_timezone(sd_bus *bus, char **args, unsigned n) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(args); + assert(n == 2); + + polkit_agent_open_if_enabled(); + + r = sd_bus_call_method(bus, + "org.freedesktop.timedate1", + "/org/freedesktop/timedate1", + "org.freedesktop.timedate1", + "SetTimezone", + &error, + NULL, + "sb", args[1], arg_ask_password); + if (r < 0) + log_error("Failed to set time zone: %s", bus_error_message(&error, -r)); + + return r; +} + +static int set_local_rtc(sd_bus *bus, char **args, unsigned n) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r, b; + + assert(args); + assert(n == 2); + + polkit_agent_open_if_enabled(); + + b = parse_boolean(args[1]); + if (b < 0) { + log_error("Failed to parse local RTC setting: %s", args[1]); + return b; + } + + r = sd_bus_call_method(bus, + "org.freedesktop.timedate1", + "/org/freedesktop/timedate1", + "org.freedesktop.timedate1", + "SetLocalRTC", + &error, + NULL, + "bbb", b, arg_adjust_system_clock, arg_ask_password); + if (r < 0) + log_error("Failed to set local RTC: %s", bus_error_message(&error, -r)); + + return r; +} + +static int set_ntp(sd_bus *bus, char **args, unsigned n) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int b, r; + + assert(args); + assert(n == 2); + + polkit_agent_open_if_enabled(); + + b = parse_boolean(args[1]); + if (b < 0) { + log_error("Failed to parse NTP setting: %s", args[1]); + return b; + } + + r = sd_bus_call_method(bus, + "org.freedesktop.timedate1", + "/org/freedesktop/timedate1", + "org.freedesktop.timedate1", + "SetNTP", + &error, + NULL, + "bb", b, arg_ask_password); + if (r < 0) + log_error("Failed to set ntp: %s", bus_error_message(&error, -r)); + + return r; +} + +static int list_timezones(sd_bus *bus, char **args, unsigned n) { + _cleanup_strv_free_ char **zones = NULL; + int r; + + assert(args); + assert(n == 1); + + r = get_timezones(&zones); + if (r < 0) + return log_error_errno(r, "Failed to read list of time zones: %m"); + + pager_open(arg_no_pager, false); + strv_print(zones); + + return 0; +} + +static void help(void) { + printf("%s [OPTIONS...] COMMAND ...\n\n" + "Query or change system time and date settings.\n\n" + " -h --help Show this help message\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --no-ask-password Do not prompt for password\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --adjust-system-clock Adjust system clock when changing local RTC mode\n\n" + "Commands:\n" + " status Show current time settings\n" + " set-time TIME Set system time\n" + " set-timezone ZONE Set system time zone\n" + " list-timezones Show known time zones\n" + " set-local-rtc BOOL Control whether RTC is in local time\n" + " set-ntp BOOL Enable or disable network time synchronization\n", + program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_ADJUST_SYSTEM_CLOCK, + ARG_NO_ASK_PASSWORD + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + case ARG_ADJUST_SYSTEM_CLOCK: + arg_adjust_system_clock = true; + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) { + + static const struct { + const char* verb; + const enum { + MORE, + LESS, + EQUAL + } argc_cmp; + const int argc; + int (* const dispatch)(sd_bus *bus, char **args, unsigned n); + } verbs[] = { + { "status", LESS, 1, show_status }, + { "set-time", EQUAL, 2, set_time }, + { "set-timezone", EQUAL, 2, set_timezone }, + { "list-timezones", EQUAL, 1, list_timezones }, + { "set-local-rtc", EQUAL, 2, set_local_rtc }, + { "set-ntp", EQUAL, 2, set_ntp, }, + }; + + int left; + unsigned i; + + assert(argc >= 0); + assert(argv); + + left = argc - optind; + + if (left <= 0) + /* Special rule: no arguments means "status" */ + i = 0; + else { + if (streq(argv[optind], "help")) { + help(); + return 0; + } + + for (i = 0; i < ELEMENTSOF(verbs); i++) + if (streq(argv[optind], verbs[i].verb)) + break; + + if (i >= ELEMENTSOF(verbs)) { + log_error("Unknown operation %s", argv[optind]); + return -EINVAL; + } + } + + switch (verbs[i].argc_cmp) { + + case EQUAL: + if (left != verbs[i].argc) { + log_error("Invalid number of arguments."); + return -EINVAL; + } + + break; + + case MORE: + if (left < verbs[i].argc) { + log_error("Too few arguments."); + return -EINVAL; + } + + break; + + case LESS: + if (left > verbs[i].argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + break; + + default: + assert_not_reached("Unknown comparison operator."); + } + + return verbs[i].dispatch(bus, argv + optind, left); +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + setlocale(LC_ALL, ""); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = bus_connect_transport(arg_transport, arg_host, false, &bus); + if (r < 0) { + log_error_errno(r, "Failed to create bus connection: %m"); + goto finish; + } + + r = timedatectl_main(bus, argc, argv); + +finish: + pager_close(); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/hibernate-resume/Makefile b/src/hibernate-resume/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/hibernate-resume/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/hibernate-resume/hibernate-resume-generator.c b/src/hibernate-resume/hibernate-resume-generator.c deleted file mode 100644 index d7ee80d58f..0000000000 --- a/src/hibernate-resume/hibernate-resume-generator.c +++ /dev/null @@ -1,99 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Ivan Shapovalov - - 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 - -#include "alloc-util.h" -#include "fstab-util.h" -#include "log.h" -#include "mkdir.h" -#include "proc-cmdline.h" -#include "special.h" -#include "string-util.h" -#include "unit-name.h" -#include "util.h" - -static const char *arg_dest = "/tmp"; -static char *arg_resume_dev = NULL; - -static int parse_proc_cmdline_item(const char *key, const char *value) { - - if (streq(key, "resume") && value) { - free(arg_resume_dev); - arg_resume_dev = fstab_node_to_udev_node(value); - if (!arg_resume_dev) - return log_oom(); - } - - return 0; -} - -static int process_resume(void) { - _cleanup_free_ char *name = NULL, *lnk = NULL; - int r; - - if (!arg_resume_dev) - return 0; - - r = unit_name_from_path_instance("systemd-hibernate-resume", arg_resume_dev, ".service", &name); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - - lnk = strjoin(arg_dest, "/" SPECIAL_SYSINIT_TARGET ".wants/", name, NULL); - if (!lnk) - return log_oom(); - - mkdir_parents_label(lnk, 0755); - if (symlink(SYSTEM_DATA_UNIT_PATH "/systemd-hibernate-resume@.service", lnk) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); - - return 0; -} - -int main(int argc, char *argv[]) { - int r = 0; - - if (argc > 1 && argc != 4) { - log_error("This program takes three or no arguments."); - return EXIT_FAILURE; - } - - if (argc > 1) - arg_dest = argv[1]; - - log_set_target(LOG_TARGET_SAFE); - log_parse_environment(); - log_open(); - - umask(0022); - - /* Don't even consider resuming outside of initramfs. */ - if (!in_initrd()) - return EXIT_SUCCESS; - - r = parse_proc_cmdline(parse_proc_cmdline_item); - if (r < 0) - log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - - r = process_resume(); - free(arg_resume_dev); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/hibernate-resume/hibernate-resume.c b/src/hibernate-resume/hibernate-resume.c deleted file mode 100644 index 21df3c4461..0000000000 --- a/src/hibernate-resume/hibernate-resume.c +++ /dev/null @@ -1,82 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Ivan Shapovalov - - 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 -#include - -#include "alloc-util.h" -#include "fileio.h" -#include "log.h" -#include "util.h" - -int main(int argc, char *argv[]) { - struct stat st; - const char *device; - _cleanup_free_ char *major_minor = NULL; - int r; - - if (argc != 2) { - log_error("This program expects one argument."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - /* Refuse to run unless we are in an initrd() */ - if (!in_initrd()) - return EXIT_SUCCESS; - - device = argv[1]; - - if (stat(device, &st) < 0) { - log_error_errno(errno, "Failed to stat '%s': %m", device); - return EXIT_FAILURE; - } - - if (!S_ISBLK(st.st_mode)) { - log_error("Resume device '%s' is not a block device.", device); - return EXIT_FAILURE; - } - - if (asprintf(&major_minor, "%d:%d", major(st.st_rdev), minor(st.st_rdev)) < 0) { - log_oom(); - return EXIT_FAILURE; - } - - r = write_string_file("/sys/power/resume", major_minor, WRITE_STRING_FILE_CREATE); - if (r < 0) { - log_error_errno(r, "Failed to write '%s' to /sys/power/resume: %m", major_minor); - return EXIT_FAILURE; - } - - /* - * The write above shall not return. - * - * However, failed resume is a normal condition (may mean that there is - * no hibernation image). - */ - - log_info("Could not resume from '%s' (%s).", device, major_minor); - return EXIT_SUCCESS; -} diff --git a/src/hostname/Makefile b/src/hostname/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/hostname/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/hostname/Makefile b/src/hostname/Makefile new file mode 100644 index 0000000000..f601560f0a --- /dev/null +++ b/src/hostname/Makefile @@ -0,0 +1,79 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_HOSTNAMED),) +systemd_hostnamed_SOURCES = \ + src/hostname/hostnamed.c + +systemd_hostnamed_LDADD = \ + libshared.la + +libexec_PROGRAMS += \ + systemd-hostnamed + +nodist_systemunit_DATA += \ + units/systemd-hostnamed.service + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.hostname1.busname + +dist_dbuspolicy_DATA += \ + src/hostname/org.freedesktop.hostname1.conf + +dist_dbussystemservice_DATA += \ + src/hostname/org.freedesktop.hostname1.service + +polkitpolicy_files += \ + src/hostname/org.freedesktop.hostname1.policy + +SYSTEM_UNIT_ALIASES += \ + systemd-hostnamed.service dbus-org.freedesktop.hostname1.service + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.hostname1.busname + +hostnamectl_SOURCES = \ + src/hostname/hostnamectl.c + +hostnamectl_LDADD = \ + libshared.la + +bin_PROGRAMS += \ + hostnamectl + +dist_bashcompletion_data += \ + shell-completion/bash/hostnamectl + +dist_zshcompletion_data += \ + shell-completion/zsh/_hostnamectl +endif # ENABLE_HOSTNAMED + +polkitpolicy_in_files += \ + src/hostname/org.freedesktop.hostname1.policy.in + +EXTRA_DIST += \ + units/systemd-hostnamed.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index c16a324232..aabc3b30c0 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -23,8 +23,8 @@ #include #include -#include "sd-bus.h" -#include "sd-id128.h" +#include +#include #include "alloc-util.h" #include "architecture.h" diff --git a/src/hwdb/Makefile b/src/hwdb/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/hwdb/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/hwdb/hwdb.c b/src/hwdb/hwdb.c deleted file mode 100644 index 1160dacdf1..0000000000 --- a/src/hwdb/hwdb.c +++ /dev/null @@ -1,739 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 -#include -#include -#include - -#include "alloc-util.h" -#include "conf-files.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "hwdb-internal.h" -#include "hwdb-util.h" -#include "mkdir.h" -#include "strbuf.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" -#include "verbs.h" - -/* - * Generic udev properties, key/value database based on modalias strings. - * Uses a Patricia/radix trie to index all matches for efficient lookup. - */ - -static const char *arg_hwdb_bin_dir = "/etc/udev"; -static const char *arg_root = ""; - -static const char * const conf_file_dirs[] = { - "/etc/udev/hwdb.d", - UDEVLIBEXECDIR "/hwdb.d", - NULL -}; - -/* in-memory trie objects */ -struct trie { - struct trie_node *root; - struct strbuf *strings; - - size_t nodes_count; - size_t children_count; - size_t values_count; -}; - -struct trie_node { - /* prefix, common part for all children of this node */ - size_t prefix_off; - - /* sorted array of pointers to children nodes */ - struct trie_child_entry *children; - uint8_t children_count; - - /* sorted array of key/value pairs */ - struct trie_value_entry *values; - size_t values_count; -}; - -/* children array item with char (0-255) index */ -struct trie_child_entry { - uint8_t c; - struct trie_node *child; -}; - -/* value array item with key/value pairs */ -struct trie_value_entry { - size_t key_off; - size_t value_off; -}; - -static int trie_children_cmp(const void *v1, const void *v2) { - const struct trie_child_entry *n1 = v1; - const struct trie_child_entry *n2 = v2; - - return n1->c - n2->c; -} - -static int node_add_child(struct trie *trie, struct trie_node *node, struct trie_node *node_child, uint8_t c) { - struct trie_child_entry *child; - - /* extend array, add new entry, sort for bisection */ - child = realloc(node->children, (node->children_count + 1) * sizeof(struct trie_child_entry)); - if (!child) - return -ENOMEM; - - node->children = child; - trie->children_count++; - node->children[node->children_count].c = c; - node->children[node->children_count].child = node_child; - node->children_count++; - qsort(node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp); - trie->nodes_count++; - - return 0; -} - -static struct trie_node *node_lookup(const struct trie_node *node, uint8_t c) { - struct trie_child_entry *child; - struct trie_child_entry search; - - search.c = c; - child = bsearch(&search, node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp); - if (child) - return child->child; - return NULL; -} - -static void trie_node_cleanup(struct trie_node *node) { - size_t i; - - for (i = 0; i < node->children_count; i++) - trie_node_cleanup(node->children[i].child); - free(node->children); - free(node->values); - free(node); -} - -static void trie_free(struct trie *trie) { - if (!trie) - return; - - if (trie->root) - trie_node_cleanup(trie->root); - - strbuf_cleanup(trie->strings); - free(trie); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct trie*, trie_free); - -static int trie_values_cmp(const void *v1, const void *v2, void *arg) { - const struct trie_value_entry *val1 = v1; - const struct trie_value_entry *val2 = v2; - struct trie *trie = arg; - - return strcmp(trie->strings->buf + val1->key_off, - trie->strings->buf + val2->key_off); -} - -static int trie_node_add_value(struct trie *trie, struct trie_node *node, - const char *key, const char *value) { - ssize_t k, v; - struct trie_value_entry *val; - - k = strbuf_add_string(trie->strings, key, strlen(key)); - if (k < 0) - return k; - v = strbuf_add_string(trie->strings, value, strlen(value)); - if (v < 0) - return v; - - if (node->values_count) { - struct trie_value_entry search = { - .key_off = k, - .value_off = v, - }; - - val = xbsearch_r(&search, node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie); - if (val) { - /* replace existing earlier key with new value */ - val->value_off = v; - return 0; - } - } - - /* extend array, add new entry, sort for bisection */ - val = realloc(node->values, (node->values_count + 1) * sizeof(struct trie_value_entry)); - if (!val) - return -ENOMEM; - trie->values_count++; - node->values = val; - node->values[node->values_count].key_off = k; - node->values[node->values_count].value_off = v; - node->values_count++; - qsort_r(node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie); - return 0; -} - -static int trie_insert(struct trie *trie, struct trie_node *node, const char *search, - const char *key, const char *value) { - size_t i = 0; - int err = 0; - - for (;;) { - size_t p; - uint8_t c; - struct trie_node *child; - - for (p = 0; (c = trie->strings->buf[node->prefix_off + p]); p++) { - _cleanup_free_ char *s = NULL; - ssize_t off; - _cleanup_free_ struct trie_node *new_child = NULL; - - if (c == search[i + p]) - continue; - - /* split node */ - new_child = new0(struct trie_node, 1); - if (!new_child) - return -ENOMEM; - - /* move values from parent to child */ - new_child->prefix_off = node->prefix_off + p+1; - new_child->children = node->children; - new_child->children_count = node->children_count; - new_child->values = node->values; - new_child->values_count = node->values_count; - - /* update parent; use strdup() because the source gets realloc()d */ - s = strndup(trie->strings->buf + node->prefix_off, p); - if (!s) - return -ENOMEM; - - off = strbuf_add_string(trie->strings, s, p); - if (off < 0) - return off; - - node->prefix_off = off; - node->children = NULL; - node->children_count = 0; - node->values = NULL; - node->values_count = 0; - err = node_add_child(trie, node, new_child, c); - if (err < 0) - return err; - - new_child = NULL; /* avoid cleanup */ - break; - } - i += p; - - c = search[i]; - if (c == '\0') - return trie_node_add_value(trie, node, key, value); - - child = node_lookup(node, c); - if (!child) { - ssize_t off; - - /* new child */ - child = new0(struct trie_node, 1); - if (!child) - return -ENOMEM; - - off = strbuf_add_string(trie->strings, search + i+1, strlen(search + i+1)); - if (off < 0) { - free(child); - return off; - } - - child->prefix_off = off; - err = node_add_child(trie, node, child, c); - if (err < 0) { - free(child); - return err; - } - - return trie_node_add_value(trie, child, key, value); - } - - node = child; - i++; - } -} - -struct trie_f { - FILE *f; - struct trie *trie; - uint64_t strings_off; - - uint64_t nodes_count; - uint64_t children_count; - uint64_t values_count; -}; - -/* calculate the storage space for the nodes, children arrays, value arrays */ -static void trie_store_nodes_size(struct trie_f *trie, struct trie_node *node) { - uint64_t i; - - for (i = 0; i < node->children_count; i++) - trie_store_nodes_size(trie, node->children[i].child); - - trie->strings_off += sizeof(struct trie_node_f); - for (i = 0; i < node->children_count; i++) - trie->strings_off += sizeof(struct trie_child_entry_f); - for (i = 0; i < node->values_count; i++) - trie->strings_off += sizeof(struct trie_value_entry_f); -} - -static int64_t trie_store_nodes(struct trie_f *trie, struct trie_node *node) { - uint64_t i; - struct trie_node_f n = { - .prefix_off = htole64(trie->strings_off + node->prefix_off), - .children_count = node->children_count, - .values_count = htole64(node->values_count), - }; - struct trie_child_entry_f *children = NULL; - int64_t node_off; - - if (node->children_count) { - children = new0(struct trie_child_entry_f, node->children_count); - if (!children) - return -ENOMEM; - } - - /* post-order recursion */ - for (i = 0; i < node->children_count; i++) { - int64_t child_off; - - child_off = trie_store_nodes(trie, node->children[i].child); - if (child_off < 0) { - free(children); - return child_off; - } - children[i].c = node->children[i].c; - children[i].child_off = htole64(child_off); - } - - /* write node */ - node_off = ftello(trie->f); - fwrite(&n, sizeof(struct trie_node_f), 1, trie->f); - trie->nodes_count++; - - /* append children array */ - if (node->children_count) { - fwrite(children, sizeof(struct trie_child_entry_f), node->children_count, trie->f); - trie->children_count += node->children_count; - free(children); - } - - /* append values array */ - for (i = 0; i < node->values_count; i++) { - struct trie_value_entry_f v = { - .key_off = htole64(trie->strings_off + node->values[i].key_off), - .value_off = htole64(trie->strings_off + node->values[i].value_off), - }; - - fwrite(&v, sizeof(struct trie_value_entry_f), 1, trie->f); - trie->values_count++; - } - - return node_off; -} - -static int trie_store(struct trie *trie, const char *filename) { - struct trie_f t = { - .trie = trie, - }; - _cleanup_free_ char *filename_tmp = NULL; - int64_t pos; - int64_t root_off; - int64_t size; - struct trie_header_f h = { - .signature = HWDB_SIG, - .tool_version = htole64(atoi(VERSION)), - .header_size = htole64(sizeof(struct trie_header_f)), - .node_size = htole64(sizeof(struct trie_node_f)), - .child_entry_size = htole64(sizeof(struct trie_child_entry_f)), - .value_entry_size = htole64(sizeof(struct trie_value_entry_f)), - }; - int err; - - /* calculate size of header, nodes, children entries, value entries */ - t.strings_off = sizeof(struct trie_header_f); - trie_store_nodes_size(&t, trie->root); - - err = fopen_temporary(filename , &t.f, &filename_tmp); - if (err < 0) - return err; - fchmod(fileno(t.f), 0444); - - /* write nodes */ - err = fseeko(t.f, sizeof(struct trie_header_f), SEEK_SET); - if (err < 0) { - fclose(t.f); - unlink_noerrno(filename_tmp); - return -errno; - } - root_off = trie_store_nodes(&t, trie->root); - h.nodes_root_off = htole64(root_off); - pos = ftello(t.f); - h.nodes_len = htole64(pos - sizeof(struct trie_header_f)); - - /* write string buffer */ - fwrite(trie->strings->buf, trie->strings->len, 1, t.f); - h.strings_len = htole64(trie->strings->len); - - /* write header */ - size = ftello(t.f); - h.file_size = htole64(size); - err = fseeko(t.f, 0, SEEK_SET); - if (err < 0) { - fclose(t.f); - unlink_noerrno(filename_tmp); - return -errno; - } - fwrite(&h, sizeof(struct trie_header_f), 1, t.f); - err = ferror(t.f); - if (err) - err = -errno; - fclose(t.f); - if (err < 0 || rename(filename_tmp, filename) < 0) { - unlink_noerrno(filename_tmp); - return err < 0 ? err : -errno; - } - - log_debug("=== trie on-disk ==="); - log_debug("size: %8"PRIi64" bytes", size); - log_debug("header: %8zu bytes", sizeof(struct trie_header_f)); - log_debug("nodes: %8"PRIu64" bytes (%8"PRIu64")", - t.nodes_count * sizeof(struct trie_node_f), t.nodes_count); - log_debug("child pointers: %8"PRIu64" bytes (%8"PRIu64")", - t.children_count * sizeof(struct trie_child_entry_f), t.children_count); - log_debug("value pointers: %8"PRIu64" bytes (%8"PRIu64")", - t.values_count * sizeof(struct trie_value_entry_f), t.values_count); - log_debug("string store: %8zu bytes", trie->strings->len); - log_debug("strings start: %8"PRIu64, t.strings_off); - - return 0; -} - -static int insert_data(struct trie *trie, char **match_list, char *line, const char *filename) { - char *value, **entry; - - value = strchr(line, '='); - if (!value) { - log_error("Error, key/value pair expected but got '%s' in '%s':", line, filename); - return -EINVAL; - } - - value[0] = '\0'; - value++; - - /* libudev requires properties to start with a space */ - while (isblank(line[0]) && isblank(line[1])) - line++; - - if (line[0] == '\0' || value[0] == '\0') { - log_error("Error, empty key or value '%s' in '%s':", line, filename); - return -EINVAL; - } - - STRV_FOREACH(entry, match_list) - trie_insert(trie, trie->root, *entry, line, value); - - return 0; -} - -static int import_file(struct trie *trie, const char *filename) { - enum { - HW_NONE, - HW_MATCH, - HW_DATA, - } state = HW_NONE; - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - _cleanup_strv_free_ char **match_list = NULL; - char *match = NULL; - int r; - - f = fopen(filename, "re"); - if (!f) - return -errno; - - while (fgets(line, sizeof(line), f)) { - size_t len; - char *pos; - - /* comment line */ - if (line[0] == '#') - continue; - - /* strip trailing comment */ - pos = strchr(line, '#'); - if (pos) - pos[0] = '\0'; - - /* strip trailing whitespace */ - len = strlen(line); - while (len > 0 && isspace(line[len-1])) - len--; - line[len] = '\0'; - - switch (state) { - case HW_NONE: - if (len == 0) - break; - - if (line[0] == ' ') { - log_error("Error, MATCH expected but got '%s' in '%s':", line, filename); - break; - } - - /* start of record, first match */ - state = HW_MATCH; - - match = strdup(line); - if (!match) - return -ENOMEM; - - r = strv_consume(&match_list, match); - if (r < 0) - return r; - - break; - - case HW_MATCH: - if (len == 0) { - log_error("Error, DATA expected but got empty line in '%s':", filename); - state = HW_NONE; - strv_clear(match_list); - break; - } - - /* another match */ - if (line[0] != ' ') { - match = strdup(line); - if (!match) - return -ENOMEM; - - r = strv_consume(&match_list, match); - if (r < 0) - return r; - - break; - } - - /* first data */ - state = HW_DATA; - insert_data(trie, match_list, line, filename); - break; - - case HW_DATA: - /* end of record */ - if (len == 0) { - state = HW_NONE; - strv_clear(match_list); - break; - } - - if (line[0] != ' ') { - log_error("Error, DATA expected but got '%s' in '%s':", line, filename); - state = HW_NONE; - strv_clear(match_list); - break; - } - - insert_data(trie, match_list, line, filename); - break; - }; - } - - return 0; -} - -static int hwdb_query(int argc, char *argv[], void *userdata) { - _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; - const char *key, *value; - const char *modalias; - int r; - - assert(argc >= 2); - assert(argv); - - modalias = argv[1]; - - r = sd_hwdb_new(&hwdb); - if (r < 0) - return r; - - SD_HWDB_FOREACH_PROPERTY(hwdb, modalias, key, value) - printf("%s=%s\n", key, value); - - return 0; -} - -static int hwdb_update(int argc, char *argv[], void *userdata) { - _cleanup_free_ char *hwdb_bin = NULL; - _cleanup_(trie_freep) struct trie *trie = NULL; - char **files, **f; - int r; - - trie = new0(struct trie, 1); - if (!trie) - return -ENOMEM; - - /* string store */ - trie->strings = strbuf_new(); - if (!trie->strings) - return -ENOMEM; - - /* index */ - trie->root = new0(struct trie_node, 1); - if (!trie->root) - return -ENOMEM; - - trie->nodes_count++; - - r = conf_files_list_strv(&files, ".hwdb", arg_root, conf_file_dirs); - if (r < 0) - return log_error_errno(r, "failed to enumerate hwdb files: %m"); - - STRV_FOREACH(f, files) { - log_debug("reading file '%s'", *f); - import_file(trie, *f); - } - strv_free(files); - - strbuf_complete(trie->strings); - - log_debug("=== trie in-memory ==="); - log_debug("nodes: %8zu bytes (%8zu)", - trie->nodes_count * sizeof(struct trie_node), trie->nodes_count); - log_debug("children arrays: %8zu bytes (%8zu)", - trie->children_count * sizeof(struct trie_child_entry), trie->children_count); - log_debug("values arrays: %8zu bytes (%8zu)", - trie->values_count * sizeof(struct trie_value_entry), trie->values_count); - log_debug("strings: %8zu bytes", - trie->strings->len); - log_debug("strings incoming: %8zu bytes (%8zu)", - trie->strings->in_len, trie->strings->in_count); - log_debug("strings dedup'ed: %8zu bytes (%8zu)", - trie->strings->dedup_len, trie->strings->dedup_count); - - hwdb_bin = strjoin(arg_root, "/", arg_hwdb_bin_dir, "/hwdb.bin", NULL); - if (!hwdb_bin) - return -ENOMEM; - - mkdir_parents(hwdb_bin, 0755); - r = trie_store(trie, hwdb_bin); - if (r < 0) - return log_error_errno(r, "Failure writing database %s: %m", hwdb_bin); - - return 0; -} - -static void help(void) { - printf("Usage: %s OPTIONS COMMAND\n\n" - "Update or query the hardware database.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --usr Generate in " UDEVLIBEXECDIR " instead of /etc/udev\n" - " -r --root=PATH Alternative root path in the filesystem\n\n" - "Commands:\n" - " update Update the hwdb database\n" - " query MODALIAS Query database and print result\n", - program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_USR, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "usr", no_argument, NULL, ARG_USR }, - { "root", required_argument, NULL, 'r' }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "ut:r:h", options, NULL)) >= 0) { - switch(c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_USR: - arg_hwdb_bin_dir = UDEVLIBEXECDIR; - break; - - case 'r': - arg_root = optarg; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unknown option"); - } - } - - return 1; -} - -static int hwdb_main(int argc, char *argv[]) { - const Verb verbs[] = { - { "update", 1, 1, 0, hwdb_update }, - { "query", 2, 2, 0, hwdb_query }, - {}, - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - -int main (int argc, char *argv[]) { - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = hwdb_main(argc, argv); - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/import/Makefile b/src/import/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/import/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/import/Makefile b/src/import/Makefile new file mode 100644 index 0000000000..a918dd5344 --- /dev/null +++ b/src/import/Makefile @@ -0,0 +1,192 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_IMPORTD),) + +ifneq ($(HAVE_LIBCURL),) +ifneq ($(HAVE_XZ),) +ifneq ($(HAVE_ZLIB),) +ifneq ($(HAVE_BZIP2),) +ifneq ($(HAVE_GCRYPT),) + +libexec_PROGRAMS += \ + systemd-importd \ + systemd-pull \ + systemd-import \ + systemd-export + +systemd_importd_SOURCES = \ + src/import/importd.c + +systemd_importd_CFLAGS = \ + $(AM_CFLAGS) \ + -D SYSTEMD_PULL_PATH=\"$(libexecdir)/systemd-pull\" \ + -D SYSTEMD_IMPORT_PATH=\"$(libexecdir)/systemd-import\" \ + -D SYSTEMD_EXPORT_PATH=\"$(libexecdir)/systemd-export\" + +systemd_importd_LDADD = \ + libshared.la + +systemd_pull_SOURCES = \ + src/import/pull.c \ + src/import/pull-raw.c \ + src/import/pull-raw.h \ + src/import/pull-tar.c \ + src/import/pull-tar.h \ + src/import/pull-job.c \ + src/import/pull-job.h \ + src/import/pull-common.c \ + src/import/pull-common.h \ + src/import/import-common.c \ + src/import/import-common.h \ + src/import/import-compress.c \ + src/import/import-compress.h \ + src/import/curl-util.c \ + src/import/curl-util.h \ + src/import/qcow2-util.c \ + src/import/qcow2-util.h + +systemd_pull_CFLAGS = \ + $(AM_CFLAGS) \ + $(LIBCURL_CFLAGS) \ + $(XZ_CFLAGS) \ + $(ZLIB_CFLAGS) \ + $(BZIP2_CFLAGS) \ + $(GCRYPT_CFLAGS) \ + -D VENDOR_KEYRING_PATH=\"$(libexecdir)/import-pubring.gpg\" \ + -D USER_KEYRING_PATH=\"$(pkgsysconfdir)/import-pubring.gpg\" + +systemd_pull_LDADD = \ + libshared.la \ + $(LIBCURL_LIBS) \ + $(XZ_LIBS) \ + $(ZLIB_LIBS) \ + $(BZIP2_LIBS) \ + $(GCRYPT_LIBS) + +systemd_import_SOURCES = \ + src/import/import.c \ + src/import/import-raw.c \ + src/import/import-raw.h \ + src/import/import-tar.c \ + src/import/import-tar.h \ + src/import/import-common.c \ + src/import/import-common.h \ + src/import/import-compress.c \ + src/import/import-compress.h \ + src/import/qcow2-util.c \ + src/import/qcow2-util.h + +systemd_import_CFLAGS = \ + $(AM_CFLAGS) \ + $(XZ_CFLAGS) \ + $(ZLIB_CFLAGS) \ + $(BZIP2_CFLAGS) + +systemd_import_LDADD = \ + libshared.la \ + $(XZ_LIBS) \ + $(ZLIB_LIBS) \ + $(BZIP2_LIBS) + +systemd_export_SOURCES = \ + src/import/export.c \ + src/import/export-tar.c \ + src/import/export-tar.h \ + src/import/export-raw.c \ + src/import/export-raw.h \ + src/import/import-common.c \ + src/import/import-common.h \ + src/import/import-compress.c \ + src/import/import-compress.h + +systemd_export_CFLAGS = \ + $(AM_CFLAGS) \ + $(XZ_CFLAGS) \ + $(ZLIB_CFLAGS) \ + $(BZIP2_CFLAGS) + +systemd_export_LDADD = \ + libshared.la \ + $(XZ_LIBS) \ + $(ZLIB_LIBS) \ + $(BZIP2_LIBS) + +dist_libexec_DATA = \ + src/import/import-pubring.gpg + +nodist_systemunit_DATA += \ + units/systemd-importd.service + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.import1.busname + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.import1.busname + +SYSTEM_UNIT_ALIASES += \ + systemd-importd.service dbus-org.freedesktop.import1.service + +dist_dbussystemservice_DATA += \ + src/import/org.freedesktop.import1.service + +dist_dbuspolicy_DATA += \ + src/import/org.freedesktop.import1.conf + +polkitpolicy_files += \ + src/import/org.freedesktop.import1.policy + +manual_tests += \ + test-qcow2 + +test_qcow2_SOURCES = \ + src/import/test-qcow2.c \ + src/import/qcow2-util.c \ + src/import/qcow2-util.h + +test_qcow2_CFLAGS = \ + $(AM_CFLAGS) \ + $(ZLIB_CFLAGS) + +test_qcow2_LDADD = \ + libshared.la \ + $(ZLIB_LIBS) + +endif # HAVE_GCRYPT +endif # HAVE_BZIP2 +endif # HAVE_ZLIB +endif # HAVE_XZ +endif # HAVE_LIBCURL + +endif # ENABLE_IMPORTD + +polkitpolicy_in_files += \ + src/import/org.freedesktop.import1.policy.in + +EXTRA_DIST += \ + units/systemd-importd.service.in + + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/import/curl-util.h b/src/import/curl-util.h index a758cc5640..2e71bd3b5d 100644 --- a/src/import/curl-util.h +++ b/src/import/curl-util.h @@ -22,7 +22,7 @@ #include #include -#include "sd-event.h" +#include #include "hashmap.h" diff --git a/src/import/export-raw.c b/src/import/export-raw.c index db06e11b87..658f835132 100644 --- a/src/import/export-raw.c +++ b/src/import/export-raw.c @@ -25,7 +25,7 @@ #include #undef basename -#include "sd-daemon.h" +#include #include "alloc-util.h" #include "btrfs-util.h" diff --git a/src/import/export-raw.h b/src/import/export-raw.h index 8e723d4908..c7ac134603 100644 --- a/src/import/export-raw.h +++ b/src/import/export-raw.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-event.h" +#include #include "import-compress.h" #include "macro.h" diff --git a/src/import/export-tar.c b/src/import/export-tar.c index d79c27f2d0..9c511984c1 100644 --- a/src/import/export-tar.c +++ b/src/import/export-tar.c @@ -17,7 +17,7 @@ along with systemd; If not, see . ***/ -#include "sd-daemon.h" +#include #include "alloc-util.h" #include "btrfs-util.h" diff --git a/src/import/export-tar.h b/src/import/export-tar.h index 1e3c8bb80c..50206cabb3 100644 --- a/src/import/export-tar.h +++ b/src/import/export-tar.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-event.h" +#include #include "import-compress.h" #include "macro.h" diff --git a/src/import/export.c b/src/import/export.c index cc98c33ef6..0a5efe5476 100644 --- a/src/import/export.c +++ b/src/import/export.c @@ -19,7 +19,7 @@ #include -#include "sd-event.h" +#include #include "alloc-util.h" #include "export-raw.h" diff --git a/src/import/import-raw.c b/src/import/import-raw.c index fd6b9f7703..ce37392707 100644 --- a/src/import/import-raw.c +++ b/src/import/import-raw.c @@ -19,8 +19,8 @@ #include -#include "sd-daemon.h" -#include "sd-event.h" +#include +#include #include "alloc-util.h" #include "btrfs-util.h" diff --git a/src/import/import-raw.h b/src/import/import-raw.h index 4f543e0883..f0a315c088 100644 --- a/src/import/import-raw.h +++ b/src/import/import-raw.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-event.h" +#include #include "import-util.h" #include "macro.h" diff --git a/src/import/import-tar.c b/src/import/import-tar.c index 8b81324fde..016d05e77d 100644 --- a/src/import/import-tar.c +++ b/src/import/import-tar.c @@ -19,8 +19,8 @@ #include -#include "sd-daemon.h" -#include "sd-event.h" +#include +#include #include "alloc-util.h" #include "btrfs-util.h" diff --git a/src/import/import-tar.h b/src/import/import-tar.h index 24abe06c8f..b66b00ddfd 100644 --- a/src/import/import-tar.h +++ b/src/import/import-tar.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-event.h" +#include #include "import-util.h" #include "macro.h" diff --git a/src/import/import.c b/src/import/import.c index 4e442ee84a..338847dbc2 100644 --- a/src/import/import.c +++ b/src/import/import.c @@ -19,7 +19,7 @@ #include -#include "sd-event.h" +#include #include "alloc-util.h" #include "fd-util.h" diff --git a/src/import/importd.c b/src/import/importd.c index 956a82945c..e30dfdf805 100644 --- a/src/import/importd.c +++ b/src/import/importd.c @@ -19,7 +19,7 @@ #include -#include "sd-bus.h" +#include #include "alloc-util.h" #include "bus-common-errors.h" diff --git a/src/import/pull-raw.c b/src/import/pull-raw.c index 8993402821..19155cc53a 100644 --- a/src/import/pull-raw.c +++ b/src/import/pull-raw.c @@ -21,7 +21,7 @@ #include #include -#include "sd-daemon.h" +#include #include "alloc-util.h" #include "btrfs-util.h" diff --git a/src/import/pull-raw.h b/src/import/pull-raw.h index 8f6d16eb3a..6bafa6dafd 100644 --- a/src/import/pull-raw.h +++ b/src/import/pull-raw.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-event.h" +#include #include "import-util.h" #include "macro.h" diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index 8c61c46f73..e0205c3841 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -20,7 +20,7 @@ #include #include -#include "sd-daemon.h" +#include #include "alloc-util.h" #include "btrfs-util.h" diff --git a/src/import/pull-tar.h b/src/import/pull-tar.h index 7e63e496d8..9ff5bd5953 100644 --- a/src/import/pull-tar.h +++ b/src/import/pull-tar.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-event.h" +#include #include "import-util.h" #include "macro.h" diff --git a/src/import/pull.c b/src/import/pull.c index 72604a6a74..74df24f993 100644 --- a/src/import/pull.c +++ b/src/import/pull.c @@ -19,7 +19,7 @@ #include -#include "sd-event.h" +#include #include "alloc-util.h" #include "hostname-util.h" diff --git a/src/initctl/Makefile b/src/initctl/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/initctl/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/initctl/Makefile b/src/initctl/Makefile new file mode 100644 index 0000000000..b63a2e47d9 --- /dev/null +++ b/src/initctl/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_initctl_SOURCES = \ + src/initctl/initctl.c + +systemd_initctl_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/initctl/initctl.c b/src/initctl/initctl.c index 41b2237d16..05285e3846 100644 --- a/src/initctl/initctl.c +++ b/src/initctl/initctl.c @@ -23,8 +23,8 @@ #include #include -#include "sd-bus.h" -#include "sd-daemon.h" +#include +#include #include "alloc-util.h" #include "bus-error.h" diff --git a/src/journal-remote/.gitignore b/src/journal-remote/.gitignore deleted file mode 100644 index 06847b65d4..0000000000 --- a/src/journal-remote/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/journal-remote.conf -/journal-upload.conf diff --git a/src/journal-remote/Makefile b/src/journal-remote/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/journal-remote/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/journal-remote/browse.html b/src/journal-remote/browse.html deleted file mode 100644 index 32848c7673..0000000000 --- a/src/journal-remote/browse.html +++ /dev/null @@ -1,544 +0,0 @@ - - - - Journal - - - - - - - -

- -
-
-
-
-
-
- -
- -      - Only current boot -
- -
- -
- -
- - - - -      - - -
- -
- g: First Page      - ←, k, BACKSPACE: Previous Page      - →, j, SPACE: Next Page      - G: Last Page      - +: More entries      - -: Fewer entries -
- - - - diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c deleted file mode 100644 index e265027a04..0000000000 --- a/src/journal-remote/journal-gatewayd.c +++ /dev/null @@ -1,1077 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 -#ifdef HAVE_GNUTLS -#include -#endif -#include -#include -#include -#include - -#include "sd-bus.h" -#include "sd-daemon.h" -#include "sd-journal.h" - -#include "alloc-util.h" -#include "bus-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "hostname-util.h" -#include "log.h" -#include "logs-show.h" -#include "microhttpd-util.h" -#include "parse-util.h" -#include "sigbus.h" -#include "util.h" - -#define JOURNAL_WAIT_TIMEOUT (10*USEC_PER_SEC) - -static char *arg_key_pem = NULL; -static char *arg_cert_pem = NULL; -static char *arg_trust_pem = NULL; - -typedef struct RequestMeta { - sd_journal *journal; - - OutputMode mode; - - char *cursor; - int64_t n_skip; - uint64_t n_entries; - bool n_entries_set; - - FILE *tmp; - uint64_t delta, size; - - int argument_parse_error; - - bool follow; - bool discrete; - - uint64_t n_fields; - bool n_fields_set; -} RequestMeta; - -static const char* const mime_types[_OUTPUT_MODE_MAX] = { - [OUTPUT_SHORT] = "text/plain", - [OUTPUT_JSON] = "application/json", - [OUTPUT_JSON_SSE] = "text/event-stream", - [OUTPUT_EXPORT] = "application/vnd.fdo.journal", -}; - -static RequestMeta *request_meta(void **connection_cls) { - RequestMeta *m; - - assert(connection_cls); - if (*connection_cls) - return *connection_cls; - - m = new0(RequestMeta, 1); - if (!m) - return NULL; - - *connection_cls = m; - return m; -} - -static void request_meta_free( - void *cls, - struct MHD_Connection *connection, - void **connection_cls, - enum MHD_RequestTerminationCode toe) { - - RequestMeta *m = *connection_cls; - - if (!m) - return; - - sd_journal_close(m->journal); - - safe_fclose(m->tmp); - - free(m->cursor); - free(m); -} - -static int open_journal(RequestMeta *m) { - assert(m); - - if (m->journal) - return 0; - - return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM); -} - -static int request_meta_ensure_tmp(RequestMeta *m) { - assert(m); - - if (m->tmp) - rewind(m->tmp); - else { - int fd; - - fd = open_tmpfile_unlinkable("/tmp", O_RDWR|O_CLOEXEC); - if (fd < 0) - return fd; - - m->tmp = fdopen(fd, "w+"); - if (!m->tmp) { - safe_close(fd); - return -errno; - } - } - - return 0; -} - -static ssize_t request_reader_entries( - void *cls, - uint64_t pos, - char *buf, - size_t max) { - - RequestMeta *m = cls; - int r; - size_t n, k; - - assert(m); - assert(buf); - assert(max > 0); - assert(pos >= m->delta); - - pos -= m->delta; - - while (pos >= m->size) { - off_t sz; - - /* End of this entry, so let's serialize the next - * one */ - - if (m->n_entries_set && - m->n_entries <= 0) - return MHD_CONTENT_READER_END_OF_STREAM; - - if (m->n_skip < 0) - r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1); - else if (m->n_skip > 0) - r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1); - else - r = sd_journal_next(m->journal); - - if (r < 0) { - log_error_errno(r, "Failed to advance journal pointer: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } else if (r == 0) { - - if (m->follow) { - r = sd_journal_wait(m->journal, (uint64_t) JOURNAL_WAIT_TIMEOUT); - if (r < 0) { - log_error_errno(r, "Couldn't wait for journal event: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - if (r == SD_JOURNAL_NOP) - break; - - continue; - } - - return MHD_CONTENT_READER_END_OF_STREAM; - } - - if (m->discrete) { - assert(m->cursor); - - r = sd_journal_test_cursor(m->journal, m->cursor); - if (r < 0) { - log_error_errno(r, "Failed to test cursor: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - if (r == 0) - return MHD_CONTENT_READER_END_OF_STREAM; - } - - pos -= m->size; - m->delta += m->size; - - if (m->n_entries_set) - m->n_entries -= 1; - - m->n_skip = 0; - - r = request_meta_ensure_tmp(m); - if (r < 0) { - log_error_errno(r, "Failed to create temporary file: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL); - if (r < 0) { - log_error_errno(r, "Failed to serialize item: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - sz = ftello(m->tmp); - if (sz == (off_t) -1) { - log_error_errno(errno, "Failed to retrieve file position: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - m->size = (uint64_t) sz; - } - - if (fseeko(m->tmp, pos, SEEK_SET) < 0) { - log_error_errno(errno, "Failed to seek to position: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - n = m->size - pos; - if (n < 1) - return 0; - if (n > max) - n = max; - - errno = 0; - k = fread(buf, 1, n, m->tmp); - if (k != n) { - log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - return (ssize_t) k; -} - -static int request_parse_accept( - RequestMeta *m, - struct MHD_Connection *connection) { - - const char *header; - - assert(m); - assert(connection); - - header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept"); - if (!header) - return 0; - - if (streq(header, mime_types[OUTPUT_JSON])) - m->mode = OUTPUT_JSON; - else if (streq(header, mime_types[OUTPUT_JSON_SSE])) - m->mode = OUTPUT_JSON_SSE; - else if (streq(header, mime_types[OUTPUT_EXPORT])) - m->mode = OUTPUT_EXPORT; - else - m->mode = OUTPUT_SHORT; - - return 0; -} - -static int request_parse_range( - RequestMeta *m, - struct MHD_Connection *connection) { - - const char *range, *colon, *colon2; - int r; - - assert(m); - assert(connection); - - range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range"); - if (!range) - return 0; - - if (!startswith(range, "entries=")) - return 0; - - range += 8; - range += strspn(range, WHITESPACE); - - colon = strchr(range, ':'); - if (!colon) - m->cursor = strdup(range); - else { - const char *p; - - colon2 = strchr(colon + 1, ':'); - if (colon2) { - _cleanup_free_ char *t; - - t = strndup(colon + 1, colon2 - colon - 1); - if (!t) - return -ENOMEM; - - r = safe_atoi64(t, &m->n_skip); - if (r < 0) - return r; - } - - p = (colon2 ? colon2 : colon) + 1; - if (*p) { - r = safe_atou64(p, &m->n_entries); - if (r < 0) - return r; - - if (m->n_entries <= 0) - return -EINVAL; - - m->n_entries_set = true; - } - - m->cursor = strndup(range, colon - range); - } - - if (!m->cursor) - return -ENOMEM; - - m->cursor[strcspn(m->cursor, WHITESPACE)] = 0; - if (isempty(m->cursor)) - m->cursor = mfree(m->cursor); - - return 0; -} - -static int request_parse_arguments_iterator( - void *cls, - enum MHD_ValueKind kind, - const char *key, - const char *value) { - - RequestMeta *m = cls; - _cleanup_free_ char *p = NULL; - int r; - - assert(m); - - if (isempty(key)) { - m->argument_parse_error = -EINVAL; - return MHD_NO; - } - - if (streq(key, "follow")) { - if (isempty(value)) { - m->follow = true; - return MHD_YES; - } - - r = parse_boolean(value); - if (r < 0) { - m->argument_parse_error = r; - return MHD_NO; - } - - m->follow = r; - return MHD_YES; - } - - if (streq(key, "discrete")) { - if (isempty(value)) { - m->discrete = true; - return MHD_YES; - } - - r = parse_boolean(value); - if (r < 0) { - m->argument_parse_error = r; - return MHD_NO; - } - - m->discrete = r; - return MHD_YES; - } - - if (streq(key, "boot")) { - if (isempty(value)) - r = true; - else { - r = parse_boolean(value); - if (r < 0) { - m->argument_parse_error = r; - return MHD_NO; - } - } - - if (r) { - char match[9 + 32 + 1] = "_BOOT_ID="; - sd_id128_t bid; - - r = sd_id128_get_boot(&bid); - if (r < 0) { - log_error_errno(r, "Failed to get boot ID: %m"); - return MHD_NO; - } - - sd_id128_to_string(bid, match + 9); - r = sd_journal_add_match(m->journal, match, sizeof(match)-1); - if (r < 0) { - m->argument_parse_error = r; - return MHD_NO; - } - } - - return MHD_YES; - } - - p = strjoin(key, "=", strempty(value), NULL); - if (!p) { - m->argument_parse_error = log_oom(); - return MHD_NO; - } - - r = sd_journal_add_match(m->journal, p, 0); - if (r < 0) { - m->argument_parse_error = r; - return MHD_NO; - } - - return MHD_YES; -} - -static int request_parse_arguments( - RequestMeta *m, - struct MHD_Connection *connection) { - - assert(m); - assert(connection); - - m->argument_parse_error = 0; - MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m); - - return m->argument_parse_error; -} - -static int request_handler_entries( - struct MHD_Connection *connection, - void *connection_cls) { - - struct MHD_Response *response; - RequestMeta *m = connection_cls; - int r; - - assert(connection); - assert(m); - - r = open_journal(m); - if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r)); - - if (request_parse_accept(m, connection) < 0) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n"); - - if (request_parse_range(m, connection) < 0) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n"); - - if (request_parse_arguments(m, connection) < 0) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n"); - - if (m->discrete) { - if (!m->cursor) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n"); - - m->n_entries = 1; - m->n_entries_set = true; - } - - if (m->cursor) - r = sd_journal_seek_cursor(m->journal, m->cursor); - else if (m->n_skip >= 0) - r = sd_journal_seek_head(m->journal); - else if (m->n_skip < 0) - r = sd_journal_seek_tail(m->journal); - if (r < 0) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n"); - - response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL); - if (!response) - return respond_oom(connection); - - MHD_add_response_header(response, "Content-Type", mime_types[m->mode]); - - r = MHD_queue_response(connection, MHD_HTTP_OK, response); - MHD_destroy_response(response); - - return r; -} - -static int output_field(FILE *f, OutputMode m, const char *d, size_t l) { - const char *eq; - size_t j; - - eq = memchr(d, '=', l); - if (!eq) - return -EINVAL; - - j = l - (eq - d + 1); - - if (m == OUTPUT_JSON) { - fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d); - json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH); - fputs(" }\n", f); - } else { - fwrite(eq+1, 1, j, f); - fputc('\n', f); - } - - return 0; -} - -static ssize_t request_reader_fields( - void *cls, - uint64_t pos, - char *buf, - size_t max) { - - RequestMeta *m = cls; - int r; - size_t n, k; - - assert(m); - assert(buf); - assert(max > 0); - assert(pos >= m->delta); - - pos -= m->delta; - - while (pos >= m->size) { - off_t sz; - const void *d; - size_t l; - - /* End of this field, so let's serialize the next - * one */ - - if (m->n_fields_set && - m->n_fields <= 0) - return MHD_CONTENT_READER_END_OF_STREAM; - - r = sd_journal_enumerate_unique(m->journal, &d, &l); - if (r < 0) { - log_error_errno(r, "Failed to advance field index: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } else if (r == 0) - return MHD_CONTENT_READER_END_OF_STREAM; - - pos -= m->size; - m->delta += m->size; - - if (m->n_fields_set) - m->n_fields -= 1; - - r = request_meta_ensure_tmp(m); - if (r < 0) { - log_error_errno(r, "Failed to create temporary file: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - r = output_field(m->tmp, m->mode, d, l); - if (r < 0) { - log_error_errno(r, "Failed to serialize item: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - sz = ftello(m->tmp); - if (sz == (off_t) -1) { - log_error_errno(errno, "Failed to retrieve file position: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - m->size = (uint64_t) sz; - } - - if (fseeko(m->tmp, pos, SEEK_SET) < 0) { - log_error_errno(errno, "Failed to seek to position: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - n = m->size - pos; - if (n > max) - n = max; - - errno = 0; - k = fread(buf, 1, n, m->tmp); - if (k != n) { - log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - return (ssize_t) k; -} - -static int request_handler_fields( - struct MHD_Connection *connection, - const char *field, - void *connection_cls) { - - struct MHD_Response *response; - RequestMeta *m = connection_cls; - int r; - - assert(connection); - assert(m); - - r = open_journal(m); - if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r)); - - if (request_parse_accept(m, connection) < 0) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n"); - - r = sd_journal_query_unique(m->journal, field); - if (r < 0) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n"); - - response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL); - if (!response) - return respond_oom(connection); - - MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]); - - r = MHD_queue_response(connection, MHD_HTTP_OK, response); - MHD_destroy_response(response); - - return r; -} - -static int request_handler_redirect( - struct MHD_Connection *connection, - const char *target) { - - char *page; - struct MHD_Response *response; - int ret; - - assert(connection); - assert(target); - - if (asprintf(&page, "Please continue to the journal browser.", target) < 0) - return respond_oom(connection); - - response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE); - if (!response) { - free(page); - return respond_oom(connection); - } - - MHD_add_response_header(response, "Content-Type", "text/html"); - MHD_add_response_header(response, "Location", target); - - ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response); - MHD_destroy_response(response); - - return ret; -} - -static int request_handler_file( - struct MHD_Connection *connection, - const char *path, - const char *mime_type) { - - struct MHD_Response *response; - int ret; - _cleanup_close_ int fd = -1; - struct stat st; - - assert(connection); - assert(path); - assert(mime_type); - - fd = open(path, O_RDONLY|O_CLOEXEC); - if (fd < 0) - return mhd_respondf(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path); - - if (fstat(fd, &st) < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n"); - - response = MHD_create_response_from_fd_at_offset64(st.st_size, fd, 0); - if (!response) - return respond_oom(connection); - - fd = -1; - - MHD_add_response_header(response, "Content-Type", mime_type); - - ret = MHD_queue_response(connection, MHD_HTTP_OK, response); - MHD_destroy_response(response); - - return ret; -} - -static int get_virtualization(char **v) { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - char *b = NULL; - int r; - - r = sd_bus_default_system(&bus); - if (r < 0) - return r; - - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "Virtualization", - NULL, - &b); - if (r < 0) - return r; - - if (isempty(b)) { - free(b); - *v = NULL; - return 0; - } - - *v = b; - return 1; -} - -static int request_handler_machine( - struct MHD_Connection *connection, - void *connection_cls) { - - struct MHD_Response *response; - RequestMeta *m = connection_cls; - int r; - _cleanup_free_ char* hostname = NULL, *os_name = NULL; - uint64_t cutoff_from = 0, cutoff_to = 0, usage = 0; - char *json; - sd_id128_t mid, bid; - _cleanup_free_ char *v = NULL; - - assert(connection); - assert(m); - - r = open_journal(m); - if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r)); - - r = sd_id128_get_machine(&mid); - if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r)); - - r = sd_id128_get_boot(&bid); - if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r)); - - hostname = gethostname_malloc(); - if (!hostname) - return respond_oom(connection); - - r = sd_journal_get_usage(m->journal, &usage); - if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r)); - - r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to); - if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r)); - - if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL) == -ENOENT) - (void) parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL); - - get_virtualization(&v); - - r = asprintf(&json, - "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\"," - "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\"," - "\"hostname\" : \"%s\"," - "\"os_pretty_name\" : \"%s\"," - "\"virtualization\" : \"%s\"," - "\"usage\" : \"%"PRIu64"\"," - "\"cutoff_from_realtime\" : \"%"PRIu64"\"," - "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n", - SD_ID128_FORMAT_VAL(mid), - SD_ID128_FORMAT_VAL(bid), - hostname_cleanup(hostname), - os_name ? os_name : "GNU/Linux", - v ? v : "bare", - usage, - cutoff_from, - cutoff_to); - - if (r < 0) - return respond_oom(connection); - - response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE); - if (!response) { - free(json); - return respond_oom(connection); - } - - MHD_add_response_header(response, "Content-Type", "application/json"); - r = MHD_queue_response(connection, MHD_HTTP_OK, response); - MHD_destroy_response(response); - - return r; -} - -static int request_handler( - void *cls, - struct MHD_Connection *connection, - const char *url, - const char *method, - const char *version, - const char *upload_data, - size_t *upload_data_size, - void **connection_cls) { - int r, code; - - assert(connection); - assert(connection_cls); - assert(url); - assert(method); - - if (!streq(method, "GET")) - return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, - "Unsupported method.\n"); - - - if (!*connection_cls) { - if (!request_meta(connection_cls)) - return respond_oom(connection); - return MHD_YES; - } - - if (arg_trust_pem) { - r = check_permissions(connection, &code, NULL); - if (r < 0) - return code; - } - - if (streq(url, "/")) - return request_handler_redirect(connection, "/browse"); - - if (streq(url, "/entries")) - return request_handler_entries(connection, *connection_cls); - - if (startswith(url, "/fields/")) - return request_handler_fields(connection, url + 8, *connection_cls); - - if (streq(url, "/browse")) - return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html"); - - if (streq(url, "/machine")) - return request_handler_machine(connection, *connection_cls); - - return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.\n"); -} - -static void help(void) { - printf("%s [OPTIONS...] ...\n\n" - "HTTP server for journal events.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --cert=CERT.PEM Server certificate in PEM format\n" - " --key=KEY.PEM Server key in PEM format\n" - " --trust=CERT.PEM Certificat authority certificate in PEM format\n", - program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_KEY, - ARG_CERT, - ARG_TRUST, - }; - - int r, c; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "key", required_argument, NULL, ARG_KEY }, - { "cert", required_argument, NULL, ARG_CERT }, - { "trust", required_argument, NULL, ARG_TRUST }, - {} - }; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - - switch(c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_KEY: - if (arg_key_pem) { - log_error("Key file specified twice"); - return -EINVAL; - } - r = read_full_file(optarg, &arg_key_pem, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read key file: %m"); - assert(arg_key_pem); - break; - - case ARG_CERT: - if (arg_cert_pem) { - log_error("Certificate file specified twice"); - return -EINVAL; - } - r = read_full_file(optarg, &arg_cert_pem, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read certificate file: %m"); - assert(arg_cert_pem); - break; - - case ARG_TRUST: -#ifdef HAVE_GNUTLS - if (arg_trust_pem) { - log_error("CA certificate file specified twice"); - return -EINVAL; - } - r = read_full_file(optarg, &arg_trust_pem, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read CA certificate file: %m"); - assert(arg_trust_pem); - break; -#else - log_error("Option --trust is not available."); -#endif - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (optind < argc) { - log_error("This program does not take arguments."); - return -EINVAL; - } - - if (!!arg_key_pem != !!arg_cert_pem) { - log_error("Certificate and key files must be specified together"); - return -EINVAL; - } - - if (arg_trust_pem && !arg_key_pem) { - log_error("CA certificate can only be used with certificate file"); - return -EINVAL; - } - - return 1; -} - -int main(int argc, char *argv[]) { - struct MHD_Daemon *d = NULL; - int r, n; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r < 0) - return EXIT_FAILURE; - if (r == 0) - return EXIT_SUCCESS; - - sigbus_install(); - - r = setup_gnutls_logger(NULL); - if (r < 0) - return EXIT_FAILURE; - - n = sd_listen_fds(1); - if (n < 0) { - log_error_errno(n, "Failed to determine passed sockets: %m"); - goto finish; - } else if (n > 1) { - log_error("Can't listen on more than one socket."); - goto finish; - } else { - struct MHD_OptionItem opts[] = { - { MHD_OPTION_NOTIFY_COMPLETED, - (intptr_t) request_meta_free, NULL }, - { MHD_OPTION_EXTERNAL_LOGGER, - (intptr_t) microhttpd_logger, NULL }, - { MHD_OPTION_END, 0, NULL }, - { MHD_OPTION_END, 0, NULL }, - { MHD_OPTION_END, 0, NULL }, - { MHD_OPTION_END, 0, NULL }, - { MHD_OPTION_END, 0, NULL }}; - int opts_pos = 2; - - /* We force MHD_USE_PIPE_FOR_SHUTDOWN here, in order - * to make sure libmicrohttpd doesn't use shutdown() - * on our listening socket, which would break socket - * re-activation. See - * - * https://lists.gnu.org/archive/html/libmicrohttpd/2015-09/msg00014.html - * https://github.com/systemd/systemd/pull/1286 - */ - - int flags = - MHD_USE_DEBUG | - MHD_USE_DUAL_STACK | - MHD_USE_PIPE_FOR_SHUTDOWN | - MHD_USE_POLL | - MHD_USE_THREAD_PER_CONNECTION; - - if (n > 0) - opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START}; - if (arg_key_pem) { - assert(arg_cert_pem); - opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_HTTPS_MEM_KEY, 0, arg_key_pem}; - opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_HTTPS_MEM_CERT, 0, arg_cert_pem}; - flags |= MHD_USE_SSL; - } - if (arg_trust_pem) { - assert(flags & MHD_USE_SSL); - opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_HTTPS_MEM_TRUST, 0, arg_trust_pem}; - } - - d = MHD_start_daemon(flags, 19531, - NULL, NULL, - request_handler, NULL, - MHD_OPTION_ARRAY, opts, - MHD_OPTION_END); - } - - if (!d) { - log_error("Failed to start daemon!"); - goto finish; - } - - pause(); - - r = EXIT_SUCCESS; - -finish: - if (d) - MHD_stop_daemon(d); - - return r; -} diff --git a/src/journal-remote/journal-remote-parse.c b/src/journal-remote/journal-remote-parse.c deleted file mode 100644 index 9ba9ee3fc0..0000000000 --- a/src/journal-remote/journal-remote-parse.c +++ /dev/null @@ -1,506 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Zbigniew Jędrzejewski-Szmek - - 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 "alloc-util.h" -#include "fd-util.h" -#include "journal-remote-parse.h" -#include "journald-native.h" -#include "parse-util.h" -#include "string-util.h" - -#define LINE_CHUNK 8*1024u - -void source_free(RemoteSource *source) { - if (!source) - return; - - if (source->fd >= 0 && !source->passive_fd) { - log_debug("Closing fd:%d (%s)", source->fd, source->name); - safe_close(source->fd); - } - - free(source->name); - free(source->buf); - iovw_free_contents(&source->iovw); - - log_debug("Writer ref count %i", source->writer->n_ref); - writer_unref(source->writer); - - sd_event_source_unref(source->event); - sd_event_source_unref(source->buffer_event); - - free(source); -} - -/** - * Initialize zero-filled source with given values. On success, takes - * ownerhship of fd and writer, otherwise does not touch them. - */ -RemoteSource* source_new(int fd, bool passive_fd, char *name, Writer *writer) { - - RemoteSource *source; - - log_debug("Creating source for %sfd:%d (%s)", - passive_fd ? "passive " : "", fd, name); - - assert(fd >= 0); - - source = new0(RemoteSource, 1); - if (!source) - return NULL; - - source->fd = fd; - source->passive_fd = passive_fd; - source->name = name; - source->writer = writer; - - return source; -} - -static char* realloc_buffer(RemoteSource *source, size_t size) { - char *b, *old = source->buf; - - b = GREEDY_REALLOC(source->buf, source->size, size); - if (!b) - return NULL; - - iovw_rebase(&source->iovw, old, source->buf); - - return b; -} - -static int get_line(RemoteSource *source, char **line, size_t *size) { - ssize_t n; - char *c = NULL; - - assert(source); - assert(source->state == STATE_LINE); - assert(source->offset <= source->filled); - assert(source->filled <= source->size); - assert(source->buf == NULL || source->size > 0); - assert(source->fd >= 0); - - for (;;) { - if (source->buf) { - size_t start = MAX(source->scanned, source->offset); - - c = memchr(source->buf + start, '\n', - source->filled - start); - if (c != NULL) - break; - } - - source->scanned = source->filled; - if (source->scanned >= DATA_SIZE_MAX) { - log_error("Entry is bigger than %u bytes.", DATA_SIZE_MAX); - return -E2BIG; - } - - if (source->passive_fd) - /* we have to wait for some data to come to us */ - return -EAGAIN; - - /* We know that source->filled is at most DATA_SIZE_MAX, so if - we reallocate it, we'll increase the size at least a bit. */ - assert_cc(DATA_SIZE_MAX < ENTRY_SIZE_MAX); - if (source->size - source->filled < LINE_CHUNK && - !realloc_buffer(source, MIN(source->filled + LINE_CHUNK, ENTRY_SIZE_MAX))) - return log_oom(); - - assert(source->buf); - assert(source->size - source->filled >= LINE_CHUNK || - source->size == ENTRY_SIZE_MAX); - - n = read(source->fd, - source->buf + source->filled, - source->size - source->filled); - if (n < 0) { - if (errno != EAGAIN) - log_error_errno(errno, "read(%d, ..., %zu): %m", - source->fd, - source->size - source->filled); - return -errno; - } else if (n == 0) - return 0; - - source->filled += n; - } - - *line = source->buf + source->offset; - *size = c + 1 - source->buf - source->offset; - source->offset += *size; - - return 1; -} - -int push_data(RemoteSource *source, const char *data, size_t size) { - assert(source); - assert(source->state != STATE_EOF); - - if (!realloc_buffer(source, source->filled + size)) { - log_error("Failed to store received data of size %zu " - "(in addition to existing %zu bytes with %zu filled): %s", - size, source->size, source->filled, strerror(ENOMEM)); - return -ENOMEM; - } - - memcpy(source->buf + source->filled, data, size); - source->filled += size; - - return 0; -} - -static int fill_fixed_size(RemoteSource *source, void **data, size_t size) { - - assert(source); - assert(source->state == STATE_DATA_START || - source->state == STATE_DATA || - source->state == STATE_DATA_FINISH); - assert(size <= DATA_SIZE_MAX); - assert(source->offset <= source->filled); - assert(source->filled <= source->size); - assert(source->buf != NULL || source->size == 0); - assert(source->buf == NULL || source->size > 0); - assert(source->fd >= 0); - assert(data); - - while (source->filled - source->offset < size) { - int n; - - if (source->passive_fd) - /* we have to wait for some data to come to us */ - return -EAGAIN; - - if (!realloc_buffer(source, source->offset + size)) - return log_oom(); - - n = read(source->fd, source->buf + source->filled, - source->size - source->filled); - if (n < 0) { - if (errno != EAGAIN) - log_error_errno(errno, "read(%d, ..., %zu): %m", source->fd, - source->size - source->filled); - return -errno; - } else if (n == 0) - return 0; - - source->filled += n; - } - - *data = source->buf + source->offset; - source->offset += size; - - return 1; -} - -static int get_data_size(RemoteSource *source) { - int r; - void *data; - - assert(source); - assert(source->state == STATE_DATA_START); - assert(source->data_size == 0); - - r = fill_fixed_size(source, &data, sizeof(uint64_t)); - if (r <= 0) - return r; - - source->data_size = le64toh( *(uint64_t *) data ); - if (source->data_size > DATA_SIZE_MAX) { - log_error("Stream declares field with size %zu > DATA_SIZE_MAX = %u", - source->data_size, DATA_SIZE_MAX); - return -EINVAL; - } - if (source->data_size == 0) - log_warning("Binary field with zero length"); - - return 1; -} - -static int get_data_data(RemoteSource *source, void **data) { - int r; - - assert(source); - assert(data); - assert(source->state == STATE_DATA); - - r = fill_fixed_size(source, data, source->data_size); - if (r <= 0) - return r; - - return 1; -} - -static int get_data_newline(RemoteSource *source) { - int r; - char *data; - - assert(source); - assert(source->state == STATE_DATA_FINISH); - - r = fill_fixed_size(source, (void**) &data, 1); - if (r <= 0) - return r; - - assert(data); - if (*data != '\n') { - log_error("expected newline, got '%c'", *data); - return -EINVAL; - } - - return 1; -} - -static int process_dunder(RemoteSource *source, char *line, size_t n) { - const char *timestamp; - int r; - - assert(line); - assert(n > 0); - assert(line[n-1] == '\n'); - - /* XXX: is it worth to support timestamps in extended format? - * We don't produce them, but who knows... */ - - timestamp = startswith(line, "__CURSOR="); - if (timestamp) - /* ignore __CURSOR */ - return 1; - - timestamp = startswith(line, "__REALTIME_TIMESTAMP="); - if (timestamp) { - long long unsigned x; - line[n-1] = '\0'; - r = safe_atollu(timestamp, &x); - if (r < 0) - log_warning("Failed to parse __REALTIME_TIMESTAMP: '%s'", timestamp); - else - source->ts.realtime = x; - return r < 0 ? r : 1; - } - - timestamp = startswith(line, "__MONOTONIC_TIMESTAMP="); - if (timestamp) { - long long unsigned x; - line[n-1] = '\0'; - r = safe_atollu(timestamp, &x); - if (r < 0) - log_warning("Failed to parse __MONOTONIC_TIMESTAMP: '%s'", timestamp); - else - source->ts.monotonic = x; - return r < 0 ? r : 1; - } - - timestamp = startswith(line, "__"); - if (timestamp) { - log_notice("Unknown dunder line %s", line); - return 1; - } - - /* no dunder */ - return 0; -} - -static int process_data(RemoteSource *source) { - int r; - - switch(source->state) { - case STATE_LINE: { - char *line, *sep; - size_t n = 0; - - assert(source->data_size == 0); - - r = get_line(source, &line, &n); - if (r < 0) - return r; - if (r == 0) { - source->state = STATE_EOF; - return r; - } - assert(n > 0); - assert(line[n-1] == '\n'); - - if (n == 1) { - log_trace("Received empty line, event is ready"); - return 1; - } - - r = process_dunder(source, line, n); - if (r != 0) - return r < 0 ? r : 0; - - /* MESSAGE=xxx\n - or - COREDUMP\n - LLLLLLLL0011223344...\n - */ - sep = memchr(line, '=', n); - if (sep) { - /* chomp newline */ - n--; - - r = iovw_put(&source->iovw, line, n); - if (r < 0) - return r; - } else { - /* replace \n with = */ - line[n-1] = '='; - - source->field_len = n; - source->state = STATE_DATA_START; - - /* we cannot put the field in iovec until we have all data */ - } - - log_trace("Received: %.*s (%s)", (int) n, line, sep ? "text" : "binary"); - - return 0; /* continue */ - } - - case STATE_DATA_START: - assert(source->data_size == 0); - - r = get_data_size(source); - // log_debug("get_data_size() -> %d", r); - if (r < 0) - return r; - if (r == 0) { - source->state = STATE_EOF; - return 0; - } - - source->state = source->data_size > 0 ? - STATE_DATA : STATE_DATA_FINISH; - - return 0; /* continue */ - - case STATE_DATA: { - void *data; - char *field; - - assert(source->data_size > 0); - - r = get_data_data(source, &data); - // log_debug("get_data_data() -> %d", r); - if (r < 0) - return r; - if (r == 0) { - source->state = STATE_EOF; - return 0; - } - - assert(data); - - field = (char*) data - sizeof(uint64_t) - source->field_len; - memmove(field + sizeof(uint64_t), field, source->field_len); - - r = iovw_put(&source->iovw, field + sizeof(uint64_t), source->field_len + source->data_size); - if (r < 0) - return r; - - source->state = STATE_DATA_FINISH; - - return 0; /* continue */ - } - - case STATE_DATA_FINISH: - r = get_data_newline(source); - // log_debug("get_data_newline() -> %d", r); - if (r < 0) - return r; - if (r == 0) { - source->state = STATE_EOF; - return 0; - } - - source->data_size = 0; - source->state = STATE_LINE; - - return 0; /* continue */ - default: - assert_not_reached("wtf?"); - } -} - -int process_source(RemoteSource *source, bool compress, bool seal) { - size_t remain, target; - int r; - - assert(source); - assert(source->writer); - - r = process_data(source); - if (r <= 0) - return r; - - /* We have a full event */ - log_trace("Received full event from source@%p fd:%d (%s)", - source, source->fd, source->name); - - if (!source->iovw.count) { - log_warning("Entry with no payload, skipping"); - goto freeing; - } - - assert(source->iovw.iovec); - assert(source->iovw.count); - - r = writer_write(source->writer, &source->iovw, &source->ts, compress, seal); - if (r < 0) - log_error_errno(r, "Failed to write entry of %zu bytes: %m", - iovw_size(&source->iovw)); - else - r = 1; - - freeing: - iovw_free_contents(&source->iovw); - - /* possibly reset buffer position */ - remain = source->filled - source->offset; - - if (remain == 0) /* no brainer */ - source->offset = source->scanned = source->filled = 0; - else if (source->offset > source->size - source->filled && - source->offset > remain) { - memcpy(source->buf, source->buf + source->offset, remain); - source->offset = source->scanned = 0; - source->filled = remain; - } - - target = source->size; - while (target > 16 * LINE_CHUNK && source->filled < target / 2) - target /= 2; - if (target < source->size) { - char *tmp; - - tmp = realloc(source->buf, target); - if (!tmp) - log_warning("Failed to reallocate buffer to (smaller) size %zu", - target); - else { - log_debug("Reallocated buffer from %zu to %zu bytes", - source->size, target); - source->buf = tmp; - source->size = target; - } - } - - return r; -} diff --git a/src/journal-remote/journal-remote-parse.h b/src/journal-remote/journal-remote-parse.h deleted file mode 100644 index 1740a21f92..0000000000 --- a/src/journal-remote/journal-remote-parse.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Zbigniew Jędrzejewski-Szmek - - 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 "sd-event.h" - -#include "journal-remote-write.h" - -typedef enum { - STATE_LINE = 0, /* waiting to read, or reading line */ - STATE_DATA_START, /* reading binary data header */ - STATE_DATA, /* reading binary data */ - STATE_DATA_FINISH, /* expecting newline */ - STATE_EOF, /* done */ -} source_state; - -typedef struct RemoteSource { - char *name; - int fd; - bool passive_fd; - - char *buf; - size_t size; /* total size of the buffer */ - size_t offset; /* offset to the beginning of live data in the buffer */ - size_t scanned; /* number of bytes since the beginning of data without a newline */ - size_t filled; /* total number of bytes in the buffer */ - - size_t field_len; /* used for binary fields: the field name length */ - size_t data_size; /* and the size of the binary data chunk being processed */ - - struct iovec_wrapper iovw; - - source_state state; - dual_timestamp ts; - - Writer *writer; - - sd_event_source *event; - sd_event_source *buffer_event; -} RemoteSource; - -RemoteSource* source_new(int fd, bool passive_fd, char *name, Writer *writer); - -static inline size_t source_non_empty(RemoteSource *source) { - assert(source); - - return source->filled; -} - -void source_free(RemoteSource *source); -int push_data(RemoteSource *source, const char *data, size_t size); -int process_source(RemoteSource *source, bool compress, bool seal); diff --git a/src/journal-remote/journal-remote-write.c b/src/journal-remote/journal-remote-write.c deleted file mode 100644 index 7bba52566e..0000000000 --- a/src/journal-remote/journal-remote-write.c +++ /dev/null @@ -1,168 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 Zbigniew Jędrzejewski-Szmek - - 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 "alloc-util.h" -#include "journal-remote.h" - -int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len) { - if (!GREEDY_REALLOC(iovw->iovec, iovw->size_bytes, iovw->count + 1)) - return log_oom(); - - iovw->iovec[iovw->count++] = (struct iovec) {data, len}; - return 0; -} - -void iovw_free_contents(struct iovec_wrapper *iovw) { - iovw->iovec = mfree(iovw->iovec); - iovw->size_bytes = iovw->count = 0; -} - -size_t iovw_size(struct iovec_wrapper *iovw) { - size_t n = 0, i; - - for (i = 0; i < iovw->count; i++) - n += iovw->iovec[i].iov_len; - - return n; -} - -void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new) { - size_t i; - - for (i = 0; i < iovw->count; i++) - iovw->iovec[i].iov_base = (char*) iovw->iovec[i].iov_base - old + new; -} - -/********************************************************************** - ********************************************************************** - **********************************************************************/ - -static int do_rotate(JournalFile **f, bool compress, bool seal) { - int r = journal_file_rotate(f, compress, seal, NULL); - if (r < 0) { - if (*f) - log_error_errno(r, "Failed to rotate %s: %m", (*f)->path); - else - log_error_errno(r, "Failed to create rotated journal: %m"); - } - - return r; -} - -Writer* writer_new(RemoteServer *server) { - Writer *w; - - w = new0(Writer, 1); - if (!w) - return NULL; - - memset(&w->metrics, 0xFF, sizeof(w->metrics)); - - w->mmap = mmap_cache_new(); - if (!w->mmap) { - free(w); - return NULL; - } - - w->n_ref = 1; - w->server = server; - - return w; -} - -Writer* writer_free(Writer *w) { - if (!w) - return NULL; - - if (w->journal) { - log_debug("Closing journal file %s.", w->journal->path); - journal_file_close(w->journal); - } - - if (w->server && w->hashmap_key) - hashmap_remove(w->server->writers, w->hashmap_key); - - free(w->hashmap_key); - - if (w->mmap) - mmap_cache_unref(w->mmap); - - free(w); - - return NULL; -} - -Writer* writer_unref(Writer *w) { - if (w && (-- w->n_ref <= 0)) - writer_free(w); - - return NULL; -} - -Writer* writer_ref(Writer *w) { - if (w) - assert_se(++ w->n_ref >= 2); - - return w; -} - -int writer_write(Writer *w, - struct iovec_wrapper *iovw, - dual_timestamp *ts, - bool compress, - bool seal) { - int r; - - assert(w); - assert(iovw); - assert(iovw->count > 0); - - if (journal_file_rotate_suggested(w->journal, 0)) { - log_info("%s: Journal header limits reached or header out-of-date, rotating", - w->journal->path); - r = do_rotate(&w->journal, compress, seal); - if (r < 0) - return r; - } - - r = journal_file_append_entry(w->journal, ts, iovw->iovec, iovw->count, - &w->seqnum, NULL, NULL); - if (r >= 0) { - if (w->server) - w->server->event_count += 1; - return 1; - } - - log_debug_errno(r, "%s: Write failed, rotating: %m", w->journal->path); - r = do_rotate(&w->journal, compress, seal); - if (r < 0) - return r; - else - log_debug("%s: Successfully rotated journal", w->journal->path); - - log_debug("Retrying write."); - r = journal_file_append_entry(w->journal, ts, iovw->iovec, iovw->count, - &w->seqnum, NULL, NULL); - if (r < 0) - return r; - - if (w->server) - w->server->event_count += 1; - return 1; -} diff --git a/src/journal-remote/journal-remote-write.h b/src/journal-remote/journal-remote-write.h deleted file mode 100644 index 53ba45fc04..0000000000 --- a/src/journal-remote/journal-remote-write.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Zbigniew Jędrzejewski-Szmek - - 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 "journal-file.h" - -typedef struct RemoteServer RemoteServer; - -struct iovec_wrapper { - struct iovec *iovec; - size_t size_bytes; - size_t count; -}; - -int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len); -void iovw_free_contents(struct iovec_wrapper *iovw); -size_t iovw_size(struct iovec_wrapper *iovw); -void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new); - -typedef struct Writer { - JournalFile *journal; - JournalMetrics metrics; - - MMapCache *mmap; - RemoteServer *server; - char *hashmap_key; - - uint64_t seqnum; - - int n_ref; -} Writer; - -Writer* writer_new(RemoteServer* server); -Writer* writer_free(Writer *w); - -Writer* writer_ref(Writer *w); -Writer* writer_unref(Writer *w); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Writer*, writer_unref); -#define _cleanup_writer_unref_ _cleanup_(writer_unrefp) - -int writer_write(Writer *s, - struct iovec_wrapper *iovw, - dual_timestamp *ts, - bool compress, - bool seal); - -typedef enum JournalWriteSplitMode { - JOURNAL_WRITE_SPLIT_NONE, - JOURNAL_WRITE_SPLIT_HOST, - _JOURNAL_WRITE_SPLIT_MAX, - _JOURNAL_WRITE_SPLIT_INVALID = -1 -} JournalWriteSplitMode; diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c deleted file mode 100644 index 35a1e55f9e..0000000000 --- a/src/journal-remote/journal-remote.c +++ /dev/null @@ -1,1601 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 Zbigniew Jędrzejewski-Szmek - - 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 -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_GNUTLS -#include -#endif - -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "conf-parser.h" -#include "def.h" -#include "escape.h" -#include "fd-util.h" -#include "fileio.h" -#include "journal-file.h" -#include "journal-remote-write.h" -#include "journal-remote.h" -#include "journald-native.h" -#include "macro.h" -#include "parse-util.h" -#include "signal-util.h" -#include "socket-util.h" -#include "stat-util.h" -#include "stdio-util.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" - -#define REMOTE_JOURNAL_PATH "/var/log/journal/remote" - -#define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-remote.pem" -#define CERT_FILE CERTIFICATE_ROOT "/certs/journal-remote.pem" -#define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem" - -static char* arg_url = NULL; -static char* arg_getter = NULL; -static char* arg_listen_raw = NULL; -static char* arg_listen_http = NULL; -static char* arg_listen_https = NULL; -static char** arg_files = NULL; -static int arg_compress = true; -static int arg_seal = false; -static int http_socket = -1, https_socket = -1; -static char** arg_gnutls_log = NULL; - -static JournalWriteSplitMode arg_split_mode = JOURNAL_WRITE_SPLIT_HOST; -static char* arg_output = NULL; - -static char *arg_key = NULL; -static char *arg_cert = NULL; -static char *arg_trust = NULL; -static bool arg_trust_all = false; - -/********************************************************************** - ********************************************************************** - **********************************************************************/ - -static int spawn_child(const char* child, char** argv) { - int fd[2]; - pid_t parent_pid, child_pid; - int r; - - if (pipe(fd) < 0) - return log_error_errno(errno, "Failed to create pager pipe: %m"); - - parent_pid = getpid(); - - child_pid = fork(); - if (child_pid < 0) { - r = log_error_errno(errno, "Failed to fork: %m"); - safe_close_pair(fd); - return r; - } - - /* In the child */ - if (child_pid == 0) { - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - r = dup2(fd[1], STDOUT_FILENO); - if (r < 0) { - log_error_errno(errno, "Failed to dup pipe to stdout: %m"); - _exit(EXIT_FAILURE); - } - - safe_close_pair(fd); - - /* Make sure the child goes away when the parent dies */ - if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) - _exit(EXIT_FAILURE); - - /* Check whether our parent died before we were able - * to set the death signal */ - if (getppid() != parent_pid) - _exit(EXIT_SUCCESS); - - execvp(child, argv); - log_error_errno(errno, "Failed to exec child %s: %m", child); - _exit(EXIT_FAILURE); - } - - r = close(fd[1]); - if (r < 0) - log_warning_errno(errno, "Failed to close write end of pipe: %m"); - - return fd[0]; -} - -static int spawn_curl(const char* url) { - char **argv = STRV_MAKE("curl", - "-HAccept: application/vnd.fdo.journal", - "--silent", - "--show-error", - url); - int r; - - r = spawn_child("curl", argv); - if (r < 0) - log_error_errno(r, "Failed to spawn curl: %m"); - return r; -} - -static int spawn_getter(const char *getter) { - int r; - _cleanup_strv_free_ char **words = NULL; - - assert(getter); - r = strv_split_extract(&words, getter, WHITESPACE, EXTRACT_QUOTES); - if (r < 0) - return log_error_errno(r, "Failed to split getter option: %m"); - - r = spawn_child(words[0], words); - if (r < 0) - log_error_errno(r, "Failed to spawn getter %s: %m", getter); - - return r; -} - -#define filename_escape(s) xescape((s), "/ ") - -static int open_output(Writer *w, const char* host) { - _cleanup_free_ char *_output = NULL; - const char *output; - int r; - - switch (arg_split_mode) { - case JOURNAL_WRITE_SPLIT_NONE: - output = arg_output ?: REMOTE_JOURNAL_PATH "/remote.journal"; - break; - - case JOURNAL_WRITE_SPLIT_HOST: { - _cleanup_free_ char *name; - - assert(host); - - name = filename_escape(host); - if (!name) - return log_oom(); - - r = asprintf(&_output, "%s/remote-%s.journal", - arg_output ?: REMOTE_JOURNAL_PATH, - name); - if (r < 0) - return log_oom(); - - output = _output; - break; - } - - default: - assert_not_reached("what?"); - } - - r = journal_file_open_reliably(output, - O_RDWR|O_CREAT, 0640, - arg_compress, arg_seal, - &w->metrics, - w->mmap, NULL, - NULL, &w->journal); - if (r < 0) - log_error_errno(r, "Failed to open output journal %s: %m", - output); - else - log_debug("Opened output file %s", w->journal->path); - return r; -} - -/********************************************************************** - ********************************************************************** - **********************************************************************/ - -static int init_writer_hashmap(RemoteServer *s) { - static const struct hash_ops *hash_ops[] = { - [JOURNAL_WRITE_SPLIT_NONE] = NULL, - [JOURNAL_WRITE_SPLIT_HOST] = &string_hash_ops, - }; - - assert(arg_split_mode >= 0 && arg_split_mode < (int) ELEMENTSOF(hash_ops)); - - s->writers = hashmap_new(hash_ops[arg_split_mode]); - if (!s->writers) - return log_oom(); - - return 0; -} - -static int get_writer(RemoteServer *s, const char *host, - Writer **writer) { - const void *key; - _cleanup_writer_unref_ Writer *w = NULL; - int r; - - switch(arg_split_mode) { - case JOURNAL_WRITE_SPLIT_NONE: - key = "one and only"; - break; - - case JOURNAL_WRITE_SPLIT_HOST: - assert(host); - key = host; - break; - - default: - assert_not_reached("what split mode?"); - } - - w = hashmap_get(s->writers, key); - if (w) - writer_ref(w); - else { - w = writer_new(s); - if (!w) - return log_oom(); - - if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST) { - w->hashmap_key = strdup(key); - if (!w->hashmap_key) - return log_oom(); - } - - r = open_output(w, host); - if (r < 0) - return r; - - r = hashmap_put(s->writers, w->hashmap_key ?: key, w); - if (r < 0) - return r; - } - - *writer = w; - w = NULL; - return 0; -} - -/********************************************************************** - ********************************************************************** - **********************************************************************/ - -/* This should go away as soon as µhttpd allows state to be passed around. */ -static RemoteServer *server; - -static int dispatch_raw_source_event(sd_event_source *event, - int fd, - uint32_t revents, - void *userdata); -static int dispatch_raw_source_until_block(sd_event_source *event, - void *userdata); -static int dispatch_blocking_source_event(sd_event_source *event, - void *userdata); -static int dispatch_raw_connection_event(sd_event_source *event, - int fd, - uint32_t revents, - void *userdata); -static int dispatch_http_event(sd_event_source *event, - int fd, - uint32_t revents, - void *userdata); - -static int get_source_for_fd(RemoteServer *s, - int fd, char *name, RemoteSource **source) { - Writer *writer; - int r; - - /* This takes ownership of name, but only on success. */ - - assert(fd >= 0); - assert(source); - - if (!GREEDY_REALLOC0(s->sources, s->sources_size, fd + 1)) - return log_oom(); - - r = get_writer(s, name, &writer); - if (r < 0) - return log_warning_errno(r, "Failed to get writer for source %s: %m", - name); - - if (s->sources[fd] == NULL) { - s->sources[fd] = source_new(fd, false, name, writer); - if (!s->sources[fd]) { - writer_unref(writer); - return log_oom(); - } - - s->active++; - } - - *source = s->sources[fd]; - return 0; -} - -static int remove_source(RemoteServer *s, int fd) { - RemoteSource *source; - - assert(s); - assert(fd >= 0 && fd < (ssize_t) s->sources_size); - - source = s->sources[fd]; - if (source) { - /* this closes fd too */ - source_free(source); - s->sources[fd] = NULL; - s->active--; - } - - return 0; -} - -static int add_source(RemoteServer *s, int fd, char* name, bool own_name) { - - RemoteSource *source = NULL; - int r; - - /* This takes ownership of name, even on failure, if own_name is true. */ - - assert(s); - assert(fd >= 0); - assert(name); - - if (!own_name) { - name = strdup(name); - if (!name) - return log_oom(); - } - - r = get_source_for_fd(s, fd, name, &source); - if (r < 0) { - log_error_errno(r, "Failed to create source for fd:%d (%s): %m", - fd, name); - free(name); - return r; - } - - r = sd_event_add_io(s->events, &source->event, - fd, EPOLLIN|EPOLLRDHUP|EPOLLPRI, - dispatch_raw_source_event, source); - if (r == 0) { - /* Add additional source for buffer processing. It will be - * enabled later. */ - r = sd_event_add_defer(s->events, &source->buffer_event, - dispatch_raw_source_until_block, source); - if (r == 0) - sd_event_source_set_enabled(source->buffer_event, SD_EVENT_OFF); - } else if (r == -EPERM) { - log_debug("Falling back to sd_event_add_defer for fd:%d (%s)", fd, name); - r = sd_event_add_defer(s->events, &source->event, - dispatch_blocking_source_event, source); - if (r == 0) - sd_event_source_set_enabled(source->event, SD_EVENT_ON); - } - if (r < 0) { - log_error_errno(r, "Failed to register event source for fd:%d: %m", - fd); - goto error; - } - - r = sd_event_source_set_description(source->event, name); - if (r < 0) { - log_error_errno(r, "Failed to set source name for fd:%d: %m", fd); - goto error; - } - - return 1; /* work to do */ - - error: - remove_source(s, fd); - return r; -} - -static int add_raw_socket(RemoteServer *s, int fd) { - int r; - _cleanup_close_ int fd_ = fd; - char name[sizeof("raw-socket-")-1 + DECIMAL_STR_MAX(int) + 1]; - - assert(fd >= 0); - - r = sd_event_add_io(s->events, &s->listen_event, - fd, EPOLLIN, - dispatch_raw_connection_event, s); - if (r < 0) - return r; - - xsprintf(name, "raw-socket-%d", fd); - - r = sd_event_source_set_description(s->listen_event, name); - if (r < 0) - return r; - - fd_ = -1; - s->active++; - return 0; -} - -static int setup_raw_socket(RemoteServer *s, const char *address) { - int fd; - - fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM, SOCK_CLOEXEC); - if (fd < 0) - return fd; - - return add_raw_socket(s, fd); -} - -/********************************************************************** - ********************************************************************** - **********************************************************************/ - -static int request_meta(void **connection_cls, int fd, char *hostname) { - RemoteSource *source; - Writer *writer; - int r; - - assert(connection_cls); - if (*connection_cls) - return 0; - - r = get_writer(server, hostname, &writer); - if (r < 0) - return log_warning_errno(r, "Failed to get writer for source %s: %m", - hostname); - - source = source_new(fd, true, hostname, writer); - if (!source) { - writer_unref(writer); - return log_oom(); - } - - log_debug("Added RemoteSource as connection metadata %p", source); - - *connection_cls = source; - return 0; -} - -static void request_meta_free(void *cls, - struct MHD_Connection *connection, - void **connection_cls, - enum MHD_RequestTerminationCode toe) { - RemoteSource *s; - - assert(connection_cls); - s = *connection_cls; - - if (s) { - log_debug("Cleaning up connection metadata %p", s); - source_free(s); - *connection_cls = NULL; - } -} - -static int process_http_upload( - struct MHD_Connection *connection, - const char *upload_data, - size_t *upload_data_size, - RemoteSource *source) { - - bool finished = false; - size_t remaining; - int r; - - assert(source); - - log_trace("%s: connection %p, %zu bytes", - __func__, connection, *upload_data_size); - - if (*upload_data_size) { - log_trace("Received %zu bytes", *upload_data_size); - - r = push_data(source, upload_data, *upload_data_size); - if (r < 0) - return mhd_respond_oom(connection); - - *upload_data_size = 0; - } else - finished = true; - - for (;;) { - r = process_source(source, arg_compress, arg_seal); - if (r == -EAGAIN) - break; - else if (r < 0) { - log_warning("Failed to process data for connection %p", connection); - if (r == -E2BIG) - return mhd_respondf(connection, - MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, - "Entry is too large, maximum is %u bytes.\n", - DATA_SIZE_MAX); - else - return mhd_respondf(connection, - MHD_HTTP_UNPROCESSABLE_ENTITY, - "Processing failed: %s.", strerror(-r)); - } - } - - if (!finished) - return MHD_YES; - - /* The upload is finished */ - - remaining = source_non_empty(source); - if (remaining > 0) { - log_warning("Premature EOFbyte. %zu bytes lost.", remaining); - return mhd_respondf(connection, MHD_HTTP_EXPECTATION_FAILED, - "Premature EOF. %zu bytes of trailing data not processed.", - remaining); - } - - return mhd_respond(connection, MHD_HTTP_ACCEPTED, "OK.\n"); -}; - -static int request_handler( - void *cls, - struct MHD_Connection *connection, - const char *url, - const char *method, - const char *version, - const char *upload_data, - size_t *upload_data_size, - void **connection_cls) { - - const char *header; - int r, code, fd; - _cleanup_free_ char *hostname = NULL; - - assert(connection); - assert(connection_cls); - assert(url); - assert(method); - - log_trace("Handling a connection %s %s %s", method, url, version); - - if (*connection_cls) - return process_http_upload(connection, - upload_data, upload_data_size, - *connection_cls); - - if (!streq(method, "POST")) - return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, - "Unsupported method.\n"); - - if (!streq(url, "/upload")) - return mhd_respond(connection, MHD_HTTP_NOT_FOUND, - "Not found.\n"); - - header = MHD_lookup_connection_value(connection, - MHD_HEADER_KIND, "Content-Type"); - if (!header || !streq(header, "application/vnd.fdo.journal")) - return mhd_respond(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE, - "Content-Type: application/vnd.fdo.journal" - " is required.\n"); - - { - const union MHD_ConnectionInfo *ci; - - ci = MHD_get_connection_info(connection, - MHD_CONNECTION_INFO_CONNECTION_FD); - if (!ci) { - log_error("MHD_get_connection_info failed: cannot get remote fd"); - return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - "Cannot check remote address"); - } - - fd = ci->connect_fd; - assert(fd >= 0); - } - - if (server->check_trust) { - r = check_permissions(connection, &code, &hostname); - if (r < 0) - return code; - } else { - r = getpeername_pretty(fd, false, &hostname); - if (r < 0) - return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - "Cannot check remote hostname"); - } - - assert(hostname); - - r = request_meta(connection_cls, fd, hostname); - if (r == -ENOMEM) - return respond_oom(connection); - else if (r < 0) - return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - strerror(-r)); - - hostname = NULL; - return MHD_YES; -} - -static int setup_microhttpd_server(RemoteServer *s, - int fd, - const char *key, - const char *cert, - const char *trust) { - struct MHD_OptionItem opts[] = { - { MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) request_meta_free}, - { MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) microhttpd_logger}, - { MHD_OPTION_LISTEN_SOCKET, fd}, - { MHD_OPTION_CONNECTION_MEMORY_LIMIT, 128*1024}, - { MHD_OPTION_END}, - { MHD_OPTION_END}, - { MHD_OPTION_END}, - { MHD_OPTION_END}}; - int opts_pos = 4; - int flags = - MHD_USE_DEBUG | - MHD_USE_DUAL_STACK | - MHD_USE_EPOLL_LINUX_ONLY | - MHD_USE_PEDANTIC_CHECKS | - MHD_USE_PIPE_FOR_SHUTDOWN; - - const union MHD_DaemonInfo *info; - int r, epoll_fd; - MHDDaemonWrapper *d; - - assert(fd >= 0); - - r = fd_nonblock(fd, true); - if (r < 0) - return log_error_errno(r, "Failed to make fd:%d nonblocking: %m", fd); - - if (key) { - assert(cert); - - opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_HTTPS_MEM_KEY, 0, (char*) key}; - opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_HTTPS_MEM_CERT, 0, (char*) cert}; - - flags |= MHD_USE_SSL; - - if (trust) - opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_HTTPS_MEM_TRUST, 0, (char*) trust}; - } - - d = new(MHDDaemonWrapper, 1); - if (!d) - return log_oom(); - - d->fd = (uint64_t) fd; - - d->daemon = MHD_start_daemon(flags, 0, - NULL, NULL, - request_handler, NULL, - MHD_OPTION_ARRAY, opts, - MHD_OPTION_END); - if (!d->daemon) { - log_error("Failed to start µhttp daemon"); - r = -EINVAL; - goto error; - } - - log_debug("Started MHD %s daemon on fd:%d (wrapper @ %p)", - key ? "HTTPS" : "HTTP", fd, d); - - - info = MHD_get_daemon_info(d->daemon, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY); - if (!info) { - log_error("µhttp returned NULL daemon info"); - r = -EOPNOTSUPP; - goto error; - } - - epoll_fd = info->listen_fd; - if (epoll_fd < 0) { - log_error("µhttp epoll fd is invalid"); - r = -EUCLEAN; - goto error; - } - - r = sd_event_add_io(s->events, &d->event, - epoll_fd, EPOLLIN, - dispatch_http_event, d); - if (r < 0) { - log_error_errno(r, "Failed to add event callback: %m"); - goto error; - } - - r = sd_event_source_set_description(d->event, "epoll-fd"); - if (r < 0) { - log_error_errno(r, "Failed to set source name: %m"); - goto error; - } - - r = hashmap_ensure_allocated(&s->daemons, &uint64_hash_ops); - if (r < 0) { - log_oom(); - goto error; - } - - r = hashmap_put(s->daemons, &d->fd, d); - if (r < 0) { - log_error_errno(r, "Failed to add daemon to hashmap: %m"); - goto error; - } - - s->active++; - return 0; - -error: - MHD_stop_daemon(d->daemon); - free(d->daemon); - free(d); - return r; -} - -static int setup_microhttpd_socket(RemoteServer *s, - const char *address, - const char *key, - const char *cert, - const char *trust) { - int fd; - - fd = make_socket_fd(LOG_DEBUG, address, SOCK_STREAM, SOCK_CLOEXEC); - if (fd < 0) - return fd; - - return setup_microhttpd_server(s, fd, key, cert, trust); -} - -static int dispatch_http_event(sd_event_source *event, - int fd, - uint32_t revents, - void *userdata) { - MHDDaemonWrapper *d = userdata; - int r; - - assert(d); - - r = MHD_run(d->daemon); - if (r == MHD_NO) { - log_error("MHD_run failed!"); - // XXX: unregister daemon - return -EINVAL; - } - - return 1; /* work to do */ -} - -/********************************************************************** - ********************************************************************** - **********************************************************************/ - -static int setup_signals(RemoteServer *s) { - int r; - - assert(s); - - assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, -1) >= 0); - - r = sd_event_add_signal(s->events, &s->sigterm_event, SIGTERM, NULL, s); - if (r < 0) - return r; - - r = sd_event_add_signal(s->events, &s->sigint_event, SIGINT, NULL, s); - if (r < 0) - return r; - - return 0; -} - -static int negative_fd(const char *spec) { - /* Return a non-positive number as its inverse, -EINVAL otherwise. */ - - int fd, r; - - r = safe_atoi(spec, &fd); - if (r < 0) - return r; - - if (fd > 0) - return -EINVAL; - else - return -fd; -} - -static int remoteserver_init(RemoteServer *s, - const char* key, - const char* cert, - const char* trust) { - int r, n, fd; - char **file; - - assert(s); - - if ((arg_listen_raw || arg_listen_http) && trust) { - log_error("Option --trust makes all non-HTTPS connections untrusted."); - return -EINVAL; - } - - r = sd_event_default(&s->events); - if (r < 0) - return log_error_errno(r, "Failed to allocate event loop: %m"); - - setup_signals(s); - - assert(server == NULL); - server = s; - - r = init_writer_hashmap(s); - if (r < 0) - return r; - - n = sd_listen_fds(true); - if (n < 0) - return log_error_errno(n, "Failed to read listening file descriptors from environment: %m"); - else - log_debug("Received %d descriptors", n); - - if (MAX(http_socket, https_socket) >= SD_LISTEN_FDS_START + n) { - log_error("Received fewer sockets than expected"); - return -EBADFD; - } - - for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { - if (sd_is_socket(fd, AF_UNSPEC, 0, true)) { - log_debug("Received a listening socket (fd:%d)", fd); - - if (fd == http_socket) - r = setup_microhttpd_server(s, fd, NULL, NULL, NULL); - else if (fd == https_socket) - r = setup_microhttpd_server(s, fd, key, cert, trust); - else - r = add_raw_socket(s, fd); - } else if (sd_is_socket(fd, AF_UNSPEC, 0, false)) { - char *hostname; - - r = getpeername_pretty(fd, false, &hostname); - if (r < 0) - return log_error_errno(r, "Failed to retrieve remote name: %m"); - - log_debug("Received a connection socket (fd:%d) from %s", fd, hostname); - - r = add_source(s, fd, hostname, true); - } else { - log_error("Unknown socket passed on fd:%d", fd); - - return -EINVAL; - } - - if (r < 0) - return log_error_errno(r, "Failed to register socket (fd:%d): %m", - fd); - } - - if (arg_getter) { - log_info("Spawning getter %s...", arg_getter); - fd = spawn_getter(arg_getter); - if (fd < 0) - return fd; - - r = add_source(s, fd, (char*) arg_output, false); - if (r < 0) - return r; - } - - if (arg_url) { - const char *url; - char *hostname, *p; - - if (!strstr(arg_url, "/entries")) { - if (endswith(arg_url, "/")) - url = strjoina(arg_url, "entries"); - else - url = strjoina(arg_url, "/entries"); - } - else - url = strdupa(arg_url); - - log_info("Spawning curl %s...", url); - fd = spawn_curl(url); - if (fd < 0) - return fd; - - hostname = - startswith(arg_url, "https://") ?: - startswith(arg_url, "http://") ?: - arg_url; - - hostname = strdupa(hostname); - if ((p = strchr(hostname, '/'))) - *p = '\0'; - if ((p = strchr(hostname, ':'))) - *p = '\0'; - - r = add_source(s, fd, hostname, false); - if (r < 0) - return r; - } - - if (arg_listen_raw) { - log_debug("Listening on a socket..."); - r = setup_raw_socket(s, arg_listen_raw); - if (r < 0) - return r; - } - - if (arg_listen_http) { - r = setup_microhttpd_socket(s, arg_listen_http, NULL, NULL, NULL); - if (r < 0) - return r; - } - - if (arg_listen_https) { - r = setup_microhttpd_socket(s, arg_listen_https, key, cert, trust); - if (r < 0) - return r; - } - - STRV_FOREACH(file, arg_files) { - const char *output_name; - - if (streq(*file, "-")) { - log_debug("Using standard input as source."); - - fd = STDIN_FILENO; - output_name = "stdin"; - } else { - log_debug("Reading file %s...", *file); - - fd = open(*file, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", *file); - output_name = *file; - } - - r = add_source(s, fd, (char*) output_name, false); - if (r < 0) - return r; - } - - if (s->active == 0) { - log_error("Zero sources specified"); - return -EINVAL; - } - - if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE) { - /* In this case we know what the writer will be - called, so we can create it and verify that we can - create output as expected. */ - r = get_writer(s, NULL, &s->_single_writer); - if (r < 0) - return r; - } - - return 0; -} - -static void server_destroy(RemoteServer *s) { - size_t i; - MHDDaemonWrapper *d; - - while ((d = hashmap_steal_first(s->daemons))) { - MHD_stop_daemon(d->daemon); - sd_event_source_unref(d->event); - free(d); - } - - hashmap_free(s->daemons); - - assert(s->sources_size == 0 || s->sources); - for (i = 0; i < s->sources_size; i++) - remove_source(s, i); - free(s->sources); - - writer_unref(s->_single_writer); - hashmap_free(s->writers); - - sd_event_source_unref(s->sigterm_event); - sd_event_source_unref(s->sigint_event); - sd_event_source_unref(s->listen_event); - sd_event_unref(s->events); - - /* fds that we're listening on remain open... */ -} - -/********************************************************************** - ********************************************************************** - **********************************************************************/ - -static int handle_raw_source(sd_event_source *event, - int fd, - uint32_t revents, - RemoteServer *s) { - - RemoteSource *source; - int r; - - /* Returns 1 if there might be more data pending, - * 0 if data is currently exhausted, negative on error. - */ - - assert(fd >= 0 && fd < (ssize_t) s->sources_size); - source = s->sources[fd]; - assert(source->fd == fd); - - r = process_source(source, arg_compress, arg_seal); - if (source->state == STATE_EOF) { - size_t remaining; - - log_debug("EOF reached with source fd:%d (%s)", - source->fd, source->name); - - remaining = source_non_empty(source); - if (remaining > 0) - log_notice("Premature EOF. %zu bytes lost.", remaining); - remove_source(s, source->fd); - log_debug("%zu active sources remaining", s->active); - return 0; - } else if (r == -E2BIG) { - log_notice_errno(E2BIG, "Entry too big, skipped"); - return 1; - } else if (r == -EAGAIN) { - return 0; - } else if (r < 0) { - log_debug_errno(r, "Closing connection: %m"); - remove_source(server, fd); - return 0; - } else - return 1; -} - -static int dispatch_raw_source_until_block(sd_event_source *event, - void *userdata) { - RemoteSource *source = userdata; - int r; - - /* Make sure event stays around even if source is destroyed */ - sd_event_source_ref(event); - - r = handle_raw_source(event, source->fd, EPOLLIN, server); - if (r != 1) - /* No more data for now */ - sd_event_source_set_enabled(event, SD_EVENT_OFF); - - sd_event_source_unref(event); - - return r; -} - -static int dispatch_raw_source_event(sd_event_source *event, - int fd, - uint32_t revents, - void *userdata) { - RemoteSource *source = userdata; - int r; - - assert(source->event); - assert(source->buffer_event); - - r = handle_raw_source(event, fd, EPOLLIN, server); - if (r == 1) - /* Might have more data. We need to rerun the handler - * until we are sure the buffer is exhausted. */ - sd_event_source_set_enabled(source->buffer_event, SD_EVENT_ON); - - return r; -} - -static int dispatch_blocking_source_event(sd_event_source *event, - void *userdata) { - RemoteSource *source = userdata; - - return handle_raw_source(event, source->fd, EPOLLIN, server); -} - -static int accept_connection(const char* type, int fd, - SocketAddress *addr, char **hostname) { - int fd2, r; - - log_debug("Accepting new %s connection on fd:%d", type, fd); - fd2 = accept4(fd, &addr->sockaddr.sa, &addr->size, SOCK_NONBLOCK|SOCK_CLOEXEC); - if (fd2 < 0) - return log_error_errno(errno, "accept() on fd:%d failed: %m", fd); - - switch(socket_address_family(addr)) { - case AF_INET: - case AF_INET6: { - _cleanup_free_ char *a = NULL; - char *b; - - r = socket_address_print(addr, &a); - if (r < 0) { - log_error_errno(r, "socket_address_print(): %m"); - close(fd2); - return r; - } - - r = socknameinfo_pretty(&addr->sockaddr, addr->size, &b); - if (r < 0) { - log_error_errno(r, "Resolving hostname failed: %m"); - close(fd2); - return r; - } - - log_debug("Accepted %s %s connection from %s", - type, - socket_address_family(addr) == AF_INET ? "IP" : "IPv6", - a); - - *hostname = b; - - return fd2; - }; - default: - log_error("Rejected %s connection with unsupported family %d", - type, socket_address_family(addr)); - close(fd2); - - return -EINVAL; - } -} - -static int dispatch_raw_connection_event(sd_event_source *event, - int fd, - uint32_t revents, - void *userdata) { - RemoteServer *s = userdata; - int fd2; - SocketAddress addr = { - .size = sizeof(union sockaddr_union), - .type = SOCK_STREAM, - }; - char *hostname = NULL; - - fd2 = accept_connection("raw", fd, &addr, &hostname); - if (fd2 < 0) - return fd2; - - return add_source(s, fd2, hostname, true); -} - -/********************************************************************** - ********************************************************************** - **********************************************************************/ - -static const char* const journal_write_split_mode_table[_JOURNAL_WRITE_SPLIT_MAX] = { - [JOURNAL_WRITE_SPLIT_NONE] = "none", - [JOURNAL_WRITE_SPLIT_HOST] = "host", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP(journal_write_split_mode, JournalWriteSplitMode); -static DEFINE_CONFIG_PARSE_ENUM(config_parse_write_split_mode, - journal_write_split_mode, - JournalWriteSplitMode, - "Failed to parse split mode setting"); - -static int parse_config(void) { - const ConfigTableItem items[] = { - { "Remote", "Seal", config_parse_bool, 0, &arg_seal }, - { "Remote", "SplitMode", config_parse_write_split_mode, 0, &arg_split_mode }, - { "Remote", "ServerKeyFile", config_parse_path, 0, &arg_key }, - { "Remote", "ServerCertificateFile", config_parse_path, 0, &arg_cert }, - { "Remote", "TrustedCertificateFile", config_parse_path, 0, &arg_trust }, - {}}; - - return config_parse_many(PKGSYSCONFDIR "/journal-remote.conf", - CONF_PATHS_NULSTR("systemd/journal-remote.conf.d"), - "Remote\0", config_item_table_lookup, items, - false, NULL); -} - -static void help(void) { - printf("%s [OPTIONS...] {FILE|-}...\n\n" - "Write external journal events to journal file(s).\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --url=URL Read events from systemd-journal-gatewayd at URL\n" - " --getter=COMMAND Read events from the output of COMMAND\n" - " --listen-raw=ADDR Listen for connections at ADDR\n" - " --listen-http=ADDR Listen for HTTP connections at ADDR\n" - " --listen-https=ADDR Listen for HTTPS connections at ADDR\n" - " -o --output=FILE|DIR Write output to FILE or DIR/external-*.journal\n" - " --compress[=BOOL] XZ-compress the output journal (default: yes)\n" - " --seal[=BOOL] Use event sealing (default: no)\n" - " --key=FILENAME SSL key in PEM format (default:\n" - " \"" PRIV_KEY_FILE "\")\n" - " --cert=FILENAME SSL certificate in PEM format (default:\n" - " \"" CERT_FILE "\")\n" - " --trust=FILENAME|all SSL CA certificate or disable checking (default:\n" - " \"" TRUST_FILE "\")\n" - " --gnutls-log=CATEGORY...\n" - " Specify a list of gnutls logging categories\n" - " --split-mode=none|host How many output files to create\n" - "\n" - "Note: file descriptors from sd_listen_fds() will be consumed, too.\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_URL, - ARG_LISTEN_RAW, - ARG_LISTEN_HTTP, - ARG_LISTEN_HTTPS, - ARG_GETTER, - ARG_SPLIT_MODE, - ARG_COMPRESS, - ARG_SEAL, - ARG_KEY, - ARG_CERT, - ARG_TRUST, - ARG_GNUTLS_LOG, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "url", required_argument, NULL, ARG_URL }, - { "getter", required_argument, NULL, ARG_GETTER }, - { "listen-raw", required_argument, NULL, ARG_LISTEN_RAW }, - { "listen-http", required_argument, NULL, ARG_LISTEN_HTTP }, - { "listen-https", required_argument, NULL, ARG_LISTEN_HTTPS }, - { "output", required_argument, NULL, 'o' }, - { "split-mode", required_argument, NULL, ARG_SPLIT_MODE }, - { "compress", optional_argument, NULL, ARG_COMPRESS }, - { "seal", optional_argument, NULL, ARG_SEAL }, - { "key", required_argument, NULL, ARG_KEY }, - { "cert", required_argument, NULL, ARG_CERT }, - { "trust", required_argument, NULL, ARG_TRUST }, - { "gnutls-log", required_argument, NULL, ARG_GNUTLS_LOG }, - {} - }; - - int c, r; - bool type_a, type_b; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "ho:", options, NULL)) >= 0) - switch(c) { - case 'h': - help(); - return 0 /* done */; - - case ARG_VERSION: - return version(); - - case ARG_URL: - if (arg_url) { - log_error("cannot currently set more than one --url"); - return -EINVAL; - } - - arg_url = optarg; - break; - - case ARG_GETTER: - if (arg_getter) { - log_error("cannot currently use --getter more than once"); - return -EINVAL; - } - - arg_getter = optarg; - break; - - case ARG_LISTEN_RAW: - if (arg_listen_raw) { - log_error("cannot currently use --listen-raw more than once"); - return -EINVAL; - } - - arg_listen_raw = optarg; - break; - - case ARG_LISTEN_HTTP: - if (arg_listen_http || http_socket >= 0) { - log_error("cannot currently use --listen-http more than once"); - return -EINVAL; - } - - r = negative_fd(optarg); - if (r >= 0) - http_socket = r; - else - arg_listen_http = optarg; - break; - - case ARG_LISTEN_HTTPS: - if (arg_listen_https || https_socket >= 0) { - log_error("cannot currently use --listen-https more than once"); - return -EINVAL; - } - - r = negative_fd(optarg); - if (r >= 0) - https_socket = r; - else - arg_listen_https = optarg; - - break; - - case ARG_KEY: - if (arg_key) { - log_error("Key file specified twice"); - return -EINVAL; - } - - arg_key = strdup(optarg); - if (!arg_key) - return log_oom(); - - break; - - case ARG_CERT: - if (arg_cert) { - log_error("Certificate file specified twice"); - return -EINVAL; - } - - arg_cert = strdup(optarg); - if (!arg_cert) - return log_oom(); - - break; - - case ARG_TRUST: - if (arg_trust || arg_trust_all) { - log_error("Confusing trusted CA configuration"); - return -EINVAL; - } - - if (streq(optarg, "all")) - arg_trust_all = true; - else { -#ifdef HAVE_GNUTLS - arg_trust = strdup(optarg); - if (!arg_trust) - return log_oom(); -#else - log_error("Option --trust is not available."); - return -EINVAL; -#endif - } - - break; - - case 'o': - if (arg_output) { - log_error("cannot use --output/-o more than once"); - return -EINVAL; - } - - arg_output = optarg; - break; - - case ARG_SPLIT_MODE: - arg_split_mode = journal_write_split_mode_from_string(optarg); - if (arg_split_mode == _JOURNAL_WRITE_SPLIT_INVALID) { - log_error("Invalid split mode: %s", optarg); - return -EINVAL; - } - break; - - case ARG_COMPRESS: - if (optarg) { - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --compress= parameter."); - return -EINVAL; - } - - arg_compress = !!r; - } else - arg_compress = true; - - break; - - case ARG_SEAL: - if (optarg) { - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --seal= parameter."); - return -EINVAL; - } - - arg_seal = !!r; - } else - arg_seal = true; - - break; - - case ARG_GNUTLS_LOG: { -#ifdef HAVE_GNUTLS - const char* p = optarg; - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&p, &word, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse --gnutls-log= argument: %m"); - - if (r == 0) - break; - - if (strv_push(&arg_gnutls_log, word) < 0) - return log_oom(); - - word = NULL; - } - break; -#else - log_error("Option --gnutls-log is not available."); - return -EINVAL; -#endif - } - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unknown option code."); - } - - if (optind < argc) - arg_files = argv + optind; - - type_a = arg_getter || !strv_isempty(arg_files); - type_b = arg_url - || arg_listen_raw - || arg_listen_http || arg_listen_https - || sd_listen_fds(false) > 0; - if (type_a && type_b) { - log_error("Cannot use file input or --getter with " - "--arg-listen-... or socket activation."); - return -EINVAL; - } - if (type_a) { - if (!arg_output) { - log_error("Option --output must be specified with file input or --getter."); - return -EINVAL; - } - - arg_split_mode = JOURNAL_WRITE_SPLIT_NONE; - } - - if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE - && arg_output && is_dir(arg_output, true) > 0) { - log_error("For SplitMode=none, output must be a file."); - return -EINVAL; - } - - if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST - && arg_output && is_dir(arg_output, true) <= 0) { - log_error("For SplitMode=host, output must be a directory."); - return -EINVAL; - } - - log_debug("Full config: SplitMode=%s Key=%s Cert=%s Trust=%s", - journal_write_split_mode_to_string(arg_split_mode), - strna(arg_key), - strna(arg_cert), - strna(arg_trust)); - - return 1 /* work to do */; -} - -static int load_certificates(char **key, char **cert, char **trust) { - int r; - - r = read_full_file(arg_key ?: PRIV_KEY_FILE, key, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read key from file '%s': %m", - arg_key ?: PRIV_KEY_FILE); - - r = read_full_file(arg_cert ?: CERT_FILE, cert, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read certificate from file '%s': %m", - arg_cert ?: CERT_FILE); - - if (arg_trust_all) - log_info("Certificate checking disabled."); - else { - r = read_full_file(arg_trust ?: TRUST_FILE, trust, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read CA certificate file '%s': %m", - arg_trust ?: TRUST_FILE); - } - - return 0; -} - -int main(int argc, char **argv) { - RemoteServer s = {}; - int r; - _cleanup_free_ char *key = NULL, *cert = NULL, *trust = NULL; - - log_show_color(true); - log_parse_environment(); - - r = parse_config(); - if (r < 0) - return EXIT_FAILURE; - - r = parse_argv(argc, argv); - if (r <= 0) - return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; - - - if (arg_listen_http || arg_listen_https) { - r = setup_gnutls_logger(arg_gnutls_log); - if (r < 0) - return EXIT_FAILURE; - } - - if (arg_listen_https || https_socket >= 0) - if (load_certificates(&key, &cert, &trust) < 0) - return EXIT_FAILURE; - - if (remoteserver_init(&s, key, cert, trust) < 0) - return EXIT_FAILURE; - - r = sd_event_set_watchdog(s.events, true); - if (r < 0) - log_error_errno(r, "Failed to enable watchdog: %m"); - else - log_debug("Watchdog is %s.", r > 0 ? "enabled" : "disabled"); - - log_debug("%s running as pid "PID_FMT, - program_invocation_short_name, getpid()); - sd_notify(false, - "READY=1\n" - "STATUS=Processing requests..."); - - while (s.active) { - r = sd_event_get_state(s.events); - if (r < 0) - break; - if (r == SD_EVENT_FINISHED) - break; - - r = sd_event_run(s.events, -1); - if (r < 0) { - log_error_errno(r, "Failed to run event loop: %m"); - break; - } - } - - sd_notifyf(false, - "STOPPING=1\n" - "STATUS=Shutting down after writing %" PRIu64 " entries...", s.event_count); - log_info("Finishing after writing %" PRIu64 " entries", s.event_count); - - server_destroy(&s); - - free(arg_key); - free(arg_cert); - free(arg_trust); - - return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/src/journal-remote/journal-remote.conf.in b/src/journal-remote/journal-remote.conf.in deleted file mode 100644 index 7122d63362..0000000000 --- a/src/journal-remote/journal-remote.conf.in +++ /dev/null @@ -1,6 +0,0 @@ -[Remote] -# Seal=false -# SplitMode=host -# ServerKeyFile=@CERTIFICATEROOT@/private/journal-remote.pem -# ServerCertificateFile=@CERTIFICATEROOT@/certs/journal-remote.pem -# TrustedCertificateFile=@CERTIFICATEROOT@/ca/trusted.pem diff --git a/src/journal-remote/journal-remote.h b/src/journal-remote/journal-remote.h deleted file mode 100644 index 30ad7df996..0000000000 --- a/src/journal-remote/journal-remote.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Zbigniew Jędrzejewski-Szmek - - 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 "sd-event.h" - -#include "hashmap.h" -#include "journal-remote-parse.h" -#include "journal-remote-write.h" -#include "microhttpd-util.h" - -typedef struct MHDDaemonWrapper MHDDaemonWrapper; - -struct MHDDaemonWrapper { - uint64_t fd; - struct MHD_Daemon *daemon; - - sd_event_source *event; -}; - -struct RemoteServer { - RemoteSource **sources; - size_t sources_size; - size_t active; - - sd_event *events; - sd_event_source *sigterm_event, *sigint_event, *listen_event; - - Hashmap *writers; - Writer *_single_writer; - uint64_t event_count; - - bool check_trust; - Hashmap *daemons; -}; diff --git a/src/journal-remote/journal-upload-journal.c b/src/journal-remote/journal-upload-journal.c deleted file mode 100644 index 8ce8e1895e..0000000000 --- a/src/journal-remote/journal-upload-journal.c +++ /dev/null @@ -1,422 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Zbigniew Jędrzejewski-Szmek - - 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 - -#include "alloc-util.h" -#include "journal-upload.h" -#include "log.h" -#include "utf8.h" -#include "util.h" -#include "sd-daemon.h" - -/** - * Write up to size bytes to buf. Return negative on error, and number of - * bytes written otherwise. The last case is a kind of an error too. - */ -static ssize_t write_entry(char *buf, size_t size, Uploader *u) { - int r; - size_t pos = 0; - - assert(size <= SSIZE_MAX); - - for (;;) { - - switch(u->entry_state) { - case ENTRY_CURSOR: { - u->current_cursor = mfree(u->current_cursor); - - r = sd_journal_get_cursor(u->journal, &u->current_cursor); - if (r < 0) - return log_error_errno(r, "Failed to get cursor: %m"); - - r = snprintf(buf + pos, size - pos, - "__CURSOR=%s\n", u->current_cursor); - if (pos + r > size) - /* not enough space */ - return pos; - - u->entry_state++; - - if (pos + r == size) { - /* exactly one character short, but we don't need it */ - buf[size - 1] = '\n'; - return size; - } - - pos += r; - } /* fall through */ - - case ENTRY_REALTIME: { - usec_t realtime; - - r = sd_journal_get_realtime_usec(u->journal, &realtime); - if (r < 0) - return log_error_errno(r, "Failed to get realtime timestamp: %m"); - - r = snprintf(buf + pos, size - pos, - "__REALTIME_TIMESTAMP="USEC_FMT"\n", realtime); - if (r + pos > size) - /* not enough space */ - return pos; - - u->entry_state++; - - if (r + pos == size) { - /* exactly one character short, but we don't need it */ - buf[size - 1] = '\n'; - return size; - } - - pos += r; - } /* fall through */ - - case ENTRY_MONOTONIC: { - usec_t monotonic; - sd_id128_t boot_id; - - r = sd_journal_get_monotonic_usec(u->journal, &monotonic, &boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get monotonic timestamp: %m"); - - r = snprintf(buf + pos, size - pos, - "__MONOTONIC_TIMESTAMP="USEC_FMT"\n", monotonic); - if (r + pos > size) - /* not enough space */ - return pos; - - u->entry_state++; - - if (r + pos == size) { - /* exactly one character short, but we don't need it */ - buf[size - 1] = '\n'; - return size; - } - - pos += r; - } /* fall through */ - - case ENTRY_BOOT_ID: { - sd_id128_t boot_id; - char sid[33]; - - r = sd_journal_get_monotonic_usec(u->journal, NULL, &boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get monotonic timestamp: %m"); - - r = snprintf(buf + pos, size - pos, - "_BOOT_ID=%s\n", sd_id128_to_string(boot_id, sid)); - if (r + pos > size) - /* not enough space */ - return pos; - - u->entry_state++; - - if (r + pos == size) { - /* exactly one character short, but we don't need it */ - buf[size - 1] = '\n'; - return size; - } - - pos += r; - } /* fall through */ - - case ENTRY_NEW_FIELD: { - u->field_pos = 0; - - r = sd_journal_enumerate_data(u->journal, - &u->field_data, - &u->field_length); - if (r < 0) - return log_error_errno(r, "Failed to move to next field in entry: %m"); - else if (r == 0) { - u->entry_state = ENTRY_OUTRO; - continue; - } - - if (!utf8_is_printable_newline(u->field_data, - u->field_length, false)) { - u->entry_state = ENTRY_BINARY_FIELD_START; - continue; - } - - u->entry_state++; - } /* fall through */ - - case ENTRY_TEXT_FIELD: - case ENTRY_BINARY_FIELD: { - bool done; - size_t tocopy; - - done = size - pos > u->field_length - u->field_pos; - if (done) - tocopy = u->field_length - u->field_pos; - else - tocopy = size - pos; - - memcpy(buf + pos, - (char*) u->field_data + u->field_pos, - tocopy); - - if (done) { - buf[pos + tocopy] = '\n'; - pos += tocopy + 1; - u->entry_state = ENTRY_NEW_FIELD; - continue; - } else { - u->field_pos += tocopy; - return size; - } - } - - case ENTRY_BINARY_FIELD_START: { - const char *c; - size_t len; - - c = memchr(u->field_data, '=', u->field_length); - if (!c || c == u->field_data) { - log_error("Invalid field."); - return -EINVAL; - } - - len = c - (const char*)u->field_data; - - /* need space for label + '\n' */ - if (size - pos < len + 1) - return pos; - - memcpy(buf + pos, u->field_data, len); - buf[pos + len] = '\n'; - pos += len + 1; - - u->field_pos = len + 1; - u->entry_state++; - } /* fall through */ - - case ENTRY_BINARY_FIELD_SIZE: { - uint64_t le64; - - /* need space for uint64_t */ - if (size - pos < 8) - return pos; - - le64 = htole64(u->field_length - u->field_pos); - memcpy(buf + pos, &le64, 8); - pos += 8; - - u->entry_state++; - continue; - } - - case ENTRY_OUTRO: - /* need space for '\n' */ - if (size - pos < 1) - return pos; - - buf[pos++] = '\n'; - u->entry_state++; - u->entries_sent++; - - return pos; - - default: - assert_not_reached("WTF?"); - } - } - assert_not_reached("WTF?"); -} - -static inline void check_update_watchdog(Uploader *u) { - usec_t after; - usec_t elapsed_time; - - if (u->watchdog_usec <= 0) - return; - - after = now(CLOCK_MONOTONIC); - elapsed_time = usec_sub(after, u->watchdog_timestamp); - if (elapsed_time > u->watchdog_usec / 2) { - log_debug("Update watchdog timer"); - sd_notify(false, "WATCHDOG=1"); - u->watchdog_timestamp = after; - } -} - -static size_t journal_input_callback(void *buf, size_t size, size_t nmemb, void *userp) { - Uploader *u = userp; - int r; - sd_journal *j; - size_t filled = 0; - ssize_t w; - - assert(u); - assert(nmemb <= SSIZE_MAX / size); - - check_update_watchdog(u); - - j = u->journal; - - while (j && filled < size * nmemb) { - if (u->entry_state == ENTRY_DONE) { - r = sd_journal_next(j); - if (r < 0) { - log_error_errno(r, "Failed to move to next entry in journal: %m"); - return CURL_READFUNC_ABORT; - } else if (r == 0) { - if (u->input_event) - log_debug("No more entries, waiting for journal."); - else { - log_info("No more entries, closing journal."); - close_journal_input(u); - } - - u->uploading = false; - - break; - } - - u->entry_state = ENTRY_CURSOR; - } - - w = write_entry((char*)buf + filled, size * nmemb - filled, u); - if (w < 0) - return CURL_READFUNC_ABORT; - filled += w; - - if (filled == 0) { - log_error("Buffer space is too small to write entry."); - return CURL_READFUNC_ABORT; - } else if (u->entry_state != ENTRY_DONE) - /* This means that all available space was used up */ - break; - - log_debug("Entry %zu (%s) has been uploaded.", - u->entries_sent, u->current_cursor); - } - - return filled; -} - -void close_journal_input(Uploader *u) { - assert(u); - - if (u->journal) { - log_debug("Closing journal input."); - - sd_journal_close(u->journal); - u->journal = NULL; - } - u->timeout = 0; -} - -static int process_journal_input(Uploader *u, int skip) { - int r; - - if (u->uploading) - return 0; - - r = sd_journal_next_skip(u->journal, skip); - if (r < 0) - return log_error_errno(r, "Failed to skip to next entry: %m"); - else if (r < skip) - return 0; - - /* have data */ - u->entry_state = ENTRY_CURSOR; - return start_upload(u, journal_input_callback, u); -} - -int check_journal_input(Uploader *u) { - if (u->input_event) { - int r; - - r = sd_journal_process(u->journal); - if (r < 0) { - log_error_errno(r, "Failed to process journal: %m"); - close_journal_input(u); - return r; - } - - if (r == SD_JOURNAL_NOP) - return 0; - } - - return process_journal_input(u, 1); -} - -static int dispatch_journal_input(sd_event_source *event, - int fd, - uint32_t revents, - void *userp) { - Uploader *u = userp; - - assert(u); - - if (u->uploading) - return 0; - - log_debug("Detected journal input, checking for new data."); - return check_journal_input(u); -} - -int open_journal_for_upload(Uploader *u, - sd_journal *j, - const char *cursor, - bool after_cursor, - bool follow) { - int fd, r, events; - - u->journal = j; - - sd_journal_set_data_threshold(j, 0); - - if (follow) { - fd = sd_journal_get_fd(j); - if (fd < 0) - return log_error_errno(fd, "sd_journal_get_fd failed: %m"); - - events = sd_journal_get_events(j); - - r = sd_journal_reliable_fd(j); - assert(r >= 0); - if (r > 0) - u->timeout = -1; - else - u->timeout = JOURNAL_UPLOAD_POLL_TIMEOUT; - - r = sd_event_add_io(u->events, &u->input_event, - fd, events, dispatch_journal_input, u); - if (r < 0) - return log_error_errno(r, "Failed to register input event: %m"); - - log_debug("Listening for journal events on fd:%d, timeout %d", - fd, u->timeout == (uint64_t) -1 ? -1 : (int) u->timeout); - } else - log_debug("Not listening for journal events."); - - if (cursor) { - r = sd_journal_seek_cursor(j, cursor); - if (r < 0) - return log_error_errno(r, "Failed to seek to cursor %s: %m", - cursor); - } - - return process_journal_input(u, 1 + !!after_cursor); -} diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c deleted file mode 100644 index 4647cfdeb3..0000000000 --- a/src/journal-remote/journal-upload.c +++ /dev/null @@ -1,880 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Zbigniew Jędrzejewski-Szmek - - 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 -#include -#include -#include - -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "conf-parser.h" -#include "def.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "glob-util.h" -#include "journal-upload.h" -#include "log.h" -#include "mkdir.h" -#include "parse-util.h" -#include "sigbus.h" -#include "signal-util.h" -#include "string-util.h" -#include "util.h" - -#define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-upload.pem" -#define CERT_FILE CERTIFICATE_ROOT "/certs/journal-upload.pem" -#define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem" -#define DEFAULT_PORT 19532 - -static const char* arg_url = NULL; -static const char *arg_key = NULL; -static const char *arg_cert = NULL; -static const char *arg_trust = NULL; -static const char *arg_directory = NULL; -static char **arg_file = NULL; -static const char *arg_cursor = NULL; -static bool arg_after_cursor = false; -static int arg_journal_type = 0; -static const char *arg_machine = NULL; -static bool arg_merge = false; -static int arg_follow = -1; -static const char *arg_save_state = NULL; - -static void close_fd_input(Uploader *u); - -#define SERVER_ANSWER_KEEP 2048 - -#define STATE_FILE "/var/lib/systemd/journal-upload/state" - -#define easy_setopt(curl, opt, value, level, cmd) \ - do { \ - code = curl_easy_setopt(curl, opt, value); \ - if (code) { \ - log_full(level, \ - "curl_easy_setopt " #opt " failed: %s", \ - curl_easy_strerror(code)); \ - cmd; \ - } \ - } while (0) - -static size_t output_callback(char *buf, - size_t size, - size_t nmemb, - void *userp) { - Uploader *u = userp; - - assert(u); - - log_debug("The server answers (%zu bytes): %.*s", - size*nmemb, (int)(size*nmemb), buf); - - if (nmemb && !u->answer) { - u->answer = strndup(buf, size*nmemb); - if (!u->answer) - log_warning_errno(ENOMEM, "Failed to store server answer (%zu bytes): %m", - size*nmemb); - } - - return size * nmemb; -} - -static int check_cursor_updating(Uploader *u) { - _cleanup_free_ char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - if (!u->state_file) - return 0; - - r = mkdir_parents(u->state_file, 0755); - if (r < 0) - return log_error_errno(r, "Cannot create parent directory of state file %s: %m", - u->state_file); - - r = fopen_temporary(u->state_file, &f, &temp_path); - if (r < 0) - return log_error_errno(r, "Cannot save state to %s: %m", - u->state_file); - unlink(temp_path); - - return 0; -} - -static int update_cursor_state(Uploader *u) { - _cleanup_free_ char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - if (!u->state_file || !u->last_cursor) - return 0; - - r = fopen_temporary(u->state_file, &f, &temp_path); - if (r < 0) - goto fail; - - fprintf(f, - "# This is private data. Do not parse.\n" - "LAST_CURSOR=%s\n", - u->last_cursor); - - r = fflush_and_check(f); - if (r < 0) - goto fail; - - if (rename(temp_path, u->state_file) < 0) { - r = -errno; - goto fail; - } - - return 0; - -fail: - if (temp_path) - (void) unlink(temp_path); - - (void) unlink(u->state_file); - - return log_error_errno(r, "Failed to save state %s: %m", u->state_file); -} - -static int load_cursor_state(Uploader *u) { - int r; - - if (!u->state_file) - return 0; - - r = parse_env_file(u->state_file, NEWLINE, - "LAST_CURSOR", &u->last_cursor, - NULL); - - if (r == -ENOENT) - log_debug("State file %s is not present.", u->state_file); - else if (r < 0) - return log_error_errno(r, "Failed to read state file %s: %m", - u->state_file); - else - log_debug("Last cursor was %s", u->last_cursor); - - return 0; -} - - - -int start_upload(Uploader *u, - size_t (*input_callback)(void *ptr, - size_t size, - size_t nmemb, - void *userdata), - void *data) { - CURLcode code; - - assert(u); - assert(input_callback); - - if (!u->header) { - struct curl_slist *h; - - h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal"); - if (!h) - return log_oom(); - - h = curl_slist_append(h, "Transfer-Encoding: chunked"); - if (!h) { - curl_slist_free_all(h); - return log_oom(); - } - - h = curl_slist_append(h, "Accept: text/plain"); - if (!h) { - curl_slist_free_all(h); - return log_oom(); - } - - u->header = h; - } - - if (!u->easy) { - CURL *curl; - - curl = curl_easy_init(); - if (!curl) { - log_error("Call to curl_easy_init failed."); - return -ENOSR; - } - - /* tell it to POST to the URL */ - easy_setopt(curl, CURLOPT_POST, 1L, - LOG_ERR, return -EXFULL); - - easy_setopt(curl, CURLOPT_ERRORBUFFER, u->error, - LOG_ERR, return -EXFULL); - - /* set where to write to */ - easy_setopt(curl, CURLOPT_WRITEFUNCTION, output_callback, - LOG_ERR, return -EXFULL); - - easy_setopt(curl, CURLOPT_WRITEDATA, data, - LOG_ERR, return -EXFULL); - - /* set where to read from */ - easy_setopt(curl, CURLOPT_READFUNCTION, input_callback, - LOG_ERR, return -EXFULL); - - easy_setopt(curl, CURLOPT_READDATA, data, - LOG_ERR, return -EXFULL); - - /* use our special own mime type and chunked transfer */ - easy_setopt(curl, CURLOPT_HTTPHEADER, u->header, - LOG_ERR, return -EXFULL); - - if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) - /* enable verbose for easier tracing */ - easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, ); - - easy_setopt(curl, CURLOPT_USERAGENT, - "systemd-journal-upload " PACKAGE_STRING, - LOG_WARNING, ); - - if (arg_key || startswith(u->url, "https://")) { - easy_setopt(curl, CURLOPT_SSLKEY, arg_key ?: PRIV_KEY_FILE, - LOG_ERR, return -EXFULL); - easy_setopt(curl, CURLOPT_SSLCERT, arg_cert ?: CERT_FILE, - LOG_ERR, return -EXFULL); - } - - if (streq_ptr(arg_trust, "all")) - easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0, - LOG_ERR, return -EUCLEAN); - else if (arg_trust || startswith(u->url, "https://")) - easy_setopt(curl, CURLOPT_CAINFO, arg_trust ?: TRUST_FILE, - LOG_ERR, return -EXFULL); - - if (arg_key || arg_trust) - easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1, - LOG_WARNING, ); - - u->easy = curl; - } else { - /* truncate the potential old error message */ - u->error[0] = '\0'; - - free(u->answer); - u->answer = 0; - } - - /* upload to this place */ - code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url); - if (code) { - log_error("curl_easy_setopt CURLOPT_URL failed: %s", - curl_easy_strerror(code)); - return -EXFULL; - } - - u->uploading = true; - - return 0; -} - -static size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *userp) { - Uploader *u = userp; - - ssize_t r; - - assert(u); - assert(nmemb <= SSIZE_MAX / size); - - if (u->input < 0) - return 0; - - r = read(u->input, buf, size * nmemb); - log_debug("%s: allowed %zu, read %zd", __func__, size*nmemb, r); - - if (r > 0) - return r; - - u->uploading = false; - if (r == 0) { - log_debug("Reached EOF"); - close_fd_input(u); - return 0; - } else { - log_error_errno(errno, "Aborting transfer after read error on input: %m."); - return CURL_READFUNC_ABORT; - } -} - -static void close_fd_input(Uploader *u) { - assert(u); - - if (u->input >= 0) - close_nointr(u->input); - u->input = -1; - u->timeout = 0; -} - -static int dispatch_fd_input(sd_event_source *event, - int fd, - uint32_t revents, - void *userp) { - Uploader *u = userp; - - assert(u); - assert(fd >= 0); - - if (revents & EPOLLHUP) { - log_debug("Received HUP"); - close_fd_input(u); - return 0; - } - - if (!(revents & EPOLLIN)) { - log_warning("Unexpected poll event %"PRIu32".", revents); - return -EINVAL; - } - - if (u->uploading) { - log_warning("dispatch_fd_input called when uploading, ignoring."); - return 0; - } - - return start_upload(u, fd_input_callback, u); -} - -static int open_file_for_upload(Uploader *u, const char *filename) { - int fd, r = 0; - - if (streq(filename, "-")) - fd = STDIN_FILENO; - else { - fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", filename); - } - - u->input = fd; - - if (arg_follow) { - r = sd_event_add_io(u->events, &u->input_event, - fd, EPOLLIN, dispatch_fd_input, u); - if (r < 0) { - if (r != -EPERM || arg_follow > 0) - return log_error_errno(r, "Failed to register input event: %m"); - - /* Normal files should just be consumed without polling. */ - r = start_upload(u, fd_input_callback, u); - } - } - - return r; -} - -static int dispatch_sigterm(sd_event_source *event, - const struct signalfd_siginfo *si, - void *userdata) { - Uploader *u = userdata; - - assert(u); - - log_received_signal(LOG_INFO, si); - - close_fd_input(u); - close_journal_input(u); - - sd_event_exit(u->events, 0); - return 0; -} - -static int setup_signals(Uploader *u) { - int r; - - assert(u); - - assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, -1) >= 0); - - r = sd_event_add_signal(u->events, &u->sigterm_event, SIGTERM, dispatch_sigterm, u); - if (r < 0) - return r; - - r = sd_event_add_signal(u->events, &u->sigint_event, SIGINT, dispatch_sigterm, u); - if (r < 0) - return r; - - return 0; -} - -static int setup_uploader(Uploader *u, const char *url, const char *state_file) { - int r; - const char *host, *proto = ""; - - assert(u); - assert(url); - - memzero(u, sizeof(Uploader)); - u->input = -1; - - if (!(host = startswith(url, "http://")) && !(host = startswith(url, "https://"))) { - host = url; - proto = "https://"; - } - - if (strchr(host, ':')) - u->url = strjoin(proto, url, "/upload", NULL); - else { - char *t; - size_t x; - - t = strdupa(url); - x = strlen(t); - while (x > 0 && t[x - 1] == '/') - t[x - 1] = '\0'; - - u->url = strjoin(proto, t, ":" STRINGIFY(DEFAULT_PORT), "/upload", NULL); - } - if (!u->url) - return log_oom(); - - u->state_file = state_file; - - r = sd_event_default(&u->events); - if (r < 0) - return log_error_errno(r, "sd_event_default failed: %m"); - - r = setup_signals(u); - if (r < 0) - return log_error_errno(r, "Failed to set up signals: %m"); - - (void) sd_watchdog_enabled(false, &u->watchdog_usec); - - return load_cursor_state(u); -} - -static void destroy_uploader(Uploader *u) { - assert(u); - - curl_easy_cleanup(u->easy); - curl_slist_free_all(u->header); - free(u->answer); - - free(u->last_cursor); - free(u->current_cursor); - - free(u->url); - - u->input_event = sd_event_source_unref(u->input_event); - - close_fd_input(u); - close_journal_input(u); - - sd_event_source_unref(u->sigterm_event); - sd_event_source_unref(u->sigint_event); - sd_event_unref(u->events); -} - -static int perform_upload(Uploader *u) { - CURLcode code; - long status; - - assert(u); - - u->watchdog_timestamp = now(CLOCK_MONOTONIC); - code = curl_easy_perform(u->easy); - if (code) { - if (u->error[0]) - log_error("Upload to %s failed: %.*s", - u->url, (int) sizeof(u->error), u->error); - else - log_error("Upload to %s failed: %s", - u->url, curl_easy_strerror(code)); - return -EIO; - } - - code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status); - if (code) { - log_error("Failed to retrieve response code: %s", - curl_easy_strerror(code)); - return -EUCLEAN; - } - - if (status >= 300) { - log_error("Upload to %s failed with code %ld: %s", - u->url, status, strna(u->answer)); - return -EIO; - } else if (status < 200) { - log_error("Upload to %s finished with unexpected code %ld: %s", - u->url, status, strna(u->answer)); - return -EIO; - } else - log_debug("Upload finished successfully with code %ld: %s", - status, strna(u->answer)); - - free(u->last_cursor); - u->last_cursor = u->current_cursor; - u->current_cursor = NULL; - - return update_cursor_state(u); -} - -static int parse_config(void) { - const ConfigTableItem items[] = { - { "Upload", "URL", config_parse_string, 0, &arg_url }, - { "Upload", "ServerKeyFile", config_parse_path, 0, &arg_key }, - { "Upload", "ServerCertificateFile", config_parse_path, 0, &arg_cert }, - { "Upload", "TrustedCertificateFile", config_parse_path, 0, &arg_trust }, - {}}; - - return config_parse_many(PKGSYSCONFDIR "/journal-upload.conf", - CONF_PATHS_NULSTR("systemd/journal-upload.conf.d"), - "Upload\0", config_item_table_lookup, items, - false, NULL); -} - -static void help(void) { - printf("%s -u URL {FILE|-}...\n\n" - "Upload journal events to a remote server.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -u --url=URL Upload to this address (default port " - STRINGIFY(DEFAULT_PORT) ")\n" - " --key=FILENAME Specify key in PEM format (default:\n" - " \"" PRIV_KEY_FILE "\")\n" - " --cert=FILENAME Specify certificate in PEM format (default:\n" - " \"" CERT_FILE "\")\n" - " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n" - " \"" TRUST_FILE "\")\n" - " --system Use the system journal\n" - " --user Use the user journal for the current user\n" - " -m --merge Use all available journals\n" - " -M --machine=CONTAINER Operate on local container\n" - " -D --directory=PATH Use journal files from directory\n" - " --file=PATH Use this journal file\n" - " --cursor=CURSOR Start at the specified cursor\n" - " --after-cursor=CURSOR Start after the specified cursor\n" - " --follow[=BOOL] Do [not] wait for input\n" - " --save-state[=FILE] Save uploaded cursors (default \n" - " " STATE_FILE ")\n" - " -h --help Show this help and exit\n" - " --version Print version string and exit\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_KEY, - ARG_CERT, - ARG_TRUST, - ARG_USER, - ARG_SYSTEM, - ARG_FILE, - ARG_CURSOR, - ARG_AFTER_CURSOR, - ARG_FOLLOW, - ARG_SAVE_STATE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "url", required_argument, NULL, 'u' }, - { "key", required_argument, NULL, ARG_KEY }, - { "cert", required_argument, NULL, ARG_CERT }, - { "trust", required_argument, NULL, ARG_TRUST }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "merge", no_argument, NULL, 'm' }, - { "machine", required_argument, NULL, 'M' }, - { "directory", required_argument, NULL, 'D' }, - { "file", required_argument, NULL, ARG_FILE }, - { "cursor", required_argument, NULL, ARG_CURSOR }, - { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR }, - { "follow", optional_argument, NULL, ARG_FOLLOW }, - { "save-state", optional_argument, NULL, ARG_SAVE_STATE }, - {} - }; - - int c, r; - - assert(argc >= 0); - assert(argv); - - opterr = 0; - - while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0) - switch(c) { - case 'h': - help(); - return 0 /* done */; - - case ARG_VERSION: - return version(); - - case 'u': - if (arg_url) { - log_error("cannot use more than one --url"); - return -EINVAL; - } - - arg_url = optarg; - break; - - case ARG_KEY: - if (arg_key) { - log_error("cannot use more than one --key"); - return -EINVAL; - } - - arg_key = optarg; - break; - - case ARG_CERT: - if (arg_cert) { - log_error("cannot use more than one --cert"); - return -EINVAL; - } - - arg_cert = optarg; - break; - - case ARG_TRUST: - if (arg_trust) { - log_error("cannot use more than one --trust"); - return -EINVAL; - } - - arg_trust = optarg; - break; - - case ARG_SYSTEM: - arg_journal_type |= SD_JOURNAL_SYSTEM; - break; - - case ARG_USER: - arg_journal_type |= SD_JOURNAL_CURRENT_USER; - break; - - case 'm': - arg_merge = true; - break; - - case 'M': - if (arg_machine) { - log_error("cannot use more than one --machine/-M"); - return -EINVAL; - } - - arg_machine = optarg; - break; - - case 'D': - if (arg_directory) { - log_error("cannot use more than one --directory/-D"); - return -EINVAL; - } - - arg_directory = optarg; - break; - - case ARG_FILE: - r = glob_extend(&arg_file, optarg); - if (r < 0) - return log_error_errno(r, "Failed to add paths: %m"); - break; - - case ARG_CURSOR: - if (arg_cursor) { - log_error("cannot use more than one --cursor/--after-cursor"); - return -EINVAL; - } - - arg_cursor = optarg; - break; - - case ARG_AFTER_CURSOR: - if (arg_cursor) { - log_error("cannot use more than one --cursor/--after-cursor"); - return -EINVAL; - } - - arg_cursor = optarg; - arg_after_cursor = true; - break; - - case ARG_FOLLOW: - if (optarg) { - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --follow= parameter."); - return -EINVAL; - } - - arg_follow = !!r; - } else - arg_follow = true; - - break; - - case ARG_SAVE_STATE: - arg_save_state = optarg ?: STATE_FILE; - break; - - case '?': - log_error("Unknown option %s.", argv[optind-1]); - return -EINVAL; - - case ':': - log_error("Missing argument to %s.", argv[optind-1]); - return -EINVAL; - - default: - assert_not_reached("Unhandled option code."); - } - - if (!arg_url) { - log_error("Required --url/-u option missing."); - return -EINVAL; - } - - if (!!arg_key != !!arg_cert) { - log_error("Options --key and --cert must be used together."); - return -EINVAL; - } - - if (optind < argc && (arg_directory || arg_file || arg_machine || arg_journal_type)) { - log_error("Input arguments make no sense with journal input."); - return -EINVAL; - } - - return 1; -} - -static int open_journal(sd_journal **j) { - int r; - - if (arg_directory) - r = sd_journal_open_directory(j, arg_directory, arg_journal_type); - else if (arg_file) - r = sd_journal_open_files(j, (const char**) arg_file, 0); - else if (arg_machine) - r = sd_journal_open_container(j, arg_machine, 0); - else - r = sd_journal_open(j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type); - if (r < 0) - log_error_errno(r, "Failed to open %s: %m", - arg_directory ? arg_directory : arg_file ? "files" : "journal"); - return r; -} - -int main(int argc, char **argv) { - Uploader u; - int r; - bool use_journal; - - log_show_color(true); - log_parse_environment(); - - r = parse_config(); - if (r < 0) - goto finish; - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - sigbus_install(); - - r = setup_uploader(&u, arg_url, arg_save_state); - if (r < 0) - goto cleanup; - - sd_event_set_watchdog(u.events, true); - - r = check_cursor_updating(&u); - if (r < 0) - goto cleanup; - - log_debug("%s running as pid "PID_FMT, - program_invocation_short_name, getpid()); - - use_journal = optind >= argc; - if (use_journal) { - sd_journal *j; - r = open_journal(&j); - if (r < 0) - goto finish; - r = open_journal_for_upload(&u, j, - arg_cursor ?: u.last_cursor, - arg_cursor ? arg_after_cursor : true, - !!arg_follow); - if (r < 0) - goto finish; - } - - sd_notify(false, - "READY=1\n" - "STATUS=Processing input..."); - - for (;;) { - r = sd_event_get_state(u.events); - if (r < 0) - break; - if (r == SD_EVENT_FINISHED) - break; - - if (use_journal) { - if (!u.journal) - break; - - r = check_journal_input(&u); - } else if (u.input < 0 && !use_journal) { - if (optind >= argc) - break; - - log_debug("Using %s as input.", argv[optind]); - r = open_file_for_upload(&u, argv[optind++]); - } - if (r < 0) - goto cleanup; - - if (u.uploading) { - r = perform_upload(&u); - if (r < 0) - break; - } - - r = sd_event_run(u.events, u.timeout); - if (r < 0) { - log_error_errno(r, "Failed to run event loop: %m"); - break; - } - } - -cleanup: - sd_notify(false, - "STOPPING=1\n" - "STATUS=Shutting down..."); - - destroy_uploader(&u); - -finish: - return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/src/journal-remote/journal-upload.conf.in b/src/journal-remote/journal-upload.conf.in deleted file mode 100644 index c5670682e8..0000000000 --- a/src/journal-remote/journal-upload.conf.in +++ /dev/null @@ -1,5 +0,0 @@ -[Upload] -# URL= -# ServerKeyFile=@CERTIFICATEROOT@/private/journal-upload.pem -# ServerCertificateFile=@CERTIFICATEROOT@/certs/journal-upload.pem -# TrustedCertificateFile=@CERTIFICATEROOT@/ca/trusted.pem diff --git a/src/journal-remote/journal-upload.h b/src/journal-remote/journal-upload.h deleted file mode 100644 index 5711905f86..0000000000 --- a/src/journal-remote/journal-upload.h +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -#include - -#include "sd-event.h" -#include "sd-journal.h" -#include "time-util.h" - -typedef enum { - ENTRY_CURSOR = 0, /* Nothing actually written yet. */ - ENTRY_REALTIME, - ENTRY_MONOTONIC, - ENTRY_BOOT_ID, - ENTRY_NEW_FIELD, /* In between fields. */ - ENTRY_TEXT_FIELD, /* In the middle of a text field. */ - ENTRY_BINARY_FIELD_START, /* Writing the name of a binary field. */ - ENTRY_BINARY_FIELD_SIZE, /* Writing the size of a binary field. */ - ENTRY_BINARY_FIELD, /* In the middle of a binary field. */ - ENTRY_OUTRO, /* Writing '\n' */ - ENTRY_DONE, /* Need to move to a new field. */ -} entry_state; - -typedef struct Uploader { - sd_event *events; - sd_event_source *sigint_event, *sigterm_event; - - char *url; - CURL *easy; - bool uploading; - char error[CURL_ERROR_SIZE]; - struct curl_slist *header; - char *answer; - - sd_event_source *input_event; - uint64_t timeout; - - /* fd stuff */ - int input; - - /* journal stuff */ - sd_journal* journal; - - entry_state entry_state; - const void *field_data; - size_t field_pos, field_length; - - /* general metrics */ - const char *state_file; - - size_t entries_sent; - char *last_cursor, *current_cursor; - usec_t watchdog_timestamp; - usec_t watchdog_usec; -} Uploader; - -#define JOURNAL_UPLOAD_POLL_TIMEOUT (10 * USEC_PER_SEC) - -int start_upload(Uploader *u, - size_t (*input_callback)(void *ptr, - size_t size, - size_t nmemb, - void *userdata), - void *data); - -int open_journal_for_upload(Uploader *u, - sd_journal *j, - const char *cursor, - bool after_cursor, - bool follow); -void close_journal_input(Uploader *u); -int check_journal_input(Uploader *u); diff --git a/src/journal-remote/log-generator.py b/src/journal-remote/log-generator.py deleted file mode 100755 index fd6964e758..0000000000 --- a/src/journal-remote/log-generator.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/python -from __future__ import print_function -import sys -import argparse - -PARSER = argparse.ArgumentParser() -PARSER.add_argument('n', type=int) -PARSER.add_argument('--dots', action='store_true') -PARSER.add_argument('--data-size', type=int, default=4000) -PARSER.add_argument('--data-type', choices={'random', 'simple'}) -OPTIONS = PARSER.parse_args() - -template = """\ -__CURSOR=s=6863c726210b4560b7048889d8ada5c5;i=3e931;b=f446871715504074bf7049ef0718fa93;m={m:x};t=4fd05c -__REALTIME_TIMESTAMP={realtime_ts} -__MONOTONIC_TIMESTAMP={monotonic_ts} -_BOOT_ID=f446871715504074bf7049ef0718fa93 -_TRANSPORT=syslog -PRIORITY={priority} -SYSLOG_FACILITY={facility} -SYSLOG_IDENTIFIER=/USR/SBIN/CRON -MESSAGE={message} -_UID=0 -_GID=0 -_MACHINE_ID=69121ca41d12c1b69a7960174c27b618 -_HOSTNAME=hostname -SYSLOG_PID=25721 -_PID=25721 -_SOURCE_REALTIME_TIMESTAMP={source_realtime_ts} -DATA={data} -""" - -m = 0x198603b12d7 -realtime_ts = 1404101101501873 -monotonic_ts = 1753961140951 -source_realtime_ts = 1404101101483516 -priority = 3 -facility = 6 - -src = open('/dev/urandom', 'rb') - -bytes = 0 -counter = 0 - -for i in range(OPTIONS.n): - message = repr(src.read(2000)) - if OPTIONS.data_type == 'random': - data = repr(src.read(OPTIONS.data_size)) - else: - # keep the pattern non-repeating so we get a different blob every time - data = '{:0{}}'.format(counter, OPTIONS.data_size) - counter += 1 - - entry = template.format(m=m, - realtime_ts=realtime_ts, - monotonic_ts=monotonic_ts, - source_realtime_ts=source_realtime_ts, - priority=priority, - facility=facility, - message=message, - data=data) - m += 1 - realtime_ts += 1 - monotonic_ts += 1 - source_realtime_ts += 1 - - bytes += len(entry) - - print(entry) - - if OPTIONS.dots: - print('.', file=sys.stderr, end='', flush=True) - -if OPTIONS.dots: - print(file=sys.stderr) -print('Wrote {} bytes'.format(bytes), file=sys.stderr) diff --git a/src/journal-remote/microhttpd-util.c b/src/journal-remote/microhttpd-util.c deleted file mode 100644 index c65c43186f..0000000000 --- a/src/journal-remote/microhttpd-util.c +++ /dev/null @@ -1,327 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 Lennart Poettering - Copyright 2012 Zbigniew Jędrzejewski-Szmek - - 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 -#include - -#ifdef HAVE_GNUTLS -#include -#include -#endif - -#include "alloc-util.h" -#include "log.h" -#include "macro.h" -#include "microhttpd-util.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -void microhttpd_logger(void *arg, const char *fmt, va_list ap) { - char *f; - - f = strjoina("microhttpd: ", fmt); - - DISABLE_WARNING_FORMAT_NONLITERAL; - log_internalv(LOG_INFO, 0, NULL, 0, NULL, f, ap); - REENABLE_WARNING; -} - - -static int mhd_respond_internal(struct MHD_Connection *connection, - enum MHD_RequestTerminationCode code, - char *buffer, - size_t size, - enum MHD_ResponseMemoryMode mode) { - struct MHD_Response *response; - int r; - - assert(connection); - - response = MHD_create_response_from_buffer(size, buffer, mode); - if (!response) - return MHD_NO; - - log_debug("Queing response %u: %s", code, buffer); - MHD_add_response_header(response, "Content-Type", "text/plain"); - r = MHD_queue_response(connection, code, response); - MHD_destroy_response(response); - - return r; -} - -int mhd_respond(struct MHD_Connection *connection, - enum MHD_RequestTerminationCode code, - const char *message) { - - return mhd_respond_internal(connection, code, - (char*) message, strlen(message), - MHD_RESPMEM_PERSISTENT); -} - -int mhd_respond_oom(struct MHD_Connection *connection) { - return mhd_respond(connection, MHD_HTTP_SERVICE_UNAVAILABLE, "Out of memory.\n"); -} - -int mhd_respondf(struct MHD_Connection *connection, - enum MHD_RequestTerminationCode code, - const char *format, ...) { - - char *m; - int r; - va_list ap; - - assert(connection); - assert(format); - - va_start(ap, format); - r = vasprintf(&m, format, ap); - va_end(ap); - - if (r < 0) - return respond_oom(connection); - - return mhd_respond_internal(connection, code, m, r, MHD_RESPMEM_MUST_FREE); -} - -#ifdef HAVE_GNUTLS - -static struct { - const char *const names[4]; - int level; - bool enabled; -} gnutls_log_map[] = { - { {"0"}, LOG_DEBUG }, - { {"1", "audit"}, LOG_WARNING, true}, /* gnutls session audit */ - { {"2", "assert"}, LOG_DEBUG }, /* gnutls assert log */ - { {"3", "hsk", "ext"}, LOG_DEBUG }, /* gnutls handshake log */ - { {"4", "rec"}, LOG_DEBUG }, /* gnutls record log */ - { {"5", "dtls"}, LOG_DEBUG }, /* gnutls DTLS log */ - { {"6", "buf"}, LOG_DEBUG }, - { {"7", "write", "read"}, LOG_DEBUG }, - { {"8"}, LOG_DEBUG }, - { {"9", "enc", "int"}, LOG_DEBUG }, -}; - -static void log_func_gnutls(int level, const char *message) { - assert_se(message); - - if (0 <= level && level < (int) ELEMENTSOF(gnutls_log_map)) { - if (gnutls_log_map[level].enabled) - log_internal(gnutls_log_map[level].level, 0, NULL, 0, NULL, "gnutls %d/%s: %s", level, gnutls_log_map[level].names[1], message); - } else { - log_debug("Received GNUTLS message with unknown level %d.", level); - log_internal(LOG_DEBUG, 0, NULL, 0, NULL, "gnutls: %s", message); - } -} - -static void log_reset_gnutls_level(void) { - int i; - - for (i = ELEMENTSOF(gnutls_log_map) - 1; i >= 0; i--) - if (gnutls_log_map[i].enabled) { - log_debug("Setting gnutls log level to %d", i); - gnutls_global_set_log_level(i); - break; - } -} - -static int log_enable_gnutls_category(const char *cat) { - unsigned i; - - if (streq(cat, "all")) { - for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++) - gnutls_log_map[i].enabled = true; - log_reset_gnutls_level(); - return 0; - } else - for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++) - if (strv_contains((char**)gnutls_log_map[i].names, cat)) { - gnutls_log_map[i].enabled = true; - log_reset_gnutls_level(); - return 0; - } - log_error("No such log category: %s", cat); - return -EINVAL; -} - -int setup_gnutls_logger(char **categories) { - char **cat; - int r; - - gnutls_global_set_log_function(log_func_gnutls); - - if (categories) { - STRV_FOREACH(cat, categories) { - r = log_enable_gnutls_category(*cat); - if (r < 0) - return r; - } - } else - log_reset_gnutls_level(); - - return 0; -} - -static int verify_cert_authorized(gnutls_session_t session) { - unsigned status; - gnutls_certificate_type_t type; - gnutls_datum_t out; - int r; - - r = gnutls_certificate_verify_peers2(session, &status); - if (r < 0) - return log_error_errno(r, "gnutls_certificate_verify_peers2 failed: %m"); - - type = gnutls_certificate_type_get(session); - r = gnutls_certificate_verification_status_print(status, type, &out, 0); - if (r < 0) - return log_error_errno(r, "gnutls_certificate_verification_status_print failed: %m"); - - log_debug("Certificate status: %s", out.data); - gnutls_free(out.data); - - return status == 0 ? 0 : -EPERM; -} - -static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_cert) { - const gnutls_datum_t *pcert; - unsigned listsize; - gnutls_x509_crt_t cert; - int r; - - assert(session); - assert(client_cert); - - pcert = gnutls_certificate_get_peers(session, &listsize); - if (!pcert || !listsize) { - log_error("Failed to retrieve certificate chain"); - return -EINVAL; - } - - r = gnutls_x509_crt_init(&cert); - if (r < 0) { - log_error("Failed to initialize client certificate"); - return r; - } - - /* Note that by passing values between 0 and listsize here, you - can get access to the CA's certs */ - r = gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER); - if (r < 0) { - log_error("Failed to import client certificate"); - gnutls_x509_crt_deinit(cert); - return r; - } - - *client_cert = cert; - return 0; -} - -static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) { - size_t len = 0; - int r; - - assert(buf); - assert(*buf == NULL); - - r = gnutls_x509_crt_get_dn(client_cert, NULL, &len); - if (r != GNUTLS_E_SHORT_MEMORY_BUFFER) { - log_error("gnutls_x509_crt_get_dn failed"); - return r; - } - - *buf = malloc(len); - if (!*buf) - return log_oom(); - - gnutls_x509_crt_get_dn(client_cert, *buf, &len); - return 0; -} - -static inline void gnutls_x509_crt_deinitp(gnutls_x509_crt_t *p) { - gnutls_x509_crt_deinit(*p); -} - -int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) { - const union MHD_ConnectionInfo *ci; - gnutls_session_t session; - _cleanup_(gnutls_x509_crt_deinitp) gnutls_x509_crt_t client_cert = NULL; - _cleanup_free_ char *buf = NULL; - int r; - - assert(connection); - assert(code); - - *code = 0; - - ci = MHD_get_connection_info(connection, - MHD_CONNECTION_INFO_GNUTLS_SESSION); - if (!ci) { - log_error("MHD_get_connection_info failed: session is unencrypted"); - *code = mhd_respond(connection, MHD_HTTP_FORBIDDEN, - "Encrypted connection is required"); - return -EPERM; - } - session = ci->tls_session; - assert(session); - - r = get_client_cert(session, &client_cert); - if (r < 0) { - *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED, - "Authorization through certificate is required"); - return -EPERM; - } - - r = get_auth_dn(client_cert, &buf); - if (r < 0) { - *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED, - "Failed to determine distinguished name from certificate"); - return -EPERM; - } - - log_debug("Connection from %s", buf); - - if (hostname) { - *hostname = buf; - buf = NULL; - } - - r = verify_cert_authorized(session); - if (r < 0) { - log_warning("Client is not authorized"); - *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED, - "Client certificate not signed by recognized authority"); - } - return r; -} - -#else -int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) { - return -EPERM; -} - -int setup_gnutls_logger(char **categories) { - if (categories) - log_notice("Ignoring specified gnutls logging categories — gnutls not available."); - return 0; -} -#endif diff --git a/src/journal-remote/microhttpd-util.h b/src/journal-remote/microhttpd-util.h deleted file mode 100644 index ea160f212b..0000000000 --- a/src/journal-remote/microhttpd-util.h +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 Zbigniew Jędrzejewski-Szmek - - 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 - -#include "macro.h" - -/* Compatiblity with libmicrohttpd < 0.9.38 */ -#ifndef MHD_HTTP_NOT_ACCEPTABLE -#define MHD_HTTP_NOT_ACCEPTABLE MHD_HTTP_METHOD_NOT_ACCEPTABLE -#endif - -#if MHD_VERSION < 0x00094203 -#define MHD_create_response_from_fd_at_offset64 MHD_create_response_from_fd_at_offset -#endif - -void microhttpd_logger(void *arg, const char *fmt, va_list ap) _printf_(2, 0); - -/* respond_oom() must be usable with return, hence this form. */ -#define respond_oom(connection) log_oom(), mhd_respond_oom(connection) - -int mhd_respondf(struct MHD_Connection *connection, - unsigned code, - const char *format, ...) _printf_(3,4); - -int mhd_respond(struct MHD_Connection *connection, - unsigned code, - const char *message); - -int mhd_respond_oom(struct MHD_Connection *connection); - -int check_permissions(struct MHD_Connection *connection, int *code, char **hostname); - -/* Set gnutls internal logging function to a callback which uses our - * own logging framework. - * - * gnutls categories are additionally filtered by our internal log - * level, so it should be set fairly high to capture all potentially - * interesting events without overwhelming detail. - */ -int setup_gnutls_logger(char **categories); diff --git a/src/journal/.gitignore b/src/journal/.gitignore deleted file mode 100644 index 04d5852547..0000000000 --- a/src/journal/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/journald-gperf.c -/libsystemd-journal.pc -/audit_type-list.txt -/audit_type-*-name.* diff --git a/src/journal/Makefile b/src/journal/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/journal/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/journal/audit-type.c b/src/journal/audit-type.c deleted file mode 100644 index 71e8790ca8..0000000000 --- a/src/journal/audit-type.c +++ /dev/null @@ -1,29 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 Zbigniew Jędrzejewski-Szmek - - 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 -#ifdef HAVE_AUDIT -# include -#endif - -#include "missing.h" -#include "audit-type.h" -#include "audit_type-to-name.h" -#include "macro.h" diff --git a/src/journal/audit-type.h b/src/journal/audit-type.h deleted file mode 100644 index 1dd2163707..0000000000 --- a/src/journal/audit-type.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 Zbigniew Jędrzejewski-Szmek - - 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 "macro.h" - -const char *audit_type_to_string(int type); -int audit_type_from_string(const char *s); - -/* This is inspired by DNS TYPEnnn formatting */ -#define audit_type_name_alloca(type) \ - ({ \ - const char *_s_; \ - _s_ = audit_type_to_string(type); \ - if (!_s_) { \ - _s_ = alloca(strlen("AUDIT") + DECIMAL_STR_MAX(int)); \ - sprintf((char*) _s_, "AUDIT%04i", type); \ - } \ - _s_; \ - }) diff --git a/src/journal/cat.c b/src/journal/cat.c deleted file mode 100644 index 08c844d44f..0000000000 --- a/src/journal/cat.c +++ /dev/null @@ -1,161 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 -#include -#include -#include -#include - -#include "sd-journal.h" - -#include "fd-util.h" -#include "parse-util.h" -#include "string-util.h" -#include "syslog-util.h" -#include "util.h" - -static const char *arg_identifier = NULL; -static int arg_priority = LOG_INFO; -static bool arg_level_prefix = true; - -static void help(void) { - printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Execute process with stdout/stderr connected to the journal.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -t --identifier=STRING Set syslog identifier\n" - " -p --priority=PRIORITY Set priority value (0..7)\n" - " --level-prefix=BOOL Control whether level prefix shall be parsed\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_LEVEL_PREFIX - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "identifier", required_argument, NULL, 't' }, - { "priority", required_argument, NULL, 'p' }, - { "level-prefix", required_argument, NULL, ARG_LEVEL_PREFIX }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "+ht:p:", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case 't': - if (isempty(optarg)) - arg_identifier = NULL; - else - arg_identifier = optarg; - break; - - case 'p': - arg_priority = log_level_from_string(optarg); - if (arg_priority < 0) { - log_error("Failed to parse priority value."); - return -EINVAL; - } - break; - - case ARG_LEVEL_PREFIX: { - int k; - - k = parse_boolean(optarg); - if (k < 0) - return log_error_errno(k, "Failed to parse level prefix value."); - - arg_level_prefix = k; - break; - } - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -int main(int argc, char *argv[]) { - _cleanup_close_ int fd = -1, saved_stderr = -1; - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - fd = sd_journal_stream_fd(arg_identifier, arg_priority, arg_level_prefix); - if (fd < 0) { - r = log_error_errno(fd, "Failed to create stream fd: %m"); - goto finish; - } - - saved_stderr = fcntl(STDERR_FILENO, F_DUPFD_CLOEXEC, 3); - - if (dup3(fd, STDOUT_FILENO, 0) < 0 || - dup3(fd, STDERR_FILENO, 0) < 0) { - r = log_error_errno(errno, "Failed to duplicate fd: %m"); - goto finish; - } - - if (fd >= 3) - safe_close(fd); - fd = -1; - - if (argc <= optind) - (void) execl("/bin/cat", "/bin/cat", NULL); - else - (void) execvp(argv[optind], argv + optind); - r = -errno; - - /* Let's try to restore a working stderr, so we can print the error message */ - if (saved_stderr >= 0) - (void) dup3(saved_stderr, STDERR_FILENO, 0); - - log_error_errno(r, "Failed to execute process: %m"); - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/journal/catalog.c b/src/journal/catalog.c deleted file mode 100644 index 886f6efd8b..0000000000 --- a/src/journal/catalog.c +++ /dev/null @@ -1,767 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 -#include -#include -#include -#include -#include - -#include "sd-id128.h" - -#include "alloc-util.h" -#include "catalog.h" -#include "conf-files.h" -#include "fd-util.h" -#include "fileio.h" -#include "hashmap.h" -#include "log.h" -#include "mkdir.h" -#include "path-util.h" -#include "siphash24.h" -#include "sparse-endian.h" -#include "strbuf.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -const char * const catalog_file_dirs[] = { - "/usr/local/lib/systemd/catalog/", - "/usr/lib/systemd/catalog/", - NULL -}; - -#define CATALOG_SIGNATURE (uint8_t[]) { 'R', 'H', 'H', 'H', 'K', 'S', 'L', 'P' } - -typedef struct CatalogHeader { - uint8_t signature[8]; /* "RHHHKSLP" */ - le32_t compatible_flags; - le32_t incompatible_flags; - le64_t header_size; - le64_t n_items; - le64_t catalog_item_size; -} CatalogHeader; - -typedef struct CatalogItem { - sd_id128_t id; - char language[32]; - le64_t offset; -} CatalogItem; - -static void catalog_hash_func(const void *p, struct siphash *state) { - const CatalogItem *i = p; - - siphash24_compress(&i->id, sizeof(i->id), state); - siphash24_compress(i->language, strlen(i->language), state); -} - -static int catalog_compare_func(const void *a, const void *b) { - const CatalogItem *i = a, *j = b; - unsigned k; - - for (k = 0; k < ELEMENTSOF(j->id.bytes); k++) { - if (i->id.bytes[k] < j->id.bytes[k]) - return -1; - if (i->id.bytes[k] > j->id.bytes[k]) - return 1; - } - - return strcmp(i->language, j->language); -} - -const struct hash_ops catalog_hash_ops = { - .hash = catalog_hash_func, - .compare = catalog_compare_func -}; - -static bool next_header(const char **s) { - const char *e; - - e = strchr(*s, '\n'); - - /* Unexpected end */ - if (!e) - return false; - - /* End of headers */ - if (e == *s) - return false; - - *s = e + 1; - return true; -} - -static const char *skip_header(const char *s) { - while (next_header(&s)) - ; - return s; -} - -static char *combine_entries(const char *one, const char *two) { - const char *b1, *b2; - size_t l1, l2, n; - char *dest, *p; - - /* Find split point of headers to body */ - b1 = skip_header(one); - b2 = skip_header(two); - - l1 = strlen(one); - l2 = strlen(two); - dest = new(char, l1 + l2 + 1); - if (!dest) { - log_oom(); - return NULL; - } - - p = dest; - - /* Headers from @one */ - n = b1 - one; - p = mempcpy(p, one, n); - - /* Headers from @two, these will only be found if not present above */ - n = b2 - two; - p = mempcpy(p, two, n); - - /* Body from @one */ - n = l1 - (b1 - one); - if (n > 0) { - memcpy(p, b1, n); - p += n; - - /* Body from @two */ - } else { - n = l2 - (b2 - two); - memcpy(p, b2, n); - p += n; - } - - assert(p - dest <= (ptrdiff_t)(l1 + l2)); - p[0] = '\0'; - return dest; -} - -static int finish_item( - Hashmap *h, - sd_id128_t id, - const char *language, - char *payload, size_t payload_size) { - - _cleanup_free_ CatalogItem *i = NULL; - _cleanup_free_ char *prev = NULL, *combined = NULL; - - assert(h); - assert(payload); - assert(payload_size > 0); - - i = new0(CatalogItem, 1); - if (!i) - return log_oom(); - - i->id = id; - if (language) { - assert(strlen(language) > 1 && strlen(language) < 32); - strcpy(i->language, language); - } - - prev = hashmap_get(h, i); - if (prev) { - /* Already have such an item, combine them */ - combined = combine_entries(payload, prev); - if (!combined) - return log_oom(); - - if (hashmap_update(h, i, combined) < 0) - return log_oom(); - combined = NULL; - } else { - /* A new item */ - combined = memdup(payload, payload_size + 1); - if (!combined) - return log_oom(); - - if (hashmap_put(h, i, combined) < 0) - return log_oom(); - i = NULL; - combined = NULL; - } - - return 0; -} - -int catalog_file_lang(const char* filename, char **lang) { - char *beg, *end, *_lang; - - end = endswith(filename, ".catalog"); - if (!end) - return 0; - - beg = end - 1; - while (beg > filename && *beg != '.' && *beg != '/' && end - beg < 32) - beg--; - - if (*beg != '.' || end <= beg + 1) - return 0; - - _lang = strndup(beg + 1, end - beg - 1); - if (!_lang) - return -ENOMEM; - - *lang = _lang; - return 1; -} - -static int catalog_entry_lang(const char* filename, int line, - const char* t, const char* deflang, char **lang) { - size_t c; - - c = strlen(t); - if (c == 0) { - log_error("[%s:%u] Language too short.", filename, line); - return -EINVAL; - } - if (c > 31) { - log_error("[%s:%u] language too long.", filename, line); - return -EINVAL; - } - - if (deflang) { - if (streq(t, deflang)) { - log_warning("[%s:%u] language specified unnecessarily", - filename, line); - return 0; - } else - log_warning("[%s:%u] language differs from default for file", - filename, line); - } - - *lang = strdup(t); - if (!*lang) - return -ENOMEM; - - return 0; -} - -int catalog_import_file(Hashmap *h, const char *path) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *payload = NULL; - size_t payload_size = 0, payload_allocated = 0; - unsigned n = 0; - sd_id128_t id; - _cleanup_free_ char *deflang = NULL, *lang = NULL; - bool got_id = false, empty_line = true; - int r; - - assert(h); - assert(path); - - f = fopen(path, "re"); - if (!f) - return log_error_errno(errno, "Failed to open file %s: %m", path); - - r = catalog_file_lang(path, &deflang); - if (r < 0) - log_error_errno(r, "Failed to determine language for file %s: %m", path); - if (r == 1) - log_debug("File %s has language %s.", path, deflang); - - for (;;) { - char line[LINE_MAX]; - size_t line_len; - - if (!fgets(line, sizeof(line), f)) { - if (feof(f)) - break; - - return log_error_errno(errno, "Failed to read file %s: %m", path); - } - - n++; - - truncate_nl(line); - - if (line[0] == 0) { - empty_line = true; - continue; - } - - if (strchr(COMMENTS "\n", line[0])) - continue; - - if (empty_line && - strlen(line) >= 2+1+32 && - line[0] == '-' && - line[1] == '-' && - line[2] == ' ' && - (line[2+1+32] == ' ' || line[2+1+32] == '\0')) { - - bool with_language; - sd_id128_t jd; - - /* New entry */ - - with_language = line[2+1+32] != '\0'; - line[2+1+32] = '\0'; - - if (sd_id128_from_string(line + 2 + 1, &jd) >= 0) { - - if (got_id) { - if (payload_size == 0) { - log_error("[%s:%u] No payload text.", path, n); - return -EINVAL; - } - - r = finish_item(h, id, lang ?: deflang, payload, payload_size); - if (r < 0) - return r; - - lang = mfree(lang); - payload_size = 0; - } - - if (with_language) { - char *t; - - t = strstrip(line + 2 + 1 + 32 + 1); - r = catalog_entry_lang(path, n, t, deflang, &lang); - if (r < 0) - return r; - } - - got_id = true; - empty_line = false; - id = jd; - - continue; - } - } - - /* Payload */ - if (!got_id) { - log_error("[%s:%u] Got payload before ID.", path, n); - return -EINVAL; - } - - line_len = strlen(line); - if (!GREEDY_REALLOC(payload, payload_allocated, - payload_size + (empty_line ? 1 : 0) + line_len + 1 + 1)) - return log_oom(); - - if (empty_line) - payload[payload_size++] = '\n'; - memcpy(payload + payload_size, line, line_len); - payload_size += line_len; - payload[payload_size++] = '\n'; - payload[payload_size] = '\0'; - - empty_line = false; - } - - if (got_id) { - if (payload_size == 0) { - log_error("[%s:%u] No payload text.", path, n); - return -EINVAL; - } - - r = finish_item(h, id, lang ?: deflang, payload, payload_size); - if (r < 0) - return r; - } - - return 0; -} - -static int64_t write_catalog(const char *database, struct strbuf *sb, - CatalogItem *items, size_t n) { - CatalogHeader header; - _cleanup_fclose_ FILE *w = NULL; - int r; - _cleanup_free_ char *d, *p = NULL; - size_t k; - - d = dirname_malloc(database); - if (!d) - return log_oom(); - - r = mkdir_p(d, 0775); - if (r < 0) - return log_error_errno(r, "Recursive mkdir %s: %m", d); - - r = fopen_temporary(database, &w, &p); - if (r < 0) - return log_error_errno(r, "Failed to open database for writing: %s: %m", - database); - - zero(header); - memcpy(header.signature, CATALOG_SIGNATURE, sizeof(header.signature)); - header.header_size = htole64(ALIGN_TO(sizeof(CatalogHeader), 8)); - header.catalog_item_size = htole64(sizeof(CatalogItem)); - header.n_items = htole64(n); - - r = -EIO; - - k = fwrite(&header, 1, sizeof(header), w); - if (k != sizeof(header)) { - log_error("%s: failed to write header.", p); - goto error; - } - - k = fwrite(items, 1, n * sizeof(CatalogItem), w); - if (k != n * sizeof(CatalogItem)) { - log_error("%s: failed to write database.", p); - goto error; - } - - k = fwrite(sb->buf, 1, sb->len, w); - if (k != sb->len) { - log_error("%s: failed to write strings.", p); - goto error; - } - - r = fflush_and_check(w); - if (r < 0) { - log_error_errno(r, "%s: failed to write database: %m", p); - goto error; - } - - fchmod(fileno(w), 0644); - - if (rename(p, database) < 0) { - r = log_error_errno(errno, "rename (%s -> %s) failed: %m", p, database); - goto error; - } - - return ftello(w); - -error: - (void) unlink(p); - return r; -} - -int catalog_update(const char* database, const char* root, const char* const* dirs) { - _cleanup_strv_free_ char **files = NULL; - char **f; - struct strbuf *sb = NULL; - _cleanup_hashmap_free_free_free_ Hashmap *h = NULL; - _cleanup_free_ CatalogItem *items = NULL; - ssize_t offset; - char *payload; - CatalogItem *i; - Iterator j; - unsigned n; - int r; - int64_t sz; - - h = hashmap_new(&catalog_hash_ops); - sb = strbuf_new(); - - if (!h || !sb) { - r = log_oom(); - goto finish; - } - - r = conf_files_list_strv(&files, ".catalog", root, dirs); - if (r < 0) { - log_error_errno(r, "Failed to get catalog files: %m"); - goto finish; - } - - STRV_FOREACH(f, files) { - log_debug("Reading file '%s'", *f); - r = catalog_import_file(h, *f); - if (r < 0) { - log_error_errno(r, "Failed to import file '%s': %m", *f); - goto finish; - } - } - - if (hashmap_size(h) <= 0) { - log_info("No items in catalog."); - goto finish; - } else - log_debug("Found %u items in catalog.", hashmap_size(h)); - - items = new(CatalogItem, hashmap_size(h)); - if (!items) { - r = log_oom(); - goto finish; - } - - n = 0; - HASHMAP_FOREACH_KEY(payload, i, h, j) { - log_debug("Found " SD_ID128_FORMAT_STR ", language %s", - SD_ID128_FORMAT_VAL(i->id), - isempty(i->language) ? "C" : i->language); - - offset = strbuf_add_string(sb, payload, strlen(payload)); - if (offset < 0) { - r = log_oom(); - goto finish; - } - i->offset = htole64((uint64_t) offset); - items[n++] = *i; - } - - assert(n == hashmap_size(h)); - qsort_safe(items, n, sizeof(CatalogItem), catalog_compare_func); - - strbuf_complete(sb); - - sz = write_catalog(database, sb, items, n); - if (sz < 0) - r = log_error_errno(sz, "Failed to write %s: %m", database); - else { - r = 0; - log_debug("%s: wrote %u items, with %zu bytes of strings, %"PRIi64" total size.", - database, n, sb->len, sz); - } - -finish: - strbuf_cleanup(sb); - - return r; -} - -static int open_mmap(const char *database, int *_fd, struct stat *_st, void **_p) { - const CatalogHeader *h; - int fd; - void *p; - struct stat st; - - assert(_fd); - assert(_st); - assert(_p); - - fd = open(database, O_RDONLY|O_CLOEXEC); - if (fd < 0) - return -errno; - - if (fstat(fd, &st) < 0) { - safe_close(fd); - return -errno; - } - - if (st.st_size < (off_t) sizeof(CatalogHeader)) { - safe_close(fd); - return -EINVAL; - } - - p = mmap(NULL, PAGE_ALIGN(st.st_size), PROT_READ, MAP_SHARED, fd, 0); - if (p == MAP_FAILED) { - safe_close(fd); - return -errno; - } - - h = p; - if (memcmp(h->signature, CATALOG_SIGNATURE, sizeof(h->signature)) != 0 || - le64toh(h->header_size) < sizeof(CatalogHeader) || - le64toh(h->catalog_item_size) < sizeof(CatalogItem) || - h->incompatible_flags != 0 || - le64toh(h->n_items) <= 0 || - st.st_size < (off_t) (le64toh(h->header_size) + le64toh(h->catalog_item_size) * le64toh(h->n_items))) { - safe_close(fd); - munmap(p, st.st_size); - return -EBADMSG; - } - - *_fd = fd; - *_st = st; - *_p = p; - - return 0; -} - -static const char *find_id(void *p, sd_id128_t id) { - CatalogItem key, *f = NULL; - const CatalogHeader *h = p; - const char *loc; - - zero(key); - key.id = id; - - loc = setlocale(LC_MESSAGES, NULL); - if (loc && loc[0] && !streq(loc, "C") && !streq(loc, "POSIX")) { - strncpy(key.language, loc, sizeof(key.language)); - key.language[strcspn(key.language, ".@")] = 0; - - f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func); - if (!f) { - char *e; - - e = strchr(key.language, '_'); - if (e) { - *e = 0; - f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func); - } - } - } - - if (!f) { - zero(key.language); - f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func); - } - - if (!f) - return NULL; - - return (const char*) p + - le64toh(h->header_size) + - le64toh(h->n_items) * le64toh(h->catalog_item_size) + - le64toh(f->offset); -} - -int catalog_get(const char* database, sd_id128_t id, char **_text) { - _cleanup_close_ int fd = -1; - void *p = NULL; - struct stat st = {}; - char *text = NULL; - int r; - const char *s; - - assert(_text); - - r = open_mmap(database, &fd, &st, &p); - if (r < 0) - return r; - - s = find_id(p, id); - if (!s) { - r = -ENOENT; - goto finish; - } - - text = strdup(s); - if (!text) { - r = -ENOMEM; - goto finish; - } - - *_text = text; - r = 0; - -finish: - if (p) - munmap(p, st.st_size); - - return r; -} - -static char *find_header(const char *s, const char *header) { - - for (;;) { - const char *v; - - v = startswith(s, header); - if (v) { - v += strspn(v, WHITESPACE); - return strndup(v, strcspn(v, NEWLINE)); - } - - if (!next_header(&s)) - return NULL; - } -} - -static void dump_catalog_entry(FILE *f, sd_id128_t id, const char *s, bool oneline) { - if (oneline) { - _cleanup_free_ char *subject = NULL, *defined_by = NULL; - - subject = find_header(s, "Subject:"); - defined_by = find_header(s, "Defined-By:"); - - fprintf(f, SD_ID128_FORMAT_STR " %s: %s\n", - SD_ID128_FORMAT_VAL(id), - strna(defined_by), strna(subject)); - } else - fprintf(f, "-- " SD_ID128_FORMAT_STR "\n%s\n", - SD_ID128_FORMAT_VAL(id), s); -} - - -int catalog_list(FILE *f, const char *database, bool oneline) { - _cleanup_close_ int fd = -1; - void *p = NULL; - struct stat st; - const CatalogHeader *h; - const CatalogItem *items; - int r; - unsigned n; - sd_id128_t last_id; - bool last_id_set = false; - - r = open_mmap(database, &fd, &st, &p); - if (r < 0) - return r; - - h = p; - items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size)); - - for (n = 0; n < le64toh(h->n_items); n++) { - const char *s; - - if (last_id_set && sd_id128_equal(last_id, items[n].id)) - continue; - - assert_se(s = find_id(p, items[n].id)); - - dump_catalog_entry(f, items[n].id, s, oneline); - - last_id_set = true; - last_id = items[n].id; - } - - munmap(p, st.st_size); - - return 0; -} - -int catalog_list_items(FILE *f, const char *database, bool oneline, char **items) { - char **item; - int r = 0; - - STRV_FOREACH(item, items) { - sd_id128_t id; - int k; - _cleanup_free_ char *msg = NULL; - - k = sd_id128_from_string(*item, &id); - if (k < 0) { - log_error_errno(k, "Failed to parse id128 '%s': %m", *item); - if (r == 0) - r = k; - continue; - } - - k = catalog_get(database, id, &msg); - if (k < 0) { - log_full_errno(k == -ENOENT ? LOG_NOTICE : LOG_ERR, k, - "Failed to retrieve catalog entry for '%s': %m", *item); - if (r == 0) - r = k; - continue; - } - - dump_catalog_entry(f, id, msg, oneline); - } - - return r; -} diff --git a/src/journal/catalog.h b/src/journal/catalog.h deleted file mode 100644 index 1b1014b335..0000000000 --- a/src/journal/catalog.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 "sd-id128.h" - -#include "hashmap.h" -#include "strbuf.h" - -int catalog_import_file(Hashmap *h, const char *path); -int catalog_update(const char* database, const char* root, const char* const* dirs); -int catalog_get(const char* database, sd_id128_t id, char **data); -int catalog_list(FILE *f, const char* database, bool oneline); -int catalog_list_items(FILE *f, const char* database, bool oneline, char **items); -int catalog_file_lang(const char *filename, char **lang); -extern const char * const catalog_file_dirs[]; -extern const struct hash_ops catalog_hash_ops; diff --git a/src/journal/compress.c b/src/journal/compress.c deleted file mode 100644 index ba734b5561..0000000000 --- a/src/journal/compress.c +++ /dev/null @@ -1,683 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include -#include -#include - -#ifdef HAVE_XZ -#include -#endif - -#ifdef HAVE_LZ4 -#include -#include -#endif - -#include "alloc-util.h" -#include "compress.h" -#include "fd-util.h" -#include "io-util.h" -#include "journal-def.h" -#include "macro.h" -#include "sparse-endian.h" -#include "string-table.h" -#include "string-util.h" -#include "util.h" - -#ifdef HAVE_LZ4 -DEFINE_TRIVIAL_CLEANUP_FUNC(LZ4F_compressionContext_t, LZ4F_freeCompressionContext); -DEFINE_TRIVIAL_CLEANUP_FUNC(LZ4F_decompressionContext_t, LZ4F_freeDecompressionContext); -#endif - -#define ALIGN_8(l) ALIGN_TO(l, sizeof(size_t)) - -static const char* const object_compressed_table[_OBJECT_COMPRESSED_MAX] = { - [OBJECT_COMPRESSED_XZ] = "XZ", - [OBJECT_COMPRESSED_LZ4] = "LZ4", -}; - -DEFINE_STRING_TABLE_LOOKUP(object_compressed, int); - -int compress_blob_xz(const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size) { -#ifdef HAVE_XZ - static const lzma_options_lzma opt = { - 1u << 20u, NULL, 0, LZMA_LC_DEFAULT, LZMA_LP_DEFAULT, - LZMA_PB_DEFAULT, LZMA_MODE_FAST, 128, LZMA_MF_HC3, 4 - }; - static const lzma_filter filters[] = { - { LZMA_FILTER_LZMA2, (lzma_options_lzma*) &opt }, - { LZMA_VLI_UNKNOWN, NULL } - }; - lzma_ret ret; - size_t out_pos = 0; - - assert(src); - assert(src_size > 0); - assert(dst); - assert(dst_alloc_size > 0); - assert(dst_size); - - /* Returns < 0 if we couldn't compress the data or the - * compressed result is longer than the original */ - - if (src_size < 80) - return -ENOBUFS; - - ret = lzma_stream_buffer_encode((lzma_filter*) filters, LZMA_CHECK_NONE, NULL, - src, src_size, dst, &out_pos, dst_alloc_size); - if (ret != LZMA_OK) - return -ENOBUFS; - - *dst_size = out_pos; - return 0; -#else - return -EPROTONOSUPPORT; -#endif -} - -int compress_blob_lz4(const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size) { -#ifdef HAVE_LZ4 - int r; - - assert(src); - assert(src_size > 0); - assert(dst); - assert(dst_alloc_size > 0); - assert(dst_size); - - /* Returns < 0 if we couldn't compress the data or the - * compressed result is longer than the original */ - - if (src_size < 9) - return -ENOBUFS; - - r = LZ4_compress_limitedOutput(src, (char*)dst + 8, src_size, (int) dst_alloc_size - 8); - if (r <= 0) - return -ENOBUFS; - - *(le64_t*) dst = htole64(src_size); - *dst_size = r + 8; - - return 0; -#else - return -EPROTONOSUPPORT; -#endif -} - - -int decompress_blob_xz(const void *src, uint64_t src_size, - void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max) { - -#ifdef HAVE_XZ - _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT; - lzma_ret ret; - size_t space; - - assert(src); - assert(src_size > 0); - assert(dst); - assert(dst_alloc_size); - assert(dst_size); - assert(*dst_alloc_size == 0 || *dst); - - ret = lzma_stream_decoder(&s, UINT64_MAX, 0); - if (ret != LZMA_OK) - return -ENOMEM; - - space = MIN(src_size * 2, dst_max ?: (size_t) -1); - if (!greedy_realloc(dst, dst_alloc_size, space, 1)) - return -ENOMEM; - - s.next_in = src; - s.avail_in = src_size; - - s.next_out = *dst; - s.avail_out = space; - - for (;;) { - size_t used; - - ret = lzma_code(&s, LZMA_FINISH); - - if (ret == LZMA_STREAM_END) - break; - else if (ret != LZMA_OK) - return -ENOMEM; - - if (dst_max > 0 && (space - s.avail_out) >= dst_max) - break; - else if (dst_max > 0 && space == dst_max) - return -ENOBUFS; - - used = space - s.avail_out; - space = MIN(2 * space, dst_max ?: (size_t) -1); - if (!greedy_realloc(dst, dst_alloc_size, space, 1)) - return -ENOMEM; - - s.avail_out = space - used; - s.next_out = *(uint8_t**)dst + used; - } - - *dst_size = space - s.avail_out; - return 0; -#else - return -EPROTONOSUPPORT; -#endif -} - -int decompress_blob_lz4(const void *src, uint64_t src_size, - void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max) { - -#ifdef HAVE_LZ4 - char* out; - int r, size; /* LZ4 uses int for size */ - - assert(src); - assert(src_size > 0); - assert(dst); - assert(dst_alloc_size); - assert(dst_size); - assert(*dst_alloc_size == 0 || *dst); - - if (src_size <= 8) - return -EBADMSG; - - size = le64toh( *(le64_t*)src ); - if (size < 0 || (unsigned) size != le64toh(*(le64_t*)src)) - return -EFBIG; - if ((size_t) size > *dst_alloc_size) { - out = realloc(*dst, size); - if (!out) - return -ENOMEM; - *dst = out; - *dst_alloc_size = size; - } else - out = *dst; - - r = LZ4_decompress_safe((char*)src + 8, out, src_size - 8, size); - if (r < 0 || r != size) - return -EBADMSG; - - *dst_size = size; - return 0; -#else - return -EPROTONOSUPPORT; -#endif -} - -int decompress_blob(int compression, - const void *src, uint64_t src_size, - void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max) { - if (compression == OBJECT_COMPRESSED_XZ) - return decompress_blob_xz(src, src_size, - dst, dst_alloc_size, dst_size, dst_max); - else if (compression == OBJECT_COMPRESSED_LZ4) - return decompress_blob_lz4(src, src_size, - dst, dst_alloc_size, dst_size, dst_max); - else - return -EBADMSG; -} - - -int decompress_startswith_xz(const void *src, uint64_t src_size, - void **buffer, size_t *buffer_size, - const void *prefix, size_t prefix_len, - uint8_t extra) { - -#ifdef HAVE_XZ - _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT; - lzma_ret ret; - - /* Checks whether the decompressed blob starts with the - * mentioned prefix. The byte extra needs to follow the - * prefix */ - - assert(src); - assert(src_size > 0); - assert(buffer); - assert(buffer_size); - assert(prefix); - assert(*buffer_size == 0 || *buffer); - - ret = lzma_stream_decoder(&s, UINT64_MAX, 0); - if (ret != LZMA_OK) - return -EBADMSG; - - if (!(greedy_realloc(buffer, buffer_size, ALIGN_8(prefix_len + 1), 1))) - return -ENOMEM; - - s.next_in = src; - s.avail_in = src_size; - - s.next_out = *buffer; - s.avail_out = *buffer_size; - - for (;;) { - ret = lzma_code(&s, LZMA_FINISH); - - if (ret != LZMA_STREAM_END && ret != LZMA_OK) - return -EBADMSG; - - if (*buffer_size - s.avail_out >= prefix_len + 1) - return memcmp(*buffer, prefix, prefix_len) == 0 && - ((const uint8_t*) *buffer)[prefix_len] == extra; - - if (ret == LZMA_STREAM_END) - return 0; - - s.avail_out += *buffer_size; - - if (!(greedy_realloc(buffer, buffer_size, *buffer_size * 2, 1))) - return -ENOMEM; - - s.next_out = *(uint8_t**)buffer + *buffer_size - s.avail_out; - } - -#else - return -EPROTONOSUPPORT; -#endif -} - -int decompress_startswith_lz4(const void *src, uint64_t src_size, - void **buffer, size_t *buffer_size, - const void *prefix, size_t prefix_len, - uint8_t extra) { -#ifdef HAVE_LZ4 - /* Checks whether the decompressed blob starts with the - * mentioned prefix. The byte extra needs to follow the - * prefix */ - - int r; - size_t size; - - assert(src); - assert(src_size > 0); - assert(buffer); - assert(buffer_size); - assert(prefix); - assert(*buffer_size == 0 || *buffer); - - if (src_size <= 8) - return -EBADMSG; - - if (!(greedy_realloc(buffer, buffer_size, ALIGN_8(prefix_len + 1), 1))) - return -ENOMEM; - - r = LZ4_decompress_safe_partial((char*)src + 8, *buffer, src_size - 8, - prefix_len + 1, *buffer_size); - if (r >= 0) - size = (unsigned) r; - else { - /* lz4 always tries to decode full "sequence", so in - * pathological cases might need to decompress the - * full field. */ - r = decompress_blob_lz4(src, src_size, buffer, buffer_size, &size, 0); - if (r < 0) - return r; - } - - if (size >= prefix_len + 1) - return memcmp(*buffer, prefix, prefix_len) == 0 && - ((const uint8_t*) *buffer)[prefix_len] == extra; - else - return 0; - -#else - return -EPROTONOSUPPORT; -#endif -} - -int decompress_startswith(int compression, - const void *src, uint64_t src_size, - void **buffer, size_t *buffer_size, - const void *prefix, size_t prefix_len, - uint8_t extra) { - if (compression == OBJECT_COMPRESSED_XZ) - return decompress_startswith_xz(src, src_size, - buffer, buffer_size, - prefix, prefix_len, - extra); - else if (compression == OBJECT_COMPRESSED_LZ4) - return decompress_startswith_lz4(src, src_size, - buffer, buffer_size, - prefix, prefix_len, - extra); - else - return -EBADMSG; -} - -int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes) { -#ifdef HAVE_XZ - _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT; - lzma_ret ret; - uint8_t buf[BUFSIZ], out[BUFSIZ]; - lzma_action action = LZMA_RUN; - - assert(fdf >= 0); - assert(fdt >= 0); - - ret = lzma_easy_encoder(&s, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64); - if (ret != LZMA_OK) { - log_error("Failed to initialize XZ encoder: code %u", ret); - return -EINVAL; - } - - for (;;) { - if (s.avail_in == 0 && action == LZMA_RUN) { - size_t m = sizeof(buf); - ssize_t n; - - if (max_bytes != (uint64_t) -1 && (uint64_t) m > max_bytes) - m = (size_t) max_bytes; - - n = read(fdf, buf, m); - if (n < 0) - return -errno; - if (n == 0) - action = LZMA_FINISH; - else { - s.next_in = buf; - s.avail_in = n; - - if (max_bytes != (uint64_t) -1) { - assert(max_bytes >= (uint64_t) n); - max_bytes -= n; - } - } - } - - if (s.avail_out == 0) { - s.next_out = out; - s.avail_out = sizeof(out); - } - - ret = lzma_code(&s, action); - if (ret != LZMA_OK && ret != LZMA_STREAM_END) { - log_error("Compression failed: code %u", ret); - return -EBADMSG; - } - - if (s.avail_out == 0 || ret == LZMA_STREAM_END) { - ssize_t n, k; - - n = sizeof(out) - s.avail_out; - - k = loop_write(fdt, out, n, false); - if (k < 0) - return k; - - if (ret == LZMA_STREAM_END) { - log_debug("XZ compression finished (%"PRIu64" -> %"PRIu64" bytes, %.1f%%)", - s.total_in, s.total_out, - (double) s.total_out / s.total_in * 100); - - return 0; - } - } - } -#else - return -EPROTONOSUPPORT; -#endif -} - -#define LZ4_BUFSIZE (512*1024u) - -int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) { - -#ifdef HAVE_LZ4 - LZ4F_errorCode_t c; - _cleanup_(LZ4F_freeCompressionContextp) LZ4F_compressionContext_t ctx = NULL; - _cleanup_free_ char *buf = NULL; - char *src = NULL; - size_t size, n, total_in = 0, total_out, offset = 0, frame_size; - struct stat st; - int r; - static const LZ4F_compressOptions_t options = { - .stableSrc = 1, - }; - static const LZ4F_preferences_t preferences = { - .frameInfo.blockSizeID = 5, - }; - - c = LZ4F_createCompressionContext(&ctx, LZ4F_VERSION); - if (LZ4F_isError(c)) - return -ENOMEM; - - if (fstat(fdf, &st) < 0) - return log_debug_errno(errno, "fstat() failed: %m"); - - frame_size = LZ4F_compressBound(LZ4_BUFSIZE, &preferences); - size = frame_size + 64*1024; /* add some space for header and trailer */ - buf = malloc(size); - if (!buf) - return -ENOMEM; - - n = offset = total_out = LZ4F_compressBegin(ctx, buf, size, &preferences); - if (LZ4F_isError(n)) - return -EINVAL; - - src = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fdf, 0); - if (src == MAP_FAILED) - return -errno; - - log_debug("Buffer size is %zu bytes, header size %zu bytes.", size, n); - - while (total_in < (size_t) st.st_size) { - ssize_t k; - - k = MIN(LZ4_BUFSIZE, st.st_size - total_in); - n = LZ4F_compressUpdate(ctx, buf + offset, size - offset, - src + total_in, k, &options); - if (LZ4F_isError(n)) { - r = -ENOTRECOVERABLE; - goto cleanup; - } - - total_in += k; - offset += n; - total_out += n; - - if (max_bytes != (uint64_t) -1 && total_out > (size_t) max_bytes) { - log_debug("Compressed stream longer than %"PRIu64" bytes", max_bytes); - return -EFBIG; - } - - if (size - offset < frame_size + 4) { - k = loop_write(fdt, buf, offset, false); - if (k < 0) { - r = k; - goto cleanup; - } - offset = 0; - } - } - - n = LZ4F_compressEnd(ctx, buf + offset, size - offset, &options); - if (LZ4F_isError(n)) { - r = -ENOTRECOVERABLE; - goto cleanup; - } - - offset += n; - total_out += n; - r = loop_write(fdt, buf, offset, false); - if (r < 0) - goto cleanup; - - log_debug("LZ4 compression finished (%zu -> %zu bytes, %.1f%%)", - total_in, total_out, - (double) total_out / total_in * 100); - cleanup: - munmap(src, st.st_size); - return r; -#else - return -EPROTONOSUPPORT; -#endif -} - -int decompress_stream_xz(int fdf, int fdt, uint64_t max_bytes) { - -#ifdef HAVE_XZ - _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT; - lzma_ret ret; - - uint8_t buf[BUFSIZ], out[BUFSIZ]; - lzma_action action = LZMA_RUN; - - assert(fdf >= 0); - assert(fdt >= 0); - - ret = lzma_stream_decoder(&s, UINT64_MAX, 0); - if (ret != LZMA_OK) { - log_debug("Failed to initialize XZ decoder: code %u", ret); - return -ENOMEM; - } - - for (;;) { - if (s.avail_in == 0 && action == LZMA_RUN) { - ssize_t n; - - n = read(fdf, buf, sizeof(buf)); - if (n < 0) - return -errno; - if (n == 0) - action = LZMA_FINISH; - else { - s.next_in = buf; - s.avail_in = n; - } - } - - if (s.avail_out == 0) { - s.next_out = out; - s.avail_out = sizeof(out); - } - - ret = lzma_code(&s, action); - if (ret != LZMA_OK && ret != LZMA_STREAM_END) { - log_debug("Decompression failed: code %u", ret); - return -EBADMSG; - } - - if (s.avail_out == 0 || ret == LZMA_STREAM_END) { - ssize_t n, k; - - n = sizeof(out) - s.avail_out; - - if (max_bytes != (uint64_t) -1) { - if (max_bytes < (uint64_t) n) - return -EFBIG; - - max_bytes -= n; - } - - k = loop_write(fdt, out, n, false); - if (k < 0) - return k; - - if (ret == LZMA_STREAM_END) { - log_debug("XZ decompression finished (%"PRIu64" -> %"PRIu64" bytes, %.1f%%)", - s.total_in, s.total_out, - (double) s.total_out / s.total_in * 100); - - return 0; - } - } - } -#else - log_debug("Cannot decompress file. Compiled without XZ support."); - return -EPROTONOSUPPORT; -#endif -} - -int decompress_stream_lz4(int in, int out, uint64_t max_bytes) { -#ifdef HAVE_LZ4 - size_t c; - _cleanup_(LZ4F_freeDecompressionContextp) LZ4F_decompressionContext_t ctx = NULL; - _cleanup_free_ char *buf = NULL; - char *src; - struct stat st; - int r = 0; - size_t total_in = 0, total_out = 0; - - c = LZ4F_createDecompressionContext(&ctx, LZ4F_VERSION); - if (LZ4F_isError(c)) - return -ENOMEM; - - if (fstat(in, &st) < 0) - return log_debug_errno(errno, "fstat() failed: %m"); - - buf = malloc(LZ4_BUFSIZE); - if (!buf) - return -ENOMEM; - - src = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, in, 0); - if (src == MAP_FAILED) - return -errno; - - while (total_in < (size_t) st.st_size) { - size_t produced = LZ4_BUFSIZE; - size_t used = st.st_size - total_in; - - c = LZ4F_decompress(ctx, buf, &produced, src + total_in, &used, NULL); - if (LZ4F_isError(c)) { - r = -EBADMSG; - goto cleanup; - } - - total_in += used; - total_out += produced; - - if (max_bytes != (uint64_t) -1 && total_out > (size_t) max_bytes) { - log_debug("Decompressed stream longer than %"PRIu64" bytes", max_bytes); - r = -EFBIG; - goto cleanup; - } - - r = loop_write(out, buf, produced, false); - if (r < 0) - goto cleanup; - } - - log_debug("LZ4 decompression finished (%zu -> %zu bytes, %.1f%%)", - total_in, total_out, - (double) total_out / total_in * 100); - cleanup: - munmap(src, st.st_size); - return r; -#else - log_debug("Cannot decompress file. Compiled without LZ4 support."); - return -EPROTONOSUPPORT; -#endif -} - -int decompress_stream(const char *filename, int fdf, int fdt, uint64_t max_bytes) { - - if (endswith(filename, ".lz4")) - return decompress_stream_lz4(fdf, fdt, max_bytes); - else if (endswith(filename, ".xz")) - return decompress_stream_xz(fdf, fdt, max_bytes); - else - return -EPROTONOSUPPORT; -} diff --git a/src/journal/compress.h b/src/journal/compress.h deleted file mode 100644 index c138099d9a..0000000000 --- a/src/journal/compress.h +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 "journal-def.h" - -const char* object_compressed_to_string(int compression); -int object_compressed_from_string(const char *compression); - -int compress_blob_xz(const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size); -int compress_blob_lz4(const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size); - -static inline int compress_blob(const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size) { - int r; -#ifdef HAVE_LZ4 - r = compress_blob_lz4(src, src_size, dst, dst_alloc_size, dst_size); - if (r == 0) - return OBJECT_COMPRESSED_LZ4; -#else - r = compress_blob_xz(src, src_size, dst, dst_alloc_size, dst_size); - if (r == 0) - return OBJECT_COMPRESSED_XZ; -#endif - return r; -} - -int decompress_blob_xz(const void *src, uint64_t src_size, - void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max); -int decompress_blob_lz4(const void *src, uint64_t src_size, - void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max); -int decompress_blob(int compression, - const void *src, uint64_t src_size, - void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max); - -int decompress_startswith_xz(const void *src, uint64_t src_size, - void **buffer, size_t *buffer_size, - const void *prefix, size_t prefix_len, - uint8_t extra); -int decompress_startswith_lz4(const void *src, uint64_t src_size, - void **buffer, size_t *buffer_size, - const void *prefix, size_t prefix_len, - uint8_t extra); -int decompress_startswith(int compression, - const void *src, uint64_t src_size, - void **buffer, size_t *buffer_size, - const void *prefix, size_t prefix_len, - uint8_t extra); - -int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes); -int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes); - -int decompress_stream_xz(int fdf, int fdt, uint64_t max_size); -int decompress_stream_lz4(int fdf, int fdt, uint64_t max_size); - -#ifdef HAVE_LZ4 -# define compress_stream compress_stream_lz4 -# define COMPRESSED_EXT ".lz4" -#else -# define compress_stream compress_stream_xz -# define COMPRESSED_EXT ".xz" -#endif - -int decompress_stream(const char *filename, int fdf, int fdt, uint64_t max_bytes); diff --git a/src/journal/fsprg.c b/src/journal/fsprg.c deleted file mode 100644 index 612b10f3a9..0000000000 --- a/src/journal/fsprg.c +++ /dev/null @@ -1,374 +0,0 @@ -/* - * fsprg v0.1 - (seekable) forward-secure pseudorandom generator - * Copyright (C) 2012 B. Poettering - * Contact: fsprg@point-at-infinity.org - * - * This library 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. - * - * This library 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 this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301 USA - */ - -/* - * See "Practical Secure Logging: Seekable Sequential Key Generators" - * by G. A. Marson, B. Poettering for details: - * - * http://eprint.iacr.org/2013/397 - */ - -#include -#include - -#include "fsprg.h" -#include "gcrypt-util.h" - -#define ISVALID_SECPAR(secpar) (((secpar) % 16 == 0) && ((secpar) >= 16) && ((secpar) <= 16384)) -#define VALIDATE_SECPAR(secpar) assert(ISVALID_SECPAR(secpar)); - -#define RND_HASH GCRY_MD_SHA256 -#define RND_GEN_P 0x01 -#define RND_GEN_Q 0x02 -#define RND_GEN_X 0x03 - -/******************************************************************************/ - -static void mpi_export(void *buf, size_t buflen, const gcry_mpi_t x) { - unsigned len; - size_t nwritten; - - assert(gcry_mpi_cmp_ui(x, 0) >= 0); - len = (gcry_mpi_get_nbits(x) + 7) / 8; - assert(len <= buflen); - memzero(buf, buflen); - gcry_mpi_print(GCRYMPI_FMT_USG, buf + (buflen - len), len, &nwritten, x); - assert(nwritten == len); -} - -static gcry_mpi_t mpi_import(const void *buf, size_t buflen) { - gcry_mpi_t h; - unsigned len; - - assert_se(gcry_mpi_scan(&h, GCRYMPI_FMT_USG, buf, buflen, NULL) == 0); - len = (gcry_mpi_get_nbits(h) + 7) / 8; - assert(len <= buflen); - assert(gcry_mpi_cmp_ui(h, 0) >= 0); - - return h; -} - -static void uint64_export(void *buf, size_t buflen, uint64_t x) { - assert(buflen == 8); - ((uint8_t*) buf)[0] = (x >> 56) & 0xff; - ((uint8_t*) buf)[1] = (x >> 48) & 0xff; - ((uint8_t*) buf)[2] = (x >> 40) & 0xff; - ((uint8_t*) buf)[3] = (x >> 32) & 0xff; - ((uint8_t*) buf)[4] = (x >> 24) & 0xff; - ((uint8_t*) buf)[5] = (x >> 16) & 0xff; - ((uint8_t*) buf)[6] = (x >> 8) & 0xff; - ((uint8_t*) buf)[7] = (x >> 0) & 0xff; -} - -_pure_ static uint64_t uint64_import(const void *buf, size_t buflen) { - assert(buflen == 8); - return - (uint64_t)(((uint8_t*) buf)[0]) << 56 | - (uint64_t)(((uint8_t*) buf)[1]) << 48 | - (uint64_t)(((uint8_t*) buf)[2]) << 40 | - (uint64_t)(((uint8_t*) buf)[3]) << 32 | - (uint64_t)(((uint8_t*) buf)[4]) << 24 | - (uint64_t)(((uint8_t*) buf)[5]) << 16 | - (uint64_t)(((uint8_t*) buf)[6]) << 8 | - (uint64_t)(((uint8_t*) buf)[7]) << 0; -} - -/* deterministically generate from seed/idx a string of buflen pseudorandom bytes */ -static void det_randomize(void *buf, size_t buflen, const void *seed, size_t seedlen, uint32_t idx) { - gcry_md_hd_t hd, hd2; - size_t olen, cpylen; - uint32_t ctr; - - olen = gcry_md_get_algo_dlen(RND_HASH); - gcry_md_open(&hd, RND_HASH, 0); - gcry_md_write(hd, seed, seedlen); - gcry_md_putc(hd, (idx >> 24) & 0xff); - gcry_md_putc(hd, (idx >> 16) & 0xff); - gcry_md_putc(hd, (idx >> 8) & 0xff); - gcry_md_putc(hd, (idx >> 0) & 0xff); - - for (ctr = 0; buflen; ctr++) { - gcry_md_copy(&hd2, hd); - gcry_md_putc(hd2, (ctr >> 24) & 0xff); - gcry_md_putc(hd2, (ctr >> 16) & 0xff); - gcry_md_putc(hd2, (ctr >> 8) & 0xff); - gcry_md_putc(hd2, (ctr >> 0) & 0xff); - gcry_md_final(hd2); - cpylen = (buflen < olen) ? buflen : olen; - memcpy(buf, gcry_md_read(hd2, RND_HASH), cpylen); - gcry_md_close(hd2); - buf += cpylen; - buflen -= cpylen; - } - gcry_md_close(hd); -} - -/* deterministically generate from seed/idx a prime of length `bits' that is 3 (mod 4) */ -static gcry_mpi_t genprime3mod4(int bits, const void *seed, size_t seedlen, uint32_t idx) { - size_t buflen = bits / 8; - uint8_t buf[buflen]; - gcry_mpi_t p; - - assert(bits % 8 == 0); - assert(buflen > 0); - - det_randomize(buf, buflen, seed, seedlen, idx); - buf[0] |= 0xc0; /* set upper two bits, so that n=pq has maximum size */ - buf[buflen - 1] |= 0x03; /* set lower two bits, to have result 3 (mod 4) */ - - p = mpi_import(buf, buflen); - while (gcry_prime_check(p, 0)) - gcry_mpi_add_ui(p, p, 4); - - return p; -} - -/* deterministically generate from seed/idx a quadratic residue (mod n) */ -static gcry_mpi_t gensquare(const gcry_mpi_t n, const void *seed, size_t seedlen, uint32_t idx, unsigned secpar) { - size_t buflen = secpar / 8; - uint8_t buf[buflen]; - gcry_mpi_t x; - - det_randomize(buf, buflen, seed, seedlen, idx); - buf[0] &= 0x7f; /* clear upper bit, so that we have x < n */ - x = mpi_import(buf, buflen); - assert(gcry_mpi_cmp(x, n) < 0); - gcry_mpi_mulm(x, x, x, n); - return x; -} - -/* compute 2^m (mod phi(p)), for a prime p */ -static gcry_mpi_t twopowmodphi(uint64_t m, const gcry_mpi_t p) { - gcry_mpi_t phi, r; - int n; - - phi = gcry_mpi_new(0); - gcry_mpi_sub_ui(phi, p, 1); - - /* count number of used bits in m */ - for (n = 0; (1ULL << n) <= m; n++) - ; - - r = gcry_mpi_new(0); - gcry_mpi_set_ui(r, 1); - while (n) { /* square and multiply algorithm for fast exponentiation */ - n--; - gcry_mpi_mulm(r, r, r, phi); - if (m & ((uint64_t)1 << n)) { - gcry_mpi_add(r, r, r); - if (gcry_mpi_cmp(r, phi) >= 0) - gcry_mpi_sub(r, r, phi); - } - } - - gcry_mpi_release(phi); - return r; -} - -/* Decompose $x \in Z_n$ into $(xp,xq) \in Z_p \times Z_q$ using Chinese Remainder Theorem */ -static void CRT_decompose(gcry_mpi_t *xp, gcry_mpi_t *xq, const gcry_mpi_t x, const gcry_mpi_t p, const gcry_mpi_t q) { - *xp = gcry_mpi_new(0); - *xq = gcry_mpi_new(0); - gcry_mpi_mod(*xp, x, p); - gcry_mpi_mod(*xq, x, q); -} - -/* Compose $(xp,xq) \in Z_p \times Z_q$ into $x \in Z_n$ using Chinese Remainder Theorem */ -static void CRT_compose(gcry_mpi_t *x, const gcry_mpi_t xp, const gcry_mpi_t xq, const gcry_mpi_t p, const gcry_mpi_t q) { - gcry_mpi_t a, u; - - a = gcry_mpi_new(0); - u = gcry_mpi_new(0); - *x = gcry_mpi_new(0); - gcry_mpi_subm(a, xq, xp, q); - gcry_mpi_invm(u, p, q); - gcry_mpi_mulm(a, a, u, q); /* a = (xq - xp) / p (mod q) */ - gcry_mpi_mul(*x, p, a); - gcry_mpi_add(*x, *x, xp); /* x = p * ((xq - xp) / p mod q) + xp */ - gcry_mpi_release(a); - gcry_mpi_release(u); -} - -/******************************************************************************/ - -size_t FSPRG_mskinbytes(unsigned _secpar) { - VALIDATE_SECPAR(_secpar); - return 2 + 2 * (_secpar / 2) / 8; /* to store header,p,q */ -} - -size_t FSPRG_mpkinbytes(unsigned _secpar) { - VALIDATE_SECPAR(_secpar); - return 2 + _secpar / 8; /* to store header,n */ -} - -size_t FSPRG_stateinbytes(unsigned _secpar) { - VALIDATE_SECPAR(_secpar); - return 2 + 2 * _secpar / 8 + 8; /* to store header,n,x,epoch */ -} - -static void store_secpar(void *buf, uint16_t secpar) { - secpar = secpar / 16 - 1; - ((uint8_t*) buf)[0] = (secpar >> 8) & 0xff; - ((uint8_t*) buf)[1] = (secpar >> 0) & 0xff; -} - -static uint16_t read_secpar(const void *buf) { - uint16_t secpar; - secpar = - (uint16_t)(((uint8_t*) buf)[0]) << 8 | - (uint16_t)(((uint8_t*) buf)[1]) << 0; - return 16 * (secpar + 1); -} - -void FSPRG_GenMK(void *msk, void *mpk, const void *seed, size_t seedlen, unsigned _secpar) { - uint8_t iseed[FSPRG_RECOMMENDED_SEEDLEN]; - gcry_mpi_t n, p, q; - uint16_t secpar; - - VALIDATE_SECPAR(_secpar); - secpar = _secpar; - - initialize_libgcrypt(false); - - if (!seed) { - gcry_randomize(iseed, FSPRG_RECOMMENDED_SEEDLEN, GCRY_STRONG_RANDOM); - seed = iseed; - seedlen = FSPRG_RECOMMENDED_SEEDLEN; - } - - p = genprime3mod4(secpar / 2, seed, seedlen, RND_GEN_P); - q = genprime3mod4(secpar / 2, seed, seedlen, RND_GEN_Q); - - if (msk) { - store_secpar(msk + 0, secpar); - mpi_export(msk + 2 + 0 * (secpar / 2) / 8, (secpar / 2) / 8, p); - mpi_export(msk + 2 + 1 * (secpar / 2) / 8, (secpar / 2) / 8, q); - } - - if (mpk) { - n = gcry_mpi_new(0); - gcry_mpi_mul(n, p, q); - assert(gcry_mpi_get_nbits(n) == secpar); - - store_secpar(mpk + 0, secpar); - mpi_export(mpk + 2, secpar / 8, n); - - gcry_mpi_release(n); - } - - gcry_mpi_release(p); - gcry_mpi_release(q); -} - -void FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seedlen) { - gcry_mpi_t n, x; - uint16_t secpar; - - initialize_libgcrypt(false); - - secpar = read_secpar(mpk + 0); - n = mpi_import(mpk + 2, secpar / 8); - x = gensquare(n, seed, seedlen, RND_GEN_X, secpar); - - memcpy(state, mpk, 2 + secpar / 8); - mpi_export(state + 2 + 1 * secpar / 8, secpar / 8, x); - memzero(state + 2 + 2 * secpar / 8, 8); - - gcry_mpi_release(n); - gcry_mpi_release(x); -} - -void FSPRG_Evolve(void *state) { - gcry_mpi_t n, x; - uint16_t secpar; - uint64_t epoch; - - initialize_libgcrypt(false); - - secpar = read_secpar(state + 0); - n = mpi_import(state + 2 + 0 * secpar / 8, secpar / 8); - x = mpi_import(state + 2 + 1 * secpar / 8, secpar / 8); - epoch = uint64_import(state + 2 + 2 * secpar / 8, 8); - - gcry_mpi_mulm(x, x, x, n); - epoch++; - - mpi_export(state + 2 + 1 * secpar / 8, secpar / 8, x); - uint64_export(state + 2 + 2 * secpar / 8, 8, epoch); - - gcry_mpi_release(n); - gcry_mpi_release(x); -} - -uint64_t FSPRG_GetEpoch(const void *state) { - uint16_t secpar; - secpar = read_secpar(state + 0); - return uint64_import(state + 2 + 2 * secpar / 8, 8); -} - -void FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, size_t seedlen) { - gcry_mpi_t p, q, n, x, xp, xq, kp, kq, xm; - uint16_t secpar; - - initialize_libgcrypt(false); - - secpar = read_secpar(msk + 0); - p = mpi_import(msk + 2 + 0 * (secpar / 2) / 8, (secpar / 2) / 8); - q = mpi_import(msk + 2 + 1 * (secpar / 2) / 8, (secpar / 2) / 8); - - n = gcry_mpi_new(0); - gcry_mpi_mul(n, p, q); - - x = gensquare(n, seed, seedlen, RND_GEN_X, secpar); - CRT_decompose(&xp, &xq, x, p, q); /* split (mod n) into (mod p) and (mod q) using CRT */ - - kp = twopowmodphi(epoch, p); /* compute 2^epoch (mod phi(p)) */ - kq = twopowmodphi(epoch, q); /* compute 2^epoch (mod phi(q)) */ - - gcry_mpi_powm(xp, xp, kp, p); /* compute x^(2^epoch) (mod p) */ - gcry_mpi_powm(xq, xq, kq, q); /* compute x^(2^epoch) (mod q) */ - - CRT_compose(&xm, xp, xq, p, q); /* combine (mod p) and (mod q) to (mod n) using CRT */ - - store_secpar(state + 0, secpar); - mpi_export(state + 2 + 0 * secpar / 8, secpar / 8, n); - mpi_export(state + 2 + 1 * secpar / 8, secpar / 8, xm); - uint64_export(state + 2 + 2 * secpar / 8, 8, epoch); - - gcry_mpi_release(p); - gcry_mpi_release(q); - gcry_mpi_release(n); - gcry_mpi_release(x); - gcry_mpi_release(xp); - gcry_mpi_release(xq); - gcry_mpi_release(kp); - gcry_mpi_release(kq); - gcry_mpi_release(xm); -} - -void FSPRG_GetKey(const void *state, void *key, size_t keylen, uint32_t idx) { - uint16_t secpar; - - initialize_libgcrypt(false); - - secpar = read_secpar(state + 0); - det_randomize(key, keylen, state + 2, 2 * secpar / 8 + 8, idx); -} diff --git a/src/journal/fsprg.h b/src/journal/fsprg.h deleted file mode 100644 index 829b56e240..0000000000 --- a/src/journal/fsprg.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef __fsprgh__ -#define __fsprgh__ - -/* - * fsprg v0.1 - (seekable) forward-secure pseudorandom generator - * Copyright (C) 2012 B. Poettering - * Contact: fsprg@point-at-infinity.org - * - * This library 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. - * - * This library 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 this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301 USA - * - */ - -#include -#include - -#include "macro.h" -#include "util.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define FSPRG_RECOMMENDED_SECPAR 1536 -#define FSPRG_RECOMMENDED_SEEDLEN (96/8) - -size_t FSPRG_mskinbytes(unsigned secpar) _const_; -size_t FSPRG_mpkinbytes(unsigned secpar) _const_; -size_t FSPRG_stateinbytes(unsigned secpar) _const_; - -/* Setup msk and mpk. Providing seed != NULL makes this algorithm deterministic. */ -void FSPRG_GenMK(void *msk, void *mpk, const void *seed, size_t seedlen, unsigned secpar); - -/* Initialize state deterministically in dependence on seed. */ -/* Note: in case one wants to run only one GenState0 per GenMK it is safe to use - the same seed for both GenMK and GenState0. -*/ -void FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seedlen); - -void FSPRG_Evolve(void *state); - -uint64_t FSPRG_GetEpoch(const void *state) _pure_; - -/* Seek to any arbitrary state (by providing msk together with seed from GenState0). */ -void FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, size_t seedlen); - -void FSPRG_GetKey(const void *state, void *key, size_t keylen, uint32_t idx); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/journal/journal-authenticate.c b/src/journal/journal-authenticate.c deleted file mode 100644 index d8af113d3f..0000000000 --- a/src/journal/journal-authenticate.c +++ /dev/null @@ -1,551 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 - -#include "fd-util.h" -#include "fsprg.h" -#include "gcrypt-util.h" -#include "hexdecoct.h" -#include "journal-authenticate.h" -#include "journal-def.h" -#include "journal-file.h" - -static uint64_t journal_file_tag_seqnum(JournalFile *f) { - uint64_t r; - - assert(f); - - r = le64toh(f->header->n_tags) + 1; - f->header->n_tags = htole64(r); - - return r; -} - -int journal_file_append_tag(JournalFile *f) { - Object *o; - uint64_t p; - int r; - - assert(f); - - if (!f->seal) - return 0; - - if (!f->hmac_running) - return 0; - - assert(f->hmac); - - r = journal_file_append_object(f, OBJECT_TAG, sizeof(struct TagObject), &o, &p); - if (r < 0) - return r; - - o->tag.seqnum = htole64(journal_file_tag_seqnum(f)); - o->tag.epoch = htole64(FSPRG_GetEpoch(f->fsprg_state)); - - log_debug("Writing tag %"PRIu64" for epoch %"PRIu64"", - le64toh(o->tag.seqnum), - FSPRG_GetEpoch(f->fsprg_state)); - - /* Add the tag object itself, so that we can protect its - * header. This will exclude the actual hash value in it */ - r = journal_file_hmac_put_object(f, OBJECT_TAG, o, p); - if (r < 0) - return r; - - /* Get the HMAC tag and store it in the object */ - memcpy(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH); - f->hmac_running = false; - - return 0; -} - -int journal_file_hmac_start(JournalFile *f) { - uint8_t key[256 / 8]; /* Let's pass 256 bit from FSPRG to HMAC */ - assert(f); - - if (!f->seal) - return 0; - - if (f->hmac_running) - return 0; - - /* Prepare HMAC for next cycle */ - gcry_md_reset(f->hmac); - FSPRG_GetKey(f->fsprg_state, key, sizeof(key), 0); - gcry_md_setkey(f->hmac, key, sizeof(key)); - - f->hmac_running = true; - - return 0; -} - -static int journal_file_get_epoch(JournalFile *f, uint64_t realtime, uint64_t *epoch) { - uint64_t t; - - assert(f); - assert(epoch); - assert(f->seal); - - if (f->fss_start_usec == 0 || - f->fss_interval_usec == 0) - return -EOPNOTSUPP; - - if (realtime < f->fss_start_usec) - return -ESTALE; - - t = realtime - f->fss_start_usec; - t = t / f->fss_interval_usec; - - *epoch = t; - return 0; -} - -static int journal_file_fsprg_need_evolve(JournalFile *f, uint64_t realtime) { - uint64_t goal, epoch; - int r; - assert(f); - - if (!f->seal) - return 0; - - r = journal_file_get_epoch(f, realtime, &goal); - if (r < 0) - return r; - - epoch = FSPRG_GetEpoch(f->fsprg_state); - if (epoch > goal) - return -ESTALE; - - return epoch != goal; -} - -int journal_file_fsprg_evolve(JournalFile *f, uint64_t realtime) { - uint64_t goal, epoch; - int r; - - assert(f); - - if (!f->seal) - return 0; - - r = journal_file_get_epoch(f, realtime, &goal); - if (r < 0) - return r; - - epoch = FSPRG_GetEpoch(f->fsprg_state); - if (epoch < goal) - log_debug("Evolving FSPRG key from epoch %"PRIu64" to %"PRIu64".", epoch, goal); - - for (;;) { - if (epoch > goal) - return -ESTALE; - if (epoch == goal) - return 0; - - FSPRG_Evolve(f->fsprg_state); - epoch = FSPRG_GetEpoch(f->fsprg_state); - } -} - -int journal_file_fsprg_seek(JournalFile *f, uint64_t goal) { - void *msk; - uint64_t epoch; - - assert(f); - - if (!f->seal) - return 0; - - assert(f->fsprg_seed); - - if (f->fsprg_state) { - /* Cheaper... */ - - epoch = FSPRG_GetEpoch(f->fsprg_state); - if (goal == epoch) - return 0; - - if (goal == epoch+1) { - FSPRG_Evolve(f->fsprg_state); - return 0; - } - } else { - f->fsprg_state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR); - f->fsprg_state = malloc(f->fsprg_state_size); - - if (!f->fsprg_state) - return -ENOMEM; - } - - log_debug("Seeking FSPRG key to %"PRIu64".", goal); - - msk = alloca(FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR)); - FSPRG_GenMK(msk, NULL, f->fsprg_seed, f->fsprg_seed_size, FSPRG_RECOMMENDED_SECPAR); - FSPRG_Seek(f->fsprg_state, goal, msk, f->fsprg_seed, f->fsprg_seed_size); - return 0; -} - -int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime) { - int r; - - assert(f); - - if (!f->seal) - return 0; - - if (realtime <= 0) - realtime = now(CLOCK_REALTIME); - - r = journal_file_fsprg_need_evolve(f, realtime); - if (r <= 0) - return 0; - - r = journal_file_append_tag(f); - if (r < 0) - return r; - - r = journal_file_fsprg_evolve(f, realtime); - if (r < 0) - return r; - - return 0; -} - -int journal_file_hmac_put_object(JournalFile *f, ObjectType type, Object *o, uint64_t p) { - int r; - - assert(f); - - if (!f->seal) - return 0; - - r = journal_file_hmac_start(f); - if (r < 0) - return r; - - if (!o) { - r = journal_file_move_to_object(f, type, p, &o); - if (r < 0) - return r; - } else { - if (type > OBJECT_UNUSED && o->object.type != type) - return -EBADMSG; - } - - gcry_md_write(f->hmac, o, offsetof(ObjectHeader, payload)); - - switch (o->object.type) { - - case OBJECT_DATA: - /* All but hash and payload are mutable */ - gcry_md_write(f->hmac, &o->data.hash, sizeof(o->data.hash)); - gcry_md_write(f->hmac, o->data.payload, le64toh(o->object.size) - offsetof(DataObject, payload)); - break; - - case OBJECT_FIELD: - /* Same here */ - gcry_md_write(f->hmac, &o->field.hash, sizeof(o->field.hash)); - gcry_md_write(f->hmac, o->field.payload, le64toh(o->object.size) - offsetof(FieldObject, payload)); - break; - - case OBJECT_ENTRY: - /* All */ - gcry_md_write(f->hmac, &o->entry.seqnum, le64toh(o->object.size) - offsetof(EntryObject, seqnum)); - break; - - case OBJECT_FIELD_HASH_TABLE: - case OBJECT_DATA_HASH_TABLE: - case OBJECT_ENTRY_ARRAY: - /* Nothing: everything is mutable */ - break; - - case OBJECT_TAG: - /* All but the tag itself */ - gcry_md_write(f->hmac, &o->tag.seqnum, sizeof(o->tag.seqnum)); - gcry_md_write(f->hmac, &o->tag.epoch, sizeof(o->tag.epoch)); - break; - default: - return -EINVAL; - } - - return 0; -} - -int journal_file_hmac_put_header(JournalFile *f) { - int r; - - assert(f); - - if (!f->seal) - return 0; - - r = journal_file_hmac_start(f); - if (r < 0) - return r; - - /* All but state+reserved, boot_id, arena_size, - * tail_object_offset, n_objects, n_entries, - * tail_entry_seqnum, head_entry_seqnum, entry_array_offset, - * head_entry_realtime, tail_entry_realtime, - * tail_entry_monotonic, n_data, n_fields, n_tags, - * n_entry_arrays. */ - - gcry_md_write(f->hmac, f->header->signature, offsetof(Header, state) - offsetof(Header, signature)); - gcry_md_write(f->hmac, &f->header->file_id, offsetof(Header, boot_id) - offsetof(Header, file_id)); - gcry_md_write(f->hmac, &f->header->seqnum_id, offsetof(Header, arena_size) - offsetof(Header, seqnum_id)); - gcry_md_write(f->hmac, &f->header->data_hash_table_offset, offsetof(Header, tail_object_offset) - offsetof(Header, data_hash_table_offset)); - - return 0; -} - -int journal_file_fss_load(JournalFile *f) { - int r, fd = -1; - char *p = NULL; - struct stat st; - FSSHeader *m = NULL; - sd_id128_t machine; - - assert(f); - - if (!f->seal) - return 0; - - r = sd_id128_get_machine(&machine); - if (r < 0) - return r; - - if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss", - SD_ID128_FORMAT_VAL(machine)) < 0) - return -ENOMEM; - - fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY, 0600); - if (fd < 0) { - if (errno != ENOENT) - log_error_errno(errno, "Failed to open %s: %m", p); - - r = -errno; - goto finish; - } - - if (fstat(fd, &st) < 0) { - r = -errno; - goto finish; - } - - if (st.st_size < (off_t) sizeof(FSSHeader)) { - r = -ENODATA; - goto finish; - } - - m = mmap(NULL, PAGE_ALIGN(sizeof(FSSHeader)), PROT_READ, MAP_SHARED, fd, 0); - if (m == MAP_FAILED) { - m = NULL; - r = -errno; - goto finish; - } - - if (memcmp(m->signature, FSS_HEADER_SIGNATURE, 8) != 0) { - r = -EBADMSG; - goto finish; - } - - if (m->incompatible_flags != 0) { - r = -EPROTONOSUPPORT; - goto finish; - } - - if (le64toh(m->header_size) < sizeof(FSSHeader)) { - r = -EBADMSG; - goto finish; - } - - if (le64toh(m->fsprg_state_size) != FSPRG_stateinbytes(le16toh(m->fsprg_secpar))) { - r = -EBADMSG; - goto finish; - } - - f->fss_file_size = le64toh(m->header_size) + le64toh(m->fsprg_state_size); - if ((uint64_t) st.st_size < f->fss_file_size) { - r = -ENODATA; - goto finish; - } - - if (!sd_id128_equal(machine, m->machine_id)) { - r = -EHOSTDOWN; - goto finish; - } - - if (le64toh(m->start_usec) <= 0 || - le64toh(m->interval_usec) <= 0) { - r = -EBADMSG; - goto finish; - } - - f->fss_file = mmap(NULL, PAGE_ALIGN(f->fss_file_size), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); - if (f->fss_file == MAP_FAILED) { - f->fss_file = NULL; - r = -errno; - goto finish; - } - - f->fss_start_usec = le64toh(f->fss_file->start_usec); - f->fss_interval_usec = le64toh(f->fss_file->interval_usec); - - f->fsprg_state = (uint8_t*) f->fss_file + le64toh(f->fss_file->header_size); - f->fsprg_state_size = le64toh(f->fss_file->fsprg_state_size); - - r = 0; - -finish: - if (m) - munmap(m, PAGE_ALIGN(sizeof(FSSHeader))); - - safe_close(fd); - free(p); - - return r; -} - -int journal_file_hmac_setup(JournalFile *f) { - gcry_error_t e; - - if (!f->seal) - return 0; - - initialize_libgcrypt(true); - - e = gcry_md_open(&f->hmac, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC); - if (e != 0) - return -EOPNOTSUPP; - - return 0; -} - -int journal_file_append_first_tag(JournalFile *f) { - int r; - uint64_t p; - - if (!f->seal) - return 0; - - log_debug("Calculating first tag..."); - - r = journal_file_hmac_put_header(f); - if (r < 0) - return r; - - p = le64toh(f->header->field_hash_table_offset); - if (p < offsetof(Object, hash_table.items)) - return -EINVAL; - p -= offsetof(Object, hash_table.items); - - r = journal_file_hmac_put_object(f, OBJECT_FIELD_HASH_TABLE, NULL, p); - if (r < 0) - return r; - - p = le64toh(f->header->data_hash_table_offset); - if (p < offsetof(Object, hash_table.items)) - return -EINVAL; - p -= offsetof(Object, hash_table.items); - - r = journal_file_hmac_put_object(f, OBJECT_DATA_HASH_TABLE, NULL, p); - if (r < 0) - return r; - - r = journal_file_append_tag(f); - if (r < 0) - return r; - - return 0; -} - -int journal_file_parse_verification_key(JournalFile *f, const char *key) { - uint8_t *seed; - size_t seed_size, c; - const char *k; - int r; - unsigned long long start, interval; - - seed_size = FSPRG_RECOMMENDED_SEEDLEN; - seed = malloc(seed_size); - if (!seed) - return -ENOMEM; - - k = key; - for (c = 0; c < seed_size; c++) { - int x, y; - - while (*k == '-') - k++; - - x = unhexchar(*k); - if (x < 0) { - free(seed); - return -EINVAL; - } - k++; - y = unhexchar(*k); - if (y < 0) { - free(seed); - return -EINVAL; - } - k++; - - seed[c] = (uint8_t) (x * 16 + y); - } - - if (*k != '/') { - free(seed); - return -EINVAL; - } - k++; - - r = sscanf(k, "%llx-%llx", &start, &interval); - if (r != 2) { - free(seed); - return -EINVAL; - } - - f->fsprg_seed = seed; - f->fsprg_seed_size = seed_size; - - f->fss_start_usec = start * interval; - f->fss_interval_usec = interval; - - return 0; -} - -bool journal_file_next_evolve_usec(JournalFile *f, usec_t *u) { - uint64_t epoch; - - assert(f); - assert(u); - - if (!f->seal) - return false; - - epoch = FSPRG_GetEpoch(f->fsprg_state); - - *u = (usec_t) (f->fss_start_usec + f->fss_interval_usec * epoch + f->fss_interval_usec); - - return true; -} diff --git a/src/journal/journal-authenticate.h b/src/journal/journal-authenticate.h deleted file mode 100644 index 6c87319ede..0000000000 --- a/src/journal/journal-authenticate.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 "journal-file.h" - -int journal_file_append_tag(JournalFile *f); -int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime); -int journal_file_append_first_tag(JournalFile *f); - -int journal_file_hmac_setup(JournalFile *f); -int journal_file_hmac_start(JournalFile *f); -int journal_file_hmac_put_header(JournalFile *f); -int journal_file_hmac_put_object(JournalFile *f, ObjectType type, Object *o, uint64_t p); - -int journal_file_fss_load(JournalFile *f); -int journal_file_parse_verification_key(JournalFile *f, const char *key); - -int journal_file_fsprg_evolve(JournalFile *f, uint64_t realtime); -int journal_file_fsprg_seek(JournalFile *f, uint64_t epoch); - -bool journal_file_next_evolve_usec(JournalFile *f, usec_t *u); diff --git a/src/journal/journal-def.h b/src/journal/journal-def.h deleted file mode 100644 index 67edb43960..0000000000 --- a/src/journal/journal-def.h +++ /dev/null @@ -1,237 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 "sd-id128.h" - -#include "macro.h" -#include "sparse-endian.h" - -/* - * If you change this file you probably should also change its documentation: - * - * http://www.freedesktop.org/wiki/Software/systemd/journal-files - * - */ - -typedef struct Header Header; - -typedef struct ObjectHeader ObjectHeader; -typedef union Object Object; - -typedef struct DataObject DataObject; -typedef struct FieldObject FieldObject; -typedef struct EntryObject EntryObject; -typedef struct HashTableObject HashTableObject; -typedef struct EntryArrayObject EntryArrayObject; -typedef struct TagObject TagObject; - -typedef struct EntryItem EntryItem; -typedef struct HashItem HashItem; - -typedef struct FSSHeader FSSHeader; - -/* Object types */ -typedef enum ObjectType { - OBJECT_UNUSED, /* also serves as "any type" or "additional context" */ - OBJECT_DATA, - OBJECT_FIELD, - OBJECT_ENTRY, - OBJECT_DATA_HASH_TABLE, - OBJECT_FIELD_HASH_TABLE, - OBJECT_ENTRY_ARRAY, - OBJECT_TAG, - _OBJECT_TYPE_MAX -} ObjectType; - -/* Object flags */ -enum { - OBJECT_COMPRESSED_XZ = 1 << 0, - OBJECT_COMPRESSED_LZ4 = 1 << 1, - _OBJECT_COMPRESSED_MAX -}; - -#define OBJECT_COMPRESSION_MASK (OBJECT_COMPRESSED_XZ | OBJECT_COMPRESSED_LZ4) - -struct ObjectHeader { - uint8_t type; - uint8_t flags; - uint8_t reserved[6]; - le64_t size; - uint8_t payload[]; -} _packed_; - -struct DataObject { - ObjectHeader object; - le64_t hash; - le64_t next_hash_offset; - le64_t next_field_offset; - le64_t entry_offset; /* the first array entry we store inline */ - le64_t entry_array_offset; - le64_t n_entries; - uint8_t payload[]; -} _packed_; - -struct FieldObject { - ObjectHeader object; - le64_t hash; - le64_t next_hash_offset; - le64_t head_data_offset; - uint8_t payload[]; -} _packed_; - -struct EntryItem { - le64_t object_offset; - le64_t hash; -} _packed_; - -struct EntryObject { - ObjectHeader object; - le64_t seqnum; - le64_t realtime; - le64_t monotonic; - sd_id128_t boot_id; - le64_t xor_hash; - EntryItem items[]; -} _packed_; - -struct HashItem { - le64_t head_hash_offset; - le64_t tail_hash_offset; -} _packed_; - -struct HashTableObject { - ObjectHeader object; - HashItem items[]; -} _packed_; - -struct EntryArrayObject { - ObjectHeader object; - le64_t next_entry_array_offset; - le64_t items[]; -} _packed_; - -#define TAG_LENGTH (256/8) - -struct TagObject { - ObjectHeader object; - le64_t seqnum; - le64_t epoch; - uint8_t tag[TAG_LENGTH]; /* SHA-256 HMAC */ -} _packed_; - -union Object { - ObjectHeader object; - DataObject data; - FieldObject field; - EntryObject entry; - HashTableObject hash_table; - EntryArrayObject entry_array; - TagObject tag; -}; - -enum { - STATE_OFFLINE = 0, - STATE_ONLINE = 1, - STATE_ARCHIVED = 2, - _STATE_MAX -}; - -/* Header flags */ -enum { - HEADER_INCOMPATIBLE_COMPRESSED_XZ = 1 << 0, - HEADER_INCOMPATIBLE_COMPRESSED_LZ4 = 1 << 1, -}; - -#define HEADER_INCOMPATIBLE_ANY (HEADER_INCOMPATIBLE_COMPRESSED_XZ|HEADER_INCOMPATIBLE_COMPRESSED_LZ4) - -#if defined(HAVE_XZ) && defined(HAVE_LZ4) -# define HEADER_INCOMPATIBLE_SUPPORTED HEADER_INCOMPATIBLE_ANY -#elif defined(HAVE_XZ) -# define HEADER_INCOMPATIBLE_SUPPORTED HEADER_INCOMPATIBLE_COMPRESSED_XZ -#elif defined(HAVE_LZ4) -# define HEADER_INCOMPATIBLE_SUPPORTED HEADER_INCOMPATIBLE_COMPRESSED_LZ4 -#else -# define HEADER_INCOMPATIBLE_SUPPORTED 0 -#endif - -enum { - HEADER_COMPATIBLE_SEALED = 1 -}; - -#define HEADER_COMPATIBLE_ANY HEADER_COMPATIBLE_SEALED -#ifdef HAVE_GCRYPT -# define HEADER_COMPATIBLE_SUPPORTED HEADER_COMPATIBLE_SEALED -#else -# define HEADER_COMPATIBLE_SUPPORTED 0 -#endif - -#define HEADER_SIGNATURE ((char[]) { 'L', 'P', 'K', 'S', 'H', 'H', 'R', 'H' }) - -struct Header { - uint8_t signature[8]; /* "LPKSHHRH" */ - le32_t compatible_flags; - le32_t incompatible_flags; - uint8_t state; - uint8_t reserved[7]; - sd_id128_t file_id; - sd_id128_t machine_id; - sd_id128_t boot_id; /* last writer */ - sd_id128_t seqnum_id; - le64_t header_size; - le64_t arena_size; - le64_t data_hash_table_offset; - le64_t data_hash_table_size; - le64_t field_hash_table_offset; - le64_t field_hash_table_size; - le64_t tail_object_offset; - le64_t n_objects; - le64_t n_entries; - le64_t tail_entry_seqnum; - le64_t head_entry_seqnum; - le64_t entry_array_offset; - le64_t head_entry_realtime; - le64_t tail_entry_realtime; - le64_t tail_entry_monotonic; - /* Added in 187 */ - le64_t n_data; - le64_t n_fields; - /* Added in 189 */ - le64_t n_tags; - le64_t n_entry_arrays; - - /* Size: 240 */ -} _packed_; - -#define FSS_HEADER_SIGNATURE ((char[]) { 'K', 'S', 'H', 'H', 'R', 'H', 'L', 'P' }) - -struct FSSHeader { - uint8_t signature[8]; /* "KSHHRHLP" */ - le32_t compatible_flags; - le32_t incompatible_flags; - sd_id128_t machine_id; - sd_id128_t boot_id; /* last writer */ - le64_t header_size; - le64_t start_usec; - le64_t interval_usec; - le16_t fsprg_secpar; - le16_t reserved[3]; - le64_t fsprg_state_size; -} _packed_; diff --git a/src/journal/journal-file.c b/src/journal/journal-file.c deleted file mode 100644 index 7504326bff..0000000000 --- a/src/journal/journal-file.c +++ /dev/null @@ -1,3616 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "btrfs-util.h" -#include "chattr-util.h" -#include "compress.h" -#include "fd-util.h" -#include "journal-authenticate.h" -#include "journal-def.h" -#include "journal-file.h" -#include "lookup3.h" -#include "parse-util.h" -#include "path-util.h" -#include "random-util.h" -#include "sd-event.h" -#include "set.h" -#include "string-util.h" -#include "xattr-util.h" - -#define DEFAULT_DATA_HASH_TABLE_SIZE (2047ULL*sizeof(HashItem)) -#define DEFAULT_FIELD_HASH_TABLE_SIZE (333ULL*sizeof(HashItem)) - -#define COMPRESSION_SIZE_THRESHOLD (512ULL) - -/* This is the minimum journal file size */ -#define JOURNAL_FILE_SIZE_MIN (512ULL*1024ULL) /* 512 KiB */ - -/* These are the lower and upper bounds if we deduce the max_use value - * from the file system size */ -#define DEFAULT_MAX_USE_LOWER (1ULL*1024ULL*1024ULL) /* 1 MiB */ -#define DEFAULT_MAX_USE_UPPER (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */ - -/* This is the default minimal use limit, how much we'll use even if keep_free suggests otherwise. */ -#define DEFAULT_MIN_USE (1ULL*1024ULL*1024ULL) /* 1 MiB */ - -/* This is the upper bound if we deduce max_size from max_use */ -#define DEFAULT_MAX_SIZE_UPPER (128ULL*1024ULL*1024ULL) /* 128 MiB */ - -/* This is the upper bound if we deduce the keep_free value from the - * file system size */ -#define DEFAULT_KEEP_FREE_UPPER (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */ - -/* This is the keep_free value when we can't determine the system - * size */ -#define DEFAULT_KEEP_FREE (1024ULL*1024ULL) /* 1 MB */ - -/* This is the default maximum number of journal files to keep around. */ -#define DEFAULT_N_MAX_FILES (100) - -/* n_data was the first entry we added after the initial file format design */ -#define HEADER_SIZE_MIN ALIGN64(offsetof(Header, n_data)) - -/* How many entries to keep in the entry array chain cache at max */ -#define CHAIN_CACHE_MAX 20 - -/* How much to increase the journal file size at once each time we allocate something new. */ -#define FILE_SIZE_INCREASE (8ULL*1024ULL*1024ULL) /* 8MB */ - -/* Reread fstat() of the file for detecting deletions at least this often */ -#define LAST_STAT_REFRESH_USEC (5*USEC_PER_SEC) - -/* The mmap context to use for the header we pick as one above the last defined typed */ -#define CONTEXT_HEADER _OBJECT_TYPE_MAX - -/* This may be called from a separate thread to prevent blocking the caller for the duration of fsync(). - * As a result we use atomic operations on f->offline_state for inter-thread communications with - * journal_file_set_offline() and journal_file_set_online(). */ -static void journal_file_set_offline_internal(JournalFile *f) { - assert(f); - assert(f->fd >= 0); - assert(f->header); - - for (;;) { - switch (f->offline_state) { - case OFFLINE_CANCEL: - if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_CANCEL, OFFLINE_DONE)) - continue; - return; - - case OFFLINE_AGAIN_FROM_SYNCING: - if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_AGAIN_FROM_SYNCING, OFFLINE_SYNCING)) - continue; - break; - - case OFFLINE_AGAIN_FROM_OFFLINING: - if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_AGAIN_FROM_OFFLINING, OFFLINE_SYNCING)) - continue; - break; - - case OFFLINE_SYNCING: - (void) fsync(f->fd); - - if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_SYNCING, OFFLINE_OFFLINING)) - continue; - - f->header->state = f->archive ? STATE_ARCHIVED : STATE_OFFLINE; - (void) fsync(f->fd); - break; - - case OFFLINE_OFFLINING: - if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_OFFLINING, OFFLINE_DONE)) - continue; - /* fall through */ - - case OFFLINE_DONE: - return; - - case OFFLINE_JOINED: - log_debug("OFFLINE_JOINED unexpected offline state for journal_file_set_offline_internal()"); - return; - } - } -} - -static void * journal_file_set_offline_thread(void *arg) { - JournalFile *f = arg; - - journal_file_set_offline_internal(f); - - return NULL; -} - -static int journal_file_set_offline_thread_join(JournalFile *f) { - int r; - - assert(f); - - if (f->offline_state == OFFLINE_JOINED) - return 0; - - r = pthread_join(f->offline_thread, NULL); - if (r) - return -r; - - f->offline_state = OFFLINE_JOINED; - - if (mmap_cache_got_sigbus(f->mmap, f->fd)) - return -EIO; - - return 0; -} - -/* Trigger a restart if the offline thread is mid-flight in a restartable state. */ -static bool journal_file_set_offline_try_restart(JournalFile *f) { - for (;;) { - switch (f->offline_state) { - case OFFLINE_AGAIN_FROM_SYNCING: - case OFFLINE_AGAIN_FROM_OFFLINING: - return true; - - case OFFLINE_CANCEL: - if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_CANCEL, OFFLINE_AGAIN_FROM_SYNCING)) - continue; - return true; - - case OFFLINE_SYNCING: - if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_SYNCING, OFFLINE_AGAIN_FROM_SYNCING)) - continue; - return true; - - case OFFLINE_OFFLINING: - if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_OFFLINING, OFFLINE_AGAIN_FROM_OFFLINING)) - continue; - return true; - - default: - return false; - } - } -} - -/* Sets a journal offline. - * - * If wait is false then an offline is dispatched in a separate thread for a - * subsequent journal_file_set_offline() or journal_file_set_online() of the - * same journal to synchronize with. - * - * If wait is true, then either an existing offline thread will be restarted - * and joined, or if none exists the offline is simply performed in this - * context without involving another thread. - */ -int journal_file_set_offline(JournalFile *f, bool wait) { - bool restarted; - int r; - - assert(f); - - if (!f->writable) - return -EPERM; - - if (!(f->fd >= 0 && f->header)) - return -EINVAL; - - /* An offlining journal is implicitly online and may modify f->header->state, - * we must also join any potentially lingering offline thread when not online. */ - if (!journal_file_is_offlining(f) && f->header->state != STATE_ONLINE) - return journal_file_set_offline_thread_join(f); - - /* Restart an in-flight offline thread and wait if needed, or join a lingering done one. */ - restarted = journal_file_set_offline_try_restart(f); - if ((restarted && wait) || !restarted) { - r = journal_file_set_offline_thread_join(f); - if (r < 0) - return r; - } - - if (restarted) - return 0; - - /* Initiate a new offline. */ - f->offline_state = OFFLINE_SYNCING; - - if (wait) /* Without using a thread if waiting. */ - journal_file_set_offline_internal(f); - else { - r = pthread_create(&f->offline_thread, NULL, journal_file_set_offline_thread, f); - if (r > 0) { - f->offline_state = OFFLINE_JOINED; - return -r; - } - } - - return 0; -} - -static int journal_file_set_online(JournalFile *f) { - bool joined = false; - - assert(f); - - if (!f->writable) - return -EPERM; - - if (!(f->fd >= 0 && f->header)) - return -EINVAL; - - while (!joined) { - switch (f->offline_state) { - case OFFLINE_JOINED: - /* No offline thread, no need to wait. */ - joined = true; - break; - - case OFFLINE_SYNCING: - if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_SYNCING, OFFLINE_CANCEL)) - continue; - /* Canceled syncing prior to offlining, no need to wait. */ - break; - - case OFFLINE_AGAIN_FROM_SYNCING: - if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_AGAIN_FROM_SYNCING, OFFLINE_CANCEL)) - continue; - /* Canceled restart from syncing, no need to wait. */ - break; - - case OFFLINE_AGAIN_FROM_OFFLINING: - if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_AGAIN_FROM_OFFLINING, OFFLINE_CANCEL)) - continue; - /* Canceled restart from offlining, must wait for offlining to complete however. */ - - /* fall through to wait */ - default: { - int r; - - r = journal_file_set_offline_thread_join(f); - if (r < 0) - return r; - - joined = true; - break; - } - } - } - - if (mmap_cache_got_sigbus(f->mmap, f->fd)) - return -EIO; - - switch (f->header->state) { - case STATE_ONLINE: - return 0; - - case STATE_OFFLINE: - f->header->state = STATE_ONLINE; - (void) fsync(f->fd); - return 0; - - default: - return -EINVAL; - } -} - -bool journal_file_is_offlining(JournalFile *f) { - assert(f); - - __sync_synchronize(); - - if (f->offline_state == OFFLINE_DONE || - f->offline_state == OFFLINE_JOINED) - return false; - - return true; -} - -JournalFile* journal_file_close(JournalFile *f) { - assert(f); - -#ifdef HAVE_GCRYPT - /* Write the final tag */ - if (f->seal && f->writable) - journal_file_append_tag(f); -#endif - - if (f->post_change_timer) { - int enabled; - - if (sd_event_source_get_enabled(f->post_change_timer, &enabled) >= 0) - if (enabled == SD_EVENT_ONESHOT) - journal_file_post_change(f); - - (void) sd_event_source_set_enabled(f->post_change_timer, SD_EVENT_OFF); - sd_event_source_unref(f->post_change_timer); - } - - journal_file_set_offline(f, true); - - if (f->mmap && f->fd >= 0) - mmap_cache_close_fd(f->mmap, f->fd); - - if (f->fd >= 0 && f->defrag_on_close) { - - /* Be friendly to btrfs: turn COW back on again now, - * and defragment the file. We won't write to the file - * ever again, hence remove all fragmentation, and - * reenable all the good bits COW usually provides - * (such as data checksumming). */ - - (void) chattr_fd(f->fd, 0, FS_NOCOW_FL); - (void) btrfs_defrag_fd(f->fd); - } - - if (f->close_fd) - safe_close(f->fd); - free(f->path); - - mmap_cache_unref(f->mmap); - - ordered_hashmap_free_free(f->chain_cache); - -#if defined(HAVE_XZ) || defined(HAVE_LZ4) - free(f->compress_buffer); -#endif - -#ifdef HAVE_GCRYPT - if (f->fss_file) - munmap(f->fss_file, PAGE_ALIGN(f->fss_file_size)); - else - free(f->fsprg_state); - - free(f->fsprg_seed); - - if (f->hmac) - gcry_md_close(f->hmac); -#endif - - free(f); - return NULL; -} - -void journal_file_close_set(Set *s) { - JournalFile *f; - - assert(s); - - while ((f = set_steal_first(s))) - (void) journal_file_close(f); -} - -static int journal_file_init_header(JournalFile *f, JournalFile *template) { - Header h = {}; - ssize_t k; - int r; - - assert(f); - - memcpy(h.signature, HEADER_SIGNATURE, 8); - h.header_size = htole64(ALIGN64(sizeof(h))); - - h.incompatible_flags |= htole32( - f->compress_xz * HEADER_INCOMPATIBLE_COMPRESSED_XZ | - f->compress_lz4 * HEADER_INCOMPATIBLE_COMPRESSED_LZ4); - - h.compatible_flags = htole32( - f->seal * HEADER_COMPATIBLE_SEALED); - - r = sd_id128_randomize(&h.file_id); - if (r < 0) - return r; - - if (template) { - h.seqnum_id = template->header->seqnum_id; - h.tail_entry_seqnum = template->header->tail_entry_seqnum; - } else - h.seqnum_id = h.file_id; - - k = pwrite(f->fd, &h, sizeof(h), 0); - if (k < 0) - return -errno; - - if (k != sizeof(h)) - return -EIO; - - return 0; -} - -static int fsync_directory_of_file(int fd) { - _cleanup_free_ char *path = NULL, *dn = NULL; - _cleanup_close_ int dfd = -1; - struct stat st; - int r; - - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISREG(st.st_mode)) - return -EBADFD; - - r = fd_get_path(fd, &path); - if (r < 0) - return r; - - if (!path_is_absolute(path)) - return -EINVAL; - - dn = dirname_malloc(path); - if (!dn) - return -ENOMEM; - - dfd = open(dn, O_RDONLY|O_CLOEXEC|O_DIRECTORY); - if (dfd < 0) - return -errno; - - if (fsync(dfd) < 0) - return -errno; - - return 0; -} - -static int journal_file_refresh_header(JournalFile *f) { - sd_id128_t boot_id; - int r; - - assert(f); - assert(f->header); - - r = sd_id128_get_machine(&f->header->machine_id); - if (r < 0) - return r; - - r = sd_id128_get_boot(&boot_id); - if (r < 0) - return r; - - if (sd_id128_equal(boot_id, f->header->boot_id)) - f->tail_entry_monotonic_valid = true; - - f->header->boot_id = boot_id; - - r = journal_file_set_online(f); - - /* Sync the online state to disk */ - (void) fsync(f->fd); - - /* We likely just created a new file, also sync the directory this file is located in. */ - (void) fsync_directory_of_file(f->fd); - - return r; -} - -static int journal_file_verify_header(JournalFile *f) { - uint32_t flags; - - assert(f); - assert(f->header); - - if (memcmp(f->header->signature, HEADER_SIGNATURE, 8)) - return -EBADMSG; - - /* In both read and write mode we refuse to open files with - * incompatible flags we don't know */ - flags = le32toh(f->header->incompatible_flags); - if (flags & ~HEADER_INCOMPATIBLE_SUPPORTED) { - if (flags & ~HEADER_INCOMPATIBLE_ANY) - log_debug("Journal file %s has unknown incompatible flags %"PRIx32, - f->path, flags & ~HEADER_INCOMPATIBLE_ANY); - flags = (flags & HEADER_INCOMPATIBLE_ANY) & ~HEADER_INCOMPATIBLE_SUPPORTED; - if (flags) - log_debug("Journal file %s uses incompatible flags %"PRIx32 - " disabled at compilation time.", f->path, flags); - return -EPROTONOSUPPORT; - } - - /* When open for writing we refuse to open files with - * compatible flags, too */ - flags = le32toh(f->header->compatible_flags); - if (f->writable && (flags & ~HEADER_COMPATIBLE_SUPPORTED)) { - if (flags & ~HEADER_COMPATIBLE_ANY) - log_debug("Journal file %s has unknown compatible flags %"PRIx32, - f->path, flags & ~HEADER_COMPATIBLE_ANY); - flags = (flags & HEADER_COMPATIBLE_ANY) & ~HEADER_COMPATIBLE_SUPPORTED; - if (flags) - log_debug("Journal file %s uses compatible flags %"PRIx32 - " disabled at compilation time.", f->path, flags); - return -EPROTONOSUPPORT; - } - - if (f->header->state >= _STATE_MAX) - return -EBADMSG; - - /* The first addition was n_data, so check that we are at least this large */ - if (le64toh(f->header->header_size) < HEADER_SIZE_MIN) - return -EBADMSG; - - if (JOURNAL_HEADER_SEALED(f->header) && !JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays)) - return -EBADMSG; - - if ((le64toh(f->header->header_size) + le64toh(f->header->arena_size)) > (uint64_t) f->last_stat.st_size) - return -ENODATA; - - if (le64toh(f->header->tail_object_offset) > (le64toh(f->header->header_size) + le64toh(f->header->arena_size))) - return -ENODATA; - - if (!VALID64(le64toh(f->header->data_hash_table_offset)) || - !VALID64(le64toh(f->header->field_hash_table_offset)) || - !VALID64(le64toh(f->header->tail_object_offset)) || - !VALID64(le64toh(f->header->entry_array_offset))) - return -ENODATA; - - if (f->writable) { - uint8_t state; - sd_id128_t machine_id; - int r; - - r = sd_id128_get_machine(&machine_id); - if (r < 0) - return r; - - if (!sd_id128_equal(machine_id, f->header->machine_id)) - return -EHOSTDOWN; - - state = f->header->state; - - if (state == STATE_ONLINE) { - log_debug("Journal file %s is already online. Assuming unclean closing.", f->path); - return -EBUSY; - } else if (state == STATE_ARCHIVED) - return -ESHUTDOWN; - else if (state != STATE_OFFLINE) { - log_debug("Journal file %s has unknown state %i.", f->path, state); - return -EBUSY; - } - } - - f->compress_xz = JOURNAL_HEADER_COMPRESSED_XZ(f->header); - f->compress_lz4 = JOURNAL_HEADER_COMPRESSED_LZ4(f->header); - - f->seal = JOURNAL_HEADER_SEALED(f->header); - - return 0; -} - -static int journal_file_fstat(JournalFile *f) { - assert(f); - assert(f->fd >= 0); - - if (fstat(f->fd, &f->last_stat) < 0) - return -errno; - - f->last_stat_usec = now(CLOCK_MONOTONIC); - - /* Refuse appending to files that are already deleted */ - if (f->last_stat.st_nlink <= 0) - return -EIDRM; - - return 0; -} - -static int journal_file_allocate(JournalFile *f, uint64_t offset, uint64_t size) { - uint64_t old_size, new_size; - int r; - - assert(f); - assert(f->header); - - /* We assume that this file is not sparse, and we know that - * for sure, since we always call posix_fallocate() - * ourselves */ - - if (mmap_cache_got_sigbus(f->mmap, f->fd)) - return -EIO; - - old_size = - le64toh(f->header->header_size) + - le64toh(f->header->arena_size); - - new_size = PAGE_ALIGN(offset + size); - if (new_size < le64toh(f->header->header_size)) - new_size = le64toh(f->header->header_size); - - if (new_size <= old_size) { - - /* We already pre-allocated enough space, but before - * we write to it, let's check with fstat() if the - * file got deleted, in order make sure we don't throw - * away the data immediately. Don't check fstat() for - * all writes though, but only once ever 10s. */ - - if (f->last_stat_usec + LAST_STAT_REFRESH_USEC > now(CLOCK_MONOTONIC)) - return 0; - - return journal_file_fstat(f); - } - - /* Allocate more space. */ - - if (f->metrics.max_size > 0 && new_size > f->metrics.max_size) - return -E2BIG; - - if (new_size > f->metrics.min_size && f->metrics.keep_free > 0) { - struct statvfs svfs; - - if (fstatvfs(f->fd, &svfs) >= 0) { - uint64_t available; - - available = LESS_BY((uint64_t) svfs.f_bfree * (uint64_t) svfs.f_bsize, f->metrics.keep_free); - - if (new_size - old_size > available) - return -E2BIG; - } - } - - /* Increase by larger blocks at once */ - new_size = ((new_size+FILE_SIZE_INCREASE-1) / FILE_SIZE_INCREASE) * FILE_SIZE_INCREASE; - if (f->metrics.max_size > 0 && new_size > f->metrics.max_size) - new_size = f->metrics.max_size; - - /* Note that the glibc fallocate() fallback is very - inefficient, hence we try to minimize the allocation area - as we can. */ - r = posix_fallocate(f->fd, old_size, new_size - old_size); - if (r != 0) - return -r; - - f->header->arena_size = htole64(new_size - le64toh(f->header->header_size)); - - return journal_file_fstat(f); -} - -static unsigned type_to_context(ObjectType type) { - /* One context for each type, plus one catch-all for the rest */ - assert_cc(_OBJECT_TYPE_MAX <= MMAP_CACHE_MAX_CONTEXTS); - assert_cc(CONTEXT_HEADER < MMAP_CACHE_MAX_CONTEXTS); - return type > OBJECT_UNUSED && type < _OBJECT_TYPE_MAX ? type : 0; -} - -static int journal_file_move_to(JournalFile *f, ObjectType type, bool keep_always, uint64_t offset, uint64_t size, void **ret) { - int r; - - assert(f); - assert(ret); - - if (size <= 0) - return -EINVAL; - - /* Avoid SIGBUS on invalid accesses */ - if (offset + size > (uint64_t) f->last_stat.st_size) { - /* Hmm, out of range? Let's refresh the fstat() data - * first, before we trust that check. */ - - r = journal_file_fstat(f); - if (r < 0) - return r; - - if (offset + size > (uint64_t) f->last_stat.st_size) - return -EADDRNOTAVAIL; - } - - return mmap_cache_get(f->mmap, f->fd, f->prot, type_to_context(type), keep_always, offset, size, &f->last_stat, ret); -} - -static uint64_t minimum_header_size(Object *o) { - - static const uint64_t table[] = { - [OBJECT_DATA] = sizeof(DataObject), - [OBJECT_FIELD] = sizeof(FieldObject), - [OBJECT_ENTRY] = sizeof(EntryObject), - [OBJECT_DATA_HASH_TABLE] = sizeof(HashTableObject), - [OBJECT_FIELD_HASH_TABLE] = sizeof(HashTableObject), - [OBJECT_ENTRY_ARRAY] = sizeof(EntryArrayObject), - [OBJECT_TAG] = sizeof(TagObject), - }; - - if (o->object.type >= ELEMENTSOF(table) || table[o->object.type] <= 0) - return sizeof(ObjectHeader); - - return table[o->object.type]; -} - -int journal_file_move_to_object(JournalFile *f, ObjectType type, uint64_t offset, Object **ret) { - int r; - void *t; - Object *o; - uint64_t s; - - assert(f); - assert(ret); - - /* Objects may only be located at multiple of 64 bit */ - if (!VALID64(offset)) - return -EBADMSG; - - /* Object may not be located in the file header */ - if (offset < le64toh(f->header->header_size)) - return -EBADMSG; - - r = journal_file_move_to(f, type, false, offset, sizeof(ObjectHeader), &t); - if (r < 0) - return r; - - o = (Object*) t; - s = le64toh(o->object.size); - - if (s < sizeof(ObjectHeader)) - return -EBADMSG; - - if (o->object.type <= OBJECT_UNUSED) - return -EBADMSG; - - if (s < minimum_header_size(o)) - return -EBADMSG; - - if (type > OBJECT_UNUSED && o->object.type != type) - return -EBADMSG; - - if (s > sizeof(ObjectHeader)) { - r = journal_file_move_to(f, type, false, offset, s, &t); - if (r < 0) - return r; - - o = (Object*) t; - } - - *ret = o; - return 0; -} - -static uint64_t journal_file_entry_seqnum(JournalFile *f, uint64_t *seqnum) { - uint64_t r; - - assert(f); - assert(f->header); - - r = le64toh(f->header->tail_entry_seqnum) + 1; - - if (seqnum) { - /* If an external seqnum counter was passed, we update - * both the local and the external one, and set it to - * the maximum of both */ - - if (*seqnum + 1 > r) - r = *seqnum + 1; - - *seqnum = r; - } - - f->header->tail_entry_seqnum = htole64(r); - - if (f->header->head_entry_seqnum == 0) - f->header->head_entry_seqnum = htole64(r); - - return r; -} - -int journal_file_append_object(JournalFile *f, ObjectType type, uint64_t size, Object **ret, uint64_t *offset) { - int r; - uint64_t p; - Object *tail, *o; - void *t; - - assert(f); - assert(f->header); - assert(type > OBJECT_UNUSED && type < _OBJECT_TYPE_MAX); - assert(size >= sizeof(ObjectHeader)); - assert(offset); - assert(ret); - - r = journal_file_set_online(f); - if (r < 0) - return r; - - p = le64toh(f->header->tail_object_offset); - if (p == 0) - p = le64toh(f->header->header_size); - else { - r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &tail); - if (r < 0) - return r; - - p += ALIGN64(le64toh(tail->object.size)); - } - - r = journal_file_allocate(f, p, size); - if (r < 0) - return r; - - r = journal_file_move_to(f, type, false, p, size, &t); - if (r < 0) - return r; - - o = (Object*) t; - - zero(o->object); - o->object.type = type; - o->object.size = htole64(size); - - f->header->tail_object_offset = htole64(p); - f->header->n_objects = htole64(le64toh(f->header->n_objects) + 1); - - *ret = o; - *offset = p; - - return 0; -} - -static int journal_file_setup_data_hash_table(JournalFile *f) { - uint64_t s, p; - Object *o; - int r; - - assert(f); - assert(f->header); - - /* We estimate that we need 1 hash table entry per 768 bytes - of journal file and we want to make sure we never get - beyond 75% fill level. Calculate the hash table size for - the maximum file size based on these metrics. */ - - s = (f->metrics.max_size * 4 / 768 / 3) * sizeof(HashItem); - if (s < DEFAULT_DATA_HASH_TABLE_SIZE) - s = DEFAULT_DATA_HASH_TABLE_SIZE; - - log_debug("Reserving %"PRIu64" entries in hash table.", s / sizeof(HashItem)); - - r = journal_file_append_object(f, - OBJECT_DATA_HASH_TABLE, - offsetof(Object, hash_table.items) + s, - &o, &p); - if (r < 0) - return r; - - memzero(o->hash_table.items, s); - - f->header->data_hash_table_offset = htole64(p + offsetof(Object, hash_table.items)); - f->header->data_hash_table_size = htole64(s); - - return 0; -} - -static int journal_file_setup_field_hash_table(JournalFile *f) { - uint64_t s, p; - Object *o; - int r; - - assert(f); - assert(f->header); - - /* We use a fixed size hash table for the fields as this - * number should grow very slowly only */ - - s = DEFAULT_FIELD_HASH_TABLE_SIZE; - r = journal_file_append_object(f, - OBJECT_FIELD_HASH_TABLE, - offsetof(Object, hash_table.items) + s, - &o, &p); - if (r < 0) - return r; - - memzero(o->hash_table.items, s); - - f->header->field_hash_table_offset = htole64(p + offsetof(Object, hash_table.items)); - f->header->field_hash_table_size = htole64(s); - - return 0; -} - -int journal_file_map_data_hash_table(JournalFile *f) { - uint64_t s, p; - void *t; - int r; - - assert(f); - assert(f->header); - - if (f->data_hash_table) - return 0; - - p = le64toh(f->header->data_hash_table_offset); - s = le64toh(f->header->data_hash_table_size); - - r = journal_file_move_to(f, - OBJECT_DATA_HASH_TABLE, - true, - p, s, - &t); - if (r < 0) - return r; - - f->data_hash_table = t; - return 0; -} - -int journal_file_map_field_hash_table(JournalFile *f) { - uint64_t s, p; - void *t; - int r; - - assert(f); - assert(f->header); - - if (f->field_hash_table) - return 0; - - p = le64toh(f->header->field_hash_table_offset); - s = le64toh(f->header->field_hash_table_size); - - r = journal_file_move_to(f, - OBJECT_FIELD_HASH_TABLE, - true, - p, s, - &t); - if (r < 0) - return r; - - f->field_hash_table = t; - return 0; -} - -static int journal_file_link_field( - JournalFile *f, - Object *o, - uint64_t offset, - uint64_t hash) { - - uint64_t p, h, m; - int r; - - assert(f); - assert(f->header); - assert(f->field_hash_table); - assert(o); - assert(offset > 0); - - if (o->object.type != OBJECT_FIELD) - return -EINVAL; - - m = le64toh(f->header->field_hash_table_size) / sizeof(HashItem); - if (m <= 0) - return -EBADMSG; - - /* This might alter the window we are looking at */ - o->field.next_hash_offset = o->field.head_data_offset = 0; - - h = hash % m; - p = le64toh(f->field_hash_table[h].tail_hash_offset); - if (p == 0) - f->field_hash_table[h].head_hash_offset = htole64(offset); - else { - r = journal_file_move_to_object(f, OBJECT_FIELD, p, &o); - if (r < 0) - return r; - - o->field.next_hash_offset = htole64(offset); - } - - f->field_hash_table[h].tail_hash_offset = htole64(offset); - - if (JOURNAL_HEADER_CONTAINS(f->header, n_fields)) - f->header->n_fields = htole64(le64toh(f->header->n_fields) + 1); - - return 0; -} - -static int journal_file_link_data( - JournalFile *f, - Object *o, - uint64_t offset, - uint64_t hash) { - - uint64_t p, h, m; - int r; - - assert(f); - assert(f->header); - assert(f->data_hash_table); - assert(o); - assert(offset > 0); - - if (o->object.type != OBJECT_DATA) - return -EINVAL; - - m = le64toh(f->header->data_hash_table_size) / sizeof(HashItem); - if (m <= 0) - return -EBADMSG; - - /* This might alter the window we are looking at */ - o->data.next_hash_offset = o->data.next_field_offset = 0; - o->data.entry_offset = o->data.entry_array_offset = 0; - o->data.n_entries = 0; - - h = hash % m; - p = le64toh(f->data_hash_table[h].tail_hash_offset); - if (p == 0) - /* Only entry in the hash table is easy */ - f->data_hash_table[h].head_hash_offset = htole64(offset); - else { - /* Move back to the previous data object, to patch in - * pointer */ - - r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); - if (r < 0) - return r; - - o->data.next_hash_offset = htole64(offset); - } - - f->data_hash_table[h].tail_hash_offset = htole64(offset); - - if (JOURNAL_HEADER_CONTAINS(f->header, n_data)) - f->header->n_data = htole64(le64toh(f->header->n_data) + 1); - - return 0; -} - -int journal_file_find_field_object_with_hash( - JournalFile *f, - const void *field, uint64_t size, uint64_t hash, - Object **ret, uint64_t *offset) { - - uint64_t p, osize, h, m; - int r; - - assert(f); - assert(f->header); - assert(field && size > 0); - - /* If the field hash table is empty, we can't find anything */ - if (le64toh(f->header->field_hash_table_size) <= 0) - return 0; - - /* Map the field hash table, if it isn't mapped yet. */ - r = journal_file_map_field_hash_table(f); - if (r < 0) - return r; - - osize = offsetof(Object, field.payload) + size; - - m = le64toh(f->header->field_hash_table_size) / sizeof(HashItem); - if (m <= 0) - return -EBADMSG; - - h = hash % m; - p = le64toh(f->field_hash_table[h].head_hash_offset); - - while (p > 0) { - Object *o; - - r = journal_file_move_to_object(f, OBJECT_FIELD, p, &o); - if (r < 0) - return r; - - if (le64toh(o->field.hash) == hash && - le64toh(o->object.size) == osize && - memcmp(o->field.payload, field, size) == 0) { - - if (ret) - *ret = o; - if (offset) - *offset = p; - - return 1; - } - - p = le64toh(o->field.next_hash_offset); - } - - return 0; -} - -int journal_file_find_field_object( - JournalFile *f, - const void *field, uint64_t size, - Object **ret, uint64_t *offset) { - - uint64_t hash; - - assert(f); - assert(field && size > 0); - - hash = hash64(field, size); - - return journal_file_find_field_object_with_hash(f, - field, size, hash, - ret, offset); -} - -int journal_file_find_data_object_with_hash( - JournalFile *f, - const void *data, uint64_t size, uint64_t hash, - Object **ret, uint64_t *offset) { - - uint64_t p, osize, h, m; - int r; - - assert(f); - assert(f->header); - assert(data || size == 0); - - /* If there's no data hash table, then there's no entry. */ - if (le64toh(f->header->data_hash_table_size) <= 0) - return 0; - - /* Map the data hash table, if it isn't mapped yet. */ - r = journal_file_map_data_hash_table(f); - if (r < 0) - return r; - - osize = offsetof(Object, data.payload) + size; - - m = le64toh(f->header->data_hash_table_size) / sizeof(HashItem); - if (m <= 0) - return -EBADMSG; - - h = hash % m; - p = le64toh(f->data_hash_table[h].head_hash_offset); - - while (p > 0) { - Object *o; - - r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); - if (r < 0) - return r; - - if (le64toh(o->data.hash) != hash) - goto next; - - if (o->object.flags & OBJECT_COMPRESSION_MASK) { -#if defined(HAVE_XZ) || defined(HAVE_LZ4) - uint64_t l; - size_t rsize = 0; - - l = le64toh(o->object.size); - if (l <= offsetof(Object, data.payload)) - return -EBADMSG; - - l -= offsetof(Object, data.payload); - - r = decompress_blob(o->object.flags & OBJECT_COMPRESSION_MASK, - o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize, 0); - if (r < 0) - return r; - - if (rsize == size && - memcmp(f->compress_buffer, data, size) == 0) { - - if (ret) - *ret = o; - - if (offset) - *offset = p; - - return 1; - } -#else - return -EPROTONOSUPPORT; -#endif - } else if (le64toh(o->object.size) == osize && - memcmp(o->data.payload, data, size) == 0) { - - if (ret) - *ret = o; - - if (offset) - *offset = p; - - return 1; - } - - next: - p = le64toh(o->data.next_hash_offset); - } - - return 0; -} - -int journal_file_find_data_object( - JournalFile *f, - const void *data, uint64_t size, - Object **ret, uint64_t *offset) { - - uint64_t hash; - - assert(f); - assert(data || size == 0); - - hash = hash64(data, size); - - return journal_file_find_data_object_with_hash(f, - data, size, hash, - ret, offset); -} - -static int journal_file_append_field( - JournalFile *f, - const void *field, uint64_t size, - Object **ret, uint64_t *offset) { - - uint64_t hash, p; - uint64_t osize; - Object *o; - int r; - - assert(f); - assert(field && size > 0); - - hash = hash64(field, size); - - r = journal_file_find_field_object_with_hash(f, field, size, hash, &o, &p); - if (r < 0) - return r; - else if (r > 0) { - - if (ret) - *ret = o; - - if (offset) - *offset = p; - - return 0; - } - - osize = offsetof(Object, field.payload) + size; - r = journal_file_append_object(f, OBJECT_FIELD, osize, &o, &p); - if (r < 0) - return r; - - o->field.hash = htole64(hash); - memcpy(o->field.payload, field, size); - - r = journal_file_link_field(f, o, p, hash); - if (r < 0) - return r; - - /* The linking might have altered the window, so let's - * refresh our pointer */ - r = journal_file_move_to_object(f, OBJECT_FIELD, p, &o); - if (r < 0) - return r; - -#ifdef HAVE_GCRYPT - r = journal_file_hmac_put_object(f, OBJECT_FIELD, o, p); - if (r < 0) - return r; -#endif - - if (ret) - *ret = o; - - if (offset) - *offset = p; - - return 0; -} - -static int journal_file_append_data( - JournalFile *f, - const void *data, uint64_t size, - Object **ret, uint64_t *offset) { - - uint64_t hash, p; - uint64_t osize; - Object *o; - int r, compression = 0; - const void *eq; - - assert(f); - assert(data || size == 0); - - hash = hash64(data, size); - - r = journal_file_find_data_object_with_hash(f, data, size, hash, &o, &p); - if (r < 0) - return r; - if (r > 0) { - - if (ret) - *ret = o; - - if (offset) - *offset = p; - - return 0; - } - - osize = offsetof(Object, data.payload) + size; - r = journal_file_append_object(f, OBJECT_DATA, osize, &o, &p); - if (r < 0) - return r; - - o->data.hash = htole64(hash); - -#if defined(HAVE_XZ) || defined(HAVE_LZ4) - if (JOURNAL_FILE_COMPRESS(f) && size >= COMPRESSION_SIZE_THRESHOLD) { - size_t rsize = 0; - - compression = compress_blob(data, size, o->data.payload, size - 1, &rsize); - - if (compression >= 0) { - o->object.size = htole64(offsetof(Object, data.payload) + rsize); - o->object.flags |= compression; - - log_debug("Compressed data object %"PRIu64" -> %zu using %s", - size, rsize, object_compressed_to_string(compression)); - } else - /* Compression didn't work, we don't really care why, let's continue without compression */ - compression = 0; - } -#endif - - if (compression == 0) - memcpy_safe(o->data.payload, data, size); - - r = journal_file_link_data(f, o, p, hash); - if (r < 0) - return r; - - /* The linking might have altered the window, so let's - * refresh our pointer */ - r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); - if (r < 0) - return r; - - if (!data) - eq = NULL; - else - eq = memchr(data, '=', size); - if (eq && eq > data) { - Object *fo = NULL; - uint64_t fp; - - /* Create field object ... */ - r = journal_file_append_field(f, data, (uint8_t*) eq - (uint8_t*) data, &fo, &fp); - if (r < 0) - return r; - - /* ... and link it in. */ - o->data.next_field_offset = fo->field.head_data_offset; - fo->field.head_data_offset = le64toh(p); - } - -#ifdef HAVE_GCRYPT - r = journal_file_hmac_put_object(f, OBJECT_DATA, o, p); - if (r < 0) - return r; -#endif - - if (ret) - *ret = o; - - if (offset) - *offset = p; - - return 0; -} - -uint64_t journal_file_entry_n_items(Object *o) { - assert(o); - - if (o->object.type != OBJECT_ENTRY) - return 0; - - return (le64toh(o->object.size) - offsetof(Object, entry.items)) / sizeof(EntryItem); -} - -uint64_t journal_file_entry_array_n_items(Object *o) { - assert(o); - - if (o->object.type != OBJECT_ENTRY_ARRAY) - return 0; - - return (le64toh(o->object.size) - offsetof(Object, entry_array.items)) / sizeof(uint64_t); -} - -uint64_t journal_file_hash_table_n_items(Object *o) { - assert(o); - - if (o->object.type != OBJECT_DATA_HASH_TABLE && - o->object.type != OBJECT_FIELD_HASH_TABLE) - return 0; - - return (le64toh(o->object.size) - offsetof(Object, hash_table.items)) / sizeof(HashItem); -} - -static int link_entry_into_array(JournalFile *f, - le64_t *first, - le64_t *idx, - uint64_t p) { - int r; - uint64_t n = 0, ap = 0, q, i, a, hidx; - Object *o; - - assert(f); - assert(f->header); - assert(first); - assert(idx); - assert(p > 0); - - a = le64toh(*first); - i = hidx = le64toh(*idx); - while (a > 0) { - - r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); - if (r < 0) - return r; - - n = journal_file_entry_array_n_items(o); - if (i < n) { - o->entry_array.items[i] = htole64(p); - *idx = htole64(hidx + 1); - return 0; - } - - i -= n; - ap = a; - a = le64toh(o->entry_array.next_entry_array_offset); - } - - if (hidx > n) - n = (hidx+1) * 2; - else - n = n * 2; - - if (n < 4) - n = 4; - - r = journal_file_append_object(f, OBJECT_ENTRY_ARRAY, - offsetof(Object, entry_array.items) + n * sizeof(uint64_t), - &o, &q); - if (r < 0) - return r; - -#ifdef HAVE_GCRYPT - r = journal_file_hmac_put_object(f, OBJECT_ENTRY_ARRAY, o, q); - if (r < 0) - return r; -#endif - - o->entry_array.items[i] = htole64(p); - - if (ap == 0) - *first = htole64(q); - else { - r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, ap, &o); - if (r < 0) - return r; - - o->entry_array.next_entry_array_offset = htole64(q); - } - - if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays)) - f->header->n_entry_arrays = htole64(le64toh(f->header->n_entry_arrays) + 1); - - *idx = htole64(hidx + 1); - - return 0; -} - -static int link_entry_into_array_plus_one(JournalFile *f, - le64_t *extra, - le64_t *first, - le64_t *idx, - uint64_t p) { - - int r; - - assert(f); - assert(extra); - assert(first); - assert(idx); - assert(p > 0); - - if (*idx == 0) - *extra = htole64(p); - else { - le64_t i; - - i = htole64(le64toh(*idx) - 1); - r = link_entry_into_array(f, first, &i, p); - if (r < 0) - return r; - } - - *idx = htole64(le64toh(*idx) + 1); - return 0; -} - -static int journal_file_link_entry_item(JournalFile *f, Object *o, uint64_t offset, uint64_t i) { - uint64_t p; - int r; - assert(f); - assert(o); - assert(offset > 0); - - p = le64toh(o->entry.items[i].object_offset); - if (p == 0) - return -EINVAL; - - r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); - if (r < 0) - return r; - - return link_entry_into_array_plus_one(f, - &o->data.entry_offset, - &o->data.entry_array_offset, - &o->data.n_entries, - offset); -} - -static int journal_file_link_entry(JournalFile *f, Object *o, uint64_t offset) { - uint64_t n, i; - int r; - - assert(f); - assert(f->header); - assert(o); - assert(offset > 0); - - if (o->object.type != OBJECT_ENTRY) - return -EINVAL; - - __sync_synchronize(); - - /* Link up the entry itself */ - r = link_entry_into_array(f, - &f->header->entry_array_offset, - &f->header->n_entries, - offset); - if (r < 0) - return r; - - /* log_debug("=> %s seqnr=%"PRIu64" n_entries=%"PRIu64, f->path, o->entry.seqnum, f->header->n_entries); */ - - if (f->header->head_entry_realtime == 0) - f->header->head_entry_realtime = o->entry.realtime; - - f->header->tail_entry_realtime = o->entry.realtime; - f->header->tail_entry_monotonic = o->entry.monotonic; - - f->tail_entry_monotonic_valid = true; - - /* Link up the items */ - n = journal_file_entry_n_items(o); - for (i = 0; i < n; i++) { - r = journal_file_link_entry_item(f, o, offset, i); - if (r < 0) - return r; - } - - return 0; -} - -static int journal_file_append_entry_internal( - JournalFile *f, - const dual_timestamp *ts, - uint64_t xor_hash, - const EntryItem items[], unsigned n_items, - uint64_t *seqnum, - Object **ret, uint64_t *offset) { - uint64_t np; - uint64_t osize; - Object *o; - int r; - - assert(f); - assert(f->header); - assert(items || n_items == 0); - assert(ts); - - osize = offsetof(Object, entry.items) + (n_items * sizeof(EntryItem)); - - r = journal_file_append_object(f, OBJECT_ENTRY, osize, &o, &np); - if (r < 0) - return r; - - o->entry.seqnum = htole64(journal_file_entry_seqnum(f, seqnum)); - memcpy_safe(o->entry.items, items, n_items * sizeof(EntryItem)); - o->entry.realtime = htole64(ts->realtime); - o->entry.monotonic = htole64(ts->monotonic); - o->entry.xor_hash = htole64(xor_hash); - o->entry.boot_id = f->header->boot_id; - -#ifdef HAVE_GCRYPT - r = journal_file_hmac_put_object(f, OBJECT_ENTRY, o, np); - if (r < 0) - return r; -#endif - - r = journal_file_link_entry(f, o, np); - if (r < 0) - return r; - - if (ret) - *ret = o; - - if (offset) - *offset = np; - - return 0; -} - -void journal_file_post_change(JournalFile *f) { - assert(f); - - /* inotify() does not receive IN_MODIFY events from file - * accesses done via mmap(). After each access we hence - * trigger IN_MODIFY by truncating the journal file to its - * current size which triggers IN_MODIFY. */ - - __sync_synchronize(); - - if (ftruncate(f->fd, f->last_stat.st_size) < 0) - log_debug_errno(errno, "Failed to truncate file to its own size: %m"); -} - -static int post_change_thunk(sd_event_source *timer, uint64_t usec, void *userdata) { - assert(userdata); - - journal_file_post_change(userdata); - - return 1; -} - -static void schedule_post_change(JournalFile *f) { - sd_event_source *timer; - int enabled, r; - uint64_t now; - - assert(f); - assert(f->post_change_timer); - - timer = f->post_change_timer; - - r = sd_event_source_get_enabled(timer, &enabled); - if (r < 0) { - log_debug_errno(r, "Failed to get ftruncate timer state: %m"); - goto fail; - } - - if (enabled == SD_EVENT_ONESHOT) - return; - - r = sd_event_now(sd_event_source_get_event(timer), CLOCK_MONOTONIC, &now); - if (r < 0) { - log_debug_errno(r, "Failed to get clock's now for scheduling ftruncate: %m"); - goto fail; - } - - r = sd_event_source_set_time(timer, now+f->post_change_timer_period); - if (r < 0) { - log_debug_errno(r, "Failed to set time for scheduling ftruncate: %m"); - goto fail; - } - - r = sd_event_source_set_enabled(timer, SD_EVENT_ONESHOT); - if (r < 0) { - log_debug_errno(r, "Failed to enable scheduled ftruncate: %m"); - goto fail; - } - - return; - -fail: - /* On failure, let's simply post the change immediately. */ - journal_file_post_change(f); -} - -/* Enable coalesced change posting in a timer on the provided sd_event instance */ -int journal_file_enable_post_change_timer(JournalFile *f, sd_event *e, usec_t t) { - _cleanup_(sd_event_source_unrefp) sd_event_source *timer = NULL; - int r; - - assert(f); - assert_return(!f->post_change_timer, -EINVAL); - assert(e); - assert(t); - - r = sd_event_add_time(e, &timer, CLOCK_MONOTONIC, 0, 0, post_change_thunk, f); - if (r < 0) - return r; - - r = sd_event_source_set_enabled(timer, SD_EVENT_OFF); - if (r < 0) - return r; - - f->post_change_timer = timer; - timer = NULL; - f->post_change_timer_period = t; - - return r; -} - -static int entry_item_cmp(const void *_a, const void *_b) { - const EntryItem *a = _a, *b = _b; - - if (le64toh(a->object_offset) < le64toh(b->object_offset)) - return -1; - if (le64toh(a->object_offset) > le64toh(b->object_offset)) - return 1; - return 0; -} - -int journal_file_append_entry(JournalFile *f, const dual_timestamp *ts, const struct iovec iovec[], unsigned n_iovec, uint64_t *seqnum, Object **ret, uint64_t *offset) { - unsigned i; - EntryItem *items; - int r; - uint64_t xor_hash = 0; - struct dual_timestamp _ts; - - assert(f); - assert(f->header); - assert(iovec || n_iovec == 0); - - if (!ts) { - dual_timestamp_get(&_ts); - ts = &_ts; - } - -#ifdef HAVE_GCRYPT - r = journal_file_maybe_append_tag(f, ts->realtime); - if (r < 0) - return r; -#endif - - /* alloca() can't take 0, hence let's allocate at least one */ - items = alloca(sizeof(EntryItem) * MAX(1u, n_iovec)); - - for (i = 0; i < n_iovec; i++) { - uint64_t p; - Object *o; - - r = journal_file_append_data(f, iovec[i].iov_base, iovec[i].iov_len, &o, &p); - if (r < 0) - return r; - - xor_hash ^= le64toh(o->data.hash); - items[i].object_offset = htole64(p); - items[i].hash = o->data.hash; - } - - /* Order by the position on disk, in order to improve seek - * times for rotating media. */ - qsort_safe(items, n_iovec, sizeof(EntryItem), entry_item_cmp); - - r = journal_file_append_entry_internal(f, ts, xor_hash, items, n_iovec, seqnum, ret, offset); - - /* If the memory mapping triggered a SIGBUS then we return an - * IO error and ignore the error code passed down to us, since - * it is very likely just an effect of a nullified replacement - * mapping page */ - - if (mmap_cache_got_sigbus(f->mmap, f->fd)) - r = -EIO; - - if (f->post_change_timer) - schedule_post_change(f); - else - journal_file_post_change(f); - - return r; -} - -typedef struct ChainCacheItem { - uint64_t first; /* the array at the beginning of the chain */ - uint64_t array; /* the cached array */ - uint64_t begin; /* the first item in the cached array */ - uint64_t total; /* the total number of items in all arrays before this one in the chain */ - uint64_t last_index; /* the last index we looked at, to optimize locality when bisecting */ -} ChainCacheItem; - -static void chain_cache_put( - OrderedHashmap *h, - ChainCacheItem *ci, - uint64_t first, - uint64_t array, - uint64_t begin, - uint64_t total, - uint64_t last_index) { - - if (!ci) { - /* If the chain item to cache for this chain is the - * first one it's not worth caching anything */ - if (array == first) - return; - - if (ordered_hashmap_size(h) >= CHAIN_CACHE_MAX) { - ci = ordered_hashmap_steal_first(h); - assert(ci); - } else { - ci = new(ChainCacheItem, 1); - if (!ci) - return; - } - - ci->first = first; - - if (ordered_hashmap_put(h, &ci->first, ci) < 0) { - free(ci); - return; - } - } else - assert(ci->first == first); - - ci->array = array; - ci->begin = begin; - ci->total = total; - ci->last_index = last_index; -} - -static int generic_array_get( - JournalFile *f, - uint64_t first, - uint64_t i, - Object **ret, uint64_t *offset) { - - Object *o; - uint64_t p = 0, a, t = 0; - int r; - ChainCacheItem *ci; - - assert(f); - - a = first; - - /* Try the chain cache first */ - ci = ordered_hashmap_get(f->chain_cache, &first); - if (ci && i > ci->total) { - a = ci->array; - i -= ci->total; - t = ci->total; - } - - while (a > 0) { - uint64_t k; - - r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); - if (r < 0) - return r; - - k = journal_file_entry_array_n_items(o); - if (i < k) { - p = le64toh(o->entry_array.items[i]); - goto found; - } - - i -= k; - t += k; - a = le64toh(o->entry_array.next_entry_array_offset); - } - - return 0; - -found: - /* Let's cache this item for the next invocation */ - chain_cache_put(f->chain_cache, ci, first, a, le64toh(o->entry_array.items[0]), t, i); - - r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o); - if (r < 0) - return r; - - if (ret) - *ret = o; - - if (offset) - *offset = p; - - return 1; -} - -static int generic_array_get_plus_one( - JournalFile *f, - uint64_t extra, - uint64_t first, - uint64_t i, - Object **ret, uint64_t *offset) { - - Object *o; - - assert(f); - - if (i == 0) { - int r; - - r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o); - if (r < 0) - return r; - - if (ret) - *ret = o; - - if (offset) - *offset = extra; - - return 1; - } - - return generic_array_get(f, first, i-1, ret, offset); -} - -enum { - TEST_FOUND, - TEST_LEFT, - TEST_RIGHT -}; - -static int generic_array_bisect( - JournalFile *f, - uint64_t first, - uint64_t n, - uint64_t needle, - int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle), - direction_t direction, - Object **ret, - uint64_t *offset, - uint64_t *idx) { - - uint64_t a, p, t = 0, i = 0, last_p = 0, last_index = (uint64_t) -1; - bool subtract_one = false; - Object *o, *array = NULL; - int r; - ChainCacheItem *ci; - - assert(f); - assert(test_object); - - /* Start with the first array in the chain */ - a = first; - - ci = ordered_hashmap_get(f->chain_cache, &first); - if (ci && n > ci->total) { - /* Ah, we have iterated this bisection array chain - * previously! Let's see if we can skip ahead in the - * chain, as far as the last time. But we can't jump - * backwards in the chain, so let's check that - * first. */ - - r = test_object(f, ci->begin, needle); - if (r < 0) - return r; - - if (r == TEST_LEFT) { - /* OK, what we are looking for is right of the - * begin of this EntryArray, so let's jump - * straight to previously cached array in the - * chain */ - - a = ci->array; - n -= ci->total; - t = ci->total; - last_index = ci->last_index; - } - } - - while (a > 0) { - uint64_t left, right, k, lp; - - r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &array); - if (r < 0) - return r; - - k = journal_file_entry_array_n_items(array); - right = MIN(k, n); - if (right <= 0) - return 0; - - i = right - 1; - lp = p = le64toh(array->entry_array.items[i]); - if (p <= 0) - r = -EBADMSG; - else - r = test_object(f, p, needle); - if (r == -EBADMSG) { - log_debug_errno(r, "Encountered invalid entry while bisecting, cutting algorithm short. (1)"); - n = i; - continue; - } - if (r < 0) - return r; - - if (r == TEST_FOUND) - r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT; - - if (r == TEST_RIGHT) { - left = 0; - right -= 1; - - if (last_index != (uint64_t) -1) { - assert(last_index <= right); - - /* If we cached the last index we - * looked at, let's try to not to jump - * too wildly around and see if we can - * limit the range to look at early to - * the immediate neighbors of the last - * index we looked at. */ - - if (last_index > 0) { - uint64_t x = last_index - 1; - - p = le64toh(array->entry_array.items[x]); - if (p <= 0) - return -EBADMSG; - - r = test_object(f, p, needle); - if (r < 0) - return r; - - if (r == TEST_FOUND) - r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT; - - if (r == TEST_RIGHT) - right = x; - else - left = x + 1; - } - - if (last_index < right) { - uint64_t y = last_index + 1; - - p = le64toh(array->entry_array.items[y]); - if (p <= 0) - return -EBADMSG; - - r = test_object(f, p, needle); - if (r < 0) - return r; - - if (r == TEST_FOUND) - r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT; - - if (r == TEST_RIGHT) - right = y; - else - left = y + 1; - } - } - - for (;;) { - if (left == right) { - if (direction == DIRECTION_UP) - subtract_one = true; - - i = left; - goto found; - } - - assert(left < right); - i = (left + right) / 2; - - p = le64toh(array->entry_array.items[i]); - if (p <= 0) - r = -EBADMSG; - else - r = test_object(f, p, needle); - if (r == -EBADMSG) { - log_debug_errno(r, "Encountered invalid entry while bisecting, cutting algorithm short. (2)"); - right = n = i; - continue; - } - if (r < 0) - return r; - - if (r == TEST_FOUND) - r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT; - - if (r == TEST_RIGHT) - right = i; - else - left = i + 1; - } - } - - if (k >= n) { - if (direction == DIRECTION_UP) { - i = n; - subtract_one = true; - goto found; - } - - return 0; - } - - last_p = lp; - - n -= k; - t += k; - last_index = (uint64_t) -1; - a = le64toh(array->entry_array.next_entry_array_offset); - } - - return 0; - -found: - if (subtract_one && t == 0 && i == 0) - return 0; - - /* Let's cache this item for the next invocation */ - chain_cache_put(f->chain_cache, ci, first, a, le64toh(array->entry_array.items[0]), t, subtract_one ? (i > 0 ? i-1 : (uint64_t) -1) : i); - - if (subtract_one && i == 0) - p = last_p; - else if (subtract_one) - p = le64toh(array->entry_array.items[i-1]); - else - p = le64toh(array->entry_array.items[i]); - - r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o); - if (r < 0) - return r; - - if (ret) - *ret = o; - - if (offset) - *offset = p; - - if (idx) - *idx = t + i + (subtract_one ? -1 : 0); - - return 1; -} - -static int generic_array_bisect_plus_one( - JournalFile *f, - uint64_t extra, - uint64_t first, - uint64_t n, - uint64_t needle, - int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle), - direction_t direction, - Object **ret, - uint64_t *offset, - uint64_t *idx) { - - int r; - bool step_back = false; - Object *o; - - assert(f); - assert(test_object); - - if (n <= 0) - return 0; - - /* This bisects the array in object 'first', but first checks - * an extra */ - r = test_object(f, extra, needle); - if (r < 0) - return r; - - if (r == TEST_FOUND) - r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT; - - /* if we are looking with DIRECTION_UP then we need to first - see if in the actual array there is a matching entry, and - return the last one of that. But if there isn't any we need - to return this one. Hence remember this, and return it - below. */ - if (r == TEST_LEFT) - step_back = direction == DIRECTION_UP; - - if (r == TEST_RIGHT) { - if (direction == DIRECTION_DOWN) - goto found; - else - return 0; - } - - r = generic_array_bisect(f, first, n-1, needle, test_object, direction, ret, offset, idx); - - if (r == 0 && step_back) - goto found; - - if (r > 0 && idx) - (*idx)++; - - return r; - -found: - r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o); - if (r < 0) - return r; - - if (ret) - *ret = o; - - if (offset) - *offset = extra; - - if (idx) - *idx = 0; - - return 1; -} - -_pure_ static int test_object_offset(JournalFile *f, uint64_t p, uint64_t needle) { - assert(f); - assert(p > 0); - - if (p == needle) - return TEST_FOUND; - else if (p < needle) - return TEST_LEFT; - else - return TEST_RIGHT; -} - -static int test_object_seqnum(JournalFile *f, uint64_t p, uint64_t needle) { - Object *o; - int r; - - assert(f); - assert(p > 0); - - r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o); - if (r < 0) - return r; - - if (le64toh(o->entry.seqnum) == needle) - return TEST_FOUND; - else if (le64toh(o->entry.seqnum) < needle) - return TEST_LEFT; - else - return TEST_RIGHT; -} - -int journal_file_move_to_entry_by_seqnum( - JournalFile *f, - uint64_t seqnum, - direction_t direction, - Object **ret, - uint64_t *offset) { - assert(f); - assert(f->header); - - return generic_array_bisect(f, - le64toh(f->header->entry_array_offset), - le64toh(f->header->n_entries), - seqnum, - test_object_seqnum, - direction, - ret, offset, NULL); -} - -static int test_object_realtime(JournalFile *f, uint64_t p, uint64_t needle) { - Object *o; - int r; - - assert(f); - assert(p > 0); - - r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o); - if (r < 0) - return r; - - if (le64toh(o->entry.realtime) == needle) - return TEST_FOUND; - else if (le64toh(o->entry.realtime) < needle) - return TEST_LEFT; - else - return TEST_RIGHT; -} - -int journal_file_move_to_entry_by_realtime( - JournalFile *f, - uint64_t realtime, - direction_t direction, - Object **ret, - uint64_t *offset) { - assert(f); - assert(f->header); - - return generic_array_bisect(f, - le64toh(f->header->entry_array_offset), - le64toh(f->header->n_entries), - realtime, - test_object_realtime, - direction, - ret, offset, NULL); -} - -static int test_object_monotonic(JournalFile *f, uint64_t p, uint64_t needle) { - Object *o; - int r; - - assert(f); - assert(p > 0); - - r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o); - if (r < 0) - return r; - - if (le64toh(o->entry.monotonic) == needle) - return TEST_FOUND; - else if (le64toh(o->entry.monotonic) < needle) - return TEST_LEFT; - else - return TEST_RIGHT; -} - -static int find_data_object_by_boot_id( - JournalFile *f, - sd_id128_t boot_id, - Object **o, - uint64_t *b) { - - char t[sizeof("_BOOT_ID=")-1 + 32 + 1] = "_BOOT_ID="; - - sd_id128_to_string(boot_id, t + 9); - return journal_file_find_data_object(f, t, sizeof(t) - 1, o, b); -} - -int journal_file_move_to_entry_by_monotonic( - JournalFile *f, - sd_id128_t boot_id, - uint64_t monotonic, - direction_t direction, - Object **ret, - uint64_t *offset) { - - Object *o; - int r; - - assert(f); - - r = find_data_object_by_boot_id(f, boot_id, &o, NULL); - if (r < 0) - return r; - if (r == 0) - return -ENOENT; - - return generic_array_bisect_plus_one(f, - le64toh(o->data.entry_offset), - le64toh(o->data.entry_array_offset), - le64toh(o->data.n_entries), - monotonic, - test_object_monotonic, - direction, - ret, offset, NULL); -} - -void journal_file_reset_location(JournalFile *f) { - f->location_type = LOCATION_HEAD; - f->current_offset = 0; - f->current_seqnum = 0; - f->current_realtime = 0; - f->current_monotonic = 0; - zero(f->current_boot_id); - f->current_xor_hash = 0; -} - -void journal_file_save_location(JournalFile *f, Object *o, uint64_t offset) { - f->location_type = LOCATION_SEEK; - f->current_offset = offset; - f->current_seqnum = le64toh(o->entry.seqnum); - f->current_realtime = le64toh(o->entry.realtime); - f->current_monotonic = le64toh(o->entry.monotonic); - f->current_boot_id = o->entry.boot_id; - f->current_xor_hash = le64toh(o->entry.xor_hash); -} - -int journal_file_compare_locations(JournalFile *af, JournalFile *bf) { - assert(af); - assert(af->header); - assert(bf); - assert(bf->header); - assert(af->location_type == LOCATION_SEEK); - assert(bf->location_type == LOCATION_SEEK); - - /* If contents and timestamps match, these entries are - * identical, even if the seqnum does not match */ - if (sd_id128_equal(af->current_boot_id, bf->current_boot_id) && - af->current_monotonic == bf->current_monotonic && - af->current_realtime == bf->current_realtime && - af->current_xor_hash == bf->current_xor_hash) - return 0; - - if (sd_id128_equal(af->header->seqnum_id, bf->header->seqnum_id)) { - - /* If this is from the same seqnum source, compare - * seqnums */ - if (af->current_seqnum < bf->current_seqnum) - return -1; - if (af->current_seqnum > bf->current_seqnum) - return 1; - - /* Wow! This is weird, different data but the same - * seqnums? Something is borked, but let's make the - * best of it and compare by time. */ - } - - if (sd_id128_equal(af->current_boot_id, bf->current_boot_id)) { - - /* If the boot id matches, compare monotonic time */ - if (af->current_monotonic < bf->current_monotonic) - return -1; - if (af->current_monotonic > bf->current_monotonic) - return 1; - } - - /* Otherwise, compare UTC time */ - if (af->current_realtime < bf->current_realtime) - return -1; - if (af->current_realtime > bf->current_realtime) - return 1; - - /* Finally, compare by contents */ - if (af->current_xor_hash < bf->current_xor_hash) - return -1; - if (af->current_xor_hash > bf->current_xor_hash) - return 1; - - return 0; -} - -int journal_file_next_entry( - JournalFile *f, - uint64_t p, - direction_t direction, - Object **ret, uint64_t *offset) { - - uint64_t i, n, ofs; - int r; - - assert(f); - assert(f->header); - - n = le64toh(f->header->n_entries); - if (n <= 0) - return 0; - - if (p == 0) - i = direction == DIRECTION_DOWN ? 0 : n - 1; - else { - r = generic_array_bisect(f, - le64toh(f->header->entry_array_offset), - le64toh(f->header->n_entries), - p, - test_object_offset, - DIRECTION_DOWN, - NULL, NULL, - &i); - if (r <= 0) - return r; - - if (direction == DIRECTION_DOWN) { - if (i >= n - 1) - return 0; - - i++; - } else { - if (i <= 0) - return 0; - - i--; - } - } - - /* And jump to it */ - r = generic_array_get(f, - le64toh(f->header->entry_array_offset), - i, - ret, &ofs); - if (r == -EBADMSG && direction == DIRECTION_DOWN) { - /* Special case: when we iterate throught the journal file linearly, and hit an entry we can't read, - * consider this the end of the journal file. */ - log_debug_errno(r, "Encountered entry we can't read while iterating through journal file. Considering this the end of the file."); - return 0; - } - if (r <= 0) - return r; - - if (p > 0 && - (direction == DIRECTION_DOWN ? ofs <= p : ofs >= p)) { - log_debug("%s: entry array corrupted at entry %" PRIu64, f->path, i); - return -EBADMSG; - } - - if (offset) - *offset = ofs; - - return 1; -} - -int journal_file_next_entry_for_data( - JournalFile *f, - Object *o, uint64_t p, - uint64_t data_offset, - direction_t direction, - Object **ret, uint64_t *offset) { - - uint64_t n, i; - int r; - Object *d; - - assert(f); - assert(p > 0 || !o); - - r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d); - if (r < 0) - return r; - - n = le64toh(d->data.n_entries); - if (n <= 0) - return n; - - if (!o) - i = direction == DIRECTION_DOWN ? 0 : n - 1; - else { - if (o->object.type != OBJECT_ENTRY) - return -EINVAL; - - r = generic_array_bisect_plus_one(f, - le64toh(d->data.entry_offset), - le64toh(d->data.entry_array_offset), - le64toh(d->data.n_entries), - p, - test_object_offset, - DIRECTION_DOWN, - NULL, NULL, - &i); - - if (r <= 0) - return r; - - if (direction == DIRECTION_DOWN) { - if (i >= n - 1) - return 0; - - i++; - } else { - if (i <= 0) - return 0; - - i--; - } - - } - - return generic_array_get_plus_one(f, - le64toh(d->data.entry_offset), - le64toh(d->data.entry_array_offset), - i, - ret, offset); -} - -int journal_file_move_to_entry_by_offset_for_data( - JournalFile *f, - uint64_t data_offset, - uint64_t p, - direction_t direction, - Object **ret, uint64_t *offset) { - - int r; - Object *d; - - assert(f); - - r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d); - if (r < 0) - return r; - - return generic_array_bisect_plus_one(f, - le64toh(d->data.entry_offset), - le64toh(d->data.entry_array_offset), - le64toh(d->data.n_entries), - p, - test_object_offset, - direction, - ret, offset, NULL); -} - -int journal_file_move_to_entry_by_monotonic_for_data( - JournalFile *f, - uint64_t data_offset, - sd_id128_t boot_id, - uint64_t monotonic, - direction_t direction, - Object **ret, uint64_t *offset) { - - Object *o, *d; - int r; - uint64_t b, z; - - assert(f); - - /* First, seek by time */ - r = find_data_object_by_boot_id(f, boot_id, &o, &b); - if (r < 0) - return r; - if (r == 0) - return -ENOENT; - - r = generic_array_bisect_plus_one(f, - le64toh(o->data.entry_offset), - le64toh(o->data.entry_array_offset), - le64toh(o->data.n_entries), - monotonic, - test_object_monotonic, - direction, - NULL, &z, NULL); - if (r <= 0) - return r; - - /* And now, continue seeking until we find an entry that - * exists in both bisection arrays */ - - for (;;) { - Object *qo; - uint64_t p, q; - - r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d); - if (r < 0) - return r; - - r = generic_array_bisect_plus_one(f, - le64toh(d->data.entry_offset), - le64toh(d->data.entry_array_offset), - le64toh(d->data.n_entries), - z, - test_object_offset, - direction, - NULL, &p, NULL); - if (r <= 0) - return r; - - r = journal_file_move_to_object(f, OBJECT_DATA, b, &o); - if (r < 0) - return r; - - r = generic_array_bisect_plus_one(f, - le64toh(o->data.entry_offset), - le64toh(o->data.entry_array_offset), - le64toh(o->data.n_entries), - p, - test_object_offset, - direction, - &qo, &q, NULL); - - if (r <= 0) - return r; - - if (p == q) { - if (ret) - *ret = qo; - if (offset) - *offset = q; - - return 1; - } - - z = q; - } -} - -int journal_file_move_to_entry_by_seqnum_for_data( - JournalFile *f, - uint64_t data_offset, - uint64_t seqnum, - direction_t direction, - Object **ret, uint64_t *offset) { - - Object *d; - int r; - - assert(f); - - r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d); - if (r < 0) - return r; - - return generic_array_bisect_plus_one(f, - le64toh(d->data.entry_offset), - le64toh(d->data.entry_array_offset), - le64toh(d->data.n_entries), - seqnum, - test_object_seqnum, - direction, - ret, offset, NULL); -} - -int journal_file_move_to_entry_by_realtime_for_data( - JournalFile *f, - uint64_t data_offset, - uint64_t realtime, - direction_t direction, - Object **ret, uint64_t *offset) { - - Object *d; - int r; - - assert(f); - - r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d); - if (r < 0) - return r; - - return generic_array_bisect_plus_one(f, - le64toh(d->data.entry_offset), - le64toh(d->data.entry_array_offset), - le64toh(d->data.n_entries), - realtime, - test_object_realtime, - direction, - ret, offset, NULL); -} - -void journal_file_dump(JournalFile *f) { - Object *o; - int r; - uint64_t p; - - assert(f); - assert(f->header); - - journal_file_print_header(f); - - p = le64toh(f->header->header_size); - while (p != 0) { - r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o); - if (r < 0) - goto fail; - - switch (o->object.type) { - - case OBJECT_UNUSED: - printf("Type: OBJECT_UNUSED\n"); - break; - - case OBJECT_DATA: - printf("Type: OBJECT_DATA\n"); - break; - - case OBJECT_FIELD: - printf("Type: OBJECT_FIELD\n"); - break; - - case OBJECT_ENTRY: - printf("Type: OBJECT_ENTRY seqnum=%"PRIu64" monotonic=%"PRIu64" realtime=%"PRIu64"\n", - le64toh(o->entry.seqnum), - le64toh(o->entry.monotonic), - le64toh(o->entry.realtime)); - break; - - case OBJECT_FIELD_HASH_TABLE: - printf("Type: OBJECT_FIELD_HASH_TABLE\n"); - break; - - case OBJECT_DATA_HASH_TABLE: - printf("Type: OBJECT_DATA_HASH_TABLE\n"); - break; - - case OBJECT_ENTRY_ARRAY: - printf("Type: OBJECT_ENTRY_ARRAY\n"); - break; - - case OBJECT_TAG: - printf("Type: OBJECT_TAG seqnum=%"PRIu64" epoch=%"PRIu64"\n", - le64toh(o->tag.seqnum), - le64toh(o->tag.epoch)); - break; - - default: - printf("Type: unknown (%i)\n", o->object.type); - break; - } - - if (o->object.flags & OBJECT_COMPRESSION_MASK) - printf("Flags: %s\n", - object_compressed_to_string(o->object.flags & OBJECT_COMPRESSION_MASK)); - - if (p == le64toh(f->header->tail_object_offset)) - p = 0; - else - p = p + ALIGN64(le64toh(o->object.size)); - } - - return; -fail: - log_error("File corrupt"); -} - -static const char* format_timestamp_safe(char *buf, size_t l, usec_t t) { - const char *x; - - x = format_timestamp(buf, l, t); - if (x) - return x; - return " --- "; -} - -void journal_file_print_header(JournalFile *f) { - char a[33], b[33], c[33], d[33]; - char x[FORMAT_TIMESTAMP_MAX], y[FORMAT_TIMESTAMP_MAX], z[FORMAT_TIMESTAMP_MAX]; - struct stat st; - char bytes[FORMAT_BYTES_MAX]; - - assert(f); - assert(f->header); - - printf("File Path: %s\n" - "File ID: %s\n" - "Machine ID: %s\n" - "Boot ID: %s\n" - "Sequential Number ID: %s\n" - "State: %s\n" - "Compatible Flags:%s%s\n" - "Incompatible Flags:%s%s%s\n" - "Header size: %"PRIu64"\n" - "Arena size: %"PRIu64"\n" - "Data Hash Table Size: %"PRIu64"\n" - "Field Hash Table Size: %"PRIu64"\n" - "Rotate Suggested: %s\n" - "Head Sequential Number: %"PRIu64" (%"PRIx64")\n" - "Tail Sequential Number: %"PRIu64" (%"PRIx64")\n" - "Head Realtime Timestamp: %s (%"PRIx64")\n" - "Tail Realtime Timestamp: %s (%"PRIx64")\n" - "Tail Monotonic Timestamp: %s (%"PRIx64")\n" - "Objects: %"PRIu64"\n" - "Entry Objects: %"PRIu64"\n", - f->path, - sd_id128_to_string(f->header->file_id, a), - sd_id128_to_string(f->header->machine_id, b), - sd_id128_to_string(f->header->boot_id, c), - sd_id128_to_string(f->header->seqnum_id, d), - f->header->state == STATE_OFFLINE ? "OFFLINE" : - f->header->state == STATE_ONLINE ? "ONLINE" : - f->header->state == STATE_ARCHIVED ? "ARCHIVED" : "UNKNOWN", - JOURNAL_HEADER_SEALED(f->header) ? " SEALED" : "", - (le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_ANY) ? " ???" : "", - JOURNAL_HEADER_COMPRESSED_XZ(f->header) ? " COMPRESSED-XZ" : "", - JOURNAL_HEADER_COMPRESSED_LZ4(f->header) ? " COMPRESSED-LZ4" : "", - (le32toh(f->header->incompatible_flags) & ~HEADER_INCOMPATIBLE_ANY) ? " ???" : "", - le64toh(f->header->header_size), - le64toh(f->header->arena_size), - le64toh(f->header->data_hash_table_size) / sizeof(HashItem), - le64toh(f->header->field_hash_table_size) / sizeof(HashItem), - yes_no(journal_file_rotate_suggested(f, 0)), - le64toh(f->header->head_entry_seqnum), le64toh(f->header->head_entry_seqnum), - le64toh(f->header->tail_entry_seqnum), le64toh(f->header->tail_entry_seqnum), - format_timestamp_safe(x, sizeof(x), le64toh(f->header->head_entry_realtime)), le64toh(f->header->head_entry_realtime), - format_timestamp_safe(y, sizeof(y), le64toh(f->header->tail_entry_realtime)), le64toh(f->header->tail_entry_realtime), - format_timespan(z, sizeof(z), le64toh(f->header->tail_entry_monotonic), USEC_PER_MSEC), le64toh(f->header->tail_entry_monotonic), - le64toh(f->header->n_objects), - le64toh(f->header->n_entries)); - - if (JOURNAL_HEADER_CONTAINS(f->header, n_data)) - printf("Data Objects: %"PRIu64"\n" - "Data Hash Table Fill: %.1f%%\n", - le64toh(f->header->n_data), - 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)))); - - if (JOURNAL_HEADER_CONTAINS(f->header, n_fields)) - printf("Field Objects: %"PRIu64"\n" - "Field Hash Table Fill: %.1f%%\n", - le64toh(f->header->n_fields), - 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)))); - - if (JOURNAL_HEADER_CONTAINS(f->header, n_tags)) - printf("Tag Objects: %"PRIu64"\n", - le64toh(f->header->n_tags)); - if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays)) - printf("Entry Array Objects: %"PRIu64"\n", - le64toh(f->header->n_entry_arrays)); - - if (fstat(f->fd, &st) >= 0) - printf("Disk usage: %s\n", format_bytes(bytes, sizeof(bytes), (uint64_t) st.st_blocks * 512ULL)); -} - -static int journal_file_warn_btrfs(JournalFile *f) { - unsigned attrs; - int r; - - assert(f); - - /* Before we write anything, check if the COW logic is turned - * off on btrfs. Given our write pattern that is quite - * unfriendly to COW file systems this should greatly improve - * performance on COW file systems, such as btrfs, at the - * expense of data integrity features (which shouldn't be too - * bad, given that we do our own checksumming). */ - - r = btrfs_is_filesystem(f->fd); - if (r < 0) - return log_warning_errno(r, "Failed to determine if journal is on btrfs: %m"); - if (!r) - return 0; - - r = read_attr_fd(f->fd, &attrs); - if (r < 0) - return log_warning_errno(r, "Failed to read file attributes: %m"); - - if (attrs & FS_NOCOW_FL) { - log_debug("Detected btrfs file system with copy-on-write disabled, all is good."); - return 0; - } - - log_notice("Creating journal file %s on a btrfs file system, and copy-on-write is enabled. " - "This is likely to slow down journal access substantially, please consider turning " - "off the copy-on-write file attribute on the journal directory, using chattr +C.", f->path); - - return 1; -} - -int journal_file_open( - int fd, - const char *fname, - int flags, - mode_t mode, - bool compress, - bool seal, - JournalMetrics *metrics, - MMapCache *mmap_cache, - Set *deferred_closes, - JournalFile *template, - JournalFile **ret) { - - bool newly_created = false; - JournalFile *f; - void *h; - int r; - - assert(ret); - assert(fd >= 0 || fname); - - if ((flags & O_ACCMODE) != O_RDONLY && - (flags & O_ACCMODE) != O_RDWR) - return -EINVAL; - - if (fname) { - if (!endswith(fname, ".journal") && - !endswith(fname, ".journal~")) - return -EINVAL; - } - - f = new0(JournalFile, 1); - if (!f) - return -ENOMEM; - - f->fd = fd; - f->mode = mode; - - f->flags = flags; - f->prot = prot_from_flags(flags); - f->writable = (flags & O_ACCMODE) != O_RDONLY; -#if defined(HAVE_LZ4) - f->compress_lz4 = compress; -#elif defined(HAVE_XZ) - f->compress_xz = compress; -#endif -#ifdef HAVE_GCRYPT - f->seal = seal; -#endif - - if (mmap_cache) - f->mmap = mmap_cache_ref(mmap_cache); - else { - f->mmap = mmap_cache_new(); - if (!f->mmap) { - r = -ENOMEM; - goto fail; - } - } - - if (fname) - f->path = strdup(fname); - else /* If we don't know the path, fill in something explanatory and vaguely useful */ - asprintf(&f->path, "/proc/self/%i", fd); - if (!f->path) { - r = -ENOMEM; - goto fail; - } - - f->chain_cache = ordered_hashmap_new(&uint64_hash_ops); - if (!f->chain_cache) { - r = -ENOMEM; - goto fail; - } - - if (f->fd < 0) { - f->fd = open(f->path, f->flags|O_CLOEXEC, f->mode); - if (f->fd < 0) { - r = -errno; - goto fail; - } - - /* fds we opened here by us should also be closed by us. */ - f->close_fd = true; - } - - r = journal_file_fstat(f); - if (r < 0) - goto fail; - - if (f->last_stat.st_size == 0 && f->writable) { - - (void) journal_file_warn_btrfs(f); - - /* Let's attach the creation time to the journal file, - * so that the vacuuming code knows the age of this - * file even if the file might end up corrupted one - * day... Ideally we'd just use the creation time many - * file systems maintain for each file, but there is - * currently no usable API to query this, hence let's - * emulate this via extended attributes. If extended - * attributes are not supported we'll just skip this, - * and rely solely on mtime/atime/ctime of the file. */ - - fd_setcrtime(f->fd, 0); - -#ifdef HAVE_GCRYPT - /* Try to load the FSPRG state, and if we can't, then - * just don't do sealing */ - if (f->seal) { - r = journal_file_fss_load(f); - if (r < 0) - f->seal = false; - } -#endif - - r = journal_file_init_header(f, template); - if (r < 0) - goto fail; - - r = journal_file_fstat(f); - if (r < 0) - goto fail; - - newly_created = true; - } - - if (f->last_stat.st_size < (off_t) HEADER_SIZE_MIN) { - r = -ENODATA; - goto fail; - } - - r = mmap_cache_get(f->mmap, f->fd, f->prot, CONTEXT_HEADER, true, 0, PAGE_ALIGN(sizeof(Header)), &f->last_stat, &h); - if (r < 0) - goto fail; - - f->header = h; - - if (!newly_created) { - if (deferred_closes) - journal_file_close_set(deferred_closes); - - r = journal_file_verify_header(f); - if (r < 0) - goto fail; - } - -#ifdef HAVE_GCRYPT - if (!newly_created && f->writable) { - r = journal_file_fss_load(f); - if (r < 0) - goto fail; - } -#endif - - if (f->writable) { - if (metrics) { - journal_default_metrics(metrics, f->fd); - f->metrics = *metrics; - } else if (template) - f->metrics = template->metrics; - - r = journal_file_refresh_header(f); - if (r < 0) - goto fail; - } - -#ifdef HAVE_GCRYPT - r = journal_file_hmac_setup(f); - if (r < 0) - goto fail; -#endif - - if (newly_created) { - r = journal_file_setup_field_hash_table(f); - if (r < 0) - goto fail; - - r = journal_file_setup_data_hash_table(f); - if (r < 0) - goto fail; - -#ifdef HAVE_GCRYPT - r = journal_file_append_first_tag(f); - if (r < 0) - goto fail; -#endif - } - - if (mmap_cache_got_sigbus(f->mmap, f->fd)) { - r = -EIO; - goto fail; - } - - if (template && template->post_change_timer) { - r = journal_file_enable_post_change_timer( - f, - sd_event_source_get_event(template->post_change_timer), - template->post_change_timer_period); - - if (r < 0) - goto fail; - } - - /* The file is opened now successfully, thus we take possession of any passed in fd. */ - f->close_fd = true; - - *ret = f; - return 0; - -fail: - if (f->fd >= 0 && mmap_cache_got_sigbus(f->mmap, f->fd)) - r = -EIO; - - (void) journal_file_close(f); - - return r; -} - -int journal_file_rotate(JournalFile **f, bool compress, bool seal, Set *deferred_closes) { - _cleanup_free_ char *p = NULL; - size_t l; - JournalFile *old_file, *new_file = NULL; - int r; - - assert(f); - assert(*f); - - old_file = *f; - - if (!old_file->writable) - return -EINVAL; - - /* Is this a journal file that was passed to us as fd? If so, we synthesized a path name for it, and we refuse - * rotation, since we don't know the actual path, and couldn't rename the file hence.*/ - if (path_startswith(old_file->path, "/proc/self/fd")) - return -EINVAL; - - if (!endswith(old_file->path, ".journal")) - return -EINVAL; - - l = strlen(old_file->path); - r = asprintf(&p, "%.*s@" SD_ID128_FORMAT_STR "-%016"PRIx64"-%016"PRIx64".journal", - (int) l - 8, old_file->path, - SD_ID128_FORMAT_VAL(old_file->header->seqnum_id), - le64toh((*f)->header->head_entry_seqnum), - le64toh((*f)->header->head_entry_realtime)); - if (r < 0) - return -ENOMEM; - - /* Try to rename the file to the archived version. If the file - * already was deleted, we'll get ENOENT, let's ignore that - * case. */ - r = rename(old_file->path, p); - if (r < 0 && errno != ENOENT) - return -errno; - - /* Sync the rename to disk */ - (void) fsync_directory_of_file(old_file->fd); - - /* Set as archive so offlining commits w/state=STATE_ARCHIVED. - * Previously we would set old_file->header->state to STATE_ARCHIVED directly here, - * but journal_file_set_offline() short-circuits when state != STATE_ONLINE, which - * would result in the rotated journal never getting fsync() called before closing. - * Now we simply queue the archive state by setting an archive bit, leaving the state - * as STATE_ONLINE so proper offlining occurs. */ - old_file->archive = true; - - /* Currently, btrfs is not very good with out write patterns - * and fragments heavily. Let's defrag our journal files when - * we archive them */ - old_file->defrag_on_close = true; - - r = journal_file_open(-1, old_file->path, old_file->flags, old_file->mode, compress, seal, NULL, old_file->mmap, deferred_closes, old_file, &new_file); - - if (deferred_closes && - set_put(deferred_closes, old_file) >= 0) - (void) journal_file_set_offline(old_file, false); - else - (void) journal_file_close(old_file); - - *f = new_file; - return r; -} - -int journal_file_open_reliably( - const char *fname, - int flags, - mode_t mode, - bool compress, - bool seal, - JournalMetrics *metrics, - MMapCache *mmap_cache, - Set *deferred_closes, - JournalFile *template, - JournalFile **ret) { - - int r; - size_t l; - _cleanup_free_ char *p = NULL; - - r = journal_file_open(-1, fname, flags, mode, compress, seal, metrics, mmap_cache, deferred_closes, template, ret); - if (!IN_SET(r, - -EBADMSG, /* corrupted */ - -ENODATA, /* truncated */ - -EHOSTDOWN, /* other machine */ - -EPROTONOSUPPORT, /* incompatible feature */ - -EBUSY, /* unclean shutdown */ - -ESHUTDOWN, /* already archived */ - -EIO, /* IO error, including SIGBUS on mmap */ - -EIDRM /* File has been deleted */)) - return r; - - if ((flags & O_ACCMODE) == O_RDONLY) - return r; - - if (!(flags & O_CREAT)) - return r; - - if (!endswith(fname, ".journal")) - return r; - - /* The file is corrupted. Rotate it away and try it again (but only once) */ - - l = strlen(fname); - if (asprintf(&p, "%.*s@%016"PRIx64 "-%016"PRIx64 ".journal~", - (int) l - 8, fname, - now(CLOCK_REALTIME), - random_u64()) < 0) - return -ENOMEM; - - if (rename(fname, p) < 0) - return -errno; - - /* btrfs doesn't cope well with our write pattern and - * fragments heavily. Let's defrag all files we rotate */ - - (void) chattr_path(p, 0, FS_NOCOW_FL); - (void) btrfs_defrag(p); - - log_warning_errno(r, "File %s corrupted or uncleanly shut down, renaming and replacing.", fname); - - return journal_file_open(-1, fname, flags, mode, compress, seal, metrics, mmap_cache, deferred_closes, template, ret); -} - -int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset) { - uint64_t i, n; - uint64_t q, xor_hash = 0; - int r; - EntryItem *items; - dual_timestamp ts; - - assert(from); - assert(to); - assert(o); - assert(p); - - if (!to->writable) - return -EPERM; - - ts.monotonic = le64toh(o->entry.monotonic); - ts.realtime = le64toh(o->entry.realtime); - - n = journal_file_entry_n_items(o); - /* alloca() can't take 0, hence let's allocate at least one */ - items = alloca(sizeof(EntryItem) * MAX(1u, n)); - - for (i = 0; i < n; i++) { - uint64_t l, h; - le64_t le_hash; - size_t t; - void *data; - Object *u; - - q = le64toh(o->entry.items[i].object_offset); - le_hash = o->entry.items[i].hash; - - r = journal_file_move_to_object(from, OBJECT_DATA, q, &o); - if (r < 0) - return r; - - if (le_hash != o->data.hash) - return -EBADMSG; - - l = le64toh(o->object.size) - offsetof(Object, data.payload); - t = (size_t) l; - - /* We hit the limit on 32bit machines */ - if ((uint64_t) t != l) - return -E2BIG; - - if (o->object.flags & OBJECT_COMPRESSION_MASK) { -#if defined(HAVE_XZ) || defined(HAVE_LZ4) - size_t rsize = 0; - - r = decompress_blob(o->object.flags & OBJECT_COMPRESSION_MASK, - o->data.payload, l, &from->compress_buffer, &from->compress_buffer_size, &rsize, 0); - if (r < 0) - return r; - - data = from->compress_buffer; - l = rsize; -#else - return -EPROTONOSUPPORT; -#endif - } else - data = o->data.payload; - - r = journal_file_append_data(to, data, l, &u, &h); - if (r < 0) - return r; - - xor_hash ^= le64toh(u->data.hash); - items[i].object_offset = htole64(h); - items[i].hash = u->data.hash; - - r = journal_file_move_to_object(from, OBJECT_ENTRY, p, &o); - if (r < 0) - return r; - } - - r = journal_file_append_entry_internal(to, &ts, xor_hash, items, n, seqnum, ret, offset); - - if (mmap_cache_got_sigbus(to->mmap, to->fd)) - return -EIO; - - return r; -} - -void journal_reset_metrics(JournalMetrics *m) { - assert(m); - - /* Set everything to "pick automatic values". */ - - *m = (JournalMetrics) { - .min_use = (uint64_t) -1, - .max_use = (uint64_t) -1, - .min_size = (uint64_t) -1, - .max_size = (uint64_t) -1, - .keep_free = (uint64_t) -1, - .n_max_files = (uint64_t) -1, - }; -} - -void journal_default_metrics(JournalMetrics *m, int fd) { - char a[FORMAT_BYTES_MAX], b[FORMAT_BYTES_MAX], c[FORMAT_BYTES_MAX], d[FORMAT_BYTES_MAX], e[FORMAT_BYTES_MAX]; - struct statvfs ss; - uint64_t fs_size; - - assert(m); - assert(fd >= 0); - - if (fstatvfs(fd, &ss) >= 0) - fs_size = ss.f_frsize * ss.f_blocks; - else { - log_debug_errno(errno, "Failed to detremine disk size: %m"); - fs_size = 0; - } - - if (m->max_use == (uint64_t) -1) { - - if (fs_size > 0) { - m->max_use = PAGE_ALIGN(fs_size / 10); /* 10% of file system size */ - - if (m->max_use > DEFAULT_MAX_USE_UPPER) - m->max_use = DEFAULT_MAX_USE_UPPER; - - if (m->max_use < DEFAULT_MAX_USE_LOWER) - m->max_use = DEFAULT_MAX_USE_LOWER; - } else - m->max_use = DEFAULT_MAX_USE_LOWER; - } else { - m->max_use = PAGE_ALIGN(m->max_use); - - if (m->max_use != 0 && m->max_use < JOURNAL_FILE_SIZE_MIN*2) - m->max_use = JOURNAL_FILE_SIZE_MIN*2; - } - - if (m->min_use == (uint64_t) -1) - m->min_use = DEFAULT_MIN_USE; - - if (m->min_use > m->max_use) - m->min_use = m->max_use; - - if (m->max_size == (uint64_t) -1) { - m->max_size = PAGE_ALIGN(m->max_use / 8); /* 8 chunks */ - - if (m->max_size > DEFAULT_MAX_SIZE_UPPER) - m->max_size = DEFAULT_MAX_SIZE_UPPER; - } else - m->max_size = PAGE_ALIGN(m->max_size); - - if (m->max_size != 0) { - if (m->max_size < JOURNAL_FILE_SIZE_MIN) - m->max_size = JOURNAL_FILE_SIZE_MIN; - - if (m->max_use != 0 && m->max_size*2 > m->max_use) - m->max_use = m->max_size*2; - } - - if (m->min_size == (uint64_t) -1) - m->min_size = JOURNAL_FILE_SIZE_MIN; - else { - m->min_size = PAGE_ALIGN(m->min_size); - - if (m->min_size < JOURNAL_FILE_SIZE_MIN) - m->min_size = JOURNAL_FILE_SIZE_MIN; - - if (m->max_size != 0 && m->min_size > m->max_size) - m->max_size = m->min_size; - } - - if (m->keep_free == (uint64_t) -1) { - - if (fs_size > 0) { - m->keep_free = PAGE_ALIGN(fs_size * 3 / 20); /* 15% of file system size */ - - if (m->keep_free > DEFAULT_KEEP_FREE_UPPER) - m->keep_free = DEFAULT_KEEP_FREE_UPPER; - - } else - m->keep_free = DEFAULT_KEEP_FREE; - } - - if (m->n_max_files == (uint64_t) -1) - m->n_max_files = DEFAULT_N_MAX_FILES; - - log_debug("Fixed min_use=%s max_use=%s max_size=%s min_size=%s keep_free=%s n_max_files=%" PRIu64, - format_bytes(a, sizeof(a), m->min_use), - format_bytes(b, sizeof(b), m->max_use), - format_bytes(c, sizeof(c), m->max_size), - format_bytes(d, sizeof(d), m->min_size), - format_bytes(e, sizeof(e), m->keep_free), - m->n_max_files); -} - -int journal_file_get_cutoff_realtime_usec(JournalFile *f, usec_t *from, usec_t *to) { - assert(f); - assert(f->header); - assert(from || to); - - if (from) { - if (f->header->head_entry_realtime == 0) - return -ENOENT; - - *from = le64toh(f->header->head_entry_realtime); - } - - if (to) { - if (f->header->tail_entry_realtime == 0) - return -ENOENT; - - *to = le64toh(f->header->tail_entry_realtime); - } - - return 1; -} - -int journal_file_get_cutoff_monotonic_usec(JournalFile *f, sd_id128_t boot_id, usec_t *from, usec_t *to) { - Object *o; - uint64_t p; - int r; - - assert(f); - assert(from || to); - - r = find_data_object_by_boot_id(f, boot_id, &o, &p); - if (r <= 0) - return r; - - if (le64toh(o->data.n_entries) <= 0) - return 0; - - if (from) { - r = journal_file_move_to_object(f, OBJECT_ENTRY, le64toh(o->data.entry_offset), &o); - if (r < 0) - return r; - - *from = le64toh(o->entry.monotonic); - } - - if (to) { - r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); - if (r < 0) - return r; - - r = generic_array_get_plus_one(f, - le64toh(o->data.entry_offset), - le64toh(o->data.entry_array_offset), - le64toh(o->data.n_entries)-1, - &o, NULL); - if (r <= 0) - return r; - - *to = le64toh(o->entry.monotonic); - } - - return 1; -} - -bool journal_file_rotate_suggested(JournalFile *f, usec_t max_file_usec) { - assert(f); - assert(f->header); - - /* If we gained new header fields we gained new features, - * hence suggest a rotation */ - if (le64toh(f->header->header_size) < sizeof(Header)) { - log_debug("%s uses an outdated header, suggesting rotation.", f->path); - return true; - } - - /* Let's check if the hash tables grew over a certain fill - * level (75%, borrowing this value from Java's hash table - * implementation), and if so suggest a rotation. To calculate - * the fill level we need the n_data field, which only exists - * in newer versions. */ - - if (JOURNAL_HEADER_CONTAINS(f->header, n_data)) - if (le64toh(f->header->n_data) * 4ULL > (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)) * 3ULL) { - log_debug("Data hash table of %s has a fill level at %.1f (%"PRIu64" of %"PRIu64" items, %llu file size, %"PRIu64" bytes per hash table item), suggesting rotation.", - f->path, - 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))), - le64toh(f->header->n_data), - le64toh(f->header->data_hash_table_size) / sizeof(HashItem), - (unsigned long long) f->last_stat.st_size, - f->last_stat.st_size / le64toh(f->header->n_data)); - return true; - } - - if (JOURNAL_HEADER_CONTAINS(f->header, n_fields)) - if (le64toh(f->header->n_fields) * 4ULL > (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)) * 3ULL) { - log_debug("Field hash table of %s has a fill level at %.1f (%"PRIu64" of %"PRIu64" items), suggesting rotation.", - f->path, - 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))), - le64toh(f->header->n_fields), - le64toh(f->header->field_hash_table_size) / sizeof(HashItem)); - return true; - } - - /* Are the data objects properly indexed by field objects? */ - if (JOURNAL_HEADER_CONTAINS(f->header, n_data) && - JOURNAL_HEADER_CONTAINS(f->header, n_fields) && - le64toh(f->header->n_data) > 0 && - le64toh(f->header->n_fields) == 0) - return true; - - if (max_file_usec > 0) { - usec_t t, h; - - h = le64toh(f->header->head_entry_realtime); - t = now(CLOCK_REALTIME); - - if (h > 0 && t > h + max_file_usec) - return true; - } - - return false; -} diff --git a/src/journal/journal-file.h b/src/journal/journal-file.h deleted file mode 100644 index 564e1a8179..0000000000 --- a/src/journal/journal-file.h +++ /dev/null @@ -1,265 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 - -#ifdef HAVE_GCRYPT -#include -#endif - -#include "sd-id128.h" - -#include "hashmap.h" -#include "journal-def.h" -#include "macro.h" -#include "mmap-cache.h" -#include "sd-event.h" -#include "sparse-endian.h" - -typedef struct JournalMetrics { - /* For all these: -1 means "pick automatically", and 0 means "no limit enforced" */ - uint64_t max_size; /* how large journal files grow at max */ - uint64_t min_size; /* how large journal files grow at least */ - uint64_t max_use; /* how much disk space to use in total at max, keep_free permitting */ - uint64_t min_use; /* how much disk space to use in total at least, even if keep_free says not to */ - uint64_t keep_free; /* how much to keep free on disk */ - uint64_t n_max_files; /* how many files to keep around at max */ -} JournalMetrics; - -typedef enum direction { - DIRECTION_UP, - DIRECTION_DOWN -} direction_t; - -typedef enum LocationType { - /* The first and last entries, resp. */ - LOCATION_HEAD, - LOCATION_TAIL, - - /* We already read the entry we currently point to, and the - * next one to read should probably not be this one again. */ - LOCATION_DISCRETE, - - /* We should seek to the precise location specified, and - * return it, as we haven't read it yet. */ - LOCATION_SEEK -} LocationType; - -typedef enum OfflineState { - OFFLINE_JOINED, - OFFLINE_SYNCING, - OFFLINE_OFFLINING, - OFFLINE_CANCEL, - OFFLINE_AGAIN_FROM_SYNCING, - OFFLINE_AGAIN_FROM_OFFLINING, - OFFLINE_DONE -} OfflineState; - -typedef struct JournalFile { - int fd; - - mode_t mode; - - int flags; - int prot; - bool writable:1; - bool compress_xz:1; - bool compress_lz4:1; - bool seal:1; - bool defrag_on_close:1; - bool close_fd:1; - bool archive:1; - - bool tail_entry_monotonic_valid:1; - - direction_t last_direction; - LocationType location_type; - uint64_t last_n_entries; - - char *path; - struct stat last_stat; - usec_t last_stat_usec; - - Header *header; - HashItem *data_hash_table; - HashItem *field_hash_table; - - uint64_t current_offset; - uint64_t current_seqnum; - uint64_t current_realtime; - uint64_t current_monotonic; - sd_id128_t current_boot_id; - uint64_t current_xor_hash; - - JournalMetrics metrics; - MMapCache *mmap; - - sd_event_source *post_change_timer; - usec_t post_change_timer_period; - - OrderedHashmap *chain_cache; - - pthread_t offline_thread; - volatile OfflineState offline_state; - -#if defined(HAVE_XZ) || defined(HAVE_LZ4) - void *compress_buffer; - size_t compress_buffer_size; -#endif - -#ifdef HAVE_GCRYPT - gcry_md_hd_t hmac; - bool hmac_running; - - FSSHeader *fss_file; - size_t fss_file_size; - - uint64_t fss_start_usec; - uint64_t fss_interval_usec; - - void *fsprg_state; - size_t fsprg_state_size; - - void *fsprg_seed; - size_t fsprg_seed_size; -#endif -} JournalFile; - -int journal_file_open( - int fd, - const char *fname, - int flags, - mode_t mode, - bool compress, - bool seal, - JournalMetrics *metrics, - MMapCache *mmap_cache, - Set *deferred_closes, - JournalFile *template, - JournalFile **ret); - -int journal_file_set_offline(JournalFile *f, bool wait); -bool journal_file_is_offlining(JournalFile *f); -JournalFile* journal_file_close(JournalFile *j); -void journal_file_close_set(Set *s); - -int journal_file_open_reliably( - const char *fname, - int flags, - mode_t mode, - bool compress, - bool seal, - JournalMetrics *metrics, - MMapCache *mmap_cache, - Set *deferred_closes, - JournalFile *template, - JournalFile **ret); - -#define ALIGN64(x) (((x) + 7ULL) & ~7ULL) -#define VALID64(x) (((x) & 7ULL) == 0ULL) - -/* Use six characters to cover the offsets common in smallish journal - * files without adding too many zeros. */ -#define OFSfmt "%06"PRIx64 - -static inline bool VALID_REALTIME(uint64_t u) { - /* This considers timestamps until the year 3112 valid. That should be plenty room... */ - return u > 0 && u < (1ULL << 55); -} - -static inline bool VALID_MONOTONIC(uint64_t u) { - /* This considers timestamps until 1142 years of runtime valid. */ - return u < (1ULL << 55); -} - -static inline bool VALID_EPOCH(uint64_t u) { - /* This allows changing the key for 1142 years, every usec. */ - return u < (1ULL << 55); -} - -#define JOURNAL_HEADER_CONTAINS(h, field) \ - (le64toh((h)->header_size) >= offsetof(Header, field) + sizeof((h)->field)) - -#define JOURNAL_HEADER_SEALED(h) \ - (!!(le32toh((h)->compatible_flags) & HEADER_COMPATIBLE_SEALED)) - -#define JOURNAL_HEADER_COMPRESSED_XZ(h) \ - (!!(le32toh((h)->incompatible_flags) & HEADER_INCOMPATIBLE_COMPRESSED_XZ)) - -#define JOURNAL_HEADER_COMPRESSED_LZ4(h) \ - (!!(le32toh((h)->incompatible_flags) & HEADER_INCOMPATIBLE_COMPRESSED_LZ4)) - -int journal_file_move_to_object(JournalFile *f, ObjectType type, uint64_t offset, Object **ret); - -uint64_t journal_file_entry_n_items(Object *o) _pure_; -uint64_t journal_file_entry_array_n_items(Object *o) _pure_; -uint64_t journal_file_hash_table_n_items(Object *o) _pure_; - -int journal_file_append_object(JournalFile *f, ObjectType type, uint64_t size, Object **ret, uint64_t *offset); -int journal_file_append_entry(JournalFile *f, const dual_timestamp *ts, const struct iovec iovec[], unsigned n_iovec, uint64_t *seqno, Object **ret, uint64_t *offset); - -int journal_file_find_data_object(JournalFile *f, const void *data, uint64_t size, Object **ret, uint64_t *offset); -int journal_file_find_data_object_with_hash(JournalFile *f, const void *data, uint64_t size, uint64_t hash, Object **ret, uint64_t *offset); - -int journal_file_find_field_object(JournalFile *f, const void *field, uint64_t size, Object **ret, uint64_t *offset); -int journal_file_find_field_object_with_hash(JournalFile *f, const void *field, uint64_t size, uint64_t hash, Object **ret, uint64_t *offset); - -void journal_file_reset_location(JournalFile *f); -void journal_file_save_location(JournalFile *f, Object *o, uint64_t offset); -int journal_file_compare_locations(JournalFile *af, JournalFile *bf); -int journal_file_next_entry(JournalFile *f, uint64_t p, direction_t direction, Object **ret, uint64_t *offset); - -int journal_file_next_entry_for_data(JournalFile *f, Object *o, uint64_t p, uint64_t data_offset, direction_t direction, Object **ret, uint64_t *offset); - -int journal_file_move_to_entry_by_seqnum(JournalFile *f, uint64_t seqnum, direction_t direction, Object **ret, uint64_t *offset); -int journal_file_move_to_entry_by_realtime(JournalFile *f, uint64_t realtime, direction_t direction, Object **ret, uint64_t *offset); -int journal_file_move_to_entry_by_monotonic(JournalFile *f, sd_id128_t boot_id, uint64_t monotonic, direction_t direction, Object **ret, uint64_t *offset); - -int journal_file_move_to_entry_by_offset_for_data(JournalFile *f, uint64_t data_offset, uint64_t p, direction_t direction, Object **ret, uint64_t *offset); -int journal_file_move_to_entry_by_seqnum_for_data(JournalFile *f, uint64_t data_offset, uint64_t seqnum, direction_t direction, Object **ret, uint64_t *offset); -int journal_file_move_to_entry_by_realtime_for_data(JournalFile *f, uint64_t data_offset, uint64_t realtime, direction_t direction, Object **ret, uint64_t *offset); -int journal_file_move_to_entry_by_monotonic_for_data(JournalFile *f, uint64_t data_offset, sd_id128_t boot_id, uint64_t monotonic, direction_t direction, Object **ret, uint64_t *offset); - -int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset); - -void journal_file_dump(JournalFile *f); -void journal_file_print_header(JournalFile *f); - -int journal_file_rotate(JournalFile **f, bool compress, bool seal, Set *deferred_closes); - -void journal_file_post_change(JournalFile *f); -int journal_file_enable_post_change_timer(JournalFile *f, sd_event *e, usec_t t); - -void journal_reset_metrics(JournalMetrics *m); -void journal_default_metrics(JournalMetrics *m, int fd); - -int journal_file_get_cutoff_realtime_usec(JournalFile *f, usec_t *from, usec_t *to); -int journal_file_get_cutoff_monotonic_usec(JournalFile *f, sd_id128_t boot, usec_t *from, usec_t *to); - -bool journal_file_rotate_suggested(JournalFile *f, usec_t max_file_usec); - -int journal_file_map_data_hash_table(JournalFile *f); -int journal_file_map_field_hash_table(JournalFile *f); - -static inline bool JOURNAL_FILE_COMPRESS(JournalFile *f) { - assert(f); - return f->compress_xz || f->compress_lz4; -} diff --git a/src/journal/journal-internal.h b/src/journal/journal-internal.h deleted file mode 100644 index 34a48141f5..0000000000 --- a/src/journal/journal-internal.h +++ /dev/null @@ -1,143 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 -#include - -#include "sd-id128.h" -#include "sd-journal.h" - -#include "hashmap.h" -#include "journal-def.h" -#include "journal-file.h" -#include "list.h" -#include "set.h" - -typedef struct Match Match; -typedef struct Location Location; -typedef struct Directory Directory; - -typedef enum MatchType { - MATCH_DISCRETE, - MATCH_OR_TERM, - MATCH_AND_TERM -} MatchType; - -struct Match { - MatchType type; - Match *parent; - LIST_FIELDS(Match, matches); - - /* For concrete matches */ - char *data; - size_t size; - le64_t le_hash; - - /* For terms */ - LIST_HEAD(Match, matches); -}; - -struct Location { - LocationType type; - - bool seqnum_set; - bool realtime_set; - bool monotonic_set; - bool xor_hash_set; - - uint64_t seqnum; - sd_id128_t seqnum_id; - - uint64_t realtime; - - uint64_t monotonic; - sd_id128_t boot_id; - - uint64_t xor_hash; -}; - -struct Directory { - char *path; - int wd; - bool is_root; -}; - -struct sd_journal { - int toplevel_fd; - - char *path; - char *prefix; - - OrderedHashmap *files; - MMapCache *mmap; - - Location current_location; - - JournalFile *current_file; - uint64_t current_field; - - Match *level0, *level1, *level2; - - pid_t original_pid; - - int inotify_fd; - unsigned current_invalidate_counter, last_invalidate_counter; - usec_t last_process_usec; - - /* Iterating through unique fields and their data values */ - char *unique_field; - JournalFile *unique_file; - uint64_t unique_offset; - - /* Iterating through known fields */ - JournalFile *fields_file; - uint64_t fields_offset; - uint64_t fields_hash_table_index; - char *fields_buffer; - size_t fields_buffer_allocated; - - int flags; - - bool on_network:1; - bool no_new_files:1; - bool no_inotify:1; - bool unique_file_lost:1; /* File we were iterating over got - removed, and there were no more - files, so sd_j_enumerate_unique - will return a value equal to 0. */ - bool fields_file_lost:1; - bool has_runtime_files:1; - bool has_persistent_files:1; - - size_t data_threshold; - - Hashmap *directories_by_path; - Hashmap *directories_by_wd; - - Hashmap *errors; -}; - -char *journal_make_match_string(sd_journal *j); -void journal_print_header(sd_journal *j); - -#define JOURNAL_FOREACH_DATA_RETVAL(j, data, l, retval) \ - for (sd_journal_restart_data(j); ((retval) = sd_journal_enumerate_data((j), &(data), &(l))) > 0; ) diff --git a/src/journal/journal-qrcode.c b/src/journal/journal-qrcode.c deleted file mode 100644 index e38730d65c..0000000000 --- a/src/journal/journal-qrcode.c +++ /dev/null @@ -1,135 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 -#include -#include -#include -#include - -#include "journal-qrcode.h" - -#define WHITE_ON_BLACK "\033[40;37;1m" -#define NORMAL "\033[0m" - -static void print_border(FILE *output, unsigned width) { - unsigned x, y; - - /* Four rows of border */ - for (y = 0; y < 4; y += 2) { - fputs(WHITE_ON_BLACK, output); - - for (x = 0; x < 4 + width + 4; x++) - fputs("\342\226\210", output); - - fputs(NORMAL "\n", output); - } -} - -int print_qr_code( - FILE *output, - const void *seed, - size_t seed_size, - uint64_t start, - uint64_t interval, - const char *hn, - sd_id128_t machine) { - - FILE *f; - char *url = NULL; - size_t url_size = 0, i; - QRcode* qr; - unsigned x, y; - - assert(seed); - assert(seed_size > 0); - - f = open_memstream(&url, &url_size); - if (!f) - return -ENOMEM; - - fputs("fss://", f); - - for (i = 0; i < seed_size; i++) { - if (i > 0 && i % 3 == 0) - fputc('-', f); - fprintf(f, "%02x", ((uint8_t*) seed)[i]); - } - - fprintf(f, "/%"PRIx64"-%"PRIx64"?machine=" SD_ID128_FORMAT_STR, - start, - interval, - SD_ID128_FORMAT_VAL(machine)); - - if (hn) - fprintf(f, ";hostname=%s", hn); - - if (ferror(f)) { - fclose(f); - free(url); - return -ENOMEM; - } - - fclose(f); - - qr = QRcode_encodeString(url, 0, QR_ECLEVEL_L, QR_MODE_8, 1); - free(url); - - if (!qr) - return -ENOMEM; - - print_border(output, qr->width); - - for (y = 0; y < (unsigned) qr->width; y += 2) { - const uint8_t *row1, *row2; - - row1 = qr->data + qr->width * y; - row2 = row1 + qr->width; - - fputs(WHITE_ON_BLACK, output); - for (x = 0; x < 4; x++) - fputs("\342\226\210", output); - - for (x = 0; x < (unsigned) qr->width; x ++) { - bool a, b; - - a = row1[x] & 1; - b = (y+1) < (unsigned) qr->width ? (row2[x] & 1) : false; - - if (a && b) - fputc(' ', output); - else if (a) - fputs("\342\226\204", output); - else if (b) - fputs("\342\226\200", output); - else - fputs("\342\226\210", output); - } - - for (x = 0; x < 4; x++) - fputs("\342\226\210", output); - fputs(NORMAL "\n", output); - } - - print_border(output, qr->width); - - QRcode_free(qr); - return 0; -} diff --git a/src/journal/journal-qrcode.h b/src/journal/journal-qrcode.h deleted file mode 100644 index ef39085561..0000000000 --- a/src/journal/journal-qrcode.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 - -#include "sd-id128.h" - -int print_qr_code(FILE *f, const void *seed, size_t seed_size, uint64_t start, uint64_t interval, const char *hn, sd_id128_t machine); diff --git a/src/journal/journal-send.c b/src/journal/journal-send.c deleted file mode 100644 index 5e8a3e3200..0000000000 --- a/src/journal/journal-send.c +++ /dev/null @@ -1,559 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include -#include -#include -#include -#include - -#define SD_JOURNAL_SUPPRESS_LOCATION - -#include "sd-journal.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "io-util.h" -#include "memfd-util.h" -#include "socket-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "util.h" - -#define SNDBUF_SIZE (8*1024*1024) - -#define ALLOCA_CODE_FUNC(f, func) \ - do { \ - size_t _fl; \ - const char *_func = (func); \ - char **_f = &(f); \ - _fl = strlen(_func) + 1; \ - *_f = alloca(_fl + 10); \ - memcpy(*_f, "CODE_FUNC=", 10); \ - memcpy(*_f + 10, _func, _fl); \ - } while (false) - -/* We open a single fd, and we'll share it with the current process, - * all its threads, and all its subprocesses. This means we need to - * initialize it atomically, and need to operate on it atomically - * never assuming we are the only user */ - -static int journal_fd(void) { - int fd; - static int fd_plus_one = 0; - -retry: - if (fd_plus_one > 0) - return fd_plus_one - 1; - - fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); - if (fd < 0) - return -errno; - - fd_inc_sndbuf(fd, SNDBUF_SIZE); - - if (!__sync_bool_compare_and_swap(&fd_plus_one, 0, fd+1)) { - safe_close(fd); - goto retry; - } - - return fd; -} - -_public_ int sd_journal_print(int priority, const char *format, ...) { - int r; - va_list ap; - - va_start(ap, format); - r = sd_journal_printv(priority, format, ap); - va_end(ap); - - return r; -} - -_public_ int sd_journal_printv(int priority, const char *format, va_list ap) { - - /* FIXME: Instead of limiting things to LINE_MAX we could do a - C99 variable-length array on the stack here in a loop. */ - - char buffer[8 + LINE_MAX], p[sizeof("PRIORITY=")-1 + DECIMAL_STR_MAX(int) + 1]; - struct iovec iov[2]; - - assert_return(priority >= 0, -EINVAL); - assert_return(priority <= 7, -EINVAL); - assert_return(format, -EINVAL); - - xsprintf(p, "PRIORITY=%i", priority & LOG_PRIMASK); - - memcpy(buffer, "MESSAGE=", 8); - vsnprintf(buffer+8, sizeof(buffer) - 8, format, ap); - - zero(iov); - IOVEC_SET_STRING(iov[0], buffer); - IOVEC_SET_STRING(iov[1], p); - - return sd_journal_sendv(iov, 2); -} - -_printf_(1, 0) static int fill_iovec_sprintf(const char *format, va_list ap, int extra, struct iovec **_iov) { - PROTECT_ERRNO; - int r, n = 0, i = 0, j; - struct iovec *iov = NULL; - - assert(_iov); - - if (extra > 0) { - n = MAX(extra * 2, extra + 4); - iov = malloc0(n * sizeof(struct iovec)); - if (!iov) { - r = -ENOMEM; - goto fail; - } - - i = extra; - } - - while (format) { - struct iovec *c; - char *buffer; - va_list aq; - - if (i >= n) { - n = MAX(i*2, 4); - c = realloc(iov, n * sizeof(struct iovec)); - if (!c) { - r = -ENOMEM; - goto fail; - } - - iov = c; - } - - va_copy(aq, ap); - if (vasprintf(&buffer, format, aq) < 0) { - va_end(aq); - r = -ENOMEM; - goto fail; - } - va_end(aq); - - VA_FORMAT_ADVANCE(format, ap); - - IOVEC_SET_STRING(iov[i++], buffer); - - format = va_arg(ap, char *); - } - - *_iov = iov; - - return i; - -fail: - for (j = 0; j < i; j++) - free(iov[j].iov_base); - - free(iov); - - return r; -} - -_public_ int sd_journal_send(const char *format, ...) { - int r, i, j; - va_list ap; - struct iovec *iov = NULL; - - va_start(ap, format); - i = fill_iovec_sprintf(format, ap, 0, &iov); - va_end(ap); - - if (_unlikely_(i < 0)) { - r = i; - goto finish; - } - - r = sd_journal_sendv(iov, i); - -finish: - for (j = 0; j < i; j++) - free(iov[j].iov_base); - - free(iov); - - return r; -} - -_public_ int sd_journal_sendv(const struct iovec *iov, int n) { - PROTECT_ERRNO; - int fd, r; - _cleanup_close_ int buffer_fd = -1; - struct iovec *w; - uint64_t *l; - int i, j = 0; - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/journal/socket", - }; - struct msghdr mh = { - .msg_name = (struct sockaddr*) &sa.sa, - .msg_namelen = SOCKADDR_UN_LEN(sa.un), - }; - ssize_t k; - bool have_syslog_identifier = false; - bool seal = true; - - assert_return(iov, -EINVAL); - assert_return(n > 0, -EINVAL); - - w = newa(struct iovec, n * 5 + 3); - l = newa(uint64_t, n); - - for (i = 0; i < n; i++) { - char *c, *nl; - - if (_unlikely_(!iov[i].iov_base || iov[i].iov_len <= 1)) - return -EINVAL; - - c = memchr(iov[i].iov_base, '=', iov[i].iov_len); - if (_unlikely_(!c || c == iov[i].iov_base)) - return -EINVAL; - - have_syslog_identifier = have_syslog_identifier || - (c == (char *) iov[i].iov_base + 17 && - startswith(iov[i].iov_base, "SYSLOG_IDENTIFIER")); - - nl = memchr(iov[i].iov_base, '\n', iov[i].iov_len); - if (nl) { - if (_unlikely_(nl < c)) - return -EINVAL; - - /* Already includes a newline? Bummer, then - * let's write the variable name, then a - * newline, then the size (64bit LE), followed - * by the data and a final newline */ - - w[j].iov_base = iov[i].iov_base; - w[j].iov_len = c - (char*) iov[i].iov_base; - j++; - - IOVEC_SET_STRING(w[j++], "\n"); - - l[i] = htole64(iov[i].iov_len - (c - (char*) iov[i].iov_base) - 1); - w[j].iov_base = &l[i]; - w[j].iov_len = sizeof(uint64_t); - j++; - - w[j].iov_base = c + 1; - w[j].iov_len = iov[i].iov_len - (c - (char*) iov[i].iov_base) - 1; - j++; - - } else - /* Nothing special? Then just add the line and - * append a newline */ - w[j++] = iov[i]; - - IOVEC_SET_STRING(w[j++], "\n"); - } - - if (!have_syslog_identifier && - string_is_safe(program_invocation_short_name)) { - - /* Implicitly add program_invocation_short_name, if it - * is not set explicitly. We only do this for - * program_invocation_short_name, and nothing else - * since everything else is much nicer to retrieve - * from the outside. */ - - IOVEC_SET_STRING(w[j++], "SYSLOG_IDENTIFIER="); - IOVEC_SET_STRING(w[j++], program_invocation_short_name); - IOVEC_SET_STRING(w[j++], "\n"); - } - - fd = journal_fd(); - if (_unlikely_(fd < 0)) - return fd; - - mh.msg_iov = w; - mh.msg_iovlen = j; - - k = sendmsg(fd, &mh, MSG_NOSIGNAL); - if (k >= 0) - return 0; - - /* Fail silently if the journal is not available */ - if (errno == ENOENT) - return 0; - - if (errno != EMSGSIZE && errno != ENOBUFS) - return -errno; - - /* Message doesn't fit... Let's dump the data in a memfd or - * temporary file and just pass a file descriptor of it to the - * other side. - * - * For the temporary files we use /dev/shm instead of /tmp - * here, since we want this to be a tmpfs, and one that is - * available from early boot on and where unprivileged users - * can create files. */ - buffer_fd = memfd_new(NULL); - if (buffer_fd < 0) { - if (buffer_fd == -ENOSYS) { - buffer_fd = open_tmpfile_unlinkable("/dev/shm", O_RDWR | O_CLOEXEC); - if (buffer_fd < 0) - return buffer_fd; - - seal = false; - } else - return buffer_fd; - } - - n = writev(buffer_fd, w, j); - if (n < 0) - return -errno; - - if (seal) { - r = memfd_set_sealed(buffer_fd); - if (r < 0) - return r; - } - - r = send_one_fd_sa(fd, buffer_fd, mh.msg_name, mh.msg_namelen, 0); - if (r == -ENOENT) - /* Fail silently if the journal is not available */ - return 0; - return r; -} - -static int fill_iovec_perror_and_send(const char *message, int skip, struct iovec iov[]) { - PROTECT_ERRNO; - size_t n, k; - - k = isempty(message) ? 0 : strlen(message) + 2; - n = 8 + k + 256 + 1; - - for (;;) { - char buffer[n]; - char* j; - - errno = 0; - j = strerror_r(_saved_errno_, buffer + 8 + k, n - 8 - k); - if (errno == 0) { - char error[sizeof("ERRNO=")-1 + DECIMAL_STR_MAX(int) + 1]; - - if (j != buffer + 8 + k) - memmove(buffer + 8 + k, j, strlen(j)+1); - - memcpy(buffer, "MESSAGE=", 8); - - if (k > 0) { - memcpy(buffer + 8, message, k - 2); - memcpy(buffer + 8 + k - 2, ": ", 2); - } - - xsprintf(error, "ERRNO=%i", _saved_errno_); - - assert_cc(3 == LOG_ERR); - IOVEC_SET_STRING(iov[skip+0], "PRIORITY=3"); - IOVEC_SET_STRING(iov[skip+1], buffer); - IOVEC_SET_STRING(iov[skip+2], error); - - return sd_journal_sendv(iov, skip + 3); - } - - if (errno != ERANGE) - return -errno; - - n *= 2; - } -} - -_public_ int sd_journal_perror(const char *message) { - struct iovec iovec[3]; - - return fill_iovec_perror_and_send(message, 0, iovec); -} - -_public_ int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix) { - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/journal/stdout", - }; - _cleanup_close_ int fd = -1; - char *header; - size_t l; - int r; - - assert_return(priority >= 0, -EINVAL); - assert_return(priority <= 7, -EINVAL); - - fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); - if (fd < 0) - return -errno; - - r = connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); - if (r < 0) - return -errno; - - if (shutdown(fd, SHUT_RD) < 0) - return -errno; - - fd_inc_sndbuf(fd, SNDBUF_SIZE); - - if (!identifier) - identifier = ""; - - l = strlen(identifier); - header = alloca(l + 1 + 1 + 2 + 2 + 2 + 2 + 2); - - memcpy(header, identifier, l); - header[l++] = '\n'; - header[l++] = '\n'; /* unit id */ - header[l++] = '0' + priority; - header[l++] = '\n'; - header[l++] = '0' + !!level_prefix; - header[l++] = '\n'; - header[l++] = '0'; - header[l++] = '\n'; - header[l++] = '0'; - header[l++] = '\n'; - header[l++] = '0'; - header[l++] = '\n'; - - r = loop_write(fd, header, l, false); - if (r < 0) - return r; - - r = fd; - fd = -1; - return r; -} - -_public_ int sd_journal_print_with_location(int priority, const char *file, const char *line, const char *func, const char *format, ...) { - int r; - va_list ap; - - va_start(ap, format); - r = sd_journal_printv_with_location(priority, file, line, func, format, ap); - va_end(ap); - - return r; -} - -_public_ int sd_journal_printv_with_location(int priority, const char *file, const char *line, const char *func, const char *format, va_list ap) { - char buffer[8 + LINE_MAX], p[sizeof("PRIORITY=")-1 + DECIMAL_STR_MAX(int) + 1]; - struct iovec iov[5]; - char *f; - - assert_return(priority >= 0, -EINVAL); - assert_return(priority <= 7, -EINVAL); - assert_return(format, -EINVAL); - - xsprintf(p, "PRIORITY=%i", priority & LOG_PRIMASK); - - memcpy(buffer, "MESSAGE=", 8); - vsnprintf(buffer+8, sizeof(buffer) - 8, format, ap); - - /* func is initialized from __func__ which is not a macro, but - * a static const char[], hence cannot easily be prefixed with - * CODE_FUNC=, hence let's do it manually here. */ - ALLOCA_CODE_FUNC(f, func); - - zero(iov); - IOVEC_SET_STRING(iov[0], buffer); - IOVEC_SET_STRING(iov[1], p); - IOVEC_SET_STRING(iov[2], file); - IOVEC_SET_STRING(iov[3], line); - IOVEC_SET_STRING(iov[4], f); - - return sd_journal_sendv(iov, ELEMENTSOF(iov)); -} - -_public_ int sd_journal_send_with_location(const char *file, const char *line, const char *func, const char *format, ...) { - int r, i, j; - va_list ap; - struct iovec *iov = NULL; - char *f; - - va_start(ap, format); - i = fill_iovec_sprintf(format, ap, 3, &iov); - va_end(ap); - - if (_unlikely_(i < 0)) { - r = i; - goto finish; - } - - ALLOCA_CODE_FUNC(f, func); - - IOVEC_SET_STRING(iov[0], file); - IOVEC_SET_STRING(iov[1], line); - IOVEC_SET_STRING(iov[2], f); - - r = sd_journal_sendv(iov, i); - -finish: - for (j = 3; j < i; j++) - free(iov[j].iov_base); - - free(iov); - - return r; -} - -_public_ int sd_journal_sendv_with_location( - const char *file, const char *line, - const char *func, - const struct iovec *iov, int n) { - - struct iovec *niov; - char *f; - - assert_return(iov, -EINVAL); - assert_return(n > 0, -EINVAL); - - niov = alloca(sizeof(struct iovec) * (n + 3)); - memcpy(niov, iov, sizeof(struct iovec) * n); - - ALLOCA_CODE_FUNC(f, func); - - IOVEC_SET_STRING(niov[n++], file); - IOVEC_SET_STRING(niov[n++], line); - IOVEC_SET_STRING(niov[n++], f); - - return sd_journal_sendv(niov, n); -} - -_public_ int sd_journal_perror_with_location( - const char *file, const char *line, - const char *func, - const char *message) { - - struct iovec iov[6]; - char *f; - - ALLOCA_CODE_FUNC(f, func); - - IOVEC_SET_STRING(iov[0], file); - IOVEC_SET_STRING(iov[1], line); - IOVEC_SET_STRING(iov[2], f); - - return fill_iovec_perror_and_send(message, 3, iov); -} diff --git a/src/journal/journal-vacuum.c b/src/journal/journal-vacuum.c deleted file mode 100644 index f09dc66e03..0000000000 --- a/src/journal/journal-vacuum.c +++ /dev/null @@ -1,349 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include - -#include "sd-id128.h" - -#include "alloc-util.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "journal-def.h" -#include "journal-file.h" -#include "journal-vacuum.h" -#include "parse-util.h" -#include "string-util.h" -#include "util.h" -#include "xattr-util.h" - -struct vacuum_info { - uint64_t usage; - char *filename; - - uint64_t realtime; - - sd_id128_t seqnum_id; - uint64_t seqnum; - bool have_seqnum; -}; - -static int vacuum_compare(const void *_a, const void *_b) { - const struct vacuum_info *a, *b; - - a = _a; - b = _b; - - if (a->have_seqnum && b->have_seqnum && - sd_id128_equal(a->seqnum_id, b->seqnum_id)) { - if (a->seqnum < b->seqnum) - return -1; - else if (a->seqnum > b->seqnum) - return 1; - else - return 0; - } - - if (a->realtime < b->realtime) - return -1; - else if (a->realtime > b->realtime) - return 1; - else if (a->have_seqnum && b->have_seqnum) - return memcmp(&a->seqnum_id, &b->seqnum_id, 16); - else - return strcmp(a->filename, b->filename); -} - -static void patch_realtime( - int fd, - const char *fn, - const struct stat *st, - unsigned long long *realtime) { - - usec_t x, crtime = 0; - - /* The timestamp was determined by the file name, but let's - * see if the file might actually be older than the file name - * suggested... */ - - assert(fd >= 0); - assert(fn); - assert(st); - assert(realtime); - - x = timespec_load(&st->st_ctim); - if (x > 0 && x != USEC_INFINITY && x < *realtime) - *realtime = x; - - x = timespec_load(&st->st_atim); - if (x > 0 && x != USEC_INFINITY && x < *realtime) - *realtime = x; - - x = timespec_load(&st->st_mtim); - if (x > 0 && x != USEC_INFINITY && x < *realtime) - *realtime = x; - - /* Let's read the original creation time, if possible. Ideally - * we'd just query the creation time the FS might provide, but - * unfortunately there's currently no sane API to query - * it. Hence let's implement this manually... */ - - if (fd_getcrtime_at(fd, fn, &crtime, 0) >= 0) { - if (crtime < *realtime) - *realtime = crtime; - } -} - -static int journal_file_empty(int dir_fd, const char *name) { - _cleanup_close_ int fd; - struct stat st; - le64_t n_entries; - ssize_t n; - - fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK|O_NOATIME); - if (fd < 0) { - /* Maybe failed due to O_NOATIME and lack of privileges? */ - fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK); - if (fd < 0) - return -errno; - } - - if (fstat(fd, &st) < 0) - return -errno; - - /* If an offline file doesn't even have a header we consider it empty */ - if (st.st_size < (off_t) sizeof(Header)) - return 1; - - /* If the number of entries is empty, we consider it empty, too */ - n = pread(fd, &n_entries, sizeof(n_entries), offsetof(Header, n_entries)); - if (n < 0) - return -errno; - if (n != sizeof(n_entries)) - return -EIO; - - return le64toh(n_entries) <= 0; -} - -int journal_directory_vacuum( - const char *directory, - uint64_t max_use, - uint64_t n_max_files, - usec_t max_retention_usec, - usec_t *oldest_usec, - bool verbose) { - - _cleanup_closedir_ DIR *d = NULL; - struct vacuum_info *list = NULL; - unsigned n_list = 0, i, n_active_files = 0; - size_t n_allocated = 0; - uint64_t sum = 0, freed = 0; - usec_t retention_limit = 0; - char sbytes[FORMAT_BYTES_MAX]; - struct dirent *de; - int r; - - assert(directory); - - if (max_use <= 0 && max_retention_usec <= 0 && n_max_files <= 0) - return 0; - - if (max_retention_usec > 0) { - retention_limit = now(CLOCK_REALTIME); - if (retention_limit > max_retention_usec) - retention_limit -= max_retention_usec; - else - max_retention_usec = retention_limit = 0; - } - - d = opendir(directory); - if (!d) - return -errno; - - FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) { - - unsigned long long seqnum = 0, realtime; - _cleanup_free_ char *p = NULL; - sd_id128_t seqnum_id; - bool have_seqnum; - uint64_t size; - struct stat st; - size_t q; - - if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { - log_debug_errno(errno, "Failed to stat file %s while vacuuming, ignoring: %m", de->d_name); - continue; - } - - if (!S_ISREG(st.st_mode)) - continue; - - q = strlen(de->d_name); - - if (endswith(de->d_name, ".journal")) { - - /* Vacuum archived files. Active files are - * left around */ - - if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8) { - n_active_files++; - continue; - } - - if (de->d_name[q-8-16-1] != '-' || - de->d_name[q-8-16-1-16-1] != '-' || - de->d_name[q-8-16-1-16-1-32-1] != '@') { - n_active_files++; - continue; - } - - p = strdup(de->d_name); - if (!p) { - r = -ENOMEM; - goto finish; - } - - de->d_name[q-8-16-1-16-1] = 0; - if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) { - n_active_files++; - continue; - } - - if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) { - n_active_files++; - continue; - } - - have_seqnum = true; - - } else if (endswith(de->d_name, ".journal~")) { - unsigned long long tmp; - - /* Vacuum corrupted files */ - - if (q < 1 + 16 + 1 + 16 + 8 + 1) { - n_active_files++; - continue; - } - - if (de->d_name[q-1-8-16-1] != '-' || - de->d_name[q-1-8-16-1-16-1] != '@') { - n_active_files++; - continue; - } - - p = strdup(de->d_name); - if (!p) { - r = -ENOMEM; - goto finish; - } - - if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) { - n_active_files++; - continue; - } - - have_seqnum = false; - } else { - /* We do not vacuum unknown files! */ - log_debug("Not vacuuming unknown file %s.", de->d_name); - continue; - } - - size = 512UL * (uint64_t) st.st_blocks; - - r = journal_file_empty(dirfd(d), p); - if (r < 0) { - log_debug_errno(r, "Failed check if %s is empty, ignoring: %m", p); - continue; - } - if (r > 0) { - /* Always vacuum empty non-online files. */ - - if (unlinkat(dirfd(d), p, 0) >= 0) { - - log_full(verbose ? LOG_INFO : LOG_DEBUG, - "Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size)); - - freed += size; - } else if (errno != ENOENT) - log_warning_errno(errno, "Failed to delete empty archived journal %s/%s: %m", directory, p); - - continue; - } - - patch_realtime(dirfd(d), p, &st, &realtime); - - if (!GREEDY_REALLOC(list, n_allocated, n_list + 1)) { - r = -ENOMEM; - goto finish; - } - - list[n_list].filename = p; - list[n_list].usage = size; - list[n_list].seqnum = seqnum; - list[n_list].realtime = realtime; - list[n_list].seqnum_id = seqnum_id; - list[n_list].have_seqnum = have_seqnum; - n_list++; - - p = NULL; - sum += size; - } - - qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare); - - for (i = 0; i < n_list; i++) { - unsigned left; - - left = n_active_files + n_list - i; - - if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) && - (max_use <= 0 || sum <= max_use) && - (n_max_files <= 0 || left <= n_max_files)) - break; - - if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) { - log_full(verbose ? LOG_INFO : LOG_DEBUG, "Deleted archived journal %s/%s (%s).", directory, list[i].filename, format_bytes(sbytes, sizeof(sbytes), list[i].usage)); - freed += list[i].usage; - - if (list[i].usage < sum) - sum -= list[i].usage; - else - sum = 0; - - } else if (errno != ENOENT) - log_warning_errno(errno, "Failed to delete archived journal %s/%s: %m", directory, list[i].filename); - } - - if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec)) - *oldest_usec = list[i].realtime; - - r = 0; - -finish: - for (i = 0; i < n_list; i++) - free(list[i].filename); - free(list); - - log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes, sizeof(sbytes), freed)); - - return r; -} diff --git a/src/journal/journal-vacuum.h b/src/journal/journal-vacuum.h deleted file mode 100644 index 1e750a2170..0000000000 --- a/src/journal/journal-vacuum.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 - -#include "time-util.h" - -int journal_directory_vacuum(const char *directory, uint64_t max_use, uint64_t n_max_files, usec_t max_retention_usec, usec_t *oldest_usec, bool verbose); diff --git a/src/journal/journal-verify.c b/src/journal/journal-verify.c deleted file mode 100644 index 26572ddd76..0000000000 --- a/src/journal/journal-verify.c +++ /dev/null @@ -1,1294 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 -#include -#include - -#include "alloc-util.h" -#include "compress.h" -#include "fd-util.h" -#include "fileio.h" -#include "journal-authenticate.h" -#include "journal-def.h" -#include "journal-file.h" -#include "journal-verify.h" -#include "lookup3.h" -#include "macro.h" -#include "terminal-util.h" -#include "util.h" - -static void draw_progress(uint64_t p, usec_t *last_usec) { - unsigned n, i, j, k; - usec_t z, x; - - if (!on_tty()) - return; - - z = now(CLOCK_MONOTONIC); - x = *last_usec; - - if (x != 0 && x + 40 * USEC_PER_MSEC > z) - return; - - *last_usec = z; - - n = (3 * columns()) / 4; - j = (n * (unsigned) p) / 65535ULL; - k = n - j; - - fputs("\r\x1B[?25l" ANSI_HIGHLIGHT_GREEN, stdout); - - for (i = 0; i < j; i++) - fputs("\xe2\x96\x88", stdout); - - fputs(ANSI_NORMAL, stdout); - - for (i = 0; i < k; i++) - fputs("\xe2\x96\x91", stdout); - - printf(" %3"PRIu64"%%", 100U * p / 65535U); - - fputs("\r\x1B[?25h", stdout); - fflush(stdout); -} - -static uint64_t scale_progress(uint64_t scale, uint64_t p, uint64_t m) { - - /* Calculates scale * p / m, but handles m == 0 safely, and saturates */ - - if (p >= m || m == 0) - return scale; - - return scale * p / m; -} - -static void flush_progress(void) { - unsigned n, i; - - if (!on_tty()) - return; - - n = (3 * columns()) / 4; - - putchar('\r'); - - for (i = 0; i < n + 5; i++) - putchar(' '); - - putchar('\r'); - fflush(stdout); -} - -#define debug(_offset, _fmt, ...) do { \ - flush_progress(); \ - log_debug(OFSfmt": " _fmt, _offset, ##__VA_ARGS__); \ - } while (0) - -#define warning(_offset, _fmt, ...) do { \ - flush_progress(); \ - log_warning(OFSfmt": " _fmt, _offset, ##__VA_ARGS__); \ - } while (0) - -#define error(_offset, _fmt, ...) do { \ - flush_progress(); \ - log_error(OFSfmt": " _fmt, (uint64_t)_offset, ##__VA_ARGS__); \ - } while (0) - -static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o) { - uint64_t i; - - assert(f); - assert(offset); - assert(o); - - /* This does various superficial tests about the length an - * possible field values. It does not follow any references to - * other objects. */ - - if ((o->object.flags & OBJECT_COMPRESSED_XZ) && - o->object.type != OBJECT_DATA) { - error(offset, "Found compressed object that isn't of type DATA, which is not allowed."); - return -EBADMSG; - } - - switch (o->object.type) { - - case OBJECT_DATA: { - uint64_t h1, h2; - int compression, r; - - if (le64toh(o->data.entry_offset) == 0) - warning(offset, "Unused data (entry_offset==0)"); - - if ((le64toh(o->data.entry_offset) == 0) ^ (le64toh(o->data.n_entries) == 0)) { - error(offset, "Bad n_entries: %"PRIu64, o->data.n_entries); - return -EBADMSG; - } - - if (le64toh(o->object.size) - offsetof(DataObject, payload) <= 0) { - error(offset, "Bad object size (<= %zu): %"PRIu64, - offsetof(DataObject, payload), - le64toh(o->object.size)); - return -EBADMSG; - } - - h1 = le64toh(o->data.hash); - - compression = o->object.flags & OBJECT_COMPRESSION_MASK; - if (compression) { - _cleanup_free_ void *b = NULL; - size_t alloc = 0, b_size; - - r = decompress_blob(compression, - o->data.payload, - le64toh(o->object.size) - offsetof(Object, data.payload), - &b, &alloc, &b_size, 0); - if (r < 0) { - error(offset, "%s decompression failed: %s", - object_compressed_to_string(compression), strerror(-r)); - return r; - } - - h2 = hash64(b, b_size); - } else - h2 = hash64(o->data.payload, le64toh(o->object.size) - offsetof(Object, data.payload)); - - if (h1 != h2) { - error(offset, "Invalid hash (%08"PRIx64" vs. %08"PRIx64, h1, h2); - return -EBADMSG; - } - - if (!VALID64(o->data.next_hash_offset) || - !VALID64(o->data.next_field_offset) || - !VALID64(o->data.entry_offset) || - !VALID64(o->data.entry_array_offset)) { - error(offset, "Invalid offset (next_hash_offset="OFSfmt", next_field_offset="OFSfmt", entry_offset="OFSfmt", entry_array_offset="OFSfmt, - o->data.next_hash_offset, - o->data.next_field_offset, - o->data.entry_offset, - o->data.entry_array_offset); - return -EBADMSG; - } - - break; - } - - case OBJECT_FIELD: - if (le64toh(o->object.size) - offsetof(FieldObject, payload) <= 0) { - error(offset, - "Bad field size (<= %zu): %"PRIu64, - offsetof(FieldObject, payload), - le64toh(o->object.size)); - return -EBADMSG; - } - - if (!VALID64(o->field.next_hash_offset) || - !VALID64(o->field.head_data_offset)) { - error(offset, - "Invalid offset (next_hash_offset="OFSfmt", head_data_offset="OFSfmt, - o->field.next_hash_offset, - o->field.head_data_offset); - return -EBADMSG; - } - break; - - case OBJECT_ENTRY: - if ((le64toh(o->object.size) - offsetof(EntryObject, items)) % sizeof(EntryItem) != 0) { - error(offset, - "Bad entry size (<= %zu): %"PRIu64, - offsetof(EntryObject, items), - le64toh(o->object.size)); - return -EBADMSG; - } - - if ((le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem) <= 0) { - error(offset, - "Invalid number items in entry: %"PRIu64, - (le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem)); - return -EBADMSG; - } - - if (le64toh(o->entry.seqnum) <= 0) { - error(offset, - "Invalid entry seqnum: %"PRIx64, - le64toh(o->entry.seqnum)); - return -EBADMSG; - } - - if (!VALID_REALTIME(le64toh(o->entry.realtime))) { - error(offset, - "Invalid entry realtime timestamp: %"PRIu64, - le64toh(o->entry.realtime)); - return -EBADMSG; - } - - if (!VALID_MONOTONIC(le64toh(o->entry.monotonic))) { - error(offset, - "Invalid entry monotonic timestamp: %"PRIu64, - le64toh(o->entry.monotonic)); - return -EBADMSG; - } - - for (i = 0; i < journal_file_entry_n_items(o); i++) { - if (o->entry.items[i].object_offset == 0 || - !VALID64(o->entry.items[i].object_offset)) { - error(offset, - "Invalid entry item (%"PRIu64"/%"PRIu64" offset: "OFSfmt, - i, journal_file_entry_n_items(o), - o->entry.items[i].object_offset); - return -EBADMSG; - } - } - - break; - - case OBJECT_DATA_HASH_TABLE: - case OBJECT_FIELD_HASH_TABLE: - if ((le64toh(o->object.size) - offsetof(HashTableObject, items)) % sizeof(HashItem) != 0 || - (le64toh(o->object.size) - offsetof(HashTableObject, items)) / sizeof(HashItem) <= 0) { - error(offset, - "Invalid %s hash table size: %"PRIu64, - o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field", - le64toh(o->object.size)); - return -EBADMSG; - } - - for (i = 0; i < journal_file_hash_table_n_items(o); i++) { - if (o->hash_table.items[i].head_hash_offset != 0 && - !VALID64(le64toh(o->hash_table.items[i].head_hash_offset))) { - error(offset, - "Invalid %s hash table item (%"PRIu64"/%"PRIu64") head_hash_offset: "OFSfmt, - o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field", - i, journal_file_hash_table_n_items(o), - le64toh(o->hash_table.items[i].head_hash_offset)); - return -EBADMSG; - } - if (o->hash_table.items[i].tail_hash_offset != 0 && - !VALID64(le64toh(o->hash_table.items[i].tail_hash_offset))) { - error(offset, - "Invalid %s hash table item (%"PRIu64"/%"PRIu64") tail_hash_offset: "OFSfmt, - o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field", - i, journal_file_hash_table_n_items(o), - le64toh(o->hash_table.items[i].tail_hash_offset)); - return -EBADMSG; - } - - if ((o->hash_table.items[i].head_hash_offset != 0) != - (o->hash_table.items[i].tail_hash_offset != 0)) { - error(offset, - "Invalid %s hash table item (%"PRIu64"/%"PRIu64"): head_hash_offset="OFSfmt" tail_hash_offset="OFSfmt, - o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field", - i, journal_file_hash_table_n_items(o), - le64toh(o->hash_table.items[i].head_hash_offset), - le64toh(o->hash_table.items[i].tail_hash_offset)); - return -EBADMSG; - } - } - - break; - - case OBJECT_ENTRY_ARRAY: - if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) % sizeof(le64_t) != 0 || - (le64toh(o->object.size) - offsetof(EntryArrayObject, items)) / sizeof(le64_t) <= 0) { - error(offset, - "Invalid object entry array size: %"PRIu64, - le64toh(o->object.size)); - return -EBADMSG; - } - - if (!VALID64(o->entry_array.next_entry_array_offset)) { - error(offset, - "Invalid object entry array next_entry_array_offset: "OFSfmt, - o->entry_array.next_entry_array_offset); - return -EBADMSG; - } - - for (i = 0; i < journal_file_entry_array_n_items(o); i++) - if (le64toh(o->entry_array.items[i]) != 0 && - !VALID64(le64toh(o->entry_array.items[i]))) { - error(offset, - "Invalid object entry array item (%"PRIu64"/%"PRIu64"): "OFSfmt, - i, journal_file_entry_array_n_items(o), - le64toh(o->entry_array.items[i])); - return -EBADMSG; - } - - break; - - case OBJECT_TAG: - if (le64toh(o->object.size) != sizeof(TagObject)) { - error(offset, - "Invalid object tag size: %"PRIu64, - le64toh(o->object.size)); - return -EBADMSG; - } - - if (!VALID_EPOCH(o->tag.epoch)) { - error(offset, - "Invalid object tag epoch: %"PRIu64, - o->tag.epoch); - return -EBADMSG; - } - - break; - } - - return 0; -} - -static int write_uint64(int fd, uint64_t p) { - ssize_t k; - - k = write(fd, &p, sizeof(p)); - if (k < 0) - return -errno; - if (k != sizeof(p)) - return -EIO; - - return 0; -} - -static int contains_uint64(MMapCache *m, int fd, uint64_t n, uint64_t p) { - uint64_t a, b; - int r; - - assert(m); - assert(fd >= 0); - - /* Bisection ... */ - - a = 0; b = n; - while (a < b) { - uint64_t c, *z; - - c = (a + b) / 2; - - r = mmap_cache_get(m, fd, PROT_READ|PROT_WRITE, 0, false, c * sizeof(uint64_t), sizeof(uint64_t), NULL, (void **) &z); - if (r < 0) - return r; - - if (*z == p) - return 1; - - if (a + 1 >= b) - return 0; - - if (p < *z) - b = c; - else - a = c; - } - - return 0; -} - -static int entry_points_to_data( - JournalFile *f, - int entry_fd, - uint64_t n_entries, - uint64_t entry_p, - uint64_t data_p) { - - int r; - uint64_t i, n, a; - Object *o; - bool found = false; - - assert(f); - assert(entry_fd >= 0); - - if (!contains_uint64(f->mmap, entry_fd, n_entries, entry_p)) { - error(data_p, "Data object references invalid entry at "OFSfmt, entry_p); - return -EBADMSG; - } - - r = journal_file_move_to_object(f, OBJECT_ENTRY, entry_p, &o); - if (r < 0) - return r; - - n = journal_file_entry_n_items(o); - for (i = 0; i < n; i++) - if (le64toh(o->entry.items[i].object_offset) == data_p) { - found = true; - break; - } - - if (!found) { - error(entry_p, "Data object at "OFSfmt" not referenced by linked entry", data_p); - return -EBADMSG; - } - - /* Check if this entry is also in main entry array. Since the - * main entry array has already been verified we can rely on - * its consistency. */ - - i = 0; - n = le64toh(f->header->n_entries); - a = le64toh(f->header->entry_array_offset); - - while (i < n) { - uint64_t m, u; - - r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); - if (r < 0) - return r; - - m = journal_file_entry_array_n_items(o); - u = MIN(n - i, m); - - if (entry_p <= le64toh(o->entry_array.items[u-1])) { - uint64_t x, y, z; - - x = 0; - y = u; - - while (x < y) { - z = (x + y) / 2; - - if (le64toh(o->entry_array.items[z]) == entry_p) - return 0; - - if (x + 1 >= y) - break; - - if (entry_p < le64toh(o->entry_array.items[z])) - y = z; - else - x = z; - } - - error(entry_p, "Entry object doesn't exist in main entry array"); - return -EBADMSG; - } - - i += u; - a = le64toh(o->entry_array.next_entry_array_offset); - } - - return 0; -} - -static int verify_data( - JournalFile *f, - Object *o, uint64_t p, - int entry_fd, uint64_t n_entries, - int entry_array_fd, uint64_t n_entry_arrays) { - - uint64_t i, n, a, last, q; - int r; - - assert(f); - assert(o); - assert(entry_fd >= 0); - assert(entry_array_fd >= 0); - - n = le64toh(o->data.n_entries); - a = le64toh(o->data.entry_array_offset); - - /* Entry array means at least two objects */ - if (a && n < 2) { - error(p, "Entry array present (entry_array_offset="OFSfmt", but n_entries=%"PRIu64")", a, n); - return -EBADMSG; - } - - if (n == 0) - return 0; - - /* We already checked that earlier */ - assert(o->data.entry_offset); - - last = q = le64toh(o->data.entry_offset); - r = entry_points_to_data(f, entry_fd, n_entries, q, p); - if (r < 0) - return r; - - i = 1; - while (i < n) { - uint64_t next, m, j; - - if (a == 0) { - error(p, "Array chain too short"); - return -EBADMSG; - } - - if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) { - error(p, "Invalid array offset "OFSfmt, a); - return -EBADMSG; - } - - r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); - if (r < 0) - return r; - - next = le64toh(o->entry_array.next_entry_array_offset); - if (next != 0 && next <= a) { - error(p, "Array chain has cycle (jumps back from "OFSfmt" to "OFSfmt")", a, next); - return -EBADMSG; - } - - m = journal_file_entry_array_n_items(o); - for (j = 0; i < n && j < m; i++, j++) { - - q = le64toh(o->entry_array.items[j]); - if (q <= last) { - error(p, "Data object's entry array not sorted"); - return -EBADMSG; - } - last = q; - - r = entry_points_to_data(f, entry_fd, n_entries, q, p); - if (r < 0) - return r; - - /* Pointer might have moved, reposition */ - r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); - if (r < 0) - return r; - } - - a = next; - } - - return 0; -} - -static int verify_hash_table( - JournalFile *f, - int data_fd, uint64_t n_data, - int entry_fd, uint64_t n_entries, - int entry_array_fd, uint64_t n_entry_arrays, - usec_t *last_usec, - bool show_progress) { - - uint64_t i, n; - int r; - - assert(f); - assert(data_fd >= 0); - assert(entry_fd >= 0); - assert(entry_array_fd >= 0); - assert(last_usec); - - n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem); - if (n <= 0) - return 0; - - r = journal_file_map_data_hash_table(f); - if (r < 0) - return log_error_errno(r, "Failed to map data hash table: %m"); - - for (i = 0; i < n; i++) { - uint64_t last = 0, p; - - if (show_progress) - draw_progress(0xC000 + scale_progress(0x3FFF, i, n), last_usec); - - p = le64toh(f->data_hash_table[i].head_hash_offset); - while (p != 0) { - Object *o; - uint64_t next; - - if (!contains_uint64(f->mmap, data_fd, n_data, p)) { - error(p, "Invalid data object at hash entry %"PRIu64" of %"PRIu64, i, n); - return -EBADMSG; - } - - r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); - if (r < 0) - return r; - - next = le64toh(o->data.next_hash_offset); - if (next != 0 && next <= p) { - error(p, "Hash chain has a cycle in hash entry %"PRIu64" of %"PRIu64, i, n); - return -EBADMSG; - } - - if (le64toh(o->data.hash) % n != i) { - error(p, "Hash value mismatch in hash entry %"PRIu64" of %"PRIu64, i, n); - return -EBADMSG; - } - - r = verify_data(f, o, p, entry_fd, n_entries, entry_array_fd, n_entry_arrays); - if (r < 0) - return r; - - last = p; - p = next; - } - - if (last != le64toh(f->data_hash_table[i].tail_hash_offset)) { - error(p, "Tail hash pointer mismatch in hash table"); - return -EBADMSG; - } - } - - return 0; -} - -static int data_object_in_hash_table(JournalFile *f, uint64_t hash, uint64_t p) { - uint64_t n, h, q; - int r; - assert(f); - - n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem); - if (n <= 0) - return 0; - - r = journal_file_map_data_hash_table(f); - if (r < 0) - return log_error_errno(r, "Failed to map data hash table: %m"); - - h = hash % n; - - q = le64toh(f->data_hash_table[h].head_hash_offset); - while (q != 0) { - Object *o; - - if (p == q) - return 1; - - r = journal_file_move_to_object(f, OBJECT_DATA, q, &o); - if (r < 0) - return r; - - q = le64toh(o->data.next_hash_offset); - } - - return 0; -} - -static int verify_entry( - JournalFile *f, - Object *o, uint64_t p, - int data_fd, uint64_t n_data) { - - uint64_t i, n; - int r; - - assert(f); - assert(o); - assert(data_fd >= 0); - - n = journal_file_entry_n_items(o); - for (i = 0; i < n; i++) { - uint64_t q, h; - Object *u; - - q = le64toh(o->entry.items[i].object_offset); - h = le64toh(o->entry.items[i].hash); - - if (!contains_uint64(f->mmap, data_fd, n_data, q)) { - error(p, "Invalid data object of entry"); - return -EBADMSG; - } - - r = journal_file_move_to_object(f, OBJECT_DATA, q, &u); - if (r < 0) - return r; - - if (le64toh(u->data.hash) != h) { - error(p, "Hash mismatch for data object of entry"); - return -EBADMSG; - } - - r = data_object_in_hash_table(f, h, q); - if (r < 0) - return r; - if (r == 0) { - error(p, "Data object missing from hash table"); - return -EBADMSG; - } - } - - return 0; -} - -static int verify_entry_array( - JournalFile *f, - int data_fd, uint64_t n_data, - int entry_fd, uint64_t n_entries, - int entry_array_fd, uint64_t n_entry_arrays, - usec_t *last_usec, - bool show_progress) { - - uint64_t i = 0, a, n, last = 0; - int r; - - assert(f); - assert(data_fd >= 0); - assert(entry_fd >= 0); - assert(entry_array_fd >= 0); - assert(last_usec); - - n = le64toh(f->header->n_entries); - a = le64toh(f->header->entry_array_offset); - while (i < n) { - uint64_t next, m, j; - Object *o; - - if (show_progress) - draw_progress(0x8000 + scale_progress(0x3FFF, i, n), last_usec); - - if (a == 0) { - error(a, "Array chain too short at %"PRIu64" of %"PRIu64, i, n); - return -EBADMSG; - } - - if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) { - error(a, "Invalid array %"PRIu64" of %"PRIu64, i, n); - return -EBADMSG; - } - - r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); - if (r < 0) - return r; - - next = le64toh(o->entry_array.next_entry_array_offset); - if (next != 0 && next <= a) { - error(a, "Array chain has cycle at %"PRIu64" of %"PRIu64" (jumps back from to "OFSfmt")", i, n, next); - return -EBADMSG; - } - - m = journal_file_entry_array_n_items(o); - for (j = 0; i < n && j < m; i++, j++) { - uint64_t p; - - p = le64toh(o->entry_array.items[j]); - if (p <= last) { - error(a, "Entry array not sorted at %"PRIu64" of %"PRIu64, i, n); - return -EBADMSG; - } - last = p; - - if (!contains_uint64(f->mmap, entry_fd, n_entries, p)) { - error(a, "Invalid array entry at %"PRIu64" of %"PRIu64, i, n); - return -EBADMSG; - } - - r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o); - if (r < 0) - return r; - - r = verify_entry(f, o, p, data_fd, n_data); - if (r < 0) - return r; - - /* Pointer might have moved, reposition */ - r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); - if (r < 0) - return r; - } - - a = next; - } - - return 0; -} - -int journal_file_verify( - JournalFile *f, - const char *key, - usec_t *first_contained, usec_t *last_validated, usec_t *last_contained, - bool show_progress) { - int r; - Object *o; - uint64_t p = 0, last_epoch = 0, last_tag_realtime = 0, last_sealed_realtime = 0; - - uint64_t entry_seqnum = 0, entry_monotonic = 0, entry_realtime = 0; - sd_id128_t entry_boot_id; - bool entry_seqnum_set = false, entry_monotonic_set = false, entry_realtime_set = false, found_main_entry_array = false; - uint64_t n_weird = 0, n_objects = 0, n_entries = 0, n_data = 0, n_fields = 0, n_data_hash_tables = 0, n_field_hash_tables = 0, n_entry_arrays = 0, n_tags = 0; - usec_t last_usec = 0; - int data_fd = -1, entry_fd = -1, entry_array_fd = -1; - unsigned i; - bool found_last = false; -#ifdef HAVE_GCRYPT - uint64_t last_tag = 0; -#endif - assert(f); - - if (key) { -#ifdef HAVE_GCRYPT - r = journal_file_parse_verification_key(f, key); - if (r < 0) { - log_error("Failed to parse seed."); - return r; - } -#else - return -EOPNOTSUPP; -#endif - } else if (f->seal) - return -ENOKEY; - - data_fd = open_tmpfile_unlinkable("/var/tmp", O_RDWR | O_CLOEXEC); - if (data_fd < 0) { - r = log_error_errno(data_fd, "Failed to create data file: %m"); - goto fail; - } - - entry_fd = open_tmpfile_unlinkable("/var/tmp", O_RDWR | O_CLOEXEC); - if (entry_fd < 0) { - r = log_error_errno(entry_fd, "Failed to create entry file: %m"); - goto fail; - } - - entry_array_fd = open_tmpfile_unlinkable("/var/tmp", O_RDWR | O_CLOEXEC); - if (entry_array_fd < 0) { - r = log_error_errno(entry_array_fd, - "Failed to create entry array file: %m"); - goto fail; - } - - if (le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_SUPPORTED) { - log_error("Cannot verify file with unknown extensions."); - r = -EOPNOTSUPP; - goto fail; - } - - for (i = 0; i < sizeof(f->header->reserved); i++) - if (f->header->reserved[i] != 0) { - error(offsetof(Header, reserved[i]), "Reserved field is non-zero"); - r = -EBADMSG; - goto fail; - } - - /* First iteration: we go through all objects, verify the - * superficial structure, headers, hashes. */ - - p = le64toh(f->header->header_size); - for (;;) { - /* Early exit if there are no objects in the file, at all */ - if (le64toh(f->header->tail_object_offset) == 0) - break; - - if (show_progress) - draw_progress(scale_progress(0x7FFF, p, le64toh(f->header->tail_object_offset)), &last_usec); - - r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o); - if (r < 0) { - error(p, "Invalid object"); - goto fail; - } - - if (p > le64toh(f->header->tail_object_offset)) { - error(offsetof(Header, tail_object_offset), "Invalid tail object pointer"); - r = -EBADMSG; - goto fail; - } - - n_objects++; - - r = journal_file_object_verify(f, p, o); - if (r < 0) { - error(p, "Invalid object contents: %s", strerror(-r)); - goto fail; - } - - if ((o->object.flags & OBJECT_COMPRESSED_XZ) && - (o->object.flags & OBJECT_COMPRESSED_LZ4)) { - error(p, "Objected with double compression"); - r = -EINVAL; - goto fail; - } - - if ((o->object.flags & OBJECT_COMPRESSED_XZ) && !JOURNAL_HEADER_COMPRESSED_XZ(f->header)) { - error(p, "XZ compressed object in file without XZ compression"); - r = -EBADMSG; - goto fail; - } - - if ((o->object.flags & OBJECT_COMPRESSED_LZ4) && !JOURNAL_HEADER_COMPRESSED_LZ4(f->header)) { - error(p, "LZ4 compressed object in file without LZ4 compression"); - r = -EBADMSG; - goto fail; - } - - switch (o->object.type) { - - case OBJECT_DATA: - r = write_uint64(data_fd, p); - if (r < 0) - goto fail; - - n_data++; - break; - - case OBJECT_FIELD: - n_fields++; - break; - - case OBJECT_ENTRY: - if (JOURNAL_HEADER_SEALED(f->header) && n_tags <= 0) { - error(p, "First entry before first tag"); - r = -EBADMSG; - goto fail; - } - - r = write_uint64(entry_fd, p); - if (r < 0) - goto fail; - - if (le64toh(o->entry.realtime) < last_tag_realtime) { - error(p, "Older entry after newer tag"); - r = -EBADMSG; - goto fail; - } - - if (!entry_seqnum_set && - le64toh(o->entry.seqnum) != le64toh(f->header->head_entry_seqnum)) { - error(p, "Head entry sequence number incorrect"); - r = -EBADMSG; - goto fail; - } - - if (entry_seqnum_set && - entry_seqnum >= le64toh(o->entry.seqnum)) { - error(p, "Entry sequence number out of synchronization"); - r = -EBADMSG; - goto fail; - } - - entry_seqnum = le64toh(o->entry.seqnum); - entry_seqnum_set = true; - - if (entry_monotonic_set && - sd_id128_equal(entry_boot_id, o->entry.boot_id) && - entry_monotonic > le64toh(o->entry.monotonic)) { - error(p, "Entry timestamp out of synchronization"); - r = -EBADMSG; - goto fail; - } - - entry_monotonic = le64toh(o->entry.monotonic); - entry_boot_id = o->entry.boot_id; - entry_monotonic_set = true; - - if (!entry_realtime_set && - le64toh(o->entry.realtime) != le64toh(f->header->head_entry_realtime)) { - error(p, "Head entry realtime timestamp incorrect"); - r = -EBADMSG; - goto fail; - } - - entry_realtime = le64toh(o->entry.realtime); - entry_realtime_set = true; - - n_entries++; - break; - - case OBJECT_DATA_HASH_TABLE: - if (n_data_hash_tables > 1) { - error(p, "More than one data hash table"); - r = -EBADMSG; - goto fail; - } - - if (le64toh(f->header->data_hash_table_offset) != p + offsetof(HashTableObject, items) || - le64toh(f->header->data_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) { - error(p, "header fields for data hash table invalid"); - r = -EBADMSG; - goto fail; - } - - n_data_hash_tables++; - break; - - case OBJECT_FIELD_HASH_TABLE: - if (n_field_hash_tables > 1) { - error(p, "More than one field hash table"); - r = -EBADMSG; - goto fail; - } - - if (le64toh(f->header->field_hash_table_offset) != p + offsetof(HashTableObject, items) || - le64toh(f->header->field_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) { - error(p, "Header fields for field hash table invalid"); - r = -EBADMSG; - goto fail; - } - - n_field_hash_tables++; - break; - - case OBJECT_ENTRY_ARRAY: - r = write_uint64(entry_array_fd, p); - if (r < 0) - goto fail; - - if (p == le64toh(f->header->entry_array_offset)) { - if (found_main_entry_array) { - error(p, "More than one main entry array"); - r = -EBADMSG; - goto fail; - } - - found_main_entry_array = true; - } - - n_entry_arrays++; - break; - - case OBJECT_TAG: - if (!JOURNAL_HEADER_SEALED(f->header)) { - error(p, "Tag object in file without sealing"); - r = -EBADMSG; - goto fail; - } - - if (le64toh(o->tag.seqnum) != n_tags + 1) { - error(p, "Tag sequence number out of synchronization"); - r = -EBADMSG; - goto fail; - } - - if (le64toh(o->tag.epoch) < last_epoch) { - error(p, "Epoch sequence out of synchronization"); - r = -EBADMSG; - goto fail; - } - -#ifdef HAVE_GCRYPT - if (f->seal) { - uint64_t q, rt; - - debug(p, "Checking tag %"PRIu64"...", le64toh(o->tag.seqnum)); - - rt = f->fss_start_usec + o->tag.epoch * f->fss_interval_usec; - if (entry_realtime_set && entry_realtime >= rt + f->fss_interval_usec) { - error(p, "tag/entry realtime timestamp out of synchronization"); - r = -EBADMSG; - goto fail; - } - - /* OK, now we know the epoch. So let's now set - * it, and calculate the HMAC for everything - * since the last tag. */ - r = journal_file_fsprg_seek(f, le64toh(o->tag.epoch)); - if (r < 0) - goto fail; - - r = journal_file_hmac_start(f); - if (r < 0) - goto fail; - - if (last_tag == 0) { - r = journal_file_hmac_put_header(f); - if (r < 0) - goto fail; - - q = le64toh(f->header->header_size); - } else - q = last_tag; - - while (q <= p) { - r = journal_file_move_to_object(f, OBJECT_UNUSED, q, &o); - if (r < 0) - goto fail; - - r = journal_file_hmac_put_object(f, OBJECT_UNUSED, o, q); - if (r < 0) - goto fail; - - q = q + ALIGN64(le64toh(o->object.size)); - } - - /* Position might have changed, let's reposition things */ - r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o); - if (r < 0) - goto fail; - - if (memcmp(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH) != 0) { - error(p, "Tag failed verification"); - r = -EBADMSG; - goto fail; - } - - f->hmac_running = false; - last_tag_realtime = rt; - last_sealed_realtime = entry_realtime; - } - - last_tag = p + ALIGN64(le64toh(o->object.size)); -#endif - - last_epoch = le64toh(o->tag.epoch); - - n_tags++; - break; - - default: - n_weird++; - } - - if (p == le64toh(f->header->tail_object_offset)) { - found_last = true; - break; - } - - p = p + ALIGN64(le64toh(o->object.size)); - }; - - if (!found_last && le64toh(f->header->tail_object_offset) != 0) { - error(le64toh(f->header->tail_object_offset), "Tail object pointer dead"); - r = -EBADMSG; - goto fail; - } - - if (n_objects != le64toh(f->header->n_objects)) { - error(offsetof(Header, n_objects), "Object number mismatch"); - r = -EBADMSG; - goto fail; - } - - if (n_entries != le64toh(f->header->n_entries)) { - error(offsetof(Header, n_entries), "Entry number mismatch"); - r = -EBADMSG; - goto fail; - } - - if (JOURNAL_HEADER_CONTAINS(f->header, n_data) && - n_data != le64toh(f->header->n_data)) { - error(offsetof(Header, n_data), "Data number mismatch"); - r = -EBADMSG; - goto fail; - } - - if (JOURNAL_HEADER_CONTAINS(f->header, n_fields) && - n_fields != le64toh(f->header->n_fields)) { - error(offsetof(Header, n_fields), "Field number mismatch"); - r = -EBADMSG; - goto fail; - } - - if (JOURNAL_HEADER_CONTAINS(f->header, n_tags) && - n_tags != le64toh(f->header->n_tags)) { - error(offsetof(Header, n_tags), "Tag number mismatch"); - r = -EBADMSG; - goto fail; - } - - if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays) && - n_entry_arrays != le64toh(f->header->n_entry_arrays)) { - error(offsetof(Header, n_entry_arrays), "Entry array number mismatch"); - r = -EBADMSG; - goto fail; - } - - if (!found_main_entry_array && le64toh(f->header->entry_array_offset) != 0) { - error(0, "Missing entry array"); - r = -EBADMSG; - goto fail; - } - - if (entry_seqnum_set && - entry_seqnum != le64toh(f->header->tail_entry_seqnum)) { - error(offsetof(Header, tail_entry_seqnum), "Invalid tail seqnum"); - r = -EBADMSG; - goto fail; - } - - if (entry_monotonic_set && - (!sd_id128_equal(entry_boot_id, f->header->boot_id) || - entry_monotonic != le64toh(f->header->tail_entry_monotonic))) { - error(0, "Invalid tail monotonic timestamp"); - r = -EBADMSG; - goto fail; - } - - if (entry_realtime_set && entry_realtime != le64toh(f->header->tail_entry_realtime)) { - error(0, "Invalid tail realtime timestamp"); - r = -EBADMSG; - goto fail; - } - - /* Second iteration: we follow all objects referenced from the - * two entry points: the object hash table and the entry - * array. We also check that everything referenced (directly - * or indirectly) in the data hash table also exists in the - * entry array, and vice versa. Note that we do not care for - * unreferenced objects. We only care that everything that is - * referenced is consistent. */ - - r = verify_entry_array(f, - data_fd, n_data, - entry_fd, n_entries, - entry_array_fd, n_entry_arrays, - &last_usec, - show_progress); - if (r < 0) - goto fail; - - r = verify_hash_table(f, - data_fd, n_data, - entry_fd, n_entries, - entry_array_fd, n_entry_arrays, - &last_usec, - show_progress); - if (r < 0) - goto fail; - - if (show_progress) - flush_progress(); - - mmap_cache_close_fd(f->mmap, data_fd); - mmap_cache_close_fd(f->mmap, entry_fd); - mmap_cache_close_fd(f->mmap, entry_array_fd); - - safe_close(data_fd); - safe_close(entry_fd); - safe_close(entry_array_fd); - - if (first_contained) - *first_contained = le64toh(f->header->head_entry_realtime); - if (last_validated) - *last_validated = last_sealed_realtime; - if (last_contained) - *last_contained = le64toh(f->header->tail_entry_realtime); - - return 0; - -fail: - if (show_progress) - flush_progress(); - - log_error("File corruption detected at %s:"OFSfmt" (of %llu bytes, %"PRIu64"%%).", - f->path, - p, - (unsigned long long) f->last_stat.st_size, - 100 * p / f->last_stat.st_size); - - if (data_fd >= 0) { - mmap_cache_close_fd(f->mmap, data_fd); - safe_close(data_fd); - } - - if (entry_fd >= 0) { - mmap_cache_close_fd(f->mmap, entry_fd); - safe_close(entry_fd); - } - - if (entry_array_fd >= 0) { - mmap_cache_close_fd(f->mmap, entry_array_fd); - safe_close(entry_array_fd); - } - - return r; -} diff --git a/src/journal/journal-verify.h b/src/journal/journal-verify.h deleted file mode 100644 index 8f0eaf6daa..0000000000 --- a/src/journal/journal-verify.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 "journal-file.h" - -int journal_file_verify(JournalFile *f, const char *key, usec_t *first_contained, usec_t *last_validated, usec_t *last_contained, bool show_progress); diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c deleted file mode 100644 index f67c556783..0000000000 --- a/src/journal/journalctl.c +++ /dev/null @@ -1,2600 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-bus.h" -#include "sd-journal.h" - -#include "acl-util.h" -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "catalog.h" -#include "chattr-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "fsprg.h" -#include "glob-util.h" -#include "hostname-util.h" -#include "io-util.h" -#include "journal-def.h" -#include "journal-internal.h" -#include "journal-qrcode.h" -#include "journal-vacuum.h" -#include "journal-verify.h" -#include "locale-util.h" -#include "log.h" -#include "logs-show.h" -#include "mkdir.h" -#include "pager.h" -#include "parse-util.h" -#include "path-util.h" -#include "rlimit-util.h" -#include "set.h" -#include "sigbus.h" -#include "strv.h" -#include "syslog-util.h" -#include "terminal-util.h" -#include "udev.h" -#include "udev-util.h" -#include "unit-name.h" -#include "user-util.h" - -#define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE) - -enum { - /* Special values for arg_lines */ - ARG_LINES_DEFAULT = -2, - ARG_LINES_ALL = -1, -}; - -static OutputMode arg_output = OUTPUT_SHORT; -static bool arg_utc = false; -static bool arg_pager_end = false; -static bool arg_follow = false; -static bool arg_full = true; -static bool arg_all = false; -static bool arg_no_pager = false; -static int arg_lines = ARG_LINES_DEFAULT; -static bool arg_no_tail = false; -static bool arg_quiet = false; -static bool arg_merge = false; -static bool arg_boot = false; -static sd_id128_t arg_boot_id = {}; -static int arg_boot_offset = 0; -static bool arg_dmesg = false; -static bool arg_no_hostname = false; -static const char *arg_cursor = NULL; -static const char *arg_after_cursor = NULL; -static bool arg_show_cursor = false; -static const char *arg_directory = NULL; -static char **arg_file = NULL; -static bool arg_file_stdin = false; -static int arg_priorities = 0xFF; -static const char *arg_verify_key = NULL; -#ifdef HAVE_GCRYPT -static usec_t arg_interval = DEFAULT_FSS_INTERVAL_USEC; -static bool arg_force = false; -#endif -static usec_t arg_since, arg_until; -static bool arg_since_set = false, arg_until_set = false; -static char **arg_syslog_identifier = NULL; -static char **arg_system_units = NULL; -static char **arg_user_units = NULL; -static const char *arg_field = NULL; -static bool arg_catalog = false; -static bool arg_reverse = false; -static int arg_journal_type = 0; -static char *arg_root = NULL; -static const char *arg_machine = NULL; -static uint64_t arg_vacuum_size = 0; -static uint64_t arg_vacuum_n_files = 0; -static usec_t arg_vacuum_time = 0; - -static enum { - ACTION_SHOW, - ACTION_NEW_ID128, - ACTION_PRINT_HEADER, - ACTION_SETUP_KEYS, - ACTION_VERIFY, - ACTION_DISK_USAGE, - ACTION_LIST_CATALOG, - ACTION_DUMP_CATALOG, - ACTION_UPDATE_CATALOG, - ACTION_LIST_BOOTS, - ACTION_FLUSH, - ACTION_SYNC, - ACTION_ROTATE, - ACTION_VACUUM, - ACTION_LIST_FIELDS, - ACTION_LIST_FIELD_NAMES, -} arg_action = ACTION_SHOW; - -typedef struct BootId { - sd_id128_t id; - uint64_t first; - uint64_t last; - LIST_FIELDS(struct BootId, boot_list); -} BootId; - -static int add_matches_for_device(sd_journal *j, const char *devpath) { - int r; - _cleanup_udev_unref_ struct udev *udev = NULL; - _cleanup_udev_device_unref_ struct udev_device *device = NULL; - struct udev_device *d = NULL; - struct stat st; - - assert(j); - assert(devpath); - - if (!path_startswith(devpath, "/dev/")) { - log_error("Devpath does not start with /dev/"); - return -EINVAL; - } - - udev = udev_new(); - if (!udev) - return log_oom(); - - r = stat(devpath, &st); - if (r < 0) - log_error_errno(errno, "Couldn't stat file: %m"); - - d = device = udev_device_new_from_devnum(udev, S_ISBLK(st.st_mode) ? 'b' : 'c', st.st_rdev); - if (!device) - return log_error_errno(errno, "Failed to get udev device from devnum %u:%u: %m", major(st.st_rdev), minor(st.st_rdev)); - - while (d) { - _cleanup_free_ char *match = NULL; - const char *subsys, *sysname, *devnode; - - subsys = udev_device_get_subsystem(d); - if (!subsys) { - d = udev_device_get_parent(d); - continue; - } - - sysname = udev_device_get_sysname(d); - if (!sysname) { - d = udev_device_get_parent(d); - continue; - } - - match = strjoin("_KERNEL_DEVICE=+", subsys, ":", sysname, NULL); - if (!match) - return log_oom(); - - r = sd_journal_add_match(j, match, 0); - if (r < 0) - return log_error_errno(r, "Failed to add match: %m"); - - devnode = udev_device_get_devnode(d); - if (devnode) { - _cleanup_free_ char *match1 = NULL; - - r = stat(devnode, &st); - if (r < 0) - return log_error_errno(r, "Failed to stat() device node \"%s\": %m", devnode); - - r = asprintf(&match1, "_KERNEL_DEVICE=%c%u:%u", S_ISBLK(st.st_mode) ? 'b' : 'c', major(st.st_rdev), minor(st.st_rdev)); - if (r < 0) - return log_oom(); - - r = sd_journal_add_match(j, match1, 0); - if (r < 0) - return log_error_errno(r, "Failed to add match: %m"); - } - - d = udev_device_get_parent(d); - } - - r = add_match_this_boot(j, arg_machine); - if (r < 0) - return log_error_errno(r, "Failed to add match for the current boot: %m"); - - return 0; -} - -static char *format_timestamp_maybe_utc(char *buf, size_t l, usec_t t) { - - if (arg_utc) - return format_timestamp_utc(buf, l, t); - - return format_timestamp(buf, l, t); -} - -static int parse_boot_descriptor(const char *x, sd_id128_t *boot_id, int *offset) { - sd_id128_t id = SD_ID128_NULL; - int off = 0, r; - - if (strlen(x) >= 32) { - char *t; - - t = strndupa(x, 32); - r = sd_id128_from_string(t, &id); - if (r >= 0) - x += 32; - - if (*x != '-' && *x != '+' && *x != 0) - return -EINVAL; - - if (*x != 0) { - r = safe_atoi(x, &off); - if (r < 0) - return r; - } - } else { - r = safe_atoi(x, &off); - if (r < 0) - return r; - } - - if (boot_id) - *boot_id = id; - - if (offset) - *offset = off; - - return 0; -} - -static void help(void) { - - pager_open(arg_no_pager, arg_pager_end); - - printf("%s [OPTIONS...] [MATCHES...]\n\n" - "Query the journal.\n\n" - "Options:\n" - " --system Show the system journal\n" - " --user Show the user journal for the current user\n" - " -M --machine=CONTAINER Operate on local container\n" - " -S --since=DATE Show entries not older than the specified date\n" - " -U --until=DATE Show entries not newer than the specified date\n" - " -c --cursor=CURSOR Show entries starting at the specified cursor\n" - " --after-cursor=CURSOR Show entries after the specified cursor\n" - " --show-cursor Print the cursor after all the entries\n" - " -b --boot[=ID] Show current boot or the specified boot\n" - " --list-boots Show terse information about recorded boots\n" - " -k --dmesg Show kernel message log from the current boot\n" - " -u --unit=UNIT Show logs from the specified unit\n" - " --user-unit=UNIT Show logs from the specified user unit\n" - " -t --identifier=STRING Show entries with the specified syslog identifier\n" - " -p --priority=RANGE Show entries with the specified priority\n" - " -e --pager-end Immediately jump to the end in the pager\n" - " -f --follow Follow the journal\n" - " -n --lines[=INTEGER] Number of journal entries to show\n" - " --no-tail Show all lines, even in follow mode\n" - " -r --reverse Show the newest entries first\n" - " -o --output=STRING Change journal output mode (short, short-iso,\n" - " short-precise, short-monotonic, verbose,\n" - " export, json, json-pretty, json-sse, cat)\n" - " --utc Express time in Coordinated Universal Time (UTC)\n" - " -x --catalog Add message explanations where available\n" - " --no-full Ellipsize fields\n" - " -a --all Show all fields, including long and unprintable\n" - " -q --quiet Do not show info messages and privilege warning\n" - " --no-pager Do not pipe output into a pager\n" - " --no-hostname Suppress output of hostname field\n" - " -m --merge Show entries from all available journals\n" - " -D --directory=PATH Show journal files from directory\n" - " --file=PATH Show journal file\n" - " --root=ROOT Operate on catalog files below a root directory\n" -#ifdef HAVE_GCRYPT - " --interval=TIME Time interval for changing the FSS sealing key\n" - " --verify-key=KEY Specify FSS verification key\n" - " --force Override of the FSS key pair with --setup-keys\n" -#endif - "\nCommands:\n" - " -h --help Show this help text\n" - " --version Show package version\n" - " -N --fields List all field names currently used\n" - " -F --field=FIELD List all values that a specified field takes\n" - " --disk-usage Show total disk usage of all journal files\n" - " --vacuum-size=BYTES Reduce disk usage below specified size\n" - " --vacuum-files=INT Leave only the specified number of journal files\n" - " --vacuum-time=TIME Remove journal files older than specified time\n" - " --verify Verify journal file consistency\n" - " --sync Synchronize unwritten journal messages to disk\n" - " --flush Flush all journal data from /run into /var\n" - " --rotate Request immediate rotation of the journal files\n" - " --header Show journal header information\n" - " --list-catalog Show all message IDs in the catalog\n" - " --dump-catalog Show entries in the message catalog\n" - " --update-catalog Update the message catalog database\n" - " --new-id128 Generate a new 128-bit ID\n" -#ifdef HAVE_GCRYPT - " --setup-keys Generate a new FSS key pair\n" -#endif - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_FULL, - ARG_NO_TAIL, - ARG_NEW_ID128, - ARG_LIST_BOOTS, - ARG_USER, - ARG_SYSTEM, - ARG_ROOT, - ARG_HEADER, - ARG_SETUP_KEYS, - ARG_FILE, - ARG_INTERVAL, - ARG_VERIFY, - ARG_VERIFY_KEY, - ARG_DISK_USAGE, - ARG_AFTER_CURSOR, - ARG_SHOW_CURSOR, - ARG_USER_UNIT, - ARG_LIST_CATALOG, - ARG_DUMP_CATALOG, - ARG_UPDATE_CATALOG, - ARG_FORCE, - ARG_UTC, - ARG_SYNC, - ARG_FLUSH, - ARG_ROTATE, - ARG_VACUUM_SIZE, - ARG_VACUUM_FILES, - ARG_VACUUM_TIME, - ARG_NO_HOSTNAME, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version" , no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "pager-end", no_argument, NULL, 'e' }, - { "follow", no_argument, NULL, 'f' }, - { "force", no_argument, NULL, ARG_FORCE }, - { "output", required_argument, NULL, 'o' }, - { "all", no_argument, NULL, 'a' }, - { "full", no_argument, NULL, 'l' }, - { "no-full", no_argument, NULL, ARG_NO_FULL }, - { "lines", optional_argument, NULL, 'n' }, - { "no-tail", no_argument, NULL, ARG_NO_TAIL }, - { "new-id128", no_argument, NULL, ARG_NEW_ID128 }, - { "quiet", no_argument, NULL, 'q' }, - { "merge", no_argument, NULL, 'm' }, - { "boot", optional_argument, NULL, 'b' }, - { "list-boots", no_argument, NULL, ARG_LIST_BOOTS }, - { "this-boot", optional_argument, NULL, 'b' }, /* deprecated */ - { "dmesg", no_argument, NULL, 'k' }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "directory", required_argument, NULL, 'D' }, - { "file", required_argument, NULL, ARG_FILE }, - { "root", required_argument, NULL, ARG_ROOT }, - { "header", no_argument, NULL, ARG_HEADER }, - { "identifier", required_argument, NULL, 't' }, - { "priority", required_argument, NULL, 'p' }, - { "setup-keys", no_argument, NULL, ARG_SETUP_KEYS }, - { "interval", required_argument, NULL, ARG_INTERVAL }, - { "verify", no_argument, NULL, ARG_VERIFY }, - { "verify-key", required_argument, NULL, ARG_VERIFY_KEY }, - { "disk-usage", no_argument, NULL, ARG_DISK_USAGE }, - { "cursor", required_argument, NULL, 'c' }, - { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR }, - { "show-cursor", no_argument, NULL, ARG_SHOW_CURSOR }, - { "since", required_argument, NULL, 'S' }, - { "until", required_argument, NULL, 'U' }, - { "unit", required_argument, NULL, 'u' }, - { "user-unit", required_argument, NULL, ARG_USER_UNIT }, - { "field", required_argument, NULL, 'F' }, - { "fields", no_argument, NULL, 'N' }, - { "catalog", no_argument, NULL, 'x' }, - { "list-catalog", no_argument, NULL, ARG_LIST_CATALOG }, - { "dump-catalog", no_argument, NULL, ARG_DUMP_CATALOG }, - { "update-catalog", no_argument, NULL, ARG_UPDATE_CATALOG }, - { "reverse", no_argument, NULL, 'r' }, - { "machine", required_argument, NULL, 'M' }, - { "utc", no_argument, NULL, ARG_UTC }, - { "flush", no_argument, NULL, ARG_FLUSH }, - { "sync", no_argument, NULL, ARG_SYNC }, - { "rotate", no_argument, NULL, ARG_ROTATE }, - { "vacuum-size", required_argument, NULL, ARG_VACUUM_SIZE }, - { "vacuum-files", required_argument, NULL, ARG_VACUUM_FILES }, - { "vacuum-time", required_argument, NULL, ARG_VACUUM_TIME }, - { "no-hostname", no_argument, NULL, ARG_NO_HOSTNAME }, - {} - }; - - int c, r; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:c:S:U:t:u:NF:xrM:", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case 'e': - arg_pager_end = true; - - if (arg_lines == ARG_LINES_DEFAULT) - arg_lines = 1000; - - break; - - case 'f': - arg_follow = true; - break; - - case 'o': - arg_output = output_mode_from_string(optarg); - if (arg_output < 0) { - log_error("Unknown output format '%s'.", optarg); - return -EINVAL; - } - - if (arg_output == OUTPUT_EXPORT || - arg_output == OUTPUT_JSON || - arg_output == OUTPUT_JSON_PRETTY || - arg_output == OUTPUT_JSON_SSE || - arg_output == OUTPUT_CAT) - arg_quiet = true; - - break; - - case 'l': - arg_full = true; - break; - - case ARG_NO_FULL: - arg_full = false; - break; - - case 'a': - arg_all = true; - break; - - case 'n': - if (optarg) { - if (streq(optarg, "all")) - arg_lines = ARG_LINES_ALL; - else { - r = safe_atoi(optarg, &arg_lines); - if (r < 0 || arg_lines < 0) { - log_error("Failed to parse lines '%s'", optarg); - return -EINVAL; - } - } - } else { - arg_lines = 10; - - /* Hmm, no argument? Maybe the next - * word on the command line is - * supposed to be the argument? Let's - * see if there is one, and is - * parsable. */ - if (optind < argc) { - int n; - if (streq(argv[optind], "all")) { - arg_lines = ARG_LINES_ALL; - optind++; - } else if (safe_atoi(argv[optind], &n) >= 0 && n >= 0) { - arg_lines = n; - optind++; - } - } - } - - break; - - case ARG_NO_TAIL: - arg_no_tail = true; - break; - - case ARG_NEW_ID128: - arg_action = ACTION_NEW_ID128; - break; - - case 'q': - arg_quiet = true; - break; - - case 'm': - arg_merge = true; - break; - - case 'b': - arg_boot = true; - - if (optarg) { - r = parse_boot_descriptor(optarg, &arg_boot_id, &arg_boot_offset); - if (r < 0) { - log_error("Failed to parse boot descriptor '%s'", optarg); - return -EINVAL; - } - } else { - - /* Hmm, no argument? Maybe the next - * word on the command line is - * supposed to be the argument? Let's - * see if there is one and is parsable - * as a boot descriptor... */ - - if (optind < argc && - parse_boot_descriptor(argv[optind], &arg_boot_id, &arg_boot_offset) >= 0) - optind++; - } - - break; - - case ARG_LIST_BOOTS: - arg_action = ACTION_LIST_BOOTS; - break; - - case 'k': - arg_boot = arg_dmesg = true; - break; - - case ARG_SYSTEM: - arg_journal_type |= SD_JOURNAL_SYSTEM; - break; - - case ARG_USER: - arg_journal_type |= SD_JOURNAL_CURRENT_USER; - break; - - case 'M': - arg_machine = optarg; - break; - - case 'D': - arg_directory = optarg; - break; - - case ARG_FILE: - if (streq(optarg, "-")) - /* An undocumented feature: we can read journal files from STDIN. We don't document - * this though, since after all we only support this for mmap-able, seekable files, and - * not for example pipes which are probably the primary usecase for reading things from - * STDIN. To avoid confusion we hence don't document this feature. */ - arg_file_stdin = true; - else { - r = glob_extend(&arg_file, optarg); - if (r < 0) - return log_error_errno(r, "Failed to add paths: %m"); - } - break; - - case ARG_ROOT: - r = parse_path_argument_and_warn(optarg, true, &arg_root); - if (r < 0) - return r; - break; - - case 'c': - arg_cursor = optarg; - break; - - case ARG_AFTER_CURSOR: - arg_after_cursor = optarg; - break; - - case ARG_SHOW_CURSOR: - arg_show_cursor = true; - break; - - case ARG_HEADER: - arg_action = ACTION_PRINT_HEADER; - break; - - case ARG_VERIFY: - arg_action = ACTION_VERIFY; - break; - - case ARG_DISK_USAGE: - arg_action = ACTION_DISK_USAGE; - break; - - case ARG_VACUUM_SIZE: - r = parse_size(optarg, 1024, &arg_vacuum_size); - if (r < 0) { - log_error("Failed to parse vacuum size: %s", optarg); - return r; - } - - arg_action = ACTION_VACUUM; - break; - - case ARG_VACUUM_FILES: - r = safe_atou64(optarg, &arg_vacuum_n_files); - if (r < 0) { - log_error("Failed to parse vacuum files: %s", optarg); - return r; - } - - arg_action = ACTION_VACUUM; - break; - - case ARG_VACUUM_TIME: - r = parse_sec(optarg, &arg_vacuum_time); - if (r < 0) { - log_error("Failed to parse vacuum time: %s", optarg); - return r; - } - - arg_action = ACTION_VACUUM; - break; - -#ifdef HAVE_GCRYPT - case ARG_FORCE: - arg_force = true; - break; - - case ARG_SETUP_KEYS: - arg_action = ACTION_SETUP_KEYS; - break; - - - case ARG_VERIFY_KEY: - arg_action = ACTION_VERIFY; - arg_verify_key = optarg; - arg_merge = false; - break; - - case ARG_INTERVAL: - r = parse_sec(optarg, &arg_interval); - if (r < 0 || arg_interval <= 0) { - log_error("Failed to parse sealing key change interval: %s", optarg); - return -EINVAL; - } - break; -#else - case ARG_SETUP_KEYS: - case ARG_VERIFY_KEY: - case ARG_INTERVAL: - case ARG_FORCE: - log_error("Forward-secure sealing not available."); - return -EOPNOTSUPP; -#endif - - case 'p': { - const char *dots; - - dots = strstr(optarg, ".."); - if (dots) { - char *a; - int from, to, i; - - /* a range */ - a = strndup(optarg, dots - optarg); - if (!a) - return log_oom(); - - from = log_level_from_string(a); - to = log_level_from_string(dots + 2); - free(a); - - if (from < 0 || to < 0) { - log_error("Failed to parse log level range %s", optarg); - return -EINVAL; - } - - arg_priorities = 0; - - if (from < to) { - for (i = from; i <= to; i++) - arg_priorities |= 1 << i; - } else { - for (i = to; i <= from; i++) - arg_priorities |= 1 << i; - } - - } else { - int p, i; - - p = log_level_from_string(optarg); - if (p < 0) { - log_error("Unknown log level %s", optarg); - return -EINVAL; - } - - arg_priorities = 0; - - for (i = 0; i <= p; i++) - arg_priorities |= 1 << i; - } - - break; - } - - case 'S': - r = parse_timestamp(optarg, &arg_since); - if (r < 0) { - log_error("Failed to parse timestamp: %s", optarg); - return -EINVAL; - } - arg_since_set = true; - break; - - case 'U': - r = parse_timestamp(optarg, &arg_until); - if (r < 0) { - log_error("Failed to parse timestamp: %s", optarg); - return -EINVAL; - } - arg_until_set = true; - break; - - case 't': - r = strv_extend(&arg_syslog_identifier, optarg); - if (r < 0) - return log_oom(); - break; - - case 'u': - r = strv_extend(&arg_system_units, optarg); - if (r < 0) - return log_oom(); - break; - - case ARG_USER_UNIT: - r = strv_extend(&arg_user_units, optarg); - if (r < 0) - return log_oom(); - break; - - case 'F': - arg_action = ACTION_LIST_FIELDS; - arg_field = optarg; - break; - - case 'N': - arg_action = ACTION_LIST_FIELD_NAMES; - break; - - case ARG_NO_HOSTNAME: - arg_no_hostname = true; - break; - - case 'x': - arg_catalog = true; - break; - - case ARG_LIST_CATALOG: - arg_action = ACTION_LIST_CATALOG; - break; - - case ARG_DUMP_CATALOG: - arg_action = ACTION_DUMP_CATALOG; - break; - - case ARG_UPDATE_CATALOG: - arg_action = ACTION_UPDATE_CATALOG; - break; - - case 'r': - arg_reverse = true; - break; - - case ARG_UTC: - arg_utc = true; - break; - - case ARG_FLUSH: - arg_action = ACTION_FLUSH; - break; - - case ARG_ROTATE: - arg_action = ACTION_ROTATE; - break; - - case ARG_SYNC: - arg_action = ACTION_SYNC; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (arg_follow && !arg_no_tail && !arg_since && arg_lines == ARG_LINES_DEFAULT) - arg_lines = 10; - - if (!!arg_directory + !!arg_file + !!arg_machine > 1) { - log_error("Please specify either -D/--directory= or --file= or -M/--machine=, not more than one."); - return -EINVAL; - } - - if (arg_since_set && arg_until_set && arg_since > arg_until) { - log_error("--since= must be before --until=."); - return -EINVAL; - } - - if (!!arg_cursor + !!arg_after_cursor + !!arg_since_set > 1) { - log_error("Please specify only one of --since=, --cursor=, and --after-cursor."); - return -EINVAL; - } - - if (arg_follow && arg_reverse) { - log_error("Please specify either --reverse= or --follow=, not both."); - return -EINVAL; - } - - if (!IN_SET(arg_action, ACTION_SHOW, ACTION_DUMP_CATALOG, ACTION_LIST_CATALOG) && optind < argc) { - log_error("Extraneous arguments starting with '%s'", argv[optind]); - return -EINVAL; - } - - if ((arg_boot || arg_action == ACTION_LIST_BOOTS) && (arg_file || arg_directory || arg_merge)) { - log_error("Using --boot or --list-boots with --file, --directory or --merge is not supported."); - return -EINVAL; - } - - if (!strv_isempty(arg_system_units) && (arg_journal_type == SD_JOURNAL_CURRENT_USER)) { - - /* Specifying --user and --unit= at the same time makes no sense (as the former excludes the user - * journal, but the latter excludes the system journal, thus resulting in empty output). Let's be nice - * to users, and automatically turn --unit= into --user-unit= if combined with --user. */ - r = strv_extend_strv(&arg_user_units, arg_system_units, true); - if (r < 0) - return -ENOMEM; - - arg_system_units = strv_free(arg_system_units); - } - - return 1; -} - -static int generate_new_id128(void) { - sd_id128_t id; - int r; - unsigned i; - - r = sd_id128_randomize(&id); - if (r < 0) - return log_error_errno(r, "Failed to generate ID: %m"); - - printf("As string:\n" - SD_ID128_FORMAT_STR "\n\n" - "As UUID:\n" - "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n\n" - "As macro:\n" - "#define MESSAGE_XYZ SD_ID128_MAKE(", - SD_ID128_FORMAT_VAL(id), - SD_ID128_FORMAT_VAL(id)); - for (i = 0; i < 16; i++) - printf("%02x%s", id.bytes[i], i != 15 ? "," : ""); - fputs(")\n\n", stdout); - - printf("As Python constant:\n" - ">>> import uuid\n" - ">>> MESSAGE_XYZ = uuid.UUID('" SD_ID128_FORMAT_STR "')\n", - SD_ID128_FORMAT_VAL(id)); - - return 0; -} - -static int add_matches(sd_journal *j, char **args) { - char **i; - bool have_term = false; - - assert(j); - - STRV_FOREACH(i, args) { - int r; - - if (streq(*i, "+")) { - if (!have_term) - break; - r = sd_journal_add_disjunction(j); - have_term = false; - - } else if (path_is_absolute(*i)) { - _cleanup_free_ char *p, *t = NULL, *t2 = NULL, *interpreter = NULL; - const char *path; - struct stat st; - - p = canonicalize_file_name(*i); - path = p ?: *i; - - if (lstat(path, &st) < 0) - return log_error_errno(errno, "Couldn't stat file: %m"); - - if (S_ISREG(st.st_mode) && (0111 & st.st_mode)) { - if (executable_is_script(path, &interpreter) > 0) { - _cleanup_free_ char *comm; - - comm = strndup(basename(path), 15); - if (!comm) - return log_oom(); - - t = strappend("_COMM=", comm); - if (!t) - return log_oom(); - - /* Append _EXE only if the interpreter is not a link. - Otherwise, it might be outdated often. */ - if (lstat(interpreter, &st) == 0 && !S_ISLNK(st.st_mode)) { - t2 = strappend("_EXE=", interpreter); - if (!t2) - return log_oom(); - } - } else { - t = strappend("_EXE=", path); - if (!t) - return log_oom(); - } - - r = sd_journal_add_match(j, t, 0); - - if (r >=0 && t2) - r = sd_journal_add_match(j, t2, 0); - - } else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) { - r = add_matches_for_device(j, path); - if (r < 0) - return r; - } else { - log_error("File is neither a device node, nor regular file, nor executable: %s", *i); - return -EINVAL; - } - - have_term = true; - } else { - r = sd_journal_add_match(j, *i, 0); - have_term = true; - } - - if (r < 0) - return log_error_errno(r, "Failed to add match '%s': %m", *i); - } - - if (!strv_isempty(args) && !have_term) { - log_error("\"+\" can only be used between terms"); - return -EINVAL; - } - - return 0; -} - -static void boot_id_free_all(BootId *l) { - - while (l) { - BootId *i = l; - LIST_REMOVE(boot_list, l, i); - free(i); - } -} - -static int discover_next_boot(sd_journal *j, - sd_id128_t previous_boot_id, - bool advance_older, - BootId **ret) { - - _cleanup_free_ BootId *next_boot = NULL; - char match[9+32+1] = "_BOOT_ID="; - sd_id128_t boot_id; - int r; - - assert(j); - assert(ret); - - /* We expect the journal to be on the last position of a boot - * (in relation to the direction we are going), so that the next - * invocation of sd_journal_next/previous will be from a different - * boot. We then collect any information we desire and then jump - * to the last location of the new boot by using a _BOOT_ID match - * coming from the other journal direction. */ - - /* Make sure we aren't restricted by any _BOOT_ID matches, so that - * we can actually advance to a *different* boot. */ - sd_journal_flush_matches(j); - - do { - if (advance_older) - r = sd_journal_previous(j); - else - r = sd_journal_next(j); - if (r < 0) - return r; - else if (r == 0) - return 0; /* End of journal, yay. */ - - r = sd_journal_get_monotonic_usec(j, NULL, &boot_id); - if (r < 0) - return r; - - /* We iterate through this in a loop, until the boot ID differs from the previous one. Note that - * normally, this will only require a single iteration, as we seeked to the last entry of the previous - * boot entry already. However, it might happen that the per-journal-field entry arrays are less - * complete than the main entry array, and hence might reference an entry that's not actually the last - * one of the boot ID as last one. Let's hence use the per-field array is initial seek position to - * speed things up, but let's not trust that it is complete, and hence, manually advance as - * necessary. */ - - } while (sd_id128_equal(boot_id, previous_boot_id)); - - next_boot = new0(BootId, 1); - if (!next_boot) - return -ENOMEM; - - next_boot->id = boot_id; - - r = sd_journal_get_realtime_usec(j, &next_boot->first); - if (r < 0) - return r; - - /* Now seek to the last occurrence of this boot ID. */ - sd_id128_to_string(next_boot->id, match + 9); - r = sd_journal_add_match(j, match, sizeof(match) - 1); - if (r < 0) - return r; - - if (advance_older) - r = sd_journal_seek_head(j); - else - r = sd_journal_seek_tail(j); - if (r < 0) - return r; - - if (advance_older) - r = sd_journal_next(j); - else - r = sd_journal_previous(j); - if (r < 0) - return r; - else if (r == 0) - return -ENODATA; /* This shouldn't happen. We just came from this very boot ID. */ - - r = sd_journal_get_realtime_usec(j, &next_boot->last); - if (r < 0) - return r; - - *ret = next_boot; - next_boot = NULL; - - return 0; -} - -static int get_boots( - sd_journal *j, - BootId **boots, - sd_id128_t *query_ref_boot, - int ref_boot_offset) { - - bool skip_once; - int r, count = 0; - BootId *head = NULL, *tail = NULL; - const bool advance_older = query_ref_boot && ref_boot_offset <= 0; - sd_id128_t previous_boot_id; - - assert(j); - - /* Adjust for the asymmetry that offset 0 is - * the last (and current) boot, while 1 is considered the - * (chronological) first boot in the journal. */ - skip_once = query_ref_boot && sd_id128_is_null(*query_ref_boot) && ref_boot_offset < 0; - - /* Advance to the earliest/latest occurrence of our reference - * boot ID (taking our lookup direction into account), so that - * discover_next_boot() can do its job. - * If no reference is given, the journal head/tail will do, - * they're "virtual" boots after all. */ - if (query_ref_boot && !sd_id128_is_null(*query_ref_boot)) { - char match[9+32+1] = "_BOOT_ID="; - - sd_journal_flush_matches(j); - - sd_id128_to_string(*query_ref_boot, match + 9); - r = sd_journal_add_match(j, match, sizeof(match) - 1); - if (r < 0) - return r; - - if (advance_older) - r = sd_journal_seek_head(j); /* seek to oldest */ - else - r = sd_journal_seek_tail(j); /* seek to newest */ - if (r < 0) - return r; - - if (advance_older) - r = sd_journal_next(j); /* read the oldest entry */ - else - r = sd_journal_previous(j); /* read the most recently added entry */ - if (r < 0) - return r; - else if (r == 0) - goto finish; - else if (ref_boot_offset == 0) { - count = 1; - goto finish; - } - - /* At this point the read pointer is positioned at the oldest/newest occurence of the reference boot - * ID. After flushing the matches, one more invocation of _previous()/_next() will hence place us at - * the following entry, which must then have an older/newer boot ID */ - } else { - - if (advance_older) - r = sd_journal_seek_tail(j); /* seek to newest */ - else - r = sd_journal_seek_head(j); /* seek to oldest */ - if (r < 0) - return r; - - /* No sd_journal_next()/_previous() here. - * - * At this point the read pointer is positioned after the newest/before the oldest entry in the whole - * journal. The next invocation of _previous()/_next() will hence position us at the newest/oldest - * entry we have. */ - } - - previous_boot_id = SD_ID128_NULL; - for (;;) { - _cleanup_free_ BootId *current = NULL; - - r = discover_next_boot(j, previous_boot_id, advance_older, ¤t); - if (r < 0) { - boot_id_free_all(head); - return r; - } - - if (!current) - break; - - previous_boot_id = current->id; - - if (query_ref_boot) { - if (!skip_once) - ref_boot_offset += advance_older ? 1 : -1; - skip_once = false; - - if (ref_boot_offset == 0) { - count = 1; - *query_ref_boot = current->id; - break; - } - } else { - LIST_INSERT_AFTER(boot_list, head, tail, current); - tail = current; - current = NULL; - count++; - } - } - -finish: - if (boots) - *boots = head; - - sd_journal_flush_matches(j); - - return count; -} - -static int list_boots(sd_journal *j) { - int w, i, count; - BootId *id, *all_ids; - - assert(j); - - count = get_boots(j, &all_ids, NULL, 0); - if (count < 0) - return log_error_errno(count, "Failed to determine boots: %m"); - if (count == 0) - return count; - - pager_open(arg_no_pager, arg_pager_end); - - /* numbers are one less, but we need an extra char for the sign */ - w = DECIMAL_STR_WIDTH(count - 1) + 1; - - i = 0; - LIST_FOREACH(boot_list, id, all_ids) { - char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX]; - - printf("% *i " SD_ID128_FORMAT_STR " %s—%s\n", - w, i - count + 1, - SD_ID128_FORMAT_VAL(id->id), - format_timestamp_maybe_utc(a, sizeof(a), id->first), - format_timestamp_maybe_utc(b, sizeof(b), id->last)); - i++; - } - - boot_id_free_all(all_ids); - - return 0; -} - -static int add_boot(sd_journal *j) { - char match[9+32+1] = "_BOOT_ID="; - sd_id128_t ref_boot_id; - int r; - - assert(j); - - if (!arg_boot) - return 0; - - if (arg_boot_offset == 0 && sd_id128_equal(arg_boot_id, SD_ID128_NULL)) - return add_match_this_boot(j, arg_machine); - - ref_boot_id = arg_boot_id; - r = get_boots(j, NULL, &ref_boot_id, arg_boot_offset); - assert(r <= 1); - if (r <= 0) { - const char *reason = (r == 0) ? "No such boot ID in journal" : strerror(-r); - - if (sd_id128_is_null(arg_boot_id)) - log_error("Data from the specified boot (%+i) is not available: %s", - arg_boot_offset, reason); - else - log_error("Data from the specified boot ("SD_ID128_FORMAT_STR") is not available: %s", - SD_ID128_FORMAT_VAL(arg_boot_id), reason); - - return r == 0 ? -ENODATA : r; - } - - sd_id128_to_string(ref_boot_id, match + 9); - - r = sd_journal_add_match(j, match, sizeof(match) - 1); - if (r < 0) - return log_error_errno(r, "Failed to add match: %m"); - - r = sd_journal_add_conjunction(j); - if (r < 0) - return log_error_errno(r, "Failed to add conjunction: %m"); - - return 0; -} - -static int add_dmesg(sd_journal *j) { - int r; - assert(j); - - if (!arg_dmesg) - return 0; - - r = sd_journal_add_match(j, "_TRANSPORT=kernel", strlen("_TRANSPORT=kernel")); - if (r < 0) - return log_error_errno(r, "Failed to add match: %m"); - - r = sd_journal_add_conjunction(j); - if (r < 0) - return log_error_errno(r, "Failed to add conjunction: %m"); - - return 0; -} - -static int get_possible_units( - sd_journal *j, - const char *fields, - char **patterns, - Set **units) { - - _cleanup_set_free_free_ Set *found; - const char *field; - int r; - - found = set_new(&string_hash_ops); - if (!found) - return -ENOMEM; - - NULSTR_FOREACH(field, fields) { - const void *data; - size_t size; - - r = sd_journal_query_unique(j, field); - if (r < 0) - return r; - - SD_JOURNAL_FOREACH_UNIQUE(j, data, size) { - char **pattern, *eq; - size_t prefix; - _cleanup_free_ char *u = NULL; - - eq = memchr(data, '=', size); - if (eq) - prefix = eq - (char*) data + 1; - else - prefix = 0; - - u = strndup((char*) data + prefix, size - prefix); - if (!u) - return -ENOMEM; - - STRV_FOREACH(pattern, patterns) - if (fnmatch(*pattern, u, FNM_NOESCAPE) == 0) { - log_debug("Matched %s with pattern %s=%s", u, field, *pattern); - - r = set_consume(found, u); - u = NULL; - if (r < 0 && r != -EEXIST) - return r; - - break; - } - } - } - - *units = found; - found = NULL; - return 0; -} - -/* This list is supposed to return the superset of unit names - * possibly matched by rules added with add_matches_for_unit... */ -#define SYSTEM_UNITS \ - "_SYSTEMD_UNIT\0" \ - "COREDUMP_UNIT\0" \ - "UNIT\0" \ - "OBJECT_SYSTEMD_UNIT\0" \ - "_SYSTEMD_SLICE\0" - -/* ... and add_matches_for_user_unit */ -#define USER_UNITS \ - "_SYSTEMD_USER_UNIT\0" \ - "USER_UNIT\0" \ - "COREDUMP_USER_UNIT\0" \ - "OBJECT_SYSTEMD_USER_UNIT\0" - -static int add_units(sd_journal *j) { - _cleanup_strv_free_ char **patterns = NULL; - int r, count = 0; - char **i; - - assert(j); - - STRV_FOREACH(i, arg_system_units) { - _cleanup_free_ char *u = NULL; - - r = unit_name_mangle(*i, UNIT_NAME_GLOB, &u); - if (r < 0) - return r; - - if (string_is_glob(u)) { - r = strv_push(&patterns, u); - if (r < 0) - return r; - u = NULL; - } else { - r = add_matches_for_unit(j, u); - if (r < 0) - return r; - r = sd_journal_add_disjunction(j); - if (r < 0) - return r; - count++; - } - } - - if (!strv_isempty(patterns)) { - _cleanup_set_free_free_ Set *units = NULL; - Iterator it; - char *u; - - r = get_possible_units(j, SYSTEM_UNITS, patterns, &units); - if (r < 0) - return r; - - SET_FOREACH(u, units, it) { - r = add_matches_for_unit(j, u); - if (r < 0) - return r; - r = sd_journal_add_disjunction(j); - if (r < 0) - return r; - count++; - } - } - - patterns = strv_free(patterns); - - STRV_FOREACH(i, arg_user_units) { - _cleanup_free_ char *u = NULL; - - r = unit_name_mangle(*i, UNIT_NAME_GLOB, &u); - if (r < 0) - return r; - - if (string_is_glob(u)) { - r = strv_push(&patterns, u); - if (r < 0) - return r; - u = NULL; - } else { - r = add_matches_for_user_unit(j, u, getuid()); - if (r < 0) - return r; - r = sd_journal_add_disjunction(j); - if (r < 0) - return r; - count++; - } - } - - if (!strv_isempty(patterns)) { - _cleanup_set_free_free_ Set *units = NULL; - Iterator it; - char *u; - - r = get_possible_units(j, USER_UNITS, patterns, &units); - if (r < 0) - return r; - - SET_FOREACH(u, units, it) { - r = add_matches_for_user_unit(j, u, getuid()); - if (r < 0) - return r; - r = sd_journal_add_disjunction(j); - if (r < 0) - return r; - count++; - } - } - - /* Complain if the user request matches but nothing whatsoever was - * found, since otherwise everything would be matched. */ - if (!(strv_isempty(arg_system_units) && strv_isempty(arg_user_units)) && count == 0) - return -ENODATA; - - r = sd_journal_add_conjunction(j); - if (r < 0) - return r; - - return 0; -} - -static int add_priorities(sd_journal *j) { - char match[] = "PRIORITY=0"; - int i, r; - assert(j); - - if (arg_priorities == 0xFF) - return 0; - - for (i = LOG_EMERG; i <= LOG_DEBUG; i++) - if (arg_priorities & (1 << i)) { - match[sizeof(match)-2] = '0' + i; - - r = sd_journal_add_match(j, match, strlen(match)); - if (r < 0) - return log_error_errno(r, "Failed to add match: %m"); - } - - r = sd_journal_add_conjunction(j); - if (r < 0) - return log_error_errno(r, "Failed to add conjunction: %m"); - - return 0; -} - - -static int add_syslog_identifier(sd_journal *j) { - int r; - char **i; - - assert(j); - - STRV_FOREACH(i, arg_syslog_identifier) { - char *u; - - u = strjoina("SYSLOG_IDENTIFIER=", *i); - r = sd_journal_add_match(j, u, 0); - if (r < 0) - return r; - r = sd_journal_add_disjunction(j); - if (r < 0) - return r; - } - - r = sd_journal_add_conjunction(j); - if (r < 0) - return r; - - return 0; -} - -static int setup_keys(void) { -#ifdef HAVE_GCRYPT - size_t mpk_size, seed_size, state_size, i; - uint8_t *mpk, *seed, *state; - int fd = -1, r; - sd_id128_t machine, boot; - char *p = NULL, *k = NULL; - struct FSSHeader h; - uint64_t n; - struct stat st; - - r = stat("/var/log/journal", &st); - if (r < 0 && errno != ENOENT && errno != ENOTDIR) - return log_error_errno(errno, "stat(\"%s\") failed: %m", "/var/log/journal"); - - if (r < 0 || !S_ISDIR(st.st_mode)) { - log_error("%s is not a directory, must be using persistent logging for FSS.", - "/var/log/journal"); - return r < 0 ? -errno : -ENOTDIR; - } - - r = sd_id128_get_machine(&machine); - if (r < 0) - return log_error_errno(r, "Failed to get machine ID: %m"); - - r = sd_id128_get_boot(&boot); - if (r < 0) - return log_error_errno(r, "Failed to get boot ID: %m"); - - if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss", - SD_ID128_FORMAT_VAL(machine)) < 0) - return log_oom(); - - if (arg_force) { - r = unlink(p); - if (r < 0 && errno != ENOENT) { - r = log_error_errno(errno, "unlink(\"%s\") failed: %m", p); - goto finish; - } - } else if (access(p, F_OK) >= 0) { - log_error("Sealing key file %s exists already. Use --force to recreate.", p); - r = -EEXIST; - goto finish; - } - - if (asprintf(&k, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss.tmp.XXXXXX", - SD_ID128_FORMAT_VAL(machine)) < 0) { - r = log_oom(); - goto finish; - } - - mpk_size = FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR); - mpk = alloca(mpk_size); - - seed_size = FSPRG_RECOMMENDED_SEEDLEN; - seed = alloca(seed_size); - - state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR); - state = alloca(state_size); - - fd = open("/dev/random", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) { - r = log_error_errno(errno, "Failed to open /dev/random: %m"); - goto finish; - } - - log_info("Generating seed..."); - r = loop_read_exact(fd, seed, seed_size, true); - if (r < 0) { - log_error_errno(r, "Failed to read random seed: %m"); - goto finish; - } - - log_info("Generating key pair..."); - FSPRG_GenMK(NULL, mpk, seed, seed_size, FSPRG_RECOMMENDED_SECPAR); - - log_info("Generating sealing key..."); - FSPRG_GenState0(state, mpk, seed, seed_size); - - assert(arg_interval > 0); - - n = now(CLOCK_REALTIME); - n /= arg_interval; - - safe_close(fd); - fd = mkostemp_safe(k, O_WRONLY|O_CLOEXEC); - if (fd < 0) { - r = log_error_errno(fd, "Failed to open %s: %m", k); - goto finish; - } - - /* Enable secure remove, exclusion from dump, synchronous - * writing and in-place updating */ - r = chattr_fd(fd, FS_SECRM_FL|FS_NODUMP_FL|FS_SYNC_FL|FS_NOCOW_FL, FS_SECRM_FL|FS_NODUMP_FL|FS_SYNC_FL|FS_NOCOW_FL); - if (r < 0) - log_warning_errno(r, "Failed to set file attributes: %m"); - - zero(h); - memcpy(h.signature, "KSHHRHLP", 8); - h.machine_id = machine; - h.boot_id = boot; - h.header_size = htole64(sizeof(h)); - h.start_usec = htole64(n * arg_interval); - h.interval_usec = htole64(arg_interval); - h.fsprg_secpar = htole16(FSPRG_RECOMMENDED_SECPAR); - h.fsprg_state_size = htole64(state_size); - - r = loop_write(fd, &h, sizeof(h), false); - if (r < 0) { - log_error_errno(r, "Failed to write header: %m"); - goto finish; - } - - r = loop_write(fd, state, state_size, false); - if (r < 0) { - log_error_errno(r, "Failed to write state: %m"); - goto finish; - } - - if (link(k, p) < 0) { - r = log_error_errno(errno, "Failed to link file: %m"); - goto finish; - } - - if (on_tty()) { - fprintf(stderr, - "\n" - "The new key pair has been generated. The " ANSI_HIGHLIGHT "secret sealing key" ANSI_NORMAL " has been written to\n" - "the following local file. This key file is automatically updated when the\n" - "sealing key is advanced. It should not be used on multiple hosts.\n" - "\n" - "\t%s\n" - "\n" - "Please write down the following " ANSI_HIGHLIGHT "secret verification key" ANSI_NORMAL ". It should be stored\n" - "at a safe location and should not be saved locally on disk.\n" - "\n\t" ANSI_HIGHLIGHT_RED, p); - fflush(stderr); - } - for (i = 0; i < seed_size; i++) { - if (i > 0 && i % 3 == 0) - putchar('-'); - printf("%02x", ((uint8_t*) seed)[i]); - } - - printf("/%llx-%llx\n", (unsigned long long) n, (unsigned long long) arg_interval); - - if (on_tty()) { - char tsb[FORMAT_TIMESPAN_MAX], *hn; - - fprintf(stderr, - ANSI_NORMAL "\n" - "The sealing key is automatically changed every %s.\n", - format_timespan(tsb, sizeof(tsb), arg_interval, 0)); - - hn = gethostname_malloc(); - - if (hn) { - hostname_cleanup(hn); - fprintf(stderr, "\nThe keys have been generated for host %s/" SD_ID128_FORMAT_STR ".\n", hn, SD_ID128_FORMAT_VAL(machine)); - } else - fprintf(stderr, "\nThe keys have been generated for host " SD_ID128_FORMAT_STR ".\n", SD_ID128_FORMAT_VAL(machine)); - -#ifdef HAVE_QRENCODE - /* If this is not an UTF-8 system don't print any QR codes */ - if (is_locale_utf8()) { - fputs("\nTo transfer the verification key to your phone please scan the QR code below:\n\n", stderr); - print_qr_code(stderr, seed, seed_size, n, arg_interval, hn, machine); - } -#endif - free(hn); - } - - r = 0; - -finish: - safe_close(fd); - - if (k) { - unlink(k); - free(k); - } - - free(p); - - return r; -#else - log_error("Forward-secure sealing not available."); - return -EOPNOTSUPP; -#endif -} - -static int verify(sd_journal *j) { - int r = 0; - Iterator i; - JournalFile *f; - - assert(j); - - log_show_color(true); - - ORDERED_HASHMAP_FOREACH(f, j->files, i) { - int k; - usec_t first = 0, validated = 0, last = 0; - -#ifdef HAVE_GCRYPT - if (!arg_verify_key && JOURNAL_HEADER_SEALED(f->header)) - log_notice("Journal file %s has sealing enabled but verification key has not been passed using --verify-key=.", f->path); -#endif - - k = journal_file_verify(f, arg_verify_key, &first, &validated, &last, true); - if (k == -EINVAL) { - /* If the key was invalid give up right-away. */ - return k; - } else if (k < 0) { - log_warning_errno(k, "FAIL: %s (%m)", f->path); - r = k; - } else { - char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX], c[FORMAT_TIMESPAN_MAX]; - log_info("PASS: %s", f->path); - - if (arg_verify_key && JOURNAL_HEADER_SEALED(f->header)) { - if (validated > 0) { - log_info("=> Validated from %s to %s, final %s entries not sealed.", - format_timestamp_maybe_utc(a, sizeof(a), first), - format_timestamp_maybe_utc(b, sizeof(b), validated), - format_timespan(c, sizeof(c), last > validated ? last - validated : 0, 0)); - } else if (last > 0) - log_info("=> No sealing yet, %s of entries not sealed.", - format_timespan(c, sizeof(c), last - first, 0)); - else - log_info("=> No sealing yet, no entries in file."); - } - } - } - - return r; -} - -static int access_check_var_log_journal(sd_journal *j) { -#ifdef HAVE_ACL - _cleanup_strv_free_ char **g = NULL; - const char* dir; -#endif - int r; - - assert(j); - - if (arg_quiet) - return 0; - - /* If we are root, we should have access, don't warn. */ - if (getuid() == 0) - return 0; - - /* If we are in the 'systemd-journal' group, we should have - * access too. */ - r = in_group("systemd-journal"); - if (r < 0) - return log_error_errno(r, "Failed to check if we are in the 'systemd-journal' group: %m"); - if (r > 0) - return 0; - -#ifdef HAVE_ACL - if (laccess("/run/log/journal", F_OK) >= 0) - dir = "/run/log/journal"; - else - dir = "/var/log/journal"; - - /* If we are in any of the groups listed in the journal ACLs, - * then all is good, too. Let's enumerate all groups from the - * default ACL of the directory, which generally should allow - * access to most journal files too. */ - r = acl_search_groups(dir, &g); - if (r < 0) - return log_error_errno(r, "Failed to search journal ACL: %m"); - if (r > 0) - return 0; - - /* Print a pretty list, if there were ACLs set. */ - if (!strv_isempty(g)) { - _cleanup_free_ char *s = NULL; - - /* Thre are groups in the ACL, let's list them */ - r = strv_extend(&g, "systemd-journal"); - if (r < 0) - return log_oom(); - - strv_sort(g); - strv_uniq(g); - - s = strv_join(g, "', '"); - if (!s) - return log_oom(); - - log_notice("Hint: You are currently not seeing messages from other users and the system.\n" - " Users in groups '%s' can see all messages.\n" - " Pass -q to turn off this notice.", s); - return 1; - } -#endif - - /* If no ACLs were found, print a short version of the message. */ - log_notice("Hint: You are currently not seeing messages from other users and the system.\n" - " Users in the 'systemd-journal' group can see all messages. Pass -q to\n" - " turn off this notice."); - - return 1; -} - -static int access_check(sd_journal *j) { - Iterator it; - void *code; - char *path; - int r = 0; - - assert(j); - - if (hashmap_isempty(j->errors)) { - if (ordered_hashmap_isempty(j->files)) - log_notice("No journal files were found."); - - return 0; - } - - if (hashmap_contains(j->errors, INT_TO_PTR(-EACCES))) { - (void) access_check_var_log_journal(j); - - if (ordered_hashmap_isempty(j->files)) - r = log_error_errno(EACCES, "No journal files were opened due to insufficient permissions."); - } - - HASHMAP_FOREACH_KEY(path, code, j->errors, it) { - int err; - - err = abs(PTR_TO_INT(code)); - - switch (err) { - case EACCES: - continue; - - case ENODATA: - log_warning_errno(err, "Journal file %s is truncated, ignoring file.", path); - break; - - case EPROTONOSUPPORT: - log_warning_errno(err, "Journal file %s uses an unsupported feature, ignoring file.", path); - break; - - case EBADMSG: - log_warning_errno(err, "Journal file %s corrupted, ignoring file.", path); - break; - - default: - log_warning_errno(err, "An error was encountered while opening journal file or directory %s, ignoring file: %m", path); - break; - } - } - - return r; -} - -static int flush_to_var(void) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_close_ int watch_fd = -1; - int r; - - if (arg_machine) { - log_error("--flush is not supported in conjunction with --machine=."); - return -EOPNOTSUPP; - } - - /* Quick exit */ - if (access("/run/systemd/journal/flushed", F_OK) >= 0) - return 0; - - /* OK, let's actually do the full logic, send SIGUSR1 to the - * daemon and set up inotify to wait for the flushed file to appear */ - r = bus_connect_system_systemd(&bus); - if (r < 0) - return log_error_errno(r, "Failed to get D-Bus connection: %m"); - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "KillUnit", - &error, - NULL, - "ssi", "systemd-journald.service", "main", SIGUSR1); - if (r < 0) - return log_error_errno(r, "Failed to kill journal service: %s", bus_error_message(&error, r)); - - mkdir_p("/run/systemd/journal", 0755); - - watch_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); - if (watch_fd < 0) - return log_error_errno(errno, "Failed to create inotify watch: %m"); - - r = inotify_add_watch(watch_fd, "/run/systemd/journal", IN_CREATE|IN_DONT_FOLLOW|IN_ONLYDIR); - if (r < 0) - return log_error_errno(errno, "Failed to watch journal directory: %m"); - - for (;;) { - if (access("/run/systemd/journal/flushed", F_OK) >= 0) - break; - - if (errno != ENOENT) - return log_error_errno(errno, "Failed to check for existence of /run/systemd/journal/flushed: %m"); - - r = fd_wait_for_event(watch_fd, POLLIN, USEC_INFINITY); - if (r < 0) - return log_error_errno(r, "Failed to wait for event: %m"); - - r = flush_fd(watch_fd); - if (r < 0) - return log_error_errno(r, "Failed to flush inotify events: %m"); - } - - return 0; -} - -static int send_signal_and_wait(int sig, const char *watch_path) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_close_ int watch_fd = -1; - usec_t start; - int r; - - if (arg_machine) { - log_error("--sync and --rotate are not supported in conjunction with --machine=."); - return -EOPNOTSUPP; - } - - start = now(CLOCK_MONOTONIC); - - /* This call sends the specified signal to journald, and waits - * for acknowledgment by watching the mtime of the specified - * flag file. This is used to trigger syncing or rotation and - * then wait for the operation to complete. */ - - for (;;) { - usec_t tstamp; - - /* See if a sync happened by now. */ - r = read_timestamp_file(watch_path, &tstamp); - if (r < 0 && r != -ENOENT) - return log_error_errno(errno, "Failed to read %s: %m", watch_path); - if (r >= 0 && tstamp >= start) - return 0; - - /* Let's ask for a sync, but only once. */ - if (!bus) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - - r = bus_connect_system_systemd(&bus); - if (r < 0) - return log_error_errno(r, "Failed to get D-Bus connection: %m"); - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "KillUnit", - &error, - NULL, - "ssi", "systemd-journald.service", "main", sig); - if (r < 0) - return log_error_errno(r, "Failed to kill journal service: %s", bus_error_message(&error, r)); - - continue; - } - - /* Let's install the inotify watch, if we didn't do that yet. */ - if (watch_fd < 0) { - - mkdir_p("/run/systemd/journal", 0755); - - watch_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); - if (watch_fd < 0) - return log_error_errno(errno, "Failed to create inotify watch: %m"); - - r = inotify_add_watch(watch_fd, "/run/systemd/journal", IN_MOVED_TO|IN_DONT_FOLLOW|IN_ONLYDIR); - if (r < 0) - return log_error_errno(errno, "Failed to watch journal directory: %m"); - - /* Recheck the flag file immediately, so that we don't miss any event since the last check. */ - continue; - } - - /* OK, all preparatory steps done, let's wait until - * inotify reports an event. */ - - r = fd_wait_for_event(watch_fd, POLLIN, USEC_INFINITY); - if (r < 0) - return log_error_errno(r, "Failed to wait for event: %m"); - - r = flush_fd(watch_fd); - if (r < 0) - return log_error_errno(r, "Failed to flush inotify events: %m"); - } - - return 0; -} - -static int rotate(void) { - return send_signal_and_wait(SIGUSR2, "/run/systemd/journal/rotated"); -} - -static int sync_journal(void) { - return send_signal_and_wait(SIGRTMIN+1, "/run/systemd/journal/synced"); -} - -int main(int argc, char *argv[]) { - int r; - _cleanup_(sd_journal_closep) sd_journal *j = NULL; - bool need_seek = false; - sd_id128_t previous_boot_id; - bool previous_boot_id_valid = false, first_line = true; - int n_shown = 0; - bool ellipsized = false; - - setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - signal(SIGWINCH, columns_lines_cache_reset); - sigbus_install(); - - /* Increase max number of open files to 16K if we can, we - * might needs this when browsing journal files, which might - * be split up into many files. */ - setrlimit_closest(RLIMIT_NOFILE, &RLIMIT_MAKE_CONST(16384)); - - switch (arg_action) { - - case ACTION_NEW_ID128: - r = generate_new_id128(); - goto finish; - - case ACTION_SETUP_KEYS: - r = setup_keys(); - goto finish; - - case ACTION_LIST_CATALOG: - case ACTION_DUMP_CATALOG: - case ACTION_UPDATE_CATALOG: { - _cleanup_free_ char *database; - - database = path_join(arg_root, CATALOG_DATABASE, NULL); - if (!database) { - r = log_oom(); - goto finish; - } - - if (arg_action == ACTION_UPDATE_CATALOG) { - r = catalog_update(database, arg_root, catalog_file_dirs); - if (r < 0) - log_error_errno(r, "Failed to list catalog: %m"); - } else { - bool oneline = arg_action == ACTION_LIST_CATALOG; - - pager_open(arg_no_pager, arg_pager_end); - - if (optind < argc) - r = catalog_list_items(stdout, database, oneline, argv + optind); - else - r = catalog_list(stdout, database, oneline); - if (r < 0) - log_error_errno(r, "Failed to list catalog: %m"); - } - - goto finish; - } - - case ACTION_FLUSH: - r = flush_to_var(); - goto finish; - - case ACTION_SYNC: - r = sync_journal(); - goto finish; - - case ACTION_ROTATE: - r = rotate(); - goto finish; - - case ACTION_SHOW: - case ACTION_PRINT_HEADER: - case ACTION_VERIFY: - case ACTION_DISK_USAGE: - case ACTION_LIST_BOOTS: - case ACTION_VACUUM: - case ACTION_LIST_FIELDS: - case ACTION_LIST_FIELD_NAMES: - /* These ones require access to the journal files, continue below. */ - break; - - default: - assert_not_reached("Unknown action"); - } - - if (arg_directory) - r = sd_journal_open_directory(&j, arg_directory, arg_journal_type); - else if (arg_file_stdin) { - int ifd = STDIN_FILENO; - r = sd_journal_open_files_fd(&j, &ifd, 1, 0); - } else if (arg_file) - r = sd_journal_open_files(&j, (const char**) arg_file, 0); - else if (arg_machine) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int fd; - - if (geteuid() != 0) { - /* The file descriptor returned by OpenMachineRootDirectory() will be owned by users/groups of - * the container, thus we need root privileges to override them. */ - log_error("Using the --machine= switch requires root privileges."); - r = -EPERM; - goto finish; - } - - r = sd_bus_open_system(&bus); - if (r < 0) { - log_error_errno(r, "Failed to open system bus: %m"); - goto finish; - } - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "OpenMachineRootDirectory", - &error, - &reply, - "s", arg_machine); - if (r < 0) { - log_error_errno(r, "Failed to open root directory: %s", bus_error_message(&error, r)); - goto finish; - } - - r = sd_bus_message_read(reply, "h", &fd); - if (r < 0) { - bus_log_parse_error(r); - goto finish; - } - - fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (fd < 0) { - r = log_error_errno(errno, "Failed to duplicate file descriptor: %m"); - goto finish; - } - - r = sd_journal_open_directory_fd(&j, fd, SD_JOURNAL_OS_ROOT); - if (r < 0) - safe_close(fd); - } else - r = sd_journal_open(&j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type); - if (r < 0) { - log_error_errno(r, "Failed to open %s: %m", arg_directory ?: arg_file ? "files" : "journal"); - goto finish; - } - - r = access_check(j); - if (r < 0) - goto finish; - - switch (arg_action) { - - case ACTION_NEW_ID128: - case ACTION_SETUP_KEYS: - case ACTION_LIST_CATALOG: - case ACTION_DUMP_CATALOG: - case ACTION_UPDATE_CATALOG: - case ACTION_FLUSH: - case ACTION_SYNC: - case ACTION_ROTATE: - assert_not_reached("Unexpected action."); - - case ACTION_PRINT_HEADER: - journal_print_header(j); - r = 0; - goto finish; - - case ACTION_VERIFY: - r = verify(j); - goto finish; - - case ACTION_DISK_USAGE: { - uint64_t bytes = 0; - char sbytes[FORMAT_BYTES_MAX]; - - r = sd_journal_get_usage(j, &bytes); - if (r < 0) - goto finish; - - printf("Archived and active journals take up %s on disk.\n", - format_bytes(sbytes, sizeof(sbytes), bytes)); - goto finish; - } - - case ACTION_LIST_BOOTS: - r = list_boots(j); - goto finish; - - case ACTION_VACUUM: { - Directory *d; - Iterator i; - - HASHMAP_FOREACH(d, j->directories_by_path, i) { - int q; - - if (d->is_root) - continue; - - q = journal_directory_vacuum(d->path, arg_vacuum_size, arg_vacuum_n_files, arg_vacuum_time, NULL, true); - if (q < 0) { - log_error_errno(q, "Failed to vacuum %s: %m", d->path); - r = q; - } - } - - goto finish; - } - - case ACTION_LIST_FIELD_NAMES: { - const char *field; - - SD_JOURNAL_FOREACH_FIELD(j, field) { - printf("%s\n", field); - n_shown++; - } - - r = 0; - goto finish; - } - - case ACTION_SHOW: - case ACTION_LIST_FIELDS: - break; - - default: - assert_not_reached("Unknown action"); - } - - if (arg_boot_offset != 0 && - sd_journal_has_runtime_files(j) > 0 && - sd_journal_has_persistent_files(j) == 0) { - log_info("Specifying boot ID has no effect, no persistent journal was found"); - r = 0; - goto finish; - } - /* add_boot() must be called first! - * It may need to seek the journal to find parent boot IDs. */ - r = add_boot(j); - if (r < 0) - goto finish; - - r = add_dmesg(j); - if (r < 0) - goto finish; - - r = add_units(j); - if (r < 0) { - log_error_errno(r, "Failed to add filter for units: %m"); - goto finish; - } - - r = add_syslog_identifier(j); - if (r < 0) { - log_error_errno(r, "Failed to add filter for syslog identifiers: %m"); - goto finish; - } - - r = add_priorities(j); - if (r < 0) - goto finish; - - r = add_matches(j, argv + optind); - if (r < 0) - goto finish; - - if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) { - _cleanup_free_ char *filter; - - filter = journal_make_match_string(j); - if (!filter) - return log_oom(); - - log_debug("Journal filter: %s", filter); - } - - if (arg_action == ACTION_LIST_FIELDS) { - const void *data; - size_t size; - - assert(arg_field); - - r = sd_journal_set_data_threshold(j, 0); - if (r < 0) { - log_error_errno(r, "Failed to unset data size threshold: %m"); - goto finish; - } - - r = sd_journal_query_unique(j, arg_field); - if (r < 0) { - log_error_errno(r, "Failed to query unique data objects: %m"); - goto finish; - } - - SD_JOURNAL_FOREACH_UNIQUE(j, data, size) { - const void *eq; - - if (arg_lines >= 0 && n_shown >= arg_lines) - break; - - eq = memchr(data, '=', size); - if (eq) - printf("%.*s\n", (int) (size - ((const uint8_t*) eq - (const uint8_t*) data + 1)), (const char*) eq + 1); - else - printf("%.*s\n", (int) size, (const char*) data); - - n_shown++; - } - - r = 0; - goto finish; - } - - /* Opening the fd now means the first sd_journal_wait() will actually wait */ - if (arg_follow) { - r = sd_journal_get_fd(j); - if (r == -EMEDIUMTYPE) { - log_error_errno(r, "The --follow switch is not supported in conjunction with reading from STDIN."); - goto finish; - } - if (r < 0) { - log_error_errno(r, "Failed to get journal fd: %m"); - goto finish; - } - } - - if (arg_cursor || arg_after_cursor) { - r = sd_journal_seek_cursor(j, arg_cursor ?: arg_after_cursor); - if (r < 0) { - log_error_errno(r, "Failed to seek to cursor: %m"); - goto finish; - } - - if (!arg_reverse) - r = sd_journal_next_skip(j, 1 + !!arg_after_cursor); - else - r = sd_journal_previous_skip(j, 1 + !!arg_after_cursor); - - if (arg_after_cursor && r < 2) { - /* We couldn't find the next entry after the cursor. */ - if (arg_follow) - need_seek = true; - else - arg_lines = 0; - } - - } else if (arg_since_set && !arg_reverse) { - r = sd_journal_seek_realtime_usec(j, arg_since); - if (r < 0) { - log_error_errno(r, "Failed to seek to date: %m"); - goto finish; - } - r = sd_journal_next(j); - - } else if (arg_until_set && arg_reverse) { - r = sd_journal_seek_realtime_usec(j, arg_until); - if (r < 0) { - log_error_errno(r, "Failed to seek to date: %m"); - goto finish; - } - r = sd_journal_previous(j); - - } else if (arg_lines >= 0) { - r = sd_journal_seek_tail(j); - if (r < 0) { - log_error_errno(r, "Failed to seek to tail: %m"); - goto finish; - } - - r = sd_journal_previous_skip(j, arg_lines); - - } else if (arg_reverse) { - r = sd_journal_seek_tail(j); - if (r < 0) { - log_error_errno(r, "Failed to seek to tail: %m"); - goto finish; - } - - r = sd_journal_previous(j); - - } else { - r = sd_journal_seek_head(j); - if (r < 0) { - log_error_errno(r, "Failed to seek to head: %m"); - goto finish; - } - - r = sd_journal_next(j); - } - - if (r < 0) { - log_error_errno(r, "Failed to iterate through journal: %m"); - goto finish; - } - if (r == 0) { - if (arg_follow) - need_seek = true; - else { - if (!arg_quiet) - printf("-- No entries --\n"); - goto finish; - } - } - - if (!arg_follow) - pager_open(arg_no_pager, arg_pager_end); - - if (!arg_quiet) { - usec_t start, end; - char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX]; - - r = sd_journal_get_cutoff_realtime_usec(j, &start, &end); - if (r < 0) { - log_error_errno(r, "Failed to get cutoff: %m"); - goto finish; - } - - if (r > 0) { - if (arg_follow) - printf("-- Logs begin at %s. --\n", - format_timestamp_maybe_utc(start_buf, sizeof(start_buf), start)); - else - printf("-- Logs begin at %s, end at %s. --\n", - format_timestamp_maybe_utc(start_buf, sizeof(start_buf), start), - format_timestamp_maybe_utc(end_buf, sizeof(end_buf), end)); - } - } - - for (;;) { - while (arg_lines < 0 || n_shown < arg_lines || (arg_follow && !first_line)) { - int flags; - - if (need_seek) { - if (!arg_reverse) - r = sd_journal_next(j); - else - r = sd_journal_previous(j); - if (r < 0) { - log_error_errno(r, "Failed to iterate through journal: %m"); - goto finish; - } - if (r == 0) - break; - } - - if (arg_until_set && !arg_reverse) { - usec_t usec; - - r = sd_journal_get_realtime_usec(j, &usec); - if (r < 0) { - log_error_errno(r, "Failed to determine timestamp: %m"); - goto finish; - } - if (usec > arg_until) - goto finish; - } - - if (arg_since_set && arg_reverse) { - usec_t usec; - - r = sd_journal_get_realtime_usec(j, &usec); - if (r < 0) { - log_error_errno(r, "Failed to determine timestamp: %m"); - goto finish; - } - if (usec < arg_since) - goto finish; - } - - if (!arg_merge && !arg_quiet) { - sd_id128_t boot_id; - - r = sd_journal_get_monotonic_usec(j, NULL, &boot_id); - if (r >= 0) { - if (previous_boot_id_valid && - !sd_id128_equal(boot_id, previous_boot_id)) - printf("%s-- Reboot --%s\n", - ansi_highlight(), ansi_normal()); - - previous_boot_id = boot_id; - previous_boot_id_valid = true; - } - } - - flags = - arg_all * OUTPUT_SHOW_ALL | - arg_full * OUTPUT_FULL_WIDTH | - colors_enabled() * OUTPUT_COLOR | - arg_catalog * OUTPUT_CATALOG | - arg_utc * OUTPUT_UTC | - arg_no_hostname * OUTPUT_NO_HOSTNAME; - - r = output_journal(stdout, j, arg_output, 0, flags, &ellipsized); - need_seek = true; - if (r == -EADDRNOTAVAIL) - break; - else if (r < 0 || ferror(stdout)) - goto finish; - - n_shown++; - } - - if (!arg_follow) { - if (arg_show_cursor) { - _cleanup_free_ char *cursor = NULL; - - r = sd_journal_get_cursor(j, &cursor); - if (r < 0 && r != -EADDRNOTAVAIL) - log_error_errno(r, "Failed to get cursor: %m"); - else if (r >= 0) - printf("-- cursor: %s\n", cursor); - } - - break; - } - - r = sd_journal_wait(j, (uint64_t) -1); - if (r < 0) { - log_error_errno(r, "Couldn't wait for journal event: %m"); - goto finish; - } - - first_line = false; - } - -finish: - pager_close(); - - strv_free(arg_file); - - strv_free(arg_syslog_identifier); - strv_free(arg_system_units); - strv_free(arg_user_units); - - free(arg_root); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/journal/journald-audit.c b/src/journal/journald-audit.c deleted file mode 100644 index a433c91c54..0000000000 --- a/src/journal/journald-audit.c +++ /dev/null @@ -1,564 +0,0 @@ -/*** - 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 "alloc-util.h" -#include "audit-type.h" -#include "fd-util.h" -#include "hexdecoct.h" -#include "io-util.h" -#include "journald-audit.h" -#include "missing.h" -#include "string-util.h" - -typedef struct MapField { - const char *audit_field; - const char *journal_field; - int (*map)(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov); -} MapField; - -static int map_simple_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) { - _cleanup_free_ char *c = NULL; - size_t l = 0, allocated = 0; - const char *e; - - assert(field); - assert(p); - assert(iov); - assert(n_iov); - - l = strlen(field); - allocated = l + 1; - c = malloc(allocated); - if (!c) - return -ENOMEM; - - memcpy(c, field, l); - for (e = *p; *e != ' ' && *e != 0; e++) { - if (!GREEDY_REALLOC(c, allocated, l+2)) - return -ENOMEM; - - c[l++] = *e; - } - - c[l] = 0; - - if (!GREEDY_REALLOC(*iov, *n_iov_allocated, *n_iov + 1)) - return -ENOMEM; - - (*iov)[*n_iov].iov_base = c; - (*iov)[*n_iov].iov_len = l; - (*n_iov)++; - - *p = e; - c = NULL; - - return 1; -} - -static int map_string_field_internal(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov, bool filter_printable) { - _cleanup_free_ char *c = NULL; - const char *s, *e; - size_t l; - - assert(field); - assert(p); - assert(iov); - assert(n_iov); - - /* The kernel formats string fields in one of two formats. */ - - if (**p == '"') { - /* Normal quoted syntax */ - s = *p + 1; - e = strchr(s, '"'); - if (!e) - return 0; - - l = strlen(field) + (e - s); - c = malloc(l+1); - if (!c) - return -ENOMEM; - - *((char*) mempcpy(stpcpy(c, field), s, e - s)) = 0; - - e += 1; - - } else if (unhexchar(**p) >= 0) { - /* Hexadecimal escaping */ - size_t allocated = 0; - - l = strlen(field); - allocated = l + 2; - c = malloc(allocated); - if (!c) - return -ENOMEM; - - memcpy(c, field, l); - for (e = *p; *e != ' ' && *e != 0; e += 2) { - int a, b; - uint8_t x; - - a = unhexchar(e[0]); - if (a < 0) - return 0; - - b = unhexchar(e[1]); - if (b < 0) - return 0; - - x = ((uint8_t) a << 4 | (uint8_t) b); - - if (filter_printable && x < (uint8_t) ' ') - x = (uint8_t) ' '; - - if (!GREEDY_REALLOC(c, allocated, l+2)) - return -ENOMEM; - - c[l++] = (char) x; - } - - c[l] = 0; - } else - return 0; - - if (!GREEDY_REALLOC(*iov, *n_iov_allocated, *n_iov + 1)) - return -ENOMEM; - - (*iov)[*n_iov].iov_base = c; - (*iov)[*n_iov].iov_len = l; - (*n_iov)++; - - *p = e; - c = NULL; - - return 1; -} - -static int map_string_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) { - return map_string_field_internal(field, p, iov, n_iov_allocated, n_iov, false); -} - -static int map_string_field_printable(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) { - return map_string_field_internal(field, p, iov, n_iov_allocated, n_iov, true); -} - -static int map_generic_field(const char *prefix, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) { - const char *e, *f; - char *c, *t; - int r; - - /* Implements fallback mappings for all fields we don't know */ - - for (e = *p; e < *p + 16; e++) { - - if (*e == 0 || *e == ' ') - return 0; - - if (*e == '=') - break; - - if (!((*e >= 'a' && *e <= 'z') || - (*e >= 'A' && *e <= 'Z') || - (*e >= '0' && *e <= '9') || - *e == '_' || *e == '-')) - return 0; - } - - if (e <= *p || e >= *p + 16) - return 0; - - c = alloca(strlen(prefix) + (e - *p) + 2); - - t = stpcpy(c, prefix); - for (f = *p; f < e; f++) { - char x; - - if (*f >= 'a' && *f <= 'z') - x = (*f - 'a') + 'A'; /* uppercase */ - else if (*f == '-') - x = '_'; /* dashes → underscores */ - else - x = *f; - - *(t++) = x; - } - strcpy(t, "="); - - e++; - - r = map_simple_field(c, &e, iov, n_iov_allocated, n_iov); - if (r < 0) - return r; - - *p = e; - return r; -} - -/* Kernel fields are those occurring in the audit string before - * msg='. All of these fields are trusted, hence carry the "_" prefix. - * We try to translate the fields we know into our native names. The - * other's are generically mapped to _AUDIT_FIELD_XYZ= */ -static const MapField map_fields_kernel[] = { - - /* First, we map certain well-known audit fields into native - * well-known fields */ - { "pid=", "_PID=", map_simple_field }, - { "ppid=", "_PPID=", map_simple_field }, - { "uid=", "_UID=", map_simple_field }, - { "euid=", "_EUID=", map_simple_field }, - { "fsuid=", "_FSUID=", map_simple_field }, - { "gid=", "_GID=", map_simple_field }, - { "egid=", "_EGID=", map_simple_field }, - { "fsgid=", "_FSGID=", map_simple_field }, - { "tty=", "_TTY=", map_simple_field }, - { "ses=", "_AUDIT_SESSION=", map_simple_field }, - { "auid=", "_AUDIT_LOGINUID=", map_simple_field }, - { "subj=", "_SELINUX_CONTEXT=", map_simple_field }, - { "comm=", "_COMM=", map_string_field }, - { "exe=", "_EXE=", map_string_field }, - { "proctitle=", "_CMDLINE=", map_string_field_printable }, - - /* Some fields don't map to native well-known fields. However, - * we know that they are string fields, hence let's undo - * string field escaping for them, though we stick to the - * generic field names. */ - { "path=", "_AUDIT_FIELD_PATH=", map_string_field }, - { "dev=", "_AUDIT_FIELD_DEV=", map_string_field }, - { "name=", "_AUDIT_FIELD_NAME=", map_string_field }, - {} -}; - -/* Userspace fields are those occurring in the audit string after - * msg='. All of these fields are untrusted, hence carry no "_" - * prefix. We map the fields we don't know to AUDIT_FIELD_XYZ= */ -static const MapField map_fields_userspace[] = { - { "cwd=", "AUDIT_FIELD_CWD=", map_string_field }, - { "cmd=", "AUDIT_FIELD_CMD=", map_string_field }, - { "acct=", "AUDIT_FIELD_ACCT=", map_string_field }, - { "exe=", "AUDIT_FIELD_EXE=", map_string_field }, - { "comm=", "AUDIT_FIELD_COMM=", map_string_field }, - {} -}; - -static int map_all_fields( - const char *p, - const MapField map_fields[], - const char *prefix, - bool handle_msg, - struct iovec **iov, - size_t *n_iov_allocated, - unsigned *n_iov) { - - int r; - - assert(p); - assert(iov); - assert(n_iov_allocated); - assert(n_iov); - - for (;;) { - bool mapped = false; - const MapField *m; - const char *v; - - p += strspn(p, WHITESPACE); - - if (*p == 0) - return 0; - - if (handle_msg) { - v = startswith(p, "msg='"); - if (v) { - const char *e; - char *c; - - /* Userspace message. It's enclosed in - simple quotation marks, is not - escaped, but the last field in the - line, hence let's remove the - quotation mark, and apply the - userspace mapping instead of the - kernel mapping. */ - - e = endswith(v, "'"); - if (!e) - return 0; /* don't continue splitting up if the final quotation mark is missing */ - - c = strndupa(v, e - v); - return map_all_fields(c, map_fields_userspace, "AUDIT_FIELD_", false, iov, n_iov_allocated, n_iov); - } - } - - /* Try to map the kernel fields to our own names */ - for (m = map_fields; m->audit_field; m++) { - v = startswith(p, m->audit_field); - if (!v) - continue; - - r = m->map(m->journal_field, &v, iov, n_iov_allocated, n_iov); - if (r < 0) - return log_debug_errno(r, "Failed to parse audit array: %m"); - - if (r > 0) { - mapped = true; - p = v; - break; - } - } - - if (!mapped) { - r = map_generic_field(prefix, &p, iov, n_iov_allocated, n_iov); - if (r < 0) - return log_debug_errno(r, "Failed to parse audit array: %m"); - - if (r == 0) - /* Couldn't process as generic field, let's just skip over it */ - p += strcspn(p, WHITESPACE); - } - } -} - -static void process_audit_string(Server *s, int type, const char *data, size_t size) { - _cleanup_free_ struct iovec *iov = NULL; - size_t n_iov_allocated = 0; - unsigned n_iov = 0, k; - uint64_t seconds, msec, id; - const char *p, *type_name; - unsigned z; - char id_field[sizeof("_AUDIT_ID=") + DECIMAL_STR_MAX(uint64_t)], - type_field[sizeof("_AUDIT_TYPE=") + DECIMAL_STR_MAX(int)], - source_time_field[sizeof("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)]; - char *m; - - assert(s); - - if (size <= 0) - return; - - if (!data) - return; - - /* Note that the input buffer is NUL terminated, but let's - * check whether there is a spurious NUL byte */ - if (memchr(data, 0, size)) - return; - - p = startswith(data, "audit"); - if (!p) - return; - - if (sscanf(p, "(%" PRIu64 ".%" PRIu64 ":%" PRIu64 "):%n", - &seconds, - &msec, - &id, - &k) != 3) - return; - - p += k; - p += strspn(p, WHITESPACE); - - if (isempty(p)) - return; - - n_iov_allocated = N_IOVEC_META_FIELDS + 7; - iov = new(struct iovec, n_iov_allocated); - if (!iov) { - log_oom(); - return; - } - - IOVEC_SET_STRING(iov[n_iov++], "_TRANSPORT=audit"); - - sprintf(source_time_field, "_SOURCE_REALTIME_TIMESTAMP=%" PRIu64, - (usec_t) seconds * USEC_PER_SEC + (usec_t) msec * USEC_PER_MSEC); - IOVEC_SET_STRING(iov[n_iov++], source_time_field); - - sprintf(type_field, "_AUDIT_TYPE=%i", type); - IOVEC_SET_STRING(iov[n_iov++], type_field); - - sprintf(id_field, "_AUDIT_ID=%" PRIu64, id); - IOVEC_SET_STRING(iov[n_iov++], id_field); - - assert_cc(4 == LOG_FAC(LOG_AUTH)); - IOVEC_SET_STRING(iov[n_iov++], "SYSLOG_FACILITY=4"); - IOVEC_SET_STRING(iov[n_iov++], "SYSLOG_IDENTIFIER=audit"); - - type_name = audit_type_name_alloca(type); - - m = strjoina("MESSAGE=", type_name, " ", p); - IOVEC_SET_STRING(iov[n_iov++], m); - - z = n_iov; - - map_all_fields(p, map_fields_kernel, "_AUDIT_FIELD_", true, &iov, &n_iov_allocated, &n_iov); - - if (!GREEDY_REALLOC(iov, n_iov_allocated, n_iov + N_IOVEC_META_FIELDS)) { - log_oom(); - goto finish; - } - - server_dispatch_message(s, iov, n_iov, n_iov_allocated, NULL, NULL, NULL, 0, NULL, LOG_NOTICE, 0); - -finish: - /* free() all entries that map_all_fields() added. All others - * are allocated on the stack or are constant. */ - - for (; z < n_iov; z++) - free(iov[z].iov_base); -} - -void server_process_audit_message( - Server *s, - const void *buffer, - size_t buffer_size, - const struct ucred *ucred, - const union sockaddr_union *sa, - socklen_t salen) { - - const struct nlmsghdr *nl = buffer; - - assert(s); - - if (buffer_size < ALIGN(sizeof(struct nlmsghdr))) - return; - - assert(buffer); - - /* Filter out fake data */ - if (!sa || - salen != sizeof(struct sockaddr_nl) || - sa->nl.nl_family != AF_NETLINK || - sa->nl.nl_pid != 0) { - log_debug("Audit netlink message from invalid sender."); - return; - } - - if (!ucred || ucred->pid != 0) { - log_debug("Audit netlink message with invalid credentials."); - return; - } - - if (!NLMSG_OK(nl, buffer_size)) { - log_error("Audit netlink message truncated."); - return; - } - - /* Ignore special Netlink messages */ - if (IN_SET(nl->nlmsg_type, NLMSG_NOOP, NLMSG_ERROR)) - return; - - /* Below AUDIT_FIRST_USER_MSG theer are only control messages, let's ignore those */ - if (nl->nlmsg_type < AUDIT_FIRST_USER_MSG) - return; - - process_audit_string(s, nl->nlmsg_type, NLMSG_DATA(nl), nl->nlmsg_len - ALIGN(sizeof(struct nlmsghdr))); -} - -static int enable_audit(int fd, bool b) { - struct { - union { - struct nlmsghdr header; - uint8_t header_space[NLMSG_HDRLEN]; - }; - struct audit_status body; - } _packed_ request = { - .header.nlmsg_len = NLMSG_LENGTH(sizeof(struct audit_status)), - .header.nlmsg_type = AUDIT_SET, - .header.nlmsg_flags = NLM_F_REQUEST, - .header.nlmsg_seq = 1, - .header.nlmsg_pid = 0, - .body.mask = AUDIT_STATUS_ENABLED, - .body.enabled = b, - }; - union sockaddr_union sa = { - .nl.nl_family = AF_NETLINK, - .nl.nl_pid = 0, - }; - struct iovec iovec = { - .iov_base = &request, - .iov_len = NLMSG_LENGTH(sizeof(struct audit_status)), - }; - struct msghdr mh = { - .msg_iov = &iovec, - .msg_iovlen = 1, - .msg_name = &sa.sa, - .msg_namelen = sizeof(sa.nl), - }; - - ssize_t n; - - n = sendmsg(fd, &mh, MSG_NOSIGNAL); - if (n < 0) - return -errno; - if (n != NLMSG_LENGTH(sizeof(struct audit_status))) - return -EIO; - - /* We don't wait for the result here, we can't do anything - * about it anyway */ - - return 0; -} - -int server_open_audit(Server *s) { - static const int one = 1; - int r; - - if (s->audit_fd < 0) { - static const union sockaddr_union sa = { - .nl.nl_family = AF_NETLINK, - .nl.nl_pid = 0, - .nl.nl_groups = AUDIT_NLGRP_READLOG, - }; - - s->audit_fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_AUDIT); - if (s->audit_fd < 0) { - if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) - log_debug("Audit not supported in the kernel."); - else - log_warning_errno(errno, "Failed to create audit socket, ignoring: %m"); - - return 0; - } - - if (bind(s->audit_fd, &sa.sa, sizeof(sa.nl)) < 0) { - log_warning_errno(errno, - "Failed to join audit multicast group. " - "The kernel is probably too old or multicast reading is not supported. " - "Ignoring: %m"); - s->audit_fd = safe_close(s->audit_fd); - return 0; - } - } else - fd_nonblock(s->audit_fd, 1); - - r = setsockopt(s->audit_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); - if (r < 0) - return log_error_errno(errno, "Failed to set SO_PASSCRED on audit socket: %m"); - - r = sd_event_add_io(s->event, &s->audit_event_source, s->audit_fd, EPOLLIN, server_process_datagram, s); - if (r < 0) - return log_error_errno(r, "Failed to add audit fd to event loop: %m"); - - /* We are listening now, try to enable audit */ - r = enable_audit(s->audit_fd, true); - if (r < 0) - log_warning_errno(r, "Failed to issue audit enable call: %m"); - - return 0; -} diff --git a/src/journal/journald-audit.h b/src/journal/journald-audit.h deleted file mode 100644 index 8c7457778c..0000000000 --- a/src/journal/journald-audit.h +++ /dev/null @@ -1,27 +0,0 @@ -#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 "journald-server.h" -#include "socket-util.h" - -void server_process_audit_message(Server *s, const void *buffer, size_t buffer_size, const struct ucred *ucred, const union sockaddr_union *sa, socklen_t salen); - -int server_open_audit(Server*s); diff --git a/src/journal/journald-console.c b/src/journal/journald-console.c deleted file mode 100644 index fcc9f25814..0000000000 --- a/src/journal/journald-console.c +++ /dev/null @@ -1,115 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "io-util.h" -#include "journald-console.h" -#include "journald-server.h" -#include "parse-util.h" -#include "process-util.h" -#include "stdio-util.h" -#include "terminal-util.h" - -static bool prefix_timestamp(void) { - - static int cached_printk_time = -1; - - if (_unlikely_(cached_printk_time < 0)) { - _cleanup_free_ char *p = NULL; - - cached_printk_time = - read_one_line_file("/sys/module/printk/parameters/time", &p) >= 0 - && parse_boolean(p) > 0; - } - - return cached_printk_time; -} - -void server_forward_console( - Server *s, - int priority, - const char *identifier, - const char *message, - const struct ucred *ucred) { - - struct iovec iovec[5]; - struct timespec ts; - char tbuf[sizeof("[] ")-1 + DECIMAL_STR_MAX(ts.tv_sec) + DECIMAL_STR_MAX(ts.tv_nsec)-3 + 1]; - char header_pid[sizeof("[]: ")-1 + DECIMAL_STR_MAX(pid_t)]; - int n = 0, fd; - _cleanup_free_ char *ident_buf = NULL; - const char *tty; - - assert(s); - assert(message); - - if (LOG_PRI(priority) > s->max_level_console) - return; - - /* First: timestamp */ - if (prefix_timestamp()) { - assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0); - xsprintf(tbuf, "[%5"PRI_TIME".%06ld] ", - ts.tv_sec, - ts.tv_nsec / 1000); - IOVEC_SET_STRING(iovec[n++], tbuf); - } - - /* Second: identifier and PID */ - if (ucred) { - if (!identifier) { - get_process_comm(ucred->pid, &ident_buf); - identifier = ident_buf; - } - - xsprintf(header_pid, "["PID_FMT"]: ", ucred->pid); - - if (identifier) - IOVEC_SET_STRING(iovec[n++], identifier); - - IOVEC_SET_STRING(iovec[n++], header_pid); - } else if (identifier) { - IOVEC_SET_STRING(iovec[n++], identifier); - IOVEC_SET_STRING(iovec[n++], ": "); - } - - /* Fourth: message */ - IOVEC_SET_STRING(iovec[n++], message); - IOVEC_SET_STRING(iovec[n++], "\n"); - - tty = s->tty_path ? s->tty_path : "/dev/console"; - - fd = open_terminal(tty, O_WRONLY|O_NOCTTY|O_CLOEXEC); - if (fd < 0) { - log_debug_errno(fd, "Failed to open %s for logging: %m", tty); - return; - } - - if (writev(fd, iovec, n) < 0) - log_debug_errno(errno, "Failed to write to %s for logging: %m", tty); - - safe_close(fd); -} diff --git a/src/journal/journald-console.h b/src/journal/journald-console.h deleted file mode 100644 index dda07e2c28..0000000000 --- a/src/journal/journald-console.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 "journald-server.h" - -void server_forward_console(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred); diff --git a/src/journal/journald-gperf.gperf b/src/journal/journald-gperf.gperf deleted file mode 100644 index 7fecd7a964..0000000000 --- a/src/journal/journald-gperf.gperf +++ /dev/null @@ -1,46 +0,0 @@ -%{ -#include -#include -#include "conf-parser.h" -#include "journald-server.h" -%} -struct ConfigPerfItem; -%null_strings -%language=ANSI-C -%define slot-name section_and_lvalue -%define hash-function-name journald_gperf_hash -%define lookup-function-name journald_gperf_lookup -%readonly-tables -%omit-struct-type -%struct-type -%includes -%% -Journal.Storage, config_parse_storage, 0, offsetof(Server, storage) -Journal.Compress, config_parse_bool, 0, offsetof(Server, compress) -Journal.Seal, config_parse_bool, 0, offsetof(Server, seal) -Journal.SyncIntervalSec, config_parse_sec, 0, offsetof(Server, sync_interval_usec) -# The following is a legacy name for compatibility -Journal.RateLimitInterval, config_parse_sec, 0, offsetof(Server, rate_limit_interval) -Journal.RateLimitIntervalSec,config_parse_sec, 0, offsetof(Server, rate_limit_interval) -Journal.RateLimitBurst, config_parse_unsigned, 0, offsetof(Server, rate_limit_burst) -Journal.SystemMaxUse, config_parse_iec_uint64, 0, offsetof(Server, system_metrics.max_use) -Journal.SystemMaxFileSize, config_parse_iec_uint64, 0, offsetof(Server, system_metrics.max_size) -Journal.SystemKeepFree, config_parse_iec_uint64, 0, offsetof(Server, system_metrics.keep_free) -Journal.SystemMaxFiles, config_parse_uint64, 0, offsetof(Server, system_metrics.n_max_files) -Journal.RuntimeMaxUse, config_parse_iec_uint64, 0, offsetof(Server, runtime_metrics.max_use) -Journal.RuntimeMaxFileSize, config_parse_iec_uint64, 0, offsetof(Server, runtime_metrics.max_size) -Journal.RuntimeKeepFree, config_parse_iec_uint64, 0, offsetof(Server, runtime_metrics.keep_free) -Journal.RuntimeMaxFiles, config_parse_uint64, 0, offsetof(Server, runtime_metrics.n_max_files) -Journal.MaxRetentionSec, config_parse_sec, 0, offsetof(Server, max_retention_usec) -Journal.MaxFileSec, config_parse_sec, 0, offsetof(Server, max_file_usec) -Journal.ForwardToSyslog, config_parse_bool, 0, offsetof(Server, forward_to_syslog) -Journal.ForwardToKMsg, config_parse_bool, 0, offsetof(Server, forward_to_kmsg) -Journal.ForwardToConsole, config_parse_bool, 0, offsetof(Server, forward_to_console) -Journal.ForwardToWall, config_parse_bool, 0, offsetof(Server, forward_to_wall) -Journal.TTYPath, config_parse_path, 0, offsetof(Server, tty_path) -Journal.MaxLevelStore, config_parse_log_level, 0, offsetof(Server, max_level_store) -Journal.MaxLevelSyslog, config_parse_log_level, 0, offsetof(Server, max_level_syslog) -Journal.MaxLevelKMsg, config_parse_log_level, 0, offsetof(Server, max_level_kmsg) -Journal.MaxLevelConsole, config_parse_log_level, 0, offsetof(Server, max_level_console) -Journal.MaxLevelWall, config_parse_log_level, 0, offsetof(Server, max_level_wall) -Journal.SplitMode, config_parse_split_mode, 0, offsetof(Server, split_mode) diff --git a/src/journal/journald-kmsg.c b/src/journal/journald-kmsg.c deleted file mode 100644 index f64abdd431..0000000000 --- a/src/journal/journald-kmsg.c +++ /dev/null @@ -1,473 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include -#include -#include - -#include "libudev.h" -#include "sd-messages.h" - -#include "escape.h" -#include "fd-util.h" -#include "formats-util.h" -#include "io-util.h" -#include "journald-kmsg.h" -#include "journald-server.h" -#include "journald-syslog.h" -#include "parse-util.h" -#include "process-util.h" -#include "stdio-util.h" -#include "string-util.h" - -void server_forward_kmsg( - Server *s, - int priority, - const char *identifier, - const char *message, - const struct ucred *ucred) { - - struct iovec iovec[5]; - char header_priority[DECIMAL_STR_MAX(priority) + 3], - header_pid[sizeof("[]: ")-1 + DECIMAL_STR_MAX(pid_t) + 1]; - int n = 0; - char *ident_buf = NULL; - - assert(s); - assert(priority >= 0); - assert(priority <= 999); - assert(message); - - if (_unlikely_(LOG_PRI(priority) > s->max_level_kmsg)) - return; - - if (_unlikely_(s->dev_kmsg_fd < 0)) - return; - - /* Never allow messages with kernel facility to be written to - * kmsg, regardless where the data comes from. */ - priority = syslog_fixup_facility(priority); - - /* First: priority field */ - xsprintf(header_priority, "<%i>", priority); - IOVEC_SET_STRING(iovec[n++], header_priority); - - /* Second: identifier and PID */ - if (ucred) { - if (!identifier) { - get_process_comm(ucred->pid, &ident_buf); - identifier = ident_buf; - } - - xsprintf(header_pid, "["PID_FMT"]: ", ucred->pid); - - if (identifier) - IOVEC_SET_STRING(iovec[n++], identifier); - - IOVEC_SET_STRING(iovec[n++], header_pid); - } else if (identifier) { - IOVEC_SET_STRING(iovec[n++], identifier); - IOVEC_SET_STRING(iovec[n++], ": "); - } - - /* Fourth: message */ - IOVEC_SET_STRING(iovec[n++], message); - IOVEC_SET_STRING(iovec[n++], "\n"); - - if (writev(s->dev_kmsg_fd, iovec, n) < 0) - log_debug_errno(errno, "Failed to write to /dev/kmsg for logging: %m"); - - free(ident_buf); -} - -static bool is_us(const char *pid) { - pid_t t; - - assert(pid); - - if (parse_pid(pid, &t) < 0) - return false; - - return t == getpid(); -} - -static void dev_kmsg_record(Server *s, const char *p, size_t l) { - struct iovec iovec[N_IOVEC_META_FIELDS + 7 + N_IOVEC_KERNEL_FIELDS + 2 + N_IOVEC_UDEV_FIELDS]; - char *message = NULL, *syslog_priority = NULL, *syslog_pid = NULL, *syslog_facility = NULL, *syslog_identifier = NULL, *source_time = NULL; - int priority, r; - unsigned n = 0, z = 0, j; - unsigned long long usec; - char *identifier = NULL, *pid = NULL, *e, *f, *k; - uint64_t serial; - size_t pl; - char *kernel_device = NULL; - - assert(s); - assert(p); - - if (l <= 0) - return; - - e = memchr(p, ',', l); - if (!e) - return; - *e = 0; - - r = safe_atoi(p, &priority); - if (r < 0 || priority < 0 || priority > 999) - return; - - if (s->forward_to_kmsg && (priority & LOG_FACMASK) != LOG_KERN) - return; - - l -= (e - p) + 1; - p = e + 1; - e = memchr(p, ',', l); - if (!e) - return; - *e = 0; - - r = safe_atou64(p, &serial); - if (r < 0) - return; - - if (s->kernel_seqnum) { - /* We already read this one? */ - if (serial < *s->kernel_seqnum) - return; - - /* Did we lose any? */ - if (serial > *s->kernel_seqnum) - server_driver_message(s, SD_MESSAGE_JOURNAL_MISSED, - LOG_MESSAGE("Missed %"PRIu64" kernel messages", - serial - *s->kernel_seqnum), - NULL); - - /* Make sure we never read this one again. Note that - * we always store the next message serial we expect - * here, simply because this makes handling the first - * message with serial 0 easy. */ - *s->kernel_seqnum = serial + 1; - } - - l -= (e - p) + 1; - p = e + 1; - f = memchr(p, ';', l); - if (!f) - return; - /* Kernel 3.6 has the flags field, kernel 3.5 lacks that */ - e = memchr(p, ',', l); - if (!e || f < e) - e = f; - *e = 0; - - r = safe_atollu(p, &usec); - if (r < 0) - return; - - l -= (f - p) + 1; - p = f + 1; - e = memchr(p, '\n', l); - if (!e) - return; - *e = 0; - - pl = e - p; - l -= (e - p) + 1; - k = e + 1; - - for (j = 0; l > 0 && j < N_IOVEC_KERNEL_FIELDS; j++) { - char *m; - /* Metadata fields attached */ - - if (*k != ' ') - break; - - k++, l--; - - e = memchr(k, '\n', l); - if (!e) - return; - - *e = 0; - - if (cunescape_length_with_prefix(k, e - k, "_KERNEL_", UNESCAPE_RELAX, &m) < 0) - break; - - if (startswith(m, "_KERNEL_DEVICE=")) - kernel_device = m + 15; - - IOVEC_SET_STRING(iovec[n++], m); - z++; - - l -= (e - k) + 1; - k = e + 1; - } - - if (kernel_device) { - struct udev_device *ud; - - ud = udev_device_new_from_device_id(s->udev, kernel_device); - if (ud) { - const char *g; - struct udev_list_entry *ll; - char *b; - - g = udev_device_get_devnode(ud); - if (g) { - b = strappend("_UDEV_DEVNODE=", g); - if (b) { - IOVEC_SET_STRING(iovec[n++], b); - z++; - } - } - - g = udev_device_get_sysname(ud); - if (g) { - b = strappend("_UDEV_SYSNAME=", g); - if (b) { - IOVEC_SET_STRING(iovec[n++], b); - z++; - } - } - - j = 0; - ll = udev_device_get_devlinks_list_entry(ud); - udev_list_entry_foreach(ll, ll) { - - if (j > N_IOVEC_UDEV_FIELDS) - break; - - g = udev_list_entry_get_name(ll); - if (g) { - b = strappend("_UDEV_DEVLINK=", g); - if (b) { - IOVEC_SET_STRING(iovec[n++], b); - z++; - } - } - - j++; - } - - udev_device_unref(ud); - } - } - - if (asprintf(&source_time, "_SOURCE_MONOTONIC_TIMESTAMP=%llu", usec) >= 0) - IOVEC_SET_STRING(iovec[n++], source_time); - - IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=kernel"); - - if (asprintf(&syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK) >= 0) - IOVEC_SET_STRING(iovec[n++], syslog_priority); - - if (asprintf(&syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)) >= 0) - IOVEC_SET_STRING(iovec[n++], syslog_facility); - - if ((priority & LOG_FACMASK) == LOG_KERN) - IOVEC_SET_STRING(iovec[n++], "SYSLOG_IDENTIFIER=kernel"); - else { - pl -= syslog_parse_identifier((const char**) &p, &identifier, &pid); - - /* Avoid any messages we generated ourselves via - * log_info() and friends. */ - if (pid && is_us(pid)) - goto finish; - - if (identifier) { - syslog_identifier = strappend("SYSLOG_IDENTIFIER=", identifier); - if (syslog_identifier) - IOVEC_SET_STRING(iovec[n++], syslog_identifier); - } - - if (pid) { - syslog_pid = strappend("SYSLOG_PID=", pid); - if (syslog_pid) - IOVEC_SET_STRING(iovec[n++], syslog_pid); - } - } - - if (cunescape_length_with_prefix(p, pl, "MESSAGE=", UNESCAPE_RELAX, &message) >= 0) - IOVEC_SET_STRING(iovec[n++], message); - - server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), NULL, NULL, NULL, 0, NULL, priority, 0); - -finish: - for (j = 0; j < z; j++) - free(iovec[j].iov_base); - - free(message); - free(syslog_priority); - free(syslog_identifier); - free(syslog_pid); - free(syslog_facility); - free(source_time); - free(identifier); - free(pid); -} - -static int server_read_dev_kmsg(Server *s) { - char buffer[8192+1]; /* the kernel-side limit per record is 8K currently */ - ssize_t l; - - assert(s); - assert(s->dev_kmsg_fd >= 0); - - l = read(s->dev_kmsg_fd, buffer, sizeof(buffer) - 1); - if (l == 0) - return 0; - if (l < 0) { - /* Old kernels who don't allow reading from /dev/kmsg - * return EINVAL when we try. So handle this cleanly, - * but don' try to ever read from it again. */ - if (errno == EINVAL) { - s->dev_kmsg_event_source = sd_event_source_unref(s->dev_kmsg_event_source); - return 0; - } - - if (errno == EAGAIN || errno == EINTR || errno == EPIPE) - return 0; - - return log_error_errno(errno, "Failed to read from kernel: %m"); - } - - dev_kmsg_record(s, buffer, l); - return 1; -} - -int server_flush_dev_kmsg(Server *s) { - int r; - - assert(s); - - if (s->dev_kmsg_fd < 0) - return 0; - - if (!s->dev_kmsg_readable) - return 0; - - log_debug("Flushing /dev/kmsg..."); - - for (;;) { - r = server_read_dev_kmsg(s); - if (r < 0) - return r; - - if (r == 0) - break; - } - - return 0; -} - -static int dispatch_dev_kmsg(sd_event_source *es, int fd, uint32_t revents, void *userdata) { - Server *s = userdata; - - assert(es); - assert(fd == s->dev_kmsg_fd); - assert(s); - - if (revents & EPOLLERR) - log_warning("/dev/kmsg buffer overrun, some messages lost."); - - if (!(revents & EPOLLIN)) - log_error("Got invalid event from epoll for /dev/kmsg: %"PRIx32, revents); - - return server_read_dev_kmsg(s); -} - -int server_open_dev_kmsg(Server *s) { - int r; - - assert(s); - - s->dev_kmsg_fd = open("/dev/kmsg", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); - if (s->dev_kmsg_fd < 0) { - log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, - "Failed to open /dev/kmsg, ignoring: %m"); - return 0; - } - - r = sd_event_add_io(s->event, &s->dev_kmsg_event_source, s->dev_kmsg_fd, EPOLLIN, dispatch_dev_kmsg, s); - if (r < 0) { - - /* This will fail with EPERM on older kernels where - * /dev/kmsg is not readable. */ - if (r == -EPERM) { - r = 0; - goto fail; - } - - log_error_errno(r, "Failed to add /dev/kmsg fd to event loop: %m"); - goto fail; - } - - r = sd_event_source_set_priority(s->dev_kmsg_event_source, SD_EVENT_PRIORITY_IMPORTANT+10); - if (r < 0) { - log_error_errno(r, "Failed to adjust priority of kmsg event source: %m"); - goto fail; - } - - s->dev_kmsg_readable = true; - - return 0; - -fail: - s->dev_kmsg_event_source = sd_event_source_unref(s->dev_kmsg_event_source); - s->dev_kmsg_fd = safe_close(s->dev_kmsg_fd); - - return r; -} - -int server_open_kernel_seqnum(Server *s) { - _cleanup_close_ int fd; - uint64_t *p; - int r; - - assert(s); - - /* We store the seqnum we last read in an mmaped file. That - * way we can just use it like a variable, but it is - * persistent and automatically flushed at reboot. */ - - fd = open("/run/systemd/journal/kernel-seqnum", O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0644); - if (fd < 0) { - log_error_errno(errno, "Failed to open /run/systemd/journal/kernel-seqnum, ignoring: %m"); - return 0; - } - - r = posix_fallocate(fd, 0, sizeof(uint64_t)); - if (r != 0) { - log_error_errno(r, "Failed to allocate sequential number file, ignoring: %m"); - return 0; - } - - p = mmap(NULL, sizeof(uint64_t), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); - if (p == MAP_FAILED) { - log_error_errno(errno, "Failed to map sequential number file, ignoring: %m"); - return 0; - } - - s->kernel_seqnum = p; - - return 0; -} diff --git a/src/journal/journald-kmsg.h b/src/journal/journald-kmsg.h deleted file mode 100644 index dab49f1e8c..0000000000 --- a/src/journal/journald-kmsg.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 "journald-server.h" - -int server_open_dev_kmsg(Server *s); -int server_flush_dev_kmsg(Server *s); - -void server_forward_kmsg(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred); - -int server_open_kernel_seqnum(Server *s); diff --git a/src/journal/journald-native.c b/src/journal/journald-native.c deleted file mode 100644 index 0a1ce205c2..0000000000 --- a/src/journal/journald-native.c +++ /dev/null @@ -1,501 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "fs-util.h" -#include "io-util.h" -#include "journald-console.h" -#include "journald-kmsg.h" -#include "journald-native.h" -#include "journald-server.h" -#include "journald-syslog.h" -#include "journald-wall.h" -#include "memfd-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "selinux-util.h" -#include "socket-util.h" -#include "string-util.h" - -bool valid_user_field(const char *p, size_t l, bool allow_protected) { - const char *a; - - /* We kinda enforce POSIX syntax recommendations for - environment variables here, but make a couple of additional - requirements. - - http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html */ - - /* No empty field names */ - if (l <= 0) - return false; - - /* Don't allow names longer than 64 chars */ - if (l > 64) - return false; - - /* Variables starting with an underscore are protected */ - if (!allow_protected && p[0] == '_') - return false; - - /* Don't allow digits as first character */ - if (p[0] >= '0' && p[0] <= '9') - return false; - - /* Only allow A-Z0-9 and '_' */ - for (a = p; a < p + l; a++) - if ((*a < 'A' || *a > 'Z') && - (*a < '0' || *a > '9') && - *a != '_') - return false; - - return true; -} - -static bool allow_object_pid(const struct ucred *ucred) { - return ucred && ucred->uid == 0; -} - -void server_process_native_message( - Server *s, - const void *buffer, size_t buffer_size, - const struct ucred *ucred, - const struct timeval *tv, - const char *label, size_t label_len) { - - struct iovec *iovec = NULL; - unsigned n = 0, j, tn = (unsigned) -1; - const char *p; - size_t remaining, m = 0, entry_size = 0; - int priority = LOG_INFO; - char *identifier = NULL, *message = NULL; - pid_t object_pid = 0; - - assert(s); - assert(buffer || buffer_size == 0); - - p = buffer; - remaining = buffer_size; - - while (remaining > 0) { - const char *e, *q; - - e = memchr(p, '\n', remaining); - - if (!e) { - /* Trailing noise, let's ignore it, and flush what we collected */ - log_debug("Received message with trailing noise, ignoring."); - break; - } - - if (e == p) { - /* Entry separator */ - - if (entry_size + n + 1 > ENTRY_SIZE_MAX) { /* data + separators + trailer */ - log_debug("Entry is too big with %u properties and %zu bytes, ignoring.", n, entry_size); - continue; - } - - server_dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, NULL, priority, object_pid); - n = 0; - priority = LOG_INFO; - entry_size = 0; - - p++; - remaining--; - continue; - } - - if (*p == '.' || *p == '#') { - /* Ignore control commands for now, and - * comments too. */ - remaining -= (e - p) + 1; - p = e + 1; - continue; - } - - /* A property follows */ - - /* n existing properties, 1 new, +1 for _TRANSPORT */ - if (!GREEDY_REALLOC(iovec, m, n + 2 + N_IOVEC_META_FIELDS + N_IOVEC_OBJECT_FIELDS)) { - log_oom(); - break; - } - - q = memchr(p, '=', e - p); - if (q) { - if (valid_user_field(p, q - p, false)) { - size_t l; - - l = e - p; - - /* If the field name starts with an - * underscore, skip the variable, - * since that indidates a trusted - * field */ - iovec[n].iov_base = (char*) p; - iovec[n].iov_len = l; - entry_size += iovec[n].iov_len; - n++; - - /* We need to determine the priority - * of this entry for the rate limiting - * logic */ - if (l == 10 && - startswith(p, "PRIORITY=") && - p[9] >= '0' && p[9] <= '9') - priority = (priority & LOG_FACMASK) | (p[9] - '0'); - - else if (l == 17 && - startswith(p, "SYSLOG_FACILITY=") && - p[16] >= '0' && p[16] <= '9') - priority = (priority & LOG_PRIMASK) | ((p[16] - '0') << 3); - - else if (l == 18 && - startswith(p, "SYSLOG_FACILITY=") && - p[16] >= '0' && p[16] <= '9' && - p[17] >= '0' && p[17] <= '9') - priority = (priority & LOG_PRIMASK) | (((p[16] - '0')*10 + (p[17] - '0')) << 3); - - else if (l >= 19 && - startswith(p, "SYSLOG_IDENTIFIER=")) { - char *t; - - t = strndup(p + 18, l - 18); - if (t) { - free(identifier); - identifier = t; - } - - } else if (l >= 8 && - startswith(p, "MESSAGE=")) { - char *t; - - t = strndup(p + 8, l - 8); - if (t) { - free(message); - message = t; - } - - } else if (l > strlen("OBJECT_PID=") && - l < strlen("OBJECT_PID=") + DECIMAL_STR_MAX(pid_t) && - startswith(p, "OBJECT_PID=") && - allow_object_pid(ucred)) { - char buf[DECIMAL_STR_MAX(pid_t)]; - memcpy(buf, p + strlen("OBJECT_PID="), l - strlen("OBJECT_PID=")); - buf[l-strlen("OBJECT_PID=")] = '\0'; - - /* ignore error */ - parse_pid(buf, &object_pid); - } - } - - remaining -= (e - p) + 1; - p = e + 1; - continue; - } else { - le64_t l_le; - uint64_t l; - char *k; - - if (remaining < e - p + 1 + sizeof(uint64_t) + 1) { - log_debug("Failed to parse message, ignoring."); - break; - } - - memcpy(&l_le, e + 1, sizeof(uint64_t)); - l = le64toh(l_le); - - if (l > DATA_SIZE_MAX) { - log_debug("Received binary data block of %"PRIu64" bytes is too large, ignoring.", l); - break; - } - - if ((uint64_t) remaining < e - p + 1 + sizeof(uint64_t) + l + 1 || - e[1+sizeof(uint64_t)+l] != '\n') { - log_debug("Failed to parse message, ignoring."); - break; - } - - k = malloc((e - p) + 1 + l); - if (!k) { - log_oom(); - break; - } - - memcpy(k, p, e - p); - k[e - p] = '='; - memcpy(k + (e - p) + 1, e + 1 + sizeof(uint64_t), l); - - if (valid_user_field(p, e - p, false)) { - iovec[n].iov_base = k; - iovec[n].iov_len = (e - p) + 1 + l; - entry_size += iovec[n].iov_len; - n++; - } else - free(k); - - remaining -= (e - p) + 1 + sizeof(uint64_t) + l + 1; - p = e + 1 + sizeof(uint64_t) + l + 1; - } - } - - if (n <= 0) - goto finish; - - tn = n++; - IOVEC_SET_STRING(iovec[tn], "_TRANSPORT=journal"); - entry_size += strlen("_TRANSPORT=journal"); - - if (entry_size + n + 1 > ENTRY_SIZE_MAX) { /* data + separators + trailer */ - log_debug("Entry is too big with %u properties and %zu bytes, ignoring.", - n, entry_size); - goto finish; - } - - if (message) { - if (s->forward_to_syslog) - server_forward_syslog(s, priority, identifier, message, ucred, tv); - - if (s->forward_to_kmsg) - server_forward_kmsg(s, priority, identifier, message, ucred); - - if (s->forward_to_console) - server_forward_console(s, priority, identifier, message, ucred); - - if (s->forward_to_wall) - server_forward_wall(s, priority, identifier, message, ucred); - } - - server_dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, NULL, priority, object_pid); - -finish: - for (j = 0; j < n; j++) { - if (j == tn) - continue; - - if (iovec[j].iov_base < buffer || - (const uint8_t*) iovec[j].iov_base >= (const uint8_t*) buffer + buffer_size) - free(iovec[j].iov_base); - } - - free(iovec); - free(identifier); - free(message); -} - -void server_process_native_file( - Server *s, - int fd, - const struct ucred *ucred, - const struct timeval *tv, - const char *label, size_t label_len) { - - struct stat st; - bool sealed; - int r; - - /* Data is in the passed fd, since it didn't fit in a - * datagram. */ - - assert(s); - assert(fd >= 0); - - /* If it's a memfd, check if it is sealed. If so, we can just - * use map it and use it, and do not need to copy the data - * out. */ - sealed = memfd_get_sealed(fd) > 0; - - if (!sealed && (!ucred || ucred->uid != 0)) { - _cleanup_free_ char *sl = NULL, *k = NULL; - const char *e; - - /* If this is not a sealed memfd, and the peer is unknown or - * unprivileged, then verify the path. */ - - if (asprintf(&sl, "/proc/self/fd/%i", fd) < 0) { - log_oom(); - return; - } - - r = readlink_malloc(sl, &k); - if (r < 0) { - log_error_errno(r, "readlink(%s) failed: %m", sl); - return; - } - - e = path_startswith(k, "/dev/shm/"); - if (!e) - e = path_startswith(k, "/tmp/"); - if (!e) - e = path_startswith(k, "/var/tmp/"); - if (!e) { - log_error("Received file outside of allowed directories. Refusing."); - return; - } - - if (!filename_is_valid(e)) { - log_error("Received file in subdirectory of allowed directories. Refusing."); - return; - } - } - - if (fstat(fd, &st) < 0) { - log_error_errno(errno, "Failed to stat passed file, ignoring: %m"); - return; - } - - if (!S_ISREG(st.st_mode)) { - log_error("File passed is not regular. Ignoring."); - return; - } - - if (st.st_size <= 0) - return; - - if (st.st_size > ENTRY_SIZE_MAX) { - log_error("File passed too large. Ignoring."); - return; - } - - if (sealed) { - void *p; - size_t ps; - - /* The file is sealed, we can just map it and use it. */ - - ps = PAGE_ALIGN(st.st_size); - p = mmap(NULL, ps, PROT_READ, MAP_PRIVATE, fd, 0); - if (p == MAP_FAILED) { - log_error_errno(errno, "Failed to map memfd, ignoring: %m"); - return; - } - - server_process_native_message(s, p, st.st_size, ucred, tv, label, label_len); - assert_se(munmap(p, ps) >= 0); - } else { - _cleanup_free_ void *p = NULL; - struct statvfs vfs; - ssize_t n; - - if (fstatvfs(fd, &vfs) < 0) { - log_error_errno(errno, "Failed to stat file system of passed file, ignoring: %m"); - return; - } - - /* Refuse operating on file systems that have - * mandatory locking enabled, see: - * - * https://github.com/systemd/systemd/issues/1822 - */ - if (vfs.f_flag & ST_MANDLOCK) { - log_error("Received file descriptor from file system with mandatory locking enable, refusing."); - return; - } - - /* Make the fd non-blocking. On regular files this has - * the effect of bypassing mandatory locking. Of - * course, this should normally not be necessary given - * the check above, but let's better be safe than - * sorry, after all NFS is pretty confusing regarding - * file system flags, and we better don't trust it, - * and so is SMB. */ - r = fd_nonblock(fd, true); - if (r < 0) { - log_error_errno(r, "Failed to make fd non-blocking, ignoring: %m"); - return; - } - - /* The file is not sealed, we can't map the file here, since - * clients might then truncate it and trigger a SIGBUS for - * us. So let's stupidly read it */ - - p = malloc(st.st_size); - if (!p) { - log_oom(); - return; - } - - n = pread(fd, p, st.st_size, 0); - if (n < 0) - log_error_errno(errno, "Failed to read file, ignoring: %m"); - else if (n > 0) - server_process_native_message(s, p, n, ucred, tv, label, label_len); - } -} - -int server_open_native_socket(Server*s) { - - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/journal/socket", - }; - static const int one = 1; - int r; - - assert(s); - - if (s->native_fd < 0) { - s->native_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (s->native_fd < 0) - return log_error_errno(errno, "socket() failed: %m"); - - (void) unlink(sa.un.sun_path); - - r = bind(s->native_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); - if (r < 0) - return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path); - - (void) chmod(sa.un.sun_path, 0666); - } else - fd_nonblock(s->native_fd, 1); - - r = setsockopt(s->native_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); - if (r < 0) - return log_error_errno(errno, "SO_PASSCRED failed: %m"); - -#ifdef HAVE_SELINUX - if (mac_selinux_have()) { - r = setsockopt(s->native_fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one)); - if (r < 0) - log_warning_errno(errno, "SO_PASSSEC failed: %m"); - } -#endif - - r = setsockopt(s->native_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)); - if (r < 0) - return log_error_errno(errno, "SO_TIMESTAMP failed: %m"); - - r = sd_event_add_io(s->event, &s->native_event_source, s->native_fd, EPOLLIN, server_process_datagram, s); - if (r < 0) - return log_error_errno(r, "Failed to add native server fd to event loop: %m"); - - r = sd_event_source_set_priority(s->native_event_source, SD_EVENT_PRIORITY_NORMAL+5); - if (r < 0) - return log_error_errno(r, "Failed to adjust native event source priority: %m"); - - return 0; -} diff --git a/src/journal/journald-native.h b/src/journal/journald-native.h deleted file mode 100644 index c13b80aa4f..0000000000 --- a/src/journal/journald-native.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 "journald-server.h" - -/* Make sure not to make this smaller than the maximum coredump - * size. See COREDUMP_MAX in coredump.c */ -#define ENTRY_SIZE_MAX (1024*1024*770u) -#define DATA_SIZE_MAX (1024*1024*768u) - -bool valid_user_field(const char *p, size_t l, bool allow_protected); - -void server_process_native_message(Server *s, const void *buffer, size_t buffer_size, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len); - -void server_process_native_file(Server *s, int fd, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len); - -int server_open_native_socket(Server*s); diff --git a/src/journal/journald-rate-limit.c b/src/journal/journald-rate-limit.c deleted file mode 100644 index fce799a6ce..0000000000 --- a/src/journal/journald-rate-limit.c +++ /dev/null @@ -1,271 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 - -#include "alloc-util.h" -#include "hashmap.h" -#include "journald-rate-limit.h" -#include "list.h" -#include "random-util.h" -#include "string-util.h" -#include "util.h" - -#define POOLS_MAX 5 -#define BUCKETS_MAX 127 -#define GROUPS_MAX 2047 - -static const int priority_map[] = { - [LOG_EMERG] = 0, - [LOG_ALERT] = 0, - [LOG_CRIT] = 0, - [LOG_ERR] = 1, - [LOG_WARNING] = 2, - [LOG_NOTICE] = 3, - [LOG_INFO] = 3, - [LOG_DEBUG] = 4 -}; - -typedef struct JournalRateLimitPool JournalRateLimitPool; -typedef struct JournalRateLimitGroup JournalRateLimitGroup; - -struct JournalRateLimitPool { - usec_t begin; - unsigned num; - unsigned suppressed; -}; - -struct JournalRateLimitGroup { - JournalRateLimit *parent; - - char *id; - JournalRateLimitPool pools[POOLS_MAX]; - uint64_t hash; - - LIST_FIELDS(JournalRateLimitGroup, bucket); - LIST_FIELDS(JournalRateLimitGroup, lru); -}; - -struct JournalRateLimit { - usec_t interval; - unsigned burst; - - JournalRateLimitGroup* buckets[BUCKETS_MAX]; - JournalRateLimitGroup *lru, *lru_tail; - - unsigned n_groups; - - uint8_t hash_key[16]; -}; - -JournalRateLimit *journal_rate_limit_new(usec_t interval, unsigned burst) { - JournalRateLimit *r; - - assert(interval > 0 || burst == 0); - - r = new0(JournalRateLimit, 1); - if (!r) - return NULL; - - r->interval = interval; - r->burst = burst; - - random_bytes(r->hash_key, sizeof(r->hash_key)); - - return r; -} - -static void journal_rate_limit_group_free(JournalRateLimitGroup *g) { - assert(g); - - if (g->parent) { - assert(g->parent->n_groups > 0); - - if (g->parent->lru_tail == g) - g->parent->lru_tail = g->lru_prev; - - LIST_REMOVE(lru, g->parent->lru, g); - LIST_REMOVE(bucket, g->parent->buckets[g->hash % BUCKETS_MAX], g); - - g->parent->n_groups--; - } - - free(g->id); - free(g); -} - -void journal_rate_limit_free(JournalRateLimit *r) { - assert(r); - - while (r->lru) - journal_rate_limit_group_free(r->lru); - - free(r); -} - -_pure_ static bool journal_rate_limit_group_expired(JournalRateLimitGroup *g, usec_t ts) { - unsigned i; - - assert(g); - - for (i = 0; i < POOLS_MAX; i++) - if (g->pools[i].begin + g->parent->interval >= ts) - return false; - - return true; -} - -static void journal_rate_limit_vacuum(JournalRateLimit *r, usec_t ts) { - assert(r); - - /* Makes room for at least one new item, but drop all - * expored items too. */ - - while (r->n_groups >= GROUPS_MAX || - (r->lru_tail && journal_rate_limit_group_expired(r->lru_tail, ts))) - journal_rate_limit_group_free(r->lru_tail); -} - -static JournalRateLimitGroup* journal_rate_limit_group_new(JournalRateLimit *r, const char *id, usec_t ts) { - JournalRateLimitGroup *g; - struct siphash state; - - assert(r); - assert(id); - - g = new0(JournalRateLimitGroup, 1); - if (!g) - return NULL; - - g->id = strdup(id); - if (!g->id) - goto fail; - - siphash24_init(&state, r->hash_key); - string_hash_func(g->id, &state); - g->hash = siphash24_finalize(&state); - - journal_rate_limit_vacuum(r, ts); - - LIST_PREPEND(bucket, r->buckets[g->hash % BUCKETS_MAX], g); - LIST_PREPEND(lru, r->lru, g); - if (!g->lru_next) - r->lru_tail = g; - r->n_groups++; - - g->parent = r; - return g; - -fail: - journal_rate_limit_group_free(g); - return NULL; -} - -static unsigned burst_modulate(unsigned burst, uint64_t available) { - unsigned k; - - /* Modulates the burst rate a bit with the amount of available - * disk space */ - - k = u64log2(available); - - /* 1MB */ - if (k <= 20) - return burst; - - burst = (burst * (k-20)) / 4; - - /* - * Example: - * - * <= 1MB = rate * 1 - * 16MB = rate * 2 - * 256MB = rate * 3 - * 4GB = rate * 4 - * 64GB = rate * 5 - * 1TB = rate * 6 - */ - - return burst; -} - -int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, uint64_t available) { - uint64_t h; - JournalRateLimitGroup *g; - JournalRateLimitPool *p; - struct siphash state; - unsigned burst; - usec_t ts; - - assert(id); - - if (!r) - return 1; - - if (r->interval == 0 || r->burst == 0) - return 1; - - burst = burst_modulate(r->burst, available); - - ts = now(CLOCK_MONOTONIC); - - siphash24_init(&state, r->hash_key); - string_hash_func(id, &state); - h = siphash24_finalize(&state); - g = r->buckets[h % BUCKETS_MAX]; - - LIST_FOREACH(bucket, g, g) - if (streq(g->id, id)) - break; - - if (!g) { - g = journal_rate_limit_group_new(r, id, ts); - if (!g) - return -ENOMEM; - } - - p = &g->pools[priority_map[priority]]; - - if (p->begin <= 0) { - p->suppressed = 0; - p->num = 1; - p->begin = ts; - return 1; - } - - if (p->begin + r->interval < ts) { - unsigned s; - - s = p->suppressed; - p->suppressed = 0; - p->num = 1; - p->begin = ts; - - return 1 + s; - } - - if (p->num <= burst) { - p->num++; - return 1; - } - - p->suppressed++; - return 0; -} diff --git a/src/journal/journald-rate-limit.h b/src/journal/journald-rate-limit.h deleted file mode 100644 index bb0abb7ee9..0000000000 --- a/src/journal/journald-rate-limit.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 struct JournalRateLimit JournalRateLimit; - -JournalRateLimit *journal_rate_limit_new(usec_t interval, unsigned burst); -void journal_rate_limit_free(JournalRateLimit *r); -int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, uint64_t available); diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c deleted file mode 100644 index 8f82d2a838..0000000000 --- a/src/journal/journald-server.c +++ /dev/null @@ -1,2007 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#ifdef HAVE_SELINUX -#include -#endif -#include -#include -#include -#include -#include - -#include "libudev.h" -#include "sd-daemon.h" -#include "sd-journal.h" -#include "sd-messages.h" - -#include "acl-util.h" -#include "alloc-util.h" -#include "audit-util.h" -#include "cgroup-util.h" -#include "conf-parser.h" -#include "dirent-util.h" -#include "extract-word.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "hashmap.h" -#include "hostname-util.h" -#include "io-util.h" -#include "journal-authenticate.h" -#include "journal-file.h" -#include "journal-internal.h" -#include "journal-vacuum.h" -#include "journald-audit.h" -#include "journald-kmsg.h" -#include "journald-native.h" -#include "journald-rate-limit.h" -#include "journald-server.h" -#include "journald-stream.h" -#include "journald-syslog.h" -#include "missing.h" -#include "mkdir.h" -#include "parse-util.h" -#include "proc-cmdline.h" -#include "process-util.h" -#include "rm-rf.h" -#include "selinux-util.h" -#include "signal-util.h" -#include "socket-util.h" -#include "stdio-util.h" -#include "string-table.h" -#include "string-util.h" -#include "user-util.h" -#include "log.h" - -#define USER_JOURNALS_MAX 1024 - -#define DEFAULT_SYNC_INTERVAL_USEC (5*USEC_PER_MINUTE) -#define DEFAULT_RATE_LIMIT_INTERVAL (30*USEC_PER_SEC) -#define DEFAULT_RATE_LIMIT_BURST 1000 -#define DEFAULT_MAX_FILE_USEC USEC_PER_MONTH - -#define RECHECK_SPACE_USEC (30*USEC_PER_SEC) - -#define NOTIFY_SNDBUF_SIZE (8*1024*1024) - -/* The period to insert between posting changes for coalescing */ -#define POST_CHANGE_TIMER_INTERVAL_USEC (250*USEC_PER_MSEC) - -static int determine_space_for( - Server *s, - JournalMetrics *metrics, - const char *path, - const char *name, - bool verbose, - bool patch_min_use, - uint64_t *available, - uint64_t *limit) { - - uint64_t sum = 0, ss_avail, avail; - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - struct statvfs ss; - const char *p; - usec_t ts; - - assert(s); - assert(metrics); - assert(path); - assert(name); - - ts = now(CLOCK_MONOTONIC); - - if (!verbose && s->cached_space_timestamp + RECHECK_SPACE_USEC > ts) { - - if (available) - *available = s->cached_space_available; - if (limit) - *limit = s->cached_space_limit; - - return 0; - } - - p = strjoina(path, SERVER_MACHINE_ID(s)); - d = opendir(p); - if (!d) - return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open %s: %m", p); - - if (fstatvfs(dirfd(d), &ss) < 0) - return log_error_errno(errno, "Failed to fstatvfs(%s): %m", p); - - FOREACH_DIRENT_ALL(de, d, break) { - struct stat st; - - if (!endswith(de->d_name, ".journal") && - !endswith(de->d_name, ".journal~")) - continue; - - if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { - log_debug_errno(errno, "Failed to stat %s/%s, ignoring: %m", p, de->d_name); - continue; - } - - if (!S_ISREG(st.st_mode)) - continue; - - sum += (uint64_t) st.st_blocks * 512UL; - } - - /* If requested, then let's bump the min_use limit to the - * current usage on disk. We do this when starting up and - * first opening the journal files. This way sudden spikes in - * disk usage will not cause journald to vacuum files without - * bounds. Note that this means that only a restart of - * journald will make it reset this value. */ - - if (patch_min_use) - metrics->min_use = MAX(metrics->min_use, sum); - - ss_avail = ss.f_bsize * ss.f_bavail; - avail = LESS_BY(ss_avail, metrics->keep_free); - - s->cached_space_limit = MIN(MAX(sum + avail, metrics->min_use), metrics->max_use); - s->cached_space_available = LESS_BY(s->cached_space_limit, sum); - s->cached_space_timestamp = ts; - - if (verbose) { - char fb1[FORMAT_BYTES_MAX], fb2[FORMAT_BYTES_MAX], fb3[FORMAT_BYTES_MAX], - fb4[FORMAT_BYTES_MAX], fb5[FORMAT_BYTES_MAX], fb6[FORMAT_BYTES_MAX]; - format_bytes(fb1, sizeof(fb1), sum); - format_bytes(fb2, sizeof(fb2), metrics->max_use); - format_bytes(fb3, sizeof(fb3), metrics->keep_free); - format_bytes(fb4, sizeof(fb4), ss_avail); - format_bytes(fb5, sizeof(fb5), s->cached_space_limit); - format_bytes(fb6, sizeof(fb6), s->cached_space_available); - - server_driver_message(s, SD_MESSAGE_JOURNAL_USAGE, - LOG_MESSAGE("%s (%s) is %s, max %s, %s free.", - name, path, fb1, fb5, fb6), - "JOURNAL_NAME=%s", name, - "JOURNAL_PATH=%s", path, - "CURRENT_USE=%"PRIu64, sum, - "CURRENT_USE_PRETTY=%s", fb1, - "MAX_USE=%"PRIu64, metrics->max_use, - "MAX_USE_PRETTY=%s", fb2, - "DISK_KEEP_FREE=%"PRIu64, metrics->keep_free, - "DISK_KEEP_FREE_PRETTY=%s", fb3, - "DISK_AVAILABLE=%"PRIu64, ss_avail, - "DISK_AVAILABLE_PRETTY=%s", fb4, - "LIMIT=%"PRIu64, s->cached_space_limit, - "LIMIT_PRETTY=%s", fb5, - "AVAILABLE=%"PRIu64, s->cached_space_available, - "AVAILABLE_PRETTY=%s", fb6, - NULL); - } - - if (available) - *available = s->cached_space_available; - if (limit) - *limit = s->cached_space_limit; - - return 1; -} - -static int determine_space(Server *s, bool verbose, bool patch_min_use, uint64_t *available, uint64_t *limit) { - JournalMetrics *metrics; - const char *path, *name; - - assert(s); - - if (s->system_journal) { - path = "/var/log/journal/"; - metrics = &s->system_metrics; - name = "System journal"; - } else { - path = "/run/log/journal/"; - metrics = &s->runtime_metrics; - name = "Runtime journal"; - } - - return determine_space_for(s, metrics, path, name, verbose, patch_min_use, available, limit); -} - -static void server_add_acls(JournalFile *f, uid_t uid) { -#ifdef HAVE_ACL - int r; -#endif - assert(f); - -#ifdef HAVE_ACL - if (uid <= SYSTEM_UID_MAX) - return; - - r = add_acls_for_user(f->fd, uid); - if (r < 0) - log_warning_errno(r, "Failed to set ACL on %s, ignoring: %m", f->path); -#endif -} - -static int open_journal( - Server *s, - bool reliably, - const char *fname, - int flags, - bool seal, - JournalMetrics *metrics, - JournalFile **ret) { - int r; - JournalFile *f; - - assert(s); - assert(fname); - assert(ret); - - if (reliably) - r = journal_file_open_reliably(fname, flags, 0640, s->compress, seal, metrics, s->mmap, s->deferred_closes, NULL, &f); - else - r = journal_file_open(-1, fname, flags, 0640, s->compress, seal, metrics, s->mmap, s->deferred_closes, NULL, &f); - if (r < 0) - return r; - - r = journal_file_enable_post_change_timer(f, s->event, POST_CHANGE_TIMER_INTERVAL_USEC); - if (r < 0) { - (void) journal_file_close(f); - return r; - } - - *ret = f; - return r; -} - -static JournalFile* find_journal(Server *s, uid_t uid) { - _cleanup_free_ char *p = NULL; - int r; - JournalFile *f; - sd_id128_t machine; - - assert(s); - - /* We split up user logs only on /var, not on /run. If the - * runtime file is open, we write to it exclusively, in order - * to guarantee proper order as soon as we flush /run to - * /var and close the runtime file. */ - - if (s->runtime_journal) - return s->runtime_journal; - - if (uid <= SYSTEM_UID_MAX) - return s->system_journal; - - r = sd_id128_get_machine(&machine); - if (r < 0) - return s->system_journal; - - f = ordered_hashmap_get(s->user_journals, UID_TO_PTR(uid)); - if (f) - return f; - - if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/user-"UID_FMT".journal", - SD_ID128_FORMAT_VAL(machine), uid) < 0) - return s->system_journal; - - while (ordered_hashmap_size(s->user_journals) >= USER_JOURNALS_MAX) { - /* Too many open? Then let's close one */ - f = ordered_hashmap_steal_first(s->user_journals); - assert(f); - (void) journal_file_close(f); - } - - r = open_journal(s, true, p, O_RDWR|O_CREAT, s->seal, &s->system_metrics, &f); - if (r < 0) - return s->system_journal; - - server_add_acls(f, uid); - - r = ordered_hashmap_put(s->user_journals, UID_TO_PTR(uid), f); - if (r < 0) { - (void) journal_file_close(f); - return s->system_journal; - } - - return f; -} - -static int do_rotate( - Server *s, - JournalFile **f, - const char* name, - bool seal, - uint32_t uid) { - - int r; - assert(s); - - if (!*f) - return -EINVAL; - - r = journal_file_rotate(f, s->compress, seal, s->deferred_closes); - if (r < 0) - if (*f) - log_error_errno(r, "Failed to rotate %s: %m", (*f)->path); - else - log_error_errno(r, "Failed to create new %s journal: %m", name); - else - server_add_acls(*f, uid); - - return r; -} - -void server_rotate(Server *s) { - JournalFile *f; - void *k; - Iterator i; - int r; - - log_debug("Rotating..."); - - (void) do_rotate(s, &s->runtime_journal, "runtime", false, 0); - (void) do_rotate(s, &s->system_journal, "system", s->seal, 0); - - ORDERED_HASHMAP_FOREACH_KEY(f, k, s->user_journals, i) { - r = do_rotate(s, &f, "user", s->seal, PTR_TO_UID(k)); - if (r >= 0) - ordered_hashmap_replace(s->user_journals, k, f); - else if (!f) - /* Old file has been closed and deallocated */ - ordered_hashmap_remove(s->user_journals, k); - } - - /* Perform any deferred closes which aren't still offlining. */ - SET_FOREACH(f, s->deferred_closes, i) - if (!journal_file_is_offlining(f)) { - (void) set_remove(s->deferred_closes, f); - (void) journal_file_close(f); - } -} - -void server_sync(Server *s) { - JournalFile *f; - Iterator i; - int r; - - if (s->system_journal) { - r = journal_file_set_offline(s->system_journal, false); - if (r < 0) - log_warning_errno(r, "Failed to sync system journal, ignoring: %m"); - } - - ORDERED_HASHMAP_FOREACH(f, s->user_journals, i) { - r = journal_file_set_offline(f, false); - if (r < 0) - log_warning_errno(r, "Failed to sync user journal, ignoring: %m"); - } - - if (s->sync_event_source) { - r = sd_event_source_set_enabled(s->sync_event_source, SD_EVENT_OFF); - if (r < 0) - log_error_errno(r, "Failed to disable sync timer source: %m"); - } - - s->sync_scheduled = false; -} - -static void do_vacuum( - Server *s, - JournalFile *f, - JournalMetrics *metrics, - const char *path, - const char *name, - bool verbose, - bool patch_min_use) { - - const char *p; - uint64_t limit; - int r; - - assert(s); - assert(metrics); - assert(path); - assert(name); - - if (!f) - return; - - p = strjoina(path, SERVER_MACHINE_ID(s)); - - limit = metrics->max_use; - (void) determine_space_for(s, metrics, path, name, verbose, patch_min_use, NULL, &limit); - - r = journal_directory_vacuum(p, limit, metrics->n_max_files, s->max_retention_usec, &s->oldest_file_usec, verbose); - if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to vacuum %s, ignoring: %m", p); -} - -int server_vacuum(Server *s, bool verbose, bool patch_min_use) { - assert(s); - - log_debug("Vacuuming..."); - - s->oldest_file_usec = 0; - - do_vacuum(s, s->system_journal, &s->system_metrics, "/var/log/journal/", "System journal", verbose, patch_min_use); - do_vacuum(s, s->runtime_journal, &s->runtime_metrics, "/run/log/journal/", "Runtime journal", verbose, patch_min_use); - - s->cached_space_limit = 0; - s->cached_space_available = 0; - s->cached_space_timestamp = 0; - - return 0; -} - -static void server_cache_machine_id(Server *s) { - sd_id128_t id; - int r; - - assert(s); - - r = sd_id128_get_machine(&id); - if (r < 0) - return; - - sd_id128_to_string(id, stpcpy(s->machine_id_field, "_MACHINE_ID=")); -} - -static void server_cache_boot_id(Server *s) { - sd_id128_t id; - int r; - - assert(s); - - r = sd_id128_get_boot(&id); - if (r < 0) - return; - - sd_id128_to_string(id, stpcpy(s->boot_id_field, "_BOOT_ID=")); -} - -static void server_cache_hostname(Server *s) { - _cleanup_free_ char *t = NULL; - char *x; - - assert(s); - - t = gethostname_malloc(); - if (!t) - return; - - x = strappend("_HOSTNAME=", t); - if (!x) - return; - - free(s->hostname_field); - s->hostname_field = x; -} - -static bool shall_try_append_again(JournalFile *f, int r) { - switch(r) { - case -E2BIG: /* Hit configured limit */ - case -EFBIG: /* Hit fs limit */ - case -EDQUOT: /* Quota limit hit */ - case -ENOSPC: /* Disk full */ - log_debug("%s: Allocation limit reached, rotating.", f->path); - return true; - case -EIO: /* I/O error of some kind (mmap) */ - log_warning("%s: IO error, rotating.", f->path); - return true; - case -EHOSTDOWN: /* Other machine */ - log_info("%s: Journal file from other machine, rotating.", f->path); - return true; - case -EBUSY: /* Unclean shutdown */ - log_info("%s: Unclean shutdown, rotating.", f->path); - return true; - case -EPROTONOSUPPORT: /* Unsupported feature */ - log_info("%s: Unsupported feature, rotating.", f->path); - return true; - case -EBADMSG: /* Corrupted */ - case -ENODATA: /* Truncated */ - case -ESHUTDOWN: /* Already archived */ - log_warning("%s: Journal file corrupted, rotating.", f->path); - return true; - case -EIDRM: /* Journal file has been deleted */ - log_warning("%s: Journal file has been deleted, rotating.", f->path); - return true; - default: - return false; - } -} - -static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, unsigned n, int priority) { - JournalFile *f; - bool vacuumed = false; - int r; - - assert(s); - assert(iovec); - assert(n > 0); - - f = find_journal(s, uid); - if (!f) - return; - - if (journal_file_rotate_suggested(f, s->max_file_usec)) { - log_debug("%s: Journal header limits reached or header out-of-date, rotating.", f->path); - server_rotate(s); - server_vacuum(s, false, false); - vacuumed = true; - - f = find_journal(s, uid); - if (!f) - return; - } - - r = journal_file_append_entry(f, NULL, iovec, n, &s->seqnum, NULL, NULL); - if (r >= 0) { - server_schedule_sync(s, priority); - return; - } - - if (vacuumed || !shall_try_append_again(f, r)) { - log_error_errno(r, "Failed to write entry (%d items, %zu bytes), ignoring: %m", n, IOVEC_TOTAL_SIZE(iovec, n)); - return; - } - - server_rotate(s); - server_vacuum(s, false, false); - - f = find_journal(s, uid); - if (!f) - return; - - log_debug("Retrying write."); - r = journal_file_append_entry(f, NULL, iovec, n, &s->seqnum, NULL, NULL); - if (r < 0) - log_error_errno(r, "Failed to write entry (%d items, %zu bytes) despite vacuuming, ignoring: %m", n, IOVEC_TOTAL_SIZE(iovec, n)); - else - server_schedule_sync(s, priority); -} - -static void dispatch_message_real( - Server *s, - struct iovec *iovec, unsigned n, unsigned m, - const struct ucred *ucred, - const struct timeval *tv, - const char *label, size_t label_len, - const char *unit_id, - int priority, - pid_t object_pid) { - - char pid[sizeof("_PID=") + DECIMAL_STR_MAX(pid_t)], - uid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)], - gid[sizeof("_GID=") + DECIMAL_STR_MAX(gid_t)], - owner_uid[sizeof("_SYSTEMD_OWNER_UID=") + DECIMAL_STR_MAX(uid_t)], - source_time[sizeof("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)], - o_uid[sizeof("OBJECT_UID=") + DECIMAL_STR_MAX(uid_t)], - o_gid[sizeof("OBJECT_GID=") + DECIMAL_STR_MAX(gid_t)], - o_owner_uid[sizeof("OBJECT_SYSTEMD_OWNER_UID=") + DECIMAL_STR_MAX(uid_t)]; - uid_t object_uid; - gid_t object_gid; - char *x; - int r; - char *t, *c; - uid_t realuid = 0, owner = 0, journal_uid; - bool owner_valid = false; -#ifdef HAVE_AUDIT - char audit_session[sizeof("_AUDIT_SESSION=") + DECIMAL_STR_MAX(uint32_t)], - audit_loginuid[sizeof("_AUDIT_LOGINUID=") + DECIMAL_STR_MAX(uid_t)], - o_audit_session[sizeof("OBJECT_AUDIT_SESSION=") + DECIMAL_STR_MAX(uint32_t)], - o_audit_loginuid[sizeof("OBJECT_AUDIT_LOGINUID=") + DECIMAL_STR_MAX(uid_t)]; - - uint32_t audit; - uid_t loginuid; -#endif - - assert(s); - assert(iovec); - assert(n > 0); - assert(n + N_IOVEC_META_FIELDS + (object_pid ? N_IOVEC_OBJECT_FIELDS : 0) <= m); - - if (ucred) { - realuid = ucred->uid; - - sprintf(pid, "_PID="PID_FMT, ucred->pid); - IOVEC_SET_STRING(iovec[n++], pid); - - sprintf(uid, "_UID="UID_FMT, ucred->uid); - IOVEC_SET_STRING(iovec[n++], uid); - - sprintf(gid, "_GID="GID_FMT, ucred->gid); - IOVEC_SET_STRING(iovec[n++], gid); - - r = get_process_comm(ucred->pid, &t); - if (r >= 0) { - x = strjoina("_COMM=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - r = get_process_exe(ucred->pid, &t); - if (r >= 0) { - x = strjoina("_EXE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - r = get_process_cmdline(ucred->pid, 0, false, &t); - if (r >= 0) { - x = strjoina("_CMDLINE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - r = get_process_capeff(ucred->pid, &t); - if (r >= 0) { - x = strjoina("_CAP_EFFECTIVE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - -#ifdef HAVE_AUDIT - r = audit_session_from_pid(ucred->pid, &audit); - if (r >= 0) { - sprintf(audit_session, "_AUDIT_SESSION=%"PRIu32, audit); - IOVEC_SET_STRING(iovec[n++], audit_session); - } - - r = audit_loginuid_from_pid(ucred->pid, &loginuid); - if (r >= 0) { - sprintf(audit_loginuid, "_AUDIT_LOGINUID="UID_FMT, loginuid); - IOVEC_SET_STRING(iovec[n++], audit_loginuid); - } -#endif - - r = cg_pid_get_path_shifted(ucred->pid, s->cgroup_root, &c); - if (r >= 0) { - char *session = NULL; - - x = strjoina("_SYSTEMD_CGROUP=", c); - IOVEC_SET_STRING(iovec[n++], x); - - r = cg_path_get_session(c, &t); - if (r >= 0) { - session = strjoina("_SYSTEMD_SESSION=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], session); - } - - if (cg_path_get_owner_uid(c, &owner) >= 0) { - owner_valid = true; - - sprintf(owner_uid, "_SYSTEMD_OWNER_UID="UID_FMT, owner); - IOVEC_SET_STRING(iovec[n++], owner_uid); - } - - if (cg_path_get_unit(c, &t) >= 0) { - x = strjoina("_SYSTEMD_UNIT=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } else if (unit_id && !session) { - x = strjoina("_SYSTEMD_UNIT=", unit_id); - IOVEC_SET_STRING(iovec[n++], x); - } - - if (cg_path_get_user_unit(c, &t) >= 0) { - x = strjoina("_SYSTEMD_USER_UNIT=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } else if (unit_id && session) { - x = strjoina("_SYSTEMD_USER_UNIT=", unit_id); - IOVEC_SET_STRING(iovec[n++], x); - } - - if (cg_path_get_slice(c, &t) >= 0) { - x = strjoina("_SYSTEMD_SLICE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - free(c); - } else if (unit_id) { - x = strjoina("_SYSTEMD_UNIT=", unit_id); - IOVEC_SET_STRING(iovec[n++], x); - } - -#ifdef HAVE_SELINUX - if (mac_selinux_have()) { - if (label) { - x = alloca(strlen("_SELINUX_CONTEXT=") + label_len + 1); - - *((char*) mempcpy(stpcpy(x, "_SELINUX_CONTEXT="), label, label_len)) = 0; - IOVEC_SET_STRING(iovec[n++], x); - } else { - security_context_t con; - - if (getpidcon(ucred->pid, &con) >= 0) { - x = strjoina("_SELINUX_CONTEXT=", con); - - freecon(con); - IOVEC_SET_STRING(iovec[n++], x); - } - } - } -#endif - } - assert(n <= m); - - if (object_pid) { - r = get_process_uid(object_pid, &object_uid); - if (r >= 0) { - sprintf(o_uid, "OBJECT_UID="UID_FMT, object_uid); - IOVEC_SET_STRING(iovec[n++], o_uid); - } - - r = get_process_gid(object_pid, &object_gid); - if (r >= 0) { - sprintf(o_gid, "OBJECT_GID="GID_FMT, object_gid); - IOVEC_SET_STRING(iovec[n++], o_gid); - } - - r = get_process_comm(object_pid, &t); - if (r >= 0) { - x = strjoina("OBJECT_COMM=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - r = get_process_exe(object_pid, &t); - if (r >= 0) { - x = strjoina("OBJECT_EXE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - r = get_process_cmdline(object_pid, 0, false, &t); - if (r >= 0) { - x = strjoina("OBJECT_CMDLINE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - -#ifdef HAVE_AUDIT - r = audit_session_from_pid(object_pid, &audit); - if (r >= 0) { - sprintf(o_audit_session, "OBJECT_AUDIT_SESSION=%"PRIu32, audit); - IOVEC_SET_STRING(iovec[n++], o_audit_session); - } - - r = audit_loginuid_from_pid(object_pid, &loginuid); - if (r >= 0) { - sprintf(o_audit_loginuid, "OBJECT_AUDIT_LOGINUID="UID_FMT, loginuid); - IOVEC_SET_STRING(iovec[n++], o_audit_loginuid); - } -#endif - - r = cg_pid_get_path_shifted(object_pid, s->cgroup_root, &c); - if (r >= 0) { - x = strjoina("OBJECT_SYSTEMD_CGROUP=", c); - IOVEC_SET_STRING(iovec[n++], x); - - r = cg_path_get_session(c, &t); - if (r >= 0) { - x = strjoina("OBJECT_SYSTEMD_SESSION=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - if (cg_path_get_owner_uid(c, &owner) >= 0) { - sprintf(o_owner_uid, "OBJECT_SYSTEMD_OWNER_UID="UID_FMT, owner); - IOVEC_SET_STRING(iovec[n++], o_owner_uid); - } - - if (cg_path_get_unit(c, &t) >= 0) { - x = strjoina("OBJECT_SYSTEMD_UNIT=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - if (cg_path_get_user_unit(c, &t) >= 0) { - x = strjoina("OBJECT_SYSTEMD_USER_UNIT=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - free(c); - } - } - assert(n <= m); - - if (tv) { - sprintf(source_time, "_SOURCE_REALTIME_TIMESTAMP=%llu", (unsigned long long) timeval_load(tv)); - IOVEC_SET_STRING(iovec[n++], source_time); - } - - /* Note that strictly speaking storing the boot id here is - * redundant since the entry includes this in-line - * anyway. However, we need this indexed, too. */ - if (!isempty(s->boot_id_field)) - IOVEC_SET_STRING(iovec[n++], s->boot_id_field); - - if (!isempty(s->machine_id_field)) - IOVEC_SET_STRING(iovec[n++], s->machine_id_field); - - if (!isempty(s->hostname_field)) - IOVEC_SET_STRING(iovec[n++], s->hostname_field); - - assert(n <= m); - - if (s->split_mode == SPLIT_UID && realuid > 0) - /* Split up strictly by any UID */ - journal_uid = realuid; - else if (s->split_mode == SPLIT_LOGIN && realuid > 0 && owner_valid && owner > 0) - /* Split up by login UIDs. We do this only if the - * realuid is not root, in order not to accidentally - * leak privileged information to the user that is - * logged by a privileged process that is part of an - * unprivileged session. */ - journal_uid = owner; - else - journal_uid = 0; - - write_to_journal(s, journal_uid, iovec, n, priority); -} - -void server_driver_message(Server *s, sd_id128_t message_id, const char *format, ...) { - char mid[11 + 32 + 1]; - struct iovec iovec[N_IOVEC_META_FIELDS + 5 + N_IOVEC_PAYLOAD_FIELDS]; - unsigned n = 0, m; - int r; - va_list ap; - struct ucred ucred = {}; - - assert(s); - assert(format); - - assert_cc(3 == LOG_FAC(LOG_DAEMON)); - IOVEC_SET_STRING(iovec[n++], "SYSLOG_FACILITY=3"); - IOVEC_SET_STRING(iovec[n++], "SYSLOG_IDENTIFIER=systemd-journald"); - - IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=driver"); - assert_cc(6 == LOG_INFO); - IOVEC_SET_STRING(iovec[n++], "PRIORITY=6"); - - if (!sd_id128_equal(message_id, SD_ID128_NULL)) { - snprintf(mid, sizeof(mid), LOG_MESSAGE_ID(message_id)); - IOVEC_SET_STRING(iovec[n++], mid); - } - - m = n; - - va_start(ap, format); - r = log_format_iovec(iovec, ELEMENTSOF(iovec), &n, false, 0, format, ap); - /* Error handling below */ - va_end(ap); - - ucred.pid = getpid(); - ucred.uid = getuid(); - ucred.gid = getgid(); - - if (r >= 0) - dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL, LOG_INFO, 0); - - while (m < n) - free(iovec[m++].iov_base); - - if (r < 0) { - /* We failed to format the message. Emit a warning instead. */ - char buf[LINE_MAX]; - - xsprintf(buf, "MESSAGE=Entry printing failed: %s", strerror(-r)); - - n = 3; - IOVEC_SET_STRING(iovec[n++], "PRIORITY=4"); - IOVEC_SET_STRING(iovec[n++], buf); - dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL, LOG_INFO, 0); - } -} - -void server_dispatch_message( - Server *s, - struct iovec *iovec, unsigned n, unsigned m, - const struct ucred *ucred, - const struct timeval *tv, - const char *label, size_t label_len, - const char *unit_id, - int priority, - pid_t object_pid) { - - int rl, r; - _cleanup_free_ char *path = NULL; - uint64_t available = 0; - char *c; - - assert(s); - assert(iovec || n == 0); - - if (n == 0) - return; - - if (LOG_PRI(priority) > s->max_level_store) - return; - - /* Stop early in case the information will not be stored - * in a journal. */ - if (s->storage == STORAGE_NONE) - return; - - if (!ucred) - goto finish; - - r = cg_pid_get_path_shifted(ucred->pid, s->cgroup_root, &path); - if (r < 0) - goto finish; - - /* example: /user/lennart/3/foobar - * /system/dbus.service/foobar - * - * So let's cut of everything past the third /, since that is - * where user directories start */ - - c = strchr(path, '/'); - if (c) { - c = strchr(c+1, '/'); - if (c) { - c = strchr(c+1, '/'); - if (c) - *c = 0; - } - } - - (void) determine_space(s, false, false, &available, NULL); - rl = journal_rate_limit_test(s->rate_limit, path, priority & LOG_PRIMASK, available); - if (rl == 0) - return; - - /* Write a suppression message if we suppressed something */ - if (rl > 1) - server_driver_message(s, SD_MESSAGE_JOURNAL_DROPPED, - LOG_MESSAGE("Suppressed %u messages from %s", rl - 1, path), - NULL); - -finish: - dispatch_message_real(s, iovec, n, m, ucred, tv, label, label_len, unit_id, priority, object_pid); -} - - -static int system_journal_open(Server *s, bool flush_requested) { - const char *fn; - int r = 0; - - if (!s->system_journal && - (s->storage == STORAGE_PERSISTENT || s->storage == STORAGE_AUTO) && - (flush_requested - || access("/run/systemd/journal/flushed", F_OK) >= 0)) { - - /* If in auto mode: first try to create the machine - * path, but not the prefix. - * - * If in persistent mode: create /var/log/journal and - * the machine path */ - - if (s->storage == STORAGE_PERSISTENT) - (void) mkdir_p("/var/log/journal/", 0755); - - fn = strjoina("/var/log/journal/", SERVER_MACHINE_ID(s)); - (void) mkdir(fn, 0755); - - fn = strjoina(fn, "/system.journal"); - r = open_journal(s, true, fn, O_RDWR|O_CREAT, s->seal, &s->system_metrics, &s->system_journal); - if (r >= 0) { - server_add_acls(s->system_journal, 0); - (void) determine_space_for(s, &s->system_metrics, "/var/log/journal/", "System journal", true, true, NULL, NULL); - } else if (r < 0) { - if (r != -ENOENT && r != -EROFS) - log_warning_errno(r, "Failed to open system journal: %m"); - - r = 0; - } - } - - if (!s->runtime_journal && - (s->storage != STORAGE_NONE)) { - - fn = strjoina("/run/log/journal/", SERVER_MACHINE_ID(s), "/system.journal"); - - if (s->system_journal) { - - /* Try to open the runtime journal, but only - * if it already exists, so that we can flush - * it into the system journal */ - - r = open_journal(s, false, fn, O_RDWR, false, &s->runtime_metrics, &s->runtime_journal); - if (r < 0) { - if (r != -ENOENT) - log_warning_errno(r, "Failed to open runtime journal: %m"); - - r = 0; - } - - } else { - - /* OK, we really need the runtime journal, so create - * it if necessary. */ - - (void) mkdir("/run/log", 0755); - (void) mkdir("/run/log/journal", 0755); - (void) mkdir_parents(fn, 0750); - - r = open_journal(s, true, fn, O_RDWR|O_CREAT, false, &s->runtime_metrics, &s->runtime_journal); - if (r < 0) - return log_error_errno(r, "Failed to open runtime journal: %m"); - } - - if (s->runtime_journal) { - server_add_acls(s->runtime_journal, 0); - (void) determine_space_for(s, &s->runtime_metrics, "/run/log/journal/", "Runtime journal", true, true, NULL, NULL); - } - } - - return r; -} - -int server_flush_to_var(Server *s) { - sd_id128_t machine; - sd_journal *j = NULL; - char ts[FORMAT_TIMESPAN_MAX]; - usec_t start; - unsigned n = 0; - int r; - - assert(s); - - if (s->storage != STORAGE_AUTO && - s->storage != STORAGE_PERSISTENT) - return 0; - - if (!s->runtime_journal) - return 0; - - (void) system_journal_open(s, true); - - if (!s->system_journal) - return 0; - - log_debug("Flushing to /var..."); - - start = now(CLOCK_MONOTONIC); - - r = sd_id128_get_machine(&machine); - if (r < 0) - return r; - - r = sd_journal_open(&j, SD_JOURNAL_RUNTIME_ONLY); - if (r < 0) - return log_error_errno(r, "Failed to read runtime journal: %m"); - - sd_journal_set_data_threshold(j, 0); - - SD_JOURNAL_FOREACH(j) { - Object *o = NULL; - JournalFile *f; - - f = j->current_file; - assert(f && f->current_offset > 0); - - n++; - - r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); - if (r < 0) { - log_error_errno(r, "Can't read entry: %m"); - goto finish; - } - - r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset, NULL, NULL, NULL); - if (r >= 0) - continue; - - if (!shall_try_append_again(s->system_journal, r)) { - log_error_errno(r, "Can't write entry: %m"); - goto finish; - } - - server_rotate(s); - server_vacuum(s, false, false); - - if (!s->system_journal) { - log_notice("Didn't flush runtime journal since rotation of system journal wasn't successful."); - r = -EIO; - goto finish; - } - - log_debug("Retrying write."); - r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset, NULL, NULL, NULL); - if (r < 0) { - log_error_errno(r, "Can't write entry: %m"); - goto finish; - } - } - - r = 0; - -finish: - journal_file_post_change(s->system_journal); - - s->runtime_journal = journal_file_close(s->runtime_journal); - - if (r >= 0) - (void) rm_rf("/run/log/journal", REMOVE_ROOT); - - sd_journal_close(j); - - server_driver_message(s, SD_ID128_NULL, - LOG_MESSAGE("Time spent on flushing to /var is %s for %u entries.", - format_timespan(ts, sizeof(ts), now(CLOCK_MONOTONIC) - start, 0), - n), - NULL); - - return r; -} - -int server_process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userdata) { - Server *s = userdata; - struct ucred *ucred = NULL; - struct timeval *tv = NULL; - struct cmsghdr *cmsg; - char *label = NULL; - size_t label_len = 0, m; - struct iovec iovec; - ssize_t n; - int *fds = NULL, v = 0; - unsigned n_fds = 0; - - union { - struct cmsghdr cmsghdr; - - /* We use NAME_MAX space for the SELinux label - * here. The kernel currently enforces no - * limit, but according to suggestions from - * the SELinux people this will change and it - * will probably be identical to NAME_MAX. For - * now we use that, but this should be updated - * one day when the final limit is known. */ - uint8_t buf[CMSG_SPACE(sizeof(struct ucred)) + - CMSG_SPACE(sizeof(struct timeval)) + - CMSG_SPACE(sizeof(int)) + /* fd */ - CMSG_SPACE(NAME_MAX)]; /* selinux label */ - } control = {}; - - union sockaddr_union sa = {}; - - struct msghdr msghdr = { - .msg_iov = &iovec, - .msg_iovlen = 1, - .msg_control = &control, - .msg_controllen = sizeof(control), - .msg_name = &sa, - .msg_namelen = sizeof(sa), - }; - - assert(s); - assert(fd == s->native_fd || fd == s->syslog_fd || fd == s->audit_fd); - - if (revents != EPOLLIN) { - log_error("Got invalid event from epoll for datagram fd: %"PRIx32, revents); - return -EIO; - } - - /* Try to get the right size, if we can. (Not all - * sockets support SIOCINQ, hence we just try, but - * don't rely on it. */ - (void) ioctl(fd, SIOCINQ, &v); - - /* Fix it up, if it is too small. We use the same fixed value as auditd here. Awful! */ - m = PAGE_ALIGN(MAX3((size_t) v + 1, - (size_t) LINE_MAX, - ALIGN(sizeof(struct nlmsghdr)) + ALIGN((size_t) MAX_AUDIT_MESSAGE_LENGTH)) + 1); - - if (!GREEDY_REALLOC(s->buffer, s->buffer_size, m)) - return log_oom(); - - iovec.iov_base = s->buffer; - iovec.iov_len = s->buffer_size - 1; /* Leave room for trailing NUL we add later */ - - n = recvmsg(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); - if (n < 0) { - if (errno == EINTR || errno == EAGAIN) - return 0; - - return log_error_errno(errno, "recvmsg() failed: %m"); - } - - CMSG_FOREACH(cmsg, &msghdr) { - - if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SCM_CREDENTIALS && - cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) - ucred = (struct ucred*) CMSG_DATA(cmsg); - else if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SCM_SECURITY) { - label = (char*) CMSG_DATA(cmsg); - label_len = cmsg->cmsg_len - CMSG_LEN(0); - } else if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SO_TIMESTAMP && - cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) - tv = (struct timeval*) CMSG_DATA(cmsg); - else if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SCM_RIGHTS) { - fds = (int*) CMSG_DATA(cmsg); - n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); - } - } - - /* And a trailing NUL, just in case */ - s->buffer[n] = 0; - - if (fd == s->syslog_fd) { - if (n > 0 && n_fds == 0) - server_process_syslog_message(s, strstrip(s->buffer), ucred, tv, label, label_len); - else if (n_fds > 0) - log_warning("Got file descriptors via syslog socket. Ignoring."); - - } else if (fd == s->native_fd) { - if (n > 0 && n_fds == 0) - server_process_native_message(s, s->buffer, n, ucred, tv, label, label_len); - else if (n == 0 && n_fds == 1) - server_process_native_file(s, fds[0], ucred, tv, label, label_len); - else if (n_fds > 0) - log_warning("Got too many file descriptors via native socket. Ignoring."); - - } else { - assert(fd == s->audit_fd); - - if (n > 0 && n_fds == 0) - server_process_audit_message(s, s->buffer, n, ucred, &sa, msghdr.msg_namelen); - else if (n_fds > 0) - log_warning("Got file descriptors via audit socket. Ignoring."); - } - - close_many(fds, n_fds); - return 0; -} - -static int dispatch_sigusr1(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) { - Server *s = userdata; - int r; - - assert(s); - - log_info("Received request to flush runtime journal from PID " PID_FMT, si->ssi_pid); - - server_flush_to_var(s); - server_sync(s); - server_vacuum(s, false, false); - - r = touch("/run/systemd/journal/flushed"); - if (r < 0) - log_warning_errno(r, "Failed to touch /run/systemd/journal/flushed, ignoring: %m"); - - return 0; -} - -static int dispatch_sigusr2(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) { - Server *s = userdata; - int r; - - assert(s); - - log_info("Received request to rotate journal from PID " PID_FMT, si->ssi_pid); - server_rotate(s); - server_vacuum(s, true, true); - - /* Let clients know when the most recent rotation happened. */ - r = write_timestamp_file_atomic("/run/systemd/journal/rotated", now(CLOCK_MONOTONIC)); - if (r < 0) - log_warning_errno(r, "Failed to write /run/systemd/journal/rotated, ignoring: %m"); - - return 0; -} - -static int dispatch_sigterm(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) { - Server *s = userdata; - - assert(s); - - log_received_signal(LOG_INFO, si); - - sd_event_exit(s->event, 0); - return 0; -} - -static int dispatch_sigrtmin1(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) { - Server *s = userdata; - int r; - - assert(s); - - log_debug("Received request to sync from PID " PID_FMT, si->ssi_pid); - - server_sync(s); - - /* Let clients know when the most recent sync happened. */ - r = write_timestamp_file_atomic("/run/systemd/journal/synced", now(CLOCK_MONOTONIC)); - if (r < 0) - log_warning_errno(r, "Failed to write /run/systemd/journal/synced, ignoring: %m"); - - return 0; -} - -static int setup_signals(Server *s) { - int r; - - assert(s); - - assert(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, SIGUSR1, SIGUSR2, SIGRTMIN+1, -1) >= 0); - - r = sd_event_add_signal(s->event, &s->sigusr1_event_source, SIGUSR1, dispatch_sigusr1, s); - if (r < 0) - return r; - - r = sd_event_add_signal(s->event, &s->sigusr2_event_source, SIGUSR2, dispatch_sigusr2, s); - if (r < 0) - return r; - - r = sd_event_add_signal(s->event, &s->sigterm_event_source, SIGTERM, dispatch_sigterm, s); - if (r < 0) - return r; - - /* Let's process SIGTERM late, so that we flush all queued - * messages to disk before we exit */ - r = sd_event_source_set_priority(s->sigterm_event_source, SD_EVENT_PRIORITY_NORMAL+20); - if (r < 0) - return r; - - /* When journald is invoked on the terminal (when debugging), - * it's useful if C-c is handled equivalent to SIGTERM. */ - r = sd_event_add_signal(s->event, &s->sigint_event_source, SIGINT, dispatch_sigterm, s); - if (r < 0) - return r; - - r = sd_event_source_set_priority(s->sigint_event_source, SD_EVENT_PRIORITY_NORMAL+20); - if (r < 0) - return r; - - /* SIGRTMIN+1 causes an immediate sync. We process this very - * late, so that everything else queued at this point is - * really written to disk. Clients can watch - * /run/systemd/journal/synced with inotify until its mtime - * changes to see when a sync happened. */ - r = sd_event_add_signal(s->event, &s->sigrtmin1_event_source, SIGRTMIN+1, dispatch_sigrtmin1, s); - if (r < 0) - return r; - - r = sd_event_source_set_priority(s->sigrtmin1_event_source, SD_EVENT_PRIORITY_NORMAL+15); - if (r < 0) - return r; - - return 0; -} - -static int server_parse_proc_cmdline(Server *s) { - _cleanup_free_ char *line = NULL; - const char *p; - int r; - - r = proc_cmdline(&line); - if (r < 0) { - log_warning_errno(r, "Failed to read /proc/cmdline, ignoring: %m"); - return 0; - } - - p = line; - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&p, &word, NULL, 0); - if (r < 0) - return log_error_errno(r, "Failed to parse journald syntax \"%s\": %m", line); - - if (r == 0) - break; - - if (startswith(word, "systemd.journald.forward_to_syslog=")) { - r = parse_boolean(word + 35); - if (r < 0) - log_warning("Failed to parse forward to syslog switch %s. Ignoring.", word + 35); - else - s->forward_to_syslog = r; - } else if (startswith(word, "systemd.journald.forward_to_kmsg=")) { - r = parse_boolean(word + 33); - if (r < 0) - log_warning("Failed to parse forward to kmsg switch %s. Ignoring.", word + 33); - else - s->forward_to_kmsg = r; - } else if (startswith(word, "systemd.journald.forward_to_console=")) { - r = parse_boolean(word + 36); - if (r < 0) - log_warning("Failed to parse forward to console switch %s. Ignoring.", word + 36); - else - s->forward_to_console = r; - } else if (startswith(word, "systemd.journald.forward_to_wall=")) { - r = parse_boolean(word + 33); - if (r < 0) - log_warning("Failed to parse forward to wall switch %s. Ignoring.", word + 33); - else - s->forward_to_wall = r; - } else if (startswith(word, "systemd.journald")) - log_warning("Invalid systemd.journald parameter. Ignoring."); - } - - /* do not warn about state here, since probably systemd already did */ - return 0; -} - -static int server_parse_config_file(Server *s) { - assert(s); - - return config_parse_many(PKGSYSCONFDIR "/journald.conf", - CONF_PATHS_NULSTR("systemd/journald.conf.d"), - "Journal\0", - config_item_perf_lookup, journald_gperf_lookup, - false, s); -} - -static int server_dispatch_sync(sd_event_source *es, usec_t t, void *userdata) { - Server *s = userdata; - - assert(s); - - server_sync(s); - return 0; -} - -int server_schedule_sync(Server *s, int priority) { - int r; - - assert(s); - - if (priority <= LOG_CRIT) { - /* Immediately sync to disk when this is of priority CRIT, ALERT, EMERG */ - server_sync(s); - return 0; - } - - if (s->sync_scheduled) - return 0; - - if (s->sync_interval_usec > 0) { - usec_t when; - - r = sd_event_now(s->event, CLOCK_MONOTONIC, &when); - if (r < 0) - return r; - - when += s->sync_interval_usec; - - if (!s->sync_event_source) { - r = sd_event_add_time( - s->event, - &s->sync_event_source, - CLOCK_MONOTONIC, - when, 0, - server_dispatch_sync, s); - if (r < 0) - return r; - - r = sd_event_source_set_priority(s->sync_event_source, SD_EVENT_PRIORITY_IMPORTANT); - } else { - r = sd_event_source_set_time(s->sync_event_source, when); - if (r < 0) - return r; - - r = sd_event_source_set_enabled(s->sync_event_source, SD_EVENT_ONESHOT); - } - if (r < 0) - return r; - - s->sync_scheduled = true; - } - - return 0; -} - -static int dispatch_hostname_change(sd_event_source *es, int fd, uint32_t revents, void *userdata) { - Server *s = userdata; - - assert(s); - - server_cache_hostname(s); - return 0; -} - -static int server_open_hostname(Server *s) { - int r; - - assert(s); - - s->hostname_fd = open("/proc/sys/kernel/hostname", O_RDONLY|O_CLOEXEC|O_NDELAY|O_NOCTTY); - if (s->hostname_fd < 0) - return log_error_errno(errno, "Failed to open /proc/sys/kernel/hostname: %m"); - - r = sd_event_add_io(s->event, &s->hostname_event_source, s->hostname_fd, 0, dispatch_hostname_change, s); - if (r < 0) { - /* kernels prior to 3.2 don't support polling this file. Ignore - * the failure. */ - if (r == -EPERM) { - log_warning_errno(r, "Failed to register hostname fd in event loop, ignoring: %m"); - s->hostname_fd = safe_close(s->hostname_fd); - return 0; - } - - return log_error_errno(r, "Failed to register hostname fd in event loop: %m"); - } - - r = sd_event_source_set_priority(s->hostname_event_source, SD_EVENT_PRIORITY_IMPORTANT-10); - if (r < 0) - return log_error_errno(r, "Failed to adjust priority of host name event source: %m"); - - return 0; -} - -static int dispatch_notify_event(sd_event_source *es, int fd, uint32_t revents, void *userdata) { - Server *s = userdata; - int r; - - assert(s); - assert(s->notify_event_source == es); - assert(s->notify_fd == fd); - - /* The $NOTIFY_SOCKET is writable again, now send exactly one - * message on it. Either it's the wtachdog event, the initial - * READY=1 event or an stdout stream event. If there's nothing - * to write anymore, turn our event source off. The next time - * there's something to send it will be turned on again. */ - - if (!s->sent_notify_ready) { - static const char p[] = - "READY=1\n" - "STATUS=Processing requests..."; - ssize_t l; - - l = send(s->notify_fd, p, strlen(p), MSG_DONTWAIT); - if (l < 0) { - if (errno == EAGAIN) - return 0; - - return log_error_errno(errno, "Failed to send READY=1 notification message: %m"); - } - - s->sent_notify_ready = true; - log_debug("Sent READY=1 notification."); - - } else if (s->send_watchdog) { - - static const char p[] = - "WATCHDOG=1"; - - ssize_t l; - - l = send(s->notify_fd, p, strlen(p), MSG_DONTWAIT); - if (l < 0) { - if (errno == EAGAIN) - return 0; - - return log_error_errno(errno, "Failed to send WATCHDOG=1 notification message: %m"); - } - - s->send_watchdog = false; - log_debug("Sent WATCHDOG=1 notification."); - - } else if (s->stdout_streams_notify_queue) - /* Dispatch one stream notification event */ - stdout_stream_send_notify(s->stdout_streams_notify_queue); - - /* Leave us enabled if there's still more to to do. */ - if (s->send_watchdog || s->stdout_streams_notify_queue) - return 0; - - /* There was nothing to do anymore, let's turn ourselves off. */ - r = sd_event_source_set_enabled(es, SD_EVENT_OFF); - if (r < 0) - return log_error_errno(r, "Failed to turn off notify event source: %m"); - - return 0; -} - -static int dispatch_watchdog(sd_event_source *es, uint64_t usec, void *userdata) { - Server *s = userdata; - int r; - - assert(s); - - s->send_watchdog = true; - - r = sd_event_source_set_enabled(s->notify_event_source, SD_EVENT_ON); - if (r < 0) - log_warning_errno(r, "Failed to turn on notify event source: %m"); - - r = sd_event_source_set_time(s->watchdog_event_source, usec + s->watchdog_usec / 2); - if (r < 0) - return log_error_errno(r, "Failed to restart watchdog event source: %m"); - - r = sd_event_source_set_enabled(s->watchdog_event_source, SD_EVENT_ON); - if (r < 0) - return log_error_errno(r, "Failed to enable watchdog event source: %m"); - - return 0; -} - -static int server_connect_notify(Server *s) { - union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - }; - const char *e; - int r; - - assert(s); - assert(s->notify_fd < 0); - assert(!s->notify_event_source); - - /* - So here's the problem: we'd like to send notification - messages to PID 1, but we cannot do that via sd_notify(), - since that's synchronous, and we might end up blocking on - it. Specifically: given that PID 1 might block on - dbus-daemon during IPC, and dbus-daemon is logging to us, - and might hence block on us, we might end up in a deadlock - if we block on sending PID 1 notification messages — by - generating a full blocking circle. To avoid this, let's - create a non-blocking socket, and connect it to the - notification socket, and then wait for POLLOUT before we - send anything. This should efficiently avoid any deadlocks, - as we'll never block on PID 1, hence PID 1 can safely block - on dbus-daemon which can safely block on us again. - - Don't think that this issue is real? It is, see: - https://github.com/systemd/systemd/issues/1505 - */ - - e = getenv("NOTIFY_SOCKET"); - if (!e) - return 0; - - if ((e[0] != '@' && e[0] != '/') || e[1] == 0) { - log_error("NOTIFY_SOCKET set to an invalid value: %s", e); - return -EINVAL; - } - - if (strlen(e) > sizeof(sa.un.sun_path)) { - log_error("NOTIFY_SOCKET path too long: %s", e); - return -EINVAL; - } - - s->notify_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (s->notify_fd < 0) - return log_error_errno(errno, "Failed to create notify socket: %m"); - - (void) fd_inc_sndbuf(s->notify_fd, NOTIFY_SNDBUF_SIZE); - - strncpy(sa.un.sun_path, e, sizeof(sa.un.sun_path)); - if (sa.un.sun_path[0] == '@') - sa.un.sun_path[0] = 0; - - r = connect(s->notify_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); - if (r < 0) - return log_error_errno(errno, "Failed to connect to notify socket: %m"); - - r = sd_event_add_io(s->event, &s->notify_event_source, s->notify_fd, EPOLLOUT, dispatch_notify_event, s); - if (r < 0) - return log_error_errno(r, "Failed to watch notification socket: %m"); - - if (sd_watchdog_enabled(false, &s->watchdog_usec) > 0) { - s->send_watchdog = true; - - r = sd_event_add_time(s->event, &s->watchdog_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + s->watchdog_usec/2, s->watchdog_usec/4, dispatch_watchdog, s); - if (r < 0) - return log_error_errno(r, "Failed to add watchdog time event: %m"); - } - - /* This should fire pretty soon, which we'll use to send the - * READY=1 event. */ - - return 0; -} - -int server_init(Server *s) { - _cleanup_fdset_free_ FDSet *fds = NULL; - int n, r, fd; - bool no_sockets; - - assert(s); - - zero(*s); - s->syslog_fd = s->native_fd = s->stdout_fd = s->dev_kmsg_fd = s->audit_fd = s->hostname_fd = s->notify_fd = -1; - s->compress = true; - s->seal = true; - - s->watchdog_usec = USEC_INFINITY; - - s->sync_interval_usec = DEFAULT_SYNC_INTERVAL_USEC; - s->sync_scheduled = false; - - s->rate_limit_interval = DEFAULT_RATE_LIMIT_INTERVAL; - s->rate_limit_burst = DEFAULT_RATE_LIMIT_BURST; - - s->forward_to_wall = true; - - s->max_file_usec = DEFAULT_MAX_FILE_USEC; - - s->max_level_store = LOG_DEBUG; - s->max_level_syslog = LOG_DEBUG; - s->max_level_kmsg = LOG_NOTICE; - s->max_level_console = LOG_INFO; - s->max_level_wall = LOG_EMERG; - - journal_reset_metrics(&s->system_metrics); - journal_reset_metrics(&s->runtime_metrics); - - server_parse_config_file(s); - server_parse_proc_cmdline(s); - - if (!!s->rate_limit_interval ^ !!s->rate_limit_burst) { - log_debug("Setting both rate limit interval and burst from "USEC_FMT",%u to 0,0", - s->rate_limit_interval, s->rate_limit_burst); - s->rate_limit_interval = s->rate_limit_burst = 0; - } - - (void) mkdir_p("/run/systemd/journal", 0755); - - s->user_journals = ordered_hashmap_new(NULL); - if (!s->user_journals) - return log_oom(); - - s->mmap = mmap_cache_new(); - if (!s->mmap) - return log_oom(); - - s->deferred_closes = set_new(NULL); - if (!s->deferred_closes) - return log_oom(); - - r = sd_event_default(&s->event); - if (r < 0) - return log_error_errno(r, "Failed to create event loop: %m"); - - n = sd_listen_fds(true); - if (n < 0) - return log_error_errno(n, "Failed to read listening file descriptors from environment: %m"); - - for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { - - if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/run/systemd/journal/socket", 0) > 0) { - - if (s->native_fd >= 0) { - log_error("Too many native sockets passed."); - return -EINVAL; - } - - s->native_fd = fd; - - } else if (sd_is_socket_unix(fd, SOCK_STREAM, 1, "/run/systemd/journal/stdout", 0) > 0) { - - if (s->stdout_fd >= 0) { - log_error("Too many stdout sockets passed."); - return -EINVAL; - } - - s->stdout_fd = fd; - - } else if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/dev/log", 0) > 0 || - sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/run/systemd/journal/dev-log", 0) > 0) { - - if (s->syslog_fd >= 0) { - log_error("Too many /dev/log sockets passed."); - return -EINVAL; - } - - s->syslog_fd = fd; - - } else if (sd_is_socket(fd, AF_NETLINK, SOCK_RAW, -1) > 0) { - - if (s->audit_fd >= 0) { - log_error("Too many audit sockets passed."); - return -EINVAL; - } - - s->audit_fd = fd; - - } else { - - if (!fds) { - fds = fdset_new(); - if (!fds) - return log_oom(); - } - - r = fdset_put(fds, fd); - if (r < 0) - return log_oom(); - } - } - - /* Try to restore streams, but don't bother if this fails */ - (void) server_restore_streams(s, fds); - - if (fdset_size(fds) > 0) { - log_warning("%u unknown file descriptors passed, closing.", fdset_size(fds)); - fds = fdset_free(fds); - } - - no_sockets = s->native_fd < 0 && s->stdout_fd < 0 && s->syslog_fd < 0 && s->audit_fd < 0; - - /* always open stdout, syslog, native, and kmsg sockets */ - - /* systemd-journald.socket: /run/systemd/journal/stdout */ - r = server_open_stdout_socket(s); - if (r < 0) - return r; - - /* systemd-journald-dev-log.socket: /run/systemd/journal/dev-log */ - r = server_open_syslog_socket(s); - if (r < 0) - return r; - - /* systemd-journald.socket: /run/systemd/journal/socket */ - r = server_open_native_socket(s); - if (r < 0) - return r; - - /* /dev/ksmg */ - r = server_open_dev_kmsg(s); - if (r < 0) - return r; - - /* Unless we got *some* sockets and not audit, open audit socket */ - if (s->audit_fd >= 0 || no_sockets) { - r = server_open_audit(s); - if (r < 0) - return r; - } - - r = server_open_kernel_seqnum(s); - if (r < 0) - return r; - - r = server_open_hostname(s); - if (r < 0) - return r; - - r = setup_signals(s); - if (r < 0) - return r; - - s->udev = udev_new(); - if (!s->udev) - return -ENOMEM; - - s->rate_limit = journal_rate_limit_new(s->rate_limit_interval, s->rate_limit_burst); - if (!s->rate_limit) - return -ENOMEM; - - r = cg_get_root_path(&s->cgroup_root); - if (r < 0) - return r; - - server_cache_hostname(s); - server_cache_boot_id(s); - server_cache_machine_id(s); - - (void) server_connect_notify(s); - - return system_journal_open(s, false); -} - -void server_maybe_append_tags(Server *s) { -#ifdef HAVE_GCRYPT - JournalFile *f; - Iterator i; - usec_t n; - - n = now(CLOCK_REALTIME); - - if (s->system_journal) - journal_file_maybe_append_tag(s->system_journal, n); - - ORDERED_HASHMAP_FOREACH(f, s->user_journals, i) - journal_file_maybe_append_tag(f, n); -#endif -} - -void server_done(Server *s) { - JournalFile *f; - assert(s); - - if (s->deferred_closes) { - journal_file_close_set(s->deferred_closes); - set_free(s->deferred_closes); - } - - while (s->stdout_streams) - stdout_stream_free(s->stdout_streams); - - if (s->system_journal) - (void) journal_file_close(s->system_journal); - - if (s->runtime_journal) - (void) journal_file_close(s->runtime_journal); - - while ((f = ordered_hashmap_steal_first(s->user_journals))) - (void) journal_file_close(f); - - ordered_hashmap_free(s->user_journals); - - sd_event_source_unref(s->syslog_event_source); - sd_event_source_unref(s->native_event_source); - sd_event_source_unref(s->stdout_event_source); - sd_event_source_unref(s->dev_kmsg_event_source); - sd_event_source_unref(s->audit_event_source); - sd_event_source_unref(s->sync_event_source); - sd_event_source_unref(s->sigusr1_event_source); - sd_event_source_unref(s->sigusr2_event_source); - sd_event_source_unref(s->sigterm_event_source); - sd_event_source_unref(s->sigint_event_source); - sd_event_source_unref(s->sigrtmin1_event_source); - sd_event_source_unref(s->hostname_event_source); - sd_event_source_unref(s->notify_event_source); - sd_event_source_unref(s->watchdog_event_source); - sd_event_unref(s->event); - - safe_close(s->syslog_fd); - safe_close(s->native_fd); - safe_close(s->stdout_fd); - safe_close(s->dev_kmsg_fd); - safe_close(s->audit_fd); - safe_close(s->hostname_fd); - safe_close(s->notify_fd); - - if (s->rate_limit) - journal_rate_limit_free(s->rate_limit); - - if (s->kernel_seqnum) - munmap(s->kernel_seqnum, sizeof(uint64_t)); - - free(s->buffer); - free(s->tty_path); - free(s->cgroup_root); - free(s->hostname_field); - - if (s->mmap) - mmap_cache_unref(s->mmap); - - udev_unref(s->udev); -} - -static const char* const storage_table[_STORAGE_MAX] = { - [STORAGE_AUTO] = "auto", - [STORAGE_VOLATILE] = "volatile", - [STORAGE_PERSISTENT] = "persistent", - [STORAGE_NONE] = "none" -}; - -DEFINE_STRING_TABLE_LOOKUP(storage, Storage); -DEFINE_CONFIG_PARSE_ENUM(config_parse_storage, storage, Storage, "Failed to parse storage setting"); - -static const char* const split_mode_table[_SPLIT_MAX] = { - [SPLIT_LOGIN] = "login", - [SPLIT_UID] = "uid", - [SPLIT_NONE] = "none", -}; - -DEFINE_STRING_TABLE_LOOKUP(split_mode, SplitMode); -DEFINE_CONFIG_PARSE_ENUM(config_parse_split_mode, split_mode, SplitMode, "Failed to parse split mode setting"); diff --git a/src/journal/journald-server.h b/src/journal/journald-server.h deleted file mode 100644 index e025a4cf90..0000000000 --- a/src/journal/journald-server.h +++ /dev/null @@ -1,186 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 - -#include "sd-event.h" - -typedef struct Server Server; - -#include "hashmap.h" -#include "journal-file.h" -#include "journald-rate-limit.h" -#include "journald-stream.h" -#include "list.h" - -typedef enum Storage { - STORAGE_AUTO, - STORAGE_VOLATILE, - STORAGE_PERSISTENT, - STORAGE_NONE, - _STORAGE_MAX, - _STORAGE_INVALID = -1 -} Storage; - -typedef enum SplitMode { - SPLIT_UID, - SPLIT_LOGIN, - SPLIT_NONE, - _SPLIT_MAX, - _SPLIT_INVALID = -1 -} SplitMode; - -struct Server { - int syslog_fd; - int native_fd; - int stdout_fd; - int dev_kmsg_fd; - int audit_fd; - int hostname_fd; - int notify_fd; - - sd_event *event; - - sd_event_source *syslog_event_source; - sd_event_source *native_event_source; - sd_event_source *stdout_event_source; - sd_event_source *dev_kmsg_event_source; - sd_event_source *audit_event_source; - sd_event_source *sync_event_source; - sd_event_source *sigusr1_event_source; - sd_event_source *sigusr2_event_source; - sd_event_source *sigterm_event_source; - sd_event_source *sigint_event_source; - sd_event_source *sigrtmin1_event_source; - sd_event_source *hostname_event_source; - sd_event_source *notify_event_source; - sd_event_source *watchdog_event_source; - - JournalFile *runtime_journal; - JournalFile *system_journal; - OrderedHashmap *user_journals; - - uint64_t seqnum; - - char *buffer; - size_t buffer_size; - - JournalRateLimit *rate_limit; - usec_t sync_interval_usec; - usec_t rate_limit_interval; - unsigned rate_limit_burst; - - JournalMetrics runtime_metrics; - JournalMetrics system_metrics; - - bool compress; - bool seal; - - bool forward_to_kmsg; - bool forward_to_syslog; - bool forward_to_console; - bool forward_to_wall; - - unsigned n_forward_syslog_missed; - usec_t last_warn_forward_syslog_missed; - - uint64_t cached_space_available; - uint64_t cached_space_limit; - usec_t cached_space_timestamp; - - uint64_t var_available_timestamp; - - usec_t max_retention_usec; - usec_t max_file_usec; - usec_t oldest_file_usec; - - LIST_HEAD(StdoutStream, stdout_streams); - LIST_HEAD(StdoutStream, stdout_streams_notify_queue); - unsigned n_stdout_streams; - - char *tty_path; - - int max_level_store; - int max_level_syslog; - int max_level_kmsg; - int max_level_console; - int max_level_wall; - - Storage storage; - SplitMode split_mode; - - MMapCache *mmap; - - Set *deferred_closes; - - struct udev *udev; - - uint64_t *kernel_seqnum; - bool dev_kmsg_readable:1; - - bool send_watchdog:1; - bool sent_notify_ready:1; - bool sync_scheduled:1; - - char machine_id_field[sizeof("_MACHINE_ID=") + 32]; - char boot_id_field[sizeof("_BOOT_ID=") + 32]; - char *hostname_field; - - /* Cached cgroup root, so that we don't have to query that all the time */ - char *cgroup_root; - - usec_t watchdog_usec; -}; - -#define SERVER_MACHINE_ID(s) ((s)->machine_id_field + strlen("_MACHINE_ID=")) - -#define N_IOVEC_META_FIELDS 20 -#define N_IOVEC_KERNEL_FIELDS 64 -#define N_IOVEC_UDEV_FIELDS 32 -#define N_IOVEC_OBJECT_FIELDS 12 -#define N_IOVEC_PAYLOAD_FIELDS 15 - -void server_dispatch_message(Server *s, struct iovec *iovec, unsigned n, unsigned m, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len, const char *unit_id, int priority, pid_t object_pid); -void server_driver_message(Server *s, sd_id128_t message_id, const char *format, ...) _printf_(3,0) _sentinel_; - -/* gperf lookup function */ -const struct ConfigPerfItem* journald_gperf_lookup(const char *key, unsigned length); - -int config_parse_storage(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); - -const char *storage_to_string(Storage s) _const_; -Storage storage_from_string(const char *s) _pure_; - -int config_parse_split_mode(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); - -const char *split_mode_to_string(SplitMode s) _const_; -SplitMode split_mode_from_string(const char *s) _pure_; - -int server_init(Server *s); -void server_done(Server *s); -void server_sync(Server *s); -int server_vacuum(Server *s, bool verbose, bool patch_min_use); -void server_rotate(Server *s); -int server_schedule_sync(Server *s, int priority); -int server_flush_to_var(Server *s); -void server_maybe_append_tags(Server *s); -int server_process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userdata); diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c deleted file mode 100644 index 4ad16ee41c..0000000000 --- a/src/journal/journald-stream.c +++ /dev/null @@ -1,785 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 - -#ifdef HAVE_SELINUX -#include -#endif - -#include "sd-daemon.h" -#include "sd-event.h" - -#include "alloc-util.h" -#include "dirent-util.h" -#include "escape.h" -#include "fd-util.h" -#include "fileio.h" -#include "io-util.h" -#include "journald-console.h" -#include "journald-kmsg.h" -#include "journald-server.h" -#include "journald-stream.h" -#include "journald-syslog.h" -#include "journald-wall.h" -#include "mkdir.h" -#include "parse-util.h" -#include "selinux-util.h" -#include "socket-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "syslog-util.h" - -#define STDOUT_STREAMS_MAX 4096 - -typedef enum StdoutStreamState { - STDOUT_STREAM_IDENTIFIER, - STDOUT_STREAM_UNIT_ID, - STDOUT_STREAM_PRIORITY, - STDOUT_STREAM_LEVEL_PREFIX, - STDOUT_STREAM_FORWARD_TO_SYSLOG, - STDOUT_STREAM_FORWARD_TO_KMSG, - STDOUT_STREAM_FORWARD_TO_CONSOLE, - STDOUT_STREAM_RUNNING -} StdoutStreamState; - -struct StdoutStream { - Server *server; - StdoutStreamState state; - - int fd; - - struct ucred ucred; - char *label; - char *identifier; - char *unit_id; - int priority; - bool level_prefix:1; - bool forward_to_syslog:1; - bool forward_to_kmsg:1; - bool forward_to_console:1; - - bool fdstore:1; - bool in_notify_queue:1; - - char buffer[LINE_MAX+1]; - size_t length; - - sd_event_source *event_source; - - char *state_file; - - LIST_FIELDS(StdoutStream, stdout_stream); - LIST_FIELDS(StdoutStream, stdout_stream_notify_queue); -}; - -void stdout_stream_free(StdoutStream *s) { - if (!s) - return; - - if (s->server) { - assert(s->server->n_stdout_streams > 0); - s->server->n_stdout_streams--; - LIST_REMOVE(stdout_stream, s->server->stdout_streams, s); - - if (s->in_notify_queue) - LIST_REMOVE(stdout_stream_notify_queue, s->server->stdout_streams_notify_queue, s); - } - - if (s->event_source) { - sd_event_source_set_enabled(s->event_source, SD_EVENT_OFF); - s->event_source = sd_event_source_unref(s->event_source); - } - - safe_close(s->fd); - free(s->label); - free(s->identifier); - free(s->unit_id); - free(s->state_file); - - free(s); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(StdoutStream*, stdout_stream_free); - -static void stdout_stream_destroy(StdoutStream *s) { - if (!s) - return; - - if (s->state_file) - (void) unlink(s->state_file); - - stdout_stream_free(s); -} - -static int stdout_stream_save(StdoutStream *s) { - _cleanup_free_ char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(s); - - if (s->state != STDOUT_STREAM_RUNNING) - return 0; - - if (!s->state_file) { - struct stat st; - - r = fstat(s->fd, &st); - if (r < 0) - return log_warning_errno(errno, "Failed to stat connected stream: %m"); - - /* We use device and inode numbers as identifier for the stream */ - if (asprintf(&s->state_file, "/run/systemd/journal/streams/%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino) < 0) - return log_oom(); - } - - mkdir_p("/run/systemd/journal/streams", 0755); - - r = fopen_temporary(s->state_file, &f, &temp_path); - if (r < 0) - goto fail; - - fprintf(f, - "# This is private data. Do not parse\n" - "PRIORITY=%i\n" - "LEVEL_PREFIX=%i\n" - "FORWARD_TO_SYSLOG=%i\n" - "FORWARD_TO_KMSG=%i\n" - "FORWARD_TO_CONSOLE=%i\n", - s->priority, - s->level_prefix, - s->forward_to_syslog, - s->forward_to_kmsg, - s->forward_to_console); - - if (!isempty(s->identifier)) { - _cleanup_free_ char *escaped; - - escaped = cescape(s->identifier); - if (!escaped) { - r = -ENOMEM; - goto fail; - } - - fprintf(f, "IDENTIFIER=%s\n", escaped); - } - - if (!isempty(s->unit_id)) { - _cleanup_free_ char *escaped; - - escaped = cescape(s->unit_id); - if (!escaped) { - r = -ENOMEM; - goto fail; - } - - fprintf(f, "UNIT=%s\n", escaped); - } - - r = fflush_and_check(f); - if (r < 0) - goto fail; - - if (rename(temp_path, s->state_file) < 0) { - r = -errno; - goto fail; - } - - if (!s->fdstore && !s->in_notify_queue) { - LIST_PREPEND(stdout_stream_notify_queue, s->server->stdout_streams_notify_queue, s); - s->in_notify_queue = true; - - if (s->server->notify_event_source) { - r = sd_event_source_set_enabled(s->server->notify_event_source, SD_EVENT_ON); - if (r < 0) - log_warning_errno(r, "Failed to enable notify event source: %m"); - } - } - - return 0; - -fail: - (void) unlink(s->state_file); - - if (temp_path) - (void) unlink(temp_path); - - return log_error_errno(r, "Failed to save stream data %s: %m", s->state_file); -} - -static int stdout_stream_log(StdoutStream *s, const char *p) { - struct iovec iovec[N_IOVEC_META_FIELDS + 5]; - int priority; - char syslog_priority[] = "PRIORITY=\0"; - char syslog_facility[sizeof("SYSLOG_FACILITY=")-1 + DECIMAL_STR_MAX(int) + 1]; - _cleanup_free_ char *message = NULL, *syslog_identifier = NULL; - unsigned n = 0; - size_t label_len; - - assert(s); - assert(p); - - priority = s->priority; - - if (s->level_prefix) - syslog_parse_priority(&p, &priority, false); - - if (isempty(p)) - return 0; - - if (s->forward_to_syslog || s->server->forward_to_syslog) - server_forward_syslog(s->server, syslog_fixup_facility(priority), s->identifier, p, &s->ucred, NULL); - - if (s->forward_to_kmsg || s->server->forward_to_kmsg) - server_forward_kmsg(s->server, priority, s->identifier, p, &s->ucred); - - if (s->forward_to_console || s->server->forward_to_console) - server_forward_console(s->server, priority, s->identifier, p, &s->ucred); - - if (s->server->forward_to_wall) - server_forward_wall(s->server, priority, s->identifier, p, &s->ucred); - - IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=stdout"); - - syslog_priority[strlen("PRIORITY=")] = '0' + LOG_PRI(priority); - IOVEC_SET_STRING(iovec[n++], syslog_priority); - - if (priority & LOG_FACMASK) { - xsprintf(syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)); - IOVEC_SET_STRING(iovec[n++], syslog_facility); - } - - if (s->identifier) { - syslog_identifier = strappend("SYSLOG_IDENTIFIER=", s->identifier); - if (syslog_identifier) - IOVEC_SET_STRING(iovec[n++], syslog_identifier); - } - - message = strappend("MESSAGE=", p); - if (message) - IOVEC_SET_STRING(iovec[n++], message); - - label_len = s->label ? strlen(s->label) : 0; - server_dispatch_message(s->server, iovec, n, ELEMENTSOF(iovec), &s->ucred, NULL, s->label, label_len, s->unit_id, priority, 0); - return 0; -} - -static int stdout_stream_line(StdoutStream *s, char *p) { - int r; - char *orig; - - assert(s); - assert(p); - - orig = p; - p = strstrip(p); - - switch (s->state) { - - case STDOUT_STREAM_IDENTIFIER: - if (isempty(p)) - s->identifier = NULL; - else { - s->identifier = strdup(p); - if (!s->identifier) - return log_oom(); - } - - s->state = STDOUT_STREAM_UNIT_ID; - return 0; - - case STDOUT_STREAM_UNIT_ID: - if (s->ucred.uid == 0) { - if (isempty(p)) - s->unit_id = NULL; - else { - s->unit_id = strdup(p); - if (!s->unit_id) - return log_oom(); - } - } - - s->state = STDOUT_STREAM_PRIORITY; - return 0; - - case STDOUT_STREAM_PRIORITY: - r = safe_atoi(p, &s->priority); - if (r < 0 || s->priority < 0 || s->priority > 999) { - log_warning("Failed to parse log priority line."); - return -EINVAL; - } - - s->state = STDOUT_STREAM_LEVEL_PREFIX; - return 0; - - case STDOUT_STREAM_LEVEL_PREFIX: - r = parse_boolean(p); - if (r < 0) { - log_warning("Failed to parse level prefix line."); - return -EINVAL; - } - - s->level_prefix = !!r; - s->state = STDOUT_STREAM_FORWARD_TO_SYSLOG; - return 0; - - case STDOUT_STREAM_FORWARD_TO_SYSLOG: - r = parse_boolean(p); - if (r < 0) { - log_warning("Failed to parse forward to syslog line."); - return -EINVAL; - } - - s->forward_to_syslog = !!r; - s->state = STDOUT_STREAM_FORWARD_TO_KMSG; - return 0; - - case STDOUT_STREAM_FORWARD_TO_KMSG: - r = parse_boolean(p); - if (r < 0) { - log_warning("Failed to parse copy to kmsg line."); - return -EINVAL; - } - - s->forward_to_kmsg = !!r; - s->state = STDOUT_STREAM_FORWARD_TO_CONSOLE; - return 0; - - case STDOUT_STREAM_FORWARD_TO_CONSOLE: - r = parse_boolean(p); - if (r < 0) { - log_warning("Failed to parse copy to console line."); - return -EINVAL; - } - - s->forward_to_console = !!r; - s->state = STDOUT_STREAM_RUNNING; - - /* Try to save the stream, so that journald can be restarted and we can recover */ - (void) stdout_stream_save(s); - return 0; - - case STDOUT_STREAM_RUNNING: - return stdout_stream_log(s, orig); - } - - assert_not_reached("Unknown stream state"); -} - -static int stdout_stream_scan(StdoutStream *s, bool force_flush) { - char *p; - size_t remaining; - int r; - - assert(s); - - p = s->buffer; - remaining = s->length; - for (;;) { - char *end; - size_t skip; - - end = memchr(p, '\n', remaining); - if (end) - skip = end - p + 1; - else if (remaining >= sizeof(s->buffer) - 1) { - end = p + sizeof(s->buffer) - 1; - skip = remaining; - } else - break; - - *end = 0; - - r = stdout_stream_line(s, p); - if (r < 0) - return r; - - remaining -= skip; - p += skip; - } - - if (force_flush && remaining > 0) { - p[remaining] = 0; - r = stdout_stream_line(s, p); - if (r < 0) - return r; - - p += remaining; - remaining = 0; - } - - if (p > s->buffer) { - memmove(s->buffer, p, remaining); - s->length = remaining; - } - - return 0; -} - -static int stdout_stream_process(sd_event_source *es, int fd, uint32_t revents, void *userdata) { - StdoutStream *s = userdata; - ssize_t l; - int r; - - assert(s); - - if ((revents|EPOLLIN|EPOLLHUP) != (EPOLLIN|EPOLLHUP)) { - log_error("Got invalid event from epoll for stdout stream: %"PRIx32, revents); - goto terminate; - } - - l = read(s->fd, s->buffer+s->length, sizeof(s->buffer)-1-s->length); - if (l < 0) { - - if (errno == EAGAIN) - return 0; - - log_warning_errno(errno, "Failed to read from stream: %m"); - goto terminate; - } - - if (l == 0) { - stdout_stream_scan(s, true); - goto terminate; - } - - s->length += l; - r = stdout_stream_scan(s, false); - if (r < 0) - goto terminate; - - return 1; - -terminate: - stdout_stream_destroy(s); - return 0; -} - -static int stdout_stream_install(Server *s, int fd, StdoutStream **ret) { - _cleanup_(stdout_stream_freep) StdoutStream *stream = NULL; - int r; - - assert(s); - assert(fd >= 0); - - stream = new0(StdoutStream, 1); - if (!stream) - return log_oom(); - - stream->fd = -1; - stream->priority = LOG_INFO; - - r = getpeercred(fd, &stream->ucred); - if (r < 0) - return log_error_errno(r, "Failed to determine peer credentials: %m"); - - if (mac_selinux_have()) { - r = getpeersec(fd, &stream->label); - if (r < 0 && r != -EOPNOTSUPP) - (void) log_warning_errno(r, "Failed to determine peer security context: %m"); - } - - (void) shutdown(fd, SHUT_WR); - - r = sd_event_add_io(s->event, &stream->event_source, fd, EPOLLIN, stdout_stream_process, stream); - if (r < 0) - return log_error_errno(r, "Failed to add stream to event loop: %m"); - - r = sd_event_source_set_priority(stream->event_source, SD_EVENT_PRIORITY_NORMAL+5); - if (r < 0) - return log_error_errno(r, "Failed to adjust stdout event source priority: %m"); - - stream->fd = fd; - - stream->server = s; - LIST_PREPEND(stdout_stream, s->stdout_streams, stream); - s->n_stdout_streams++; - - if (ret) - *ret = stream; - - stream = NULL; - - return 0; -} - -static int stdout_stream_new(sd_event_source *es, int listen_fd, uint32_t revents, void *userdata) { - _cleanup_close_ int fd = -1; - Server *s = userdata; - int r; - - assert(s); - - if (revents != EPOLLIN) { - log_error("Got invalid event from epoll for stdout server fd: %"PRIx32, revents); - return -EIO; - } - - fd = accept4(s->stdout_fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); - if (fd < 0) { - if (errno == EAGAIN) - return 0; - - return log_error_errno(errno, "Failed to accept stdout connection: %m"); - } - - if (s->n_stdout_streams >= STDOUT_STREAMS_MAX) { - log_warning("Too many stdout streams, refusing connection."); - return 0; - } - - r = stdout_stream_install(s, fd, NULL); - if (r < 0) - return r; - - fd = -1; - return 0; -} - -static int stdout_stream_load(StdoutStream *stream, const char *fname) { - _cleanup_free_ char - *priority = NULL, - *level_prefix = NULL, - *forward_to_syslog = NULL, - *forward_to_kmsg = NULL, - *forward_to_console = NULL; - int r; - - assert(stream); - assert(fname); - - if (!stream->state_file) { - stream->state_file = strappend("/run/systemd/journal/streams/", fname); - if (!stream->state_file) - return log_oom(); - } - - r = parse_env_file(stream->state_file, NEWLINE, - "PRIORITY", &priority, - "LEVEL_PREFIX", &level_prefix, - "FORWARD_TO_SYSLOG", &forward_to_syslog, - "FORWARD_TO_KMSG", &forward_to_kmsg, - "FORWARD_TO_CONSOLE", &forward_to_console, - "IDENTIFIER", &stream->identifier, - "UNIT", &stream->unit_id, - NULL); - if (r < 0) - return log_error_errno(r, "Failed to read: %s", stream->state_file); - - if (priority) { - int p; - - p = log_level_from_string(priority); - if (p >= 0) - stream->priority = p; - } - - if (level_prefix) { - r = parse_boolean(level_prefix); - if (r >= 0) - stream->level_prefix = r; - } - - if (forward_to_syslog) { - r = parse_boolean(forward_to_syslog); - if (r >= 0) - stream->forward_to_syslog = r; - } - - if (forward_to_kmsg) { - r = parse_boolean(forward_to_kmsg); - if (r >= 0) - stream->forward_to_kmsg = r; - } - - if (forward_to_console) { - r = parse_boolean(forward_to_console); - if (r >= 0) - stream->forward_to_console = r; - } - - return 0; -} - -static int stdout_stream_restore(Server *s, const char *fname, int fd) { - StdoutStream *stream; - int r; - - assert(s); - assert(fname); - assert(fd >= 0); - - if (s->n_stdout_streams >= STDOUT_STREAMS_MAX) { - log_warning("Too many stdout streams, refusing restoring of stream."); - return -ENOBUFS; - } - - r = stdout_stream_install(s, fd, &stream); - if (r < 0) - return r; - - stream->state = STDOUT_STREAM_RUNNING; - stream->fdstore = true; - - /* Ignore all parsing errors */ - (void) stdout_stream_load(stream, fname); - - return 0; -} - -int server_restore_streams(Server *s, FDSet *fds) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - int r; - - d = opendir("/run/systemd/journal/streams"); - if (!d) { - if (errno == ENOENT) - return 0; - - return log_warning_errno(errno, "Failed to enumerate /run/systemd/journal/streams: %m"); - } - - FOREACH_DIRENT(de, d, goto fail) { - unsigned long st_dev, st_ino; - bool found = false; - Iterator i; - int fd; - - if (sscanf(de->d_name, "%lu:%lu", &st_dev, &st_ino) != 2) - continue; - - FDSET_FOREACH(fd, fds, i) { - struct stat st; - - if (fstat(fd, &st) < 0) - return log_error_errno(errno, "Failed to stat %s: %m", de->d_name); - - if (S_ISSOCK(st.st_mode) && st.st_dev == st_dev && st.st_ino == st_ino) { - found = true; - break; - } - } - - if (!found) { - /* No file descriptor? Then let's delete the state file */ - log_debug("Cannot restore stream file %s", de->d_name); - unlinkat(dirfd(d), de->d_name, 0); - continue; - } - - fdset_remove(fds, fd); - - r = stdout_stream_restore(s, de->d_name, fd); - if (r < 0) - safe_close(fd); - } - - return 0; - -fail: - return log_error_errno(errno, "Failed to read streams directory: %m"); -} - -int server_open_stdout_socket(Server *s) { - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/journal/stdout", - }; - int r; - - assert(s); - - if (s->stdout_fd < 0) { - s->stdout_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (s->stdout_fd < 0) - return log_error_errno(errno, "socket() failed: %m"); - - (void) unlink(sa.un.sun_path); - - r = bind(s->stdout_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); - if (r < 0) - return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path); - - (void) chmod(sa.un.sun_path, 0666); - - if (listen(s->stdout_fd, SOMAXCONN) < 0) - return log_error_errno(errno, "listen(%s) failed: %m", sa.un.sun_path); - } else - fd_nonblock(s->stdout_fd, 1); - - r = sd_event_add_io(s->event, &s->stdout_event_source, s->stdout_fd, EPOLLIN, stdout_stream_new, s); - if (r < 0) - return log_error_errno(r, "Failed to add stdout server fd to event source: %m"); - - r = sd_event_source_set_priority(s->stdout_event_source, SD_EVENT_PRIORITY_NORMAL+5); - if (r < 0) - return log_error_errno(r, "Failed to adjust priority of stdout server event source: %m"); - - return 0; -} - -void stdout_stream_send_notify(StdoutStream *s) { - struct iovec iovec = { - .iov_base = (char*) "FDSTORE=1", - .iov_len = strlen("FDSTORE=1"), - }; - struct msghdr msghdr = { - .msg_iov = &iovec, - .msg_iovlen = 1, - }; - struct cmsghdr *cmsg; - ssize_t l; - - assert(s); - assert(!s->fdstore); - assert(s->in_notify_queue); - assert(s->server); - assert(s->server->notify_fd >= 0); - - /* Store the connection fd in PID 1, so that we get it passed - * in again on next start */ - - msghdr.msg_controllen = CMSG_SPACE(sizeof(int)); - msghdr.msg_control = alloca0(msghdr.msg_controllen); - - cmsg = CMSG_FIRSTHDR(&msghdr); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - cmsg->cmsg_len = CMSG_LEN(sizeof(int)); - - memcpy(CMSG_DATA(cmsg), &s->fd, sizeof(int)); - - l = sendmsg(s->server->notify_fd, &msghdr, MSG_DONTWAIT|MSG_NOSIGNAL); - if (l < 0) { - if (errno == EAGAIN) - return; - - log_error_errno(errno, "Failed to send stream file descriptor to service manager: %m"); - } else { - log_debug("Successfully sent stream file descriptor to service manager."); - s->fdstore = 1; - } - - LIST_REMOVE(stdout_stream_notify_queue, s->server->stdout_streams_notify_queue, s); - s->in_notify_queue = false; - -} diff --git a/src/journal/journald-stream.h b/src/journal/journald-stream.h deleted file mode 100644 index db4c67fae3..0000000000 --- a/src/journal/journald-stream.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -typedef struct StdoutStream StdoutStream; - -#include "fdset.h" -#include "journald-server.h" - -int server_open_stdout_socket(Server *s); -int server_restore_streams(Server *s, FDSet *fds); - -void stdout_stream_free(StdoutStream *s); -void stdout_stream_send_notify(StdoutStream *s); diff --git a/src/journal/journald-syslog.c b/src/journal/journald-syslog.c deleted file mode 100644 index 0609b4b694..0000000000 --- a/src/journal/journald-syslog.c +++ /dev/null @@ -1,454 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include - -#include "sd-messages.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "io-util.h" -#include "journald-console.h" -#include "journald-kmsg.h" -#include "journald-server.h" -#include "journald-syslog.h" -#include "journald-wall.h" -#include "process-util.h" -#include "selinux-util.h" -#include "socket-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "syslog-util.h" - -/* Warn once every 30s if we missed syslog message */ -#define WARN_FORWARD_SYSLOG_MISSED_USEC (30 * USEC_PER_SEC) - -static void forward_syslog_iovec(Server *s, const struct iovec *iovec, unsigned n_iovec, const struct ucred *ucred, const struct timeval *tv) { - - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/journal/syslog", - }; - struct msghdr msghdr = { - .msg_iov = (struct iovec *) iovec, - .msg_iovlen = n_iovec, - .msg_name = (struct sockaddr*) &sa.sa, - .msg_namelen = SOCKADDR_UN_LEN(sa.un), - }; - struct cmsghdr *cmsg; - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; - } control; - - assert(s); - assert(iovec); - assert(n_iovec > 0); - - if (ucred) { - zero(control); - msghdr.msg_control = &control; - msghdr.msg_controllen = sizeof(control); - - cmsg = CMSG_FIRSTHDR(&msghdr); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_CREDENTIALS; - cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred)); - memcpy(CMSG_DATA(cmsg), ucred, sizeof(struct ucred)); - msghdr.msg_controllen = cmsg->cmsg_len; - } - - /* Forward the syslog message we received via /dev/log to - * /run/systemd/syslog. Unfortunately we currently can't set - * the SO_TIMESTAMP auxiliary data, and hence we don't. */ - - if (sendmsg(s->syslog_fd, &msghdr, MSG_NOSIGNAL) >= 0) - return; - - /* The socket is full? I guess the syslog implementation is - * too slow, and we shouldn't wait for that... */ - if (errno == EAGAIN) { - s->n_forward_syslog_missed++; - return; - } - - if (ucred && (errno == ESRCH || errno == EPERM)) { - struct ucred u; - - /* Hmm, presumably the sender process vanished - * by now, or we don't have CAP_SYS_AMDIN, so - * let's fix it as good as we can, and retry */ - - u = *ucred; - u.pid = getpid(); - memcpy(CMSG_DATA(cmsg), &u, sizeof(struct ucred)); - - if (sendmsg(s->syslog_fd, &msghdr, MSG_NOSIGNAL) >= 0) - return; - - if (errno == EAGAIN) { - s->n_forward_syslog_missed++; - return; - } - } - - if (errno != ENOENT) - log_debug_errno(errno, "Failed to forward syslog message: %m"); -} - -static void forward_syslog_raw(Server *s, int priority, const char *buffer, const struct ucred *ucred, const struct timeval *tv) { - struct iovec iovec; - - assert(s); - assert(buffer); - - if (LOG_PRI(priority) > s->max_level_syslog) - return; - - IOVEC_SET_STRING(iovec, buffer); - forward_syslog_iovec(s, &iovec, 1, ucred, tv); -} - -void server_forward_syslog(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred, const struct timeval *tv) { - struct iovec iovec[5]; - char header_priority[DECIMAL_STR_MAX(priority) + 3], header_time[64], - header_pid[sizeof("[]: ")-1 + DECIMAL_STR_MAX(pid_t) + 1]; - int n = 0; - time_t t; - struct tm *tm; - char *ident_buf = NULL; - - assert(s); - assert(priority >= 0); - assert(priority <= 999); - assert(message); - - if (LOG_PRI(priority) > s->max_level_syslog) - return; - - /* First: priority field */ - xsprintf(header_priority, "<%i>", priority); - IOVEC_SET_STRING(iovec[n++], header_priority); - - /* Second: timestamp */ - t = tv ? tv->tv_sec : ((time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC)); - tm = localtime(&t); - if (!tm) - return; - if (strftime(header_time, sizeof(header_time), "%h %e %T ", tm) <= 0) - return; - IOVEC_SET_STRING(iovec[n++], header_time); - - /* Third: identifier and PID */ - if (ucred) { - if (!identifier) { - get_process_comm(ucred->pid, &ident_buf); - identifier = ident_buf; - } - - xsprintf(header_pid, "["PID_FMT"]: ", ucred->pid); - - if (identifier) - IOVEC_SET_STRING(iovec[n++], identifier); - - IOVEC_SET_STRING(iovec[n++], header_pid); - } else if (identifier) { - IOVEC_SET_STRING(iovec[n++], identifier); - IOVEC_SET_STRING(iovec[n++], ": "); - } - - /* Fourth: message */ - IOVEC_SET_STRING(iovec[n++], message); - - forward_syslog_iovec(s, iovec, n, ucred, tv); - - free(ident_buf); -} - -int syslog_fixup_facility(int priority) { - - if ((priority & LOG_FACMASK) == 0) - return (priority & LOG_PRIMASK) | LOG_USER; - - return priority; -} - -size_t syslog_parse_identifier(const char **buf, char **identifier, char **pid) { - const char *p; - char *t; - size_t l, e; - - assert(buf); - assert(identifier); - assert(pid); - - p = *buf; - - p += strspn(p, WHITESPACE); - l = strcspn(p, WHITESPACE); - - if (l <= 0 || - p[l-1] != ':') - return 0; - - e = l; - l--; - - if (p[l-1] == ']') { - size_t k = l-1; - - for (;;) { - - if (p[k] == '[') { - t = strndup(p+k+1, l-k-2); - if (t) - *pid = t; - - l = k; - break; - } - - if (k == 0) - break; - - k--; - } - } - - t = strndup(p, l); - if (t) - *identifier = t; - - if (strchr(WHITESPACE, p[e])) - e++; - *buf = p + e; - return e; -} - -static void syslog_skip_date(char **buf) { - enum { - LETTER, - SPACE, - NUMBER, - SPACE_OR_NUMBER, - COLON - } sequence[] = { - LETTER, LETTER, LETTER, - SPACE, - SPACE_OR_NUMBER, NUMBER, - SPACE, - SPACE_OR_NUMBER, NUMBER, - COLON, - SPACE_OR_NUMBER, NUMBER, - COLON, - SPACE_OR_NUMBER, NUMBER, - SPACE - }; - - char *p; - unsigned i; - - assert(buf); - assert(*buf); - - p = *buf; - - for (i = 0; i < ELEMENTSOF(sequence); i++, p++) { - - if (!*p) - return; - - switch (sequence[i]) { - - case SPACE: - if (*p != ' ') - return; - break; - - case SPACE_OR_NUMBER: - if (*p == ' ') - break; - - /* fall through */ - - case NUMBER: - if (*p < '0' || *p > '9') - return; - - break; - - case LETTER: - if (!(*p >= 'A' && *p <= 'Z') && - !(*p >= 'a' && *p <= 'z')) - return; - - break; - - case COLON: - if (*p != ':') - return; - break; - - } - } - - *buf = p; -} - -void server_process_syslog_message( - Server *s, - const char *buf, - const struct ucred *ucred, - const struct timeval *tv, - const char *label, - size_t label_len) { - - char syslog_priority[sizeof("PRIORITY=") + DECIMAL_STR_MAX(int)], - syslog_facility[sizeof("SYSLOG_FACILITY=") + DECIMAL_STR_MAX(int)]; - const char *message = NULL, *syslog_identifier = NULL, *syslog_pid = NULL; - struct iovec iovec[N_IOVEC_META_FIELDS + 6]; - unsigned n = 0; - int priority = LOG_USER | LOG_INFO; - _cleanup_free_ char *identifier = NULL, *pid = NULL; - const char *orig; - - assert(s); - assert(buf); - - orig = buf; - syslog_parse_priority(&buf, &priority, true); - - if (s->forward_to_syslog) - forward_syslog_raw(s, priority, orig, ucred, tv); - - syslog_skip_date((char**) &buf); - syslog_parse_identifier(&buf, &identifier, &pid); - - if (s->forward_to_kmsg) - server_forward_kmsg(s, priority, identifier, buf, ucred); - - if (s->forward_to_console) - server_forward_console(s, priority, identifier, buf, ucred); - - if (s->forward_to_wall) - server_forward_wall(s, priority, identifier, buf, ucred); - - IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=syslog"); - - xsprintf(syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK); - IOVEC_SET_STRING(iovec[n++], syslog_priority); - - if (priority & LOG_FACMASK) { - xsprintf(syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)); - IOVEC_SET_STRING(iovec[n++], syslog_facility); - } - - if (identifier) { - syslog_identifier = strjoina("SYSLOG_IDENTIFIER=", identifier); - IOVEC_SET_STRING(iovec[n++], syslog_identifier); - } - - if (pid) { - syslog_pid = strjoina("SYSLOG_PID=", pid); - IOVEC_SET_STRING(iovec[n++], syslog_pid); - } - - message = strjoina("MESSAGE=", buf); - if (message) - IOVEC_SET_STRING(iovec[n++], message); - - server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), ucred, tv, label, label_len, NULL, priority, 0); -} - -int server_open_syslog_socket(Server *s) { - - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/journal/dev-log", - }; - static const int one = 1; - int r; - - assert(s); - - if (s->syslog_fd < 0) { - s->syslog_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (s->syslog_fd < 0) - return log_error_errno(errno, "socket() failed: %m"); - - (void) unlink(sa.un.sun_path); - - r = bind(s->syslog_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); - if (r < 0) - return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path); - - (void) chmod(sa.un.sun_path, 0666); - } else - fd_nonblock(s->syslog_fd, 1); - - r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); - if (r < 0) - return log_error_errno(errno, "SO_PASSCRED failed: %m"); - -#ifdef HAVE_SELINUX - if (mac_selinux_have()) { - r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one)); - if (r < 0) - log_warning_errno(errno, "SO_PASSSEC failed: %m"); - } -#endif - - r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)); - if (r < 0) - return log_error_errno(errno, "SO_TIMESTAMP failed: %m"); - - r = sd_event_add_io(s->event, &s->syslog_event_source, s->syslog_fd, EPOLLIN, server_process_datagram, s); - if (r < 0) - return log_error_errno(r, "Failed to add syslog server fd to event loop: %m"); - - r = sd_event_source_set_priority(s->syslog_event_source, SD_EVENT_PRIORITY_NORMAL+5); - if (r < 0) - return log_error_errno(r, "Failed to adjust syslog event source priority: %m"); - - return 0; -} - -void server_maybe_warn_forward_syslog_missed(Server *s) { - usec_t n; - - assert(s); - - if (s->n_forward_syslog_missed <= 0) - return; - - n = now(CLOCK_MONOTONIC); - if (s->last_warn_forward_syslog_missed + WARN_FORWARD_SYSLOG_MISSED_USEC > n) - return; - - server_driver_message(s, SD_MESSAGE_FORWARD_SYSLOG_MISSED, - LOG_MESSAGE("Forwarding to syslog missed %u messages.", - s->n_forward_syslog_missed), - NULL); - - s->n_forward_syslog_missed = 0; - s->last_warn_forward_syslog_missed = n; -} diff --git a/src/journal/journald-syslog.h b/src/journal/journald-syslog.h deleted file mode 100644 index 46ad715314..0000000000 --- a/src/journal/journald-syslog.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 "journald-server.h" - -int syslog_fixup_facility(int priority) _const_; - -size_t syslog_parse_identifier(const char **buf, char **identifier, char **pid); - -void server_forward_syslog(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred, const struct timeval *tv); - -void server_process_syslog_message(Server *s, const char *buf, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len); -int server_open_syslog_socket(Server *s); - -void server_maybe_warn_forward_syslog_missed(Server *s); diff --git a/src/journal/journald-wall.c b/src/journal/journald-wall.c deleted file mode 100644 index 4d91fafffe..0000000000 --- a/src/journal/journald-wall.c +++ /dev/null @@ -1,71 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Sebastian Thorarensen - - 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 "alloc-util.h" -#include "formats-util.h" -#include "journald-server.h" -#include "journald-wall.h" -#include "process-util.h" -#include "string-util.h" -#include "utmp-wtmp.h" - -void server_forward_wall( - Server *s, - int priority, - const char *identifier, - const char *message, - const struct ucred *ucred) { - - _cleanup_free_ char *ident_buf = NULL, *l_buf = NULL; - const char *l; - int r; - - assert(s); - assert(message); - - if (LOG_PRI(priority) > s->max_level_wall) - return; - - if (ucred) { - if (!identifier) { - get_process_comm(ucred->pid, &ident_buf); - identifier = ident_buf; - } - - if (asprintf(&l_buf, "%s["PID_FMT"]: %s", strempty(identifier), ucred->pid, message) < 0) { - log_oom(); - return; - } - - l = l_buf; - - } else if (identifier) { - - l = l_buf = strjoin(identifier, ": ", message, NULL); - if (!l_buf) { - log_oom(); - return; - } - } else - l = message; - - r = utmp_wall(l, "systemd-journald", NULL, NULL, NULL); - if (r < 0) - log_debug_errno(r, "Failed to send wall message: %m"); -} diff --git a/src/journal/journald-wall.h b/src/journal/journald-wall.h deleted file mode 100644 index ebc2b89fa8..0000000000 --- a/src/journal/journald-wall.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Sebastian Thorarensen - - 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 "journald-server.h" - -void server_forward_wall(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred); diff --git a/src/journal/journald.c b/src/journal/journald.c deleted file mode 100644 index 272acb71c4..0000000000 --- a/src/journal/journald.c +++ /dev/null @@ -1,120 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 "sd-daemon.h" -#include "sd-messages.h" - -#include "formats-util.h" -#include "journal-authenticate.h" -#include "journald-kmsg.h" -#include "journald-server.h" -#include "journald-syslog.h" -#include "sigbus.h" - -int main(int argc, char *argv[]) { - Server server; - int r; - - if (argc > 1) { - log_error("This program does not take arguments."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_SAFE); - log_set_facility(LOG_SYSLOG); - log_parse_environment(); - log_open(); - - umask(0022); - - sigbus_install(); - - r = server_init(&server); - if (r < 0) - goto finish; - - server_vacuum(&server, false, false); - server_flush_to_var(&server); - server_flush_dev_kmsg(&server); - - log_debug("systemd-journald running as pid "PID_FMT, getpid()); - server_driver_message(&server, SD_MESSAGE_JOURNAL_START, - LOG_MESSAGE("Journal started"), - NULL); - - for (;;) { - usec_t t = USEC_INFINITY, n; - - r = sd_event_get_state(server.event); - if (r < 0) - goto finish; - if (r == SD_EVENT_FINISHED) - break; - - n = now(CLOCK_REALTIME); - - if (server.max_retention_usec > 0 && server.oldest_file_usec > 0) { - - /* The retention time is reached, so let's vacuum! */ - if (server.oldest_file_usec + server.max_retention_usec < n) { - log_info("Retention time reached."); - server_rotate(&server); - server_vacuum(&server, false, false); - continue; - } - - /* Calculate when to rotate the next time */ - t = server.oldest_file_usec + server.max_retention_usec - n; - } - -#ifdef HAVE_GCRYPT - if (server.system_journal) { - usec_t u; - - if (journal_file_next_evolve_usec(server.system_journal, &u)) { - if (n >= u) - t = 0; - else - t = MIN(t, u - n); - } - } -#endif - - r = sd_event_run(server.event, t); - if (r < 0) { - log_error_errno(r, "Failed to run event loop: %m"); - goto finish; - } - - server_maybe_append_tags(&server); - server_maybe_warn_forward_syslog_missed(&server); - } - - log_debug("systemd-journald stopped as pid "PID_FMT, getpid()); - server_driver_message(&server, SD_MESSAGE_JOURNAL_STOP, - LOG_MESSAGE("Journal stopped"), - NULL); - -finish: - server_done(&server); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/journal/journald.conf b/src/journal/journald.conf deleted file mode 100644 index 2541b949be..0000000000 --- a/src/journal/journald.conf +++ /dev/null @@ -1,41 +0,0 @@ -# This file is part of systemd. -# -# 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. -# -# Entries in this file show the compile time defaults. -# You can change settings by editing this file. -# Defaults can be restored by simply deleting this file. -# -# See journald.conf(5) for details. - -[Journal] -#Storage=auto -#Compress=yes -#Seal=yes -#SplitMode=uid -#SyncIntervalSec=5m -#RateLimitIntervalSec=30s -#RateLimitBurst=1000 -#SystemMaxUse= -#SystemKeepFree= -#SystemMaxFileSize= -#SystemMaxFiles=100 -#RuntimeMaxUse= -#RuntimeKeepFree= -#RuntimeMaxFileSize= -#RuntimeMaxFiles=100 -#MaxRetentionSec= -#MaxFileSec=1month -#ForwardToSyslog=no -#ForwardToKMsg=no -#ForwardToConsole=no -#ForwardToWall=yes -#TTYPath=/dev/console -#MaxLevelStore=debug -#MaxLevelSyslog=debug -#MaxLevelKMsg=notice -#MaxLevelConsole=info -#MaxLevelWall=emerg diff --git a/src/journal/lookup3.c b/src/journal/lookup3.c deleted file mode 100644 index 3d791234f4..0000000000 --- a/src/journal/lookup3.c +++ /dev/null @@ -1,1009 +0,0 @@ -/* Slightly modified by Lennart Poettering, to avoid name clashes, and - * unexport a few functions. */ - -#include "lookup3.h" - -/* -------------------------------------------------------------------------------- -lookup3.c, by Bob Jenkins, May 2006, Public Domain. - -These are functions for producing 32-bit hashes for hash table lookup. -hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final() -are externally useful functions. Routines to test the hash are included -if SELF_TEST is defined. You can use this free for any purpose. It's in -the public domain. It has no warranty. - -You probably want to use hashlittle(). hashlittle() and hashbig() -hash byte arrays. hashlittle() is faster than hashbig() on -little-endian machines. Intel and AMD are little-endian machines. -On second thought, you probably want hashlittle2(), which is identical to -hashlittle() except it returns two 32-bit hashes for the price of one. -You could implement hashbig2() if you wanted but I haven't bothered here. - -If you want to find a hash of, say, exactly 7 integers, do - a = i1; b = i2; c = i3; - mix(a,b,c); - a += i4; b += i5; c += i6; - mix(a,b,c); - a += i7; - final(a,b,c); -then use c as the hash value. If you have a variable length array of -4-byte integers to hash, use hashword(). If you have a byte array (like -a character string), use hashlittle(). If you have several byte arrays, or -a mix of things, see the comments above hashlittle(). - -Why is this so big? I read 12 bytes at a time into 3 4-byte integers, -then mix those integers. This is fast (you can do a lot more thorough -mixing with 12*3 instructions on 3 integers than you can with 3 instructions -on 1 byte), but shoehorning those bytes into integers efficiently is messy. -------------------------------------------------------------------------------- -*/ -/* #define SELF_TEST 1 */ - -#include /* defines uint32_t etc */ -#include /* defines printf for tests */ -#include /* attempt to define endianness */ -#include /* defines time_t for timings in the test */ -#ifdef linux -# include /* attempt to define endianness */ -#endif - -/* - * My best guess at if you are big-endian or little-endian. This may - * need adjustment. - */ -#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ - __BYTE_ORDER == __LITTLE_ENDIAN) || \ - (defined(i386) || defined(__i386__) || defined(__i486__) || \ - defined(__i586__) || defined(__i686__) || defined(vax) || defined(MIPSEL)) -# define HASH_LITTLE_ENDIAN 1 -# define HASH_BIG_ENDIAN 0 -#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ - __BYTE_ORDER == __BIG_ENDIAN) || \ - (defined(sparc) || defined(POWERPC) || defined(mc68000) || defined(sel)) -# define HASH_LITTLE_ENDIAN 0 -# define HASH_BIG_ENDIAN 1 -#else -# define HASH_LITTLE_ENDIAN 0 -# define HASH_BIG_ENDIAN 0 -#endif - -#define hashsize(n) ((uint32_t)1<<(n)) -#define hashmask(n) (hashsize(n)-1) -#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) - -/* -------------------------------------------------------------------------------- -mix -- mix 3 32-bit values reversibly. - -This is reversible, so any information in (a,b,c) before mix() is -still in (a,b,c) after mix(). - -If four pairs of (a,b,c) inputs are run through mix(), or through -mix() in reverse, there are at least 32 bits of the output that -are sometimes the same for one pair and different for another pair. -This was tested for: -* pairs that differed by one bit, by two bits, in any combination - of top bits of (a,b,c), or in any combination of bottom bits of - (a,b,c). -* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed - the output delta to a Gray code (a^(a>>1)) so a string of 1's (as - is commonly produced by subtraction) look like a single 1-bit - difference. -* the base values were pseudorandom, all zero but one bit set, or - all zero plus a counter that starts at zero. - -Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that -satisfy this are - 4 6 8 16 19 4 - 9 15 3 18 27 15 - 14 9 3 7 17 3 -Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing -for "differ" defined as + with a one-bit base and a two-bit delta. I -used http://burtleburtle.net/bob/hash/avalanche.html to choose -the operations, constants, and arrangements of the variables. - -This does not achieve avalanche. There are input bits of (a,b,c) -that fail to affect some output bits of (a,b,c), especially of a. The -most thoroughly mixed value is c, but it doesn't really even achieve -avalanche in c. - -This allows some parallelism. Read-after-writes are good at doubling -the number of bits affected, so the goal of mixing pulls in the opposite -direction as the goal of parallelism. I did what I could. Rotates -seem to cost as much as shifts on every machine I could lay my hands -on, and rotates are much kinder to the top and bottom bits, so I used -rotates. -------------------------------------------------------------------------------- -*/ -#define mix(a,b,c) \ -{ \ - a -= c; a ^= rot(c, 4); c += b; \ - b -= a; b ^= rot(a, 6); a += c; \ - c -= b; c ^= rot(b, 8); b += a; \ - a -= c; a ^= rot(c,16); c += b; \ - b -= a; b ^= rot(a,19); a += c; \ - c -= b; c ^= rot(b, 4); b += a; \ -} - -/* -------------------------------------------------------------------------------- -final -- final mixing of 3 32-bit values (a,b,c) into c - -Pairs of (a,b,c) values differing in only a few bits will usually -produce values of c that look totally different. This was tested for -* pairs that differed by one bit, by two bits, in any combination - of top bits of (a,b,c), or in any combination of bottom bits of - (a,b,c). -* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed - the output delta to a Gray code (a^(a>>1)) so a string of 1's (as - is commonly produced by subtraction) look like a single 1-bit - difference. -* the base values were pseudorandom, all zero but one bit set, or - all zero plus a counter that starts at zero. - -These constants passed: - 14 11 25 16 4 14 24 - 12 14 25 16 4 14 24 -and these came close: - 4 8 15 26 3 22 24 - 10 8 15 26 3 22 24 - 11 8 15 26 3 22 24 -------------------------------------------------------------------------------- -*/ -#define final(a,b,c) \ -{ \ - c ^= b; c -= rot(b,14); \ - a ^= c; a -= rot(c,11); \ - b ^= a; b -= rot(a,25); \ - c ^= b; c -= rot(b,16); \ - a ^= c; a -= rot(c,4); \ - b ^= a; b -= rot(a,14); \ - c ^= b; c -= rot(b,24); \ -} - -/* --------------------------------------------------------------------- - This works on all machines. To be useful, it requires - -- that the key be an array of uint32_t's, and - -- that the length be the number of uint32_t's in the key - - The function hashword() is identical to hashlittle() on little-endian - machines, and identical to hashbig() on big-endian machines, - except that the length has to be measured in uint32_ts rather than in - bytes. hashlittle() is more complicated than hashword() only because - hashlittle() has to dance around fitting the key bytes into registers. --------------------------------------------------------------------- -*/ -uint32_t jenkins_hashword( -const uint32_t *k, /* the key, an array of uint32_t values */ -size_t length, /* the length of the key, in uint32_ts */ -uint32_t initval) /* the previous hash, or an arbitrary value */ -{ - uint32_t a,b,c; - - /* Set up the internal state */ - a = b = c = 0xdeadbeef + (((uint32_t)length)<<2) + initval; - - /*------------------------------------------------- handle most of the key */ - while (length > 3) - { - a += k[0]; - b += k[1]; - c += k[2]; - mix(a,b,c); - length -= 3; - k += 3; - } - - /*------------------------------------------- handle the last 3 uint32_t's */ - switch(length) /* all the case statements fall through */ - { - case 3 : c+=k[2]; - case 2 : b+=k[1]; - case 1 : a+=k[0]; - final(a,b,c); - case 0: /* case 0: nothing left to add */ - break; - } - /*------------------------------------------------------ report the result */ - return c; -} - - -/* --------------------------------------------------------------------- -hashword2() -- same as hashword(), but take two seeds and return two -32-bit values. pc and pb must both be nonnull, and *pc and *pb must -both be initialized with seeds. If you pass in (*pb)==0, the output -(*pc) will be the same as the return value from hashword(). --------------------------------------------------------------------- -*/ -void jenkins_hashword2 ( -const uint32_t *k, /* the key, an array of uint32_t values */ -size_t length, /* the length of the key, in uint32_ts */ -uint32_t *pc, /* IN: seed OUT: primary hash value */ -uint32_t *pb) /* IN: more seed OUT: secondary hash value */ -{ - uint32_t a,b,c; - - /* Set up the internal state */ - a = b = c = 0xdeadbeef + ((uint32_t)(length<<2)) + *pc; - c += *pb; - - /*------------------------------------------------- handle most of the key */ - while (length > 3) - { - a += k[0]; - b += k[1]; - c += k[2]; - mix(a,b,c); - length -= 3; - k += 3; - } - - /*------------------------------------------- handle the last 3 uint32_t's */ - switch(length) /* all the case statements fall through */ - { - case 3 : c+=k[2]; - case 2 : b+=k[1]; - case 1 : a+=k[0]; - final(a,b,c); - case 0: /* case 0: nothing left to add */ - break; - } - /*------------------------------------------------------ report the result */ - *pc=c; *pb=b; -} - - -/* -------------------------------------------------------------------------------- -hashlittle() -- hash a variable-length key into a 32-bit value - k : the key (the unaligned variable-length array of bytes) - length : the length of the key, counting by bytes - initval : can be any 4-byte value -Returns a 32-bit value. Every bit of the key affects every bit of -the return value. Two keys differing by one or two bits will have -totally different hash values. - -The best hash table sizes are powers of 2. There is no need to do -mod a prime (mod is sooo slow!). If you need less than 32 bits, -use a bitmask. For example, if you need only 10 bits, do - h = (h & hashmask(10)); -In which case, the hash table should have hashsize(10) elements. - -If you are hashing n strings (uint8_t **)k, do it like this: - for (i=0, h=0; i 12) - { - a += k[0]; - b += k[1]; - c += k[2]; - mix(a,b,c); - length -= 12; - k += 3; - } - - /*----------------------------- handle the last (probably partial) block */ - /* - * "k[2]&0xffffff" actually reads beyond the end of the string, but - * then masks off the part it's not allowed to read. Because the - * string is aligned, the masked-off tail is in the same word as the - * rest of the string. Every machine with memory protection I've seen - * does it on word boundaries, so is OK with this. But VALGRIND will - * still catch it and complain. The masking trick does make the hash - * noticeably faster for short strings (like English words). - */ -#ifndef VALGRIND - - switch(length) - { - case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; - case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; - case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; - case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; - case 8 : b+=k[1]; a+=k[0]; break; - case 7 : b+=k[1]&0xffffff; a+=k[0]; break; - case 6 : b+=k[1]&0xffff; a+=k[0]; break; - case 5 : b+=k[1]&0xff; a+=k[0]; break; - case 4 : a+=k[0]; break; - case 3 : a+=k[0]&0xffffff; break; - case 2 : a+=k[0]&0xffff; break; - case 1 : a+=k[0]&0xff; break; - case 0 : return c; /* zero length strings require no mixing */ - } - -#else /* make valgrind happy */ - { - const uint8_t *k8 = (const uint8_t *) k; - - switch(length) - { - case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; - case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ - case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ - case 9 : c+=k8[8]; /* fall through */ - case 8 : b+=k[1]; a+=k[0]; break; - case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ - case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ - case 5 : b+=k8[4]; /* fall through */ - case 4 : a+=k[0]; break; - case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ - case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ - case 1 : a+=k8[0]; break; - case 0 : return c; - } - } - -#endif /* !valgrind */ - - } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { - const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ - const uint8_t *k8; - - /*--------------- all but last block: aligned reads and different mixing */ - while (length > 12) - { - a += k[0] + (((uint32_t)k[1])<<16); - b += k[2] + (((uint32_t)k[3])<<16); - c += k[4] + (((uint32_t)k[5])<<16); - mix(a,b,c); - length -= 12; - k += 6; - } - - /*----------------------------- handle the last (probably partial) block */ - k8 = (const uint8_t *)k; - switch(length) - { - case 12: c+=k[4]+(((uint32_t)k[5])<<16); - b+=k[2]+(((uint32_t)k[3])<<16); - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ - case 10: c+=k[4]; - b+=k[2]+(((uint32_t)k[3])<<16); - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 9 : c+=k8[8]; /* fall through */ - case 8 : b+=k[2]+(((uint32_t)k[3])<<16); - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ - case 6 : b+=k[2]; - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 5 : b+=k8[4]; /* fall through */ - case 4 : a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ - case 2 : a+=k[0]; - break; - case 1 : a+=k8[0]; - break; - case 0 : return c; /* zero length requires no mixing */ - } - - } else { /* need to read the key one byte at a time */ - const uint8_t *k = (const uint8_t *)key; - - /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ - while (length > 12) - { - a += k[0]; - a += ((uint32_t)k[1])<<8; - a += ((uint32_t)k[2])<<16; - a += ((uint32_t)k[3])<<24; - b += k[4]; - b += ((uint32_t)k[5])<<8; - b += ((uint32_t)k[6])<<16; - b += ((uint32_t)k[7])<<24; - c += k[8]; - c += ((uint32_t)k[9])<<8; - c += ((uint32_t)k[10])<<16; - c += ((uint32_t)k[11])<<24; - mix(a,b,c); - length -= 12; - k += 12; - } - - /*-------------------------------- last block: affect all 32 bits of (c) */ - switch(length) /* all the case statements fall through */ - { - case 12: c+=((uint32_t)k[11])<<24; - case 11: c+=((uint32_t)k[10])<<16; - case 10: c+=((uint32_t)k[9])<<8; - case 9 : c+=k[8]; - case 8 : b+=((uint32_t)k[7])<<24; - case 7 : b+=((uint32_t)k[6])<<16; - case 6 : b+=((uint32_t)k[5])<<8; - case 5 : b+=k[4]; - case 4 : a+=((uint32_t)k[3])<<24; - case 3 : a+=((uint32_t)k[2])<<16; - case 2 : a+=((uint32_t)k[1])<<8; - case 1 : a+=k[0]; - break; - case 0 : return c; - } - } - - final(a,b,c); - return c; -} - - -/* - * hashlittle2: return 2 32-bit hash values - * - * This is identical to hashlittle(), except it returns two 32-bit hash - * values instead of just one. This is good enough for hash table - * lookup with 2^^64 buckets, or if you want a second hash if you're not - * happy with the first, or if you want a probably-unique 64-bit ID for - * the key. *pc is better mixed than *pb, so use *pc first. If you want - * a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)". - */ -void jenkins_hashlittle2( - const void *key, /* the key to hash */ - size_t length, /* length of the key */ - uint32_t *pc, /* IN: primary initval, OUT: primary hash */ - uint32_t *pb) /* IN: secondary initval, OUT: secondary hash */ -{ - uint32_t a,b,c; /* internal state */ - union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */ - - /* Set up the internal state */ - a = b = c = 0xdeadbeef + ((uint32_t)length) + *pc; - c += *pb; - - u.ptr = key; - if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) { - const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */ - - /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ - while (length > 12) - { - a += k[0]; - b += k[1]; - c += k[2]; - mix(a,b,c); - length -= 12; - k += 3; - } - - /*----------------------------- handle the last (probably partial) block */ - /* - * "k[2]&0xffffff" actually reads beyond the end of the string, but - * then masks off the part it's not allowed to read. Because the - * string is aligned, the masked-off tail is in the same word as the - * rest of the string. Every machine with memory protection I've seen - * does it on word boundaries, so is OK with this. But VALGRIND will - * still catch it and complain. The masking trick does make the hash - * noticeably faster for short strings (like English words). - */ -#ifndef VALGRIND - - switch(length) - { - case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; - case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; - case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; - case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; - case 8 : b+=k[1]; a+=k[0]; break; - case 7 : b+=k[1]&0xffffff; a+=k[0]; break; - case 6 : b+=k[1]&0xffff; a+=k[0]; break; - case 5 : b+=k[1]&0xff; a+=k[0]; break; - case 4 : a+=k[0]; break; - case 3 : a+=k[0]&0xffffff; break; - case 2 : a+=k[0]&0xffff; break; - case 1 : a+=k[0]&0xff; break; - case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ - } - -#else /* make valgrind happy */ - - { - const uint8_t *k8 = (const uint8_t *)k; - switch(length) - { - case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; - case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ - case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ - case 9 : c+=k8[8]; /* fall through */ - case 8 : b+=k[1]; a+=k[0]; break; - case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ - case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ - case 5 : b+=k8[4]; /* fall through */ - case 4 : a+=k[0]; break; - case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ - case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ - case 1 : a+=k8[0]; break; - case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ - } - } - -#endif /* !valgrind */ - - } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { - const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ - const uint8_t *k8; - - /*--------------- all but last block: aligned reads and different mixing */ - while (length > 12) - { - a += k[0] + (((uint32_t)k[1])<<16); - b += k[2] + (((uint32_t)k[3])<<16); - c += k[4] + (((uint32_t)k[5])<<16); - mix(a,b,c); - length -= 12; - k += 6; - } - - /*----------------------------- handle the last (probably partial) block */ - k8 = (const uint8_t *)k; - switch(length) - { - case 12: c+=k[4]+(((uint32_t)k[5])<<16); - b+=k[2]+(((uint32_t)k[3])<<16); - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ - case 10: c+=k[4]; - b+=k[2]+(((uint32_t)k[3])<<16); - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 9 : c+=k8[8]; /* fall through */ - case 8 : b+=k[2]+(((uint32_t)k[3])<<16); - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ - case 6 : b+=k[2]; - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 5 : b+=k8[4]; /* fall through */ - case 4 : a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ - case 2 : a+=k[0]; - break; - case 1 : a+=k8[0]; - break; - case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ - } - - } else { /* need to read the key one byte at a time */ - const uint8_t *k = (const uint8_t *)key; - - /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ - while (length > 12) - { - a += k[0]; - a += ((uint32_t)k[1])<<8; - a += ((uint32_t)k[2])<<16; - a += ((uint32_t)k[3])<<24; - b += k[4]; - b += ((uint32_t)k[5])<<8; - b += ((uint32_t)k[6])<<16; - b += ((uint32_t)k[7])<<24; - c += k[8]; - c += ((uint32_t)k[9])<<8; - c += ((uint32_t)k[10])<<16; - c += ((uint32_t)k[11])<<24; - mix(a,b,c); - length -= 12; - k += 12; - } - - /*-------------------------------- last block: affect all 32 bits of (c) */ - switch(length) /* all the case statements fall through */ - { - case 12: c+=((uint32_t)k[11])<<24; - case 11: c+=((uint32_t)k[10])<<16; - case 10: c+=((uint32_t)k[9])<<8; - case 9 : c+=k[8]; - case 8 : b+=((uint32_t)k[7])<<24; - case 7 : b+=((uint32_t)k[6])<<16; - case 6 : b+=((uint32_t)k[5])<<8; - case 5 : b+=k[4]; - case 4 : a+=((uint32_t)k[3])<<24; - case 3 : a+=((uint32_t)k[2])<<16; - case 2 : a+=((uint32_t)k[1])<<8; - case 1 : a+=k[0]; - break; - case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ - } - } - - final(a,b,c); - *pc=c; *pb=b; -} - - - -/* - * hashbig(): - * This is the same as hashword() on big-endian machines. It is different - * from hashlittle() on all machines. hashbig() takes advantage of - * big-endian byte ordering. - */ -uint32_t jenkins_hashbig( const void *key, size_t length, uint32_t initval) -{ - uint32_t a,b,c; - union { const void *ptr; size_t i; } u; /* to cast key to (size_t) happily */ - - /* Set up the internal state */ - a = b = c = 0xdeadbeef + ((uint32_t)length) + initval; - - u.ptr = key; - if (HASH_BIG_ENDIAN && ((u.i & 0x3) == 0)) { - const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */ - - /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ - while (length > 12) - { - a += k[0]; - b += k[1]; - c += k[2]; - mix(a,b,c); - length -= 12; - k += 3; - } - - /*----------------------------- handle the last (probably partial) block */ - /* - * "k[2]<<8" actually reads beyond the end of the string, but - * then shifts out the part it's not allowed to read. Because the - * string is aligned, the illegal read is in the same word as the - * rest of the string. Every machine with memory protection I've seen - * does it on word boundaries, so is OK with this. But VALGRIND will - * still catch it and complain. The masking trick does make the hash - * noticeably faster for short strings (like English words). - */ -#ifndef VALGRIND - - switch(length) - { - case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; - case 11: c+=k[2]&0xffffff00; b+=k[1]; a+=k[0]; break; - case 10: c+=k[2]&0xffff0000; b+=k[1]; a+=k[0]; break; - case 9 : c+=k[2]&0xff000000; b+=k[1]; a+=k[0]; break; - case 8 : b+=k[1]; a+=k[0]; break; - case 7 : b+=k[1]&0xffffff00; a+=k[0]; break; - case 6 : b+=k[1]&0xffff0000; a+=k[0]; break; - case 5 : b+=k[1]&0xff000000; a+=k[0]; break; - case 4 : a+=k[0]; break; - case 3 : a+=k[0]&0xffffff00; break; - case 2 : a+=k[0]&0xffff0000; break; - case 1 : a+=k[0]&0xff000000; break; - case 0 : return c; /* zero length strings require no mixing */ - } - -#else /* make valgrind happy */ - - { - const uint8_t *k8 = (const uint8_t *)k; - switch(length) /* all the case statements fall through */ - { - case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; - case 11: c+=((uint32_t)k8[10])<<8; /* fall through */ - case 10: c+=((uint32_t)k8[9])<<16; /* fall through */ - case 9 : c+=((uint32_t)k8[8])<<24; /* fall through */ - case 8 : b+=k[1]; a+=k[0]; break; - case 7 : b+=((uint32_t)k8[6])<<8; /* fall through */ - case 6 : b+=((uint32_t)k8[5])<<16; /* fall through */ - case 5 : b+=((uint32_t)k8[4])<<24; /* fall through */ - case 4 : a+=k[0]; break; - case 3 : a+=((uint32_t)k8[2])<<8; /* fall through */ - case 2 : a+=((uint32_t)k8[1])<<16; /* fall through */ - case 1 : a+=((uint32_t)k8[0])<<24; break; - case 0 : return c; - } - } - -#endif /* !VALGRIND */ - - } else { /* need to read the key one byte at a time */ - const uint8_t *k = (const uint8_t *)key; - - /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ - while (length > 12) - { - a += ((uint32_t)k[0])<<24; - a += ((uint32_t)k[1])<<16; - a += ((uint32_t)k[2])<<8; - a += ((uint32_t)k[3]); - b += ((uint32_t)k[4])<<24; - b += ((uint32_t)k[5])<<16; - b += ((uint32_t)k[6])<<8; - b += ((uint32_t)k[7]); - c += ((uint32_t)k[8])<<24; - c += ((uint32_t)k[9])<<16; - c += ((uint32_t)k[10])<<8; - c += ((uint32_t)k[11]); - mix(a,b,c); - length -= 12; - k += 12; - } - - /*-------------------------------- last block: affect all 32 bits of (c) */ - switch(length) /* all the case statements fall through */ - { - case 12: c+=k[11]; - case 11: c+=((uint32_t)k[10])<<8; - case 10: c+=((uint32_t)k[9])<<16; - case 9 : c+=((uint32_t)k[8])<<24; - case 8 : b+=k[7]; - case 7 : b+=((uint32_t)k[6])<<8; - case 6 : b+=((uint32_t)k[5])<<16; - case 5 : b+=((uint32_t)k[4])<<24; - case 4 : a+=k[3]; - case 3 : a+=((uint32_t)k[2])<<8; - case 2 : a+=((uint32_t)k[1])<<16; - case 1 : a+=((uint32_t)k[0])<<24; - break; - case 0 : return c; - } - } - - final(a,b,c); - return c; -} - - -#ifdef SELF_TEST - -/* used for timings */ -void driver1() -{ - uint8_t buf[256]; - uint32_t i; - uint32_t h=0; - time_t a,z; - - time(&a); - for (i=0; i<256; ++i) buf[i] = 'x'; - for (i=0; i<1; ++i) - { - h = hashlittle(&buf[0],1,h); - } - time(&z); - if (z-a > 0) printf("time %d %.8x\n", z-a, h); -} - -/* check that every input bit changes every output bit half the time */ -#define HASHSTATE 1 -#define HASHLEN 1 -#define MAXPAIR 60 -#define MAXLEN 70 -void driver2() -{ - uint8_t qa[MAXLEN+1], qb[MAXLEN+2], *a = &qa[0], *b = &qb[1]; - uint32_t c[HASHSTATE], d[HASHSTATE], i=0, j=0, k, l, m=0, z; - uint32_t e[HASHSTATE],f[HASHSTATE],g[HASHSTATE],h[HASHSTATE]; - uint32_t x[HASHSTATE],y[HASHSTATE]; - uint32_t hlen; - - printf("No more than %d trials should ever be needed \n",MAXPAIR/2); - for (hlen=0; hlen < MAXLEN; ++hlen) - { - z=0; - for (i=0; i>(8-j)); - c[0] = hashlittle(a, hlen, m); - b[i] ^= ((k+1)<>(8-j)); - d[0] = hashlittle(b, hlen, m); - /* check every bit is 1, 0, set, and not set at least once */ - for (l=0; lz) z=k; - if (k==MAXPAIR) - { - printf("Some bit didn't change: "); - printf("%.8x %.8x %.8x %.8x %.8x %.8x ", - e[0],f[0],g[0],h[0],x[0],y[0]); - printf("i %d j %d m %d len %d\n", i, j, m, hlen); - } - if (z==MAXPAIR) goto done; - } - } - } - done: - if (z < MAXPAIR) - { - printf("Mix success %2d bytes %2d initvals ",i,m); - printf("required %d trials\n", z/2); - } - } - printf("\n"); -} - -/* Check for reading beyond the end of the buffer and alignment problems */ -void driver3() -{ - uint8_t buf[MAXLEN+20], *b; - uint32_t len; - uint8_t q[] = "This is the time for all good men to come to the aid of their country..."; - uint32_t h; - uint8_t qq[] = "xThis is the time for all good men to come to the aid of their country..."; - uint32_t i; - uint8_t qqq[] = "xxThis is the time for all good men to come to the aid of their country..."; - uint32_t j; - uint8_t qqqq[] = "xxxThis is the time for all good men to come to the aid of their country..."; - uint32_t ref,x,y; - uint8_t *p; - - printf("Endianness. These lines should all be the same (for values filled in):\n"); - printf("%.8x %.8x %.8x\n", - hashword((const uint32_t *)q, (sizeof(q)-1)/4, 13), - hashword((const uint32_t *)q, (sizeof(q)-5)/4, 13), - hashword((const uint32_t *)q, (sizeof(q)-9)/4, 13)); - p = q; - printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n", - hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13), - hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13), - hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13), - hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13), - hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13), - hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13)); - p = &qq[1]; - printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n", - hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13), - hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13), - hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13), - hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13), - hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13), - hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13)); - p = &qqq[2]; - printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n", - hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13), - hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13), - hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13), - hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13), - hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13), - hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13)); - p = &qqqq[3]; - printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n", - hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13), - hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13), - hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13), - hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13), - hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13), - hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13)); - printf("\n"); - - /* check that hashlittle2 and hashlittle produce the same results */ - i=47; j=0; - hashlittle2(q, sizeof(q), &i, &j); - if (hashlittle(q, sizeof(q), 47) != i) - printf("hashlittle2 and hashlittle mismatch\n"); - - /* check that hashword2 and hashword produce the same results */ - len = 0xdeadbeef; - i=47, j=0; - hashword2(&len, 1, &i, &j); - if (hashword(&len, 1, 47) != i) - printf("hashword2 and hashword mismatch %x %x\n", - i, hashword(&len, 1, 47)); - - /* check hashlittle doesn't read before or after the ends of the string */ - for (h=0, b=buf+1; h<8; ++h, ++b) - { - for (i=0; i -#include - -#include "macro.h" - -uint32_t jenkins_hashword(const uint32_t *k, size_t length, uint32_t initval) _pure_; -void jenkins_hashword2(const uint32_t *k, size_t length, uint32_t *pc, uint32_t *pb); - -uint32_t jenkins_hashlittle(const void *key, size_t length, uint32_t initval) _pure_; -void jenkins_hashlittle2(const void *key, size_t length, uint32_t *pc, uint32_t *pb); - -uint32_t jenkins_hashbig(const void *key, size_t length, uint32_t initval) _pure_; - -static inline uint64_t hash64(const void *data, size_t length) { - uint32_t a = 0, b = 0; - - jenkins_hashlittle2(data, length, &a, &b); - - return ((uint64_t) a << 32ULL) | (uint64_t) b; -} diff --git a/src/journal/mmap-cache.c b/src/journal/mmap-cache.c deleted file mode 100644 index 6bcd9b6ac8..0000000000 --- a/src/journal/mmap-cache.c +++ /dev/null @@ -1,725 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "hashmap.h" -#include "list.h" -#include "log.h" -#include "macro.h" -#include "mmap-cache.h" -#include "sigbus.h" -#include "util.h" - -typedef struct Window Window; -typedef struct Context Context; -typedef struct FileDescriptor FileDescriptor; - -struct Window { - MMapCache *cache; - - bool invalidated:1; - bool keep_always:1; - bool in_unused:1; - - int prot; - void *ptr; - uint64_t offset; - size_t size; - - FileDescriptor *fd; - - LIST_FIELDS(Window, by_fd); - LIST_FIELDS(Window, unused); - - LIST_HEAD(Context, contexts); -}; - -struct Context { - MMapCache *cache; - unsigned id; - Window *window; - - LIST_FIELDS(Context, by_window); -}; - -struct FileDescriptor { - MMapCache *cache; - int fd; - bool sigbus; - LIST_HEAD(Window, windows); -}; - -struct MMapCache { - int n_ref; - unsigned n_windows; - - unsigned n_hit, n_missed; - - Hashmap *fds; - Context *contexts[MMAP_CACHE_MAX_CONTEXTS]; - - LIST_HEAD(Window, unused); - Window *last_unused; -}; - -#define WINDOWS_MIN 64 - -#ifdef ENABLE_DEBUG_MMAP_CACHE -/* Tiny windows increase mmap activity and the chance of exposing unsafe use. */ -# define WINDOW_SIZE (page_size()) -#else -# define WINDOW_SIZE (8ULL*1024ULL*1024ULL) -#endif - -MMapCache* mmap_cache_new(void) { - MMapCache *m; - - m = new0(MMapCache, 1); - if (!m) - return NULL; - - m->n_ref = 1; - return m; -} - -MMapCache* mmap_cache_ref(MMapCache *m) { - assert(m); - assert(m->n_ref > 0); - - m->n_ref++; - return m; -} - -static void window_unlink(Window *w) { - Context *c; - - assert(w); - - if (w->ptr) - munmap(w->ptr, w->size); - - if (w->fd) - LIST_REMOVE(by_fd, w->fd->windows, w); - - if (w->in_unused) { - if (w->cache->last_unused == w) - w->cache->last_unused = w->unused_prev; - - LIST_REMOVE(unused, w->cache->unused, w); - } - - LIST_FOREACH(by_window, c, w->contexts) { - assert(c->window == w); - c->window = NULL; - } -} - -static void window_invalidate(Window *w) { - assert(w); - - if (w->invalidated) - return; - - /* Replace the window with anonymous pages. This is useful - * when we hit a SIGBUS and want to make sure the file cannot - * trigger any further SIGBUS, possibly overrunning the sigbus - * queue. */ - - assert_se(mmap(w->ptr, w->size, w->prot, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0) == w->ptr); - w->invalidated = true; -} - -static void window_free(Window *w) { - assert(w); - - window_unlink(w); - w->cache->n_windows--; - free(w); -} - -_pure_ static bool window_matches(Window *w, int fd, int prot, uint64_t offset, size_t size) { - assert(w); - assert(fd >= 0); - assert(size > 0); - - return - w->fd && - fd == w->fd->fd && - prot == w->prot && - offset >= w->offset && - offset + size <= w->offset + w->size; -} - -static Window *window_add(MMapCache *m, FileDescriptor *fd, int prot, bool keep_always, uint64_t offset, size_t size, void *ptr) { - Window *w; - - assert(m); - assert(fd); - - if (!m->last_unused || m->n_windows <= WINDOWS_MIN) { - - /* Allocate a new window */ - w = new0(Window, 1); - if (!w) - return NULL; - m->n_windows++; - } else { - - /* Reuse an existing one */ - w = m->last_unused; - window_unlink(w); - zero(*w); - } - - w->cache = m; - w->fd = fd; - w->prot = prot; - w->keep_always = keep_always; - w->offset = offset; - w->size = size; - w->ptr = ptr; - - LIST_PREPEND(by_fd, fd->windows, w); - - return w; -} - -static void context_detach_window(Context *c) { - Window *w; - - assert(c); - - if (!c->window) - return; - - w = c->window; - c->window = NULL; - LIST_REMOVE(by_window, w->contexts, c); - - if (!w->contexts && !w->keep_always) { - /* Not used anymore? */ -#ifdef ENABLE_DEBUG_MMAP_CACHE - /* Unmap unused windows immediately to expose use-after-unmap - * by SIGSEGV. */ - window_free(w); -#else - LIST_PREPEND(unused, c->cache->unused, w); - if (!c->cache->last_unused) - c->cache->last_unused = w; - - w->in_unused = true; -#endif - } -} - -static void context_attach_window(Context *c, Window *w) { - assert(c); - assert(w); - - if (c->window == w) - return; - - context_detach_window(c); - - if (w->in_unused) { - /* Used again? */ - LIST_REMOVE(unused, c->cache->unused, w); - if (c->cache->last_unused == w) - c->cache->last_unused = w->unused_prev; - - w->in_unused = false; - } - - c->window = w; - LIST_PREPEND(by_window, w->contexts, c); -} - -static Context *context_add(MMapCache *m, unsigned id) { - Context *c; - - assert(m); - - c = m->contexts[id]; - if (c) - return c; - - c = new0(Context, 1); - if (!c) - return NULL; - - c->cache = m; - c->id = id; - - assert(!m->contexts[id]); - m->contexts[id] = c; - - return c; -} - -static void context_free(Context *c) { - assert(c); - - context_detach_window(c); - - if (c->cache) { - assert(c->cache->contexts[c->id] == c); - c->cache->contexts[c->id] = NULL; - } - - free(c); -} - -static void fd_free(FileDescriptor *f) { - assert(f); - - while (f->windows) - window_free(f->windows); - - if (f->cache) - assert_se(hashmap_remove(f->cache->fds, FD_TO_PTR(f->fd))); - - free(f); -} - -static FileDescriptor* fd_add(MMapCache *m, int fd) { - FileDescriptor *f; - int r; - - assert(m); - assert(fd >= 0); - - f = hashmap_get(m->fds, FD_TO_PTR(fd)); - if (f) - return f; - - r = hashmap_ensure_allocated(&m->fds, NULL); - if (r < 0) - return NULL; - - f = new0(FileDescriptor, 1); - if (!f) - return NULL; - - f->cache = m; - f->fd = fd; - - r = hashmap_put(m->fds, FD_TO_PTR(fd), f); - if (r < 0) { - free(f); - return NULL; - } - - return f; -} - -static void mmap_cache_free(MMapCache *m) { - FileDescriptor *f; - int i; - - assert(m); - - for (i = 0; i < MMAP_CACHE_MAX_CONTEXTS; i++) - if (m->contexts[i]) - context_free(m->contexts[i]); - - while ((f = hashmap_first(m->fds))) - fd_free(f); - - hashmap_free(m->fds); - - while (m->unused) - window_free(m->unused); - - free(m); -} - -MMapCache* mmap_cache_unref(MMapCache *m) { - - if (!m) - return NULL; - - assert(m->n_ref > 0); - - m->n_ref--; - if (m->n_ref == 0) - mmap_cache_free(m); - - return NULL; -} - -static int make_room(MMapCache *m) { - assert(m); - - if (!m->last_unused) - return 0; - - window_free(m->last_unused); - return 1; -} - -static int try_context( - MMapCache *m, - int fd, - int prot, - unsigned context, - bool keep_always, - uint64_t offset, - size_t size, - void **ret) { - - Context *c; - - assert(m); - assert(m->n_ref > 0); - assert(fd >= 0); - assert(size > 0); - assert(ret); - - c = m->contexts[context]; - if (!c) - return 0; - - assert(c->id == context); - - if (!c->window) - return 0; - - if (!window_matches(c->window, fd, prot, offset, size)) { - - /* Drop the reference to the window, since it's unnecessary now */ - context_detach_window(c); - return 0; - } - - if (c->window->fd->sigbus) - return -EIO; - - c->window->keep_always = c->window->keep_always || keep_always; - - *ret = (uint8_t*) c->window->ptr + (offset - c->window->offset); - return 1; -} - -static int find_mmap( - MMapCache *m, - int fd, - int prot, - unsigned context, - bool keep_always, - uint64_t offset, - size_t size, - void **ret) { - - FileDescriptor *f; - Window *w; - Context *c; - - assert(m); - assert(m->n_ref > 0); - assert(fd >= 0); - assert(size > 0); - - f = hashmap_get(m->fds, FD_TO_PTR(fd)); - if (!f) - return 0; - - assert(f->fd == fd); - - if (f->sigbus) - return -EIO; - - LIST_FOREACH(by_fd, w, f->windows) - if (window_matches(w, fd, prot, offset, size)) - break; - - if (!w) - return 0; - - c = context_add(m, context); - if (!c) - return -ENOMEM; - - context_attach_window(c, w); - w->keep_always = w->keep_always || keep_always; - - *ret = (uint8_t*) w->ptr + (offset - w->offset); - return 1; -} - -static int mmap_try_harder(MMapCache *m, void *addr, int fd, int prot, int flags, uint64_t offset, size_t size, void **res) { - void *ptr; - - assert(m); - assert(fd >= 0); - assert(res); - - for (;;) { - int r; - - ptr = mmap(addr, size, prot, flags, fd, offset); - if (ptr != MAP_FAILED) - break; - if (errno != ENOMEM) - return -errno; - - r = make_room(m); - if (r < 0) - return r; - if (r == 0) - return -ENOMEM; - } - - *res = ptr; - return 0; -} - -static int add_mmap( - MMapCache *m, - int fd, - int prot, - unsigned context, - bool keep_always, - uint64_t offset, - size_t size, - struct stat *st, - void **ret) { - - uint64_t woffset, wsize; - Context *c; - FileDescriptor *f; - Window *w; - void *d; - int r; - - assert(m); - assert(m->n_ref > 0); - assert(fd >= 0); - assert(size > 0); - assert(ret); - - woffset = offset & ~((uint64_t) page_size() - 1ULL); - wsize = size + (offset - woffset); - wsize = PAGE_ALIGN(wsize); - - if (wsize < WINDOW_SIZE) { - uint64_t delta; - - delta = PAGE_ALIGN((WINDOW_SIZE - wsize) / 2); - - if (delta > offset) - woffset = 0; - else - woffset -= delta; - - wsize = WINDOW_SIZE; - } - - if (st) { - /* Memory maps that are larger then the files - underneath have undefined behavior. Hence, clamp - things to the file size if we know it */ - - if (woffset >= (uint64_t) st->st_size) - return -EADDRNOTAVAIL; - - if (woffset + wsize > (uint64_t) st->st_size) - wsize = PAGE_ALIGN(st->st_size - woffset); - } - - r = mmap_try_harder(m, NULL, fd, prot, MAP_SHARED, woffset, wsize, &d); - if (r < 0) - return r; - - c = context_add(m, context); - if (!c) - goto outofmem; - - f = fd_add(m, fd); - if (!f) - goto outofmem; - - w = window_add(m, f, prot, keep_always, woffset, wsize, d); - if (!w) - goto outofmem; - - context_detach_window(c); - c->window = w; - LIST_PREPEND(by_window, w->contexts, c); - - *ret = (uint8_t*) w->ptr + (offset - w->offset); - return 1; - -outofmem: - munmap(d, wsize); - return -ENOMEM; -} - -int mmap_cache_get( - MMapCache *m, - int fd, - int prot, - unsigned context, - bool keep_always, - uint64_t offset, - size_t size, - struct stat *st, - void **ret) { - - int r; - - assert(m); - assert(m->n_ref > 0); - assert(fd >= 0); - assert(size > 0); - assert(ret); - assert(context < MMAP_CACHE_MAX_CONTEXTS); - - /* Check whether the current context is the right one already */ - r = try_context(m, fd, prot, context, keep_always, offset, size, ret); - if (r != 0) { - m->n_hit++; - return r; - } - - /* Search for a matching mmap */ - r = find_mmap(m, fd, prot, context, keep_always, offset, size, ret); - if (r != 0) { - m->n_hit++; - return r; - } - - m->n_missed++; - - /* Create a new mmap */ - return add_mmap(m, fd, prot, context, keep_always, offset, size, st, ret); -} - -unsigned mmap_cache_get_hit(MMapCache *m) { - assert(m); - - return m->n_hit; -} - -unsigned mmap_cache_get_missed(MMapCache *m) { - assert(m); - - return m->n_missed; -} - -static void mmap_cache_process_sigbus(MMapCache *m) { - bool found = false; - FileDescriptor *f; - Iterator i; - int r; - - assert(m); - - /* Iterate through all triggered pages and mark their files as - * invalidated */ - for (;;) { - bool ours; - void *addr; - - r = sigbus_pop(&addr); - if (_likely_(r == 0)) - break; - if (r < 0) { - log_error_errno(r, "SIGBUS handling failed: %m"); - abort(); - } - - ours = false; - HASHMAP_FOREACH(f, m->fds, i) { - Window *w; - - LIST_FOREACH(by_fd, w, f->windows) { - if ((uint8_t*) addr >= (uint8_t*) w->ptr && - (uint8_t*) addr < (uint8_t*) w->ptr + w->size) { - found = ours = f->sigbus = true; - break; - } - } - - if (ours) - break; - } - - /* Didn't find a matching window, give up */ - if (!ours) { - log_error("Unknown SIGBUS page, aborting."); - abort(); - } - } - - /* The list of triggered pages is now empty. Now, let's remap - * all windows of the triggered file to anonymous maps, so - * that no page of the file in question is triggered again, so - * that we can be sure not to hit the queue size limit. */ - if (_likely_(!found)) - return; - - HASHMAP_FOREACH(f, m->fds, i) { - Window *w; - - if (!f->sigbus) - continue; - - LIST_FOREACH(by_fd, w, f->windows) - window_invalidate(w); - } -} - -bool mmap_cache_got_sigbus(MMapCache *m, int fd) { - FileDescriptor *f; - - assert(m); - assert(fd >= 0); - - mmap_cache_process_sigbus(m); - - f = hashmap_get(m->fds, FD_TO_PTR(fd)); - if (!f) - return false; - - return f->sigbus; -} - -void mmap_cache_close_fd(MMapCache *m, int fd) { - FileDescriptor *f; - - assert(m); - assert(fd >= 0); - - /* Make sure that any queued SIGBUS are first dispatched, so - * that we don't end up with a SIGBUS entry we cannot relate - * to any existing memory map */ - - mmap_cache_process_sigbus(m); - - f = hashmap_get(m->fds, FD_TO_PTR(fd)); - if (!f) - return; - - fd_free(f); -} diff --git a/src/journal/mmap-cache.h b/src/journal/mmap-cache.h deleted file mode 100644 index 199d944647..0000000000 --- a/src/journal/mmap-cache.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 - -/* One context per object type, plus one of the header, plus one "additional" one */ -#define MMAP_CACHE_MAX_CONTEXTS 9 - -typedef struct MMapCache MMapCache; - -MMapCache* mmap_cache_new(void); -MMapCache* mmap_cache_ref(MMapCache *m); -MMapCache* mmap_cache_unref(MMapCache *m); - -int mmap_cache_get( - MMapCache *m, - int fd, - int prot, - unsigned context, - bool keep_always, - uint64_t offset, - size_t size, - struct stat *st, - void **ret); -void mmap_cache_close_fd(MMapCache *m, int fd); - -unsigned mmap_cache_get_hit(MMapCache *m); -unsigned mmap_cache_get_missed(MMapCache *m); - -bool mmap_cache_got_sigbus(MMapCache *m, int fd); diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c deleted file mode 100644 index 1cea68ad42..0000000000 --- a/src/journal/sd-journal.c +++ /dev/null @@ -1,2985 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include -#include -#include -#include -#include -#include -#include - -#include "sd-journal.h" - -#include "alloc-util.h" -#include "catalog.h" -#include "compress.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "hashmap.h" -#include "hostname-util.h" -#include "io-util.h" -#include "journal-def.h" -#include "journal-file.h" -#include "journal-internal.h" -#include "list.h" -#include "lookup3.h" -#include "missing.h" -#include "path-util.h" -#include "replace-var.h" -#include "stat-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "strv.h" - -#define JOURNAL_FILES_MAX 7168 - -#define JOURNAL_FILES_RECHECK_USEC (2 * USEC_PER_SEC) - -#define REPLACE_VAR_MAX 256 - -#define DEFAULT_DATA_THRESHOLD (64*1024) - -static void remove_file_real(sd_journal *j, JournalFile *f); - -static bool journal_pid_changed(sd_journal *j) { - assert(j); - - /* We don't support people creating a journal object and - * keeping it around over a fork(). Let's complain. */ - - return j->original_pid != getpid(); -} - -static int journal_put_error(sd_journal *j, int r, const char *path) { - char *copy; - int k; - - /* Memorize an error we encountered, and store which - * file/directory it was generated from. Note that we store - * only *one* path per error code, as the error code is the - * key into the hashmap, and the path is the value. This means - * we keep track only of all error kinds, but not of all error - * locations. This has the benefit that the hashmap cannot - * grow beyond bounds. - * - * We return an error here only if we didn't manage to - * memorize the real error. */ - - if (r >= 0) - return r; - - k = hashmap_ensure_allocated(&j->errors, NULL); - if (k < 0) - return k; - - if (path) { - copy = strdup(path); - if (!copy) - return -ENOMEM; - } else - copy = NULL; - - k = hashmap_put(j->errors, INT_TO_PTR(r), copy); - if (k < 0) { - free(copy); - - if (k == -EEXIST) - return 0; - - return k; - } - - return 0; -} - -static void detach_location(sd_journal *j) { - Iterator i; - JournalFile *f; - - assert(j); - - j->current_file = NULL; - j->current_field = 0; - - ORDERED_HASHMAP_FOREACH(f, j->files, i) - journal_file_reset_location(f); -} - -static void reset_location(sd_journal *j) { - assert(j); - - detach_location(j); - zero(j->current_location); -} - -static void init_location(Location *l, LocationType type, JournalFile *f, Object *o) { - assert(l); - assert(type == LOCATION_DISCRETE || type == LOCATION_SEEK); - assert(f); - assert(o->object.type == OBJECT_ENTRY); - - l->type = type; - l->seqnum = le64toh(o->entry.seqnum); - l->seqnum_id = f->header->seqnum_id; - l->realtime = le64toh(o->entry.realtime); - l->monotonic = le64toh(o->entry.monotonic); - l->boot_id = o->entry.boot_id; - l->xor_hash = le64toh(o->entry.xor_hash); - - l->seqnum_set = l->realtime_set = l->monotonic_set = l->xor_hash_set = true; -} - -static void set_location(sd_journal *j, JournalFile *f, Object *o) { - assert(j); - assert(f); - assert(o); - - init_location(&j->current_location, LOCATION_DISCRETE, f, o); - - j->current_file = f; - j->current_field = 0; - - /* Let f know its candidate entry was picked. */ - assert(f->location_type == LOCATION_SEEK); - f->location_type = LOCATION_DISCRETE; -} - -static int match_is_valid(const void *data, size_t size) { - const char *b, *p; - - assert(data); - - if (size < 2) - return false; - - if (startswith(data, "__")) - return false; - - b = data; - for (p = b; p < b + size; p++) { - - if (*p == '=') - return p > b; - - if (*p == '_') - continue; - - if (*p >= 'A' && *p <= 'Z') - continue; - - if (*p >= '0' && *p <= '9') - continue; - - return false; - } - - return false; -} - -static bool same_field(const void *_a, size_t s, const void *_b, size_t t) { - const uint8_t *a = _a, *b = _b; - size_t j; - - for (j = 0; j < s && j < t; j++) { - - if (a[j] != b[j]) - return false; - - if (a[j] == '=') - return true; - } - - assert_not_reached("\"=\" not found"); -} - -static Match *match_new(Match *p, MatchType t) { - Match *m; - - m = new0(Match, 1); - if (!m) - return NULL; - - m->type = t; - - if (p) { - m->parent = p; - LIST_PREPEND(matches, p->matches, m); - } - - return m; -} - -static void match_free(Match *m) { - assert(m); - - while (m->matches) - match_free(m->matches); - - if (m->parent) - LIST_REMOVE(matches, m->parent->matches, m); - - free(m->data); - free(m); -} - -static void match_free_if_empty(Match *m) { - if (!m || m->matches) - return; - - match_free(m); -} - -_public_ int sd_journal_add_match(sd_journal *j, const void *data, size_t size) { - Match *l3, *l4, *add_here = NULL, *m; - le64_t le_hash; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - assert_return(data, -EINVAL); - - if (size == 0) - size = strlen(data); - - assert_return(match_is_valid(data, size), -EINVAL); - - /* level 0: AND term - * level 1: OR terms - * level 2: AND terms - * level 3: OR terms - * level 4: concrete matches */ - - if (!j->level0) { - j->level0 = match_new(NULL, MATCH_AND_TERM); - if (!j->level0) - return -ENOMEM; - } - - if (!j->level1) { - j->level1 = match_new(j->level0, MATCH_OR_TERM); - if (!j->level1) - return -ENOMEM; - } - - if (!j->level2) { - j->level2 = match_new(j->level1, MATCH_AND_TERM); - if (!j->level2) - return -ENOMEM; - } - - assert(j->level0->type == MATCH_AND_TERM); - assert(j->level1->type == MATCH_OR_TERM); - assert(j->level2->type == MATCH_AND_TERM); - - le_hash = htole64(hash64(data, size)); - - LIST_FOREACH(matches, l3, j->level2->matches) { - assert(l3->type == MATCH_OR_TERM); - - LIST_FOREACH(matches, l4, l3->matches) { - assert(l4->type == MATCH_DISCRETE); - - /* Exactly the same match already? Then ignore - * this addition */ - if (l4->le_hash == le_hash && - l4->size == size && - memcmp(l4->data, data, size) == 0) - return 0; - - /* Same field? Then let's add this to this OR term */ - if (same_field(data, size, l4->data, l4->size)) { - add_here = l3; - break; - } - } - - if (add_here) - break; - } - - if (!add_here) { - add_here = match_new(j->level2, MATCH_OR_TERM); - if (!add_here) - goto fail; - } - - m = match_new(add_here, MATCH_DISCRETE); - if (!m) - goto fail; - - m->le_hash = le_hash; - m->size = size; - m->data = memdup(data, size); - if (!m->data) - goto fail; - - detach_location(j); - - return 0; - -fail: - match_free_if_empty(add_here); - match_free_if_empty(j->level2); - match_free_if_empty(j->level1); - match_free_if_empty(j->level0); - - return -ENOMEM; -} - -_public_ int sd_journal_add_conjunction(sd_journal *j) { - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - - if (!j->level0) - return 0; - - if (!j->level1) - return 0; - - if (!j->level1->matches) - return 0; - - j->level1 = NULL; - j->level2 = NULL; - - return 0; -} - -_public_ int sd_journal_add_disjunction(sd_journal *j) { - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - - if (!j->level0) - return 0; - - if (!j->level1) - return 0; - - if (!j->level2) - return 0; - - if (!j->level2->matches) - return 0; - - j->level2 = NULL; - return 0; -} - -static char *match_make_string(Match *m) { - char *p, *r; - Match *i; - bool enclose = false; - - if (!m) - return strdup("none"); - - if (m->type == MATCH_DISCRETE) - return strndup(m->data, m->size); - - p = NULL; - LIST_FOREACH(matches, i, m->matches) { - char *t, *k; - - t = match_make_string(i); - if (!t) { - free(p); - return NULL; - } - - if (p) { - k = strjoin(p, m->type == MATCH_OR_TERM ? " OR " : " AND ", t, NULL); - free(p); - free(t); - - if (!k) - return NULL; - - p = k; - - enclose = true; - } else - p = t; - } - - if (enclose) { - r = strjoin("(", p, ")", NULL); - free(p); - return r; - } - - return p; -} - -char *journal_make_match_string(sd_journal *j) { - assert(j); - - return match_make_string(j->level0); -} - -_public_ void sd_journal_flush_matches(sd_journal *j) { - if (!j) - return; - - if (j->level0) - match_free(j->level0); - - j->level0 = j->level1 = j->level2 = NULL; - - detach_location(j); -} - -_pure_ static int compare_with_location(JournalFile *f, Location *l) { - assert(f); - assert(l); - assert(f->location_type == LOCATION_SEEK); - assert(l->type == LOCATION_DISCRETE || l->type == LOCATION_SEEK); - - if (l->monotonic_set && - sd_id128_equal(f->current_boot_id, l->boot_id) && - l->realtime_set && - f->current_realtime == l->realtime && - l->xor_hash_set && - f->current_xor_hash == l->xor_hash) - return 0; - - if (l->seqnum_set && - sd_id128_equal(f->header->seqnum_id, l->seqnum_id)) { - - if (f->current_seqnum < l->seqnum) - return -1; - if (f->current_seqnum > l->seqnum) - return 1; - } - - if (l->monotonic_set && - sd_id128_equal(f->current_boot_id, l->boot_id)) { - - if (f->current_monotonic < l->monotonic) - return -1; - if (f->current_monotonic > l->monotonic) - return 1; - } - - if (l->realtime_set) { - - if (f->current_realtime < l->realtime) - return -1; - if (f->current_realtime > l->realtime) - return 1; - } - - if (l->xor_hash_set) { - - if (f->current_xor_hash < l->xor_hash) - return -1; - if (f->current_xor_hash > l->xor_hash) - return 1; - } - - return 0; -} - -static int next_for_match( - sd_journal *j, - Match *m, - JournalFile *f, - uint64_t after_offset, - direction_t direction, - Object **ret, - uint64_t *offset) { - - int r; - uint64_t np = 0; - Object *n; - - assert(j); - assert(m); - assert(f); - - if (m->type == MATCH_DISCRETE) { - uint64_t dp; - - r = journal_file_find_data_object_with_hash(f, m->data, m->size, le64toh(m->le_hash), NULL, &dp); - if (r <= 0) - return r; - - return journal_file_move_to_entry_by_offset_for_data(f, dp, after_offset, direction, ret, offset); - - } else if (m->type == MATCH_OR_TERM) { - Match *i; - - /* Find the earliest match beyond after_offset */ - - LIST_FOREACH(matches, i, m->matches) { - uint64_t cp; - - r = next_for_match(j, i, f, after_offset, direction, NULL, &cp); - if (r < 0) - return r; - else if (r > 0) { - if (np == 0 || (direction == DIRECTION_DOWN ? cp < np : cp > np)) - np = cp; - } - } - - if (np == 0) - return 0; - - } else if (m->type == MATCH_AND_TERM) { - Match *i, *last_moved; - - /* Always jump to the next matching entry and repeat - * this until we find an offset that matches for all - * matches. */ - - if (!m->matches) - return 0; - - r = next_for_match(j, m->matches, f, after_offset, direction, NULL, &np); - if (r <= 0) - return r; - - assert(direction == DIRECTION_DOWN ? np >= after_offset : np <= after_offset); - last_moved = m->matches; - - LIST_LOOP_BUT_ONE(matches, i, m->matches, last_moved) { - uint64_t cp; - - r = next_for_match(j, i, f, np, direction, NULL, &cp); - if (r <= 0) - return r; - - assert(direction == DIRECTION_DOWN ? cp >= np : cp <= np); - if (direction == DIRECTION_DOWN ? cp > np : cp < np) { - np = cp; - last_moved = i; - } - } - } - - assert(np > 0); - - r = journal_file_move_to_object(f, OBJECT_ENTRY, np, &n); - if (r < 0) - return r; - - if (ret) - *ret = n; - if (offset) - *offset = np; - - return 1; -} - -static int find_location_for_match( - sd_journal *j, - Match *m, - JournalFile *f, - direction_t direction, - Object **ret, - uint64_t *offset) { - - int r; - - assert(j); - assert(m); - assert(f); - - if (m->type == MATCH_DISCRETE) { - uint64_t dp; - - r = journal_file_find_data_object_with_hash(f, m->data, m->size, le64toh(m->le_hash), NULL, &dp); - if (r <= 0) - return r; - - /* FIXME: missing: find by monotonic */ - - if (j->current_location.type == LOCATION_HEAD) - return journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_DOWN, ret, offset); - if (j->current_location.type == LOCATION_TAIL) - return journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_UP, ret, offset); - if (j->current_location.seqnum_set && sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id)) - return journal_file_move_to_entry_by_seqnum_for_data(f, dp, j->current_location.seqnum, direction, ret, offset); - if (j->current_location.monotonic_set) { - r = journal_file_move_to_entry_by_monotonic_for_data(f, dp, j->current_location.boot_id, j->current_location.monotonic, direction, ret, offset); - if (r != -ENOENT) - return r; - } - if (j->current_location.realtime_set) - return journal_file_move_to_entry_by_realtime_for_data(f, dp, j->current_location.realtime, direction, ret, offset); - - return journal_file_next_entry_for_data(f, NULL, 0, dp, direction, ret, offset); - - } else if (m->type == MATCH_OR_TERM) { - uint64_t np = 0; - Object *n; - Match *i; - - /* Find the earliest match */ - - LIST_FOREACH(matches, i, m->matches) { - uint64_t cp; - - r = find_location_for_match(j, i, f, direction, NULL, &cp); - if (r < 0) - return r; - else if (r > 0) { - if (np == 0 || (direction == DIRECTION_DOWN ? np > cp : np < cp)) - np = cp; - } - } - - if (np == 0) - return 0; - - r = journal_file_move_to_object(f, OBJECT_ENTRY, np, &n); - if (r < 0) - return r; - - if (ret) - *ret = n; - if (offset) - *offset = np; - - return 1; - - } else { - Match *i; - uint64_t np = 0; - - assert(m->type == MATCH_AND_TERM); - - /* First jump to the last match, and then find the - * next one where all matches match */ - - if (!m->matches) - return 0; - - LIST_FOREACH(matches, i, m->matches) { - uint64_t cp; - - r = find_location_for_match(j, i, f, direction, NULL, &cp); - if (r <= 0) - return r; - - if (np == 0 || (direction == DIRECTION_DOWN ? cp > np : cp < np)) - np = cp; - } - - return next_for_match(j, m, f, np, direction, ret, offset); - } -} - -static int find_location_with_matches( - sd_journal *j, - JournalFile *f, - direction_t direction, - Object **ret, - uint64_t *offset) { - - int r; - - assert(j); - assert(f); - assert(ret); - assert(offset); - - if (!j->level0) { - /* No matches is simple */ - - if (j->current_location.type == LOCATION_HEAD) - return journal_file_next_entry(f, 0, DIRECTION_DOWN, ret, offset); - if (j->current_location.type == LOCATION_TAIL) - return journal_file_next_entry(f, 0, DIRECTION_UP, ret, offset); - if (j->current_location.seqnum_set && sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id)) - return journal_file_move_to_entry_by_seqnum(f, j->current_location.seqnum, direction, ret, offset); - if (j->current_location.monotonic_set) { - r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, ret, offset); - if (r != -ENOENT) - return r; - } - if (j->current_location.realtime_set) - return journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, ret, offset); - - return journal_file_next_entry(f, 0, direction, ret, offset); - } else - return find_location_for_match(j, j->level0, f, direction, ret, offset); -} - -static int next_with_matches( - sd_journal *j, - JournalFile *f, - direction_t direction, - Object **ret, - uint64_t *offset) { - - assert(j); - assert(f); - assert(ret); - assert(offset); - - /* No matches is easy. We simple advance the file - * pointer by one. */ - if (!j->level0) - return journal_file_next_entry(f, f->current_offset, direction, ret, offset); - - /* If we have a match then we look for the next matching entry - * with an offset at least one step larger */ - return next_for_match(j, j->level0, f, - direction == DIRECTION_DOWN ? f->current_offset + 1 - : f->current_offset - 1, - direction, ret, offset); -} - -static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direction) { - Object *c; - uint64_t cp, n_entries; - int r; - - assert(j); - assert(f); - - n_entries = le64toh(f->header->n_entries); - - /* If we hit EOF before, we don't need to look into this file again - * unless direction changed or new entries appeared. */ - if (f->last_direction == direction && f->location_type == LOCATION_TAIL && - n_entries == f->last_n_entries) - return 0; - - f->last_n_entries = n_entries; - - if (f->last_direction == direction && f->current_offset > 0) { - /* LOCATION_SEEK here means we did the work in a previous - * iteration and the current location already points to a - * candidate entry. */ - if (f->location_type != LOCATION_SEEK) { - r = next_with_matches(j, f, direction, &c, &cp); - if (r <= 0) - return r; - - journal_file_save_location(f, c, cp); - } - } else { - f->last_direction = direction; - - r = find_location_with_matches(j, f, direction, &c, &cp); - if (r <= 0) - return r; - - journal_file_save_location(f, c, cp); - } - - /* OK, we found the spot, now let's advance until an entry - * that is actually different from what we were previously - * looking at. This is necessary to handle entries which exist - * in two (or more) journal files, and which shall all be - * suppressed but one. */ - - for (;;) { - bool found; - - if (j->current_location.type == LOCATION_DISCRETE) { - int k; - - k = compare_with_location(f, &j->current_location); - - found = direction == DIRECTION_DOWN ? k > 0 : k < 0; - } else - found = true; - - if (found) - return 1; - - r = next_with_matches(j, f, direction, &c, &cp); - if (r <= 0) - return r; - - journal_file_save_location(f, c, cp); - } -} - -static int real_journal_next(sd_journal *j, direction_t direction) { - JournalFile *f, *new_file = NULL; - Iterator i; - Object *o; - int r; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - - ORDERED_HASHMAP_FOREACH(f, j->files, i) { - bool found; - - r = next_beyond_location(j, f, direction); - if (r < 0) { - log_debug_errno(r, "Can't iterate through %s, ignoring: %m", f->path); - remove_file_real(j, f); - continue; - } else if (r == 0) { - f->location_type = LOCATION_TAIL; - continue; - } - - if (!new_file) - found = true; - else { - int k; - - k = journal_file_compare_locations(f, new_file); - - found = direction == DIRECTION_DOWN ? k < 0 : k > 0; - } - - if (found) - new_file = f; - } - - if (!new_file) - return 0; - - r = journal_file_move_to_object(new_file, OBJECT_ENTRY, new_file->current_offset, &o); - if (r < 0) - return r; - - set_location(j, new_file, o); - - return 1; -} - -_public_ int sd_journal_next(sd_journal *j) { - return real_journal_next(j, DIRECTION_DOWN); -} - -_public_ int sd_journal_previous(sd_journal *j) { - return real_journal_next(j, DIRECTION_UP); -} - -static int real_journal_next_skip(sd_journal *j, direction_t direction, uint64_t skip) { - int c = 0, r; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - - if (skip == 0) { - /* If this is not a discrete skip, then at least - * resolve the current location */ - if (j->current_location.type != LOCATION_DISCRETE) - return real_journal_next(j, direction); - - return 0; - } - - do { - r = real_journal_next(j, direction); - if (r < 0) - return r; - - if (r == 0) - return c; - - skip--; - c++; - } while (skip > 0); - - return c; -} - -_public_ int sd_journal_next_skip(sd_journal *j, uint64_t skip) { - return real_journal_next_skip(j, DIRECTION_DOWN, skip); -} - -_public_ int sd_journal_previous_skip(sd_journal *j, uint64_t skip) { - return real_journal_next_skip(j, DIRECTION_UP, skip); -} - -_public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) { - Object *o; - int r; - char bid[33], sid[33]; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - assert_return(cursor, -EINVAL); - - if (!j->current_file || j->current_file->current_offset <= 0) - return -EADDRNOTAVAIL; - - r = journal_file_move_to_object(j->current_file, OBJECT_ENTRY, j->current_file->current_offset, &o); - if (r < 0) - return r; - - sd_id128_to_string(j->current_file->header->seqnum_id, sid); - sd_id128_to_string(o->entry.boot_id, bid); - - if (asprintf(cursor, - "s=%s;i=%"PRIx64";b=%s;m=%"PRIx64";t=%"PRIx64";x=%"PRIx64, - sid, le64toh(o->entry.seqnum), - bid, le64toh(o->entry.monotonic), - le64toh(o->entry.realtime), - le64toh(o->entry.xor_hash)) < 0) - return -ENOMEM; - - return 0; -} - -_public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) { - const char *word, *state; - size_t l; - unsigned long long seqnum, monotonic, realtime, xor_hash; - bool - seqnum_id_set = false, - seqnum_set = false, - boot_id_set = false, - monotonic_set = false, - realtime_set = false, - xor_hash_set = false; - sd_id128_t seqnum_id, boot_id; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - assert_return(!isempty(cursor), -EINVAL); - - FOREACH_WORD_SEPARATOR(word, l, cursor, ";", state) { - char *item; - int k = 0; - - if (l < 2 || word[1] != '=') - return -EINVAL; - - item = strndup(word, l); - if (!item) - return -ENOMEM; - - switch (word[0]) { - - case 's': - seqnum_id_set = true; - k = sd_id128_from_string(item+2, &seqnum_id); - break; - - case 'i': - seqnum_set = true; - if (sscanf(item+2, "%llx", &seqnum) != 1) - k = -EINVAL; - break; - - case 'b': - boot_id_set = true; - k = sd_id128_from_string(item+2, &boot_id); - break; - - case 'm': - monotonic_set = true; - if (sscanf(item+2, "%llx", &monotonic) != 1) - k = -EINVAL; - break; - - case 't': - realtime_set = true; - if (sscanf(item+2, "%llx", &realtime) != 1) - k = -EINVAL; - break; - - case 'x': - xor_hash_set = true; - if (sscanf(item+2, "%llx", &xor_hash) != 1) - k = -EINVAL; - break; - } - - free(item); - - if (k < 0) - return k; - } - - if ((!seqnum_set || !seqnum_id_set) && - (!monotonic_set || !boot_id_set) && - !realtime_set) - return -EINVAL; - - reset_location(j); - - j->current_location.type = LOCATION_SEEK; - - if (realtime_set) { - j->current_location.realtime = (uint64_t) realtime; - j->current_location.realtime_set = true; - } - - if (seqnum_set && seqnum_id_set) { - j->current_location.seqnum = (uint64_t) seqnum; - j->current_location.seqnum_id = seqnum_id; - j->current_location.seqnum_set = true; - } - - if (monotonic_set && boot_id_set) { - j->current_location.monotonic = (uint64_t) monotonic; - j->current_location.boot_id = boot_id; - j->current_location.monotonic_set = true; - } - - if (xor_hash_set) { - j->current_location.xor_hash = (uint64_t) xor_hash; - j->current_location.xor_hash_set = true; - } - - return 0; -} - -_public_ int sd_journal_test_cursor(sd_journal *j, const char *cursor) { - int r; - Object *o; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - assert_return(!isempty(cursor), -EINVAL); - - if (!j->current_file || j->current_file->current_offset <= 0) - return -EADDRNOTAVAIL; - - r = journal_file_move_to_object(j->current_file, OBJECT_ENTRY, j->current_file->current_offset, &o); - if (r < 0) - return r; - - for (;;) { - _cleanup_free_ char *item = NULL; - unsigned long long ll; - sd_id128_t id; - int k = 0; - - r = extract_first_word(&cursor, &item, ";", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r < 0) - return r; - - if (r == 0) - break; - - if (strlen(item) < 2 || item[1] != '=') - return -EINVAL; - - switch (item[0]) { - - case 's': - k = sd_id128_from_string(item+2, &id); - if (k < 0) - return k; - if (!sd_id128_equal(id, j->current_file->header->seqnum_id)) - return 0; - break; - - case 'i': - if (sscanf(item+2, "%llx", &ll) != 1) - return -EINVAL; - if (ll != le64toh(o->entry.seqnum)) - return 0; - break; - - case 'b': - k = sd_id128_from_string(item+2, &id); - if (k < 0) - return k; - if (!sd_id128_equal(id, o->entry.boot_id)) - return 0; - break; - - case 'm': - if (sscanf(item+2, "%llx", &ll) != 1) - return -EINVAL; - if (ll != le64toh(o->entry.monotonic)) - return 0; - break; - - case 't': - if (sscanf(item+2, "%llx", &ll) != 1) - return -EINVAL; - if (ll != le64toh(o->entry.realtime)) - return 0; - break; - - case 'x': - if (sscanf(item+2, "%llx", &ll) != 1) - return -EINVAL; - if (ll != le64toh(o->entry.xor_hash)) - return 0; - break; - } - } - - return 1; -} - - -_public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) { - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - - reset_location(j); - j->current_location.type = LOCATION_SEEK; - j->current_location.boot_id = boot_id; - j->current_location.monotonic = usec; - j->current_location.monotonic_set = true; - - return 0; -} - -_public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) { - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - - reset_location(j); - j->current_location.type = LOCATION_SEEK; - j->current_location.realtime = usec; - j->current_location.realtime_set = true; - - return 0; -} - -_public_ int sd_journal_seek_head(sd_journal *j) { - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - - reset_location(j); - j->current_location.type = LOCATION_HEAD; - - return 0; -} - -_public_ int sd_journal_seek_tail(sd_journal *j) { - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - - reset_location(j); - j->current_location.type = LOCATION_TAIL; - - return 0; -} - -static void check_network(sd_journal *j, int fd) { - struct statfs sfs; - - assert(j); - - if (j->on_network) - return; - - if (fstatfs(fd, &sfs) < 0) - return; - - j->on_network = - F_TYPE_EQUAL(sfs.f_type, CIFS_MAGIC_NUMBER) || - F_TYPE_EQUAL(sfs.f_type, CODA_SUPER_MAGIC) || - F_TYPE_EQUAL(sfs.f_type, NCP_SUPER_MAGIC) || - F_TYPE_EQUAL(sfs.f_type, NFS_SUPER_MAGIC) || - F_TYPE_EQUAL(sfs.f_type, SMB_SUPER_MAGIC); -} - -static bool file_has_type_prefix(const char *prefix, const char *filename) { - const char *full, *tilded, *atted; - - full = strjoina(prefix, ".journal"); - tilded = strjoina(full, "~"); - atted = strjoina(prefix, "@"); - - return streq(filename, full) || - streq(filename, tilded) || - startswith(filename, atted); -} - -static bool file_type_wanted(int flags, const char *filename) { - assert(filename); - - if (!endswith(filename, ".journal") && !endswith(filename, ".journal~")) - return false; - - /* no flags set → every type is OK */ - if (!(flags & (SD_JOURNAL_SYSTEM | SD_JOURNAL_CURRENT_USER))) - return true; - - if (flags & SD_JOURNAL_SYSTEM && file_has_type_prefix("system", filename)) - return true; - - if (flags & SD_JOURNAL_CURRENT_USER) { - char prefix[5 + DECIMAL_STR_MAX(uid_t) + 1]; - - xsprintf(prefix, "user-"UID_FMT, getuid()); - - if (file_has_type_prefix(prefix, filename)) - return true; - } - - return false; -} - -static bool path_has_prefix(sd_journal *j, const char *path, const char *prefix) { - assert(j); - assert(path); - assert(prefix); - - if (j->toplevel_fd >= 0) - return false; - - return path_startswith(path, prefix); -} - -static const char *skip_slash(const char *p) { - - if (!p) - return NULL; - - while (*p == '/') - p++; - - return p; -} - -static int add_any_file(sd_journal *j, int fd, const char *path) { - JournalFile *f = NULL; - bool close_fd = false; - int r, k; - - assert(j); - assert(fd >= 0 || path); - - if (path && ordered_hashmap_get(j->files, path)) - return 0; - - if (ordered_hashmap_size(j->files) >= JOURNAL_FILES_MAX) { - log_debug("Too many open journal files, not adding %s.", path); - r = -ETOOMANYREFS; - goto fail; - } - - if (fd < 0 && j->toplevel_fd >= 0) { - - /* If there's a top-level fd defined, open the file relative to this now. (Make the path relative, - * explicitly, since otherwise openat() ignores the first argument.) */ - - fd = openat(j->toplevel_fd, skip_slash(path), O_RDONLY|O_CLOEXEC); - if (fd < 0) { - r = log_debug_errno(errno, "Failed to open journal file %s: %m", path); - goto fail; - } - - close_fd = true; - } - - r = journal_file_open(fd, path, O_RDONLY, 0, false, false, NULL, j->mmap, NULL, NULL, &f); - if (r < 0) { - if (close_fd) - safe_close(fd); - log_debug_errno(r, "Failed to open journal file %s: %m", path); - goto fail; - } - - /* journal_file_dump(f); */ - - r = ordered_hashmap_put(j->files, f->path, f); - if (r < 0) { - f->close_fd = close_fd; - (void) journal_file_close(f); - goto fail; - } - - if (!j->has_runtime_files && path_has_prefix(j, f->path, "/run")) - j->has_runtime_files = true; - else if (!j->has_persistent_files && path_has_prefix(j, f->path, "/var")) - j->has_persistent_files = true; - - log_debug("File %s added.", f->path); - - check_network(j, f->fd); - - j->current_invalidate_counter++; - - return 0; - -fail: - k = journal_put_error(j, r, path); - if (k < 0) - return k; - - return r; -} - -static int add_file(sd_journal *j, const char *prefix, const char *filename) { - const char *path; - - assert(j); - assert(prefix); - assert(filename); - - if (j->no_new_files) - return 0; - - if (!file_type_wanted(j->flags, filename)) - return 0; - - path = strjoina(prefix, "/", filename); - return add_any_file(j, -1, path); -} - -static void remove_file(sd_journal *j, const char *prefix, const char *filename) { - const char *path; - JournalFile *f; - - assert(j); - assert(prefix); - assert(filename); - - path = strjoina(prefix, "/", filename); - f = ordered_hashmap_get(j->files, path); - if (!f) - return; - - remove_file_real(j, f); -} - -static void remove_file_real(sd_journal *j, JournalFile *f) { - assert(j); - assert(f); - - ordered_hashmap_remove(j->files, f->path); - - log_debug("File %s removed.", f->path); - - if (j->current_file == f) { - j->current_file = NULL; - j->current_field = 0; - } - - if (j->unique_file == f) { - /* Jump to the next unique_file or NULL if that one was last */ - j->unique_file = ordered_hashmap_next(j->files, j->unique_file->path); - j->unique_offset = 0; - if (!j->unique_file) - j->unique_file_lost = true; - } - - if (j->fields_file == f) { - j->fields_file = ordered_hashmap_next(j->files, j->fields_file->path); - j->fields_offset = 0; - if (!j->fields_file) - j->fields_file_lost = true; - } - - (void) journal_file_close(f); - - j->current_invalidate_counter++; -} - -static int dirname_is_machine_id(const char *fn) { - sd_id128_t id, machine; - int r; - - r = sd_id128_get_machine(&machine); - if (r < 0) - return r; - - r = sd_id128_from_string(fn, &id); - if (r < 0) - return r; - - return sd_id128_equal(id, machine); -} - -static int add_directory(sd_journal *j, const char *prefix, const char *dirname) { - _cleanup_free_ char *path = NULL; - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de = NULL; - Directory *m; - int r, k; - - assert(j); - assert(prefix); - - /* Adds a journal file directory to watch. If the directory is already tracked this updates the inotify watch - * and reenumerates directory contents */ - - if (dirname) - path = strjoin(prefix, "/", dirname, NULL); - else - path = strdup(prefix); - if (!path) { - r = -ENOMEM; - goto fail; - } - - log_debug("Considering directory %s.", path); - - /* We consider everything local that is in a directory for the local machine ID, or that is stored in /run */ - if ((j->flags & SD_JOURNAL_LOCAL_ONLY) && - !((dirname && dirname_is_machine_id(dirname) > 0) || path_has_prefix(j, path, "/run"))) - return 0; - - - if (j->toplevel_fd < 0) - d = opendir(path); - else - /* Open the specified directory relative to the the toplevel fd. Enforce that the path specified is - * relative, by dropping the initial slash */ - d = xopendirat(j->toplevel_fd, skip_slash(path), 0); - if (!d) { - r = log_debug_errno(errno, "Failed to open directory %s: %m", path); - goto fail; - } - - m = hashmap_get(j->directories_by_path, path); - if (!m) { - m = new0(Directory, 1); - if (!m) { - r = -ENOMEM; - goto fail; - } - - m->is_root = false; - m->path = path; - - if (hashmap_put(j->directories_by_path, m->path, m) < 0) { - free(m); - r = -ENOMEM; - goto fail; - } - - path = NULL; /* avoid freeing in cleanup */ - j->current_invalidate_counter++; - - log_debug("Directory %s added.", m->path); - - } else if (m->is_root) - return 0; - - if (m->wd <= 0 && j->inotify_fd >= 0) { - /* Watch this directory, if it not being watched yet. */ - - m->wd = inotify_add_watch_fd(j->inotify_fd, dirfd(d), - IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE| - IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT|IN_MOVED_FROM| - IN_ONLYDIR); - - if (m->wd > 0 && hashmap_put(j->directories_by_wd, INT_TO_PTR(m->wd), m) < 0) - inotify_rm_watch(j->inotify_fd, m->wd); - } - - FOREACH_DIRENT_ALL(de, d, r = log_debug_errno(errno, "Failed to read directory %s: %m", m->path); goto fail) { - - if (dirent_is_file_with_suffix(de, ".journal") || - dirent_is_file_with_suffix(de, ".journal~")) - (void) add_file(j, m->path, de->d_name); - } - - check_network(j, dirfd(d)); - - return 0; - -fail: - k = journal_put_error(j, r, path ?: prefix); - if (k < 0) - return k; - - return r; -} - -static int add_root_directory(sd_journal *j, const char *p, bool missing_ok) { - - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - Directory *m; - int r, k; - - assert(j); - - /* Adds a root directory to our set of directories to use. If the root directory is already in the set, we - * update the inotify logic, and renumerate the directory entries. This call may hence be called to initially - * populate the set, as well as to update it later. */ - - if (p) { - /* If there's a path specified, use it. */ - - if ((j->flags & SD_JOURNAL_RUNTIME_ONLY) && - !path_has_prefix(j, p, "/run")) - return -EINVAL; - - if (j->prefix) - p = strjoina(j->prefix, p); - - if (j->toplevel_fd < 0) - d = opendir(p); - else - d = xopendirat(j->toplevel_fd, skip_slash(p), 0); - - if (!d) { - if (errno == ENOENT && missing_ok) - return 0; - - r = log_debug_errno(errno, "Failed to open root directory %s: %m", p); - goto fail; - } - } else { - int dfd; - - /* If there's no path specified, then we use the top-level fd itself. We duplicate the fd here, since - * opendir() will take possession of the fd, and close it, which we don't want. */ - - p = "."; /* store this as "." in the directories hashmap */ - - dfd = fcntl(j->toplevel_fd, F_DUPFD_CLOEXEC, 3); - if (dfd < 0) { - r = -errno; - goto fail; - } - - d = fdopendir(dfd); - if (!d) { - r = -errno; - safe_close(dfd); - goto fail; - } - - rewinddir(d); - } - - m = hashmap_get(j->directories_by_path, p); - if (!m) { - m = new0(Directory, 1); - if (!m) { - r = -ENOMEM; - goto fail; - } - - m->is_root = true; - - m->path = strdup(p); - if (!m->path) { - free(m); - r = -ENOMEM; - goto fail; - } - - if (hashmap_put(j->directories_by_path, m->path, m) < 0) { - free(m->path); - free(m); - r = -ENOMEM; - goto fail; - } - - j->current_invalidate_counter++; - - log_debug("Root directory %s added.", m->path); - - } else if (!m->is_root) - return 0; - - if (m->wd <= 0 && j->inotify_fd >= 0) { - - m->wd = inotify_add_watch_fd(j->inotify_fd, dirfd(d), - IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE| - IN_ONLYDIR); - - if (m->wd > 0 && hashmap_put(j->directories_by_wd, INT_TO_PTR(m->wd), m) < 0) - inotify_rm_watch(j->inotify_fd, m->wd); - } - - if (j->no_new_files) - return 0; - - FOREACH_DIRENT_ALL(de, d, r = log_debug_errno(errno, "Failed to read directory %s: %m", m->path); goto fail) { - sd_id128_t id; - - if (dirent_is_file_with_suffix(de, ".journal") || - dirent_is_file_with_suffix(de, ".journal~")) - (void) add_file(j, m->path, de->d_name); - else if (IN_SET(de->d_type, DT_DIR, DT_LNK, DT_UNKNOWN) && - sd_id128_from_string(de->d_name, &id) >= 0) - (void) add_directory(j, m->path, de->d_name); - } - - check_network(j, dirfd(d)); - - return 0; - -fail: - k = journal_put_error(j, r, p); - if (k < 0) - return k; - - return r; -} - -static void remove_directory(sd_journal *j, Directory *d) { - assert(j); - - if (d->wd > 0) { - hashmap_remove(j->directories_by_wd, INT_TO_PTR(d->wd)); - - if (j->inotify_fd >= 0) - inotify_rm_watch(j->inotify_fd, d->wd); - } - - hashmap_remove(j->directories_by_path, d->path); - - if (d->is_root) - log_debug("Root directory %s removed.", d->path); - else - log_debug("Directory %s removed.", d->path); - - free(d->path); - free(d); -} - -static int add_search_paths(sd_journal *j) { - - static const char search_paths[] = - "/run/log/journal\0" - "/var/log/journal\0"; - const char *p; - - assert(j); - - /* We ignore most errors here, since the idea is to only open - * what's actually accessible, and ignore the rest. */ - - NULSTR_FOREACH(p, search_paths) - (void) add_root_directory(j, p, true); - - return 0; -} - -static int add_current_paths(sd_journal *j) { - Iterator i; - JournalFile *f; - - assert(j); - assert(j->no_new_files); - - /* Simply adds all directories for files we have open as directories. We don't expect errors here, so we - * treat them as fatal. */ - - ORDERED_HASHMAP_FOREACH(f, j->files, i) { - _cleanup_free_ char *dir; - int r; - - dir = dirname_malloc(f->path); - if (!dir) - return -ENOMEM; - - r = add_directory(j, dir, NULL); - if (r < 0) - return r; - } - - return 0; -} - -static int allocate_inotify(sd_journal *j) { - assert(j); - - if (j->inotify_fd < 0) { - j->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); - if (j->inotify_fd < 0) - return -errno; - } - - return hashmap_ensure_allocated(&j->directories_by_wd, NULL); -} - -static sd_journal *journal_new(int flags, const char *path) { - sd_journal *j; - - j = new0(sd_journal, 1); - if (!j) - return NULL; - - j->original_pid = getpid(); - j->toplevel_fd = -1; - j->inotify_fd = -1; - j->flags = flags; - j->data_threshold = DEFAULT_DATA_THRESHOLD; - - if (path) { - j->path = strdup(path); - if (!j->path) - goto fail; - } - - j->files = ordered_hashmap_new(&string_hash_ops); - j->directories_by_path = hashmap_new(&string_hash_ops); - j->mmap = mmap_cache_new(); - if (!j->files || !j->directories_by_path || !j->mmap) - goto fail; - - return j; - -fail: - sd_journal_close(j); - return NULL; -} - -_public_ int sd_journal_open(sd_journal **ret, int flags) { - sd_journal *j; - int r; - - assert_return(ret, -EINVAL); - assert_return((flags & ~(SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_RUNTIME_ONLY|SD_JOURNAL_SYSTEM|SD_JOURNAL_CURRENT_USER)) == 0, -EINVAL); - - j = journal_new(flags, NULL); - if (!j) - return -ENOMEM; - - r = add_search_paths(j); - if (r < 0) - goto fail; - - *ret = j; - return 0; - -fail: - sd_journal_close(j); - - return r; -} - -_public_ int sd_journal_open_container(sd_journal **ret, const char *machine, int flags) { - _cleanup_free_ char *root = NULL, *class = NULL; - sd_journal *j; - char *p; - int r; - - /* This is pretty much deprecated, people should use machined's OpenMachineRootDirectory() call instead in - * combination with sd_journal_open_directory_fd(). */ - - assert_return(machine, -EINVAL); - assert_return(ret, -EINVAL); - assert_return((flags & ~(SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM)) == 0, -EINVAL); - assert_return(machine_name_is_valid(machine), -EINVAL); - - p = strjoina("/run/systemd/machines/", machine); - r = parse_env_file(p, NEWLINE, "ROOT", &root, "CLASS", &class, NULL); - if (r == -ENOENT) - return -EHOSTDOWN; - if (r < 0) - return r; - if (!root) - return -ENODATA; - - if (!streq_ptr(class, "container")) - return -EIO; - - j = journal_new(flags, NULL); - if (!j) - return -ENOMEM; - - j->prefix = root; - root = NULL; - - r = add_search_paths(j); - if (r < 0) - goto fail; - - *ret = j; - return 0; - -fail: - sd_journal_close(j); - return r; -} - -_public_ int sd_journal_open_directory(sd_journal **ret, const char *path, int flags) { - sd_journal *j; - int r; - - assert_return(ret, -EINVAL); - assert_return(path, -EINVAL); - assert_return((flags & ~SD_JOURNAL_OS_ROOT) == 0, -EINVAL); - - j = journal_new(flags, path); - if (!j) - return -ENOMEM; - - if (flags & SD_JOURNAL_OS_ROOT) - r = add_search_paths(j); - else - r = add_root_directory(j, path, false); - if (r < 0) - goto fail; - - *ret = j; - return 0; - -fail: - sd_journal_close(j); - return r; -} - -_public_ int sd_journal_open_files(sd_journal **ret, const char **paths, int flags) { - sd_journal *j; - const char **path; - int r; - - assert_return(ret, -EINVAL); - assert_return(flags == 0, -EINVAL); - - j = journal_new(flags, NULL); - if (!j) - return -ENOMEM; - - STRV_FOREACH(path, paths) { - r = add_any_file(j, -1, *path); - if (r < 0) - goto fail; - } - - j->no_new_files = true; - - *ret = j; - return 0; - -fail: - sd_journal_close(j); - return r; -} - -_public_ int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags) { - sd_journal *j; - struct stat st; - int r; - - assert_return(ret, -EINVAL); - assert_return(fd >= 0, -EBADF); - assert_return((flags & ~SD_JOURNAL_OS_ROOT) == 0, -EINVAL); - - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISDIR(st.st_mode)) - return -EBADFD; - - j = journal_new(flags, NULL); - if (!j) - return -ENOMEM; - - j->toplevel_fd = fd; - - if (flags & SD_JOURNAL_OS_ROOT) - r = add_search_paths(j); - else - r = add_root_directory(j, NULL, false); - if (r < 0) - goto fail; - - *ret = j; - return 0; - -fail: - sd_journal_close(j); - return r; -} - -_public_ int sd_journal_open_files_fd(sd_journal **ret, int fds[], unsigned n_fds, int flags) { - Iterator iterator; - JournalFile *f; - sd_journal *j; - unsigned i; - int r; - - assert_return(ret, -EINVAL); - assert_return(n_fds > 0, -EBADF); - assert_return(flags == 0, -EINVAL); - - j = journal_new(flags, NULL); - if (!j) - return -ENOMEM; - - for (i = 0; i < n_fds; i++) { - struct stat st; - - if (fds[i] < 0) { - r = -EBADF; - goto fail; - } - - if (fstat(fds[i], &st) < 0) { - r = -errno; - goto fail; - } - - if (!S_ISREG(st.st_mode)) { - r = -EBADFD; - goto fail; - } - - r = add_any_file(j, fds[i], NULL); - if (r < 0) - goto fail; - } - - j->no_new_files = true; - j->no_inotify = true; - - *ret = j; - return 0; - -fail: - /* If we fail, make sure we don't take possession of the files we managed to make use of successfully, and they - * remain open */ - ORDERED_HASHMAP_FOREACH(f, j->files, iterator) - f->close_fd = false; - - sd_journal_close(j); - return r; -} - -_public_ void sd_journal_close(sd_journal *j) { - Directory *d; - JournalFile *f; - char *p; - - if (!j) - return; - - sd_journal_flush_matches(j); - - while ((f = ordered_hashmap_steal_first(j->files))) - (void) journal_file_close(f); - - ordered_hashmap_free(j->files); - - while ((d = hashmap_first(j->directories_by_path))) - remove_directory(j, d); - - while ((d = hashmap_first(j->directories_by_wd))) - remove_directory(j, d); - - hashmap_free(j->directories_by_path); - hashmap_free(j->directories_by_wd); - - safe_close(j->inotify_fd); - - if (j->mmap) { - log_debug("mmap cache statistics: %u hit, %u miss", mmap_cache_get_hit(j->mmap), mmap_cache_get_missed(j->mmap)); - mmap_cache_unref(j->mmap); - } - - while ((p = hashmap_steal_first(j->errors))) - free(p); - hashmap_free(j->errors); - - free(j->path); - free(j->prefix); - free(j->unique_field); - free(j->fields_buffer); - free(j); -} - -_public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) { - Object *o; - JournalFile *f; - int r; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - assert_return(ret, -EINVAL); - - f = j->current_file; - if (!f) - return -EADDRNOTAVAIL; - - if (f->current_offset <= 0) - return -EADDRNOTAVAIL; - - r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); - if (r < 0) - return r; - - *ret = le64toh(o->entry.realtime); - return 0; -} - -_public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id) { - Object *o; - JournalFile *f; - int r; - sd_id128_t id; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - - f = j->current_file; - if (!f) - return -EADDRNOTAVAIL; - - if (f->current_offset <= 0) - return -EADDRNOTAVAIL; - - r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); - if (r < 0) - return r; - - if (ret_boot_id) - *ret_boot_id = o->entry.boot_id; - else { - r = sd_id128_get_boot(&id); - if (r < 0) - return r; - - if (!sd_id128_equal(id, o->entry.boot_id)) - return -ESTALE; - } - - if (ret) - *ret = le64toh(o->entry.monotonic); - - return 0; -} - -static bool field_is_valid(const char *field) { - const char *p; - - assert(field); - - if (isempty(field)) - return false; - - if (startswith(field, "__")) - return false; - - for (p = field; *p; p++) { - - if (*p == '_') - continue; - - if (*p >= 'A' && *p <= 'Z') - continue; - - if (*p >= '0' && *p <= '9') - continue; - - return false; - } - - return true; -} - -_public_ int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *size) { - JournalFile *f; - uint64_t i, n; - size_t field_length; - int r; - Object *o; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - assert_return(field, -EINVAL); - assert_return(data, -EINVAL); - assert_return(size, -EINVAL); - assert_return(field_is_valid(field), -EINVAL); - - f = j->current_file; - if (!f) - return -EADDRNOTAVAIL; - - if (f->current_offset <= 0) - return -EADDRNOTAVAIL; - - r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); - if (r < 0) - return r; - - field_length = strlen(field); - - n = journal_file_entry_n_items(o); - for (i = 0; i < n; i++) { - uint64_t p, l; - le64_t le_hash; - size_t t; - int compression; - - p = le64toh(o->entry.items[i].object_offset); - le_hash = o->entry.items[i].hash; - r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); - if (r < 0) - return r; - - if (le_hash != o->data.hash) - return -EBADMSG; - - l = le64toh(o->object.size) - offsetof(Object, data.payload); - - compression = o->object.flags & OBJECT_COMPRESSION_MASK; - if (compression) { -#if defined(HAVE_XZ) || defined(HAVE_LZ4) - r = decompress_startswith(compression, - o->data.payload, l, - &f->compress_buffer, &f->compress_buffer_size, - field, field_length, '='); - if (r < 0) - log_debug_errno(r, "Cannot decompress %s object of length %"PRIu64" at offset "OFSfmt": %m", - object_compressed_to_string(compression), l, p); - else if (r > 0) { - - size_t rsize; - - r = decompress_blob(compression, - o->data.payload, l, - &f->compress_buffer, &f->compress_buffer_size, &rsize, - j->data_threshold); - if (r < 0) - return r; - - *data = f->compress_buffer; - *size = (size_t) rsize; - - return 0; - } -#else - return -EPROTONOSUPPORT; -#endif - } else if (l >= field_length+1 && - memcmp(o->data.payload, field, field_length) == 0 && - o->data.payload[field_length] == '=') { - - t = (size_t) l; - - if ((uint64_t) t != l) - return -E2BIG; - - *data = o->data.payload; - *size = t; - - return 0; - } - - r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); - if (r < 0) - return r; - } - - return -ENOENT; -} - -static int return_data(sd_journal *j, JournalFile *f, Object *o, const void **data, size_t *size) { - size_t t; - uint64_t l; - int compression; - - l = le64toh(o->object.size) - offsetof(Object, data.payload); - t = (size_t) l; - - /* We can't read objects larger than 4G on a 32bit machine */ - if ((uint64_t) t != l) - return -E2BIG; - - compression = o->object.flags & OBJECT_COMPRESSION_MASK; - if (compression) { -#if defined(HAVE_XZ) || defined(HAVE_LZ4) - size_t rsize; - int r; - - r = decompress_blob(compression, - o->data.payload, l, &f->compress_buffer, - &f->compress_buffer_size, &rsize, j->data_threshold); - if (r < 0) - return r; - - *data = f->compress_buffer; - *size = (size_t) rsize; -#else - return -EPROTONOSUPPORT; -#endif - } else { - *data = o->data.payload; - *size = t; - } - - return 0; -} - -_public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *size) { - JournalFile *f; - uint64_t p, n; - le64_t le_hash; - int r; - Object *o; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - assert_return(data, -EINVAL); - assert_return(size, -EINVAL); - - f = j->current_file; - if (!f) - return -EADDRNOTAVAIL; - - if (f->current_offset <= 0) - return -EADDRNOTAVAIL; - - r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); - if (r < 0) - return r; - - n = journal_file_entry_n_items(o); - if (j->current_field >= n) - return 0; - - p = le64toh(o->entry.items[j->current_field].object_offset); - le_hash = o->entry.items[j->current_field].hash; - r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); - if (r < 0) - return r; - - if (le_hash != o->data.hash) - return -EBADMSG; - - r = return_data(j, f, o, data, size); - if (r < 0) - return r; - - j->current_field++; - - return 1; -} - -_public_ void sd_journal_restart_data(sd_journal *j) { - if (!j) - return; - - j->current_field = 0; -} - -_public_ int sd_journal_get_fd(sd_journal *j) { - int r; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - - if (j->no_inotify) - return -EMEDIUMTYPE; - - if (j->inotify_fd >= 0) - return j->inotify_fd; - - r = allocate_inotify(j); - if (r < 0) - return r; - - log_debug("Reiterating files to get inotify watches established"); - - /* Iterate through all dirs again, to add them to the - * inotify */ - if (j->no_new_files) - r = add_current_paths(j); - else if (j->toplevel_fd >= 0) - r = add_root_directory(j, NULL, false); - else if (j->path) - r = add_root_directory(j, j->path, true); - else - r = add_search_paths(j); - if (r < 0) - return r; - - return j->inotify_fd; -} - -_public_ int sd_journal_get_events(sd_journal *j) { - int fd; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - - fd = sd_journal_get_fd(j); - if (fd < 0) - return fd; - - return POLLIN; -} - -_public_ int sd_journal_get_timeout(sd_journal *j, uint64_t *timeout_usec) { - int fd; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - assert_return(timeout_usec, -EINVAL); - - fd = sd_journal_get_fd(j); - if (fd < 0) - return fd; - - if (!j->on_network) { - *timeout_usec = (uint64_t) -1; - return 0; - } - - /* If we are on the network we need to regularly check for - * changes manually */ - - *timeout_usec = j->last_process_usec + JOURNAL_FILES_RECHECK_USEC; - return 1; -} - -static void process_inotify_event(sd_journal *j, struct inotify_event *e) { - Directory *d; - - assert(j); - assert(e); - - /* Is this a subdirectory we watch? */ - d = hashmap_get(j->directories_by_wd, INT_TO_PTR(e->wd)); - if (d) { - sd_id128_t id; - - if (!(e->mask & IN_ISDIR) && e->len > 0 && - (endswith(e->name, ".journal") || - endswith(e->name, ".journal~"))) { - - /* Event for a journal file */ - - if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) - (void) add_file(j, d->path, e->name); - else if (e->mask & (IN_DELETE|IN_MOVED_FROM|IN_UNMOUNT)) - remove_file(j, d->path, e->name); - - } else if (!d->is_root && e->len == 0) { - - /* Event for a subdirectory */ - - if (e->mask & (IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT)) - remove_directory(j, d); - - } else if (d->is_root && (e->mask & IN_ISDIR) && e->len > 0 && sd_id128_from_string(e->name, &id) >= 0) { - - /* Event for root directory */ - - if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) - (void) add_directory(j, d->path, e->name); - } - - return; - } - - if (e->mask & IN_IGNORED) - return; - - log_debug("Unknown inotify event."); -} - -static int determine_change(sd_journal *j) { - bool b; - - assert(j); - - b = j->current_invalidate_counter != j->last_invalidate_counter; - j->last_invalidate_counter = j->current_invalidate_counter; - - return b ? SD_JOURNAL_INVALIDATE : SD_JOURNAL_APPEND; -} - -_public_ int sd_journal_process(sd_journal *j) { - bool got_something = false; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - - j->last_process_usec = now(CLOCK_MONOTONIC); - - for (;;) { - union inotify_event_buffer buffer; - struct inotify_event *e; - ssize_t l; - - l = read(j->inotify_fd, &buffer, sizeof(buffer)); - if (l < 0) { - if (errno == EAGAIN || errno == EINTR) - return got_something ? determine_change(j) : SD_JOURNAL_NOP; - - return -errno; - } - - got_something = true; - - FOREACH_INOTIFY_EVENT(e, buffer, l) - process_inotify_event(j, e); - } -} - -_public_ int sd_journal_wait(sd_journal *j, uint64_t timeout_usec) { - int r; - uint64_t t; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - - if (j->inotify_fd < 0) { - - /* This is the first invocation, hence create the - * inotify watch */ - r = sd_journal_get_fd(j); - if (r < 0) - return r; - - /* The journal might have changed since the context - * object was created and we weren't watching before, - * hence don't wait for anything, and return - * immediately. */ - return determine_change(j); - } - - r = sd_journal_get_timeout(j, &t); - if (r < 0) - return r; - - if (t != (uint64_t) -1) { - usec_t n; - - n = now(CLOCK_MONOTONIC); - t = t > n ? t - n : 0; - - if (timeout_usec == (uint64_t) -1 || timeout_usec > t) - timeout_usec = t; - } - - do { - r = fd_wait_for_event(j->inotify_fd, POLLIN, timeout_usec); - } while (r == -EINTR); - - if (r < 0) - return r; - - return sd_journal_process(j); -} - -_public_ int sd_journal_get_cutoff_realtime_usec(sd_journal *j, uint64_t *from, uint64_t *to) { - Iterator i; - JournalFile *f; - bool first = true; - uint64_t fmin = 0, tmax = 0; - int r; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - assert_return(from || to, -EINVAL); - assert_return(from != to, -EINVAL); - - ORDERED_HASHMAP_FOREACH(f, j->files, i) { - usec_t fr, t; - - r = journal_file_get_cutoff_realtime_usec(f, &fr, &t); - if (r == -ENOENT) - continue; - if (r < 0) - return r; - if (r == 0) - continue; - - if (first) { - fmin = fr; - tmax = t; - first = false; - } else { - fmin = MIN(fr, fmin); - tmax = MAX(t, tmax); - } - } - - if (from) - *from = fmin; - if (to) - *to = tmax; - - return first ? 0 : 1; -} - -_public_ int sd_journal_get_cutoff_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t *from, uint64_t *to) { - Iterator i; - JournalFile *f; - bool found = false; - int r; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - assert_return(from || to, -EINVAL); - assert_return(from != to, -EINVAL); - - ORDERED_HASHMAP_FOREACH(f, j->files, i) { - usec_t fr, t; - - r = journal_file_get_cutoff_monotonic_usec(f, boot_id, &fr, &t); - if (r == -ENOENT) - continue; - if (r < 0) - return r; - if (r == 0) - continue; - - if (found) { - if (from) - *from = MIN(fr, *from); - if (to) - *to = MAX(t, *to); - } else { - if (from) - *from = fr; - if (to) - *to = t; - found = true; - } - } - - return found; -} - -void journal_print_header(sd_journal *j) { - Iterator i; - JournalFile *f; - bool newline = false; - - assert(j); - - ORDERED_HASHMAP_FOREACH(f, j->files, i) { - if (newline) - putchar('\n'); - else - newline = true; - - journal_file_print_header(f); - } -} - -_public_ int sd_journal_get_usage(sd_journal *j, uint64_t *bytes) { - Iterator i; - JournalFile *f; - uint64_t sum = 0; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - assert_return(bytes, -EINVAL); - - ORDERED_HASHMAP_FOREACH(f, j->files, i) { - struct stat st; - - if (fstat(f->fd, &st) < 0) - return -errno; - - sum += (uint64_t) st.st_blocks * 512ULL; - } - - *bytes = sum; - return 0; -} - -_public_ int sd_journal_query_unique(sd_journal *j, const char *field) { - char *f; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - assert_return(!isempty(field), -EINVAL); - assert_return(field_is_valid(field), -EINVAL); - - f = strdup(field); - if (!f) - return -ENOMEM; - - free(j->unique_field); - j->unique_field = f; - j->unique_file = NULL; - j->unique_offset = 0; - j->unique_file_lost = false; - - return 0; -} - -_public_ int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l) { - size_t k; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - assert_return(data, -EINVAL); - assert_return(l, -EINVAL); - assert_return(j->unique_field, -EINVAL); - - k = strlen(j->unique_field); - - if (!j->unique_file) { - if (j->unique_file_lost) - return 0; - - j->unique_file = ordered_hashmap_first(j->files); - if (!j->unique_file) - return 0; - - j->unique_offset = 0; - } - - for (;;) { - JournalFile *of; - Iterator i; - Object *o; - const void *odata; - size_t ol; - bool found; - int r; - - /* Proceed to next data object in the field's linked list */ - if (j->unique_offset == 0) { - r = journal_file_find_field_object(j->unique_file, j->unique_field, k, &o, NULL); - if (r < 0) - return r; - - j->unique_offset = r > 0 ? le64toh(o->field.head_data_offset) : 0; - } else { - r = journal_file_move_to_object(j->unique_file, OBJECT_DATA, j->unique_offset, &o); - if (r < 0) - return r; - - j->unique_offset = le64toh(o->data.next_field_offset); - } - - /* We reached the end of the list? Then start again, with the next file */ - if (j->unique_offset == 0) { - j->unique_file = ordered_hashmap_next(j->files, j->unique_file->path); - if (!j->unique_file) - return 0; - - continue; - } - - /* We do not use OBJECT_DATA context here, but OBJECT_UNUSED - * instead, so that we can look at this data object at the same - * time as one on another file */ - r = journal_file_move_to_object(j->unique_file, OBJECT_UNUSED, j->unique_offset, &o); - if (r < 0) - return r; - - /* Let's do the type check by hand, since we used 0 context above. */ - if (o->object.type != OBJECT_DATA) { - log_debug("%s:offset " OFSfmt ": object has type %d, expected %d", - j->unique_file->path, j->unique_offset, - o->object.type, OBJECT_DATA); - return -EBADMSG; - } - - r = return_data(j, j->unique_file, o, &odata, &ol); - if (r < 0) - return r; - - /* Check if we have at least the field name and "=". */ - if (ol <= k) { - log_debug("%s:offset " OFSfmt ": object has size %zu, expected at least %zu", - j->unique_file->path, j->unique_offset, - ol, k + 1); - return -EBADMSG; - } - - if (memcmp(odata, j->unique_field, k) || ((const char*) odata)[k] != '=') { - log_debug("%s:offset " OFSfmt ": object does not start with \"%s=\"", - j->unique_file->path, j->unique_offset, - j->unique_field); - return -EBADMSG; - } - - /* OK, now let's see if we already returned this data - * object by checking if it exists in the earlier - * traversed files. */ - found = false; - ORDERED_HASHMAP_FOREACH(of, j->files, i) { - if (of == j->unique_file) - break; - - /* Skip this file it didn't have any fields indexed */ - if (JOURNAL_HEADER_CONTAINS(of->header, n_fields) && le64toh(of->header->n_fields) <= 0) - continue; - - r = journal_file_find_data_object_with_hash(of, odata, ol, le64toh(o->data.hash), NULL, NULL); - if (r < 0) - return r; - if (r > 0) { - found = true; - break; - } - } - - if (found) - continue; - - r = return_data(j, j->unique_file, o, data, l); - if (r < 0) - return r; - - return 1; - } -} - -_public_ void sd_journal_restart_unique(sd_journal *j) { - if (!j) - return; - - j->unique_file = NULL; - j->unique_offset = 0; - j->unique_file_lost = false; -} - -_public_ int sd_journal_enumerate_fields(sd_journal *j, const char **field) { - int r; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - assert_return(field, -EINVAL); - - if (!j->fields_file) { - if (j->fields_file_lost) - return 0; - - j->fields_file = ordered_hashmap_first(j->files); - if (!j->fields_file) - return 0; - - j->fields_hash_table_index = 0; - j->fields_offset = 0; - } - - for (;;) { - JournalFile *f, *of; - Iterator i; - uint64_t m; - Object *o; - size_t sz; - bool found; - - f = j->fields_file; - - if (j->fields_offset == 0) { - bool eof = false; - - /* We are not yet positioned at any field. Let's pick the first one */ - r = journal_file_map_field_hash_table(f); - if (r < 0) - return r; - - m = le64toh(f->header->field_hash_table_size) / sizeof(HashItem); - for (;;) { - if (j->fields_hash_table_index >= m) { - /* Reached the end of the hash table, go to the next file. */ - eof = true; - break; - } - - j->fields_offset = le64toh(f->field_hash_table[j->fields_hash_table_index].head_hash_offset); - - if (j->fields_offset != 0) - break; - - /* Empty hash table bucket, go to next one */ - j->fields_hash_table_index++; - } - - if (eof) { - /* Proceed with next file */ - j->fields_file = ordered_hashmap_next(j->files, f->path); - if (!j->fields_file) { - *field = NULL; - return 0; - } - - j->fields_offset = 0; - j->fields_hash_table_index = 0; - continue; - } - - } else { - /* We are already positioned at a field. If so, let's figure out the next field from it */ - - r = journal_file_move_to_object(f, OBJECT_FIELD, j->fields_offset, &o); - if (r < 0) - return r; - - j->fields_offset = le64toh(o->field.next_hash_offset); - if (j->fields_offset == 0) { - /* Reached the end of the hash table chain */ - j->fields_hash_table_index++; - continue; - } - } - - /* We use OBJECT_UNUSED here, so that the iterator below doesn't remove our mmap window */ - r = journal_file_move_to_object(f, OBJECT_UNUSED, j->fields_offset, &o); - if (r < 0) - return r; - - /* Because we used OBJECT_UNUSED above, we need to do our type check manually */ - if (o->object.type != OBJECT_FIELD) { - log_debug("%s:offset " OFSfmt ": object has type %i, expected %i", f->path, j->fields_offset, o->object.type, OBJECT_FIELD); - return -EBADMSG; - } - - sz = le64toh(o->object.size) - offsetof(Object, field.payload); - - /* Let's see if we already returned this field name before. */ - found = false; - ORDERED_HASHMAP_FOREACH(of, j->files, i) { - if (of == f) - break; - - /* Skip this file it didn't have any fields indexed */ - if (JOURNAL_HEADER_CONTAINS(of->header, n_fields) && le64toh(of->header->n_fields) <= 0) - continue; - - r = journal_file_find_field_object_with_hash(of, o->field.payload, sz, le64toh(o->field.hash), NULL, NULL); - if (r < 0) - return r; - if (r > 0) { - found = true; - break; - } - } - - if (found) - continue; - - /* Check if this is really a valid string containing no NUL byte */ - if (memchr(o->field.payload, 0, sz)) - return -EBADMSG; - - if (sz > j->data_threshold) - sz = j->data_threshold; - - if (!GREEDY_REALLOC(j->fields_buffer, j->fields_buffer_allocated, sz + 1)) - return -ENOMEM; - - memcpy(j->fields_buffer, o->field.payload, sz); - j->fields_buffer[sz] = 0; - - if (!field_is_valid(j->fields_buffer)) - return -EBADMSG; - - *field = j->fields_buffer; - return 1; - } -} - -_public_ void sd_journal_restart_fields(sd_journal *j) { - if (!j) - return; - - j->fields_file = NULL; - j->fields_hash_table_index = 0; - j->fields_offset = 0; - j->fields_file_lost = false; -} - -_public_ int sd_journal_reliable_fd(sd_journal *j) { - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - - return !j->on_network; -} - -static char *lookup_field(const char *field, void *userdata) { - sd_journal *j = userdata; - const void *data; - size_t size, d; - int r; - - assert(field); - assert(j); - - r = sd_journal_get_data(j, field, &data, &size); - if (r < 0 || - size > REPLACE_VAR_MAX) - return strdup(field); - - d = strlen(field) + 1; - - return strndup((const char*) data + d, size - d); -} - -_public_ int sd_journal_get_catalog(sd_journal *j, char **ret) { - const void *data; - size_t size; - sd_id128_t id; - _cleanup_free_ char *text = NULL, *cid = NULL; - char *t; - int r; - - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - assert_return(ret, -EINVAL); - - r = sd_journal_get_data(j, "MESSAGE_ID", &data, &size); - if (r < 0) - return r; - - cid = strndup((const char*) data + 11, size - 11); - if (!cid) - return -ENOMEM; - - r = sd_id128_from_string(cid, &id); - if (r < 0) - return r; - - r = catalog_get(CATALOG_DATABASE, id, &text); - if (r < 0) - return r; - - t = replace_var(text, lookup_field, j); - if (!t) - return -ENOMEM; - - *ret = t; - return 0; -} - -_public_ int sd_journal_get_catalog_for_message_id(sd_id128_t id, char **ret) { - assert_return(ret, -EINVAL); - - return catalog_get(CATALOG_DATABASE, id, ret); -} - -_public_ int sd_journal_set_data_threshold(sd_journal *j, size_t sz) { - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - - j->data_threshold = sz; - return 0; -} - -_public_ int sd_journal_get_data_threshold(sd_journal *j, size_t *sz) { - assert_return(j, -EINVAL); - assert_return(!journal_pid_changed(j), -ECHILD); - assert_return(sz, -EINVAL); - - *sz = j->data_threshold; - return 0; -} - -_public_ int sd_journal_has_runtime_files(sd_journal *j) { - assert_return(j, -EINVAL); - - return j->has_runtime_files; -} - -_public_ int sd_journal_has_persistent_files(sd_journal *j) { - assert_return(j, -EINVAL); - - return j->has_persistent_files; -} diff --git a/src/journal/test-audit-type.c b/src/journal/test-audit-type.c deleted file mode 100644 index 88a2e6d9d9..0000000000 --- a/src/journal/test-audit-type.c +++ /dev/null @@ -1,42 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 Zbigniew Jędrzejewski-Szmek - - 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 - -#include "audit-type.h" - -static void print_audit_label(int i) { - const char *name; - - name = audit_type_name_alloca(i); - /* This is a separate function only because of alloca */ - printf("%i → %s → %s\n", i, audit_type_to_string(i), name); -} - -static void test_audit_type(void) { - int i; - - for (i = 0; i <= AUDIT_KERNEL; i++) - print_audit_label(i); -} - -int main(int argc, char **argv) { - test_audit_type(); -} diff --git a/src/journal/test-catalog.c b/src/journal/test-catalog.c deleted file mode 100644 index 898c876450..0000000000 --- a/src/journal/test-catalog.c +++ /dev/null @@ -1,264 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 Lennart Poettering - Copyright 2013 Zbigniew Jędrzejewski-Szmek - - 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 -#include -#include - -#include "sd-messages.h" - -#include "alloc-util.h" -#include "catalog.h" -#include "fd-util.h" -#include "fileio.h" -#include "log.h" -#include "macro.h" -#include "string-util.h" -#include "util.h" - -static const char *catalog_dirs[] = { - CATALOG_DIR, - NULL, -}; - -static const char *no_catalog_dirs[] = { - "/bin/hopefully/with/no/catalog", - NULL -}; - -static Hashmap * test_import(const char* contents, ssize_t size, int code) { - int r; - char name[] = "/tmp/test-catalog.XXXXXX"; - _cleanup_close_ int fd; - Hashmap *h; - - if (size < 0) - size = strlen(contents); - - assert_se(h = hashmap_new(&catalog_hash_ops)); - - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); - assert_se(fd >= 0); - assert_se(write(fd, contents, size) == size); - - r = catalog_import_file(h, name); - assert_se(r == code); - - unlink(name); - - return h; -} - -static void test_catalog_import_invalid(void) { - _cleanup_hashmap_free_free_free_ Hashmap *h = NULL; - - h = test_import("xxx", -1, -EINVAL); - assert_se(hashmap_isempty(h)); -} - -static void test_catalog_import_badid(void) { - _cleanup_hashmap_free_free_free_ Hashmap *h = NULL; - const char *input = -"-- 0027229ca0644181a76c4e92458afaff dededededededededededededededede\n" \ -"Subject: message\n" \ -"\n" \ -"payload\n"; - h = test_import(input, -1, -EINVAL); -} - -static void test_catalog_import_one(void) { - _cleanup_hashmap_free_free_free_ Hashmap *h = NULL; - char *payload; - Iterator j; - - const char *input = -"-- 0027229ca0644181a76c4e92458afaff dededededededededededededededed\n" \ -"Subject: message\n" \ -"\n" \ -"payload\n"; - const char *expect = -"Subject: message\n" \ -"\n" \ -"payload\n"; - - h = test_import(input, -1, 0); - assert_se(hashmap_size(h) == 1); - - HASHMAP_FOREACH(payload, h, j) { - printf("expect: %s\n", expect); - printf("actual: %s\n", payload); - assert_se(streq(expect, payload)); - } -} - -static void test_catalog_import_merge(void) { - _cleanup_hashmap_free_free_free_ Hashmap *h = NULL; - char *payload; - Iterator j; - - const char *input = -"-- 0027229ca0644181a76c4e92458afaff dededededededededededededededed\n" \ -"Subject: message\n" \ -"Defined-By: me\n" \ -"\n" \ -"payload\n" \ -"\n" \ -"-- 0027229ca0644181a76c4e92458afaff dededededededededededededededed\n" \ -"Subject: override subject\n" \ -"X-Header: hello\n" \ -"\n" \ -"override payload\n"; - - const char *combined = -"Subject: override subject\n" \ -"X-Header: hello\n" \ -"Subject: message\n" \ -"Defined-By: me\n" \ -"\n" \ -"override payload\n"; - - h = test_import(input, -1, 0); - assert_se(hashmap_size(h) == 1); - - HASHMAP_FOREACH(payload, h, j) { - assert_se(streq(combined, payload)); - } -} - -static void test_catalog_import_merge_no_body(void) { - _cleanup_hashmap_free_free_free_ Hashmap *h = NULL; - char *payload; - Iterator j; - - const char *input = -"-- 0027229ca0644181a76c4e92458afaff dededededededededededededededed\n" \ -"Subject: message\n" \ -"Defined-By: me\n" \ -"\n" \ -"payload\n" \ -"\n" \ -"-- 0027229ca0644181a76c4e92458afaff dededededededededededededededed\n" \ -"Subject: override subject\n" \ -"X-Header: hello\n" \ -"\n"; - - const char *combined = -"Subject: override subject\n" \ -"X-Header: hello\n" \ -"Subject: message\n" \ -"Defined-By: me\n" \ -"\n" \ -"payload\n"; - - h = test_import(input, -1, 0); - assert_se(hashmap_size(h) == 1); - - HASHMAP_FOREACH(payload, h, j) { - assert_se(streq(combined, payload)); - } -} - -static const char* database = NULL; - -static void test_catalog_update(void) { - static char name[] = "/tmp/test-catalog.XXXXXX"; - int r; - - r = mkostemp_safe(name, O_RDWR|O_CLOEXEC); - assert_se(r >= 0); - - database = name; - - /* Test what happens if there are no files. */ - r = catalog_update(database, NULL, NULL); - assert_se(r >= 0); - - /* Test what happens if there are no files in the directory. */ - r = catalog_update(database, NULL, no_catalog_dirs); - assert_se(r >= 0); - - /* Make sure that we at least have some files loaded or the - catalog_list below will fail. */ - r = catalog_update(database, NULL, catalog_dirs); - assert_se(r >= 0); -} - -static void test_catalog_file_lang(void) { - _cleanup_free_ char *lang = NULL, *lang2 = NULL, *lang3 = NULL, *lang4 = NULL; - - assert_se(catalog_file_lang("systemd.de_DE.catalog", &lang) == 1); - assert_se(streq(lang, "de_DE")); - - assert_se(catalog_file_lang("systemd..catalog", &lang2) == 0); - assert_se(lang2 == NULL); - - assert_se(catalog_file_lang("systemd.fr.catalog", &lang2) == 1); - assert_se(streq(lang2, "fr")); - - assert_se(catalog_file_lang("systemd.fr.catalog.gz", &lang3) == 0); - assert_se(lang3 == NULL); - - assert_se(catalog_file_lang("systemd.01234567890123456789012345678901.catalog", &lang3) == 0); - assert_se(lang3 == NULL); - - assert_se(catalog_file_lang("systemd.0123456789012345678901234567890.catalog", &lang3) == 1); - assert_se(streq(lang3, "0123456789012345678901234567890")); - - assert_se(catalog_file_lang("/x/y/systemd.catalog", &lang4) == 0); - assert_se(lang4 == NULL); - - assert_se(catalog_file_lang("/x/y/systemd.ru_RU.catalog", &lang4) == 1); - assert_se(streq(lang4, "ru_RU")); -} - -int main(int argc, char *argv[]) { - _cleanup_free_ char *text = NULL; - int r; - - setlocale(LC_ALL, "de_DE.UTF-8"); - - log_parse_environment(); - log_open(); - - test_catalog_file_lang(); - - test_catalog_import_invalid(); - test_catalog_import_badid(); - test_catalog_import_one(); - test_catalog_import_merge(); - test_catalog_import_merge_no_body(); - - test_catalog_update(); - - r = catalog_list(stdout, database, true); - assert_se(r >= 0); - - r = catalog_list(stdout, database, false); - assert_se(r >= 0); - - assert_se(catalog_get(database, SD_MESSAGE_COREDUMP, &text) >= 0); - printf(">>>%s<<<\n", text); - - if (database) - unlink(database); - - return 0; -} diff --git a/src/journal/test-compress-benchmark.c b/src/journal/test-compress-benchmark.c deleted file mode 100644 index 6f6d71435d..0000000000 --- a/src/journal/test-compress-benchmark.c +++ /dev/null @@ -1,180 +0,0 @@ -/*** - This file is part of systemd - - Copyright 2014 Zbigniew Jędrzejewski-Szmek - - 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 "alloc-util.h" -#include "compress.h" -#include "macro.h" -#include "parse-util.h" -#include "random-util.h" -#include "string-util.h" -#include "util.h" - -typedef int (compress_t)(const void *src, uint64_t src_size, void *dst, - size_t dst_alloc_size, size_t *dst_size); -typedef int (decompress_t)(const void *src, uint64_t src_size, - void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max); - -static usec_t arg_duration = 2 * USEC_PER_SEC; -static size_t arg_start; - -#define MAX_SIZE (1024*1024LU) -#define PRIME 1048571 /* A prime close enough to one megabyte that mod 4 == 3 */ - -static size_t _permute(size_t x) { - size_t residue; - - if (x >= PRIME) - return x; - - residue = x*x % PRIME; - if (x <= PRIME / 2) - return residue; - else - return PRIME - residue; -} - -static size_t permute(size_t x) { - return _permute((_permute(x) + arg_start) % MAX_SIZE ^ 0xFF345); -} - -static char* make_buf(size_t count, const char *type) { - char *buf; - size_t i; - - buf = malloc(count); - assert_se(buf); - - if (streq(type, "zeros")) - memzero(buf, count); - else if (streq(type, "simple")) - for (i = 0; i < count; i++) - buf[i] = 'a' + i % ('z' - 'a' + 1); - else if (streq(type, "random")) { - size_t step = count / 10; - - random_bytes(buf, step); - memzero(buf + 1*step, step); - random_bytes(buf + 2*step, step); - memzero(buf + 3*step, step); - random_bytes(buf + 4*step, step); - memzero(buf + 5*step, step); - random_bytes(buf + 6*step, step); - memzero(buf + 7*step, step); - random_bytes(buf + 8*step, step); - memzero(buf + 9*step, step); - } else - assert_not_reached("here"); - - return buf; -} - -static void test_compress_decompress(const char* label, const char* type, - compress_t compress, decompress_t decompress) { - usec_t n, n2 = 0; - float dt; - - _cleanup_free_ char *text, *buf; - _cleanup_free_ void *buf2 = NULL; - size_t buf2_allocated = 0; - size_t skipped = 0, compressed = 0, total = 0; - - text = make_buf(MAX_SIZE, type); - buf = calloc(MAX_SIZE + 1, 1); - assert_se(text && buf); - - n = now(CLOCK_MONOTONIC); - - for (size_t i = 0; i <= MAX_SIZE; i++) { - size_t j = 0, k = 0, size; - int r; - - size = permute(i); - if (size == 0) - continue; - - log_debug("%s %zu %zu", type, i, size); - - memzero(buf, MIN(size + 1000, MAX_SIZE)); - - r = compress(text, size, buf, size, &j); - /* assume compression must be successful except for small or random inputs */ - assert_se(r == 0 || (size < 2048 && r == -ENOBUFS) || streq(type, "random")); - - /* check for overwrites */ - assert_se(buf[size] == 0); - if (r != 0) { - skipped += size; - continue; - } - - assert_se(j > 0); - if (j >= size) - log_error("%s \"compressed\" %zu -> %zu", label, size, j); - - r = decompress(buf, j, &buf2, &buf2_allocated, &k, 0); - assert_se(r == 0); - assert_se(buf2_allocated >= k); - assert_se(k == size); - - assert_se(memcmp(text, buf2, size) == 0); - - total += size; - compressed += j; - - n2 = now(CLOCK_MONOTONIC); - if (n2 - n > arg_duration) - break; - } - - dt = (n2-n) / 1e6; - - log_info("%s/%s: compressed & decompressed %zu bytes in %.2fs (%.2fMiB/s), " - "mean compresion %.2f%%, skipped %zu bytes", - label, type, total, dt, - total / 1024. / 1024 / dt, - 100 - compressed * 100. / total, - skipped); -} - -int main(int argc, char *argv[]) { - const char *i; - - log_set_max_level(LOG_INFO); - - if (argc >= 2) { - unsigned x; - - assert_se(safe_atou(argv[1], &x) >= 0); - arg_duration = x * USEC_PER_SEC; - } - if (argc == 3) - (void) safe_atozu(argv[2], &arg_start); - else - arg_start = getpid(); - - NULSTR_FOREACH(i, "zeros\0simple\0random\0") { -#ifdef HAVE_XZ - test_compress_decompress("XZ", i, compress_blob_xz, decompress_blob_xz); -#endif -#ifdef HAVE_LZ4 - test_compress_decompress("LZ4", i, compress_blob_lz4, decompress_blob_lz4); -#endif - } - return 0; -} diff --git a/src/journal/test-compress.c b/src/journal/test-compress.c deleted file mode 100644 index 68c9a4d76c..0000000000 --- a/src/journal/test-compress.c +++ /dev/null @@ -1,308 +0,0 @@ -/*** - This file is part of systemd - - Copyright 2014 Ronny Chevalier - - 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 . -***/ - -#ifdef HAVE_LZ4 -#include -#endif - -#include "alloc-util.h" -#include "compress.h" -#include "fd-util.h" -#include "fileio.h" -#include "macro.h" -#include "random-util.h" -#include "util.h" - -#ifdef HAVE_XZ -# define XZ_OK 0 -#else -# define XZ_OK -EPROTONOSUPPORT -#endif - -#ifdef HAVE_LZ4 -# define LZ4_OK 0 -#else -# define LZ4_OK -EPROTONOSUPPORT -#endif - -typedef int (compress_blob_t)(const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size); -typedef int (decompress_blob_t)(const void *src, uint64_t src_size, - void **dst, size_t *dst_alloc_size, - size_t* dst_size, size_t dst_max); -typedef int (decompress_sw_t)(const void *src, uint64_t src_size, - void **buffer, size_t *buffer_size, - const void *prefix, size_t prefix_len, - uint8_t extra); - -typedef int (compress_stream_t)(int fdf, int fdt, uint64_t max_bytes); -typedef int (decompress_stream_t)(int fdf, int fdt, uint64_t max_size); - -static void test_compress_decompress(int compression, - compress_blob_t compress, - decompress_blob_t decompress, - const char *data, - size_t data_len, - bool may_fail) { - char compressed[512]; - size_t csize, usize = 0; - _cleanup_free_ char *decompressed = NULL; - int r; - - log_info("/* testing %s %s blob compression/decompression */", - object_compressed_to_string(compression), data); - - r = compress(data, data_len, compressed, sizeof(compressed), &csize); - if (r == -ENOBUFS) { - log_info_errno(r, "compression failed: %m"); - assert_se(may_fail); - } else { - assert_se(r == 0); - r = decompress(compressed, csize, - (void **) &decompressed, &usize, &csize, 0); - assert_se(r == 0); - assert_se(decompressed); - assert_se(memcmp(decompressed, data, data_len) == 0); - } - - r = decompress("garbage", 7, - (void **) &decompressed, &usize, &csize, 0); - assert_se(r < 0); - - /* make sure to have the minimal lz4 compressed size */ - r = decompress("00000000\1g", 9, - (void **) &decompressed, &usize, &csize, 0); - assert_se(r < 0); - - r = decompress("\100000000g", 9, - (void **) &decompressed, &usize, &csize, 0); - assert_se(r < 0); - - memzero(decompressed, usize); -} - -static void test_decompress_startswith(int compression, - compress_blob_t compress, - decompress_sw_t decompress_sw, - const char *data, - size_t data_len, - bool may_fail) { - - char *compressed; - _cleanup_free_ char *compressed1 = NULL, *compressed2 = NULL, *decompressed = NULL; - size_t csize, usize = 0, len; - int r; - - log_info("/* testing decompress_startswith with %s on %.20s text*/", - object_compressed_to_string(compression), data); - -#define BUFSIZE_1 512 -#define BUFSIZE_2 20000 - - compressed = compressed1 = malloc(BUFSIZE_1); - assert_se(compressed1); - r = compress(data, data_len, compressed, BUFSIZE_1, &csize); - if (r == -ENOBUFS) { - log_info_errno(r, "compression failed: %m"); - assert_se(may_fail); - - compressed = compressed2 = malloc(BUFSIZE_2); - assert_se(compressed2); - r = compress(data, data_len, compressed, BUFSIZE_2, &csize); - assert(r == 0); - } - assert_se(r == 0); - - len = strlen(data); - - r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len, '\0'); - assert_se(r > 0); - r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len, 'w'); - assert_se(r == 0); - r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, "barbarbar", 9, ' '); - assert_se(r == 0); - r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len - 1, data[len-1]); - assert_se(r > 0); - r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len - 1, 'w'); - assert_se(r == 0); - r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len, '\0'); - assert_se(r > 0); -} - -static void test_compress_stream(int compression, - const char* cat, - compress_stream_t compress, - decompress_stream_t decompress, - const char *srcfile) { - - _cleanup_close_ int src = -1, dst = -1, dst2 = -1; - char pattern[] = "/tmp/systemd-test.compressed.XXXXXX", - pattern2[] = "/tmp/systemd-test.compressed.XXXXXX"; - int r; - _cleanup_free_ char *cmd = NULL, *cmd2; - struct stat st = {}; - - log_debug("/* testing %s compression */", - object_compressed_to_string(compression)); - - log_debug("/* create source from %s */", srcfile); - - assert_se((src = open(srcfile, O_RDONLY|O_CLOEXEC)) >= 0); - - log_debug("/* test compression */"); - - assert_se((dst = mkostemp_safe(pattern, O_RDWR|O_CLOEXEC)) >= 0); - - assert_se(compress(src, dst, -1) == 0); - - if (cat) { - assert_se(asprintf(&cmd, "%s %s | diff %s -", cat, pattern, srcfile) > 0); - assert_se(system(cmd) == 0); - } - - log_debug("/* test decompression */"); - - assert_se((dst2 = mkostemp_safe(pattern2, O_RDWR|O_CLOEXEC)) >= 0); - - assert_se(stat(srcfile, &st) == 0); - - assert_se(lseek(dst, 0, SEEK_SET) == 0); - r = decompress(dst, dst2, st.st_size); - assert_se(r == 0); - - assert_se(asprintf(&cmd2, "diff %s %s", srcfile, pattern2) > 0); - assert_se(system(cmd2) == 0); - - log_debug("/* test faulty decompression */"); - - assert_se(lseek(dst, 1, SEEK_SET) == 1); - r = decompress(dst, dst2, st.st_size); - assert_se(r == -EBADMSG || r == 0); - - assert_se(lseek(dst, 0, SEEK_SET) == 0); - assert_se(lseek(dst2, 0, SEEK_SET) == 0); - r = decompress(dst, dst2, st.st_size - 1); - assert_se(r == -EFBIG); - - assert_se(unlink(pattern) == 0); - assert_se(unlink(pattern2) == 0); -} - -#ifdef HAVE_LZ4 -static void test_lz4_decompress_partial(void) { - char buf[20000]; - size_t buf_size = sizeof(buf), compressed; - int r; - _cleanup_free_ char *huge = NULL; - -#define HUGE_SIZE (4096*1024) - huge = malloc(HUGE_SIZE); - memset(huge, 'x', HUGE_SIZE); - memcpy(huge, "HUGE=", 5); - - r = LZ4_compress_limitedOutput(huge, buf, HUGE_SIZE, buf_size); - assert_se(r >= 0); - compressed = r; - log_info("Compressed %i → %zu", HUGE_SIZE, compressed); - - r = LZ4_decompress_safe(buf, huge, r, HUGE_SIZE); - assert_se(r >= 0); - log_info("Decompressed → %i", r); - - r = LZ4_decompress_safe_partial(buf, huge, - compressed, - 12, HUGE_SIZE); - assert_se(r >= 0); - log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE, r); - - /* We expect this to fail, because that's how current lz4 works. If this - * call succeeds, then lz4 has been fixed, and we need to change our code. - */ - r = LZ4_decompress_safe_partial(buf, huge, - compressed, - 12, HUGE_SIZE-1); - assert_se(r < 0); - log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE-1, r); -} -#endif - -int main(int argc, char *argv[]) { - const char text[] = - "text\0foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF" - "foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF"; - - char data[512] = "random\0"; - - char huge[4096*1024]; - memset(huge, 'x', sizeof(huge)); - memcpy(huge, "HUGE=", 5); - char_array_0(huge); - - log_set_max_level(LOG_DEBUG); - - random_bytes(data + 7, sizeof(data) - 7); - -#ifdef HAVE_XZ - test_compress_decompress(OBJECT_COMPRESSED_XZ, compress_blob_xz, decompress_blob_xz, - text, sizeof(text), false); - test_compress_decompress(OBJECT_COMPRESSED_XZ, compress_blob_xz, decompress_blob_xz, - data, sizeof(data), true); - - test_decompress_startswith(OBJECT_COMPRESSED_XZ, - compress_blob_xz, decompress_startswith_xz, - text, sizeof(text), false); - test_decompress_startswith(OBJECT_COMPRESSED_XZ, - compress_blob_xz, decompress_startswith_xz, - data, sizeof(data), true); - test_decompress_startswith(OBJECT_COMPRESSED_XZ, - compress_blob_xz, decompress_startswith_xz, - huge, sizeof(huge), true); - - test_compress_stream(OBJECT_COMPRESSED_XZ, "xzcat", - compress_stream_xz, decompress_stream_xz, argv[0]); -#else - log_info("/* XZ test skipped */"); -#endif - -#ifdef HAVE_LZ4 - test_compress_decompress(OBJECT_COMPRESSED_LZ4, compress_blob_lz4, decompress_blob_lz4, - text, sizeof(text), false); - test_compress_decompress(OBJECT_COMPRESSED_LZ4, compress_blob_lz4, decompress_blob_lz4, - data, sizeof(data), true); - - test_decompress_startswith(OBJECT_COMPRESSED_LZ4, - compress_blob_lz4, decompress_startswith_lz4, - text, sizeof(text), false); - test_decompress_startswith(OBJECT_COMPRESSED_LZ4, - compress_blob_lz4, decompress_startswith_lz4, - data, sizeof(data), true); - test_decompress_startswith(OBJECT_COMPRESSED_LZ4, - compress_blob_lz4, decompress_startswith_lz4, - huge, sizeof(huge), true); - - test_compress_stream(OBJECT_COMPRESSED_LZ4, "lz4cat", - compress_stream_lz4, decompress_stream_lz4, argv[0]); - - test_lz4_decompress_partial(); -#else - log_info("/* LZ4 test skipped */"); -#endif - - return 0; -} diff --git a/src/journal/test-journal-enum.c b/src/journal/test-journal-enum.c deleted file mode 100644 index 354c2c3c00..0000000000 --- a/src/journal/test-journal-enum.c +++ /dev/null @@ -1,53 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 "sd-journal.h" - -#include "journal-internal.h" -#include "log.h" -#include "macro.h" - -int main(int argc, char *argv[]) { - unsigned n = 0; - _cleanup_(sd_journal_closep) sd_journal*j = NULL; - - log_set_max_level(LOG_DEBUG); - - assert_se(sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY) >= 0); - - assert_se(sd_journal_add_match(j, "_TRANSPORT=syslog", 0) >= 0); - assert_se(sd_journal_add_match(j, "_UID=0", 0) >= 0); - - SD_JOURNAL_FOREACH_BACKWARDS(j) { - const void *d; - size_t l; - - assert_se(sd_journal_get_data(j, "MESSAGE", &d, &l) >= 0); - - printf("%.*s\n", (int) l, (char*) d); - - n++; - if (n >= 10) - break; - } - - return 0; -} diff --git a/src/journal/test-journal-flush.c b/src/journal/test-journal-flush.c deleted file mode 100644 index ba8b20b228..0000000000 --- a/src/journal/test-journal-flush.c +++ /dev/null @@ -1,75 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-journal.h" - -#include "alloc-util.h" -#include "journal-file.h" -#include "journal-internal.h" -#include "macro.h" -#include "string-util.h" - -int main(int argc, char *argv[]) { - _cleanup_free_ char *fn = NULL; - char dn[] = "/var/tmp/test-journal-flush.XXXXXX"; - JournalFile *new_journal = NULL; - sd_journal *j = NULL; - unsigned n = 0; - int r; - - assert_se(mkdtemp(dn)); - fn = strappend(dn, "/test.journal"); - - r = journal_file_open(-1, fn, O_CREAT|O_RDWR, 0644, false, false, NULL, NULL, NULL, NULL, &new_journal); - assert_se(r >= 0); - - r = sd_journal_open(&j, 0); - assert_se(r >= 0); - - sd_journal_set_data_threshold(j, 0); - - SD_JOURNAL_FOREACH(j) { - Object *o; - JournalFile *f; - - f = j->current_file; - assert_se(f && f->current_offset > 0); - - r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); - assert_se(r >= 0); - - r = journal_file_copy_entry(f, new_journal, o, f->current_offset, NULL, NULL, NULL); - assert_se(r >= 0); - - n++; - if (n > 10000) - break; - } - - sd_journal_close(j); - - (void) journal_file_close(new_journal); - - unlink(fn); - assert_se(rmdir(dn) == 0); - - return 0; -} diff --git a/src/journal/test-journal-init.c b/src/journal/test-journal-init.c deleted file mode 100644 index ef21e2d05f..0000000000 --- a/src/journal/test-journal-init.c +++ /dev/null @@ -1,64 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 Zbigniew Jędrzejewski-Szmek - - 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 "sd-journal.h" - -#include "log.h" -#include "parse-util.h" -#include "rm-rf.h" -#include "util.h" - -int main(int argc, char *argv[]) { - sd_journal *j; - int r, i, I = 100; - char t[] = "/tmp/journal-stream-XXXXXX"; - - log_set_max_level(LOG_DEBUG); - - if (argc >= 2) { - r = safe_atoi(argv[1], &I); - if (r < 0) - log_info("Could not parse loop count argument. Using default."); - } - - log_info("Running %d loops", I); - - assert_se(mkdtemp(t)); - - for (i = 0; i < I; i++) { - r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); - assert_se(r == 0); - - sd_journal_close(j); - - r = sd_journal_open_directory(&j, t, 0); - assert_se(r == 0); - - sd_journal_close(j); - - j = NULL; - r = sd_journal_open_directory(&j, t, SD_JOURNAL_LOCAL_ONLY); - assert_se(r == -EINVAL); - assert_se(j == NULL); - } - - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); - - return 0; -} diff --git a/src/journal/test-journal-interleaving.c b/src/journal/test-journal-interleaving.c deleted file mode 100644 index 5e063f4d04..0000000000 --- a/src/journal/test-journal-interleaving.c +++ /dev/null @@ -1,307 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 Marius Vollmer - Copyright 2013 Zbigniew Jędrzejewski-Szmek - - 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 - -#include "sd-journal.h" - -#include "alloc-util.h" -#include "journal-file.h" -#include "journal-vacuum.h" -#include "log.h" -#include "parse-util.h" -#include "rm-rf.h" -#include "util.h" - -/* This program tests skipping around in a multi-file journal. - */ - -static bool arg_keep = false; - -noreturn static void log_assert_errno(const char *text, int eno, const char *file, int line, const char *func) { - log_internal(LOG_CRIT, 0, file, line, func, - "'%s' failed at %s:%u (%s): %s.", - text, file, line, func, strerror(eno)); - abort(); -} - -#define assert_ret(expr) \ - do { \ - int _r_ = (expr); \ - if (_unlikely_(_r_ < 0)) \ - log_assert_errno(#expr, -_r_, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ - } while (false) - -static JournalFile *test_open(const char *name) { - JournalFile *f; - assert_ret(journal_file_open(-1, name, O_RDWR|O_CREAT, 0644, true, false, NULL, NULL, NULL, NULL, &f)); - return f; -} - -static void test_close(JournalFile *f) { - (void) journal_file_close (f); -} - -static void append_number(JournalFile *f, int n, uint64_t *seqnum) { - char *p; - dual_timestamp ts; - static dual_timestamp previous_ts = {}; - struct iovec iovec[1]; - - dual_timestamp_get(&ts); - - if (ts.monotonic <= previous_ts.monotonic) - ts.monotonic = previous_ts.monotonic + 1; - - if (ts.realtime <= previous_ts.realtime) - ts.realtime = previous_ts.realtime + 1; - - previous_ts = ts; - - assert_se(asprintf(&p, "NUMBER=%d", n) >= 0); - iovec[0].iov_base = p; - iovec[0].iov_len = strlen(p); - assert_ret(journal_file_append_entry(f, &ts, iovec, 1, seqnum, NULL, NULL)); - free(p); -} - -static void test_check_number (sd_journal *j, int n) { - const void *d; - _cleanup_free_ char *k; - size_t l; - int x; - - assert_ret(sd_journal_get_data(j, "NUMBER", &d, &l)); - assert_se(k = strndup(d, l)); - printf("%s\n", k); - - assert_se(safe_atoi(k + 7, &x) >= 0); - assert_se(n == x); -} - -static void test_check_numbers_down (sd_journal *j, int count) { - int i; - - for (i = 1; i <= count; i++) { - int r; - test_check_number(j, i); - assert_ret(r = sd_journal_next(j)); - if (i == count) - assert_se(r == 0); - else - assert_se(r == 1); - } - -} - -static void test_check_numbers_up (sd_journal *j, int count) { - for (int i = count; i >= 1; i--) { - int r; - test_check_number(j, i); - assert_ret(r = sd_journal_previous(j)); - if (i == 1) - assert_se(r == 0); - else - assert_se(r == 1); - } - -} - -static void setup_sequential(void) { - JournalFile *one, *two; - one = test_open("one.journal"); - two = test_open("two.journal"); - append_number(one, 1, NULL); - append_number(one, 2, NULL); - append_number(two, 3, NULL); - append_number(two, 4, NULL); - test_close(one); - test_close(two); -} - -static void setup_interleaved(void) { - JournalFile *one, *two; - one = test_open("one.journal"); - two = test_open("two.journal"); - append_number(one, 1, NULL); - append_number(two, 2, NULL); - append_number(one, 3, NULL); - append_number(two, 4, NULL); - test_close(one); - test_close(two); -} - -static void test_skip(void (*setup)(void)) { - char t[] = "/tmp/journal-skip-XXXXXX"; - sd_journal *j; - int r; - - assert_se(mkdtemp(t)); - assert_se(chdir(t) >= 0); - - setup(); - - /* Seek to head, iterate down. - */ - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_ret(sd_journal_seek_head(j)); - assert_ret(sd_journal_next(j)); - test_check_numbers_down(j, 4); - sd_journal_close(j); - - /* Seek to tail, iterate up. - */ - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_ret(sd_journal_seek_tail(j)); - assert_ret(sd_journal_previous(j)); - test_check_numbers_up(j, 4); - sd_journal_close(j); - - /* Seek to tail, skip to head, iterate down. - */ - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_ret(sd_journal_seek_tail(j)); - assert_ret(r = sd_journal_previous_skip(j, 4)); - assert_se(r == 4); - test_check_numbers_down(j, 4); - sd_journal_close(j); - - /* Seek to head, skip to tail, iterate up. - */ - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_ret(sd_journal_seek_head(j)); - assert_ret(r = sd_journal_next_skip(j, 4)); - assert_se(r == 4); - test_check_numbers_up(j, 4); - sd_journal_close(j); - - log_info("Done..."); - - if (arg_keep) - log_info("Not removing %s", t); - else { - journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); - - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); - } - - puts("------------------------------------------------------------"); -} - -static void test_sequence_numbers(void) { - - char t[] = "/tmp/journal-seq-XXXXXX"; - JournalFile *one, *two; - uint64_t seqnum = 0; - sd_id128_t seqnum_id; - - assert_se(mkdtemp(t)); - assert_se(chdir(t) >= 0); - - assert_se(journal_file_open(-1, "one.journal", O_RDWR|O_CREAT, 0644, - true, false, NULL, NULL, NULL, NULL, &one) == 0); - - append_number(one, 1, &seqnum); - printf("seqnum=%"PRIu64"\n", seqnum); - assert_se(seqnum == 1); - append_number(one, 2, &seqnum); - printf("seqnum=%"PRIu64"\n", seqnum); - assert_se(seqnum == 2); - - assert_se(one->header->state == STATE_ONLINE); - assert_se(!sd_id128_equal(one->header->file_id, one->header->machine_id)); - assert_se(!sd_id128_equal(one->header->file_id, one->header->boot_id)); - assert_se(sd_id128_equal(one->header->file_id, one->header->seqnum_id)); - - memcpy(&seqnum_id, &one->header->seqnum_id, sizeof(sd_id128_t)); - - assert_se(journal_file_open(-1, "two.journal", O_RDWR|O_CREAT, 0644, - true, false, NULL, NULL, NULL, one, &two) == 0); - - assert_se(two->header->state == STATE_ONLINE); - assert_se(!sd_id128_equal(two->header->file_id, one->header->file_id)); - assert_se(sd_id128_equal(one->header->machine_id, one->header->machine_id)); - assert_se(sd_id128_equal(one->header->boot_id, one->header->boot_id)); - assert_se(sd_id128_equal(one->header->seqnum_id, one->header->seqnum_id)); - - append_number(two, 3, &seqnum); - printf("seqnum=%"PRIu64"\n", seqnum); - assert_se(seqnum == 3); - append_number(two, 4, &seqnum); - printf("seqnum=%"PRIu64"\n", seqnum); - assert_se(seqnum == 4); - - test_close(two); - - append_number(one, 5, &seqnum); - printf("seqnum=%"PRIu64"\n", seqnum); - assert_se(seqnum == 5); - - append_number(one, 6, &seqnum); - printf("seqnum=%"PRIu64"\n", seqnum); - assert_se(seqnum == 6); - - test_close(one); - - /* restart server */ - seqnum = 0; - - assert_se(journal_file_open(-1, "two.journal", O_RDWR, 0, - true, false, NULL, NULL, NULL, NULL, &two) == 0); - - assert_se(sd_id128_equal(two->header->seqnum_id, seqnum_id)); - - append_number(two, 7, &seqnum); - printf("seqnum=%"PRIu64"\n", seqnum); - assert_se(seqnum == 5); - - /* So..., here we have the same seqnum in two files with the - * same seqnum_id. */ - - test_close(two); - - log_info("Done..."); - - if (arg_keep) - log_info("Not removing %s", t); - else { - journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); - - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); - } -} - -int main(int argc, char *argv[]) { - log_set_max_level(LOG_DEBUG); - - /* journal_file_open requires a valid machine id */ - if (access("/etc/machine-id", F_OK) != 0) - return EXIT_TEST_SKIP; - - arg_keep = argc > 1; - - test_skip(setup_sequential); - test_skip(setup_interleaved); - - test_sequence_numbers(); - - return 0; -} diff --git a/src/journal/test-journal-match.c b/src/journal/test-journal-match.c deleted file mode 100644 index 3ab554b9b0..0000000000 --- a/src/journal/test-journal-match.c +++ /dev/null @@ -1,76 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 "sd-journal.h" - -#include "alloc-util.h" -#include "journal-internal.h" -#include "log.h" -#include "string-util.h" -#include "util.h" - -int main(int argc, char *argv[]) { - _cleanup_(sd_journal_closep) sd_journal*j = NULL; - _cleanup_free_ char *t; - - log_set_max_level(LOG_DEBUG); - - assert_se(sd_journal_open(&j, 0) >= 0); - - assert_se(sd_journal_add_match(j, "foobar", 0) < 0); - assert_se(sd_journal_add_match(j, "foobar=waldo", 0) < 0); - assert_se(sd_journal_add_match(j, "", 0) < 0); - assert_se(sd_journal_add_match(j, "=", 0) < 0); - assert_se(sd_journal_add_match(j, "=xxxxx", 0) < 0); - assert_se(sd_journal_add_match(j, "HALLO=WALDO", 0) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=mmmm", 0) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=xxxxx", 0) >= 0); - assert_se(sd_journal_add_match(j, "HALLO=", 0) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=xxxxx", 0) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=yyyyy", 0) >= 0); - assert_se(sd_journal_add_match(j, "PIFF=paff", 0) >= 0); - - assert_se(sd_journal_add_disjunction(j) >= 0); - - assert_se(sd_journal_add_match(j, "ONE=one", 0) >= 0); - assert_se(sd_journal_add_match(j, "ONE=two", 0) >= 0); - assert_se(sd_journal_add_match(j, "TWO=two", 0) >= 0); - - assert_se(sd_journal_add_conjunction(j) >= 0); - - assert_se(sd_journal_add_match(j, "L4_1=yes", 0) >= 0); - assert_se(sd_journal_add_match(j, "L4_1=ok", 0) >= 0); - assert_se(sd_journal_add_match(j, "L4_2=yes", 0) >= 0); - assert_se(sd_journal_add_match(j, "L4_2=ok", 0) >= 0); - - assert_se(sd_journal_add_disjunction(j) >= 0); - - assert_se(sd_journal_add_match(j, "L3=yes", 0) >= 0); - assert_se(sd_journal_add_match(j, "L3=ok", 0) >= 0); - - assert_se(t = journal_make_match_string(j)); - - printf("resulting match expression is: %s\n", t); - - assert_se(streq(t, "(((L3=ok OR L3=yes) OR ((L4_2=ok OR L4_2=yes) AND (L4_1=ok OR L4_1=yes))) AND ((TWO=two AND (ONE=two OR ONE=one)) OR (PIFF=paff AND (QUUX=yyyyy OR QUUX=xxxxx OR QUUX=mmmm) AND (HALLO= OR HALLO=WALDO))))")); - - return 0; -} diff --git a/src/journal/test-journal-send.c b/src/journal/test-journal-send.c deleted file mode 100644 index d70f0b0bc8..0000000000 --- a/src/journal/test-journal-send.c +++ /dev/null @@ -1,102 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include - -#include "sd-journal.h" - -#include "macro.h" - -int main(int argc, char *argv[]) { - char huge[4096*1024]; - - /* utf-8 and non-utf-8, message-less and message-ful iovecs */ - struct iovec graph1[] = { - {(char*) "GRAPH=graph", strlen("GRAPH=graph")} - }; - struct iovec graph2[] = { - {(char*) "GRAPH=graph\n", strlen("GRAPH=graph\n")} - }; - struct iovec message1[] = { - {(char*) "MESSAGE=graph", strlen("MESSAGE=graph")} - }; - struct iovec message2[] = { - {(char*) "MESSAGE=graph\n", strlen("MESSAGE=graph\n")} - }; - - assert_se(sd_journal_print(LOG_INFO, "piepapo") == 0); - - assert_se(sd_journal_send("MESSAGE=foobar", - "VALUE=%i", 7, - NULL) == 0); - - errno = ENOENT; - assert_se(sd_journal_perror("Foobar") == 0); - - assert_se(sd_journal_perror("") == 0); - - memset(huge, 'x', sizeof(huge)); - memcpy(huge, "HUGE=", 5); - char_array_0(huge); - - assert_se(sd_journal_send("MESSAGE=Huge field attached", - huge, - NULL) == 0); - - assert_se(sd_journal_send("MESSAGE=uiui", - "VALUE=A", - "VALUE=B", - "VALUE=C", - "SINGLETON=1", - "OTHERVALUE=X", - "OTHERVALUE=Y", - "WITH_BINARY=this is a binary value \a", - NULL) == 0); - - syslog(LOG_NOTICE, "Hello World!"); - - assert_se(sd_journal_print(LOG_NOTICE, "Hello World") == 0); - - assert_se(sd_journal_send("MESSAGE=Hello World!", - "MESSAGE_ID=52fb62f99e2c49d89cfbf9d6de5e3555", - "PRIORITY=5", - "HOME=%s", getenv("HOME"), - "TERM=%s", getenv("TERM"), - "PAGE_SIZE=%li", sysconf(_SC_PAGESIZE), - "N_CPUS=%li", sysconf(_SC_NPROCESSORS_ONLN), - NULL) == 0); - - assert_se(sd_journal_sendv(graph1, 1) == 0); - assert_se(sd_journal_sendv(graph2, 1) == 0); - assert_se(sd_journal_sendv(message1, 1) == 0); - assert_se(sd_journal_sendv(message2, 1) == 0); - - /* test without location fields */ -#undef sd_journal_sendv - assert_se(sd_journal_sendv(graph1, 1) == 0); - assert_se(sd_journal_sendv(graph2, 1) == 0); - assert_se(sd_journal_sendv(message1, 1) == 0); - assert_se(sd_journal_sendv(message2, 1) == 0); - - sleep(1); - - return 0; -} diff --git a/src/journal/test-journal-stream.c b/src/journal/test-journal-stream.c deleted file mode 100644 index 7e5a980719..0000000000 --- a/src/journal/test-journal-stream.c +++ /dev/null @@ -1,196 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 - -#include "sd-journal.h" - -#include "alloc-util.h" -#include "journal-file.h" -#include "journal-internal.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "rm-rf.h" -#include "util.h" - -#define N_ENTRIES 200 - -static void verify_contents(sd_journal *j, unsigned skip) { - unsigned i; - - assert_se(j); - - i = 0; - SD_JOURNAL_FOREACH(j) { - const void *d; - char *k, *c; - size_t l; - unsigned u = 0; - - assert_se(sd_journal_get_cursor(j, &k) >= 0); - printf("cursor: %s\n", k); - free(k); - - assert_se(sd_journal_get_data(j, "MAGIC", &d, &l) >= 0); - printf("\t%.*s\n", (int) l, (const char*) d); - - assert_se(sd_journal_get_data(j, "NUMBER", &d, &l) >= 0); - assert_se(k = strndup(d, l)); - printf("\t%s\n", k); - - if (skip > 0) { - assert_se(safe_atou(k + 7, &u) >= 0); - assert_se(i == u); - i += skip; - } - - free(k); - - assert_se(sd_journal_get_cursor(j, &c) >= 0); - assert_se(sd_journal_test_cursor(j, c) > 0); - free(c); - } - - if (skip > 0) - assert_se(i == N_ENTRIES); -} - -int main(int argc, char *argv[]) { - JournalFile *one, *two, *three; - char t[] = "/tmp/journal-stream-XXXXXX"; - unsigned i; - _cleanup_(sd_journal_closep) sd_journal *j = NULL; - char *z; - const void *data; - size_t l; - dual_timestamp previous_ts = DUAL_TIMESTAMP_NULL; - - /* journal_file_open requires a valid machine id */ - if (access("/etc/machine-id", F_OK) != 0) - return EXIT_TEST_SKIP; - - log_set_max_level(LOG_DEBUG); - - assert_se(mkdtemp(t)); - assert_se(chdir(t) >= 0); - - assert_se(journal_file_open(-1, "one.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &one) == 0); - assert_se(journal_file_open(-1, "two.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &two) == 0); - assert_se(journal_file_open(-1, "three.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &three) == 0); - - for (i = 0; i < N_ENTRIES; i++) { - char *p, *q; - dual_timestamp ts; - struct iovec iovec[2]; - - dual_timestamp_get(&ts); - - if (ts.monotonic <= previous_ts.monotonic) - ts.monotonic = previous_ts.monotonic + 1; - - if (ts.realtime <= previous_ts.realtime) - ts.realtime = previous_ts.realtime + 1; - - previous_ts = ts; - - assert_se(asprintf(&p, "NUMBER=%u", i) >= 0); - iovec[0].iov_base = p; - iovec[0].iov_len = strlen(p); - - assert_se(asprintf(&q, "MAGIC=%s", i % 5 == 0 ? "quux" : "waldo") >= 0); - - iovec[1].iov_base = q; - iovec[1].iov_len = strlen(q); - - if (i % 10 == 0) - assert_se(journal_file_append_entry(three, &ts, iovec, 2, NULL, NULL, NULL) == 0); - else { - if (i % 3 == 0) - assert_se(journal_file_append_entry(two, &ts, iovec, 2, NULL, NULL, NULL) == 0); - - assert_se(journal_file_append_entry(one, &ts, iovec, 2, NULL, NULL, NULL) == 0); - } - - free(p); - free(q); - } - - (void) journal_file_close(one); - (void) journal_file_close(two); - (void) journal_file_close(three); - - assert_se(sd_journal_open_directory(&j, t, 0) >= 0); - - assert_se(sd_journal_add_match(j, "MAGIC=quux", 0) >= 0); - SD_JOURNAL_FOREACH_BACKWARDS(j) { - _cleanup_free_ char *c; - - assert_se(sd_journal_get_data(j, "NUMBER", &data, &l) >= 0); - printf("\t%.*s\n", (int) l, (const char*) data); - - assert_se(sd_journal_get_cursor(j, &c) >= 0); - assert_se(sd_journal_test_cursor(j, c) > 0); - } - - SD_JOURNAL_FOREACH(j) { - _cleanup_free_ char *c; - - assert_se(sd_journal_get_data(j, "NUMBER", &data, &l) >= 0); - printf("\t%.*s\n", (int) l, (const char*) data); - - assert_se(sd_journal_get_cursor(j, &c) >= 0); - assert_se(sd_journal_test_cursor(j, c) > 0); - } - - sd_journal_flush_matches(j); - - verify_contents(j, 1); - - printf("NEXT TEST\n"); - assert_se(sd_journal_add_match(j, "MAGIC=quux", 0) >= 0); - - assert_se(z = journal_make_match_string(j)); - printf("resulting match expression is: %s\n", z); - free(z); - - verify_contents(j, 5); - - printf("NEXT TEST\n"); - sd_journal_flush_matches(j); - assert_se(sd_journal_add_match(j, "MAGIC=waldo", 0) >= 0); - assert_se(sd_journal_add_match(j, "NUMBER=10", 0) >= 0); - assert_se(sd_journal_add_match(j, "NUMBER=11", 0) >= 0); - assert_se(sd_journal_add_match(j, "NUMBER=12", 0) >= 0); - - assert_se(z = journal_make_match_string(j)); - printf("resulting match expression is: %s\n", z); - free(z); - - verify_contents(j, 0); - - assert_se(sd_journal_query_unique(j, "NUMBER") >= 0); - SD_JOURNAL_FOREACH_UNIQUE(j, data, l) - printf("%.*s\n", (int) l, (const char*) data); - - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); - - return 0; -} diff --git a/src/journal/test-journal-syslog.c b/src/journal/test-journal-syslog.c deleted file mode 100644 index 4ff7f3ec2e..0000000000 --- a/src/journal/test-journal-syslog.c +++ /dev/null @@ -1,44 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 "alloc-util.h" -#include "journald-syslog.h" -#include "macro.h" -#include "string-util.h" - -static void test_syslog_parse_identifier(const char* str, - const char *ident, const char*pid, int ret) { - const char *buf = str; - _cleanup_free_ char *ident2 = NULL, *pid2 = NULL; - int ret2; - - ret2 = syslog_parse_identifier(&buf, &ident2, &pid2); - - assert_se(ret == ret2); - assert_se(ident == ident2 || streq_ptr(ident, ident2)); - assert_se(pid == pid2 || streq_ptr(pid, pid2)); -} - -int main(void) { - test_syslog_parse_identifier("pidu[111]: xxx", "pidu", "111", 11); - test_syslog_parse_identifier("pidu: xxx", "pidu", NULL, 6); - test_syslog_parse_identifier("pidu xxx", NULL, NULL, 0); - - return 0; -} diff --git a/src/journal/test-journal-verify.c b/src/journal/test-journal-verify.c deleted file mode 100644 index 3d2312fc55..0000000000 --- a/src/journal/test-journal-verify.c +++ /dev/null @@ -1,150 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 -#include - -#include "fd-util.h" -#include "journal-file.h" -#include "journal-verify.h" -#include "log.h" -#include "rm-rf.h" -#include "terminal-util.h" -#include "util.h" - -#define N_ENTRIES 6000 -#define RANDOM_RANGE 77 - -static void bit_toggle(const char *fn, uint64_t p) { - uint8_t b; - ssize_t r; - int fd; - - fd = open(fn, O_RDWR|O_CLOEXEC); - assert_se(fd >= 0); - - r = pread(fd, &b, 1, p/8); - assert_se(r == 1); - - b ^= 1 << (p % 8); - - r = pwrite(fd, &b, 1, p/8); - assert_se(r == 1); - - safe_close(fd); -} - -static int raw_verify(const char *fn, const char *verification_key) { - JournalFile *f; - int r; - - r = journal_file_open(-1, fn, O_RDONLY, 0666, true, !!verification_key, NULL, NULL, NULL, NULL, &f); - if (r < 0) - return r; - - r = journal_file_verify(f, verification_key, NULL, NULL, NULL, false); - (void) journal_file_close(f); - - return r; -} - -int main(int argc, char *argv[]) { - char t[] = "/tmp/journal-XXXXXX"; - unsigned n; - JournalFile *f; - const char *verification_key = argv[1]; - usec_t from = 0, to = 0, total = 0; - char a[FORMAT_TIMESTAMP_MAX]; - char b[FORMAT_TIMESTAMP_MAX]; - char c[FORMAT_TIMESPAN_MAX]; - struct stat st; - uint64_t p; - - /* journal_file_open requires a valid machine id */ - if (access("/etc/machine-id", F_OK) != 0) - return EXIT_TEST_SKIP; - - log_set_max_level(LOG_DEBUG); - - assert_se(mkdtemp(t)); - assert_se(chdir(t) >= 0); - - log_info("Generating..."); - - assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, 0666, true, !!verification_key, NULL, NULL, NULL, NULL, &f) == 0); - - for (n = 0; n < N_ENTRIES; n++) { - struct iovec iovec; - struct dual_timestamp ts; - char *test; - - dual_timestamp_get(&ts); - - assert_se(asprintf(&test, "RANDOM=%lu", random() % RANDOM_RANGE)); - - iovec.iov_base = (void*) test; - iovec.iov_len = strlen(test); - - assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0); - - free(test); - } - - (void) journal_file_close(f); - - log_info("Verifying..."); - - assert_se(journal_file_open(-1, "test.journal", O_RDONLY, 0666, true, !!verification_key, NULL, NULL, NULL, NULL, &f) == 0); - /* journal_file_print_header(f); */ - journal_file_dump(f); - - assert_se(journal_file_verify(f, verification_key, &from, &to, &total, true) >= 0); - - if (verification_key && JOURNAL_HEADER_SEALED(f->header)) - log_info("=> Validated from %s to %s, %s missing", - format_timestamp(a, sizeof(a), from), - format_timestamp(b, sizeof(b), to), - format_timespan(c, sizeof(c), total > to ? total - to : 0, 0)); - - (void) journal_file_close(f); - - if (verification_key) { - log_info("Toggling bits..."); - - assert_se(stat("test.journal", &st) >= 0); - - for (p = 38448*8+0; p < ((uint64_t) st.st_size * 8); p ++) { - bit_toggle("test.journal", p); - - log_info("[ %"PRIu64"+%"PRIu64"]", p / 8, p % 8); - - if (raw_verify("test.journal", verification_key) >= 0) - log_notice(ANSI_HIGHLIGHT_RED ">>>> %"PRIu64" (bit %"PRIu64") can be toggled without detection." ANSI_NORMAL, p / 8, p % 8); - - bit_toggle("test.journal", p); - } - } - - log_info("Exiting..."); - - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); - - return 0; -} diff --git a/src/journal/test-journal.c b/src/journal/test-journal.c deleted file mode 100644 index 2543d64b5b..0000000000 --- a/src/journal/test-journal.c +++ /dev/null @@ -1,178 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 - -#include "journal-authenticate.h" -#include "journal-file.h" -#include "journal-vacuum.h" -#include "log.h" -#include "rm-rf.h" - -static bool arg_keep = false; - -static void test_non_empty(void) { - dual_timestamp ts; - JournalFile *f; - struct iovec iovec; - static const char test[] = "TEST1=1", test2[] = "TEST2=2"; - Object *o; - uint64_t p; - char t[] = "/tmp/journal-XXXXXX"; - - log_set_max_level(LOG_DEBUG); - - assert_se(mkdtemp(t)); - assert_se(chdir(t) >= 0); - - assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, 0666, true, true, NULL, NULL, NULL, NULL, &f) == 0); - - dual_timestamp_get(&ts); - - iovec.iov_base = (void*) test; - iovec.iov_len = strlen(test); - assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0); - - iovec.iov_base = (void*) test2; - iovec.iov_len = strlen(test2); - assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0); - - iovec.iov_base = (void*) test; - iovec.iov_len = strlen(test); - assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0); - -#ifdef HAVE_GCRYPT - journal_file_append_tag(f); -#endif - journal_file_dump(f); - - assert_se(journal_file_next_entry(f, 0, DIRECTION_DOWN, &o, &p) == 1); - assert_se(le64toh(o->entry.seqnum) == 1); - - assert_se(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 1); - assert_se(le64toh(o->entry.seqnum) == 2); - - assert_se(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 1); - assert_se(le64toh(o->entry.seqnum) == 3); - - assert_se(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 0); - - assert_se(journal_file_next_entry(f, 0, DIRECTION_DOWN, &o, &p) == 1); - assert_se(le64toh(o->entry.seqnum) == 1); - - assert_se(journal_file_find_data_object(f, test, strlen(test), NULL, &p) == 1); - assert_se(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 1); - - assert_se(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_UP, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 3); - - assert_se(journal_file_find_data_object(f, test2, strlen(test2), NULL, &p) == 1); - assert_se(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_UP, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 2); - - assert_se(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 2); - - assert_se(journal_file_find_data_object(f, "quux", 4, NULL, &p) == 0); - - assert_se(journal_file_move_to_entry_by_seqnum(f, 1, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 1); - - assert_se(journal_file_move_to_entry_by_seqnum(f, 3, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 3); - - assert_se(journal_file_move_to_entry_by_seqnum(f, 2, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 2); - - assert_se(journal_file_move_to_entry_by_seqnum(f, 10, DIRECTION_DOWN, &o, NULL) == 0); - - journal_file_rotate(&f, true, true, NULL); - journal_file_rotate(&f, true, true, NULL); - - (void) journal_file_close(f); - - log_info("Done..."); - - if (arg_keep) - log_info("Not removing %s", t); - else { - journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); - - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); - } - - puts("------------------------------------------------------------"); -} - -static void test_empty(void) { - JournalFile *f1, *f2, *f3, *f4; - char t[] = "/tmp/journal-XXXXXX"; - - log_set_max_level(LOG_DEBUG); - - assert_se(mkdtemp(t)); - assert_se(chdir(t) >= 0); - - assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, 0666, false, false, NULL, NULL, NULL, NULL, &f1) == 0); - - assert_se(journal_file_open(-1, "test-compress.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &f2) == 0); - - assert_se(journal_file_open(-1, "test-seal.journal", O_RDWR|O_CREAT, 0666, false, true, NULL, NULL, NULL, NULL, &f3) == 0); - - assert_se(journal_file_open(-1, "test-seal-compress.journal", O_RDWR|O_CREAT, 0666, true, true, NULL, NULL, NULL, NULL, &f4) == 0); - - journal_file_print_header(f1); - puts(""); - journal_file_print_header(f2); - puts(""); - journal_file_print_header(f3); - puts(""); - journal_file_print_header(f4); - puts(""); - - log_info("Done..."); - - if (arg_keep) - log_info("Not removing %s", t); - else { - journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); - - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); - } - - (void) journal_file_close(f1); - (void) journal_file_close(f2); - (void) journal_file_close(f3); - (void) journal_file_close(f4); -} - -int main(int argc, char *argv[]) { - arg_keep = argc > 1; - - /* journal_file_open requires a valid machine id */ - if (access("/etc/machine-id", F_OK) != 0) - return EXIT_TEST_SKIP; - - test_non_empty(); - test_empty(); - - return 0; -} diff --git a/src/journal/test-mmap-cache.c b/src/journal/test-mmap-cache.c deleted file mode 100644 index 009aabf55e..0000000000 --- a/src/journal/test-mmap-cache.c +++ /dev/null @@ -1,79 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 -#include -#include - -#include "fd-util.h" -#include "fileio.h" -#include "macro.h" -#include "mmap-cache.h" -#include "util.h" - -int main(int argc, char *argv[]) { - int x, y, z, r; - char px[] = "/tmp/testmmapXXXXXXX", py[] = "/tmp/testmmapYXXXXXX", pz[] = "/tmp/testmmapZXXXXXX"; - MMapCache *m; - void *p, *q; - - assert_se(m = mmap_cache_new()); - - x = mkostemp_safe(px, O_RDWR|O_CLOEXEC); - assert_se(x >= 0); - unlink(px); - - y = mkostemp_safe(py, O_RDWR|O_CLOEXEC); - assert_se(y >= 0); - unlink(py); - - z = mkostemp_safe(pz, O_RDWR|O_CLOEXEC); - assert_se(z >= 0); - unlink(pz); - - r = mmap_cache_get(m, x, PROT_READ, 0, false, 1, 2, NULL, &p); - assert_se(r >= 0); - - r = mmap_cache_get(m, x, PROT_READ, 0, false, 2, 2, NULL, &q); - assert_se(r >= 0); - - assert_se((uint8_t*) p + 1 == (uint8_t*) q); - - r = mmap_cache_get(m, x, PROT_READ, 1, false, 3, 2, NULL, &q); - assert_se(r >= 0); - - assert_se((uint8_t*) p + 2 == (uint8_t*) q); - - r = mmap_cache_get(m, x, PROT_READ, 0, false, 16ULL*1024ULL*1024ULL, 2, NULL, &p); - assert_se(r >= 0); - - r = mmap_cache_get(m, x, PROT_READ, 1, false, 16ULL*1024ULL*1024ULL+1, 2, NULL, &q); - assert_se(r >= 0); - - assert_se((uint8_t*) p + 1 == (uint8_t*) q); - - mmap_cache_unref(m); - - safe_close(x); - safe_close(y); - safe_close(z); - - return 0; -} diff --git a/src/kernel-install/Makefile b/src/kernel-install/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/kernel-install/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/kernel-install/Makefile b/src/kernel-install/Makefile new file mode 100644 index 0000000000..d50b684d01 --- /dev/null +++ b/src/kernel-install/Makefile @@ -0,0 +1,33 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +dist_bin_SCRIPTS = \ + src/kernel-install/kernel-install + +dist_kernelinstall_SCRIPTS = \ + src/kernel-install/50-depmod.install \ + src/kernel-install/90-loaderentry.install + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/kernel-install/bash-completion_kernel-install b/src/kernel-install/bash-completion_kernel-install new file mode 100644 index 0000000000..7cd2494cf7 --- /dev/null +++ b/src/kernel-install/bash-completion_kernel-install @@ -0,0 +1,50 @@ +# kernel-install(8) completion -*- shell-script -*- +# +# This file is part of systemd. +# +# Copyright 2013 Kay Sievers +# Copyright 2013 Harald Hoyer +# +# 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 +# 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 . + +_kernel_install() { + local comps + local MACHINE_ID + local cur=${COMP_WORDS[COMP_CWORD]} + + case $COMP_CWORD in + 1) + comps="add remove" + ;; + 2) + comps=$(cd /lib/modules; echo [0-9]*) + if [[ ${COMP_WORDS[1]} == "remove" ]] && [[ -f /etc/machine-id ]]; then + read MACHINE_ID < /etc/machine-id + if [[ $MACHINE_ID ]] && ( [[ -d /boot/$MACHINE_ID ]] || [[ -L /boot/$MACHINE_ID ]] ); then + comps=$(cd "/boot/$MACHINE_ID"; echo [0-9]*) + fi + fi + ;; + 3) + [[ "$cur" ]] || cur=/boot/vmlinuz-${COMP_WORDS[2]} + comps=$(compgen -f -- "$cur") + compopt -o filenames + ;; + esac + + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 +} + +complete -F _kernel_install kernel-install diff --git a/src/kernel-install/kernel-install.xml b/src/kernel-install/kernel-install.xml new file mode 100644 index 0000000000..eb519188a6 --- /dev/null +++ b/src/kernel-install/kernel-install.xml @@ -0,0 +1,192 @@ + + + + + + + + + kernel-install + systemd + + + + Developer + Harald + Hoyer + harald@redhat.com + + + + + + kernel-install + 8 + + + + kernel-install + Add and remove kernel and initramfs images to and from /boot + + + + + kernel-install + COMMAND + KERNEL-VERSION + KERNEL-IMAGE + + + + + Description + + kernel-install is used to install and remove kernel and + initramfs images to and from /boot. + + + kernel-install will execute the files + located in the directory /usr/lib/kernel/install.d/ + and the local administration directory /etc/kernel/install.d/. + All files are collectively sorted and executed in lexical order, regardless of the directory in + which they live. However, files with identical filenames replace each other. + Files in /etc/kernel/install.d/ take precedence over files with the same name + in /usr/lib/kernel/install.d/. This can be used to override a system-supplied + executables with a local file if needed; a symbolic link in /etc/kernel/install.d/ + with the same name as an executable in /usr/lib/kernel/install.d/, + pointing to /dev/null, disables the executable entirely. Executables must have the + extension .install; other extensions are ignored. + + + + + Commands + The following commands are understood: + + + add KERNEL-VERSION KERNEL-IMAGE + + kernel-install creates the directory + /boot/MACHINE-ID/KERNEL-VERSION/ + and calls every executable + /usr/lib/kernel/install.d/*.install and + /etc/kernel/install.d/*.install with + the arguments + add KERNEL-VERSION /boot/MACHINE-ID/KERNEL-VERSION/ + + + The kernel-install plugin 50-depmod.install runs depmod for the KERNEL-VERSION. + + The kernel-install plugin + 90-loaderentry.install copies + KERNEL-IMAGE to + /boot/MACHINE-ID/KERNEL-VERSION/linux. + It also creates a boot loader entry according to the boot + loader specification in + /boot/loader/entries/MACHINE-ID-KERNEL-VERSION.conf. + The title of the entry is the + PRETTY_NAME parameter specified + in /etc/os-release or + /usr/lib/os-release (if the former is + missing), or "GNU/Linux + KERNEL-VERSION", if unset. If + the file initrd is found next to the + linux file, the initrd will be added to + the configuration. + + + + remove KERNEL-VERSION + + Calls every executable /usr/lib/kernel/install.d/*.install + and /etc/kernel/install.d/*.install with the arguments + remove KERNEL-VERSION /boot/MACHINE-ID/KERNEL-VERSION/ + + + kernel-install removes the entire directory + /boot/MACHINE-ID/KERNEL-VERSION/ afterwards. + + The kernel-install plugin 90-loaderentry.install removes the file + /boot/loader/entries/MACHINE-ID-KERNEL-VERSION.conf. + + + + + + + + + Exit status + If every executable returns with 0, 0 is returned, a non-zero failure code otherwise. + + + + Files + + + + /usr/lib/kernel/install.d/*.install + /etc/kernel/install.d/*.install + + + Drop-in files which are executed by kernel-install. + + + + + /etc/kernel/cmdline + /proc/cmdline + + + The content of the file /etc/kernel/cmdline specifies the kernel command line to use. + If that file does not exist, /proc/cmdline is used. + + + + + /etc/machine-id + + + The content of the file specifies the machine identification MACHINE-ID. + + + + + /etc/os-release + /usr/lib/os-release + + + The content of the file specifies the operating system title PRETTY_NAME. + + + + + + + See Also + + machine-id5, + os-release5, + Boot loader specification + + + + diff --git a/src/kernel-install/zsh-completion_kernel-install b/src/kernel-install/zsh-completion_kernel-install new file mode 100644 index 0000000000..4fdd3a4ae7 --- /dev/null +++ b/src/kernel-install/zsh-completion_kernel-install @@ -0,0 +1,26 @@ +#compdef kernel-install + +_images(){ + if [[ "$words[2]" == "remove" ]]; then + _message 'No more options' + else + _path_files -W /boot/ -P /boot/ -g "vmlinuz-*" + fi +} + +_kernels(){ + read _MACHINE_ID < /etc/machine-id + _kernel=( /lib/modules/[0-9]* ) + if [[ "$cmd" == "remove" && -n "$_MACHINE_ID" ]]; then + _kernel=( "/boot/$_MACHINE_ID"/[0-9]* ) + fi + _kernel=( ${_kernel##*/} ) + _describe "installed kernels" _kernel +} + +_arguments \ + '1::add or remove:(add remove)' \ + '2::kernel versions:_kernels' \ + '3::kernel images:_images' + +#vim: set ft=zsh sw=4 ts=4 et diff --git a/src/libbasic/.gitignore b/src/libbasic/.gitignore new file mode 100644 index 0000000000..e22411e484 --- /dev/null +++ b/src/libbasic/.gitignore @@ -0,0 +1,16 @@ +/cap-from-name.gperf +/cap-from-name.h +/cap-list.txt +/cap-to-name.h +/errno-from-name.gperf +/errno-from-name.h +/errno-list.txt +/errno-to-name.h +/af-from-name.gperf +/af-from-name.h +/af-list.txt +/af-to-name.h +/arphrd-from-name.gperf +/arphrd-from-name.h +/arphrd-list.txt +/arphrd-to-name.h diff --git a/src/libbasic/Makefile b/src/libbasic/Makefile new file mode 100644 index 0000000000..9f8d4ec995 --- /dev/null +++ b/src/libbasic/Makefile @@ -0,0 +1,279 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +noinst_LTLIBRARIES += \ + libbasic.la + +libbasic_la_SOURCES = \ + src/basic/missing.h \ + src/basic/missing_syscall.h \ + src/basic/capability-util.c \ + src/basic/capability-util.h \ + src/basic/conf-files.c \ + src/basic/conf-files.h \ + src/basic/stdio-util.h \ + src/basic/hostname-util.h \ + src/basic/hostname-util.c \ + src/basic/unit-name.c \ + src/basic/unit-name.h \ + src/basic/ioprio.h \ + src/basic/securebits.h \ + src/basic/special.h \ + src/basic/list.h \ + src/basic/unaligned.h \ + src/basic/macro.h \ + src/basic/def.h \ + src/basic/sparse-endian.h \ + src/basic/refcnt.h \ + src/basic/util.c \ + src/basic/util.h \ + src/basic/io-util.c \ + src/basic/io-util.h \ + src/basic/string-util.c \ + src/basic/string-util.h \ + src/basic/fd-util.c \ + src/basic/fd-util.h \ + src/basic/parse-util.c \ + src/basic/parse-util.h \ + src/basic/user-util.c \ + src/basic/user-util.h \ + src/basic/rlimit-util.c \ + src/basic/rlimit-util.h \ + src/basic/dirent-util.c \ + src/basic/dirent-util.h \ + src/basic/xattr-util.c \ + src/basic/xattr-util.h \ + src/basic/chattr-util.c \ + src/basic/chattr-util.h \ + src/basic/proc-cmdline.c \ + src/basic/proc-cmdline.h \ + src/basic/fs-util.c \ + src/basic/fs-util.h \ + src/basic/syslog-util.c \ + src/basic/syslog-util.h \ + src/basic/stat-util.c \ + src/basic/stat-util.h \ + src/basic/mount-util.c \ + src/basic/mount-util.h \ + src/basic/hexdecoct.c \ + src/basic/hexdecoct.h \ + src/basic/glob-util.h \ + src/basic/glob-util.c \ + src/basic/extract-word.c \ + src/basic/extract-word.h \ + src/basic/escape.c \ + src/basic/escape.h \ + src/basic/cpu-set-util.c \ + src/basic/cpu-set-util.h \ + src/basic/lockfile-util.c \ + src/basic/lockfile-util.h \ + src/basic/path-util.c \ + src/basic/path-util.h \ + src/basic/time-util.c \ + src/basic/time-util.h \ + src/basic/locale-util.c \ + src/basic/locale-util.h \ + src/basic/umask-util.h \ + src/basic/signal-util.c \ + src/basic/signal-util.h \ + src/basic/string-table.c \ + src/basic/string-table.h \ + src/basic/mempool.c \ + src/basic/mempool.h \ + src/basic/hashmap.c \ + src/basic/hashmap.h \ + src/basic/hash-funcs.c \ + src/basic/hash-funcs.h \ + src/basic/siphash24.c \ + src/basic/siphash24.h \ + src/basic/set.h \ + src/basic/ordered-set.h \ + src/basic/ordered-set.c \ + src/basic/bitmap.c \ + src/basic/bitmap.h \ + src/basic/fdset.c \ + src/basic/fdset.h \ + src/basic/prioq.c \ + src/basic/prioq.h \ + src/basic/web-util.c \ + src/basic/web-util.h \ + src/basic/strv.c \ + src/basic/strv.h \ + src/basic/env-util.c \ + src/basic/env-util.h \ + src/basic/strbuf.c \ + src/basic/strbuf.h \ + src/basic/strxcpyx.c \ + src/basic/strxcpyx.h \ + src/basic/log.c \ + src/basic/log.h \ + src/basic/bus-label.c \ + src/basic/bus-label.h \ + src/basic/ratelimit.h \ + src/basic/ratelimit.c \ + src/basic/exit-status.c \ + src/basic/exit-status.h \ + src/basic/virt.c \ + src/basic/virt.h \ + src/basic/architecture.c \ + src/basic/architecture.h \ + src/basic/smack-util.c \ + src/basic/smack-util.h \ + src/basic/device-nodes.c \ + src/basic/device-nodes.h \ + src/basic/utf8.c \ + src/basic/utf8.h \ + src/basic/gunicode.c \ + src/basic/gunicode.h \ + src/basic/socket-util.c \ + src/basic/socket-util.h \ + src/basic/in-addr-util.c \ + src/basic/in-addr-util.h \ + src/basic/ether-addr-util.h \ + src/basic/ether-addr-util.c \ + src/basic/replace-var.c \ + src/basic/replace-var.h \ + src/basic/clock-util.c \ + src/basic/clock-util.h \ + src/basic/calendarspec.c \ + src/basic/calendarspec.h \ + src/basic/fileio.c \ + src/basic/fileio.h \ + src/basic/MurmurHash2.c \ + src/basic/MurmurHash2.h \ + src/basic/mkdir.c \ + src/basic/mkdir.h \ + src/basic/cgroup-util.c \ + src/basic/cgroup-util.h \ + src/basic/errno-list.c \ + src/basic/errno-list.h \ + src/basic/af-list.c \ + src/basic/af-list.h \ + src/basic/arphrd-list.c \ + src/basic/arphrd-list.h \ + src/basic/terminal-util.c \ + src/basic/terminal-util.h \ + src/basic/login-util.h \ + src/basic/login-util.c \ + src/basic/cap-list.c \ + src/basic/cap-list.h \ + src/basic/audit-util.c \ + src/basic/audit-util.h \ + src/basic/xml.c \ + src/basic/xml.h \ + src/basic/barrier.c \ + src/basic/barrier.h \ + src/basic/async.c \ + src/basic/async.h \ + src/basic/memfd-util.c \ + src/basic/memfd-util.h \ + src/basic/process-util.c \ + src/basic/process-util.h \ + src/basic/random-util.c \ + src/basic/random-util.h \ + src/basic/verbs.c \ + src/basic/verbs.h \ + src/basic/sigbus.c \ + src/basic/sigbus.h \ + src/basic/build.h \ + src/basic/socket-label.c \ + src/basic/label.c \ + src/basic/label.h \ + src/basic/btrfs-util.c \ + src/basic/btrfs-util.h \ + src/basic/btrfs-ctree.h \ + src/basic/selinux-util.c \ + src/basic/selinux-util.h \ + src/basic/mkdir-label.c \ + src/basic/fileio-label.c \ + src/basic/fileio-label.h \ + src/basic/rm-rf.c \ + src/basic/rm-rf.h \ + src/basic/copy.c \ + src/basic/copy.h \ + src/basic/alloc-util.h \ + src/basic/alloc-util.c \ + src/basic/formats-util.h \ + src/basic/nss-util.h + +nodist_libbasic_la_SOURCES = \ + src/basic/errno-from-name.h \ + src/basic/errno-to-name.h \ + src/basic/af-from-name.h \ + src/basic/af-to-name.h \ + src/basic/arphrd-from-name.h \ + src/basic/arphrd-to-name.h \ + src/basic/cap-from-name.h \ + src/basic/cap-to-name.h + +libbasic_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(SELINUX_CFLAGS) \ + $(CAP_CFLAGS) \ + -pthread + +libbasic_la_LIBADD = \ + $(SELINUX_LIBS) \ + $(CAP_LIBS) \ + -lrt \ + -lm + +$(outdir)/errno-list.txt: + $(AM_V_GEN)$(CPP) $(ALL_CPPFLAGS) -dM -include errno.h - $@ + +$(outdir)/errno-to-name.h: $(outdir)/errno-list.txt + $(AM_V_GEN)$(AWK) 'BEGIN{ print "static const char* const errno_names[] = { "} !/EDEADLOCK/ && !/EWOULDBLOCK/ && !/ENOTSUP/ { printf "[%s] = \"%s\",\n", $$1, $$1 } END{print "};"}' <$< >$@ + + +$(outdir)/af-list.txt: + $(AM_V_GEN)$(CPP) $(ALL_CPPFLAGS) -dM -include sys/socket.h - $@ + +$(outdir)/af-to-name.h: $(outdir)/af-list.txt + $(AM_V_GEN)$(AWK) 'BEGIN{ print "static const char* const af_names[] = { "} !/AF_FILE/ && !/AF_ROUTE/ && !/AF_LOCAL/ { printf "[%s] = \"%s\",\n", $$1, $$1 } END{print "};"}' <$< >$@ + + +$(outdir)/arphrd-list.txt: + $(AM_V_GEN)$(CPP) $(ALL_CPPFLAGS) -dM -include net/if_arp.h - $@ + +$(outdir)/arphrd-to-name.h: $(outdir)/arphrd-list.txt + $(AM_V_GEN)$(AWK) 'BEGIN{ print "static const char* const arphrd_names[] = { "} !/CISCO/ { printf "[ARPHRD_%s] = \"%s\",\n", $$1, $$1 } END{print "};"}' <$< >$@ + +$(outdir)/arphrd-from-name.gperf: $(outdir)/arphrd-list.txt + $(AM_V_GEN)$(AWK) 'BEGIN{ print "struct arphrd_name { const char* name; int id; };"; print "%null-strings"; print "%%";} { printf "%s, ARPHRD_%s\n", $$1, $$1 }' <$< >$@ + + +$(outdir)/cap-list.txt: + $(AM_V_GEN)$(CPP) $(ALL_CPPFLAGS) -dM -include linux/capability.h -include missing.h - $@ + +$(outdir)/cap-to-name.h: $(outdir)/cap-list.txt + $(AM_V_GEN)$(AWK) 'BEGIN{ print "static const char* const capability_names[] = { "} { printf "[%s] = \"%s\",\n", $$1, tolower($$1) } END{print "};"}' <$< >$@ + +$(outdir)/cap-from-name.gperf: $(outdir)/cap-list.txt + $(AM_V_GEN)$(AWK) 'BEGIN{ print "struct capability_name { const char* name; int id; };"; print "%null-strings"; print "%%";} { printf "%s, %s\n", $$1, $$1 }' <$< >$@ + +$(outdir)/cap-from-name.h: $(outdir)/cap-from-name.gperf + $(AM_V_GPERF)$(GPERF) -L ANSI-C -t --ignore-case -N lookup_capability -H hash_capability_name -p -C <$< >$@ + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libbasic/MurmurHash2.c b/src/libbasic/MurmurHash2.c new file mode 100644 index 0000000000..9020793930 --- /dev/null +++ b/src/libbasic/MurmurHash2.c @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------------- +// MurmurHash2 was written by Austin Appleby, and is placed in the public +// domain. The author hereby disclaims copyright to this source code. + +// Note - This code makes a few assumptions about how your machine behaves - + +// 1. We can read a 4-byte value from any address without crashing +// 2. sizeof(int) == 4 + +// And it has a few limitations - + +// 1. It will not work incrementally. +// 2. It will not produce the same results on little-endian and big-endian +// machines. + +#include "MurmurHash2.h" + +//----------------------------------------------------------------------------- +// Platform-specific functions and macros + +// Microsoft Visual Studio + +#if defined(_MSC_VER) + +#define BIG_CONSTANT(x) (x) + +// Other compilers + +#else // defined(_MSC_VER) + +#define BIG_CONSTANT(x) (x##LLU) + +#endif // !defined(_MSC_VER) + +//----------------------------------------------------------------------------- + +uint32_t MurmurHash2 ( const void * key, int len, uint32_t seed ) +{ + // 'm' and 'r' are mixing constants generated offline. + // They're not really 'magic', they just happen to work well. + + const uint32_t m = 0x5bd1e995; + const int r = 24; + + // Initialize the hash to a 'random' value + + uint32_t h = seed ^ len; + + // Mix 4 bytes at a time into the hash + + const unsigned char * data = (const unsigned char *)key; + + while (len >= 4) + { + uint32_t k = *(uint32_t*)data; + + k *= m; + k ^= k >> r; + k *= m; + + h *= m; + h ^= k; + + data += 4; + len -= 4; + } + + // Handle the last few bytes of the input array + + switch(len) + { + case 3: h ^= data[2] << 16; + case 2: h ^= data[1] << 8; + case 1: h ^= data[0]; + h *= m; + }; + + // Do a few final mixes of the hash to ensure the last few + // bytes are well-incorporated. + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; +} diff --git a/src/libbasic/MurmurHash2.h b/src/libbasic/MurmurHash2.h new file mode 100644 index 0000000000..93362dd485 --- /dev/null +++ b/src/libbasic/MurmurHash2.h @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------------- +// MurmurHash2 was written by Austin Appleby, and is placed in the public +// domain. The author hereby disclaims copyright to this source code. + +#ifndef _MURMURHASH2_H_ +#define _MURMURHASH2_H_ + +//----------------------------------------------------------------------------- +// Platform-specific functions and macros + +// Microsoft Visual Studio + +#if defined(_MSC_VER) + +typedef unsigned char uint8_t; +typedef unsigned long uint32_t; +typedef unsigned __int64 uint64_t; + +// Other compilers + +#else // defined(_MSC_VER) + +#include + +#endif // !defined(_MSC_VER) + +//----------------------------------------------------------------------------- + +uint32_t MurmurHash2 ( const void * key, int len, uint32_t seed ); + +//----------------------------------------------------------------------------- + +#endif // _MURMURHASH2_H_ diff --git a/src/libbasic/af-list.c b/src/libbasic/af-list.c new file mode 100644 index 0000000000..3fac9c508b --- /dev/null +++ b/src/libbasic/af-list.c @@ -0,0 +1,56 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include "af-list.h" +#include "macro.h" + +static const struct af_name* lookup_af(register const char *str, register unsigned int len); + +#include "af-from-name.h" +#include "af-to-name.h" + +const char *af_to_name(int id) { + + if (id <= 0) + return NULL; + + if (id >= (int) ELEMENTSOF(af_names)) + return NULL; + + return af_names[id]; +} + +int af_from_name(const char *name) { + const struct af_name *sc; + + assert(name); + + sc = lookup_af(name, strlen(name)); + if (!sc) + return AF_UNSPEC; + + return sc->id; +} + +int af_max(void) { + return ELEMENTSOF(af_names); +} diff --git a/src/libbasic/af-list.h b/src/libbasic/af-list.h new file mode 100644 index 0000000000..6a4cc03839 --- /dev/null +++ b/src/libbasic/af-list.h @@ -0,0 +1,41 @@ +#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 "string-util.h" + +const char *af_to_name(int id); +int af_from_name(const char *name); + +static inline const char* af_to_name_short(int id) { + const char *f; + + if (id == AF_UNSPEC) + return "*"; + + f = af_to_name(id); + if (!f) + return "unknown"; + + assert(startswith(f, "AF_")); + return f + 3; +} + +int af_max(void); diff --git a/src/libbasic/alloc-util.c b/src/libbasic/alloc-util.c new file mode 100644 index 0000000000..b540dcddf5 --- /dev/null +++ b/src/libbasic/alloc-util.c @@ -0,0 +1,83 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 + +#include "alloc-util.h" +#include "macro.h" +#include "util.h" + +void* memdup(const void *p, size_t l) { + void *r; + + assert(p); + + r = malloc(l); + if (!r) + return NULL; + + memcpy(r, p, l); + return r; +} + +void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size) { + size_t a, newalloc; + void *q; + + assert(p); + assert(allocated); + + if (*allocated >= need) + return *p; + + newalloc = MAX(need * 2, 64u / size); + a = newalloc * size; + + /* check for overflows */ + if (a < size * need) + return NULL; + + q = realloc(*p, a); + if (!q) + return NULL; + + *p = q; + *allocated = newalloc; + return q; +} + +void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size) { + size_t prev; + uint8_t *q; + + assert(p); + assert(allocated); + + prev = *allocated; + + q = greedy_realloc(p, allocated, need, size); + if (!q) + return NULL; + + if (*allocated > prev) + memzero(q + prev * size, (*allocated - prev) * size); + + return q; +} diff --git a/src/libbasic/alloc-util.h b/src/libbasic/alloc-util.h new file mode 100644 index 0000000000..ceeee519b7 --- /dev/null +++ b/src/libbasic/alloc-util.h @@ -0,0 +1,111 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include + +#include "macro.h" + +#define new(t, n) ((t*) malloc_multiply(sizeof(t), (n))) + +#define new0(t, n) ((t*) calloc((n), sizeof(t))) + +#define newa(t, n) ((t*) alloca(sizeof(t)*(n))) + +#define newa0(t, n) ((t*) alloca0(sizeof(t)*(n))) + +#define newdup(t, p, n) ((t*) memdup_multiply(p, sizeof(t), (n))) + +#define malloc0(n) (calloc(1, (n))) + +static inline void *mfree(void *memory) { + free(memory); + return NULL; +} + +void* memdup(const void *p, size_t l) _alloc_(2); + +static inline void freep(void *p) { + free(*(void**) p); +} + +#define _cleanup_free_ _cleanup_(freep) + +static inline bool size_multiply_overflow(size_t size, size_t need) { + return _unlikely_(need != 0 && size > (SIZE_MAX / need)); +} + +_malloc_ _alloc_(1, 2) static inline void *malloc_multiply(size_t size, size_t need) { + if (size_multiply_overflow(size, need)) + return NULL; + + return malloc(size * need); +} + +_alloc_(2, 3) static inline void *realloc_multiply(void *p, size_t size, size_t need) { + if (size_multiply_overflow(size, need)) + return NULL; + + return realloc(p, size * need); +} + +_alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t size, size_t need) { + if (size_multiply_overflow(size, need)) + return NULL; + + return memdup(p, size * need); +} + +void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size); +void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size); + +#define GREEDY_REALLOC(array, allocated, need) \ + greedy_realloc((void**) &(array), &(allocated), (need), sizeof((array)[0])) + +#define GREEDY_REALLOC0(array, allocated, need) \ + greedy_realloc0((void**) &(array), &(allocated), (need), sizeof((array)[0])) + +#define alloca0(n) \ + ({ \ + char *_new_; \ + size_t _len_ = n; \ + _new_ = alloca(_len_); \ + (void *) memset(_new_, 0, _len_); \ + }) + +/* It's not clear what alignment glibc/gcc alloca() guarantee, hence provide a guaranteed safe version */ +#define alloca_align(size, align) \ + ({ \ + void *_ptr_; \ + size_t _mask_ = (align) - 1; \ + _ptr_ = alloca((size) + _mask_); \ + (void*)(((uintptr_t)_ptr_ + _mask_) & ~_mask_); \ + }) + +#define alloca0_align(size, align) \ + ({ \ + void *_new_; \ + size_t _size_ = (size); \ + _new_ = alloca_align(_size_, (align)); \ + (void*)memset(_new_, 0, _size_); \ + }) diff --git a/src/libbasic/architecture.c b/src/libbasic/architecture.c new file mode 100644 index 0000000000..b1c8e91f50 --- /dev/null +++ b/src/libbasic/architecture.c @@ -0,0 +1,179 @@ +/*** + 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" +#include "macro.h" +#include "string-table.h" +#include "string-util.h" + +int 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 distinguish CPUs not CPU features, but + * actual architectures, i.e. that have genuinely different + * code. */ + + static const struct { + const char *machine; + int 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 }, + { "ppc64le", ARCHITECTURE_PPC64_LE }, + { "ppc", ARCHITECTURE_PPC }, + { "ppcle", ARCHITECTURE_PPC_LE }, +#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__) + { "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__) + { "sh5", ARCHITECTURE_SH64 }, + { "sh2", ARCHITECTURE_SH }, + { "sh2a", ARCHITECTURE_SH }, + { "sh3", ARCHITECTURE_SH }, + { "sh4", ARCHITECTURE_SH }, + { "sh4a", ARCHITECTURE_SH }, +#elif defined(__m68k__) + { "m68k", ARCHITECTURE_M68K }, +#elif defined(__tilegx__) + { "tilegx", ARCHITECTURE_TILEGX }, +#elif defined(__cris__) + { "crisv32", ARCHITECTURE_CRIS }, +#elif defined(__nios2__) + { "nios2", ARCHITECTURE_NIOS2 }, +#else +#error "Please register your architecture here!" +#endif + }; + + static int 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_PPC_LE] = "ppc-le", + [ARCHITECTURE_PPC64] = "ppc64", + [ARCHITECTURE_PPC64_LE] = "ppc64-le", + [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_MIPS_LE] = "mips-le", + [ARCHITECTURE_MIPS64] = "mips64", + [ARCHITECTURE_MIPS64_LE] = "mips64-le", + [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", + [ARCHITECTURE_TILEGX] = "tilegx", + [ARCHITECTURE_CRIS] = "cris", + [ARCHITECTURE_NIOS2] = "nios2", +}; + +DEFINE_STRING_TABLE_LOOKUP(architecture, int); diff --git a/src/libbasic/architecture.h b/src/libbasic/architecture.h new file mode 100644 index 0000000000..b3e4d85906 --- /dev/null +++ b/src/libbasic/architecture.h @@ -0,0 +1,199 @@ +#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 + +#include "macro.h" +#include "util.h" + +/* A cleaned up architecture definition. We don't want to get lost in + * processor features, models, generations or even ABIs. Hence we + * focus on general family, and distinguish word width and + * endianness. */ + +enum { + ARCHITECTURE_X86 = 0, + ARCHITECTURE_X86_64, + ARCHITECTURE_PPC, + ARCHITECTURE_PPC_LE, + ARCHITECTURE_PPC64, + ARCHITECTURE_PPC64_LE, + ARCHITECTURE_IA64, + ARCHITECTURE_PARISC, + ARCHITECTURE_PARISC64, + ARCHITECTURE_S390, + ARCHITECTURE_S390X, + ARCHITECTURE_SPARC, + ARCHITECTURE_SPARC64, + ARCHITECTURE_MIPS, + ARCHITECTURE_MIPS_LE, + ARCHITECTURE_MIPS64, + ARCHITECTURE_MIPS64_LE, + ARCHITECTURE_ALPHA, + ARCHITECTURE_ARM, + ARCHITECTURE_ARM_BE, + ARCHITECTURE_ARM64, + ARCHITECTURE_ARM64_BE, + ARCHITECTURE_SH, + ARCHITECTURE_SH64, + ARCHITECTURE_M68K, + ARCHITECTURE_TILEGX, + ARCHITECTURE_CRIS, + ARCHITECTURE_NIOS2, + _ARCHITECTURE_MAX, + _ARCHITECTURE_INVALID = -1 +}; + +int uname_architecture(void); + +/* + * LIB_ARCH_TUPLE should resolve to the local library path + * architecture tuple systemd is built for, according to the Debian + * tuple list: + * + * https://wiki.debian.org/Multiarch/Tuples + * + * This is used in library search paths that should understand + * Debian's paths on all distributions. + */ + +#if defined(__x86_64__) +# define native_architecture() ARCHITECTURE_X86_64 +# define LIB_ARCH_TUPLE "x86_64-linux-gnu" +# define SECONDARY_ARCHITECTURE ARCHITECTURE_X86 +#elif defined(__i386__) +# define native_architecture() ARCHITECTURE_X86 +# define LIB_ARCH_TUPLE "i386-linux-gnu" +#elif defined(__powerpc64__) +# if __BYTE_ORDER == __BIG_ENDIAN +# define native_architecture() ARCHITECTURE_PPC64 +# define LIB_ARCH_TUPLE "ppc64-linux-gnu" +# define SECONDARY_ARCHITECTURE ARCHITECTURE_PPC +# else +# define native_architecture() ARCHITECTURE_PPC64_LE +# define LIB_ARCH_TUPLE "powerpc64le-linux-gnu" +# define SECONDARY_ARCHITECTURE ARCHITECTURE_PPC_LE +# endif +#elif defined(__powerpc__) +# if __BYTE_ORDER == __BIG_ENDIAN +# define native_architecture() ARCHITECTURE_PPC +# define LIB_ARCH_TUPLE "powerpc-linux-gnu" +# else +# define native_architecture() ARCHITECTURE_PPC_LE +# error "Missing LIB_ARCH_TUPLE for PPCLE" +# endif +#elif defined(__ia64__) +# define native_architecture() ARCHITECTURE_IA64 +# define LIB_ARCH_TUPLE "ia64-linux-gnu" +#elif defined(__hppa64__) +# define native_architecture() ARCHITECTURE_PARISC64 +# error "Missing LIB_ARCH_TUPLE for HPPA64" +#elif defined(__hppa__) +# define native_architecture() ARCHITECTURE_PARISC +# define LIB_ARCH_TUPLE "hppa‑linux‑gnu" +#elif defined(__s390x__) +# define native_architecture() ARCHITECTURE_S390X +# define LIB_ARCH_TUPLE "s390x-linux-gnu" +# define SECONDARY_ARCHITECTURE ARCHITECTURE_S390 +#elif defined(__s390__) +# define native_architecture() ARCHITECTURE_S390 +# define LIB_ARCH_TUPLE "s390-linux-gnu" +#elif defined(__sparc__) && defined (__arch64__) +# define native_architecture() ARCHITECTURE_SPARC64 +# define LIB_ARCH_TUPLE "sparc64-linux-gnu" +#elif defined(__sparc__) +# define native_architecture() ARCHITECTURE_SPARC +# define LIB_ARCH_TUPLE "sparc-linux-gnu" +#elif defined(__mips64__) +# if __BYTE_ORDER == __BIG_ENDIAN +# define native_architecture() ARCHITECTURE_MIPS64 +# error "Missing LIB_ARCH_TUPLE for MIPS64" +# else +# define native_architecture() ARCHITECTURE_MIPS64_LE +# error "Missing LIB_ARCH_TUPLE for MIPS64_LE" +# endif +#elif defined(__mips__) +# if __BYTE_ORDER == __BIG_ENDIAN +# define native_architecture() ARCHITECTURE_MIPS +# define LIB_ARCH_TUPLE "mips-linux-gnu" +# else +# define native_architecture() ARCHITECTURE_MIPS_LE +# define LIB_ARCH_TUPLE "mipsel-linux-gnu" +# endif +#elif defined(__alpha__) +# define native_architecture() ARCHITECTURE_ALPHA +# define LIB_ARCH_TUPLE "alpha-linux-gnu" +#elif defined(__aarch64__) +# if __BYTE_ORDER == __BIG_ENDIAN +# define native_architecture() ARCHITECTURE_ARM64_BE +# define LIB_ARCH_TUPLE "aarch64_be-linux-gnu" +# else +# define native_architecture() ARCHITECTURE_ARM64 +# define LIB_ARCH_TUPLE "aarch64-linux-gnu" +# endif +#elif defined(__arm__) +# if __BYTE_ORDER == __BIG_ENDIAN +# define native_architecture() ARCHITECTURE_ARM_BE +# if defined(__ARM_EABI__) +# if defined(__ARM_PCS_VFP) +# define LIB_ARCH_TUPLE "armeb-linux-gnueabihf" +# else +# define LIB_ARCH_TUPLE "armeb-linux-gnueabi" +# endif +# else +# define LIB_ARCH_TUPLE "armeb-linux-gnu" +# endif +# else +# define native_architecture() ARCHITECTURE_ARM +# if defined(__ARM_EABI__) +# if defined(__ARM_PCS_VFP) +# define LIB_ARCH_TUPLE "arm-linux-gnueabihf" +# else +# define LIB_ARCH_TUPLE "arm-linux-gnueabi" +# endif +# else +# define LIB_ARCH_TUPLE "arm-linux-gnu" +# endif +# endif +#elif defined(__sh64__) +# define native_architecture() ARCHITECTURE_SH64 +# error "Missing LIB_ARCH_TUPLE for SH64" +#elif defined(__sh__) +# define native_architecture() ARCHITECTURE_SH +# define LIB_ARCH_TUPLE "sh4-linux-gnu" +#elif defined(__m68k__) +# define native_architecture() ARCHITECTURE_M68K +# define LIB_ARCH_TUPLE "m68k-linux-gnu" +#elif defined(__tilegx__) +# define native_architecture() ARCHITECTURE_TILEGX +# error "Missing LIB_ARCH_TUPLE for TILEGX" +#elif defined(__cris__) +# define native_architecture() ARCHITECTURE_CRIS +# error "Missing LIB_ARCH_TUPLE for CRIS" +#elif defined(__nios2__) +# define native_architecture() ARCHITECTURE_NIOS2 +# define LIB_ARCH_TUPLE "nios2-linux-gnu" +#else +# error "Please register your architecture here!" +#endif + +const char *architecture_to_string(int a) _const_; +int architecture_from_string(const char *s) _pure_; diff --git a/src/libbasic/arphrd-list.c b/src/libbasic/arphrd-list.c new file mode 100644 index 0000000000..6792d1ee3f --- /dev/null +++ b/src/libbasic/arphrd-list.c @@ -0,0 +1,56 @@ +/*** + 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 + +#include "arphrd-list.h" +#include "macro.h" + +static const struct arphrd_name* lookup_arphrd(register const char *str, register unsigned int len); + +#include "arphrd-from-name.h" +#include "arphrd-to-name.h" + +const char *arphrd_to_name(int id) { + + if (id <= 0) + return NULL; + + if (id >= (int) ELEMENTSOF(arphrd_names)) + return NULL; + + return arphrd_names[id]; +} + +int arphrd_from_name(const char *name) { + const struct arphrd_name *sc; + + assert(name); + + sc = lookup_arphrd(name, strlen(name)); + if (!sc) + return 0; + + return sc->id; +} + +int arphrd_max(void) { + return ELEMENTSOF(arphrd_names); +} diff --git a/src/libbasic/arphrd-list.h b/src/libbasic/arphrd-list.h new file mode 100644 index 0000000000..c0f8758dbe --- /dev/null +++ b/src/libbasic/arphrd-list.h @@ -0,0 +1,25 @@ +#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 . +***/ + +const char *arphrd_to_name(int id); +int arphrd_from_name(const char *name); + +int arphrd_max(void); diff --git a/src/libbasic/async.c b/src/libbasic/async.c new file mode 100644 index 0000000000..a1f163f27b --- /dev/null +++ b/src/libbasic/async.c @@ -0,0 +1,94 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 +#include +#include + +#include "async.h" +#include "fd-util.h" +#include "log.h" +#include "macro.h" +#include "util.h" + +int asynchronous_job(void* (*func)(void *p), void *arg) { + pthread_attr_t a; + pthread_t t; + int r; + + /* It kinda sucks that we have to resort to threads to + * implement an asynchronous sync(), but well, such is + * life. + * + * Note that issuing this command right before exiting a + * process will cause the process to wait for the sync() to + * complete. This function hence is nicely asynchronous really + * only in long running processes. */ + + r = pthread_attr_init(&a); + if (r > 0) + return -r; + + r = pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED); + if (r > 0) + goto finish; + + r = pthread_create(&t, &a, func, arg); + +finish: + pthread_attr_destroy(&a); + return -r; +} + +static void *sync_thread(void *p) { + sync(); + return NULL; +} + +int asynchronous_sync(void) { + log_debug("Spawning new thread for sync"); + + return asynchronous_job(sync_thread, NULL); +} + +static void *close_thread(void *p) { + assert_se(close_nointr(PTR_TO_FD(p)) != -EBADF); + return NULL; +} + +int asynchronous_close(int fd) { + int r; + + /* This is supposed to behave similar to safe_close(), but + * actually invoke close() asynchronously, so that it will + * never block. Ideally the kernel would have an API for this, + * but it doesn't, so we work around it, and hide this as a + * far away as we can. */ + + if (fd >= 0) { + PROTECT_ERRNO; + + r = asynchronous_job(close_thread, FD_TO_PTR(fd)); + if (r < 0) + assert_se(close_nointr(fd) != -EBADF); + } + + return -1; +} diff --git a/src/libbasic/async.h b/src/libbasic/async.h new file mode 100644 index 0000000000..9bd13ff6e0 --- /dev/null +++ b/src/libbasic/async.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +int asynchronous_job(void* (*func)(void *p), void *arg); + +int asynchronous_sync(void); +int asynchronous_close(int fd); diff --git a/src/libbasic/audit-util.c b/src/libbasic/audit-util.c new file mode 100644 index 0000000000..5741fecdd6 --- /dev/null +++ b/src/libbasic/audit-util.c @@ -0,0 +1,104 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include + +#include "alloc-util.h" +#include "audit-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "macro.h" +#include "parse-util.h" +#include "process-util.h" +#include "user-util.h" + +int audit_session_from_pid(pid_t pid, uint32_t *id) { + _cleanup_free_ char *s = NULL; + const char *p; + uint32_t u; + int r; + + assert(id); + + /* We don't convert ENOENT to ESRCH here, since we can't + * really distuingish between "audit is not available in the + * kernel" and "the process does not exist", both which will + * result in ENOENT. */ + + p = procfs_file_alloca(pid, "sessionid"); + + r = read_one_line_file(p, &s); + if (r < 0) + return r; + + r = safe_atou32(s, &u); + if (r < 0) + return r; + + if (u == AUDIT_SESSION_INVALID || u <= 0) + return -ENODATA; + + *id = u; + return 0; +} + +int audit_loginuid_from_pid(pid_t pid, uid_t *uid) { + _cleanup_free_ char *s = NULL; + const char *p; + uid_t u; + int r; + + assert(uid); + + p = procfs_file_alloca(pid, "loginuid"); + + r = read_one_line_file(p, &s); + if (r < 0) + return r; + + r = parse_uid(s, &u); + if (r == -ENXIO) /* the UID was -1 */ + return -ENODATA; + if (r < 0) + return r; + + *uid = (uid_t) u; + return 0; +} + +bool use_audit(void) { + static int cached_use = -1; + + if (cached_use < 0) { + int fd; + + fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_AUDIT); + if (fd < 0) + cached_use = errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT; + else { + cached_use = true; + safe_close(fd); + } + } + + return cached_use; +} diff --git a/src/libbasic/audit-util.h b/src/libbasic/audit-util.h new file mode 100644 index 0000000000..e048503991 --- /dev/null +++ b/src/libbasic/audit-util.h @@ -0,0 +1,31 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include + +#define AUDIT_SESSION_INVALID ((uint32_t) -1) + +int audit_session_from_pid(pid_t pid, uint32_t *id); +int audit_loginuid_from_pid(pid_t pid, uid_t *uid); + +bool use_audit(void); diff --git a/src/libbasic/barrier.c b/src/libbasic/barrier.c new file mode 100644 index 0000000000..2da633b311 --- /dev/null +++ b/src/libbasic/barrier.c @@ -0,0 +1,415 @@ +/*** + This file is part of systemd. + + Copyright 2014 David Herrmann + + 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 +#include +#include +#include +#include +#include +#include +#include + +#include "barrier.h" +#include "fd-util.h" +#include "macro.h" + +/** + * Barriers + * This barrier implementation provides a simple synchronization method based + * on file-descriptors that can safely be used between threads and processes. A + * barrier object contains 2 shared counters based on eventfd. Both processes + * can now place barriers and wait for the other end to reach a random or + * specific barrier. + * Barriers are numbered, so you can either wait for the other end to reach any + * barrier or the last barrier that you placed. This way, you can use barriers + * for one-way *and* full synchronization. Note that even-though barriers are + * numbered, these numbers are internal and recycled once both sides reached the + * same barrier (implemented as a simple signed counter). It is thus not + * possible to address barriers by their ID. + * + * Barrier-API: Both ends can place as many barriers via barrier_place() as + * they want and each pair of barriers on both sides will be implicitly linked. + * Each side can use the barrier_wait/sync_*() family of calls to wait for the + * other side to place a specific barrier. barrier_wait_next() waits until the + * other side calls barrier_place(). No links between the barriers are + * considered and this simply serves as most basic asynchronous barrier. + * barrier_sync_next() is like barrier_wait_next() and waits for the other side + * to place their next barrier via barrier_place(). However, it only waits for + * barriers that are linked to a barrier we already placed. If the other side + * already placed more barriers than we did, barrier_sync_next() returns + * immediately. + * barrier_sync() extends barrier_sync_next() and waits until the other end + * placed as many barriers via barrier_place() as we did. If they already placed + * as many as we did (or more), it returns immediately. + * + * Additionally to basic barriers, an abortion event is available. + * barrier_abort() places an abortion event that cannot be undone. An abortion + * immediately cancels all placed barriers and replaces them. Any running and + * following wait/sync call besides barrier_wait_abortion() will immediately + * return false on both sides (otherwise, they always return true). + * barrier_abort() can be called multiple times on both ends and will be a + * no-op if already called on this side. + * barrier_wait_abortion() can be used to wait for the other side to call + * barrier_abort() and is the only wait/sync call that does not return + * immediately if we aborted outself. It only returns once the other side + * called barrier_abort(). + * + * Barriers can be used for in-process and inter-process synchronization. + * However, for in-process synchronization you could just use mutexes. + * Therefore, main target is IPC and we require both sides to *not* share the FD + * table. If that's given, barriers provide target tracking: If the remote side + * exit()s, an abortion event is implicitly queued on the other side. This way, + * a sync/wait call will be woken up if the remote side crashed or exited + * unexpectedly. However, note that these abortion events are only queued if the + * barrier-queue has been drained. Therefore, it is safe to place a barrier and + * exit. The other side can safely wait on the barrier even though the exit + * queued an abortion event. Usually, the abortion event would overwrite the + * barrier, however, that's not true for exit-abortion events. Those are only + * queued if the barrier-queue is drained (thus, the receiving side has placed + * more barriers than the remote side). + */ + +/** + * barrier_create() - Initialize a barrier object + * @obj: barrier to initialize + * + * This initializes a barrier object. The caller is responsible of allocating + * the memory and keeping it valid. The memory does not have to be zeroed + * beforehand. + * Two eventfd objects are allocated for each barrier. If allocation fails, an + * error is returned. + * + * If this function fails, the barrier is reset to an invalid state so it is + * safe to call barrier_destroy() on the object regardless whether the + * initialization succeeded or not. + * + * The caller is responsible to destroy the object via barrier_destroy() before + * releasing the underlying memory. + * + * Returns: 0 on success, negative error code on failure. + */ +int barrier_create(Barrier *b) { + _cleanup_(barrier_destroyp) Barrier *staging = b; + int r; + + assert(b); + + b->me = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (b->me < 0) + return -errno; + + b->them = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (b->them < 0) + return -errno; + + r = pipe2(b->pipe, O_CLOEXEC | O_NONBLOCK); + if (r < 0) + return -errno; + + staging = NULL; + return 0; +} + +/** + * barrier_destroy() - Destroy a barrier object + * @b: barrier to destroy or NULL + * + * This destroys a barrier object that has previously been passed to + * barrier_create(). The object is released and reset to invalid + * state. Therefore, it is safe to call barrier_destroy() multiple + * times or even if barrier_create() failed. However, barrier must be + * always initialized with BARRIER_NULL. + * + * If @b is NULL, this is a no-op. + */ +void barrier_destroy(Barrier *b) { + if (!b) + return; + + b->me = safe_close(b->me); + b->them = safe_close(b->them); + safe_close_pair(b->pipe); + b->barriers = 0; +} + +/** + * barrier_set_role() - Set the local role of the barrier + * @b: barrier to operate on + * @role: role to set on the barrier + * + * This sets the roles on a barrier object. This is needed to know + * which side of the barrier you're on. Usually, the parent creates + * the barrier via barrier_create() and then calls fork() or clone(). + * Therefore, the FDs are duplicated and the child retains the same + * barrier object. + * + * Both sides need to call barrier_set_role() after fork() or clone() + * are done. If this is not done, barriers will not work correctly. + * + * Note that barriers could be supported without fork() or clone(). However, + * this is currently not needed so it hasn't been implemented. + */ +void barrier_set_role(Barrier *b, unsigned int role) { + int fd; + + assert(b); + assert(role == BARRIER_PARENT || role == BARRIER_CHILD); + /* make sure this is only called once */ + assert(b->pipe[0] >= 0 && b->pipe[1] >= 0); + + if (role == BARRIER_PARENT) + b->pipe[1] = safe_close(b->pipe[1]); + else { + b->pipe[0] = safe_close(b->pipe[0]); + + /* swap me/them for children */ + fd = b->me; + b->me = b->them; + b->them = fd; + } +} + +/* places barrier; returns false if we aborted, otherwise true */ +static bool barrier_write(Barrier *b, uint64_t buf) { + ssize_t len; + + /* prevent new sync-points if we already aborted */ + if (barrier_i_aborted(b)) + return false; + + assert(b->me >= 0); + do { + len = write(b->me, &buf, sizeof(buf)); + } while (len < 0 && IN_SET(errno, EAGAIN, EINTR)); + + if (len != sizeof(buf)) + goto error; + + /* lock if we aborted */ + if (buf >= (uint64_t)BARRIER_ABORTION) { + if (barrier_they_aborted(b)) + b->barriers = BARRIER_WE_ABORTED; + else + b->barriers = BARRIER_I_ABORTED; + } else if (!barrier_is_aborted(b)) + b->barriers += buf; + + return !barrier_i_aborted(b); + +error: + /* If there is an unexpected error, we have to make this fatal. There + * is no way we can recover from sync-errors. Therefore, we close the + * pipe-ends and treat this as abortion. The other end will notice the + * pipe-close and treat it as abortion, too. */ + + safe_close_pair(b->pipe); + b->barriers = BARRIER_WE_ABORTED; + return false; +} + +/* waits for barriers; returns false if they aborted, otherwise true */ +static bool barrier_read(Barrier *b, int64_t comp) { + if (barrier_they_aborted(b)) + return false; + + while (b->barriers > comp) { + struct pollfd pfd[2] = { + { .fd = b->pipe[0] >= 0 ? b->pipe[0] : b->pipe[1], + .events = POLLHUP }, + { .fd = b->them, + .events = POLLIN }}; + uint64_t buf; + int r; + + r = poll(pfd, 2, -1); + if (r < 0 && IN_SET(errno, EAGAIN, EINTR)) + continue; + else if (r < 0) + goto error; + + if (pfd[1].revents) { + ssize_t len; + + /* events on @them signal new data for us */ + len = read(b->them, &buf, sizeof(buf)); + if (len < 0 && IN_SET(errno, EAGAIN, EINTR)) + continue; + + if (len != sizeof(buf)) + goto error; + } else if (pfd[0].revents & (POLLHUP | POLLERR | POLLNVAL)) + /* POLLHUP on the pipe tells us the other side exited. + * We treat this as implicit abortion. But we only + * handle it if there's no event on the eventfd. This + * guarantees that exit-abortions do not overwrite real + * barriers. */ + buf = BARRIER_ABORTION; + else + continue; + + /* lock if they aborted */ + if (buf >= (uint64_t)BARRIER_ABORTION) { + if (barrier_i_aborted(b)) + b->barriers = BARRIER_WE_ABORTED; + else + b->barriers = BARRIER_THEY_ABORTED; + } else if (!barrier_is_aborted(b)) + b->barriers -= buf; + } + + return !barrier_they_aborted(b); + +error: + /* If there is an unexpected error, we have to make this fatal. There + * is no way we can recover from sync-errors. Therefore, we close the + * pipe-ends and treat this as abortion. The other end will notice the + * pipe-close and treat it as abortion, too. */ + + safe_close_pair(b->pipe); + b->barriers = BARRIER_WE_ABORTED; + return false; +} + +/** + * barrier_place() - Place a new barrier + * @b: barrier object + * + * This places a new barrier on the barrier object. If either side already + * aborted, this is a no-op and returns "false". Otherwise, the barrier is + * placed and this returns "true". + * + * Returns: true if barrier was placed, false if either side aborted. + */ +bool barrier_place(Barrier *b) { + assert(b); + + if (barrier_is_aborted(b)) + return false; + + barrier_write(b, BARRIER_SINGLE); + return true; +} + +/** + * barrier_abort() - Abort the synchronization + * @b: barrier object to abort + * + * This aborts the barrier-synchronization. If barrier_abort() was already + * called on this side, this is a no-op. Otherwise, the barrier is put into the + * ABORT-state and will stay there. The other side is notified about the + * abortion. Any following attempt to place normal barriers or to wait on normal + * barriers will return immediately as "false". + * + * You can wait for the other side to call barrier_abort(), too. Use + * barrier_wait_abortion() for that. + * + * Returns: false if the other side already aborted, true otherwise. + */ +bool barrier_abort(Barrier *b) { + assert(b); + + barrier_write(b, BARRIER_ABORTION); + return !barrier_they_aborted(b); +} + +/** + * barrier_wait_next() - Wait for the next barrier of the other side + * @b: barrier to operate on + * + * This waits until the other side places its next barrier. This is independent + * of any barrier-links and just waits for any next barrier of the other side. + * + * If either side aborted, this returns false. + * + * Returns: false if either side aborted, true otherwise. + */ +bool barrier_wait_next(Barrier *b) { + assert(b); + + if (barrier_is_aborted(b)) + return false; + + barrier_read(b, b->barriers - 1); + return !barrier_is_aborted(b); +} + +/** + * barrier_wait_abortion() - Wait for the other side to abort + * @b: barrier to operate on + * + * This waits until the other side called barrier_abort(). This can be called + * regardless whether the local side already called barrier_abort() or not. + * + * If the other side has already aborted, this returns immediately. + * + * Returns: false if the local side aborted, true otherwise. + */ +bool barrier_wait_abortion(Barrier *b) { + assert(b); + + barrier_read(b, BARRIER_THEY_ABORTED); + return !barrier_i_aborted(b); +} + +/** + * barrier_sync_next() - Wait for the other side to place a next linked barrier + * @b: barrier to operate on + * + * This is like barrier_wait_next() and waits for the other side to call + * barrier_place(). However, this only waits for linked barriers. That means, if + * the other side already placed more barriers than (or as much as) we did, this + * returns immediately instead of waiting. + * + * If either side aborted, this returns false. + * + * Returns: false if either side aborted, true otherwise. + */ +bool barrier_sync_next(Barrier *b) { + assert(b); + + if (barrier_is_aborted(b)) + return false; + + barrier_read(b, MAX((int64_t)0, b->barriers - 1)); + return !barrier_is_aborted(b); +} + +/** + * barrier_sync() - Wait for the other side to place as many barriers as we did + * @b: barrier to operate on + * + * This is like barrier_sync_next() but waits for the other side to call + * barrier_place() as often as we did (in total). If they already placed as much + * as we did (or more), this returns immediately instead of waiting. + * + * If either side aborted, this returns false. + * + * Returns: false if either side aborted, true otherwise. + */ +bool barrier_sync(Barrier *b) { + assert(b); + + if (barrier_is_aborted(b)) + return false; + + barrier_read(b, 0); + return !barrier_is_aborted(b); +} diff --git a/src/libbasic/barrier.h b/src/libbasic/barrier.h new file mode 100644 index 0000000000..6347fddc4d --- /dev/null +++ b/src/libbasic/barrier.h @@ -0,0 +1,91 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 David Herrmann + + 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 +#include + +#include "macro.h" + +/* See source file for an API description. */ + +typedef struct Barrier Barrier; + +enum { + BARRIER_SINGLE = 1LL, + BARRIER_ABORTION = INT64_MAX, + + /* bias values to store state; keep @WE < @THEY < @I */ + BARRIER_BIAS = INT64_MIN, + BARRIER_WE_ABORTED = BARRIER_BIAS + 1LL, + BARRIER_THEY_ABORTED = BARRIER_BIAS + 2LL, + BARRIER_I_ABORTED = BARRIER_BIAS + 3LL, +}; + +enum { + BARRIER_PARENT, + BARRIER_CHILD, +}; + +struct Barrier { + int me; + int them; + int pipe[2]; + int64_t barriers; +}; + +#define BARRIER_NULL {-1, -1, {-1, -1}, 0} + +int barrier_create(Barrier *obj); +void barrier_destroy(Barrier *b); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Barrier*, barrier_destroy); + +void barrier_set_role(Barrier *b, unsigned int role); + +bool barrier_place(Barrier *b); +bool barrier_abort(Barrier *b); + +bool barrier_wait_next(Barrier *b); +bool barrier_wait_abortion(Barrier *b); +bool barrier_sync_next(Barrier *b); +bool barrier_sync(Barrier *b); + +static inline bool barrier_i_aborted(Barrier *b) { + return b->barriers == BARRIER_I_ABORTED || b->barriers == BARRIER_WE_ABORTED; +} + +static inline bool barrier_they_aborted(Barrier *b) { + return b->barriers == BARRIER_THEY_ABORTED || b->barriers == BARRIER_WE_ABORTED; +} + +static inline bool barrier_we_aborted(Barrier *b) { + return b->barriers == BARRIER_WE_ABORTED; +} + +static inline bool barrier_is_aborted(Barrier *b) { + return b->barriers == BARRIER_I_ABORTED || b->barriers == BARRIER_THEY_ABORTED || b->barriers == BARRIER_WE_ABORTED; +} + +static inline bool barrier_place_and_sync(Barrier *b) { + (void) barrier_place(b); + return barrier_sync(b); +} diff --git a/src/libbasic/bitmap.c b/src/libbasic/bitmap.c new file mode 100644 index 0000000000..ad1fda0198 --- /dev/null +++ b/src/libbasic/bitmap.c @@ -0,0 +1,220 @@ +/*** + This file is part of systemd. + + Copyright 2015 Tom Gundersen + + 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 +#include +#include +#include + +#include "alloc-util.h" +#include "bitmap.h" +#include "hashmap.h" +#include "macro.h" + +struct Bitmap { + uint64_t *bitmaps; + size_t n_bitmaps; + size_t bitmaps_allocated; +}; + +/* Bitmaps are only meant to store relatively small numbers + * (corresponding to, say, an enum), so it is ok to limit + * the max entry. 64k should be plenty. */ +#define BITMAPS_MAX_ENTRY 0xffff + +/* This indicates that we reached the end of the bitmap */ +#define BITMAP_END ((unsigned) -1) + +#define BITMAP_NUM_TO_OFFSET(n) ((n) / (sizeof(uint64_t) * 8)) +#define BITMAP_NUM_TO_REM(n) ((n) % (sizeof(uint64_t) * 8)) +#define BITMAP_OFFSET_TO_NUM(offset, rem) ((offset) * sizeof(uint64_t) * 8 + (rem)) + +Bitmap *bitmap_new(void) { + return new0(Bitmap, 1); +} + +void bitmap_free(Bitmap *b) { + if (!b) + return; + + free(b->bitmaps); + free(b); +} + +int bitmap_ensure_allocated(Bitmap **b) { + Bitmap *a; + + assert(b); + + if (*b) + return 0; + + a = bitmap_new(); + if (!a) + return -ENOMEM; + + *b = a; + + return 0; +} + +int bitmap_set(Bitmap *b, unsigned n) { + uint64_t bitmask; + unsigned offset; + + assert(b); + + /* we refuse to allocate huge bitmaps */ + if (n > BITMAPS_MAX_ENTRY) + return -ERANGE; + + offset = BITMAP_NUM_TO_OFFSET(n); + + if (offset >= b->n_bitmaps) { + if (!GREEDY_REALLOC0(b->bitmaps, b->bitmaps_allocated, offset + 1)) + return -ENOMEM; + + b->n_bitmaps = offset + 1; + } + + bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n); + + b->bitmaps[offset] |= bitmask; + + return 0; +} + +void bitmap_unset(Bitmap *b, unsigned n) { + uint64_t bitmask; + unsigned offset; + + if (!b) + return; + + offset = BITMAP_NUM_TO_OFFSET(n); + + if (offset >= b->n_bitmaps) + return; + + bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n); + + b->bitmaps[offset] &= ~bitmask; +} + +bool bitmap_isset(Bitmap *b, unsigned n) { + uint64_t bitmask; + unsigned offset; + + if (!b) + return false; + + offset = BITMAP_NUM_TO_OFFSET(n); + + if (offset >= b->n_bitmaps) + return false; + + bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n); + + return !!(b->bitmaps[offset] & bitmask); +} + +bool bitmap_isclear(Bitmap *b) { + unsigned i; + + if (!b) + return true; + + for (i = 0; i < b->n_bitmaps; i++) + if (b->bitmaps[i] != 0) + return false; + + return true; +} + +void bitmap_clear(Bitmap *b) { + + if (!b) + return; + + b->bitmaps = mfree(b->bitmaps); + b->n_bitmaps = 0; + b->bitmaps_allocated = 0; +} + +bool bitmap_iterate(Bitmap *b, Iterator *i, unsigned *n) { + uint64_t bitmask; + unsigned offset, rem; + + assert(i); + assert(n); + + if (!b || i->idx == BITMAP_END) + return false; + + offset = BITMAP_NUM_TO_OFFSET(i->idx); + rem = BITMAP_NUM_TO_REM(i->idx); + bitmask = UINT64_C(1) << rem; + + for (; offset < b->n_bitmaps; offset ++) { + if (b->bitmaps[offset]) { + for (; bitmask; bitmask <<= 1, rem ++) { + if (b->bitmaps[offset] & bitmask) { + *n = BITMAP_OFFSET_TO_NUM(offset, rem); + i->idx = *n + 1; + + return true; + } + } + } + + rem = 0; + bitmask = 1; + } + + i->idx = BITMAP_END; + + return false; +} + +bool bitmap_equal(Bitmap *a, Bitmap *b) { + size_t common_n_bitmaps; + Bitmap *c; + unsigned i; + + if (a == b) + return true; + + if (!a != !b) + return false; + + if (!a) + return true; + + common_n_bitmaps = MIN(a->n_bitmaps, b->n_bitmaps); + if (memcmp(a->bitmaps, b->bitmaps, sizeof(uint64_t) * common_n_bitmaps) != 0) + return false; + + c = a->n_bitmaps > b->n_bitmaps ? a : b; + for (i = common_n_bitmaps; i < c->n_bitmaps; i++) + if (c->bitmaps[i] != 0) + return false; + + return true; +} diff --git a/src/libbasic/bitmap.h b/src/libbasic/bitmap.h new file mode 100644 index 0000000000..f5f8f2f018 --- /dev/null +++ b/src/libbasic/bitmap.h @@ -0,0 +1,50 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Tom Gundersen + + 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 "hashmap.h" +#include "macro.h" + +typedef struct Bitmap Bitmap; + +Bitmap *bitmap_new(void); + +void bitmap_free(Bitmap *b); + +int bitmap_ensure_allocated(Bitmap **b); + +int bitmap_set(Bitmap *b, unsigned n); +void bitmap_unset(Bitmap *b, unsigned n); +bool bitmap_isset(Bitmap *b, unsigned n); +bool bitmap_isclear(Bitmap *b); +void bitmap_clear(Bitmap *b); + +bool bitmap_iterate(Bitmap *b, Iterator *i, unsigned *n); + +bool bitmap_equal(Bitmap *a, Bitmap *b); + +#define BITMAP_FOREACH(n, b, i) \ + for ((i).idx = 0; bitmap_iterate((b), &(i), (unsigned*)&(n)); ) + +DEFINE_TRIVIAL_CLEANUP_FUNC(Bitmap*, bitmap_free); + +#define _cleanup_bitmap_free_ _cleanup_(bitmap_freep) diff --git a/src/libbasic/blkid-util.h b/src/libbasic/blkid-util.h new file mode 100644 index 0000000000..7aa75eb091 --- /dev/null +++ b/src/libbasic/blkid-util.h @@ -0,0 +1,31 @@ +#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 . +***/ + +#ifdef HAVE_BLKID +#include +#endif + +#include "util.h" + +#ifdef HAVE_BLKID +DEFINE_TRIVIAL_CLEANUP_FUNC(blkid_probe, blkid_free_probe); +#define _cleanup_blkid_free_probe_ _cleanup_(blkid_free_probep) +#endif diff --git a/src/libbasic/btrfs-ctree.h b/src/libbasic/btrfs-ctree.h new file mode 100644 index 0000000000..66bdf9736e --- /dev/null +++ b/src/libbasic/btrfs-ctree.h @@ -0,0 +1,96 @@ +#pragma once + +#include "macro.h" +#include "sparse-endian.h" + +/* Stolen from btrfs' ctree.h */ + +struct btrfs_timespec { + le64_t sec; + le32_t nsec; +} _packed_; + +struct btrfs_disk_key { + le64_t objectid; + uint8_t type; + le64_t offset; +} _packed_; + +struct btrfs_inode_item { + le64_t generation; + le64_t transid; + le64_t size; + le64_t nbytes; + le64_t block_group; + le32_t nlink; + le32_t uid; + le32_t gid; + le32_t mode; + le64_t rdev; + le64_t flags; + le64_t sequence; + le64_t reserved[4]; + struct btrfs_timespec atime; + struct btrfs_timespec ctime; + struct btrfs_timespec mtime; + struct btrfs_timespec otime; +} _packed_; + +struct btrfs_root_item { + struct btrfs_inode_item inode; + le64_t generation; + le64_t root_dirid; + le64_t bytenr; + le64_t byte_limit; + le64_t bytes_used; + le64_t last_snapshot; + le64_t flags; + le32_t refs; + struct btrfs_disk_key drop_progress; + uint8_t drop_level; + uint8_t level; + le64_t generation_v2; + uint8_t uuid[BTRFS_UUID_SIZE]; + uint8_t parent_uuid[BTRFS_UUID_SIZE]; + uint8_t received_uuid[BTRFS_UUID_SIZE]; + le64_t ctransid; + le64_t otransid; + le64_t stransid; + le64_t rtransid; + struct btrfs_timespec ctime; + struct btrfs_timespec otime; + struct btrfs_timespec stime; + struct btrfs_timespec rtime; + le64_t reserved[8]; +} _packed_; + +#define BTRFS_ROOT_SUBVOL_RDONLY (1ULL << 0) + +struct btrfs_qgroup_info_item { + le64_t generation; + le64_t rfer; + le64_t rfer_cmpr; + le64_t excl; + le64_t excl_cmpr; +} _packed_; + +#define BTRFS_QGROUP_LIMIT_MAX_RFER (1ULL << 0) +#define BTRFS_QGROUP_LIMIT_MAX_EXCL (1ULL << 1) +#define BTRFS_QGROUP_LIMIT_RSV_RFER (1ULL << 2) +#define BTRFS_QGROUP_LIMIT_RSV_EXCL (1ULL << 3) +#define BTRFS_QGROUP_LIMIT_RFER_CMPR (1ULL << 4) +#define BTRFS_QGROUP_LIMIT_EXCL_CMPR (1ULL << 5) + +struct btrfs_qgroup_limit_item { + le64_t flags; + le64_t max_rfer; + le64_t max_excl; + le64_t rsv_rfer; + le64_t rsv_excl; +} _packed_; + +struct btrfs_root_ref { + le64_t dirid; + le64_t sequence; + le16_t name_len; +} _packed_; diff --git a/src/libbasic/btrfs-util.c b/src/libbasic/btrfs-util.c new file mode 100644 index 0000000000..359d85f2e8 --- /dev/null +++ b/src/libbasic/btrfs-util.c @@ -0,0 +1,2075 @@ +/*** + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LINUX_BTRFS_H +#include +#endif + +#include "alloc-util.h" +#include "btrfs-ctree.h" +#include "btrfs-util.h" +#include "copy.h" +#include "fd-util.h" +#include "fileio.h" +#include "io-util.h" +#include "macro.h" +#include "missing.h" +#include "path-util.h" +#include "selinux-util.h" +#include "smack-util.h" +#include "sparse-endian.h" +#include "stat-util.h" +#include "string-util.h" +#include "time-util.h" +#include "util.h" + +/* WARNING: Be careful with file system ioctls! When we get an fd, we + * need to make sure it either refers to only a regular file or + * directory, or that it is located on btrfs, before invoking any + * btrfs ioctls. The ioctl numbers are reused by some device drivers + * (such as DRM), and hence might have bad effects when invoked on + * device nodes (that reference drivers) rather than fds to normal + * files or directories. */ + +static int validate_subvolume_name(const char *name) { + + if (!filename_is_valid(name)) + return -EINVAL; + + if (strlen(name) > BTRFS_SUBVOL_NAME_MAX) + return -E2BIG; + + return 0; +} + +static int open_parent(const char *path, int flags) { + _cleanup_free_ char *parent = NULL; + int fd; + + assert(path); + + parent = dirname_malloc(path); + if (!parent) + return -ENOMEM; + + fd = open(parent, flags); + if (fd < 0) + return -errno; + + return fd; +} + +static int extract_subvolume_name(const char *path, const char **subvolume) { + const char *fn; + int r; + + assert(path); + assert(subvolume); + + fn = basename(path); + + r = validate_subvolume_name(fn); + if (r < 0) + return r; + + *subvolume = fn; + return 0; +} + +int btrfs_is_filesystem(int fd) { + struct statfs sfs; + + assert(fd >= 0); + + if (fstatfs(fd, &sfs) < 0) + return -errno; + + return F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC); +} + +int btrfs_is_subvol_fd(int fd) { + struct stat st; + + assert(fd >= 0); + + /* On btrfs subvolumes always have the inode 256 */ + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode) || st.st_ino != 256) + return 0; + + return btrfs_is_filesystem(fd); +} + +int btrfs_is_subvol(const char *path) { + _cleanup_close_ int fd = -1; + + assert(path); + + fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return -errno; + + return btrfs_is_subvol_fd(fd); +} + +int btrfs_subvol_make(const char *path) { + struct btrfs_ioctl_vol_args args = {}; + _cleanup_close_ int fd = -1; + const char *subvolume; + int r; + + assert(path); + + r = extract_subvolume_name(path, &subvolume); + if (r < 0) + return r; + + fd = open_parent(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return fd; + + strncpy(args.name, subvolume, sizeof(args.name)-1); + + if (ioctl(fd, BTRFS_IOC_SUBVOL_CREATE, &args) < 0) + return -errno; + + return 0; +} + +int btrfs_subvol_make_label(const char *path) { + int r; + + assert(path); + + r = mac_selinux_create_file_prepare(path, S_IFDIR); + if (r < 0) + return r; + + r = btrfs_subvol_make(path); + mac_selinux_create_file_clear(); + + if (r < 0) + return r; + + return mac_smack_fix(path, false, false); +} + +int btrfs_subvol_set_read_only_fd(int fd, bool b) { + uint64_t flags, nflags; + struct stat st; + + assert(fd >= 0); + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode) || st.st_ino != 256) + return -EINVAL; + + if (ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags) < 0) + return -errno; + + if (b) + nflags = flags | BTRFS_SUBVOL_RDONLY; + else + nflags = flags & ~BTRFS_SUBVOL_RDONLY; + + if (flags == nflags) + return 0; + + if (ioctl(fd, BTRFS_IOC_SUBVOL_SETFLAGS, &nflags) < 0) + return -errno; + + return 0; +} + +int btrfs_subvol_set_read_only(const char *path, bool b) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return -errno; + + return btrfs_subvol_set_read_only_fd(fd, b); +} + +int btrfs_subvol_get_read_only_fd(int fd) { + uint64_t flags; + struct stat st; + + assert(fd >= 0); + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode) || st.st_ino != 256) + return -EINVAL; + + if (ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags) < 0) + return -errno; + + return !!(flags & BTRFS_SUBVOL_RDONLY); +} + +int btrfs_reflink(int infd, int outfd) { + struct stat st; + int r; + + assert(infd >= 0); + assert(outfd >= 0); + + /* Make sure we invoke the ioctl on a regular file, so that no + * device driver accidentally gets it. */ + + if (fstat(outfd, &st) < 0) + return -errno; + + if (!S_ISREG(st.st_mode)) + return -EINVAL; + + r = ioctl(outfd, BTRFS_IOC_CLONE, infd); + if (r < 0) + return -errno; + + return 0; +} + +int btrfs_clone_range(int infd, uint64_t in_offset, int outfd, uint64_t out_offset, uint64_t sz) { + struct btrfs_ioctl_clone_range_args args = { + .src_fd = infd, + .src_offset = in_offset, + .src_length = sz, + .dest_offset = out_offset, + }; + struct stat st; + int r; + + assert(infd >= 0); + assert(outfd >= 0); + assert(sz > 0); + + if (fstat(outfd, &st) < 0) + return -errno; + + if (!S_ISREG(st.st_mode)) + return -EINVAL; + + r = ioctl(outfd, BTRFS_IOC_CLONE_RANGE, &args); + if (r < 0) + return -errno; + + return 0; +} + +int btrfs_get_block_device_fd(int fd, dev_t *dev) { + struct btrfs_ioctl_fs_info_args fsi = {}; + uint64_t id; + int r; + + assert(fd >= 0); + assert(dev); + + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + + if (ioctl(fd, BTRFS_IOC_FS_INFO, &fsi) < 0) + return -errno; + + /* We won't do this for btrfs RAID */ + if (fsi.num_devices != 1) + return 0; + + for (id = 1; id <= fsi.max_id; id++) { + struct btrfs_ioctl_dev_info_args di = { + .devid = id, + }; + struct stat st; + + if (ioctl(fd, BTRFS_IOC_DEV_INFO, &di) < 0) { + if (errno == ENODEV) + continue; + + return -errno; + } + + if (stat((char*) di.path, &st) < 0) + return -errno; + + if (!S_ISBLK(st.st_mode)) + return -ENODEV; + + if (major(st.st_rdev) == 0) + return -ENODEV; + + *dev = st.st_rdev; + return 1; + } + + return -ENODEV; +} + +int btrfs_get_block_device(const char *path, dev_t *dev) { + _cleanup_close_ int fd = -1; + + assert(path); + assert(dev); + + fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return -errno; + + return btrfs_get_block_device_fd(fd, dev); +} + +int btrfs_subvol_get_id_fd(int fd, uint64_t *ret) { + struct btrfs_ioctl_ino_lookup_args args = { + .objectid = BTRFS_FIRST_FREE_OBJECTID + }; + int r; + + assert(fd >= 0); + assert(ret); + + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + + if (ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args) < 0) + return -errno; + + *ret = args.treeid; + return 0; +} + +int btrfs_subvol_get_id(int fd, const char *subvol, uint64_t *ret) { + _cleanup_close_ int subvol_fd = -1; + + assert(fd >= 0); + assert(ret); + + subvol_fd = openat(fd, subvol, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (subvol_fd < 0) + return -errno; + + return btrfs_subvol_get_id_fd(subvol_fd, ret); +} + +static bool btrfs_ioctl_search_args_inc(struct btrfs_ioctl_search_args *args) { + assert(args); + + /* the objectid, type, offset together make up the btrfs key, + * which is considered a single 136byte integer when + * comparing. This call increases the counter by one, dealing + * with the overflow between the overflows */ + + if (args->key.min_offset < (uint64_t) -1) { + args->key.min_offset++; + return true; + } + + if (args->key.min_type < (uint8_t) -1) { + args->key.min_type++; + args->key.min_offset = 0; + return true; + } + + if (args->key.min_objectid < (uint64_t) -1) { + args->key.min_objectid++; + args->key.min_offset = 0; + args->key.min_type = 0; + return true; + } + + return 0; +} + +static void btrfs_ioctl_search_args_set(struct btrfs_ioctl_search_args *args, const struct btrfs_ioctl_search_header *h) { + assert(args); + assert(h); + + args->key.min_objectid = h->objectid; + args->key.min_type = h->type; + args->key.min_offset = h->offset; +} + +static int btrfs_ioctl_search_args_compare(const struct btrfs_ioctl_search_args *args) { + assert(args); + + /* Compare min and max */ + + if (args->key.min_objectid < args->key.max_objectid) + return -1; + if (args->key.min_objectid > args->key.max_objectid) + return 1; + + if (args->key.min_type < args->key.max_type) + return -1; + if (args->key.min_type > args->key.max_type) + return 1; + + if (args->key.min_offset < args->key.max_offset) + return -1; + if (args->key.min_offset > args->key.max_offset) + return 1; + + return 0; +} + +#define FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) \ + for ((i) = 0, \ + (sh) = (const struct btrfs_ioctl_search_header*) (args).buf; \ + (i) < (args).key.nr_items; \ + (i)++, \ + (sh) = (const struct btrfs_ioctl_search_header*) ((uint8_t*) (sh) + sizeof(struct btrfs_ioctl_search_header) + (sh)->len)) + +#define BTRFS_IOCTL_SEARCH_HEADER_BODY(sh) \ + ((void*) ((uint8_t*) sh + sizeof(struct btrfs_ioctl_search_header))) + +int btrfs_subvol_get_info_fd(int fd, uint64_t subvol_id, BtrfsSubvolInfo *ret) { + struct btrfs_ioctl_search_args args = { + /* Tree of tree roots */ + .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, + + /* Look precisely for the subvolume items */ + .key.min_type = BTRFS_ROOT_ITEM_KEY, + .key.max_type = BTRFS_ROOT_ITEM_KEY, + + .key.min_offset = 0, + .key.max_offset = (uint64_t) -1, + + /* No restrictions on the other components */ + .key.min_transid = 0, + .key.max_transid = (uint64_t) -1, + }; + + bool found = false; + int r; + + assert(fd >= 0); + assert(ret); + + if (subvol_id == 0) { + r = btrfs_subvol_get_id_fd(fd, &subvol_id); + if (r < 0) + return r; + } else { + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + } + + args.key.min_objectid = args.key.max_objectid = subvol_id; + + while (btrfs_ioctl_search_args_compare(&args) <= 0) { + const struct btrfs_ioctl_search_header *sh; + unsigned i; + + args.key.nr_items = 256; + if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) + return -errno; + + if (args.key.nr_items <= 0) + break; + + FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { + + const struct btrfs_root_item *ri; + + /* Make sure we start the next search at least from this entry */ + btrfs_ioctl_search_args_set(&args, sh); + + if (sh->objectid != subvol_id) + continue; + if (sh->type != BTRFS_ROOT_ITEM_KEY) + continue; + + /* Older versions of the struct lacked the otime setting */ + if (sh->len < offsetof(struct btrfs_root_item, otime) + sizeof(struct btrfs_timespec)) + continue; + + ri = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); + + ret->otime = (usec_t) le64toh(ri->otime.sec) * USEC_PER_SEC + + (usec_t) le32toh(ri->otime.nsec) / NSEC_PER_USEC; + + ret->subvol_id = subvol_id; + ret->read_only = !!(le64toh(ri->flags) & BTRFS_ROOT_SUBVOL_RDONLY); + + assert_cc(sizeof(ri->uuid) == sizeof(ret->uuid)); + memcpy(&ret->uuid, ri->uuid, sizeof(ret->uuid)); + memcpy(&ret->parent_uuid, ri->parent_uuid, sizeof(ret->parent_uuid)); + + found = true; + goto finish; + } + + /* Increase search key by one, to read the next item, if we can. */ + if (!btrfs_ioctl_search_args_inc(&args)) + break; + } + +finish: + if (!found) + return -ENODATA; + + return 0; +} + +int btrfs_qgroup_get_quota_fd(int fd, uint64_t qgroupid, BtrfsQuotaInfo *ret) { + + struct btrfs_ioctl_search_args args = { + /* Tree of quota items */ + .key.tree_id = BTRFS_QUOTA_TREE_OBJECTID, + + /* The object ID is always 0 */ + .key.min_objectid = 0, + .key.max_objectid = 0, + + /* Look precisely for the quota items */ + .key.min_type = BTRFS_QGROUP_STATUS_KEY, + .key.max_type = BTRFS_QGROUP_LIMIT_KEY, + + /* No restrictions on the other components */ + .key.min_transid = 0, + .key.max_transid = (uint64_t) -1, + }; + + bool found_info = false, found_limit = false; + int r; + + assert(fd >= 0); + assert(ret); + + if (qgroupid == 0) { + r = btrfs_subvol_get_id_fd(fd, &qgroupid); + if (r < 0) + return r; + } else { + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + } + + args.key.min_offset = args.key.max_offset = qgroupid; + + while (btrfs_ioctl_search_args_compare(&args) <= 0) { + const struct btrfs_ioctl_search_header *sh; + unsigned i; + + args.key.nr_items = 256; + if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) { + if (errno == ENOENT) /* quota tree is missing: quota disabled */ + break; + + return -errno; + } + + if (args.key.nr_items <= 0) + break; + + FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { + + /* Make sure we start the next search at least from this entry */ + btrfs_ioctl_search_args_set(&args, sh); + + if (sh->objectid != 0) + continue; + if (sh->offset != qgroupid) + continue; + + if (sh->type == BTRFS_QGROUP_INFO_KEY) { + const struct btrfs_qgroup_info_item *qii = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); + + ret->referenced = le64toh(qii->rfer); + ret->exclusive = le64toh(qii->excl); + + found_info = true; + + } else if (sh->type == BTRFS_QGROUP_LIMIT_KEY) { + const struct btrfs_qgroup_limit_item *qli = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); + + if (le64toh(qli->flags) & BTRFS_QGROUP_LIMIT_MAX_RFER) + ret->referenced_max = le64toh(qli->max_rfer); + else + ret->referenced_max = (uint64_t) -1; + + if (le64toh(qli->flags) & BTRFS_QGROUP_LIMIT_MAX_EXCL) + ret->exclusive_max = le64toh(qli->max_excl); + else + ret->exclusive_max = (uint64_t) -1; + + found_limit = true; + } + + if (found_info && found_limit) + goto finish; + } + + /* Increase search key by one, to read the next item, if we can. */ + if (!btrfs_ioctl_search_args_inc(&args)) + break; + } + +finish: + if (!found_limit && !found_info) + return -ENODATA; + + if (!found_info) { + ret->referenced = (uint64_t) -1; + ret->exclusive = (uint64_t) -1; + } + + if (!found_limit) { + ret->referenced_max = (uint64_t) -1; + ret->exclusive_max = (uint64_t) -1; + } + + return 0; +} + +int btrfs_qgroup_get_quota(const char *path, uint64_t qgroupid, BtrfsQuotaInfo *ret) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return btrfs_qgroup_get_quota_fd(fd, qgroupid, ret); +} + +int btrfs_subvol_find_subtree_qgroup(int fd, uint64_t subvol_id, uint64_t *ret) { + uint64_t level, lowest = (uint64_t) -1, lowest_qgroupid = 0; + _cleanup_free_ uint64_t *qgroups = NULL; + int r, n, i; + + assert(fd >= 0); + assert(ret); + + /* This finds the "subtree" qgroup for a specific + * subvolume. This only works for subvolumes that have been + * prepared with btrfs_subvol_auto_qgroup_fd() with + * insert_intermediary_qgroup=true (or equivalent). For others + * it will return the leaf qgroup instead. The two cases may + * be distuingished via the return value, which is 1 in case + * an appropriate "subtree" qgroup was found, and 0 + * otherwise. */ + + if (subvol_id == 0) { + r = btrfs_subvol_get_id_fd(fd, &subvol_id); + if (r < 0) + return r; + } + + r = btrfs_qgroupid_split(subvol_id, &level, NULL); + if (r < 0) + return r; + if (level != 0) /* Input must be a leaf qgroup */ + return -EINVAL; + + n = btrfs_qgroup_find_parents(fd, subvol_id, &qgroups); + if (n < 0) + return n; + + for (i = 0; i < n; i++) { + uint64_t id; + + r = btrfs_qgroupid_split(qgroups[i], &level, &id); + if (r < 0) + return r; + + if (id != subvol_id) + continue; + + if (lowest == (uint64_t) -1 || level < lowest) { + lowest_qgroupid = qgroups[i]; + lowest = level; + } + } + + if (lowest == (uint64_t) -1) { + /* No suitable higher-level qgroup found, let's return + * the leaf qgroup instead, and indicate that with the + * return value. */ + + *ret = subvol_id; + return 0; + } + + *ret = lowest_qgroupid; + return 1; +} + +int btrfs_subvol_get_subtree_quota_fd(int fd, uint64_t subvol_id, BtrfsQuotaInfo *ret) { + uint64_t qgroupid; + int r; + + assert(fd >= 0); + assert(ret); + + /* This determines the quota data of the qgroup with the + * lowest level, that shares the id part with the specified + * subvolume. This is useful for determining the quota data + * for entire subvolume subtrees, as long as the subtrees have + * been set up with btrfs_qgroup_subvol_auto_fd() or in a + * compatible way */ + + r = btrfs_subvol_find_subtree_qgroup(fd, subvol_id, &qgroupid); + if (r < 0) + return r; + + return btrfs_qgroup_get_quota_fd(fd, qgroupid, ret); +} + +int btrfs_subvol_get_subtree_quota(const char *path, uint64_t subvol_id, BtrfsQuotaInfo *ret) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return btrfs_subvol_get_subtree_quota_fd(fd, subvol_id, ret); +} + +int btrfs_defrag_fd(int fd) { + struct stat st; + + assert(fd >= 0); + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISREG(st.st_mode)) + return -EINVAL; + + if (ioctl(fd, BTRFS_IOC_DEFRAG, NULL) < 0) + return -errno; + + return 0; +} + +int btrfs_defrag(const char *p) { + _cleanup_close_ int fd = -1; + + fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return btrfs_defrag_fd(fd); +} + +int btrfs_quota_enable_fd(int fd, bool b) { + struct btrfs_ioctl_quota_ctl_args args = { + .cmd = b ? BTRFS_QUOTA_CTL_ENABLE : BTRFS_QUOTA_CTL_DISABLE, + }; + int r; + + assert(fd >= 0); + + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + + if (ioctl(fd, BTRFS_IOC_QUOTA_CTL, &args) < 0) + return -errno; + + return 0; +} + +int btrfs_quota_enable(const char *path, bool b) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return btrfs_quota_enable_fd(fd, b); +} + +int btrfs_qgroup_set_limit_fd(int fd, uint64_t qgroupid, uint64_t referenced_max) { + + struct btrfs_ioctl_qgroup_limit_args args = { + .lim.max_rfer = referenced_max, + .lim.flags = BTRFS_QGROUP_LIMIT_MAX_RFER, + }; + unsigned c; + int r; + + assert(fd >= 0); + + if (qgroupid == 0) { + r = btrfs_subvol_get_id_fd(fd, &qgroupid); + if (r < 0) + return r; + } else { + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + } + + args.qgroupid = qgroupid; + + for (c = 0;; c++) { + if (ioctl(fd, BTRFS_IOC_QGROUP_LIMIT, &args) < 0) { + + if (errno == EBUSY && c < 10) { + (void) btrfs_quota_scan_wait(fd); + continue; + } + + return -errno; + } + + break; + } + + return 0; +} + +int btrfs_qgroup_set_limit(const char *path, uint64_t qgroupid, uint64_t referenced_max) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return btrfs_qgroup_set_limit_fd(fd, qgroupid, referenced_max); +} + +int btrfs_subvol_set_subtree_quota_limit_fd(int fd, uint64_t subvol_id, uint64_t referenced_max) { + uint64_t qgroupid; + int r; + + assert(fd >= 0); + + r = btrfs_subvol_find_subtree_qgroup(fd, subvol_id, &qgroupid); + if (r < 0) + return r; + + return btrfs_qgroup_set_limit_fd(fd, qgroupid, referenced_max); +} + +int btrfs_subvol_set_subtree_quota_limit(const char *path, uint64_t subvol_id, uint64_t referenced_max) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return btrfs_subvol_set_subtree_quota_limit_fd(fd, subvol_id, referenced_max); +} + +int btrfs_resize_loopback_fd(int fd, uint64_t new_size, bool grow_only) { + struct btrfs_ioctl_vol_args args = {}; + _cleanup_free_ char *p = NULL, *loop = NULL, *backing = NULL; + _cleanup_close_ int loop_fd = -1, backing_fd = -1; + struct stat st; + dev_t dev = 0; + int r; + + /* In contrast to btrfs quota ioctls ftruncate() cannot make sense of "infinity" or file sizes > 2^31 */ + if (!FILE_SIZE_VALID(new_size)) + return -EINVAL; + + /* btrfs cannot handle file systems < 16M, hence use this as minimum */ + if (new_size < 16*1024*1024) + new_size = 16*1024*1024; + + r = btrfs_get_block_device_fd(fd, &dev); + if (r < 0) + return r; + if (r == 0) + return -ENODEV; + + if (asprintf(&p, "/sys/dev/block/%u:%u/loop/backing_file", major(dev), minor(dev)) < 0) + return -ENOMEM; + r = read_one_line_file(p, &backing); + if (r == -ENOENT) + return -ENODEV; + if (r < 0) + return r; + if (isempty(backing) || !path_is_absolute(backing)) + return -ENODEV; + + backing_fd = open(backing, O_RDWR|O_CLOEXEC|O_NOCTTY); + if (backing_fd < 0) + return -errno; + + if (fstat(backing_fd, &st) < 0) + return -errno; + if (!S_ISREG(st.st_mode)) + return -ENODEV; + + if (new_size == (uint64_t) st.st_size) + return 0; + + if (grow_only && new_size < (uint64_t) st.st_size) + return -EINVAL; + + if (asprintf(&loop, "/dev/block/%u:%u", major(dev), minor(dev)) < 0) + return -ENOMEM; + loop_fd = open(loop, O_RDWR|O_CLOEXEC|O_NOCTTY); + if (loop_fd < 0) + return -errno; + + if (snprintf(args.name, sizeof(args.name), "%" PRIu64, new_size) >= (int) sizeof(args.name)) + return -EINVAL; + + if (new_size < (uint64_t) st.st_size) { + /* Decrease size: first decrease btrfs size, then shorten loopback */ + if (ioctl(fd, BTRFS_IOC_RESIZE, &args) < 0) + return -errno; + } + + if (ftruncate(backing_fd, new_size) < 0) + return -errno; + + if (ioctl(loop_fd, LOOP_SET_CAPACITY, 0) < 0) + return -errno; + + if (new_size > (uint64_t) st.st_size) { + /* Increase size: first enlarge loopback, then increase btrfs size */ + if (ioctl(fd, BTRFS_IOC_RESIZE, &args) < 0) + return -errno; + } + + /* Make sure the free disk space is correctly updated for both file systems */ + (void) fsync(fd); + (void) fsync(backing_fd); + + return 1; +} + +int btrfs_resize_loopback(const char *p, uint64_t new_size, bool grow_only) { + _cleanup_close_ int fd = -1; + + fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return -errno; + + return btrfs_resize_loopback_fd(fd, new_size, grow_only); +} + +int btrfs_qgroupid_make(uint64_t level, uint64_t id, uint64_t *ret) { + assert(ret); + + if (level >= (UINT64_C(1) << (64 - BTRFS_QGROUP_LEVEL_SHIFT))) + return -EINVAL; + + if (id >= (UINT64_C(1) << BTRFS_QGROUP_LEVEL_SHIFT)) + return -EINVAL; + + *ret = (level << BTRFS_QGROUP_LEVEL_SHIFT) | id; + return 0; +} + +int btrfs_qgroupid_split(uint64_t qgroupid, uint64_t *level, uint64_t *id) { + assert(level || id); + + if (level) + *level = qgroupid >> BTRFS_QGROUP_LEVEL_SHIFT; + + if (id) + *id = qgroupid & ((UINT64_C(1) << BTRFS_QGROUP_LEVEL_SHIFT) - 1); + + return 0; +} + +static int qgroup_create_or_destroy(int fd, bool b, uint64_t qgroupid) { + + struct btrfs_ioctl_qgroup_create_args args = { + .create = b, + .qgroupid = qgroupid, + }; + unsigned c; + int r; + + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (r == 0) + return -ENOTTY; + + for (c = 0;; c++) { + if (ioctl(fd, BTRFS_IOC_QGROUP_CREATE, &args) < 0) { + + /* If quota is not enabled, we get EINVAL. Turn this into a recognizable error */ + if (errno == EINVAL) + return -ENOPROTOOPT; + + if (errno == EBUSY && c < 10) { + (void) btrfs_quota_scan_wait(fd); + continue; + } + + return -errno; + } + + break; + } + + return 0; +} + +int btrfs_qgroup_create(int fd, uint64_t qgroupid) { + return qgroup_create_or_destroy(fd, true, qgroupid); +} + +int btrfs_qgroup_destroy(int fd, uint64_t qgroupid) { + return qgroup_create_or_destroy(fd, false, qgroupid); +} + +int btrfs_qgroup_destroy_recursive(int fd, uint64_t qgroupid) { + _cleanup_free_ uint64_t *qgroups = NULL; + uint64_t subvol_id; + int i, n, r; + + /* Destroys the specified qgroup, but unassigns it from all + * its parents first. Also, it recursively destroys all + * qgroups it is assgined to that have the same id part of the + * qgroupid as the specified group. */ + + r = btrfs_qgroupid_split(qgroupid, NULL, &subvol_id); + if (r < 0) + return r; + + n = btrfs_qgroup_find_parents(fd, qgroupid, &qgroups); + if (n < 0) + return n; + + for (i = 0; i < n; i++) { + uint64_t id; + + r = btrfs_qgroupid_split(qgroups[i], NULL, &id); + if (r < 0) + return r; + + r = btrfs_qgroup_unassign(fd, qgroupid, qgroups[i]); + if (r < 0) + return r; + + if (id != subvol_id) + continue; + + /* The parent qgroupid shares the same id part with + * us? If so, destroy it too. */ + + (void) btrfs_qgroup_destroy_recursive(fd, qgroups[i]); + } + + return btrfs_qgroup_destroy(fd, qgroupid); +} + +int btrfs_quota_scan_start(int fd) { + struct btrfs_ioctl_quota_rescan_args args = {}; + + assert(fd >= 0); + + if (ioctl(fd, BTRFS_IOC_QUOTA_RESCAN, &args) < 0) + return -errno; + + return 0; +} + +int btrfs_quota_scan_wait(int fd) { + assert(fd >= 0); + + if (ioctl(fd, BTRFS_IOC_QUOTA_RESCAN_WAIT) < 0) + return -errno; + + return 0; +} + +int btrfs_quota_scan_ongoing(int fd) { + struct btrfs_ioctl_quota_rescan_args args = {}; + + assert(fd >= 0); + + if (ioctl(fd, BTRFS_IOC_QUOTA_RESCAN_STATUS, &args) < 0) + return -errno; + + return !!args.flags; +} + +static int qgroup_assign_or_unassign(int fd, bool b, uint64_t child, uint64_t parent) { + struct btrfs_ioctl_qgroup_assign_args args = { + .assign = b, + .src = child, + .dst = parent, + }; + unsigned c; + int r; + + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (r == 0) + return -ENOTTY; + + for (c = 0;; c++) { + r = ioctl(fd, BTRFS_IOC_QGROUP_ASSIGN, &args); + if (r < 0) { + if (errno == EBUSY && c < 10) { + (void) btrfs_quota_scan_wait(fd); + continue; + } + + return -errno; + } + + if (r == 0) + return 0; + + /* If the return value is > 0, we need to request a rescan */ + + (void) btrfs_quota_scan_start(fd); + return 1; + } +} + +int btrfs_qgroup_assign(int fd, uint64_t child, uint64_t parent) { + return qgroup_assign_or_unassign(fd, true, child, parent); +} + +int btrfs_qgroup_unassign(int fd, uint64_t child, uint64_t parent) { + return qgroup_assign_or_unassign(fd, false, child, parent); +} + +static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol_id, BtrfsRemoveFlags flags) { + struct btrfs_ioctl_search_args args = { + .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, + + .key.min_objectid = BTRFS_FIRST_FREE_OBJECTID, + .key.max_objectid = BTRFS_LAST_FREE_OBJECTID, + + .key.min_type = BTRFS_ROOT_BACKREF_KEY, + .key.max_type = BTRFS_ROOT_BACKREF_KEY, + + .key.min_transid = 0, + .key.max_transid = (uint64_t) -1, + }; + + struct btrfs_ioctl_vol_args vol_args = {}; + _cleanup_close_ int subvol_fd = -1; + struct stat st; + bool made_writable = false; + int r; + + assert(fd >= 0); + assert(subvolume); + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode)) + return -EINVAL; + + subvol_fd = openat(fd, subvolume, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (subvol_fd < 0) + return -errno; + + if (subvol_id == 0) { + r = btrfs_subvol_get_id_fd(subvol_fd, &subvol_id); + if (r < 0) + return r; + } + + /* First, try to remove the subvolume. If it happens to be + * already empty, this will just work. */ + strncpy(vol_args.name, subvolume, sizeof(vol_args.name)-1); + if (ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &vol_args) >= 0) { + (void) btrfs_qgroup_destroy_recursive(fd, subvol_id); /* for the leaf subvolumes, the qgroup id is identical to the subvol id */ + return 0; + } + if (!(flags & BTRFS_REMOVE_RECURSIVE) || errno != ENOTEMPTY) + return -errno; + + /* OK, the subvolume is not empty, let's look for child + * subvolumes, and remove them, first */ + + args.key.min_offset = args.key.max_offset = subvol_id; + + while (btrfs_ioctl_search_args_compare(&args) <= 0) { + const struct btrfs_ioctl_search_header *sh; + unsigned i; + + args.key.nr_items = 256; + if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) + return -errno; + + if (args.key.nr_items <= 0) + break; + + FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { + _cleanup_free_ char *p = NULL; + const struct btrfs_root_ref *ref; + struct btrfs_ioctl_ino_lookup_args ino_args; + + btrfs_ioctl_search_args_set(&args, sh); + + if (sh->type != BTRFS_ROOT_BACKREF_KEY) + continue; + if (sh->offset != subvol_id) + continue; + + ref = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); + + p = strndup((char*) ref + sizeof(struct btrfs_root_ref), le64toh(ref->name_len)); + if (!p) + return -ENOMEM; + + zero(ino_args); + ino_args.treeid = subvol_id; + ino_args.objectid = htole64(ref->dirid); + + if (ioctl(fd, BTRFS_IOC_INO_LOOKUP, &ino_args) < 0) + return -errno; + + if (!made_writable) { + r = btrfs_subvol_set_read_only_fd(subvol_fd, false); + if (r < 0) + return r; + + made_writable = true; + } + + if (isempty(ino_args.name)) + /* Subvolume is in the top-level + * directory of the subvolume. */ + r = subvol_remove_children(subvol_fd, p, sh->objectid, flags); + else { + _cleanup_close_ int child_fd = -1; + + /* Subvolume is somewhere further down, + * hence we need to open the + * containing directory first */ + + child_fd = openat(subvol_fd, ino_args.name, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (child_fd < 0) + return -errno; + + r = subvol_remove_children(child_fd, p, sh->objectid, flags); + } + if (r < 0) + return r; + } + + /* Increase search key by one, to read the next item, if we can. */ + if (!btrfs_ioctl_search_args_inc(&args)) + break; + } + + /* OK, the child subvolumes should all be gone now, let's try + * again to remove the subvolume */ + if (ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &vol_args) < 0) + return -errno; + + (void) btrfs_qgroup_destroy_recursive(fd, subvol_id); + return 0; +} + +int btrfs_subvol_remove(const char *path, BtrfsRemoveFlags flags) { + _cleanup_close_ int fd = -1; + const char *subvolume; + int r; + + assert(path); + + r = extract_subvolume_name(path, &subvolume); + if (r < 0) + return r; + + fd = open_parent(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return fd; + + return subvol_remove_children(fd, subvolume, 0, flags); +} + +int btrfs_subvol_remove_fd(int fd, const char *subvolume, BtrfsRemoveFlags flags) { + return subvol_remove_children(fd, subvolume, 0, flags); +} + +int btrfs_qgroup_copy_limits(int fd, uint64_t old_qgroupid, uint64_t new_qgroupid) { + + struct btrfs_ioctl_search_args args = { + /* Tree of quota items */ + .key.tree_id = BTRFS_QUOTA_TREE_OBJECTID, + + /* The object ID is always 0 */ + .key.min_objectid = 0, + .key.max_objectid = 0, + + /* Look precisely for the quota items */ + .key.min_type = BTRFS_QGROUP_LIMIT_KEY, + .key.max_type = BTRFS_QGROUP_LIMIT_KEY, + + /* For our qgroup */ + .key.min_offset = old_qgroupid, + .key.max_offset = old_qgroupid, + + /* No restrictions on the other components */ + .key.min_transid = 0, + .key.max_transid = (uint64_t) -1, + }; + + int r; + + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + + while (btrfs_ioctl_search_args_compare(&args) <= 0) { + const struct btrfs_ioctl_search_header *sh; + unsigned i; + + args.key.nr_items = 256; + if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) { + if (errno == ENOENT) /* quota tree missing: quota is not enabled, hence nothing to copy */ + break; + + return -errno; + } + + if (args.key.nr_items <= 0) + break; + + FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { + const struct btrfs_qgroup_limit_item *qli = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); + struct btrfs_ioctl_qgroup_limit_args qargs; + unsigned c; + + /* Make sure we start the next search at least from this entry */ + btrfs_ioctl_search_args_set(&args, sh); + + if (sh->objectid != 0) + continue; + if (sh->type != BTRFS_QGROUP_LIMIT_KEY) + continue; + if (sh->offset != old_qgroupid) + continue; + + /* We found the entry, now copy things over. */ + + qargs = (struct btrfs_ioctl_qgroup_limit_args) { + .qgroupid = new_qgroupid, + + .lim.max_rfer = le64toh(qli->max_rfer), + .lim.max_excl = le64toh(qli->max_excl), + .lim.rsv_rfer = le64toh(qli->rsv_rfer), + .lim.rsv_excl = le64toh(qli->rsv_excl), + + .lim.flags = le64toh(qli->flags) & (BTRFS_QGROUP_LIMIT_MAX_RFER| + BTRFS_QGROUP_LIMIT_MAX_EXCL| + BTRFS_QGROUP_LIMIT_RSV_RFER| + BTRFS_QGROUP_LIMIT_RSV_EXCL), + }; + + for (c = 0;; c++) { + if (ioctl(fd, BTRFS_IOC_QGROUP_LIMIT, &qargs) < 0) { + if (errno == EBUSY && c < 10) { + (void) btrfs_quota_scan_wait(fd); + continue; + } + return -errno; + } + + break; + } + + return 1; + } + + /* Increase search key by one, to read the next item, if we can. */ + if (!btrfs_ioctl_search_args_inc(&args)) + break; + } + + return 0; +} + +static int copy_quota_hierarchy(int fd, uint64_t old_subvol_id, uint64_t new_subvol_id) { + _cleanup_free_ uint64_t *old_qgroups = NULL, *old_parent_qgroups = NULL; + bool copy_from_parent = false, insert_intermediary_qgroup = false; + int n_old_qgroups, n_old_parent_qgroups, r, i; + uint64_t old_parent_id; + + assert(fd >= 0); + + /* Copies a reduced form of quota information from the old to + * the new subvolume. */ + + n_old_qgroups = btrfs_qgroup_find_parents(fd, old_subvol_id, &old_qgroups); + if (n_old_qgroups <= 0) /* Nothing to copy */ + return n_old_qgroups; + + r = btrfs_subvol_get_parent(fd, old_subvol_id, &old_parent_id); + if (r == -ENXIO) + /* We have no parent, hence nothing to copy. */ + n_old_parent_qgroups = 0; + else if (r < 0) + return r; + else { + n_old_parent_qgroups = btrfs_qgroup_find_parents(fd, old_parent_id, &old_parent_qgroups); + if (n_old_parent_qgroups < 0) + return n_old_parent_qgroups; + } + + for (i = 0; i < n_old_qgroups; i++) { + uint64_t id; + int j; + + r = btrfs_qgroupid_split(old_qgroups[i], NULL, &id); + if (r < 0) + return r; + + if (id == old_subvol_id) { + /* The old subvolume was member of a qgroup + * that had the same id, but a different level + * as it self. Let's set up something similar + * in the destination. */ + insert_intermediary_qgroup = true; + break; + } + + for (j = 0; j < n_old_parent_qgroups; j++) + if (old_parent_qgroups[j] == old_qgroups[i]) { + /* The old subvolume shared a common + * parent qgroup with its parent + * subvolume. Let's set up something + * similar in the destination. */ + copy_from_parent = true; + } + } + + if (!insert_intermediary_qgroup && !copy_from_parent) + return 0; + + return btrfs_subvol_auto_qgroup_fd(fd, new_subvol_id, insert_intermediary_qgroup); +} + +static int copy_subtree_quota_limits(int fd, uint64_t old_subvol, uint64_t new_subvol) { + uint64_t old_subtree_qgroup, new_subtree_qgroup; + bool changed; + int r; + + /* First copy the leaf limits */ + r = btrfs_qgroup_copy_limits(fd, old_subvol, new_subvol); + if (r < 0) + return r; + changed = r > 0; + + /* Then, try to copy the subtree limits, if there are any. */ + r = btrfs_subvol_find_subtree_qgroup(fd, old_subvol, &old_subtree_qgroup); + if (r < 0) + return r; + if (r == 0) + return changed; + + r = btrfs_subvol_find_subtree_qgroup(fd, new_subvol, &new_subtree_qgroup); + if (r < 0) + return r; + if (r == 0) + return changed; + + r = btrfs_qgroup_copy_limits(fd, old_subtree_qgroup, new_subtree_qgroup); + if (r != 0) + return r; + + return changed; +} + +static int subvol_snapshot_children(int old_fd, int new_fd, const char *subvolume, uint64_t old_subvol_id, BtrfsSnapshotFlags flags) { + + struct btrfs_ioctl_search_args args = { + .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, + + .key.min_objectid = BTRFS_FIRST_FREE_OBJECTID, + .key.max_objectid = BTRFS_LAST_FREE_OBJECTID, + + .key.min_type = BTRFS_ROOT_BACKREF_KEY, + .key.max_type = BTRFS_ROOT_BACKREF_KEY, + + .key.min_transid = 0, + .key.max_transid = (uint64_t) -1, + }; + + struct btrfs_ioctl_vol_args_v2 vol_args = { + .flags = flags & BTRFS_SNAPSHOT_READ_ONLY ? BTRFS_SUBVOL_RDONLY : 0, + .fd = old_fd, + }; + _cleanup_close_ int subvolume_fd = -1; + uint64_t new_subvol_id; + int r; + + assert(old_fd >= 0); + assert(new_fd >= 0); + assert(subvolume); + + strncpy(vol_args.name, subvolume, sizeof(vol_args.name)-1); + + if (ioctl(new_fd, BTRFS_IOC_SNAP_CREATE_V2, &vol_args) < 0) + return -errno; + + if (!(flags & BTRFS_SNAPSHOT_RECURSIVE) && + !(flags & BTRFS_SNAPSHOT_QUOTA)) + return 0; + + if (old_subvol_id == 0) { + r = btrfs_subvol_get_id_fd(old_fd, &old_subvol_id); + if (r < 0) + return r; + } + + r = btrfs_subvol_get_id(new_fd, vol_args.name, &new_subvol_id); + if (r < 0) + return r; + + if (flags & BTRFS_SNAPSHOT_QUOTA) + (void) copy_quota_hierarchy(new_fd, old_subvol_id, new_subvol_id); + + if (!(flags & BTRFS_SNAPSHOT_RECURSIVE)) { + + if (flags & BTRFS_SNAPSHOT_QUOTA) + (void) copy_subtree_quota_limits(new_fd, old_subvol_id, new_subvol_id); + + return 0; + } + + args.key.min_offset = args.key.max_offset = old_subvol_id; + + while (btrfs_ioctl_search_args_compare(&args) <= 0) { + const struct btrfs_ioctl_search_header *sh; + unsigned i; + + args.key.nr_items = 256; + if (ioctl(old_fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) + return -errno; + + if (args.key.nr_items <= 0) + break; + + FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { + _cleanup_free_ char *p = NULL, *c = NULL, *np = NULL; + struct btrfs_ioctl_ino_lookup_args ino_args; + const struct btrfs_root_ref *ref; + _cleanup_close_ int old_child_fd = -1, new_child_fd = -1; + + btrfs_ioctl_search_args_set(&args, sh); + + if (sh->type != BTRFS_ROOT_BACKREF_KEY) + continue; + + /* Avoid finding the source subvolume a second + * time */ + if (sh->offset != old_subvol_id) + continue; + + /* Avoid running into loops if the new + * subvolume is below the old one. */ + if (sh->objectid == new_subvol_id) + continue; + + ref = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); + p = strndup((char*) ref + sizeof(struct btrfs_root_ref), le64toh(ref->name_len)); + if (!p) + return -ENOMEM; + + zero(ino_args); + ino_args.treeid = old_subvol_id; + ino_args.objectid = htole64(ref->dirid); + + if (ioctl(old_fd, BTRFS_IOC_INO_LOOKUP, &ino_args) < 0) + return -errno; + + /* The kernel returns an empty name if the + * subvolume is in the top-level directory, + * and otherwise appends a slash, so that we + * can just concatenate easily here, without + * adding a slash. */ + c = strappend(ino_args.name, p); + if (!c) + return -ENOMEM; + + old_child_fd = openat(old_fd, c, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (old_child_fd < 0) + return -errno; + + np = strjoin(subvolume, "/", ino_args.name, NULL); + if (!np) + return -ENOMEM; + + new_child_fd = openat(new_fd, np, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (new_child_fd < 0) + return -errno; + + if (flags & BTRFS_SNAPSHOT_READ_ONLY) { + /* If the snapshot is read-only we + * need to mark it writable + * temporarily, to put the subsnapshot + * into place. */ + + if (subvolume_fd < 0) { + subvolume_fd = openat(new_fd, subvolume, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (subvolume_fd < 0) + return -errno; + } + + r = btrfs_subvol_set_read_only_fd(subvolume_fd, false); + if (r < 0) + return r; + } + + /* When btrfs clones the subvolumes, child + * subvolumes appear as empty directories. Remove + * them, so that we can create a new snapshot + * in their place */ + if (unlinkat(new_child_fd, p, AT_REMOVEDIR) < 0) { + int k = -errno; + + if (flags & BTRFS_SNAPSHOT_READ_ONLY) + (void) btrfs_subvol_set_read_only_fd(subvolume_fd, true); + + return k; + } + + r = subvol_snapshot_children(old_child_fd, new_child_fd, p, sh->objectid, flags & ~BTRFS_SNAPSHOT_FALLBACK_COPY); + + /* Restore the readonly flag */ + if (flags & BTRFS_SNAPSHOT_READ_ONLY) { + int k; + + k = btrfs_subvol_set_read_only_fd(subvolume_fd, true); + if (r >= 0 && k < 0) + return k; + } + + if (r < 0) + return r; + } + + /* Increase search key by one, to read the next item, if we can. */ + if (!btrfs_ioctl_search_args_inc(&args)) + break; + } + + if (flags & BTRFS_SNAPSHOT_QUOTA) + (void) copy_subtree_quota_limits(new_fd, old_subvol_id, new_subvol_id); + + return 0; +} + +int btrfs_subvol_snapshot_fd(int old_fd, const char *new_path, BtrfsSnapshotFlags flags) { + _cleanup_close_ int new_fd = -1; + const char *subvolume; + int r; + + assert(old_fd >= 0); + assert(new_path); + + r = btrfs_is_subvol_fd(old_fd); + if (r < 0) + return r; + if (r == 0) { + if (!(flags & BTRFS_SNAPSHOT_FALLBACK_COPY)) + return -EISDIR; + + r = btrfs_subvol_make(new_path); + if (r < 0) + return r; + + r = copy_directory_fd(old_fd, new_path, true); + if (r < 0) { + (void) btrfs_subvol_remove(new_path, BTRFS_REMOVE_QUOTA); + return r; + } + + if (flags & BTRFS_SNAPSHOT_READ_ONLY) { + r = btrfs_subvol_set_read_only(new_path, true); + if (r < 0) { + (void) btrfs_subvol_remove(new_path, BTRFS_REMOVE_QUOTA); + return r; + } + } + + return 0; + } + + r = extract_subvolume_name(new_path, &subvolume); + if (r < 0) + return r; + + new_fd = open_parent(new_path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (new_fd < 0) + return new_fd; + + return subvol_snapshot_children(old_fd, new_fd, subvolume, 0, flags); +} + +int btrfs_subvol_snapshot(const char *old_path, const char *new_path, BtrfsSnapshotFlags flags) { + _cleanup_close_ int old_fd = -1; + + assert(old_path); + assert(new_path); + + old_fd = open(old_path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (old_fd < 0) + return -errno; + + return btrfs_subvol_snapshot_fd(old_fd, new_path, flags); +} + +int btrfs_qgroup_find_parents(int fd, uint64_t qgroupid, uint64_t **ret) { + + struct btrfs_ioctl_search_args args = { + /* Tree of quota items */ + .key.tree_id = BTRFS_QUOTA_TREE_OBJECTID, + + /* Look precisely for the quota relation items */ + .key.min_type = BTRFS_QGROUP_RELATION_KEY, + .key.max_type = BTRFS_QGROUP_RELATION_KEY, + + /* No restrictions on the other components */ + .key.min_offset = 0, + .key.max_offset = (uint64_t) -1, + + .key.min_transid = 0, + .key.max_transid = (uint64_t) -1, + }; + + _cleanup_free_ uint64_t *items = NULL; + size_t n_items = 0, n_allocated = 0; + int r; + + assert(fd >= 0); + assert(ret); + + if (qgroupid == 0) { + r = btrfs_subvol_get_id_fd(fd, &qgroupid); + if (r < 0) + return r; + } else { + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + } + + args.key.min_objectid = args.key.max_objectid = qgroupid; + + while (btrfs_ioctl_search_args_compare(&args) <= 0) { + const struct btrfs_ioctl_search_header *sh; + unsigned i; + + args.key.nr_items = 256; + if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) { + if (errno == ENOENT) /* quota tree missing: quota is disabled */ + break; + + return -errno; + } + + if (args.key.nr_items <= 0) + break; + + FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { + + /* Make sure we start the next search at least from this entry */ + btrfs_ioctl_search_args_set(&args, sh); + + if (sh->type != BTRFS_QGROUP_RELATION_KEY) + continue; + if (sh->offset < sh->objectid) + continue; + if (sh->objectid != qgroupid) + continue; + + if (!GREEDY_REALLOC(items, n_allocated, n_items+1)) + return -ENOMEM; + + items[n_items++] = sh->offset; + } + + /* Increase search key by one, to read the next item, if we can. */ + if (!btrfs_ioctl_search_args_inc(&args)) + break; + } + + if (n_items <= 0) { + *ret = NULL; + return 0; + } + + *ret = items; + items = NULL; + + return (int) n_items; +} + +int btrfs_subvol_auto_qgroup_fd(int fd, uint64_t subvol_id, bool insert_intermediary_qgroup) { + _cleanup_free_ uint64_t *qgroups = NULL; + uint64_t parent_subvol; + bool changed = false; + int n = 0, r; + + assert(fd >= 0); + + /* + * Sets up the specified subvolume's qgroup automatically in + * one of two ways: + * + * If insert_intermediary_qgroup is false, the subvolume's + * leaf qgroup will be assigned to the same parent qgroups as + * the subvolume's parent subvolume. + * + * If insert_intermediary_qgroup is true a new intermediary + * higher-level qgroup is created, with a higher level number, + * but reusing the id of the subvolume. The level number is + * picked as one smaller than the lowest level qgroup the + * parent subvolume is a member of. If the parent subvolume's + * leaf qgroup is assigned to no higher-level qgroup a new + * qgroup of level 255 is created instead. Either way, the new + * qgroup is then assigned to the parent's higher-level + * qgroup, and the subvolume itself is assigned to it. + * + * If the subvolume is already assigned to a higher level + * qgroup, no operation is executed. + * + * Effectively this means: regardless if + * insert_intermediary_qgroup is true or not, after this + * function is invoked the subvolume will be accounted within + * the same qgroups as the parent. However, if it is true, it + * will also get its own higher-level qgroup, which may in + * turn be used by subvolumes created beneath this subvolume + * later on. + * + * This hence defines a simple default qgroup setup for + * subvolumes, as long as this function is invoked on each + * created subvolume: each subvolume is always accounting + * together with its immediate parents. Optionally, if + * insert_intermediary_qgroup is true, it will also get a + * qgroup that then includes all its own child subvolumes. + */ + + if (subvol_id == 0) { + r = btrfs_is_subvol_fd(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + + r = btrfs_subvol_get_id_fd(fd, &subvol_id); + if (r < 0) + return r; + } + + n = btrfs_qgroup_find_parents(fd, subvol_id, &qgroups); + if (n < 0) + return n; + if (n > 0) /* already parent qgroups set up, let's bail */ + return 0; + + qgroups = mfree(qgroups); + + r = btrfs_subvol_get_parent(fd, subvol_id, &parent_subvol); + if (r == -ENXIO) + /* No parent, hence no qgroup memberships */ + n = 0; + else if (r < 0) + return r; + else { + n = btrfs_qgroup_find_parents(fd, parent_subvol, &qgroups); + if (n < 0) + return n; + } + + if (insert_intermediary_qgroup) { + uint64_t lowest = 256, new_qgroupid; + bool created = false; + int i; + + /* Determine the lowest qgroup that the parent + * subvolume is assigned to. */ + + for (i = 0; i < n; i++) { + uint64_t level; + + r = btrfs_qgroupid_split(qgroups[i], &level, NULL); + if (r < 0) + return r; + + if (level < lowest) + lowest = level; + } + + if (lowest <= 1) /* There are no levels left we could use insert an intermediary qgroup at */ + return -EBUSY; + + r = btrfs_qgroupid_make(lowest - 1, subvol_id, &new_qgroupid); + if (r < 0) + return r; + + /* Create the new intermediary group, unless it already exists */ + r = btrfs_qgroup_create(fd, new_qgroupid); + if (r < 0 && r != -EEXIST) + return r; + if (r >= 0) + changed = created = true; + + for (i = 0; i < n; i++) { + r = btrfs_qgroup_assign(fd, new_qgroupid, qgroups[i]); + if (r < 0 && r != -EEXIST) { + if (created) + (void) btrfs_qgroup_destroy_recursive(fd, new_qgroupid); + + return r; + } + if (r >= 0) + changed = true; + } + + r = btrfs_qgroup_assign(fd, subvol_id, new_qgroupid); + if (r < 0 && r != -EEXIST) { + if (created) + (void) btrfs_qgroup_destroy_recursive(fd, new_qgroupid); + return r; + } + if (r >= 0) + changed = true; + + } else { + int i; + + /* Assign our subvolume to all the same qgroups as the parent */ + + for (i = 0; i < n; i++) { + r = btrfs_qgroup_assign(fd, subvol_id, qgroups[i]); + if (r < 0 && r != -EEXIST) + return r; + if (r >= 0) + changed = true; + } + } + + return changed; +} + +int btrfs_subvol_auto_qgroup(const char *path, uint64_t subvol_id, bool create_intermediary_qgroup) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return -errno; + + return btrfs_subvol_auto_qgroup_fd(fd, subvol_id, create_intermediary_qgroup); +} + +int btrfs_subvol_get_parent(int fd, uint64_t subvol_id, uint64_t *ret) { + + struct btrfs_ioctl_search_args args = { + /* Tree of tree roots */ + .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, + + /* Look precisely for the subvolume items */ + .key.min_type = BTRFS_ROOT_BACKREF_KEY, + .key.max_type = BTRFS_ROOT_BACKREF_KEY, + + /* No restrictions on the other components */ + .key.min_offset = 0, + .key.max_offset = (uint64_t) -1, + + .key.min_transid = 0, + .key.max_transid = (uint64_t) -1, + }; + int r; + + assert(fd >= 0); + assert(ret); + + if (subvol_id == 0) { + r = btrfs_subvol_get_id_fd(fd, &subvol_id); + if (r < 0) + return r; + } else { + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + } + + args.key.min_objectid = args.key.max_objectid = subvol_id; + + while (btrfs_ioctl_search_args_compare(&args) <= 0) { + const struct btrfs_ioctl_search_header *sh; + unsigned i; + + args.key.nr_items = 256; + if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) + return negative_errno(); + + if (args.key.nr_items <= 0) + break; + + FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { + + if (sh->type != BTRFS_ROOT_BACKREF_KEY) + continue; + if (sh->objectid != subvol_id) + continue; + + *ret = sh->offset; + return 0; + } + } + + return -ENXIO; +} diff --git a/src/libbasic/btrfs-util.h b/src/libbasic/btrfs-util.h new file mode 100644 index 0000000000..db431f5b74 --- /dev/null +++ b/src/libbasic/btrfs-util.h @@ -0,0 +1,131 @@ +#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 +#include +#include + +#include + +#include "time-util.h" + +typedef struct BtrfsSubvolInfo { + uint64_t subvol_id; + usec_t otime; + + sd_id128_t uuid; + sd_id128_t parent_uuid; + + bool read_only; +} BtrfsSubvolInfo; + +typedef struct BtrfsQuotaInfo { + uint64_t referenced; + uint64_t exclusive; + uint64_t referenced_max; + uint64_t exclusive_max; +} BtrfsQuotaInfo; + +typedef enum BtrfsSnapshotFlags { + BTRFS_SNAPSHOT_FALLBACK_COPY = 1, + BTRFS_SNAPSHOT_READ_ONLY = 2, + BTRFS_SNAPSHOT_RECURSIVE = 4, + BTRFS_SNAPSHOT_QUOTA = 8, +} BtrfsSnapshotFlags; + +typedef enum BtrfsRemoveFlags { + BTRFS_REMOVE_RECURSIVE = 1, + BTRFS_REMOVE_QUOTA = 2, +} BtrfsRemoveFlags; + +int btrfs_is_filesystem(int fd); + +int btrfs_is_subvol_fd(int fd); +int btrfs_is_subvol(const char *path); + +int btrfs_reflink(int infd, int outfd); +int btrfs_clone_range(int infd, uint64_t in_offset, int ofd, uint64_t out_offset, uint64_t sz); + +int btrfs_get_block_device_fd(int fd, dev_t *dev); +int btrfs_get_block_device(const char *path, dev_t *dev); + +int btrfs_defrag_fd(int fd); +int btrfs_defrag(const char *p); + +int btrfs_quota_enable_fd(int fd, bool b); +int btrfs_quota_enable(const char *path, bool b); + +int btrfs_quota_scan_start(int fd); +int btrfs_quota_scan_wait(int fd); +int btrfs_quota_scan_ongoing(int fd); + +int btrfs_resize_loopback_fd(int fd, uint64_t size, bool grow_only); +int btrfs_resize_loopback(const char *path, uint64_t size, bool grow_only); + +int btrfs_subvol_make(const char *path); +int btrfs_subvol_make_label(const char *path); + +int btrfs_subvol_snapshot_fd(int old_fd, const char *new_path, BtrfsSnapshotFlags flags); +int btrfs_subvol_snapshot(const char *old_path, const char *new_path, BtrfsSnapshotFlags flags); + +int btrfs_subvol_remove(const char *path, BtrfsRemoveFlags flags); +int btrfs_subvol_remove_fd(int fd, const char *subvolume, BtrfsRemoveFlags flags); + +int btrfs_subvol_set_read_only_fd(int fd, bool b); +int btrfs_subvol_set_read_only(const char *path, bool b); +int btrfs_subvol_get_read_only_fd(int fd); + +int btrfs_subvol_get_id(int fd, const char *subvolume, uint64_t *ret); +int btrfs_subvol_get_id_fd(int fd, uint64_t *ret); +int btrfs_subvol_get_parent(int fd, uint64_t subvol_id, uint64_t *ret); + +int btrfs_subvol_get_info_fd(int fd, uint64_t subvol_id, BtrfsSubvolInfo *info); + +int btrfs_subvol_find_subtree_qgroup(int fd, uint64_t subvol_id, uint64_t *ret); + +int btrfs_subvol_get_subtree_quota(const char *path, uint64_t subvol_id, BtrfsQuotaInfo *quota); +int btrfs_subvol_get_subtree_quota_fd(int fd, uint64_t subvol_id, BtrfsQuotaInfo *quota); + +int btrfs_subvol_set_subtree_quota_limit(const char *path, uint64_t subvol_id, uint64_t referenced_max); +int btrfs_subvol_set_subtree_quota_limit_fd(int fd, uint64_t subvol_id, uint64_t referenced_max); + +int btrfs_subvol_auto_qgroup_fd(int fd, uint64_t subvol_id, bool new_qgroup); +int btrfs_subvol_auto_qgroup(const char *path, uint64_t subvol_id, bool create_intermediary_qgroup); + +int btrfs_qgroupid_make(uint64_t level, uint64_t id, uint64_t *ret); +int btrfs_qgroupid_split(uint64_t qgroupid, uint64_t *level, uint64_t *id); + +int btrfs_qgroup_create(int fd, uint64_t qgroupid); +int btrfs_qgroup_destroy(int fd, uint64_t qgroupid); +int btrfs_qgroup_destroy_recursive(int fd, uint64_t qgroupid); + +int btrfs_qgroup_set_limit_fd(int fd, uint64_t qgroupid, uint64_t referenced_max); +int btrfs_qgroup_set_limit(const char *path, uint64_t qgroupid, uint64_t referenced_max); + +int btrfs_qgroup_copy_limits(int fd, uint64_t old_qgroupid, uint64_t new_qgroupid); + +int btrfs_qgroup_assign(int fd, uint64_t child, uint64_t parent); +int btrfs_qgroup_unassign(int fd, uint64_t child, uint64_t parent); + +int btrfs_qgroup_find_parents(int fd, uint64_t qgroupid, uint64_t **ret); + +int btrfs_qgroup_get_quota_fd(int fd, uint64_t qgroupid, BtrfsQuotaInfo *quota); +int btrfs_qgroup_get_quota(const char *path, uint64_t qgroupid, BtrfsQuotaInfo *quota); diff --git a/src/libbasic/build.h b/src/libbasic/build.h new file mode 100644 index 0000000000..633c2aaccb --- /dev/null +++ b/src/libbasic/build.h @@ -0,0 +1,155 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#ifdef HAVE_PAM +#define _PAM_FEATURE_ "+PAM" +#else +#define _PAM_FEATURE_ "-PAM" +#endif + +#ifdef HAVE_AUDIT +#define _AUDIT_FEATURE_ "+AUDIT" +#else +#define _AUDIT_FEATURE_ "-AUDIT" +#endif + +#ifdef HAVE_SELINUX +#define _SELINUX_FEATURE_ "+SELINUX" +#else +#define _SELINUX_FEATURE_ "-SELINUX" +#endif + +#ifdef HAVE_APPARMOR +#define _APPARMOR_FEATURE_ "+APPARMOR" +#else +#define _APPARMOR_FEATURE_ "-APPARMOR" +#endif + +#ifdef HAVE_IMA +#define _IMA_FEATURE_ "+IMA" +#else +#define _IMA_FEATURE_ "-IMA" +#endif + +#ifdef HAVE_SMACK +#define _SMACK_FEATURE_ "+SMACK" +#else +#define _SMACK_FEATURE_ "-SMACK" +#endif + +#ifdef HAVE_SYSV_COMPAT +#define _SYSVINIT_FEATURE_ "+SYSVINIT" +#else +#define _SYSVINIT_FEATURE_ "-SYSVINIT" +#endif + +#ifdef HAVE_UTMP +#define _UTMP_FEATURE_ "+UTMP" +#else +#define _UTMP_FEATURE_ "-UTMP" +#endif + +#ifdef HAVE_LIBCRYPTSETUP +#define _LIBCRYPTSETUP_FEATURE_ "+LIBCRYPTSETUP" +#else +#define _LIBCRYPTSETUP_FEATURE_ "-LIBCRYPTSETUP" +#endif + +#ifdef HAVE_GCRYPT +#define _GCRYPT_FEATURE_ "+GCRYPT" +#else +#define _GCRYPT_FEATURE_ "-GCRYPT" +#endif + +#ifdef HAVE_GNUTLS +#define _GNUTLS_FEATURE_ "+GNUTLS" +#else +#define _GNUTLS_FEATURE_ "-GNUTLS" +#endif + +#ifdef HAVE_ACL +#define _ACL_FEATURE_ "+ACL" +#else +#define _ACL_FEATURE_ "-ACL" +#endif + +#ifdef HAVE_XZ +#define _XZ_FEATURE_ "+XZ" +#else +#define _XZ_FEATURE_ "-XZ" +#endif + +#ifdef HAVE_LZ4 +#define _LZ4_FEATURE_ "+LZ4" +#else +#define _LZ4_FEATURE_ "-LZ4" +#endif + +#ifdef HAVE_SECCOMP +#define _SECCOMP_FEATURE_ "+SECCOMP" +#else +#define _SECCOMP_FEATURE_ "-SECCOMP" +#endif + +#ifdef HAVE_BLKID +#define _BLKID_FEATURE_ "+BLKID" +#else +#define _BLKID_FEATURE_ "-BLKID" +#endif + +#ifdef HAVE_ELFUTILS +#define _ELFUTILS_FEATURE_ "+ELFUTILS" +#else +#define _ELFUTILS_FEATURE_ "-ELFUTILS" +#endif + +#ifdef HAVE_KMOD +#define _KMOD_FEATURE_ "+KMOD" +#else +#define _KMOD_FEATURE_ "-KMOD" +#endif + +#ifdef HAVE_LIBIDN +#define _IDN_FEATURE_ "+IDN" +#else +#define _IDN_FEATURE_ "-IDN" +#endif + +#define SYSTEMD_FEATURES \ + _PAM_FEATURE_ " " \ + _AUDIT_FEATURE_ " " \ + _SELINUX_FEATURE_ " " \ + _IMA_FEATURE_ " " \ + _APPARMOR_FEATURE_ " " \ + _SMACK_FEATURE_ " " \ + _SYSVINIT_FEATURE_ " " \ + _UTMP_FEATURE_ " " \ + _LIBCRYPTSETUP_FEATURE_ " " \ + _GCRYPT_FEATURE_ " " \ + _GNUTLS_FEATURE_ " " \ + _ACL_FEATURE_ " " \ + _XZ_FEATURE_ " " \ + _LZ4_FEATURE_ " " \ + _SECCOMP_FEATURE_ " " \ + _BLKID_FEATURE_ " " \ + _ELFUTILS_FEATURE_ " " \ + _KMOD_FEATURE_ " " \ + _IDN_FEATURE_ diff --git a/src/libbasic/bus-label.c b/src/libbasic/bus-label.c new file mode 100644 index 0000000000..d4531c7947 --- /dev/null +++ b/src/libbasic/bus-label.c @@ -0,0 +1,98 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "alloc-util.h" +#include "bus-label.h" +#include "hexdecoct.h" +#include "macro.h" + +char *bus_label_escape(const char *s) { + char *r, *t; + const char *f; + + assert_return(s, NULL); + + /* Escapes all chars that D-Bus' object path cannot deal + * with. Can be reversed with bus_path_unescape(). We special + * case the empty string. */ + + if (*s == 0) + return strdup("_"); + + r = new(char, strlen(s)*3 + 1); + if (!r) + return NULL; + + for (f = s, t = r; *f; f++) { + + /* Escape everything that is not a-zA-Z0-9. We also + * escape 0-9 if it's the first character */ + + if (!(*f >= 'A' && *f <= 'Z') && + !(*f >= 'a' && *f <= 'z') && + !(f > s && *f >= '0' && *f <= '9')) { + *(t++) = '_'; + *(t++) = hexchar(*f >> 4); + *(t++) = hexchar(*f); + } else + *(t++) = *f; + } + + *t = 0; + + return r; +} + +char *bus_label_unescape_n(const char *f, size_t l) { + char *r, *t; + size_t i; + + assert_return(f, NULL); + + /* Special case for the empty string */ + if (l == 1 && *f == '_') + return strdup(""); + + r = new(char, l + 1); + if (!r) + return NULL; + + for (i = 0, t = r; i < l; ++i) { + if (f[i] == '_') { + int a, b; + + if (l - i < 3 || + (a = unhexchar(f[i + 1])) < 0 || + (b = unhexchar(f[i + 2])) < 0) { + /* Invalid escape code, let's take it literal then */ + *(t++) = '_'; + } else { + *(t++) = (char) ((a << 4) | b); + i += 2; + } + } else + *(t++) = f[i]; + } + + *t = 0; + + return r; +} diff --git a/src/libbasic/bus-label.h b/src/libbasic/bus-label.h new file mode 100644 index 0000000000..62fb2c450c --- /dev/null +++ b/src/libbasic/bus-label.h @@ -0,0 +1,31 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 +#include + +char *bus_label_escape(const char *s); +char *bus_label_unescape_n(const char *f, size_t l); + +static inline char *bus_label_unescape(const char *f) { + return bus_label_unescape_n(f, f ? strlen(f) : 0); +} diff --git a/src/libbasic/calendarspec.c b/src/libbasic/calendarspec.c new file mode 100644 index 0000000000..6e0bab9b94 --- /dev/null +++ b/src/libbasic/calendarspec.c @@ -0,0 +1,1089 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "calendarspec.h" +#include "fileio.h" +#include "macro.h" +#include "parse-util.h" +#include "string-util.h" + +#define BITS_WEEKDAYS 127 + +static void free_chain(CalendarComponent *c) { + CalendarComponent *n; + + while (c) { + n = c->next; + free(c); + c = n; + } +} + +void calendar_spec_free(CalendarSpec *c) { + + if (!c) + return; + + free_chain(c->year); + free_chain(c->month); + free_chain(c->day); + free_chain(c->hour); + free_chain(c->minute); + free_chain(c->microsecond); + + free(c); +} + +static int component_compare(const void *_a, const void *_b) { + CalendarComponent * const *a = _a, * const *b = _b; + + if ((*a)->value < (*b)->value) + return -1; + if ((*a)->value > (*b)->value) + return 1; + + if ((*a)->repeat < (*b)->repeat) + return -1; + if ((*a)->repeat > (*b)->repeat) + return 1; + + return 0; +} + +static void sort_chain(CalendarComponent **c) { + unsigned n = 0, k; + CalendarComponent **b, *i, **j, *next; + + assert(c); + + for (i = *c; i; i = i->next) + n++; + + if (n <= 1) + return; + + j = b = alloca(sizeof(CalendarComponent*) * n); + for (i = *c; i; i = i->next) + *(j++) = i; + + qsort(b, n, sizeof(CalendarComponent*), component_compare); + + b[n-1]->next = NULL; + next = b[n-1]; + + /* Drop non-unique entries */ + for (k = n-1; k > 0; k--) { + if (b[k-1]->value == next->value && + b[k-1]->repeat == next->repeat) { + free(b[k-1]); + continue; + } + + b[k-1]->next = next; + next = b[k-1]; + } + + *c = next; +} + +static void fix_year(CalendarComponent *c) { + /* Turns 12 → 2012, 89 → 1989 */ + + while (c) { + CalendarComponent *n = c->next; + + if (c->value >= 0 && c->value < 70) + c->value += 2000; + + if (c->value >= 70 && c->value < 100) + c->value += 1900; + + c = n; + } +} + +int calendar_spec_normalize(CalendarSpec *c) { + assert(c); + + if (c->weekdays_bits <= 0 || c->weekdays_bits >= BITS_WEEKDAYS) + c->weekdays_bits = -1; + + fix_year(c->year); + + sort_chain(&c->year); + sort_chain(&c->month); + sort_chain(&c->day); + sort_chain(&c->hour); + sort_chain(&c->minute); + sort_chain(&c->microsecond); + + return 0; +} + +_pure_ static bool chain_valid(CalendarComponent *c, int from, int to) { + if (!c) + return true; + + if (c->value < from || c->value > to) + return false; + + if (c->value + c->repeat > to) + return false; + + if (c->next) + return chain_valid(c->next, from, to); + + return true; +} + +_pure_ bool calendar_spec_valid(CalendarSpec *c) { + assert(c); + + if (c->weekdays_bits > BITS_WEEKDAYS) + return false; + + if (!chain_valid(c->year, 1970, 2199)) + return false; + + if (!chain_valid(c->month, 1, 12)) + return false; + + if (!chain_valid(c->day, 1, 31)) + return false; + + if (!chain_valid(c->hour, 0, 23)) + return false; + + if (!chain_valid(c->minute, 0, 59)) + return false; + + if (!chain_valid(c->microsecond, 0, 60*USEC_PER_SEC-1)) + return false; + + return true; +} + +static void format_weekdays(FILE *f, const CalendarSpec *c) { + static const char *const days[] = { + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun" + }; + + int l, x; + bool need_colon = false; + + assert(f); + assert(c); + assert(c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS); + + for (x = 0, l = -1; x < (int) ELEMENTSOF(days); x++) { + + if (c->weekdays_bits & (1 << x)) { + + if (l < 0) { + if (need_colon) + fputc(',', f); + else + need_colon = true; + + fputs(days[x], f); + l = x; + } + + } else if (l >= 0) { + + if (x > l + 1) { + fputc(x > l + 2 ? '-' : ',', f); + fputs(days[x-1], f); + } + + l = -1; + } + } + + if (l >= 0 && x > l + 1) { + fputc(x > l + 2 ? '-' : ',', f); + fputs(days[x-1], f); + } +} + +static void format_chain(FILE *f, int space, const CalendarComponent *c, bool usec) { + assert(f); + + if (!c) { + fputc('*', f); + return; + } + + assert(c->value >= 0); + if (!usec) + fprintf(f, "%0*i", space, c->value); + else if (c->value % USEC_PER_SEC == 0) + fprintf(f, "%0*i", space, (int) (c->value / USEC_PER_SEC)); + else + fprintf(f, "%0*i.%06i", space, (int) (c->value / USEC_PER_SEC), (int) (c->value % USEC_PER_SEC)); + + if (c->repeat > 0) { + if (!usec) + fprintf(f, "/%i", c->repeat); + else if (c->repeat % USEC_PER_SEC == 0) + fprintf(f, "/%i", (int) (c->repeat / USEC_PER_SEC)); + else + fprintf(f, "/%i.%06i", (int) (c->repeat / USEC_PER_SEC), (int) (c->repeat % USEC_PER_SEC)); + } + + if (c->next) { + fputc(',', f); + format_chain(f, space, c->next, usec); + } +} + +int calendar_spec_to_string(const CalendarSpec *c, char **p) { + char *buf = NULL; + size_t sz = 0; + FILE *f; + int r; + + assert(c); + assert(p); + + f = open_memstream(&buf, &sz); + if (!f) + return -ENOMEM; + + if (c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS) { + format_weekdays(f, c); + fputc(' ', f); + } + + format_chain(f, 4, c->year, false); + fputc('-', f); + format_chain(f, 2, c->month, false); + fputc('-', f); + format_chain(f, 2, c->day, false); + fputc(' ', f); + format_chain(f, 2, c->hour, false); + fputc(':', f); + format_chain(f, 2, c->minute, false); + fputc(':', f); + format_chain(f, 2, c->microsecond, true); + + if (c->utc) + fputs(" UTC", f); + + r = fflush_and_check(f); + if (r < 0) { + free(buf); + fclose(f); + return r; + } + + fclose(f); + + *p = buf; + return 0; +} + +static int parse_weekdays(const char **p, CalendarSpec *c) { + static const struct { + const char *name; + const int nr; + } day_nr[] = { + { "Monday", 0 }, + { "Mon", 0 }, + { "Tuesday", 1 }, + { "Tue", 1 }, + { "Wednesday", 2 }, + { "Wed", 2 }, + { "Thursday", 3 }, + { "Thu", 3 }, + { "Friday", 4 }, + { "Fri", 4 }, + { "Saturday", 5 }, + { "Sat", 5 }, + { "Sunday", 6 }, + { "Sun", 6 } + }; + + int l = -1; + bool first = true; + + assert(p); + assert(*p); + assert(c); + + for (;;) { + unsigned i; + + if (!first && **p == ' ') + return 0; + + for (i = 0; i < ELEMENTSOF(day_nr); i++) { + size_t skip; + + if (!startswith_no_case(*p, day_nr[i].name)) + continue; + + skip = strlen(day_nr[i].name); + + if ((*p)[skip] != '-' && + (*p)[skip] != ',' && + (*p)[skip] != ' ' && + (*p)[skip] != 0) + return -EINVAL; + + c->weekdays_bits |= 1 << day_nr[i].nr; + + if (l >= 0) { + int j; + + if (l > day_nr[i].nr) + return -EINVAL; + + for (j = l + 1; j < day_nr[i].nr; j++) + c->weekdays_bits |= 1 << j; + } + + *p += skip; + break; + } + + /* Couldn't find this prefix, so let's assume the + weekday was not specified and let's continue with + the date */ + if (i >= ELEMENTSOF(day_nr)) + return first ? 0 : -EINVAL; + + /* We reached the end of the string */ + if (**p == 0) + return 0; + + /* We reached the end of the weekday spec part */ + if (**p == ' ') { + *p += strspn(*p, " "); + return 0; + } + + if (**p == '-') { + if (l >= 0) + return -EINVAL; + + l = day_nr[i].nr; + } else + l = -1; + + *p += 1; + first = false; + } +} + +static int parse_component_decimal(const char **p, bool usec, unsigned long *res) { + unsigned long value; + const char *e = NULL; + char *ee = NULL; + int r; + + errno = 0; + value = strtoul(*p, &ee, 10); + if (errno > 0) + return -errno; + if (ee == *p) + return -EINVAL; + if ((unsigned long) (int) value != value) + return -ERANGE; + e = ee; + + if (usec) { + if (value * USEC_PER_SEC / USEC_PER_SEC != value) + return -ERANGE; + + value *= USEC_PER_SEC; + if (*e == '.') { + unsigned add; + + e++; + r = parse_fractional_part_u(&e, 6, &add); + if (r < 0) + return r; + + if (add + value < value) + return -ERANGE; + value += add; + } + } + + *p = e; + *res = value; + + return 0; +} + +static int prepend_component(const char **p, bool usec, CalendarComponent **c) { + unsigned long value, repeat = 0; + CalendarComponent *cc; + int r; + const char *e; + + assert(p); + assert(c); + + e = *p; + + r = parse_component_decimal(&e, usec, &value); + if (r < 0) + return r; + + if (*e == '/') { + e++; + r = parse_component_decimal(&e, usec, &repeat); + if (r < 0) + return r; + + if (repeat == 0) + return -ERANGE; + } + + if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != ':') + return -EINVAL; + + cc = new0(CalendarComponent, 1); + if (!cc) + return -ENOMEM; + + cc->value = value; + cc->repeat = repeat; + cc->next = *c; + + *p = e; + *c = cc; + + if (*e ==',') { + *p += 1; + return prepend_component(p, usec, c); + } + + return 0; +} + +static int const_chain(int value, CalendarComponent **c) { + CalendarComponent *cc = NULL; + + assert(c); + + cc = new0(CalendarComponent, 1); + if (!cc) + return -ENOMEM; + + cc->value = value; + cc->repeat = 0; + cc->next = *c; + + *c = cc; + + return 0; +} + +static int parse_chain(const char **p, bool usec, CalendarComponent **c) { + const char *t; + CalendarComponent *cc = NULL; + int r; + + assert(p); + assert(c); + + t = *p; + + if (t[0] == '*') { + if (usec) { + r = const_chain(0, c); + if (r < 0) + return r; + (*c)->repeat = USEC_PER_SEC; + } else + *c = NULL; + + *p = t + 1; + return 0; + } + + r = prepend_component(&t, usec, &cc); + if (r < 0) { + free_chain(cc); + return r; + } + + *p = t; + *c = cc; + return 0; +} + +static int parse_date(const char **p, CalendarSpec *c) { + const char *t; + int r; + CalendarComponent *first, *second, *third; + + assert(p); + assert(*p); + assert(c); + + t = *p; + + if (*t == 0) + return 0; + + r = parse_chain(&t, false, &first); + if (r < 0) + return r; + + /* Already the end? A ':' as separator? In that case this was a time, not a date */ + if (*t == 0 || *t == ':') { + free_chain(first); + return 0; + } + + if (*t != '-') { + free_chain(first); + return -EINVAL; + } + + t++; + r = parse_chain(&t, false, &second); + if (r < 0) { + free_chain(first); + return r; + } + + /* Got two parts, hence it's month and day */ + if (*t == ' ' || *t == 0) { + *p = t + strspn(t, " "); + c->month = first; + c->day = second; + return 0; + } + + if (*t != '-') { + free_chain(first); + free_chain(second); + return -EINVAL; + } + + t++; + r = parse_chain(&t, false, &third); + if (r < 0) { + free_chain(first); + free_chain(second); + return r; + } + + /* Got tree parts, hence it is year, month and day */ + if (*t == ' ' || *t == 0) { + *p = t + strspn(t, " "); + c->year = first; + c->month = second; + c->day = third; + return 0; + } + + free_chain(first); + free_chain(second); + free_chain(third); + return -EINVAL; +} + +static int parse_calendar_time(const char **p, CalendarSpec *c) { + CalendarComponent *h = NULL, *m = NULL, *s = NULL; + const char *t; + int r; + + assert(p); + assert(*p); + assert(c); + + t = *p; + + if (*t == 0) { + /* If no time is specified at all, but a date of some + * kind, then this means 00:00:00 */ + if (c->day || c->weekdays_bits > 0) + goto null_hour; + + goto finish; + } + + r = parse_chain(&t, false, &h); + if (r < 0) + goto fail; + + if (*t != ':') { + r = -EINVAL; + goto fail; + } + + t++; + r = parse_chain(&t, false, &m); + if (r < 0) + goto fail; + + /* Already at the end? Then it's hours and minutes, and seconds are 0 */ + if (*t == 0) { + if (m != NULL) + goto null_second; + + goto finish; + } + + if (*t != ':') { + r = -EINVAL; + goto fail; + } + + t++; + r = parse_chain(&t, true, &s); + if (r < 0) + goto fail; + + /* At the end? Then it's hours, minutes and seconds */ + if (*t == 0) + goto finish; + + r = -EINVAL; + goto fail; + +null_hour: + r = const_chain(0, &h); + if (r < 0) + goto fail; + + r = const_chain(0, &m); + if (r < 0) + goto fail; + +null_second: + r = const_chain(0, &s); + if (r < 0) + goto fail; + +finish: + *p = t; + c->hour = h; + c->minute = m; + c->microsecond = s; + + return 0; + +fail: + free_chain(h); + free_chain(m); + free_chain(s); + return r; +} + +int calendar_spec_from_string(const char *p, CalendarSpec **spec) { + CalendarSpec *c; + int r; + const char *utc; + + assert(p); + assert(spec); + + if (isempty(p)) + return -EINVAL; + + c = new0(CalendarSpec, 1); + if (!c) + return -ENOMEM; + + utc = endswith_no_case(p, " UTC"); + if (utc) { + c->utc = true; + p = strndupa(p, utc - p); + } + + if (strcaseeq(p, "minutely")) { + r = const_chain(0, &c->microsecond); + if (r < 0) + goto fail; + + } else if (strcaseeq(p, "hourly")) { + r = const_chain(0, &c->minute); + if (r < 0) + goto fail; + r = const_chain(0, &c->microsecond); + if (r < 0) + goto fail; + + } else if (strcaseeq(p, "daily")) { + r = const_chain(0, &c->hour); + if (r < 0) + goto fail; + r = const_chain(0, &c->minute); + if (r < 0) + goto fail; + r = const_chain(0, &c->microsecond); + if (r < 0) + goto fail; + + } else if (strcaseeq(p, "monthly")) { + r = const_chain(1, &c->day); + if (r < 0) + goto fail; + r = const_chain(0, &c->hour); + if (r < 0) + goto fail; + r = const_chain(0, &c->minute); + if (r < 0) + goto fail; + r = const_chain(0, &c->microsecond); + if (r < 0) + goto fail; + + } else if (strcaseeq(p, "annually") || + strcaseeq(p, "yearly") || + strcaseeq(p, "anually") /* backwards compatibility */ ) { + + r = const_chain(1, &c->month); + if (r < 0) + goto fail; + r = const_chain(1, &c->day); + if (r < 0) + goto fail; + r = const_chain(0, &c->hour); + if (r < 0) + goto fail; + r = const_chain(0, &c->minute); + if (r < 0) + goto fail; + r = const_chain(0, &c->microsecond); + if (r < 0) + goto fail; + + } else if (strcaseeq(p, "weekly")) { + + c->weekdays_bits = 1; + + r = const_chain(0, &c->hour); + if (r < 0) + goto fail; + r = const_chain(0, &c->minute); + if (r < 0) + goto fail; + r = const_chain(0, &c->microsecond); + if (r < 0) + goto fail; + + } else if (strcaseeq(p, "quarterly")) { + + r = const_chain(1, &c->month); + if (r < 0) + goto fail; + r = const_chain(4, &c->month); + if (r < 0) + goto fail; + r = const_chain(7, &c->month); + if (r < 0) + goto fail; + r = const_chain(10, &c->month); + if (r < 0) + goto fail; + r = const_chain(1, &c->day); + if (r < 0) + goto fail; + r = const_chain(0, &c->hour); + if (r < 0) + goto fail; + r = const_chain(0, &c->minute); + if (r < 0) + goto fail; + r = const_chain(0, &c->microsecond); + if (r < 0) + goto fail; + + } else if (strcaseeq(p, "biannually") || + strcaseeq(p, "bi-annually") || + strcaseeq(p, "semiannually") || + strcaseeq(p, "semi-annually")) { + + r = const_chain(1, &c->month); + if (r < 0) + goto fail; + r = const_chain(7, &c->month); + if (r < 0) + goto fail; + r = const_chain(1, &c->day); + if (r < 0) + goto fail; + r = const_chain(0, &c->hour); + if (r < 0) + goto fail; + r = const_chain(0, &c->minute); + if (r < 0) + goto fail; + r = const_chain(0, &c->microsecond); + if (r < 0) + goto fail; + + } else { + r = parse_weekdays(&p, c); + if (r < 0) + goto fail; + + r = parse_date(&p, c); + if (r < 0) + goto fail; + + r = parse_calendar_time(&p, c); + if (r < 0) + goto fail; + + if (*p != 0) { + r = -EINVAL; + goto fail; + } + } + + r = calendar_spec_normalize(c); + if (r < 0) + goto fail; + + if (!calendar_spec_valid(c)) { + r = -EINVAL; + goto fail; + } + + *spec = c; + return 0; + +fail: + calendar_spec_free(c); + return r; +} + +static int find_matching_component(const CalendarComponent *c, int *val) { + const CalendarComponent *n; + int d = -1; + bool d_set = false; + int r; + + assert(val); + + if (!c) + return 0; + + while (c) { + n = c->next; + + if (c->value >= *val) { + + if (!d_set || c->value < d) { + d = c->value; + d_set = true; + } + + } else if (c->repeat > 0) { + int k; + + k = c->value + c->repeat * ((*val - c->value + c->repeat -1) / c->repeat); + + if (!d_set || k < d) { + d = k; + d_set = true; + } + } + + c = n; + } + + if (!d_set) + return -ENOENT; + + r = *val != d; + *val = d; + return r; +} + +static bool tm_out_of_bounds(const struct tm *tm, bool utc) { + struct tm t; + assert(tm); + + t = *tm; + + if (mktime_or_timegm(&t, utc) == (time_t) -1) + return true; + + /* Did any normalization take place? If so, it was out of bounds before */ + return + t.tm_year != tm->tm_year || + t.tm_mon != tm->tm_mon || + t.tm_mday != tm->tm_mday || + t.tm_hour != tm->tm_hour || + t.tm_min != tm->tm_min || + t.tm_sec != tm->tm_sec; +} + +static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) { + struct tm t; + int k; + + if (weekdays_bits < 0 || weekdays_bits >= BITS_WEEKDAYS) + return true; + + t = *tm; + if (mktime_or_timegm(&t, utc) == (time_t) -1) + return false; + + k = t.tm_wday == 0 ? 6 : t.tm_wday - 1; + return (weekdays_bits & (1 << k)); +} + +static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { + struct tm c; + int tm_usec; + int r; + + assert(spec); + assert(tm); + + c = *tm; + tm_usec = *usec; + + for (;;) { + /* Normalize the current date */ + (void) mktime_or_timegm(&c, spec->utc); + c.tm_isdst = -1; + + c.tm_year += 1900; + r = find_matching_component(spec->year, &c.tm_year); + c.tm_year -= 1900; + + if (r > 0) { + c.tm_mon = 0; + c.tm_mday = 1; + c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; + } + if (r < 0) + return r; + if (tm_out_of_bounds(&c, spec->utc)) + return -ENOENT; + + c.tm_mon += 1; + r = find_matching_component(spec->month, &c.tm_mon); + c.tm_mon -= 1; + + if (r > 0) { + c.tm_mday = 1; + c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; + } + if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { + c.tm_year++; + c.tm_mon = 0; + c.tm_mday = 1; + c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; + continue; + } + + r = find_matching_component(spec->day, &c.tm_mday); + if (r > 0) + c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; + if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { + c.tm_mon++; + c.tm_mday = 1; + c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; + continue; + } + + if (!matches_weekday(spec->weekdays_bits, &c, spec->utc)) { + c.tm_mday++; + c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; + continue; + } + + r = find_matching_component(spec->hour, &c.tm_hour); + if (r > 0) + c.tm_min = c.tm_sec = tm_usec = 0; + if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { + c.tm_mday++; + c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; + continue; + } + + r = find_matching_component(spec->minute, &c.tm_min); + if (r > 0) + c.tm_sec = tm_usec = 0; + if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { + c.tm_hour++; + c.tm_min = c.tm_sec = tm_usec = 0; + continue; + } + + c.tm_sec = c.tm_sec * USEC_PER_SEC + tm_usec; + r = find_matching_component(spec->microsecond, &c.tm_sec); + tm_usec = c.tm_sec % USEC_PER_SEC; + c.tm_sec /= USEC_PER_SEC; + + if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { + c.tm_min++; + c.tm_sec = tm_usec = 0; + continue; + } + + *tm = c; + *usec = tm_usec; + return 0; + } +} + +int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) { + struct tm tm; + time_t t; + int r; + usec_t tm_usec; + + assert(spec); + assert(next); + + usec++; + t = (time_t) (usec / USEC_PER_SEC); + assert_se(localtime_or_gmtime_r(&t, &tm, spec->utc)); + tm_usec = usec % USEC_PER_SEC; + + r = find_next(spec, &tm, &tm_usec); + if (r < 0) + return r; + + t = mktime_or_timegm(&tm, spec->utc); + if (t == (time_t) -1) + return -EINVAL; + + *next = (usec_t) t * USEC_PER_SEC + tm_usec; + return 0; +} diff --git a/src/libbasic/calendarspec.h b/src/libbasic/calendarspec.h new file mode 100644 index 0000000000..f6472c1244 --- /dev/null +++ b/src/libbasic/calendarspec.h @@ -0,0 +1,58 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +/* A structure for specifying (possibly repetitive) points in calendar + * time, a la cron */ + +#include + +#include "time-util.h" +#include "util.h" + +typedef struct CalendarComponent { + int value; + int repeat; + + struct CalendarComponent *next; +} CalendarComponent; + +typedef struct CalendarSpec { + int weekdays_bits; + bool utc; + + CalendarComponent *year; + CalendarComponent *month; + CalendarComponent *day; + + CalendarComponent *hour; + CalendarComponent *minute; + CalendarComponent *microsecond; +} CalendarSpec; + +void calendar_spec_free(CalendarSpec *c); + +int calendar_spec_normalize(CalendarSpec *spec); +bool calendar_spec_valid(CalendarSpec *spec); + +int calendar_spec_to_string(const CalendarSpec *spec, char **p); +int calendar_spec_from_string(const char *p, CalendarSpec **spec); + +int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next); diff --git a/src/libbasic/cap-list.c b/src/libbasic/cap-list.c new file mode 100644 index 0000000000..3e773a06f5 --- /dev/null +++ b/src/libbasic/cap-list.c @@ -0,0 +1,66 @@ +/*** + 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 + +#include "cap-list.h" +#include "macro.h" +#include "missing.h" +#include "parse-util.h" +#include "util.h" + +static const struct capability_name* lookup_capability(register const char *str, register unsigned int len); + +#include "cap-from-name.h" +#include "cap-to-name.h" + +const char *capability_to_name(int id) { + + if (id < 0) + return NULL; + + if (id >= (int) ELEMENTSOF(capability_names)) + return NULL; + + return capability_names[id]; +} + +int capability_from_name(const char *name) { + const struct capability_name *sc; + int r, i; + + assert(name); + + /* Try to parse numeric capability */ + r = safe_atoi(name, &i); + if (r >= 0 && i >= 0) + return i; + + /* Try to parse string capability */ + sc = lookup_capability(name, strlen(name)); + if (!sc) + return -EINVAL; + + return sc->id; +} + +int capability_list_length(void) { + return (int) ELEMENTSOF(capability_names); +} diff --git a/src/libbasic/cap-list.h b/src/libbasic/cap-list.h new file mode 100644 index 0000000000..c1f6b94ad3 --- /dev/null +++ b/src/libbasic/cap-list.h @@ -0,0 +1,24 @@ +#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 . +***/ + +const char *capability_to_name(int id); +int capability_from_name(const char *name); +int capability_list_length(void); diff --git a/src/libbasic/capability-util.c b/src/libbasic/capability-util.c new file mode 100644 index 0000000000..d4c5bd6937 --- /dev/null +++ b/src/libbasic/capability-util.c @@ -0,0 +1,361 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "capability-util.h" +#include "fileio.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "util.h" + +int have_effective_cap(int value) { + _cleanup_cap_free_ cap_t cap; + cap_flag_value_t fv; + + cap = cap_get_proc(); + if (!cap) + return -errno; + + if (cap_get_flag(cap, value, CAP_EFFECTIVE, &fv) < 0) + return -errno; + else + return fv == CAP_SET; +} + +unsigned long cap_last_cap(void) { + static thread_local unsigned long saved; + static thread_local bool valid = false; + _cleanup_free_ char *content = NULL; + unsigned long p = 0; + int r; + + if (valid) + return saved; + + /* available since linux-3.2 */ + r = read_one_line_file("/proc/sys/kernel/cap_last_cap", &content); + if (r >= 0) { + r = safe_atolu(content, &p); + if (r >= 0) { + saved = p; + valid = true; + return p; + } + } + + /* fall back to syscall-probing for pre linux-3.2 */ + p = (unsigned long) CAP_LAST_CAP; + + if (prctl(PR_CAPBSET_READ, p) < 0) { + + /* Hmm, look downwards, until we find one that + * works */ + for (p--; p > 0; p --) + if (prctl(PR_CAPBSET_READ, p) >= 0) + break; + + } else { + + /* Hmm, look upwards, until we find one that doesn't + * work */ + for (;; p++) + if (prctl(PR_CAPBSET_READ, p+1) < 0) + break; + } + + saved = p; + valid = true; + + return p; +} + +int capability_update_inherited_set(cap_t caps, uint64_t set) { + unsigned long i; + + /* Add capabilities in the set to the inherited caps. Do not apply + * them yet. */ + + for (i = 0; i < cap_last_cap(); i++) { + + if (set & (UINT64_C(1) << i)) { + cap_value_t v; + + v = (cap_value_t) i; + + /* Make the capability inheritable. */ + if (cap_set_flag(caps, CAP_INHERITABLE, 1, &v, CAP_SET) < 0) + return -errno; + } + } + + return 0; +} + +int capability_ambient_set_apply(uint64_t set, bool also_inherit) { + unsigned long i; + _cleanup_cap_free_ cap_t caps = NULL; + + /* Add the capabilities to the ambient set. */ + + if (also_inherit) { + int r; + caps = cap_get_proc(); + if (!caps) + return -errno; + + r = capability_update_inherited_set(caps, set); + if (r < 0) + return -errno; + + if (cap_set_proc(caps) < 0) + return -errno; + } + + for (i = 0; i < cap_last_cap(); i++) { + + if (set & (UINT64_C(1) << i)) { + + /* Add the capability to the ambient set. */ + if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, i, 0, 0) < 0) + return -errno; + } + } + + return 0; +} + +int capability_bounding_set_drop(uint64_t keep, bool right_now) { + _cleanup_cap_free_ cap_t after_cap = NULL; + cap_flag_value_t fv; + unsigned long i; + int r; + + /* If we are run as PID 1 we will lack CAP_SETPCAP by default + * in the effective set (yes, the kernel drops that when + * executing init!), so get it back temporarily so that we can + * call PR_CAPBSET_DROP. */ + + after_cap = cap_get_proc(); + if (!after_cap) + return -errno; + + if (cap_get_flag(after_cap, CAP_SETPCAP, CAP_EFFECTIVE, &fv) < 0) + return -errno; + + if (fv != CAP_SET) { + _cleanup_cap_free_ cap_t temp_cap = NULL; + static const cap_value_t v = CAP_SETPCAP; + + temp_cap = cap_dup(after_cap); + if (!temp_cap) { + r = -errno; + goto finish; + } + + if (cap_set_flag(temp_cap, CAP_EFFECTIVE, 1, &v, CAP_SET) < 0) { + r = -errno; + goto finish; + } + + if (cap_set_proc(temp_cap) < 0) { + r = -errno; + goto finish; + } + } + + for (i = 0; i <= cap_last_cap(); i++) { + + if (!(keep & (UINT64_C(1) << i))) { + cap_value_t v; + + /* Drop it from the bounding set */ + if (prctl(PR_CAPBSET_DROP, i) < 0) { + r = -errno; + goto finish; + } + v = (cap_value_t) i; + + /* Also drop it from the inheritable set, so + * that anything we exec() loses the + * capability for good. */ + if (cap_set_flag(after_cap, CAP_INHERITABLE, 1, &v, CAP_CLEAR) < 0) { + r = -errno; + goto finish; + } + + /* If we shall apply this right now drop it + * also from our own capability sets. */ + if (right_now) { + if (cap_set_flag(after_cap, CAP_PERMITTED, 1, &v, CAP_CLEAR) < 0 || + cap_set_flag(after_cap, CAP_EFFECTIVE, 1, &v, CAP_CLEAR) < 0) { + r = -errno; + goto finish; + } + } + } + } + + r = 0; + +finish: + if (cap_set_proc(after_cap) < 0) + return -errno; + + return r; +} + +static int drop_from_file(const char *fn, uint64_t keep) { + int r, k; + uint32_t hi, lo; + uint64_t current, after; + char *p; + + r = read_one_line_file(fn, &p); + if (r < 0) + return r; + + assert_cc(sizeof(hi) == sizeof(unsigned)); + assert_cc(sizeof(lo) == sizeof(unsigned)); + + k = sscanf(p, "%u %u", &lo, &hi); + free(p); + + if (k != 2) + return -EIO; + + current = (uint64_t) lo | ((uint64_t) hi << 32ULL); + after = current & keep; + + if (current == after) + return 0; + + lo = (unsigned) (after & 0xFFFFFFFFULL); + hi = (unsigned) ((after >> 32ULL) & 0xFFFFFFFFULL); + + if (asprintf(&p, "%u %u", lo, hi) < 0) + return -ENOMEM; + + r = write_string_file(fn, p, WRITE_STRING_FILE_CREATE); + free(p); + + return r; +} + +int capability_bounding_set_drop_usermode(uint64_t keep) { + int r; + + r = drop_from_file("/proc/sys/kernel/usermodehelper/inheritable", keep); + if (r < 0) + return r; + + r = drop_from_file("/proc/sys/kernel/usermodehelper/bset", keep); + if (r < 0) + return r; + + return r; +} + +int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities) { + _cleanup_cap_free_ cap_t d = NULL; + unsigned i, j = 0; + int r; + + /* Unfortunately we cannot leave privilege dropping to PID 1 + * here, since we want to run as user but want to keep some + * capabilities. Since file capabilities have been introduced + * this cannot be done across exec() anymore, unless our + * binary has the capability configured in the file system, + * which we want to avoid. */ + + if (setresgid(gid, gid, gid) < 0) + return log_error_errno(errno, "Failed to change group ID: %m"); + + if (setgroups(0, NULL) < 0) + return log_error_errno(errno, "Failed to drop auxiliary groups list: %m"); + + /* Ensure we keep the permitted caps across the setresuid() */ + if (prctl(PR_SET_KEEPCAPS, 1) < 0) + return log_error_errno(errno, "Failed to enable keep capabilities flag: %m"); + + r = setresuid(uid, uid, uid); + if (r < 0) + return log_error_errno(errno, "Failed to change user ID: %m"); + + if (prctl(PR_SET_KEEPCAPS, 0) < 0) + return log_error_errno(errno, "Failed to disable keep capabilities flag: %m"); + + /* Drop all caps from the bounding set, except the ones we want */ + r = capability_bounding_set_drop(keep_capabilities, true); + if (r < 0) + return log_error_errno(r, "Failed to drop capabilities: %m"); + + /* Now upgrade the permitted caps we still kept to effective caps */ + d = cap_init(); + if (!d) + return log_oom(); + + if (keep_capabilities) { + cap_value_t bits[u64log2(keep_capabilities) + 1]; + + for (i = 0; i < ELEMENTSOF(bits); i++) + if (keep_capabilities & (1ULL << i)) + bits[j++] = i; + + /* use enough bits */ + assert(i == 64 || (keep_capabilities >> i) == 0); + /* don't use too many bits */ + assert(keep_capabilities & (1ULL << (i - 1))); + + if (cap_set_flag(d, CAP_EFFECTIVE, j, bits, CAP_SET) < 0 || + cap_set_flag(d, CAP_PERMITTED, j, bits, CAP_SET) < 0) + return log_error_errno(errno, "Failed to enable capabilities bits: %m"); + + if (cap_set_proc(d) < 0) + return log_error_errno(errno, "Failed to increase capabilities: %m"); + } + + return 0; +} + +int drop_capability(cap_value_t cv) { + _cleanup_cap_free_ cap_t tmp_cap = NULL; + + tmp_cap = cap_get_proc(); + if (!tmp_cap) + return -errno; + + if ((cap_set_flag(tmp_cap, CAP_INHERITABLE, 1, &cv, CAP_CLEAR) < 0) || + (cap_set_flag(tmp_cap, CAP_PERMITTED, 1, &cv, CAP_CLEAR) < 0) || + (cap_set_flag(tmp_cap, CAP_EFFECTIVE, 1, &cv, CAP_CLEAR) < 0)) + return -errno; + + if (cap_set_proc(tmp_cap) < 0) + return -errno; + + return 0; +} diff --git a/src/libbasic/capability-util.h b/src/libbasic/capability-util.h new file mode 100644 index 0000000000..35a896e229 --- /dev/null +++ b/src/libbasic/capability-util.h @@ -0,0 +1,57 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include + +#include "macro.h" +#include "util.h" + +#define CAP_ALL (uint64_t) -1 + +unsigned long cap_last_cap(void); +int have_effective_cap(int value); +int capability_bounding_set_drop(uint64_t keep, bool right_now); +int capability_bounding_set_drop_usermode(uint64_t keep); + +int capability_ambient_set_apply(uint64_t set, bool also_inherit); +int capability_update_inherited_set(cap_t caps, uint64_t ambient_set); + +int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities); + +int drop_capability(cap_value_t cv); + +DEFINE_TRIVIAL_CLEANUP_FUNC(cap_t, cap_free); +#define _cleanup_cap_free_ _cleanup_(cap_freep) + +static inline void cap_free_charpp(char **p) { + if (*p) + cap_free(*p); +} +#define _cleanup_cap_free_charp_ _cleanup_(cap_free_charpp) + +static inline bool cap_test_all(uint64_t caps) { + uint64_t m; + m = (UINT64_C(1) << (cap_last_cap() + 1)) - 1; + return (caps & m) == m; +} diff --git a/src/libbasic/cgroup-util.c b/src/libbasic/cgroup-util.c new file mode 100644 index 0000000000..7cdc97ee3c --- /dev/null +++ b/src/libbasic/cgroup-util.c @@ -0,0 +1,2338 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "cgroup-util.h" +#include "def.h" +#include "dirent-util.h" +#include "extract-word.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "log.h" +#include "login-util.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "parse-util.h" +#include "path-util.h" +#include "proc-cmdline.h" +#include "process-util.h" +#include "set.h" +#include "special.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" +#include "unit-name.h" +#include "user-util.h" + +int cg_enumerate_processes(const char *controller, const char *path, FILE **_f) { + _cleanup_free_ char *fs = NULL; + FILE *f; + int r; + + assert(_f); + + r = cg_get_path(controller, path, "cgroup.procs", &fs); + if (r < 0) + return r; + + f = fopen(fs, "re"); + if (!f) + return -errno; + + *_f = f; + return 0; +} + +int cg_read_pid(FILE *f, pid_t *_pid) { + unsigned long ul; + + /* Note that the cgroup.procs might contain duplicates! See + * cgroups.txt for details. */ + + assert(f); + assert(_pid); + + errno = 0; + if (fscanf(f, "%lu", &ul) != 1) { + + if (feof(f)) + return 0; + + return errno > 0 ? -errno : -EIO; + } + + if (ul <= 0) + return -EIO; + + *_pid = (pid_t) ul; + return 1; +} + +int cg_read_event(const char *controller, const char *path, const char *event, + char **val) +{ + _cleanup_free_ char *events = NULL, *content = NULL; + char *p, *line; + int r; + + r = cg_get_path(controller, path, "cgroup.events", &events); + if (r < 0) + return r; + + r = read_full_file(events, &content, NULL); + if (r < 0) + return r; + + p = content; + while ((line = strsep(&p, "\n"))) { + char *key; + + key = strsep(&line, " "); + if (!key || !line) + return -EINVAL; + + if (strcmp(key, event)) + continue; + + *val = strdup(line); + return 0; + } + + return -ENOENT; +} + +int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d) { + _cleanup_free_ char *fs = NULL; + int r; + DIR *d; + + assert(_d); + + /* This is not recursive! */ + + r = cg_get_path(controller, path, NULL, &fs); + if (r < 0) + return r; + + d = opendir(fs); + if (!d) + return -errno; + + *_d = d; + return 0; +} + +int cg_read_subgroup(DIR *d, char **fn) { + struct dirent *de; + + assert(d); + assert(fn); + + FOREACH_DIRENT_ALL(de, d, return -errno) { + char *b; + + if (de->d_type != DT_DIR) + continue; + + if (streq(de->d_name, ".") || + streq(de->d_name, "..")) + continue; + + b = strdup(de->d_name); + if (!b) + return -ENOMEM; + + *fn = b; + return 1; + } + + return 0; +} + +int cg_rmdir(const char *controller, const char *path) { + _cleanup_free_ char *p = NULL; + int r; + + r = cg_get_path(controller, path, NULL, &p); + if (r < 0) + return r; + + r = rmdir(p); + if (r < 0 && errno != ENOENT) + return -errno; + + return 0; +} + +int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s) { + _cleanup_set_free_ Set *allocated_set = NULL; + bool done = false; + int r, ret = 0; + pid_t my_pid; + + assert(sig >= 0); + + /* This goes through the tasks list and kills them all. This + * is repeated until no further processes are added to the + * tasks list, to properly handle forking processes */ + + if (!s) { + s = allocated_set = set_new(NULL); + if (!s) + return -ENOMEM; + } + + my_pid = getpid(); + + do { + _cleanup_fclose_ FILE *f = NULL; + pid_t pid = 0; + done = true; + + r = cg_enumerate_processes(controller, path, &f); + if (r < 0) { + if (ret >= 0 && r != -ENOENT) + return r; + + return ret; + } + + while ((r = cg_read_pid(f, &pid)) > 0) { + + if (ignore_self && pid == my_pid) + continue; + + if (set_get(s, PID_TO_PTR(pid)) == PID_TO_PTR(pid)) + continue; + + /* If we haven't killed this process yet, kill + * it */ + if (kill(pid, sig) < 0) { + if (ret >= 0 && errno != ESRCH) + ret = -errno; + } else { + if (sigcont && sig != SIGKILL) + (void) kill(pid, SIGCONT); + + if (ret == 0) + ret = 1; + } + + done = false; + + r = set_put(s, PID_TO_PTR(pid)); + if (r < 0) { + if (ret >= 0) + return r; + + return ret; + } + } + + if (r < 0) { + if (ret >= 0) + return r; + + return ret; + } + + /* To avoid racing against processes which fork + * quicker than we can kill them we repeat this until + * no new pids need to be killed. */ + + } while (!done); + + return ret; +} + +int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool rem, Set *s) { + _cleanup_set_free_ Set *allocated_set = NULL; + _cleanup_closedir_ DIR *d = NULL; + int r, ret; + char *fn; + + assert(path); + assert(sig >= 0); + + if (!s) { + s = allocated_set = set_new(NULL); + if (!s) + return -ENOMEM; + } + + ret = cg_kill(controller, path, sig, sigcont, ignore_self, s); + + r = cg_enumerate_subgroups(controller, path, &d); + if (r < 0) { + if (ret >= 0 && r != -ENOENT) + return r; + + return ret; + } + + while ((r = cg_read_subgroup(d, &fn)) > 0) { + _cleanup_free_ char *p = NULL; + + p = strjoin(path, "/", fn, NULL); + free(fn); + if (!p) + return -ENOMEM; + + r = cg_kill_recursive(controller, p, sig, sigcont, ignore_self, rem, s); + if (r != 0 && ret >= 0) + ret = r; + } + + if (ret >= 0 && r < 0) + ret = r; + + if (rem) { + r = cg_rmdir(controller, path); + if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY) + return r; + } + + return ret; +} + +int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self) { + bool done = false; + _cleanup_set_free_ Set *s = NULL; + int r, ret = 0; + pid_t my_pid; + + assert(cfrom); + assert(pfrom); + assert(cto); + assert(pto); + + s = set_new(NULL); + if (!s) + return -ENOMEM; + + my_pid = getpid(); + + do { + _cleanup_fclose_ FILE *f = NULL; + pid_t pid = 0; + done = true; + + r = cg_enumerate_processes(cfrom, pfrom, &f); + if (r < 0) { + if (ret >= 0 && r != -ENOENT) + return r; + + return ret; + } + + while ((r = cg_read_pid(f, &pid)) > 0) { + + /* This might do weird stuff if we aren't a + * single-threaded program. However, we + * luckily know we are not */ + if (ignore_self && pid == my_pid) + continue; + + if (set_get(s, PID_TO_PTR(pid)) == PID_TO_PTR(pid)) + continue; + + /* Ignore kernel threads. Since they can only + * exist in the root cgroup, we only check for + * them there. */ + if (cfrom && + (isempty(pfrom) || path_equal(pfrom, "/")) && + is_kernel_thread(pid) > 0) + continue; + + r = cg_attach(cto, pto, pid); + if (r < 0) { + if (ret >= 0 && r != -ESRCH) + ret = r; + } else if (ret == 0) + ret = 1; + + done = false; + + r = set_put(s, PID_TO_PTR(pid)); + if (r < 0) { + if (ret >= 0) + return r; + + return ret; + } + } + + if (r < 0) { + if (ret >= 0) + return r; + + return ret; + } + } while (!done); + + return ret; +} + +int cg_migrate_recursive( + const char *cfrom, + const char *pfrom, + const char *cto, + const char *pto, + bool ignore_self, + bool rem) { + + _cleanup_closedir_ DIR *d = NULL; + int r, ret = 0; + char *fn; + + assert(cfrom); + assert(pfrom); + assert(cto); + assert(pto); + + ret = cg_migrate(cfrom, pfrom, cto, pto, ignore_self); + + r = cg_enumerate_subgroups(cfrom, pfrom, &d); + if (r < 0) { + if (ret >= 0 && r != -ENOENT) + return r; + + return ret; + } + + while ((r = cg_read_subgroup(d, &fn)) > 0) { + _cleanup_free_ char *p = NULL; + + p = strjoin(pfrom, "/", fn, NULL); + free(fn); + if (!p) + return -ENOMEM; + + r = cg_migrate_recursive(cfrom, p, cto, pto, ignore_self, rem); + if (r != 0 && ret >= 0) + ret = r; + } + + if (r < 0 && ret >= 0) + ret = r; + + if (rem) { + r = cg_rmdir(cfrom, pfrom); + if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY) + return r; + } + + return ret; +} + +int cg_migrate_recursive_fallback( + const char *cfrom, + const char *pfrom, + const char *cto, + const char *pto, + bool ignore_self, + bool rem) { + + int r; + + assert(cfrom); + assert(pfrom); + assert(cto); + assert(pto); + + r = cg_migrate_recursive(cfrom, pfrom, cto, pto, ignore_self, rem); + if (r < 0) { + char prefix[strlen(pto) + 1]; + + /* This didn't work? Then let's try all prefixes of the destination */ + + PATH_FOREACH_PREFIX(prefix, pto) { + int q; + + q = cg_migrate_recursive(cfrom, pfrom, cto, prefix, ignore_self, rem); + if (q >= 0) + return q; + } + } + + return r; +} + +static const char *controller_to_dirname(const char *controller) { + const char *e; + + assert(controller); + + /* Converts a controller name to the directory name below + * /sys/fs/cgroup/ we want to mount it to. Effectively, this + * just cuts off the name= prefixed used for named + * hierarchies, if it is specified. */ + + e = startswith(controller, "name="); + if (e) + return e; + + return controller; +} + +static int join_path_legacy(const char *controller, const char *path, const char *suffix, char **fs) { + const char *dn; + char *t = NULL; + + assert(fs); + assert(controller); + + dn = controller_to_dirname(controller); + + if (isempty(path) && isempty(suffix)) + t = strappend("/sys/fs/cgroup/", dn); + else if (isempty(path)) + t = strjoin("/sys/fs/cgroup/", dn, "/", suffix, NULL); + else if (isempty(suffix)) + t = strjoin("/sys/fs/cgroup/", dn, "/", path, NULL); + else + t = strjoin("/sys/fs/cgroup/", dn, "/", path, "/", suffix, NULL); + if (!t) + return -ENOMEM; + + *fs = t; + return 0; +} + +static int join_path_unified(const char *path, const char *suffix, char **fs) { + char *t; + + assert(fs); + + if (isempty(path) && isempty(suffix)) + t = strdup("/sys/fs/cgroup"); + else if (isempty(path)) + t = strappend("/sys/fs/cgroup/", suffix); + else if (isempty(suffix)) + t = strappend("/sys/fs/cgroup/", path); + else + t = strjoin("/sys/fs/cgroup/", path, "/", suffix, NULL); + if (!t) + return -ENOMEM; + + *fs = t; + return 0; +} + +int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs) { + int unified, r; + + assert(fs); + + if (!controller) { + char *t; + + /* If no controller is specified, we return the path + * *below* the controllers, without any prefix. */ + + if (!path && !suffix) + return -EINVAL; + + if (!suffix) + t = strdup(path); + else if (!path) + t = strdup(suffix); + else + t = strjoin(path, "/", suffix, NULL); + if (!t) + return -ENOMEM; + + *fs = path_kill_slashes(t); + return 0; + } + + if (!cg_controller_is_valid(controller)) + return -EINVAL; + + unified = cg_unified(); + if (unified < 0) + return unified; + + if (unified > 0) + r = join_path_unified(path, suffix, fs); + else + r = join_path_legacy(controller, path, suffix, fs); + if (r < 0) + return r; + + path_kill_slashes(*fs); + return 0; +} + +static int controller_is_accessible(const char *controller) { + int unified; + + assert(controller); + + /* Checks whether a specific controller is accessible, + * i.e. its hierarchy mounted. In the unified hierarchy all + * controllers are considered accessible, except for the named + * hierarchies */ + + if (!cg_controller_is_valid(controller)) + return -EINVAL; + + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified > 0) { + /* We don't support named hierarchies if we are using + * the unified hierarchy. */ + + if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) + return 0; + + if (startswith(controller, "name=")) + return -EOPNOTSUPP; + + } else { + const char *cc, *dn; + + dn = controller_to_dirname(controller); + cc = strjoina("/sys/fs/cgroup/", dn); + + if (laccess(cc, F_OK) < 0) + return -errno; + } + + return 0; +} + +int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs) { + int r; + + assert(controller); + assert(fs); + + /* Check if the specified controller is actually accessible */ + r = controller_is_accessible(controller); + if (r < 0) + return r; + + return cg_get_path(controller, path, suffix, fs); +} + +static int trim_cb(const char *path, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { + assert(path); + assert(sb); + assert(ftwbuf); + + if (typeflag != FTW_DP) + return 0; + + if (ftwbuf->level < 1) + return 0; + + (void) rmdir(path); + return 0; +} + +int cg_trim(const char *controller, const char *path, bool delete_root) { + _cleanup_free_ char *fs = NULL; + int r = 0; + + assert(path); + + r = cg_get_path(controller, path, NULL, &fs); + if (r < 0) + return r; + + errno = 0; + if (nftw(fs, trim_cb, 64, FTW_DEPTH|FTW_MOUNT|FTW_PHYS) != 0) { + if (errno == ENOENT) + r = 0; + else if (errno > 0) + r = -errno; + else + r = -EIO; + } + + if (delete_root) { + if (rmdir(fs) < 0 && errno != ENOENT) + return -errno; + } + + return r; +} + +int cg_create(const char *controller, const char *path) { + _cleanup_free_ char *fs = NULL; + int r; + + r = cg_get_path_and_check(controller, path, NULL, &fs); + if (r < 0) + return r; + + r = mkdir_parents(fs, 0755); + if (r < 0) + return r; + + if (mkdir(fs, 0755) < 0) { + + if (errno == EEXIST) + return 0; + + return -errno; + } + + return 1; +} + +int cg_create_and_attach(const char *controller, const char *path, pid_t pid) { + int r, q; + + assert(pid >= 0); + + r = cg_create(controller, path); + if (r < 0) + return r; + + q = cg_attach(controller, path, pid); + if (q < 0) + return q; + + /* This does not remove the cgroup on failure */ + return r; +} + +int cg_attach(const char *controller, const char *path, pid_t pid) { + _cleanup_free_ char *fs = NULL; + char c[DECIMAL_STR_MAX(pid_t) + 2]; + int r; + + assert(path); + assert(pid >= 0); + + r = cg_get_path_and_check(controller, path, "cgroup.procs", &fs); + if (r < 0) + return r; + + if (pid == 0) + pid = getpid(); + + xsprintf(c, PID_FMT "\n", pid); + + return write_string_file(fs, c, 0); +} + +int cg_attach_fallback(const char *controller, const char *path, pid_t pid) { + int r; + + assert(controller); + assert(path); + assert(pid >= 0); + + r = cg_attach(controller, path, pid); + if (r < 0) { + char prefix[strlen(path) + 1]; + + /* This didn't work? Then let's try all prefixes of + * the destination */ + + PATH_FOREACH_PREFIX(prefix, path) { + int q; + + q = cg_attach(controller, prefix, pid); + if (q >= 0) + return q; + } + } + + return r; +} + +int cg_set_group_access( + const char *controller, + const char *path, + mode_t mode, + uid_t uid, + gid_t gid) { + + _cleanup_free_ char *fs = NULL; + int r; + + if (mode == MODE_INVALID && uid == UID_INVALID && gid == GID_INVALID) + return 0; + + if (mode != MODE_INVALID) + mode &= 0777; + + r = cg_get_path(controller, path, NULL, &fs); + if (r < 0) + return r; + + return chmod_and_chown(fs, mode, uid, gid); +} + +int cg_set_task_access( + const char *controller, + const char *path, + mode_t mode, + uid_t uid, + gid_t gid) { + + _cleanup_free_ char *fs = NULL, *procs = NULL; + int r, unified; + + assert(path); + + if (mode == MODE_INVALID && uid == UID_INVALID && gid == GID_INVALID) + return 0; + + if (mode != MODE_INVALID) + mode &= 0666; + + r = cg_get_path(controller, path, "cgroup.procs", &fs); + if (r < 0) + return r; + + r = chmod_and_chown(fs, mode, uid, gid); + if (r < 0) + return r; + + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified) + return 0; + + /* Compatibility, Always keep values for "tasks" in sync with + * "cgroup.procs" */ + if (cg_get_path(controller, path, "tasks", &procs) >= 0) + (void) chmod_and_chown(procs, mode, uid, gid); + + return 0; +} + +int cg_pid_get_path(const char *controller, pid_t pid, char **path) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + const char *fs; + size_t cs = 0; + int unified; + + assert(path); + assert(pid >= 0); + + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified == 0) { + if (controller) { + if (!cg_controller_is_valid(controller)) + return -EINVAL; + } else + controller = SYSTEMD_CGROUP_CONTROLLER; + + cs = strlen(controller); + } + + fs = procfs_file_alloca(pid, "cgroup"); + f = fopen(fs, "re"); + if (!f) + return errno == ENOENT ? -ESRCH : -errno; + + FOREACH_LINE(line, f, return -errno) { + char *e, *p; + + truncate_nl(line); + + if (unified) { + e = startswith(line, "0:"); + if (!e) + continue; + + e = strchr(e, ':'); + if (!e) + continue; + } else { + char *l; + size_t k; + const char *word, *state; + bool found = false; + + l = strchr(line, ':'); + if (!l) + continue; + + l++; + e = strchr(l, ':'); + if (!e) + continue; + + *e = 0; + FOREACH_WORD_SEPARATOR(word, k, l, ",", state) { + if (k == cs && memcmp(word, controller, cs) == 0) { + found = true; + break; + } + } + + if (!found) + continue; + } + + p = strdup(e + 1); + if (!p) + return -ENOMEM; + + *path = p; + return 0; + } + + return -ENODATA; +} + +int cg_install_release_agent(const char *controller, const char *agent) { + _cleanup_free_ char *fs = NULL, *contents = NULL; + const char *sc; + int r, unified; + + assert(agent); + + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified) /* doesn't apply to unified hierarchy */ + return -EOPNOTSUPP; + + r = cg_get_path(controller, NULL, "release_agent", &fs); + if (r < 0) + return r; + + r = read_one_line_file(fs, &contents); + if (r < 0) + return r; + + sc = strstrip(contents); + if (isempty(sc)) { + r = write_string_file(fs, agent, 0); + if (r < 0) + return r; + } else if (!path_equal(sc, agent)) + return -EEXIST; + + fs = mfree(fs); + r = cg_get_path(controller, NULL, "notify_on_release", &fs); + if (r < 0) + return r; + + contents = mfree(contents); + r = read_one_line_file(fs, &contents); + if (r < 0) + return r; + + sc = strstrip(contents); + if (streq(sc, "0")) { + r = write_string_file(fs, "1", 0); + if (r < 0) + return r; + + return 1; + } + + if (!streq(sc, "1")) + return -EIO; + + return 0; +} + +int cg_uninstall_release_agent(const char *controller) { + _cleanup_free_ char *fs = NULL; + int r, unified; + + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified) /* Doesn't apply to unified hierarchy */ + return -EOPNOTSUPP; + + r = cg_get_path(controller, NULL, "notify_on_release", &fs); + if (r < 0) + return r; + + r = write_string_file(fs, "0", 0); + if (r < 0) + return r; + + fs = mfree(fs); + + r = cg_get_path(controller, NULL, "release_agent", &fs); + if (r < 0) + return r; + + r = write_string_file(fs, "", 0); + if (r < 0) + return r; + + return 0; +} + +int cg_is_empty(const char *controller, const char *path) { + _cleanup_fclose_ FILE *f = NULL; + pid_t pid; + int r; + + assert(path); + + r = cg_enumerate_processes(controller, path, &f); + if (r == -ENOENT) + return 1; + if (r < 0) + return r; + + r = cg_read_pid(f, &pid); + if (r < 0) + return r; + + return r == 0; +} + +int cg_is_empty_recursive(const char *controller, const char *path) { + int unified, r; + + assert(path); + + /* The root cgroup is always populated */ + if (controller && (isempty(path) || path_equal(path, "/"))) + return false; + + unified = cg_unified(); + if (unified < 0) + return unified; + + if (unified > 0) { + _cleanup_free_ char *t = NULL; + + /* On the unified hierarchy we can check empty state + * via the "populated" attribute of "cgroup.events". */ + + r = cg_read_event(controller, path, "populated", &t); + if (r < 0) + return r; + + return streq(t, "0"); + } else { + _cleanup_closedir_ DIR *d = NULL; + char *fn; + + r = cg_is_empty(controller, path); + if (r <= 0) + return r; + + r = cg_enumerate_subgroups(controller, path, &d); + if (r == -ENOENT) + return 1; + if (r < 0) + return r; + + while ((r = cg_read_subgroup(d, &fn)) > 0) { + _cleanup_free_ char *p = NULL; + + p = strjoin(path, "/", fn, NULL); + free(fn); + if (!p) + return -ENOMEM; + + r = cg_is_empty_recursive(controller, p); + if (r <= 0) + return r; + } + if (r < 0) + return r; + + return true; + } +} + +int cg_split_spec(const char *spec, char **controller, char **path) { + char *t = NULL, *u = NULL; + const char *e; + + assert(spec); + + if (*spec == '/') { + if (!path_is_safe(spec)) + return -EINVAL; + + if (path) { + t = strdup(spec); + if (!t) + return -ENOMEM; + + *path = path_kill_slashes(t); + } + + if (controller) + *controller = NULL; + + return 0; + } + + e = strchr(spec, ':'); + if (!e) { + if (!cg_controller_is_valid(spec)) + return -EINVAL; + + if (controller) { + t = strdup(spec); + if (!t) + return -ENOMEM; + + *controller = t; + } + + if (path) + *path = NULL; + + return 0; + } + + t = strndup(spec, e-spec); + if (!t) + return -ENOMEM; + if (!cg_controller_is_valid(t)) { + free(t); + return -EINVAL; + } + + if (isempty(e+1)) + u = NULL; + else { + u = strdup(e+1); + if (!u) { + free(t); + return -ENOMEM; + } + + if (!path_is_safe(u) || + !path_is_absolute(u)) { + free(t); + free(u); + return -EINVAL; + } + + path_kill_slashes(u); + } + + if (controller) + *controller = t; + else + free(t); + + if (path) + *path = u; + else + free(u); + + return 0; +} + +int cg_mangle_path(const char *path, char **result) { + _cleanup_free_ char *c = NULL, *p = NULL; + char *t; + int r; + + assert(path); + assert(result); + + /* First, check if it already is a filesystem path */ + if (path_startswith(path, "/sys/fs/cgroup")) { + + t = strdup(path); + if (!t) + return -ENOMEM; + + *result = path_kill_slashes(t); + return 0; + } + + /* Otherwise, treat it as cg spec */ + r = cg_split_spec(path, &c, &p); + if (r < 0) + return r; + + return cg_get_path(c ?: SYSTEMD_CGROUP_CONTROLLER, p ?: "/", NULL, result); +} + +int cg_get_root_path(char **path) { + char *p, *e; + int r; + + assert(path); + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &p); + if (r < 0) + return r; + + e = endswith(p, "/" SPECIAL_INIT_SCOPE); + if (!e) + e = endswith(p, "/" SPECIAL_SYSTEM_SLICE); /* legacy */ + if (!e) + e = endswith(p, "/system"); /* even more legacy */ + if (e) + *e = 0; + + *path = p; + return 0; +} + +int cg_shift_path(const char *cgroup, const char *root, const char **shifted) { + _cleanup_free_ char *rt = NULL; + char *p; + int r; + + assert(cgroup); + assert(shifted); + + if (!root) { + /* If the root was specified let's use that, otherwise + * let's determine it from PID 1 */ + + r = cg_get_root_path(&rt); + if (r < 0) + return r; + + root = rt; + } + + p = path_startswith(cgroup, root); + if (p && p > cgroup) + *shifted = p - 1; + else + *shifted = cgroup; + + return 0; +} + +int cg_pid_get_path_shifted(pid_t pid, const char *root, char **cgroup) { + _cleanup_free_ char *raw = NULL; + const char *c; + int r; + + assert(pid >= 0); + assert(cgroup); + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &raw); + if (r < 0) + return r; + + r = cg_shift_path(raw, root, &c); + if (r < 0) + return r; + + if (c == raw) { + *cgroup = raw; + raw = NULL; + } else { + char *n; + + n = strdup(c); + if (!n) + return -ENOMEM; + + *cgroup = n; + } + + return 0; +} + +int cg_path_decode_unit(const char *cgroup, char **unit) { + char *c, *s; + size_t n; + + assert(cgroup); + assert(unit); + + n = strcspn(cgroup, "/"); + if (n < 3) + return -ENXIO; + + c = strndupa(cgroup, n); + c = cg_unescape(c); + + if (!unit_name_is_valid(c, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) + return -ENXIO; + + s = strdup(c); + if (!s) + return -ENOMEM; + + *unit = s; + return 0; +} + +static bool valid_slice_name(const char *p, size_t n) { + + if (!p) + return false; + + if (n < strlen("x.slice")) + return false; + + if (memcmp(p + n - 6, ".slice", 6) == 0) { + char buf[n+1], *c; + + memcpy(buf, p, n); + buf[n] = 0; + + c = cg_unescape(buf); + + return unit_name_is_valid(c, UNIT_NAME_PLAIN); + } + + return false; +} + +static const char *skip_slices(const char *p) { + assert(p); + + /* Skips over all slice assignments */ + + for (;;) { + size_t n; + + p += strspn(p, "/"); + + n = strcspn(p, "/"); + if (!valid_slice_name(p, n)) + return p; + + p += n; + } +} + +int cg_path_get_unit(const char *path, char **ret) { + const char *e; + char *unit; + int r; + + assert(path); + assert(ret); + + e = skip_slices(path); + + r = cg_path_decode_unit(e, &unit); + if (r < 0) + return r; + + /* We skipped over the slices, don't accept any now */ + if (endswith(unit, ".slice")) { + free(unit); + return -ENXIO; + } + + *ret = unit; + return 0; +} + +int cg_pid_get_unit(pid_t pid, char **unit) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(unit); + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_unit(cgroup, unit); +} + +/** + * Skip session-*.scope, but require it to be there. + */ +static const char *skip_session(const char *p) { + size_t n; + + if (isempty(p)) + return NULL; + + p += strspn(p, "/"); + + n = strcspn(p, "/"); + if (n < strlen("session-x.scope")) + return NULL; + + if (memcmp(p, "session-", 8) == 0 && memcmp(p + n - 6, ".scope", 6) == 0) { + char buf[n - 8 - 6 + 1]; + + memcpy(buf, p + 8, n - 8 - 6); + buf[n - 8 - 6] = 0; + + /* Note that session scopes never need unescaping, + * since they cannot conflict with the kernel's own + * names, hence we don't need to call cg_unescape() + * here. */ + + if (!session_id_valid(buf)) + return false; + + p += n; + p += strspn(p, "/"); + return p; + } + + return NULL; +} + +/** + * Skip user@*.service, but require it to be there. + */ +static const char *skip_user_manager(const char *p) { + size_t n; + + if (isempty(p)) + return NULL; + + p += strspn(p, "/"); + + n = strcspn(p, "/"); + if (n < strlen("user@x.service")) + return NULL; + + if (memcmp(p, "user@", 5) == 0 && memcmp(p + n - 8, ".service", 8) == 0) { + char buf[n - 5 - 8 + 1]; + + memcpy(buf, p + 5, n - 5 - 8); + buf[n - 5 - 8] = 0; + + /* Note that user manager services never need unescaping, + * since they cannot conflict with the kernel's own + * names, hence we don't need to call cg_unescape() + * here. */ + + if (parse_uid(buf, NULL) < 0) + return NULL; + + p += n; + p += strspn(p, "/"); + + return p; + } + + return NULL; +} + +static const char *skip_user_prefix(const char *path) { + const char *e, *t; + + assert(path); + + /* Skip slices, if there are any */ + e = skip_slices(path); + + /* Skip the user manager, if it's in the path now... */ + t = skip_user_manager(e); + if (t) + return t; + + /* Alternatively skip the user session if it is in the path... */ + return skip_session(e); +} + +int cg_path_get_user_unit(const char *path, char **ret) { + const char *t; + + assert(path); + assert(ret); + + t = skip_user_prefix(path); + if (!t) + return -ENXIO; + + /* And from here on it looks pretty much the same as for a + * system unit, hence let's use the same parser from here + * on. */ + return cg_path_get_unit(t, ret); +} + +int cg_pid_get_user_unit(pid_t pid, char **unit) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(unit); + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_user_unit(cgroup, unit); +} + +int cg_path_get_machine_name(const char *path, char **machine) { + _cleanup_free_ char *u = NULL; + const char *sl; + int r; + + r = cg_path_get_unit(path, &u); + if (r < 0) + return r; + + sl = strjoina("/run/systemd/machines/unit:", u); + return readlink_malloc(sl, machine); +} + +int cg_pid_get_machine_name(pid_t pid, char **machine) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(machine); + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_machine_name(cgroup, machine); +} + +int cg_path_get_session(const char *path, char **session) { + _cleanup_free_ char *unit = NULL; + char *start, *end; + int r; + + assert(path); + + r = cg_path_get_unit(path, &unit); + if (r < 0) + return r; + + start = startswith(unit, "session-"); + if (!start) + return -ENXIO; + end = endswith(start, ".scope"); + if (!end) + return -ENXIO; + + *end = 0; + if (!session_id_valid(start)) + return -ENXIO; + + if (session) { + char *rr; + + rr = strdup(start); + if (!rr) + return -ENOMEM; + + *session = rr; + } + + return 0; +} + +int cg_pid_get_session(pid_t pid, char **session) { + _cleanup_free_ char *cgroup = NULL; + int r; + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_session(cgroup, session); +} + +int cg_path_get_owner_uid(const char *path, uid_t *uid) { + _cleanup_free_ char *slice = NULL; + char *start, *end; + int r; + + assert(path); + + r = cg_path_get_slice(path, &slice); + if (r < 0) + return r; + + start = startswith(slice, "user-"); + if (!start) + return -ENXIO; + end = endswith(start, ".slice"); + if (!end) + return -ENXIO; + + *end = 0; + if (parse_uid(start, uid) < 0) + return -ENXIO; + + return 0; +} + +int cg_pid_get_owner_uid(pid_t pid, uid_t *uid) { + _cleanup_free_ char *cgroup = NULL; + int r; + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_owner_uid(cgroup, uid); +} + +int cg_path_get_slice(const char *p, char **slice) { + const char *e = NULL; + + assert(p); + assert(slice); + + /* Finds the right-most slice unit from the beginning, but + * stops before we come to the first non-slice unit. */ + + for (;;) { + size_t n; + + p += strspn(p, "/"); + + n = strcspn(p, "/"); + if (!valid_slice_name(p, n)) { + + if (!e) { + char *s; + + s = strdup("-.slice"); + if (!s) + return -ENOMEM; + + *slice = s; + return 0; + } + + return cg_path_decode_unit(e, slice); + } + + e = p; + p += n; + } +} + +int cg_pid_get_slice(pid_t pid, char **slice) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(slice); + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_slice(cgroup, slice); +} + +int cg_path_get_user_slice(const char *p, char **slice) { + const char *t; + assert(p); + assert(slice); + + t = skip_user_prefix(p); + if (!t) + return -ENXIO; + + /* And now it looks pretty much the same as for a system + * slice, so let's just use the same parser from here on. */ + return cg_path_get_slice(t, slice); +} + +int cg_pid_get_user_slice(pid_t pid, char **slice) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(slice); + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_user_slice(cgroup, slice); +} + +char *cg_escape(const char *p) { + bool need_prefix = false; + + /* This implements very minimal escaping for names to be used + * as file names in the cgroup tree: any name which might + * conflict with a kernel name or is prefixed with '_' is + * prefixed with a '_'. That way, when reading cgroup names it + * is sufficient to remove a single prefixing underscore if + * there is one. */ + + /* The return value of this function (unlike cg_unescape()) + * needs free()! */ + + if (p[0] == 0 || + p[0] == '_' || + p[0] == '.' || + streq(p, "notify_on_release") || + streq(p, "release_agent") || + streq(p, "tasks") || + startswith(p, "cgroup.")) + need_prefix = true; + else { + const char *dot; + + dot = strrchr(p, '.'); + if (dot) { + CGroupController c; + size_t l = dot - p; + + for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { + const char *n; + + n = cgroup_controller_to_string(c); + + if (l != strlen(n)) + continue; + + if (memcmp(p, n, l) != 0) + continue; + + need_prefix = true; + break; + } + } + } + + if (need_prefix) + return strappend("_", p); + + return strdup(p); +} + +char *cg_unescape(const char *p) { + assert(p); + + /* The return value of this function (unlike cg_escape()) + * doesn't need free()! */ + + if (p[0] == '_') + return (char*) p+1; + + return (char*) p; +} + +#define CONTROLLER_VALID \ + DIGITS LETTERS \ + "_" + +bool cg_controller_is_valid(const char *p) { + const char *t, *s; + + if (!p) + return false; + + s = startswith(p, "name="); + if (s) + p = s; + + if (*p == 0 || *p == '_') + return false; + + for (t = p; *t; t++) + if (!strchr(CONTROLLER_VALID, *t)) + return false; + + if (t - p > FILENAME_MAX) + return false; + + return true; +} + +int cg_slice_to_path(const char *unit, char **ret) { + _cleanup_free_ char *p = NULL, *s = NULL, *e = NULL; + const char *dash; + int r; + + assert(unit); + assert(ret); + + if (streq(unit, "-.slice")) { + char *x; + + x = strdup(""); + if (!x) + return -ENOMEM; + *ret = x; + return 0; + } + + if (!unit_name_is_valid(unit, UNIT_NAME_PLAIN)) + return -EINVAL; + + if (!endswith(unit, ".slice")) + return -EINVAL; + + r = unit_name_to_prefix(unit, &p); + if (r < 0) + return r; + + dash = strchr(p, '-'); + + /* Don't allow initial dashes */ + if (dash == p) + return -EINVAL; + + while (dash) { + _cleanup_free_ char *escaped = NULL; + char n[dash - p + sizeof(".slice")]; + + /* Don't allow trailing or double dashes */ + if (dash[1] == 0 || dash[1] == '-') + return -EINVAL; + + strcpy(stpncpy(n, p, dash - p), ".slice"); + if (!unit_name_is_valid(n, UNIT_NAME_PLAIN)) + return -EINVAL; + + escaped = cg_escape(n); + if (!escaped) + return -ENOMEM; + + if (!strextend(&s, escaped, "/", NULL)) + return -ENOMEM; + + dash = strchr(dash+1, '-'); + } + + e = cg_escape(unit); + if (!e) + return -ENOMEM; + + if (!strextend(&s, e, NULL)) + return -ENOMEM; + + *ret = s; + s = NULL; + + return 0; +} + +int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value) { + _cleanup_free_ char *p = NULL; + int r; + + r = cg_get_path(controller, path, attribute, &p); + if (r < 0) + return r; + + return write_string_file(p, value, 0); +} + +int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret) { + _cleanup_free_ char *p = NULL; + int r; + + r = cg_get_path(controller, path, attribute, &p); + if (r < 0) + return r; + + return read_one_line_file(p, ret); +} + +int cg_create_everywhere(CGroupMask supported, CGroupMask mask, const char *path) { + CGroupController c; + int r, unified; + + /* This one will create a cgroup in our private tree, but also + * duplicate it in the trees specified in mask, and remove it + * in all others */ + + /* First create the cgroup in our own hierarchy. */ + r = cg_create(SYSTEMD_CGROUP_CONTROLLER, path); + if (r < 0) + return r; + + /* If we are in the unified hierarchy, we are done now */ + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified > 0) + return 0; + + /* Otherwise, do the same in the other hierarchies */ + for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { + CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); + const char *n; + + n = cgroup_controller_to_string(c); + + if (mask & bit) + (void) cg_create(n, path); + else if (supported & bit) + (void) cg_trim(n, path, true); + } + + return 0; +} + +int cg_attach_everywhere(CGroupMask supported, const char *path, pid_t pid, cg_migrate_callback_t path_callback, void *userdata) { + CGroupController c; + int r, unified; + + r = cg_attach(SYSTEMD_CGROUP_CONTROLLER, path, pid); + if (r < 0) + return r; + + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified > 0) + return 0; + + for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { + CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); + const char *p = NULL; + + if (!(supported & bit)) + continue; + + if (path_callback) + p = path_callback(bit, userdata); + + if (!p) + p = path; + + (void) cg_attach_fallback(cgroup_controller_to_string(c), p, pid); + } + + return 0; +} + +int cg_attach_many_everywhere(CGroupMask supported, const char *path, Set* pids, cg_migrate_callback_t path_callback, void *userdata) { + Iterator i; + void *pidp; + int r = 0; + + SET_FOREACH(pidp, pids, i) { + pid_t pid = PTR_TO_PID(pidp); + int q; + + q = cg_attach_everywhere(supported, path, pid, path_callback, userdata); + if (q < 0 && r >= 0) + r = q; + } + + return r; +} + +int cg_migrate_everywhere(CGroupMask supported, const char *from, const char *to, cg_migrate_callback_t to_callback, void *userdata) { + CGroupController c; + int r = 0, unified; + + if (!path_equal(from, to)) { + r = cg_migrate_recursive(SYSTEMD_CGROUP_CONTROLLER, from, SYSTEMD_CGROUP_CONTROLLER, to, false, true); + if (r < 0) + return r; + } + + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified > 0) + return r; + + for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { + CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); + const char *p = NULL; + + if (!(supported & bit)) + continue; + + if (to_callback) + p = to_callback(bit, userdata); + + if (!p) + p = to; + + (void) cg_migrate_recursive_fallback(SYSTEMD_CGROUP_CONTROLLER, to, cgroup_controller_to_string(c), p, false, false); + } + + return 0; +} + +int cg_trim_everywhere(CGroupMask supported, const char *path, bool delete_root) { + CGroupController c; + int r, unified; + + r = cg_trim(SYSTEMD_CGROUP_CONTROLLER, path, delete_root); + if (r < 0) + return r; + + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified > 0) + return r; + + for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { + CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); + + if (!(supported & bit)) + continue; + + (void) cg_trim(cgroup_controller_to_string(c), path, delete_root); + } + + return 0; +} + +int cg_mask_supported(CGroupMask *ret) { + CGroupMask mask = 0; + int r, unified; + + /* Determines the mask of supported cgroup controllers. Only + * includes controllers we can make sense of and that are + * actually accessible. */ + + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified > 0) { + _cleanup_free_ char *root = NULL, *controllers = NULL, *path = NULL; + const char *c; + + /* In the unified hierarchy we can read the supported + * and accessible controllers from a the top-level + * cgroup attribute */ + + r = cg_get_root_path(&root); + if (r < 0) + return r; + + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, root, "cgroup.controllers", &path); + if (r < 0) + return r; + + r = read_one_line_file(path, &controllers); + if (r < 0) + return r; + + c = controllers; + for (;;) { + _cleanup_free_ char *n = NULL; + CGroupController v; + + r = extract_first_word(&c, &n, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + v = cgroup_controller_from_string(n); + if (v < 0) + continue; + + mask |= CGROUP_CONTROLLER_TO_MASK(v); + } + + /* Currently, we only support the memory, io and pids + * controller in the unified hierarchy, mask + * everything else off. */ + mask &= CGROUP_MASK_MEMORY | CGROUP_MASK_IO | CGROUP_MASK_PIDS; + + } else { + CGroupController c; + + /* In the legacy hierarchy, we check whether which + * hierarchies are mounted. */ + + for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { + const char *n; + + n = cgroup_controller_to_string(c); + if (controller_is_accessible(n) >= 0) + mask |= CGROUP_CONTROLLER_TO_MASK(c); + } + } + + *ret = mask; + return 0; +} + +int cg_kernel_controllers(Set *controllers) { + _cleanup_fclose_ FILE *f = NULL; + char buf[LINE_MAX]; + int r; + + assert(controllers); + + /* Determines the full list of kernel-known controllers. Might + * include controllers we don't actually support, arbitrary + * named hierarchies and controllers that aren't currently + * accessible (because not mounted). */ + + f = fopen("/proc/cgroups", "re"); + if (!f) { + if (errno == ENOENT) + return 0; + return -errno; + } + + /* Ignore the header line */ + (void) fgets(buf, sizeof(buf), f); + + for (;;) { + char *controller; + int enabled = 0; + + errno = 0; + if (fscanf(f, "%ms %*i %*i %i", &controller, &enabled) != 2) { + + if (feof(f)) + break; + + if (ferror(f) && errno > 0) + return -errno; + + return -EBADMSG; + } + + if (!enabled) { + free(controller); + continue; + } + + if (!cg_controller_is_valid(controller)) { + free(controller); + return -EBADMSG; + } + + r = set_consume(controllers, controller); + if (r < 0) + return r; + } + + return 0; +} + +static thread_local int unified_cache = -1; + +int cg_unified(void) { + struct statfs fs; + + /* Checks if we support the unified hierarchy. Returns an + * error when the cgroup hierarchies aren't mounted yet or we + * have any other trouble determining if the unified hierarchy + * is supported. */ + + if (unified_cache >= 0) + return unified_cache; + + if (statfs("/sys/fs/cgroup/", &fs) < 0) + return -errno; + + if (F_TYPE_EQUAL(fs.f_type, CGROUP2_SUPER_MAGIC)) + unified_cache = true; + else if (F_TYPE_EQUAL(fs.f_type, TMPFS_MAGIC)) + unified_cache = false; + else + return -ENOMEDIUM; + + return unified_cache; +} + +void cg_unified_flush(void) { + unified_cache = -1; +} + +int cg_enable_everywhere(CGroupMask supported, CGroupMask mask, const char *p) { + _cleanup_free_ char *fs = NULL; + CGroupController c; + int r, unified; + + assert(p); + + if (supported == 0) + return 0; + + unified = cg_unified(); + if (unified < 0) + return unified; + if (!unified) /* on the legacy hiearchy there's no joining of controllers defined */ + return 0; + + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, p, "cgroup.subtree_control", &fs); + if (r < 0) + return r; + + for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { + CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); + const char *n; + + if (!(supported & bit)) + continue; + + n = cgroup_controller_to_string(c); + { + char s[1 + strlen(n) + 1]; + + s[0] = mask & bit ? '+' : '-'; + strcpy(s + 1, n); + + r = write_string_file(fs, s, 0); + if (r < 0) + log_debug_errno(r, "Failed to enable controller %s for %s (%s): %m", n, p, fs); + } + } + + return 0; +} + +bool cg_is_unified_wanted(void) { + static thread_local int wanted = -1; + int r, unified; + + /* If the hierarchy is already mounted, then follow whatever + * was chosen for it. */ + unified = cg_unified(); + if (unified >= 0) + return unified; + + /* Otherwise, let's see what the kernel command line has to + * say. Since checking that is expensive, let's cache the + * result. */ + if (wanted >= 0) + return wanted; + + r = get_proc_cmdline_key("systemd.unified_cgroup_hierarchy", NULL); + if (r > 0) + return (wanted = true); + else { + _cleanup_free_ char *value = NULL; + + r = get_proc_cmdline_key("systemd.unified_cgroup_hierarchy=", &value); + if (r < 0) + return false; + if (r == 0) + return (wanted = false); + + return (wanted = parse_boolean(value) > 0); + } +} + +bool cg_is_legacy_wanted(void) { + return !cg_is_unified_wanted(); +} + +int cg_weight_parse(const char *s, uint64_t *ret) { + uint64_t u; + int r; + + if (isempty(s)) { + *ret = CGROUP_WEIGHT_INVALID; + return 0; + } + + r = safe_atou64(s, &u); + if (r < 0) + return r; + + if (u < CGROUP_WEIGHT_MIN || u > CGROUP_WEIGHT_MAX) + return -ERANGE; + + *ret = u; + return 0; +} + +const uint64_t cgroup_io_limit_defaults[_CGROUP_IO_LIMIT_TYPE_MAX] = { + [CGROUP_IO_RBPS_MAX] = CGROUP_LIMIT_MAX, + [CGROUP_IO_WBPS_MAX] = CGROUP_LIMIT_MAX, + [CGROUP_IO_RIOPS_MAX] = CGROUP_LIMIT_MAX, + [CGROUP_IO_WIOPS_MAX] = CGROUP_LIMIT_MAX, +}; + +static const char* const cgroup_io_limit_type_table[_CGROUP_IO_LIMIT_TYPE_MAX] = { + [CGROUP_IO_RBPS_MAX] = "IOReadBandwidthMax", + [CGROUP_IO_WBPS_MAX] = "IOWriteBandwidthMax", + [CGROUP_IO_RIOPS_MAX] = "IOReadIOPSMax", + [CGROUP_IO_WIOPS_MAX] = "IOWriteIOPSMax", +}; + +DEFINE_STRING_TABLE_LOOKUP(cgroup_io_limit_type, CGroupIOLimitType); + +int cg_cpu_shares_parse(const char *s, uint64_t *ret) { + uint64_t u; + int r; + + if (isempty(s)) { + *ret = CGROUP_CPU_SHARES_INVALID; + return 0; + } + + r = safe_atou64(s, &u); + if (r < 0) + return r; + + if (u < CGROUP_CPU_SHARES_MIN || u > CGROUP_CPU_SHARES_MAX) + return -ERANGE; + + *ret = u; + return 0; +} + +int cg_blkio_weight_parse(const char *s, uint64_t *ret) { + uint64_t u; + int r; + + if (isempty(s)) { + *ret = CGROUP_BLKIO_WEIGHT_INVALID; + return 0; + } + + r = safe_atou64(s, &u); + if (r < 0) + return r; + + if (u < CGROUP_BLKIO_WEIGHT_MIN || u > CGROUP_BLKIO_WEIGHT_MAX) + return -ERANGE; + + *ret = u; + return 0; +} + +static const char *cgroup_controller_table[_CGROUP_CONTROLLER_MAX] = { + [CGROUP_CONTROLLER_CPU] = "cpu", + [CGROUP_CONTROLLER_CPUACCT] = "cpuacct", + [CGROUP_CONTROLLER_IO] = "io", + [CGROUP_CONTROLLER_BLKIO] = "blkio", + [CGROUP_CONTROLLER_MEMORY] = "memory", + [CGROUP_CONTROLLER_DEVICES] = "devices", + [CGROUP_CONTROLLER_PIDS] = "pids", +}; + +DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController); diff --git a/src/libbasic/cgroup-util.h b/src/libbasic/cgroup-util.h new file mode 100644 index 0000000000..4bb5291296 --- /dev/null +++ b/src/libbasic/cgroup-util.h @@ -0,0 +1,228 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include + +#include "def.h" +#include "hashmap.h" +#include "macro.h" +#include "set.h" + +/* An enum of well known cgroup controllers */ +typedef enum CGroupController { + CGROUP_CONTROLLER_CPU, + CGROUP_CONTROLLER_CPUACCT, + CGROUP_CONTROLLER_IO, + CGROUP_CONTROLLER_BLKIO, + CGROUP_CONTROLLER_MEMORY, + CGROUP_CONTROLLER_DEVICES, + CGROUP_CONTROLLER_PIDS, + _CGROUP_CONTROLLER_MAX, + _CGROUP_CONTROLLER_INVALID = -1, +} CGroupController; + +#define CGROUP_CONTROLLER_TO_MASK(c) (1 << (c)) + +/* A bit mask of well known cgroup controllers */ +typedef enum CGroupMask { + CGROUP_MASK_CPU = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_CPU), + CGROUP_MASK_CPUACCT = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_CPUACCT), + CGROUP_MASK_IO = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_IO), + CGROUP_MASK_BLKIO = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BLKIO), + CGROUP_MASK_MEMORY = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_MEMORY), + CGROUP_MASK_DEVICES = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_DEVICES), + CGROUP_MASK_PIDS = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_PIDS), + _CGROUP_MASK_ALL = CGROUP_CONTROLLER_TO_MASK(_CGROUP_CONTROLLER_MAX) - 1 +} CGroupMask; + +/* Special values for all weight knobs on unified hierarchy */ +#define CGROUP_WEIGHT_INVALID ((uint64_t) -1) +#define CGROUP_WEIGHT_MIN UINT64_C(1) +#define CGROUP_WEIGHT_MAX UINT64_C(10000) +#define CGROUP_WEIGHT_DEFAULT UINT64_C(100) + +#define CGROUP_LIMIT_MIN UINT64_C(0) +#define CGROUP_LIMIT_MAX ((uint64_t) -1) + +static inline bool CGROUP_WEIGHT_IS_OK(uint64_t x) { + return + x == CGROUP_WEIGHT_INVALID || + (x >= CGROUP_WEIGHT_MIN && x <= CGROUP_WEIGHT_MAX); +} + +/* IO limits on unified hierarchy */ +typedef enum CGroupIOLimitType { + CGROUP_IO_RBPS_MAX, + CGROUP_IO_WBPS_MAX, + CGROUP_IO_RIOPS_MAX, + CGROUP_IO_WIOPS_MAX, + + _CGROUP_IO_LIMIT_TYPE_MAX, + _CGROUP_IO_LIMIT_TYPE_INVALID = -1 +} CGroupIOLimitType; + +extern const uint64_t cgroup_io_limit_defaults[_CGROUP_IO_LIMIT_TYPE_MAX]; + +const char* cgroup_io_limit_type_to_string(CGroupIOLimitType t) _const_; +CGroupIOLimitType cgroup_io_limit_type_from_string(const char *s) _pure_; + +/* Special values for the cpu.shares attribute */ +#define CGROUP_CPU_SHARES_INVALID ((uint64_t) -1) +#define CGROUP_CPU_SHARES_MIN UINT64_C(2) +#define CGROUP_CPU_SHARES_MAX UINT64_C(262144) +#define CGROUP_CPU_SHARES_DEFAULT UINT64_C(1024) + +static inline bool CGROUP_CPU_SHARES_IS_OK(uint64_t x) { + return + x == CGROUP_CPU_SHARES_INVALID || + (x >= CGROUP_CPU_SHARES_MIN && x <= CGROUP_CPU_SHARES_MAX); +} + +/* Special values for the blkio.weight attribute */ +#define CGROUP_BLKIO_WEIGHT_INVALID ((uint64_t) -1) +#define CGROUP_BLKIO_WEIGHT_MIN UINT64_C(10) +#define CGROUP_BLKIO_WEIGHT_MAX UINT64_C(1000) +#define CGROUP_BLKIO_WEIGHT_DEFAULT UINT64_C(500) + +static inline bool CGROUP_BLKIO_WEIGHT_IS_OK(uint64_t x) { + return + x == CGROUP_BLKIO_WEIGHT_INVALID || + (x >= CGROUP_BLKIO_WEIGHT_MIN && x <= CGROUP_BLKIO_WEIGHT_MAX); +} + +/* + * General rules: + * + * We accept named hierarchies in the syntax "foo" and "name=foo". + * + * We expect that named hierarchies do not conflict in name with a + * kernel hierarchy, modulo the "name=" prefix. + * + * We always generate "normalized" controller names, i.e. without the + * "name=" prefix. + * + * We require absolute cgroup paths. When returning, we will always + * generate paths with multiple adjacent / removed. + */ + +int cg_enumerate_processes(const char *controller, const char *path, FILE **_f); +int cg_read_pid(FILE *f, pid_t *_pid); +int cg_read_event(const char *controller, const char *path, const char *event, + char **val); + +int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d); +int cg_read_subgroup(DIR *d, char **fn); + +int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s); +int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool remove, Set *s); + +int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self); +int cg_migrate_recursive(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self, bool remove); +int cg_migrate_recursive_fallback(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self, bool rem); + +int cg_split_spec(const char *spec, char **controller, char **path); +int cg_mangle_path(const char *path, char **result); + +int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs); +int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs); + +int cg_pid_get_path(const char *controller, pid_t pid, char **path); + +int cg_trim(const char *controller, const char *path, bool delete_root); + +int cg_rmdir(const char *controller, const char *path); + +int cg_create(const char *controller, const char *path); +int cg_attach(const char *controller, const char *path, pid_t pid); +int cg_attach_fallback(const char *controller, const char *path, pid_t pid); +int cg_create_and_attach(const char *controller, const char *path, pid_t pid); + +int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value); +int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret); + +int cg_set_group_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid); +int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid); + +int cg_install_release_agent(const char *controller, const char *agent); +int cg_uninstall_release_agent(const char *controller); + +int cg_is_empty(const char *controller, const char *path); +int cg_is_empty_recursive(const char *controller, const char *path); + +int cg_get_root_path(char **path); + +int cg_path_get_session(const char *path, char **session); +int cg_path_get_owner_uid(const char *path, uid_t *uid); +int cg_path_get_unit(const char *path, char **unit); +int cg_path_get_user_unit(const char *path, char **unit); +int cg_path_get_machine_name(const char *path, char **machine); +int cg_path_get_slice(const char *path, char **slice); +int cg_path_get_user_slice(const char *path, char **slice); + +int cg_shift_path(const char *cgroup, const char *cached_root, const char **shifted); +int cg_pid_get_path_shifted(pid_t pid, const char *cached_root, char **cgroup); + +int cg_pid_get_session(pid_t pid, char **session); +int cg_pid_get_owner_uid(pid_t pid, uid_t *uid); +int cg_pid_get_unit(pid_t pid, char **unit); +int cg_pid_get_user_unit(pid_t pid, char **unit); +int cg_pid_get_machine_name(pid_t pid, char **machine); +int cg_pid_get_slice(pid_t pid, char **slice); +int cg_pid_get_user_slice(pid_t pid, char **slice); + +int cg_path_decode_unit(const char *cgroup, char **unit); + +char *cg_escape(const char *p); +char *cg_unescape(const char *p) _pure_; + +bool cg_controller_is_valid(const char *p); + +int cg_slice_to_path(const char *unit, char **ret); + +typedef const char* (*cg_migrate_callback_t)(CGroupMask mask, void *userdata); + +int cg_create_everywhere(CGroupMask supported, CGroupMask mask, const char *path); +int cg_attach_everywhere(CGroupMask supported, const char *path, pid_t pid, cg_migrate_callback_t callback, void *userdata); +int cg_attach_many_everywhere(CGroupMask supported, const char *path, Set* pids, cg_migrate_callback_t callback, void *userdata); +int cg_migrate_everywhere(CGroupMask supported, const char *from, const char *to, cg_migrate_callback_t callback, void *userdata); +int cg_trim_everywhere(CGroupMask supported, const char *path, bool delete_root); +int cg_enable_everywhere(CGroupMask supported, CGroupMask mask, const char *p); + +int cg_mask_supported(CGroupMask *ret); + +int cg_kernel_controllers(Set *controllers); + +int cg_unified(void); +void cg_unified_flush(void); + +bool cg_is_unified_wanted(void); +bool cg_is_legacy_wanted(void); + +const char* cgroup_controller_to_string(CGroupController c) _const_; +CGroupController cgroup_controller_from_string(const char *s) _pure_; + +int cg_weight_parse(const char *s, uint64_t *ret); +int cg_cpu_shares_parse(const char *s, uint64_t *ret); +int cg_blkio_weight_parse(const char *s, uint64_t *ret); diff --git a/src/libbasic/chattr-util.c b/src/libbasic/chattr-util.c new file mode 100644 index 0000000000..2896a729af --- /dev/null +++ b/src/libbasic/chattr-util.c @@ -0,0 +1,107 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include + +#include "chattr-util.h" +#include "fd-util.h" +#include "macro.h" + +int chattr_fd(int fd, unsigned value, unsigned mask) { + unsigned old_attr, new_attr; + struct stat st; + + assert(fd >= 0); + + if (fstat(fd, &st) < 0) + return -errno; + + /* Explicitly check whether this is a regular file or + * directory. If it is anything else (such as a device node or + * fifo), then the ioctl will not hit the file systems but + * possibly drivers, where the ioctl might have different + * effects. Notably, DRM is using the same ioctl() number. */ + + if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) + return -ENOTTY; + + if (mask == 0) + return 0; + + if (ioctl(fd, FS_IOC_GETFLAGS, &old_attr) < 0) + return -errno; + + new_attr = (old_attr & ~mask) | (value & mask); + if (new_attr == old_attr) + return 0; + + if (ioctl(fd, FS_IOC_SETFLAGS, &new_attr) < 0) + return -errno; + + return 1; +} + +int chattr_path(const char *p, unsigned value, unsigned mask) { + _cleanup_close_ int fd = -1; + + assert(p); + + if (mask == 0) + return 0; + + fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return chattr_fd(fd, value, mask); +} + +int read_attr_fd(int fd, unsigned *ret) { + struct stat st; + + assert(fd >= 0); + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) + return -ENOTTY; + + if (ioctl(fd, FS_IOC_GETFLAGS, ret) < 0) + return -errno; + + return 0; +} + +int read_attr_path(const char *p, unsigned *ret) { + _cleanup_close_ int fd = -1; + + assert(p); + assert(ret); + + fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return read_attr_fd(fd, ret); +} diff --git a/src/libbasic/chattr-util.h b/src/libbasic/chattr-util.h new file mode 100644 index 0000000000..960cf6d5b3 --- /dev/null +++ b/src/libbasic/chattr-util.h @@ -0,0 +1,26 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +int chattr_fd(int fd, unsigned value, unsigned mask); +int chattr_path(const char *p, unsigned value, unsigned mask); + +int read_attr_fd(int fd, unsigned *ret); +int read_attr_path(const char *p, unsigned *ret); diff --git a/src/libbasic/clock-util.c b/src/libbasic/clock-util.c new file mode 100644 index 0000000000..7fe8d35ea5 --- /dev/null +++ b/src/libbasic/clock-util.c @@ -0,0 +1,165 @@ +/*** + This file is part of systemd. + + Copyright 2010-2012 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 +#include +#include +#include +#include +#include +#include +#include + +#include "clock-util.h" +#include "fd-util.h" +#include "macro.h" +#include "string-util.h" +#include "util.h" + +int clock_get_hwclock(struct tm *tm) { + _cleanup_close_ int fd = -1; + + assert(tm); + + fd = open("/dev/rtc", O_RDONLY|O_CLOEXEC); + if (fd < 0) + return -errno; + + /* This leaves the timezone fields of struct tm + * uninitialized! */ + if (ioctl(fd, RTC_RD_TIME, tm) < 0) + return -errno; + + /* We don't know daylight saving, so we reset this in order not + * to confuse mktime(). */ + tm->tm_isdst = -1; + + return 0; +} + +int clock_set_hwclock(const struct tm *tm) { + _cleanup_close_ int fd = -1; + + assert(tm); + + fd = open("/dev/rtc", O_RDONLY|O_CLOEXEC); + if (fd < 0) + return -errno; + + if (ioctl(fd, RTC_SET_TIME, tm) < 0) + return -errno; + + return 0; +} + +int clock_is_localtime(const char* adjtime_path) { + _cleanup_fclose_ FILE *f; + + if (adjtime_path == NULL) + adjtime_path = "/etc/adjtime"; + + /* + * The third line of adjtime is "UTC" or "LOCAL" or nothing. + * # /etc/adjtime + * 0.0 0 0 + * 0 + * UTC + */ + f = fopen(adjtime_path, "re"); + if (f) { + char line[LINE_MAX]; + bool b; + + b = fgets(line, sizeof(line), f) && + fgets(line, sizeof(line), f) && + fgets(line, sizeof(line), f); + if (!b) + /* less than three lines -> default to UTC */ + return 0; + + truncate_nl(line); + return streq(line, "LOCAL"); + + } else if (errno != ENOENT) + return -errno; + + /* adjtime not present -> default to UTC */ + return 0; +} + +int clock_set_timezone(int *min) { + const struct timeval *tv_null = NULL; + struct timespec ts; + struct tm *tm; + int minutesdelta; + struct timezone tz; + + assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); + assert_se(tm = localtime(&ts.tv_sec)); + minutesdelta = tm->tm_gmtoff / 60; + + tz.tz_minuteswest = -minutesdelta; + tz.tz_dsttime = 0; /* DST_NONE */ + + /* + * If the RTC does not run in UTC but in local time, the very first + * call to settimeofday() will set the kernel's timezone and will warp the + * system clock, so that it runs in UTC instead of the local time we + * have read from the RTC. + */ + if (settimeofday(tv_null, &tz) < 0) + return negative_errno(); + + if (min) + *min = minutesdelta; + return 0; +} + +int clock_reset_timewarp(void) { + const struct timeval *tv_null = NULL; + struct timezone tz; + + tz.tz_minuteswest = 0; + tz.tz_dsttime = 0; /* DST_NONE */ + + /* + * The very first call to settimeofday() does time warp magic. Do a + * dummy call here, so the time warping is sealed and all later calls + * behave as expected. + */ + if (settimeofday(tv_null, &tz) < 0) + return -errno; + + return 0; +} + +#define TIME_EPOCH_USEC ((usec_t) TIME_EPOCH * USEC_PER_SEC) + +int clock_apply_epoch(void) { + struct timespec ts; + + if (now(CLOCK_REALTIME) >= TIME_EPOCH_USEC) + return 0; + + if (clock_settime(CLOCK_REALTIME, timespec_store(&ts, TIME_EPOCH_USEC)) < 0) + return -errno; + + return 1; +} diff --git a/src/libbasic/clock-util.h b/src/libbasic/clock-util.h new file mode 100644 index 0000000000..8830cd2f38 --- /dev/null +++ b/src/libbasic/clock-util.h @@ -0,0 +1,29 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-2012 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 + +int clock_is_localtime(const char* adjtime_path); +int clock_set_timezone(int *min); +int clock_reset_timewarp(void); +int clock_get_hwclock(struct tm *tm); +int clock_set_hwclock(const struct tm *tm); +int clock_apply_epoch(void); diff --git a/src/libbasic/conf-files.c b/src/libbasic/conf-files.c new file mode 100644 index 0000000000..c781610e14 --- /dev/null +++ b/src/libbasic/conf-files.c @@ -0,0 +1,166 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include + +#include "conf-files.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "path-util.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +static int files_add(Hashmap *h, const char *root, const char *path, const char *suffix) { + _cleanup_closedir_ DIR *dir = NULL; + const char *dirpath; + struct dirent *de; + int r; + + assert(path); + assert(suffix); + + dirpath = prefix_roota(root, path); + + dir = opendir(dirpath); + if (!dir) { + if (errno == ENOENT) + return 0; + return -errno; + } + + FOREACH_DIRENT(de, dir, return -errno) { + char *p; + + if (!dirent_is_file_with_suffix(de, suffix)) + continue; + + p = strjoin(dirpath, "/", de->d_name, NULL); + if (!p) + return -ENOMEM; + + r = hashmap_put(h, basename(p), p); + if (r == -EEXIST) { + log_debug("Skipping overridden file: %s.", p); + free(p); + } else if (r < 0) { + free(p); + return r; + } else if (r == 0) { + log_debug("Duplicate file %s", p); + free(p); + } + } + + return 0; +} + +static int base_cmp(const void *a, const void *b) { + const char *s1, *s2; + + s1 = *(char * const *)a; + s2 = *(char * const *)b; + return strcmp(basename(s1), basename(s2)); +} + +static int conf_files_list_strv_internal(char ***strv, const char *suffix, const char *root, char **dirs) { + _cleanup_hashmap_free_ Hashmap *fh = NULL; + char **files, **p; + int r; + + assert(strv); + assert(suffix); + + /* This alters the dirs string array */ + if (!path_strv_resolve_uniq(dirs, root)) + return -ENOMEM; + + fh = hashmap_new(&string_hash_ops); + if (!fh) + return -ENOMEM; + + STRV_FOREACH(p, dirs) { + r = files_add(fh, root, *p, suffix); + if (r == -ENOMEM) + return r; + if (r < 0) + log_debug_errno(r, "Failed to search for files in %s, ignoring: %m", *p); + } + + files = hashmap_get_strv(fh); + if (!files) + return -ENOMEM; + + qsort_safe(files, hashmap_size(fh), sizeof(char *), base_cmp); + *strv = files; + + return 0; +} + +int conf_files_list_strv(char ***strv, const char *suffix, const char *root, const char* const* dirs) { + _cleanup_strv_free_ char **copy = NULL; + + assert(strv); + assert(suffix); + + copy = strv_copy((char**) dirs); + if (!copy) + return -ENOMEM; + + return conf_files_list_strv_internal(strv, suffix, root, copy); +} + +int conf_files_list(char ***strv, const char *suffix, const char *root, const char *dir, ...) { + _cleanup_strv_free_ char **dirs = NULL; + va_list ap; + + assert(strv); + assert(suffix); + + va_start(ap, dir); + dirs = strv_new_ap(dir, ap); + va_end(ap); + + if (!dirs) + return -ENOMEM; + + return conf_files_list_strv_internal(strv, suffix, root, dirs); +} + +int conf_files_list_nulstr(char ***strv, const char *suffix, const char *root, const char *d) { + _cleanup_strv_free_ char **dirs = NULL; + + assert(strv); + assert(suffix); + + dirs = strv_split_nulstr(d); + if (!dirs) + return -ENOMEM; + + return conf_files_list_strv_internal(strv, suffix, root, dirs); +} diff --git a/src/libbasic/conf-files.h b/src/libbasic/conf-files.h new file mode 100644 index 0000000000..e00e0e81fb --- /dev/null +++ b/src/libbasic/conf-files.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-2012 Lennart Poettering + Copyright 2010-2012 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 . +***/ + +int conf_files_list(char ***ret, const char *suffix, const char *root, const char *dir, ...); +int conf_files_list_strv(char ***ret, const char *suffix, const char *root, const char* const* dirs); +int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, const char *dirs); diff --git a/src/libbasic/copy.c b/src/libbasic/copy.c new file mode 100644 index 0000000000..c3586728d0 --- /dev/null +++ b/src/libbasic/copy.c @@ -0,0 +1,603 @@ +/*** + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "btrfs-util.h" +#include "chattr-util.h" +#include "copy.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "io-util.h" +#include "macro.h" +#include "missing.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "umask-util.h" +#include "xattr-util.h" + +#define COPY_BUFFER_SIZE (16*1024u) + +static ssize_t try_copy_file_range(int fd_in, loff_t *off_in, + int fd_out, loff_t *off_out, + size_t len, + unsigned int flags) { + static int have = -1; + ssize_t r; + + if (have == false) + return -ENOSYS; + + r = copy_file_range(fd_in, off_in, fd_out, off_out, len, flags); + if (_unlikely_(have < 0)) + have = r >= 0 || errno != ENOSYS; + if (r >= 0) + return r; + else + return -errno; +} + +int copy_bytes(int fdf, int fdt, uint64_t max_bytes, bool try_reflink) { + bool try_cfr = true, try_sendfile = true, try_splice = true; + int r; + size_t m = SSIZE_MAX; /* that is the maximum that sendfile and c_f_r accept */ + + assert(fdf >= 0); + assert(fdt >= 0); + + /* Try btrfs reflinks first. */ + if (try_reflink && + max_bytes == (uint64_t) -1 && + lseek(fdf, 0, SEEK_CUR) == 0 && + lseek(fdt, 0, SEEK_CUR) == 0) { + + r = btrfs_reflink(fdf, fdt); + if (r >= 0) + return 0; /* we copied the whole thing, hence hit EOF, return 0 */ + } + + for (;;) { + ssize_t n; + + if (max_bytes != (uint64_t) -1) { + if (max_bytes <= 0) + return 1; /* return > 0 if we hit the max_bytes limit */ + + if (m > max_bytes) + m = max_bytes; + } + + /* First try copy_file_range(), unless we already tried */ + if (try_cfr) { + n = try_copy_file_range(fdf, NULL, fdt, NULL, m, 0u); + if (n < 0) { + if (!IN_SET(n, -EINVAL, -ENOSYS, -EXDEV, -EBADF)) + return n; + + try_cfr = false; + /* use fallback below */ + } else if (n == 0) /* EOF */ + break; + else + /* Success! */ + goto next; + } + + /* First try sendfile(), unless we already tried */ + if (try_sendfile) { + n = sendfile(fdt, fdf, NULL, m); + if (n < 0) { + if (!IN_SET(errno, EINVAL, ENOSYS)) + return -errno; + + try_sendfile = false; + /* use fallback below */ + } else if (n == 0) /* EOF */ + break; + else + /* Success! */ + goto next; + } + + /* Then try splice, unless we already tried */ + if (try_splice) { + n = splice(fdf, NULL, fdt, NULL, m, 0); + if (n < 0) { + if (!IN_SET(errno, EINVAL, ENOSYS)) + return -errno; + + try_splice = false; + /* use fallback below */ + } else if (n == 0) /* EOF */ + break; + else + /* Success! */ + goto next; + } + + /* As a fallback just copy bits by hand */ + { + uint8_t buf[MIN(m, COPY_BUFFER_SIZE)]; + + n = read(fdf, buf, sizeof buf); + if (n < 0) + return -errno; + if (n == 0) /* EOF */ + break; + + r = loop_write(fdt, buf, (size_t) n, false); + if (r < 0) + return r; + } + + next: + if (max_bytes != (uint64_t) -1) { + assert(max_bytes >= (uint64_t) n); + max_bytes -= n; + } + /* sendfile accepts at most SSIZE_MAX-offset bytes to copy, + * so reduce our maximum by the amount we already copied, + * but don't go below our copy buffer size, unless we are + * close the the limit of bytes we are allowed to copy. */ + m = MAX(MIN(COPY_BUFFER_SIZE, max_bytes), m - n); + } + + return 0; /* return 0 if we hit EOF earlier than the size limit */ +} + +static int fd_copy_symlink(int df, const char *from, const struct stat *st, int dt, const char *to) { + _cleanup_free_ char *target = NULL; + int r; + + assert(from); + assert(st); + assert(to); + + r = readlinkat_malloc(df, from, &target); + if (r < 0) + return r; + + if (symlinkat(target, dt, to) < 0) + return -errno; + + if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0) + return -errno; + + return 0; +} + +static int fd_copy_regular(int df, const char *from, const struct stat *st, int dt, const char *to) { + _cleanup_close_ int fdf = -1, fdt = -1; + struct timespec ts[2]; + int r, q; + + assert(from); + assert(st); + assert(to); + + fdf = openat(df, from, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fdf < 0) + return -errno; + + fdt = openat(dt, to, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, st->st_mode & 07777); + if (fdt < 0) + return -errno; + + r = copy_bytes(fdf, fdt, (uint64_t) -1, true); + if (r < 0) { + unlinkat(dt, to, 0); + return r; + } + + if (fchown(fdt, st->st_uid, st->st_gid) < 0) + r = -errno; + + if (fchmod(fdt, st->st_mode & 07777) < 0) + r = -errno; + + ts[0] = st->st_atim; + ts[1] = st->st_mtim; + (void) futimens(fdt, ts); + + (void) copy_xattr(fdf, fdt); + + q = close(fdt); + fdt = -1; + + if (q < 0) { + r = -errno; + unlinkat(dt, to, 0); + } + + return r; +} + +static int fd_copy_fifo(int df, const char *from, const struct stat *st, int dt, const char *to) { + int r; + + assert(from); + assert(st); + assert(to); + + r = mkfifoat(dt, to, st->st_mode & 07777); + if (r < 0) + return -errno; + + if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0) + r = -errno; + + if (fchmodat(dt, to, st->st_mode & 07777, 0) < 0) + r = -errno; + + return r; +} + +static int fd_copy_node(int df, const char *from, const struct stat *st, int dt, const char *to) { + int r; + + assert(from); + assert(st); + assert(to); + + r = mknodat(dt, to, st->st_mode, st->st_rdev); + if (r < 0) + return -errno; + + if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0) + r = -errno; + + if (fchmodat(dt, to, st->st_mode & 07777, 0) < 0) + r = -errno; + + return r; +} + +static int fd_copy_directory( + int df, + const char *from, + const struct stat *st, + int dt, + const char *to, + dev_t original_device, + bool merge) { + + _cleanup_close_ int fdf = -1, fdt = -1; + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + bool created; + int r; + + assert(st); + assert(to); + + if (from) + fdf = openat(df, from, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + else + fdf = fcntl(df, F_DUPFD_CLOEXEC, 3); + if (fdf < 0) + return -errno; + + d = fdopendir(fdf); + if (!d) + return -errno; + fdf = -1; + + r = mkdirat(dt, to, st->st_mode & 07777); + if (r >= 0) + created = true; + else if (errno == EEXIST && merge) + created = false; + else + return -errno; + + fdt = openat(dt, to, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fdt < 0) + return -errno; + + r = 0; + + FOREACH_DIRENT_ALL(de, d, return -errno) { + struct stat buf; + int q; + + if (STR_IN_SET(de->d_name, ".", "..")) + continue; + + if (fstatat(dirfd(d), de->d_name, &buf, AT_SYMLINK_NOFOLLOW) < 0) { + r = -errno; + continue; + } + + if (buf.st_dev != original_device) + continue; + + if (S_ISREG(buf.st_mode)) + q = fd_copy_regular(dirfd(d), de->d_name, &buf, fdt, de->d_name); + else if (S_ISDIR(buf.st_mode)) + q = fd_copy_directory(dirfd(d), de->d_name, &buf, fdt, de->d_name, original_device, merge); + else if (S_ISLNK(buf.st_mode)) + q = fd_copy_symlink(dirfd(d), de->d_name, &buf, fdt, de->d_name); + else if (S_ISFIFO(buf.st_mode)) + q = fd_copy_fifo(dirfd(d), de->d_name, &buf, fdt, de->d_name); + else if (S_ISBLK(buf.st_mode) || S_ISCHR(buf.st_mode) || S_ISSOCK(buf.st_mode)) + q = fd_copy_node(dirfd(d), de->d_name, &buf, fdt, de->d_name); + else + q = -EOPNOTSUPP; + + if (q == -EEXIST && merge) + q = 0; + + if (q < 0) + r = q; + } + + if (created) { + struct timespec ut[2] = { + st->st_atim, + st->st_mtim + }; + + if (fchown(fdt, st->st_uid, st->st_gid) < 0) + r = -errno; + + if (fchmod(fdt, st->st_mode & 07777) < 0) + r = -errno; + + (void) copy_xattr(dirfd(d), fdt); + (void) futimens(fdt, ut); + } + + return r; +} + +int copy_tree_at(int fdf, const char *from, int fdt, const char *to, bool merge) { + struct stat st; + + assert(from); + assert(to); + + if (fstatat(fdf, from, &st, AT_SYMLINK_NOFOLLOW) < 0) + return -errno; + + if (S_ISREG(st.st_mode)) + return fd_copy_regular(fdf, from, &st, fdt, to); + else if (S_ISDIR(st.st_mode)) + return fd_copy_directory(fdf, from, &st, fdt, to, st.st_dev, merge); + else if (S_ISLNK(st.st_mode)) + return fd_copy_symlink(fdf, from, &st, fdt, to); + else if (S_ISFIFO(st.st_mode)) + return fd_copy_fifo(fdf, from, &st, fdt, to); + else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode) || S_ISSOCK(st.st_mode)) + return fd_copy_node(fdf, from, &st, fdt, to); + else + return -EOPNOTSUPP; +} + +int copy_tree(const char *from, const char *to, bool merge) { + return copy_tree_at(AT_FDCWD, from, AT_FDCWD, to, merge); +} + +int copy_directory_fd(int dirfd, const char *to, bool merge) { + struct stat st; + + assert(dirfd >= 0); + assert(to); + + if (fstat(dirfd, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode)) + return -ENOTDIR; + + return fd_copy_directory(dirfd, NULL, &st, AT_FDCWD, to, st.st_dev, merge); +} + +int copy_directory(const char *from, const char *to, bool merge) { + struct stat st; + + assert(from); + assert(to); + + if (lstat(from, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode)) + return -ENOTDIR; + + return fd_copy_directory(AT_FDCWD, from, &st, AT_FDCWD, to, st.st_dev, merge); +} + +int copy_file_fd(const char *from, int fdt, bool try_reflink) { + _cleanup_close_ int fdf = -1; + int r; + + assert(from); + assert(fdt >= 0); + + fdf = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fdf < 0) + return -errno; + + r = copy_bytes(fdf, fdt, (uint64_t) -1, try_reflink); + + (void) copy_times(fdf, fdt); + (void) copy_xattr(fdf, fdt); + + return r; +} + +int copy_file(const char *from, const char *to, int flags, mode_t mode, unsigned chattr_flags) { + int fdt = -1, r; + + assert(from); + assert(to); + + RUN_WITH_UMASK(0000) { + fdt = open(to, flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, mode); + if (fdt < 0) + return -errno; + } + + if (chattr_flags != 0) + (void) chattr_fd(fdt, chattr_flags, (unsigned) -1); + + r = copy_file_fd(from, fdt, true); + if (r < 0) { + close(fdt); + unlink(to); + return r; + } + + if (close(fdt) < 0) { + unlink_noerrno(to); + return -errno; + } + + return 0; +} + +int copy_file_atomic(const char *from, const char *to, mode_t mode, bool replace, unsigned chattr_flags) { + _cleanup_free_ char *t = NULL; + int r; + + assert(from); + assert(to); + + r = tempfn_random(to, NULL, &t); + if (r < 0) + return r; + + r = copy_file(from, t, O_NOFOLLOW|O_EXCL, mode, chattr_flags); + if (r < 0) + return r; + + if (replace) { + r = renameat(AT_FDCWD, t, AT_FDCWD, to); + if (r < 0) + r = -errno; + } else + r = rename_noreplace(AT_FDCWD, t, AT_FDCWD, to); + if (r < 0) { + (void) unlink_noerrno(t); + return r; + } + + return 0; +} + +int copy_times(int fdf, int fdt) { + struct timespec ut[2]; + struct stat st; + usec_t crtime = 0; + + assert(fdf >= 0); + assert(fdt >= 0); + + if (fstat(fdf, &st) < 0) + return -errno; + + ut[0] = st.st_atim; + ut[1] = st.st_mtim; + + if (futimens(fdt, ut) < 0) + return -errno; + + if (fd_getcrtime(fdf, &crtime) >= 0) + (void) fd_setcrtime(fdt, crtime); + + return 0; +} + +int copy_xattr(int fdf, int fdt) { + _cleanup_free_ char *bufa = NULL, *bufb = NULL; + size_t sza = 100, szb = 100; + ssize_t n; + int ret = 0; + const char *p; + + for (;;) { + bufa = malloc(sza); + if (!bufa) + return -ENOMEM; + + n = flistxattr(fdf, bufa, sza); + if (n == 0) + return 0; + if (n > 0) + break; + if (errno != ERANGE) + return -errno; + + sza *= 2; + + bufa = mfree(bufa); + } + + p = bufa; + while (n > 0) { + size_t l; + + l = strlen(p); + assert(l < (size_t) n); + + if (startswith(p, "user.")) { + ssize_t m; + + if (!bufb) { + bufb = malloc(szb); + if (!bufb) + return -ENOMEM; + } + + m = fgetxattr(fdf, p, bufb, szb); + if (m < 0) { + if (errno == ERANGE) { + szb *= 2; + bufb = mfree(bufb); + continue; + } + + return -errno; + } + + if (fsetxattr(fdt, p, bufb, m, 0) < 0) + ret = -errno; + } + + p += l + 1; + n -= l + 1; + } + + return ret; +} diff --git a/src/libbasic/copy.h b/src/libbasic/copy.h new file mode 100644 index 0000000000..b5d08ebafe --- /dev/null +++ b/src/libbasic/copy.h @@ -0,0 +1,36 @@ +#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 +#include +#include +#include + +int copy_file_fd(const char *from, int to, bool try_reflink); +int copy_file(const char *from, const char *to, int flags, mode_t mode, unsigned chattr_flags); +int copy_file_atomic(const char *from, const char *to, mode_t mode, bool replace, unsigned chattr_flags); +int copy_tree(const char *from, const char *to, bool merge); +int copy_tree_at(int fdf, const char *from, int fdt, const char *to, bool merge); +int copy_directory_fd(int dirfd, const char *to, bool merge); +int copy_directory(const char *from, const char *to, bool merge); +int copy_bytes(int fdf, int fdt, uint64_t max_bytes, bool try_reflink); +int copy_times(int fdf, int fdt); +int copy_xattr(int fdf, int fdt); diff --git a/src/libbasic/cpu-set-util.c b/src/libbasic/cpu-set-util.c new file mode 100644 index 0000000000..95ed6928ff --- /dev/null +++ b/src/libbasic/cpu-set-util.c @@ -0,0 +1,114 @@ +/*** + This file is part of systemd. + + Copyright 2010-2015 Lennart Poettering + Copyright 2015 Filipe Brandenburger + + 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 +#include + +#include "alloc-util.h" +#include "cpu-set-util.h" +#include "extract-word.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "string-util.h" + +cpu_set_t* cpu_set_malloc(unsigned *ncpus) { + cpu_set_t *c; + unsigned n = 1024; + + /* Allocates the cpuset in the right size */ + + for (;;) { + c = CPU_ALLOC(n); + if (!c) + return NULL; + + if (sched_getaffinity(0, CPU_ALLOC_SIZE(n), c) >= 0) { + CPU_ZERO_S(CPU_ALLOC_SIZE(n), c); + + if (ncpus) + *ncpus = n; + + return c; + } + + CPU_FREE(c); + + if (errno != EINVAL) + return NULL; + + n *= 2; + } +} + +int parse_cpu_set_and_warn( + const char *rvalue, + cpu_set_t **cpu_set, + const char *unit, + const char *filename, + unsigned line, + const char *lvalue) { + + const char *whole_rvalue = rvalue; + _cleanup_cpu_free_ cpu_set_t *c = NULL; + unsigned ncpus = 0; + + assert(lvalue); + assert(rvalue); + + for (;;) { + _cleanup_free_ char *word = NULL; + unsigned cpu, cpu_lower, cpu_upper; + int r; + + r = extract_first_word(&rvalue, &word, WHITESPACE ",", EXTRACT_QUOTES); + if (r < 0) + return log_syntax(unit, LOG_ERR, filename, line, r, "Invalid value for %s: %s", lvalue, whole_rvalue); + if (r == 0) + break; + + if (!c) { + c = cpu_set_malloc(&ncpus); + if (!c) + return log_oom(); + } + + r = parse_range(word, &cpu_lower, &cpu_upper); + if (r < 0) + return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CPU affinity '%s'", word); + if (cpu_lower >= ncpus || cpu_upper >= ncpus) + return log_syntax(unit, LOG_ERR, filename, line, EINVAL, "CPU out of range '%s' ncpus is %u", word, ncpus); + + if (cpu_lower > cpu_upper) + log_syntax(unit, LOG_WARNING, filename, line, 0, "Range '%s' is invalid, %u > %u", word, cpu_lower, cpu_upper); + else + for (cpu = cpu_lower; cpu <= cpu_upper; cpu++) + CPU_SET_S(cpu, CPU_ALLOC_SIZE(ncpus), c); + } + + /* On success, sets *cpu_set and returns ncpus for the system. */ + if (c) { + *cpu_set = c; + c = NULL; + } + + return (int) ncpus; +} diff --git a/src/libbasic/cpu-set-util.h b/src/libbasic/cpu-set-util.h new file mode 100644 index 0000000000..6f49d9afb0 --- /dev/null +++ b/src/libbasic/cpu-set-util.h @@ -0,0 +1,32 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-2015 Lennart Poettering + Copyright 2015 Filipe Brandenburger + + 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 "macro.h" + +DEFINE_TRIVIAL_CLEANUP_FUNC(cpu_set_t*, CPU_FREE); +#define _cleanup_cpu_free_ _cleanup_(CPU_FREEp) + +cpu_set_t* cpu_set_malloc(unsigned *ncpus); + +int parse_cpu_set_and_warn(const char *rvalue, cpu_set_t **cpu_set, const char *unit, const char *filename, unsigned line, const char *lvalue); diff --git a/src/libbasic/def.h b/src/libbasic/def.h new file mode 100644 index 0000000000..1a7a0f4928 --- /dev/null +++ b/src/libbasic/def.h @@ -0,0 +1,90 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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" + +#define DEFAULT_TIMEOUT_USEC (90*USEC_PER_SEC) +#define DEFAULT_RESTART_USEC (100*USEC_PER_MSEC) +#define DEFAULT_CONFIRM_USEC (30*USEC_PER_SEC) + +#define DEFAULT_START_LIMIT_INTERVAL (10*USEC_PER_SEC) +#define DEFAULT_START_LIMIT_BURST 5 + +/* The default time after which exit-on-idle services exit. This + * should be kept lower than the watchdog timeout, because otherwise + * the watchdog pings will keep the loop busy. */ +#define DEFAULT_EXIT_USEC (30*USEC_PER_SEC) + +/* The default value for the net.unix.max_dgram_qlen sysctl */ +#define DEFAULT_UNIX_MAX_DGRAM_QLEN 512UL + +#define SYSTEMD_CGROUP_CONTROLLER "name=systemd" + +#define SIGNALS_CRASH_HANDLER SIGSEGV,SIGILL,SIGFPE,SIGBUS,SIGQUIT,SIGABRT +#define SIGNALS_IGNORE SIGPIPE + +#ifdef HAVE_SPLIT_USR +#define KBD_KEYMAP_DIRS \ + "/usr/share/keymaps/\0" \ + "/usr/share/kbd/keymaps/\0" \ + "/usr/lib/kbd/keymaps/\0" \ + "/lib/kbd/keymaps/\0" +#else +#define KBD_KEYMAP_DIRS \ + "/usr/share/keymaps/\0" \ + "/usr/share/kbd/keymaps/\0" \ + "/usr/lib/kbd/keymaps/\0" +#endif + +#define UNIX_SYSTEM_BUS_ADDRESS "unix:path=/var/run/dbus/system_bus_socket" +#define KERNEL_SYSTEM_BUS_ADDRESS "kernel:path=/sys/fs/kdbus/0-system/bus" +#define DEFAULT_SYSTEM_BUS_ADDRESS KERNEL_SYSTEM_BUS_ADDRESS ";" UNIX_SYSTEM_BUS_ADDRESS +#define UNIX_USER_BUS_ADDRESS_FMT "unix:path=%s/bus" +#define KERNEL_USER_BUS_ADDRESS_FMT "kernel:path=/sys/fs/kdbus/"UID_FMT"-user/bus" + +#define PLYMOUTH_SOCKET { \ + .un.sun_family = AF_UNIX, \ + .un.sun_path = "\0/org/freedesktop/plymouthd", \ + } + +#ifndef TTY_GID +#define TTY_GID 5 +#endif + +#define NOTIFY_FD_MAX 768 +#define NOTIFY_BUFFER_MAX PIPE_BUF + +#ifdef HAVE_SPLIT_USR +#define _CONF_PATHS_SPLIT_USR(n) "/lib/" n "\0" +#else +#define _CONF_PATHS_SPLIT_USR(n) +#endif + +/* Return a nulstr for a standard cascade of configuration paths, + * suitable to pass to conf_files_list_nulstr() or config_parse_many() + * to implement drop-in directories for extending configuration + * files. */ +#define CONF_PATHS_NULSTR(n) \ + "/etc/" n "\0" \ + "/run/" n "\0" \ + "/usr/local/lib/" n "\0" \ + "/usr/lib/" n "\0" \ + _CONF_PATHS_SPLIT_USR(n) diff --git a/src/libbasic/device-nodes.c b/src/libbasic/device-nodes.c new file mode 100644 index 0000000000..38c0628a90 --- /dev/null +++ b/src/libbasic/device-nodes.c @@ -0,0 +1,80 @@ +/*** + This file is part of systemd. + + Copyright 2008-2011 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 +#include +#include + +#include "device-nodes.h" +#include "utf8.h" + +int whitelisted_char_for_devnode(char c, const char *white) { + + if ((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + strchr("#+-.:=@_", c) != NULL || + (white != NULL && strchr(white, c) != NULL)) + return 1; + + return 0; +} + +int encode_devnode_name(const char *str, char *str_enc, size_t len) { + size_t i, j; + + if (str == NULL || str_enc == NULL) + return -EINVAL; + + for (i = 0, j = 0; str[i] != '\0'; i++) { + int seqlen; + + seqlen = utf8_encoded_valid_unichar(&str[i]); + if (seqlen > 1) { + + if (len-j < (size_t)seqlen) + return -EINVAL; + + memcpy(&str_enc[j], &str[i], seqlen); + j += seqlen; + i += (seqlen-1); + + } else if (str[i] == '\\' || !whitelisted_char_for_devnode(str[i], NULL)) { + + if (len-j < 4) + return -EINVAL; + + sprintf(&str_enc[j], "\\x%02x", (unsigned char) str[i]); + j += 4; + + } else { + if (len-j < 1) + return -EINVAL; + + str_enc[j] = str[i]; + j++; + } + } + + if (len-j < 1) + return -EINVAL; + + str_enc[j] = '\0'; + return 0; +} diff --git a/src/libbasic/device-nodes.h b/src/libbasic/device-nodes.h new file mode 100644 index 0000000000..94f385abcb --- /dev/null +++ b/src/libbasic/device-nodes.h @@ -0,0 +1,26 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 + +int encode_devnode_name(const char *str, char *str_enc, size_t len); +int whitelisted_char_for_devnode(char c, const char *additional); diff --git a/src/libbasic/dirent-util.c b/src/libbasic/dirent-util.c new file mode 100644 index 0000000000..59067121b7 --- /dev/null +++ b/src/libbasic/dirent-util.c @@ -0,0 +1,74 @@ +/*** + This file is part of systemd. + + Copyright 2010-2012 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 + +#include "dirent-util.h" +#include "path-util.h" +#include "string-util.h" + +int dirent_ensure_type(DIR *d, struct dirent *de) { + struct stat st; + + assert(d); + assert(de); + + if (de->d_type != DT_UNKNOWN) + return 0; + + if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) + return -errno; + + de->d_type = + S_ISREG(st.st_mode) ? DT_REG : + S_ISDIR(st.st_mode) ? DT_DIR : + S_ISLNK(st.st_mode) ? DT_LNK : + S_ISFIFO(st.st_mode) ? DT_FIFO : + S_ISSOCK(st.st_mode) ? DT_SOCK : + S_ISCHR(st.st_mode) ? DT_CHR : + S_ISBLK(st.st_mode) ? DT_BLK : + DT_UNKNOWN; + + return 0; +} + +bool dirent_is_file(const struct dirent *de) { + assert(de); + + if (!IN_SET(de->d_type, DT_REG, DT_LNK, DT_UNKNOWN)) + return false; + + if (hidden_or_backup_file(de->d_name)) + return false; + + return true; +} + +bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) { + assert(de); + + if (!IN_SET(de->d_type, DT_REG, DT_LNK, DT_UNKNOWN)) + return false; + + if (de->d_name[0] == '.') + return false; + + return endswith(de->d_name, suffix); +} diff --git a/src/libbasic/dirent-util.h b/src/libbasic/dirent-util.h new file mode 100644 index 0000000000..b91d04908f --- /dev/null +++ b/src/libbasic/dirent-util.h @@ -0,0 +1,52 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include + +#include "macro.h" +#include "path-util.h" + +int dirent_ensure_type(DIR *d, struct dirent *de); + +bool dirent_is_file(const struct dirent *de) _pure_; +bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) _pure_; + +#define FOREACH_DIRENT(de, d, on_error) \ + for (errno = 0, de = readdir(d);; errno = 0, de = readdir(d)) \ + if (!de) { \ + if (errno > 0) { \ + on_error; \ + } \ + break; \ + } else if (hidden_or_backup_file((de)->d_name)) \ + continue; \ + else + +#define FOREACH_DIRENT_ALL(de, d, on_error) \ + for (errno = 0, de = readdir(d);; errno = 0, de = readdir(d)) \ + if (!de) { \ + if (errno > 0) { \ + on_error; \ + } \ + break; \ + } else diff --git a/src/libbasic/env-util.c b/src/libbasic/env-util.c new file mode 100644 index 0000000000..7f5fddb700 --- /dev/null +++ b/src/libbasic/env-util.c @@ -0,0 +1,624 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 +#include +#include +#include +#include + +#include "alloc-util.h" +#include "env-util.h" +#include "extract-word.h" +#include "macro.h" +#include "parse-util.h" +#include "string-util.h" +#include "strv.h" +#include "utf8.h" + +#define VALID_CHARS_ENV_NAME \ + DIGITS LETTERS \ + "_" + +#ifndef ARG_MAX +#define ARG_MAX ((size_t) sysconf(_SC_ARG_MAX)) +#endif + +static bool env_name_is_valid_n(const char *e, size_t n) { + const char *p; + + if (!e) + return false; + + if (n <= 0) + return false; + + if (e[0] >= '0' && e[0] <= '9') + return false; + + /* POSIX says the overall size of the environment block cannot + * be > ARG_MAX, an individual assignment hence cannot be + * either. Discounting the equal sign and trailing NUL this + * hence leaves ARG_MAX-2 as longest possible variable + * name. */ + if (n > ARG_MAX - 2) + return false; + + for (p = e; p < e + n; p++) + if (!strchr(VALID_CHARS_ENV_NAME, *p)) + return false; + + return true; +} + +bool env_name_is_valid(const char *e) { + if (!e) + return false; + + return env_name_is_valid_n(e, strlen(e)); +} + +bool env_value_is_valid(const char *e) { + if (!e) + return false; + + if (!utf8_is_valid(e)) + return false; + + /* bash allows tabs in environment variables, and so should + * we */ + if (string_has_cc(e, "\t")) + return false; + + /* POSIX says the overall size of the environment block cannot + * be > ARG_MAX, an individual assignment hence cannot be + * either. Discounting the shortest possible variable name of + * length 1, the equal sign and trailing NUL this hence leaves + * ARG_MAX-3 as longest possible variable value. */ + if (strlen(e) > ARG_MAX - 3) + return false; + + return true; +} + +bool env_assignment_is_valid(const char *e) { + const char *eq; + + eq = strchr(e, '='); + if (!eq) + return false; + + if (!env_name_is_valid_n(e, eq - e)) + return false; + + if (!env_value_is_valid(eq + 1)) + return false; + + /* POSIX says the overall size of the environment block cannot + * be > ARG_MAX, hence the individual variable assignments + * cannot be either, but let's leave room for one trailing NUL + * byte. */ + if (strlen(e) > ARG_MAX - 1) + return false; + + return true; +} + +bool strv_env_is_valid(char **e) { + char **p, **q; + + STRV_FOREACH(p, e) { + size_t k; + + if (!env_assignment_is_valid(*p)) + return false; + + /* Check if there are duplicate assginments */ + k = strcspn(*p, "="); + STRV_FOREACH(q, p + 1) + if (strneq(*p, *q, k) && (*q)[k] == '=') + return false; + } + + return true; +} + +bool strv_env_name_is_valid(char **l) { + char **p, **q; + + STRV_FOREACH(p, l) { + if (!env_name_is_valid(*p)) + return false; + + STRV_FOREACH(q, p + 1) + if (streq(*p, *q)) + return false; + } + + return true; +} + +bool strv_env_name_or_assignment_is_valid(char **l) { + char **p, **q; + + STRV_FOREACH(p, l) { + if (!env_assignment_is_valid(*p) && !env_name_is_valid(*p)) + return false; + + STRV_FOREACH(q, p + 1) + if (streq(*p, *q)) + return false; + } + + return true; +} + +static int env_append(char **r, char ***k, char **a) { + assert(r); + assert(k); + + if (!a) + return 0; + + /* Add the entries of a to *k unless they already exist in *r + * in which case they are overridden instead. This assumes + * there is enough space in the r array. */ + + for (; *a; a++) { + char **j; + size_t n; + + n = strcspn(*a, "="); + + if ((*a)[n] == '=') + n++; + + for (j = r; j < *k; j++) + if (strneq(*j, *a, n)) + break; + + if (j >= *k) + (*k)++; + else + free(*j); + + *j = strdup(*a); + if (!*j) + return -ENOMEM; + } + + return 0; +} + +char **strv_env_merge(unsigned n_lists, ...) { + size_t n = 0; + char **l, **k, **r; + va_list ap; + unsigned i; + + /* Merges an arbitrary number of environment sets */ + + va_start(ap, n_lists); + for (i = 0; i < n_lists; i++) { + l = va_arg(ap, char**); + n += strv_length(l); + } + va_end(ap); + + r = new(char*, n+1); + if (!r) + return NULL; + + k = r; + + va_start(ap, n_lists); + for (i = 0; i < n_lists; i++) { + l = va_arg(ap, char**); + if (env_append(r, &k, l) < 0) + goto fail; + } + va_end(ap); + + *k = NULL; + + return r; + +fail: + va_end(ap); + strv_free(r); + + return NULL; +} + +_pure_ static bool env_match(const char *t, const char *pattern) { + assert(t); + assert(pattern); + + /* pattern a matches string a + * a matches a= + * a matches a=b + * a= matches a= + * a=b matches a=b + * a= does not match a + * a=b does not match a= + * a=b does not match a + * a=b does not match a=c */ + + if (streq(t, pattern)) + return true; + + if (!strchr(pattern, '=')) { + size_t l = strlen(pattern); + + return strneq(t, pattern, l) && t[l] == '='; + } + + return false; +} + +char **strv_env_delete(char **x, unsigned n_lists, ...) { + size_t n, i = 0; + char **k, **r; + va_list ap; + + /* Deletes every entry from x that is mentioned in the other + * string lists */ + + n = strv_length(x); + + r = new(char*, n+1); + if (!r) + return NULL; + + STRV_FOREACH(k, x) { + unsigned v; + + va_start(ap, n_lists); + for (v = 0; v < n_lists; v++) { + char **l, **j; + + l = va_arg(ap, char**); + STRV_FOREACH(j, l) + if (env_match(*k, *j)) + goto skip; + } + va_end(ap); + + r[i] = strdup(*k); + if (!r[i]) { + strv_free(r); + return NULL; + } + + i++; + continue; + + skip: + va_end(ap); + } + + r[i] = NULL; + + assert(i <= n); + + return r; +} + +char **strv_env_unset(char **l, const char *p) { + + char **f, **t; + + if (!l) + return NULL; + + assert(p); + + /* Drops every occurrence of the env var setting p in the + * string list. Edits in-place. */ + + for (f = t = l; *f; f++) { + + if (env_match(*f, p)) { + free(*f); + continue; + } + + *(t++) = *f; + } + + *t = NULL; + return l; +} + +char **strv_env_unset_many(char **l, ...) { + + char **f, **t; + + if (!l) + return NULL; + + /* Like strv_env_unset() but applies many at once. Edits in-place. */ + + for (f = t = l; *f; f++) { + bool found = false; + const char *p; + va_list ap; + + va_start(ap, l); + + while ((p = va_arg(ap, const char*))) { + if (env_match(*f, p)) { + found = true; + break; + } + } + + va_end(ap); + + if (found) { + free(*f); + continue; + } + + *(t++) = *f; + } + + *t = NULL; + return l; +} + +char **strv_env_set(char **x, const char *p) { + + char **k, **r; + char* m[2] = { (char*) p, NULL }; + + /* Overrides the env var setting of p, returns a new copy */ + + r = new(char*, strv_length(x)+2); + if (!r) + return NULL; + + k = r; + if (env_append(r, &k, x) < 0) + goto fail; + + if (env_append(r, &k, m) < 0) + goto fail; + + *k = NULL; + + return r; + +fail: + strv_free(r); + return NULL; +} + +char *strv_env_get_n(char **l, const char *name, size_t k) { + char **i; + + assert(name); + + if (k <= 0) + return NULL; + + STRV_FOREACH(i, l) + if (strneq(*i, name, k) && + (*i)[k] == '=') + return *i + k + 1; + + return NULL; +} + +char *strv_env_get(char **l, const char *name) { + assert(name); + + return strv_env_get_n(l, name, strlen(name)); +} + +char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) { + char **p, **q; + int k = 0; + + STRV_FOREACH(p, e) { + size_t n; + bool duplicate = false; + + if (!env_assignment_is_valid(*p)) { + if (invalid_callback) + invalid_callback(*p, userdata); + free(*p); + continue; + } + + n = strcspn(*p, "="); + STRV_FOREACH(q, p + 1) + if (strneq(*p, *q, n) && (*q)[n] == '=') { + duplicate = true; + break; + } + + if (duplicate) { + free(*p); + continue; + } + + e[k++] = *p; + } + + if (e) + e[k] = NULL; + + return e; +} + +char *replace_env(const char *format, char **env) { + enum { + WORD, + CURLY, + VARIABLE + } state = WORD; + + const char *e, *word = format; + char *r = NULL, *k; + + assert(format); + + for (e = format; *e; e ++) { + + switch (state) { + + case WORD: + if (*e == '$') + state = CURLY; + break; + + case CURLY: + if (*e == '{') { + k = strnappend(r, word, e-word-1); + if (!k) + goto fail; + + free(r); + r = k; + + word = e-1; + state = VARIABLE; + + } else if (*e == '$') { + k = strnappend(r, word, e-word); + if (!k) + goto fail; + + free(r); + r = k; + + word = e+1; + state = WORD; + } else + state = WORD; + break; + + case VARIABLE: + if (*e == '}') { + const char *t; + + t = strempty(strv_env_get_n(env, word+2, e-word-2)); + + k = strappend(r, t); + if (!k) + goto fail; + + free(r); + r = k; + + word = e+1; + state = WORD; + } + break; + } + } + + k = strnappend(r, word, e-word); + if (!k) + goto fail; + + free(r); + return k; + +fail: + free(r); + return NULL; +} + +char **replace_env_argv(char **argv, char **env) { + char **ret, **i; + unsigned k = 0, l = 0; + + l = strv_length(argv); + + ret = new(char*, l+1); + if (!ret) + return NULL; + + STRV_FOREACH(i, argv) { + + /* If $FOO appears as single word, replace it by the split up variable */ + if ((*i)[0] == '$' && (*i)[1] != '{' && (*i)[1] != '$') { + char *e; + char **w, **m = NULL; + unsigned q; + + e = strv_env_get(env, *i+1); + if (e) { + int r; + + r = strv_split_extract(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_QUOTES); + if (r < 0) { + ret[k] = NULL; + strv_free(ret); + return NULL; + } + } else + m = NULL; + + q = strv_length(m); + l = l + q - 1; + + w = realloc(ret, sizeof(char*) * (l+1)); + if (!w) { + ret[k] = NULL; + strv_free(ret); + strv_free(m); + return NULL; + } + + ret = w; + if (m) { + memcpy(ret + k, m, q * sizeof(char*)); + free(m); + } + + k += q; + continue; + } + + /* If ${FOO} appears as part of a word, replace it by the variable as-is */ + ret[k] = replace_env(*i, env); + if (!ret[k]) { + strv_free(ret); + return NULL; + } + k++; + } + + ret[k] = NULL; + return ret; +} + +int getenv_bool(const char *p) { + const char *e; + + e = getenv(p); + if (!e) + return -ENXIO; + + return parse_boolean(e); +} diff --git a/src/libbasic/env-util.h b/src/libbasic/env-util.h new file mode 100644 index 0000000000..b1fef704c2 --- /dev/null +++ b/src/libbasic/env-util.h @@ -0,0 +1,51 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include "macro.h" + +bool env_name_is_valid(const char *e); +bool env_value_is_valid(const char *e); +bool env_assignment_is_valid(const char *e); + +char *replace_env(const char *format, char **env); +char **replace_env_argv(char **argv, char **env); + +bool strv_env_is_valid(char **e); +#define strv_env_clean(l) strv_env_clean_with_callback(l, NULL, NULL) +char **strv_env_clean_with_callback(char **l, void (*invalid_callback)(const char *p, void *userdata), void *userdata); + +bool strv_env_name_is_valid(char **l); +bool strv_env_name_or_assignment_is_valid(char **l); + +char **strv_env_merge(unsigned n_lists, ...); +char **strv_env_delete(char **x, unsigned n_lists, ...); /* New copy */ + +char **strv_env_set(char **x, const char *p); /* New copy ... */ +char **strv_env_unset(char **l, const char *p); /* In place ... */ +char **strv_env_unset_many(char **l, ...) _sentinel_; + +char *strv_env_get_n(char **l, const char *name, size_t k) _pure_; +char *strv_env_get(char **x, const char *n) _pure_; + +int getenv_bool(const char *p); diff --git a/src/libbasic/errno-list.c b/src/libbasic/errno-list.c new file mode 100644 index 0000000000..31b66bad5e --- /dev/null +++ b/src/libbasic/errno-list.c @@ -0,0 +1,57 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "errno-list.h" +#include "macro.h" + +static const struct errno_name* lookup_errno(register const char *str, + register unsigned int len); + +#include "errno-from-name.h" +#include "errno-to-name.h" + +const char *errno_to_name(int id) { + + if (id < 0) + id = -id; + + if (id >= (int) ELEMENTSOF(errno_names)) + return NULL; + + return errno_names[id]; +} + +int errno_from_name(const char *name) { + const struct errno_name *sc; + + assert(name); + + sc = lookup_errno(name, strlen(name)); + if (!sc) + return -EINVAL; + + assert(sc->id > 0); + return sc->id; +} + +int errno_max(void) { + return ELEMENTSOF(errno_names); +} diff --git a/src/libbasic/errno-list.h b/src/libbasic/errno-list.h new file mode 100644 index 0000000000..4eec0cc786 --- /dev/null +++ b/src/libbasic/errno-list.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +const char *errno_to_name(int id); +int errno_from_name(const char *name); + +int errno_max(void); diff --git a/src/libbasic/escape.c b/src/libbasic/escape.c new file mode 100644 index 0000000000..01daf11ce7 --- /dev/null +++ b/src/libbasic/escape.c @@ -0,0 +1,502 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include + +#include "alloc-util.h" +#include "escape.h" +#include "hexdecoct.h" +#include "macro.h" +#include "utf8.h" + +size_t cescape_char(char c, char *buf) { + char * buf_old = buf; + + switch (c) { + + case '\a': + *(buf++) = '\\'; + *(buf++) = 'a'; + break; + case '\b': + *(buf++) = '\\'; + *(buf++) = 'b'; + break; + case '\f': + *(buf++) = '\\'; + *(buf++) = 'f'; + break; + case '\n': + *(buf++) = '\\'; + *(buf++) = 'n'; + break; + case '\r': + *(buf++) = '\\'; + *(buf++) = 'r'; + break; + case '\t': + *(buf++) = '\\'; + *(buf++) = 't'; + break; + case '\v': + *(buf++) = '\\'; + *(buf++) = 'v'; + break; + case '\\': + *(buf++) = '\\'; + *(buf++) = '\\'; + break; + case '"': + *(buf++) = '\\'; + *(buf++) = '"'; + break; + case '\'': + *(buf++) = '\\'; + *(buf++) = '\''; + break; + + default: + /* For special chars we prefer octal over + * hexadecimal encoding, simply because glib's + * g_strescape() does the same */ + if ((c < ' ') || (c >= 127)) { + *(buf++) = '\\'; + *(buf++) = octchar((unsigned char) c >> 6); + *(buf++) = octchar((unsigned char) c >> 3); + *(buf++) = octchar((unsigned char) c); + } else + *(buf++) = c; + break; + } + + return buf - buf_old; +} + +char *cescape_length(const char *s, size_t n) { + const char *f; + char *r, *t; + + assert(s || n == 0); + + /* Does C style string escaping. May be reversed with + * cunescape(). */ + + r = new(char, n*4 + 1); + if (!r) + return NULL; + + for (f = s, t = r; f < s + n; f++) + t += cescape_char(*f, t); + + *t = 0; + + return r; +} + +char *cescape(const char *s) { + assert(s); + + return cescape_length(s, strlen(s)); +} + +int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit) { + int r = 1; + + assert(p); + assert(*p); + assert(ret); + + /* Unescapes C style. Returns the unescaped character in ret. + * Sets *eight_bit to true if the escaped sequence either fits in + * one byte in UTF-8 or is a non-unicode literal byte and should + * instead be copied directly. + */ + + if (length != (size_t) -1 && length < 1) + return -EINVAL; + + switch (p[0]) { + + case 'a': + *ret = '\a'; + break; + case 'b': + *ret = '\b'; + break; + case 'f': + *ret = '\f'; + break; + case 'n': + *ret = '\n'; + break; + case 'r': + *ret = '\r'; + break; + case 't': + *ret = '\t'; + break; + case 'v': + *ret = '\v'; + break; + case '\\': + *ret = '\\'; + break; + case '"': + *ret = '"'; + break; + case '\'': + *ret = '\''; + break; + + case 's': + /* This is an extension of the XDG syntax files */ + *ret = ' '; + break; + + case 'x': { + /* hexadecimal encoding */ + int a, b; + + if (length != (size_t) -1 && length < 3) + return -EINVAL; + + a = unhexchar(p[1]); + if (a < 0) + return -EINVAL; + + b = unhexchar(p[2]); + if (b < 0) + return -EINVAL; + + /* Don't allow NUL bytes */ + if (a == 0 && b == 0) + return -EINVAL; + + *ret = (a << 4U) | b; + *eight_bit = true; + r = 3; + break; + } + + case 'u': { + /* C++11 style 16bit unicode */ + + int a[4]; + unsigned i; + uint32_t c; + + if (length != (size_t) -1 && length < 5) + return -EINVAL; + + for (i = 0; i < 4; i++) { + a[i] = unhexchar(p[1 + i]); + if (a[i] < 0) + return a[i]; + } + + c = ((uint32_t) a[0] << 12U) | ((uint32_t) a[1] << 8U) | ((uint32_t) a[2] << 4U) | (uint32_t) a[3]; + + /* Don't allow 0 chars */ + if (c == 0) + return -EINVAL; + + *ret = c; + r = 5; + break; + } + + case 'U': { + /* C++11 style 32bit unicode */ + + int a[8]; + unsigned i; + char32_t c; + + if (length != (size_t) -1 && length < 9) + return -EINVAL; + + for (i = 0; i < 8; i++) { + a[i] = unhexchar(p[1 + i]); + if (a[i] < 0) + return a[i]; + } + + c = ((uint32_t) a[0] << 28U) | ((uint32_t) a[1] << 24U) | ((uint32_t) a[2] << 20U) | ((uint32_t) a[3] << 16U) | + ((uint32_t) a[4] << 12U) | ((uint32_t) a[5] << 8U) | ((uint32_t) a[6] << 4U) | (uint32_t) a[7]; + + /* Don't allow 0 chars */ + if (c == 0) + return -EINVAL; + + /* Don't allow invalid code points */ + if (!unichar_is_valid(c)) + return -EINVAL; + + *ret = c; + r = 9; + break; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': { + /* octal encoding */ + int a, b, c; + char32_t m; + + if (length != (size_t) -1 && length < 3) + return -EINVAL; + + a = unoctchar(p[0]); + if (a < 0) + return -EINVAL; + + b = unoctchar(p[1]); + if (b < 0) + return -EINVAL; + + c = unoctchar(p[2]); + if (c < 0) + return -EINVAL; + + /* don't allow NUL bytes */ + if (a == 0 && b == 0 && c == 0) + return -EINVAL; + + /* Don't allow bytes above 255 */ + m = ((uint32_t) a << 6U) | ((uint32_t) b << 3U) | (uint32_t) c; + if (m > 255) + return -EINVAL; + + *ret = m; + *eight_bit = true; + r = 3; + break; + } + + default: + return -EINVAL; + } + + return r; +} + +int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) { + char *r, *t; + const char *f; + size_t pl; + + assert(s); + assert(ret); + + /* Undoes C style string escaping, and optionally prefixes it. */ + + pl = prefix ? strlen(prefix) : 0; + + r = new(char, pl+length+1); + if (!r) + return -ENOMEM; + + if (prefix) + memcpy(r, prefix, pl); + + for (f = s, t = r + pl; f < s + length; f++) { + size_t remaining; + bool eight_bit = false; + char32_t u; + int k; + + remaining = s + length - f; + assert(remaining > 0); + + if (*f != '\\') { + /* A literal literal, copy verbatim */ + *(t++) = *f; + continue; + } + + if (remaining == 1) { + if (flags & UNESCAPE_RELAX) { + /* A trailing backslash, copy verbatim */ + *(t++) = *f; + continue; + } + + free(r); + return -EINVAL; + } + + k = cunescape_one(f + 1, remaining - 1, &u, &eight_bit); + if (k < 0) { + if (flags & UNESCAPE_RELAX) { + /* Invalid escape code, let's take it literal then */ + *(t++) = '\\'; + continue; + } + + free(r); + return k; + } + + f += k; + if (eight_bit) + /* One byte? Set directly as specified */ + *(t++) = u; + else + /* Otherwise encode as multi-byte UTF-8 */ + t += utf8_encode_unichar(t, u); + } + + *t = 0; + + *ret = r; + return t - r; +} + +int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) { + return cunescape_length_with_prefix(s, length, NULL, flags, ret); +} + +int cunescape(const char *s, UnescapeFlags flags, char **ret) { + return cunescape_length(s, strlen(s), flags, ret); +} + +char *xescape(const char *s, const char *bad) { + char *r, *t; + const char *f; + + /* Escapes all chars in bad, in addition to \ and all special + * chars, in \xFF style escaping. May be reversed with + * cunescape(). */ + + r = new(char, strlen(s) * 4 + 1); + if (!r) + return NULL; + + for (f = s, t = r; *f; f++) { + + if ((*f < ' ') || (*f >= 127) || + (*f == '\\') || strchr(bad, *f)) { + *(t++) = '\\'; + *(t++) = 'x'; + *(t++) = hexchar(*f >> 4); + *(t++) = hexchar(*f); + } else + *(t++) = *f; + } + + *t = 0; + + return r; +} + +char *octescape(const char *s, size_t len) { + char *r, *t; + const char *f; + + /* Escapes all chars in bad, in addition to \ and " chars, + * in \nnn style escaping. */ + + r = new(char, len * 4 + 1); + if (!r) + return NULL; + + for (f = s, t = r; f < s + len; f++) { + + if (*f < ' ' || *f >= 127 || *f == '\\' || *f == '"') { + *(t++) = '\\'; + *(t++) = '0' + (*f >> 6); + *(t++) = '0' + ((*f >> 3) & 8); + *(t++) = '0' + (*f & 8); + } else + *(t++) = *f; + } + + *t = 0; + + return r; + +} + +static char *strcpy_backslash_escaped(char *t, const char *s, const char *bad) { + assert(bad); + + for (; *s; s++) { + if (*s == '\\' || strchr(bad, *s)) + *(t++) = '\\'; + + *(t++) = *s; + } + + return t; +} + +char *shell_escape(const char *s, const char *bad) { + char *r, *t; + + r = new(char, strlen(s)*2+1); + if (!r) + return NULL; + + t = strcpy_backslash_escaped(r, s, bad); + *t = 0; + + return r; +} + +char *shell_maybe_quote(const char *s) { + const char *p; + char *r, *t; + + assert(s); + + /* Encloses a string in double quotes if necessary to make it + * OK as shell string. */ + + for (p = s; *p; p++) + if (*p <= ' ' || + *p >= 127 || + strchr(SHELL_NEED_QUOTES, *p)) + break; + + if (!*p) + return strdup(s); + + r = new(char, 1+strlen(s)*2+1+1); + if (!r) + return NULL; + + t = r; + *(t++) = '"'; + t = mempcpy(t, s, p - s); + + t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE); + + *(t++)= '"'; + *t = 0; + + return r; +} diff --git a/src/libbasic/escape.h b/src/libbasic/escape.h new file mode 100644 index 0000000000..deaa4def28 --- /dev/null +++ b/src/libbasic/escape.h @@ -0,0 +1,54 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include + +#include "string-util.h" +#include "missing.h" + +/* What characters are special in the shell? */ +/* must be escaped outside and inside double-quotes */ +#define SHELL_NEED_ESCAPE "\"\\`$" +/* can be escaped or double-quoted */ +#define SHELL_NEED_QUOTES SHELL_NEED_ESCAPE GLOB_CHARS "'()<>|&;" + +typedef enum UnescapeFlags { + UNESCAPE_RELAX = 1, +} UnescapeFlags; + +char *cescape(const char *s); +char *cescape_length(const char *s, size_t n); +size_t cescape_char(char c, char *buf); + +int cunescape(const char *s, UnescapeFlags flags, char **ret); +int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret); +int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret); +int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit); + +char *xescape(const char *s, const char *bad); +char *octescape(const char *s, size_t len); + +char *shell_escape(const char *s, const char *bad); +char *shell_maybe_quote(const char *s); diff --git a/src/libbasic/ether-addr-util.c b/src/libbasic/ether-addr-util.c new file mode 100644 index 0000000000..5697e8d132 --- /dev/null +++ b/src/libbasic/ether-addr-util.c @@ -0,0 +1,125 @@ +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 +#include + +#include "ether-addr-util.h" +#include "macro.h" +#include "string-util.h" + +char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR_TO_STRING_MAX]) { + assert(addr); + assert(buffer); + + /* Like ether_ntoa() but uses %02x instead of %x to print + * ethernet addresses, which makes them look less funny. Also, + * doesn't use a static buffer. */ + + sprintf(buffer, "%02x:%02x:%02x:%02x:%02x:%02x", + addr->ether_addr_octet[0], + addr->ether_addr_octet[1], + addr->ether_addr_octet[2], + addr->ether_addr_octet[3], + addr->ether_addr_octet[4], + addr->ether_addr_octet[5]); + + return buffer; +} + +bool ether_addr_equal(const struct ether_addr *a, const struct ether_addr *b) { + assert(a); + assert(b); + + return a->ether_addr_octet[0] == b->ether_addr_octet[0] && + a->ether_addr_octet[1] == b->ether_addr_octet[1] && + a->ether_addr_octet[2] == b->ether_addr_octet[2] && + a->ether_addr_octet[3] == b->ether_addr_octet[3] && + a->ether_addr_octet[4] == b->ether_addr_octet[4] && + a->ether_addr_octet[5] == b->ether_addr_octet[5]; +} + +int ether_addr_from_string(const char *s, struct ether_addr *ret, size_t *offset) { + size_t pos = 0, n, field; + char sep = '\0'; + const char *hex = HEXDIGITS, *hexoff; + size_t x; + bool touched; + +#define parse_fields(v) \ + for (field = 0; field < ELEMENTSOF(v); field++) { \ + touched = false; \ + for (n = 0; n < (2 * sizeof(v[0])); n++) { \ + if (s[pos] == '\0') \ + break; \ + hexoff = strchr(hex, s[pos]); \ + if (hexoff == NULL) \ + break; \ + assert(hexoff >= hex); \ + x = hexoff - hex; \ + if (x >= 16) \ + x -= 6; /* A-F */ \ + assert(x < 16); \ + touched = true; \ + v[field] <<= 4; \ + v[field] += x; \ + pos++; \ + } \ + if (!touched) \ + return -EINVAL; \ + if (field < (ELEMENTSOF(v)-1)) { \ + if (s[pos] != sep) \ + return -EINVAL; \ + else \ + pos++; \ + } \ + } + + assert(s); + assert(ret); + + sep = s[strspn(s, hex)]; + if (sep == '\n') + return -EINVAL; + if (strchr(":.-", sep) == NULL) + return -EINVAL; + + if (sep == '.') { + uint16_t shorts[3] = { 0 }; + + parse_fields(shorts); + + for (n = 0; n < ELEMENTSOF(shorts); n++) { + ret->ether_addr_octet[2*n] = ((shorts[n] & (uint16_t)0xff00) >> 8); + ret->ether_addr_octet[2*n + 1] = (shorts[n] & (uint16_t)0x00ff); + } + } else { + struct ether_addr out = { .ether_addr_octet = { 0 } }; + + parse_fields(out.ether_addr_octet); + + for (n = 0; n < ELEMENTSOF(out.ether_addr_octet); n++) + ret->ether_addr_octet[n] = out.ether_addr_octet[n]; + } + + if (offset) + *offset = pos; + return 0; +} diff --git a/src/libbasic/ether-addr-util.h b/src/libbasic/ether-addr-util.h new file mode 100644 index 0000000000..74e125a95f --- /dev/null +++ b/src/libbasic/ether-addr-util.h @@ -0,0 +1,39 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 + +#define ETHER_ADDR_FORMAT_STR "%02X%02X%02X%02X%02X%02X" +#define ETHER_ADDR_FORMAT_VAL(x) (x).ether_addr_octet[0], (x).ether_addr_octet[1], (x).ether_addr_octet[2], (x).ether_addr_octet[3], (x).ether_addr_octet[4], (x).ether_addr_octet[5] + +#define ETHER_ADDR_TO_STRING_MAX (3*6) +char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR_TO_STRING_MAX]); + +bool ether_addr_equal(const struct ether_addr *a, const struct ether_addr *b); + +#define ETHER_ADDR_NULL ((const struct ether_addr){}) + +static inline bool ether_addr_is_null(const struct ether_addr *addr) { + return ether_addr_equal(addr, ÐER_ADDR_NULL); +} + +int ether_addr_from_string(const char *s, struct ether_addr *ret, size_t *offset); diff --git a/src/libbasic/exit-status.c b/src/libbasic/exit-status.c new file mode 100644 index 0000000000..92fa5ace61 --- /dev/null +++ b/src/libbasic/exit-status.c @@ -0,0 +1,240 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 + +#include "exit-status.h" +#include "macro.h" +#include "set.h" + +const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) { + + /* We cast to int here, so that -Wenum doesn't complain that + * EXIT_SUCCESS/EXIT_FAILURE aren't in the enum */ + + switch ((int) status) { + + case EXIT_SUCCESS: + return "SUCCESS"; + + case EXIT_FAILURE: + return "FAILURE"; + } + + + if (level == EXIT_STATUS_SYSTEMD || level == EXIT_STATUS_LSB) { + switch ((int) status) { + + case EXIT_CHDIR: + return "CHDIR"; + + case EXIT_NICE: + return "NICE"; + + case EXIT_FDS: + return "FDS"; + + case EXIT_EXEC: + return "EXEC"; + + case EXIT_MEMORY: + return "MEMORY"; + + case EXIT_LIMITS: + return "LIMITS"; + + case EXIT_OOM_ADJUST: + return "OOM_ADJUST"; + + case EXIT_SIGNAL_MASK: + return "SIGNAL_MASK"; + + case EXIT_STDIN: + return "STDIN"; + + case EXIT_STDOUT: + return "STDOUT"; + + case EXIT_CHROOT: + return "CHROOT"; + + case EXIT_IOPRIO: + return "IOPRIO"; + + case EXIT_TIMERSLACK: + return "TIMERSLACK"; + + case EXIT_SECUREBITS: + return "SECUREBITS"; + + case EXIT_SETSCHEDULER: + return "SETSCHEDULER"; + + case EXIT_CPUAFFINITY: + return "CPUAFFINITY"; + + case EXIT_GROUP: + return "GROUP"; + + case EXIT_USER: + return "USER"; + + case EXIT_CAPABILITIES: + return "CAPABILITIES"; + + case EXIT_CGROUP: + return "CGROUP"; + + case EXIT_SETSID: + return "SETSID"; + + case EXIT_CONFIRM: + return "CONFIRM"; + + case EXIT_STDERR: + return "STDERR"; + + case EXIT_PAM: + return "PAM"; + + case EXIT_NETWORK: + return "NETWORK"; + + case EXIT_NAMESPACE: + return "NAMESPACE"; + + case EXIT_NO_NEW_PRIVILEGES: + return "NO_NEW_PRIVILEGES"; + + case EXIT_SECCOMP: + return "SECCOMP"; + + case EXIT_SELINUX_CONTEXT: + return "SELINUX_CONTEXT"; + + case EXIT_PERSONALITY: + return "PERSONALITY"; + + case EXIT_APPARMOR_PROFILE: + return "APPARMOR"; + + case EXIT_ADDRESS_FAMILIES: + return "ADDRESS_FAMILIES"; + + case EXIT_RUNTIME_DIRECTORY: + return "RUNTIME_DIRECTORY"; + + case EXIT_CHOWN: + return "CHOWN"; + + case EXIT_MAKE_STARTER: + return "MAKE_STARTER"; + + case EXIT_SMACK_PROCESS_LABEL: + return "SMACK_PROCESS_LABEL"; + } + } + + if (level == EXIT_STATUS_LSB) { + switch ((int) status) { + + case EXIT_INVALIDARGUMENT: + return "INVALIDARGUMENT"; + + case EXIT_NOTIMPLEMENTED: + return "NOTIMPLEMENTED"; + + case EXIT_NOPERMISSION: + return "NOPERMISSION"; + + case EXIT_NOTINSTALLED: + return "NOTINSTALLED"; + + case EXIT_NOTCONFIGURED: + return "NOTCONFIGURED"; + + case EXIT_NOTRUNNING: + return "NOTRUNNING"; + } + } + + return NULL; +} + + +bool is_clean_exit(int code, int status, ExitStatusSet *success_status) { + + if (code == CLD_EXITED) + return status == 0 || + (success_status && + set_contains(success_status->status, INT_TO_PTR(status))); + + /* If a daemon does not implement handlers for some of the + * signals that's not considered an unclean shutdown */ + if (code == CLD_KILLED) + return + status == SIGHUP || + status == SIGINT || + status == SIGTERM || + status == SIGPIPE || + (success_status && + set_contains(success_status->signal, INT_TO_PTR(status))); + + return false; +} + +bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status) { + + if (is_clean_exit(code, status, success_status)) + return true; + + return + code == CLD_EXITED && + (status == EXIT_NOTINSTALLED || status == EXIT_NOTCONFIGURED); +} + +void exit_status_set_free(ExitStatusSet *x) { + assert(x); + + set_free(x->status); + set_free(x->signal); + x->status = x->signal = NULL; +} + +bool exit_status_set_is_empty(ExitStatusSet *x) { + if (!x) + return true; + + return set_isempty(x->status) && set_isempty(x->signal); +} + +bool exit_status_set_test(ExitStatusSet *x, int code, int status) { + + if (exit_status_set_is_empty(x)) + return false; + + if (code == CLD_EXITED && set_contains(x->status, INT_TO_PTR(status))) + return true; + + if (IN_SET(code, CLD_KILLED, CLD_DUMPED) && set_contains(x->signal, INT_TO_PTR(status))) + return true; + + return false; +} diff --git a/src/libbasic/exit-status.h b/src/libbasic/exit-status.h new file mode 100644 index 0000000000..1208c8feed --- /dev/null +++ b/src/libbasic/exit-status.h @@ -0,0 +1,102 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "hashmap.h" +#include "macro.h" +#include "set.h" + +typedef enum ExitStatus { + /* EXIT_SUCCESS defined by libc */ + /* EXIT_FAILURE defined by libc */ + EXIT_INVALIDARGUMENT = 2, + EXIT_NOTIMPLEMENTED = 3, + EXIT_NOPERMISSION = 4, + EXIT_NOTINSTALLED = 5, + EXIT_NOTCONFIGURED = 6, + EXIT_NOTRUNNING = 7, + + /* The LSB suggests that error codes >= 200 are "reserved". We + * use them here under the assumption that they hence are + * unused by init scripts. + * + * http://refspecs.linuxfoundation.org/LSB_3.2.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html */ + + EXIT_CHDIR = 200, + EXIT_NICE, + EXIT_FDS, + EXIT_EXEC, + EXIT_MEMORY, + EXIT_LIMITS, + EXIT_OOM_ADJUST, + EXIT_SIGNAL_MASK, + EXIT_STDIN, + EXIT_STDOUT, + EXIT_CHROOT, /* 210 */ + EXIT_IOPRIO, + EXIT_TIMERSLACK, + EXIT_SECUREBITS, + EXIT_SETSCHEDULER, + EXIT_CPUAFFINITY, + EXIT_GROUP, + EXIT_USER, + EXIT_CAPABILITIES, + EXIT_CGROUP, + EXIT_SETSID, /* 220 */ + EXIT_CONFIRM, + EXIT_STDERR, + _EXIT_RESERVED, /* used to be tcpwrap, don't reuse! */ + EXIT_PAM, + EXIT_NETWORK, + EXIT_NAMESPACE, + EXIT_NO_NEW_PRIVILEGES, + EXIT_SECCOMP, + EXIT_SELINUX_CONTEXT, + EXIT_PERSONALITY, /* 230 */ + EXIT_APPARMOR_PROFILE, + EXIT_ADDRESS_FAMILIES, + EXIT_RUNTIME_DIRECTORY, + EXIT_MAKE_STARTER, + EXIT_CHOWN, + EXIT_SMACK_PROCESS_LABEL, +} ExitStatus; + +typedef enum ExitStatusLevel { + EXIT_STATUS_MINIMAL, + EXIT_STATUS_SYSTEMD, + EXIT_STATUS_LSB, + EXIT_STATUS_FULL = EXIT_STATUS_LSB +} ExitStatusLevel; + +typedef struct ExitStatusSet { + Set *status; + Set *signal; +} ExitStatusSet; + +const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) _const_; + +bool is_clean_exit(int code, int status, ExitStatusSet *success_status); +bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status); + +void exit_status_set_free(ExitStatusSet *x); +bool exit_status_set_is_empty(ExitStatusSet *x); +bool exit_status_set_test(ExitStatusSet *x, int code, int status); diff --git a/src/libbasic/extract-word.c b/src/libbasic/extract-word.c new file mode 100644 index 0000000000..d6c1228463 --- /dev/null +++ b/src/libbasic/extract-word.c @@ -0,0 +1,298 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "escape.h" +#include "extract-word.h" +#include "log.h" +#include "macro.h" +#include "string-util.h" +#include "utf8.h" + +int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags) { + _cleanup_free_ char *s = NULL; + size_t allocated = 0, sz = 0; + char c; + int r; + + char quote = 0; /* 0 or ' or " */ + bool backslash = false; /* whether we've just seen a backslash */ + + assert(p); + assert(ret); + + /* Bail early if called after last value or with no input */ + if (!*p) + goto finish_force_terminate; + c = **p; + + if (!separators) + separators = WHITESPACE; + + /* Parses the first word of a string, and returns it in + * *ret. Removes all quotes in the process. When parsing fails + * (because of an uneven number of quotes or similar), leaves + * the pointer *p at the first invalid character. */ + + if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) + if (!GREEDY_REALLOC(s, allocated, sz+1)) + return -ENOMEM; + + for (;; (*p)++, c = **p) { + if (c == 0) + goto finish_force_terminate; + else if (strchr(separators, c)) { + if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) { + (*p)++; + goto finish_force_next; + } + } else { + /* We found a non-blank character, so we will always + * want to return a string (even if it is empty), + * allocate it here. */ + if (!GREEDY_REALLOC(s, allocated, sz+1)) + return -ENOMEM; + break; + } + } + + for (;; (*p)++, c = **p) { + if (backslash) { + if (!GREEDY_REALLOC(s, allocated, sz+7)) + return -ENOMEM; + + if (c == 0) { + if ((flags & EXTRACT_CUNESCAPE_RELAX) && + (!quote || flags & EXTRACT_RELAX)) { + /* If we find an unquoted trailing backslash and we're in + * EXTRACT_CUNESCAPE_RELAX mode, keep it verbatim in the + * output. + * + * Unbalanced quotes will only be allowed in EXTRACT_RELAX + * mode, EXTRACT_CUNESCAPE_RELAX mode does not allow them. + */ + s[sz++] = '\\'; + goto finish_force_terminate; + } + if (flags & EXTRACT_RELAX) + goto finish_force_terminate; + return -EINVAL; + } + + if (flags & EXTRACT_CUNESCAPE) { + bool eight_bit = false; + char32_t u; + + r = cunescape_one(*p, (size_t) -1, &u, &eight_bit); + if (r < 0) { + if (flags & EXTRACT_CUNESCAPE_RELAX) { + s[sz++] = '\\'; + s[sz++] = c; + } else + return -EINVAL; + } else { + (*p) += r - 1; + + if (eight_bit) + s[sz++] = u; + else + sz += utf8_encode_unichar(s + sz, u); + } + } else + s[sz++] = c; + + backslash = false; + + } else if (quote) { /* inside either single or double quotes */ + for (;; (*p)++, c = **p) { + if (c == 0) { + if (flags & EXTRACT_RELAX) + goto finish_force_terminate; + return -EINVAL; + } else if (c == quote) { /* found the end quote */ + quote = 0; + break; + } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) { + backslash = true; + break; + } else { + if (!GREEDY_REALLOC(s, allocated, sz+2)) + return -ENOMEM; + + s[sz++] = c; + } + } + + } else { + for (;; (*p)++, c = **p) { + if (c == 0) + goto finish_force_terminate; + else if ((c == '\'' || c == '"') && (flags & EXTRACT_QUOTES)) { + quote = c; + break; + } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) { + backslash = true; + break; + } else if (strchr(separators, c)) { + if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) { + (*p)++; + goto finish_force_next; + } + /* Skip additional coalesced separators. */ + for (;; (*p)++, c = **p) { + if (c == 0) + goto finish_force_terminate; + if (!strchr(separators, c)) + break; + } + goto finish; + + } else { + if (!GREEDY_REALLOC(s, allocated, sz+2)) + return -ENOMEM; + + s[sz++] = c; + } + } + } + } + +finish_force_terminate: + *p = NULL; +finish: + if (!s) { + *p = NULL; + *ret = NULL; + return 0; + } + +finish_force_next: + s[sz] = 0; + *ret = s; + s = NULL; + + return 1; +} + +int extract_first_word_and_warn( + const char **p, + char **ret, + const char *separators, + ExtractFlags flags, + const char *unit, + const char *filename, + unsigned line, + const char *rvalue) { + + /* Try to unquote it, if it fails, warn about it and try again + * but this time using EXTRACT_CUNESCAPE_RELAX to keep the + * backslashes verbatim in invalid escape sequences. */ + + const char *save; + int r; + + save = *p; + r = extract_first_word(p, ret, separators, flags); + if (r >= 0) + return r; + + if (r == -EINVAL && !(flags & EXTRACT_CUNESCAPE_RELAX)) { + + /* Retry it with EXTRACT_CUNESCAPE_RELAX. */ + *p = save; + r = extract_first_word(p, ret, separators, flags|EXTRACT_CUNESCAPE_RELAX); + if (r >= 0) { + /* It worked this time, hence it must have been an invalid escape sequence we could correct. */ + log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Invalid escape sequences in line, correcting: \"%s\"", rvalue); + return r; + } + + /* If it's still EINVAL; then it must be unbalanced quoting, report this. */ + if (r == -EINVAL) + return log_syntax(unit, LOG_ERR, filename, line, r, "Unbalanced quoting, ignoring: \"%s\"", rvalue); + } + + /* Can be any error, report it */ + return log_syntax(unit, LOG_ERR, filename, line, r, "Unable to decode word \"%s\", ignoring: %m", rvalue); +} + +int extract_many_words(const char **p, const char *separators, ExtractFlags flags, ...) { + va_list ap; + char **l; + int n = 0, i, c, r; + + /* Parses a number of words from a string, stripping any + * quotes if necessary. */ + + assert(p); + + /* Count how many words are expected */ + va_start(ap, flags); + for (;;) { + if (!va_arg(ap, char **)) + break; + n++; + } + va_end(ap); + + if (n <= 0) + return 0; + + /* Read all words into a temporary array */ + l = newa0(char*, n); + for (c = 0; c < n; c++) { + + r = extract_first_word(p, &l[c], separators, flags); + if (r < 0) { + int j; + + for (j = 0; j < c; j++) + free(l[j]); + + return r; + } + + if (r == 0) + break; + } + + /* If we managed to parse all words, return them in the passed + * in parameters */ + va_start(ap, flags); + for (i = 0; i < n; i++) { + char **v; + + v = va_arg(ap, char **); + assert(v); + + *v = l[i]; + } + va_end(ap); + + return c; +} diff --git a/src/libbasic/extract-word.h b/src/libbasic/extract-word.h new file mode 100644 index 0000000000..21db5ef33f --- /dev/null +++ b/src/libbasic/extract-word.h @@ -0,0 +1,35 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "macro.h" + +typedef enum ExtractFlags { + EXTRACT_RELAX = 1, + EXTRACT_CUNESCAPE = 2, + EXTRACT_CUNESCAPE_RELAX = 4, + EXTRACT_QUOTES = 8, + EXTRACT_DONT_COALESCE_SEPARATORS = 16, + EXTRACT_RETAIN_ESCAPE = 32, +} ExtractFlags; + +int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags); +int extract_first_word_and_warn(const char **p, char **ret, const char *separators, ExtractFlags flags, const char *unit, const char *filename, unsigned line, const char *rvalue); +int extract_many_words(const char **p, const char *separators, ExtractFlags flags, ...) _sentinel_; diff --git a/src/libbasic/fd-util.c b/src/libbasic/fd-util.c new file mode 100644 index 0000000000..8b466cff15 --- /dev/null +++ b/src/libbasic/fd-util.c @@ -0,0 +1,374 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include + +#include "fd-util.h" +#include "fs-util.h" +#include "macro.h" +#include "missing.h" +#include "parse-util.h" +#include "path-util.h" +#include "socket-util.h" +#include "stdio-util.h" +#include "util.h" + +int close_nointr(int fd) { + assert(fd >= 0); + + if (close(fd) >= 0) + return 0; + + /* + * Just ignore EINTR; a retry loop is the wrong thing to do on + * Linux. + * + * http://lkml.indiana.edu/hypermail/linux/kernel/0509.1/0877.html + * https://bugzilla.gnome.org/show_bug.cgi?id=682819 + * http://utcc.utoronto.ca/~cks/space/blog/unix/CloseEINTR + * https://sites.google.com/site/michaelsafyan/software-engineering/checkforeintrwheninvokingclosethinkagain + */ + if (errno == EINTR) + return 0; + + return -errno; +} + +int safe_close(int fd) { + + /* + * Like close_nointr() but cannot fail. Guarantees errno is + * unchanged. Is a NOP with negative fds passed, and returns + * -1, so that it can be used in this syntax: + * + * fd = safe_close(fd); + */ + + if (fd >= 0) { + PROTECT_ERRNO; + + /* The kernel might return pretty much any error code + * via close(), but the fd will be closed anyway. The + * only condition we want to check for here is whether + * the fd was invalid at all... */ + + assert_se(close_nointr(fd) != -EBADF); + } + + return -1; +} + +void safe_close_pair(int p[]) { + assert(p); + + if (p[0] == p[1]) { + /* Special case pairs which use the same fd in both + * directions... */ + p[0] = p[1] = safe_close(p[0]); + return; + } + + p[0] = safe_close(p[0]); + p[1] = safe_close(p[1]); +} + +void close_many(const int fds[], unsigned n_fd) { + unsigned i; + + assert(fds || n_fd <= 0); + + for (i = 0; i < n_fd; i++) + safe_close(fds[i]); +} + +int fclose_nointr(FILE *f) { + assert(f); + + /* Same as close_nointr(), but for fclose() */ + + if (fclose(f) == 0) + return 0; + + if (errno == EINTR) + return 0; + + return -errno; +} + +FILE* safe_fclose(FILE *f) { + + /* Same as safe_close(), but for fclose() */ + + if (f) { + PROTECT_ERRNO; + + assert_se(fclose_nointr(f) != EBADF); + } + + return NULL; +} + +DIR* safe_closedir(DIR *d) { + + if (d) { + PROTECT_ERRNO; + + assert_se(closedir(d) >= 0 || errno != EBADF); + } + + return NULL; +} + +int fd_nonblock(int fd, bool nonblock) { + int flags, nflags; + + assert(fd >= 0); + + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) + return -errno; + + if (nonblock) + nflags = flags | O_NONBLOCK; + else + nflags = flags & ~O_NONBLOCK; + + if (nflags == flags) + return 0; + + if (fcntl(fd, F_SETFL, nflags) < 0) + return -errno; + + return 0; +} + +int fd_cloexec(int fd, bool cloexec) { + int flags, nflags; + + assert(fd >= 0); + + flags = fcntl(fd, F_GETFD, 0); + if (flags < 0) + return -errno; + + if (cloexec) + nflags = flags | FD_CLOEXEC; + else + nflags = flags & ~FD_CLOEXEC; + + if (nflags == flags) + return 0; + + if (fcntl(fd, F_SETFD, nflags) < 0) + return -errno; + + return 0; +} + +_pure_ static bool fd_in_set(int fd, const int fdset[], unsigned n_fdset) { + unsigned i; + + assert(n_fdset == 0 || fdset); + + for (i = 0; i < n_fdset; i++) + if (fdset[i] == fd) + return true; + + return false; +} + +int close_all_fds(const int except[], unsigned n_except) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r = 0; + + assert(n_except == 0 || except); + + d = opendir("/proc/self/fd"); + if (!d) { + int fd; + struct rlimit rl; + + /* When /proc isn't available (for example in chroots) + * the fallback is brute forcing through the fd + * table */ + + assert_se(getrlimit(RLIMIT_NOFILE, &rl) >= 0); + for (fd = 3; fd < (int) rl.rlim_max; fd ++) { + + if (fd_in_set(fd, except, n_except)) + continue; + + if (close_nointr(fd) < 0) + if (errno != EBADF && r == 0) + r = -errno; + } + + return r; + } + + while ((de = readdir(d))) { + int fd = -1; + + if (hidden_or_backup_file(de->d_name)) + continue; + + if (safe_atoi(de->d_name, &fd) < 0) + /* Let's better ignore this, just in case */ + continue; + + if (fd < 3) + continue; + + if (fd == dirfd(d)) + continue; + + if (fd_in_set(fd, except, n_except)) + continue; + + if (close_nointr(fd) < 0) { + /* Valgrind has its own FD and doesn't want to have it closed */ + if (errno != EBADF && r == 0) + r = -errno; + } + } + + return r; +} + +int same_fd(int a, int b) { + struct stat sta, stb; + pid_t pid; + int r, fa, fb; + + assert(a >= 0); + assert(b >= 0); + + /* Compares two file descriptors. Note that semantics are + * quite different depending on whether we have kcmp() or we + * don't. If we have kcmp() this will only return true for + * dup()ed file descriptors, but not otherwise. If we don't + * have kcmp() this will also return true for two fds of the same + * file, created by separate open() calls. Since we use this + * call mostly for filtering out duplicates in the fd store + * this difference hopefully doesn't matter too much. */ + + if (a == b) + return true; + + /* Try to use kcmp() if we have it. */ + pid = getpid(); + r = kcmp(pid, pid, KCMP_FILE, a, b); + if (r == 0) + return true; + if (r > 0) + return false; + if (errno != ENOSYS) + return -errno; + + /* We don't have kcmp(), use fstat() instead. */ + if (fstat(a, &sta) < 0) + return -errno; + + if (fstat(b, &stb) < 0) + return -errno; + + if ((sta.st_mode & S_IFMT) != (stb.st_mode & S_IFMT)) + return false; + + /* We consider all device fds different, since two device fds + * might refer to quite different device contexts even though + * they share the same inode and backing dev_t. */ + + if (S_ISCHR(sta.st_mode) || S_ISBLK(sta.st_mode)) + return false; + + if (sta.st_dev != stb.st_dev || sta.st_ino != stb.st_ino) + return false; + + /* The fds refer to the same inode on disk, let's also check + * if they have the same fd flags. This is useful to + * distinguish the read and write side of a pipe created with + * pipe(). */ + fa = fcntl(a, F_GETFL); + if (fa < 0) + return -errno; + + fb = fcntl(b, F_GETFL); + if (fb < 0) + return -errno; + + return fa == fb; +} + +void cmsg_close_all(struct msghdr *mh) { + struct cmsghdr *cmsg; + + assert(mh); + + CMSG_FOREACH(cmsg, mh) + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) + close_many((int*) CMSG_DATA(cmsg), (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int)); +} + +bool fdname_is_valid(const char *s) { + const char *p; + + /* Validates a name for $LISTEN_FDNAMES. We basically allow + * everything ASCII that's not a control character. Also, as + * special exception the ":" character is not allowed, as we + * use that as field separator in $LISTEN_FDNAMES. + * + * Note that the empty string is explicitly allowed + * here. However, we limit the length of the names to 255 + * characters. */ + + if (!s) + return false; + + for (p = s; *p; p++) { + if (*p < ' ') + return false; + if (*p >= 127) + return false; + if (*p == ':') + return false; + } + + return p - s < 256; +} + +int fd_get_path(int fd, char **ret) { + char procfs_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; + int r; + + xsprintf(procfs_path, "/proc/self/fd/%i", fd); + + r = readlink_malloc(procfs_path, ret); + + if (r == -ENOENT) /* If the file doesn't exist the fd is invalid */ + return -EBADF; + + return r; +} diff --git a/src/libbasic/fd-util.h b/src/libbasic/fd-util.h new file mode 100644 index 0000000000..b86e41698a --- /dev/null +++ b/src/libbasic/fd-util.h @@ -0,0 +1,79 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include + +#include "macro.h" + +/* Make sure we can distinguish fd 0 and NULL */ +#define FD_TO_PTR(fd) INT_TO_PTR((fd)+1) +#define PTR_TO_FD(p) (PTR_TO_INT(p)-1) + +int close_nointr(int fd); +int safe_close(int fd); +void safe_close_pair(int p[]); + +void close_many(const int fds[], unsigned n_fd); + +int fclose_nointr(FILE *f); +FILE* safe_fclose(FILE *f); +DIR* safe_closedir(DIR *f); + +static inline void closep(int *fd) { + safe_close(*fd); +} + +static inline void close_pairp(int (*p)[2]) { + safe_close_pair(*p); +} + +static inline void fclosep(FILE **f) { + safe_fclose(*f); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, pclose); +DEFINE_TRIVIAL_CLEANUP_FUNC(DIR*, closedir); + +#define _cleanup_close_ _cleanup_(closep) +#define _cleanup_fclose_ _cleanup_(fclosep) +#define _cleanup_pclose_ _cleanup_(pclosep) +#define _cleanup_closedir_ _cleanup_(closedirp) +#define _cleanup_close_pair_ _cleanup_(close_pairp) + +int fd_nonblock(int fd, bool nonblock); +int fd_cloexec(int fd, bool cloexec); + +int close_all_fds(const int except[], unsigned n_except); + +int same_fd(int a, int b); + +void cmsg_close_all(struct msghdr *mh); + +bool fdname_is_valid(const char *s); + +int fd_get_path(int fd, char **ret); + +/* Hint: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5 */ +#define ERRNO_IS_DISCONNECT(r) \ + IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, ENETUNREACH) diff --git a/src/libbasic/fdset.c b/src/libbasic/fdset.c new file mode 100644 index 0000000000..b52bf1ad05 --- /dev/null +++ b/src/libbasic/fdset.c @@ -0,0 +1,273 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include + +#include + +#include "fd-util.h" +#include "fdset.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "path-util.h" +#include "set.h" + +#define MAKE_SET(s) ((Set*) s) +#define MAKE_FDSET(s) ((FDSet*) s) + +FDSet *fdset_new(void) { + return MAKE_FDSET(set_new(NULL)); +} + +int fdset_new_array(FDSet **ret, const int *fds, unsigned n_fds) { + unsigned i; + FDSet *s; + int r; + + assert(ret); + + s = fdset_new(); + if (!s) + return -ENOMEM; + + for (i = 0; i < n_fds; i++) { + + r = fdset_put(s, fds[i]); + if (r < 0) { + set_free(MAKE_SET(s)); + return r; + } + } + + *ret = s; + return 0; +} + +FDSet* fdset_free(FDSet *s) { + void *p; + + while ((p = set_steal_first(MAKE_SET(s)))) { + /* Valgrind's fd might have ended up in this set here, + * due to fdset_new_fill(). We'll ignore all failures + * here, so that the EBADFD that valgrind will return + * us on close() doesn't influence us */ + + /* When reloading duplicates of the private bus + * connection fds and suchlike are closed here, which + * has no effect at all, since they are only + * duplicates. So don't be surprised about these log + * messages. */ + + log_debug("Closing left-over fd %i", PTR_TO_FD(p)); + close_nointr(PTR_TO_FD(p)); + } + + set_free(MAKE_SET(s)); + return NULL; +} + +int fdset_put(FDSet *s, int fd) { + assert(s); + assert(fd >= 0); + + return set_put(MAKE_SET(s), FD_TO_PTR(fd)); +} + +int fdset_put_dup(FDSet *s, int fd) { + int copy, r; + + assert(s); + assert(fd >= 0); + + copy = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (copy < 0) + return -errno; + + r = fdset_put(s, copy); + if (r < 0) { + safe_close(copy); + return r; + } + + return copy; +} + +bool fdset_contains(FDSet *s, int fd) { + assert(s); + assert(fd >= 0); + + return !!set_get(MAKE_SET(s), FD_TO_PTR(fd)); +} + +int fdset_remove(FDSet *s, int fd) { + assert(s); + assert(fd >= 0); + + return set_remove(MAKE_SET(s), FD_TO_PTR(fd)) ? fd : -ENOENT; +} + +int fdset_new_fill(FDSet **_s) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r = 0; + FDSet *s; + + assert(_s); + + /* Creates an fdset and fills in all currently open file + * descriptors. */ + + d = opendir("/proc/self/fd"); + if (!d) + return -errno; + + s = fdset_new(); + if (!s) { + r = -ENOMEM; + goto finish; + } + + while ((de = readdir(d))) { + int fd = -1; + + if (hidden_or_backup_file(de->d_name)) + continue; + + r = safe_atoi(de->d_name, &fd); + if (r < 0) + goto finish; + + if (fd < 3) + continue; + + if (fd == dirfd(d)) + continue; + + r = fdset_put(s, fd); + if (r < 0) + goto finish; + } + + r = 0; + *_s = s; + s = NULL; + +finish: + /* We won't close the fds here! */ + if (s) + set_free(MAKE_SET(s)); + + return r; +} + +int fdset_cloexec(FDSet *fds, bool b) { + Iterator i; + void *p; + int r; + + assert(fds); + + SET_FOREACH(p, MAKE_SET(fds), i) { + r = fd_cloexec(PTR_TO_FD(p), b); + if (r < 0) + return r; + } + + return 0; +} + +int fdset_new_listen_fds(FDSet **_s, bool unset) { + int n, fd, r; + FDSet *s; + + assert(_s); + + /* Creates an fdset and fills in all passed file descriptors */ + + s = fdset_new(); + if (!s) { + r = -ENOMEM; + goto fail; + } + + n = sd_listen_fds(unset); + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) { + r = fdset_put(s, fd); + if (r < 0) + goto fail; + } + + *_s = s; + return 0; + + +fail: + if (s) + set_free(MAKE_SET(s)); + + return r; +} + +int fdset_close_others(FDSet *fds) { + void *e; + Iterator i; + int *a; + unsigned j, m; + + j = 0, m = fdset_size(fds); + a = alloca(sizeof(int) * m); + SET_FOREACH(e, MAKE_SET(fds), i) + a[j++] = PTR_TO_FD(e); + + assert(j == m); + + return close_all_fds(a, j); +} + +unsigned fdset_size(FDSet *fds) { + return set_size(MAKE_SET(fds)); +} + +bool fdset_isempty(FDSet *fds) { + return set_isempty(MAKE_SET(fds)); +} + +int fdset_iterate(FDSet *s, Iterator *i) { + void *p; + + if (!set_iterate(MAKE_SET(s), i, &p)) + return -ENOENT; + + return PTR_TO_FD(p); +} + +int fdset_steal_first(FDSet *fds) { + void *p; + + p = set_steal_first(MAKE_SET(fds)); + if (!p) + return -ENOENT; + + return PTR_TO_FD(p); +} diff --git a/src/libbasic/fdset.h b/src/libbasic/fdset.h new file mode 100644 index 0000000000..16efe5bdf2 --- /dev/null +++ b/src/libbasic/fdset.h @@ -0,0 +1,58 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "hashmap.h" +#include "macro.h" +#include "set.h" + +typedef struct FDSet FDSet; + +FDSet* fdset_new(void); +FDSet* fdset_free(FDSet *s); + +int fdset_put(FDSet *s, int fd); +int fdset_put_dup(FDSet *s, int fd); + +bool fdset_contains(FDSet *s, int fd); +int fdset_remove(FDSet *s, int fd); + +int fdset_new_array(FDSet **ret, const int *fds, unsigned n_fds); +int fdset_new_fill(FDSet **ret); +int fdset_new_listen_fds(FDSet **ret, bool unset); + +int fdset_cloexec(FDSet *fds, bool b); + +int fdset_close_others(FDSet *fds); + +unsigned fdset_size(FDSet *fds); +bool fdset_isempty(FDSet *fds); + +int fdset_iterate(FDSet *s, Iterator *i); + +int fdset_steal_first(FDSet *fds); + +#define FDSET_FOREACH(fd, fds, i) \ + for ((i) = ITERATOR_FIRST, (fd) = fdset_iterate((fds), &(i)); (fd) >= 0; (fd) = fdset_iterate((fds), &(i))) + +DEFINE_TRIVIAL_CLEANUP_FUNC(FDSet*, fdset_free); +#define _cleanup_fdset_free_ _cleanup_(fdset_freep) diff --git a/src/libbasic/fileio-label.c b/src/libbasic/fileio-label.c new file mode 100644 index 0000000000..66dbc0fe1e --- /dev/null +++ b/src/libbasic/fileio-label.c @@ -0,0 +1,68 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2010 Harald Hoyer + + 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 "fileio-label.h" +#include "fileio.h" +#include "selinux-util.h" + +int write_string_file_atomic_label(const char *fn, const char *line) { + int r; + + r = mac_selinux_create_file_prepare(fn, S_IFREG); + if (r < 0) + return r; + + r = write_string_file(fn, line, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); + + mac_selinux_create_file_clear(); + + return r; +} + +int write_env_file_label(const char *fname, char **l) { + int r; + + r = mac_selinux_create_file_prepare(fname, S_IFREG); + if (r < 0) + return r; + + r = write_env_file(fname, l); + + mac_selinux_create_file_clear(); + + return r; +} + +int fopen_temporary_label(const char *target, + const char *path, FILE **f, char **temp_path) { + int r; + + r = mac_selinux_create_file_prepare(target, S_IFREG); + if (r < 0) + return r; + + r = fopen_temporary(path, f, temp_path); + + mac_selinux_create_file_clear(); + + return r; +} diff --git a/src/libbasic/fileio-label.h b/src/libbasic/fileio-label.h new file mode 100644 index 0000000000..fe7543013d --- /dev/null +++ b/src/libbasic/fileio-label.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2010 Harald Hoyer + + 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 "fileio.h" + +int write_string_file_atomic_label(const char *fn, const char *line); +int write_env_file_label(const char *fname, char **l); +int fopen_temporary_label(const char *target, + const char *path, FILE **f, char **temp_path); diff --git a/src/libbasic/fileio.c b/src/libbasic/fileio.c new file mode 100644 index 0000000000..29f5374222 --- /dev/null +++ b/src/libbasic/fileio.c @@ -0,0 +1,1356 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "ctype.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "hexdecoct.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "path-util.h" +#include "random-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "umask-util.h" +#include "utf8.h" + +int write_string_stream(FILE *f, const char *line, bool enforce_newline) { + + assert(f); + assert(line); + + fputs(line, f); + if (enforce_newline && !endswith(line, "\n")) + fputc('\n', f); + + return fflush_and_check(f); +} + +static int write_string_file_atomic(const char *fn, const char *line, bool enforce_newline) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *p = NULL; + int r; + + assert(fn); + assert(line); + + r = fopen_temporary(fn, &f, &p); + if (r < 0) + return r; + + (void) fchmod_umask(fileno(f), 0644); + + r = write_string_stream(f, line, enforce_newline); + if (r >= 0) { + if (rename(p, fn) < 0) + r = -errno; + } + + if (r < 0) + (void) unlink(p); + + return r; +} + +int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) { + _cleanup_fclose_ FILE *f = NULL; + int q, r; + + assert(fn); + assert(line); + + if (flags & WRITE_STRING_FILE_ATOMIC) { + assert(flags & WRITE_STRING_FILE_CREATE); + + r = write_string_file_atomic(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE)); + if (r < 0) + goto fail; + + return r; + } + + if (flags & WRITE_STRING_FILE_CREATE) { + f = fopen(fn, "we"); + if (!f) { + r = -errno; + goto fail; + } + } else { + int fd; + + /* We manually build our own version of fopen(..., "we") that + * works without O_CREAT */ + fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + r = -errno; + goto fail; + } + + f = fdopen(fd, "we"); + if (!f) { + r = -errno; + safe_close(fd); + goto fail; + } + } + + r = write_string_stream(f, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE)); + if (r < 0) + goto fail; + + return 0; + +fail: + if (!(flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE)) + return r; + + f = safe_fclose(f); + + /* OK, the operation failed, but let's see if the right + * contents in place already. If so, eat up the error. */ + + q = verify_file(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE)); + if (q <= 0) + return r; + + return 0; +} + +int read_one_line_file(const char *fn, char **line) { + _cleanup_fclose_ FILE *f = NULL; + char t[LINE_MAX], *c; + + assert(fn); + assert(line); + + f = fopen(fn, "re"); + if (!f) + return -errno; + + if (!fgets(t, sizeof(t), f)) { + + if (ferror(f)) + return errno > 0 ? -errno : -EIO; + + t[0] = 0; + } + + c = strdup(t); + if (!c) + return -ENOMEM; + truncate_nl(c); + + *line = c; + return 0; +} + +int verify_file(const char *fn, const char *blob, bool accept_extra_nl) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *buf = NULL; + size_t l, k; + + assert(fn); + assert(blob); + + l = strlen(blob); + + if (accept_extra_nl && endswith(blob, "\n")) + accept_extra_nl = false; + + buf = malloc(l + accept_extra_nl + 1); + if (!buf) + return -ENOMEM; + + f = fopen(fn, "re"); + if (!f) + return -errno; + + /* We try to read one byte more than we need, so that we know whether we hit eof */ + errno = 0; + k = fread(buf, 1, l + accept_extra_nl + 1, f); + if (ferror(f)) + return errno > 0 ? -errno : -EIO; + + if (k != l && k != l + accept_extra_nl) + return 0; + if (memcmp(buf, blob, l) != 0) + return 0; + if (k > l && buf[l] != '\n') + return 0; + + return 1; +} + +int read_full_stream(FILE *f, char **contents, size_t *size) { + size_t n, l; + _cleanup_free_ char *buf = NULL; + struct stat st; + + assert(f); + assert(contents); + + if (fstat(fileno(f), &st) < 0) + return -errno; + + n = LINE_MAX; + + if (S_ISREG(st.st_mode)) { + + /* Safety check */ + if (st.st_size > 4*1024*1024) + return -E2BIG; + + /* Start with the right file size, but be prepared for + * files from /proc which generally report a file size + * of 0 */ + if (st.st_size > 0) + n = st.st_size; + } + + l = 0; + for (;;) { + char *t; + size_t k; + + t = realloc(buf, n+1); + if (!t) + return -ENOMEM; + + buf = t; + k = fread(buf + l, 1, n - l, f); + + if (k <= 0) { + if (ferror(f)) + return -errno; + + break; + } + + l += k; + n *= 2; + + /* Safety check */ + if (n > 4*1024*1024) + return -E2BIG; + } + + buf[l] = 0; + *contents = buf; + buf = NULL; /* do not free */ + + if (size) + *size = l; + + return 0; +} + +int read_full_file(const char *fn, char **contents, size_t *size) { + _cleanup_fclose_ FILE *f = NULL; + + assert(fn); + assert(contents); + + f = fopen(fn, "re"); + if (!f) + return -errno; + + return read_full_stream(f, contents, size); +} + +static int parse_env_file_internal( + FILE *f, + const char *fname, + const char *newline, + int (*push) (const char *filename, unsigned line, + const char *key, char *value, void *userdata, int *n_pushed), + void *userdata, + int *n_pushed) { + + _cleanup_free_ char *contents = NULL, *key = NULL; + size_t key_alloc = 0, n_key = 0, value_alloc = 0, n_value = 0, last_value_whitespace = (size_t) -1, last_key_whitespace = (size_t) -1; + char *p, *value = NULL; + int r; + unsigned line = 1; + + enum { + PRE_KEY, + KEY, + PRE_VALUE, + VALUE, + VALUE_ESCAPE, + SINGLE_QUOTE_VALUE, + SINGLE_QUOTE_VALUE_ESCAPE, + DOUBLE_QUOTE_VALUE, + DOUBLE_QUOTE_VALUE_ESCAPE, + COMMENT, + COMMENT_ESCAPE + } state = PRE_KEY; + + assert(newline); + + if (f) + r = read_full_stream(f, &contents, NULL); + else + r = read_full_file(fname, &contents, NULL); + if (r < 0) + return r; + + for (p = contents; *p; p++) { + char c = *p; + + switch (state) { + + case PRE_KEY: + if (strchr(COMMENTS, c)) + state = COMMENT; + else if (!strchr(WHITESPACE, c)) { + state = KEY; + last_key_whitespace = (size_t) -1; + + if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) { + r = -ENOMEM; + goto fail; + } + + key[n_key++] = c; + } + break; + + case KEY: + if (strchr(newline, c)) { + state = PRE_KEY; + line++; + n_key = 0; + } else if (c == '=') { + state = PRE_VALUE; + last_value_whitespace = (size_t) -1; + } else { + if (!strchr(WHITESPACE, c)) + last_key_whitespace = (size_t) -1; + else if (last_key_whitespace == (size_t) -1) + last_key_whitespace = n_key; + + if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) { + r = -ENOMEM; + goto fail; + } + + key[n_key++] = c; + } + + break; + + case PRE_VALUE: + if (strchr(newline, c)) { + state = PRE_KEY; + line++; + key[n_key] = 0; + + if (value) + value[n_value] = 0; + + /* strip trailing whitespace from key */ + if (last_key_whitespace != (size_t) -1) + key[last_key_whitespace] = 0; + + r = push(fname, line, key, value, userdata, n_pushed); + if (r < 0) + goto fail; + + n_key = 0; + value = NULL; + value_alloc = n_value = 0; + + } else if (c == '\'') + state = SINGLE_QUOTE_VALUE; + else if (c == '\"') + state = DOUBLE_QUOTE_VALUE; + else if (c == '\\') + state = VALUE_ESCAPE; + else if (!strchr(WHITESPACE, c)) { + state = VALUE; + + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + + break; + + case VALUE: + if (strchr(newline, c)) { + state = PRE_KEY; + line++; + + key[n_key] = 0; + + if (value) + value[n_value] = 0; + + /* Chomp off trailing whitespace from value */ + if (last_value_whitespace != (size_t) -1) + value[last_value_whitespace] = 0; + + /* strip trailing whitespace from key */ + if (last_key_whitespace != (size_t) -1) + key[last_key_whitespace] = 0; + + r = push(fname, line, key, value, userdata, n_pushed); + if (r < 0) + goto fail; + + n_key = 0; + value = NULL; + value_alloc = n_value = 0; + + } else if (c == '\\') { + state = VALUE_ESCAPE; + last_value_whitespace = (size_t) -1; + } else { + if (!strchr(WHITESPACE, c)) + last_value_whitespace = (size_t) -1; + else if (last_value_whitespace == (size_t) -1) + last_value_whitespace = n_value; + + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + + break; + + case VALUE_ESCAPE: + state = VALUE; + + if (!strchr(newline, c)) { + /* Escaped newlines we eat up entirely */ + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + break; + + case SINGLE_QUOTE_VALUE: + if (c == '\'') + state = PRE_VALUE; + else if (c == '\\') + state = SINGLE_QUOTE_VALUE_ESCAPE; + else { + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + + break; + + case SINGLE_QUOTE_VALUE_ESCAPE: + state = SINGLE_QUOTE_VALUE; + + if (!strchr(newline, c)) { + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + break; + + case DOUBLE_QUOTE_VALUE: + if (c == '\"') + state = PRE_VALUE; + else if (c == '\\') + state = DOUBLE_QUOTE_VALUE_ESCAPE; + else { + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + + break; + + case DOUBLE_QUOTE_VALUE_ESCAPE: + state = DOUBLE_QUOTE_VALUE; + + if (!strchr(newline, c)) { + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + break; + + case COMMENT: + if (c == '\\') + state = COMMENT_ESCAPE; + else if (strchr(newline, c)) { + state = PRE_KEY; + line++; + } + break; + + case COMMENT_ESCAPE: + state = COMMENT; + break; + } + } + + if (state == PRE_VALUE || + state == VALUE || + state == VALUE_ESCAPE || + state == SINGLE_QUOTE_VALUE || + state == SINGLE_QUOTE_VALUE_ESCAPE || + state == DOUBLE_QUOTE_VALUE || + state == DOUBLE_QUOTE_VALUE_ESCAPE) { + + key[n_key] = 0; + + if (value) + value[n_value] = 0; + + if (state == VALUE) + if (last_value_whitespace != (size_t) -1) + value[last_value_whitespace] = 0; + + /* strip trailing whitespace from key */ + if (last_key_whitespace != (size_t) -1) + key[last_key_whitespace] = 0; + + r = push(fname, line, key, value, userdata, n_pushed); + if (r < 0) + goto fail; + } + + return 0; + +fail: + free(value); + return r; +} + +static int parse_env_file_push( + const char *filename, unsigned line, + const char *key, char *value, + void *userdata, + int *n_pushed) { + + const char *k; + va_list aq, *ap = userdata; + + if (!utf8_is_valid(key)) { + _cleanup_free_ char *p = NULL; + + p = utf8_escape_invalid(key); + log_error("%s:%u: invalid UTF-8 in key '%s', ignoring.", strna(filename), line, p); + return -EINVAL; + } + + if (value && !utf8_is_valid(value)) { + _cleanup_free_ char *p = NULL; + + p = utf8_escape_invalid(value); + log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, p); + return -EINVAL; + } + + va_copy(aq, *ap); + + while ((k = va_arg(aq, const char *))) { + char **v; + + v = va_arg(aq, char **); + + if (streq(key, k)) { + va_end(aq); + free(*v); + *v = value; + + if (n_pushed) + (*n_pushed)++; + + return 1; + } + } + + va_end(aq); + free(value); + + return 0; +} + +int parse_env_file( + const char *fname, + const char *newline, ...) { + + va_list ap; + int r, n_pushed = 0; + + if (!newline) + newline = NEWLINE; + + va_start(ap, newline); + r = parse_env_file_internal(NULL, fname, newline, parse_env_file_push, &ap, &n_pushed); + va_end(ap); + + return r < 0 ? r : n_pushed; +} + +static int load_env_file_push( + const char *filename, unsigned line, + const char *key, char *value, + void *userdata, + int *n_pushed) { + char ***m = userdata; + char *p; + int r; + + if (!utf8_is_valid(key)) { + _cleanup_free_ char *t = utf8_escape_invalid(key); + + log_error("%s:%u: invalid UTF-8 for key '%s', ignoring.", strna(filename), line, t); + return -EINVAL; + } + + if (value && !utf8_is_valid(value)) { + _cleanup_free_ char *t = utf8_escape_invalid(value); + + log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, t); + return -EINVAL; + } + + p = strjoin(key, "=", strempty(value), NULL); + if (!p) + return -ENOMEM; + + r = strv_consume(m, p); + if (r < 0) + return r; + + if (n_pushed) + (*n_pushed)++; + + free(value); + return 0; +} + +int load_env_file(FILE *f, const char *fname, const char *newline, char ***rl) { + char **m = NULL; + int r; + + if (!newline) + newline = NEWLINE; + + r = parse_env_file_internal(f, fname, newline, load_env_file_push, &m, NULL); + if (r < 0) { + strv_free(m); + return r; + } + + *rl = m; + return 0; +} + +static int load_env_file_push_pairs( + const char *filename, unsigned line, + const char *key, char *value, + void *userdata, + int *n_pushed) { + char ***m = userdata; + int r; + + if (!utf8_is_valid(key)) { + _cleanup_free_ char *t = utf8_escape_invalid(key); + + log_error("%s:%u: invalid UTF-8 for key '%s', ignoring.", strna(filename), line, t); + return -EINVAL; + } + + if (value && !utf8_is_valid(value)) { + _cleanup_free_ char *t = utf8_escape_invalid(value); + + log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, t); + return -EINVAL; + } + + r = strv_extend(m, key); + if (r < 0) + return -ENOMEM; + + if (!value) { + r = strv_extend(m, ""); + if (r < 0) + return -ENOMEM; + } else { + r = strv_push(m, value); + if (r < 0) + return r; + } + + if (n_pushed) + (*n_pushed)++; + + return 0; +} + +int load_env_file_pairs(FILE *f, const char *fname, const char *newline, char ***rl) { + char **m = NULL; + int r; + + if (!newline) + newline = NEWLINE; + + r = parse_env_file_internal(f, fname, newline, load_env_file_push_pairs, &m, NULL); + if (r < 0) { + strv_free(m); + return r; + } + + *rl = m; + return 0; +} + +static void write_env_var(FILE *f, const char *v) { + const char *p; + + p = strchr(v, '='); + if (!p) { + /* Fallback */ + fputs(v, f); + fputc('\n', f); + return; + } + + p++; + fwrite(v, 1, p-v, f); + + if (string_has_cc(p, NULL) || chars_intersect(p, WHITESPACE SHELL_NEED_QUOTES)) { + fputc('\"', f); + + for (; *p; p++) { + if (strchr(SHELL_NEED_ESCAPE, *p)) + fputc('\\', f); + + fputc(*p, f); + } + + fputc('\"', f); + } else + fputs(p, f); + + fputc('\n', f); +} + +int write_env_file(const char *fname, char **l) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *p = NULL; + char **i; + int r; + + assert(fname); + + r = fopen_temporary(fname, &f, &p); + if (r < 0) + return r; + + fchmod_umask(fileno(f), 0644); + + STRV_FOREACH(i, l) + write_env_var(f, *i); + + r = fflush_and_check(f); + if (r >= 0) { + if (rename(p, fname) >= 0) + return 0; + + r = -errno; + } + + unlink(p); + return r; +} + +int executable_is_script(const char *path, char **interpreter) { + int r; + _cleanup_free_ char *line = NULL; + int len; + char *ans; + + assert(path); + + r = read_one_line_file(path, &line); + if (r < 0) + return r; + + if (!startswith(line, "#!")) + return 0; + + ans = strstrip(line + 2); + len = strcspn(ans, " \t"); + + if (len == 0) + return 0; + + ans = strndup(ans, len); + if (!ans) + return -ENOMEM; + + *interpreter = ans; + return 1; +} + +/** + * Retrieve one field from a file like /proc/self/status. pattern + * should not include whitespace or the delimiter (':'). pattern matches only + * the beginning of a line. Whitespace before ':' is skipped. Whitespace and + * zeros after the ':' will be skipped. field must be freed afterwards. + * terminator specifies the terminating characters of the field value (not + * included in the value). + */ +int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field) { + _cleanup_free_ char *status = NULL; + char *t, *f; + size_t len; + int r; + + assert(terminator); + assert(filename); + assert(pattern); + assert(field); + + r = read_full_file(filename, &status, NULL); + if (r < 0) + return r; + + t = status; + + do { + bool pattern_ok; + + do { + t = strstr(t, pattern); + if (!t) + return -ENOENT; + + /* Check that pattern occurs in beginning of line. */ + pattern_ok = (t == status || t[-1] == '\n'); + + t += strlen(pattern); + + } while (!pattern_ok); + + t += strspn(t, " \t"); + if (!*t) + return -ENOENT; + + } while (*t != ':'); + + t++; + + if (*t) { + t += strspn(t, " \t"); + + /* Also skip zeros, because when this is used for + * capabilities, we don't want the zeros. This way the + * same capability set always maps to the same string, + * irrespective of the total capability set size. For + * other numbers it shouldn't matter. */ + t += strspn(t, "0"); + /* Back off one char if there's nothing but whitespace + and zeros */ + if (!*t || isspace(*t)) + t--; + } + + len = strcspn(t, terminator); + + f = strndup(t, len); + if (!f) + return -ENOMEM; + + *field = f; + return 0; +} + +DIR *xopendirat(int fd, const char *name, int flags) { + int nfd; + DIR *d; + + assert(!(flags & O_CREAT)); + + nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags, 0); + if (nfd < 0) + return NULL; + + d = fdopendir(nfd); + if (!d) { + safe_close(nfd); + return NULL; + } + + return d; +} + +static int search_and_fopen_internal(const char *path, const char *mode, const char *root, char **search, FILE **_f) { + char **i; + + assert(path); + assert(mode); + assert(_f); + + if (!path_strv_resolve_uniq(search, root)) + return -ENOMEM; + + STRV_FOREACH(i, search) { + _cleanup_free_ char *p = NULL; + FILE *f; + + if (root) + p = strjoin(root, *i, "/", path, NULL); + else + p = strjoin(*i, "/", path, NULL); + if (!p) + return -ENOMEM; + + f = fopen(p, mode); + if (f) { + *_f = f; + return 0; + } + + if (errno != ENOENT) + return -errno; + } + + return -ENOENT; +} + +int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **_f) { + _cleanup_strv_free_ char **copy = NULL; + + assert(path); + assert(mode); + assert(_f); + + if (path_is_absolute(path)) { + FILE *f; + + f = fopen(path, mode); + if (f) { + *_f = f; + return 0; + } + + return -errno; + } + + copy = strv_copy((char**) search); + if (!copy) + return -ENOMEM; + + return search_and_fopen_internal(path, mode, root, copy, _f); +} + +int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **_f) { + _cleanup_strv_free_ char **s = NULL; + + if (path_is_absolute(path)) { + FILE *f; + + f = fopen(path, mode); + if (f) { + *_f = f; + return 0; + } + + return -errno; + } + + s = strv_split_nulstr(search); + if (!s) + return -ENOMEM; + + return search_and_fopen_internal(path, mode, root, s, _f); +} + +int fopen_temporary(const char *path, FILE **_f, char **_temp_path) { + FILE *f; + char *t; + int r, fd; + + assert(path); + assert(_f); + assert(_temp_path); + + r = tempfn_xxxxxx(path, NULL, &t); + if (r < 0) + return r; + + fd = mkostemp_safe(t, O_WRONLY|O_CLOEXEC); + if (fd < 0) { + free(t); + return -errno; + } + + f = fdopen(fd, "we"); + if (!f) { + unlink_noerrno(t); + free(t); + safe_close(fd); + return -errno; + } + + *_f = f; + *_temp_path = t; + + return 0; +} + +int fflush_and_check(FILE *f) { + assert(f); + + errno = 0; + fflush(f); + + if (ferror(f)) + return errno > 0 ? -errno : -EIO; + + return 0; +} + +/* This is much like like mkostemp() but is subject to umask(). */ +int mkostemp_safe(char *pattern, int flags) { + _cleanup_umask_ mode_t u = 0; + int fd; + + assert(pattern); + + u = umask(077); + + fd = mkostemp(pattern, flags); + if (fd < 0) + return -errno; + + return fd; +} + +int tempfn_xxxxxx(const char *p, const char *extra, char **ret) { + const char *fn; + char *t; + + assert(p); + assert(ret); + + /* + * Turns this: + * /foo/bar/waldo + * + * Into this: + * /foo/bar/.#waldoXXXXXX + */ + + fn = basename(p); + if (!filename_is_valid(fn)) + return -EINVAL; + + if (extra == NULL) + extra = ""; + + t = new(char, strlen(p) + 2 + strlen(extra) + 6 + 1); + if (!t) + return -ENOMEM; + + strcpy(stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn), "XXXXXX"); + + *ret = path_kill_slashes(t); + return 0; +} + +int tempfn_random(const char *p, const char *extra, char **ret) { + const char *fn; + char *t, *x; + uint64_t u; + unsigned i; + + assert(p); + assert(ret); + + /* + * Turns this: + * /foo/bar/waldo + * + * Into this: + * /foo/bar/.#waldobaa2a261115984a9 + */ + + fn = basename(p); + if (!filename_is_valid(fn)) + return -EINVAL; + + if (!extra) + extra = ""; + + t = new(char, strlen(p) + 2 + strlen(extra) + 16 + 1); + if (!t) + return -ENOMEM; + + x = stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn); + + u = random_u64(); + for (i = 0; i < 16; i++) { + *(x++) = hexchar(u & 0xF); + u >>= 4; + } + + *x = 0; + + *ret = path_kill_slashes(t); + return 0; +} + +int tempfn_random_child(const char *p, const char *extra, char **ret) { + char *t, *x; + uint64_t u; + unsigned i; + + assert(p); + assert(ret); + + /* Turns this: + * /foo/bar/waldo + * Into this: + * /foo/bar/waldo/.#3c2b6219aa75d7d0 + */ + + if (!extra) + extra = ""; + + t = new(char, strlen(p) + 3 + strlen(extra) + 16 + 1); + if (!t) + return -ENOMEM; + + x = stpcpy(stpcpy(stpcpy(t, p), "/.#"), extra); + + u = random_u64(); + for (i = 0; i < 16; i++) { + *(x++) = hexchar(u & 0xF); + u >>= 4; + } + + *x = 0; + + *ret = path_kill_slashes(t); + return 0; +} + +int write_timestamp_file_atomic(const char *fn, usec_t n) { + char ln[DECIMAL_STR_MAX(n)+2]; + + /* Creates a "timestamp" file, that contains nothing but a + * usec_t timestamp, formatted in ASCII. */ + + if (n <= 0 || n >= USEC_INFINITY) + return -ERANGE; + + xsprintf(ln, USEC_FMT "\n", n); + + return write_string_file(fn, ln, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); +} + +int read_timestamp_file(const char *fn, usec_t *ret) { + _cleanup_free_ char *ln = NULL; + uint64_t t; + int r; + + r = read_one_line_file(fn, &ln); + if (r < 0) + return r; + + r = safe_atou64(ln, &t); + if (r < 0) + return r; + + if (t <= 0 || t >= (uint64_t) USEC_INFINITY) + return -ERANGE; + + *ret = (usec_t) t; + return 0; +} + +int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space) { + int r; + + assert(s); + + /* Outputs the specified string with fputs(), but optionally prefixes it with a separator. The *space parameter + * when specified shall initially point to a boolean variable initialized to false. It is set to true after the + * first invocation. This call is supposed to be use in loops, where a separator shall be inserted between each + * element, but not before the first one. */ + + if (!f) + f = stdout; + + if (space) { + if (!separator) + separator = " "; + + if (*space) { + r = fputs(separator, f); + if (r < 0) + return r; + } + + *space = true; + } + + return fputs(s, f); +} + +int open_tmpfile_unlinkable(const char *directory, int flags) { + char *p; + int fd; + + assert(directory); + + /* Returns an unlinked temporary file that cannot be linked into the file system anymore */ + +#ifdef O_TMPFILE + /* Try O_TMPFILE first, if it is supported */ + fd = open(directory, flags|O_TMPFILE|O_EXCL, S_IRUSR|S_IWUSR); + if (fd >= 0) + return fd; +#endif + + /* Fall back to unguessable name + unlinking */ + p = strjoina(directory, "/systemd-tmp-XXXXXX"); + + fd = mkostemp_safe(p, flags); + if (fd < 0) + return fd; + + (void) unlink(p); + + return fd; +} + +int open_tmpfile_linkable(const char *target, int flags, char **ret_path) { + _cleanup_free_ char *tmp = NULL; + int r, fd; + + assert(target); + assert(ret_path); + + /* Don't allow O_EXCL, as that has a special meaning for O_TMPFILE */ + assert((flags & O_EXCL) == 0); + + /* Creates a temporary file, that shall be renamed to "target" later. If possible, this uses O_TMPFILE – in + * which case "ret_path" will be returned as NULL. If not possible a the tempoary path name used is returned in + * "ret_path". Use link_tmpfile() below to rename the result after writing the file in full. */ + +#ifdef O_TMPFILE + { + _cleanup_free_ char *dn = NULL; + + dn = dirname_malloc(target); + if (!dn) + return -ENOMEM; + + fd = open(dn, O_TMPFILE|flags, 0640); + if (fd >= 0) { + *ret_path = NULL; + return fd; + } + + log_debug_errno(errno, "Failed to use O_TMPFILE on %s: %m", dn); + } +#endif + + r = tempfn_random(target, NULL, &tmp); + if (r < 0) + return r; + + fd = open(tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640); + if (fd < 0) + return -errno; + + *ret_path = tmp; + tmp = NULL; + + return fd; +} + +int link_tmpfile(int fd, const char *path, const char *target) { + + assert(fd >= 0); + assert(target); + + /* Moves a temporary file created with open_tmpfile() above into its final place. if "path" is NULL an fd + * created with O_TMPFILE is assumed, and linkat() is used. Otherwise it is assumed O_TMPFILE is not supported + * on the directory, and renameat2() is used instead. + * + * Note that in both cases we will not replace existing files. This is because linkat() does not support this + * operation currently (renameat2() does), and there is no nice way to emulate this. */ + + if (path) { + if (rename_noreplace(AT_FDCWD, path, AT_FDCWD, target) < 0) + return -errno; + } else { + char proc_fd_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1]; + + xsprintf(proc_fd_path, "/proc/self/fd/%i", fd); + + if (linkat(AT_FDCWD, proc_fd_path, AT_FDCWD, target, AT_SYMLINK_FOLLOW) < 0) + return -errno; + } + + return 0; +} diff --git a/src/libbasic/fileio.h b/src/libbasic/fileio.h new file mode 100644 index 0000000000..58dbc80c24 --- /dev/null +++ b/src/libbasic/fileio.h @@ -0,0 +1,88 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include + +#include "macro.h" +#include "time-util.h" + +typedef enum { + WRITE_STRING_FILE_CREATE = 1, + WRITE_STRING_FILE_ATOMIC = 2, + WRITE_STRING_FILE_AVOID_NEWLINE = 4, + WRITE_STRING_FILE_VERIFY_ON_FAILURE = 8, +} WriteStringFileFlags; + +int write_string_stream(FILE *f, const char *line, bool enforce_newline); +int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags); + +int read_one_line_file(const char *fn, char **line); +int read_full_file(const char *fn, char **contents, size_t *size); +int read_full_stream(FILE *f, char **contents, size_t *size); + +int verify_file(const char *fn, const char *blob, bool accept_extra_nl); + +int parse_env_file(const char *fname, const char *separator, ...) _sentinel_; +int load_env_file(FILE *f, const char *fname, const char *separator, char ***l); +int load_env_file_pairs(FILE *f, const char *fname, const char *separator, char ***l); + +int write_env_file(const char *fname, char **l); + +int executable_is_script(const char *path, char **interpreter); + +int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field); + +DIR *xopendirat(int dirfd, const char *name, int flags); + +int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **_f); +int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **_f); + +#define FOREACH_LINE(line, f, on_error) \ + for (;;) \ + if (!fgets(line, sizeof(line), f)) { \ + if (ferror(f)) { \ + on_error; \ + } \ + break; \ + } else + +int fflush_and_check(FILE *f); + +int fopen_temporary(const char *path, FILE **_f, char **_temp_path); +int mkostemp_safe(char *pattern, int flags); + +int tempfn_xxxxxx(const char *p, const char *extra, char **ret); +int tempfn_random(const char *p, const char *extra, char **ret); +int tempfn_random_child(const char *p, const char *extra, char **ret); + +int write_timestamp_file_atomic(const char *fn, usec_t n); +int read_timestamp_file(const char *fn, usec_t *ret); + +int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space); + +int open_tmpfile_unlinkable(const char *directory, int flags); +int open_tmpfile_linkable(const char *target, int flags, char **ret_path); + +int link_tmpfile(int fd, const char *path, const char *target); diff --git a/src/libbasic/formats-util.h b/src/libbasic/formats-util.h new file mode 100644 index 0000000000..9b4e8e98fa --- /dev/null +++ b/src/libbasic/formats-util.h @@ -0,0 +1,63 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Ronny Chevalier + + 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 + +#if SIZEOF_PID_T == 4 +# define PID_PRI PRIi32 +#elif SIZEOF_PID_T == 2 +# define PID_PRI PRIi16 +#else +# error Unknown pid_t size +#endif +#define PID_FMT "%" PID_PRI + +#if SIZEOF_UID_T == 4 +# define UID_FMT "%" PRIu32 +#elif SIZEOF_UID_T == 2 +# define UID_FMT "%" PRIu16 +#else +# error Unknown uid_t size +#endif + +#if SIZEOF_GID_T == 4 +# define GID_FMT "%" PRIu32 +#elif SIZEOF_GID_T == 2 +# define GID_FMT "%" PRIu16 +#else +# error Unknown gid_t size +#endif + +#if SIZEOF_TIME_T == 8 +# define PRI_TIME PRIi64 +#elif SIZEOF_TIME_T == 4 +# define PRI_TIME "li" +#else +# error Unknown time_t size +#endif + +#if SIZEOF_RLIM_T == 8 +# define RLIM_FMT "%" PRIu64 +#elif SIZEOF_RLIM_T == 4 +# define RLIM_FMT "%" PRIu32 +#else +# error Unknown rlim_t size +#endif diff --git a/src/libbasic/fs-util.c b/src/libbasic/fs-util.c new file mode 100644 index 0000000000..e24e7036f7 --- /dev/null +++ b/src/libbasic/fs-util.c @@ -0,0 +1,510 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "parse-util.h" +#include "path-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "user-util.h" +#include "util.h" + +int unlink_noerrno(const char *path) { + PROTECT_ERRNO; + int r; + + r = unlink(path); + if (r < 0) + return -errno; + + return 0; +} + +int rmdir_parents(const char *path, const char *stop) { + size_t l; + int r = 0; + + assert(path); + assert(stop); + + l = strlen(path); + + /* Skip trailing slashes */ + while (l > 0 && path[l-1] == '/') + l--; + + while (l > 0) { + char *t; + + /* Skip last component */ + while (l > 0 && path[l-1] != '/') + l--; + + /* Skip trailing slashes */ + while (l > 0 && path[l-1] == '/') + l--; + + if (l <= 0) + break; + + t = strndup(path, l); + if (!t) + return -ENOMEM; + + if (path_startswith(stop, t)) { + free(t); + return 0; + } + + r = rmdir(t); + free(t); + + if (r < 0) + if (errno != ENOENT) + return -errno; + } + + return 0; +} + + +int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { + struct stat buf; + int ret; + + ret = renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE); + if (ret >= 0) + return 0; + + /* renameat2() exists since Linux 3.15, btrfs added support for it later. + * If it is not implemented, fallback to another method. */ + if (!IN_SET(errno, EINVAL, ENOSYS)) + return -errno; + + /* The link()/unlink() fallback does not work on directories. But + * renameat() without RENAME_NOREPLACE gives the same semantics on + * directories, except when newpath is an *empty* directory. This is + * good enough. */ + ret = fstatat(olddirfd, oldpath, &buf, AT_SYMLINK_NOFOLLOW); + if (ret >= 0 && S_ISDIR(buf.st_mode)) { + ret = renameat(olddirfd, oldpath, newdirfd, newpath); + return ret >= 0 ? 0 : -errno; + } + + /* If it is not a directory, use the link()/unlink() fallback. */ + ret = linkat(olddirfd, oldpath, newdirfd, newpath, 0); + if (ret < 0) + return -errno; + + ret = unlinkat(olddirfd, oldpath, 0); + if (ret < 0) { + /* backup errno before the following unlinkat() alters it */ + ret = errno; + (void) unlinkat(newdirfd, newpath, 0); + errno = ret; + return -errno; + } + + return 0; +} + +int readlinkat_malloc(int fd, const char *p, char **ret) { + size_t l = 100; + int r; + + assert(p); + assert(ret); + + for (;;) { + char *c; + ssize_t n; + + c = new(char, l); + if (!c) + return -ENOMEM; + + n = readlinkat(fd, p, c, l-1); + if (n < 0) { + r = -errno; + free(c); + return r; + } + + if ((size_t) n < l-1) { + c[n] = 0; + *ret = c; + return 0; + } + + free(c); + l *= 2; + } +} + +int readlink_malloc(const char *p, char **ret) { + return readlinkat_malloc(AT_FDCWD, p, ret); +} + +int readlink_value(const char *p, char **ret) { + _cleanup_free_ char *link = NULL; + char *value; + int r; + + r = readlink_malloc(p, &link); + if (r < 0) + return r; + + value = basename(link); + if (!value) + return -ENOENT; + + value = strdup(value); + if (!value) + return -ENOMEM; + + *ret = value; + + return 0; +} + +int readlink_and_make_absolute(const char *p, char **r) { + _cleanup_free_ char *target = NULL; + char *k; + int j; + + assert(p); + assert(r); + + j = readlink_malloc(p, &target); + if (j < 0) + return j; + + k = file_in_same_dir(p, target); + if (!k) + return -ENOMEM; + + *r = k; + return 0; +} + +int readlink_and_canonicalize(const char *p, char **r) { + char *t, *s; + int j; + + assert(p); + assert(r); + + j = readlink_and_make_absolute(p, &t); + if (j < 0) + return j; + + s = canonicalize_file_name(t); + if (s) { + free(t); + *r = s; + } else + *r = t; + + path_kill_slashes(*r); + + return 0; +} + +int readlink_and_make_absolute_root(const char *root, const char *path, char **ret) { + _cleanup_free_ char *target = NULL, *t = NULL; + const char *full; + int r; + + full = prefix_roota(root, path); + r = readlink_malloc(full, &target); + if (r < 0) + return r; + + t = file_in_same_dir(path, target); + if (!t) + return -ENOMEM; + + *ret = t; + t = NULL; + + return 0; +} + +int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { + assert(path); + + /* Under the assumption that we are running privileged we + * first change the access mode and only then hand out + * ownership to avoid a window where access is too open. */ + + if (mode != MODE_INVALID) + if (chmod(path, mode) < 0) + return -errno; + + if (uid != UID_INVALID || gid != GID_INVALID) + if (chown(path, uid, gid) < 0) + return -errno; + + return 0; +} + +int fchmod_umask(int fd, mode_t m) { + mode_t u; + int r; + + u = umask(0777); + r = fchmod(fd, m & (~u)) < 0 ? -errno : 0; + umask(u); + + return r; +} + +int fd_warn_permissions(const char *path, int fd) { + struct stat st; + + if (fstat(fd, &st) < 0) + return -errno; + + if (st.st_mode & 0111) + log_warning("Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway.", path); + + if (st.st_mode & 0002) + log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path); + + if (getpid() == 1 && (st.st_mode & 0044) != 0044) + log_warning("Configuration file %s is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway.", path); + + return 0; +} + +int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) { + _cleanup_close_ int fd; + int r; + + assert(path); + + if (parents) + mkdir_parents(path, 0755); + + fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, + (mode == 0 || mode == MODE_INVALID) ? 0644 : mode); + if (fd < 0) + return -errno; + + if (mode != MODE_INVALID) { + r = fchmod(fd, mode); + if (r < 0) + return -errno; + } + + if (uid != UID_INVALID || gid != GID_INVALID) { + r = fchown(fd, uid, gid); + if (r < 0) + return -errno; + } + + if (stamp != USEC_INFINITY) { + struct timespec ts[2]; + + timespec_store(&ts[0], stamp); + ts[1] = ts[0]; + r = futimens(fd, ts); + } else + r = futimens(fd, NULL); + if (r < 0) + return -errno; + + return 0; +} + +int touch(const char *path) { + return touch_file(path, false, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID); +} + +int symlink_idempotent(const char *from, const char *to) { + _cleanup_free_ char *p = NULL; + int r; + + assert(from); + assert(to); + + if (symlink(from, to) < 0) { + if (errno != EEXIST) + return -errno; + + r = readlink_malloc(to, &p); + if (r < 0) + return r; + + if (!streq(p, from)) + return -EINVAL; + } + + return 0; +} + +int symlink_atomic(const char *from, const char *to) { + _cleanup_free_ char *t = NULL; + int r; + + assert(from); + assert(to); + + r = tempfn_random(to, NULL, &t); + if (r < 0) + return r; + + if (symlink(from, t) < 0) + return -errno; + + if (rename(t, to) < 0) { + unlink_noerrno(t); + return -errno; + } + + return 0; +} + +int mknod_atomic(const char *path, mode_t mode, dev_t dev) { + _cleanup_free_ char *t = NULL; + int r; + + assert(path); + + r = tempfn_random(path, NULL, &t); + if (r < 0) + return r; + + if (mknod(t, mode, dev) < 0) + return -errno; + + if (rename(t, path) < 0) { + unlink_noerrno(t); + return -errno; + } + + return 0; +} + +int mkfifo_atomic(const char *path, mode_t mode) { + _cleanup_free_ char *t = NULL; + int r; + + assert(path); + + r = tempfn_random(path, NULL, &t); + if (r < 0) + return r; + + if (mkfifo(t, mode) < 0) + return -errno; + + if (rename(t, path) < 0) { + unlink_noerrno(t); + return -errno; + } + + return 0; +} + +int get_files_in_directory(const char *path, char ***list) { + _cleanup_closedir_ DIR *d = NULL; + size_t bufsize = 0, n = 0; + _cleanup_strv_free_ char **l = NULL; + + assert(path); + + /* Returns all files in a directory in *list, and the number + * of files as return value. If list is NULL returns only the + * number. */ + + d = opendir(path); + if (!d) + return -errno; + + for (;;) { + struct dirent *de; + + errno = 0; + de = readdir(d); + if (!de && errno > 0) + return -errno; + if (!de) + break; + + dirent_ensure_type(d, de); + + if (!dirent_is_file(de)) + continue; + + if (list) { + /* one extra slot is needed for the terminating NULL */ + if (!GREEDY_REALLOC(l, bufsize, n + 2)) + return -ENOMEM; + + l[n] = strdup(de->d_name); + if (!l[n]) + return -ENOMEM; + + l[++n] = NULL; + } else + n++; + } + + if (list) { + *list = l; + l = NULL; /* avoid freeing */ + } + + return n; +} + +int inotify_add_watch_fd(int fd, int what, uint32_t mask) { + char path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; + int r; + + /* This is like inotify_add_watch(), except that the file to watch is not referenced by a path, but by an fd */ + xsprintf(path, "/proc/self/fd/%i", what); + + r = inotify_add_watch(fd, path, mask); + if (r < 0) + return -errno; + + return r; +} diff --git a/src/libbasic/fs-util.h b/src/libbasic/fs-util.h new file mode 100644 index 0000000000..517b599d6f --- /dev/null +++ b/src/libbasic/fs-util.h @@ -0,0 +1,76 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include + +#include "time-util.h" + +int unlink_noerrno(const char *path); + +int rmdir_parents(const char *path, const char *stop); + +int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); + +int readlinkat_malloc(int fd, const char *p, char **ret); +int readlink_malloc(const char *p, char **r); +int readlink_value(const char *p, char **ret); +int readlink_and_make_absolute(const char *p, char **r); +int readlink_and_canonicalize(const char *p, char **r); +int readlink_and_make_absolute_root(const char *root, const char *path, char **ret); + +int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid); + +int fchmod_umask(int fd, mode_t mode); + +int fd_warn_permissions(const char *path, int fd); + +#define laccess(path, mode) faccessat(AT_FDCWD, (path), (mode), AT_SYMLINK_NOFOLLOW) + +int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode); +int touch(const char *path); + +int symlink_idempotent(const char *from, const char *to); + +int symlink_atomic(const char *from, const char *to); +int mknod_atomic(const char *path, mode_t mode, dev_t dev); +int mkfifo_atomic(const char *path, mode_t mode); + +int get_files_in_directory(const char *path, char ***list); + +#define INOTIFY_EVENT_MAX (sizeof(struct inotify_event) + NAME_MAX + 1) + +#define FOREACH_INOTIFY_EVENT(e, buffer, sz) \ + for ((e) = &buffer.ev; \ + (uint8_t*) (e) < (uint8_t*) (buffer.raw) + (sz); \ + (e) = (struct inotify_event*) ((uint8_t*) (e) + sizeof(struct inotify_event) + (e)->len)) + +union inotify_event_buffer { + struct inotify_event ev; + uint8_t raw[INOTIFY_EVENT_MAX]; +}; + +int inotify_add_watch_fd(int fd, int what, uint32_t mask); diff --git a/src/libbasic/glob-util.c b/src/libbasic/glob-util.c new file mode 100644 index 0000000000..007198c269 --- /dev/null +++ b/src/libbasic/glob-util.c @@ -0,0 +1,70 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 + +#include "glob-util.h" +#include "macro.h" +#include "strv.h" + +int glob_exists(const char *path) { + _cleanup_globfree_ glob_t g = {}; + int k; + + assert(path); + + errno = 0; + k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g); + + if (k == GLOB_NOMATCH) + return 0; + if (k == GLOB_NOSPACE) + return -ENOMEM; + if (k != 0) + return errno > 0 ? -errno : -EIO; + + return !strv_isempty(g.gl_pathv); +} + +int glob_extend(char ***strv, const char *path) { + _cleanup_globfree_ glob_t g = {}; + int k; + char **p; + + errno = 0; + k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g); + + if (k == GLOB_NOMATCH) + return -ENOENT; + if (k == GLOB_NOSPACE) + return -ENOMEM; + if (k != 0) + return errno > 0 ? -errno : -EIO; + if (strv_isempty(g.gl_pathv)) + return -ENOENT; + + STRV_FOREACH(p, g.gl_pathv) { + k = strv_extend(strv, *p); + if (k < 0) + return k; + } + + return 0; +} diff --git a/src/libbasic/glob-util.h b/src/libbasic/glob-util.h new file mode 100644 index 0000000000..5d8fb47a26 --- /dev/null +++ b/src/libbasic/glob-util.h @@ -0,0 +1,36 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 + +#include "macro.h" +#include "string-util.h" + +int glob_exists(const char *path); +int glob_extend(char ***strv, const char *path); + +#define _cleanup_globfree_ _cleanup_(globfree) + +_pure_ static inline bool string_is_glob(const char *p) { + /* Check if a string contains any glob patterns. */ + return !!strpbrk(p, GLOB_CHARS); +} diff --git a/src/libbasic/gunicode.c b/src/libbasic/gunicode.c new file mode 100644 index 0000000000..542110503f --- /dev/null +++ b/src/libbasic/gunicode.c @@ -0,0 +1,112 @@ +/* gunicode.c - Unicode manipulation functions + * + * Copyright (C) 1999, 2000 Tom Tromey + * Copyright 2000, 2005 Red Hat, Inc. + */ + +#include + +#include "gunicode.h" + +#define unichar uint32_t + +/** + * g_utf8_prev_char: + * @p: a pointer to a position within a UTF-8 encoded string + * + * Finds the previous UTF-8 character in the string before @p. + * + * @p does not have to be at the beginning of a UTF-8 character. No check + * is made to see if the character found is actually valid other than + * it starts with an appropriate byte. If @p might be the first + * character of the string, you must use g_utf8_find_prev_char() instead. + * + * Return value: a pointer to the found character. + **/ +char * +utf8_prev_char (const char *p) +{ + while (1) + { + p--; + if ((*p & 0xc0) != 0x80) + return (char *)p; + } +} + +struct Interval +{ + unichar start, end; +}; + +static int +interval_compare (const void *key, const void *elt) +{ + unichar c = (unichar) (long) (key); + struct Interval *interval = (struct Interval *)elt; + + if (c < interval->start) + return -1; + if (c > interval->end) + return +1; + + return 0; +} + +/* + * NOTE: + * + * The tables for g_unichar_iswide() and g_unichar_iswide_cjk() are + * generated from the Unicode Character Database's file + * extracted/DerivedEastAsianWidth.txt using the gen-iswide-table.py + * in this way: + * + * ./gen-iswide-table.py < path/to/ucd/extracted/DerivedEastAsianWidth.txt | fmt + * + * Last update for Unicode 6.0. + */ + +/** + * g_unichar_iswide: + * @c: a Unicode character + * + * Determines if a character is typically rendered in a double-width + * cell. + * + * Return value: %TRUE if the character is wide + **/ +bool +unichar_iswide (unichar c) +{ + /* See NOTE earlier for how to update this table. */ + static const struct Interval wide[] = { + {0x1100, 0x115F}, {0x2329, 0x232A}, {0x2E80, 0x2E99}, {0x2E9B, 0x2EF3}, + {0x2F00, 0x2FD5}, {0x2FF0, 0x2FFB}, {0x3000, 0x303E}, {0x3041, 0x3096}, + {0x3099, 0x30FF}, {0x3105, 0x312D}, {0x3131, 0x318E}, {0x3190, 0x31BA}, + {0x31C0, 0x31E3}, {0x31F0, 0x321E}, {0x3220, 0x3247}, {0x3250, 0x32FE}, + {0x3300, 0x4DBF}, {0x4E00, 0xA48C}, {0xA490, 0xA4C6}, {0xA960, 0xA97C}, + {0xAC00, 0xD7A3}, {0xF900, 0xFAFF}, {0xFE10, 0xFE19}, {0xFE30, 0xFE52}, + {0xFE54, 0xFE66}, {0xFE68, 0xFE6B}, {0xFF01, 0xFF60}, {0xFFE0, 0xFFE6}, + {0x1B000, 0x1B001}, {0x1F200, 0x1F202}, {0x1F210, 0x1F23A}, + {0x1F240, 0x1F248}, {0x1F250, 0x1F251}, + {0x1F300, 0x1F567}, /* Miscellaneous Symbols and Pictographs */ + {0x20000, 0x2FFFD}, {0x30000, 0x3FFFD}, + }; + + if (bsearch ((void *)(uintptr_t)c, wide, (sizeof (wide) / sizeof ((wide)[0])), sizeof wide[0], + interval_compare)) + return true; + + return false; +} + +const char utf8_skip_data[256] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1 +}; diff --git a/src/libbasic/gunicode.h b/src/libbasic/gunicode.h new file mode 100644 index 0000000000..5975bc8fc9 --- /dev/null +++ b/src/libbasic/gunicode.h @@ -0,0 +1,30 @@ +#pragma once + +/* gunicode.h - Unicode manipulation functions + * + * Copyright (C) 1999, 2000 Tom Tromey + * Copyright 2000, 2005 Red Hat, Inc. + */ + +#include +#include +#include + +char *utf8_prev_char (const char *p); + +extern const char utf8_skip_data[256]; + +/** + * g_utf8_next_char: + * @p: Pointer to the start of a valid UTF-8 character + * + * Skips to the next character in a UTF-8 string. The string must be + * valid; this macro is as fast as possible, and has no error-checking. + * You would use this macro to iterate over a string character by + * character. The macro returns the start of the next UTF-8 character. + * Before using this macro, use g_utf8_validate() to validate strings + * that may contain invalid UTF-8. + */ +#define utf8_next_char(p) (char *)((p) + utf8_skip_data[*(const unsigned char *)(p)]) + +bool unichar_iswide (uint32_t c); diff --git a/src/libbasic/hash-funcs.c b/src/libbasic/hash-funcs.c new file mode 100644 index 0000000000..c3a4a011b5 --- /dev/null +++ b/src/libbasic/hash-funcs.c @@ -0,0 +1,81 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2014 Michal Schmidt + + 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 "hash-funcs.h" + +void string_hash_func(const void *p, struct siphash *state) { + siphash24_compress(p, strlen(p) + 1, state); +} + +int string_compare_func(const void *a, const void *b) { + return strcmp(a, b); +} + +const struct hash_ops string_hash_ops = { + .hash = string_hash_func, + .compare = string_compare_func +}; + +void trivial_hash_func(const void *p, struct siphash *state) { + siphash24_compress(&p, sizeof(p), state); +} + +int trivial_compare_func(const void *a, const void *b) { + return a < b ? -1 : (a > b ? 1 : 0); +} + +const struct hash_ops trivial_hash_ops = { + .hash = trivial_hash_func, + .compare = trivial_compare_func +}; + +void uint64_hash_func(const void *p, struct siphash *state) { + siphash24_compress(p, sizeof(uint64_t), state); +} + +int uint64_compare_func(const void *_a, const void *_b) { + uint64_t a, b; + a = *(const uint64_t*) _a; + b = *(const uint64_t*) _b; + return a < b ? -1 : (a > b ? 1 : 0); +} + +const struct hash_ops uint64_hash_ops = { + .hash = uint64_hash_func, + .compare = uint64_compare_func +}; + +#if SIZEOF_DEV_T != 8 +void devt_hash_func(const void *p, struct siphash *state) { + siphash24_compress(p, sizeof(dev_t), state); +} + +int devt_compare_func(const void *_a, const void *_b) { + dev_t a, b; + a = *(const dev_t*) _a; + b = *(const dev_t*) _b; + return a < b ? -1 : (a > b ? 1 : 0); +} + +const struct hash_ops devt_hash_ops = { + .hash = devt_hash_func, + .compare = devt_compare_func +}; +#endif diff --git a/src/libbasic/hash-funcs.h b/src/libbasic/hash-funcs.h new file mode 100644 index 0000000000..299189d143 --- /dev/null +++ b/src/libbasic/hash-funcs.h @@ -0,0 +1,65 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2014 Michal Schmidt + + 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 "macro.h" +#include "siphash24.h" + +typedef void (*hash_func_t)(const void *p, struct siphash *state); +typedef int (*compare_func_t)(const void *a, const void *b); + +struct hash_ops { + hash_func_t hash; + compare_func_t compare; +}; + +void string_hash_func(const void *p, struct siphash *state); +int string_compare_func(const void *a, const void *b) _pure_; +extern const struct hash_ops string_hash_ops; + +/* This will compare the passed pointers directly, and will not + * dereference them. This is hence not useful for strings or + * suchlike. */ +void trivial_hash_func(const void *p, struct siphash *state); +int trivial_compare_func(const void *a, const void *b) _const_; +extern const struct hash_ops trivial_hash_ops; + +/* 32bit values we can always just embed in the pointer itself, but + * in order to support 32bit archs we need store 64bit values + * indirectly, since they don't fit in a pointer. */ +void uint64_hash_func(const void *p, struct siphash *state); +int uint64_compare_func(const void *a, const void *b) _pure_; +extern const struct hash_ops uint64_hash_ops; + +/* On some archs dev_t is 32bit, and on others 64bit. And sometimes + * it's 64bit on 32bit archs, and sometimes 32bit on 64bit archs. Yuck! */ +#if SIZEOF_DEV_T != 8 +void devt_hash_func(const void *p, struct siphash *state) _pure_; +int devt_compare_func(const void *a, const void *b) _pure_; +extern const struct hash_ops devt_hash_ops = { + .hash = devt_hash_func, + .compare = devt_compare_func +}; +#else +#define devt_hash_func uint64_hash_func +#define devt_compare_func uint64_compare_func +#define devt_hash_ops uint64_hash_ops +#endif diff --git a/src/libbasic/hashmap.c b/src/libbasic/hashmap.c new file mode 100644 index 0000000000..49a0479592 --- /dev/null +++ b/src/libbasic/hashmap.c @@ -0,0 +1,1803 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2014 Michal Schmidt + + 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 +#include +#include + +#include "alloc-util.h" +#include "hashmap.h" +#include "macro.h" +#include "mempool.h" +#include "process-util.h" +#include "random-util.h" +#include "set.h" +#include "siphash24.h" +#include "strv.h" +#include "util.h" + +#ifdef ENABLE_DEBUG_HASHMAP +#include +#include "list.h" +#endif + +/* + * Implementation of hashmaps. + * Addressing: open + * - uses less RAM compared to closed addressing (chaining), because + * our entries are small (especially in Sets, which tend to contain + * the majority of entries in systemd). + * Collision resolution: Robin Hood + * - tends to equalize displacement of entries from their optimal buckets. + * Probe sequence: linear + * - though theoretically worse than random probing/uniform hashing/double + * hashing, it is good for cache locality. + * + * References: + * Celis, P. 1986. Robin Hood Hashing. + * Ph.D. Dissertation. University of Waterloo, Waterloo, Ont., Canada, Canada. + * https://cs.uwaterloo.ca/research/tr/1986/CS-86-14.pdf + * - The results are derived for random probing. Suggests deletion with + * tombstones and two mean-centered search methods. None of that works + * well for linear probing. + * + * Janson, S. 2005. Individual displacements for linear probing hashing with different insertion policies. + * ACM Trans. Algorithms 1, 2 (October 2005), 177-213. + * DOI=10.1145/1103963.1103964 http://doi.acm.org/10.1145/1103963.1103964 + * http://www.math.uu.se/~svante/papers/sj157.pdf + * - Applies to Robin Hood with linear probing. Contains remarks on + * the unsuitability of mean-centered search with linear probing. + * + * Viola, A. 2005. Exact distribution of individual displacements in linear probing hashing. + * ACM Trans. Algorithms 1, 2 (October 2005), 214-242. + * DOI=10.1145/1103963.1103965 http://doi.acm.org/10.1145/1103963.1103965 + * - Similar to Janson. Note that Viola writes about C_{m,n} (number of probes + * in a successful search), and Janson writes about displacement. C = d + 1. + * + * Goossaert, E. 2013. Robin Hood hashing: backward shift deletion. + * http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/ + * - Explanation of backward shift deletion with pictures. + * + * Khuong, P. 2013. The Other Robin Hood Hashing. + * http://www.pvk.ca/Blog/2013/11/26/the-other-robin-hood-hashing/ + * - Short summary of random vs. linear probing, and tombstones vs. backward shift. + */ + +/* + * XXX Ideas for improvement: + * For unordered hashmaps, randomize iteration order, similarly to Perl: + * http://blog.booking.com/hardening-perls-hash-function.html + */ + +/* INV_KEEP_FREE = 1 / (1 - max_load_factor) + * e.g. 1 / (1 - 0.8) = 5 ... keep one fifth of the buckets free. */ +#define INV_KEEP_FREE 5U + +/* Fields common to entries of all hashmap/set types */ +struct hashmap_base_entry { + const void *key; +}; + +/* Entry types for specific hashmap/set types + * hashmap_base_entry must be at the beginning of each entry struct. */ + +struct plain_hashmap_entry { + struct hashmap_base_entry b; + void *value; +}; + +struct ordered_hashmap_entry { + struct plain_hashmap_entry p; + unsigned iterate_next, iterate_previous; +}; + +struct set_entry { + struct hashmap_base_entry b; +}; + +/* In several functions it is advantageous to have the hash table extended + * virtually by a couple of additional buckets. We reserve special index values + * for these "swap" buckets. */ +#define _IDX_SWAP_BEGIN (UINT_MAX - 3) +#define IDX_PUT (_IDX_SWAP_BEGIN + 0) +#define IDX_TMP (_IDX_SWAP_BEGIN + 1) +#define _IDX_SWAP_END (_IDX_SWAP_BEGIN + 2) + +#define IDX_FIRST (UINT_MAX - 1) /* special index for freshly initialized iterators */ +#define IDX_NIL UINT_MAX /* special index value meaning "none" or "end" */ + +assert_cc(IDX_FIRST == _IDX_SWAP_END); +assert_cc(IDX_FIRST == _IDX_ITERATOR_FIRST); + +/* Storage space for the "swap" buckets. + * All entry types can fit into a ordered_hashmap_entry. */ +struct swap_entries { + struct ordered_hashmap_entry e[_IDX_SWAP_END - _IDX_SWAP_BEGIN]; +}; + +/* Distance from Initial Bucket */ +typedef uint8_t dib_raw_t; +#define DIB_RAW_OVERFLOW ((dib_raw_t)0xfdU) /* indicates DIB value is greater than representable */ +#define DIB_RAW_REHASH ((dib_raw_t)0xfeU) /* entry yet to be rehashed during in-place resize */ +#define DIB_RAW_FREE ((dib_raw_t)0xffU) /* a free bucket */ +#define DIB_RAW_INIT ((char)DIB_RAW_FREE) /* a byte to memset a DIB store with when initializing */ + +#define DIB_FREE UINT_MAX + +#ifdef ENABLE_DEBUG_HASHMAP +struct hashmap_debug_info { + LIST_FIELDS(struct hashmap_debug_info, debug_list); + unsigned max_entries; /* high watermark of n_entries */ + + /* who allocated this hashmap */ + int line; + const char *file; + const char *func; + + /* fields to detect modification while iterating */ + unsigned put_count; /* counts puts into the hashmap */ + unsigned rem_count; /* counts removals from hashmap */ + unsigned last_rem_idx; /* remembers last removal index */ +}; + +/* Tracks all existing hashmaps. Get at it from gdb. See sd_dump_hashmaps.py */ +static LIST_HEAD(struct hashmap_debug_info, hashmap_debug_list); +static pthread_mutex_t hashmap_debug_list_mutex = PTHREAD_MUTEX_INITIALIZER; + +#define HASHMAP_DEBUG_FIELDS struct hashmap_debug_info debug; + +#else /* !ENABLE_DEBUG_HASHMAP */ +#define HASHMAP_DEBUG_FIELDS +#endif /* ENABLE_DEBUG_HASHMAP */ + +enum HashmapType { + HASHMAP_TYPE_PLAIN, + HASHMAP_TYPE_ORDERED, + HASHMAP_TYPE_SET, + _HASHMAP_TYPE_MAX +}; + +struct _packed_ indirect_storage { + void *storage; /* where buckets and DIBs are stored */ + uint8_t hash_key[HASH_KEY_SIZE]; /* hash key; changes during resize */ + + unsigned n_entries; /* number of stored entries */ + unsigned n_buckets; /* number of buckets */ + + unsigned idx_lowest_entry; /* Index below which all buckets are free. + Makes "while(hashmap_steal_first())" loops + O(n) instead of O(n^2) for unordered hashmaps. */ + uint8_t _pad[3]; /* padding for the whole HashmapBase */ + /* The bitfields in HashmapBase complete the alignment of the whole thing. */ +}; + +struct direct_storage { + /* This gives us 39 bytes on 64bit, or 35 bytes on 32bit. + * That's room for 4 set_entries + 4 DIB bytes + 3 unused bytes on 64bit, + * or 7 set_entries + 7 DIB bytes + 0 unused bytes on 32bit. */ + uint8_t storage[sizeof(struct indirect_storage)]; +}; + +#define DIRECT_BUCKETS(entry_t) \ + (sizeof(struct direct_storage) / (sizeof(entry_t) + sizeof(dib_raw_t))) + +/* We should be able to store at least one entry directly. */ +assert_cc(DIRECT_BUCKETS(struct ordered_hashmap_entry) >= 1); + +/* We have 3 bits for n_direct_entries. */ +assert_cc(DIRECT_BUCKETS(struct set_entry) < (1 << 3)); + +/* Hashmaps with directly stored entries all use this shared hash key. + * It's no big deal if the key is guessed, because there can be only + * a handful of directly stored entries in a hashmap. When a hashmap + * outgrows direct storage, it gets its own key for indirect storage. */ +static uint8_t shared_hash_key[HASH_KEY_SIZE]; +static bool shared_hash_key_initialized; + +/* Fields that all hashmap/set types must have */ +struct HashmapBase { + const struct hash_ops *hash_ops; /* hash and compare ops to use */ + + union _packed_ { + struct indirect_storage indirect; /* if has_indirect */ + struct direct_storage direct; /* if !has_indirect */ + }; + + enum HashmapType type:2; /* HASHMAP_TYPE_* */ + bool has_indirect:1; /* whether indirect storage is used */ + unsigned n_direct_entries:3; /* Number of entries in direct storage. + * Only valid if !has_indirect. */ + bool from_pool:1; /* whether was allocated from mempool */ + HASHMAP_DEBUG_FIELDS /* optional hashmap_debug_info */ +}; + +/* Specific hash types + * HashmapBase must be at the beginning of each hashmap struct. */ + +struct Hashmap { + struct HashmapBase b; +}; + +struct OrderedHashmap { + struct HashmapBase b; + unsigned iterate_list_head, iterate_list_tail; +}; + +struct Set { + struct HashmapBase b; +}; + +DEFINE_MEMPOOL(hashmap_pool, Hashmap, 8); +DEFINE_MEMPOOL(ordered_hashmap_pool, OrderedHashmap, 8); +/* No need for a separate Set pool */ +assert_cc(sizeof(Hashmap) == sizeof(Set)); + +struct hashmap_type_info { + size_t head_size; + size_t entry_size; + struct mempool *mempool; + unsigned n_direct_buckets; +}; + +static const struct hashmap_type_info hashmap_type_info[_HASHMAP_TYPE_MAX] = { + [HASHMAP_TYPE_PLAIN] = { + .head_size = sizeof(Hashmap), + .entry_size = sizeof(struct plain_hashmap_entry), + .mempool = &hashmap_pool, + .n_direct_buckets = DIRECT_BUCKETS(struct plain_hashmap_entry), + }, + [HASHMAP_TYPE_ORDERED] = { + .head_size = sizeof(OrderedHashmap), + .entry_size = sizeof(struct ordered_hashmap_entry), + .mempool = &ordered_hashmap_pool, + .n_direct_buckets = DIRECT_BUCKETS(struct ordered_hashmap_entry), + }, + [HASHMAP_TYPE_SET] = { + .head_size = sizeof(Set), + .entry_size = sizeof(struct set_entry), + .mempool = &hashmap_pool, + .n_direct_buckets = DIRECT_BUCKETS(struct set_entry), + }, +}; + +static unsigned n_buckets(HashmapBase *h) { + return h->has_indirect ? h->indirect.n_buckets + : hashmap_type_info[h->type].n_direct_buckets; +} + +static unsigned n_entries(HashmapBase *h) { + return h->has_indirect ? h->indirect.n_entries + : h->n_direct_entries; +} + +static void n_entries_inc(HashmapBase *h) { + if (h->has_indirect) + h->indirect.n_entries++; + else + h->n_direct_entries++; +} + +static void n_entries_dec(HashmapBase *h) { + if (h->has_indirect) + h->indirect.n_entries--; + else + h->n_direct_entries--; +} + +static void *storage_ptr(HashmapBase *h) { + return h->has_indirect ? h->indirect.storage + : h->direct.storage; +} + +static uint8_t *hash_key(HashmapBase *h) { + return h->has_indirect ? h->indirect.hash_key + : shared_hash_key; +} + +static unsigned base_bucket_hash(HashmapBase *h, const void *p) { + struct siphash state; + uint64_t hash; + + siphash24_init(&state, hash_key(h)); + + h->hash_ops->hash(p, &state); + + hash = siphash24_finalize(&state); + + return (unsigned) (hash % n_buckets(h)); +} +#define bucket_hash(h, p) base_bucket_hash(HASHMAP_BASE(h), p) + +static void get_hash_key(uint8_t hash_key[HASH_KEY_SIZE], bool reuse_is_ok) { + static uint8_t current[HASH_KEY_SIZE]; + static bool current_initialized = false; + + /* Returns a hash function key to use. In order to keep things + * fast we will not generate a new key each time we allocate a + * new hash table. Instead, we'll just reuse the most recently + * generated one, except if we never generated one or when we + * are rehashing an entire hash table because we reached a + * fill level */ + + if (!current_initialized || !reuse_is_ok) { + random_bytes(current, sizeof(current)); + current_initialized = true; + } + + memcpy(hash_key, current, sizeof(current)); +} + +static struct hashmap_base_entry *bucket_at(HashmapBase *h, unsigned idx) { + return (struct hashmap_base_entry*) + ((uint8_t*) storage_ptr(h) + idx * hashmap_type_info[h->type].entry_size); +} + +static struct plain_hashmap_entry *plain_bucket_at(Hashmap *h, unsigned idx) { + return (struct plain_hashmap_entry*) bucket_at(HASHMAP_BASE(h), idx); +} + +static struct ordered_hashmap_entry *ordered_bucket_at(OrderedHashmap *h, unsigned idx) { + return (struct ordered_hashmap_entry*) bucket_at(HASHMAP_BASE(h), idx); +} + +static struct set_entry *set_bucket_at(Set *h, unsigned idx) { + return (struct set_entry*) bucket_at(HASHMAP_BASE(h), idx); +} + +static struct ordered_hashmap_entry *bucket_at_swap(struct swap_entries *swap, unsigned idx) { + return &swap->e[idx - _IDX_SWAP_BEGIN]; +} + +/* Returns a pointer to the bucket at index idx. + * Understands real indexes and swap indexes, hence "_virtual". */ +static struct hashmap_base_entry *bucket_at_virtual(HashmapBase *h, struct swap_entries *swap, + unsigned idx) { + if (idx < _IDX_SWAP_BEGIN) + return bucket_at(h, idx); + + if (idx < _IDX_SWAP_END) + return &bucket_at_swap(swap, idx)->p.b; + + assert_not_reached("Invalid index"); +} + +static dib_raw_t *dib_raw_ptr(HashmapBase *h) { + return (dib_raw_t*) + ((uint8_t*) storage_ptr(h) + hashmap_type_info[h->type].entry_size * n_buckets(h)); +} + +static unsigned bucket_distance(HashmapBase *h, unsigned idx, unsigned from) { + return idx >= from ? idx - from + : n_buckets(h) + idx - from; +} + +static unsigned bucket_calculate_dib(HashmapBase *h, unsigned idx, dib_raw_t raw_dib) { + unsigned initial_bucket; + + if (raw_dib == DIB_RAW_FREE) + return DIB_FREE; + + if (_likely_(raw_dib < DIB_RAW_OVERFLOW)) + return raw_dib; + + /* + * Having an overflow DIB value is very unlikely. The hash function + * would have to be bad. For example, in a table of size 2^24 filled + * to load factor 0.9 the maximum observed DIB is only about 60. + * In theory (assuming I used Maxima correctly), for an infinite size + * hash table with load factor 0.8 the probability of a given entry + * having DIB > 40 is 1.9e-8. + * This returns the correct DIB value by recomputing the hash value in + * the unlikely case. XXX Hitting this case could be a hint to rehash. + */ + initial_bucket = bucket_hash(h, bucket_at(h, idx)->key); + return bucket_distance(h, idx, initial_bucket); +} + +static void bucket_set_dib(HashmapBase *h, unsigned idx, unsigned dib) { + dib_raw_ptr(h)[idx] = dib != DIB_FREE ? MIN(dib, DIB_RAW_OVERFLOW) : DIB_RAW_FREE; +} + +static unsigned skip_free_buckets(HashmapBase *h, unsigned idx) { + dib_raw_t *dibs; + + dibs = dib_raw_ptr(h); + + for ( ; idx < n_buckets(h); idx++) + if (dibs[idx] != DIB_RAW_FREE) + return idx; + + return IDX_NIL; +} + +static void bucket_mark_free(HashmapBase *h, unsigned idx) { + memzero(bucket_at(h, idx), hashmap_type_info[h->type].entry_size); + bucket_set_dib(h, idx, DIB_FREE); +} + +static void bucket_move_entry(HashmapBase *h, struct swap_entries *swap, + unsigned from, unsigned to) { + struct hashmap_base_entry *e_from, *e_to; + + assert(from != to); + + e_from = bucket_at_virtual(h, swap, from); + e_to = bucket_at_virtual(h, swap, to); + + memcpy(e_to, e_from, hashmap_type_info[h->type].entry_size); + + if (h->type == HASHMAP_TYPE_ORDERED) { + OrderedHashmap *lh = (OrderedHashmap*) h; + struct ordered_hashmap_entry *le, *le_to; + + le_to = (struct ordered_hashmap_entry*) e_to; + + if (le_to->iterate_next != IDX_NIL) { + le = (struct ordered_hashmap_entry*) + bucket_at_virtual(h, swap, le_to->iterate_next); + le->iterate_previous = to; + } + + if (le_to->iterate_previous != IDX_NIL) { + le = (struct ordered_hashmap_entry*) + bucket_at_virtual(h, swap, le_to->iterate_previous); + le->iterate_next = to; + } + + if (lh->iterate_list_head == from) + lh->iterate_list_head = to; + if (lh->iterate_list_tail == from) + lh->iterate_list_tail = to; + } +} + +static unsigned next_idx(HashmapBase *h, unsigned idx) { + return (idx + 1U) % n_buckets(h); +} + +static unsigned prev_idx(HashmapBase *h, unsigned idx) { + return (n_buckets(h) + idx - 1U) % n_buckets(h); +} + +static void *entry_value(HashmapBase *h, struct hashmap_base_entry *e) { + switch (h->type) { + + case HASHMAP_TYPE_PLAIN: + case HASHMAP_TYPE_ORDERED: + return ((struct plain_hashmap_entry*)e)->value; + + case HASHMAP_TYPE_SET: + return (void*) e->key; + + default: + assert_not_reached("Unknown hashmap type"); + } +} + +static void base_remove_entry(HashmapBase *h, unsigned idx) { + unsigned left, right, prev, dib; + dib_raw_t raw_dib, *dibs; + + dibs = dib_raw_ptr(h); + assert(dibs[idx] != DIB_RAW_FREE); + +#ifdef ENABLE_DEBUG_HASHMAP + h->debug.rem_count++; + h->debug.last_rem_idx = idx; +#endif + + left = idx; + /* Find the stop bucket ("right"). It is either free or has DIB == 0. */ + for (right = next_idx(h, left); ; right = next_idx(h, right)) { + raw_dib = dibs[right]; + if (raw_dib == 0 || raw_dib == DIB_RAW_FREE) + break; + + /* The buckets are not supposed to be all occupied and with DIB > 0. + * That would mean we could make everyone better off by shifting them + * backward. This scenario is impossible. */ + assert(left != right); + } + + if (h->type == HASHMAP_TYPE_ORDERED) { + OrderedHashmap *lh = (OrderedHashmap*) h; + struct ordered_hashmap_entry *le = ordered_bucket_at(lh, idx); + + if (le->iterate_next != IDX_NIL) + ordered_bucket_at(lh, le->iterate_next)->iterate_previous = le->iterate_previous; + else + lh->iterate_list_tail = le->iterate_previous; + + if (le->iterate_previous != IDX_NIL) + ordered_bucket_at(lh, le->iterate_previous)->iterate_next = le->iterate_next; + else + lh->iterate_list_head = le->iterate_next; + } + + /* Now shift all buckets in the interval (left, right) one step backwards */ + for (prev = left, left = next_idx(h, left); left != right; + prev = left, left = next_idx(h, left)) { + dib = bucket_calculate_dib(h, left, dibs[left]); + assert(dib != 0); + bucket_move_entry(h, NULL, left, prev); + bucket_set_dib(h, prev, dib - 1); + } + + bucket_mark_free(h, prev); + n_entries_dec(h); +} +#define remove_entry(h, idx) base_remove_entry(HASHMAP_BASE(h), idx) + +static unsigned hashmap_iterate_in_insertion_order(OrderedHashmap *h, Iterator *i) { + struct ordered_hashmap_entry *e; + unsigned idx; + + assert(h); + assert(i); + + if (i->idx == IDX_NIL) + goto at_end; + + if (i->idx == IDX_FIRST && h->iterate_list_head == IDX_NIL) + goto at_end; + + if (i->idx == IDX_FIRST) { + idx = h->iterate_list_head; + e = ordered_bucket_at(h, idx); + } else { + idx = i->idx; + e = ordered_bucket_at(h, idx); + /* + * We allow removing the current entry while iterating, but removal may cause + * a backward shift. The next entry may thus move one bucket to the left. + * To detect when it happens, we remember the key pointer of the entry we were + * going to iterate next. If it does not match, there was a backward shift. + */ + if (e->p.b.key != i->next_key) { + idx = prev_idx(HASHMAP_BASE(h), idx); + e = ordered_bucket_at(h, idx); + } + assert(e->p.b.key == i->next_key); + } + +#ifdef ENABLE_DEBUG_HASHMAP + i->prev_idx = idx; +#endif + + if (e->iterate_next != IDX_NIL) { + struct ordered_hashmap_entry *n; + i->idx = e->iterate_next; + n = ordered_bucket_at(h, i->idx); + i->next_key = n->p.b.key; + } else + i->idx = IDX_NIL; + + return idx; + +at_end: + i->idx = IDX_NIL; + return IDX_NIL; +} + +static unsigned hashmap_iterate_in_internal_order(HashmapBase *h, Iterator *i) { + unsigned idx; + + assert(h); + assert(i); + + if (i->idx == IDX_NIL) + goto at_end; + + if (i->idx == IDX_FIRST) { + /* fast forward to the first occupied bucket */ + if (h->has_indirect) { + i->idx = skip_free_buckets(h, h->indirect.idx_lowest_entry); + h->indirect.idx_lowest_entry = i->idx; + } else + i->idx = skip_free_buckets(h, 0); + + if (i->idx == IDX_NIL) + goto at_end; + } else { + struct hashmap_base_entry *e; + + assert(i->idx > 0); + + e = bucket_at(h, i->idx); + /* + * We allow removing the current entry while iterating, but removal may cause + * a backward shift. The next entry may thus move one bucket to the left. + * To detect when it happens, we remember the key pointer of the entry we were + * going to iterate next. If it does not match, there was a backward shift. + */ + if (e->key != i->next_key) + e = bucket_at(h, --i->idx); + + assert(e->key == i->next_key); + } + + idx = i->idx; +#ifdef ENABLE_DEBUG_HASHMAP + i->prev_idx = idx; +#endif + + i->idx = skip_free_buckets(h, i->idx + 1); + if (i->idx != IDX_NIL) + i->next_key = bucket_at(h, i->idx)->key; + else + i->idx = IDX_NIL; + + return idx; + +at_end: + i->idx = IDX_NIL; + return IDX_NIL; +} + +static unsigned hashmap_iterate_entry(HashmapBase *h, Iterator *i) { + if (!h) { + i->idx = IDX_NIL; + return IDX_NIL; + } + +#ifdef ENABLE_DEBUG_HASHMAP + if (i->idx == IDX_FIRST) { + i->put_count = h->debug.put_count; + i->rem_count = h->debug.rem_count; + } else { + /* While iterating, must not add any new entries */ + assert(i->put_count == h->debug.put_count); + /* ... or remove entries other than the current one */ + assert(i->rem_count == h->debug.rem_count || + (i->rem_count == h->debug.rem_count - 1 && + i->prev_idx == h->debug.last_rem_idx)); + /* Reset our removals counter */ + i->rem_count = h->debug.rem_count; + } +#endif + + return h->type == HASHMAP_TYPE_ORDERED ? hashmap_iterate_in_insertion_order((OrderedHashmap*) h, i) + : hashmap_iterate_in_internal_order(h, i); +} + +bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key) { + struct hashmap_base_entry *e; + void *data; + unsigned idx; + + idx = hashmap_iterate_entry(h, i); + if (idx == IDX_NIL) { + if (value) + *value = NULL; + if (key) + *key = NULL; + + return false; + } + + e = bucket_at(h, idx); + data = entry_value(h, e); + if (value) + *value = data; + if (key) + *key = e->key; + + return true; +} + +bool set_iterate(Set *s, Iterator *i, void **value) { + return internal_hashmap_iterate(HASHMAP_BASE(s), i, value, NULL); +} + +#define HASHMAP_FOREACH_IDX(idx, h, i) \ + for ((i) = ITERATOR_FIRST, (idx) = hashmap_iterate_entry((h), &(i)); \ + (idx != IDX_NIL); \ + (idx) = hashmap_iterate_entry((h), &(i))) + +static void reset_direct_storage(HashmapBase *h) { + const struct hashmap_type_info *hi = &hashmap_type_info[h->type]; + void *p; + + assert(!h->has_indirect); + + p = mempset(h->direct.storage, 0, hi->entry_size * hi->n_direct_buckets); + memset(p, DIB_RAW_INIT, sizeof(dib_raw_t) * hi->n_direct_buckets); +} + +static struct HashmapBase *hashmap_base_new(const struct hash_ops *hash_ops, enum HashmapType type HASHMAP_DEBUG_PARAMS) { + HashmapBase *h; + const struct hashmap_type_info *hi = &hashmap_type_info[type]; + bool use_pool; + + use_pool = is_main_thread(); + + h = use_pool ? mempool_alloc0_tile(hi->mempool) : malloc0(hi->head_size); + + if (!h) + return NULL; + + h->type = type; + h->from_pool = use_pool; + h->hash_ops = hash_ops ? hash_ops : &trivial_hash_ops; + + if (type == HASHMAP_TYPE_ORDERED) { + OrderedHashmap *lh = (OrderedHashmap*)h; + lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL; + } + + reset_direct_storage(h); + + if (!shared_hash_key_initialized) { + random_bytes(shared_hash_key, sizeof(shared_hash_key)); + shared_hash_key_initialized= true; + } + +#ifdef ENABLE_DEBUG_HASHMAP + h->debug.func = func; + h->debug.file = file; + h->debug.line = line; + assert_se(pthread_mutex_lock(&hashmap_debug_list_mutex) == 0); + LIST_PREPEND(debug_list, hashmap_debug_list, &h->debug); + assert_se(pthread_mutex_unlock(&hashmap_debug_list_mutex) == 0); +#endif + + return h; +} + +Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { + return (Hashmap*) hashmap_base_new(hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS); +} + +OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { + return (OrderedHashmap*) hashmap_base_new(hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS); +} + +Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { + return (Set*) hashmap_base_new(hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS); +} + +static int hashmap_base_ensure_allocated(HashmapBase **h, const struct hash_ops *hash_ops, + enum HashmapType type HASHMAP_DEBUG_PARAMS) { + HashmapBase *q; + + assert(h); + + if (*h) + return 0; + + q = hashmap_base_new(hash_ops, type HASHMAP_DEBUG_PASS_ARGS); + if (!q) + return -ENOMEM; + + *h = q; + return 0; +} + +int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { + return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS); +} + +int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { + return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS); +} + +int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { + return hashmap_base_ensure_allocated((HashmapBase**)s, hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS); +} + +static void hashmap_free_no_clear(HashmapBase *h) { + assert(!h->has_indirect); + assert(!h->n_direct_entries); + +#ifdef ENABLE_DEBUG_HASHMAP + assert_se(pthread_mutex_lock(&hashmap_debug_list_mutex) == 0); + LIST_REMOVE(debug_list, hashmap_debug_list, &h->debug); + assert_se(pthread_mutex_unlock(&hashmap_debug_list_mutex) == 0); +#endif + + if (h->from_pool) + mempool_free_tile(hashmap_type_info[h->type].mempool, h); + else + free(h); +} + +HashmapBase *internal_hashmap_free(HashmapBase *h) { + + /* Free the hashmap, but nothing in it */ + + if (h) { + internal_hashmap_clear(h); + hashmap_free_no_clear(h); + } + + return NULL; +} + +HashmapBase *internal_hashmap_free_free(HashmapBase *h) { + + /* Free the hashmap and all data objects in it, but not the + * keys */ + + if (h) { + internal_hashmap_clear_free(h); + hashmap_free_no_clear(h); + } + + return NULL; +} + +Hashmap *hashmap_free_free_free(Hashmap *h) { + + /* Free the hashmap and all data and key objects in it */ + + if (h) { + hashmap_clear_free_free(h); + hashmap_free_no_clear(HASHMAP_BASE(h)); + } + + return NULL; +} + +void internal_hashmap_clear(HashmapBase *h) { + if (!h) + return; + + if (h->has_indirect) { + free(h->indirect.storage); + h->has_indirect = false; + } + + h->n_direct_entries = 0; + reset_direct_storage(h); + + if (h->type == HASHMAP_TYPE_ORDERED) { + OrderedHashmap *lh = (OrderedHashmap*) h; + lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL; + } +} + +void internal_hashmap_clear_free(HashmapBase *h) { + unsigned idx; + + if (!h) + return; + + for (idx = skip_free_buckets(h, 0); idx != IDX_NIL; + idx = skip_free_buckets(h, idx + 1)) + free(entry_value(h, bucket_at(h, idx))); + + internal_hashmap_clear(h); +} + +void hashmap_clear_free_free(Hashmap *h) { + unsigned idx; + + if (!h) + return; + + for (idx = skip_free_buckets(HASHMAP_BASE(h), 0); idx != IDX_NIL; + idx = skip_free_buckets(HASHMAP_BASE(h), idx + 1)) { + struct plain_hashmap_entry *e = plain_bucket_at(h, idx); + free((void*)e->b.key); + free(e->value); + } + + internal_hashmap_clear(HASHMAP_BASE(h)); +} + +static int resize_buckets(HashmapBase *h, unsigned entries_add); + +/* + * Finds an empty bucket to put an entry into, starting the scan at 'idx'. + * Performs Robin Hood swaps as it goes. The entry to put must be placed + * by the caller into swap slot IDX_PUT. + * If used for in-place resizing, may leave a displaced entry in swap slot + * IDX_PUT. Caller must rehash it next. + * Returns: true if it left a displaced entry to rehash next in IDX_PUT, + * false otherwise. + */ +static bool hashmap_put_robin_hood(HashmapBase *h, unsigned idx, + struct swap_entries *swap) { + dib_raw_t raw_dib, *dibs; + unsigned dib, distance; + +#ifdef ENABLE_DEBUG_HASHMAP + h->debug.put_count++; +#endif + + dibs = dib_raw_ptr(h); + + for (distance = 0; ; distance++) { + raw_dib = dibs[idx]; + if (raw_dib == DIB_RAW_FREE || raw_dib == DIB_RAW_REHASH) { + if (raw_dib == DIB_RAW_REHASH) + bucket_move_entry(h, swap, idx, IDX_TMP); + + if (h->has_indirect && h->indirect.idx_lowest_entry > idx) + h->indirect.idx_lowest_entry = idx; + + bucket_set_dib(h, idx, distance); + bucket_move_entry(h, swap, IDX_PUT, idx); + if (raw_dib == DIB_RAW_REHASH) { + bucket_move_entry(h, swap, IDX_TMP, IDX_PUT); + return true; + } + + return false; + } + + dib = bucket_calculate_dib(h, idx, raw_dib); + + if (dib < distance) { + /* Found a wealthier entry. Go Robin Hood! */ + bucket_set_dib(h, idx, distance); + + /* swap the entries */ + bucket_move_entry(h, swap, idx, IDX_TMP); + bucket_move_entry(h, swap, IDX_PUT, idx); + bucket_move_entry(h, swap, IDX_TMP, IDX_PUT); + + distance = dib; + } + + idx = next_idx(h, idx); + } +} + +/* + * Puts an entry into a hashmap, boldly - no check whether key already exists. + * The caller must place the entry (only its key and value, not link indexes) + * in swap slot IDX_PUT. + * Caller must ensure: the key does not exist yet in the hashmap. + * that resize is not needed if !may_resize. + * Returns: 1 if entry was put successfully. + * -ENOMEM if may_resize==true and resize failed with -ENOMEM. + * Cannot return -ENOMEM if !may_resize. + */ +static int hashmap_base_put_boldly(HashmapBase *h, unsigned idx, + struct swap_entries *swap, bool may_resize) { + struct ordered_hashmap_entry *new_entry; + int r; + + assert(idx < n_buckets(h)); + + new_entry = bucket_at_swap(swap, IDX_PUT); + + if (may_resize) { + r = resize_buckets(h, 1); + if (r < 0) + return r; + if (r > 0) + idx = bucket_hash(h, new_entry->p.b.key); + } + assert(n_entries(h) < n_buckets(h)); + + if (h->type == HASHMAP_TYPE_ORDERED) { + OrderedHashmap *lh = (OrderedHashmap*) h; + + new_entry->iterate_next = IDX_NIL; + new_entry->iterate_previous = lh->iterate_list_tail; + + if (lh->iterate_list_tail != IDX_NIL) { + struct ordered_hashmap_entry *old_tail; + + old_tail = ordered_bucket_at(lh, lh->iterate_list_tail); + assert(old_tail->iterate_next == IDX_NIL); + old_tail->iterate_next = IDX_PUT; + } + + lh->iterate_list_tail = IDX_PUT; + if (lh->iterate_list_head == IDX_NIL) + lh->iterate_list_head = IDX_PUT; + } + + assert_se(hashmap_put_robin_hood(h, idx, swap) == false); + + n_entries_inc(h); +#ifdef ENABLE_DEBUG_HASHMAP + h->debug.max_entries = MAX(h->debug.max_entries, n_entries(h)); +#endif + + return 1; +} +#define hashmap_put_boldly(h, idx, swap, may_resize) \ + hashmap_base_put_boldly(HASHMAP_BASE(h), idx, swap, may_resize) + +/* + * Returns 0 if resize is not needed. + * 1 if successfully resized. + * -ENOMEM on allocation failure. + */ +static int resize_buckets(HashmapBase *h, unsigned entries_add) { + struct swap_entries swap; + void *new_storage; + dib_raw_t *old_dibs, *new_dibs; + const struct hashmap_type_info *hi; + unsigned idx, optimal_idx; + unsigned old_n_buckets, new_n_buckets, n_rehashed, new_n_entries; + uint8_t new_shift; + bool rehash_next; + + assert(h); + + hi = &hashmap_type_info[h->type]; + new_n_entries = n_entries(h) + entries_add; + + /* overflow? */ + if (_unlikely_(new_n_entries < entries_add)) + return -ENOMEM; + + /* For direct storage we allow 100% load, because it's tiny. */ + if (!h->has_indirect && new_n_entries <= hi->n_direct_buckets) + return 0; + + /* + * Load factor = n/m = 1 - (1/INV_KEEP_FREE). + * From it follows: m = n + n/(INV_KEEP_FREE - 1) + */ + new_n_buckets = new_n_entries + new_n_entries / (INV_KEEP_FREE - 1); + /* overflow? */ + if (_unlikely_(new_n_buckets < new_n_entries)) + return -ENOMEM; + + if (_unlikely_(new_n_buckets > UINT_MAX / (hi->entry_size + sizeof(dib_raw_t)))) + return -ENOMEM; + + old_n_buckets = n_buckets(h); + + if (_likely_(new_n_buckets <= old_n_buckets)) + return 0; + + new_shift = log2u_round_up(MAX( + new_n_buckets * (hi->entry_size + sizeof(dib_raw_t)), + 2 * sizeof(struct direct_storage))); + + /* Realloc storage (buckets and DIB array). */ + new_storage = realloc(h->has_indirect ? h->indirect.storage : NULL, + 1U << new_shift); + if (!new_storage) + return -ENOMEM; + + /* Must upgrade direct to indirect storage. */ + if (!h->has_indirect) { + memcpy(new_storage, h->direct.storage, + old_n_buckets * (hi->entry_size + sizeof(dib_raw_t))); + h->indirect.n_entries = h->n_direct_entries; + h->indirect.idx_lowest_entry = 0; + h->n_direct_entries = 0; + } + + /* Get a new hash key. If we've just upgraded to indirect storage, + * allow reusing a previously generated key. It's still a different key + * from the shared one that we used for direct storage. */ + get_hash_key(h->indirect.hash_key, !h->has_indirect); + + h->has_indirect = true; + h->indirect.storage = new_storage; + h->indirect.n_buckets = (1U << new_shift) / + (hi->entry_size + sizeof(dib_raw_t)); + + old_dibs = (dib_raw_t*)((uint8_t*) new_storage + hi->entry_size * old_n_buckets); + new_dibs = dib_raw_ptr(h); + + /* + * Move the DIB array to the new place, replacing valid DIB values with + * DIB_RAW_REHASH to indicate all of the used buckets need rehashing. + * Note: Overlap is not possible, because we have at least doubled the + * number of buckets and dib_raw_t is smaller than any entry type. + */ + for (idx = 0; idx < old_n_buckets; idx++) { + assert(old_dibs[idx] != DIB_RAW_REHASH); + new_dibs[idx] = old_dibs[idx] == DIB_RAW_FREE ? DIB_RAW_FREE + : DIB_RAW_REHASH; + } + + /* Zero the area of newly added entries (including the old DIB area) */ + memzero(bucket_at(h, old_n_buckets), + (n_buckets(h) - old_n_buckets) * hi->entry_size); + + /* The upper half of the new DIB array needs initialization */ + memset(&new_dibs[old_n_buckets], DIB_RAW_INIT, + (n_buckets(h) - old_n_buckets) * sizeof(dib_raw_t)); + + /* Rehash entries that need it */ + n_rehashed = 0; + for (idx = 0; idx < old_n_buckets; idx++) { + if (new_dibs[idx] != DIB_RAW_REHASH) + continue; + + optimal_idx = bucket_hash(h, bucket_at(h, idx)->key); + + /* + * Not much to do if by luck the entry hashes to its current + * location. Just set its DIB. + */ + if (optimal_idx == idx) { + new_dibs[idx] = 0; + n_rehashed++; + continue; + } + + new_dibs[idx] = DIB_RAW_FREE; + bucket_move_entry(h, &swap, idx, IDX_PUT); + /* bucket_move_entry does not clear the source */ + memzero(bucket_at(h, idx), hi->entry_size); + + do { + /* + * Find the new bucket for the current entry. This may make + * another entry homeless and load it into IDX_PUT. + */ + rehash_next = hashmap_put_robin_hood(h, optimal_idx, &swap); + n_rehashed++; + + /* Did the current entry displace another one? */ + if (rehash_next) + optimal_idx = bucket_hash(h, bucket_at_swap(&swap, IDX_PUT)->p.b.key); + } while (rehash_next); + } + + assert(n_rehashed == n_entries(h)); + + return 1; +} + +/* + * Finds an entry with a matching key + * Returns: index of the found entry, or IDX_NIL if not found. + */ +static unsigned base_bucket_scan(HashmapBase *h, unsigned idx, const void *key) { + struct hashmap_base_entry *e; + unsigned dib, distance; + dib_raw_t *dibs = dib_raw_ptr(h); + + assert(idx < n_buckets(h)); + + for (distance = 0; ; distance++) { + if (dibs[idx] == DIB_RAW_FREE) + return IDX_NIL; + + dib = bucket_calculate_dib(h, idx, dibs[idx]); + + if (dib < distance) + return IDX_NIL; + if (dib == distance) { + e = bucket_at(h, idx); + if (h->hash_ops->compare(e->key, key) == 0) + return idx; + } + + idx = next_idx(h, idx); + } +} +#define bucket_scan(h, idx, key) base_bucket_scan(HASHMAP_BASE(h), idx, key) + +int hashmap_put(Hashmap *h, const void *key, void *value) { + struct swap_entries swap; + struct plain_hashmap_entry *e; + unsigned hash, idx; + + assert(h); + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx != IDX_NIL) { + e = plain_bucket_at(h, idx); + if (e->value == value) + return 0; + return -EEXIST; + } + + e = &bucket_at_swap(&swap, IDX_PUT)->p; + e->b.key = key; + e->value = value; + return hashmap_put_boldly(h, hash, &swap, true); +} + +int set_put(Set *s, const void *key) { + struct swap_entries swap; + struct hashmap_base_entry *e; + unsigned hash, idx; + + assert(s); + + hash = bucket_hash(s, key); + idx = bucket_scan(s, hash, key); + if (idx != IDX_NIL) + return 0; + + e = &bucket_at_swap(&swap, IDX_PUT)->p.b; + e->key = key; + return hashmap_put_boldly(s, hash, &swap, true); +} + +int hashmap_replace(Hashmap *h, const void *key, void *value) { + struct swap_entries swap; + struct plain_hashmap_entry *e; + unsigned hash, idx; + + assert(h); + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx != IDX_NIL) { + e = plain_bucket_at(h, idx); +#ifdef ENABLE_DEBUG_HASHMAP + /* Although the key is equal, the key pointer may have changed, + * and this would break our assumption for iterating. So count + * this operation as incompatible with iteration. */ + if (e->b.key != key) { + h->b.debug.put_count++; + h->b.debug.rem_count++; + h->b.debug.last_rem_idx = idx; + } +#endif + e->b.key = key; + e->value = value; + return 0; + } + + e = &bucket_at_swap(&swap, IDX_PUT)->p; + e->b.key = key; + e->value = value; + return hashmap_put_boldly(h, hash, &swap, true); +} + +int hashmap_update(Hashmap *h, const void *key, void *value) { + struct plain_hashmap_entry *e; + unsigned hash, idx; + + assert(h); + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) + return -ENOENT; + + e = plain_bucket_at(h, idx); + e->value = value; + return 0; +} + +void *internal_hashmap_get(HashmapBase *h, const void *key) { + struct hashmap_base_entry *e; + unsigned hash, idx; + + if (!h) + return NULL; + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) + return NULL; + + e = bucket_at(h, idx); + return entry_value(h, e); +} + +void *hashmap_get2(Hashmap *h, const void *key, void **key2) { + struct plain_hashmap_entry *e; + unsigned hash, idx; + + if (!h) + return NULL; + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) + return NULL; + + e = plain_bucket_at(h, idx); + if (key2) + *key2 = (void*) e->b.key; + + return e->value; +} + +bool internal_hashmap_contains(HashmapBase *h, const void *key) { + unsigned hash; + + if (!h) + return false; + + hash = bucket_hash(h, key); + return bucket_scan(h, hash, key) != IDX_NIL; +} + +void *internal_hashmap_remove(HashmapBase *h, const void *key) { + struct hashmap_base_entry *e; + unsigned hash, idx; + void *data; + + if (!h) + return NULL; + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) + return NULL; + + e = bucket_at(h, idx); + data = entry_value(h, e); + remove_entry(h, idx); + + return data; +} + +void *hashmap_remove2(Hashmap *h, const void *key, void **rkey) { + struct plain_hashmap_entry *e; + unsigned hash, idx; + void *data; + + if (!h) { + if (rkey) + *rkey = NULL; + return NULL; + } + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) { + if (rkey) + *rkey = NULL; + return NULL; + } + + e = plain_bucket_at(h, idx); + data = e->value; + if (rkey) + *rkey = (void*) e->b.key; + + remove_entry(h, idx); + + return data; +} + +int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value) { + struct swap_entries swap; + struct plain_hashmap_entry *e; + unsigned old_hash, new_hash, idx; + + if (!h) + return -ENOENT; + + old_hash = bucket_hash(h, old_key); + idx = bucket_scan(h, old_hash, old_key); + if (idx == IDX_NIL) + return -ENOENT; + + new_hash = bucket_hash(h, new_key); + if (bucket_scan(h, new_hash, new_key) != IDX_NIL) + return -EEXIST; + + remove_entry(h, idx); + + e = &bucket_at_swap(&swap, IDX_PUT)->p; + e->b.key = new_key; + e->value = value; + assert_se(hashmap_put_boldly(h, new_hash, &swap, false) == 1); + + return 0; +} + +int set_remove_and_put(Set *s, const void *old_key, const void *new_key) { + struct swap_entries swap; + struct hashmap_base_entry *e; + unsigned old_hash, new_hash, idx; + + if (!s) + return -ENOENT; + + old_hash = bucket_hash(s, old_key); + idx = bucket_scan(s, old_hash, old_key); + if (idx == IDX_NIL) + return -ENOENT; + + new_hash = bucket_hash(s, new_key); + if (bucket_scan(s, new_hash, new_key) != IDX_NIL) + return -EEXIST; + + remove_entry(s, idx); + + e = &bucket_at_swap(&swap, IDX_PUT)->p.b; + e->key = new_key; + assert_se(hashmap_put_boldly(s, new_hash, &swap, false) == 1); + + return 0; +} + +int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value) { + struct swap_entries swap; + struct plain_hashmap_entry *e; + unsigned old_hash, new_hash, idx_old, idx_new; + + if (!h) + return -ENOENT; + + old_hash = bucket_hash(h, old_key); + idx_old = bucket_scan(h, old_hash, old_key); + if (idx_old == IDX_NIL) + return -ENOENT; + + old_key = bucket_at(HASHMAP_BASE(h), idx_old)->key; + + new_hash = bucket_hash(h, new_key); + idx_new = bucket_scan(h, new_hash, new_key); + if (idx_new != IDX_NIL) + if (idx_old != idx_new) { + remove_entry(h, idx_new); + /* Compensate for a possible backward shift. */ + if (old_key != bucket_at(HASHMAP_BASE(h), idx_old)->key) + idx_old = prev_idx(HASHMAP_BASE(h), idx_old); + assert(old_key == bucket_at(HASHMAP_BASE(h), idx_old)->key); + } + + remove_entry(h, idx_old); + + e = &bucket_at_swap(&swap, IDX_PUT)->p; + e->b.key = new_key; + e->value = value; + assert_se(hashmap_put_boldly(h, new_hash, &swap, false) == 1); + + return 0; +} + +void *hashmap_remove_value(Hashmap *h, const void *key, void *value) { + struct plain_hashmap_entry *e; + unsigned hash, idx; + + if (!h) + return NULL; + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) + return NULL; + + e = plain_bucket_at(h, idx); + if (e->value != value) + return NULL; + + remove_entry(h, idx); + + return value; +} + +static unsigned find_first_entry(HashmapBase *h) { + Iterator i = ITERATOR_FIRST; + + if (!h || !n_entries(h)) + return IDX_NIL; + + return hashmap_iterate_entry(h, &i); +} + +void *internal_hashmap_first(HashmapBase *h) { + unsigned idx; + + idx = find_first_entry(h); + if (idx == IDX_NIL) + return NULL; + + return entry_value(h, bucket_at(h, idx)); +} + +void *internal_hashmap_first_key(HashmapBase *h) { + struct hashmap_base_entry *e; + unsigned idx; + + idx = find_first_entry(h); + if (idx == IDX_NIL) + return NULL; + + e = bucket_at(h, idx); + return (void*) e->key; +} + +void *internal_hashmap_steal_first(HashmapBase *h) { + struct hashmap_base_entry *e; + void *data; + unsigned idx; + + idx = find_first_entry(h); + if (idx == IDX_NIL) + return NULL; + + e = bucket_at(h, idx); + data = entry_value(h, e); + remove_entry(h, idx); + + return data; +} + +void *internal_hashmap_steal_first_key(HashmapBase *h) { + struct hashmap_base_entry *e; + void *key; + unsigned idx; + + idx = find_first_entry(h); + if (idx == IDX_NIL) + return NULL; + + e = bucket_at(h, idx); + key = (void*) e->key; + remove_entry(h, idx); + + return key; +} + +unsigned internal_hashmap_size(HashmapBase *h) { + + if (!h) + return 0; + + return n_entries(h); +} + +unsigned internal_hashmap_buckets(HashmapBase *h) { + + if (!h) + return 0; + + return n_buckets(h); +} + +int internal_hashmap_merge(Hashmap *h, Hashmap *other) { + Iterator i; + unsigned idx; + + assert(h); + + HASHMAP_FOREACH_IDX(idx, HASHMAP_BASE(other), i) { + struct plain_hashmap_entry *pe = plain_bucket_at(other, idx); + int r; + + r = hashmap_put(h, pe->b.key, pe->value); + if (r < 0 && r != -EEXIST) + return r; + } + + return 0; +} + +int set_merge(Set *s, Set *other) { + Iterator i; + unsigned idx; + + assert(s); + + HASHMAP_FOREACH_IDX(idx, HASHMAP_BASE(other), i) { + struct set_entry *se = set_bucket_at(other, idx); + int r; + + r = set_put(s, se->b.key); + if (r < 0) + return r; + } + + return 0; +} + +int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add) { + int r; + + assert(h); + + r = resize_buckets(h, entries_add); + if (r < 0) + return r; + + return 0; +} + +/* + * The same as hashmap_merge(), but every new item from other is moved to h. + * Keys already in h are skipped and stay in other. + * Returns: 0 on success. + * -ENOMEM on alloc failure, in which case no move has been done. + */ +int internal_hashmap_move(HashmapBase *h, HashmapBase *other) { + struct swap_entries swap; + struct hashmap_base_entry *e, *n; + Iterator i; + unsigned idx; + int r; + + assert(h); + + if (!other) + return 0; + + assert(other->type == h->type); + + /* + * This reserves buckets for the worst case, where none of other's + * entries are yet present in h. This is preferable to risking + * an allocation failure in the middle of the moving and having to + * rollback or return a partial result. + */ + r = resize_buckets(h, n_entries(other)); + if (r < 0) + return r; + + HASHMAP_FOREACH_IDX(idx, other, i) { + unsigned h_hash; + + e = bucket_at(other, idx); + h_hash = bucket_hash(h, e->key); + if (bucket_scan(h, h_hash, e->key) != IDX_NIL) + continue; + + n = &bucket_at_swap(&swap, IDX_PUT)->p.b; + n->key = e->key; + if (h->type != HASHMAP_TYPE_SET) + ((struct plain_hashmap_entry*) n)->value = + ((struct plain_hashmap_entry*) e)->value; + assert_se(hashmap_put_boldly(h, h_hash, &swap, false) == 1); + + remove_entry(other, idx); + } + + return 0; +} + +int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key) { + struct swap_entries swap; + unsigned h_hash, other_hash, idx; + struct hashmap_base_entry *e, *n; + int r; + + assert(h); + + h_hash = bucket_hash(h, key); + if (bucket_scan(h, h_hash, key) != IDX_NIL) + return -EEXIST; + + if (!other) + return -ENOENT; + + assert(other->type == h->type); + + other_hash = bucket_hash(other, key); + idx = bucket_scan(other, other_hash, key); + if (idx == IDX_NIL) + return -ENOENT; + + e = bucket_at(other, idx); + + n = &bucket_at_swap(&swap, IDX_PUT)->p.b; + n->key = e->key; + if (h->type != HASHMAP_TYPE_SET) + ((struct plain_hashmap_entry*) n)->value = + ((struct plain_hashmap_entry*) e)->value; + r = hashmap_put_boldly(h, h_hash, &swap, true); + if (r < 0) + return r; + + remove_entry(other, idx); + return 0; +} + +HashmapBase *internal_hashmap_copy(HashmapBase *h) { + HashmapBase *copy; + int r; + + assert(h); + + copy = hashmap_base_new(h->hash_ops, h->type HASHMAP_DEBUG_SRC_ARGS); + if (!copy) + return NULL; + + switch (h->type) { + case HASHMAP_TYPE_PLAIN: + case HASHMAP_TYPE_ORDERED: + r = hashmap_merge((Hashmap*)copy, (Hashmap*)h); + break; + case HASHMAP_TYPE_SET: + r = set_merge((Set*)copy, (Set*)h); + break; + default: + assert_not_reached("Unknown hashmap type"); + } + + if (r < 0) { + internal_hashmap_free(copy); + return NULL; + } + + return copy; +} + +char **internal_hashmap_get_strv(HashmapBase *h) { + char **sv; + Iterator i; + unsigned idx, n; + + sv = new(char*, n_entries(h)+1); + if (!sv) + return NULL; + + n = 0; + HASHMAP_FOREACH_IDX(idx, h, i) + sv[n++] = entry_value(h, bucket_at(h, idx)); + sv[n] = NULL; + + return sv; +} + +void *ordered_hashmap_next(OrderedHashmap *h, const void *key) { + struct ordered_hashmap_entry *e; + unsigned hash, idx; + + if (!h) + return NULL; + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) + return NULL; + + e = ordered_bucket_at(h, idx); + if (e->iterate_next == IDX_NIL) + return NULL; + return ordered_bucket_at(h, e->iterate_next)->p.value; +} + +int set_consume(Set *s, void *value) { + int r; + + r = set_put(s, value); + if (r <= 0) + free(value); + + return r; +} + +int set_put_strdup(Set *s, const char *p) { + char *c; + + assert(s); + assert(p); + + if (set_contains(s, (char*) p)) + return 0; + + c = strdup(p); + if (!c) + return -ENOMEM; + + return set_consume(s, c); +} + +int set_put_strdupv(Set *s, char **l) { + int n = 0, r; + char **i; + + STRV_FOREACH(i, l) { + r = set_put_strdup(s, *i); + if (r < 0) + return r; + + n += r; + } + + return n; +} diff --git a/src/libbasic/hashmap.h b/src/libbasic/hashmap.h new file mode 100644 index 0000000000..6d1ae48b21 --- /dev/null +++ b/src/libbasic/hashmap.h @@ -0,0 +1,372 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2014 Michal Schmidt + + 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 +#include + +#include "hash-funcs.h" +#include "macro.h" +#include "util.h" + +/* + * A hash table implementation. As a minor optimization a NULL hashmap object + * will be treated as empty hashmap for all read operations. That way it is not + * necessary to instantiate an object for each Hashmap use. + * + * If ENABLE_DEBUG_HASHMAP is defined (by configuring with --enable-debug=hashmap), + * the implemention will: + * - store extra data for debugging and statistics (see tools/gdb-sd_dump_hashmaps.py) + * - perform extra checks for invalid use of iterators + */ + +#define HASH_KEY_SIZE 16 + +/* The base type for all hashmap and set types. Many functions in the + * implementation take (HashmapBase*) parameters and are run-time polymorphic, + * though the API is not meant to be polymorphic (do not call functions + * internal_*() directly). */ +typedef struct HashmapBase HashmapBase; + +/* Specific hashmap/set types */ +typedef struct Hashmap Hashmap; /* Maps keys to values */ +typedef struct OrderedHashmap OrderedHashmap; /* Like Hashmap, but also remembers entry insertion order */ +typedef struct Set Set; /* Stores just keys */ + +/* Ideally the Iterator would be an opaque struct, but it is instantiated + * by hashmap users, so the definition has to be here. Do not use its fields + * directly. */ +typedef struct { + unsigned idx; /* index of an entry to be iterated next */ + const void *next_key; /* expected value of that entry's key pointer */ +#ifdef ENABLE_DEBUG_HASHMAP + unsigned put_count; /* hashmap's put_count recorded at start of iteration */ + unsigned rem_count; /* hashmap's rem_count in previous iteration */ + unsigned prev_idx; /* idx in previous iteration */ +#endif +} Iterator; + +#define _IDX_ITERATOR_FIRST (UINT_MAX - 1) +#define ITERATOR_FIRST ((Iterator) { .idx = _IDX_ITERATOR_FIRST, .next_key = NULL }) + +/* Macros for type checking */ +#define PTR_COMPATIBLE_WITH_HASHMAP_BASE(h) \ + (__builtin_types_compatible_p(typeof(h), HashmapBase*) || \ + __builtin_types_compatible_p(typeof(h), Hashmap*) || \ + __builtin_types_compatible_p(typeof(h), OrderedHashmap*) || \ + __builtin_types_compatible_p(typeof(h), Set*)) + +#define PTR_COMPATIBLE_WITH_PLAIN_HASHMAP(h) \ + (__builtin_types_compatible_p(typeof(h), Hashmap*) || \ + __builtin_types_compatible_p(typeof(h), OrderedHashmap*)) \ + +#define HASHMAP_BASE(h) \ + __builtin_choose_expr(PTR_COMPATIBLE_WITH_HASHMAP_BASE(h), \ + (HashmapBase*)(h), \ + (void)0) + +#define PLAIN_HASHMAP(h) \ + __builtin_choose_expr(PTR_COMPATIBLE_WITH_PLAIN_HASHMAP(h), \ + (Hashmap*)(h), \ + (void)0) + +#ifdef ENABLE_DEBUG_HASHMAP +# define HASHMAP_DEBUG_PARAMS , const char *func, const char *file, int line +# define HASHMAP_DEBUG_SRC_ARGS , __func__, __FILE__, __LINE__ +# define HASHMAP_DEBUG_PASS_ARGS , func, file, line +#else +# define HASHMAP_DEBUG_PARAMS +# define HASHMAP_DEBUG_SRC_ARGS +# define HASHMAP_DEBUG_PASS_ARGS +#endif + +Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +#define hashmap_new(ops) internal_hashmap_new(ops HASHMAP_DEBUG_SRC_ARGS) +#define ordered_hashmap_new(ops) internal_ordered_hashmap_new(ops HASHMAP_DEBUG_SRC_ARGS) + +HashmapBase *internal_hashmap_free(HashmapBase *h); +static inline Hashmap *hashmap_free(Hashmap *h) { + return (void*)internal_hashmap_free(HASHMAP_BASE(h)); +} +static inline OrderedHashmap *ordered_hashmap_free(OrderedHashmap *h) { + return (void*)internal_hashmap_free(HASHMAP_BASE(h)); +} + +HashmapBase *internal_hashmap_free_free(HashmapBase *h); +static inline Hashmap *hashmap_free_free(Hashmap *h) { + return (void*)internal_hashmap_free_free(HASHMAP_BASE(h)); +} +static inline OrderedHashmap *ordered_hashmap_free_free(OrderedHashmap *h) { + return (void*)internal_hashmap_free_free(HASHMAP_BASE(h)); +} + +Hashmap *hashmap_free_free_free(Hashmap *h); +static inline OrderedHashmap *ordered_hashmap_free_free_free(OrderedHashmap *h) { + return (void*)hashmap_free_free_free(PLAIN_HASHMAP(h)); +} + +HashmapBase *internal_hashmap_copy(HashmapBase *h); +static inline Hashmap *hashmap_copy(Hashmap *h) { + return (Hashmap*) internal_hashmap_copy(HASHMAP_BASE(h)); +} +static inline OrderedHashmap *ordered_hashmap_copy(OrderedHashmap *h) { + return (OrderedHashmap*) internal_hashmap_copy(HASHMAP_BASE(h)); +} + +int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +#define hashmap_ensure_allocated(h, ops) internal_hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) +#define ordered_hashmap_ensure_allocated(h, ops) internal_ordered_hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) + +int hashmap_put(Hashmap *h, const void *key, void *value); +static inline int ordered_hashmap_put(OrderedHashmap *h, const void *key, void *value) { + return hashmap_put(PLAIN_HASHMAP(h), key, value); +} + +int hashmap_update(Hashmap *h, const void *key, void *value); +static inline int ordered_hashmap_update(OrderedHashmap *h, const void *key, void *value) { + return hashmap_update(PLAIN_HASHMAP(h), key, value); +} + +int hashmap_replace(Hashmap *h, const void *key, void *value); +static inline int ordered_hashmap_replace(OrderedHashmap *h, const void *key, void *value) { + return hashmap_replace(PLAIN_HASHMAP(h), key, value); +} + +void *internal_hashmap_get(HashmapBase *h, const void *key); +static inline void *hashmap_get(Hashmap *h, const void *key) { + return internal_hashmap_get(HASHMAP_BASE(h), key); +} +static inline void *ordered_hashmap_get(OrderedHashmap *h, const void *key) { + return internal_hashmap_get(HASHMAP_BASE(h), key); +} + +void *hashmap_get2(Hashmap *h, const void *key, void **rkey); +static inline void *ordered_hashmap_get2(OrderedHashmap *h, const void *key, void **rkey) { + return hashmap_get2(PLAIN_HASHMAP(h), key, rkey); +} + +bool internal_hashmap_contains(HashmapBase *h, const void *key); +static inline bool hashmap_contains(Hashmap *h, const void *key) { + return internal_hashmap_contains(HASHMAP_BASE(h), key); +} +static inline bool ordered_hashmap_contains(OrderedHashmap *h, const void *key) { + return internal_hashmap_contains(HASHMAP_BASE(h), key); +} + +void *internal_hashmap_remove(HashmapBase *h, const void *key); +static inline void *hashmap_remove(Hashmap *h, const void *key) { + return internal_hashmap_remove(HASHMAP_BASE(h), key); +} +static inline void *ordered_hashmap_remove(OrderedHashmap *h, const void *key) { + return internal_hashmap_remove(HASHMAP_BASE(h), key); +} + +void *hashmap_remove2(Hashmap *h, const void *key, void **rkey); +static inline void *ordered_hashmap_remove2(OrderedHashmap *h, const void *key, void **rkey) { + return hashmap_remove2(PLAIN_HASHMAP(h), key, rkey); +} + +void *hashmap_remove_value(Hashmap *h, const void *key, void *value); +static inline void *ordered_hashmap_remove_value(OrderedHashmap *h, const void *key, void *value) { + return hashmap_remove_value(PLAIN_HASHMAP(h), key, value); +} + +int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value); +static inline int ordered_hashmap_remove_and_put(OrderedHashmap *h, const void *old_key, const void *new_key, void *value) { + return hashmap_remove_and_put(PLAIN_HASHMAP(h), old_key, new_key, value); +} + +int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value); +static inline int ordered_hashmap_remove_and_replace(OrderedHashmap *h, const void *old_key, const void *new_key, void *value) { + return hashmap_remove_and_replace(PLAIN_HASHMAP(h), old_key, new_key, value); +} + +/* Since merging data from a OrderedHashmap into a Hashmap or vice-versa + * should just work, allow this by having looser type-checking here. */ +int internal_hashmap_merge(Hashmap *h, Hashmap *other); +#define hashmap_merge(h, other) internal_hashmap_merge(PLAIN_HASHMAP(h), PLAIN_HASHMAP(other)) +#define ordered_hashmap_merge(h, other) hashmap_merge(h, other) + +int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add); +static inline int hashmap_reserve(Hashmap *h, unsigned entries_add) { + return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add); +} +static inline int ordered_hashmap_reserve(OrderedHashmap *h, unsigned entries_add) { + return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add); +} + +int internal_hashmap_move(HashmapBase *h, HashmapBase *other); +/* Unlike hashmap_merge, hashmap_move does not allow mixing the types. */ +static inline int hashmap_move(Hashmap *h, Hashmap *other) { + return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other)); +} +static inline int ordered_hashmap_move(OrderedHashmap *h, OrderedHashmap *other) { + return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other)); +} + +int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key); +static inline int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key) { + return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key); +} +static inline int ordered_hashmap_move_one(OrderedHashmap *h, OrderedHashmap *other, const void *key) { + return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key); +} + +unsigned internal_hashmap_size(HashmapBase *h) _pure_; +static inline unsigned hashmap_size(Hashmap *h) { + return internal_hashmap_size(HASHMAP_BASE(h)); +} +static inline unsigned ordered_hashmap_size(OrderedHashmap *h) { + return internal_hashmap_size(HASHMAP_BASE(h)); +} + +static inline bool hashmap_isempty(Hashmap *h) { + return hashmap_size(h) == 0; +} +static inline bool ordered_hashmap_isempty(OrderedHashmap *h) { + return ordered_hashmap_size(h) == 0; +} + +unsigned internal_hashmap_buckets(HashmapBase *h) _pure_; +static inline unsigned hashmap_buckets(Hashmap *h) { + return internal_hashmap_buckets(HASHMAP_BASE(h)); +} +static inline unsigned ordered_hashmap_buckets(OrderedHashmap *h) { + return internal_hashmap_buckets(HASHMAP_BASE(h)); +} + +bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key); +static inline bool hashmap_iterate(Hashmap *h, Iterator *i, void **value, const void **key) { + return internal_hashmap_iterate(HASHMAP_BASE(h), i, value, key); +} +static inline bool ordered_hashmap_iterate(OrderedHashmap *h, Iterator *i, void **value, const void **key) { + return internal_hashmap_iterate(HASHMAP_BASE(h), i, value, key); +} + +void internal_hashmap_clear(HashmapBase *h); +static inline void hashmap_clear(Hashmap *h) { + internal_hashmap_clear(HASHMAP_BASE(h)); +} +static inline void ordered_hashmap_clear(OrderedHashmap *h) { + internal_hashmap_clear(HASHMAP_BASE(h)); +} + +void internal_hashmap_clear_free(HashmapBase *h); +static inline void hashmap_clear_free(Hashmap *h) { + internal_hashmap_clear_free(HASHMAP_BASE(h)); +} +static inline void ordered_hashmap_clear_free(OrderedHashmap *h) { + internal_hashmap_clear_free(HASHMAP_BASE(h)); +} + +void hashmap_clear_free_free(Hashmap *h); +static inline void ordered_hashmap_clear_free_free(OrderedHashmap *h) { + hashmap_clear_free_free(PLAIN_HASHMAP(h)); +} + +/* + * Note about all *_first*() functions + * + * For plain Hashmaps and Sets the order of entries is undefined. + * The functions find whatever entry is first in the implementation + * internal order. + * + * Only for OrderedHashmaps the order is well defined and finding + * the first entry is O(1). + */ + +void *internal_hashmap_steal_first(HashmapBase *h); +static inline void *hashmap_steal_first(Hashmap *h) { + return internal_hashmap_steal_first(HASHMAP_BASE(h)); +} +static inline void *ordered_hashmap_steal_first(OrderedHashmap *h) { + return internal_hashmap_steal_first(HASHMAP_BASE(h)); +} + +void *internal_hashmap_steal_first_key(HashmapBase *h); +static inline void *hashmap_steal_first_key(Hashmap *h) { + return internal_hashmap_steal_first_key(HASHMAP_BASE(h)); +} +static inline void *ordered_hashmap_steal_first_key(OrderedHashmap *h) { + return internal_hashmap_steal_first_key(HASHMAP_BASE(h)); +} + +void *internal_hashmap_first_key(HashmapBase *h) _pure_; +static inline void *hashmap_first_key(Hashmap *h) { + return internal_hashmap_first_key(HASHMAP_BASE(h)); +} +static inline void *ordered_hashmap_first_key(OrderedHashmap *h) { + return internal_hashmap_first_key(HASHMAP_BASE(h)); +} + +void *internal_hashmap_first(HashmapBase *h) _pure_; +static inline void *hashmap_first(Hashmap *h) { + return internal_hashmap_first(HASHMAP_BASE(h)); +} +static inline void *ordered_hashmap_first(OrderedHashmap *h) { + return internal_hashmap_first(HASHMAP_BASE(h)); +} + +/* no hashmap_next */ +void *ordered_hashmap_next(OrderedHashmap *h, const void *key); + +char **internal_hashmap_get_strv(HashmapBase *h); +static inline char **hashmap_get_strv(Hashmap *h) { + return internal_hashmap_get_strv(HASHMAP_BASE(h)); +} +static inline char **ordered_hashmap_get_strv(OrderedHashmap *h) { + return internal_hashmap_get_strv(HASHMAP_BASE(h)); +} + +/* + * Hashmaps are iterated in unpredictable order. + * OrderedHashmaps are an exception to this. They are iterated in the order + * the entries were inserted. + * It is safe to remove the current entry. + */ +#define HASHMAP_FOREACH(e, h, i) \ + for ((i) = ITERATOR_FIRST; hashmap_iterate((h), &(i), (void**)&(e), NULL); ) + +#define ORDERED_HASHMAP_FOREACH(e, h, i) \ + for ((i) = ITERATOR_FIRST; ordered_hashmap_iterate((h), &(i), (void**)&(e), NULL); ) + +#define HASHMAP_FOREACH_KEY(e, k, h, i) \ + for ((i) = ITERATOR_FIRST; hashmap_iterate((h), &(i), (void**)&(e), (const void**) &(k)); ) + +#define ORDERED_HASHMAP_FOREACH_KEY(e, k, h, i) \ + for ((i) = ITERATOR_FIRST; ordered_hashmap_iterate((h), &(i), (void**)&(e), (const void**) &(k)); ) + +DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_free); + +#define _cleanup_hashmap_free_ _cleanup_(hashmap_freep) +#define _cleanup_hashmap_free_free_ _cleanup_(hashmap_free_freep) +#define _cleanup_hashmap_free_free_free_ _cleanup_(hashmap_free_free_freep) +#define _cleanup_ordered_hashmap_free_ _cleanup_(ordered_hashmap_freep) +#define _cleanup_ordered_hashmap_free_free_ _cleanup_(ordered_hashmap_free_freep) +#define _cleanup_ordered_hashmap_free_free_free_ _cleanup_(ordered_hashmap_free_free_freep) diff --git a/src/libbasic/hexdecoct.c b/src/libbasic/hexdecoct.c new file mode 100644 index 0000000000..c5bda6c4d6 --- /dev/null +++ b/src/libbasic/hexdecoct.c @@ -0,0 +1,754 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include + +#include "alloc-util.h" +#include "hexdecoct.h" +#include "macro.h" +#include "util.h" + +char octchar(int x) { + return '0' + (x & 7); +} + +int unoctchar(char c) { + + if (c >= '0' && c <= '7') + return c - '0'; + + return -EINVAL; +} + +char decchar(int x) { + return '0' + (x % 10); +} + +int undecchar(char c) { + + if (c >= '0' && c <= '9') + return c - '0'; + + return -EINVAL; +} + +char hexchar(int x) { + static const char table[16] = "0123456789abcdef"; + + return table[x & 15]; +} + +int unhexchar(char c) { + + if (c >= '0' && c <= '9') + return c - '0'; + + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + + return -EINVAL; +} + +char *hexmem(const void *p, size_t l) { + char *r, *z; + const uint8_t *x; + + z = r = malloc(l * 2 + 1); + if (!r) + return NULL; + + for (x = p; x < (const uint8_t*) p + l; x++) { + *(z++) = hexchar(*x >> 4); + *(z++) = hexchar(*x & 15); + } + + *z = 0; + return r; +} + +int unhexmem(const char *p, size_t l, void **mem, size_t *len) { + _cleanup_free_ uint8_t *r = NULL; + uint8_t *z; + const char *x; + + assert(mem); + assert(len); + assert(p); + + z = r = malloc((l + 1) / 2 + 1); + if (!r) + return -ENOMEM; + + for (x = p; x < p + l; x += 2) { + int a, b; + + a = unhexchar(x[0]); + if (a < 0) + return a; + else if (x+1 < p + l) { + b = unhexchar(x[1]); + if (b < 0) + return b; + } else + b = 0; + + *(z++) = (uint8_t) a << 4 | (uint8_t) b; + } + + *z = 0; + + *mem = r; + r = NULL; + *len = (l + 1) / 2; + + return 0; +} + +/* https://tools.ietf.org/html/rfc4648#section-6 + * Notice that base32hex differs from base32 in the alphabet it uses. + * The distinction is that the base32hex representation preserves the + * order of the underlying data when compared as bytestrings, this is + * useful when representing NSEC3 hashes, as one can then verify the + * order of hashes directly from their representation. */ +char base32hexchar(int x) { + static const char table[32] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUV"; + + return table[x & 31]; +} + +int unbase32hexchar(char c) { + unsigned offset; + + if (c >= '0' && c <= '9') + return c - '0'; + + offset = '9' - '0' + 1; + + if (c >= 'A' && c <= 'V') + return c - 'A' + offset; + + return -EINVAL; +} + +char *base32hexmem(const void *p, size_t l, bool padding) { + char *r, *z; + const uint8_t *x; + size_t len; + + if (padding) + /* five input bytes makes eight output bytes, padding is added so we must round up */ + len = 8 * (l + 4) / 5; + else { + /* same, but round down as there is no padding */ + len = 8 * l / 5; + + switch (l % 5) { + case 4: + len += 7; + break; + case 3: + len += 5; + break; + case 2: + len += 4; + break; + case 1: + len += 2; + break; + } + } + + z = r = malloc(len + 1); + if (!r) + return NULL; + + for (x = p; x < (const uint8_t*) p + (l / 5) * 5; x += 5) { + /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ + x[3] == QQQQQQQQ; x[4] == WWWWWWWW */ + *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ + *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ + *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ + *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */ + *(z++) = base32hexchar((x[2] & 15) << 1 | x[3] >> 7); /* 000ZZZZQ */ + *(z++) = base32hexchar((x[3] & 127) >> 2); /* 000QQQQQ */ + *(z++) = base32hexchar((x[3] & 3) << 3 | x[4] >> 5); /* 000QQWWW */ + *(z++) = base32hexchar((x[4] & 31)); /* 000WWWWW */ + } + + switch (l % 5) { + case 4: + *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ + *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ + *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ + *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */ + *(z++) = base32hexchar((x[2] & 15) << 1 | x[3] >> 7); /* 000ZZZZQ */ + *(z++) = base32hexchar((x[3] & 127) >> 2); /* 000QQQQQ */ + *(z++) = base32hexchar((x[3] & 3) << 3); /* 000QQ000 */ + if (padding) + *(z++) = '='; + + break; + + case 3: + *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ + *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ + *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ + *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */ + *(z++) = base32hexchar((x[2] & 15) << 1); /* 000ZZZZ0 */ + if (padding) { + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + } + + break; + + case 2: + *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ + *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ + *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ + *(z++) = base32hexchar((x[1] & 1) << 4); /* 000Y0000 */ + if (padding) { + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + } + + break; + + case 1: + *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ + *(z++) = base32hexchar((x[0] & 7) << 2); /* 000XXX00 */ + if (padding) { + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + } + + break; + } + + *z = 0; + return r; +} + +int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *_len) { + _cleanup_free_ uint8_t *r = NULL; + int a, b, c, d, e, f, g, h; + uint8_t *z; + const char *x; + size_t len; + unsigned pad = 0; + + assert(p); + + /* padding ensures any base32hex input has input divisible by 8 */ + if (padding && l % 8 != 0) + return -EINVAL; + + if (padding) { + /* strip the padding */ + while (l > 0 && p[l - 1] == '=' && pad < 7) { + pad++; + l--; + } + } + + /* a group of eight input bytes needs five output bytes, in case of + padding we need to add some extra bytes */ + len = (l / 8) * 5; + + switch (l % 8) { + case 7: + len += 4; + break; + case 5: + len += 3; + break; + case 4: + len += 2; + break; + case 2: + len += 1; + break; + case 0: + break; + default: + return -EINVAL; + } + + z = r = malloc(len + 1); + if (!r) + return -ENOMEM; + + for (x = p; x < p + (l / 8) * 8; x += 8) { + /* a == 000XXXXX; b == 000YYYYY; c == 000ZZZZZ; d == 000WWWWW + e == 000SSSSS; f == 000QQQQQ; g == 000VVVVV; h == 000RRRRR */ + a = unbase32hexchar(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase32hexchar(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase32hexchar(x[2]); + if (c < 0) + return -EINVAL; + + d = unbase32hexchar(x[3]); + if (d < 0) + return -EINVAL; + + e = unbase32hexchar(x[4]); + if (e < 0) + return -EINVAL; + + f = unbase32hexchar(x[5]); + if (f < 0) + return -EINVAL; + + g = unbase32hexchar(x[6]); + if (g < 0) + return -EINVAL; + + h = unbase32hexchar(x[7]); + if (h < 0) + return -EINVAL; + + *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ + *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ + *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1; /* WWWWSSSS */ + *(z++) = (uint8_t) e << 7 | (uint8_t) f << 2 | (uint8_t) g >> 3; /* SQQQQQVV */ + *(z++) = (uint8_t) g << 5 | (uint8_t) h; /* VVVRRRRR */ + } + + switch (l % 8) { + case 7: + a = unbase32hexchar(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase32hexchar(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase32hexchar(x[2]); + if (c < 0) + return -EINVAL; + + d = unbase32hexchar(x[3]); + if (d < 0) + return -EINVAL; + + e = unbase32hexchar(x[4]); + if (e < 0) + return -EINVAL; + + f = unbase32hexchar(x[5]); + if (f < 0) + return -EINVAL; + + g = unbase32hexchar(x[6]); + if (g < 0) + return -EINVAL; + + /* g == 000VV000 */ + if (g & 7) + return -EINVAL; + + *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ + *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ + *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1; /* WWWWSSSS */ + *(z++) = (uint8_t) e << 7 | (uint8_t) f << 2 | (uint8_t) g >> 3; /* SQQQQQVV */ + + break; + case 5: + a = unbase32hexchar(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase32hexchar(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase32hexchar(x[2]); + if (c < 0) + return -EINVAL; + + d = unbase32hexchar(x[3]); + if (d < 0) + return -EINVAL; + + e = unbase32hexchar(x[4]); + if (e < 0) + return -EINVAL; + + /* e == 000SSSS0 */ + if (e & 1) + return -EINVAL; + + *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ + *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ + *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1; /* WWWWSSSS */ + + break; + case 4: + a = unbase32hexchar(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase32hexchar(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase32hexchar(x[2]); + if (c < 0) + return -EINVAL; + + d = unbase32hexchar(x[3]); + if (d < 0) + return -EINVAL; + + /* d == 000W0000 */ + if (d & 15) + return -EINVAL; + + *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ + *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ + + break; + case 2: + a = unbase32hexchar(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase32hexchar(x[1]); + if (b < 0) + return -EINVAL; + + /* b == 000YYY00 */ + if (b & 3) + return -EINVAL; + + *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ + + break; + case 0: + break; + default: + return -EINVAL; + } + + *z = 0; + + *mem = r; + r = NULL; + *_len = len; + + return 0; +} + +/* https://tools.ietf.org/html/rfc4648#section-4 */ +char base64char(int x) { + static const char table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + return table[x & 63]; +} + +int unbase64char(char c) { + unsigned offset; + + if (c >= 'A' && c <= 'Z') + return c - 'A'; + + offset = 'Z' - 'A' + 1; + + if (c >= 'a' && c <= 'z') + return c - 'a' + offset; + + offset += 'z' - 'a' + 1; + + if (c >= '0' && c <= '9') + return c - '0' + offset; + + offset += '9' - '0' + 1; + + if (c == '+') + return offset; + + offset++; + + if (c == '/') + return offset; + + return -EINVAL; +} + +ssize_t base64mem(const void *p, size_t l, char **out) { + char *r, *z; + const uint8_t *x; + + /* three input bytes makes four output bytes, padding is added so we must round up */ + z = r = malloc(4 * (l + 2) / 3 + 1); + if (!r) + return -ENOMEM; + + for (x = p; x < (const uint8_t*) p + (l / 3) * 3; x += 3) { + /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ */ + *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ + *(z++) = base64char((x[1] & 15) << 2 | x[2] >> 6); /* 00YYYYZZ */ + *(z++) = base64char(x[2] & 63); /* 00ZZZZZZ */ + } + + switch (l % 3) { + case 2: + *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ + *(z++) = base64char((x[1] & 15) << 2); /* 00YYYY00 */ + *(z++) = '='; + + break; + case 1: + *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + *(z++) = base64char((x[0] & 3) << 4); /* 00XX0000 */ + *(z++) = '='; + *(z++) = '='; + + break; + } + + *z = 0; + *out = r; + return z - r; +} + +static int base64_append_width(char **prefix, int plen, + const char *sep, int indent, + const void *p, size_t l, + int width) { + + _cleanup_free_ char *x = NULL; + char *t, *s; + ssize_t slen, len, avail; + int line, lines; + + len = base64mem(p, l, &x); + if (len <= 0) + return len; + + lines = (len + width - 1) / width; + + slen = sep ? strlen(sep) : 0; + t = realloc(*prefix, plen + 1 + slen + (indent + width + 1) * lines); + if (!t) + return -ENOMEM; + + memcpy_safe(t + plen, sep, slen); + + for (line = 0, s = t + plen + slen, avail = len; line < lines; line++) { + int act = MIN(width, avail); + + if (line > 0 || sep) { + memset(s, ' ', indent); + s += indent; + } + + memcpy(s, x + width * line, act); + s += act; + *(s++) = line < lines - 1 ? '\n' : '\0'; + avail -= act; + } + assert(avail == 0); + + *prefix = t; + return 0; +} + +int base64_append(char **prefix, int plen, + const void *p, size_t l, + int indent, int width) { + if (plen > width / 2 || plen + indent > width) + /* leave indent on the left, keep last column free */ + return base64_append_width(prefix, plen, "\n", indent, p, l, width - indent - 1); + else + /* leave plen on the left, keep last column free */ + return base64_append_width(prefix, plen, NULL, plen, p, l, width - plen - 1); +}; + + +int unbase64mem(const char *p, size_t l, void **mem, size_t *_len) { + _cleanup_free_ uint8_t *r = NULL; + int a, b, c, d; + uint8_t *z; + const char *x; + size_t len; + + assert(p); + + /* padding ensures any base63 input has input divisible by 4 */ + if (l % 4 != 0) + return -EINVAL; + + /* strip the padding */ + if (l > 0 && p[l - 1] == '=') + l--; + if (l > 0 && p[l - 1] == '=') + l--; + + /* a group of four input bytes needs three output bytes, in case of + padding we need to add two or three extra bytes */ + len = (l / 4) * 3 + (l % 4 ? (l % 4) - 1 : 0); + + z = r = malloc(len + 1); + if (!r) + return -ENOMEM; + + for (x = p; x < p + (l / 4) * 4; x += 4) { + /* a == 00XXXXXX; b == 00YYYYYY; c == 00ZZZZZZ; d == 00WWWWWW */ + a = unbase64char(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase64char(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase64char(x[2]); + if (c < 0) + return -EINVAL; + + d = unbase64char(x[3]); + if (d < 0) + return -EINVAL; + + *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ + *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ + *(z++) = (uint8_t) c << 6 | (uint8_t) d; /* ZZWWWWWW */ + } + + switch (l % 4) { + case 3: + a = unbase64char(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase64char(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase64char(x[2]); + if (c < 0) + return -EINVAL; + + /* c == 00ZZZZ00 */ + if (c & 3) + return -EINVAL; + + *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ + *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ + + break; + case 2: + a = unbase64char(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase64char(x[1]); + if (b < 0) + return -EINVAL; + + /* b == 00YY0000 */ + if (b & 15) + return -EINVAL; + + *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */ + + break; + case 0: + + break; + default: + return -EINVAL; + } + + *z = 0; + + *mem = r; + r = NULL; + *_len = len; + + return 0; +} + +void hexdump(FILE *f, const void *p, size_t s) { + const uint8_t *b = p; + unsigned n = 0; + + assert(s == 0 || b); + + while (s > 0) { + size_t i; + + fprintf(f, "%04x ", n); + + for (i = 0; i < 16; i++) { + + if (i >= s) + fputs(" ", f); + else + fprintf(f, "%02x ", b[i]); + + if (i == 7) + fputc(' ', f); + } + + fputc(' ', f); + + for (i = 0; i < 16; i++) { + + if (i >= s) + fputc(' ', f); + else + fputc(isprint(b[i]) ? (char) b[i] : '.', f); + } + + fputc('\n', f); + + if (s < 16) + break; + + n += 16; + b += 16; + s -= 16; + } +} diff --git a/src/libbasic/hexdecoct.h b/src/libbasic/hexdecoct.h new file mode 100644 index 0000000000..1ba2f69ebd --- /dev/null +++ b/src/libbasic/hexdecoct.h @@ -0,0 +1,56 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include + +#include "macro.h" + +char octchar(int x) _const_; +int unoctchar(char c) _const_; + +char decchar(int x) _const_; +int undecchar(char c) _const_; + +char hexchar(int x) _const_; +int unhexchar(char c) _const_; + +char *hexmem(const void *p, size_t l); +int unhexmem(const char *p, size_t l, void **mem, size_t *len); + +char base32hexchar(int x) _const_; +int unbase32hexchar(char c) _const_; + +char base64char(int x) _const_; +int unbase64char(char c) _const_; + +char *base32hexmem(const void *p, size_t l, bool padding); +int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *len); + +ssize_t base64mem(const void *p, size_t l, char **out); +int base64_append(char **prefix, int plen, + const void *p, size_t l, + int margin, int width); +int unbase64mem(const char *p, size_t l, void **mem, size_t *len); + +void hexdump(FILE *f, const void *p, size_t s); diff --git a/src/libbasic/hostname-util.c b/src/libbasic/hostname-util.c new file mode 100644 index 0000000000..13c3bb6446 --- /dev/null +++ b/src/libbasic/hostname-util.c @@ -0,0 +1,252 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 +#include +#include +#include +#include + +#include "fd-util.h" +#include "fileio.h" +#include "hostname-util.h" +#include "macro.h" +#include "string-util.h" + +bool hostname_is_set(void) { + struct utsname u; + + assert_se(uname(&u) >= 0); + + if (isempty(u.nodename)) + return false; + + /* This is the built-in kernel default host name */ + if (streq(u.nodename, "(none)")) + return false; + + return true; +} + +char* gethostname_malloc(void) { + struct utsname u; + + /* This call tries to return something useful, either the actual hostname + * or it makes something up. The only reason it might fail is OOM. + * It might even return "localhost" if that's set. */ + + assert_se(uname(&u) >= 0); + + if (isempty(u.nodename) || streq(u.nodename, "(none)")) + return strdup(u.sysname); + + return strdup(u.nodename); +} + +int gethostname_strict(char **ret) { + struct utsname u; + char *k; + + /* This call will rather fail than make up a name. It will not return "localhost" either. */ + + assert_se(uname(&u) >= 0); + + if (isempty(u.nodename)) + return -ENXIO; + + if (streq(u.nodename, "(none)")) + return -ENXIO; + + if (is_localhost(u.nodename)) + return -ENXIO; + + k = strdup(u.nodename); + if (!k) + return -ENOMEM; + + *ret = k; + return 0; +} + +static bool hostname_valid_char(char c) { + return + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '-' || + c == '_' || + c == '.'; +} + +/** + * Check if s looks like a valid host name or FQDN. This does not do + * full DNS validation, but only checks if the name is composed of + * allowed characters and the length is not above the maximum allowed + * by Linux (c.f. dns_name_is_valid()). Trailing dot is allowed if + * allow_trailing_dot is true and at least two components are present + * in the name. Note that due to the restricted charset and length + * this call is substantially more conservative than + * dns_name_is_valid(). + */ +bool hostname_is_valid(const char *s, bool allow_trailing_dot) { + unsigned n_dots = 0; + const char *p; + bool dot; + + if (isempty(s)) + return false; + + /* Doesn't accept empty hostnames, hostnames with + * leading dots, and hostnames with multiple dots in a + * sequence. Also ensures that the length stays below + * HOST_NAME_MAX. */ + + for (p = s, dot = true; *p; p++) { + if (*p == '.') { + if (dot) + return false; + + dot = true; + n_dots++; + } else { + if (!hostname_valid_char(*p)) + return false; + + dot = false; + } + } + + if (dot && (n_dots < 2 || !allow_trailing_dot)) + return false; + + if (p-s > HOST_NAME_MAX) /* Note that HOST_NAME_MAX is 64 on + * Linux, but DNS allows domain names + * up to 255 characters */ + return false; + + return true; +} + +char* hostname_cleanup(char *s) { + char *p, *d; + bool dot; + + assert(s); + + strshorten(s, HOST_NAME_MAX); + + for (p = s, d = s, dot = true; *p; p++) { + if (*p == '.') { + if (dot) + continue; + + *(d++) = '.'; + dot = true; + } else if (hostname_valid_char(*p)) { + *(d++) = *p; + dot = false; + } + + } + + if (dot && d > s) + d[-1] = 0; + else + *d = 0; + + return s; +} + +bool is_localhost(const char *hostname) { + assert(hostname); + + /* This tries to identify local host and domain names + * described in RFC6761 plus the redhatism of localdomain */ + + return strcaseeq(hostname, "localhost") || + strcaseeq(hostname, "localhost.") || + strcaseeq(hostname, "localhost.localdomain") || + strcaseeq(hostname, "localhost.localdomain.") || + endswith_no_case(hostname, ".localhost") || + endswith_no_case(hostname, ".localhost.") || + endswith_no_case(hostname, ".localhost.localdomain") || + endswith_no_case(hostname, ".localhost.localdomain."); +} + +bool is_gateway_hostname(const char *hostname) { + assert(hostname); + + /* This tries to identify the valid syntaxes for the our + * synthetic "gateway" host. */ + + return + strcaseeq(hostname, "gateway") || + strcaseeq(hostname, "gateway."); +} + +int sethostname_idempotent(const char *s) { + char buf[HOST_NAME_MAX + 1] = {}; + + assert(s); + + if (gethostname(buf, sizeof(buf)) < 0) + return -errno; + + if (streq(buf, s)) + return 0; + + if (sethostname(s, strlen(s)) < 0) + return -errno; + + return 1; +} + +int read_hostname_config(const char *path, char **hostname) { + _cleanup_fclose_ FILE *f = NULL; + char l[LINE_MAX]; + char *name = NULL; + + assert(path); + assert(hostname); + + f = fopen(path, "re"); + if (!f) + return -errno; + + /* may have comments, ignore them */ + FOREACH_LINE(l, f, return -errno) { + truncate_nl(l); + if (l[0] != '\0' && l[0] != '#') { + /* found line with value */ + name = hostname_cleanup(l); + name = strdup(name); + if (!name) + return -ENOMEM; + break; + } + } + + if (!name) + /* no non-empty line found */ + return -ENOENT; + + *hostname = name; + return 0; +} diff --git a/src/libbasic/hostname-util.h b/src/libbasic/hostname-util.h new file mode 100644 index 0000000000..7af4e6c7ec --- /dev/null +++ b/src/libbasic/hostname-util.h @@ -0,0 +1,41 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-2015 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 "macro.h" + +bool hostname_is_set(void); + +char* gethostname_malloc(void); +int gethostname_strict(char **ret); + +bool hostname_is_valid(const char *s, bool allow_trailing_dot) _pure_; +char* hostname_cleanup(char *s); + +#define machine_name_is_valid(s) hostname_is_valid(s, false) + +bool is_localhost(const char *hostname); +bool is_gateway_hostname(const char *hostname); + +int sethostname_idempotent(const char *s); + +int read_hostname_config(const char *path, char **hostname); diff --git a/src/libbasic/in-addr-util.c b/src/libbasic/in-addr-util.c new file mode 100644 index 0000000000..245107ebb8 --- /dev/null +++ b/src/libbasic/in-addr-util.c @@ -0,0 +1,356 @@ +/*** + 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 +#include +#include +#include + +#include "alloc-util.h" +#include "in-addr-util.h" +#include "macro.h" +#include "util.h" + +int in_addr_is_null(int family, const union in_addr_union *u) { + assert(u); + + if (family == AF_INET) + return u->in.s_addr == 0; + + if (family == AF_INET6) + return + u->in6.s6_addr32[0] == 0 && + u->in6.s6_addr32[1] == 0 && + u->in6.s6_addr32[2] == 0 && + u->in6.s6_addr32[3] == 0; + + return -EAFNOSUPPORT; +} + +int in_addr_is_link_local(int family, const union in_addr_union *u) { + assert(u); + + if (family == AF_INET) + return (be32toh(u->in.s_addr) & UINT32_C(0xFFFF0000)) == (UINT32_C(169) << 24 | UINT32_C(254) << 16); + + if (family == AF_INET6) + return IN6_IS_ADDR_LINKLOCAL(&u->in6); + + return -EAFNOSUPPORT; +} + +int in_addr_is_localhost(int family, const union in_addr_union *u) { + assert(u); + + if (family == AF_INET) + /* All of 127.x.x.x is localhost. */ + return (be32toh(u->in.s_addr) & UINT32_C(0xFF000000)) == UINT32_C(127) << 24; + + if (family == AF_INET6) + return IN6_IS_ADDR_LOOPBACK(&u->in6); + + return -EAFNOSUPPORT; +} + +int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b) { + assert(a); + assert(b); + + if (family == AF_INET) + return a->in.s_addr == b->in.s_addr; + + if (family == AF_INET6) + return + a->in6.s6_addr32[0] == b->in6.s6_addr32[0] && + a->in6.s6_addr32[1] == b->in6.s6_addr32[1] && + a->in6.s6_addr32[2] == b->in6.s6_addr32[2] && + a->in6.s6_addr32[3] == b->in6.s6_addr32[3]; + + return -EAFNOSUPPORT; +} + +int in_addr_prefix_intersect( + int family, + const union in_addr_union *a, + unsigned aprefixlen, + const union in_addr_union *b, + unsigned bprefixlen) { + + unsigned m; + + assert(a); + assert(b); + + /* Checks whether there are any addresses that are in both + * networks */ + + m = MIN(aprefixlen, bprefixlen); + + if (family == AF_INET) { + uint32_t x, nm; + + x = be32toh(a->in.s_addr ^ b->in.s_addr); + nm = (m == 0) ? 0 : 0xFFFFFFFFUL << (32 - m); + + return (x & nm) == 0; + } + + if (family == AF_INET6) { + unsigned i; + + if (m > 128) + m = 128; + + for (i = 0; i < 16; i++) { + uint8_t x, nm; + + x = a->in6.s6_addr[i] ^ b->in6.s6_addr[i]; + + if (m < 8) + nm = 0xFF << (8 - m); + else + nm = 0xFF; + + if ((x & nm) != 0) + return 0; + + if (m > 8) + m -= 8; + else + m = 0; + } + + return 1; + } + + return -EAFNOSUPPORT; +} + +int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen) { + assert(u); + + /* Increases the network part of an address by one. Returns + * positive it that succeeds, or 0 if this overflows. */ + + if (prefixlen <= 0) + return 0; + + if (family == AF_INET) { + uint32_t c, n; + + if (prefixlen > 32) + prefixlen = 32; + + c = be32toh(u->in.s_addr); + n = c + (1UL << (32 - prefixlen)); + if (n < c) + return 0; + n &= 0xFFFFFFFFUL << (32 - prefixlen); + + u->in.s_addr = htobe32(n); + return 1; + } + + if (family == AF_INET6) { + struct in6_addr add = {}, result; + uint8_t overflow = 0; + unsigned i; + + if (prefixlen > 128) + prefixlen = 128; + + /* First calculate what we have to add */ + add.s6_addr[(prefixlen-1) / 8] = 1 << (7 - (prefixlen-1) % 8); + + for (i = 16; i > 0; i--) { + unsigned j = i - 1; + + result.s6_addr[j] = u->in6.s6_addr[j] + add.s6_addr[j] + overflow; + overflow = (result.s6_addr[j] < u->in6.s6_addr[j]); + } + + if (overflow) + return 0; + + u->in6 = result; + return 1; + } + + return -EAFNOSUPPORT; +} + +int in_addr_to_string(int family, const union in_addr_union *u, char **ret) { + char *x; + size_t l; + + assert(u); + assert(ret); + + if (family == AF_INET) + l = INET_ADDRSTRLEN; + else if (family == AF_INET6) + l = INET6_ADDRSTRLEN; + else + return -EAFNOSUPPORT; + + x = new(char, l); + if (!x) + return -ENOMEM; + + errno = 0; + if (!inet_ntop(family, u, x, l)) { + free(x); + return errno > 0 ? -errno : -EINVAL; + } + + *ret = x; + return 0; +} + +int in_addr_from_string(int family, const char *s, union in_addr_union *ret) { + + assert(s); + assert(ret); + + if (!IN_SET(family, AF_INET, AF_INET6)) + return -EAFNOSUPPORT; + + errno = 0; + if (inet_pton(family, s, ret) <= 0) + return errno > 0 ? -errno : -EINVAL; + + return 0; +} + +int in_addr_from_string_auto(const char *s, int *family, union in_addr_union *ret) { + int r; + + assert(s); + assert(family); + assert(ret); + + r = in_addr_from_string(AF_INET, s, ret); + if (r >= 0) { + *family = AF_INET; + return 0; + } + + r = in_addr_from_string(AF_INET6, s, ret); + if (r >= 0) { + *family = AF_INET6; + return 0; + } + + return -EINVAL; +} + +unsigned char in_addr_netmask_to_prefixlen(const struct in_addr *addr) { + assert(addr); + + return 32 - u32ctz(be32toh(addr->s_addr)); +} + +struct in_addr* in_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen) { + assert(addr); + assert(prefixlen <= 32); + + /* Shifting beyond 32 is not defined, handle this specially. */ + if (prefixlen == 0) + addr->s_addr = 0; + else + addr->s_addr = htobe32((0xffffffff << (32 - prefixlen)) & 0xffffffff); + + return addr; +} + +int in_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen) { + uint8_t msb_octet = *(uint8_t*) addr; + + /* addr may not be aligned, so make sure we only access it byte-wise */ + + assert(addr); + assert(prefixlen); + + if (msb_octet < 128) + /* class A, leading bits: 0 */ + *prefixlen = 8; + else if (msb_octet < 192) + /* class B, leading bits 10 */ + *prefixlen = 16; + else if (msb_octet < 224) + /* class C, leading bits 110 */ + *prefixlen = 24; + else + /* class D or E, no default prefixlen */ + return -ERANGE; + + return 0; +} + +int in_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask) { + unsigned char prefixlen; + int r; + + assert(addr); + assert(mask); + + r = in_addr_default_prefixlen(addr, &prefixlen); + if (r < 0) + return r; + + in_addr_prefixlen_to_netmask(mask, prefixlen); + return 0; +} + +int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen) { + assert(addr); + + if (family == AF_INET) { + struct in_addr mask; + + if (!in_addr_prefixlen_to_netmask(&mask, prefixlen)) + return -EINVAL; + + addr->in.s_addr &= mask.s_addr; + return 0; + } + + if (family == AF_INET6) { + unsigned i; + + for (i = 0; i < 16; i++) { + uint8_t mask; + + if (prefixlen >= 8) { + mask = 0xFF; + prefixlen -= 8; + } else { + mask = 0xFF << (8 - prefixlen); + prefixlen = 0; + } + + addr->in6.s6_addr[i] &= mask; + } + + return 0; + } + + return -EAFNOSUPPORT; +} diff --git a/src/libbasic/in-addr-util.h b/src/libbasic/in-addr-util.h new file mode 100644 index 0000000000..17798ce816 --- /dev/null +++ b/src/libbasic/in-addr-util.h @@ -0,0 +1,59 @@ +#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 +#include +#include + +#include "macro.h" +#include "util.h" + +union in_addr_union { + struct in_addr in; + struct in6_addr in6; +}; + +struct in_addr_data { + int family; + union in_addr_union address; +}; + +int in_addr_is_null(int family, const union in_addr_union *u); +int in_addr_is_link_local(int family, const union in_addr_union *u); +int in_addr_is_localhost(int family, const union in_addr_union *u); +int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b); +int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen); +int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen); +int in_addr_to_string(int family, const union in_addr_union *u, char **ret); +int in_addr_from_string(int family, const char *s, union in_addr_union *ret); +int in_addr_from_string_auto(const char *s, int *family, union in_addr_union *ret); +unsigned char in_addr_netmask_to_prefixlen(const struct in_addr *addr); +struct in_addr* in_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen); +int in_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen); +int in_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask); +int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen); + +static inline size_t FAMILY_ADDRESS_SIZE(int family) { + assert(family == AF_INET || family == AF_INET6); + return family == AF_INET6 ? 16 : 4; +} + +#define IN_ADDR_NULL ((union in_addr_union) {}) diff --git a/src/libbasic/io-util.c b/src/libbasic/io-util.c new file mode 100644 index 0000000000..cc6dfa8c1b --- /dev/null +++ b/src/libbasic/io-util.c @@ -0,0 +1,269 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include + +#include "io-util.h" +#include "time-util.h" + +int flush_fd(int fd) { + struct pollfd pollfd = { + .fd = fd, + .events = POLLIN, + }; + + /* Read from the specified file descriptor, until POLLIN is not set anymore, throwing away everything + * read. Note that some file descriptors (notable IP sockets) will trigger POLLIN even when no data can be read + * (due to IP packet checksum mismatches), hence this function is only safe to be non-blocking if the fd used + * was set to non-blocking too. */ + + for (;;) { + char buf[LINE_MAX]; + ssize_t l; + int r; + + r = poll(&pollfd, 1, 0); + if (r < 0) { + if (errno == EINTR) + continue; + + return -errno; + + } else if (r == 0) + return 0; + + l = read(fd, buf, sizeof(buf)); + if (l < 0) { + + if (errno == EINTR) + continue; + + if (errno == EAGAIN) + return 0; + + return -errno; + } else if (l == 0) + return 0; + } +} + +ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { + uint8_t *p = buf; + ssize_t n = 0; + + assert(fd >= 0); + assert(buf); + + /* If called with nbytes == 0, let's call read() at least + * once, to validate the operation */ + + if (nbytes > (size_t) SSIZE_MAX) + return -EINVAL; + + do { + ssize_t k; + + k = read(fd, p, nbytes); + if (k < 0) { + if (errno == EINTR) + continue; + + if (errno == EAGAIN && do_poll) { + + /* We knowingly ignore any return value here, + * and expect that any error/EOF is reported + * via read() */ + + (void) fd_wait_for_event(fd, POLLIN, USEC_INFINITY); + continue; + } + + return n > 0 ? n : -errno; + } + + if (k == 0) + return n; + + assert((size_t) k <= nbytes); + + p += k; + nbytes -= k; + n += k; + } while (nbytes > 0); + + return n; +} + +int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll) { + ssize_t n; + + n = loop_read(fd, buf, nbytes, do_poll); + if (n < 0) + return (int) n; + if ((size_t) n != nbytes) + return -EIO; + + return 0; +} + +int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { + const uint8_t *p = buf; + + assert(fd >= 0); + assert(buf); + + if (nbytes > (size_t) SSIZE_MAX) + return -EINVAL; + + do { + ssize_t k; + + k = write(fd, p, nbytes); + if (k < 0) { + if (errno == EINTR) + continue; + + if (errno == EAGAIN && do_poll) { + /* We knowingly ignore any return value here, + * and expect that any error/EOF is reported + * via write() */ + + (void) fd_wait_for_event(fd, POLLOUT, USEC_INFINITY); + continue; + } + + return -errno; + } + + if (_unlikely_(nbytes > 0 && k == 0)) /* Can't really happen */ + return -EIO; + + assert((size_t) k <= nbytes); + + p += k; + nbytes -= k; + } while (nbytes > 0); + + return 0; +} + +int pipe_eof(int fd) { + struct pollfd pollfd = { + .fd = fd, + .events = POLLIN|POLLHUP, + }; + + int r; + + r = poll(&pollfd, 1, 0); + if (r < 0) + return -errno; + + if (r == 0) + return 0; + + return pollfd.revents & POLLHUP; +} + +int fd_wait_for_event(int fd, int event, usec_t t) { + + struct pollfd pollfd = { + .fd = fd, + .events = event, + }; + + struct timespec ts; + int r; + + r = ppoll(&pollfd, 1, t == USEC_INFINITY ? NULL : timespec_store(&ts, t), NULL); + if (r < 0) + return -errno; + + if (r == 0) + return 0; + + return pollfd.revents; +} + +static size_t nul_length(const uint8_t *p, size_t sz) { + size_t n = 0; + + while (sz > 0) { + if (*p != 0) + break; + + n++; + p++; + sz--; + } + + return n; +} + +ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length) { + const uint8_t *q, *w, *e; + ssize_t l; + + q = w = p; + e = q + sz; + while (q < e) { + size_t n; + + n = nul_length(q, e - q); + + /* If there are more than the specified run length of + * NUL bytes, or if this is the beginning or the end + * of the buffer, then seek instead of write */ + if ((n > run_length) || + (n > 0 && q == p) || + (n > 0 && q + n >= e)) { + if (q > w) { + l = write(fd, w, q - w); + if (l < 0) + return -errno; + if (l != q -w) + return -EIO; + } + + if (lseek(fd, n, SEEK_CUR) == (off_t) -1) + return -errno; + + q += n; + w = q; + } else if (n > 0) + q += n; + else + q++; + } + + if (q > w) { + l = write(fd, w, q - w); + if (l < 0) + return -errno; + if (l != q - w) + return -EIO; + } + + return q - (const uint8_t*) p; +} diff --git a/src/libbasic/io-util.h b/src/libbasic/io-util.h new file mode 100644 index 0000000000..4684ed3bfc --- /dev/null +++ b/src/libbasic/io-util.h @@ -0,0 +1,95 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include + +#include "macro.h" +#include "time-util.h" + +int flush_fd(int fd); + +ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll); +int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll); +int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll); + +int pipe_eof(int fd); + +int fd_wait_for_event(int fd, int event, usec_t timeout); + +ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length); + +#define IOVEC_SET_STRING(i, s) \ + do { \ + struct iovec *_i = &(i); \ + char *_s = (char *)(s); \ + _i->iov_base = _s; \ + _i->iov_len = strlen(_s); \ + } while (false) + +static inline size_t IOVEC_TOTAL_SIZE(const struct iovec *i, unsigned n) { + unsigned j; + size_t r = 0; + + for (j = 0; j < n; j++) + r += i[j].iov_len; + + return r; +} + +static inline size_t IOVEC_INCREMENT(struct iovec *i, unsigned n, size_t k) { + unsigned j; + + for (j = 0; j < n; j++) { + size_t sub; + + if (_unlikely_(k <= 0)) + break; + + sub = MIN(i[j].iov_len, k); + i[j].iov_len -= sub; + i[j].iov_base = (uint8_t*) i[j].iov_base + sub; + k -= sub; + } + + return k; +} + +static inline bool FILE_SIZE_VALID(uint64_t l) { + /* ftruncate() and friends take an unsigned file size, but actually cannot deal with file sizes larger than + * 2^63 since the kernel internally handles it as signed value. This call allows checking for this early. */ + + return (l >> 63) == 0; +} + +static inline bool FILE_SIZE_VALID_OR_INFINITY(uint64_t l) { + + /* Same as above, but allows one extra value: -1 as indication for infinity. */ + + if (l == (uint64_t) -1) + return true; + + return FILE_SIZE_VALID(l); + +} diff --git a/src/libbasic/ioprio.h b/src/libbasic/ioprio.h new file mode 100644 index 0000000000..d8bb6eb497 --- /dev/null +++ b/src/libbasic/ioprio.h @@ -0,0 +1,55 @@ +#ifndef IOPRIO_H +#define IOPRIO_H + +/* This is minimal version of Linux' linux/ioprio.h header file, which + * is licensed GPL2 */ + +#include +#include + +/* + * Gives us 8 prio classes with 13-bits of data for each class + */ +#define IOPRIO_BITS (16) +#define IOPRIO_CLASS_SHIFT (13) +#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1) + +#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT) +#define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK) +#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data) + +#define ioprio_valid(mask) (IOPRIO_PRIO_CLASS((mask)) != IOPRIO_CLASS_NONE) + +/* + * These are the io priority groups as implemented by CFQ. RT is the realtime + * class, it always gets premium service. BE is the best-effort scheduling + * class, the default for any process. IDLE is the idle scheduling class, it + * is only served when no one else is using the disk. + */ +enum { + IOPRIO_CLASS_NONE, + IOPRIO_CLASS_RT, + IOPRIO_CLASS_BE, + IOPRIO_CLASS_IDLE, +}; + +/* + * 8 best effort priority levels are supported + */ +#define IOPRIO_BE_NR (8) + +enum { + IOPRIO_WHO_PROCESS = 1, + IOPRIO_WHO_PGRP, + IOPRIO_WHO_USER, +}; + +static inline int ioprio_set(int which, int who, int ioprio) { + return syscall(__NR_ioprio_set, which, who, ioprio); +} + +static inline int ioprio_get(int which, int who) { + return syscall(__NR_ioprio_get, which, who); +} + +#endif diff --git a/src/libbasic/label.c b/src/libbasic/label.c new file mode 100644 index 0000000000..f5ab855d32 --- /dev/null +++ b/src/libbasic/label.c @@ -0,0 +1,82 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include + +#include "label.h" +#include "macro.h" +#include "selinux-util.h" +#include "smack-util.h" + +int label_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { + int r, q; + + r = mac_selinux_fix(path, ignore_enoent, ignore_erofs); + q = mac_smack_fix(path, ignore_enoent, ignore_erofs); + + if (r < 0) + return r; + if (q < 0) + return q; + + return 0; +} + +int mkdir_label(const char *path, mode_t mode) { + int r; + + assert(path); + + r = mac_selinux_create_file_prepare(path, S_IFDIR); + if (r < 0) + return r; + + if (mkdir(path, mode) < 0) + r = -errno; + + mac_selinux_create_file_clear(); + + if (r < 0) + return r; + + return mac_smack_fix(path, false, false); +} + +int symlink_label(const char *old_path, const char *new_path) { + int r; + + assert(old_path); + assert(new_path); + + r = mac_selinux_create_file_prepare(new_path, S_IFLNK); + if (r < 0) + return r; + + if (symlink(old_path, new_path) < 0) + r = -errno; + + mac_selinux_create_file_clear(); + + if (r < 0) + return r; + + return mac_smack_fix(new_path, false, false); +} diff --git a/src/libbasic/label.h b/src/libbasic/label.h new file mode 100644 index 0000000000..3e9251aa71 --- /dev/null +++ b/src/libbasic/label.h @@ -0,0 +1,28 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 + +int label_fix(const char *path, bool ignore_enoent, bool ignore_erofs); + +int mkdir_label(const char *path, mode_t mode); +int symlink_label(const char *old_path, const char *new_path); diff --git a/src/libbasic/list.h b/src/libbasic/list.h new file mode 100644 index 0000000000..5962aa4211 --- /dev/null +++ b/src/libbasic/list.h @@ -0,0 +1,182 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +/* The head of the linked list. Use this in the structure that shall + * contain the head of the linked list */ +#define LIST_HEAD(t,name) \ + t *name + +/* The pointers in the linked list's items. Use this in the item structure */ +#define LIST_FIELDS(t,name) \ + t *name##_next, *name##_prev + +/* Initialize the list's head */ +#define LIST_HEAD_INIT(head) \ + do { \ + (head) = NULL; } \ + while (false) + +/* Initialize a list item */ +#define LIST_INIT(name,item) \ + do { \ + typeof(*(item)) *_item = (item); \ + assert(_item); \ + _item->name##_prev = _item->name##_next = NULL; \ + } while (false) + +/* Prepend an item to the list */ +#define LIST_PREPEND(name,head,item) \ + do { \ + typeof(*(head)) **_head = &(head), *_item = (item); \ + assert(_item); \ + if ((_item->name##_next = *_head)) \ + _item->name##_next->name##_prev = _item; \ + _item->name##_prev = NULL; \ + *_head = _item; \ + } while (false) + +/* Append an item to the list */ +#define LIST_APPEND(name,head,item) \ + do { \ + typeof(*(head)) *_tail; \ + LIST_FIND_TAIL(name,head,_tail); \ + LIST_INSERT_AFTER(name,head,_tail,item); \ + } while (false) + +/* Remove an item from the list */ +#define LIST_REMOVE(name,head,item) \ + do { \ + typeof(*(head)) **_head = &(head), *_item = (item); \ + assert(_item); \ + if (_item->name##_next) \ + _item->name##_next->name##_prev = _item->name##_prev; \ + if (_item->name##_prev) \ + _item->name##_prev->name##_next = _item->name##_next; \ + else { \ + assert(*_head == _item); \ + *_head = _item->name##_next; \ + } \ + _item->name##_next = _item->name##_prev = NULL; \ + } while (false) + +/* Find the head of the list */ +#define LIST_FIND_HEAD(name,item,head) \ + do { \ + typeof(*(item)) *_item = (item); \ + if (!_item) \ + (head) = NULL; \ + else { \ + while (_item->name##_prev) \ + _item = _item->name##_prev; \ + (head) = _item; \ + } \ + } while (false) + +/* Find the tail of the list */ +#define LIST_FIND_TAIL(name,item,tail) \ + do { \ + typeof(*(item)) *_item = (item); \ + if (!_item) \ + (tail) = NULL; \ + else { \ + while (_item->name##_next) \ + _item = _item->name##_next; \ + (tail) = _item; \ + } \ + } while (false) + +/* Insert an item after another one (a = where, b = what) */ +#define LIST_INSERT_AFTER(name,head,a,b) \ + do { \ + typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \ + assert(_b); \ + if (!_a) { \ + if ((_b->name##_next = *_head)) \ + _b->name##_next->name##_prev = _b; \ + _b->name##_prev = NULL; \ + *_head = _b; \ + } else { \ + if ((_b->name##_next = _a->name##_next)) \ + _b->name##_next->name##_prev = _b; \ + _b->name##_prev = _a; \ + _a->name##_next = _b; \ + } \ + } while (false) + +/* Insert an item before another one (a = where, b = what) */ +#define LIST_INSERT_BEFORE(name,head,a,b) \ + do { \ + typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \ + assert(_b); \ + if (!_a) { \ + if (!*_head) { \ + _b->name##_next = NULL; \ + _b->name##_prev = NULL; \ + *_head = _b; \ + } else { \ + typeof(*(head)) *_tail = (head); \ + while (_tail->name##_next) \ + _tail = _tail->name##_next; \ + _b->name##_next = NULL; \ + _b->name##_prev = _tail; \ + _tail->name##_next = _b; \ + } \ + } else { \ + if ((_b->name##_prev = _a->name##_prev)) \ + _b->name##_prev->name##_next = _b; \ + _b->name##_next = _a; \ + _a->name##_prev = _b; \ + } \ + } while (false) + +#define LIST_JUST_US(name,item) \ + (!(item)->name##_prev && !(item)->name##_next) \ + +#define LIST_FOREACH(name,i,head) \ + for ((i) = (head); (i); (i) = (i)->name##_next) + +#define LIST_FOREACH_SAFE(name,i,n,head) \ + for ((i) = (head); (i) && (((n) = (i)->name##_next), 1); (i) = (n)) + +#define LIST_FOREACH_BEFORE(name,i,p) \ + for ((i) = (p)->name##_prev; (i); (i) = (i)->name##_prev) + +#define LIST_FOREACH_AFTER(name,i,p) \ + for ((i) = (p)->name##_next; (i); (i) = (i)->name##_next) + +/* Iterate through all the members of the list p is included in, but skip over p */ +#define LIST_FOREACH_OTHERS(name,i,p) \ + for (({ \ + (i) = (p); \ + while ((i) && (i)->name##_prev) \ + (i) = (i)->name##_prev; \ + if ((i) == (p)) \ + (i) = (p)->name##_next; \ + }); \ + (i); \ + (i) = (i)->name##_next == (p) ? (p)->name##_next : (i)->name##_next) + +/* Loop starting from p->next until p->prev. + p can be adjusted meanwhile. */ +#define LIST_LOOP_BUT_ONE(name,i,head,p) \ + for ((i) = (p)->name##_next ? (p)->name##_next : (head); \ + (i) != (p); \ + (i) = (i)->name##_next ? (i)->name##_next : (head)) diff --git a/src/libbasic/locale-util.c b/src/libbasic/locale-util.c new file mode 100644 index 0000000000..ada0a28cd8 --- /dev/null +++ b/src/libbasic/locale-util.c @@ -0,0 +1,322 @@ +/*** + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dirent-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "locale-util.h" +#include "path-util.h" +#include "set.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "utf8.h" + +static int add_locales_from_archive(Set *locales) { + /* Stolen from glibc... */ + + struct locarhead { + uint32_t magic; + /* Serial number. */ + uint32_t serial; + /* Name hash table. */ + uint32_t namehash_offset; + uint32_t namehash_used; + uint32_t namehash_size; + /* String table. */ + uint32_t string_offset; + uint32_t string_used; + uint32_t string_size; + /* Table with locale records. */ + uint32_t locrectab_offset; + uint32_t locrectab_used; + uint32_t locrectab_size; + /* MD5 sum hash table. */ + uint32_t sumhash_offset; + uint32_t sumhash_used; + uint32_t sumhash_size; + }; + + struct namehashent { + /* Hash value of the name. */ + uint32_t hashval; + /* Offset of the name in the string table. */ + uint32_t name_offset; + /* Offset of the locale record. */ + uint32_t locrec_offset; + }; + + const struct locarhead *h; + const struct namehashent *e; + const void *p = MAP_FAILED; + _cleanup_close_ int fd = -1; + size_t sz = 0; + struct stat st; + unsigned i; + int r; + + fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return errno == ENOENT ? 0 : -errno; + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISREG(st.st_mode)) + return -EBADMSG; + + if (st.st_size < (off_t) sizeof(struct locarhead)) + return -EBADMSG; + + p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (p == MAP_FAILED) + return -errno; + + h = (const struct locarhead *) p; + if (h->magic != 0xde020109 || + h->namehash_offset + h->namehash_size > st.st_size || + h->string_offset + h->string_size > st.st_size || + h->locrectab_offset + h->locrectab_size > st.st_size || + h->sumhash_offset + h->sumhash_size > st.st_size) { + r = -EBADMSG; + goto finish; + } + + e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset); + for (i = 0; i < h->namehash_size; i++) { + char *z; + + if (e[i].locrec_offset == 0) + continue; + + if (!utf8_is_valid((char*) p + e[i].name_offset)) + continue; + + z = strdup((char*) p + e[i].name_offset); + if (!z) { + r = -ENOMEM; + goto finish; + } + + r = set_consume(locales, z); + if (r < 0) + goto finish; + } + + r = 0; + + finish: + if (p != MAP_FAILED) + munmap((void*) p, sz); + + return r; +} + +static int add_locales_from_libdir (Set *locales) { + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *entry; + int r; + + dir = opendir("/usr/lib/locale"); + if (!dir) + return errno == ENOENT ? 0 : -errno; + + FOREACH_DIRENT(entry, dir, return -errno) { + char *z; + + dirent_ensure_type(dir, entry); + + if (entry->d_type != DT_DIR) + continue; + + z = strdup(entry->d_name); + if (!z) + return -ENOMEM; + + r = set_consume(locales, z); + if (r < 0 && r != -EEXIST) + return r; + } + + return 0; +} + +int get_locales(char ***ret) { + _cleanup_set_free_ Set *locales = NULL; + _cleanup_strv_free_ char **l = NULL; + int r; + + locales = set_new(&string_hash_ops); + if (!locales) + return -ENOMEM; + + r = add_locales_from_archive(locales); + if (r < 0 && r != -ENOENT) + return r; + + r = add_locales_from_libdir(locales); + if (r < 0) + return r; + + l = set_get_strv(locales); + if (!l) + return -ENOMEM; + + strv_sort(l); + + *ret = l; + l = NULL; + + return 0; +} + +bool locale_is_valid(const char *name) { + + if (isempty(name)) + return false; + + if (strlen(name) >= 128) + return false; + + if (!utf8_is_valid(name)) + return false; + + if (!filename_is_valid(name)) + return false; + + if (!string_is_safe(name)) + return false; + + return true; +} + +void init_gettext(void) { + setlocale(LC_ALL, ""); + textdomain(GETTEXT_PACKAGE); +} + +bool is_locale_utf8(void) { + const char *set; + static int cached_answer = -1; + + /* Note that we default to 'true' here, since today UTF8 is + * pretty much supported everywhere. */ + + if (cached_answer >= 0) + goto out; + + if (!setlocale(LC_ALL, "")) { + cached_answer = true; + goto out; + } + + set = nl_langinfo(CODESET); + if (!set) { + cached_answer = true; + goto out; + } + + if (streq(set, "UTF-8")) { + cached_answer = true; + goto out; + } + + /* For LC_CTYPE=="C" return true, because CTYPE is effectly + * unset and everything can do to UTF-8 nowadays. */ + set = setlocale(LC_CTYPE, NULL); + if (!set) { + cached_answer = true; + goto out; + } + + /* Check result, but ignore the result if C was set + * explicitly. */ + cached_answer = + STR_IN_SET(set, "C", "POSIX") && + !getenv("LC_ALL") && + !getenv("LC_CTYPE") && + !getenv("LANG"); + +out: + return (bool) cached_answer; +} + + +const char *special_glyph(SpecialGlyph code) { + + static const char* const draw_table[2][_SPECIAL_GLYPH_MAX] = { + /* ASCII fallback */ + [false] = { + [TREE_VERTICAL] = "| ", + [TREE_BRANCH] = "|-", + [TREE_RIGHT] = "`-", + [TREE_SPACE] = " ", + [TRIANGULAR_BULLET] = ">", + [BLACK_CIRCLE] = "*", + [ARROW] = "->", + [MDASH] = "-", + }, + + /* UTF-8 */ + [ true ] = { + [TREE_VERTICAL] = "\342\224\202 ", /* │ */ + [TREE_BRANCH] = "\342\224\234\342\224\200", /* ├─ */ + [TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */ + [TREE_SPACE] = " ", /* */ + [TRIANGULAR_BULLET] = "\342\200\243", /* ‣ */ + [BLACK_CIRCLE] = "\342\227\217", /* ● */ + [ARROW] = "\342\206\222", /* → */ + [MDASH] = "\342\200\223", /* – */ + }, + }; + + return draw_table[is_locale_utf8()][code]; +} + +static const char * const locale_variable_table[_VARIABLE_LC_MAX] = { + [VARIABLE_LANG] = "LANG", + [VARIABLE_LANGUAGE] = "LANGUAGE", + [VARIABLE_LC_CTYPE] = "LC_CTYPE", + [VARIABLE_LC_NUMERIC] = "LC_NUMERIC", + [VARIABLE_LC_TIME] = "LC_TIME", + [VARIABLE_LC_COLLATE] = "LC_COLLATE", + [VARIABLE_LC_MONETARY] = "LC_MONETARY", + [VARIABLE_LC_MESSAGES] = "LC_MESSAGES", + [VARIABLE_LC_PAPER] = "LC_PAPER", + [VARIABLE_LC_NAME] = "LC_NAME", + [VARIABLE_LC_ADDRESS] = "LC_ADDRESS", + [VARIABLE_LC_TELEPHONE] = "LC_TELEPHONE", + [VARIABLE_LC_MEASUREMENT] = "LC_MEASUREMENT", + [VARIABLE_LC_IDENTIFICATION] = "LC_IDENTIFICATION" +}; + +DEFINE_STRING_TABLE_LOOKUP(locale_variable, LocaleVariable); diff --git a/src/libbasic/locale-util.h b/src/libbasic/locale-util.h new file mode 100644 index 0000000000..0630a034ab --- /dev/null +++ b/src/libbasic/locale-util.h @@ -0,0 +1,73 @@ +#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 +#include + +#include "macro.h" + +typedef enum LocaleVariable { + /* We don't list LC_ALL here on purpose. People should be + * using LANG instead. */ + + VARIABLE_LANG, + VARIABLE_LANGUAGE, + VARIABLE_LC_CTYPE, + VARIABLE_LC_NUMERIC, + VARIABLE_LC_TIME, + VARIABLE_LC_COLLATE, + VARIABLE_LC_MONETARY, + VARIABLE_LC_MESSAGES, + VARIABLE_LC_PAPER, + VARIABLE_LC_NAME, + VARIABLE_LC_ADDRESS, + VARIABLE_LC_TELEPHONE, + VARIABLE_LC_MEASUREMENT, + VARIABLE_LC_IDENTIFICATION, + _VARIABLE_LC_MAX, + _VARIABLE_LC_INVALID = -1 +} LocaleVariable; + +int get_locales(char ***l); +bool locale_is_valid(const char *name); + +#define _(String) gettext(String) +#define N_(String) String +void init_gettext(void); + +bool is_locale_utf8(void); + +typedef enum { + TREE_VERTICAL, + TREE_BRANCH, + TREE_RIGHT, + TREE_SPACE, + TRIANGULAR_BULLET, + BLACK_CIRCLE, + ARROW, + MDASH, + _SPECIAL_GLYPH_MAX +} SpecialGlyph; + +const char *special_glyph(SpecialGlyph code) _const_; + +const char* locale_variable_to_string(LocaleVariable i) _const_; +LocaleVariable locale_variable_from_string(const char *s) _pure_; diff --git a/src/libbasic/lockfile-util.c b/src/libbasic/lockfile-util.c new file mode 100644 index 0000000000..3ee4191e4d --- /dev/null +++ b/src/libbasic/lockfile-util.c @@ -0,0 +1,153 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "lockfile-util.h" +#include "macro.h" +#include "path-util.h" + +int make_lock_file(const char *p, int operation, LockFile *ret) { + _cleanup_close_ int fd = -1; + _cleanup_free_ char *t = NULL; + int r; + + /* + * We use UNPOSIX locks if they are available. They have nice + * semantics, and are mostly compatible with NFS. However, + * they are only available on new kernels. When we detect we + * are running on an older kernel, then we fall back to good + * old BSD locks. They also have nice semantics, but are + * slightly problematic on NFS, where they are upgraded to + * POSIX locks, even though locally they are orthogonal to + * POSIX locks. + */ + + t = strdup(p); + if (!t) + return -ENOMEM; + + for (;;) { + struct flock fl = { + .l_type = (operation & ~LOCK_NB) == LOCK_EX ? F_WRLCK : F_RDLCK, + .l_whence = SEEK_SET, + }; + struct stat st; + + fd = open(p, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600); + if (fd < 0) + return -errno; + + r = fcntl(fd, (operation & LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW, &fl); + if (r < 0) { + + /* If the kernel is too old, use good old BSD locks */ + if (errno == EINVAL) + r = flock(fd, operation); + + if (r < 0) + return errno == EAGAIN ? -EBUSY : -errno; + } + + /* If we acquired the lock, let's check if the file + * still exists in the file system. If not, then the + * previous exclusive owner removed it and then closed + * it. In such a case our acquired lock is worthless, + * hence try again. */ + + r = fstat(fd, &st); + if (r < 0) + return -errno; + if (st.st_nlink > 0) + break; + + fd = safe_close(fd); + } + + ret->path = t; + ret->fd = fd; + ret->operation = operation; + + fd = -1; + t = NULL; + + return r; +} + +int make_lock_file_for(const char *p, int operation, LockFile *ret) { + const char *fn; + char *t; + + assert(p); + assert(ret); + + fn = basename(p); + if (!filename_is_valid(fn)) + return -EINVAL; + + t = newa(char, strlen(p) + 2 + 4 + 1); + stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn), ".lck"); + + return make_lock_file(t, operation, ret); +} + +void release_lock_file(LockFile *f) { + int r; + + if (!f) + return; + + if (f->path) { + + /* If we are the exclusive owner we can safely delete + * the lock file itself. If we are not the exclusive + * owner, we can try becoming it. */ + + if (f->fd >= 0 && + (f->operation & ~LOCK_NB) == LOCK_SH) { + static const struct flock fl = { + .l_type = F_WRLCK, + .l_whence = SEEK_SET, + }; + + r = fcntl(f->fd, F_OFD_SETLK, &fl); + if (r < 0 && errno == EINVAL) + r = flock(f->fd, LOCK_EX|LOCK_NB); + + if (r >= 0) + f->operation = LOCK_EX|LOCK_NB; + } + + if ((f->operation & ~LOCK_NB) == LOCK_EX) + unlink_noerrno(f->path); + + f->path = mfree(f->path); + } + + f->fd = safe_close(f->fd); + f->operation = 0; +} diff --git a/src/libbasic/lockfile-util.h b/src/libbasic/lockfile-util.h new file mode 100644 index 0000000000..22491ee8e1 --- /dev/null +++ b/src/libbasic/lockfile-util.h @@ -0,0 +1,39 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 "macro.h" +#include "missing.h" + +typedef struct LockFile { + char *path; + int fd; + int operation; +} LockFile; + +int make_lock_file(const char *p, int operation, LockFile *ret); +int make_lock_file_for(const char *p, int operation, LockFile *ret); +void release_lock_file(LockFile *f); + +#define _cleanup_release_lock_file_ _cleanup_(release_lock_file) + +#define LOCK_FILE_INIT { .fd = -1, .path = NULL } diff --git a/src/libbasic/log.c b/src/libbasic/log.c new file mode 100644 index 0000000000..05c4896f55 --- /dev/null +++ b/src/libbasic/log.c @@ -0,0 +1,1170 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "io-util.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "process-util.h" +#include "signal-util.h" +#include "socket-util.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" +#include "syslog-util.h" +#include "terminal-util.h" +#include "time-util.h" +#include "util.h" + +#define SNDBUF_SIZE (8*1024*1024) + +static LogTarget log_target = LOG_TARGET_CONSOLE; +static int log_max_level = LOG_INFO; +static int log_facility = LOG_DAEMON; + +static int console_fd = STDERR_FILENO; +static int syslog_fd = -1; +static int kmsg_fd = -1; +static int journal_fd = -1; + +static bool syslog_is_stream = false; + +static bool show_color = false; +static bool show_location = false; + +static bool upgrade_syslog_to_journal = false; + +/* Akin to glibc's __abort_msg; which is private and we hence cannot + * use here. */ +static char *log_abort_msg = NULL; + +void log_close_console(void) { + + if (console_fd < 0) + return; + + if (getpid() == 1) { + if (console_fd >= 3) + safe_close(console_fd); + + console_fd = -1; + } +} + +static int log_open_console(void) { + + if (console_fd >= 0) + return 0; + + if (getpid() == 1) { + console_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (console_fd < 0) + return console_fd; + } else + console_fd = STDERR_FILENO; + + return 0; +} + +void log_close_kmsg(void) { + kmsg_fd = safe_close(kmsg_fd); +} + +static int log_open_kmsg(void) { + + if (kmsg_fd >= 0) + return 0; + + kmsg_fd = open("/dev/kmsg", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (kmsg_fd < 0) + return -errno; + + return 0; +} + +void log_close_syslog(void) { + syslog_fd = safe_close(syslog_fd); +} + +static int create_log_socket(int type) { + struct timeval tv; + int fd; + + fd = socket(AF_UNIX, type|SOCK_CLOEXEC, 0); + if (fd < 0) + return -errno; + + fd_inc_sndbuf(fd, SNDBUF_SIZE); + + /* We need a blocking fd here since we'd otherwise lose + messages way too early. However, let's not hang forever in the + unlikely case of a deadlock. */ + if (getpid() == 1) + timeval_store(&tv, 10 * USEC_PER_MSEC); + else + timeval_store(&tv, 10 * USEC_PER_SEC); + (void) setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + + return fd; +} + +static int log_open_syslog(void) { + + static const union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/dev/log", + }; + + int r; + + if (syslog_fd >= 0) + return 0; + + syslog_fd = create_log_socket(SOCK_DGRAM); + if (syslog_fd < 0) { + r = syslog_fd; + goto fail; + } + + if (connect(syslog_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) { + safe_close(syslog_fd); + + /* Some legacy syslog systems still use stream + * sockets. They really shouldn't. But what can we + * do... */ + syslog_fd = create_log_socket(SOCK_STREAM); + if (syslog_fd < 0) { + r = syslog_fd; + goto fail; + } + + if (connect(syslog_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) { + r = -errno; + goto fail; + } + + syslog_is_stream = true; + } else + syslog_is_stream = false; + + return 0; + +fail: + log_close_syslog(); + return r; +} + +void log_close_journal(void) { + journal_fd = safe_close(journal_fd); +} + +static int log_open_journal(void) { + + static const union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/journal/socket", + }; + + int r; + + if (journal_fd >= 0) + return 0; + + journal_fd = create_log_socket(SOCK_DGRAM); + if (journal_fd < 0) { + r = journal_fd; + goto fail; + } + + if (connect(journal_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) { + r = -errno; + goto fail; + } + + return 0; + +fail: + log_close_journal(); + return r; +} + +int log_open(void) { + int r; + + /* If we don't use the console we close it here, to not get + * killed by SAK. If we don't use syslog we close it here so + * that we are not confused by somebody deleting the socket in + * the fs. If we don't use /dev/kmsg we still keep it open, + * because there is no reason to close it. */ + + if (log_target == LOG_TARGET_NULL) { + log_close_journal(); + log_close_syslog(); + log_close_console(); + return 0; + } + + if ((log_target != LOG_TARGET_AUTO && log_target != LOG_TARGET_SAFE) || + getpid() == 1 || + isatty(STDERR_FILENO) <= 0) { + + if (log_target == LOG_TARGET_AUTO || + log_target == LOG_TARGET_JOURNAL_OR_KMSG || + log_target == LOG_TARGET_JOURNAL) { + r = log_open_journal(); + if (r >= 0) { + log_close_syslog(); + log_close_console(); + return r; + } + } + + if (log_target == LOG_TARGET_SYSLOG_OR_KMSG || + log_target == LOG_TARGET_SYSLOG) { + r = log_open_syslog(); + if (r >= 0) { + log_close_journal(); + log_close_console(); + return r; + } + } + + if (log_target == LOG_TARGET_AUTO || + log_target == LOG_TARGET_SAFE || + log_target == LOG_TARGET_JOURNAL_OR_KMSG || + log_target == LOG_TARGET_SYSLOG_OR_KMSG || + log_target == LOG_TARGET_KMSG) { + r = log_open_kmsg(); + if (r >= 0) { + log_close_journal(); + log_close_syslog(); + log_close_console(); + return r; + } + } + } + + log_close_journal(); + log_close_syslog(); + + return log_open_console(); +} + +void log_set_target(LogTarget target) { + assert(target >= 0); + assert(target < _LOG_TARGET_MAX); + + if (upgrade_syslog_to_journal) { + if (target == LOG_TARGET_SYSLOG) + target = LOG_TARGET_JOURNAL; + else if (target == LOG_TARGET_SYSLOG_OR_KMSG) + target = LOG_TARGET_JOURNAL_OR_KMSG; + } + + log_target = target; +} + +void log_close(void) { + log_close_journal(); + log_close_syslog(); + log_close_kmsg(); + log_close_console(); +} + +void log_forget_fds(void) { + console_fd = kmsg_fd = syslog_fd = journal_fd = -1; +} + +void log_set_max_level(int level) { + assert((level & LOG_PRIMASK) == level); + + log_max_level = level; +} + +void log_set_facility(int facility) { + log_facility = facility; +} + +static int write_to_console( + int level, + int error, + const char *file, + int line, + const char *func, + const char *object_field, + const char *object, + const char *buffer) { + + char location[64], prefix[1 + DECIMAL_STR_MAX(int) + 2]; + struct iovec iovec[6] = {}; + unsigned n = 0; + bool highlight; + + if (console_fd < 0) + return 0; + + if (log_target == LOG_TARGET_CONSOLE_PREFIXED) { + sprintf(prefix, "<%i>", level); + IOVEC_SET_STRING(iovec[n++], prefix); + } + + highlight = LOG_PRI(level) <= LOG_ERR && show_color; + + if (show_location) { + xsprintf(location, "(%s:%i) ", file, line); + IOVEC_SET_STRING(iovec[n++], location); + } + + if (highlight) + IOVEC_SET_STRING(iovec[n++], ANSI_HIGHLIGHT_RED); + IOVEC_SET_STRING(iovec[n++], buffer); + if (highlight) + IOVEC_SET_STRING(iovec[n++], ANSI_NORMAL); + IOVEC_SET_STRING(iovec[n++], "\n"); + + if (writev(console_fd, iovec, n) < 0) { + + if (errno == EIO && getpid() == 1) { + + /* If somebody tried to kick us from our + * console tty (via vhangup() or suchlike), + * try to reconnect */ + + log_close_console(); + log_open_console(); + + if (console_fd < 0) + return 0; + + if (writev(console_fd, iovec, n) < 0) + return -errno; + } else + return -errno; + } + + return 1; +} + +static int write_to_syslog( + int level, + int error, + const char *file, + int line, + const char *func, + const char *object_field, + const char *object, + const char *buffer) { + + char header_priority[2 + DECIMAL_STR_MAX(int) + 1], + header_time[64], + header_pid[4 + DECIMAL_STR_MAX(pid_t) + 1]; + struct iovec iovec[5] = {}; + struct msghdr msghdr = { + .msg_iov = iovec, + .msg_iovlen = ELEMENTSOF(iovec), + }; + time_t t; + struct tm *tm; + + if (syslog_fd < 0) + return 0; + + xsprintf(header_priority, "<%i>", level); + + t = (time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC); + tm = localtime(&t); + if (!tm) + return -EINVAL; + + if (strftime(header_time, sizeof(header_time), "%h %e %T ", tm) <= 0) + return -EINVAL; + + xsprintf(header_pid, "["PID_FMT"]: ", getpid()); + + IOVEC_SET_STRING(iovec[0], header_priority); + IOVEC_SET_STRING(iovec[1], header_time); + IOVEC_SET_STRING(iovec[2], program_invocation_short_name); + IOVEC_SET_STRING(iovec[3], header_pid); + IOVEC_SET_STRING(iovec[4], buffer); + + /* When using syslog via SOCK_STREAM separate the messages by NUL chars */ + if (syslog_is_stream) + iovec[4].iov_len++; + + for (;;) { + ssize_t n; + + n = sendmsg(syslog_fd, &msghdr, MSG_NOSIGNAL); + if (n < 0) + return -errno; + + if (!syslog_is_stream || + (size_t) n >= IOVEC_TOTAL_SIZE(iovec, ELEMENTSOF(iovec))) + break; + + IOVEC_INCREMENT(iovec, ELEMENTSOF(iovec), n); + } + + return 1; +} + +static int write_to_kmsg( + int level, + int error, + const char *file, + int line, + const char *func, + const char *object_field, + const char *object, + const char *buffer) { + + char header_priority[2 + DECIMAL_STR_MAX(int) + 1], + header_pid[4 + DECIMAL_STR_MAX(pid_t) + 1]; + struct iovec iovec[5] = {}; + + if (kmsg_fd < 0) + return 0; + + xsprintf(header_priority, "<%i>", level); + xsprintf(header_pid, "["PID_FMT"]: ", getpid()); + + IOVEC_SET_STRING(iovec[0], header_priority); + IOVEC_SET_STRING(iovec[1], program_invocation_short_name); + IOVEC_SET_STRING(iovec[2], header_pid); + IOVEC_SET_STRING(iovec[3], buffer); + IOVEC_SET_STRING(iovec[4], "\n"); + + if (writev(kmsg_fd, iovec, ELEMENTSOF(iovec)) < 0) + return -errno; + + return 1; +} + +static int log_do_header( + char *header, + size_t size, + int level, + int error, + const char *file, int line, const char *func, + const char *object_field, const char *object) { + + snprintf(header, size, + "PRIORITY=%i\n" + "SYSLOG_FACILITY=%i\n" + "%s%s%s" + "%s%.*i%s" + "%s%s%s" + "%s%.*i%s" + "%s%s%s" + "SYSLOG_IDENTIFIER=%s\n", + LOG_PRI(level), + LOG_FAC(level), + isempty(file) ? "" : "CODE_FILE=", + isempty(file) ? "" : file, + isempty(file) ? "" : "\n", + line ? "CODE_LINE=" : "", + line ? 1 : 0, line, /* %.0d means no output too, special case for 0 */ + line ? "\n" : "", + isempty(func) ? "" : "CODE_FUNCTION=", + isempty(func) ? "" : func, + isempty(func) ? "" : "\n", + error ? "ERRNO=" : "", + error ? 1 : 0, error, + error ? "\n" : "", + isempty(object) ? "" : object_field, + isempty(object) ? "" : object, + isempty(object) ? "" : "\n", + program_invocation_short_name); + + return 0; +} + +static int write_to_journal( + int level, + int error, + const char *file, + int line, + const char *func, + const char *object_field, + const char *object, + const char *buffer) { + + char header[LINE_MAX]; + struct iovec iovec[4] = {}; + struct msghdr mh = {}; + + if (journal_fd < 0) + return 0; + + log_do_header(header, sizeof(header), level, error, file, line, func, object_field, object); + + IOVEC_SET_STRING(iovec[0], header); + IOVEC_SET_STRING(iovec[1], "MESSAGE="); + IOVEC_SET_STRING(iovec[2], buffer); + IOVEC_SET_STRING(iovec[3], "\n"); + + mh.msg_iov = iovec; + mh.msg_iovlen = ELEMENTSOF(iovec); + + if (sendmsg(journal_fd, &mh, MSG_NOSIGNAL) < 0) + return -errno; + + return 1; +} + +static int log_dispatch( + int level, + int error, + const char *file, + int line, + const char *func, + const char *object_field, + const char *object, + char *buffer) { + + assert(buffer); + + if (log_target == LOG_TARGET_NULL) + return -error; + + /* Patch in LOG_DAEMON facility if necessary */ + if ((level & LOG_FACMASK) == 0) + level = log_facility | LOG_PRI(level); + + if (error < 0) + error = -error; + + do { + char *e; + int k = 0; + + buffer += strspn(buffer, NEWLINE); + + if (buffer[0] == 0) + break; + + if ((e = strpbrk(buffer, NEWLINE))) + *(e++) = 0; + + if (log_target == LOG_TARGET_AUTO || + log_target == LOG_TARGET_JOURNAL_OR_KMSG || + log_target == LOG_TARGET_JOURNAL) { + + k = write_to_journal(level, error, file, line, func, object_field, object, buffer); + if (k < 0) { + if (k != -EAGAIN) + log_close_journal(); + log_open_kmsg(); + } + } + + if (log_target == LOG_TARGET_SYSLOG_OR_KMSG || + log_target == LOG_TARGET_SYSLOG) { + + k = write_to_syslog(level, error, file, line, func, object_field, object, buffer); + if (k < 0) { + if (k != -EAGAIN) + log_close_syslog(); + log_open_kmsg(); + } + } + + if (k <= 0 && + (log_target == LOG_TARGET_AUTO || + log_target == LOG_TARGET_SAFE || + log_target == LOG_TARGET_SYSLOG_OR_KMSG || + log_target == LOG_TARGET_JOURNAL_OR_KMSG || + log_target == LOG_TARGET_KMSG)) { + + k = write_to_kmsg(level, error, file, line, func, object_field, object, buffer); + if (k < 0) { + log_close_kmsg(); + log_open_console(); + } + } + + if (k <= 0) + (void) write_to_console(level, error, file, line, func, object_field, object, buffer); + + buffer = e; + } while (buffer); + + return -error; +} + +int log_dump_internal( + int level, + int error, + const char *file, + int line, + const char *func, + char *buffer) { + + PROTECT_ERRNO; + + /* This modifies the buffer... */ + + if (error < 0) + error = -error; + + if (_likely_(LOG_PRI(level) > log_max_level)) + return -error; + + return log_dispatch(level, error, file, line, func, NULL, NULL, buffer); +} + +int log_internalv( + int level, + int error, + const char *file, + int line, + const char *func, + const char *format, + va_list ap) { + + PROTECT_ERRNO; + char buffer[LINE_MAX]; + + if (error < 0) + error = -error; + + if (_likely_(LOG_PRI(level) > log_max_level)) + return -error; + + /* Make sure that %m maps to the specified error */ + if (error != 0) + errno = error; + + vsnprintf(buffer, sizeof(buffer), format, ap); + + return log_dispatch(level, error, file, line, func, NULL, NULL, buffer); +} + +int log_internal( + int level, + int error, + const char *file, + int line, + const char *func, + const char *format, ...) { + + va_list ap; + int r; + + va_start(ap, format); + r = log_internalv(level, error, file, line, func, format, ap); + va_end(ap); + + return r; +} + +int log_object_internalv( + int level, + int error, + const char *file, + int line, + const char *func, + const char *object_field, + const char *object, + const char *format, + va_list ap) { + + PROTECT_ERRNO; + char *buffer, *b; + size_t l; + + if (error < 0) + error = -error; + + if (_likely_(LOG_PRI(level) > log_max_level)) + return -error; + + /* Make sure that %m maps to the specified error */ + if (error != 0) + errno = error; + + /* Prepend the object name before the message */ + if (object) { + size_t n; + + n = strlen(object); + l = n + 2 + LINE_MAX; + + buffer = newa(char, l); + b = stpcpy(stpcpy(buffer, object), ": "); + } else { + l = LINE_MAX; + b = buffer = newa(char, l); + } + + vsnprintf(b, l, format, ap); + + return log_dispatch(level, error, file, line, func, object_field, object, buffer); +} + +int log_object_internal( + int level, + int error, + const char *file, + int line, + const char *func, + const char *object_field, + const char *object, + const char *format, ...) { + + va_list ap; + int r; + + va_start(ap, format); + r = log_object_internalv(level, error, file, line, func, object_field, object, format, ap); + va_end(ap); + + return r; +} + +static void log_assert( + int level, + const char *text, + const char *file, + int line, + const char *func, + const char *format) { + + static char buffer[LINE_MAX]; + + if (_likely_(LOG_PRI(level) > log_max_level)) + return; + + DISABLE_WARNING_FORMAT_NONLITERAL; + xsprintf(buffer, format, text, file, line, func); + REENABLE_WARNING; + + log_abort_msg = buffer; + + log_dispatch(level, 0, file, line, func, NULL, NULL, buffer); +} + +noreturn void log_assert_failed(const char *text, const char *file, int line, const char *func) { + log_assert(LOG_CRIT, text, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Aborting."); + abort(); +} + +noreturn void log_assert_failed_unreachable(const char *text, const char *file, int line, const char *func) { + log_assert(LOG_CRIT, text, file, line, func, "Code should not be reached '%s' at %s:%u, function %s(). Aborting."); + abort(); +} + +void log_assert_failed_return(const char *text, const char *file, int line, const char *func) { + PROTECT_ERRNO; + log_assert(LOG_DEBUG, text, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Ignoring."); +} + +int log_oom_internal(const char *file, int line, const char *func) { + log_internal(LOG_ERR, ENOMEM, file, line, func, "Out of memory."); + return -ENOMEM; +} + +int log_format_iovec( + struct iovec *iovec, + unsigned iovec_len, + unsigned *n, + bool newline_separator, + int error, + const char *format, + va_list ap) { + + static const char nl = '\n'; + + while (format && *n + 1 < iovec_len) { + va_list aq; + char *m; + int r; + + /* We need to copy the va_list structure, + * since vasprintf() leaves it afterwards at + * an undefined location */ + + if (error != 0) + errno = error; + + va_copy(aq, ap); + r = vasprintf(&m, format, aq); + va_end(aq); + if (r < 0) + return -EINVAL; + + /* Now, jump enough ahead, so that we point to + * the next format string */ + VA_FORMAT_ADVANCE(format, ap); + + IOVEC_SET_STRING(iovec[(*n)++], m); + + if (newline_separator) { + iovec[*n].iov_base = (char*) &nl; + iovec[*n].iov_len = 1; + (*n)++; + } + + format = va_arg(ap, char *); + } + return 0; +} + +int log_struct_internal( + int level, + int error, + const char *file, + int line, + const char *func, + const char *format, ...) { + + char buf[LINE_MAX]; + bool found = false; + PROTECT_ERRNO; + va_list ap; + + if (error < 0) + error = -error; + + if (_likely_(LOG_PRI(level) > log_max_level)) + return -error; + + if (log_target == LOG_TARGET_NULL) + return -error; + + if ((level & LOG_FACMASK) == 0) + level = log_facility | LOG_PRI(level); + + if ((log_target == LOG_TARGET_AUTO || + log_target == LOG_TARGET_JOURNAL_OR_KMSG || + log_target == LOG_TARGET_JOURNAL) && + journal_fd >= 0) { + char header[LINE_MAX]; + struct iovec iovec[17] = {}; + unsigned n = 0, i; + int r; + struct msghdr mh = { + .msg_iov = iovec, + }; + bool fallback = false; + + /* If the journal is available do structured logging */ + log_do_header(header, sizeof(header), level, error, file, line, func, NULL, NULL); + IOVEC_SET_STRING(iovec[n++], header); + + va_start(ap, format); + r = log_format_iovec(iovec, ELEMENTSOF(iovec), &n, true, error, format, ap); + if (r < 0) + fallback = true; + else { + mh.msg_iovlen = n; + (void) sendmsg(journal_fd, &mh, MSG_NOSIGNAL); + } + + va_end(ap); + for (i = 1; i < n; i += 2) + free(iovec[i].iov_base); + + if (!fallback) + return -error; + } + + /* Fallback if journal logging is not available or didn't work. */ + + va_start(ap, format); + while (format) { + va_list aq; + + if (error != 0) + errno = error; + + va_copy(aq, ap); + vsnprintf(buf, sizeof(buf), format, aq); + va_end(aq); + + if (startswith(buf, "MESSAGE=")) { + found = true; + break; + } + + VA_FORMAT_ADVANCE(format, ap); + + format = va_arg(ap, char *); + } + va_end(ap); + + if (!found) + return -error; + + return log_dispatch(level, error, file, line, func, NULL, NULL, buf + 8); +} + +int log_set_target_from_string(const char *e) { + LogTarget t; + + t = log_target_from_string(e); + if (t < 0) + return -EINVAL; + + log_set_target(t); + return 0; +} + +int log_set_max_level_from_string(const char *e) { + int t; + + t = log_level_from_string(e); + if (t < 0) + return -EINVAL; + + log_set_max_level(t); + return 0; +} + +static int parse_proc_cmdline_item(const char *key, const char *value) { + + /* + * The systemd.log_xyz= settings are parsed by all tools, and + * so is "debug". + * + * However, "quiet" is only parsed by PID 1, and only turns of + * status output to /dev/console, but does not alter the log + * level. + */ + + if (streq(key, "debug") && !value) + log_set_max_level(LOG_DEBUG); + + else if (streq(key, "systemd.log_target") && value) { + + if (log_set_target_from_string(value) < 0) + log_warning("Failed to parse log target '%s'. Ignoring.", value); + + } else if (streq(key, "systemd.log_level") && value) { + + if (log_set_max_level_from_string(value) < 0) + log_warning("Failed to parse log level '%s'. Ignoring.", value); + + } else if (streq(key, "systemd.log_color") && value) { + + if (log_show_color_from_string(value) < 0) + log_warning("Failed to parse log color setting '%s'. Ignoring.", value); + + } else if (streq(key, "systemd.log_location") && value) { + + if (log_show_location_from_string(value) < 0) + log_warning("Failed to parse log location setting '%s'. Ignoring.", value); + } + + return 0; +} + +void log_parse_environment(void) { + const char *e; + + if (get_ctty_devnr(0, NULL) < 0) + /* Only try to read the command line in daemons. + We assume that anything that has a controlling + tty is user stuff. */ + (void) parse_proc_cmdline(parse_proc_cmdline_item); + + e = secure_getenv("SYSTEMD_LOG_TARGET"); + if (e && log_set_target_from_string(e) < 0) + log_warning("Failed to parse log target '%s'. Ignoring.", e); + + e = secure_getenv("SYSTEMD_LOG_LEVEL"); + if (e && log_set_max_level_from_string(e) < 0) + log_warning("Failed to parse log level '%s'. Ignoring.", e); + + e = secure_getenv("SYSTEMD_LOG_COLOR"); + if (e && log_show_color_from_string(e) < 0) + log_warning("Failed to parse bool '%s'. Ignoring.", e); + + e = secure_getenv("SYSTEMD_LOG_LOCATION"); + if (e && log_show_location_from_string(e) < 0) + log_warning("Failed to parse bool '%s'. Ignoring.", e); +} + +LogTarget log_get_target(void) { + return log_target; +} + +int log_get_max_level(void) { + return log_max_level; +} + +void log_show_color(bool b) { + show_color = b; +} + +bool log_get_show_color(void) { + return show_color; +} + +void log_show_location(bool b) { + show_location = b; +} + +bool log_get_show_location(void) { + return show_location; +} + +int log_show_color_from_string(const char *e) { + int t; + + t = parse_boolean(e); + if (t < 0) + return t; + + log_show_color(t); + return 0; +} + +int log_show_location_from_string(const char *e) { + int t; + + t = parse_boolean(e); + if (t < 0) + return t; + + log_show_location(t); + return 0; +} + +bool log_on_console(void) { + if (log_target == LOG_TARGET_CONSOLE || + log_target == LOG_TARGET_CONSOLE_PREFIXED) + return true; + + return syslog_fd < 0 && kmsg_fd < 0 && journal_fd < 0; +} + +static const char *const log_target_table[_LOG_TARGET_MAX] = { + [LOG_TARGET_CONSOLE] = "console", + [LOG_TARGET_CONSOLE_PREFIXED] = "console-prefixed", + [LOG_TARGET_KMSG] = "kmsg", + [LOG_TARGET_JOURNAL] = "journal", + [LOG_TARGET_JOURNAL_OR_KMSG] = "journal-or-kmsg", + [LOG_TARGET_SYSLOG] = "syslog", + [LOG_TARGET_SYSLOG_OR_KMSG] = "syslog-or-kmsg", + [LOG_TARGET_AUTO] = "auto", + [LOG_TARGET_SAFE] = "safe", + [LOG_TARGET_NULL] = "null" +}; + +DEFINE_STRING_TABLE_LOOKUP(log_target, LogTarget); + +void log_received_signal(int level, const struct signalfd_siginfo *si) { + if (si->ssi_pid > 0) { + _cleanup_free_ char *p = NULL; + + get_process_comm(si->ssi_pid, &p); + + log_full(level, + "Received SIG%s from PID %"PRIu32" (%s).", + signal_to_string(si->ssi_signo), + si->ssi_pid, strna(p)); + } else + log_full(level, + "Received SIG%s.", + signal_to_string(si->ssi_signo)); + +} + +void log_set_upgrade_syslog_to_journal(bool b) { + upgrade_syslog_to_journal = b; +} + +int log_syntax_internal( + const char *unit, + int level, + const char *config_file, + unsigned config_line, + int error, + const char *file, + int line, + const char *func, + const char *format, ...) { + + PROTECT_ERRNO; + char buffer[LINE_MAX]; + int r; + va_list ap; + + if (error < 0) + error = -error; + + if (_likely_(LOG_PRI(level) > log_max_level)) + return -error; + + if (log_target == LOG_TARGET_NULL) + return -error; + + if (error != 0) + errno = error; + + va_start(ap, format); + vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + if (unit) + r = log_struct_internal( + level, error, + file, line, func, + getpid() == 1 ? "UNIT=%s" : "USER_UNIT=%s", unit, + LOG_MESSAGE_ID(SD_MESSAGE_INVALID_CONFIGURATION), + "CONFIG_FILE=%s", config_file, + "CONFIG_LINE=%u", config_line, + LOG_MESSAGE("[%s:%u] %s", config_file, config_line, buffer), + NULL); + else + r = log_struct_internal( + level, error, + file, line, func, + LOG_MESSAGE_ID(SD_MESSAGE_INVALID_CONFIGURATION), + "CONFIG_FILE=%s", config_file, + "CONFIG_LINE=%u", config_line, + LOG_MESSAGE("[%s:%u] %s", config_file, config_line, buffer), + NULL); + + return r; +} diff --git a/src/libbasic/log.h b/src/libbasic/log.h new file mode 100644 index 0000000000..d2a22b5829 --- /dev/null +++ b/src/libbasic/log.h @@ -0,0 +1,249 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include + +#include + +#include "macro.h" + +typedef enum LogTarget{ + LOG_TARGET_CONSOLE, + LOG_TARGET_CONSOLE_PREFIXED, + LOG_TARGET_KMSG, + LOG_TARGET_JOURNAL, + LOG_TARGET_JOURNAL_OR_KMSG, + LOG_TARGET_SYSLOG, + LOG_TARGET_SYSLOG_OR_KMSG, + LOG_TARGET_AUTO, /* console if stderr is tty, JOURNAL_OR_KMSG otherwise */ + LOG_TARGET_SAFE, /* console if stderr is tty, KMSG otherwise */ + LOG_TARGET_NULL, + _LOG_TARGET_MAX, + _LOG_TARGET_INVALID = -1 +} LogTarget; + +void log_set_target(LogTarget target); +void log_set_max_level(int level); +void log_set_facility(int facility); + +int log_set_target_from_string(const char *e); +int log_set_max_level_from_string(const char *e); + +void log_show_color(bool b); +bool log_get_show_color(void) _pure_; +void log_show_location(bool b); +bool log_get_show_location(void) _pure_; + +int log_show_color_from_string(const char *e); +int log_show_location_from_string(const char *e); + +LogTarget log_get_target(void) _pure_; +int log_get_max_level(void) _pure_; + +int log_open(void); +void log_close(void); +void log_forget_fds(void); + +void log_close_syslog(void); +void log_close_journal(void); +void log_close_kmsg(void); +void log_close_console(void); + +void log_parse_environment(void); + +int log_internal( + int level, + int error, + const char *file, + int line, + const char *func, + const char *format, ...) _printf_(6,7); + +int log_internalv( + int level, + int error, + const char *file, + int line, + const char *func, + const char *format, + va_list ap) _printf_(6,0); + +int log_object_internal( + int level, + int error, + const char *file, + int line, + const char *func, + const char *object_field, + const char *object, + const char *format, ...) _printf_(8,9); + +int log_object_internalv( + int level, + int error, + const char*file, + int line, + const char *func, + const char *object_field, + const char *object, + const char *format, + va_list ap) _printf_(8,0); + +int log_struct_internal( + int level, + int error, + const char *file, + int line, + const char *func, + const char *format, ...) _printf_(6,0) _sentinel_; + +int log_oom_internal( + const char *file, + int line, + const char *func); + +int log_format_iovec( + struct iovec *iovec, + unsigned iovec_len, + unsigned *n, + bool newline_separator, + int error, + const char *format, + va_list ap); + +/* This modifies the buffer passed! */ +int log_dump_internal( + int level, + int error, + const char *file, + int line, + const char *func, + char *buffer); + +/* Logging for various assertions */ +noreturn void log_assert_failed( + const char *text, + const char *file, + int line, + const char *func); + +noreturn void log_assert_failed_unreachable( + const char *text, + const char *file, + int line, + const char *func); + +void log_assert_failed_return( + const char *text, + const char *file, + int line, + const char *func); + +/* Logging with level */ +#define log_full_errno(level, error, ...) \ + ({ \ + int _level = (level), _e = (error); \ + (log_get_max_level() >= LOG_PRI(_level)) \ + ? log_internal(_level, _e, __FILE__, __LINE__, __func__, __VA_ARGS__) \ + : -abs(_e); \ + }) + +#define log_full(level, ...) log_full_errno(level, 0, __VA_ARGS__) + +/* Normal logging */ +#define log_debug(...) log_full(LOG_DEBUG, __VA_ARGS__) +#define log_info(...) log_full(LOG_INFO, __VA_ARGS__) +#define log_notice(...) log_full(LOG_NOTICE, __VA_ARGS__) +#define log_warning(...) log_full(LOG_WARNING, __VA_ARGS__) +#define log_error(...) log_full(LOG_ERR, __VA_ARGS__) +#define log_emergency(...) log_full(getpid() == 1 ? LOG_EMERG : LOG_ERR, __VA_ARGS__) + +/* Logging triggered by an errno-like error */ +#define log_debug_errno(error, ...) log_full_errno(LOG_DEBUG, error, __VA_ARGS__) +#define log_info_errno(error, ...) log_full_errno(LOG_INFO, error, __VA_ARGS__) +#define log_notice_errno(error, ...) log_full_errno(LOG_NOTICE, error, __VA_ARGS__) +#define log_warning_errno(error, ...) log_full_errno(LOG_WARNING, error, __VA_ARGS__) +#define log_error_errno(error, ...) log_full_errno(LOG_ERR, error, __VA_ARGS__) +#define log_emergency_errno(error, ...) log_full_errno(getpid() == 1 ? LOG_EMERG : LOG_ERR, error, __VA_ARGS__) + +#ifdef LOG_TRACE +# define log_trace(...) log_debug(__VA_ARGS__) +#else +# define log_trace(...) do {} while (0) +#endif + +/* Structured logging */ +#define log_struct(level, ...) log_struct_internal(level, 0, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define log_struct_errno(level, error, ...) log_struct_internal(level, error, __FILE__, __LINE__, __func__, __VA_ARGS__) + +/* This modifies the buffer passed! */ +#define log_dump(level, buffer) log_dump_internal(level, 0, __FILE__, __LINE__, __func__, buffer) + +#define log_oom() log_oom_internal(__FILE__, __LINE__, __func__) + +bool log_on_console(void) _pure_; + +const char *log_target_to_string(LogTarget target) _const_; +LogTarget log_target_from_string(const char *s) _pure_; + +/* Helpers to prepare various fields for structured logging */ +#define LOG_MESSAGE(fmt, ...) "MESSAGE=" fmt, ##__VA_ARGS__ +#define LOG_MESSAGE_ID(x) "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(x) + +void log_received_signal(int level, const struct signalfd_siginfo *si); + +void log_set_upgrade_syslog_to_journal(bool b); + +int log_syntax_internal( + const char *unit, + int level, + const char *config_file, + unsigned config_line, + int error, + const char *file, + int line, + const char *func, + const char *format, ...) _printf_(9, 10); + +#define log_syntax(unit, level, config_file, config_line, error, ...) \ + ({ \ + int _level = (level), _e = (error); \ + (log_get_max_level() >= LOG_PRI(_level)) \ + ? log_syntax_internal(unit, _level, config_file, config_line, _e, __FILE__, __LINE__, __func__, __VA_ARGS__) \ + : -abs(_e); \ + }) + +#define log_syntax_invalid_utf8(unit, level, config_file, config_line, rvalue) \ + ({ \ + int _level = (level); \ + if (log_get_max_level() >= LOG_PRI(_level)) { \ + _cleanup_free_ char *_p = NULL; \ + _p = utf8_escape_invalid(rvalue); \ + log_syntax_internal(unit, _level, config_file, config_line, 0, __FILE__, __LINE__, __func__, \ + "String is not UTF-8 clean, ignoring assignment: %s", strna(_p)); \ + } \ + }) diff --git a/src/libbasic/login-util.c b/src/libbasic/login-util.c new file mode 100644 index 0000000000..339e94f12d --- /dev/null +++ b/src/libbasic/login-util.c @@ -0,0 +1,31 @@ +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + 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 "login-util.h" +#include "string-util.h" + +bool session_id_valid(const char *id) { + + if (isempty(id)) + return false; + + return id[strspn(id, LETTERS DIGITS)] == '\0'; +} diff --git a/src/libbasic/login-util.h b/src/libbasic/login-util.h new file mode 100644 index 0000000000..b01ee25c88 --- /dev/null +++ b/src/libbasic/login-util.h @@ -0,0 +1,29 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + 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 + +bool session_id_valid(const char *id); + +static inline bool logind_running(void) { + return access("/run/systemd/seats/", F_OK) >= 0; +} diff --git a/src/libbasic/macro.h b/src/libbasic/macro.h new file mode 100644 index 0000000000..e41aa4260f --- /dev/null +++ b/src/libbasic/macro.h @@ -0,0 +1,406 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include + +#define _printf_(a,b) __attribute__ ((format (printf, a, b))) +#ifdef __clang__ +# define _alloc_(...) +#else +# define _alloc_(...) __attribute__ ((alloc_size(__VA_ARGS__))) +#endif +#define _sentinel_ __attribute__ ((sentinel)) +#define _unused_ __attribute__ ((unused)) +#define _destructor_ __attribute__ ((destructor)) +#define _pure_ __attribute__ ((pure)) +#define _const_ __attribute__ ((const)) +#define _deprecated_ __attribute__ ((deprecated)) +#define _packed_ __attribute__ ((packed)) +#define _malloc_ __attribute__ ((malloc)) +#define _weak_ __attribute__ ((weak)) +#define _likely_(x) (__builtin_expect(!!(x),1)) +#define _unlikely_(x) (__builtin_expect(!!(x),0)) +#define _public_ __attribute__ ((visibility("default"))) +#define _hidden_ __attribute__ ((visibility("hidden"))) +#define _weakref_(x) __attribute__((weakref(#x))) +#define _alignas_(x) __attribute__((aligned(__alignof(x)))) +#define _cleanup_(x) __attribute__((cleanup(x))) + +/* Temporarily disable some warnings */ +#define DISABLE_WARNING_DECLARATION_AFTER_STATEMENT \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wdeclaration-after-statement\"") + +#define DISABLE_WARNING_FORMAT_NONLITERAL \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wformat-nonliteral\"") + +#define DISABLE_WARNING_MISSING_PROTOTYPES \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wmissing-prototypes\"") + +#define DISABLE_WARNING_NONNULL \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wnonnull\"") + +#define DISABLE_WARNING_SHADOW \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wshadow\"") + +#define DISABLE_WARNING_INCOMPATIBLE_POINTER_TYPES \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wincompatible-pointer-types\"") + +#define REENABLE_WARNING \ + _Pragma("GCC diagnostic pop") + +/* automake test harness */ +#define EXIT_TEST_SKIP 77 + +#define XSTRINGIFY(x) #x +#define STRINGIFY(x) XSTRINGIFY(x) + +#define XCONCATENATE(x, y) x ## y +#define CONCATENATE(x, y) XCONCATENATE(x, y) + +#define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq)) +#define UNIQ __COUNTER__ + +/* Rounds up */ + +#define ALIGN4(l) (((l) + 3) & ~3) +#define ALIGN8(l) (((l) + 7) & ~7) + +#if __SIZEOF_POINTER__ == 8 +#define ALIGN(l) ALIGN8(l) +#elif __SIZEOF_POINTER__ == 4 +#define ALIGN(l) ALIGN4(l) +#else +#error "Wut? Pointers are neither 4 nor 8 bytes long?" +#endif + +#define ALIGN_PTR(p) ((void*) ALIGN((unsigned long) (p))) +#define ALIGN4_PTR(p) ((void*) ALIGN4((unsigned long) (p))) +#define ALIGN8_PTR(p) ((void*) ALIGN8((unsigned long) (p))) + +static inline size_t ALIGN_TO(size_t l, size_t ali) { + return ((l + ali - 1) & ~(ali - 1)); +} + +#define ALIGN_TO_PTR(p, ali) ((void*) ALIGN_TO((unsigned long) (p), (ali))) + +/* align to next higher power-of-2 (except for: 0 => 0, overflow => 0) */ +static inline unsigned long ALIGN_POWER2(unsigned long u) { + /* clz(0) is undefined */ + if (u == 1) + return 1; + + /* left-shift overflow is undefined */ + if (__builtin_clzl(u - 1UL) < 1) + return 0; + + return 1UL << (sizeof(u) * 8 - __builtin_clzl(u - 1UL)); +} + +#define ELEMENTSOF(x) \ + __extension__ (__builtin_choose_expr( \ + !__builtin_types_compatible_p(typeof(x), typeof(&*(x))), \ + sizeof(x)/sizeof((x)[0]), \ + (void)0)) +/* + * container_of - cast a member of a structure out to the containing structure + * @ptr: the pointer to the member. + * @type: the type of the container struct this is embedded in. + * @member: the name of the member within the struct. + */ +#define container_of(ptr, type, member) __container_of(UNIQ, (ptr), type, member) +#define __container_of(uniq, ptr, type, member) \ + __extension__ ({ \ + const typeof( ((type*)0)->member ) *UNIQ_T(A, uniq) = (ptr); \ + (type*)( (char *)UNIQ_T(A, uniq) - offsetof(type,member) ); \ + }) + +#undef MAX +#define MAX(a, b) __MAX(UNIQ, (a), UNIQ, (b)) +#define __MAX(aq, a, bq, b) \ + __extension__ ({ \ + const typeof(a) UNIQ_T(A, aq) = (a); \ + const typeof(b) UNIQ_T(B, bq) = (b); \ + UNIQ_T(A,aq) > UNIQ_T(B,bq) ? UNIQ_T(A,aq) : UNIQ_T(B,bq); \ + }) + +/* evaluates to (void) if _A or _B are not constant or of different types */ +#define CONST_MAX(_A, _B) \ + __extension__ (__builtin_choose_expr( \ + __builtin_constant_p(_A) && \ + __builtin_constant_p(_B) && \ + __builtin_types_compatible_p(typeof(_A), typeof(_B)), \ + ((_A) > (_B)) ? (_A) : (_B), \ + (void)0)) + +/* takes two types and returns the size of the larger one */ +#define MAXSIZE(A, B) (sizeof(union _packed_ { typeof(A) a; typeof(B) b; })) + +#define MAX3(x,y,z) \ + __extension__ ({ \ + const typeof(x) _c = MAX(x,y); \ + MAX(_c, z); \ + }) + +#undef MIN +#define MIN(a, b) __MIN(UNIQ, (a), UNIQ, (b)) +#define __MIN(aq, a, bq, b) \ + __extension__ ({ \ + const typeof(a) UNIQ_T(A, aq) = (a); \ + const typeof(b) UNIQ_T(B, bq) = (b); \ + UNIQ_T(A,aq) < UNIQ_T(B,bq) ? UNIQ_T(A,aq) : UNIQ_T(B,bq); \ + }) + +#define MIN3(x,y,z) \ + __extension__ ({ \ + const typeof(x) _c = MIN(x,y); \ + MIN(_c, z); \ + }) + +#define LESS_BY(a, b) __LESS_BY(UNIQ, (a), UNIQ, (b)) +#define __LESS_BY(aq, a, bq, b) \ + __extension__ ({ \ + const typeof(a) UNIQ_T(A, aq) = (a); \ + const typeof(b) UNIQ_T(B, bq) = (b); \ + UNIQ_T(A,aq) > UNIQ_T(B,bq) ? UNIQ_T(A,aq) - UNIQ_T(B,bq) : 0; \ + }) + +#undef CLAMP +#define CLAMP(x, low, high) __CLAMP(UNIQ, (x), UNIQ, (low), UNIQ, (high)) +#define __CLAMP(xq, x, lowq, low, highq, high) \ + __extension__ ({ \ + const typeof(x) UNIQ_T(X,xq) = (x); \ + const typeof(low) UNIQ_T(LOW,lowq) = (low); \ + const typeof(high) UNIQ_T(HIGH,highq) = (high); \ + UNIQ_T(X,xq) > UNIQ_T(HIGH,highq) ? \ + UNIQ_T(HIGH,highq) : \ + UNIQ_T(X,xq) < UNIQ_T(LOW,lowq) ? \ + UNIQ_T(LOW,lowq) : \ + UNIQ_T(X,xq); \ + }) + +/* [(x + y - 1) / y] suffers from an integer overflow, even though the + * computation should be possible in the given type. Therefore, we use + * [x / y + !!(x % y)]. Note that on "Real CPUs" a division returns both the + * quotient and the remainder, so both should be equally fast. */ +#define DIV_ROUND_UP(_x, _y) \ + __extension__ ({ \ + const typeof(_x) __x = (_x); \ + const typeof(_y) __y = (_y); \ + (__x / __y + !!(__x % __y)); \ + }) + +#define assert_message_se(expr, message) \ + do { \ + if (_unlikely_(!(expr))) \ + log_assert_failed(message, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ + } while (false) + +#define assert_se(expr) assert_message_se(expr, #expr) + +/* We override the glibc assert() here. */ +#undef assert +#ifdef NDEBUG +#define assert(expr) do {} while (false) +#else +#define assert(expr) assert_message_se(expr, #expr) +#endif + +#define assert_not_reached(t) \ + do { \ + log_assert_failed_unreachable(t, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ + } while (false) + +#if defined(static_assert) +/* static_assert() is sometimes defined in a way that trips up + * -Wdeclaration-after-statement, hence let's temporarily turn off + * this warning around it. */ +#define assert_cc(expr) \ + DISABLE_WARNING_DECLARATION_AFTER_STATEMENT; \ + static_assert(expr, #expr); \ + REENABLE_WARNING +#else +#define assert_cc(expr) \ + DISABLE_WARNING_DECLARATION_AFTER_STATEMENT; \ + struct CONCATENATE(_assert_struct_, __COUNTER__) { \ + char x[(expr) ? 0 : -1]; \ + }; \ + REENABLE_WARNING +#endif + +#define assert_log(expr, message) ((_likely_(expr)) \ + ? (true) \ + : (log_assert_failed_return(message, __FILE__, __LINE__, __PRETTY_FUNCTION__), false)) + +#define assert_return(expr, r) \ + do { \ + if (!assert_log(expr, #expr)) \ + return (r); \ + } while (false) + +#define assert_return_errno(expr, r, err) \ + do { \ + if (!assert_log(expr, #expr)) { \ + errno = err; \ + return (r); \ + } \ + } while (false) + +#define PTR_TO_INT(p) ((int) ((intptr_t) (p))) +#define INT_TO_PTR(u) ((void *) ((intptr_t) (u))) +#define PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p))) +#define UINT_TO_PTR(u) ((void *) ((uintptr_t) (u))) + +#define PTR_TO_LONG(p) ((long) ((intptr_t) (p))) +#define LONG_TO_PTR(u) ((void *) ((intptr_t) (u))) +#define PTR_TO_ULONG(p) ((unsigned long) ((uintptr_t) (p))) +#define ULONG_TO_PTR(u) ((void *) ((uintptr_t) (u))) + +#define PTR_TO_INT32(p) ((int32_t) ((intptr_t) (p))) +#define INT32_TO_PTR(u) ((void *) ((intptr_t) (u))) +#define PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p))) +#define UINT32_TO_PTR(u) ((void *) ((uintptr_t) (u))) + +#define PTR_TO_INT64(p) ((int64_t) ((intptr_t) (p))) +#define INT64_TO_PTR(u) ((void *) ((intptr_t) (u))) +#define PTR_TO_UINT64(p) ((uint64_t) ((uintptr_t) (p))) +#define UINT64_TO_PTR(u) ((void *) ((uintptr_t) (u))) + +#define PTR_TO_SIZE(p) ((size_t) ((uintptr_t) (p))) +#define SIZE_TO_PTR(u) ((void *) ((uintptr_t) (u))) + +#define CHAR_TO_STR(x) ((char[2]) { x, 0 }) + +#define char_array_0(x) x[sizeof(x)-1] = 0; + +/* Returns the number of chars needed to format variables of the + * specified type as a decimal string. Adds in extra space for a + * negative '-' prefix (hence works correctly on signed + * types). Includes space for the trailing NUL. */ +#define DECIMAL_STR_MAX(type) \ + (2+(sizeof(type) <= 1 ? 3 : \ + sizeof(type) <= 2 ? 5 : \ + sizeof(type) <= 4 ? 10 : \ + sizeof(type) <= 8 ? 20 : sizeof(int[-2*(sizeof(type) > 8)]))) + +#define DECIMAL_STR_WIDTH(x) \ + ({ \ + typeof(x) _x_ = (x); \ + unsigned ans = 1; \ + while (_x_ /= 10) \ + ans++; \ + ans; \ + }) + +#define SET_FLAG(v, flag, b) \ + (v) = (b) ? ((v) | (flag)) : ((v) & ~(flag)) + +#define CASE_F(X) case X: +#define CASE_F_1(CASE, X) CASE_F(X) +#define CASE_F_2(CASE, X, ...) CASE(X) CASE_F_1(CASE, __VA_ARGS__) +#define CASE_F_3(CASE, X, ...) CASE(X) CASE_F_2(CASE, __VA_ARGS__) +#define CASE_F_4(CASE, X, ...) CASE(X) CASE_F_3(CASE, __VA_ARGS__) +#define CASE_F_5(CASE, X, ...) CASE(X) CASE_F_4(CASE, __VA_ARGS__) +#define CASE_F_6(CASE, X, ...) CASE(X) CASE_F_5(CASE, __VA_ARGS__) +#define CASE_F_7(CASE, X, ...) CASE(X) CASE_F_6(CASE, __VA_ARGS__) +#define CASE_F_8(CASE, X, ...) CASE(X) CASE_F_7(CASE, __VA_ARGS__) +#define CASE_F_9(CASE, X, ...) CASE(X) CASE_F_8(CASE, __VA_ARGS__) +#define CASE_F_10(CASE, X, ...) CASE(X) CASE_F_9(CASE, __VA_ARGS__) +#define CASE_F_11(CASE, X, ...) CASE(X) CASE_F_10(CASE, __VA_ARGS__) +#define CASE_F_12(CASE, X, ...) CASE(X) CASE_F_11(CASE, __VA_ARGS__) +#define CASE_F_13(CASE, X, ...) CASE(X) CASE_F_12(CASE, __VA_ARGS__) +#define CASE_F_14(CASE, X, ...) CASE(X) CASE_F_13(CASE, __VA_ARGS__) +#define CASE_F_15(CASE, X, ...) CASE(X) CASE_F_14(CASE, __VA_ARGS__) +#define CASE_F_16(CASE, X, ...) CASE(X) CASE_F_15(CASE, __VA_ARGS__) +#define CASE_F_17(CASE, X, ...) CASE(X) CASE_F_16(CASE, __VA_ARGS__) +#define CASE_F_18(CASE, X, ...) CASE(X) CASE_F_17(CASE, __VA_ARGS__) +#define CASE_F_19(CASE, X, ...) CASE(X) CASE_F_18(CASE, __VA_ARGS__) +#define CASE_F_20(CASE, X, ...) CASE(X) CASE_F_19(CASE, __VA_ARGS__) + +#define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,NAME,...) NAME +#define FOR_EACH_MAKE_CASE(...) \ + GET_CASE_F(__VA_ARGS__,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12,CASE_F_11, \ + CASE_F_10,CASE_F_9,CASE_F_8,CASE_F_7,CASE_F_6,CASE_F_5,CASE_F_4,CASE_F_3,CASE_F_2,CASE_F_1) \ + (CASE_F,__VA_ARGS__) + +#define IN_SET(x, ...) \ + ({ \ + bool _found = false; \ + /* If the build breaks in the line below, you need to extend the case macros */ \ + static _unused_ char _static_assert__macros_need_to_be_extended[20 - sizeof((int[]){__VA_ARGS__})/sizeof(int)]; \ + switch(x) { \ + FOR_EACH_MAKE_CASE(__VA_ARGS__) \ + _found = true; \ + break; \ + default: \ + break; \ + } \ + _found; \ + }) + +#define SWAP_TWO(x, y) do { \ + typeof(x) _t = (x); \ + (x) = (y); \ + (y) = (_t); \ + } while (false) + +/* Define C11 thread_local attribute even on older gcc compiler + * version */ +#ifndef thread_local +/* + * Don't break on glibc < 2.16 that doesn't define __STDC_NO_THREADS__ + * see http://gcc.gnu.org/bugzilla/show_bug.cgi?id=53769 + */ +#if __STDC_VERSION__ >= 201112L && !(defined(__STDC_NO_THREADS__) || (defined(__GNU_LIBRARY__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16)) +#define thread_local _Thread_local +#else +#define thread_local __thread +#endif +#endif + +/* Define C11 noreturn without and even on older gcc + * compiler versions */ +#ifndef noreturn +#if __STDC_VERSION__ >= 201112L +#define noreturn _Noreturn +#else +#define noreturn __attribute__((noreturn)) +#endif +#endif + +#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \ + static inline void func##p(type *p) { \ + if (*p) \ + func(*p); \ + } \ + struct __useless_struct_to_allow_trailing_semicolon__ + +#include "log.h" diff --git a/src/libbasic/memfd-util.c b/src/libbasic/memfd-util.c new file mode 100644 index 0000000000..8c8cc78ebf --- /dev/null +++ b/src/libbasic/memfd-util.c @@ -0,0 +1,174 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 +#include +#include +#ifdef HAVE_LINUX_MEMFD_H +#include +#endif +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "macro.h" +#include "memfd-util.h" +#include "missing.h" +#include "string-util.h" +#include "utf8.h" + +int memfd_new(const char *name) { + _cleanup_free_ char *g = NULL; + int fd; + + if (!name) { + char pr[17] = {}; + + /* If no name is specified we generate one. We include + * a hint indicating our library implementation, and + * add the thread name to it */ + + assert_se(prctl(PR_GET_NAME, (unsigned long) pr) >= 0); + + if (isempty(pr)) + name = "sd"; + else { + _cleanup_free_ char *e = NULL; + + e = utf8_escape_invalid(pr); + if (!e) + return -ENOMEM; + + g = strappend("sd-", e); + if (!g) + return -ENOMEM; + + name = g; + } + } + + fd = memfd_create(name, MFD_ALLOW_SEALING | MFD_CLOEXEC); + if (fd < 0) + return -errno; + + return fd; +} + +int memfd_map(int fd, uint64_t offset, size_t size, void **p) { + void *q; + int sealed; + + assert(fd >= 0); + assert(size > 0); + assert(p); + + sealed = memfd_get_sealed(fd); + if (sealed < 0) + return sealed; + + if (sealed) + q = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, offset); + else + q = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset); + + if (q == MAP_FAILED) + return -errno; + + *p = q; + return 0; +} + +int memfd_set_sealed(int fd) { + int r; + + assert(fd >= 0); + + r = fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL); + if (r < 0) + return -errno; + + return 0; +} + +int memfd_get_sealed(int fd) { + int r; + + assert(fd >= 0); + + r = fcntl(fd, F_GET_SEALS); + if (r < 0) + return -errno; + + return r == (F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL); +} + +int memfd_get_size(int fd, uint64_t *sz) { + struct stat stat; + int r; + + assert(fd >= 0); + assert(sz); + + r = fstat(fd, &stat); + if (r < 0) + return -errno; + + *sz = stat.st_size; + return 0; +} + +int memfd_set_size(int fd, uint64_t sz) { + int r; + + assert(fd >= 0); + + r = ftruncate(fd, sz); + if (r < 0) + return -errno; + + return 0; +} + +int memfd_new_and_map(const char *name, size_t sz, void **p) { + _cleanup_close_ int fd = -1; + int r; + + assert(sz > 0); + assert(p); + + fd = memfd_new(name); + if (fd < 0) + return fd; + + r = memfd_set_size(fd, sz); + if (r < 0) + return r; + + r = memfd_map(fd, 0, sz, p); + if (r < 0) + return r; + + r = fd; + fd = -1; + + return r; +} diff --git a/src/libbasic/memfd-util.h b/src/libbasic/memfd-util.h new file mode 100644 index 0000000000..46d4989e4c --- /dev/null +++ b/src/libbasic/memfd-util.h @@ -0,0 +1,36 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 +#include +#include + +int memfd_new(const char *name); +int memfd_new_and_map(const char *name, size_t sz, void **p); + +int memfd_map(int fd, uint64_t offset, size_t size, void **p); + +int memfd_set_sealed(int fd); +int memfd_get_sealed(int fd); + +int memfd_get_size(int fd, uint64_t *sz); +int memfd_set_size(int fd, uint64_t sz); diff --git a/src/libbasic/mempool.c b/src/libbasic/mempool.c new file mode 100644 index 0000000000..f95e2beb0f --- /dev/null +++ b/src/libbasic/mempool.c @@ -0,0 +1,104 @@ +/*** + This file is part of systemd. + + Copyright 2010-2014 Lennart Poettering + Copyright 2014 Michal Schmidt + + 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 + +#include "macro.h" +#include "mempool.h" +#include "util.h" + +struct pool { + struct pool *next; + unsigned n_tiles; + unsigned n_used; +}; + +void* mempool_alloc_tile(struct mempool *mp) { + unsigned i; + + /* When a tile is released we add it to the list and simply + * place the next pointer at its offset 0. */ + + assert(mp->tile_size >= sizeof(void*)); + assert(mp->at_least > 0); + + if (mp->freelist) { + void *r; + + r = mp->freelist; + mp->freelist = * (void**) mp->freelist; + return r; + } + + if (_unlikely_(!mp->first_pool) || + _unlikely_(mp->first_pool->n_used >= mp->first_pool->n_tiles)) { + unsigned n; + size_t size; + struct pool *p; + + n = mp->first_pool ? mp->first_pool->n_tiles : 0; + n = MAX(mp->at_least, n * 2); + size = PAGE_ALIGN(ALIGN(sizeof(struct pool)) + n*mp->tile_size); + n = (size - ALIGN(sizeof(struct pool))) / mp->tile_size; + + p = malloc(size); + if (!p) + return NULL; + + p->next = mp->first_pool; + p->n_tiles = n; + p->n_used = 0; + + mp->first_pool = p; + } + + i = mp->first_pool->n_used++; + + return ((uint8_t*) mp->first_pool) + ALIGN(sizeof(struct pool)) + i*mp->tile_size; +} + +void* mempool_alloc0_tile(struct mempool *mp) { + void *p; + + p = mempool_alloc_tile(mp); + if (p) + memzero(p, mp->tile_size); + return p; +} + +void mempool_free_tile(struct mempool *mp, void *p) { + * (void**) p = mp->freelist; + mp->freelist = p; +} + +#ifdef VALGRIND + +void mempool_drop(struct mempool *mp) { + struct pool *p = mp->first_pool; + while (p) { + struct pool *n; + n = p->next; + free(p); + p = n; + } +} + +#endif diff --git a/src/libbasic/mempool.h b/src/libbasic/mempool.h new file mode 100644 index 0000000000..0618b8dd22 --- /dev/null +++ b/src/libbasic/mempool.h @@ -0,0 +1,47 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011-2014 Lennart Poettering + Copyright 2014 Michal Schmidt + + 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 + +struct pool; + +struct mempool { + struct pool *first_pool; + void *freelist; + size_t tile_size; + unsigned at_least; +}; + +void* mempool_alloc_tile(struct mempool *mp); +void* mempool_alloc0_tile(struct mempool *mp); +void mempool_free_tile(struct mempool *mp, void *p); + +#define DEFINE_MEMPOOL(pool_name, tile_type, alloc_at_least) \ +static struct mempool pool_name = { \ + .tile_size = sizeof(tile_type), \ + .at_least = alloc_at_least, \ +} + + +#ifdef VALGRIND +void mempool_drop(struct mempool *mp); +#endif diff --git a/src/libbasic/missing.h b/src/libbasic/missing.h new file mode 100644 index 0000000000..651e414395 --- /dev/null +++ b/src/libbasic/missing.h @@ -0,0 +1,1016 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +/* Missing glibc definitions to access certain kernel APIs */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_AUDIT +#include +#endif + +#ifdef ARCH_MIPS +#include +#endif + +#ifdef HAVE_LINUX_BTRFS_H +#include +#endif + +#include "macro.h" + +#ifndef RLIMIT_RTTIME +#define RLIMIT_RTTIME 15 +#endif + +/* If RLIMIT_RTTIME is not defined, then we cannot use RLIMIT_NLIMITS as is */ +#define _RLIMIT_MAX (RLIMIT_RTTIME+1 > RLIMIT_NLIMITS ? RLIMIT_RTTIME+1 : RLIMIT_NLIMITS) + +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif + +#ifndef F_SETPIPE_SZ +#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7) +#endif + +#ifndef F_GETPIPE_SZ +#define F_GETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 8) +#endif + +#ifndef F_ADD_SEALS +#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) +#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10) + +#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */ +#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */ +#define F_SEAL_GROW 0x0004 /* prevent file from growing */ +#define F_SEAL_WRITE 0x0008 /* prevent writes */ +#endif + +#ifndef F_OFD_GETLK +#define F_OFD_GETLK 36 +#define F_OFD_SETLK 37 +#define F_OFD_SETLKW 38 +#endif + +#ifndef MFD_ALLOW_SEALING +#define MFD_ALLOW_SEALING 0x0002U +#endif + +#ifndef MFD_CLOEXEC +#define MFD_CLOEXEC 0x0001U +#endif + +#ifndef IP_FREEBIND +#define IP_FREEBIND 15 +#endif + +#ifndef OOM_SCORE_ADJ_MIN +#define OOM_SCORE_ADJ_MIN (-1000) +#endif + +#ifndef OOM_SCORE_ADJ_MAX +#define OOM_SCORE_ADJ_MAX 1000 +#endif + +#ifndef AUDIT_SERVICE_START +#define AUDIT_SERVICE_START 1130 /* Service (daemon) start */ +#endif + +#ifndef AUDIT_SERVICE_STOP +#define AUDIT_SERVICE_STOP 1131 /* Service (daemon) stop */ +#endif + +#ifndef TIOCVHANGUP +#define TIOCVHANGUP 0x5437 +#endif + +#ifndef IP_TRANSPARENT +#define IP_TRANSPARENT 19 +#endif + +#ifndef SOL_NETLINK +#define SOL_NETLINK 270 +#endif + +#ifndef NETLINK_LIST_MEMBERSHIPS +#define NETLINK_LIST_MEMBERSHIPS 9 +#endif + +#ifndef SOL_SCTP +#define SOL_SCTP 132 +#endif + +#ifndef GRND_NONBLOCK +#define GRND_NONBLOCK 0x0001 +#endif + +#ifndef GRND_RANDOM +#define GRND_RANDOM 0x0002 +#endif + +#ifndef BTRFS_IOCTL_MAGIC +#define BTRFS_IOCTL_MAGIC 0x94 +#endif + +#ifndef BTRFS_PATH_NAME_MAX +#define BTRFS_PATH_NAME_MAX 4087 +#endif + +#ifndef BTRFS_DEVICE_PATH_NAME_MAX +#define BTRFS_DEVICE_PATH_NAME_MAX 1024 +#endif + +#ifndef BTRFS_FSID_SIZE +#define BTRFS_FSID_SIZE 16 +#endif + +#ifndef BTRFS_UUID_SIZE +#define BTRFS_UUID_SIZE 16 +#endif + +#ifndef BTRFS_SUBVOL_RDONLY +#define BTRFS_SUBVOL_RDONLY (1ULL << 1) +#endif + +#ifndef BTRFS_SUBVOL_NAME_MAX +#define BTRFS_SUBVOL_NAME_MAX 4039 +#endif + +#ifndef BTRFS_INO_LOOKUP_PATH_MAX +#define BTRFS_INO_LOOKUP_PATH_MAX 4080 +#endif + +#ifndef BTRFS_SEARCH_ARGS_BUFSIZE +#define BTRFS_SEARCH_ARGS_BUFSIZE (4096 - sizeof(struct btrfs_ioctl_search_key)) +#endif + +#ifndef BTRFS_QGROUP_LEVEL_SHIFT +#define BTRFS_QGROUP_LEVEL_SHIFT 48 +#endif + +#ifndef HAVE_LINUX_BTRFS_H +struct btrfs_ioctl_vol_args { + int64_t fd; + char name[BTRFS_PATH_NAME_MAX + 1]; +}; + +struct btrfs_qgroup_limit { + __u64 flags; + __u64 max_rfer; + __u64 max_excl; + __u64 rsv_rfer; + __u64 rsv_excl; +}; + +struct btrfs_qgroup_inherit { + __u64 flags; + __u64 num_qgroups; + __u64 num_ref_copies; + __u64 num_excl_copies; + struct btrfs_qgroup_limit lim; + __u64 qgroups[0]; +}; + +struct btrfs_ioctl_qgroup_limit_args { + __u64 qgroupid; + struct btrfs_qgroup_limit lim; +}; + +struct btrfs_ioctl_vol_args_v2 { + __s64 fd; + __u64 transid; + __u64 flags; + union { + struct { + __u64 size; + struct btrfs_qgroup_inherit *qgroup_inherit; + }; + __u64 unused[4]; + }; + char name[BTRFS_SUBVOL_NAME_MAX + 1]; +}; + +struct btrfs_ioctl_dev_info_args { + uint64_t devid; /* in/out */ + uint8_t uuid[BTRFS_UUID_SIZE]; /* in/out */ + uint64_t bytes_used; /* out */ + uint64_t total_bytes; /* out */ + uint64_t unused[379]; /* pad to 4k */ + char path[BTRFS_DEVICE_PATH_NAME_MAX]; /* out */ +}; + +struct btrfs_ioctl_fs_info_args { + uint64_t max_id; /* out */ + uint64_t num_devices; /* out */ + uint8_t fsid[BTRFS_FSID_SIZE]; /* out */ + uint64_t reserved[124]; /* pad to 1k */ +}; + +struct btrfs_ioctl_ino_lookup_args { + __u64 treeid; + __u64 objectid; + char name[BTRFS_INO_LOOKUP_PATH_MAX]; +}; + +struct btrfs_ioctl_search_key { + /* which root are we searching. 0 is the tree of tree roots */ + __u64 tree_id; + + /* keys returned will be >= min and <= max */ + __u64 min_objectid; + __u64 max_objectid; + + /* keys returned will be >= min and <= max */ + __u64 min_offset; + __u64 max_offset; + + /* max and min transids to search for */ + __u64 min_transid; + __u64 max_transid; + + /* keys returned will be >= min and <= max */ + __u32 min_type; + __u32 max_type; + + /* + * how many items did userland ask for, and how many are we + * returning + */ + __u32 nr_items; + + /* align to 64 bits */ + __u32 unused; + + /* some extra for later */ + __u64 unused1; + __u64 unused2; + __u64 unused3; + __u64 unused4; +}; + +struct btrfs_ioctl_search_header { + __u64 transid; + __u64 objectid; + __u64 offset; + __u32 type; + __u32 len; +}; + + +struct btrfs_ioctl_search_args { + struct btrfs_ioctl_search_key key; + char buf[BTRFS_SEARCH_ARGS_BUFSIZE]; +}; + +struct btrfs_ioctl_clone_range_args { + __s64 src_fd; + __u64 src_offset, src_length; + __u64 dest_offset; +}; + +#define BTRFS_QUOTA_CTL_ENABLE 1 +#define BTRFS_QUOTA_CTL_DISABLE 2 +#define BTRFS_QUOTA_CTL_RESCAN__NOTUSED 3 +struct btrfs_ioctl_quota_ctl_args { + __u64 cmd; + __u64 status; +}; +#endif + +#ifndef BTRFS_IOC_DEFRAG +#define BTRFS_IOC_DEFRAG _IOW(BTRFS_IOCTL_MAGIC, 2, \ + struct btrfs_ioctl_vol_args) +#endif + +#ifndef BTRFS_IOC_RESIZE +#define BTRFS_IOC_RESIZE _IOW(BTRFS_IOCTL_MAGIC, 3, \ + struct btrfs_ioctl_vol_args) +#endif + +#ifndef BTRFS_IOC_CLONE +#define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int) +#endif + +#ifndef BTRFS_IOC_CLONE_RANGE +#define BTRFS_IOC_CLONE_RANGE _IOW(BTRFS_IOCTL_MAGIC, 13, \ + struct btrfs_ioctl_clone_range_args) +#endif + +#ifndef BTRFS_IOC_SUBVOL_CREATE +#define BTRFS_IOC_SUBVOL_CREATE _IOW(BTRFS_IOCTL_MAGIC, 14, \ + struct btrfs_ioctl_vol_args) +#endif + +#ifndef BTRFS_IOC_SNAP_DESTROY +#define BTRFS_IOC_SNAP_DESTROY _IOW(BTRFS_IOCTL_MAGIC, 15, \ + struct btrfs_ioctl_vol_args) +#endif + +#ifndef BTRFS_IOC_TREE_SEARCH +#define BTRFS_IOC_TREE_SEARCH _IOWR(BTRFS_IOCTL_MAGIC, 17, \ + struct btrfs_ioctl_search_args) +#endif + +#ifndef BTRFS_IOC_INO_LOOKUP +#define BTRFS_IOC_INO_LOOKUP _IOWR(BTRFS_IOCTL_MAGIC, 18, \ + struct btrfs_ioctl_ino_lookup_args) +#endif + +#ifndef BTRFS_IOC_SNAP_CREATE_V2 +#define BTRFS_IOC_SNAP_CREATE_V2 _IOW(BTRFS_IOCTL_MAGIC, 23, \ + struct btrfs_ioctl_vol_args_v2) +#endif + +#ifndef BTRFS_IOC_SUBVOL_GETFLAGS +#define BTRFS_IOC_SUBVOL_GETFLAGS _IOR(BTRFS_IOCTL_MAGIC, 25, __u64) +#endif + +#ifndef BTRFS_IOC_SUBVOL_SETFLAGS +#define BTRFS_IOC_SUBVOL_SETFLAGS _IOW(BTRFS_IOCTL_MAGIC, 26, __u64) +#endif + +#ifndef BTRFS_IOC_DEV_INFO +#define BTRFS_IOC_DEV_INFO _IOWR(BTRFS_IOCTL_MAGIC, 30, \ + struct btrfs_ioctl_dev_info_args) +#endif + +#ifndef BTRFS_IOC_FS_INFO +#define BTRFS_IOC_FS_INFO _IOR(BTRFS_IOCTL_MAGIC, 31, \ + struct btrfs_ioctl_fs_info_args) +#endif + +#ifndef BTRFS_IOC_DEVICES_READY +#define BTRFS_IOC_DEVICES_READY _IOR(BTRFS_IOCTL_MAGIC, 39, \ + struct btrfs_ioctl_vol_args) +#endif + +#ifndef BTRFS_IOC_QUOTA_CTL +#define BTRFS_IOC_QUOTA_CTL _IOWR(BTRFS_IOCTL_MAGIC, 40, \ + struct btrfs_ioctl_quota_ctl_args) +#endif + +#ifndef BTRFS_IOC_QGROUP_LIMIT +#define BTRFS_IOC_QGROUP_LIMIT _IOR(BTRFS_IOCTL_MAGIC, 43, \ + struct btrfs_ioctl_qgroup_limit_args) +#endif + +#ifndef BTRFS_IOC_QUOTA_RESCAN_WAIT +#define BTRFS_IOC_QUOTA_RESCAN_WAIT _IO(BTRFS_IOCTL_MAGIC, 46) +#endif + +#ifndef BTRFS_FIRST_FREE_OBJECTID +#define BTRFS_FIRST_FREE_OBJECTID 256 +#endif + +#ifndef BTRFS_LAST_FREE_OBJECTID +#define BTRFS_LAST_FREE_OBJECTID -256ULL +#endif + +#ifndef BTRFS_ROOT_TREE_OBJECTID +#define BTRFS_ROOT_TREE_OBJECTID 1 +#endif + +#ifndef BTRFS_QUOTA_TREE_OBJECTID +#define BTRFS_QUOTA_TREE_OBJECTID 8ULL +#endif + +#ifndef BTRFS_ROOT_ITEM_KEY +#define BTRFS_ROOT_ITEM_KEY 132 +#endif + +#ifndef BTRFS_QGROUP_STATUS_KEY +#define BTRFS_QGROUP_STATUS_KEY 240 +#endif + +#ifndef BTRFS_QGROUP_INFO_KEY +#define BTRFS_QGROUP_INFO_KEY 242 +#endif + +#ifndef BTRFS_QGROUP_LIMIT_KEY +#define BTRFS_QGROUP_LIMIT_KEY 244 +#endif + +#ifndef BTRFS_QGROUP_RELATION_KEY +#define BTRFS_QGROUP_RELATION_KEY 246 +#endif + +#ifndef BTRFS_ROOT_BACKREF_KEY +#define BTRFS_ROOT_BACKREF_KEY 144 +#endif + +#ifndef BTRFS_SUPER_MAGIC +#define BTRFS_SUPER_MAGIC 0x9123683E +#endif + +#ifndef CGROUP_SUPER_MAGIC +#define CGROUP_SUPER_MAGIC 0x27e0eb +#endif + +#ifndef CGROUP2_SUPER_MAGIC +#define CGROUP2_SUPER_MAGIC 0x63677270 +#endif + +#ifndef TMPFS_MAGIC +#define TMPFS_MAGIC 0x01021994 +#endif + +#ifndef MQUEUE_MAGIC +#define MQUEUE_MAGIC 0x19800202 +#endif + +#ifndef MS_MOVE +#define MS_MOVE 8192 +#endif + +#ifndef MS_PRIVATE +#define MS_PRIVATE (1 << 18) +#endif + +#ifndef SCM_SECURITY +#define SCM_SECURITY 0x03 +#endif + +#ifndef MS_STRICTATIME +#define MS_STRICTATIME (1<<24) +#endif + +#ifndef MS_REC +#define MS_REC 16384 +#endif + +#ifndef MS_SHARED +#define MS_SHARED (1<<20) +#endif + +#ifndef PR_SET_NO_NEW_PRIVS +#define PR_SET_NO_NEW_PRIVS 38 +#endif + +#ifndef PR_SET_CHILD_SUBREAPER +#define PR_SET_CHILD_SUBREAPER 36 +#endif + +#ifndef MAX_HANDLE_SZ +#define MAX_HANDLE_SZ 128 +#endif + +#ifndef HAVE_SECURE_GETENV +# ifdef HAVE___SECURE_GETENV +# define secure_getenv __secure_getenv +# else +# error "neither secure_getenv nor __secure_getenv are available" +# endif +#endif + +#ifndef CIFS_MAGIC_NUMBER +# define CIFS_MAGIC_NUMBER 0xFF534D42 +#endif + +#ifndef TFD_TIMER_CANCEL_ON_SET +# define TFD_TIMER_CANCEL_ON_SET (1 << 1) +#endif + +#ifndef SO_REUSEPORT +# define SO_REUSEPORT 15 +#endif + +#ifndef EVIOCREVOKE +# define EVIOCREVOKE _IOW('E', 0x91, int) +#endif + +#ifndef DRM_IOCTL_SET_MASTER +# define DRM_IOCTL_SET_MASTER _IO('d', 0x1e) +#endif + +#ifndef DRM_IOCTL_DROP_MASTER +# define DRM_IOCTL_DROP_MASTER _IO('d', 0x1f) +#endif + +#if defined(__i386__) || defined(__x86_64__) + +/* The precise definition of __O_TMPFILE is arch specific, so let's + * just define this on x86 where we know the value. */ + +#ifndef __O_TMPFILE +#define __O_TMPFILE 020000000 +#endif + +/* a horrid kludge trying to make sure that this will fail on old kernels */ +#ifndef O_TMPFILE +#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY) +#endif + +#endif + +#if !HAVE_DECL_LO_FLAGS_PARTSCAN +#define LO_FLAGS_PARTSCAN 8 +#endif + +#ifndef LOOP_CTL_REMOVE +#define LOOP_CTL_REMOVE 0x4C81 +#endif + +#ifndef LOOP_CTL_GET_FREE +#define LOOP_CTL_GET_FREE 0x4C82 +#endif + +#if !HAVE_DECL_IFLA_INET6_ADDR_GEN_MODE +#define IFLA_INET6_UNSPEC 0 +#define IFLA_INET6_FLAGS 1 +#define IFLA_INET6_CONF 2 +#define IFLA_INET6_STATS 3 +#define IFLA_INET6_MCAST 4 +#define IFLA_INET6_CACHEINFO 5 +#define IFLA_INET6_ICMP6STATS 6 +#define IFLA_INET6_TOKEN 7 +#define IFLA_INET6_ADDR_GEN_MODE 8 +#define __IFLA_INET6_MAX 9 + +#define IFLA_INET6_MAX (__IFLA_INET6_MAX - 1) + +#define IN6_ADDR_GEN_MODE_EUI64 0 +#define IN6_ADDR_GEN_MODE_NONE 1 +#endif + +#if !HAVE_DECL_IFLA_MACVLAN_FLAGS +#define IFLA_MACVLAN_UNSPEC 0 +#define IFLA_MACVLAN_MODE 1 +#define IFLA_MACVLAN_FLAGS 2 +#define __IFLA_MACVLAN_MAX 3 + +#define IFLA_MACVLAN_MAX (__IFLA_MACVLAN_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_IPVLAN_MODE +#define IFLA_IPVLAN_UNSPEC 0 +#define IFLA_IPVLAN_MODE 1 +#define __IFLA_IPVLAN_MAX 2 + +#define IFLA_IPVLAN_MAX (__IFLA_IPVLAN_MAX - 1) + +#define IPVLAN_MODE_L2 0 +#define IPVLAN_MODE_L3 1 +#define IPVLAN_MAX 2 +#endif + +#if !HAVE_DECL_IFLA_VTI_REMOTE +#define IFLA_VTI_UNSPEC 0 +#define IFLA_VTI_LINK 1 +#define IFLA_VTI_IKEY 2 +#define IFLA_VTI_OKEY 3 +#define IFLA_VTI_LOCAL 4 +#define IFLA_VTI_REMOTE 5 +#define __IFLA_VTI_MAX 6 + +#define IFLA_VTI_MAX (__IFLA_VTI_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_PHYS_PORT_ID +#define IFLA_EXT_MASK 29 +#undef IFLA_PROMISCUITY +#define IFLA_PROMISCUITY 30 +#define IFLA_NUM_TX_QUEUES 31 +#define IFLA_NUM_RX_QUEUES 32 +#define IFLA_CARRIER 33 +#define IFLA_PHYS_PORT_ID 34 +#define __IFLA_MAX 35 + +#define IFLA_MAX (__IFLA_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_BOND_AD_INFO +#define IFLA_BOND_UNSPEC 0 +#define IFLA_BOND_MODE 1 +#define IFLA_BOND_ACTIVE_SLAVE 2 +#define IFLA_BOND_MIIMON 3 +#define IFLA_BOND_UPDELAY 4 +#define IFLA_BOND_DOWNDELAY 5 +#define IFLA_BOND_USE_CARRIER 6 +#define IFLA_BOND_ARP_INTERVAL 7 +#define IFLA_BOND_ARP_IP_TARGET 8 +#define IFLA_BOND_ARP_VALIDATE 9 +#define IFLA_BOND_ARP_ALL_TARGETS 10 +#define IFLA_BOND_PRIMARY 11 +#define IFLA_BOND_PRIMARY_RESELECT 12 +#define IFLA_BOND_FAIL_OVER_MAC 13 +#define IFLA_BOND_XMIT_HASH_POLICY 14 +#define IFLA_BOND_RESEND_IGMP 15 +#define IFLA_BOND_NUM_PEER_NOTIF 16 +#define IFLA_BOND_ALL_SLAVES_ACTIVE 17 +#define IFLA_BOND_MIN_LINKS 18 +#define IFLA_BOND_LP_INTERVAL 19 +#define IFLA_BOND_PACKETS_PER_SLAVE 20 +#define IFLA_BOND_AD_LACP_RATE 21 +#define IFLA_BOND_AD_SELECT 22 +#define IFLA_BOND_AD_INFO 23 +#define __IFLA_BOND_MAX 24 + +#define IFLA_BOND_MAX (__IFLA_BOND_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_VLAN_PROTOCOL +#define IFLA_VLAN_UNSPEC 0 +#define IFLA_VLAN_ID 1 +#define IFLA_VLAN_FLAGS 2 +#define IFLA_VLAN_EGRESS_QOS 3 +#define IFLA_VLAN_INGRESS_QOS 4 +#define IFLA_VLAN_PROTOCOL 5 +#define __IFLA_VLAN_MAX 6 + +#define IFLA_VLAN_MAX (__IFLA_VLAN_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_VXLAN_REMCSUM_NOPARTIAL +#define IFLA_VXLAN_UNSPEC 0 +#define IFLA_VXLAN_ID 1 +#define IFLA_VXLAN_GROUP 2 +#define IFLA_VXLAN_LINK 3 +#define IFLA_VXLAN_LOCAL 4 +#define IFLA_VXLAN_TTL 5 +#define IFLA_VXLAN_TOS 6 +#define IFLA_VXLAN_LEARNING 7 +#define IFLA_VXLAN_AGEING 8 +#define IFLA_VXLAN_LIMIT 9 +#define IFLA_VXLAN_PORT_RANGE 10 +#define IFLA_VXLAN_PROXY 11 +#define IFLA_VXLAN_RSC 12 +#define IFLA_VXLAN_L2MISS 13 +#define IFLA_VXLAN_L3MISS 14 +#define IFLA_VXLAN_PORT 15 +#define IFLA_VXLAN_GROUP6 16 +#define IFLA_VXLAN_LOCAL6 17 +#define IFLA_VXLAN_UDP_CSUM 18 +#define IFLA_VXLAN_UDP_ZERO_CSUM6_TX 19 +#define IFLA_VXLAN_UDP_ZERO_CSUM6_RX 20 +#define IFLA_VXLAN_REMCSUM_TX 21 +#define IFLA_VXLAN_REMCSUM_RX 22 +#define IFLA_VXLAN_GBP 23 +#define IFLA_VXLAN_REMCSUM_NOPARTIAL 24 +#define __IFLA_VXLAN_MAX 25 + +#define IFLA_VXLAN_MAX (__IFLA_VXLAN_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_IPTUN_ENCAP_DPORT +#define IFLA_IPTUN_UNSPEC 0 +#define IFLA_IPTUN_LINK 1 +#define IFLA_IPTUN_LOCAL 2 +#define IFLA_IPTUN_REMOTE 3 +#define IFLA_IPTUN_TTL 4 +#define IFLA_IPTUN_TOS 5 +#define IFLA_IPTUN_ENCAP_LIMIT 6 +#define IFLA_IPTUN_FLOWINFO 7 +#define IFLA_IPTUN_FLAGS 8 +#define IFLA_IPTUN_PROTO 9 +#define IFLA_IPTUN_PMTUDISC 10 +#define IFLA_IPTUN_6RD_PREFIX 11 +#define IFLA_IPTUN_6RD_RELAY_PREFIX 12 +#define IFLA_IPTUN_6RD_PREFIXLEN 13 +#define IFLA_IPTUN_6RD_RELAY_PREFIXLEN 14 +#define IFLA_IPTUN_ENCAP_TYPE 15 +#define IFLA_IPTUN_ENCAP_FLAGS 16 +#define IFLA_IPTUN_ENCAP_SPORT 17 +#define IFLA_IPTUN_ENCAP_DPORT 18 + +#define __IFLA_IPTUN_MAX 19 + +#define IFLA_IPTUN_MAX (__IFLA_IPTUN_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_GRE_ENCAP_DPORT +#define IFLA_GRE_UNSPEC 0 +#define IFLA_GRE_LINK 1 +#define IFLA_GRE_IFLAGS 2 +#define IFLA_GRE_OFLAGS 3 +#define IFLA_GRE_IKEY 4 +#define IFLA_GRE_OKEY 5 +#define IFLA_GRE_LOCAL 6 +#define IFLA_GRE_REMOTE 7 +#define IFLA_GRE_TTL 8 +#define IFLA_GRE_TOS 9 +#define IFLA_GRE_PMTUDISC 10 +#define IFLA_GRE_ENCAP_LIMIT 11 +#define IFLA_GRE_FLOWINFO 12 +#define IFLA_GRE_FLAGS 13 +#define IFLA_GRE_ENCAP_TYPE 14 +#define IFLA_GRE_ENCAP_FLAGS 15 +#define IFLA_GRE_ENCAP_SPORT 16 +#define IFLA_GRE_ENCAP_DPORT 17 + +#define __IFLA_GRE_MAX 18 + +#define IFLA_GRE_MAX (__IFLA_GRE_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_BRIDGE_VLAN_INFO +#define IFLA_BRIDGE_FLAGS 0 +#define IFLA_BRIDGE_MODE 1 +#define IFLA_BRIDGE_VLAN_INFO 2 +#define __IFLA_BRIDGE_MAX 3 + +#define IFLA_BRIDGE_MAX (__IFLA_BRIDGE_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_BR_VLAN_DEFAULT_PVID +#define IFLA_BR_UNSPEC 0 +#define IFLA_BR_FORWARD_DELAY 1 +#define IFLA_BR_HELLO_TIME 2 +#define IFLA_BR_MAX_AGE 3 +#define IFLA_BR_AGEING_TIME 4 +#define IFLA_BR_STP_STATE 5 +#define IFLA_BR_PRIORITY 6 +#define IFLA_BR_VLAN_FILTERING 7 +#define IFLA_BR_VLAN_PROTOCOL 8 +#define IFLA_BR_GROUP_FWD_MASK 9 +#define IFLA_BR_ROOT_ID 10 +#define IFLA_BR_BRIDGE_ID 11 +#define IFLA_BR_ROOT_PORT 12 +#define IFLA_BR_ROOT_PATH_COST 13 +#define IFLA_BR_TOPOLOGY_CHANGE 14 +#define IFLA_BR_TOPOLOGY_CHANGE_DETECTED 15 +#define IFLA_BR_HELLO_TIMER 16 +#define IFLA_BR_TCN_TIMER 17 +#define IFLA_BR_TOPOLOGY_CHANGE_TIMER 18 +#define IFLA_BR_GC_TIMER 19 +#define IFLA_BR_GROUP_ADDR 20 +#define IFLA_BR_FDB_FLUSH 21 +#define IFLA_BR_MCAST_ROUTER 22 +#define IFLA_BR_MCAST_SNOOPING 23 +#define IFLA_BR_MCAST_QUERY_USE_IFADDR 24 +#define IFLA_BR_MCAST_QUERIER 25 +#define IFLA_BR_MCAST_HASH_ELASTICITY 26 +#define IFLA_BR_MCAST_HASH_MAX 27 +#define IFLA_BR_MCAST_LAST_MEMBER_CNT 28 +#define IFLA_BR_MCAST_STARTUP_QUERY_CNT 29 +#define IFLA_BR_MCAST_LAST_MEMBER_INTVL 30 +#define IFLA_BR_MCAST_MEMBERSHIP_INTVL 31 +#define IFLA_BR_MCAST_QUERIER_INTVL 32 +#define IFLA_BR_MCAST_QUERY_INTVL 33 +#define IFLA_BR_MCAST_QUERY_RESPONSE_INTVL 34 +#define IFLA_BR_MCAST_STARTUP_QUERY_INTVL 35 +#define IFLA_BR_NF_CALL_IPTABLES 36 +#define IFLA_BR_NF_CALL_IP6TABLES 37 +#define IFLA_BR_NF_CALL_ARPTABLES 38 +#define IFLA_BR_VLAN_DEFAULT_PVID 39 +#define __IFLA_BR_MAX 40 + +#define IFLA_BR_MAX (__IFLA_BR_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_BRPORT_LEARNING_SYNC +#define IFLA_BRPORT_UNSPEC 0 +#define IFLA_BRPORT_STATE 1 +#define IFLA_BRPORT_PRIORITY 2 +#define IFLA_BRPORT_COST 3 +#define IFLA_BRPORT_MODE 4 +#define IFLA_BRPORT_GUARD 5 +#define IFLA_BRPORT_PROTECT 6 +#define IFLA_BRPORT_FAST_LEAVE 7 +#define IFLA_BRPORT_LEARNING 8 +#define IFLA_BRPORT_UNICAST_FLOOD 9 +#define IFLA_BRPORT_LEARNING_SYNC 11 +#define __IFLA_BRPORT_MAX 12 + +#define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_BRPORT_PROXYARP +#define IFLA_BRPORT_PROXYARP 10 +#endif + +#if !HAVE_DECL_NDA_IFINDEX +#define NDA_UNSPEC 0 +#define NDA_DST 1 +#define NDA_LLADDR 2 +#define NDA_CACHEINFO 3 +#define NDA_PROBES 4 +#define NDA_VLAN 5 +#define NDA_PORT 6 +#define NDA_VNI 7 +#define NDA_IFINDEX 8 +#define __NDA_MAX 9 + +#define NDA_MAX (__NDA_MAX - 1) +#endif + +#ifndef RTA_PREF +#define RTA_PREF 20 +#endif + +#ifndef IPV6_UNICAST_IF +#define IPV6_UNICAST_IF 76 +#endif + +#ifndef IPV6_MIN_MTU +#define IPV6_MIN_MTU 1280 +#endif + +#ifndef IFF_MULTI_QUEUE +#define IFF_MULTI_QUEUE 0x100 +#endif + +#ifndef IFF_LOWER_UP +#define IFF_LOWER_UP 0x10000 +#endif + +#ifndef IFF_DORMANT +#define IFF_DORMANT 0x20000 +#endif + +#ifndef BOND_XMIT_POLICY_ENCAP23 +#define BOND_XMIT_POLICY_ENCAP23 3 +#endif + +#ifndef BOND_XMIT_POLICY_ENCAP34 +#define BOND_XMIT_POLICY_ENCAP34 4 +#endif + +#ifndef NET_ADDR_RANDOM +# define NET_ADDR_RANDOM 1 +#endif + +#ifndef NET_NAME_UNKNOWN +# define NET_NAME_UNKNOWN 0 +#endif + +#ifndef NET_NAME_ENUM +# define NET_NAME_ENUM 1 +#endif + +#ifndef NET_NAME_PREDICTABLE +# define NET_NAME_PREDICTABLE 2 +#endif + +#ifndef NET_NAME_USER +# define NET_NAME_USER 3 +#endif + +#ifndef NET_NAME_RENAMED +# define NET_NAME_RENAMED 4 +#endif + +#ifndef BPF_XOR +# define BPF_XOR 0xa0 +#endif + +/* Note that LOOPBACK_IFINDEX is currently not exported by the + * kernel/glibc, but hardcoded internally by the kernel. However, as + * it is exported to userspace indirectly via rtnetlink and the + * ioctls, and made use of widely we define it here too, in a way that + * is compatible with the kernel's internal definition. */ +#ifndef LOOPBACK_IFINDEX +#define LOOPBACK_IFINDEX 1 +#endif + +#if !HAVE_DECL_IFA_FLAGS +#define IFA_FLAGS 8 +#endif + +#ifndef IFA_F_MANAGETEMPADDR +#define IFA_F_MANAGETEMPADDR 0x100 +#endif + +#ifndef IFA_F_NOPREFIXROUTE +#define IFA_F_NOPREFIXROUTE 0x200 +#endif + +#ifndef MAX_AUDIT_MESSAGE_LENGTH +#define MAX_AUDIT_MESSAGE_LENGTH 8970 +#endif + +#ifndef AUDIT_NLGRP_MAX +#define AUDIT_NLGRP_READLOG 1 +#endif + +#ifndef CAP_MAC_OVERRIDE +#define CAP_MAC_OVERRIDE 32 +#endif + +#ifndef CAP_MAC_ADMIN +#define CAP_MAC_ADMIN 33 +#endif + +#ifndef CAP_SYSLOG +#define CAP_SYSLOG 34 +#endif + +#ifndef CAP_WAKE_ALARM +#define CAP_WAKE_ALARM 35 +#endif + +#ifndef CAP_BLOCK_SUSPEND +#define CAP_BLOCK_SUSPEND 36 +#endif + +#ifndef CAP_AUDIT_READ +#define CAP_AUDIT_READ 37 +#endif + +#ifndef RENAME_NOREPLACE +#define RENAME_NOREPLACE (1 << 0) +#endif + +#ifndef KCMP_FILE +#define KCMP_FILE 0 +#endif + +#ifndef INPUT_PROP_POINTING_STICK +#define INPUT_PROP_POINTING_STICK 0x05 +#endif + +#ifndef INPUT_PROP_ACCELEROMETER +#define INPUT_PROP_ACCELEROMETER 0x06 +#endif + +#ifndef HAVE_KEY_SERIAL_T +typedef int32_t key_serial_t; +#endif + +#ifndef KEYCTL_READ +#define KEYCTL_READ 11 +#endif + +#ifndef KEYCTL_SET_TIMEOUT +#define KEYCTL_SET_TIMEOUT 15 +#endif + +#ifndef KEY_SPEC_USER_KEYRING +#define KEY_SPEC_USER_KEYRING -4 +#endif + +#ifndef PR_CAP_AMBIENT +#define PR_CAP_AMBIENT 47 +#endif + +#ifndef PR_CAP_AMBIENT_IS_SET +#define PR_CAP_AMBIENT_IS_SET 1 +#endif + +#ifndef PR_CAP_AMBIENT_RAISE +#define PR_CAP_AMBIENT_RAISE 2 +#endif + +#ifndef PR_CAP_AMBIENT_CLEAR_ALL +#define PR_CAP_AMBIENT_CLEAR_ALL 4 +#endif + +/* The following two defines are actually available in the kernel headers for longer, but we define them here anyway, + * since that makes it easier to use them in conjunction with the glibc net/if.h header which conflicts with + * linux/if.h. */ +#ifndef IF_OPER_UNKNOWN +#define IF_OPER_UNKNOWN 0 +#endif + +#ifndef IF_OPER_UP +#define IF_OPER_UP 6 + +#ifndef HAVE_CHAR32_T +#define char32_t uint32_t +#endif + +#ifndef HAVE_CHAR16_T +#define char16_t uint16_t +#endif + +#ifndef ETHERTYPE_LLDP +#define ETHERTYPE_LLDP 0x88cc +#endif + +#endif + +#include "missing_syscall.h" diff --git a/src/libbasic/missing_syscall.h b/src/libbasic/missing_syscall.h new file mode 100644 index 0000000000..d502d3b9ca --- /dev/null +++ b/src/libbasic/missing_syscall.h @@ -0,0 +1,310 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2016 Zbigniew Jędrzejewski-Szmek + + 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 . +***/ + +/* Missing glibc definitions to access certain kernel APIs */ + +#if !HAVE_DECL_PIVOT_ROOT +static inline int pivot_root(const char *new_root, const char *put_old) { + return syscall(SYS_pivot_root, new_root, put_old); +} +#endif + +/* ======================================================================= */ + +#if !HAVE_DECL_MEMFD_CREATE +# ifndef __NR_memfd_create +# if defined __x86_64__ +# define __NR_memfd_create 319 +# elif defined __arm__ +# define __NR_memfd_create 385 +# elif defined __aarch64__ +# define __NR_memfd_create 279 +# elif defined __s390__ +# define __NR_memfd_create 350 +# elif defined _MIPS_SIM +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# define __NR_memfd_create 4354 +# endif +# if _MIPS_SIM == _MIPS_SIM_NABI32 +# define __NR_memfd_create 6318 +# endif +# if _MIPS_SIM == _MIPS_SIM_ABI64 +# define __NR_memfd_create 5314 +# endif +# elif defined __i386__ +# define __NR_memfd_create 356 +# else +# warning "__NR_memfd_create unknown for your architecture" +# endif +# endif + +static inline int memfd_create(const char *name, unsigned int flags) { +# ifdef __NR_memfd_create + return syscall(__NR_memfd_create, name, flags); +# else + errno = ENOSYS; + return -1; +# endif +} +#endif + +/* ======================================================================= */ + +#if !HAVE_DECL_GETRANDOM +# ifndef __NR_getrandom +# if defined __x86_64__ +# define __NR_getrandom 318 +# elif defined(__i386__) +# define __NR_getrandom 355 +# elif defined(__arm__) +# define __NR_getrandom 384 +# elif defined(__aarch64__) +# define __NR_getrandom 278 +# elif defined(__ia64__) +# define __NR_getrandom 1339 +# elif defined(__m68k__) +# define __NR_getrandom 352 +# elif defined(__s390x__) +# define __NR_getrandom 349 +# elif defined(__powerpc__) +# define __NR_getrandom 359 +# elif defined _MIPS_SIM +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# define __NR_getrandom 4353 +# endif +# if _MIPS_SIM == _MIPS_SIM_NABI32 +# define __NR_getrandom 6317 +# endif +# if _MIPS_SIM == _MIPS_SIM_ABI64 +# define __NR_getrandom 5313 +# endif +# else +# warning "__NR_getrandom unknown for your architecture" +# endif +# endif + +static inline int getrandom(void *buffer, size_t count, unsigned flags) { +# ifdef __NR_getrandom + return syscall(__NR_getrandom, buffer, count, flags); +# else + errno = ENOSYS; + return -1; +# endif +} +#endif + +/* ======================================================================= */ + +#if !HAVE_DECL_GETTID +static inline pid_t gettid(void) { + return (pid_t) syscall(SYS_gettid); +} +#endif + +/* ======================================================================= */ + +#if !HAVE_DECL_NAME_TO_HANDLE_AT +# ifndef __NR_name_to_handle_at +# if defined(__x86_64__) +# define __NR_name_to_handle_at 303 +# elif defined(__i386__) +# define __NR_name_to_handle_at 341 +# elif defined(__arm__) +# define __NR_name_to_handle_at 370 +# elif defined(__powerpc__) +# define __NR_name_to_handle_at 345 +# else +# error "__NR_name_to_handle_at is not defined" +# endif +# endif + +struct file_handle { + unsigned int handle_bytes; + int handle_type; + unsigned char f_handle[0]; +}; + +static inline int name_to_handle_at(int fd, const char *name, struct file_handle *handle, int *mnt_id, int flags) { +# ifdef __NR_name_to_handle_at + return syscall(__NR_name_to_handle_at, fd, name, handle, mnt_id, flags); +# else + errno = ENOSYS; + return -1; +# endif +} +#endif + +/* ======================================================================= */ + +#if !HAVE_DECL_SETNS +# ifndef __NR_setns +# if defined(__x86_64__) +# define __NR_setns 308 +# elif defined(__i386__) +# define __NR_setns 346 +# else +# error "__NR_setns is not defined" +# endif +# endif + +static inline int setns(int fd, int nstype) { +# ifdef __NR_setns + return syscall(__NR_setns, fd, nstype); +# else + errno = ENOSYS; + return -1; +# endif +} +#endif + +/* ======================================================================= */ + +static inline int raw_clone(unsigned long flags, void *child_stack) { +#if defined(__s390__) || defined(__CRIS__) + /* On s390 and cris the order of the first and second arguments + * of the raw clone() system call is reversed. */ + return (int) syscall(__NR_clone, child_stack, flags); +#else + return (int) syscall(__NR_clone, flags, child_stack); +#endif +} + +/* ======================================================================= */ + +static inline pid_t raw_getpid(void) { +#if defined(__alpha__) + return (pid_t) syscall(__NR_getxpid); +#else + return (pid_t) syscall(__NR_getpid); +#endif +} + +/* ======================================================================= */ + +#if !HAVE_DECL_RENAMEAT2 +# ifndef __NR_renameat2 +# if defined __x86_64__ +# define __NR_renameat2 316 +# elif defined __arm__ +# define __NR_renameat2 382 +# elif defined _MIPS_SIM +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# define __NR_renameat2 4351 +# endif +# if _MIPS_SIM == _MIPS_SIM_NABI32 +# define __NR_renameat2 6315 +# endif +# if _MIPS_SIM == _MIPS_SIM_ABI64 +# define __NR_renameat2 5311 +# endif +# elif defined __i386__ +# define __NR_renameat2 353 +# else +# warning "__NR_renameat2 unknown for your architecture" +# endif +# endif + +static inline int renameat2(int oldfd, const char *oldname, int newfd, const char *newname, unsigned flags) { +# ifdef __NR_renameat2 + return syscall(__NR_renameat2, oldfd, oldname, newfd, newname, flags); +# else + errno = ENOSYS; + return -1; +# endif +} +#endif + +/* ======================================================================= */ + +#if !HAVE_DECL_KCMP +static inline int kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2) { +# ifdef __NR_kcmp + return syscall(__NR_kcmp, pid1, pid2, type, idx1, idx2); +# else + errno = ENOSYS; + return -1; +# endif +} +#endif + +/* ======================================================================= */ + +#if !HAVE_DECL_KEYCTL +static inline long keyctl(int cmd, unsigned long arg2, unsigned long arg3, unsigned long arg4,unsigned long arg5) { +# ifdef __NR_keyctl + return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5); +# else + errno = ENOSYS; + return -1; +# endif +} + +static inline key_serial_t add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid) { +# ifdef __NR_add_key + return syscall(__NR_add_key, type, description, payload, plen, ringid); +# else + errno = ENOSYS; + return -1; +# endif +} + +static inline key_serial_t request_key(const char *type, const char *description, const char * callout_info, key_serial_t destringid) { +# ifdef __NR_request_key + return syscall(__NR_request_key, type, description, callout_info, destringid); +# else + errno = ENOSYS; + return -1; +# endif +} +#endif + +/* ======================================================================= */ + +#if !HAVE_DECL_COPY_FILE_RANGE +# ifndef __NR_copy_file_range +# if defined(__x86_64__) +# define __NR_copy_file_range 326 +# elif defined(__i386__) +# define __NR_copy_file_range 377 +# elif defined __s390__ +# define __NR_copy_file_range 375 +# elif defined __arm__ +# define __NR_copy_file_range 391 +# elif defined __aarch64__ +# define __NR_copy_file_range 285 +# else +# warning "__NR_copy_file_range not defined for your architecture" +# endif +# endif + +static inline ssize_t copy_file_range(int fd_in, loff_t *off_in, + int fd_out, loff_t *off_out, + size_t len, + unsigned int flags) { +# ifdef __NR_copy_file_range + return syscall(__NR_copy_file_range, fd_in, off_in, fd_out, off_out, len, flags); +# else + errno = ENOSYS; + return -1; +# endif +} +#endif diff --git a/src/libbasic/mkdir-label.c b/src/libbasic/mkdir-label.c new file mode 100644 index 0000000000..aa6878cdf0 --- /dev/null +++ b/src/libbasic/mkdir-label.c @@ -0,0 +1,38 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2013 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 +#include +#include + +#include "label.h" +#include "mkdir.h" + +int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid) { + return mkdir_safe_internal(path, mode, uid, gid, mkdir_label); +} + +int mkdir_parents_label(const char *path, mode_t mode) { + return mkdir_parents_internal(NULL, path, mode, mkdir_label); +} + +int mkdir_p_label(const char *path, mode_t mode) { + return mkdir_p_internal(NULL, path, mode, mkdir_label); +} diff --git a/src/libbasic/mkdir.c b/src/libbasic/mkdir.c new file mode 100644 index 0000000000..6b1a98402c --- /dev/null +++ b/src/libbasic/mkdir.c @@ -0,0 +1,128 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include + +#include "fs-util.h" +#include "macro.h" +#include "mkdir.h" +#include "path-util.h" +#include "stat-util.h" +#include "user-util.h" + +int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, mkdir_func_t _mkdir) { + struct stat st; + + if (_mkdir(path, mode) >= 0) + if (chmod_and_chown(path, mode, uid, gid) < 0) + return -errno; + + if (lstat(path, &st) < 0) + return -errno; + + if ((st.st_mode & 0007) > (mode & 0007) || + (st.st_mode & 0070) > (mode & 0070) || + (st.st_mode & 0700) > (mode & 0700) || + (uid != UID_INVALID && st.st_uid != uid) || + (gid != GID_INVALID && st.st_gid != gid) || + !S_ISDIR(st.st_mode)) + return -EEXIST; + + return 0; +} + +int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid) { + return mkdir_safe_internal(path, mode, uid, gid, mkdir); +} + +int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir) { + const char *p, *e; + int r; + + assert(path); + + if (prefix && !path_startswith(path, prefix)) + return -ENOTDIR; + + /* return immediately if directory exists */ + e = strrchr(path, '/'); + if (!e) + return -EINVAL; + + if (e == path) + return 0; + + p = strndupa(path, e - path); + r = is_dir(p, true); + if (r > 0) + return 0; + if (r == 0) + return -ENOTDIR; + + /* create every parent directory in the path, except the last component */ + p = path + strspn(path, "/"); + for (;;) { + char t[strlen(path) + 1]; + + e = p + strcspn(p, "/"); + p = e + strspn(e, "/"); + + /* Is this the last component? If so, then we're + * done */ + if (*p == 0) + return 0; + + memcpy(t, path, e - path); + t[e-path] = 0; + + if (prefix && path_startswith(prefix, t)) + continue; + + r = _mkdir(t, mode); + if (r < 0 && errno != EEXIST) + return -errno; + } +} + +int mkdir_parents(const char *path, mode_t mode) { + return mkdir_parents_internal(NULL, path, mode, mkdir); +} + +int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir) { + int r; + + /* Like mkdir -p */ + + r = mkdir_parents_internal(prefix, path, mode, _mkdir); + if (r < 0) + return r; + + r = _mkdir(path, mode); + if (r < 0 && (errno != EEXIST || is_dir(path, true) <= 0)) + return -errno; + + return 0; +} + +int mkdir_p(const char *path, mode_t mode) { + return mkdir_p_internal(NULL, path, mode, mkdir); +} diff --git a/src/libbasic/mkdir.h b/src/libbasic/mkdir.h new file mode 100644 index 0000000000..d564a3547f --- /dev/null +++ b/src/libbasic/mkdir.h @@ -0,0 +1,38 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2013 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 + +int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid); +int mkdir_parents(const char *path, mode_t mode); +int mkdir_p(const char *path, mode_t mode); + +/* mandatory access control(MAC) versions */ +int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid); +int mkdir_parents_label(const char *path, mode_t mode); +int mkdir_p_label(const char *path, mode_t mode); + +/* internally used */ +typedef int (*mkdir_func_t)(const char *pathname, mode_t mode); +int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, mkdir_func_t _mkdir); +int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir); +int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir); diff --git a/src/libbasic/mount-util.c b/src/libbasic/mount-util.c new file mode 100644 index 0000000000..ba698959b7 --- /dev/null +++ b/src/libbasic/mount-util.c @@ -0,0 +1,533 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "hashmap.h" +#include "mount-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "set.h" +#include "stdio-util.h" +#include "string-util.h" + +static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *mnt_id) { + char path[strlen("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)]; + _cleanup_free_ char *fdinfo = NULL; + _cleanup_close_ int subfd = -1; + char *p; + int r; + + if ((flags & AT_EMPTY_PATH) && isempty(filename)) + xsprintf(path, "/proc/self/fdinfo/%i", fd); + else { + subfd = openat(fd, filename, O_CLOEXEC|O_PATH); + if (subfd < 0) + return -errno; + + xsprintf(path, "/proc/self/fdinfo/%i", subfd); + } + + r = read_full_file(path, &fdinfo, NULL); + if (r == -ENOENT) /* The fdinfo directory is a relatively new addition */ + return -EOPNOTSUPP; + if (r < 0) + return -errno; + + p = startswith(fdinfo, "mnt_id:"); + if (!p) { + p = strstr(fdinfo, "\nmnt_id:"); + if (!p) /* The mnt_id field is a relatively new addition */ + return -EOPNOTSUPP; + + p += 8; + } + + p += strspn(p, WHITESPACE); + p[strcspn(p, WHITESPACE)] = 0; + + return safe_atoi(p, mnt_id); +} + + +int fd_is_mount_point(int fd, const char *filename, int flags) { + union file_handle_union h = FILE_HANDLE_INIT, h_parent = FILE_HANDLE_INIT; + int mount_id = -1, mount_id_parent = -1; + bool nosupp = false, check_st_dev = true; + struct stat a, b; + int r; + + assert(fd >= 0); + assert(filename); + + /* First we will try the name_to_handle_at() syscall, which + * tells us the mount id and an opaque file "handle". It is + * not supported everywhere though (kernel compile-time + * option, not all file systems are hooked up). If it works + * the mount id is usually good enough to tell us whether + * something is a mount point. + * + * If that didn't work we will try to read the mount id from + * /proc/self/fdinfo/. This is almost as good as + * name_to_handle_at(), however, does not return the + * opaque file handle. The opaque file handle is pretty useful + * to detect the root directory, which we should always + * consider a mount point. Hence we use this only as + * fallback. Exporting the mnt_id in fdinfo is a pretty recent + * kernel addition. + * + * As last fallback we do traditional fstat() based st_dev + * comparisons. This is how things were traditionally done, + * but unionfs breaks breaks this since it exposes file + * systems with a variety of st_dev reported. Also, btrfs + * subvolumes have different st_dev, even though they aren't + * real mounts of their own. */ + + r = name_to_handle_at(fd, filename, &h.handle, &mount_id, flags); + if (r < 0) { + if (errno == ENOSYS) + /* This kernel does not support name_to_handle_at() + * fall back to simpler logic. */ + goto fallback_fdinfo; + else if (errno == EOPNOTSUPP) + /* This kernel or file system does not support + * name_to_handle_at(), hence let's see if the + * upper fs supports it (in which case it is a + * mount point), otherwise fallback to the + * traditional stat() logic */ + nosupp = true; + else + return -errno; + } + + r = name_to_handle_at(fd, "", &h_parent.handle, &mount_id_parent, AT_EMPTY_PATH); + if (r < 0) { + if (errno == EOPNOTSUPP) { + if (nosupp) + /* Neither parent nor child do name_to_handle_at()? + We have no choice but to fall back. */ + goto fallback_fdinfo; + else + /* The parent can't do name_to_handle_at() but the + * directory we are interested in can? + * If so, it must be a mount point. */ + return 1; + } else + return -errno; + } + + /* The parent can do name_to_handle_at() but the + * directory we are interested in can't? If so, it + * must be a mount point. */ + if (nosupp) + return 1; + + /* If the file handle for the directory we are + * interested in and its parent are identical, we + * assume this is the root directory, which is a mount + * point. */ + + if (h.handle.handle_bytes == h_parent.handle.handle_bytes && + h.handle.handle_type == h_parent.handle.handle_type && + memcmp(h.handle.f_handle, h_parent.handle.f_handle, h.handle.handle_bytes) == 0) + return 1; + + return mount_id != mount_id_parent; + +fallback_fdinfo: + r = fd_fdinfo_mnt_id(fd, filename, flags, &mount_id); + if (r == -EOPNOTSUPP) + goto fallback_fstat; + if (r < 0) + return r; + + r = fd_fdinfo_mnt_id(fd, "", AT_EMPTY_PATH, &mount_id_parent); + if (r < 0) + return r; + + if (mount_id != mount_id_parent) + return 1; + + /* Hmm, so, the mount ids are the same. This leaves one + * special case though for the root file system. For that, + * let's see if the parent directory has the same inode as we + * are interested in. Hence, let's also do fstat() checks now, + * too, but avoid the st_dev comparisons, since they aren't + * that useful on unionfs mounts. */ + check_st_dev = false; + +fallback_fstat: + /* yay for fstatat() taking a different set of flags than the other + * _at() above */ + if (flags & AT_SYMLINK_FOLLOW) + flags &= ~AT_SYMLINK_FOLLOW; + else + flags |= AT_SYMLINK_NOFOLLOW; + if (fstatat(fd, filename, &a, flags) < 0) + return -errno; + + if (fstatat(fd, "", &b, AT_EMPTY_PATH) < 0) + return -errno; + + /* A directory with same device and inode as its parent? Must + * be the root directory */ + if (a.st_dev == b.st_dev && + a.st_ino == b.st_ino) + return 1; + + return check_st_dev && (a.st_dev != b.st_dev); +} + +/* flags can be AT_SYMLINK_FOLLOW or 0 */ +int path_is_mount_point(const char *t, int flags) { + _cleanup_close_ int fd = -1; + _cleanup_free_ char *canonical = NULL, *parent = NULL; + + assert(t); + + if (path_equal(t, "/")) + return 1; + + /* we need to resolve symlinks manually, we can't just rely on + * fd_is_mount_point() to do that for us; if we have a structure like + * /bin -> /usr/bin/ and /usr is a mount point, then the parent that we + * look at needs to be /usr, not /. */ + if (flags & AT_SYMLINK_FOLLOW) { + canonical = canonicalize_file_name(t); + if (!canonical) + return -errno; + + t = canonical; + } + + parent = dirname_malloc(t); + if (!parent) + return -ENOMEM; + + fd = openat(AT_FDCWD, parent, O_DIRECTORY|O_CLOEXEC|O_PATH); + if (fd < 0) + return -errno; + + return fd_is_mount_point(fd, basename(t), flags); +} + +int umount_recursive(const char *prefix, int flags) { + bool again; + int n = 0, r; + + /* Try to umount everything recursively below a + * directory. Also, take care of stacked mounts, and keep + * unmounting them until they are gone. */ + + do { + _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; + + again = false; + r = 0; + + proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); + if (!proc_self_mountinfo) + return -errno; + + for (;;) { + _cleanup_free_ char *path = NULL, *p = NULL; + int k; + + k = fscanf(proc_self_mountinfo, + "%*s " /* (1) mount id */ + "%*s " /* (2) parent id */ + "%*s " /* (3) major:minor */ + "%*s " /* (4) root */ + "%ms " /* (5) mount point */ + "%*s" /* (6) mount options */ + "%*[^-]" /* (7) optional fields */ + "- " /* (8) separator */ + "%*s " /* (9) file system type */ + "%*s" /* (10) mount source */ + "%*s" /* (11) mount options 2 */ + "%*[^\n]", /* some rubbish at the end */ + &path); + if (k != 1) { + if (k == EOF) + break; + + continue; + } + + r = cunescape(path, UNESCAPE_RELAX, &p); + if (r < 0) + return r; + + if (!path_startswith(p, prefix)) + continue; + + if (umount2(p, flags) < 0) { + r = -errno; + continue; + } + + again = true; + n++; + + break; + } + + } while (again); + + return r ? r : n; +} + +static int get_mount_flags(const char *path, unsigned long *flags) { + struct statvfs buf; + + if (statvfs(path, &buf) < 0) + return -errno; + *flags = buf.f_flag; + return 0; +} + +int bind_remount_recursive(const char *prefix, bool ro) { + _cleanup_set_free_free_ Set *done = NULL; + _cleanup_free_ char *cleaned = NULL; + int r; + + /* Recursively remount a directory (and all its submounts) + * read-only or read-write. If the directory is already + * mounted, we reuse the mount and simply mark it + * MS_BIND|MS_RDONLY (or remove the MS_RDONLY for read-write + * operation). If it isn't we first make it one. Afterwards we + * apply MS_BIND|MS_RDONLY (or remove MS_RDONLY) to all + * submounts we can access, too. When mounts are stacked on + * the same mount point we only care for each individual + * "top-level" mount on each point, as we cannot + * influence/access the underlying mounts anyway. We do not + * have any effect on future submounts that might get + * propagated, they migt be writable. This includes future + * submounts that have been triggered via autofs. */ + + cleaned = strdup(prefix); + if (!cleaned) + return -ENOMEM; + + path_kill_slashes(cleaned); + + done = set_new(&string_hash_ops); + if (!done) + return -ENOMEM; + + for (;;) { + _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; + _cleanup_set_free_free_ Set *todo = NULL; + bool top_autofs = false; + char *x; + unsigned long orig_flags; + + todo = set_new(&string_hash_ops); + if (!todo) + return -ENOMEM; + + proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); + if (!proc_self_mountinfo) + return -errno; + + for (;;) { + _cleanup_free_ char *path = NULL, *p = NULL, *type = NULL; + int k; + + k = fscanf(proc_self_mountinfo, + "%*s " /* (1) mount id */ + "%*s " /* (2) parent id */ + "%*s " /* (3) major:minor */ + "%*s " /* (4) root */ + "%ms " /* (5) mount point */ + "%*s" /* (6) mount options (superblock) */ + "%*[^-]" /* (7) optional fields */ + "- " /* (8) separator */ + "%ms " /* (9) file system type */ + "%*s" /* (10) mount source */ + "%*s" /* (11) mount options (bind mount) */ + "%*[^\n]", /* some rubbish at the end */ + &path, + &type); + if (k != 2) { + if (k == EOF) + break; + + continue; + } + + r = cunescape(path, UNESCAPE_RELAX, &p); + if (r < 0) + return r; + + /* Let's ignore autofs mounts. If they aren't + * triggered yet, we want to avoid triggering + * them, as we don't make any guarantees for + * future submounts anyway. If they are + * already triggered, then we will find + * another entry for this. */ + if (streq(type, "autofs")) { + top_autofs = top_autofs || path_equal(cleaned, p); + continue; + } + + if (path_startswith(p, cleaned) && + !set_contains(done, p)) { + + r = set_consume(todo, p); + p = NULL; + + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + } + + /* If we have no submounts to process anymore and if + * the root is either already done, or an autofs, we + * are done */ + if (set_isempty(todo) && + (top_autofs || set_contains(done, cleaned))) + return 0; + + if (!set_contains(done, cleaned) && + !set_contains(todo, cleaned)) { + /* The prefix directory itself is not yet a + * mount, make it one. */ + if (mount(cleaned, cleaned, NULL, MS_BIND|MS_REC, NULL) < 0) + return -errno; + + orig_flags = 0; + (void) get_mount_flags(cleaned, &orig_flags); + orig_flags &= ~MS_RDONLY; + + if (mount(NULL, prefix, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) + return -errno; + + x = strdup(cleaned); + if (!x) + return -ENOMEM; + + r = set_consume(done, x); + if (r < 0) + return r; + } + + while ((x = set_steal_first(todo))) { + + r = set_consume(done, x); + if (r == -EEXIST || r == 0) + continue; + if (r < 0) + return r; + + /* Try to reuse the original flag set, but + * don't care for errors, in case of + * obstructed mounts */ + orig_flags = 0; + (void) get_mount_flags(x, &orig_flags); + orig_flags &= ~MS_RDONLY; + + if (mount(NULL, x, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) { + + /* Deal with mount points that are + * obstructed by a later mount */ + + if (errno != ENOENT) + return -errno; + } + + } + } +} + +int mount_move_root(const char *path) { + assert(path); + + if (chdir(path) < 0) + return -errno; + + if (mount(path, "/", NULL, MS_MOVE, NULL) < 0) + return -errno; + + if (chroot(".") < 0) + return -errno; + + if (chdir("/") < 0) + return -errno; + + return 0; +} + +bool fstype_is_network(const char *fstype) { + static const char table[] = + "afs\0" + "cifs\0" + "smbfs\0" + "sshfs\0" + "ncpfs\0" + "ncp\0" + "nfs\0" + "nfs4\0" + "gfs\0" + "gfs2\0" + "glusterfs\0" + "pvfs2\0" /* OrangeFS */ + ; + + const char *x; + + x = startswith(fstype, "fuse."); + if (x) + fstype = x; + + return nulstr_contains(table, fstype); +} + +int repeat_unmount(const char *path, int flags) { + bool done = false; + + assert(path); + + /* If there are multiple mounts on a mount point, this + * removes them all */ + + for (;;) { + if (umount2(path, flags) < 0) { + + if (errno == EINVAL) + return done; + + return -errno; + } + + done = true; + } +} diff --git a/src/libbasic/mount-util.h b/src/libbasic/mount-util.h new file mode 100644 index 0000000000..bdb525d6b0 --- /dev/null +++ b/src/libbasic/mount-util.h @@ -0,0 +1,52 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include + +#include "macro.h" +#include "missing.h" + +int fd_is_mount_point(int fd, const char *filename, int flags); +int path_is_mount_point(const char *path, int flags); + +int repeat_unmount(const char *path, int flags); + +int umount_recursive(const char *target, int flags); +int bind_remount_recursive(const char *prefix, bool ro); + +int mount_move_root(const char *path); + +DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, endmntent); +#define _cleanup_endmntent_ _cleanup_(endmntentp) + +bool fstype_is_network(const char *fstype); + +union file_handle_union { + struct file_handle handle; + char padding[sizeof(struct file_handle) + MAX_HANDLE_SZ]; +}; + +#define FILE_HANDLE_INIT { .handle.handle_bytes = MAX_HANDLE_SZ } diff --git a/src/libbasic/nss-util.h b/src/libbasic/nss-util.h new file mode 100644 index 0000000000..bf7c4854fc --- /dev/null +++ b/src/libbasic/nss-util.h @@ -0,0 +1,199 @@ +#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 +#include +#include +#include +#include + +#define NSS_SIGNALS_BLOCK SIGALRM,SIGVTALRM,SIGPIPE,SIGCHLD,SIGTSTP,SIGIO,SIGHUP,SIGUSR1,SIGUSR2,SIGPROF,SIGURG,SIGWINCH + +#define NSS_GETHOSTBYNAME_PROTOTYPES(module) \ +enum nss_status _nss_##module##_gethostbyname4_r( \ + const char *name, \ + struct gaih_addrtuple **pat, \ + char *buffer, size_t buflen, \ + int *errnop, int *h_errnop, \ + int32_t *ttlp) _public_; \ +enum nss_status _nss_##module##_gethostbyname3_r( \ + const char *name, \ + int af, \ + struct hostent *host, \ + char *buffer, size_t buflen, \ + int *errnop, int *h_errnop, \ + int32_t *ttlp, \ + char **canonp) _public_; \ +enum nss_status _nss_##module##_gethostbyname2_r( \ + const char *name, \ + int af, \ + struct hostent *host, \ + char *buffer, size_t buflen, \ + int *errnop, int *h_errnop) _public_; \ +enum nss_status _nss_##module##_gethostbyname_r( \ + const char *name, \ + struct hostent *host, \ + char *buffer, size_t buflen, \ + int *errnop, int *h_errnop) _public_ + +#define NSS_GETHOSTBYADDR_PROTOTYPES(module) \ +enum nss_status _nss_##module##_gethostbyaddr2_r( \ + const void* addr, socklen_t len, \ + int af, \ + struct hostent *host, \ + char *buffer, size_t buflen, \ + int *errnop, int *h_errnop, \ + int32_t *ttlp) _public_; \ +enum nss_status _nss_##module##_gethostbyaddr_r( \ + const void* addr, socklen_t len, \ + int af, \ + struct hostent *host, \ + char *buffer, size_t buflen, \ + int *errnop, int *h_errnop) _public_ + +#define NSS_GETHOSTBYNAME_FALLBACKS(module) \ +enum nss_status _nss_##module##_gethostbyname2_r( \ + const char *name, \ + int af, \ + struct hostent *host, \ + char *buffer, size_t buflen, \ + int *errnop, int *h_errnop) { \ + return _nss_##module##_gethostbyname3_r( \ + name, \ + af, \ + host, \ + buffer, buflen, \ + errnop, h_errnop, \ + NULL, \ + NULL); \ +} \ +enum nss_status _nss_##module##_gethostbyname_r( \ + const char *name, \ + struct hostent *host, \ + char *buffer, size_t buflen, \ + int *errnop, int *h_errnop) { \ + enum nss_status ret = NSS_STATUS_NOTFOUND; \ + \ + if (_res.options & RES_USE_INET6) \ + ret = _nss_##module##_gethostbyname3_r( \ + name, \ + AF_INET6, \ + host, \ + buffer, buflen, \ + errnop, h_errnop, \ + NULL, \ + NULL); \ + if (ret == NSS_STATUS_NOTFOUND) \ + ret = _nss_##module##_gethostbyname3_r( \ + name, \ + AF_INET, \ + host, \ + buffer, buflen, \ + errnop, h_errnop, \ + NULL, \ + NULL); \ + return ret; \ +} \ +struct __useless_struct_to_allow_trailing_semicolon__ + +#define NSS_GETHOSTBYADDR_FALLBACKS(module) \ +enum nss_status _nss_##module##_gethostbyaddr_r( \ + const void* addr, socklen_t len, \ + int af, \ + struct hostent *host, \ + char *buffer, size_t buflen, \ + int *errnop, int *h_errnop) { \ + return _nss_##module##_gethostbyaddr2_r( \ + addr, len, \ + af, \ + host, \ + buffer, buflen, \ + errnop, h_errnop, \ + NULL); \ +} \ +struct __useless_struct_to_allow_trailing_semicolon__ + +#define NSS_GETPW_PROTOTYPES(module) \ +enum nss_status _nss_##module##_getpwnam_r( \ + const char *name, \ + struct passwd *pwd, \ + char *buffer, size_t buflen, \ + int *errnop) _public_; \ +enum nss_status _nss_mymachines_getpwuid_r( \ + uid_t uid, \ + struct passwd *pwd, \ + char *buffer, size_t buflen, \ + int *errnop) _public_ + +#define NSS_GETGR_PROTOTYPES(module) \ +enum nss_status _nss_##module##_getgrnam_r( \ + const char *name, \ + struct group *gr, \ + char *buffer, size_t buflen, \ + int *errnop) _public_; \ +enum nss_status _nss_##module##_getgrgid_r( \ + gid_t gid, \ + struct group *gr, \ + char *buffer, size_t buflen, \ + int *errnop) _public_ + +typedef enum nss_status (*_nss_gethostbyname4_r_t)( + const char *name, + struct gaih_addrtuple **pat, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp); + +typedef enum nss_status (*_nss_gethostbyname3_r_t)( + const char *name, + int af, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp, + char **canonp); + +typedef enum nss_status (*_nss_gethostbyname2_r_t)( + const char *name, + int af, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop); + +typedef enum nss_status (*_nss_gethostbyname_r_t)( + const char *name, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop); + +typedef enum nss_status (*_nss_gethostbyaddr2_r_t)( + const void* addr, socklen_t len, + int af, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp); +typedef enum nss_status (*_nss_gethostbyaddr_r_t)( + const void* addr, socklen_t len, + int af, + struct hostent *host, + char *buffer, size_t buflen, + int *errnop, int *h_errnop); diff --git a/src/libbasic/ordered-set.c b/src/libbasic/ordered-set.c new file mode 100644 index 0000000000..2e0bdf6488 --- /dev/null +++ b/src/libbasic/ordered-set.c @@ -0,0 +1,64 @@ +/*** + This file is part of systemd. + + Copyright 2016 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 "ordered-set.h" +#include "strv.h" + +int ordered_set_consume(OrderedSet *s, void *p) { + int r; + + r = ordered_set_put(s, p); + if (r <= 0) + free(p); + + return r; +} + +int ordered_set_put_strdup(OrderedSet *s, const char *p) { + char *c; + int r; + + assert(s); + assert(p); + + c = strdup(p); + if (!c) + return -ENOMEM; + + r = ordered_set_consume(s, c); + if (r == -EEXIST) + return 0; + + return r; +} + +int ordered_set_put_strdupv(OrderedSet *s, char **l) { + int n = 0, r; + char **i; + + STRV_FOREACH(i, l) { + r = ordered_set_put_strdup(s, *i); + if (r < 0) + return r; + + n += r; + } + + return n; +} diff --git a/src/libbasic/ordered-set.h b/src/libbasic/ordered-set.h new file mode 100644 index 0000000000..e1dfc86380 --- /dev/null +++ b/src/libbasic/ordered-set.h @@ -0,0 +1,74 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 "hashmap.h" + +typedef struct OrderedSet OrderedSet; + +static inline OrderedSet* ordered_set_new(const struct hash_ops *ops) { + return (OrderedSet*) ordered_hashmap_new(ops); +} + +static inline int ordered_set_ensure_allocated(OrderedSet **s, const struct hash_ops *ops) { + if (*s) + return 0; + + *s = ordered_set_new(ops); + if (!*s) + return -ENOMEM; + + return 0; +} + +static inline OrderedSet* ordered_set_free(OrderedSet *s) { + ordered_hashmap_free((OrderedHashmap*) s); + return NULL; +} + +static inline OrderedSet* ordered_set_free_free(OrderedSet *s) { + ordered_hashmap_free_free((OrderedHashmap*) s); + return NULL; +} + +static inline int ordered_set_put(OrderedSet *s, void *p) { + return ordered_hashmap_put((OrderedHashmap*) s, p, p); +} + +static inline bool ordered_set_isempty(OrderedSet *s) { + return ordered_hashmap_isempty((OrderedHashmap*) s); +} + +static inline bool ordered_set_iterate(OrderedSet *s, Iterator *i, void **value) { + return ordered_hashmap_iterate((OrderedHashmap*) s, i, value, NULL); +} + +int ordered_set_consume(OrderedSet *s, void *p); +int ordered_set_put_strdup(OrderedSet *s, const char *p); +int ordered_set_put_strdupv(OrderedSet *s, char **l); + +#define ORDERED_SET_FOREACH(e, s, i) \ + for ((i) = ITERATOR_FIRST; ordered_set_iterate((s), &(i), (void**)&(e)); ) + +DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedSet*, ordered_set_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedSet*, ordered_set_free_free); + +#define _cleanup_ordered_set_free_ _cleanup_(ordered_set_freep) +#define _cleanup_ordered_set_free_free_ _cleanup_(ordered_set_free_freep) diff --git a/src/libbasic/parse-util.c b/src/libbasic/parse-util.c new file mode 100644 index 0000000000..6c11b605a9 --- /dev/null +++ b/src/libbasic/parse-util.c @@ -0,0 +1,534 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "extract-word.h" +#include "macro.h" +#include "parse-util.h" +#include "string-util.h" + +int parse_boolean(const char *v) { + assert(v); + + if (streq(v, "1") || strcaseeq(v, "yes") || strcaseeq(v, "y") || strcaseeq(v, "true") || strcaseeq(v, "t") || strcaseeq(v, "on")) + return 1; + else if (streq(v, "0") || strcaseeq(v, "no") || strcaseeq(v, "n") || strcaseeq(v, "false") || strcaseeq(v, "f") || strcaseeq(v, "off")) + return 0; + + return -EINVAL; +} + +int parse_pid(const char *s, pid_t* ret_pid) { + unsigned long ul = 0; + pid_t pid; + int r; + + assert(s); + assert(ret_pid); + + r = safe_atolu(s, &ul); + if (r < 0) + return r; + + pid = (pid_t) ul; + + if ((unsigned long) pid != ul) + return -ERANGE; + + if (pid <= 0) + return -ERANGE; + + *ret_pid = pid; + return 0; +} + +int parse_mode(const char *s, mode_t *ret) { + char *x; + long l; + + assert(s); + assert(ret); + + s += strspn(s, WHITESPACE); + if (s[0] == '-') + return -ERANGE; + + errno = 0; + l = strtol(s, &x, 8); + if (errno > 0) + return -errno; + if (!x || x == s || *x) + return -EINVAL; + if (l < 0 || l > 07777) + return -ERANGE; + + *ret = (mode_t) l; + return 0; +} + +int parse_ifindex(const char *s, int *ret) { + int ifi, r; + + r = safe_atoi(s, &ifi); + if (r < 0) + return r; + if (ifi <= 0) + return -EINVAL; + + *ret = ifi; + return 0; +} + +int parse_size(const char *t, uint64_t base, uint64_t *size) { + + /* Soo, sometimes we want to parse IEC binary suffixes, and + * sometimes SI decimal suffixes. This function can parse + * both. Which one is the right way depends on the + * context. Wikipedia suggests that SI is customary for + * hardware metrics and network speeds, while IEC is + * customary for most data sizes used by software and volatile + * (RAM) memory. Hence be careful which one you pick! + * + * In either case we use just K, M, G as suffix, and not Ki, + * Mi, Gi or so (as IEC would suggest). That's because that's + * frickin' ugly. But this means you really need to make sure + * to document which base you are parsing when you use this + * call. */ + + struct table { + const char *suffix; + unsigned long long factor; + }; + + static const struct table iec[] = { + { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + { "T", 1024ULL*1024ULL*1024ULL*1024ULL }, + { "G", 1024ULL*1024ULL*1024ULL }, + { "M", 1024ULL*1024ULL }, + { "K", 1024ULL }, + { "B", 1ULL }, + { "", 1ULL }, + }; + + static const struct table si[] = { + { "E", 1000ULL*1000ULL*1000ULL*1000ULL*1000ULL*1000ULL }, + { "P", 1000ULL*1000ULL*1000ULL*1000ULL*1000ULL }, + { "T", 1000ULL*1000ULL*1000ULL*1000ULL }, + { "G", 1000ULL*1000ULL*1000ULL }, + { "M", 1000ULL*1000ULL }, + { "K", 1000ULL }, + { "B", 1ULL }, + { "", 1ULL }, + }; + + const struct table *table; + const char *p; + unsigned long long r = 0; + unsigned n_entries, start_pos = 0; + + assert(t); + assert(base == 1000 || base == 1024); + assert(size); + + if (base == 1000) { + table = si; + n_entries = ELEMENTSOF(si); + } else { + table = iec; + n_entries = ELEMENTSOF(iec); + } + + p = t; + do { + unsigned long long l, tmp; + double frac = 0; + char *e; + unsigned i; + + p += strspn(p, WHITESPACE); + + errno = 0; + l = strtoull(p, &e, 10); + if (errno > 0) + return -errno; + if (e == p) + return -EINVAL; + if (*p == '-') + return -ERANGE; + + if (*e == '.') { + e++; + + /* strtoull() itself would accept space/+/- */ + if (*e >= '0' && *e <= '9') { + unsigned long long l2; + char *e2; + + l2 = strtoull(e, &e2, 10); + if (errno > 0) + return -errno; + + /* Ignore failure. E.g. 10.M is valid */ + frac = l2; + for (; e < e2; e++) + frac /= 10; + } + } + + e += strspn(e, WHITESPACE); + + for (i = start_pos; i < n_entries; i++) + if (startswith(e, table[i].suffix)) + break; + + if (i >= n_entries) + return -EINVAL; + + if (l + (frac > 0) > ULLONG_MAX / table[i].factor) + return -ERANGE; + + tmp = l * table[i].factor + (unsigned long long) (frac * table[i].factor); + if (tmp > ULLONG_MAX - r) + return -ERANGE; + + r += tmp; + if ((unsigned long long) (uint64_t) r != r) + return -ERANGE; + + p = e + strlen(table[i].suffix); + + start_pos = i + 1; + + } while (*p); + + *size = r; + + return 0; +} + +int parse_range(const char *t, unsigned *lower, unsigned *upper) { + _cleanup_free_ char *word = NULL; + unsigned l, u; + int r; + + assert(lower); + assert(upper); + + /* Extract the lower bound. */ + r = extract_first_word(&t, &word, "-", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + r = safe_atou(word, &l); + if (r < 0) + return r; + + /* Check for the upper bound and extract it if needed */ + if (!t) + /* Single number with no dashes. */ + u = l; + else if (!*t) + /* Trailing dash is an error. */ + return -EINVAL; + else { + r = safe_atou(t, &u); + if (r < 0) + return r; + } + + *lower = l; + *upper = u; + return 0; +} + +char *format_bytes(char *buf, size_t l, uint64_t t) { + unsigned i; + + /* This only does IEC units so far */ + + static const struct { + const char *suffix; + uint64_t factor; + } table[] = { + { "E", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, + { "P", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, + { "T", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, + { "G", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, + { "M", UINT64_C(1024)*UINT64_C(1024) }, + { "K", UINT64_C(1024) }, + }; + + if (t == (uint64_t) -1) + return NULL; + + for (i = 0; i < ELEMENTSOF(table); i++) { + + if (t >= table[i].factor) { + snprintf(buf, l, + "%" PRIu64 ".%" PRIu64 "%s", + t / table[i].factor, + ((t*UINT64_C(10)) / table[i].factor) % UINT64_C(10), + table[i].suffix); + + goto finish; + } + } + + snprintf(buf, l, "%" PRIu64 "B", t); + +finish: + buf[l-1] = 0; + return buf; + +} + +int safe_atou(const char *s, unsigned *ret_u) { + char *x = NULL; + unsigned long l; + + assert(s); + assert(ret_u); + + /* strtoul() is happy to parse negative values, and silently + * converts them to unsigned values without generating an + * error. We want a clean error, hence let's look for the "-" + * prefix on our own, and generate an error. But let's do so + * only after strtoul() validated that the string is clean + * otherwise, so that we return EINVAL preferably over + * ERANGE. */ + + s += strspn(s, WHITESPACE); + + errno = 0; + l = strtoul(s, &x, 0); + if (errno > 0) + return -errno; + if (!x || x == s || *x) + return -EINVAL; + if (s[0] == '-') + return -ERANGE; + if ((unsigned long) (unsigned) l != l) + return -ERANGE; + + *ret_u = (unsigned) l; + return 0; +} + +int safe_atoi(const char *s, int *ret_i) { + char *x = NULL; + long l; + + assert(s); + assert(ret_i); + + errno = 0; + l = strtol(s, &x, 0); + if (errno > 0) + return -errno; + if (!x || x == s || *x) + return -EINVAL; + if ((long) (int) l != l) + return -ERANGE; + + *ret_i = (int) l; + return 0; +} + +int safe_atollu(const char *s, long long unsigned *ret_llu) { + char *x = NULL; + unsigned long long l; + + assert(s); + assert(ret_llu); + + s += strspn(s, WHITESPACE); + + errno = 0; + l = strtoull(s, &x, 0); + if (errno > 0) + return -errno; + if (!x || x == s || *x) + return -EINVAL; + if (*s == '-') + return -ERANGE; + + *ret_llu = l; + return 0; +} + +int safe_atolli(const char *s, long long int *ret_lli) { + char *x = NULL; + long long l; + + assert(s); + assert(ret_lli); + + errno = 0; + l = strtoll(s, &x, 0); + if (errno > 0) + return -errno; + if (!x || x == s || *x) + return -EINVAL; + + *ret_lli = l; + return 0; +} + +int safe_atou8(const char *s, uint8_t *ret) { + char *x = NULL; + unsigned long l; + + assert(s); + assert(ret); + + s += strspn(s, WHITESPACE); + + errno = 0; + l = strtoul(s, &x, 0); + if (errno > 0) + return -errno; + if (!x || x == s || *x) + return -EINVAL; + if (s[0] == '-') + return -ERANGE; + if ((unsigned long) (uint8_t) l != l) + return -ERANGE; + + *ret = (uint8_t) l; + return 0; +} + +int safe_atou16(const char *s, uint16_t *ret) { + char *x = NULL; + unsigned long l; + + assert(s); + assert(ret); + + s += strspn(s, WHITESPACE); + + errno = 0; + l = strtoul(s, &x, 0); + if (errno > 0) + return -errno; + if (!x || x == s || *x) + return -EINVAL; + if (s[0] == '-') + return -ERANGE; + if ((unsigned long) (uint16_t) l != l) + return -ERANGE; + + *ret = (uint16_t) l; + return 0; +} + +int safe_atoi16(const char *s, int16_t *ret) { + char *x = NULL; + long l; + + assert(s); + assert(ret); + + errno = 0; + l = strtol(s, &x, 0); + if (errno > 0) + return -errno; + if (!x || x == s || *x) + return -EINVAL; + if ((long) (int16_t) l != l) + return -ERANGE; + + *ret = (int16_t) l; + return 0; +} + +int safe_atod(const char *s, double *ret_d) { + char *x = NULL; + double d = 0; + locale_t loc; + + assert(s); + assert(ret_d); + + loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); + if (loc == (locale_t) 0) + return -errno; + + errno = 0; + d = strtod_l(s, &x, loc); + if (errno > 0) { + freelocale(loc); + return -errno; + } + if (!x || x == s || *x) { + freelocale(loc); + return -EINVAL; + } + + freelocale(loc); + *ret_d = (double) d; + return 0; +} + +int parse_fractional_part_u(const char **p, size_t digits, unsigned *res) { + size_t i; + unsigned val = 0; + const char *s; + + s = *p; + + /* accept any number of digits, strtoull is limted to 19 */ + for (i=0; i < digits; i++,s++) { + if (*s < '0' || *s > '9') { + if (i == 0) + return -EINVAL; + + /* too few digits, pad with 0 */ + for (; i < digits; i++) + val *= 10; + + break; + } + + val *= 10; + val += *s - '0'; + } + + /* maybe round up */ + if (*s >= '5' && *s <= '9') + val++; + + s += strspn(s, DIGITS); + + *p = s; + *res = val; + + return 0; +} diff --git a/src/libbasic/parse-util.h b/src/libbasic/parse-util.h new file mode 100644 index 0000000000..7dc579a159 --- /dev/null +++ b/src/libbasic/parse-util.h @@ -0,0 +1,107 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include + +#include "macro.h" + +#define MODE_INVALID ((mode_t) -1) + +int parse_boolean(const char *v) _pure_; +int parse_pid(const char *s, pid_t* ret_pid); +int parse_mode(const char *s, mode_t *ret); +int parse_ifindex(const char *s, int *ret); + +int parse_size(const char *t, uint64_t base, uint64_t *size); +int parse_range(const char *t, unsigned *lower, unsigned *upper); + +#define FORMAT_BYTES_MAX 8 +char *format_bytes(char *buf, size_t l, uint64_t t); + +int safe_atou(const char *s, unsigned *ret_u); +int safe_atoi(const char *s, int *ret_i); +int safe_atollu(const char *s, unsigned long long *ret_u); +int safe_atolli(const char *s, long long int *ret_i); + +int safe_atou8(const char *s, uint8_t *ret); + +int safe_atou16(const char *s, uint16_t *ret); +int safe_atoi16(const char *s, int16_t *ret); + +static inline int safe_atou32(const char *s, uint32_t *ret_u) { + assert_cc(sizeof(uint32_t) == sizeof(unsigned)); + return safe_atou(s, (unsigned*) ret_u); +} + +static inline int safe_atoi32(const char *s, int32_t *ret_i) { + assert_cc(sizeof(int32_t) == sizeof(int)); + return safe_atoi(s, (int*) ret_i); +} + +static inline int safe_atou64(const char *s, uint64_t *ret_u) { + assert_cc(sizeof(uint64_t) == sizeof(unsigned long long)); + return safe_atollu(s, (unsigned long long*) ret_u); +} + +static inline int safe_atoi64(const char *s, int64_t *ret_i) { + assert_cc(sizeof(int64_t) == sizeof(long long int)); + return safe_atolli(s, (long long int*) ret_i); +} + +#if LONG_MAX == INT_MAX +static inline int safe_atolu(const char *s, unsigned long *ret_u) { + assert_cc(sizeof(unsigned long) == sizeof(unsigned)); + return safe_atou(s, (unsigned*) ret_u); +} +static inline int safe_atoli(const char *s, long int *ret_u) { + assert_cc(sizeof(long int) == sizeof(int)); + return safe_atoi(s, (int*) ret_u); +} +#else +static inline int safe_atolu(const char *s, unsigned long *ret_u) { + assert_cc(sizeof(unsigned long) == sizeof(unsigned long long)); + return safe_atollu(s, (unsigned long long*) ret_u); +} +static inline int safe_atoli(const char *s, long int *ret_u) { + assert_cc(sizeof(long int) == sizeof(long long int)); + return safe_atolli(s, (long long int*) ret_u); +} +#endif + +#if SIZE_MAX == UINT_MAX +static inline int safe_atozu(const char *s, size_t *ret_u) { + assert_cc(sizeof(size_t) == sizeof(unsigned)); + return safe_atou(s, (unsigned *) ret_u); +} +#else +static inline int safe_atozu(const char *s, size_t *ret_u) { + assert_cc(sizeof(size_t) == sizeof(long unsigned)); + return safe_atolu(s, ret_u); +} +#endif + +int safe_atod(const char *s, double *ret_d); + +int parse_fractional_part_u(const char **s, size_t digits, unsigned *res); diff --git a/src/libbasic/path-util.c b/src/libbasic/path-util.c new file mode 100644 index 0000000000..b2fa81a294 --- /dev/null +++ b/src/libbasic/path-util.c @@ -0,0 +1,816 @@ +/*** + This file is part of systemd. + + Copyright 2010-2012 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 +#include +#include +#include +#include +#include + +/* When we include libgen.h because we need dirname() we immediately + * undefine basename() since libgen.h defines it as a macro to the + * POSIX version which is really broken. We prefer GNU basename(). */ +#include +#undef basename + +#include "alloc-util.h" +#include "extract-word.h" +#include "fs-util.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "path-util.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" + +bool path_is_absolute(const char *p) { + return p[0] == '/'; +} + +bool is_path(const char *p) { + return !!strchr(p, '/'); +} + +int path_split_and_make_absolute(const char *p, char ***ret) { + char **l; + int r; + + assert(p); + assert(ret); + + l = strv_split(p, ":"); + if (!l) + return -ENOMEM; + + r = path_strv_make_absolute_cwd(l); + if (r < 0) { + strv_free(l); + return r; + } + + *ret = l; + return r; +} + +char *path_make_absolute(const char *p, const char *prefix) { + assert(p); + + /* Makes every item in the list an absolute path by prepending + * the prefix, if specified and necessary */ + + if (path_is_absolute(p) || !prefix) + return strdup(p); + + return strjoin(prefix, "/", p, NULL); +} + +int path_make_absolute_cwd(const char *p, char **ret) { + char *c; + + assert(p); + assert(ret); + + /* Similar to path_make_absolute(), but prefixes with the + * current working directory. */ + + if (path_is_absolute(p)) + c = strdup(p); + else { + _cleanup_free_ char *cwd = NULL; + + cwd = get_current_dir_name(); + if (!cwd) + return negative_errno(); + + c = strjoin(cwd, "/", p, NULL); + } + if (!c) + return -ENOMEM; + + *ret = c; + return 0; +} + +int path_make_relative(const char *from_dir, const char *to_path, char **_r) { + char *r, *p; + unsigned n_parents; + + assert(from_dir); + assert(to_path); + assert(_r); + + /* Strips the common part, and adds ".." elements as necessary. */ + + if (!path_is_absolute(from_dir)) + return -EINVAL; + + if (!path_is_absolute(to_path)) + return -EINVAL; + + /* Skip the common part. */ + for (;;) { + size_t a; + size_t b; + + from_dir += strspn(from_dir, "/"); + to_path += strspn(to_path, "/"); + + if (!*from_dir) { + if (!*to_path) + /* from_dir equals to_path. */ + r = strdup("."); + else + /* from_dir is a parent directory of to_path. */ + r = strdup(to_path); + + if (!r) + return -ENOMEM; + + path_kill_slashes(r); + + *_r = r; + return 0; + } + + if (!*to_path) + break; + + a = strcspn(from_dir, "/"); + b = strcspn(to_path, "/"); + + if (a != b) + break; + + if (memcmp(from_dir, to_path, a) != 0) + break; + + from_dir += a; + to_path += b; + } + + /* If we're here, then "from_dir" has one or more elements that need to + * be replaced with "..". */ + + /* Count the number of necessary ".." elements. */ + for (n_parents = 0;;) { + from_dir += strspn(from_dir, "/"); + + if (!*from_dir) + break; + + from_dir += strcspn(from_dir, "/"); + n_parents++; + } + + r = malloc(n_parents * 3 + strlen(to_path) + 1); + if (!r) + return -ENOMEM; + + for (p = r; n_parents > 0; n_parents--, p += 3) + memcpy(p, "../", 3); + + strcpy(p, to_path); + path_kill_slashes(r); + + *_r = r; + return 0; +} + +int path_strv_make_absolute_cwd(char **l) { + char **s; + int r; + + /* Goes through every item in the string list and makes it + * absolute. This works in place and won't rollback any + * changes on failure. */ + + STRV_FOREACH(s, l) { + char *t; + + r = path_make_absolute_cwd(*s, &t); + if (r < 0) + return r; + + free(*s); + *s = t; + } + + return 0; +} + +char **path_strv_resolve(char **l, const char *prefix) { + char **s; + unsigned k = 0; + bool enomem = false; + + if (strv_isempty(l)) + return l; + + /* Goes through every item in the string list and canonicalize + * the path. This works in place and won't rollback any + * changes on failure. */ + + STRV_FOREACH(s, l) { + char *t, *u; + _cleanup_free_ char *orig = NULL; + + if (!path_is_absolute(*s)) { + free(*s); + continue; + } + + if (prefix) { + orig = *s; + t = strappend(prefix, orig); + if (!t) { + enomem = true; + continue; + } + } else + t = *s; + + errno = 0; + u = canonicalize_file_name(t); + if (!u) { + if (errno == ENOENT) { + if (prefix) { + u = orig; + orig = NULL; + free(t); + } else + u = t; + } else { + free(t); + if (errno == ENOMEM || errno == 0) + enomem = true; + + continue; + } + } else if (prefix) { + char *x; + + free(t); + x = path_startswith(u, prefix); + if (x) { + /* restore the slash if it was lost */ + if (!startswith(x, "/")) + *(--x) = '/'; + + t = strdup(x); + free(u); + if (!t) { + enomem = true; + continue; + } + u = t; + } else { + /* canonicalized path goes outside of + * prefix, keep the original path instead */ + free(u); + u = orig; + orig = NULL; + } + } else + free(t); + + l[k++] = u; + } + + l[k] = NULL; + + if (enomem) + return NULL; + + return l; +} + +char **path_strv_resolve_uniq(char **l, const char *prefix) { + + if (strv_isempty(l)) + return l; + + if (!path_strv_resolve(l, prefix)) + return NULL; + + return strv_uniq(l); +} + +char *path_kill_slashes(char *path) { + char *f, *t; + bool slash = false; + + /* Removes redundant inner and trailing slashes. Modifies the + * passed string in-place. + * + * ///foo///bar/ becomes /foo/bar + */ + + for (f = path, t = path; *f; f++) { + + if (*f == '/') { + slash = true; + continue; + } + + if (slash) { + slash = false; + *(t++) = '/'; + } + + *(t++) = *f; + } + + /* Special rule, if we are talking of the root directory, a + trailing slash is good */ + + if (t == path && slash) + *(t++) = '/'; + + *t = 0; + return path; +} + +char* path_startswith(const char *path, const char *prefix) { + assert(path); + assert(prefix); + + if ((path[0] == '/') != (prefix[0] == '/')) + return NULL; + + for (;;) { + size_t a, b; + + path += strspn(path, "/"); + prefix += strspn(prefix, "/"); + + if (*prefix == 0) + return (char*) path; + + if (*path == 0) + return NULL; + + a = strcspn(path, "/"); + b = strcspn(prefix, "/"); + + if (a != b) + return NULL; + + if (memcmp(path, prefix, a) != 0) + return NULL; + + path += a; + prefix += b; + } +} + +int path_compare(const char *a, const char *b) { + int d; + + assert(a); + assert(b); + + /* A relative path and an abolute path must not compare as equal. + * Which one is sorted before the other does not really matter. + * Here a relative path is ordered before an absolute path. */ + d = (a[0] == '/') - (b[0] == '/'); + if (d != 0) + return d; + + for (;;) { + size_t j, k; + + a += strspn(a, "/"); + b += strspn(b, "/"); + + if (*a == 0 && *b == 0) + return 0; + + /* Order prefixes first: "/foo" before "/foo/bar" */ + if (*a == 0) + return -1; + if (*b == 0) + return 1; + + j = strcspn(a, "/"); + k = strcspn(b, "/"); + + /* Alphabetical sort: "/foo/aaa" before "/foo/b" */ + d = memcmp(a, b, MIN(j, k)); + if (d != 0) + return (d > 0) - (d < 0); /* sign of d */ + + /* Sort "/foo/a" before "/foo/aaa" */ + d = (j > k) - (j < k); /* sign of (j - k) */ + if (d != 0) + return d; + + a += j; + b += k; + } +} + +bool path_equal(const char *a, const char *b) { + return path_compare(a, b) == 0; +} + +bool path_equal_or_files_same(const char *a, const char *b) { + return path_equal(a, b) || files_same(a, b) > 0; +} + +char* path_join(const char *root, const char *path, const char *rest) { + assert(path); + + if (!isempty(root)) + return strjoin(root, endswith(root, "/") ? "" : "/", + path[0] == '/' ? path+1 : path, + rest ? (endswith(path, "/") ? "" : "/") : NULL, + rest && rest[0] == '/' ? rest+1 : rest, + NULL); + else + return strjoin(path, + rest ? (endswith(path, "/") ? "" : "/") : NULL, + rest && rest[0] == '/' ? rest+1 : rest, + NULL); +} + +int find_binary(const char *name, char **ret) { + int last_error, r; + const char *p; + + assert(name); + + if (is_path(name)) { + if (access(name, X_OK) < 0) + return -errno; + + if (ret) { + r = path_make_absolute_cwd(name, ret); + if (r < 0) + return r; + } + + return 0; + } + + /** + * Plain getenv, not secure_getenv, because we want + * to actually allow the user to pick the binary. + */ + p = getenv("PATH"); + if (!p) + p = DEFAULT_PATH; + + last_error = -ENOENT; + + for (;;) { + _cleanup_free_ char *j = NULL, *element = NULL; + + r = extract_first_word(&p, &element, ":", EXTRACT_RELAX|EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0) + break; + + if (!path_is_absolute(element)) + continue; + + j = strjoin(element, "/", name, NULL); + if (!j) + return -ENOMEM; + + if (access(j, X_OK) >= 0) { + /* Found it! */ + + if (ret) { + *ret = path_kill_slashes(j); + j = NULL; + } + + return 0; + } + + last_error = -errno; + } + + return last_error; +} + +bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) { + bool changed = false; + const char* const* i; + + assert(timestamp); + + if (paths == NULL) + return false; + + STRV_FOREACH(i, paths) { + struct stat stats; + usec_t u; + + if (stat(*i, &stats) < 0) + continue; + + u = timespec_load(&stats.st_mtim); + + /* first check */ + if (*timestamp >= u) + continue; + + log_debug("timestamp of '%s' changed", *i); + + /* update timestamp */ + if (update) { + *timestamp = u; + changed = true; + } else + return true; + } + + return changed; +} + +static int binary_is_good(const char *binary) { + _cleanup_free_ char *p = NULL, *d = NULL; + int r; + + r = find_binary(binary, &p); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + /* An fsck that is linked to /bin/true is a non-existent + * fsck */ + + r = readlink_malloc(p, &d); + if (r == -EINVAL) /* not a symlink */ + return 1; + if (r < 0) + return r; + + return !PATH_IN_SET(d, "true" + "/bin/true", + "/usr/bin/true", + "/dev/null"); +} + +int fsck_exists(const char *fstype) { + const char *checker; + + assert(fstype); + + if (streq(fstype, "auto")) + return -EINVAL; + + checker = strjoina("fsck.", fstype); + return binary_is_good(checker); +} + +int mkfs_exists(const char *fstype) { + const char *mkfs; + + assert(fstype); + + if (streq(fstype, "auto")) + return -EINVAL; + + mkfs = strjoina("mkfs.", fstype); + return binary_is_good(mkfs); +} + +char *prefix_root(const char *root, const char *path) { + char *n, *p; + size_t l; + + /* If root is passed, prefixes path with it. Otherwise returns + * it as is. */ + + assert(path); + + /* First, drop duplicate prefixing slashes from the path */ + while (path[0] == '/' && path[1] == '/') + path++; + + if (isempty(root) || path_equal(root, "/")) + return strdup(path); + + l = strlen(root) + 1 + strlen(path) + 1; + + n = new(char, l); + if (!n) + return NULL; + + p = stpcpy(n, root); + + while (p > n && p[-1] == '/') + p--; + + if (path[0] != '/') + *(p++) = '/'; + + strcpy(p, path); + return n; +} + +int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg) { + char *p; + int r; + + /* + * This function is intended to be used in command line + * parsers, to handle paths that are passed in. It makes the + * path absolute, and reduces it to NULL if omitted or + * root (the latter optionally). + * + * NOTE THAT THIS WILL FREE THE PREVIOUS ARGUMENT POINTER ON + * SUCCESS! Hence, do not pass in uninitialized pointers. + */ + + if (isempty(path)) { + *arg = mfree(*arg); + return 0; + } + + r = path_make_absolute_cwd(path, &p); + if (r < 0) + return log_error_errno(r, "Failed to parse path \"%s\" and make it absolute: %m", path); + + path_kill_slashes(p); + if (suppress_root && path_equal(p, "/")) + p = mfree(p); + + free(*arg); + *arg = p; + return 0; +} + +char* dirname_malloc(const char *path) { + char *d, *dir, *dir2; + + assert(path); + + d = strdup(path); + if (!d) + return NULL; + + dir = dirname(d); + assert(dir); + + if (dir == d) + return d; + + dir2 = strdup(dir); + free(d); + + return dir2; +} + +bool filename_is_valid(const char *p) { + const char *e; + + if (isempty(p)) + return false; + + if (streq(p, ".")) + return false; + + if (streq(p, "..")) + return false; + + e = strchrnul(p, '/'); + if (*e != 0) + return false; + + if (e - p > FILENAME_MAX) + return false; + + return true; +} + +bool path_is_safe(const char *p) { + + if (isempty(p)) + return false; + + if (streq(p, "..") || startswith(p, "../") || endswith(p, "/..") || strstr(p, "/../")) + return false; + + if (strlen(p)+1 > PATH_MAX) + return false; + + /* The following two checks are not really dangerous, but hey, they still are confusing */ + if (streq(p, ".") || startswith(p, "./") || endswith(p, "/.") || strstr(p, "/./")) + return false; + + if (strstr(p, "//")) + return false; + + return true; +} + +char *file_in_same_dir(const char *path, const char *filename) { + char *e, *ret; + size_t k; + + assert(path); + assert(filename); + + /* This removes the last component of path and appends + * filename, unless the latter is absolute anyway or the + * former isn't */ + + if (path_is_absolute(filename)) + return strdup(filename); + + e = strrchr(path, '/'); + if (!e) + return strdup(filename); + + k = strlen(filename); + ret = new(char, (e + 1 - path) + k + 1); + if (!ret) + return NULL; + + memcpy(mempcpy(ret, path, e + 1 - path), filename, k + 1); + return ret; +} + +bool hidden_or_backup_file(const char *filename) { + const char *p; + + assert(filename); + + if (filename[0] == '.' || + streq(filename, "lost+found") || + streq(filename, "aquota.user") || + streq(filename, "aquota.group") || + endswith(filename, "~")) + return true; + + p = strrchr(filename, '.'); + if (!p) + return false; + + /* Please, let's not add more entries to the list below. If external projects think it's a good idea to come up + * with always new suffixes and that everybody else should just adjust to that, then it really should be on + * them. Hence, in future, let's not add any more entries. Instead, let's ask those packages to instead adopt + * one of the generic suffixes/prefixes for hidden files or backups, possibly augmented with an additional + * string. Specifically: there's now: + * + * The generic suffixes "~" and ".bak" for backup files + * The generic prefix "." for hidden files + * + * Thus, if a new package manager "foopkg" wants its own set of ".foopkg-new", ".foopkg-old", ".foopkg-dist" + * or so registered, let's refuse that and ask them to use ".foopkg.new", ".foopkg.old" or ".foopkg~" instead. + */ + + return STR_IN_SET(p + 1, + "rpmnew", + "rpmsave", + "rpmorig", + "dpkg-old", + "dpkg-new", + "dpkg-tmp", + "dpkg-dist", + "dpkg-bak", + "dpkg-backup", + "dpkg-remove", + "ucf-new", + "ucf-old", + "ucf-dist", + "swp", + "bak", + "old", + "new"); +} + +bool is_device_path(const char *path) { + + /* Returns true on paths that refer to a device, either in + * sysfs or in /dev */ + + return + path_startswith(path, "/dev/") || + path_startswith(path, "/sys/"); +} diff --git a/src/libbasic/path-util.h b/src/libbasic/path-util.h new file mode 100644 index 0000000000..a27c13fcc3 --- /dev/null +++ b/src/libbasic/path-util.h @@ -0,0 +1,127 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-2012 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 +#include + +#include "macro.h" +#include "time-util.h" + +#define DEFAULT_PATH_NORMAL "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin" +#define DEFAULT_PATH_SPLIT_USR DEFAULT_PATH_NORMAL ":/sbin:/bin" + +#ifdef HAVE_SPLIT_USR +# define DEFAULT_PATH DEFAULT_PATH_SPLIT_USR +#else +# define DEFAULT_PATH DEFAULT_PATH_NORMAL +#endif + +bool is_path(const char *p) _pure_; +int path_split_and_make_absolute(const char *p, char ***ret); +bool path_is_absolute(const char *p) _pure_; +char* path_make_absolute(const char *p, const char *prefix); +int path_make_absolute_cwd(const char *p, char **ret); +int path_make_relative(const char *from_dir, const char *to_path, char **_r); +char* path_kill_slashes(char *path); +char* path_startswith(const char *path, const char *prefix) _pure_; +int path_compare(const char *a, const char *b) _pure_; +bool path_equal(const char *a, const char *b) _pure_; +bool path_equal_or_files_same(const char *a, const char *b); +char* path_join(const char *root, const char *path, const char *rest); + +static inline bool path_equal_ptr(const char *a, const char *b) { + return !!a == !!b && (!a || path_equal(a, b)); +} + +/* Note: the search terminates on the first NULL item. */ +#define PATH_IN_SET(p, ...) \ + ({ \ + char **s; \ + bool _found = false; \ + STRV_FOREACH(s, STRV_MAKE(__VA_ARGS__)) \ + if (path_equal(p, *s)) { \ + _found = true; \ + break; \ + } \ + _found; \ + }) + +int path_strv_make_absolute_cwd(char **l); +char** path_strv_resolve(char **l, const char *prefix); +char** path_strv_resolve_uniq(char **l, const char *prefix); + +int find_binary(const char *name, char **filename); + +bool paths_check_timestamp(const char* const* paths, usec_t *paths_ts_usec, bool update); + +int fsck_exists(const char *fstype); +int mkfs_exists(const char *fstype); + +/* Iterates through the path prefixes of the specified path, going up + * the tree, to root. Also returns "" (and not "/"!) for the root + * directory. Excludes the specified directory itself */ +#define PATH_FOREACH_PREFIX(prefix, path) \ + for (char *_slash = ({ path_kill_slashes(strcpy(prefix, path)); streq(prefix, "/") ? NULL : strrchr(prefix, '/'); }); _slash && ((*_slash = 0), true); _slash = strrchr((prefix), '/')) + +/* Same as PATH_FOREACH_PREFIX but also includes the specified path itself */ +#define PATH_FOREACH_PREFIX_MORE(prefix, path) \ + for (char *_slash = ({ path_kill_slashes(strcpy(prefix, path)); if (streq(prefix, "/")) prefix[0] = 0; strrchr(prefix, 0); }); _slash && ((*_slash = 0), true); _slash = strrchr((prefix), '/')) + +char *prefix_root(const char *root, const char *path); + +/* Similar to prefix_root(), but returns an alloca() buffer, or + * possibly a const pointer into the path parameter */ +#define prefix_roota(root, path) \ + ({ \ + const char* _path = (path), *_root = (root), *_ret; \ + char *_p, *_n; \ + size_t _l; \ + while (_path[0] == '/' && _path[1] == '/') \ + _path ++; \ + if (isempty(_root) || path_equal(_root, "/")) \ + _ret = _path; \ + else { \ + _l = strlen(_root) + 1 + strlen(_path) + 1; \ + _n = alloca(_l); \ + _p = stpcpy(_n, _root); \ + while (_p > _n && _p[-1] == '/') \ + _p--; \ + if (_path[0] != '/') \ + *(_p++) = '/'; \ + strcpy(_p, _path); \ + _ret = _n; \ + } \ + _ret; \ + }) + +int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg); + +char* dirname_malloc(const char *path); + +bool filename_is_valid(const char *p) _pure_; +bool path_is_safe(const char *p) _pure_; + +char *file_in_same_dir(const char *path, const char *filename); + +bool hidden_or_backup_file(const char *filename) _pure_; + +bool is_device_path(const char *path); diff --git a/src/libbasic/prioq.c b/src/libbasic/prioq.c new file mode 100644 index 0000000000..d2ec516d29 --- /dev/null +++ b/src/libbasic/prioq.c @@ -0,0 +1,320 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +/* + * Priority Queue + * The prioq object implements a priority queue. That is, it orders objects by + * their priority and allows O(1) access to the object with the highest + * priority. Insertion and removal are Θ(log n). Optionally, the caller can + * provide a pointer to an index which will be kept up-to-date by the prioq. + * + * The underlying algorithm used in this implementation is a Heap. + */ + +#include +#include + +#include "alloc-util.h" +#include "hashmap.h" +#include "prioq.h" + +struct prioq_item { + void *data; + unsigned *idx; +}; + +struct Prioq { + compare_func_t compare_func; + unsigned n_items, n_allocated; + + struct prioq_item *items; +}; + +Prioq *prioq_new(compare_func_t compare_func) { + Prioq *q; + + q = new0(Prioq, 1); + if (!q) + return q; + + q->compare_func = compare_func; + return q; +} + +Prioq* prioq_free(Prioq *q) { + if (!q) + return NULL; + + free(q->items); + free(q); + + return NULL; +} + +int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func) { + assert(q); + + if (*q) + return 0; + + *q = prioq_new(compare_func); + if (!*q) + return -ENOMEM; + + return 0; +} + +static void swap(Prioq *q, unsigned j, unsigned k) { + void *saved_data; + unsigned *saved_idx; + + assert(q); + assert(j < q->n_items); + assert(k < q->n_items); + + assert(!q->items[j].idx || *(q->items[j].idx) == j); + assert(!q->items[k].idx || *(q->items[k].idx) == k); + + saved_data = q->items[j].data; + saved_idx = q->items[j].idx; + q->items[j].data = q->items[k].data; + q->items[j].idx = q->items[k].idx; + q->items[k].data = saved_data; + q->items[k].idx = saved_idx; + + if (q->items[j].idx) + *q->items[j].idx = j; + + if (q->items[k].idx) + *q->items[k].idx = k; +} + +static unsigned shuffle_up(Prioq *q, unsigned idx) { + assert(q); + + while (idx > 0) { + unsigned k; + + k = (idx-1)/2; + + if (q->compare_func(q->items[k].data, q->items[idx].data) <= 0) + break; + + swap(q, idx, k); + idx = k; + } + + return idx; +} + +static unsigned shuffle_down(Prioq *q, unsigned idx) { + assert(q); + + for (;;) { + unsigned j, k, s; + + k = (idx+1)*2; /* right child */ + j = k-1; /* left child */ + + if (j >= q->n_items) + break; + + if (q->compare_func(q->items[j].data, q->items[idx].data) < 0) + + /* So our left child is smaller than we are, let's + * remember this fact */ + s = j; + else + s = idx; + + if (k < q->n_items && + q->compare_func(q->items[k].data, q->items[s].data) < 0) + + /* So our right child is smaller than we are, let's + * remember this fact */ + s = k; + + /* s now points to the smallest of the three items */ + + if (s == idx) + /* No swap necessary, we're done */ + break; + + swap(q, idx, s); + idx = s; + } + + return idx; +} + +int prioq_put(Prioq *q, void *data, unsigned *idx) { + struct prioq_item *i; + unsigned k; + + assert(q); + + if (q->n_items >= q->n_allocated) { + unsigned n; + struct prioq_item *j; + + n = MAX((q->n_items+1) * 2, 16u); + j = realloc(q->items, sizeof(struct prioq_item) * n); + if (!j) + return -ENOMEM; + + q->items = j; + q->n_allocated = n; + } + + k = q->n_items++; + i = q->items + k; + i->data = data; + i->idx = idx; + + if (idx) + *idx = k; + + shuffle_up(q, k); + + return 0; +} + +static void remove_item(Prioq *q, struct prioq_item *i) { + struct prioq_item *l; + + assert(q); + assert(i); + + l = q->items + q->n_items - 1; + + if (i == l) + /* Last entry, let's just remove it */ + q->n_items--; + else { + unsigned k; + + /* Not last entry, let's replace the last entry with + * this one, and reshuffle */ + + k = i - q->items; + + i->data = l->data; + i->idx = l->idx; + if (i->idx) + *i->idx = k; + q->n_items--; + + k = shuffle_down(q, k); + shuffle_up(q, k); + } +} + +_pure_ static struct prioq_item* find_item(Prioq *q, void *data, unsigned *idx) { + struct prioq_item *i; + + assert(q); + + if (idx) { + if (*idx == PRIOQ_IDX_NULL || + *idx > q->n_items) + return NULL; + + i = q->items + *idx; + if (i->data != data) + return NULL; + + return i; + } else { + for (i = q->items; i < q->items + q->n_items; i++) + if (i->data == data) + return i; + return NULL; + } +} + +int prioq_remove(Prioq *q, void *data, unsigned *idx) { + struct prioq_item *i; + + if (!q) + return 0; + + i = find_item(q, data, idx); + if (!i) + return 0; + + remove_item(q, i); + return 1; +} + +int prioq_reshuffle(Prioq *q, void *data, unsigned *idx) { + struct prioq_item *i; + unsigned k; + + assert(q); + + i = find_item(q, data, idx); + if (!i) + return 0; + + k = i - q->items; + k = shuffle_down(q, k); + shuffle_up(q, k); + return 1; +} + +void *prioq_peek(Prioq *q) { + + if (!q) + return NULL; + + if (q->n_items <= 0) + return NULL; + + return q->items[0].data; +} + +void *prioq_pop(Prioq *q) { + void *data; + + if (!q) + return NULL; + + if (q->n_items <= 0) + return NULL; + + data = q->items[0].data; + remove_item(q, q->items); + return data; +} + +unsigned prioq_size(Prioq *q) { + + if (!q) + return 0; + + return q->n_items; +} + +bool prioq_isempty(Prioq *q) { + + if (!q) + return true; + + return q->n_items <= 0; +} diff --git a/src/libbasic/prioq.h b/src/libbasic/prioq.h new file mode 100644 index 0000000000..113c73d040 --- /dev/null +++ b/src/libbasic/prioq.h @@ -0,0 +1,43 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 "hashmap.h" +#include "macro.h" + +typedef struct Prioq Prioq; + +#define PRIOQ_IDX_NULL ((unsigned) -1) + +Prioq *prioq_new(compare_func_t compare); +Prioq *prioq_free(Prioq *q); +int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func); + +int prioq_put(Prioq *q, void *data, unsigned *idx); +int prioq_remove(Prioq *q, void *data, unsigned *idx); +int prioq_reshuffle(Prioq *q, void *data, unsigned *idx); + +void *prioq_peek(Prioq *q) _pure_; +void *prioq_pop(Prioq *q); + +unsigned prioq_size(Prioq *q) _pure_; +bool prioq_isempty(Prioq *q) _pure_; diff --git a/src/libbasic/proc-cmdline.c b/src/libbasic/proc-cmdline.c new file mode 100644 index 0000000000..3505fa9c9a --- /dev/null +++ b/src/libbasic/proc-cmdline.c @@ -0,0 +1,176 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include + +#include "alloc-util.h" +#include "extract-word.h" +#include "fileio.h" +#include "macro.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "process-util.h" +#include "special.h" +#include "string-util.h" +#include "util.h" +#include "virt.h" + +int proc_cmdline(char **ret) { + assert(ret); + + if (detect_container() > 0) + return get_process_cmdline(1, 0, false, ret); + else + return read_one_line_file("/proc/cmdline", ret); +} + +int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value)) { + _cleanup_free_ char *line = NULL; + const char *p; + int r; + + assert(parse_item); + + r = proc_cmdline(&line); + if (r < 0) + return r; + + p = line; + for (;;) { + _cleanup_free_ char *word = NULL; + char *value = NULL; + + r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); + if (r < 0) + return r; + if (r == 0) + break; + + /* Filter out arguments that are intended only for the + * initrd */ + if (!in_initrd() && startswith(word, "rd.")) + continue; + + value = strchr(word, '='); + if (value) + *(value++) = 0; + + r = parse_item(word, value); + if (r < 0) + return r; + } + + return 0; +} + +int get_proc_cmdline_key(const char *key, char **value) { + _cleanup_free_ char *line = NULL, *ret = NULL; + bool found = false; + const char *p; + int r; + + assert(key); + + r = proc_cmdline(&line); + if (r < 0) + return r; + + p = line; + for (;;) { + _cleanup_free_ char *word = NULL; + const char *e; + + r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); + if (r < 0) + return r; + if (r == 0) + break; + + /* Filter out arguments that are intended only for the + * initrd */ + if (!in_initrd() && startswith(word, "rd.")) + continue; + + if (value) { + e = startswith(word, key); + if (!e) + continue; + + r = free_and_strdup(&ret, e); + if (r < 0) + return r; + + found = true; + } else { + if (streq(word, key)) + found = true; + } + } + + if (value) { + *value = ret; + ret = NULL; + } + + return found; + +} + +int shall_restore_state(void) { + _cleanup_free_ char *value = NULL; + int r; + + r = get_proc_cmdline_key("systemd.restore_state=", &value); + if (r < 0) + return r; + if (r == 0) + return true; + + return parse_boolean(value); +} + +static const char * const rlmap[] = { + "emergency", SPECIAL_EMERGENCY_TARGET, + "-b", SPECIAL_EMERGENCY_TARGET, + "rescue", SPECIAL_RESCUE_TARGET, + "single", SPECIAL_RESCUE_TARGET, + "-s", SPECIAL_RESCUE_TARGET, + "s", SPECIAL_RESCUE_TARGET, + "S", SPECIAL_RESCUE_TARGET, + "1", SPECIAL_RESCUE_TARGET, + "2", SPECIAL_MULTI_USER_TARGET, + "3", SPECIAL_MULTI_USER_TARGET, + "4", SPECIAL_MULTI_USER_TARGET, + "5", SPECIAL_GRAPHICAL_TARGET, +}; + +const char* runlevel_to_target(const char *word) { + size_t i; + + if (!word) + return NULL; + + for (i = 0; i < ELEMENTSOF(rlmap); i += 2) + if (streq(word, rlmap[i])) + return rlmap[i+1]; + + return NULL; +} diff --git a/src/libbasic/proc-cmdline.h b/src/libbasic/proc-cmdline.h new file mode 100644 index 0000000000..452642a2f5 --- /dev/null +++ b/src/libbasic/proc-cmdline.h @@ -0,0 +1,27 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +int proc_cmdline(char **ret); +int parse_proc_cmdline(int (*parse_word)(const char *key, const char *value)); +int get_proc_cmdline_key(const char *parameter, char **value); + +int shall_restore_state(void); +const char* runlevel_to_target(const char *rl); diff --git a/src/libbasic/process-util.c b/src/libbasic/process-util.c new file mode 100644 index 0000000000..1ad8816206 --- /dev/null +++ b/src/libbasic/process-util.c @@ -0,0 +1,782 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_VALGRIND_VALGRIND_H +#include +#endif + +#include "alloc-util.h" +#include "architecture.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "ioprio.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "process-util.h" +#include "signal-util.h" +#include "stat-util.h" +#include "string-table.h" +#include "string-util.h" +#include "user-util.h" +#include "util.h" + +int get_process_state(pid_t pid) { + const char *p; + char state; + int r; + _cleanup_free_ char *line = NULL; + + assert(pid >= 0); + + p = procfs_file_alloca(pid, "stat"); + + r = read_one_line_file(p, &line); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) + return r; + + p = strrchr(line, ')'); + if (!p) + return -EIO; + + p++; + + if (sscanf(p, " %c", &state) != 1) + return -EIO; + + return (unsigned char) state; +} + +int get_process_comm(pid_t pid, char **name) { + const char *p; + int r; + + assert(name); + assert(pid >= 0); + + p = procfs_file_alloca(pid, "comm"); + + r = read_one_line_file(p, name); + if (r == -ENOENT) + return -ESRCH; + + return r; +} + +int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) { + _cleanup_fclose_ FILE *f = NULL; + char *r = NULL, *k; + const char *p; + int c; + + assert(line); + assert(pid >= 0); + + p = procfs_file_alloca(pid, "cmdline"); + + f = fopen(p, "re"); + if (!f) { + if (errno == ENOENT) + return -ESRCH; + return -errno; + } + + if (max_length == 0) { + size_t len = 0, allocated = 0; + + while ((c = getc(f)) != EOF) { + + if (!GREEDY_REALLOC(r, allocated, len+2)) { + free(r); + return -ENOMEM; + } + + r[len++] = isprint(c) ? c : ' '; + } + + if (len > 0) + r[len-1] = 0; + + } else { + bool space = false; + size_t left; + + r = new(char, max_length); + if (!r) + return -ENOMEM; + + k = r; + left = max_length; + while ((c = getc(f)) != EOF) { + + if (isprint(c)) { + if (space) { + if (left <= 4) + break; + + *(k++) = ' '; + left--; + space = false; + } + + if (left <= 4) + break; + + *(k++) = (char) c; + left--; + } else + space = true; + } + + if (left <= 4) { + size_t n = MIN(left-1, 3U); + memcpy(k, "...", n); + k[n] = 0; + } else + *k = 0; + } + + /* Kernel threads have no argv[] */ + if (isempty(r)) { + _cleanup_free_ char *t = NULL; + int h; + + free(r); + + if (!comm_fallback) + return -ENOENT; + + h = get_process_comm(pid, &t); + if (h < 0) + return h; + + r = strjoin("[", t, "]", NULL); + if (!r) + return -ENOMEM; + } + + *line = r; + return 0; +} + +void rename_process(const char name[8]) { + assert(name); + + /* This is a like a poor man's setproctitle(). It changes the + * comm field, argv[0], and also the glibc's internally used + * name of the process. For the first one a limit of 16 chars + * applies, to the second one usually one of 10 (i.e. length + * of "/sbin/init"), to the third one one of 7 (i.e. length of + * "systemd"). If you pass a longer string it will be + * truncated */ + + (void) prctl(PR_SET_NAME, name); + + if (program_invocation_name) + strncpy(program_invocation_name, name, strlen(program_invocation_name)); + + if (saved_argc > 0) { + int i; + + if (saved_argv[0]) + strncpy(saved_argv[0], name, strlen(saved_argv[0])); + + for (i = 1; i < saved_argc; i++) { + if (!saved_argv[i]) + break; + + memzero(saved_argv[i], strlen(saved_argv[i])); + } + } +} + +int is_kernel_thread(pid_t pid) { + const char *p; + size_t count; + char c; + bool eof; + FILE *f; + + if (pid == 0 || pid == 1) /* pid 1, and we ourselves certainly aren't a kernel thread */ + return 0; + + assert(pid > 1); + + p = procfs_file_alloca(pid, "cmdline"); + f = fopen(p, "re"); + if (!f) { + if (errno == ENOENT) + return -ESRCH; + return -errno; + } + + count = fread(&c, 1, 1, f); + eof = feof(f); + fclose(f); + + /* Kernel threads have an empty cmdline */ + + if (count <= 0) + return eof ? 1 : -errno; + + return 0; +} + +int get_process_capeff(pid_t pid, char **capeff) { + const char *p; + int r; + + assert(capeff); + assert(pid >= 0); + + p = procfs_file_alloca(pid, "status"); + + r = get_proc_field(p, "CapEff", WHITESPACE, capeff); + if (r == -ENOENT) + return -ESRCH; + + return r; +} + +static int get_process_link_contents(const char *proc_file, char **name) { + int r; + + assert(proc_file); + assert(name); + + r = readlink_malloc(proc_file, name); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) + return r; + + return 0; +} + +int get_process_exe(pid_t pid, char **name) { + const char *p; + char *d; + int r; + + assert(pid >= 0); + + p = procfs_file_alloca(pid, "exe"); + r = get_process_link_contents(p, name); + if (r < 0) + return r; + + d = endswith(*name, " (deleted)"); + if (d) + *d = '\0'; + + return 0; +} + +static int get_process_id(pid_t pid, const char *field, uid_t *uid) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + const char *p; + + assert(field); + assert(uid); + + if (pid == 0) + return getuid(); + + p = procfs_file_alloca(pid, "status"); + f = fopen(p, "re"); + if (!f) { + if (errno == ENOENT) + return -ESRCH; + return -errno; + } + + FOREACH_LINE(line, f, return -errno) { + char *l; + + l = strstrip(line); + + if (startswith(l, field)) { + l += strlen(field); + l += strspn(l, WHITESPACE); + + l[strcspn(l, WHITESPACE)] = 0; + + return parse_uid(l, uid); + } + } + + return -EIO; +} + +int get_process_uid(pid_t pid, uid_t *uid) { + return get_process_id(pid, "Uid:", uid); +} + +int get_process_gid(pid_t pid, gid_t *gid) { + assert_cc(sizeof(uid_t) == sizeof(gid_t)); + return get_process_id(pid, "Gid:", gid); +} + +int get_process_cwd(pid_t pid, char **cwd) { + const char *p; + + assert(pid >= 0); + + p = procfs_file_alloca(pid, "cwd"); + + return get_process_link_contents(p, cwd); +} + +int get_process_root(pid_t pid, char **root) { + const char *p; + + assert(pid >= 0); + + p = procfs_file_alloca(pid, "root"); + + return get_process_link_contents(p, root); +} + +int get_process_environ(pid_t pid, char **env) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *outcome = NULL; + int c; + const char *p; + size_t allocated = 0, sz = 0; + + assert(pid >= 0); + assert(env); + + p = procfs_file_alloca(pid, "environ"); + + f = fopen(p, "re"); + if (!f) { + if (errno == ENOENT) + return -ESRCH; + return -errno; + } + + while ((c = fgetc(f)) != EOF) { + if (!GREEDY_REALLOC(outcome, allocated, sz + 5)) + return -ENOMEM; + + if (c == '\0') + outcome[sz++] = '\n'; + else + sz += cescape_char(c, outcome + sz); + } + + if (!outcome) { + outcome = strdup(""); + if (!outcome) + return -ENOMEM; + } else + outcome[sz] = '\0'; + + *env = outcome; + outcome = NULL; + + return 0; +} + +int get_process_ppid(pid_t pid, pid_t *_ppid) { + int r; + _cleanup_free_ char *line = NULL; + long unsigned ppid; + const char *p; + + assert(pid >= 0); + assert(_ppid); + + if (pid == 0) { + *_ppid = getppid(); + return 0; + } + + p = procfs_file_alloca(pid, "stat"); + r = read_one_line_file(p, &line); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) + return r; + + /* Let's skip the pid and comm fields. The latter is enclosed + * in () but does not escape any () in its value, so let's + * skip over it manually */ + + p = strrchr(line, ')'); + if (!p) + return -EIO; + + p++; + + if (sscanf(p, " " + "%*c " /* state */ + "%lu ", /* ppid */ + &ppid) != 1) + return -EIO; + + if ((long unsigned) (pid_t) ppid != ppid) + return -ERANGE; + + *_ppid = (pid_t) ppid; + + return 0; +} + +int wait_for_terminate(pid_t pid, siginfo_t *status) { + siginfo_t dummy; + + assert(pid >= 1); + + if (!status) + status = &dummy; + + for (;;) { + zero(*status); + + if (waitid(P_PID, pid, status, WEXITED) < 0) { + + if (errno == EINTR) + continue; + + return -errno; + } + + return 0; + } +} + +/* + * Return values: + * < 0 : wait_for_terminate() failed to get the state of the + * process, the process was terminated by a signal, or + * failed for an unknown reason. + * >=0 : The process terminated normally, and its exit code is + * returned. + * + * That is, success is indicated by a return value of zero, and an + * error is indicated by a non-zero value. + * + * A warning is emitted if the process terminates abnormally, + * and also if it returns non-zero unless check_exit_code is true. + */ +int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_code) { + int r; + siginfo_t status; + + assert(name); + assert(pid > 1); + + r = wait_for_terminate(pid, &status); + if (r < 0) + return log_warning_errno(r, "Failed to wait for %s: %m", name); + + if (status.si_code == CLD_EXITED) { + if (status.si_status != 0) + log_full(check_exit_code ? LOG_WARNING : LOG_DEBUG, + "%s failed with error code %i.", name, status.si_status); + else + log_debug("%s succeeded.", name); + + return status.si_status; + } else if (status.si_code == CLD_KILLED || + status.si_code == CLD_DUMPED) { + + log_warning("%s terminated by signal %s.", name, signal_to_string(status.si_status)); + return -EPROTO; + } + + log_warning("%s failed due to unknown reason.", name); + return -EPROTO; +} + +void sigkill_wait(pid_t pid) { + assert(pid > 1); + + if (kill(pid, SIGKILL) > 0) + (void) wait_for_terminate(pid, NULL); +} + +void sigkill_waitp(pid_t *pid) { + if (!pid) + return; + if (*pid <= 1) + return; + + sigkill_wait(*pid); +} + +int kill_and_sigcont(pid_t pid, int sig) { + int r; + + r = kill(pid, sig) < 0 ? -errno : 0; + + if (r >= 0) + kill(pid, SIGCONT); + + return r; +} + +int getenv_for_pid(pid_t pid, const char *field, char **_value) { + _cleanup_fclose_ FILE *f = NULL; + char *value = NULL; + int r; + bool done = false; + size_t l; + const char *path; + + assert(pid >= 0); + assert(field); + assert(_value); + + path = procfs_file_alloca(pid, "environ"); + + f = fopen(path, "re"); + if (!f) { + if (errno == ENOENT) + return -ESRCH; + return -errno; + } + + l = strlen(field); + r = 0; + + do { + char line[LINE_MAX]; + unsigned i; + + for (i = 0; i < sizeof(line)-1; i++) { + int c; + + c = getc(f); + if (_unlikely_(c == EOF)) { + done = true; + break; + } else if (c == 0) + break; + + line[i] = c; + } + line[i] = 0; + + if (memcmp(line, field, l) == 0 && line[l] == '=') { + value = strdup(line + l + 1); + if (!value) + return -ENOMEM; + + r = 1; + break; + } + + } while (!done); + + *_value = value; + return r; +} + +bool pid_is_unwaited(pid_t pid) { + /* Checks whether a PID is still valid at all, including a zombie */ + + if (pid < 0) + return false; + + if (pid <= 1) /* If we or PID 1 would be dead and have been waited for, this code would not be running */ + return true; + + if (kill(pid, 0) >= 0) + return true; + + return errno != ESRCH; +} + +bool pid_is_alive(pid_t pid) { + int r; + + /* Checks whether a PID is still valid and not a zombie */ + + if (pid < 0) + return false; + + if (pid <= 1) /* If we or PID 1 would be a zombie, this code would not be running */ + return true; + + r = get_process_state(pid); + if (r == -ESRCH || r == 'Z') + return false; + + return true; +} + +int pid_from_same_root_fs(pid_t pid) { + const char *root; + + if (pid < 0) + return 0; + + root = procfs_file_alloca(pid, "root"); + + return files_same(root, "/proc/1/root"); +} + +bool is_main_thread(void) { + static thread_local int cached = 0; + + if (_unlikely_(cached == 0)) + cached = getpid() == gettid() ? 1 : -1; + + return cached > 0; +} + +noreturn void freeze(void) { + + log_close(); + + /* Make sure nobody waits for us on a socket anymore */ + close_all_fds(NULL, 0); + + sync(); + + for (;;) + pause(); +} + +bool oom_score_adjust_is_valid(int oa) { + return oa >= OOM_SCORE_ADJ_MIN && oa <= OOM_SCORE_ADJ_MAX; +} + +unsigned long personality_from_string(const char *p) { + int architecture; + + if (!p) + return PERSONALITY_INVALID; + + /* Parse a personality specifier. We use our own identifiers that indicate specific ABIs, rather than just + * hints regarding the register size, since we want to keep things open for multiple locally supported ABIs for + * the same register size. */ + + architecture = architecture_from_string(p); + if (architecture < 0) + return PERSONALITY_INVALID; + + if (architecture == native_architecture()) + return PER_LINUX; +#ifdef SECONDARY_ARCHITECTURE + if (architecture == SECONDARY_ARCHITECTURE) + return PER_LINUX32; +#endif + + return PERSONALITY_INVALID; +} + +const char* personality_to_string(unsigned long p) { + int architecture = _ARCHITECTURE_INVALID; + + if (p == PER_LINUX) + architecture = native_architecture(); +#ifdef SECONDARY_ARCHITECTURE + else if (p == PER_LINUX32) + architecture = SECONDARY_ARCHITECTURE; +#endif + + if (architecture < 0) + return NULL; + + return architecture_to_string(architecture); +} + +void valgrind_summary_hack(void) { +#ifdef HAVE_VALGRIND_VALGRIND_H + if (getpid() == 1 && RUNNING_ON_VALGRIND) { + pid_t pid; + pid = raw_clone(SIGCHLD, NULL); + if (pid < 0) + log_emergency_errno(errno, "Failed to fork off valgrind helper: %m"); + else if (pid == 0) + exit(EXIT_SUCCESS); + else { + log_info("Spawned valgrind helper as PID "PID_FMT".", pid); + (void) wait_for_terminate(pid, NULL); + } + } +#endif +} + +int pid_compare_func(const void *a, const void *b) { + const pid_t *p = a, *q = b; + + /* Suitable for usage in qsort() */ + + if (*p < *q) + return -1; + if (*p > *q) + return 1; + return 0; +} + +static const char *const ioprio_class_table[] = { + [IOPRIO_CLASS_NONE] = "none", + [IOPRIO_CLASS_RT] = "realtime", + [IOPRIO_CLASS_BE] = "best-effort", + [IOPRIO_CLASS_IDLE] = "idle" +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ioprio_class, int, INT_MAX); + +static const char *const sigchld_code_table[] = { + [CLD_EXITED] = "exited", + [CLD_KILLED] = "killed", + [CLD_DUMPED] = "dumped", + [CLD_TRAPPED] = "trapped", + [CLD_STOPPED] = "stopped", + [CLD_CONTINUED] = "continued", +}; + +DEFINE_STRING_TABLE_LOOKUP(sigchld_code, int); + +static const char* const sched_policy_table[] = { + [SCHED_OTHER] = "other", + [SCHED_BATCH] = "batch", + [SCHED_IDLE] = "idle", + [SCHED_FIFO] = "fifo", + [SCHED_RR] = "rr" +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(sched_policy, int, INT_MAX); diff --git a/src/libbasic/process-util.h b/src/libbasic/process-util.h new file mode 100644 index 0000000000..9f75088796 --- /dev/null +++ b/src/libbasic/process-util.h @@ -0,0 +1,105 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include + +#include "formats-util.h" +#include "macro.h" + +#define procfs_file_alloca(pid, field) \ + ({ \ + pid_t _pid_ = (pid); \ + const char *_r_; \ + if (_pid_ == 0) { \ + _r_ = ("/proc/self/" field); \ + } else { \ + _r_ = alloca(strlen("/proc/") + DECIMAL_STR_MAX(pid_t) + 1 + sizeof(field)); \ + sprintf((char*) _r_, "/proc/"PID_FMT"/" field, _pid_); \ + } \ + _r_; \ + }) + +int get_process_state(pid_t pid); +int get_process_comm(pid_t pid, char **name); +int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line); +int get_process_exe(pid_t pid, char **name); +int get_process_uid(pid_t pid, uid_t *uid); +int get_process_gid(pid_t pid, gid_t *gid); +int get_process_capeff(pid_t pid, char **capeff); +int get_process_cwd(pid_t pid, char **cwd); +int get_process_root(pid_t pid, char **root); +int get_process_environ(pid_t pid, char **environ); +int get_process_ppid(pid_t pid, pid_t *ppid); + +int wait_for_terminate(pid_t pid, siginfo_t *status); +int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_code); + +void sigkill_wait(pid_t pid); +void sigkill_waitp(pid_t *pid); + +int kill_and_sigcont(pid_t pid, int sig); + +void rename_process(const char name[8]); +int is_kernel_thread(pid_t pid); + +int getenv_for_pid(pid_t pid, const char *field, char **_value); + +bool pid_is_alive(pid_t pid); +bool pid_is_unwaited(pid_t pid); +int pid_from_same_root_fs(pid_t pid); + +bool is_main_thread(void); + +noreturn void freeze(void); + +bool oom_score_adjust_is_valid(int oa); + +#ifndef PERSONALITY_INVALID +/* personality(7) documents that 0xffffffffUL is used for querying the + * current personality, hence let's use that here as error + * indicator. */ +#define PERSONALITY_INVALID 0xffffffffLU +#endif + +unsigned long personality_from_string(const char *p); +const char *personality_to_string(unsigned long); + +int ioprio_class_to_string_alloc(int i, char **s); +int ioprio_class_from_string(const char *s); + +const char *sigchld_code_to_string(int i) _const_; +int sigchld_code_from_string(const char *s) _pure_; + +int sched_policy_to_string_alloc(int i, char **s); +int sched_policy_from_string(const char *s); + +#define PTR_TO_PID(p) ((pid_t) ((uintptr_t) p)) +#define PID_TO_PTR(p) ((void*) ((uintptr_t) p)) + +void valgrind_summary_hack(void); + +int pid_compare_func(const void *a, const void *b); diff --git a/src/libbasic/random-util.c b/src/libbasic/random-util.c new file mode 100644 index 0000000000..2f468db770 --- /dev/null +++ b/src/libbasic/random-util.c @@ -0,0 +1,133 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_AUXV_H +#include +#endif + +#include "fd-util.h" +#include "io-util.h" +#include "missing.h" +#include "random-util.h" +#include "time-util.h" + +int dev_urandom(void *p, size_t n) { + static int have_syscall = -1; + + _cleanup_close_ int fd = -1; + int r; + + /* Gathers some randomness from the kernel. This call will + * never block, and will always return some data from the + * kernel, regardless if the random pool is fully initialized + * or not. It thus makes no guarantee for the quality of the + * returned entropy, but is good enough for or usual usecases + * of seeding the hash functions for hashtable */ + + /* Use the getrandom() syscall unless we know we don't have + * it, or when the requested size is too large for it. */ + if (have_syscall != 0 || (size_t) (int) n != n) { + r = getrandom(p, n, GRND_NONBLOCK); + if (r == (int) n) { + have_syscall = true; + return 0; + } + + if (r < 0) { + if (errno == ENOSYS) + /* we lack the syscall, continue with + * reading from /dev/urandom */ + have_syscall = false; + else if (errno == EAGAIN) + /* not enough entropy for now. Let's + * remember to use the syscall the + * next time, again, but also read + * from /dev/urandom for now, which + * doesn't care about the current + * amount of entropy. */ + have_syscall = true; + else + return -errno; + } else + /* too short read? */ + return -ENODATA; + } + + fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return errno == ENOENT ? -ENOSYS : -errno; + + return loop_read_exact(fd, p, n, true); +} + +void initialize_srand(void) { + static bool srand_called = false; + unsigned x; +#ifdef HAVE_SYS_AUXV_H + void *auxv; +#endif + + if (srand_called) + return; + +#ifdef HAVE_SYS_AUXV_H + /* The kernel provides us with 16 bytes of entropy in auxv, so let's try to make use of that to seed the + * pseudo-random generator. It's better than nothing... */ + + auxv = (void*) getauxval(AT_RANDOM); + if (auxv) { + assert_cc(sizeof(x) < 16); + memcpy(&x, auxv, sizeof(x)); + } else +#endif + x = 0; + + + x ^= (unsigned) now(CLOCK_REALTIME); + x ^= (unsigned) gettid(); + + srand(x); + srand_called = true; +} + +void random_bytes(void *p, size_t n) { + uint8_t *q; + int r; + + r = dev_urandom(p, n); + if (r >= 0) + return; + + /* If some idiot made /dev/urandom unavailable to us, he'll + * get a PRNG instead. */ + + initialize_srand(); + + for (q = p; q < (uint8_t*) p + n; q ++) + *q = rand(); +} diff --git a/src/libbasic/random-util.h b/src/libbasic/random-util.h new file mode 100644 index 0000000000..3cee4c5014 --- /dev/null +++ b/src/libbasic/random-util.h @@ -0,0 +1,39 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 + +int dev_urandom(void *p, size_t n); +void random_bytes(void *p, size_t n); +void initialize_srand(void); + +static inline uint64_t random_u64(void) { + uint64_t u; + random_bytes(&u, sizeof(u)); + return u; +} + +static inline uint32_t random_u32(void) { + uint32_t u; + random_bytes(&u, sizeof(u)); + return u; +} diff --git a/src/libbasic/ratelimit.c b/src/libbasic/ratelimit.c new file mode 100644 index 0000000000..3ca5625e4d --- /dev/null +++ b/src/libbasic/ratelimit.c @@ -0,0 +1,56 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 "macro.h" +#include "ratelimit.h" + +/* Modelled after Linux' lib/ratelimit.c by Dave Young + * , which is licensed GPLv2. */ + +bool ratelimit_test(RateLimit *r) { + usec_t ts; + + assert(r); + + if (r->interval <= 0 || r->burst <= 0) + return true; + + ts = now(CLOCK_MONOTONIC); + + if (r->begin <= 0 || + r->begin + r->interval < ts) { + r->begin = ts; + + /* Reset counter */ + r->num = 0; + goto good; + } + + if (r->num < r->burst) + goto good; + + return false; + +good: + r->num++; + return true; +} diff --git a/src/libbasic/ratelimit.h b/src/libbasic/ratelimit.h new file mode 100644 index 0000000000..9c8dddf5ad --- /dev/null +++ b/src/libbasic/ratelimit.h @@ -0,0 +1,58 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "time-util.h" +#include "util.h" + +typedef struct RateLimit { + usec_t interval; + usec_t begin; + unsigned burst; + unsigned num; +} RateLimit; + +#define RATELIMIT_DEFINE(_name, _interval, _burst) \ + RateLimit _name = { \ + .interval = (_interval), \ + .burst = (_burst), \ + .num = 0, \ + .begin = 0 \ + } + +#define RATELIMIT_INIT(v, _interval, _burst) \ + do { \ + RateLimit *_r = &(v); \ + _r->interval = (_interval); \ + _r->burst = (_burst); \ + _r->num = 0; \ + _r->begin = 0; \ + } while (false) + +#define RATELIMIT_RESET(v) \ + do { \ + RateLimit *_r = &(v); \ + _r->num = 0; \ + _r->begin = 0; \ + } while (false) + +bool ratelimit_test(RateLimit *r); diff --git a/src/libbasic/refcnt.h b/src/libbasic/refcnt.h new file mode 100644 index 0000000000..1d77a6445a --- /dev/null +++ b/src/libbasic/refcnt.h @@ -0,0 +1,34 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +/* A type-safe atomic refcounter. + * + * DO NOT USE THIS UNLESS YOU ACTUALLY CARE ABOUT THREAD SAFETY! */ + +typedef struct { + volatile unsigned _value; +} RefCount; + +#define REFCNT_GET(r) ((r)._value) +#define REFCNT_INC(r) (__sync_add_and_fetch(&(r)._value, 1)) +#define REFCNT_DEC(r) (__sync_sub_and_fetch(&(r)._value, 1)) + +#define REFCNT_INIT ((RefCount) { ._value = 1 }) diff --git a/src/libbasic/replace-var.c b/src/libbasic/replace-var.c new file mode 100644 index 0000000000..6a204b9ec3 --- /dev/null +++ b/src/libbasic/replace-var.c @@ -0,0 +1,112 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 +#include +#include + +#include "alloc-util.h" +#include "macro.h" +#include "replace-var.h" +#include "string-util.h" + +/* + * Generic infrastructure for replacing @FOO@ style variables in + * strings. Will call a callback for each replacement. + */ + +static int get_variable(const char *b, char **r) { + size_t k; + char *t; + + assert(b); + assert(r); + + if (*b != '@') + return 0; + + k = strspn(b + 1, UPPERCASE_LETTERS "_"); + if (k <= 0 || b[k+1] != '@') + return 0; + + t = strndup(b + 1, k); + if (!t) + return -ENOMEM; + + *r = t; + return 1; +} + +char *replace_var(const char *text, char *(*lookup)(const char *variable, void*userdata), void *userdata) { + char *r, *t; + const char *f; + size_t l; + + assert(text); + assert(lookup); + + l = strlen(text); + r = new(char, l+1); + if (!r) + return NULL; + + f = text; + t = r; + while (*f) { + _cleanup_free_ char *v = NULL, *n = NULL; + char *a; + int k; + size_t skip, d, nl; + + k = get_variable(f, &v); + if (k < 0) + goto oom; + if (k == 0) { + *(t++) = *(f++); + continue; + } + + n = lookup(v, userdata); + if (!n) + goto oom; + + skip = strlen(v) + 2; + + d = t - r; + nl = l - skip + strlen(n); + a = realloc(r, nl + 1); + if (!a) + goto oom; + + l = nl; + r = a; + t = r + d; + + t = stpcpy(t, n); + f += skip; + } + + *t = 0; + return r; + +oom: + free(r); + return NULL; +} diff --git a/src/libbasic/replace-var.h b/src/libbasic/replace-var.h new file mode 100644 index 0000000000..78412910b2 --- /dev/null +++ b/src/libbasic/replace-var.h @@ -0,0 +1,22 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +char *replace_var(const char *text, char *(*lookup)(const char *variable, void*userdata), void *userdata); diff --git a/src/libbasic/rlimit-util.c b/src/libbasic/rlimit-util.c new file mode 100644 index 0000000000..ee063720ed --- /dev/null +++ b/src/libbasic/rlimit-util.c @@ -0,0 +1,321 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 + +#include "alloc-util.h" +#include "extract-word.h" +#include "formats-util.h" +#include "macro.h" +#include "missing.h" +#include "rlimit-util.h" +#include "string-table.h" +#include "time-util.h" + +int setrlimit_closest(int resource, const struct rlimit *rlim) { + struct rlimit highest, fixed; + + assert(rlim); + + if (setrlimit(resource, rlim) >= 0) + return 0; + + if (errno != EPERM) + return -errno; + + /* So we failed to set the desired setrlimit, then let's try + * to get as close as we can */ + assert_se(getrlimit(resource, &highest) == 0); + + fixed.rlim_cur = MIN(rlim->rlim_cur, highest.rlim_max); + fixed.rlim_max = MIN(rlim->rlim_max, highest.rlim_max); + + if (setrlimit(resource, &fixed) < 0) + return -errno; + + return 0; +} + +static int rlimit_parse_u64(const char *val, rlim_t *ret) { + uint64_t u; + int r; + + assert(val); + assert(ret); + + if (streq(val, "infinity")) { + *ret = RLIM_INFINITY; + return 0; + } + + /* setrlimit(2) suggests rlim_t is always 64bit on Linux. */ + assert_cc(sizeof(rlim_t) == sizeof(uint64_t)); + + r = safe_atou64(val, &u); + if (r < 0) + return r; + if (u >= (uint64_t) RLIM_INFINITY) + return -ERANGE; + + *ret = (rlim_t) u; + return 0; +} + +static int rlimit_parse_size(const char *val, rlim_t *ret) { + uint64_t u; + int r; + + assert(val); + assert(ret); + + if (streq(val, "infinity")) { + *ret = RLIM_INFINITY; + return 0; + } + + r = parse_size(val, 1024, &u); + if (r < 0) + return r; + if (u >= (uint64_t) RLIM_INFINITY) + return -ERANGE; + + *ret = (rlim_t) u; + return 0; +} + +static int rlimit_parse_sec(const char *val, rlim_t *ret) { + uint64_t u; + usec_t t; + int r; + + assert(val); + assert(ret); + + if (streq(val, "infinity")) { + *ret = RLIM_INFINITY; + return 0; + } + + r = parse_sec(val, &t); + if (r < 0) + return r; + if (t == USEC_INFINITY) { + *ret = RLIM_INFINITY; + return 0; + } + + u = (uint64_t) DIV_ROUND_UP(t, USEC_PER_SEC); + if (u >= (uint64_t) RLIM_INFINITY) + return -ERANGE; + + *ret = (rlim_t) u; + return 0; +} + +static int rlimit_parse_usec(const char *val, rlim_t *ret) { + usec_t t; + int r; + + assert(val); + assert(ret); + + if (streq(val, "infinity")) { + *ret = RLIM_INFINITY; + return 0; + } + + r = parse_time(val, &t, 1); + if (r < 0) + return r; + if (t == USEC_INFINITY) { + *ret = RLIM_INFINITY; + return 0; + } + + *ret = (rlim_t) t; + return 0; +} + +static int rlimit_parse_nice(const char *val, rlim_t *ret) { + uint64_t rl; + int r; + + /* So, Linux is weird. The range for RLIMIT_NICE is 40..1, mapping to the nice levels -20..19. However, the + * RLIMIT_NICE limit defaults to 0 by the kernel, i.e. a value that maps to nice level 20, which of course is + * bogus and does not exist. In order to permit parsing the RLIMIT_NICE of 0 here we hence implement a slight + * asymmetry: when parsing as positive nice level we permit 0..19. When parsing as negative nice level, we + * permit -20..0. But when parsing as raw resource limit value then we also allow the special value 0. + * + * Yeah, Linux is quality engineering sometimes... */ + + if (val[0] == '+') { + + /* Prefixed with "+": Parse as positive user-friendly nice value */ + r = safe_atou64(val + 1, &rl); + if (r < 0) + return r; + + if (rl >= PRIO_MAX) + return -ERANGE; + + rl = 20 - rl; + + } else if (val[0] == '-') { + + /* Prefixed with "-": Parse as negative user-friendly nice value */ + r = safe_atou64(val + 1, &rl); + if (r < 0) + return r; + + if (rl > (uint64_t) (-PRIO_MIN)) + return -ERANGE; + + rl = 20 + rl; + } else { + + /* Not prefixed: parse as raw resource limit value */ + r = safe_atou64(val, &rl); + if (r < 0) + return r; + + if (rl > (uint64_t) (20 - PRIO_MIN)) + return -ERANGE; + } + + *ret = (rlim_t) rl; + return 0; +} + +static int (*const rlimit_parse_table[_RLIMIT_MAX])(const char *val, rlim_t *ret) = { + [RLIMIT_CPU] = rlimit_parse_sec, + [RLIMIT_FSIZE] = rlimit_parse_size, + [RLIMIT_DATA] = rlimit_parse_size, + [RLIMIT_STACK] = rlimit_parse_size, + [RLIMIT_CORE] = rlimit_parse_size, + [RLIMIT_RSS] = rlimit_parse_size, + [RLIMIT_NOFILE] = rlimit_parse_u64, + [RLIMIT_AS] = rlimit_parse_size, + [RLIMIT_NPROC] = rlimit_parse_u64, + [RLIMIT_MEMLOCK] = rlimit_parse_size, + [RLIMIT_LOCKS] = rlimit_parse_u64, + [RLIMIT_SIGPENDING] = rlimit_parse_u64, + [RLIMIT_MSGQUEUE] = rlimit_parse_size, + [RLIMIT_NICE] = rlimit_parse_nice, + [RLIMIT_RTPRIO] = rlimit_parse_u64, + [RLIMIT_RTTIME] = rlimit_parse_usec, +}; + +int rlimit_parse_one(int resource, const char *val, rlim_t *ret) { + assert(val); + assert(ret); + + if (resource < 0) + return -EINVAL; + if (resource >= _RLIMIT_MAX) + return -EINVAL; + + return rlimit_parse_table[resource](val, ret); +} + +int rlimit_parse(int resource, const char *val, struct rlimit *ret) { + _cleanup_free_ char *hard = NULL, *soft = NULL; + rlim_t hl, sl; + int r; + + assert(val); + assert(ret); + + r = extract_first_word(&val, &soft, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + r = rlimit_parse_one(resource, soft, &sl); + if (r < 0) + return r; + + r = extract_first_word(&val, &hard, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (!isempty(val)) + return -EINVAL; + if (r == 0) + hl = sl; + else { + r = rlimit_parse_one(resource, hard, &hl); + if (r < 0) + return r; + if (sl > hl) + return -EILSEQ; + } + + *ret = (struct rlimit) { + .rlim_cur = sl, + .rlim_max = hl, + }; + + return 0; +} + +int rlimit_format(const struct rlimit *rl, char **ret) { + char *s = NULL; + + assert(rl); + assert(ret); + + if (rl->rlim_cur >= RLIM_INFINITY && rl->rlim_max >= RLIM_INFINITY) + s = strdup("infinity"); + else if (rl->rlim_cur >= RLIM_INFINITY) + (void) asprintf(&s, "infinity:" RLIM_FMT, rl->rlim_max); + else if (rl->rlim_max >= RLIM_INFINITY) + (void) asprintf(&s, RLIM_FMT ":infinity", rl->rlim_cur); + else if (rl->rlim_cur == rl->rlim_max) + (void) asprintf(&s, RLIM_FMT, rl->rlim_cur); + else + (void) asprintf(&s, RLIM_FMT ":" RLIM_FMT, rl->rlim_cur, rl->rlim_max); + + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +static const char* const rlimit_table[_RLIMIT_MAX] = { + [RLIMIT_CPU] = "LimitCPU", + [RLIMIT_FSIZE] = "LimitFSIZE", + [RLIMIT_DATA] = "LimitDATA", + [RLIMIT_STACK] = "LimitSTACK", + [RLIMIT_CORE] = "LimitCORE", + [RLIMIT_RSS] = "LimitRSS", + [RLIMIT_NOFILE] = "LimitNOFILE", + [RLIMIT_AS] = "LimitAS", + [RLIMIT_NPROC] = "LimitNPROC", + [RLIMIT_MEMLOCK] = "LimitMEMLOCK", + [RLIMIT_LOCKS] = "LimitLOCKS", + [RLIMIT_SIGPENDING] = "LimitSIGPENDING", + [RLIMIT_MSGQUEUE] = "LimitMSGQUEUE", + [RLIMIT_NICE] = "LimitNICE", + [RLIMIT_RTPRIO] = "LimitRTPRIO", + [RLIMIT_RTTIME] = "LimitRTTIME" +}; + +DEFINE_STRING_TABLE_LOOKUP(rlimit, int); diff --git a/src/libbasic/rlimit-util.h b/src/libbasic/rlimit-util.h new file mode 100644 index 0000000000..d4594eccd6 --- /dev/null +++ b/src/libbasic/rlimit-util.h @@ -0,0 +1,36 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "macro.h" + +const char *rlimit_to_string(int i) _const_; +int rlimit_from_string(const char *s) _pure_; + +int setrlimit_closest(int resource, const struct rlimit *rlim); + +int rlimit_parse_one(int resource, const char *val, rlim_t *ret); +int rlimit_parse(int resource, const char *val, struct rlimit *ret); + +int rlimit_format(const struct rlimit *rl, char **ret); + +#define RLIMIT_MAKE_CONST(lim) ((struct rlimit) { lim, lim }) diff --git a/src/libbasic/rm-rf.c b/src/libbasic/rm-rf.c new file mode 100644 index 0000000000..43816fd1bb --- /dev/null +++ b/src/libbasic/rm-rf.c @@ -0,0 +1,236 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 +#include +#include +#include +#include +#include +#include + +#include "btrfs-util.h" +#include "fd-util.h" +#include "log.h" +#include "macro.h" +#include "mount-util.h" +#include "path-util.h" +#include "rm-rf.h" +#include "stat-util.h" +#include "string-util.h" + +int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { + _cleanup_closedir_ DIR *d = NULL; + int ret = 0, r; + + assert(fd >= 0); + + /* This returns the first error we run into, but nevertheless + * tries to go on. This closes the passed fd. */ + + if (!(flags & REMOVE_PHYSICAL)) { + + r = fd_is_temporary_fs(fd); + if (r < 0) { + safe_close(fd); + return r; + } + + if (!r) { + /* We refuse to clean physical file systems + * with this call, unless explicitly + * requested. This is extra paranoia just to + * be sure we never ever remove non-state + * data */ + + log_error("Attempted to remove disk file system, and we can't allow that."); + safe_close(fd); + return -EPERM; + } + } + + d = fdopendir(fd); + if (!d) { + safe_close(fd); + return errno == ENOENT ? 0 : -errno; + } + + for (;;) { + struct dirent *de; + bool is_dir; + struct stat st; + + errno = 0; + de = readdir(d); + if (!de) { + if (errno > 0 && ret == 0) + ret = -errno; + return ret; + } + + if (streq(de->d_name, ".") || streq(de->d_name, "..")) + continue; + + if (de->d_type == DT_UNKNOWN || + (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) { + if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + continue; + } + + is_dir = S_ISDIR(st.st_mode); + } else + is_dir = de->d_type == DT_DIR; + + if (is_dir) { + int subdir_fd; + + /* if root_dev is set, remove subdirectories only if device is same */ + if (root_dev && st.st_dev != root_dev->st_dev) + continue; + + subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + if (subdir_fd < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + continue; + } + + /* Stop at mount points */ + r = fd_is_mount_point(fd, de->d_name, 0); + if (r < 0) { + if (ret == 0 && r != -ENOENT) + ret = r; + + safe_close(subdir_fd); + continue; + } + if (r) { + safe_close(subdir_fd); + continue; + } + + if ((flags & REMOVE_SUBVOLUME) && st.st_ino == 256) { + + /* This could be a subvolume, try to remove it */ + + r = btrfs_subvol_remove_fd(fd, de->d_name, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); + if (r < 0) { + if (r != -ENOTTY && r != -EINVAL) { + if (ret == 0) + ret = r; + + safe_close(subdir_fd); + continue; + } + + /* ENOTTY, then it wasn't a + * btrfs subvolume, continue + * below. */ + } else { + /* It was a subvolume, continue. */ + safe_close(subdir_fd); + continue; + } + } + + /* We pass REMOVE_PHYSICAL here, to avoid + * doing the fstatfs() to check the file + * system type again for each directory */ + r = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev); + if (r < 0 && ret == 0) + ret = r; + + if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + } + + } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) { + + if (unlinkat(fd, de->d_name, 0) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + } + } + } +} + +int rm_rf(const char *path, RemoveFlags flags) { + int fd, r; + struct statfs s; + + assert(path); + + /* We refuse to clean the root file system with this + * call. This is extra paranoia to never cause a really + * seriously broken system. */ + if (path_equal(path, "/")) { + log_error("Attempted to remove entire root file system, and we can't allow that."); + return -EPERM; + } + + if ((flags & (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) == (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) { + /* Try to remove as subvolume first */ + r = btrfs_subvol_remove(path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); + if (r >= 0) + return r; + + if (r != -ENOTTY && r != -EINVAL && r != -ENOTDIR) + return r; + + /* Not btrfs or not a subvolume */ + } + + fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + if (fd < 0) { + + if (errno != ENOTDIR && errno != ELOOP) + return -errno; + + if (!(flags & REMOVE_PHYSICAL)) { + if (statfs(path, &s) < 0) + return -errno; + + if (!is_temporary_fs(&s)) { + log_error("Attempted to remove disk file system, and we can't allow that."); + return -EPERM; + } + } + + if ((flags & REMOVE_ROOT) && !(flags & REMOVE_ONLY_DIRECTORIES)) + if (unlink(path) < 0 && errno != ENOENT) + return -errno; + + return 0; + } + + r = rm_rf_children(fd, flags, NULL); + + if (flags & REMOVE_ROOT) { + if (rmdir(path) < 0) { + if (r == 0 && errno != ENOENT) + r = -errno; + } + } + + return r; +} diff --git a/src/libbasic/rm-rf.h b/src/libbasic/rm-rf.h new file mode 100644 index 0000000000..f693a5bb7c --- /dev/null +++ b/src/libbasic/rm-rf.h @@ -0,0 +1,41 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 + +typedef enum RemoveFlags { + REMOVE_ONLY_DIRECTORIES = 1, + REMOVE_ROOT = 2, + REMOVE_PHYSICAL = 4, /* if not set, only removes files on tmpfs, never physical file systems */ + REMOVE_SUBVOLUME = 8, +} RemoveFlags; + +int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev); +int rm_rf(const char *path, RemoveFlags flags); + +/* Useful for usage with _cleanup_(), destroys a directory and frees the pointer */ +static inline void rm_rf_physical_and_free(char *p) { + if (!p) + return; + (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); + free(p); +} +DEFINE_TRIVIAL_CLEANUP_FUNC(char*, rm_rf_physical_and_free); diff --git a/src/libbasic/securebits.h b/src/libbasic/securebits.h new file mode 100644 index 0000000000..98fbe0d433 --- /dev/null +++ b/src/libbasic/securebits.h @@ -0,0 +1,45 @@ +#ifndef _LINUX_SECUREBITS_H +#define _LINUX_SECUREBITS_H 1 + +/* This is minimal version of Linux' linux/securebits.h header file, + * which is licensed GPL2 */ + +#define SECUREBITS_DEFAULT 0x00000000 + +/* When set UID 0 has no special privileges. When unset, we support + inheritance of root-permissions and suid-root executable under + compatibility mode. We raise the effective and inheritable bitmasks + *of the executable file* if the effective uid of the new process is + 0. If the real uid is 0, we raise the effective (legacy) bit of the + executable file. */ +#define SECURE_NOROOT 0 +#define SECURE_NOROOT_LOCKED 1 /* make bit-0 immutable */ + +/* When set, setuid to/from uid 0 does not trigger capability-"fixup". + When unset, to provide compatibility with old programs relying on + set*uid to gain/lose privilege, transitions to/from uid 0 cause + capabilities to be gained/lost. */ +#define SECURE_NO_SETUID_FIXUP 2 +#define SECURE_NO_SETUID_FIXUP_LOCKED 3 /* make bit-2 immutable */ + +/* When set, a process can retain its capabilities even after + transitioning to a non-root user (the set-uid fixup suppressed by + bit 2). Bit-4 is cleared when a process calls exec(); setting both + bit 4 and 5 will create a barrier through exec that no exec()'d + child can use this feature again. */ +#define SECURE_KEEP_CAPS 4 +#define SECURE_KEEP_CAPS_LOCKED 5 /* make bit-4 immutable */ + +/* Each securesetting is implemented using two bits. One bit specifies + whether the setting is on or off. The other bit specify whether the + setting is locked or not. A setting which is locked cannot be + changed from user-level. */ +#define issecure_mask(X) (1 << (X)) +#define issecure(X) (issecure_mask(X) & current_cred_xxx(securebits)) + +#define SECURE_ALL_BITS (issecure_mask(SECURE_NOROOT) | \ + issecure_mask(SECURE_NO_SETUID_FIXUP) | \ + issecure_mask(SECURE_KEEP_CAPS)) +#define SECURE_ALL_LOCKS (SECURE_ALL_BITS << 1) + +#endif /* !_LINUX_SECUREBITS_H */ diff --git a/src/libbasic/selinux-util.c b/src/libbasic/selinux-util.c new file mode 100644 index 0000000000..10c2f39369 --- /dev/null +++ b/src/libbasic/selinux-util.c @@ -0,0 +1,485 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SELINUX +#include +#include +#include +#endif + +#include "alloc-util.h" +#include "log.h" +#include "macro.h" +#include "path-util.h" +#include "selinux-util.h" +#include "time-util.h" +#include "util.h" + +#ifdef HAVE_SELINUX +DEFINE_TRIVIAL_CLEANUP_FUNC(security_context_t, freecon); +DEFINE_TRIVIAL_CLEANUP_FUNC(context_t, context_free); + +#define _cleanup_security_context_free_ _cleanup_(freeconp) +#define _cleanup_context_free_ _cleanup_(context_freep) + +static int cached_use = -1; +static struct selabel_handle *label_hnd = NULL; + +#define log_enforcing(...) log_full(security_getenforce() == 1 ? LOG_ERR : LOG_DEBUG, __VA_ARGS__) +#endif + +bool mac_selinux_have(void) { +#ifdef HAVE_SELINUX + if (cached_use < 0) + cached_use = is_selinux_enabled() > 0; + + return cached_use; +#else + return false; +#endif +} + +bool mac_selinux_use(void) { + if (!mac_selinux_have()) + return false; + + /* Never try to configure SELinux features if we aren't + * root */ + + return getuid() == 0; +} + +void mac_selinux_retest(void) { +#ifdef HAVE_SELINUX + cached_use = -1; +#endif +} + +int mac_selinux_init(void) { + int r = 0; + +#ifdef HAVE_SELINUX + usec_t before_timestamp, after_timestamp; + struct mallinfo before_mallinfo, after_mallinfo; + + if (label_hnd) + return 0; + + if (!mac_selinux_use()) + return 0; + + before_mallinfo = mallinfo(); + before_timestamp = now(CLOCK_MONOTONIC); + + label_hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0); + if (!label_hnd) { + log_enforcing("Failed to initialize SELinux context: %m"); + r = security_getenforce() == 1 ? -errno : 0; + } else { + char timespan[FORMAT_TIMESPAN_MAX]; + int l; + + after_timestamp = now(CLOCK_MONOTONIC); + after_mallinfo = mallinfo(); + + l = after_mallinfo.uordblks > before_mallinfo.uordblks ? after_mallinfo.uordblks - before_mallinfo.uordblks : 0; + + log_debug("Successfully loaded SELinux database in %s, size on heap is %iK.", + format_timespan(timespan, sizeof(timespan), after_timestamp - before_timestamp, 0), + (l+1023)/1024); + } +#endif + + return r; +} + +void mac_selinux_finish(void) { + +#ifdef HAVE_SELINUX + if (!label_hnd) + return; + + selabel_close(label_hnd); + label_hnd = NULL; +#endif +} + +int mac_selinux_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { + +#ifdef HAVE_SELINUX + struct stat st; + int r; + + assert(path); + + /* if mac_selinux_init() wasn't called before we are a NOOP */ + if (!label_hnd) + return 0; + + r = lstat(path, &st); + if (r >= 0) { + _cleanup_security_context_free_ security_context_t fcon = NULL; + + r = selabel_lookup_raw(label_hnd, &fcon, path, st.st_mode); + + /* If there's no label to set, then exit without warning */ + if (r < 0 && errno == ENOENT) + return 0; + + if (r >= 0) { + r = lsetfilecon_raw(path, fcon); + + /* If the FS doesn't support labels, then exit without warning */ + if (r < 0 && errno == EOPNOTSUPP) + return 0; + } + } + + if (r < 0) { + /* Ignore ENOENT in some cases */ + if (ignore_enoent && errno == ENOENT) + return 0; + + if (ignore_erofs && errno == EROFS) + return 0; + + log_enforcing("Unable to fix SELinux security context of %s: %m", path); + if (security_getenforce() == 1) + return -errno; + } +#endif + + return 0; +} + +int mac_selinux_apply(const char *path, const char *label) { + +#ifdef HAVE_SELINUX + if (!mac_selinux_use()) + return 0; + + assert(path); + assert(label); + + if (setfilecon(path, (security_context_t) label) < 0) { + log_enforcing("Failed to set SELinux security context %s on path %s: %m", label, path); + if (security_getenforce() > 0) + return -errno; + } +#endif + return 0; +} + +int mac_selinux_get_create_label_from_exe(const char *exe, char **label) { + int r = -EOPNOTSUPP; + +#ifdef HAVE_SELINUX + _cleanup_security_context_free_ security_context_t mycon = NULL, fcon = NULL; + security_class_t sclass; + + assert(exe); + assert(label); + + if (!mac_selinux_have()) + return -EOPNOTSUPP; + + r = getcon_raw(&mycon); + if (r < 0) + return -errno; + + r = getfilecon_raw(exe, &fcon); + if (r < 0) + return -errno; + + sclass = string_to_security_class("process"); + r = security_compute_create_raw(mycon, fcon, sclass, (security_context_t *) label); + if (r < 0) + return -errno; +#endif + + return r; +} + +int mac_selinux_get_our_label(char **label) { + int r = -EOPNOTSUPP; + + assert(label); + +#ifdef HAVE_SELINUX + if (!mac_selinux_have()) + return -EOPNOTSUPP; + + r = getcon_raw(label); + if (r < 0) + return -errno; +#endif + + return r; +} + +int mac_selinux_get_child_mls_label(int socket_fd, const char *exe, const char *exec_label, char **label) { + int r = -EOPNOTSUPP; + +#ifdef HAVE_SELINUX + _cleanup_security_context_free_ security_context_t mycon = NULL, peercon = NULL, fcon = NULL; + _cleanup_context_free_ context_t pcon = NULL, bcon = NULL; + security_class_t sclass; + const char *range = NULL; + + assert(socket_fd >= 0); + assert(exe); + assert(label); + + if (!mac_selinux_have()) + return -EOPNOTSUPP; + + r = getcon_raw(&mycon); + if (r < 0) + return -errno; + + r = getpeercon_raw(socket_fd, &peercon); + if (r < 0) + return -errno; + + if (!exec_label) { + /* If there is no context set for next exec let's use context + of target executable */ + r = getfilecon_raw(exe, &fcon); + if (r < 0) + return -errno; + } + + bcon = context_new(mycon); + if (!bcon) + return -ENOMEM; + + pcon = context_new(peercon); + if (!pcon) + return -ENOMEM; + + range = context_range_get(pcon); + if (!range) + return -errno; + + r = context_range_set(bcon, range); + if (r) + return -errno; + + freecon(mycon); + mycon = strdup(context_str(bcon)); + if (!mycon) + return -ENOMEM; + + sclass = string_to_security_class("process"); + r = security_compute_create_raw(mycon, fcon, sclass, (security_context_t *) label); + if (r < 0) + return -errno; +#endif + + return r; +} + +char* mac_selinux_free(char *label) { + +#ifdef HAVE_SELINUX + if (!label) + return NULL; + + if (!mac_selinux_have()) + return NULL; + + + freecon((security_context_t) label); +#endif + + return NULL; +} + +int mac_selinux_create_file_prepare(const char *path, mode_t mode) { + +#ifdef HAVE_SELINUX + _cleanup_security_context_free_ security_context_t filecon = NULL; + int r; + + assert(path); + + if (!label_hnd) + return 0; + + if (path_is_absolute(path)) + r = selabel_lookup_raw(label_hnd, &filecon, path, mode); + else { + _cleanup_free_ char *newpath = NULL; + + r = path_make_absolute_cwd(path, &newpath); + if (r < 0) + return r; + + r = selabel_lookup_raw(label_hnd, &filecon, newpath, mode); + } + + if (r < 0) { + /* No context specified by the policy? Proceed without setting it. */ + if (errno == ENOENT) + return 0; + + log_enforcing("Failed to determine SELinux security context for %s: %m", path); + } else { + if (setfscreatecon_raw(filecon) >= 0) + return 0; /* Success! */ + + log_enforcing("Failed to set SELinux security context %s for %s: %m", filecon, path); + } + + if (security_getenforce() > 0) + return -errno; + +#endif + return 0; +} + +void mac_selinux_create_file_clear(void) { + +#ifdef HAVE_SELINUX + PROTECT_ERRNO; + + if (!mac_selinux_use()) + return; + + setfscreatecon_raw(NULL); +#endif +} + +int mac_selinux_create_socket_prepare(const char *label) { + +#ifdef HAVE_SELINUX + if (!mac_selinux_use()) + return 0; + + assert(label); + + if (setsockcreatecon((security_context_t) label) < 0) { + log_enforcing("Failed to set SELinux security context %s for sockets: %m", label); + + if (security_getenforce() == 1) + return -errno; + } +#endif + + return 0; +} + +void mac_selinux_create_socket_clear(void) { + +#ifdef HAVE_SELINUX + PROTECT_ERRNO; + + if (!mac_selinux_use()) + return; + + setsockcreatecon_raw(NULL); +#endif +} + +int mac_selinux_bind(int fd, const struct sockaddr *addr, socklen_t addrlen) { + + /* Binds a socket and label its file system object according to the SELinux policy */ + +#ifdef HAVE_SELINUX + _cleanup_security_context_free_ security_context_t fcon = NULL; + const struct sockaddr_un *un; + bool context_changed = false; + char *path; + int r; + + assert(fd >= 0); + assert(addr); + assert(addrlen >= sizeof(sa_family_t)); + + if (!label_hnd) + goto skipped; + + /* Filter out non-local sockets */ + if (addr->sa_family != AF_UNIX) + goto skipped; + + /* Filter out anonymous sockets */ + if (addrlen < offsetof(struct sockaddr_un, sun_path) + 1) + goto skipped; + + /* Filter out abstract namespace sockets */ + un = (const struct sockaddr_un*) addr; + if (un->sun_path[0] == 0) + goto skipped; + + path = strndupa(un->sun_path, addrlen - offsetof(struct sockaddr_un, sun_path)); + + if (path_is_absolute(path)) + r = selabel_lookup_raw(label_hnd, &fcon, path, S_IFSOCK); + else { + _cleanup_free_ char *newpath = NULL; + + r = path_make_absolute_cwd(path, &newpath); + if (r < 0) + return r; + + r = selabel_lookup_raw(label_hnd, &fcon, newpath, S_IFSOCK); + } + + if (r < 0) { + /* No context specified by the policy? Proceed without setting it */ + if (errno == ENOENT) + goto skipped; + + log_enforcing("Failed to determine SELinux security context for %s: %m", path); + if (security_getenforce() > 0) + return -errno; + + } else { + if (setfscreatecon_raw(fcon) < 0) { + log_enforcing("Failed to set SELinux security context %s for %s: %m", fcon, path); + if (security_getenforce() > 0) + return -errno; + } else + context_changed = true; + } + + r = bind(fd, addr, addrlen) < 0 ? -errno : 0; + + if (context_changed) + setfscreatecon_raw(NULL); + + return r; + +skipped: +#endif + if (bind(fd, addr, addrlen) < 0) + return -errno; + + return 0; +} diff --git a/src/libbasic/selinux-util.h b/src/libbasic/selinux-util.h new file mode 100644 index 0000000000..ce6bc8e44c --- /dev/null +++ b/src/libbasic/selinux-util.h @@ -0,0 +1,51 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include + +#include "macro.h" + +bool mac_selinux_use(void); +bool mac_selinux_have(void); +void mac_selinux_retest(void); + +int mac_selinux_init(void); +void mac_selinux_finish(void); + +int mac_selinux_fix(const char *path, bool ignore_enoent, bool ignore_erofs); +int mac_selinux_apply(const char *path, const char *label); + +int mac_selinux_get_create_label_from_exe(const char *exe, char **label); +int mac_selinux_get_our_label(char **label); +int mac_selinux_get_child_mls_label(int socket_fd, const char *exe, const char *exec_label, char **label); +char* mac_selinux_free(char *label); + +int mac_selinux_create_file_prepare(const char *path, mode_t mode); +void mac_selinux_create_file_clear(void); + +int mac_selinux_create_socket_prepare(const char *label); +void mac_selinux_create_socket_clear(void); + +int mac_selinux_bind(int fd, const struct sockaddr *addr, socklen_t addrlen); + +DEFINE_TRIVIAL_CLEANUP_FUNC(char*, mac_selinux_free); diff --git a/src/libbasic/set.h b/src/libbasic/set.h new file mode 100644 index 0000000000..e0d9dd001c --- /dev/null +++ b/src/libbasic/set.h @@ -0,0 +1,136 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "hashmap.h" +#include "macro.h" + +Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +#define set_new(ops) internal_set_new(ops HASHMAP_DEBUG_SRC_ARGS) + +static inline Set *set_free(Set *s) { + internal_hashmap_free(HASHMAP_BASE(s)); + return NULL; +} + +static inline Set *set_free_free(Set *s) { + internal_hashmap_free_free(HASHMAP_BASE(s)); + return NULL; +} + +/* no set_free_free_free */ + +static inline Set *set_copy(Set *s) { + return (Set*) internal_hashmap_copy(HASHMAP_BASE(s)); +} + +int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +#define set_ensure_allocated(h, ops) internal_set_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) + +int set_put(Set *s, const void *key); +/* no set_update */ +/* no set_replace */ +static inline void *set_get(Set *s, void *key) { + return internal_hashmap_get(HASHMAP_BASE(s), key); +} +/* no set_get2 */ + +static inline bool set_contains(Set *s, const void *key) { + return internal_hashmap_contains(HASHMAP_BASE(s), key); +} + +static inline void *set_remove(Set *s, const void *key) { + return internal_hashmap_remove(HASHMAP_BASE(s), key); +} + +/* no set_remove2 */ +/* no set_remove_value */ +int set_remove_and_put(Set *s, const void *old_key, const void *new_key); +/* no set_remove_and_replace */ +int set_merge(Set *s, Set *other); + +static inline int set_reserve(Set *h, unsigned entries_add) { + return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add); +} + +static inline int set_move(Set *s, Set *other) { + return internal_hashmap_move(HASHMAP_BASE(s), HASHMAP_BASE(other)); +} + +static inline int set_move_one(Set *s, Set *other, const void *key) { + return internal_hashmap_move_one(HASHMAP_BASE(s), HASHMAP_BASE(other), key); +} + +static inline unsigned set_size(Set *s) { + return internal_hashmap_size(HASHMAP_BASE(s)); +} + +static inline bool set_isempty(Set *s) { + return set_size(s) == 0; +} + +static inline unsigned set_buckets(Set *s) { + return internal_hashmap_buckets(HASHMAP_BASE(s)); +} + +bool set_iterate(Set *s, Iterator *i, void **value); + +static inline void set_clear(Set *s) { + internal_hashmap_clear(HASHMAP_BASE(s)); +} + +static inline void set_clear_free(Set *s) { + internal_hashmap_clear_free(HASHMAP_BASE(s)); +} + +/* no set_clear_free_free */ + +static inline void *set_steal_first(Set *s) { + return internal_hashmap_steal_first(HASHMAP_BASE(s)); +} + +/* no set_steal_first_key */ +/* no set_first_key */ + +static inline void *set_first(Set *s) { + return internal_hashmap_first(HASHMAP_BASE(s)); +} + +/* no set_next */ + +static inline char **set_get_strv(Set *s) { + return internal_hashmap_get_strv(HASHMAP_BASE(s)); +} + +int set_consume(Set *s, void *value); +int set_put_strdup(Set *s, const char *p); +int set_put_strdupv(Set *s, char **l); + +#define SET_FOREACH(e, s, i) \ + for ((i) = ITERATOR_FIRST; set_iterate((s), &(i), (void**)&(e)); ) + +#define SET_FOREACH_MOVE(e, d, s) \ + for (; ({ e = set_first(s); assert_se(!e || set_move_one(d, s, e) >= 0); e; }); ) + +DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free_free); + +#define _cleanup_set_free_ _cleanup_(set_freep) +#define _cleanup_set_free_free_ _cleanup_(set_free_freep) diff --git a/src/libbasic/sigbus.c b/src/libbasic/sigbus.c new file mode 100644 index 0000000000..0ce4f75684 --- /dev/null +++ b/src/libbasic/sigbus.c @@ -0,0 +1,152 @@ +/*** + 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 +#include +#include + +#include "macro.h" +#include "sigbus.h" +#include "util.h" + +#define SIGBUS_QUEUE_MAX 64 + +static struct sigaction old_sigaction; +static unsigned n_installed = 0; + +/* We maintain a fixed size list of page addresses that triggered a + SIGBUS. We access with list with atomic operations, so that we + don't have to deal with locks between signal handler and main + programs in possibly multiple threads. */ + +static void* volatile sigbus_queue[SIGBUS_QUEUE_MAX]; +static volatile sig_atomic_t n_sigbus_queue = 0; + +static void sigbus_push(void *addr) { + unsigned u; + + assert(addr); + + /* Find a free place, increase the number of entries and leave, if we can */ + for (u = 0; u < SIGBUS_QUEUE_MAX; u++) + if (__sync_bool_compare_and_swap(&sigbus_queue[u], NULL, addr)) { + __sync_fetch_and_add(&n_sigbus_queue, 1); + return; + } + + /* If we can't, make sure the queue size is out of bounds, to + * mark it as overflow */ + for (;;) { + unsigned c; + + __sync_synchronize(); + c = n_sigbus_queue; + + if (c > SIGBUS_QUEUE_MAX) /* already overflow */ + return; + + if (__sync_bool_compare_and_swap(&n_sigbus_queue, c, c + SIGBUS_QUEUE_MAX)) + return; + } +} + +int sigbus_pop(void **ret) { + assert(ret); + + for (;;) { + unsigned u, c; + + __sync_synchronize(); + c = n_sigbus_queue; + + if (_likely_(c == 0)) + return 0; + + if (_unlikely_(c >= SIGBUS_QUEUE_MAX)) + return -EOVERFLOW; + + for (u = 0; u < SIGBUS_QUEUE_MAX; u++) { + void *addr; + + addr = sigbus_queue[u]; + if (!addr) + continue; + + if (__sync_bool_compare_and_swap(&sigbus_queue[u], addr, NULL)) { + __sync_fetch_and_sub(&n_sigbus_queue, 1); + *ret = addr; + return 1; + } + } + } +} + +static void sigbus_handler(int sn, siginfo_t *si, void *data) { + unsigned long ul; + void *aligned; + + assert(sn == SIGBUS); + assert(si); + + if (si->si_code != BUS_ADRERR || !si->si_addr) { + assert_se(sigaction(SIGBUS, &old_sigaction, NULL) == 0); + raise(SIGBUS); + return; + } + + ul = (unsigned long) si->si_addr; + ul = ul / page_size(); + ul = ul * page_size(); + aligned = (void*) ul; + + /* Let's remember which address failed */ + sigbus_push(aligned); + + /* Replace mapping with an anonymous page, so that the + * execution can continue, however with a zeroed out page */ + assert_se(mmap(aligned, page_size(), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0) == aligned); +} + +void sigbus_install(void) { + struct sigaction sa = { + .sa_sigaction = sigbus_handler, + .sa_flags = SA_SIGINFO, + }; + + n_installed++; + + if (n_installed == 1) + assert_se(sigaction(SIGBUS, &sa, &old_sigaction) == 0); + + return; +} + +void sigbus_reset(void) { + + if (n_installed <= 0) + return; + + n_installed--; + + if (n_installed == 0) + assert_se(sigaction(SIGBUS, &old_sigaction, NULL) == 0); + + return; +} diff --git a/src/libbasic/sigbus.h b/src/libbasic/sigbus.h new file mode 100644 index 0000000000..980243d9ce --- /dev/null +++ b/src/libbasic/sigbus.h @@ -0,0 +1,25 @@ +#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 . +***/ + +void sigbus_install(void); +void sigbus_reset(void); + +int sigbus_pop(void **ret); diff --git a/src/libbasic/signal-util.c b/src/libbasic/signal-util.c new file mode 100644 index 0000000000..280b5c3251 --- /dev/null +++ b/src/libbasic/signal-util.c @@ -0,0 +1,278 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 +#include + +#include "macro.h" +#include "parse-util.h" +#include "signal-util.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" + +int reset_all_signal_handlers(void) { + static const struct sigaction sa = { + .sa_handler = SIG_DFL, + .sa_flags = SA_RESTART, + }; + int sig, r = 0; + + for (sig = 1; sig < _NSIG; sig++) { + + /* These two cannot be caught... */ + if (sig == SIGKILL || sig == SIGSTOP) + continue; + + /* On Linux the first two RT signals are reserved by + * glibc, and sigaction() will return EINVAL for them. */ + if ((sigaction(sig, &sa, NULL) < 0)) + if (errno != EINVAL && r >= 0) + r = -errno; + } + + return r; +} + +int reset_signal_mask(void) { + sigset_t ss; + + if (sigemptyset(&ss) < 0) + return -errno; + + if (sigprocmask(SIG_SETMASK, &ss, NULL) < 0) + return -errno; + + return 0; +} + +static int sigaction_many_ap(const struct sigaction *sa, int sig, va_list ap) { + int r = 0; + + /* negative signal ends the list. 0 signal is skipped. */ + + if (sig < 0) + return 0; + + if (sig > 0) { + if (sigaction(sig, sa, NULL) < 0) + r = -errno; + } + + while ((sig = va_arg(ap, int)) >= 0) { + + if (sig == 0) + continue; + + if (sigaction(sig, sa, NULL) < 0) { + if (r >= 0) + r = -errno; + } + } + + return r; +} + +int sigaction_many(const struct sigaction *sa, ...) { + va_list ap; + int r; + + va_start(ap, sa); + r = sigaction_many_ap(sa, 0, ap); + va_end(ap); + + return r; +} + +int ignore_signals(int sig, ...) { + + static const struct sigaction sa = { + .sa_handler = SIG_IGN, + .sa_flags = SA_RESTART, + }; + + va_list ap; + int r; + + va_start(ap, sig); + r = sigaction_many_ap(&sa, sig, ap); + va_end(ap); + + return r; +} + +int default_signals(int sig, ...) { + + static const struct sigaction sa = { + .sa_handler = SIG_DFL, + .sa_flags = SA_RESTART, + }; + + va_list ap; + int r; + + va_start(ap, sig); + r = sigaction_many_ap(&sa, sig, ap); + va_end(ap); + + return r; +} + +static int sigset_add_many_ap(sigset_t *ss, va_list ap) { + int sig, r = 0; + + assert(ss); + + while ((sig = va_arg(ap, int)) >= 0) { + + if (sig == 0) + continue; + + if (sigaddset(ss, sig) < 0) { + if (r >= 0) + r = -errno; + } + } + + return r; +} + +int sigset_add_many(sigset_t *ss, ...) { + va_list ap; + int r; + + va_start(ap, ss); + r = sigset_add_many_ap(ss, ap); + va_end(ap); + + return r; +} + +int sigprocmask_many(int how, sigset_t *old, ...) { + va_list ap; + sigset_t ss; + int r; + + if (sigemptyset(&ss) < 0) + return -errno; + + va_start(ap, old); + r = sigset_add_many_ap(&ss, ap); + va_end(ap); + + if (r < 0) + return r; + + if (sigprocmask(how, &ss, old) < 0) + return -errno; + + return 0; +} + +static const char *const __signal_table[] = { + [SIGHUP] = "HUP", + [SIGINT] = "INT", + [SIGQUIT] = "QUIT", + [SIGILL] = "ILL", + [SIGTRAP] = "TRAP", + [SIGABRT] = "ABRT", + [SIGBUS] = "BUS", + [SIGFPE] = "FPE", + [SIGKILL] = "KILL", + [SIGUSR1] = "USR1", + [SIGSEGV] = "SEGV", + [SIGUSR2] = "USR2", + [SIGPIPE] = "PIPE", + [SIGALRM] = "ALRM", + [SIGTERM] = "TERM", +#ifdef SIGSTKFLT + [SIGSTKFLT] = "STKFLT", /* Linux on SPARC doesn't know SIGSTKFLT */ +#endif + [SIGCHLD] = "CHLD", + [SIGCONT] = "CONT", + [SIGSTOP] = "STOP", + [SIGTSTP] = "TSTP", + [SIGTTIN] = "TTIN", + [SIGTTOU] = "TTOU", + [SIGURG] = "URG", + [SIGXCPU] = "XCPU", + [SIGXFSZ] = "XFSZ", + [SIGVTALRM] = "VTALRM", + [SIGPROF] = "PROF", + [SIGWINCH] = "WINCH", + [SIGIO] = "IO", + [SIGPWR] = "PWR", + [SIGSYS] = "SYS" +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(__signal, int); + +const char *signal_to_string(int signo) { + static thread_local char buf[sizeof("RTMIN+")-1 + DECIMAL_STR_MAX(int) + 1]; + const char *name; + + name = __signal_to_string(signo); + if (name) + return name; + + if (signo >= SIGRTMIN && signo <= SIGRTMAX) + xsprintf(buf, "RTMIN+%d", signo - SIGRTMIN); + else + xsprintf(buf, "%d", signo); + + return buf; +} + +int signal_from_string(const char *s) { + int signo; + int offset = 0; + unsigned u; + + signo = __signal_from_string(s); + if (signo > 0) + return signo; + + if (startswith(s, "RTMIN+")) { + s += 6; + offset = SIGRTMIN; + } + if (safe_atou(s, &u) >= 0) { + signo = (int) u + offset; + if (SIGNAL_VALID(signo)) + return signo; + } + return -EINVAL; +} + +int signal_from_string_try_harder(const char *s) { + int signo; + assert(s); + + signo = signal_from_string(s); + if (signo <= 0) + if (startswith(s, "SIG")) + return signal_from_string(s+3); + + return signo; +} + +void nop_signal_handler(int sig) { + /* nothing here */ +} diff --git a/src/libbasic/signal-util.h b/src/libbasic/signal-util.h new file mode 100644 index 0000000000..dfd6eb564d --- /dev/null +++ b/src/libbasic/signal-util.h @@ -0,0 +1,56 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-2015 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 "macro.h" + +int reset_all_signal_handlers(void); +int reset_signal_mask(void); + +int ignore_signals(int sig, ...); +int default_signals(int sig, ...); +int sigaction_many(const struct sigaction *sa, ...); + +int sigset_add_many(sigset_t *ss, ...); +int sigprocmask_many(int how, sigset_t *old, ...); + +const char *signal_to_string(int i) _const_; +int signal_from_string(const char *s) _pure_; + +int signal_from_string_try_harder(const char *s); + +void nop_signal_handler(int sig); + +static inline void block_signals_reset(sigset_t *ss) { + assert_se(sigprocmask(SIG_SETMASK, ss, NULL) >= 0); +} + +#define BLOCK_SIGNALS(...) \ + _cleanup_(block_signals_reset) _unused_ sigset_t _saved_sigset = ({ \ + sigset_t t; \ + assert_se(sigprocmask_many(SIG_BLOCK, &t, __VA_ARGS__, -1) >= 0); \ + t; \ + }) + +static inline bool SIGNAL_VALID(int signo) { + return signo > 0 && signo < _NSIG; +} diff --git a/src/libbasic/siphash24.c b/src/libbasic/siphash24.c new file mode 100644 index 0000000000..060e8ba387 --- /dev/null +++ b/src/libbasic/siphash24.c @@ -0,0 +1,191 @@ +/* + SipHash reference C implementation + + Written in 2012 by + Jean-Philippe Aumasson + Daniel J. Bernstein + + To the extent possible under law, the author(s) have dedicated all copyright + and related and neighboring rights to this software to the public domain + worldwide. This software is distributed without any warranty. + + You should have received a copy of the CC0 Public Domain Dedication along with + this software. If not, see . + + (Minimal changes made by Lennart Poettering, to make clean for inclusion in systemd) + (Refactored by Tom Gundersen to split up in several functions and follow systemd + coding style) +*/ + +#include "macro.h" +#include "siphash24.h" +#include "unaligned.h" + +static inline uint64_t rotate_left(uint64_t x, uint8_t b) { + assert(b < 64); + + return (x << b) | (x >> (64 - b)); +} + +static inline void sipround(struct siphash *state) { + assert(state); + + state->v0 += state->v1; + state->v1 = rotate_left(state->v1, 13); + state->v1 ^= state->v0; + state->v0 = rotate_left(state->v0, 32); + state->v2 += state->v3; + state->v3 = rotate_left(state->v3, 16); + state->v3 ^= state->v2; + state->v0 += state->v3; + state->v3 = rotate_left(state->v3, 21); + state->v3 ^= state->v0; + state->v2 += state->v1; + state->v1 = rotate_left(state->v1, 17); + state->v1 ^= state->v2; + state->v2 = rotate_left(state->v2, 32); +} + +void siphash24_init(struct siphash *state, const uint8_t k[16]) { + uint64_t k0, k1; + + assert(state); + assert(k); + + k0 = unaligned_read_le64(k); + k1 = unaligned_read_le64(k + 8); + + *state = (struct siphash) { + /* "somepseudorandomlygeneratedbytes" */ + .v0 = 0x736f6d6570736575ULL ^ k0, + .v1 = 0x646f72616e646f6dULL ^ k1, + .v2 = 0x6c7967656e657261ULL ^ k0, + .v3 = 0x7465646279746573ULL ^ k1, + .padding = 0, + .inlen = 0, + }; +} + +void siphash24_compress(const void *_in, size_t inlen, struct siphash *state) { + + const uint8_t *in = _in; + const uint8_t *end = in + inlen; + size_t left = state->inlen & 7; + uint64_t m; + + assert(in); + assert(state); + + /* Update total length */ + state->inlen += inlen; + + /* If padding exists, fill it out */ + if (left > 0) { + for ( ; in < end && left < 8; in ++, left ++) + state->padding |= ((uint64_t) *in) << (left * 8); + + if (in == end && left < 8) + /* We did not have enough input to fill out the padding completely */ + return; + +#ifdef DEBUG + printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); + printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); + printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); + printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); + printf("(%3zu) compress padding %08x %08x\n", state->inlen, (uint32_t) (state->padding >> 32), (uint32_t)state->padding); +#endif + + state->v3 ^= state->padding; + sipround(state); + sipround(state); + state->v0 ^= state->padding; + + state->padding = 0; + } + + end -= (state->inlen % sizeof(uint64_t)); + + for ( ; in < end; in += 8) { + m = unaligned_read_le64(in); +#ifdef DEBUG + printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); + printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); + printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); + printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); + printf("(%3zu) compress %08x %08x\n", state->inlen, (uint32_t) (m >> 32), (uint32_t) m); +#endif + state->v3 ^= m; + sipround(state); + sipround(state); + state->v0 ^= m; + } + + left = state->inlen & 7; + switch (left) { + case 7: + state->padding |= ((uint64_t) in[6]) << 48; + case 6: + state->padding |= ((uint64_t) in[5]) << 40; + case 5: + state->padding |= ((uint64_t) in[4]) << 32; + case 4: + state->padding |= ((uint64_t) in[3]) << 24; + case 3: + state->padding |= ((uint64_t) in[2]) << 16; + case 2: + state->padding |= ((uint64_t) in[1]) << 8; + case 1: + state->padding |= ((uint64_t) in[0]); + case 0: + break; + } +} + +uint64_t siphash24_finalize(struct siphash *state) { + uint64_t b; + + assert(state); + + b = state->padding | (((uint64_t) state->inlen) << 56); + +#ifdef DEBUG + printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); + printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); + printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); + printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); + printf("(%3zu) padding %08x %08x\n", state->inlen, (uint32_t) (state->padding >> 32), (uint32_t) state->padding); +#endif + + state->v3 ^= b; + sipround(state); + sipround(state); + state->v0 ^= b; + +#ifdef DEBUG + printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); + printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); + printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); + printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); +#endif + state->v2 ^= 0xff; + + sipround(state); + sipround(state); + sipround(state); + sipround(state); + + return state->v0 ^ state->v1 ^ state->v2 ^ state->v3; +} + +uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]) { + struct siphash state; + + assert(in); + assert(k); + + siphash24_init(&state, k); + siphash24_compress(in, inlen, &state); + + return siphash24_finalize(&state); +} diff --git a/src/libbasic/siphash24.h b/src/libbasic/siphash24.h new file mode 100644 index 0000000000..54e2420cc6 --- /dev/null +++ b/src/libbasic/siphash24.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include +#include + +struct siphash { + uint64_t v0; + uint64_t v1; + uint64_t v2; + uint64_t v3; + uint64_t padding; + size_t inlen; +}; + +void siphash24_init(struct siphash *state, const uint8_t k[16]); +void siphash24_compress(const void *in, size_t inlen, struct siphash *state); +#define siphash24_compress_byte(byte, state) siphash24_compress((const uint8_t[]) { (byte) }, 1, (state)) + +uint64_t siphash24_finalize(struct siphash *state); + +uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]); diff --git a/src/libbasic/smack-util.c b/src/libbasic/smack-util.c new file mode 100644 index 0000000000..3a3df987df --- /dev/null +++ b/src/libbasic/smack-util.c @@ -0,0 +1,241 @@ +/*** + This file is part of systemd. + + Copyright 2013 Intel Corporation + + Author: Auke Kok + + 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 +#include +#include +#include + +#include "alloc-util.h" +#include "fileio.h" +#include "log.h" +#include "macro.h" +#include "path-util.h" +#include "process-util.h" +#include "smack-util.h" +#include "string-table.h" +#include "xattr-util.h" + +#ifdef HAVE_SMACK +bool mac_smack_use(void) { + static int cached_use = -1; + + if (cached_use < 0) + cached_use = access("/sys/fs/smackfs/", F_OK) >= 0; + + return cached_use; +} + +static const char* const smack_attr_table[_SMACK_ATTR_MAX] = { + [SMACK_ATTR_ACCESS] = "security.SMACK64", + [SMACK_ATTR_EXEC] = "security.SMACK64EXEC", + [SMACK_ATTR_MMAP] = "security.SMACK64MMAP", + [SMACK_ATTR_TRANSMUTE] = "security.SMACK64TRANSMUTE", + [SMACK_ATTR_IPIN] = "security.SMACK64IPIN", + [SMACK_ATTR_IPOUT] = "security.SMACK64IPOUT", +}; + +DEFINE_STRING_TABLE_LOOKUP(smack_attr, SmackAttr); + +int mac_smack_read(const char *path, SmackAttr attr, char **label) { + assert(path); + assert(attr >= 0 && attr < _SMACK_ATTR_MAX); + assert(label); + + if (!mac_smack_use()) + return 0; + + return getxattr_malloc(path, smack_attr_to_string(attr), label, true); +} + +int mac_smack_read_fd(int fd, SmackAttr attr, char **label) { + assert(fd >= 0); + assert(attr >= 0 && attr < _SMACK_ATTR_MAX); + assert(label); + + if (!mac_smack_use()) + return 0; + + return fgetxattr_malloc(fd, smack_attr_to_string(attr), label); +} + +int mac_smack_apply(const char *path, SmackAttr attr, const char *label) { + int r; + + assert(path); + assert(attr >= 0 && attr < _SMACK_ATTR_MAX); + + if (!mac_smack_use()) + return 0; + + if (label) + r = lsetxattr(path, smack_attr_to_string(attr), label, strlen(label), 0); + else + r = lremovexattr(path, smack_attr_to_string(attr)); + if (r < 0) + return -errno; + + return 0; +} + +int mac_smack_apply_fd(int fd, SmackAttr attr, const char *label) { + int r; + + assert(fd >= 0); + assert(attr >= 0 && attr < _SMACK_ATTR_MAX); + + if (!mac_smack_use()) + return 0; + + if (label) + r = fsetxattr(fd, smack_attr_to_string(attr), label, strlen(label), 0); + else + r = fremovexattr(fd, smack_attr_to_string(attr)); + if (r < 0) + return -errno; + + return 0; +} + +int mac_smack_apply_pid(pid_t pid, const char *label) { + const char *p; + int r = 0; + + assert(label); + + if (!mac_smack_use()) + return 0; + + p = procfs_file_alloca(pid, "attr/current"); + r = write_string_file(p, label, 0); + if (r < 0) + return r; + + return r; +} + +int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { + struct stat st; + int r = 0; + + assert(path); + + if (!mac_smack_use()) + return 0; + + /* + * Path must be in /dev and must exist + */ + if (!path_startswith(path, "/dev")) + return 0; + + r = lstat(path, &st); + if (r >= 0) { + const char *label; + + /* + * Label directories and character devices "*". + * Label symlinks "_". + * Don't change anything else. + */ + + if (S_ISDIR(st.st_mode)) + label = SMACK_STAR_LABEL; + else if (S_ISLNK(st.st_mode)) + label = SMACK_FLOOR_LABEL; + else if (S_ISCHR(st.st_mode)) + label = SMACK_STAR_LABEL; + else + return 0; + + r = lsetxattr(path, "security.SMACK64", label, strlen(label), 0); + + /* If the FS doesn't support labels, then exit without warning */ + if (r < 0 && errno == EOPNOTSUPP) + return 0; + } + + if (r < 0) { + /* Ignore ENOENT in some cases */ + if (ignore_enoent && errno == ENOENT) + return 0; + + if (ignore_erofs && errno == EROFS) + return 0; + + r = log_debug_errno(errno, "Unable to fix SMACK label of %s: %m", path); + } + + return r; +} + +int mac_smack_copy(const char *dest, const char *src) { + int r = 0; + _cleanup_free_ char *label = NULL; + + assert(dest); + assert(src); + + r = mac_smack_read(src, SMACK_ATTR_ACCESS, &label); + if (r < 0) + return r; + + r = mac_smack_apply(dest, SMACK_ATTR_ACCESS, label); + if (r < 0) + return r; + + return r; +} + +#else +bool mac_smack_use(void) { + return false; +} + +int mac_smack_read(const char *path, SmackAttr attr, char **label) { + return -EOPNOTSUPP; +} + +int mac_smack_read_fd(int fd, SmackAttr attr, char **label) { + return -EOPNOTSUPP; +} + +int mac_smack_apply(const char *path, SmackAttr attr, const char *label) { + return 0; +} + +int mac_smack_apply_fd(int fd, SmackAttr attr, const char *label) { + return 0; +} + +int mac_smack_apply_pid(pid_t pid, const char *label) { + return 0; +} + +int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { + return 0; +} + +int mac_smack_copy(const char *dest, const char *src) { + return 0; +} +#endif diff --git a/src/libbasic/smack-util.h b/src/libbasic/smack-util.h new file mode 100644 index 0000000000..f90ba0a027 --- /dev/null +++ b/src/libbasic/smack-util.h @@ -0,0 +1,54 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Intel Corporation + + Author: Auke Kok + + 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 + +#include "macro.h" + +#define SMACK_FLOOR_LABEL "_" +#define SMACK_STAR_LABEL "*" + +typedef enum SmackAttr { + SMACK_ATTR_ACCESS = 0, + SMACK_ATTR_EXEC = 1, + SMACK_ATTR_MMAP = 2, + SMACK_ATTR_TRANSMUTE = 3, + SMACK_ATTR_IPIN = 4, + SMACK_ATTR_IPOUT = 5, + _SMACK_ATTR_MAX, + _SMACK_ATTR_INVALID = -1, +} SmackAttr; + +bool mac_smack_use(void); + +int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs); + +const char* smack_attr_to_string(SmackAttr i) _const_; +SmackAttr smack_attr_from_string(const char *s) _pure_; +int mac_smack_read(const char *path, SmackAttr attr, char **label); +int mac_smack_read_fd(int fd, SmackAttr attr, char **label); +int mac_smack_apply(const char *path, SmackAttr attr, const char *label); +int mac_smack_apply_fd(int fd, SmackAttr attr, const char *label); +int mac_smack_apply_pid(pid_t pid, const char *label); +int mac_smack_copy(const char *dest, const char *src); diff --git a/src/libbasic/socket-label.c b/src/libbasic/socket-label.c new file mode 100644 index 0000000000..6d1dc83874 --- /dev/null +++ b/src/libbasic/socket-label.c @@ -0,0 +1,170 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "selinux-util.h" +#include "socket-util.h" +#include "umask-util.h" + +int socket_address_listen( + const SocketAddress *a, + int flags, + int backlog, + SocketAddressBindIPv6Only only, + const char *bind_to_device, + bool reuse_port, + bool free_bind, + bool transparent, + mode_t directory_mode, + mode_t socket_mode, + const char *label) { + + _cleanup_close_ int fd = -1; + int r, one; + + assert(a); + + r = socket_address_verify(a); + if (r < 0) + return r; + + if (socket_address_family(a) == AF_INET6 && !socket_ipv6_is_supported()) + return -EAFNOSUPPORT; + + if (label) { + r = mac_selinux_create_socket_prepare(label); + if (r < 0) + return r; + } + + fd = socket(socket_address_family(a), a->type | flags, a->protocol); + r = fd < 0 ? -errno : 0; + + if (label) + mac_selinux_create_socket_clear(); + + if (r < 0) + return r; + + if (socket_address_family(a) == AF_INET6 && only != SOCKET_ADDRESS_DEFAULT) { + int flag = only == SOCKET_ADDRESS_IPV6_ONLY; + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag)) < 0) + return -errno; + } + + if (socket_address_family(a) == AF_INET || socket_address_family(a) == AF_INET6) { + if (bind_to_device) + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, bind_to_device, strlen(bind_to_device)+1) < 0) + return -errno; + + if (reuse_port) { + one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)) < 0) + log_warning_errno(errno, "SO_REUSEPORT failed: %m"); + } + + if (free_bind) { + one = 1; + if (setsockopt(fd, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one)) < 0) + log_warning_errno(errno, "IP_FREEBIND failed: %m"); + } + + if (transparent) { + one = 1; + if (setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &one, sizeof(one)) < 0) + log_warning_errno(errno, "IP_TRANSPARENT failed: %m"); + } + } + + one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) + return -errno; + + if (socket_address_family(a) == AF_UNIX && a->sockaddr.un.sun_path[0] != 0) { + /* Create parents */ + (void) mkdir_parents_label(a->sockaddr.un.sun_path, directory_mode); + + /* Enforce the right access mode for the socket */ + RUN_WITH_UMASK(~socket_mode) { + r = mac_selinux_bind(fd, &a->sockaddr.sa, a->size); + if (r == -EADDRINUSE) { + /* Unlink and try again */ + unlink(a->sockaddr.un.sun_path); + if (bind(fd, &a->sockaddr.sa, a->size) < 0) + return -errno; + } else if (r < 0) + return r; + } + } else { + if (bind(fd, &a->sockaddr.sa, a->size) < 0) + return -errno; + } + + if (socket_address_can_accept(a)) + if (listen(fd, backlog) < 0) + return -errno; + + r = fd; + fd = -1; + + return r; +} + +int make_socket_fd(int log_level, const char* address, int type, int flags) { + SocketAddress a; + int fd, r; + + r = socket_address_parse(&a, address); + if (r < 0) + return log_error_errno(r, "Failed to parse socket address \"%s\": %m", address); + + a.type = type; + + fd = socket_address_listen(&a, type | flags, SOMAXCONN, SOCKET_ADDRESS_DEFAULT, + NULL, false, false, false, 0755, 0644, NULL); + if (fd < 0 || log_get_max_level() >= log_level) { + _cleanup_free_ char *p = NULL; + + r = socket_address_print(&a, &p); + if (r < 0) + return log_error_errno(r, "socket_address_print(): %m"); + + if (fd < 0) + log_error_errno(fd, "Failed to listen on %s: %m", p); + else + log_full(log_level, "Listening on %s", p); + } + + return fd; +} diff --git a/src/libbasic/socket-util.c b/src/libbasic/socket-util.c new file mode 100644 index 0000000000..c8769a54f4 --- /dev/null +++ b/src/libbasic/socket-util.c @@ -0,0 +1,1050 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "parse-util.h" +#include "path-util.h" +#include "socket-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" +#include "utf8.h" +#include "util.h" + +int socket_address_parse(SocketAddress *a, const char *s) { + char *e, *n; + unsigned u; + int r; + + assert(a); + assert(s); + + zero(*a); + a->type = SOCK_STREAM; + + if (*s == '[') { + /* IPv6 in [x:.....:z]:p notation */ + + e = strchr(s+1, ']'); + if (!e) + return -EINVAL; + + n = strndupa(s+1, e-s-1); + + errno = 0; + if (inet_pton(AF_INET6, n, &a->sockaddr.in6.sin6_addr) <= 0) + return errno > 0 ? -errno : -EINVAL; + + e++; + if (*e != ':') + return -EINVAL; + + e++; + r = safe_atou(e, &u); + if (r < 0) + return r; + + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + + a->sockaddr.in6.sin6_family = AF_INET6; + a->sockaddr.in6.sin6_port = htons((uint16_t) u); + a->size = sizeof(struct sockaddr_in6); + + } else if (*s == '/') { + /* AF_UNIX socket */ + + size_t l; + + l = strlen(s); + if (l >= sizeof(a->sockaddr.un.sun_path)) + return -EINVAL; + + a->sockaddr.un.sun_family = AF_UNIX; + memcpy(a->sockaddr.un.sun_path, s, l); + a->size = offsetof(struct sockaddr_un, sun_path) + l + 1; + + } else if (*s == '@') { + /* Abstract AF_UNIX socket */ + size_t l; + + l = strlen(s+1); + if (l >= sizeof(a->sockaddr.un.sun_path) - 1) + return -EINVAL; + + a->sockaddr.un.sun_family = AF_UNIX; + memcpy(a->sockaddr.un.sun_path+1, s+1, l); + a->size = offsetof(struct sockaddr_un, sun_path) + 1 + l; + + } else { + e = strchr(s, ':'); + if (e) { + r = safe_atou(e+1, &u); + if (r < 0) + return r; + + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + + n = strndupa(s, e-s); + + /* IPv4 in w.x.y.z:p notation? */ + r = inet_pton(AF_INET, n, &a->sockaddr.in.sin_addr); + if (r < 0) + return -errno; + + if (r > 0) { + /* Gotcha, it's a traditional IPv4 address */ + a->sockaddr.in.sin_family = AF_INET; + a->sockaddr.in.sin_port = htons((uint16_t) u); + a->size = sizeof(struct sockaddr_in); + } else { + unsigned idx; + + if (strlen(n) > IF_NAMESIZE-1) + return -EINVAL; + + /* Uh, our last resort, an interface name */ + idx = if_nametoindex(n); + if (idx == 0) + return -EINVAL; + + a->sockaddr.in6.sin6_family = AF_INET6; + a->sockaddr.in6.sin6_port = htons((uint16_t) u); + a->sockaddr.in6.sin6_scope_id = idx; + a->sockaddr.in6.sin6_addr = in6addr_any; + a->size = sizeof(struct sockaddr_in6); + } + } else { + + /* Just a port */ + r = safe_atou(s, &u); + if (r < 0) + return r; + + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + + if (socket_ipv6_is_supported()) { + a->sockaddr.in6.sin6_family = AF_INET6; + a->sockaddr.in6.sin6_port = htons((uint16_t) u); + a->sockaddr.in6.sin6_addr = in6addr_any; + a->size = sizeof(struct sockaddr_in6); + } else { + a->sockaddr.in.sin_family = AF_INET; + a->sockaddr.in.sin_port = htons((uint16_t) u); + a->sockaddr.in.sin_addr.s_addr = INADDR_ANY; + a->size = sizeof(struct sockaddr_in); + } + } + } + + return 0; +} + +int socket_address_parse_and_warn(SocketAddress *a, const char *s) { + SocketAddress b; + int r; + + /* Similar to socket_address_parse() but warns for IPv6 sockets when we don't support them. */ + + r = socket_address_parse(&b, s); + if (r < 0) + return r; + + if (!socket_ipv6_is_supported() && b.sockaddr.sa.sa_family == AF_INET6) { + log_warning("Binding to IPv6 address not available since kernel does not support IPv6."); + return -EAFNOSUPPORT; + } + + *a = b; + return 0; +} + +int socket_address_parse_netlink(SocketAddress *a, const char *s) { + int family; + unsigned group = 0; + _cleanup_free_ char *sfamily = NULL; + assert(a); + assert(s); + + zero(*a); + a->type = SOCK_RAW; + + errno = 0; + if (sscanf(s, "%ms %u", &sfamily, &group) < 1) + return errno > 0 ? -errno : -EINVAL; + + family = netlink_family_from_string(sfamily); + if (family < 0) + return -EINVAL; + + a->sockaddr.nl.nl_family = AF_NETLINK; + a->sockaddr.nl.nl_groups = group; + + a->type = SOCK_RAW; + a->size = sizeof(struct sockaddr_nl); + a->protocol = family; + + return 0; +} + +int socket_address_verify(const SocketAddress *a) { + assert(a); + + switch (socket_address_family(a)) { + + case AF_INET: + if (a->size != sizeof(struct sockaddr_in)) + return -EINVAL; + + if (a->sockaddr.in.sin_port == 0) + return -EINVAL; + + if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM) + return -EINVAL; + + return 0; + + case AF_INET6: + if (a->size != sizeof(struct sockaddr_in6)) + return -EINVAL; + + if (a->sockaddr.in6.sin6_port == 0) + return -EINVAL; + + if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM) + return -EINVAL; + + return 0; + + case AF_UNIX: + if (a->size < offsetof(struct sockaddr_un, sun_path)) + return -EINVAL; + + if (a->size > offsetof(struct sockaddr_un, sun_path)) { + + if (a->sockaddr.un.sun_path[0] != 0) { + char *e; + + /* path */ + e = memchr(a->sockaddr.un.sun_path, 0, sizeof(a->sockaddr.un.sun_path)); + if (!e) + return -EINVAL; + + if (a->size != offsetof(struct sockaddr_un, sun_path) + (e - a->sockaddr.un.sun_path) + 1) + return -EINVAL; + } + } + + if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM && a->type != SOCK_SEQPACKET) + return -EINVAL; + + return 0; + + case AF_NETLINK: + + if (a->size != sizeof(struct sockaddr_nl)) + return -EINVAL; + + if (a->type != SOCK_RAW && a->type != SOCK_DGRAM) + return -EINVAL; + + return 0; + + default: + return -EAFNOSUPPORT; + } +} + +int socket_address_print(const SocketAddress *a, char **ret) { + int r; + + assert(a); + assert(ret); + + r = socket_address_verify(a); + if (r < 0) + return r; + + if (socket_address_family(a) == AF_NETLINK) { + _cleanup_free_ char *sfamily = NULL; + + r = netlink_family_to_string_alloc(a->protocol, &sfamily); + if (r < 0) + return r; + + r = asprintf(ret, "%s %u", sfamily, a->sockaddr.nl.nl_groups); + if (r < 0) + return -ENOMEM; + + return 0; + } + + return sockaddr_pretty(&a->sockaddr.sa, a->size, false, true, ret); +} + +bool socket_address_can_accept(const SocketAddress *a) { + assert(a); + + return + a->type == SOCK_STREAM || + a->type == SOCK_SEQPACKET; +} + +bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) { + assert(a); + assert(b); + + /* Invalid addresses are unequal to all */ + if (socket_address_verify(a) < 0 || + socket_address_verify(b) < 0) + return false; + + if (a->type != b->type) + return false; + + if (socket_address_family(a) != socket_address_family(b)) + return false; + + switch (socket_address_family(a)) { + + case AF_INET: + if (a->sockaddr.in.sin_addr.s_addr != b->sockaddr.in.sin_addr.s_addr) + return false; + + if (a->sockaddr.in.sin_port != b->sockaddr.in.sin_port) + return false; + + break; + + case AF_INET6: + if (memcmp(&a->sockaddr.in6.sin6_addr, &b->sockaddr.in6.sin6_addr, sizeof(a->sockaddr.in6.sin6_addr)) != 0) + return false; + + if (a->sockaddr.in6.sin6_port != b->sockaddr.in6.sin6_port) + return false; + + break; + + case AF_UNIX: + if (a->size <= offsetof(struct sockaddr_un, sun_path) || + b->size <= offsetof(struct sockaddr_un, sun_path)) + return false; + + if ((a->sockaddr.un.sun_path[0] == 0) != (b->sockaddr.un.sun_path[0] == 0)) + return false; + + if (a->sockaddr.un.sun_path[0]) { + if (!path_equal_or_files_same(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path)) + return false; + } else { + if (a->size != b->size) + return false; + + if (memcmp(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, a->size) != 0) + return false; + } + + break; + + case AF_NETLINK: + if (a->protocol != b->protocol) + return false; + + if (a->sockaddr.nl.nl_groups != b->sockaddr.nl.nl_groups) + return false; + + break; + + default: + /* Cannot compare, so we assume the addresses are different */ + return false; + } + + return true; +} + +bool socket_address_is(const SocketAddress *a, const char *s, int type) { + struct SocketAddress b; + + assert(a); + assert(s); + + if (socket_address_parse(&b, s) < 0) + return false; + + b.type = type; + + return socket_address_equal(a, &b); +} + +bool socket_address_is_netlink(const SocketAddress *a, const char *s) { + struct SocketAddress b; + + assert(a); + assert(s); + + if (socket_address_parse_netlink(&b, s) < 0) + return false; + + return socket_address_equal(a, &b); +} + +const char* socket_address_get_path(const SocketAddress *a) { + assert(a); + + if (socket_address_family(a) != AF_UNIX) + return NULL; + + if (a->sockaddr.un.sun_path[0] == 0) + return NULL; + + return a->sockaddr.un.sun_path; +} + +bool socket_ipv6_is_supported(void) { + if (access("/proc/net/sockstat6", F_OK) != 0) + return false; + + return true; +} + +bool socket_address_matches_fd(const SocketAddress *a, int fd) { + SocketAddress b; + socklen_t solen; + + assert(a); + assert(fd >= 0); + + b.size = sizeof(b.sockaddr); + if (getsockname(fd, &b.sockaddr.sa, &b.size) < 0) + return false; + + if (b.sockaddr.sa.sa_family != a->sockaddr.sa.sa_family) + return false; + + solen = sizeof(b.type); + if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &b.type, &solen) < 0) + return false; + + if (b.type != a->type) + return false; + + if (a->protocol != 0) { + solen = sizeof(b.protocol); + if (getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &b.protocol, &solen) < 0) + return false; + + if (b.protocol != a->protocol) + return false; + } + + return socket_address_equal(a, &b); +} + +int sockaddr_port(const struct sockaddr *_sa) { + union sockaddr_union *sa = (union sockaddr_union*) _sa; + + assert(sa); + + if (!IN_SET(sa->sa.sa_family, AF_INET, AF_INET6)) + return -EAFNOSUPPORT; + + return ntohs(sa->sa.sa_family == AF_INET6 ? + sa->in6.sin6_port : + sa->in.sin_port); +} + +int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ipv6, bool include_port, char **ret) { + union sockaddr_union *sa = (union sockaddr_union*) _sa; + char *p; + int r; + + assert(sa); + assert(salen >= sizeof(sa->sa.sa_family)); + + switch (sa->sa.sa_family) { + + case AF_INET: { + uint32_t a; + + a = ntohl(sa->in.sin_addr.s_addr); + + if (include_port) + r = asprintf(&p, + "%u.%u.%u.%u:%u", + a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF, + ntohs(sa->in.sin_port)); + else + r = asprintf(&p, + "%u.%u.%u.%u", + a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF); + if (r < 0) + return -ENOMEM; + break; + } + + case AF_INET6: { + static const unsigned char ipv4_prefix[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF + }; + + if (translate_ipv6 && + memcmp(&sa->in6.sin6_addr, ipv4_prefix, sizeof(ipv4_prefix)) == 0) { + const uint8_t *a = sa->in6.sin6_addr.s6_addr+12; + if (include_port) + r = asprintf(&p, + "%u.%u.%u.%u:%u", + a[0], a[1], a[2], a[3], + ntohs(sa->in6.sin6_port)); + else + r = asprintf(&p, + "%u.%u.%u.%u", + a[0], a[1], a[2], a[3]); + if (r < 0) + return -ENOMEM; + } else { + char a[INET6_ADDRSTRLEN]; + + inet_ntop(AF_INET6, &sa->in6.sin6_addr, a, sizeof(a)); + + if (include_port) { + r = asprintf(&p, + "[%s]:%u", + a, + ntohs(sa->in6.sin6_port)); + if (r < 0) + return -ENOMEM; + } else { + p = strdup(a); + if (!p) + return -ENOMEM; + } + } + + break; + } + + case AF_UNIX: + if (salen <= offsetof(struct sockaddr_un, sun_path)) { + p = strdup(""); + if (!p) + return -ENOMEM; + + } else if (sa->un.sun_path[0] == 0) { + /* abstract */ + + /* FIXME: We assume we can print the + * socket path here and that it hasn't + * more than one NUL byte. That is + * actually an invalid assumption */ + + p = new(char, sizeof(sa->un.sun_path)+1); + if (!p) + return -ENOMEM; + + p[0] = '@'; + memcpy(p+1, sa->un.sun_path+1, sizeof(sa->un.sun_path)-1); + p[sizeof(sa->un.sun_path)] = 0; + + } else { + p = strndup(sa->un.sun_path, sizeof(sa->un.sun_path)); + if (!p) + return -ENOMEM; + } + + break; + + default: + return -EOPNOTSUPP; + } + + + *ret = p; + return 0; +} + +int getpeername_pretty(int fd, bool include_port, char **ret) { + union sockaddr_union sa; + socklen_t salen = sizeof(sa); + int r; + + assert(fd >= 0); + assert(ret); + + if (getpeername(fd, &sa.sa, &salen) < 0) + return -errno; + + if (sa.sa.sa_family == AF_UNIX) { + struct ucred ucred = {}; + + /* UNIX connection sockets are anonymous, so let's use + * PID/UID as pretty credentials instead */ + + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + if (asprintf(ret, "PID "PID_FMT"/UID "UID_FMT, ucred.pid, ucred.uid) < 0) + return -ENOMEM; + + return 0; + } + + /* For remote sockets we translate IPv6 addresses back to IPv4 + * if applicable, since that's nicer. */ + + return sockaddr_pretty(&sa.sa, salen, true, include_port, ret); +} + +int getsockname_pretty(int fd, char **ret) { + union sockaddr_union sa; + socklen_t salen = sizeof(sa); + + assert(fd >= 0); + assert(ret); + + if (getsockname(fd, &sa.sa, &salen) < 0) + return -errno; + + /* For local sockets we do not translate IPv6 addresses back + * to IPv6 if applicable, since this is usually used for + * listening sockets where the difference between IPv4 and + * IPv6 matters. */ + + return sockaddr_pretty(&sa.sa, salen, false, true, ret); +} + +int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret) { + int r; + char host[NI_MAXHOST], *ret; + + assert(_ret); + + r = getnameinfo(&sa->sa, salen, host, sizeof(host), NULL, 0, + NI_IDN|NI_IDN_USE_STD3_ASCII_RULES); + if (r != 0) { + int saved_errno = errno; + + r = sockaddr_pretty(&sa->sa, salen, true, true, &ret); + if (r < 0) + return r; + + log_debug_errno(saved_errno, "getnameinfo(%s) failed: %m", ret); + } else { + ret = strdup(host); + if (!ret) + return -ENOMEM; + } + + *_ret = ret; + return 0; +} + +int getnameinfo_pretty(int fd, char **ret) { + union sockaddr_union sa; + socklen_t salen = sizeof(sa); + + assert(fd >= 0); + assert(ret); + + if (getsockname(fd, &sa.sa, &salen) < 0) + return -errno; + + return socknameinfo_pretty(&sa, salen, ret); +} + +int socket_address_unlink(SocketAddress *a) { + assert(a); + + if (socket_address_family(a) != AF_UNIX) + return 0; + + if (a->sockaddr.un.sun_path[0] == 0) + return 0; + + if (unlink(a->sockaddr.un.sun_path) < 0) + return -errno; + + return 1; +} + +static const char* const netlink_family_table[] = { + [NETLINK_ROUTE] = "route", + [NETLINK_FIREWALL] = "firewall", + [NETLINK_INET_DIAG] = "inet-diag", + [NETLINK_NFLOG] = "nflog", + [NETLINK_XFRM] = "xfrm", + [NETLINK_SELINUX] = "selinux", + [NETLINK_ISCSI] = "iscsi", + [NETLINK_AUDIT] = "audit", + [NETLINK_FIB_LOOKUP] = "fib-lookup", + [NETLINK_CONNECTOR] = "connector", + [NETLINK_NETFILTER] = "netfilter", + [NETLINK_IP6_FW] = "ip6-fw", + [NETLINK_DNRTMSG] = "dnrtmsg", + [NETLINK_KOBJECT_UEVENT] = "kobject-uevent", + [NETLINK_GENERIC] = "generic", + [NETLINK_SCSITRANSPORT] = "scsitransport", + [NETLINK_ECRYPTFS] = "ecryptfs" +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(netlink_family, int, INT_MAX); + +static const char* const socket_address_bind_ipv6_only_table[_SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX] = { + [SOCKET_ADDRESS_DEFAULT] = "default", + [SOCKET_ADDRESS_BOTH] = "both", + [SOCKET_ADDRESS_IPV6_ONLY] = "ipv6-only" +}; + +DEFINE_STRING_TABLE_LOOKUP(socket_address_bind_ipv6_only, SocketAddressBindIPv6Only); + +bool sockaddr_equal(const union sockaddr_union *a, const union sockaddr_union *b) { + assert(a); + assert(b); + + if (a->sa.sa_family != b->sa.sa_family) + return false; + + if (a->sa.sa_family == AF_INET) + return a->in.sin_addr.s_addr == b->in.sin_addr.s_addr; + + if (a->sa.sa_family == AF_INET6) + return memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a->in6.sin6_addr)) == 0; + + return false; +} + +int fd_inc_sndbuf(int fd, size_t n) { + int r, value; + socklen_t l = sizeof(value); + + r = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, &l); + if (r >= 0 && l == sizeof(value) && (size_t) value >= n*2) + return 0; + + /* If we have the privileges we will ignore the kernel limit. */ + + value = (int) n; + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUFFORCE, &value, sizeof(value)) < 0) + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)) < 0) + return -errno; + + return 1; +} + +int fd_inc_rcvbuf(int fd, size_t n) { + int r, value; + socklen_t l = sizeof(value); + + r = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, &l); + if (r >= 0 && l == sizeof(value) && (size_t) value >= n*2) + return 0; + + /* If we have the privileges we will ignore the kernel limit. */ + + value = (int) n; + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &value, sizeof(value)) < 0) + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)) < 0) + return -errno; + return 1; +} + +static const char* const ip_tos_table[] = { + [IPTOS_LOWDELAY] = "low-delay", + [IPTOS_THROUGHPUT] = "throughput", + [IPTOS_RELIABILITY] = "reliability", + [IPTOS_LOWCOST] = "low-cost", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ip_tos, int, 0xff); + +bool ifname_valid(const char *p) { + bool numeric = true; + + /* Checks whether a network interface name is valid. This is inspired by dev_valid_name() in the kernel sources + * but slightly stricter, as we only allow non-control, non-space ASCII characters in the interface name. We + * also don't permit names that only container numbers, to avoid confusion with numeric interface indexes. */ + + if (isempty(p)) + return false; + + if (strlen(p) >= IFNAMSIZ) + return false; + + if (STR_IN_SET(p, ".", "..")) + return false; + + while (*p) { + if ((unsigned char) *p >= 127U) + return false; + + if ((unsigned char) *p <= 32U) + return false; + + if (*p == ':' || *p == '/') + return false; + + numeric = numeric && (*p >= '0' && *p <= '9'); + p++; + } + + if (numeric) + return false; + + return true; +} + +int getpeercred(int fd, struct ucred *ucred) { + socklen_t n = sizeof(struct ucred); + struct ucred u; + int r; + + assert(fd >= 0); + assert(ucred); + + r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &u, &n); + if (r < 0) + return -errno; + + if (n != sizeof(struct ucred)) + return -EIO; + + /* Check if the data is actually useful and not suppressed due + * to namespacing issues */ + if (u.pid <= 0) + return -ENODATA; + if (u.uid == UID_INVALID) + return -ENODATA; + if (u.gid == GID_INVALID) + return -ENODATA; + + *ucred = u; + return 0; +} + +int getpeersec(int fd, char **ret) { + socklen_t n = 64; + char *s; + int r; + + assert(fd >= 0); + assert(ret); + + s = new0(char, n); + if (!s) + return -ENOMEM; + + r = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n); + if (r < 0) { + free(s); + + if (errno != ERANGE) + return -errno; + + s = new0(char, n); + if (!s) + return -ENOMEM; + + r = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n); + if (r < 0) { + free(s); + return -errno; + } + } + + if (isempty(s)) { + free(s); + return -EOPNOTSUPP; + } + + *ret = s; + return 0; +} + +int send_one_fd_sa( + int transport_fd, + int fd, + const struct sockaddr *sa, socklen_t len, + int flags) { + + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + struct msghdr mh = { + .msg_name = (struct sockaddr*) sa, + .msg_namelen = len, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg; + + assert(transport_fd >= 0); + assert(fd >= 0); + + cmsg = CMSG_FIRSTHDR(&mh); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cmsg), &fd, sizeof(int)); + + mh.msg_controllen = CMSG_SPACE(sizeof(int)); + if (sendmsg(transport_fd, &mh, MSG_NOSIGNAL | flags) < 0) + return -errno; + + return 0; +} + +int receive_one_fd(int transport_fd, int flags) { + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + struct msghdr mh = { + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg, *found = NULL; + + assert(transport_fd >= 0); + + /* + * Receive a single FD via @transport_fd. We don't care for + * the transport-type. We retrieve a single FD at most, so for + * packet-based transports, the caller must ensure to send + * only a single FD per packet. This is best used in + * combination with send_one_fd(). + */ + + if (recvmsg(transport_fd, &mh, MSG_NOSIGNAL | MSG_CMSG_CLOEXEC | flags) < 0) + return -errno; + + CMSG_FOREACH(cmsg, &mh) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS && + cmsg->cmsg_len == CMSG_LEN(sizeof(int))) { + assert(!found); + found = cmsg; + break; + } + } + + if (!found) { + cmsg_close_all(&mh); + return -EIO; + } + + return *(int*) CMSG_DATA(found); +} + +ssize_t next_datagram_size_fd(int fd) { + ssize_t l; + int k; + + /* This is a bit like FIONREAD/SIOCINQ, however a bit more powerful. The difference being: recv(MSG_PEEK) will + * actually cause the next datagram in the queue to be validated regarding checksums, which FIONREAD doesn't + * do. This difference is actually of major importance as we need to be sure that the size returned here + * actually matches what we will read with recvmsg() next, as otherwise we might end up allocating a buffer of + * the wrong size. */ + + l = recv(fd, NULL, 0, MSG_PEEK|MSG_TRUNC); + if (l < 0) { + if (errno == EOPNOTSUPP) + goto fallback; + + return -errno; + } + if (l == 0) + goto fallback; + + return l; + +fallback: + k = 0; + + /* Some sockets (AF_PACKET) do not support null-sized recv() with MSG_TRUNC set, let's fall back to FIONREAD + * for them. Checksums don't matter for raw sockets anyway, hence this should be fine. */ + + if (ioctl(fd, FIONREAD, &k) < 0) + return -errno; + + return (ssize_t) k; +} + +int flush_accept(int fd) { + + struct pollfd pollfd = { + .fd = fd, + .events = POLLIN, + }; + int r; + + + /* Similar to flush_fd() but flushes all incoming connection by accepting them and immediately closing them. */ + + for (;;) { + int cfd; + + r = poll(&pollfd, 1, 0); + if (r < 0) { + if (errno == EINTR) + continue; + + return -errno; + + } else if (r == 0) + return 0; + + cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); + if (cfd < 0) { + if (errno == EINTR) + continue; + + if (errno == EAGAIN) + return 0; + + return -errno; + } + + close(cfd); + } +} diff --git a/src/libbasic/socket-util.h b/src/libbasic/socket-util.h new file mode 100644 index 0000000000..e9230e4a9f --- /dev/null +++ b/src/libbasic/socket-util.h @@ -0,0 +1,154 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include + +#include "macro.h" +#include "util.h" + +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in in; + struct sockaddr_in6 in6; + struct sockaddr_un un; + struct sockaddr_nl nl; + struct sockaddr_storage storage; + struct sockaddr_ll ll; +}; + +typedef struct SocketAddress { + union sockaddr_union sockaddr; + + /* We store the size here explicitly due to the weird + * sockaddr_un semantics for abstract sockets */ + socklen_t size; + + /* Socket type, i.e. SOCK_STREAM, SOCK_DGRAM, ... */ + int type; + + /* Socket protocol, IPPROTO_xxx, usually 0, except for netlink */ + int protocol; +} SocketAddress; + +typedef enum SocketAddressBindIPv6Only { + SOCKET_ADDRESS_DEFAULT, + SOCKET_ADDRESS_BOTH, + SOCKET_ADDRESS_IPV6_ONLY, + _SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX, + _SOCKET_ADDRESS_BIND_IPV6_ONLY_INVALID = -1 +} SocketAddressBindIPv6Only; + +#define socket_address_family(a) ((a)->sockaddr.sa.sa_family) + +int socket_address_parse(SocketAddress *a, const char *s); +int socket_address_parse_and_warn(SocketAddress *a, const char *s); +int socket_address_parse_netlink(SocketAddress *a, const char *s); +int socket_address_print(const SocketAddress *a, char **p); +int socket_address_verify(const SocketAddress *a) _pure_; +int socket_address_unlink(SocketAddress *a); + +bool socket_address_can_accept(const SocketAddress *a) _pure_; + +int socket_address_listen( + const SocketAddress *a, + int flags, + int backlog, + SocketAddressBindIPv6Only only, + const char *bind_to_device, + bool reuse_port, + bool free_bind, + bool transparent, + mode_t directory_mode, + mode_t socket_mode, + const char *label); +int make_socket_fd(int log_level, const char* address, int type, int flags); + +bool socket_address_is(const SocketAddress *a, const char *s, int type); +bool socket_address_is_netlink(const SocketAddress *a, const char *s); + +bool socket_address_matches_fd(const SocketAddress *a, int fd); + +bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) _pure_; + +const char* socket_address_get_path(const SocketAddress *a); + +bool socket_ipv6_is_supported(void); + +int sockaddr_port(const struct sockaddr *_sa) _pure_; + +int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ipv6, bool include_port, char **ret); +int getpeername_pretty(int fd, bool include_port, char **ret); +int getsockname_pretty(int fd, char **ret); + +int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret); +int getnameinfo_pretty(int fd, char **ret); + +const char* socket_address_bind_ipv6_only_to_string(SocketAddressBindIPv6Only b) _const_; +SocketAddressBindIPv6Only socket_address_bind_ipv6_only_from_string(const char *s) _pure_; + +int netlink_family_to_string_alloc(int b, char **s); +int netlink_family_from_string(const char *s) _pure_; + +bool sockaddr_equal(const union sockaddr_union *a, const union sockaddr_union *b); + +int fd_inc_sndbuf(int fd, size_t n); +int fd_inc_rcvbuf(int fd, size_t n); + +int ip_tos_to_string_alloc(int i, char **s); +int ip_tos_from_string(const char *s); + +bool ifname_valid(const char *p); + +int getpeercred(int fd, struct ucred *ucred); +int getpeersec(int fd, char **ret); + +int send_one_fd_sa(int transport_fd, + int fd, + const struct sockaddr *sa, socklen_t len, + int flags); +#define send_one_fd(transport_fd, fd, flags) send_one_fd_sa(transport_fd, fd, NULL, 0, flags) +int receive_one_fd(int transport_fd, int flags); + +ssize_t next_datagram_size_fd(int fd); + +int flush_accept(int fd); + +#define CMSG_FOREACH(cmsg, mh) \ + for ((cmsg) = CMSG_FIRSTHDR(mh); (cmsg); (cmsg) = CMSG_NXTHDR((mh), (cmsg))) + +/* Covers only file system and abstract AF_UNIX socket addresses, but not unnamed socket addresses. */ +#define SOCKADDR_UN_LEN(sa) \ + ({ \ + const struct sockaddr_un *_sa = &(sa); \ + assert(_sa->sun_family == AF_UNIX); \ + offsetof(struct sockaddr_un, sun_path) + \ + (_sa->sun_path[0] == 0 ? \ + 1 + strnlen(_sa->sun_path+1, sizeof(_sa->sun_path)-1) : \ + strnlen(_sa->sun_path, sizeof(_sa->sun_path))); \ + }) diff --git a/src/libbasic/sparse-endian.h b/src/libbasic/sparse-endian.h new file mode 100644 index 0000000000..c913fda8c5 --- /dev/null +++ b/src/libbasic/sparse-endian.h @@ -0,0 +1,88 @@ +/* Copyright (c) 2012 Josh Triplett + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef SPARSE_ENDIAN_H +#define SPARSE_ENDIAN_H + +#include +#include +#include + +#ifdef __CHECKER__ +#define __bitwise __attribute__((bitwise)) +#define __force __attribute__((force)) +#else +#define __bitwise +#define __force +#endif + +typedef uint16_t __bitwise le16_t; +typedef uint16_t __bitwise be16_t; +typedef uint32_t __bitwise le32_t; +typedef uint32_t __bitwise be32_t; +typedef uint64_t __bitwise le64_t; +typedef uint64_t __bitwise be64_t; + +#undef htobe16 +#undef htole16 +#undef be16toh +#undef le16toh +#undef htobe32 +#undef htole32 +#undef be32toh +#undef le32toh +#undef htobe64 +#undef htole64 +#undef be64toh +#undef le64toh + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define bswap_16_on_le(x) __bswap_16(x) +#define bswap_32_on_le(x) __bswap_32(x) +#define bswap_64_on_le(x) __bswap_64(x) +#define bswap_16_on_be(x) (x) +#define bswap_32_on_be(x) (x) +#define bswap_64_on_be(x) (x) +#elif __BYTE_ORDER == __BIG_ENDIAN +#define bswap_16_on_le(x) (x) +#define bswap_32_on_le(x) (x) +#define bswap_64_on_le(x) (x) +#define bswap_16_on_be(x) __bswap_16(x) +#define bswap_32_on_be(x) __bswap_32(x) +#define bswap_64_on_be(x) __bswap_64(x) +#endif + +static inline le16_t htole16(uint16_t value) { return (le16_t __force) bswap_16_on_be(value); } +static inline le32_t htole32(uint32_t value) { return (le32_t __force) bswap_32_on_be(value); } +static inline le64_t htole64(uint64_t value) { return (le64_t __force) bswap_64_on_be(value); } + +static inline be16_t htobe16(uint16_t value) { return (be16_t __force) bswap_16_on_le(value); } +static inline be32_t htobe32(uint32_t value) { return (be32_t __force) bswap_32_on_le(value); } +static inline be64_t htobe64(uint64_t value) { return (be64_t __force) bswap_64_on_le(value); } + +static inline uint16_t le16toh(le16_t value) { return bswap_16_on_be((uint16_t __force)value); } +static inline uint32_t le32toh(le32_t value) { return bswap_32_on_be((uint32_t __force)value); } +static inline uint64_t le64toh(le64_t value) { return bswap_64_on_be((uint64_t __force)value); } + +static inline uint16_t be16toh(be16_t value) { return bswap_16_on_le((uint16_t __force)value); } +static inline uint32_t be32toh(be32_t value) { return bswap_32_on_le((uint32_t __force)value); } +static inline uint64_t be64toh(be64_t value) { return bswap_64_on_le((uint64_t __force)value); } + +#endif /* SPARSE_ENDIAN_H */ diff --git a/src/libbasic/special.h b/src/libbasic/special.h new file mode 100644 index 0000000000..084d3dfa23 --- /dev/null +++ b/src/libbasic/special.h @@ -0,0 +1,119 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#define SPECIAL_DEFAULT_TARGET "default.target" + +/* Shutdown targets */ +#define SPECIAL_UMOUNT_TARGET "umount.target" +/* This is not really intended to be started by directly. This is + * mostly so that other targets (reboot/halt/poweroff) can depend on + * it to bring all services down that want to be brought down on + * system shutdown. */ +#define SPECIAL_SHUTDOWN_TARGET "shutdown.target" +#define SPECIAL_HALT_TARGET "halt.target" +#define SPECIAL_POWEROFF_TARGET "poweroff.target" +#define SPECIAL_REBOOT_TARGET "reboot.target" +#define SPECIAL_KEXEC_TARGET "kexec.target" +#define SPECIAL_EXIT_TARGET "exit.target" +#define SPECIAL_SUSPEND_TARGET "suspend.target" +#define SPECIAL_HIBERNATE_TARGET "hibernate.target" +#define SPECIAL_HYBRID_SLEEP_TARGET "hybrid-sleep.target" + +/* Special boot targets */ +#define SPECIAL_RESCUE_TARGET "rescue.target" +#define SPECIAL_EMERGENCY_TARGET "emergency.target" +#define SPECIAL_MULTI_USER_TARGET "multi-user.target" +#define SPECIAL_GRAPHICAL_TARGET "graphical.target" + +/* Early boot targets */ +#define SPECIAL_SYSINIT_TARGET "sysinit.target" +#define SPECIAL_SOCKETS_TARGET "sockets.target" +#define SPECIAL_BUSNAMES_TARGET "busnames.target" +#define SPECIAL_TIMERS_TARGET "timers.target" +#define SPECIAL_PATHS_TARGET "paths.target" +#define SPECIAL_LOCAL_FS_TARGET "local-fs.target" +#define SPECIAL_LOCAL_FS_PRE_TARGET "local-fs-pre.target" +#define SPECIAL_INITRD_FS_TARGET "initrd-fs.target" +#define SPECIAL_INITRD_ROOT_DEVICE_TARGET "initrd-root-device.target" +#define SPECIAL_INITRD_ROOT_FS_TARGET "initrd-root-fs.target" +#define SPECIAL_REMOTE_FS_TARGET "remote-fs.target" /* LSB's $remote_fs */ +#define SPECIAL_REMOTE_FS_PRE_TARGET "remote-fs-pre.target" +#define SPECIAL_SWAP_TARGET "swap.target" +#define SPECIAL_NETWORK_ONLINE_TARGET "network-online.target" +#define SPECIAL_TIME_SYNC_TARGET "time-sync.target" /* LSB's $time */ +#define SPECIAL_BASIC_TARGET "basic.target" + +/* LSB compatibility */ +#define SPECIAL_NETWORK_TARGET "network.target" /* LSB's $network */ +#define SPECIAL_NSS_LOOKUP_TARGET "nss-lookup.target" /* LSB's $named */ +#define SPECIAL_RPCBIND_TARGET "rpcbind.target" /* LSB's $portmap */ + +/* + * Rules regarding adding further high level targets like the above: + * + * - Be conservative, only add more of these when we really need + * them. We need strong usecases for further additions. + * + * - When there can be multiple implementations running side-by-side, + * it needs to be a .target unit which can pull in all + * implementations. + * + * - If something can be implemented with socket activation, and + * without, it needs to be a .target unit, so that it can pull in + * the appropriate unit. + * + * - Otherwise, it should be a .service unit. + * + * - In some cases it is OK to have both a .service and a .target + * unit, i.e. if there can be multiple parallel implementations, but + * only one is the "system" one. Example: syslog. + * + * Or to put this in other words: .service symlinks can be used to + * arbitrate between multiple implementations if there can be only one + * of a kind. .target units can be used to support multiple + * implementations that can run side-by-side. + */ + +/* Magic early boot services */ +#define SPECIAL_FSCK_SERVICE "systemd-fsck@.service" +#define SPECIAL_QUOTACHECK_SERVICE "systemd-quotacheck.service" +#define SPECIAL_QUOTAON_SERVICE "quotaon.service" +#define SPECIAL_REMOUNT_FS_SERVICE "systemd-remount-fs.service" + +/* Services systemd relies on */ +#define SPECIAL_DBUS_SERVICE "dbus.service" +#define SPECIAL_DBUS_SOCKET "dbus.socket" +#define SPECIAL_JOURNALD_SOCKET "systemd-journald.socket" +#define SPECIAL_JOURNALD_SERVICE "systemd-journald.service" + +/* Magic init signals */ +#define SPECIAL_KBREQUEST_TARGET "kbrequest.target" +#define SPECIAL_SIGPWR_TARGET "sigpwr.target" +#define SPECIAL_CTRL_ALT_DEL_TARGET "ctrl-alt-del.target" + +/* Where we add all our system units, users and machines by default */ +#define SPECIAL_SYSTEM_SLICE "system.slice" +#define SPECIAL_USER_SLICE "user.slice" +#define SPECIAL_MACHINE_SLICE "machine.slice" +#define SPECIAL_ROOT_SLICE "-.slice" + +/* The scope unit systemd itself lives in. */ +#define SPECIAL_INIT_SCOPE "init.scope" diff --git a/src/libbasic/stat-util.c b/src/libbasic/stat-util.c new file mode 100644 index 0000000000..309e84b93d --- /dev/null +++ b/src/libbasic/stat-util.c @@ -0,0 +1,218 @@ +/*** + This file is part of systemd. + + Copyright 2010-2012 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 +#include +#include +#include +#include +#include +#include + +#include "dirent-util.h" +#include "fd-util.h" +#include "macro.h" +#include "missing.h" +#include "stat-util.h" +#include "string-util.h" + +int is_symlink(const char *path) { + struct stat info; + + assert(path); + + if (lstat(path, &info) < 0) + return -errno; + + return !!S_ISLNK(info.st_mode); +} + +int is_dir(const char* path, bool follow) { + struct stat st; + int r; + + assert(path); + + if (follow) + r = stat(path, &st); + else + r = lstat(path, &st); + if (r < 0) + return -errno; + + return !!S_ISDIR(st.st_mode); +} + +int is_device_node(const char *path) { + struct stat info; + + assert(path); + + if (lstat(path, &info) < 0) + return -errno; + + return !!(S_ISBLK(info.st_mode) || S_ISCHR(info.st_mode)); +} + +int dir_is_empty(const char *path) { + _cleanup_closedir_ DIR *d; + struct dirent *de; + + d = opendir(path); + if (!d) + return -errno; + + FOREACH_DIRENT(de, d, return -errno) + return 0; + + return 1; +} + +bool null_or_empty(struct stat *st) { + assert(st); + + if (S_ISREG(st->st_mode) && st->st_size <= 0) + return true; + + /* We don't want to hardcode the major/minor of /dev/null, + * hence we do a simpler "is this a device node?" check. */ + + if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) + return true; + + return false; +} + +int null_or_empty_path(const char *fn) { + struct stat st; + + assert(fn); + + if (stat(fn, &st) < 0) + return -errno; + + return null_or_empty(&st); +} + +int null_or_empty_fd(int fd) { + struct stat st; + + assert(fd >= 0); + + if (fstat(fd, &st) < 0) + return -errno; + + return null_or_empty(&st); +} + +int path_is_read_only_fs(const char *path) { + struct statvfs st; + + assert(path); + + if (statvfs(path, &st) < 0) + return -errno; + + if (st.f_flag & ST_RDONLY) + return true; + + /* On NFS, statvfs() might not reflect whether we can actually + * write to the remote share. Let's try again with + * access(W_OK) which is more reliable, at least sometimes. */ + if (access(path, W_OK) < 0 && errno == EROFS) + return true; + + return false; +} + +int path_is_os_tree(const char *path) { + char *p; + int r; + + assert(path); + + /* We use /usr/lib/os-release as flag file if something is an OS */ + p = strjoina(path, "/usr/lib/os-release"); + r = access(p, F_OK); + if (r >= 0) + return 1; + + /* Also check for the old location in /etc, just in case. */ + p = strjoina(path, "/etc/os-release"); + r = access(p, F_OK); + + return r >= 0; +} + +int files_same(const char *filea, const char *fileb) { + struct stat a, b; + + assert(filea); + assert(fileb); + + if (stat(filea, &a) < 0) + return -errno; + + if (stat(fileb, &b) < 0) + return -errno; + + return a.st_dev == b.st_dev && + a.st_ino == b.st_ino; +} + +bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) { + assert(s); + assert_cc(sizeof(statfs_f_type_t) >= sizeof(s->f_type)); + + return F_TYPE_EQUAL(s->f_type, magic_value); +} + +int fd_check_fstype(int fd, statfs_f_type_t magic_value) { + struct statfs s; + + if (fstatfs(fd, &s) < 0) + return -errno; + + return is_fs_type(&s, magic_value); +} + +int path_check_fstype(const char *path, statfs_f_type_t magic_value) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + + return fd_check_fstype(fd, magic_value); +} + +bool is_temporary_fs(const struct statfs *s) { + return is_fs_type(s, TMPFS_MAGIC) || + is_fs_type(s, RAMFS_MAGIC); +} + +int fd_is_temporary_fs(int fd) { + struct statfs s; + + if (fstatfs(fd, &s) < 0) + return -errno; + + return is_temporary_fs(&s); +} diff --git a/src/libbasic/stat-util.h b/src/libbasic/stat-util.h new file mode 100644 index 0000000000..56d28f791e --- /dev/null +++ b/src/libbasic/stat-util.h @@ -0,0 +1,69 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-2012 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 +#include +#include +#include +#include + +#include "macro.h" + +int is_symlink(const char *path); +int is_dir(const char *path, bool follow); +int is_device_node(const char *path); + +int dir_is_empty(const char *path); + +static inline int dir_is_populated(const char *path) { + int r; + r = dir_is_empty(path); + if (r < 0) + return r; + return !r; +} + +bool null_or_empty(struct stat *st) _pure_; +int null_or_empty_path(const char *fn); +int null_or_empty_fd(int fd); + +int path_is_read_only_fs(const char *path); +int path_is_os_tree(const char *path); + +int files_same(const char *filea, const char *fileb); + +/* The .f_type field of struct statfs is really weird defined on + * different archs. Let's give its type a name. */ +typedef typeof(((struct statfs*)NULL)->f_type) statfs_f_type_t; + +bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) _pure_; +int fd_check_fstype(int fd, statfs_f_type_t magic_value); +int path_check_fstype(const char *path, statfs_f_type_t magic_value); + +bool is_temporary_fs(const struct statfs *s) _pure_; +int fd_is_temporary_fs(int fd); + +/* Because statfs.t_type can be int on some architectures, we have to cast + * the const magic to the type, otherwise the compiler warns about + * signed/unsigned comparison, because the magic can be 32 bit unsigned. + */ +#define F_TYPE_EQUAL(a, b) (a == (typeof(a)) b) diff --git a/src/libbasic/stdio-util.h b/src/libbasic/stdio-util.h new file mode 100644 index 0000000000..bd1144b4c9 --- /dev/null +++ b/src/libbasic/stdio-util.h @@ -0,0 +1,76 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include + +#include "macro.h" + +#define xsprintf(buf, fmt, ...) \ + assert_message_se((size_t) snprintf(buf, ELEMENTSOF(buf), fmt, __VA_ARGS__) < ELEMENTSOF(buf), "xsprintf: " #buf "[] must be big enough") + + +#define VA_FORMAT_ADVANCE(format, ap) \ +do { \ + int _argtypes[128]; \ + size_t _i, _k; \ + _k = parse_printf_format((format), ELEMENTSOF(_argtypes), _argtypes); \ + assert(_k < ELEMENTSOF(_argtypes)); \ + for (_i = 0; _i < _k; _i++) { \ + if (_argtypes[_i] & PA_FLAG_PTR) { \ + (void) va_arg(ap, void*); \ + continue; \ + } \ + \ + switch (_argtypes[_i]) { \ + case PA_INT: \ + case PA_INT|PA_FLAG_SHORT: \ + case PA_CHAR: \ + (void) va_arg(ap, int); \ + break; \ + case PA_INT|PA_FLAG_LONG: \ + (void) va_arg(ap, long int); \ + break; \ + case PA_INT|PA_FLAG_LONG_LONG: \ + (void) va_arg(ap, long long int); \ + break; \ + case PA_WCHAR: \ + (void) va_arg(ap, wchar_t); \ + break; \ + case PA_WSTRING: \ + case PA_STRING: \ + case PA_POINTER: \ + (void) va_arg(ap, void*); \ + break; \ + case PA_FLOAT: \ + case PA_DOUBLE: \ + (void) va_arg(ap, double); \ + break; \ + case PA_DOUBLE|PA_FLAG_LONG_DOUBLE: \ + (void) va_arg(ap, long double); \ + break; \ + default: \ + assert_not_reached("Unknown format string argument."); \ + } \ + } \ +} while (false) diff --git a/src/libbasic/strbuf.c b/src/libbasic/strbuf.c new file mode 100644 index 0000000000..4bef87d3c2 --- /dev/null +++ b/src/libbasic/strbuf.c @@ -0,0 +1,205 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 +#include +#include + +#include "alloc-util.h" +#include "strbuf.h" + +/* + * Strbuf stores given strings in a single continuous allocated memory + * area. Identical strings are de-duplicated and return the same offset + * as the first string stored. If the tail of a string already exists + * in the buffer, the tail is returned. + * + * A trie (http://en.wikipedia.org/wiki/Trie) is used to maintain the + * information about the stored strings. + * + * Example of udev rules: + * $ ./udevadm test . + * ... + * read rules file: /usr/lib/udev/rules.d/99-systemd.rules + * rules contain 196608 bytes tokens (16384 * 12 bytes), 39742 bytes strings + * 23939 strings (207859 bytes), 20404 de-duplicated (171653 bytes), 3536 trie nodes used + * ... + */ + +struct strbuf *strbuf_new(void) { + struct strbuf *str; + + str = new0(struct strbuf, 1); + if (!str) + return NULL; + + str->buf = new0(char, 1); + if (!str->buf) + goto err; + str->len = 1; + + str->root = new0(struct strbuf_node, 1); + if (!str->root) + goto err; + str->nodes_count = 1; + return str; +err: + free(str->buf); + free(str->root); + free(str); + return NULL; +} + +static void strbuf_node_cleanup(struct strbuf_node *node) { + size_t i; + + for (i = 0; i < node->children_count; i++) + strbuf_node_cleanup(node->children[i].child); + free(node->children); + free(node); +} + +/* clean up trie data, leave only the string buffer */ +void strbuf_complete(struct strbuf *str) { + if (!str) + return; + if (str->root) + strbuf_node_cleanup(str->root); + str->root = NULL; +} + +/* clean up everything */ +void strbuf_cleanup(struct strbuf *str) { + if (!str) + return; + if (str->root) + strbuf_node_cleanup(str->root); + free(str->buf); + free(str); +} + +static int strbuf_children_cmp(const struct strbuf_child_entry *n1, + const struct strbuf_child_entry *n2) { + return n1->c - n2->c; +} + +static void bubbleinsert(struct strbuf_node *node, + uint8_t c, + struct strbuf_node *node_child) { + + struct strbuf_child_entry new = { + .c = c, + .child = node_child, + }; + int left = 0, right = node->children_count; + + while (right > left) { + int middle = (right + left) / 2 ; + if (strbuf_children_cmp(&node->children[middle], &new) <= 0) + left = middle + 1; + else + right = middle; + } + + memmove(node->children + left + 1, node->children + left, + sizeof(struct strbuf_child_entry) * (node->children_count - left)); + node->children[left] = new; + + node->children_count++; +} + +/* add string, return the index/offset into the buffer */ +ssize_t strbuf_add_string(struct strbuf *str, const char *s, size_t len) { + uint8_t c; + struct strbuf_node *node; + size_t depth; + char *buf_new; + struct strbuf_child_entry *child; + struct strbuf_node *node_child; + ssize_t off; + + if (!str->root) + return -EINVAL; + + /* search string; start from last character to find possibly matching tails */ + if (len == 0) + return 0; + str->in_count++; + str->in_len += len; + + node = str->root; + c = s[len-1]; + for (depth = 0; depth <= len; depth++) { + struct strbuf_child_entry search; + + /* match against current node */ + off = node->value_off + node->value_len - len; + if (depth == len || (node->value_len >= len && memcmp(str->buf + off, s, len) == 0)) { + str->dedup_len += len; + str->dedup_count++; + return off; + } + + c = s[len - 1 - depth]; + + /* bsearch is not allowed on a NULL sequence */ + if (node->children_count == 0) + break; + + /* lookup child node */ + search.c = c; + child = bsearch(&search, node->children, node->children_count, + sizeof(struct strbuf_child_entry), + (__compar_fn_t) strbuf_children_cmp); + if (!child) + break; + node = child->child; + } + + /* add new string */ + buf_new = realloc(str->buf, str->len + len+1); + if (!buf_new) + return -ENOMEM; + str->buf = buf_new; + off = str->len; + memcpy(str->buf + off, s, len); + str->len += len; + str->buf[str->len++] = '\0'; + + /* new node */ + node_child = new0(struct strbuf_node, 1); + if (!node_child) + return -ENOMEM; + node_child->value_off = off; + node_child->value_len = len; + + /* extend array, add new entry, sort for bisection */ + child = realloc(node->children, (node->children_count + 1) * sizeof(struct strbuf_child_entry)); + if (!child) { + free(node_child); + return -ENOMEM; + } + + str->nodes_count++; + + node->children = child; + bubbleinsert(node, c, node_child); + + return off; +} diff --git a/src/libbasic/strbuf.h b/src/libbasic/strbuf.h new file mode 100644 index 0000000000..a1632da0e8 --- /dev/null +++ b/src/libbasic/strbuf.h @@ -0,0 +1,54 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 +#include +#include + +struct strbuf { + char *buf; + size_t len; + struct strbuf_node *root; + + size_t nodes_count; + size_t in_count; + size_t in_len; + size_t dedup_len; + size_t dedup_count; +}; + +struct strbuf_node { + size_t value_off; + size_t value_len; + + struct strbuf_child_entry *children; + uint8_t children_count; +}; + +struct strbuf_child_entry { + uint8_t c; + struct strbuf_node *child; +}; + +struct strbuf *strbuf_new(void); +ssize_t strbuf_add_string(struct strbuf *str, const char *s, size_t len); +void strbuf_complete(struct strbuf *str); +void strbuf_cleanup(struct strbuf *str); diff --git a/src/libbasic/string-table.c b/src/libbasic/string-table.c new file mode 100644 index 0000000000..a1499ab126 --- /dev/null +++ b/src/libbasic/string-table.c @@ -0,0 +1,34 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 "string-table.h" +#include "string-util.h" + +ssize_t string_table_lookup(const char * const *table, size_t len, const char *key) { + size_t i; + + if (!key) + return -1; + + for (i = 0; i < len; ++i) + if (streq_ptr(table[i], key)) + return (ssize_t) i; + + return -1; +} diff --git a/src/libbasic/string-table.h b/src/libbasic/string-table.h new file mode 100644 index 0000000000..d88625fca7 --- /dev/null +++ b/src/libbasic/string-table.h @@ -0,0 +1,117 @@ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include + +#include "macro.h" +#include "parse-util.h" +#include "string-util.h" + +ssize_t string_table_lookup(const char * const *table, size_t len, const char *key); + +/* For basic lookup tables with strictly enumerated entries */ +#define _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ + scope const char *name##_to_string(type i) { \ + if (i < 0 || i >= (type) ELEMENTSOF(name##_table)) \ + return NULL; \ + return name##_table[i]; \ + } + +#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope) \ + scope type name##_from_string(const char *s) { \ + return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \ + } + +#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \ + scope type name##_from_string(const char *s) { \ + int b; \ + b = parse_boolean(s); \ + if (b == 0) \ + return (type) 0; \ + else if (b > 0) \ + return yes; \ + return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \ + } + +#define _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,scope) \ + scope int name##_to_string_alloc(type i, char **str) { \ + char *s; \ + if (i < 0 || i > max) \ + return -ERANGE; \ + if (i < (type) ELEMENTSOF(name##_table)) { \ + s = strdup(name##_table[i]); \ + if (!s) \ + return -ENOMEM; \ + } else { \ + if (asprintf(&s, "%i", i) < 0) \ + return -ENOMEM; \ + } \ + *str = s; \ + return 0; \ + } + +#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,scope) \ + type name##_from_string(const char *s) { \ + type i; \ + unsigned u = 0; \ + if (!s) \ + return (type) -1; \ + for (i = 0; i < (type) ELEMENTSOF(name##_table); i++) \ + if (streq_ptr(name##_table[i], s)) \ + return i; \ + if (safe_atou(s, &u) >= 0 && u <= max) \ + return (type) u; \ + return (type) -1; \ + } \ + + +#define _DEFINE_STRING_TABLE_LOOKUP(name,type,scope) \ + _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ + _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope) \ + struct __useless_struct_to_allow_trailing_semicolon__ + +#define _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,scope) \ + _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ + _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \ + struct __useless_struct_to_allow_trailing_semicolon__ + +#define DEFINE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,) +#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,static) +#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,static) +#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,static) + +#define DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes) _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,) + +/* For string conversions where numbers are also acceptable */ +#define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max) \ + _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,) \ + _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,) \ + struct __useless_struct_to_allow_trailing_semicolon__ + +#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max) \ + _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,static) +#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max) \ + _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,static) diff --git a/src/libbasic/string-util.c b/src/libbasic/string-util.c new file mode 100644 index 0000000000..293a15f9c0 --- /dev/null +++ b/src/libbasic/string-util.c @@ -0,0 +1,855 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include + +#include "alloc-util.h" +#include "gunicode.h" +#include "macro.h" +#include "string-util.h" +#include "utf8.h" +#include "util.h" + +int strcmp_ptr(const char *a, const char *b) { + + /* Like strcmp(), but tries to make sense of NULL pointers */ + if (a && b) + return strcmp(a, b); + + if (!a && b) + return -1; + + if (a && !b) + return 1; + + return 0; +} + +char* endswith(const char *s, const char *postfix) { + size_t sl, pl; + + assert(s); + assert(postfix); + + sl = strlen(s); + pl = strlen(postfix); + + if (pl == 0) + return (char*) s + sl; + + if (sl < pl) + return NULL; + + if (memcmp(s + sl - pl, postfix, pl) != 0) + return NULL; + + return (char*) s + sl - pl; +} + +char* endswith_no_case(const char *s, const char *postfix) { + size_t sl, pl; + + assert(s); + assert(postfix); + + sl = strlen(s); + pl = strlen(postfix); + + if (pl == 0) + return (char*) s + sl; + + if (sl < pl) + return NULL; + + if (strcasecmp(s + sl - pl, postfix) != 0) + return NULL; + + return (char*) s + sl - pl; +} + +char* first_word(const char *s, const char *word) { + size_t sl, wl; + const char *p; + + assert(s); + assert(word); + + /* Checks if the string starts with the specified word, either + * followed by NUL or by whitespace. Returns a pointer to the + * NUL or the first character after the whitespace. */ + + sl = strlen(s); + wl = strlen(word); + + if (sl < wl) + return NULL; + + if (wl == 0) + return (char*) s; + + if (memcmp(s, word, wl) != 0) + return NULL; + + p = s + wl; + if (*p == 0) + return (char*) p; + + if (!strchr(WHITESPACE, *p)) + return NULL; + + p += strspn(p, WHITESPACE); + return (char*) p; +} + +static size_t strcspn_escaped(const char *s, const char *reject) { + bool escaped = false; + int n; + + for (n=0; s[n]; n++) { + if (escaped) + escaped = false; + else if (s[n] == '\\') + escaped = true; + else if (strchr(reject, s[n])) + break; + } + + /* if s ends in \, return index of previous char */ + return n - escaped; +} + +/* Split a string into words. */ +const char* split(const char **state, size_t *l, const char *separator, bool quoted) { + const char *current; + + current = *state; + + if (!*current) { + assert(**state == '\0'); + return NULL; + } + + current += strspn(current, separator); + if (!*current) { + *state = current; + return NULL; + } + + if (quoted && strchr("\'\"", *current)) { + char quotechars[2] = {*current, '\0'}; + + *l = strcspn_escaped(current + 1, quotechars); + if (current[*l + 1] == '\0' || current[*l + 1] != quotechars[0] || + (current[*l + 2] && !strchr(separator, current[*l + 2]))) { + /* right quote missing or garbage at the end */ + *state = current; + return NULL; + } + *state = current++ + *l + 2; + } else if (quoted) { + *l = strcspn_escaped(current, separator); + if (current[*l] && !strchr(separator, current[*l])) { + /* unfinished escape */ + *state = current; + return NULL; + } + *state = current + *l; + } else { + *l = strcspn(current, separator); + *state = current + *l; + } + + return current; +} + +char *strnappend(const char *s, const char *suffix, size_t b) { + size_t a; + char *r; + + if (!s && !suffix) + return strdup(""); + + if (!s) + return strndup(suffix, b); + + if (!suffix) + return strdup(s); + + assert(s); + assert(suffix); + + a = strlen(s); + if (b > ((size_t) -1) - a) + return NULL; + + r = new(char, a+b+1); + if (!r) + return NULL; + + memcpy(r, s, a); + memcpy(r+a, suffix, b); + r[a+b] = 0; + + return r; +} + +char *strappend(const char *s, const char *suffix) { + return strnappend(s, suffix, suffix ? strlen(suffix) : 0); +} + +char *strjoin(const char *x, ...) { + va_list ap; + size_t l; + char *r, *p; + + va_start(ap, x); + + if (x) { + l = strlen(x); + + for (;;) { + const char *t; + size_t n; + + t = va_arg(ap, const char *); + if (!t) + break; + + n = strlen(t); + if (n > ((size_t) -1) - l) { + va_end(ap); + return NULL; + } + + l += n; + } + } else + l = 0; + + va_end(ap); + + r = new(char, l+1); + if (!r) + return NULL; + + if (x) { + p = stpcpy(r, x); + + va_start(ap, x); + + for (;;) { + const char *t; + + t = va_arg(ap, const char *); + if (!t) + break; + + p = stpcpy(p, t); + } + + va_end(ap); + } else + r[0] = 0; + + return r; +} + +char *strstrip(char *s) { + char *e; + + /* Drops trailing whitespace. Modifies the string in + * place. Returns pointer to first non-space character */ + + s += strspn(s, WHITESPACE); + + for (e = strchr(s, 0); e > s; e --) + if (!strchr(WHITESPACE, e[-1])) + break; + + *e = 0; + + return s; +} + +char *delete_chars(char *s, const char *bad) { + char *f, *t; + + /* Drops all whitespace, regardless where in the string */ + + for (f = s, t = s; *f; f++) { + if (strchr(bad, *f)) + continue; + + *(t++) = *f; + } + + *t = 0; + + return s; +} + +char *truncate_nl(char *s) { + assert(s); + + s[strcspn(s, NEWLINE)] = 0; + return s; +} + +char ascii_tolower(char x) { + + if (x >= 'A' && x <= 'Z') + return x - 'A' + 'a'; + + return x; +} + +char *ascii_strlower(char *t) { + char *p; + + assert(t); + + for (p = t; *p; p++) + *p = ascii_tolower(*p); + + return t; +} + +char *ascii_strlower_n(char *t, size_t n) { + size_t i; + + if (n <= 0) + return t; + + for (i = 0; i < n; i++) + t[i] = ascii_tolower(t[i]); + + return t; +} + +int ascii_strcasecmp_n(const char *a, const char *b, size_t n) { + + for (; n > 0; a++, b++, n--) { + int x, y; + + x = (int) (uint8_t) ascii_tolower(*a); + y = (int) (uint8_t) ascii_tolower(*b); + + if (x != y) + return x - y; + } + + return 0; +} + +int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m) { + int r; + + r = ascii_strcasecmp_n(a, b, MIN(n, m)); + if (r != 0) + return r; + + if (n < m) + return -1; + else if (n > m) + return 1; + else + return 0; +} + +bool chars_intersect(const char *a, const char *b) { + const char *p; + + /* Returns true if any of the chars in a are in b. */ + for (p = a; *p; p++) + if (strchr(b, *p)) + return true; + + return false; +} + +bool string_has_cc(const char *p, const char *ok) { + const char *t; + + assert(p); + + /* + * Check if a string contains control characters. If 'ok' is + * non-NULL it may be a string containing additional CCs to be + * considered OK. + */ + + for (t = p; *t; t++) { + if (ok && strchr(ok, *t)) + continue; + + if (*t > 0 && *t < ' ') + return true; + + if (*t == 127) + return true; + } + + return false; +} + +static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { + size_t x; + char *r; + + assert(s); + assert(percent <= 100); + assert(new_length >= 3); + + if (old_length <= 3 || old_length <= new_length) + return strndup(s, old_length); + + r = new0(char, new_length+1); + if (!r) + return NULL; + + x = (new_length * percent) / 100; + + if (x > new_length - 3) + x = new_length - 3; + + memcpy(r, s, x); + r[x] = '.'; + r[x+1] = '.'; + r[x+2] = '.'; + memcpy(r + x + 3, + s + old_length - (new_length - x - 3), + new_length - x - 3); + + return r; +} + +char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { + size_t x; + char *e; + const char *i, *j; + unsigned k, len, len2; + int r; + + assert(s); + assert(percent <= 100); + assert(new_length >= 3); + + /* if no multibyte characters use ascii_ellipsize_mem for speed */ + if (ascii_is_valid(s)) + return ascii_ellipsize_mem(s, old_length, new_length, percent); + + if (old_length <= 3 || old_length <= new_length) + return strndup(s, old_length); + + x = (new_length * percent) / 100; + + if (x > new_length - 3) + x = new_length - 3; + + k = 0; + for (i = s; k < x && i < s + old_length; i = utf8_next_char(i)) { + char32_t c; + + r = utf8_encoded_to_unichar(i, &c); + if (r < 0) + return NULL; + k += unichar_iswide(c) ? 2 : 1; + } + + if (k > x) /* last character was wide and went over quota */ + x++; + + for (j = s + old_length; k < new_length && j > i; ) { + char32_t c; + + j = utf8_prev_char(j); + r = utf8_encoded_to_unichar(j, &c); + if (r < 0) + return NULL; + k += unichar_iswide(c) ? 2 : 1; + } + assert(i <= j); + + /* we don't actually need to ellipsize */ + if (i == j) + return memdup(s, old_length + 1); + + /* make space for ellipsis */ + j = utf8_next_char(j); + + len = i - s; + len2 = s + old_length - j; + e = new(char, len + 3 + len2 + 1); + if (!e) + return NULL; + + /* + printf("old_length=%zu new_length=%zu x=%zu len=%u len2=%u k=%u\n", + old_length, new_length, x, len, len2, k); + */ + + memcpy(e, s, len); + e[len] = 0xe2; /* tri-dot ellipsis: … */ + e[len + 1] = 0x80; + e[len + 2] = 0xa6; + + memcpy(e + len + 3, j, len2 + 1); + + return e; +} + +char *ellipsize(const char *s, size_t length, unsigned percent) { + return ellipsize_mem(s, strlen(s), length, percent); +} + +bool nulstr_contains(const char*nulstr, const char *needle) { + const char *i; + + if (!nulstr) + return false; + + NULSTR_FOREACH(i, nulstr) + if (streq(i, needle)) + return true; + + return false; +} + +char* strshorten(char *s, size_t l) { + assert(s); + + if (l < strlen(s)) + s[l] = 0; + + return s; +} + +char *strreplace(const char *text, const char *old_string, const char *new_string) { + const char *f; + char *t, *r; + size_t l, old_len, new_len; + + assert(text); + assert(old_string); + assert(new_string); + + old_len = strlen(old_string); + new_len = strlen(new_string); + + l = strlen(text); + r = new(char, l+1); + if (!r) + return NULL; + + f = text; + t = r; + while (*f) { + char *a; + size_t d, nl; + + if (!startswith(f, old_string)) { + *(t++) = *(f++); + continue; + } + + d = t - r; + nl = l - old_len + new_len; + a = realloc(r, nl + 1); + if (!a) + goto oom; + + l = nl; + r = a; + t = r + d; + + t = stpcpy(t, new_string); + f += old_len; + } + + *t = 0; + return r; + +oom: + free(r); + return NULL; +} + +char *strip_tab_ansi(char **ibuf, size_t *_isz) { + const char *i, *begin = NULL; + enum { + STATE_OTHER, + STATE_ESCAPE, + STATE_BRACKET + } state = STATE_OTHER; + char *obuf = NULL; + size_t osz = 0, isz; + FILE *f; + + assert(ibuf); + assert(*ibuf); + + /* Strips ANSI color and replaces TABs by 8 spaces */ + + isz = _isz ? *_isz : strlen(*ibuf); + + f = open_memstream(&obuf, &osz); + if (!f) + return NULL; + + for (i = *ibuf; i < *ibuf + isz + 1; i++) { + + switch (state) { + + case STATE_OTHER: + if (i >= *ibuf + isz) /* EOT */ + break; + else if (*i == '\x1B') + state = STATE_ESCAPE; + else if (*i == '\t') + fputs(" ", f); + else + fputc(*i, f); + break; + + case STATE_ESCAPE: + if (i >= *ibuf + isz) { /* EOT */ + fputc('\x1B', f); + break; + } else if (*i == '[') { + state = STATE_BRACKET; + begin = i + 1; + } else { + fputc('\x1B', f); + fputc(*i, f); + state = STATE_OTHER; + } + + break; + + case STATE_BRACKET: + + if (i >= *ibuf + isz || /* EOT */ + (!(*i >= '0' && *i <= '9') && *i != ';' && *i != 'm')) { + fputc('\x1B', f); + fputc('[', f); + state = STATE_OTHER; + i = begin-1; + } else if (*i == 'm') + state = STATE_OTHER; + break; + } + } + + if (ferror(f)) { + fclose(f); + free(obuf); + return NULL; + } + + fclose(f); + + free(*ibuf); + *ibuf = obuf; + + if (_isz) + *_isz = osz; + + return obuf; +} + +char *strextend(char **x, ...) { + va_list ap; + size_t f, l; + char *r, *p; + + assert(x); + + l = f = *x ? strlen(*x) : 0; + + va_start(ap, x); + for (;;) { + const char *t; + size_t n; + + t = va_arg(ap, const char *); + if (!t) + break; + + n = strlen(t); + if (n > ((size_t) -1) - l) { + va_end(ap); + return NULL; + } + + l += n; + } + va_end(ap); + + r = realloc(*x, l+1); + if (!r) + return NULL; + + p = r + f; + + va_start(ap, x); + for (;;) { + const char *t; + + t = va_arg(ap, const char *); + if (!t) + break; + + p = stpcpy(p, t); + } + va_end(ap); + + *p = 0; + *x = r; + + return r + l; +} + +char *strrep(const char *s, unsigned n) { + size_t l; + char *r, *p; + unsigned i; + + assert(s); + + l = strlen(s); + p = r = malloc(l * n + 1); + if (!r) + return NULL; + + for (i = 0; i < n; i++) + p = stpcpy(p, s); + + *p = 0; + return r; +} + +int split_pair(const char *s, const char *sep, char **l, char **r) { + char *x, *a, *b; + + assert(s); + assert(sep); + assert(l); + assert(r); + + if (isempty(sep)) + return -EINVAL; + + x = strstr(s, sep); + if (!x) + return -EINVAL; + + a = strndup(s, x - s); + if (!a) + return -ENOMEM; + + b = strdup(x + strlen(sep)); + if (!b) { + free(a); + return -ENOMEM; + } + + *l = a; + *r = b; + + return 0; +} + +int free_and_strdup(char **p, const char *s) { + char *t; + + assert(p); + + /* Replaces a string pointer with an strdup()ed new string, + * possibly freeing the old one. */ + + if (streq_ptr(*p, s)) + return 0; + + if (s) { + t = strdup(s); + if (!t) + return -ENOMEM; + } else + t = NULL; + + free(*p); + *p = t; + + return 1; +} + +#pragma GCC push_options +#pragma GCC optimize("O0") + +void* memory_erase(void *p, size_t l) { + volatile uint8_t* x = (volatile uint8_t*) p; + + /* This basically does what memset() does, but hopefully isn't + * optimized away by the compiler. One of those days, when + * glibc learns memset_s() we should replace this call by + * memset_s(), but until then this has to do. */ + + for (; l > 0; l--) + *(x++) = 'x'; + + return p; +} + +#pragma GCC pop_options + +char* string_erase(char *x) { + + if (!x) + return NULL; + + /* A delicious drop of snake-oil! To be called on memory where + * we stored passphrases or so, after we used them. */ + + return memory_erase(x, strlen(x)); +} + +char *string_free_erase(char *s) { + return mfree(string_erase(s)); +} + +bool string_is_safe(const char *p) { + const char *t; + + if (!p) + return false; + + for (t = p; *t; t++) { + if (*t > 0 && *t < ' ') /* no control characters */ + return false; + + if (strchr(QUOTES "\\\x7f", *t)) + return false; + } + + return true; +} diff --git a/src/libbasic/string-util.h b/src/libbasic/string-util.h new file mode 100644 index 0000000000..139cc8c91b --- /dev/null +++ b/src/libbasic/string-util.h @@ -0,0 +1,190 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include + +#include "macro.h" + +/* What is interpreted as whitespace? */ +#define WHITESPACE " \t\n\r" +#define NEWLINE "\n\r" +#define QUOTES "\"\'" +#define COMMENTS "#;" +#define GLOB_CHARS "*?[" +#define DIGITS "0123456789" +#define LOWERCASE_LETTERS "abcdefghijklmnopqrstuvwxyz" +#define UPPERCASE_LETTERS "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +#define LETTERS LOWERCASE_LETTERS UPPERCASE_LETTERS +#define ALPHANUMERICAL LETTERS DIGITS +#define HEXDIGITS DIGITS "abcdefABCDEF" + +#define streq(a,b) (strcmp((a),(b)) == 0) +#define strneq(a, b, n) (strncmp((a), (b), (n)) == 0) +#define strcaseeq(a,b) (strcasecmp((a),(b)) == 0) +#define strncaseeq(a, b, n) (strncasecmp((a), (b), (n)) == 0) + +int strcmp_ptr(const char *a, const char *b) _pure_; + +static inline bool streq_ptr(const char *a, const char *b) { + return strcmp_ptr(a, b) == 0; +} + +static inline const char* strempty(const char *s) { + return s ? s : ""; +} + +static inline const char* strnull(const char *s) { + return s ? s : "(null)"; +} + +static inline const char *strna(const char *s) { + return s ? s : "n/a"; +} + +static inline bool isempty(const char *p) { + return !p || !p[0]; +} + +static inline char *startswith(const char *s, const char *prefix) { + size_t l; + + l = strlen(prefix); + if (strncmp(s, prefix, l) == 0) + return (char*) s + l; + + return NULL; +} + +static inline char *startswith_no_case(const char *s, const char *prefix) { + size_t l; + + l = strlen(prefix); + if (strncasecmp(s, prefix, l) == 0) + return (char*) s + l; + + return NULL; +} + +char *endswith(const char *s, const char *postfix) _pure_; +char *endswith_no_case(const char *s, const char *postfix) _pure_; + +char *first_word(const char *s, const char *word) _pure_; + +const char* split(const char **state, size_t *l, const char *separator, bool quoted); + +#define FOREACH_WORD(word, length, s, state) \ + _FOREACH_WORD(word, length, s, WHITESPACE, false, state) + +#define FOREACH_WORD_SEPARATOR(word, length, s, separator, state) \ + _FOREACH_WORD(word, length, s, separator, false, state) + +#define FOREACH_WORD_QUOTED(word, length, s, state) \ + _FOREACH_WORD(word, length, s, WHITESPACE, true, state) + +#define _FOREACH_WORD(word, length, s, separator, quoted, state) \ + for ((state) = (s), (word) = split(&(state), &(length), (separator), (quoted)); (word); (word) = split(&(state), &(length), (separator), (quoted))) + +char *strappend(const char *s, const char *suffix); +char *strnappend(const char *s, const char *suffix, size_t length); + +char *strjoin(const char *x, ...) _sentinel_; + +#define strjoina(a, ...) \ + ({ \ + const char *_appendees_[] = { a, __VA_ARGS__ }; \ + char *_d_, *_p_; \ + int _len_ = 0; \ + unsigned _i_; \ + for (_i_ = 0; _i_ < ELEMENTSOF(_appendees_) && _appendees_[_i_]; _i_++) \ + _len_ += strlen(_appendees_[_i_]); \ + _p_ = _d_ = alloca(_len_ + 1); \ + for (_i_ = 0; _i_ < ELEMENTSOF(_appendees_) && _appendees_[_i_]; _i_++) \ + _p_ = stpcpy(_p_, _appendees_[_i_]); \ + *_p_ = 0; \ + _d_; \ + }) + +char *strstrip(char *s); +char *delete_chars(char *s, const char *bad); +char *truncate_nl(char *s); + +char ascii_tolower(char x); +char *ascii_strlower(char *s); +char *ascii_strlower_n(char *s, size_t n); + +int ascii_strcasecmp_n(const char *a, const char *b, size_t n); +int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m); + +bool chars_intersect(const char *a, const char *b) _pure_; + +static inline bool _pure_ in_charset(const char *s, const char* charset) { + assert(s); + assert(charset); + return s[strspn(s, charset)] == '\0'; +} + +bool string_has_cc(const char *p, const char *ok) _pure_; + +char *ellipsize_mem(const char *s, size_t old_length_bytes, size_t new_length_columns, unsigned percent); +char *ellipsize(const char *s, size_t length, unsigned percent); + +bool nulstr_contains(const char*nulstr, const char *needle); + +char* strshorten(char *s, size_t l); + +char *strreplace(const char *text, const char *old_string, const char *new_string); + +char *strip_tab_ansi(char **p, size_t *l); + +char *strextend(char **x, ...) _sentinel_; + +char *strrep(const char *s, unsigned n); + +int split_pair(const char *s, const char *sep, char **l, char **r); + +int free_and_strdup(char **p, const char *s); + +/* Normal memmem() requires haystack to be nonnull, which is annoying for zero-length buffers */ +static inline void *memmem_safe(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) { + + if (needlelen <= 0) + return (void*) haystack; + + if (haystacklen < needlelen) + return NULL; + + assert(haystack); + assert(needle); + + return memmem(haystack, haystacklen, needle, needlelen); +} + +void* memory_erase(void *p, size_t l); +char *string_erase(char *x); + +char *string_free_erase(char *s); +DEFINE_TRIVIAL_CLEANUP_FUNC(char *, string_free_erase); +#define _cleanup_string_free_erase_ _cleanup_(string_free_erasep) + +bool string_is_safe(const char *p) _pure_; diff --git a/src/libbasic/strv.c b/src/libbasic/strv.c new file mode 100644 index 0000000000..97a96e5762 --- /dev/null +++ b/src/libbasic/strv.c @@ -0,0 +1,927 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include + +#include "alloc-util.h" +#include "escape.h" +#include "extract-word.h" +#include "fileio.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +char *strv_find(char **l, const char *name) { + char **i; + + assert(name); + + STRV_FOREACH(i, l) + if (streq(*i, name)) + return *i; + + return NULL; +} + +char *strv_find_prefix(char **l, const char *name) { + char **i; + + assert(name); + + STRV_FOREACH(i, l) + if (startswith(*i, name)) + return *i; + + return NULL; +} + +char *strv_find_startswith(char **l, const char *name) { + char **i, *e; + + assert(name); + + /* Like strv_find_prefix, but actually returns only the + * suffix, not the whole item */ + + STRV_FOREACH(i, l) { + e = startswith(*i, name); + if (e) + return e; + } + + return NULL; +} + +void strv_clear(char **l) { + char **k; + + if (!l) + return; + + for (k = l; *k; k++) + free(*k); + + *l = NULL; +} + +char **strv_free(char **l) { + strv_clear(l); + free(l); + return NULL; +} + +char **strv_free_erase(char **l) { + char **i; + + STRV_FOREACH(i, l) + string_erase(*i); + + return strv_free(l); +} + +char **strv_copy(char * const *l) { + char **r, **k; + + k = r = new(char*, strv_length(l) + 1); + if (!r) + return NULL; + + if (l) + for (; *l; k++, l++) { + *k = strdup(*l); + if (!*k) { + strv_free(r); + return NULL; + } + } + + *k = NULL; + return r; +} + +unsigned strv_length(char * const *l) { + unsigned n = 0; + + if (!l) + return 0; + + for (; *l; l++) + n++; + + return n; +} + +char **strv_new_ap(const char *x, va_list ap) { + const char *s; + char **a; + unsigned n = 0, i = 0; + va_list aq; + + /* As a special trick we ignore all listed strings that equal + * (const char*) -1. This is supposed to be used with the + * STRV_IFNOTNULL() macro to include possibly NULL strings in + * the string list. */ + + if (x) { + n = x == (const char*) -1 ? 0 : 1; + + va_copy(aq, ap); + while ((s = va_arg(aq, const char*))) { + if (s == (const char*) -1) + continue; + + n++; + } + + va_end(aq); + } + + a = new(char*, n+1); + if (!a) + return NULL; + + if (x) { + if (x != (const char*) -1) { + a[i] = strdup(x); + if (!a[i]) + goto fail; + i++; + } + + while ((s = va_arg(ap, const char*))) { + + if (s == (const char*) -1) + continue; + + a[i] = strdup(s); + if (!a[i]) + goto fail; + + i++; + } + } + + a[i] = NULL; + + return a; + +fail: + strv_free(a); + return NULL; +} + +char **strv_new(const char *x, ...) { + char **r; + va_list ap; + + va_start(ap, x); + r = strv_new_ap(x, ap); + va_end(ap); + + return r; +} + +int strv_extend_strv(char ***a, char **b, bool filter_duplicates) { + char **s, **t; + size_t p, q, i = 0, j; + + assert(a); + + if (strv_isempty(b)) + return 0; + + p = strv_length(*a); + q = strv_length(b); + + t = realloc(*a, sizeof(char*) * (p + q + 1)); + if (!t) + return -ENOMEM; + + t[p] = NULL; + *a = t; + + STRV_FOREACH(s, b) { + + if (filter_duplicates && strv_contains(t, *s)) + continue; + + t[p+i] = strdup(*s); + if (!t[p+i]) + goto rollback; + + i++; + t[p+i] = NULL; + } + + assert(i <= q); + + return (int) i; + +rollback: + for (j = 0; j < i; j++) + free(t[p + j]); + + t[p] = NULL; + return -ENOMEM; +} + +int strv_extend_strv_concat(char ***a, char **b, const char *suffix) { + int r; + char **s; + + STRV_FOREACH(s, b) { + char *v; + + v = strappend(*s, suffix); + if (!v) + return -ENOMEM; + + r = strv_push(a, v); + if (r < 0) { + free(v); + return r; + } + } + + return 0; +} + +char **strv_split(const char *s, const char *separator) { + const char *word, *state; + size_t l; + unsigned n, i; + char **r; + + assert(s); + + n = 0; + FOREACH_WORD_SEPARATOR(word, l, s, separator, state) + n++; + + r = new(char*, n+1); + if (!r) + return NULL; + + i = 0; + FOREACH_WORD_SEPARATOR(word, l, s, separator, state) { + r[i] = strndup(word, l); + if (!r[i]) { + strv_free(r); + return NULL; + } + + i++; + } + + r[i] = NULL; + return r; +} + +char **strv_split_newlines(const char *s) { + char **l; + unsigned n; + + assert(s); + + /* Special version of strv_split() that splits on newlines and + * suppresses an empty string at the end */ + + l = strv_split(s, NEWLINE); + if (!l) + return NULL; + + n = strv_length(l); + if (n <= 0) + return l; + + if (isempty(l[n - 1])) + l[n - 1] = mfree(l[n - 1]); + + return l; +} + +int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags) { + _cleanup_strv_free_ char **l = NULL; + size_t n = 0, allocated = 0; + int r; + + assert(t); + assert(s); + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&s, &word, separators, flags); + if (r < 0) + return r; + if (r == 0) + break; + + if (!GREEDY_REALLOC(l, allocated, n + 2)) + return -ENOMEM; + + l[n++] = word; + word = NULL; + + l[n] = NULL; + } + + if (!l) { + l = new0(char*, 1); + if (!l) + return -ENOMEM; + } + + *t = l; + l = NULL; + + return (int) n; +} + +char *strv_join(char **l, const char *separator) { + char *r, *e; + char **s; + size_t n, k; + + if (!separator) + separator = " "; + + k = strlen(separator); + + n = 0; + STRV_FOREACH(s, l) { + if (s != l) + n += k; + n += strlen(*s); + } + + r = new(char, n+1); + if (!r) + return NULL; + + e = r; + STRV_FOREACH(s, l) { + if (s != l) + e = stpcpy(e, separator); + + e = stpcpy(e, *s); + } + + *e = 0; + + return r; +} + +char *strv_join_quoted(char **l) { + char *buf = NULL; + char **s; + size_t allocated = 0, len = 0; + + STRV_FOREACH(s, l) { + /* assuming here that escaped string cannot be more + * than twice as long, and reserving space for the + * separator and quotes. + */ + _cleanup_free_ char *esc = NULL; + size_t needed; + + if (!GREEDY_REALLOC(buf, allocated, + len + strlen(*s) * 2 + 3)) + goto oom; + + esc = cescape(*s); + if (!esc) + goto oom; + + needed = snprintf(buf + len, allocated - len, "%s\"%s\"", + len > 0 ? " " : "", esc); + assert(needed < allocated - len); + len += needed; + } + + if (!buf) + buf = malloc0(1); + + return buf; + + oom: + free(buf); + return NULL; +} + +int strv_push(char ***l, char *value) { + char **c; + unsigned n, m; + + if (!value) + return 0; + + n = strv_length(*l); + + /* Increase and check for overflow */ + m = n + 2; + if (m < n) + return -ENOMEM; + + c = realloc_multiply(*l, sizeof(char*), m); + if (!c) + return -ENOMEM; + + c[n] = value; + c[n+1] = NULL; + + *l = c; + return 0; +} + +int strv_push_pair(char ***l, char *a, char *b) { + char **c; + unsigned n, m; + + if (!a && !b) + return 0; + + n = strv_length(*l); + + /* increase and check for overflow */ + m = n + !!a + !!b + 1; + if (m < n) + return -ENOMEM; + + c = realloc_multiply(*l, sizeof(char*), m); + if (!c) + return -ENOMEM; + + if (a) + c[n++] = a; + if (b) + c[n++] = b; + c[n] = NULL; + + *l = c; + return 0; +} + +int strv_push_prepend(char ***l, char *value) { + char **c; + unsigned n, m, i; + + if (!value) + return 0; + + n = strv_length(*l); + + /* increase and check for overflow */ + m = n + 2; + if (m < n) + return -ENOMEM; + + c = new(char*, m); + if (!c) + return -ENOMEM; + + for (i = 0; i < n; i++) + c[i+1] = (*l)[i]; + + c[0] = value; + c[n+1] = NULL; + + free(*l); + *l = c; + + return 0; +} + +int strv_consume(char ***l, char *value) { + int r; + + r = strv_push(l, value); + if (r < 0) + free(value); + + return r; +} + +int strv_consume_pair(char ***l, char *a, char *b) { + int r; + + r = strv_push_pair(l, a, b); + if (r < 0) { + free(a); + free(b); + } + + return r; +} + +int strv_consume_prepend(char ***l, char *value) { + int r; + + r = strv_push_prepend(l, value); + if (r < 0) + free(value); + + return r; +} + +int strv_extend(char ***l, const char *value) { + char *v; + + if (!value) + return 0; + + v = strdup(value); + if (!v) + return -ENOMEM; + + return strv_consume(l, v); +} + +int strv_extend_front(char ***l, const char *value) { + size_t n, m; + char *v, **c; + + assert(l); + + /* Like strv_extend(), but prepends rather than appends the new entry */ + + if (!value) + return 0; + + n = strv_length(*l); + + /* Increase and overflow check. */ + m = n + 2; + if (m < n) + return -ENOMEM; + + v = strdup(value); + if (!v) + return -ENOMEM; + + c = realloc_multiply(*l, sizeof(char*), m); + if (!c) { + free(v); + return -ENOMEM; + } + + memmove(c+1, c, n * sizeof(char*)); + c[0] = v; + c[n+1] = NULL; + + *l = c; + return 0; +} + +char **strv_uniq(char **l) { + char **i; + + /* Drops duplicate entries. The first identical string will be + * kept, the others dropped */ + + STRV_FOREACH(i, l) + strv_remove(i+1, *i); + + return l; +} + +bool strv_is_uniq(char **l) { + char **i; + + STRV_FOREACH(i, l) + if (strv_find(i+1, *i)) + return false; + + return true; +} + +char **strv_remove(char **l, const char *s) { + char **f, **t; + + if (!l) + return NULL; + + assert(s); + + /* Drops every occurrence of s in the string list, edits + * in-place. */ + + for (f = t = l; *f; f++) + if (streq(*f, s)) + free(*f); + else + *(t++) = *f; + + *t = NULL; + return l; +} + +char **strv_parse_nulstr(const char *s, size_t l) { + const char *p; + unsigned c = 0, i = 0; + char **v; + + assert(s || l <= 0); + + if (l <= 0) + return new0(char*, 1); + + for (p = s; p < s + l; p++) + if (*p == 0) + c++; + + if (s[l-1] != 0) + c++; + + v = new0(char*, c+1); + if (!v) + return NULL; + + p = s; + while (p < s + l) { + const char *e; + + e = memchr(p, 0, s + l - p); + + v[i] = strndup(p, e ? e - p : s + l - p); + if (!v[i]) { + strv_free(v); + return NULL; + } + + i++; + + if (!e) + break; + + p = e + 1; + } + + assert(i == c); + + return v; +} + +char **strv_split_nulstr(const char *s) { + const char *i; + char **r = NULL; + + NULSTR_FOREACH(i, s) + if (strv_extend(&r, i) < 0) { + strv_free(r); + return NULL; + } + + if (!r) + return strv_new(NULL, NULL); + + return r; +} + +int strv_make_nulstr(char **l, char **p, size_t *q) { + size_t n_allocated = 0, n = 0; + _cleanup_free_ char *m = NULL; + char **i; + + assert(p); + assert(q); + + STRV_FOREACH(i, l) { + size_t z; + + z = strlen(*i); + + if (!GREEDY_REALLOC(m, n_allocated, n + z + 1)) + return -ENOMEM; + + memcpy(m + n, *i, z + 1); + n += z + 1; + } + + if (!m) { + m = new0(char, 1); + if (!m) + return -ENOMEM; + n = 0; + } + + *p = m; + *q = n; + + m = NULL; + + return 0; +} + +bool strv_overlap(char **a, char **b) { + char **i; + + STRV_FOREACH(i, a) + if (strv_contains(b, *i)) + return true; + + return false; +} + +static int str_compare(const void *_a, const void *_b) { + const char **a = (const char**) _a, **b = (const char**) _b; + + return strcmp(*a, *b); +} + +char **strv_sort(char **l) { + + if (strv_isempty(l)) + return l; + + qsort(l, strv_length(l), sizeof(char*), str_compare); + return l; +} + +bool strv_equal(char **a, char **b) { + + if (strv_isempty(a)) + return strv_isempty(b); + + if (strv_isempty(b)) + return false; + + for ( ; *a || *b; ++a, ++b) + if (!streq_ptr(*a, *b)) + return false; + + return true; +} + +void strv_print(char **l) { + char **s; + + STRV_FOREACH(s, l) + puts(*s); +} + +int strv_extendf(char ***l, const char *format, ...) { + va_list ap; + char *x; + int r; + + va_start(ap, format); + r = vasprintf(&x, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return strv_consume(l, x); +} + +char **strv_reverse(char **l) { + unsigned n, i; + + n = strv_length(l); + if (n <= 1) + return l; + + for (i = 0; i < n / 2; i++) { + char *t; + + t = l[i]; + l[i] = l[n-1-i]; + l[n-1-i] = t; + } + + return l; +} + +char **strv_shell_escape(char **l, const char *bad) { + char **s; + + /* Escapes every character in every string in l that is in bad, + * edits in-place, does not roll-back on error. */ + + STRV_FOREACH(s, l) { + char *v; + + v = shell_escape(*s, bad); + if (!v) + return NULL; + + free(*s); + *s = v; + } + + return l; +} + +bool strv_fnmatch(char* const* patterns, const char *s, int flags) { + char* const* p; + + STRV_FOREACH(p, patterns) + if (fnmatch(*p, s, 0) == 0) + return true; + + return false; +} + +char ***strv_free_free(char ***l) { + char ***i; + + if (!l) + return NULL; + + for (i = l; *i; i++) + strv_free(*i); + + free(l); + return NULL; +} + +char **strv_skip(char **l, size_t n) { + + while (n > 0) { + if (strv_isempty(l)) + return l; + + l++, n--; + } + + return l; +} + +int strv_extend_n(char ***l, const char *value, size_t n) { + size_t i, j, k; + char **nl; + + assert(l); + + if (!value) + return 0; + if (n == 0) + return 0; + + /* Adds the value value n times to l */ + + k = strv_length(*l); + + nl = realloc(*l, sizeof(char*) * (k + n + 1)); + if (!nl) + return -ENOMEM; + + *l = nl; + + for (i = k; i < k + n; i++) { + nl[i] = strdup(value); + if (!nl[i]) + goto rollback; + } + + nl[i] = NULL; + return 0; + +rollback: + for (j = k; j < i; j++) + free(nl[j]); + + nl[k] = NULL; + return -ENOMEM; +} + +int fputstrv(FILE *f, char **l, const char *separator, bool *space) { + bool b = false; + char **s; + int r; + + /* Like fputs(), but for strv, and with a less stupid argument order */ + + if (!space) + space = &b; + + STRV_FOREACH(s, l) { + r = fputs_with_space(f, *s, separator, space); + if (r < 0) + return r; + } + + return 0; +} diff --git a/src/libbasic/strv.h b/src/libbasic/strv.h new file mode 100644 index 0000000000..f61bbb5386 --- /dev/null +++ b/src/libbasic/strv.h @@ -0,0 +1,172 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include + +#include "alloc-util.h" +#include "extract-word.h" +#include "macro.h" +#include "util.h" + +char *strv_find(char **l, const char *name) _pure_; +char *strv_find_prefix(char **l, const char *name) _pure_; +char *strv_find_startswith(char **l, const char *name) _pure_; + +char **strv_free(char **l); +DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free); +#define _cleanup_strv_free_ _cleanup_(strv_freep) + +char **strv_free_erase(char **l); +DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free_erase); +#define _cleanup_strv_free_erase_ _cleanup_(strv_free_erasep) + +void strv_clear(char **l); + +char **strv_copy(char * const *l); +unsigned strv_length(char * const *l) _pure_; + +int strv_extend_strv(char ***a, char **b, bool filter_duplicates); +int strv_extend_strv_concat(char ***a, char **b, const char *suffix); +int strv_extend(char ***l, const char *value); +int strv_extendf(char ***l, const char *format, ...) _printf_(2,0); +int strv_extend_front(char ***l, const char *value); +int strv_push(char ***l, char *value); +int strv_push_pair(char ***l, char *a, char *b); +int strv_push_prepend(char ***l, char *value); +int strv_consume(char ***l, char *value); +int strv_consume_pair(char ***l, char *a, char *b); +int strv_consume_prepend(char ***l, char *value); + +char **strv_remove(char **l, const char *s); +char **strv_uniq(char **l); +bool strv_is_uniq(char **l); + +bool strv_equal(char **a, char **b); + +#define strv_contains(l, s) (!!strv_find((l), (s))) + +char **strv_new(const char *x, ...) _sentinel_; +char **strv_new_ap(const char *x, va_list ap); + +static inline const char* STRV_IFNOTNULL(const char *x) { + return x ? x : (const char *) -1; +} + +static inline bool strv_isempty(char * const *l) { + return !l || !*l; +} + +char **strv_split(const char *s, const char *separator); +char **strv_split_newlines(const char *s); + +int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags); + +char *strv_join(char **l, const char *separator); +char *strv_join_quoted(char **l); + +char **strv_parse_nulstr(const char *s, size_t l); +char **strv_split_nulstr(const char *s); +int strv_make_nulstr(char **l, char **p, size_t *n); + +bool strv_overlap(char **a, char **b) _pure_; + +#define STRV_FOREACH(s, l) \ + for ((s) = (l); (s) && *(s); (s)++) + +#define STRV_FOREACH_BACKWARDS(s, l) \ + STRV_FOREACH(s, l) \ + ; \ + for ((s)--; (l) && ((s) >= (l)); (s)--) + +#define STRV_FOREACH_PAIR(x, y, l) \ + for ((x) = (l), (y) = (x+1); (x) && *(x) && *(y); (x) += 2, (y) = (x + 1)) + +char **strv_sort(char **l); +void strv_print(char **l); + +#define STRV_MAKE(...) ((char**) ((const char*[]) { __VA_ARGS__, NULL })) + +#define STRV_MAKE_EMPTY ((char*[1]) { NULL }) + +#define strv_from_stdarg_alloca(first) \ + ({ \ + char **_l; \ + \ + if (!first) \ + _l = (char**) &first; \ + else { \ + unsigned _n; \ + va_list _ap; \ + \ + _n = 1; \ + va_start(_ap, first); \ + while (va_arg(_ap, char*)) \ + _n++; \ + va_end(_ap); \ + \ + _l = newa(char*, _n+1); \ + _l[_n = 0] = (char*) first; \ + va_start(_ap, first); \ + for (;;) { \ + _l[++_n] = va_arg(_ap, char*); \ + if (!_l[_n]) \ + break; \ + } \ + va_end(_ap); \ + } \ + _l; \ + }) + +#define STR_IN_SET(x, ...) strv_contains(STRV_MAKE(__VA_ARGS__), x) + +#define FOREACH_STRING(x, ...) \ + for (char **_l = ({ \ + char **_ll = STRV_MAKE(__VA_ARGS__); \ + x = _ll ? _ll[0] : NULL; \ + _ll; \ + }); \ + _l && *_l; \ + x = ({ \ + _l ++; \ + _l[0]; \ + })) + +char **strv_reverse(char **l); +char **strv_shell_escape(char **l, const char *bad); + +bool strv_fnmatch(char* const* patterns, const char *s, int flags); + +static inline bool strv_fnmatch_or_empty(char* const* patterns, const char *s, int flags) { + assert(s); + return strv_isempty(patterns) || + strv_fnmatch(patterns, s, flags); +} + +char ***strv_free_free(char ***l); + +char **strv_skip(char **l, size_t n); + +int strv_extend_n(char ***l, const char *value, size_t n); + +int fputstrv(FILE *f, char **l, const char *separator, bool *space); diff --git a/src/libbasic/strxcpyx.c b/src/libbasic/strxcpyx.c new file mode 100644 index 0000000000..aaf11d21f6 --- /dev/null +++ b/src/libbasic/strxcpyx.c @@ -0,0 +1,100 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +/* + * Concatenates/copies strings. In any case, terminates in all cases + * with '\0' * and moves the @dest pointer forward to the added '\0'. + * Returns the * remaining size, and 0 if the string was truncated. + */ + +#include +#include +#include + +#include "strxcpyx.h" + +size_t strpcpy(char **dest, size_t size, const char *src) { + size_t len; + + len = strlen(src); + if (len >= size) { + if (size > 1) + *dest = mempcpy(*dest, src, size-1); + size = 0; + } else { + if (len > 0) { + *dest = mempcpy(*dest, src, len); + size -= len; + } + } + *dest[0] = '\0'; + return size; +} + +size_t strpcpyf(char **dest, size_t size, const char *src, ...) { + va_list va; + int i; + + va_start(va, src); + i = vsnprintf(*dest, size, src, va); + if (i < (int)size) { + *dest += i; + size -= i; + } else { + *dest += size; + size = 0; + } + va_end(va); + *dest[0] = '\0'; + return size; +} + +size_t strpcpyl(char **dest, size_t size, const char *src, ...) { + va_list va; + + va_start(va, src); + do { + size = strpcpy(dest, size, src); + src = va_arg(va, char *); + } while (src != NULL); + va_end(va); + return size; +} + +size_t strscpy(char *dest, size_t size, const char *src) { + char *s; + + s = dest; + return strpcpy(&s, size, src); +} + +size_t strscpyl(char *dest, size_t size, const char *src, ...) { + va_list va; + char *s; + + va_start(va, src); + s = dest; + do { + size = strpcpy(&s, size, src); + src = va_arg(va, char *); + } while (src != NULL); + va_end(va); + + return size; +} diff --git a/src/libbasic/strxcpyx.h b/src/libbasic/strxcpyx.h new file mode 100644 index 0000000000..80ff58726b --- /dev/null +++ b/src/libbasic/strxcpyx.h @@ -0,0 +1,31 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include "macro.h" + +size_t strpcpy(char **dest, size_t size, const char *src); +size_t strpcpyf(char **dest, size_t size, const char *src, ...) _printf_(3, 4); +size_t strpcpyl(char **dest, size_t size, const char *src, ...) _sentinel_; +size_t strscpy(char *dest, size_t size, const char *src); +size_t strscpyl(char *dest, size_t size, const char *src, ...) _sentinel_; diff --git a/src/libbasic/syslog-util.c b/src/libbasic/syslog-util.c new file mode 100644 index 0000000000..db3405154e --- /dev/null +++ b/src/libbasic/syslog-util.c @@ -0,0 +1,114 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 + +#include "hexdecoct.h" +#include "macro.h" +#include "string-table.h" +#include "syslog-util.h" + +int syslog_parse_priority(const char **p, int *priority, bool with_facility) { + int a = 0, b = 0, c = 0; + int k; + + assert(p); + assert(*p); + assert(priority); + + if ((*p)[0] != '<') + return 0; + + if (!strchr(*p, '>')) + return 0; + + if ((*p)[2] == '>') { + c = undecchar((*p)[1]); + k = 3; + } else if ((*p)[3] == '>') { + b = undecchar((*p)[1]); + c = undecchar((*p)[2]); + k = 4; + } else if ((*p)[4] == '>') { + a = undecchar((*p)[1]); + b = undecchar((*p)[2]); + c = undecchar((*p)[3]); + k = 5; + } else + return 0; + + if (a < 0 || b < 0 || c < 0 || + (!with_facility && (a || b || c > 7))) + return 0; + + if (with_facility) + *priority = a*100 + b*10 + c; + else + *priority = (*priority & LOG_FACMASK) | c; + + *p += k; + return 1; +} + +static const char *const log_facility_unshifted_table[LOG_NFACILITIES] = { + [LOG_FAC(LOG_KERN)] = "kern", + [LOG_FAC(LOG_USER)] = "user", + [LOG_FAC(LOG_MAIL)] = "mail", + [LOG_FAC(LOG_DAEMON)] = "daemon", + [LOG_FAC(LOG_AUTH)] = "auth", + [LOG_FAC(LOG_SYSLOG)] = "syslog", + [LOG_FAC(LOG_LPR)] = "lpr", + [LOG_FAC(LOG_NEWS)] = "news", + [LOG_FAC(LOG_UUCP)] = "uucp", + [LOG_FAC(LOG_CRON)] = "cron", + [LOG_FAC(LOG_AUTHPRIV)] = "authpriv", + [LOG_FAC(LOG_FTP)] = "ftp", + [LOG_FAC(LOG_LOCAL0)] = "local0", + [LOG_FAC(LOG_LOCAL1)] = "local1", + [LOG_FAC(LOG_LOCAL2)] = "local2", + [LOG_FAC(LOG_LOCAL3)] = "local3", + [LOG_FAC(LOG_LOCAL4)] = "local4", + [LOG_FAC(LOG_LOCAL5)] = "local5", + [LOG_FAC(LOG_LOCAL6)] = "local6", + [LOG_FAC(LOG_LOCAL7)] = "local7" +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(log_facility_unshifted, int, LOG_FAC(~0)); + +bool log_facility_unshifted_is_valid(int facility) { + return facility >= 0 && facility <= LOG_FAC(~0); +} + +static const char *const log_level_table[] = { + [LOG_EMERG] = "emerg", + [LOG_ALERT] = "alert", + [LOG_CRIT] = "crit", + [LOG_ERR] = "err", + [LOG_WARNING] = "warning", + [LOG_NOTICE] = "notice", + [LOG_INFO] = "info", + [LOG_DEBUG] = "debug" +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(log_level, int, LOG_DEBUG); + +bool log_level_is_valid(int level) { + return level >= 0 && level <= LOG_DEBUG; +} diff --git a/src/libbasic/syslog-util.h b/src/libbasic/syslog-util.h new file mode 100644 index 0000000000..5cb606a1bf --- /dev/null +++ b/src/libbasic/syslog-util.h @@ -0,0 +1,32 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 + +int log_facility_unshifted_to_string_alloc(int i, char **s); +int log_facility_unshifted_from_string(const char *s); +bool log_facility_unshifted_is_valid(int faciliy); + +int log_level_to_string_alloc(int i, char **s); +int log_level_from_string(const char *s); +bool log_level_is_valid(int level); + +int syslog_parse_priority(const char **p, int *priority, bool with_facility); diff --git a/src/libbasic/terminal-util.c b/src/libbasic/terminal-util.c new file mode 100644 index 0000000000..9521b79daa --- /dev/null +++ b/src/libbasic/terminal-util.c @@ -0,0 +1,1153 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "io-util.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "process-util.h" +#include "socket-util.h" +#include "stat-util.h" +#include "string-util.h" +#include "terminal-util.h" +#include "time-util.h" +#include "util.h" + +static volatile unsigned cached_columns = 0; +static volatile unsigned cached_lines = 0; + +int chvt(int vt) { + _cleanup_close_ int fd; + + fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); + if (fd < 0) + return -errno; + + if (vt <= 0) { + int tiocl[2] = { + TIOCL_GETKMSGREDIRECT, + 0 + }; + + if (ioctl(fd, TIOCLINUX, tiocl) < 0) + return -errno; + + vt = tiocl[0] <= 0 ? 1 : tiocl[0]; + } + + if (ioctl(fd, VT_ACTIVATE, vt) < 0) + return -errno; + + return 0; +} + +int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) { + struct termios old_termios, new_termios; + char c, line[LINE_MAX]; + + assert(f); + assert(ret); + + if (tcgetattr(fileno(f), &old_termios) >= 0) { + new_termios = old_termios; + + new_termios.c_lflag &= ~ICANON; + new_termios.c_cc[VMIN] = 1; + new_termios.c_cc[VTIME] = 0; + + if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) { + size_t k; + + if (t != USEC_INFINITY) { + if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) { + tcsetattr(fileno(f), TCSADRAIN, &old_termios); + return -ETIMEDOUT; + } + } + + k = fread(&c, 1, 1, f); + + tcsetattr(fileno(f), TCSADRAIN, &old_termios); + + if (k <= 0) + return -EIO; + + if (need_nl) + *need_nl = c != '\n'; + + *ret = c; + return 0; + } + } + + if (t != USEC_INFINITY) { + if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) + return -ETIMEDOUT; + } + + errno = 0; + if (!fgets(line, sizeof(line), f)) + return errno > 0 ? -errno : -EIO; + + truncate_nl(line); + + if (strlen(line) != 1) + return -EBADMSG; + + if (need_nl) + *need_nl = false; + + *ret = line[0]; + return 0; +} + +int ask_char(char *ret, const char *replies, const char *text, ...) { + int r; + + assert(ret); + assert(replies); + assert(text); + + for (;;) { + va_list ap; + char c; + bool need_nl = true; + + if (on_tty()) + fputs(ANSI_HIGHLIGHT, stdout); + + va_start(ap, text); + vprintf(text, ap); + va_end(ap); + + if (on_tty()) + fputs(ANSI_NORMAL, stdout); + + fflush(stdout); + + r = read_one_char(stdin, &c, USEC_INFINITY, &need_nl); + if (r < 0) { + + if (r == -EBADMSG) { + puts("Bad input, please try again."); + continue; + } + + putchar('\n'); + return r; + } + + if (need_nl) + putchar('\n'); + + if (strchr(replies, c)) { + *ret = c; + return 0; + } + + puts("Read unexpected character, please try again."); + } +} + +int ask_string(char **ret, const char *text, ...) { + assert(ret); + assert(text); + + for (;;) { + char line[LINE_MAX]; + va_list ap; + + if (on_tty()) + fputs(ANSI_HIGHLIGHT, stdout); + + va_start(ap, text); + vprintf(text, ap); + va_end(ap); + + if (on_tty()) + fputs(ANSI_NORMAL, stdout); + + fflush(stdout); + + errno = 0; + if (!fgets(line, sizeof(line), stdin)) + return errno > 0 ? -errno : -EIO; + + if (!endswith(line, "\n")) + putchar('\n'); + else { + char *s; + + if (isempty(line)) + continue; + + truncate_nl(line); + s = strdup(line); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; + } + } +} + +int reset_terminal_fd(int fd, bool switch_to_text) { + struct termios termios; + int r = 0; + + /* Set terminal to some sane defaults */ + + assert(fd >= 0); + + /* We leave locked terminal attributes untouched, so that + * Plymouth may set whatever it wants to set, and we don't + * interfere with that. */ + + /* Disable exclusive mode, just in case */ + (void) ioctl(fd, TIOCNXCL); + + /* Switch to text mode */ + if (switch_to_text) + (void) ioctl(fd, KDSETMODE, KD_TEXT); + + /* Enable console unicode mode */ + (void) ioctl(fd, KDSKBMODE, K_UNICODE); + + if (tcgetattr(fd, &termios) < 0) { + r = -errno; + goto finish; + } + + /* We only reset the stuff that matters to the software. How + * hardware is set up we don't touch assuming that somebody + * else will do that for us */ + + termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC); + termios.c_iflag |= ICRNL | IMAXBEL | IUTF8; + termios.c_oflag |= ONLCR; + termios.c_cflag |= CREAD; + termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOPRT | ECHOKE; + + termios.c_cc[VINTR] = 03; /* ^C */ + termios.c_cc[VQUIT] = 034; /* ^\ */ + termios.c_cc[VERASE] = 0177; + termios.c_cc[VKILL] = 025; /* ^X */ + termios.c_cc[VEOF] = 04; /* ^D */ + termios.c_cc[VSTART] = 021; /* ^Q */ + termios.c_cc[VSTOP] = 023; /* ^S */ + termios.c_cc[VSUSP] = 032; /* ^Z */ + termios.c_cc[VLNEXT] = 026; /* ^V */ + termios.c_cc[VWERASE] = 027; /* ^W */ + termios.c_cc[VREPRINT] = 022; /* ^R */ + termios.c_cc[VEOL] = 0; + termios.c_cc[VEOL2] = 0; + + termios.c_cc[VTIME] = 0; + termios.c_cc[VMIN] = 1; + + if (tcsetattr(fd, TCSANOW, &termios) < 0) + r = -errno; + +finish: + /* Just in case, flush all crap out */ + (void) tcflush(fd, TCIOFLUSH); + + return r; +} + +int reset_terminal(const char *name) { + _cleanup_close_ int fd = -1; + + /* We open the terminal with O_NONBLOCK here, to ensure we + * don't block on carrier if this is a terminal with carrier + * configured. */ + + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); + if (fd < 0) + return fd; + + return reset_terminal_fd(fd, true); +} + +int open_terminal(const char *name, int mode) { + int fd, r; + unsigned c = 0; + + /* + * If a TTY is in the process of being closed opening it might + * cause EIO. This is horribly awful, but unlikely to be + * changed in the kernel. Hence we work around this problem by + * retrying a couple of times. + * + * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245 + */ + + if (mode & O_CREAT) + return -EINVAL; + + for (;;) { + fd = open(name, mode, 0); + if (fd >= 0) + break; + + if (errno != EIO) + return -errno; + + /* Max 1s in total */ + if (c >= 20) + return -errno; + + usleep(50 * USEC_PER_MSEC); + c++; + } + + r = isatty(fd); + if (r < 0) { + safe_close(fd); + return -errno; + } + + if (!r) { + safe_close(fd); + return -ENOTTY; + } + + return fd; +} + +int acquire_terminal( + const char *name, + bool fail, + bool force, + bool ignore_tiocstty_eperm, + usec_t timeout) { + + int fd = -1, notify = -1, r = 0, wd = -1; + usec_t ts = 0; + + assert(name); + + /* We use inotify to be notified when the tty is closed. We + * create the watch before checking if we can actually acquire + * it, so that we don't lose any event. + * + * Note: strictly speaking this actually watches for the + * device being closed, it does *not* really watch whether a + * tty loses its controlling process. However, unless some + * rogue process uses TIOCNOTTY on /dev/tty *after* closing + * its tty otherwise this will not become a problem. As long + * as the administrator makes sure not configure any service + * on the same tty as an untrusted user this should not be a + * problem. (Which he probably should not do anyway.) */ + + if (timeout != USEC_INFINITY) + ts = now(CLOCK_MONOTONIC); + + if (!fail && !force) { + notify = inotify_init1(IN_CLOEXEC | (timeout != USEC_INFINITY ? IN_NONBLOCK : 0)); + if (notify < 0) { + r = -errno; + goto fail; + } + + wd = inotify_add_watch(notify, name, IN_CLOSE); + if (wd < 0) { + r = -errno; + goto fail; + } + } + + for (;;) { + struct sigaction sa_old, sa_new = { + .sa_handler = SIG_IGN, + .sa_flags = SA_RESTART, + }; + + if (notify >= 0) { + r = flush_fd(notify); + if (r < 0) + goto fail; + } + + /* We pass here O_NOCTTY only so that we can check the return + * value TIOCSCTTY and have a reliable way to figure out if we + * successfully became the controlling process of the tty */ + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed + * if we already own the tty. */ + assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0); + + /* First, try to get the tty */ + if (ioctl(fd, TIOCSCTTY, force) < 0) + r = -errno; + + assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0); + + /* Sometimes, it makes sense to ignore TIOCSCTTY + * returning EPERM, i.e. when very likely we already + * are have this controlling terminal. */ + if (r < 0 && r == -EPERM && ignore_tiocstty_eperm) + r = 0; + + if (r < 0 && (force || fail || r != -EPERM)) + goto fail; + + if (r >= 0) + break; + + assert(!fail); + assert(!force); + assert(notify >= 0); + + for (;;) { + union inotify_event_buffer buffer; + struct inotify_event *e; + ssize_t l; + + if (timeout != USEC_INFINITY) { + usec_t n; + + n = now(CLOCK_MONOTONIC); + if (ts + timeout < n) { + r = -ETIMEDOUT; + goto fail; + } + + r = fd_wait_for_event(fd, POLLIN, ts + timeout - n); + if (r < 0) + goto fail; + + if (r == 0) { + r = -ETIMEDOUT; + goto fail; + } + } + + l = read(notify, &buffer, sizeof(buffer)); + if (l < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + + r = -errno; + goto fail; + } + + FOREACH_INOTIFY_EVENT(e, buffer, l) { + if (e->wd != wd || !(e->mask & IN_CLOSE)) { + r = -EIO; + goto fail; + } + } + + break; + } + + /* We close the tty fd here since if the old session + * ended our handle will be dead. It's important that + * we do this after sleeping, so that we don't enter + * an endless loop. */ + fd = safe_close(fd); + } + + safe_close(notify); + + return fd; + +fail: + safe_close(fd); + safe_close(notify); + + return r; +} + +int release_terminal(void) { + static const struct sigaction sa_new = { + .sa_handler = SIG_IGN, + .sa_flags = SA_RESTART, + }; + + _cleanup_close_ int fd = -1; + struct sigaction sa_old; + int r = 0; + + fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); + if (fd < 0) + return -errno; + + /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed + * by our own TIOCNOTTY */ + assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0); + + if (ioctl(fd, TIOCNOTTY) < 0) + r = -errno; + + assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0); + + return r; +} + +int terminal_vhangup_fd(int fd) { + assert(fd >= 0); + + if (ioctl(fd, TIOCVHANGUP) < 0) + return -errno; + + return 0; +} + +int terminal_vhangup(const char *name) { + _cleanup_close_ int fd; + + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); + if (fd < 0) + return fd; + + return terminal_vhangup_fd(fd); +} + +int vt_disallocate(const char *name) { + _cleanup_close_ int fd = -1; + unsigned u; + int r; + + /* Deallocate the VT if possible. If not possible + * (i.e. because it is the active one), at least clear it + * entirely (including the scrollback buffer) */ + + if (!startswith(name, "/dev/")) + return -EINVAL; + + if (!tty_is_vc(name)) { + /* So this is not a VT. I guess we cannot deallocate + * it then. But let's at least clear the screen */ + + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + loop_write(fd, + "\033[r" /* clear scrolling region */ + "\033[H" /* move home */ + "\033[2J", /* clear screen */ + 10, false); + return 0; + } + + if (!startswith(name, "/dev/tty")) + return -EINVAL; + + r = safe_atou(name+8, &u); + if (r < 0) + return r; + + if (u <= 0) + return -EINVAL; + + /* Try to deallocate */ + fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); + if (fd < 0) + return fd; + + r = ioctl(fd, VT_DISALLOCATE, u); + fd = safe_close(fd); + + if (r >= 0) + return 0; + + if (errno != EBUSY) + return -errno; + + /* Couldn't deallocate, so let's clear it fully with + * scrollback */ + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + loop_write(fd, + "\033[r" /* clear scrolling region */ + "\033[H" /* move home */ + "\033[3J", /* clear screen including scrollback, requires Linux 2.6.40 */ + 10, false); + return 0; +} + +int make_console_stdio(void) { + int fd, r; + + /* Make /dev/console the controlling terminal and stdin/stdout/stderr */ + + fd = acquire_terminal("/dev/console", false, true, true, USEC_INFINITY); + if (fd < 0) + return log_error_errno(fd, "Failed to acquire terminal: %m"); + + r = reset_terminal_fd(fd, true); + if (r < 0) + log_warning_errno(r, "Failed to reset terminal, ignoring: %m"); + + r = make_stdio(fd); + if (r < 0) + return log_error_errno(r, "Failed to duplicate terminal fd: %m"); + + return 0; +} + +bool tty_is_vc(const char *tty) { + assert(tty); + + return vtnr_from_tty(tty) >= 0; +} + +bool tty_is_console(const char *tty) { + assert(tty); + + if (startswith(tty, "/dev/")) + tty += 5; + + return streq(tty, "console"); +} + +int vtnr_from_tty(const char *tty) { + int i, r; + + assert(tty); + + if (startswith(tty, "/dev/")) + tty += 5; + + if (!startswith(tty, "tty") ) + return -EINVAL; + + if (tty[3] < '0' || tty[3] > '9') + return -EINVAL; + + r = safe_atoi(tty+3, &i); + if (r < 0) + return r; + + if (i < 0 || i > 63) + return -EINVAL; + + return i; +} + +char *resolve_dev_console(char **active) { + char *tty; + + /* Resolve where /dev/console is pointing to, if /sys is actually ours + * (i.e. not read-only-mounted which is a sign for container setups) */ + + if (path_is_read_only_fs("/sys") > 0) + return NULL; + + if (read_one_line_file("/sys/class/tty/console/active", active) < 0) + return NULL; + + /* If multiple log outputs are configured the last one is what + * /dev/console points to */ + tty = strrchr(*active, ' '); + if (tty) + tty++; + else + tty = *active; + + if (streq(tty, "tty0")) { + char *tmp; + + /* Get the active VC (e.g. tty1) */ + if (read_one_line_file("/sys/class/tty/tty0/active", &tmp) >= 0) { + free(*active); + tty = *active = tmp; + } + } + + return tty; +} + +bool tty_is_vc_resolve(const char *tty) { + _cleanup_free_ char *active = NULL; + + assert(tty); + + if (startswith(tty, "/dev/")) + tty += 5; + + if (streq(tty, "console")) { + tty = resolve_dev_console(&active); + if (!tty) + return false; + } + + return tty_is_vc(tty); +} + +const char *default_term_for_tty(const char *tty) { + return tty && tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt220"; +} + +int fd_columns(int fd) { + struct winsize ws = {}; + + if (ioctl(fd, TIOCGWINSZ, &ws) < 0) + return -errno; + + if (ws.ws_col <= 0) + return -EIO; + + return ws.ws_col; +} + +unsigned columns(void) { + const char *e; + int c; + + if (_likely_(cached_columns > 0)) + return cached_columns; + + c = 0; + e = getenv("COLUMNS"); + if (e) + (void) safe_atoi(e, &c); + + if (c <= 0) + c = fd_columns(STDOUT_FILENO); + + if (c <= 0) + c = 80; + + cached_columns = c; + return cached_columns; +} + +int fd_lines(int fd) { + struct winsize ws = {}; + + if (ioctl(fd, TIOCGWINSZ, &ws) < 0) + return -errno; + + if (ws.ws_row <= 0) + return -EIO; + + return ws.ws_row; +} + +unsigned lines(void) { + const char *e; + int l; + + if (_likely_(cached_lines > 0)) + return cached_lines; + + l = 0; + e = getenv("LINES"); + if (e) + (void) safe_atoi(e, &l); + + if (l <= 0) + l = fd_lines(STDOUT_FILENO); + + if (l <= 0) + l = 24; + + cached_lines = l; + return cached_lines; +} + +/* intended to be used as a SIGWINCH sighandler */ +void columns_lines_cache_reset(int signum) { + cached_columns = 0; + cached_lines = 0; +} + +bool on_tty(void) { + static int cached_on_tty = -1; + + if (_unlikely_(cached_on_tty < 0)) + cached_on_tty = isatty(STDOUT_FILENO) > 0; + + return cached_on_tty; +} + +int make_stdio(int fd) { + int r, s, t; + + assert(fd >= 0); + + r = dup2(fd, STDIN_FILENO); + s = dup2(fd, STDOUT_FILENO); + t = dup2(fd, STDERR_FILENO); + + if (fd >= 3) + safe_close(fd); + + if (r < 0 || s < 0 || t < 0) + return -errno; + + /* Explicitly unset O_CLOEXEC, since if fd was < 3, then + * dup2() was a NOP and the bit hence possibly set. */ + fd_cloexec(STDIN_FILENO, false); + fd_cloexec(STDOUT_FILENO, false); + fd_cloexec(STDERR_FILENO, false); + + return 0; +} + +int make_null_stdio(void) { + int null_fd; + + null_fd = open("/dev/null", O_RDWR|O_NOCTTY); + if (null_fd < 0) + return -errno; + + return make_stdio(null_fd); +} + +int getttyname_malloc(int fd, char **ret) { + size_t l = 100; + int r; + + assert(fd >= 0); + assert(ret); + + for (;;) { + char path[l]; + + r = ttyname_r(fd, path, sizeof(path)); + if (r == 0) { + const char *p; + char *c; + + p = startswith(path, "/dev/"); + c = strdup(p ?: path); + if (!c) + return -ENOMEM; + + *ret = c; + return 0; + } + + if (r != ERANGE) + return -r; + + l *= 2; + } + + return 0; +} + +int getttyname_harder(int fd, char **r) { + int k; + char *s = NULL; + + k = getttyname_malloc(fd, &s); + if (k < 0) + return k; + + if (streq(s, "tty")) { + free(s); + return get_ctty(0, NULL, r); + } + + *r = s; + return 0; +} + +int get_ctty_devnr(pid_t pid, dev_t *d) { + int r; + _cleanup_free_ char *line = NULL; + const char *p; + unsigned long ttynr; + + assert(pid >= 0); + + p = procfs_file_alloca(pid, "stat"); + r = read_one_line_file(p, &line); + if (r < 0) + return r; + + p = strrchr(line, ')'); + if (!p) + return -EIO; + + p++; + + if (sscanf(p, " " + "%*c " /* state */ + "%*d " /* ppid */ + "%*d " /* pgrp */ + "%*d " /* session */ + "%lu ", /* ttynr */ + &ttynr) != 1) + return -EIO; + + if (major(ttynr) == 0 && minor(ttynr) == 0) + return -ENXIO; + + if (d) + *d = (dev_t) ttynr; + + return 0; +} + +int get_ctty(pid_t pid, dev_t *_devnr, char **r) { + char fn[sizeof("/dev/char/")-1 + 2*DECIMAL_STR_MAX(unsigned) + 1 + 1], *b = NULL; + _cleanup_free_ char *s = NULL; + const char *p; + dev_t devnr; + int k; + + assert(r); + + k = get_ctty_devnr(pid, &devnr); + if (k < 0) + return k; + + sprintf(fn, "/dev/char/%u:%u", major(devnr), minor(devnr)); + + k = readlink_malloc(fn, &s); + if (k < 0) { + + if (k != -ENOENT) + return k; + + /* This is an ugly hack */ + if (major(devnr) == 136) { + if (asprintf(&b, "pts/%u", minor(devnr)) < 0) + return -ENOMEM; + } else { + /* Probably something like the ptys which have no + * symlink in /dev/char. Let's return something + * vaguely useful. */ + + b = strdup(fn + 5); + if (!b) + return -ENOMEM; + } + } else { + if (startswith(s, "/dev/")) + p = s + 5; + else if (startswith(s, "../")) + p = s + 3; + else + p = s; + + b = strdup(p); + if (!b) + return -ENOMEM; + } + + *r = b; + if (_devnr) + *_devnr = devnr; + + return 0; +} + +int ptsname_malloc(int fd, char **ret) { + size_t l = 100; + + assert(fd >= 0); + assert(ret); + + for (;;) { + char *c; + + c = new(char, l); + if (!c) + return -ENOMEM; + + if (ptsname_r(fd, c, l) == 0) { + *ret = c; + return 0; + } + if (errno != ERANGE) { + free(c); + return -errno; + } + + free(c); + l *= 2; + } +} + +int ptsname_namespace(int pty, char **ret) { + int no = -1, r; + + /* Like ptsname(), but doesn't assume that the path is + * accessible in the local namespace. */ + + r = ioctl(pty, TIOCGPTN, &no); + if (r < 0) + return -errno; + + if (no < 0) + return -EIO; + + if (asprintf(ret, "/dev/pts/%i", no) < 0) + return -ENOMEM; + + return 0; +} + +int openpt_in_namespace(pid_t pid, int flags) { + _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1; + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + siginfo_t si; + pid_t child; + int r; + + assert(pid > 0); + + r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) + return -errno; + + child = fork(); + if (child < 0) + return -errno; + + if (child == 0) { + int master; + + pair[0] = safe_close(pair[0]); + + r = namespace_enter(pidnsfd, mntnsfd, -1, usernsfd, rootfd); + if (r < 0) + _exit(EXIT_FAILURE); + + master = posix_openpt(flags|O_NOCTTY|O_CLOEXEC); + if (master < 0) + _exit(EXIT_FAILURE); + + if (unlockpt(master) < 0) + _exit(EXIT_FAILURE); + + if (send_one_fd(pair[1], master, 0) < 0) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + + r = wait_for_terminate(child, &si); + if (r < 0) + return r; + if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) + return -EIO; + + return receive_one_fd(pair[0], 0); +} + +int open_terminal_in_namespace(pid_t pid, const char *name, int mode) { + _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1; + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + siginfo_t si; + pid_t child; + int r; + + r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) + return -errno; + + child = fork(); + if (child < 0) + return -errno; + + if (child == 0) { + int master; + + pair[0] = safe_close(pair[0]); + + r = namespace_enter(pidnsfd, mntnsfd, -1, usernsfd, rootfd); + if (r < 0) + _exit(EXIT_FAILURE); + + master = open_terminal(name, mode|O_NOCTTY|O_CLOEXEC); + if (master < 0) + _exit(EXIT_FAILURE); + + if (send_one_fd(pair[1], master, 0) < 0) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + + r = wait_for_terminate(child, &si); + if (r < 0) + return r; + if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) + return -EIO; + + return receive_one_fd(pair[0], 0); +} + +bool colors_enabled(void) { + static int enabled = -1; + + if (_unlikely_(enabled < 0)) { + const char *colors; + + colors = getenv("SYSTEMD_COLORS"); + if (colors) + enabled = parse_boolean(colors) != 0; + else if (streq_ptr(getenv("TERM"), "dumb")) + enabled = false; + else + enabled = on_tty(); + } + + return enabled; +} diff --git a/src/libbasic/terminal-util.h b/src/libbasic/terminal-util.h new file mode 100644 index 0000000000..a7c96a77cb --- /dev/null +++ b/src/libbasic/terminal-util.h @@ -0,0 +1,126 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include + +#include "macro.h" +#include "time-util.h" + +#define ANSI_RED "\x1B[0;31m" +#define ANSI_GREEN "\x1B[0;32m" +#define ANSI_UNDERLINE "\x1B[0;4m" +#define ANSI_HIGHLIGHT "\x1B[0;1;39m" +#define ANSI_HIGHLIGHT_RED "\x1B[0;1;31m" +#define ANSI_HIGHLIGHT_GREEN "\x1B[0;1;32m" +#define ANSI_HIGHLIGHT_YELLOW "\x1B[0;1;33m" +#define ANSI_HIGHLIGHT_BLUE "\x1B[0;1;34m" +#define ANSI_HIGHLIGHT_UNDERLINE "\x1B[0;1;4m" +#define ANSI_NORMAL "\x1B[0m" + +#define ANSI_ERASE_TO_END_OF_LINE "\x1B[K" + +/* Set cursor to top left corner and clear screen */ +#define ANSI_HOME_CLEAR "\x1B[H\x1B[2J" + +int reset_terminal_fd(int fd, bool switch_to_text); +int reset_terminal(const char *name); + +int open_terminal(const char *name, int mode); +int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocstty_eperm, usec_t timeout); +int release_terminal(void); + +int terminal_vhangup_fd(int fd); +int terminal_vhangup(const char *name); + +int chvt(int vt); + +int read_one_char(FILE *f, char *ret, usec_t timeout, bool *need_nl); +int ask_char(char *ret, const char *replies, const char *text, ...) _printf_(3, 4); +int ask_string(char **ret, const char *text, ...) _printf_(2, 3); + +int vt_disallocate(const char *name); + +char *resolve_dev_console(char **active); +bool tty_is_vc(const char *tty); +bool tty_is_vc_resolve(const char *tty); +bool tty_is_console(const char *tty) _pure_; +int vtnr_from_tty(const char *tty); +const char *default_term_for_tty(const char *tty); + +int make_stdio(int fd); +int make_null_stdio(void); +int make_console_stdio(void); + +int fd_columns(int fd); +unsigned columns(void); +int fd_lines(int fd); +unsigned lines(void); +void columns_lines_cache_reset(int _unused_ signum); + +bool on_tty(void); +bool colors_enabled(void); + +static inline const char *ansi_underline(void) { + return colors_enabled() ? ANSI_UNDERLINE : ""; +} + +static inline const char *ansi_highlight(void) { + return colors_enabled() ? ANSI_HIGHLIGHT : ""; +} + +static inline const char *ansi_highlight_underline(void) { + return colors_enabled() ? ANSI_HIGHLIGHT_UNDERLINE : ""; +} + +static inline const char *ansi_highlight_red(void) { + return colors_enabled() ? ANSI_HIGHLIGHT_RED : ""; +} + +static inline const char *ansi_highlight_green(void) { + return colors_enabled() ? ANSI_HIGHLIGHT_GREEN : ""; +} + +static inline const char *ansi_highlight_yellow(void) { + return colors_enabled() ? ANSI_HIGHLIGHT_YELLOW : ""; +} + +static inline const char *ansi_highlight_blue(void) { + return colors_enabled() ? ANSI_HIGHLIGHT_BLUE : ""; +} + +static inline const char *ansi_normal(void) { + return colors_enabled() ? ANSI_NORMAL : ""; +} + +int get_ctty_devnr(pid_t pid, dev_t *d); +int get_ctty(pid_t, dev_t *_devnr, char **r); + +int getttyname_malloc(int fd, char **r); +int getttyname_harder(int fd, char **r); + +int ptsname_malloc(int fd, char **ret); +int ptsname_namespace(int pty, char **ret); + +int openpt_in_namespace(pid_t pid, int flags); +int open_terminal_in_namespace(pid_t pid, const char *name, int mode); diff --git a/src/libbasic/time-util.c b/src/libbasic/time-util.c new file mode 100644 index 0000000000..edd9179cb8 --- /dev/null +++ b/src/libbasic/time-util.c @@ -0,0 +1,1157 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "path-util.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" + +static nsec_t timespec_load_nsec(const struct timespec *ts); + +static clockid_t map_clock_id(clockid_t c) { + + /* Some more exotic archs (s390, ppc, …) lack the "ALARM" flavour of the clocks. Thus, clock_gettime() will + * fail for them. Since they are essentially the same as their non-ALARM pendants (their only difference is + * when timers are set on them), let's just map them accordingly. This way, we can get the correct time even on + * those archs. */ + + switch (c) { + + case CLOCK_BOOTTIME_ALARM: + return CLOCK_BOOTTIME; + + case CLOCK_REALTIME_ALARM: + return CLOCK_REALTIME; + + default: + return c; + } +} + +usec_t now(clockid_t clock_id) { + struct timespec ts; + + assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0); + + return timespec_load(&ts); +} + +nsec_t now_nsec(clockid_t clock_id) { + struct timespec ts; + + assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0); + + return timespec_load_nsec(&ts); +} + +dual_timestamp* dual_timestamp_get(dual_timestamp *ts) { + assert(ts); + + ts->realtime = now(CLOCK_REALTIME); + ts->monotonic = now(CLOCK_MONOTONIC); + + return ts; +} + +dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) { + int64_t delta; + assert(ts); + + if (u == USEC_INFINITY || u <= 0) { + ts->realtime = ts->monotonic = u; + return ts; + } + + ts->realtime = u; + + delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u; + ts->monotonic = usec_sub(now(CLOCK_MONOTONIC), delta); + + return ts; +} + +dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) { + int64_t delta; + assert(ts); + + if (u == USEC_INFINITY) { + ts->realtime = ts->monotonic = USEC_INFINITY; + return ts; + } + + ts->monotonic = u; + delta = (int64_t) now(CLOCK_MONOTONIC) - (int64_t) u; + ts->realtime = usec_sub(now(CLOCK_REALTIME), delta); + + return ts; +} + +dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, usec_t u) { + int64_t delta; + + if (u == USEC_INFINITY) { + ts->realtime = ts->monotonic = USEC_INFINITY; + return ts; + } + + dual_timestamp_get(ts); + delta = (int64_t) now(clock_boottime_or_monotonic()) - (int64_t) u; + ts->realtime = usec_sub(ts->realtime, delta); + ts->monotonic = usec_sub(ts->monotonic, delta); + + return ts; +} + +usec_t timespec_load(const struct timespec *ts) { + assert(ts); + + if (ts->tv_sec == (time_t) -1 && ts->tv_nsec == (long) -1) + return USEC_INFINITY; + + if ((usec_t) ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC) + return USEC_INFINITY; + + return + (usec_t) ts->tv_sec * USEC_PER_SEC + + (usec_t) ts->tv_nsec / NSEC_PER_USEC; +} + +static nsec_t timespec_load_nsec(const struct timespec *ts) { + assert(ts); + + if (ts->tv_sec == (time_t) -1 && ts->tv_nsec == (long) -1) + return NSEC_INFINITY; + + if ((nsec_t) ts->tv_sec >= (UINT64_MAX - ts->tv_nsec) / NSEC_PER_SEC) + return NSEC_INFINITY; + + return (nsec_t) ts->tv_sec * NSEC_PER_SEC + (nsec_t) ts->tv_nsec; +} + +struct timespec *timespec_store(struct timespec *ts, usec_t u) { + assert(ts); + + if (u == USEC_INFINITY) { + ts->tv_sec = (time_t) -1; + ts->tv_nsec = (long) -1; + return ts; + } + + ts->tv_sec = (time_t) (u / USEC_PER_SEC); + ts->tv_nsec = (long int) ((u % USEC_PER_SEC) * NSEC_PER_USEC); + + return ts; +} + +usec_t timeval_load(const struct timeval *tv) { + assert(tv); + + if (tv->tv_sec == (time_t) -1 && + tv->tv_usec == (suseconds_t) -1) + return USEC_INFINITY; + + if ((usec_t) tv->tv_sec > (UINT64_MAX - tv->tv_usec) / USEC_PER_SEC) + return USEC_INFINITY; + + return + (usec_t) tv->tv_sec * USEC_PER_SEC + + (usec_t) tv->tv_usec; +} + +struct timeval *timeval_store(struct timeval *tv, usec_t u) { + assert(tv); + + if (u == USEC_INFINITY) { + tv->tv_sec = (time_t) -1; + tv->tv_usec = (suseconds_t) -1; + } else { + tv->tv_sec = (time_t) (u / USEC_PER_SEC); + tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC); + } + + return tv; +} + +static char *format_timestamp_internal(char *buf, size_t l, usec_t t, + bool utc, bool us) { + struct tm tm; + time_t sec; + int k; + + assert(buf); + assert(l > 0); + + if (t <= 0 || t == USEC_INFINITY) + return NULL; + + sec = (time_t) (t / USEC_PER_SEC); + localtime_or_gmtime_r(&sec, &tm, utc); + + if (us) + k = strftime(buf, l, "%a %Y-%m-%d %H:%M:%S", &tm); + else + k = strftime(buf, l, "%a %Y-%m-%d %H:%M:%S %Z", &tm); + + if (k <= 0) + return NULL; + if (us) { + snprintf(buf + strlen(buf), l - strlen(buf), ".%06llu", (unsigned long long) (t % USEC_PER_SEC)); + if (strftime(buf + strlen(buf), l - strlen(buf), " %Z", &tm) <= 0) + return NULL; + } + + return buf; +} + +char *format_timestamp(char *buf, size_t l, usec_t t) { + return format_timestamp_internal(buf, l, t, false, false); +} + +char *format_timestamp_utc(char *buf, size_t l, usec_t t) { + return format_timestamp_internal(buf, l, t, true, false); +} + +char *format_timestamp_us(char *buf, size_t l, usec_t t) { + return format_timestamp_internal(buf, l, t, false, true); +} + +char *format_timestamp_us_utc(char *buf, size_t l, usec_t t) { + return format_timestamp_internal(buf, l, t, true, true); +} + +char *format_timestamp_relative(char *buf, size_t l, usec_t t) { + const char *s; + usec_t n, d; + + if (t <= 0 || t == USEC_INFINITY) + return NULL; + + n = now(CLOCK_REALTIME); + if (n > t) { + d = n - t; + s = "ago"; + } else { + d = t - n; + s = "left"; + } + + if (d >= USEC_PER_YEAR) + snprintf(buf, l, USEC_FMT " years " USEC_FMT " months %s", + d / USEC_PER_YEAR, + (d % USEC_PER_YEAR) / USEC_PER_MONTH, s); + else if (d >= USEC_PER_MONTH) + snprintf(buf, l, USEC_FMT " months " USEC_FMT " days %s", + d / USEC_PER_MONTH, + (d % USEC_PER_MONTH) / USEC_PER_DAY, s); + else if (d >= USEC_PER_WEEK) + snprintf(buf, l, USEC_FMT " weeks " USEC_FMT " days %s", + d / USEC_PER_WEEK, + (d % USEC_PER_WEEK) / USEC_PER_DAY, s); + else if (d >= 2*USEC_PER_DAY) + snprintf(buf, l, USEC_FMT " days %s", d / USEC_PER_DAY, s); + else if (d >= 25*USEC_PER_HOUR) + snprintf(buf, l, "1 day " USEC_FMT "h %s", + (d - USEC_PER_DAY) / USEC_PER_HOUR, s); + else if (d >= 6*USEC_PER_HOUR) + snprintf(buf, l, USEC_FMT "h %s", + d / USEC_PER_HOUR, s); + else if (d >= USEC_PER_HOUR) + snprintf(buf, l, USEC_FMT "h " USEC_FMT "min %s", + d / USEC_PER_HOUR, + (d % USEC_PER_HOUR) / USEC_PER_MINUTE, s); + else if (d >= 5*USEC_PER_MINUTE) + snprintf(buf, l, USEC_FMT "min %s", + d / USEC_PER_MINUTE, s); + else if (d >= USEC_PER_MINUTE) + snprintf(buf, l, USEC_FMT "min " USEC_FMT "s %s", + d / USEC_PER_MINUTE, + (d % USEC_PER_MINUTE) / USEC_PER_SEC, s); + else if (d >= USEC_PER_SEC) + snprintf(buf, l, USEC_FMT "s %s", + d / USEC_PER_SEC, s); + else if (d >= USEC_PER_MSEC) + snprintf(buf, l, USEC_FMT "ms %s", + d / USEC_PER_MSEC, s); + else if (d > 0) + snprintf(buf, l, USEC_FMT"us %s", + d, s); + else + snprintf(buf, l, "now"); + + buf[l-1] = 0; + return buf; +} + +char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) { + static const struct { + const char *suffix; + usec_t usec; + } table[] = { + { "y", USEC_PER_YEAR }, + { "month", USEC_PER_MONTH }, + { "w", USEC_PER_WEEK }, + { "d", USEC_PER_DAY }, + { "h", USEC_PER_HOUR }, + { "min", USEC_PER_MINUTE }, + { "s", USEC_PER_SEC }, + { "ms", USEC_PER_MSEC }, + { "us", 1 }, + }; + + unsigned i; + char *p = buf; + bool something = false; + + assert(buf); + assert(l > 0); + + if (t == USEC_INFINITY) { + strncpy(p, "infinity", l-1); + p[l-1] = 0; + return p; + } + + if (t <= 0) { + strncpy(p, "0", l-1); + p[l-1] = 0; + return p; + } + + /* The result of this function can be parsed with parse_sec */ + + for (i = 0; i < ELEMENTSOF(table); i++) { + int k = 0; + size_t n; + bool done = false; + usec_t a, b; + + if (t <= 0) + break; + + if (t < accuracy && something) + break; + + if (t < table[i].usec) + continue; + + if (l <= 1) + break; + + a = t / table[i].usec; + b = t % table[i].usec; + + /* Let's see if we should shows this in dot notation */ + if (t < USEC_PER_MINUTE && b > 0) { + usec_t cc; + int j; + + j = 0; + for (cc = table[i].usec; cc > 1; cc /= 10) + j++; + + for (cc = accuracy; cc > 1; cc /= 10) { + b /= 10; + j--; + } + + if (j > 0) { + k = snprintf(p, l, + "%s"USEC_FMT".%0*llu%s", + p > buf ? " " : "", + a, + j, + (unsigned long long) b, + table[i].suffix); + + t = 0; + done = true; + } + } + + /* No? Then let's show it normally */ + if (!done) { + k = snprintf(p, l, + "%s"USEC_FMT"%s", + p > buf ? " " : "", + a, + table[i].suffix); + + t = b; + } + + n = MIN((size_t) k, l); + + l -= n; + p += n; + + something = true; + } + + *p = 0; + + return buf; +} + +void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) { + + assert(f); + assert(name); + assert(t); + + if (!dual_timestamp_is_set(t)) + return; + + fprintf(f, "%s="USEC_FMT" "USEC_FMT"\n", + name, + t->realtime, + t->monotonic); +} + +int dual_timestamp_deserialize(const char *value, dual_timestamp *t) { + unsigned long long a, b; + + assert(value); + assert(t); + + if (sscanf(value, "%llu %llu", &a, &b) != 2) { + log_debug("Failed to parse dual timestamp value \"%s\": %m", value); + return -EINVAL; + } + + t->realtime = a; + t->monotonic = b; + + return 0; +} + +int timestamp_deserialize(const char *value, usec_t *timestamp) { + int r; + + assert(value); + + r = safe_atou64(value, timestamp); + if (r < 0) + return log_debug_errno(r, "Failed to parse timestamp value \"%s\": %m", value); + + return r; +} + +int parse_timestamp(const char *t, usec_t *usec) { + static const struct { + const char *name; + const int nr; + } day_nr[] = { + { "Sunday", 0 }, + { "Sun", 0 }, + { "Monday", 1 }, + { "Mon", 1 }, + { "Tuesday", 2 }, + { "Tue", 2 }, + { "Wednesday", 3 }, + { "Wed", 3 }, + { "Thursday", 4 }, + { "Thu", 4 }, + { "Friday", 5 }, + { "Fri", 5 }, + { "Saturday", 6 }, + { "Sat", 6 }, + }; + + const char *k; + const char *utc; + struct tm tm, copy; + time_t x; + usec_t x_usec, plus = 0, minus = 0, ret; + int r, weekday = -1; + unsigned i; + + /* + * Allowed syntaxes: + * + * 2012-09-22 16:34:22 + * 2012-09-22 16:34 (seconds will be set to 0) + * 2012-09-22 (time will be set to 00:00:00) + * 16:34:22 (date will be set to today) + * 16:34 (date will be set to today, seconds to 0) + * now + * yesterday (time is set to 00:00:00) + * today (time is set to 00:00:00) + * tomorrow (time is set to 00:00:00) + * +5min + * -5days + * @2147483647 (seconds since epoch) + * + */ + + assert(t); + assert(usec); + + if (t[0] == '@') + return parse_sec(t + 1, usec); + + ret = now(CLOCK_REALTIME); + + if (streq(t, "now")) + goto finish; + + else if (t[0] == '+') { + r = parse_sec(t+1, &plus); + if (r < 0) + return r; + + goto finish; + + } else if (t[0] == '-') { + r = parse_sec(t+1, &minus); + if (r < 0) + return r; + + goto finish; + + } else if ((k = endswith(t, " ago"))) { + t = strndupa(t, k - t); + + r = parse_sec(t, &minus); + if (r < 0) + return r; + + goto finish; + + } else if ((k = endswith(t, " left"))) { + t = strndupa(t, k - t); + + r = parse_sec(t, &plus); + if (r < 0) + return r; + + goto finish; + } + + utc = endswith_no_case(t, " UTC"); + if (utc) + t = strndupa(t, utc - t); + + x = ret / USEC_PER_SEC; + x_usec = 0; + + assert_se(localtime_or_gmtime_r(&x, &tm, utc)); + tm.tm_isdst = -1; + + if (streq(t, "today")) { + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto from_tm; + + } else if (streq(t, "yesterday")) { + tm.tm_mday--; + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto from_tm; + + } else if (streq(t, "tomorrow")) { + tm.tm_mday++; + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto from_tm; + } + + + for (i = 0; i < ELEMENTSOF(day_nr); i++) { + size_t skip; + + if (!startswith_no_case(t, day_nr[i].name)) + continue; + + skip = strlen(day_nr[i].name); + if (t[skip] != ' ') + continue; + + weekday = day_nr[i].nr; + t += skip + 1; + break; + } + + copy = tm; + k = strptime(t, "%y-%m-%d %H:%M:%S", &tm); + if (k) { + if (*k == '.') + goto parse_usec; + else if (*k == 0) + goto from_tm; + } + + tm = copy; + k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm); + if (k) { + if (*k == '.') + goto parse_usec; + else if (*k == 0) + goto from_tm; + } + + tm = copy; + k = strptime(t, "%y-%m-%d %H:%M", &tm); + if (k && *k == 0) { + tm.tm_sec = 0; + goto from_tm; + } + + tm = copy; + k = strptime(t, "%Y-%m-%d %H:%M", &tm); + if (k && *k == 0) { + tm.tm_sec = 0; + goto from_tm; + } + + tm = copy; + k = strptime(t, "%y-%m-%d", &tm); + if (k && *k == 0) { + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto from_tm; + } + + tm = copy; + k = strptime(t, "%Y-%m-%d", &tm); + if (k && *k == 0) { + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto from_tm; + } + + tm = copy; + k = strptime(t, "%H:%M:%S", &tm); + if (k) { + if (*k == '.') + goto parse_usec; + else if (*k == 0) + goto from_tm; + } + + tm = copy; + k = strptime(t, "%H:%M", &tm); + if (k && *k == 0) { + tm.tm_sec = 0; + goto from_tm; + } + + return -EINVAL; + +parse_usec: + { + unsigned add; + + k++; + r = parse_fractional_part_u(&k, 6, &add); + if (r < 0) + return -EINVAL; + + if (*k) + return -EINVAL; + + x_usec = add; + + } + +from_tm: + x = mktime_or_timegm(&tm, utc); + if (x == (time_t) -1) + return -EINVAL; + + if (weekday >= 0 && tm.tm_wday != weekday) + return -EINVAL; + + ret = (usec_t) x * USEC_PER_SEC + x_usec; + +finish: + ret += plus; + if (ret > minus) + ret -= minus; + else + ret = 0; + + *usec = ret; + + return 0; +} + +static char* extract_multiplier(char *p, usec_t *multiplier) { + static const struct { + const char *suffix; + usec_t usec; + } table[] = { + { "seconds", USEC_PER_SEC }, + { "second", USEC_PER_SEC }, + { "sec", USEC_PER_SEC }, + { "s", USEC_PER_SEC }, + { "minutes", USEC_PER_MINUTE }, + { "minute", USEC_PER_MINUTE }, + { "min", USEC_PER_MINUTE }, + { "months", USEC_PER_MONTH }, + { "month", USEC_PER_MONTH }, + { "M", USEC_PER_MONTH }, + { "msec", USEC_PER_MSEC }, + { "ms", USEC_PER_MSEC }, + { "m", USEC_PER_MINUTE }, + { "hours", USEC_PER_HOUR }, + { "hour", USEC_PER_HOUR }, + { "hr", USEC_PER_HOUR }, + { "h", USEC_PER_HOUR }, + { "days", USEC_PER_DAY }, + { "day", USEC_PER_DAY }, + { "d", USEC_PER_DAY }, + { "weeks", USEC_PER_WEEK }, + { "week", USEC_PER_WEEK }, + { "w", USEC_PER_WEEK }, + { "years", USEC_PER_YEAR }, + { "year", USEC_PER_YEAR }, + { "y", USEC_PER_YEAR }, + { "usec", 1ULL }, + { "us", 1ULL }, + }; + unsigned i; + + for (i = 0; i < ELEMENTSOF(table); i++) { + char *e; + + e = startswith(p, table[i].suffix); + if (e) { + *multiplier = table[i].usec; + return e; + } + } + + return p; +} + +int parse_time(const char *t, usec_t *usec, usec_t default_unit) { + const char *p, *s; + usec_t r = 0; + bool something = false; + + assert(t); + assert(usec); + assert(default_unit > 0); + + p = t; + + p += strspn(p, WHITESPACE); + s = startswith(p, "infinity"); + if (s) { + s += strspn(s, WHITESPACE); + if (*s != 0) + return -EINVAL; + + *usec = USEC_INFINITY; + return 0; + } + + for (;;) { + long long l, z = 0; + char *e; + unsigned n = 0; + usec_t multiplier = default_unit, k; + + p += strspn(p, WHITESPACE); + + if (*p == 0) { + if (!something) + return -EINVAL; + + break; + } + + errno = 0; + l = strtoll(p, &e, 10); + if (errno > 0) + return -errno; + if (l < 0) + return -ERANGE; + + if (*e == '.') { + char *b = e + 1; + + errno = 0; + z = strtoll(b, &e, 10); + if (errno > 0) + return -errno; + + if (z < 0) + return -ERANGE; + + if (e == b) + return -EINVAL; + + n = e - b; + + } else if (e == p) + return -EINVAL; + + e += strspn(e, WHITESPACE); + p = extract_multiplier(e, &multiplier); + + something = true; + + k = (usec_t) z * multiplier; + + for (; n > 0; n--) + k /= 10; + + r += (usec_t) l * multiplier + k; + } + + *usec = r; + + return 0; +} + +int parse_sec(const char *t, usec_t *usec) { + return parse_time(t, usec, USEC_PER_SEC); +} + +int parse_nsec(const char *t, nsec_t *nsec) { + static const struct { + const char *suffix; + nsec_t nsec; + } table[] = { + { "seconds", NSEC_PER_SEC }, + { "second", NSEC_PER_SEC }, + { "sec", NSEC_PER_SEC }, + { "s", NSEC_PER_SEC }, + { "minutes", NSEC_PER_MINUTE }, + { "minute", NSEC_PER_MINUTE }, + { "min", NSEC_PER_MINUTE }, + { "months", NSEC_PER_MONTH }, + { "month", NSEC_PER_MONTH }, + { "msec", NSEC_PER_MSEC }, + { "ms", NSEC_PER_MSEC }, + { "m", NSEC_PER_MINUTE }, + { "hours", NSEC_PER_HOUR }, + { "hour", NSEC_PER_HOUR }, + { "hr", NSEC_PER_HOUR }, + { "h", NSEC_PER_HOUR }, + { "days", NSEC_PER_DAY }, + { "day", NSEC_PER_DAY }, + { "d", NSEC_PER_DAY }, + { "weeks", NSEC_PER_WEEK }, + { "week", NSEC_PER_WEEK }, + { "w", NSEC_PER_WEEK }, + { "years", NSEC_PER_YEAR }, + { "year", NSEC_PER_YEAR }, + { "y", NSEC_PER_YEAR }, + { "usec", NSEC_PER_USEC }, + { "us", NSEC_PER_USEC }, + { "nsec", 1ULL }, + { "ns", 1ULL }, + { "", 1ULL }, /* default is nsec */ + }; + + const char *p, *s; + nsec_t r = 0; + bool something = false; + + assert(t); + assert(nsec); + + p = t; + + p += strspn(p, WHITESPACE); + s = startswith(p, "infinity"); + if (s) { + s += strspn(s, WHITESPACE); + if (*s != 0) + return -EINVAL; + + *nsec = NSEC_INFINITY; + return 0; + } + + for (;;) { + long long l, z = 0; + char *e; + unsigned i, n = 0; + + p += strspn(p, WHITESPACE); + + if (*p == 0) { + if (!something) + return -EINVAL; + + break; + } + + errno = 0; + l = strtoll(p, &e, 10); + + if (errno > 0) + return -errno; + + if (l < 0) + return -ERANGE; + + if (*e == '.') { + char *b = e + 1; + + errno = 0; + z = strtoll(b, &e, 10); + if (errno > 0) + return -errno; + + if (z < 0) + return -ERANGE; + + if (e == b) + return -EINVAL; + + n = e - b; + + } else if (e == p) + return -EINVAL; + + e += strspn(e, WHITESPACE); + + for (i = 0; i < ELEMENTSOF(table); i++) + if (startswith(e, table[i].suffix)) { + nsec_t k = (nsec_t) z * table[i].nsec; + + for (; n > 0; n--) + k /= 10; + + r += (nsec_t) l * table[i].nsec + k; + p = e + strlen(table[i].suffix); + + something = true; + break; + } + + if (i >= ELEMENTSOF(table)) + return -EINVAL; + + } + + *nsec = r; + + return 0; +} + +bool ntp_synced(void) { + struct timex txc = {}; + + if (adjtimex(&txc) < 0) + return false; + + if (txc.status & STA_UNSYNC) + return false; + + return true; +} + +int get_timezones(char ***ret) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_strv_free_ char **zones = NULL; + size_t n_zones = 0, n_allocated = 0; + + assert(ret); + + zones = strv_new("UTC", NULL); + if (!zones) + return -ENOMEM; + + n_allocated = 2; + n_zones = 1; + + f = fopen("/usr/share/zoneinfo/zone.tab", "re"); + if (f) { + char l[LINE_MAX]; + + FOREACH_LINE(l, f, return -errno) { + char *p, *w; + size_t k; + + p = strstrip(l); + + if (isempty(p) || *p == '#') + continue; + + /* Skip over country code */ + p += strcspn(p, WHITESPACE); + p += strspn(p, WHITESPACE); + + /* Skip over coordinates */ + p += strcspn(p, WHITESPACE); + p += strspn(p, WHITESPACE); + + /* Found timezone name */ + k = strcspn(p, WHITESPACE); + if (k <= 0) + continue; + + w = strndup(p, k); + if (!w) + return -ENOMEM; + + if (!GREEDY_REALLOC(zones, n_allocated, n_zones + 2)) { + free(w); + return -ENOMEM; + } + + zones[n_zones++] = w; + zones[n_zones] = NULL; + } + + strv_sort(zones); + + } else if (errno != ENOENT) + return -errno; + + *ret = zones; + zones = NULL; + + return 0; +} + +bool timezone_is_valid(const char *name) { + bool slash = false; + const char *p, *t; + struct stat st; + + if (isempty(name)) + return false; + + if (name[0] == '/') + return false; + + for (p = name; *p; p++) { + if (!(*p >= '0' && *p <= '9') && + !(*p >= 'a' && *p <= 'z') && + !(*p >= 'A' && *p <= 'Z') && + !(*p == '-' || *p == '_' || *p == '+' || *p == '/')) + return false; + + if (*p == '/') { + + if (slash) + return false; + + slash = true; + } else + slash = false; + } + + if (slash) + return false; + + t = strjoina("/usr/share/zoneinfo/", name); + if (stat(t, &st) < 0) + return false; + + if (!S_ISREG(st.st_mode)) + return false; + + return true; +} + +bool clock_boottime_supported(void) { + static int supported = -1; + + /* Note that this checks whether CLOCK_BOOTTIME is available in general as well as available for timerfds()! */ + + if (supported < 0) { + int fd; + + fd = timerfd_create(CLOCK_BOOTTIME, TFD_NONBLOCK|TFD_CLOEXEC); + if (fd < 0) + supported = false; + else { + safe_close(fd); + supported = true; + } + } + + return supported; +} + +clockid_t clock_boottime_or_monotonic(void) { + if (clock_boottime_supported()) + return CLOCK_BOOTTIME; + else + return CLOCK_MONOTONIC; +} + +int get_timezone(char **tz) { + _cleanup_free_ char *t = NULL; + const char *e; + char *z; + int r; + + r = readlink_malloc("/etc/localtime", &t); + if (r < 0) + return r; /* returns EINVAL if not a symlink */ + + e = path_startswith(t, "/usr/share/zoneinfo/"); + if (!e) + e = path_startswith(t, "../usr/share/zoneinfo/"); + if (!e) + return -EINVAL; + + if (!timezone_is_valid(e)) + return -EINVAL; + + z = strdup(e); + if (!z) + return -ENOMEM; + + *tz = z; + return 0; +} + +time_t mktime_or_timegm(struct tm *tm, bool utc) { + return utc ? timegm(tm) : mktime(tm); +} + +struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc) { + return utc ? gmtime_r(t, tm) : localtime_r(t, tm); +} + +unsigned long usec_to_jiffies(usec_t u) { + static thread_local unsigned long hz = 0; + long r; + + if (hz == 0) { + r = sysconf(_SC_CLK_TCK); + + assert(r > 0); + hz = (unsigned long) r; + } + + return DIV_ROUND_UP(u , USEC_PER_SEC / hz); +} diff --git a/src/libbasic/time-util.h b/src/libbasic/time-util.h new file mode 100644 index 0000000000..a5e3f567ec --- /dev/null +++ b/src/libbasic/time-util.h @@ -0,0 +1,153 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include + +typedef uint64_t usec_t; +typedef uint64_t nsec_t; + +#define NSEC_FMT "%" PRIu64 +#define USEC_FMT "%" PRIu64 + +#include "macro.h" + +typedef struct dual_timestamp { + usec_t realtime; + usec_t monotonic; +} dual_timestamp; + +#define USEC_INFINITY ((usec_t) -1) +#define NSEC_INFINITY ((nsec_t) -1) + +#define MSEC_PER_SEC 1000ULL +#define USEC_PER_SEC ((usec_t) 1000000ULL) +#define USEC_PER_MSEC ((usec_t) 1000ULL) +#define NSEC_PER_SEC ((nsec_t) 1000000000ULL) +#define NSEC_PER_MSEC ((nsec_t) 1000000ULL) +#define NSEC_PER_USEC ((nsec_t) 1000ULL) + +#define USEC_PER_MINUTE ((usec_t) (60ULL*USEC_PER_SEC)) +#define NSEC_PER_MINUTE ((nsec_t) (60ULL*NSEC_PER_SEC)) +#define USEC_PER_HOUR ((usec_t) (60ULL*USEC_PER_MINUTE)) +#define NSEC_PER_HOUR ((nsec_t) (60ULL*NSEC_PER_MINUTE)) +#define USEC_PER_DAY ((usec_t) (24ULL*USEC_PER_HOUR)) +#define NSEC_PER_DAY ((nsec_t) (24ULL*NSEC_PER_HOUR)) +#define USEC_PER_WEEK ((usec_t) (7ULL*USEC_PER_DAY)) +#define NSEC_PER_WEEK ((nsec_t) (7ULL*NSEC_PER_DAY)) +#define USEC_PER_MONTH ((usec_t) (2629800ULL*USEC_PER_SEC)) +#define NSEC_PER_MONTH ((nsec_t) (2629800ULL*NSEC_PER_SEC)) +#define USEC_PER_YEAR ((usec_t) (31557600ULL*USEC_PER_SEC)) +#define NSEC_PER_YEAR ((nsec_t) (31557600ULL*NSEC_PER_SEC)) + +#define FORMAT_TIMESTAMP_MAX ((4*4+1)+11+9+4+1) /* weekdays can be unicode */ +#define FORMAT_TIMESTAMP_WIDTH 28 /* when outputting, assume this width */ +#define FORMAT_TIMESTAMP_RELATIVE_MAX 256 +#define FORMAT_TIMESPAN_MAX 64 + +#define TIME_T_MAX (time_t)((UINTMAX_C(1) << ((sizeof(time_t) << 3) - 1)) - 1) + +#define DUAL_TIMESTAMP_NULL ((struct dual_timestamp) { 0ULL, 0ULL }) + +usec_t now(clockid_t clock); +nsec_t now_nsec(clockid_t clock); + +dual_timestamp* dual_timestamp_get(dual_timestamp *ts); +dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u); +dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u); +dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, usec_t u); + +static inline bool dual_timestamp_is_set(dual_timestamp *ts) { + return ((ts->realtime > 0 && ts->realtime != USEC_INFINITY) || + (ts->monotonic > 0 && ts->monotonic != USEC_INFINITY)); +} + +usec_t timespec_load(const struct timespec *ts) _pure_; +struct timespec *timespec_store(struct timespec *ts, usec_t u); + +usec_t timeval_load(const struct timeval *tv) _pure_; +struct timeval *timeval_store(struct timeval *tv, usec_t u); + +char *format_timestamp(char *buf, size_t l, usec_t t); +char *format_timestamp_utc(char *buf, size_t l, usec_t t); +char *format_timestamp_us(char *buf, size_t l, usec_t t); +char *format_timestamp_us_utc(char *buf, size_t l, usec_t t); +char *format_timestamp_relative(char *buf, size_t l, usec_t t); +char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy); + +void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t); +int dual_timestamp_deserialize(const char *value, dual_timestamp *t); +int timestamp_deserialize(const char *value, usec_t *timestamp); + +int parse_timestamp(const char *t, usec_t *usec); + +int parse_sec(const char *t, usec_t *usec); +int parse_time(const char *t, usec_t *usec, usec_t default_unit); +int parse_nsec(const char *t, nsec_t *nsec); + +bool ntp_synced(void); + +int get_timezones(char ***l); +bool timezone_is_valid(const char *name); + +bool clock_boottime_supported(void); +clockid_t clock_boottime_or_monotonic(void); + +#define xstrftime(buf, fmt, tm) \ + assert_message_se(strftime(buf, ELEMENTSOF(buf), fmt, tm) > 0, \ + "xstrftime: " #buf "[] must be big enough") + +int get_timezone(char **timezone); + +time_t mktime_or_timegm(struct tm *tm, bool utc); +struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc); + +unsigned long usec_to_jiffies(usec_t usec); + +static inline usec_t usec_add(usec_t a, usec_t b) { + usec_t c; + + /* Adds two time values, and makes sure USEC_INFINITY as input results as USEC_INFINITY in output, and doesn't + * overflow. */ + + c = a + b; + if (c < a || c < b) /* overflow check */ + return USEC_INFINITY; + + return c; +} + +static inline usec_t usec_sub(usec_t timestamp, int64_t delta) { + if (delta < 0) + return usec_add(timestamp, (usec_t) (-delta)); + + if (timestamp == USEC_INFINITY) /* Make sure infinity doesn't degrade */ + return USEC_INFINITY; + + if (timestamp < (usec_t) delta) + return 0; + + return timestamp - delta; +} diff --git a/src/libbasic/umask-util.h b/src/libbasic/umask-util.h new file mode 100644 index 0000000000..359d87d27c --- /dev/null +++ b/src/libbasic/umask-util.h @@ -0,0 +1,46 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include + +#include "macro.h" + +static inline void umaskp(mode_t *u) { + umask(*u); +} + +#define _cleanup_umask_ _cleanup_(umaskp) + +struct _umask_struct_ { + mode_t mask; + bool quit; +}; + +static inline void _reset_umask_(struct _umask_struct_ *s) { + umask(s->mask); +}; + +#define RUN_WITH_UMASK(mask) \ + for (_cleanup_(_reset_umask_) struct _umask_struct_ _saved_umask_ = { umask(mask), false }; \ + !_saved_umask_.quit ; \ + _saved_umask_.quit = true) diff --git a/src/libbasic/unaligned.h b/src/libbasic/unaligned.h new file mode 100644 index 0000000000..79be645bed --- /dev/null +++ b/src/libbasic/unaligned.h @@ -0,0 +1,111 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 + +/* BE */ + +static inline uint16_t unaligned_read_be16(const void *_u) { + const uint8_t *u = _u; + + return (((uint16_t) u[0]) << 8) | + ((uint16_t) u[1]); +} + +static inline uint32_t unaligned_read_be32(const void *_u) { + const uint8_t *u = _u; + + return (((uint32_t) unaligned_read_be16(u)) << 16) | + ((uint32_t) unaligned_read_be16(u + 2)); +} + +static inline uint64_t unaligned_read_be64(const void *_u) { + const uint8_t *u = _u; + + return (((uint64_t) unaligned_read_be32(u)) << 32) | + ((uint64_t) unaligned_read_be32(u + 4)); +} + +static inline void unaligned_write_be16(void *_u, uint16_t a) { + uint8_t *u = _u; + + u[0] = (uint8_t) (a >> 8); + u[1] = (uint8_t) a; +} + +static inline void unaligned_write_be32(void *_u, uint32_t a) { + uint8_t *u = _u; + + unaligned_write_be16(u, (uint16_t) (a >> 16)); + unaligned_write_be16(u + 2, (uint16_t) a); +} + +static inline void unaligned_write_be64(void *_u, uint64_t a) { + uint8_t *u = _u; + + unaligned_write_be32(u, (uint32_t) (a >> 32)); + unaligned_write_be32(u + 4, (uint32_t) a); +} + +/* LE */ + +static inline uint16_t unaligned_read_le16(const void *_u) { + const uint8_t *u = _u; + + return (((uint16_t) u[1]) << 8) | + ((uint16_t) u[0]); +} + +static inline uint32_t unaligned_read_le32(const void *_u) { + const uint8_t *u = _u; + + return (((uint32_t) unaligned_read_le16(u + 2)) << 16) | + ((uint32_t) unaligned_read_le16(u)); +} + +static inline uint64_t unaligned_read_le64(const void *_u) { + const uint8_t *u = _u; + + return (((uint64_t) unaligned_read_le32(u + 4)) << 32) | + ((uint64_t) unaligned_read_le32(u)); +} + +static inline void unaligned_write_le16(void *_u, uint16_t a) { + uint8_t *u = _u; + + u[0] = (uint8_t) a; + u[1] = (uint8_t) (a >> 8); +} + +static inline void unaligned_write_le32(void *_u, uint32_t a) { + uint8_t *u = _u; + + unaligned_write_le16(u, (uint16_t) a); + unaligned_write_le16(u + 2, (uint16_t) (a >> 16)); +} + +static inline void unaligned_write_le64(void *_u, uint64_t a) { + uint8_t *u = _u; + + unaligned_write_le32(u, (uint32_t) a); + unaligned_write_le32(u + 4, (uint32_t) (a >> 32)); +} diff --git a/src/libbasic/unit-name.c b/src/libbasic/unit-name.c new file mode 100644 index 0000000000..fe883b95c7 --- /dev/null +++ b/src/libbasic/unit-name.c @@ -0,0 +1,1049 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include + +#include "alloc-util.h" +#include "bus-label.h" +#include "glob-util.h" +#include "hexdecoct.h" +#include "macro.h" +#include "path-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" + +/* Characters valid in a unit name. */ +#define VALID_CHARS \ + DIGITS \ + LETTERS \ + ":-_.\\" + +/* The same, but also permits the single @ character that may appear */ +#define VALID_CHARS_WITH_AT \ + "@" \ + VALID_CHARS + +/* All chars valid in a unit name glob */ +#define VALID_CHARS_GLOB \ + VALID_CHARS_WITH_AT \ + "[]!-*?" + +bool unit_name_is_valid(const char *n, UnitNameFlags flags) { + const char *e, *i, *at; + + assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0); + + if (_unlikely_(flags == 0)) + return false; + + if (isempty(n)) + return false; + + if (strlen(n) >= UNIT_NAME_MAX) + return false; + + e = strrchr(n, '.'); + if (!e || e == n) + return false; + + if (unit_type_from_string(e + 1) < 0) + return false; + + for (i = n, at = NULL; i < e; i++) { + + if (*i == '@' && !at) + at = i; + + if (!strchr("@" VALID_CHARS, *i)) + return false; + } + + if (at == n) + return false; + + if (flags & UNIT_NAME_PLAIN) + if (!at) + return true; + + if (flags & UNIT_NAME_INSTANCE) + if (at && e > at + 1) + return true; + + if (flags & UNIT_NAME_TEMPLATE) + if (at && e == at + 1) + return true; + + return false; +} + +bool unit_prefix_is_valid(const char *p) { + + /* We don't allow additional @ in the prefix string */ + + if (isempty(p)) + return false; + + return in_charset(p, VALID_CHARS); +} + +bool unit_instance_is_valid(const char *i) { + + /* The max length depends on the length of the string, so we + * don't really check this here. */ + + if (isempty(i)) + return false; + + /* We allow additional @ in the instance string, we do not + * allow them in the prefix! */ + + return in_charset(i, "@" VALID_CHARS); +} + +bool unit_suffix_is_valid(const char *s) { + if (isempty(s)) + return false; + + if (s[0] != '.') + return false; + + if (unit_type_from_string(s + 1) < 0) + return false; + + return true; +} + +int unit_name_to_prefix(const char *n, char **ret) { + const char *p; + char *s; + + assert(n); + assert(ret); + + if (!unit_name_is_valid(n, UNIT_NAME_ANY)) + return -EINVAL; + + p = strchr(n, '@'); + if (!p) + p = strrchr(n, '.'); + + assert_se(p); + + s = strndup(n, p - n); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +int unit_name_to_instance(const char *n, char **instance) { + const char *p, *d; + char *i; + + assert(n); + assert(instance); + + if (!unit_name_is_valid(n, UNIT_NAME_ANY)) + return -EINVAL; + + /* Everything past the first @ and before the last . is the instance */ + p = strchr(n, '@'); + if (!p) { + *instance = NULL; + return 0; + } + + p++; + + d = strrchr(p, '.'); + if (!d) + return -EINVAL; + + i = strndup(p, d-p); + if (!i) + return -ENOMEM; + + *instance = i; + return 1; +} + +int unit_name_to_prefix_and_instance(const char *n, char **ret) { + const char *d; + char *s; + + assert(n); + assert(ret); + + if (!unit_name_is_valid(n, UNIT_NAME_ANY)) + return -EINVAL; + + d = strrchr(n, '.'); + if (!d) + return -EINVAL; + + s = strndup(n, d - n); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +UnitType unit_name_to_type(const char *n) { + const char *e; + + assert(n); + + if (!unit_name_is_valid(n, UNIT_NAME_ANY)) + return _UNIT_TYPE_INVALID; + + assert_se(e = strrchr(n, '.')); + + return unit_type_from_string(e + 1); +} + +int unit_name_change_suffix(const char *n, const char *suffix, char **ret) { + char *e, *s; + size_t a, b; + + assert(n); + assert(suffix); + assert(ret); + + if (!unit_name_is_valid(n, UNIT_NAME_ANY)) + return -EINVAL; + + if (!unit_suffix_is_valid(suffix)) + return -EINVAL; + + assert_se(e = strrchr(n, '.')); + + a = e - n; + b = strlen(suffix); + + s = new(char, a + b + 1); + if (!s) + return -ENOMEM; + + strcpy(mempcpy(s, n, a), suffix); + *ret = s; + + return 0; +} + +int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) { + char *s; + + assert(prefix); + assert(suffix); + assert(ret); + + if (!unit_prefix_is_valid(prefix)) + return -EINVAL; + + if (instance && !unit_instance_is_valid(instance)) + return -EINVAL; + + if (!unit_suffix_is_valid(suffix)) + return -EINVAL; + + if (!instance) + s = strappend(prefix, suffix); + else + s = strjoin(prefix, "@", instance, suffix, NULL); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +static char *do_escape_char(char c, char *t) { + assert(t); + + *(t++) = '\\'; + *(t++) = 'x'; + *(t++) = hexchar(c >> 4); + *(t++) = hexchar(c); + + return t; +} + +static char *do_escape(const char *f, char *t) { + assert(f); + assert(t); + + /* do not create units with a leading '.', like for "/.dotdir" mount points */ + if (*f == '.') { + t = do_escape_char(*f, t); + f++; + } + + for (; *f; f++) { + if (*f == '/') + *(t++) = '-'; + else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f)) + t = do_escape_char(*f, t); + else + *(t++) = *f; + } + + return t; +} + +char *unit_name_escape(const char *f) { + char *r, *t; + + assert(f); + + r = new(char, strlen(f)*4+1); + if (!r) + return NULL; + + t = do_escape(f, r); + *t = 0; + + return r; +} + +int unit_name_unescape(const char *f, char **ret) { + _cleanup_free_ char *r = NULL; + char *t; + + assert(f); + + r = strdup(f); + if (!r) + return -ENOMEM; + + for (t = r; *f; f++) { + if (*f == '-') + *(t++) = '/'; + else if (*f == '\\') { + int a, b; + + if (f[1] != 'x') + return -EINVAL; + + a = unhexchar(f[2]); + if (a < 0) + return -EINVAL; + + b = unhexchar(f[3]); + if (b < 0) + return -EINVAL; + + *(t++) = (char) (((uint8_t) a << 4U) | (uint8_t) b); + f += 3; + } else + *(t++) = *f; + } + + *t = 0; + + *ret = r; + r = NULL; + + return 0; +} + +int unit_name_path_escape(const char *f, char **ret) { + char *p, *s; + + assert(f); + assert(ret); + + p = strdupa(f); + if (!p) + return -ENOMEM; + + path_kill_slashes(p); + + if (STR_IN_SET(p, "/", "")) + s = strdup("-"); + else { + char *e; + + if (!path_is_safe(p)) + return -EINVAL; + + /* Truncate trailing slashes */ + e = endswith(p, "/"); + if (e) + *e = 0; + + /* Truncate leading slashes */ + if (p[0] == '/') + p++; + + s = unit_name_escape(p); + } + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +int unit_name_path_unescape(const char *f, char **ret) { + char *s; + int r; + + assert(f); + + if (isempty(f)) + return -EINVAL; + + if (streq(f, "-")) { + s = strdup("/"); + if (!s) + return -ENOMEM; + } else { + char *w; + + r = unit_name_unescape(f, &w); + if (r < 0) + return r; + + /* Don't accept trailing or leading slashes */ + if (startswith(w, "/") || endswith(w, "/")) { + free(w); + return -EINVAL; + } + + /* Prefix a slash again */ + s = strappend("/", w); + free(w); + if (!s) + return -ENOMEM; + + if (!path_is_safe(s)) { + free(s); + return -EINVAL; + } + } + + if (ret) + *ret = s; + else + free(s); + + return 0; +} + +int unit_name_replace_instance(const char *f, const char *i, char **ret) { + const char *p, *e; + char *s; + size_t a, b; + + assert(f); + assert(i); + assert(ret); + + if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) + return -EINVAL; + if (!unit_instance_is_valid(i)) + return -EINVAL; + + assert_se(p = strchr(f, '@')); + assert_se(e = strrchr(f, '.')); + + a = p - f; + b = strlen(i); + + s = new(char, a + 1 + b + strlen(e) + 1); + if (!s) + return -ENOMEM; + + strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e); + + *ret = s; + return 0; +} + +int unit_name_template(const char *f, char **ret) { + const char *p, *e; + char *s; + size_t a; + + assert(f); + assert(ret); + + if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) + return -EINVAL; + + assert_se(p = strchr(f, '@')); + assert_se(e = strrchr(f, '.')); + + a = p - f; + + s = new(char, a + 1 + strlen(e) + 1); + if (!s) + return -ENOMEM; + + strcpy(mempcpy(s, f, a + 1), e); + + *ret = s; + return 0; +} + +int unit_name_from_path(const char *path, const char *suffix, char **ret) { + _cleanup_free_ char *p = NULL; + char *s = NULL; + int r; + + assert(path); + assert(suffix); + assert(ret); + + if (!unit_suffix_is_valid(suffix)) + return -EINVAL; + + r = unit_name_path_escape(path, &p); + if (r < 0) + return r; + + s = strappend(p, suffix); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) { + _cleanup_free_ char *p = NULL; + char *s; + int r; + + assert(prefix); + assert(path); + assert(suffix); + assert(ret); + + if (!unit_prefix_is_valid(prefix)) + return -EINVAL; + + if (!unit_suffix_is_valid(suffix)) + return -EINVAL; + + r = unit_name_path_escape(path, &p); + if (r < 0) + return r; + + s = strjoin(prefix, "@", p, suffix, NULL); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +int unit_name_to_path(const char *name, char **ret) { + _cleanup_free_ char *prefix = NULL; + int r; + + assert(name); + + r = unit_name_to_prefix(name, &prefix); + if (r < 0) + return r; + + return unit_name_path_unescape(prefix, ret); +} + +char *unit_dbus_path_from_name(const char *name) { + _cleanup_free_ char *e = NULL; + + assert(name); + + e = bus_label_escape(name); + if (!e) + return NULL; + + return strappend("/org/freedesktop/systemd1/unit/", e); +} + +int unit_name_from_dbus_path(const char *path, char **name) { + const char *e; + char *n; + + e = startswith(path, "/org/freedesktop/systemd1/unit/"); + if (!e) + return -EINVAL; + + n = bus_label_unescape(e); + if (!n) + return -ENOMEM; + + *name = n; + return 0; +} + +const char* unit_dbus_interface_from_type(UnitType t) { + + static const char *const table[_UNIT_TYPE_MAX] = { + [UNIT_SERVICE] = "org.freedesktop.systemd1.Service", + [UNIT_SOCKET] = "org.freedesktop.systemd1.Socket", + [UNIT_BUSNAME] = "org.freedesktop.systemd1.BusName", + [UNIT_TARGET] = "org.freedesktop.systemd1.Target", + [UNIT_DEVICE] = "org.freedesktop.systemd1.Device", + [UNIT_MOUNT] = "org.freedesktop.systemd1.Mount", + [UNIT_AUTOMOUNT] = "org.freedesktop.systemd1.Automount", + [UNIT_SWAP] = "org.freedesktop.systemd1.Swap", + [UNIT_TIMER] = "org.freedesktop.systemd1.Timer", + [UNIT_PATH] = "org.freedesktop.systemd1.Path", + [UNIT_SLICE] = "org.freedesktop.systemd1.Slice", + [UNIT_SCOPE] = "org.freedesktop.systemd1.Scope", + }; + + if (t < 0) + return NULL; + if (t >= _UNIT_TYPE_MAX) + return NULL; + + return table[t]; +} + +const char *unit_dbus_interface_from_name(const char *name) { + UnitType t; + + t = unit_name_to_type(name); + if (t < 0) + return NULL; + + return unit_dbus_interface_from_type(t); +} + +static char *do_escape_mangle(const char *f, UnitNameMangle allow_globs, char *t) { + const char *valid_chars; + + assert(f); + assert(IN_SET(allow_globs, UNIT_NAME_GLOB, UNIT_NAME_NOGLOB)); + assert(t); + + /* We'll only escape the obvious characters here, to play + * safe. */ + + valid_chars = allow_globs == UNIT_NAME_GLOB ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT; + + for (; *f; f++) { + if (*f == '/') + *(t++) = '-'; + else if (!strchr(valid_chars, *f)) + t = do_escape_char(*f, t); + else + *(t++) = *f; + } + + return t; +} + +/** + * Convert a string to a unit name. /dev/blah is converted to dev-blah.device, + * /blah/blah is converted to blah-blah.mount, anything else is left alone, + * except that @suffix is appended if a valid unit suffix is not present. + * + * If @allow_globs, globs characters are preserved. Otherwise, they are escaped. + */ +int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, const char *suffix, char **ret) { + char *s, *t; + int r; + + assert(name); + assert(suffix); + assert(ret); + + if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */ + return -EINVAL; + + if (!unit_suffix_is_valid(suffix)) + return -EINVAL; + + /* Already a fully valid unit name? If so, no mangling is necessary... */ + if (unit_name_is_valid(name, UNIT_NAME_ANY)) + goto good; + + /* Already a fully valid globbing expression? If so, no mangling is necessary either... */ + if (allow_globs == UNIT_NAME_GLOB && + string_is_glob(name) && + in_charset(name, VALID_CHARS_GLOB)) + goto good; + + if (is_device_path(name)) { + r = unit_name_from_path(name, ".device", ret); + if (r >= 0) + return 1; + if (r != -EINVAL) + return r; + } + + if (path_is_absolute(name)) { + r = unit_name_from_path(name, ".mount", ret); + if (r >= 0) + return 1; + if (r != -EINVAL) + return r; + } + + s = new(char, strlen(name) * 4 + strlen(suffix) + 1); + if (!s) + return -ENOMEM; + + t = do_escape_mangle(name, allow_globs, s); + *t = 0; + + /* Append a suffix if it doesn't have any, but only if this is not a glob, so that we can allow "foo.*" as a + * valid glob. */ + if ((allow_globs != UNIT_NAME_GLOB || !string_is_glob(s)) && unit_name_to_type(s) < 0) + strcpy(t, suffix); + + *ret = s; + return 1; + +good: + s = strdup(name); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +int slice_build_parent_slice(const char *slice, char **ret) { + char *s, *dash; + int r; + + assert(slice); + assert(ret); + + if (!slice_name_is_valid(slice)) + return -EINVAL; + + if (streq(slice, "-.slice")) { + *ret = NULL; + return 0; + } + + s = strdup(slice); + if (!s) + return -ENOMEM; + + dash = strrchr(s, '-'); + if (dash) + strcpy(dash, ".slice"); + else { + r = free_and_strdup(&s, "-.slice"); + if (r < 0) { + free(s); + return r; + } + } + + *ret = s; + return 1; +} + +int slice_build_subslice(const char *slice, const char*name, char **ret) { + char *subslice; + + assert(slice); + assert(name); + assert(ret); + + if (!slice_name_is_valid(slice)) + return -EINVAL; + + if (!unit_prefix_is_valid(name)) + return -EINVAL; + + if (streq(slice, "-.slice")) + subslice = strappend(name, ".slice"); + else { + char *e; + + assert_se(e = endswith(slice, ".slice")); + + subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1); + if (!subslice) + return -ENOMEM; + + stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice"); + } + + *ret = subslice; + return 0; +} + +bool slice_name_is_valid(const char *name) { + const char *p, *e; + bool dash = false; + + if (!unit_name_is_valid(name, UNIT_NAME_PLAIN)) + return false; + + if (streq(name, "-.slice")) + return true; + + e = endswith(name, ".slice"); + if (!e) + return false; + + for (p = name; p < e; p++) { + + if (*p == '-') { + + /* Don't allow initial dash */ + if (p == name) + return false; + + /* Don't allow multiple dashes */ + if (dash) + return false; + + dash = true; + } else + dash = false; + } + + /* Don't allow trailing hash */ + if (dash) + return false; + + return true; +} + +static const char* const unit_type_table[_UNIT_TYPE_MAX] = { + [UNIT_SERVICE] = "service", + [UNIT_SOCKET] = "socket", + [UNIT_BUSNAME] = "busname", + [UNIT_TARGET] = "target", + [UNIT_DEVICE] = "device", + [UNIT_MOUNT] = "mount", + [UNIT_AUTOMOUNT] = "automount", + [UNIT_SWAP] = "swap", + [UNIT_TIMER] = "timer", + [UNIT_PATH] = "path", + [UNIT_SLICE] = "slice", + [UNIT_SCOPE] = "scope", +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType); + +static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = { + [UNIT_STUB] = "stub", + [UNIT_LOADED] = "loaded", + [UNIT_NOT_FOUND] = "not-found", + [UNIT_ERROR] = "error", + [UNIT_MERGED] = "merged", + [UNIT_MASKED] = "masked" +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState); + +static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = { + [UNIT_ACTIVE] = "active", + [UNIT_RELOADING] = "reloading", + [UNIT_INACTIVE] = "inactive", + [UNIT_FAILED] = "failed", + [UNIT_ACTIVATING] = "activating", + [UNIT_DEACTIVATING] = "deactivating" +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState); + +static const char* const automount_state_table[_AUTOMOUNT_STATE_MAX] = { + [AUTOMOUNT_DEAD] = "dead", + [AUTOMOUNT_WAITING] = "waiting", + [AUTOMOUNT_RUNNING] = "running", + [AUTOMOUNT_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(automount_state, AutomountState); + +static const char* const busname_state_table[_BUSNAME_STATE_MAX] = { + [BUSNAME_DEAD] = "dead", + [BUSNAME_MAKING] = "making", + [BUSNAME_REGISTERED] = "registered", + [BUSNAME_LISTENING] = "listening", + [BUSNAME_RUNNING] = "running", + [BUSNAME_SIGTERM] = "sigterm", + [BUSNAME_SIGKILL] = "sigkill", + [BUSNAME_FAILED] = "failed", +}; + +DEFINE_STRING_TABLE_LOOKUP(busname_state, BusNameState); + +static const char* const device_state_table[_DEVICE_STATE_MAX] = { + [DEVICE_DEAD] = "dead", + [DEVICE_TENTATIVE] = "tentative", + [DEVICE_PLUGGED] = "plugged", +}; + +DEFINE_STRING_TABLE_LOOKUP(device_state, DeviceState); + +static const char* const mount_state_table[_MOUNT_STATE_MAX] = { + [MOUNT_DEAD] = "dead", + [MOUNT_MOUNTING] = "mounting", + [MOUNT_MOUNTING_DONE] = "mounting-done", + [MOUNT_MOUNTED] = "mounted", + [MOUNT_REMOUNTING] = "remounting", + [MOUNT_UNMOUNTING] = "unmounting", + [MOUNT_MOUNTING_SIGTERM] = "mounting-sigterm", + [MOUNT_MOUNTING_SIGKILL] = "mounting-sigkill", + [MOUNT_REMOUNTING_SIGTERM] = "remounting-sigterm", + [MOUNT_REMOUNTING_SIGKILL] = "remounting-sigkill", + [MOUNT_UNMOUNTING_SIGTERM] = "unmounting-sigterm", + [MOUNT_UNMOUNTING_SIGKILL] = "unmounting-sigkill", + [MOUNT_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(mount_state, MountState); + +static const char* const path_state_table[_PATH_STATE_MAX] = { + [PATH_DEAD] = "dead", + [PATH_WAITING] = "waiting", + [PATH_RUNNING] = "running", + [PATH_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(path_state, PathState); + +static const char* const scope_state_table[_SCOPE_STATE_MAX] = { + [SCOPE_DEAD] = "dead", + [SCOPE_RUNNING] = "running", + [SCOPE_ABANDONED] = "abandoned", + [SCOPE_STOP_SIGTERM] = "stop-sigterm", + [SCOPE_STOP_SIGKILL] = "stop-sigkill", + [SCOPE_FAILED] = "failed", +}; + +DEFINE_STRING_TABLE_LOOKUP(scope_state, ScopeState); + +static const char* const service_state_table[_SERVICE_STATE_MAX] = { + [SERVICE_DEAD] = "dead", + [SERVICE_START_PRE] = "start-pre", + [SERVICE_START] = "start", + [SERVICE_START_POST] = "start-post", + [SERVICE_RUNNING] = "running", + [SERVICE_EXITED] = "exited", + [SERVICE_RELOAD] = "reload", + [SERVICE_STOP] = "stop", + [SERVICE_STOP_SIGABRT] = "stop-sigabrt", + [SERVICE_STOP_SIGTERM] = "stop-sigterm", + [SERVICE_STOP_SIGKILL] = "stop-sigkill", + [SERVICE_STOP_POST] = "stop-post", + [SERVICE_FINAL_SIGTERM] = "final-sigterm", + [SERVICE_FINAL_SIGKILL] = "final-sigkill", + [SERVICE_FAILED] = "failed", + [SERVICE_AUTO_RESTART] = "auto-restart", +}; + +DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState); + +static const char* const slice_state_table[_SLICE_STATE_MAX] = { + [SLICE_DEAD] = "dead", + [SLICE_ACTIVE] = "active" +}; + +DEFINE_STRING_TABLE_LOOKUP(slice_state, SliceState); + +static const char* const socket_state_table[_SOCKET_STATE_MAX] = { + [SOCKET_DEAD] = "dead", + [SOCKET_START_PRE] = "start-pre", + [SOCKET_START_CHOWN] = "start-chown", + [SOCKET_START_POST] = "start-post", + [SOCKET_LISTENING] = "listening", + [SOCKET_RUNNING] = "running", + [SOCKET_STOP_PRE] = "stop-pre", + [SOCKET_STOP_PRE_SIGTERM] = "stop-pre-sigterm", + [SOCKET_STOP_PRE_SIGKILL] = "stop-pre-sigkill", + [SOCKET_STOP_POST] = "stop-post", + [SOCKET_FINAL_SIGTERM] = "final-sigterm", + [SOCKET_FINAL_SIGKILL] = "final-sigkill", + [SOCKET_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(socket_state, SocketState); + +static const char* const swap_state_table[_SWAP_STATE_MAX] = { + [SWAP_DEAD] = "dead", + [SWAP_ACTIVATING] = "activating", + [SWAP_ACTIVATING_DONE] = "activating-done", + [SWAP_ACTIVE] = "active", + [SWAP_DEACTIVATING] = "deactivating", + [SWAP_ACTIVATING_SIGTERM] = "activating-sigterm", + [SWAP_ACTIVATING_SIGKILL] = "activating-sigkill", + [SWAP_DEACTIVATING_SIGTERM] = "deactivating-sigterm", + [SWAP_DEACTIVATING_SIGKILL] = "deactivating-sigkill", + [SWAP_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(swap_state, SwapState); + +static const char* const target_state_table[_TARGET_STATE_MAX] = { + [TARGET_DEAD] = "dead", + [TARGET_ACTIVE] = "active" +}; + +DEFINE_STRING_TABLE_LOOKUP(target_state, TargetState); + +static const char* const timer_state_table[_TIMER_STATE_MAX] = { + [TIMER_DEAD] = "dead", + [TIMER_WAITING] = "waiting", + [TIMER_RUNNING] = "running", + [TIMER_ELAPSED] = "elapsed", + [TIMER_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(timer_state, TimerState); + +static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = { + [UNIT_REQUIRES] = "Requires", + [UNIT_REQUISITE] = "Requisite", + [UNIT_WANTS] = "Wants", + [UNIT_BINDS_TO] = "BindsTo", + [UNIT_PART_OF] = "PartOf", + [UNIT_REQUIRED_BY] = "RequiredBy", + [UNIT_REQUISITE_OF] = "RequisiteOf", + [UNIT_WANTED_BY] = "WantedBy", + [UNIT_BOUND_BY] = "BoundBy", + [UNIT_CONSISTS_OF] = "ConsistsOf", + [UNIT_CONFLICTS] = "Conflicts", + [UNIT_CONFLICTED_BY] = "ConflictedBy", + [UNIT_BEFORE] = "Before", + [UNIT_AFTER] = "After", + [UNIT_ON_FAILURE] = "OnFailure", + [UNIT_TRIGGERS] = "Triggers", + [UNIT_TRIGGERED_BY] = "TriggeredBy", + [UNIT_PROPAGATES_RELOAD_TO] = "PropagatesReloadTo", + [UNIT_RELOAD_PROPAGATED_FROM] = "ReloadPropagatedFrom", + [UNIT_JOINS_NAMESPACE_OF] = "JoinsNamespaceOf", + [UNIT_REFERENCES] = "References", + [UNIT_REFERENCED_BY] = "ReferencedBy", +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency); diff --git a/src/libbasic/unit-name.h b/src/libbasic/unit-name.h new file mode 100644 index 0000000000..f209a84634 --- /dev/null +++ b/src/libbasic/unit-name.h @@ -0,0 +1,368 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "macro.h" + +#define UNIT_NAME_MAX 256 + +typedef enum UnitType { + UNIT_SERVICE = 0, + UNIT_SOCKET, + UNIT_BUSNAME, + UNIT_TARGET, + UNIT_DEVICE, + UNIT_MOUNT, + UNIT_AUTOMOUNT, + UNIT_SWAP, + UNIT_TIMER, + UNIT_PATH, + UNIT_SLICE, + UNIT_SCOPE, + _UNIT_TYPE_MAX, + _UNIT_TYPE_INVALID = -1 +} UnitType; + +typedef enum UnitLoadState { + UNIT_STUB = 0, + UNIT_LOADED, + UNIT_NOT_FOUND, + UNIT_ERROR, + UNIT_MERGED, + UNIT_MASKED, + _UNIT_LOAD_STATE_MAX, + _UNIT_LOAD_STATE_INVALID = -1 +} UnitLoadState; + +typedef enum UnitActiveState { + UNIT_ACTIVE, + UNIT_RELOADING, + UNIT_INACTIVE, + UNIT_FAILED, + UNIT_ACTIVATING, + UNIT_DEACTIVATING, + _UNIT_ACTIVE_STATE_MAX, + _UNIT_ACTIVE_STATE_INVALID = -1 +} UnitActiveState; + +typedef enum AutomountState { + AUTOMOUNT_DEAD, + AUTOMOUNT_WAITING, + AUTOMOUNT_RUNNING, + AUTOMOUNT_FAILED, + _AUTOMOUNT_STATE_MAX, + _AUTOMOUNT_STATE_INVALID = -1 +} AutomountState; + +typedef enum BusNameState { + BUSNAME_DEAD, + BUSNAME_MAKING, + BUSNAME_REGISTERED, + BUSNAME_LISTENING, + BUSNAME_RUNNING, + BUSNAME_SIGTERM, + BUSNAME_SIGKILL, + BUSNAME_FAILED, + _BUSNAME_STATE_MAX, + _BUSNAME_STATE_INVALID = -1 +} BusNameState; + +/* We simply watch devices, we cannot plug/unplug them. That + * simplifies the state engine greatly */ +typedef enum DeviceState { + DEVICE_DEAD, + DEVICE_TENTATIVE, /* mounted or swapped, but not (yet) announced by udev */ + DEVICE_PLUGGED, /* announced by udev */ + _DEVICE_STATE_MAX, + _DEVICE_STATE_INVALID = -1 +} DeviceState; + +typedef enum MountState { + MOUNT_DEAD, + MOUNT_MOUNTING, /* /usr/bin/mount is running, but the mount is not done yet. */ + MOUNT_MOUNTING_DONE, /* /usr/bin/mount is running, and the mount is done. */ + MOUNT_MOUNTED, + MOUNT_REMOUNTING, + MOUNT_UNMOUNTING, + MOUNT_MOUNTING_SIGTERM, + MOUNT_MOUNTING_SIGKILL, + MOUNT_REMOUNTING_SIGTERM, + MOUNT_REMOUNTING_SIGKILL, + MOUNT_UNMOUNTING_SIGTERM, + MOUNT_UNMOUNTING_SIGKILL, + MOUNT_FAILED, + _MOUNT_STATE_MAX, + _MOUNT_STATE_INVALID = -1 +} MountState; + +typedef enum PathState { + PATH_DEAD, + PATH_WAITING, + PATH_RUNNING, + PATH_FAILED, + _PATH_STATE_MAX, + _PATH_STATE_INVALID = -1 +} PathState; + +typedef enum ScopeState { + SCOPE_DEAD, + SCOPE_RUNNING, + SCOPE_ABANDONED, + SCOPE_STOP_SIGTERM, + SCOPE_STOP_SIGKILL, + SCOPE_FAILED, + _SCOPE_STATE_MAX, + _SCOPE_STATE_INVALID = -1 +} ScopeState; + +typedef enum ServiceState { + SERVICE_DEAD, + SERVICE_START_PRE, + SERVICE_START, + SERVICE_START_POST, + SERVICE_RUNNING, + SERVICE_EXITED, /* Nothing is running anymore, but RemainAfterExit is true hence this is OK */ + SERVICE_RELOAD, + SERVICE_STOP, /* No STOP_PRE state, instead just register multiple STOP executables */ + SERVICE_STOP_SIGABRT, /* Watchdog timeout */ + SERVICE_STOP_SIGTERM, + SERVICE_STOP_SIGKILL, + SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, /* In case the STOP_POST executable hangs, we shoot that down, too */ + SERVICE_FINAL_SIGKILL, + SERVICE_FAILED, + SERVICE_AUTO_RESTART, + _SERVICE_STATE_MAX, + _SERVICE_STATE_INVALID = -1 +} ServiceState; + +typedef enum SliceState { + SLICE_DEAD, + SLICE_ACTIVE, + _SLICE_STATE_MAX, + _SLICE_STATE_INVALID = -1 +} SliceState; + +typedef enum SocketState { + SOCKET_DEAD, + SOCKET_START_PRE, + SOCKET_START_CHOWN, + SOCKET_START_POST, + SOCKET_LISTENING, + SOCKET_RUNNING, + SOCKET_STOP_PRE, + SOCKET_STOP_PRE_SIGTERM, + SOCKET_STOP_PRE_SIGKILL, + SOCKET_STOP_POST, + SOCKET_FINAL_SIGTERM, + SOCKET_FINAL_SIGKILL, + SOCKET_FAILED, + _SOCKET_STATE_MAX, + _SOCKET_STATE_INVALID = -1 +} SocketState; + +typedef enum SwapState { + SWAP_DEAD, + SWAP_ACTIVATING, /* /sbin/swapon is running, but the swap not yet enabled. */ + SWAP_ACTIVATING_DONE, /* /sbin/swapon is running, and the swap is done. */ + SWAP_ACTIVE, + SWAP_DEACTIVATING, + SWAP_ACTIVATING_SIGTERM, + SWAP_ACTIVATING_SIGKILL, + SWAP_DEACTIVATING_SIGTERM, + SWAP_DEACTIVATING_SIGKILL, + SWAP_FAILED, + _SWAP_STATE_MAX, + _SWAP_STATE_INVALID = -1 +} SwapState; + + +typedef enum TargetState { + TARGET_DEAD, + TARGET_ACTIVE, + _TARGET_STATE_MAX, + _TARGET_STATE_INVALID = -1 +} TargetState; + +typedef enum TimerState { + TIMER_DEAD, + TIMER_WAITING, + TIMER_RUNNING, + TIMER_ELAPSED, + TIMER_FAILED, + _TIMER_STATE_MAX, + _TIMER_STATE_INVALID = -1 +} TimerState; + +typedef enum UnitDependency { + /* Positive dependencies */ + UNIT_REQUIRES, + UNIT_REQUISITE, + UNIT_WANTS, + UNIT_BINDS_TO, + UNIT_PART_OF, + + /* Inverse of the above */ + UNIT_REQUIRED_BY, /* inverse of 'requires' is 'required_by' */ + UNIT_REQUISITE_OF, /* inverse of 'requisite' is 'requisite_of' */ + UNIT_WANTED_BY, /* inverse of 'wants' */ + UNIT_BOUND_BY, /* inverse of 'binds_to' */ + UNIT_CONSISTS_OF, /* inverse of 'part_of' */ + + /* Negative dependencies */ + UNIT_CONFLICTS, /* inverse of 'conflicts' is 'conflicted_by' */ + UNIT_CONFLICTED_BY, + + /* Order */ + UNIT_BEFORE, /* inverse of 'before' is 'after' and vice versa */ + UNIT_AFTER, + + /* On Failure */ + UNIT_ON_FAILURE, + + /* Triggers (i.e. a socket triggers a service) */ + UNIT_TRIGGERS, + UNIT_TRIGGERED_BY, + + /* Propagate reloads */ + UNIT_PROPAGATES_RELOAD_TO, + UNIT_RELOAD_PROPAGATED_FROM, + + /* Joins namespace of */ + UNIT_JOINS_NAMESPACE_OF, + + /* Reference information for GC logic */ + UNIT_REFERENCES, /* Inverse of 'references' is 'referenced_by' */ + UNIT_REFERENCED_BY, + + _UNIT_DEPENDENCY_MAX, + _UNIT_DEPENDENCY_INVALID = -1 +} UnitDependency; + +typedef enum UnitNameFlags { + UNIT_NAME_PLAIN = 1, /* Allow foo.service */ + UNIT_NAME_INSTANCE = 2, /* Allow foo@bar.service */ + UNIT_NAME_TEMPLATE = 4, /* Allow foo@.service */ + UNIT_NAME_ANY = UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE, +} UnitNameFlags; + +bool unit_name_is_valid(const char *n, UnitNameFlags flags) _pure_; +bool unit_prefix_is_valid(const char *p) _pure_; +bool unit_instance_is_valid(const char *i) _pure_; +bool unit_suffix_is_valid(const char *s) _pure_; + +static inline int unit_prefix_and_instance_is_valid(const char *p) { + /* For prefix+instance and instance the same rules apply */ + return unit_instance_is_valid(p); +} + +int unit_name_to_prefix(const char *n, char **prefix); +int unit_name_to_instance(const char *n, char **instance); +int unit_name_to_prefix_and_instance(const char *n, char **ret); + +UnitType unit_name_to_type(const char *n) _pure_; + +int unit_name_change_suffix(const char *n, const char *suffix, char **ret); + +int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret); + +char *unit_name_escape(const char *f); +int unit_name_unescape(const char *f, char **ret); +int unit_name_path_escape(const char *f, char **ret); +int unit_name_path_unescape(const char *f, char **ret); + +int unit_name_replace_instance(const char *f, const char *i, char **ret); + +int unit_name_template(const char *f, char **ret); + +int unit_name_from_path(const char *path, const char *suffix, char **ret); +int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret); +int unit_name_to_path(const char *name, char **ret); + +char *unit_dbus_path_from_name(const char *name); +int unit_name_from_dbus_path(const char *path, char **name); + +const char* unit_dbus_interface_from_type(UnitType t); +const char *unit_dbus_interface_from_name(const char *name); + +typedef enum UnitNameMangle { + UNIT_NAME_NOGLOB, + UNIT_NAME_GLOB, +} UnitNameMangle; + +int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, const char *suffix, char **ret); + +static inline int unit_name_mangle(const char *name, UnitNameMangle allow_globs, char **ret) { + return unit_name_mangle_with_suffix(name, allow_globs, ".service", ret); +} + +int slice_build_parent_slice(const char *slice, char **ret); +int slice_build_subslice(const char *slice, const char*name, char **subslice); +bool slice_name_is_valid(const char *name); + +const char *unit_type_to_string(UnitType i) _const_; +UnitType unit_type_from_string(const char *s) _pure_; + +const char *unit_load_state_to_string(UnitLoadState i) _const_; +UnitLoadState unit_load_state_from_string(const char *s) _pure_; + +const char *unit_active_state_to_string(UnitActiveState i) _const_; +UnitActiveState unit_active_state_from_string(const char *s) _pure_; + +const char* automount_state_to_string(AutomountState i) _const_; +AutomountState automount_state_from_string(const char *s) _pure_; + +const char* busname_state_to_string(BusNameState i) _const_; +BusNameState busname_state_from_string(const char *s) _pure_; + +const char* device_state_to_string(DeviceState i) _const_; +DeviceState device_state_from_string(const char *s) _pure_; + +const char* mount_state_to_string(MountState i) _const_; +MountState mount_state_from_string(const char *s) _pure_; + +const char* path_state_to_string(PathState i) _const_; +PathState path_state_from_string(const char *s) _pure_; + +const char* scope_state_to_string(ScopeState i) _const_; +ScopeState scope_state_from_string(const char *s) _pure_; + +const char* service_state_to_string(ServiceState i) _const_; +ServiceState service_state_from_string(const char *s) _pure_; + +const char* slice_state_to_string(SliceState i) _const_; +SliceState slice_state_from_string(const char *s) _pure_; + +const char* socket_state_to_string(SocketState i) _const_; +SocketState socket_state_from_string(const char *s) _pure_; + +const char* swap_state_to_string(SwapState i) _const_; +SwapState swap_state_from_string(const char *s) _pure_; + +const char* target_state_to_string(TargetState i) _const_; +TargetState target_state_from_string(const char *s) _pure_; + +const char *timer_state_to_string(TimerState i) _const_; +TimerState timer_state_from_string(const char *s) _pure_; + +const char *unit_dependency_to_string(UnitDependency i) _const_; +UnitDependency unit_dependency_from_string(const char *s) _pure_; diff --git a/src/libbasic/user-util.c b/src/libbasic/user-util.c new file mode 100644 index 0000000000..f65ca3edaa --- /dev/null +++ b/src/libbasic/user-util.c @@ -0,0 +1,481 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "missing.h" +#include "alloc-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "macro.h" +#include "parse-util.h" +#include "path-util.h" +#include "string-util.h" +#include "user-util.h" + +bool uid_is_valid(uid_t uid) { + + /* Some libc APIs use UID_INVALID as special placeholder */ + if (uid == (uid_t) UINT32_C(0xFFFFFFFF)) + return false; + + /* A long time ago UIDs where 16bit, hence explicitly avoid the 16bit -1 too */ + if (uid == (uid_t) UINT32_C(0xFFFF)) + return false; + + return true; +} + +int parse_uid(const char *s, uid_t *ret) { + uint32_t uid = 0; + int r; + + assert(s); + + assert_cc(sizeof(uid_t) == sizeof(uint32_t)); + r = safe_atou32(s, &uid); + if (r < 0) + return r; + + if (!uid_is_valid(uid)) + return -ENXIO; /* we return ENXIO instead of EINVAL + * here, to make it easy to distuingish + * invalid numeric uids from invalid + * strings. */ + + if (ret) + *ret = uid; + + return 0; +} + +char* getlogname_malloc(void) { + uid_t uid; + struct stat st; + + if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0) + uid = st.st_uid; + else + uid = getuid(); + + return uid_to_name(uid); +} + +char *getusername_malloc(void) { + const char *e; + + e = getenv("USER"); + if (e) + return strdup(e); + + return uid_to_name(getuid()); +} + +int get_user_creds( + const char **username, + uid_t *uid, gid_t *gid, + const char **home, + const char **shell) { + + struct passwd *p; + uid_t u; + + assert(username); + assert(*username); + + /* We enforce some special rules for uid=0: in order to avoid + * NSS lookups for root we hardcode its data. */ + + if (streq(*username, "root") || streq(*username, "0")) { + *username = "root"; + + if (uid) + *uid = 0; + + if (gid) + *gid = 0; + + if (home) + *home = "/root"; + + if (shell) + *shell = "/bin/sh"; + + return 0; + } + + if (parse_uid(*username, &u) >= 0) { + errno = 0; + p = getpwuid(u); + + /* If there are multiple users with the same id, make + * sure to leave $USER to the configured value instead + * of the first occurrence in the database. However if + * the uid was configured by a numeric uid, then let's + * pick the real username from /etc/passwd. */ + if (p) + *username = p->pw_name; + } else { + errno = 0; + p = getpwnam(*username); + } + + if (!p) + return errno > 0 ? -errno : -ESRCH; + + if (uid) { + if (!uid_is_valid(p->pw_uid)) + return -EBADMSG; + + *uid = p->pw_uid; + } + + if (gid) { + if (!gid_is_valid(p->pw_gid)) + return -EBADMSG; + + *gid = p->pw_gid; + } + + if (home) + *home = p->pw_dir; + + if (shell) + *shell = p->pw_shell; + + return 0; +} + +int get_group_creds(const char **groupname, gid_t *gid) { + struct group *g; + gid_t id; + + assert(groupname); + + /* We enforce some special rules for gid=0: in order to avoid + * NSS lookups for root we hardcode its data. */ + + if (streq(*groupname, "root") || streq(*groupname, "0")) { + *groupname = "root"; + + if (gid) + *gid = 0; + + return 0; + } + + if (parse_gid(*groupname, &id) >= 0) { + errno = 0; + g = getgrgid(id); + + if (g) + *groupname = g->gr_name; + } else { + errno = 0; + g = getgrnam(*groupname); + } + + if (!g) + return errno > 0 ? -errno : -ESRCH; + + if (gid) { + if (!gid_is_valid(g->gr_gid)) + return -EBADMSG; + + *gid = g->gr_gid; + } + + return 0; +} + +char* uid_to_name(uid_t uid) { + char *ret; + int r; + + /* Shortcut things to avoid NSS lookups */ + if (uid == 0) + return strdup("root"); + + if (uid_is_valid(uid)) { + long bufsize; + + bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); + if (bufsize <= 0) + bufsize = 4096; + + for (;;) { + struct passwd pwbuf, *pw = NULL; + _cleanup_free_ char *buf = NULL; + + buf = malloc(bufsize); + if (!buf) + return NULL; + + r = getpwuid_r(uid, &pwbuf, buf, (size_t) bufsize, &pw); + if (r == 0 && pw) + return strdup(pw->pw_name); + if (r != ERANGE) + break; + + bufsize *= 2; + } + } + + if (asprintf(&ret, UID_FMT, uid) < 0) + return NULL; + + return ret; +} + +char* gid_to_name(gid_t gid) { + char *ret; + int r; + + if (gid == 0) + return strdup("root"); + + if (gid_is_valid(gid)) { + long bufsize; + + bufsize = sysconf(_SC_GETGR_R_SIZE_MAX); + if (bufsize <= 0) + bufsize = 4096; + + for (;;) { + struct group grbuf, *gr = NULL; + _cleanup_free_ char *buf = NULL; + + buf = malloc(bufsize); + if (!buf) + return NULL; + + r = getgrgid_r(gid, &grbuf, buf, (size_t) bufsize, &gr); + if (r == 0 && gr) + return strdup(gr->gr_name); + if (r != ERANGE) + break; + + bufsize *= 2; + } + } + + if (asprintf(&ret, GID_FMT, gid) < 0) + return NULL; + + return ret; +} + +int in_gid(gid_t gid) { + gid_t *gids; + int ngroups_max, r, i; + + if (getgid() == gid) + return 1; + + if (getegid() == gid) + return 1; + + if (!gid_is_valid(gid)) + return -EINVAL; + + ngroups_max = sysconf(_SC_NGROUPS_MAX); + assert(ngroups_max > 0); + + gids = alloca(sizeof(gid_t) * ngroups_max); + + r = getgroups(ngroups_max, gids); + if (r < 0) + return -errno; + + for (i = 0; i < r; i++) + if (gids[i] == gid) + return 1; + + return 0; +} + +int in_group(const char *name) { + int r; + gid_t gid; + + r = get_group_creds(&name, &gid); + if (r < 0) + return r; + + return in_gid(gid); +} + +int get_home_dir(char **_h) { + struct passwd *p; + const char *e; + char *h; + uid_t u; + + assert(_h); + + /* Take the user specified one */ + e = secure_getenv("HOME"); + if (e && path_is_absolute(e)) { + h = strdup(e); + if (!h) + return -ENOMEM; + + *_h = h; + return 0; + } + + /* Hardcode home directory for root to avoid NSS */ + u = getuid(); + if (u == 0) { + h = strdup("/root"); + if (!h) + return -ENOMEM; + + *_h = h; + return 0; + } + + /* Check the database... */ + errno = 0; + p = getpwuid(u); + if (!p) + return errno > 0 ? -errno : -ESRCH; + + if (!path_is_absolute(p->pw_dir)) + return -EINVAL; + + h = strdup(p->pw_dir); + if (!h) + return -ENOMEM; + + *_h = h; + return 0; +} + +int get_shell(char **_s) { + struct passwd *p; + const char *e; + char *s; + uid_t u; + + assert(_s); + + /* Take the user specified one */ + e = getenv("SHELL"); + if (e) { + s = strdup(e); + if (!s) + return -ENOMEM; + + *_s = s; + return 0; + } + + /* Hardcode home directory for root to avoid NSS */ + u = getuid(); + if (u == 0) { + s = strdup("/bin/sh"); + if (!s) + return -ENOMEM; + + *_s = s; + return 0; + } + + /* Check the database... */ + errno = 0; + p = getpwuid(u); + if (!p) + return errno > 0 ? -errno : -ESRCH; + + if (!path_is_absolute(p->pw_shell)) + return -EINVAL; + + s = strdup(p->pw_shell); + if (!s) + return -ENOMEM; + + *_s = s; + return 0; +} + +int reset_uid_gid(void) { + + if (setgroups(0, NULL) < 0) + return -errno; + + if (setresgid(0, 0, 0) < 0) + return -errno; + + if (setresuid(0, 0, 0) < 0) + return -errno; + + return 0; +} + +int take_etc_passwd_lock(const char *root) { + + struct flock flock = { + .l_type = F_WRLCK, + .l_whence = SEEK_SET, + .l_start = 0, + .l_len = 0, + }; + + const char *path; + int fd, r; + + /* This is roughly the same as lckpwdf(), but not as awful. We + * don't want to use alarm() and signals, hence we implement + * our own trivial version of this. + * + * Note that shadow-utils also takes per-database locks in + * addition to lckpwdf(). However, we don't given that they + * are redundant as they they invoke lckpwdf() first and keep + * it during everything they do. The per-database locks are + * awfully racy, and thus we just won't do them. */ + + if (root) + path = prefix_roota(root, "/etc/.pwd.lock"); + else + path = "/etc/.pwd.lock"; + + fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600); + if (fd < 0) + return -errno; + + r = fcntl(fd, F_SETLKW, &flock); + if (r < 0) { + safe_close(fd); + return -errno; + } + + return fd; +} diff --git a/src/libbasic/user-util.h b/src/libbasic/user-util.h new file mode 100644 index 0000000000..8026eca3f4 --- /dev/null +++ b/src/libbasic/user-util.h @@ -0,0 +1,70 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include + +bool uid_is_valid(uid_t uid); + +static inline bool gid_is_valid(gid_t gid) { + return uid_is_valid((uid_t) gid); +} + +int parse_uid(const char *s, uid_t* ret_uid); + +static inline int parse_gid(const char *s, gid_t *ret_gid) { + return parse_uid(s, (uid_t*) ret_gid); +} + +char* getlogname_malloc(void); +char* getusername_malloc(void); + +int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home, const char **shell); +int get_group_creds(const char **groupname, gid_t *gid); + +char* uid_to_name(uid_t uid); +char* gid_to_name(gid_t gid); + +int in_gid(gid_t gid); +int in_group(const char *name); + +int get_home_dir(char **ret); +int get_shell(char **_ret); + +int reset_uid_gid(void); + +int take_etc_passwd_lock(const char *root); + +#define UID_INVALID ((uid_t) -1) +#define GID_INVALID ((gid_t) -1) + +/* The following macros add 1 when converting things, since UID 0 is a + * valid UID, while the pointer NULL is special */ +#define PTR_TO_UID(p) ((uid_t) (((uintptr_t) (p))-1)) +#define UID_TO_PTR(u) ((void*) (((uintptr_t) (u))+1)) + +#define PTR_TO_GID(p) ((gid_t) (((uintptr_t) (p))-1)) +#define GID_TO_PTR(u) ((void*) (((uintptr_t) (u))+1)) + +static inline bool userns_supported(void) { + return access("/proc/self/uid_map", F_OK) >= 0; +} diff --git a/src/libbasic/utf8.c b/src/libbasic/utf8.c new file mode 100644 index 0000000000..6eae2b983d --- /dev/null +++ b/src/libbasic/utf8.c @@ -0,0 +1,409 @@ +/*** + This file is part of systemd. + + Copyright 2008-2011 Kay Sievers + Copyright 2012 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 . +***/ + +/* Parts of this file are based on the GLIB utf8 validation functions. The + * original license text follows. */ + +/* gutf8.c - Operations on UTF-8 strings. + * + * Copyright (C) 1999 Tom Tromey + * Copyright (C) 2000 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include + +#include "alloc-util.h" +#include "hexdecoct.h" +#include "macro.h" +#include "utf8.h" + +bool unichar_is_valid(char32_t ch) { + + if (ch >= 0x110000) /* End of unicode space */ + return false; + if ((ch & 0xFFFFF800) == 0xD800) /* Reserved area for UTF-16 */ + return false; + if ((ch >= 0xFDD0) && (ch <= 0xFDEF)) /* Reserved */ + return false; + if ((ch & 0xFFFE) == 0xFFFE) /* BOM (Byte Order Mark) */ + return false; + + return true; +} + +static bool unichar_is_control(char32_t ch) { + + /* + 0 to ' '-1 is the C0 range. + DEL=0x7F, and DEL+1 to 0x9F is C1 range. + '\t' is in C0 range, but more or less harmless and commonly used. + */ + + return (ch < ' ' && ch != '\t' && ch != '\n') || + (0x7F <= ch && ch <= 0x9F); +} + +/* count of characters used to encode one unicode char */ +static int utf8_encoded_expected_len(const char *str) { + unsigned char c; + + assert(str); + + c = (unsigned char) str[0]; + if (c < 0x80) + return 1; + if ((c & 0xe0) == 0xc0) + return 2; + if ((c & 0xf0) == 0xe0) + return 3; + if ((c & 0xf8) == 0xf0) + return 4; + if ((c & 0xfc) == 0xf8) + return 5; + if ((c & 0xfe) == 0xfc) + return 6; + + return 0; +} + +/* decode one unicode char */ +int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar) { + char32_t unichar; + int len, i; + + assert(str); + + len = utf8_encoded_expected_len(str); + + switch (len) { + case 1: + *ret_unichar = (char32_t)str[0]; + return 0; + case 2: + unichar = str[0] & 0x1f; + break; + case 3: + unichar = (char32_t)str[0] & 0x0f; + break; + case 4: + unichar = (char32_t)str[0] & 0x07; + break; + case 5: + unichar = (char32_t)str[0] & 0x03; + break; + case 6: + unichar = (char32_t)str[0] & 0x01; + break; + default: + return -EINVAL; + } + + for (i = 1; i < len; i++) { + if (((char32_t)str[i] & 0xc0) != 0x80) + return -EINVAL; + unichar <<= 6; + unichar |= (char32_t)str[i] & 0x3f; + } + + *ret_unichar = unichar; + + return 0; +} + +bool utf8_is_printable_newline(const char* str, size_t length, bool newline) { + const char *p; + + assert(str); + + for (p = str; length;) { + int encoded_len, r; + char32_t val; + + encoded_len = utf8_encoded_valid_unichar(p); + if (encoded_len < 0 || + (size_t) encoded_len > length) + return false; + + r = utf8_encoded_to_unichar(p, &val); + if (r < 0 || + unichar_is_control(val) || + (!newline && val == '\n')) + return false; + + length -= encoded_len; + p += encoded_len; + } + + return true; +} + +const char *utf8_is_valid(const char *str) { + const uint8_t *p; + + assert(str); + + for (p = (const uint8_t*) str; *p; ) { + int len; + + len = utf8_encoded_valid_unichar((const char *)p); + if (len < 0) + return NULL; + + p += len; + } + + return str; +} + +char *utf8_escape_invalid(const char *str) { + char *p, *s; + + assert(str); + + p = s = malloc(strlen(str) * 4 + 1); + if (!p) + return NULL; + + while (*str) { + int len; + + len = utf8_encoded_valid_unichar(str); + if (len > 0) { + s = mempcpy(s, str, len); + str += len; + } else { + s = stpcpy(s, UTF8_REPLACEMENT_CHARACTER); + str += 1; + } + } + + *s = '\0'; + + return p; +} + +char *utf8_escape_non_printable(const char *str) { + char *p, *s; + + assert(str); + + p = s = malloc(strlen(str) * 4 + 1); + if (!p) + return NULL; + + while (*str) { + int len; + + len = utf8_encoded_valid_unichar(str); + if (len > 0) { + if (utf8_is_printable(str, len)) { + s = mempcpy(s, str, len); + str += len; + } else { + while (len > 0) { + *(s++) = '\\'; + *(s++) = 'x'; + *(s++) = hexchar((int) *str >> 4); + *(s++) = hexchar((int) *str); + + str += 1; + len--; + } + } + } else { + s = stpcpy(s, UTF8_REPLACEMENT_CHARACTER); + str += 1; + } + } + + *s = '\0'; + + return p; +} + +char *ascii_is_valid(const char *str) { + const char *p; + + assert(str); + + for (p = str; *p; p++) + if ((unsigned char) *p >= 128) + return NULL; + + return (char*) str; +} + +/** + * utf8_encode_unichar() - Encode single UCS-4 character as UTF-8 + * @out_utf8: output buffer of at least 4 bytes or NULL + * @g: UCS-4 character to encode + * + * This encodes a single UCS-4 character as UTF-8 and writes it into @out_utf8. + * The length of the character is returned. It is not zero-terminated! If the + * output buffer is NULL, only the length is returned. + * + * Returns: The length in bytes that the UTF-8 representation does or would + * occupy. + */ +size_t utf8_encode_unichar(char *out_utf8, char32_t g) { + + if (g < (1 << 7)) { + if (out_utf8) + out_utf8[0] = g & 0x7f; + return 1; + } else if (g < (1 << 11)) { + if (out_utf8) { + out_utf8[0] = 0xc0 | ((g >> 6) & 0x1f); + out_utf8[1] = 0x80 | (g & 0x3f); + } + return 2; + } else if (g < (1 << 16)) { + if (out_utf8) { + out_utf8[0] = 0xe0 | ((g >> 12) & 0x0f); + out_utf8[1] = 0x80 | ((g >> 6) & 0x3f); + out_utf8[2] = 0x80 | (g & 0x3f); + } + return 3; + } else if (g < (1 << 21)) { + if (out_utf8) { + out_utf8[0] = 0xf0 | ((g >> 18) & 0x07); + out_utf8[1] = 0x80 | ((g >> 12) & 0x3f); + out_utf8[2] = 0x80 | ((g >> 6) & 0x3f); + out_utf8[3] = 0x80 | (g & 0x3f); + } + return 4; + } + + return 0; +} + +char *utf16_to_utf8(const void *s, size_t length) { + const uint8_t *f; + char *r, *t; + + r = new(char, (length * 4 + 1) / 2 + 1); + if (!r) + return NULL; + + f = s; + t = r; + + while (f < (const uint8_t*) s + length) { + char16_t w1, w2; + + /* see RFC 2781 section 2.2 */ + + w1 = f[1] << 8 | f[0]; + f += 2; + + if (!utf16_is_surrogate(w1)) { + t += utf8_encode_unichar(t, w1); + + continue; + } + + if (utf16_is_trailing_surrogate(w1)) + continue; + else if (f >= (const uint8_t*) s + length) + break; + + w2 = f[1] << 8 | f[0]; + f += 2; + + if (!utf16_is_trailing_surrogate(w2)) { + f -= 2; + continue; + } + + t += utf8_encode_unichar(t, utf16_surrogate_pair_to_unichar(w1, w2)); + } + + *t = 0; + return r; +} + +/* expected size used to encode one unicode char */ +static int utf8_unichar_to_encoded_len(char32_t unichar) { + + if (unichar < 0x80) + return 1; + if (unichar < 0x800) + return 2; + if (unichar < 0x10000) + return 3; + if (unichar < 0x200000) + return 4; + if (unichar < 0x4000000) + return 5; + + return 6; +} + +/* validate one encoded unicode char and return its length */ +int utf8_encoded_valid_unichar(const char *str) { + int len, i, r; + char32_t unichar; + + assert(str); + + len = utf8_encoded_expected_len(str); + if (len == 0) + return -EINVAL; + + /* ascii is valid */ + if (len == 1) + return 1; + + /* check if expected encoded chars are available */ + for (i = 0; i < len; i++) + if ((str[i] & 0x80) != 0x80) + return -EINVAL; + + r = utf8_encoded_to_unichar(str, &unichar); + if (r < 0) + return r; + + /* check if encoded length matches encoded value */ + if (utf8_unichar_to_encoded_len(unichar) != len) + return -EINVAL; + + /* check if value has valid range */ + if (!unichar_is_valid(unichar)) + return -EINVAL; + + return len; +} diff --git a/src/libbasic/utf8.h b/src/libbasic/utf8.h new file mode 100644 index 0000000000..f9b9c9468b --- /dev/null +++ b/src/libbasic/utf8.h @@ -0,0 +1,60 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 +#include +#include + +#include "macro.h" +#include "missing.h" + +#define UTF8_REPLACEMENT_CHARACTER "\xef\xbf\xbd" +#define UTF8_BYTE_ORDER_MARK "\xef\xbb\xbf" + +bool unichar_is_valid(char32_t c); + +const char *utf8_is_valid(const char *s) _pure_; +char *ascii_is_valid(const char *s) _pure_; + +bool utf8_is_printable_newline(const char* str, size_t length, bool newline) _pure_; +#define utf8_is_printable(str, length) utf8_is_printable_newline(str, length, true) + +char *utf8_escape_invalid(const char *s); +char *utf8_escape_non_printable(const char *str); + +size_t utf8_encode_unichar(char *out_utf8, char32_t g); +char *utf16_to_utf8(const void *s, size_t length); + +int utf8_encoded_valid_unichar(const char *str); +int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar); + +static inline bool utf16_is_surrogate(char16_t c) { + return (0xd800 <= c && c <= 0xdfff); +} + +static inline bool utf16_is_trailing_surrogate(char16_t c) { + return (0xdc00 <= c && c <= 0xdfff); +} + +static inline char32_t utf16_surrogate_pair_to_unichar(char16_t lead, char16_t trail) { + return ((lead - 0xd800) << 10) + (trail - 0xdc00) + 0x10000; +} diff --git a/src/libbasic/util.c b/src/libbasic/util.c new file mode 100644 index 0000000000..756c663be4 --- /dev/null +++ b/src/libbasic/util.c @@ -0,0 +1,808 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "build.h" +#include "def.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "hashmap.h" +#include "hostname-util.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "set.h" +#include "signal-util.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "umask-util.h" +#include "user-util.h" +#include "util.h" + +/* Put this test here for a lack of better place */ +assert_cc(EAGAIN == EWOULDBLOCK); + +int saved_argc = 0; +char **saved_argv = NULL; + +size_t page_size(void) { + static thread_local size_t pgsz = 0; + long r; + + if (_likely_(pgsz > 0)) + return pgsz; + + r = sysconf(_SC_PAGESIZE); + assert(r > 0); + + pgsz = (size_t) r; + return pgsz; +} + +static int do_execute(char **directories, usec_t timeout, char *argv[]) { + _cleanup_hashmap_free_free_ Hashmap *pids = NULL; + _cleanup_set_free_free_ Set *seen = NULL; + char **directory; + + /* We fork this all off from a child process so that we can + * somewhat cleanly make use of SIGALRM to set a time limit */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + pids = hashmap_new(NULL); + if (!pids) + return log_oom(); + + seen = set_new(&string_hash_ops); + if (!seen) + return log_oom(); + + STRV_FOREACH(directory, directories) { + _cleanup_closedir_ DIR *d; + struct dirent *de; + + d = opendir(*directory); + if (!d) { + if (errno == ENOENT) + continue; + + return log_error_errno(errno, "Failed to open directory %s: %m", *directory); + } + + FOREACH_DIRENT(de, d, break) { + _cleanup_free_ char *path = NULL; + pid_t pid; + int r; + + if (!dirent_is_file(de)) + continue; + + if (set_contains(seen, de->d_name)) { + log_debug("%1$s/%2$s skipped (%2$s was already seen).", *directory, de->d_name); + continue; + } + + r = set_put_strdup(seen, de->d_name); + if (r < 0) + return log_oom(); + + path = strjoin(*directory, "/", de->d_name, NULL); + if (!path) + return log_oom(); + + if (null_or_empty_path(path)) { + log_debug("%s is empty (a mask).", path); + continue; + } + + pid = fork(); + if (pid < 0) { + log_error_errno(errno, "Failed to fork: %m"); + continue; + } else if (pid == 0) { + char *_argv[2]; + + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + if (!argv) { + _argv[0] = path; + _argv[1] = NULL; + argv = _argv; + } else + argv[0] = path; + + execv(path, argv); + return log_error_errno(errno, "Failed to execute %s: %m", path); + } + + log_debug("Spawned %s as " PID_FMT ".", path, pid); + + r = hashmap_put(pids, PID_TO_PTR(pid), path); + if (r < 0) + return log_oom(); + path = NULL; + } + } + + /* Abort execution of this process after the timout. We simply + * rely on SIGALRM as default action terminating the process, + * and turn on alarm(). */ + + if (timeout != USEC_INFINITY) + alarm((timeout + USEC_PER_SEC - 1) / USEC_PER_SEC); + + while (!hashmap_isempty(pids)) { + _cleanup_free_ char *path = NULL; + pid_t pid; + + pid = PTR_TO_PID(hashmap_first_key(pids)); + assert(pid > 0); + + path = hashmap_remove(pids, PID_TO_PTR(pid)); + assert(path); + + wait_for_terminate_and_warn(path, pid, true); + } + + return 0; +} + +void execute_directories(const char* const* directories, usec_t timeout, char *argv[]) { + pid_t executor_pid; + int r; + char *name; + char **dirs = (char**) directories; + + assert(!strv_isempty(dirs)); + + name = basename(dirs[0]); + assert(!isempty(name)); + + /* Executes all binaries in the directories in parallel and waits + * for them to finish. Optionally a timeout is applied. If a file + * with the same name exists in more than one directory, the + * earliest one wins. */ + + executor_pid = fork(); + if (executor_pid < 0) { + log_error_errno(errno, "Failed to fork: %m"); + return; + + } else if (executor_pid == 0) { + r = do_execute(dirs, timeout, argv); + _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); + } + + wait_for_terminate_and_warn(name, executor_pid, true); +} + +bool plymouth_running(void) { + return access("/run/plymouth/pid", F_OK) >= 0; +} + +bool display_is_local(const char *display) { + assert(display); + + return + display[0] == ':' && + display[1] >= '0' && + display[1] <= '9'; +} + +int socket_from_display(const char *display, char **path) { + size_t k; + char *f, *c; + + assert(display); + assert(path); + + if (!display_is_local(display)) + return -EINVAL; + + k = strspn(display+1, "0123456789"); + + f = new(char, strlen("/tmp/.X11-unix/X") + k + 1); + if (!f) + return -ENOMEM; + + c = stpcpy(f, "/tmp/.X11-unix/X"); + memcpy(c, display+1, k); + c[k] = 0; + + *path = f; + + return 0; +} + +int block_get_whole_disk(dev_t d, dev_t *ret) { + char *p, *s; + int r; + unsigned n, m; + + assert(ret); + + /* If it has a queue this is good enough for us */ + if (asprintf(&p, "/sys/dev/block/%u:%u/queue", major(d), minor(d)) < 0) + return -ENOMEM; + + r = access(p, F_OK); + free(p); + + if (r >= 0) { + *ret = d; + return 0; + } + + /* If it is a partition find the originating device */ + if (asprintf(&p, "/sys/dev/block/%u:%u/partition", major(d), minor(d)) < 0) + return -ENOMEM; + + r = access(p, F_OK); + free(p); + + if (r < 0) + return -ENOENT; + + /* Get parent dev_t */ + if (asprintf(&p, "/sys/dev/block/%u:%u/../dev", major(d), minor(d)) < 0) + return -ENOMEM; + + r = read_one_line_file(p, &s); + free(p); + + if (r < 0) + return r; + + r = sscanf(s, "%u:%u", &m, &n); + free(s); + + if (r != 2) + return -EINVAL; + + /* Only return this if it is really good enough for us. */ + if (asprintf(&p, "/sys/dev/block/%u:%u/queue", m, n) < 0) + return -ENOMEM; + + r = access(p, F_OK); + free(p); + + if (r >= 0) { + *ret = makedev(m, n); + return 0; + } + + return -ENOENT; +} + +bool kexec_loaded(void) { + bool loaded = false; + char *s; + + if (read_one_line_file("/sys/kernel/kexec_loaded", &s) >= 0) { + if (s[0] == '1') + loaded = true; + free(s); + } + return loaded; +} + +int prot_from_flags(int flags) { + + switch (flags & O_ACCMODE) { + + case O_RDONLY: + return PROT_READ; + + case O_WRONLY: + return PROT_WRITE; + + case O_RDWR: + return PROT_READ|PROT_WRITE; + + default: + return -EINVAL; + } +} + +int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...) { + bool stdout_is_tty, stderr_is_tty; + pid_t parent_pid, agent_pid; + sigset_t ss, saved_ss; + unsigned n, i; + va_list ap; + char **l; + + assert(pid); + assert(path); + + /* Spawns a temporary TTY agent, making sure it goes away when + * we go away */ + + parent_pid = getpid(); + + /* First we temporarily block all signals, so that the new + * child has them blocked initially. This way, we can be sure + * that SIGTERMs are not lost we might send to the agent. */ + assert_se(sigfillset(&ss) >= 0); + assert_se(sigprocmask(SIG_SETMASK, &ss, &saved_ss) >= 0); + + agent_pid = fork(); + if (agent_pid < 0) { + assert_se(sigprocmask(SIG_SETMASK, &saved_ss, NULL) >= 0); + return -errno; + } + + if (agent_pid != 0) { + assert_se(sigprocmask(SIG_SETMASK, &saved_ss, NULL) >= 0); + *pid = agent_pid; + return 0; + } + + /* In the child: + * + * Make sure the agent goes away when the parent dies */ + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + _exit(EXIT_FAILURE); + + /* Make sure we actually can kill the agent, if we need to, in + * case somebody invoked us from a shell script that trapped + * SIGTERM or so... */ + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + /* Check whether our parent died before we were able + * to set the death signal and unblock the signals */ + if (getppid() != parent_pid) + _exit(EXIT_SUCCESS); + + /* Don't leak fds to the agent */ + close_all_fds(except, n_except); + + stdout_is_tty = isatty(STDOUT_FILENO); + stderr_is_tty = isatty(STDERR_FILENO); + + if (!stdout_is_tty || !stderr_is_tty) { + int fd; + + /* Detach from stdout/stderr. and reopen + * /dev/tty for them. This is important to + * ensure that when systemctl is started via + * popen() or a similar call that expects to + * read EOF we actually do generate EOF and + * not delay this indefinitely by because we + * keep an unused copy of stdin around. */ + fd = open("/dev/tty", O_WRONLY); + if (fd < 0) { + log_error_errno(errno, "Failed to open /dev/tty: %m"); + _exit(EXIT_FAILURE); + } + + if (!stdout_is_tty && dup2(fd, STDOUT_FILENO) < 0) { + log_error_errno(errno, "Failed to dup2 /dev/tty: %m"); + _exit(EXIT_FAILURE); + } + + if (!stderr_is_tty && dup2(fd, STDERR_FILENO) < 0) { + log_error_errno(errno, "Failed to dup2 /dev/tty: %m"); + _exit(EXIT_FAILURE); + } + + if (fd > STDERR_FILENO) + close(fd); + } + + /* Count arguments */ + va_start(ap, path); + for (n = 0; va_arg(ap, char*); n++) + ; + va_end(ap); + + /* Allocate strv */ + l = alloca(sizeof(char *) * (n + 1)); + + /* Fill in arguments */ + va_start(ap, path); + for (i = 0; i <= n; i++) + l[i] = va_arg(ap, char*); + va_end(ap); + + execv(path, l); + _exit(EXIT_FAILURE); +} + +bool in_initrd(void) { + static int saved = -1; + struct statfs s; + + if (saved >= 0) + return saved; + + /* We make two checks here: + * + * 1. the flag file /etc/initrd-release must exist + * 2. the root file system must be a memory file system + * + * The second check is extra paranoia, since misdetecting an + * initrd can have bad bad consequences due the initrd + * emptying when transititioning to the main systemd. + */ + + saved = access("/etc/initrd-release", F_OK) >= 0 && + statfs("/", &s) >= 0 && + is_temporary_fs(&s); + + return saved; +} + +/* hey glibc, APIs with callbacks without a user pointer are so useless */ +void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, + int (*compar) (const void *, const void *, void *), void *arg) { + size_t l, u, idx; + const void *p; + int comparison; + + l = 0; + u = nmemb; + while (l < u) { + idx = (l + u) / 2; + p = (void *)(((const char *) base) + (idx * size)); + comparison = compar(key, p, arg); + if (comparison < 0) + u = idx; + else if (comparison > 0) + l = idx + 1; + else + return (void *)p; + } + return NULL; +} + +int on_ac_power(void) { + bool found_offline = false, found_online = false; + _cleanup_closedir_ DIR *d = NULL; + + d = opendir("/sys/class/power_supply"); + if (!d) + return errno == ENOENT ? true : -errno; + + for (;;) { + struct dirent *de; + _cleanup_close_ int fd = -1, device = -1; + char contents[6]; + ssize_t n; + + errno = 0; + de = readdir(d); + if (!de && errno > 0) + return -errno; + + if (!de) + break; + + if (hidden_or_backup_file(de->d_name)) + continue; + + device = openat(dirfd(d), de->d_name, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (device < 0) { + if (errno == ENOENT || errno == ENOTDIR) + continue; + + return -errno; + } + + fd = openat(device, "type", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + if (errno == ENOENT) + continue; + + return -errno; + } + + n = read(fd, contents, sizeof(contents)); + if (n < 0) + return -errno; + + if (n != 6 || memcmp(contents, "Mains\n", 6)) + continue; + + safe_close(fd); + fd = openat(device, "online", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + if (errno == ENOENT) + continue; + + return -errno; + } + + n = read(fd, contents, sizeof(contents)); + if (n < 0) + return -errno; + + if (n != 2 || contents[1] != '\n') + return -EIO; + + if (contents[0] == '1') { + found_online = true; + break; + } else if (contents[0] == '0') + found_offline = true; + else + return -EIO; + } + + return found_online || !found_offline; +} + +bool id128_is_valid(const char *s) { + size_t i, l; + + l = strlen(s); + if (l == 32) { + + /* Simple formatted 128bit hex string */ + + for (i = 0; i < l; i++) { + char c = s[i]; + + if (!(c >= '0' && c <= '9') && + !(c >= 'a' && c <= 'z') && + !(c >= 'A' && c <= 'Z')) + return false; + } + + } else if (l == 36) { + + /* Formatted UUID */ + + for (i = 0; i < l; i++) { + char c = s[i]; + + if ((i == 8 || i == 13 || i == 18 || i == 23)) { + if (c != '-') + return false; + } else { + if (!(c >= '0' && c <= '9') && + !(c >= 'a' && c <= 'z') && + !(c >= 'A' && c <= 'Z')) + return false; + } + } + + } else + return false; + + return true; +} + +int container_get_leader(const char *machine, pid_t *pid) { + _cleanup_free_ char *s = NULL, *class = NULL; + const char *p; + pid_t leader; + int r; + + assert(machine); + assert(pid); + + if (!machine_name_is_valid(machine)) + return -EINVAL; + + p = strjoina("/run/systemd/machines/", machine); + r = parse_env_file(p, NEWLINE, "LEADER", &s, "CLASS", &class, NULL); + if (r == -ENOENT) + return -EHOSTDOWN; + if (r < 0) + return r; + if (!s) + return -EIO; + + if (!streq_ptr(class, "container")) + return -EIO; + + r = parse_pid(s, &leader); + if (r < 0) + return r; + if (leader <= 1) + return -EIO; + + *pid = leader; + return 0; +} + +int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd) { + _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, netnsfd = -1, usernsfd = -1; + int rfd = -1; + + assert(pid >= 0); + + if (mntns_fd) { + const char *mntns; + + mntns = procfs_file_alloca(pid, "ns/mnt"); + mntnsfd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (mntnsfd < 0) + return -errno; + } + + if (pidns_fd) { + const char *pidns; + + pidns = procfs_file_alloca(pid, "ns/pid"); + pidnsfd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (pidnsfd < 0) + return -errno; + } + + if (netns_fd) { + const char *netns; + + netns = procfs_file_alloca(pid, "ns/net"); + netnsfd = open(netns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (netnsfd < 0) + return -errno; + } + + if (userns_fd) { + const char *userns; + + userns = procfs_file_alloca(pid, "ns/user"); + usernsfd = open(userns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (usernsfd < 0 && errno != ENOENT) + return -errno; + } + + if (root_fd) { + const char *root; + + root = procfs_file_alloca(pid, "root"); + rfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (rfd < 0) + return -errno; + } + + if (pidns_fd) + *pidns_fd = pidnsfd; + + if (mntns_fd) + *mntns_fd = mntnsfd; + + if (netns_fd) + *netns_fd = netnsfd; + + if (userns_fd) + *userns_fd = usernsfd; + + if (root_fd) + *root_fd = rfd; + + pidnsfd = mntnsfd = netnsfd = usernsfd = -1; + + return 0; +} + +int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd) { + if (userns_fd >= 0) { + /* Can't setns to your own userns, since then you could + * escalate from non-root to root in your own namespace, so + * check if namespaces equal before attempting to enter. */ + _cleanup_free_ char *userns_fd_path = NULL; + int r; + if (asprintf(&userns_fd_path, "/proc/self/fd/%d", userns_fd) < 0) + return -ENOMEM; + + r = files_same(userns_fd_path, "/proc/self/ns/user"); + if (r < 0) + return r; + if (r) + userns_fd = -1; + } + + if (pidns_fd >= 0) + if (setns(pidns_fd, CLONE_NEWPID) < 0) + return -errno; + + if (mntns_fd >= 0) + if (setns(mntns_fd, CLONE_NEWNS) < 0) + return -errno; + + if (netns_fd >= 0) + if (setns(netns_fd, CLONE_NEWNET) < 0) + return -errno; + + if (userns_fd >= 0) + if (setns(userns_fd, CLONE_NEWUSER) < 0) + return -errno; + + if (root_fd >= 0) { + if (fchdir(root_fd) < 0) + return -errno; + + if (chroot(".") < 0) + return -errno; + } + + return reset_uid_gid(); +} + +uint64_t physical_memory(void) { + long mem; + + /* We return this as uint64_t in case we are running as 32bit + * process on a 64bit kernel with huge amounts of memory */ + + mem = sysconf(_SC_PHYS_PAGES); + assert(mem > 0); + + return (uint64_t) mem * (uint64_t) page_size(); +} + +int update_reboot_parameter_and_warn(const char *param) { + int r; + + if (isempty(param)) { + if (unlink("/run/systemd/reboot-param") < 0) { + if (errno == ENOENT) + return 0; + + return log_warning_errno(errno, "Failed to unlink reboot parameter file: %m"); + } + + return 0; + } + + RUN_WITH_UMASK(0022) { + r = write_string_file("/run/systemd/reboot-param", param, WRITE_STRING_FILE_CREATE); + if (r < 0) + return log_warning_errno(r, "Failed to write reboot parameter file: %m"); + } + + return 0; +} + +int version(void) { + puts(PACKAGE_STRING "\n" + SYSTEMD_FEATURES); + return 0; +} diff --git a/src/libbasic/util.h b/src/libbasic/util.h new file mode 100644 index 0000000000..1c032c15c9 --- /dev/null +++ b/src/libbasic/util.h @@ -0,0 +1,189 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "formats-util.h" +#include "macro.h" +#include "missing.h" +#include "time-util.h" + +size_t page_size(void) _pure_; +#define PAGE_ALIGN(l) ALIGN_TO((l), page_size()) + +static inline const char* yes_no(bool b) { + return b ? "yes" : "no"; +} + +static inline const char* true_false(bool b) { + return b ? "true" : "false"; +} + +static inline const char* one_zero(bool b) { + return b ? "1" : "0"; +} + +void execute_directories(const char* const* directories, usec_t timeout, char *argv[]); + +bool plymouth_running(void); + +bool display_is_local(const char *display) _pure_; +int socket_from_display(const char *display, char **path); + +int block_get_whole_disk(dev_t d, dev_t *ret); + +#define NULSTR_FOREACH(i, l) \ + for ((i) = (l); (i) && *(i); (i) = strchr((i), 0)+1) + +#define NULSTR_FOREACH_PAIR(i, j, l) \ + for ((i) = (l), (j) = strchr((i), 0)+1; (i) && *(i); (i) = strchr((j), 0)+1, (j) = *(i) ? strchr((i), 0)+1 : (i)) + +extern int saved_argc; +extern char **saved_argv; + +bool kexec_loaded(void); + +int prot_from_flags(int flags) _const_; + +int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...); + +bool in_initrd(void); + +void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, + int (*compar) (const void *, const void *, void *), + void *arg); + +/** + * Normal qsort requires base to be nonnull. Here were require + * that only if nmemb > 0. + */ +static inline void qsort_safe(void *base, size_t nmemb, size_t size, comparison_fn_t compar) { + if (nmemb <= 1) + return; + + assert(base); + qsort(base, nmemb, size, compar); +} + +/** + * Normal memcpy requires src to be nonnull. We do nothing if n is 0. + */ +static inline void memcpy_safe(void *dst, const void *src, size_t n) { + if (n == 0) + return; + assert(src); + memcpy(dst, src, n); +} + +int on_ac_power(void); + +#define memzero(x,l) (memset((x), 0, (l))) +#define zero(x) (memzero(&(x), sizeof(x))) + +static inline void *mempset(void *s, int c, size_t n) { + memset(s, c, n); + return (uint8_t*)s + n; +} + +static inline void _reset_errno_(int *saved_errno) { + errno = *saved_errno; +} + +#define PROTECT_ERRNO _cleanup_(_reset_errno_) __attribute__((unused)) int _saved_errno_ = errno + +static inline int negative_errno(void) { + /* This helper should be used to shut up gcc if you know 'errno' is + * negative. Instead of "return -errno;", use "return negative_errno();" + * It will suppress bogus gcc warnings in case it assumes 'errno' might + * be 0 and thus the caller's error-handling might not be triggered. */ + assert_return(errno > 0, -EINVAL); + return -errno; +} + +static inline unsigned u64log2(uint64_t n) { +#if __SIZEOF_LONG_LONG__ == 8 + return (n > 1) ? (unsigned) __builtin_clzll(n) ^ 63U : 0; +#else +#error "Wut?" +#endif +} + +static inline unsigned u32ctz(uint32_t n) { +#if __SIZEOF_INT__ == 4 + return __builtin_ctz(n); +#else +#error "Wut?" +#endif +} + +static inline unsigned log2i(int x) { + assert(x > 0); + + return __SIZEOF_INT__ * 8 - __builtin_clz(x) - 1; +} + +static inline unsigned log2u(unsigned x) { + assert(x > 0); + + return sizeof(unsigned) * 8 - __builtin_clz(x) - 1; +} + +static inline unsigned log2u_round_up(unsigned x) { + assert(x > 0); + + if (x == 1) + return 0; + + return log2u(x - 1) + 1; +} + +bool id128_is_valid(const char *s) _pure_; + +int container_get_leader(const char *machine, pid_t *pid); + +int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd); +int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd); + +uint64_t physical_memory(void); + +int update_reboot_parameter_and_warn(const char *param); + +int version(void); diff --git a/src/libbasic/verbs.c b/src/libbasic/verbs.c new file mode 100644 index 0000000000..d9cdb38d65 --- /dev/null +++ b/src/libbasic/verbs.c @@ -0,0 +1,101 @@ +/*** + 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 +#include +#include + +#include "log.h" +#include "macro.h" +#include "string-util.h" +#include "verbs.h" +#include "virt.h" + +int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { + const Verb *verb; + const char *name; + unsigned i; + int left; + + assert(verbs); + assert(verbs[0].dispatch); + assert(argc >= 0); + assert(argv); + assert(argc >= optind); + + left = argc - optind; + name = argv[optind]; + + for (i = 0;; i++) { + bool found; + + /* At the end of the list? */ + if (!verbs[i].dispatch) { + if (name) + log_error("Unknown operation %s.", name); + else + log_error("Requires operation parameter."); + return -EINVAL; + } + + if (name) + found = streq(name, verbs[i].verb); + else + found = !!(verbs[i].flags & VERB_DEFAULT); + + if (found) { + verb = &verbs[i]; + break; + } + } + + assert(verb); + + if (!name) + left = 1; + + if (verb->min_args != VERB_ANY && + (unsigned) left < verb->min_args) { + log_error("Too few arguments."); + return -EINVAL; + } + + if (verb->max_args != VERB_ANY && + (unsigned) left > verb->max_args) { + log_error("Too many arguments."); + return -EINVAL; + } + + if ((verb->flags & VERB_NOCHROOT) && running_in_chroot() > 0) { + log_info("Running in chroot, ignoring request."); + return 0; + } + + if (name) + return verb->dispatch(left, argv + optind, userdata); + else { + char* fake[2] = { + (char*) verb->verb, + NULL + }; + + return verb->dispatch(1, fake, userdata); + } +} diff --git a/src/libbasic/verbs.h b/src/libbasic/verbs.h new file mode 100644 index 0000000000..7b5e18510f --- /dev/null +++ b/src/libbasic/verbs.h @@ -0,0 +1,33 @@ +#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 . +***/ + +#define VERB_ANY ((unsigned) -1) +#define VERB_DEFAULT 1U +#define VERB_NOCHROOT 2U + +typedef struct { + const char *verb; + unsigned min_args, max_args; + unsigned flags; + int (* const dispatch)(int argc, char *argv[], void *userdata); +} Verb; + +int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata); diff --git a/src/libbasic/virt.c b/src/libbasic/virt.c new file mode 100644 index 0000000000..dace1f4328 --- /dev/null +++ b/src/libbasic/virt.c @@ -0,0 +1,516 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include +#include +#include + +#include "alloc-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "macro.h" +#include "process-util.h" +#include "stat-util.h" +#include "string-table.h" +#include "string-util.h" +#include "virt.h" + +static int detect_vm_cpuid(void) { + + /* CPUID is an x86 specific interface. */ +#if defined(__i386__) || defined(__x86_64__) + + static const struct { + const char *cpuid; + int id; + } cpuid_vendor_table[] = { + { "XenVMMXenVMM", VIRTUALIZATION_XEN }, + { "KVMKVMKVM", VIRTUALIZATION_KVM }, + /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ + { "VMwareVMware", VIRTUALIZATION_VMWARE }, + /* http://msdn.microsoft.com/en-us/library/ff542428.aspx */ + { "Microsoft Hv", VIRTUALIZATION_MICROSOFT }, + }; + + uint32_t eax, ecx; + bool hypervisor; + + /* http://lwn.net/Articles/301888/ */ + +#if defined (__i386__) +#define REG_a "eax" +#define REG_b "ebx" +#elif defined (__amd64__) +#define REG_a "rax" +#define REG_b "rbx" +#endif + + /* First detect whether there is a hypervisor */ + eax = 1; + __asm__ __volatile__ ( + /* ebx/rbx is being used for PIC! */ + " push %%"REG_b" \n\t" + " cpuid \n\t" + " pop %%"REG_b" \n\t" + + : "=a" (eax), "=c" (ecx) + : "0" (eax) + ); + + hypervisor = !!(ecx & 0x80000000U); + + if (hypervisor) { + union { + uint32_t sig32[3]; + char text[13]; + } sig = {}; + unsigned j; + + /* There is a hypervisor, see what it is */ + eax = 0x40000000U; + __asm__ __volatile__ ( + /* ebx/rbx is being used for PIC! */ + " push %%"REG_b" \n\t" + " cpuid \n\t" + " mov %%ebx, %1 \n\t" + " pop %%"REG_b" \n\t" + + : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2]) + : "0" (eax) + ); + + log_debug("Virtualization found, CPUID=%s", sig.text); + + for (j = 0; j < ELEMENTSOF(cpuid_vendor_table); j ++) + if (streq(sig.text, cpuid_vendor_table[j].cpuid)) + return cpuid_vendor_table[j].id; + + return VIRTUALIZATION_VM_OTHER; + } +#endif + log_debug("No virtualization found in CPUID"); + + return VIRTUALIZATION_NONE; +} + +static int detect_vm_device_tree(void) { +#if defined(__arm__) || defined(__aarch64__) || defined(__powerpc__) || defined(__powerpc64__) + _cleanup_free_ char *hvtype = NULL; + int r; + + r = read_one_line_file("/proc/device-tree/hypervisor/compatible", &hvtype); + if (r == -ENOENT) { + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *dent; + + dir = opendir("/proc/device-tree"); + if (!dir) { + if (errno == ENOENT) { + log_debug_errno(errno, "/proc/device-tree: %m"); + return VIRTUALIZATION_NONE; + } + return -errno; + } + + FOREACH_DIRENT(dent, dir, return -errno) + if (strstr(dent->d_name, "fw-cfg")) { + log_debug("Virtualization QEMU: \"fw-cfg\" present in /proc/device-tree/%s", dent->d_name); + return VIRTUALIZATION_QEMU; + } + + log_debug("No virtualization found in /proc/device-tree/*"); + return VIRTUALIZATION_NONE; + } else if (r < 0) + return r; + + log_debug("Virtualization %s found in /proc/device-tree/hypervisor/compatible", hvtype); + if (streq(hvtype, "linux,kvm")) + return VIRTUALIZATION_KVM; + else if (strstr(hvtype, "xen")) + return VIRTUALIZATION_XEN; + else + return VIRTUALIZATION_VM_OTHER; +#else + log_debug("This platform does not support /proc/device-tree"); + return VIRTUALIZATION_NONE; +#endif +} + +static int detect_vm_dmi(void) { +#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) + + static const char *const dmi_vendors[] = { + "/sys/class/dmi/id/product_name", /* Test this before sys_vendor to detect KVM over QEMU */ + "/sys/class/dmi/id/sys_vendor", + "/sys/class/dmi/id/board_vendor", + "/sys/class/dmi/id/bios_vendor" + }; + + static const struct { + const char *vendor; + int id; + } dmi_vendor_table[] = { + { "KVM", VIRTUALIZATION_KVM }, + { "QEMU", VIRTUALIZATION_QEMU }, + /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ + { "VMware", VIRTUALIZATION_VMWARE }, + { "VMW", VIRTUALIZATION_VMWARE }, + { "innotek GmbH", VIRTUALIZATION_ORACLE }, + { "Xen", VIRTUALIZATION_XEN }, + { "Bochs", VIRTUALIZATION_BOCHS }, + { "Parallels", VIRTUALIZATION_PARALLELS }, + }; + unsigned i; + int r; + + for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) { + _cleanup_free_ char *s = NULL; + unsigned j; + + r = read_one_line_file(dmi_vendors[i], &s); + if (r < 0) { + if (r == -ENOENT) + continue; + + return r; + } + + + + for (j = 0; j < ELEMENTSOF(dmi_vendor_table); j++) + if (startswith(s, dmi_vendor_table[j].vendor)) { + log_debug("Virtualization %s found in DMI (%s)", s, dmi_vendors[i]); + return dmi_vendor_table[j].id; + } + } +#endif + + log_debug("No virtualization found in DMI"); + + return VIRTUALIZATION_NONE; +} + +static int detect_vm_xen(void) { + /* Check for Dom0 will be executed later in detect_vm_xen_dom0 + Thats why we dont check the content of /proc/xen/capabilities here. */ + if (access("/proc/xen/capabilities", F_OK) < 0) { + log_debug("Virtualization XEN not found, /proc/xen/capabilities does not exist"); + return VIRTUALIZATION_NONE; + } + + log_debug("Virtualization XEN found (/proc/xen/capabilities exists)"); + return VIRTUALIZATION_XEN; + +} + +static bool detect_vm_xen_dom0(void) { + _cleanup_free_ char *domcap = NULL; + char *cap, *i; + int r; + + r = read_one_line_file("/proc/xen/capabilities", &domcap); + if (r == -ENOENT) { + log_debug("Virtualization XEN not found, /proc/xen/capabilities does not exist"); + return false; + } + if (r < 0) + return r; + + i = domcap; + while ((cap = strsep(&i, ","))) + if (streq(cap, "control_d")) + break; + if (!cap) { + log_debug("Virtualization XEN DomU found (/proc/xen/capabilites)"); + return false; + } + + log_debug("Virtualization XEN Dom0 ignored (/proc/xen/capabilities)"); + return true; +} + +static int detect_vm_hypervisor(void) { + _cleanup_free_ char *hvtype = NULL; + int r; + + r = read_one_line_file("/sys/hypervisor/type", &hvtype); + if (r == -ENOENT) + return VIRTUALIZATION_NONE; + if (r < 0) + return r; + + log_debug("Virtualization %s found in /sys/hypervisor/type", hvtype); + + if (streq(hvtype, "xen")) + return VIRTUALIZATION_XEN; + else + return VIRTUALIZATION_VM_OTHER; +} + +static int detect_vm_uml(void) { + _cleanup_free_ char *cpuinfo_contents = NULL; + int r; + + /* Detect User-Mode Linux by reading /proc/cpuinfo */ + r = read_full_file("/proc/cpuinfo", &cpuinfo_contents, NULL); + if (r < 0) + return r; + + if (strstr(cpuinfo_contents, "\nvendor_id\t: User Mode Linux\n")) { + log_debug("UML virtualization found in /proc/cpuinfo"); + return VIRTUALIZATION_UML; + } + + log_debug("No virtualization found in /proc/cpuinfo."); + return VIRTUALIZATION_NONE; +} + +static int detect_vm_zvm(void) { + +#if defined(__s390__) + _cleanup_free_ char *t = NULL; + int r; + + r = get_proc_field("/proc/sysinfo", "VM00 Control Program", WHITESPACE, &t); + if (r == -ENOENT) + return VIRTUALIZATION_NONE; + if (r < 0) + return r; + + log_debug("Virtualization %s found in /proc/sysinfo", t); + if (streq(t, "z/VM")) + return VIRTUALIZATION_ZVM; + else + return VIRTUALIZATION_KVM; +#else + log_debug("This platform does not support /proc/sysinfo"); + return VIRTUALIZATION_NONE; +#endif +} + +/* Returns a short identifier for the various VM implementations */ +int detect_vm(void) { + static thread_local int cached_found = _VIRTUALIZATION_INVALID; + int r; + + if (cached_found >= 0) + return cached_found; + + /* We have to use the correct order here: + * Some virtualization technologies do use KVM hypervisor but are + * expected to be detected as something else. So detect DMI first. + * + * An example is Virtualbox since version 5.0, which uses KVM backend. + * Detection via DMI works corretly, the CPU ID would find KVM + * only. */ + r = detect_vm_dmi(); + if (r < 0) + return r; + if (r != VIRTUALIZATION_NONE) + goto finish; + + r = detect_vm_cpuid(); + if (r < 0) + return r; + if (r != VIRTUALIZATION_NONE) + goto finish; + + /* x86 xen will most likely be detected by cpuid. If not (most likely + * because we're not an x86 guest), then we should try the xen capabilities + * file next. If that's not found, then we check for the high-level + * hypervisor sysfs file: + * + * https://bugs.freedesktop.org/show_bug.cgi?id=77271 */ + + r = detect_vm_xen(); + if (r < 0) + return r; + if (r != VIRTUALIZATION_NONE) + goto finish; + + r = detect_vm_hypervisor(); + if (r < 0) + return r; + if (r != VIRTUALIZATION_NONE) + goto finish; + + r = detect_vm_device_tree(); + if (r < 0) + return r; + if (r != VIRTUALIZATION_NONE) + goto finish; + + r = detect_vm_uml(); + if (r < 0) + return r; + if (r != VIRTUALIZATION_NONE) + goto finish; + + r = detect_vm_zvm(); + if (r < 0) + return r; + +finish: + /* x86 xen Dom0 is detected as XEN in hypervisor and maybe others. + * In order to detect the Dom0 as not virtualization we need to + * double-check it */ + if (r == VIRTUALIZATION_XEN && detect_vm_xen_dom0()) + r = VIRTUALIZATION_NONE; + + cached_found = r; + log_debug("Found VM virtualization %s", virtualization_to_string(r)); + return r; +} + +int detect_container(void) { + + static const struct { + const char *value; + int id; + } value_table[] = { + { "lxc", VIRTUALIZATION_LXC }, + { "lxc-libvirt", VIRTUALIZATION_LXC_LIBVIRT }, + { "systemd-nspawn", VIRTUALIZATION_SYSTEMD_NSPAWN }, + { "docker", VIRTUALIZATION_DOCKER }, + { "rkt", VIRTUALIZATION_RKT }, + }; + + static thread_local int cached_found = _VIRTUALIZATION_INVALID; + _cleanup_free_ char *m = NULL; + const char *e = NULL; + unsigned j; + int r; + + if (cached_found >= 0) + return cached_found; + + /* /proc/vz exists in container and outside of the container, + * /proc/bc only outside of the container. */ + if (access("/proc/vz", F_OK) >= 0 && + access("/proc/bc", F_OK) < 0) { + r = VIRTUALIZATION_OPENVZ; + goto finish; + } + + if (getpid() == 1) { + /* If we are PID 1 we can just check our own + * environment variable */ + + e = getenv("container"); + if (isempty(e)) { + r = VIRTUALIZATION_NONE; + goto finish; + } + } else { + + /* Otherwise, PID 1 dropped this information into a + * file in /run. This is better than accessing + * /proc/1/environ, since we don't need CAP_SYS_PTRACE + * for that. */ + + r = read_one_line_file("/run/systemd/container", &m); + if (r == -ENOENT) { + + /* Fallback for cases where PID 1 was not + * systemd (for example, cases where + * init=/bin/sh is used. */ + + r = getenv_for_pid(1, "container", &m); + if (r <= 0) { + + /* If that didn't work, give up, + * assume no container manager. + * + * Note: This means we still cannot + * detect containers if init=/bin/sh + * is passed but privileges dropped, + * as /proc/1/environ is only readable + * with privileges. */ + + r = VIRTUALIZATION_NONE; + goto finish; + } + } + if (r < 0) + return r; + + e = m; + } + + for (j = 0; j < ELEMENTSOF(value_table); j++) + if (streq(e, value_table[j].value)) { + r = value_table[j].id; + goto finish; + } + + r = VIRTUALIZATION_CONTAINER_OTHER; + +finish: + log_debug("Found container virtualization %s", virtualization_to_string(r)); + cached_found = r; + return r; +} + +int detect_virtualization(void) { + int r; + + r = detect_container(); + if (r == 0) + r = detect_vm(); + + return r; +} + +int running_in_chroot(void) { + int ret; + + ret = files_same("/proc/1/root", "/"); + if (ret < 0) + return ret; + + return ret == 0; +} + +static const char *const virtualization_table[_VIRTUALIZATION_MAX] = { + [VIRTUALIZATION_NONE] = "none", + [VIRTUALIZATION_KVM] = "kvm", + [VIRTUALIZATION_QEMU] = "qemu", + [VIRTUALIZATION_BOCHS] = "bochs", + [VIRTUALIZATION_XEN] = "xen", + [VIRTUALIZATION_UML] = "uml", + [VIRTUALIZATION_VMWARE] = "vmware", + [VIRTUALIZATION_ORACLE] = "oracle", + [VIRTUALIZATION_MICROSOFT] = "microsoft", + [VIRTUALIZATION_ZVM] = "zvm", + [VIRTUALIZATION_PARALLELS] = "parallels", + [VIRTUALIZATION_VM_OTHER] = "vm-other", + + [VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn", + [VIRTUALIZATION_LXC_LIBVIRT] = "lxc-libvirt", + [VIRTUALIZATION_LXC] = "lxc", + [VIRTUALIZATION_OPENVZ] = "openvz", + [VIRTUALIZATION_DOCKER] = "docker", + [VIRTUALIZATION_RKT] = "rkt", + [VIRTUALIZATION_CONTAINER_OTHER] = "container-other", +}; + +DEFINE_STRING_TABLE_LOOKUP(virtualization, int); diff --git a/src/libbasic/virt.h b/src/libbasic/virt.h new file mode 100644 index 0000000000..a538f07f6b --- /dev/null +++ b/src/libbasic/virt.h @@ -0,0 +1,72 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 "macro.h" + +enum { + VIRTUALIZATION_NONE = 0, + + VIRTUALIZATION_VM_FIRST, + VIRTUALIZATION_KVM = VIRTUALIZATION_VM_FIRST, + VIRTUALIZATION_QEMU, + VIRTUALIZATION_BOCHS, + VIRTUALIZATION_XEN, + VIRTUALIZATION_UML, + VIRTUALIZATION_VMWARE, + VIRTUALIZATION_ORACLE, + VIRTUALIZATION_MICROSOFT, + VIRTUALIZATION_ZVM, + VIRTUALIZATION_PARALLELS, + VIRTUALIZATION_VM_OTHER, + VIRTUALIZATION_VM_LAST = VIRTUALIZATION_VM_OTHER, + + VIRTUALIZATION_CONTAINER_FIRST, + VIRTUALIZATION_SYSTEMD_NSPAWN = VIRTUALIZATION_CONTAINER_FIRST, + VIRTUALIZATION_LXC_LIBVIRT, + VIRTUALIZATION_LXC, + VIRTUALIZATION_OPENVZ, + VIRTUALIZATION_DOCKER, + VIRTUALIZATION_RKT, + VIRTUALIZATION_CONTAINER_OTHER, + VIRTUALIZATION_CONTAINER_LAST = VIRTUALIZATION_CONTAINER_OTHER, + + _VIRTUALIZATION_MAX, + _VIRTUALIZATION_INVALID = -1 +}; + +static inline bool VIRTUALIZATION_IS_VM(int x) { + return x >= VIRTUALIZATION_VM_FIRST && x <= VIRTUALIZATION_VM_LAST; +} + +static inline bool VIRTUALIZATION_IS_CONTAINER(int x) { + return x >= VIRTUALIZATION_CONTAINER_FIRST && x <= VIRTUALIZATION_CONTAINER_LAST; +} + +int detect_vm(void); +int detect_container(void); +int detect_virtualization(void); + +int running_in_chroot(void); + +const char *virtualization_to_string(int v) _const_; +int virtualization_from_string(const char *s) _pure_; diff --git a/src/libbasic/web-util.c b/src/libbasic/web-util.c new file mode 100644 index 0000000000..595688ed93 --- /dev/null +++ b/src/libbasic/web-util.c @@ -0,0 +1,76 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 "string-util.h" +#include "utf8.h" +#include "web-util.h" + +bool http_etag_is_valid(const char *etag) { + if (isempty(etag)) + return false; + + if (!endswith(etag, "\"")) + return false; + + if (!startswith(etag, "\"") && !startswith(etag, "W/\"")) + return false; + + return true; +} + +bool http_url_is_valid(const char *url) { + const char *p; + + if (isempty(url)) + return false; + + p = startswith(url, "http://"); + if (!p) + p = startswith(url, "https://"); + if (!p) + return false; + + if (isempty(p)) + return false; + + return ascii_is_valid(p); +} + +bool documentation_url_is_valid(const char *url) { + const char *p; + + if (isempty(url)) + return false; + + if (http_url_is_valid(url)) + return true; + + p = startswith(url, "file:/"); + if (!p) + p = startswith(url, "info:"); + if (!p) + p = startswith(url, "man:"); + + if (isempty(p)) + return false; + + return ascii_is_valid(p); +} diff --git a/src/libbasic/web-util.h b/src/libbasic/web-util.h new file mode 100644 index 0000000000..e6bb6b53f5 --- /dev/null +++ b/src/libbasic/web-util.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "macro.h" + +bool http_url_is_valid(const char *url) _pure_; + +bool documentation_url_is_valid(const char *url) _pure_; + +bool http_etag_is_valid(const char *etag); diff --git a/src/libbasic/xattr-util.c b/src/libbasic/xattr-util.c new file mode 100644 index 0000000000..8256899eda --- /dev/null +++ b/src/libbasic/xattr-util.c @@ -0,0 +1,200 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "macro.h" +#include "sparse-endian.h" +#include "stdio-util.h" +#include "time-util.h" +#include "xattr-util.h" + +int getxattr_malloc(const char *path, const char *name, char **value, bool allow_symlink) { + char *v; + size_t l; + ssize_t n; + + assert(path); + assert(name); + assert(value); + + for (l = 100; ; l = (size_t) n + 1) { + v = new0(char, l); + if (!v) + return -ENOMEM; + + if (allow_symlink) + n = lgetxattr(path, name, v, l); + else + n = getxattr(path, name, v, l); + + if (n >= 0 && (size_t) n < l) { + *value = v; + return n; + } + + free(v); + + if (n < 0 && errno != ERANGE) + return -errno; + + if (allow_symlink) + n = lgetxattr(path, name, NULL, 0); + else + n = getxattr(path, name, NULL, 0); + if (n < 0) + return -errno; + } +} + +int fgetxattr_malloc(int fd, const char *name, char **value) { + char *v; + size_t l; + ssize_t n; + + assert(fd >= 0); + assert(name); + assert(value); + + for (l = 100; ; l = (size_t) n + 1) { + v = new0(char, l); + if (!v) + return -ENOMEM; + + n = fgetxattr(fd, name, v, l); + + if (n >= 0 && (size_t) n < l) { + *value = v; + return n; + } + + free(v); + + if (n < 0 && errno != ERANGE) + return -errno; + + n = fgetxattr(fd, name, NULL, 0); + if (n < 0) + return -errno; + } +} + +ssize_t fgetxattrat_fake(int dirfd, const char *filename, const char *attribute, void *value, size_t size, int flags) { + char fn[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; + _cleanup_close_ int fd = -1; + ssize_t l; + + /* The kernel doesn't have a fgetxattrat() command, hence let's emulate one */ + + fd = openat(dirfd, filename, O_CLOEXEC|O_PATH|(flags & AT_SYMLINK_NOFOLLOW ? O_NOFOLLOW : 0)); + if (fd < 0) + return -errno; + + xsprintf(fn, "/proc/self/fd/%i", fd); + + l = getxattr(fn, attribute, value, size); + if (l < 0) + return -errno; + + return l; +} + +static int parse_crtime(le64_t le, usec_t *usec) { + uint64_t u; + + assert(usec); + + u = le64toh(le); + if (u == 0 || u == (uint64_t) -1) + return -EIO; + + *usec = (usec_t) u; + return 0; +} + +int fd_getcrtime(int fd, usec_t *usec) { + le64_t le; + ssize_t n; + + assert(fd >= 0); + assert(usec); + + /* Until Linux gets a real concept of birthtime/creation time, + * let's fake one with xattrs */ + + n = fgetxattr(fd, "user.crtime_usec", &le, sizeof(le)); + if (n < 0) + return -errno; + if (n != sizeof(le)) + return -EIO; + + return parse_crtime(le, usec); +} + +int fd_getcrtime_at(int dirfd, const char *name, usec_t *usec, int flags) { + le64_t le; + ssize_t n; + + n = fgetxattrat_fake(dirfd, name, "user.crtime_usec", &le, sizeof(le), flags); + if (n < 0) + return -errno; + if (n != sizeof(le)) + return -EIO; + + return parse_crtime(le, usec); +} + +int path_getcrtime(const char *p, usec_t *usec) { + le64_t le; + ssize_t n; + + assert(p); + assert(usec); + + n = getxattr(p, "user.crtime_usec", &le, sizeof(le)); + if (n < 0) + return -errno; + if (n != sizeof(le)) + return -EIO; + + return parse_crtime(le, usec); +} + +int fd_setcrtime(int fd, usec_t usec) { + le64_t le; + + assert(fd >= 0); + + if (usec <= 0) + usec = now(CLOCK_REALTIME); + + le = htole64((uint64_t) usec); + if (fsetxattr(fd, "user.crtime_usec", &le, sizeof(le), 0) < 0) + return -errno; + + return 0; +} diff --git a/src/libbasic/xattr-util.h b/src/libbasic/xattr-util.h new file mode 100644 index 0000000000..6fa097bf7e --- /dev/null +++ b/src/libbasic/xattr-util.h @@ -0,0 +1,37 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include + +#include "time-util.h" + +int getxattr_malloc(const char *path, const char *name, char **value, bool allow_symlink); +int fgetxattr_malloc(int fd, const char *name, char **value); + +ssize_t fgetxattrat_fake(int dirfd, const char *filename, const char *attribute, void *value, size_t size, int flags); + +int fd_setcrtime(int fd, usec_t usec); + +int fd_getcrtime(int fd, usec_t *usec); +int path_getcrtime(const char *p, usec_t *usec); +int fd_getcrtime_at(int dirfd, const char *name, usec_t *usec, int flags); diff --git a/src/libbasic/xml.c b/src/libbasic/xml.c new file mode 100644 index 0000000000..1dbeac7324 --- /dev/null +++ b/src/libbasic/xml.c @@ -0,0 +1,255 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 +#include + +#include "macro.h" +#include "string-util.h" +#include "xml.h" + +enum { + STATE_NULL, + STATE_TEXT, + STATE_TAG, + STATE_ATTRIBUTE, +}; + +static void inc_lines(unsigned *line, const char *s, size_t n) { + const char *p = s; + + if (!line) + return; + + for (;;) { + const char *f; + + f = memchr(p, '\n', n); + if (!f) + return; + + n -= (f - p) + 1; + p = f + 1; + (*line)++; + } +} + +/* We don't actually do real XML here. We only read a simplistic + * subset, that is a bit less strict that XML and lacks all the more + * complex features, like entities, or namespaces. However, we do + * support some HTML5-like simplifications */ + +int xml_tokenize(const char **p, char **name, void **state, unsigned *line) { + const char *c, *e, *b; + char *ret; + int t; + + assert(p); + assert(*p); + assert(name); + assert(state); + + t = PTR_TO_INT(*state); + c = *p; + + if (t == STATE_NULL) { + if (line) + *line = 1; + t = STATE_TEXT; + } + + for (;;) { + if (*c == 0) + return XML_END; + + switch (t) { + + case STATE_TEXT: { + int x; + + e = strchrnul(c, '<'); + if (e > c) { + /* More text... */ + ret = strndup(c, e - c); + if (!ret) + return -ENOMEM; + + inc_lines(line, c, e - c); + + *name = ret; + *p = e; + *state = INT_TO_PTR(STATE_TEXT); + + return XML_TEXT; + } + + assert(*e == '<'); + b = c + 1; + + if (startswith(b, "!--")) { + /* A comment */ + e = strstr(b + 3, "-->"); + if (!e) + return -EINVAL; + + inc_lines(line, b, e + 3 - b); + + c = e + 3; + continue; + } + + if (*b == '?') { + /* Processing instruction */ + + e = strstr(b + 1, "?>"); + if (!e) + return -EINVAL; + + inc_lines(line, b, e + 2 - b); + + c = e + 2; + continue; + } + + if (*b == '!') { + /* DTD */ + + e = strchr(b + 1, '>'); + if (!e) + return -EINVAL; + + inc_lines(line, b, e + 1 - b); + + c = e + 1; + continue; + } + + if (*b == '/') { + /* A closing tag */ + x = XML_TAG_CLOSE; + b++; + } else + x = XML_TAG_OPEN; + + e = strpbrk(b, WHITESPACE "/>"); + if (!e) + return -EINVAL; + + ret = strndup(b, e - b); + if (!ret) + return -ENOMEM; + + *name = ret; + *p = e; + *state = INT_TO_PTR(STATE_TAG); + + return x; + } + + case STATE_TAG: + + b = c + strspn(c, WHITESPACE); + if (*b == 0) + return -EINVAL; + + inc_lines(line, c, b - c); + + e = b + strcspn(b, WHITESPACE "=/>"); + if (e > b) { + /* An attribute */ + + ret = strndup(b, e - b); + if (!ret) + return -ENOMEM; + + *name = ret; + *p = e; + *state = INT_TO_PTR(STATE_ATTRIBUTE); + + return XML_ATTRIBUTE_NAME; + } + + if (startswith(b, "/>")) { + /* An empty tag */ + + *name = NULL; /* For empty tags we return a NULL name, the caller must be prepared for that */ + *p = b + 2; + *state = INT_TO_PTR(STATE_TEXT); + + return XML_TAG_CLOSE_EMPTY; + } + + if (*b != '>') + return -EINVAL; + + c = b + 1; + t = STATE_TEXT; + continue; + + case STATE_ATTRIBUTE: + + if (*c == '=') { + c++; + + if (*c == '\'' || *c == '\"') { + /* Tag with a quoted value */ + + e = strchr(c+1, *c); + if (!e) + return -EINVAL; + + inc_lines(line, c, e - c); + + ret = strndup(c+1, e - c - 1); + if (!ret) + return -ENOMEM; + + *name = ret; + *p = e + 1; + *state = INT_TO_PTR(STATE_TAG); + + return XML_ATTRIBUTE_VALUE; + + } + + /* Tag with a value without quotes */ + + b = strpbrk(c, WHITESPACE ">"); + if (!b) + b = c; + + ret = strndup(c, b - c); + if (!ret) + return -ENOMEM; + + *name = ret; + *p = b; + *state = INT_TO_PTR(STATE_TAG); + return XML_ATTRIBUTE_VALUE; + } + + t = STATE_TAG; + continue; + } + + } + + assert_not_reached("Bad state"); +} diff --git a/src/libbasic/xml.h b/src/libbasic/xml.h new file mode 100644 index 0000000000..41cb69f0dc --- /dev/null +++ b/src/libbasic/xml.h @@ -0,0 +1,32 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +enum { + XML_END, + XML_TEXT, + XML_TAG_OPEN, + XML_TAG_CLOSE, + XML_TAG_CLOSE_EMPTY, + XML_ATTRIBUTE_NAME, + XML_ATTRIBUTE_VALUE, +}; + +int xml_tokenize(const char **p, char **name, void **state, unsigned *line); diff --git a/src/libcore/.gitignore b/src/libcore/.gitignore new file mode 100644 index 0000000000..465b4fcc20 --- /dev/null +++ b/src/libcore/.gitignore @@ -0,0 +1,3 @@ +/macros.systemd +/triggers.systemd +/systemd.pc diff --git a/src/libcore/Makefile b/src/libcore/Makefile new file mode 100644 index 0000000000..f40a115042 --- /dev/null +++ b/src/libcore/Makefile @@ -0,0 +1,170 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +noinst_LTLIBRARIES += \ + libcore.la + +libcore_la_SOURCES = \ + src/core/unit.c \ + src/core/unit.h \ + src/core/unit-printf.c \ + src/core/unit-printf.h \ + src/core/job.c \ + src/core/job.h \ + src/core/manager.c \ + src/core/manager.h \ + src/core/transaction.c \ + src/core/transaction.h \ + src/core/load-fragment.c \ + src/core/load-fragment.h \ + src/core/service.c \ + src/core/service.h \ + src/core/socket.c \ + src/core/socket.h \ + src/core/busname.c \ + src/core/busname.h \ + src/core/bus-policy.c \ + src/core/bus-policy.h \ + src/core/target.c \ + src/core/target.h \ + src/core/device.c \ + src/core/device.h \ + src/core/mount.c \ + src/core/mount.h \ + src/core/automount.c \ + src/core/automount.h \ + src/core/swap.c \ + src/core/swap.h \ + src/core/timer.c \ + src/core/timer.h \ + src/core/path.c \ + src/core/path.h \ + src/core/slice.c \ + src/core/slice.h \ + src/core/scope.c \ + src/core/scope.h \ + src/core/load-dropin.c \ + src/core/load-dropin.h \ + src/core/execute.c \ + src/core/execute.h \ + src/core/kill.c \ + src/core/kill.h \ + src/core/dbus.c \ + src/core/dbus.h \ + src/core/dbus-manager.c \ + src/core/dbus-manager.h \ + src/core/dbus-unit.c \ + src/core/dbus-unit.h \ + src/core/dbus-job.c \ + src/core/dbus-job.h \ + src/core/dbus-service.c \ + src/core/dbus-service.h \ + src/core/dbus-socket.c \ + src/core/dbus-socket.h \ + src/core/dbus-busname.c \ + src/core/dbus-busname.h \ + src/core/dbus-target.c \ + src/core/dbus-target.h \ + src/core/dbus-device.c \ + src/core/dbus-device.h \ + src/core/dbus-mount.c \ + src/core/dbus-mount.h \ + src/core/dbus-automount.c \ + src/core/dbus-automount.h \ + src/core/dbus-swap.c \ + src/core/dbus-swap.h \ + src/core/dbus-timer.c \ + src/core/dbus-timer.h \ + src/core/dbus-path.c \ + src/core/dbus-path.h \ + src/core/dbus-slice.c \ + src/core/dbus-slice.h \ + src/core/dbus-scope.c \ + src/core/dbus-scope.h \ + src/core/dbus-execute.c \ + src/core/dbus-execute.h \ + src/core/dbus-kill.c \ + src/core/dbus-kill.h \ + src/core/dbus-cgroup.c \ + src/core/dbus-cgroup.h \ + src/core/cgroup.c \ + src/core/cgroup.h \ + src/core/selinux-access.c \ + src/core/selinux-access.h \ + src/core/selinux-setup.c \ + src/core/selinux-setup.h \ + src/core/smack-setup.c \ + src/core/smack-setup.h \ + src/core/ima-setup.c \ + src/core/ima-setup.h \ + src/core/locale-setup.h \ + src/core/locale-setup.c \ + src/core/hostname-setup.c \ + src/core/hostname-setup.h \ + src/core/machine-id-setup.c \ + src/core/machine-id-setup.h \ + src/core/mount-setup.c \ + src/core/mount-setup.h \ + src/core/kmod-setup.c \ + src/core/kmod-setup.h \ + src/core/loopback-setup.h \ + src/core/loopback-setup.c \ + src/core/namespace.c \ + src/core/namespace.h \ + src/core/killall.h \ + src/core/killall.c \ + src/core/audit-fd.c \ + src/core/audit-fd.h \ + src/core/show-status.c \ + src/core/show-status.h \ + src/core/failure-action.c \ + src/core/failure-action.h + +nodist_libcore_la_SOURCES = \ + src/core/load-fragment-gperf.c \ + src/core/load-fragment-gperf-nulstr.c + +libcore_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(PAM_CFLAGS) \ + $(AUDIT_CFLAGS) \ + $(KMOD_CFLAGS) \ + $(APPARMOR_CFLAGS) \ + $(MOUNT_CFLAGS) \ + $(SECCOMP_CFLAGS) + +libcore_la_LIBADD = \ + libshared.la \ + $(PAM_LIBS) \ + $(AUDIT_LIBS) \ + $(KMOD_LIBS) \ + $(APPARMOR_LIBS) \ + $(MOUNT_LIBS) + +$(outdir)/load-fragment-gperf-nulstr.c: src/core/load-fragment-gperf.gperf + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)$(AWK) 'BEGIN{ keywords=0 ; FS="," ; print "extern const char load_fragment_gperf_nulstr[];" ; print "const char load_fragment_gperf_nulstr[] ="} ; keyword==1 { print "\"" $$1 "\\0\"" } ; /%%/ { keyword=1} ; END { print ";" }' < $< > $@ + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libcore/audit-fd.c b/src/libcore/audit-fd.c new file mode 100644 index 0000000000..76afe3fe15 --- /dev/null +++ b/src/libcore/audit-fd.c @@ -0,0 +1,73 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 "audit-fd.h" + +#ifdef HAVE_AUDIT + +#include +#include + +#include "fd-util.h" +#include "log.h" +#include "util.h" + +static bool initialized = false; +static int audit_fd; + +int get_audit_fd(void) { + + if (!initialized) { + audit_fd = audit_open(); + + if (audit_fd < 0) { + if (errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT) + log_error_errno(errno, "Failed to connect to audit log: %m"); + + audit_fd = errno ? -errno : -EINVAL; + } + + initialized = true; + } + + return audit_fd; +} + +void close_audit_fd(void) { + + if (initialized && audit_fd >= 0) + safe_close(audit_fd); + + initialized = true; + audit_fd = -ECONNRESET; +} + +#else + +int get_audit_fd(void) { + return -EAFNOSUPPORT; +} + +void close_audit_fd(void) { +} + +#endif diff --git a/src/libcore/audit-fd.h b/src/libcore/audit-fd.h new file mode 100644 index 0000000000..0eccb59210 --- /dev/null +++ b/src/libcore/audit-fd.h @@ -0,0 +1,23 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +int get_audit_fd(void); +void close_audit_fd(void); diff --git a/src/libcore/automount.c b/src/libcore/automount.c new file mode 100644 index 0000000000..f06d837e30 --- /dev/null +++ b/src/libcore/automount.c @@ -0,0 +1,1108 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "async.h" +#include "automount.h" +#include "bus-error.h" +#include "bus-util.h" +#include "dbus-automount.h" +#include "fd-util.h" +#include "formats-util.h" +#include "io-util.h" +#include "label.h" +#include "mkdir.h" +#include "mount-util.h" +#include "mount.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "special.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" +#include "unit-name.h" +#include "unit.h" + +static const UnitActiveState state_translation_table[_AUTOMOUNT_STATE_MAX] = { + [AUTOMOUNT_DEAD] = UNIT_INACTIVE, + [AUTOMOUNT_WAITING] = UNIT_ACTIVE, + [AUTOMOUNT_RUNNING] = UNIT_ACTIVE, + [AUTOMOUNT_FAILED] = UNIT_FAILED +}; + +struct expire_data { + int dev_autofs_fd; + int ioctl_fd; +}; + +static inline void expire_data_free(struct expire_data *data) { + if (!data) + return; + + safe_close(data->dev_autofs_fd); + safe_close(data->ioctl_fd); + free(data); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct expire_data*, expire_data_free); + +static int open_dev_autofs(Manager *m); +static int automount_dispatch_io(sd_event_source *s, int fd, uint32_t events, void *userdata); +static int automount_start_expire(Automount *a); +static void automount_stop_expire(Automount *a); +static int automount_send_ready(Automount *a, Set *tokens, int status); + +static void automount_init(Unit *u) { + Automount *a = AUTOMOUNT(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + a->pipe_fd = -1; + a->directory_mode = 0755; + UNIT(a)->ignore_on_isolate = true; +} + +static void unmount_autofs(Automount *a) { + int r; + + assert(a); + + if (a->pipe_fd < 0) + return; + + automount_send_ready(a, a->tokens, -EHOSTDOWN); + automount_send_ready(a, a->expire_tokens, -EHOSTDOWN); + + a->pipe_event_source = sd_event_source_unref(a->pipe_event_source); + a->pipe_fd = safe_close(a->pipe_fd); + + /* If we reload/reexecute things we keep the mount point + * around */ + if (a->where && + (UNIT(a)->manager->exit_code != MANAGER_RELOAD && + UNIT(a)->manager->exit_code != MANAGER_REEXECUTE)) { + r = repeat_unmount(a->where, MNT_DETACH); + if (r < 0) + log_error_errno(r, "Failed to unmount: %m"); + } +} + +static void automount_done(Unit *u) { + Automount *a = AUTOMOUNT(u); + + assert(a); + + unmount_autofs(a); + + a->where = mfree(a->where); + + a->tokens = set_free(a->tokens); + a->expire_tokens = set_free(a->expire_tokens); + + a->expire_event_source = sd_event_source_unref(a->expire_event_source); +} + +static int automount_add_mount_links(Automount *a) { + _cleanup_free_ char *parent = NULL; + + assert(a); + + parent = dirname_malloc(a->where); + if (!parent) + return -ENOMEM; + + return unit_require_mounts_for(UNIT(a), parent); +} + +static int automount_add_default_dependencies(Automount *a) { + int r; + + assert(a); + + if (!UNIT(a)->default_dependencies) + return 0; + + if (!MANAGER_IS_SYSTEM(UNIT(a)->manager)) + return 0; + + r = unit_add_two_dependencies_by_name(UNIT(a), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); + if (r < 0) + return r; + + return 0; +} + +static int automount_verify(Automount *a) { + _cleanup_free_ char *e = NULL; + int r; + + assert(a); + + if (UNIT(a)->load_state != UNIT_LOADED) + return 0; + + if (path_equal(a->where, "/")) { + log_unit_error(UNIT(a), "Cannot have an automount unit for the root directory. Refusing."); + return -EINVAL; + } + + r = unit_name_from_path(a->where, ".automount", &e); + if (r < 0) + return log_unit_error(UNIT(a), "Failed to generate unit name from path: %m"); + + if (!unit_has_name(UNIT(a), e)) { + log_unit_error(UNIT(a), "Where= setting doesn't match unit name. Refusing."); + return -EINVAL; + } + + return 0; +} + +static int automount_load(Unit *u) { + Automount *a = AUTOMOUNT(u); + int r; + + assert(u); + assert(u->load_state == UNIT_STUB); + + /* Load a .automount file */ + r = unit_load_fragment_and_dropin_optional(u); + if (r < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + Unit *x; + + if (!a->where) { + r = unit_name_to_path(u->id, &a->where); + if (r < 0) + return r; + } + + path_kill_slashes(a->where); + + r = unit_load_related_unit(u, ".mount", &x); + if (r < 0) + return r; + + r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, x, true); + if (r < 0) + return r; + + r = automount_add_mount_links(a); + if (r < 0) + return r; + + r = automount_add_default_dependencies(a); + if (r < 0) + return r; + } + + return automount_verify(a); +} + +static void automount_set_state(Automount *a, AutomountState state) { + AutomountState old_state; + assert(a); + + old_state = a->state; + a->state = state; + + if (state != AUTOMOUNT_RUNNING) + automount_stop_expire(a); + + if (state != AUTOMOUNT_WAITING && + state != AUTOMOUNT_RUNNING) + unmount_autofs(a); + + if (state != old_state) + log_unit_debug(UNIT(a), "Changed %s -> %s", automount_state_to_string(old_state), automount_state_to_string(state)); + + unit_notify(UNIT(a), state_translation_table[old_state], state_translation_table[state], true); +} + +static int automount_coldplug(Unit *u) { + Automount *a = AUTOMOUNT(u); + int r; + + assert(a); + assert(a->state == AUTOMOUNT_DEAD); + + if (a->deserialized_state != a->state) { + + r = open_dev_autofs(u->manager); + if (r < 0) + return r; + + if (a->deserialized_state == AUTOMOUNT_WAITING || + a->deserialized_state == AUTOMOUNT_RUNNING) { + assert(a->pipe_fd >= 0); + + r = sd_event_add_io(u->manager->event, &a->pipe_event_source, a->pipe_fd, EPOLLIN, automount_dispatch_io, u); + if (r < 0) + return r; + + (void) sd_event_source_set_description(a->pipe_event_source, "automount-io"); + } + + automount_set_state(a, a->deserialized_state); + } + + return 0; +} + +static void automount_dump(Unit *u, FILE *f, const char *prefix) { + char time_string[FORMAT_TIMESPAN_MAX]; + Automount *a = AUTOMOUNT(u); + + assert(a); + + fprintf(f, + "%sAutomount State: %s\n" + "%sResult: %s\n" + "%sWhere: %s\n" + "%sDirectoryMode: %04o\n" + "%sTimeoutIdleUSec: %s\n", + prefix, automount_state_to_string(a->state), + prefix, automount_result_to_string(a->result), + prefix, a->where, + prefix, a->directory_mode, + prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, a->timeout_idle_usec, USEC_PER_SEC)); +} + +static void automount_enter_dead(Automount *a, AutomountResult f) { + assert(a); + + if (f != AUTOMOUNT_SUCCESS) + a->result = f; + + automount_set_state(a, a->result != AUTOMOUNT_SUCCESS ? AUTOMOUNT_FAILED : AUTOMOUNT_DEAD); +} + +static int open_dev_autofs(Manager *m) { + struct autofs_dev_ioctl param; + + assert(m); + + if (m->dev_autofs_fd >= 0) + return m->dev_autofs_fd; + + label_fix("/dev/autofs", false, false); + + m->dev_autofs_fd = open("/dev/autofs", O_CLOEXEC|O_RDONLY); + if (m->dev_autofs_fd < 0) + return log_error_errno(errno, "Failed to open /dev/autofs: %m"); + + init_autofs_dev_ioctl(¶m); + if (ioctl(m->dev_autofs_fd, AUTOFS_DEV_IOCTL_VERSION, ¶m) < 0) { + m->dev_autofs_fd = safe_close(m->dev_autofs_fd); + return -errno; + } + + log_debug("Autofs kernel version %i.%i", param.ver_major, param.ver_minor); + + return m->dev_autofs_fd; +} + +static int open_ioctl_fd(int dev_autofs_fd, const char *where, dev_t devid) { + struct autofs_dev_ioctl *param; + size_t l; + + assert(dev_autofs_fd >= 0); + assert(where); + + l = sizeof(struct autofs_dev_ioctl) + strlen(where) + 1; + param = alloca(l); + + init_autofs_dev_ioctl(param); + param->size = l; + param->ioctlfd = -1; + param->openmount.devid = devid; + strcpy(param->path, where); + + if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_OPENMOUNT, param) < 0) + return -errno; + + if (param->ioctlfd < 0) + return -EIO; + + (void) fd_cloexec(param->ioctlfd, true); + return param->ioctlfd; +} + +static int autofs_protocol(int dev_autofs_fd, int ioctl_fd) { + uint32_t major, minor; + struct autofs_dev_ioctl param; + + assert(dev_autofs_fd >= 0); + assert(ioctl_fd >= 0); + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctl_fd; + + if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_PROTOVER, ¶m) < 0) + return -errno; + + major = param.protover.version; + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctl_fd; + + if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_PROTOSUBVER, ¶m) < 0) + return -errno; + + minor = param.protosubver.sub_version; + + log_debug("Autofs protocol version %i.%i", major, minor); + return 0; +} + +static int autofs_set_timeout(int dev_autofs_fd, int ioctl_fd, usec_t usec) { + struct autofs_dev_ioctl param; + + assert(dev_autofs_fd >= 0); + assert(ioctl_fd >= 0); + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctl_fd; + + /* Convert to seconds, rounding up. */ + param.timeout.timeout = (usec + USEC_PER_SEC - 1) / USEC_PER_SEC; + + if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_TIMEOUT, ¶m) < 0) + return -errno; + + return 0; +} + +static int autofs_send_ready(int dev_autofs_fd, int ioctl_fd, uint32_t token, int status) { + struct autofs_dev_ioctl param; + + assert(dev_autofs_fd >= 0); + assert(ioctl_fd >= 0); + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctl_fd; + + if (status != 0) { + param.fail.token = token; + param.fail.status = status; + } else + param.ready.token = token; + + if (ioctl(dev_autofs_fd, status ? AUTOFS_DEV_IOCTL_FAIL : AUTOFS_DEV_IOCTL_READY, ¶m) < 0) + return -errno; + + return 0; +} + +static int automount_send_ready(Automount *a, Set *tokens, int status) { + _cleanup_close_ int ioctl_fd = -1; + unsigned token; + int r; + + assert(a); + assert(status <= 0); + + if (set_isempty(tokens)) + return 0; + + ioctl_fd = open_ioctl_fd(UNIT(a)->manager->dev_autofs_fd, a->where, a->dev_id); + if (ioctl_fd < 0) + return ioctl_fd; + + if (status != 0) + log_unit_debug_errno(UNIT(a), status, "Sending failure: %m"); + else + log_unit_debug(UNIT(a), "Sending success."); + + r = 0; + + /* Autofs thankfully does not hand out 0 as a token */ + while ((token = PTR_TO_UINT(set_steal_first(tokens)))) { + int k; + + /* Autofs fun fact II: + * + * if you pass a positive status code here, the kernel will + * freeze! Yay! */ + + k = autofs_send_ready(UNIT(a)->manager->dev_autofs_fd, + ioctl_fd, + token, + status); + if (k < 0) + r = k; + } + + return r; +} + +static void automount_trigger_notify(Unit *u, Unit *other) { + Automount *a = AUTOMOUNT(u); + int r; + + assert(a); + assert(other); + + /* Filter out invocations with bogus state */ + if (other->load_state != UNIT_LOADED || other->type != UNIT_MOUNT) + return; + + /* Don't propagate state changes from the mount if we are already down */ + if (!IN_SET(a->state, AUTOMOUNT_WAITING, AUTOMOUNT_RUNNING)) + return; + + /* Propagate start limit hit state */ + if (other->start_limit_hit) { + automount_enter_dead(a, AUTOMOUNT_FAILURE_MOUNT_START_LIMIT_HIT); + return; + } + + /* Don't propagate anything if there's still a job queued */ + if (other->job) + return; + + /* The mount is successfully established */ + if (IN_SET(MOUNT(other)->state, MOUNT_MOUNTED, MOUNT_REMOUNTING)) { + (void) automount_send_ready(a, a->tokens, 0); + + r = automount_start_expire(a); + if (r < 0) + log_unit_warning_errno(UNIT(a), r, "Failed to start expiration timer, ignoring: %m"); + + automount_set_state(a, AUTOMOUNT_RUNNING); + } + + /* The mount is in some unhappy state now, let's unfreeze any waiting clients */ + if (IN_SET(MOUNT(other)->state, + MOUNT_DEAD, MOUNT_UNMOUNTING, + MOUNT_MOUNTING_SIGTERM, MOUNT_MOUNTING_SIGKILL, + MOUNT_REMOUNTING_SIGTERM, MOUNT_REMOUNTING_SIGKILL, + MOUNT_UNMOUNTING_SIGTERM, MOUNT_UNMOUNTING_SIGKILL, + MOUNT_FAILED)) { + + (void) automount_send_ready(a, a->tokens, -ENODEV); + + automount_set_state(a, AUTOMOUNT_WAITING); + } +} + +static void automount_enter_waiting(Automount *a) { + _cleanup_close_ int ioctl_fd = -1; + int p[2] = { -1, -1 }; + char name[sizeof("systemd-")-1 + DECIMAL_STR_MAX(pid_t) + 1]; + char options[sizeof("fd=,pgrp=,minproto=5,maxproto=5,direct")-1 + + DECIMAL_STR_MAX(int) + DECIMAL_STR_MAX(gid_t) + 1]; + bool mounted = false; + int r, dev_autofs_fd; + struct stat st; + + assert(a); + assert(a->pipe_fd < 0); + assert(a->where); + + set_clear(a->tokens); + + r = unit_fail_if_symlink(UNIT(a), a->where); + if (r < 0) + goto fail; + + (void) mkdir_p_label(a->where, 0555); + + unit_warn_if_dir_nonempty(UNIT(a), a->where); + + dev_autofs_fd = open_dev_autofs(UNIT(a)->manager); + if (dev_autofs_fd < 0) { + r = dev_autofs_fd; + goto fail; + } + + if (pipe2(p, O_NONBLOCK|O_CLOEXEC) < 0) { + r = -errno; + goto fail; + } + + xsprintf(options, "fd=%i,pgrp="PID_FMT",minproto=5,maxproto=5,direct", p[1], getpgrp()); + xsprintf(name, "systemd-"PID_FMT, getpid()); + if (mount(name, a->where, "autofs", 0, options) < 0) { + r = -errno; + goto fail; + } + + mounted = true; + + p[1] = safe_close(p[1]); + + if (stat(a->where, &st) < 0) { + r = -errno; + goto fail; + } + + ioctl_fd = open_ioctl_fd(dev_autofs_fd, a->where, st.st_dev); + if (ioctl_fd < 0) { + r = ioctl_fd; + goto fail; + } + + r = autofs_protocol(dev_autofs_fd, ioctl_fd); + if (r < 0) + goto fail; + + r = autofs_set_timeout(dev_autofs_fd, ioctl_fd, a->timeout_idle_usec); + if (r < 0) + goto fail; + + /* Autofs fun fact: + * + * Unless we close the ioctl fd here, for some weird reason + * the direct mount will not receive events from the + * kernel. */ + + r = sd_event_add_io(UNIT(a)->manager->event, &a->pipe_event_source, p[0], EPOLLIN, automount_dispatch_io, a); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(a->pipe_event_source, "automount-io"); + + a->pipe_fd = p[0]; + a->dev_id = st.st_dev; + + automount_set_state(a, AUTOMOUNT_WAITING); + + return; + +fail: + log_unit_error_errno(UNIT(a), r, "Failed to initialize automounter: %m"); + + safe_close_pair(p); + + if (mounted) { + r = repeat_unmount(a->where, MNT_DETACH); + if (r < 0) + log_error_errno(r, "Failed to unmount, ignoring: %m"); + } + + automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); +} + +static void *expire_thread(void *p) { + struct autofs_dev_ioctl param; + _cleanup_(expire_data_freep) struct expire_data *data = (struct expire_data*)p; + int r; + + assert(data->dev_autofs_fd >= 0); + assert(data->ioctl_fd >= 0); + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = data->ioctl_fd; + + do { + r = ioctl(data->dev_autofs_fd, AUTOFS_DEV_IOCTL_EXPIRE, ¶m); + } while (r >= 0); + + if (errno != EAGAIN) + log_warning_errno(errno, "Failed to expire automount, ignoring: %m"); + + return NULL; +} + +static int automount_dispatch_expire(sd_event_source *source, usec_t usec, void *userdata) { + Automount *a = AUTOMOUNT(userdata); + _cleanup_(expire_data_freep) struct expire_data *data = NULL; + int r; + + assert(a); + assert(source == a->expire_event_source); + + data = new0(struct expire_data, 1); + if (!data) + return log_oom(); + + data->ioctl_fd = -1; + + data->dev_autofs_fd = fcntl(UNIT(a)->manager->dev_autofs_fd, F_DUPFD_CLOEXEC, 3); + if (data->dev_autofs_fd < 0) + return log_unit_error_errno(UNIT(a), errno, "Failed to duplicate autofs fd: %m"); + + data->ioctl_fd = open_ioctl_fd(UNIT(a)->manager->dev_autofs_fd, a->where, a->dev_id); + if (data->ioctl_fd < 0) + return log_unit_error_errno(UNIT(a), data->ioctl_fd, "Couldn't open autofs ioctl fd: %m"); + + r = asynchronous_job(expire_thread, data); + if (r < 0) + return log_unit_error_errno(UNIT(a), r, "Failed to start expire job: %m"); + + data = NULL; + + return automount_start_expire(a); +} + +static int automount_start_expire(Automount *a) { + int r; + usec_t timeout; + + assert(a); + + if (a->timeout_idle_usec == 0) + return 0; + + timeout = now(CLOCK_MONOTONIC) + MAX(a->timeout_idle_usec/3, USEC_PER_SEC); + + if (a->expire_event_source) { + r = sd_event_source_set_time(a->expire_event_source, timeout); + if (r < 0) + return r; + + return sd_event_source_set_enabled(a->expire_event_source, SD_EVENT_ONESHOT); + } + + r = sd_event_add_time( + UNIT(a)->manager->event, + &a->expire_event_source, + CLOCK_MONOTONIC, timeout, 0, + automount_dispatch_expire, a); + if (r < 0) + return r; + + (void) sd_event_source_set_description(a->expire_event_source, "automount-expire"); + + return 0; +} + +static void automount_stop_expire(Automount *a) { + assert(a); + + if (!a->expire_event_source) + return; + + (void) sd_event_source_set_enabled(a->expire_event_source, SD_EVENT_OFF); +} + +static void automount_enter_runnning(Automount *a) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + struct stat st; + int r; + + assert(a); + + /* We don't take mount requests anymore if we are supposed to + * shut down anyway */ + if (unit_stop_pending(UNIT(a))) { + log_unit_debug(UNIT(a), "Suppressing automount request since unit stop is scheduled."); + automount_send_ready(a, a->tokens, -EHOSTDOWN); + automount_send_ready(a, a->expire_tokens, -EHOSTDOWN); + return; + } + + mkdir_p_label(a->where, a->directory_mode); + + /* Before we do anything, let's see if somebody is playing games with us? */ + if (lstat(a->where, &st) < 0) { + log_unit_warning_errno(UNIT(a), errno, "Failed to stat automount point: %m"); + goto fail; + } + + if (!S_ISDIR(st.st_mode) || st.st_dev != a->dev_id) + log_unit_info(UNIT(a), "Automount point already active?"); + else { + Unit *trigger; + + trigger = UNIT_TRIGGER(UNIT(a)); + if (!trigger) { + log_unit_error(UNIT(a), "Unit to trigger vanished."); + goto fail; + } + + r = manager_add_job(UNIT(a)->manager, JOB_START, trigger, JOB_REPLACE, &error, NULL); + if (r < 0) { + log_unit_warning(UNIT(a), "Failed to queue mount startup job: %s", bus_error_message(&error, r)); + goto fail; + } + } + + automount_set_state(a, AUTOMOUNT_RUNNING); + return; + +fail: + automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); +} + +static int automount_start(Unit *u) { + Automount *a = AUTOMOUNT(u); + Unit *trigger; + int r; + + assert(a); + assert(a->state == AUTOMOUNT_DEAD || a->state == AUTOMOUNT_FAILED); + + if (path_is_mount_point(a->where, 0) > 0) { + log_unit_error(u, "Path %s is already a mount point, refusing start.", a->where); + return -EEXIST; + } + + trigger = UNIT_TRIGGER(u); + if (!trigger || trigger->load_state != UNIT_LOADED) { + log_unit_error(u, "Refusing to start, unit to trigger not loaded."); + return -ENOENT; + } + + r = unit_start_limit_test(u); + if (r < 0) { + automount_enter_dead(a, AUTOMOUNT_FAILURE_START_LIMIT_HIT); + return r; + } + + a->result = AUTOMOUNT_SUCCESS; + automount_enter_waiting(a); + return 1; +} + +static int automount_stop(Unit *u) { + Automount *a = AUTOMOUNT(u); + + assert(a); + assert(a->state == AUTOMOUNT_WAITING || a->state == AUTOMOUNT_RUNNING); + + automount_enter_dead(a, AUTOMOUNT_SUCCESS); + return 1; +} + +static int automount_serialize(Unit *u, FILE *f, FDSet *fds) { + Automount *a = AUTOMOUNT(u); + Iterator i; + void *p; + int r; + + assert(a); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", automount_state_to_string(a->state)); + unit_serialize_item(u, f, "result", automount_result_to_string(a->result)); + unit_serialize_item_format(u, f, "dev-id", "%u", (unsigned) a->dev_id); + + SET_FOREACH(p, a->tokens, i) + unit_serialize_item_format(u, f, "token", "%u", PTR_TO_UINT(p)); + SET_FOREACH(p, a->expire_tokens, i) + unit_serialize_item_format(u, f, "expire-token", "%u", PTR_TO_UINT(p)); + + r = unit_serialize_item_fd(u, f, fds, "pipe-fd", a->pipe_fd); + if (r < 0) + return r; + + return 0; +} + +static int automount_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Automount *a = AUTOMOUNT(u); + int r; + + assert(a); + assert(fds); + + if (streq(key, "state")) { + AutomountState state; + + state = automount_state_from_string(value); + if (state < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + a->deserialized_state = state; + } else if (streq(key, "result")) { + AutomountResult f; + + f = automount_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse result value: %s", value); + else if (f != AUTOMOUNT_SUCCESS) + a->result = f; + + } else if (streq(key, "dev-id")) { + unsigned d; + + if (safe_atou(value, &d) < 0) + log_unit_debug(u, "Failed to parse dev-id value: %s", value); + else + a->dev_id = (unsigned) d; + } else if (streq(key, "token")) { + unsigned token; + + if (safe_atou(value, &token) < 0) + log_unit_debug(u, "Failed to parse token value: %s", value); + else { + r = set_ensure_allocated(&a->tokens, NULL); + if (r < 0) { + log_oom(); + return 0; + } + + r = set_put(a->tokens, UINT_TO_PTR(token)); + if (r < 0) + log_unit_error_errno(u, r, "Failed to add token to set: %m"); + } + } else if (streq(key, "expire-token")) { + unsigned token; + + if (safe_atou(value, &token) < 0) + log_unit_debug(u, "Failed to parse token value: %s", value); + else { + r = set_ensure_allocated(&a->expire_tokens, NULL); + if (r < 0) { + log_oom(); + return 0; + } + + r = set_put(a->expire_tokens, UINT_TO_PTR(token)); + if (r < 0) + log_unit_error_errno(u, r, "Failed to add expire token to set: %m"); + } + } else if (streq(key, "pipe-fd")) { + int fd; + + if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse pipe-fd value: %s", value); + else { + safe_close(a->pipe_fd); + a->pipe_fd = fdset_remove(fds, fd); + } + } else + log_unit_debug(u, "Unknown serialization key: %s", key); + + return 0; +} + +static UnitActiveState automount_active_state(Unit *u) { + assert(u); + + return state_translation_table[AUTOMOUNT(u)->state]; +} + +static const char *automount_sub_state_to_string(Unit *u) { + assert(u); + + return automount_state_to_string(AUTOMOUNT(u)->state); +} + +static bool automount_check_gc(Unit *u) { + assert(u); + + if (!UNIT_TRIGGER(u)) + return false; + + return UNIT_VTABLE(UNIT_TRIGGER(u))->check_gc(UNIT_TRIGGER(u)); +} + +static int automount_dispatch_io(sd_event_source *s, int fd, uint32_t events, void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + union autofs_v5_packet_union packet; + Automount *a = AUTOMOUNT(userdata); + struct stat st; + Unit *trigger; + int r; + + assert(a); + assert(fd == a->pipe_fd); + + if (events != EPOLLIN) { + log_unit_error(UNIT(a), "Got invalid poll event %"PRIu32" on pipe (fd=%d)", events, fd); + goto fail; + } + + r = loop_read_exact(a->pipe_fd, &packet, sizeof(packet), true); + if (r < 0) { + log_unit_error_errno(UNIT(a), r, "Invalid read from pipe: %m"); + goto fail; + } + + switch (packet.hdr.type) { + + case autofs_ptype_missing_direct: + + if (packet.v5_packet.pid > 0) { + _cleanup_free_ char *p = NULL; + + get_process_comm(packet.v5_packet.pid, &p); + log_unit_info(UNIT(a), "Got automount request for %s, triggered by %"PRIu32" (%s)", a->where, packet.v5_packet.pid, strna(p)); + } else + log_unit_debug(UNIT(a), "Got direct mount request on %s", a->where); + + r = set_ensure_allocated(&a->tokens, NULL); + if (r < 0) { + log_unit_error(UNIT(a), "Failed to allocate token set."); + goto fail; + } + + r = set_put(a->tokens, UINT_TO_PTR(packet.v5_packet.wait_queue_token)); + if (r < 0) { + log_unit_error_errno(UNIT(a), r, "Failed to remember token: %m"); + goto fail; + } + + automount_enter_runnning(a); + break; + + case autofs_ptype_expire_direct: + log_unit_debug(UNIT(a), "Got direct umount request on %s", a->where); + + automount_stop_expire(a); + + r = set_ensure_allocated(&a->expire_tokens, NULL); + if (r < 0) { + log_unit_error(UNIT(a), "Failed to allocate token set."); + goto fail; + } + + r = set_put(a->expire_tokens, UINT_TO_PTR(packet.v5_packet.wait_queue_token)); + if (r < 0) { + log_unit_error_errno(UNIT(a), r, "Failed to remember token: %m"); + goto fail; + } + + /* Before we do anything, let's see if somebody is playing games with us? */ + if (lstat(a->where, &st) < 0) { + log_unit_warning_errno(UNIT(a), errno, "Failed to stat automount point: %m"); + goto fail; + } + + if (!S_ISDIR(st.st_mode) || st.st_dev == a->dev_id) { + log_unit_info(UNIT(a), "Automount point already unmounted?"); + automount_send_ready(a, a->expire_tokens, 0); + break; + } + + trigger = UNIT_TRIGGER(UNIT(a)); + if (!trigger) { + log_unit_error(UNIT(a), "Unit to trigger vanished."); + goto fail; + } + + r = manager_add_job(UNIT(a)->manager, JOB_STOP, trigger, JOB_REPLACE, &error, NULL); + if (r < 0) { + log_unit_warning(UNIT(a), "Failed to queue umount startup job: %s", bus_error_message(&error, r)); + goto fail; + } + break; + + default: + log_unit_error(UNIT(a), "Received unknown automount request %i", packet.hdr.type); + break; + } + + return 0; + +fail: + automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); + return 0; +} + +static void automount_shutdown(Manager *m) { + assert(m); + + m->dev_autofs_fd = safe_close(m->dev_autofs_fd); +} + +static void automount_reset_failed(Unit *u) { + Automount *a = AUTOMOUNT(u); + + assert(a); + + if (a->state == AUTOMOUNT_FAILED) + automount_set_state(a, AUTOMOUNT_DEAD); + + a->result = AUTOMOUNT_SUCCESS; +} + +static bool automount_supported(void) { + static int supported = -1; + + if (supported < 0) + supported = access("/dev/autofs", F_OK) >= 0; + + return supported; +} + +static const char* const automount_result_table[_AUTOMOUNT_RESULT_MAX] = { + [AUTOMOUNT_SUCCESS] = "success", + [AUTOMOUNT_FAILURE_RESOURCES] = "resources", + [AUTOMOUNT_FAILURE_START_LIMIT_HIT] = "start-limit-hit", + [AUTOMOUNT_FAILURE_MOUNT_START_LIMIT_HIT] = "mount-start-limit-hit", +}; + +DEFINE_STRING_TABLE_LOOKUP(automount_result, AutomountResult); + +const UnitVTable automount_vtable = { + .object_size = sizeof(Automount), + + .sections = + "Unit\0" + "Automount\0" + "Install\0", + + .init = automount_init, + .load = automount_load, + .done = automount_done, + + .coldplug = automount_coldplug, + + .dump = automount_dump, + + .start = automount_start, + .stop = automount_stop, + + .serialize = automount_serialize, + .deserialize_item = automount_deserialize_item, + + .active_state = automount_active_state, + .sub_state_to_string = automount_sub_state_to_string, + + .check_gc = automount_check_gc, + + .trigger_notify = automount_trigger_notify, + + .reset_failed = automount_reset_failed, + + .bus_vtable = bus_automount_vtable, + + .shutdown = automount_shutdown, + .supported = automount_supported, + + .status_message_formats = { + .finished_start_job = { + [JOB_DONE] = "Set up automount %s.", + [JOB_FAILED] = "Failed to set up automount %s.", + }, + .finished_stop_job = { + [JOB_DONE] = "Unset automount %s.", + [JOB_FAILED] = "Failed to unset automount %s.", + }, + }, +}; diff --git a/src/libcore/automount.h b/src/libcore/automount.h new file mode 100644 index 0000000000..76a201178e --- /dev/null +++ b/src/libcore/automount.h @@ -0,0 +1,59 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct Automount Automount; + +#include "unit.h" + +typedef enum AutomountResult { + AUTOMOUNT_SUCCESS, + AUTOMOUNT_FAILURE_RESOURCES, + AUTOMOUNT_FAILURE_START_LIMIT_HIT, + AUTOMOUNT_FAILURE_MOUNT_START_LIMIT_HIT, + _AUTOMOUNT_RESULT_MAX, + _AUTOMOUNT_RESULT_INVALID = -1 +} AutomountResult; + +struct Automount { + Unit meta; + + AutomountState state, deserialized_state; + + char *where; + usec_t timeout_idle_usec; + + int pipe_fd; + sd_event_source *pipe_event_source; + mode_t directory_mode; + dev_t dev_id; + + Set *tokens; + Set *expire_tokens; + + sd_event_source *expire_event_source; + + AutomountResult result; +}; + +extern const UnitVTable automount_vtable; + +const char* automount_result_to_string(AutomountResult i) _const_; +AutomountResult automount_result_from_string(const char *s) _pure_; diff --git a/src/libcore/bus-policy.c b/src/libcore/bus-policy.c new file mode 100644 index 0000000000..4907c268e8 --- /dev/null +++ b/src/libcore/bus-policy.c @@ -0,0 +1,180 @@ +/*** + This file is part of systemd. + + Copyright 2014 Daniel Mack + + 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 "alloc-util.h" +#include "bus-kernel.h" +#include "bus-policy.h" +#include "kdbus.h" +#include "string-table.h" +#include "user-util.h" +#include "util.h" + +int bus_kernel_translate_access(BusPolicyAccess access) { + assert(access >= 0); + assert(access < _BUS_POLICY_ACCESS_MAX); + + switch (access) { + + case BUS_POLICY_ACCESS_SEE: + return KDBUS_POLICY_SEE; + + case BUS_POLICY_ACCESS_TALK: + return KDBUS_POLICY_TALK; + + case BUS_POLICY_ACCESS_OWN: + return KDBUS_POLICY_OWN; + + default: + assert_not_reached("Unknown policy access"); + } +} + +int bus_kernel_translate_policy(const BusNamePolicy *policy, struct kdbus_item *item) { + int r; + + assert(policy); + assert(item); + + switch (policy->type) { + + case BUSNAME_POLICY_TYPE_USER: { + const char *user = policy->name; + uid_t uid; + + r = get_user_creds(&user, &uid, NULL, NULL, NULL); + if (r < 0) + return r; + + item->policy_access.type = KDBUS_POLICY_ACCESS_USER; + item->policy_access.id = uid; + break; + } + + case BUSNAME_POLICY_TYPE_GROUP: { + const char *group = policy->name; + gid_t gid; + + r = get_group_creds(&group, &gid); + if (r < 0) + return r; + + item->policy_access.type = KDBUS_POLICY_ACCESS_GROUP; + item->policy_access.id = gid; + break; + } + + default: + assert_not_reached("Unknown policy type"); + } + + item->policy_access.access = bus_kernel_translate_access(policy->access); + + return 0; +} + +int bus_kernel_make_starter( + int fd, + const char *name, + bool activating, + bool accept_fd, + BusNamePolicy *policy, + BusPolicyAccess world_policy) { + + struct kdbus_cmd_free cmd_free = { .size = sizeof(cmd_free) }; + struct kdbus_cmd_hello *hello; + struct kdbus_item *n; + size_t policy_cnt = 0; + BusNamePolicy *po; + size_t size; + int r; + + assert(fd >= 0); + assert(name); + + LIST_FOREACH(policy, po, policy) + policy_cnt++; + + if (world_policy >= 0) + policy_cnt++; + + size = offsetof(struct kdbus_cmd_hello, items) + + ALIGN8(offsetof(struct kdbus_item, str) + strlen(name) + 1) + + policy_cnt * ALIGN8(offsetof(struct kdbus_item, policy_access) + sizeof(struct kdbus_policy_access)); + + hello = alloca0_align(size, 8); + + n = hello->items; + strcpy(n->str, name); + n->size = offsetof(struct kdbus_item, str) + strlen(n->str) + 1; + n->type = KDBUS_ITEM_NAME; + n = KDBUS_ITEM_NEXT(n); + + LIST_FOREACH(policy, po, policy) { + n->type = KDBUS_ITEM_POLICY_ACCESS; + n->size = offsetof(struct kdbus_item, policy_access) + sizeof(struct kdbus_policy_access); + + r = bus_kernel_translate_policy(po, n); + if (r < 0) + return r; + + n = KDBUS_ITEM_NEXT(n); + } + + if (world_policy >= 0) { + n->type = KDBUS_ITEM_POLICY_ACCESS; + n->size = offsetof(struct kdbus_item, policy_access) + sizeof(struct kdbus_policy_access); + n->policy_access.type = KDBUS_POLICY_ACCESS_WORLD; + n->policy_access.access = bus_kernel_translate_access(world_policy); + } + + hello->size = size; + hello->flags = + (activating ? KDBUS_HELLO_ACTIVATOR : KDBUS_HELLO_POLICY_HOLDER) | + (accept_fd ? KDBUS_HELLO_ACCEPT_FD : 0); + hello->pool_size = KDBUS_POOL_SIZE; + hello->attach_flags_send = _KDBUS_ATTACH_ANY; + hello->attach_flags_recv = _KDBUS_ATTACH_ANY; + + if (ioctl(fd, KDBUS_CMD_HELLO, hello) < 0) { + if (errno == ENOTTY) /* Major API change */ + return -ESOCKTNOSUPPORT; + return -errno; + } + + /* not interested in any output values */ + cmd_free.offset = hello->offset; + (void) ioctl(fd, KDBUS_CMD_FREE, &cmd_free); + + /* The higher 32bit of the bus_flags fields are considered + * 'incompatible flags'. Refuse them all for now. */ + if (hello->bus_flags > 0xFFFFFFFFULL) + return -ESOCKTNOSUPPORT; + + return fd; +} + +static const char* const bus_policy_access_table[_BUS_POLICY_ACCESS_MAX] = { + [BUS_POLICY_ACCESS_SEE] = "see", + [BUS_POLICY_ACCESS_TALK] = "talk", + [BUS_POLICY_ACCESS_OWN] = "own", +}; + +DEFINE_STRING_TABLE_LOOKUP(bus_policy_access, BusPolicyAccess); diff --git a/src/libcore/bus-policy.h b/src/libcore/bus-policy.h new file mode 100644 index 0000000000..5b2c4d5953 --- /dev/null +++ b/src/libcore/bus-policy.h @@ -0,0 +1,64 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Daniel Mack + + 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 "kdbus.h" +#include "list.h" +#include "macro.h" + +typedef struct BusNamePolicy BusNamePolicy; + +typedef enum BusPolicyAccess { + BUS_POLICY_ACCESS_SEE, + BUS_POLICY_ACCESS_TALK, + BUS_POLICY_ACCESS_OWN, + _BUS_POLICY_ACCESS_MAX, + _BUS_POLICY_ACCESS_INVALID = -1 +} BusPolicyAccess; + +typedef enum BusNamePolicyType { + BUSNAME_POLICY_TYPE_USER, + BUSNAME_POLICY_TYPE_GROUP, + _BUSNAME_POLICY_TYPE_MAX, + _BUSNAME_POLICY_TYPE_INVALID = -1 +} BusNamePolicyType; + +struct BusNamePolicy { + BusNamePolicyType type; + BusPolicyAccess access; + + char *name; + + LIST_FIELDS(BusNamePolicy, policy); +}; + +int bus_kernel_translate_access(BusPolicyAccess access); +int bus_kernel_translate_policy(const BusNamePolicy *policy, struct kdbus_item *item); + +const char* bus_policy_access_to_string(BusPolicyAccess i) _const_; +BusPolicyAccess bus_policy_access_from_string(const char *s) _pure_; + +int bus_kernel_make_starter( + int fd, + const char *name, + bool activating, + bool accept_fd, + BusNamePolicy *policy, + BusPolicyAccess world_policy); diff --git a/src/libcore/busname.c b/src/libcore/busname.c new file mode 100644 index 0000000000..f03a95c24e --- /dev/null +++ b/src/libcore/busname.c @@ -0,0 +1,1082 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "alloc-util.h" +#include "bus-internal.h" +#include "bus-kernel.h" +#include "bus-policy.h" +#include "bus-util.h" +#include "busname.h" +#include "dbus-busname.h" +#include "fd-util.h" +#include "formats-util.h" +#include "kdbus.h" +#include "parse-util.h" +#include "process-util.h" +#include "service.h" +#include "signal-util.h" +#include "special.h" +#include "string-table.h" +#include "string-util.h" + +static const UnitActiveState state_translation_table[_BUSNAME_STATE_MAX] = { + [BUSNAME_DEAD] = UNIT_INACTIVE, + [BUSNAME_MAKING] = UNIT_ACTIVATING, + [BUSNAME_REGISTERED] = UNIT_ACTIVE, + [BUSNAME_LISTENING] = UNIT_ACTIVE, + [BUSNAME_RUNNING] = UNIT_ACTIVE, + [BUSNAME_SIGTERM] = UNIT_DEACTIVATING, + [BUSNAME_SIGKILL] = UNIT_DEACTIVATING, + [BUSNAME_FAILED] = UNIT_FAILED +}; + +static int busname_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); +static int busname_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); + +static void busname_init(Unit *u) { + BusName *n = BUSNAME(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + n->starter_fd = -1; + n->accept_fd = true; + n->activating = true; + + n->timeout_usec = u->manager->default_timeout_start_usec; +} + +static void busname_unwatch_control_pid(BusName *n) { + assert(n); + + if (n->control_pid <= 0) + return; + + unit_unwatch_pid(UNIT(n), n->control_pid); + n->control_pid = 0; +} + +static void busname_free_policy(BusName *n) { + BusNamePolicy *p; + + assert(n); + + while ((p = n->policy)) { + LIST_REMOVE(policy, n->policy, p); + + free(p->name); + free(p); + } +} + +static void busname_close_fd(BusName *n) { + assert(n); + + n->starter_event_source = sd_event_source_unref(n->starter_event_source); + n->starter_fd = safe_close(n->starter_fd); +} + +static void busname_done(Unit *u) { + BusName *n = BUSNAME(u); + + assert(n); + + n->name = mfree(n->name); + + busname_free_policy(n); + busname_unwatch_control_pid(n); + busname_close_fd(n); + + unit_ref_unset(&n->service); + + n->timer_event_source = sd_event_source_unref(n->timer_event_source); +} + +static int busname_arm_timer(BusName *n, usec_t usec) { + int r; + + assert(n); + + if (n->timer_event_source) { + r = sd_event_source_set_time(n->timer_event_source, usec); + if (r < 0) + return r; + + return sd_event_source_set_enabled(n->timer_event_source, SD_EVENT_ONESHOT); + } + + if (usec == USEC_INFINITY) + return 0; + + r = sd_event_add_time( + UNIT(n)->manager->event, + &n->timer_event_source, + CLOCK_MONOTONIC, + usec, 0, + busname_dispatch_timer, n); + if (r < 0) + return r; + + (void) sd_event_source_set_description(n->timer_event_source, "busname-timer"); + + return 0; +} + +static int busname_add_default_default_dependencies(BusName *n) { + int r; + + assert(n); + + r = unit_add_dependency_by_name(UNIT(n), UNIT_BEFORE, SPECIAL_BUSNAMES_TARGET, NULL, true); + if (r < 0) + return r; + + if (MANAGER_IS_SYSTEM(UNIT(n)->manager)) { + r = unit_add_two_dependencies_by_name(UNIT(n), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); + if (r < 0) + return r; + } + + return unit_add_two_dependencies_by_name(UNIT(n), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); +} + +static int busname_add_extras(BusName *n) { + Unit *u = UNIT(n); + int r; + + assert(n); + + if (!n->name) { + r = unit_name_to_prefix(u->id, &n->name); + if (r < 0) + return r; + } + + if (!u->description) { + r = unit_set_description(u, n->name); + if (r < 0) + return r; + } + + if (n->activating) { + if (!UNIT_DEREF(n->service)) { + Unit *x; + + r = unit_load_related_unit(u, ".service", &x); + if (r < 0) + return r; + + unit_ref_set(&n->service, x); + } + + r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(n->service), true); + if (r < 0) + return r; + } + + if (u->default_dependencies) { + r = busname_add_default_default_dependencies(n); + if (r < 0) + return r; + } + + return 0; +} + +static int busname_verify(BusName *n) { + char *e; + + assert(n); + + if (UNIT(n)->load_state != UNIT_LOADED) + return 0; + + if (!service_name_is_valid(n->name)) { + log_unit_error(UNIT(n), "Name= setting is not a valid service name Refusing."); + return -EINVAL; + } + + e = strjoina(n->name, ".busname"); + if (!unit_has_name(UNIT(n), e)) { + log_unit_error(UNIT(n), "Name= setting doesn't match unit name. Refusing."); + return -EINVAL; + } + + return 0; +} + +static int busname_load(Unit *u) { + BusName *n = BUSNAME(u); + int r; + + assert(u); + assert(u->load_state == UNIT_STUB); + + r = unit_load_fragment_and_dropin(u); + if (r < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + /* This is a new unit? Then let's add in some extras */ + r = busname_add_extras(n); + if (r < 0) + return r; + } + + return busname_verify(n); +} + +static void busname_dump(Unit *u, FILE *f, const char *prefix) { + BusName *n = BUSNAME(u); + + assert(n); + assert(f); + + fprintf(f, + "%sBus Name State: %s\n" + "%sResult: %s\n" + "%sName: %s\n" + "%sActivating: %s\n" + "%sAccept FD: %s\n", + prefix, busname_state_to_string(n->state), + prefix, busname_result_to_string(n->result), + prefix, n->name, + prefix, yes_no(n->activating), + prefix, yes_no(n->accept_fd)); + + if (n->control_pid > 0) + fprintf(f, + "%sControl PID: "PID_FMT"\n", + prefix, n->control_pid); +} + +static void busname_unwatch_fd(BusName *n) { + int r; + + assert(n); + + if (!n->starter_event_source) + return; + + r = sd_event_source_set_enabled(n->starter_event_source, SD_EVENT_OFF); + if (r < 0) + log_unit_debug_errno(UNIT(n), r, "Failed to disable event source: %m"); +} + +static int busname_watch_fd(BusName *n) { + int r; + + assert(n); + + if (n->starter_fd < 0) + return 0; + + if (n->starter_event_source) { + r = sd_event_source_set_enabled(n->starter_event_source, SD_EVENT_ON); + if (r < 0) + goto fail; + } else { + r = sd_event_add_io(UNIT(n)->manager->event, &n->starter_event_source, n->starter_fd, EPOLLIN, busname_dispatch_io, n); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(n->starter_event_source, "busname-starter"); + } + + return 0; + +fail: + log_unit_warning_errno(UNIT(n), r, "Failed to watch starter fd: %m"); + busname_unwatch_fd(n); + return r; +} + +static int busname_open_fd(BusName *n) { + _cleanup_free_ char *path = NULL; + const char *mode; + + assert(n); + + if (n->starter_fd >= 0) + return 0; + + mode = MANAGER_IS_SYSTEM(UNIT(n)->manager) ? "system" : "user"; + n->starter_fd = bus_kernel_open_bus_fd(mode, &path); + if (n->starter_fd < 0) + return log_unit_warning_errno(UNIT(n), n->starter_fd, "Failed to open %s: %m", path ?: "kdbus"); + + return 0; +} + +static void busname_set_state(BusName *n, BusNameState state) { + BusNameState old_state; + assert(n); + + old_state = n->state; + n->state = state; + + if (!IN_SET(state, BUSNAME_MAKING, BUSNAME_SIGTERM, BUSNAME_SIGKILL)) { + n->timer_event_source = sd_event_source_unref(n->timer_event_source); + busname_unwatch_control_pid(n); + } + + if (state != BUSNAME_LISTENING) + busname_unwatch_fd(n); + + if (!IN_SET(state, BUSNAME_LISTENING, BUSNAME_MAKING, BUSNAME_REGISTERED, BUSNAME_RUNNING)) + busname_close_fd(n); + + if (state != old_state) + log_unit_debug(UNIT(n), "Changed %s -> %s", busname_state_to_string(old_state), busname_state_to_string(state)); + + unit_notify(UNIT(n), state_translation_table[old_state], state_translation_table[state], true); +} + +static int busname_coldplug(Unit *u) { + BusName *n = BUSNAME(u); + int r; + + assert(n); + assert(n->state == BUSNAME_DEAD); + + if (n->deserialized_state == n->state) + return 0; + + if (n->control_pid > 0 && + pid_is_unwaited(n->control_pid) && + IN_SET(n->deserialized_state, BUSNAME_MAKING, BUSNAME_SIGTERM, BUSNAME_SIGKILL)) { + + r = unit_watch_pid(UNIT(n), n->control_pid); + if (r < 0) + return r; + + r = busname_arm_timer(n, usec_add(u->state_change_timestamp.monotonic, n->timeout_usec)); + if (r < 0) + return r; + } + + if (IN_SET(n->deserialized_state, BUSNAME_MAKING, BUSNAME_LISTENING, BUSNAME_REGISTERED, BUSNAME_RUNNING)) { + r = busname_open_fd(n); + if (r < 0) + return r; + } + + if (n->deserialized_state == BUSNAME_LISTENING) { + r = busname_watch_fd(n); + if (r < 0) + return r; + } + + busname_set_state(n, n->deserialized_state); + return 0; +} + +static int busname_make_starter(BusName *n, pid_t *_pid) { + pid_t pid; + int r; + + r = busname_arm_timer(n, usec_add(now(CLOCK_MONOTONIC), n->timeout_usec)); + if (r < 0) + goto fail; + + /* We have to resolve the user/group names out-of-process, + * hence let's fork here. It's messy, but well, what can we + * do? */ + + pid = fork(); + if (pid < 0) + return -errno; + + if (pid == 0) { + int ret; + + (void) default_signals(SIGNALS_CRASH_HANDLER, SIGNALS_IGNORE, -1); + (void) ignore_signals(SIGPIPE, -1); + log_forget_fds(); + + r = bus_kernel_make_starter(n->starter_fd, n->name, n->activating, n->accept_fd, n->policy, n->policy_world); + if (r < 0) { + ret = EXIT_MAKE_STARTER; + goto fail_child; + } + + _exit(0); + + fail_child: + log_open(); + log_error_errno(r, "Failed to create starter connection at step %s: %m", exit_status_to_string(ret, EXIT_STATUS_SYSTEMD)); + + _exit(ret); + } + + r = unit_watch_pid(UNIT(n), pid); + if (r < 0) + goto fail; + + *_pid = pid; + return 0; + +fail: + n->timer_event_source = sd_event_source_unref(n->timer_event_source); + return r; +} + +static void busname_enter_dead(BusName *n, BusNameResult f) { + assert(n); + + if (f != BUSNAME_SUCCESS) + n->result = f; + + busname_set_state(n, n->result != BUSNAME_SUCCESS ? BUSNAME_FAILED : BUSNAME_DEAD); +} + +static void busname_enter_signal(BusName *n, BusNameState state, BusNameResult f) { + KillContext kill_context = {}; + int r; + + assert(n); + + if (f != BUSNAME_SUCCESS) + n->result = f; + + kill_context_init(&kill_context); + + r = unit_kill_context(UNIT(n), + &kill_context, + state != BUSNAME_SIGTERM ? KILL_KILL : KILL_TERMINATE, + -1, + n->control_pid, + false); + if (r < 0) { + log_unit_warning_errno(UNIT(n), r, "Failed to kill control process: %m"); + goto fail; + } + + if (r > 0) { + r = busname_arm_timer(n, usec_add(now(CLOCK_MONOTONIC), n->timeout_usec)); + if (r < 0) { + log_unit_warning_errno(UNIT(n), r, "Failed to arm timer: %m"); + goto fail; + } + + busname_set_state(n, state); + } else if (state == BUSNAME_SIGTERM) + busname_enter_signal(n, BUSNAME_SIGKILL, BUSNAME_SUCCESS); + else + busname_enter_dead(n, BUSNAME_SUCCESS); + + return; + +fail: + busname_enter_dead(n, BUSNAME_FAILURE_RESOURCES); +} + +static void busname_enter_listening(BusName *n) { + int r; + + assert(n); + + if (n->activating) { + r = busname_watch_fd(n); + if (r < 0) { + log_unit_warning_errno(UNIT(n), r, "Failed to watch names: %m"); + goto fail; + } + + busname_set_state(n, BUSNAME_LISTENING); + } else + busname_set_state(n, BUSNAME_REGISTERED); + + return; + +fail: + busname_enter_signal(n, BUSNAME_SIGTERM, BUSNAME_FAILURE_RESOURCES); +} + +static void busname_enter_making(BusName *n) { + int r; + + assert(n); + + r = busname_open_fd(n); + if (r < 0) + goto fail; + + if (n->policy) { + /* If there is a policy, we need to resolve user/group + * names, which we can't do from PID1, hence let's + * fork. */ + busname_unwatch_control_pid(n); + + r = busname_make_starter(n, &n->control_pid); + if (r < 0) { + log_unit_warning_errno(UNIT(n), r, "Failed to fork 'making' task: %m"); + goto fail; + } + + busname_set_state(n, BUSNAME_MAKING); + } else { + /* If there is no policy, we can do everything + * directly from PID 1, hence do so. */ + + r = bus_kernel_make_starter(n->starter_fd, n->name, n->activating, n->accept_fd, NULL, n->policy_world); + if (r < 0) { + log_unit_warning_errno(UNIT(n), r, "Failed to make starter: %m"); + goto fail; + } + + busname_enter_listening(n); + } + + return; + +fail: + busname_enter_dead(n, BUSNAME_FAILURE_RESOURCES); +} + +static void busname_enter_running(BusName *n) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + bool pending = false; + Unit *other; + Iterator i; + int r; + + assert(n); + + if (!n->activating) + return; + + /* We don't take connections anymore if we are supposed to + * shut down anyway */ + + if (unit_stop_pending(UNIT(n))) { + log_unit_debug(UNIT(n), "Suppressing activation request since unit stop is scheduled."); + + /* Flush all queued activation reqeuest by closing and reopening the connection */ + bus_kernel_drop_one(n->starter_fd); + + busname_enter_listening(n); + return; + } + + /* If there's already a start pending don't bother to do + * anything */ + SET_FOREACH(other, UNIT(n)->dependencies[UNIT_TRIGGERS], i) + if (unit_active_or_pending(other)) { + pending = true; + break; + } + + if (!pending) { + if (!UNIT_ISSET(n->service)) { + log_unit_error(UNIT(n), "Service to activate vanished, refusing activation."); + r = -ENOENT; + goto fail; + } + + r = manager_add_job(UNIT(n)->manager, JOB_START, UNIT_DEREF(n->service), JOB_REPLACE, &error, NULL); + if (r < 0) + goto fail; + } + + busname_set_state(n, BUSNAME_RUNNING); + return; + +fail: + log_unit_warning(UNIT(n), "Failed to queue service startup job: %s", bus_error_message(&error, r)); + busname_enter_dead(n, BUSNAME_FAILURE_RESOURCES); +} + +static int busname_start(Unit *u) { + BusName *n = BUSNAME(u); + int r; + + assert(n); + + /* We cannot fulfill this request right now, try again later + * please! */ + if (IN_SET(n->state, BUSNAME_SIGTERM, BUSNAME_SIGKILL)) + return -EAGAIN; + + /* Already on it! */ + if (n->state == BUSNAME_MAKING) + return 0; + + if (n->activating && UNIT_ISSET(n->service)) { + Service *service; + + service = SERVICE(UNIT_DEREF(n->service)); + + if (UNIT(service)->load_state != UNIT_LOADED) { + log_unit_error(u, "Bus service %s not loaded, refusing.", UNIT(service)->id); + return -ENOENT; + } + } + + assert(IN_SET(n->state, BUSNAME_DEAD, BUSNAME_FAILED)); + + r = unit_start_limit_test(u); + if (r < 0) { + busname_enter_dead(n, BUSNAME_FAILURE_START_LIMIT_HIT); + return r; + } + + n->result = BUSNAME_SUCCESS; + busname_enter_making(n); + + return 1; +} + +static int busname_stop(Unit *u) { + BusName *n = BUSNAME(u); + + assert(n); + + /* Already on it */ + if (IN_SET(n->state, BUSNAME_SIGTERM, BUSNAME_SIGKILL)) + return 0; + + /* If there's already something running, we go directly into + * kill mode. */ + + if (n->state == BUSNAME_MAKING) { + busname_enter_signal(n, BUSNAME_SIGTERM, BUSNAME_SUCCESS); + return -EAGAIN; + } + + assert(IN_SET(n->state, BUSNAME_REGISTERED, BUSNAME_LISTENING, BUSNAME_RUNNING)); + + busname_enter_dead(n, BUSNAME_SUCCESS); + return 1; +} + +static int busname_serialize(Unit *u, FILE *f, FDSet *fds) { + BusName *n = BUSNAME(u); + int r; + + assert(n); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", busname_state_to_string(n->state)); + unit_serialize_item(u, f, "result", busname_result_to_string(n->result)); + + if (n->control_pid > 0) + unit_serialize_item_format(u, f, "control-pid", PID_FMT, n->control_pid); + + r = unit_serialize_item_fd(u, f, fds, "starter-fd", n->starter_fd); + if (r < 0) + return r; + + return 0; +} + +static int busname_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + BusName *n = BUSNAME(u); + + assert(n); + assert(key); + assert(value); + + if (streq(key, "state")) { + BusNameState state; + + state = busname_state_from_string(value); + if (state < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + n->deserialized_state = state; + + } else if (streq(key, "result")) { + BusNameResult f; + + f = busname_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse result value: %s", value); + else if (f != BUSNAME_SUCCESS) + n->result = f; + + } else if (streq(key, "control-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_unit_debug(u, "Failed to parse control-pid value: %s", value); + else + n->control_pid = pid; + } else if (streq(key, "starter-fd")) { + int fd; + + if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse starter fd value: %s", value); + else { + safe_close(n->starter_fd); + n->starter_fd = fdset_remove(fds, fd); + } + } else + log_unit_debug(u, "Unknown serialization key: %s", key); + + return 0; +} + +_pure_ static UnitActiveState busname_active_state(Unit *u) { + assert(u); + + return state_translation_table[BUSNAME(u)->state]; +} + +_pure_ static const char *busname_sub_state_to_string(Unit *u) { + assert(u); + + return busname_state_to_string(BUSNAME(u)->state); +} + +static int busname_peek_message(BusName *n) { + struct kdbus_cmd_recv cmd_recv = { + .size = sizeof(cmd_recv), + .flags = KDBUS_RECV_PEEK, + }; + struct kdbus_cmd_free cmd_free = { + .size = sizeof(cmd_free), + }; + const char *comm = NULL; + struct kdbus_item *d; + struct kdbus_msg *k; + size_t start, ps, sz, delta; + void *p = NULL; + pid_t pid = 0; + int r; + + /* Generate a friendly debug log message about which process + * caused triggering of this bus name. This simply peeks the + * metadata of the first queued message and logs it. */ + + assert(n); + + /* Let's shortcut things a bit, if debug logging is turned off + * anyway. */ + + if (log_get_max_level() < LOG_DEBUG) + return 0; + + r = ioctl(n->starter_fd, KDBUS_CMD_RECV, &cmd_recv); + if (r < 0) { + if (errno == EINTR || errno == EAGAIN) + return 0; + + return log_unit_error_errno(UNIT(n), errno, "Failed to query activation message: %m"); + } + + /* We map as late as possible, and unmap imemdiately after + * use. On 32bit address space is scarce and we want to be + * able to handle a lot of activator connections at the same + * time, and hence shouldn't keep the mmap()s around for + * longer than necessary. */ + + ps = page_size(); + start = (cmd_recv.msg.offset / ps) * ps; + delta = cmd_recv.msg.offset - start; + sz = PAGE_ALIGN(delta + cmd_recv.msg.msg_size); + + p = mmap(NULL, sz, PROT_READ, MAP_SHARED, n->starter_fd, start); + if (p == MAP_FAILED) { + r = log_unit_error_errno(UNIT(n), errno, "Failed to map activation message: %m"); + goto finish; + } + + k = (struct kdbus_msg *) ((uint8_t *) p + delta); + KDBUS_ITEM_FOREACH(d, k, items) { + switch (d->type) { + + case KDBUS_ITEM_PIDS: + pid = d->pids.pid; + break; + + case KDBUS_ITEM_PID_COMM: + comm = d->str; + break; + } + } + + if (pid > 0) + log_unit_debug(UNIT(n), "Activation triggered by process " PID_FMT " (%s)", pid, strna(comm)); + + r = 0; + +finish: + if (p) + (void) munmap(p, sz); + + cmd_free.offset = cmd_recv.msg.offset; + if (ioctl(n->starter_fd, KDBUS_CMD_FREE, &cmd_free) < 0) + log_unit_warning(UNIT(n), "Failed to free peeked message, ignoring: %m"); + + return r; +} + +static int busname_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + BusName *n = userdata; + + assert(n); + assert(fd >= 0); + + if (n->state != BUSNAME_LISTENING) + return 0; + + log_unit_debug(UNIT(n), "Activation request"); + + if (revents != EPOLLIN) { + log_unit_error(UNIT(n), "Got unexpected poll event (0x%x) on starter fd.", revents); + goto fail; + } + + busname_peek_message(n); + busname_enter_running(n); + return 0; +fail: + + busname_enter_dead(n, BUSNAME_FAILURE_RESOURCES); + return 0; +} + +static void busname_sigchld_event(Unit *u, pid_t pid, int code, int status) { + BusName *n = BUSNAME(u); + BusNameResult f; + + assert(n); + assert(pid >= 0); + + if (pid != n->control_pid) + return; + + n->control_pid = 0; + + if (is_clean_exit(code, status, NULL)) + f = BUSNAME_SUCCESS; + else if (code == CLD_EXITED) + f = BUSNAME_FAILURE_EXIT_CODE; + else if (code == CLD_KILLED) + f = BUSNAME_FAILURE_SIGNAL; + else if (code == CLD_DUMPED) + f = BUSNAME_FAILURE_CORE_DUMP; + else + assert_not_reached("Unknown sigchld code"); + + log_unit_full(u, f == BUSNAME_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, + "Control process exited, code=%s status=%i", sigchld_code_to_string(code), status); + + if (f != BUSNAME_SUCCESS) + n->result = f; + + switch (n->state) { + + case BUSNAME_MAKING: + if (f == BUSNAME_SUCCESS) + busname_enter_listening(n); + else + busname_enter_signal(n, BUSNAME_SIGTERM, f); + break; + + case BUSNAME_SIGTERM: + case BUSNAME_SIGKILL: + busname_enter_dead(n, f); + break; + + default: + assert_not_reached("Uh, control process died at wrong time."); + } + + /* Notify clients about changed exit status */ + unit_add_to_dbus_queue(u); +} + +static int busname_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { + BusName *n = BUSNAME(userdata); + + assert(n); + assert(n->timer_event_source == source); + + switch (n->state) { + + case BUSNAME_MAKING: + log_unit_warning(UNIT(n), "Making timed out. Terminating."); + busname_enter_signal(n, BUSNAME_SIGTERM, BUSNAME_FAILURE_TIMEOUT); + break; + + case BUSNAME_SIGTERM: + log_unit_warning(UNIT(n), "Stopping timed out. Killing."); + busname_enter_signal(n, BUSNAME_SIGKILL, BUSNAME_FAILURE_TIMEOUT); + break; + + case BUSNAME_SIGKILL: + log_unit_warning(UNIT(n), "Processes still around after SIGKILL. Ignoring."); + busname_enter_dead(n, BUSNAME_FAILURE_TIMEOUT); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } + + return 0; +} + +static void busname_reset_failed(Unit *u) { + BusName *n = BUSNAME(u); + + assert(n); + + if (n->state == BUSNAME_FAILED) + busname_set_state(n, BUSNAME_DEAD); + + n->result = BUSNAME_SUCCESS; +} + +static void busname_trigger_notify(Unit *u, Unit *other) { + BusName *n = BUSNAME(u); + + assert(n); + assert(other); + + if (!IN_SET(n->state, BUSNAME_RUNNING, BUSNAME_LISTENING)) + return; + + if (other->start_limit_hit) { + busname_enter_dead(n, BUSNAME_FAILURE_SERVICE_START_LIMIT_HIT); + return; + } + + if (other->load_state != UNIT_LOADED || other->type != UNIT_SERVICE) + return; + + if (IN_SET(SERVICE(other)->state, + SERVICE_DEAD, SERVICE_FAILED, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, + SERVICE_AUTO_RESTART)) + busname_enter_listening(n); + + if (SERVICE(other)->state == SERVICE_RUNNING) + busname_set_state(n, BUSNAME_RUNNING); +} + +static int busname_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { + return unit_kill_common(u, who, signo, -1, BUSNAME(u)->control_pid, error); +} + +static int busname_get_timeout(Unit *u, usec_t *timeout) { + BusName *n = BUSNAME(u); + usec_t t; + int r; + + if (!n->timer_event_source) + return 0; + + r = sd_event_source_get_time(n->timer_event_source, &t); + if (r < 0) + return r; + if (t == USEC_INFINITY) + return 0; + + *timeout = t; + return 1; +} + +static bool busname_supported(void) { + static int supported = -1; + + if (supported < 0) + supported = is_kdbus_available(); + + return supported; +} + +static int busname_control_pid(Unit *u) { + BusName *n = BUSNAME(u); + + assert(n); + + return n->control_pid; +} + +static const char* const busname_result_table[_BUSNAME_RESULT_MAX] = { + [BUSNAME_SUCCESS] = "success", + [BUSNAME_FAILURE_RESOURCES] = "resources", + [BUSNAME_FAILURE_TIMEOUT] = "timeout", + [BUSNAME_FAILURE_EXIT_CODE] = "exit-code", + [BUSNAME_FAILURE_SIGNAL] = "signal", + [BUSNAME_FAILURE_CORE_DUMP] = "core-dump", + [BUSNAME_FAILURE_START_LIMIT_HIT] = "start-limit-hit", + [BUSNAME_FAILURE_SERVICE_START_LIMIT_HIT] = "service-start-limit-hit", +}; + +DEFINE_STRING_TABLE_LOOKUP(busname_result, BusNameResult); + +const UnitVTable busname_vtable = { + .object_size = sizeof(BusName), + + .sections = + "Unit\0" + "BusName\0" + "Install\0", + .private_section = "BusName", + + .init = busname_init, + .done = busname_done, + .load = busname_load, + + .coldplug = busname_coldplug, + + .dump = busname_dump, + + .start = busname_start, + .stop = busname_stop, + + .kill = busname_kill, + + .get_timeout = busname_get_timeout, + + .serialize = busname_serialize, + .deserialize_item = busname_deserialize_item, + + .active_state = busname_active_state, + .sub_state_to_string = busname_sub_state_to_string, + + .sigchld_event = busname_sigchld_event, + + .trigger_notify = busname_trigger_notify, + + .reset_failed = busname_reset_failed, + + .supported = busname_supported, + + .control_pid = busname_control_pid, + + .bus_vtable = bus_busname_vtable, + + .status_message_formats = { + .finished_start_job = { + [JOB_DONE] = "Listening on %s.", + [JOB_FAILED] = "Failed to listen on %s.", + }, + .finished_stop_job = { + [JOB_DONE] = "Closed %s.", + [JOB_FAILED] = "Failed stopping %s.", + }, + }, +}; diff --git a/src/libcore/busname.h b/src/libcore/busname.h new file mode 100644 index 0000000000..a8562db458 --- /dev/null +++ b/src/libcore/busname.h @@ -0,0 +1,69 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +typedef struct BusName BusName; +typedef struct BusNamePolicy BusNamePolicy; + +#include "unit.h" +#include "bus-policy.h" + +typedef enum BusNameResult { + BUSNAME_SUCCESS, + BUSNAME_FAILURE_RESOURCES, + BUSNAME_FAILURE_TIMEOUT, + BUSNAME_FAILURE_EXIT_CODE, + BUSNAME_FAILURE_SIGNAL, + BUSNAME_FAILURE_CORE_DUMP, + BUSNAME_FAILURE_START_LIMIT_HIT, + BUSNAME_FAILURE_SERVICE_START_LIMIT_HIT, + _BUSNAME_RESULT_MAX, + _BUSNAME_RESULT_INVALID = -1 +} BusNameResult; + +struct BusName { + Unit meta; + + char *name; + int starter_fd; + + bool activating; + bool accept_fd; + + UnitRef service; + + BusNameState state, deserialized_state; + BusNameResult result; + + usec_t timeout_usec; + + sd_event_source *starter_event_source; + sd_event_source *timer_event_source; + + pid_t control_pid; + + LIST_HEAD(BusNamePolicy, policy); + BusPolicyAccess policy_world; +}; + +extern const UnitVTable busname_vtable; + +const char* busname_result_to_string(BusNameResult i) _const_; +BusNameResult busname_result_from_string(const char *s) _pure_; diff --git a/src/libcore/cgroup.c b/src/libcore/cgroup.c new file mode 100644 index 0000000000..0fb63b1bd1 --- /dev/null +++ b/src/libcore/cgroup.c @@ -0,0 +1,1895 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include "alloc-util.h" +#include "cgroup-util.h" +#include "cgroup.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "special.h" +#include "string-table.h" +#include "string-util.h" +#include "stdio-util.h" + +#define CGROUP_CPU_QUOTA_PERIOD_USEC ((usec_t) 100 * USEC_PER_MSEC) + +void cgroup_context_init(CGroupContext *c) { + assert(c); + + /* Initialize everything to the kernel defaults, assuming the + * structure is preinitialized to 0 */ + + c->cpu_shares = CGROUP_CPU_SHARES_INVALID; + c->startup_cpu_shares = CGROUP_CPU_SHARES_INVALID; + c->cpu_quota_per_sec_usec = USEC_INFINITY; + + c->memory_limit = (uint64_t) -1; + + c->io_weight = CGROUP_WEIGHT_INVALID; + c->startup_io_weight = CGROUP_WEIGHT_INVALID; + + c->blockio_weight = CGROUP_BLKIO_WEIGHT_INVALID; + c->startup_blockio_weight = CGROUP_BLKIO_WEIGHT_INVALID; + + c->tasks_max = (uint64_t) -1; +} + +void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a) { + assert(c); + assert(a); + + LIST_REMOVE(device_allow, c->device_allow, a); + free(a->path); + free(a); +} + +void cgroup_context_free_io_device_weight(CGroupContext *c, CGroupIODeviceWeight *w) { + assert(c); + assert(w); + + LIST_REMOVE(device_weights, c->io_device_weights, w); + free(w->path); + free(w); +} + +void cgroup_context_free_io_device_limit(CGroupContext *c, CGroupIODeviceLimit *l) { + assert(c); + assert(l); + + LIST_REMOVE(device_limits, c->io_device_limits, l); + free(l->path); + free(l); +} + +void cgroup_context_free_blockio_device_weight(CGroupContext *c, CGroupBlockIODeviceWeight *w) { + assert(c); + assert(w); + + LIST_REMOVE(device_weights, c->blockio_device_weights, w); + free(w->path); + free(w); +} + +void cgroup_context_free_blockio_device_bandwidth(CGroupContext *c, CGroupBlockIODeviceBandwidth *b) { + assert(c); + assert(b); + + LIST_REMOVE(device_bandwidths, c->blockio_device_bandwidths, b); + free(b->path); + free(b); +} + +void cgroup_context_done(CGroupContext *c) { + assert(c); + + while (c->io_device_weights) + cgroup_context_free_io_device_weight(c, c->io_device_weights); + + while (c->io_device_limits) + cgroup_context_free_io_device_limit(c, c->io_device_limits); + + while (c->blockio_device_weights) + cgroup_context_free_blockio_device_weight(c, c->blockio_device_weights); + + while (c->blockio_device_bandwidths) + cgroup_context_free_blockio_device_bandwidth(c, c->blockio_device_bandwidths); + + while (c->device_allow) + cgroup_context_free_device_allow(c, c->device_allow); +} + +void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { + CGroupIODeviceLimit *il; + CGroupIODeviceWeight *iw; + CGroupBlockIODeviceBandwidth *b; + CGroupBlockIODeviceWeight *w; + CGroupDeviceAllow *a; + char u[FORMAT_TIMESPAN_MAX]; + + assert(c); + assert(f); + + prefix = strempty(prefix); + + fprintf(f, + "%sCPUAccounting=%s\n" + "%sIOAccounting=%s\n" + "%sBlockIOAccounting=%s\n" + "%sMemoryAccounting=%s\n" + "%sTasksAccounting=%s\n" + "%sCPUShares=%" PRIu64 "\n" + "%sStartupCPUShares=%" PRIu64 "\n" + "%sCPUQuotaPerSecSec=%s\n" + "%sIOWeight=%" PRIu64 "\n" + "%sStartupIOWeight=%" PRIu64 "\n" + "%sBlockIOWeight=%" PRIu64 "\n" + "%sStartupBlockIOWeight=%" PRIu64 "\n" + "%sMemoryLimit=%" PRIu64 "\n" + "%sTasksMax=%" PRIu64 "\n" + "%sDevicePolicy=%s\n" + "%sDelegate=%s\n", + prefix, yes_no(c->cpu_accounting), + prefix, yes_no(c->io_accounting), + prefix, yes_no(c->blockio_accounting), + prefix, yes_no(c->memory_accounting), + prefix, yes_no(c->tasks_accounting), + prefix, c->cpu_shares, + prefix, c->startup_cpu_shares, + prefix, format_timespan(u, sizeof(u), c->cpu_quota_per_sec_usec, 1), + prefix, c->io_weight, + prefix, c->startup_io_weight, + prefix, c->blockio_weight, + prefix, c->startup_blockio_weight, + prefix, c->memory_limit, + prefix, c->tasks_max, + prefix, cgroup_device_policy_to_string(c->device_policy), + prefix, yes_no(c->delegate)); + + LIST_FOREACH(device_allow, a, c->device_allow) + fprintf(f, + "%sDeviceAllow=%s %s%s%s\n", + prefix, + a->path, + a->r ? "r" : "", a->w ? "w" : "", a->m ? "m" : ""); + + LIST_FOREACH(device_weights, iw, c->io_device_weights) + fprintf(f, + "%sIODeviceWeight=%s %" PRIu64, + prefix, + iw->path, + iw->weight); + + LIST_FOREACH(device_limits, il, c->io_device_limits) { + char buf[FORMAT_BYTES_MAX]; + CGroupIOLimitType type; + + for (type = 0; type < _CGROUP_IO_LIMIT_TYPE_MAX; type++) + if (il->limits[type] != cgroup_io_limit_defaults[type]) + fprintf(f, + "%s%s=%s %s\n", + prefix, + cgroup_io_limit_type_to_string(type), + il->path, + format_bytes(buf, sizeof(buf), il->limits[type])); + } + + LIST_FOREACH(device_weights, w, c->blockio_device_weights) + fprintf(f, + "%sBlockIODeviceWeight=%s %" PRIu64, + prefix, + w->path, + w->weight); + + LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { + char buf[FORMAT_BYTES_MAX]; + + if (b->rbps != CGROUP_LIMIT_MAX) + fprintf(f, + "%sBlockIOReadBandwidth=%s %s\n", + prefix, + b->path, + format_bytes(buf, sizeof(buf), b->rbps)); + if (b->wbps != CGROUP_LIMIT_MAX) + fprintf(f, + "%sBlockIOWriteBandwidth=%s %s\n", + prefix, + b->path, + format_bytes(buf, sizeof(buf), b->wbps)); + } +} + +static int lookup_block_device(const char *p, dev_t *dev) { + struct stat st; + int r; + + assert(p); + assert(dev); + + r = stat(p, &st); + if (r < 0) + return log_warning_errno(errno, "Couldn't stat device %s: %m", p); + + if (S_ISBLK(st.st_mode)) + *dev = st.st_rdev; + else if (major(st.st_dev) != 0) { + /* If this is not a device node then find the block + * device this file is stored on */ + *dev = st.st_dev; + + /* If this is a partition, try to get the originating + * block device */ + block_get_whole_disk(*dev, dev); + } else { + log_warning("%s is not a block device and file system block device cannot be determined or is not local.", p); + return -ENODEV; + } + + return 0; +} + +static int whitelist_device(const char *path, const char *node, const char *acc) { + char buf[2+DECIMAL_STR_MAX(dev_t)*2+2+4]; + struct stat st; + int r; + + assert(path); + assert(acc); + + if (stat(node, &st) < 0) { + log_warning("Couldn't stat device %s", node); + return -errno; + } + + if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) { + log_warning("%s is not a device.", node); + return -ENODEV; + } + + sprintf(buf, + "%c %u:%u %s", + S_ISCHR(st.st_mode) ? 'c' : 'b', + major(st.st_rdev), minor(st.st_rdev), + acc); + + r = cg_set_attribute("devices", path, "devices.allow", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set devices.allow on %s: %m", path); + + return r; +} + +static int whitelist_major(const char *path, const char *name, char type, const char *acc) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + bool good = false; + int r; + + assert(path); + assert(acc); + assert(type == 'b' || type == 'c'); + + f = fopen("/proc/devices", "re"); + if (!f) + return log_warning_errno(errno, "Cannot open /proc/devices to resolve %s (%c): %m", name, type); + + FOREACH_LINE(line, f, goto fail) { + char buf[2+DECIMAL_STR_MAX(unsigned)+3+4], *p, *w; + unsigned maj; + + truncate_nl(line); + + if (type == 'c' && streq(line, "Character devices:")) { + good = true; + continue; + } + + if (type == 'b' && streq(line, "Block devices:")) { + good = true; + continue; + } + + if (isempty(line)) { + good = false; + continue; + } + + if (!good) + continue; + + p = strstrip(line); + + w = strpbrk(p, WHITESPACE); + if (!w) + continue; + *w = 0; + + r = safe_atou(p, &maj); + if (r < 0) + continue; + if (maj <= 0) + continue; + + w++; + w += strspn(w, WHITESPACE); + + if (fnmatch(name, w, 0) != 0) + continue; + + sprintf(buf, + "%c %u:* %s", + type, + maj, + acc); + + r = cg_set_attribute("devices", path, "devices.allow", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set devices.allow on %s: %m", path); + } + + return 0; + +fail: + log_warning_errno(errno, "Failed to read /proc/devices: %m"); + return -errno; +} + +static bool cgroup_context_has_io_config(CGroupContext *c) { + return c->io_accounting || + c->io_weight != CGROUP_WEIGHT_INVALID || + c->startup_io_weight != CGROUP_WEIGHT_INVALID || + c->io_device_weights || + c->io_device_limits; +} + +static bool cgroup_context_has_blockio_config(CGroupContext *c) { + return c->blockio_accounting || + c->blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID || + c->startup_blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID || + c->blockio_device_weights || + c->blockio_device_bandwidths; +} + +static uint64_t cgroup_context_io_weight(CGroupContext *c, ManagerState state) { + if (IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && + c->startup_io_weight != CGROUP_WEIGHT_INVALID) + return c->startup_io_weight; + else if (c->io_weight != CGROUP_WEIGHT_INVALID) + return c->io_weight; + else + return CGROUP_WEIGHT_DEFAULT; +} + +static uint64_t cgroup_context_blkio_weight(CGroupContext *c, ManagerState state) { + if (IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && + c->startup_blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID) + return c->startup_blockio_weight; + else if (c->blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID) + return c->blockio_weight; + else + return CGROUP_BLKIO_WEIGHT_DEFAULT; +} + +static uint64_t cgroup_weight_blkio_to_io(uint64_t blkio_weight) { + return CLAMP(blkio_weight * CGROUP_WEIGHT_DEFAULT / CGROUP_BLKIO_WEIGHT_DEFAULT, + CGROUP_WEIGHT_MIN, CGROUP_WEIGHT_MAX); +} + +static uint64_t cgroup_weight_io_to_blkio(uint64_t io_weight) { + return CLAMP(io_weight * CGROUP_BLKIO_WEIGHT_DEFAULT / CGROUP_WEIGHT_DEFAULT, + CGROUP_BLKIO_WEIGHT_MIN, CGROUP_BLKIO_WEIGHT_MAX); +} + +static void cgroup_apply_io_device_weight(const char *path, const char *dev_path, uint64_t io_weight) { + char buf[DECIMAL_STR_MAX(dev_t)*2+2+DECIMAL_STR_MAX(uint64_t)+1]; + dev_t dev; + int r; + + r = lookup_block_device(dev_path, &dev); + if (r < 0) + return; + + xsprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), io_weight); + r = cg_set_attribute("io", path, "io.weight", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set io.weight on %s: %m", path); +} + +static void cgroup_apply_blkio_device_weight(const char *path, const char *dev_path, uint64_t blkio_weight) { + char buf[DECIMAL_STR_MAX(dev_t)*2+2+DECIMAL_STR_MAX(uint64_t)+1]; + dev_t dev; + int r; + + r = lookup_block_device(dev_path, &dev); + if (r < 0) + return; + + xsprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), blkio_weight); + r = cg_set_attribute("blkio", path, "blkio.weight_device", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set blkio.weight_device on %s: %m", path); +} + +static unsigned cgroup_apply_io_device_limit(const char *path, const char *dev_path, uint64_t *limits) { + char limit_bufs[_CGROUP_IO_LIMIT_TYPE_MAX][DECIMAL_STR_MAX(uint64_t)]; + char buf[DECIMAL_STR_MAX(dev_t)*2+2+(6+DECIMAL_STR_MAX(uint64_t)+1)*4]; + CGroupIOLimitType type; + dev_t dev; + unsigned n = 0; + int r; + + r = lookup_block_device(dev_path, &dev); + if (r < 0) + return 0; + + for (type = 0; type < _CGROUP_IO_LIMIT_TYPE_MAX; type++) { + if (limits[type] != cgroup_io_limit_defaults[type]) { + xsprintf(limit_bufs[type], "%" PRIu64, limits[type]); + n++; + } else { + xsprintf(limit_bufs[type], "%s", limits[type] == CGROUP_LIMIT_MAX ? "max" : "0"); + } + } + + xsprintf(buf, "%u:%u rbps=%s wbps=%s riops=%s wiops=%s\n", major(dev), minor(dev), + limit_bufs[CGROUP_IO_RBPS_MAX], limit_bufs[CGROUP_IO_WBPS_MAX], + limit_bufs[CGROUP_IO_RIOPS_MAX], limit_bufs[CGROUP_IO_WIOPS_MAX]); + r = cg_set_attribute("io", path, "io.max", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set io.max on %s: %m", path); + return n; +} + +static unsigned cgroup_apply_blkio_device_limit(const char *path, const char *dev_path, uint64_t rbps, uint64_t wbps) { + char buf[DECIMAL_STR_MAX(dev_t)*2+2+DECIMAL_STR_MAX(uint64_t)+1]; + dev_t dev; + unsigned n = 0; + int r; + + r = lookup_block_device(dev_path, &dev); + if (r < 0) + return 0; + + if (rbps != CGROUP_LIMIT_MAX) + n++; + sprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), rbps); + r = cg_set_attribute("blkio", path, "blkio.throttle.read_bps_device", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set blkio.throttle.read_bps_device on %s: %m", path); + + if (wbps != CGROUP_LIMIT_MAX) + n++; + sprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), wbps); + r = cg_set_attribute("blkio", path, "blkio.throttle.write_bps_device", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set blkio.throttle.write_bps_device on %s: %m", path); + + return n; +} + +void cgroup_context_apply(CGroupContext *c, CGroupMask mask, const char *path, ManagerState state) { + bool is_root; + int r; + + assert(c); + assert(path); + + if (mask == 0) + return; + + /* Some cgroup attributes are not supported on the root cgroup, + * hence silently ignore */ + is_root = isempty(path) || path_equal(path, "/"); + if (is_root) + /* Make sure we don't try to display messages with an empty path. */ + path = "/"; + + /* We generally ignore errors caused by read-only mounted + * cgroup trees (assuming we are running in a container then), + * and missing cgroups, i.e. EROFS and ENOENT. */ + + if ((mask & CGROUP_MASK_CPU) && !is_root) { + char buf[MAX(DECIMAL_STR_MAX(uint64_t), DECIMAL_STR_MAX(usec_t)) + 1]; + + sprintf(buf, "%" PRIu64 "\n", + IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && c->startup_cpu_shares != CGROUP_CPU_SHARES_INVALID ? c->startup_cpu_shares : + c->cpu_shares != CGROUP_CPU_SHARES_INVALID ? c->cpu_shares : CGROUP_CPU_SHARES_DEFAULT); + r = cg_set_attribute("cpu", path, "cpu.shares", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set cpu.shares on %s: %m", path); + + sprintf(buf, USEC_FMT "\n", CGROUP_CPU_QUOTA_PERIOD_USEC); + r = cg_set_attribute("cpu", path, "cpu.cfs_period_us", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set cpu.cfs_period_us on %s: %m", path); + + if (c->cpu_quota_per_sec_usec != USEC_INFINITY) { + sprintf(buf, USEC_FMT "\n", c->cpu_quota_per_sec_usec * CGROUP_CPU_QUOTA_PERIOD_USEC / USEC_PER_SEC); + r = cg_set_attribute("cpu", path, "cpu.cfs_quota_us", buf); + } else + r = cg_set_attribute("cpu", path, "cpu.cfs_quota_us", "-1"); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set cpu.cfs_quota_us on %s: %m", path); + } + + if (mask & CGROUP_MASK_IO) { + bool has_io = cgroup_context_has_io_config(c); + bool has_blockio = cgroup_context_has_blockio_config(c); + + if (!is_root) { + char buf[8+DECIMAL_STR_MAX(uint64_t)+1]; + uint64_t weight; + + if (has_io) + weight = cgroup_context_io_weight(c, state); + else if (has_blockio) + weight = cgroup_weight_blkio_to_io(cgroup_context_blkio_weight(c, state)); + else + weight = CGROUP_WEIGHT_DEFAULT; + + xsprintf(buf, "default %" PRIu64 "\n", weight); + r = cg_set_attribute("io", path, "io.weight", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set io.weight on %s: %m", path); + + if (has_io) { + CGroupIODeviceWeight *w; + + /* FIXME: no way to reset this list */ + LIST_FOREACH(device_weights, w, c->io_device_weights) + cgroup_apply_io_device_weight(path, w->path, w->weight); + } else if (has_blockio) { + CGroupBlockIODeviceWeight *w; + + /* FIXME: no way to reset this list */ + LIST_FOREACH(device_weights, w, c->blockio_device_weights) + cgroup_apply_io_device_weight(path, w->path, cgroup_weight_blkio_to_io(w->weight)); + } + } + + /* Apply limits and free ones without config. */ + if (has_io) { + CGroupIODeviceLimit *l, *next; + + LIST_FOREACH_SAFE(device_limits, l, next, c->io_device_limits) { + if (!cgroup_apply_io_device_limit(path, l->path, l->limits)) + cgroup_context_free_io_device_limit(c, l); + } + } else if (has_blockio) { + CGroupBlockIODeviceBandwidth *b, *next; + + LIST_FOREACH_SAFE(device_bandwidths, b, next, c->blockio_device_bandwidths) { + uint64_t limits[_CGROUP_IO_LIMIT_TYPE_MAX]; + CGroupIOLimitType type; + + for (type = 0; type < _CGROUP_IO_LIMIT_TYPE_MAX; type++) + limits[type] = cgroup_io_limit_defaults[type]; + + limits[CGROUP_IO_RBPS_MAX] = b->rbps; + limits[CGROUP_IO_WBPS_MAX] = b->wbps; + + if (!cgroup_apply_io_device_limit(path, b->path, limits)) + cgroup_context_free_blockio_device_bandwidth(c, b); + } + } + } + + if (mask & CGROUP_MASK_BLKIO) { + bool has_io = cgroup_context_has_io_config(c); + bool has_blockio = cgroup_context_has_blockio_config(c); + + if (!is_root) { + char buf[DECIMAL_STR_MAX(uint64_t)+1]; + uint64_t weight; + + if (has_blockio) + weight = cgroup_context_blkio_weight(c, state); + else if (has_io) + weight = cgroup_weight_io_to_blkio(cgroup_context_io_weight(c, state)); + else + weight = CGROUP_BLKIO_WEIGHT_DEFAULT; + + xsprintf(buf, "%" PRIu64 "\n", weight); + r = cg_set_attribute("blkio", path, "blkio.weight", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set blkio.weight on %s: %m", path); + + if (has_blockio) { + CGroupBlockIODeviceWeight *w; + + /* FIXME: no way to reset this list */ + LIST_FOREACH(device_weights, w, c->blockio_device_weights) + cgroup_apply_blkio_device_weight(path, w->path, w->weight); + } else if (has_io) { + CGroupIODeviceWeight *w; + + /* FIXME: no way to reset this list */ + LIST_FOREACH(device_weights, w, c->io_device_weights) + cgroup_apply_blkio_device_weight(path, w->path, cgroup_weight_io_to_blkio(w->weight)); + } + } + + /* Apply limits and free ones without config. */ + if (has_blockio) { + CGroupBlockIODeviceBandwidth *b, *next; + + LIST_FOREACH_SAFE(device_bandwidths, b, next, c->blockio_device_bandwidths) { + if (!cgroup_apply_blkio_device_limit(path, b->path, b->rbps, b->wbps)) + cgroup_context_free_blockio_device_bandwidth(c, b); + } + } else if (has_io) { + CGroupIODeviceLimit *l, *next; + + LIST_FOREACH_SAFE(device_limits, l, next, c->io_device_limits) { + if (!cgroup_apply_blkio_device_limit(path, l->path, l->limits[CGROUP_IO_RBPS_MAX], l->limits[CGROUP_IO_WBPS_MAX])) + cgroup_context_free_io_device_limit(c, l); + } + } + } + + if ((mask & CGROUP_MASK_MEMORY) && !is_root) { + if (c->memory_limit != (uint64_t) -1) { + char buf[DECIMAL_STR_MAX(uint64_t) + 1]; + + sprintf(buf, "%" PRIu64 "\n", c->memory_limit); + + if (cg_unified() <= 0) + r = cg_set_attribute("memory", path, "memory.limit_in_bytes", buf); + else + r = cg_set_attribute("memory", path, "memory.max", buf); + + } else { + if (cg_unified() <= 0) + r = cg_set_attribute("memory", path, "memory.limit_in_bytes", "-1"); + else + r = cg_set_attribute("memory", path, "memory.max", "max"); + } + + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set memory.limit_in_bytes/memory.max on %s: %m", path); + } + + if ((mask & CGROUP_MASK_DEVICES) && !is_root) { + CGroupDeviceAllow *a; + + /* Changing the devices list of a populated cgroup + * might result in EINVAL, hence ignore EINVAL + * here. */ + + if (c->device_allow || c->device_policy != CGROUP_AUTO) + r = cg_set_attribute("devices", path, "devices.deny", "a"); + else + r = cg_set_attribute("devices", path, "devices.allow", "a"); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to reset devices.list on %s: %m", path); + + if (c->device_policy == CGROUP_CLOSED || + (c->device_policy == CGROUP_AUTO && c->device_allow)) { + static const char auto_devices[] = + "/dev/null\0" "rwm\0" + "/dev/zero\0" "rwm\0" + "/dev/full\0" "rwm\0" + "/dev/random\0" "rwm\0" + "/dev/urandom\0" "rwm\0" + "/dev/tty\0" "rwm\0" + "/dev/pts/ptmx\0" "rw\0"; /* /dev/pts/ptmx may not be duplicated, but accessed */ + + const char *x, *y; + + NULSTR_FOREACH_PAIR(x, y, auto_devices) + whitelist_device(path, x, y); + + whitelist_major(path, "pts", 'c', "rw"); + whitelist_major(path, "kdbus", 'c', "rw"); + whitelist_major(path, "kdbus/*", 'c', "rw"); + } + + LIST_FOREACH(device_allow, a, c->device_allow) { + char acc[4]; + unsigned k = 0; + + if (a->r) + acc[k++] = 'r'; + if (a->w) + acc[k++] = 'w'; + if (a->m) + acc[k++] = 'm'; + + if (k == 0) + continue; + + acc[k++] = 0; + + if (startswith(a->path, "/dev/")) + whitelist_device(path, a->path, acc); + else if (startswith(a->path, "block-")) + whitelist_major(path, a->path + 6, 'b', acc); + else if (startswith(a->path, "char-")) + whitelist_major(path, a->path + 5, 'c', acc); + else + log_debug("Ignoring device %s while writing cgroup attribute.", a->path); + } + } + + if ((mask & CGROUP_MASK_PIDS) && !is_root) { + + if (c->tasks_max != (uint64_t) -1) { + char buf[DECIMAL_STR_MAX(uint64_t) + 2]; + + sprintf(buf, "%" PRIu64 "\n", c->tasks_max); + r = cg_set_attribute("pids", path, "pids.max", buf); + } else + r = cg_set_attribute("pids", path, "pids.max", "max"); + + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set pids.max on %s: %m", path); + } +} + +CGroupMask cgroup_context_get_mask(CGroupContext *c) { + CGroupMask mask = 0; + + /* Figure out which controllers we need */ + + if (c->cpu_accounting || + c->cpu_shares != CGROUP_CPU_SHARES_INVALID || + c->startup_cpu_shares != CGROUP_CPU_SHARES_INVALID || + c->cpu_quota_per_sec_usec != USEC_INFINITY) + mask |= CGROUP_MASK_CPUACCT | CGROUP_MASK_CPU; + + if (cgroup_context_has_io_config(c) || cgroup_context_has_blockio_config(c)) + mask |= CGROUP_MASK_IO | CGROUP_MASK_BLKIO; + + if (c->memory_accounting || + c->memory_limit != (uint64_t) -1) + mask |= CGROUP_MASK_MEMORY; + + if (c->device_allow || + c->device_policy != CGROUP_AUTO) + mask |= CGROUP_MASK_DEVICES; + + if (c->tasks_accounting || + c->tasks_max != (uint64_t) -1) + mask |= CGROUP_MASK_PIDS; + + return mask; +} + +CGroupMask unit_get_own_mask(Unit *u) { + CGroupContext *c; + + /* Returns the mask of controllers the unit needs for itself */ + + c = unit_get_cgroup_context(u); + if (!c) + return 0; + + /* If delegation is turned on, then turn on all cgroups, + * unless we are on the legacy hierarchy and the process we + * fork into it is known to drop privileges, and hence + * shouldn't get access to the controllers. + * + * Note that on the unified hierarchy it is safe to delegate + * controllers to unprivileged services. */ + + if (c->delegate) { + ExecContext *e; + + e = unit_get_exec_context(u); + if (!e || + exec_context_maintains_privileges(e) || + cg_unified() > 0) + return _CGROUP_MASK_ALL; + } + + return cgroup_context_get_mask(c); +} + +CGroupMask unit_get_members_mask(Unit *u) { + assert(u); + + /* Returns the mask of controllers all of the unit's children + * require, merged */ + + if (u->cgroup_members_mask_valid) + return u->cgroup_members_mask; + + u->cgroup_members_mask = 0; + + if (u->type == UNIT_SLICE) { + Unit *member; + Iterator i; + + SET_FOREACH(member, u->dependencies[UNIT_BEFORE], i) { + + if (member == u) + continue; + + if (UNIT_DEREF(member->slice) != u) + continue; + + u->cgroup_members_mask |= + unit_get_own_mask(member) | + unit_get_members_mask(member); + } + } + + u->cgroup_members_mask_valid = true; + return u->cgroup_members_mask; +} + +CGroupMask unit_get_siblings_mask(Unit *u) { + assert(u); + + /* Returns the mask of controllers all of the unit's siblings + * require, i.e. the members mask of the unit's parent slice + * if there is one. */ + + if (UNIT_ISSET(u->slice)) + return unit_get_members_mask(UNIT_DEREF(u->slice)); + + return unit_get_own_mask(u) | unit_get_members_mask(u); +} + +CGroupMask unit_get_subtree_mask(Unit *u) { + + /* Returns the mask of this subtree, meaning of the group + * itself and its children. */ + + return unit_get_own_mask(u) | unit_get_members_mask(u); +} + +CGroupMask unit_get_target_mask(Unit *u) { + CGroupMask mask; + + /* This returns the cgroup mask of all controllers to enable + * for a specific cgroup, i.e. everything it needs itself, + * plus all that its children need, plus all that its siblings + * need. This is primarily useful on the legacy cgroup + * hierarchy, where we need to duplicate each cgroup in each + * hierarchy that shall be enabled for it. */ + + mask = unit_get_own_mask(u) | unit_get_members_mask(u) | unit_get_siblings_mask(u); + mask &= u->manager->cgroup_supported; + + return mask; +} + +CGroupMask unit_get_enable_mask(Unit *u) { + CGroupMask mask; + + /* This returns the cgroup mask of all controllers to enable + * for the children of a specific cgroup. This is primarily + * useful for the unified cgroup hierarchy, where each cgroup + * controls which controllers are enabled for its children. */ + + mask = unit_get_members_mask(u); + mask &= u->manager->cgroup_supported; + + return mask; +} + +/* Recurse from a unit up through its containing slices, propagating + * mask bits upward. A unit is also member of itself. */ +void unit_update_cgroup_members_masks(Unit *u) { + CGroupMask m; + bool more; + + assert(u); + + /* Calculate subtree mask */ + m = unit_get_subtree_mask(u); + + /* See if anything changed from the previous invocation. If + * not, we're done. */ + if (u->cgroup_subtree_mask_valid && m == u->cgroup_subtree_mask) + return; + + more = + u->cgroup_subtree_mask_valid && + ((m & ~u->cgroup_subtree_mask) != 0) && + ((~m & u->cgroup_subtree_mask) == 0); + + u->cgroup_subtree_mask = m; + u->cgroup_subtree_mask_valid = true; + + if (UNIT_ISSET(u->slice)) { + Unit *s = UNIT_DEREF(u->slice); + + if (more) + /* There's more set now than before. We + * propagate the new mask to the parent's mask + * (not caring if it actually was valid or + * not). */ + + s->cgroup_members_mask |= m; + + else + /* There's less set now than before (or we + * don't know), we need to recalculate + * everything, so let's invalidate the + * parent's members mask */ + + s->cgroup_members_mask_valid = false; + + /* And now make sure that this change also hits our + * grandparents */ + unit_update_cgroup_members_masks(s); + } +} + +static const char *migrate_callback(CGroupMask mask, void *userdata) { + Unit *u = userdata; + + assert(mask != 0); + assert(u); + + while (u) { + if (u->cgroup_path && + u->cgroup_realized && + (u->cgroup_realized_mask & mask) == mask) + return u->cgroup_path; + + u = UNIT_DEREF(u->slice); + } + + return NULL; +} + +char *unit_default_cgroup_path(Unit *u) { + _cleanup_free_ char *escaped = NULL, *slice = NULL; + int r; + + assert(u); + + if (unit_has_name(u, SPECIAL_ROOT_SLICE)) + return strdup(u->manager->cgroup_root); + + if (UNIT_ISSET(u->slice) && !unit_has_name(UNIT_DEREF(u->slice), SPECIAL_ROOT_SLICE)) { + r = cg_slice_to_path(UNIT_DEREF(u->slice)->id, &slice); + if (r < 0) + return NULL; + } + + escaped = cg_escape(u->id); + if (!escaped) + return NULL; + + if (slice) + return strjoin(u->manager->cgroup_root, "/", slice, "/", escaped, NULL); + else + return strjoin(u->manager->cgroup_root, "/", escaped, NULL); +} + +int unit_set_cgroup_path(Unit *u, const char *path) { + _cleanup_free_ char *p = NULL; + int r; + + assert(u); + + if (path) { + p = strdup(path); + if (!p) + return -ENOMEM; + } else + p = NULL; + + if (streq_ptr(u->cgroup_path, p)) + return 0; + + if (p) { + r = hashmap_put(u->manager->cgroup_unit, p, u); + if (r < 0) + return r; + } + + unit_release_cgroup(u); + + u->cgroup_path = p; + p = NULL; + + return 1; +} + +int unit_watch_cgroup(Unit *u) { + _cleanup_free_ char *events = NULL; + int r; + + assert(u); + + if (!u->cgroup_path) + return 0; + + if (u->cgroup_inotify_wd >= 0) + return 0; + + /* Only applies to the unified hierarchy */ + r = cg_unified(); + if (r < 0) + return log_unit_error_errno(u, r, "Failed detect wether the unified hierarchy is used: %m"); + if (r == 0) + return 0; + + /* Don't watch the root slice, it's pointless. */ + if (unit_has_name(u, SPECIAL_ROOT_SLICE)) + return 0; + + r = hashmap_ensure_allocated(&u->manager->cgroup_inotify_wd_unit, &trivial_hash_ops); + if (r < 0) + return log_oom(); + + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.events", &events); + if (r < 0) + return log_oom(); + + u->cgroup_inotify_wd = inotify_add_watch(u->manager->cgroup_inotify_fd, events, IN_MODIFY); + if (u->cgroup_inotify_wd < 0) { + + if (errno == ENOENT) /* If the directory is already + * gone we don't need to track + * it, so this is not an error */ + return 0; + + return log_unit_error_errno(u, errno, "Failed to add inotify watch descriptor for control group %s: %m", u->cgroup_path); + } + + r = hashmap_put(u->manager->cgroup_inotify_wd_unit, INT_TO_PTR(u->cgroup_inotify_wd), u); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to add inotify watch descriptor to hash map: %m"); + + return 0; +} + +static int unit_create_cgroup( + Unit *u, + CGroupMask target_mask, + CGroupMask enable_mask) { + + CGroupContext *c; + int r; + + assert(u); + + c = unit_get_cgroup_context(u); + if (!c) + return 0; + + if (!u->cgroup_path) { + _cleanup_free_ char *path = NULL; + + path = unit_default_cgroup_path(u); + if (!path) + return log_oom(); + + r = unit_set_cgroup_path(u, path); + if (r == -EEXIST) + return log_unit_error_errno(u, r, "Control group %s exists already.", path); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to set unit's control group path to %s: %m", path); + } + + /* First, create our own group */ + r = cg_create_everywhere(u->manager->cgroup_supported, target_mask, u->cgroup_path); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to create cgroup %s: %m", u->cgroup_path); + + /* Start watching it */ + (void) unit_watch_cgroup(u); + + /* Enable all controllers we need */ + r = cg_enable_everywhere(u->manager->cgroup_supported, enable_mask, u->cgroup_path); + if (r < 0) + log_unit_warning_errno(u, r, "Failed to enable controllers on cgroup %s, ignoring: %m", u->cgroup_path); + + /* Keep track that this is now realized */ + u->cgroup_realized = true; + u->cgroup_realized_mask = target_mask; + u->cgroup_enabled_mask = enable_mask; + + if (u->type != UNIT_SLICE && !c->delegate) { + + /* Then, possibly move things over, but not if + * subgroups may contain processes, which is the case + * for slice and delegation units. */ + r = cg_migrate_everywhere(u->manager->cgroup_supported, u->cgroup_path, u->cgroup_path, migrate_callback, u); + if (r < 0) + log_unit_warning_errno(u, r, "Failed to migrate cgroup from to %s, ignoring: %m", u->cgroup_path); + } + + return 0; +} + +int unit_attach_pids_to_cgroup(Unit *u) { + int r; + assert(u); + + r = unit_realize_cgroup(u); + if (r < 0) + return r; + + r = cg_attach_many_everywhere(u->manager->cgroup_supported, u->cgroup_path, u->pids, migrate_callback, u); + if (r < 0) + return r; + + return 0; +} + +static bool unit_has_mask_realized(Unit *u, CGroupMask target_mask, CGroupMask enable_mask) { + assert(u); + + return u->cgroup_realized && u->cgroup_realized_mask == target_mask && u->cgroup_enabled_mask == enable_mask; +} + +/* Check if necessary controllers and attributes for a unit are in place. + * + * If so, do nothing. + * If not, create paths, move processes over, and set attributes. + * + * Returns 0 on success and < 0 on failure. */ +static int unit_realize_cgroup_now(Unit *u, ManagerState state) { + CGroupMask target_mask, enable_mask; + int r; + + assert(u); + + if (u->in_cgroup_queue) { + LIST_REMOVE(cgroup_queue, u->manager->cgroup_queue, u); + u->in_cgroup_queue = false; + } + + target_mask = unit_get_target_mask(u); + enable_mask = unit_get_enable_mask(u); + + if (unit_has_mask_realized(u, target_mask, enable_mask)) + return 0; + + /* First, realize parents */ + if (UNIT_ISSET(u->slice)) { + r = unit_realize_cgroup_now(UNIT_DEREF(u->slice), state); + if (r < 0) + return r; + } + + /* And then do the real work */ + r = unit_create_cgroup(u, target_mask, enable_mask); + if (r < 0) + return r; + + /* Finally, apply the necessary attributes. */ + cgroup_context_apply(unit_get_cgroup_context(u), target_mask, u->cgroup_path, state); + + return 0; +} + +static void unit_add_to_cgroup_queue(Unit *u) { + + if (u->in_cgroup_queue) + return; + + LIST_PREPEND(cgroup_queue, u->manager->cgroup_queue, u); + u->in_cgroup_queue = true; +} + +unsigned manager_dispatch_cgroup_queue(Manager *m) { + ManagerState state; + unsigned n = 0; + Unit *i; + int r; + + state = manager_state(m); + + while ((i = m->cgroup_queue)) { + assert(i->in_cgroup_queue); + + r = unit_realize_cgroup_now(i, state); + if (r < 0) + log_warning_errno(r, "Failed to realize cgroups for queued unit %s, ignoring: %m", i->id); + + n++; + } + + return n; +} + +static void unit_queue_siblings(Unit *u) { + Unit *slice; + + /* This adds the siblings of the specified unit and the + * siblings of all parent units to the cgroup queue. (But + * neither the specified unit itself nor the parents.) */ + + while ((slice = UNIT_DEREF(u->slice))) { + Iterator i; + Unit *m; + + SET_FOREACH(m, slice->dependencies[UNIT_BEFORE], i) { + if (m == u) + continue; + + /* Skip units that have a dependency on the slice + * but aren't actually in it. */ + if (UNIT_DEREF(m->slice) != slice) + continue; + + /* No point in doing cgroup application for units + * without active processes. */ + if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(m))) + continue; + + /* If the unit doesn't need any new controllers + * and has current ones realized, it doesn't need + * any changes. */ + if (unit_has_mask_realized(m, unit_get_target_mask(m), unit_get_enable_mask(m))) + continue; + + unit_add_to_cgroup_queue(m); + } + + u = slice; + } +} + +int unit_realize_cgroup(Unit *u) { + assert(u); + + if (!UNIT_HAS_CGROUP_CONTEXT(u)) + return 0; + + /* So, here's the deal: when realizing the cgroups for this + * unit, we need to first create all parents, but there's more + * actually: for the weight-based controllers we also need to + * make sure that all our siblings (i.e. units that are in the + * same slice as we are) have cgroups, too. Otherwise, things + * would become very uneven as each of their processes would + * get as much resources as all our group together. This call + * will synchronously create the parent cgroups, but will + * defer work on the siblings to the next event loop + * iteration. */ + + /* Add all sibling slices to the cgroup queue. */ + unit_queue_siblings(u); + + /* And realize this one now (and apply the values) */ + return unit_realize_cgroup_now(u, manager_state(u->manager)); +} + +void unit_release_cgroup(Unit *u) { + assert(u); + + /* Forgets all cgroup details for this cgroup */ + + if (u->cgroup_path) { + (void) hashmap_remove(u->manager->cgroup_unit, u->cgroup_path); + u->cgroup_path = mfree(u->cgroup_path); + } + + if (u->cgroup_inotify_wd >= 0) { + if (inotify_rm_watch(u->manager->cgroup_inotify_fd, u->cgroup_inotify_wd) < 0) + log_unit_debug_errno(u, errno, "Failed to remove cgroup inotify watch %i for %s, ignoring", u->cgroup_inotify_wd, u->id); + + (void) hashmap_remove(u->manager->cgroup_inotify_wd_unit, INT_TO_PTR(u->cgroup_inotify_wd)); + u->cgroup_inotify_wd = -1; + } +} + +void unit_prune_cgroup(Unit *u) { + int r; + bool is_root_slice; + + assert(u); + + /* Removes the cgroup, if empty and possible, and stops watching it. */ + + if (!u->cgroup_path) + return; + + is_root_slice = unit_has_name(u, SPECIAL_ROOT_SLICE); + + r = cg_trim_everywhere(u->manager->cgroup_supported, u->cgroup_path, !is_root_slice); + if (r < 0) { + log_debug_errno(r, "Failed to destroy cgroup %s, ignoring: %m", u->cgroup_path); + return; + } + + if (is_root_slice) + return; + + unit_release_cgroup(u); + + u->cgroup_realized = false; + u->cgroup_realized_mask = 0; + u->cgroup_enabled_mask = 0; +} + +int unit_search_main_pid(Unit *u, pid_t *ret) { + _cleanup_fclose_ FILE *f = NULL; + pid_t pid = 0, npid, mypid; + int r; + + assert(u); + assert(ret); + + if (!u->cgroup_path) + return -ENXIO; + + r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, &f); + if (r < 0) + return r; + + mypid = getpid(); + while (cg_read_pid(f, &npid) > 0) { + pid_t ppid; + + if (npid == pid) + continue; + + /* Ignore processes that aren't our kids */ + if (get_process_ppid(npid, &ppid) >= 0 && ppid != mypid) + continue; + + if (pid != 0) + /* Dang, there's more than one daemonized PID + in this group, so we don't know what process + is the main process. */ + + return -ENODATA; + + pid = npid; + } + + *ret = pid; + return 0; +} + +static int unit_watch_pids_in_path(Unit *u, const char *path) { + _cleanup_closedir_ DIR *d = NULL; + _cleanup_fclose_ FILE *f = NULL; + int ret = 0, r; + + assert(u); + assert(path); + + r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, path, &f); + if (r < 0) + ret = r; + else { + pid_t pid; + + while ((r = cg_read_pid(f, &pid)) > 0) { + r = unit_watch_pid(u, pid); + if (r < 0 && ret >= 0) + ret = r; + } + + if (r < 0 && ret >= 0) + ret = r; + } + + r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, path, &d); + if (r < 0) { + if (ret >= 0) + ret = r; + } else { + char *fn; + + while ((r = cg_read_subgroup(d, &fn)) > 0) { + _cleanup_free_ char *p = NULL; + + p = strjoin(path, "/", fn, NULL); + free(fn); + + if (!p) + return -ENOMEM; + + r = unit_watch_pids_in_path(u, p); + if (r < 0 && ret >= 0) + ret = r; + } + + if (r < 0 && ret >= 0) + ret = r; + } + + return ret; +} + +int unit_watch_all_pids(Unit *u) { + assert(u); + + /* Adds all PIDs from our cgroup to the set of PIDs we + * watch. This is a fallback logic for cases where we do not + * get reliable cgroup empty notifications: we try to use + * SIGCHLD as replacement. */ + + if (!u->cgroup_path) + return -ENOENT; + + if (cg_unified() > 0) /* On unified we can use proper notifications */ + return 0; + + return unit_watch_pids_in_path(u, u->cgroup_path); +} + +int unit_notify_cgroup_empty(Unit *u) { + int r; + + assert(u); + + if (!u->cgroup_path) + return 0; + + r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path); + if (r <= 0) + return r; + + unit_add_to_gc_queue(u); + + if (UNIT_VTABLE(u)->notify_cgroup_empty) + UNIT_VTABLE(u)->notify_cgroup_empty(u); + + return 0; +} + +static int on_cgroup_inotify_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + + assert(s); + assert(fd >= 0); + assert(m); + + for (;;) { + union inotify_event_buffer buffer; + struct inotify_event *e; + ssize_t l; + + l = read(fd, &buffer, sizeof(buffer)); + if (l < 0) { + if (errno == EINTR || errno == EAGAIN) + return 0; + + return log_error_errno(errno, "Failed to read control group inotify events: %m"); + } + + FOREACH_INOTIFY_EVENT(e, buffer, l) { + Unit *u; + + if (e->wd < 0) + /* Queue overflow has no watch descriptor */ + continue; + + if (e->mask & IN_IGNORED) + /* The watch was just removed */ + continue; + + u = hashmap_get(m->cgroup_inotify_wd_unit, INT_TO_PTR(e->wd)); + if (!u) /* Not that inotify might deliver + * events for a watch even after it + * was removed, because it was queued + * before the removal. Let's ignore + * this here safely. */ + continue; + + (void) unit_notify_cgroup_empty(u); + } + } +} + +int manager_setup_cgroup(Manager *m) { + _cleanup_free_ char *path = NULL; + CGroupController c; + int r, unified; + char *e; + + assert(m); + + /* 1. Determine hierarchy */ + m->cgroup_root = mfree(m->cgroup_root); + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &m->cgroup_root); + if (r < 0) + return log_error_errno(r, "Cannot determine cgroup we are running in: %m"); + + /* Chop off the init scope, if we are already located in it */ + e = endswith(m->cgroup_root, "/" SPECIAL_INIT_SCOPE); + + /* LEGACY: Also chop off the system slice if we are in + * it. This is to support live upgrades from older systemd + * versions where PID 1 was moved there. Also see + * cg_get_root_path(). */ + if (!e && MANAGER_IS_SYSTEM(m)) { + e = endswith(m->cgroup_root, "/" SPECIAL_SYSTEM_SLICE); + if (!e) + e = endswith(m->cgroup_root, "/system"); /* even more legacy */ + } + if (e) + *e = 0; + + /* And make sure to store away the root value without trailing + * slash, even for the root dir, so that we can easily prepend + * it everywhere. */ + while ((e = endswith(m->cgroup_root, "/"))) + *e = 0; + + /* 2. Show data */ + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, NULL, &path); + if (r < 0) + return log_error_errno(r, "Cannot find cgroup mount point: %m"); + + unified = cg_unified(); + if (unified < 0) + return log_error_errno(r, "Couldn't determine if we are running in the unified hierarchy: %m"); + if (unified > 0) + log_debug("Unified cgroup hierarchy is located at %s.", path); + else + log_debug("Using cgroup controller " SYSTEMD_CGROUP_CONTROLLER ". File system hierarchy is at %s.", path); + + if (!m->test_run) { + const char *scope_path; + + /* 3. Install agent */ + if (unified) { + + /* In the unified hierarchy we can can get + * cgroup empty notifications via inotify. */ + + m->cgroup_inotify_event_source = sd_event_source_unref(m->cgroup_inotify_event_source); + safe_close(m->cgroup_inotify_fd); + + m->cgroup_inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); + if (m->cgroup_inotify_fd < 0) + return log_error_errno(errno, "Failed to create control group inotify object: %m"); + + r = sd_event_add_io(m->event, &m->cgroup_inotify_event_source, m->cgroup_inotify_fd, EPOLLIN, on_cgroup_inotify_event, m); + if (r < 0) + return log_error_errno(r, "Failed to watch control group inotify object: %m"); + + /* Process cgroup empty notifications early, but after service notifications and SIGCHLD. Also + * see handling of cgroup agent notifications, for the classic cgroup hierarchy support. */ + r = sd_event_source_set_priority(m->cgroup_inotify_event_source, SD_EVENT_PRIORITY_NORMAL-5); + if (r < 0) + return log_error_errno(r, "Failed to set priority of inotify event source: %m"); + + (void) sd_event_source_set_description(m->cgroup_inotify_event_source, "cgroup-inotify"); + + } else if (MANAGER_IS_SYSTEM(m)) { + + /* On the legacy hierarchy we only get + * notifications via cgroup agents. (Which + * isn't really reliable, since it does not + * generate events when control groups with + * children run empty. */ + + r = cg_install_release_agent(SYSTEMD_CGROUP_CONTROLLER, SYSTEMD_CGROUP_AGENT_PATH); + if (r < 0) + log_warning_errno(r, "Failed to install release agent, ignoring: %m"); + else if (r > 0) + log_debug("Installed release agent."); + else if (r == 0) + log_debug("Release agent already installed."); + } + + /* 4. Make sure we are in the special "init.scope" unit in the root slice. */ + scope_path = strjoina(m->cgroup_root, "/" SPECIAL_INIT_SCOPE); + r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, scope_path, 0); + if (r < 0) + return log_error_errno(r, "Failed to create %s control group: %m", scope_path); + + /* also, move all other userspace processes remaining + * in the root cgroup into that scope. */ + r = cg_migrate(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, SYSTEMD_CGROUP_CONTROLLER, scope_path, false); + if (r < 0) + log_warning_errno(r, "Couldn't move remaining userspace processes, ignoring: %m"); + + /* 5. And pin it, so that it cannot be unmounted */ + safe_close(m->pin_cgroupfs_fd); + m->pin_cgroupfs_fd = open(path, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY|O_NONBLOCK); + if (m->pin_cgroupfs_fd < 0) + return log_error_errno(errno, "Failed to open pin file: %m"); + + /* 6. Always enable hierarchical support if it exists... */ + if (!unified) + (void) cg_set_attribute("memory", "/", "memory.use_hierarchy", "1"); + } + + /* 7. Figure out which controllers are supported */ + r = cg_mask_supported(&m->cgroup_supported); + if (r < 0) + return log_error_errno(r, "Failed to determine supported controllers: %m"); + + for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) + log_debug("Controller '%s' supported: %s", cgroup_controller_to_string(c), yes_no(m->cgroup_supported & c)); + + return 0; +} + +void manager_shutdown_cgroup(Manager *m, bool delete) { + assert(m); + + /* We can't really delete the group, since we are in it. But + * let's trim it. */ + if (delete && m->cgroup_root) + (void) cg_trim(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, false); + + m->cgroup_inotify_wd_unit = hashmap_free(m->cgroup_inotify_wd_unit); + + m->cgroup_inotify_event_source = sd_event_source_unref(m->cgroup_inotify_event_source); + m->cgroup_inotify_fd = safe_close(m->cgroup_inotify_fd); + + m->pin_cgroupfs_fd = safe_close(m->pin_cgroupfs_fd); + + m->cgroup_root = mfree(m->cgroup_root); +} + +Unit* manager_get_unit_by_cgroup(Manager *m, const char *cgroup) { + char *p; + Unit *u; + + assert(m); + assert(cgroup); + + u = hashmap_get(m->cgroup_unit, cgroup); + if (u) + return u; + + p = strdupa(cgroup); + for (;;) { + char *e; + + e = strrchr(p, '/'); + if (!e || e == p) + return hashmap_get(m->cgroup_unit, SPECIAL_ROOT_SLICE); + + *e = 0; + + u = hashmap_get(m->cgroup_unit, p); + if (u) + return u; + } +} + +Unit *manager_get_unit_by_pid_cgroup(Manager *m, pid_t pid) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(m); + + if (pid <= 0) + return NULL; + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cgroup); + if (r < 0) + return NULL; + + return manager_get_unit_by_cgroup(m, cgroup); +} + +Unit *manager_get_unit_by_pid(Manager *m, pid_t pid) { + Unit *u; + + assert(m); + + if (pid <= 0) + return NULL; + + if (pid == 1) + return hashmap_get(m->units, SPECIAL_INIT_SCOPE); + + u = hashmap_get(m->watch_pids1, PID_TO_PTR(pid)); + if (u) + return u; + + u = hashmap_get(m->watch_pids2, PID_TO_PTR(pid)); + if (u) + return u; + + return manager_get_unit_by_pid_cgroup(m, pid); +} + +int manager_notify_cgroup_empty(Manager *m, const char *cgroup) { + Unit *u; + + assert(m); + assert(cgroup); + + log_debug("Got cgroup empty notification for: %s", cgroup); + + u = manager_get_unit_by_cgroup(m, cgroup); + if (!u) + return 0; + + return unit_notify_cgroup_empty(u); +} + +int unit_get_memory_current(Unit *u, uint64_t *ret) { + _cleanup_free_ char *v = NULL; + int r; + + assert(u); + assert(ret); + + if (!u->cgroup_path) + return -ENODATA; + + if ((u->cgroup_realized_mask & CGROUP_MASK_MEMORY) == 0) + return -ENODATA; + + if (cg_unified() <= 0) + r = cg_get_attribute("memory", u->cgroup_path, "memory.usage_in_bytes", &v); + else + r = cg_get_attribute("memory", u->cgroup_path, "memory.current", &v); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + + return safe_atou64(v, ret); +} + +int unit_get_tasks_current(Unit *u, uint64_t *ret) { + _cleanup_free_ char *v = NULL; + int r; + + assert(u); + assert(ret); + + if (!u->cgroup_path) + return -ENODATA; + + if ((u->cgroup_realized_mask & CGROUP_MASK_PIDS) == 0) + return -ENODATA; + + r = cg_get_attribute("pids", u->cgroup_path, "pids.current", &v); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + + return safe_atou64(v, ret); +} + +static int unit_get_cpu_usage_raw(Unit *u, nsec_t *ret) { + _cleanup_free_ char *v = NULL; + uint64_t ns; + int r; + + assert(u); + assert(ret); + + if (!u->cgroup_path) + return -ENODATA; + + if ((u->cgroup_realized_mask & CGROUP_MASK_CPUACCT) == 0) + return -ENODATA; + + r = cg_get_attribute("cpuacct", u->cgroup_path, "cpuacct.usage", &v); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + + r = safe_atou64(v, &ns); + if (r < 0) + return r; + + *ret = ns; + return 0; +} + +int unit_get_cpu_usage(Unit *u, nsec_t *ret) { + nsec_t ns; + int r; + + r = unit_get_cpu_usage_raw(u, &ns); + if (r < 0) + return r; + + if (ns > u->cpuacct_usage_base) + ns -= u->cpuacct_usage_base; + else + ns = 0; + + *ret = ns; + return 0; +} + +int unit_reset_cpu_usage(Unit *u) { + nsec_t ns; + int r; + + assert(u); + + r = unit_get_cpu_usage_raw(u, &ns); + if (r < 0) { + u->cpuacct_usage_base = 0; + return r; + } + + u->cpuacct_usage_base = ns; + return 0; +} + +bool unit_cgroup_delegate(Unit *u) { + CGroupContext *c; + + assert(u); + + c = unit_get_cgroup_context(u); + if (!c) + return false; + + return c->delegate; +} + +void unit_invalidate_cgroup(Unit *u, CGroupMask m) { + assert(u); + + if (!UNIT_HAS_CGROUP_CONTEXT(u)) + return; + + if (m == 0) + return; + + /* always invalidate compat pairs together */ + if (m & (CGROUP_MASK_IO | CGROUP_MASK_BLKIO)) + m |= CGROUP_MASK_IO | CGROUP_MASK_BLKIO; + + if ((u->cgroup_realized_mask & m) == 0) + return; + + u->cgroup_realized_mask &= ~m; + unit_add_to_cgroup_queue(u); +} + +void manager_invalidate_startup_units(Manager *m) { + Iterator i; + Unit *u; + + assert(m); + + SET_FOREACH(u, m->startup_units, i) + unit_invalidate_cgroup(u, CGROUP_MASK_CPU|CGROUP_MASK_IO|CGROUP_MASK_BLKIO); +} + +static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = { + [CGROUP_AUTO] = "auto", + [CGROUP_CLOSED] = "closed", + [CGROUP_STRICT] = "strict", +}; + +DEFINE_STRING_TABLE_LOOKUP(cgroup_device_policy, CGroupDevicePolicy); diff --git a/src/libcore/cgroup.h b/src/libcore/cgroup.h new file mode 100644 index 0000000000..2b1edbafc4 --- /dev/null +++ b/src/libcore/cgroup.h @@ -0,0 +1,181 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 "list.h" +#include "time-util.h" +#include "cgroup-util.h" + +typedef struct CGroupContext CGroupContext; +typedef struct CGroupDeviceAllow CGroupDeviceAllow; +typedef struct CGroupIODeviceWeight CGroupIODeviceWeight; +typedef struct CGroupIODeviceLimit CGroupIODeviceLimit; +typedef struct CGroupBlockIODeviceWeight CGroupBlockIODeviceWeight; +typedef struct CGroupBlockIODeviceBandwidth CGroupBlockIODeviceBandwidth; + +typedef enum CGroupDevicePolicy { + + /* When devices listed, will allow those, plus built-in ones, + if none are listed will allow everything. */ + CGROUP_AUTO, + + /* Everything forbidden, except built-in ones and listed ones. */ + CGROUP_CLOSED, + + /* Everythings forbidden, except for the listed devices */ + CGROUP_STRICT, + + _CGROUP_DEVICE_POLICY_MAX, + _CGROUP_DEVICE_POLICY_INVALID = -1 +} CGroupDevicePolicy; + +struct CGroupDeviceAllow { + LIST_FIELDS(CGroupDeviceAllow, device_allow); + char *path; + bool r:1; + bool w:1; + bool m:1; +}; + +struct CGroupIODeviceWeight { + LIST_FIELDS(CGroupIODeviceWeight, device_weights); + char *path; + uint64_t weight; +}; + +struct CGroupIODeviceLimit { + LIST_FIELDS(CGroupIODeviceLimit, device_limits); + char *path; + uint64_t limits[_CGROUP_IO_LIMIT_TYPE_MAX]; +}; + +struct CGroupBlockIODeviceWeight { + LIST_FIELDS(CGroupBlockIODeviceWeight, device_weights); + char *path; + uint64_t weight; +}; + +struct CGroupBlockIODeviceBandwidth { + LIST_FIELDS(CGroupBlockIODeviceBandwidth, device_bandwidths); + char *path; + uint64_t rbps; + uint64_t wbps; +}; + +struct CGroupContext { + bool cpu_accounting; + bool io_accounting; + bool blockio_accounting; + bool memory_accounting; + bool tasks_accounting; + + /* For unified hierarchy */ + uint64_t io_weight; + uint64_t startup_io_weight; + LIST_HEAD(CGroupIODeviceWeight, io_device_weights); + LIST_HEAD(CGroupIODeviceLimit, io_device_limits); + + /* For legacy hierarchies */ + uint64_t cpu_shares; + uint64_t startup_cpu_shares; + usec_t cpu_quota_per_sec_usec; + + uint64_t blockio_weight; + uint64_t startup_blockio_weight; + LIST_HEAD(CGroupBlockIODeviceWeight, blockio_device_weights); + LIST_HEAD(CGroupBlockIODeviceBandwidth, blockio_device_bandwidths); + + uint64_t memory_limit; + + CGroupDevicePolicy device_policy; + LIST_HEAD(CGroupDeviceAllow, device_allow); + + /* Common */ + uint64_t tasks_max; + + bool delegate; +}; + +#include "cgroup-util.h" +#include "unit.h" + +void cgroup_context_init(CGroupContext *c); +void cgroup_context_done(CGroupContext *c); +void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix); +void cgroup_context_apply(CGroupContext *c, CGroupMask mask, const char *path, ManagerState state); + +CGroupMask cgroup_context_get_mask(CGroupContext *c); + +void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a); +void cgroup_context_free_io_device_weight(CGroupContext *c, CGroupIODeviceWeight *w); +void cgroup_context_free_io_device_limit(CGroupContext *c, CGroupIODeviceLimit *l); +void cgroup_context_free_blockio_device_weight(CGroupContext *c, CGroupBlockIODeviceWeight *w); +void cgroup_context_free_blockio_device_bandwidth(CGroupContext *c, CGroupBlockIODeviceBandwidth *b); + +CGroupMask unit_get_own_mask(Unit *u); +CGroupMask unit_get_siblings_mask(Unit *u); +CGroupMask unit_get_members_mask(Unit *u); +CGroupMask unit_get_subtree_mask(Unit *u); + +CGroupMask unit_get_target_mask(Unit *u); +CGroupMask unit_get_enable_mask(Unit *u); + +void unit_update_cgroup_members_masks(Unit *u); + +char *unit_default_cgroup_path(Unit *u); +int unit_set_cgroup_path(Unit *u, const char *path); + +int unit_realize_cgroup(Unit *u); +void unit_release_cgroup(Unit *u); +void unit_prune_cgroup(Unit *u); +int unit_watch_cgroup(Unit *u); + +int unit_attach_pids_to_cgroup(Unit *u); + +int manager_setup_cgroup(Manager *m); +void manager_shutdown_cgroup(Manager *m, bool delete); + +unsigned manager_dispatch_cgroup_queue(Manager *m); + +Unit *manager_get_unit_by_cgroup(Manager *m, const char *cgroup); +Unit *manager_get_unit_by_pid_cgroup(Manager *m, pid_t pid); +Unit* manager_get_unit_by_pid(Manager *m, pid_t pid); + +int unit_search_main_pid(Unit *u, pid_t *ret); +int unit_watch_all_pids(Unit *u); + +int unit_get_memory_current(Unit *u, uint64_t *ret); +int unit_get_tasks_current(Unit *u, uint64_t *ret); +int unit_get_cpu_usage(Unit *u, nsec_t *ret); +int unit_reset_cpu_usage(Unit *u); + +bool unit_cgroup_delegate(Unit *u); + +int unit_notify_cgroup_empty(Unit *u); +int manager_notify_cgroup_empty(Manager *m, const char *group); + +void unit_invalidate_cgroup(Unit *u, CGroupMask m); + +void manager_invalidate_startup_units(Manager *m); + +const char* cgroup_device_policy_to_string(CGroupDevicePolicy i) _const_; +CGroupDevicePolicy cgroup_device_policy_from_string(const char *s) _pure_; diff --git a/src/libcore/dbus-automount.c b/src/libcore/dbus-automount.c new file mode 100644 index 0000000000..b2806ad86f --- /dev/null +++ b/src/libcore/dbus-automount.c @@ -0,0 +1,34 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 "automount.h" +#include "bus-util.h" +#include "dbus-automount.h" +#include "string-util.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, automount_result, AutomountResult); + +const sd_bus_vtable bus_automount_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Where", "s", NULL, offsetof(Automount, where), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Automount, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Automount, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("TimeoutIdleUSec", "t", bus_property_get_usec, offsetof(Automount, timeout_idle_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_VTABLE_END +}; diff --git a/src/libcore/dbus-automount.h b/src/libcore/dbus-automount.h new file mode 100644 index 0000000000..7b51eb973a --- /dev/null +++ b/src/libcore/dbus-automount.h @@ -0,0 +1,23 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + + +extern const sd_bus_vtable bus_automount_vtable[]; diff --git a/src/libcore/dbus-busname.c b/src/libcore/dbus-busname.c new file mode 100644 index 0000000000..cf816ba15b --- /dev/null +++ b/src/libcore/dbus-busname.c @@ -0,0 +1,37 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "bus-util.h" +#include "busname.h" +#include "dbus-busname.h" +#include "string-util.h" +#include "unit.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, busname_result, BusNameResult); + +const sd_bus_vtable bus_busname_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Name", "s", NULL, offsetof(BusName, name), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TimeoutUSec", "t", bus_property_get_usec, offsetof(BusName, timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(BusName, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(BusName, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Activating", "b", bus_property_get_bool, offsetof(BusName, activating), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("AcceptFileDescriptors", "b", bus_property_get_bool, offsetof(BusName, accept_fd), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_VTABLE_END +}; diff --git a/src/libcore/dbus-busname.h b/src/libcore/dbus-busname.h new file mode 100644 index 0000000000..8643d1a404 --- /dev/null +++ b/src/libcore/dbus-busname.h @@ -0,0 +1,23 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + + +extern const sd_bus_vtable bus_busname_vtable[]; diff --git a/src/libcore/dbus-cgroup.c b/src/libcore/dbus-cgroup.c new file mode 100644 index 0000000000..eef1c47c14 --- /dev/null +++ b/src/libcore/dbus-cgroup.c @@ -0,0 +1,1004 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "alloc-util.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "cgroup.h" +#include "dbus-cgroup.h" +#include "fd-util.h" +#include "fileio.h" +#include "path-util.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_cgroup_device_policy, cgroup_device_policy, CGroupDevicePolicy); + +static int property_get_io_device_weight( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + CGroupContext *c = userdata; + CGroupIODeviceWeight *w; + int r; + + assert(bus); + assert(reply); + assert(c); + + r = sd_bus_message_open_container(reply, 'a', "(st)"); + if (r < 0) + return r; + + LIST_FOREACH(device_weights, w, c->io_device_weights) { + r = sd_bus_message_append(reply, "(st)", w->path, w->weight); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_io_device_limits( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + CGroupContext *c = userdata; + CGroupIODeviceLimit *l; + int r; + + assert(bus); + assert(reply); + assert(c); + + r = sd_bus_message_open_container(reply, 'a', "(st)"); + if (r < 0) + return r; + + LIST_FOREACH(device_limits, l, c->io_device_limits) { + CGroupIOLimitType type; + + type = cgroup_io_limit_type_from_string(property); + if (type < 0 || l->limits[type] == cgroup_io_limit_defaults[type]) + continue; + + r = sd_bus_message_append(reply, "(st)", l->path, l->limits[type]); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_blockio_device_weight( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + CGroupContext *c = userdata; + CGroupBlockIODeviceWeight *w; + int r; + + assert(bus); + assert(reply); + assert(c); + + r = sd_bus_message_open_container(reply, 'a', "(st)"); + if (r < 0) + return r; + + LIST_FOREACH(device_weights, w, c->blockio_device_weights) { + r = sd_bus_message_append(reply, "(st)", w->path, w->weight); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_blockio_device_bandwidths( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + CGroupContext *c = userdata; + CGroupBlockIODeviceBandwidth *b; + int r; + + assert(bus); + assert(reply); + assert(c); + + r = sd_bus_message_open_container(reply, 'a', "(st)"); + if (r < 0) + return r; + + LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { + uint64_t v; + + if (streq(property, "BlockIOReadBandwidth")) + v = b->rbps; + else + v = b->wbps; + + if (v == CGROUP_LIMIT_MAX) + continue; + + r = sd_bus_message_append(reply, "(st)", b->path, v); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_device_allow( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + CGroupContext *c = userdata; + CGroupDeviceAllow *a; + int r; + + assert(bus); + assert(reply); + assert(c); + + r = sd_bus_message_open_container(reply, 'a', "(ss)"); + if (r < 0) + return r; + + LIST_FOREACH(device_allow, a, c->device_allow) { + unsigned k = 0; + char rwm[4]; + + if (a->r) + rwm[k++] = 'r'; + if (a->w) + rwm[k++] = 'w'; + if (a->m) + rwm[k++] = 'm'; + + rwm[k] = 0; + + r = sd_bus_message_append(reply, "(ss)", a->path, rwm); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +const sd_bus_vtable bus_cgroup_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Delegate", "b", bus_property_get_bool, offsetof(CGroupContext, delegate), 0), + SD_BUS_PROPERTY("CPUAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, cpu_accounting), 0), + SD_BUS_PROPERTY("CPUShares", "t", NULL, offsetof(CGroupContext, cpu_shares), 0), + SD_BUS_PROPERTY("StartupCPUShares", "t", NULL, offsetof(CGroupContext, startup_cpu_shares), 0), + SD_BUS_PROPERTY("CPUQuotaPerSecUSec", "t", bus_property_get_usec, offsetof(CGroupContext, cpu_quota_per_sec_usec), 0), + SD_BUS_PROPERTY("IOAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, io_accounting), 0), + SD_BUS_PROPERTY("IOWeight", "t", NULL, offsetof(CGroupContext, io_weight), 0), + SD_BUS_PROPERTY("StartupIOWeight", "t", NULL, offsetof(CGroupContext, startup_io_weight), 0), + SD_BUS_PROPERTY("IODeviceWeight", "a(st)", property_get_io_device_weight, 0, 0), + SD_BUS_PROPERTY("IOReadBandwidthMax", "a(st)", property_get_io_device_limits, 0, 0), + SD_BUS_PROPERTY("IOWriteBandwidthMax", "a(st)", property_get_io_device_limits, 0, 0), + SD_BUS_PROPERTY("IOReadIOPSMax", "a(st)", property_get_io_device_limits, 0, 0), + SD_BUS_PROPERTY("IOWriteIOPSMax", "a(st)", property_get_io_device_limits, 0, 0), + SD_BUS_PROPERTY("BlockIOAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, blockio_accounting), 0), + SD_BUS_PROPERTY("BlockIOWeight", "t", NULL, offsetof(CGroupContext, blockio_weight), 0), + SD_BUS_PROPERTY("StartupBlockIOWeight", "t", NULL, offsetof(CGroupContext, startup_blockio_weight), 0), + SD_BUS_PROPERTY("BlockIODeviceWeight", "a(st)", property_get_blockio_device_weight, 0, 0), + SD_BUS_PROPERTY("BlockIOReadBandwidth", "a(st)", property_get_blockio_device_bandwidths, 0, 0), + SD_BUS_PROPERTY("BlockIOWriteBandwidth", "a(st)", property_get_blockio_device_bandwidths, 0, 0), + SD_BUS_PROPERTY("MemoryAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, memory_accounting), 0), + SD_BUS_PROPERTY("MemoryLimit", "t", NULL, offsetof(CGroupContext, memory_limit), 0), + SD_BUS_PROPERTY("DevicePolicy", "s", property_get_cgroup_device_policy, offsetof(CGroupContext, device_policy), 0), + SD_BUS_PROPERTY("DeviceAllow", "a(ss)", property_get_device_allow, 0, 0), + SD_BUS_PROPERTY("TasksAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, tasks_accounting), 0), + SD_BUS_PROPERTY("TasksMax", "t", NULL, offsetof(CGroupContext, tasks_max), 0), + SD_BUS_VTABLE_END +}; + +static int bus_cgroup_set_transient_property( + Unit *u, + CGroupContext *c, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + int r; + + assert(u); + assert(c); + assert(name); + assert(message); + + if (streq(name, "Delegate")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->delegate = b; + unit_write_drop_in_private(u, mode, name, b ? "Delegate=yes" : "Delegate=no"); + } + + return 1; + } + + return 0; +} + +int bus_cgroup_set_property( + Unit *u, + CGroupContext *c, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + CGroupIOLimitType iol_type; + int r; + + assert(u); + assert(c); + assert(name); + assert(message); + + if (streq(name, "CPUAccounting")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->cpu_accounting = b; + unit_invalidate_cgroup(u, CGROUP_MASK_CPUACCT|CGROUP_MASK_CPU); + unit_write_drop_in_private(u, mode, name, b ? "CPUAccounting=yes" : "CPUAccounting=no"); + } + + return 1; + + } else if (streq(name, "CPUShares")) { + uint64_t shares; + + r = sd_bus_message_read(message, "t", &shares); + if (r < 0) + return r; + + if (!CGROUP_CPU_SHARES_IS_OK(shares)) + return sd_bus_error_set_errnof(error, EINVAL, "CPUShares value out of range"); + + if (mode != UNIT_CHECK) { + c->cpu_shares = shares; + unit_invalidate_cgroup(u, CGROUP_MASK_CPU); + + if (shares == CGROUP_CPU_SHARES_INVALID) + unit_write_drop_in_private(u, mode, name, "CPUShares="); + else + unit_write_drop_in_private_format(u, mode, name, "CPUShares=%" PRIu64, shares); + } + + return 1; + + } else if (streq(name, "StartupCPUShares")) { + uint64_t shares; + + r = sd_bus_message_read(message, "t", &shares); + if (r < 0) + return r; + + if (!CGROUP_CPU_SHARES_IS_OK(shares)) + return sd_bus_error_set_errnof(error, EINVAL, "StartupCPUShares value out of range"); + + if (mode != UNIT_CHECK) { + c->startup_cpu_shares = shares; + unit_invalidate_cgroup(u, CGROUP_MASK_CPU); + + if (shares == CGROUP_CPU_SHARES_INVALID) + unit_write_drop_in_private(u, mode, name, "StartupCPUShares="); + else + unit_write_drop_in_private_format(u, mode, name, "StartupCPUShares=%" PRIu64, shares); + } + + return 1; + + } else if (streq(name, "CPUQuotaPerSecUSec")) { + uint64_t u64; + + r = sd_bus_message_read(message, "t", &u64); + if (r < 0) + return r; + + if (u64 <= 0) + return sd_bus_error_set_errnof(error, EINVAL, "CPUQuotaPerSecUSec value out of range"); + + if (mode != UNIT_CHECK) { + c->cpu_quota_per_sec_usec = u64; + unit_invalidate_cgroup(u, CGROUP_MASK_CPU); + unit_write_drop_in_private_format(u, mode, "CPUQuota", "CPUQuota=%0.f%%", (double) (c->cpu_quota_per_sec_usec / 10000)); + } + + return 1; + + } else if (streq(name, "IOAccounting")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->io_accounting = b; + unit_invalidate_cgroup(u, CGROUP_MASK_IO); + unit_write_drop_in_private(u, mode, name, b ? "IOAccounting=yes" : "IOAccounting=no"); + } + + return 1; + + } else if (streq(name, "IOWeight")) { + uint64_t weight; + + r = sd_bus_message_read(message, "t", &weight); + if (r < 0) + return r; + + if (!CGROUP_WEIGHT_IS_OK(weight)) + return sd_bus_error_set_errnof(error, EINVAL, "IOWeight value out of range"); + + if (mode != UNIT_CHECK) { + c->io_weight = weight; + unit_invalidate_cgroup(u, CGROUP_MASK_IO); + + if (weight == CGROUP_WEIGHT_INVALID) + unit_write_drop_in_private(u, mode, name, "IOWeight="); + else + unit_write_drop_in_private_format(u, mode, name, "IOWeight=%" PRIu64, weight); + } + + return 1; + + } else if (streq(name, "StartupIOWeight")) { + uint64_t weight; + + r = sd_bus_message_read(message, "t", &weight); + if (r < 0) + return r; + + if (CGROUP_WEIGHT_IS_OK(weight)) + return sd_bus_error_set_errnof(error, EINVAL, "StartupIOWeight value out of range"); + + if (mode != UNIT_CHECK) { + c->startup_io_weight = weight; + unit_invalidate_cgroup(u, CGROUP_MASK_IO); + + if (weight == CGROUP_WEIGHT_INVALID) + unit_write_drop_in_private(u, mode, name, "StartupIOWeight="); + else + unit_write_drop_in_private_format(u, mode, name, "StartupIOWeight=%" PRIu64, weight); + } + + return 1; + + } else if ((iol_type = cgroup_io_limit_type_from_string(name)) >= 0) { + const char *path; + unsigned n = 0; + uint64_t u64; + + r = sd_bus_message_enter_container(message, 'a', "(st)"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(message, "(st)", &path, &u64)) > 0) { + + if (mode != UNIT_CHECK) { + CGroupIODeviceLimit *a = NULL, *b; + + LIST_FOREACH(device_limits, b, c->io_device_limits) { + if (path_equal(path, b->path)) { + a = b; + break; + } + } + + if (!a) { + CGroupIOLimitType type; + + a = new0(CGroupIODeviceLimit, 1); + if (!a) + return -ENOMEM; + + a->path = strdup(path); + if (!a->path) { + free(a); + return -ENOMEM; + } + + for (type = 0; type < _CGROUP_IO_LIMIT_TYPE_MAX; type++) + a->limits[type] = cgroup_io_limit_defaults[type]; + + LIST_PREPEND(device_limits, c->io_device_limits, a); + } + + a->limits[iol_type] = u64; + } + + n++; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + CGroupIODeviceLimit *a; + _cleanup_free_ char *buf = NULL; + _cleanup_fclose_ FILE *f = NULL; + size_t size = 0; + + if (n == 0) { + LIST_FOREACH(device_limits, a, c->io_device_limits) + a->limits[iol_type] = cgroup_io_limit_defaults[iol_type]; + } + + unit_invalidate_cgroup(u, CGROUP_MASK_IO); + + f = open_memstream(&buf, &size); + if (!f) + return -ENOMEM; + + fprintf(f, "%s=\n", name); + LIST_FOREACH(device_limits, a, c->io_device_limits) + if (a->limits[iol_type] != cgroup_io_limit_defaults[iol_type]) + fprintf(f, "%s=%s %" PRIu64 "\n", name, a->path, a->limits[iol_type]); + + r = fflush_and_check(f); + if (r < 0) + return r; + unit_write_drop_in_private(u, mode, name, buf); + } + + return 1; + + } else if (streq(name, "IODeviceWeight")) { + const char *path; + uint64_t weight; + unsigned n = 0; + + r = sd_bus_message_enter_container(message, 'a', "(st)"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(message, "(st)", &path, &weight)) > 0) { + + if (!CGROUP_WEIGHT_IS_OK(weight) || weight == CGROUP_WEIGHT_INVALID) + return sd_bus_error_set_errnof(error, EINVAL, "IODeviceWeight out of range"); + + if (mode != UNIT_CHECK) { + CGroupIODeviceWeight *a = NULL, *b; + + LIST_FOREACH(device_weights, b, c->io_device_weights) { + if (path_equal(b->path, path)) { + a = b; + break; + } + } + + if (!a) { + a = new0(CGroupIODeviceWeight, 1); + if (!a) + return -ENOMEM; + + a->path = strdup(path); + if (!a->path) { + free(a); + return -ENOMEM; + } + LIST_PREPEND(device_weights,c->io_device_weights, a); + } + + a->weight = weight; + } + + n++; + } + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *buf = NULL; + _cleanup_fclose_ FILE *f = NULL; + CGroupIODeviceWeight *a; + size_t size = 0; + + if (n == 0) { + while (c->io_device_weights) + cgroup_context_free_io_device_weight(c, c->io_device_weights); + } + + unit_invalidate_cgroup(u, CGROUP_MASK_IO); + + f = open_memstream(&buf, &size); + if (!f) + return -ENOMEM; + + fputs("IODeviceWeight=\n", f); + LIST_FOREACH(device_weights, a, c->io_device_weights) + fprintf(f, "IODeviceWeight=%s %" PRIu64 "\n", a->path, a->weight); + + r = fflush_and_check(f); + if (r < 0) + return r; + unit_write_drop_in_private(u, mode, name, buf); + } + + return 1; + + } else if (streq(name, "BlockIOAccounting")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->blockio_accounting = b; + unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); + unit_write_drop_in_private(u, mode, name, b ? "BlockIOAccounting=yes" : "BlockIOAccounting=no"); + } + + return 1; + + } else if (streq(name, "BlockIOWeight")) { + uint64_t weight; + + r = sd_bus_message_read(message, "t", &weight); + if (r < 0) + return r; + + if (!CGROUP_BLKIO_WEIGHT_IS_OK(weight)) + return sd_bus_error_set_errnof(error, EINVAL, "BlockIOWeight value out of range"); + + if (mode != UNIT_CHECK) { + c->blockio_weight = weight; + unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); + + if (weight == CGROUP_BLKIO_WEIGHT_INVALID) + unit_write_drop_in_private(u, mode, name, "BlockIOWeight="); + else + unit_write_drop_in_private_format(u, mode, name, "BlockIOWeight=%" PRIu64, weight); + } + + return 1; + + } else if (streq(name, "StartupBlockIOWeight")) { + uint64_t weight; + + r = sd_bus_message_read(message, "t", &weight); + if (r < 0) + return r; + + if (CGROUP_BLKIO_WEIGHT_IS_OK(weight)) + return sd_bus_error_set_errnof(error, EINVAL, "StartupBlockIOWeight value out of range"); + + if (mode != UNIT_CHECK) { + c->startup_blockio_weight = weight; + unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); + + if (weight == CGROUP_BLKIO_WEIGHT_INVALID) + unit_write_drop_in_private(u, mode, name, "StartupBlockIOWeight="); + else + unit_write_drop_in_private_format(u, mode, name, "StartupBlockIOWeight=%" PRIu64, weight); + } + + return 1; + + } else if (streq(name, "BlockIOReadBandwidth") || streq(name, "BlockIOWriteBandwidth")) { + const char *path; + bool read = true; + unsigned n = 0; + uint64_t u64; + + if (streq(name, "BlockIOWriteBandwidth")) + read = false; + + r = sd_bus_message_enter_container(message, 'a', "(st)"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(message, "(st)", &path, &u64)) > 0) { + + if (mode != UNIT_CHECK) { + CGroupBlockIODeviceBandwidth *a = NULL, *b; + + LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { + if (path_equal(path, b->path)) { + a = b; + break; + } + } + + if (!a) { + a = new0(CGroupBlockIODeviceBandwidth, 1); + if (!a) + return -ENOMEM; + + a->rbps = CGROUP_LIMIT_MAX; + a->wbps = CGROUP_LIMIT_MAX; + a->path = strdup(path); + if (!a->path) { + free(a); + return -ENOMEM; + } + + LIST_PREPEND(device_bandwidths, c->blockio_device_bandwidths, a); + } + + if (read) + a->rbps = u64; + else + a->wbps = u64; + } + + n++; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + CGroupBlockIODeviceBandwidth *a; + _cleanup_free_ char *buf = NULL; + _cleanup_fclose_ FILE *f = NULL; + size_t size = 0; + + if (n == 0) { + LIST_FOREACH(device_bandwidths, a, c->blockio_device_bandwidths) { + if (read) + a->rbps = CGROUP_LIMIT_MAX; + else + a->wbps = CGROUP_LIMIT_MAX; + } + } + + unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); + + f = open_memstream(&buf, &size); + if (!f) + return -ENOMEM; + + if (read) { + fputs("BlockIOReadBandwidth=\n", f); + LIST_FOREACH(device_bandwidths, a, c->blockio_device_bandwidths) + if (a->rbps != CGROUP_LIMIT_MAX) + fprintf(f, "BlockIOReadBandwidth=%s %" PRIu64 "\n", a->path, a->rbps); + } else { + fputs("BlockIOWriteBandwidth=\n", f); + LIST_FOREACH(device_bandwidths, a, c->blockio_device_bandwidths) + if (a->wbps != CGROUP_LIMIT_MAX) + fprintf(f, "BlockIOWriteBandwidth=%s %" PRIu64 "\n", a->path, a->wbps); + } + + r = fflush_and_check(f); + if (r < 0) + return r; + unit_write_drop_in_private(u, mode, name, buf); + } + + return 1; + + } else if (streq(name, "BlockIODeviceWeight")) { + const char *path; + uint64_t weight; + unsigned n = 0; + + r = sd_bus_message_enter_container(message, 'a', "(st)"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(message, "(st)", &path, &weight)) > 0) { + + if (!CGROUP_BLKIO_WEIGHT_IS_OK(weight) || weight == CGROUP_BLKIO_WEIGHT_INVALID) + return sd_bus_error_set_errnof(error, EINVAL, "BlockIODeviceWeight out of range"); + + if (mode != UNIT_CHECK) { + CGroupBlockIODeviceWeight *a = NULL, *b; + + LIST_FOREACH(device_weights, b, c->blockio_device_weights) { + if (path_equal(b->path, path)) { + a = b; + break; + } + } + + if (!a) { + a = new0(CGroupBlockIODeviceWeight, 1); + if (!a) + return -ENOMEM; + + a->path = strdup(path); + if (!a->path) { + free(a); + return -ENOMEM; + } + LIST_PREPEND(device_weights,c->blockio_device_weights, a); + } + + a->weight = weight; + } + + n++; + } + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *buf = NULL; + _cleanup_fclose_ FILE *f = NULL; + CGroupBlockIODeviceWeight *a; + size_t size = 0; + + if (n == 0) { + while (c->blockio_device_weights) + cgroup_context_free_blockio_device_weight(c, c->blockio_device_weights); + } + + unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); + + f = open_memstream(&buf, &size); + if (!f) + return -ENOMEM; + + fputs("BlockIODeviceWeight=\n", f); + LIST_FOREACH(device_weights, a, c->blockio_device_weights) + fprintf(f, "BlockIODeviceWeight=%s %" PRIu64 "\n", a->path, a->weight); + + r = fflush_and_check(f); + if (r < 0) + return r; + unit_write_drop_in_private(u, mode, name, buf); + } + + return 1; + + } else if (streq(name, "MemoryAccounting")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->memory_accounting = b; + unit_invalidate_cgroup(u, CGROUP_MASK_MEMORY); + unit_write_drop_in_private(u, mode, name, b ? "MemoryAccounting=yes" : "MemoryAccounting=no"); + } + + return 1; + + } else if (streq(name, "MemoryLimit")) { + uint64_t limit; + + r = sd_bus_message_read(message, "t", &limit); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->memory_limit = limit; + unit_invalidate_cgroup(u, CGROUP_MASK_MEMORY); + + if (limit == (uint64_t) -1) + unit_write_drop_in_private(u, mode, name, "MemoryLimit=infinity"); + else + unit_write_drop_in_private_format(u, mode, name, "MemoryLimit=%" PRIu64, limit); + } + + return 1; + + } else if (streq(name, "DevicePolicy")) { + const char *policy; + CGroupDevicePolicy p; + + r = sd_bus_message_read(message, "s", &policy); + if (r < 0) + return r; + + p = cgroup_device_policy_from_string(policy); + if (p < 0) + return -EINVAL; + + if (mode != UNIT_CHECK) { + char *buf; + + c->device_policy = p; + unit_invalidate_cgroup(u, CGROUP_MASK_DEVICES); + + buf = strjoina("DevicePolicy=", policy); + unit_write_drop_in_private(u, mode, name, buf); + } + + return 1; + + } else if (streq(name, "DeviceAllow")) { + const char *path, *rwm; + unsigned n = 0; + + r = sd_bus_message_enter_container(message, 'a', "(ss)"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(message, "(ss)", &path, &rwm)) > 0) { + + if ((!startswith(path, "/dev/") && + !startswith(path, "block-") && + !startswith(path, "char-")) || + strpbrk(path, WHITESPACE)) + return sd_bus_error_set_errnof(error, EINVAL, "DeviceAllow= requires device node"); + + if (isempty(rwm)) + rwm = "rwm"; + + if (!in_charset(rwm, "rwm")) + return sd_bus_error_set_errnof(error, EINVAL, "DeviceAllow= requires combination of rwm flags"); + + if (mode != UNIT_CHECK) { + CGroupDeviceAllow *a = NULL, *b; + + LIST_FOREACH(device_allow, b, c->device_allow) { + if (path_equal(b->path, path)) { + a = b; + break; + } + } + + if (!a) { + a = new0(CGroupDeviceAllow, 1); + if (!a) + return -ENOMEM; + + a->path = strdup(path); + if (!a->path) { + free(a); + return -ENOMEM; + } + + LIST_PREPEND(device_allow, c->device_allow, a); + } + + a->r = !!strchr(rwm, 'r'); + a->w = !!strchr(rwm, 'w'); + a->m = !!strchr(rwm, 'm'); + } + + n++; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *buf = NULL; + _cleanup_fclose_ FILE *f = NULL; + CGroupDeviceAllow *a; + size_t size = 0; + + if (n == 0) { + while (c->device_allow) + cgroup_context_free_device_allow(c, c->device_allow); + } + + unit_invalidate_cgroup(u, CGROUP_MASK_DEVICES); + + f = open_memstream(&buf, &size); + if (!f) + return -ENOMEM; + + fputs("DeviceAllow=\n", f); + LIST_FOREACH(device_allow, a, c->device_allow) + fprintf(f, "DeviceAllow=%s %s%s%s\n", a->path, a->r ? "r" : "", a->w ? "w" : "", a->m ? "m" : ""); + + r = fflush_and_check(f); + if (r < 0) + return r; + unit_write_drop_in_private(u, mode, name, buf); + } + + return 1; + + } else if (streq(name, "TasksAccounting")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->tasks_accounting = b; + unit_invalidate_cgroup(u, CGROUP_MASK_PIDS); + unit_write_drop_in_private(u, mode, name, b ? "TasksAccounting=yes" : "TasksAccounting=no"); + } + + return 1; + + } else if (streq(name, "TasksMax")) { + uint64_t limit; + + r = sd_bus_message_read(message, "t", &limit); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->tasks_max = limit; + unit_invalidate_cgroup(u, CGROUP_MASK_PIDS); + + if (limit == (uint64_t) -1) + unit_write_drop_in_private(u, mode, name, "TasksMax=infinity"); + else + unit_write_drop_in_private_format(u, mode, name, "TasksMax=%" PRIu64, limit); + } + + return 1; + } + + if (u->transient && u->load_state == UNIT_STUB) { + r = bus_cgroup_set_transient_property(u, c, name, message, mode, error); + if (r != 0) + return r; + + } + + return 0; +} diff --git a/src/libcore/dbus-cgroup.h b/src/libcore/dbus-cgroup.h new file mode 100644 index 0000000000..84d0f1ba04 --- /dev/null +++ b/src/libcore/dbus-cgroup.h @@ -0,0 +1,28 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 "cgroup.h" + +extern const sd_bus_vtable bus_cgroup_vtable[]; + +int bus_cgroup_set_property(Unit *u, CGroupContext *c, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); diff --git a/src/libcore/dbus-device.c b/src/libcore/dbus-device.c new file mode 100644 index 0000000000..e1a12224d3 --- /dev/null +++ b/src/libcore/dbus-device.c @@ -0,0 +1,28 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 "dbus-device.h" +#include "device.h" +#include "unit.h" + +const sd_bus_vtable bus_device_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("SysFSPath", "s", NULL, offsetof(Device, sysfs), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_VTABLE_END +}; diff --git a/src/libcore/dbus-device.h b/src/libcore/dbus-device.h new file mode 100644 index 0000000000..eb1d8c3278 --- /dev/null +++ b/src/libcore/dbus-device.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "unit.h" + +extern const sd_bus_vtable bus_device_vtable[]; diff --git a/src/libcore/dbus-execute.c b/src/libcore/dbus-execute.c new file mode 100644 index 0000000000..06943c6365 --- /dev/null +++ b/src/libcore/dbus-execute.c @@ -0,0 +1,1545 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 + +#ifdef HAVE_SECCOMP +#include +#endif + +#include "af-list.h" +#include "alloc-util.h" +#include "bus-util.h" +#include "capability-util.h" +#include "dbus-execute.h" +#include "env-util.h" +#include "execute.h" +#include "fd-util.h" +#include "fileio.h" +#include "ioprio.h" +#include "missing.h" +#include "namespace.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "rlimit-util.h" +#ifdef HAVE_SECCOMP +#include "seccomp-util.h" +#endif +#include "strv.h" +#include "syslog-util.h" +#include "utf8.h" + +BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_exec_output, exec_output, ExecOutput); + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_input, exec_input, ExecInput); + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_utmp_mode, exec_utmp_mode, ExecUtmpMode); + +static BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_protect_home, protect_home, ProtectHome); +static BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_protect_system, protect_system, ProtectSystem); + +static int property_get_environment_files( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + char **j; + int r; + + assert(bus); + assert(reply); + assert(c); + + r = sd_bus_message_open_container(reply, 'a', "(sb)"); + if (r < 0) + return r; + + STRV_FOREACH(j, c->environment_files) { + const char *fn = *j; + + r = sd_bus_message_append(reply, "(sb)", fn[0] == '-' ? fn + 1 : fn, fn[0] == '-'); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_oom_score_adjust( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + + ExecContext *c = userdata; + int32_t n; + + assert(bus); + assert(reply); + assert(c); + + if (c->oom_score_adjust_set) + n = c->oom_score_adjust; + else { + _cleanup_free_ char *t = NULL; + + n = 0; + if (read_one_line_file("/proc/self/oom_score_adj", &t) >= 0) + safe_atoi32(t, &n); + } + + return sd_bus_message_append(reply, "i", n); +} + +static int property_get_nice( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + + ExecContext *c = userdata; + int32_t n; + + assert(bus); + assert(reply); + assert(c); + + if (c->nice_set) + n = c->nice; + else { + errno = 0; + n = getpriority(PRIO_PROCESS, 0); + if (errno > 0) + n = 0; + } + + return sd_bus_message_append(reply, "i", n); +} + +static int property_get_ioprio( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + + ExecContext *c = userdata; + int32_t n; + + assert(bus); + assert(reply); + assert(c); + + if (c->ioprio_set) + n = c->ioprio; + else { + n = ioprio_get(IOPRIO_WHO_PROCESS, 0); + if (n < 0) + n = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 4); + } + + return sd_bus_message_append(reply, "i", n); +} + +static int property_get_cpu_sched_policy( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + int32_t n; + + assert(bus); + assert(reply); + assert(c); + + if (c->cpu_sched_set) + n = c->cpu_sched_policy; + else { + n = sched_getscheduler(0); + if (n < 0) + n = SCHED_OTHER; + } + + return sd_bus_message_append(reply, "i", n); +} + +static int property_get_cpu_sched_priority( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + int32_t n; + + assert(bus); + assert(reply); + assert(c); + + if (c->cpu_sched_set) + n = c->cpu_sched_priority; + else { + struct sched_param p = {}; + + if (sched_getparam(0, &p) >= 0) + n = p.sched_priority; + else + n = 0; + } + + return sd_bus_message_append(reply, "i", n); +} + +static int property_get_cpu_affinity( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + if (c->cpuset) + return sd_bus_message_append_array(reply, 'y', c->cpuset, CPU_ALLOC_SIZE(c->cpuset_ncpus)); + else + return sd_bus_message_append_array(reply, 'y', NULL, 0); +} + +static int property_get_timer_slack_nsec( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + uint64_t u; + + assert(bus); + assert(reply); + assert(c); + + if (c->timer_slack_nsec != NSEC_INFINITY) + u = (uint64_t) c->timer_slack_nsec; + else + u = (uint64_t) prctl(PR_GET_TIMERSLACK); + + return sd_bus_message_append(reply, "t", u); +} + +static int property_get_capability_bounding_set( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "t", c->capability_bounding_set); +} + +static int property_get_ambient_capabilities( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "t", c->capability_ambient_set); +} + +static int property_get_empty_string( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + assert(bus); + assert(reply); + + return sd_bus_message_append(reply, "s", ""); +} + +static int property_get_syscall_filter( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + _cleanup_strv_free_ char **l = NULL; + int r; + +#ifdef HAVE_SECCOMP + Iterator i; + void *id; +#endif + + assert(bus); + assert(reply); + assert(c); + + r = sd_bus_message_open_container(reply, 'r', "bas"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "b", c->syscall_whitelist); + if (r < 0) + return r; + +#ifdef HAVE_SECCOMP + SET_FOREACH(id, c->syscall_filter, i) { + char *name; + + name = seccomp_syscall_resolve_num_arch(SCMP_ARCH_NATIVE, PTR_TO_INT(id) - 1); + if (!name) + continue; + + r = strv_consume(&l, name); + if (r < 0) + return r; + } +#endif + + strv_sort(l); + + r = sd_bus_message_append_strv(reply, l); + if (r < 0) + return r; + + return sd_bus_message_close_container(reply); +} + +static int property_get_syscall_archs( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + _cleanup_strv_free_ char **l = NULL; + int r; + +#ifdef HAVE_SECCOMP + Iterator i; + void *id; +#endif + + assert(bus); + assert(reply); + assert(c); + +#ifdef HAVE_SECCOMP + SET_FOREACH(id, c->syscall_archs, i) { + const char *name; + + name = seccomp_arch_to_string(PTR_TO_UINT32(id) - 1); + if (!name) + continue; + + r = strv_extend(&l, name); + if (r < 0) + return -ENOMEM; + } +#endif + + strv_sort(l); + + r = sd_bus_message_append_strv(reply, l); + if (r < 0) + return r; + + return 0; +} + +static int property_get_syscall_errno( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "i", (int32_t) c->syscall_errno); +} + +static int property_get_selinux_context( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "(bs)", c->selinux_context_ignore, c->selinux_context); +} + +static int property_get_apparmor_profile( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "(bs)", c->apparmor_profile_ignore, c->apparmor_profile); +} + +static int property_get_smack_process_label( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "(bs)", c->smack_process_label_ignore, c->smack_process_label); +} + +static int property_get_personality( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "s", personality_to_string(c->personality)); +} + +static int property_get_address_families( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + _cleanup_strv_free_ char **l = NULL; + Iterator i; + void *af; + int r; + + assert(bus); + assert(reply); + assert(c); + + r = sd_bus_message_open_container(reply, 'r', "bas"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "b", c->address_families_whitelist); + if (r < 0) + return r; + + SET_FOREACH(af, c->address_families, i) { + const char *name; + + name = af_to_name(PTR_TO_INT(af)); + if (!name) + continue; + + r = strv_extend(&l, name); + if (r < 0) + return -ENOMEM; + } + + strv_sort(l); + + r = sd_bus_message_append_strv(reply, l); + if (r < 0) + return r; + + return sd_bus_message_close_container(reply); +} + +static int property_get_working_directory( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + const char *wd; + + assert(bus); + assert(reply); + assert(c); + + if (c->working_directory_home) + wd = "~"; + else + wd = c->working_directory; + + if (c->working_directory_missing_ok) + wd = strjoina("!", wd); + + return sd_bus_message_append(reply, "s", wd); +} + +static int property_get_syslog_level( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "i", LOG_PRI(c->syslog_priority)); +} + +static int property_get_syslog_facility( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "i", LOG_FAC(c->syslog_priority)); +} + +const sd_bus_vtable bus_exec_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("EnvironmentFiles", "a(sb)", property_get_environment_files, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PassEnvironment", "as", NULL, offsetof(ExecContext, pass_environment), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("UMask", "u", bus_property_get_mode, offsetof(ExecContext, umask), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitCPU", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitCPUSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitFSIZE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitFSIZESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitDATA", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitDATASoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitSTACK", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitSTACKSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitCORE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitCORESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitRSS", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitRSSSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitNOFILE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitNOFILESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitAS", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitASSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitNPROC", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitNPROCSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitMEMLOCK", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitMEMLOCKSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitLOCKS", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitLOCKSSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitSIGPENDING", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitSIGPENDINGSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitMSGQUEUE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitMSGQUEUESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitNICE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitNICESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitRTPRIO", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitRTPRIOSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitRTTIME", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitRTTIMESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("WorkingDirectory", "s", property_get_working_directory, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RootDirectory", "s", NULL, offsetof(ExecContext, root_directory), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("OOMScoreAdjust", "i", property_get_oom_score_adjust, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Nice", "i", property_get_nice, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("IOScheduling", "i", property_get_ioprio, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CPUSchedulingPolicy", "i", property_get_cpu_sched_policy, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CPUSchedulingPriority", "i", property_get_cpu_sched_priority, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CPUAffinity", "ay", property_get_cpu_affinity, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TimerSlackNSec", "t", property_get_timer_slack_nsec, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CPUSchedulingResetOnFork", "b", bus_property_get_bool, offsetof(ExecContext, cpu_sched_reset_on_fork), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("NonBlocking", "b", bus_property_get_bool, offsetof(ExecContext, non_blocking), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StandardInput", "s", property_get_exec_input, offsetof(ExecContext, std_input), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StandardOutput", "s", bus_property_get_exec_output, offsetof(ExecContext, std_output), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StandardError", "s", bus_property_get_exec_output, offsetof(ExecContext, std_error), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TTYPath", "s", NULL, offsetof(ExecContext, tty_path), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TTYReset", "b", bus_property_get_bool, offsetof(ExecContext, tty_reset), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TTYVHangup", "b", bus_property_get_bool, offsetof(ExecContext, tty_vhangup), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TTYVTDisallocate", "b", bus_property_get_bool, offsetof(ExecContext, tty_vt_disallocate), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SyslogPriority", "i", bus_property_get_int, offsetof(ExecContext, syslog_priority), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SyslogIdentifier", "s", NULL, offsetof(ExecContext, syslog_identifier), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SyslogLevelPrefix", "b", bus_property_get_bool, offsetof(ExecContext, syslog_level_prefix), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SyslogLevel", "i", property_get_syslog_level, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SyslogFacility", "i", property_get_syslog_facility, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Capabilities", "s", property_get_empty_string, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("SecureBits", "i", bus_property_get_int, offsetof(ExecContext, secure_bits), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CapabilityBoundingSet", "t", property_get_capability_bounding_set, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("AmbientCapabilities", "t", property_get_ambient_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("User", "s", NULL, offsetof(ExecContext, user), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Group", "s", NULL, offsetof(ExecContext, group), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PAMName", "s", NULL, offsetof(ExecContext, pam_name), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ReadWriteDirectories", "as", NULL, offsetof(ExecContext, read_write_dirs), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ReadOnlyDirectories", "as", NULL, offsetof(ExecContext, read_only_dirs), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("InaccessibleDirectories", "as", NULL, offsetof(ExecContext, inaccessible_dirs), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("MountFlags", "t", bus_property_get_ulong, offsetof(ExecContext, mount_flags), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PrivateTmp", "b", bus_property_get_bool, offsetof(ExecContext, private_tmp), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PrivateNetwork", "b", bus_property_get_bool, offsetof(ExecContext, private_network), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PrivateDevices", "b", bus_property_get_bool, offsetof(ExecContext, private_devices), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ProtectHome", "s", bus_property_get_protect_home, offsetof(ExecContext, protect_home), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ProtectSystem", "s", bus_property_get_protect_system, offsetof(ExecContext, protect_system), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SameProcessGroup", "b", bus_property_get_bool, offsetof(ExecContext, same_pgrp), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("UtmpIdentifier", "s", NULL, offsetof(ExecContext, utmp_id), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("UtmpMode", "s", property_get_exec_utmp_mode, offsetof(ExecContext, utmp_mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SELinuxContext", "(bs)", property_get_selinux_context, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("AppArmorProfile", "(bs)", property_get_apparmor_profile, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SmackProcessLabel", "(bs)", property_get_smack_process_label, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("IgnoreSIGPIPE", "b", bus_property_get_bool, offsetof(ExecContext, ignore_sigpipe), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("NoNewPrivileges", "b", bus_property_get_bool, offsetof(ExecContext, no_new_privileges), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SystemCallFilter", "(bas)", property_get_syscall_filter, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SystemCallArchitectures", "as", property_get_syscall_archs, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SystemCallErrorNumber", "i", property_get_syscall_errno, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Personality", "s", property_get_personality, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RestrictAddressFamilies", "(bas)", property_get_address_families, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RuntimeDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, runtime_directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RuntimeDirectory", "as", NULL, offsetof(ExecContext, runtime_directory), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_VTABLE_END +}; + +static int append_exec_command(sd_bus_message *reply, ExecCommand *c) { + int r; + + assert(reply); + assert(c); + + if (!c->path) + return 0; + + r = sd_bus_message_open_container(reply, 'r', "sasbttttuii"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "s", c->path); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(reply, c->argv); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "bttttuii", + c->ignore, + c->exec_status.start_timestamp.realtime, + c->exec_status.start_timestamp.monotonic, + c->exec_status.exit_timestamp.realtime, + c->exec_status.exit_timestamp.monotonic, + (uint32_t) c->exec_status.pid, + (int32_t) c->exec_status.code, + (int32_t) c->exec_status.status); + if (r < 0) + return r; + + return sd_bus_message_close_container(reply); +} + +int bus_property_get_exec_command( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *ret_error) { + + ExecCommand *c = (ExecCommand*) userdata; + int r; + + assert(bus); + assert(reply); + + r = sd_bus_message_open_container(reply, 'a', "(sasbttttuii)"); + if (r < 0) + return r; + + r = append_exec_command(reply, c); + if (r < 0) + return r; + + return sd_bus_message_close_container(reply); +} + +int bus_property_get_exec_command_list( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *ret_error) { + + ExecCommand *c = *(ExecCommand**) userdata; + int r; + + assert(bus); + assert(reply); + + r = sd_bus_message_open_container(reply, 'a', "(sasbttttuii)"); + if (r < 0) + return r; + + LIST_FOREACH(command, c, c) { + r = append_exec_command(reply, c); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +int bus_exec_context_set_transient_property( + Unit *u, + ExecContext *c, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + const char *soft = NULL; + int r, ri; + + assert(u); + assert(c); + assert(name); + assert(message); + + if (streq(name, "User")) { + const char *uu; + + r = sd_bus_message_read(message, "s", &uu); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + + if (isempty(uu)) + c->user = mfree(c->user); + else if (free_and_strdup(&c->user, uu) < 0) + return -ENOMEM; + + unit_write_drop_in_private_format(u, mode, name, "User=%s\n", uu); + } + + return 1; + + } else if (streq(name, "Group")) { + const char *gg; + + r = sd_bus_message_read(message, "s", &gg); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + + if (isempty(gg)) + c->group = mfree(c->group); + else if (free_and_strdup(&c->group, gg) < 0) + return -ENOMEM; + + unit_write_drop_in_private_format(u, mode, name, "Group=%s\n", gg); + } + + return 1; + } else if (streq(name, "SyslogIdentifier")) { + const char *id; + + r = sd_bus_message_read(message, "s", &id); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + + if (isempty(id)) + c->syslog_identifier = mfree(c->syslog_identifier); + else if (free_and_strdup(&c->syslog_identifier, id) < 0) + return -ENOMEM; + + unit_write_drop_in_private_format(u, mode, name, "SyslogIdentifier=%s\n", id); + } + + return 1; + } else if (streq(name, "SyslogLevel")) { + int level; + + r = sd_bus_message_read(message, "i", &level); + if (r < 0) + return r; + + if (!log_level_is_valid(level)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Log level value out of range"); + + if (mode != UNIT_CHECK) { + c->syslog_priority = (c->syslog_priority & LOG_FACMASK) | level; + unit_write_drop_in_private_format(u, mode, name, "SyslogLevel=%i\n", level); + } + + return 1; + } else if (streq(name, "SyslogFacility")) { + int facility; + + r = sd_bus_message_read(message, "i", &facility); + if (r < 0) + return r; + + if (!log_facility_unshifted_is_valid(facility)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Log facility value out of range"); + + if (mode != UNIT_CHECK) { + c->syslog_priority = (facility << 3) | LOG_PRI(c->syslog_priority); + unit_write_drop_in_private_format(u, mode, name, "SyslogFacility=%i\n", facility); + } + + return 1; + } else if (streq(name, "Nice")) { + int n; + + r = sd_bus_message_read(message, "i", &n); + if (r < 0) + return r; + + if (n < PRIO_MIN || n >= PRIO_MAX) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Nice value out of range"); + + if (mode != UNIT_CHECK) { + c->nice = n; + unit_write_drop_in_private_format(u, mode, name, "Nice=%i\n", n); + } + + return 1; + + } else if (STR_IN_SET(name, "TTYPath", "RootDirectory")) { + const char *s; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + if (!path_is_absolute(s)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "%s takes an absolute path", name); + + if (mode != UNIT_CHECK) { + if (streq(name, "TTYPath")) + r = free_and_strdup(&c->tty_path, s); + else { + assert(streq(name, "RootDirectory")); + r = free_and_strdup(&c->root_directory, s); + } + if (r < 0) + return r; + + unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, s); + } + + return 1; + + } else if (streq(name, "WorkingDirectory")) { + const char *s; + bool missing_ok; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + if (s[0] == '-') { + missing_ok = true; + s++; + } else + missing_ok = false; + + if (!streq(s, "~") && !path_is_absolute(s)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "WorkingDirectory= expects an absolute path or '~'"); + + if (mode != UNIT_CHECK) { + if (streq(s, "~")) { + c->working_directory = mfree(c->working_directory); + c->working_directory_home = true; + } else { + r = free_and_strdup(&c->working_directory, s); + if (r < 0) + return r; + + c->working_directory_home = false; + } + + c->working_directory_missing_ok = missing_ok; + unit_write_drop_in_private_format(u, mode, name, "WorkingDirectory=%s%s", missing_ok ? "-" : "", s); + } + + return 1; + + } else if (streq(name, "StandardInput")) { + const char *s; + ExecInput p; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + p = exec_input_from_string(s); + if (p < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid standard input name"); + + if (mode != UNIT_CHECK) { + c->std_input = p; + + unit_write_drop_in_private_format(u, mode, name, "StandardInput=%s\n", exec_input_to_string(p)); + } + + return 1; + + + } else if (streq(name, "StandardOutput")) { + const char *s; + ExecOutput p; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + p = exec_output_from_string(s); + if (p < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid standard output name"); + + if (mode != UNIT_CHECK) { + c->std_output = p; + + unit_write_drop_in_private_format(u, mode, name, "StandardOutput=%s\n", exec_output_to_string(p)); + } + + return 1; + + } else if (streq(name, "StandardError")) { + const char *s; + ExecOutput p; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + p = exec_output_from_string(s); + if (p < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid standard error name"); + + if (mode != UNIT_CHECK) { + c->std_error = p; + + unit_write_drop_in_private_format(u, mode, name, "StandardError=%s\n", exec_output_to_string(p)); + } + + return 1; + + } else if (STR_IN_SET(name, + "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", + "PrivateTmp", "PrivateDevices", "PrivateNetwork", + "NoNewPrivileges", "SyslogLevelPrefix")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + if (streq(name, "IgnoreSIGPIPE")) + c->ignore_sigpipe = b; + else if (streq(name, "TTYVHangup")) + c->tty_vhangup = b; + else if (streq(name, "TTYReset")) + c->tty_reset = b; + else if (streq(name, "PrivateTmp")) + c->private_tmp = b; + else if (streq(name, "PrivateDevices")) + c->private_devices = b; + else if (streq(name, "PrivateNetwork")) + c->private_network = b; + else if (streq(name, "NoNewPrivileges")) + c->no_new_privileges = b; + else if (streq(name, "SyslogLevelPrefix")) + c->syslog_level_prefix = b; + + unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, yes_no(b)); + } + + return 1; + + } else if (streq(name, "UtmpIdentifier")) { + const char *id; + + r = sd_bus_message_read(message, "s", &id); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + if (isempty(id)) + c->utmp_id = mfree(c->utmp_id); + else if (free_and_strdup(&c->utmp_id, id) < 0) + return -ENOMEM; + + unit_write_drop_in_private_format(u, mode, name, "UtmpIdentifier=%s\n", strempty(id)); + } + + return 1; + + } else if (streq(name, "UtmpMode")) { + const char *s; + ExecUtmpMode m; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + m = exec_utmp_mode_from_string(s); + if (m < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid utmp mode"); + + if (mode != UNIT_CHECK) { + c->utmp_mode = m; + + unit_write_drop_in_private_format(u, mode, name, "UtmpMode=%s\n", exec_utmp_mode_to_string(m)); + } + + return 1; + + } else if (streq(name, "PAMName")) { + const char *n; + + r = sd_bus_message_read(message, "s", &n); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + if (isempty(n)) + c->pam_name = mfree(c->pam_name); + else if (free_and_strdup(&c->pam_name, n) < 0) + return -ENOMEM; + + unit_write_drop_in_private_format(u, mode, name, "PAMName=%s\n", strempty(n)); + } + + return 1; + + } else if (streq(name, "Environment")) { + + _cleanup_strv_free_ char **l = NULL; + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + if (!strv_env_is_valid(l)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment block."); + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *joined = NULL; + char **e; + + if (strv_length(l) == 0) { + c->environment = strv_free(c->environment); + unit_write_drop_in_private_format(u, mode, name, "Environment=\n"); + } else { + e = strv_env_merge(2, c->environment, l); + if (!e) + return -ENOMEM; + + strv_free(c->environment); + c->environment = e; + + joined = strv_join_quoted(c->environment); + if (!joined) + return -ENOMEM; + + unit_write_drop_in_private_format(u, mode, name, "Environment=%s\n", joined); + } + } + + return 1; + + } else if (streq(name, "TimerSlackNSec")) { + + nsec_t n; + + r = sd_bus_message_read(message, "t", &n); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->timer_slack_nsec = n; + unit_write_drop_in_private_format(u, mode, name, "TimerSlackNSec=" NSEC_FMT "\n", n); + } + + return 1; + + } else if (streq(name, "OOMScoreAdjust")) { + int oa; + + r = sd_bus_message_read(message, "i", &oa); + if (r < 0) + return r; + + if (!oom_score_adjust_is_valid(oa)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "OOM score adjust value out of range"); + + if (mode != UNIT_CHECK) { + c->oom_score_adjust = oa; + c->oom_score_adjust_set = true; + unit_write_drop_in_private_format(u, mode, name, "OOMScoreAdjust=%i\n", oa); + } + + return 1; + + } else if (streq(name, "EnvironmentFiles")) { + + _cleanup_free_ char *joined = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char **l = NULL; + size_t size = 0; + char **i; + + r = sd_bus_message_enter_container(message, 'a', "(sb)"); + if (r < 0) + return r; + + f = open_memstream(&joined, &size); + if (!f) + return -ENOMEM; + + STRV_FOREACH(i, c->environment_files) + fprintf(f, "EnvironmentFile=%s\n", *i); + + while ((r = sd_bus_message_enter_container(message, 'r', "sb")) > 0) { + const char *path; + int b; + + r = sd_bus_message_read(message, "sb", &path, &b); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (!isempty(path) && !path_is_absolute(path)) + return sd_bus_error_set_errnof(error, EINVAL, "Path %s is not absolute.", path); + + if (mode != UNIT_CHECK) { + char *buf = NULL; + + buf = strjoin(b ? "-" : "", path, NULL); + if (!buf) + return -ENOMEM; + + fprintf(f, "EnvironmentFile=%s\n", buf); + + r = strv_consume(&l, buf); + if (r < 0) + return r; + } + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + r = fflush_and_check(f); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + if (strv_isempty(l)) { + c->environment_files = strv_free(c->environment_files); + unit_write_drop_in_private(u, mode, name, "EnvironmentFile=\n"); + } else { + r = strv_extend_strv(&c->environment_files, l, true); + if (r < 0) + return r; + + unit_write_drop_in_private(u, mode, name, joined); + } + } + + return 1; + + } else if (streq(name, "PassEnvironment")) { + + _cleanup_strv_free_ char **l = NULL; + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + if (!strv_env_name_is_valid(l)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid PassEnvironment block."); + + if (mode != UNIT_CHECK) { + if (strv_isempty(l)) { + c->pass_environment = strv_free(c->pass_environment); + unit_write_drop_in_private_format(u, mode, name, "PassEnvironment=\n"); + } else { + _cleanup_free_ char *joined = NULL; + + r = strv_extend_strv(&c->pass_environment, l, true); + if (r < 0) + return r; + + joined = strv_join_quoted(c->pass_environment); + if (!joined) + return -ENOMEM; + + unit_write_drop_in_private_format(u, mode, name, "PassEnvironment=%s\n", joined); + } + } + + return 1; + + } else if (STR_IN_SET(name, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories")) { + + _cleanup_strv_free_ char **l = NULL; + char ***dirs; + char **p; + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + STRV_FOREACH(p, l) { + int offset; + if (!utf8_is_valid(*p)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid %s", name); + + offset = **p == '-'; + if (!path_is_absolute(*p + offset)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid %s", name); + } + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *joined = NULL; + + if (streq(name, "ReadWriteDirectories")) + dirs = &c->read_write_dirs; + else if (streq(name, "ReadOnlyDirectories")) + dirs = &c->read_only_dirs; + else /* "InaccessibleDirectories" */ + dirs = &c->inaccessible_dirs; + + if (strv_length(l) == 0) { + *dirs = strv_free(*dirs); + unit_write_drop_in_private_format(u, mode, name, "%s=\n", name); + } else { + r = strv_extend_strv(dirs, l, true); + + if (r < 0) + return -ENOMEM; + + joined = strv_join_quoted(*dirs); + if (!joined) + return -ENOMEM; + + unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, joined); + } + + } + + return 1; + + } else if (streq(name, "ProtectSystem")) { + const char *s; + ProtectSystem ps; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + r = parse_boolean(s); + if (r > 0) + ps = PROTECT_SYSTEM_YES; + else if (r == 0) + ps = PROTECT_SYSTEM_NO; + else { + ps = protect_system_from_string(s); + if (ps < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse protect system value"); + } + + if (mode != UNIT_CHECK) { + c->protect_system = ps; + unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, s); + } + + return 1; + + } else if (streq(name, "ProtectHome")) { + const char *s; + ProtectHome ph; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + r = parse_boolean(s); + if (r > 0) + ph = PROTECT_HOME_YES; + else if (r == 0) + ph = PROTECT_HOME_NO; + else { + ph = protect_home_from_string(s); + if (ph < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse protect home value"); + } + + if (mode != UNIT_CHECK) { + c->protect_home = ph; + unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, s); + } + + return 1; + + } else if (streq(name, "RuntimeDirectory")) { + _cleanup_strv_free_ char **l = NULL; + char **p; + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + STRV_FOREACH(p, l) { + if (!filename_is_valid(*p)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Runtime directory is not valid %s", *p); + } + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *joined = NULL; + + if (strv_isempty(l)) { + c->runtime_directory = strv_free(c->runtime_directory); + unit_write_drop_in_private_format(u, mode, name, "%s=\n", name); + } else { + r = strv_extend_strv(&c->runtime_directory, l, true); + + if (r < 0) + return -ENOMEM; + + joined = strv_join_quoted(c->runtime_directory); + if (!joined) + return -ENOMEM; + + unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, joined); + } + } + + return 1; + + } else if (streq(name, "SELinuxContext")) { + const char *s; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + if (isempty(s)) + c->selinux_context = mfree(c->selinux_context); + else if (free_and_strdup(&c->selinux_context, s) < 0) + return -ENOMEM; + + unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, strempty(s)); + } + + return 1; + + } + + ri = rlimit_from_string(name); + if (ri < 0) { + soft = endswith(name, "Soft"); + if (soft) { + const char *n; + + n = strndupa(name, soft - name); + ri = rlimit_from_string(n); + if (ri >= 0) + name = n; + + } + } + + if (ri >= 0) { + uint64_t rl; + rlim_t x; + + r = sd_bus_message_read(message, "t", &rl); + if (r < 0) + return r; + + if (rl == (uint64_t) -1) + x = RLIM_INFINITY; + else { + x = (rlim_t) rl; + + if ((uint64_t) x != rl) + return -ERANGE; + } + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *f = NULL; + struct rlimit nl; + + if (c->rlimit[ri]) { + nl = *c->rlimit[ri]; + + if (soft) + nl.rlim_cur = x; + else + nl.rlim_max = x; + } else + /* When the resource limit is not initialized yet, then assign the value to both fields */ + nl = (struct rlimit) { + .rlim_cur = x, + .rlim_max = x, + }; + + r = rlimit_format(&nl, &f); + if (r < 0) + return r; + + if (c->rlimit[ri]) + *c->rlimit[ri] = nl; + else { + c->rlimit[ri] = newdup(struct rlimit, &nl, 1); + if (!c->rlimit[ri]) + return -ENOMEM; + } + + unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, f); + } + + return 1; + } + + return 0; +} diff --git a/src/libcore/dbus-execute.h b/src/libcore/dbus-execute.h new file mode 100644 index 0000000000..bdfef41db4 --- /dev/null +++ b/src/libcore/dbus-execute.h @@ -0,0 +1,45 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "execute.h" + +#define BUS_EXEC_STATUS_VTABLE(prefix, offset, flags) \ + BUS_PROPERTY_DUAL_TIMESTAMP(prefix "StartTimestamp", (offset) + offsetof(ExecStatus, start_timestamp), flags), \ + BUS_PROPERTY_DUAL_TIMESTAMP(prefix "ExitTimestamp", (offset) + offsetof(ExecStatus, exit_timestamp), flags), \ + SD_BUS_PROPERTY(prefix "PID", "u", bus_property_get_pid, (offset) + offsetof(ExecStatus, pid), flags), \ + SD_BUS_PROPERTY(prefix "Code", "i", bus_property_get_int, (offset) + offsetof(ExecStatus, code), flags), \ + SD_BUS_PROPERTY(prefix "Status", "i", bus_property_get_int, (offset) + offsetof(ExecStatus, status), flags) + +#define BUS_EXEC_COMMAND_VTABLE(name, offset, flags) \ + SD_BUS_PROPERTY(name, "a(sasbttttuii)", bus_property_get_exec_command, offset, flags) + +#define BUS_EXEC_COMMAND_LIST_VTABLE(name, offset, flags) \ + SD_BUS_PROPERTY(name, "a(sasbttttuii)", bus_property_get_exec_command_list, offset, flags) + +extern const sd_bus_vtable bus_exec_vtable[]; + +int bus_property_get_exec_output(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); +int bus_property_get_exec_command(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); +int bus_property_get_exec_command_list(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); + +int bus_exec_context_set_transient_property(Unit *u, ExecContext *c, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); diff --git a/src/libcore/dbus-job.c b/src/libcore/dbus-job.c new file mode 100644 index 0000000000..1d739787bb --- /dev/null +++ b/src/libcore/dbus-job.c @@ -0,0 +1,193 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 "alloc-util.h" +#include "dbus-job.h" +#include "dbus.h" +#include "job.h" +#include "log.h" +#include "selinux-access.h" +#include "string-util.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, job_type, JobType); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_state, job_state, JobState); + +static int property_get_unit( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + _cleanup_free_ char *p = NULL; + Job *j = userdata; + + assert(bus); + assert(reply); + assert(j); + + p = unit_dbus_path(j->unit); + if (!p) + return -ENOMEM; + + return sd_bus_message_append(reply, "(so)", j->unit->id, p); +} + +int bus_job_method_cancel(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Job *j = userdata; + int r; + + assert(message); + assert(j); + + r = mac_selinux_unit_access_check(j->unit, message, "stop", error); + if (r < 0) + return r; + + /* Access is granted to the job owner */ + if (!sd_bus_track_contains(j->clients, sd_bus_message_get_sender(message))) { + + /* And for everybody else consult PolicyKit */ + r = bus_verify_manage_units_async(j->unit->manager, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + } + + job_finish_and_invalidate(j, JOB_CANCELED, true, false); + + return sd_bus_reply_method_return(message, NULL); +} + +const sd_bus_vtable bus_job_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("Cancel", NULL, NULL, bus_job_method_cancel, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_PROPERTY("Id", "u", NULL, offsetof(Job, id), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Unit", "(so)", property_get_unit, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("JobType", "s", property_get_type, offsetof(Job, type), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("State", "s", property_get_state, offsetof(Job, state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_VTABLE_END +}; + +static int send_new_signal(sd_bus *bus, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *p = NULL; + Job *j = userdata; + int r; + + assert(bus); + assert(j); + + p = job_dbus_path(j); + if (!p) + return -ENOMEM; + + r = sd_bus_message_new_signal( + bus, + &m, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "JobNew"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "uos", j->id, p, j->unit->id); + if (r < 0) + return r; + + return sd_bus_send(bus, m, NULL); +} + +static int send_changed_signal(sd_bus *bus, void *userdata) { + _cleanup_free_ char *p = NULL; + Job *j = userdata; + + assert(bus); + assert(j); + + p = job_dbus_path(j); + if (!p) + return -ENOMEM; + + return sd_bus_emit_properties_changed(bus, p, "org.freedesktop.systemd1.Job", "State", NULL); +} + +void bus_job_send_change_signal(Job *j) { + int r; + + assert(j); + + if (j->in_dbus_queue) { + LIST_REMOVE(dbus_queue, j->manager->dbus_job_queue, j); + j->in_dbus_queue = false; + } + + r = bus_foreach_bus(j->manager, j->clients, j->sent_dbus_new_signal ? send_changed_signal : send_new_signal, j); + if (r < 0) + log_debug_errno(r, "Failed to send job change signal for %u: %m", j->id); + + j->sent_dbus_new_signal = true; +} + +static int send_removed_signal(sd_bus *bus, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *p = NULL; + Job *j = userdata; + int r; + + assert(bus); + assert(j); + + p = job_dbus_path(j); + if (!p) + return -ENOMEM; + + r = sd_bus_message_new_signal( + bus, + &m, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "JobRemoved"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "uoss", j->id, p, j->unit->id, job_result_to_string(j->result)); + if (r < 0) + return r; + + return sd_bus_send(bus, m, NULL); +} + +void bus_job_send_removed_signal(Job *j) { + int r; + + assert(j); + + if (!j->sent_dbus_new_signal) + bus_job_send_change_signal(j); + + r = bus_foreach_bus(j->manager, j->clients, send_removed_signal, j); + if (r < 0) + log_debug_errno(r, "Failed to send job remove signal for %u: %m", j->id); +} diff --git a/src/libcore/dbus-job.h b/src/libcore/dbus-job.h new file mode 100644 index 0000000000..95664cb90c --- /dev/null +++ b/src/libcore/dbus-job.h @@ -0,0 +1,31 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "job.h" + +extern const sd_bus_vtable bus_job_vtable[]; + +int bus_job_method_cancel(sd_bus_message *message, void *job, sd_bus_error *error); + +void bus_job_send_change_signal(Job *j); +void bus_job_send_removed_signal(Job *j); diff --git a/src/libcore/dbus-kill.c b/src/libcore/dbus-kill.c new file mode 100644 index 0000000000..0f54c6b84b --- /dev/null +++ b/src/libcore/dbus-kill.c @@ -0,0 +1,122 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 "bus-util.h" +#include "dbus-kill.h" +#include "kill.h" +#include "signal-util.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_kill_mode, kill_mode, KillMode); + +const sd_bus_vtable bus_kill_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("KillMode", "s", property_get_kill_mode, offsetof(KillContext, kill_mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("KillSignal", "i", bus_property_get_int, offsetof(KillContext, kill_signal), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SendSIGKILL", "b", bus_property_get_bool, offsetof(KillContext, send_sigkill), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SendSIGHUP", "b", bus_property_get_bool, offsetof(KillContext, send_sighup), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_VTABLE_END +}; + +int bus_kill_context_set_transient_property( + Unit *u, + KillContext *c, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + int r; + + assert(u); + assert(c); + assert(name); + assert(message); + + if (streq(name, "KillMode")) { + const char *m; + KillMode k; + + r = sd_bus_message_read(message, "s", &m); + if (r < 0) + return r; + + k = kill_mode_from_string(m); + if (k < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Kill mode '%s' not known.", m); + + if (mode != UNIT_CHECK) { + c->kill_mode = k; + + unit_write_drop_in_private_format(u, mode, name, "KillMode=%s\n", kill_mode_to_string(k)); + } + + return 1; + + } else if (streq(name, "KillSignal")) { + int sig; + + r = sd_bus_message_read(message, "i", &sig); + if (r < 0) + return r; + + if (!SIGNAL_VALID(sig)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Signal %i out of range", sig); + + if (mode != UNIT_CHECK) { + c->kill_signal = sig; + + unit_write_drop_in_private_format(u, mode, name, "KillSignal=%s\n", signal_to_string(sig)); + } + + return 1; + + } else if (streq(name, "SendSIGHUP")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->send_sighup = b; + + unit_write_drop_in_private_format(u, mode, name, "SendSIGHUP=%s\n", yes_no(b)); + } + + return 1; + + } else if (streq(name, "SendSIGKILL")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->send_sigkill = b; + + unit_write_drop_in_private_format(u, mode, name, "SendSIGKILL=%s\n", yes_no(b)); + } + + return 1; + + } + + return 0; +} diff --git a/src/libcore/dbus-kill.h b/src/libcore/dbus-kill.h new file mode 100644 index 0000000000..b32ce9d223 --- /dev/null +++ b/src/libcore/dbus-kill.h @@ -0,0 +1,29 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 "kill.h" +#include "unit.h" + +extern const sd_bus_vtable bus_kill_vtable[]; + +int bus_kill_context_set_transient_property(Unit *u, KillContext *c, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); diff --git a/src/libcore/dbus-manager.c b/src/libcore/dbus-manager.c new file mode 100644 index 0000000000..86722e1162 --- /dev/null +++ b/src/libcore/dbus-manager.c @@ -0,0 +1,2305 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include + +#include "alloc-util.h" +#include "architecture.h" +#include "build.h" +#include "bus-common-errors.h" +#include "clock-util.h" +#include "dbus-execute.h" +#include "dbus-job.h" +#include "dbus-manager.h" +#include "dbus-unit.h" +#include "dbus.h" +#include "env-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "install.h" +#include "log.h" +#include "path-util.h" +#include "selinux-access.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "syslog-util.h" +#include "virt.h" +#include "watchdog.h" + +static int property_get_version( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + assert(bus); + assert(reply); + + return sd_bus_message_append(reply, "s", PACKAGE_VERSION); +} + +static int property_get_features( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + assert(bus); + assert(reply); + + return sd_bus_message_append(reply, "s", SYSTEMD_FEATURES); +} + +static int property_get_virtualization( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + int v; + + assert(bus); + assert(reply); + + v = detect_virtualization(); + + /* Make sure to return the empty string when we detect no virtualization, as that is the API. + * + * https://github.com/systemd/systemd/issues/1423 + */ + + return sd_bus_message_append( + reply, "s", + v == VIRTUALIZATION_NONE ? "" : virtualization_to_string(v)); +} + +static int property_get_architecture( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + assert(bus); + assert(reply); + + return sd_bus_message_append(reply, "s", architecture_to_string(uname_architecture())); +} + +static int property_get_tainted( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + char buf[sizeof("split-usr:cgroups-missing:local-hwclock:")] = "", *e = buf; + Manager *m = userdata; + + assert(bus); + assert(reply); + assert(m); + + if (m->taint_usr) + e = stpcpy(e, "split-usr:"); + + if (access("/proc/cgroups", F_OK) < 0) + e = stpcpy(e, "cgroups-missing:"); + + if (clock_is_localtime(NULL) > 0) + e = stpcpy(e, "local-hwclock:"); + + /* remove the last ':' */ + if (e != buf) + e[-1] = 0; + + return sd_bus_message_append(reply, "s", buf); +} + +static int property_get_log_target( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + assert(bus); + assert(reply); + + return sd_bus_message_append(reply, "s", log_target_to_string(log_get_target())); +} + +static int property_set_log_target( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *value, + void *userdata, + sd_bus_error *error) { + + const char *t; + int r; + + assert(bus); + assert(value); + + r = sd_bus_message_read(value, "s", &t); + if (r < 0) + return r; + + return log_set_target_from_string(t); +} + +static int property_get_log_level( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + _cleanup_free_ char *t = NULL; + int r; + + assert(bus); + assert(reply); + + r = log_level_to_string_alloc(log_get_max_level(), &t); + if (r < 0) + return r; + + return sd_bus_message_append(reply, "s", t); +} + +static int property_set_log_level( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *value, + void *userdata, + sd_bus_error *error) { + + const char *t; + int r; + + assert(bus); + assert(value); + + r = sd_bus_message_read(value, "s", &t); + if (r < 0) + return r; + + r = log_set_max_level_from_string(t); + if (r == 0) + log_info("Setting log level to %s.", t); + return r; +} + +static int property_get_n_names( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Manager *m = userdata; + + assert(bus); + assert(reply); + assert(m); + + return sd_bus_message_append(reply, "u", (uint32_t) hashmap_size(m->units)); +} + +static int property_get_n_failed_units( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Manager *m = userdata; + + assert(bus); + assert(reply); + assert(m); + + return sd_bus_message_append(reply, "u", (uint32_t) set_size(m->failed_units)); +} + +static int property_get_n_jobs( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Manager *m = userdata; + + assert(bus); + assert(reply); + assert(m); + + return sd_bus_message_append(reply, "u", (uint32_t) hashmap_size(m->jobs)); +} + +static int property_get_progress( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Manager *m = userdata; + double d; + + assert(bus); + assert(reply); + assert(m); + + if (dual_timestamp_is_set(&m->finish_timestamp)) + d = 1.0; + else + d = 1.0 - ((double) hashmap_size(m->jobs) / (double) m->n_installed_jobs); + + return sd_bus_message_append(reply, "d", d); +} + +static int property_get_system_state( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Manager *m = userdata; + + assert(bus); + assert(reply); + assert(m); + + return sd_bus_message_append(reply, "s", manager_state_to_string(manager_state(m))); +} + +static int property_set_runtime_watchdog( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *value, + void *userdata, + sd_bus_error *error) { + + usec_t *t = userdata; + int r; + + assert(bus); + assert(value); + + assert_cc(sizeof(usec_t) == sizeof(uint64_t)); + + r = sd_bus_message_read(value, "t", t); + if (r < 0) + return r; + + return watchdog_set_timeout(t); +} + +static int property_get_timer_slack_nsec( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + assert(bus); + assert(reply); + + return sd_bus_message_append(reply, "t", (uint64_t) prctl(PR_GET_TIMERSLACK)); +} + +static int method_get_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *path = NULL; + Manager *m = userdata; + const char *name; + Unit *u; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + if (isempty(name)) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + pid_t pid; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r < 0) + return r; + + u = manager_get_unit_by_pid(m, pid); + if (!u) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Client not member of any unit."); + } else { + u = manager_get_unit(m, name); + if (!u) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s not loaded.", name); + } + + r = mac_selinux_unit_access_check(u, message, "status", error); + if (r < 0) + return r; + + path = unit_dbus_path(u); + if (!path) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", path); +} + +static int method_get_unit_by_pid(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *path = NULL; + Manager *m = userdata; + pid_t pid; + Unit *u; + int r; + + assert(message); + assert(m); + + assert_cc(sizeof(pid_t) == sizeof(uint32_t)); + + /* Anyone can call this method */ + + r = sd_bus_message_read(message, "u", &pid); + if (r < 0) + return r; + if (pid < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid PID " PID_FMT, pid); + + if (pid == 0) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r < 0) + return r; + } + + u = manager_get_unit_by_pid(m, pid); + if (!u) + return sd_bus_error_setf(error, BUS_ERROR_NO_UNIT_FOR_PID, "PID "PID_FMT" does not belong to any loaded unit.", pid); + + r = mac_selinux_unit_access_check(u, message, "status", error); + if (r < 0) + return r; + + path = unit_dbus_path(u); + if (!path) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", path); +} + +static int method_load_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *path = NULL; + Manager *m = userdata; + const char *name; + Unit *u; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + if (isempty(name)) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + pid_t pid; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r < 0) + return r; + + u = manager_get_unit_by_pid(m, pid); + if (!u) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Client not member of any unit."); + } else { + r = manager_load_unit(m, name, NULL, error, &u); + if (r < 0) + return r; + } + + r = mac_selinux_unit_access_check(u, message, "status", error); + if (r < 0) + return r; + + path = unit_dbus_path(u); + if (!path) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", path); +} + +static int method_start_unit_generic(sd_bus_message *message, Manager *m, JobType job_type, bool reload_if_possible, sd_bus_error *error) { + const char *name; + Unit *u; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + r = manager_load_unit(m, name, NULL, error, &u); + if (r < 0) + return r; + + return bus_unit_method_start_generic(message, u, job_type, reload_if_possible, error); +} + +static int method_start_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_start_unit_generic(message, userdata, JOB_START, false, error); +} + +static int method_stop_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_start_unit_generic(message, userdata, JOB_STOP, false, error); +} + +static int method_reload_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_start_unit_generic(message, userdata, JOB_RELOAD, false, error); +} + +static int method_restart_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_start_unit_generic(message, userdata, JOB_RESTART, false, error); +} + +static int method_try_restart_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_start_unit_generic(message, userdata, JOB_TRY_RESTART, false, error); +} + +static int method_reload_or_restart_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_start_unit_generic(message, userdata, JOB_RESTART, true, error); +} + +static int method_reload_or_try_restart_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_start_unit_generic(message, userdata, JOB_TRY_RESTART, true, error); +} + +static int method_start_unit_replace(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + const char *old_name; + Unit *u; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &old_name); + if (r < 0) + return r; + + u = manager_get_unit(m, old_name); + if (!u || !u->job || u->job->type != JOB_START) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_JOB, "No job queued for unit %s", old_name); + + return method_start_unit_generic(message, m, JOB_START, false, error); +} + +static int method_kill_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + const char *name; + Unit *u; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + u = manager_get_unit(m, name); + if (!u) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); + + return bus_unit_method_kill(message, u, error); +} + +static int method_reset_failed_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + const char *name; + Unit *u; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + u = manager_get_unit(m, name); + if (!u) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); + + return bus_unit_method_reset_failed(message, u, error); +} + +static int method_set_unit_properties(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + const char *name; + Unit *u; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + r = manager_load_unit(m, name, NULL, error, &u); + if (r < 0) + return r; + + r = bus_unit_check_load_state(u, error); + if (r < 0) + return r; + + return bus_unit_method_set_properties(message, u, error); +} + +static int reply_unit_info(sd_bus_message *reply, Unit *u) { + _cleanup_free_ char *unit_path = NULL, *job_path = NULL; + Unit *following; + + following = unit_following(u); + + unit_path = unit_dbus_path(u); + if (!unit_path) + return -ENOMEM; + + if (u->job) { + job_path = job_dbus_path(u->job); + if (!job_path) + return -ENOMEM; + } + + return sd_bus_message_append( + reply, "(ssssssouso)", + u->id, + unit_description(u), + unit_load_state_to_string(u->load_state), + unit_active_state_to_string(unit_active_state(u)), + unit_sub_state_to_string(u), + following ? following->id : "", + unit_path, + u->job ? u->job->id : 0, + u->job ? job_type_to_string(u->job->type) : "", + job_path ? job_path : "/"); +} + +static int method_list_units_by_names(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Manager *m = userdata; + int r; + char **unit; + _cleanup_strv_free_ char **units = NULL; + + assert(message); + assert(m); + + r = sd_bus_message_read_strv(message, &units); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(ssssssouso)"); + if (r < 0) + return r; + + STRV_FOREACH(unit, units) { + Unit *u; + + if (!unit_name_is_valid(*unit, UNIT_NAME_ANY)) + continue; + + r = manager_load_unit(m, *unit, NULL, error, &u); + if (r < 0) + return r; + + r = reply_unit_info(reply, u); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +static int method_get_unit_processes(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + const char *name; + Unit *u; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + r = manager_load_unit(m, name, NULL, error, &u); + if (r < 0) + return r; + + r = bus_unit_check_load_state(u, error); + if (r < 0) + return r; + + return bus_unit_method_get_processes(message, u, error); +} + +static int transient_unit_from_message( + Manager *m, + sd_bus_message *message, + const char *name, + Unit **unit, + sd_bus_error *error) { + + UnitType t; + Unit *u; + int r; + + assert(m); + assert(message); + assert(name); + + t = unit_name_to_type(name); + if (t < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid unit name or type."); + + if (!unit_vtable[t]->can_transient) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit type %s does not support transient units.", unit_type_to_string(t)); + + r = manager_load_unit(m, name, NULL, error, &u); + if (r < 0) + return r; + + if (!unit_is_pristine(u)) + return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, "Unit %s already exists.", name); + + /* OK, the unit failed to load and is unreferenced, now let's + * fill in the transient data instead */ + r = unit_make_transient(u); + if (r < 0) + return r; + + /* Set our properties */ + r = bus_unit_set_properties(u, message, UNIT_RUNTIME, false, error); + if (r < 0) + return r; + + /* Now load the missing bits of the unit we just created */ + manager_dispatch_load_queue(m); + + *unit = u; + + return 0; +} + +static int transient_aux_units_from_message( + Manager *m, + sd_bus_message *message, + sd_bus_error *error) { + + int r; + + assert(m); + assert(message); + + r = sd_bus_message_enter_container(message, 'a', "(sa(sv))"); + if (r < 0) + return r; + + while ((r = sd_bus_message_enter_container(message, 'r', "sa(sv)")) > 0) { + const char *name = NULL; + Unit *u; + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + r = transient_unit_from_message(m, message, name, &u, error); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + return 0; +} + +static int method_start_transient_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + const char *name, *smode; + Manager *m = userdata; + JobMode mode; + Unit *u; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "start", error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "ss", &name, &smode); + if (r < 0) + return r; + + mode = job_mode_from_string(smode); + if (mode < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Job mode %s is invalid.", smode); + + r = bus_verify_manage_units_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = transient_unit_from_message(m, message, name, &u, error); + if (r < 0) + return r; + + r = transient_aux_units_from_message(m, message, error); + if (r < 0) + return r; + + /* Finally, start it */ + return bus_unit_queue_job(message, u, JOB_START, mode, false, error); +} + +static int method_get_job(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *path = NULL; + Manager *m = userdata; + uint32_t id; + Job *j; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = sd_bus_message_read(message, "u", &id); + if (r < 0) + return r; + + j = manager_get_job(m, id); + if (!j) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_JOB, "Job %u does not exist.", (unsigned) id); + + r = mac_selinux_unit_access_check(j->unit, message, "status", error); + if (r < 0) + return r; + + path = job_dbus_path(j); + if (!path) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", path); +} + +static int method_cancel_job(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + uint32_t id; + Job *j; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "u", &id); + if (r < 0) + return r; + + j = manager_get_job(m, id); + if (!j) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_JOB, "Job %u does not exist.", (unsigned) id); + + return bus_job_method_cancel(message, j, error); +} + +static int method_clear_jobs(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reload", error); + if (r < 0) + return r; + + r = bus_verify_manage_units_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + manager_clear_jobs(m); + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_reset_failed(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reload", error); + if (r < 0) + return r; + + r = bus_verify_manage_units_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + manager_reset_failed(m); + + return sd_bus_reply_method_return(message, NULL); +} + +static int list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_error *error, char **states, char **patterns) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Manager *m = userdata; + const char *k; + Iterator i; + Unit *u; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = mac_selinux_access_check(message, "status", error); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(ssssssouso)"); + if (r < 0) + return r; + + HASHMAP_FOREACH_KEY(u, k, m->units, i) { + if (k != u->id) + continue; + + if (!strv_isempty(states) && + !strv_contains(states, unit_load_state_to_string(u->load_state)) && + !strv_contains(states, unit_active_state_to_string(unit_active_state(u))) && + !strv_contains(states, unit_sub_state_to_string(u))) + continue; + + if (!strv_isempty(patterns) && + !strv_fnmatch_or_empty(patterns, u->id, FNM_NOESCAPE)) + continue; + + r = reply_unit_info(reply, u); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +static int method_list_units(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return list_units_filtered(message, userdata, error, NULL, NULL); +} + +static int method_list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **states = NULL; + int r; + + r = sd_bus_message_read_strv(message, &states); + if (r < 0) + return r; + + return list_units_filtered(message, userdata, error, states, NULL); +} + +static int method_list_units_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **states = NULL; + _cleanup_strv_free_ char **patterns = NULL; + int r; + + r = sd_bus_message_read_strv(message, &states); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(message, &patterns); + if (r < 0) + return r; + + return list_units_filtered(message, userdata, error, states, patterns); +} + +static int method_list_jobs(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Manager *m = userdata; + Iterator i; + Job *j; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = mac_selinux_access_check(message, "status", error); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(usssoo)"); + if (r < 0) + return r; + + HASHMAP_FOREACH(j, m->jobs, i) { + _cleanup_free_ char *unit_path = NULL, *job_path = NULL; + + job_path = job_dbus_path(j); + if (!job_path) + return -ENOMEM; + + unit_path = unit_dbus_path(j->unit); + if (!unit_path) + return -ENOMEM; + + r = sd_bus_message_append( + reply, "(usssoo)", + j->id, + j->unit->id, + job_type_to_string(j->type), + job_state_to_string(j->state), + job_path, + unit_path); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +static int method_subscribe(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = mac_selinux_access_check(message, "status", error); + if (r < 0) + return r; + + if (sd_bus_message_get_bus(message) == m->api_bus) { + + /* Note that direct bus connection subscribe by + * default, we only track peers on the API bus here */ + + if (!m->subscribed) { + r = sd_bus_track_new(sd_bus_message_get_bus(message), &m->subscribed, NULL, NULL); + if (r < 0) + return r; + } + + r = sd_bus_track_add_sender(m->subscribed, message); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_ALREADY_SUBSCRIBED, "Client is already subscribed."); + } + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_unsubscribe(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = mac_selinux_access_check(message, "status", error); + if (r < 0) + return r; + + if (sd_bus_message_get_bus(message) == m->api_bus) { + r = sd_bus_track_remove_sender(m->subscribed, message); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NOT_SUBSCRIBED, "Client is not subscribed."); + } + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_dump(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *dump = NULL; + _cleanup_fclose_ FILE *f = NULL; + Manager *m = userdata; + size_t size; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = mac_selinux_access_check(message, "status", error); + if (r < 0) + return r; + + f = open_memstream(&dump, &size); + if (!f) + return -ENOMEM; + + manager_dump_units(m, f, NULL); + manager_dump_jobs(m, f, NULL); + + r = fflush_and_check(f); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, "s", dump); +} + +static int method_refuse_snapshot(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Support for snapshots has been removed."); +} + +static int method_reload(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reload", error); + if (r < 0) + return r; + + r = bus_verify_reload_daemon_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + /* Instead of sending the reply back right away, we just + * remember that we need to and then send it after the reload + * is finished. That way the caller knows when the reload + * finished. */ + + assert(!m->queued_message); + r = sd_bus_message_new_method_return(message, &m->queued_message); + if (r < 0) + return r; + + m->exit_code = MANAGER_RELOAD; + + return 1; +} + +static int method_reexecute(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reload", error); + if (r < 0) + return r; + + r = bus_verify_reload_daemon_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + /* We don't send a reply back here, the client should + * just wait for us disconnecting. */ + + m->exit_code = MANAGER_REEXECUTE; + return 1; +} + +static int method_exit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "halt", error); + if (r < 0) + return r; + + /* Exit() (in contrast to SetExitCode()) is actually allowed even if + * we are running on the host. It will fall back on reboot() in + * systemd-shutdown if it cannot do the exit() because it isn't a + * container. */ + + m->exit_code = MANAGER_EXIT; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_reboot(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reboot", error); + if (r < 0) + return r; + + if (!MANAGER_IS_SYSTEM(m)) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Reboot is only supported for system managers."); + + m->exit_code = MANAGER_REBOOT; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_poweroff(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "halt", error); + if (r < 0) + return r; + + if (!MANAGER_IS_SYSTEM(m)) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Powering off is only supported for system managers."); + + m->exit_code = MANAGER_POWEROFF; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_halt(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "halt", error); + if (r < 0) + return r; + + if (!MANAGER_IS_SYSTEM(m)) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Halt is only supported for system managers."); + + m->exit_code = MANAGER_HALT; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_kexec(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reboot", error); + if (r < 0) + return r; + + if (!MANAGER_IS_SYSTEM(m)) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "KExec is only supported for system managers."); + + m->exit_code = MANAGER_KEXEC; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_switch_root(sd_bus_message *message, void *userdata, sd_bus_error *error) { + char *ri = NULL, *rt = NULL; + const char *root, *init; + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reboot", error); + if (r < 0) + return r; + + if (!MANAGER_IS_SYSTEM(m)) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Root switching is only supported by system manager."); + + r = sd_bus_message_read(message, "ss", &root, &init); + if (r < 0) + return r; + + if (path_equal(root, "/") || !path_is_absolute(root)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid switch root path %s", root); + + /* Safety check */ + if (isempty(init)) { + if (!path_is_os_tree(root)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified switch root path %s does not seem to be an OS tree. os-release file is missing.", root); + } else { + _cleanup_free_ char *p = NULL; + + if (!path_is_absolute(init)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid init path %s", init); + + p = strappend(root, init); + if (!p) + return -ENOMEM; + + if (access(p, X_OK) < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified init binary %s does not exist.", p); + } + + rt = strdup(root); + if (!rt) + return -ENOMEM; + + if (!isempty(init)) { + ri = strdup(init); + if (!ri) { + free(rt); + return -ENOMEM; + } + } + + free(m->switch_root); + m->switch_root = rt; + + free(m->switch_root_init); + m->switch_root_init = ri; + + m->exit_code = MANAGER_SWITCH_ROOT; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_set_environment(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **plus = NULL; + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reload", error); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(message, &plus); + if (r < 0) + return r; + if (!strv_env_is_valid(plus)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments"); + + r = bus_verify_set_environment_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = manager_environment_add(m, NULL, plus); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_unset_environment(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **minus = NULL; + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reload", error); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(message, &minus); + if (r < 0) + return r; + + if (!strv_env_name_or_assignment_is_valid(minus)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment variable names or assignments"); + + r = bus_verify_set_environment_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = manager_environment_add(m, minus, NULL); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_unset_and_set_environment(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **minus = NULL, **plus = NULL; + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reload", error); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(message, &minus); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(message, &plus); + if (r < 0) + return r; + + if (!strv_env_name_or_assignment_is_valid(minus)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment variable names or assignments"); + if (!strv_env_is_valid(plus)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments"); + + r = bus_verify_set_environment_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = manager_environment_add(m, minus, plus); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_set_exit_code(sd_bus_message *message, void *userdata, sd_bus_error *error) { + uint8_t code; + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "exit", error); + if (r < 0) + return r; + + r = sd_bus_message_read_basic(message, 'y', &code); + if (r < 0) + return r; + + if (MANAGER_IS_SYSTEM(m) && detect_container() <= 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "ExitCode can only be set for user service managers or in containers."); + + m->return_value = code; + + return sd_bus_reply_method_return(message, NULL); +} + +static int list_unit_files_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error, char **states, char **patterns) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Manager *m = userdata; + UnitFileList *item; + Hashmap *h; + Iterator i; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = mac_selinux_access_check(message, "status", error); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + h = hashmap_new(&string_hash_ops); + if (!h) + return -ENOMEM; + + r = unit_file_get_list(m->unit_file_scope, NULL, h, states, patterns); + if (r < 0) + goto fail; + + r = sd_bus_message_open_container(reply, 'a', "(ss)"); + if (r < 0) + goto fail; + + HASHMAP_FOREACH(item, h, i) { + + r = sd_bus_message_append(reply, "(ss)", item->path, unit_file_state_to_string(item->state)); + if (r < 0) + goto fail; + } + + unit_file_list_free(h); + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); + +fail: + unit_file_list_free(h); + return r; +} + +static int method_list_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return list_unit_files_by_patterns(message, userdata, error, NULL, NULL); +} + +static int method_list_unit_files_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **states = NULL; + _cleanup_strv_free_ char **patterns = NULL; + int r; + + r = sd_bus_message_read_strv(message, &states); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(message, &patterns); + if (r < 0) + return r; + + return list_unit_files_by_patterns(message, userdata, error, states, patterns); +} + +static int method_get_unit_file_state(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + const char *name; + UnitFileState state; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = mac_selinux_access_check(message, "status", error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + r = unit_file_get_state(m->unit_file_scope, NULL, name, &state); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, "s", unit_file_state_to_string(state)); +} + +static int method_get_default_target(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *default_target = NULL; + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = mac_selinux_access_check(message, "status", error); + if (r < 0) + return r; + + r = unit_file_get_default(m->unit_file_scope, NULL, &default_target); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, "s", default_target); +} + +static int send_unit_files_changed(sd_bus *bus, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *message = NULL; + int r; + + assert(bus); + + r = sd_bus_message_new_signal(bus, &message, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitFilesChanged"); + if (r < 0) + return r; + + return sd_bus_send(bus, message, NULL); +} + +static int reply_unit_file_changes_and_free( + Manager *m, + sd_bus_message *message, + int carries_install_info, + UnitFileChange *changes, + unsigned n_changes) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + unsigned i; + int r; + + if (unit_file_changes_have_modification(changes, n_changes)) { + r = bus_foreach_bus(m, NULL, send_unit_files_changed, NULL); + if (r < 0) + log_debug_errno(r, "Failed to send UnitFilesChanged signal: %m"); + } + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + goto fail; + + if (carries_install_info >= 0) { + r = sd_bus_message_append(reply, "b", carries_install_info); + if (r < 0) + goto fail; + } + + r = sd_bus_message_open_container(reply, 'a', "(sss)"); + if (r < 0) + goto fail; + + for (i = 0; i < n_changes; i++) + if (changes[i].type >= 0) { + const char *change = unit_file_change_type_to_string(changes[i].type); + assert(change != NULL); + + r = sd_bus_message_append( + reply, "(sss)", + change, + changes[i].path, + changes[i].source); + if (r < 0) + goto fail; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto fail; + + unit_file_changes_free(changes, n_changes); + return sd_bus_send(NULL, reply, NULL); + +fail: + unit_file_changes_free(changes, n_changes); + return r; +} + +/* Create an error reply, using the error information from changes[] + * if possible, and fall back to generating an error from error code c. + * The error message only describes the first error. + * + * Coordinate with unit_file_dump_changes() in install.c. + */ +static int install_error( + sd_bus_error *error, + int c, + UnitFileChange *changes, + unsigned n_changes) { + int r; + unsigned i; + assert(c < 0); + + for (i = 0; i < n_changes; i++) + switch(changes[i].type) { + case 0 ... INT_MAX: + continue; + case -EEXIST: + if (changes[i].source) + r = sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, + "File %s already exists and is a symlink to %s.", + changes[i].path, changes[i].source); + else + r = sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, + "File %s already exists.", + changes[i].path); + goto found; + case -ERFKILL: + r = sd_bus_error_setf(error, BUS_ERROR_UNIT_MASKED, + "Unit file %s is masked.", changes[i].path); + goto found; + case -EADDRNOTAVAIL: + r = sd_bus_error_setf(error, BUS_ERROR_UNIT_GENERATED, + "Unit %s is transient or generated.", changes[i].path); + goto found; + case -ELOOP: + r = sd_bus_error_setf(error, BUS_ERROR_UNIT_LINKED, + "Refusing to operate on linked unit file %s", changes[i].path); + goto found; + default: + r = sd_bus_error_set_errnof(error, changes[i].type, "File %s: %m", changes[i].path); + goto found; + } + + r = c; + found: + unit_file_changes_free(changes, n_changes); + return r; +} + +static int method_enable_unit_files_generic( + sd_bus_message *message, + Manager *m, + int (*call)(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes), + bool carries_install_info, + sd_bus_error *error) { + + _cleanup_strv_free_ char **l = NULL; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + int runtime, force, r; + + assert(message); + assert(m); + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "bb", &runtime, &force); + if (r < 0) + return r; + + r = bus_verify_manage_unit_files_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = call(m->unit_file_scope, runtime, NULL, l, force, &changes, &n_changes); + if (r < 0) + return install_error(error, r, changes, n_changes); + + return reply_unit_file_changes_and_free(m, message, carries_install_info ? r : -1, changes, n_changes); +} + +static int method_enable_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_enable_unit_files_generic(message, userdata, unit_file_enable, true, error); +} + +static int method_reenable_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_enable_unit_files_generic(message, userdata, unit_file_reenable, true, error); +} + +static int method_link_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_enable_unit_files_generic(message, userdata, unit_file_link, false, error); +} + +static int unit_file_preset_without_mode(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes) { + return unit_file_preset(scope, runtime, root_dir, files, UNIT_FILE_PRESET_FULL, force, changes, n_changes); +} + +static int method_preset_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_enable_unit_files_generic(message, userdata, unit_file_preset_without_mode, true, error); +} + +static int method_mask_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_enable_unit_files_generic(message, userdata, unit_file_mask, false, error); +} + +static int method_preset_unit_files_with_mode(sd_bus_message *message, void *userdata, sd_bus_error *error) { + + _cleanup_strv_free_ char **l = NULL; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + Manager *m = userdata; + UnitFilePresetMode mm; + int runtime, force, r; + const char *mode; + + assert(message); + assert(m); + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "sbb", &mode, &runtime, &force); + if (r < 0) + return r; + + if (isempty(mode)) + mm = UNIT_FILE_PRESET_FULL; + else { + mm = unit_file_preset_mode_from_string(mode); + if (mm < 0) + return -EINVAL; + } + + r = bus_verify_manage_unit_files_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = unit_file_preset(m->unit_file_scope, runtime, NULL, l, mm, force, &changes, &n_changes); + if (r < 0) + return install_error(error, r, changes, n_changes); + + return reply_unit_file_changes_and_free(m, message, r, changes, n_changes); +} + +static int method_disable_unit_files_generic( + sd_bus_message *message, + Manager *m, + int (*call)(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], UnitFileChange **changes, unsigned *n_changes), + sd_bus_error *error) { + + _cleanup_strv_free_ char **l = NULL; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + int r, runtime; + + assert(message); + assert(m); + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "b", &runtime); + if (r < 0) + return r; + + r = bus_verify_manage_unit_files_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = call(m->unit_file_scope, runtime, NULL, l, &changes, &n_changes); + if (r < 0) + return install_error(error, r, changes, n_changes); + + return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); +} + +static int method_disable_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_disable_unit_files_generic(message, userdata, unit_file_disable, error); +} + +static int method_unmask_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_disable_unit_files_generic(message, userdata, unit_file_unmask, error); +} + +static int method_revert_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **l = NULL; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + r = bus_verify_manage_unit_files_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = unit_file_revert(m->unit_file_scope, NULL, l, &changes, &n_changes); + if (r < 0) + return install_error(error, r, changes, n_changes); + + return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); +} + +static int method_set_default_target(sd_bus_message *message, void *userdata, sd_bus_error *error) { + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + Manager *m = userdata; + const char *name; + int force, r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "enable", error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "sb", &name, &force); + if (r < 0) + return r; + + r = bus_verify_manage_unit_files_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = unit_file_set_default(m->unit_file_scope, NULL, name, force, &changes, &n_changes); + if (r < 0) + return install_error(error, r, changes, n_changes); + + return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); +} + +static int method_preset_all_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + Manager *m = userdata; + UnitFilePresetMode mm; + const char *mode; + int force, runtime, r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "enable", error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "sbb", &mode, &runtime, &force); + if (r < 0) + return r; + + if (isempty(mode)) + mm = UNIT_FILE_PRESET_FULL; + else { + mm = unit_file_preset_mode_from_string(mode); + if (mm < 0) + return -EINVAL; + } + + r = bus_verify_manage_unit_files_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = unit_file_preset_all(m->unit_file_scope, runtime, NULL, mm, force, &changes, &n_changes); + if (r < 0) + return install_error(error, r, changes, n_changes); + + return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); +} + +static int method_add_dependency_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **l = NULL; + Manager *m = userdata; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + int runtime, force, r; + char *target, *type; + UnitDependency dep; + + assert(message); + assert(m); + + r = bus_verify_manage_unit_files_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "ssbb", &target, &type, &runtime, &force); + if (r < 0) + return r; + + dep = unit_dependency_from_string(type); + if (dep < 0) + return -EINVAL; + + r = unit_file_add_dependency(m->unit_file_scope, runtime, NULL, l, target, dep, force, &changes, &n_changes); + if (r < 0) + return install_error(error, r, changes, n_changes); + + return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); +} + +const sd_bus_vtable bus_manager_vtable[] = { + SD_BUS_VTABLE_START(0), + + SD_BUS_PROPERTY("Version", "s", property_get_version, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Features", "s", property_get_features, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Virtualization", "s", property_get_virtualization, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Architecture", "s", property_get_architecture, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Tainted", "s", property_get_tainted, 0, SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("FirmwareTimestamp", offsetof(Manager, firmware_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("LoaderTimestamp", offsetof(Manager, loader_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("KernelTimestamp", offsetof(Manager, kernel_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("InitRDTimestamp", offsetof(Manager, initrd_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("UserspaceTimestamp", offsetof(Manager, userspace_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("FinishTimestamp", offsetof(Manager, finish_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("SecurityStartTimestamp", offsetof(Manager, security_start_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("SecurityFinishTimestamp", offsetof(Manager, security_finish_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("GeneratorsStartTimestamp", offsetof(Manager, generators_start_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("GeneratorsFinishTimestamp", offsetof(Manager, generators_finish_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("UnitsLoadStartTimestamp", offsetof(Manager, units_load_start_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("UnitsLoadFinishTimestamp", offsetof(Manager, units_load_finish_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_WRITABLE_PROPERTY("LogLevel", "s", property_get_log_level, property_set_log_level, 0, 0), + SD_BUS_WRITABLE_PROPERTY("LogTarget", "s", property_get_log_target, property_set_log_target, 0, 0), + SD_BUS_PROPERTY("NNames", "u", property_get_n_names, 0, 0), + SD_BUS_PROPERTY("NFailedUnits", "u", property_get_n_failed_units, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("NJobs", "u", property_get_n_jobs, 0, 0), + SD_BUS_PROPERTY("NInstalledJobs", "u", bus_property_get_unsigned, offsetof(Manager, n_installed_jobs), 0), + SD_BUS_PROPERTY("NFailedJobs", "u", bus_property_get_unsigned, offsetof(Manager, n_failed_jobs), 0), + SD_BUS_PROPERTY("Progress", "d", property_get_progress, 0, 0), + SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(Manager, environment), 0), + SD_BUS_PROPERTY("ConfirmSpawn", "b", bus_property_get_bool, offsetof(Manager, confirm_spawn), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ShowStatus", "b", bus_property_get_bool, offsetof(Manager, show_status), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("UnitPath", "as", NULL, offsetof(Manager, lookup_paths.search_path), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultStandardOutput", "s", bus_property_get_exec_output, offsetof(Manager, default_std_output), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultStandardError", "s", bus_property_get_exec_output, offsetof(Manager, default_std_output), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_WRITABLE_PROPERTY("RuntimeWatchdogUSec", "t", bus_property_get_usec, property_set_runtime_watchdog, offsetof(Manager, runtime_watchdog), 0), + SD_BUS_WRITABLE_PROPERTY("ShutdownWatchdogUSec", "t", bus_property_get_usec, bus_property_set_usec, offsetof(Manager, shutdown_watchdog), 0), + SD_BUS_PROPERTY("ControlGroup", "s", NULL, offsetof(Manager, cgroup_root), 0), + SD_BUS_PROPERTY("SystemState", "s", property_get_system_state, 0, 0), + SD_BUS_PROPERTY("ExitCode", "y", bus_property_get_unsigned, offsetof(Manager, return_value), 0), + SD_BUS_PROPERTY("DefaultTimerAccuracyUSec", "t", bus_property_get_usec, offsetof(Manager, default_timer_accuracy_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultTimeoutStartUSec", "t", bus_property_get_usec, offsetof(Manager, default_timeout_start_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultTimeoutStopUSec", "t", bus_property_get_usec, offsetof(Manager, default_timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultRestartUSec", "t", bus_property_get_usec, offsetof(Manager, default_restart_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultStartLimitIntervalSec", "t", bus_property_get_usec, offsetof(Manager, default_start_limit_interval), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultStartLimitInterval", "t", bus_property_get_usec, offsetof(Manager, default_start_limit_interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), /* obsolete alias name */ + SD_BUS_PROPERTY("DefaultStartLimitBurst", "u", bus_property_get_unsigned, offsetof(Manager, default_start_limit_burst), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultCPUAccounting", "b", bus_property_get_bool, offsetof(Manager, default_cpu_accounting), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultBlockIOAccounting", "b", bus_property_get_bool, offsetof(Manager, default_blockio_accounting), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultMemoryAccounting", "b", bus_property_get_bool, offsetof(Manager, default_memory_accounting), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultTasksAccounting", "b", bus_property_get_bool, offsetof(Manager, default_tasks_accounting), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitCPU", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitCPUSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitFSIZE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitFSIZESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitDATA", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitDATASoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitSTACK", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitSTACKSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitCORE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitCORESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitRSS", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitRSSSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitNOFILE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitNOFILESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitAS", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitASSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitNPROC", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitNPROCSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitMEMLOCK", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitMEMLOCKSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitLOCKS", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitLOCKSSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitSIGPENDING", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitSIGPENDINGSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitMSGQUEUE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitMSGQUEUESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitNICE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitNICESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitRTPRIO", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitRTPRIOSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitRTTIME", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitRTTIMESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultTasksMax", "t", NULL, offsetof(Manager, default_tasks_max), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TimerSlackNSec", "t", property_get_timer_slack_nsec, 0, SD_BUS_VTABLE_PROPERTY_CONST), + + SD_BUS_METHOD("GetUnit", "s", "o", method_get_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetUnitByPID", "u", "o", method_get_unit_by_pid, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("LoadUnit", "s", "o", method_load_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("StartUnit", "ss", "o", method_start_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("StartUnitReplace", "sss", "o", method_start_unit_replace, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("StopUnit", "ss", "o", method_stop_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ReloadUnit", "ss", "o", method_reload_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RestartUnit", "ss", "o", method_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("TryRestartUnit", "ss", "o", method_try_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ReloadOrRestartUnit", "ss", "o", method_reload_or_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ReloadOrTryRestartUnit", "ss", "o", method_reload_or_try_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("KillUnit", "ssi", NULL, method_kill_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ResetFailedUnit", "s", NULL, method_reset_failed_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetUnitProperties", "sba(sv)", NULL, method_set_unit_properties, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("StartTransientUnit", "ssa(sv)a(sa(sv))", "o", method_start_transient_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetUnitProcesses", "s", "a(sus)", method_get_unit_processes, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetJob", "u", "o", method_get_job, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CancelJob", "u", NULL, method_cancel_job, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ClearJobs", NULL, NULL, method_clear_jobs, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ResetFailed", NULL, NULL, method_reset_failed, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListUnits", NULL, "a(ssssssouso)", method_list_units, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListUnitsFiltered", "as", "a(ssssssouso)", method_list_units_filtered, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListUnitsByPatterns", "asas", "a(ssssssouso)", method_list_units_by_patterns, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListUnitsByNames", "as", "a(ssssssouso)", method_list_units_by_names, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListJobs", NULL, "a(usssoo)", method_list_jobs, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Subscribe", NULL, NULL, method_subscribe, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Unsubscribe", NULL, NULL, method_unsubscribe, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Dump", NULL, "s", method_dump, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CreateSnapshot", "sb", "o", method_refuse_snapshot, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RemoveSnapshot", "s", NULL, method_refuse_snapshot, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Reload", NULL, NULL, method_reload, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Reexecute", NULL, NULL, method_reexecute, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Exit", NULL, NULL, method_exit, 0), + SD_BUS_METHOD("Reboot", NULL, NULL, method_reboot, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), + SD_BUS_METHOD("PowerOff", NULL, NULL, method_poweroff, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), + SD_BUS_METHOD("Halt", NULL, NULL, method_halt, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), + SD_BUS_METHOD("KExec", NULL, NULL, method_kexec, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), + SD_BUS_METHOD("SwitchRoot", "ss", NULL, method_switch_root, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), + SD_BUS_METHOD("SetEnvironment", "as", NULL, method_set_environment, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("UnsetEnvironment", "as", NULL, method_unset_environment, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("UnsetAndSetEnvironment", "asas", NULL, method_unset_and_set_environment, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListUnitFiles", NULL, "a(ss)", method_list_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListUnitFilesByPatterns", "asas", "a(ss)", method_list_unit_files_by_patterns, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetUnitFileState", "s", "s", method_get_unit_file_state, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("EnableUnitFiles", "asbb", "ba(sss)", method_enable_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("DisableUnitFiles", "asb", "a(sss)", method_disable_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ReenableUnitFiles", "asbb", "ba(sss)", method_reenable_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("LinkUnitFiles", "asbb", "a(sss)", method_link_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("PresetUnitFiles", "asbb", "ba(sss)", method_preset_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("PresetUnitFilesWithMode", "assbb", "ba(sss)", method_preset_unit_files_with_mode, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MaskUnitFiles", "asbb", "a(sss)", method_mask_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("UnmaskUnitFiles", "asb", "a(sss)", method_unmask_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RevertUnitFiles", "as", "a(sss)", method_revert_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetDefaultTarget", "sb", "a(sss)", method_set_default_target, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetDefaultTarget", NULL, "s", method_get_default_target, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("PresetAllUnitFiles", "sbb", "a(sss)", method_preset_all_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("AddDependencyUnitFiles", "asssbb", "a(sss)", method_add_dependency_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetExitCode", "y", NULL, method_set_exit_code, SD_BUS_VTABLE_UNPRIVILEGED), + + SD_BUS_SIGNAL("UnitNew", "so", 0), + SD_BUS_SIGNAL("UnitRemoved", "so", 0), + SD_BUS_SIGNAL("JobNew", "uos", 0), + SD_BUS_SIGNAL("JobRemoved", "uoss", 0), + SD_BUS_SIGNAL("StartupFinished", "tttttt", 0), + SD_BUS_SIGNAL("UnitFilesChanged", NULL, 0), + SD_BUS_SIGNAL("Reloading", "b", 0), + + SD_BUS_VTABLE_END +}; + +static int send_finished(sd_bus *bus, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *message = NULL; + usec_t *times = userdata; + int r; + + assert(bus); + assert(times); + + r = sd_bus_message_new_signal(bus, &message, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartupFinished"); + if (r < 0) + return r; + + r = sd_bus_message_append(message, "tttttt", times[0], times[1], times[2], times[3], times[4], times[5]); + if (r < 0) + return r; + + return sd_bus_send(bus, message, NULL); +} + +void bus_manager_send_finished( + Manager *m, + usec_t firmware_usec, + usec_t loader_usec, + usec_t kernel_usec, + usec_t initrd_usec, + usec_t userspace_usec, + usec_t total_usec) { + + int r; + + assert(m); + + r = bus_foreach_bus( + m, + NULL, + send_finished, + (usec_t[6]) { + firmware_usec, + loader_usec, + kernel_usec, + initrd_usec, + userspace_usec, + total_usec + }); + if (r < 0) + log_debug_errno(r, "Failed to send finished signal: %m"); +} + +static int send_reloading(sd_bus *bus, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *message = NULL; + int r; + + assert(bus); + + r = sd_bus_message_new_signal(bus, &message, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "Reloading"); + if (r < 0) + return r; + + r = sd_bus_message_append(message, "b", PTR_TO_INT(userdata)); + if (r < 0) + return r; + + return sd_bus_send(bus, message, NULL); +} + +void bus_manager_send_reloading(Manager *m, bool active) { + int r; + + assert(m); + + r = bus_foreach_bus(m, NULL, send_reloading, INT_TO_PTR(active)); + if (r < 0) + log_debug_errno(r, "Failed to send reloading signal: %m"); +} + +static int send_changed_signal(sd_bus *bus, void *userdata) { + assert(bus); + + return sd_bus_emit_properties_changed_strv(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + NULL); +} + +void bus_manager_send_change_signal(Manager *m) { + int r; + + assert(m); + + r = bus_foreach_bus(m, NULL, send_changed_signal, NULL); + if (r < 0) + log_debug_errno(r, "Failed to send manager change signal: %m"); +} diff --git a/src/libcore/dbus-manager.h b/src/libcore/dbus-manager.h new file mode 100644 index 0000000000..36a2e9481b --- /dev/null +++ b/src/libcore/dbus-manager.h @@ -0,0 +1,28 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "manager.h" + +extern const sd_bus_vtable bus_manager_vtable[]; + +void bus_manager_send_finished(Manager *m, usec_t firmware_usec, usec_t loader_usec, usec_t kernel_usec, usec_t initrd_usec, usec_t userspace_usec, usec_t total_usec); +void bus_manager_send_reloading(Manager *m, bool active); +void bus_manager_send_change_signal(Manager *m); diff --git a/src/libcore/dbus-mount.c b/src/libcore/dbus-mount.c new file mode 100644 index 0000000000..935db7c48b --- /dev/null +++ b/src/libcore/dbus-mount.c @@ -0,0 +1,211 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 "bus-util.h" +#include "dbus-cgroup.h" +#include "dbus-execute.h" +#include "dbus-kill.h" +#include "dbus-mount.h" +#include "mount.h" +#include "string-util.h" +#include "unit.h" + +static int property_get_what( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Mount *m = userdata; + const char *d; + + assert(bus); + assert(reply); + assert(m); + + if (m->from_proc_self_mountinfo && m->parameters_proc_self_mountinfo.what) + d = m->parameters_proc_self_mountinfo.what; + else if (m->from_fragment && m->parameters_fragment.what) + d = m->parameters_fragment.what; + else + d = ""; + + return sd_bus_message_append(reply, "s", d); +} + +static int property_get_options( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Mount *m = userdata; + const char *d; + + assert(bus); + assert(reply); + assert(m); + + if (m->from_proc_self_mountinfo && m->parameters_proc_self_mountinfo.options) + d = m->parameters_proc_self_mountinfo.options; + else if (m->from_fragment && m->parameters_fragment.options) + d = m->parameters_fragment.options; + else + d = ""; + + return sd_bus_message_append(reply, "s", d); +} + +static int property_get_type( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Mount *m = userdata; + const char *d; + + assert(bus); + assert(reply); + assert(m); + + if (m->from_proc_self_mountinfo && m->parameters_proc_self_mountinfo.fstype) + d = m->parameters_proc_self_mountinfo.fstype; + else if (m->from_fragment && m->parameters_fragment.fstype) + d = m->parameters_fragment.fstype; + else + d = ""; + + return sd_bus_message_append(reply, "s", d); +} + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, mount_result, MountResult); + +const sd_bus_vtable bus_mount_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Where", "s", NULL, offsetof(Mount, where), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("What", "s", property_get_what, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Options","s", property_get_options, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Type", "s", property_get_type, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("TimeoutUSec", "t", bus_property_get_usec, offsetof(Mount, timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Mount, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Mount, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SloppyOptions", "b", bus_property_get_bool, offsetof(Mount, sloppy_options), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Mount, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_EXEC_COMMAND_VTABLE("ExecMount", offsetof(Mount, exec_command[MOUNT_EXEC_MOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_VTABLE("ExecUnmount", offsetof(Mount, exec_command[MOUNT_EXEC_UNMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_VTABLE("ExecRemount", offsetof(Mount, exec_command[MOUNT_EXEC_REMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + SD_BUS_VTABLE_END +}; + +static int bus_mount_set_transient_property( + Mount *m, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + const char *new_property; + char **property; + char *p; + int r; + + assert(m); + assert(name); + assert(message); + + if (streq(name, "What")) + property = &m->parameters_fragment.what; + else if (streq(name, "Options")) + property = &m->parameters_fragment.options; + else if (streq(name, "Type")) + property = &m->parameters_fragment.fstype; + else + return 0; + + r = sd_bus_message_read(message, "s", &new_property); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + p = strdup(new_property); + if (!p) + return -ENOMEM; + + free(*property); + *property = p; + } + + return 1; +} + +int bus_mount_set_property( + Unit *u, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + Mount *m = MOUNT(u); + int r; + + assert(m); + assert(name); + assert(message); + + r = bus_cgroup_set_property(u, &m->cgroup_context, name, message, mode, error); + if (r != 0) + return r; + + if (u->transient && u->load_state == UNIT_STUB) { + /* This is a transient unit, let's load a little more */ + + r = bus_mount_set_transient_property(m, name, message, mode, error); + if (r != 0) + return r; + + r = bus_exec_context_set_transient_property(u, &m->exec_context, name, message, mode, error); + if (r != 0) + return r; + + r = bus_kill_context_set_transient_property(u, &m->kill_context, name, message, mode, error); + if (r != 0) + return r; + } + + return 0; +} + +int bus_mount_commit_properties(Unit *u) { + assert(u); + + unit_update_cgroup_members_masks(u); + unit_realize_cgroup(u); + + return 0; +} diff --git a/src/libcore/dbus-mount.h b/src/libcore/dbus-mount.h new file mode 100644 index 0000000000..f9844e449d --- /dev/null +++ b/src/libcore/dbus-mount.h @@ -0,0 +1,29 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "unit.h" + +extern const sd_bus_vtable bus_mount_vtable[]; + +int bus_mount_set_property(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); +int bus_mount_commit_properties(Unit *u); diff --git a/src/libcore/dbus-path.c b/src/libcore/dbus-path.c new file mode 100644 index 0000000000..1e153e503f --- /dev/null +++ b/src/libcore/dbus-path.c @@ -0,0 +1,86 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 "bus-util.h" +#include "dbus-path.h" +#include "path.h" +#include "string-util.h" +#include "unit.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, path_result, PathResult); + +static int property_get_paths( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Path *p = userdata; + PathSpec *k; + int r; + + assert(bus); + assert(reply); + assert(p); + + r = sd_bus_message_open_container(reply, 'a', "(ss)"); + if (r < 0) + return r; + + LIST_FOREACH(spec, k, p->specs) { + r = sd_bus_message_append(reply, "(ss)", path_type_to_string(k->type), k->path); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_unit( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *p = userdata, *trigger; + + assert(bus); + assert(reply); + assert(p); + + trigger = UNIT_TRIGGER(p); + + return sd_bus_message_append(reply, "s", trigger ? trigger->id : ""); +} + +const sd_bus_vtable bus_path_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Unit", "s", property_get_unit, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Paths", "a(ss)", property_get_paths, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("MakeDirectory", "b", bus_property_get_bool, offsetof(Path, make_directory), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Path, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Path, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_VTABLE_END +}; diff --git a/src/libcore/dbus-path.h b/src/libcore/dbus-path.h new file mode 100644 index 0000000000..d3c19e0c2b --- /dev/null +++ b/src/libcore/dbus-path.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + + + +extern const sd_bus_vtable bus_path_vtable[]; diff --git a/src/libcore/dbus-scope.c b/src/libcore/dbus-scope.c new file mode 100644 index 0000000000..34ee9a8fa9 --- /dev/null +++ b/src/libcore/dbus-scope.c @@ -0,0 +1,229 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-internal.h" +#include "bus-util.h" +#include "dbus-cgroup.h" +#include "dbus-kill.h" +#include "dbus-scope.h" +#include "dbus-unit.h" +#include "dbus.h" +#include "scope.h" +#include "selinux-access.h" +#include "unit.h" + +static int bus_scope_abandon(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Scope *s = userdata; + int r; + + assert(message); + assert(s); + + r = mac_selinux_unit_access_check(UNIT(s), message, "stop", error); + if (r < 0) + return r; + + r = bus_verify_manage_units_async(UNIT(s)->manager, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = scope_abandon(s); + if (r == -ESTALE) + return sd_bus_error_setf(error, BUS_ERROR_SCOPE_NOT_RUNNING, "Scope %s is not running, cannot abandon.", UNIT(s)->id); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, scope_result, ScopeResult); + +const sd_bus_vtable bus_scope_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Controller", "s", NULL, offsetof(Scope, controller), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TimeoutStopUSec", "t", bus_property_get_usec, offsetof(Scope, timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Scope, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_SIGNAL("RequestStop", NULL, 0), + SD_BUS_METHOD("Abandon", NULL, NULL, bus_scope_abandon, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END +}; + +static int bus_scope_set_transient_property( + Scope *s, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + int r; + + assert(s); + assert(name); + assert(message); + + if (streq(name, "PIDs")) { + unsigned n = 0; + uint32_t pid; + + r = sd_bus_message_enter_container(message, 'a', "u"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(message, "u", &pid)) > 0) { + + if (pid <= 1) + return -EINVAL; + + if (mode != UNIT_CHECK) { + r = unit_watch_pid(UNIT(s), pid); + if (r < 0 && r != -EEXIST) + return r; + } + + n++; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (n <= 0) + return -EINVAL; + + return 1; + + } else if (streq(name, "Controller")) { + const char *controller; + char *c; + + r = sd_bus_message_read(message, "s", &controller); + if (r < 0) + return r; + + if (!isempty(controller) && !service_name_is_valid(controller)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Controller '%s' is not a valid bus name.", controller); + + if (mode != UNIT_CHECK) { + if (isempty(controller)) + c = NULL; + else { + c = strdup(controller); + if (!c) + return -ENOMEM; + } + + free(s->controller); + s->controller = c; + } + + return 1; + + } else if (streq(name, "TimeoutStopUSec")) { + + if (mode != UNIT_CHECK) { + r = sd_bus_message_read(message, "t", &s->timeout_stop_usec); + if (r < 0) + return r; + + unit_write_drop_in_format(UNIT(s), mode, name, "[Scope]\nTimeoutStopSec="USEC_FMT"us\n", s->timeout_stop_usec); + } else { + r = sd_bus_message_skip(message, "t"); + if (r < 0) + return r; + } + + return 1; + } + + return 0; +} + +int bus_scope_set_property( + Unit *u, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + Scope *s = SCOPE(u); + int r; + + assert(s); + assert(name); + assert(message); + + r = bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); + if (r != 0) + return r; + + if (u->load_state == UNIT_STUB) { + /* While we are created we still accept PIDs */ + + r = bus_scope_set_transient_property(s, name, message, mode, error); + if (r != 0) + return r; + + r = bus_kill_context_set_transient_property(u, &s->kill_context, name, message, mode, error); + if (r != 0) + return r; + } + + return 0; +} + +int bus_scope_commit_properties(Unit *u) { + assert(u); + + unit_update_cgroup_members_masks(u); + unit_realize_cgroup(u); + + return 0; +} + +int bus_scope_send_request_stop(Scope *s) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *p = NULL; + int r; + + assert(s); + + if (!s->controller) + return 0; + + p = unit_dbus_path(UNIT(s)); + if (!p) + return -ENOMEM; + + r = sd_bus_message_new_signal( + UNIT(s)->manager->api_bus, + &m, + p, + "org.freedesktop.systemd1.Scope", + "RequestStop"); + if (r < 0) + return r; + + return sd_bus_send_to(UNIT(s)->manager->api_bus, m, /* s->controller */ NULL, NULL); +} diff --git a/src/libcore/dbus-scope.h b/src/libcore/dbus-scope.h new file mode 100644 index 0000000000..f96ddef0cf --- /dev/null +++ b/src/libcore/dbus-scope.h @@ -0,0 +1,31 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 "unit.h" + +extern const sd_bus_vtable bus_scope_vtable[]; + +int bus_scope_set_property(Unit *u, const char *name, sd_bus_message *i, UnitSetPropertiesMode mode, sd_bus_error *error); +int bus_scope_commit_properties(Unit *u); + +int bus_scope_send_request_stop(Scope *s); diff --git a/src/libcore/dbus-service.c b/src/libcore/dbus-service.c new file mode 100644 index 0000000000..03eecca911 --- /dev/null +++ b/src/libcore/dbus-service.c @@ -0,0 +1,322 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 "alloc-util.h" +#include "async.h" +#include "bus-util.h" +#include "dbus-cgroup.h" +#include "dbus-execute.h" +#include "dbus-kill.h" +#include "dbus-service.h" +#include "fd-util.h" +#include "fileio.h" +#include "path-util.h" +#include "service.h" +#include "string-util.h" +#include "strv.h" +#include "unit.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, service_type, ServiceType); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, service_result, ServiceResult); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_restart, service_restart, ServiceRestart); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_notify_access, notify_access, NotifyAccess); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_failure_action, failure_action, FailureAction); + +const sd_bus_vtable bus_service_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Service, type), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Restart", "s", property_get_restart, offsetof(Service, restart), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PIDFile", "s", NULL, offsetof(Service, pid_file), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("NotifyAccess", "s", property_get_notify_access, offsetof(Service, notify_access), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RestartUSec", "t", bus_property_get_usec, offsetof(Service, restart_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TimeoutStartUSec", "t", bus_property_get_usec, offsetof(Service, timeout_start_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TimeoutStopUSec", "t", bus_property_get_usec, offsetof(Service, timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RuntimeMaxUSec", "t", bus_property_get_usec, offsetof(Service, runtime_max_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("WatchdogUSec", "t", bus_property_get_usec, offsetof(Service, watchdog_usec), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("WatchdogTimestamp", offsetof(Service, watchdog_timestamp), 0), + /* The following four are obsolete, and thus marked hidden here. They moved into the Unit interface */ + SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("StartLimitAction", "s", property_get_failure_action, offsetof(Unit, start_limit_action), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("RebootArgument", "s", NULL, offsetof(Unit, reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("FailureAction", "s", property_get_failure_action, offsetof(Service, failure_action), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PermissionsStartOnly", "b", bus_property_get_bool, offsetof(Service, permissions_start_only), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RootDirectoryStartOnly", "b", bus_property_get_bool, offsetof(Service, root_directory_start_only), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RemainAfterExit", "b", bus_property_get_bool, offsetof(Service, remain_after_exit), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("GuessMainPID", "b", bus_property_get_bool, offsetof(Service, guess_main_pid), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("MainPID", "u", bus_property_get_pid, offsetof(Service, main_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Service, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("BusName", "s", NULL, offsetof(Service, bus_name), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("FileDescriptorStoreMax", "u", bus_property_get_unsigned, offsetof(Service, n_fd_store_max), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("NFileDescriptorStore", "u", bus_property_get_unsigned, offsetof(Service, n_fd_store), 0), + SD_BUS_PROPERTY("StatusText", "s", NULL, offsetof(Service, status_text), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("StatusErrno", "i", NULL, offsetof(Service, status_errno), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Service, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("USBFunctionDescriptors", "s", NULL, offsetof(Service, usb_function_descriptors), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("USBFunctionStrings", "s", NULL, offsetof(Service, usb_function_strings), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_EXEC_STATUS_VTABLE("ExecMain", offsetof(Service, main_exec_status), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPre", offsetof(Service, exec_command[SERVICE_EXEC_START_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStart", offsetof(Service, exec_command[SERVICE_EXEC_START]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPost", offsetof(Service, exec_command[SERVICE_EXEC_START_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecReload", offsetof(Service, exec_command[SERVICE_EXEC_RELOAD]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStop", offsetof(Service, exec_command[SERVICE_EXEC_STOP]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPost", offsetof(Service, exec_command[SERVICE_EXEC_STOP_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + SD_BUS_VTABLE_END +}; + +static int bus_service_set_transient_property( + Service *s, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + int r; + + assert(s); + assert(name); + assert(message); + + if (streq(name, "RemainAfterExit")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + s->remain_after_exit = b; + unit_write_drop_in_private_format(UNIT(s), mode, name, "RemainAfterExit=%s\n", yes_no(b)); + } + + return 1; + + } else if (streq(name, "Type")) { + const char *t; + ServiceType k; + + r = sd_bus_message_read(message, "s", &t); + if (r < 0) + return r; + + k = service_type_from_string(t); + if (k < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid service type %s", t); + + if (mode != UNIT_CHECK) { + s->type = k; + unit_write_drop_in_private_format(UNIT(s), mode, name, "Type=%s\n", service_type_to_string(s->type)); + } + + return 1; + } else if (streq(name, "RuntimeMaxUSec")) { + usec_t u; + + r = sd_bus_message_read(message, "t", &u); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + s->runtime_max_usec = u; + unit_write_drop_in_private_format(UNIT(s), mode, name, "RuntimeMaxSec=" USEC_FMT "us\n", u); + } + + return 1; + + } else if (STR_IN_SET(name, + "StandardInputFileDescriptor", + "StandardOutputFileDescriptor", + "StandardErrorFileDescriptor")) { + int fd; + + r = sd_bus_message_read(message, "h", &fd); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + int copy; + + copy = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (copy < 0) + return -errno; + + if (streq(name, "StandardInputFileDescriptor")) { + asynchronous_close(s->stdin_fd); + s->stdin_fd = copy; + } else if (streq(name, "StandardOutputFileDescriptor")) { + asynchronous_close(s->stdout_fd); + s->stdout_fd = copy; + } else { + asynchronous_close(s->stderr_fd); + s->stderr_fd = copy; + } + + s->exec_context.stdio_as_fds = true; + } + + return 1; + + } else if (streq(name, "ExecStart")) { + unsigned n = 0; + + r = sd_bus_message_enter_container(message, 'a', "(sasb)"); + if (r < 0) + return r; + + while ((r = sd_bus_message_enter_container(message, 'r', "sasb")) > 0) { + _cleanup_strv_free_ char **argv = NULL; + const char *path; + int b; + + r = sd_bus_message_read(message, "s", &path); + if (r < 0) + return r; + + if (!path_is_absolute(path)) + return sd_bus_error_set_errnof(error, EINVAL, "Path %s is not absolute.", path); + + r = sd_bus_message_read_strv(message, &argv); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + ExecCommand *c; + + c = new0(ExecCommand, 1); + if (!c) + return -ENOMEM; + + c->path = strdup(path); + if (!c->path) { + free(c); + return -ENOMEM; + } + + c->argv = argv; + argv = NULL; + + c->ignore = b; + + path_kill_slashes(c->path); + exec_command_append_list(&s->exec_command[SERVICE_EXEC_START], c); + } + + n++; + } + + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *buf = NULL; + _cleanup_fclose_ FILE *f = NULL; + ExecCommand *c; + size_t size = 0; + + if (n == 0) + s->exec_command[SERVICE_EXEC_START] = exec_command_free_list(s->exec_command[SERVICE_EXEC_START]); + + f = open_memstream(&buf, &size); + if (!f) + return -ENOMEM; + + fputs("ExecStart=\n", f); + + LIST_FOREACH(command, c, s->exec_command[SERVICE_EXEC_START]) { + _cleanup_free_ char *a; + + a = strv_join_quoted(c->argv); + if (!a) + return -ENOMEM; + + fprintf(f, "ExecStart=%s@%s %s\n", + c->ignore ? "-" : "", + c->path, + a); + } + + r = fflush_and_check(f); + if (r < 0) + return r; + unit_write_drop_in_private(UNIT(s), mode, name, buf); + } + + return 1; + } + + return 0; +} + +int bus_service_set_property( + Unit *u, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + Service *s = SERVICE(u); + int r; + + assert(s); + assert(name); + assert(message); + + r = bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); + if (r != 0) + return r; + + if (u->transient && u->load_state == UNIT_STUB) { + /* This is a transient unit, let's load a little more */ + + r = bus_service_set_transient_property(s, name, message, mode, error); + if (r != 0) + return r; + + r = bus_exec_context_set_transient_property(u, &s->exec_context, name, message, mode, error); + if (r != 0) + return r; + + r = bus_kill_context_set_transient_property(u, &s->kill_context, name, message, mode, error); + if (r != 0) + return r; + } + + return 0; +} + +int bus_service_commit_properties(Unit *u) { + assert(u); + + unit_update_cgroup_members_masks(u); + unit_realize_cgroup(u); + + return 0; +} diff --git a/src/libcore/dbus-service.h b/src/libcore/dbus-service.h new file mode 100644 index 0000000000..291959325c --- /dev/null +++ b/src/libcore/dbus-service.h @@ -0,0 +1,29 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "unit.h" + +extern const sd_bus_vtable bus_service_vtable[]; + +int bus_service_set_property(Unit *u, const char *name, sd_bus_message *i, UnitSetPropertiesMode mode, sd_bus_error *error); +int bus_service_commit_properties(Unit *u); diff --git a/src/libcore/dbus-slice.c b/src/libcore/dbus-slice.c new file mode 100644 index 0000000000..e37f50b283 --- /dev/null +++ b/src/libcore/dbus-slice.c @@ -0,0 +1,52 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "dbus-cgroup.h" +#include "dbus-slice.h" +#include "slice.h" +#include "unit.h" + +const sd_bus_vtable bus_slice_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_VTABLE_END +}; + +int bus_slice_set_property( + Unit *u, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + Slice *s = SLICE(u); + + assert(name); + assert(u); + + return bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); +} + +int bus_slice_commit_properties(Unit *u) { + assert(u); + + unit_update_cgroup_members_masks(u); + unit_realize_cgroup(u); + + return 0; +} diff --git a/src/libcore/dbus-slice.h b/src/libcore/dbus-slice.h new file mode 100644 index 0000000000..8e4cabbf8a --- /dev/null +++ b/src/libcore/dbus-slice.h @@ -0,0 +1,29 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 "unit.h" + +extern const sd_bus_vtable bus_slice_vtable[]; + +int bus_slice_set_property(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); +int bus_slice_commit_properties(Unit *u); diff --git a/src/libcore/dbus-socket.c b/src/libcore/dbus-socket.c new file mode 100644 index 0000000000..961340608d --- /dev/null +++ b/src/libcore/dbus-socket.c @@ -0,0 +1,184 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 "alloc-util.h" +#include "bus-util.h" +#include "dbus-cgroup.h" +#include "dbus-execute.h" +#include "dbus-socket.h" +#include "socket.h" +#include "string-util.h" +#include "unit.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, socket_result, SocketResult); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_bind_ipv6_only, socket_address_bind_ipv6_only, SocketAddressBindIPv6Only); + +static int property_get_listen( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + + Socket *s = SOCKET(userdata); + SocketPort *p; + int r; + + assert(bus); + assert(reply); + assert(s); + + r = sd_bus_message_open_container(reply, 'a', "(ss)"); + if (r < 0) + return r; + + LIST_FOREACH(port, p, s->ports) { + _cleanup_free_ char *address = NULL; + const char *a; + + switch (p->type) { + case SOCKET_SOCKET: { + r = socket_address_print(&p->address, &address); + if (r) + return r; + + a = address; + break; + } + + case SOCKET_SPECIAL: + case SOCKET_MQUEUE: + case SOCKET_FIFO: + case SOCKET_USB_FUNCTION: + a = p->path; + break; + + default: + assert_not_reached("Unknown socket type"); + } + + r = sd_bus_message_append(reply, "(ss)", socket_port_type_to_string(p), a); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + + +static int property_get_fdname( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Socket *s = SOCKET(userdata); + + assert(bus); + assert(reply); + assert(s); + + return sd_bus_message_append(reply, "s", socket_fdname(s)); +} + +const sd_bus_vtable bus_socket_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("BindIPv6Only", "s", property_get_bind_ipv6_only, offsetof(Socket, bind_ipv6_only), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Backlog", "u", bus_property_get_unsigned, offsetof(Socket, backlog), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TimeoutUSec", "t", bus_property_get_usec, offsetof(Socket, timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("BindToDevice", "s", NULL, offsetof(Socket, bind_to_device), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SocketUser", "s", NULL, offsetof(Socket, user), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SocketGroup", "s", NULL, offsetof(Socket, group), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SocketMode", "u", bus_property_get_mode, offsetof(Socket, socket_mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Socket, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Accept", "b", bus_property_get_bool, offsetof(Socket, accept), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Writable", "b", bus_property_get_bool, offsetof(Socket, writable), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("KeepAlive", "b", bus_property_get_bool, offsetof(Socket, keep_alive), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("KeepAliveTimeUSec", "t", bus_property_get_usec, offsetof(Socket, keep_alive_time), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("KeepAliveIntervalUSec", "t", bus_property_get_usec, offsetof(Socket, keep_alive_interval), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("KeepAliveProbes", "u", bus_property_get_unsigned, offsetof(Socket, keep_alive_cnt), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DeferAcceptUSec" , "t", bus_property_get_usec, offsetof(Socket, defer_accept), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("NoDelay", "b", bus_property_get_bool, offsetof(Socket, no_delay), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Priority", "i", bus_property_get_int, offsetof(Socket, priority), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ReceiveBuffer", "t", bus_property_get_size, offsetof(Socket, receive_buffer), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SendBuffer", "t", bus_property_get_size, offsetof(Socket, send_buffer), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("IPTOS", "i", bus_property_get_int, offsetof(Socket, ip_tos), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("IPTTL", "i", bus_property_get_int, offsetof(Socket, ip_ttl), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PipeSize", "t", bus_property_get_size, offsetof(Socket, pipe_size), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("FreeBind", "b", bus_property_get_bool, offsetof(Socket, free_bind), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Transparent", "b", bus_property_get_bool, offsetof(Socket, transparent), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Broadcast", "b", bus_property_get_bool, offsetof(Socket, broadcast), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PassCredentials", "b", bus_property_get_bool, offsetof(Socket, pass_cred), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PassSecurity", "b", bus_property_get_bool, offsetof(Socket, pass_sec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RemoveOnStop", "b", bus_property_get_bool, offsetof(Socket, remove_on_stop), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Listen", "a(ss)", property_get_listen, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Symlinks", "as", NULL, offsetof(Socket, symlinks), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Mark", "i", bus_property_get_int, offsetof(Socket, mark), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("MaxConnections", "u", bus_property_get_unsigned, offsetof(Socket, max_connections), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("MessageQueueMaxMessages", "x", bus_property_get_long, offsetof(Socket, mq_maxmsg), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("MessageQueueMessageSize", "x", bus_property_get_long, offsetof(Socket, mq_msgsize), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ReusePort", "b", bus_property_get_bool, offsetof(Socket, reuse_port), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SmackLabel", "s", NULL, offsetof(Socket, smack), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SmackLabelIPIn", "s", NULL, offsetof(Socket, smack_ip_in), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SmackLabelIPOut", "s", NULL, offsetof(Socket, smack_ip_out), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Socket, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + 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("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), + SD_BUS_PROPERTY("TriggerLimitBurst", "u", bus_property_get_unsigned, offsetof(Socket, trigger_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPre", offsetof(Socket, exec_command[SOCKET_EXEC_START_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPost", offsetof(Socket, exec_command[SOCKET_EXEC_START_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPre", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPost", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + SD_BUS_VTABLE_END +}; + +int bus_socket_set_property( + Unit *u, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + Socket *s = SOCKET(u); + + assert(s); + assert(name); + assert(message); + + return bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); +} + +int bus_socket_commit_properties(Unit *u) { + assert(u); + + unit_update_cgroup_members_masks(u); + unit_realize_cgroup(u); + + return 0; +} diff --git a/src/libcore/dbus-socket.h b/src/libcore/dbus-socket.h new file mode 100644 index 0000000000..a31906feea --- /dev/null +++ b/src/libcore/dbus-socket.h @@ -0,0 +1,29 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "unit.h" + +extern const sd_bus_vtable bus_socket_vtable[]; + +int bus_socket_set_property(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); +int bus_socket_commit_properties(Unit *u); diff --git a/src/libcore/dbus-swap.c b/src/libcore/dbus-swap.c new file mode 100644 index 0000000000..292f8738c6 --- /dev/null +++ b/src/libcore/dbus-swap.c @@ -0,0 +1,115 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2010 Maarten Lankhorst + + 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 "bus-util.h" +#include "dbus-cgroup.h" +#include "dbus-execute.h" +#include "dbus-swap.h" +#include "string-util.h" +#include "swap.h" +#include "unit.h" + +static int property_get_priority( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Swap *s = SWAP(userdata); + int p; + + assert(bus); + assert(reply); + assert(s); + + if (s->from_proc_swaps) + p = s->parameters_proc_swaps.priority; + else if (s->from_fragment) + p = s->parameters_fragment.priority; + else + p = -1; + + return sd_bus_message_append(reply, "i", p); +} + +static int property_get_options( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Swap *s = SWAP(userdata); + const char *options = NULL; + + assert(bus); + assert(reply); + assert(s); + + if (s->from_fragment) + options = s->parameters_fragment.options; + + return sd_bus_message_append(reply, "s", options); +} + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, swap_result, SwapResult); + +const sd_bus_vtable bus_swap_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("What", "s", NULL, offsetof(Swap, what), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Priority", "i", property_get_priority, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Options", "s", property_get_options, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("TimeoutUSec", "t", bus_property_get_usec, offsetof(Swap, timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Swap, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Swap, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_EXEC_COMMAND_VTABLE("ExecActivate", offsetof(Swap, exec_command[SWAP_EXEC_ACTIVATE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_VTABLE("ExecDeactivate", offsetof(Swap, exec_command[SWAP_EXEC_DEACTIVATE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + SD_BUS_VTABLE_END +}; + +int bus_swap_set_property( + Unit *u, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + Swap *s = SWAP(u); + + assert(s); + assert(name); + assert(message); + + return bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); +} + +int bus_swap_commit_properties(Unit *u) { + assert(u); + + unit_update_cgroup_members_masks(u); + unit_realize_cgroup(u); + + return 0; +} diff --git a/src/libcore/dbus-swap.h b/src/libcore/dbus-swap.h new file mode 100644 index 0000000000..19151fb771 --- /dev/null +++ b/src/libcore/dbus-swap.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2010 Maarten Lankhorst + + 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 "unit.h" + +extern const sd_bus_vtable bus_swap_vtable[]; + +int bus_swap_set_property(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); +int bus_swap_commit_properties(Unit *u); diff --git a/src/libcore/dbus-target.c b/src/libcore/dbus-target.c new file mode 100644 index 0000000000..6858b1ce72 --- /dev/null +++ b/src/libcore/dbus-target.c @@ -0,0 +1,26 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 "dbus-target.h" +#include "unit.h" + +const sd_bus_vtable bus_target_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_VTABLE_END +}; diff --git a/src/libcore/dbus-target.h b/src/libcore/dbus-target.h new file mode 100644 index 0000000000..c97a9d626e --- /dev/null +++ b/src/libcore/dbus-target.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 + +extern const sd_bus_vtable bus_target_vtable[]; diff --git a/src/libcore/dbus-timer.c b/src/libcore/dbus-timer.c new file mode 100644 index 0000000000..a0e61b023e --- /dev/null +++ b/src/libcore/dbus-timer.c @@ -0,0 +1,352 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 "alloc-util.h" +#include "bus-util.h" +#include "dbus-timer.h" +#include "strv.h" +#include "timer.h" +#include "unit.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, timer_result, TimerResult); + +static int property_get_monotonic_timers( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Timer *t = userdata; + TimerValue *v; + int r; + + assert(bus); + assert(reply); + assert(t); + + r = sd_bus_message_open_container(reply, 'a', "(stt)"); + if (r < 0) + return r; + + LIST_FOREACH(value, v, t->values) { + _cleanup_free_ char *buf = NULL; + const char *s; + size_t l; + + if (v->base == TIMER_CALENDAR) + continue; + + s = timer_base_to_string(v->base); + assert(endswith(s, "Sec")); + + /* s/Sec/USec/ */ + l = strlen(s); + buf = new(char, l+2); + if (!buf) + return -ENOMEM; + + memcpy(buf, s, l-3); + memcpy(buf+l-3, "USec", 5); + + r = sd_bus_message_append(reply, "(stt)", buf, v->value, v->next_elapse); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_calendar_timers( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Timer *t = userdata; + TimerValue *v; + int r; + + assert(bus); + assert(reply); + assert(t); + + r = sd_bus_message_open_container(reply, 'a', "(sst)"); + if (r < 0) + return r; + + LIST_FOREACH(value, v, t->values) { + _cleanup_free_ char *buf = NULL; + + if (v->base != TIMER_CALENDAR) + continue; + + r = calendar_spec_to_string(v->calendar_spec, &buf); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "(sst)", timer_base_to_string(v->base), buf, v->next_elapse); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_unit( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata, *trigger; + + assert(bus); + assert(reply); + assert(u); + + trigger = UNIT_TRIGGER(u); + + return sd_bus_message_append(reply, "s", trigger ? trigger->id : ""); +} + +static int property_get_next_elapse_monotonic( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Timer *t = userdata; + usec_t x; + + assert(bus); + assert(reply); + assert(t); + + if (t->next_elapse_monotonic_or_boottime <= 0) + x = 0; + else if (t->wake_system) { + usec_t a, b; + + a = now(CLOCK_MONOTONIC); + b = now(clock_boottime_or_monotonic()); + + if (t->next_elapse_monotonic_or_boottime + a > b) + x = t->next_elapse_monotonic_or_boottime + a - b; + else + x = 0; + } else + x = t->next_elapse_monotonic_or_boottime; + + return sd_bus_message_append(reply, "t", x); +} + +const sd_bus_vtable bus_timer_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Unit", "s", property_get_unit, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TimersMonotonic", "a(stt)", property_get_monotonic_timers, 0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + SD_BUS_PROPERTY("TimersCalendar", "a(sst)", property_get_calendar_timers, 0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + SD_BUS_PROPERTY("NextElapseUSecRealtime", "t", bus_property_get_usec, offsetof(Timer, next_elapse_realtime), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("NextElapseUSecMonotonic", "t", property_get_next_elapse_monotonic, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_PROPERTY_DUAL_TIMESTAMP("LastTriggerUSec", offsetof(Timer, last_trigger), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Timer, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("AccuracyUSec", "t", bus_property_get_usec, offsetof(Timer, accuracy_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RandomizedDelayUSec", "t", bus_property_get_usec, offsetof(Timer, random_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Persistent", "b", bus_property_get_bool, offsetof(Timer, persistent), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("WakeSystem", "b", bus_property_get_bool, offsetof(Timer, wake_system), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RemainAfterElapse", "b", bus_property_get_bool, offsetof(Timer, remain_after_elapse), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_VTABLE_END +}; + +static int bus_timer_set_transient_property( + Timer *t, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + int r; + + assert(t); + assert(name); + assert(message); + + if (STR_IN_SET(name, + "OnActiveSec", + "OnBootSec", + "OnStartupSec", + "OnUnitActiveSec", + "OnUnitInactiveSec")) { + + TimerValue *v; + TimerBase b = _TIMER_BASE_INVALID; + usec_t u = 0; + + b = timer_base_from_string(name); + if (b < 0) + return -EINVAL; + + r = sd_bus_message_read(message, "t", &u); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + char time[FORMAT_TIMESPAN_MAX]; + + unit_write_drop_in_private_format(UNIT(t), mode, name, "%s=%s\n", name, format_timespan(time, sizeof(time), u, USEC_PER_MSEC)); + + v = new0(TimerValue, 1); + if (!v) + return -ENOMEM; + + v->base = b; + v->value = u; + + LIST_PREPEND(value, t->values, v); + } + + return 1; + + } else if (streq(name, "OnCalendar")) { + + TimerValue *v; + CalendarSpec *c = NULL; + const char *str; + + r = sd_bus_message_read(message, "s", &str); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + r = calendar_spec_from_string(str, &c); + if (r < 0) + return r; + + unit_write_drop_in_private_format(UNIT(t), mode, name, "%s=%s\n", name, str); + + v = new0(TimerValue, 1); + if (!v) { + calendar_spec_free(c); + return -ENOMEM; + } + + v->base = TIMER_CALENDAR; + v->calendar_spec = c; + + LIST_PREPEND(value, t->values, v); + } + + return 1; + + } else if (STR_IN_SET(name, "AccuracyUSec", "AccuracySec")) { + usec_t u = 0; + + if (streq(name, "AccuracySec")) + log_notice("Client is using obsolete AccuracySec= transient property, please use AccuracyUSec= instead."); + + r = sd_bus_message_read(message, "t", &u); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + t->accuracy_usec = u; + unit_write_drop_in_private_format(UNIT(t), mode, name, "AccuracySec=" USEC_FMT "us\n", u); + } + + return 1; + + } else if (streq(name, "RandomizedDelayUSec")) { + usec_t u = 0; + + r = sd_bus_message_read(message, "t", &u); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + t->random_usec = u; + unit_write_drop_in_private_format(UNIT(t), mode, name, "RandomizedDelaySec=" USEC_FMT "us\n", u); + } + + return 1; + + } else if (streq(name, "WakeSystem")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + t->wake_system = b; + unit_write_drop_in_private_format(UNIT(t), mode, name, "%s=%s\n", name, yes_no(b)); + } + + return 1; + + } else if (streq(name, "RemainAfterElapse")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + t->remain_after_elapse = b; + unit_write_drop_in_private_format(UNIT(t), mode, name, "%s=%s\n", name, yes_no(b)); + } + + return 1; + } + + return 0; +} + +int bus_timer_set_property( + Unit *u, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + Timer *t = TIMER(u); + int r; + + assert(t); + assert(name); + assert(message); + + if (u->transient && u->load_state == UNIT_STUB) { + r = bus_timer_set_transient_property(t, name, message, mode, error); + if (r != 0) + return r; + } + + return 0; +} diff --git a/src/libcore/dbus-timer.h b/src/libcore/dbus-timer.h new file mode 100644 index 0000000000..505fb5df72 --- /dev/null +++ b/src/libcore/dbus-timer.h @@ -0,0 +1,28 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "unit.h" + +extern const sd_bus_vtable bus_timer_vtable[]; + +int bus_timer_set_property(Unit *u, const char *name, sd_bus_message *i, UnitSetPropertiesMode mode, sd_bus_error *error); diff --git a/src/libcore/dbus-unit.c b/src/libcore/dbus-unit.c new file mode 100644 index 0000000000..dcd8db0898 --- /dev/null +++ b/src/libcore/dbus-unit.c @@ -0,0 +1,1423 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 "alloc-util.h" +#include "bus-common-errors.h" +#include "cgroup-util.h" +#include "dbus-unit.h" +#include "dbus.h" +#include "fd-util.h" +#include "locale-util.h" +#include "log.h" +#include "process-util.h" +#include "selinux-access.h" +#include "signal-util.h" +#include "special.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_load_state, unit_load_state, UnitLoadState); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_job_mode, job_mode, JobMode); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_failure_action, failure_action, FailureAction); + +static int property_get_names( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + Iterator i; + const char *t; + int r; + + assert(bus); + assert(reply); + assert(u); + + r = sd_bus_message_open_container(reply, 'a', "s"); + if (r < 0) + return r; + + SET_FOREACH(t, u->names, i) { + r = sd_bus_message_append(reply, "s", t); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_following( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata, *f; + + assert(bus); + assert(reply); + assert(u); + + f = unit_following(u); + return sd_bus_message_append(reply, "s", f ? f->id : ""); +} + +static int property_get_dependencies( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Set *s = *(Set**) userdata; + Iterator j; + Unit *u; + int r; + + assert(bus); + assert(reply); + + r = sd_bus_message_open_container(reply, 'a', "s"); + if (r < 0) + return r; + + SET_FOREACH(u, s, j) { + r = sd_bus_message_append(reply, "s", u->id); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_obsolete_dependencies( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + assert(bus); + assert(reply); + + /* For dependency types we don't support anymore always return an empty array */ + return sd_bus_message_append(reply, "as", 0); +} + +static int property_get_description( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + return sd_bus_message_append(reply, "s", unit_description(u)); +} + +static int property_get_active_state( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + return sd_bus_message_append(reply, "s", unit_active_state_to_string(unit_active_state(u))); +} + +static int property_get_sub_state( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + return sd_bus_message_append(reply, "s", unit_sub_state_to_string(u)); +} + +static int property_get_unit_file_preset( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + int r; + + assert(bus); + assert(reply); + assert(u); + + r = unit_get_unit_file_preset(u); + + return sd_bus_message_append(reply, "s", + r < 0 ? "": + r > 0 ? "enabled" : "disabled"); +} + +static int property_get_unit_file_state( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + return sd_bus_message_append(reply, "s", unit_file_state_to_string(unit_get_unit_file_state(u))); +} + +static int property_get_can_start( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + return sd_bus_message_append(reply, "b", unit_can_start(u) && !u->refuse_manual_start); +} + +static int property_get_can_stop( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + /* On the lower levels we assume that every unit we can start + * we can also stop */ + + return sd_bus_message_append(reply, "b", unit_can_start(u) && !u->refuse_manual_stop); +} + +static int property_get_can_reload( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + return sd_bus_message_append(reply, "b", unit_can_reload(u)); +} + +static int property_get_can_isolate( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + return sd_bus_message_append(reply, "b", unit_can_isolate(u) && !u->refuse_manual_start); +} + +static int property_get_job( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + _cleanup_free_ char *p = NULL; + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + if (!u->job) + return sd_bus_message_append(reply, "(uo)", 0, "/"); + + p = job_dbus_path(u->job); + if (!p) + return -ENOMEM; + + return sd_bus_message_append(reply, "(uo)", u->job->id, p); +} + +static int property_get_need_daemon_reload( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + return sd_bus_message_append(reply, "b", unit_need_daemon_reload(u)); +} + +static int property_get_conditions( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + const char *(*to_string)(ConditionType type) = NULL; + Condition **list = userdata, *c; + int r; + + assert(bus); + assert(reply); + assert(list); + + to_string = streq(property, "Asserts") ? assert_type_to_string : condition_type_to_string; + + r = sd_bus_message_open_container(reply, 'a', "(sbbsi)"); + if (r < 0) + return r; + + LIST_FOREACH(conditions, c, *list) { + int tristate; + + tristate = + c->result == CONDITION_UNTESTED ? 0 : + c->result == CONDITION_SUCCEEDED ? 1 : -1; + + r = sd_bus_message_append(reply, "(sbbsi)", + to_string(c->type), + c->trigger, c->negate, + c->parameter, tristate); + if (r < 0) + return r; + + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_load_error( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL; + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + if (u->load_error != 0) + sd_bus_error_set_errno(&e, u->load_error); + + return sd_bus_message_append(reply, "(ss)", e.name, e.message); +} + +static int bus_verify_manage_units_async_full( + Unit *u, + const char *verb, + int capability, + const char *polkit_message, + sd_bus_message *call, + sd_bus_error *error) { + + const char *details[9] = { + "unit", u->id, + "verb", verb, + }; + + if (polkit_message) { + details[4] = "polkit.message"; + details[5] = polkit_message; + details[6] = "polkit.gettext_domain"; + details[7] = GETTEXT_PACKAGE; + } + + return bus_verify_polkit_async(call, capability, "org.freedesktop.systemd1.manage-units", details, false, UID_INVALID, &u->manager->polkit_registry, error); +} + +int bus_unit_method_start_generic( + sd_bus_message *message, + Unit *u, + JobType job_type, + bool reload_if_possible, + sd_bus_error *error) { + + const char *smode; + JobMode mode; + _cleanup_free_ char *verb = NULL; + static const char *const polkit_message_for_job[_JOB_TYPE_MAX] = { + [JOB_START] = N_("Authentication is required to start '$(unit)'."), + [JOB_STOP] = N_("Authentication is required to stop '$(unit)'."), + [JOB_RELOAD] = N_("Authentication is required to reload '$(unit)'."), + [JOB_RESTART] = N_("Authentication is required to restart '$(unit)'."), + [JOB_TRY_RESTART] = N_("Authentication is required to restart '$(unit)'."), + }; + int r; + + assert(message); + assert(u); + assert(job_type >= 0 && job_type < _JOB_TYPE_MAX); + + r = mac_selinux_unit_access_check( + u, message, + job_type_to_access_method(job_type), + error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "s", &smode); + if (r < 0) + return r; + + mode = job_mode_from_string(smode); + if (mode < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Job mode %s invalid", smode); + + if (reload_if_possible) + verb = strjoin("reload-or-", job_type_to_string(job_type), NULL); + else + verb = strdup(job_type_to_string(job_type)); + if (!verb) + return -ENOMEM; + + r = bus_verify_manage_units_async_full( + u, + verb, + CAP_SYS_ADMIN, + job_type < _JOB_TYPE_MAX ? polkit_message_for_job[job_type] : NULL, + message, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + return bus_unit_queue_job(message, u, job_type, mode, reload_if_possible, error); +} + +static int method_start(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return bus_unit_method_start_generic(message, userdata, JOB_START, false, error); +} + +static int method_stop(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return bus_unit_method_start_generic(message, userdata, JOB_STOP, false, error); +} + +static int method_reload(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return bus_unit_method_start_generic(message, userdata, JOB_RELOAD, false, error); +} + +static int method_restart(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return bus_unit_method_start_generic(message, userdata, JOB_RESTART, false, error); +} + +static int method_try_restart(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return bus_unit_method_start_generic(message, userdata, JOB_TRY_RESTART, false, error); +} + +static int method_reload_or_restart(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return bus_unit_method_start_generic(message, userdata, JOB_RESTART, true, error); +} + +static int method_reload_or_try_restart(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return bus_unit_method_start_generic(message, userdata, JOB_TRY_RESTART, true, error); +} + +int bus_unit_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Unit *u = userdata; + const char *swho; + int32_t signo; + KillWho who; + int r; + + assert(message); + assert(u); + + r = mac_selinux_unit_access_check(u, message, "stop", error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "si", &swho, &signo); + if (r < 0) + return r; + + if (isempty(swho)) + who = KILL_ALL; + else { + who = kill_who_from_string(swho); + if (who < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid who argument %s", swho); + } + + if (!SIGNAL_VALID(signo)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Signal number out of range."); + + r = bus_verify_manage_units_async_full( + u, + "kill", + CAP_KILL, + N_("Authentication is required to kill '$(unit)'."), + message, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = unit_kill(u, who, signo, error); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_unit_method_reset_failed(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Unit *u = userdata; + int r; + + assert(message); + assert(u); + + r = mac_selinux_unit_access_check(u, message, "reload", error); + if (r < 0) + return r; + + r = bus_verify_manage_units_async_full( + u, + "reset-failed", + CAP_SYS_ADMIN, + N_("Authentication is required to reset the \"failed\" state of '$(unit)'."), + message, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + unit_reset_failed(u); + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_unit_method_set_properties(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Unit *u = userdata; + int runtime, r; + + assert(message); + assert(u); + + r = mac_selinux_unit_access_check(u, message, "start", error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "b", &runtime); + if (r < 0) + return r; + + r = bus_verify_manage_units_async_full( + u, + "set-property", + CAP_SYS_ADMIN, + N_("Authentication is required to set properties on '$(unit)'."), + message, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = bus_unit_set_properties(u, message, runtime ? UNIT_RUNTIME : UNIT_PERSISTENT, true, error); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +const sd_bus_vtable bus_unit_vtable[] = { + SD_BUS_VTABLE_START(0), + + SD_BUS_PROPERTY("Id", "s", NULL, offsetof(Unit, id), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Names", "as", property_get_names, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Following", "s", property_get_following, 0, 0), + SD_BUS_PROPERTY("Requires", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUIRES]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Requisite", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUISITE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Wants", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_WANTS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("BindsTo", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_BINDS_TO]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PartOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_PART_OF]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RequiredBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUIRED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RequisiteOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUISITE_OF]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("WantedBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_WANTED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("BoundBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_BOUND_BY]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ConsistsOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_CONSISTS_OF]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Conflicts", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_CONFLICTS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ConflictedBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_CONFLICTED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Before", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_BEFORE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("After", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_AFTER]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("OnFailure", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_ON_FAILURE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Triggers", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_TRIGGERS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TriggeredBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_TRIGGERED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PropagatesReloadTo", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_PROPAGATES_RELOAD_TO]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ReloadPropagatedFrom", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_RELOAD_PROPAGATED_FROM]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("JoinsNamespaceOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_JOINS_NAMESPACE_OF]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RequiresOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("RequisiteOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("RequiredByOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("RequisiteOfOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("RequiresMountsFor", "as", NULL, offsetof(Unit, requires_mounts_for), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Documentation", "as", NULL, offsetof(Unit, documentation), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Description", "s", property_get_description, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LoadState", "s", property_get_load_state, offsetof(Unit, load_state), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ActiveState", "s", property_get_active_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("SubState", "s", property_get_sub_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("FragmentPath", "s", NULL, offsetof(Unit, fragment_path), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SourcePath", "s", NULL, offsetof(Unit, source_path), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DropInPaths", "as", NULL, offsetof(Unit, dropin_paths), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("UnitFileState", "s", property_get_unit_file_state, 0, 0), + SD_BUS_PROPERTY("UnitFilePreset", "s", property_get_unit_file_preset, 0, 0), + BUS_PROPERTY_DUAL_TIMESTAMP("StateChangeTimestamp", offsetof(Unit, state_change_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_PROPERTY_DUAL_TIMESTAMP("InactiveExitTimestamp", offsetof(Unit, inactive_exit_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_PROPERTY_DUAL_TIMESTAMP("ActiveEnterTimestamp", offsetof(Unit, active_enter_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_PROPERTY_DUAL_TIMESTAMP("ActiveExitTimestamp", offsetof(Unit, active_exit_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_PROPERTY_DUAL_TIMESTAMP("InactiveEnterTimestamp", offsetof(Unit, inactive_enter_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("CanStart", "b", property_get_can_start, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CanStop", "b", property_get_can_stop, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CanReload", "b", property_get_can_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CanIsolate", "b", property_get_can_isolate, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Job", "(uo)", property_get_job, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("StopWhenUnneeded", "b", bus_property_get_bool, offsetof(Unit, stop_when_unneeded), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RefuseManualStart", "b", bus_property_get_bool, offsetof(Unit, refuse_manual_start), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RefuseManualStop", "b", bus_property_get_bool, offsetof(Unit, refuse_manual_stop), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("AllowIsolate", "b", bus_property_get_bool, offsetof(Unit, allow_isolate), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultDependencies", "b", bus_property_get_bool, offsetof(Unit, default_dependencies), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("OnFailureJobMode", "s", property_get_job_mode, offsetof(Unit, on_failure_job_mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("IgnoreOnIsolate", "b", bus_property_get_bool, offsetof(Unit, ignore_on_isolate), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("NeedDaemonReload", "b", property_get_need_daemon_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("JobTimeoutUSec", "t", bus_property_get_usec, offsetof(Unit, job_timeout), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("JobTimeoutAction", "s", property_get_failure_action, offsetof(Unit, job_timeout_action), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("JobTimeoutRebootArgument", "s", NULL, offsetof(Unit, job_timeout_reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ConditionResult", "b", bus_property_get_bool, offsetof(Unit, condition_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("AssertResult", "b", bus_property_get_bool, offsetof(Unit, assert_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_PROPERTY_DUAL_TIMESTAMP("ConditionTimestamp", offsetof(Unit, condition_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_PROPERTY_DUAL_TIMESTAMP("AssertTimestamp", offsetof(Unit, assert_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Conditions", "a(sbbsi)", property_get_conditions, offsetof(Unit, conditions), 0), + SD_BUS_PROPERTY("Asserts", "a(sbbsi)", property_get_conditions, offsetof(Unit, asserts), 0), + SD_BUS_PROPERTY("LoadError", "(ss)", property_get_load_error, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Transient", "b", bus_property_get_bool, offsetof(Unit, transient), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StartLimitIntervalSec", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), /* obsolete alias name */ + SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StartLimitAction", "s", property_get_failure_action, offsetof(Unit, start_limit_action), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RebootArgument", "s", NULL, offsetof(Unit, reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST), + + SD_BUS_METHOD("Start", "s", "o", method_start, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Stop", "s", "o", method_stop, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Reload", "s", "o", method_reload, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Restart", "s", "o", method_restart, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("TryRestart", "s", "o", method_try_restart, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ReloadOrRestart", "s", "o", method_reload_or_restart, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ReloadOrTryRestart", "s", "o", method_reload_or_try_restart, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Kill", "si", NULL, bus_unit_method_kill, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ResetFailed", NULL, NULL, bus_unit_method_reset_failed, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetProperties", "ba(sv)", NULL, bus_unit_method_set_properties, SD_BUS_VTABLE_UNPRIVILEGED), + + SD_BUS_VTABLE_END +}; + +static int property_get_slice( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + return sd_bus_message_append(reply, "s", unit_slice_name(u)); +} + +static int property_get_current_memory( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + uint64_t sz = (uint64_t) -1; + Unit *u = userdata; + int r; + + assert(bus); + assert(reply); + assert(u); + + r = unit_get_memory_current(u, &sz); + if (r < 0 && r != -ENODATA) + log_unit_warning_errno(u, r, "Failed to get memory.usage_in_bytes attribute: %m"); + + return sd_bus_message_append(reply, "t", sz); +} + +static int property_get_current_tasks( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + uint64_t cn = (uint64_t) -1; + Unit *u = userdata; + int r; + + assert(bus); + assert(reply); + assert(u); + + r = unit_get_tasks_current(u, &cn); + if (r < 0 && r != -ENODATA) + log_unit_warning_errno(u, r, "Failed to get pids.current attribute: %m"); + + return sd_bus_message_append(reply, "t", cn); +} + +static int property_get_cpu_usage( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + nsec_t ns = (nsec_t) -1; + Unit *u = userdata; + int r; + + assert(bus); + assert(reply); + assert(u); + + r = unit_get_cpu_usage(u, &ns); + if (r < 0 && r != -ENODATA) + log_unit_warning_errno(u, r, "Failed to get cpuacct.usage attribute: %m"); + + return sd_bus_message_append(reply, "t", ns); +} + +static int property_get_cgroup( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + const char *t; + + assert(bus); + assert(reply); + assert(u); + + /* Three cases: a) u->cgroup_path is NULL, in which case the + * unit has no control group, which we report as the empty + * string. b) u->cgroup_path is the empty string, which + * indicates the root cgroup, which we report as "/". c) all + * other cases we report as-is. */ + + if (u->cgroup_path) + t = isempty(u->cgroup_path) ? "/" : u->cgroup_path; + else + t = ""; + + return sd_bus_message_append(reply, "s", t); +} + +static int append_process(sd_bus_message *reply, const char *p, pid_t pid, Set *pids) { + _cleanup_free_ char *buf = NULL, *cmdline = NULL; + int r; + + assert(reply); + assert(pid > 0); + + r = set_put(pids, PID_TO_PTR(pid)); + if (r == -EEXIST || r == 0) + return 0; + if (r < 0) + return r; + + if (!p) { + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &buf); + if (r == -ESRCH) + return 0; + if (r < 0) + return r; + + p = buf; + } + + (void) get_process_cmdline(pid, 0, true, &cmdline); + + return sd_bus_message_append(reply, + "(sus)", + p, + (uint32_t) pid, + cmdline); +} + +static int append_cgroup(sd_bus_message *reply, const char *p, Set *pids) { + _cleanup_closedir_ DIR *d = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(reply); + assert(p); + + r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, p, &f); + if (r == ENOENT) + return 0; + if (r < 0) + return r; + + for (;;) { + pid_t pid; + + r = cg_read_pid(f, &pid); + if (r < 0) + return r; + if (r == 0) + break; + + if (is_kernel_thread(pid) > 0) + continue; + + r = append_process(reply, p, pid, pids); + if (r < 0) + return r; + } + + r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, p, &d); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + for (;;) { + _cleanup_free_ char *g = NULL, *j = NULL; + + r = cg_read_subgroup(d, &g); + if (r < 0) + return r; + if (r == 0) + break; + + j = strjoin(p, "/", g, NULL); + if (!j) + return -ENOMEM; + + r = append_cgroup(reply, j, pids); + if (r < 0) + return r; + } + + return 0; +} + +int bus_unit_method_get_processes(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(set_freep) Set *pids = NULL; + Unit *u = userdata; + pid_t pid; + int r; + + assert(message); + + pids = set_new(NULL); + if (!pids) + return -ENOMEM; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(sus)"); + if (r < 0) + return r; + + if (u->cgroup_path) { + r = append_cgroup(reply, u->cgroup_path, pids); + if (r < 0) + return r; + } + + /* The main and control pids might live outside of the cgroup, hence fetch them separately */ + pid = unit_main_pid(u); + if (pid > 0) { + r = append_process(reply, NULL, pid, pids); + if (r < 0) + return r; + } + + pid = unit_control_pid(u); + if (pid > 0) { + r = append_process(reply, NULL, pid, pids); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +const sd_bus_vtable bus_unit_cgroup_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Slice", "s", property_get_slice, 0, 0), + SD_BUS_PROPERTY("ControlGroup", "s", property_get_cgroup, 0, 0), + SD_BUS_PROPERTY("MemoryCurrent", "t", property_get_current_memory, 0, 0), + SD_BUS_PROPERTY("CPUUsageNSec", "t", property_get_cpu_usage, 0, 0), + SD_BUS_PROPERTY("TasksCurrent", "t", property_get_current_tasks, 0, 0), + SD_BUS_METHOD("GetProcesses", NULL, "a(sus)", bus_unit_method_get_processes, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END +}; + +static int send_new_signal(sd_bus *bus, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *p = NULL; + Unit *u = userdata; + int r; + + assert(bus); + assert(u); + + p = unit_dbus_path(u); + if (!p) + return -ENOMEM; + + r = sd_bus_message_new_signal( + bus, + &m, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UnitNew"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "so", u->id, p); + if (r < 0) + return r; + + return sd_bus_send(bus, m, NULL); +} + +static int send_changed_signal(sd_bus *bus, void *userdata) { + _cleanup_free_ char *p = NULL; + Unit *u = userdata; + int r; + + assert(bus); + assert(u); + + p = unit_dbus_path(u); + if (!p) + return -ENOMEM; + + /* Send a properties changed signal. First for the specific + * type, then for the generic unit. The clients may rely on + * this order to get atomic behavior if needed. */ + + r = sd_bus_emit_properties_changed_strv( + bus, p, + unit_dbus_interface_from_type(u->type), + NULL); + if (r < 0) + return r; + + return sd_bus_emit_properties_changed_strv( + bus, p, + "org.freedesktop.systemd1.Unit", + NULL); +} + +void bus_unit_send_change_signal(Unit *u) { + int r; + assert(u); + + if (u->in_dbus_queue) { + LIST_REMOVE(dbus_queue, u->manager->dbus_unit_queue, u); + u->in_dbus_queue = false; + } + + if (!u->id) + return; + + r = bus_foreach_bus(u->manager, NULL, u->sent_dbus_new_signal ? send_changed_signal : send_new_signal, u); + if (r < 0) + log_unit_debug_errno(u, r, "Failed to send unit change signal for %s: %m", u->id); + + u->sent_dbus_new_signal = true; +} + +static int send_removed_signal(sd_bus *bus, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *p = NULL; + Unit *u = userdata; + int r; + + assert(bus); + assert(u); + + p = unit_dbus_path(u); + if (!p) + return -ENOMEM; + + r = sd_bus_message_new_signal( + bus, + &m, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UnitRemoved"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "so", u->id, p); + if (r < 0) + return r; + + return sd_bus_send(bus, m, NULL); +} + +void bus_unit_send_removed_signal(Unit *u) { + int r; + assert(u); + + if (!u->sent_dbus_new_signal) + bus_unit_send_change_signal(u); + + if (!u->id) + return; + + r = bus_foreach_bus(u->manager, NULL, send_removed_signal, u); + if (r < 0) + log_unit_debug_errno(u, r, "Failed to send unit remove signal for %s: %m", u->id); +} + +int bus_unit_queue_job( + sd_bus_message *message, + Unit *u, + JobType type, + JobMode mode, + bool reload_if_possible, + sd_bus_error *error) { + + _cleanup_free_ char *path = NULL; + Job *j; + int r; + + assert(message); + assert(u); + assert(type >= 0 && type < _JOB_TYPE_MAX); + assert(mode >= 0 && mode < _JOB_MODE_MAX); + + r = mac_selinux_unit_access_check( + u, message, + job_type_to_access_method(type), + error); + if (r < 0) + return r; + + if (reload_if_possible && unit_can_reload(u)) { + if (type == JOB_RESTART) + type = JOB_RELOAD_OR_START; + else if (type == JOB_TRY_RESTART) + type = JOB_TRY_RELOAD; + } + + if (type == JOB_STOP && + (u->load_state == UNIT_NOT_FOUND || u->load_state == UNIT_ERROR) && + unit_active_state(u) == UNIT_INACTIVE) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s not loaded.", u->id); + + if ((type == JOB_START && u->refuse_manual_start) || + (type == JOB_STOP && u->refuse_manual_stop) || + ((type == JOB_RESTART || type == JOB_TRY_RESTART) && (u->refuse_manual_start || u->refuse_manual_stop)) || + (type == JOB_RELOAD_OR_START && job_type_collapse(type, u) == JOB_START && u->refuse_manual_start)) + return sd_bus_error_setf(error, BUS_ERROR_ONLY_BY_DEPENDENCY, "Operation refused, unit %s may be requested by dependency only.", u->id); + + r = manager_add_job(u->manager, type, u, mode, error, &j); + if (r < 0) + return r; + + if (sd_bus_message_get_bus(message) == u->manager->api_bus) { + if (!j->clients) { + r = sd_bus_track_new(sd_bus_message_get_bus(message), &j->clients, NULL, NULL); + if (r < 0) + return r; + } + + r = sd_bus_track_add_sender(j->clients, message); + if (r < 0) + return r; + } + + path = job_dbus_path(j); + if (!path) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", path); +} + +static int bus_unit_set_transient_property( + Unit *u, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + int r; + + assert(u); + assert(name); + assert(message); + + if (streq(name, "Description")) { + const char *d; + + r = sd_bus_message_read(message, "s", &d); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + r = unit_set_description(u, d); + if (r < 0) + return r; + + unit_write_drop_in_format(u, mode, name, "[Unit]\nDescription=%s\n", d); + } + + return 1; + + } else if (streq(name, "DefaultDependencies")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + u->default_dependencies = b; + unit_write_drop_in_format(u, mode, name, "[Unit]\nDefaultDependencies=%s\n", yes_no(b)); + } + + return 1; + + } else if (streq(name, "Slice")) { + Unit *slice; + const char *s; + + if (!UNIT_HAS_CGROUP_CONTEXT(u)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "The slice property is only available for units with control groups."); + if (u->type == UNIT_SLICE) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Slice may not be set for slice units."); + if (unit_has_name(u, SPECIAL_INIT_SCOPE)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot set slice for init.scope"); + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + if (!unit_name_is_valid(s, UNIT_NAME_PLAIN)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid unit name '%s'", s); + + /* Note that we do not dispatch the load queue here yet, as we don't want our own transient unit to be + * loaded while we are still setting it up. Or in other words, we use manager_load_unit_prepare() + * instead of manager_load_unit() on purpose, here. */ + r = manager_load_unit_prepare(u->manager, s, NULL, error, &slice); + if (r < 0) + return r; + + if (slice->type != UNIT_SLICE) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit name '%s' is not a slice", s); + + if (mode != UNIT_CHECK) { + r = unit_set_slice(u, slice); + if (r < 0) + return r; + + unit_write_drop_in_private_format(u, mode, name, "Slice=%s\n", s); + } + + return 1; + + } else if (STR_IN_SET(name, + "Requires", "RequiresOverridable", + "Requisite", "RequisiteOverridable", + "Wants", + "BindsTo", + "Conflicts", + "Before", "After", + "OnFailure", + "PropagatesReloadTo", "ReloadPropagatedFrom", + "PartOf")) { + + UnitDependency d; + const char *other; + + if (streq(name, "RequiresOverridable")) + d = UNIT_REQUIRES; /* redirect for obsolete unit dependency type */ + else if (streq(name, "RequisiteOverridable")) + d = UNIT_REQUISITE; /* same here */ + else { + d = unit_dependency_from_string(name); + if (d < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid unit dependency: %s", name); + } + + r = sd_bus_message_enter_container(message, 'a', "s"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(message, "s", &other)) > 0) { + if (!unit_name_is_valid(other, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid unit name %s", other); + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *label = NULL; + + r = unit_add_dependency_by_name(u, d, other, NULL, true); + if (r < 0) + return r; + + label = strjoin(name, "-", other, NULL); + if (!label) + return -ENOMEM; + + unit_write_drop_in_format(u, mode, label, "[Unit]\n%s=%s\n", name, other); + } + + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + return 1; + } + + return 0; +} + +int bus_unit_set_properties( + Unit *u, + sd_bus_message *message, + UnitSetPropertiesMode mode, + bool commit, + sd_bus_error *error) { + + bool for_real = false; + unsigned n = 0; + int r; + + assert(u); + assert(message); + + /* We iterate through the array twice. First run we just check + * if all passed data is valid, second run actually applies + * it. This is to implement transaction-like behaviour without + * actually providing full transactions. */ + + r = sd_bus_message_enter_container(message, 'a', "(sv)"); + if (r < 0) + return r; + + for (;;) { + const char *name; + + r = sd_bus_message_enter_container(message, 'r', "sv"); + if (r < 0) + return r; + if (r == 0) { + if (for_real || mode == UNIT_CHECK) + break; + + /* Reached EOF. Let's try again, and this time for realz... */ + r = sd_bus_message_rewind(message, false); + if (r < 0) + return r; + + for_real = true; + continue; + } + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + if (!UNIT_VTABLE(u)->bus_set_property) + return sd_bus_error_setf(error, SD_BUS_ERROR_PROPERTY_READ_ONLY, "Objects of this type do not support setting properties."); + + r = sd_bus_message_enter_container(message, 'v', NULL); + if (r < 0) + return r; + + r = UNIT_VTABLE(u)->bus_set_property(u, name, message, for_real ? mode : UNIT_CHECK, error); + if (r == 0 && u->transient && u->load_state == UNIT_STUB) + r = bus_unit_set_transient_property(u, name, message, for_real ? mode : UNIT_CHECK, error); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_PROPERTY_READ_ONLY, "Cannot set property %s, or unknown property.", name); + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + n += for_real; + } + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (commit && n > 0 && UNIT_VTABLE(u)->bus_commit_properties) + UNIT_VTABLE(u)->bus_commit_properties(u); + + return n; +} + +int bus_unit_check_load_state(Unit *u, sd_bus_error *error) { + assert(u); + + if (u->load_state == UNIT_LOADED) + return 0; + + /* Give a better description of the unit error when + * possible. Note that in the case of UNIT_MASKED, load_error + * is not set. */ + if (u->load_state == UNIT_MASKED) + return sd_bus_error_setf(error, BUS_ERROR_UNIT_MASKED, "Unit %s is masked.", u->id); + + if (u->load_state == UNIT_NOT_FOUND) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s not found.", u->id); + + return sd_bus_error_set_errnof(error, u->load_error, "Unit %s is not loaded properly: %m.", u->id); +} diff --git a/src/libcore/dbus-unit.h b/src/libcore/dbus-unit.h new file mode 100644 index 0000000000..758045a47c --- /dev/null +++ b/src/libcore/dbus-unit.h @@ -0,0 +1,41 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "unit.h" + +extern const sd_bus_vtable bus_unit_vtable[]; +extern const sd_bus_vtable bus_unit_cgroup_vtable[]; + +void bus_unit_send_change_signal(Unit *u); +void bus_unit_send_removed_signal(Unit *u); + +int bus_unit_method_start_generic(sd_bus_message *message, Unit *u, JobType job_type, bool reload_if_possible, sd_bus_error *error); +int bus_unit_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_unit_method_reset_failed(sd_bus_message *message, void *userdata, sd_bus_error *error); + +int bus_unit_queue_job(sd_bus_message *message, Unit *u, JobType type, JobMode mode, bool reload_if_possible, sd_bus_error *error); +int bus_unit_set_properties(Unit *u, sd_bus_message *message, UnitSetPropertiesMode mode, bool commit, sd_bus_error *error); +int bus_unit_method_set_properties(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_unit_method_get_processes(sd_bus_message *message, void *userdata, sd_bus_error *error); + +int bus_unit_check_load_state(Unit *u, sd_bus_error *error); diff --git a/src/libcore/dbus.c b/src/libcore/dbus.c new file mode 100644 index 0000000000..1b217da303 --- /dev/null +++ b/src/libcore/dbus.c @@ -0,0 +1,1243 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include + +#include + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-error.h" +#include "bus-internal.h" +#include "bus-util.h" +#include "dbus-cgroup.h" +#include "dbus-execute.h" +#include "dbus-job.h" +#include "dbus-kill.h" +#include "dbus-manager.h" +#include "dbus-unit.h" +#include "dbus.h" +#include "fd-util.h" +#include "log.h" +#include "missing.h" +#include "mkdir.h" +#include "selinux-access.h" +#include "special.h" +#include "string-util.h" +#include "strv.h" +#include "strxcpyx.h" +#include "user-util.h" + +#define CONNECTIONS_MAX 4096 + +static void destroy_bus(Manager *m, sd_bus **bus); + +int bus_send_queued_message(Manager *m) { + int r; + + assert(m); + + if (!m->queued_message) + return 0; + + /* If we cannot get rid of this message we won't dispatch any + * D-Bus messages, so that we won't end up wanting to queue + * another message. */ + + r = sd_bus_send(NULL, m->queued_message, NULL); + if (r < 0) + log_warning_errno(r, "Failed to send queued message: %m"); + + m->queued_message = sd_bus_message_unref(m->queued_message); + + return 0; +} + +int bus_forward_agent_released(Manager *m, const char *path) { + int r; + + assert(m); + assert(path); + + if (!MANAGER_IS_SYSTEM(m)) + return 0; + + if (!m->system_bus) + return 0; + + /* If we are running a system instance we forward the agent message on the system bus, so that the user + * instances get notified about this, too */ + + r = sd_bus_emit_signal(m->system_bus, + "/org/freedesktop/systemd1/agent", + "org.freedesktop.systemd1.Agent", + "Released", + "s", path); + if (r < 0) + return log_warning_errno(r, "Failed to propagate agent release message: %m"); + + return 1; +} + +static int signal_agent_released(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + Manager *m = userdata; + const char *cgroup; + uid_t sender_uid; + int r; + + assert(message); + assert(m); + + /* only accept org.freedesktop.systemd1.Agent from UID=0 */ + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_euid(creds, &sender_uid); + if (r < 0 || sender_uid != 0) + return 0; + + /* parse 'cgroup-empty' notification */ + r = sd_bus_message_read(message, "s", &cgroup); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + manager_notify_cgroup_empty(m, cgroup); + return 0; +} + +static int signal_disconnected(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + sd_bus *bus; + + assert(message); + assert(m); + assert_se(bus = sd_bus_message_get_bus(message)); + + if (bus == m->api_bus) + destroy_bus(m, &m->api_bus); + if (bus == m->system_bus) + destroy_bus(m, &m->system_bus); + if (set_remove(m->private_buses, bus)) { + log_debug("Got disconnect on private connection."); + destroy_bus(m, &bus); + } + + return 0; +} + +static int signal_activation_request(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Manager *m = userdata; + const char *name; + Unit *u; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (manager_unit_inactive_or_pending(m, SPECIAL_DBUS_SERVICE) || + manager_unit_inactive_or_pending(m, SPECIAL_DBUS_SOCKET)) { + r = sd_bus_error_setf(&error, BUS_ERROR_SHUTTING_DOWN, "Refusing activation, D-Bus is shutting down."); + goto failed; + } + + r = manager_load_unit(m, name, NULL, &error, &u); + if (r < 0) + goto failed; + + if (u->refuse_manual_start) { + r = sd_bus_error_setf(&error, BUS_ERROR_ONLY_BY_DEPENDENCY, "Operation refused, %s may be requested by dependency only.", u->id); + goto failed; + } + + r = manager_add_job(m, JOB_START, u, JOB_REPLACE, &error, NULL); + if (r < 0) + goto failed; + + /* Successfully queued, that's it for us */ + return 0; + +failed: + if (!sd_bus_error_is_set(&error)) + sd_bus_error_set_errno(&error, r); + + log_debug("D-Bus activation failed for %s: %s", name, bus_error_message(&error, r)); + + r = sd_bus_message_new_signal(sd_bus_message_get_bus(message), &reply, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Activator", "ActivationFailure"); + if (r < 0) { + bus_log_create_error(r); + return 0; + } + + r = sd_bus_message_append(reply, "sss", name, error.name, error.message); + if (r < 0) { + bus_log_create_error(r); + return 0; + } + + r = sd_bus_send_to(NULL, reply, "org.freedesktop.DBus", NULL); + if (r < 0) + return log_error_errno(r, "Failed to respond with to bus activation request: %m"); + + return 0; +} + +#ifdef HAVE_SELINUX +static int mac_selinux_filter(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + const char *verb, *path; + Unit *u = NULL; + Job *j; + int r; + + assert(message); + + /* Our own method calls are all protected individually with + * selinux checks, but the built-in interfaces need to be + * protected too. */ + + if (sd_bus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "Set")) + verb = "reload"; + else if (sd_bus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", NULL) || + sd_bus_message_is_method_call(message, "org.freedesktop.DBus.Properties", NULL) || + sd_bus_message_is_method_call(message, "org.freedesktop.DBus.ObjectManager", NULL) || + sd_bus_message_is_method_call(message, "org.freedesktop.DBus.Peer", NULL)) + verb = "status"; + else + return 0; + + path = sd_bus_message_get_path(message); + + if (object_path_startswith("/org/freedesktop/systemd1", path)) { + + r = mac_selinux_access_check(message, verb, error); + if (r < 0) + return r; + + return 0; + } + + if (streq_ptr(path, "/org/freedesktop/systemd1/unit/self")) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + pid_t pid; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return 0; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r < 0) + return 0; + + u = manager_get_unit_by_pid(m, pid); + } else { + r = manager_get_job_from_dbus_path(m, path, &j); + if (r >= 0) + u = j->unit; + else + manager_load_unit_from_dbus_path(m, path, NULL, &u); + } + + if (!u) + return 0; + + r = mac_selinux_unit_access_check(u, message, verb, error); + if (r < 0) + return r; + + return 0; +} +#endif + +static int bus_job_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + Job *j; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + r = manager_get_job_from_dbus_path(m, path, &j); + if (r < 0) + return 0; + + *found = j; + return 1; +} + +static int find_unit(Manager *m, sd_bus *bus, const char *path, Unit **unit, sd_bus_error *error) { + Unit *u; + int r; + + assert(m); + assert(bus); + assert(path); + + if (streq_ptr(path, "/org/freedesktop/systemd1/unit/self")) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + sd_bus_message *message; + pid_t pid; + + message = sd_bus_get_current_message(bus); + if (!message) + return 0; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r < 0) + return r; + + u = manager_get_unit_by_pid(m, pid); + } else { + r = manager_load_unit_from_dbus_path(m, path, error, &u); + if (r < 0) + return 0; + } + + if (!u) + return 0; + + *unit = u; + return 1; +} + +static int bus_unit_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + return find_unit(m, bus, path, (Unit**) found, error); +} + +static int bus_unit_interface_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + Unit *u; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + r = find_unit(m, bus, path, &u, error); + if (r <= 0) + return r; + + if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) + return 0; + + *found = u; + return 1; +} + +static int bus_unit_cgroup_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + Unit *u; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + r = find_unit(m, bus, path, &u, error); + if (r <= 0) + return r; + + if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) + return 0; + + if (!UNIT_HAS_CGROUP_CONTEXT(u)) + return 0; + + *found = u; + return 1; +} + +static int bus_cgroup_context_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + CGroupContext *c; + Unit *u; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + r = find_unit(m, bus, path, &u, error); + if (r <= 0) + return r; + + if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) + return 0; + + c = unit_get_cgroup_context(u); + if (!c) + return 0; + + *found = c; + return 1; +} + +static int bus_exec_context_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + ExecContext *c; + Unit *u; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + r = find_unit(m, bus, path, &u, error); + if (r <= 0) + return r; + + if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) + return 0; + + c = unit_get_exec_context(u); + if (!c) + return 0; + + *found = c; + return 1; +} + +static int bus_kill_context_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + KillContext *c; + Unit *u; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + r = find_unit(m, bus, path, &u, error); + if (r <= 0) + return r; + + if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) + return 0; + + c = unit_get_kill_context(u); + if (!c) + return 0; + + *found = c; + return 1; +} + +static int bus_job_enumerate(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + _cleanup_free_ char **l = NULL; + Manager *m = userdata; + unsigned k = 0; + Iterator i; + Job *j; + + l = new0(char*, hashmap_size(m->jobs)+1); + if (!l) + return -ENOMEM; + + HASHMAP_FOREACH(j, m->jobs, i) { + l[k] = job_dbus_path(j); + if (!l[k]) + return -ENOMEM; + + k++; + } + + assert(hashmap_size(m->jobs) == k); + + *nodes = l; + l = NULL; + + return k; +} + +static int bus_unit_enumerate(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + _cleanup_free_ char **l = NULL; + Manager *m = userdata; + unsigned k = 0; + Iterator i; + Unit *u; + + l = new0(char*, hashmap_size(m->units)+1); + if (!l) + return -ENOMEM; + + HASHMAP_FOREACH(u, m->units, i) { + l[k] = unit_dbus_path(u); + if (!l[k]) + return -ENOMEM; + + k++; + } + + *nodes = l; + l = NULL; + + return k; +} + +static int bus_setup_api_vtables(Manager *m, sd_bus *bus) { + UnitType t; + int r; + + assert(m); + assert(bus); + +#ifdef HAVE_SELINUX + r = sd_bus_add_filter(bus, NULL, mac_selinux_filter, m); + if (r < 0) + return log_error_errno(r, "Failed to add SELinux access filter: %m"); +#endif + + r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", bus_manager_vtable, m); + if (r < 0) + return log_error_errno(r, "Failed to register Manager vtable: %m"); + + r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/job", "org.freedesktop.systemd1.Job", bus_job_vtable, bus_job_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register Job vtable: %m"); + + r = sd_bus_add_node_enumerator(bus, NULL, "/org/freedesktop/systemd1/job", bus_job_enumerate, m); + if (r < 0) + return log_error_errno(r, "Failed to add job enumerator: %m"); + + r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", "org.freedesktop.systemd1.Unit", bus_unit_vtable, bus_unit_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register Unit vtable: %m"); + + r = sd_bus_add_node_enumerator(bus, NULL, "/org/freedesktop/systemd1/unit", bus_unit_enumerate, m); + if (r < 0) + return log_error_errno(r, "Failed to add job enumerator: %m"); + + for (t = 0; t < _UNIT_TYPE_MAX; t++) { + const char *interface; + + assert_se(interface = unit_dbus_interface_from_type(t)); + + r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, unit_vtable[t]->bus_vtable, bus_unit_interface_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register type specific vtable for %s: %m", interface); + + if (unit_vtable[t]->cgroup_context_offset > 0) { + r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, bus_unit_cgroup_vtable, bus_unit_cgroup_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register control group unit vtable for %s: %m", interface); + + r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, bus_cgroup_vtable, bus_cgroup_context_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register control group vtable for %s: %m", interface); + } + + if (unit_vtable[t]->exec_context_offset > 0) { + r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, bus_exec_vtable, bus_exec_context_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register execute vtable for %s: %m", interface); + } + + if (unit_vtable[t]->kill_context_offset > 0) { + r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, bus_kill_vtable, bus_kill_context_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register kill vtable for %s: %m", interface); + } + } + + return 0; +} + +static int bus_setup_disconnected_match(Manager *m, sd_bus *bus) { + int r; + + assert(m); + assert(bus); + + r = sd_bus_add_match( + bus, + NULL, + "sender='org.freedesktop.DBus.Local'," + "type='signal'," + "path='/org/freedesktop/DBus/Local'," + "interface='org.freedesktop.DBus.Local'," + "member='Disconnected'", + signal_disconnected, m); + + if (r < 0) + return log_error_errno(r, "Failed to register match for Disconnected message: %m"); + + return 0; +} + +static int bus_on_connection(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_close_ int nfd = -1; + Manager *m = userdata; + sd_id128_t id; + int r; + + assert(s); + assert(m); + + nfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); + if (nfd < 0) { + log_warning_errno(errno, "Failed to accept private connection, ignoring: %m"); + return 0; + } + + if (set_size(m->private_buses) >= CONNECTIONS_MAX) { + log_warning("Too many concurrent connections, refusing"); + return 0; + } + + r = set_ensure_allocated(&m->private_buses, NULL); + if (r < 0) { + log_oom(); + return 0; + } + + r = sd_bus_new(&bus); + if (r < 0) { + log_warning_errno(r, "Failed to allocate new private connection bus: %m"); + return 0; + } + + r = sd_bus_set_fd(bus, nfd, nfd); + if (r < 0) { + log_warning_errno(r, "Failed to set fd on new connection bus: %m"); + return 0; + } + + nfd = -1; + + r = bus_check_peercred(bus); + if (r < 0) { + log_warning_errno(r, "Incoming private connection from unprivileged client, refusing: %m"); + return 0; + } + + assert_se(sd_id128_randomize(&id) >= 0); + + r = sd_bus_set_server(bus, 1, id); + if (r < 0) { + log_warning_errno(r, "Failed to enable server support for new connection bus: %m"); + return 0; + } + + r = sd_bus_negotiate_creds(bus, 1, + SD_BUS_CREDS_PID|SD_BUS_CREDS_UID| + SD_BUS_CREDS_EUID|SD_BUS_CREDS_EFFECTIVE_CAPS| + SD_BUS_CREDS_SELINUX_CONTEXT); + if (r < 0) { + log_warning_errno(r, "Failed to enable credentials for new connection: %m"); + return 0; + } + + r = sd_bus_start(bus); + if (r < 0) { + log_warning_errno(r, "Failed to start new connection bus: %m"); + return 0; + } + + r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) { + log_warning_errno(r, "Failed to attach new connection bus to event loop: %m"); + return 0; + } + + r = bus_setup_disconnected_match(m, bus); + if (r < 0) + return 0; + + r = bus_setup_api_vtables(m, bus); + if (r < 0) { + log_warning_errno(r, "Failed to set up API vtables on new connection bus: %m"); + return 0; + } + + r = set_put(m->private_buses, bus); + if (r < 0) { + log_warning_errno(r, "Failed to add new connection bus to set: %m"); + return 0; + } + + bus = NULL; + + log_debug("Accepted new private connection."); + + return 0; +} + +int manager_sync_bus_names(Manager *m, sd_bus *bus) { + _cleanup_strv_free_ char **names = NULL; + const char *name; + Iterator i; + Unit *u; + int r; + + assert(m); + assert(bus); + + r = sd_bus_list_names(bus, &names, NULL); + if (r < 0) + return log_error_errno(r, "Failed to get initial list of names: %m"); + + /* We have to synchronize the current bus names with the + * list of active services. To do this, walk the list of + * all units with bus names. */ + HASHMAP_FOREACH_KEY(u, name, m->watch_bus, i) { + Service *s = SERVICE(u); + + assert(s); + + if (!streq_ptr(s->bus_name, name)) { + log_unit_warning(u, "Bus name has changed from %s → %s, ignoring.", s->bus_name, name); + continue; + } + + /* Check if a service's bus name is in the list of currently + * active names */ + if (strv_contains(names, name)) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + const char *unique; + + /* If it is, determine its current owner */ + r = sd_bus_get_name_creds(bus, name, SD_BUS_CREDS_UNIQUE_NAME, &creds); + if (r < 0) { + log_error_errno(r, "Failed to get bus name owner %s: %m", name); + continue; + } + + r = sd_bus_creds_get_unique_name(creds, &unique); + if (r < 0) { + log_error_errno(r, "Failed to get unique name for %s: %m", name); + continue; + } + + /* Now, let's compare that to the previous bus owner, and + * if it's still the same, all is fine, so just don't + * bother the service. Otherwise, the name has apparently + * changed, so synthesize a name owner changed signal. */ + + if (!streq_ptr(unique, s->bus_name_owner)) + UNIT_VTABLE(u)->bus_name_owner_change(u, name, s->bus_name_owner, unique); + } else { + /* So, the name we're watching is not on the bus. + * This either means it simply hasn't appeared yet, + * or it was lost during the daemon reload. + * Check if the service has a stored name owner, + * and synthesize a name loss signal in this case. */ + + if (s->bus_name_owner) + UNIT_VTABLE(u)->bus_name_owner_change(u, name, s->bus_name_owner, NULL); + } + } + + return 0; +} + +static int bus_setup_api(Manager *m, sd_bus *bus) { + Iterator i; + char *name; + Unit *u; + int r; + + assert(m); + assert(bus); + + /* Let's make sure we have enough credential bits so that we can make security and selinux decisions */ + r = sd_bus_negotiate_creds(bus, 1, + SD_BUS_CREDS_PID|SD_BUS_CREDS_UID| + SD_BUS_CREDS_EUID|SD_BUS_CREDS_EFFECTIVE_CAPS| + SD_BUS_CREDS_SELINUX_CONTEXT); + if (r < 0) + log_warning_errno(r, "Failed to enable credential passing, ignoring: %m"); + + r = bus_setup_api_vtables(m, bus); + if (r < 0) + return r; + + HASHMAP_FOREACH_KEY(u, name, m->watch_bus, i) { + r = unit_install_bus_match(u, bus, name); + if (r < 0) + log_error_errno(r, "Failed to subscribe to NameOwnerChanged signal for '%s': %m", name); + } + + r = sd_bus_add_match( + bus, + NULL, + "type='signal'," + "sender='org.freedesktop.DBus'," + "path='/org/freedesktop/DBus'," + "interface='org.freedesktop.systemd1.Activator'," + "member='ActivationRequest'", + signal_activation_request, m); + if (r < 0) + log_warning_errno(r, "Failed to subscribe to activation signal: %m"); + + /* Allow replacing of our name, to ease implementation of + * reexecution, where we keep the old connection open until + * after the new connection is set up and the name installed + * to allow clients to synchronously wait for reexecution to + * finish */ + r = sd_bus_request_name(bus,"org.freedesktop.systemd1", SD_BUS_NAME_REPLACE_EXISTING|SD_BUS_NAME_ALLOW_REPLACEMENT); + if (r < 0) + return log_error_errno(r, "Failed to register name: %m"); + + r = manager_sync_bus_names(m, bus); + if (r < 0) + return r; + + log_debug("Successfully connected to API bus."); + return 0; +} + +static int bus_init_api(Manager *m) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + int r; + + if (m->api_bus) + return 0; + + /* The API and system bus is the same if we are running in system mode */ + if (MANAGER_IS_SYSTEM(m) && m->system_bus) + bus = sd_bus_ref(m->system_bus); + else { + if (MANAGER_IS_SYSTEM(m)) + r = sd_bus_open_system(&bus); + else + r = sd_bus_open_user(&bus); + + if (r < 0) { + log_debug("Failed to connect to API bus, retrying later..."); + return 0; + } + + r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) { + log_error_errno(r, "Failed to attach API bus to event loop: %m"); + return 0; + } + + r = bus_setup_disconnected_match(m, bus); + if (r < 0) + return 0; + } + + r = bus_setup_api(m, bus); + if (r < 0) { + log_error_errno(r, "Failed to set up API bus: %m"); + return 0; + } + + m->api_bus = bus; + bus = NULL; + + return 0; +} + +static int bus_setup_system(Manager *m, sd_bus *bus) { + int r; + + assert(m); + assert(bus); + + /* if we are a user instance we get the Released message via the system bus */ + if (MANAGER_IS_USER(m)) { + r = sd_bus_add_match( + bus, + NULL, + "type='signal'," + "interface='org.freedesktop.systemd1.Agent'," + "member='Released'," + "path='/org/freedesktop/systemd1/agent'", + signal_agent_released, m); + if (r < 0) + log_warning_errno(r, "Failed to register Released match on system bus: %m"); + } + + log_debug("Successfully connected to system bus."); + return 0; +} + +static int bus_init_system(Manager *m) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + int r; + + if (m->system_bus) + return 0; + + /* The API and system bus is the same if we are running in system mode */ + if (MANAGER_IS_SYSTEM(m) && m->api_bus) { + m->system_bus = sd_bus_ref(m->api_bus); + return 0; + } + + r = sd_bus_open_system(&bus); + if (r < 0) { + log_debug("Failed to connect to system bus, retrying later..."); + return 0; + } + + r = bus_setup_disconnected_match(m, bus); + if (r < 0) + return 0; + + r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) { + log_error_errno(r, "Failed to attach system bus to event loop: %m"); + return 0; + } + + r = bus_setup_system(m, bus); + if (r < 0) { + log_error_errno(r, "Failed to set up system bus: %m"); + return 0; + } + + m->system_bus = bus; + bus = NULL; + + return 0; +} + +static int bus_init_private(Manager *m) { + _cleanup_close_ int fd = -1; + union sockaddr_union sa = { + .un.sun_family = AF_UNIX + }; + sd_event_source *s; + socklen_t salen; + int r; + + assert(m); + + if (m->private_listen_fd >= 0) + return 0; + + /* We don't need the private socket if we have kdbus */ + if (m->kdbus_fd >= 0) + return 0; + + if (MANAGER_IS_SYSTEM(m)) { + + /* We want the private bus only when running as init */ + if (getpid() != 1) + return 0; + + strcpy(sa.un.sun_path, "/run/systemd/private"); + salen = SOCKADDR_UN_LEN(sa.un); + } else { + size_t left = sizeof(sa.un.sun_path); + char *p = sa.un.sun_path; + const char *e; + + e = secure_getenv("XDG_RUNTIME_DIR"); + if (!e) { + log_error("Failed to determine XDG_RUNTIME_DIR"); + return -EHOSTDOWN; + } + + left = strpcpy(&p, left, e); + left = strpcpy(&p, left, "/systemd/private"); + + salen = sizeof(sa.un) - left; + } + + (void) mkdir_parents_label(sa.un.sun_path, 0755); + (void) unlink(sa.un.sun_path); + + fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) + return log_error_errno(errno, "Failed to allocate private socket: %m"); + + r = bind(fd, &sa.sa, salen); + if (r < 0) + return log_error_errno(errno, "Failed to bind private socket: %m"); + + r = listen(fd, SOMAXCONN); + if (r < 0) + return log_error_errno(errno, "Failed to make private socket listening: %m"); + + r = sd_event_add_io(m->event, &s, fd, EPOLLIN, bus_on_connection, m); + if (r < 0) + return log_error_errno(r, "Failed to allocate event source: %m"); + + (void) sd_event_source_set_description(s, "bus-connection"); + + m->private_listen_fd = fd; + m->private_listen_event_source = s; + fd = -1; + + log_debug("Successfully created private D-Bus server."); + + return 0; +} + +int bus_init(Manager *m, bool try_bus_connect) { + int r; + + if (try_bus_connect) { + r = bus_init_system(m); + if (r < 0) + return r; + + r = bus_init_api(m); + if (r < 0) + return r; + } + + r = bus_init_private(m); + if (r < 0) + return r; + + return 0; +} + +static void destroy_bus(Manager *m, sd_bus **bus) { + Iterator i; + Job *j; + + assert(m); + assert(bus); + + if (!*bus) + return; + + /* Get rid of tracked clients on this bus */ + if (m->subscribed && sd_bus_track_get_bus(m->subscribed) == *bus) + m->subscribed = sd_bus_track_unref(m->subscribed); + + HASHMAP_FOREACH(j, m->jobs, i) + if (j->clients && sd_bus_track_get_bus(j->clients) == *bus) + j->clients = sd_bus_track_unref(j->clients); + + /* Get rid of queued message on this bus */ + if (m->queued_message && sd_bus_message_get_bus(m->queued_message) == *bus) + m->queued_message = sd_bus_message_unref(m->queued_message); + + /* Possibly flush unwritten data, but only if we are + * unprivileged, since we don't want to sync here */ + if (!MANAGER_IS_SYSTEM(m)) + sd_bus_flush(*bus); + + /* And destroy the object */ + sd_bus_close(*bus); + *bus = sd_bus_unref(*bus); +} + +void bus_done(Manager *m) { + sd_bus *b; + + assert(m); + + if (m->api_bus) + destroy_bus(m, &m->api_bus); + if (m->system_bus) + destroy_bus(m, &m->system_bus); + while ((b = set_steal_first(m->private_buses))) + destroy_bus(m, &b); + + m->private_buses = set_free(m->private_buses); + + m->subscribed = sd_bus_track_unref(m->subscribed); + m->deserialized_subscribed = strv_free(m->deserialized_subscribed); + + if (m->private_listen_event_source) + m->private_listen_event_source = sd_event_source_unref(m->private_listen_event_source); + + m->private_listen_fd = safe_close(m->private_listen_fd); + + bus_verify_polkit_async_registry_free(m->polkit_registry); +} + +int bus_fdset_add_all(Manager *m, FDSet *fds) { + Iterator i; + sd_bus *b; + int fd; + + assert(m); + assert(fds); + + /* When we are about to reexecute we add all D-Bus fds to the + * set to pass over to the newly executed systemd. They won't + * be used there however, except thatt they are closed at the + * very end of deserialization, those making it possible for + * clients to synchronously wait for systemd to reexec by + * simply waiting for disconnection */ + + if (m->api_bus) { + fd = sd_bus_get_fd(m->api_bus); + if (fd >= 0) { + fd = fdset_put_dup(fds, fd); + if (fd < 0) + return fd; + } + } + + SET_FOREACH(b, m->private_buses, i) { + fd = sd_bus_get_fd(b); + if (fd >= 0) { + fd = fdset_put_dup(fds, fd); + if (fd < 0) + return fd; + } + } + + /* We don't offer any APIs on the system bus (well, unless it + * is the same as the API bus) hence we don't bother with it + * here */ + + return 0; +} + +int bus_foreach_bus( + Manager *m, + sd_bus_track *subscribed2, + int (*send_message)(sd_bus *bus, void *userdata), + void *userdata) { + + Iterator i; + sd_bus *b; + int r, ret = 0; + + /* Send to all direct buses, unconditionally */ + SET_FOREACH(b, m->private_buses, i) { + r = send_message(b, userdata); + if (r < 0) + ret = r; + } + + /* Send to API bus, but only if somebody is subscribed */ + if (sd_bus_track_count(m->subscribed) > 0 || + sd_bus_track_count(subscribed2) > 0) { + r = send_message(m->api_bus, userdata); + if (r < 0) + ret = r; + } + + return ret; +} + +void bus_track_serialize(sd_bus_track *t, FILE *f) { + const char *n; + + assert(f); + + for (n = sd_bus_track_first(t); n; n = sd_bus_track_next(t)) + fprintf(f, "subscribed=%s\n", n); +} + +int bus_track_deserialize_item(char ***l, const char *line) { + const char *e; + int r; + + assert(l); + assert(line); + + e = startswith(line, "subscribed="); + if (!e) + return 0; + + r = strv_extend(l, e); + if (r < 0) + return r; + + return 1; +} + +int bus_track_coldplug(Manager *m, sd_bus_track **t, char ***l) { + int r = 0; + + assert(m); + assert(t); + assert(l); + + if (!strv_isempty(*l) && m->api_bus) { + char **i; + + if (!*t) { + r = sd_bus_track_new(m->api_bus, t, NULL, NULL); + if (r < 0) + return r; + } + + r = 0; + STRV_FOREACH(i, *l) { + int k; + + k = sd_bus_track_add_name(*t, *i); + if (k < 0) + r = k; + } + } + + *l = strv_free(*l); + + return r; +} + +int bus_verify_manage_units_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { + return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.manage-units", NULL, false, UID_INVALID, &m->polkit_registry, error); +} + +int bus_verify_manage_unit_files_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { + return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.manage-unit-files", NULL, false, UID_INVALID, &m->polkit_registry, error); +} + +int bus_verify_reload_daemon_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { + return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.reload-daemon", NULL, false, UID_INVALID, &m->polkit_registry, error); +} + +int bus_verify_set_environment_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { + return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.set-environment", NULL, false, UID_INVALID, &m->polkit_registry, error); +} diff --git a/src/libcore/dbus.h b/src/libcore/dbus.h new file mode 100644 index 0000000000..6baaffbd75 --- /dev/null +++ b/src/libcore/dbus.h @@ -0,0 +1,44 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "manager.h" + +int bus_send_queued_message(Manager *m); + +int bus_init(Manager *m, bool try_bus_connect); +void bus_done(Manager *m); + +int bus_fdset_add_all(Manager *m, FDSet *fds); + +void bus_track_serialize(sd_bus_track *t, FILE *f); +int bus_track_deserialize_item(char ***l, const char *line); +int bus_track_coldplug(Manager *m, sd_bus_track **t, char ***l); + +int manager_sync_bus_names(Manager *m, sd_bus *bus); + +int bus_foreach_bus(Manager *m, sd_bus_track *subscribed2, int (*send_message)(sd_bus *bus, void *userdata), void *userdata); + +int bus_verify_manage_units_async(Manager *m, sd_bus_message *call, sd_bus_error *error); +int bus_verify_manage_unit_files_async(Manager *m, sd_bus_message *call, sd_bus_error *error); +int bus_verify_reload_daemon_async(Manager *m, sd_bus_message *call, sd_bus_error *error); +int bus_verify_set_environment_async(Manager *m, sd_bus_message *call, sd_bus_error *error); + +int bus_forward_agent_released(Manager *m, const char *path); diff --git a/src/libcore/device.c b/src/libcore/device.c new file mode 100644 index 0000000000..16e56efcc3 --- /dev/null +++ b/src/libcore/device.c @@ -0,0 +1,876 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 + +#include "libudev.h" + +#include "alloc-util.h" +#include "dbus-device.h" +#include "device.h" +#include "log.h" +#include "parse-util.h" +#include "path-util.h" +#include "stat-util.h" +#include "string-util.h" +#include "swap.h" +#include "udev-util.h" +#include "unit-name.h" +#include "unit.h" + +static const UnitActiveState state_translation_table[_DEVICE_STATE_MAX] = { + [DEVICE_DEAD] = UNIT_INACTIVE, + [DEVICE_TENTATIVE] = UNIT_ACTIVATING, + [DEVICE_PLUGGED] = UNIT_ACTIVE, +}; + +static int device_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); + +static void device_unset_sysfs(Device *d) { + Hashmap *devices; + Device *first; + + assert(d); + + if (!d->sysfs) + return; + + /* Remove this unit from the chain of devices which share the + * same sysfs path. */ + devices = UNIT(d)->manager->devices_by_sysfs; + first = hashmap_get(devices, d->sysfs); + LIST_REMOVE(same_sysfs, first, d); + + if (first) + hashmap_remove_and_replace(devices, d->sysfs, first->sysfs, first); + else + hashmap_remove(devices, d->sysfs); + + d->sysfs = mfree(d->sysfs); +} + +static int device_set_sysfs(Device *d, const char *sysfs) { + Device *first; + char *copy; + int r; + + assert(d); + + if (streq_ptr(d->sysfs, sysfs)) + return 0; + + r = hashmap_ensure_allocated(&UNIT(d)->manager->devices_by_sysfs, &string_hash_ops); + if (r < 0) + return r; + + copy = strdup(sysfs); + if (!copy) + return -ENOMEM; + + device_unset_sysfs(d); + + first = hashmap_get(UNIT(d)->manager->devices_by_sysfs, sysfs); + LIST_PREPEND(same_sysfs, first, d); + + r = hashmap_replace(UNIT(d)->manager->devices_by_sysfs, copy, first); + if (r < 0) { + LIST_REMOVE(same_sysfs, first, d); + free(copy); + return r; + } + + d->sysfs = copy; + + return 0; +} + +static void device_init(Unit *u) { + Device *d = DEVICE(u); + + assert(d); + assert(UNIT(d)->load_state == UNIT_STUB); + + /* In contrast to all other unit types we timeout jobs waiting + * for devices by default. This is because they otherwise wait + * indefinitely for plugged in devices, something which cannot + * happen for the other units since their operations time out + * anyway. */ + u->job_timeout = u->manager->default_timeout_start_usec; + + u->ignore_on_isolate = true; +} + +static void device_done(Unit *u) { + Device *d = DEVICE(u); + + assert(d); + + device_unset_sysfs(d); +} + +static void device_set_state(Device *d, DeviceState state) { + DeviceState old_state; + assert(d); + + old_state = d->state; + d->state = state; + + if (state != old_state) + log_unit_debug(UNIT(d), "Changed %s -> %s", device_state_to_string(old_state), device_state_to_string(state)); + + unit_notify(UNIT(d), state_translation_table[old_state], state_translation_table[state], true); +} + +static int device_coldplug(Unit *u) { + Device *d = DEVICE(u); + + assert(d); + assert(d->state == DEVICE_DEAD); + + if (d->found & DEVICE_FOUND_UDEV) + /* If udev says the device is around, it's around */ + device_set_state(d, DEVICE_PLUGGED); + else if (d->found != DEVICE_NOT_FOUND && d->deserialized_state != DEVICE_PLUGGED) + /* If a device is found in /proc/self/mountinfo or + * /proc/swaps, and was not yet announced via udev, + * it's "tentatively" around. */ + device_set_state(d, DEVICE_TENTATIVE); + + return 0; +} + +static int device_serialize(Unit *u, FILE *f, FDSet *fds) { + Device *d = DEVICE(u); + + assert(u); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", device_state_to_string(d->state)); + + return 0; +} + +static int device_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Device *d = DEVICE(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + DeviceState state; + + state = device_state_from_string(value); + if (state < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + d->deserialized_state = state; + } else + log_unit_debug(u, "Unknown serialization key: %s", key); + + return 0; +} + +static void device_dump(Unit *u, FILE *f, const char *prefix) { + Device *d = DEVICE(u); + + assert(d); + + fprintf(f, + "%sDevice State: %s\n" + "%sSysfs Path: %s\n", + prefix, device_state_to_string(d->state), + prefix, strna(d->sysfs)); +} + +_pure_ static UnitActiveState device_active_state(Unit *u) { + assert(u); + + return state_translation_table[DEVICE(u)->state]; +} + +_pure_ static const char *device_sub_state_to_string(Unit *u) { + assert(u); + + return device_state_to_string(DEVICE(u)->state); +} + +static int device_update_description(Unit *u, struct udev_device *dev, const char *path) { + const char *model; + int r; + + assert(u); + assert(dev); + assert(path); + + model = udev_device_get_property_value(dev, "ID_MODEL_FROM_DATABASE"); + if (!model) + model = udev_device_get_property_value(dev, "ID_MODEL"); + + if (model) { + const char *label; + + /* Try to concatenate the device model string with a label, if there is one */ + label = udev_device_get_property_value(dev, "ID_FS_LABEL"); + if (!label) + label = udev_device_get_property_value(dev, "ID_PART_ENTRY_NAME"); + if (!label) + label = udev_device_get_property_value(dev, "ID_PART_ENTRY_NUMBER"); + + if (label) { + _cleanup_free_ char *j; + + j = strjoin(model, " ", label, NULL); + if (j) + r = unit_set_description(u, j); + else + r = -ENOMEM; + } else + r = unit_set_description(u, model); + } else + r = unit_set_description(u, path); + + if (r < 0) + log_unit_error_errno(u, r, "Failed to set device description: %m"); + + return r; +} + +static int device_add_udev_wants(Unit *u, struct udev_device *dev) { + const char *wants; + const char *word, *state; + size_t l; + int r; + const char *property; + + assert(u); + assert(dev); + + property = MANAGER_IS_USER(u->manager) ? "SYSTEMD_USER_WANTS" : "SYSTEMD_WANTS"; + wants = udev_device_get_property_value(dev, property); + if (!wants) + return 0; + + FOREACH_WORD_QUOTED(word, l, wants, state) { + _cleanup_free_ char *n = NULL; + char e[l+1]; + + memcpy(e, word, l); + e[l] = 0; + + r = unit_name_mangle(e, UNIT_NAME_NOGLOB, &n); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to mangle unit name: %m"); + + r = unit_add_dependency_by_name(u, UNIT_WANTS, n, NULL, true); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to add wants dependency: %m"); + } + if (!isempty(state)) + log_unit_warning(u, "Property %s on %s has trailing garbage, ignoring.", property, strna(udev_device_get_syspath(dev))); + + return 0; +} + +static int device_setup_unit(Manager *m, struct udev_device *dev, const char *path, bool main) { + _cleanup_free_ char *e = NULL; + const char *sysfs = NULL; + Unit *u = NULL; + bool delete; + int r; + + assert(m); + assert(path); + + if (dev) { + sysfs = udev_device_get_syspath(dev); + if (!sysfs) + return 0; + } + + r = unit_name_from_path(path, ".device", &e); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name from device path: %m"); + + u = manager_get_unit(m, e); + + /* The device unit can still be present even if the device was + * unplugged: a mount unit can reference it hence preventing + * the GC to have garbaged it. That's desired since the device + * unit may have a dependency on the mount unit which was + * added during the loading of the later. */ + if (dev && u && DEVICE(u)->state == DEVICE_PLUGGED) { + /* This unit is in plugged state: we're sure it's + * attached to a device. */ + if (!path_equal(DEVICE(u)->sysfs, sysfs)) { + log_unit_debug(u, "Dev %s appeared twice with different sysfs paths %s and %s", + e, DEVICE(u)->sysfs, sysfs); + return -EEXIST; + } + } + + if (!u) { + delete = true; + + u = unit_new(m, sizeof(Device)); + if (!u) + return log_oom(); + + r = unit_add_name(u, e); + if (r < 0) + goto fail; + + unit_add_to_load_queue(u); + } else + delete = false; + + /* If this was created via some dependency and has not + * actually been seen yet ->sysfs will not be + * initialized. Hence initialize it if necessary. */ + if (sysfs) { + r = device_set_sysfs(DEVICE(u), sysfs); + if (r < 0) + goto fail; + + (void) device_update_description(u, dev, path); + + /* The additional systemd udev properties we only interpret + * for the main object */ + if (main) + (void) device_add_udev_wants(u, dev); + } + + + /* Note that this won't dispatch the load queue, the caller + * has to do that if needed and appropriate */ + + unit_add_to_dbus_queue(u); + return 0; + +fail: + log_unit_warning_errno(u, r, "Failed to set up device unit: %m"); + + if (delete) + unit_free(u); + + return r; +} + +static int device_process_new(Manager *m, struct udev_device *dev) { + const char *sysfs, *dn, *alias; + struct udev_list_entry *item = NULL, *first = NULL; + int r; + + assert(m); + + sysfs = udev_device_get_syspath(dev); + if (!sysfs) + return 0; + + /* Add the main unit named after the sysfs path */ + r = device_setup_unit(m, dev, sysfs, true); + if (r < 0) + return r; + + /* Add an additional unit for the device node */ + dn = udev_device_get_devnode(dev); + if (dn) + (void) device_setup_unit(m, dev, dn, false); + + /* Add additional units for all symlinks */ + first = udev_device_get_devlinks_list_entry(dev); + udev_list_entry_foreach(item, first) { + const char *p; + struct stat st; + + /* Don't bother with the /dev/block links */ + p = udev_list_entry_get_name(item); + + if (path_startswith(p, "/dev/block/") || + path_startswith(p, "/dev/char/")) + continue; + + /* Verify that the symlink in the FS actually belongs + * to this device. This is useful to deal with + * conflicting devices, e.g. when two disks want the + * same /dev/disk/by-label/xxx link because they have + * the same label. We want to make sure that the same + * device that won the symlink wins in systemd, so we + * check the device node major/minor */ + if (stat(p, &st) >= 0) + if ((!S_ISBLK(st.st_mode) && !S_ISCHR(st.st_mode)) || + st.st_rdev != udev_device_get_devnum(dev)) + continue; + + (void) device_setup_unit(m, dev, p, false); + } + + /* Add additional units for all explicitly configured + * aliases */ + alias = udev_device_get_property_value(dev, "SYSTEMD_ALIAS"); + if (alias) { + const char *word, *state; + size_t l; + + FOREACH_WORD_QUOTED(word, l, alias, state) { + char e[l+1]; + + memcpy(e, word, l); + e[l] = 0; + + if (path_is_absolute(e)) + (void) device_setup_unit(m, dev, e, false); + else + log_warning("SYSTEMD_ALIAS for %s is not an absolute path, ignoring: %s", sysfs, e); + } + if (!isempty(state)) + log_warning("SYSTEMD_ALIAS for %s has trailing garbage, ignoring.", sysfs); + } + + return 0; +} + +static void device_update_found_one(Device *d, bool add, DeviceFound found, bool now) { + DeviceFound n, previous; + + assert(d); + + n = add ? (d->found | found) : (d->found & ~found); + if (n == d->found) + return; + + previous = d->found; + d->found = n; + + if (!now) + return; + + if (d->found & DEVICE_FOUND_UDEV) + /* When the device is known to udev we consider it + * plugged. */ + device_set_state(d, DEVICE_PLUGGED); + else if (d->found != DEVICE_NOT_FOUND && (previous & DEVICE_FOUND_UDEV) == 0) + /* If the device has not been seen by udev yet, but is + * now referenced by the kernel, then we assume the + * kernel knows it now, and udev might soon too. */ + device_set_state(d, DEVICE_TENTATIVE); + else + /* If nobody sees the device, or if the device was + * previously seen by udev and now is only referenced + * from the kernel, then we consider the device is + * gone, the kernel just hasn't noticed it yet. */ + device_set_state(d, DEVICE_DEAD); +} + +static int device_update_found_by_sysfs(Manager *m, const char *sysfs, bool add, DeviceFound found, bool now) { + Device *d, *l; + + assert(m); + assert(sysfs); + + if (found == DEVICE_NOT_FOUND) + return 0; + + l = hashmap_get(m->devices_by_sysfs, sysfs); + LIST_FOREACH(same_sysfs, d, l) + device_update_found_one(d, add, found, now); + + return 0; +} + +static int device_update_found_by_name(Manager *m, const char *path, bool add, DeviceFound found, bool now) { + _cleanup_free_ char *e = NULL; + Unit *u; + int r; + + assert(m); + assert(path); + + if (found == DEVICE_NOT_FOUND) + return 0; + + r = unit_name_from_path(path, ".device", &e); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name from device path: %m"); + + u = manager_get_unit(m, e); + if (!u) + return 0; + + device_update_found_one(DEVICE(u), add, found, now); + return 0; +} + +static bool device_is_ready(struct udev_device *dev) { + const char *ready; + + assert(dev); + + ready = udev_device_get_property_value(dev, "SYSTEMD_READY"); + if (!ready) + return true; + + return parse_boolean(ready) != 0; +} + +static Unit *device_following(Unit *u) { + Device *d = DEVICE(u); + Device *other, *first = NULL; + + assert(d); + + if (startswith(u->id, "sys-")) + return NULL; + + /* Make everybody follow the unit that's named after the sysfs path */ + for (other = d->same_sysfs_next; other; other = other->same_sysfs_next) + if (startswith(UNIT(other)->id, "sys-")) + return UNIT(other); + + for (other = d->same_sysfs_prev; other; other = other->same_sysfs_prev) { + if (startswith(UNIT(other)->id, "sys-")) + return UNIT(other); + + first = other; + } + + return UNIT(first); +} + +static int device_following_set(Unit *u, Set **_set) { + Device *d = DEVICE(u), *other; + Set *set; + int r; + + assert(d); + assert(_set); + + if (LIST_JUST_US(same_sysfs, d)) { + *_set = NULL; + return 0; + } + + set = set_new(NULL); + if (!set) + return -ENOMEM; + + LIST_FOREACH_AFTER(same_sysfs, other, d) { + r = set_put(set, other); + if (r < 0) + goto fail; + } + + LIST_FOREACH_BEFORE(same_sysfs, other, d) { + r = set_put(set, other); + if (r < 0) + goto fail; + } + + *_set = set; + return 1; + +fail: + set_free(set); + return r; +} + +static void device_shutdown(Manager *m) { + assert(m); + + m->udev_event_source = sd_event_source_unref(m->udev_event_source); + + if (m->udev_monitor) { + udev_monitor_unref(m->udev_monitor); + m->udev_monitor = NULL; + } + + m->devices_by_sysfs = hashmap_free(m->devices_by_sysfs); +} + +static void device_enumerate(Manager *m) { + _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + int r; + + assert(m); + + if (!m->udev_monitor) { + m->udev_monitor = udev_monitor_new_from_netlink(m->udev, "udev"); + if (!m->udev_monitor) { + log_oom(); + goto fail; + } + + /* This will fail if we are unprivileged, but that + * should not matter much, as user instances won't run + * during boot. */ + (void) udev_monitor_set_receive_buffer_size(m->udev_monitor, 128*1024*1024); + + r = udev_monitor_filter_add_match_tag(m->udev_monitor, "systemd"); + if (r < 0) { + log_error_errno(r, "Failed to add udev tag match: %m"); + goto fail; + } + + r = udev_monitor_enable_receiving(m->udev_monitor); + if (r < 0) { + log_error_errno(r, "Failed to enable udev event reception: %m"); + goto fail; + } + + r = sd_event_add_io(m->event, &m->udev_event_source, udev_monitor_get_fd(m->udev_monitor), EPOLLIN, device_dispatch_io, m); + if (r < 0) { + log_error_errno(r, "Failed to watch udev file descriptor: %m"); + goto fail; + } + + (void) sd_event_source_set_description(m->udev_event_source, "device"); + } + + e = udev_enumerate_new(m->udev); + if (!e) { + log_oom(); + goto fail; + } + + r = udev_enumerate_add_match_tag(e, "systemd"); + if (r < 0) { + log_error_errno(r, "Failed to create udev tag enumeration: %m"); + goto fail; + } + + r = udev_enumerate_add_match_is_initialized(e); + if (r < 0) { + log_error_errno(r, "Failed to install initialization match into enumeration: %m"); + goto fail; + } + + r = udev_enumerate_scan_devices(e); + if (r < 0) { + log_error_errno(r, "Failed to enumerate devices: %m"); + goto fail; + } + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) { + _cleanup_udev_device_unref_ struct udev_device *dev = NULL; + const char *sysfs; + + sysfs = udev_list_entry_get_name(item); + + dev = udev_device_new_from_syspath(m->udev, sysfs); + if (!dev) { + log_oom(); + continue; + } + + if (!device_is_ready(dev)) + continue; + + (void) device_process_new(m, dev); + + device_update_found_by_sysfs(m, sysfs, true, DEVICE_FOUND_UDEV, false); + } + + return; + +fail: + device_shutdown(m); +} + +static int device_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + _cleanup_udev_device_unref_ struct udev_device *dev = NULL; + Manager *m = userdata; + const char *action, *sysfs; + int r; + + assert(m); + + if (revents != EPOLLIN) { + static RATELIMIT_DEFINE(limit, 10*USEC_PER_SEC, 5); + + if (!ratelimit_test(&limit)) + log_error_errno(errno, "Failed to get udev event: %m"); + if (!(revents & EPOLLIN)) + return 0; + } + + /* + * libudev might filter-out devices which pass the bloom + * filter, so getting NULL here is not necessarily an error. + */ + dev = udev_monitor_receive_device(m->udev_monitor); + if (!dev) + return 0; + + sysfs = udev_device_get_syspath(dev); + if (!sysfs) { + log_error("Failed to get udev sys path."); + return 0; + } + + action = udev_device_get_action(dev); + if (!action) { + log_error("Failed to get udev action string."); + return 0; + } + + if (streq(action, "remove")) { + r = swap_process_device_remove(m, dev); + if (r < 0) + log_error_errno(r, "Failed to process swap device remove event: %m"); + + /* If we get notified that a device was removed by + * udev, then it's completely gone, hence unset all + * found bits */ + device_update_found_by_sysfs(m, sysfs, false, DEVICE_FOUND_UDEV|DEVICE_FOUND_MOUNT|DEVICE_FOUND_SWAP, true); + + } else if (device_is_ready(dev)) { + + (void) device_process_new(m, dev); + + r = swap_process_device_new(m, dev); + if (r < 0) + log_error_errno(r, "Failed to process swap device new event: %m"); + + manager_dispatch_load_queue(m); + + /* The device is found now, set the udev found bit */ + device_update_found_by_sysfs(m, sysfs, true, DEVICE_FOUND_UDEV, true); + + } else { + /* The device is nominally around, but not ready for + * us. Hence unset the udev bit, but leave the rest + * around. */ + + device_update_found_by_sysfs(m, sysfs, false, DEVICE_FOUND_UDEV, true); + } + + return 0; +} + +static bool device_supported(void) { + static int read_only = -1; + + /* If /sys is read-only we don't support device units, and any + * attempts to start one should fail immediately. */ + + if (read_only < 0) + read_only = path_is_read_only_fs("/sys"); + + return read_only <= 0; +} + +int device_found_node(Manager *m, const char *node, bool add, DeviceFound found, bool now) { + _cleanup_udev_device_unref_ struct udev_device *dev = NULL; + struct stat st; + + assert(m); + assert(node); + + if (!device_supported()) + return 0; + + /* This is called whenever we find a device referenced in + * /proc/swaps or /proc/self/mounts. Such a device might be + * mounted/enabled at a time where udev has not finished + * probing it yet, and we thus haven't learned about it + * yet. In this case we will set the device unit to + * "tentative" state. */ + + if (add) { + if (!path_startswith(node, "/dev")) + return 0; + + /* We make an extra check here, if the device node + * actually exists. If it's missing, then this is an + * indication that device was unplugged but is still + * referenced in /proc/swaps or + * /proc/self/mountinfo. Note that this check doesn't + * really cover all cases where a device might be gone + * away, since drives that can have a medium inserted + * will still have a device node even when the medium + * is not there... */ + + if (stat(node, &st) >= 0) { + if (!S_ISBLK(st.st_mode) && !S_ISCHR(st.st_mode)) + return 0; + + dev = udev_device_new_from_devnum(m->udev, S_ISBLK(st.st_mode) ? 'b' : 'c', st.st_rdev); + if (!dev && errno != ENOENT) + return log_error_errno(errno, "Failed to get udev device from devnum %u:%u: %m", major(st.st_rdev), minor(st.st_rdev)); + + } else if (errno != ENOENT) + return log_error_errno(errno, "Failed to stat device node file %s: %m", node); + + /* If the device is known in the kernel and newly + * appeared, then we'll create a device unit for it, + * under the name referenced in /proc/swaps or + * /proc/self/mountinfo. */ + + (void) device_setup_unit(m, dev, node, false); + } + + /* Update the device unit's state, should it exist */ + return device_update_found_by_name(m, node, add, found, now); +} + +const UnitVTable device_vtable = { + .object_size = sizeof(Device), + .sections = + "Unit\0" + "Device\0" + "Install\0", + + .init = device_init, + .done = device_done, + .load = unit_load_fragment_and_dropin_optional, + + .coldplug = device_coldplug, + + .serialize = device_serialize, + .deserialize_item = device_deserialize_item, + + .dump = device_dump, + + .active_state = device_active_state, + .sub_state_to_string = device_sub_state_to_string, + + .bus_vtable = bus_device_vtable, + + .following = device_following, + .following_set = device_following_set, + + .enumerate = device_enumerate, + .shutdown = device_shutdown, + .supported = device_supported, + + .status_message_formats = { + .starting_stopping = { + [0] = "Expecting device %s...", + }, + .finished_start_job = { + [JOB_DONE] = "Found device %s.", + [JOB_TIMEOUT] = "Timed out waiting for device %s.", + }, + }, +}; diff --git a/src/libcore/device.h b/src/libcore/device.h new file mode 100644 index 0000000000..184a1a349b --- /dev/null +++ b/src/libcore/device.h @@ -0,0 +1,47 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct Device Device; + +typedef enum DeviceFound { + DEVICE_NOT_FOUND = 0, + DEVICE_FOUND_UDEV = 1, + DEVICE_FOUND_MOUNT = 2, + DEVICE_FOUND_SWAP = 4, +} DeviceFound; + +struct Device { + Unit meta; + + char *sysfs; + DeviceFound found; + + /* In order to be able to distinguish dependencies on + different device nodes we might end up creating multiple + devices for the same sysfs path. We chain them up here. */ + LIST_FIELDS(struct Device, same_sysfs); + + DeviceState state, deserialized_state; +}; + +extern const UnitVTable device_vtable; + +int device_found_node(Manager *m, const char *node, bool add, DeviceFound found, bool now); diff --git a/src/libcore/execute.c b/src/libcore/execute.c new file mode 100644 index 0000000000..b58fb80be2 --- /dev/null +++ b/src/libcore/execute.c @@ -0,0 +1,3092 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_PAM +#include +#endif + +#ifdef HAVE_SELINUX +#include +#endif + +#ifdef HAVE_SECCOMP +#include +#endif + +#ifdef HAVE_APPARMOR +#include +#endif + +#include + +#include "af-list.h" +#include "alloc-util.h" +#ifdef HAVE_APPARMOR +#include "apparmor-util.h" +#endif +#include "async.h" +#include "barrier.h" +#include "cap-list.h" +#include "capability-util.h" +#include "def.h" +#include "env-util.h" +#include "errno-list.h" +#include "execute.h" +#include "exit-status.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "glob-util.h" +#include "io-util.h" +#include "ioprio.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "namespace.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "rlimit-util.h" +#include "rm-rf.h" +#ifdef HAVE_SECCOMP +#include "seccomp-util.h" +#endif +#include "securebits.h" +#include "selinux-util.h" +#include "signal-util.h" +#include "smack-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "syslog-util.h" +#include "terminal-util.h" +#include "unit.h" +#include "user-util.h" +#include "util.h" +#include "utmp-wtmp.h" + +#define IDLE_TIMEOUT_USEC (5*USEC_PER_SEC) +#define IDLE_TIMEOUT2_USEC (1*USEC_PER_SEC) + +/* This assumes there is a 'tty' group */ +#define TTY_MODE 0620 + +#define SNDBUF_SIZE (8*1024*1024) + +static int shift_fds(int fds[], unsigned n_fds) { + int start, restart_from; + + if (n_fds <= 0) + return 0; + + /* Modifies the fds array! (sorts it) */ + + assert(fds); + + start = 0; + for (;;) { + int i; + + restart_from = -1; + + for (i = start; i < (int) n_fds; i++) { + int nfd; + + /* Already at right index? */ + if (fds[i] == i+3) + continue; + + nfd = fcntl(fds[i], F_DUPFD, i + 3); + if (nfd < 0) + return -errno; + + safe_close(fds[i]); + fds[i] = nfd; + + /* Hmm, the fd we wanted isn't free? Then + * let's remember that and try again from here */ + if (nfd != i+3 && restart_from < 0) + restart_from = i; + } + + if (restart_from < 0) + break; + + start = restart_from; + } + + return 0; +} + +static int flags_fds(const int fds[], unsigned n_fds, bool nonblock) { + unsigned i; + int r; + + if (n_fds <= 0) + return 0; + + assert(fds); + + /* Drops/Sets O_NONBLOCK and FD_CLOEXEC from the file flags */ + + for (i = 0; i < n_fds; i++) { + + r = fd_nonblock(fds[i], nonblock); + if (r < 0) + return r; + + /* We unconditionally drop FD_CLOEXEC from the fds, + * since after all we want to pass these fds to our + * children */ + + r = fd_cloexec(fds[i], false); + if (r < 0) + return r; + } + + return 0; +} + +static const char *exec_context_tty_path(const ExecContext *context) { + assert(context); + + if (context->stdio_as_fds) + return NULL; + + if (context->tty_path) + return context->tty_path; + + return "/dev/console"; +} + +static void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) { + const char *path; + + assert(context); + + path = exec_context_tty_path(context); + + if (context->tty_vhangup) { + if (p && p->stdin_fd >= 0) + (void) terminal_vhangup_fd(p->stdin_fd); + else if (path) + (void) terminal_vhangup(path); + } + + if (context->tty_reset) { + if (p && p->stdin_fd >= 0) + (void) reset_terminal_fd(p->stdin_fd, true); + else if (path) + (void) reset_terminal(path); + } + + if (context->tty_vt_disallocate && path) + (void) vt_disallocate(path); +} + +static bool is_terminal_output(ExecOutput o) { + return + o == EXEC_OUTPUT_TTY || + o == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || + o == EXEC_OUTPUT_KMSG_AND_CONSOLE || + o == EXEC_OUTPUT_JOURNAL_AND_CONSOLE; +} + +static int open_null_as(int flags, int nfd) { + int fd, r; + + assert(nfd >= 0); + + fd = open("/dev/null", flags|O_NOCTTY); + if (fd < 0) + return -errno; + + if (fd != nfd) { + r = dup2(fd, nfd) < 0 ? -errno : nfd; + safe_close(fd); + } else + r = nfd; + + return r; +} + +static int connect_journal_socket(int fd, uid_t uid, gid_t gid) { + union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/journal/stdout", + }; + uid_t olduid = UID_INVALID; + gid_t oldgid = GID_INVALID; + int r; + + if (gid != GID_INVALID) { + oldgid = getgid(); + + r = setegid(gid); + if (r < 0) + return -errno; + } + + if (uid != UID_INVALID) { + olduid = getuid(); + + r = seteuid(uid); + if (r < 0) { + r = -errno; + goto restore_gid; + } + } + + r = connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); + if (r < 0) + r = -errno; + + /* If we fail to restore the uid or gid, things will likely + fail later on. This should only happen if an LSM interferes. */ + + if (uid != UID_INVALID) + (void) seteuid(olduid); + + restore_gid: + if (gid != GID_INVALID) + (void) setegid(oldgid); + + return r; +} + +static int connect_logger_as(const ExecContext *context, ExecOutput output, const char *ident, const char *unit_id, int nfd, uid_t uid, gid_t gid) { + int fd, r; + + assert(context); + assert(output < _EXEC_OUTPUT_MAX); + assert(ident); + assert(nfd >= 0); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + return -errno; + + r = connect_journal_socket(fd, uid, gid); + if (r < 0) + return r; + + if (shutdown(fd, SHUT_RD) < 0) { + safe_close(fd); + return -errno; + } + + fd_inc_sndbuf(fd, SNDBUF_SIZE); + + dprintf(fd, + "%s\n" + "%s\n" + "%i\n" + "%i\n" + "%i\n" + "%i\n" + "%i\n", + context->syslog_identifier ? context->syslog_identifier : ident, + unit_id, + context->syslog_priority, + !!context->syslog_level_prefix, + output == EXEC_OUTPUT_SYSLOG || output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE, + output == EXEC_OUTPUT_KMSG || output == EXEC_OUTPUT_KMSG_AND_CONSOLE, + is_terminal_output(output)); + + if (fd != nfd) { + r = dup2(fd, nfd) < 0 ? -errno : nfd; + safe_close(fd); + } else + r = nfd; + + return r; +} +static int open_terminal_as(const char *path, mode_t mode, int nfd) { + int fd, r; + + assert(path); + assert(nfd >= 0); + + fd = open_terminal(path, mode | O_NOCTTY); + if (fd < 0) + return fd; + + if (fd != nfd) { + r = dup2(fd, nfd) < 0 ? -errno : nfd; + safe_close(fd); + } else + r = nfd; + + return r; +} + +static bool is_terminal_input(ExecInput i) { + return + i == EXEC_INPUT_TTY || + i == EXEC_INPUT_TTY_FORCE || + i == EXEC_INPUT_TTY_FAIL; +} + +static int fixup_input(ExecInput std_input, int socket_fd, bool apply_tty_stdin) { + + if (is_terminal_input(std_input) && !apply_tty_stdin) + return EXEC_INPUT_NULL; + + if (std_input == EXEC_INPUT_SOCKET && socket_fd < 0) + return EXEC_INPUT_NULL; + + return std_input; +} + +static int fixup_output(ExecOutput std_output, int socket_fd) { + + if (std_output == EXEC_OUTPUT_SOCKET && socket_fd < 0) + return EXEC_OUTPUT_INHERIT; + + return std_output; +} + +static int setup_input( + const ExecContext *context, + const ExecParameters *params, + int socket_fd) { + + ExecInput i; + + assert(context); + assert(params); + + if (params->stdin_fd >= 0) { + if (dup2(params->stdin_fd, STDIN_FILENO) < 0) + return -errno; + + /* Try to make this the controlling tty, if it is a tty, and reset it */ + (void) ioctl(STDIN_FILENO, TIOCSCTTY, context->std_input == EXEC_INPUT_TTY_FORCE); + (void) reset_terminal_fd(STDIN_FILENO, true); + + return STDIN_FILENO; + } + + i = fixup_input(context->std_input, socket_fd, params->apply_tty_stdin); + + switch (i) { + + case EXEC_INPUT_NULL: + return open_null_as(O_RDONLY, STDIN_FILENO); + + case EXEC_INPUT_TTY: + case EXEC_INPUT_TTY_FORCE: + case EXEC_INPUT_TTY_FAIL: { + int fd, r; + + fd = acquire_terminal(exec_context_tty_path(context), + i == EXEC_INPUT_TTY_FAIL, + i == EXEC_INPUT_TTY_FORCE, + false, + USEC_INFINITY); + if (fd < 0) + return fd; + + if (fd != STDIN_FILENO) { + r = dup2(fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO; + safe_close(fd); + } else + r = STDIN_FILENO; + + return r; + } + + case EXEC_INPUT_SOCKET: + return dup2(socket_fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO; + + default: + assert_not_reached("Unknown input type"); + } +} + +static int setup_output( + Unit *unit, + const ExecContext *context, + const ExecParameters *params, + int fileno, + int socket_fd, + const char *ident, + uid_t uid, gid_t gid) { + + ExecOutput o; + ExecInput i; + int r; + + assert(unit); + assert(context); + assert(params); + assert(ident); + + if (fileno == STDOUT_FILENO && params->stdout_fd >= 0) { + + if (dup2(params->stdout_fd, STDOUT_FILENO) < 0) + return -errno; + + return STDOUT_FILENO; + } + + if (fileno == STDERR_FILENO && params->stderr_fd >= 0) { + if (dup2(params->stderr_fd, STDERR_FILENO) < 0) + return -errno; + + return STDERR_FILENO; + } + + i = fixup_input(context->std_input, socket_fd, params->apply_tty_stdin); + o = fixup_output(context->std_output, socket_fd); + + if (fileno == STDERR_FILENO) { + ExecOutput e; + e = fixup_output(context->std_error, socket_fd); + + /* This expects the input and output are already set up */ + + /* Don't change the stderr file descriptor if we inherit all + * the way and are not on a tty */ + if (e == EXEC_OUTPUT_INHERIT && + o == EXEC_OUTPUT_INHERIT && + i == EXEC_INPUT_NULL && + !is_terminal_input(context->std_input) && + getppid () != 1) + return fileno; + + /* Duplicate from stdout if possible */ + if (e == o || e == EXEC_OUTPUT_INHERIT) + return dup2(STDOUT_FILENO, fileno) < 0 ? -errno : fileno; + + o = e; + + } else if (o == EXEC_OUTPUT_INHERIT) { + /* If input got downgraded, inherit the original value */ + if (i == EXEC_INPUT_NULL && is_terminal_input(context->std_input)) + return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno); + + /* If the input is connected to anything that's not a /dev/null, inherit that... */ + if (i != EXEC_INPUT_NULL) + return dup2(STDIN_FILENO, fileno) < 0 ? -errno : fileno; + + /* If we are not started from PID 1 we just inherit STDOUT from our parent process. */ + if (getppid() != 1) + return fileno; + + /* We need to open /dev/null here anew, to get the right access mode. */ + return open_null_as(O_WRONLY, fileno); + } + + switch (o) { + + case EXEC_OUTPUT_NULL: + return open_null_as(O_WRONLY, fileno); + + case EXEC_OUTPUT_TTY: + if (is_terminal_input(i)) + return dup2(STDIN_FILENO, fileno) < 0 ? -errno : fileno; + + /* We don't reset the terminal if this is just about output */ + return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno); + + case EXEC_OUTPUT_SYSLOG: + case EXEC_OUTPUT_SYSLOG_AND_CONSOLE: + case EXEC_OUTPUT_KMSG: + case EXEC_OUTPUT_KMSG_AND_CONSOLE: + case EXEC_OUTPUT_JOURNAL: + case EXEC_OUTPUT_JOURNAL_AND_CONSOLE: + r = connect_logger_as(context, o, ident, unit->id, fileno, uid, gid); + if (r < 0) { + log_unit_error_errno(unit, r, "Failed to connect %s to the journal socket, ignoring: %m", fileno == STDOUT_FILENO ? "stdout" : "stderr"); + r = open_null_as(O_WRONLY, fileno); + } + return r; + + case EXEC_OUTPUT_SOCKET: + assert(socket_fd >= 0); + return dup2(socket_fd, fileno) < 0 ? -errno : fileno; + + default: + assert_not_reached("Unknown error type"); + } +} + +static int chown_terminal(int fd, uid_t uid) { + struct stat st; + + assert(fd >= 0); + + /* This might fail. What matters are the results. */ + (void) fchown(fd, uid, -1); + (void) fchmod(fd, TTY_MODE); + + if (fstat(fd, &st) < 0) + return -errno; + + if (st.st_uid != uid || (st.st_mode & 0777) != TTY_MODE) + return -EPERM; + + return 0; +} + +static int setup_confirm_stdio(int *_saved_stdin, int *_saved_stdout) { + _cleanup_close_ int fd = -1, saved_stdin = -1, saved_stdout = -1; + int r; + + assert(_saved_stdin); + assert(_saved_stdout); + + saved_stdin = fcntl(STDIN_FILENO, F_DUPFD, 3); + if (saved_stdin < 0) + return -errno; + + saved_stdout = fcntl(STDOUT_FILENO, F_DUPFD, 3); + if (saved_stdout < 0) + return -errno; + + fd = acquire_terminal( + "/dev/console", + false, + false, + false, + DEFAULT_CONFIRM_USEC); + if (fd < 0) + return fd; + + r = chown_terminal(fd, getuid()); + if (r < 0) + return r; + + r = reset_terminal_fd(fd, true); + if (r < 0) + return r; + + if (dup2(fd, STDIN_FILENO) < 0) + return -errno; + + if (dup2(fd, STDOUT_FILENO) < 0) + return -errno; + + if (fd >= 2) + safe_close(fd); + fd = -1; + + *_saved_stdin = saved_stdin; + *_saved_stdout = saved_stdout; + + saved_stdin = saved_stdout = -1; + + return 0; +} + +_printf_(1, 2) static int write_confirm_message(const char *format, ...) { + _cleanup_close_ int fd = -1; + va_list ap; + + assert(format); + + fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + va_start(ap, format); + vdprintf(fd, format, ap); + va_end(ap); + + return 0; +} + +static int restore_confirm_stdio(int *saved_stdin, int *saved_stdout) { + int r = 0; + + assert(saved_stdin); + assert(saved_stdout); + + release_terminal(); + + if (*saved_stdin >= 0) + if (dup2(*saved_stdin, STDIN_FILENO) < 0) + r = -errno; + + if (*saved_stdout >= 0) + if (dup2(*saved_stdout, STDOUT_FILENO) < 0) + r = -errno; + + *saved_stdin = safe_close(*saved_stdin); + *saved_stdout = safe_close(*saved_stdout); + + return r; +} + +static int ask_for_confirmation(char *response, char **argv) { + int saved_stdout = -1, saved_stdin = -1, r; + _cleanup_free_ char *line = NULL; + + r = setup_confirm_stdio(&saved_stdin, &saved_stdout); + if (r < 0) + return r; + + line = exec_command_line(argv); + if (!line) + return -ENOMEM; + + r = ask_char(response, "yns", "Execute %s? [Yes, No, Skip] ", line); + + restore_confirm_stdio(&saved_stdin, &saved_stdout); + + return r; +} + +static int enforce_groups(const ExecContext *context, const char *username, gid_t gid) { + bool keep_groups = false; + int r; + + assert(context); + + /* Lookup and set GID and supplementary group list. Here too + * we avoid NSS lookups for gid=0. */ + + if (context->group || username) { + /* First step, initialize groups from /etc/groups */ + if (username && gid != 0) { + if (initgroups(username, gid) < 0) + return -errno; + + keep_groups = true; + } + + /* Second step, set our gids */ + if (setresgid(gid, gid, gid) < 0) + return -errno; + } + + if (context->supplementary_groups) { + int ngroups_max, k; + gid_t *gids; + char **i; + + /* Final step, initialize any manually set supplementary groups */ + assert_se((ngroups_max = (int) sysconf(_SC_NGROUPS_MAX)) > 0); + + if (!(gids = new(gid_t, ngroups_max))) + return -ENOMEM; + + if (keep_groups) { + k = getgroups(ngroups_max, gids); + if (k < 0) { + free(gids); + return -errno; + } + } else + k = 0; + + STRV_FOREACH(i, context->supplementary_groups) { + const char *g; + + if (k >= ngroups_max) { + free(gids); + return -E2BIG; + } + + g = *i; + r = get_group_creds(&g, gids+k); + if (r < 0) { + free(gids); + return r; + } + + k++; + } + + if (setgroups(k, gids) < 0) { + free(gids); + return -errno; + } + + free(gids); + } + + return 0; +} + +static int enforce_user(const ExecContext *context, uid_t uid) { + assert(context); + + /* Sets (but doesn't look up) the uid and make sure we keep the + * capabilities while doing so. */ + + if (context->capability_ambient_set != 0) { + + /* First step: If we need to keep capabilities but + * drop privileges we need to make sure we keep our + * caps, while we drop privileges. */ + if (uid != 0) { + int sb = context->secure_bits | 1<= 0); + + parent_pid = getpid(); + + pam_pid = fork(); + if (pam_pid < 0) { + r = -errno; + goto fail; + } + + if (pam_pid == 0) { + int sig, ret = EXIT_PAM; + + /* The child's job is to reset the PAM session on + * termination */ + barrier_set_role(&barrier, BARRIER_CHILD); + + /* This string must fit in 10 chars (i.e. the length + * of "/sbin/init"), to look pretty in /bin/ps */ + rename_process("(sd-pam)"); + + /* Make sure we don't keep open the passed fds in this + child. We assume that otherwise only those fds are + open here that have been opened by PAM. */ + close_many(fds, n_fds); + + /* Drop privileges - we don't need any to pam_close_session + * and this will make PR_SET_PDEATHSIG work in most cases. + * If this fails, ignore the error - but expect sd-pam threads + * to fail to exit normally */ + if (setresuid(uid, uid, uid) < 0) + log_error_errno(r, "Error: Failed to setresuid() in sd-pam: %m"); + + (void) ignore_signals(SIGPIPE, -1); + + /* Wait until our parent died. This will only work if + * the above setresuid() succeeds, otherwise the kernel + * will not allow unprivileged parents kill their privileged + * children this way. We rely on the control groups kill logic + * to do the rest for us. */ + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + goto child_finish; + + /* Tell the parent that our setup is done. This is especially + * important regarding dropping privileges. Otherwise, unit + * setup might race against our setresuid(2) call. */ + barrier_place(&barrier); + + /* Check if our parent process might already have + * died? */ + if (getppid() == parent_pid) { + sigset_t ss; + + assert_se(sigemptyset(&ss) >= 0); + assert_se(sigaddset(&ss, SIGTERM) >= 0); + + for (;;) { + if (sigwait(&ss, &sig) < 0) { + if (errno == EINTR) + continue; + + goto child_finish; + } + + assert(sig == SIGTERM); + break; + } + } + + /* If our parent died we'll end the session */ + if (getppid() != parent_pid) { + pam_code = pam_close_session(handle, flags); + if (pam_code != PAM_SUCCESS) + goto child_finish; + } + + ret = 0; + + child_finish: + pam_end(handle, pam_code | flags); + _exit(ret); + } + + barrier_set_role(&barrier, BARRIER_PARENT); + + /* If the child was forked off successfully it will do all the + * cleanups, so forget about the handle here. */ + handle = NULL; + + /* Unblock SIGTERM again in the parent */ + assert_se(sigprocmask(SIG_SETMASK, &old_ss, NULL) >= 0); + + /* We close the log explicitly here, since the PAM modules + * might have opened it, but we don't want this fd around. */ + closelog(); + + /* Synchronously wait for the child to initialize. We don't care for + * errors as we cannot recover. However, warn loudly if it happens. */ + if (!barrier_place_and_sync(&barrier)) + log_error("PAM initialization failed"); + + *pam_env = e; + e = NULL; + + return 0; + +fail: + if (pam_code != PAM_SUCCESS) { + log_error("PAM failed: %s", pam_strerror(handle, pam_code)); + r = -EPERM; /* PAM errors do not map to errno */ + } else + log_error_errno(r, "PAM failed: %m"); + + if (handle) { + if (close_session) + pam_code = pam_close_session(handle, flags); + + pam_end(handle, pam_code | flags); + } + + strv_free(e); + closelog(); + + return r; +} +#endif + +static void rename_process_from_path(const char *path) { + char process_name[11]; + const char *p; + size_t l; + + /* This resulting string must fit in 10 chars (i.e. the length + * of "/sbin/init") to look pretty in /bin/ps */ + + p = basename(path); + if (isempty(p)) { + rename_process("(...)"); + return; + } + + l = strlen(p); + if (l > 8) { + /* The end of the process name is usually more + * interesting, since the first bit might just be + * "systemd-" */ + p = p + l - 8; + l = 8; + } + + process_name[0] = '('; + memcpy(process_name+1, p, l); + process_name[1+l] = ')'; + process_name[1+l+1] = 0; + + rename_process(process_name); +} + +#ifdef HAVE_SECCOMP + +static int apply_seccomp(const ExecContext *c) { + uint32_t negative_action, action; + scmp_filter_ctx *seccomp; + Iterator i; + void *id; + int r; + + assert(c); + + negative_action = c->syscall_errno == 0 ? SCMP_ACT_KILL : SCMP_ACT_ERRNO(c->syscall_errno); + + seccomp = seccomp_init(c->syscall_whitelist ? negative_action : SCMP_ACT_ALLOW); + if (!seccomp) + return -ENOMEM; + + if (c->syscall_archs) { + + SET_FOREACH(id, c->syscall_archs, i) { + r = seccomp_arch_add(seccomp, PTR_TO_UINT32(id) - 1); + if (r == -EEXIST) + continue; + if (r < 0) + goto finish; + } + + } else { + r = seccomp_add_secondary_archs(seccomp); + if (r < 0) + goto finish; + } + + action = c->syscall_whitelist ? SCMP_ACT_ALLOW : negative_action; + SET_FOREACH(id, c->syscall_filter, i) { + r = seccomp_rule_add(seccomp, action, PTR_TO_INT(id) - 1, 0); + if (r < 0) + goto finish; + } + + r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); + if (r < 0) + goto finish; + + r = seccomp_load(seccomp); + +finish: + seccomp_release(seccomp); + return r; +} + +static int apply_address_families(const ExecContext *c) { + scmp_filter_ctx *seccomp; + Iterator i; + int r; + + assert(c); + + seccomp = seccomp_init(SCMP_ACT_ALLOW); + if (!seccomp) + return -ENOMEM; + + r = seccomp_add_secondary_archs(seccomp); + if (r < 0) + goto finish; + + if (c->address_families_whitelist) { + int af, first = 0, last = 0; + void *afp; + + /* If this is a whitelist, we first block the address + * families that are out of range and then everything + * that is not in the set. First, we find the lowest + * and highest address family in the set. */ + + SET_FOREACH(afp, c->address_families, i) { + af = PTR_TO_INT(afp); + + if (af <= 0 || af >= af_max()) + continue; + + if (first == 0 || af < first) + first = af; + + if (last == 0 || af > last) + last = af; + } + + assert((first == 0) == (last == 0)); + + if (first == 0) { + + /* No entries in the valid range, block everything */ + r = seccomp_rule_add( + seccomp, + SCMP_ACT_ERRNO(EPROTONOSUPPORT), + SCMP_SYS(socket), + 0); + if (r < 0) + goto finish; + + } else { + + /* Block everything below the first entry */ + r = seccomp_rule_add( + seccomp, + SCMP_ACT_ERRNO(EPROTONOSUPPORT), + SCMP_SYS(socket), + 1, + SCMP_A0(SCMP_CMP_LT, first)); + if (r < 0) + goto finish; + + /* Block everything above the last entry */ + r = seccomp_rule_add( + seccomp, + SCMP_ACT_ERRNO(EPROTONOSUPPORT), + SCMP_SYS(socket), + 1, + SCMP_A0(SCMP_CMP_GT, last)); + if (r < 0) + goto finish; + + /* Block everything between the first and last + * entry */ + for (af = 1; af < af_max(); af++) { + + if (set_contains(c->address_families, INT_TO_PTR(af))) + continue; + + r = seccomp_rule_add( + seccomp, + SCMP_ACT_ERRNO(EPROTONOSUPPORT), + SCMP_SYS(socket), + 1, + SCMP_A0(SCMP_CMP_EQ, af)); + if (r < 0) + goto finish; + } + } + + } else { + void *af; + + /* If this is a blacklist, then generate one rule for + * each address family that are then combined in OR + * checks. */ + + SET_FOREACH(af, c->address_families, i) { + + r = seccomp_rule_add( + seccomp, + SCMP_ACT_ERRNO(EPROTONOSUPPORT), + SCMP_SYS(socket), + 1, + SCMP_A0(SCMP_CMP_EQ, PTR_TO_INT(af))); + if (r < 0) + goto finish; + } + } + + r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); + if (r < 0) + goto finish; + + r = seccomp_load(seccomp); + +finish: + seccomp_release(seccomp); + return r; +} + +#endif + +static void do_idle_pipe_dance(int idle_pipe[4]) { + assert(idle_pipe); + + + idle_pipe[1] = safe_close(idle_pipe[1]); + idle_pipe[2] = safe_close(idle_pipe[2]); + + if (idle_pipe[0] >= 0) { + int r; + + r = fd_wait_for_event(idle_pipe[0], POLLHUP, IDLE_TIMEOUT_USEC); + + if (idle_pipe[3] >= 0 && r == 0 /* timeout */) { + ssize_t n; + + /* Signal systemd that we are bored and want to continue. */ + n = write(idle_pipe[3], "x", 1); + if (n > 0) + /* Wait for systemd to react to the signal above. */ + fd_wait_for_event(idle_pipe[0], POLLHUP, IDLE_TIMEOUT2_USEC); + } + + idle_pipe[0] = safe_close(idle_pipe[0]); + + } + + idle_pipe[3] = safe_close(idle_pipe[3]); +} + +static int build_environment( + const ExecContext *c, + const ExecParameters *p, + unsigned n_fds, + const char *home, + const char *username, + const char *shell, + char ***ret) { + + _cleanup_strv_free_ char **our_env = NULL; + unsigned n_env = 0; + char *x; + + assert(c); + assert(ret); + + our_env = new0(char*, 11); + if (!our_env) + return -ENOMEM; + + if (n_fds > 0) { + _cleanup_free_ char *joined = NULL; + + if (asprintf(&x, "LISTEN_PID="PID_FMT, getpid()) < 0) + return -ENOMEM; + our_env[n_env++] = x; + + if (asprintf(&x, "LISTEN_FDS=%u", n_fds) < 0) + return -ENOMEM; + our_env[n_env++] = x; + + joined = strv_join(p->fd_names, ":"); + if (!joined) + return -ENOMEM; + + x = strjoin("LISTEN_FDNAMES=", joined, NULL); + if (!x) + return -ENOMEM; + our_env[n_env++] = x; + } + + if (p->watchdog_usec > 0) { + if (asprintf(&x, "WATCHDOG_PID="PID_FMT, getpid()) < 0) + return -ENOMEM; + our_env[n_env++] = x; + + if (asprintf(&x, "WATCHDOG_USEC="USEC_FMT, p->watchdog_usec) < 0) + return -ENOMEM; + our_env[n_env++] = x; + } + + if (home) { + x = strappend("HOME=", home); + if (!x) + return -ENOMEM; + our_env[n_env++] = x; + } + + if (username) { + x = strappend("LOGNAME=", username); + if (!x) + return -ENOMEM; + our_env[n_env++] = x; + + x = strappend("USER=", username); + if (!x) + return -ENOMEM; + our_env[n_env++] = x; + } + + if (shell) { + x = strappend("SHELL=", shell); + if (!x) + return -ENOMEM; + our_env[n_env++] = x; + } + + if (is_terminal_input(c->std_input) || + c->std_output == EXEC_OUTPUT_TTY || + c->std_error == EXEC_OUTPUT_TTY || + c->tty_path) { + + x = strdup(default_term_for_tty(exec_context_tty_path(c))); + if (!x) + return -ENOMEM; + our_env[n_env++] = x; + } + + our_env[n_env++] = NULL; + assert(n_env <= 11); + + *ret = our_env; + our_env = NULL; + + return 0; +} + +static int build_pass_environment(const ExecContext *c, char ***ret) { + _cleanup_strv_free_ char **pass_env = NULL; + size_t n_env = 0, n_bufsize = 0; + char **i; + + STRV_FOREACH(i, c->pass_environment) { + _cleanup_free_ char *x = NULL; + char *v; + + v = getenv(*i); + if (!v) + continue; + x = strjoin(*i, "=", v, NULL); + if (!x) + return -ENOMEM; + if (!GREEDY_REALLOC(pass_env, n_bufsize, n_env + 2)) + return -ENOMEM; + pass_env[n_env++] = x; + pass_env[n_env] = NULL; + x = NULL; + } + + *ret = pass_env; + pass_env = NULL; + + return 0; +} + +static bool exec_needs_mount_namespace( + const ExecContext *context, + const ExecParameters *params, + ExecRuntime *runtime) { + + assert(context); + assert(params); + + if (!strv_isempty(context->read_write_dirs) || + !strv_isempty(context->read_only_dirs) || + !strv_isempty(context->inaccessible_dirs)) + return true; + + if (context->mount_flags != 0) + return true; + + if (context->private_tmp && runtime && (runtime->tmp_dir || runtime->var_tmp_dir)) + return true; + + if (context->private_devices || + context->protect_system != PROTECT_SYSTEM_NO || + context->protect_home != PROTECT_HOME_NO) + return true; + + return false; +} + +static int close_remaining_fds( + const ExecParameters *params, + ExecRuntime *runtime, + int socket_fd, + int *fds, unsigned n_fds) { + + unsigned n_dont_close = 0; + int dont_close[n_fds + 7]; + + assert(params); + + if (params->stdin_fd >= 0) + dont_close[n_dont_close++] = params->stdin_fd; + if (params->stdout_fd >= 0) + dont_close[n_dont_close++] = params->stdout_fd; + if (params->stderr_fd >= 0) + dont_close[n_dont_close++] = params->stderr_fd; + + if (socket_fd >= 0) + dont_close[n_dont_close++] = socket_fd; + if (n_fds > 0) { + memcpy(dont_close + n_dont_close, fds, sizeof(int) * n_fds); + n_dont_close += n_fds; + } + + if (runtime) { + if (runtime->netns_storage_socket[0] >= 0) + dont_close[n_dont_close++] = runtime->netns_storage_socket[0]; + if (runtime->netns_storage_socket[1] >= 0) + dont_close[n_dont_close++] = runtime->netns_storage_socket[1]; + } + + return close_all_fds(dont_close, n_dont_close); +} + +static int exec_child( + Unit *unit, + ExecCommand *command, + const ExecContext *context, + const ExecParameters *params, + ExecRuntime *runtime, + char **argv, + int socket_fd, + int *fds, unsigned n_fds, + char **files_env, + int *exit_status) { + + _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **pam_env = NULL, **final_env = NULL, **final_argv = NULL; + _cleanup_free_ char *mac_selinux_context_net = NULL; + const char *username = NULL, *home = NULL, *shell = NULL, *wd; + uid_t uid = UID_INVALID; + gid_t gid = GID_INVALID; + int i, r; + bool needs_mount_namespace; + + assert(unit); + assert(command); + assert(context); + assert(params); + assert(exit_status); + + rename_process_from_path(command->path); + + /* We reset exactly these signals, since they are the + * only ones we set to SIG_IGN in the main daemon. All + * others we leave untouched because we set them to + * SIG_DFL or a valid handler initially, both of which + * will be demoted to SIG_DFL. */ + (void) default_signals(SIGNALS_CRASH_HANDLER, + SIGNALS_IGNORE, -1); + + if (context->ignore_sigpipe) + (void) ignore_signals(SIGPIPE, -1); + + r = reset_signal_mask(); + if (r < 0) { + *exit_status = EXIT_SIGNAL_MASK; + return r; + } + + if (params->idle_pipe) + do_idle_pipe_dance(params->idle_pipe); + + /* Close sockets very early to make sure we don't + * block init reexecution because it cannot bind its + * sockets */ + + log_forget_fds(); + + r = close_remaining_fds(params, runtime, socket_fd, fds, n_fds); + if (r < 0) { + *exit_status = EXIT_FDS; + return r; + } + + if (!context->same_pgrp) + if (setsid() < 0) { + *exit_status = EXIT_SETSID; + return -errno; + } + + exec_context_tty_reset(context, params); + + if (params->confirm_spawn) { + char response; + + r = ask_for_confirmation(&response, argv); + if (r == -ETIMEDOUT) + write_confirm_message("Confirmation question timed out, assuming positive response.\n"); + else if (r < 0) + write_confirm_message("Couldn't ask confirmation question, assuming positive response: %s\n", strerror(-r)); + else if (response == 's') { + write_confirm_message("Skipping execution.\n"); + *exit_status = EXIT_CONFIRM; + return -ECANCELED; + } else if (response == 'n') { + write_confirm_message("Failing execution.\n"); + *exit_status = 0; + return 0; + } + } + + if (context->user) { + username = context->user; + r = get_user_creds(&username, &uid, &gid, &home, &shell); + if (r < 0) { + *exit_status = EXIT_USER; + return r; + } + } + + if (context->group) { + const char *g = context->group; + + r = get_group_creds(&g, &gid); + if (r < 0) { + *exit_status = EXIT_GROUP; + return r; + } + } + + + /* If a socket is connected to STDIN/STDOUT/STDERR, we + * must sure to drop O_NONBLOCK */ + if (socket_fd >= 0) + (void) fd_nonblock(socket_fd, false); + + r = setup_input(context, params, socket_fd); + if (r < 0) { + *exit_status = EXIT_STDIN; + return r; + } + + r = setup_output(unit, context, params, STDOUT_FILENO, socket_fd, basename(command->path), uid, gid); + if (r < 0) { + *exit_status = EXIT_STDOUT; + return r; + } + + r = setup_output(unit, context, params, STDERR_FILENO, socket_fd, basename(command->path), uid, gid); + if (r < 0) { + *exit_status = EXIT_STDERR; + return r; + } + + if (params->cgroup_path) { + r = cg_attach_everywhere(params->cgroup_supported, params->cgroup_path, 0, NULL, NULL); + if (r < 0) { + *exit_status = EXIT_CGROUP; + return r; + } + } + + if (context->oom_score_adjust_set) { + char t[DECIMAL_STR_MAX(context->oom_score_adjust)]; + + /* When we can't make this change due to EPERM, then + * let's silently skip over it. User namespaces + * prohibit write access to this file, and we + * shouldn't trip up over that. */ + + sprintf(t, "%i", context->oom_score_adjust); + r = write_string_file("/proc/self/oom_score_adj", t, 0); + if (r == -EPERM || r == -EACCES) { + log_open(); + log_unit_debug_errno(unit, r, "Failed to adjust OOM setting, assuming containerized execution, ignoring: %m"); + log_close(); + } else if (r < 0) { + *exit_status = EXIT_OOM_ADJUST; + return -errno; + } + } + + if (context->nice_set) + if (setpriority(PRIO_PROCESS, 0, context->nice) < 0) { + *exit_status = EXIT_NICE; + return -errno; + } + + if (context->cpu_sched_set) { + struct sched_param param = { + .sched_priority = context->cpu_sched_priority, + }; + + r = sched_setscheduler(0, + context->cpu_sched_policy | + (context->cpu_sched_reset_on_fork ? + SCHED_RESET_ON_FORK : 0), + ¶m); + if (r < 0) { + *exit_status = EXIT_SETSCHEDULER; + return -errno; + } + } + + if (context->cpuset) + if (sched_setaffinity(0, CPU_ALLOC_SIZE(context->cpuset_ncpus), context->cpuset) < 0) { + *exit_status = EXIT_CPUAFFINITY; + return -errno; + } + + if (context->ioprio_set) + if (ioprio_set(IOPRIO_WHO_PROCESS, 0, context->ioprio) < 0) { + *exit_status = EXIT_IOPRIO; + return -errno; + } + + if (context->timer_slack_nsec != NSEC_INFINITY) + if (prctl(PR_SET_TIMERSLACK, context->timer_slack_nsec) < 0) { + *exit_status = EXIT_TIMERSLACK; + return -errno; + } + + if (context->personality != PERSONALITY_INVALID) + if (personality(context->personality) < 0) { + *exit_status = EXIT_PERSONALITY; + return -errno; + } + + if (context->utmp_id) + utmp_put_init_process(context->utmp_id, getpid(), getsid(0), context->tty_path, + context->utmp_mode == EXEC_UTMP_INIT ? INIT_PROCESS : + context->utmp_mode == EXEC_UTMP_LOGIN ? LOGIN_PROCESS : + USER_PROCESS, + username ? "root" : context->user); + + if (context->user && is_terminal_input(context->std_input)) { + r = chown_terminal(STDIN_FILENO, uid); + if (r < 0) { + *exit_status = EXIT_STDIN; + return r; + } + } + + /* If delegation is enabled we'll pass ownership of the cgroup + * (but only in systemd's own controller hierarchy!) to the + * user of the new process. */ + if (params->cgroup_path && context->user && params->cgroup_delegate) { + r = cg_set_task_access(SYSTEMD_CGROUP_CONTROLLER, params->cgroup_path, 0644, uid, gid); + if (r < 0) { + *exit_status = EXIT_CGROUP; + return r; + } + + + r = cg_set_group_access(SYSTEMD_CGROUP_CONTROLLER, params->cgroup_path, 0755, uid, gid); + if (r < 0) { + *exit_status = EXIT_CGROUP; + return r; + } + } + + if (!strv_isempty(context->runtime_directory) && params->runtime_prefix) { + char **rt; + + STRV_FOREACH(rt, context->runtime_directory) { + _cleanup_free_ char *p; + + p = strjoin(params->runtime_prefix, "/", *rt, NULL); + if (!p) { + *exit_status = EXIT_RUNTIME_DIRECTORY; + return -ENOMEM; + } + + r = mkdir_p_label(p, context->runtime_directory_mode); + if (r < 0) { + *exit_status = EXIT_RUNTIME_DIRECTORY; + return r; + } + + r = chmod_and_chown(p, context->runtime_directory_mode, uid, gid); + if (r < 0) { + *exit_status = EXIT_RUNTIME_DIRECTORY; + return r; + } + } + } + + umask(context->umask); + + if (params->apply_permissions) { + r = enforce_groups(context, username, gid); + if (r < 0) { + *exit_status = EXIT_GROUP; + return r; + } +#ifdef HAVE_SMACK + if (context->smack_process_label) { + r = mac_smack_apply_pid(0, context->smack_process_label); + if (r < 0) { + *exit_status = EXIT_SMACK_PROCESS_LABEL; + return r; + } + } +#ifdef SMACK_DEFAULT_PROCESS_LABEL + else { + _cleanup_free_ char *exec_label = NULL; + + r = mac_smack_read(command->path, SMACK_ATTR_EXEC, &exec_label); + if (r < 0 && r != -ENODATA && r != -EOPNOTSUPP) { + *exit_status = EXIT_SMACK_PROCESS_LABEL; + return r; + } + + r = mac_smack_apply_pid(0, exec_label ? : SMACK_DEFAULT_PROCESS_LABEL); + if (r < 0) { + *exit_status = EXIT_SMACK_PROCESS_LABEL; + return r; + } + } +#endif +#endif +#ifdef HAVE_PAM + if (context->pam_name && username) { + r = setup_pam(context->pam_name, username, uid, context->tty_path, &pam_env, fds, n_fds); + if (r < 0) { + *exit_status = EXIT_PAM; + return r; + } + } +#endif + } + + if (context->private_network && runtime && runtime->netns_storage_socket[0] >= 0) { + r = setup_netns(runtime->netns_storage_socket); + if (r < 0) { + *exit_status = EXIT_NETWORK; + return r; + } + } + + needs_mount_namespace = exec_needs_mount_namespace(context, params, runtime); + + if (needs_mount_namespace) { + char *tmp = NULL, *var = NULL; + + /* The runtime struct only contains the parent + * of the private /tmp, which is + * non-accessible to world users. Inside of it + * there's a /tmp that is sticky, and that's + * the one we want to use here. */ + + if (context->private_tmp && runtime) { + if (runtime->tmp_dir) + tmp = strjoina(runtime->tmp_dir, "/tmp"); + if (runtime->var_tmp_dir) + var = strjoina(runtime->var_tmp_dir, "/tmp"); + } + + r = setup_namespace( + params->apply_chroot ? context->root_directory : NULL, + context->read_write_dirs, + context->read_only_dirs, + context->inaccessible_dirs, + tmp, + var, + context->private_devices, + context->protect_home, + context->protect_system, + context->mount_flags); + + /* If we couldn't set up the namespace this is + * probably due to a missing capability. In this case, + * silently proceeed. */ + if (r == -EPERM || r == -EACCES) { + log_open(); + log_unit_debug_errno(unit, r, "Failed to set up namespace, assuming containerized execution, ignoring: %m"); + log_close(); + } else if (r < 0) { + *exit_status = EXIT_NAMESPACE; + return r; + } + } + + if (context->working_directory_home) + wd = home; + else if (context->working_directory) + wd = context->working_directory; + else + wd = "/"; + + if (params->apply_chroot) { + if (!needs_mount_namespace && context->root_directory) + if (chroot(context->root_directory) < 0) { + *exit_status = EXIT_CHROOT; + return -errno; + } + + if (chdir(wd) < 0 && + !context->working_directory_missing_ok) { + *exit_status = EXIT_CHDIR; + return -errno; + } + } else { + const char *d; + + d = strjoina(strempty(context->root_directory), "/", strempty(wd)); + if (chdir(d) < 0 && + !context->working_directory_missing_ok) { + *exit_status = EXIT_CHDIR; + return -errno; + } + } + +#ifdef HAVE_SELINUX + if (params->apply_permissions && mac_selinux_use() && params->selinux_context_net && socket_fd >= 0) { + r = mac_selinux_get_child_mls_label(socket_fd, command->path, context->selinux_context, &mac_selinux_context_net); + if (r < 0) { + *exit_status = EXIT_SELINUX_CONTEXT; + return r; + } + } +#endif + + /* We repeat the fd closing here, to make sure that + * nothing is leaked from the PAM modules. Note that + * we are more aggressive this time since socket_fd + * and the netns fds we don't need anymore. The custom + * endpoint fd was needed to upload the policy and can + * now be closed as well. */ + r = close_all_fds(fds, n_fds); + if (r >= 0) + r = shift_fds(fds, n_fds); + if (r >= 0) + r = flags_fds(fds, n_fds, context->non_blocking); + if (r < 0) { + *exit_status = EXIT_FDS; + return r; + } + + if (params->apply_permissions) { + + bool use_address_families = context->address_families_whitelist || + !set_isempty(context->address_families); + bool use_syscall_filter = context->syscall_whitelist || + !set_isempty(context->syscall_filter) || + !set_isempty(context->syscall_archs); + int secure_bits = context->secure_bits; + + for (i = 0; i < _RLIMIT_MAX; i++) { + if (!context->rlimit[i]) + continue; + + if (setrlimit_closest(i, context->rlimit[i]) < 0) { + *exit_status = EXIT_LIMITS; + return -errno; + } + } + + if (!cap_test_all(context->capability_bounding_set)) { + r = capability_bounding_set_drop(context->capability_bounding_set, false); + if (r < 0) { + *exit_status = EXIT_CAPABILITIES; + return r; + } + } + + /* This is done before enforce_user, but ambient set + * does not survive over setresuid() if keep_caps is not set. */ + if (context->capability_ambient_set != 0) { + r = capability_ambient_set_apply(context->capability_ambient_set, true); + if (r < 0) { + *exit_status = EXIT_CAPABILITIES; + return r; + } + } + + if (context->user) { + r = enforce_user(context, uid); + if (r < 0) { + *exit_status = EXIT_USER; + return r; + } + if (context->capability_ambient_set != 0) { + + /* Fix the ambient capabilities after user change. */ + r = capability_ambient_set_apply(context->capability_ambient_set, false); + if (r < 0) { + *exit_status = EXIT_CAPABILITIES; + return r; + } + + /* If we were asked to change user and ambient capabilities + * were requested, we had to add keep-caps to the securebits + * so that we would maintain the inherited capability set + * through the setresuid(). Make sure that the bit is added + * also to the context secure_bits so that we don't try to + * drop the bit away next. */ + + secure_bits |= 1<no_new_privileges || + (!have_effective_cap(CAP_SYS_ADMIN) && (use_address_families || use_syscall_filter))) + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { + *exit_status = EXIT_NO_NEW_PRIVILEGES; + return -errno; + } + +#ifdef HAVE_SECCOMP + if (use_address_families) { + r = apply_address_families(context); + if (r < 0) { + *exit_status = EXIT_ADDRESS_FAMILIES; + return r; + } + } + + if (use_syscall_filter) { + r = apply_seccomp(context); + if (r < 0) { + *exit_status = EXIT_SECCOMP; + return r; + } + } +#endif + +#ifdef HAVE_SELINUX + if (mac_selinux_use()) { + char *exec_context = mac_selinux_context_net ?: context->selinux_context; + + if (exec_context) { + r = setexeccon(exec_context); + if (r < 0) { + *exit_status = EXIT_SELINUX_CONTEXT; + return r; + } + } + } +#endif + +#ifdef HAVE_APPARMOR + if (context->apparmor_profile && mac_apparmor_use()) { + r = aa_change_onexec(context->apparmor_profile); + if (r < 0 && !context->apparmor_profile_ignore) { + *exit_status = EXIT_APPARMOR_PROFILE; + return -errno; + } + } +#endif + } + + r = build_environment(context, params, n_fds, home, username, shell, &our_env); + if (r < 0) { + *exit_status = EXIT_MEMORY; + return r; + } + + r = build_pass_environment(context, &pass_env); + if (r < 0) { + *exit_status = EXIT_MEMORY; + return r; + } + + final_env = strv_env_merge(6, + params->environment, + our_env, + pass_env, + context->environment, + files_env, + pam_env, + NULL); + if (!final_env) { + *exit_status = EXIT_MEMORY; + return -ENOMEM; + } + + final_argv = replace_env_argv(argv, final_env); + if (!final_argv) { + *exit_status = EXIT_MEMORY; + return -ENOMEM; + } + + final_env = strv_env_clean(final_env); + + if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) { + _cleanup_free_ char *line; + + line = exec_command_line(final_argv); + if (line) { + log_open(); + log_struct(LOG_DEBUG, + LOG_UNIT_ID(unit), + "EXECUTABLE=%s", command->path, + LOG_UNIT_MESSAGE(unit, "Executing: %s", line), + NULL); + log_close(); + } + } + + execve(command->path, final_argv, final_env); + *exit_status = EXIT_EXEC; + return -errno; +} + +int exec_spawn(Unit *unit, + ExecCommand *command, + const ExecContext *context, + const ExecParameters *params, + ExecRuntime *runtime, + pid_t *ret) { + + _cleanup_strv_free_ char **files_env = NULL; + int *fds = NULL; unsigned n_fds = 0; + _cleanup_free_ char *line = NULL; + int socket_fd, r; + char **argv; + pid_t pid; + + assert(unit); + assert(command); + assert(context); + assert(ret); + assert(params); + assert(params->fds || params->n_fds <= 0); + + if (context->std_input == EXEC_INPUT_SOCKET || + context->std_output == EXEC_OUTPUT_SOCKET || + context->std_error == EXEC_OUTPUT_SOCKET) { + + if (params->n_fds != 1) { + log_unit_error(unit, "Got more than one socket."); + return -EINVAL; + } + + socket_fd = params->fds[0]; + } else { + socket_fd = -1; + fds = params->fds; + n_fds = params->n_fds; + } + + r = exec_context_load_environment(unit, context, &files_env); + if (r < 0) + return log_unit_error_errno(unit, r, "Failed to load environment files: %m"); + + argv = params->argv ?: command->argv; + line = exec_command_line(argv); + if (!line) + return log_oom(); + + log_struct(LOG_DEBUG, + LOG_UNIT_ID(unit), + LOG_UNIT_MESSAGE(unit, "About to execute: %s", line), + "EXECUTABLE=%s", command->path, + NULL); + pid = fork(); + if (pid < 0) + return log_unit_error_errno(unit, errno, "Failed to fork: %m"); + + if (pid == 0) { + int exit_status; + + r = exec_child(unit, + command, + context, + params, + runtime, + argv, + socket_fd, + fds, n_fds, + files_env, + &exit_status); + if (r < 0) { + log_open(); + log_struct_errno(LOG_ERR, r, + LOG_MESSAGE_ID(SD_MESSAGE_SPAWN_FAILED), + LOG_UNIT_ID(unit), + LOG_UNIT_MESSAGE(unit, "Failed at step %s spawning %s: %m", + exit_status_to_string(exit_status, EXIT_STATUS_SYSTEMD), + command->path), + "EXECUTABLE=%s", command->path, + NULL); + } + + _exit(exit_status); + } + + log_unit_debug(unit, "Forked %s as "PID_FMT, command->path, pid); + + /* We add the new process to the cgroup both in the child (so + * that we can be sure that no user code is ever executed + * outside of the cgroup) and in the parent (so that we can be + * sure that when we kill the cgroup the process will be + * killed too). */ + if (params->cgroup_path) + (void) cg_attach(SYSTEMD_CGROUP_CONTROLLER, params->cgroup_path, pid); + + exec_status_start(&command->exec_status, pid); + + *ret = pid; + return 0; +} + +void exec_context_init(ExecContext *c) { + assert(c); + + c->umask = 0022; + c->ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 0); + c->cpu_sched_policy = SCHED_OTHER; + c->syslog_priority = LOG_DAEMON|LOG_INFO; + c->syslog_level_prefix = true; + c->ignore_sigpipe = true; + c->timer_slack_nsec = NSEC_INFINITY; + c->personality = PERSONALITY_INVALID; + c->runtime_directory_mode = 0755; + c->capability_bounding_set = CAP_ALL; +} + +void exec_context_done(ExecContext *c) { + unsigned l; + + assert(c); + + c->environment = strv_free(c->environment); + c->environment_files = strv_free(c->environment_files); + c->pass_environment = strv_free(c->pass_environment); + + for (l = 0; l < ELEMENTSOF(c->rlimit); l++) + c->rlimit[l] = mfree(c->rlimit[l]); + + c->working_directory = mfree(c->working_directory); + c->root_directory = mfree(c->root_directory); + c->tty_path = mfree(c->tty_path); + c->syslog_identifier = mfree(c->syslog_identifier); + c->user = mfree(c->user); + c->group = mfree(c->group); + + c->supplementary_groups = strv_free(c->supplementary_groups); + + c->pam_name = mfree(c->pam_name); + + c->read_only_dirs = strv_free(c->read_only_dirs); + c->read_write_dirs = strv_free(c->read_write_dirs); + c->inaccessible_dirs = strv_free(c->inaccessible_dirs); + + if (c->cpuset) + CPU_FREE(c->cpuset); + + c->utmp_id = mfree(c->utmp_id); + c->selinux_context = mfree(c->selinux_context); + c->apparmor_profile = mfree(c->apparmor_profile); + + c->syscall_filter = set_free(c->syscall_filter); + c->syscall_archs = set_free(c->syscall_archs); + c->address_families = set_free(c->address_families); + + c->runtime_directory = strv_free(c->runtime_directory); +} + +int exec_context_destroy_runtime_directory(ExecContext *c, const char *runtime_prefix) { + char **i; + + assert(c); + + if (!runtime_prefix) + return 0; + + STRV_FOREACH(i, c->runtime_directory) { + _cleanup_free_ char *p; + + p = strjoin(runtime_prefix, "/", *i, NULL); + if (!p) + return -ENOMEM; + + /* We execute this synchronously, since we need to be + * sure this is gone when we start the service + * next. */ + (void) rm_rf(p, REMOVE_ROOT); + } + + return 0; +} + +void exec_command_done(ExecCommand *c) { + assert(c); + + c->path = mfree(c->path); + + c->argv = strv_free(c->argv); +} + +void exec_command_done_array(ExecCommand *c, unsigned n) { + unsigned i; + + for (i = 0; i < n; i++) + exec_command_done(c+i); +} + +ExecCommand* exec_command_free_list(ExecCommand *c) { + ExecCommand *i; + + while ((i = c)) { + LIST_REMOVE(command, c, i); + exec_command_done(i); + free(i); + } + + return NULL; +} + +void exec_command_free_array(ExecCommand **c, unsigned n) { + unsigned i; + + for (i = 0; i < n; i++) + c[i] = exec_command_free_list(c[i]); +} + +typedef struct InvalidEnvInfo { + Unit *unit; + const char *path; +} InvalidEnvInfo; + +static void invalid_env(const char *p, void *userdata) { + InvalidEnvInfo *info = userdata; + + log_unit_error(info->unit, "Ignoring invalid environment assignment '%s': %s", p, info->path); +} + +int exec_context_load_environment(Unit *unit, const ExecContext *c, char ***l) { + char **i, **r = NULL; + + assert(c); + assert(l); + + STRV_FOREACH(i, c->environment_files) { + char *fn; + int k; + bool ignore = false; + char **p; + _cleanup_globfree_ glob_t pglob = {}; + int count, n; + + fn = *i; + + if (fn[0] == '-') { + ignore = true; + fn++; + } + + if (!path_is_absolute(fn)) { + if (ignore) + continue; + + strv_free(r); + return -EINVAL; + } + + /* Filename supports globbing, take all matching files */ + errno = 0; + if (glob(fn, 0, NULL, &pglob) != 0) { + if (ignore) + continue; + + strv_free(r); + return errno > 0 ? -errno : -EINVAL; + } + count = pglob.gl_pathc; + if (count == 0) { + if (ignore) + continue; + + strv_free(r); + return -EINVAL; + } + for (n = 0; n < count; n++) { + k = load_env_file(NULL, pglob.gl_pathv[n], NULL, &p); + if (k < 0) { + if (ignore) + continue; + + strv_free(r); + return k; + } + /* Log invalid environment variables with filename */ + if (p) { + InvalidEnvInfo info = { + .unit = unit, + .path = pglob.gl_pathv[n] + }; + + p = strv_env_clean_with_callback(p, invalid_env, &info); + } + + if (r == NULL) + r = p; + else { + char **m; + + m = strv_env_merge(2, r, p); + strv_free(r); + strv_free(p); + if (!m) + return -ENOMEM; + + r = m; + } + } + } + + *l = r; + + return 0; +} + +static bool tty_may_match_dev_console(const char *tty) { + _cleanup_free_ char *active = NULL; + char *console; + + if (!tty) + return true; + + if (startswith(tty, "/dev/")) + tty += 5; + + /* trivial identity? */ + if (streq(tty, "console")) + return true; + + console = resolve_dev_console(&active); + /* if we could not resolve, assume it may */ + if (!console) + return true; + + /* "tty0" means the active VC, so it may be the same sometimes */ + return streq(console, tty) || (streq(console, "tty0") && tty_is_vc(tty)); +} + +bool exec_context_may_touch_console(ExecContext *ec) { + + return (ec->tty_reset || + ec->tty_vhangup || + ec->tty_vt_disallocate || + is_terminal_input(ec->std_input) || + is_terminal_output(ec->std_output) || + is_terminal_output(ec->std_error)) && + tty_may_match_dev_console(exec_context_tty_path(ec)); +} + +static void strv_fprintf(FILE *f, char **l) { + char **g; + + assert(f); + + STRV_FOREACH(g, l) + fprintf(f, " %s", *g); +} + +void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { + char **e, **d; + unsigned i; + + assert(c); + assert(f); + + prefix = strempty(prefix); + + fprintf(f, + "%sUMask: %04o\n" + "%sWorkingDirectory: %s\n" + "%sRootDirectory: %s\n" + "%sNonBlocking: %s\n" + "%sPrivateTmp: %s\n" + "%sPrivateNetwork: %s\n" + "%sPrivateDevices: %s\n" + "%sProtectHome: %s\n" + "%sProtectSystem: %s\n" + "%sIgnoreSIGPIPE: %s\n", + prefix, c->umask, + prefix, c->working_directory ? c->working_directory : "/", + prefix, c->root_directory ? c->root_directory : "/", + prefix, yes_no(c->non_blocking), + prefix, yes_no(c->private_tmp), + prefix, yes_no(c->private_network), + prefix, yes_no(c->private_devices), + prefix, protect_home_to_string(c->protect_home), + prefix, protect_system_to_string(c->protect_system), + prefix, yes_no(c->ignore_sigpipe)); + + STRV_FOREACH(e, c->environment) + fprintf(f, "%sEnvironment: %s\n", prefix, *e); + + STRV_FOREACH(e, c->environment_files) + fprintf(f, "%sEnvironmentFile: %s\n", prefix, *e); + + STRV_FOREACH(e, c->pass_environment) + fprintf(f, "%sPassEnvironment: %s\n", prefix, *e); + + fprintf(f, "%sRuntimeDirectoryMode: %04o\n", prefix, c->runtime_directory_mode); + + STRV_FOREACH(d, c->runtime_directory) + fprintf(f, "%sRuntimeDirectory: %s\n", prefix, *d); + + if (c->nice_set) + fprintf(f, + "%sNice: %i\n", + prefix, c->nice); + + if (c->oom_score_adjust_set) + fprintf(f, + "%sOOMScoreAdjust: %i\n", + prefix, c->oom_score_adjust); + + for (i = 0; i < RLIM_NLIMITS; i++) + if (c->rlimit[i]) { + fprintf(f, "%s%s: " RLIM_FMT "\n", + prefix, rlimit_to_string(i), c->rlimit[i]->rlim_max); + fprintf(f, "%s%sSoft: " RLIM_FMT "\n", + prefix, rlimit_to_string(i), c->rlimit[i]->rlim_cur); + } + + if (c->ioprio_set) { + _cleanup_free_ char *class_str = NULL; + + ioprio_class_to_string_alloc(IOPRIO_PRIO_CLASS(c->ioprio), &class_str); + fprintf(f, + "%sIOSchedulingClass: %s\n" + "%sIOPriority: %i\n", + prefix, strna(class_str), + prefix, (int) IOPRIO_PRIO_DATA(c->ioprio)); + } + + if (c->cpu_sched_set) { + _cleanup_free_ char *policy_str = NULL; + + sched_policy_to_string_alloc(c->cpu_sched_policy, &policy_str); + fprintf(f, + "%sCPUSchedulingPolicy: %s\n" + "%sCPUSchedulingPriority: %i\n" + "%sCPUSchedulingResetOnFork: %s\n", + prefix, strna(policy_str), + prefix, c->cpu_sched_priority, + prefix, yes_no(c->cpu_sched_reset_on_fork)); + } + + if (c->cpuset) { + fprintf(f, "%sCPUAffinity:", prefix); + for (i = 0; i < c->cpuset_ncpus; i++) + if (CPU_ISSET_S(i, CPU_ALLOC_SIZE(c->cpuset_ncpus), c->cpuset)) + fprintf(f, " %u", i); + fputs("\n", f); + } + + if (c->timer_slack_nsec != NSEC_INFINITY) + fprintf(f, "%sTimerSlackNSec: "NSEC_FMT "\n", prefix, c->timer_slack_nsec); + + fprintf(f, + "%sStandardInput: %s\n" + "%sStandardOutput: %s\n" + "%sStandardError: %s\n", + prefix, exec_input_to_string(c->std_input), + prefix, exec_output_to_string(c->std_output), + prefix, exec_output_to_string(c->std_error)); + + if (c->tty_path) + fprintf(f, + "%sTTYPath: %s\n" + "%sTTYReset: %s\n" + "%sTTYVHangup: %s\n" + "%sTTYVTDisallocate: %s\n", + prefix, c->tty_path, + prefix, yes_no(c->tty_reset), + prefix, yes_no(c->tty_vhangup), + prefix, yes_no(c->tty_vt_disallocate)); + + if (c->std_output == EXEC_OUTPUT_SYSLOG || + c->std_output == EXEC_OUTPUT_KMSG || + c->std_output == EXEC_OUTPUT_JOURNAL || + c->std_output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || + c->std_output == EXEC_OUTPUT_KMSG_AND_CONSOLE || + c->std_output == EXEC_OUTPUT_JOURNAL_AND_CONSOLE || + c->std_error == EXEC_OUTPUT_SYSLOG || + c->std_error == EXEC_OUTPUT_KMSG || + c->std_error == EXEC_OUTPUT_JOURNAL || + c->std_error == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || + c->std_error == EXEC_OUTPUT_KMSG_AND_CONSOLE || + c->std_error == EXEC_OUTPUT_JOURNAL_AND_CONSOLE) { + + _cleanup_free_ char *fac_str = NULL, *lvl_str = NULL; + + log_facility_unshifted_to_string_alloc(c->syslog_priority >> 3, &fac_str); + log_level_to_string_alloc(LOG_PRI(c->syslog_priority), &lvl_str); + + fprintf(f, + "%sSyslogFacility: %s\n" + "%sSyslogLevel: %s\n", + prefix, strna(fac_str), + prefix, strna(lvl_str)); + } + + if (c->secure_bits) + fprintf(f, "%sSecure Bits:%s%s%s%s%s%s\n", + prefix, + (c->secure_bits & 1<secure_bits & 1<secure_bits & 1<secure_bits & 1<secure_bits & 1<secure_bits & 1<capability_bounding_set != CAP_ALL) { + unsigned long l; + fprintf(f, "%sCapabilityBoundingSet:", prefix); + + for (l = 0; l <= cap_last_cap(); l++) + if (c->capability_bounding_set & (UINT64_C(1) << l)) + fprintf(f, " %s", strna(capability_to_name(l))); + + fputs("\n", f); + } + + if (c->capability_ambient_set != 0) { + unsigned long l; + fprintf(f, "%sAmbientCapabilities:", prefix); + + for (l = 0; l <= cap_last_cap(); l++) + if (c->capability_ambient_set & (UINT64_C(1) << l)) + fprintf(f, " %s", strna(capability_to_name(l))); + + fputs("\n", f); + } + + if (c->user) + fprintf(f, "%sUser: %s\n", prefix, c->user); + if (c->group) + fprintf(f, "%sGroup: %s\n", prefix, c->group); + + if (strv_length(c->supplementary_groups) > 0) { + fprintf(f, "%sSupplementaryGroups:", prefix); + strv_fprintf(f, c->supplementary_groups); + fputs("\n", f); + } + + if (c->pam_name) + fprintf(f, "%sPAMName: %s\n", prefix, c->pam_name); + + if (strv_length(c->read_write_dirs) > 0) { + fprintf(f, "%sReadWriteDirs:", prefix); + strv_fprintf(f, c->read_write_dirs); + fputs("\n", f); + } + + if (strv_length(c->read_only_dirs) > 0) { + fprintf(f, "%sReadOnlyDirs:", prefix); + strv_fprintf(f, c->read_only_dirs); + fputs("\n", f); + } + + if (strv_length(c->inaccessible_dirs) > 0) { + fprintf(f, "%sInaccessibleDirs:", prefix); + strv_fprintf(f, c->inaccessible_dirs); + fputs("\n", f); + } + + if (c->utmp_id) + fprintf(f, + "%sUtmpIdentifier: %s\n", + prefix, c->utmp_id); + + if (c->selinux_context) + fprintf(f, + "%sSELinuxContext: %s%s\n", + prefix, c->selinux_context_ignore ? "-" : "", c->selinux_context); + + if (c->personality != PERSONALITY_INVALID) + fprintf(f, + "%sPersonality: %s\n", + prefix, strna(personality_to_string(c->personality))); + + if (c->syscall_filter) { +#ifdef HAVE_SECCOMP + Iterator j; + void *id; + bool first = true; +#endif + + fprintf(f, + "%sSystemCallFilter: ", + prefix); + + if (!c->syscall_whitelist) + fputc('~', f); + +#ifdef HAVE_SECCOMP + SET_FOREACH(id, c->syscall_filter, j) { + _cleanup_free_ char *name = NULL; + + if (first) + first = false; + else + fputc(' ', f); + + name = seccomp_syscall_resolve_num_arch(SCMP_ARCH_NATIVE, PTR_TO_INT(id) - 1); + fputs(strna(name), f); + } +#endif + + fputc('\n', f); + } + + if (c->syscall_archs) { +#ifdef HAVE_SECCOMP + Iterator j; + void *id; +#endif + + fprintf(f, + "%sSystemCallArchitectures:", + prefix); + +#ifdef HAVE_SECCOMP + SET_FOREACH(id, c->syscall_archs, j) + fprintf(f, " %s", strna(seccomp_arch_to_string(PTR_TO_UINT32(id) - 1))); +#endif + fputc('\n', f); + } + + if (c->syscall_errno > 0) + fprintf(f, + "%sSystemCallErrorNumber: %s\n", + prefix, strna(errno_to_name(c->syscall_errno))); + + if (c->apparmor_profile) + fprintf(f, + "%sAppArmorProfile: %s%s\n", + prefix, c->apparmor_profile_ignore ? "-" : "", c->apparmor_profile); +} + +bool exec_context_maintains_privileges(ExecContext *c) { + assert(c); + + /* Returns true if the process forked off would run run under + * an unchanged UID or as root. */ + + if (!c->user) + return true; + + if (streq(c->user, "root") || streq(c->user, "0")) + return true; + + return false; +} + +void exec_status_start(ExecStatus *s, pid_t pid) { + assert(s); + + zero(*s); + s->pid = pid; + dual_timestamp_get(&s->start_timestamp); +} + +void exec_status_exit(ExecStatus *s, ExecContext *context, pid_t pid, int code, int status) { + assert(s); + + if (s->pid && s->pid != pid) + zero(*s); + + s->pid = pid; + dual_timestamp_get(&s->exit_timestamp); + + s->code = code; + s->status = status; + + if (context) { + if (context->utmp_id) + utmp_put_dead_process(context->utmp_id, pid, code, status); + + exec_context_tty_reset(context, NULL); + } +} + +void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix) { + char buf[FORMAT_TIMESTAMP_MAX]; + + assert(s); + assert(f); + + if (s->pid <= 0) + return; + + prefix = strempty(prefix); + + fprintf(f, + "%sPID: "PID_FMT"\n", + prefix, s->pid); + + if (s->start_timestamp.realtime > 0) + fprintf(f, + "%sStart Timestamp: %s\n", + prefix, format_timestamp(buf, sizeof(buf), s->start_timestamp.realtime)); + + if (s->exit_timestamp.realtime > 0) + fprintf(f, + "%sExit Timestamp: %s\n" + "%sExit Code: %s\n" + "%sExit Status: %i\n", + prefix, format_timestamp(buf, sizeof(buf), s->exit_timestamp.realtime), + prefix, sigchld_code_to_string(s->code), + prefix, s->status); +} + +char *exec_command_line(char **argv) { + size_t k; + char *n, *p, **a; + bool first = true; + + assert(argv); + + k = 1; + STRV_FOREACH(a, argv) + k += strlen(*a)+3; + + if (!(n = new(char, k))) + return NULL; + + p = n; + STRV_FOREACH(a, argv) { + + if (!first) + *(p++) = ' '; + else + first = false; + + if (strpbrk(*a, WHITESPACE)) { + *(p++) = '\''; + p = stpcpy(p, *a); + *(p++) = '\''; + } else + p = stpcpy(p, *a); + + } + + *p = 0; + + /* FIXME: this doesn't really handle arguments that have + * spaces and ticks in them */ + + return n; +} + +void exec_command_dump(ExecCommand *c, FILE *f, const char *prefix) { + _cleanup_free_ char *cmd = NULL; + const char *prefix2; + + assert(c); + assert(f); + + prefix = strempty(prefix); + prefix2 = strjoina(prefix, "\t"); + + cmd = exec_command_line(c->argv); + fprintf(f, + "%sCommand Line: %s\n", + prefix, cmd ? cmd : strerror(ENOMEM)); + + exec_status_dump(&c->exec_status, f, prefix2); +} + +void exec_command_dump_list(ExecCommand *c, FILE *f, const char *prefix) { + assert(f); + + prefix = strempty(prefix); + + LIST_FOREACH(command, c, c) + exec_command_dump(c, f, prefix); +} + +void exec_command_append_list(ExecCommand **l, ExecCommand *e) { + ExecCommand *end; + + assert(l); + assert(e); + + if (*l) { + /* It's kind of important, that we keep the order here */ + LIST_FIND_TAIL(command, *l, end); + LIST_INSERT_AFTER(command, *l, end, e); + } else + *l = e; +} + +int exec_command_set(ExecCommand *c, const char *path, ...) { + va_list ap; + char **l, *p; + + assert(c); + assert(path); + + va_start(ap, path); + l = strv_new_ap(path, ap); + va_end(ap); + + if (!l) + return -ENOMEM; + + p = strdup(path); + if (!p) { + strv_free(l); + return -ENOMEM; + } + + free(c->path); + c->path = p; + + strv_free(c->argv); + c->argv = l; + + return 0; +} + +int exec_command_append(ExecCommand *c, const char *path, ...) { + _cleanup_strv_free_ char **l = NULL; + va_list ap; + int r; + + assert(c); + assert(path); + + va_start(ap, path); + l = strv_new_ap(path, ap); + va_end(ap); + + if (!l) + return -ENOMEM; + + r = strv_extend_strv(&c->argv, l, false); + if (r < 0) + return r; + + return 0; +} + + +static int exec_runtime_allocate(ExecRuntime **rt) { + + if (*rt) + return 0; + + *rt = new0(ExecRuntime, 1); + if (!*rt) + return -ENOMEM; + + (*rt)->n_ref = 1; + (*rt)->netns_storage_socket[0] = (*rt)->netns_storage_socket[1] = -1; + + return 0; +} + +int exec_runtime_make(ExecRuntime **rt, ExecContext *c, const char *id) { + int r; + + assert(rt); + assert(c); + assert(id); + + if (*rt) + return 1; + + if (!c->private_network && !c->private_tmp) + return 0; + + r = exec_runtime_allocate(rt); + if (r < 0) + return r; + + if (c->private_network && (*rt)->netns_storage_socket[0] < 0) { + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, (*rt)->netns_storage_socket) < 0) + return -errno; + } + + if (c->private_tmp && !(*rt)->tmp_dir) { + r = setup_tmp_dirs(id, &(*rt)->tmp_dir, &(*rt)->var_tmp_dir); + if (r < 0) + return r; + } + + return 1; +} + +ExecRuntime *exec_runtime_ref(ExecRuntime *r) { + assert(r); + assert(r->n_ref > 0); + + r->n_ref++; + return r; +} + +ExecRuntime *exec_runtime_unref(ExecRuntime *r) { + + if (!r) + return NULL; + + assert(r->n_ref > 0); + + r->n_ref--; + if (r->n_ref > 0) + return NULL; + + free(r->tmp_dir); + free(r->var_tmp_dir); + safe_close_pair(r->netns_storage_socket); + free(r); + + return NULL; +} + +int exec_runtime_serialize(Unit *u, ExecRuntime *rt, FILE *f, FDSet *fds) { + assert(u); + assert(f); + assert(fds); + + if (!rt) + return 0; + + if (rt->tmp_dir) + unit_serialize_item(u, f, "tmp-dir", rt->tmp_dir); + + if (rt->var_tmp_dir) + unit_serialize_item(u, f, "var-tmp-dir", rt->var_tmp_dir); + + if (rt->netns_storage_socket[0] >= 0) { + int copy; + + copy = fdset_put_dup(fds, rt->netns_storage_socket[0]); + if (copy < 0) + return copy; + + unit_serialize_item_format(u, f, "netns-socket-0", "%i", copy); + } + + if (rt->netns_storage_socket[1] >= 0) { + int copy; + + copy = fdset_put_dup(fds, rt->netns_storage_socket[1]); + if (copy < 0) + return copy; + + unit_serialize_item_format(u, f, "netns-socket-1", "%i", copy); + } + + return 0; +} + +int exec_runtime_deserialize_item(Unit *u, ExecRuntime **rt, const char *key, const char *value, FDSet *fds) { + int r; + + assert(rt); + assert(key); + assert(value); + + if (streq(key, "tmp-dir")) { + char *copy; + + r = exec_runtime_allocate(rt); + if (r < 0) + return log_oom(); + + copy = strdup(value); + if (!copy) + return log_oom(); + + free((*rt)->tmp_dir); + (*rt)->tmp_dir = copy; + + } else if (streq(key, "var-tmp-dir")) { + char *copy; + + r = exec_runtime_allocate(rt); + if (r < 0) + return log_oom(); + + copy = strdup(value); + if (!copy) + return log_oom(); + + free((*rt)->var_tmp_dir); + (*rt)->var_tmp_dir = copy; + + } else if (streq(key, "netns-socket-0")) { + int fd; + + r = exec_runtime_allocate(rt); + if (r < 0) + return log_oom(); + + if (safe_atoi(value, &fd) < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse netns socket value: %s", value); + else { + safe_close((*rt)->netns_storage_socket[0]); + (*rt)->netns_storage_socket[0] = fdset_remove(fds, fd); + } + } else if (streq(key, "netns-socket-1")) { + int fd; + + r = exec_runtime_allocate(rt); + if (r < 0) + return log_oom(); + + if (safe_atoi(value, &fd) < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse netns socket value: %s", value); + else { + safe_close((*rt)->netns_storage_socket[1]); + (*rt)->netns_storage_socket[1] = fdset_remove(fds, fd); + } + } else + return 0; + + return 1; +} + +static void *remove_tmpdir_thread(void *p) { + _cleanup_free_ char *path = p; + + (void) rm_rf(path, REMOVE_ROOT|REMOVE_PHYSICAL); + return NULL; +} + +void exec_runtime_destroy(ExecRuntime *rt) { + int r; + + if (!rt) + return; + + /* If there are multiple users of this, let's leave the stuff around */ + if (rt->n_ref > 1) + return; + + if (rt->tmp_dir) { + log_debug("Spawning thread to nuke %s", rt->tmp_dir); + + r = asynchronous_job(remove_tmpdir_thread, rt->tmp_dir); + if (r < 0) { + log_warning_errno(r, "Failed to nuke %s: %m", rt->tmp_dir); + free(rt->tmp_dir); + } + + rt->tmp_dir = NULL; + } + + if (rt->var_tmp_dir) { + log_debug("Spawning thread to nuke %s", rt->var_tmp_dir); + + r = asynchronous_job(remove_tmpdir_thread, rt->var_tmp_dir); + if (r < 0) { + log_warning_errno(r, "Failed to nuke %s: %m", rt->var_tmp_dir); + free(rt->var_tmp_dir); + } + + rt->var_tmp_dir = NULL; + } + + safe_close_pair(rt->netns_storage_socket); +} + +static const char* const exec_input_table[_EXEC_INPUT_MAX] = { + [EXEC_INPUT_NULL] = "null", + [EXEC_INPUT_TTY] = "tty", + [EXEC_INPUT_TTY_FORCE] = "tty-force", + [EXEC_INPUT_TTY_FAIL] = "tty-fail", + [EXEC_INPUT_SOCKET] = "socket" +}; + +DEFINE_STRING_TABLE_LOOKUP(exec_input, ExecInput); + +static const char* const exec_output_table[_EXEC_OUTPUT_MAX] = { + [EXEC_OUTPUT_INHERIT] = "inherit", + [EXEC_OUTPUT_NULL] = "null", + [EXEC_OUTPUT_TTY] = "tty", + [EXEC_OUTPUT_SYSLOG] = "syslog", + [EXEC_OUTPUT_SYSLOG_AND_CONSOLE] = "syslog+console", + [EXEC_OUTPUT_KMSG] = "kmsg", + [EXEC_OUTPUT_KMSG_AND_CONSOLE] = "kmsg+console", + [EXEC_OUTPUT_JOURNAL] = "journal", + [EXEC_OUTPUT_JOURNAL_AND_CONSOLE] = "journal+console", + [EXEC_OUTPUT_SOCKET] = "socket" +}; + +DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput); + +static const char* const exec_utmp_mode_table[_EXEC_UTMP_MODE_MAX] = { + [EXEC_UTMP_INIT] = "init", + [EXEC_UTMP_LOGIN] = "login", + [EXEC_UTMP_USER] = "user", +}; + +DEFINE_STRING_TABLE_LOOKUP(exec_utmp_mode, ExecUtmpMode); diff --git a/src/libcore/execute.h b/src/libcore/execute.h new file mode 100644 index 0000000000..41148bcea2 --- /dev/null +++ b/src/libcore/execute.h @@ -0,0 +1,287 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct ExecStatus ExecStatus; +typedef struct ExecCommand ExecCommand; +typedef struct ExecContext ExecContext; +typedef struct ExecRuntime ExecRuntime; +typedef struct ExecParameters ExecParameters; + +#include +#include +#include +#include + +#include "fdset.h" +#include "list.h" +#include "missing.h" +#include "namespace.h" + +typedef enum ExecUtmpMode { + EXEC_UTMP_INIT, + EXEC_UTMP_LOGIN, + EXEC_UTMP_USER, + _EXEC_UTMP_MODE_MAX, + _EXEC_UTMP_MODE_INVALID = -1 +} ExecUtmpMode; + +typedef enum ExecInput { + EXEC_INPUT_NULL, + EXEC_INPUT_TTY, + EXEC_INPUT_TTY_FORCE, + EXEC_INPUT_TTY_FAIL, + EXEC_INPUT_SOCKET, + _EXEC_INPUT_MAX, + _EXEC_INPUT_INVALID = -1 +} ExecInput; + +typedef enum ExecOutput { + EXEC_OUTPUT_INHERIT, + EXEC_OUTPUT_NULL, + EXEC_OUTPUT_TTY, + EXEC_OUTPUT_SYSLOG, + EXEC_OUTPUT_SYSLOG_AND_CONSOLE, + EXEC_OUTPUT_KMSG, + EXEC_OUTPUT_KMSG_AND_CONSOLE, + EXEC_OUTPUT_JOURNAL, + EXEC_OUTPUT_JOURNAL_AND_CONSOLE, + EXEC_OUTPUT_SOCKET, + _EXEC_OUTPUT_MAX, + _EXEC_OUTPUT_INVALID = -1 +} ExecOutput; + +struct ExecStatus { + dual_timestamp start_timestamp; + dual_timestamp exit_timestamp; + pid_t pid; + int code; /* as in siginfo_t::si_code */ + int status; /* as in sigingo_t::si_status */ +}; + +struct ExecCommand { + char *path; + char **argv; + ExecStatus exec_status; + LIST_FIELDS(ExecCommand, command); /* useful for chaining commands */ + bool ignore; +}; + +struct ExecRuntime { + int n_ref; + + char *tmp_dir; + char *var_tmp_dir; + + int netns_storage_socket[2]; +}; + +struct ExecContext { + char **environment; + char **environment_files; + char **pass_environment; + + struct rlimit *rlimit[_RLIMIT_MAX]; + char *working_directory, *root_directory; + bool working_directory_missing_ok; + bool working_directory_home; + + mode_t umask; + int oom_score_adjust; + int nice; + int ioprio; + int cpu_sched_policy; + int cpu_sched_priority; + + cpu_set_t *cpuset; + unsigned cpuset_ncpus; + + ExecInput std_input; + ExecOutput std_output; + ExecOutput std_error; + + nsec_t timer_slack_nsec; + + bool stdio_as_fds; + + char *tty_path; + + bool tty_reset; + bool tty_vhangup; + bool tty_vt_disallocate; + + bool ignore_sigpipe; + + /* Since resolving these names might might involve socket + * connections and we don't want to deadlock ourselves these + * names are resolved on execution only and in the child + * process. */ + char *user; + char *group; + char **supplementary_groups; + + char *pam_name; + + char *utmp_id; + ExecUtmpMode utmp_mode; + + bool selinux_context_ignore; + char *selinux_context; + + bool apparmor_profile_ignore; + char *apparmor_profile; + + bool smack_process_label_ignore; + char *smack_process_label; + + char **read_write_dirs, **read_only_dirs, **inaccessible_dirs; + unsigned long mount_flags; + + uint64_t capability_bounding_set; + uint64_t capability_ambient_set; + int secure_bits; + + int syslog_priority; + char *syslog_identifier; + bool syslog_level_prefix; + + bool cpu_sched_reset_on_fork; + bool non_blocking; + bool private_tmp; + bool private_network; + bool private_devices; + ProtectSystem protect_system; + ProtectHome protect_home; + + bool no_new_privileges; + + /* This is not exposed to the user but available + * internally. We need it to make sure that whenever we spawn + * /usr/bin/mount it is run in the same process group as us so + * that the autofs logic detects that it belongs to us and we + * don't enter a trigger loop. */ + bool same_pgrp; + + unsigned long personality; + + Set *syscall_filter; + Set *syscall_archs; + int syscall_errno; + bool syscall_whitelist:1; + + Set *address_families; + bool address_families_whitelist:1; + + char **runtime_directory; + mode_t runtime_directory_mode; + + bool oom_score_adjust_set:1; + bool nice_set:1; + bool ioprio_set:1; + bool cpu_sched_set:1; + bool no_new_privileges_set:1; +}; + +#include "cgroup-util.h" +#include "cgroup.h" + +struct ExecParameters { + char **argv; + char **environment; + + int *fds; + char **fd_names; + unsigned n_fds; + + bool apply_permissions:1; + bool apply_chroot:1; + bool apply_tty_stdin:1; + + bool confirm_spawn:1; + bool selinux_context_net:1; + + bool cgroup_delegate:1; + CGroupMask cgroup_supported; + const char *cgroup_path; + + const char *runtime_prefix; + + usec_t watchdog_usec; + + int *idle_pipe; + + int stdin_fd; + int stdout_fd; + int stderr_fd; +}; + +int exec_spawn(Unit *unit, + ExecCommand *command, + const ExecContext *context, + const ExecParameters *exec_params, + ExecRuntime *runtime, + pid_t *ret); + +void exec_command_done(ExecCommand *c); +void exec_command_done_array(ExecCommand *c, unsigned n); + +ExecCommand* exec_command_free_list(ExecCommand *c); +void exec_command_free_array(ExecCommand **c, unsigned n); + +char *exec_command_line(char **argv); + +void exec_command_dump(ExecCommand *c, FILE *f, const char *prefix); +void exec_command_dump_list(ExecCommand *c, FILE *f, const char *prefix); +void exec_command_append_list(ExecCommand **l, ExecCommand *e); +int exec_command_set(ExecCommand *c, const char *path, ...); +int exec_command_append(ExecCommand *c, const char *path, ...); + +void exec_context_init(ExecContext *c); +void exec_context_done(ExecContext *c); +void exec_context_dump(ExecContext *c, FILE* f, const char *prefix); + +int exec_context_destroy_runtime_directory(ExecContext *c, const char *runtime_root); + +int exec_context_load_environment(Unit *unit, const ExecContext *c, char ***l); + +bool exec_context_may_touch_console(ExecContext *c); +bool exec_context_maintains_privileges(ExecContext *c); + +void exec_status_start(ExecStatus *s, pid_t pid); +void exec_status_exit(ExecStatus *s, ExecContext *context, pid_t pid, int code, int status); +void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix); + +int exec_runtime_make(ExecRuntime **rt, ExecContext *c, const char *id); +ExecRuntime *exec_runtime_ref(ExecRuntime *r); +ExecRuntime *exec_runtime_unref(ExecRuntime *r); + +int exec_runtime_serialize(Unit *unit, ExecRuntime *rt, FILE *f, FDSet *fds); +int exec_runtime_deserialize_item(Unit *unit, ExecRuntime **rt, const char *key, const char *value, FDSet *fds); + +void exec_runtime_destroy(ExecRuntime *rt); + +const char* exec_output_to_string(ExecOutput i) _const_; +ExecOutput exec_output_from_string(const char *s) _pure_; + +const char* exec_input_to_string(ExecInput i) _const_; +ExecInput exec_input_from_string(const char *s) _pure_; + +const char* exec_utmp_mode_to_string(ExecUtmpMode i) _const_; +ExecUtmpMode exec_utmp_mode_from_string(const char *s) _pure_; diff --git a/src/libcore/failure-action.c b/src/libcore/failure-action.c new file mode 100644 index 0000000000..ddae46190f --- /dev/null +++ b/src/libcore/failure-action.c @@ -0,0 +1,127 @@ +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + Copyright 2012 Michael Olbrich + + 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 + +#include "bus-error.h" +#include "bus-util.h" +#include "failure-action.h" +#include "special.h" +#include "string-table.h" +#include "terminal-util.h" + +static void log_and_status(Manager *m, const char *message) { + log_warning("%s", message); + manager_status_printf(m, STATUS_TYPE_EMERGENCY, + ANSI_HIGHLIGHT_RED " !! " ANSI_NORMAL, + "%s", message); +} + +int failure_action( + Manager *m, + FailureAction action, + const char *reboot_arg) { + + assert(m); + assert(action >= 0); + assert(action < _FAILURE_ACTION_MAX); + + if (action == FAILURE_ACTION_NONE) + return -ECANCELED; + + if (!MANAGER_IS_SYSTEM(m)) { + /* Downgrade all options to simply exiting if we run + * in user mode */ + + log_warning("Exiting as result of failure."); + m->exit_code = MANAGER_EXIT; + return -ECANCELED; + } + + switch (action) { + + case FAILURE_ACTION_REBOOT: + log_and_status(m, "Rebooting as result of failure."); + + (void) update_reboot_parameter_and_warn(reboot_arg); + (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_REBOOT_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL); + + break; + + case FAILURE_ACTION_REBOOT_FORCE: + log_and_status(m, "Forcibly rebooting as result of failure."); + + (void) update_reboot_parameter_and_warn(reboot_arg); + m->exit_code = MANAGER_REBOOT; + + break; + + case FAILURE_ACTION_REBOOT_IMMEDIATE: + log_and_status(m, "Rebooting immediately as result of failure."); + + sync(); + + if (!isempty(reboot_arg)) { + log_info("Rebooting with argument '%s'.", reboot_arg); + syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, reboot_arg); + log_warning_errno(errno, "Failed to reboot with parameter, retrying without: %m"); + } + + log_info("Rebooting."); + reboot(RB_AUTOBOOT); + break; + + case FAILURE_ACTION_POWEROFF: + log_and_status(m, "Powering off as result of failure."); + (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_POWEROFF_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL); + break; + + case FAILURE_ACTION_POWEROFF_FORCE: + log_and_status(m, "Forcibly powering off as result of failure."); + m->exit_code = MANAGER_POWEROFF; + break; + + case FAILURE_ACTION_POWEROFF_IMMEDIATE: + log_and_status(m, "Powering off immediately as result of failure."); + + sync(); + + log_info("Powering off."); + reboot(RB_POWER_OFF); + break; + + default: + assert_not_reached("Unknown failure action"); + } + + return -ECANCELED; +} + +static const char* const failure_action_table[_FAILURE_ACTION_MAX] = { + [FAILURE_ACTION_NONE] = "none", + [FAILURE_ACTION_REBOOT] = "reboot", + [FAILURE_ACTION_REBOOT_FORCE] = "reboot-force", + [FAILURE_ACTION_REBOOT_IMMEDIATE] = "reboot-immediate", + [FAILURE_ACTION_POWEROFF] = "poweroff", + [FAILURE_ACTION_POWEROFF_FORCE] = "poweroff-force", + [FAILURE_ACTION_POWEROFF_IMMEDIATE] = "poweroff-immediate" +}; +DEFINE_STRING_TABLE_LOOKUP(failure_action, FailureAction); diff --git a/src/libcore/failure-action.h b/src/libcore/failure-action.h new file mode 100644 index 0000000000..1adac4ad5c --- /dev/null +++ b/src/libcore/failure-action.h @@ -0,0 +1,41 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + Copyright 2012 Michael Olbrich + + 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 . +***/ + +typedef enum FailureAction { + FAILURE_ACTION_NONE, + FAILURE_ACTION_REBOOT, + FAILURE_ACTION_REBOOT_FORCE, + FAILURE_ACTION_REBOOT_IMMEDIATE, + FAILURE_ACTION_POWEROFF, + FAILURE_ACTION_POWEROFF_FORCE, + FAILURE_ACTION_POWEROFF_IMMEDIATE, + _FAILURE_ACTION_MAX, + _FAILURE_ACTION_INVALID = -1 +} FailureAction; + +#include "macro.h" +#include "manager.h" + +int failure_action(Manager *m, FailureAction action, const char *reboot_arg); + +const char* failure_action_to_string(FailureAction i) _const_; +FailureAction failure_action_from_string(const char *s) _pure_; diff --git a/src/libcore/hostname-setup.c b/src/libcore/hostname-setup.c new file mode 100644 index 0000000000..68be52856b --- /dev/null +++ b/src/libcore/hostname-setup.c @@ -0,0 +1,68 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include + +#include "alloc-util.h" +#include "fileio.h" +#include "hostname-setup.h" +#include "hostname-util.h" +#include "log.h" +#include "macro.h" +#include "string-util.h" +#include "util.h" + +int hostname_setup(void) { + int r; + _cleanup_free_ char *b = NULL; + const char *hn; + bool enoent = false; + + r = read_hostname_config("/etc/hostname", &b); + if (r < 0) { + if (r == -ENOENT) + enoent = true; + else + log_warning_errno(r, "Failed to read configured hostname: %m"); + + hn = NULL; + } else + hn = b; + + if (isempty(hn)) { + /* Don't override the hostname if it is already set + * and not explicitly configured */ + if (hostname_is_set()) + return 0; + + if (enoent) + log_info("No hostname configured."); + + hn = "localhost"; + } + + r = sethostname_idempotent(hn); + if (r < 0) + return log_warning_errno(r, "Failed to set hostname to <%s>: %m", hn); + + log_info("Set hostname to <%s>.", hn); + return 0; +} diff --git a/src/libcore/hostname-setup.h b/src/libcore/hostname-setup.h new file mode 100644 index 0000000000..73e8c75c71 --- /dev/null +++ b/src/libcore/hostname-setup.h @@ -0,0 +1,22 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +int hostname_setup(void); diff --git a/src/libcore/ima-setup.c b/src/libcore/ima-setup.c new file mode 100644 index 0000000000..d1b0ce76ef --- /dev/null +++ b/src/libcore/ima-setup.c @@ -0,0 +1,80 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright (C) 2012 Roberto Sassu - Politecnico di Torino, Italy + TORSEC group — http://security.polito.it + + 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 + +#include "fd-util.h" +#include "fileio.h" +#include "ima-setup.h" +#include "log.h" +#include "util.h" + +#define IMA_SECFS_DIR "/sys/kernel/security/ima" +#define IMA_SECFS_POLICY IMA_SECFS_DIR "/policy" +#define IMA_POLICY_PATH "/etc/ima/ima-policy" + +int ima_setup(void) { +#ifdef HAVE_IMA + _cleanup_fclose_ FILE *input = NULL; + _cleanup_close_ int imafd = -1; + unsigned lineno = 0; + char line[page_size()]; + + if (access(IMA_SECFS_DIR, F_OK) < 0) { + log_debug("IMA support is disabled in the kernel, ignoring."); + return 0; + } + + input = fopen(IMA_POLICY_PATH, "re"); + if (!input) { + log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno, + "Failed to open the IMA custom policy file "IMA_POLICY_PATH", ignoring: %m"); + return 0; + } + + if (access(IMA_SECFS_POLICY, F_OK) < 0) { + log_warning("Another IMA custom policy has already been loaded, ignoring."); + return 0; + } + + imafd = open(IMA_SECFS_POLICY, O_WRONLY|O_CLOEXEC); + if (imafd < 0) { + log_error_errno(errno, "Failed to open the IMA kernel interface "IMA_SECFS_POLICY", ignoring: %m"); + return 0; + } + + FOREACH_LINE(line, input, + return log_error_errno(errno, "Failed to read the IMA custom policy file "IMA_POLICY_PATH": %m")) { + size_t len; + + len = strlen(line); + lineno++; + + if (len > 0 && write(imafd, line, len) < 0) + return log_error_errno(errno, "Failed to load the IMA custom policy file "IMA_POLICY_PATH"%u: %m", + lineno); + } + + log_info("Successfully loaded the IMA custom policy "IMA_POLICY_PATH"."); +#endif /* HAVE_IMA */ + return 0; +} diff --git a/src/libcore/ima-setup.h b/src/libcore/ima-setup.h new file mode 100644 index 0000000000..472b58cb00 --- /dev/null +++ b/src/libcore/ima-setup.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright (C) 2012 Roberto Sassu - Politecnico di Torino, Italy + TORSEC group — http://security.polito.it + + 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 . +***/ + +int ima_setup(void); diff --git a/src/libcore/job.c b/src/libcore/job.c new file mode 100644 index 0000000000..42fdcb988a --- /dev/null +++ b/src/libcore/job.c @@ -0,0 +1,1259 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include + +#include "alloc-util.h" +#include "async.h" +#include "dbus-job.h" +#include "dbus.h" +#include "escape.h" +#include "job.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "set.h" +#include "special.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "unit.h" +#include "virt.h" + +Job* job_new_raw(Unit *unit) { + Job *j; + + /* used for deserialization */ + + assert(unit); + + j = new0(Job, 1); + if (!j) + return NULL; + + j->manager = unit->manager; + j->unit = unit; + j->type = _JOB_TYPE_INVALID; + + return j; +} + +Job* job_new(Unit *unit, JobType type) { + Job *j; + + assert(type < _JOB_TYPE_MAX); + + j = job_new_raw(unit); + if (!j) + return NULL; + + j->id = j->manager->current_job_id++; + j->type = type; + + /* We don't link it here, that's what job_dependency() is for */ + + return j; +} + +void job_free(Job *j) { + assert(j); + assert(!j->installed); + assert(!j->transaction_prev); + assert(!j->transaction_next); + assert(!j->subject_list); + assert(!j->object_list); + + if (j->in_run_queue) + LIST_REMOVE(run_queue, j->manager->run_queue, j); + + if (j->in_dbus_queue) + LIST_REMOVE(dbus_queue, j->manager->dbus_job_queue, j); + + sd_event_source_unref(j->timer_event_source); + + sd_bus_track_unref(j->clients); + strv_free(j->deserialized_clients); + + free(j); +} + +static void job_set_state(Job *j, JobState state) { + assert(j); + assert(state >= 0); + assert(state < _JOB_STATE_MAX); + + if (j->state == state) + return; + + j->state = state; + + if (!j->installed) + return; + + if (j->state == JOB_RUNNING) + j->unit->manager->n_running_jobs++; + else { + assert(j->state == JOB_WAITING); + assert(j->unit->manager->n_running_jobs > 0); + + j->unit->manager->n_running_jobs--; + + if (j->unit->manager->n_running_jobs <= 0) + j->unit->manager->jobs_in_progress_event_source = sd_event_source_unref(j->unit->manager->jobs_in_progress_event_source); + } +} + +void job_uninstall(Job *j) { + Job **pj; + + assert(j->installed); + + job_set_state(j, JOB_WAITING); + + pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job; + assert(*pj == j); + + /* Detach from next 'bigger' objects */ + + /* daemon-reload should be transparent to job observers */ + if (!MANAGER_IS_RELOADING(j->manager)) + bus_job_send_removed_signal(j); + + *pj = NULL; + + unit_add_to_gc_queue(j->unit); + + hashmap_remove(j->manager->jobs, UINT32_TO_PTR(j->id)); + j->installed = false; +} + +static bool job_type_allows_late_merge(JobType t) { + /* Tells whether it is OK to merge a job of type 't' with an already + * running job. + * Reloads cannot be merged this way. Think of the sequence: + * 1. Reload of a daemon is in progress; the daemon has already loaded + * its config file, but hasn't completed the reload operation yet. + * 2. Edit foo's config file. + * 3. Trigger another reload to have the daemon use the new config. + * Should the second reload job be merged into the first one, the daemon + * would not know about the new config. + * JOB_RESTART jobs on the other hand can be merged, because they get + * patched into JOB_START after stopping the unit. So if we see a + * JOB_RESTART running, it means the unit hasn't stopped yet and at + * this time the merge is still allowed. */ + return t != JOB_RELOAD; +} + +static void job_merge_into_installed(Job *j, Job *other) { + assert(j->installed); + assert(j->unit == other->unit); + + if (j->type != JOB_NOP) + job_type_merge_and_collapse(&j->type, other->type, j->unit); + else + assert(other->type == JOB_NOP); + + j->irreversible = j->irreversible || other->irreversible; + j->ignore_order = j->ignore_order || other->ignore_order; +} + +Job* job_install(Job *j) { + Job **pj; + Job *uj; + + assert(!j->installed); + assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION); + assert(j->state == JOB_WAITING); + + pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job; + uj = *pj; + + if (uj) { + if (job_type_is_conflicting(uj->type, j->type)) + job_finish_and_invalidate(uj, JOB_CANCELED, false, false); + else { + /* not conflicting, i.e. mergeable */ + + if (uj->state == JOB_WAITING || + (job_type_allows_late_merge(j->type) && job_type_is_superset(uj->type, j->type))) { + job_merge_into_installed(uj, j); + log_unit_debug(uj->unit, + "Merged into installed job %s/%s as %u", + uj->unit->id, job_type_to_string(uj->type), (unsigned) uj->id); + return uj; + } else { + /* already running and not safe to merge into */ + /* Patch uj to become a merged job and re-run it. */ + /* XXX It should be safer to queue j to run after uj finishes, but it is + * not currently possible to have more than one installed job per unit. */ + job_merge_into_installed(uj, j); + log_unit_debug(uj->unit, + "Merged into running job, re-running: %s/%s as %u", + uj->unit->id, job_type_to_string(uj->type), (unsigned) uj->id); + + job_set_state(uj, JOB_WAITING); + return uj; + } + } + } + + /* Install the job */ + *pj = j; + j->installed = true; + + j->manager->n_installed_jobs++; + log_unit_debug(j->unit, + "Installed new job %s/%s as %u", + j->unit->id, job_type_to_string(j->type), (unsigned) j->id); + return j; +} + +int job_install_deserialized(Job *j) { + Job **pj; + + assert(!j->installed); + + if (j->type < 0 || j->type >= _JOB_TYPE_MAX_IN_TRANSACTION) { + log_debug("Invalid job type %s in deserialization.", strna(job_type_to_string(j->type))); + return -EINVAL; + } + + pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job; + if (*pj) { + log_unit_debug(j->unit, "Unit already has a job installed. Not installing deserialized job."); + return -EEXIST; + } + + *pj = j; + j->installed = true; + + if (j->state == JOB_RUNNING) + j->unit->manager->n_running_jobs++; + + log_unit_debug(j->unit, + "Reinstalled deserialized job %s/%s as %u", + j->unit->id, job_type_to_string(j->type), (unsigned) j->id); + return 0; +} + +JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts) { + JobDependency *l; + + assert(object); + + /* Adds a new job link, which encodes that the 'subject' job + * needs the 'object' job in some way. If 'subject' is NULL + * this means the 'anchor' job (i.e. the one the user + * explicitly asked for) is the requester. */ + + if (!(l = new0(JobDependency, 1))) + return NULL; + + l->subject = subject; + l->object = object; + l->matters = matters; + l->conflicts = conflicts; + + if (subject) + LIST_PREPEND(subject, subject->subject_list, l); + + LIST_PREPEND(object, object->object_list, l); + + return l; +} + +void job_dependency_free(JobDependency *l) { + assert(l); + + if (l->subject) + LIST_REMOVE(subject, l->subject->subject_list, l); + + LIST_REMOVE(object, l->object->object_list, l); + + free(l); +} + +void job_dump(Job *j, FILE*f, const char *prefix) { + assert(j); + assert(f); + + if (!prefix) + prefix = ""; + + fprintf(f, + "%s-> Job %u:\n" + "%s\tAction: %s -> %s\n" + "%s\tState: %s\n" + "%s\tIrreversible: %s\n", + prefix, j->id, + prefix, j->unit->id, job_type_to_string(j->type), + prefix, job_state_to_string(j->state), + prefix, yes_no(j->irreversible)); +} + +/* + * Merging is commutative, so imagine the matrix as symmetric. We store only + * its lower triangle to avoid duplication. We don't store the main diagonal, + * because A merged with A is simply A. + * + * If the resulting type is collapsed immediately afterwards (to get rid of + * the JOB_RELOAD_OR_START, which lies outside the lookup function's domain), + * the following properties hold: + * + * Merging is associative! A merged with B, and then merged with C is the same + * as A merged with the result of B merged with C. + * + * Mergeability is transitive! If A can be merged with B and B with C then + * A also with C. + * + * Also, if A merged with B cannot be merged with C, then either A or B cannot + * be merged with C either. + */ +static const JobType job_merging_table[] = { +/* What \ With * JOB_START JOB_VERIFY_ACTIVE JOB_STOP JOB_RELOAD */ +/*********************************************************************************/ +/*JOB_START */ +/*JOB_VERIFY_ACTIVE */ JOB_START, +/*JOB_STOP */ -1, -1, +/*JOB_RELOAD */ JOB_RELOAD_OR_START, JOB_RELOAD, -1, +/*JOB_RESTART */ JOB_RESTART, JOB_RESTART, -1, JOB_RESTART, +}; + +JobType job_type_lookup_merge(JobType a, JobType b) { + assert_cc(ELEMENTSOF(job_merging_table) == _JOB_TYPE_MAX_MERGING * (_JOB_TYPE_MAX_MERGING - 1) / 2); + assert(a >= 0 && a < _JOB_TYPE_MAX_MERGING); + assert(b >= 0 && b < _JOB_TYPE_MAX_MERGING); + + if (a == b) + return a; + + if (a < b) { + JobType tmp = a; + a = b; + b = tmp; + } + + return job_merging_table[(a - 1) * a / 2 + b]; +} + +bool job_type_is_redundant(JobType a, UnitActiveState b) { + switch (a) { + + case JOB_START: + return + b == UNIT_ACTIVE || + b == UNIT_RELOADING; + + case JOB_STOP: + return + b == UNIT_INACTIVE || + b == UNIT_FAILED; + + case JOB_VERIFY_ACTIVE: + return + b == UNIT_ACTIVE || + b == UNIT_RELOADING; + + case JOB_RELOAD: + return + b == UNIT_RELOADING; + + case JOB_RESTART: + return + b == UNIT_ACTIVATING; + + case JOB_NOP: + return true; + + default: + assert_not_reached("Invalid job type"); + } +} + +JobType job_type_collapse(JobType t, Unit *u) { + UnitActiveState s; + + switch (t) { + + case JOB_TRY_RESTART: + s = unit_active_state(u); + if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s)) + return JOB_NOP; + + return JOB_RESTART; + + case JOB_TRY_RELOAD: + s = unit_active_state(u); + if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s)) + return JOB_NOP; + + return JOB_RELOAD; + + case JOB_RELOAD_OR_START: + s = unit_active_state(u); + if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s)) + return JOB_START; + + return JOB_RELOAD; + + default: + return t; + } +} + +int job_type_merge_and_collapse(JobType *a, JobType b, Unit *u) { + JobType t; + + t = job_type_lookup_merge(*a, b); + if (t < 0) + return -EEXIST; + + *a = job_type_collapse(t, u); + return 0; +} + +static bool job_is_runnable(Job *j) { + Iterator i; + Unit *other; + + assert(j); + assert(j->installed); + + /* Checks whether there is any job running for the units this + * job needs to be running after (in the case of a 'positive' + * job type) or before (in the case of a 'negative' job + * type. */ + + /* Note that unit types have a say in what is runnable, + * too. For example, if they return -EAGAIN from + * unit_start() they can indicate they are not + * runnable yet. */ + + /* First check if there is an override */ + if (j->ignore_order) + return true; + + if (j->type == JOB_NOP) + return true; + + if (j->type == JOB_START || + j->type == JOB_VERIFY_ACTIVE || + j->type == JOB_RELOAD) { + + /* Immediate result is that the job is or might be + * started. In this case let's wait for the + * dependencies, regardless whether they are + * starting or stopping something. */ + + SET_FOREACH(other, j->unit->dependencies[UNIT_AFTER], i) + if (other->job) + return false; + } + + /* Also, if something else is being stopped and we should + * change state after it, then let's wait. */ + + SET_FOREACH(other, j->unit->dependencies[UNIT_BEFORE], i) + if (other->job && + (other->job->type == JOB_STOP || + other->job->type == JOB_RESTART)) + return false; + + /* This means that for a service a and a service b where b + * shall be started after a: + * + * start a + start b → 1st step start a, 2nd step start b + * start a + stop b → 1st step stop b, 2nd step start a + * stop a + start b → 1st step stop a, 2nd step start b + * stop a + stop b → 1st step stop b, 2nd step stop a + * + * This has the side effect that restarts are properly + * synchronized too. */ + + return true; +} + +static void job_change_type(Job *j, JobType newtype) { + assert(j); + + log_unit_debug(j->unit, + "Converting job %s/%s -> %s/%s", + j->unit->id, job_type_to_string(j->type), + j->unit->id, job_type_to_string(newtype)); + + j->type = newtype; +} + +static int job_perform_on_unit(Job **j) { + uint32_t id; + Manager *m; + JobType t; + Unit *u; + int r; + + /* While we execute this operation the job might go away (for + * example: because it finishes immediately or is replaced by + * a new, conflicting job.) To make sure we don't access a + * freed job later on we store the id here, so that we can + * verify the job is still valid. */ + + assert(j); + assert(*j); + + m = (*j)->manager; + u = (*j)->unit; + t = (*j)->type; + id = (*j)->id; + + switch (t) { + case JOB_START: + r = unit_start(u); + break; + + case JOB_RESTART: + t = JOB_STOP; + /* fall through */ + case JOB_STOP: + r = unit_stop(u); + break; + + case JOB_RELOAD: + r = unit_reload(u); + break; + + default: + assert_not_reached("Invalid job type"); + } + + /* Log if the job still exists and the start/stop/reload function + * actually did something. */ + *j = manager_get_job(m, id); + if (*j && r > 0) + unit_status_emit_starting_stopping_reloading(u, t); + + return r; +} + +int job_run_and_invalidate(Job *j) { + int r; + + assert(j); + assert(j->installed); + assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION); + assert(j->in_run_queue); + + LIST_REMOVE(run_queue, j->manager->run_queue, j); + j->in_run_queue = false; + + if (j->state != JOB_WAITING) + return 0; + + if (!job_is_runnable(j)) + return -EAGAIN; + + job_set_state(j, JOB_RUNNING); + job_add_to_dbus_queue(j); + + + switch (j->type) { + + case JOB_VERIFY_ACTIVE: { + UnitActiveState t = unit_active_state(j->unit); + if (UNIT_IS_ACTIVE_OR_RELOADING(t)) + r = -EALREADY; + else if (t == UNIT_ACTIVATING) + r = -EAGAIN; + else + r = -EBADR; + break; + } + + case JOB_START: + case JOB_STOP: + case JOB_RESTART: + r = job_perform_on_unit(&j); + + /* If the unit type does not support starting/stopping, + * then simply wait. */ + if (r == -EBADR) + r = 0; + break; + + case JOB_RELOAD: + r = job_perform_on_unit(&j); + break; + + case JOB_NOP: + r = -EALREADY; + break; + + default: + assert_not_reached("Unknown job type"); + } + + if (j) { + if (r == -EALREADY) + r = job_finish_and_invalidate(j, JOB_DONE, true, true); + else if (r == -EBADR) + r = job_finish_and_invalidate(j, JOB_SKIPPED, true, false); + else if (r == -ENOEXEC) + r = job_finish_and_invalidate(j, JOB_INVALID, true, false); + else if (r == -EPROTO) + r = job_finish_and_invalidate(j, JOB_ASSERT, true, false); + else if (r == -EOPNOTSUPP) + r = job_finish_and_invalidate(j, JOB_UNSUPPORTED, true, false); + else if (r == -EAGAIN) + job_set_state(j, JOB_WAITING); + else if (r < 0) + r = job_finish_and_invalidate(j, JOB_FAILED, true, false); + } + + return r; +} + +_pure_ static const char *job_get_status_message_format(Unit *u, JobType t, JobResult result) { + + static const char *const generic_finished_start_job[_JOB_RESULT_MAX] = { + [JOB_DONE] = "Started %s.", + [JOB_TIMEOUT] = "Timed out starting %s.", + [JOB_FAILED] = "Failed to start %s.", + [JOB_DEPENDENCY] = "Dependency failed for %s.", + [JOB_ASSERT] = "Assertion failed for %s.", + [JOB_UNSUPPORTED] = "Starting of %s not supported.", + }; + static const char *const generic_finished_stop_job[_JOB_RESULT_MAX] = { + [JOB_DONE] = "Stopped %s.", + [JOB_FAILED] = "Stopped (with error) %s.", + [JOB_TIMEOUT] = "Timed out stopping %s.", + }; + static const char *const generic_finished_reload_job[_JOB_RESULT_MAX] = { + [JOB_DONE] = "Reloaded %s.", + [JOB_FAILED] = "Reload failed for %s.", + [JOB_TIMEOUT] = "Timed out reloading %s.", + }; + /* When verify-active detects the unit is inactive, report it. + * Most likely a DEPEND warning from a requisiting unit will + * occur next and it's nice to see what was requisited. */ + static const char *const generic_finished_verify_active_job[_JOB_RESULT_MAX] = { + [JOB_SKIPPED] = "%s is not active.", + }; + + const UnitStatusMessageFormats *format_table; + const char *format; + + assert(u); + assert(t >= 0); + assert(t < _JOB_TYPE_MAX); + + if (IN_SET(t, JOB_START, JOB_STOP, JOB_RESTART)) { + format_table = &UNIT_VTABLE(u)->status_message_formats; + if (format_table) { + format = t == JOB_START ? format_table->finished_start_job[result] : + format_table->finished_stop_job[result]; + if (format) + return format; + } + } + + /* Return generic strings */ + if (t == JOB_START) + return generic_finished_start_job[result]; + else if (t == JOB_STOP || t == JOB_RESTART) + return generic_finished_stop_job[result]; + else if (t == JOB_RELOAD) + return generic_finished_reload_job[result]; + else if (t == JOB_VERIFY_ACTIVE) + return generic_finished_verify_active_job[result]; + + return NULL; +} + +static void job_print_status_message(Unit *u, JobType t, JobResult result) { + static struct { + const char *color, *word; + } const statuses[_JOB_RESULT_MAX] = { + [JOB_DONE] = {ANSI_GREEN, " OK "}, + [JOB_TIMEOUT] = {ANSI_HIGHLIGHT_RED, " TIME "}, + [JOB_FAILED] = {ANSI_HIGHLIGHT_RED, "FAILED"}, + [JOB_DEPENDENCY] = {ANSI_HIGHLIGHT_YELLOW, "DEPEND"}, + [JOB_SKIPPED] = {ANSI_HIGHLIGHT, " INFO "}, + [JOB_ASSERT] = {ANSI_HIGHLIGHT_YELLOW, "ASSERT"}, + [JOB_UNSUPPORTED] = {ANSI_HIGHLIGHT_YELLOW, "UNSUPP"}, + }; + + const char *format; + const char *status; + + assert(u); + assert(t >= 0); + assert(t < _JOB_TYPE_MAX); + + /* Reload status messages have traditionally not been printed to console. */ + if (t == JOB_RELOAD) + return; + + format = job_get_status_message_format(u, t, result); + if (!format) + return; + + if (log_get_show_color()) + status = strjoina(statuses[result].color, statuses[result].word, ANSI_NORMAL); + else + status = statuses[result].word; + + if (result != JOB_DONE) + manager_flip_auto_status(u->manager, true); + + DISABLE_WARNING_FORMAT_NONLITERAL; + unit_status_printf(u, status, format); + REENABLE_WARNING; + + if (t == JOB_START && result == JOB_FAILED) { + _cleanup_free_ char *quoted; + + quoted = shell_maybe_quote(u->id); + manager_status_printf(u->manager, STATUS_TYPE_NORMAL, NULL, "See 'systemctl status %s' for details.", strna(quoted)); + } +} + +static void job_log_status_message(Unit *u, JobType t, JobResult result) { + const char *format; + char buf[LINE_MAX]; + sd_id128_t mid; + static const int job_result_log_level[_JOB_RESULT_MAX] = { + [JOB_DONE] = LOG_INFO, + [JOB_CANCELED] = LOG_INFO, + [JOB_TIMEOUT] = LOG_ERR, + [JOB_FAILED] = LOG_ERR, + [JOB_DEPENDENCY] = LOG_WARNING, + [JOB_SKIPPED] = LOG_NOTICE, + [JOB_INVALID] = LOG_INFO, + [JOB_ASSERT] = LOG_WARNING, + [JOB_UNSUPPORTED] = LOG_WARNING, + }; + + assert(u); + assert(t >= 0); + assert(t < _JOB_TYPE_MAX); + + /* Skip this if it goes to the console. since we already print + * to the console anyway... */ + + if (log_on_console()) + return; + + format = job_get_status_message_format(u, t, result); + if (!format) + return; + + DISABLE_WARNING_FORMAT_NONLITERAL; + xsprintf(buf, format, unit_description(u)); + REENABLE_WARNING; + + switch (t) { + + case JOB_START: + mid = result == JOB_DONE ? SD_MESSAGE_UNIT_STARTED : SD_MESSAGE_UNIT_FAILED; + break; + + case JOB_RELOAD: + mid = SD_MESSAGE_UNIT_RELOADED; + break; + + case JOB_STOP: + case JOB_RESTART: + mid = SD_MESSAGE_UNIT_STOPPED; + break; + + default: + log_struct(job_result_log_level[result], + LOG_UNIT_ID(u), + LOG_MESSAGE("%s", buf), + "RESULT=%s", job_result_to_string(result), + NULL); + return; + } + + log_struct(job_result_log_level[result], + LOG_MESSAGE_ID(mid), + LOG_UNIT_ID(u), + LOG_MESSAGE("%s", buf), + "RESULT=%s", job_result_to_string(result), + NULL); +} + +static void job_emit_status_message(Unit *u, JobType t, JobResult result) { + + /* No message if the job did not actually do anything due to failed condition. */ + if (t == JOB_START && result == JOB_DONE && !u->condition_result) + return; + + job_log_status_message(u, t, result); + job_print_status_message(u, t, result); +} + +static void job_fail_dependencies(Unit *u, UnitDependency d) { + Unit *other; + Iterator i; + + assert(u); + + SET_FOREACH(other, u->dependencies[d], i) { + Job *j = other->job; + + if (!j) + continue; + if (!IN_SET(j->type, JOB_START, JOB_VERIFY_ACTIVE)) + continue; + + job_finish_and_invalidate(j, JOB_DEPENDENCY, true, false); + } +} + +int job_finish_and_invalidate(Job *j, JobResult result, bool recursive, bool already) { + Unit *u; + Unit *other; + JobType t; + Iterator i; + + assert(j); + assert(j->installed); + assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION); + + u = j->unit; + t = j->type; + + j->result = result; + + log_unit_debug(u, "Job %s/%s finished, result=%s", u->id, job_type_to_string(t), job_result_to_string(result)); + + /* If this job did nothing to respective unit we don't log the status message */ + if (!already) + job_emit_status_message(u, t, result); + + job_add_to_dbus_queue(j); + + /* Patch restart jobs so that they become normal start jobs */ + if (result == JOB_DONE && t == JOB_RESTART) { + + job_change_type(j, JOB_START); + job_set_state(j, JOB_WAITING); + + job_add_to_run_queue(j); + + goto finish; + } + + if (result == JOB_FAILED || result == JOB_INVALID) + j->manager->n_failed_jobs++; + + job_uninstall(j); + job_free(j); + + /* Fail depending jobs on failure */ + if (result != JOB_DONE && recursive) { + if (IN_SET(t, JOB_START, JOB_VERIFY_ACTIVE)) { + job_fail_dependencies(u, UNIT_REQUIRED_BY); + job_fail_dependencies(u, UNIT_REQUISITE_OF); + job_fail_dependencies(u, UNIT_BOUND_BY); + } else if (t == JOB_STOP) + job_fail_dependencies(u, UNIT_CONFLICTED_BY); + } + + /* Trigger OnFailure dependencies that are not generated by + * the unit itself. We don't treat JOB_CANCELED as failure in + * this context. And JOB_FAILURE is already handled by the + * unit itself. */ + if (result == JOB_TIMEOUT || result == JOB_DEPENDENCY) { + log_struct(LOG_NOTICE, + "JOB_TYPE=%s", job_type_to_string(t), + "JOB_RESULT=%s", job_result_to_string(result), + LOG_UNIT_ID(u), + LOG_UNIT_MESSAGE(u, "Job %s/%s failed with result '%s'.", + u->id, + job_type_to_string(t), + job_result_to_string(result)), + NULL); + + unit_start_on_failure(u); + } + + unit_trigger_notify(u); + +finish: + /* Try to start the next jobs that can be started */ + SET_FOREACH(other, u->dependencies[UNIT_AFTER], i) + if (other->job) + job_add_to_run_queue(other->job); + SET_FOREACH(other, u->dependencies[UNIT_BEFORE], i) + if (other->job) + job_add_to_run_queue(other->job); + + manager_check_finished(u->manager); + + return 0; +} + +static int job_dispatch_timer(sd_event_source *s, uint64_t monotonic, void *userdata) { + Job *j = userdata; + Unit *u; + + assert(j); + assert(s == j->timer_event_source); + + log_unit_warning(j->unit, "Job %s/%s timed out.", j->unit->id, job_type_to_string(j->type)); + + u = j->unit; + job_finish_and_invalidate(j, JOB_TIMEOUT, true, false); + + failure_action(u->manager, u->job_timeout_action, u->job_timeout_reboot_arg); + + return 0; +} + +int job_start_timer(Job *j) { + int r; + + if (j->timer_event_source) + return 0; + + j->begin_usec = now(CLOCK_MONOTONIC); + + if (j->unit->job_timeout == USEC_INFINITY) + return 0; + + r = sd_event_add_time( + j->manager->event, + &j->timer_event_source, + CLOCK_MONOTONIC, + usec_add(j->begin_usec, j->unit->job_timeout), 0, + job_dispatch_timer, j); + if (r < 0) + return r; + + (void) sd_event_source_set_description(j->timer_event_source, "job-start"); + + return 0; +} + +void job_add_to_run_queue(Job *j) { + assert(j); + assert(j->installed); + + if (j->in_run_queue) + return; + + if (!j->manager->run_queue) + sd_event_source_set_enabled(j->manager->run_queue_event_source, SD_EVENT_ONESHOT); + + LIST_PREPEND(run_queue, j->manager->run_queue, j); + j->in_run_queue = true; +} + +void job_add_to_dbus_queue(Job *j) { + assert(j); + assert(j->installed); + + if (j->in_dbus_queue) + return; + + /* We don't check if anybody is subscribed here, since this + * job might just have been created and not yet assigned to a + * connection/client. */ + + LIST_PREPEND(dbus_queue, j->manager->dbus_job_queue, j); + j->in_dbus_queue = true; +} + +char *job_dbus_path(Job *j) { + char *p; + + assert(j); + + if (asprintf(&p, "/org/freedesktop/systemd1/job/%"PRIu32, j->id) < 0) + return NULL; + + return p; +} + +int job_serialize(Job *j, FILE *f, FDSet *fds) { + fprintf(f, "job-id=%u\n", j->id); + fprintf(f, "job-type=%s\n", job_type_to_string(j->type)); + fprintf(f, "job-state=%s\n", job_state_to_string(j->state)); + fprintf(f, "job-irreversible=%s\n", yes_no(j->irreversible)); + fprintf(f, "job-sent-dbus-new-signal=%s\n", yes_no(j->sent_dbus_new_signal)); + fprintf(f, "job-ignore-order=%s\n", yes_no(j->ignore_order)); + + if (j->begin_usec > 0) + fprintf(f, "job-begin="USEC_FMT"\n", j->begin_usec); + + bus_track_serialize(j->clients, f); + + /* End marker */ + fputc('\n', f); + return 0; +} + +int job_deserialize(Job *j, FILE *f, FDSet *fds) { + assert(j); + + for (;;) { + char line[LINE_MAX], *l, *v; + size_t k; + + if (!fgets(line, sizeof(line), f)) { + if (feof(f)) + return 0; + return -errno; + } + + char_array_0(line); + l = strstrip(line); + + /* End marker */ + if (l[0] == 0) + return 0; + + k = strcspn(l, "="); + + if (l[k] == '=') { + l[k] = 0; + v = l+k+1; + } else + v = l+k; + + if (streq(l, "job-id")) { + + if (safe_atou32(v, &j->id) < 0) + log_debug("Failed to parse job id value %s", v); + + } else if (streq(l, "job-type")) { + JobType t; + + t = job_type_from_string(v); + if (t < 0) + log_debug("Failed to parse job type %s", v); + else if (t >= _JOB_TYPE_MAX_IN_TRANSACTION) + log_debug("Cannot deserialize job of type %s", v); + else + j->type = t; + + } else if (streq(l, "job-state")) { + JobState s; + + s = job_state_from_string(v); + if (s < 0) + log_debug("Failed to parse job state %s", v); + else + job_set_state(j, s); + + } else if (streq(l, "job-irreversible")) { + int b; + + b = parse_boolean(v); + if (b < 0) + log_debug("Failed to parse job irreversible flag %s", v); + else + j->irreversible = j->irreversible || b; + + } else if (streq(l, "job-sent-dbus-new-signal")) { + int b; + + b = parse_boolean(v); + if (b < 0) + log_debug("Failed to parse job sent_dbus_new_signal flag %s", v); + else + j->sent_dbus_new_signal = j->sent_dbus_new_signal || b; + + } else if (streq(l, "job-ignore-order")) { + int b; + + b = parse_boolean(v); + if (b < 0) + log_debug("Failed to parse job ignore_order flag %s", v); + else + j->ignore_order = j->ignore_order || b; + + } else if (streq(l, "job-begin")) { + unsigned long long ull; + + if (sscanf(v, "%llu", &ull) != 1) + log_debug("Failed to parse job-begin value %s", v); + else + j->begin_usec = ull; + + } else if (streq(l, "subscribed")) { + + if (strv_extend(&j->deserialized_clients, v) < 0) + return log_oom(); + } + } +} + +int job_coldplug(Job *j) { + int r; + + assert(j); + + /* After deserialization is complete and the bus connection + * set up again, let's start watching our subscribers again */ + r = bus_track_coldplug(j->manager, &j->clients, &j->deserialized_clients); + if (r < 0) + return r; + + if (j->state == JOB_WAITING) + job_add_to_run_queue(j); + + if (j->begin_usec == 0 || j->unit->job_timeout == USEC_INFINITY) + return 0; + + j->timer_event_source = sd_event_source_unref(j->timer_event_source); + + r = sd_event_add_time( + j->manager->event, + &j->timer_event_source, + CLOCK_MONOTONIC, + usec_add(j->begin_usec, j->unit->job_timeout), 0, + job_dispatch_timer, j); + if (r < 0) + log_debug_errno(r, "Failed to restart timeout for job: %m"); + + (void) sd_event_source_set_description(j->timer_event_source, "job-timeout"); + + return r; +} + +void job_shutdown_magic(Job *j) { + assert(j); + + /* The shutdown target gets some special treatment here: we + * tell the kernel to begin with flushing its disk caches, to + * optimize shutdown time a bit. Ideally we wouldn't hardcode + * this magic into PID 1. However all other processes aren't + * options either since they'd exit much sooner than PID 1 and + * asynchronous sync() would cause their exit to be + * delayed. */ + + if (j->type != JOB_START) + return; + + if (!MANAGER_IS_SYSTEM(j->unit->manager)) + return; + + if (!unit_has_name(j->unit, SPECIAL_SHUTDOWN_TARGET)) + return; + + /* In case messages on console has been disabled on boot */ + j->unit->manager->no_console_output = false; + + if (detect_container() > 0) + return; + + asynchronous_sync(); +} + +int job_get_timeout(Job *j, usec_t *timeout) { + usec_t x = USEC_INFINITY, y = USEC_INFINITY; + Unit *u = j->unit; + int r; + + assert(u); + + if (j->timer_event_source) { + r = sd_event_source_get_time(j->timer_event_source, &x); + if (r < 0) + return r; + } + + if (UNIT_VTABLE(u)->get_timeout) { + r = UNIT_VTABLE(u)->get_timeout(u, &y); + if (r < 0) + return r; + } + + if (x == USEC_INFINITY && y == USEC_INFINITY) + return 0; + + *timeout = MIN(x, y); + return 1; +} + +static const char* const job_state_table[_JOB_STATE_MAX] = { + [JOB_WAITING] = "waiting", + [JOB_RUNNING] = "running" +}; + +DEFINE_STRING_TABLE_LOOKUP(job_state, JobState); + +static const char* const job_type_table[_JOB_TYPE_MAX] = { + [JOB_START] = "start", + [JOB_VERIFY_ACTIVE] = "verify-active", + [JOB_STOP] = "stop", + [JOB_RELOAD] = "reload", + [JOB_RELOAD_OR_START] = "reload-or-start", + [JOB_RESTART] = "restart", + [JOB_TRY_RESTART] = "try-restart", + [JOB_TRY_RELOAD] = "try-reload", + [JOB_NOP] = "nop", +}; + +DEFINE_STRING_TABLE_LOOKUP(job_type, JobType); + +static const char* const job_mode_table[_JOB_MODE_MAX] = { + [JOB_FAIL] = "fail", + [JOB_REPLACE] = "replace", + [JOB_REPLACE_IRREVERSIBLY] = "replace-irreversibly", + [JOB_ISOLATE] = "isolate", + [JOB_FLUSH] = "flush", + [JOB_IGNORE_DEPENDENCIES] = "ignore-dependencies", + [JOB_IGNORE_REQUIREMENTS] = "ignore-requirements", +}; + +DEFINE_STRING_TABLE_LOOKUP(job_mode, JobMode); + +static const char* const job_result_table[_JOB_RESULT_MAX] = { + [JOB_DONE] = "done", + [JOB_CANCELED] = "canceled", + [JOB_TIMEOUT] = "timeout", + [JOB_FAILED] = "failed", + [JOB_DEPENDENCY] = "dependency", + [JOB_SKIPPED] = "skipped", + [JOB_INVALID] = "invalid", + [JOB_ASSERT] = "assert", + [JOB_UNSUPPORTED] = "unsupported", +}; + +DEFINE_STRING_TABLE_LOOKUP(job_result, JobResult); + +const char* job_type_to_access_method(JobType t) { + assert(t >= 0); + assert(t < _JOB_TYPE_MAX); + + if (IN_SET(t, JOB_START, JOB_RESTART, JOB_TRY_RESTART)) + return "start"; + else if (t == JOB_STOP) + return "stop"; + else + return "reload"; +} diff --git a/src/libcore/job.h b/src/libcore/job.h new file mode 100644 index 0000000000..68c2089b91 --- /dev/null +++ b/src/libcore/job.h @@ -0,0 +1,242 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 + +#include "list.h" +#include "unit-name.h" + +typedef struct Job Job; +typedef struct JobDependency JobDependency; +typedef enum JobType JobType; +typedef enum JobState JobState; +typedef enum JobMode JobMode; +typedef enum JobResult JobResult; + +/* Be careful when changing the job types! Adjust job_merging_table[] accordingly! */ +enum JobType { + JOB_START, /* if a unit does not support being started, we'll just wait until it becomes active */ + JOB_VERIFY_ACTIVE, + + JOB_STOP, + + JOB_RELOAD, /* if running, reload */ + + /* Note that restarts are first treated like JOB_STOP, but + * then instead of finishing are patched to become + * JOB_START. */ + JOB_RESTART, /* If running, stop. Then start unconditionally. */ + + _JOB_TYPE_MAX_MERGING, + + /* JOB_NOP can enter into a transaction, but as it won't pull in + * any dependencies and it uses the special 'nop_job' slot in Unit, + * it won't have to merge with anything (except possibly into another + * JOB_NOP, previously installed). JOB_NOP is special-cased in + * job_type_is_*() functions so that the transaction can be + * activated. */ + JOB_NOP = _JOB_TYPE_MAX_MERGING, /* do nothing */ + + _JOB_TYPE_MAX_IN_TRANSACTION, + + /* JOB_TRY_RESTART can never appear in a transaction, because + * it always collapses into JOB_RESTART or JOB_NOP before entering. + * Thus we never need to merge it with anything. */ + JOB_TRY_RESTART = _JOB_TYPE_MAX_IN_TRANSACTION, /* if running, stop and then start */ + + /* Similar to JOB_TRY_RESTART but collapses to JOB_RELOAD or JOB_NOP */ + JOB_TRY_RELOAD, + + /* JOB_RELOAD_OR_START won't enter into a transaction and cannot result + * from transaction merging (there's no way for JOB_RELOAD and + * JOB_START to meet in one transaction). It can result from a merge + * during job installation, but then it will immediately collapse into + * one of the two simpler types. */ + JOB_RELOAD_OR_START, /* if running, reload, otherwise start */ + + _JOB_TYPE_MAX, + _JOB_TYPE_INVALID = -1 +}; + +enum JobState { + JOB_WAITING, + JOB_RUNNING, + _JOB_STATE_MAX, + _JOB_STATE_INVALID = -1 +}; + +enum JobMode { + JOB_FAIL, /* Fail if a conflicting job is already queued */ + JOB_REPLACE, /* Replace an existing conflicting job */ + JOB_REPLACE_IRREVERSIBLY,/* Like JOB_REPLACE + produce irreversible jobs */ + JOB_ISOLATE, /* Start a unit, and stop all others */ + JOB_FLUSH, /* Flush out all other queued jobs when queing this one */ + JOB_IGNORE_DEPENDENCIES, /* Ignore both requirement and ordering dependencies */ + JOB_IGNORE_REQUIREMENTS, /* Ignore requirement dependencies */ + _JOB_MODE_MAX, + _JOB_MODE_INVALID = -1 +}; + +enum JobResult { + JOB_DONE, /* Job completed successfully */ + JOB_CANCELED, /* Job canceled by a conflicting job installation or by explicit cancel request */ + JOB_TIMEOUT, /* Job timeout elapsed */ + JOB_FAILED, /* Job failed */ + JOB_DEPENDENCY, /* A required dependency job did not result in JOB_DONE */ + JOB_SKIPPED, /* Negative result of JOB_VERIFY_ACTIVE */ + JOB_INVALID, /* JOB_RELOAD of inactive unit */ + JOB_ASSERT, /* Couldn't start a unit, because an assert didn't hold */ + JOB_UNSUPPORTED, /* Couldn't start a unit, because the unit type is not supported on the system */ + _JOB_RESULT_MAX, + _JOB_RESULT_INVALID = -1 +}; + +#include "unit.h" + +struct JobDependency { + /* Encodes that the 'subject' job needs the 'object' job in + * some way. This structure is used only while building a transaction. */ + Job *subject; + Job *object; + + LIST_FIELDS(JobDependency, subject); + LIST_FIELDS(JobDependency, object); + + bool matters; + bool conflicts; +}; + +struct Job { + Manager *manager; + Unit *unit; + + LIST_FIELDS(Job, transaction); + LIST_FIELDS(Job, run_queue); + LIST_FIELDS(Job, dbus_queue); + + LIST_HEAD(JobDependency, subject_list); + LIST_HEAD(JobDependency, object_list); + + /* Used for graph algs as a "I have been here" marker */ + Job* marker; + unsigned generation; + + uint32_t id; + + JobType type; + JobState state; + + sd_event_source *timer_event_source; + usec_t begin_usec; + + /* + * This tracks where to send signals, and also which clients + * are allowed to call DBus methods on the job (other than + * root). + * + * There can be more than one client, because of job merging. + */ + sd_bus_track *clients; + char **deserialized_clients; + + JobResult result; + + bool installed:1; + bool in_run_queue:1; + bool matters_to_anchor:1; + bool in_dbus_queue:1; + bool sent_dbus_new_signal:1; + bool ignore_order:1; + bool irreversible:1; +}; + +Job* job_new(Unit *unit, JobType type); +Job* job_new_raw(Unit *unit); +void job_free(Job *job); +Job* job_install(Job *j); +int job_install_deserialized(Job *j); +void job_uninstall(Job *j); +void job_dump(Job *j, FILE*f, const char *prefix); +int job_serialize(Job *j, FILE *f, FDSet *fds); +int job_deserialize(Job *j, FILE *f, FDSet *fds); +int job_coldplug(Job *j); + +JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts); +void job_dependency_free(JobDependency *l); + +int job_merge(Job *j, Job *other); + +JobType job_type_lookup_merge(JobType a, JobType b) _pure_; + +_pure_ static inline bool job_type_is_mergeable(JobType a, JobType b) { + return job_type_lookup_merge(a, b) >= 0; +} + +_pure_ static inline bool job_type_is_conflicting(JobType a, JobType b) { + return a != JOB_NOP && b != JOB_NOP && !job_type_is_mergeable(a, b); +} + +_pure_ static inline bool job_type_is_superset(JobType a, JobType b) { + /* Checks whether operation a is a "superset" of b in its actions */ + if (b == JOB_NOP) + return true; + if (a == JOB_NOP) + return false; + return a == job_type_lookup_merge(a, b); +} + +bool job_type_is_redundant(JobType a, UnitActiveState b) _pure_; + +/* Collapses a state-dependent job type into a simpler type by observing + * the state of the unit which it is going to be applied to. */ +JobType job_type_collapse(JobType t, Unit *u); + +int job_type_merge_and_collapse(JobType *a, JobType b, Unit *u); + +void job_add_to_run_queue(Job *j); +void job_add_to_dbus_queue(Job *j); + +int job_start_timer(Job *j); + +int job_run_and_invalidate(Job *j); +int job_finish_and_invalidate(Job *j, JobResult result, bool recursive, bool already); + +char *job_dbus_path(Job *j); + +void job_shutdown_magic(Job *j); + +int job_get_timeout(Job *j, usec_t *timeout) _pure_; + +const char* job_type_to_string(JobType t) _const_; +JobType job_type_from_string(const char *s) _pure_; + +const char* job_state_to_string(JobState t) _const_; +JobState job_state_from_string(const char *s) _pure_; + +const char* job_mode_to_string(JobMode t) _const_; +JobMode job_mode_from_string(const char *s) _pure_; + +const char* job_result_to_string(JobResult t) _const_; +JobResult job_result_from_string(const char *s) _pure_; + +const char* job_type_to_access_method(JobType t); diff --git a/src/libcore/kill.c b/src/libcore/kill.c new file mode 100644 index 0000000000..6854587d54 --- /dev/null +++ b/src/libcore/kill.c @@ -0,0 +1,68 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 "kill.h" +#include "signal-util.h" +#include "string-table.h" +#include "util.h" + +void kill_context_init(KillContext *c) { + assert(c); + + c->kill_signal = SIGTERM; + c->send_sigkill = true; + c->send_sighup = false; +} + +void kill_context_dump(KillContext *c, FILE *f, const char *prefix) { + assert(c); + + if (!prefix) + prefix = ""; + + fprintf(f, + "%sKillMode: %s\n" + "%sKillSignal: SIG%s\n" + "%sSendSIGKILL: %s\n" + "%sSendSIGHUP: %s\n", + prefix, kill_mode_to_string(c->kill_mode), + prefix, signal_to_string(c->kill_signal), + prefix, yes_no(c->send_sigkill), + prefix, yes_no(c->send_sighup)); +} + +static const char* const kill_mode_table[_KILL_MODE_MAX] = { + [KILL_CONTROL_GROUP] = "control-group", + [KILL_PROCESS] = "process", + [KILL_MIXED] = "mixed", + [KILL_NONE] = "none" +}; + +DEFINE_STRING_TABLE_LOOKUP(kill_mode, KillMode); + +static const char* const kill_who_table[_KILL_WHO_MAX] = { + [KILL_MAIN] = "main", + [KILL_CONTROL] = "control", + [KILL_ALL] = "all", + [KILL_MAIN_FAIL] = "main-fail", + [KILL_CONTROL_FAIL] = "control-fail", + [KILL_ALL_FAIL] = "all-fail" +}; + +DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho); diff --git a/src/libcore/kill.h b/src/libcore/kill.h new file mode 100644 index 0000000000..b3d2056cb0 --- /dev/null +++ b/src/libcore/kill.h @@ -0,0 +1,65 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +typedef struct KillContext KillContext; + +#include +#include + +#include "macro.h" + +typedef enum KillMode { + /* The kill mode is a property of a unit. */ + KILL_CONTROL_GROUP = 0, + KILL_PROCESS, + KILL_MIXED, + KILL_NONE, + _KILL_MODE_MAX, + _KILL_MODE_INVALID = -1 +} KillMode; + +struct KillContext { + KillMode kill_mode; + int kill_signal; + bool send_sigkill; + bool send_sighup; +}; + +typedef enum KillWho { + /* Kill who is a property of an operation */ + KILL_MAIN, + KILL_CONTROL, + KILL_ALL, + KILL_MAIN_FAIL, + KILL_CONTROL_FAIL, + KILL_ALL_FAIL, + _KILL_WHO_MAX, + _KILL_WHO_INVALID = -1 +} KillWho; + +void kill_context_init(KillContext *c); +void kill_context_dump(KillContext *c, FILE *f, const char *prefix); + +const char *kill_mode_to_string(KillMode k) _const_; +KillMode kill_mode_from_string(const char *s) _pure_; + +const char *kill_who_to_string(KillWho k) _const_; +KillWho kill_who_from_string(const char *s) _pure_; diff --git a/src/libcore/killall.c b/src/libcore/killall.c new file mode 100644 index 0000000000..09378f7085 --- /dev/null +++ b/src/libcore/killall.c @@ -0,0 +1,249 @@ +/*** + This file is part of systemd. + + Copyright 2010 ProFUSION embedded systems + + 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 +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "killall.h" +#include "parse-util.h" +#include "process-util.h" +#include "set.h" +#include "string-util.h" +#include "terminal-util.h" +#include "util.h" + +#define TIMEOUT_USEC (10 * USEC_PER_SEC) + +static bool ignore_proc(pid_t pid, bool warn_rootfs) { + _cleanup_fclose_ FILE *f = NULL; + char c; + const char *p; + size_t count; + uid_t uid; + int r; + + /* We are PID 1, let's not commit suicide */ + if (pid == 1) + return true; + + r = get_process_uid(pid, &uid); + if (r < 0) + return true; /* not really, but better safe than sorry */ + + /* Non-root processes otherwise are always subject to be killed */ + if (uid != 0) + return false; + + p = procfs_file_alloca(pid, "cmdline"); + f = fopen(p, "re"); + if (!f) + return true; /* not really, but has the desired effect */ + + count = fread(&c, 1, 1, f); + + /* Kernel threads have an empty cmdline */ + if (count <= 0) + return true; + + /* Processes with argv[0][0] = '@' we ignore from the killing + * spree. + * + * http://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons */ + if (c == '@' && warn_rootfs) { + _cleanup_free_ char *comm = NULL; + + r = pid_from_same_root_fs(pid); + if (r < 0) + return true; + + get_process_comm(pid, &comm); + + if (r) + log_notice("Process " PID_FMT " (%s) has been been marked to be excluded from killing. It is " + "running from the root file system, and thus likely to block re-mounting of the " + "root file system to read-only. Please consider moving it into an initrd file " + "system instead.", pid, strna(comm)); + return true; + } else if (c == '@') + return true; + + return false; +} + +static void wait_for_children(Set *pids, sigset_t *mask) { + usec_t until; + + assert(mask); + + if (set_isempty(pids)) + return; + + until = now(CLOCK_MONOTONIC) + TIMEOUT_USEC; + for (;;) { + struct timespec ts; + int k; + usec_t n; + void *p; + Iterator i; + + /* First, let the kernel inform us about killed + * children. Most processes will probably be our + * children, but some are not (might be our + * grandchildren instead...). */ + for (;;) { + pid_t pid; + + pid = waitpid(-1, NULL, WNOHANG); + if (pid == 0) + break; + if (pid < 0) { + if (errno == ECHILD) + break; + + log_error_errno(errno, "waitpid() failed: %m"); + return; + } + + (void) set_remove(pids, PID_TO_PTR(pid)); + } + + /* Now explicitly check who might be remaining, who + * might not be our child. */ + SET_FOREACH(p, pids, i) { + + /* We misuse getpgid as a check whether a + * process still exists. */ + if (getpgid(PTR_TO_PID(p)) >= 0) + continue; + + if (errno != ESRCH) + continue; + + set_remove(pids, p); + } + + if (set_isempty(pids)) + return; + + n = now(CLOCK_MONOTONIC); + if (n >= until) + return; + + timespec_store(&ts, until - n); + k = sigtimedwait(mask, NULL, &ts); + if (k != SIGCHLD) { + + if (k < 0 && errno != EAGAIN) { + log_error_errno(errno, "sigtimedwait() failed: %m"); + return; + } + + if (k >= 0) + log_warning("sigtimedwait() returned unexpected signal."); + } + } +} + +static int killall(int sig, Set *pids, bool send_sighup) { + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *d; + + dir = opendir("/proc"); + if (!dir) + return -errno; + + while ((d = readdir(dir))) { + pid_t pid; + int r; + + if (d->d_type != DT_DIR && + d->d_type != DT_UNKNOWN) + continue; + + if (parse_pid(d->d_name, &pid) < 0) + continue; + + if (ignore_proc(pid, sig == SIGKILL && !in_initrd())) + continue; + + if (sig == SIGKILL) { + _cleanup_free_ char *s = NULL; + + get_process_comm(pid, &s); + log_notice("Sending SIGKILL to PID "PID_FMT" (%s).", pid, strna(s)); + } + + if (kill(pid, sig) >= 0) { + if (pids) { + r = set_put(pids, PID_TO_PTR(pid)); + if (r < 0) + log_oom(); + } + } else if (errno != ENOENT) + log_warning_errno(errno, "Could not kill %d: %m", pid); + + if (send_sighup) { + /* Optionally, also send a SIGHUP signal, but + only if the process has a controlling + tty. This is useful to allow handling of + shells which ignore SIGTERM but react to + SIGHUP. We do not send this to processes that + have no controlling TTY since we don't want to + trigger reloads of daemon processes. Also we + make sure to only send this after SIGTERM so + that SIGTERM is always first in the queue. */ + + + if (get_ctty_devnr(pid, NULL) >= 0) + kill(pid, SIGHUP); + } + } + + return set_size(pids); +} + +void broadcast_signal(int sig, bool wait_for_exit, bool send_sighup) { + sigset_t mask, oldmask; + _cleanup_set_free_ Set *pids = NULL; + + if (wait_for_exit) + pids = set_new(NULL); + + assert_se(sigemptyset(&mask) == 0); + assert_se(sigaddset(&mask, SIGCHLD) == 0); + assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0); + + if (kill(-1, SIGSTOP) < 0 && errno != ESRCH) + log_warning_errno(errno, "kill(-1, SIGSTOP) failed: %m"); + + killall(sig, pids, send_sighup); + + if (kill(-1, SIGCONT) < 0 && errno != ESRCH) + log_warning_errno(errno, "kill(-1, SIGCONT) failed: %m"); + + if (wait_for_exit) + wait_for_children(pids, &mask); + + assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0); +} diff --git a/src/libcore/killall.h b/src/libcore/killall.h new file mode 100644 index 0000000000..acc2439f00 --- /dev/null +++ b/src/libcore/killall.h @@ -0,0 +1,22 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +void broadcast_signal(int sig, bool wait_for_exit, bool send_sighup); diff --git a/src/libcore/kmod-setup.c b/src/libcore/kmod-setup.c new file mode 100644 index 0000000000..3503db52ed --- /dev/null +++ b/src/libcore/kmod-setup.c @@ -0,0 +1,131 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 + +#ifdef HAVE_KMOD +#include +#endif + +#include "bus-util.h" +#include "capability-util.h" +#include "kmod-setup.h" +#include "macro.h" + +#ifdef HAVE_KMOD +static void systemd_kmod_log( + void *data, + int priority, + const char *file, int line, + const char *fn, + const char *format, + va_list args) { + + /* library logging is enabled at debug only */ + DISABLE_WARNING_FORMAT_NONLITERAL; + log_internalv(LOG_DEBUG, 0, file, line, fn, format, args); + REENABLE_WARNING; +} +#endif + +int kmod_setup(void) { +#ifdef HAVE_KMOD + + static const struct { + const char *module; + const char *path; + bool warn_if_unavailable:1; + bool warn_if_module:1; + bool (*condition_fn)(void); + } kmod_table[] = { + /* auto-loading on use doesn't work before udev is up */ + { "autofs4", "/sys/class/misc/autofs", true, false, NULL }, + + /* early configure of ::1 on the loopback device */ + { "ipv6", "/sys/module/ipv6", false, true, NULL }, + + /* this should never be a module */ + { "unix", "/proc/net/unix", true, true, NULL }, + + /* IPC is needed before we bring up any other services */ + { "kdbus", "/sys/fs/kdbus", false, false, is_kdbus_wanted }, + +#ifdef HAVE_LIBIPTC + /* netfilter is needed by networkd, nspawn among others, and cannot be autoloaded */ + { "ip_tables", "/proc/net/ip_tables_names", false, false, NULL }, +#endif + }; + struct kmod_ctx *ctx = NULL; + unsigned int i; + int r; + + if (have_effective_cap(CAP_SYS_MODULE) == 0) + return 0; + + for (i = 0; i < ELEMENTSOF(kmod_table); i++) { + struct kmod_module *mod; + + if (kmod_table[i].path && access(kmod_table[i].path, F_OK) >= 0) + continue; + + if (kmod_table[i].condition_fn && !kmod_table[i].condition_fn()) + continue; + + if (kmod_table[i].warn_if_module) + log_debug("Your kernel apparently lacks built-in %s support. Might be " + "a good idea to compile it in. We'll now try to work around " + "this by loading the module...", kmod_table[i].module); + + if (!ctx) { + ctx = kmod_new(NULL, NULL); + if (!ctx) + return log_oom(); + + kmod_set_log_fn(ctx, systemd_kmod_log, NULL); + kmod_load_resources(ctx); + } + + r = kmod_module_new_from_name(ctx, kmod_table[i].module, &mod); + if (r < 0) { + log_error("Failed to lookup module '%s'", kmod_table[i].module); + continue; + } + + r = kmod_module_probe_insert_module(mod, KMOD_PROBE_APPLY_BLACKLIST, NULL, NULL, NULL, NULL); + if (r == 0) + log_debug("Inserted module '%s'", kmod_module_get_name(mod)); + else if (r == KMOD_PROBE_APPLY_BLACKLIST) + log_info("Module '%s' is blacklisted", kmod_module_get_name(mod)); + else { + bool print_warning = kmod_table[i].warn_if_unavailable || (r < 0 && r != -ENOENT); + + log_full_errno(print_warning ? LOG_WARNING : LOG_DEBUG, r, + "Failed to insert module '%s': %m", kmod_module_get_name(mod)); + } + + kmod_module_unref(mod); + } + + if (ctx) + kmod_unref(ctx); + +#endif + return 0; +} diff --git a/src/libcore/kmod-setup.h b/src/libcore/kmod-setup.h new file mode 100644 index 0000000000..685f4df301 --- /dev/null +++ b/src/libcore/kmod-setup.h @@ -0,0 +1,22 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +int kmod_setup(void); diff --git a/src/libcore/linux/auto_dev-ioctl.h b/src/libcore/linux/auto_dev-ioctl.h new file mode 100644 index 0000000000..aeaeb3ea7a --- /dev/null +++ b/src/libcore/linux/auto_dev-ioctl.h @@ -0,0 +1,228 @@ +/* + * Copyright 2008 Red Hat, Inc. All rights reserved. + * Copyright 2008 Ian Kent + * + * This file is part of the Linux kernel and is made available under + * the terms of the GNU General Public License, version 2, or at your + * option, any later version, incorporated herein by reference. + */ + +#ifndef _LINUX_AUTO_DEV_IOCTL_H +#define _LINUX_AUTO_DEV_IOCTL_H + +#include + +#ifdef __KERNEL__ +#include +#else +#include +#endif /* __KERNEL__ */ + +#define AUTOFS_DEVICE_NAME "autofs" + +#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 +#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 + +#define AUTOFS_DEVID_LEN 16 + +#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) + +/* + * An ioctl interface for autofs mount point control. + */ + +struct args_protover { + __u32 version; +}; + +struct args_protosubver { + __u32 sub_version; +}; + +struct args_openmount { + __u32 devid; +}; + +struct args_ready { + __u32 token; +}; + +struct args_fail { + __u32 token; + __s32 status; +}; + +struct args_setpipefd { + __s32 pipefd; +}; + +struct args_timeout { + __u64 timeout; +}; + +struct args_requester { + __u32 uid; + __u32 gid; +}; + +struct args_expire { + __u32 how; +}; + +struct args_askumount { + __u32 may_umount; +}; + +struct args_ismountpoint { + union { + struct args_in { + __u32 type; + } in; + struct args_out { + __u32 devid; + __u32 magic; + } out; + }; +}; + +/* + * All the ioctls use this structure. + * When sending a path size must account for the total length + * of the chunk of memory otherwise is is the size of the + * structure. + */ + +struct autofs_dev_ioctl { + __u32 ver_major; + __u32 ver_minor; + __u32 size; /* total size of data passed in + * including this struct */ + __s32 ioctlfd; /* automount command fd */ + + /* Command parameters */ + + union { + struct args_protover protover; + struct args_protosubver protosubver; + struct args_openmount openmount; + struct args_ready ready; + struct args_fail fail; + struct args_setpipefd setpipefd; + struct args_timeout timeout; + struct args_requester requester; + struct args_expire expire; + struct args_askumount askumount; + struct args_ismountpoint ismountpoint; + }; + + char path[0]; +}; + +static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) { + memset(in, 0, sizeof(struct autofs_dev_ioctl)); + in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; + in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; + in->size = sizeof(struct autofs_dev_ioctl); + in->ioctlfd = -1; + return; +} + +/* + * If you change this make sure you make the corresponding change + * to autofs-dev-ioctl.c:lookup_ioctl() + */ +enum { + /* Get various version info */ + AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, + AUTOFS_DEV_IOCTL_PROTOVER_CMD, + AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, + + /* Open mount ioctl fd */ + AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, + + /* Close mount ioctl fd */ + AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, + + /* Mount/expire status returns */ + AUTOFS_DEV_IOCTL_READY_CMD, + AUTOFS_DEV_IOCTL_FAIL_CMD, + + /* Activate/deactivate autofs mount */ + AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, + AUTOFS_DEV_IOCTL_CATATONIC_CMD, + + /* Expiry timeout */ + AUTOFS_DEV_IOCTL_TIMEOUT_CMD, + + /* Get mount last requesting uid and gid */ + AUTOFS_DEV_IOCTL_REQUESTER_CMD, + + /* Check for eligible expire candidates */ + AUTOFS_DEV_IOCTL_EXPIRE_CMD, + + /* Request busy status */ + AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, + + /* Check if path is a mountpoint */ + AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, +}; + +#define AUTOFS_IOCTL 0x93 + +#define AUTOFS_DEV_IOCTL_VERSION \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_PROTOVER \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_OPENMOUNT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_READY \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_FAIL \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_SETPIPEFD \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_CATATONIC \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_TIMEOUT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_REQUESTER \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_EXPIRE \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) + +#endif /* _LINUX_AUTO_DEV_IOCTL_H */ diff --git a/src/libcore/load-dropin.c b/src/libcore/load-dropin.c new file mode 100644 index 0000000000..f83fa09301 --- /dev/null +++ b/src/libcore/load-dropin.c @@ -0,0 +1,90 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 "conf-parser.h" +#include "load-dropin.h" +#include "load-fragment.h" +#include "log.h" +#include "strv.h" +#include "unit-name.h" +#include "unit.h" + +static int add_dependency_consumer( + UnitDependency dependency, + const char *entry, + const char* filepath, + void *arg) { + Unit *u = arg; + int r; + + assert(u); + + r = unit_add_dependency_by_name(u, dependency, entry, filepath, true); + if (r < 0) + log_error_errno(r, "Cannot add dependency %s to %s, ignoring: %m", entry, u->id); + + return 0; +} + +int unit_load_dropin(Unit *u) { + _cleanup_strv_free_ char **l = NULL; + Iterator i; + char *t, **f; + int r; + + assert(u); + + /* Load dependencies from supplementary drop-in directories */ + + SET_FOREACH(t, u->names, i) { + char **p; + + STRV_FOREACH(p, u->manager->lookup_paths.search_path) { + unit_file_process_dir(u->manager->unit_path_cache, *p, t, ".wants", UNIT_WANTS, + add_dependency_consumer, u, NULL); + unit_file_process_dir(u->manager->unit_path_cache, *p, t, ".requires", UNIT_REQUIRES, + add_dependency_consumer, u, NULL); + } + } + + r = unit_find_dropin_paths(u, &l); + if (r <= 0) + return 0; + + if (!u->dropin_paths) { + u->dropin_paths = l; + l = NULL; + } else { + r = strv_extend_strv(&u->dropin_paths, l, true); + if (r < 0) + return log_oom(); + } + + STRV_FOREACH(f, u->dropin_paths) { + config_parse(u->id, *f, NULL, + UNIT_VTABLE(u)->sections, + config_item_perf_lookup, load_fragment_gperf_lookup, + false, false, false, u); + } + + u->dropin_mtime = now(CLOCK_REALTIME); + + return 0; +} diff --git a/src/libcore/load-dropin.h b/src/libcore/load-dropin.h new file mode 100644 index 0000000000..942d26724e --- /dev/null +++ b/src/libcore/load-dropin.h @@ -0,0 +1,34 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "dropin.h" +#include "unit.h" + +/* Read service data supplementary drop-in directories */ + +static inline int unit_find_dropin_paths(Unit *u, char ***paths) { + return unit_file_find_dropin_paths(u->manager->lookup_paths.search_path, + u->manager->unit_path_cache, + u->names, + paths); +} + +int unit_load_dropin(Unit *u); diff --git a/src/libcore/load-fragment-gperf.gperf.m4 b/src/libcore/load-fragment-gperf.gperf.m4 new file mode 100644 index 0000000000..8193418980 --- /dev/null +++ b/src/libcore/load-fragment-gperf.gperf.m4 @@ -0,0 +1,391 @@ +%{ +#include +#include "conf-parser.h" +#include "load-fragment.h" +#include "missing.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name load_fragment_gperf_hash +%define lookup-function-name load_fragment_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +m4_dnl Define the context options only once +m4_define(`EXEC_CONTEXT_CONFIG_ITEMS', +`$1.WorkingDirectory, config_parse_working_directory, 0, offsetof($1, exec_context) +$1.RootDirectory, config_parse_unit_path_printf, 0, offsetof($1, exec_context.root_directory) +$1.User, config_parse_unit_string_printf, 0, offsetof($1, exec_context.user) +$1.Group, config_parse_unit_string_printf, 0, offsetof($1, exec_context.group) +$1.SupplementaryGroups, config_parse_strv, 0, offsetof($1, exec_context.supplementary_groups) +$1.Nice, config_parse_exec_nice, 0, offsetof($1, exec_context) +$1.OOMScoreAdjust, config_parse_exec_oom_score_adjust, 0, offsetof($1, exec_context) +$1.IOSchedulingClass, config_parse_exec_io_class, 0, offsetof($1, exec_context) +$1.IOSchedulingPriority, config_parse_exec_io_priority, 0, offsetof($1, exec_context) +$1.CPUSchedulingPolicy, config_parse_exec_cpu_sched_policy, 0, offsetof($1, exec_context) +$1.CPUSchedulingPriority, config_parse_exec_cpu_sched_prio, 0, offsetof($1, exec_context) +$1.CPUSchedulingResetOnFork, config_parse_bool, 0, offsetof($1, exec_context.cpu_sched_reset_on_fork) +$1.CPUAffinity, config_parse_exec_cpu_affinity, 0, offsetof($1, exec_context) +$1.UMask, config_parse_mode, 0, offsetof($1, exec_context.umask) +$1.Environment, config_parse_environ, 0, offsetof($1, exec_context.environment) +$1.EnvironmentFile, config_parse_unit_env_file, 0, offsetof($1, exec_context.environment_files) +$1.PassEnvironment, config_parse_pass_environ, 0, offsetof($1, exec_context.pass_environment) +$1.StandardInput, config_parse_input, 0, offsetof($1, exec_context.std_input) +$1.StandardOutput, config_parse_output, 0, offsetof($1, exec_context.std_output) +$1.StandardError, config_parse_output, 0, offsetof($1, exec_context.std_error) +$1.TTYPath, config_parse_unit_path_printf, 0, offsetof($1, exec_context.tty_path) +$1.TTYReset, config_parse_bool, 0, offsetof($1, exec_context.tty_reset) +$1.TTYVHangup, config_parse_bool, 0, offsetof($1, exec_context.tty_vhangup) +$1.TTYVTDisallocate, config_parse_bool, 0, offsetof($1, exec_context.tty_vt_disallocate) +$1.SyslogIdentifier, config_parse_unit_string_printf, 0, offsetof($1, exec_context.syslog_identifier) +$1.SyslogFacility, config_parse_log_facility, 0, offsetof($1, exec_context.syslog_priority) +$1.SyslogLevel, config_parse_log_level, 0, offsetof($1, exec_context.syslog_priority) +$1.SyslogLevelPrefix, config_parse_bool, 0, offsetof($1, exec_context.syslog_level_prefix) +$1.Capabilities, config_parse_warn_compat, DISABLED_LEGACY, offsetof($1, exec_context) +$1.SecureBits, config_parse_exec_secure_bits, 0, offsetof($1, exec_context) +$1.CapabilityBoundingSet, config_parse_capability_set, 0, offsetof($1, exec_context.capability_bounding_set) +$1.AmbientCapabilities, config_parse_capability_set, 0, offsetof($1, exec_context.capability_ambient_set) +$1.TimerSlackNSec, config_parse_nsec, 0, offsetof($1, exec_context.timer_slack_nsec) +$1.NoNewPrivileges, config_parse_no_new_privileges, 0, offsetof($1, exec_context) +m4_ifdef(`HAVE_SECCOMP', +`$1.SystemCallFilter, config_parse_syscall_filter, 0, offsetof($1, exec_context) +$1.SystemCallArchitectures, config_parse_syscall_archs, 0, offsetof($1, exec_context.syscall_archs) +$1.SystemCallErrorNumber, config_parse_syscall_errno, 0, offsetof($1, exec_context) +$1.RestrictAddressFamilies, config_parse_address_families, 0, offsetof($1, exec_context)', +`$1.SystemCallFilter, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 +$1.SystemCallArchitectures, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 +$1.SystemCallErrorNumber, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 +$1.RestrictAddressFamilies, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') +$1.LimitCPU, config_parse_limit, RLIMIT_CPU, offsetof($1, exec_context.rlimit) +$1.LimitFSIZE, config_parse_limit, RLIMIT_FSIZE, offsetof($1, exec_context.rlimit) +$1.LimitDATA, config_parse_limit, RLIMIT_DATA, offsetof($1, exec_context.rlimit) +$1.LimitSTACK, config_parse_limit, RLIMIT_STACK, offsetof($1, exec_context.rlimit) +$1.LimitCORE, config_parse_limit, RLIMIT_CORE, offsetof($1, exec_context.rlimit) +$1.LimitRSS, config_parse_limit, RLIMIT_RSS, offsetof($1, exec_context.rlimit) +$1.LimitNOFILE, config_parse_limit, RLIMIT_NOFILE, offsetof($1, exec_context.rlimit) +$1.LimitAS, config_parse_limit, RLIMIT_AS, offsetof($1, exec_context.rlimit) +$1.LimitNPROC, config_parse_limit, RLIMIT_NPROC, offsetof($1, exec_context.rlimit) +$1.LimitMEMLOCK, config_parse_limit, RLIMIT_MEMLOCK, offsetof($1, exec_context.rlimit) +$1.LimitLOCKS, config_parse_limit, RLIMIT_LOCKS, offsetof($1, exec_context.rlimit) +$1.LimitSIGPENDING, config_parse_limit, RLIMIT_SIGPENDING, offsetof($1, exec_context.rlimit) +$1.LimitMSGQUEUE, config_parse_limit, RLIMIT_MSGQUEUE, offsetof($1, exec_context.rlimit) +$1.LimitNICE, config_parse_limit, RLIMIT_NICE, offsetof($1, exec_context.rlimit) +$1.LimitRTPRIO, config_parse_limit, RLIMIT_RTPRIO, offsetof($1, exec_context.rlimit) +$1.LimitRTTIME, config_parse_limit, RLIMIT_RTTIME, offsetof($1, exec_context.rlimit) +$1.ReadWriteDirectories, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_write_dirs) +$1.ReadOnlyDirectories, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_only_dirs) +$1.InaccessibleDirectories, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.inaccessible_dirs) +$1.PrivateTmp, config_parse_bool, 0, offsetof($1, exec_context.private_tmp) +$1.PrivateNetwork, config_parse_bool, 0, offsetof($1, exec_context.private_network) +$1.PrivateDevices, config_parse_bool, 0, offsetof($1, exec_context.private_devices) +$1.ProtectSystem, config_parse_protect_system, 0, offsetof($1, exec_context) +$1.ProtectHome, config_parse_protect_home, 0, offsetof($1, exec_context) +$1.MountFlags, config_parse_exec_mount_flags, 0, offsetof($1, exec_context) +$1.Personality, config_parse_personality, 0, offsetof($1, exec_context.personality) +$1.RuntimeDirectoryMode, config_parse_mode, 0, offsetof($1, exec_context.runtime_directory_mode) +$1.RuntimeDirectory, config_parse_runtime_directory, 0, offsetof($1, exec_context.runtime_directory) +m4_ifdef(`HAVE_PAM', +`$1.PAMName, config_parse_unit_string_printf, 0, offsetof($1, exec_context.pam_name)', +`$1.PAMName, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') +$1.IgnoreSIGPIPE, config_parse_bool, 0, offsetof($1, exec_context.ignore_sigpipe) +$1.UtmpIdentifier, config_parse_unit_string_printf, 0, offsetof($1, exec_context.utmp_id) +$1.UtmpMode, config_parse_exec_utmp_mode, 0, offsetof($1, exec_context.utmp_mode) +m4_ifdef(`HAVE_SELINUX', +`$1.SELinuxContext, config_parse_exec_selinux_context, 0, offsetof($1, exec_context)', +`$1.SELinuxContext, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') +m4_ifdef(`HAVE_APPARMOR', +`$1.AppArmorProfile, config_parse_exec_apparmor_profile, 0, offsetof($1, exec_context)', +`$1.AppArmorProfile, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') +m4_ifdef(`HAVE_SMACK', +`$1.SmackProcessLabel, config_parse_exec_smack_process_label, 0, offsetof($1, exec_context)', +`$1.SmackProcessLabel, config_parse_warn_compat, DISABLED_CONFIGURATION, 0')' +)m4_dnl +m4_define(`KILL_CONTEXT_CONFIG_ITEMS', +`$1.SendSIGKILL, config_parse_bool, 0, offsetof($1, kill_context.send_sigkill) +$1.SendSIGHUP, config_parse_bool, 0, offsetof($1, kill_context.send_sighup) +$1.KillMode, config_parse_kill_mode, 0, offsetof($1, kill_context.kill_mode) +$1.KillSignal, config_parse_signal, 0, offsetof($1, kill_context.kill_signal)' +)m4_dnl +m4_define(`CGROUP_CONTEXT_CONFIG_ITEMS', +`$1.Slice, config_parse_unit_slice, 0, 0 +$1.CPUAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.cpu_accounting) +$1.CPUShares, config_parse_cpu_shares, 0, offsetof($1, cgroup_context.cpu_shares) +$1.StartupCPUShares, config_parse_cpu_shares, 0, offsetof($1, cgroup_context.startup_cpu_shares) +$1.CPUQuota, config_parse_cpu_quota, 0, offsetof($1, cgroup_context) +$1.MemoryAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.memory_accounting) +$1.MemoryLimit, config_parse_memory_limit, 0, offsetof($1, cgroup_context) +$1.DeviceAllow, config_parse_device_allow, 0, offsetof($1, cgroup_context) +$1.DevicePolicy, config_parse_device_policy, 0, offsetof($1, cgroup_context.device_policy) +$1.IOAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.io_accounting) +$1.IOWeight, config_parse_io_weight, 0, offsetof($1, cgroup_context.io_weight) +$1.StartupIOWeight, config_parse_io_weight, 0, offsetof($1, cgroup_context.startup_io_weight) +$1.IODeviceWeight, config_parse_io_device_weight, 0, offsetof($1, cgroup_context) +$1.IOReadBandwidthMax, config_parse_io_limit, 0, offsetof($1, cgroup_context) +$1.IOWriteBandwidthMax, config_parse_io_limit, 0, offsetof($1, cgroup_context) +$1.IOReadIOPSMax, config_parse_io_limit, 0, offsetof($1, cgroup_context) +$1.IOWriteIOPSMax, config_parse_io_limit, 0, offsetof($1, cgroup_context) +$1.BlockIOAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.blockio_accounting) +$1.BlockIOWeight, config_parse_blockio_weight, 0, offsetof($1, cgroup_context.blockio_weight) +$1.StartupBlockIOWeight, config_parse_blockio_weight, 0, offsetof($1, cgroup_context.startup_blockio_weight) +$1.BlockIODeviceWeight, config_parse_blockio_device_weight, 0, offsetof($1, cgroup_context) +$1.BlockIOReadBandwidth, config_parse_blockio_bandwidth, 0, offsetof($1, cgroup_context) +$1.BlockIOWriteBandwidth, config_parse_blockio_bandwidth, 0, offsetof($1, cgroup_context) +$1.TasksAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.tasks_accounting) +$1.TasksMax, config_parse_tasks_max, 0, offsetof($1, cgroup_context.tasks_max) +$1.Delegate, config_parse_bool, 0, offsetof($1, cgroup_context.delegate) +$1.NetClass, config_parse_warn_compat, DISABLED_LEGACY, 0' +)m4_dnl +Unit.Description, config_parse_unit_string_printf, 0, offsetof(Unit, description) +Unit.Documentation, config_parse_documentation, 0, offsetof(Unit, documentation) +Unit.SourcePath, config_parse_path, 0, offsetof(Unit, source_path) +Unit.Requires, config_parse_unit_deps, UNIT_REQUIRES, 0 +Unit.Requisite, config_parse_unit_deps, UNIT_REQUISITE, 0 +Unit.Wants, config_parse_unit_deps, UNIT_WANTS, 0 +Unit.BindsTo, config_parse_unit_deps, UNIT_BINDS_TO, 0 +Unit.BindTo, config_parse_unit_deps, UNIT_BINDS_TO, 0 +Unit.Conflicts, config_parse_unit_deps, UNIT_CONFLICTS, 0 +Unit.Before, config_parse_unit_deps, UNIT_BEFORE, 0 +Unit.After, config_parse_unit_deps, UNIT_AFTER, 0 +Unit.OnFailure, config_parse_unit_deps, UNIT_ON_FAILURE, 0 +Unit.PropagatesReloadTo, config_parse_unit_deps, UNIT_PROPAGATES_RELOAD_TO, 0 +Unit.PropagateReloadTo, config_parse_unit_deps, UNIT_PROPAGATES_RELOAD_TO, 0 +Unit.ReloadPropagatedFrom, config_parse_unit_deps, UNIT_RELOAD_PROPAGATED_FROM, 0 +Unit.PropagateReloadFrom, config_parse_unit_deps, UNIT_RELOAD_PROPAGATED_FROM, 0 +Unit.PartOf, config_parse_unit_deps, UNIT_PART_OF, 0 +Unit.JoinsNamespaceOf, config_parse_unit_deps, UNIT_JOINS_NAMESPACE_OF, 0 +Unit.RequiresOverridable, config_parse_obsolete_unit_deps, UNIT_REQUIRES, 0 +Unit.RequisiteOverridable, config_parse_obsolete_unit_deps, UNIT_REQUISITE, 0 +Unit.RequiresMountsFor, config_parse_unit_requires_mounts_for, 0, 0 +Unit.StopWhenUnneeded, config_parse_bool, 0, offsetof(Unit, stop_when_unneeded) +Unit.RefuseManualStart, config_parse_bool, 0, offsetof(Unit, refuse_manual_start) +Unit.RefuseManualStop, config_parse_bool, 0, offsetof(Unit, refuse_manual_stop) +Unit.AllowIsolate, config_parse_bool, 0, offsetof(Unit, allow_isolate) +Unit.DefaultDependencies, config_parse_bool, 0, offsetof(Unit, default_dependencies) +Unit.OnFailureJobMode, config_parse_job_mode, 0, offsetof(Unit, on_failure_job_mode) +Unit.OnFailureIsolate, config_parse_job_mode_isolate, 0, offsetof(Unit, on_failure_job_mode) +Unit.IgnoreOnIsolate, config_parse_bool, 0, offsetof(Unit, ignore_on_isolate) +Unit.IgnoreOnSnapshot, config_parse_warn_compat, DISABLED_LEGACY, 0 +Unit.JobTimeoutSec, config_parse_sec_fix_0, 0, offsetof(Unit, job_timeout) +Unit.JobTimeoutAction, config_parse_failure_action, 0, offsetof(Unit, job_timeout_action) +Unit.JobTimeoutRebootArgument, config_parse_string, 0, offsetof(Unit, job_timeout_reboot_arg) +Unit.StartLimitIntervalSec, config_parse_sec, 0, offsetof(Unit, start_limit.interval) +m4_dnl The following is a legacy alias name for compatibility +Unit.StartLimitInterval, config_parse_sec, 0, offsetof(Unit, start_limit.interval) +Unit.StartLimitBurst, config_parse_unsigned, 0, offsetof(Unit, start_limit.burst) +Unit.StartLimitAction, config_parse_failure_action, 0, offsetof(Unit, start_limit_action) +Unit.RebootArgument, config_parse_string, 0, offsetof(Unit, reboot_arg) +Unit.ConditionPathExists, config_parse_unit_condition_path, CONDITION_PATH_EXISTS, offsetof(Unit, conditions) +Unit.ConditionPathExistsGlob, config_parse_unit_condition_path, CONDITION_PATH_EXISTS_GLOB, offsetof(Unit, conditions) +Unit.ConditionPathIsDirectory, config_parse_unit_condition_path, CONDITION_PATH_IS_DIRECTORY, offsetof(Unit, conditions) +Unit.ConditionPathIsSymbolicLink,config_parse_unit_condition_path, CONDITION_PATH_IS_SYMBOLIC_LINK,offsetof(Unit, conditions) +Unit.ConditionPathIsMountPoint, config_parse_unit_condition_path, CONDITION_PATH_IS_MOUNT_POINT, offsetof(Unit, conditions) +Unit.ConditionPathIsReadWrite, config_parse_unit_condition_path, CONDITION_PATH_IS_READ_WRITE, offsetof(Unit, conditions) +Unit.ConditionDirectoryNotEmpty, config_parse_unit_condition_path, CONDITION_DIRECTORY_NOT_EMPTY, offsetof(Unit, conditions) +Unit.ConditionFileNotEmpty, config_parse_unit_condition_path, CONDITION_FILE_NOT_EMPTY, offsetof(Unit, conditions) +Unit.ConditionFileIsExecutable, config_parse_unit_condition_path, CONDITION_FILE_IS_EXECUTABLE, offsetof(Unit, conditions) +Unit.ConditionNeedsUpdate, config_parse_unit_condition_path, CONDITION_NEEDS_UPDATE, offsetof(Unit, conditions) +Unit.ConditionFirstBoot, config_parse_unit_condition_string, CONDITION_FIRST_BOOT, offsetof(Unit, conditions) +Unit.ConditionKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, offsetof(Unit, conditions) +Unit.ConditionArchitecture, config_parse_unit_condition_string, CONDITION_ARCHITECTURE, offsetof(Unit, conditions) +Unit.ConditionVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, offsetof(Unit, conditions) +Unit.ConditionSecurity, config_parse_unit_condition_string, CONDITION_SECURITY, offsetof(Unit, conditions) +Unit.ConditionCapability, config_parse_unit_condition_string, CONDITION_CAPABILITY, offsetof(Unit, conditions) +Unit.ConditionHost, config_parse_unit_condition_string, CONDITION_HOST, offsetof(Unit, conditions) +Unit.ConditionACPower, config_parse_unit_condition_string, CONDITION_AC_POWER, offsetof(Unit, conditions) +Unit.ConditionNull, config_parse_unit_condition_null, 0, offsetof(Unit, conditions) +Unit.AssertPathExists, config_parse_unit_condition_path, CONDITION_PATH_EXISTS, offsetof(Unit, asserts) +Unit.AssertPathExistsGlob, config_parse_unit_condition_path, CONDITION_PATH_EXISTS_GLOB, offsetof(Unit, asserts) +Unit.AssertPathIsDirectory, config_parse_unit_condition_path, CONDITION_PATH_IS_DIRECTORY, offsetof(Unit, asserts) +Unit.AssertPathIsSymbolicLink, config_parse_unit_condition_path, CONDITION_PATH_IS_SYMBOLIC_LINK,offsetof(Unit, asserts) +Unit.AssertPathIsMountPoint, config_parse_unit_condition_path, CONDITION_PATH_IS_MOUNT_POINT, offsetof(Unit, asserts) +Unit.AssertPathIsReadWrite, config_parse_unit_condition_path, CONDITION_PATH_IS_READ_WRITE, offsetof(Unit, asserts) +Unit.AssertDirectoryNotEmpty, config_parse_unit_condition_path, CONDITION_DIRECTORY_NOT_EMPTY, offsetof(Unit, asserts) +Unit.AssertFileNotEmpty, config_parse_unit_condition_path, CONDITION_FILE_NOT_EMPTY, offsetof(Unit, asserts) +Unit.AssertFileIsExecutable, config_parse_unit_condition_path, CONDITION_FILE_IS_EXECUTABLE, offsetof(Unit, asserts) +Unit.AssertNeedsUpdate, config_parse_unit_condition_path, CONDITION_NEEDS_UPDATE, offsetof(Unit, asserts) +Unit.AssertFirstBoot, config_parse_unit_condition_string, CONDITION_FIRST_BOOT, offsetof(Unit, asserts) +Unit.AssertKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, offsetof(Unit, asserts) +Unit.AssertArchitecture, config_parse_unit_condition_string, CONDITION_ARCHITECTURE, offsetof(Unit, asserts) +Unit.AssertVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, offsetof(Unit, asserts) +Unit.AssertSecurity, config_parse_unit_condition_string, CONDITION_SECURITY, offsetof(Unit, asserts) +Unit.AssertCapability, config_parse_unit_condition_string, CONDITION_CAPABILITY, offsetof(Unit, asserts) +Unit.AssertHost, config_parse_unit_condition_string, CONDITION_HOST, offsetof(Unit, asserts) +Unit.AssertACPower, config_parse_unit_condition_string, CONDITION_AC_POWER, offsetof(Unit, asserts) +Unit.AssertNull, config_parse_unit_condition_null, 0, offsetof(Unit, asserts) +m4_dnl +Service.PIDFile, config_parse_unit_path_printf, 0, offsetof(Service, pid_file) +Service.ExecStartPre, config_parse_exec, SERVICE_EXEC_START_PRE, offsetof(Service, exec_command) +Service.ExecStart, config_parse_exec, SERVICE_EXEC_START, offsetof(Service, exec_command) +Service.ExecStartPost, config_parse_exec, SERVICE_EXEC_START_POST, offsetof(Service, exec_command) +Service.ExecReload, config_parse_exec, SERVICE_EXEC_RELOAD, offsetof(Service, exec_command) +Service.ExecStop, config_parse_exec, SERVICE_EXEC_STOP, offsetof(Service, exec_command) +Service.ExecStopPost, config_parse_exec, SERVICE_EXEC_STOP_POST, offsetof(Service, exec_command) +Service.RestartSec, config_parse_sec, 0, offsetof(Service, restart_usec) +Service.TimeoutSec, config_parse_service_timeout, 0, 0 +Service.TimeoutStartSec, config_parse_service_timeout, 0, 0 +Service.TimeoutStopSec, config_parse_service_timeout, 0, 0 +Service.RuntimeMaxSec, config_parse_sec, 0, offsetof(Service, runtime_max_usec) +Service.WatchdogSec, config_parse_sec, 0, offsetof(Service, watchdog_usec) +m4_dnl The following three only exist for compatibility, they moved into Unit, see above +Service.StartLimitInterval, config_parse_sec, 0, offsetof(Unit, start_limit.interval) +Service.StartLimitBurst, config_parse_unsigned, 0, offsetof(Unit, start_limit.burst) +Service.StartLimitAction, config_parse_failure_action, 0, offsetof(Unit, start_limit_action) +Service.RebootArgument, config_parse_string, 0, offsetof(Unit, reboot_arg) +Service.FailureAction, config_parse_failure_action, 0, offsetof(Service, failure_action) +Service.Type, config_parse_service_type, 0, offsetof(Service, type) +Service.Restart, config_parse_service_restart, 0, offsetof(Service, restart) +Service.PermissionsStartOnly, config_parse_bool, 0, offsetof(Service, permissions_start_only) +Service.RootDirectoryStartOnly, config_parse_bool, 0, offsetof(Service, root_directory_start_only) +Service.RemainAfterExit, config_parse_bool, 0, offsetof(Service, remain_after_exit) +Service.GuessMainPID, config_parse_bool, 0, offsetof(Service, guess_main_pid) +Service.RestartPreventExitStatus, config_parse_set_status, 0, offsetof(Service, restart_prevent_status) +Service.RestartForceExitStatus, config_parse_set_status, 0, offsetof(Service, restart_force_status) +Service.SuccessExitStatus, config_parse_set_status, 0, offsetof(Service, success_status) +Service.SysVStartPriority, config_parse_warn_compat, DISABLED_LEGACY, 0 +Service.NonBlocking, config_parse_bool, 0, offsetof(Service, exec_context.non_blocking) +Service.BusName, config_parse_bus_name, 0, offsetof(Service, bus_name) +Service.FileDescriptorStoreMax, config_parse_unsigned, 0, offsetof(Service, n_fd_store_max) +Service.NotifyAccess, config_parse_notify_access, 0, offsetof(Service, notify_access) +Service.Sockets, config_parse_service_sockets, 0, 0 +Service.BusPolicy, config_parse_warn_compat, DISABLED_LEGACY, 0 +Service.USBFunctionDescriptors, config_parse_path, 0, offsetof(Service, usb_function_descriptors) +Service.USBFunctionStrings, config_parse_path, 0, offsetof(Service, usb_function_strings) +EXEC_CONTEXT_CONFIG_ITEMS(Service)m4_dnl +CGROUP_CONTEXT_CONFIG_ITEMS(Service)m4_dnl +KILL_CONTEXT_CONFIG_ITEMS(Service)m4_dnl +m4_dnl +Socket.ListenStream, config_parse_socket_listen, SOCKET_SOCKET, 0 +Socket.ListenDatagram, config_parse_socket_listen, SOCKET_SOCKET, 0 +Socket.ListenSequentialPacket, config_parse_socket_listen, SOCKET_SOCKET, 0 +Socket.ListenFIFO, config_parse_socket_listen, SOCKET_FIFO, 0 +Socket.ListenNetlink, config_parse_socket_listen, SOCKET_SOCKET, 0 +Socket.ListenSpecial, config_parse_socket_listen, SOCKET_SPECIAL, 0 +Socket.ListenMessageQueue, config_parse_socket_listen, SOCKET_MQUEUE, 0 +Socket.ListenUSBFunction, config_parse_socket_listen, SOCKET_USB_FUNCTION, 0 +Socket.SocketProtocol, config_parse_socket_protocol, 0, 0 +Socket.BindIPv6Only, config_parse_socket_bind, 0, 0, +Socket.Backlog, config_parse_unsigned, 0, offsetof(Socket, backlog) +Socket.BindToDevice, config_parse_socket_bindtodevice, 0, 0 +Socket.ExecStartPre, config_parse_exec, SOCKET_EXEC_START_PRE, offsetof(Socket, exec_command) +Socket.ExecStartPost, config_parse_exec, SOCKET_EXEC_START_POST, offsetof(Socket, exec_command) +Socket.ExecStopPre, config_parse_exec, SOCKET_EXEC_STOP_PRE, offsetof(Socket, exec_command) +Socket.ExecStopPost, config_parse_exec, SOCKET_EXEC_STOP_POST, offsetof(Socket, exec_command) +Socket.TimeoutSec, config_parse_sec, 0, offsetof(Socket, timeout_usec) +Socket.SocketUser, config_parse_unit_string_printf, 0, offsetof(Socket, user) +Socket.SocketGroup, config_parse_unit_string_printf, 0, offsetof(Socket, group) +Socket.SocketMode, config_parse_mode, 0, offsetof(Socket, socket_mode) +Socket.DirectoryMode, config_parse_mode, 0, offsetof(Socket, directory_mode) +Socket.Accept, config_parse_bool, 0, offsetof(Socket, accept) +Socket.Writable, config_parse_bool, 0, offsetof(Socket, writable) +Socket.MaxConnections, config_parse_unsigned, 0, offsetof(Socket, max_connections) +Socket.KeepAlive, config_parse_bool, 0, offsetof(Socket, keep_alive) +Socket.KeepAliveTimeSec, config_parse_sec, 0, offsetof(Socket, keep_alive_time) +Socket.KeepAliveIntervalSec, config_parse_sec, 0, offsetof(Socket, keep_alive_interval) +Socket.KeepAliveProbes, config_parse_unsigned, 0, offsetof(Socket, keep_alive_cnt) +Socket.DeferAcceptSec, config_parse_sec, 0, offsetof(Socket, defer_accept) +Socket.NoDelay, config_parse_bool, 0, offsetof(Socket, no_delay) +Socket.Priority, config_parse_int, 0, offsetof(Socket, priority) +Socket.ReceiveBuffer, config_parse_iec_size, 0, offsetof(Socket, receive_buffer) +Socket.SendBuffer, config_parse_iec_size, 0, offsetof(Socket, send_buffer) +Socket.IPTOS, config_parse_ip_tos, 0, offsetof(Socket, ip_tos) +Socket.IPTTL, config_parse_int, 0, offsetof(Socket, ip_ttl) +Socket.Mark, config_parse_int, 0, offsetof(Socket, mark) +Socket.PipeSize, config_parse_iec_size, 0, offsetof(Socket, pipe_size) +Socket.FreeBind, config_parse_bool, 0, offsetof(Socket, free_bind) +Socket.Transparent, config_parse_bool, 0, offsetof(Socket, transparent) +Socket.Broadcast, config_parse_bool, 0, offsetof(Socket, broadcast) +Socket.PassCredentials, config_parse_bool, 0, offsetof(Socket, pass_cred) +Socket.PassSecurity, config_parse_bool, 0, offsetof(Socket, pass_sec) +Socket.TCPCongestion, config_parse_string, 0, offsetof(Socket, tcp_congestion) +Socket.ReusePort, config_parse_bool, 0, offsetof(Socket, reuse_port) +Socket.MessageQueueMaxMessages, config_parse_long, 0, offsetof(Socket, mq_maxmsg) +Socket.MessageQueueMessageSize, config_parse_long, 0, offsetof(Socket, mq_msgsize) +Socket.RemoveOnStop, config_parse_bool, 0, offsetof(Socket, remove_on_stop) +Socket.Symlinks, config_parse_unit_path_strv_printf, 0, offsetof(Socket, symlinks) +Socket.FileDescriptorName, config_parse_fdname, 0, 0 +Socket.Service, config_parse_socket_service, 0, 0 +Socket.TriggerLimitIntervalSec, config_parse_sec, 0, offsetof(Socket, trigger_limit.interval) +Socket.TriggerLimitBurst, config_parse_unsigned, 0, offsetof(Socket, trigger_limit.burst) +m4_ifdef(`HAVE_SMACK', +`Socket.SmackLabel, config_parse_string, 0, offsetof(Socket, smack) +Socket.SmackLabelIPIn, config_parse_string, 0, offsetof(Socket, smack_ip_in) +Socket.SmackLabelIPOut, config_parse_string, 0, offsetof(Socket, smack_ip_out)', +`Socket.SmackLabel, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 +Socket.SmackLabelIPIn, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 +Socket.SmackLabelIPOut, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') +m4_ifdef(`HAVE_SELINUX', +`Socket.SELinuxContextFromNet, config_parse_bool, 0, offsetof(Socket, selinux_context_from_net)', +`Socket.SELinuxContextFromNet, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') +EXEC_CONTEXT_CONFIG_ITEMS(Socket)m4_dnl +CGROUP_CONTEXT_CONFIG_ITEMS(Socket)m4_dnl +KILL_CONTEXT_CONFIG_ITEMS(Socket)m4_dnl +m4_dnl +BusName.Name, config_parse_string, 0, offsetof(BusName, name) +BusName.Activating, config_parse_bool, 0, offsetof(BusName, activating) +BusName.Service, config_parse_busname_service, 0, 0 +BusName.AllowUser, config_parse_bus_policy, 0, 0 +BusName.AllowGroup, config_parse_bus_policy, 0, 0 +BusName.AllowWorld, config_parse_bus_policy_world, 0, offsetof(BusName, policy_world) +BusName.SELinuxContext, config_parse_exec_selinux_context, 0, 0 +BusName.AcceptFileDescriptors, config_parse_bool, 0, offsetof(BusName, accept_fd) +m4_dnl +Mount.What, config_parse_string, 0, offsetof(Mount, parameters_fragment.what) +Mount.Where, config_parse_path, 0, offsetof(Mount, where) +Mount.Options, config_parse_string, 0, offsetof(Mount, parameters_fragment.options) +Mount.Type, config_parse_string, 0, offsetof(Mount, parameters_fragment.fstype) +Mount.TimeoutSec, config_parse_sec, 0, offsetof(Mount, timeout_usec) +Mount.DirectoryMode, config_parse_mode, 0, offsetof(Mount, directory_mode) +Mount.SloppyOptions, config_parse_bool, 0, offsetof(Mount, sloppy_options) +EXEC_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl +CGROUP_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl +KILL_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl +m4_dnl +Automount.Where, config_parse_path, 0, offsetof(Automount, where) +Automount.DirectoryMode, config_parse_mode, 0, offsetof(Automount, directory_mode) +Automount.TimeoutIdleSec, config_parse_sec, 0, offsetof(Automount, timeout_idle_usec) +m4_dnl +Swap.What, config_parse_path, 0, offsetof(Swap, parameters_fragment.what) +Swap.Priority, config_parse_int, 0, offsetof(Swap, parameters_fragment.priority) +Swap.Options, config_parse_string, 0, offsetof(Swap, parameters_fragment.options) +Swap.TimeoutSec, config_parse_sec, 0, offsetof(Swap, timeout_usec) +EXEC_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl +CGROUP_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl +KILL_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl +m4_dnl +Timer.OnCalendar, config_parse_timer, 0, 0 +Timer.OnActiveSec, config_parse_timer, 0, 0 +Timer.OnBootSec, config_parse_timer, 0, 0 +Timer.OnStartupSec, config_parse_timer, 0, 0 +Timer.OnUnitActiveSec, config_parse_timer, 0, 0 +Timer.OnUnitInactiveSec, config_parse_timer, 0, 0 +Timer.Persistent, config_parse_bool, 0, offsetof(Timer, persistent) +Timer.WakeSystem, config_parse_bool, 0, offsetof(Timer, wake_system) +Timer.RemainAfterElapse, config_parse_bool, 0, offsetof(Timer, remain_after_elapse) +Timer.AccuracySec, config_parse_sec, 0, offsetof(Timer, accuracy_usec) +Timer.RandomizedDelaySec, config_parse_sec, 0, offsetof(Timer, random_usec) +Timer.Unit, config_parse_trigger_unit, 0, 0 +m4_dnl +Path.PathExists, config_parse_path_spec, 0, 0 +Path.PathExistsGlob, config_parse_path_spec, 0, 0 +Path.PathChanged, config_parse_path_spec, 0, 0 +Path.PathModified, config_parse_path_spec, 0, 0 +Path.DirectoryNotEmpty, config_parse_path_spec, 0, 0 +Path.Unit, config_parse_trigger_unit, 0, 0 +Path.MakeDirectory, config_parse_bool, 0, offsetof(Path, make_directory) +Path.DirectoryMode, config_parse_mode, 0, offsetof(Path, directory_mode) +m4_dnl +CGROUP_CONTEXT_CONFIG_ITEMS(Slice)m4_dnl +m4_dnl +CGROUP_CONTEXT_CONFIG_ITEMS(Scope)m4_dnl +KILL_CONTEXT_CONFIG_ITEMS(Scope)m4_dnl +Scope.TimeoutStopSec, config_parse_sec, 0, offsetof(Scope, timeout_stop_usec) +m4_dnl The [Install] section is ignored here. +Install.Alias, NULL, 0, 0 +Install.WantedBy, NULL, 0, 0 +Install.RequiredBy, NULL, 0, 0 +Install.Also, NULL, 0, 0 +Install.DefaultInstance, NULL, 0, 0 diff --git a/src/libcore/load-fragment.c b/src/libcore/load-fragment.c new file mode 100644 index 0000000000..86b4fb071b --- /dev/null +++ b/src/libcore/load-fragment.c @@ -0,0 +1,4083 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2012 Holger Hans Peter Freyther + + 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 +#include +#include +#ifdef HAVE_SECCOMP +#include +#endif +#include +#include +#include +#include + +#include "af-list.h" +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-internal.h" +#include "bus-util.h" +#include "cap-list.h" +#include "capability-util.h" +#include "cgroup.h" +#include "conf-parser.h" +#include "cpu-set-util.h" +#include "env-util.h" +#include "errno-list.h" +#include "escape.h" +#include "fd-util.h" +#include "fs-util.h" +#include "ioprio.h" +#include "load-fragment.h" +#include "log.h" +#include "missing.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "rlimit-util.h" +#ifdef HAVE_SECCOMP +#include "seccomp-util.h" +#endif +#include "securebits.h" +#include "signal-util.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "unit-printf.h" +#include "unit.h" +#include "utf8.h" +#include "web-util.h" + +int config_parse_warn_compat( + 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) { + Disabled reason = ltype; + + switch(reason) { + case DISABLED_CONFIGURATION: + log_syntax(unit, LOG_DEBUG, filename, line, 0, + "Support for option %s= has been disabled at compile time and it is ignored", lvalue); + break; + case DISABLED_LEGACY: + log_syntax(unit, LOG_INFO, filename, line, 0, + "Support for option %s= has been removed and it is ignored", lvalue); + break; + case DISABLED_EXPERIMENTAL: + log_syntax(unit, LOG_INFO, filename, line, 0, + "Support for option %s= has not yet been enabled and it is ignored", lvalue); + break; + }; + + return 0; +} + +int config_parse_unit_deps( + 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) { + + UnitDependency d = ltype; + Unit *u = userdata; + const char *p; + + assert(filename); + assert(lvalue); + assert(rvalue); + + p = rvalue; + for (;;) { + _cleanup_free_ char *word = NULL, *k = NULL; + int r; + + r = extract_first_word(&p, &word, NULL, EXTRACT_RETAIN_ESCAPE); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue); + break; + } + + r = unit_name_printf(u, word, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); + continue; + } + + r = unit_add_dependency_by_name(u, d, k, NULL, true); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add dependency on %s, ignoring: %m", k); + } + + return 0; +} + +int config_parse_obsolete_unit_deps( + 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) { + + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Unit dependency type %s= is obsolete, replacing by %s=, please update your unit file", lvalue, unit_dependency_to_string(ltype)); + + return config_parse_unit_deps(unit, filename, line, section, section_line, lvalue, ltype, rvalue, data, userdata); +} + +int config_parse_unit_string_printf( + 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) { + + _cleanup_free_ char *k = NULL; + Unit *u = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + r = unit_full_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); + return 0; + } + + return config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); +} + +int config_parse_unit_strv_printf( + 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) { + + Unit *u = userdata; + _cleanup_free_ char *k = NULL; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + r = unit_full_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); + return 0; + } + + return config_parse_strv(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); +} + +int config_parse_unit_path_printf( + 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) { + + _cleanup_free_ char *k = NULL; + Unit *u = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + r = unit_full_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); + return 0; + } + + return config_parse_path(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); +} + +int config_parse_unit_path_strv_printf( + 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) { + + char ***x = data; + const char *word, *state; + Unit *u = userdata; + size_t l; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + FOREACH_WORD_QUOTED(word, l, rvalue, state) { + _cleanup_free_ char *k = NULL; + char t[l+1]; + + memcpy(t, word, l); + t[l] = 0; + + r = unit_full_printf(u, t, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", t); + return 0; + } + + if (!utf8_is_valid(k)) { + log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); + return 0; + } + + if (!path_is_absolute(k)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Symlink path %s is not absolute, ignoring: %m", k); + return 0; + } + + path_kill_slashes(k); + + r = strv_push(x, k); + if (r < 0) + return log_oom(); + + k = NULL; + } + if (!isempty(state)) + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid syntax, ignoring."); + + return 0; +} + +int config_parse_socket_listen(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) { + + _cleanup_free_ SocketPort *p = NULL; + SocketPort *tail; + Socket *s; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + s = SOCKET(data); + + if (isempty(rvalue)) { + /* An empty assignment removes all ports */ + socket_free_ports(s); + return 0; + } + + p = new0(SocketPort, 1); + if (!p) + return log_oom(); + + if (ltype != SOCKET_SOCKET) { + + p->type = ltype; + r = unit_full_printf(UNIT(s), rvalue, &p->path); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); + return 0; + } + + path_kill_slashes(p->path); + + } else if (streq(lvalue, "ListenNetlink")) { + _cleanup_free_ char *k = NULL; + + p->type = SOCKET_SOCKET; + r = unit_full_printf(UNIT(s), rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); + return 0; + } + + r = socket_address_parse_netlink(&p->address, k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse address value, ignoring: %s", rvalue); + return 0; + } + + } else { + _cleanup_free_ char *k = NULL; + + p->type = SOCKET_SOCKET; + r = unit_full_printf(UNIT(s), rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r,"Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); + return 0; + } + + r = socket_address_parse_and_warn(&p->address, k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse address value, ignoring: %s", rvalue); + return 0; + } + + if (streq(lvalue, "ListenStream")) + p->address.type = SOCK_STREAM; + else if (streq(lvalue, "ListenDatagram")) + p->address.type = SOCK_DGRAM; + else { + assert(streq(lvalue, "ListenSequentialPacket")); + p->address.type = SOCK_SEQPACKET; + } + + if (socket_address_family(&p->address) != AF_LOCAL && p->address.type == SOCK_SEQPACKET) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Address family not supported, ignoring: %s", rvalue); + return 0; + } + } + + p->fd = -1; + p->auxiliary_fds = NULL; + p->n_auxiliary_fds = 0; + p->socket = s; + + if (s->ports) { + LIST_FIND_TAIL(port, s->ports, tail); + LIST_INSERT_AFTER(port, s->ports, tail, p); + } else + LIST_PREPEND(port, s->ports, p); + p = NULL; + + return 0; +} + +int config_parse_socket_protocol(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) { + Socket *s; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + s = SOCKET(data); + + if (streq(rvalue, "udplite")) + s->socket_protocol = IPPROTO_UDPLITE; + else if (streq(rvalue, "sctp")) + s->socket_protocol = IPPROTO_SCTP; + else { + log_syntax(unit, LOG_ERR, filename, line, 0, "Socket protocol not supported, ignoring: %s", rvalue); + return 0; + } + + return 0; +} + +int config_parse_socket_bind(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) { + + Socket *s; + SocketAddressBindIPv6Only b; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + s = SOCKET(data); + + b = socket_address_bind_ipv6_only_from_string(rvalue); + if (b < 0) { + int r; + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse bind IPv6 only value, ignoring: %s", rvalue); + return 0; + } + + s->bind_ipv6_only = r ? SOCKET_ADDRESS_IPV6_ONLY : SOCKET_ADDRESS_BOTH; + } else + s->bind_ipv6_only = b; + + return 0; +} + +int config_parse_exec_nice(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) { + + ExecContext *c = data; + int priority, r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atoi(rvalue, &priority); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse nice priority, ignoring: %s", rvalue); + return 0; + } + + if (priority < PRIO_MIN || priority >= PRIO_MAX) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Nice priority out of range, ignoring: %s", rvalue); + return 0; + } + + c->nice = priority; + c->nice_set = true; + + return 0; +} + +int config_parse_exec_oom_score_adjust(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) { + + ExecContext *c = data; + int oa, r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atoi(rvalue, &oa); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse the OOM score adjust value, ignoring: %s", rvalue); + return 0; + } + + if (oa < OOM_SCORE_ADJ_MIN || oa > OOM_SCORE_ADJ_MAX) { + log_syntax(unit, LOG_ERR, filename, line, 0, "OOM score adjust value out of range, ignoring: %s", rvalue); + return 0; + } + + c->oom_score_adjust = oa; + c->oom_score_adjust_set = true; + + return 0; +} + +int config_parse_exec( + 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) { + + ExecCommand **e = data; + const char *p; + bool semicolon; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(e); + + e += ltype; + rvalue += strspn(rvalue, WHITESPACE); + + if (isempty(rvalue)) { + /* An empty assignment resets the list */ + *e = exec_command_free_list(*e); + return 0; + } + + p = rvalue; + do { + _cleanup_free_ char *path = NULL, *firstword = NULL; + bool separate_argv0 = false, ignore = false; + _cleanup_free_ ExecCommand *nce = NULL; + _cleanup_strv_free_ char **n = NULL; + size_t nlen = 0, nbufsize = 0; + char *f; + int i; + + semicolon = false; + + r = extract_first_word_and_warn(&p, &firstword, WHITESPACE, EXTRACT_QUOTES|EXTRACT_CUNESCAPE, unit, filename, line, rvalue); + if (r <= 0) + return 0; + + f = firstword; + for (i = 0; i < 2; i++) { + /* We accept an absolute path as first argument, or + * alternatively an absolute prefixed with @ to allow + * overriding of argv[0]. */ + if (*f == '-' && !ignore) + ignore = true; + else if (*f == '@' && !separate_argv0) + separate_argv0 = true; + else + break; + f++; + } + + if (isempty(f)) { + /* First word is either "-" or "@" with no command. */ + log_syntax(unit, LOG_ERR, filename, line, 0, "Empty path in command line, ignoring: \"%s\"", rvalue); + return 0; + } + if (!string_is_safe(f)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Executable path contains special characters, ignoring: %s", rvalue); + return 0; + } + if (!path_is_absolute(f)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Executable path is not absolute, ignoring: %s", rvalue); + return 0; + } + if (endswith(f, "/")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Executable path specifies a directory, ignoring: %s", rvalue); + return 0; + } + + if (f == firstword) { + path = firstword; + firstword = NULL; + } else { + path = strdup(f); + if (!path) + return log_oom(); + } + + if (!separate_argv0) { + if (!GREEDY_REALLOC(n, nbufsize, nlen + 2)) + return log_oom(); + f = strdup(path); + if (!f) + return log_oom(); + n[nlen++] = f; + n[nlen] = NULL; + } + + path_kill_slashes(path); + + while (!isempty(p)) { + _cleanup_free_ char *word = NULL; + + /* Check explicitly for an unquoted semicolon as + * command separator token. */ + if (p[0] == ';' && (!p[1] || strchr(WHITESPACE, p[1]))) { + p++; + p += strspn(p, WHITESPACE); + semicolon = true; + break; + } + + /* Check for \; explicitly, to not confuse it with \\; + * or "\;" or "\\;" etc. extract_first_word would + * return the same for all of those. */ + if (p[0] == '\\' && p[1] == ';' && (!p[2] || strchr(WHITESPACE, p[2]))) { + p += 2; + p += strspn(p, WHITESPACE); + if (!GREEDY_REALLOC(n, nbufsize, nlen + 2)) + return log_oom(); + f = strdup(";"); + if (!f) + return log_oom(); + n[nlen++] = f; + n[nlen] = NULL; + continue; + } + + r = extract_first_word_and_warn(&p, &word, WHITESPACE, EXTRACT_QUOTES|EXTRACT_CUNESCAPE, unit, filename, line, rvalue); + if (r == 0) + break; + else if (r < 0) + return 0; + + if (!GREEDY_REALLOC(n, nbufsize, nlen + 2)) + return log_oom(); + n[nlen++] = word; + n[nlen] = NULL; + word = NULL; + } + + if (!n || !n[0]) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Empty executable name or zeroeth argument, ignoring: %s", rvalue); + return 0; + } + + nce = new0(ExecCommand, 1); + if (!nce) + return log_oom(); + + nce->argv = n; + nce->path = path; + nce->ignore = ignore; + + exec_command_append_list(e, nce); + + /* Do not _cleanup_free_ these. */ + n = NULL; + path = NULL; + nce = NULL; + + rvalue = p; + } while (semicolon); + + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_service_type, service_type, ServiceType, "Failed to parse service type"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_service_restart, service_restart, ServiceRestart, "Failed to parse service restart specifier"); + +int config_parse_socket_bindtodevice( + 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) { + + Socket *s = data; + char *n; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (rvalue[0] && !streq(rvalue, "*")) { + if (!ifname_valid(rvalue)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is invalid, ignoring: %s", rvalue); + return 0; + } + + n = strdup(rvalue); + if (!n) + return log_oom(); + } else + n = NULL; + + free(s->bind_to_device); + s->bind_to_device = n; + + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_output, exec_output, ExecOutput, "Failed to parse output specifier"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_input, exec_input, ExecInput, "Failed to parse input specifier"); + +int config_parse_exec_io_class(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) { + + ExecContext *c = data; + int x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + x = ioprio_class_from_string(rvalue); + if (x < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse IO scheduling class, ignoring: %s", rvalue); + return 0; + } + + c->ioprio = IOPRIO_PRIO_VALUE(x, IOPRIO_PRIO_DATA(c->ioprio)); + c->ioprio_set = true; + + return 0; +} + +int config_parse_exec_io_priority(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) { + + ExecContext *c = data; + int i, r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atoi(rvalue, &i); + if (r < 0 || i < 0 || i >= IOPRIO_BE_NR) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse IO priority, ignoring: %s", rvalue); + return 0; + } + + c->ioprio = IOPRIO_PRIO_VALUE(IOPRIO_PRIO_CLASS(c->ioprio), i); + c->ioprio_set = true; + + return 0; +} + +int config_parse_exec_cpu_sched_policy(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) { + + + ExecContext *c = data; + int x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + x = sched_policy_from_string(rvalue); + if (x < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse CPU scheduling policy, ignoring: %s", rvalue); + return 0; + } + + c->cpu_sched_policy = x; + /* Moving to or from real-time policy? We need to adjust the priority */ + c->cpu_sched_priority = CLAMP(c->cpu_sched_priority, sched_get_priority_min(x), sched_get_priority_max(x)); + c->cpu_sched_set = true; + + return 0; +} + +int config_parse_exec_cpu_sched_prio(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) { + + ExecContext *c = data; + int i, min, max, r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atoi(rvalue, &i); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CPU scheduling policy, ignoring: %s", rvalue); + return 0; + } + + /* On Linux RR/FIFO range from 1 to 99 and OTHER/BATCH may only be 0 */ + min = sched_get_priority_min(c->cpu_sched_policy); + max = sched_get_priority_max(c->cpu_sched_policy); + + if (i < min || i > max) { + log_syntax(unit, LOG_ERR, filename, line, 0, "CPU scheduling priority is out of range, ignoring: %s", rvalue); + return 0; + } + + c->cpu_sched_priority = i; + c->cpu_sched_set = true; + + return 0; +} + +int config_parse_exec_cpu_affinity(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) { + + ExecContext *c = data; + _cleanup_cpu_free_ cpu_set_t *cpuset = NULL; + int ncpus; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + ncpus = parse_cpu_set_and_warn(rvalue, &cpuset, unit, filename, line, lvalue); + if (ncpus < 0) + return ncpus; + + if (c->cpuset) + CPU_FREE(c->cpuset); + + if (ncpus == 0) + /* An empty assignment resets the CPU list */ + c->cpuset = NULL; + else { + c->cpuset = cpuset; + cpuset = NULL; + } + c->cpuset_ncpus = ncpus; + + return 0; +} + +int config_parse_exec_secure_bits(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) { + + ExecContext *c = data; + size_t l; + const char *word, *state; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* An empty assignment resets the field */ + c->secure_bits = 0; + return 0; + } + + FOREACH_WORD_QUOTED(word, l, rvalue, state) { + if (first_word(word, "keep-caps")) + c->secure_bits |= 1<secure_bits |= 1<secure_bits |= 1<secure_bits |= 1<secure_bits |= 1<secure_bits |= 1< replace */ + *capability_set = sum; + else + /* previous data -> merge */ + *capability_set |= sum; + + return 0; +} + +int config_parse_limit( + 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) { + + struct rlimit **rl = data, d = {}; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = rlimit_parse(ltype, rvalue, &d); + if (r == -EILSEQ) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Soft resource limit chosen higher than hard limit, ignoring: %s", rvalue); + return 0; + } + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", rvalue); + return 0; + } + + if (rl[ltype]) + *rl[ltype] = d; + else { + rl[ltype] = newdup(struct rlimit, &d, 1); + if (!rl[ltype]) + return log_oom(); + } + + return 0; +} + +#ifdef HAVE_SYSV_COMPAT +int config_parse_sysv_priority(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) { + + int *priority = data; + int i, r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atoi(rvalue, &i); + if (r < 0 || i < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse SysV start priority, ignoring: %s", rvalue); + return 0; + } + + *priority = (int) i; + return 0; +} +#endif + +DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_utmp_mode, exec_utmp_mode, ExecUtmpMode, "Failed to parse utmp mode"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_kill_mode, kill_mode, KillMode, "Failed to parse kill mode"); + +int config_parse_exec_mount_flags(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 long flags = 0; + ExecContext *c = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(rvalue, "shared")) + flags = MS_SHARED; + else if (streq(rvalue, "slave")) + flags = MS_SLAVE; + else if (streq(rvalue, "private")) + flags = MS_PRIVATE; + else { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse mount flag %s, ignoring.", rvalue); + return 0; + } + + c->mount_flags = flags; + + return 0; +} + +int config_parse_exec_selinux_context( + 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) { + + ExecContext *c = data; + Unit *u = userdata; + bool ignore; + char *k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + c->selinux_context = mfree(c->selinux_context); + c->selinux_context_ignore = false; + return 0; + } + + if (rvalue[0] == '-') { + ignore = true; + rvalue++; + } else + ignore = false; + + r = unit_name_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); + return 0; + } + + free(c->selinux_context); + c->selinux_context = k; + c->selinux_context_ignore = ignore; + + return 0; +} + +int config_parse_exec_apparmor_profile( + 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) { + + ExecContext *c = data; + Unit *u = userdata; + bool ignore; + char *k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + c->apparmor_profile = mfree(c->apparmor_profile); + c->apparmor_profile_ignore = false; + return 0; + } + + if (rvalue[0] == '-') { + ignore = true; + rvalue++; + } else + ignore = false; + + r = unit_name_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); + return 0; + } + + free(c->apparmor_profile); + c->apparmor_profile = k; + c->apparmor_profile_ignore = ignore; + + return 0; +} + +int config_parse_exec_smack_process_label( + 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) { + + ExecContext *c = data; + Unit *u = userdata; + bool ignore; + char *k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + c->smack_process_label = mfree(c->smack_process_label); + c->smack_process_label_ignore = false; + return 0; + } + + if (rvalue[0] == '-') { + ignore = true; + rvalue++; + } else + ignore = false; + + r = unit_name_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); + return 0; + } + + free(c->smack_process_label); + c->smack_process_label = k; + c->smack_process_label_ignore = ignore; + + return 0; +} + +int config_parse_timer(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) { + + Timer *t = data; + usec_t u = 0; + TimerValue *v; + TimerBase b; + CalendarSpec *c = NULL; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets list */ + timer_free_values(t); + return 0; + } + + b = timer_base_from_string(lvalue); + if (b < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse timer base, ignoring: %s", lvalue); + return 0; + } + + if (b == TIMER_CALENDAR) { + if (calendar_spec_from_string(rvalue, &c) < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse calendar specification, ignoring: %s", rvalue); + return 0; + } + } else { + if (parse_sec(rvalue, &u) < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse timer value, ignoring: %s", rvalue); + return 0; + } + } + + v = new0(TimerValue, 1); + if (!v) { + calendar_spec_free(c); + return log_oom(); + } + + v->base = b; + v->value = u; + v->calendar_spec = c; + + LIST_PREPEND(value, t->values, v); + + return 0; +} + +int config_parse_trigger_unit( + 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) { + + _cleanup_free_ char *p = NULL; + Unit *u = data; + UnitType type; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (!set_isempty(u->dependencies[UNIT_TRIGGERS])) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Multiple units to trigger specified, ignoring: %s", rvalue); + return 0; + } + + r = unit_name_printf(u, rvalue, &p); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); + return 0; + } + + type = unit_name_to_type(p); + if (type < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Unit type not valid, ignoring: %s", rvalue); + return 0; + } + + if (type == u->type) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Trigger cannot be of same type, ignoring: %s", rvalue); + return 0; + } + + r = unit_add_two_dependencies_by_name(u, UNIT_BEFORE, UNIT_TRIGGERS, p, NULL, true); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add trigger on %s, ignoring: %m", p); + return 0; + } + + return 0; +} + +int config_parse_path_spec(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) { + + Path *p = data; + PathSpec *s; + PathType b; + _cleanup_free_ char *k = NULL; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment clears list */ + path_free_specs(p); + return 0; + } + + b = path_type_from_string(lvalue); + if (b < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse path type, ignoring: %s", lvalue); + return 0; + } + + r = unit_full_printf(UNIT(p), rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s. Ignoring.", rvalue); + return 0; + } + + if (!path_is_absolute(k)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Path is not absolute, ignoring: %s", k); + return 0; + } + + s = new0(PathSpec, 1); + if (!s) + return log_oom(); + + s->unit = UNIT(p); + s->path = path_kill_slashes(k); + k = NULL; + s->type = b; + s->inotify_fd = -1; + + LIST_PREPEND(spec, p->specs, s); + + return 0; +} + +int config_parse_socket_service( + 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) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *p = NULL; + Socket *s = data; + Unit *x; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = unit_name_printf(UNIT(s), rvalue, &p); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); + return 0; + } + + if (!endswith(p, ".service")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Unit must be of type service, ignoring: %s", rvalue); + return 0; + } + + r = manager_load_unit(UNIT(s)->manager, p, NULL, &error, &x); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to load unit %s, ignoring: %s", rvalue, bus_error_message(&error, r)); + return 0; + } + + unit_ref_set(&s->service, x); + + return 0; +} + +int config_parse_fdname( + 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) { + + _cleanup_free_ char *p = NULL; + Socket *s = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + s->fdname = mfree(s->fdname); + return 0; + } + + r = unit_name_printf(UNIT(s), rvalue, &p); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); + return 0; + } + + if (!fdname_is_valid(p)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid file descriptor name, ignoring: %s", p); + return 0; + } + + free(s->fdname); + s->fdname = p; + p = NULL; + + return 0; +} + +int config_parse_service_sockets( + 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) { + + Service *s = data; + const char *p; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + p = rvalue; + for (;;) { + _cleanup_free_ char *word = NULL, *k = NULL; + + r = extract_first_word(&p, &word, NULL, 0); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Trailing garbage in sockets, ignoring: %s", rvalue); + break; + } + + r = unit_name_printf(UNIT(s), word, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); + continue; + } + + if (!endswith(k, ".socket")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Unit must be of type socket, ignoring: %s", k); + continue; + } + + r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_WANTS, UNIT_AFTER, k, NULL, true); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add dependency on %s, ignoring: %m", k); + + r = unit_add_dependency_by_name(UNIT(s), UNIT_TRIGGERED_BY, k, NULL, true); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add dependency on %s, ignoring: %m", k); + } + + return 0; +} + +int config_parse_bus_name( + 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) { + + _cleanup_free_ char *k = NULL; + Unit *u = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + r = unit_full_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); + return 0; + } + + if (!service_name_is_valid(k)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid bus name %s, ignoring.", k); + return 0; + } + + return config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); +} + +int config_parse_service_timeout( + 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) { + + Service *s = userdata; + usec_t usec; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(s); + + /* This is called for three cases: TimeoutSec=, TimeoutStopSec= and TimeoutStartSec=. */ + + r = parse_sec(rvalue, &usec); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s= parameter, ignoring: %s", lvalue, rvalue); + return 0; + } + + /* Traditionally, these options accepted 0 to disable the timeouts. However, a timeout of 0 suggests it happens + * immediately, hence fix this to become USEC_INFINITY instead. This is in-line with how we internally handle + * all other timeouts. */ + if (usec <= 0) + usec = USEC_INFINITY; + + if (!streq(lvalue, "TimeoutStopSec")) { + s->start_timeout_defined = true; + s->timeout_start_usec = usec; + } + + if (!streq(lvalue, "TimeoutStartSec")) + s->timeout_stop_usec = usec; + + return 0; +} + +int config_parse_sec_fix_0( + 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) { + + usec_t *usec = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(usec); + + /* This is pretty much like config_parse_sec(), except that this treats a time of 0 as infinity, for + * compatibility with older versions of systemd where 0 instead of infinity was used as indicator to turn off a + * timeout. */ + + r = parse_sec(rvalue, usec); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s= parameter, ignoring: %s", lvalue, rvalue); + return 0; + } + + if (*usec <= 0) + *usec = USEC_INFINITY; + + return 0; +} + +int config_parse_busname_service( + 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) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + BusName *n = data; + int r; + Unit *x; + _cleanup_free_ char *p = NULL; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = unit_name_printf(UNIT(n), rvalue, &p); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); + return 0; + } + + if (!endswith(p, ".service")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Unit must be of type service, ignoring: %s", rvalue); + return 0; + } + + r = manager_load_unit(UNIT(n)->manager, p, NULL, &error, &x); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to load unit %s, ignoring: %s", rvalue, bus_error_message(&error, r)); + return 0; + } + + unit_ref_set(&n->service, x); + + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_bus_policy_world, bus_policy_access, BusPolicyAccess, "Failed to parse bus name policy access"); + +int config_parse_bus_policy( + 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) { + + _cleanup_free_ BusNamePolicy *p = NULL; + _cleanup_free_ char *id_str = NULL; + BusName *busname = data; + char *access_str; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + p = new0(BusNamePolicy, 1); + if (!p) + return log_oom(); + + if (streq(lvalue, "AllowUser")) + p->type = BUSNAME_POLICY_TYPE_USER; + else if (streq(lvalue, "AllowGroup")) + p->type = BUSNAME_POLICY_TYPE_GROUP; + else + assert_not_reached("Unknown lvalue"); + + id_str = strdup(rvalue); + if (!id_str) + return log_oom(); + + access_str = strpbrk(id_str, WHITESPACE); + if (!access_str) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid busname policy value '%s'", rvalue); + return 0; + } + + *access_str = '\0'; + access_str++; + access_str += strspn(access_str, WHITESPACE); + + p->access = bus_policy_access_from_string(access_str); + if (p->access < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid busname policy access type '%s'", access_str); + return 0; + } + + p->name = id_str; + id_str = NULL; + + LIST_PREPEND(policy, busname->policy, p); + p = NULL; + + return 0; +} + +int config_parse_working_directory( + 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) { + + ExecContext *c = data; + Unit *u = userdata; + bool missing_ok; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(c); + assert(u); + + if (rvalue[0] == '-') { + missing_ok = true; + rvalue++; + } else + missing_ok = false; + + if (streq(rvalue, "~")) { + c->working_directory_home = true; + c->working_directory = mfree(c->working_directory); + } else { + _cleanup_free_ char *k = NULL; + + r = unit_full_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in working directory path '%s', ignoring: %m", rvalue); + return 0; + } + + path_kill_slashes(k); + + if (!utf8_is_valid(k)) { + log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); + return 0; + } + + if (!path_is_absolute(k)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Working directory path '%s' is not absolute, ignoring.", rvalue); + return 0; + } + + free(c->working_directory); + c->working_directory = k; + k = NULL; + + c->working_directory_home = false; + } + + c->working_directory_missing_ok = missing_ok; + return 0; +} + +int config_parse_unit_env_file(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) { + + char ***env = data; + Unit *u = userdata; + _cleanup_free_ char *n = NULL; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment frees the list */ + *env = strv_free(*env); + return 0; + } + + r = unit_full_printf(u, rvalue, &n); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); + return 0; + } + + if (!path_is_absolute(n[0] == '-' ? n + 1 : n)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Path '%s' is not absolute, ignoring.", n); + return 0; + } + + r = strv_extend(env, n); + if (r < 0) + return log_oom(); + + return 0; +} + +int config_parse_environ(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) { + + Unit *u = userdata; + char*** env = data; + const char *word, *state; + size_t l; + _cleanup_free_ char *k = NULL; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + *env = strv_free(*env); + return 0; + } + + if (u) { + r = unit_full_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); + return 0; + } + } + + if (!k) { + k = strdup(rvalue); + if (!k) + return log_oom(); + } + + FOREACH_WORD_QUOTED(word, l, k, state) { + _cleanup_free_ char *n = NULL; + char **x; + + r = cunescape_length(word, l, 0, &n); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Couldn't unescape assignment, ignoring: %s", rvalue); + continue; + } + + if (!env_assignment_is_valid(n)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid environment assignment, ignoring: %s", rvalue); + continue; + } + + x = strv_env_set(*env, n); + if (!x) + return log_oom(); + + strv_free(*env); + *env = x; + } + if (!isempty(state)) + log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); + + return 0; +} + +int config_parse_pass_environ(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) { + + const char *whole_rvalue = rvalue; + char*** passenv = data; + _cleanup_strv_free_ char **n = NULL; + size_t nlen = 0, nbufsize = 0; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + *passenv = strv_free(*passenv); + return 0; + } + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&rvalue, &word, WHITESPACE, EXTRACT_QUOTES); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Trailing garbage in %s, ignoring: %s", lvalue, whole_rvalue); + break; + } + + if (!env_name_is_valid(word)) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Invalid environment name for %s, ignoring: %s", lvalue, word); + continue; + } + + if (!GREEDY_REALLOC(n, nbufsize, nlen + 2)) + return log_oom(); + n[nlen++] = word; + n[nlen] = NULL; + word = NULL; + } + + if (n) { + r = strv_extend_strv(passenv, n, true); + if (r < 0) + return r; + } + + return 0; +} + +int config_parse_ip_tos(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) { + + int *ip_tos = data, x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + x = ip_tos_from_string(rvalue); + if (x < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse IP TOS value, ignoring: %s", rvalue); + return 0; + } + + *ip_tos = x; + return 0; +} + +int config_parse_unit_condition_path( + 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) { + + _cleanup_free_ char *p = NULL; + Condition **list = data, *c; + ConditionType t = ltype; + bool trigger, negate; + Unit *u = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + *list = condition_free_list(*list); + return 0; + } + + trigger = rvalue[0] == '|'; + if (trigger) + rvalue++; + + negate = rvalue[0] == '!'; + if (negate) + rvalue++; + + r = unit_full_printf(u, rvalue, &p); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); + return 0; + } + + if (!path_is_absolute(p)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Path in condition not absolute, ignoring: %s", p); + return 0; + } + + c = condition_new(t, p, trigger, negate); + if (!c) + return log_oom(); + + LIST_PREPEND(conditions, *list, c); + return 0; +} + +int config_parse_unit_condition_string( + 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) { + + _cleanup_free_ char *s = NULL; + Condition **list = data, *c; + ConditionType t = ltype; + bool trigger, negate; + Unit *u = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + *list = condition_free_list(*list); + return 0; + } + + trigger = rvalue[0] == '|'; + if (trigger) + rvalue++; + + negate = rvalue[0] == '!'; + if (negate) + rvalue++; + + r = unit_full_printf(u, rvalue, &s); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); + return 0; + } + + c = condition_new(t, s, trigger, negate); + if (!c) + return log_oom(); + + LIST_PREPEND(conditions, *list, c); + return 0; +} + +int config_parse_unit_condition_null( + 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) { + + Condition **list = data, *c; + bool trigger, negate; + int b; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + *list = condition_free_list(*list); + return 0; + } + + trigger = rvalue[0] == '|'; + if (trigger) + rvalue++; + + negate = rvalue[0] == '!'; + if (negate) + rvalue++; + + b = parse_boolean(rvalue); + if (b < 0) { + log_syntax(unit, LOG_ERR, filename, line, b, "Failed to parse boolean value in condition, ignoring: %s", rvalue); + return 0; + } + + if (!b) + negate = !negate; + + c = condition_new(CONDITION_NULL, NULL, trigger, negate); + if (!c) + return log_oom(); + + LIST_PREPEND(conditions, *list, c); + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess, "Failed to parse notify access specifier"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_failure_action, failure_action, FailureAction, "Failed to parse failure action specifier"); + +int config_parse_unit_requires_mounts_for( + 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) { + + Unit *u = userdata; + const char *word, *state; + size_t l; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + FOREACH_WORD_QUOTED(word, l, rvalue, state) { + int r; + _cleanup_free_ char *n; + + n = strndup(word, l); + if (!n) + return log_oom(); + + if (!utf8_is_valid(n)) { + log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); + continue; + } + + r = unit_require_mounts_for(u, n); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add required mount for, ignoring: %s", rvalue); + continue; + } + } + if (!isempty(state)) + log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); + + return 0; +} + +int config_parse_documentation(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) { + + Unit *u = userdata; + int r; + char **a, **b; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + u->documentation = strv_free(u->documentation); + return 0; + } + + r = config_parse_unit_strv_printf(unit, filename, line, section, section_line, lvalue, ltype, + rvalue, data, userdata); + if (r < 0) + return r; + + for (a = b = u->documentation; a && *a; a++) { + + if (documentation_url_is_valid(*a)) + *(b++) = *a; + else { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid URL, ignoring: %s", *a); + free(*a); + } + } + if (b) + *b = NULL; + + return r; +} + +#ifdef HAVE_SECCOMP +int config_parse_syscall_filter( + 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) { + + static const char default_syscalls[] = + "execve\0" + "exit\0" + "exit_group\0" + "rt_sigreturn\0" + "sigreturn\0"; + + ExecContext *c = data; + Unit *u = userdata; + bool invert = false; + const char *word, *state; + size_t l; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + c->syscall_filter = set_free(c->syscall_filter); + c->syscall_whitelist = false; + return 0; + } + + if (rvalue[0] == '~') { + invert = true; + rvalue++; + } + + if (!c->syscall_filter) { + c->syscall_filter = set_new(NULL); + if (!c->syscall_filter) + return log_oom(); + + if (invert) + /* Allow everything but the ones listed */ + c->syscall_whitelist = false; + else { + const char *i; + + /* Allow nothing but the ones listed */ + c->syscall_whitelist = true; + + /* Accept default syscalls if we are on a whitelist */ + NULSTR_FOREACH(i, default_syscalls) { + int id; + + id = seccomp_syscall_resolve_name(i); + if (id < 0) + continue; + + r = set_put(c->syscall_filter, INT_TO_PTR(id + 1)); + if (r == 0) + continue; + if (r < 0) + return log_oom(); + } + } + } + + FOREACH_WORD_QUOTED(word, l, rvalue, state) { + _cleanup_free_ char *t = NULL; + int id; + + t = strndup(word, l); + if (!t) + return log_oom(); + + id = seccomp_syscall_resolve_name(t); + if (id < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse system call, ignoring: %s", t); + continue; + } + + /* If we previously wanted to forbid a syscall and now + * we want to allow it, then remove it from the list + */ + if (!invert == c->syscall_whitelist) { + r = set_put(c->syscall_filter, INT_TO_PTR(id + 1)); + if (r == 0) + continue; + if (r < 0) + return log_oom(); + } else + set_remove(c->syscall_filter, INT_TO_PTR(id + 1)); + } + if (!isempty(state)) + log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); + + /* Turn on NNP, but only if it wasn't configured explicitly + * before, and only if we are in user mode. */ + if (!c->no_new_privileges_set && MANAGER_IS_USER(u->manager)) + c->no_new_privileges = true; + + return 0; +} + +int config_parse_syscall_archs( + 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) { + + Set **archs = data; + const char *word, *state; + size_t l; + int r; + + if (isempty(rvalue)) { + *archs = set_free(*archs); + return 0; + } + + r = set_ensure_allocated(archs, NULL); + if (r < 0) + return log_oom(); + + FOREACH_WORD_QUOTED(word, l, rvalue, state) { + _cleanup_free_ char *t = NULL; + uint32_t a; + + t = strndup(word, l); + if (!t) + return log_oom(); + + r = seccomp_arch_from_string(t, &a); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse system call architecture, ignoring: %s", t); + continue; + } + + r = set_put(*archs, UINT32_TO_PTR(a + 1)); + if (r == 0) + continue; + if (r < 0) + return log_oom(); + } + if (!isempty(state)) + log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); + + return 0; +} + +int config_parse_syscall_errno( + 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) { + + ExecContext *c = data; + int e; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + /* Empty assignment resets to KILL */ + c->syscall_errno = 0; + return 0; + } + + e = errno_from_name(rvalue); + if (e < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse error number, ignoring: %s", rvalue); + return 0; + } + + c->syscall_errno = e; + return 0; +} + +int config_parse_address_families( + 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) { + + ExecContext *c = data; + bool invert = false; + const char *word, *state; + size_t l; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + c->address_families = set_free(c->address_families); + c->address_families_whitelist = false; + return 0; + } + + if (rvalue[0] == '~') { + invert = true; + rvalue++; + } + + if (!c->address_families) { + c->address_families = set_new(NULL); + if (!c->address_families) + return log_oom(); + + c->address_families_whitelist = !invert; + } + + FOREACH_WORD_QUOTED(word, l, rvalue, state) { + _cleanup_free_ char *t = NULL; + int af; + + t = strndup(word, l); + if (!t) + return log_oom(); + + af = af_from_name(t); + if (af <= 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse address family, ignoring: %s", t); + continue; + } + + /* If we previously wanted to forbid an address family and now + * we want to allow it, then remove it from the list + */ + if (!invert == c->address_families_whitelist) { + r = set_put(c->address_families, INT_TO_PTR(af)); + if (r == 0) + continue; + if (r < 0) + return log_oom(); + } else + set_remove(c->address_families, INT_TO_PTR(af)); + } + if (!isempty(state)) + log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); + + return 0; +} +#endif + +int config_parse_unit_slice( + 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) { + + _cleanup_free_ char *k = NULL; + Unit *u = userdata, *slice = NULL; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + r = unit_name_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s. Ignoring.", rvalue); + return 0; + } + + r = manager_load_unit(u->manager, k, NULL, NULL, &slice); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to load slice unit %s. Ignoring.", k); + return 0; + } + + r = unit_set_slice(u, slice); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to assign slice %s to unit %s. Ignoring.", slice->id, u->id); + return 0; + } + + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_device_policy, cgroup_device_policy, CGroupDevicePolicy, "Failed to parse device policy"); + +int config_parse_cpu_shares( + 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) { + + uint64_t *shares = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = cg_cpu_shares_parse(rvalue, shares); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "CPU shares '%s' invalid. Ignoring.", rvalue); + return 0; + } + + return 0; +} + +int config_parse_cpu_quota( + 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) { + + CGroupContext *c = data; + double percent; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + c->cpu_quota_per_sec_usec = USEC_INFINITY; + return 0; + } + + if (!endswith(rvalue, "%")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "CPU quota '%s' not ending in '%%'. Ignoring.", rvalue); + return 0; + } + + if (sscanf(rvalue, "%lf%%", &percent) != 1 || percent <= 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "CPU quota '%s' invalid. Ignoring.", rvalue); + return 0; + } + + c->cpu_quota_per_sec_usec = (usec_t) (percent * USEC_PER_SEC / 100); + + return 0; +} + +int config_parse_memory_limit( + 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) { + + CGroupContext *c = data; + uint64_t bytes; + int r; + + if (isempty(rvalue) || streq(rvalue, "infinity")) { + c->memory_limit = (uint64_t) -1; + return 0; + } + + r = parse_size(rvalue, 1024, &bytes); + if (r < 0 || bytes < 1) { + log_syntax(unit, LOG_ERR, filename, line, r, "Memory limit '%s' invalid. Ignoring.", rvalue); + return 0; + } + + c->memory_limit = bytes; + return 0; +} + +int config_parse_tasks_max( + 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) { + + uint64_t *tasks_max = data, u; + int r; + + if (isempty(rvalue) || streq(rvalue, "infinity")) { + *tasks_max = (uint64_t) -1; + return 0; + } + + r = safe_atou64(rvalue, &u); + if (r < 0 || u < 1) { + log_syntax(unit, LOG_ERR, filename, line, r, "Maximum tasks value '%s' invalid. Ignoring.", rvalue); + return 0; + } + + *tasks_max = u; + return 0; +} + +int config_parse_device_allow( + 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) { + + _cleanup_free_ char *path = NULL, *t = NULL; + CGroupContext *c = data; + CGroupDeviceAllow *a; + const char *m = NULL; + size_t n; + int r; + + if (isempty(rvalue)) { + while (c->device_allow) + cgroup_context_free_device_allow(c, c->device_allow); + + return 0; + } + + r = unit_full_printf(userdata, rvalue, &t); + if(r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to resolve specifiers in %s, ignoring: %m", + rvalue); + } + + n = strcspn(t, WHITESPACE); + + path = strndup(t, n); + if (!path) + return log_oom(); + + if (!startswith(path, "/dev/") && + !startswith(path, "block-") && + !startswith(path, "char-")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device node path '%s'. Ignoring.", path); + return 0; + } + + m = t + n + strspn(t + n, WHITESPACE); + if (isempty(m)) + m = "rwm"; + + if (!in_charset(m, "rwm")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device rights '%s'. Ignoring.", m); + return 0; + } + + a = new0(CGroupDeviceAllow, 1); + if (!a) + return log_oom(); + + a->path = path; + path = NULL; + a->r = !!strchr(m, 'r'); + a->w = !!strchr(m, 'w'); + a->m = !!strchr(m, 'm'); + + LIST_PREPEND(device_allow, c->device_allow, a); + return 0; +} + +int config_parse_io_weight( + 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) { + + uint64_t *weight = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = cg_weight_parse(rvalue, weight); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "IO weight '%s' invalid. Ignoring.", rvalue); + return 0; + } + + return 0; +} + +int config_parse_io_device_weight( + 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) { + + _cleanup_free_ char *path = NULL; + CGroupIODeviceWeight *w; + CGroupContext *c = data; + const char *weight; + uint64_t u; + size_t n; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + while (c->io_device_weights) + cgroup_context_free_io_device_weight(c, c->io_device_weights); + + return 0; + } + + n = strcspn(rvalue, WHITESPACE); + weight = rvalue + n; + weight += strspn(weight, WHITESPACE); + + if (isempty(weight)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Expected block device and device weight. Ignoring."); + return 0; + } + + path = strndup(rvalue, n); + if (!path) + return log_oom(); + + if (!path_startswith(path, "/dev")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device node path '%s'. Ignoring.", path); + return 0; + } + + r = cg_weight_parse(weight, &u); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "IO weight '%s' invalid. Ignoring.", weight); + return 0; + } + + assert(u != CGROUP_WEIGHT_INVALID); + + w = new0(CGroupIODeviceWeight, 1); + if (!w) + return log_oom(); + + w->path = path; + path = NULL; + + w->weight = u; + + LIST_PREPEND(device_weights, c->io_device_weights, w); + return 0; +} + +int config_parse_io_limit( + 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) { + + _cleanup_free_ char *path = NULL; + CGroupIODeviceLimit *l = NULL, *t; + CGroupContext *c = data; + CGroupIOLimitType type; + const char *limit; + uint64_t num; + size_t n; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + type = cgroup_io_limit_type_from_string(lvalue); + assert(type >= 0); + + if (isempty(rvalue)) { + LIST_FOREACH(device_limits, l, c->io_device_limits) + l->limits[type] = cgroup_io_limit_defaults[type]; + return 0; + } + + n = strcspn(rvalue, WHITESPACE); + limit = rvalue + n; + limit += strspn(limit, WHITESPACE); + + if (!*limit) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Expected space separated pair of device node and bandwidth. Ignoring."); + return 0; + } + + path = strndup(rvalue, n); + if (!path) + return log_oom(); + + if (!path_startswith(path, "/dev")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device node path '%s'. Ignoring.", path); + return 0; + } + + if (streq("max", limit)) { + num = CGROUP_LIMIT_MAX; + } else { + r = parse_size(limit, 1000, &num); + if (r < 0 || num <= 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "IO Limit '%s' invalid. Ignoring.", rvalue); + return 0; + } + } + + LIST_FOREACH(device_limits, t, c->io_device_limits) { + if (path_equal(path, t->path)) { + l = t; + break; + } + } + + if (!l) { + CGroupIOLimitType ttype; + + l = new0(CGroupIODeviceLimit, 1); + if (!l) + return log_oom(); + + l->path = path; + path = NULL; + for (ttype = 0; ttype < _CGROUP_IO_LIMIT_TYPE_MAX; ttype++) + l->limits[ttype] = cgroup_io_limit_defaults[ttype]; + + LIST_PREPEND(device_limits, c->io_device_limits, l); + } + + l->limits[type] = num; + + return 0; +} + +int config_parse_blockio_weight( + 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) { + + uint64_t *weight = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = cg_blkio_weight_parse(rvalue, weight); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Block IO weight '%s' invalid. Ignoring.", rvalue); + return 0; + } + + return 0; +} + +int config_parse_blockio_device_weight( + 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) { + + _cleanup_free_ char *path = NULL; + CGroupBlockIODeviceWeight *w; + CGroupContext *c = data; + const char *weight; + uint64_t u; + size_t n; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + while (c->blockio_device_weights) + cgroup_context_free_blockio_device_weight(c, c->blockio_device_weights); + + return 0; + } + + n = strcspn(rvalue, WHITESPACE); + weight = rvalue + n; + weight += strspn(weight, WHITESPACE); + + if (isempty(weight)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Expected block device and device weight. Ignoring."); + return 0; + } + + path = strndup(rvalue, n); + if (!path) + return log_oom(); + + if (!path_startswith(path, "/dev")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device node path '%s'. Ignoring.", path); + return 0; + } + + r = cg_blkio_weight_parse(weight, &u); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Block IO weight '%s' invalid. Ignoring.", weight); + return 0; + } + + assert(u != CGROUP_BLKIO_WEIGHT_INVALID); + + w = new0(CGroupBlockIODeviceWeight, 1); + if (!w) + return log_oom(); + + w->path = path; + path = NULL; + + w->weight = u; + + LIST_PREPEND(device_weights, c->blockio_device_weights, w); + return 0; +} + +int config_parse_blockio_bandwidth( + 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) { + + _cleanup_free_ char *path = NULL; + CGroupBlockIODeviceBandwidth *b = NULL, *t; + CGroupContext *c = data; + const char *bandwidth; + uint64_t bytes; + bool read; + size_t n; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + read = streq("BlockIOReadBandwidth", lvalue); + + if (isempty(rvalue)) { + LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { + b->rbps = CGROUP_LIMIT_MAX; + b->wbps = CGROUP_LIMIT_MAX; + } + return 0; + } + + n = strcspn(rvalue, WHITESPACE); + bandwidth = rvalue + n; + bandwidth += strspn(bandwidth, WHITESPACE); + + if (!*bandwidth) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Expected space separated pair of device node and bandwidth. Ignoring."); + return 0; + } + + path = strndup(rvalue, n); + if (!path) + return log_oom(); + + if (!path_startswith(path, "/dev")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device node path '%s'. Ignoring.", path); + return 0; + } + + r = parse_size(bandwidth, 1000, &bytes); + if (r < 0 || bytes <= 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Block IO Bandwidth '%s' invalid. Ignoring.", rvalue); + return 0; + } + + LIST_FOREACH(device_bandwidths, t, c->blockio_device_bandwidths) { + if (path_equal(path, t->path)) { + b = t; + break; + } + } + + if (!t) { + b = new0(CGroupBlockIODeviceBandwidth, 1); + if (!b) + return log_oom(); + + b->path = path; + path = NULL; + b->rbps = CGROUP_LIMIT_MAX; + b->wbps = CGROUP_LIMIT_MAX; + + LIST_PREPEND(device_bandwidths, c->blockio_device_bandwidths, b); + } + + if (read) + b->rbps = bytes; + else + b->wbps = bytes; + + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_job_mode, job_mode, JobMode, "Failed to parse job mode"); + +int config_parse_job_mode_isolate( + 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) { + + JobMode *m = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse boolean, ignoring: %s", rvalue); + return 0; + } + + *m = r ? JOB_ISOLATE : JOB_REPLACE; + return 0; +} + +int config_parse_runtime_directory( + 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) { + + char***rt = data; + Unit *u = userdata; + const char *word, *state; + size_t l; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + *rt = strv_free(*rt); + return 0; + } + + FOREACH_WORD_QUOTED(word, l, rvalue, state) { + _cleanup_free_ char *t = NULL, *n = NULL; + + t = strndup(word, l); + if (!t) + return log_oom(); + + r = unit_name_printf(u, t, &n); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); + continue; + } + + if (!filename_is_valid(n)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Runtime directory is not valid, ignoring assignment: %s", rvalue); + continue; + } + + r = strv_push(rt, n); + if (r < 0) + return log_oom(); + + n = NULL; + } + if (!isempty(state)) + log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); + + return 0; +} + +int config_parse_set_status( + 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) { + + size_t l; + const char *word, *state; + int r; + ExitStatusSet *status_set = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + /* Empty assignment resets the list */ + if (isempty(rvalue)) { + exit_status_set_free(status_set); + return 0; + } + + FOREACH_WORD(word, l, rvalue, state) { + _cleanup_free_ char *temp; + int val; + Set **set; + + temp = strndup(word, l); + if (!temp) + return log_oom(); + + r = safe_atoi(temp, &val); + if (r < 0) { + val = signal_from_string_try_harder(temp); + + if (val <= 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse value, ignoring: %s", word); + continue; + } + set = &status_set->signal; + } else { + if (val < 0 || val > 255) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Value %d is outside range 0-255, ignoring", val); + continue; + } + set = &status_set->status; + } + + r = set_ensure_allocated(set, NULL); + if (r < 0) + return log_oom(); + + r = set_put(*set, INT_TO_PTR(val)); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Unable to store: %s", word); + return r; + } + } + if (!isempty(state)) + log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); + + return 0; +} + +int config_parse_namespace_path_strv( + 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) { + + char*** sv = data; + const char *prev; + const char *cur; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + *sv = strv_free(*sv); + return 0; + } + + prev = cur = rvalue; + for (;;) { + _cleanup_free_ char *word = NULL; + int offset; + + r = extract_first_word(&cur, &word, NULL, EXTRACT_QUOTES); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Trailing garbage, ignoring: %s", prev); + return 0; + } + + if (!utf8_is_valid(word)) { + log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, word); + prev = cur; + continue; + } + + offset = word[0] == '-'; + if (!path_is_absolute(word + offset)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute path, ignoring: %s", word); + prev = cur; + continue; + } + + path_kill_slashes(word + offset); + + r = strv_push(sv, word); + if (r < 0) + return log_oom(); + + prev = cur; + word = NULL; + } + + return 0; +} + +int config_parse_no_new_privileges( + 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) { + + ExecContext *c = data; + int k; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + k = parse_boolean(rvalue); + if (k < 0) { + log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse boolean value, ignoring: %s", rvalue); + return 0; + } + + c->no_new_privileges = !!k; + c->no_new_privileges_set = true; + + return 0; +} + +int config_parse_protect_home( + 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) { + + ExecContext *c = data; + int k; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + /* Our enum shall be a superset of booleans, hence first try + * to parse as as boolean, and then as enum */ + + k = parse_boolean(rvalue); + if (k > 0) + c->protect_home = PROTECT_HOME_YES; + else if (k == 0) + c->protect_home = PROTECT_HOME_NO; + else { + ProtectHome h; + + h = protect_home_from_string(rvalue); + if (h < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse protect home value, ignoring: %s", rvalue); + return 0; + } + + c->protect_home = h; + } + + return 0; +} + +int config_parse_protect_system( + 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) { + + ExecContext *c = data; + int k; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + /* Our enum shall be a superset of booleans, hence first try + * to parse as as boolean, and then as enum */ + + k = parse_boolean(rvalue); + if (k > 0) + c->protect_system = PROTECT_SYSTEM_YES; + else if (k == 0) + c->protect_system = PROTECT_SYSTEM_NO; + else { + ProtectSystem s; + + s = protect_system_from_string(rvalue); + if (s < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse protect system value, ignoring: %s", rvalue); + return 0; + } + + c->protect_system = s; + } + + return 0; +} + +#define FOLLOW_MAX 8 + +static int open_follow(char **filename, FILE **_f, Set *names, char **_final) { + char *id = NULL; + unsigned c = 0; + int fd, r; + FILE *f; + + assert(filename); + assert(*filename); + assert(_f); + assert(names); + + /* This will update the filename pointer if the loaded file is + * reached by a symlink. The old string will be freed. */ + + for (;;) { + char *target, *name; + + if (c++ >= FOLLOW_MAX) + return -ELOOP; + + path_kill_slashes(*filename); + + /* Add the file name we are currently looking at to + * the names of this unit, but only if it is a valid + * unit name. */ + name = basename(*filename); + if (unit_name_is_valid(name, UNIT_NAME_ANY)) { + + id = set_get(names, name); + if (!id) { + id = strdup(name); + if (!id) + return -ENOMEM; + + r = set_consume(names, id); + if (r < 0) + return r; + } + } + + /* Try to open the file name, but don't if its a symlink */ + fd = open(*filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd >= 0) + break; + + if (errno != ELOOP) + return -errno; + + /* Hmm, so this is a symlink. Let's read the name, and follow it manually */ + r = readlink_and_make_absolute(*filename, &target); + if (r < 0) + return r; + + free(*filename); + *filename = target; + } + + f = fdopen(fd, "re"); + if (!f) { + safe_close(fd); + return -errno; + } + + *_f = f; + *_final = id; + + return 0; +} + +static int merge_by_names(Unit **u, Set *names, const char *id) { + char *k; + int r; + + assert(u); + assert(*u); + assert(names); + + /* Let's try to add in all symlink names we found */ + while ((k = set_steal_first(names))) { + + /* First try to merge in the other name into our + * unit */ + r = unit_merge_by_name(*u, k); + if (r < 0) { + Unit *other; + + /* Hmm, we couldn't merge the other unit into + * ours? Then let's try it the other way + * round */ + + /* If the symlink name we are looking at is unit template, then + we must search for instance of this template */ + if (unit_name_is_valid(k, UNIT_NAME_TEMPLATE)) { + _cleanup_free_ char *instance = NULL; + + r = unit_name_replace_instance(k, (*u)->instance, &instance); + if (r < 0) + return r; + + other = manager_get_unit((*u)->manager, instance); + } else + other = manager_get_unit((*u)->manager, k); + + free(k); + + if (other) { + r = unit_merge(other, *u); + if (r >= 0) { + *u = other; + return merge_by_names(u, names, NULL); + } + } + + return r; + } + + if (id == k) + unit_choose_id(*u, id); + + free(k); + } + + return 0; +} + +static int load_from_path(Unit *u, const char *path) { + _cleanup_set_free_free_ Set *symlink_names = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *filename = NULL; + char *id = NULL; + Unit *merged; + struct stat st; + int r; + + assert(u); + assert(path); + + symlink_names = set_new(&string_hash_ops); + if (!symlink_names) + return -ENOMEM; + + if (path_is_absolute(path)) { + + filename = strdup(path); + if (!filename) + return -ENOMEM; + + r = open_follow(&filename, &f, symlink_names, &id); + if (r < 0) { + filename = mfree(filename); + if (r != -ENOENT) + return r; + } + + } else { + char **p; + + STRV_FOREACH(p, u->manager->lookup_paths.search_path) { + + /* Instead of opening the path right away, we manually + * follow all symlinks and add their name to our unit + * name set while doing so */ + filename = path_make_absolute(path, *p); + if (!filename) + return -ENOMEM; + + if (u->manager->unit_path_cache && + !set_get(u->manager->unit_path_cache, filename)) + r = -ENOENT; + else + r = open_follow(&filename, &f, symlink_names, &id); + if (r >= 0) + break; + filename = mfree(filename); + if (r != -ENOENT) + return r; + + /* Empty the symlink names for the next run */ + set_clear_free(symlink_names); + } + } + + if (!filename) + /* Hmm, no suitable file found? */ + return 0; + + if (!unit_type_may_alias(u->type) && set_size(symlink_names) > 1) { + log_unit_warning(u, "Unit type of %s does not support alias names, refusing loading via symlink.", u->id); + return -ELOOP; + } + + merged = u; + r = merge_by_names(&merged, symlink_names, id); + if (r < 0) + return r; + + if (merged != u) { + u->load_state = UNIT_MERGED; + return 0; + } + + if (fstat(fileno(f), &st) < 0) + return -errno; + + if (null_or_empty(&st)) { + u->load_state = UNIT_MASKED; + u->fragment_mtime = 0; + } else { + u->load_state = UNIT_LOADED; + u->fragment_mtime = timespec_load(&st.st_mtim); + + /* Now, parse the file contents */ + r = config_parse(u->id, filename, f, + UNIT_VTABLE(u)->sections, + config_item_perf_lookup, load_fragment_gperf_lookup, + false, true, false, u); + if (r < 0) + return r; + } + + free(u->fragment_path); + u->fragment_path = filename; + filename = NULL; + + if (u->source_path) { + if (stat(u->source_path, &st) >= 0) + u->source_mtime = timespec_load(&st.st_mtim); + else + u->source_mtime = 0; + } + + return 0; +} + +int unit_load_fragment(Unit *u) { + int r; + Iterator i; + const char *t; + + assert(u); + assert(u->load_state == UNIT_STUB); + assert(u->id); + + if (u->transient) { + u->load_state = UNIT_LOADED; + return 0; + } + + /* First, try to find the unit under its id. We always look + * for unit files in the default directories, to make it easy + * to override things by placing things in /etc/systemd/system */ + r = load_from_path(u, u->id); + if (r < 0) + return r; + + /* Try to find an alias we can load this with */ + if (u->load_state == UNIT_STUB) { + SET_FOREACH(t, u->names, i) { + + if (t == u->id) + continue; + + r = load_from_path(u, t); + if (r < 0) + return r; + + if (u->load_state != UNIT_STUB) + break; + } + } + + /* And now, try looking for it under the suggested (originally linked) path */ + if (u->load_state == UNIT_STUB && u->fragment_path) { + + r = load_from_path(u, u->fragment_path); + if (r < 0) + return r; + + if (u->load_state == UNIT_STUB) + /* Hmm, this didn't work? Then let's get rid + * of the fragment path stored for us, so that + * we don't point to an invalid location. */ + u->fragment_path = mfree(u->fragment_path); + } + + /* Look for a template */ + if (u->load_state == UNIT_STUB && u->instance) { + _cleanup_free_ char *k = NULL; + + r = unit_name_template(u->id, &k); + if (r < 0) + return r; + + r = load_from_path(u, k); + if (r < 0) + return r; + + if (u->load_state == UNIT_STUB) { + SET_FOREACH(t, u->names, i) { + _cleanup_free_ char *z = NULL; + + if (t == u->id) + continue; + + r = unit_name_template(t, &z); + if (r < 0) + return r; + + r = load_from_path(u, z); + if (r < 0) + return r; + + if (u->load_state != UNIT_STUB) + break; + } + } + } + + return 0; +} + +void unit_dump_config_items(FILE *f) { + static const struct { + const ConfigParserCallback callback; + const char *rvalue; + } table[] = { +#if !defined(HAVE_SYSV_COMPAT) || !defined(HAVE_SECCOMP) || !defined(HAVE_PAM) || !defined(HAVE_SELINUX) || !defined(HAVE_SMACK) || !defined(HAVE_APPARMOR) + { config_parse_warn_compat, "NOTSUPPORTED" }, +#endif + { config_parse_int, "INTEGER" }, + { config_parse_unsigned, "UNSIGNED" }, + { config_parse_iec_size, "SIZE" }, + { config_parse_iec_uint64, "SIZE" }, + { config_parse_si_size, "SIZE" }, + { config_parse_bool, "BOOLEAN" }, + { config_parse_string, "STRING" }, + { config_parse_path, "PATH" }, + { config_parse_unit_path_printf, "PATH" }, + { config_parse_strv, "STRING [...]" }, + { config_parse_exec_nice, "NICE" }, + { config_parse_exec_oom_score_adjust, "OOMSCOREADJUST" }, + { config_parse_exec_io_class, "IOCLASS" }, + { config_parse_exec_io_priority, "IOPRIORITY" }, + { config_parse_exec_cpu_sched_policy, "CPUSCHEDPOLICY" }, + { config_parse_exec_cpu_sched_prio, "CPUSCHEDPRIO" }, + { config_parse_exec_cpu_affinity, "CPUAFFINITY" }, + { config_parse_mode, "MODE" }, + { config_parse_unit_env_file, "FILE" }, + { config_parse_output, "OUTPUT" }, + { config_parse_input, "INPUT" }, + { config_parse_log_facility, "FACILITY" }, + { config_parse_log_level, "LEVEL" }, + { config_parse_exec_secure_bits, "SECUREBITS" }, + { config_parse_capability_set, "BOUNDINGSET" }, + { config_parse_limit, "LIMIT" }, + { config_parse_unit_deps, "UNIT [...]" }, + { config_parse_exec, "PATH [ARGUMENT [...]]" }, + { config_parse_service_type, "SERVICETYPE" }, + { config_parse_service_restart, "SERVICERESTART" }, +#ifdef HAVE_SYSV_COMPAT + { config_parse_sysv_priority, "SYSVPRIORITY" }, +#endif + { config_parse_kill_mode, "KILLMODE" }, + { config_parse_signal, "SIGNAL" }, + { config_parse_socket_listen, "SOCKET [...]" }, + { config_parse_socket_bind, "SOCKETBIND" }, + { config_parse_socket_bindtodevice, "NETWORKINTERFACE" }, + { config_parse_sec, "SECONDS" }, + { config_parse_nsec, "NANOSECONDS" }, + { config_parse_namespace_path_strv, "PATH [...]" }, + { config_parse_unit_requires_mounts_for, "PATH [...]" }, + { config_parse_exec_mount_flags, "MOUNTFLAG [...]" }, + { config_parse_unit_string_printf, "STRING" }, + { config_parse_trigger_unit, "UNIT" }, + { config_parse_timer, "TIMER" }, + { config_parse_path_spec, "PATH" }, + { config_parse_notify_access, "ACCESS" }, + { config_parse_ip_tos, "TOS" }, + { config_parse_unit_condition_path, "CONDITION" }, + { config_parse_unit_condition_string, "CONDITION" }, + { config_parse_unit_condition_null, "CONDITION" }, + { config_parse_unit_slice, "SLICE" }, + { config_parse_documentation, "URL" }, + { config_parse_service_timeout, "SECONDS" }, + { config_parse_failure_action, "ACTION" }, + { config_parse_set_status, "STATUS" }, + { config_parse_service_sockets, "SOCKETS" }, + { config_parse_environ, "ENVIRON" }, +#ifdef HAVE_SECCOMP + { config_parse_syscall_filter, "SYSCALLS" }, + { config_parse_syscall_archs, "ARCHS" }, + { config_parse_syscall_errno, "ERRNO" }, + { config_parse_address_families, "FAMILIES" }, +#endif + { config_parse_cpu_shares, "SHARES" }, + { config_parse_memory_limit, "LIMIT" }, + { config_parse_device_allow, "DEVICE" }, + { config_parse_device_policy, "POLICY" }, + { config_parse_io_limit, "LIMIT" }, + { config_parse_io_weight, "WEIGHT" }, + { config_parse_io_device_weight, "DEVICEWEIGHT" }, + { config_parse_blockio_bandwidth, "BANDWIDTH" }, + { config_parse_blockio_weight, "WEIGHT" }, + { config_parse_blockio_device_weight, "DEVICEWEIGHT" }, + { config_parse_long, "LONG" }, + { config_parse_socket_service, "SERVICE" }, +#ifdef HAVE_SELINUX + { config_parse_exec_selinux_context, "LABEL" }, +#endif + { config_parse_job_mode, "MODE" }, + { config_parse_job_mode_isolate, "BOOLEAN" }, + { config_parse_personality, "PERSONALITY" }, + }; + + const char *prev = NULL; + const char *i; + + assert(f); + + NULSTR_FOREACH(i, load_fragment_gperf_nulstr) { + const char *rvalue = "OTHER", *lvalue; + unsigned j; + size_t prefix_len; + const char *dot; + const ConfigPerfItem *p; + + assert_se(p = load_fragment_gperf_lookup(i, strlen(i))); + + dot = strchr(i, '.'); + lvalue = dot ? dot + 1 : i; + prefix_len = dot-i; + + if (dot) + if (!prev || !strneq(prev, i, prefix_len+1)) { + if (prev) + fputc('\n', f); + + fprintf(f, "[%.*s]\n", (int) prefix_len, i); + } + + for (j = 0; j < ELEMENTSOF(table); j++) + if (p->parse == table[j].callback) { + rvalue = table[j].rvalue; + break; + } + + fprintf(f, "%s=%s\n", lvalue, rvalue); + prev = i; + } +} diff --git a/src/libcore/load-fragment.h b/src/libcore/load-fragment.h new file mode 100644 index 0000000000..b36a2e3a02 --- /dev/null +++ b/src/libcore/load-fragment.h @@ -0,0 +1,123 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "unit.h" + +/* Read service data from .desktop file style configuration fragments */ + +int unit_load_fragment(Unit *u); + +void unit_dump_config_items(FILE *f); + +int config_parse_warn_compat(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); +int config_parse_unit_deps(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); +int config_parse_obsolete_unit_deps(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); +int config_parse_unit_string_printf(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); +int config_parse_unit_strv_printf(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); +int config_parse_unit_path_printf(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); +int config_parse_unit_path_strv_printf(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); +int config_parse_documentation(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); +int config_parse_socket_listen(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); +int config_parse_socket_protocol(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); +int config_parse_socket_bind(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); +int config_parse_exec_nice(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); +int config_parse_exec_oom_score_adjust(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); +int config_parse_exec(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); +int config_parse_service_timeout(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); +int config_parse_service_type(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); +int config_parse_service_restart(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); +int config_parse_socket_bindtodevice(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); +int config_parse_output(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); +int config_parse_input(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); +int config_parse_exec_io_class(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); +int config_parse_exec_io_priority(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); +int config_parse_exec_cpu_sched_policy(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); +int config_parse_exec_cpu_sched_prio(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); +int config_parse_exec_cpu_affinity(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); +int config_parse_exec_secure_bits(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); +int config_parse_capability_set(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); +int config_parse_limit(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); +int config_parse_sysv_priority(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); +int config_parse_kill_signal(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); +int config_parse_exec_mount_flags(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); +int config_parse_timer(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); +int config_parse_trigger_unit(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); +int config_parse_path_spec(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); +int config_parse_socket_service(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); +int config_parse_service_sockets(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); +int config_parse_busname_service(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); +int config_parse_bus_policy(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); +int config_parse_bus_policy_world(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); +int config_parse_unit_env_file(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); +int config_parse_ip_tos(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); +int config_parse_unit_condition_path(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); +int config_parse_unit_condition_string(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); +int config_parse_unit_condition_null(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); +int config_parse_kill_mode(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); +int config_parse_notify_access(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); +int config_parse_failure_action(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); +int config_parse_unit_requires_mounts_for(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); +int config_parse_syscall_filter(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); +int config_parse_syscall_archs(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); +int config_parse_syscall_errno(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); +int config_parse_environ(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); +int config_parse_pass_environ(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); +int config_parse_unit_slice(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); +int config_parse_cpu_shares(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); +int config_parse_memory_limit(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); +int config_parse_tasks_max(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); +int config_parse_device_policy(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); +int config_parse_device_allow(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); +int config_parse_io_weight(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); +int config_parse_io_device_weight(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); +int config_parse_io_limit(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); +int config_parse_blockio_weight(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); +int config_parse_blockio_device_weight(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); +int config_parse_blockio_bandwidth(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); +int config_parse_netclass(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); +int config_parse_job_mode(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); +int config_parse_job_mode_isolate(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); +int config_parse_exec_selinux_context(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); +int config_parse_exec_apparmor_profile(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); +int config_parse_exec_smack_process_label(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); +int config_parse_address_families(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); +int config_parse_runtime_directory(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); +int config_parse_set_status(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); +int config_parse_namespace_path_strv(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); +int config_parse_no_new_privileges(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); +int config_parse_cpu_quota(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); +int config_parse_protect_home(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); +int config_parse_protect_system(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); +int config_parse_bus_name(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); +int config_parse_exec_utmp_mode(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); +int config_parse_working_directory(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); +int config_parse_fdname(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); +int config_parse_sec_fix_0(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); + +/* gperf prototypes */ +const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length); +extern const char load_fragment_gperf_nulstr[]; + +typedef enum Disabled { + DISABLED_CONFIGURATION, + DISABLED_LEGACY, + DISABLED_EXPERIMENTAL, +} Disabled; diff --git a/src/libcore/locale-setup.c b/src/libcore/locale-setup.c new file mode 100644 index 0000000000..ccf61d29fb --- /dev/null +++ b/src/libcore/locale-setup.c @@ -0,0 +1,124 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 + +#include "env-util.h" +#include "fileio.h" +#include "locale-setup.h" +#include "locale-util.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" +#include "virt.h" + +int locale_setup(char ***environment) { + char **add; + char *variables[_VARIABLE_LC_MAX] = {}; + int r = 0, i; + + if (detect_container() <= 0) { + r = parse_env_file("/proc/cmdline", WHITESPACE, + "locale.LANG", &variables[VARIABLE_LANG], + "locale.LANGUAGE", &variables[VARIABLE_LANGUAGE], + "locale.LC_CTYPE", &variables[VARIABLE_LC_CTYPE], + "locale.LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], + "locale.LC_TIME", &variables[VARIABLE_LC_TIME], + "locale.LC_COLLATE", &variables[VARIABLE_LC_COLLATE], + "locale.LC_MONETARY", &variables[VARIABLE_LC_MONETARY], + "locale.LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], + "locale.LC_PAPER", &variables[VARIABLE_LC_PAPER], + "locale.LC_NAME", &variables[VARIABLE_LC_NAME], + "locale.LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], + "locale.LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], + "locale.LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], + "locale.LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION], + NULL); + + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to read /proc/cmdline: %m"); + } + + /* Hmm, nothing set on the kernel cmd line? Then let's + * try /etc/locale.conf */ + if (r <= 0) { + r = parse_env_file("/etc/locale.conf", NEWLINE, + "LANG", &variables[VARIABLE_LANG], + "LANGUAGE", &variables[VARIABLE_LANGUAGE], + "LC_CTYPE", &variables[VARIABLE_LC_CTYPE], + "LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], + "LC_TIME", &variables[VARIABLE_LC_TIME], + "LC_COLLATE", &variables[VARIABLE_LC_COLLATE], + "LC_MONETARY", &variables[VARIABLE_LC_MONETARY], + "LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], + "LC_PAPER", &variables[VARIABLE_LC_PAPER], + "LC_NAME", &variables[VARIABLE_LC_NAME], + "LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], + "LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], + "LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], + "LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION], + NULL); + + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to read /etc/locale.conf: %m"); + } + + add = NULL; + for (i = 0; i < _VARIABLE_LC_MAX; i++) { + char *s; + + if (!variables[i]) + continue; + + s = strjoin(locale_variable_to_string(i), "=", variables[i], NULL); + if (!s) { + r = -ENOMEM; + goto finish; + } + + if (strv_consume(&add, s) < 0) { + r = -ENOMEM; + goto finish; + } + } + + if (!strv_isempty(add)) { + char **e; + + e = strv_env_merge(2, *environment, add); + if (!e) { + r = -ENOMEM; + goto finish; + } + + strv_free(*environment); + *environment = e; + } + + r = 0; + +finish: + strv_free(add); + + for (i = 0; i < _VARIABLE_LC_MAX; i++) + free(variables[i]); + + return r; +} diff --git a/src/libcore/locale-setup.h b/src/libcore/locale-setup.h new file mode 100644 index 0000000000..3b97497afe --- /dev/null +++ b/src/libcore/locale-setup.h @@ -0,0 +1,22 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +int locale_setup(char ***environment); diff --git a/src/libcore/loopback-setup.c b/src/libcore/loopback-setup.c new file mode 100644 index 0000000000..d56bbfa6fc --- /dev/null +++ b/src/libcore/loopback-setup.c @@ -0,0 +1,90 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 + +#include + +#include "loopback-setup.h" +#include "missing.h" +#include "netlink-util.h" + +static int start_loopback(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + r = sd_rtnl_message_new_link(rtnl, &req, RTM_SETLINK, LOOPBACK_IFINDEX); + if (r < 0) + return r; + + r = sd_rtnl_message_link_set_flags(req, IFF_UP, IFF_UP); + if (r < 0) + return r; + + r = sd_netlink_call(rtnl, req, 0, NULL); + if (r < 0) + return r; + + return 0; +} + +static bool check_loopback(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + unsigned flags; + int r; + + r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, LOOPBACK_IFINDEX); + if (r < 0) + return false; + + r = sd_netlink_call(rtnl, req, 0, &reply); + if (r < 0) + return false; + + r = sd_rtnl_message_link_get_flags(reply, &flags); + if (r < 0) + return false; + + return flags & IFF_UP; +} + +int loopback_setup(void) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + int r; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return r; + + r = start_loopback(rtnl); + if (r < 0) { + + /* If we lack the permissions to configure the + * loopback device, but we find it to be already + * configured, let's exit cleanly, in order to + * supported unprivileged containers. */ + if (r == -EPERM && check_loopback(rtnl)) + return 0; + + return log_warning_errno(r, "Failed to configure loopback device: %m"); + } + + return 0; +} diff --git a/src/libcore/loopback-setup.h b/src/libcore/loopback-setup.h new file mode 100644 index 0000000000..e7547b8a26 --- /dev/null +++ b/src/libcore/loopback-setup.h @@ -0,0 +1,22 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +int loopback_setup(void); diff --git a/src/libcore/machine-id-setup.c b/src/libcore/machine-id-setup.c new file mode 100644 index 0000000000..812e4b038c --- /dev/null +++ b/src/libcore/machine-id-setup.c @@ -0,0 +1,364 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "hexdecoct.h" +#include "io-util.h" +#include "log.h" +#include "machine-id-setup.h" +#include "macro.h" +#include "mkdir.h" +#include "mount-util.h" +#include "path-util.h" +#include "process-util.h" +#include "stat-util.h" +#include "string-util.h" +#include "umask-util.h" +#include "util.h" +#include "virt.h" + +static int shorten_uuid(char destination[34], const char source[36]) { + unsigned i, j; + + assert(destination); + assert(source); + + /* Converts a UUID into a machine ID, by lowercasing it and + * removing dashes. Validates everything. */ + + for (i = 0, j = 0; i < 36 && j < 32; i++) { + int t; + + t = unhexchar(source[i]); + if (t < 0) + continue; + + destination[j++] = hexchar(t); + } + + if (i != 36 || j != 32) + return -EINVAL; + + destination[32] = '\n'; + destination[33] = 0; + return 0; +} + +static int read_machine_id(int fd, char id[34]) { + char id_to_validate[34]; + int r; + + assert(fd >= 0); + assert(id); + + /* Reads a machine ID from a file, validates it, and returns + * it. The returned ID ends in a newline. */ + + r = loop_read_exact(fd, id_to_validate, 33, false); + if (r < 0) + return r; + + if (id_to_validate[32] != '\n') + return -EINVAL; + + id_to_validate[32] = 0; + + if (!id128_is_valid(id_to_validate)) + return -EINVAL; + + memcpy(id, id_to_validate, 32); + id[32] = '\n'; + id[33] = 0; + return 0; +} + +static int write_machine_id(int fd, const char id[34]) { + int r; + + assert(fd >= 0); + assert(id); + + if (lseek(fd, 0, SEEK_SET) < 0) + return -errno; + + r = loop_write(fd, id, 33, false); + if (r < 0) + return r; + + if (fsync(fd) < 0) + return -errno; + + return 0; +} + +static int generate_machine_id(char id[34], const char *root) { + int fd, r; + unsigned char *p; + sd_id128_t buf; + char *q; + const char *dbus_machine_id; + + assert(id); + + dbus_machine_id = prefix_roota(root, "/var/lib/dbus/machine-id"); + + /* First, try reading the D-Bus machine id, unless it is a symlink */ + fd = open(dbus_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd >= 0) { + r = read_machine_id(fd, id); + safe_close(fd); + + if (r >= 0) { + log_info("Initializing machine ID from D-Bus machine ID."); + return 0; + } + } + + if (isempty(root)) { + /* If that didn't work, see if we are running in a container, + * and a machine ID was passed in via $container_uuid the way + * libvirt/LXC does it */ + + if (detect_container() > 0) { + _cleanup_free_ char *e = NULL; + + r = getenv_for_pid(1, "container_uuid", &e); + if (r > 0) { + r = shorten_uuid(id, e); + if (r >= 0) { + log_info("Initializing machine ID from container UUID."); + return 0; + } + } + + } else if (detect_vm() == VIRTUALIZATION_KVM) { + + /* If we are not running in a container, see if we are + * running in qemu/kvm and a machine ID was passed in + * via -uuid on the qemu/kvm command line */ + + char uuid[36]; + + fd = open("/sys/class/dmi/id/product_uuid", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd >= 0) { + r = loop_read_exact(fd, uuid, 36, false); + safe_close(fd); + + if (r >= 0) { + r = shorten_uuid(id, uuid); + if (r >= 0) { + log_info("Initializing machine ID from KVM UUID."); + return 0; + } + } + } + } + } + + /* If that didn't work, generate a random machine id */ + r = sd_id128_randomize(&buf); + if (r < 0) + return log_error_errno(r, "Failed to open /dev/urandom: %m"); + + for (p = buf.bytes, q = id; p < buf.bytes + sizeof(buf); p++, q += 2) { + q[0] = hexchar(*p >> 4); + q[1] = hexchar(*p & 15); + } + + id[32] = '\n'; + id[33] = 0; + + log_info("Initializing machine ID from random generator."); + + return 0; +} + +int machine_id_setup(const char *root, sd_id128_t machine_id) { + const char *etc_machine_id, *run_machine_id; + _cleanup_close_ int fd = -1; + bool writable = true; + char id[34]; /* 32 + \n + \0 */ + int r; + + etc_machine_id = prefix_roota(root, "/etc/machine-id"); + run_machine_id = prefix_roota(root, "/run/machine-id"); + + RUN_WITH_UMASK(0000) { + /* We create this 0444, to indicate that this isn't really + * something you should ever modify. Of course, since the file + * will be owned by root it doesn't matter much, but maybe + * people look. */ + + mkdir_parents(etc_machine_id, 0755); + fd = open(etc_machine_id, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0444); + if (fd < 0) { + int old_errno = errno; + + fd = open(etc_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + if (old_errno == EROFS && errno == ENOENT) + log_error_errno(errno, + "System cannot boot: Missing /etc/machine-id and /etc is mounted read-only.\n" + "Booting up is supported only when:\n" + "1) /etc/machine-id exists and is populated.\n" + "2) /etc/machine-id exists and is empty.\n" + "3) /etc/machine-id is missing and /etc is writable.\n"); + else + log_error_errno(errno, "Cannot open %s: %m", etc_machine_id); + + return -errno; + } + + writable = false; + } + } + + /* A machine id argument overrides all other machined-ids */ + if (!sd_id128_is_null(machine_id)) { + sd_id128_to_string(machine_id, id); + id[32] = '\n'; + id[33] = 0; + } else { + if (read_machine_id(fd, id) >= 0) + return 0; + + /* Hmm, so, the id currently stored is not useful, then let's + * generate one */ + + r = generate_machine_id(id, root); + if (r < 0) + return r; + } + + if (writable) + if (write_machine_id(fd, id) >= 0) + return 0; + + fd = safe_close(fd); + + /* Hmm, we couldn't write it? So let's write it to + * /run/machine-id as a replacement */ + + RUN_WITH_UMASK(0022) { + r = write_string_file(run_machine_id, id, WRITE_STRING_FILE_CREATE); + if (r < 0) { + (void) unlink(run_machine_id); + return log_error_errno(r, "Cannot write %s: %m", run_machine_id); + } + } + + /* And now, let's mount it over */ + if (mount(run_machine_id, etc_machine_id, NULL, MS_BIND, NULL) < 0) { + (void) unlink_noerrno(run_machine_id); + return log_error_errno(errno, "Failed to mount %s: %m", etc_machine_id); + } + + log_info("Installed transient %s file.", etc_machine_id); + + /* Mark the mount read-only */ + if (mount(NULL, etc_machine_id, NULL, MS_BIND|MS_RDONLY|MS_REMOUNT, NULL) < 0) + log_warning_errno(errno, "Failed to make transient %s read-only: %m", etc_machine_id); + + return 0; +} + +int machine_id_commit(const char *root) { + _cleanup_close_ int fd = -1, initial_mntns_fd = -1; + const char *etc_machine_id; + char id[34]; /* 32 + \n + \0 */ + int r; + + etc_machine_id = prefix_roota(root, "/etc/machine-id"); + + r = path_is_mount_point(etc_machine_id, 0); + if (r < 0) + return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", etc_machine_id); + if (r == 0) { + log_debug("%s is is not a mount point. Nothing to do.", etc_machine_id); + return 0; + } + + /* Read existing machine-id */ + fd = open(etc_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Cannot open %s: %m", etc_machine_id); + + r = read_machine_id(fd, id); + if (r < 0) + return log_error_errno(r, "We didn't find a valid machine ID in %s.", etc_machine_id); + + r = fd_is_temporary_fs(fd); + if (r < 0) + return log_error_errno(r, "Failed to determine whether %s is on a temporary file system: %m", etc_machine_id); + if (r == 0) { + log_error("%s is not on a temporary file system.", etc_machine_id); + return -EROFS; + } + + fd = safe_close(fd); + + /* Store current mount namespace */ + r = namespace_open(0, NULL, &initial_mntns_fd, NULL, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Can't fetch current mount namespace: %m"); + + /* Switch to a new mount namespace, isolate ourself and unmount etc_machine_id in our new namespace */ + if (unshare(CLONE_NEWNS) < 0) + return log_error_errno(errno, "Failed to enter new namespace: %m"); + + if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) + return log_error_errno(errno, "Couldn't make-rslave / mountpoint in our private namespace: %m"); + + if (umount(etc_machine_id) < 0) + return log_error_errno(errno, "Failed to unmount transient %s file in our private namespace: %m", etc_machine_id); + + /* Update a persistent version of etc_machine_id */ + fd = open(etc_machine_id, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0444); + if (fd < 0) + return log_error_errno(errno, "Cannot open for writing %s. This is mandatory to get a persistent machine-id: %m", etc_machine_id); + + r = write_machine_id(fd, id); + if (r < 0) + return log_error_errno(r, "Cannot write %s: %m", etc_machine_id); + + fd = safe_close(fd); + + /* Return to initial namespace and proceed a lazy tmpfs unmount */ + r = namespace_enter(-1, initial_mntns_fd, -1, -1, -1); + if (r < 0) + return log_warning_errno(r, "Failed to switch back to initial mount namespace: %m.\nWe'll keep transient %s file until next reboot.", etc_machine_id); + + if (umount2(etc_machine_id, MNT_DETACH) < 0) + return log_warning_errno(errno, "Failed to unmount transient %s file: %m.\nWe keep that mount until next reboot.", etc_machine_id); + + return 0; +} diff --git a/src/libcore/machine-id-setup.h b/src/libcore/machine-id-setup.h new file mode 100644 index 0000000000..a7e7678ed9 --- /dev/null +++ b/src/libcore/machine-id-setup.h @@ -0,0 +1,23 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +int machine_id_commit(const char *root); +int machine_id_setup(const char *root, sd_id128_t machine_id); diff --git a/src/libcore/manager.c b/src/libcore/manager.c new file mode 100644 index 0000000000..831fdbaabf --- /dev/null +++ b/src/libcore/manager.c @@ -0,0 +1,3134 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_AUDIT +#include +#endif + +#include +#include + +#include "alloc-util.h" +#include "audit-fd.h" +#include "boot-timestamps.h" +#include "bus-common-errors.h" +#include "bus-error.h" +#include "bus-kernel.h" +#include "bus-util.h" +#include "dbus-job.h" +#include "dbus-manager.h" +#include "dbus-unit.h" +#include "dbus.h" +#include "dirent-util.h" +#include "env-util.h" +#include "escape.h" +#include "exit-status.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "hashmap.h" +#include "io-util.h" +#include "locale-setup.h" +#include "log.h" +#include "macro.h" +#include "manager.h" +#include "missing.h" +#include "mkdir.h" +#include "mkdir.h" +#include "parse-util.h" +#include "path-lookup.h" +#include "path-util.h" +#include "process-util.h" +#include "ratelimit.h" +#include "rm-rf.h" +#include "signal-util.h" +#include "special.h" +#include "stat-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "time-util.h" +#include "transaction.h" +#include "umask-util.h" +#include "unit-name.h" +#include "util.h" +#include "virt.h" +#include "watchdog.h" + +#define NOTIFY_RCVBUF_SIZE (8*1024*1024) +#define CGROUPS_AGENT_RCVBUF_SIZE (8*1024*1024) + +/* Initial delay and the interval for printing status messages about running jobs */ +#define JOBS_IN_PROGRESS_WAIT_USEC (5*USEC_PER_SEC) +#define JOBS_IN_PROGRESS_PERIOD_USEC (USEC_PER_SEC / 3) +#define JOBS_IN_PROGRESS_PERIOD_DIVISOR 3 + +static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); +static int manager_dispatch_cgroups_agent_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); +static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); +static int manager_dispatch_time_change_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); +static int manager_dispatch_idle_pipe_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); +static int manager_dispatch_jobs_in_progress(sd_event_source *source, usec_t usec, void *userdata); +static int manager_dispatch_run_queue(sd_event_source *source, void *userdata); +static int manager_run_generators(Manager *m); + +static void manager_watch_jobs_in_progress(Manager *m) { + usec_t next; + int r; + + assert(m); + + if (m->jobs_in_progress_event_source) + return; + + next = now(CLOCK_MONOTONIC) + JOBS_IN_PROGRESS_WAIT_USEC; + r = sd_event_add_time( + m->event, + &m->jobs_in_progress_event_source, + CLOCK_MONOTONIC, + next, 0, + manager_dispatch_jobs_in_progress, m); + if (r < 0) + return; + + (void) sd_event_source_set_description(m->jobs_in_progress_event_source, "manager-jobs-in-progress"); +} + +#define CYLON_BUFFER_EXTRA (2*(sizeof(ANSI_RED)-1) + sizeof(ANSI_HIGHLIGHT_RED)-1 + 2*(sizeof(ANSI_NORMAL)-1)) + +static void draw_cylon(char buffer[], size_t buflen, unsigned width, unsigned pos) { + char *p = buffer; + + assert(buflen >= CYLON_BUFFER_EXTRA + width + 1); + assert(pos <= width+1); /* 0 or width+1 mean that the center light is behind the corner */ + + if (pos > 1) { + if (pos > 2) + p = mempset(p, ' ', pos-2); + p = stpcpy(p, ANSI_RED); + *p++ = '*'; + } + + if (pos > 0 && pos <= width) { + p = stpcpy(p, ANSI_HIGHLIGHT_RED); + *p++ = '*'; + } + + p = stpcpy(p, ANSI_NORMAL); + + if (pos < width) { + p = stpcpy(p, ANSI_RED); + *p++ = '*'; + if (pos < width-1) + p = mempset(p, ' ', width-1-pos); + strcpy(p, ANSI_NORMAL); + } +} + +void manager_flip_auto_status(Manager *m, bool enable) { + assert(m); + + if (enable) { + if (m->show_status == SHOW_STATUS_AUTO) + manager_set_show_status(m, SHOW_STATUS_TEMPORARY); + } else { + if (m->show_status == SHOW_STATUS_TEMPORARY) + manager_set_show_status(m, SHOW_STATUS_AUTO); + } +} + +static void manager_print_jobs_in_progress(Manager *m) { + _cleanup_free_ char *job_of_n = NULL; + Iterator i; + Job *j; + unsigned counter = 0, print_nr; + char cylon[6 + CYLON_BUFFER_EXTRA + 1]; + unsigned cylon_pos; + char time[FORMAT_TIMESPAN_MAX], limit[FORMAT_TIMESPAN_MAX] = "no limit"; + uint64_t x; + + assert(m); + assert(m->n_running_jobs > 0); + + manager_flip_auto_status(m, true); + + print_nr = (m->jobs_in_progress_iteration / JOBS_IN_PROGRESS_PERIOD_DIVISOR) % m->n_running_jobs; + + HASHMAP_FOREACH(j, m->jobs, i) + if (j->state == JOB_RUNNING && counter++ == print_nr) + break; + + /* m->n_running_jobs must be consistent with the contents of m->jobs, + * so the above loop must have succeeded in finding j. */ + assert(counter == print_nr + 1); + assert(j); + + cylon_pos = m->jobs_in_progress_iteration % 14; + if (cylon_pos >= 8) + cylon_pos = 14 - cylon_pos; + draw_cylon(cylon, sizeof(cylon), 6, cylon_pos); + + m->jobs_in_progress_iteration++; + + if (m->n_running_jobs > 1) { + if (asprintf(&job_of_n, "(%u of %u) ", counter, m->n_running_jobs) < 0) + job_of_n = NULL; + } + + format_timespan(time, sizeof(time), now(CLOCK_MONOTONIC) - j->begin_usec, 1*USEC_PER_SEC); + if (job_get_timeout(j, &x) > 0) + format_timespan(limit, sizeof(limit), x - j->begin_usec, 1*USEC_PER_SEC); + + manager_status_printf(m, STATUS_TYPE_EPHEMERAL, cylon, + "%sA %s job is running for %s (%s / %s)", + strempty(job_of_n), + job_type_to_string(j->type), + unit_description(j->unit), + time, limit); +} + +static int have_ask_password(void) { + _cleanup_closedir_ DIR *dir; + + dir = opendir("/run/systemd/ask-password"); + if (!dir) { + if (errno == ENOENT) + return false; + else + return -errno; + } + + for (;;) { + struct dirent *de; + + errno = 0; + de = readdir(dir); + if (!de && errno > 0) + return -errno; + if (!de) + return false; + + if (startswith(de->d_name, "ask.")) + return true; + } +} + +static int manager_dispatch_ask_password_fd(sd_event_source *source, + int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + + assert(m); + + flush_fd(fd); + + m->have_ask_password = have_ask_password(); + if (m->have_ask_password < 0) + /* Log error but continue. Negative have_ask_password + * is treated as unknown status. */ + log_error_errno(m->have_ask_password, "Failed to list /run/systemd/ask-password: %m"); + + return 0; +} + +static void manager_close_ask_password(Manager *m) { + assert(m); + + m->ask_password_event_source = sd_event_source_unref(m->ask_password_event_source); + m->ask_password_inotify_fd = safe_close(m->ask_password_inotify_fd); + m->have_ask_password = -EINVAL; +} + +static int manager_check_ask_password(Manager *m) { + int r; + + assert(m); + + if (!m->ask_password_event_source) { + assert(m->ask_password_inotify_fd < 0); + + mkdir_p_label("/run/systemd/ask-password", 0755); + + m->ask_password_inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); + if (m->ask_password_inotify_fd < 0) + return log_error_errno(errno, "inotify_init1() failed: %m"); + + if (inotify_add_watch(m->ask_password_inotify_fd, "/run/systemd/ask-password", IN_CREATE|IN_DELETE|IN_MOVE) < 0) { + log_error_errno(errno, "Failed to add watch on /run/systemd/ask-password: %m"); + manager_close_ask_password(m); + return -errno; + } + + r = sd_event_add_io(m->event, &m->ask_password_event_source, + m->ask_password_inotify_fd, EPOLLIN, + manager_dispatch_ask_password_fd, m); + if (r < 0) { + log_error_errno(errno, "Failed to add event source for /run/systemd/ask-password: %m"); + manager_close_ask_password(m); + return -errno; + } + + (void) sd_event_source_set_description(m->ask_password_event_source, "manager-ask-password"); + + /* Queries might have been added meanwhile... */ + manager_dispatch_ask_password_fd(m->ask_password_event_source, + m->ask_password_inotify_fd, EPOLLIN, m); + } + + return m->have_ask_password; +} + +static int manager_watch_idle_pipe(Manager *m) { + int r; + + assert(m); + + if (m->idle_pipe_event_source) + return 0; + + if (m->idle_pipe[2] < 0) + return 0; + + r = sd_event_add_io(m->event, &m->idle_pipe_event_source, m->idle_pipe[2], EPOLLIN, manager_dispatch_idle_pipe_fd, m); + if (r < 0) + return log_error_errno(r, "Failed to watch idle pipe: %m"); + + (void) sd_event_source_set_description(m->idle_pipe_event_source, "manager-idle-pipe"); + + return 0; +} + +static void manager_close_idle_pipe(Manager *m) { + assert(m); + + m->idle_pipe_event_source = sd_event_source_unref(m->idle_pipe_event_source); + + safe_close_pair(m->idle_pipe); + safe_close_pair(m->idle_pipe + 2); +} + +static int manager_setup_time_change(Manager *m) { + int r; + + /* We only care for the cancellation event, hence we set the + * timeout to the latest possible value. */ + struct itimerspec its = { + .it_value.tv_sec = TIME_T_MAX, + }; + + assert(m); + assert_cc(sizeof(time_t) == sizeof(TIME_T_MAX)); + + if (m->test_run) + return 0; + + /* Uses TFD_TIMER_CANCEL_ON_SET to get notifications whenever + * CLOCK_REALTIME makes a jump relative to CLOCK_MONOTONIC */ + + m->time_change_fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC); + if (m->time_change_fd < 0) + return log_error_errno(errno, "Failed to create timerfd: %m"); + + if (timerfd_settime(m->time_change_fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0) { + log_debug_errno(errno, "Failed to set up TFD_TIMER_CANCEL_ON_SET, ignoring: %m"); + m->time_change_fd = safe_close(m->time_change_fd); + return 0; + } + + r = sd_event_add_io(m->event, &m->time_change_event_source, m->time_change_fd, EPOLLIN, manager_dispatch_time_change_fd, m); + if (r < 0) + return log_error_errno(r, "Failed to create time change event source: %m"); + + (void) sd_event_source_set_description(m->time_change_event_source, "manager-time-change"); + + log_debug("Set up TFD_TIMER_CANCEL_ON_SET timerfd."); + + return 0; +} + +static int enable_special_signals(Manager *m) { + _cleanup_close_ int fd = -1; + + assert(m); + + if (m->test_run) + return 0; + + /* Enable that we get SIGINT on control-alt-del. In containers + * this will fail with EPERM (older) or EINVAL (newer), so + * ignore that. */ + if (reboot(RB_DISABLE_CAD) < 0 && errno != EPERM && errno != EINVAL) + log_warning_errno(errno, "Failed to enable ctrl-alt-del handling: %m"); + + fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) { + /* Support systems without virtual console */ + if (fd != -ENOENT) + log_warning_errno(errno, "Failed to open /dev/tty0: %m"); + } else { + /* Enable that we get SIGWINCH on kbrequest */ + if (ioctl(fd, KDSIGACCEPT, SIGWINCH) < 0) + log_warning_errno(errno, "Failed to enable kbrequest handling: %m"); + } + + return 0; +} + +static int manager_setup_signals(Manager *m) { + struct sigaction sa = { + .sa_handler = SIG_DFL, + .sa_flags = SA_NOCLDSTOP|SA_RESTART, + }; + sigset_t mask; + int r; + + assert(m); + + assert_se(sigaction(SIGCHLD, &sa, NULL) == 0); + + /* We make liberal use of realtime signals here. On + * Linux/glibc we have 30 of them (with the exception of Linux + * on hppa, see below), between SIGRTMIN+0 ... SIGRTMIN+30 + * (aka SIGRTMAX). */ + + assert_se(sigemptyset(&mask) == 0); + sigset_add_many(&mask, + SIGCHLD, /* Child died */ + SIGTERM, /* Reexecute daemon */ + SIGHUP, /* Reload configuration */ + SIGUSR1, /* systemd/upstart: reconnect to D-Bus */ + SIGUSR2, /* systemd: dump status */ + SIGINT, /* Kernel sends us this on control-alt-del */ + SIGWINCH, /* Kernel sends us this on kbrequest (alt-arrowup) */ + SIGPWR, /* Some kernel drivers and upsd send us this on power failure */ + + SIGRTMIN+0, /* systemd: start default.target */ + SIGRTMIN+1, /* systemd: isolate rescue.target */ + SIGRTMIN+2, /* systemd: isolate emergency.target */ + SIGRTMIN+3, /* systemd: start halt.target */ + SIGRTMIN+4, /* systemd: start poweroff.target */ + SIGRTMIN+5, /* systemd: start reboot.target */ + SIGRTMIN+6, /* systemd: start kexec.target */ + + /* ... space for more special targets ... */ + + SIGRTMIN+13, /* systemd: Immediate halt */ + SIGRTMIN+14, /* systemd: Immediate poweroff */ + SIGRTMIN+15, /* systemd: Immediate reboot */ + SIGRTMIN+16, /* systemd: Immediate kexec */ + + /* ... space for more immediate system state changes ... */ + + SIGRTMIN+20, /* systemd: enable status messages */ + SIGRTMIN+21, /* systemd: disable status messages */ + SIGRTMIN+22, /* systemd: set log level to LOG_DEBUG */ + SIGRTMIN+23, /* systemd: set log level to LOG_INFO */ + SIGRTMIN+24, /* systemd: Immediate exit (--user only) */ + + /* .. one free signal here ... */ + +#if !defined(__hppa64__) && !defined(__hppa__) + /* Apparently Linux on hppa has fewer RT + * signals (SIGRTMAX is SIGRTMIN+25 there), + * hence let's not try to make use of them + * here. Since these commands are accessible + * by different means and only really a safety + * net, the missing functionality on hppa + * shouldn't matter. */ + + SIGRTMIN+26, /* systemd: set log target to journal-or-kmsg */ + SIGRTMIN+27, /* systemd: set log target to console */ + SIGRTMIN+28, /* systemd: set log target to kmsg */ + SIGRTMIN+29, /* systemd: set log target to syslog-or-kmsg (obsolete) */ + + /* ... one free signal here SIGRTMIN+30 ... */ +#endif + -1); + assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); + + m->signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); + if (m->signal_fd < 0) + return -errno; + + r = sd_event_add_io(m->event, &m->signal_event_source, m->signal_fd, EPOLLIN, manager_dispatch_signal_fd, m); + if (r < 0) + return r; + + (void) sd_event_source_set_description(m->signal_event_source, "manager-signal"); + + /* Process signals a bit earlier than the rest of things, but later than notify_fd processing, so that the + * notify processing can still figure out to which process/service a message belongs, before we reap the + * process. Also, process this before handling cgroup notifications, so that we always collect child exit + * status information before detecting that there's no process in a cgroup. */ + r = sd_event_source_set_priority(m->signal_event_source, SD_EVENT_PRIORITY_NORMAL-6); + if (r < 0) + return r; + + if (MANAGER_IS_SYSTEM(m)) + return enable_special_signals(m); + + return 0; +} + +static void manager_clean_environment(Manager *m) { + assert(m); + + /* Let's remove some environment variables that we + * need ourselves to communicate with our clients */ + strv_env_unset_many( + m->environment, + "NOTIFY_SOCKET", + "MAINPID", + "MANAGERPID", + "LISTEN_PID", + "LISTEN_FDS", + "LISTEN_FDNAMES", + "WATCHDOG_PID", + "WATCHDOG_USEC", + NULL); +} + +static int manager_default_environment(Manager *m) { + assert(m); + + if (MANAGER_IS_SYSTEM(m)) { + /* The system manager always starts with a clean + * environment for its children. It does not import + * the kernel or the parents exported variables. + * + * The initial passed environ is untouched to keep + * /proc/self/environ valid; it is used for tagging + * the init process inside containers. */ + m->environment = strv_new("PATH=" DEFAULT_PATH, + NULL); + + /* Import locale variables LC_*= from configuration */ + locale_setup(&m->environment); + } else { + /* The user manager passes its own environment + * along to its children. */ + m->environment = strv_copy(environ); + } + + if (!m->environment) + return -ENOMEM; + + manager_clean_environment(m); + strv_sort(m->environment); + + return 0; +} + + +int manager_new(UnitFileScope scope, bool test_run, Manager **_m) { + Manager *m; + int r; + + assert(_m); + assert(IN_SET(scope, UNIT_FILE_SYSTEM, UNIT_FILE_USER)); + + m = new0(Manager, 1); + if (!m) + return -ENOMEM; + + m->unit_file_scope = scope; + m->exit_code = _MANAGER_EXIT_CODE_INVALID; + m->default_timer_accuracy_usec = USEC_PER_MINUTE; + m->default_tasks_accounting = true; + m->default_tasks_max = UINT64_C(512); + +#ifdef ENABLE_EFI + if (MANAGER_IS_SYSTEM(m) && detect_container() <= 0) + boot_timestamps(&m->userspace_timestamp, &m->firmware_timestamp, &m->loader_timestamp); +#endif + + /* Prepare log fields we can use for structured logging */ + if (MANAGER_IS_SYSTEM(m)) { + m->unit_log_field = "UNIT="; + m->unit_log_format_string = "UNIT=%s"; + } else { + m->unit_log_field = "USER_UNIT="; + m->unit_log_format_string = "USER_UNIT=%s"; + } + + m->idle_pipe[0] = m->idle_pipe[1] = m->idle_pipe[2] = m->idle_pipe[3] = -1; + + m->pin_cgroupfs_fd = m->notify_fd = m->cgroups_agent_fd = m->signal_fd = m->time_change_fd = + m->dev_autofs_fd = m->private_listen_fd = m->kdbus_fd = m->cgroup_inotify_fd = + m->ask_password_inotify_fd = -1; + + m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */ + + m->have_ask_password = -EINVAL; /* we don't know */ + m->first_boot = -1; + + m->test_run = test_run; + + /* Reboot immediately if the user hits C-A-D more often than 7x per 2s */ + RATELIMIT_INIT(m->ctrl_alt_del_ratelimit, 2 * USEC_PER_SEC, 7); + + r = manager_default_environment(m); + if (r < 0) + goto fail; + + r = hashmap_ensure_allocated(&m->units, &string_hash_ops); + if (r < 0) + goto fail; + + r = hashmap_ensure_allocated(&m->jobs, NULL); + if (r < 0) + goto fail; + + r = hashmap_ensure_allocated(&m->cgroup_unit, &string_hash_ops); + if (r < 0) + goto fail; + + r = hashmap_ensure_allocated(&m->watch_bus, &string_hash_ops); + if (r < 0) + goto fail; + + r = sd_event_default(&m->event); + if (r < 0) + goto fail; + + r = sd_event_add_defer(m->event, &m->run_queue_event_source, manager_dispatch_run_queue, m); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(m->run_queue_event_source, SD_EVENT_PRIORITY_IDLE); + if (r < 0) + goto fail; + + r = sd_event_source_set_enabled(m->run_queue_event_source, SD_EVENT_OFF); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(m->run_queue_event_source, "manager-run-queue"); + + r = manager_setup_signals(m); + if (r < 0) + goto fail; + + r = manager_setup_cgroup(m); + if (r < 0) + goto fail; + + r = manager_setup_time_change(m); + if (r < 0) + goto fail; + + m->udev = udev_new(); + if (!m->udev) { + r = -ENOMEM; + goto fail; + } + + /* Note that we set up neither kdbus, nor the notify fd + * here. We do that after deserialization, since they might + * have gotten serialized across the reexec. */ + + m->taint_usr = dir_is_empty("/usr") > 0; + + *_m = m; + return 0; + +fail: + manager_free(m); + return r; +} + +static int manager_setup_notify(Manager *m) { + int r; + + if (m->test_run) + return 0; + + if (m->notify_fd < 0) { + _cleanup_close_ int fd = -1; + union sockaddr_union sa = { + .sa.sa_family = AF_UNIX, + }; + static const int one = 1; + const char *e; + + /* First free all secondary fields */ + m->notify_socket = mfree(m->notify_socket); + m->notify_event_source = sd_event_source_unref(m->notify_event_source); + + fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) + return log_error_errno(errno, "Failed to allocate notification socket: %m"); + + fd_inc_rcvbuf(fd, NOTIFY_RCVBUF_SIZE); + + e = manager_get_runtime_prefix(m); + if (!e) { + log_error("Failed to determine runtime prefix."); + return -EINVAL; + } + + m->notify_socket = strappend(e, "/systemd/notify"); + if (!m->notify_socket) + return log_oom(); + + (void) mkdir_parents_label(m->notify_socket, 0755); + (void) unlink(m->notify_socket); + + strncpy(sa.un.sun_path, m->notify_socket, sizeof(sa.un.sun_path)-1); + r = bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); + if (r < 0) + return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path); + + r = setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); + if (r < 0) + return log_error_errno(errno, "SO_PASSCRED failed: %m"); + + m->notify_fd = fd; + fd = -1; + + log_debug("Using notification socket %s", m->notify_socket); + } + + if (!m->notify_event_source) { + r = sd_event_add_io(m->event, &m->notify_event_source, m->notify_fd, EPOLLIN, manager_dispatch_notify_fd, m); + if (r < 0) + return log_error_errno(r, "Failed to allocate notify event source: %m"); + + /* Process notification messages a bit earlier than SIGCHLD, so that we can still identify to which + * service an exit message belongs. */ + r = sd_event_source_set_priority(m->notify_event_source, SD_EVENT_PRIORITY_NORMAL-7); + if (r < 0) + return log_error_errno(r, "Failed to set priority of notify event source: %m"); + + (void) sd_event_source_set_description(m->notify_event_source, "manager-notify"); + } + + return 0; +} + +static int manager_setup_cgroups_agent(Manager *m) { + + static const union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/cgroups-agent", + }; + int r; + + /* This creates a listening socket we receive cgroups agent messages on. We do not use D-Bus for delivering + * these messages from the cgroups agent binary to PID 1, as the cgroups agent binary is very short-living, and + * each instance of it needs a new D-Bus connection. Since D-Bus connections are SOCK_STREAM/AF_UNIX, on + * overloaded systems the backlog of the D-Bus socket becomes relevant, as not more than the configured number + * of D-Bus connections may be queued until the kernel will start dropping further incoming connections, + * possibly resulting in lost cgroups agent messages. To avoid this, we'll use a private SOCK_DGRAM/AF_UNIX + * socket, where no backlog is relevant as communication may take place without an actual connect() cycle, and + * we thus won't lose messages. + * + * Note that PID 1 will forward the agent message to system bus, so that the user systemd instance may listen + * to it. The system instance hence listens on this special socket, but the user instances listen on the system + * bus for these messages. */ + + if (m->test_run) + return 0; + + if (!MANAGER_IS_SYSTEM(m)) + return 0; + + if (cg_unified() > 0) /* We don't need this anymore on the unified hierarchy */ + return 0; + + if (m->cgroups_agent_fd < 0) { + _cleanup_close_ int fd = -1; + + /* First free all secondary fields */ + m->cgroups_agent_event_source = sd_event_source_unref(m->cgroups_agent_event_source); + + fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) + return log_error_errno(errno, "Failed to allocate cgroups agent socket: %m"); + + fd_inc_rcvbuf(fd, CGROUPS_AGENT_RCVBUF_SIZE); + + (void) unlink(sa.un.sun_path); + + /* Only allow root to connect to this socket */ + RUN_WITH_UMASK(0077) + r = bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); + if (r < 0) + return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path); + + m->cgroups_agent_fd = fd; + fd = -1; + } + + if (!m->cgroups_agent_event_source) { + r = sd_event_add_io(m->event, &m->cgroups_agent_event_source, m->cgroups_agent_fd, EPOLLIN, manager_dispatch_cgroups_agent_fd, m); + if (r < 0) + return log_error_errno(r, "Failed to allocate cgroups agent event source: %m"); + + /* Process cgroups notifications early, but after having processed service notification messages or + * SIGCHLD signals, so that a cgroup running empty is always just the last safety net of notification, + * and we collected the metadata the notification and SIGCHLD stuff offers first. Also see handling of + * cgroup inotify for the unified cgroup stuff. */ + r = sd_event_source_set_priority(m->cgroups_agent_event_source, SD_EVENT_PRIORITY_NORMAL-5); + if (r < 0) + return log_error_errno(r, "Failed to set priority of cgroups agent event source: %m"); + + (void) sd_event_source_set_description(m->cgroups_agent_event_source, "manager-cgroups-agent"); + } + + return 0; +} + +static int manager_setup_kdbus(Manager *m) { + _cleanup_free_ char *p = NULL; + + assert(m); + + if (m->test_run || m->kdbus_fd >= 0) + return 0; + if (!is_kdbus_available()) + return -ESOCKTNOSUPPORT; + + m->kdbus_fd = bus_kernel_create_bus( + MANAGER_IS_SYSTEM(m) ? "system" : "user", + MANAGER_IS_SYSTEM(m), &p); + + if (m->kdbus_fd < 0) + return log_debug_errno(m->kdbus_fd, "Failed to set up kdbus: %m"); + + log_debug("Successfully set up kdbus on %s", p); + + return 0; +} + +static int manager_connect_bus(Manager *m, bool reexecuting) { + bool try_bus_connect; + + assert(m); + + if (m->test_run) + return 0; + + try_bus_connect = + m->kdbus_fd >= 0 || + reexecuting || + (MANAGER_IS_USER(m) && getenv("DBUS_SESSION_BUS_ADDRESS")); + + /* Try to connect to the buses, if possible. */ + return bus_init(m, try_bus_connect); +} + +static unsigned manager_dispatch_cleanup_queue(Manager *m) { + Unit *u; + unsigned n = 0; + + assert(m); + + while ((u = m->cleanup_queue)) { + assert(u->in_cleanup_queue); + + unit_free(u); + n++; + } + + return n; +} + +enum { + GC_OFFSET_IN_PATH, /* This one is on the path we were traveling */ + GC_OFFSET_UNSURE, /* No clue */ + GC_OFFSET_GOOD, /* We still need this unit */ + GC_OFFSET_BAD, /* We don't need this unit anymore */ + _GC_OFFSET_MAX +}; + +static void unit_gc_sweep(Unit *u, unsigned gc_marker) { + Iterator i; + Unit *other; + bool is_bad; + + assert(u); + + if (u->gc_marker == gc_marker + GC_OFFSET_GOOD || + u->gc_marker == gc_marker + GC_OFFSET_BAD || + u->gc_marker == gc_marker + GC_OFFSET_IN_PATH) + return; + + if (u->in_cleanup_queue) + goto bad; + + if (unit_check_gc(u)) + goto good; + + u->gc_marker = gc_marker + GC_OFFSET_IN_PATH; + + is_bad = true; + + SET_FOREACH(other, u->dependencies[UNIT_REFERENCED_BY], i) { + unit_gc_sweep(other, gc_marker); + + if (other->gc_marker == gc_marker + GC_OFFSET_GOOD) + goto good; + + if (other->gc_marker != gc_marker + GC_OFFSET_BAD) + is_bad = false; + } + + if (is_bad) + goto bad; + + /* We were unable to find anything out about this entry, so + * let's investigate it later */ + u->gc_marker = gc_marker + GC_OFFSET_UNSURE; + unit_add_to_gc_queue(u); + return; + +bad: + /* We definitely know that this one is not useful anymore, so + * let's mark it for deletion */ + u->gc_marker = gc_marker + GC_OFFSET_BAD; + unit_add_to_cleanup_queue(u); + return; + +good: + u->gc_marker = gc_marker + GC_OFFSET_GOOD; +} + +static unsigned manager_dispatch_gc_queue(Manager *m) { + Unit *u; + unsigned n = 0; + unsigned gc_marker; + + assert(m); + + /* log_debug("Running GC..."); */ + + m->gc_marker += _GC_OFFSET_MAX; + if (m->gc_marker + _GC_OFFSET_MAX <= _GC_OFFSET_MAX) + m->gc_marker = 1; + + gc_marker = m->gc_marker; + + while ((u = m->gc_queue)) { + assert(u->in_gc_queue); + + unit_gc_sweep(u, gc_marker); + + LIST_REMOVE(gc_queue, m->gc_queue, u); + u->in_gc_queue = false; + + n++; + + if (u->gc_marker == gc_marker + GC_OFFSET_BAD || + u->gc_marker == gc_marker + GC_OFFSET_UNSURE) { + if (u->id) + log_unit_debug(u, "Collecting."); + u->gc_marker = gc_marker + GC_OFFSET_BAD; + unit_add_to_cleanup_queue(u); + } + } + + m->n_in_gc_queue = 0; + + return n; +} + +static void manager_clear_jobs_and_units(Manager *m) { + Unit *u; + + assert(m); + + while ((u = hashmap_first(m->units))) + unit_free(u); + + manager_dispatch_cleanup_queue(m); + + assert(!m->load_queue); + assert(!m->run_queue); + assert(!m->dbus_unit_queue); + assert(!m->dbus_job_queue); + assert(!m->cleanup_queue); + assert(!m->gc_queue); + + assert(hashmap_isempty(m->jobs)); + assert(hashmap_isempty(m->units)); + + m->n_on_console = 0; + m->n_running_jobs = 0; +} + +Manager* manager_free(Manager *m) { + UnitType c; + int i; + + if (!m) + return NULL; + + manager_clear_jobs_and_units(m); + + for (c = 0; c < _UNIT_TYPE_MAX; c++) + if (unit_vtable[c]->shutdown) + unit_vtable[c]->shutdown(m); + + /* If we reexecute ourselves, we keep the root cgroup + * around */ + manager_shutdown_cgroup(m, m->exit_code != MANAGER_REEXECUTE); + + lookup_paths_flush_generator(&m->lookup_paths); + + bus_done(m); + + hashmap_free(m->units); + hashmap_free(m->jobs); + hashmap_free(m->watch_pids1); + hashmap_free(m->watch_pids2); + hashmap_free(m->watch_bus); + + set_free(m->startup_units); + set_free(m->failed_units); + + sd_event_source_unref(m->signal_event_source); + sd_event_source_unref(m->notify_event_source); + sd_event_source_unref(m->cgroups_agent_event_source); + sd_event_source_unref(m->time_change_event_source); + sd_event_source_unref(m->jobs_in_progress_event_source); + sd_event_source_unref(m->run_queue_event_source); + + safe_close(m->signal_fd); + safe_close(m->notify_fd); + safe_close(m->cgroups_agent_fd); + safe_close(m->time_change_fd); + safe_close(m->kdbus_fd); + + manager_close_ask_password(m); + + manager_close_idle_pipe(m); + + udev_unref(m->udev); + sd_event_unref(m->event); + + free(m->notify_socket); + + lookup_paths_free(&m->lookup_paths); + strv_free(m->environment); + + hashmap_free(m->cgroup_unit); + set_free_free(m->unit_path_cache); + + free(m->switch_root); + free(m->switch_root_init); + + for (i = 0; i < _RLIMIT_MAX; i++) + m->rlimit[i] = mfree(m->rlimit[i]); + + assert(hashmap_isempty(m->units_requiring_mounts_for)); + hashmap_free(m->units_requiring_mounts_for); + + free(m); + return NULL; +} + +void manager_enumerate(Manager *m) { + UnitType c; + + assert(m); + + /* Let's ask every type to load all units from disk/kernel + * that it might know */ + for (c = 0; c < _UNIT_TYPE_MAX; c++) { + if (!unit_type_supported(c)) { + log_debug("Unit type .%s is not supported on this system.", unit_type_to_string(c)); + continue; + } + + if (!unit_vtable[c]->enumerate) + continue; + + unit_vtable[c]->enumerate(m); + } + + manager_dispatch_load_queue(m); +} + +static void manager_coldplug(Manager *m) { + Iterator i; + Unit *u; + char *k; + int r; + + assert(m); + + /* Then, let's set up their initial state. */ + HASHMAP_FOREACH_KEY(u, k, m->units, i) { + + /* ignore aliases */ + if (u->id != k) + continue; + + r = unit_coldplug(u); + if (r < 0) + log_warning_errno(r, "We couldn't coldplug %s, proceeding anyway: %m", u->id); + } +} + +static void manager_build_unit_path_cache(Manager *m) { + char **i; + int r; + + assert(m); + + set_free_free(m->unit_path_cache); + + m->unit_path_cache = set_new(&string_hash_ops); + if (!m->unit_path_cache) { + r = -ENOMEM; + goto fail; + } + + /* This simply builds a list of files we know exist, so that + * we don't always have to go to disk */ + + STRV_FOREACH(i, m->lookup_paths.search_path) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + + d = opendir(*i); + if (!d) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to open directory %s, ignoring: %m", *i); + continue; + } + + FOREACH_DIRENT(de, d, r = -errno; goto fail) { + char *p; + + p = strjoin(streq(*i, "/") ? "" : *i, "/", de->d_name, NULL); + if (!p) { + r = -ENOMEM; + goto fail; + } + + r = set_consume(m->unit_path_cache, p); + if (r < 0) + goto fail; + } + } + + return; + +fail: + log_warning_errno(r, "Failed to build unit path cache, proceeding without: %m"); + m->unit_path_cache = set_free_free(m->unit_path_cache); +} + +static void manager_distribute_fds(Manager *m, FDSet *fds) { + Iterator i; + Unit *u; + + assert(m); + + HASHMAP_FOREACH(u, m->units, i) { + + if (fdset_size(fds) <= 0) + break; + + if (!UNIT_VTABLE(u)->distribute_fds) + continue; + + UNIT_VTABLE(u)->distribute_fds(u, fds); + } +} + +int manager_startup(Manager *m, FILE *serialization, FDSet *fds) { + int r, q; + + assert(m); + + r = lookup_paths_init(&m->lookup_paths, m->unit_file_scope, 0, NULL); + if (r < 0) + return r; + + /* Make sure the transient directory always exists, so that it remains in the search path */ + r = mkdir_p_label(m->lookup_paths.transient, 0755); + if (r < 0) + return r; + + dual_timestamp_get(&m->generators_start_timestamp); + r = manager_run_generators(m); + dual_timestamp_get(&m->generators_finish_timestamp); + if (r < 0) + return r; + + lookup_paths_reduce(&m->lookup_paths); + manager_build_unit_path_cache(m); + + /* If we will deserialize make sure that during enumeration + * this is already known, so we increase the counter here + * already */ + if (serialization) + m->n_reloading++; + + /* First, enumerate what we can from all config files */ + dual_timestamp_get(&m->units_load_start_timestamp); + manager_enumerate(m); + dual_timestamp_get(&m->units_load_finish_timestamp); + + /* Second, deserialize if there is something to deserialize */ + if (serialization) + r = manager_deserialize(m, serialization, fds); + + /* Any fds left? Find some unit which wants them. This is + * useful to allow container managers to pass some file + * descriptors to us pre-initialized. This enables + * socket-based activation of entire containers. */ + manager_distribute_fds(m, fds); + + /* We might have deserialized the notify fd, but if we didn't + * then let's create the bus now */ + q = manager_setup_notify(m); + if (q < 0 && r == 0) + r = q; + + q = manager_setup_cgroups_agent(m); + if (q < 0 && r == 0) + r = q; + + /* We might have deserialized the kdbus control fd, but if we + * didn't, then let's create the bus now. */ + manager_setup_kdbus(m); + manager_connect_bus(m, !!serialization); + bus_track_coldplug(m, &m->subscribed, &m->deserialized_subscribed); + + /* Third, fire things up! */ + manager_coldplug(m); + + if (serialization) { + assert(m->n_reloading > 0); + m->n_reloading--; + + /* Let's wait for the UnitNew/JobNew messages being + * sent, before we notify that the reload is + * finished */ + m->send_reloading_done = true; + } + + return r; +} + +int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, sd_bus_error *e, Job **_ret) { + int r; + Transaction *tr; + + assert(m); + assert(type < _JOB_TYPE_MAX); + assert(unit); + assert(mode < _JOB_MODE_MAX); + + if (mode == JOB_ISOLATE && type != JOB_START) + return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Isolate is only valid for start."); + + if (mode == JOB_ISOLATE && !unit->allow_isolate) + return sd_bus_error_setf(e, BUS_ERROR_NO_ISOLATION, "Operation refused, unit may not be isolated."); + + log_unit_debug(unit, "Trying to enqueue job %s/%s/%s", unit->id, job_type_to_string(type), job_mode_to_string(mode)); + + type = job_type_collapse(type, unit); + + tr = transaction_new(mode == JOB_REPLACE_IRREVERSIBLY); + if (!tr) + return -ENOMEM; + + r = transaction_add_job_and_dependencies(tr, type, unit, NULL, true, false, + mode == JOB_IGNORE_DEPENDENCIES || mode == JOB_IGNORE_REQUIREMENTS, + mode == JOB_IGNORE_DEPENDENCIES, e); + if (r < 0) + goto tr_abort; + + if (mode == JOB_ISOLATE) { + r = transaction_add_isolate_jobs(tr, m); + if (r < 0) + goto tr_abort; + } + + r = transaction_activate(tr, m, mode, e); + if (r < 0) + goto tr_abort; + + log_unit_debug(unit, + "Enqueued job %s/%s as %u", unit->id, + job_type_to_string(type), (unsigned) tr->anchor_job->id); + + if (_ret) + *_ret = tr->anchor_job; + + transaction_free(tr); + return 0; + +tr_abort: + transaction_abort(tr); + transaction_free(tr); + return r; +} + +int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, sd_bus_error *e, Job **ret) { + Unit *unit; + int r; + + assert(m); + assert(type < _JOB_TYPE_MAX); + assert(name); + assert(mode < _JOB_MODE_MAX); + + r = manager_load_unit(m, name, NULL, NULL, &unit); + if (r < 0) + return r; + + return manager_add_job(m, type, unit, mode, e, ret); +} + +int manager_add_job_by_name_and_warn(Manager *m, JobType type, const char *name, JobMode mode, Job **ret) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(m); + assert(type < _JOB_TYPE_MAX); + assert(name); + assert(mode < _JOB_MODE_MAX); + + r = manager_add_job_by_name(m, type, name, mode, &error, ret); + if (r < 0) + return log_warning_errno(r, "Failed to enqueue %s job for %s: %s", job_mode_to_string(mode), name, bus_error_message(&error, r)); + + return r; +} + +Job *manager_get_job(Manager *m, uint32_t id) { + assert(m); + + return hashmap_get(m->jobs, UINT32_TO_PTR(id)); +} + +Unit *manager_get_unit(Manager *m, const char *name) { + assert(m); + assert(name); + + return hashmap_get(m->units, name); +} + +unsigned manager_dispatch_load_queue(Manager *m) { + Unit *u; + unsigned n = 0; + + assert(m); + + /* Make sure we are not run recursively */ + if (m->dispatching_load_queue) + return 0; + + m->dispatching_load_queue = true; + + /* Dispatches the load queue. Takes a unit from the queue and + * tries to load its data until the queue is empty */ + + while ((u = m->load_queue)) { + assert(u->in_load_queue); + + unit_load(u); + n++; + } + + m->dispatching_load_queue = false; + return n; +} + +int manager_load_unit_prepare( + Manager *m, + const char *name, + const char *path, + sd_bus_error *e, + Unit **_ret) { + + Unit *ret; + UnitType t; + int r; + + assert(m); + assert(name || path); + + /* This will prepare the unit for loading, but not actually + * load anything from disk. */ + + if (path && !is_path(path)) + return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Path %s is not absolute.", path); + + if (!name) + name = basename(path); + + t = unit_name_to_type(name); + + if (t == _UNIT_TYPE_INVALID || !unit_name_is_valid(name, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) { + if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) + return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Unit name %s is missing the instance name.", name); + + return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Unit name %s is not valid.", name); + } + + ret = manager_get_unit(m, name); + if (ret) { + *_ret = ret; + return 1; + } + + ret = unit_new(m, unit_vtable[t]->object_size); + if (!ret) + return -ENOMEM; + + if (path) { + ret->fragment_path = strdup(path); + if (!ret->fragment_path) { + unit_free(ret); + return -ENOMEM; + } + } + + r = unit_add_name(ret, name); + if (r < 0) { + unit_free(ret); + return r; + } + + unit_add_to_load_queue(ret); + unit_add_to_dbus_queue(ret); + unit_add_to_gc_queue(ret); + + if (_ret) + *_ret = ret; + + return 0; +} + +int manager_load_unit( + Manager *m, + const char *name, + const char *path, + sd_bus_error *e, + Unit **_ret) { + + int r; + + assert(m); + + /* This will load the service information files, but not actually + * start any services or anything. */ + + r = manager_load_unit_prepare(m, name, path, e, _ret); + if (r != 0) + return r; + + manager_dispatch_load_queue(m); + + if (_ret) + *_ret = unit_follow_merge(*_ret); + + return 0; +} + +void manager_dump_jobs(Manager *s, FILE *f, const char *prefix) { + Iterator i; + Job *j; + + assert(s); + assert(f); + + HASHMAP_FOREACH(j, s->jobs, i) + job_dump(j, f, prefix); +} + +void manager_dump_units(Manager *s, FILE *f, const char *prefix) { + Iterator i; + Unit *u; + const char *t; + + assert(s); + assert(f); + + HASHMAP_FOREACH_KEY(u, t, s->units, i) + if (u->id == t) + unit_dump(u, f, prefix); +} + +void manager_clear_jobs(Manager *m) { + Job *j; + + assert(m); + + while ((j = hashmap_first(m->jobs))) + /* No need to recurse. We're cancelling all jobs. */ + job_finish_and_invalidate(j, JOB_CANCELED, false, false); +} + +static int manager_dispatch_run_queue(sd_event_source *source, void *userdata) { + Manager *m = userdata; + Job *j; + + assert(source); + assert(m); + + while ((j = m->run_queue)) { + assert(j->installed); + assert(j->in_run_queue); + + job_run_and_invalidate(j); + } + + if (m->n_running_jobs > 0) + manager_watch_jobs_in_progress(m); + + if (m->n_on_console > 0) + manager_watch_idle_pipe(m); + + return 1; +} + +static unsigned manager_dispatch_dbus_queue(Manager *m) { + Job *j; + Unit *u; + unsigned n = 0; + + assert(m); + + if (m->dispatching_dbus_queue) + return 0; + + m->dispatching_dbus_queue = true; + + while ((u = m->dbus_unit_queue)) { + assert(u->in_dbus_queue); + + bus_unit_send_change_signal(u); + n++; + } + + while ((j = m->dbus_job_queue)) { + assert(j->in_dbus_queue); + + bus_job_send_change_signal(j); + n++; + } + + m->dispatching_dbus_queue = false; + + if (m->send_reloading_done) { + m->send_reloading_done = false; + + bus_manager_send_reloading(m, false); + } + + if (m->queued_message) + bus_send_queued_message(m); + + return n; +} + +static int manager_dispatch_cgroups_agent_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + char buf[PATH_MAX+1]; + ssize_t n; + + n = recv(fd, buf, sizeof(buf), 0); + if (n < 0) + return log_error_errno(errno, "Failed to read cgroups agent message: %m"); + if (n == 0) { + log_error("Got zero-length cgroups agent message, ignoring."); + return 0; + } + if ((size_t) n >= sizeof(buf)) { + log_error("Got overly long cgroups agent message, ignoring."); + return 0; + } + + if (memchr(buf, 0, n)) { + log_error("Got cgroups agent message with embedded NUL byte, ignoring."); + return 0; + } + buf[n] = 0; + + manager_notify_cgroup_empty(m, buf); + bus_forward_agent_released(m, buf); + + return 0; +} + +static void manager_invoke_notify_message(Manager *m, Unit *u, pid_t pid, const char *buf, size_t n, FDSet *fds) { + _cleanup_strv_free_ char **tags = NULL; + + assert(m); + assert(u); + assert(buf); + assert(n > 0); + + tags = strv_split(buf, "\n\r"); + if (!tags) { + log_oom(); + return; + } + + if (UNIT_VTABLE(u)->notify_message) + UNIT_VTABLE(u)->notify_message(u, pid, tags, fds); + else + log_unit_debug(u, "Got notification message for unit. Ignoring."); +} + +static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + _cleanup_fdset_free_ FDSet *fds = NULL; + Manager *m = userdata; + + char buf[NOTIFY_BUFFER_MAX+1]; + struct iovec iovec = { + .iov_base = buf, + .iov_len = sizeof(buf)-1, + }; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(struct ucred)) + + CMSG_SPACE(sizeof(int) * NOTIFY_FD_MAX)]; + } control = {}; + struct msghdr msghdr = { + .msg_iov = &iovec, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + + struct cmsghdr *cmsg; + struct ucred *ucred = NULL; + bool found = false; + Unit *u1, *u2, *u3; + int r, *fd_array = NULL; + unsigned n_fds = 0; + ssize_t n; + + assert(m); + assert(m->notify_fd == fd); + + if (revents != EPOLLIN) { + log_warning("Got unexpected poll event for notify fd."); + return 0; + } + + n = recvmsg(m->notify_fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); + if (n < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return -errno; + } + + CMSG_FOREACH(cmsg, &msghdr) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + + fd_array = (int*) CMSG_DATA(cmsg); + n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + + } else if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_CREDENTIALS && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) { + + ucred = (struct ucred*) CMSG_DATA(cmsg); + } + } + + if (n_fds > 0) { + assert(fd_array); + + r = fdset_new_array(&fds, fd_array, n_fds); + if (r < 0) { + close_many(fd_array, n_fds); + return log_oom(); + } + } + + if (!ucred || ucred->pid <= 0) { + log_warning("Received notify message without valid credentials. Ignoring."); + return 0; + } + + if ((size_t) n >= sizeof(buf)) { + log_warning("Received notify message exceeded maximum size. Ignoring."); + return 0; + } + + buf[n] = 0; + + /* Notify every unit that might be interested, but try + * to avoid notifying the same one multiple times. */ + u1 = manager_get_unit_by_pid_cgroup(m, ucred->pid); + if (u1) { + manager_invoke_notify_message(m, u1, ucred->pid, buf, n, fds); + found = true; + } + + u2 = hashmap_get(m->watch_pids1, PID_TO_PTR(ucred->pid)); + if (u2 && u2 != u1) { + manager_invoke_notify_message(m, u2, ucred->pid, buf, n, fds); + found = true; + } + + u3 = hashmap_get(m->watch_pids2, PID_TO_PTR(ucred->pid)); + if (u3 && u3 != u2 && u3 != u1) { + manager_invoke_notify_message(m, u3, ucred->pid, buf, n, fds); + found = true; + } + + if (!found) + log_warning("Cannot find unit for notify message of PID "PID_FMT".", ucred->pid); + + if (fdset_size(fds) > 0) + log_warning("Got auxiliary fds with notification message, closing all."); + + return 0; +} + +static void invoke_sigchld_event(Manager *m, Unit *u, const siginfo_t *si) { + assert(m); + assert(u); + assert(si); + + log_unit_debug(u, "Child "PID_FMT" belongs to %s", si->si_pid, u->id); + + unit_unwatch_pid(u, si->si_pid); + + if (UNIT_VTABLE(u)->sigchld_event) + UNIT_VTABLE(u)->sigchld_event(u, si->si_pid, si->si_code, si->si_status); +} + +static int manager_dispatch_sigchld(Manager *m) { + assert(m); + + for (;;) { + siginfo_t si = {}; + + /* First we call waitd() for a PID and do not reap the + * zombie. That way we can still access /proc/$PID for + * it while it is a zombie. */ + if (waitid(P_ALL, 0, &si, WEXITED|WNOHANG|WNOWAIT) < 0) { + + if (errno == ECHILD) + break; + + if (errno == EINTR) + continue; + + return -errno; + } + + if (si.si_pid <= 0) + break; + + if (si.si_code == CLD_EXITED || si.si_code == CLD_KILLED || si.si_code == CLD_DUMPED) { + _cleanup_free_ char *name = NULL; + Unit *u1, *u2, *u3; + + get_process_comm(si.si_pid, &name); + + log_debug("Child "PID_FMT" (%s) died (code=%s, status=%i/%s)", + si.si_pid, strna(name), + sigchld_code_to_string(si.si_code), + si.si_status, + strna(si.si_code == CLD_EXITED + ? exit_status_to_string(si.si_status, EXIT_STATUS_FULL) + : signal_to_string(si.si_status))); + + /* And now figure out the unit this belongs + * to, it might be multiple... */ + u1 = manager_get_unit_by_pid_cgroup(m, si.si_pid); + if (u1) + invoke_sigchld_event(m, u1, &si); + u2 = hashmap_get(m->watch_pids1, PID_TO_PTR(si.si_pid)); + if (u2 && u2 != u1) + invoke_sigchld_event(m, u2, &si); + u3 = hashmap_get(m->watch_pids2, PID_TO_PTR(si.si_pid)); + if (u3 && u3 != u2 && u3 != u1) + invoke_sigchld_event(m, u3, &si); + } + + /* And now, we actually reap the zombie. */ + if (waitid(P_PID, si.si_pid, &si, WEXITED) < 0) { + if (errno == EINTR) + continue; + + return -errno; + } + } + + return 0; +} + +static int manager_start_target(Manager *m, const char *name, JobMode mode) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + log_debug("Activating special unit %s", name); + + r = manager_add_job_by_name(m, JOB_START, name, mode, &error, NULL); + if (r < 0) + log_error("Failed to enqueue %s job: %s", name, bus_error_message(&error, r)); + + return r; +} + +static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + ssize_t n; + struct signalfd_siginfo sfsi; + bool sigchld = false; + int r; + + assert(m); + assert(m->signal_fd == fd); + + if (revents != EPOLLIN) { + log_warning("Got unexpected events from signal file descriptor."); + return 0; + } + + for (;;) { + n = read(m->signal_fd, &sfsi, sizeof(sfsi)); + if (n != sizeof(sfsi)) { + + if (n >= 0) + return -EIO; + + if (errno == EINTR || errno == EAGAIN) + break; + + return -errno; + } + + log_received_signal(sfsi.ssi_signo == SIGCHLD || + (sfsi.ssi_signo == SIGTERM && MANAGER_IS_USER(m)) + ? LOG_DEBUG : LOG_INFO, + &sfsi); + + switch (sfsi.ssi_signo) { + + case SIGCHLD: + sigchld = true; + break; + + case SIGTERM: + if (MANAGER_IS_SYSTEM(m)) { + /* This is for compatibility with the + * original sysvinit */ + m->exit_code = MANAGER_REEXECUTE; + break; + } + + /* Fall through */ + + case SIGINT: + if (MANAGER_IS_SYSTEM(m)) { + + /* If the user presses C-A-D more than + * 7 times within 2s, we reboot + * immediately. */ + + if (ratelimit_test(&m->ctrl_alt_del_ratelimit)) + manager_start_target(m, SPECIAL_CTRL_ALT_DEL_TARGET, JOB_REPLACE_IRREVERSIBLY); + else { + log_notice("Ctrl-Alt-Del was pressed more than 7 times within 2s, rebooting immediately."); + status_printf(NULL, true, false, "Ctrl-Alt-Del was pressed more than 7 times within 2s, rebooting immediately."); + m->exit_code = MANAGER_REBOOT; + } + + break; + } + + /* Run the exit target if there is one, if not, just exit. */ + if (manager_start_target(m, SPECIAL_EXIT_TARGET, JOB_REPLACE) < 0) { + m->exit_code = MANAGER_EXIT; + return 0; + } + + break; + + case SIGWINCH: + if (MANAGER_IS_SYSTEM(m)) + manager_start_target(m, SPECIAL_KBREQUEST_TARGET, JOB_REPLACE); + + /* This is a nop on non-init */ + break; + + case SIGPWR: + if (MANAGER_IS_SYSTEM(m)) + manager_start_target(m, SPECIAL_SIGPWR_TARGET, JOB_REPLACE); + + /* This is a nop on non-init */ + break; + + case SIGUSR1: { + Unit *u; + + u = manager_get_unit(m, SPECIAL_DBUS_SERVICE); + + if (!u || UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) { + log_info("Trying to reconnect to bus..."); + bus_init(m, true); + } + + if (!u || !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) { + log_info("Loading D-Bus service..."); + manager_start_target(m, SPECIAL_DBUS_SERVICE, JOB_REPLACE); + } + + break; + } + + case SIGUSR2: { + _cleanup_free_ char *dump = NULL; + _cleanup_fclose_ FILE *f = NULL; + size_t size; + + f = open_memstream(&dump, &size); + if (!f) { + log_warning_errno(errno, "Failed to allocate memory stream: %m"); + break; + } + + manager_dump_units(m, f, "\t"); + manager_dump_jobs(m, f, "\t"); + + r = fflush_and_check(f); + if (r < 0) { + log_warning_errno(r, "Failed to write status stream: %m"); + break; + } + + log_dump(LOG_INFO, dump); + break; + } + + case SIGHUP: + m->exit_code = MANAGER_RELOAD; + break; + + default: { + + /* Starting SIGRTMIN+0 */ + static const char * const target_table[] = { + [0] = SPECIAL_DEFAULT_TARGET, + [1] = SPECIAL_RESCUE_TARGET, + [2] = SPECIAL_EMERGENCY_TARGET, + [3] = SPECIAL_HALT_TARGET, + [4] = SPECIAL_POWEROFF_TARGET, + [5] = SPECIAL_REBOOT_TARGET, + [6] = SPECIAL_KEXEC_TARGET + }; + + /* Starting SIGRTMIN+13, so that target halt and system halt are 10 apart */ + static const ManagerExitCode code_table[] = { + [0] = MANAGER_HALT, + [1] = MANAGER_POWEROFF, + [2] = MANAGER_REBOOT, + [3] = MANAGER_KEXEC + }; + + if ((int) sfsi.ssi_signo >= SIGRTMIN+0 && + (int) sfsi.ssi_signo < SIGRTMIN+(int) ELEMENTSOF(target_table)) { + int idx = (int) sfsi.ssi_signo - SIGRTMIN; + manager_start_target(m, target_table[idx], + (idx == 1 || idx == 2) ? JOB_ISOLATE : JOB_REPLACE); + break; + } + + if ((int) sfsi.ssi_signo >= SIGRTMIN+13 && + (int) sfsi.ssi_signo < SIGRTMIN+13+(int) ELEMENTSOF(code_table)) { + m->exit_code = code_table[sfsi.ssi_signo - SIGRTMIN - 13]; + break; + } + + switch (sfsi.ssi_signo - SIGRTMIN) { + + case 20: + manager_set_show_status(m, SHOW_STATUS_YES); + break; + + case 21: + manager_set_show_status(m, SHOW_STATUS_NO); + break; + + case 22: + log_set_max_level(LOG_DEBUG); + log_info("Setting log level to debug."); + break; + + case 23: + log_set_max_level(LOG_INFO); + log_info("Setting log level to info."); + break; + + case 24: + if (MANAGER_IS_USER(m)) { + m->exit_code = MANAGER_EXIT; + return 0; + } + + /* This is a nop on init */ + break; + + case 26: + case 29: /* compatibility: used to be mapped to LOG_TARGET_SYSLOG_OR_KMSG */ + log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); + log_notice("Setting log target to journal-or-kmsg."); + break; + + case 27: + log_set_target(LOG_TARGET_CONSOLE); + log_notice("Setting log target to console."); + break; + + case 28: + log_set_target(LOG_TARGET_KMSG); + log_notice("Setting log target to kmsg."); + break; + + default: + log_warning("Got unhandled signal <%s>.", signal_to_string(sfsi.ssi_signo)); + } + } + } + } + + if (sigchld) + manager_dispatch_sigchld(m); + + return 0; +} + +static int manager_dispatch_time_change_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + Iterator i; + Unit *u; + + assert(m); + assert(m->time_change_fd == fd); + + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_TIME_CHANGE), + LOG_MESSAGE("Time has been changed"), + NULL); + + /* Restart the watch */ + m->time_change_event_source = sd_event_source_unref(m->time_change_event_source); + m->time_change_fd = safe_close(m->time_change_fd); + + manager_setup_time_change(m); + + HASHMAP_FOREACH(u, m->units, i) + if (UNIT_VTABLE(u)->time_change) + UNIT_VTABLE(u)->time_change(u); + + return 0; +} + +static int manager_dispatch_idle_pipe_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + + assert(m); + assert(m->idle_pipe[2] == fd); + + m->no_console_output = m->n_on_console > 0; + + manager_close_idle_pipe(m); + + return 0; +} + +static int manager_dispatch_jobs_in_progress(sd_event_source *source, usec_t usec, void *userdata) { + Manager *m = userdata; + int r; + uint64_t next; + + assert(m); + assert(source); + + manager_print_jobs_in_progress(m); + + next = now(CLOCK_MONOTONIC) + JOBS_IN_PROGRESS_PERIOD_USEC; + r = sd_event_source_set_time(source, next); + if (r < 0) + return r; + + return sd_event_source_set_enabled(source, SD_EVENT_ONESHOT); +} + +int manager_loop(Manager *m) { + int r; + + RATELIMIT_DEFINE(rl, 1*USEC_PER_SEC, 50000); + + assert(m); + m->exit_code = MANAGER_OK; + + /* Release the path cache */ + m->unit_path_cache = set_free_free(m->unit_path_cache); + + manager_check_finished(m); + + /* There might still be some zombies hanging around from + * before we were exec()'ed. Let's reap them. */ + r = manager_dispatch_sigchld(m); + if (r < 0) + return r; + + while (m->exit_code == MANAGER_OK) { + usec_t wait_usec; + + if (m->runtime_watchdog > 0 && m->runtime_watchdog != USEC_INFINITY && MANAGER_IS_SYSTEM(m)) + watchdog_ping(); + + if (!ratelimit_test(&rl)) { + /* Yay, something is going seriously wrong, pause a little */ + log_warning("Looping too fast. Throttling execution a little."); + sleep(1); + } + + if (manager_dispatch_load_queue(m) > 0) + continue; + + if (manager_dispatch_gc_queue(m) > 0) + continue; + + if (manager_dispatch_cleanup_queue(m) > 0) + continue; + + if (manager_dispatch_cgroup_queue(m) > 0) + continue; + + if (manager_dispatch_dbus_queue(m) > 0) + continue; + + /* Sleep for half the watchdog time */ + if (m->runtime_watchdog > 0 && m->runtime_watchdog != USEC_INFINITY && MANAGER_IS_SYSTEM(m)) { + wait_usec = m->runtime_watchdog / 2; + if (wait_usec <= 0) + wait_usec = 1; + } else + wait_usec = USEC_INFINITY; + + r = sd_event_run(m->event, wait_usec); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + } + + return m->exit_code; +} + +int manager_load_unit_from_dbus_path(Manager *m, const char *s, sd_bus_error *e, Unit **_u) { + _cleanup_free_ char *n = NULL; + Unit *u; + int r; + + assert(m); + assert(s); + assert(_u); + + r = unit_name_from_dbus_path(s, &n); + if (r < 0) + return r; + + r = manager_load_unit(m, n, NULL, e, &u); + if (r < 0) + return r; + + *_u = u; + + return 0; +} + +int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j) { + const char *p; + unsigned id; + Job *j; + int r; + + assert(m); + assert(s); + assert(_j); + + p = startswith(s, "/org/freedesktop/systemd1/job/"); + if (!p) + return -EINVAL; + + r = safe_atou(p, &id); + if (r < 0) + return r; + + j = manager_get_job(m, id); + if (!j) + return -ENOENT; + + *_j = j; + + return 0; +} + +void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success) { + +#ifdef HAVE_AUDIT + _cleanup_free_ char *p = NULL; + const char *msg; + int audit_fd, r; + + if (!MANAGER_IS_SYSTEM(m)) + return; + + audit_fd = get_audit_fd(); + if (audit_fd < 0) + return; + + /* Don't generate audit events if the service was already + * started and we're just deserializing */ + if (MANAGER_IS_RELOADING(m)) + return; + + if (u->type != UNIT_SERVICE) + return; + + r = unit_name_to_prefix_and_instance(u->id, &p); + if (r < 0) { + log_error_errno(r, "Failed to extract prefix and instance of unit name: %m"); + return; + } + + msg = strjoina("unit=", p); + if (audit_log_user_comm_message(audit_fd, type, msg, "systemd", NULL, NULL, NULL, success) < 0) { + if (errno == EPERM) + /* We aren't allowed to send audit messages? + * Then let's not retry again. */ + close_audit_fd(); + else + log_warning_errno(errno, "Failed to send audit message: %m"); + } +#endif + +} + +void manager_send_unit_plymouth(Manager *m, Unit *u) { + static const union sockaddr_union sa = PLYMOUTH_SOCKET; + _cleanup_free_ char *message = NULL; + _cleanup_close_ int fd = -1; + int n = 0; + + /* Don't generate plymouth events if the service was already + * started and we're just deserializing */ + if (MANAGER_IS_RELOADING(m)) + return; + + if (!MANAGER_IS_SYSTEM(m)) + return; + + if (detect_container() > 0) + return; + + if (u->type != UNIT_SERVICE && + u->type != UNIT_MOUNT && + u->type != UNIT_SWAP) + return; + + /* We set SOCK_NONBLOCK here so that we rather drop the + * message then wait for plymouth */ + fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) { + log_error_errno(errno, "socket() failed: %m"); + return; + } + + if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) { + + if (!IN_SET(errno, EPIPE, EAGAIN, ENOENT, ECONNREFUSED, ECONNRESET, ECONNABORTED)) + log_error_errno(errno, "connect() failed: %m"); + return; + } + + if (asprintf(&message, "U\002%c%s%n", (int) (strlen(u->id) + 1), u->id, &n) < 0) { + log_oom(); + return; + } + + errno = 0; + if (write(fd, message, n + 1) != n + 1) + if (!IN_SET(errno, EPIPE, EAGAIN, ENOENT, ECONNREFUSED, ECONNRESET, ECONNABORTED)) + log_error_errno(errno, "Failed to write Plymouth message: %m"); +} + +int manager_open_serialization(Manager *m, FILE **_f) { + const char *path; + int fd = -1; + FILE *f; + + assert(_f); + + path = MANAGER_IS_SYSTEM(m) ? "/run/systemd" : "/tmp"; + fd = open_tmpfile_unlinkable(path, O_RDWR|O_CLOEXEC); + if (fd < 0) + return -errno; + + log_debug("Serializing state to %s", path); + + f = fdopen(fd, "w+"); + if (!f) { + safe_close(fd); + return -errno; + } + + *_f = f; + + return 0; +} + +int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) { + Iterator i; + Unit *u; + const char *t; + char **e; + int r; + + assert(m); + assert(f); + assert(fds); + + m->n_reloading++; + + fprintf(f, "current-job-id=%"PRIu32"\n", m->current_job_id); + fprintf(f, "taint-usr=%s\n", yes_no(m->taint_usr)); + fprintf(f, "n-installed-jobs=%u\n", m->n_installed_jobs); + fprintf(f, "n-failed-jobs=%u\n", m->n_failed_jobs); + + dual_timestamp_serialize(f, "firmware-timestamp", &m->firmware_timestamp); + dual_timestamp_serialize(f, "loader-timestamp", &m->loader_timestamp); + dual_timestamp_serialize(f, "kernel-timestamp", &m->kernel_timestamp); + dual_timestamp_serialize(f, "initrd-timestamp", &m->initrd_timestamp); + + if (!in_initrd()) { + dual_timestamp_serialize(f, "userspace-timestamp", &m->userspace_timestamp); + dual_timestamp_serialize(f, "finish-timestamp", &m->finish_timestamp); + dual_timestamp_serialize(f, "security-start-timestamp", &m->security_start_timestamp); + dual_timestamp_serialize(f, "security-finish-timestamp", &m->security_finish_timestamp); + dual_timestamp_serialize(f, "generators-start-timestamp", &m->generators_start_timestamp); + dual_timestamp_serialize(f, "generators-finish-timestamp", &m->generators_finish_timestamp); + dual_timestamp_serialize(f, "units-load-start-timestamp", &m->units_load_start_timestamp); + dual_timestamp_serialize(f, "units-load-finish-timestamp", &m->units_load_finish_timestamp); + } + + if (!switching_root) { + STRV_FOREACH(e, m->environment) { + _cleanup_free_ char *ce; + + ce = cescape(*e); + if (!ce) + return -ENOMEM; + + fprintf(f, "env=%s\n", *e); + } + } + + if (m->notify_fd >= 0) { + int copy; + + copy = fdset_put_dup(fds, m->notify_fd); + if (copy < 0) + return copy; + + fprintf(f, "notify-fd=%i\n", copy); + fprintf(f, "notify-socket=%s\n", m->notify_socket); + } + + if (m->cgroups_agent_fd >= 0) { + int copy; + + copy = fdset_put_dup(fds, m->cgroups_agent_fd); + if (copy < 0) + return copy; + + fprintf(f, "cgroups-agent-fd=%i\n", copy); + } + + if (m->kdbus_fd >= 0) { + int copy; + + copy = fdset_put_dup(fds, m->kdbus_fd); + if (copy < 0) + return copy; + + fprintf(f, "kdbus-fd=%i\n", copy); + } + + bus_track_serialize(m->subscribed, f); + + fputc('\n', f); + + HASHMAP_FOREACH_KEY(u, t, m->units, i) { + if (u->id != t) + continue; + + /* Start marker */ + fputs(u->id, f); + fputc('\n', f); + + r = unit_serialize(u, f, fds, !switching_root); + if (r < 0) { + m->n_reloading--; + return r; + } + } + + assert(m->n_reloading > 0); + m->n_reloading--; + + if (ferror(f)) + return -EIO; + + r = bus_fdset_add_all(m, fds); + if (r < 0) + return r; + + return 0; +} + +int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { + int r = 0; + + assert(m); + assert(f); + + log_debug("Deserializing state..."); + + m->n_reloading++; + + for (;;) { + char line[LINE_MAX], *l; + + if (!fgets(line, sizeof(line), f)) { + if (feof(f)) + r = 0; + else + r = -errno; + + goto finish; + } + + char_array_0(line); + l = strstrip(line); + + if (l[0] == 0) + break; + + if (startswith(l, "current-job-id=")) { + uint32_t id; + + if (safe_atou32(l+15, &id) < 0) + log_debug("Failed to parse current job id value %s", l+15); + else + m->current_job_id = MAX(m->current_job_id, id); + + } else if (startswith(l, "n-installed-jobs=")) { + uint32_t n; + + if (safe_atou32(l+17, &n) < 0) + log_debug("Failed to parse installed jobs counter %s", l+17); + else + m->n_installed_jobs += n; + + } else if (startswith(l, "n-failed-jobs=")) { + uint32_t n; + + if (safe_atou32(l+14, &n) < 0) + log_debug("Failed to parse failed jobs counter %s", l+14); + else + m->n_failed_jobs += n; + + } else if (startswith(l, "taint-usr=")) { + int b; + + b = parse_boolean(l+10); + if (b < 0) + log_debug("Failed to parse taint /usr flag %s", l+10); + else + m->taint_usr = m->taint_usr || b; + + } else if (startswith(l, "firmware-timestamp=")) + dual_timestamp_deserialize(l+19, &m->firmware_timestamp); + else if (startswith(l, "loader-timestamp=")) + dual_timestamp_deserialize(l+17, &m->loader_timestamp); + else if (startswith(l, "kernel-timestamp=")) + dual_timestamp_deserialize(l+17, &m->kernel_timestamp); + else if (startswith(l, "initrd-timestamp=")) + dual_timestamp_deserialize(l+17, &m->initrd_timestamp); + else if (startswith(l, "userspace-timestamp=")) + dual_timestamp_deserialize(l+20, &m->userspace_timestamp); + else if (startswith(l, "finish-timestamp=")) + dual_timestamp_deserialize(l+17, &m->finish_timestamp); + else if (startswith(l, "security-start-timestamp=")) + dual_timestamp_deserialize(l+25, &m->security_start_timestamp); + else if (startswith(l, "security-finish-timestamp=")) + dual_timestamp_deserialize(l+26, &m->security_finish_timestamp); + else if (startswith(l, "generators-start-timestamp=")) + dual_timestamp_deserialize(l+27, &m->generators_start_timestamp); + else if (startswith(l, "generators-finish-timestamp=")) + dual_timestamp_deserialize(l+28, &m->generators_finish_timestamp); + else if (startswith(l, "units-load-start-timestamp=")) + dual_timestamp_deserialize(l+27, &m->units_load_start_timestamp); + else if (startswith(l, "units-load-finish-timestamp=")) + dual_timestamp_deserialize(l+28, &m->units_load_finish_timestamp); + else if (startswith(l, "env=")) { + _cleanup_free_ char *uce = NULL; + char **e; + + r = cunescape(l + 4, UNESCAPE_RELAX, &uce); + if (r < 0) + goto finish; + + e = strv_env_set(m->environment, uce); + if (!e) { + r = -ENOMEM; + goto finish; + } + + strv_free(m->environment); + m->environment = e; + + } else if (startswith(l, "notify-fd=")) { + int fd; + + if (safe_atoi(l + 10, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_debug("Failed to parse notify fd: %s", l + 10); + else { + m->notify_event_source = sd_event_source_unref(m->notify_event_source); + safe_close(m->notify_fd); + m->notify_fd = fdset_remove(fds, fd); + } + + } else if (startswith(l, "notify-socket=")) { + char *n; + + n = strdup(l+14); + if (!n) { + r = -ENOMEM; + goto finish; + } + + free(m->notify_socket); + m->notify_socket = n; + + } else if (startswith(l, "cgroups-agent-fd=")) { + int fd; + + if (safe_atoi(l + 17, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_debug("Failed to parse cgroups agent fd: %s", l + 10); + else { + m->cgroups_agent_event_source = sd_event_source_unref(m->cgroups_agent_event_source); + safe_close(m->cgroups_agent_fd); + m->cgroups_agent_fd = fdset_remove(fds, fd); + } + + } else if (startswith(l, "kdbus-fd=")) { + int fd; + + if (safe_atoi(l + 9, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_debug("Failed to parse kdbus fd: %s", l + 9); + else { + safe_close(m->kdbus_fd); + m->kdbus_fd = fdset_remove(fds, fd); + } + + } else { + int k; + + k = bus_track_deserialize_item(&m->deserialized_subscribed, l); + if (k < 0) + log_debug_errno(k, "Failed to deserialize bus tracker object: %m"); + else if (k == 0) + log_debug("Unknown serialization item '%s'", l); + } + } + + for (;;) { + Unit *u; + char name[UNIT_NAME_MAX+2]; + + /* Start marker */ + if (!fgets(name, sizeof(name), f)) { + if (feof(f)) + r = 0; + else + r = -errno; + + goto finish; + } + + char_array_0(name); + + r = manager_load_unit(m, strstrip(name), NULL, NULL, &u); + if (r < 0) + goto finish; + + r = unit_deserialize(u, f, fds); + if (r < 0) + goto finish; + } + +finish: + if (ferror(f)) + r = -EIO; + + assert(m->n_reloading > 0); + m->n_reloading--; + + return r; +} + +int manager_reload(Manager *m) { + int r, q; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_fdset_free_ FDSet *fds = NULL; + + assert(m); + + r = manager_open_serialization(m, &f); + if (r < 0) + return r; + + m->n_reloading++; + bus_manager_send_reloading(m, true); + + fds = fdset_new(); + if (!fds) { + m->n_reloading--; + return -ENOMEM; + } + + r = manager_serialize(m, f, fds, false); + if (r < 0) { + m->n_reloading--; + return r; + } + + if (fseeko(f, 0, SEEK_SET) < 0) { + m->n_reloading--; + return -errno; + } + + /* From here on there is no way back. */ + manager_clear_jobs_and_units(m); + lookup_paths_flush_generator(&m->lookup_paths); + lookup_paths_free(&m->lookup_paths); + + q = lookup_paths_init(&m->lookup_paths, m->unit_file_scope, 0, NULL); + if (q < 0 && r >= 0) + r = q; + + /* Find new unit paths */ + q = manager_run_generators(m); + if (q < 0 && r >= 0) + r = q; + + lookup_paths_reduce(&m->lookup_paths); + manager_build_unit_path_cache(m); + + /* First, enumerate what we can from all config files */ + manager_enumerate(m); + + /* Second, deserialize our stored data */ + q = manager_deserialize(m, f, fds); + if (q < 0 && r >= 0) + r = q; + + fclose(f); + f = NULL; + + /* Re-register notify_fd as event source */ + q = manager_setup_notify(m); + if (q < 0 && r >= 0) + r = q; + + q = manager_setup_cgroups_agent(m); + if (q < 0 && r >= 0) + r = q; + + /* Third, fire things up! */ + manager_coldplug(m); + + /* Sync current state of bus names with our set of listening units */ + if (m->api_bus) + manager_sync_bus_names(m, m->api_bus); + + assert(m->n_reloading > 0); + m->n_reloading--; + + m->send_reloading_done = true; + + return r; +} + +void manager_reset_failed(Manager *m) { + Unit *u; + Iterator i; + + assert(m); + + HASHMAP_FOREACH(u, m->units, i) + unit_reset_failed(u); +} + +bool manager_unit_inactive_or_pending(Manager *m, const char *name) { + Unit *u; + + assert(m); + assert(name); + + /* Returns true if the unit is inactive or going down */ + u = manager_get_unit(m, name); + if (!u) + return true; + + return unit_inactive_or_pending(u); +} + +static void manager_notify_finished(Manager *m) { + char userspace[FORMAT_TIMESPAN_MAX], initrd[FORMAT_TIMESPAN_MAX], kernel[FORMAT_TIMESPAN_MAX], sum[FORMAT_TIMESPAN_MAX]; + usec_t firmware_usec, loader_usec, kernel_usec, initrd_usec, userspace_usec, total_usec; + + if (m->test_run) + return; + + if (MANAGER_IS_SYSTEM(m) && detect_container() <= 0) { + + /* Note that m->kernel_usec.monotonic is always at 0, + * and m->firmware_usec.monotonic and + * m->loader_usec.monotonic should be considered + * negative values. */ + + firmware_usec = m->firmware_timestamp.monotonic - m->loader_timestamp.monotonic; + loader_usec = m->loader_timestamp.monotonic - m->kernel_timestamp.monotonic; + userspace_usec = m->finish_timestamp.monotonic - m->userspace_timestamp.monotonic; + total_usec = m->firmware_timestamp.monotonic + m->finish_timestamp.monotonic; + + if (dual_timestamp_is_set(&m->initrd_timestamp)) { + + kernel_usec = m->initrd_timestamp.monotonic - m->kernel_timestamp.monotonic; + initrd_usec = m->userspace_timestamp.monotonic - m->initrd_timestamp.monotonic; + + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_STARTUP_FINISHED), + "KERNEL_USEC="USEC_FMT, kernel_usec, + "INITRD_USEC="USEC_FMT, initrd_usec, + "USERSPACE_USEC="USEC_FMT, userspace_usec, + LOG_MESSAGE("Startup finished in %s (kernel) + %s (initrd) + %s (userspace) = %s.", + format_timespan(kernel, sizeof(kernel), kernel_usec, USEC_PER_MSEC), + format_timespan(initrd, sizeof(initrd), initrd_usec, USEC_PER_MSEC), + format_timespan(userspace, sizeof(userspace), userspace_usec, USEC_PER_MSEC), + format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC)), + NULL); + } else { + kernel_usec = m->userspace_timestamp.monotonic - m->kernel_timestamp.monotonic; + initrd_usec = 0; + + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_STARTUP_FINISHED), + "KERNEL_USEC="USEC_FMT, kernel_usec, + "USERSPACE_USEC="USEC_FMT, userspace_usec, + LOG_MESSAGE("Startup finished in %s (kernel) + %s (userspace) = %s.", + format_timespan(kernel, sizeof(kernel), kernel_usec, USEC_PER_MSEC), + format_timespan(userspace, sizeof(userspace), userspace_usec, USEC_PER_MSEC), + format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC)), + NULL); + } + } else { + firmware_usec = loader_usec = initrd_usec = kernel_usec = 0; + total_usec = userspace_usec = m->finish_timestamp.monotonic - m->userspace_timestamp.monotonic; + + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_STARTUP_FINISHED), + "USERSPACE_USEC="USEC_FMT, userspace_usec, + LOG_MESSAGE("Startup finished in %s.", + format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC)), + NULL); + } + + bus_manager_send_finished(m, firmware_usec, loader_usec, kernel_usec, initrd_usec, userspace_usec, total_usec); + + sd_notifyf(false, + "READY=1\n" + "STATUS=Startup finished in %s.", + format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC)); +} + +void manager_check_finished(Manager *m) { + assert(m); + + if (MANAGER_IS_RELOADING(m)) + return; + + /* Verify that we are actually running currently. Initially + * the exit code is set to invalid, and during operation it is + * then set to MANAGER_OK */ + if (m->exit_code != MANAGER_OK) + return; + + if (hashmap_size(m->jobs) > 0) { + if (m->jobs_in_progress_event_source) + /* Ignore any failure, this is only for feedback */ + (void) sd_event_source_set_time(m->jobs_in_progress_event_source, now(CLOCK_MONOTONIC) + JOBS_IN_PROGRESS_WAIT_USEC); + + return; + } + + manager_flip_auto_status(m, false); + + /* Notify Type=idle units that we are done now */ + manager_close_idle_pipe(m); + + /* Turn off confirm spawn now */ + m->confirm_spawn = false; + + /* No need to update ask password status when we're going non-interactive */ + manager_close_ask_password(m); + + /* This is no longer the first boot */ + manager_set_first_boot(m, false); + + if (dual_timestamp_is_set(&m->finish_timestamp)) + return; + + dual_timestamp_get(&m->finish_timestamp); + + manager_notify_finished(m); + + manager_invalidate_startup_units(m); +} + +static int manager_run_generators(Manager *m) { + _cleanup_strv_free_ char **paths = NULL; + const char *argv[5]; + char **path; + int r; + + assert(m); + + if (m->test_run) + return 0; + + paths = generator_binary_paths(m->unit_file_scope); + if (!paths) + return log_oom(); + + /* Optimize by skipping the whole process by not creating output directories + * if no generators are found. */ + STRV_FOREACH(path, paths) { + if (access(*path, F_OK) >= 0) + goto found; + if (errno != ENOENT) + log_warning_errno(errno, "Failed to open generator directory %s: %m", *path); + } + + return 0; + + found: + r = lookup_paths_mkdir_generator(&m->lookup_paths); + if (r < 0) + goto finish; + + argv[0] = NULL; /* Leave this empty, execute_directory() will fill something in */ + argv[1] = m->lookup_paths.generator; + argv[2] = m->lookup_paths.generator_early; + argv[3] = m->lookup_paths.generator_late; + argv[4] = NULL; + + RUN_WITH_UMASK(0022) + execute_directories((const char* const*) paths, DEFAULT_TIMEOUT_USEC, (char**) argv); + +finish: + lookup_paths_trim_generator(&m->lookup_paths); + return r; +} + +int manager_environment_add(Manager *m, char **minus, char **plus) { + char **a = NULL, **b = NULL, **l; + assert(m); + + l = m->environment; + + if (!strv_isempty(minus)) { + a = strv_env_delete(l, 1, minus); + if (!a) + return -ENOMEM; + + l = a; + } + + if (!strv_isempty(plus)) { + b = strv_env_merge(2, l, plus); + if (!b) { + strv_free(a); + return -ENOMEM; + } + + l = b; + } + + if (m->environment != l) + strv_free(m->environment); + if (a != l) + strv_free(a); + if (b != l) + strv_free(b); + + m->environment = l; + manager_clean_environment(m); + strv_sort(m->environment); + + return 0; +} + +int manager_set_default_rlimits(Manager *m, struct rlimit **default_rlimit) { + int i; + + assert(m); + + for (i = 0; i < _RLIMIT_MAX; i++) { + m->rlimit[i] = mfree(m->rlimit[i]); + + if (!default_rlimit[i]) + continue; + + m->rlimit[i] = newdup(struct rlimit, default_rlimit[i], 1); + if (!m->rlimit[i]) + return -ENOMEM; + } + + return 0; +} + +void manager_recheck_journal(Manager *m) { + Unit *u; + + assert(m); + + if (!MANAGER_IS_SYSTEM(m)) + return; + + u = manager_get_unit(m, SPECIAL_JOURNALD_SOCKET); + if (u && SOCKET(u)->state != SOCKET_RUNNING) { + log_close_journal(); + return; + } + + u = manager_get_unit(m, SPECIAL_JOURNALD_SERVICE); + if (u && SERVICE(u)->state != SERVICE_RUNNING) { + log_close_journal(); + return; + } + + /* Hmm, OK, so the socket is fully up and the service is up + * too, then let's make use of the thing. */ + log_open(); +} + +void manager_set_show_status(Manager *m, ShowStatus mode) { + assert(m); + assert(IN_SET(mode, SHOW_STATUS_AUTO, SHOW_STATUS_NO, SHOW_STATUS_YES, SHOW_STATUS_TEMPORARY)); + + if (!MANAGER_IS_SYSTEM(m)) + return; + + if (m->show_status != mode) + log_debug("%s showing of status.", + mode == SHOW_STATUS_NO ? "Disabling" : "Enabling"); + m->show_status = mode; + + if (mode > 0) + (void) touch("/run/systemd/show-status"); + else + (void) unlink("/run/systemd/show-status"); +} + +static bool manager_get_show_status(Manager *m, StatusType type) { + assert(m); + + if (!MANAGER_IS_SYSTEM(m)) + return false; + + if (m->no_console_output) + return false; + + if (!IN_SET(manager_state(m), MANAGER_INITIALIZING, MANAGER_STARTING, MANAGER_STOPPING)) + return false; + + /* If we cannot find out the status properly, just proceed. */ + if (type != STATUS_TYPE_EMERGENCY && manager_check_ask_password(m) > 0) + return false; + + if (m->show_status > 0) + return true; + + return false; +} + +void manager_set_first_boot(Manager *m, bool b) { + assert(m); + + if (!MANAGER_IS_SYSTEM(m)) + return; + + if (m->first_boot != (int) b) { + if (b) + (void) touch("/run/systemd/first-boot"); + else + (void) unlink("/run/systemd/first-boot"); + } + + m->first_boot = b; +} + +void manager_status_printf(Manager *m, StatusType type, const char *status, const char *format, ...) { + va_list ap; + + /* If m is NULL, assume we're after shutdown and let the messages through. */ + + if (m && !manager_get_show_status(m, type)) + return; + + /* XXX We should totally drop the check for ephemeral here + * and thus effectively make 'Type=idle' pointless. */ + if (type == STATUS_TYPE_EPHEMERAL && m && m->n_on_console > 0) + return; + + va_start(ap, format); + status_vprintf(status, true, type == STATUS_TYPE_EPHEMERAL, format, ap); + va_end(ap); +} + +Set *manager_get_units_requiring_mounts_for(Manager *m, const char *path) { + char p[strlen(path)+1]; + + assert(m); + assert(path); + + strcpy(p, path); + path_kill_slashes(p); + + return hashmap_get(m->units_requiring_mounts_for, streq(p, "/") ? "" : p); +} + +const char *manager_get_runtime_prefix(Manager *m) { + assert(m); + + return MANAGER_IS_SYSTEM(m) ? + "/run" : + getenv("XDG_RUNTIME_DIR"); +} + +int manager_update_failed_units(Manager *m, Unit *u, bool failed) { + unsigned size; + int r; + + assert(m); + assert(u->manager == m); + + size = set_size(m->failed_units); + + if (failed) { + r = set_ensure_allocated(&m->failed_units, NULL); + if (r < 0) + return log_oom(); + + if (set_put(m->failed_units, u) < 0) + return log_oom(); + } else + (void) set_remove(m->failed_units, u); + + if (set_size(m->failed_units) != size) + bus_manager_send_change_signal(m); + + return 0; +} + +ManagerState manager_state(Manager *m) { + Unit *u; + + assert(m); + + /* Did we ever finish booting? If not then we are still starting up */ + if (!dual_timestamp_is_set(&m->finish_timestamp)) { + + u = manager_get_unit(m, SPECIAL_BASIC_TARGET); + if (!u || !UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) + return MANAGER_INITIALIZING; + + return MANAGER_STARTING; + } + + /* Is the special shutdown target queued? If so, we are in shutdown state */ + u = manager_get_unit(m, SPECIAL_SHUTDOWN_TARGET); + if (u && u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_RELOAD_OR_START)) + return MANAGER_STOPPING; + + /* Are the rescue or emergency targets active or queued? If so we are in maintenance state */ + u = manager_get_unit(m, SPECIAL_RESCUE_TARGET); + if (u && (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)) || + (u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_RELOAD_OR_START)))) + return MANAGER_MAINTENANCE; + + u = manager_get_unit(m, SPECIAL_EMERGENCY_TARGET); + if (u && (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)) || + (u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_RELOAD_OR_START)))) + return MANAGER_MAINTENANCE; + + /* Are there any failed units? If so, we are in degraded mode */ + if (set_size(m->failed_units) > 0) + return MANAGER_DEGRADED; + + return MANAGER_RUNNING; +} + +static const char *const manager_state_table[_MANAGER_STATE_MAX] = { + [MANAGER_INITIALIZING] = "initializing", + [MANAGER_STARTING] = "starting", + [MANAGER_RUNNING] = "running", + [MANAGER_DEGRADED] = "degraded", + [MANAGER_MAINTENANCE] = "maintenance", + [MANAGER_STOPPING] = "stopping", +}; + +DEFINE_STRING_TABLE_LOOKUP(manager_state, ManagerState); diff --git a/src/libcore/manager.h b/src/libcore/manager.h new file mode 100644 index 0000000000..70d79ce549 --- /dev/null +++ b/src/libcore/manager.h @@ -0,0 +1,379 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include + +#include +#include + +#include "cgroup-util.h" +#include "fdset.h" +#include "hashmap.h" +#include "list.h" +#include "ratelimit.h" + +/* Enforce upper limit how many names we allow */ +#define MANAGER_MAX_NAMES 131072 /* 128K */ + +typedef struct Manager Manager; + +typedef enum ManagerState { + MANAGER_INITIALIZING, + MANAGER_STARTING, + MANAGER_RUNNING, + MANAGER_DEGRADED, + MANAGER_MAINTENANCE, + MANAGER_STOPPING, + _MANAGER_STATE_MAX, + _MANAGER_STATE_INVALID = -1 +} ManagerState; + +typedef enum ManagerExitCode { + MANAGER_OK, + MANAGER_EXIT, + MANAGER_RELOAD, + MANAGER_REEXECUTE, + MANAGER_REBOOT, + MANAGER_POWEROFF, + MANAGER_HALT, + MANAGER_KEXEC, + MANAGER_SWITCH_ROOT, + _MANAGER_EXIT_CODE_MAX, + _MANAGER_EXIT_CODE_INVALID = -1 +} ManagerExitCode; + +typedef enum StatusType { + STATUS_TYPE_EPHEMERAL, + STATUS_TYPE_NORMAL, + STATUS_TYPE_EMERGENCY, +} StatusType; + +#include "execute.h" +#include "job.h" +#include "path-lookup.h" +#include "show-status.h" +#include "unit-name.h" + +struct Manager { + /* Note that the set of units we know of is allowed to be + * inconsistent. However the subset of it that is loaded may + * not, and the list of jobs may neither. */ + + /* Active jobs and units */ + Hashmap *units; /* name string => Unit object n:1 */ + Hashmap *jobs; /* job id => Job object 1:1 */ + + /* To make it easy to iterate through the units of a specific + * type we maintain a per type linked list */ + LIST_HEAD(Unit, units_by_type[_UNIT_TYPE_MAX]); + + /* Units that need to be loaded */ + LIST_HEAD(Unit, load_queue); /* this is actually more a stack than a queue, but uh. */ + + /* Jobs that need to be run */ + LIST_HEAD(Job, run_queue); /* more a stack than a queue, too */ + + /* Units and jobs that have not yet been announced via + * D-Bus. When something about a job changes it is added here + * if it is not in there yet. This allows easy coalescing of + * D-Bus change signals. */ + LIST_HEAD(Unit, dbus_unit_queue); + LIST_HEAD(Job, dbus_job_queue); + + /* Units to remove */ + LIST_HEAD(Unit, cleanup_queue); + + /* Units to check when doing GC */ + LIST_HEAD(Unit, gc_queue); + + /* Units that should be realized */ + LIST_HEAD(Unit, cgroup_queue); + + sd_event *event; + + /* We use two hash tables here, since the same PID might be + * watched by two different units: once the unit that forked + * it off, and possibly a different unit to which it was + * joined as cgroup member. Since we know that it is either + * one or two units for each PID we just use to hashmaps + * here. */ + Hashmap *watch_pids1; /* pid => Unit object n:1 */ + Hashmap *watch_pids2; /* pid => Unit object n:1 */ + + /* A set contains all units which cgroup should be refreshed after startup */ + Set *startup_units; + + /* A set which contains all currently failed units */ + Set *failed_units; + + sd_event_source *run_queue_event_source; + + char *notify_socket; + int notify_fd; + sd_event_source *notify_event_source; + + int cgroups_agent_fd; + sd_event_source *cgroups_agent_event_source; + + int signal_fd; + sd_event_source *signal_event_source; + + int time_change_fd; + sd_event_source *time_change_event_source; + + sd_event_source *jobs_in_progress_event_source; + + UnitFileScope unit_file_scope; + LookupPaths lookup_paths; + Set *unit_path_cache; + + char **environment; + + usec_t runtime_watchdog; + usec_t shutdown_watchdog; + + dual_timestamp firmware_timestamp; + dual_timestamp loader_timestamp; + dual_timestamp kernel_timestamp; + dual_timestamp initrd_timestamp; + dual_timestamp userspace_timestamp; + dual_timestamp finish_timestamp; + + dual_timestamp security_start_timestamp; + dual_timestamp security_finish_timestamp; + dual_timestamp generators_start_timestamp; + dual_timestamp generators_finish_timestamp; + dual_timestamp units_load_start_timestamp; + dual_timestamp units_load_finish_timestamp; + + struct udev* udev; + + /* Data specific to the device subsystem */ + struct udev_monitor* udev_monitor; + sd_event_source *udev_event_source; + Hashmap *devices_by_sysfs; + + /* Data specific to the mount subsystem */ + struct libmnt_monitor *mount_monitor; + sd_event_source *mount_event_source; + + /* Data specific to the swap filesystem */ + FILE *proc_swaps; + sd_event_source *swap_event_source; + Hashmap *swaps_by_devnode; + + /* Data specific to the D-Bus subsystem */ + sd_bus *api_bus, *system_bus; + Set *private_buses; + int private_listen_fd; + sd_event_source *private_listen_event_source; + + /* Contains all the clients that are subscribed to signals via + the API bus. Note that private bus connections are always + considered subscribes, since they last for very short only, + and it is much simpler that way. */ + sd_bus_track *subscribed; + char **deserialized_subscribed; + + /* This is used during reloading: before the reload we queue + * the reply message here, and afterwards we send it */ + sd_bus_message *queued_message; + + Hashmap *watch_bus; /* D-Bus names => Unit object n:1 */ + + bool send_reloading_done; + + uint32_t current_job_id; + uint32_t default_unit_job_id; + + /* Data specific to the Automount subsystem */ + int dev_autofs_fd; + + /* Data specific to the cgroup subsystem */ + Hashmap *cgroup_unit; + CGroupMask cgroup_supported; + char *cgroup_root; + + /* Notifications from cgroups, when the unified hierarchy is + * used is done via inotify. */ + int cgroup_inotify_fd; + sd_event_source *cgroup_inotify_event_source; + Hashmap *cgroup_inotify_wd_unit; + + /* Make sure the user cannot accidentally unmount our cgroup + * file system */ + int pin_cgroupfs_fd; + + int gc_marker; + unsigned n_in_gc_queue; + + /* Flags */ + ManagerExitCode exit_code:5; + + bool dispatching_load_queue:1; + bool dispatching_dbus_queue:1; + + bool taint_usr:1; + + bool test_run:1; + + /* If non-zero, exit with the following value when the systemd + * process terminate. Useful for containers: systemd-nspawn could get + * the return value. */ + uint8_t return_value; + + ShowStatus show_status; + bool confirm_spawn; + bool no_console_output; + + ExecOutput default_std_output, default_std_error; + + usec_t default_restart_usec, default_timeout_start_usec, default_timeout_stop_usec; + + usec_t default_start_limit_interval; + unsigned default_start_limit_burst; + + bool default_cpu_accounting; + bool default_memory_accounting; + bool default_io_accounting; + bool default_blockio_accounting; + bool default_tasks_accounting; + + uint64_t default_tasks_max; + usec_t default_timer_accuracy_usec; + + struct rlimit *rlimit[_RLIMIT_MAX]; + + /* non-zero if we are reloading or reexecuting, */ + int n_reloading; + + unsigned n_installed_jobs; + unsigned n_failed_jobs; + + /* Jobs in progress watching */ + unsigned n_running_jobs; + unsigned n_on_console; + unsigned jobs_in_progress_iteration; + + /* Do we have any outstanding password prompts? */ + int have_ask_password; + int ask_password_inotify_fd; + sd_event_source *ask_password_event_source; + + /* Type=idle pipes */ + int idle_pipe[4]; + sd_event_source *idle_pipe_event_source; + + char *switch_root; + char *switch_root_init; + + /* This maps all possible path prefixes to the units needing + * them. It's a hashmap with a path string as key and a Set as + * value where Unit objects are contained. */ + Hashmap *units_requiring_mounts_for; + + /* Reference to the kdbus bus control fd */ + int kdbus_fd; + + /* Used for processing polkit authorization responses */ + Hashmap *polkit_registry; + + /* When the user hits C-A-D more than 7 times per 2s, reboot immediately... */ + RateLimit ctrl_alt_del_ratelimit; + + const char *unit_log_field; + const char *unit_log_format_string; + + int first_boot; /* tri-state */ +}; + +#define MANAGER_IS_SYSTEM(m) ((m)->unit_file_scope == UNIT_FILE_SYSTEM) +#define MANAGER_IS_USER(m) ((m)->unit_file_scope != UNIT_FILE_SYSTEM) + +#define MANAGER_IS_RELOADING(m) ((m)->n_reloading > 0) + +int manager_new(UnitFileScope scope, bool test_run, Manager **m); +Manager* manager_free(Manager *m); + +void manager_enumerate(Manager *m); +int manager_startup(Manager *m, FILE *serialization, FDSet *fds); + +Job *manager_get_job(Manager *m, uint32_t id); +Unit *manager_get_unit(Manager *m, const char *name); + +int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j); + +int manager_load_unit_prepare(Manager *m, const char *name, const char *path, sd_bus_error *e, Unit **_ret); +int manager_load_unit(Manager *m, const char *name, const char *path, sd_bus_error *e, Unit **_ret); +int manager_load_unit_from_dbus_path(Manager *m, const char *s, sd_bus_error *e, Unit **_u); + +int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, sd_bus_error *e, Job **_ret); +int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, sd_bus_error *e, Job **_ret); +int manager_add_job_by_name_and_warn(Manager *m, JobType type, const char *name, JobMode mode, Job **ret); + +void manager_dump_units(Manager *s, FILE *f, const char *prefix); +void manager_dump_jobs(Manager *s, FILE *f, const char *prefix); + +void manager_clear_jobs(Manager *m); + +unsigned manager_dispatch_load_queue(Manager *m); + +int manager_environment_add(Manager *m, char **minus, char **plus); +int manager_set_default_rlimits(Manager *m, struct rlimit **default_rlimit); + +int manager_loop(Manager *m); + +int manager_open_serialization(Manager *m, FILE **_f); + +int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root); +int manager_deserialize(Manager *m, FILE *f, FDSet *fds); + +int manager_reload(Manager *m); + +void manager_reset_failed(Manager *m); + +void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success); +void manager_send_unit_plymouth(Manager *m, Unit *u); + +bool manager_unit_inactive_or_pending(Manager *m, const char *name); + +void manager_check_finished(Manager *m); + +void manager_recheck_journal(Manager *m); + +void manager_set_show_status(Manager *m, ShowStatus mode); +void manager_set_first_boot(Manager *m, bool b); + +void manager_status_printf(Manager *m, StatusType type, const char *status, const char *format, ...) _printf_(4,5); +void manager_flip_auto_status(Manager *m, bool enable); + +Set *manager_get_units_requiring_mounts_for(Manager *m, const char *path); + +const char *manager_get_runtime_prefix(Manager *m); + +ManagerState manager_state(Manager *m); + +int manager_update_failed_units(Manager *m, Unit *u, bool failed); + +const char *manager_state_to_string(ManagerState m) _const_; +ManagerState manager_state_from_string(const char *s) _pure_; diff --git a/src/libcore/mount-setup.c b/src/libcore/mount-setup.c new file mode 100644 index 0000000000..40fc548b42 --- /dev/null +++ b/src/libcore/mount-setup.c @@ -0,0 +1,413 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include + +#include "alloc-util.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "dev-setup.h" +#include "efivars.h" +#include "label.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "mount-setup.h" +#include "mount-util.h" +#include "path-util.h" +#include "set.h" +#include "smack-util.h" +#include "strv.h" +#include "user-util.h" +#include "util.h" +#include "virt.h" + +typedef enum MountMode { + MNT_NONE = 0, + MNT_FATAL = 1 << 0, + MNT_IN_CONTAINER = 1 << 1, +} MountMode; + +typedef struct MountPoint { + const char *what; + const char *where; + const char *type; + const char *options; + unsigned long flags; + bool (*condition_fn)(void); + MountMode mode; +} MountPoint; + +/* The first three entries we might need before SELinux is up. The + * fourth (securityfs) is needed by IMA to load a custom policy. The + * other ones we can delay until SELinux and IMA are loaded. When + * SMACK is enabled we need smackfs, too, so it's a fifth one. */ +#ifdef HAVE_SMACK +#define N_EARLY_MOUNT 5 +#else +#define N_EARLY_MOUNT 4 +#endif + +static const MountPoint mount_table[] = { + { "sysfs", "/sys", "sysfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, + NULL, MNT_FATAL|MNT_IN_CONTAINER }, + { "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, + NULL, MNT_FATAL|MNT_IN_CONTAINER }, + { "devtmpfs", "/dev", "devtmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME, + NULL, MNT_FATAL|MNT_IN_CONTAINER }, + { "securityfs", "/sys/kernel/security", "securityfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, + NULL, MNT_NONE }, +#ifdef HAVE_SMACK + { "smackfs", "/sys/fs/smackfs", "smackfs", "smackfsdef=*", MS_NOSUID|MS_NOEXEC|MS_NODEV, + mac_smack_use, MNT_FATAL }, + { "tmpfs", "/dev/shm", "tmpfs", "mode=1777,smackfsroot=*", MS_NOSUID|MS_NODEV|MS_STRICTATIME, + mac_smack_use, MNT_FATAL }, +#endif + { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, + NULL, MNT_FATAL|MNT_IN_CONTAINER }, + { "devpts", "/dev/pts", "devpts", "mode=620,gid=" STRINGIFY(TTY_GID), MS_NOSUID|MS_NOEXEC, + NULL, MNT_IN_CONTAINER }, +#ifdef HAVE_SMACK + { "tmpfs", "/run", "tmpfs", "mode=755,smackfsroot=*", MS_NOSUID|MS_NODEV|MS_STRICTATIME, + mac_smack_use, MNT_FATAL }, +#endif + { "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME, + NULL, MNT_FATAL|MNT_IN_CONTAINER }, + { "cgroup", "/sys/fs/cgroup", "cgroup2", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, + cg_is_unified_wanted, MNT_FATAL|MNT_IN_CONTAINER }, + { "tmpfs", "/sys/fs/cgroup", "tmpfs", "mode=755", MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, + cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER }, + { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd,xattr", MS_NOSUID|MS_NOEXEC|MS_NODEV, + cg_is_legacy_wanted, MNT_IN_CONTAINER }, + { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd", MS_NOSUID|MS_NOEXEC|MS_NODEV, + cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER }, + { "pstore", "/sys/fs/pstore", "pstore", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, + NULL, MNT_NONE }, +#ifdef ENABLE_EFI + { "efivarfs", "/sys/firmware/efi/efivars", "efivarfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, + is_efi_boot, MNT_NONE }, +#endif + { "kdbusfs", "/sys/fs/kdbus", "kdbusfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, + is_kdbus_wanted, MNT_IN_CONTAINER }, +}; + +/* These are API file systems that might be mounted by other software, + * we just list them here so that we know that we should ignore them */ + +static const char ignore_paths[] = + /* SELinux file systems */ + "/sys/fs/selinux\0" + /* Container bind mounts */ + "/proc/sys\0" + "/dev/console\0" + "/proc/kmsg\0"; + +bool mount_point_is_api(const char *path) { + unsigned i; + + /* Checks if this mount point is considered "API", and hence + * should be ignored */ + + for (i = 0; i < ELEMENTSOF(mount_table); i ++) + if (path_equal(path, mount_table[i].where)) + return true; + + return path_startswith(path, "/sys/fs/cgroup/"); +} + +bool mount_point_ignore(const char *path) { + const char *i; + + NULSTR_FOREACH(i, ignore_paths) + if (path_equal(path, i)) + return true; + + return false; +} + +static int mount_one(const MountPoint *p, bool relabel) { + int r; + + assert(p); + + if (p->condition_fn && !p->condition_fn()) + return 0; + + /* Relabel first, just in case */ + if (relabel) + (void) label_fix(p->where, true, true); + + r = path_is_mount_point(p->where, AT_SYMLINK_FOLLOW); + if (r < 0 && r != -ENOENT) { + log_full_errno((p->mode & MNT_FATAL) ? LOG_ERR : LOG_DEBUG, r, "Failed to determine whether %s is a mount point: %m", p->where); + return (p->mode & MNT_FATAL) ? r : 0; + } + if (r > 0) + return 0; + + /* Skip securityfs in a container */ + if (!(p->mode & MNT_IN_CONTAINER) && detect_container() > 0) + return 0; + + /* The access mode here doesn't really matter too much, since + * the mounted file system will take precedence anyway. */ + if (relabel) + (void) mkdir_p_label(p->where, 0755); + else + (void) mkdir_p(p->where, 0755); + + log_debug("Mounting %s to %s of type %s with options %s.", + p->what, + p->where, + p->type, + strna(p->options)); + + if (mount(p->what, + p->where, + p->type, + p->flags, + p->options) < 0) { + log_full_errno((p->mode & MNT_FATAL) ? LOG_ERR : LOG_DEBUG, errno, "Failed to mount %s at %s: %m", p->type, p->where); + return (p->mode & MNT_FATAL) ? -errno : 0; + } + + /* Relabel again, since we now mounted something fresh here */ + if (relabel) + (void) label_fix(p->where, false, false); + + return 1; +} + +static int mount_points_setup(unsigned n, bool loaded_policy) { + unsigned i; + int r = 0; + + for (i = 0; i < n; i ++) { + int j; + + j = mount_one(mount_table + i, loaded_policy); + if (j != 0 && r >= 0) + r = j; + } + + return r; +} + +int mount_setup_early(void) { + assert_cc(N_EARLY_MOUNT <= ELEMENTSOF(mount_table)); + + /* Do a minimal mount of /proc and friends to enable the most + * basic stuff, such as SELinux */ + return mount_points_setup(N_EARLY_MOUNT, false); +} + +int mount_cgroup_controllers(char ***join_controllers) { + _cleanup_set_free_free_ Set *controllers = NULL; + int r; + + if (!cg_is_legacy_wanted()) + return 0; + + /* Mount all available cgroup controllers that are built into the kernel. */ + + controllers = set_new(&string_hash_ops); + if (!controllers) + return log_oom(); + + r = cg_kernel_controllers(controllers); + if (r < 0) + return log_error_errno(r, "Failed to enumerate cgroup controllers: %m"); + + for (;;) { + _cleanup_free_ char *options = NULL, *controller = NULL, *where = NULL; + MountPoint p = { + .what = "cgroup", + .type = "cgroup", + .flags = MS_NOSUID|MS_NOEXEC|MS_NODEV, + .mode = MNT_IN_CONTAINER, + }; + char ***k = NULL; + + controller = set_steal_first(controllers); + if (!controller) + break; + + if (join_controllers) + for (k = join_controllers; *k; k++) + if (strv_find(*k, controller)) + break; + + if (k && *k) { + char **i, **j; + + for (i = *k, j = *k; *i; i++) { + + if (!streq(*i, controller)) { + _cleanup_free_ char *t; + + t = set_remove(controllers, *i); + if (!t) { + free(*i); + continue; + } + } + + *(j++) = *i; + } + + *j = NULL; + + options = strv_join(*k, ","); + if (!options) + return log_oom(); + } else { + options = controller; + controller = NULL; + } + + where = strappend("/sys/fs/cgroup/", options); + if (!where) + return log_oom(); + + p.where = where; + p.options = options; + + r = mount_one(&p, true); + if (r < 0) + return r; + + if (r > 0 && k && *k) { + char **i; + + for (i = *k; *i; i++) { + _cleanup_free_ char *t = NULL; + + t = strappend("/sys/fs/cgroup/", *i); + if (!t) + return log_oom(); + + r = symlink(options, t); + if (r >= 0) { +#ifdef SMACK_RUN_LABEL + _cleanup_free_ char *src; + src = strappend("/sys/fs/cgroup/", options); + if (!src) + return log_oom(); + r = mac_smack_copy(t, src); + if (r < 0 && r != -EOPNOTSUPP) + return log_error_errno(r, "Failed to copy smack label from %s to %s: %m", src, t); +#endif + } else if (errno != EEXIST) + return log_error_errno(errno, "Failed to create symlink %s: %m", t); + } + } + } + + /* Now that we mounted everything, let's make the tmpfs the + * cgroup file systems are mounted into read-only. */ + (void) mount("tmpfs", "/sys/fs/cgroup", "tmpfs", MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, "mode=755"); + + return 0; +} + +#if defined(HAVE_SELINUX) || defined(HAVE_SMACK) +static int nftw_cb( + const char *fpath, + const struct stat *sb, + int tflag, + struct FTW *ftwbuf) { + + /* No need to label /dev twice in a row... */ + if (_unlikely_(ftwbuf->level == 0)) + return FTW_CONTINUE; + + label_fix(fpath, false, false); + + /* /run/initramfs is static data and big, no need to + * dynamically relabel its contents at boot... */ + if (_unlikely_(ftwbuf->level == 1 && + tflag == FTW_D && + streq(fpath, "/run/initramfs"))) + return FTW_SKIP_SUBTREE; + + return FTW_CONTINUE; +}; +#endif + +int mount_setup(bool loaded_policy) { + int r = 0; + + r = mount_points_setup(ELEMENTSOF(mount_table), loaded_policy); + + if (r < 0) + return r; + +#if defined(HAVE_SELINUX) || defined(HAVE_SMACK) + /* Nodes in devtmpfs and /run need to be manually updated for + * the appropriate labels, after mounting. The other virtual + * API file systems like /sys and /proc do not need that, they + * use the same label for all their files. */ + if (loaded_policy) { + usec_t before_relabel, after_relabel; + char timespan[FORMAT_TIMESPAN_MAX]; + + before_relabel = now(CLOCK_MONOTONIC); + + nftw("/dev", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL); + nftw("/dev/shm", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL); + nftw("/run", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL); + + after_relabel = now(CLOCK_MONOTONIC); + + log_info("Relabelled /dev and /run in %s.", + format_timespan(timespan, sizeof(timespan), after_relabel - before_relabel, 0)); + } +#endif + + /* Create a few default symlinks, which are normally created + * by udevd, but some scripts might need them before we start + * udevd. */ + dev_setup(NULL, UID_INVALID, GID_INVALID); + + /* Mark the root directory as shared in regards to mount + * propagation. The kernel defaults to "private", but we think + * it makes more sense to have a default of "shared" so that + * nspawn and the container tools work out of the box. If + * specific setups need other settings they can reset the + * propagation mode to private if needed. */ + if (detect_container() <= 0) + if (mount(NULL, "/", NULL, MS_REC|MS_SHARED, NULL) < 0) + log_warning_errno(errno, "Failed to set up the root directory for shared mount propagation: %m"); + + /* Create a few directories we always want around, Note that + * sd_booted() checks for /run/systemd/system, so this mkdir + * really needs to stay for good, otherwise software that + * copied sd-daemon.c into their sources will misdetect + * systemd. */ + mkdir_label("/run/systemd", 0755); + mkdir_label("/run/systemd/system", 0755); + mkdir_label("/run/systemd/inaccessible", 0000); + + return 0; +} diff --git a/src/libcore/mount-setup.h b/src/libcore/mount-setup.h new file mode 100644 index 0000000000..647bd770ae --- /dev/null +++ b/src/libcore/mount-setup.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 + +int mount_setup_early(void); +int mount_setup(bool loaded_policy); + +int mount_cgroup_controllers(char ***join_controllers); + +bool mount_point_is_api(const char *path); +bool mount_point_ignore(const char *path); diff --git a/src/libcore/mount.c b/src/libcore/mount.c new file mode 100644 index 0000000000..7db9d1325b --- /dev/null +++ b/src/libcore/mount.c @@ -0,0 +1,1881 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include + +#include + +#include "alloc-util.h" +#include "dbus-mount.h" +#include "escape.h" +#include "exit-status.h" +#include "formats-util.h" +#include "fstab-util.h" +#include "log.h" +#include "manager.h" +#include "mkdir.h" +#include "mount-setup.h" +#include "mount-util.h" +#include "mount.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "special.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "unit.h" + +#define RETRY_UMOUNT_MAX 32 + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct libmnt_table*, mnt_free_table); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct libmnt_iter*, mnt_free_iter); + +static const UnitActiveState state_translation_table[_MOUNT_STATE_MAX] = { + [MOUNT_DEAD] = UNIT_INACTIVE, + [MOUNT_MOUNTING] = UNIT_ACTIVATING, + [MOUNT_MOUNTING_DONE] = UNIT_ACTIVE, + [MOUNT_MOUNTED] = UNIT_ACTIVE, + [MOUNT_REMOUNTING] = UNIT_RELOADING, + [MOUNT_UNMOUNTING] = UNIT_DEACTIVATING, + [MOUNT_MOUNTING_SIGTERM] = UNIT_DEACTIVATING, + [MOUNT_MOUNTING_SIGKILL] = UNIT_DEACTIVATING, + [MOUNT_REMOUNTING_SIGTERM] = UNIT_RELOADING, + [MOUNT_REMOUNTING_SIGKILL] = UNIT_RELOADING, + [MOUNT_UNMOUNTING_SIGTERM] = UNIT_DEACTIVATING, + [MOUNT_UNMOUNTING_SIGKILL] = UNIT_DEACTIVATING, + [MOUNT_FAILED] = UNIT_FAILED +}; + +static int mount_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); +static int mount_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); + +static bool mount_needs_network(const char *options, const char *fstype) { + if (fstab_test_option(options, "_netdev\0")) + return true; + + if (fstype && fstype_is_network(fstype)) + return true; + + return false; +} + +static bool mount_is_network(const MountParameters *p) { + assert(p); + + return mount_needs_network(p->options, p->fstype); +} + +static bool mount_is_loop(const MountParameters *p) { + assert(p); + + if (fstab_test_option(p->options, "loop\0")) + return true; + + return false; +} + +static bool mount_is_bind(const MountParameters *p) { + assert(p); + + if (fstab_test_option(p->options, "bind\0" "rbind\0")) + return true; + + if (p->fstype && STR_IN_SET(p->fstype, "bind", "rbind")) + return true; + + return false; +} + +static bool mount_is_auto(const MountParameters *p) { + assert(p); + + return !fstab_test_option(p->options, "noauto\0"); +} + +static bool mount_is_automount(const MountParameters *p) { + assert(p); + + return fstab_test_option(p->options, + "comment=systemd.automount\0" + "x-systemd.automount\0"); +} + +static bool mount_state_active(MountState state) { + return IN_SET(state, + MOUNT_MOUNTING, + MOUNT_MOUNTING_DONE, + MOUNT_REMOUNTING, + MOUNT_UNMOUNTING, + MOUNT_MOUNTING_SIGTERM, + MOUNT_MOUNTING_SIGKILL, + MOUNT_UNMOUNTING_SIGTERM, + MOUNT_UNMOUNTING_SIGKILL, + MOUNT_REMOUNTING_SIGTERM, + MOUNT_REMOUNTING_SIGKILL); +} + +static bool needs_quota(const MountParameters *p) { + assert(p); + + /* Quotas are not enabled on network filesystems, + * but we want them, for example, on storage connected via iscsi */ + if (p->fstype && fstype_is_network(p->fstype)) + return false; + + if (mount_is_bind(p)) + return false; + + return fstab_test_option(p->options, + "usrquota\0" "grpquota\0" "quota\0" "usrjquota\0" "grpjquota\0"); +} + +static void mount_init(Unit *u) { + Mount *m = MOUNT(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + m->timeout_usec = u->manager->default_timeout_start_usec; + m->directory_mode = 0755; + + if (unit_has_name(u, "-.mount")) { + /* Don't allow start/stop for root directory */ + u->refuse_manual_start = true; + u->refuse_manual_stop = true; + } else { + /* The stdio/kmsg bridge socket is on /, in order to avoid a + * dep loop, don't use kmsg logging for -.mount */ + m->exec_context.std_output = u->manager->default_std_output; + m->exec_context.std_error = u->manager->default_std_error; + } + + /* We need to make sure that /usr/bin/mount is always called + * in the same process group as us, so that the autofs kernel + * side doesn't send us another mount request while we are + * already trying to comply its last one. */ + m->exec_context.same_pgrp = true; + + m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; + + u->ignore_on_isolate = true; +} + +static int mount_arm_timer(Mount *m, usec_t usec) { + int r; + + assert(m); + + if (m->timer_event_source) { + r = sd_event_source_set_time(m->timer_event_source, usec); + if (r < 0) + return r; + + return sd_event_source_set_enabled(m->timer_event_source, SD_EVENT_ONESHOT); + } + + if (usec == USEC_INFINITY) + return 0; + + r = sd_event_add_time( + UNIT(m)->manager->event, + &m->timer_event_source, + CLOCK_MONOTONIC, + usec, 0, + mount_dispatch_timer, m); + if (r < 0) + return r; + + (void) sd_event_source_set_description(m->timer_event_source, "mount-timer"); + + return 0; +} + +static void mount_unwatch_control_pid(Mount *m) { + assert(m); + + if (m->control_pid <= 0) + return; + + unit_unwatch_pid(UNIT(m), m->control_pid); + m->control_pid = 0; +} + +static void mount_parameters_done(MountParameters *p) { + assert(p); + + free(p->what); + free(p->options); + free(p->fstype); + + p->what = p->options = p->fstype = NULL; +} + +static void mount_done(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + m->where = mfree(m->where); + + mount_parameters_done(&m->parameters_proc_self_mountinfo); + mount_parameters_done(&m->parameters_fragment); + + m->exec_runtime = exec_runtime_unref(m->exec_runtime); + exec_command_done_array(m->exec_command, _MOUNT_EXEC_COMMAND_MAX); + m->control_command = NULL; + + mount_unwatch_control_pid(m); + + m->timer_event_source = sd_event_source_unref(m->timer_event_source); +} + +_pure_ static MountParameters* get_mount_parameters_fragment(Mount *m) { + assert(m); + + if (m->from_fragment) + return &m->parameters_fragment; + + return NULL; +} + +_pure_ static MountParameters* get_mount_parameters(Mount *m) { + assert(m); + + if (m->from_proc_self_mountinfo) + return &m->parameters_proc_self_mountinfo; + + return get_mount_parameters_fragment(m); +} + +static int mount_add_mount_links(Mount *m) { + _cleanup_free_ char *parent = NULL; + MountParameters *pm; + Unit *other; + Iterator i; + Set *s; + int r; + + assert(m); + + if (!path_equal(m->where, "/")) { + /* Adds in links to other mount points that might lie further + * up in the hierarchy */ + + parent = dirname_malloc(m->where); + if (!parent) + return -ENOMEM; + + r = unit_require_mounts_for(UNIT(m), parent); + if (r < 0) + return r; + } + + /* Adds in links to other mount points that might be needed + * for the source path (if this is a bind mount or a loop mount) to be + * available. */ + pm = get_mount_parameters_fragment(m); + if (pm && pm->what && + path_is_absolute(pm->what) && + (mount_is_bind(pm) || mount_is_loop(pm) || !mount_is_network(pm))) { + + r = unit_require_mounts_for(UNIT(m), pm->what); + if (r < 0) + return r; + } + + /* Adds in links to other units that use this path or paths + * further down in the hierarchy */ + s = manager_get_units_requiring_mounts_for(UNIT(m)->manager, m->where); + SET_FOREACH(other, s, i) { + + if (other->load_state != UNIT_LOADED) + continue; + + if (other == UNIT(m)) + continue; + + r = unit_add_dependency(other, UNIT_AFTER, UNIT(m), true); + if (r < 0) + return r; + + if (UNIT(m)->fragment_path) { + /* If we have fragment configuration, then make this dependency required */ + r = unit_add_dependency(other, UNIT_REQUIRES, UNIT(m), true); + if (r < 0) + return r; + } + } + + return 0; +} + +static int mount_add_device_links(Mount *m) { + MountParameters *p; + bool device_wants_mount = false; + int r; + + assert(m); + + p = get_mount_parameters(m); + if (!p) + return 0; + + if (!p->what) + return 0; + + if (mount_is_bind(p)) + return 0; + + if (!is_device_path(p->what)) + return 0; + + /* /dev/root is a really weird thing, it's not a real device, + * but just a path the kernel exports for the root file system + * specified on the kernel command line. Ignore it here. */ + if (path_equal(p->what, "/dev/root")) + return 0; + + if (path_equal(m->where, "/")) + return 0; + + if (mount_is_auto(p) && !mount_is_automount(p) && MANAGER_IS_SYSTEM(UNIT(m)->manager)) + device_wants_mount = true; + + r = unit_add_node_link(UNIT(m), p->what, device_wants_mount, m->from_fragment ? UNIT_BINDS_TO : UNIT_REQUIRES); + if (r < 0) + return r; + + return 0; +} + +static int mount_add_quota_links(Mount *m) { + int r; + MountParameters *p; + + assert(m); + + if (!MANAGER_IS_SYSTEM(UNIT(m)->manager)) + return 0; + + p = get_mount_parameters_fragment(m); + if (!p) + return 0; + + if (!needs_quota(p)) + return 0; + + r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTACHECK_SERVICE, NULL, true); + if (r < 0) + return r; + + r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTAON_SERVICE, NULL, true); + if (r < 0) + return r; + + return 0; +} + +static bool should_umount(Mount *m) { + MountParameters *p; + + if (PATH_IN_SET(m->where, "/", "/usr") || + path_startswith(m->where, "/run/initramfs")) + return false; + + p = get_mount_parameters(m); + if (p && fstab_test_option(p->options, "x-initrd.mount\0") && + !in_initrd()) + return false; + + return true; +} + +static int mount_add_default_dependencies(Mount *m) { + MountParameters *p; + const char *after; + int r; + + assert(m); + + if (!UNIT(m)->default_dependencies) + return 0; + + if (!MANAGER_IS_SYSTEM(UNIT(m)->manager)) + return 0; + + /* We do not add any default dependencies to /, /usr or + * /run/initramfs/, since they are guaranteed to stay + * mounted the whole time, since our system is on it. + * Also, don't bother with anything mounted below virtual + * file systems, it's also going to be virtual, and hence + * not worth the effort. */ + if (PATH_IN_SET(m->where, "/", "/usr") || + path_startswith(m->where, "/run/initramfs") || + path_startswith(m->where, "/proc") || + path_startswith(m->where, "/sys") || + path_startswith(m->where, "/dev")) + return 0; + + p = get_mount_parameters(m); + if (!p) + return 0; + + if (mount_is_network(p)) { + /* We order ourselves after network.target. This is + * primarily useful at shutdown: services that take + * down the network should order themselves before + * network.target, so that they are shut down only + * after this mount unit is stopped. */ + + r = unit_add_dependency_by_name(UNIT(m), UNIT_AFTER, SPECIAL_NETWORK_TARGET, NULL, true); + if (r < 0) + return r; + + /* We pull in network-online.target, and order + * ourselves after it. This is useful at start-up to + * actively pull in tools that want to be started + * before we start mounting network file systems, and + * whose purpose it is to delay this until the network + * is "up". */ + + r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_WANTS, UNIT_AFTER, SPECIAL_NETWORK_ONLINE_TARGET, NULL, true); + if (r < 0) + return r; + + after = SPECIAL_REMOTE_FS_PRE_TARGET; + } else + after = SPECIAL_LOCAL_FS_PRE_TARGET; + + r = unit_add_dependency_by_name(UNIT(m), UNIT_AFTER, after, NULL, true); + if (r < 0) + return r; + + if (should_umount(m)) { + r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); + if (r < 0) + return r; + } + + return 0; +} + +static int mount_verify(Mount *m) { + _cleanup_free_ char *e = NULL; + int r; + + assert(m); + + if (UNIT(m)->load_state != UNIT_LOADED) + return 0; + + if (!m->from_fragment && !m->from_proc_self_mountinfo) + return -ENOENT; + + r = unit_name_from_path(m->where, ".mount", &e); + if (r < 0) + return log_unit_error_errno(UNIT(m), r, "Failed to generate unit name from mount path: %m"); + + if (!unit_has_name(UNIT(m), e)) { + log_unit_error(UNIT(m), "Where= setting doesn't match unit name. Refusing."); + return -EINVAL; + } + + if (mount_point_is_api(m->where) || mount_point_ignore(m->where)) { + log_unit_error(UNIT(m), "Cannot create mount unit for API file system %s. Refusing.", m->where); + return -EINVAL; + } + + if (UNIT(m)->fragment_path && !m->parameters_fragment.what) { + log_unit_error(UNIT(m), "What= setting is missing. Refusing."); + return -EBADMSG; + } + + if (m->exec_context.pam_name && m->kill_context.kill_mode != KILL_CONTROL_GROUP) { + log_unit_error(UNIT(m), "Unit has PAM enabled. Kill mode must be set to control-group'. Refusing."); + return -EINVAL; + } + + return 0; +} + +static int mount_add_extras(Mount *m) { + Unit *u = UNIT(m); + int r; + + assert(m); + + if (u->fragment_path) + m->from_fragment = true; + + if (!m->where) { + r = unit_name_to_path(u->id, &m->where); + if (r < 0) + return r; + } + + path_kill_slashes(m->where); + + if (!u->description) { + r = unit_set_description(u, m->where); + if (r < 0) + return r; + } + + r = mount_add_device_links(m); + if (r < 0) + return r; + + r = mount_add_mount_links(m); + if (r < 0) + return r; + + r = mount_add_quota_links(m); + if (r < 0) + return r; + + r = unit_patch_contexts(u); + if (r < 0) + return r; + + r = unit_add_exec_dependencies(u, &m->exec_context); + if (r < 0) + return r; + + r = unit_set_default_slice(u); + if (r < 0) + return r; + + r = mount_add_default_dependencies(m); + if (r < 0) + return r; + + return 0; +} + +static int mount_load(Unit *u) { + Mount *m = MOUNT(u); + int r; + + assert(u); + assert(u->load_state == UNIT_STUB); + + if (m->from_proc_self_mountinfo) + r = unit_load_fragment_and_dropin_optional(u); + else + r = unit_load_fragment_and_dropin(u); + + if (r < 0) + return r; + + /* This is a new unit? Then let's add in some extras */ + if (u->load_state == UNIT_LOADED) { + r = mount_add_extras(m); + if (r < 0) + return r; + } + + return mount_verify(m); +} + +static void mount_set_state(Mount *m, MountState state) { + MountState old_state; + assert(m); + + old_state = m->state; + m->state = state; + + if (!mount_state_active(state)) { + m->timer_event_source = sd_event_source_unref(m->timer_event_source); + mount_unwatch_control_pid(m); + m->control_command = NULL; + m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; + } + + if (state != old_state) + log_unit_debug(UNIT(m), "Changed %s -> %s", mount_state_to_string(old_state), mount_state_to_string(state)); + + unit_notify(UNIT(m), state_translation_table[old_state], state_translation_table[state], m->reload_result == MOUNT_SUCCESS); + m->reload_result = MOUNT_SUCCESS; +} + +static int mount_coldplug(Unit *u) { + Mount *m = MOUNT(u); + MountState new_state = MOUNT_DEAD; + int r; + + assert(m); + assert(m->state == MOUNT_DEAD); + + if (m->deserialized_state != m->state) + new_state = m->deserialized_state; + else if (m->from_proc_self_mountinfo) + new_state = MOUNT_MOUNTED; + + if (new_state == m->state) + return 0; + + if (m->control_pid > 0 && + pid_is_unwaited(m->control_pid) && + mount_state_active(new_state)) { + + r = unit_watch_pid(UNIT(m), m->control_pid); + if (r < 0) + return r; + + r = mount_arm_timer(m, usec_add(u->state_change_timestamp.monotonic, m->timeout_usec)); + if (r < 0) + return r; + } + + mount_set_state(m, new_state); + return 0; +} + +static void mount_dump(Unit *u, FILE *f, const char *prefix) { + Mount *m = MOUNT(u); + MountParameters *p; + + assert(m); + assert(f); + + p = get_mount_parameters(m); + + fprintf(f, + "%sMount State: %s\n" + "%sResult: %s\n" + "%sWhere: %s\n" + "%sWhat: %s\n" + "%sFile System Type: %s\n" + "%sOptions: %s\n" + "%sFrom /proc/self/mountinfo: %s\n" + "%sFrom fragment: %s\n" + "%sDirectoryMode: %04o\n", + prefix, mount_state_to_string(m->state), + prefix, mount_result_to_string(m->result), + prefix, m->where, + prefix, p ? strna(p->what) : "n/a", + prefix, p ? strna(p->fstype) : "n/a", + prefix, p ? strna(p->options) : "n/a", + prefix, yes_no(m->from_proc_self_mountinfo), + prefix, yes_no(m->from_fragment), + prefix, m->directory_mode); + + if (m->control_pid > 0) + fprintf(f, + "%sControl PID: "PID_FMT"\n", + prefix, m->control_pid); + + exec_context_dump(&m->exec_context, f, prefix); + kill_context_dump(&m->kill_context, f, prefix); +} + +static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { + pid_t pid; + int r; + ExecParameters exec_params = { + .apply_permissions = true, + .apply_chroot = true, + .apply_tty_stdin = true, + .stdin_fd = -1, + .stdout_fd = -1, + .stderr_fd = -1, + }; + + assert(m); + assert(c); + assert(_pid); + + (void) unit_realize_cgroup(UNIT(m)); + if (m->reset_cpu_usage) { + (void) unit_reset_cpu_usage(UNIT(m)); + m->reset_cpu_usage = false; + } + + r = unit_setup_exec_runtime(UNIT(m)); + if (r < 0) + return r; + + r = mount_arm_timer(m, usec_add(now(CLOCK_MONOTONIC), m->timeout_usec)); + if (r < 0) + return r; + + exec_params.environment = UNIT(m)->manager->environment; + exec_params.confirm_spawn = UNIT(m)->manager->confirm_spawn; + exec_params.cgroup_supported = UNIT(m)->manager->cgroup_supported; + exec_params.cgroup_path = UNIT(m)->cgroup_path; + exec_params.cgroup_delegate = m->cgroup_context.delegate; + exec_params.runtime_prefix = manager_get_runtime_prefix(UNIT(m)->manager); + + r = exec_spawn(UNIT(m), + c, + &m->exec_context, + &exec_params, + m->exec_runtime, + &pid); + if (r < 0) + return r; + + r = unit_watch_pid(UNIT(m), pid); + if (r < 0) + /* FIXME: we need to do something here */ + return r; + + *_pid = pid; + + return 0; +} + +static void mount_enter_dead(Mount *m, MountResult f) { + assert(m); + + if (f != MOUNT_SUCCESS) + m->result = f; + + exec_runtime_destroy(m->exec_runtime); + m->exec_runtime = exec_runtime_unref(m->exec_runtime); + + exec_context_destroy_runtime_directory(&m->exec_context, manager_get_runtime_prefix(UNIT(m)->manager)); + + mount_set_state(m, m->result != MOUNT_SUCCESS ? MOUNT_FAILED : MOUNT_DEAD); +} + +static void mount_enter_mounted(Mount *m, MountResult f) { + assert(m); + + if (f != MOUNT_SUCCESS) + m->result = f; + + mount_set_state(m, MOUNT_MOUNTED); +} + +static void mount_enter_signal(Mount *m, MountState state, MountResult f) { + int r; + + assert(m); + + if (f != MOUNT_SUCCESS) + m->result = f; + + r = unit_kill_context( + UNIT(m), + &m->kill_context, + (state != MOUNT_MOUNTING_SIGTERM && state != MOUNT_UNMOUNTING_SIGTERM && state != MOUNT_REMOUNTING_SIGTERM) ? + KILL_KILL : KILL_TERMINATE, + -1, + m->control_pid, + false); + if (r < 0) + goto fail; + + if (r > 0) { + r = mount_arm_timer(m, usec_add(now(CLOCK_MONOTONIC), m->timeout_usec)); + if (r < 0) + goto fail; + + mount_set_state(m, state); + } else if (state == MOUNT_REMOUNTING_SIGTERM) + mount_enter_signal(m, MOUNT_REMOUNTING_SIGKILL, MOUNT_SUCCESS); + else if (state == MOUNT_REMOUNTING_SIGKILL) + mount_enter_mounted(m, MOUNT_SUCCESS); + else if (state == MOUNT_MOUNTING_SIGTERM) + mount_enter_signal(m, MOUNT_MOUNTING_SIGKILL, MOUNT_SUCCESS); + else if (state == MOUNT_UNMOUNTING_SIGTERM) + mount_enter_signal(m, MOUNT_UNMOUNTING_SIGKILL, MOUNT_SUCCESS); + else + mount_enter_dead(m, MOUNT_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(m), r, "Failed to kill processes: %m"); + + if (state == MOUNT_REMOUNTING_SIGTERM || state == MOUNT_REMOUNTING_SIGKILL) + mount_enter_mounted(m, MOUNT_FAILURE_RESOURCES); + else + mount_enter_dead(m, MOUNT_FAILURE_RESOURCES); +} + +static void mount_enter_unmounting(Mount *m) { + int r; + + assert(m); + + /* Start counting our attempts */ + if (!IN_SET(m->state, + MOUNT_UNMOUNTING, + MOUNT_UNMOUNTING_SIGTERM, + MOUNT_UNMOUNTING_SIGKILL)) + m->n_retry_umount = 0; + + m->control_command_id = MOUNT_EXEC_UNMOUNT; + m->control_command = m->exec_command + MOUNT_EXEC_UNMOUNT; + + r = exec_command_set(m->control_command, UMOUNT_PATH, m->where, NULL); + if (r < 0) + goto fail; + + mount_unwatch_control_pid(m); + + r = mount_spawn(m, m->control_command, &m->control_pid); + if (r < 0) + goto fail; + + mount_set_state(m, MOUNT_UNMOUNTING); + + return; + +fail: + log_unit_warning_errno(UNIT(m), r, "Failed to run 'umount' task: %m"); + mount_enter_mounted(m, MOUNT_FAILURE_RESOURCES); +} + +static int mount_get_opts(Mount *m, char **ret) { + return fstab_filter_options(m->parameters_fragment.options, + "nofail\0" "noauto\0" "auto\0", NULL, NULL, ret); +} + +static void mount_enter_mounting(Mount *m) { + int r; + MountParameters *p; + + assert(m); + + m->control_command_id = MOUNT_EXEC_MOUNT; + m->control_command = m->exec_command + MOUNT_EXEC_MOUNT; + + r = unit_fail_if_symlink(UNIT(m), m->where); + if (r < 0) + goto fail; + + (void) mkdir_p_label(m->where, m->directory_mode); + + unit_warn_if_dir_nonempty(UNIT(m), m->where); + + /* Create the source directory for bind-mounts if needed */ + p = get_mount_parameters_fragment(m); + if (p && mount_is_bind(p)) + (void) mkdir_p_label(p->what, m->directory_mode); + + if (m->from_fragment) { + _cleanup_free_ char *opts = NULL; + + r = mount_get_opts(m, &opts); + if (r < 0) + goto fail; + + r = exec_command_set(m->control_command, MOUNT_PATH, + m->parameters_fragment.what, m->where, NULL); + if (r >= 0 && m->sloppy_options) + r = exec_command_append(m->control_command, "-s", NULL); + if (r >= 0 && m->parameters_fragment.fstype) + r = exec_command_append(m->control_command, "-t", m->parameters_fragment.fstype, NULL); + if (r >= 0 && !isempty(opts)) + r = exec_command_append(m->control_command, "-o", opts, NULL); + } else + r = -ENOENT; + + if (r < 0) + goto fail; + + mount_unwatch_control_pid(m); + + r = mount_spawn(m, m->control_command, &m->control_pid); + if (r < 0) + goto fail; + + mount_set_state(m, MOUNT_MOUNTING); + + return; + +fail: + log_unit_warning_errno(UNIT(m), r, "Failed to run 'mount' task: %m"); + mount_enter_dead(m, MOUNT_FAILURE_RESOURCES); +} + +static void mount_enter_remounting(Mount *m) { + int r; + + assert(m); + + m->control_command_id = MOUNT_EXEC_REMOUNT; + m->control_command = m->exec_command + MOUNT_EXEC_REMOUNT; + + if (m->from_fragment) { + const char *o; + + if (m->parameters_fragment.options) + o = strjoina("remount,", m->parameters_fragment.options); + else + o = "remount"; + + r = exec_command_set(m->control_command, MOUNT_PATH, + m->parameters_fragment.what, m->where, + "-o", o, NULL); + if (r >= 0 && m->sloppy_options) + r = exec_command_append(m->control_command, "-s", NULL); + if (r >= 0 && m->parameters_fragment.fstype) + r = exec_command_append(m->control_command, "-t", m->parameters_fragment.fstype, NULL); + } else + r = -ENOENT; + + if (r < 0) + goto fail; + + mount_unwatch_control_pid(m); + + r = mount_spawn(m, m->control_command, &m->control_pid); + if (r < 0) + goto fail; + + mount_set_state(m, MOUNT_REMOUNTING); + + return; + +fail: + log_unit_warning_errno(UNIT(m), r, "Failed to run 'remount' task: %m"); + m->reload_result = MOUNT_FAILURE_RESOURCES; + mount_enter_mounted(m, MOUNT_SUCCESS); +} + +static int mount_start(Unit *u) { + Mount *m = MOUNT(u); + int r; + + assert(m); + + /* We cannot fulfill this request right now, try again later + * please! */ + if (m->state == MOUNT_UNMOUNTING || + m->state == MOUNT_UNMOUNTING_SIGTERM || + m->state == MOUNT_UNMOUNTING_SIGKILL || + m->state == MOUNT_MOUNTING_SIGTERM || + m->state == MOUNT_MOUNTING_SIGKILL) + return -EAGAIN; + + /* Already on it! */ + if (m->state == MOUNT_MOUNTING) + return 0; + + assert(m->state == MOUNT_DEAD || m->state == MOUNT_FAILED); + + r = unit_start_limit_test(u); + if (r < 0) { + mount_enter_dead(m, MOUNT_FAILURE_START_LIMIT_HIT); + return r; + } + + m->result = MOUNT_SUCCESS; + m->reload_result = MOUNT_SUCCESS; + m->reset_cpu_usage = true; + + mount_enter_mounting(m); + return 1; +} + +static int mount_stop(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + /* Already on it */ + if (m->state == MOUNT_UNMOUNTING || + m->state == MOUNT_UNMOUNTING_SIGKILL || + m->state == MOUNT_UNMOUNTING_SIGTERM || + m->state == MOUNT_MOUNTING_SIGTERM || + m->state == MOUNT_MOUNTING_SIGKILL) + return 0; + + assert(m->state == MOUNT_MOUNTING || + m->state == MOUNT_MOUNTING_DONE || + m->state == MOUNT_MOUNTED || + m->state == MOUNT_REMOUNTING || + m->state == MOUNT_REMOUNTING_SIGTERM || + m->state == MOUNT_REMOUNTING_SIGKILL); + + mount_enter_unmounting(m); + return 1; +} + +static int mount_reload(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + if (m->state == MOUNT_MOUNTING_DONE) + return -EAGAIN; + + assert(m->state == MOUNT_MOUNTED); + + mount_enter_remounting(m); + return 1; +} + +static int mount_serialize(Unit *u, FILE *f, FDSet *fds) { + Mount *m = MOUNT(u); + + assert(m); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", mount_state_to_string(m->state)); + unit_serialize_item(u, f, "result", mount_result_to_string(m->result)); + unit_serialize_item(u, f, "reload-result", mount_result_to_string(m->reload_result)); + + if (m->control_pid > 0) + unit_serialize_item_format(u, f, "control-pid", PID_FMT, m->control_pid); + + if (m->control_command_id >= 0) + unit_serialize_item(u, f, "control-command", mount_exec_command_to_string(m->control_command_id)); + + return 0; +} + +static int mount_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Mount *m = MOUNT(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + MountState state; + + if ((state = mount_state_from_string(value)) < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + m->deserialized_state = state; + } else if (streq(key, "result")) { + MountResult f; + + f = mount_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse result value: %s", value); + else if (f != MOUNT_SUCCESS) + m->result = f; + + } else if (streq(key, "reload-result")) { + MountResult f; + + f = mount_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse reload result value: %s", value); + else if (f != MOUNT_SUCCESS) + m->reload_result = f; + + } else if (streq(key, "control-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_unit_debug(u, "Failed to parse control-pid value: %s", value); + else + m->control_pid = pid; + } else if (streq(key, "control-command")) { + MountExecCommand id; + + id = mount_exec_command_from_string(value); + if (id < 0) + log_unit_debug(u, "Failed to parse exec-command value: %s", value); + else { + m->control_command_id = id; + m->control_command = m->exec_command + id; + } + } else + log_unit_debug(u, "Unknown serialization key: %s", key); + + return 0; +} + +_pure_ static UnitActiveState mount_active_state(Unit *u) { + assert(u); + + return state_translation_table[MOUNT(u)->state]; +} + +_pure_ static const char *mount_sub_state_to_string(Unit *u) { + assert(u); + + return mount_state_to_string(MOUNT(u)->state); +} + +_pure_ static bool mount_check_gc(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + return m->from_proc_self_mountinfo; +} + +static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { + Mount *m = MOUNT(u); + MountResult f; + + assert(m); + assert(pid >= 0); + + if (pid != m->control_pid) + return; + + m->control_pid = 0; + + if (is_clean_exit(code, status, NULL)) + f = MOUNT_SUCCESS; + else if (code == CLD_EXITED) + f = MOUNT_FAILURE_EXIT_CODE; + else if (code == CLD_KILLED) + f = MOUNT_FAILURE_SIGNAL; + else if (code == CLD_DUMPED) + f = MOUNT_FAILURE_CORE_DUMP; + else + assert_not_reached("Unknown code"); + + if (f != MOUNT_SUCCESS) + m->result = f; + + if (m->control_command) { + exec_status_exit(&m->control_command->exec_status, &m->exec_context, pid, code, status); + + m->control_command = NULL; + m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; + } + + log_unit_full(u, f == MOUNT_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, + "Mount process exited, code=%s status=%i", sigchld_code_to_string(code), status); + + /* Note that mount(8) returning and the kernel sending us a + * mount table change event might happen out-of-order. If an + * operation succeed we assume the kernel will follow soon too + * and already change into the resulting state. If it fails + * we check if the kernel still knows about the mount. and + * change state accordingly. */ + + switch (m->state) { + + case MOUNT_MOUNTING: + case MOUNT_MOUNTING_DONE: + case MOUNT_MOUNTING_SIGKILL: + case MOUNT_MOUNTING_SIGTERM: + + if (f == MOUNT_SUCCESS) + mount_enter_mounted(m, f); + else if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, f); + else + mount_enter_dead(m, f); + break; + + case MOUNT_REMOUNTING: + case MOUNT_REMOUNTING_SIGKILL: + case MOUNT_REMOUNTING_SIGTERM: + + m->reload_result = f; + if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, MOUNT_SUCCESS); + else + mount_enter_dead(m, MOUNT_SUCCESS); + + break; + + case MOUNT_UNMOUNTING: + case MOUNT_UNMOUNTING_SIGKILL: + case MOUNT_UNMOUNTING_SIGTERM: + + if (f == MOUNT_SUCCESS) { + + if (m->from_proc_self_mountinfo) { + + /* Still a mount point? If so, let's + * try again. Most likely there were + * multiple mount points stacked on + * top of each other. Note that due to + * the io event priority logic we can + * be sure the new mountinfo is loaded + * before we process the SIGCHLD for + * the mount command. */ + + if (m->n_retry_umount < RETRY_UMOUNT_MAX) { + log_unit_debug(u, "Mount still present, trying again."); + m->n_retry_umount++; + mount_enter_unmounting(m); + } else { + log_unit_debug(u, "Mount still present after %u attempts to unmount, giving up.", m->n_retry_umount); + mount_enter_mounted(m, f); + } + } else + mount_enter_dead(m, f); + + } else if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, f); + else + mount_enter_dead(m, f); + break; + + default: + assert_not_reached("Uh, control process died at wrong time."); + } + + /* Notify clients about changed exit status */ + unit_add_to_dbus_queue(u); +} + +static int mount_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { + Mount *m = MOUNT(userdata); + + assert(m); + assert(m->timer_event_source == source); + + switch (m->state) { + + case MOUNT_MOUNTING: + case MOUNT_MOUNTING_DONE: + log_unit_warning(UNIT(m), "Mounting timed out. Stopping."); + mount_enter_signal(m, MOUNT_MOUNTING_SIGTERM, MOUNT_FAILURE_TIMEOUT); + break; + + case MOUNT_REMOUNTING: + log_unit_warning(UNIT(m), "Remounting timed out. Stopping."); + m->reload_result = MOUNT_FAILURE_TIMEOUT; + mount_enter_mounted(m, MOUNT_SUCCESS); + break; + + case MOUNT_UNMOUNTING: + log_unit_warning(UNIT(m), "Unmounting timed out. Stopping."); + mount_enter_signal(m, MOUNT_UNMOUNTING_SIGTERM, MOUNT_FAILURE_TIMEOUT); + break; + + case MOUNT_MOUNTING_SIGTERM: + if (m->kill_context.send_sigkill) { + log_unit_warning(UNIT(m), "Mounting timed out. Killing."); + mount_enter_signal(m, MOUNT_MOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(m), "Mounting timed out. Skipping SIGKILL. Ignoring."); + + if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); + else + mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); + } + break; + + case MOUNT_REMOUNTING_SIGTERM: + if (m->kill_context.send_sigkill) { + log_unit_warning(UNIT(m), "Remounting timed out. Killing."); + mount_enter_signal(m, MOUNT_REMOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(m), "Remounting timed out. Skipping SIGKILL. Ignoring."); + + if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); + else + mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); + } + break; + + case MOUNT_UNMOUNTING_SIGTERM: + if (m->kill_context.send_sigkill) { + log_unit_warning(UNIT(m), "Unmounting timed out. Killing."); + mount_enter_signal(m, MOUNT_UNMOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(m), "Unmounting timed out. Skipping SIGKILL. Ignoring."); + + if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); + else + mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); + } + break; + + case MOUNT_MOUNTING_SIGKILL: + case MOUNT_REMOUNTING_SIGKILL: + case MOUNT_UNMOUNTING_SIGKILL: + log_unit_warning(UNIT(m),"Mount process still around after SIGKILL. Ignoring."); + + if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); + else + mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } + + return 0; +} + +static int mount_setup_unit( + Manager *m, + const char *what, + const char *where, + const char *options, + const char *fstype, + bool set_flags) { + + _cleanup_free_ char *e = NULL, *w = NULL, *o = NULL, *f = NULL; + bool load_extras = false; + MountParameters *p; + bool delete, changed = false; + Unit *u; + int r; + + assert(m); + assert(what); + assert(where); + assert(options); + assert(fstype); + + /* Ignore API mount points. They should never be referenced in + * dependencies ever. */ + if (mount_point_is_api(where) || mount_point_ignore(where)) + return 0; + + if (streq(fstype, "autofs")) + return 0; + + /* probably some kind of swap, ignore */ + if (!is_path(where)) + return 0; + + r = unit_name_from_path(where, ".mount", &e); + if (r < 0) + return r; + + u = manager_get_unit(m, e); + if (!u) { + delete = true; + + u = unit_new(m, sizeof(Mount)); + if (!u) + return log_oom(); + + r = unit_add_name(u, e); + if (r < 0) + goto fail; + + MOUNT(u)->where = strdup(where); + if (!MOUNT(u)->where) { + r = -ENOMEM; + goto fail; + } + + u->source_path = strdup("/proc/self/mountinfo"); + if (!u->source_path) { + r = -ENOMEM; + goto fail; + } + + if (MANAGER_IS_SYSTEM(m)) { + const char* target; + + target = mount_needs_network(options, fstype) ? SPECIAL_REMOTE_FS_TARGET : SPECIAL_LOCAL_FS_TARGET; + r = unit_add_dependency_by_name(u, UNIT_BEFORE, target, NULL, true); + if (r < 0) + goto fail; + + if (should_umount(MOUNT(u))) { + r = unit_add_dependency_by_name(u, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); + if (r < 0) + goto fail; + } + } + + unit_add_to_load_queue(u); + changed = true; + } else { + delete = false; + + if (!MOUNT(u)->where) { + MOUNT(u)->where = strdup(where); + if (!MOUNT(u)->where) { + r = -ENOMEM; + goto fail; + } + } + + if (MANAGER_IS_SYSTEM(m) && + mount_needs_network(options, fstype)) { + /* _netdev option may have shown up late, or on a + * remount. Add remote-fs dependencies, even though + * local-fs ones may already be there. */ + unit_add_dependency_by_name(u, UNIT_BEFORE, SPECIAL_REMOTE_FS_TARGET, NULL, true); + load_extras = true; + } + + if (u->load_state == UNIT_NOT_FOUND) { + u->load_state = UNIT_LOADED; + u->load_error = 0; + + /* Load in the extras later on, after we + * finished initialization of the unit */ + load_extras = true; + changed = true; + } + } + + w = strdup(what); + o = strdup(options); + f = strdup(fstype); + if (!w || !o || !f) { + r = -ENOMEM; + goto fail; + } + + p = &MOUNT(u)->parameters_proc_self_mountinfo; + + changed = changed || + !streq_ptr(p->options, options) || + !streq_ptr(p->what, what) || + !streq_ptr(p->fstype, fstype); + + if (set_flags) { + MOUNT(u)->is_mounted = true; + MOUNT(u)->just_mounted = !MOUNT(u)->from_proc_self_mountinfo; + MOUNT(u)->just_changed = changed; + } + + MOUNT(u)->from_proc_self_mountinfo = true; + + free(p->what); + p->what = w; + w = NULL; + + free(p->options); + p->options = o; + o = NULL; + + free(p->fstype); + p->fstype = f; + f = NULL; + + if (load_extras) { + r = mount_add_extras(MOUNT(u)); + if (r < 0) + goto fail; + } + + if (changed) + unit_add_to_dbus_queue(u); + + return 0; + +fail: + log_warning_errno(r, "Failed to set up mount unit: %m"); + + if (delete && u) + unit_free(u); + + return r; +} + +static int mount_load_proc_self_mountinfo(Manager *m, bool set_flags) { + _cleanup_(mnt_free_tablep) struct libmnt_table *t = NULL; + _cleanup_(mnt_free_iterp) struct libmnt_iter *i = NULL; + int r = 0; + + assert(m); + + t = mnt_new_table(); + if (!t) + return log_oom(); + + i = mnt_new_iter(MNT_ITER_FORWARD); + if (!i) + return log_oom(); + + r = mnt_table_parse_mtab(t, NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse /proc/self/mountinfo: %m"); + + r = 0; + for (;;) { + const char *device, *path, *options, *fstype; + _cleanup_free_ char *d = NULL, *p = NULL; + struct libmnt_fs *fs; + int k; + + k = mnt_table_next_fs(t, i, &fs); + if (k == 1) + break; + if (k < 0) + return log_error_errno(k, "Failed to get next entry from /proc/self/mountinfo: %m"); + + device = mnt_fs_get_source(fs); + path = mnt_fs_get_target(fs); + options = mnt_fs_get_options(fs); + fstype = mnt_fs_get_fstype(fs); + + if (!device || !path) + continue; + + if (cunescape(device, UNESCAPE_RELAX, &d) < 0) + return log_oom(); + + if (cunescape(path, UNESCAPE_RELAX, &p) < 0) + return log_oom(); + + (void) device_found_node(m, d, true, DEVICE_FOUND_MOUNT, set_flags); + + k = mount_setup_unit(m, d, p, options, fstype, set_flags); + if (r == 0 && k < 0) + r = k; + } + + return r; +} + +static void mount_shutdown(Manager *m) { + + assert(m); + + m->mount_event_source = sd_event_source_unref(m->mount_event_source); + + mnt_unref_monitor(m->mount_monitor); + m->mount_monitor = NULL; +} + +static int mount_get_timeout(Unit *u, usec_t *timeout) { + Mount *m = MOUNT(u); + usec_t t; + int r; + + if (!m->timer_event_source) + return 0; + + r = sd_event_source_get_time(m->timer_event_source, &t); + if (r < 0) + return r; + if (t == USEC_INFINITY) + return 0; + + *timeout = t; + return 1; +} + +static void mount_enumerate(Manager *m) { + int r; + + assert(m); + + mnt_init_debug(0); + + if (!m->mount_monitor) { + int fd; + + m->mount_monitor = mnt_new_monitor(); + if (!m->mount_monitor) { + log_oom(); + goto fail; + } + + r = mnt_monitor_enable_kernel(m->mount_monitor, 1); + if (r < 0) { + log_error_errno(r, "Failed to enable watching of kernel mount events: %m"); + goto fail; + } + + r = mnt_monitor_enable_userspace(m->mount_monitor, 1, NULL); + if (r < 0) { + log_error_errno(r, "Failed to enable watching of userspace mount events: %m"); + goto fail; + } + + /* mnt_unref_monitor() will close the fd */ + fd = r = mnt_monitor_get_fd(m->mount_monitor); + if (r < 0) { + log_error_errno(r, "Failed to acquire watch file descriptor: %m"); + goto fail; + } + + r = sd_event_add_io(m->event, &m->mount_event_source, fd, EPOLLIN, mount_dispatch_io, m); + if (r < 0) { + log_error_errno(r, "Failed to watch mount file descriptor: %m"); + goto fail; + } + + r = sd_event_source_set_priority(m->mount_event_source, -10); + if (r < 0) { + log_error_errno(r, "Failed to adjust mount watch priority: %m"); + goto fail; + } + + (void) sd_event_source_set_description(m->mount_event_source, "mount-monitor-dispatch"); + } + + r = mount_load_proc_self_mountinfo(m, false); + if (r < 0) + goto fail; + + return; + +fail: + mount_shutdown(m); +} + +static int mount_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + _cleanup_set_free_ Set *around = NULL, *gone = NULL; + Manager *m = userdata; + const char *what; + Iterator i; + Unit *u; + int r; + + assert(m); + assert(revents & EPOLLIN); + + if (fd == mnt_monitor_get_fd(m->mount_monitor)) { + bool rescan = false; + + /* Drain all events and verify that the event is valid. + * + * Note that libmount also monitors /run/mount mkdir if the + * directory does not exist yet. The mkdir may generate event + * which is irrelevant for us. + * + * error: r < 0; valid: r == 0, false positive: rc == 1 */ + do { + r = mnt_monitor_next_change(m->mount_monitor, NULL, NULL); + if (r == 0) + rescan = true; + else if (r < 0) + return log_error_errno(r, "Failed to drain libmount events"); + } while (r == 0); + + log_debug("libmount event [rescan: %s]", yes_no(rescan)); + if (!rescan) + return 0; + } + + r = mount_load_proc_self_mountinfo(m, true); + if (r < 0) { + /* Reset flags, just in case, for later calls */ + LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_MOUNT]) { + Mount *mount = MOUNT(u); + + mount->is_mounted = mount->just_mounted = mount->just_changed = false; + } + + return 0; + } + + manager_dispatch_load_queue(m); + + LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_MOUNT]) { + Mount *mount = MOUNT(u); + + if (!mount->is_mounted) { + + /* A mount point is not around right now. It + * might be gone, or might never have + * existed. */ + + if (mount->from_proc_self_mountinfo && + mount->parameters_proc_self_mountinfo.what) { + + /* Remember that this device might just have disappeared */ + if (set_ensure_allocated(&gone, &string_hash_ops) < 0 || + set_put(gone, mount->parameters_proc_self_mountinfo.what) < 0) + log_oom(); /* we don't care too much about OOM here... */ + } + + mount->from_proc_self_mountinfo = false; + + switch (mount->state) { + + case MOUNT_MOUNTED: + /* This has just been unmounted by + * somebody else, follow the state + * change. */ + mount_enter_dead(mount, MOUNT_SUCCESS); + break; + + default: + break; + } + + } else if (mount->just_mounted || mount->just_changed) { + + /* A mount point was added or changed */ + + switch (mount->state) { + + case MOUNT_DEAD: + case MOUNT_FAILED: + /* This has just been mounted by + * somebody else, follow the state + * change. */ + mount_enter_mounted(mount, MOUNT_SUCCESS); + break; + + case MOUNT_MOUNTING: + mount_set_state(mount, MOUNT_MOUNTING_DONE); + break; + + default: + /* Nothing really changed, but let's + * issue an notification call + * nonetheless, in case somebody is + * waiting for this. (e.g. file system + * ro/rw remounts.) */ + mount_set_state(mount, mount->state); + break; + } + } + + if (mount->is_mounted && + mount->from_proc_self_mountinfo && + mount->parameters_proc_self_mountinfo.what) { + + if (set_ensure_allocated(&around, &string_hash_ops) < 0 || + set_put(around, mount->parameters_proc_self_mountinfo.what) < 0) + log_oom(); + } + + /* Reset the flags for later calls */ + mount->is_mounted = mount->just_mounted = mount->just_changed = false; + } + + SET_FOREACH(what, gone, i) { + if (set_contains(around, what)) + continue; + + /* Let the device units know that the device is no longer mounted */ + (void) device_found_node(m, what, false, DEVICE_FOUND_MOUNT, true); + } + + return 0; +} + +static void mount_reset_failed(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + if (m->state == MOUNT_FAILED) + mount_set_state(m, MOUNT_DEAD); + + m->result = MOUNT_SUCCESS; + m->reload_result = MOUNT_SUCCESS; +} + +static int mount_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { + return unit_kill_common(u, who, signo, -1, MOUNT(u)->control_pid, error); +} + +static int mount_control_pid(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + return m->control_pid; +} + +static const char* const mount_exec_command_table[_MOUNT_EXEC_COMMAND_MAX] = { + [MOUNT_EXEC_MOUNT] = "ExecMount", + [MOUNT_EXEC_UNMOUNT] = "ExecUnmount", + [MOUNT_EXEC_REMOUNT] = "ExecRemount", +}; + +DEFINE_STRING_TABLE_LOOKUP(mount_exec_command, MountExecCommand); + +static const char* const mount_result_table[_MOUNT_RESULT_MAX] = { + [MOUNT_SUCCESS] = "success", + [MOUNT_FAILURE_RESOURCES] = "resources", + [MOUNT_FAILURE_TIMEOUT] = "timeout", + [MOUNT_FAILURE_EXIT_CODE] = "exit-code", + [MOUNT_FAILURE_SIGNAL] = "signal", + [MOUNT_FAILURE_CORE_DUMP] = "core-dump", + [MOUNT_FAILURE_START_LIMIT_HIT] = "start-limit-hit", +}; + +DEFINE_STRING_TABLE_LOOKUP(mount_result, MountResult); + +const UnitVTable mount_vtable = { + .object_size = sizeof(Mount), + .exec_context_offset = offsetof(Mount, exec_context), + .cgroup_context_offset = offsetof(Mount, cgroup_context), + .kill_context_offset = offsetof(Mount, kill_context), + .exec_runtime_offset = offsetof(Mount, exec_runtime), + + .sections = + "Unit\0" + "Mount\0" + "Install\0", + .private_section = "Mount", + + .init = mount_init, + .load = mount_load, + .done = mount_done, + + .coldplug = mount_coldplug, + + .dump = mount_dump, + + .start = mount_start, + .stop = mount_stop, + .reload = mount_reload, + + .kill = mount_kill, + + .serialize = mount_serialize, + .deserialize_item = mount_deserialize_item, + + .active_state = mount_active_state, + .sub_state_to_string = mount_sub_state_to_string, + + .check_gc = mount_check_gc, + + .sigchld_event = mount_sigchld_event, + + .reset_failed = mount_reset_failed, + + .control_pid = mount_control_pid, + + .bus_vtable = bus_mount_vtable, + .bus_set_property = bus_mount_set_property, + .bus_commit_properties = bus_mount_commit_properties, + + .get_timeout = mount_get_timeout, + + .can_transient = true, + + .enumerate = mount_enumerate, + .shutdown = mount_shutdown, + + .status_message_formats = { + .starting_stopping = { + [0] = "Mounting %s...", + [1] = "Unmounting %s...", + }, + .finished_start_job = { + [JOB_DONE] = "Mounted %s.", + [JOB_FAILED] = "Failed to mount %s.", + [JOB_TIMEOUT] = "Timed out mounting %s.", + }, + .finished_stop_job = { + [JOB_DONE] = "Unmounted %s.", + [JOB_FAILED] = "Failed unmounting %s.", + [JOB_TIMEOUT] = "Timed out unmounting %s.", + }, + }, +}; diff --git a/src/libcore/mount.h b/src/libcore/mount.h new file mode 100644 index 0000000000..da529c44f4 --- /dev/null +++ b/src/libcore/mount.h @@ -0,0 +1,108 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct Mount Mount; + +#include "execute.h" +#include "kill.h" + +typedef enum MountExecCommand { + MOUNT_EXEC_MOUNT, + MOUNT_EXEC_UNMOUNT, + MOUNT_EXEC_REMOUNT, + _MOUNT_EXEC_COMMAND_MAX, + _MOUNT_EXEC_COMMAND_INVALID = -1 +} MountExecCommand; + +typedef enum MountResult { + MOUNT_SUCCESS, + MOUNT_FAILURE_RESOURCES, + MOUNT_FAILURE_TIMEOUT, + MOUNT_FAILURE_EXIT_CODE, + MOUNT_FAILURE_SIGNAL, + MOUNT_FAILURE_CORE_DUMP, + MOUNT_FAILURE_START_LIMIT_HIT, + _MOUNT_RESULT_MAX, + _MOUNT_RESULT_INVALID = -1 +} MountResult; + +typedef struct MountParameters { + char *what; + char *options; + char *fstype; +} MountParameters; + +struct Mount { + Unit meta; + + char *where; + + MountParameters parameters_proc_self_mountinfo; + MountParameters parameters_fragment; + + bool from_proc_self_mountinfo:1; + bool from_fragment:1; + + /* Used while looking for mount points that vanished or got + * added from/to /proc/self/mountinfo */ + bool is_mounted:1; + bool just_mounted:1; + bool just_changed:1; + + bool reset_cpu_usage:1; + + bool sloppy_options; + + MountResult result; + MountResult reload_result; + + mode_t directory_mode; + + usec_t timeout_usec; + + ExecCommand exec_command[_MOUNT_EXEC_COMMAND_MAX]; + + ExecContext exec_context; + KillContext kill_context; + CGroupContext cgroup_context; + + ExecRuntime *exec_runtime; + + MountState state, deserialized_state; + + ExecCommand* control_command; + MountExecCommand control_command_id; + pid_t control_pid; + + sd_event_source *timer_event_source; + + unsigned n_retry_umount; +}; + +extern const UnitVTable mount_vtable; + +void mount_fd_event(Manager *m, int events); + +const char* mount_exec_command_to_string(MountExecCommand i) _const_; +MountExecCommand mount_exec_command_from_string(const char *s) _pure_; + +const char* mount_result_to_string(MountResult i) _const_; +MountResult mount_result_from_string(const char *s) _pure_; diff --git a/src/libcore/namespace.c b/src/libcore/namespace.c new file mode 100644 index 0000000000..203d122810 --- /dev/null +++ b/src/libcore/namespace.c @@ -0,0 +1,650 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "dev-setup.h" +#include "fd-util.h" +#include "loopback-setup.h" +#include "missing.h" +#include "mkdir.h" +#include "mount-util.h" +#include "namespace.h" +#include "path-util.h" +#include "selinux-util.h" +#include "socket-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "umask-util.h" +#include "user-util.h" +#include "util.h" + +#define DEV_MOUNT_OPTIONS (MS_NOSUID|MS_STRICTATIME|MS_NOEXEC) + +typedef enum MountMode { + /* This is ordered by priority! */ + INACCESSIBLE, + READONLY, + PRIVATE_TMP, + PRIVATE_VAR_TMP, + PRIVATE_DEV, + READWRITE +} MountMode; + +typedef struct BindMount { + const char *path; + MountMode mode; + bool done; + bool ignore; +} BindMount; + +static int append_mounts(BindMount **p, char **strv, MountMode mode) { + char **i; + + assert(p); + + STRV_FOREACH(i, strv) { + + (*p)->ignore = false; + (*p)->done = false; + + if ((mode == INACCESSIBLE || mode == READONLY || mode == READWRITE) && (*i)[0] == '-') { + (*p)->ignore = true; + (*i)++; + } + + if (!path_is_absolute(*i)) + return -EINVAL; + + (*p)->path = *i; + (*p)->mode = mode; + (*p)++; + } + + return 0; +} + +static int mount_path_compare(const void *a, const void *b) { + const BindMount *p = a, *q = b; + int d; + + d = path_compare(p->path, q->path); + + if (d == 0) { + /* If the paths are equal, check the mode */ + if (p->mode < q->mode) + return -1; + + if (p->mode > q->mode) + return 1; + + return 0; + } + + /* If the paths are not equal, then order prefixes first */ + return d; +} + +static void drop_duplicates(BindMount *m, unsigned *n) { + BindMount *f, *t, *previous; + + assert(m); + assert(n); + + for (f = m, t = m, previous = NULL; f < m+*n; f++) { + + /* The first one wins */ + if (previous && path_equal(f->path, previous->path)) + continue; + + *t = *f; + + previous = t; + + t++; + } + + *n = t - m; +} + +static int mount_dev(BindMount *m) { + static const char devnodes[] = + "/dev/null\0" + "/dev/zero\0" + "/dev/full\0" + "/dev/random\0" + "/dev/urandom\0" + "/dev/tty\0"; + + char temporary_mount[] = "/tmp/namespace-dev-XXXXXX"; + const char *d, *dev = NULL, *devpts = NULL, *devshm = NULL, *devhugepages = NULL, *devmqueue = NULL, *devlog = NULL, *devptmx = NULL; + _cleanup_umask_ mode_t u; + int r; + + assert(m); + + u = umask(0000); + + if (!mkdtemp(temporary_mount)) + return -errno; + + dev = strjoina(temporary_mount, "/dev"); + (void) mkdir(dev, 0755); + if (mount("tmpfs", dev, "tmpfs", DEV_MOUNT_OPTIONS, "mode=755") < 0) { + r = -errno; + goto fail; + } + + devpts = strjoina(temporary_mount, "/dev/pts"); + (void) mkdir(devpts, 0755); + if (mount("/dev/pts", devpts, NULL, MS_BIND, NULL) < 0) { + r = -errno; + goto fail; + } + + devptmx = strjoina(temporary_mount, "/dev/ptmx"); + if (symlink("pts/ptmx", devptmx) < 0) { + r = -errno; + goto fail; + } + + devshm = strjoina(temporary_mount, "/dev/shm"); + (void) mkdir(devshm, 01777); + r = mount("/dev/shm", devshm, NULL, MS_BIND, NULL); + if (r < 0) { + r = -errno; + goto fail; + } + + devmqueue = strjoina(temporary_mount, "/dev/mqueue"); + (void) mkdir(devmqueue, 0755); + (void) mount("/dev/mqueue", devmqueue, NULL, MS_BIND, NULL); + + devhugepages = strjoina(temporary_mount, "/dev/hugepages"); + (void) mkdir(devhugepages, 0755); + (void) mount("/dev/hugepages", devhugepages, NULL, MS_BIND, NULL); + + devlog = strjoina(temporary_mount, "/dev/log"); + (void) symlink("/run/systemd/journal/dev-log", devlog); + + NULSTR_FOREACH(d, devnodes) { + _cleanup_free_ char *dn = NULL; + struct stat st; + + r = stat(d, &st); + if (r < 0) { + + if (errno == ENOENT) + continue; + + r = -errno; + goto fail; + } + + if (!S_ISBLK(st.st_mode) && + !S_ISCHR(st.st_mode)) { + r = -EINVAL; + goto fail; + } + + if (st.st_rdev == 0) + continue; + + dn = strappend(temporary_mount, d); + if (!dn) { + r = -ENOMEM; + goto fail; + } + + mac_selinux_create_file_prepare(d, st.st_mode); + r = mknod(dn, st.st_mode, st.st_rdev); + mac_selinux_create_file_clear(); + + if (r < 0) { + r = -errno; + goto fail; + } + } + + dev_setup(temporary_mount, UID_INVALID, GID_INVALID); + + /* Create the /dev directory if missing. It is more likely to be + * missing when the service is started with RootDirectory. This is + * consistent with mount units creating the mount points when missing. + */ + (void) mkdir_p_label(m->path, 0755); + + /* Unmount everything in old /dev */ + umount_recursive(m->path, 0); + if (mount(dev, m->path, NULL, MS_MOVE, NULL) < 0) { + r = -errno; + goto fail; + } + + rmdir(dev); + rmdir(temporary_mount); + + return 0; + +fail: + if (devpts) + umount(devpts); + + if (devshm) + umount(devshm); + + if (devhugepages) + umount(devhugepages); + + if (devmqueue) + umount(devmqueue); + + umount(dev); + rmdir(dev); + rmdir(temporary_mount); + + return r; +} + +static int apply_mount( + BindMount *m, + const char *tmp_dir, + const char *var_tmp_dir) { + + const char *what; + int r; + + assert(m); + + switch (m->mode) { + + case INACCESSIBLE: + + /* First, get rid of everything that is below if there + * is anything... Then, overmount it with an + * inaccessible directory. */ + umount_recursive(m->path, 0); + + what = "/run/systemd/inaccessible"; + break; + + case READONLY: + case READWRITE: + /* Nothing to mount here, we just later toggle the + * MS_RDONLY bit for the mount point */ + return 0; + + case PRIVATE_TMP: + what = tmp_dir; + break; + + case PRIVATE_VAR_TMP: + what = var_tmp_dir; + break; + + case PRIVATE_DEV: + return mount_dev(m); + + default: + assert_not_reached("Unknown mode"); + } + + assert(what); + + r = mount(what, m->path, NULL, MS_BIND|MS_REC, NULL); + if (r >= 0) + log_debug("Successfully mounted %s to %s", what, m->path); + else if (m->ignore && errno == ENOENT) + return 0; + + return r; +} + +static int make_read_only(BindMount *m) { + int r; + + assert(m); + + if (IN_SET(m->mode, INACCESSIBLE, READONLY)) + r = bind_remount_recursive(m->path, true); + else if (IN_SET(m->mode, READWRITE, PRIVATE_TMP, PRIVATE_VAR_TMP, PRIVATE_DEV)) { + r = bind_remount_recursive(m->path, false); + if (r == 0 && m->mode == PRIVATE_DEV) /* can be readonly but the submounts can't*/ + r = mount(NULL, m->path, NULL, MS_REMOUNT|DEV_MOUNT_OPTIONS|MS_RDONLY, NULL); + } else + r = 0; + + if (m->ignore && r == -ENOENT) + return 0; + + return r; +} + +int setup_namespace( + const char* root_directory, + char** read_write_dirs, + char** read_only_dirs, + char** inaccessible_dirs, + const char* tmp_dir, + const char* var_tmp_dir, + bool private_dev, + ProtectHome protect_home, + ProtectSystem protect_system, + unsigned long mount_flags) { + + BindMount *m, *mounts = NULL; + unsigned n; + int r = 0; + + if (mount_flags == 0) + mount_flags = MS_SHARED; + + if (unshare(CLONE_NEWNS) < 0) + return -errno; + + n = !!tmp_dir + !!var_tmp_dir + + strv_length(read_write_dirs) + + strv_length(read_only_dirs) + + strv_length(inaccessible_dirs) + + private_dev + + (protect_home != PROTECT_HOME_NO ? 3 : 0) + + (protect_system != PROTECT_SYSTEM_NO ? 2 : 0) + + (protect_system == PROTECT_SYSTEM_FULL ? 1 : 0); + + if (n > 0) { + m = mounts = (BindMount *) alloca0(n * sizeof(BindMount)); + r = append_mounts(&m, read_write_dirs, READWRITE); + if (r < 0) + return r; + + r = append_mounts(&m, read_only_dirs, READONLY); + if (r < 0) + return r; + + r = append_mounts(&m, inaccessible_dirs, INACCESSIBLE); + if (r < 0) + return r; + + if (tmp_dir) { + m->path = prefix_roota(root_directory, "/tmp"); + m->mode = PRIVATE_TMP; + m++; + } + + if (var_tmp_dir) { + m->path = prefix_roota(root_directory, "/var/tmp"); + m->mode = PRIVATE_VAR_TMP; + m++; + } + + if (private_dev) { + m->path = prefix_roota(root_directory, "/dev"); + m->mode = PRIVATE_DEV; + m++; + } + + if (protect_home != PROTECT_HOME_NO) { + const char *home_dir, *run_user_dir, *root_dir; + + home_dir = prefix_roota(root_directory, "/home"); + home_dir = strjoina("-", home_dir); + run_user_dir = prefix_roota(root_directory, "/run/user"); + run_user_dir = strjoina("-", run_user_dir); + root_dir = prefix_roota(root_directory, "/root"); + root_dir = strjoina("-", root_dir); + + r = append_mounts(&m, STRV_MAKE(home_dir, run_user_dir, root_dir), + protect_home == PROTECT_HOME_READ_ONLY ? READONLY : INACCESSIBLE); + if (r < 0) + return r; + } + + if (protect_system != PROTECT_SYSTEM_NO) { + const char *usr_dir, *boot_dir, *etc_dir; + + usr_dir = prefix_roota(root_directory, "/usr"); + boot_dir = prefix_roota(root_directory, "/boot"); + boot_dir = strjoina("-", boot_dir); + etc_dir = prefix_roota(root_directory, "/etc"); + + r = append_mounts(&m, protect_system == PROTECT_SYSTEM_FULL + ? STRV_MAKE(usr_dir, boot_dir, etc_dir) + : STRV_MAKE(usr_dir, boot_dir), READONLY); + if (r < 0) + return r; + } + + assert(mounts + n == m); + + qsort(mounts, n, sizeof(BindMount), mount_path_compare); + drop_duplicates(mounts, &n); + } + + if (n > 0 || root_directory) { + /* Remount / as SLAVE so that nothing now mounted in the namespace + shows up in the parent */ + if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) + return -errno; + } + + if (root_directory) { + /* Turn directory into bind mount */ + if (mount(root_directory, root_directory, NULL, MS_BIND|MS_REC, NULL) < 0) + return -errno; + } + + if (n > 0) { + for (m = mounts; m < mounts + n; ++m) { + r = apply_mount(m, tmp_dir, var_tmp_dir); + if (r < 0) + goto fail; + } + + for (m = mounts; m < mounts + n; ++m) { + r = make_read_only(m); + if (r < 0) + goto fail; + } + } + + if (root_directory) { + /* MS_MOVE does not work on MS_SHARED so the remount MS_SHARED will be done later */ + r = mount_move_root(root_directory); + + /* at this point, we cannot rollback */ + if (r < 0) + return r; + } + + /* Remount / as the desired mode. Not that this will not + * reestablish propagation from our side to the host, since + * what's disconnected is disconnected. */ + if (mount(NULL, "/", NULL, mount_flags | MS_REC, NULL) < 0) + /* at this point, we cannot rollback */ + return -errno; + + return 0; + +fail: + if (n > 0) { + for (m = mounts; m < mounts + n; ++m) + if (m->done) + (void) umount2(m->path, MNT_DETACH); + } + + return r; +} + +static int setup_one_tmp_dir(const char *id, const char *prefix, char **path) { + _cleanup_free_ char *x = NULL; + char bid[SD_ID128_STRING_MAX]; + sd_id128_t boot_id; + int r; + + assert(id); + assert(prefix); + assert(path); + + /* We include the boot id in the directory so that after a + * reboot we can easily identify obsolete directories. */ + + r = sd_id128_get_boot(&boot_id); + if (r < 0) + return r; + + x = strjoin(prefix, "/systemd-private-", sd_id128_to_string(boot_id, bid), "-", id, "-XXXXXX", NULL); + if (!x) + return -ENOMEM; + + RUN_WITH_UMASK(0077) + if (!mkdtemp(x)) + return -errno; + + RUN_WITH_UMASK(0000) { + char *y; + + y = strjoina(x, "/tmp"); + + if (mkdir(y, 0777 | S_ISVTX) < 0) + return -errno; + } + + *path = x; + x = NULL; + + return 0; +} + +int setup_tmp_dirs(const char *id, char **tmp_dir, char **var_tmp_dir) { + char *a, *b; + int r; + + assert(id); + assert(tmp_dir); + assert(var_tmp_dir); + + r = setup_one_tmp_dir(id, "/tmp", &a); + if (r < 0) + return r; + + r = setup_one_tmp_dir(id, "/var/tmp", &b); + if (r < 0) { + char *t; + + t = strjoina(a, "/tmp"); + rmdir(t); + rmdir(a); + + free(a); + return r; + } + + *tmp_dir = a; + *var_tmp_dir = b; + + return 0; +} + +int setup_netns(int netns_storage_socket[2]) { + _cleanup_close_ int netns = -1; + int r, q; + + assert(netns_storage_socket); + assert(netns_storage_socket[0] >= 0); + assert(netns_storage_socket[1] >= 0); + + /* We use the passed socketpair as a storage buffer for our + * namespace reference fd. Whatever process runs this first + * shall create a new namespace, all others should just join + * it. To serialize that we use a file lock on the socket + * pair. + * + * It's a bit crazy, but hey, works great! */ + + if (lockf(netns_storage_socket[0], F_LOCK, 0) < 0) + return -errno; + + netns = receive_one_fd(netns_storage_socket[0], MSG_DONTWAIT); + if (netns == -EAGAIN) { + /* Nothing stored yet, so let's create a new namespace */ + + if (unshare(CLONE_NEWNET) < 0) { + r = -errno; + goto fail; + } + + loopback_setup(); + + netns = open("/proc/self/ns/net", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (netns < 0) { + r = -errno; + goto fail; + } + + r = 1; + + } else if (netns < 0) { + r = netns; + goto fail; + + } else { + /* Yay, found something, so let's join the namespace */ + if (setns(netns, CLONE_NEWNET) < 0) { + r = -errno; + goto fail; + } + + r = 0; + } + + q = send_one_fd(netns_storage_socket[1], netns, MSG_DONTWAIT); + if (q < 0) { + r = q; + goto fail; + } + +fail: + lockf(netns_storage_socket[0], F_ULOCK, 0); + return r; +} + +static const char *const protect_home_table[_PROTECT_HOME_MAX] = { + [PROTECT_HOME_NO] = "no", + [PROTECT_HOME_YES] = "yes", + [PROTECT_HOME_READ_ONLY] = "read-only", +}; + +DEFINE_STRING_TABLE_LOOKUP(protect_home, ProtectHome); + +static const char *const protect_system_table[_PROTECT_SYSTEM_MAX] = { + [PROTECT_SYSTEM_NO] = "no", + [PROTECT_SYSTEM_YES] = "yes", + [PROTECT_SYSTEM_FULL] = "full", +}; + +DEFINE_STRING_TABLE_LOOKUP(protect_system, ProtectSystem); diff --git a/src/libcore/namespace.h b/src/libcore/namespace.h new file mode 100644 index 0000000000..b54b7b47d6 --- /dev/null +++ b/src/libcore/namespace.h @@ -0,0 +1,63 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "macro.h" + +typedef enum ProtectHome { + PROTECT_HOME_NO, + PROTECT_HOME_YES, + PROTECT_HOME_READ_ONLY, + _PROTECT_HOME_MAX, + _PROTECT_HOME_INVALID = -1 +} ProtectHome; + +typedef enum ProtectSystem { + PROTECT_SYSTEM_NO, + PROTECT_SYSTEM_YES, + PROTECT_SYSTEM_FULL, + _PROTECT_SYSTEM_MAX, + _PROTECT_SYSTEM_INVALID = -1 +} ProtectSystem; + +int setup_namespace(const char *chroot, + char **read_write_dirs, + char **read_only_dirs, + char **inaccessible_dirs, + const char *tmp_dir, + const char *var_tmp_dir, + bool private_dev, + ProtectHome protect_home, + ProtectSystem protect_system, + unsigned long mount_flags); + +int setup_tmp_dirs(const char *id, + char **tmp_dir, + char **var_tmp_dir); + +int setup_netns(int netns_storage_socket[2]); + +const char* protect_home_to_string(ProtectHome p) _const_; +ProtectHome protect_home_from_string(const char *s) _pure_; + +const char* protect_system_to_string(ProtectSystem p) _const_; +ProtectSystem protect_system_from_string(const char *s) _pure_; diff --git a/src/libcore/path.c b/src/libcore/path.c new file mode 100644 index 0000000000..0dd0d375d8 --- /dev/null +++ b/src/libcore/path.c @@ -0,0 +1,784 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include + +#include "bus-error.h" +#include "bus-util.h" +#include "dbus-path.h" +#include "fd-util.h" +#include "fs-util.h" +#include "glob-util.h" +#include "macro.h" +#include "mkdir.h" +#include "path.h" +#include "special.h" +#include "stat-util.h" +#include "string-table.h" +#include "string-util.h" +#include "unit-name.h" +#include "unit.h" + +static const UnitActiveState state_translation_table[_PATH_STATE_MAX] = { + [PATH_DEAD] = UNIT_INACTIVE, + [PATH_WAITING] = UNIT_ACTIVE, + [PATH_RUNNING] = UNIT_ACTIVE, + [PATH_FAILED] = UNIT_FAILED +}; + +static int path_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); + +int path_spec_watch(PathSpec *s, sd_event_io_handler_t handler) { + + static const int flags_table[_PATH_TYPE_MAX] = { + [PATH_EXISTS] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB, + [PATH_EXISTS_GLOB] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB, + [PATH_CHANGED] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO, + [PATH_MODIFIED] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO|IN_MODIFY, + [PATH_DIRECTORY_NOT_EMPTY] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CREATE|IN_MOVED_TO + }; + + bool exists = false; + char *slash, *oldslash = NULL; + int r; + + assert(s); + assert(s->unit); + assert(handler); + + path_spec_unwatch(s); + + s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); + if (s->inotify_fd < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(s->unit->manager->event, &s->event_source, s->inotify_fd, EPOLLIN, handler, s); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(s->event_source, "path"); + + /* This assumes the path was passed through path_kill_slashes()! */ + + for (slash = strchr(s->path, '/'); ; slash = strchr(slash+1, '/')) { + char *cut = NULL; + int flags; + char tmp; + + if (slash) { + cut = slash + (slash == s->path); + tmp = *cut; + *cut = '\0'; + + flags = IN_MOVE_SELF | IN_DELETE_SELF | IN_ATTRIB | IN_CREATE | IN_MOVED_TO; + } else + flags = flags_table[s->type]; + + r = inotify_add_watch(s->inotify_fd, s->path, flags); + if (r < 0) { + if (errno == EACCES || errno == ENOENT) { + if (cut) + *cut = tmp; + break; + } + + r = log_warning_errno(errno, "Failed to add watch on %s: %s", s->path, errno == ENOSPC ? "too many watches" : strerror(-r)); + if (cut) + *cut = tmp; + goto fail; + } else { + exists = true; + + /* Path exists, we don't need to watch parent too closely. */ + if (oldslash) { + char *cut2 = oldslash + (oldslash == s->path); + char tmp2 = *cut2; + *cut2 = '\0'; + + (void) inotify_add_watch(s->inotify_fd, s->path, IN_MOVE_SELF); + /* Error is ignored, the worst can happen is we get spurious events. */ + + *cut2 = tmp2; + } + } + + if (cut) + *cut = tmp; + + if (slash) + oldslash = slash; + else { + /* whole path has been iterated over */ + s->primary_wd = r; + break; + } + } + + if (!exists) { + r = log_error_errno(errno, "Failed to add watch on any of the components of %s: %m", s->path); + /* either EACCESS or ENOENT */ + goto fail; + } + + return 0; + +fail: + path_spec_unwatch(s); + return r; +} + +void path_spec_unwatch(PathSpec *s) { + assert(s); + + s->event_source = sd_event_source_unref(s->event_source); + s->inotify_fd = safe_close(s->inotify_fd); +} + +int path_spec_fd_event(PathSpec *s, uint32_t revents) { + union inotify_event_buffer buffer; + struct inotify_event *e; + ssize_t l; + int r = 0; + + if (revents != EPOLLIN) { + log_error("Got invalid poll event on inotify."); + return -EINVAL; + } + + l = read(s->inotify_fd, &buffer, sizeof(buffer)); + if (l < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return log_error_errno(errno, "Failed to read inotify event: %m"); + } + + FOREACH_INOTIFY_EVENT(e, buffer, l) { + if ((s->type == PATH_CHANGED || s->type == PATH_MODIFIED) && + s->primary_wd == e->wd) + r = 1; + } + + return r; +} + +static bool path_spec_check_good(PathSpec *s, bool initial) { + bool good = false; + + switch (s->type) { + + case PATH_EXISTS: + good = access(s->path, F_OK) >= 0; + break; + + case PATH_EXISTS_GLOB: + good = glob_exists(s->path) > 0; + break; + + case PATH_DIRECTORY_NOT_EMPTY: { + int k; + + k = dir_is_empty(s->path); + good = !(k == -ENOENT || k > 0); + break; + } + + case PATH_CHANGED: + case PATH_MODIFIED: { + bool b; + + b = access(s->path, F_OK) >= 0; + good = !initial && b != s->previous_exists; + s->previous_exists = b; + break; + } + + default: + ; + } + + return good; +} + +static void path_spec_mkdir(PathSpec *s, mode_t mode) { + int r; + + if (s->type == PATH_EXISTS || s->type == PATH_EXISTS_GLOB) + return; + + r = mkdir_p_label(s->path, mode); + if (r < 0) + log_warning_errno(r, "mkdir(%s) failed: %m", s->path); +} + +static void path_spec_dump(PathSpec *s, FILE *f, const char *prefix) { + fprintf(f, + "%s%s: %s\n", + prefix, + path_type_to_string(s->type), + s->path); +} + +void path_spec_done(PathSpec *s) { + assert(s); + assert(s->inotify_fd == -1); + + free(s->path); +} + +static void path_init(Unit *u) { + Path *p = PATH(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + p->directory_mode = 0755; +} + +void path_free_specs(Path *p) { + PathSpec *s; + + assert(p); + + while ((s = p->specs)) { + path_spec_unwatch(s); + LIST_REMOVE(spec, p->specs, s); + path_spec_done(s); + free(s); + } +} + +static void path_done(Unit *u) { + Path *p = PATH(u); + + assert(p); + + path_free_specs(p); +} + +static int path_add_mount_links(Path *p) { + PathSpec *s; + int r; + + assert(p); + + LIST_FOREACH(spec, s, p->specs) { + r = unit_require_mounts_for(UNIT(p), s->path); + if (r < 0) + return r; + } + + return 0; +} + +static int path_verify(Path *p) { + assert(p); + + if (UNIT(p)->load_state != UNIT_LOADED) + return 0; + + if (!p->specs) { + log_unit_error(UNIT(p), "Path unit lacks path setting. Refusing."); + return -EINVAL; + } + + return 0; +} + +static int path_add_default_dependencies(Path *p) { + int r; + + assert(p); + + if (!UNIT(p)->default_dependencies) + return 0; + + r = unit_add_dependency_by_name(UNIT(p), UNIT_BEFORE, SPECIAL_PATHS_TARGET, NULL, true); + if (r < 0) + return r; + + if (MANAGER_IS_SYSTEM(UNIT(p)->manager)) { + r = unit_add_two_dependencies_by_name(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); + if (r < 0) + return r; + } + + return unit_add_two_dependencies_by_name(UNIT(p), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); +} + +static int path_load(Unit *u) { + Path *p = PATH(u); + int r; + + assert(u); + assert(u->load_state == UNIT_STUB); + + r = unit_load_fragment_and_dropin(u); + if (r < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + + if (set_isempty(u->dependencies[UNIT_TRIGGERS])) { + Unit *x; + + r = unit_load_related_unit(u, ".service", &x); + if (r < 0) + return r; + + r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, x, true); + if (r < 0) + return r; + } + + r = path_add_mount_links(p); + if (r < 0) + return r; + + r = path_add_default_dependencies(p); + if (r < 0) + return r; + } + + return path_verify(p); +} + +static void path_dump(Unit *u, FILE *f, const char *prefix) { + Path *p = PATH(u); + Unit *trigger; + PathSpec *s; + + assert(p); + assert(f); + + trigger = UNIT_TRIGGER(u); + + fprintf(f, + "%sPath State: %s\n" + "%sResult: %s\n" + "%sUnit: %s\n" + "%sMakeDirectory: %s\n" + "%sDirectoryMode: %04o\n", + prefix, path_state_to_string(p->state), + prefix, path_result_to_string(p->result), + prefix, trigger ? trigger->id : "n/a", + prefix, yes_no(p->make_directory), + prefix, p->directory_mode); + + LIST_FOREACH(spec, s, p->specs) + path_spec_dump(s, f, prefix); +} + +static void path_unwatch(Path *p) { + PathSpec *s; + + assert(p); + + LIST_FOREACH(spec, s, p->specs) + path_spec_unwatch(s); +} + +static int path_watch(Path *p) { + int r; + PathSpec *s; + + assert(p); + + LIST_FOREACH(spec, s, p->specs) { + r = path_spec_watch(s, path_dispatch_io); + if (r < 0) + return r; + } + + return 0; +} + +static void path_set_state(Path *p, PathState state) { + PathState old_state; + assert(p); + + old_state = p->state; + p->state = state; + + if (state != PATH_WAITING && + (state != PATH_RUNNING || p->inotify_triggered)) + path_unwatch(p); + + if (state != old_state) + log_unit_debug(UNIT(p), "Changed %s -> %s", path_state_to_string(old_state), path_state_to_string(state)); + + unit_notify(UNIT(p), state_translation_table[old_state], state_translation_table[state], true); +} + +static void path_enter_waiting(Path *p, bool initial, bool recheck); + +static int path_coldplug(Unit *u) { + Path *p = PATH(u); + + assert(p); + assert(p->state == PATH_DEAD); + + if (p->deserialized_state != p->state) { + + if (p->deserialized_state == PATH_WAITING || + p->deserialized_state == PATH_RUNNING) + path_enter_waiting(p, true, true); + else + path_set_state(p, p->deserialized_state); + } + + return 0; +} + +static void path_enter_dead(Path *p, PathResult f) { + assert(p); + + if (f != PATH_SUCCESS) + p->result = f; + + path_set_state(p, p->result != PATH_SUCCESS ? PATH_FAILED : PATH_DEAD); +} + +static void path_enter_running(Path *p) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + Unit *trigger; + int r; + + assert(p); + + /* Don't start job if we are supposed to go down */ + if (unit_stop_pending(UNIT(p))) + return; + + trigger = UNIT_TRIGGER(UNIT(p)); + if (!trigger) { + log_unit_error(UNIT(p), "Unit to trigger vanished."); + path_enter_dead(p, PATH_FAILURE_RESOURCES); + return; + } + + r = manager_add_job(UNIT(p)->manager, JOB_START, trigger, JOB_REPLACE, &error, NULL); + if (r < 0) + goto fail; + + p->inotify_triggered = false; + + r = path_watch(p); + if (r < 0) + goto fail; + + path_set_state(p, PATH_RUNNING); + return; + +fail: + log_unit_warning(UNIT(p), "Failed to queue unit startup job: %s", bus_error_message(&error, r)); + path_enter_dead(p, PATH_FAILURE_RESOURCES); +} + +static bool path_check_good(Path *p, bool initial) { + PathSpec *s; + bool good = false; + + assert(p); + + LIST_FOREACH(spec, s, p->specs) { + good = path_spec_check_good(s, initial); + + if (good) + break; + } + + return good; +} + +static void path_enter_waiting(Path *p, bool initial, bool recheck) { + int r; + + if (recheck) + if (path_check_good(p, initial)) { + log_unit_debug(UNIT(p), "Got triggered."); + path_enter_running(p); + return; + } + + r = path_watch(p); + if (r < 0) + goto fail; + + /* Hmm, so now we have created inotify watches, but the file + * might have appeared/been removed by now, so we must + * recheck */ + + if (recheck) + if (path_check_good(p, false)) { + log_unit_debug(UNIT(p), "Got triggered."); + path_enter_running(p); + return; + } + + path_set_state(p, PATH_WAITING); + return; + +fail: + log_unit_warning_errno(UNIT(p), r, "Failed to enter waiting state: %m"); + path_enter_dead(p, PATH_FAILURE_RESOURCES); +} + +static void path_mkdir(Path *p) { + PathSpec *s; + + assert(p); + + if (!p->make_directory) + return; + + LIST_FOREACH(spec, s, p->specs) + path_spec_mkdir(s, p->directory_mode); +} + +static int path_start(Unit *u) { + Path *p = PATH(u); + Unit *trigger; + int r; + + assert(p); + assert(p->state == PATH_DEAD || p->state == PATH_FAILED); + + trigger = UNIT_TRIGGER(u); + if (!trigger || trigger->load_state != UNIT_LOADED) { + log_unit_error(u, "Refusing to start, unit to trigger not loaded."); + return -ENOENT; + } + + r = unit_start_limit_test(u); + if (r < 0) { + path_enter_dead(p, PATH_FAILURE_START_LIMIT_HIT); + return r; + } + + path_mkdir(p); + + p->result = PATH_SUCCESS; + path_enter_waiting(p, true, true); + + return 1; +} + +static int path_stop(Unit *u) { + Path *p = PATH(u); + + assert(p); + assert(p->state == PATH_WAITING || p->state == PATH_RUNNING); + + path_enter_dead(p, PATH_SUCCESS); + return 1; +} + +static int path_serialize(Unit *u, FILE *f, FDSet *fds) { + Path *p = PATH(u); + + assert(u); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", path_state_to_string(p->state)); + unit_serialize_item(u, f, "result", path_result_to_string(p->result)); + + return 0; +} + +static int path_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Path *p = PATH(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + PathState state; + + state = path_state_from_string(value); + if (state < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + p->deserialized_state = state; + + } else if (streq(key, "result")) { + PathResult f; + + f = path_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse result value: %s", value); + else if (f != PATH_SUCCESS) + p->result = f; + + } else + log_unit_debug(u, "Unknown serialization key: %s", key); + + return 0; +} + +_pure_ static UnitActiveState path_active_state(Unit *u) { + assert(u); + + return state_translation_table[PATH(u)->state]; +} + +_pure_ static const char *path_sub_state_to_string(Unit *u) { + assert(u); + + return path_state_to_string(PATH(u)->state); +} + +static int path_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + PathSpec *s = userdata; + Path *p; + int changed; + + assert(s); + assert(s->unit); + assert(fd >= 0); + + p = PATH(s->unit); + + if (p->state != PATH_WAITING && + p->state != PATH_RUNNING) + return 0; + + /* log_debug("inotify wakeup on %s.", u->id); */ + + LIST_FOREACH(spec, s, p->specs) + if (path_spec_owns_inotify_fd(s, fd)) + break; + + if (!s) { + log_error("Got event on unknown fd."); + goto fail; + } + + changed = path_spec_fd_event(s, revents); + if (changed < 0) + goto fail; + + /* If we are already running, then remember that one event was + * dispatched so that we restart the service only if something + * actually changed on disk */ + p->inotify_triggered = true; + + if (changed) + path_enter_running(p); + else + path_enter_waiting(p, false, true); + + return 0; + +fail: + path_enter_dead(p, PATH_FAILURE_RESOURCES); + return 0; +} + +static void path_trigger_notify(Unit *u, Unit *other) { + Path *p = PATH(u); + + assert(u); + assert(other); + + /* Invoked whenever the unit we trigger changes state or gains + * or loses a job */ + + if (other->load_state != UNIT_LOADED) + return; + + if (p->state == PATH_RUNNING && + UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) { + log_unit_debug(UNIT(p), "Got notified about unit deactivation."); + + /* Hmm, so inotify was triggered since the + * last activation, so I guess we need to + * recheck what is going on. */ + path_enter_waiting(p, false, p->inotify_triggered); + } +} + +static void path_reset_failed(Unit *u) { + Path *p = PATH(u); + + assert(p); + + if (p->state == PATH_FAILED) + path_set_state(p, PATH_DEAD); + + p->result = PATH_SUCCESS; +} + +static const char* const path_type_table[_PATH_TYPE_MAX] = { + [PATH_EXISTS] = "PathExists", + [PATH_EXISTS_GLOB] = "PathExistsGlob", + [PATH_DIRECTORY_NOT_EMPTY] = "DirectoryNotEmpty", + [PATH_CHANGED] = "PathChanged", + [PATH_MODIFIED] = "PathModified", +}; + +DEFINE_STRING_TABLE_LOOKUP(path_type, PathType); + +static const char* const path_result_table[_PATH_RESULT_MAX] = { + [PATH_SUCCESS] = "success", + [PATH_FAILURE_RESOURCES] = "resources", + [PATH_FAILURE_START_LIMIT_HIT] = "start-limit-hit", +}; + +DEFINE_STRING_TABLE_LOOKUP(path_result, PathResult); + +const UnitVTable path_vtable = { + .object_size = sizeof(Path), + + .sections = + "Unit\0" + "Path\0" + "Install\0", + + .init = path_init, + .done = path_done, + .load = path_load, + + .coldplug = path_coldplug, + + .dump = path_dump, + + .start = path_start, + .stop = path_stop, + + .serialize = path_serialize, + .deserialize_item = path_deserialize_item, + + .active_state = path_active_state, + .sub_state_to_string = path_sub_state_to_string, + + .trigger_notify = path_trigger_notify, + + .reset_failed = path_reset_failed, + + .bus_vtable = bus_path_vtable +}; diff --git a/src/libcore/path.h b/src/libcore/path.h new file mode 100644 index 0000000000..4230c8fb99 --- /dev/null +++ b/src/libcore/path.h @@ -0,0 +1,93 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct Path Path; +typedef struct PathSpec PathSpec; + +#include "unit.h" + +typedef enum PathType { + PATH_EXISTS, + PATH_EXISTS_GLOB, + PATH_DIRECTORY_NOT_EMPTY, + PATH_CHANGED, + PATH_MODIFIED, + _PATH_TYPE_MAX, + _PATH_TYPE_INVALID = -1 +} PathType; + +typedef struct PathSpec { + Unit *unit; + + char *path; + + sd_event_source *event_source; + + LIST_FIELDS(struct PathSpec, spec); + + PathType type; + int inotify_fd; + int primary_wd; + + bool previous_exists; +} PathSpec; + +int path_spec_watch(PathSpec *s, sd_event_io_handler_t handler); +void path_spec_unwatch(PathSpec *s); +int path_spec_fd_event(PathSpec *s, uint32_t events); +void path_spec_done(PathSpec *s); + +static inline bool path_spec_owns_inotify_fd(PathSpec *s, int fd) { + return s->inotify_fd == fd; +} + +typedef enum PathResult { + PATH_SUCCESS, + PATH_FAILURE_RESOURCES, + PATH_FAILURE_START_LIMIT_HIT, + _PATH_RESULT_MAX, + _PATH_RESULT_INVALID = -1 +} PathResult; + +struct Path { + Unit meta; + + LIST_HEAD(PathSpec, specs); + + PathState state, deserialized_state; + + bool inotify_triggered; + + bool make_directory; + mode_t directory_mode; + + PathResult result; +}; + +void path_free_specs(Path *p); + +extern const UnitVTable path_vtable; + +const char* path_type_to_string(PathType i) _const_; +PathType path_type_from_string(const char *s) _pure_; + +const char* path_result_to_string(PathResult i) _const_; +PathResult path_result_from_string(const char *s) _pure_; diff --git a/src/libcore/scope.c b/src/libcore/scope.c new file mode 100644 index 0000000000..238f63a729 --- /dev/null +++ b/src/libcore/scope.c @@ -0,0 +1,608 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include "alloc-util.h" +#include "dbus-scope.h" +#include "load-dropin.h" +#include "log.h" +#include "scope.h" +#include "special.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "unit.h" + +static const UnitActiveState state_translation_table[_SCOPE_STATE_MAX] = { + [SCOPE_DEAD] = UNIT_INACTIVE, + [SCOPE_RUNNING] = UNIT_ACTIVE, + [SCOPE_ABANDONED] = UNIT_ACTIVE, + [SCOPE_STOP_SIGTERM] = UNIT_DEACTIVATING, + [SCOPE_STOP_SIGKILL] = UNIT_DEACTIVATING, + [SCOPE_FAILED] = UNIT_FAILED +}; + +static int scope_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); + +static void scope_init(Unit *u) { + Scope *s = SCOPE(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + s->timeout_stop_usec = u->manager->default_timeout_stop_usec; + u->ignore_on_isolate = true; +} + +static void scope_done(Unit *u) { + Scope *s = SCOPE(u); + + assert(u); + + free(s->controller); + + s->timer_event_source = sd_event_source_unref(s->timer_event_source); +} + +static int scope_arm_timer(Scope *s, usec_t usec) { + int r; + + assert(s); + + if (s->timer_event_source) { + r = sd_event_source_set_time(s->timer_event_source, usec); + if (r < 0) + return r; + + return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); + } + + if (usec == USEC_INFINITY) + return 0; + + r = sd_event_add_time( + UNIT(s)->manager->event, + &s->timer_event_source, + CLOCK_MONOTONIC, + usec, 0, + scope_dispatch_timer, s); + if (r < 0) + return r; + + (void) sd_event_source_set_description(s->timer_event_source, "scope-timer"); + + return 0; +} + +static void scope_set_state(Scope *s, ScopeState state) { + ScopeState old_state; + assert(s); + + old_state = s->state; + s->state = state; + + if (!IN_SET(state, SCOPE_STOP_SIGTERM, SCOPE_STOP_SIGKILL)) + s->timer_event_source = sd_event_source_unref(s->timer_event_source); + + if (IN_SET(state, SCOPE_DEAD, SCOPE_FAILED)) + unit_unwatch_all_pids(UNIT(s)); + + if (state != old_state) + log_debug("%s changed %s -> %s", UNIT(s)->id, scope_state_to_string(old_state), scope_state_to_string(state)); + + unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); +} + +static int scope_add_default_dependencies(Scope *s) { + int r; + + assert(s); + + if (!UNIT(s)->default_dependencies) + return 0; + + /* Make sure scopes are unloaded on shutdown */ + r = unit_add_two_dependencies_by_name( + UNIT(s), + UNIT_BEFORE, UNIT_CONFLICTS, + SPECIAL_SHUTDOWN_TARGET, NULL, true); + if (r < 0) + return r; + + return 0; +} + +static int scope_verify(Scope *s) { + assert(s); + + if (UNIT(s)->load_state != UNIT_LOADED) + return 0; + + if (set_isempty(UNIT(s)->pids) && + !MANAGER_IS_RELOADING(UNIT(s)->manager) && + !unit_has_name(UNIT(s), SPECIAL_INIT_SCOPE)) { + log_unit_error(UNIT(s), "Scope has no PIDs. Refusing."); + return -EINVAL; + } + + return 0; +} + +static int scope_load(Unit *u) { + Scope *s = SCOPE(u); + int r; + + assert(s); + assert(u->load_state == UNIT_STUB); + + if (!u->transient && !MANAGER_IS_RELOADING(u->manager)) + /* Refuse to load non-transient scope units, but allow them while reloading. */ + return -ENOENT; + + r = unit_load_fragment_and_dropin_optional(u); + if (r < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + r = unit_patch_contexts(u); + if (r < 0) + return r; + + r = unit_set_default_slice(u); + if (r < 0) + return r; + + r = scope_add_default_dependencies(s); + if (r < 0) + return r; + } + + return scope_verify(s); +} + +static int scope_coldplug(Unit *u) { + Scope *s = SCOPE(u); + int r; + + assert(s); + assert(s->state == SCOPE_DEAD); + + if (s->deserialized_state == s->state) + return 0; + + if (IN_SET(s->deserialized_state, SCOPE_STOP_SIGKILL, SCOPE_STOP_SIGTERM)) { + r = scope_arm_timer(s, usec_add(u->state_change_timestamp.monotonic, s->timeout_stop_usec)); + if (r < 0) + return r; + } + + if (!IN_SET(s->deserialized_state, SCOPE_DEAD, SCOPE_FAILED)) + unit_watch_all_pids(UNIT(s)); + + scope_set_state(s, s->deserialized_state); + return 0; +} + +static void scope_dump(Unit *u, FILE *f, const char *prefix) { + Scope *s = SCOPE(u); + + assert(s); + assert(f); + + fprintf(f, + "%sScope State: %s\n" + "%sResult: %s\n", + prefix, scope_state_to_string(s->state), + prefix, scope_result_to_string(s->result)); + + cgroup_context_dump(&s->cgroup_context, f, prefix); + kill_context_dump(&s->kill_context, f, prefix); +} + +static void scope_enter_dead(Scope *s, ScopeResult f) { + assert(s); + + if (f != SCOPE_SUCCESS) + s->result = f; + + scope_set_state(s, s->result != SCOPE_SUCCESS ? SCOPE_FAILED : SCOPE_DEAD); +} + +static void scope_enter_signal(Scope *s, ScopeState state, ScopeResult f) { + bool skip_signal = false; + int r; + + assert(s); + + if (f != SCOPE_SUCCESS) + s->result = f; + + unit_watch_all_pids(UNIT(s)); + + /* If we have a controller set let's ask the controller nicely + * to terminate the scope, instead of us going directly into + * SIGTERM beserk mode */ + if (state == SCOPE_STOP_SIGTERM) + skip_signal = bus_scope_send_request_stop(s) > 0; + + if (!skip_signal) { + r = unit_kill_context( + UNIT(s), + &s->kill_context, + state != SCOPE_STOP_SIGTERM ? KILL_KILL : KILL_TERMINATE, + -1, -1, false); + if (r < 0) + goto fail; + } else + r = 1; + + if (r > 0) { + r = scope_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_stop_usec)); + if (r < 0) + goto fail; + + scope_set_state(s, state); + } else if (state == SCOPE_STOP_SIGTERM) + scope_enter_signal(s, SCOPE_STOP_SIGKILL, SCOPE_SUCCESS); + else + scope_enter_dead(s, SCOPE_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); + + scope_enter_dead(s, SCOPE_FAILURE_RESOURCES); +} + +static int scope_start(Unit *u) { + Scope *s = SCOPE(u); + int r; + + assert(s); + + if (unit_has_name(u, SPECIAL_INIT_SCOPE)) + return -EPERM; + + if (s->state == SCOPE_FAILED) + return -EPERM; + + /* We can't fulfill this right now, please try again later */ + if (s->state == SCOPE_STOP_SIGTERM || + s->state == SCOPE_STOP_SIGKILL) + return -EAGAIN; + + assert(s->state == SCOPE_DEAD); + + if (!u->transient && !MANAGER_IS_RELOADING(u->manager)) + return -ENOENT; + + (void) unit_realize_cgroup(u); + (void) unit_reset_cpu_usage(u); + + r = unit_attach_pids_to_cgroup(u); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to add PIDs to scope's control group: %m"); + scope_enter_dead(s, SCOPE_FAILURE_RESOURCES); + return r; + } + + s->result = SCOPE_SUCCESS; + + scope_set_state(s, SCOPE_RUNNING); + return 1; +} + +static int scope_stop(Unit *u) { + Scope *s = SCOPE(u); + + assert(s); + + if (s->state == SCOPE_STOP_SIGTERM || + s->state == SCOPE_STOP_SIGKILL) + return 0; + + assert(s->state == SCOPE_RUNNING || + s->state == SCOPE_ABANDONED); + + scope_enter_signal(s, SCOPE_STOP_SIGTERM, SCOPE_SUCCESS); + return 1; +} + +static void scope_reset_failed(Unit *u) { + Scope *s = SCOPE(u); + + assert(s); + + if (s->state == SCOPE_FAILED) + scope_set_state(s, SCOPE_DEAD); + + s->result = SCOPE_SUCCESS; +} + +static int scope_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { + return unit_kill_common(u, who, signo, -1, -1, error); +} + +static int scope_get_timeout(Unit *u, usec_t *timeout) { + Scope *s = SCOPE(u); + usec_t t; + int r; + + if (!s->timer_event_source) + return 0; + + r = sd_event_source_get_time(s->timer_event_source, &t); + if (r < 0) + return r; + if (t == USEC_INFINITY) + return 0; + + *timeout = t; + return 1; +} + +static int scope_serialize(Unit *u, FILE *f, FDSet *fds) { + Scope *s = SCOPE(u); + + assert(s); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", scope_state_to_string(s->state)); + return 0; +} + +static int scope_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Scope *s = SCOPE(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + ScopeState state; + + state = scope_state_from_string(value); + if (state < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + s->deserialized_state = state; + + } else + log_unit_debug(u, "Unknown serialization key: %s", key); + + return 0; +} + +static bool scope_check_gc(Unit *u) { + assert(u); + + /* Never clean up scopes that still have a process around, + * even if the scope is formally dead. */ + + if (!u->cgroup_path) + return false; + + return cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path) <= 0; +} + +static void scope_notify_cgroup_empty_event(Unit *u) { + Scope *s = SCOPE(u); + assert(u); + + log_unit_debug(u, "cgroup is empty"); + + if (IN_SET(s->state, SCOPE_RUNNING, SCOPE_ABANDONED, SCOPE_STOP_SIGTERM, SCOPE_STOP_SIGKILL)) + scope_enter_dead(s, SCOPE_SUCCESS); +} + +static void scope_sigchld_event(Unit *u, pid_t pid, int code, int status) { + + /* If we get a SIGCHLD event for one of the processes we were + interested in, then we look for others to watch, under the + assumption that we'll sooner or later get a SIGCHLD for + them, as the original process we watched was probably the + parent of them, and they are hence now our children. */ + + unit_tidy_watch_pids(u, 0, 0); + unit_watch_all_pids(u); + + /* If the PID set is empty now, then let's finish this off */ + if (set_isempty(u->pids)) + scope_notify_cgroup_empty_event(u); +} + +static int scope_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { + Scope *s = SCOPE(userdata); + + assert(s); + assert(s->timer_event_source == source); + + switch (s->state) { + + case SCOPE_STOP_SIGTERM: + if (s->kill_context.send_sigkill) { + log_unit_warning(UNIT(s), "Stopping timed out. Killing."); + scope_enter_signal(s, SCOPE_STOP_SIGKILL, SCOPE_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(s), "Stopping timed out. Skipping SIGKILL."); + scope_enter_dead(s, SCOPE_FAILURE_TIMEOUT); + } + + break; + + case SCOPE_STOP_SIGKILL: + log_unit_warning(UNIT(s), "Still around after SIGKILL. Ignoring."); + scope_enter_dead(s, SCOPE_FAILURE_TIMEOUT); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } + + return 0; +} + +int scope_abandon(Scope *s) { + assert(s); + + if (unit_has_name(UNIT(s), SPECIAL_INIT_SCOPE)) + return -EPERM; + + if (!IN_SET(s->state, SCOPE_RUNNING, SCOPE_ABANDONED)) + return -ESTALE; + + s->controller = mfree(s->controller); + + /* The client is no longer watching the remaining processes, + * so let's step in here, under the assumption that the + * remaining processes will be sooner or later reassigned to + * us as parent. */ + + unit_tidy_watch_pids(UNIT(s), 0, 0); + unit_watch_all_pids(UNIT(s)); + + /* If the PID set is empty now, then let's finish this off */ + if (set_isempty(UNIT(s)->pids)) + scope_notify_cgroup_empty_event(UNIT(s)); + else + scope_set_state(s, SCOPE_ABANDONED); + + return 0; +} + +_pure_ static UnitActiveState scope_active_state(Unit *u) { + assert(u); + + return state_translation_table[SCOPE(u)->state]; +} + +_pure_ static const char *scope_sub_state_to_string(Unit *u) { + assert(u); + + return scope_state_to_string(SCOPE(u)->state); +} + +static void scope_enumerate(Manager *m) { + Unit *u; + int r; + + assert(m); + + /* Let's unconditionally add the "init.scope" special unit + * that encapsulates PID 1. Note that PID 1 already is in the + * cgroup for this, we hence just need to allocate the object + * for it and that's it. */ + + u = manager_get_unit(m, SPECIAL_INIT_SCOPE); + if (!u) { + u = unit_new(m, sizeof(Scope)); + if (!u) { + log_oom(); + return; + } + + r = unit_add_name(u, SPECIAL_INIT_SCOPE); + if (r < 0) { + unit_free(u); + log_error_errno(r, "Failed to add init.scope name"); + return; + } + } + + u->transient = true; + u->default_dependencies = false; + u->no_gc = true; + u->ignore_on_isolate = true; + u->refuse_manual_start = true; + u->refuse_manual_stop = true; + SCOPE(u)->deserialized_state = SCOPE_RUNNING; + SCOPE(u)->kill_context.kill_signal = SIGRTMIN+14; + + /* Prettify things, if we can. */ + if (!u->description) + u->description = strdup("System and Service Manager"); + if (!u->documentation) + (void) strv_extend(&u->documentation, "man:systemd(1)"); + + unit_add_to_load_queue(u); + unit_add_to_dbus_queue(u); +} + +static const char* const scope_result_table[_SCOPE_RESULT_MAX] = { + [SCOPE_SUCCESS] = "success", + [SCOPE_FAILURE_RESOURCES] = "resources", + [SCOPE_FAILURE_TIMEOUT] = "timeout", +}; + +DEFINE_STRING_TABLE_LOOKUP(scope_result, ScopeResult); + +const UnitVTable scope_vtable = { + .object_size = sizeof(Scope), + .cgroup_context_offset = offsetof(Scope, cgroup_context), + .kill_context_offset = offsetof(Scope, kill_context), + + .sections = + "Unit\0" + "Scope\0" + "Install\0", + .private_section = "Scope", + + .can_transient = true, + + .init = scope_init, + .load = scope_load, + .done = scope_done, + + .coldplug = scope_coldplug, + + .dump = scope_dump, + + .start = scope_start, + .stop = scope_stop, + + .kill = scope_kill, + + .get_timeout = scope_get_timeout, + + .serialize = scope_serialize, + .deserialize_item = scope_deserialize_item, + + .active_state = scope_active_state, + .sub_state_to_string = scope_sub_state_to_string, + + .check_gc = scope_check_gc, + + .sigchld_event = scope_sigchld_event, + + .reset_failed = scope_reset_failed, + + .notify_cgroup_empty = scope_notify_cgroup_empty_event, + + .bus_vtable = bus_scope_vtable, + .bus_set_property = bus_scope_set_property, + .bus_commit_properties = bus_scope_commit_properties, + + .enumerate = scope_enumerate, +}; diff --git a/src/libcore/scope.h b/src/libcore/scope.h new file mode 100644 index 0000000000..2dc86325c5 --- /dev/null +++ b/src/libcore/scope.h @@ -0,0 +1,55 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +typedef struct Scope Scope; + +#include "kill.h" + +typedef enum ScopeResult { + SCOPE_SUCCESS, + SCOPE_FAILURE_RESOURCES, + SCOPE_FAILURE_TIMEOUT, + _SCOPE_RESULT_MAX, + _SCOPE_RESULT_INVALID = -1 +} ScopeResult; + +struct Scope { + Unit meta; + + CGroupContext cgroup_context; + KillContext kill_context; + + ScopeState state, deserialized_state; + ScopeResult result; + + usec_t timeout_stop_usec; + + char *controller; + + sd_event_source *timer_event_source; +}; + +extern const UnitVTable scope_vtable; + +int scope_abandon(Scope *s); + +const char* scope_result_to_string(ScopeResult i) _const_; +ScopeResult scope_result_from_string(const char *s) _pure_; diff --git a/src/libcore/selinux-access.c b/src/libcore/selinux-access.c new file mode 100644 index 0000000000..2c04fb0a8f --- /dev/null +++ b/src/libcore/selinux-access.c @@ -0,0 +1,283 @@ +/*** + This file is part of systemd. + + Copyright 2012 Dan Walsh + + 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 "selinux-access.h" + +#ifdef HAVE_SELINUX + +#include +#include +#include +#include +#ifdef HAVE_AUDIT +#include +#endif + +#include + +#include "alloc-util.h" +#include "audit-fd.h" +#include "bus-util.h" +#include "log.h" +#include "path-util.h" +#include "selinux-util.h" +#include "stdio-util.h" +#include "strv.h" +#include "util.h" + +static bool initialized = false; + +struct audit_info { + sd_bus_creds *creds; + const char *path; + const char *cmdline; +}; + +/* + Any time an access gets denied this callback will be called + with the audit data. We then need to just copy the audit data into the msgbuf. +*/ +static int audit_callback( + void *auditdata, + security_class_t cls, + char *msgbuf, + size_t msgbufsize) { + + const struct audit_info *audit = auditdata; + uid_t uid = 0, login_uid = 0; + gid_t gid = 0; + char login_uid_buf[DECIMAL_STR_MAX(uid_t) + 1] = "n/a"; + char uid_buf[DECIMAL_STR_MAX(uid_t) + 1] = "n/a"; + char gid_buf[DECIMAL_STR_MAX(gid_t) + 1] = "n/a"; + + if (sd_bus_creds_get_audit_login_uid(audit->creds, &login_uid) >= 0) + xsprintf(login_uid_buf, UID_FMT, login_uid); + if (sd_bus_creds_get_euid(audit->creds, &uid) >= 0) + xsprintf(uid_buf, UID_FMT, uid); + if (sd_bus_creds_get_egid(audit->creds, &gid) >= 0) + xsprintf(gid_buf, GID_FMT, gid); + + snprintf(msgbuf, msgbufsize, + "auid=%s uid=%s gid=%s%s%s%s%s%s%s", + login_uid_buf, uid_buf, gid_buf, + audit->path ? " path=\"" : "", strempty(audit->path), audit->path ? "\"" : "", + audit->cmdline ? " cmdline=\"" : "", strempty(audit->cmdline), audit->cmdline ? "\"" : ""); + + return 0; +} + +static int callback_type_to_priority(int type) { + switch(type) { + + case SELINUX_ERROR: + return LOG_ERR; + + case SELINUX_WARNING: + return LOG_WARNING; + + case SELINUX_INFO: + return LOG_INFO; + + case SELINUX_AVC: + default: + return LOG_NOTICE; + } +} + +/* + libselinux uses this callback when access gets denied or other + events happen. If audit is turned on, messages will be reported + using audit netlink, otherwise they will be logged using the usual + channels. + + Code copied from dbus and modified. +*/ +_printf_(2, 3) static int log_callback(int type, const char *fmt, ...) { + va_list ap; + const char *fmt2; + +#ifdef HAVE_AUDIT + int fd; + + fd = get_audit_fd(); + + if (fd >= 0) { + _cleanup_free_ char *buf = NULL; + int r; + + va_start(ap, fmt); + r = vasprintf(&buf, fmt, ap); + va_end(ap); + + if (r >= 0) { + audit_log_user_avc_message(fd, AUDIT_USER_AVC, buf, NULL, NULL, NULL, 0); + return 0; + } + } +#endif + + fmt2 = strjoina("selinux: ", fmt); + + va_start(ap, fmt); + log_internalv(LOG_AUTH | callback_type_to_priority(type), 0, __FILE__, __LINE__, __FUNCTION__, fmt2, ap); + va_end(ap); + + return 0; +} + +static int access_init(sd_bus_error *error) { + + if (!mac_selinux_use()) + return 0; + + if (initialized) + return 1; + + if (avc_open(NULL, 0) != 0) { + int enforce, saved_errno = errno; + + enforce = security_getenforce(); + log_full_errno(enforce != 0 ? LOG_ERR : LOG_WARNING, saved_errno, "Failed to open the SELinux AVC: %m"); + + /* If enforcement isn't on, then let's suppress this + * error, and just don't do any AVC checks. The + * warning we printed is hence all the admin will + * see. */ + if (enforce == 0) + return 0; + + /* Return an access denied error, if we couldn't load + * the AVC but enforcing mode was on, or we couldn't + * determine whether it is one. */ + return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to open the SELinux AVC: %s", strerror(saved_errno)); + } + + selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) audit_callback); + selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) log_callback); + + initialized = true; + return 1; +} + +/* + This function communicates with the kernel to check whether or not it should + allow the access. + If the machine is in permissive mode it will return ok. Audit messages will + still be generated if the access would be denied in enforcing mode. +*/ +int mac_selinux_generic_access_check( + sd_bus_message *message, + const char *path, + const char *permission, + sd_bus_error *error) { + + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + const char *tclass = NULL, *scon = NULL; + struct audit_info audit_info = {}; + _cleanup_free_ char *cl = NULL; + security_context_t fcon = NULL; + char **cmdline = NULL; + int r = 0; + + assert(message); + assert(permission); + assert(error); + + r = access_init(error); + if (r <= 0) + return r; + + r = sd_bus_query_sender_creds( + message, + SD_BUS_CREDS_PID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID| + SD_BUS_CREDS_CMDLINE|SD_BUS_CREDS_AUDIT_LOGIN_UID| + SD_BUS_CREDS_SELINUX_CONTEXT| + SD_BUS_CREDS_AUGMENT /* get more bits from /proc */, + &creds); + if (r < 0) + goto finish; + + /* The SELinux context is something we really should have + * gotten directly from the message or sender, and not be an + * augmented field. If it was augmented we cannot use it for + * authorization, since this is racy and vulnerable. Let's add + * an extra check, just in case, even though this really + * shouldn't be possible. */ + assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_SELINUX_CONTEXT) == 0, -EPERM); + + r = sd_bus_creds_get_selinux_context(creds, &scon); + if (r < 0) + goto finish; + + if (path) { + /* Get the file context of the unit file */ + + r = getfilecon_raw(path, &fcon); + if (r < 0) { + r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get file context on %s.", path); + goto finish; + } + + tclass = "service"; + } else { + r = getcon_raw(&fcon); + if (r < 0) { + r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get current context."); + goto finish; + } + + tclass = "system"; + } + + sd_bus_creds_get_cmdline(creds, &cmdline); + cl = strv_join(cmdline, " "); + + audit_info.creds = creds; + audit_info.path = path; + audit_info.cmdline = cl; + + r = selinux_check_access(scon, fcon, tclass, permission, &audit_info); + if (r < 0) + r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "SELinux policy denies access."); + + log_debug("SELinux access check scon=%s tcon=%s tclass=%s perm=%s path=%s cmdline=%s: %i", scon, fcon, tclass, permission, path, cl, r); + +finish: + freecon(fcon); + + if (r < 0 && security_getenforce() != 1) { + sd_bus_error_free(error); + r = 0; + } + + return r; +} + +#else + +int mac_selinux_generic_access_check( + sd_bus_message *message, + const char *path, + const char *permission, + sd_bus_error *error) { + + return 0; +} + +#endif diff --git a/src/libcore/selinux-access.h b/src/libcore/selinux-access.h new file mode 100644 index 0000000000..cbf33ef6c4 --- /dev/null +++ b/src/libcore/selinux-access.h @@ -0,0 +1,45 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 Dan Walsh + + 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 "bus-util.h" +#include "manager.h" + +int mac_selinux_generic_access_check(sd_bus_message *message, const char *path, const char *permission, sd_bus_error *error); + +#ifdef HAVE_SELINUX + +#define mac_selinux_access_check(message, permission, error) \ + mac_selinux_generic_access_check((message), NULL, (permission), (error)) + +#define mac_selinux_unit_access_check(unit, message, permission, error) \ + ({ \ + Unit *_unit = (unit); \ + mac_selinux_generic_access_check((message), _unit->source_path ?: _unit->fragment_path, (permission), (error)); \ + }) + +#else + +#define mac_selinux_access_check(message, permission, error) 0 +#define mac_selinux_unit_access_check(unit, message, permission, error) 0 + +#endif diff --git a/src/libcore/selinux-setup.c b/src/libcore/selinux-setup.c new file mode 100644 index 0000000000..4072df58e6 --- /dev/null +++ b/src/libcore/selinux-setup.c @@ -0,0 +1,121 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include + +#ifdef HAVE_SELINUX +#include +#endif + +#include "log.h" +#include "macro.h" +#include "selinux-setup.h" +#include "selinux-util.h" +#include "string-util.h" +#include "util.h" + +#ifdef HAVE_SELINUX +_printf_(2,3) +static int null_log(int type, const char *fmt, ...) { + return 0; +} +#endif + +int mac_selinux_setup(bool *loaded_policy) { + +#ifdef HAVE_SELINUX + int enforce = 0; + usec_t before_load, after_load; + security_context_t con; + int r; + union selinux_callback cb; + bool initialized = false; + + assert(loaded_policy); + + /* Turn off all of SELinux' own logging, we want to do that */ + cb.func_log = null_log; + selinux_set_callback(SELINUX_CB_LOG, cb); + + /* Don't load policy in the initrd if we don't appear to have + * it. For the real root, we check below if we've already + * loaded policy, and return gracefully. + */ + if (in_initrd() && access(selinux_path(), F_OK) < 0) + return 0; + + /* Already initialized by somebody else? */ + r = getcon_raw(&con); + if (r == 0) { + initialized = !streq(con, "kernel"); + freecon(con); + } + + /* Make sure we have no fds open while loading the policy and + * transitioning */ + log_close(); + + /* Now load the policy */ + before_load = now(CLOCK_MONOTONIC); + r = selinux_init_load_policy(&enforce); + if (r == 0) { + _cleanup_(mac_selinux_freep) char *label = NULL; + char timespan[FORMAT_TIMESPAN_MAX]; + + mac_selinux_retest(); + + /* Transition to the new context */ + r = mac_selinux_get_create_label_from_exe(SYSTEMD_BINARY_PATH, &label); + if (r < 0 || !label) { + log_open(); + log_error("Failed to compute init label, ignoring."); + } else { + r = setcon_raw(label); + + log_open(); + if (r < 0) + log_error("Failed to transition into init label '%s', ignoring.", label); + } + + after_load = now(CLOCK_MONOTONIC); + + log_info("Successfully loaded SELinux policy in %s.", + format_timespan(timespan, sizeof(timespan), after_load - before_load, 0)); + + *loaded_policy = true; + + } else { + log_open(); + + if (enforce > 0) { + if (!initialized) { + log_emergency("Failed to load SELinux policy."); + return -EIO; + } + + log_warning("Failed to load new SELinux policy. Continuing with old policy."); + } else + log_debug("Unable to load SELinux policy. Ignoring."); + } +#endif + + return 0; +} diff --git a/src/libcore/selinux-setup.h b/src/libcore/selinux-setup.h new file mode 100644 index 0000000000..7b613249b0 --- /dev/null +++ b/src/libcore/selinux-setup.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 + +int mac_selinux_setup(bool *loaded_policy); diff --git a/src/libcore/service.c b/src/libcore/service.c new file mode 100644 index 0000000000..7ebabca5d6 --- /dev/null +++ b/src/libcore/service.c @@ -0,0 +1,3353 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include + +#include "alloc-util.h" +#include "async.h" +#include "bus-error.h" +#include "bus-kernel.h" +#include "bus-util.h" +#include "dbus-service.h" +#include "def.h" +#include "env-util.h" +#include "escape.h" +#include "exit-status.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "load-dropin.h" +#include "load-fragment.h" +#include "log.h" +#include "manager.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "service.h" +#include "signal-util.h" +#include "special.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "unit-printf.h" +#include "unit.h" +#include "utf8.h" +#include "util.h" + +static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = { + [SERVICE_DEAD] = UNIT_INACTIVE, + [SERVICE_START_PRE] = UNIT_ACTIVATING, + [SERVICE_START] = UNIT_ACTIVATING, + [SERVICE_START_POST] = UNIT_ACTIVATING, + [SERVICE_RUNNING] = UNIT_ACTIVE, + [SERVICE_EXITED] = UNIT_ACTIVE, + [SERVICE_RELOAD] = UNIT_RELOADING, + [SERVICE_STOP] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGABRT] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGKILL] = UNIT_DEACTIVATING, + [SERVICE_STOP_POST] = UNIT_DEACTIVATING, + [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING, + [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING, + [SERVICE_FAILED] = UNIT_FAILED, + [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING +}; + +/* For Type=idle we never want to delay any other jobs, hence we + * consider idle jobs active as soon as we start working on them */ +static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] = { + [SERVICE_DEAD] = UNIT_INACTIVE, + [SERVICE_START_PRE] = UNIT_ACTIVE, + [SERVICE_START] = UNIT_ACTIVE, + [SERVICE_START_POST] = UNIT_ACTIVE, + [SERVICE_RUNNING] = UNIT_ACTIVE, + [SERVICE_EXITED] = UNIT_ACTIVE, + [SERVICE_RELOAD] = UNIT_RELOADING, + [SERVICE_STOP] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGABRT] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGKILL] = UNIT_DEACTIVATING, + [SERVICE_STOP_POST] = UNIT_DEACTIVATING, + [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING, + [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING, + [SERVICE_FAILED] = UNIT_FAILED, + [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING +}; + +static int service_dispatch_io(sd_event_source *source, int fd, uint32_t events, void *userdata); +static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); +static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void *userdata); + +static void service_enter_signal(Service *s, ServiceState state, ServiceResult f); +static void service_enter_reload_by_notify(Service *s); + +static void service_init(Unit *u) { + Service *s = SERVICE(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + s->timeout_start_usec = u->manager->default_timeout_start_usec; + s->timeout_stop_usec = u->manager->default_timeout_stop_usec; + s->restart_usec = u->manager->default_restart_usec; + s->runtime_max_usec = USEC_INFINITY; + s->type = _SERVICE_TYPE_INVALID; + s->socket_fd = -1; + s->stdin_fd = s->stdout_fd = s->stderr_fd = -1; + s->guess_main_pid = true; + + s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; +} + +static void service_unwatch_control_pid(Service *s) { + assert(s); + + if (s->control_pid <= 0) + return; + + unit_unwatch_pid(UNIT(s), s->control_pid); + s->control_pid = 0; +} + +static void service_unwatch_main_pid(Service *s) { + assert(s); + + if (s->main_pid <= 0) + return; + + unit_unwatch_pid(UNIT(s), s->main_pid); + s->main_pid = 0; +} + +static void service_unwatch_pid_file(Service *s) { + if (!s->pid_file_pathspec) + return; + + log_unit_debug(UNIT(s), "Stopping watch for PID file %s", s->pid_file_pathspec->path); + path_spec_unwatch(s->pid_file_pathspec); + path_spec_done(s->pid_file_pathspec); + s->pid_file_pathspec = mfree(s->pid_file_pathspec); +} + +static int service_set_main_pid(Service *s, pid_t pid) { + pid_t ppid; + + assert(s); + + if (pid <= 1) + return -EINVAL; + + if (pid == getpid()) + return -EINVAL; + + if (s->main_pid == pid && s->main_pid_known) + return 0; + + if (s->main_pid != pid) { + service_unwatch_main_pid(s); + exec_status_start(&s->main_exec_status, pid); + } + + s->main_pid = pid; + s->main_pid_known = true; + + if (get_process_ppid(pid, &ppid) >= 0 && ppid != getpid()) { + log_unit_warning(UNIT(s), "Supervising process "PID_FMT" which is not our child. We'll most likely not notice when it exits.", pid); + s->main_pid_alien = true; + } else + s->main_pid_alien = false; + + return 0; +} + +void service_close_socket_fd(Service *s) { + assert(s); + + /* Undo the effect of service_set_socket_fd(). */ + + s->socket_fd = asynchronous_close(s->socket_fd); + + if (UNIT_ISSET(s->accept_socket)) { + socket_connection_unref(SOCKET(UNIT_DEREF(s->accept_socket))); + unit_ref_unset(&s->accept_socket); + } +} + +static void service_stop_watchdog(Service *s) { + assert(s); + + s->watchdog_event_source = sd_event_source_unref(s->watchdog_event_source); + s->watchdog_timestamp = DUAL_TIMESTAMP_NULL; +} + +static void service_start_watchdog(Service *s) { + int r; + + assert(s); + + if (s->watchdog_usec <= 0) + return; + + if (s->watchdog_event_source) { + r = sd_event_source_set_time(s->watchdog_event_source, usec_add(s->watchdog_timestamp.monotonic, s->watchdog_usec)); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to reset watchdog timer: %m"); + return; + } + + r = sd_event_source_set_enabled(s->watchdog_event_source, SD_EVENT_ONESHOT); + } else { + r = sd_event_add_time( + UNIT(s)->manager->event, + &s->watchdog_event_source, + CLOCK_MONOTONIC, + usec_add(s->watchdog_timestamp.monotonic, s->watchdog_usec), 0, + service_dispatch_watchdog, s); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to add watchdog timer: %m"); + return; + } + + (void) sd_event_source_set_description(s->watchdog_event_source, "service-watchdog"); + + /* Let's process everything else which might be a sign + * of living before we consider a service died. */ + r = sd_event_source_set_priority(s->watchdog_event_source, SD_EVENT_PRIORITY_IDLE); + } + + if (r < 0) + log_unit_warning_errno(UNIT(s), r, "Failed to install watchdog timer: %m"); +} + +static void service_reset_watchdog(Service *s) { + assert(s); + + dual_timestamp_get(&s->watchdog_timestamp); + service_start_watchdog(s); +} + +static void service_fd_store_unlink(ServiceFDStore *fs) { + + if (!fs) + return; + + if (fs->service) { + assert(fs->service->n_fd_store > 0); + LIST_REMOVE(fd_store, fs->service->fd_store, fs); + fs->service->n_fd_store--; + } + + if (fs->event_source) { + sd_event_source_set_enabled(fs->event_source, SD_EVENT_OFF); + sd_event_source_unref(fs->event_source); + } + + free(fs->fdname); + safe_close(fs->fd); + free(fs); +} + +static void service_release_resources(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + if (!s->fd_store && s->stdin_fd < 0 && s->stdout_fd < 0 && s->stderr_fd < 0) + return; + + log_unit_debug(u, "Releasing all resources."); + + s->stdin_fd = safe_close(s->stdin_fd); + s->stdout_fd = safe_close(s->stdout_fd); + s->stderr_fd = safe_close(s->stderr_fd); + + while (s->fd_store) + service_fd_store_unlink(s->fd_store); + + assert(s->n_fd_store == 0); +} + +static void service_done(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + s->pid_file = mfree(s->pid_file); + s->status_text = mfree(s->status_text); + + s->exec_runtime = exec_runtime_unref(s->exec_runtime); + exec_command_free_array(s->exec_command, _SERVICE_EXEC_COMMAND_MAX); + s->control_command = NULL; + s->main_command = NULL; + + exit_status_set_free(&s->restart_prevent_status); + exit_status_set_free(&s->restart_force_status); + exit_status_set_free(&s->success_status); + + /* This will leak a process, but at least no memory or any of + * our resources */ + service_unwatch_main_pid(s); + service_unwatch_control_pid(s); + service_unwatch_pid_file(s); + + if (s->bus_name) { + unit_unwatch_bus_name(u, s->bus_name); + s->bus_name = mfree(s->bus_name); + } + + s->bus_name_owner = mfree(s->bus_name_owner); + + service_close_socket_fd(s); + + unit_ref_unset(&s->accept_socket); + + service_stop_watchdog(s); + + s->timer_event_source = sd_event_source_unref(s->timer_event_source); + + service_release_resources(u); +} + +static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *userdata) { + ServiceFDStore *fs = userdata; + + assert(e); + assert(fs); + + /* If we get either EPOLLHUP or EPOLLERR, it's time to remove this entry from the fd store */ + service_fd_store_unlink(fs); + return 0; +} + +static int service_add_fd_store(Service *s, int fd, const char *name) { + ServiceFDStore *fs; + int r; + + assert(s); + assert(fd >= 0); + + if (s->n_fd_store >= s->n_fd_store_max) + return 0; + + LIST_FOREACH(fd_store, fs, s->fd_store) { + r = same_fd(fs->fd, fd); + if (r < 0) + return r; + if (r > 0) { + /* Already included */ + safe_close(fd); + return 1; + } + } + + fs = new0(ServiceFDStore, 1); + if (!fs) + return -ENOMEM; + + fs->fd = fd; + fs->service = s; + fs->fdname = strdup(name ?: "stored"); + if (!fs->fdname) { + free(fs); + return -ENOMEM; + } + + r = sd_event_add_io(UNIT(s)->manager->event, &fs->event_source, fd, 0, on_fd_store_io, fs); + if (r < 0) { + free(fs->fdname); + free(fs); + return r; + } + + (void) sd_event_source_set_description(fs->event_source, "service-fd-store"); + + LIST_PREPEND(fd_store, s->fd_store, fs); + s->n_fd_store++; + + return 1; +} + +static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name) { + int r; + + assert(s); + + if (fdset_size(fds) <= 0) + return 0; + + while (s->n_fd_store < s->n_fd_store_max) { + _cleanup_close_ int fd = -1; + + fd = fdset_steal_first(fds); + if (fd < 0) + break; + + r = service_add_fd_store(s, fd, name); + if (r < 0) + return log_unit_error_errno(UNIT(s), r, "Couldn't add fd to fd store: %m"); + if (r > 0) { + log_unit_debug(UNIT(s), "Added fd to fd store."); + fd = -1; + } + } + + if (fdset_size(fds) > 0) + log_unit_warning(UNIT(s), "Tried to store more fds than FileDescriptorStoreMax=%u allows, closing remaining.", s->n_fd_store_max); + + return 0; +} + +static int service_arm_timer(Service *s, usec_t usec) { + int r; + + assert(s); + + if (s->timer_event_source) { + r = sd_event_source_set_time(s->timer_event_source, usec); + if (r < 0) + return r; + + return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); + } + + if (usec == USEC_INFINITY) + return 0; + + r = sd_event_add_time( + UNIT(s)->manager->event, + &s->timer_event_source, + CLOCK_MONOTONIC, + usec, 0, + service_dispatch_timer, s); + if (r < 0) + return r; + + (void) sd_event_source_set_description(s->timer_event_source, "service-timer"); + + return 0; +} + +static int service_verify(Service *s) { + assert(s); + + if (UNIT(s)->load_state != UNIT_LOADED) + return 0; + + if (!s->exec_command[SERVICE_EXEC_START] && !s->exec_command[SERVICE_EXEC_STOP]) { + log_unit_error(UNIT(s), "Service lacks both ExecStart= and ExecStop= setting. Refusing."); + return -EINVAL; + } + + if (s->type != SERVICE_ONESHOT && !s->exec_command[SERVICE_EXEC_START]) { + log_unit_error(UNIT(s), "Service has no ExecStart= setting, which is only allowed for Type=oneshot services. Refusing."); + return -EINVAL; + } + + if (!s->remain_after_exit && !s->exec_command[SERVICE_EXEC_START]) { + log_unit_error(UNIT(s), "Service has no ExecStart= setting, which is only allowed for RemainAfterExit=yes services. Refusing."); + return -EINVAL; + } + + if (s->type != SERVICE_ONESHOT && s->exec_command[SERVICE_EXEC_START]->command_next) { + log_unit_error(UNIT(s), "Service has more than one ExecStart= setting, which is only allowed for Type=oneshot services. Refusing."); + return -EINVAL; + } + + if (s->type == SERVICE_ONESHOT && s->restart != SERVICE_RESTART_NO) { + log_unit_error(UNIT(s), "Service has Restart= setting other than no, which isn't allowed for Type=oneshot services. Refusing."); + return -EINVAL; + } + + if (s->type == SERVICE_ONESHOT && !exit_status_set_is_empty(&s->restart_force_status)) { + log_unit_error(UNIT(s), "Service has RestartForceStatus= set, which isn't allowed for Type=oneshot services. Refusing."); + return -EINVAL; + } + + if (s->type == SERVICE_DBUS && !s->bus_name) { + log_unit_error(UNIT(s), "Service is of type D-Bus but no D-Bus service name has been specified. Refusing."); + return -EINVAL; + } + + if (s->bus_name && s->type != SERVICE_DBUS) + log_unit_warning(UNIT(s), "Service has a D-Bus service name specified, but is not of type dbus. Ignoring."); + + if (s->exec_context.pam_name && !(s->kill_context.kill_mode == KILL_CONTROL_GROUP || s->kill_context.kill_mode == KILL_MIXED)) { + log_unit_error(UNIT(s), "Service has PAM enabled. Kill mode must be set to 'control-group' or 'mixed'. Refusing."); + return -EINVAL; + } + + if (s->usb_function_descriptors && !s->usb_function_strings) + log_unit_warning(UNIT(s), "Service has USBFunctionDescriptors= setting, but no USBFunctionStrings=. Ignoring."); + + if (!s->usb_function_descriptors && s->usb_function_strings) + log_unit_warning(UNIT(s), "Service has USBFunctionStrings= setting, but no USBFunctionDescriptors=. Ignoring."); + + if (s->runtime_max_usec != USEC_INFINITY && s->type == SERVICE_ONESHOT) + log_unit_warning(UNIT(s), "MaxRuntimeSec= has no effect in combination with Type=oneshot. Ignoring."); + + return 0; +} + +static int service_add_default_dependencies(Service *s) { + int r; + + assert(s); + + if (!UNIT(s)->default_dependencies) + return 0; + + /* Add a number of automatic dependencies useful for the + * majority of services. */ + + if (MANAGER_IS_SYSTEM(UNIT(s)->manager)) { + /* First, pull in the really early boot stuff, and + * require it, so that we fail if we can't acquire + * it. */ + + r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); + if (r < 0) + return r; + } else { + + /* In the --user instance there's no sysinit.target, + * in that case require basic.target instead. */ + + r = unit_add_dependency_by_name(UNIT(s), UNIT_REQUIRES, SPECIAL_BASIC_TARGET, NULL, true); + if (r < 0) + return r; + } + + /* Second, if the rest of the base system is in the same + * transaction, order us after it, but do not pull it in or + * even require it. */ + r = unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_BASIC_TARGET, NULL, true); + if (r < 0) + return r; + + /* Third, add us in for normal shutdown. */ + return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); +} + +static void service_fix_output(Service *s) { + assert(s); + + /* If nothing has been explicitly configured, patch default + * output in. If input is socket/tty we avoid this however, + * since in that case we want output to default to the same + * place as we read input from. */ + + if (s->exec_context.std_error == EXEC_OUTPUT_INHERIT && + s->exec_context.std_output == EXEC_OUTPUT_INHERIT && + s->exec_context.std_input == EXEC_INPUT_NULL) + s->exec_context.std_error = UNIT(s)->manager->default_std_error; + + if (s->exec_context.std_output == EXEC_OUTPUT_INHERIT && + s->exec_context.std_input == EXEC_INPUT_NULL) + s->exec_context.std_output = UNIT(s)->manager->default_std_output; +} + +static int service_setup_bus_name(Service *s) { + int r; + + assert(s); + + if (!s->bus_name) + return 0; + + if (is_kdbus_available()) { + const char *n; + + n = strjoina(s->bus_name, ".busname"); + r = unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, n, NULL, true); + if (r < 0) + return log_unit_error_errno(UNIT(s), r, "Failed to add dependency to .busname unit: %m"); + + } else { + /* If kdbus is not available, we know the dbus socket is required, hence pull it in, and require it */ + r = unit_add_dependency_by_name(UNIT(s), UNIT_REQUIRES, SPECIAL_DBUS_SOCKET, NULL, true); + if (r < 0) + return log_unit_error_errno(UNIT(s), r, "Failed to add dependency on " SPECIAL_DBUS_SOCKET ": %m"); + } + + /* Regardless if kdbus is used or not, we always want to be ordered against dbus.socket if both are in the transaction. */ + r = unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_DBUS_SOCKET, NULL, true); + if (r < 0) + return log_unit_error_errno(UNIT(s), r, "Failed to add dependency on " SPECIAL_DBUS_SOCKET ": %m"); + + r = unit_watch_bus_name(UNIT(s), s->bus_name); + if (r == -EEXIST) + return log_unit_error_errno(UNIT(s), r, "Two services allocated for the same bus name %s, refusing operation.", s->bus_name); + if (r < 0) + return log_unit_error_errno(UNIT(s), r, "Cannot watch bus name %s: %m", s->bus_name); + + return 0; +} + +static int service_add_extras(Service *s) { + int r; + + assert(s); + + if (s->type == _SERVICE_TYPE_INVALID) { + /* Figure out a type automatically */ + if (s->bus_name) + s->type = SERVICE_DBUS; + else if (s->exec_command[SERVICE_EXEC_START]) + s->type = SERVICE_SIMPLE; + else + s->type = SERVICE_ONESHOT; + } + + /* Oneshot services have disabled start timeout by default */ + if (s->type == SERVICE_ONESHOT && !s->start_timeout_defined) + s->timeout_start_usec = USEC_INFINITY; + + service_fix_output(s); + + r = unit_patch_contexts(UNIT(s)); + if (r < 0) + return r; + + r = unit_add_exec_dependencies(UNIT(s), &s->exec_context); + if (r < 0) + return r; + + r = unit_set_default_slice(UNIT(s)); + if (r < 0) + return r; + + if (s->type == SERVICE_NOTIFY && s->notify_access == NOTIFY_NONE) + s->notify_access = NOTIFY_MAIN; + + if (s->watchdog_usec > 0 && s->notify_access == NOTIFY_NONE) + s->notify_access = NOTIFY_MAIN; + + r = service_add_default_dependencies(s); + if (r < 0) + return r; + + r = service_setup_bus_name(s); + if (r < 0) + return r; + + return 0; +} + +static int service_load(Unit *u) { + Service *s = SERVICE(u); + int r; + + assert(s); + + /* Load a .service file */ + r = unit_load_fragment(u); + if (r < 0) + return r; + + /* Still nothing found? Then let's give up */ + if (u->load_state == UNIT_STUB) + return -ENOENT; + + /* This is a new unit? Then let's add in some extras */ + if (u->load_state == UNIT_LOADED) { + + /* We were able to load something, then let's add in + * the dropin directories. */ + r = unit_load_dropin(u); + if (r < 0) + return r; + + /* This is a new unit? Then let's add in some + * extras */ + r = service_add_extras(s); + if (r < 0) + return r; + } + + return service_verify(s); +} + +static void service_dump(Unit *u, FILE *f, const char *prefix) { + ServiceExecCommand c; + Service *s = SERVICE(u); + const char *prefix2; + + assert(s); + + prefix = strempty(prefix); + prefix2 = strjoina(prefix, "\t"); + + fprintf(f, + "%sService State: %s\n" + "%sResult: %s\n" + "%sReload Result: %s\n" + "%sPermissionsStartOnly: %s\n" + "%sRootDirectoryStartOnly: %s\n" + "%sRemainAfterExit: %s\n" + "%sGuessMainPID: %s\n" + "%sType: %s\n" + "%sRestart: %s\n" + "%sNotifyAccess: %s\n" + "%sNotifyState: %s\n", + prefix, service_state_to_string(s->state), + prefix, service_result_to_string(s->result), + prefix, service_result_to_string(s->reload_result), + prefix, yes_no(s->permissions_start_only), + prefix, yes_no(s->root_directory_start_only), + prefix, yes_no(s->remain_after_exit), + prefix, yes_no(s->guess_main_pid), + prefix, service_type_to_string(s->type), + prefix, service_restart_to_string(s->restart), + prefix, notify_access_to_string(s->notify_access), + prefix, notify_state_to_string(s->notify_state)); + + if (s->control_pid > 0) + fprintf(f, + "%sControl PID: "PID_FMT"\n", + prefix, s->control_pid); + + if (s->main_pid > 0) + fprintf(f, + "%sMain PID: "PID_FMT"\n" + "%sMain PID Known: %s\n" + "%sMain PID Alien: %s\n", + prefix, s->main_pid, + prefix, yes_no(s->main_pid_known), + prefix, yes_no(s->main_pid_alien)); + + if (s->pid_file) + fprintf(f, + "%sPIDFile: %s\n", + prefix, s->pid_file); + + if (s->bus_name) + fprintf(f, + "%sBusName: %s\n" + "%sBus Name Good: %s\n", + prefix, s->bus_name, + prefix, yes_no(s->bus_name_good)); + + kill_context_dump(&s->kill_context, f, prefix); + exec_context_dump(&s->exec_context, f, prefix); + + for (c = 0; c < _SERVICE_EXEC_COMMAND_MAX; c++) { + + if (!s->exec_command[c]) + continue; + + fprintf(f, "%s-> %s:\n", + prefix, service_exec_command_to_string(c)); + + exec_command_dump_list(s->exec_command[c], f, prefix2); + } + + if (s->status_text) + fprintf(f, "%sStatus Text: %s\n", + prefix, s->status_text); + + if (s->n_fd_store_max > 0) + fprintf(f, + "%sFile Descriptor Store Max: %u\n" + "%sFile Descriptor Store Current: %u\n", + prefix, s->n_fd_store_max, + prefix, s->n_fd_store); +} + +static int service_load_pid_file(Service *s, bool may_warn) { + _cleanup_free_ char *k = NULL; + int r; + pid_t pid; + + assert(s); + + if (!s->pid_file) + return -ENOENT; + + r = read_one_line_file(s->pid_file, &k); + if (r < 0) { + if (may_warn) + log_unit_info_errno(UNIT(s), r, "PID file %s not readable (yet?) after %s: %m", s->pid_file, service_state_to_string(s->state)); + return r; + } + + r = parse_pid(k, &pid); + if (r < 0) { + if (may_warn) + log_unit_info_errno(UNIT(s), r, "Failed to read PID from file %s: %m", s->pid_file); + return r; + } + + if (!pid_is_alive(pid)) { + if (may_warn) + log_unit_info(UNIT(s), "PID "PID_FMT" read from file %s does not exist or is a zombie.", pid, s->pid_file); + return -ESRCH; + } + + if (s->main_pid_known) { + if (pid == s->main_pid) + return 0; + + log_unit_debug(UNIT(s), "Main PID changing: "PID_FMT" -> "PID_FMT, s->main_pid, pid); + + service_unwatch_main_pid(s); + s->main_pid_known = false; + } else + log_unit_debug(UNIT(s), "Main PID loaded: "PID_FMT, pid); + + r = service_set_main_pid(s, pid); + if (r < 0) + return r; + + r = unit_watch_pid(UNIT(s), pid); + if (r < 0) { + /* FIXME: we need to do something here */ + log_unit_warning_errno(UNIT(s), r, "Failed to watch PID "PID_FMT" for service: %m", pid); + return r; + } + + return 0; +} + +static void service_search_main_pid(Service *s) { + pid_t pid = 0; + int r; + + assert(s); + + /* If we know it anyway, don't ever fallback to unreliable + * heuristics */ + if (s->main_pid_known) + return; + + if (!s->guess_main_pid) + return; + + assert(s->main_pid <= 0); + + if (unit_search_main_pid(UNIT(s), &pid) < 0) + return; + + log_unit_debug(UNIT(s), "Main PID guessed: "PID_FMT, pid); + if (service_set_main_pid(s, pid) < 0) + return; + + r = unit_watch_pid(UNIT(s), pid); + if (r < 0) + /* FIXME: we need to do something here */ + log_unit_warning_errno(UNIT(s), r, "Failed to watch PID "PID_FMT" from: %m", pid); +} + +static void service_set_state(Service *s, ServiceState state) { + ServiceState old_state; + const UnitActiveState *table; + + assert(s); + + table = s->type == SERVICE_IDLE ? state_translation_table_idle : state_translation_table; + + old_state = s->state; + s->state = state; + + service_unwatch_pid_file(s); + + if (!IN_SET(state, + SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, + SERVICE_RUNNING, + SERVICE_RELOAD, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, + SERVICE_AUTO_RESTART)) + s->timer_event_source = sd_event_source_unref(s->timer_event_source); + + if (!IN_SET(state, + SERVICE_START, SERVICE_START_POST, + SERVICE_RUNNING, SERVICE_RELOAD, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) { + service_unwatch_main_pid(s); + s->main_command = NULL; + } + + if (!IN_SET(state, + SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, + SERVICE_RELOAD, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) { + service_unwatch_control_pid(s); + s->control_command = NULL; + s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; + } + + if (IN_SET(state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART)) + unit_unwatch_all_pids(UNIT(s)); + + if (!IN_SET(state, + SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, + SERVICE_RUNNING, SERVICE_RELOAD, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL) && + !(state == SERVICE_DEAD && UNIT(s)->job)) + service_close_socket_fd(s); + + if (!IN_SET(state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) + service_stop_watchdog(s); + + /* For the inactive states unit_notify() will trim the cgroup, + * but for exit we have to do that ourselves... */ + if (state == SERVICE_EXITED && !MANAGER_IS_RELOADING(UNIT(s)->manager)) + unit_prune_cgroup(UNIT(s)); + + /* For remain_after_exit services, let's see if we can "release" the + * hold on the console, since unit_notify() only does that in case of + * change of state */ + if (state == SERVICE_EXITED && + s->remain_after_exit && + UNIT(s)->manager->n_on_console > 0) { + + ExecContext *ec; + + ec = unit_get_exec_context(UNIT(s)); + if (ec && exec_context_may_touch_console(ec)) { + Manager *m = UNIT(s)->manager; + + m->n_on_console--; + if (m->n_on_console == 0) + /* unset no_console_output flag, since the console is free */ + m->no_console_output = false; + } + } + + if (old_state != state) + log_unit_debug(UNIT(s), "Changed %s -> %s", service_state_to_string(old_state), service_state_to_string(state)); + + unit_notify(UNIT(s), table[old_state], table[state], s->reload_result == SERVICE_SUCCESS); +} + +static usec_t service_coldplug_timeout(Service *s) { + assert(s); + + switch (s->deserialized_state) { + + case SERVICE_START_PRE: + case SERVICE_START: + case SERVICE_START_POST: + case SERVICE_RELOAD: + return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_start_usec); + + case SERVICE_RUNNING: + return usec_add(UNIT(s)->active_enter_timestamp.monotonic, s->runtime_max_usec); + + case SERVICE_STOP: + case SERVICE_STOP_SIGABRT: + case SERVICE_STOP_SIGTERM: + case SERVICE_STOP_SIGKILL: + case SERVICE_STOP_POST: + case SERVICE_FINAL_SIGTERM: + case SERVICE_FINAL_SIGKILL: + return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_stop_usec); + + case SERVICE_AUTO_RESTART: + return usec_add(UNIT(s)->inactive_enter_timestamp.monotonic, s->restart_usec); + + default: + return USEC_INFINITY; + } +} + +static int service_coldplug(Unit *u) { + Service *s = SERVICE(u); + int r; + + assert(s); + assert(s->state == SERVICE_DEAD); + + if (s->deserialized_state == s->state) + return 0; + + r = service_arm_timer(s, service_coldplug_timeout(s)); + if (r < 0) + return r; + + if (s->main_pid > 0 && + pid_is_unwaited(s->main_pid) && + ((s->deserialized_state == SERVICE_START && IN_SET(s->type, SERVICE_FORKING, SERVICE_DBUS, SERVICE_ONESHOT, SERVICE_NOTIFY)) || + IN_SET(s->deserialized_state, + SERVICE_START, SERVICE_START_POST, + SERVICE_RUNNING, SERVICE_RELOAD, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL))) { + r = unit_watch_pid(UNIT(s), s->main_pid); + if (r < 0) + return r; + } + + if (s->control_pid > 0 && + pid_is_unwaited(s->control_pid) && + IN_SET(s->deserialized_state, + SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, + SERVICE_RELOAD, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) { + r = unit_watch_pid(UNIT(s), s->control_pid); + if (r < 0) + return r; + } + + if (!IN_SET(s->deserialized_state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART)) + unit_watch_all_pids(UNIT(s)); + + if (IN_SET(s->deserialized_state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) + service_start_watchdog(s); + + service_set_state(s, s->deserialized_state); + return 0; +} + +static int service_collect_fds(Service *s, int **fds, char ***fd_names) { + _cleanup_strv_free_ char **rfd_names = NULL; + _cleanup_free_ int *rfds = NULL; + int rn_fds = 0, r; + + assert(s); + assert(fds); + assert(fd_names); + + if (s->socket_fd >= 0) { + + /* Pass the per-connection socket */ + + rfds = new(int, 1); + if (!rfds) + return -ENOMEM; + rfds[0] = s->socket_fd; + + rfd_names = strv_new("connection", NULL); + if (!rfd_names) + return -ENOMEM; + + rn_fds = 1; + } else { + Iterator i; + Unit *u; + + /* Pass all our configured sockets for singleton services */ + + SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERED_BY], i) { + _cleanup_free_ int *cfds = NULL; + Socket *sock; + int cn_fds; + + if (u->type != UNIT_SOCKET) + continue; + + sock = SOCKET(u); + + cn_fds = socket_collect_fds(sock, &cfds); + if (cn_fds < 0) + return cn_fds; + + if (cn_fds <= 0) + continue; + + if (!rfds) { + rfds = cfds; + rn_fds = cn_fds; + + cfds = NULL; + } else { + int *t; + + t = realloc(rfds, (rn_fds + cn_fds) * sizeof(int)); + if (!t) + return -ENOMEM; + + memcpy(t + rn_fds, cfds, cn_fds * sizeof(int)); + + rfds = t; + rn_fds += cn_fds; + } + + r = strv_extend_n(&rfd_names, socket_fdname(sock), cn_fds); + if (r < 0) + return r; + } + } + + if (s->n_fd_store > 0) { + ServiceFDStore *fs; + char **nl; + int *t; + + t = realloc(rfds, (rn_fds + s->n_fd_store) * sizeof(int)); + if (!t) + return -ENOMEM; + + rfds = t; + + nl = realloc(rfd_names, (rn_fds + s->n_fd_store + 1) * sizeof(char*)); + if (!nl) + return -ENOMEM; + + rfd_names = nl; + + LIST_FOREACH(fd_store, fs, s->fd_store) { + rfds[rn_fds] = fs->fd; + rfd_names[rn_fds] = strdup(strempty(fs->fdname)); + if (!rfd_names[rn_fds]) + return -ENOMEM; + + rn_fds++; + } + + rfd_names[rn_fds] = NULL; + } + + *fds = rfds; + *fd_names = rfd_names; + + rfds = NULL; + rfd_names = NULL; + + return rn_fds; +} + +static int service_spawn( + Service *s, + ExecCommand *c, + usec_t timeout, + bool pass_fds, + bool apply_permissions, + bool apply_chroot, + bool apply_tty_stdin, + bool is_control, + pid_t *_pid) { + + _cleanup_strv_free_ char **argv = NULL, **final_env = NULL, **our_env = NULL, **fd_names = NULL; + _cleanup_free_ int *fds = NULL; + unsigned n_fds = 0, n_env = 0; + const char *path; + pid_t pid; + + ExecParameters exec_params = { + .apply_permissions = apply_permissions, + .apply_chroot = apply_chroot, + .apply_tty_stdin = apply_tty_stdin, + .stdin_fd = -1, + .stdout_fd = -1, + .stderr_fd = -1, + }; + + int r; + + assert(s); + assert(c); + assert(_pid); + + (void) unit_realize_cgroup(UNIT(s)); + if (s->reset_cpu_usage) { + (void) unit_reset_cpu_usage(UNIT(s)); + s->reset_cpu_usage = false; + } + + r = unit_setup_exec_runtime(UNIT(s)); + if (r < 0) + return r; + + if (pass_fds || + s->exec_context.std_input == EXEC_INPUT_SOCKET || + s->exec_context.std_output == EXEC_OUTPUT_SOCKET || + s->exec_context.std_error == EXEC_OUTPUT_SOCKET) { + + r = service_collect_fds(s, &fds, &fd_names); + if (r < 0) + return r; + + n_fds = r; + } + + r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), timeout)); + if (r < 0) + return r; + + r = unit_full_printf_strv(UNIT(s), c->argv, &argv); + if (r < 0) + return r; + + our_env = new0(char*, 6); + if (!our_env) + return -ENOMEM; + + if (is_control ? s->notify_access == NOTIFY_ALL : s->notify_access != NOTIFY_NONE) + if (asprintf(our_env + n_env++, "NOTIFY_SOCKET=%s", UNIT(s)->manager->notify_socket) < 0) + return -ENOMEM; + + if (s->main_pid > 0) + if (asprintf(our_env + n_env++, "MAINPID="PID_FMT, s->main_pid) < 0) + return -ENOMEM; + + if (!MANAGER_IS_SYSTEM(UNIT(s)->manager)) + if (asprintf(our_env + n_env++, "MANAGERPID="PID_FMT, getpid()) < 0) + return -ENOMEM; + + if (s->socket_fd >= 0) { + union sockaddr_union sa; + socklen_t salen = sizeof(sa); + + r = getpeername(s->socket_fd, &sa.sa, &salen); + if (r < 0) + return -errno; + + if (IN_SET(sa.sa.sa_family, AF_INET, AF_INET6)) { + _cleanup_free_ char *addr = NULL; + char *t; + int port; + + r = sockaddr_pretty(&sa.sa, salen, true, false, &addr); + if (r < 0) + return r; + + t = strappend("REMOTE_ADDR=", addr); + if (!t) + return -ENOMEM; + our_env[n_env++] = t; + + port = sockaddr_port(&sa.sa); + if (port < 0) + return port; + + if (asprintf(&t, "REMOTE_PORT=%u", port) < 0) + return -ENOMEM; + our_env[n_env++] = t; + } + } + + final_env = strv_env_merge(2, UNIT(s)->manager->environment, our_env, NULL); + if (!final_env) + return -ENOMEM; + + if (is_control && UNIT(s)->cgroup_path) { + path = strjoina(UNIT(s)->cgroup_path, "/control"); + (void) cg_create(SYSTEMD_CGROUP_CONTROLLER, path); + } else + path = UNIT(s)->cgroup_path; + + exec_params.argv = argv; + exec_params.fds = fds; + exec_params.fd_names = fd_names; + exec_params.n_fds = n_fds; + exec_params.environment = final_env; + exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn; + exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; + exec_params.cgroup_path = path; + exec_params.cgroup_delegate = s->cgroup_context.delegate; + exec_params.runtime_prefix = manager_get_runtime_prefix(UNIT(s)->manager); + exec_params.watchdog_usec = s->watchdog_usec; + exec_params.selinux_context_net = s->socket_fd_selinux_context_net; + if (s->type == SERVICE_IDLE) + exec_params.idle_pipe = UNIT(s)->manager->idle_pipe; + exec_params.stdin_fd = s->stdin_fd; + exec_params.stdout_fd = s->stdout_fd; + exec_params.stderr_fd = s->stderr_fd; + + r = exec_spawn(UNIT(s), + c, + &s->exec_context, + &exec_params, + s->exec_runtime, + &pid); + if (r < 0) + return r; + + r = unit_watch_pid(UNIT(s), pid); + if (r < 0) + /* FIXME: we need to do something here */ + return r; + + *_pid = pid; + + return 0; +} + +static int main_pid_good(Service *s) { + assert(s); + + /* Returns 0 if the pid is dead, 1 if it is good, -1 if we + * don't know */ + + /* If we know the pid file, then let's just check if it is + * still valid */ + if (s->main_pid_known) { + + /* If it's an alien child let's check if it is still + * alive ... */ + if (s->main_pid_alien && s->main_pid > 0) + return pid_is_alive(s->main_pid); + + /* .. otherwise assume we'll get a SIGCHLD for it, + * which we really should wait for to collect exit + * status and code */ + return s->main_pid > 0; + } + + /* We don't know the pid */ + return -EAGAIN; +} + +_pure_ static int control_pid_good(Service *s) { + assert(s); + + return s->control_pid > 0; +} + +static int cgroup_good(Service *s) { + int r; + + assert(s); + + if (!UNIT(s)->cgroup_path) + return 0; + + r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, UNIT(s)->cgroup_path); + if (r < 0) + return r; + + return !r; +} + +static bool service_shall_restart(Service *s) { + assert(s); + + /* Don't restart after manual stops */ + if (s->forbid_restart) + return false; + + /* Never restart if this is configured as special exception */ + if (exit_status_set_test(&s->restart_prevent_status, s->main_exec_status.code, s->main_exec_status.status)) + return false; + + /* Restart if the exit code/status are configured as restart triggers */ + if (exit_status_set_test(&s->restart_force_status, s->main_exec_status.code, s->main_exec_status.status)) + return true; + + switch (s->restart) { + + case SERVICE_RESTART_NO: + return false; + + case SERVICE_RESTART_ALWAYS: + return true; + + case SERVICE_RESTART_ON_SUCCESS: + return s->result == SERVICE_SUCCESS; + + case SERVICE_RESTART_ON_FAILURE: + return s->result != SERVICE_SUCCESS; + + case SERVICE_RESTART_ON_ABNORMAL: + return !IN_SET(s->result, SERVICE_SUCCESS, SERVICE_FAILURE_EXIT_CODE); + + case SERVICE_RESTART_ON_WATCHDOG: + return s->result == SERVICE_FAILURE_WATCHDOG; + + case SERVICE_RESTART_ON_ABORT: + return IN_SET(s->result, SERVICE_FAILURE_SIGNAL, SERVICE_FAILURE_CORE_DUMP); + + default: + assert_not_reached("unknown restart setting"); + } +} + +static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) { + int r; + assert(s); + + if (f != SERVICE_SUCCESS) + s->result = f; + + service_set_state(s, s->result != SERVICE_SUCCESS ? SERVICE_FAILED : SERVICE_DEAD); + + if (s->result != SERVICE_SUCCESS) { + log_unit_warning(UNIT(s), "Failed with result '%s'.", service_result_to_string(s->result)); + failure_action(UNIT(s)->manager, s->failure_action, UNIT(s)->reboot_arg); + } + + if (allow_restart && service_shall_restart(s)) { + + r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->restart_usec)); + if (r < 0) + goto fail; + + service_set_state(s, SERVICE_AUTO_RESTART); + } + + /* The next restart might not be a manual stop, hence reset the flag indicating manual stops */ + s->forbid_restart = false; + + /* We want fresh tmpdirs in case service is started again immediately */ + exec_runtime_destroy(s->exec_runtime); + s->exec_runtime = exec_runtime_unref(s->exec_runtime); + + /* Also, remove the runtime directory in */ + exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager)); + + /* Try to delete the pid file. At this point it will be + * out-of-date, and some software might be confused by it, so + * let's remove it. */ + if (s->pid_file) + (void) unlink(s->pid_file); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run install restart timer: %m"); + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, false); +} + +static void service_enter_stop_post(Service *s, ServiceResult f) { + int r; + assert(s); + + if (f != SERVICE_SUCCESS) + s->result = f; + + service_unwatch_control_pid(s); + unit_watch_all_pids(UNIT(s)); + + s->control_command = s->exec_command[SERVICE_EXEC_STOP_POST]; + if (s->control_command) { + s->control_command_id = SERVICE_EXEC_STOP_POST; + + r = service_spawn(s, + s->control_command, + s->timeout_stop_usec, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + true, + true, + &s->control_pid); + if (r < 0) + goto fail; + + service_set_state(s, SERVICE_STOP_POST); + } else + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'stop-post' task: %m"); + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); +} + +static int state_to_kill_operation(ServiceState state) { + switch (state) { + + case SERVICE_STOP_SIGABRT: + return KILL_ABORT; + + case SERVICE_STOP_SIGTERM: + case SERVICE_FINAL_SIGTERM: + return KILL_TERMINATE; + + case SERVICE_STOP_SIGKILL: + case SERVICE_FINAL_SIGKILL: + return KILL_KILL; + + default: + return _KILL_OPERATION_INVALID; + } +} + +static void service_enter_signal(Service *s, ServiceState state, ServiceResult f) { + int r; + + assert(s); + + if (f != SERVICE_SUCCESS) + s->result = f; + + unit_watch_all_pids(UNIT(s)); + + r = unit_kill_context( + UNIT(s), + &s->kill_context, + state_to_kill_operation(state), + s->main_pid, + s->control_pid, + s->main_pid_alien); + + if (r < 0) + goto fail; + + if (r > 0) { + r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_stop_usec)); + if (r < 0) + goto fail; + + service_set_state(s, state); + } else if (IN_SET(state, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM) && s->kill_context.send_sigkill) + service_enter_signal(s, SERVICE_STOP_SIGKILL, SERVICE_SUCCESS); + else if (IN_SET(state, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL)) + service_enter_stop_post(s, SERVICE_SUCCESS); + else if (state == SERVICE_FINAL_SIGTERM && s->kill_context.send_sigkill) + service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_SUCCESS); + else + service_enter_dead(s, SERVICE_SUCCESS, true); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); + + if (IN_SET(state, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL)) + service_enter_stop_post(s, SERVICE_FAILURE_RESOURCES); + else + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); +} + +static void service_enter_stop_by_notify(Service *s) { + assert(s); + + unit_watch_all_pids(UNIT(s)); + + service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_stop_usec)); + + /* The service told us it's stopping, so it's as if we SIGTERM'd it. */ + service_set_state(s, SERVICE_STOP_SIGTERM); +} + +static void service_enter_stop(Service *s, ServiceResult f) { + int r; + + assert(s); + + if (f != SERVICE_SUCCESS) + s->result = f; + + service_unwatch_control_pid(s); + unit_watch_all_pids(UNIT(s)); + + s->control_command = s->exec_command[SERVICE_EXEC_STOP]; + if (s->control_command) { + s->control_command_id = SERVICE_EXEC_STOP; + + r = service_spawn(s, + s->control_command, + s->timeout_stop_usec, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + false, + true, + &s->control_pid); + if (r < 0) + goto fail; + + service_set_state(s, SERVICE_STOP); + } else + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'stop' task: %m"); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); +} + +static bool service_good(Service *s) { + int main_pid_ok; + assert(s); + + if (s->type == SERVICE_DBUS && !s->bus_name_good) + return false; + + main_pid_ok = main_pid_good(s); + if (main_pid_ok > 0) /* It's alive */ + return true; + if (main_pid_ok == 0) /* It's dead */ + return false; + + /* OK, we don't know anything about the main PID, maybe + * because there is none. Let's check the control group + * instead. */ + + return cgroup_good(s) != 0; +} + +static void service_enter_running(Service *s, ServiceResult f) { + assert(s); + + if (f != SERVICE_SUCCESS) + s->result = f; + + service_unwatch_control_pid(s); + + if (service_good(s)) { + + /* If there are any queued up sd_notify() + * notifications, process them now */ + if (s->notify_state == NOTIFY_RELOADING) + service_enter_reload_by_notify(s); + else if (s->notify_state == NOTIFY_STOPPING) + service_enter_stop_by_notify(s); + else { + service_set_state(s, SERVICE_RUNNING); + service_arm_timer(s, usec_add(UNIT(s)->active_enter_timestamp.monotonic, s->runtime_max_usec)); + } + + } else if (s->remain_after_exit) + service_set_state(s, SERVICE_EXITED); + else + service_enter_stop(s, SERVICE_SUCCESS); +} + +static void service_enter_start_post(Service *s) { + int r; + assert(s); + + service_unwatch_control_pid(s); + service_reset_watchdog(s); + + s->control_command = s->exec_command[SERVICE_EXEC_START_POST]; + if (s->control_command) { + s->control_command_id = SERVICE_EXEC_START_POST; + + r = service_spawn(s, + s->control_command, + s->timeout_start_usec, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + false, + true, + &s->control_pid); + if (r < 0) + goto fail; + + service_set_state(s, SERVICE_START_POST); + } else + service_enter_running(s, SERVICE_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'start-post' task: %m"); + service_enter_stop(s, SERVICE_FAILURE_RESOURCES); +} + +static void service_kill_control_processes(Service *s) { + char *p; + + if (!UNIT(s)->cgroup_path) + return; + + p = strjoina(UNIT(s)->cgroup_path, "/control"); + cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, p, SIGKILL, true, true, true, NULL); +} + +static void service_enter_start(Service *s) { + ExecCommand *c; + usec_t timeout; + pid_t pid; + int r; + + assert(s); + + service_unwatch_control_pid(s); + service_unwatch_main_pid(s); + + /* We want to ensure that nobody leaks processes from + * START_PRE here, so let's go on a killing spree, People + * should not spawn long running processes from START_PRE. */ + service_kill_control_processes(s); + + if (s->type == SERVICE_FORKING) { + s->control_command_id = SERVICE_EXEC_START; + c = s->control_command = s->exec_command[SERVICE_EXEC_START]; + + s->main_command = NULL; + } else { + s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; + s->control_command = NULL; + + c = s->main_command = s->exec_command[SERVICE_EXEC_START]; + } + + if (!c) { + assert(s->type == SERVICE_ONESHOT); + service_enter_start_post(s); + return; + } + + if (IN_SET(s->type, SERVICE_SIMPLE, SERVICE_IDLE)) + /* For simple + idle this is the main process. We don't apply any timeout here, but + * service_enter_running() will later apply the .runtime_max_usec timeout. */ + timeout = USEC_INFINITY; + else + timeout = s->timeout_start_usec; + + r = service_spawn(s, + c, + timeout, + true, + true, + true, + true, + false, + &pid); + if (r < 0) + goto fail; + + if (IN_SET(s->type, SERVICE_SIMPLE, SERVICE_IDLE)) { + /* For simple services we immediately start + * the START_POST binaries. */ + + service_set_main_pid(s, pid); + service_enter_start_post(s); + + } else if (s->type == SERVICE_FORKING) { + + /* For forking services we wait until the start + * process exited. */ + + s->control_pid = pid; + service_set_state(s, SERVICE_START); + + } else if (IN_SET(s->type, SERVICE_ONESHOT, SERVICE_DBUS, SERVICE_NOTIFY)) { + + /* For oneshot services we wait until the start + * process exited, too, but it is our main process. */ + + /* For D-Bus services we know the main pid right away, + * but wait for the bus name to appear on the + * bus. Notify services are similar. */ + + service_set_main_pid(s, pid); + service_set_state(s, SERVICE_START); + } else + assert_not_reached("Unknown service type"); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'start' task: %m"); + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); +} + +static void service_enter_start_pre(Service *s) { + int r; + + assert(s); + + service_unwatch_control_pid(s); + + s->control_command = s->exec_command[SERVICE_EXEC_START_PRE]; + if (s->control_command) { + /* Before we start anything, let's clear up what might + * be left from previous runs. */ + service_kill_control_processes(s); + + s->control_command_id = SERVICE_EXEC_START_PRE; + + r = service_spawn(s, + s->control_command, + s->timeout_start_usec, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + true, + true, + &s->control_pid); + if (r < 0) + goto fail; + + service_set_state(s, SERVICE_START_PRE); + } else + service_enter_start(s); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'start-pre' task: %m"); + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); +} + +static void service_enter_restart(Service *s) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(s); + + if (UNIT(s)->job && UNIT(s)->job->type == JOB_STOP) { + /* Don't restart things if we are going down anyway */ + log_unit_info(UNIT(s), "Stop job pending for unit, delaying automatic restart."); + + r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->restart_usec)); + if (r < 0) + goto fail; + + return; + } + + /* Any units that are bound to this service must also be + * restarted. We use JOB_RESTART (instead of the more obvious + * JOB_START) here so that those dependency jobs will be added + * as well. */ + r = manager_add_job(UNIT(s)->manager, JOB_RESTART, UNIT(s), JOB_FAIL, &error, NULL); + if (r < 0) + goto fail; + + /* Note that we stay in the SERVICE_AUTO_RESTART state here, + * it will be canceled as part of the service_stop() call that + * is executed as part of JOB_RESTART. */ + + log_unit_debug(UNIT(s), "Scheduled restart job."); + return; + +fail: + log_unit_warning(UNIT(s), "Failed to schedule restart job: %s", bus_error_message(&error, -r)); + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, false); +} + +static void service_enter_reload_by_notify(Service *s) { + assert(s); + + service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_start_usec)); + service_set_state(s, SERVICE_RELOAD); +} + +static void service_enter_reload(Service *s) { + int r; + + assert(s); + + service_unwatch_control_pid(s); + s->reload_result = SERVICE_SUCCESS; + + s->control_command = s->exec_command[SERVICE_EXEC_RELOAD]; + if (s->control_command) { + s->control_command_id = SERVICE_EXEC_RELOAD; + + r = service_spawn(s, + s->control_command, + s->timeout_start_usec, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + false, + true, + &s->control_pid); + if (r < 0) + goto fail; + + service_set_state(s, SERVICE_RELOAD); + } else + service_enter_running(s, SERVICE_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'reload' task: %m"); + s->reload_result = SERVICE_FAILURE_RESOURCES; + service_enter_running(s, SERVICE_SUCCESS); +} + +static void service_run_next_control(Service *s) { + usec_t timeout; + int r; + + assert(s); + assert(s->control_command); + assert(s->control_command->command_next); + + assert(s->control_command_id != SERVICE_EXEC_START); + + s->control_command = s->control_command->command_next; + service_unwatch_control_pid(s); + + if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) + timeout = s->timeout_start_usec; + else + timeout = s->timeout_stop_usec; + + r = service_spawn(s, + s->control_command, + timeout, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + s->control_command_id == SERVICE_EXEC_START_PRE || + s->control_command_id == SERVICE_EXEC_STOP_POST, + true, + &s->control_pid); + if (r < 0) + goto fail; + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run next control task: %m"); + + if (s->state == SERVICE_START_PRE) + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); + else if (s->state == SERVICE_STOP) + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); + else if (s->state == SERVICE_STOP_POST) + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); + else if (s->state == SERVICE_RELOAD) { + s->reload_result = SERVICE_FAILURE_RESOURCES; + service_enter_running(s, SERVICE_SUCCESS); + } else + service_enter_stop(s, SERVICE_FAILURE_RESOURCES); +} + +static void service_run_next_main(Service *s) { + pid_t pid; + int r; + + assert(s); + assert(s->main_command); + assert(s->main_command->command_next); + assert(s->type == SERVICE_ONESHOT); + + s->main_command = s->main_command->command_next; + service_unwatch_main_pid(s); + + r = service_spawn(s, + s->main_command, + s->timeout_start_usec, + true, + true, + true, + true, + false, + &pid); + if (r < 0) + goto fail; + + service_set_main_pid(s, pid); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run next main task: %m"); + service_enter_stop(s, SERVICE_FAILURE_RESOURCES); +} + +static int service_start(Unit *u) { + Service *s = SERVICE(u); + int r; + + assert(s); + + /* We cannot fulfill this request right now, try again later + * please! */ + if (IN_SET(s->state, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) + return -EAGAIN; + + /* Already on it! */ + if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST)) + return 0; + + /* A service that will be restarted must be stopped first to + * trigger BindsTo and/or OnFailure dependencies. If a user + * does not want to wait for the holdoff time to elapse, the + * service should be manually restarted, not started. We + * simply return EAGAIN here, so that any start jobs stay + * queued, and assume that the auto restart timer will + * eventually trigger the restart. */ + if (s->state == SERVICE_AUTO_RESTART) + return -EAGAIN; + + assert(IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED)); + + /* Make sure we don't enter a busy loop of some kind. */ + r = unit_start_limit_test(u); + if (r < 0) { + service_enter_dead(s, SERVICE_FAILURE_START_LIMIT_HIT, false); + return r; + } + + s->result = SERVICE_SUCCESS; + s->reload_result = SERVICE_SUCCESS; + s->main_pid_known = false; + s->main_pid_alien = false; + s->forbid_restart = false; + s->reset_cpu_usage = true; + + s->status_text = mfree(s->status_text); + s->status_errno = 0; + + s->notify_state = NOTIFY_UNKNOWN; + + service_enter_start_pre(s); + return 1; +} + +static int service_stop(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + /* Don't create restart jobs from manual stops. */ + s->forbid_restart = true; + + /* Already on it */ + if (IN_SET(s->state, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) + return 0; + + /* A restart will be scheduled or is in progress. */ + if (s->state == SERVICE_AUTO_RESTART) { + service_set_state(s, SERVICE_DEAD); + return 0; + } + + /* If there's already something running we go directly into + * kill mode. */ + if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD)) { + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS); + return 0; + } + + assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_EXITED)); + + service_enter_stop(s, SERVICE_SUCCESS); + return 1; +} + +static int service_reload(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + assert(s->state == SERVICE_RUNNING || s->state == SERVICE_EXITED); + + service_enter_reload(s); + return 1; +} + +_pure_ static bool service_can_reload(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + return !!s->exec_command[SERVICE_EXEC_RELOAD]; +} + +static int service_serialize(Unit *u, FILE *f, FDSet *fds) { + Service *s = SERVICE(u); + ServiceFDStore *fs; + int r; + + assert(u); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", service_state_to_string(s->state)); + unit_serialize_item(u, f, "result", service_result_to_string(s->result)); + unit_serialize_item(u, f, "reload-result", service_result_to_string(s->reload_result)); + + if (s->control_pid > 0) + unit_serialize_item_format(u, f, "control-pid", PID_FMT, s->control_pid); + + if (s->main_pid_known && s->main_pid > 0) + unit_serialize_item_format(u, f, "main-pid", PID_FMT, s->main_pid); + + unit_serialize_item(u, f, "main-pid-known", yes_no(s->main_pid_known)); + unit_serialize_item(u, f, "bus-name-good", yes_no(s->bus_name_good)); + unit_serialize_item(u, f, "bus-name-owner", s->bus_name_owner); + + r = unit_serialize_item_escaped(u, f, "status-text", s->status_text); + if (r < 0) + return r; + + /* FIXME: There's a minor uncleanliness here: if there are + * multiple commands attached here, we will start from the + * first one again */ + if (s->control_command_id >= 0) + unit_serialize_item(u, f, "control-command", service_exec_command_to_string(s->control_command_id)); + + r = unit_serialize_item_fd(u, f, fds, "stdin-fd", s->stdin_fd); + if (r < 0) + return r; + r = unit_serialize_item_fd(u, f, fds, "stdout-fd", s->stdout_fd); + if (r < 0) + return r; + r = unit_serialize_item_fd(u, f, fds, "stderr-fd", s->stderr_fd); + if (r < 0) + return r; + + r = unit_serialize_item_fd(u, f, fds, "socket-fd", s->socket_fd); + if (r < 0) + return r; + + LIST_FOREACH(fd_store, fs, s->fd_store) { + _cleanup_free_ char *c = NULL; + int copy; + + copy = fdset_put_dup(fds, fs->fd); + if (copy < 0) + return copy; + + c = cescape(fs->fdname); + + unit_serialize_item_format(u, f, "fd-store-fd", "%i %s", copy, strempty(c)); + } + + if (s->main_exec_status.pid > 0) { + unit_serialize_item_format(u, f, "main-exec-status-pid", PID_FMT, s->main_exec_status.pid); + dual_timestamp_serialize(f, "main-exec-status-start", &s->main_exec_status.start_timestamp); + dual_timestamp_serialize(f, "main-exec-status-exit", &s->main_exec_status.exit_timestamp); + + if (dual_timestamp_is_set(&s->main_exec_status.exit_timestamp)) { + unit_serialize_item_format(u, f, "main-exec-status-code", "%i", s->main_exec_status.code); + unit_serialize_item_format(u, f, "main-exec-status-status", "%i", s->main_exec_status.status); + } + } + + dual_timestamp_serialize(f, "watchdog-timestamp", &s->watchdog_timestamp); + + unit_serialize_item(u, f, "forbid-restart", yes_no(s->forbid_restart)); + + return 0; +} + +static int service_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Service *s = SERVICE(u); + int r; + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + ServiceState state; + + state = service_state_from_string(value); + if (state < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + s->deserialized_state = state; + } else if (streq(key, "result")) { + ServiceResult f; + + f = service_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse result value: %s", value); + else if (f != SERVICE_SUCCESS) + s->result = f; + + } else if (streq(key, "reload-result")) { + ServiceResult f; + + f = service_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse reload result value: %s", value); + else if (f != SERVICE_SUCCESS) + s->reload_result = f; + + } else if (streq(key, "control-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_unit_debug(u, "Failed to parse control-pid value: %s", value); + else + s->control_pid = pid; + } else if (streq(key, "main-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_unit_debug(u, "Failed to parse main-pid value: %s", value); + else { + service_set_main_pid(s, pid); + unit_watch_pid(UNIT(s), pid); + } + } else if (streq(key, "main-pid-known")) { + int b; + + b = parse_boolean(value); + if (b < 0) + log_unit_debug(u, "Failed to parse main-pid-known value: %s", value); + else + s->main_pid_known = b; + } else if (streq(key, "bus-name-good")) { + int b; + + b = parse_boolean(value); + if (b < 0) + log_unit_debug(u, "Failed to parse bus-name-good value: %s", value); + else + s->bus_name_good = b; + } else if (streq(key, "bus-name-owner")) { + r = free_and_strdup(&s->bus_name_owner, value); + if (r < 0) + log_unit_error_errno(u, r, "Unable to deserialize current bus owner %s: %m", value); + } else if (streq(key, "status-text")) { + char *t; + + r = cunescape(value, 0, &t); + if (r < 0) + log_unit_debug_errno(u, r, "Failed to unescape status text: %s", value); + else { + free(s->status_text); + s->status_text = t; + } + + } else if (streq(key, "control-command")) { + ServiceExecCommand id; + + id = service_exec_command_from_string(value); + if (id < 0) + log_unit_debug(u, "Failed to parse exec-command value: %s", value); + else { + s->control_command_id = id; + s->control_command = s->exec_command[id]; + } + } else if (streq(key, "socket-fd")) { + int fd; + + if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse socket-fd value: %s", value); + else { + asynchronous_close(s->socket_fd); + s->socket_fd = fdset_remove(fds, fd); + } + } else if (streq(key, "fd-store-fd")) { + const char *fdv; + size_t pf; + int fd; + + pf = strcspn(value, WHITESPACE); + fdv = strndupa(value, pf); + + if (safe_atoi(fdv, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse fd-store-fd value: %s", value); + else { + _cleanup_free_ char *t = NULL; + const char *fdn; + + fdn = value + pf; + fdn += strspn(fdn, WHITESPACE); + (void) cunescape(fdn, 0, &t); + + r = service_add_fd_store(s, fd, t); + if (r < 0) + log_unit_error_errno(u, r, "Failed to add fd to store: %m"); + else if (r > 0) + fdset_remove(fds, fd); + } + + } else if (streq(key, "main-exec-status-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_unit_debug(u, "Failed to parse main-exec-status-pid value: %s", value); + else + s->main_exec_status.pid = pid; + } else if (streq(key, "main-exec-status-code")) { + int i; + + if (safe_atoi(value, &i) < 0) + log_unit_debug(u, "Failed to parse main-exec-status-code value: %s", value); + else + s->main_exec_status.code = i; + } else if (streq(key, "main-exec-status-status")) { + int i; + + if (safe_atoi(value, &i) < 0) + log_unit_debug(u, "Failed to parse main-exec-status-status value: %s", value); + else + s->main_exec_status.status = i; + } else if (streq(key, "main-exec-status-start")) + dual_timestamp_deserialize(value, &s->main_exec_status.start_timestamp); + else if (streq(key, "main-exec-status-exit")) + dual_timestamp_deserialize(value, &s->main_exec_status.exit_timestamp); + else if (streq(key, "watchdog-timestamp")) + dual_timestamp_deserialize(value, &s->watchdog_timestamp); + else if (streq(key, "forbid-restart")) { + int b; + + b = parse_boolean(value); + if (b < 0) + log_unit_debug(u, "Failed to parse forbid-restart value: %s", value); + else + s->forbid_restart = b; + } else if (streq(key, "stdin-fd")) { + int fd; + + if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse stdin-fd value: %s", value); + else { + asynchronous_close(s->stdin_fd); + s->stdin_fd = fdset_remove(fds, fd); + s->exec_context.stdio_as_fds = true; + } + } else if (streq(key, "stdout-fd")) { + int fd; + + if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse stdout-fd value: %s", value); + else { + asynchronous_close(s->stdout_fd); + s->stdout_fd = fdset_remove(fds, fd); + s->exec_context.stdio_as_fds = true; + } + } else if (streq(key, "stderr-fd")) { + int fd; + + if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse stderr-fd value: %s", value); + else { + asynchronous_close(s->stderr_fd); + s->stderr_fd = fdset_remove(fds, fd); + s->exec_context.stdio_as_fds = true; + } + } else + log_unit_debug(u, "Unknown serialization key: %s", key); + + return 0; +} + +_pure_ static UnitActiveState service_active_state(Unit *u) { + const UnitActiveState *table; + + assert(u); + + table = SERVICE(u)->type == SERVICE_IDLE ? state_translation_table_idle : state_translation_table; + + return table[SERVICE(u)->state]; +} + +static const char *service_sub_state_to_string(Unit *u) { + assert(u); + + return service_state_to_string(SERVICE(u)->state); +} + +static bool service_check_gc(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + /* Never clean up services that still have a process around, + * even if the service is formally dead. */ + if (cgroup_good(s) > 0 || + main_pid_good(s) > 0 || + control_pid_good(s) > 0) + return true; + + return false; +} + +static int service_retry_pid_file(Service *s) { + int r; + + assert(s->pid_file); + assert(s->state == SERVICE_START || s->state == SERVICE_START_POST); + + r = service_load_pid_file(s, false); + if (r < 0) + return r; + + service_unwatch_pid_file(s); + + service_enter_running(s, SERVICE_SUCCESS); + return 0; +} + +static int service_watch_pid_file(Service *s) { + int r; + + log_unit_debug(UNIT(s), "Setting watch for PID file %s", s->pid_file_pathspec->path); + + r = path_spec_watch(s->pid_file_pathspec, service_dispatch_io); + if (r < 0) + goto fail; + + /* the pidfile might have appeared just before we set the watch */ + log_unit_debug(UNIT(s), "Trying to read PID file %s in case it changed", s->pid_file_pathspec->path); + service_retry_pid_file(s); + + return 0; +fail: + log_unit_error_errno(UNIT(s), r, "Failed to set a watch for PID file %s: %m", s->pid_file_pathspec->path); + service_unwatch_pid_file(s); + return r; +} + +static int service_demand_pid_file(Service *s) { + PathSpec *ps; + + assert(s->pid_file); + assert(!s->pid_file_pathspec); + + ps = new0(PathSpec, 1); + if (!ps) + return -ENOMEM; + + ps->unit = UNIT(s); + ps->path = strdup(s->pid_file); + if (!ps->path) { + free(ps); + return -ENOMEM; + } + + path_kill_slashes(ps->path); + + /* PATH_CHANGED would not be enough. There are daemons (sendmail) that + * keep their PID file open all the time. */ + ps->type = PATH_MODIFIED; + ps->inotify_fd = -1; + + s->pid_file_pathspec = ps; + + return service_watch_pid_file(s); +} + +static int service_dispatch_io(sd_event_source *source, int fd, uint32_t events, void *userdata) { + PathSpec *p = userdata; + Service *s; + + assert(p); + + s = SERVICE(p->unit); + + assert(s); + assert(fd >= 0); + assert(s->state == SERVICE_START || s->state == SERVICE_START_POST); + assert(s->pid_file_pathspec); + assert(path_spec_owns_inotify_fd(s->pid_file_pathspec, fd)); + + log_unit_debug(UNIT(s), "inotify event"); + + if (path_spec_fd_event(p, events) < 0) + goto fail; + + if (service_retry_pid_file(s) == 0) + return 0; + + if (service_watch_pid_file(s) < 0) + goto fail; + + return 0; + +fail: + service_unwatch_pid_file(s); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); + return 0; +} + +static void service_notify_cgroup_empty_event(Unit *u) { + Service *s = SERVICE(u); + + assert(u); + + log_unit_debug(u, "cgroup is empty"); + + switch (s->state) { + + /* Waiting for SIGCHLD is usually more interesting, + * because it includes return codes/signals. Which is + * why we ignore the cgroup events for most cases, + * except when we don't know pid which to expect the + * SIGCHLD for. */ + + case SERVICE_START: + case SERVICE_START_POST: + /* If we were hoping for the daemon to write its PID file, + * we can give up now. */ + if (s->pid_file_pathspec) { + log_unit_warning(u, "Daemon never wrote its PID file. Failing."); + + service_unwatch_pid_file(s); + if (s->state == SERVICE_START) + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); + else + service_enter_stop(s, SERVICE_FAILURE_RESOURCES); + } + break; + + case SERVICE_RUNNING: + /* service_enter_running() will figure out what to do */ + service_enter_running(s, SERVICE_SUCCESS); + break; + + case SERVICE_STOP_SIGABRT: + case SERVICE_STOP_SIGTERM: + case SERVICE_STOP_SIGKILL: + + if (main_pid_good(s) <= 0 && !control_pid_good(s)) + service_enter_stop_post(s, SERVICE_SUCCESS); + + break; + + case SERVICE_STOP_POST: + case SERVICE_FINAL_SIGTERM: + case SERVICE_FINAL_SIGKILL: + if (main_pid_good(s) <= 0 && !control_pid_good(s)) + service_enter_dead(s, SERVICE_SUCCESS, true); + + break; + + default: + ; + } +} + +static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { + Service *s = SERVICE(u); + ServiceResult f; + + assert(s); + assert(pid >= 0); + + if (UNIT(s)->fragment_path ? is_clean_exit(code, status, &s->success_status) : + is_clean_exit_lsb(code, status, &s->success_status)) + f = SERVICE_SUCCESS; + else if (code == CLD_EXITED) + f = SERVICE_FAILURE_EXIT_CODE; + else if (code == CLD_KILLED) + f = SERVICE_FAILURE_SIGNAL; + else if (code == CLD_DUMPED) + f = SERVICE_FAILURE_CORE_DUMP; + else + assert_not_reached("Unknown code"); + + if (s->main_pid == pid) { + /* Forking services may occasionally move to a new PID. + * As long as they update the PID file before exiting the old + * PID, they're fine. */ + if (service_load_pid_file(s, false) == 0) + return; + + s->main_pid = 0; + exec_status_exit(&s->main_exec_status, &s->exec_context, pid, code, status); + + if (s->main_command) { + /* If this is not a forking service than the + * main process got started and hence we copy + * the exit status so that it is recorded both + * as main and as control process exit + * status */ + + s->main_command->exec_status = s->main_exec_status; + + if (s->main_command->ignore) + f = SERVICE_SUCCESS; + } else if (s->exec_command[SERVICE_EXEC_START]) { + + /* If this is a forked process, then we should + * ignore the return value if this was + * configured for the starter process */ + + if (s->exec_command[SERVICE_EXEC_START]->ignore) + f = SERVICE_SUCCESS; + } + + log_struct(f == SERVICE_SUCCESS ? LOG_DEBUG : LOG_NOTICE, + LOG_UNIT_ID(u), + LOG_UNIT_MESSAGE(u, "Main process exited, code=%s, status=%i/%s", + sigchld_code_to_string(code), status, + strna(code == CLD_EXITED + ? exit_status_to_string(status, EXIT_STATUS_FULL) + : signal_to_string(status))), + "EXIT_CODE=%s", sigchld_code_to_string(code), + "EXIT_STATUS=%i", status, + NULL); + + if (f != SERVICE_SUCCESS) + s->result = f; + + if (s->main_command && + s->main_command->command_next && + f == SERVICE_SUCCESS) { + + /* There is another command to * + * execute, so let's do that. */ + + log_unit_debug(u, "Running next main command for state %s.", service_state_to_string(s->state)); + service_run_next_main(s); + + } else { + + /* The service exited, so the service is officially + * gone. */ + s->main_command = NULL; + + switch (s->state) { + + case SERVICE_START_POST: + case SERVICE_RELOAD: + case SERVICE_STOP: + /* Need to wait until the operation is + * done */ + break; + + case SERVICE_START: + if (s->type == SERVICE_ONESHOT) { + /* This was our main goal, so let's go on */ + if (f == SERVICE_SUCCESS) + service_enter_start_post(s); + else + service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); + break; + } + + /* Fall through */ + + case SERVICE_RUNNING: + service_enter_running(s, f); + break; + + case SERVICE_STOP_SIGABRT: + case SERVICE_STOP_SIGTERM: + case SERVICE_STOP_SIGKILL: + + if (!control_pid_good(s)) + service_enter_stop_post(s, f); + + /* If there is still a control process, wait for that first */ + break; + + case SERVICE_STOP_POST: + case SERVICE_FINAL_SIGTERM: + case SERVICE_FINAL_SIGKILL: + + if (!control_pid_good(s)) + service_enter_dead(s, f, true); + break; + + default: + assert_not_reached("Uh, main process died at wrong time."); + } + } + + } else if (s->control_pid == pid) { + s->control_pid = 0; + + if (s->control_command) { + exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status); + + if (s->control_command->ignore) + f = SERVICE_SUCCESS; + } + + log_unit_full(u, f == SERVICE_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, + "Control process exited, code=%s status=%i", + sigchld_code_to_string(code), status); + + if (f != SERVICE_SUCCESS) + s->result = f; + + /* Immediately get rid of the cgroup, so that the + * kernel doesn't delay the cgroup empty messages for + * the service cgroup any longer than necessary */ + service_kill_control_processes(s); + + if (s->control_command && + s->control_command->command_next && + f == SERVICE_SUCCESS) { + + /* There is another command to * + * execute, so let's do that. */ + + log_unit_debug(u, "Running next control command for state %s.", service_state_to_string(s->state)); + service_run_next_control(s); + + } else { + /* No further commands for this step, so let's + * figure out what to do next */ + + s->control_command = NULL; + s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; + + log_unit_debug(u, "Got final SIGCHLD for state %s.", service_state_to_string(s->state)); + + switch (s->state) { + + case SERVICE_START_PRE: + if (f == SERVICE_SUCCESS) + service_enter_start(s); + else + service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); + break; + + case SERVICE_START: + if (s->type != SERVICE_FORKING) + /* Maybe spurious event due to a reload that changed the type? */ + break; + + if (f != SERVICE_SUCCESS) { + service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); + break; + } + + if (s->pid_file) { + bool has_start_post; + int r; + + /* Let's try to load the pid file here if we can. + * The PID file might actually be created by a START_POST + * script. In that case don't worry if the loading fails. */ + + has_start_post = !!s->exec_command[SERVICE_EXEC_START_POST]; + r = service_load_pid_file(s, !has_start_post); + if (!has_start_post && r < 0) { + r = service_demand_pid_file(s); + if (r < 0 || !cgroup_good(s)) + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); + break; + } + } else + service_search_main_pid(s); + + service_enter_start_post(s); + break; + + case SERVICE_START_POST: + if (f != SERVICE_SUCCESS) { + service_enter_signal(s, SERVICE_STOP_SIGTERM, f); + break; + } + + if (s->pid_file) { + int r; + + r = service_load_pid_file(s, true); + if (r < 0) { + r = service_demand_pid_file(s); + if (r < 0 || !cgroup_good(s)) + service_enter_stop(s, SERVICE_FAILURE_RESOURCES); + break; + } + } else + service_search_main_pid(s); + + service_enter_running(s, SERVICE_SUCCESS); + break; + + case SERVICE_RELOAD: + if (f == SERVICE_SUCCESS) + if (service_load_pid_file(s, true) < 0) + service_search_main_pid(s); + + s->reload_result = f; + service_enter_running(s, SERVICE_SUCCESS); + break; + + case SERVICE_STOP: + service_enter_signal(s, SERVICE_STOP_SIGTERM, f); + break; + + case SERVICE_STOP_SIGABRT: + case SERVICE_STOP_SIGTERM: + case SERVICE_STOP_SIGKILL: + if (main_pid_good(s) <= 0) + service_enter_stop_post(s, f); + + /* If there is still a service + * process around, wait until + * that one quit, too */ + break; + + case SERVICE_STOP_POST: + case SERVICE_FINAL_SIGTERM: + case SERVICE_FINAL_SIGKILL: + if (main_pid_good(s) <= 0) + service_enter_dead(s, f, true); + break; + + default: + assert_not_reached("Uh, control process died at wrong time."); + } + } + } + + /* Notify clients about changed exit status */ + unit_add_to_dbus_queue(u); + + /* We got one SIGCHLD for the service, let's watch all + * processes that are now running of the service, and watch + * that. Among the PIDs we then watch will be children + * reassigned to us, which hopefully allows us to identify + * when all children are gone */ + unit_tidy_watch_pids(u, s->main_pid, s->control_pid); + unit_watch_all_pids(u); + + /* If the PID set is empty now, then let's finish this off */ + if (set_isempty(u->pids)) + service_notify_cgroup_empty_event(u); +} + +static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { + Service *s = SERVICE(userdata); + + assert(s); + assert(source == s->timer_event_source); + + switch (s->state) { + + case SERVICE_START_PRE: + case SERVICE_START: + log_unit_warning(UNIT(s), "%s operation timed out. Terminating.", s->state == SERVICE_START ? "Start" : "Start-pre"); + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_START_POST: + log_unit_warning(UNIT(s), "Start-post operation timed out. Stopping."); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_RUNNING: + log_unit_warning(UNIT(s), "Service reached runtime time limit. Stopping."); + service_enter_stop(s, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_RELOAD: + log_unit_warning(UNIT(s), "Reload operation timed out. Killing reload process."); + service_kill_control_processes(s); + s->reload_result = SERVICE_FAILURE_TIMEOUT; + service_enter_running(s, SERVICE_SUCCESS); + break; + + case SERVICE_STOP: + log_unit_warning(UNIT(s), "Stopping timed out. Terminating."); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_STOP_SIGABRT: + log_unit_warning(UNIT(s), "State 'stop-sigabrt' timed out. Terminating."); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_STOP_SIGTERM: + if (s->kill_context.send_sigkill) { + log_unit_warning(UNIT(s), "State 'stop-sigterm' timed out. Killing."); + service_enter_signal(s, SERVICE_STOP_SIGKILL, SERVICE_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(s), "State 'stop-sigterm' timed out. Skipping SIGKILL."); + service_enter_stop_post(s, SERVICE_FAILURE_TIMEOUT); + } + + break; + + case SERVICE_STOP_SIGKILL: + /* Uh, we sent a SIGKILL and it is still not gone? + * Must be something we cannot kill, so let's just be + * weirded out and continue */ + + log_unit_warning(UNIT(s), "Processes still around after SIGKILL. Ignoring."); + service_enter_stop_post(s, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_STOP_POST: + log_unit_warning(UNIT(s), "State 'stop-post' timed out. Terminating."); + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_FINAL_SIGTERM: + if (s->kill_context.send_sigkill) { + log_unit_warning(UNIT(s), "State 'stop-final-sigterm' timed out. Killing."); + service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(s), "State 'stop-final-sigterm' timed out. Skipping SIGKILL. Entering failed mode."); + service_enter_dead(s, SERVICE_FAILURE_TIMEOUT, false); + } + + break; + + case SERVICE_FINAL_SIGKILL: + log_unit_warning(UNIT(s), "Processes still around after final SIGKILL. Entering failed mode."); + service_enter_dead(s, SERVICE_FAILURE_TIMEOUT, true); + break; + + case SERVICE_AUTO_RESTART: + log_unit_info(UNIT(s), + s->restart_usec > 0 ? + "Service hold-off time over, scheduling restart." : + "Service has no hold-off time, scheduling restart."); + service_enter_restart(s); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } + + return 0; +} + +static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void *userdata) { + Service *s = SERVICE(userdata); + char t[FORMAT_TIMESPAN_MAX]; + + assert(s); + assert(source == s->watchdog_event_source); + + log_unit_error(UNIT(s), "Watchdog timeout (limit %s)!", + format_timespan(t, sizeof(t), s->watchdog_usec, 1)); + + service_enter_signal(s, SERVICE_STOP_SIGABRT, SERVICE_FAILURE_WATCHDOG); + + return 0; +} + +static void service_notify_message(Unit *u, pid_t pid, char **tags, FDSet *fds) { + Service *s = SERVICE(u); + _cleanup_free_ char *cc = NULL; + bool notify_dbus = false; + const char *e; + + assert(u); + + cc = strv_join(tags, ", "); + + if (s->notify_access == NOTIFY_NONE) { + log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception is disabled.", pid); + return; + } else if (s->notify_access == NOTIFY_MAIN && pid != s->main_pid) { + if (s->main_pid != 0) + log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT, pid, s->main_pid); + else + log_unit_debug(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID which is currently not known", pid); + return; + } else + log_unit_debug(u, "Got notification message from PID "PID_FMT" (%s)", pid, isempty(cc) ? "n/a" : cc); + + /* Interpret MAINPID= */ + e = strv_find_startswith(tags, "MAINPID="); + if (e && IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) { + if (parse_pid(e, &pid) < 0) + log_unit_warning(u, "Failed to parse MAINPID= field in notification message: %s", e); + else { + service_set_main_pid(s, pid); + unit_watch_pid(UNIT(s), pid); + notify_dbus = true; + } + } + + /* Interpret RELOADING= */ + if (strv_find(tags, "RELOADING=1")) { + + s->notify_state = NOTIFY_RELOADING; + + if (s->state == SERVICE_RUNNING) + service_enter_reload_by_notify(s); + + notify_dbus = true; + } + + /* Interpret READY= */ + if (strv_find(tags, "READY=1")) { + + s->notify_state = NOTIFY_READY; + + /* Type=notify services inform us about completed + * initialization with READY=1 */ + if (s->type == SERVICE_NOTIFY && s->state == SERVICE_START) + service_enter_start_post(s); + + /* Sending READY=1 while we are reloading informs us + * that the reloading is complete */ + if (s->state == SERVICE_RELOAD && s->control_pid == 0) + service_enter_running(s, SERVICE_SUCCESS); + + notify_dbus = true; + } + + /* Interpret STOPPING= */ + if (strv_find(tags, "STOPPING=1")) { + + s->notify_state = NOTIFY_STOPPING; + + if (s->state == SERVICE_RUNNING) + service_enter_stop_by_notify(s); + + notify_dbus = true; + } + + /* Interpret STATUS= */ + e = strv_find_startswith(tags, "STATUS="); + if (e) { + _cleanup_free_ char *t = NULL; + + if (!isempty(e)) { + if (!utf8_is_valid(e)) + log_unit_warning(u, "Status message in notification message is not UTF-8 clean."); + else { + t = strdup(e); + if (!t) + log_oom(); + } + } + + if (!streq_ptr(s->status_text, t)) { + + free(s->status_text); + s->status_text = t; + t = NULL; + + notify_dbus = true; + } + } + + /* Interpret ERRNO= */ + e = strv_find_startswith(tags, "ERRNO="); + if (e) { + int status_errno; + + if (safe_atoi(e, &status_errno) < 0 || status_errno < 0) + log_unit_warning(u, "Failed to parse ERRNO= field in notification message: %s", e); + else { + if (s->status_errno != status_errno) { + s->status_errno = status_errno; + notify_dbus = true; + } + } + } + + /* Interpret WATCHDOG= */ + if (strv_find(tags, "WATCHDOG=1")) + service_reset_watchdog(s); + + if (strv_find(tags, "FDSTORE=1")) { + const char *name; + + name = strv_find_startswith(tags, "FDNAME="); + if (name && !fdname_is_valid(name)) { + log_unit_warning(u, "Passed FDNAME= name is invalid, ignoring."); + name = NULL; + } + + service_add_fd_store_set(s, fds, name); + } + + /* Notify clients about changed status or main pid */ + if (notify_dbus) + unit_add_to_dbus_queue(u); +} + +static int service_get_timeout(Unit *u, usec_t *timeout) { + Service *s = SERVICE(u); + uint64_t t; + int r; + + if (!s->timer_event_source) + return 0; + + r = sd_event_source_get_time(s->timer_event_source, &t); + if (r < 0) + return r; + if (t == USEC_INFINITY) + return 0; + + *timeout = t; + return 1; +} + +static void service_bus_name_owner_change( + Unit *u, + const char *name, + const char *old_owner, + const char *new_owner) { + + Service *s = SERVICE(u); + int r; + + assert(s); + assert(name); + + assert(streq(s->bus_name, name)); + assert(old_owner || new_owner); + + if (old_owner && new_owner) + log_unit_debug(u, "D-Bus name %s changed owner from %s to %s", name, old_owner, new_owner); + else if (old_owner) + log_unit_debug(u, "D-Bus name %s no longer registered by %s", name, old_owner); + else + log_unit_debug(u, "D-Bus name %s now registered by %s", name, new_owner); + + s->bus_name_good = !!new_owner; + + /* Track the current owner, so we can reconstruct changes after a daemon reload */ + r = free_and_strdup(&s->bus_name_owner, new_owner); + if (r < 0) { + log_unit_error_errno(u, r, "Unable to set new bus name owner %s: %m", new_owner); + return; + } + + if (s->type == SERVICE_DBUS) { + + /* service_enter_running() will figure out what to + * do */ + if (s->state == SERVICE_RUNNING) + service_enter_running(s, SERVICE_SUCCESS); + else if (s->state == SERVICE_START && new_owner) + service_enter_start_post(s); + + } else if (new_owner && + s->main_pid <= 0 && + (s->state == SERVICE_START || + s->state == SERVICE_START_POST || + s->state == SERVICE_RUNNING || + s->state == SERVICE_RELOAD)) { + + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + pid_t pid; + + /* Try to acquire PID from bus service */ + + r = sd_bus_get_name_creds(u->manager->api_bus, name, SD_BUS_CREDS_PID, &creds); + if (r >= 0) + r = sd_bus_creds_get_pid(creds, &pid); + if (r >= 0) { + log_unit_debug(u, "D-Bus name %s is now owned by process %u", name, (unsigned) pid); + + service_set_main_pid(s, pid); + unit_watch_pid(UNIT(s), pid); + } + } +} + +int service_set_socket_fd(Service *s, int fd, Socket *sock, bool selinux_context_net) { + _cleanup_free_ char *peer = NULL; + int r; + + assert(s); + assert(fd >= 0); + + /* This is called by the socket code when instantiating a new service for a stream socket and the socket needs + * to be configured. We take ownership of the passed fd on success. */ + + if (UNIT(s)->load_state != UNIT_LOADED) + return -EINVAL; + + if (s->socket_fd >= 0) + return -EBUSY; + + if (s->state != SERVICE_DEAD) + return -EAGAIN; + + if (getpeername_pretty(fd, true, &peer) >= 0) { + + if (UNIT(s)->description) { + _cleanup_free_ char *a; + + a = strjoin(UNIT(s)->description, " (", peer, ")", NULL); + if (!a) + return -ENOMEM; + + r = unit_set_description(UNIT(s), a); + } else + r = unit_set_description(UNIT(s), peer); + + if (r < 0) + return r; + } + + r = unit_add_two_dependencies(UNIT(sock), UNIT_BEFORE, UNIT_TRIGGERS, UNIT(s), false); + if (r < 0) + return r; + + s->socket_fd = fd; + s->socket_fd_selinux_context_net = selinux_context_net; + + unit_ref_set(&s->accept_socket, UNIT(sock)); + return 0; +} + +static void service_reset_failed(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + if (s->state == SERVICE_FAILED) + service_set_state(s, SERVICE_DEAD); + + s->result = SERVICE_SUCCESS; + s->reload_result = SERVICE_SUCCESS; +} + +static int service_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { + Service *s = SERVICE(u); + + return unit_kill_common(u, who, signo, s->main_pid, s->control_pid, error); +} + +static int service_main_pid(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + return s->main_pid; +} + +static int service_control_pid(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + return s->control_pid; +} + +static const char* const service_restart_table[_SERVICE_RESTART_MAX] = { + [SERVICE_RESTART_NO] = "no", + [SERVICE_RESTART_ON_SUCCESS] = "on-success", + [SERVICE_RESTART_ON_FAILURE] = "on-failure", + [SERVICE_RESTART_ON_ABNORMAL] = "on-abnormal", + [SERVICE_RESTART_ON_WATCHDOG] = "on-watchdog", + [SERVICE_RESTART_ON_ABORT] = "on-abort", + [SERVICE_RESTART_ALWAYS] = "always", +}; + +DEFINE_STRING_TABLE_LOOKUP(service_restart, ServiceRestart); + +static const char* const service_type_table[_SERVICE_TYPE_MAX] = { + [SERVICE_SIMPLE] = "simple", + [SERVICE_FORKING] = "forking", + [SERVICE_ONESHOT] = "oneshot", + [SERVICE_DBUS] = "dbus", + [SERVICE_NOTIFY] = "notify", + [SERVICE_IDLE] = "idle" +}; + +DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType); + +static const char* const service_exec_command_table[_SERVICE_EXEC_COMMAND_MAX] = { + [SERVICE_EXEC_START_PRE] = "ExecStartPre", + [SERVICE_EXEC_START] = "ExecStart", + [SERVICE_EXEC_START_POST] = "ExecStartPost", + [SERVICE_EXEC_RELOAD] = "ExecReload", + [SERVICE_EXEC_STOP] = "ExecStop", + [SERVICE_EXEC_STOP_POST] = "ExecStopPost", +}; + +DEFINE_STRING_TABLE_LOOKUP(service_exec_command, ServiceExecCommand); + +static const char* const notify_access_table[_NOTIFY_ACCESS_MAX] = { + [NOTIFY_NONE] = "none", + [NOTIFY_MAIN] = "main", + [NOTIFY_ALL] = "all" +}; + +DEFINE_STRING_TABLE_LOOKUP(notify_access, NotifyAccess); + +static const char* const notify_state_table[_NOTIFY_STATE_MAX] = { + [NOTIFY_UNKNOWN] = "unknown", + [NOTIFY_READY] = "ready", + [NOTIFY_RELOADING] = "reloading", + [NOTIFY_STOPPING] = "stopping", +}; + +DEFINE_STRING_TABLE_LOOKUP(notify_state, NotifyState); + +static const char* const service_result_table[_SERVICE_RESULT_MAX] = { + [SERVICE_SUCCESS] = "success", + [SERVICE_FAILURE_RESOURCES] = "resources", + [SERVICE_FAILURE_TIMEOUT] = "timeout", + [SERVICE_FAILURE_EXIT_CODE] = "exit-code", + [SERVICE_FAILURE_SIGNAL] = "signal", + [SERVICE_FAILURE_CORE_DUMP] = "core-dump", + [SERVICE_FAILURE_WATCHDOG] = "watchdog", + [SERVICE_FAILURE_START_LIMIT_HIT] = "start-limit-hit", +}; + +DEFINE_STRING_TABLE_LOOKUP(service_result, ServiceResult); + +const UnitVTable service_vtable = { + .object_size = sizeof(Service), + .exec_context_offset = offsetof(Service, exec_context), + .cgroup_context_offset = offsetof(Service, cgroup_context), + .kill_context_offset = offsetof(Service, kill_context), + .exec_runtime_offset = offsetof(Service, exec_runtime), + + .sections = + "Unit\0" + "Service\0" + "Install\0", + .private_section = "Service", + + .init = service_init, + .done = service_done, + .load = service_load, + .release_resources = service_release_resources, + + .coldplug = service_coldplug, + + .dump = service_dump, + + .start = service_start, + .stop = service_stop, + .reload = service_reload, + + .can_reload = service_can_reload, + + .kill = service_kill, + + .serialize = service_serialize, + .deserialize_item = service_deserialize_item, + + .active_state = service_active_state, + .sub_state_to_string = service_sub_state_to_string, + + .check_gc = service_check_gc, + + .sigchld_event = service_sigchld_event, + + .reset_failed = service_reset_failed, + + .notify_cgroup_empty = service_notify_cgroup_empty_event, + .notify_message = service_notify_message, + + .main_pid = service_main_pid, + .control_pid = service_control_pid, + + .bus_name_owner_change = service_bus_name_owner_change, + + .bus_vtable = bus_service_vtable, + .bus_set_property = bus_service_set_property, + .bus_commit_properties = bus_service_commit_properties, + + .get_timeout = service_get_timeout, + .can_transient = true, + + .status_message_formats = { + .starting_stopping = { + [0] = "Starting %s...", + [1] = "Stopping %s...", + }, + .finished_start_job = { + [JOB_DONE] = "Started %s.", + [JOB_FAILED] = "Failed to start %s.", + }, + .finished_stop_job = { + [JOB_DONE] = "Stopped %s.", + [JOB_FAILED] = "Stopped (with error) %s.", + }, + }, +}; diff --git a/src/libcore/service.h b/src/libcore/service.h new file mode 100644 index 0000000000..4af3d40439 --- /dev/null +++ b/src/libcore/service.h @@ -0,0 +1,220 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct Service Service; +typedef struct ServiceFDStore ServiceFDStore; + +#include "exit-status.h" +#include "kill.h" +#include "path.h" +#include "ratelimit.h" + +typedef enum ServiceRestart { + SERVICE_RESTART_NO, + SERVICE_RESTART_ON_SUCCESS, + SERVICE_RESTART_ON_FAILURE, + SERVICE_RESTART_ON_ABNORMAL, + SERVICE_RESTART_ON_WATCHDOG, + SERVICE_RESTART_ON_ABORT, + SERVICE_RESTART_ALWAYS, + _SERVICE_RESTART_MAX, + _SERVICE_RESTART_INVALID = -1 +} ServiceRestart; + +typedef enum ServiceType { + SERVICE_SIMPLE, /* we fork and go on right-away (i.e. modern socket activated daemons) */ + SERVICE_FORKING, /* forks by itself (i.e. traditional daemons) */ + SERVICE_ONESHOT, /* we fork and wait until the program finishes (i.e. programs like fsck which run and need to finish before we continue) */ + SERVICE_DBUS, /* we fork and wait until a specific D-Bus name appears on the bus */ + SERVICE_NOTIFY, /* we fork and wait until a daemon sends us a ready message with sd_notify() */ + SERVICE_IDLE, /* much like simple, but delay exec() until all jobs are dispatched. */ + _SERVICE_TYPE_MAX, + _SERVICE_TYPE_INVALID = -1 +} ServiceType; + +typedef enum ServiceExecCommand { + SERVICE_EXEC_START_PRE, + SERVICE_EXEC_START, + SERVICE_EXEC_START_POST, + SERVICE_EXEC_RELOAD, + SERVICE_EXEC_STOP, + SERVICE_EXEC_STOP_POST, + _SERVICE_EXEC_COMMAND_MAX, + _SERVICE_EXEC_COMMAND_INVALID = -1 +} ServiceExecCommand; + +typedef enum NotifyAccess { + NOTIFY_NONE, + NOTIFY_ALL, + NOTIFY_MAIN, + _NOTIFY_ACCESS_MAX, + _NOTIFY_ACCESS_INVALID = -1 +} NotifyAccess; + +typedef enum NotifyState { + NOTIFY_UNKNOWN, + NOTIFY_READY, + NOTIFY_RELOADING, + NOTIFY_STOPPING, + _NOTIFY_STATE_MAX, + _NOTIFY_STATE_INVALID = -1 +} NotifyState; + +typedef enum ServiceResult { + SERVICE_SUCCESS, + SERVICE_FAILURE_RESOURCES, /* a bit of a misnomer, just our catch-all error for errnos we didn't expect */ + SERVICE_FAILURE_TIMEOUT, + SERVICE_FAILURE_EXIT_CODE, + SERVICE_FAILURE_SIGNAL, + SERVICE_FAILURE_CORE_DUMP, + SERVICE_FAILURE_WATCHDOG, + SERVICE_FAILURE_START_LIMIT_HIT, + _SERVICE_RESULT_MAX, + _SERVICE_RESULT_INVALID = -1 +} ServiceResult; + +struct ServiceFDStore { + Service *service; + + int fd; + char *fdname; + sd_event_source *event_source; + + LIST_FIELDS(ServiceFDStore, fd_store); +}; + +struct Service { + Unit meta; + + ServiceType type; + ServiceRestart restart; + ExitStatusSet restart_prevent_status; + ExitStatusSet restart_force_status; + ExitStatusSet success_status; + + /* If set we'll read the main daemon PID from this file */ + char *pid_file; + + usec_t restart_usec; + usec_t timeout_start_usec; + usec_t timeout_stop_usec; + usec_t runtime_max_usec; + + dual_timestamp watchdog_timestamp; + usec_t watchdog_usec; + sd_event_source *watchdog_event_source; + + ExecCommand* exec_command[_SERVICE_EXEC_COMMAND_MAX]; + + ExecContext exec_context; + KillContext kill_context; + CGroupContext cgroup_context; + + ServiceState state, deserialized_state; + + /* The exit status of the real main process */ + ExecStatus main_exec_status; + + /* The currently executed control process */ + ExecCommand *control_command; + + /* The currently executed main process, which may be NULL if + * the main process got started via forking mode and not by + * us */ + ExecCommand *main_command; + + /* The ID of the control command currently being executed */ + ServiceExecCommand control_command_id; + + /* Runtime data of the execution context */ + ExecRuntime *exec_runtime; + + pid_t main_pid, control_pid; + int socket_fd; + bool socket_fd_selinux_context_net; + + bool permissions_start_only; + bool root_directory_start_only; + bool remain_after_exit; + bool guess_main_pid; + + /* If we shut down, remember why */ + ServiceResult result; + ServiceResult reload_result; + + bool main_pid_known:1; + bool main_pid_alien:1; + bool bus_name_good:1; + bool forbid_restart:1; + bool start_timeout_defined:1; + + bool reset_cpu_usage:1; + + char *bus_name; + char *bus_name_owner; /* unique name of the current owner */ + + char *status_text; + int status_errno; + + FailureAction failure_action; + + UnitRef accept_socket; + + sd_event_source *timer_event_source; + PathSpec *pid_file_pathspec; + + NotifyAccess notify_access; + NotifyState notify_state; + + ServiceFDStore *fd_store; + unsigned n_fd_store; + unsigned n_fd_store_max; + + char *usb_function_descriptors; + char *usb_function_strings; + + int stdin_fd; + int stdout_fd; + int stderr_fd; +}; + +extern const UnitVTable service_vtable; + +int service_set_socket_fd(Service *s, int fd, struct Socket *socket, bool selinux_context_net); +void service_close_socket_fd(Service *s); + +const char* service_restart_to_string(ServiceRestart i) _const_; +ServiceRestart service_restart_from_string(const char *s) _pure_; + +const char* service_type_to_string(ServiceType i) _const_; +ServiceType service_type_from_string(const char *s) _pure_; + +const char* service_exec_command_to_string(ServiceExecCommand i) _const_; +ServiceExecCommand service_exec_command_from_string(const char *s) _pure_; + +const char* notify_access_to_string(NotifyAccess i) _const_; +NotifyAccess notify_access_from_string(const char *s) _pure_; + +const char* notify_state_to_string(NotifyState i) _const_; +NotifyState notify_state_from_string(const char *s) _pure_; + +const char* service_result_to_string(ServiceResult i) _const_; +ServiceResult service_result_from_string(const char *s) _pure_; diff --git a/src/libcore/show-status.c b/src/libcore/show-status.c new file mode 100644 index 0000000000..59ebdc7219 --- /dev/null +++ b/src/libcore/show-status.c @@ -0,0 +1,124 @@ +/*** + 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 "alloc-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "parse-util.h" +#include "show-status.h" +#include "string-util.h" +#include "terminal-util.h" +#include "util.h" + +int parse_show_status(const char *v, ShowStatus *ret) { + int r; + + assert(v); + assert(ret); + + if (streq(v, "auto")) { + *ret = SHOW_STATUS_AUTO; + return 0; + } + + r = parse_boolean(v); + if (r < 0) + return r; + + *ret = r ? SHOW_STATUS_YES : SHOW_STATUS_NO; + return 0; +} + +int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) { + static const char status_indent[] = " "; /* "[" STATUS "] " */ + _cleanup_free_ char *s = NULL; + _cleanup_close_ int fd = -1; + struct iovec iovec[6] = {}; + int n = 0; + static bool prev_ephemeral; + + assert(format); + + /* This is independent of logging, as status messages are + * optional and go exclusively to the console. */ + + if (vasprintf(&s, format, ap) < 0) + return log_oom(); + + fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + if (ellipse) { + char *e; + size_t emax, sl; + int c; + + c = fd_columns(fd); + if (c <= 0) + c = 80; + + sl = status ? sizeof(status_indent)-1 : 0; + + emax = c - sl - 1; + if (emax < 3) + emax = 3; + + e = ellipsize(s, emax, 50); + if (e) { + free(s); + s = e; + } + } + + if (prev_ephemeral) + IOVEC_SET_STRING(iovec[n++], "\r" ANSI_ERASE_TO_END_OF_LINE); + prev_ephemeral = ephemeral; + + if (status) { + if (!isempty(status)) { + IOVEC_SET_STRING(iovec[n++], "["); + IOVEC_SET_STRING(iovec[n++], status); + IOVEC_SET_STRING(iovec[n++], "] "); + } else + IOVEC_SET_STRING(iovec[n++], status_indent); + } + + IOVEC_SET_STRING(iovec[n++], s); + if (!ephemeral) + IOVEC_SET_STRING(iovec[n++], "\n"); + + if (writev(fd, iovec, n) < 0) + return -errno; + + return 0; +} + +int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) { + va_list ap; + int r; + + assert(format); + + va_start(ap, format); + r = status_vprintf(status, ellipse, ephemeral, format, ap); + va_end(ap); + + return r; +} diff --git a/src/libcore/show-status.h b/src/libcore/show-status.h new file mode 100644 index 0000000000..9a29e72645 --- /dev/null +++ b/src/libcore/show-status.h @@ -0,0 +1,39 @@ +#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 + +#include "macro.h" + +/* Manager status */ + +typedef enum ShowStatus { + _SHOW_STATUS_UNSET = -2, + SHOW_STATUS_AUTO = -1, + SHOW_STATUS_NO = 0, + SHOW_STATUS_YES = 1, + SHOW_STATUS_TEMPORARY = 2, +} ShowStatus; + +int parse_show_status(const char *v, ShowStatus *ret); + +int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) _printf_(4,0); +int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) _printf_(4,5); diff --git a/src/libcore/shutdown.c b/src/libcore/shutdown.c new file mode 100644 index 0000000000..e14755d84e --- /dev/null +++ b/src/libcore/shutdown.c @@ -0,0 +1,440 @@ +/*** + This file is part of systemd. + + Copyright 2010 ProFUSION embedded systems + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "cgroup-util.h" +#include "def.h" +#include "fileio.h" +#include "killall.h" +#include "log.h" +#include "missing.h" +#include "parse-util.h" +#include "process-util.h" +#include "string-util.h" +#include "switch-root.h" +#include "terminal-util.h" +#include "umount.h" +#include "util.h" +#include "virt.h" +#include "watchdog.h" + +#define FINALIZE_ATTEMPTS 50 + +static char* arg_verb; +static uint8_t arg_exit_code; + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_LOG_LEVEL = 0x100, + ARG_LOG_TARGET, + ARG_LOG_COLOR, + ARG_LOG_LOCATION, + ARG_EXIT_CODE, + }; + + static const struct option options[] = { + { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + { "log-target", required_argument, NULL, ARG_LOG_TARGET }, + { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, + { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, + { "exit-code", required_argument, NULL, ARG_EXIT_CODE }, + {} + }; + + int c, r; + + assert(argc >= 1); + assert(argv); + + /* "-" prevents getopt from permuting argv[] and moving the verb away + * from argv[1]. Our interface to initrd promises it'll be there. */ + while ((c = getopt_long(argc, argv, "-", options, NULL)) >= 0) + switch (c) { + + case ARG_LOG_LEVEL: + r = log_set_max_level_from_string(optarg); + if (r < 0) + log_error("Failed to parse log level %s, ignoring.", optarg); + + break; + + case ARG_LOG_TARGET: + r = log_set_target_from_string(optarg); + if (r < 0) + log_error("Failed to parse log target %s, ignoring", optarg); + + break; + + case ARG_LOG_COLOR: + + if (optarg) { + r = log_show_color_from_string(optarg); + if (r < 0) + log_error("Failed to parse log color setting %s, ignoring", optarg); + } else + log_show_color(true); + + break; + + case ARG_LOG_LOCATION: + if (optarg) { + r = log_show_location_from_string(optarg); + if (r < 0) + log_error("Failed to parse log location setting %s, ignoring", optarg); + } else + log_show_location(true); + + break; + + case ARG_EXIT_CODE: + r = safe_atou8(optarg, &arg_exit_code); + if (r < 0) + log_error("Failed to parse exit code %s, ignoring", optarg); + + break; + + case '\001': + if (!arg_verb) + arg_verb = optarg; + else + log_error("Excess arguments, ignoring"); + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option code."); + } + + if (!arg_verb) { + log_error("Verb argument missing."); + return -EINVAL; + } + + return 0; +} + +static int switch_root_initramfs(void) { + if (mount("/run/initramfs", "/run/initramfs", NULL, MS_BIND, NULL) < 0) + return log_error_errno(errno, "Failed to mount bind /run/initramfs on /run/initramfs: %m"); + + if (mount(NULL, "/run/initramfs", NULL, MS_PRIVATE, NULL) < 0) + return log_error_errno(errno, "Failed to make /run/initramfs private mount: %m"); + + /* switch_root with MS_BIND, because there might still be processes lurking around, which have open file descriptors. + * /run/initramfs/shutdown will take care of these. + * Also do not detach the old root, because /run/initramfs/shutdown needs to access it. + */ + return switch_root("/run/initramfs", "/oldroot", false, MS_BIND); +} + + +int main(int argc, char *argv[]) { + bool need_umount, need_swapoff, need_loop_detach, need_dm_detach; + bool in_container, use_watchdog = false; + _cleanup_free_ char *cgroup = NULL; + char *arguments[3]; + unsigned retries; + int cmd, r; + static const char* const dirs[] = {SYSTEM_SHUTDOWN_PATH, NULL}; + + log_parse_environment(); + r = parse_argv(argc, argv); + if (r < 0) + goto error; + + /* journald will die if not gone yet. The log target defaults + * to console, but may have been changed by command line options. */ + + log_close_console(); /* force reopen of /dev/console */ + log_open(); + + umask(0022); + + if (getpid() != 1) { + log_error("Not executed by init (PID 1)."); + r = -EPERM; + goto error; + } + + if (streq(arg_verb, "reboot")) + cmd = RB_AUTOBOOT; + else if (streq(arg_verb, "poweroff")) + cmd = RB_POWER_OFF; + else if (streq(arg_verb, "halt")) + cmd = RB_HALT_SYSTEM; + else if (streq(arg_verb, "kexec")) + cmd = LINUX_REBOOT_CMD_KEXEC; + else if (streq(arg_verb, "exit")) + cmd = 0; /* ignored, just checking that arg_verb is valid */ + else { + r = -EINVAL; + log_error("Unknown action '%s'.", arg_verb); + goto error; + } + + (void) cg_get_root_path(&cgroup); + + use_watchdog = !!getenv("WATCHDOG_USEC"); + + /* lock us into memory */ + mlockall(MCL_CURRENT|MCL_FUTURE); + + log_info("Sending SIGTERM to remaining processes..."); + broadcast_signal(SIGTERM, true, true); + + log_info("Sending SIGKILL to remaining processes..."); + broadcast_signal(SIGKILL, true, false); + + in_container = detect_container() > 0; + + need_umount = !in_container; + need_swapoff = !in_container; + need_loop_detach = !in_container; + need_dm_detach = !in_container; + + /* Unmount all mountpoints, swaps, and loopback devices */ + for (retries = 0; retries < FINALIZE_ATTEMPTS; retries++) { + bool changed = false; + + if (use_watchdog) + watchdog_ping(); + + /* Let's trim the cgroup tree on each iteration so + that we leave an empty cgroup tree around, so that + container managers get a nice notify event when we + are down */ + if (cgroup) + cg_trim(SYSTEMD_CGROUP_CONTROLLER, cgroup, false); + + if (need_umount) { + log_info("Unmounting file systems."); + r = umount_all(&changed); + if (r == 0) { + need_umount = false; + log_info("All filesystems unmounted."); + } else if (r > 0) + log_info("Not all file systems unmounted, %d left.", r); + else + log_error_errno(r, "Failed to unmount file systems: %m"); + } + + if (need_swapoff) { + log_info("Deactivating swaps."); + r = swapoff_all(&changed); + if (r == 0) { + need_swapoff = false; + log_info("All swaps deactivated."); + } else if (r > 0) + log_info("Not all swaps deactivated, %d left.", r); + else + log_error_errno(r, "Failed to deactivate swaps: %m"); + } + + if (need_loop_detach) { + log_info("Detaching loop devices."); + r = loopback_detach_all(&changed); + if (r == 0) { + need_loop_detach = false; + log_info("All loop devices detached."); + } else if (r > 0) + log_info("Not all loop devices detached, %d left.", r); + else + log_error_errno(r, "Failed to detach loop devices: %m"); + } + + if (need_dm_detach) { + log_info("Detaching DM devices."); + r = dm_detach_all(&changed); + if (r == 0) { + need_dm_detach = false; + log_info("All DM devices detached."); + } else if (r > 0) + log_info("Not all DM devices detached, %d left.", r); + else + log_error_errno(r, "Failed to detach DM devices: %m"); + } + + if (!need_umount && !need_swapoff && !need_loop_detach && !need_dm_detach) { + if (retries > 0) + log_info("All filesystems, swaps, loop devices, DM devices detached."); + /* Yay, done */ + goto initrd_jump; + } + + /* If in this iteration we didn't manage to + * unmount/deactivate anything, we simply give up */ + if (!changed) { + log_info("Cannot finalize remaining%s%s%s%s continuing.", + need_umount ? " file systems," : "", + need_swapoff ? " swap devices," : "", + need_loop_detach ? " loop devices," : "", + need_dm_detach ? " DM devices," : ""); + goto initrd_jump; + } + + log_debug("After %u retries, couldn't finalize remaining %s%s%s%s trying again.", + retries + 1, + need_umount ? " file systems," : "", + need_swapoff ? " swap devices," : "", + need_loop_detach ? " loop devices," : "", + need_dm_detach ? " DM devices," : ""); + } + + log_error("Too many iterations, giving up."); + + initrd_jump: + + arguments[0] = NULL; + arguments[1] = arg_verb; + arguments[2] = NULL; + execute_directories(dirs, DEFAULT_TIMEOUT_USEC, arguments); + + if (!in_container && !in_initrd() && + access("/run/initramfs/shutdown", X_OK) == 0) { + r = switch_root_initramfs(); + if (r >= 0) { + argv[0] = (char*) "/shutdown"; + + setsid(); + make_console_stdio(); + + log_info("Successfully changed into root pivot.\n" + "Returning to initrd..."); + + execv("/shutdown", argv); + log_error_errno(errno, "Failed to execute shutdown binary: %m"); + } else + log_error_errno(r, "Failed to switch root to \"/run/initramfs\": %m"); + + } + + if (need_umount || need_swapoff || need_loop_detach || need_dm_detach) + log_error("Failed to finalize %s%s%s%s ignoring", + need_umount ? " file systems," : "", + need_swapoff ? " swap devices," : "", + need_loop_detach ? " loop devices," : "", + need_dm_detach ? " DM devices," : ""); + + /* The kernel will automaticall flush ATA disks and suchlike + * on reboot(), but the file systems need to be synce'd + * explicitly in advance. So let's do this here, but not + * needlessly slow down containers. */ + if (!in_container) + sync(); + + if (streq(arg_verb, "exit")) { + if (in_container) + exit(arg_exit_code); + else { + /* We cannot exit() on the host, fallback on another + * method. */ + cmd = RB_POWER_OFF; + } + } + + switch (cmd) { + + case LINUX_REBOOT_CMD_KEXEC: + + if (!in_container) { + /* We cheat and exec kexec to avoid doing all its work */ + pid_t pid; + + log_info("Rebooting with kexec."); + + pid = fork(); + if (pid < 0) + log_error_errno(errno, "Failed to fork: %m"); + else if (pid == 0) { + + const char * const args[] = { + KEXEC, "-e", NULL + }; + + /* Child */ + + execv(args[0], (char * const *) args); + _exit(EXIT_FAILURE); + } else + wait_for_terminate_and_warn("kexec", pid, true); + } + + cmd = RB_AUTOBOOT; + /* Fall through */ + + case RB_AUTOBOOT: + + if (!in_container) { + _cleanup_free_ char *param = NULL; + + r = read_one_line_file("/run/systemd/reboot-param", ¶m); + if (r < 0) + log_warning_errno(r, "Failed to read reboot parameter file: %m"); + + if (!isempty(param)) { + log_info("Rebooting with argument '%s'.", param); + syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, param); + log_warning_errno(errno, "Failed to reboot with parameter, retrying without: %m"); + } + } + + log_info("Rebooting."); + break; + + case RB_POWER_OFF: + log_info("Powering off."); + break; + + case RB_HALT_SYSTEM: + log_info("Halting system."); + break; + + default: + assert_not_reached("Unknown magic"); + } + + reboot(cmd); + if (errno == EPERM && in_container) { + /* If we are in a container, and we lacked + * CAP_SYS_BOOT just exit, this will kill our + * container for good. */ + log_info("Exiting container."); + exit(0); + } + + r = log_error_errno(errno, "Failed to invoke reboot(): %m"); + + error: + log_emergency_errno(r, "Critical error while doing system shutdown: %m"); + freeze(); +} diff --git a/src/libcore/slice.c b/src/libcore/slice.c new file mode 100644 index 0000000000..c7700b8857 --- /dev/null +++ b/src/libcore/slice.c @@ -0,0 +1,346 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "alloc-util.h" +#include "dbus-slice.h" +#include "log.h" +#include "slice.h" +#include "special.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "unit.h" + +static const UnitActiveState state_translation_table[_SLICE_STATE_MAX] = { + [SLICE_DEAD] = UNIT_INACTIVE, + [SLICE_ACTIVE] = UNIT_ACTIVE +}; + +static void slice_init(Unit *u) { + assert(u); + assert(u->load_state == UNIT_STUB); + + u->ignore_on_isolate = true; +} + +static void slice_set_state(Slice *t, SliceState state) { + SliceState old_state; + assert(t); + + old_state = t->state; + t->state = state; + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(t)->id, + slice_state_to_string(old_state), + slice_state_to_string(state)); + + unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], true); +} + +static int slice_add_parent_slice(Slice *s) { + char *a, *dash; + Unit *parent; + int r; + + assert(s); + + if (UNIT_ISSET(UNIT(s)->slice)) + return 0; + + if (unit_has_name(UNIT(s), SPECIAL_ROOT_SLICE)) + return 0; + + a = strdupa(UNIT(s)->id); + dash = strrchr(a, '-'); + if (dash) + strcpy(dash, ".slice"); + else + a = (char*) SPECIAL_ROOT_SLICE; + + r = manager_load_unit(UNIT(s)->manager, a, NULL, NULL, &parent); + if (r < 0) + return r; + + unit_ref_set(&UNIT(s)->slice, parent); + return 0; +} + +static int slice_add_default_dependencies(Slice *s) { + int r; + + assert(s); + + if (!UNIT(s)->default_dependencies) + return 0; + + /* Make sure slices are unloaded on shutdown */ + r = unit_add_two_dependencies_by_name( + UNIT(s), + UNIT_BEFORE, UNIT_CONFLICTS, + SPECIAL_SHUTDOWN_TARGET, NULL, true); + if (r < 0) + return r; + + return 0; +} + +static int slice_verify(Slice *s) { + _cleanup_free_ char *parent = NULL; + int r; + + assert(s); + + if (UNIT(s)->load_state != UNIT_LOADED) + return 0; + + if (!slice_name_is_valid(UNIT(s)->id)) { + log_unit_error(UNIT(s), "Slice name %s is not valid. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + r = slice_build_parent_slice(UNIT(s)->id, &parent); + if (r < 0) + return log_unit_error_errno(UNIT(s), r, "Failed to determine parent slice: %m"); + + if (parent ? !unit_has_name(UNIT_DEREF(UNIT(s)->slice), parent) : UNIT_ISSET(UNIT(s)->slice)) { + log_unit_error(UNIT(s), "Located outside of parent slice. Refusing."); + return -EINVAL; + } + + return 0; +} + +static int slice_load(Unit *u) { + Slice *s = SLICE(u); + int r; + + assert(s); + assert(u->load_state == UNIT_STUB); + + r = unit_load_fragment_and_dropin_optional(u); + if (r < 0) + return r; + + /* This is a new unit? Then let's add in some extras */ + if (u->load_state == UNIT_LOADED) { + + r = unit_patch_contexts(u); + if (r < 0) + return r; + + r = slice_add_parent_slice(s); + if (r < 0) + return r; + + r = slice_add_default_dependencies(s); + if (r < 0) + return r; + } + + return slice_verify(s); +} + +static int slice_coldplug(Unit *u) { + Slice *t = SLICE(u); + + assert(t); + assert(t->state == SLICE_DEAD); + + if (t->deserialized_state != t->state) + slice_set_state(t, t->deserialized_state); + + return 0; +} + +static void slice_dump(Unit *u, FILE *f, const char *prefix) { + Slice *t = SLICE(u); + + assert(t); + assert(f); + + fprintf(f, + "%sSlice State: %s\n", + prefix, slice_state_to_string(t->state)); + + cgroup_context_dump(&t->cgroup_context, f, prefix); +} + +static int slice_start(Unit *u) { + Slice *t = SLICE(u); + + assert(t); + assert(t->state == SLICE_DEAD); + + (void) unit_realize_cgroup(u); + (void) unit_reset_cpu_usage(u); + + slice_set_state(t, SLICE_ACTIVE); + return 1; +} + +static int slice_stop(Unit *u) { + Slice *t = SLICE(u); + + assert(t); + assert(t->state == SLICE_ACTIVE); + + /* We do not need to destroy the cgroup explicitly, + * unit_notify() will do that for us anyway. */ + + slice_set_state(t, SLICE_DEAD); + return 1; +} + +static int slice_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { + return unit_kill_common(u, who, signo, -1, -1, error); +} + +static int slice_serialize(Unit *u, FILE *f, FDSet *fds) { + Slice *s = SLICE(u); + + assert(s); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", slice_state_to_string(s->state)); + return 0; +} + +static int slice_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Slice *s = SLICE(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + SliceState state; + + state = slice_state_from_string(value); + if (state < 0) + log_debug("Failed to parse state value %s", value); + else + s->deserialized_state = state; + + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +_pure_ static UnitActiveState slice_active_state(Unit *u) { + assert(u); + + return state_translation_table[SLICE(u)->state]; +} + +_pure_ static const char *slice_sub_state_to_string(Unit *u) { + assert(u); + + return slice_state_to_string(SLICE(u)->state); +} + +static void slice_enumerate(Manager *m) { + Unit *u; + int r; + + assert(m); + + u = manager_get_unit(m, SPECIAL_ROOT_SLICE); + if (!u) { + u = unit_new(m, sizeof(Slice)); + if (!u) { + log_oom(); + return; + } + + r = unit_add_name(u, SPECIAL_ROOT_SLICE); + if (r < 0) { + unit_free(u); + log_error_errno(r, "Failed to add -.slice name"); + return; + } + } + + u->default_dependencies = false; + u->no_gc = true; + u->ignore_on_isolate = true; + u->refuse_manual_start = true; + u->refuse_manual_stop = true; + SLICE(u)->deserialized_state = SLICE_ACTIVE; + + if (!u->description) + u->description = strdup("Root Slice"); + if (!u->documentation) + (void) strv_extend(&u->documentation, "man:systemd.special(7)"); + + unit_add_to_load_queue(u); + unit_add_to_dbus_queue(u); +} + +const UnitVTable slice_vtable = { + .object_size = sizeof(Slice), + .cgroup_context_offset = offsetof(Slice, cgroup_context), + + .sections = + "Unit\0" + "Slice\0" + "Install\0", + .private_section = "Slice", + + .can_transient = true, + + .init = slice_init, + .load = slice_load, + + .coldplug = slice_coldplug, + + .dump = slice_dump, + + .start = slice_start, + .stop = slice_stop, + + .kill = slice_kill, + + .serialize = slice_serialize, + .deserialize_item = slice_deserialize_item, + + .active_state = slice_active_state, + .sub_state_to_string = slice_sub_state_to_string, + + .bus_vtable = bus_slice_vtable, + .bus_set_property = bus_slice_set_property, + .bus_commit_properties = bus_slice_commit_properties, + + .enumerate = slice_enumerate, + + .status_message_formats = { + .finished_start_job = { + [JOB_DONE] = "Created slice %s.", + }, + .finished_stop_job = { + [JOB_DONE] = "Removed slice %s.", + }, + }, +}; diff --git a/src/libcore/slice.h b/src/libcore/slice.h new file mode 100644 index 0000000000..c9f3f61067 --- /dev/null +++ b/src/libcore/slice.h @@ -0,0 +1,32 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +typedef struct Slice Slice; + +struct Slice { + Unit meta; + + SliceState state, deserialized_state; + + CGroupContext cgroup_context; +}; + +extern const UnitVTable slice_vtable; diff --git a/src/libcore/smack-setup.c b/src/libcore/smack-setup.c new file mode 100644 index 0000000000..5a6d11cfa1 --- /dev/null +++ b/src/libcore/smack-setup.c @@ -0,0 +1,346 @@ +/*** + This file is part of systemd. + + Copyright (C) 2013 Intel Corporation + Authors: + Nathaniel Chen + + 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 +#include +#include +#include +#include + +#include "alloc-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "log.h" +#include "macro.h" +#include "smack-setup.h" +#include "string-util.h" +#include "util.h" + +#ifdef HAVE_SMACK + +static int write_access2_rules(const char* srcdir) { + _cleanup_close_ int load2_fd = -1, change_fd = -1; + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *entry; + char buf[NAME_MAX]; + int dfd = -1; + int r = 0; + + load2_fd = open("/sys/fs/smackfs/load2", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (load2_fd < 0) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to open '/sys/fs/smackfs/load2': %m"); + return -errno; /* negative error */ + } + + change_fd = open("/sys/fs/smackfs/change-rule", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (change_fd < 0) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to open '/sys/fs/smackfs/change-rule': %m"); + return -errno; /* negative error */ + } + + /* write rules to load2 or change-rule from every file in the directory */ + dir = opendir(srcdir); + if (!dir) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to opendir '%s': %m", srcdir); + return errno; /* positive on purpose */ + } + + dfd = dirfd(dir); + assert(dfd >= 0); + + FOREACH_DIRENT(entry, dir, return 0) { + int fd; + _cleanup_fclose_ FILE *policy = NULL; + + if (!dirent_is_file(entry)) + continue; + + fd = openat(dfd, entry->d_name, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + if (r == 0) + r = -errno; + log_warning_errno(errno, "Failed to open '%s': %m", entry->d_name); + continue; + } + + policy = fdopen(fd, "re"); + if (!policy) { + if (r == 0) + r = -errno; + safe_close(fd); + log_error_errno(errno, "Failed to open '%s': %m", entry->d_name); + continue; + } + + /* load2 write rules in the kernel require a line buffered stream */ + FOREACH_LINE(buf, policy, + log_error_errno(errno, "Failed to read line from '%s': %m", + entry->d_name)) { + + _cleanup_free_ char *sbj = NULL, *obj = NULL, *acc1 = NULL, *acc2 = NULL; + + if (isempty(truncate_nl(buf))) + continue; + + /* if 3 args -> load rule : subject object access1 */ + /* if 4 args -> change rule : subject object access1 access2 */ + if (sscanf(buf, "%ms %ms %ms %ms", &sbj, &obj, &acc1, &acc2) < 3) { + log_error_errno(errno, "Failed to parse rule '%s' in '%s', ignoring.", buf, entry->d_name); + continue; + } + + if (write(isempty(acc2) ? load2_fd : change_fd, buf, strlen(buf)) < 0) { + if (r == 0) + r = -errno; + log_error_errno(errno, "Failed to write '%s' to '%s' in '%s'", + buf, isempty(acc2) ? "/sys/fs/smackfs/load2" : "/sys/fs/smackfs/change-rule", entry->d_name); + } + } + } + + return r; +} + +static int write_cipso2_rules(const char* srcdir) { + _cleanup_close_ int cipso2_fd = -1; + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *entry; + char buf[NAME_MAX]; + int dfd = -1; + int r = 0; + + cipso2_fd = open("/sys/fs/smackfs/cipso2", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (cipso2_fd < 0) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to open '/sys/fs/smackfs/cipso2': %m"); + return -errno; /* negative error */ + } + + /* write rules to cipso2 from every file in the directory */ + dir = opendir(srcdir); + if (!dir) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to opendir '%s': %m", srcdir); + return errno; /* positive on purpose */ + } + + dfd = dirfd(dir); + assert(dfd >= 0); + + FOREACH_DIRENT(entry, dir, return 0) { + int fd; + _cleanup_fclose_ FILE *policy = NULL; + + if (!dirent_is_file(entry)) + continue; + + fd = openat(dfd, entry->d_name, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + if (r == 0) + r = -errno; + log_error_errno(errno, "Failed to open '%s': %m", entry->d_name); + continue; + } + + policy = fdopen(fd, "re"); + if (!policy) { + if (r == 0) + r = -errno; + safe_close(fd); + log_error_errno(errno, "Failed to open '%s': %m", entry->d_name); + continue; + } + + /* cipso2 write rules in the kernel require a line buffered stream */ + FOREACH_LINE(buf, policy, + log_error_errno(errno, "Failed to read line from '%s': %m", + entry->d_name)) { + + if (isempty(truncate_nl(buf))) + continue; + + if (write(cipso2_fd, buf, strlen(buf)) < 0) { + if (r == 0) + r = -errno; + log_error_errno(errno, "Failed to write '%s' to '/sys/fs/smackfs/cipso2' in '%s'", + buf, entry->d_name); + break; + } + } + } + + return r; +} + +static int write_netlabel_rules(const char* srcdir) { + _cleanup_fclose_ FILE *dst = NULL; + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *entry; + char buf[NAME_MAX]; + int dfd = -1; + int r = 0; + + dst = fopen("/sys/fs/smackfs/netlabel", "we"); + if (!dst) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to open /sys/fs/smackfs/netlabel: %m"); + return -errno; /* negative error */ + } + + /* write rules to dst from every file in the directory */ + dir = opendir(srcdir); + if (!dir) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to opendir %s: %m", srcdir); + return errno; /* positive on purpose */ + } + + dfd = dirfd(dir); + assert(dfd >= 0); + + FOREACH_DIRENT(entry, dir, return 0) { + int fd; + _cleanup_fclose_ FILE *policy = NULL; + + fd = openat(dfd, entry->d_name, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + if (r == 0) + r = -errno; + log_warning_errno(errno, "Failed to open %s: %m", entry->d_name); + continue; + } + + policy = fdopen(fd, "re"); + if (!policy) { + if (r == 0) + r = -errno; + safe_close(fd); + log_error_errno(errno, "Failed to open %s: %m", entry->d_name); + continue; + } + + /* load2 write rules in the kernel require a line buffered stream */ + FOREACH_LINE(buf, policy, + log_error_errno(errno, "Failed to read line from %s: %m", + entry->d_name)) { + if (!fputs(buf, dst)) { + if (r == 0) + r = -EINVAL; + log_error_errno(errno, "Failed to write line to /sys/fs/smackfs/netlabel"); + break; + } + if (fflush(dst)) { + if (r == 0) + r = -errno; + log_error_errno(errno, "Failed to flush writes to /sys/fs/smackfs/netlabel: %m"); + break; + } + } + } + + return r; +} + +#endif + +int mac_smack_setup(bool *loaded_policy) { + +#ifdef HAVE_SMACK + + int r; + + assert(loaded_policy); + + r = write_access2_rules("/etc/smack/accesses.d/"); + switch(r) { + case -ENOENT: + log_debug("Smack is not enabled in the kernel."); + return 0; + case ENOENT: + log_debug("Smack access rules directory '/etc/smack/accesses.d/' not found"); + return 0; + case 0: + log_info("Successfully loaded Smack policies."); + break; + default: + log_warning_errno(r, "Failed to load Smack access rules, ignoring: %m"); + return 0; + } + +#ifdef SMACK_RUN_LABEL + r = write_string_file("/proc/self/attr/current", SMACK_RUN_LABEL, 0); + if (r < 0) + log_warning_errno(r, "Failed to set SMACK label \"" SMACK_RUN_LABEL "\" on self: %m"); + r = write_string_file("/sys/fs/smackfs/ambient", SMACK_RUN_LABEL, 0); + if (r < 0) + log_warning_errno(r, "Failed to set SMACK ambient label \"" SMACK_RUN_LABEL "\": %m"); + r = write_string_file("/sys/fs/smackfs/netlabel", + "0.0.0.0/0 " SMACK_RUN_LABEL, 0); + if (r < 0) + log_warning_errno(r, "Failed to set SMACK netlabel rule \"0.0.0.0/0 " SMACK_RUN_LABEL "\": %m"); + r = write_string_file("/sys/fs/smackfs/netlabel", "127.0.0.1 -CIPSO", 0); + if (r < 0) + log_warning_errno(r, "Failed to set SMACK netlabel rule \"127.0.0.1 -CIPSO\": %m"); +#endif + + r = write_cipso2_rules("/etc/smack/cipso.d/"); + switch(r) { + case -ENOENT: + log_debug("Smack/CIPSO is not enabled in the kernel."); + return 0; + case ENOENT: + log_debug("Smack/CIPSO access rules directory '/etc/smack/cipso.d/' not found"); + break; + case 0: + log_info("Successfully loaded Smack/CIPSO policies."); + break; + default: + log_warning_errno(r, "Failed to load Smack/CIPSO access rules, ignoring: %m"); + break; + } + + r = write_netlabel_rules("/etc/smack/netlabel.d/"); + switch(r) { + case -ENOENT: + log_debug("Smack/CIPSO is not enabled in the kernel."); + return 0; + case ENOENT: + log_debug("Smack network host rules directory '/etc/smack/netlabel.d/' not found"); + break; + case 0: + log_info("Successfully loaded Smack network host rules."); + break; + default: + log_warning_errno(r, "Failed to load Smack network host rules: %m, ignoring."); + break; + } + + *loaded_policy = true; + +#endif + + return 0; +} diff --git a/src/libcore/smack-setup.h b/src/libcore/smack-setup.h new file mode 100644 index 0000000000..78164c85e6 --- /dev/null +++ b/src/libcore/smack-setup.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2013 Intel Corporation + Authors: + Nathaniel Chen + + 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 . +***/ + +int mac_smack_setup(bool *loaded_policy); diff --git a/src/libcore/socket.c b/src/libcore/socket.c new file mode 100644 index 0000000000..f6204d04bf --- /dev/null +++ b/src/libcore/socket.c @@ -0,0 +1,2992 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "copy.h" +#include "dbus-socket.h" +#include "def.h" +#include "exit-status.h" +#include "fd-util.h" +#include "formats-util.h" +#include "io-util.h" +#include "label.h" +#include "log.h" +#include "missing.h" +#include "mkdir.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "selinux-util.h" +#include "signal-util.h" +#include "smack-util.h" +#include "socket.h" +#include "special.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "unit-printf.h" +#include "unit.h" +#include "user-util.h" + +static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = { + [SOCKET_DEAD] = UNIT_INACTIVE, + [SOCKET_START_PRE] = UNIT_ACTIVATING, + [SOCKET_START_CHOWN] = UNIT_ACTIVATING, + [SOCKET_START_POST] = UNIT_ACTIVATING, + [SOCKET_LISTENING] = UNIT_ACTIVE, + [SOCKET_RUNNING] = UNIT_ACTIVE, + [SOCKET_STOP_PRE] = UNIT_DEACTIVATING, + [SOCKET_STOP_PRE_SIGTERM] = UNIT_DEACTIVATING, + [SOCKET_STOP_PRE_SIGKILL] = UNIT_DEACTIVATING, + [SOCKET_STOP_POST] = UNIT_DEACTIVATING, + [SOCKET_FINAL_SIGTERM] = UNIT_DEACTIVATING, + [SOCKET_FINAL_SIGKILL] = UNIT_DEACTIVATING, + [SOCKET_FAILED] = UNIT_FAILED +}; + +static int socket_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); +static int socket_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); + +static void socket_init(Unit *u) { + Socket *s = SOCKET(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + s->backlog = SOMAXCONN; + s->timeout_usec = u->manager->default_timeout_start_usec; + s->directory_mode = 0755; + s->socket_mode = 0666; + + s->max_connections = 64; + + s->priority = -1; + s->ip_tos = -1; + s->ip_ttl = -1; + s->mark = -1; + + s->exec_context.std_output = u->manager->default_std_output; + s->exec_context.std_error = u->manager->default_std_error; + + s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; + + s->trigger_limit.interval = USEC_INFINITY; + s->trigger_limit.burst = (unsigned) -1; +} + +static void socket_unwatch_control_pid(Socket *s) { + assert(s); + + if (s->control_pid <= 0) + return; + + unit_unwatch_pid(UNIT(s), s->control_pid); + s->control_pid = 0; +} + +static void socket_cleanup_fd_list(SocketPort *p) { + assert(p); + + close_many(p->auxiliary_fds, p->n_auxiliary_fds); + p->auxiliary_fds = mfree(p->auxiliary_fds); + p->n_auxiliary_fds = 0; +} + +void socket_free_ports(Socket *s) { + SocketPort *p; + + assert(s); + + while ((p = s->ports)) { + LIST_REMOVE(port, s->ports, p); + + sd_event_source_unref(p->event_source); + + socket_cleanup_fd_list(p); + safe_close(p->fd); + free(p->path); + free(p); + } +} + +static void socket_done(Unit *u) { + Socket *s = SOCKET(u); + + assert(s); + + socket_free_ports(s); + + s->exec_runtime = exec_runtime_unref(s->exec_runtime); + exec_command_free_array(s->exec_command, _SOCKET_EXEC_COMMAND_MAX); + s->control_command = NULL; + + socket_unwatch_control_pid(s); + + unit_ref_unset(&s->service); + + s->tcp_congestion = mfree(s->tcp_congestion); + s->bind_to_device = mfree(s->bind_to_device); + + s->smack = mfree(s->smack); + s->smack_ip_in = mfree(s->smack_ip_in); + s->smack_ip_out = mfree(s->smack_ip_out); + + strv_free(s->symlinks); + + s->user = mfree(s->user); + s->group = mfree(s->group); + + s->fdname = mfree(s->fdname); + + s->timer_event_source = sd_event_source_unref(s->timer_event_source); +} + +static int socket_arm_timer(Socket *s, usec_t usec) { + int r; + + assert(s); + + if (s->timer_event_source) { + r = sd_event_source_set_time(s->timer_event_source, usec); + if (r < 0) + return r; + + return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); + } + + if (usec == USEC_INFINITY) + return 0; + + r = sd_event_add_time( + UNIT(s)->manager->event, + &s->timer_event_source, + CLOCK_MONOTONIC, + usec, 0, + socket_dispatch_timer, s); + if (r < 0) + return r; + + (void) sd_event_source_set_description(s->timer_event_source, "socket-timer"); + + return 0; +} + +int socket_instantiate_service(Socket *s) { + _cleanup_free_ char *prefix = NULL, *name = NULL; + int r; + Unit *u; + + assert(s); + + /* This fills in s->service if it isn't filled in yet. For + * Accept=yes sockets we create the next connection service + * here. For Accept=no this is mostly a NOP since the service + * is figured out at load time anyway. */ + + if (UNIT_DEREF(s->service)) + return 0; + + if (!s->accept) + return 0; + + r = unit_name_to_prefix(UNIT(s)->id, &prefix); + if (r < 0) + return r; + + if (asprintf(&name, "%s@%u.service", prefix, s->n_accepted) < 0) + return -ENOMEM; + + r = manager_load_unit(UNIT(s)->manager, name, NULL, NULL, &u); + if (r < 0) + return r; + + unit_ref_set(&s->service, u); + + return unit_add_two_dependencies(UNIT(s), UNIT_BEFORE, UNIT_TRIGGERS, u, false); +} + +static bool have_non_accept_socket(Socket *s) { + SocketPort *p; + + assert(s); + + if (!s->accept) + return true; + + LIST_FOREACH(port, p, s->ports) { + + if (p->type != SOCKET_SOCKET) + return true; + + if (!socket_address_can_accept(&p->address)) + return true; + } + + return false; +} + +static int socket_add_mount_links(Socket *s) { + SocketPort *p; + int r; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + const char *path = NULL; + + if (p->type == SOCKET_SOCKET) + path = socket_address_get_path(&p->address); + else if (IN_SET(p->type, SOCKET_FIFO, SOCKET_SPECIAL, SOCKET_USB_FUNCTION)) + path = p->path; + + if (!path) + continue; + + r = unit_require_mounts_for(UNIT(s), path); + if (r < 0) + return r; + } + + return 0; +} + +static int socket_add_device_link(Socket *s) { + char *t; + + assert(s); + + if (!s->bind_to_device || streq(s->bind_to_device, "lo")) + return 0; + + t = strjoina("/sys/subsystem/net/devices/", s->bind_to_device); + return unit_add_node_link(UNIT(s), t, false, UNIT_BINDS_TO); +} + +static int socket_add_default_dependencies(Socket *s) { + int r; + assert(s); + + if (!UNIT(s)->default_dependencies) + return 0; + + r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_SOCKETS_TARGET, NULL, true); + if (r < 0) + return r; + + if (MANAGER_IS_SYSTEM(UNIT(s)->manager)) { + r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); + if (r < 0) + return r; + } + + return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); +} + +_pure_ static bool socket_has_exec(Socket *s) { + unsigned i; + assert(s); + + for (i = 0; i < _SOCKET_EXEC_COMMAND_MAX; i++) + if (s->exec_command[i]) + return true; + + return false; +} + +static int socket_add_extras(Socket *s) { + Unit *u = UNIT(s); + int r; + + assert(s); + + /* Pick defaults for the trigger limit, if nothing was explicitly configured. We pick a relatively high limit + * in Accept=yes mode, and a lower limit for Accept=no. Reason: in Accept=yes mode we are invoking accept() + * ourselves before the trigger limit can hit, thus incoming connections are taken off the socket queue quickly + * and reliably. This is different for Accept=no, where the spawned service has to take the incoming traffic + * off the queues, which it might not necessarily do. Moreover, while Accept=no services are supposed to + * process whatever is queued in one go, and thus should normally never have to be started frequently. This is + * different for Accept=yes where each connection is processed by a new service instance, and thus frequent + * service starts are typical. */ + + if (s->trigger_limit.interval == USEC_INFINITY) + s->trigger_limit.interval = 2 * USEC_PER_SEC; + + if (s->trigger_limit.burst == (unsigned) -1) { + if (s->accept) + s->trigger_limit.burst = 200; + else + s->trigger_limit.burst = 20; + } + + if (have_non_accept_socket(s)) { + + if (!UNIT_DEREF(s->service)) { + Unit *x; + + r = unit_load_related_unit(u, ".service", &x); + if (r < 0) + return r; + + unit_ref_set(&s->service, x); + } + + r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(s->service), true); + if (r < 0) + return r; + } + + r = socket_add_mount_links(s); + if (r < 0) + return r; + + r = socket_add_device_link(s); + if (r < 0) + return r; + + r = unit_patch_contexts(u); + if (r < 0) + return r; + + if (socket_has_exec(s)) { + r = unit_add_exec_dependencies(u, &s->exec_context); + if (r < 0) + return r; + + r = unit_set_default_slice(u); + if (r < 0) + return r; + } + + r = socket_add_default_dependencies(s); + if (r < 0) + return r; + + return 0; +} + +static const char *socket_find_symlink_target(Socket *s) { + const char *found = NULL; + SocketPort *p; + + LIST_FOREACH(port, p, s->ports) { + const char *f = NULL; + + switch (p->type) { + + case SOCKET_FIFO: + f = p->path; + break; + + case SOCKET_SOCKET: + if (p->address.sockaddr.un.sun_path[0] != 0) + f = p->address.sockaddr.un.sun_path; + break; + + default: + break; + } + + if (f) { + if (found) + return NULL; + + found = f; + } + } + + return found; +} + +static int socket_verify(Socket *s) { + assert(s); + + if (UNIT(s)->load_state != UNIT_LOADED) + return 0; + + if (!s->ports) { + log_unit_error(UNIT(s), "Unit lacks Listen setting. Refusing."); + return -EINVAL; + } + + if (s->accept && have_non_accept_socket(s)) { + log_unit_error(UNIT(s), "Unit configured for accepting sockets, but sockets are non-accepting. Refusing."); + return -EINVAL; + } + + if (s->accept && s->max_connections <= 0) { + log_unit_error(UNIT(s), "MaxConnection= setting too small. Refusing."); + return -EINVAL; + } + + if (s->accept && UNIT_DEREF(s->service)) { + log_unit_error(UNIT(s), "Explicit service configuration for accepting socket units not supported. Refusing."); + return -EINVAL; + } + + if (s->exec_context.pam_name && s->kill_context.kill_mode != KILL_CONTROL_GROUP) { + log_unit_error(UNIT(s), "Unit has PAM enabled. Kill mode must be set to 'control-group'. Refusing."); + return -EINVAL; + } + + if (!strv_isempty(s->symlinks) && !socket_find_symlink_target(s)) { + log_unit_error(UNIT(s), "Unit has symlinks set but none or more than one node in the file system. Refusing."); + return -EINVAL; + } + + return 0; +} + +static int socket_load(Unit *u) { + Socket *s = SOCKET(u); + int r; + + assert(u); + assert(u->load_state == UNIT_STUB); + + r = unit_load_fragment_and_dropin(u); + if (r < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + /* This is a new unit? Then let's add in some extras */ + r = socket_add_extras(s); + if (r < 0) + return r; + } + + return socket_verify(s); +} + +_const_ static const char* listen_lookup(int family, int type) { + + if (family == AF_NETLINK) + return "ListenNetlink"; + + if (type == SOCK_STREAM) + return "ListenStream"; + else if (type == SOCK_DGRAM) + return "ListenDatagram"; + else if (type == SOCK_SEQPACKET) + return "ListenSequentialPacket"; + + assert_not_reached("Unknown socket type"); + return NULL; +} + +static void socket_dump(Unit *u, FILE *f, const char *prefix) { + char time_string[FORMAT_TIMESPAN_MAX]; + SocketExecCommand c; + Socket *s = SOCKET(u); + SocketPort *p; + const char *prefix2; + + assert(s); + assert(f); + + prefix = strempty(prefix); + prefix2 = strjoina(prefix, "\t"); + + fprintf(f, + "%sSocket State: %s\n" + "%sResult: %s\n" + "%sBindIPv6Only: %s\n" + "%sBacklog: %u\n" + "%sSocketMode: %04o\n" + "%sDirectoryMode: %04o\n" + "%sKeepAlive: %s\n" + "%sNoDelay: %s\n" + "%sFreeBind: %s\n" + "%sTransparent: %s\n" + "%sBroadcast: %s\n" + "%sPassCredentials: %s\n" + "%sPassSecurity: %s\n" + "%sTCPCongestion: %s\n" + "%sRemoveOnStop: %s\n" + "%sWritable: %s\n" + "%sFDName: %s\n" + "%sSELinuxContextFromNet: %s\n", + prefix, socket_state_to_string(s->state), + prefix, socket_result_to_string(s->result), + prefix, socket_address_bind_ipv6_only_to_string(s->bind_ipv6_only), + prefix, s->backlog, + prefix, s->socket_mode, + prefix, s->directory_mode, + prefix, yes_no(s->keep_alive), + prefix, yes_no(s->no_delay), + prefix, yes_no(s->free_bind), + prefix, yes_no(s->transparent), + prefix, yes_no(s->broadcast), + prefix, yes_no(s->pass_cred), + prefix, yes_no(s->pass_sec), + prefix, strna(s->tcp_congestion), + prefix, yes_no(s->remove_on_stop), + prefix, yes_no(s->writable), + prefix, socket_fdname(s), + prefix, yes_no(s->selinux_context_from_net)); + + if (s->control_pid > 0) + fprintf(f, + "%sControl PID: "PID_FMT"\n", + prefix, s->control_pid); + + if (s->bind_to_device) + fprintf(f, + "%sBindToDevice: %s\n", + prefix, s->bind_to_device); + + if (s->accept) + fprintf(f, + "%sAccepted: %u\n" + "%sNConnections: %u\n" + "%sMaxConnections: %u\n", + prefix, s->n_accepted, + prefix, s->n_connections, + prefix, s->max_connections); + + if (s->priority >= 0) + fprintf(f, + "%sPriority: %i\n", + prefix, s->priority); + + if (s->receive_buffer > 0) + fprintf(f, + "%sReceiveBuffer: %zu\n", + prefix, s->receive_buffer); + + if (s->send_buffer > 0) + fprintf(f, + "%sSendBuffer: %zu\n", + prefix, s->send_buffer); + + if (s->ip_tos >= 0) + fprintf(f, + "%sIPTOS: %i\n", + prefix, s->ip_tos); + + if (s->ip_ttl >= 0) + fprintf(f, + "%sIPTTL: %i\n", + prefix, s->ip_ttl); + + if (s->pipe_size > 0) + fprintf(f, + "%sPipeSize: %zu\n", + prefix, s->pipe_size); + + if (s->mark >= 0) + fprintf(f, + "%sMark: %i\n", + prefix, s->mark); + + if (s->mq_maxmsg > 0) + fprintf(f, + "%sMessageQueueMaxMessages: %li\n", + prefix, s->mq_maxmsg); + + if (s->mq_msgsize > 0) + fprintf(f, + "%sMessageQueueMessageSize: %li\n", + prefix, s->mq_msgsize); + + if (s->reuse_port) + fprintf(f, + "%sReusePort: %s\n", + prefix, yes_no(s->reuse_port)); + + if (s->smack) + fprintf(f, + "%sSmackLabel: %s\n", + prefix, s->smack); + + if (s->smack_ip_in) + fprintf(f, + "%sSmackLabelIPIn: %s\n", + prefix, s->smack_ip_in); + + if (s->smack_ip_out) + fprintf(f, + "%sSmackLabelIPOut: %s\n", + prefix, s->smack_ip_out); + + if (!isempty(s->user) || !isempty(s->group)) + fprintf(f, + "%sSocketUser: %s\n" + "%sSocketGroup: %s\n", + prefix, strna(s->user), + prefix, strna(s->group)); + + if (s->keep_alive_time > 0) + fprintf(f, + "%sKeepAliveTimeSec: %s\n", + prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, s->keep_alive_time, USEC_PER_SEC)); + + if (s->keep_alive_interval) + fprintf(f, + "%sKeepAliveIntervalSec: %s\n", + prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, s->keep_alive_interval, USEC_PER_SEC)); + + if (s->keep_alive_cnt) + fprintf(f, + "%sKeepAliveProbes: %u\n", + prefix, s->keep_alive_cnt); + + if (s->defer_accept) + fprintf(f, + "%sDeferAcceptSec: %s\n", + prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, s->defer_accept, USEC_PER_SEC)); + + LIST_FOREACH(port, p, s->ports) { + + if (p->type == SOCKET_SOCKET) { + const char *t; + int r; + char *k = NULL; + + r = socket_address_print(&p->address, &k); + if (r < 0) + t = strerror(-r); + else + t = k; + + fprintf(f, "%s%s: %s\n", prefix, listen_lookup(socket_address_family(&p->address), p->address.type), t); + free(k); + } else if (p->type == SOCKET_SPECIAL) + fprintf(f, "%sListenSpecial: %s\n", prefix, p->path); + else if (p->type == SOCKET_USB_FUNCTION) + fprintf(f, "%sListenUSBFunction: %s\n", prefix, p->path); + else if (p->type == SOCKET_MQUEUE) + fprintf(f, "%sListenMessageQueue: %s\n", prefix, p->path); + else + fprintf(f, "%sListenFIFO: %s\n", prefix, p->path); + } + + fprintf(f, + "%sTriggerLimitIntervalSec: %s\n" + "%sTriggerLimitBurst: %u\n", + prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, s->trigger_limit.interval, USEC_PER_SEC), + prefix, s->trigger_limit.burst); + + exec_context_dump(&s->exec_context, f, prefix); + kill_context_dump(&s->kill_context, f, prefix); + + for (c = 0; c < _SOCKET_EXEC_COMMAND_MAX; c++) { + if (!s->exec_command[c]) + continue; + + fprintf(f, "%s-> %s:\n", + prefix, socket_exec_command_to_string(c)); + + exec_command_dump_list(s->exec_command[c], f, prefix2); + } +} + +static int instance_from_socket(int fd, unsigned nr, char **instance) { + socklen_t l; + char *r; + union sockaddr_union local, remote; + + assert(fd >= 0); + assert(instance); + + l = sizeof(local); + if (getsockname(fd, &local.sa, &l) < 0) + return -errno; + + l = sizeof(remote); + if (getpeername(fd, &remote.sa, &l) < 0) + return -errno; + + switch (local.sa.sa_family) { + + case AF_INET: { + uint32_t + a = ntohl(local.in.sin_addr.s_addr), + b = ntohl(remote.in.sin_addr.s_addr); + + if (asprintf(&r, + "%u-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u", + nr, + a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF, + ntohs(local.in.sin_port), + b >> 24, (b >> 16) & 0xFF, (b >> 8) & 0xFF, b & 0xFF, + ntohs(remote.in.sin_port)) < 0) + return -ENOMEM; + + break; + } + + case AF_INET6: { + static const unsigned char ipv4_prefix[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF + }; + + if (memcmp(&local.in6.sin6_addr, ipv4_prefix, sizeof(ipv4_prefix)) == 0 && + memcmp(&remote.in6.sin6_addr, ipv4_prefix, sizeof(ipv4_prefix)) == 0) { + const uint8_t + *a = local.in6.sin6_addr.s6_addr+12, + *b = remote.in6.sin6_addr.s6_addr+12; + + if (asprintf(&r, + "%u-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u", + nr, + a[0], a[1], a[2], a[3], + ntohs(local.in6.sin6_port), + b[0], b[1], b[2], b[3], + ntohs(remote.in6.sin6_port)) < 0) + return -ENOMEM; + } else { + char a[INET6_ADDRSTRLEN], b[INET6_ADDRSTRLEN]; + + if (asprintf(&r, + "%u-%s:%u-%s:%u", + nr, + inet_ntop(AF_INET6, &local.in6.sin6_addr, a, sizeof(a)), + ntohs(local.in6.sin6_port), + inet_ntop(AF_INET6, &remote.in6.sin6_addr, b, sizeof(b)), + ntohs(remote.in6.sin6_port)) < 0) + return -ENOMEM; + } + + break; + } + + case AF_UNIX: { + struct ucred ucred; + int k; + + k = getpeercred(fd, &ucred); + if (k >= 0) { + if (asprintf(&r, + "%u-"PID_FMT"-"UID_FMT, + nr, ucred.pid, ucred.uid) < 0) + return -ENOMEM; + } else if (k == -ENODATA) { + /* This handles the case where somebody is + * connecting from another pid/uid namespace + * (e.g. from outside of our container). */ + if (asprintf(&r, + "%u-unknown", + nr) < 0) + return -ENOMEM; + } else + return k; + + break; + } + + default: + assert_not_reached("Unhandled socket type."); + } + + *instance = r; + return 0; +} + +static void socket_close_fds(Socket *s) { + SocketPort *p; + char **i; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + bool was_open; + + was_open = p->fd >= 0; + + p->event_source = sd_event_source_unref(p->event_source); + p->fd = safe_close(p->fd); + socket_cleanup_fd_list(p); + + /* One little note: we should normally not delete any sockets in the file system here! After all some + * other process we spawned might still have a reference of this fd and wants to continue to use + * it. Therefore we normally delete sockets in the file system before we create a new one, not after we + * stopped using one! That all said, if the user explicitly requested this, we'll delete them here + * anyway, but only then. */ + + if (!was_open || !s->remove_on_stop) + continue; + + switch (p->type) { + + case SOCKET_FIFO: + (void) unlink(p->path); + break; + + case SOCKET_MQUEUE: + (void) mq_unlink(p->path); + break; + + case SOCKET_SOCKET: + (void) socket_address_unlink(&p->address); + break; + + default: + break; + } + } + + if (s->remove_on_stop) + STRV_FOREACH(i, s->symlinks) + (void) unlink(*i); +} + +static void socket_apply_socket_options(Socket *s, int fd) { + int r; + + assert(s); + assert(fd >= 0); + + if (s->keep_alive) { + int b = s->keep_alive; + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &b, sizeof(b)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SO_KEEPALIVE failed: %m"); + } + + if (s->keep_alive_time) { + int value = s->keep_alive_time / USEC_PER_SEC; + if (setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &value, sizeof(value)) < 0) + log_unit_warning_errno(UNIT(s), errno, "TCP_KEEPIDLE failed: %m"); + } + + if (s->keep_alive_interval) { + int value = s->keep_alive_interval / USEC_PER_SEC; + if (setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &value, sizeof(value)) < 0) + log_unit_warning_errno(UNIT(s), errno, "TCP_KEEPINTVL failed: %m"); + } + + if (s->keep_alive_cnt) { + int value = s->keep_alive_cnt; + if (setsockopt(fd, SOL_TCP, TCP_KEEPCNT, &value, sizeof(value)) < 0) + log_unit_warning_errno(UNIT(s), errno, "TCP_KEEPCNT failed: %m"); + } + + if (s->defer_accept) { + int value = s->defer_accept / USEC_PER_SEC; + if (setsockopt(fd, SOL_TCP, TCP_DEFER_ACCEPT, &value, sizeof(value)) < 0) + log_unit_warning_errno(UNIT(s), errno, "TCP_DEFER_ACCEPT failed: %m"); + } + + if (s->no_delay) { + int b = s->no_delay; + + if (s->socket_protocol == IPPROTO_SCTP) { + if (setsockopt(fd, SOL_SCTP, SCTP_NODELAY, &b, sizeof(b)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SCTP_NODELAY failed: %m"); + } else { + if (setsockopt(fd, SOL_TCP, TCP_NODELAY, &b, sizeof(b)) < 0) + log_unit_warning_errno(UNIT(s), errno, "TCP_NODELAY failed: %m"); + } + } + + if (s->broadcast) { + int one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SO_BROADCAST failed: %m"); + } + + if (s->pass_cred) { + int one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SO_PASSCRED failed: %m"); + } + + if (s->pass_sec) { + int one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SO_PASSSEC failed: %m"); + } + + if (s->priority >= 0) + if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &s->priority, sizeof(s->priority)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SO_PRIORITY failed: %m"); + + if (s->receive_buffer > 0) { + int value = (int) s->receive_buffer; + + /* We first try with SO_RCVBUFFORCE, in case we have the perms for that */ + + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &value, sizeof(value)) < 0) + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SO_RCVBUF failed: %m"); + } + + if (s->send_buffer > 0) { + int value = (int) s->send_buffer; + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUFFORCE, &value, sizeof(value)) < 0) + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SO_SNDBUF failed: %m"); + } + + if (s->mark >= 0) + if (setsockopt(fd, SOL_SOCKET, SO_MARK, &s->mark, sizeof(s->mark)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SO_MARK failed: %m"); + + if (s->ip_tos >= 0) + if (setsockopt(fd, IPPROTO_IP, IP_TOS, &s->ip_tos, sizeof(s->ip_tos)) < 0) + log_unit_warning_errno(UNIT(s), errno, "IP_TOS failed: %m"); + + if (s->ip_ttl >= 0) { + int x; + + r = setsockopt(fd, IPPROTO_IP, IP_TTL, &s->ip_ttl, sizeof(s->ip_ttl)); + + if (socket_ipv6_is_supported()) + x = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &s->ip_ttl, sizeof(s->ip_ttl)); + else { + x = -1; + errno = EAFNOSUPPORT; + } + + if (r < 0 && x < 0) + log_unit_warning_errno(UNIT(s), errno, "IP_TTL/IPV6_UNICAST_HOPS failed: %m"); + } + + if (s->tcp_congestion) + if (setsockopt(fd, SOL_TCP, TCP_CONGESTION, s->tcp_congestion, strlen(s->tcp_congestion)+1) < 0) + log_unit_warning_errno(UNIT(s), errno, "TCP_CONGESTION failed: %m"); + + if (s->smack_ip_in) { + r = mac_smack_apply_fd(fd, SMACK_ATTR_IPIN, s->smack_ip_in); + if (r < 0) + log_unit_error_errno(UNIT(s), r, "mac_smack_apply_ip_in_fd: %m"); + } + + if (s->smack_ip_out) { + r = mac_smack_apply_fd(fd, SMACK_ATTR_IPOUT, s->smack_ip_out); + if (r < 0) + log_unit_error_errno(UNIT(s), r, "mac_smack_apply_ip_out_fd: %m"); + } +} + +static void socket_apply_fifo_options(Socket *s, int fd) { + int r; + + assert(s); + assert(fd >= 0); + + if (s->pipe_size > 0) + if (fcntl(fd, F_SETPIPE_SZ, s->pipe_size) < 0) + log_unit_warning_errno(UNIT(s), errno, "Setting pipe size failed, ignoring: %m"); + + if (s->smack) { + r = mac_smack_apply_fd(fd, SMACK_ATTR_ACCESS, s->smack); + if (r < 0) + log_unit_error_errno(UNIT(s), r, "SMACK relabelling failed, ignoring: %m"); + } +} + +static int fifo_address_create( + const char *path, + mode_t directory_mode, + mode_t socket_mode) { + + _cleanup_close_ int fd = -1; + mode_t old_mask; + struct stat st; + int r; + + assert(path); + + mkdir_parents_label(path, directory_mode); + + r = mac_selinux_create_file_prepare(path, S_IFIFO); + if (r < 0) + return r; + + /* Enforce the right access mode for the fifo */ + old_mask = umask(~ socket_mode); + + /* Include the original umask in our mask */ + (void) umask(~socket_mode | old_mask); + + r = mkfifo(path, socket_mode); + (void) umask(old_mask); + + if (r < 0 && errno != EEXIST) { + r = -errno; + goto fail; + } + + fd = open(path, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK | O_NOFOLLOW); + if (fd < 0) { + r = -errno; + goto fail; + } + + mac_selinux_create_file_clear(); + + if (fstat(fd, &st) < 0) { + r = -errno; + goto fail; + } + + if (!S_ISFIFO(st.st_mode) || + (st.st_mode & 0777) != (socket_mode & ~old_mask) || + st.st_uid != getuid() || + st.st_gid != getgid()) { + r = -EEXIST; + goto fail; + } + + r = fd; + fd = -1; + + return r; + +fail: + mac_selinux_create_file_clear(); + return r; +} + +static int special_address_create(const char *path, bool writable) { + _cleanup_close_ int fd = -1; + struct stat st; + int r; + + assert(path); + + fd = open(path, (writable ? O_RDWR : O_RDONLY)|O_CLOEXEC|O_NOCTTY|O_NONBLOCK|O_NOFOLLOW); + if (fd < 0) + return -errno; + + if (fstat(fd, &st) < 0) + return -errno; + + /* Check whether this is a /proc, /sys or /dev file or char device */ + if (!S_ISREG(st.st_mode) && !S_ISCHR(st.st_mode)) + return -EEXIST; + + r = fd; + fd = -1; + + return r; +} + +static int usbffs_address_create(const char *path) { + _cleanup_close_ int fd = -1; + struct stat st; + int r; + + assert(path); + + fd = open(path, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK|O_NOFOLLOW); + if (fd < 0) + return -errno; + + if (fstat(fd, &st) < 0) + return -errno; + + /* Check whether this is a regular file (ffs endpoint)*/ + if (!S_ISREG(st.st_mode)) + return -EEXIST; + + r = fd; + fd = -1; + + return r; +} + +static int mq_address_create( + const char *path, + mode_t mq_mode, + long maxmsg, + long msgsize) { + + _cleanup_close_ int fd = -1; + struct stat st; + mode_t old_mask; + struct mq_attr _attr, *attr = NULL; + int r; + + assert(path); + + if (maxmsg > 0 && msgsize > 0) { + _attr = (struct mq_attr) { + .mq_flags = O_NONBLOCK, + .mq_maxmsg = maxmsg, + .mq_msgsize = msgsize, + }; + attr = &_attr; + } + + /* Enforce the right access mode for the mq */ + old_mask = umask(~ mq_mode); + + /* Include the original umask in our mask */ + (void) umask(~mq_mode | old_mask); + fd = mq_open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_CREAT, mq_mode, attr); + (void) umask(old_mask); + + if (fd < 0) + return -errno; + + if (fstat(fd, &st) < 0) + return -errno; + + if ((st.st_mode & 0777) != (mq_mode & ~old_mask) || + st.st_uid != getuid() || + st.st_gid != getgid()) + return -EEXIST; + + r = fd; + fd = -1; + + return r; +} + +static int socket_symlink(Socket *s) { + const char *p; + char **i; + + assert(s); + + p = socket_find_symlink_target(s); + if (!p) + return 0; + + STRV_FOREACH(i, s->symlinks) + symlink_label(p, *i); + + return 0; +} + +static int usbffs_write_descs(int fd, Service *s) { + int r; + + if (!s->usb_function_descriptors || !s->usb_function_strings) + return -EINVAL; + + r = copy_file_fd(s->usb_function_descriptors, fd, false); + if (r < 0) + return r; + + return copy_file_fd(s->usb_function_strings, fd, false); +} + +static int usbffs_select_ep(const struct dirent *d) { + return d->d_name[0] != '.' && !streq(d->d_name, "ep0"); +} + +static int usbffs_dispatch_eps(SocketPort *p) { + _cleanup_free_ struct dirent **ent = NULL; + _cleanup_free_ char *path = NULL; + int r, i, n, k; + + path = dirname_malloc(p->path); + if (!path) + return -ENOMEM; + + r = scandir(path, &ent, usbffs_select_ep, alphasort); + if (r < 0) + return -errno; + + n = r; + p->auxiliary_fds = new(int, n); + if (!p->auxiliary_fds) + return -ENOMEM; + + p->n_auxiliary_fds = n; + + k = 0; + for (i = 0; i < n; ++i) { + _cleanup_free_ char *ep = NULL; + + ep = path_make_absolute(ent[i]->d_name, path); + if (!ep) + return -ENOMEM; + + path_kill_slashes(ep); + + r = usbffs_address_create(ep); + if (r < 0) + goto fail; + + p->auxiliary_fds[k] = r; + + ++k; + free(ent[i]); + } + + return r; + +fail: + close_many(p->auxiliary_fds, k); + p->auxiliary_fds = mfree(p->auxiliary_fds); + p->n_auxiliary_fds = 0; + + return r; +} + +static int socket_determine_selinux_label(Socket *s, char **ret) { + ExecCommand *c; + int r; + + assert(s); + assert(ret); + + if (s->selinux_context_from_net) { + /* If this is requested, get label from the network label */ + + r = mac_selinux_get_our_label(ret); + if (r == -EOPNOTSUPP) + goto no_label; + + } else { + /* Otherwise, get it from the executable we are about to start */ + r = socket_instantiate_service(s); + if (r < 0) + return r; + + if (!UNIT_ISSET(s->service)) + goto no_label; + + c = SERVICE(UNIT_DEREF(s->service))->exec_command[SERVICE_EXEC_START]; + if (!c) + goto no_label; + + r = mac_selinux_get_create_label_from_exe(c->path, ret); + if (r == -EPERM || r == -EOPNOTSUPP) + goto no_label; + } + + return r; + +no_label: + *ret = NULL; + return 0; +} + +static int socket_open_fds(Socket *s) { + _cleanup_(mac_selinux_freep) char *label = NULL; + bool know_label = false; + SocketPort *p; + int r; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + + if (p->fd >= 0) + continue; + + switch (p->type) { + + case SOCKET_SOCKET: + + if (!know_label) { + /* Figure out label, if we don't it know yet. We do it once, for the first socket where + * we need this and remember it for the rest. */ + + r = socket_determine_selinux_label(s, &label); + if (r < 0) + goto rollback; + + know_label = true; + } + + /* Apply the socket protocol */ + switch (p->address.type) { + + case SOCK_STREAM: + case SOCK_SEQPACKET: + if (s->socket_protocol == IPPROTO_SCTP) + p->address.protocol = s->socket_protocol; + break; + + case SOCK_DGRAM: + if (s->socket_protocol == IPPROTO_UDPLITE) + p->address.protocol = s->socket_protocol; + break; + } + + r = socket_address_listen( + &p->address, + SOCK_CLOEXEC|SOCK_NONBLOCK, + s->backlog, + s->bind_ipv6_only, + s->bind_to_device, + s->reuse_port, + s->free_bind, + s->transparent, + s->directory_mode, + s->socket_mode, + label); + if (r < 0) + goto rollback; + + p->fd = r; + socket_apply_socket_options(s, p->fd); + socket_symlink(s); + break; + + case SOCKET_SPECIAL: + + p->fd = special_address_create(p->path, s->writable); + if (p->fd < 0) { + r = p->fd; + goto rollback; + } + break; + + case SOCKET_FIFO: + + p->fd = fifo_address_create( + p->path, + s->directory_mode, + s->socket_mode); + if (p->fd < 0) { + r = p->fd; + goto rollback; + } + + socket_apply_fifo_options(s, p->fd); + socket_symlink(s); + break; + + case SOCKET_MQUEUE: + + p->fd = mq_address_create( + p->path, + s->socket_mode, + s->mq_maxmsg, + s->mq_msgsize); + if (p->fd < 0) { + r = p->fd; + goto rollback; + } + break; + + case SOCKET_USB_FUNCTION: { + _cleanup_free_ char *ep = NULL; + + ep = path_make_absolute("ep0", p->path); + + p->fd = usbffs_address_create(ep); + if (p->fd < 0) { + r = p->fd; + goto rollback; + } + + r = usbffs_write_descs(p->fd, SERVICE(UNIT_DEREF(s->service))); + if (r < 0) + goto rollback; + + r = usbffs_dispatch_eps(p); + if (r < 0) + goto rollback; + + break; + } + default: + assert_not_reached("Unknown port type"); + } + } + + return 0; + +rollback: + socket_close_fds(s); + return r; +} + +static void socket_unwatch_fds(Socket *s) { + SocketPort *p; + int r; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + if (p->fd < 0) + continue; + + if (!p->event_source) + continue; + + r = sd_event_source_set_enabled(p->event_source, SD_EVENT_OFF); + if (r < 0) + log_unit_debug_errno(UNIT(s), r, "Failed to disable event source: %m"); + } +} + +static int socket_watch_fds(Socket *s) { + SocketPort *p; + int r; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + if (p->fd < 0) + continue; + + if (p->event_source) { + r = sd_event_source_set_enabled(p->event_source, SD_EVENT_ON); + if (r < 0) + goto fail; + } else { + r = sd_event_add_io(UNIT(s)->manager->event, &p->event_source, p->fd, EPOLLIN, socket_dispatch_io, p); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(p->event_source, "socket-port-io"); + } + } + + return 0; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to watch listening fds: %m"); + socket_unwatch_fds(s); + return r; +} + +enum { + SOCKET_OPEN_NONE, + SOCKET_OPEN_SOME, + SOCKET_OPEN_ALL, +}; + +static int socket_check_open(Socket *s) { + bool have_open = false, have_closed = false; + SocketPort *p; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + if (p->fd < 0) + have_closed = true; + else + have_open = true; + + if (have_open && have_closed) + return SOCKET_OPEN_SOME; + } + + if (have_open) + return SOCKET_OPEN_ALL; + + return SOCKET_OPEN_NONE; +} + +static void socket_set_state(Socket *s, SocketState state) { + SocketState old_state; + assert(s); + + old_state = s->state; + s->state = state; + + if (!IN_SET(state, + SOCKET_START_PRE, + SOCKET_START_CHOWN, + SOCKET_START_POST, + SOCKET_STOP_PRE, + SOCKET_STOP_PRE_SIGTERM, + SOCKET_STOP_PRE_SIGKILL, + SOCKET_STOP_POST, + SOCKET_FINAL_SIGTERM, + SOCKET_FINAL_SIGKILL)) { + + s->timer_event_source = sd_event_source_unref(s->timer_event_source); + socket_unwatch_control_pid(s); + s->control_command = NULL; + s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; + } + + if (state != SOCKET_LISTENING) + socket_unwatch_fds(s); + + if (!IN_SET(state, + SOCKET_START_CHOWN, + SOCKET_START_POST, + SOCKET_LISTENING, + SOCKET_RUNNING, + SOCKET_STOP_PRE, + SOCKET_STOP_PRE_SIGTERM, + SOCKET_STOP_PRE_SIGKILL)) + socket_close_fds(s); + + if (state != old_state) + log_unit_debug(UNIT(s), "Changed %s -> %s", socket_state_to_string(old_state), socket_state_to_string(state)); + + unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); +} + +static int socket_coldplug(Unit *u) { + Socket *s = SOCKET(u); + int r; + + assert(s); + assert(s->state == SOCKET_DEAD); + + if (s->deserialized_state == s->state) + return 0; + + if (s->control_pid > 0 && + pid_is_unwaited(s->control_pid) && + IN_SET(s->deserialized_state, + SOCKET_START_PRE, + SOCKET_START_CHOWN, + SOCKET_START_POST, + SOCKET_STOP_PRE, + SOCKET_STOP_PRE_SIGTERM, + SOCKET_STOP_PRE_SIGKILL, + SOCKET_STOP_POST, + SOCKET_FINAL_SIGTERM, + SOCKET_FINAL_SIGKILL)) { + + r = unit_watch_pid(UNIT(s), s->control_pid); + if (r < 0) + return r; + + r = socket_arm_timer(s, usec_add(u->state_change_timestamp.monotonic, s->timeout_usec)); + if (r < 0) + return r; + } + + if (IN_SET(s->deserialized_state, + SOCKET_START_CHOWN, + SOCKET_START_POST, + SOCKET_LISTENING, + SOCKET_RUNNING)) { + + /* Originally, we used to simply reopen all sockets here that we didn't have file descriptors + * for. However, this is problematic, as we won't traverse throught the SOCKET_START_CHOWN state for + * them, and thus the UID/GID wouldn't be right. Hence, instead simply check if we have all fds open, + * and if there's a mismatch, warn loudly. */ + + r = socket_check_open(s); + if (r == SOCKET_OPEN_NONE) + log_unit_warning(UNIT(s), + "Socket unit configuration has changed while unit has been running, " + "no open socket file descriptor left. " + "The socket unit is not functional until restarted."); + else if (r == SOCKET_OPEN_SOME) + log_unit_warning(UNIT(s), + "Socket unit configuration has changed while unit has been running, " + "and some socket file descriptors have not been opened yet. " + "The socket unit is not fully functional until restarted."); + } + + if (s->deserialized_state == SOCKET_LISTENING) { + r = socket_watch_fds(s); + if (r < 0) + return r; + } + + socket_set_state(s, s->deserialized_state); + return 0; +} + +static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { + _cleanup_free_ char **argv = NULL; + pid_t pid; + int r; + ExecParameters exec_params = { + .apply_permissions = true, + .apply_chroot = true, + .apply_tty_stdin = true, + .stdin_fd = -1, + .stdout_fd = -1, + .stderr_fd = -1, + }; + + assert(s); + assert(c); + assert(_pid); + + (void) unit_realize_cgroup(UNIT(s)); + if (s->reset_cpu_usage) { + (void) unit_reset_cpu_usage(UNIT(s)); + s->reset_cpu_usage = false; + } + + r = unit_setup_exec_runtime(UNIT(s)); + if (r < 0) + return r; + + r = socket_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); + if (r < 0) + return r; + + r = unit_full_printf_strv(UNIT(s), c->argv, &argv); + if (r < 0) + return r; + + exec_params.argv = argv; + exec_params.environment = UNIT(s)->manager->environment; + exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn; + exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; + exec_params.cgroup_path = UNIT(s)->cgroup_path; + exec_params.cgroup_delegate = s->cgroup_context.delegate; + exec_params.runtime_prefix = manager_get_runtime_prefix(UNIT(s)->manager); + + r = exec_spawn(UNIT(s), + c, + &s->exec_context, + &exec_params, + s->exec_runtime, + &pid); + if (r < 0) + return r; + + r = unit_watch_pid(UNIT(s), pid); + if (r < 0) + /* FIXME: we need to do something here */ + return r; + + *_pid = pid; + return 0; +} + +static int socket_chown(Socket *s, pid_t *_pid) { + pid_t pid; + int r; + + r = socket_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); + if (r < 0) + goto fail; + + /* We have to resolve the user names out-of-process, hence + * let's fork here. It's messy, but well, what can we do? */ + + pid = fork(); + if (pid < 0) + return -errno; + + if (pid == 0) { + SocketPort *p; + uid_t uid = UID_INVALID; + gid_t gid = GID_INVALID; + int ret; + + (void) default_signals(SIGNALS_CRASH_HANDLER, SIGNALS_IGNORE, -1); + (void) ignore_signals(SIGPIPE, -1); + log_forget_fds(); + + if (!isempty(s->user)) { + const char *user = s->user; + + r = get_user_creds(&user, &uid, &gid, NULL, NULL); + if (r < 0) { + ret = EXIT_USER; + goto fail_child; + } + } + + if (!isempty(s->group)) { + const char *group = s->group; + + r = get_group_creds(&group, &gid); + if (r < 0) { + ret = EXIT_GROUP; + goto fail_child; + } + } + + LIST_FOREACH(port, p, s->ports) { + const char *path = NULL; + + if (p->type == SOCKET_SOCKET) + path = socket_address_get_path(&p->address); + else if (p->type == SOCKET_FIFO) + path = p->path; + + if (!path) + continue; + + if (chown(path, uid, gid) < 0) { + r = -errno; + ret = EXIT_CHOWN; + goto fail_child; + } + } + + _exit(0); + + fail_child: + log_open(); + log_error_errno(r, "Failed to chown socket at step %s: %m", exit_status_to_string(ret, EXIT_STATUS_SYSTEMD)); + + _exit(ret); + } + + r = unit_watch_pid(UNIT(s), pid); + if (r < 0) + goto fail; + + *_pid = pid; + return 0; + +fail: + s->timer_event_source = sd_event_source_unref(s->timer_event_source); + return r; +} + +static void socket_enter_dead(Socket *s, SocketResult f) { + assert(s); + + if (f != SOCKET_SUCCESS) + s->result = f; + + exec_runtime_destroy(s->exec_runtime); + s->exec_runtime = exec_runtime_unref(s->exec_runtime); + + exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager)); + + socket_set_state(s, s->result != SOCKET_SUCCESS ? SOCKET_FAILED : SOCKET_DEAD); +} + +static void socket_enter_signal(Socket *s, SocketState state, SocketResult f); + +static void socket_enter_stop_post(Socket *s, SocketResult f) { + int r; + assert(s); + + if (f != SOCKET_SUCCESS) + s->result = f; + + socket_unwatch_control_pid(s); + s->control_command_id = SOCKET_EXEC_STOP_POST; + s->control_command = s->exec_command[SOCKET_EXEC_STOP_POST]; + + if (s->control_command) { + r = socket_spawn(s, s->control_command, &s->control_pid); + if (r < 0) + goto fail; + + socket_set_state(s, SOCKET_STOP_POST); + } else + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'stop-post' task: %m"); + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_signal(Socket *s, SocketState state, SocketResult f) { + int r; + + assert(s); + + if (f != SOCKET_SUCCESS) + s->result = f; + + r = unit_kill_context( + UNIT(s), + &s->kill_context, + (state != SOCKET_STOP_PRE_SIGTERM && state != SOCKET_FINAL_SIGTERM) ? + KILL_KILL : KILL_TERMINATE, + -1, + s->control_pid, + false); + if (r < 0) + goto fail; + + if (r > 0) { + r = socket_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); + if (r < 0) + goto fail; + + socket_set_state(s, state); + } else if (state == SOCKET_STOP_PRE_SIGTERM) + socket_enter_signal(s, SOCKET_STOP_PRE_SIGKILL, SOCKET_SUCCESS); + else if (state == SOCKET_STOP_PRE_SIGKILL) + socket_enter_stop_post(s, SOCKET_SUCCESS); + else if (state == SOCKET_FINAL_SIGTERM) + socket_enter_signal(s, SOCKET_FINAL_SIGKILL, SOCKET_SUCCESS); + else + socket_enter_dead(s, SOCKET_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); + + if (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_STOP_PRE_SIGKILL) + socket_enter_stop_post(s, SOCKET_FAILURE_RESOURCES); + else + socket_enter_dead(s, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_stop_pre(Socket *s, SocketResult f) { + int r; + assert(s); + + if (f != SOCKET_SUCCESS) + s->result = f; + + socket_unwatch_control_pid(s); + s->control_command_id = SOCKET_EXEC_STOP_PRE; + s->control_command = s->exec_command[SOCKET_EXEC_STOP_PRE]; + + if (s->control_command) { + r = socket_spawn(s, s->control_command, &s->control_pid); + if (r < 0) + goto fail; + + socket_set_state(s, SOCKET_STOP_PRE); + } else + socket_enter_stop_post(s, SOCKET_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'stop-pre' task: %m"); + socket_enter_stop_post(s, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_listening(Socket *s) { + int r; + assert(s); + + r = socket_watch_fds(s); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to watch sockets: %m"); + goto fail; + } + + socket_set_state(s, SOCKET_LISTENING); + return; + +fail: + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_start_post(Socket *s) { + int r; + assert(s); + + socket_unwatch_control_pid(s); + s->control_command_id = SOCKET_EXEC_START_POST; + s->control_command = s->exec_command[SOCKET_EXEC_START_POST]; + + if (s->control_command) { + r = socket_spawn(s, s->control_command, &s->control_pid); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to run 'start-post' task: %m"); + goto fail; + } + + socket_set_state(s, SOCKET_START_POST); + } else + socket_enter_listening(s); + + return; + +fail: + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_start_chown(Socket *s) { + int r; + + assert(s); + + r = socket_open_fds(s); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to listen on sockets: %m"); + goto fail; + } + + if (!isempty(s->user) || !isempty(s->group)) { + + socket_unwatch_control_pid(s); + s->control_command_id = SOCKET_EXEC_START_CHOWN; + s->control_command = NULL; + + r = socket_chown(s, &s->control_pid); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to fork 'start-chown' task: %m"); + goto fail; + } + + socket_set_state(s, SOCKET_START_CHOWN); + } else + socket_enter_start_post(s); + + return; + +fail: + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_start_pre(Socket *s) { + int r; + assert(s); + + socket_unwatch_control_pid(s); + s->control_command_id = SOCKET_EXEC_START_PRE; + s->control_command = s->exec_command[SOCKET_EXEC_START_PRE]; + + if (s->control_command) { + r = socket_spawn(s, s->control_command, &s->control_pid); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to run 'start-pre' task: %m"); + goto fail; + } + + socket_set_state(s, SOCKET_START_PRE); + } else + socket_enter_start_chown(s); + + return; + +fail: + socket_enter_dead(s, SOCKET_FAILURE_RESOURCES); +} + +static void flush_ports(Socket *s) { + SocketPort *p; + + /* Flush all incoming traffic, regardless if actual bytes or new connections, so that this socket isn't busy + * anymore */ + + LIST_FOREACH(port, p, s->ports) { + if (p->fd < 0) + continue; + + (void) flush_accept(p->fd); + (void) flush_fd(p->fd); + } +} + +static void socket_enter_running(Socket *s, int cfd) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + /* Note that this call takes possession of the connection fd passed. It either has to assign it somewhere or + * close it. */ + + assert(s); + + /* We don't take connections anymore if we are supposed to shut down anyway */ + if (unit_stop_pending(UNIT(s))) { + + log_unit_debug(UNIT(s), "Suppressing connection request since unit stop is scheduled."); + + if (cfd >= 0) + cfd = safe_close(cfd); + else + flush_ports(s); + + return; + } + + if (!ratelimit_test(&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; + } + + if (cfd < 0) { + Iterator i; + Unit *other; + bool pending = false; + + /* If there's already a start pending don't bother to + * do anything */ + SET_FOREACH(other, UNIT(s)->dependencies[UNIT_TRIGGERS], i) + if (unit_active_or_pending(other)) { + pending = true; + break; + } + + if (!pending) { + if (!UNIT_ISSET(s->service)) { + log_unit_error(UNIT(s), "Service to activate vanished, refusing activation."); + r = -ENOENT; + goto fail; + } + + r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT_DEREF(s->service), JOB_REPLACE, &error, NULL); + if (r < 0) + goto fail; + } + + socket_set_state(s, SOCKET_RUNNING); + } else { + _cleanup_free_ char *prefix = NULL, *instance = NULL, *name = NULL; + Service *service; + + if (s->n_connections >= s->max_connections) { + log_unit_warning(UNIT(s), "Too many incoming connections (%u), refusing connection attempt.", s->n_connections); + safe_close(cfd); + return; + } + + r = socket_instantiate_service(s); + if (r < 0) + goto fail; + + r = instance_from_socket(cfd, s->n_accepted, &instance); + if (r < 0) { + if (r != -ENOTCONN) + goto fail; + + /* 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; + } + + r = unit_name_to_prefix(UNIT(s)->id, &prefix); + if (r < 0) + goto fail; + + r = unit_name_build(prefix, instance, ".service", &name); + if (r < 0) + goto fail; + + r = unit_add_name(UNIT_DEREF(s->service), name); + if (r < 0) + goto fail; + + service = SERVICE(UNIT_DEREF(s->service)); + unit_ref_unset(&s->service); + + s->n_accepted++; + unit_choose_id(UNIT(service), name); + + r = service_set_socket_fd(service, cfd, s, s->selinux_context_from_net); + if (r < 0) + goto fail; + + cfd = -1; /* We passed ownership of the fd to the service now. Forget it here. */ + s->n_connections++; + + r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT(service), JOB_REPLACE, &error, NULL); + if (r < 0) { + /* We failed to activate the new service, but it still exists. Let's make sure the service + * closes and forgets the connection fd again, immediately. */ + service_close_socket_fd(service); + goto fail; + } + + /* Notify clients about changed counters */ + unit_add_to_dbus_queue(UNIT(s)); + } + + 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", + bus_error_message(&error, r)); + + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); + safe_close(cfd); +} + +static void socket_run_next(Socket *s) { + int r; + + assert(s); + assert(s->control_command); + assert(s->control_command->command_next); + + socket_unwatch_control_pid(s); + + s->control_command = s->control_command->command_next; + + r = socket_spawn(s, s->control_command, &s->control_pid); + if (r < 0) + goto fail; + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run next task: %m"); + + if (s->state == SOCKET_START_POST) + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); + else if (s->state == SOCKET_STOP_POST) + socket_enter_dead(s, SOCKET_FAILURE_RESOURCES); + else + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_RESOURCES); +} + +static int socket_start(Unit *u) { + Socket *s = SOCKET(u); + int r; + + assert(s); + + /* We cannot fulfill this request right now, try again later + * please! */ + if (IN_SET(s->state, + SOCKET_STOP_PRE, + SOCKET_STOP_PRE_SIGKILL, + SOCKET_STOP_PRE_SIGTERM, + SOCKET_STOP_POST, + SOCKET_FINAL_SIGTERM, + SOCKET_FINAL_SIGKILL)) + return -EAGAIN; + + /* Already on it! */ + if (IN_SET(s->state, + SOCKET_START_PRE, + SOCKET_START_CHOWN, + SOCKET_START_POST)) + return 0; + + /* Cannot run this without the service being around */ + if (UNIT_ISSET(s->service)) { + Service *service; + + service = SERVICE(UNIT_DEREF(s->service)); + + if (UNIT(service)->load_state != UNIT_LOADED) { + log_unit_error(u, "Socket service %s not loaded, refusing.", UNIT(service)->id); + return -ENOENT; + } + + /* If the service is already active we cannot start the + * socket */ + if (service->state != SERVICE_DEAD && + service->state != SERVICE_FAILED && + service->state != SERVICE_AUTO_RESTART) { + log_unit_error(u, "Socket service %s already active, refusing.", UNIT(service)->id); + return -EBUSY; + } + } + + assert(s->state == SOCKET_DEAD || s->state == SOCKET_FAILED); + + r = unit_start_limit_test(u); + if (r < 0) { + socket_enter_dead(s, SOCKET_FAILURE_START_LIMIT_HIT); + return r; + } + + s->result = SOCKET_SUCCESS; + s->reset_cpu_usage = true; + + socket_enter_start_pre(s); + + return 1; +} + +static int socket_stop(Unit *u) { + Socket *s = SOCKET(u); + + assert(s); + + /* Already on it */ + if (IN_SET(s->state, + SOCKET_STOP_PRE, + SOCKET_STOP_PRE_SIGTERM, + SOCKET_STOP_PRE_SIGKILL, + SOCKET_STOP_POST, + SOCKET_FINAL_SIGTERM, + SOCKET_FINAL_SIGKILL)) + return 0; + + /* If there's already something running we go directly into + * kill mode. */ + if (IN_SET(s->state, + SOCKET_START_PRE, + SOCKET_START_CHOWN, + SOCKET_START_POST)) { + socket_enter_signal(s, SOCKET_STOP_PRE_SIGTERM, SOCKET_SUCCESS); + return -EAGAIN; + } + + assert(s->state == SOCKET_LISTENING || s->state == SOCKET_RUNNING); + + socket_enter_stop_pre(s, SOCKET_SUCCESS); + return 1; +} + +static int socket_serialize(Unit *u, FILE *f, FDSet *fds) { + Socket *s = SOCKET(u); + SocketPort *p; + int r; + + assert(u); + assert(f); + assert(fds); + + 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); + + if (s->control_pid > 0) + unit_serialize_item_format(u, f, "control-pid", PID_FMT, s->control_pid); + + if (s->control_command_id >= 0) + unit_serialize_item(u, f, "control-command", socket_exec_command_to_string(s->control_command_id)); + + LIST_FOREACH(port, p, s->ports) { + int copy; + + if (p->fd < 0) + continue; + + copy = fdset_put_dup(fds, p->fd); + if (copy < 0) + return copy; + + if (p->type == SOCKET_SOCKET) { + _cleanup_free_ char *t = NULL; + + r = socket_address_print(&p->address, &t); + if (r < 0) + return r; + + if (socket_address_family(&p->address) == AF_NETLINK) + unit_serialize_item_format(u, f, "netlink", "%i %s", copy, t); + else + unit_serialize_item_format(u, f, "socket", "%i %i %s", copy, p->address.type, t); + + } else if (p->type == SOCKET_SPECIAL) + unit_serialize_item_format(u, f, "special", "%i %s", copy, p->path); + else if (p->type == SOCKET_MQUEUE) + unit_serialize_item_format(u, f, "mqueue", "%i %s", copy, p->path); + else if (p->type == SOCKET_USB_FUNCTION) + unit_serialize_item_format(u, f, "ffs", "%i %s", copy, p->path); + else { + assert(p->type == SOCKET_FIFO); + unit_serialize_item_format(u, f, "fifo", "%i %s", copy, p->path); + } + } + + return 0; +} + +static int socket_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Socket *s = SOCKET(u); + + assert(u); + assert(key); + assert(value); + + if (streq(key, "state")) { + SocketState state; + + state = socket_state_from_string(value); + if (state < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + s->deserialized_state = state; + } else if (streq(key, "result")) { + SocketResult f; + + f = socket_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse result value: %s", value); + else if (f != SOCKET_SUCCESS) + s->result = f; + + } else if (streq(key, "n-accepted")) { + unsigned k; + + if (safe_atou(value, &k) < 0) + log_unit_debug(u, "Failed to parse n-accepted value: %s", value); + else + s->n_accepted += k; + } else if (streq(key, "control-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_unit_debug(u, "Failed to parse control-pid value: %s", value); + else + s->control_pid = pid; + } else if (streq(key, "control-command")) { + SocketExecCommand id; + + id = socket_exec_command_from_string(value); + if (id < 0) + log_unit_debug(u, "Failed to parse exec-command value: %s", value); + else { + s->control_command_id = id; + s->control_command = s->exec_command[id]; + } + } else if (streq(key, "fifo")) { + int fd, skip = 0; + SocketPort *p; + + if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse fifo value: %s", value); + else { + + LIST_FOREACH(port, p, s->ports) + if (p->type == SOCKET_FIFO && + path_equal_or_files_same(p->path, value+skip)) + break; + + if (p) { + safe_close(p->fd); + p->fd = fdset_remove(fds, fd); + } + } + + } else if (streq(key, "special")) { + int fd, skip = 0; + SocketPort *p; + + if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse special value: %s", value); + else { + + LIST_FOREACH(port, p, s->ports) + if (p->type == SOCKET_SPECIAL && + path_equal_or_files_same(p->path, value+skip)) + break; + + if (p) { + safe_close(p->fd); + p->fd = fdset_remove(fds, fd); + } + } + + } else if (streq(key, "mqueue")) { + int fd, skip = 0; + SocketPort *p; + + if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse mqueue value: %s", value); + else { + + LIST_FOREACH(port, p, s->ports) + if (p->type == SOCKET_MQUEUE && + streq(p->path, value+skip)) + break; + + if (p) { + safe_close(p->fd); + p->fd = fdset_remove(fds, fd); + } + } + + } else if (streq(key, "socket")) { + int fd, type, skip = 0; + SocketPort *p; + + if (sscanf(value, "%i %i %n", &fd, &type, &skip) < 2 || fd < 0 || type < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse socket value: %s", value); + else { + + LIST_FOREACH(port, p, s->ports) + if (socket_address_is(&p->address, value+skip, type)) + break; + + if (p) { + safe_close(p->fd); + p->fd = fdset_remove(fds, fd); + } + } + + } else if (streq(key, "netlink")) { + int fd, skip = 0; + SocketPort *p; + + if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse socket value: %s", value); + else { + + LIST_FOREACH(port, p, s->ports) + if (socket_address_is_netlink(&p->address, value+skip)) + break; + + if (p) { + safe_close(p->fd); + p->fd = fdset_remove(fds, fd); + } + } + + } else if (streq(key, "ffs")) { + int fd, skip = 0; + SocketPort *p; + + if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse ffs value: %s", value); + else { + + LIST_FOREACH(port, p, s->ports) + if (p->type == SOCKET_USB_FUNCTION && + path_equal_or_files_same(p->path, value+skip)) + break; + + if (p) { + safe_close(p->fd); + p->fd = fdset_remove(fds, fd); + } + } + + } else + log_unit_debug(UNIT(s), "Unknown serialization key: %s", key); + + return 0; +} + +static void socket_distribute_fds(Unit *u, FDSet *fds) { + Socket *s = SOCKET(u); + SocketPort *p; + + assert(u); + + LIST_FOREACH(port, p, s->ports) { + Iterator i; + int fd; + + if (p->type != SOCKET_SOCKET) + continue; + + if (p->fd >= 0) + continue; + + FDSET_FOREACH(fd, fds, i) { + if (socket_address_matches_fd(&p->address, fd)) { + p->fd = fdset_remove(fds, fd); + s->deserialized_state = SOCKET_LISTENING; + break; + } + } + } +} + +_pure_ static UnitActiveState socket_active_state(Unit *u) { + assert(u); + + return state_translation_table[SOCKET(u)->state]; +} + +_pure_ static const char *socket_sub_state_to_string(Unit *u) { + assert(u); + + return socket_state_to_string(SOCKET(u)->state); +} + +const char* socket_port_type_to_string(SocketPort *p) { + + assert(p); + + switch (p->type) { + + case SOCKET_SOCKET: + + switch (p->address.type) { + + case SOCK_STREAM: + return "Stream"; + + case SOCK_DGRAM: + return "Datagram"; + + case SOCK_SEQPACKET: + return "SequentialPacket"; + + case SOCK_RAW: + if (socket_address_family(&p->address) == AF_NETLINK) + return "Netlink"; + + default: + return NULL; + } + + case SOCKET_SPECIAL: + return "Special"; + + case SOCKET_MQUEUE: + return "MessageQueue"; + + case SOCKET_FIFO: + return "FIFO"; + + case SOCKET_USB_FUNCTION: + return "USBFunction"; + + default: + return NULL; + } +} + +_pure_ static bool socket_check_gc(Unit *u) { + Socket *s = SOCKET(u); + + assert(u); + + return s->n_connections > 0; +} + +static int socket_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + SocketPort *p = userdata; + int cfd = -1; + + assert(p); + assert(fd >= 0); + + if (p->socket->state != SOCKET_LISTENING) + return 0; + + log_unit_debug(UNIT(p->socket), "Incoming traffic"); + + if (revents != EPOLLIN) { + + if (revents & EPOLLHUP) + log_unit_error(UNIT(p->socket), "Got POLLHUP on a listening socket. The service probably invoked shutdown() on it, and should better not do that."); + else + log_unit_error(UNIT(p->socket), "Got unexpected poll event (0x%x) on socket.", revents); + goto fail; + } + + if (p->socket->accept && + p->type == SOCKET_SOCKET && + socket_address_can_accept(&p->address)) { + + for (;;) { + + cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK); + if (cfd < 0) { + + if (errno == EINTR) + continue; + + log_unit_error_errno(UNIT(p->socket), errno, "Failed to accept socket: %m"); + goto fail; + } + + break; + } + + socket_apply_socket_options(p->socket, cfd); + } + + socket_enter_running(p->socket, cfd); + return 0; + +fail: + socket_enter_stop_pre(p->socket, SOCKET_FAILURE_RESOURCES); + return 0; +} + +static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) { + Socket *s = SOCKET(u); + SocketResult f; + + assert(s); + assert(pid >= 0); + + if (pid != s->control_pid) + return; + + s->control_pid = 0; + + if (is_clean_exit(code, status, NULL)) + f = SOCKET_SUCCESS; + else if (code == CLD_EXITED) + f = SOCKET_FAILURE_EXIT_CODE; + else if (code == CLD_KILLED) + f = SOCKET_FAILURE_SIGNAL; + else if (code == CLD_DUMPED) + f = SOCKET_FAILURE_CORE_DUMP; + else + assert_not_reached("Unknown sigchld code"); + + if (s->control_command) { + exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status); + + if (s->control_command->ignore) + f = SOCKET_SUCCESS; + } + + log_unit_full(u, f == SOCKET_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, + "Control process exited, code=%s status=%i", + sigchld_code_to_string(code), status); + + if (f != SOCKET_SUCCESS) + s->result = f; + + if (s->control_command && + s->control_command->command_next && + f == SOCKET_SUCCESS) { + + log_unit_debug(u, "Running next command for state %s", socket_state_to_string(s->state)); + socket_run_next(s); + } else { + s->control_command = NULL; + s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; + + /* No further commands for this step, so let's figure + * out what to do next */ + + log_unit_debug(u, "Got final SIGCHLD for state %s", socket_state_to_string(s->state)); + + switch (s->state) { + + case SOCKET_START_PRE: + if (f == SOCKET_SUCCESS) + socket_enter_start_chown(s); + else + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, f); + break; + + case SOCKET_START_CHOWN: + if (f == SOCKET_SUCCESS) + socket_enter_start_post(s); + else + socket_enter_stop_pre(s, f); + break; + + case SOCKET_START_POST: + if (f == SOCKET_SUCCESS) + socket_enter_listening(s); + else + socket_enter_stop_pre(s, f); + break; + + case SOCKET_STOP_PRE: + case SOCKET_STOP_PRE_SIGTERM: + case SOCKET_STOP_PRE_SIGKILL: + socket_enter_stop_post(s, f); + break; + + case SOCKET_STOP_POST: + case SOCKET_FINAL_SIGTERM: + case SOCKET_FINAL_SIGKILL: + socket_enter_dead(s, f); + break; + + default: + assert_not_reached("Uh, control process died at wrong time."); + } + } + + /* Notify clients about changed exit status */ + unit_add_to_dbus_queue(u); +} + +static int socket_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { + Socket *s = SOCKET(userdata); + + assert(s); + assert(s->timer_event_source == source); + + switch (s->state) { + + case SOCKET_START_PRE: + log_unit_warning(UNIT(s), "Starting timed out. Terminating."); + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_START_CHOWN: + case SOCKET_START_POST: + log_unit_warning(UNIT(s), "Starting timed out. Stopping."); + socket_enter_stop_pre(s, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_STOP_PRE: + log_unit_warning(UNIT(s), "Stopping timed out. Terminating."); + socket_enter_signal(s, SOCKET_STOP_PRE_SIGTERM, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_STOP_PRE_SIGTERM: + if (s->kill_context.send_sigkill) { + log_unit_warning(UNIT(s), "Stopping timed out. Killing."); + socket_enter_signal(s, SOCKET_STOP_PRE_SIGKILL, SOCKET_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(s), "Stopping timed out. Skipping SIGKILL. Ignoring."); + socket_enter_stop_post(s, SOCKET_FAILURE_TIMEOUT); + } + break; + + case SOCKET_STOP_PRE_SIGKILL: + log_unit_warning(UNIT(s), "Processes still around after SIGKILL. Ignoring."); + socket_enter_stop_post(s, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_STOP_POST: + log_unit_warning(UNIT(s), "Stopping timed out (2). Terminating."); + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_FINAL_SIGTERM: + if (s->kill_context.send_sigkill) { + log_unit_warning(UNIT(s), "Stopping timed out (2). Killing."); + socket_enter_signal(s, SOCKET_FINAL_SIGKILL, SOCKET_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(s), "Stopping timed out (2). Skipping SIGKILL. Ignoring."); + socket_enter_dead(s, SOCKET_FAILURE_TIMEOUT); + } + break; + + case SOCKET_FINAL_SIGKILL: + log_unit_warning(UNIT(s), "Still around after SIGKILL (2). Entering failed mode."); + socket_enter_dead(s, SOCKET_FAILURE_TIMEOUT); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } + + return 0; +} + +int socket_collect_fds(Socket *s, int **fds) { + int *rfds, k = 0, n = 0; + SocketPort *p; + + assert(s); + assert(fds); + + /* Called from the service code for requesting our fds */ + + LIST_FOREACH(port, p, s->ports) { + if (p->fd >= 0) + n++; + n += p->n_auxiliary_fds; + } + + if (n <= 0) { + *fds = NULL; + return 0; + } + + rfds = new(int, n); + if (!rfds) + return -ENOMEM; + + LIST_FOREACH(port, p, s->ports) { + int i; + + if (p->fd >= 0) + rfds[k++] = p->fd; + for (i = 0; i < p->n_auxiliary_fds; ++i) + rfds[k++] = p->auxiliary_fds[i]; + } + + assert(k == n); + + *fds = rfds; + return n; +} + +static void socket_reset_failed(Unit *u) { + Socket *s = SOCKET(u); + + assert(s); + + if (s->state == SOCKET_FAILED) + socket_set_state(s, SOCKET_DEAD); + + s->result = SOCKET_SUCCESS; +} + +void socket_connection_unref(Socket *s) { + assert(s); + + /* The service is dead. Yay! + * + * This is strictly for one-instance-per-connection + * services. */ + + assert(s->n_connections > 0); + s->n_connections--; + + log_unit_debug(UNIT(s), "One connection closed, %u left.", s->n_connections); +} + +static void socket_trigger_notify(Unit *u, Unit *other) { + Socket *s = SOCKET(u); + + assert(u); + assert(other); + + /* Filter out invocations with bogus state */ + if (other->load_state != UNIT_LOADED || other->type != UNIT_SERVICE) + return; + + /* Don't propagate state changes from the service if we are already down */ + if (!IN_SET(s->state, SOCKET_RUNNING, SOCKET_LISTENING)) + return; + + /* We don't care for the service state if we are in Accept=yes mode */ + if (s->accept) + return; + + /* Propagate start limit hit state */ + if (other->start_limit_hit) { + socket_enter_stop_pre(s, SOCKET_FAILURE_SERVICE_START_LIMIT_HIT); + return; + } + + /* Don't propagate anything if there's still a job queued */ + if (other->job) + return; + + if (IN_SET(SERVICE(other)->state, + SERVICE_DEAD, SERVICE_FAILED, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, + SERVICE_AUTO_RESTART)) + socket_enter_listening(s); + + if (SERVICE(other)->state == SERVICE_RUNNING) + socket_set_state(s, SOCKET_RUNNING); +} + +static int socket_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { + return unit_kill_common(u, who, signo, -1, SOCKET(u)->control_pid, error); +} + +static int socket_get_timeout(Unit *u, usec_t *timeout) { + Socket *s = SOCKET(u); + usec_t t; + int r; + + if (!s->timer_event_source) + return 0; + + r = sd_event_source_get_time(s->timer_event_source, &t); + if (r < 0) + return r; + if (t == USEC_INFINITY) + return 0; + + *timeout = t; + return 1; +} + +char *socket_fdname(Socket *s) { + assert(s); + + /* Returns the name to use for $LISTEN_NAMES. If the user + * didn't specify anything specifically, use the socket unit's + * name as fallback. */ + + if (s->fdname) + return s->fdname; + + return UNIT(s)->id; +} + +static int socket_control_pid(Unit *u) { + Socket *s = SOCKET(u); + + assert(s); + + return s->control_pid; +} + +static const char* const socket_exec_command_table[_SOCKET_EXEC_COMMAND_MAX] = { + [SOCKET_EXEC_START_PRE] = "StartPre", + [SOCKET_EXEC_START_CHOWN] = "StartChown", + [SOCKET_EXEC_START_POST] = "StartPost", + [SOCKET_EXEC_STOP_PRE] = "StopPre", + [SOCKET_EXEC_STOP_POST] = "StopPost" +}; + +DEFINE_STRING_TABLE_LOOKUP(socket_exec_command, SocketExecCommand); + +static const char* const socket_result_table[_SOCKET_RESULT_MAX] = { + [SOCKET_SUCCESS] = "success", + [SOCKET_FAILURE_RESOURCES] = "resources", + [SOCKET_FAILURE_TIMEOUT] = "timeout", + [SOCKET_FAILURE_EXIT_CODE] = "exit-code", + [SOCKET_FAILURE_SIGNAL] = "signal", + [SOCKET_FAILURE_CORE_DUMP] = "core-dump", + [SOCKET_FAILURE_START_LIMIT_HIT] = "start-limit-hit", + [SOCKET_FAILURE_TRIGGER_LIMIT_HIT] = "trigger-limit-hit", + [SOCKET_FAILURE_SERVICE_START_LIMIT_HIT] = "service-start-limit-hit" +}; + +DEFINE_STRING_TABLE_LOOKUP(socket_result, SocketResult); + +const UnitVTable socket_vtable = { + .object_size = sizeof(Socket), + .exec_context_offset = offsetof(Socket, exec_context), + .cgroup_context_offset = offsetof(Socket, cgroup_context), + .kill_context_offset = offsetof(Socket, kill_context), + .exec_runtime_offset = offsetof(Socket, exec_runtime), + + .sections = + "Unit\0" + "Socket\0" + "Install\0", + .private_section = "Socket", + + .init = socket_init, + .done = socket_done, + .load = socket_load, + + .coldplug = socket_coldplug, + + .dump = socket_dump, + + .start = socket_start, + .stop = socket_stop, + + .kill = socket_kill, + + .get_timeout = socket_get_timeout, + + .serialize = socket_serialize, + .deserialize_item = socket_deserialize_item, + .distribute_fds = socket_distribute_fds, + + .active_state = socket_active_state, + .sub_state_to_string = socket_sub_state_to_string, + + .check_gc = socket_check_gc, + + .sigchld_event = socket_sigchld_event, + + .trigger_notify = socket_trigger_notify, + + .reset_failed = socket_reset_failed, + + .control_pid = socket_control_pid, + + .bus_vtable = bus_socket_vtable, + .bus_set_property = bus_socket_set_property, + .bus_commit_properties = bus_socket_commit_properties, + + .status_message_formats = { + /*.starting_stopping = { + [0] = "Starting socket %s...", + [1] = "Stopping socket %s...", + },*/ + .finished_start_job = { + [JOB_DONE] = "Listening on %s.", + [JOB_FAILED] = "Failed to listen on %s.", + [JOB_TIMEOUT] = "Timed out starting %s.", + }, + .finished_stop_job = { + [JOB_DONE] = "Closed %s.", + [JOB_FAILED] = "Failed stopping %s.", + [JOB_TIMEOUT] = "Timed out stopping %s.", + }, + }, +}; diff --git a/src/libcore/socket.h b/src/libcore/socket.h new file mode 100644 index 0000000000..0f1ac69c6f --- /dev/null +++ b/src/libcore/socket.h @@ -0,0 +1,185 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct Socket Socket; + +#include "mount.h" +#include "service.h" +#include "socket-util.h" + +typedef enum SocketExecCommand { + SOCKET_EXEC_START_PRE, + SOCKET_EXEC_START_CHOWN, + SOCKET_EXEC_START_POST, + SOCKET_EXEC_STOP_PRE, + SOCKET_EXEC_STOP_POST, + _SOCKET_EXEC_COMMAND_MAX, + _SOCKET_EXEC_COMMAND_INVALID = -1 +} SocketExecCommand; + +typedef enum SocketType { + SOCKET_SOCKET, + SOCKET_FIFO, + SOCKET_SPECIAL, + SOCKET_MQUEUE, + SOCKET_USB_FUNCTION, + _SOCKET_FIFO_MAX, + _SOCKET_FIFO_INVALID = -1 +} SocketType; + +typedef enum SocketResult { + SOCKET_SUCCESS, + SOCKET_FAILURE_RESOURCES, + SOCKET_FAILURE_TIMEOUT, + SOCKET_FAILURE_EXIT_CODE, + SOCKET_FAILURE_SIGNAL, + SOCKET_FAILURE_CORE_DUMP, + SOCKET_FAILURE_START_LIMIT_HIT, + SOCKET_FAILURE_TRIGGER_LIMIT_HIT, + SOCKET_FAILURE_SERVICE_START_LIMIT_HIT, + _SOCKET_RESULT_MAX, + _SOCKET_RESULT_INVALID = -1 +} SocketResult; + +typedef struct SocketPort { + Socket *socket; + + SocketType type; + int fd; + int *auxiliary_fds; + int n_auxiliary_fds; + + SocketAddress address; + char *path; + sd_event_source *event_source; + + LIST_FIELDS(struct SocketPort, port); +} SocketPort; + +struct Socket { + Unit meta; + + LIST_HEAD(SocketPort, ports); + + unsigned n_accepted; + unsigned n_connections; + unsigned max_connections; + + unsigned backlog; + unsigned keep_alive_cnt; + usec_t timeout_usec; + usec_t keep_alive_time; + usec_t keep_alive_interval; + usec_t defer_accept; + + ExecCommand* exec_command[_SOCKET_EXEC_COMMAND_MAX]; + ExecContext exec_context; + KillContext kill_context; + CGroupContext cgroup_context; + ExecRuntime *exec_runtime; + + /* For Accept=no sockets refers to the one service we'll + activate. For Accept=yes sockets is either NULL, or filled + when the next service we spawn. */ + UnitRef service; + + SocketState state, deserialized_state; + + sd_event_source *timer_event_source; + + ExecCommand* control_command; + SocketExecCommand control_command_id; + pid_t control_pid; + + mode_t directory_mode; + mode_t socket_mode; + + SocketResult result; + + char **symlinks; + + bool accept; + bool remove_on_stop; + bool writable; + + int socket_protocol; + + /* Socket options */ + bool keep_alive; + bool no_delay; + bool free_bind; + bool transparent; + bool broadcast; + bool pass_cred; + bool pass_sec; + + /* Only for INET6 sockets: issue IPV6_V6ONLY sockopt */ + SocketAddressBindIPv6Only bind_ipv6_only; + + int priority; + int mark; + size_t receive_buffer; + size_t send_buffer; + int ip_tos; + int ip_ttl; + size_t pipe_size; + char *bind_to_device; + char *tcp_congestion; + bool reuse_port; + long mq_maxmsg; + long mq_msgsize; + + char *smack; + char *smack_ip_in; + char *smack_ip_out; + + bool selinux_context_from_net; + + char *user, *group; + + bool reset_cpu_usage:1; + + char *fdname; + + RateLimit trigger_limit; +}; + +/* Called from the service code when collecting fds */ +int socket_collect_fds(Socket *s, int **fds); + +/* Called from the service code when a per-connection service ended */ +void socket_connection_unref(Socket *s); + +void socket_free_ports(Socket *s); + +int socket_instantiate_service(Socket *s); + +char *socket_fdname(Socket *s); + +extern const UnitVTable socket_vtable; + +const char* socket_exec_command_to_string(SocketExecCommand i) _const_; +SocketExecCommand socket_exec_command_from_string(const char *s) _pure_; + +const char* socket_result_to_string(SocketResult i) _const_; +SocketResult socket_result_from_string(const char *s) _pure_; + +const char* socket_port_type_to_string(SocketPort *p) _pure_; diff --git a/src/libcore/swap.c b/src/libcore/swap.c new file mode 100644 index 0000000000..a532b15be8 --- /dev/null +++ b/src/libcore/swap.c @@ -0,0 +1,1532 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include + +#include "libudev.h" + +#include "alloc-util.h" +#include "dbus-swap.h" +#include "escape.h" +#include "exit-status.h" +#include "fd-util.h" +#include "formats-util.h" +#include "fstab-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "special.h" +#include "string-table.h" +#include "string-util.h" +#include "swap.h" +#include "udev-util.h" +#include "unit-name.h" +#include "unit.h" +#include "virt.h" + +static const UnitActiveState state_translation_table[_SWAP_STATE_MAX] = { + [SWAP_DEAD] = UNIT_INACTIVE, + [SWAP_ACTIVATING] = UNIT_ACTIVATING, + [SWAP_ACTIVATING_DONE] = UNIT_ACTIVE, + [SWAP_ACTIVE] = UNIT_ACTIVE, + [SWAP_DEACTIVATING] = UNIT_DEACTIVATING, + [SWAP_ACTIVATING_SIGTERM] = UNIT_DEACTIVATING, + [SWAP_ACTIVATING_SIGKILL] = UNIT_DEACTIVATING, + [SWAP_DEACTIVATING_SIGTERM] = UNIT_DEACTIVATING, + [SWAP_DEACTIVATING_SIGKILL] = UNIT_DEACTIVATING, + [SWAP_FAILED] = UNIT_FAILED +}; + +static int swap_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); +static int swap_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); + +static void swap_unset_proc_swaps(Swap *s) { + assert(s); + + if (!s->from_proc_swaps) + return; + + s->parameters_proc_swaps.what = mfree(s->parameters_proc_swaps.what); + + s->from_proc_swaps = false; +} + +static int swap_set_devnode(Swap *s, const char *devnode) { + Hashmap *swaps; + Swap *first; + int r; + + assert(s); + + r = hashmap_ensure_allocated(&UNIT(s)->manager->swaps_by_devnode, &string_hash_ops); + if (r < 0) + return r; + + swaps = UNIT(s)->manager->swaps_by_devnode; + + if (s->devnode) { + first = hashmap_get(swaps, s->devnode); + + LIST_REMOVE(same_devnode, first, s); + if (first) + hashmap_replace(swaps, first->devnode, first); + else + hashmap_remove(swaps, s->devnode); + + s->devnode = mfree(s->devnode); + } + + if (devnode) { + s->devnode = strdup(devnode); + if (!s->devnode) + return -ENOMEM; + + first = hashmap_get(swaps, s->devnode); + LIST_PREPEND(same_devnode, first, s); + + return hashmap_replace(swaps, first->devnode, first); + } + + return 0; +} + +static void swap_init(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + assert(UNIT(s)->load_state == UNIT_STUB); + + s->timeout_usec = u->manager->default_timeout_start_usec; + + s->exec_context.std_output = u->manager->default_std_output; + s->exec_context.std_error = u->manager->default_std_error; + + s->parameters_proc_swaps.priority = s->parameters_fragment.priority = -1; + + s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; + + u->ignore_on_isolate = true; +} + +static void swap_unwatch_control_pid(Swap *s) { + assert(s); + + if (s->control_pid <= 0) + return; + + unit_unwatch_pid(UNIT(s), s->control_pid); + s->control_pid = 0; +} + +static void swap_done(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + + swap_unset_proc_swaps(s); + swap_set_devnode(s, NULL); + + s->what = mfree(s->what); + s->parameters_fragment.what = mfree(s->parameters_fragment.what); + s->parameters_fragment.options = mfree(s->parameters_fragment.options); + + s->exec_runtime = exec_runtime_unref(s->exec_runtime); + exec_command_done_array(s->exec_command, _SWAP_EXEC_COMMAND_MAX); + s->control_command = NULL; + + swap_unwatch_control_pid(s); + + s->timer_event_source = sd_event_source_unref(s->timer_event_source); +} + +static int swap_arm_timer(Swap *s, usec_t usec) { + int r; + + assert(s); + + if (s->timer_event_source) { + r = sd_event_source_set_time(s->timer_event_source, usec); + if (r < 0) + return r; + + return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); + } + + if (usec == USEC_INFINITY) + return 0; + + r = sd_event_add_time( + UNIT(s)->manager->event, + &s->timer_event_source, + CLOCK_MONOTONIC, + usec, 0, + swap_dispatch_timer, s); + if (r < 0) + return r; + + (void) sd_event_source_set_description(s->timer_event_source, "swap-timer"); + + return 0; +} + +static int swap_add_device_links(Swap *s) { + assert(s); + + if (!s->what) + return 0; + + if (!s->from_fragment) + return 0; + + if (is_device_path(s->what)) + return unit_add_node_link(UNIT(s), s->what, MANAGER_IS_SYSTEM(UNIT(s)->manager), UNIT_BINDS_TO); + else + /* File based swap devices need to be ordered after + * systemd-remount-fs.service, since they might need a + * writable file system. */ + return unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_REMOUNT_FS_SERVICE, NULL, true); +} + +static int swap_add_default_dependencies(Swap *s) { + int r; + + assert(s); + + if (!UNIT(s)->default_dependencies) + return 0; + + if (!MANAGER_IS_SYSTEM(UNIT(s)->manager)) + return 0; + + if (detect_container() > 0) + return 0; + + /* swap units generated for the swap dev links are missing the + * ordering dep against the swap target. */ + r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_SWAP_TARGET, NULL, true); + if (r < 0) + return r; + + return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); +} + +static int swap_verify(Swap *s) { + _cleanup_free_ char *e = NULL; + int r; + + if (UNIT(s)->load_state != UNIT_LOADED) + return 0; + + r = unit_name_from_path(s->what, ".swap", &e); + if (r < 0) + return log_unit_error_errno(UNIT(s), r, "Failed to generate unit name from path: %m"); + + if (!unit_has_name(UNIT(s), e)) { + log_unit_error(UNIT(s), "Value of What= and unit name do not match, not loading."); + return -EINVAL; + } + + if (s->exec_context.pam_name && s->kill_context.kill_mode != KILL_CONTROL_GROUP) { + log_unit_error(UNIT(s), "Unit has PAM enabled. Kill mode must be set to 'control-group'. Refusing to load."); + return -EINVAL; + } + + return 0; +} + +static int swap_load_devnode(Swap *s) { + _cleanup_udev_device_unref_ struct udev_device *d = NULL; + struct stat st; + const char *p; + + assert(s); + + if (stat(s->what, &st) < 0 || !S_ISBLK(st.st_mode)) + return 0; + + d = udev_device_new_from_devnum(UNIT(s)->manager->udev, 'b', st.st_rdev); + if (!d) + return 0; + + p = udev_device_get_devnode(d); + if (!p) + return 0; + + return swap_set_devnode(s, p); +} + +static int swap_load(Unit *u) { + int r; + Swap *s = SWAP(u); + + assert(s); + assert(u->load_state == UNIT_STUB); + + /* Load a .swap file */ + r = unit_load_fragment_and_dropin_optional(u); + if (r < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + + if (UNIT(s)->fragment_path) + s->from_fragment = true; + + if (!s->what) { + if (s->parameters_fragment.what) + s->what = strdup(s->parameters_fragment.what); + else if (s->parameters_proc_swaps.what) + s->what = strdup(s->parameters_proc_swaps.what); + else { + r = unit_name_to_path(u->id, &s->what); + if (r < 0) + return r; + } + + if (!s->what) + return -ENOMEM; + } + + path_kill_slashes(s->what); + + if (!UNIT(s)->description) { + r = unit_set_description(u, s->what); + if (r < 0) + return r; + } + + r = unit_require_mounts_for(UNIT(s), s->what); + if (r < 0) + return r; + + r = swap_add_device_links(s); + if (r < 0) + return r; + + r = swap_load_devnode(s); + if (r < 0) + return r; + + r = unit_patch_contexts(u); + if (r < 0) + return r; + + r = unit_add_exec_dependencies(u, &s->exec_context); + if (r < 0) + return r; + + r = unit_set_default_slice(u); + if (r < 0) + return r; + + r = swap_add_default_dependencies(s); + if (r < 0) + return r; + } + + return swap_verify(s); +} + +static int swap_setup_unit( + Manager *m, + const char *what, + const char *what_proc_swaps, + int priority, + bool set_flags) { + + _cleanup_free_ char *e = NULL; + bool delete = false; + Unit *u = NULL; + int r; + SwapParameters *p; + + assert(m); + assert(what); + assert(what_proc_swaps); + + r = unit_name_from_path(what, ".swap", &e); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to generate unit name from path: %m"); + + u = manager_get_unit(m, e); + + if (u && + SWAP(u)->from_proc_swaps && + !path_equal(SWAP(u)->parameters_proc_swaps.what, what_proc_swaps)) { + log_error("Swap %s appeared twice with different device paths %s and %s", e, SWAP(u)->parameters_proc_swaps.what, what_proc_swaps); + return -EEXIST; + } + + if (!u) { + delete = true; + + u = unit_new(m, sizeof(Swap)); + if (!u) + return log_oom(); + + r = unit_add_name(u, e); + if (r < 0) + goto fail; + + SWAP(u)->what = strdup(what); + if (!SWAP(u)->what) { + r = -ENOMEM; + goto fail; + } + + unit_add_to_load_queue(u); + } else + delete = false; + + p = &SWAP(u)->parameters_proc_swaps; + + if (!p->what) { + p->what = strdup(what_proc_swaps); + if (!p->what) { + r = -ENOMEM; + goto fail; + } + } + + if (set_flags) { + SWAP(u)->is_active = true; + SWAP(u)->just_activated = !SWAP(u)->from_proc_swaps; + } + + SWAP(u)->from_proc_swaps = true; + + p->priority = priority; + + unit_add_to_dbus_queue(u); + return 0; + +fail: + log_unit_warning_errno(u, r, "Failed to load swap unit: %m"); + + if (delete && u) + unit_free(u); + + return r; +} + +static int swap_process_new(Manager *m, const char *device, int prio, bool set_flags) { + _cleanup_udev_device_unref_ struct udev_device *d = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + const char *dn; + struct stat st; + int r; + + assert(m); + + r = swap_setup_unit(m, device, device, prio, set_flags); + if (r < 0) + return r; + + /* If this is a block device, then let's add duplicates for + * all other names of this block device */ + if (stat(device, &st) < 0 || !S_ISBLK(st.st_mode)) + return 0; + + d = udev_device_new_from_devnum(m->udev, 'b', st.st_rdev); + if (!d) + return 0; + + /* Add the main device node */ + dn = udev_device_get_devnode(d); + if (dn && !streq(dn, device)) + swap_setup_unit(m, dn, device, prio, set_flags); + + /* Add additional units for all symlinks */ + first = udev_device_get_devlinks_list_entry(d); + udev_list_entry_foreach(item, first) { + const char *p; + + /* Don't bother with the /dev/block links */ + p = udev_list_entry_get_name(item); + + if (streq(p, device)) + continue; + + if (path_startswith(p, "/dev/block/")) + continue; + + if (stat(p, &st) >= 0) + if (!S_ISBLK(st.st_mode) || + st.st_rdev != udev_device_get_devnum(d)) + continue; + + swap_setup_unit(m, p, device, prio, set_flags); + } + + return r; +} + +static void swap_set_state(Swap *s, SwapState state) { + SwapState old_state; + Swap *other; + + assert(s); + + old_state = s->state; + s->state = state; + + if (state != SWAP_ACTIVATING && + state != SWAP_ACTIVATING_SIGTERM && + state != SWAP_ACTIVATING_SIGKILL && + state != SWAP_ACTIVATING_DONE && + state != SWAP_DEACTIVATING && + state != SWAP_DEACTIVATING_SIGTERM && + state != SWAP_DEACTIVATING_SIGKILL) { + s->timer_event_source = sd_event_source_unref(s->timer_event_source); + swap_unwatch_control_pid(s); + s->control_command = NULL; + s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; + } + + if (state != old_state) + log_unit_debug(UNIT(s), "Changed %s -> %s", swap_state_to_string(old_state), swap_state_to_string(state)); + + unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); + + /* If there other units for the same device node have a job + queued it might be worth checking again if it is runnable + now. This is necessary, since swap_start() refuses + operation with EAGAIN if there's already another job for + the same device node queued. */ + LIST_FOREACH_OTHERS(same_devnode, other, s) + if (UNIT(other)->job) + job_add_to_run_queue(UNIT(other)->job); +} + +static int swap_coldplug(Unit *u) { + Swap *s = SWAP(u); + SwapState new_state = SWAP_DEAD; + int r; + + assert(s); + assert(s->state == SWAP_DEAD); + + if (s->deserialized_state != s->state) + new_state = s->deserialized_state; + else if (s->from_proc_swaps) + new_state = SWAP_ACTIVE; + + if (new_state == s->state) + return 0; + + if (s->control_pid > 0 && + pid_is_unwaited(s->control_pid) && + IN_SET(new_state, + SWAP_ACTIVATING, + SWAP_ACTIVATING_SIGTERM, + SWAP_ACTIVATING_SIGKILL, + SWAP_ACTIVATING_DONE, + SWAP_DEACTIVATING, + SWAP_DEACTIVATING_SIGTERM, + SWAP_DEACTIVATING_SIGKILL)) { + + r = unit_watch_pid(UNIT(s), s->control_pid); + if (r < 0) + return r; + + r = swap_arm_timer(s, usec_add(u->state_change_timestamp.monotonic, s->timeout_usec)); + if (r < 0) + return r; + } + + swap_set_state(s, new_state); + return 0; +} + +static void swap_dump(Unit *u, FILE *f, const char *prefix) { + Swap *s = SWAP(u); + SwapParameters *p; + + assert(s); + assert(f); + + if (s->from_proc_swaps) + p = &s->parameters_proc_swaps; + else if (s->from_fragment) + p = &s->parameters_fragment; + else + p = NULL; + + fprintf(f, + "%sSwap State: %s\n" + "%sResult: %s\n" + "%sWhat: %s\n" + "%sFrom /proc/swaps: %s\n" + "%sFrom fragment: %s\n", + prefix, swap_state_to_string(s->state), + prefix, swap_result_to_string(s->result), + prefix, s->what, + prefix, yes_no(s->from_proc_swaps), + prefix, yes_no(s->from_fragment)); + + if (s->devnode) + fprintf(f, "%sDevice Node: %s\n", prefix, s->devnode); + + if (p) + fprintf(f, + "%sPriority: %i\n" + "%sOptions: %s\n", + prefix, p->priority, + prefix, strempty(p->options)); + + if (s->control_pid > 0) + fprintf(f, + "%sControl PID: "PID_FMT"\n", + prefix, s->control_pid); + + exec_context_dump(&s->exec_context, f, prefix); + kill_context_dump(&s->kill_context, f, prefix); +} + +static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) { + pid_t pid; + int r; + ExecParameters exec_params = { + .apply_permissions = true, + .apply_chroot = true, + .apply_tty_stdin = true, + .stdin_fd = -1, + .stdout_fd = -1, + .stderr_fd = -1, + }; + + assert(s); + assert(c); + assert(_pid); + + (void) unit_realize_cgroup(UNIT(s)); + if (s->reset_cpu_usage) { + (void) unit_reset_cpu_usage(UNIT(s)); + s->reset_cpu_usage = false; + } + + r = unit_setup_exec_runtime(UNIT(s)); + if (r < 0) + goto fail; + + r = swap_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); + if (r < 0) + goto fail; + + exec_params.environment = UNIT(s)->manager->environment; + exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn; + exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; + exec_params.cgroup_path = UNIT(s)->cgroup_path; + exec_params.cgroup_delegate = s->cgroup_context.delegate; + exec_params.runtime_prefix = manager_get_runtime_prefix(UNIT(s)->manager); + + r = exec_spawn(UNIT(s), + c, + &s->exec_context, + &exec_params, + s->exec_runtime, + &pid); + if (r < 0) + goto fail; + + r = unit_watch_pid(UNIT(s), pid); + if (r < 0) + /* FIXME: we need to do something here */ + goto fail; + + *_pid = pid; + + return 0; + +fail: + s->timer_event_source = sd_event_source_unref(s->timer_event_source); + return r; +} + +static void swap_enter_dead(Swap *s, SwapResult f) { + assert(s); + + if (f != SWAP_SUCCESS) + s->result = f; + + exec_runtime_destroy(s->exec_runtime); + s->exec_runtime = exec_runtime_unref(s->exec_runtime); + + exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager)); + + swap_set_state(s, s->result != SWAP_SUCCESS ? SWAP_FAILED : SWAP_DEAD); +} + +static void swap_enter_active(Swap *s, SwapResult f) { + assert(s); + + if (f != SWAP_SUCCESS) + s->result = f; + + swap_set_state(s, SWAP_ACTIVE); +} + +static void swap_enter_signal(Swap *s, SwapState state, SwapResult f) { + int r; + + assert(s); + + if (f != SWAP_SUCCESS) + s->result = f; + + r = unit_kill_context( + UNIT(s), + &s->kill_context, + (state != SWAP_ACTIVATING_SIGTERM && state != SWAP_DEACTIVATING_SIGTERM) ? + KILL_KILL : KILL_TERMINATE, + -1, + s->control_pid, + false); + if (r < 0) + goto fail; + + if (r > 0) { + r = swap_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); + if (r < 0) + goto fail; + + swap_set_state(s, state); + } else if (state == SWAP_ACTIVATING_SIGTERM) + swap_enter_signal(s, SWAP_ACTIVATING_SIGKILL, SWAP_SUCCESS); + else if (state == SWAP_DEACTIVATING_SIGTERM) + swap_enter_signal(s, SWAP_DEACTIVATING_SIGKILL, SWAP_SUCCESS); + else + swap_enter_dead(s, SWAP_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); + swap_enter_dead(s, SWAP_FAILURE_RESOURCES); +} + +static void swap_enter_activating(Swap *s) { + _cleanup_free_ char *opts = NULL; + int r; + + assert(s); + + s->control_command_id = SWAP_EXEC_ACTIVATE; + s->control_command = s->exec_command + SWAP_EXEC_ACTIVATE; + + if (s->from_fragment) { + int priority = -1; + + r = fstab_find_pri(s->parameters_fragment.options, &priority); + if (r < 0) + log_warning_errno(r, "Failed to parse swap priority \"%s\", ignoring: %m", s->parameters_fragment.options); + else if (r == 1 && s->parameters_fragment.priority >= 0) + log_warning("Duplicate swap priority configuration by Priority and Options fields."); + + if (r <= 0 && s->parameters_fragment.priority >= 0) { + if (s->parameters_fragment.options) + r = asprintf(&opts, "%s,pri=%i", s->parameters_fragment.options, s->parameters_fragment.priority); + else + r = asprintf(&opts, "pri=%i", s->parameters_fragment.priority); + if (r < 0) + goto fail; + } + } + + r = exec_command_set(s->control_command, "/sbin/swapon", NULL); + if (r < 0) + goto fail; + + if (s->parameters_fragment.options || opts) { + r = exec_command_append(s->control_command, "-o", + opts ? : s->parameters_fragment.options, NULL); + if (r < 0) + goto fail; + } + + r = exec_command_append(s->control_command, s->what, NULL); + if (r < 0) + goto fail; + + swap_unwatch_control_pid(s); + + r = swap_spawn(s, s->control_command, &s->control_pid); + if (r < 0) + goto fail; + + swap_set_state(s, SWAP_ACTIVATING); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'swapon' task: %m"); + swap_enter_dead(s, SWAP_FAILURE_RESOURCES); +} + +static void swap_enter_deactivating(Swap *s) { + int r; + + assert(s); + + s->control_command_id = SWAP_EXEC_DEACTIVATE; + s->control_command = s->exec_command + SWAP_EXEC_DEACTIVATE; + + r = exec_command_set(s->control_command, + "/sbin/swapoff", + s->what, + NULL); + if (r < 0) + goto fail; + + swap_unwatch_control_pid(s); + + r = swap_spawn(s, s->control_command, &s->control_pid); + if (r < 0) + goto fail; + + swap_set_state(s, SWAP_DEACTIVATING); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'swapoff' task: %m"); + swap_enter_active(s, SWAP_FAILURE_RESOURCES); +} + +static int swap_start(Unit *u) { + Swap *s = SWAP(u), *other; + int r; + + assert(s); + + /* We cannot fulfill this request right now, try again later + * please! */ + + if (s->state == SWAP_DEACTIVATING || + s->state == SWAP_DEACTIVATING_SIGTERM || + s->state == SWAP_DEACTIVATING_SIGKILL || + s->state == SWAP_ACTIVATING_SIGTERM || + s->state == SWAP_ACTIVATING_SIGKILL) + return -EAGAIN; + + if (s->state == SWAP_ACTIVATING) + return 0; + + assert(s->state == SWAP_DEAD || s->state == SWAP_FAILED); + + if (detect_container() > 0) + return -EPERM; + + /* If there's a job for another swap unit for the same node + * running, then let's not dispatch this one for now, and wait + * until that other job has finished. */ + LIST_FOREACH_OTHERS(same_devnode, other, s) + if (UNIT(other)->job && UNIT(other)->job->state == JOB_RUNNING) + return -EAGAIN; + + r = unit_start_limit_test(u); + if (r < 0) { + swap_enter_dead(s, SWAP_FAILURE_START_LIMIT_HIT); + return r; + } + + s->result = SWAP_SUCCESS; + s->reset_cpu_usage = true; + + swap_enter_activating(s); + return 1; +} + +static int swap_stop(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + + if (s->state == SWAP_DEACTIVATING || + s->state == SWAP_DEACTIVATING_SIGTERM || + s->state == SWAP_DEACTIVATING_SIGKILL || + s->state == SWAP_ACTIVATING_SIGTERM || + s->state == SWAP_ACTIVATING_SIGKILL) + return 0; + + assert(s->state == SWAP_ACTIVATING || + s->state == SWAP_ACTIVATING_DONE || + s->state == SWAP_ACTIVE); + + if (detect_container() > 0) + return -EPERM; + + swap_enter_deactivating(s); + return 1; +} + +static int swap_serialize(Unit *u, FILE *f, FDSet *fds) { + Swap *s = SWAP(u); + + assert(s); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", swap_state_to_string(s->state)); + unit_serialize_item(u, f, "result", swap_result_to_string(s->result)); + + if (s->control_pid > 0) + unit_serialize_item_format(u, f, "control-pid", PID_FMT, s->control_pid); + + if (s->control_command_id >= 0) + unit_serialize_item(u, f, "control-command", swap_exec_command_to_string(s->control_command_id)); + + return 0; +} + +static int swap_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Swap *s = SWAP(u); + + assert(s); + assert(fds); + + if (streq(key, "state")) { + SwapState state; + + state = swap_state_from_string(value); + if (state < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + s->deserialized_state = state; + } else if (streq(key, "result")) { + SwapResult f; + + f = swap_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse result value: %s", value); + else if (f != SWAP_SUCCESS) + s->result = f; + } else if (streq(key, "control-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_unit_debug(u, "Failed to parse control-pid value: %s", value); + else + s->control_pid = pid; + + } else if (streq(key, "control-command")) { + SwapExecCommand id; + + id = swap_exec_command_from_string(value); + if (id < 0) + log_unit_debug(u, "Failed to parse exec-command value: %s", value); + else { + s->control_command_id = id; + s->control_command = s->exec_command + id; + } + } else + log_unit_debug(u, "Unknown serialization key: %s", key); + + return 0; +} + +_pure_ static UnitActiveState swap_active_state(Unit *u) { + assert(u); + + return state_translation_table[SWAP(u)->state]; +} + +_pure_ static const char *swap_sub_state_to_string(Unit *u) { + assert(u); + + return swap_state_to_string(SWAP(u)->state); +} + +_pure_ static bool swap_check_gc(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + + return s->from_proc_swaps; +} + +static void swap_sigchld_event(Unit *u, pid_t pid, int code, int status) { + Swap *s = SWAP(u); + SwapResult f; + + assert(s); + assert(pid >= 0); + + if (pid != s->control_pid) + return; + + s->control_pid = 0; + + if (is_clean_exit(code, status, NULL)) + f = SWAP_SUCCESS; + else if (code == CLD_EXITED) + f = SWAP_FAILURE_EXIT_CODE; + else if (code == CLD_KILLED) + f = SWAP_FAILURE_SIGNAL; + else if (code == CLD_DUMPED) + f = SWAP_FAILURE_CORE_DUMP; + else + assert_not_reached("Unknown code"); + + if (f != SWAP_SUCCESS) + s->result = f; + + if (s->control_command) { + exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status); + + s->control_command = NULL; + s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; + } + + log_unit_full(u, f == SWAP_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, + "Swap process exited, code=%s status=%i", sigchld_code_to_string(code), status); + + switch (s->state) { + + case SWAP_ACTIVATING: + case SWAP_ACTIVATING_DONE: + case SWAP_ACTIVATING_SIGTERM: + case SWAP_ACTIVATING_SIGKILL: + + if (f == SWAP_SUCCESS) + swap_enter_active(s, f); + else + swap_enter_dead(s, f); + break; + + case SWAP_DEACTIVATING: + case SWAP_DEACTIVATING_SIGKILL: + case SWAP_DEACTIVATING_SIGTERM: + + swap_enter_dead(s, f); + break; + + default: + assert_not_reached("Uh, control process died at wrong time."); + } + + /* Notify clients about changed exit status */ + unit_add_to_dbus_queue(u); +} + +static int swap_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { + Swap *s = SWAP(userdata); + + assert(s); + assert(s->timer_event_source == source); + + switch (s->state) { + + case SWAP_ACTIVATING: + case SWAP_ACTIVATING_DONE: + log_unit_warning(UNIT(s), "Activation timed out. Stopping."); + swap_enter_signal(s, SWAP_ACTIVATING_SIGTERM, SWAP_FAILURE_TIMEOUT); + break; + + case SWAP_DEACTIVATING: + log_unit_warning(UNIT(s), "Deactivation timed out. Stopping."); + swap_enter_signal(s, SWAP_DEACTIVATING_SIGTERM, SWAP_FAILURE_TIMEOUT); + break; + + case SWAP_ACTIVATING_SIGTERM: + if (s->kill_context.send_sigkill) { + log_unit_warning(UNIT(s), "Activation timed out. Killing."); + swap_enter_signal(s, SWAP_ACTIVATING_SIGKILL, SWAP_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(s), "Activation timed out. Skipping SIGKILL. Ignoring."); + swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); + } + break; + + case SWAP_DEACTIVATING_SIGTERM: + if (s->kill_context.send_sigkill) { + log_unit_warning(UNIT(s), "Deactivation timed out. Killing."); + swap_enter_signal(s, SWAP_DEACTIVATING_SIGKILL, SWAP_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(s), "Deactivation timed out. Skipping SIGKILL. Ignoring."); + swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); + } + break; + + case SWAP_ACTIVATING_SIGKILL: + case SWAP_DEACTIVATING_SIGKILL: + log_unit_warning(UNIT(s), "Swap process still around after SIGKILL. Ignoring."); + swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } + + return 0; +} + +static int swap_load_proc_swaps(Manager *m, bool set_flags) { + unsigned i; + int r = 0; + + assert(m); + + rewind(m->proc_swaps); + + (void) fscanf(m->proc_swaps, "%*s %*s %*s %*s %*s\n"); + + for (i = 1;; i++) { + _cleanup_free_ char *dev = NULL, *d = NULL; + int prio = 0, k; + + k = fscanf(m->proc_swaps, + "%ms " /* device/file */ + "%*s " /* type of swap */ + "%*s " /* swap size */ + "%*s " /* used */ + "%i\n", /* priority */ + &dev, &prio); + if (k != 2) { + if (k == EOF) + break; + + log_warning("Failed to parse /proc/swaps:%u.", i); + continue; + } + + if (cunescape(dev, UNESCAPE_RELAX, &d) < 0) + return log_oom(); + + device_found_node(m, d, true, DEVICE_FOUND_SWAP, set_flags); + + k = swap_process_new(m, d, prio, set_flags); + if (k < 0) + r = k; + } + + return r; +} + +static int swap_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + Unit *u; + int r; + + assert(m); + assert(revents & EPOLLPRI); + + r = swap_load_proc_swaps(m, true); + if (r < 0) { + log_error_errno(r, "Failed to reread /proc/swaps: %m"); + + /* Reset flags, just in case, for late calls */ + LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_SWAP]) { + Swap *swap = SWAP(u); + + swap->is_active = swap->just_activated = false; + } + + return 0; + } + + manager_dispatch_load_queue(m); + + LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_SWAP]) { + Swap *swap = SWAP(u); + + if (!swap->is_active) { + /* This has just been deactivated */ + + swap_unset_proc_swaps(swap); + + switch (swap->state) { + + case SWAP_ACTIVE: + swap_enter_dead(swap, SWAP_SUCCESS); + break; + + default: + /* Fire again */ + swap_set_state(swap, swap->state); + break; + } + + if (swap->what) + device_found_node(m, swap->what, false, DEVICE_FOUND_SWAP, true); + + } else if (swap->just_activated) { + + /* New swap entry */ + + switch (swap->state) { + + case SWAP_DEAD: + case SWAP_FAILED: + swap_enter_active(swap, SWAP_SUCCESS); + break; + + case SWAP_ACTIVATING: + swap_set_state(swap, SWAP_ACTIVATING_DONE); + break; + + default: + /* Nothing really changed, but let's + * issue an notification call + * nonetheless, in case somebody is + * waiting for this. */ + swap_set_state(swap, swap->state); + break; + } + } + + /* Reset the flags for later calls */ + swap->is_active = swap->just_activated = false; + } + + return 1; +} + +static Unit *swap_following(Unit *u) { + Swap *s = SWAP(u); + Swap *other, *first = NULL; + + assert(s); + + /* If the user configured the swap through /etc/fstab or + * a device unit, follow that. */ + + if (s->from_fragment) + return NULL; + + LIST_FOREACH_OTHERS(same_devnode, other, s) + if (other->from_fragment) + return UNIT(other); + + /* Otherwise, make everybody follow the unit that's named after + * the swap device in the kernel */ + + if (streq_ptr(s->what, s->devnode)) + return NULL; + + LIST_FOREACH_AFTER(same_devnode, other, s) + if (streq_ptr(other->what, other->devnode)) + return UNIT(other); + + LIST_FOREACH_BEFORE(same_devnode, other, s) { + if (streq_ptr(other->what, other->devnode)) + return UNIT(other); + + first = other; + } + + /* Fall back to the first on the list */ + return UNIT(first); +} + +static int swap_following_set(Unit *u, Set **_set) { + Swap *s = SWAP(u), *other; + Set *set; + int r; + + assert(s); + assert(_set); + + if (LIST_JUST_US(same_devnode, s)) { + *_set = NULL; + return 0; + } + + set = set_new(NULL); + if (!set) + return -ENOMEM; + + LIST_FOREACH_OTHERS(same_devnode, other, s) { + r = set_put(set, other); + if (r < 0) + goto fail; + } + + *_set = set; + return 1; + +fail: + set_free(set); + return r; +} + +static void swap_shutdown(Manager *m) { + assert(m); + + m->swap_event_source = sd_event_source_unref(m->swap_event_source); + + m->proc_swaps = safe_fclose(m->proc_swaps); + + m->swaps_by_devnode = hashmap_free(m->swaps_by_devnode); +} + +static void swap_enumerate(Manager *m) { + int r; + + assert(m); + + if (!m->proc_swaps) { + m->proc_swaps = fopen("/proc/swaps", "re"); + if (!m->proc_swaps) { + if (errno == ENOENT) + log_debug("Not swap enabled, skipping enumeration"); + else + log_error_errno(errno, "Failed to open /proc/swaps: %m"); + + return; + } + + r = sd_event_add_io(m->event, &m->swap_event_source, fileno(m->proc_swaps), EPOLLPRI, swap_dispatch_io, m); + if (r < 0) { + log_error_errno(r, "Failed to watch /proc/swaps: %m"); + goto fail; + } + + /* Dispatch this before we dispatch SIGCHLD, so that + * we always get the events from /proc/swaps before + * the SIGCHLD of /sbin/swapon. */ + r = sd_event_source_set_priority(m->swap_event_source, -10); + if (r < 0) { + log_error_errno(r, "Failed to change /proc/swaps priority: %m"); + goto fail; + } + + (void) sd_event_source_set_description(m->swap_event_source, "swap-proc"); + } + + r = swap_load_proc_swaps(m, false); + if (r < 0) + goto fail; + + return; + +fail: + swap_shutdown(m); +} + +int swap_process_device_new(Manager *m, struct udev_device *dev) { + struct udev_list_entry *item = NULL, *first = NULL; + _cleanup_free_ char *e = NULL; + const char *dn; + Swap *s; + int r = 0; + + assert(m); + assert(dev); + + dn = udev_device_get_devnode(dev); + if (!dn) + return 0; + + r = unit_name_from_path(dn, ".swap", &e); + if (r < 0) + return r; + + s = hashmap_get(m->units, e); + if (s) + r = swap_set_devnode(s, dn); + + first = udev_device_get_devlinks_list_entry(dev); + udev_list_entry_foreach(item, first) { + _cleanup_free_ char *n = NULL; + int q; + + q = unit_name_from_path(udev_list_entry_get_name(item), ".swap", &n); + if (q < 0) + return q; + + s = hashmap_get(m->units, n); + if (s) { + q = swap_set_devnode(s, dn); + if (q < 0) + r = q; + } + } + + return r; +} + +int swap_process_device_remove(Manager *m, struct udev_device *dev) { + const char *dn; + int r = 0; + Swap *s; + + dn = udev_device_get_devnode(dev); + if (!dn) + return 0; + + while ((s = hashmap_get(m->swaps_by_devnode, dn))) { + int q; + + q = swap_set_devnode(s, NULL); + if (q < 0) + r = q; + } + + return r; +} + +static void swap_reset_failed(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + + if (s->state == SWAP_FAILED) + swap_set_state(s, SWAP_DEAD); + + s->result = SWAP_SUCCESS; +} + +static int swap_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { + return unit_kill_common(u, who, signo, -1, SWAP(u)->control_pid, error); +} + +static int swap_get_timeout(Unit *u, usec_t *timeout) { + Swap *s = SWAP(u); + usec_t t; + int r; + + if (!s->timer_event_source) + return 0; + + r = sd_event_source_get_time(s->timer_event_source, &t); + if (r < 0) + return r; + if (t == USEC_INFINITY) + return 0; + + *timeout = t; + return 1; +} + +static bool swap_supported(void) { + static int supported = -1; + + /* If swap support is not available in the kernel, or we are + * running in a container we don't support swap units, and any + * attempts to starting one should fail immediately. */ + + if (supported < 0) + supported = + access("/proc/swaps", F_OK) >= 0 && + detect_container() <= 0; + + return supported; +} + +static int swap_control_pid(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + + return s->control_pid; +} + +static const char* const swap_exec_command_table[_SWAP_EXEC_COMMAND_MAX] = { + [SWAP_EXEC_ACTIVATE] = "ExecActivate", + [SWAP_EXEC_DEACTIVATE] = "ExecDeactivate", +}; + +DEFINE_STRING_TABLE_LOOKUP(swap_exec_command, SwapExecCommand); + +static const char* const swap_result_table[_SWAP_RESULT_MAX] = { + [SWAP_SUCCESS] = "success", + [SWAP_FAILURE_RESOURCES] = "resources", + [SWAP_FAILURE_TIMEOUT] = "timeout", + [SWAP_FAILURE_EXIT_CODE] = "exit-code", + [SWAP_FAILURE_SIGNAL] = "signal", + [SWAP_FAILURE_CORE_DUMP] = "core-dump", + [SWAP_FAILURE_START_LIMIT_HIT] = "start-limit-hit", +}; + +DEFINE_STRING_TABLE_LOOKUP(swap_result, SwapResult); + +const UnitVTable swap_vtable = { + .object_size = sizeof(Swap), + .exec_context_offset = offsetof(Swap, exec_context), + .cgroup_context_offset = offsetof(Swap, cgroup_context), + .kill_context_offset = offsetof(Swap, kill_context), + .exec_runtime_offset = offsetof(Swap, exec_runtime), + + .sections = + "Unit\0" + "Swap\0" + "Install\0", + .private_section = "Swap", + + .init = swap_init, + .load = swap_load, + .done = swap_done, + + .coldplug = swap_coldplug, + + .dump = swap_dump, + + .start = swap_start, + .stop = swap_stop, + + .kill = swap_kill, + + .get_timeout = swap_get_timeout, + + .serialize = swap_serialize, + .deserialize_item = swap_deserialize_item, + + .active_state = swap_active_state, + .sub_state_to_string = swap_sub_state_to_string, + + .check_gc = swap_check_gc, + + .sigchld_event = swap_sigchld_event, + + .reset_failed = swap_reset_failed, + + .control_pid = swap_control_pid, + + .bus_vtable = bus_swap_vtable, + .bus_set_property = bus_swap_set_property, + .bus_commit_properties = bus_swap_commit_properties, + + .following = swap_following, + .following_set = swap_following_set, + + .enumerate = swap_enumerate, + .shutdown = swap_shutdown, + .supported = swap_supported, + + .status_message_formats = { + .starting_stopping = { + [0] = "Activating swap %s...", + [1] = "Deactivating swap %s...", + }, + .finished_start_job = { + [JOB_DONE] = "Activated swap %s.", + [JOB_FAILED] = "Failed to activate swap %s.", + [JOB_TIMEOUT] = "Timed out activating swap %s.", + }, + .finished_stop_job = { + [JOB_DONE] = "Deactivated swap %s.", + [JOB_FAILED] = "Failed deactivating swap %s.", + [JOB_TIMEOUT] = "Timed out deactivating swap %s.", + }, + }, +}; diff --git a/src/libcore/swap.h b/src/libcore/swap.h new file mode 100644 index 0000000000..fbf66debdc --- /dev/null +++ b/src/libcore/swap.h @@ -0,0 +1,110 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2010 Maarten Lankhorst + + 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 "libudev.h" + +typedef struct Swap Swap; + +typedef enum SwapExecCommand { + SWAP_EXEC_ACTIVATE, + SWAP_EXEC_DEACTIVATE, + _SWAP_EXEC_COMMAND_MAX, + _SWAP_EXEC_COMMAND_INVALID = -1 +} SwapExecCommand; + +typedef enum SwapResult { + SWAP_SUCCESS, + SWAP_FAILURE_RESOURCES, + SWAP_FAILURE_TIMEOUT, + SWAP_FAILURE_EXIT_CODE, + SWAP_FAILURE_SIGNAL, + SWAP_FAILURE_CORE_DUMP, + SWAP_FAILURE_START_LIMIT_HIT, + _SWAP_RESULT_MAX, + _SWAP_RESULT_INVALID = -1 +} SwapResult; + +typedef struct SwapParameters { + char *what; + char *options; + int priority; +} SwapParameters; + +struct Swap { + Unit meta; + + char *what; + + /* If the device has already shown up, this is the device + * node, which might be different from what, due to + * symlinks */ + char *devnode; + + SwapParameters parameters_proc_swaps; + SwapParameters parameters_fragment; + + bool from_proc_swaps:1; + bool from_fragment:1; + + /* Used while looking for swaps that vanished or got added + * from/to /proc/swaps */ + bool is_active:1; + bool just_activated:1; + + bool reset_cpu_usage:1; + + SwapResult result; + + usec_t timeout_usec; + + ExecCommand exec_command[_SWAP_EXEC_COMMAND_MAX]; + ExecContext exec_context; + KillContext kill_context; + CGroupContext cgroup_context; + + ExecRuntime *exec_runtime; + + SwapState state, deserialized_state; + + ExecCommand* control_command; + SwapExecCommand control_command_id; + pid_t control_pid; + + sd_event_source *timer_event_source; + + /* In order to be able to distinguish dependencies on + different device nodes we might end up creating multiple + devices for the same swap. We chain them up here. */ + + LIST_FIELDS(struct Swap, same_devnode); +}; + +extern const UnitVTable swap_vtable; + +int swap_process_device_new(Manager *m, struct udev_device *dev); +int swap_process_device_remove(Manager *m, struct udev_device *dev); + +const char* swap_exec_command_to_string(SwapExecCommand i) _const_; +SwapExecCommand swap_exec_command_from_string(const char *s) _pure_; + +const char* swap_result_to_string(SwapResult i) _const_; +SwapResult swap_result_from_string(const char *s) _pure_; diff --git a/src/libcore/target.c b/src/libcore/target.c new file mode 100644 index 0000000000..61a91aad07 --- /dev/null +++ b/src/libcore/target.c @@ -0,0 +1,223 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 "dbus-target.h" +#include "log.h" +#include "special.h" +#include "string-util.h" +#include "unit-name.h" +#include "unit.h" +#include "target.h" + +static const UnitActiveState state_translation_table[_TARGET_STATE_MAX] = { + [TARGET_DEAD] = UNIT_INACTIVE, + [TARGET_ACTIVE] = UNIT_ACTIVE +}; + +static void target_set_state(Target *t, TargetState state) { + TargetState old_state; + assert(t); + + old_state = t->state; + t->state = state; + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(t)->id, + target_state_to_string(old_state), + target_state_to_string(state)); + + unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], true); +} + +static int target_add_default_dependencies(Target *t) { + + static const UnitDependency deps[] = { + UNIT_REQUIRES, + UNIT_REQUISITE, + UNIT_WANTS, + UNIT_BINDS_TO, + UNIT_PART_OF + }; + + Iterator i; + Unit *other; + int r; + unsigned k; + + assert(t); + + /* Imply ordering for requirement dependencies on target + * units. Note that when the user created a contradicting + * ordering manually we won't add anything in here to make + * sure we don't create a loop. */ + + for (k = 0; k < ELEMENTSOF(deps); k++) + SET_FOREACH(other, UNIT(t)->dependencies[deps[k]], i) { + r = unit_add_default_target_dependency(other, UNIT(t)); + if (r < 0) + return r; + } + + /* Make sure targets are unloaded on shutdown */ + return unit_add_dependency_by_name(UNIT(t), UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); +} + +static int target_load(Unit *u) { + Target *t = TARGET(u); + int r; + + assert(t); + + r = unit_load_fragment_and_dropin(u); + if (r < 0) + return r; + + /* This is a new unit? Then let's add in some extras */ + if (u->load_state == UNIT_LOADED && u->default_dependencies) { + r = target_add_default_dependencies(t); + if (r < 0) + return r; + } + + return 0; +} + +static int target_coldplug(Unit *u) { + Target *t = TARGET(u); + + assert(t); + assert(t->state == TARGET_DEAD); + + if (t->deserialized_state != t->state) + target_set_state(t, t->deserialized_state); + + return 0; +} + +static void target_dump(Unit *u, FILE *f, const char *prefix) { + Target *t = TARGET(u); + + assert(t); + assert(f); + + fprintf(f, + "%sTarget State: %s\n", + prefix, target_state_to_string(t->state)); +} + +static int target_start(Unit *u) { + Target *t = TARGET(u); + + assert(t); + assert(t->state == TARGET_DEAD); + + target_set_state(t, TARGET_ACTIVE); + return 1; +} + +static int target_stop(Unit *u) { + Target *t = TARGET(u); + + assert(t); + assert(t->state == TARGET_ACTIVE); + + target_set_state(t, TARGET_DEAD); + return 1; +} + +static int target_serialize(Unit *u, FILE *f, FDSet *fds) { + Target *s = TARGET(u); + + assert(s); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", target_state_to_string(s->state)); + return 0; +} + +static int target_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Target *s = TARGET(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + TargetState state; + + state = target_state_from_string(value); + if (state < 0) + log_debug("Failed to parse state value %s", value); + else + s->deserialized_state = state; + + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +_pure_ static UnitActiveState target_active_state(Unit *u) { + assert(u); + + return state_translation_table[TARGET(u)->state]; +} + +_pure_ static const char *target_sub_state_to_string(Unit *u) { + assert(u); + + return target_state_to_string(TARGET(u)->state); +} + +const UnitVTable target_vtable = { + .object_size = sizeof(Target), + + .sections = + "Unit\0" + "Target\0" + "Install\0", + + .load = target_load, + .coldplug = target_coldplug, + + .dump = target_dump, + + .start = target_start, + .stop = target_stop, + + .serialize = target_serialize, + .deserialize_item = target_deserialize_item, + + .active_state = target_active_state, + .sub_state_to_string = target_sub_state_to_string, + + .bus_vtable = bus_target_vtable, + + .status_message_formats = { + .finished_start_job = { + [JOB_DONE] = "Reached target %s.", + }, + .finished_stop_job = { + [JOB_DONE] = "Stopped target %s.", + }, + }, +}; diff --git a/src/libcore/target.h b/src/libcore/target.h new file mode 100644 index 0000000000..339aea154e --- /dev/null +++ b/src/libcore/target.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct Target Target; + +struct Target { + Unit meta; + + TargetState state, deserialized_state; +}; + +extern const UnitVTable target_vtable; diff --git a/src/libcore/timer.c b/src/libcore/timer.c new file mode 100644 index 0000000000..3206296f09 --- /dev/null +++ b/src/libcore/timer.c @@ -0,0 +1,859 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "dbus-timer.h" +#include "fs-util.h" +#include "parse-util.h" +#include "random-util.h" +#include "special.h" +#include "string-table.h" +#include "string-util.h" +#include "timer.h" +#include "unit-name.h" +#include "unit.h" +#include "user-util.h" +#include "virt.h" + +static const UnitActiveState state_translation_table[_TIMER_STATE_MAX] = { + [TIMER_DEAD] = UNIT_INACTIVE, + [TIMER_WAITING] = UNIT_ACTIVE, + [TIMER_RUNNING] = UNIT_ACTIVE, + [TIMER_ELAPSED] = UNIT_ACTIVE, + [TIMER_FAILED] = UNIT_FAILED +}; + +static int timer_dispatch(sd_event_source *s, uint64_t usec, void *userdata); + +static void timer_init(Unit *u) { + Timer *t = TIMER(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + t->next_elapse_monotonic_or_boottime = USEC_INFINITY; + t->next_elapse_realtime = USEC_INFINITY; + t->accuracy_usec = u->manager->default_timer_accuracy_usec; + t->remain_after_elapse = true; +} + +void timer_free_values(Timer *t) { + TimerValue *v; + + assert(t); + + while ((v = t->values)) { + LIST_REMOVE(value, t->values, v); + calendar_spec_free(v->calendar_spec); + free(v); + } +} + +static void timer_done(Unit *u) { + Timer *t = TIMER(u); + + assert(t); + + timer_free_values(t); + + t->monotonic_event_source = sd_event_source_unref(t->monotonic_event_source); + t->realtime_event_source = sd_event_source_unref(t->realtime_event_source); + + free(t->stamp_path); +} + +static int timer_verify(Timer *t) { + assert(t); + + if (UNIT(t)->load_state != UNIT_LOADED) + return 0; + + if (!t->values) { + log_unit_error(UNIT(t), "Timer unit lacks value setting. Refusing."); + return -EINVAL; + } + + return 0; +} + +static int timer_add_default_dependencies(Timer *t) { + int r; + TimerValue *v; + + assert(t); + + if (!UNIT(t)->default_dependencies) + return 0; + + r = unit_add_dependency_by_name(UNIT(t), UNIT_BEFORE, SPECIAL_TIMERS_TARGET, NULL, true); + if (r < 0) + return r; + + if (MANAGER_IS_SYSTEM(UNIT(t)->manager)) { + r = unit_add_two_dependencies_by_name(UNIT(t), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); + if (r < 0) + return r; + + LIST_FOREACH(value, v, t->values) { + if (v->base == TIMER_CALENDAR) { + r = unit_add_dependency_by_name(UNIT(t), UNIT_AFTER, SPECIAL_TIME_SYNC_TARGET, NULL, true); + if (r < 0) + return r; + break; + } + } + } + + return unit_add_two_dependencies_by_name(UNIT(t), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); +} + +static int timer_setup_persistent(Timer *t) { + int r; + + assert(t); + + if (!t->persistent) + return 0; + + if (MANAGER_IS_SYSTEM(UNIT(t)->manager)) { + + r = unit_require_mounts_for(UNIT(t), "/var/lib/systemd/timers"); + if (r < 0) + return r; + + t->stamp_path = strappend("/var/lib/systemd/timers/stamp-", UNIT(t)->id); + } else { + const char *e; + + e = getenv("XDG_DATA_HOME"); + if (e) + t->stamp_path = strjoin(e, "/systemd/timers/stamp-", UNIT(t)->id, NULL); + else { + + _cleanup_free_ char *h = NULL; + + r = get_home_dir(&h); + if (r < 0) + return log_unit_error_errno(UNIT(t), r, "Failed to determine home directory: %m"); + + t->stamp_path = strjoin(h, "/.local/share/systemd/timers/stamp-", UNIT(t)->id, NULL); + } + } + + if (!t->stamp_path) + return log_oom(); + + return 0; +} + +static int timer_load(Unit *u) { + Timer *t = TIMER(u); + int r; + + assert(u); + assert(u->load_state == UNIT_STUB); + + r = unit_load_fragment_and_dropin(u); + if (r < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + + if (set_isempty(u->dependencies[UNIT_TRIGGERS])) { + Unit *x; + + r = unit_load_related_unit(u, ".service", &x); + if (r < 0) + return r; + + r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, x, true); + if (r < 0) + return r; + } + + r = timer_setup_persistent(t); + if (r < 0) + return r; + + r = timer_add_default_dependencies(t); + if (r < 0) + return r; + } + + return timer_verify(t); +} + +static void timer_dump(Unit *u, FILE *f, const char *prefix) { + char buf[FORMAT_TIMESPAN_MAX]; + Timer *t = TIMER(u); + Unit *trigger; + TimerValue *v; + + trigger = UNIT_TRIGGER(u); + + fprintf(f, + "%sTimer State: %s\n" + "%sResult: %s\n" + "%sUnit: %s\n" + "%sPersistent: %s\n" + "%sWakeSystem: %s\n" + "%sAccuracy: %s\n" + "%sRemainAfterElapse: %s\n", + prefix, timer_state_to_string(t->state), + prefix, timer_result_to_string(t->result), + prefix, trigger ? trigger->id : "n/a", + prefix, yes_no(t->persistent), + prefix, yes_no(t->wake_system), + prefix, format_timespan(buf, sizeof(buf), t->accuracy_usec, 1), + prefix, yes_no(t->remain_after_elapse)); + + LIST_FOREACH(value, v, t->values) { + + if (v->base == TIMER_CALENDAR) { + _cleanup_free_ char *p = NULL; + + calendar_spec_to_string(v->calendar_spec, &p); + + fprintf(f, + "%s%s: %s\n", + prefix, + timer_base_to_string(v->base), + strna(p)); + } else { + char timespan1[FORMAT_TIMESPAN_MAX]; + + fprintf(f, + "%s%s: %s\n", + prefix, + timer_base_to_string(v->base), + format_timespan(timespan1, sizeof(timespan1), v->value, 0)); + } + } +} + +static void timer_set_state(Timer *t, TimerState state) { + TimerState old_state; + assert(t); + + old_state = t->state; + t->state = state; + + if (state != TIMER_WAITING) { + t->monotonic_event_source = sd_event_source_unref(t->monotonic_event_source); + t->realtime_event_source = sd_event_source_unref(t->realtime_event_source); + } + + if (state != old_state) + log_unit_debug(UNIT(t), "Changed %s -> %s", timer_state_to_string(old_state), timer_state_to_string(state)); + + unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], true); +} + +static void timer_enter_waiting(Timer *t, bool initial); + +static int timer_coldplug(Unit *u) { + Timer *t = TIMER(u); + + assert(t); + assert(t->state == TIMER_DEAD); + + if (t->deserialized_state == t->state) + return 0; + + if (t->deserialized_state == TIMER_WAITING) + timer_enter_waiting(t, false); + else + timer_set_state(t, t->deserialized_state); + + return 0; +} + +static void timer_enter_dead(Timer *t, TimerResult f) { + assert(t); + + if (f != TIMER_SUCCESS) + t->result = f; + + timer_set_state(t, t->result != TIMER_SUCCESS ? TIMER_FAILED : TIMER_DEAD); +} + +static void timer_enter_elapsed(Timer *t, bool leave_around) { + assert(t); + + /* If a unit is marked with RemainAfterElapse=yes we leave it + * around even after it elapsed once, so that starting it + * later again does not necessarily mean immediate + * retriggering. We unconditionally leave units with + * TIMER_UNIT_ACTIVE or TIMER_UNIT_INACTIVE triggers around, + * since they might be restarted automatically at any time + * later on. */ + + if (t->remain_after_elapse || leave_around) + timer_set_state(t, TIMER_ELAPSED); + else + timer_enter_dead(t, TIMER_SUCCESS); +} + +static usec_t monotonic_to_boottime(usec_t t) { + usec_t a, b; + + if (t <= 0) + return 0; + + a = now(clock_boottime_or_monotonic()); + b = now(CLOCK_MONOTONIC); + + if (t + a > b) + return t + a - b; + else + return 0; +} + +static void add_random(Timer *t, usec_t *v) { + char s[FORMAT_TIMESPAN_MAX]; + usec_t add; + + assert(t); + assert(v); + + if (t->random_usec == 0) + return; + if (*v == USEC_INFINITY) + return; + + add = random_u64() % t->random_usec; + + if (*v + add < *v) /* overflow */ + *v = (usec_t) -2; /* Highest possible value, that is not USEC_INFINITY */ + else + *v += add; + + log_unit_info(UNIT(t), "Adding %s random time.", format_timespan(s, sizeof(s), add, 0)); +} + +static void timer_enter_waiting(Timer *t, bool initial) { + bool found_monotonic = false, found_realtime = false; + usec_t ts_realtime, ts_monotonic; + usec_t base = 0; + bool leave_around = false; + TimerValue *v; + Unit *trigger; + int r; + + assert(t); + + trigger = UNIT_TRIGGER(UNIT(t)); + if (!trigger) { + log_unit_error(UNIT(t), "Unit to trigger vanished."); + timer_enter_dead(t, TIMER_FAILURE_RESOURCES); + return; + } + + /* If we shall wake the system we use the boottime clock + * rather than the monotonic clock. */ + + ts_realtime = now(CLOCK_REALTIME); + ts_monotonic = now(t->wake_system ? clock_boottime_or_monotonic() : CLOCK_MONOTONIC); + t->next_elapse_monotonic_or_boottime = t->next_elapse_realtime = 0; + + LIST_FOREACH(value, v, t->values) { + + if (v->disabled) + continue; + + if (v->base == TIMER_CALENDAR) { + usec_t b; + + /* If we know the last time this was + * triggered, schedule the job based relative + * to that. If we don't just start from + * now. */ + + b = t->last_trigger.realtime > 0 ? t->last_trigger.realtime : ts_realtime; + + r = calendar_spec_next_usec(v->calendar_spec, b, &v->next_elapse); + if (r < 0) + continue; + + if (!found_realtime) + t->next_elapse_realtime = v->next_elapse; + else + t->next_elapse_realtime = MIN(t->next_elapse_realtime, v->next_elapse); + + found_realtime = true; + + } else { + switch (v->base) { + + case TIMER_ACTIVE: + if (state_translation_table[t->state] == UNIT_ACTIVE) + base = UNIT(t)->inactive_exit_timestamp.monotonic; + else + base = ts_monotonic; + break; + + case TIMER_BOOT: + if (detect_container() <= 0) { + /* CLOCK_MONOTONIC equals the uptime on Linux */ + base = 0; + break; + } + /* In a container we don't want to include the time the host + * was already up when the container started, so count from + * our own startup. Fall through. */ + case TIMER_STARTUP: + base = UNIT(t)->manager->userspace_timestamp.monotonic; + break; + + case TIMER_UNIT_ACTIVE: + leave_around = true; + base = trigger->inactive_exit_timestamp.monotonic; + + if (base <= 0) + base = t->last_trigger.monotonic; + + if (base <= 0) + continue; + + break; + + case TIMER_UNIT_INACTIVE: + leave_around = true; + base = trigger->inactive_enter_timestamp.monotonic; + + if (base <= 0) + base = t->last_trigger.monotonic; + + if (base <= 0) + continue; + + break; + + default: + assert_not_reached("Unknown timer base"); + } + + if (t->wake_system) + base = monotonic_to_boottime(base); + + v->next_elapse = base + v->value; + + if (!initial && v->next_elapse < ts_monotonic && IN_SET(v->base, TIMER_ACTIVE, TIMER_BOOT, TIMER_STARTUP)) { + /* This is a one time trigger, disable it now */ + v->disabled = true; + continue; + } + + if (!found_monotonic) + t->next_elapse_monotonic_or_boottime = v->next_elapse; + else + t->next_elapse_monotonic_or_boottime = MIN(t->next_elapse_monotonic_or_boottime, v->next_elapse); + + found_monotonic = true; + } + } + + if (!found_monotonic && !found_realtime) { + log_unit_debug(UNIT(t), "Timer is elapsed."); + timer_enter_elapsed(t, leave_around); + return; + } + + if (found_monotonic) { + char buf[FORMAT_TIMESPAN_MAX]; + usec_t left; + + add_random(t, &t->next_elapse_monotonic_or_boottime); + + left = t->next_elapse_monotonic_or_boottime > ts_monotonic ? t->next_elapse_monotonic_or_boottime - ts_monotonic : 0; + log_unit_debug(UNIT(t), "Monotonic timer elapses in %s.", format_timespan(buf, sizeof(buf), left, 0)); + + if (t->monotonic_event_source) { + r = sd_event_source_set_time(t->monotonic_event_source, t->next_elapse_monotonic_or_boottime); + if (r < 0) + goto fail; + + r = sd_event_source_set_enabled(t->monotonic_event_source, SD_EVENT_ONESHOT); + if (r < 0) + goto fail; + } else { + + r = sd_event_add_time( + UNIT(t)->manager->event, + &t->monotonic_event_source, + t->wake_system ? CLOCK_BOOTTIME_ALARM : CLOCK_MONOTONIC, + t->next_elapse_monotonic_or_boottime, t->accuracy_usec, + timer_dispatch, t); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(t->monotonic_event_source, "timer-monotonic"); + } + + } else if (t->monotonic_event_source) { + + r = sd_event_source_set_enabled(t->monotonic_event_source, SD_EVENT_OFF); + if (r < 0) + goto fail; + } + + if (found_realtime) { + char buf[FORMAT_TIMESTAMP_MAX]; + + add_random(t, &t->next_elapse_realtime); + + log_unit_debug(UNIT(t), "Realtime timer elapses at %s.", format_timestamp(buf, sizeof(buf), t->next_elapse_realtime)); + + if (t->realtime_event_source) { + r = sd_event_source_set_time(t->realtime_event_source, t->next_elapse_realtime); + if (r < 0) + goto fail; + + r = sd_event_source_set_enabled(t->realtime_event_source, SD_EVENT_ONESHOT); + if (r < 0) + goto fail; + } else { + r = sd_event_add_time( + UNIT(t)->manager->event, + &t->realtime_event_source, + t->wake_system ? CLOCK_REALTIME_ALARM : CLOCK_REALTIME, + t->next_elapse_realtime, t->accuracy_usec, + timer_dispatch, t); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(t->realtime_event_source, "timer-realtime"); + } + + } else if (t->realtime_event_source) { + + r = sd_event_source_set_enabled(t->realtime_event_source, SD_EVENT_OFF); + if (r < 0) + goto fail; + } + + timer_set_state(t, TIMER_WAITING); + return; + +fail: + log_unit_warning_errno(UNIT(t), r, "Failed to enter waiting state: %m"); + timer_enter_dead(t, TIMER_FAILURE_RESOURCES); +} + +static void timer_enter_running(Timer *t) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + Unit *trigger; + int r; + + assert(t); + + /* Don't start job if we are supposed to go down */ + if (unit_stop_pending(UNIT(t))) + return; + + trigger = UNIT_TRIGGER(UNIT(t)); + if (!trigger) { + log_unit_error(UNIT(t), "Unit to trigger vanished."); + timer_enter_dead(t, TIMER_FAILURE_RESOURCES); + return; + } + + r = manager_add_job(UNIT(t)->manager, JOB_START, trigger, JOB_REPLACE, &error, NULL); + if (r < 0) + goto fail; + + dual_timestamp_get(&t->last_trigger); + + if (t->stamp_path) + touch_file(t->stamp_path, true, t->last_trigger.realtime, UID_INVALID, GID_INVALID, MODE_INVALID); + + timer_set_state(t, TIMER_RUNNING); + return; + +fail: + log_unit_warning(UNIT(t), "Failed to queue unit startup job: %s", bus_error_message(&error, r)); + timer_enter_dead(t, TIMER_FAILURE_RESOURCES); +} + +static int timer_start(Unit *u) { + Timer *t = TIMER(u); + TimerValue *v; + Unit *trigger; + int r; + + assert(t); + assert(t->state == TIMER_DEAD || t->state == TIMER_FAILED); + + trigger = UNIT_TRIGGER(u); + if (!trigger || trigger->load_state != UNIT_LOADED) { + log_unit_error(u, "Refusing to start, unit to trigger not loaded."); + return -ENOENT; + } + + r = unit_start_limit_test(u); + if (r < 0) { + timer_enter_dead(t, TIMER_FAILURE_START_LIMIT_HIT); + return r; + } + + t->last_trigger = DUAL_TIMESTAMP_NULL; + + /* Reenable all timers that depend on unit activation time */ + LIST_FOREACH(value, v, t->values) + if (v->base == TIMER_ACTIVE) + v->disabled = false; + + if (t->stamp_path) { + struct stat st; + + if (stat(t->stamp_path, &st) >= 0) + t->last_trigger.realtime = timespec_load(&st.st_atim); + else if (errno == ENOENT) + /* The timer has never run before, + * make sure a stamp file exists. + */ + touch_file(t->stamp_path, true, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID); + } + + t->result = TIMER_SUCCESS; + timer_enter_waiting(t, true); + return 1; +} + +static int timer_stop(Unit *u) { + Timer *t = TIMER(u); + + assert(t); + assert(t->state == TIMER_WAITING || t->state == TIMER_RUNNING || t->state == TIMER_ELAPSED); + + timer_enter_dead(t, TIMER_SUCCESS); + return 1; +} + +static int timer_serialize(Unit *u, FILE *f, FDSet *fds) { + Timer *t = TIMER(u); + + assert(u); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", timer_state_to_string(t->state)); + unit_serialize_item(u, f, "result", timer_result_to_string(t->result)); + + if (t->last_trigger.realtime > 0) + unit_serialize_item_format(u, f, "last-trigger-realtime", "%" PRIu64, t->last_trigger.realtime); + + if (t->last_trigger.monotonic > 0) + unit_serialize_item_format(u, f, "last-trigger-monotonic", "%" PRIu64, t->last_trigger.monotonic); + + return 0; +} + +static int timer_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Timer *t = TIMER(u); + int r; + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + TimerState state; + + state = timer_state_from_string(value); + if (state < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + t->deserialized_state = state; + } else if (streq(key, "result")) { + TimerResult f; + + f = timer_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse result value: %s", value); + else if (f != TIMER_SUCCESS) + t->result = f; + } else if (streq(key, "last-trigger-realtime")) { + + r = safe_atou64(value, &t->last_trigger.realtime); + if (r < 0) + log_unit_debug(u, "Failed to parse last-trigger-realtime value: %s", value); + + } else if (streq(key, "last-trigger-monotonic")) { + + r = safe_atou64(value, &t->last_trigger.monotonic); + if (r < 0) + log_unit_debug(u, "Failed to parse last-trigger-monotonic value: %s", value); + + } else + log_unit_debug(u, "Unknown serialization key: %s", key); + + return 0; +} + +_pure_ static UnitActiveState timer_active_state(Unit *u) { + assert(u); + + return state_translation_table[TIMER(u)->state]; +} + +_pure_ static const char *timer_sub_state_to_string(Unit *u) { + assert(u); + + return timer_state_to_string(TIMER(u)->state); +} + +static int timer_dispatch(sd_event_source *s, uint64_t usec, void *userdata) { + Timer *t = TIMER(userdata); + + assert(t); + + if (t->state != TIMER_WAITING) + return 0; + + log_unit_debug(UNIT(t), "Timer elapsed."); + timer_enter_running(t); + return 0; +} + +static void timer_trigger_notify(Unit *u, Unit *other) { + Timer *t = TIMER(u); + TimerValue *v; + + assert(u); + assert(other); + + if (other->load_state != UNIT_LOADED) + return; + + /* Reenable all timers that depend on unit state */ + LIST_FOREACH(value, v, t->values) + if (v->base == TIMER_UNIT_ACTIVE || + v->base == TIMER_UNIT_INACTIVE) + v->disabled = false; + + switch (t->state) { + + case TIMER_WAITING: + case TIMER_ELAPSED: + + /* Recalculate sleep time */ + timer_enter_waiting(t, false); + break; + + case TIMER_RUNNING: + + if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) { + log_unit_debug(UNIT(t), "Got notified about unit deactivation."); + timer_enter_waiting(t, false); + } + break; + + case TIMER_DEAD: + case TIMER_FAILED: + break; + + default: + assert_not_reached("Unknown timer state"); + } +} + +static void timer_reset_failed(Unit *u) { + Timer *t = TIMER(u); + + assert(t); + + if (t->state == TIMER_FAILED) + timer_set_state(t, TIMER_DEAD); + + t->result = TIMER_SUCCESS; +} + +static void timer_time_change(Unit *u) { + Timer *t = TIMER(u); + + assert(u); + + if (t->state != TIMER_WAITING) + return; + + log_unit_debug(u, "Time change, recalculating next elapse."); + timer_enter_waiting(t, false); +} + +static const char* const timer_base_table[_TIMER_BASE_MAX] = { + [TIMER_ACTIVE] = "OnActiveSec", + [TIMER_BOOT] = "OnBootSec", + [TIMER_STARTUP] = "OnStartupSec", + [TIMER_UNIT_ACTIVE] = "OnUnitActiveSec", + [TIMER_UNIT_INACTIVE] = "OnUnitInactiveSec", + [TIMER_CALENDAR] = "OnCalendar" +}; + +DEFINE_STRING_TABLE_LOOKUP(timer_base, TimerBase); + +static const char* const timer_result_table[_TIMER_RESULT_MAX] = { + [TIMER_SUCCESS] = "success", + [TIMER_FAILURE_RESOURCES] = "resources", + [TIMER_FAILURE_START_LIMIT_HIT] = "start-limit-hit", +}; + +DEFINE_STRING_TABLE_LOOKUP(timer_result, TimerResult); + +const UnitVTable timer_vtable = { + .object_size = sizeof(Timer), + + .sections = + "Unit\0" + "Timer\0" + "Install\0", + .private_section = "Timer", + + .init = timer_init, + .done = timer_done, + .load = timer_load, + + .coldplug = timer_coldplug, + + .dump = timer_dump, + + .start = timer_start, + .stop = timer_stop, + + .serialize = timer_serialize, + .deserialize_item = timer_deserialize_item, + + .active_state = timer_active_state, + .sub_state_to_string = timer_sub_state_to_string, + + .trigger_notify = timer_trigger_notify, + + .reset_failed = timer_reset_failed, + .time_change = timer_time_change, + + .bus_vtable = bus_timer_vtable, + .bus_set_property = bus_timer_set_property, + + .can_transient = true, +}; diff --git a/src/libcore/timer.h b/src/libcore/timer.h new file mode 100644 index 0000000000..9c4b64f898 --- /dev/null +++ b/src/libcore/timer.h @@ -0,0 +1,89 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct Timer Timer; + +#include "calendarspec.h" + +typedef enum TimerBase { + TIMER_ACTIVE, + TIMER_BOOT, + TIMER_STARTUP, + TIMER_UNIT_ACTIVE, + TIMER_UNIT_INACTIVE, + TIMER_CALENDAR, + _TIMER_BASE_MAX, + _TIMER_BASE_INVALID = -1 +} TimerBase; + +typedef struct TimerValue { + TimerBase base; + bool disabled; + + usec_t value; /* only for monotonic events */ + CalendarSpec *calendar_spec; /* only for calendar events */ + usec_t next_elapse; + + LIST_FIELDS(struct TimerValue, value); +} TimerValue; + +typedef enum TimerResult { + TIMER_SUCCESS, + TIMER_FAILURE_RESOURCES, + TIMER_FAILURE_START_LIMIT_HIT, + _TIMER_RESULT_MAX, + _TIMER_RESULT_INVALID = -1 +} TimerResult; + +struct Timer { + Unit meta; + + usec_t accuracy_usec; + usec_t random_usec; + + LIST_HEAD(TimerValue, values); + usec_t next_elapse_realtime; + usec_t next_elapse_monotonic_or_boottime; + dual_timestamp last_trigger; + + TimerState state, deserialized_state; + + sd_event_source *monotonic_event_source; + sd_event_source *realtime_event_source; + + TimerResult result; + + bool persistent; + bool wake_system; + bool remain_after_elapse; + + char *stamp_path; +}; + +void timer_free_values(Timer *t); + +extern const UnitVTable timer_vtable; + +const char *timer_base_to_string(TimerBase i) _const_; +TimerBase timer_base_from_string(const char *s) _pure_; + +const char* timer_result_to_string(TimerResult i) _const_; +TimerResult timer_result_from_string(const char *s) _pure_; diff --git a/src/libcore/transaction.c b/src/libcore/transaction.c new file mode 100644 index 0000000000..e06a48a2f1 --- /dev/null +++ b/src/libcore/transaction.c @@ -0,0 +1,1099 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-error.h" +#include "terminal-util.h" +#include "transaction.h" +#include "dbus-unit.h" + +static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies); + +static void transaction_delete_job(Transaction *tr, Job *j, bool delete_dependencies) { + assert(tr); + assert(j); + + /* Deletes one job from the transaction */ + + transaction_unlink_job(tr, j, delete_dependencies); + + job_free(j); +} + +static void transaction_delete_unit(Transaction *tr, Unit *u) { + Job *j; + + /* Deletes all jobs associated with a certain unit from the + * transaction */ + + while ((j = hashmap_get(tr->jobs, u))) + transaction_delete_job(tr, j, true); +} + +void transaction_abort(Transaction *tr) { + Job *j; + + assert(tr); + + while ((j = hashmap_first(tr->jobs))) + transaction_delete_job(tr, j, false); + + assert(hashmap_isempty(tr->jobs)); +} + +static void transaction_find_jobs_that_matter_to_anchor(Job *j, unsigned generation) { + JobDependency *l; + + /* A recursive sweep through the graph that marks all units + * that matter to the anchor job, i.e. are directly or + * indirectly a dependency of the anchor job via paths that + * are fully marked as mattering. */ + + j->matters_to_anchor = true; + j->generation = generation; + + LIST_FOREACH(subject, l, j->subject_list) { + + /* This link does not matter */ + if (!l->matters) + continue; + + /* This unit has already been marked */ + if (l->object->generation == generation) + continue; + + transaction_find_jobs_that_matter_to_anchor(l->object, generation); + } +} + +static void transaction_merge_and_delete_job(Transaction *tr, Job *j, Job *other, JobType t) { + JobDependency *l, *last; + + assert(j); + assert(other); + assert(j->unit == other->unit); + assert(!j->installed); + + /* Merges 'other' into 'j' and then deletes 'other'. */ + + j->type = t; + j->state = JOB_WAITING; + j->irreversible = j->irreversible || other->irreversible; + j->matters_to_anchor = j->matters_to_anchor || other->matters_to_anchor; + + /* Patch us in as new owner of the JobDependency objects */ + last = NULL; + LIST_FOREACH(subject, l, other->subject_list) { + assert(l->subject == other); + l->subject = j; + last = l; + } + + /* Merge both lists */ + if (last) { + last->subject_next = j->subject_list; + if (j->subject_list) + j->subject_list->subject_prev = last; + j->subject_list = other->subject_list; + } + + /* Patch us in as new owner of the JobDependency objects */ + last = NULL; + LIST_FOREACH(object, l, other->object_list) { + assert(l->object == other); + l->object = j; + last = l; + } + + /* Merge both lists */ + if (last) { + last->object_next = j->object_list; + if (j->object_list) + j->object_list->object_prev = last; + j->object_list = other->object_list; + } + + /* Kill the other job */ + other->subject_list = NULL; + other->object_list = NULL; + transaction_delete_job(tr, other, true); +} + +_pure_ static bool job_is_conflicted_by(Job *j) { + JobDependency *l; + + assert(j); + + /* Returns true if this job is pulled in by a least one + * ConflictedBy dependency. */ + + LIST_FOREACH(object, l, j->object_list) + if (l->conflicts) + return true; + + return false; +} + +static int delete_one_unmergeable_job(Transaction *tr, Job *j) { + Job *k; + + assert(j); + + /* Tries to delete one item in the linked list + * j->transaction_next->transaction_next->... that conflicts + * with another one, in an attempt to make an inconsistent + * transaction work. */ + + /* We rely here on the fact that if a merged with b does not + * merge with c, either a or b merge with c neither */ + LIST_FOREACH(transaction, j, j) + LIST_FOREACH(transaction, k, j->transaction_next) { + Job *d; + + /* Is this one mergeable? Then skip it */ + if (job_type_is_mergeable(j->type, k->type)) + continue; + + /* Ok, we found two that conflict, let's see if we can + * drop one of them */ + if (!j->matters_to_anchor && !k->matters_to_anchor) { + + /* Both jobs don't matter, so let's + * find the one that is smarter to + * remove. Let's think positive and + * rather remove stops then starts -- + * except if something is being + * stopped because it is conflicted by + * another unit in which case we + * rather remove the start. */ + + log_unit_debug(j->unit, + "Looking at job %s/%s conflicted_by=%s", + j->unit->id, job_type_to_string(j->type), + yes_no(j->type == JOB_STOP && job_is_conflicted_by(j))); + log_unit_debug(k->unit, + "Looking at job %s/%s conflicted_by=%s", + k->unit->id, job_type_to_string(k->type), + yes_no(k->type == JOB_STOP && job_is_conflicted_by(k))); + + if (j->type == JOB_STOP) { + + if (job_is_conflicted_by(j)) + d = k; + else + d = j; + + } else if (k->type == JOB_STOP) { + + if (job_is_conflicted_by(k)) + d = j; + else + d = k; + } else + d = j; + + } else if (!j->matters_to_anchor) + d = j; + else if (!k->matters_to_anchor) + d = k; + else + return -ENOEXEC; + + /* Ok, we can drop one, so let's do so. */ + log_unit_debug(d->unit, + "Fixing conflicting jobs %s/%s,%s/%s by deleting job %s/%s", + j->unit->id, job_type_to_string(j->type), + k->unit->id, job_type_to_string(k->type), + d->unit->id, job_type_to_string(d->type)); + transaction_delete_job(tr, d, true); + return 0; + } + + return -EINVAL; +} + +static int transaction_merge_jobs(Transaction *tr, sd_bus_error *e) { + Job *j; + Iterator i; + int r; + + assert(tr); + + /* First step, check whether any of the jobs for one specific + * task conflict. If so, try to drop one of them. */ + HASHMAP_FOREACH(j, tr->jobs, i) { + JobType t; + Job *k; + + t = j->type; + LIST_FOREACH(transaction, k, j->transaction_next) { + if (job_type_merge_and_collapse(&t, k->type, j->unit) >= 0) + continue; + + /* OK, we could not merge all jobs for this + * action. Let's see if we can get rid of one + * of them */ + + r = delete_one_unmergeable_job(tr, j); + if (r >= 0) + /* Ok, we managed to drop one, now + * let's ask our callers to call us + * again after garbage collecting */ + return -EAGAIN; + + /* We couldn't merge anything. Failure */ + return sd_bus_error_setf(e, BUS_ERROR_TRANSACTION_JOBS_CONFLICTING, + "Transaction contains conflicting jobs '%s' and '%s' for %s. " + "Probably contradicting requirement dependencies configured.", + job_type_to_string(t), + job_type_to_string(k->type), + k->unit->id); + } + } + + /* Second step, merge the jobs. */ + HASHMAP_FOREACH(j, tr->jobs, i) { + JobType t = j->type; + Job *k; + + /* Merge all transaction jobs for j->unit */ + LIST_FOREACH(transaction, k, j->transaction_next) + assert_se(job_type_merge_and_collapse(&t, k->type, j->unit) == 0); + + while ((k = j->transaction_next)) { + if (tr->anchor_job == k) { + transaction_merge_and_delete_job(tr, k, j, t); + j = k; + } else + transaction_merge_and_delete_job(tr, j, k, t); + } + + assert(!j->transaction_next); + assert(!j->transaction_prev); + } + + return 0; +} + +static void transaction_drop_redundant(Transaction *tr) { + Job *j; + Iterator i; + + /* Goes through the transaction and removes all jobs of the units + * whose jobs are all noops. If not all of a unit's jobs are + * redundant, they are kept. */ + + assert(tr); + +rescan: + HASHMAP_FOREACH(j, tr->jobs, i) { + Job *k; + + LIST_FOREACH(transaction, k, j) { + + if (tr->anchor_job == k || + !job_type_is_redundant(k->type, unit_active_state(k->unit)) || + (k->unit->job && job_type_is_conflicting(k->type, k->unit->job->type))) + goto next_unit; + } + + /* log_debug("Found redundant job %s/%s, dropping.", j->unit->id, job_type_to_string(j->type)); */ + transaction_delete_job(tr, j, false); + goto rescan; + next_unit:; + } +} + +_pure_ static bool unit_matters_to_anchor(Unit *u, Job *j) { + assert(u); + assert(!j->transaction_prev); + + /* Checks whether at least one of the jobs for this unit + * matters to the anchor. */ + + LIST_FOREACH(transaction, j, j) + if (j->matters_to_anchor) + return true; + + return false; +} + +static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsigned generation, sd_bus_error *e) { + Iterator i; + Unit *u; + int r; + + assert(tr); + assert(j); + assert(!j->transaction_prev); + + /* Does a recursive sweep through the ordering graph, looking + * for a cycle. If we find a cycle we try to break it. */ + + /* Have we seen this before? */ + if (j->generation == generation) { + Job *k, *delete; + + /* If the marker is NULL we have been here already and + * decided the job was loop-free from here. Hence + * shortcut things and return right-away. */ + if (!j->marker) + return 0; + + /* So, the marker is not NULL and we already have been + * here. We have a cycle. Let's try to break it. We go + * backwards in our path and try to find a suitable + * job to remove. We use the marker to find our way + * back, since smart how we are we stored our way back + * in there. */ + log_unit_warning(j->unit, + "Found ordering cycle on %s/%s", + j->unit->id, job_type_to_string(j->type)); + + delete = NULL; + for (k = from; k; k = ((k->generation == generation && k->marker != k) ? k->marker : NULL)) { + + /* logging for j not k here here to provide consistent narrative */ + log_unit_warning(j->unit, + "Found dependency on %s/%s", + k->unit->id, job_type_to_string(k->type)); + + if (!delete && hashmap_get(tr->jobs, k->unit) && !unit_matters_to_anchor(k->unit, k)) + /* Ok, we can drop this one, so let's + * do so. */ + delete = k; + + /* Check if this in fact was the beginning of + * the cycle */ + if (k == j) + break; + } + + + if (delete) { + const char *status; + /* logging for j not k here here to provide consistent narrative */ + log_unit_warning(j->unit, + "Breaking ordering cycle by deleting job %s/%s", + delete->unit->id, job_type_to_string(delete->type)); + log_unit_error(delete->unit, + "Job %s/%s deleted to break ordering cycle starting with %s/%s", + delete->unit->id, job_type_to_string(delete->type), + j->unit->id, job_type_to_string(j->type)); + + if (log_get_show_color()) + status = ANSI_HIGHLIGHT_RED " SKIP " ANSI_NORMAL; + else + status = " SKIP "; + + unit_status_printf(delete->unit, status, + "Ordering cycle found, skipping %s"); + transaction_delete_unit(tr, delete->unit); + return -EAGAIN; + } + + log_error("Unable to break cycle"); + + return sd_bus_error_setf(e, BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC, + "Transaction order is cyclic. See system logs for details."); + } + + /* Make the marker point to where we come from, so that we can + * find our way backwards if we want to break a cycle. We use + * a special marker for the beginning: we point to + * ourselves. */ + j->marker = from ? from : j; + j->generation = generation; + + /* We assume that the dependencies are bidirectional, and + * hence can ignore UNIT_AFTER */ + SET_FOREACH(u, j->unit->dependencies[UNIT_BEFORE], i) { + Job *o; + + /* Is there a job for this unit? */ + o = hashmap_get(tr->jobs, u); + if (!o) { + /* Ok, there is no job for this in the + * transaction, but maybe there is already one + * running? */ + o = u->job; + if (!o) + continue; + } + + r = transaction_verify_order_one(tr, o, j, generation, e); + if (r < 0) + return r; + } + + /* Ok, let's backtrack, and remember that this entry is not on + * our path anymore. */ + j->marker = NULL; + + return 0; +} + +static int transaction_verify_order(Transaction *tr, unsigned *generation, sd_bus_error *e) { + Job *j; + int r; + Iterator i; + unsigned g; + + assert(tr); + assert(generation); + + /* Check if the ordering graph is cyclic. If it is, try to fix + * that up by dropping one of the jobs. */ + + g = (*generation)++; + + HASHMAP_FOREACH(j, tr->jobs, i) { + r = transaction_verify_order_one(tr, j, NULL, g, e); + if (r < 0) + return r; + } + + return 0; +} + +static void transaction_collect_garbage(Transaction *tr) { + Iterator i; + Job *j; + + assert(tr); + + /* Drop jobs that are not required by any other job */ + +rescan: + HASHMAP_FOREACH(j, tr->jobs, i) { + if (tr->anchor_job == j || j->object_list) { + /* log_debug("Keeping job %s/%s because of %s/%s", */ + /* j->unit->id, job_type_to_string(j->type), */ + /* j->object_list->subject ? j->object_list->subject->unit->id : "root", */ + /* j->object_list->subject ? job_type_to_string(j->object_list->subject->type) : "root"); */ + continue; + } + + /* log_debug("Garbage collecting job %s/%s", j->unit->id, job_type_to_string(j->type)); */ + transaction_delete_job(tr, j, true); + goto rescan; + } +} + +static int transaction_is_destructive(Transaction *tr, JobMode mode, sd_bus_error *e) { + Iterator i; + Job *j; + + assert(tr); + + /* Checks whether applying this transaction means that + * existing jobs would be replaced */ + + HASHMAP_FOREACH(j, tr->jobs, i) { + + /* Assume merged */ + assert(!j->transaction_prev); + assert(!j->transaction_next); + + if (j->unit->job && (mode == JOB_FAIL || j->unit->job->irreversible) && + job_type_is_conflicting(j->unit->job->type, j->type)) + return sd_bus_error_setf(e, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE, + "Transaction is destructive."); + } + + return 0; +} + +static void transaction_minimize_impact(Transaction *tr) { + Job *j; + Iterator i; + + assert(tr); + + /* Drops all unnecessary jobs that reverse already active jobs + * or that stop a running service. */ + +rescan: + HASHMAP_FOREACH(j, tr->jobs, i) { + LIST_FOREACH(transaction, j, j) { + bool stops_running_service, changes_existing_job; + + /* If it matters, we shouldn't drop it */ + if (j->matters_to_anchor) + continue; + + /* Would this stop a running service? + * Would this change an existing job? + * If so, let's drop this entry */ + + stops_running_service = + j->type == JOB_STOP && UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(j->unit)); + + changes_existing_job = + j->unit->job && + job_type_is_conflicting(j->type, j->unit->job->type); + + if (!stops_running_service && !changes_existing_job) + continue; + + if (stops_running_service) + log_unit_debug(j->unit, + "%s/%s would stop a running service.", + j->unit->id, job_type_to_string(j->type)); + + if (changes_existing_job) + log_unit_debug(j->unit, + "%s/%s would change existing job.", + j->unit->id, job_type_to_string(j->type)); + + /* Ok, let's get rid of this */ + log_unit_debug(j->unit, + "Deleting %s/%s to minimize impact.", + j->unit->id, job_type_to_string(j->type)); + + transaction_delete_job(tr, j, true); + goto rescan; + } + } +} + +static int transaction_apply(Transaction *tr, Manager *m, JobMode mode) { + Iterator i; + Job *j; + int r; + + /* Moves the transaction jobs to the set of active jobs */ + + if (mode == JOB_ISOLATE || mode == JOB_FLUSH) { + + /* When isolating first kill all installed jobs which + * aren't part of the new transaction */ + HASHMAP_FOREACH(j, m->jobs, i) { + assert(j->installed); + + if (hashmap_get(tr->jobs, j->unit)) + continue; + + /* Not invalidating recursively. Avoids triggering + * OnFailure= actions of dependent jobs. Also avoids + * invalidating our iterator. */ + job_finish_and_invalidate(j, JOB_CANCELED, false, false); + } + } + + HASHMAP_FOREACH(j, tr->jobs, i) { + /* Assume merged */ + assert(!j->transaction_prev); + assert(!j->transaction_next); + + r = hashmap_put(m->jobs, UINT32_TO_PTR(j->id), j); + if (r < 0) + goto rollback; + } + + while ((j = hashmap_steal_first(tr->jobs))) { + Job *installed_job; + + /* Clean the job dependencies */ + transaction_unlink_job(tr, j, false); + + installed_job = job_install(j); + if (installed_job != j) { + /* j has been merged into a previously installed job */ + if (tr->anchor_job == j) + tr->anchor_job = installed_job; + hashmap_remove(m->jobs, UINT32_TO_PTR(j->id)); + job_free(j); + j = installed_job; + } + + job_add_to_run_queue(j); + job_add_to_dbus_queue(j); + job_start_timer(j); + job_shutdown_magic(j); + } + + return 0; + +rollback: + + HASHMAP_FOREACH(j, tr->jobs, i) + hashmap_remove(m->jobs, UINT32_TO_PTR(j->id)); + + return r; +} + +int transaction_activate(Transaction *tr, Manager *m, JobMode mode, sd_bus_error *e) { + Iterator i; + Job *j; + int r; + unsigned generation = 1; + + assert(tr); + + /* This applies the changes recorded in tr->jobs to + * the actual list of jobs, if possible. */ + + /* Reset the generation counter of all installed jobs. The detection of cycles + * looks at installed jobs. If they had a non-zero generation from some previous + * walk of the graph, the algorithm would break. */ + HASHMAP_FOREACH(j, m->jobs, i) + j->generation = 0; + + /* First step: figure out which jobs matter */ + transaction_find_jobs_that_matter_to_anchor(tr->anchor_job, generation++); + + /* Second step: Try not to stop any running services if + * we don't have to. Don't try to reverse running + * jobs if we don't have to. */ + if (mode == JOB_FAIL) + transaction_minimize_impact(tr); + + /* Third step: Drop redundant jobs */ + transaction_drop_redundant(tr); + + for (;;) { + /* Fourth step: Let's remove unneeded jobs that might + * be lurking. */ + if (mode != JOB_ISOLATE) + transaction_collect_garbage(tr); + + /* Fifth step: verify order makes sense and correct + * cycles if necessary and possible */ + r = transaction_verify_order(tr, &generation, e); + if (r >= 0) + break; + + if (r != -EAGAIN) { + log_warning("Requested transaction contains an unfixable cyclic ordering dependency: %s", bus_error_message(e, r)); + return r; + } + + /* Let's see if the resulting transaction ordering + * graph is still cyclic... */ + } + + for (;;) { + /* Sixth step: let's drop unmergeable entries if + * necessary and possible, merge entries we can + * merge */ + r = transaction_merge_jobs(tr, e); + if (r >= 0) + break; + + if (r != -EAGAIN) { + log_warning("Requested transaction contains unmergeable jobs: %s", bus_error_message(e, r)); + return r; + } + + /* Seventh step: an entry got dropped, let's garbage + * collect its dependencies. */ + if (mode != JOB_ISOLATE) + transaction_collect_garbage(tr); + + /* Let's see if the resulting transaction still has + * unmergeable entries ... */ + } + + /* Eights step: Drop redundant jobs again, if the merging now allows us to drop more. */ + transaction_drop_redundant(tr); + + /* Ninth step: check whether we can actually apply this */ + r = transaction_is_destructive(tr, mode, e); + if (r < 0) { + log_notice("Requested transaction contradicts existing jobs: %s", bus_error_message(e, r)); + return r; + } + + /* Tenth step: apply changes */ + r = transaction_apply(tr, m, mode); + if (r < 0) + return log_warning_errno(r, "Failed to apply transaction: %m"); + + assert(hashmap_isempty(tr->jobs)); + + if (!hashmap_isempty(m->jobs)) { + /* Are there any jobs now? Then make sure we have the + * idle pipe around. We don't really care too much + * whether this works or not, as the idle pipe is a + * feature for cosmetics, not actually useful for + * anything beyond that. */ + + if (m->idle_pipe[0] < 0 && m->idle_pipe[1] < 0 && + m->idle_pipe[2] < 0 && m->idle_pipe[3] < 0) { + (void) pipe2(m->idle_pipe, O_NONBLOCK|O_CLOEXEC); + (void) pipe2(m->idle_pipe + 2, O_NONBLOCK|O_CLOEXEC); + } + } + + return 0; +} + +static Job* transaction_add_one_job(Transaction *tr, JobType type, Unit *unit, bool *is_new) { + Job *j, *f; + + assert(tr); + assert(unit); + + /* Looks for an existing prospective job and returns that. If + * it doesn't exist it is created and added to the prospective + * jobs list. */ + + f = hashmap_get(tr->jobs, unit); + + LIST_FOREACH(transaction, j, f) { + assert(j->unit == unit); + + if (j->type == type) { + if (is_new) + *is_new = false; + return j; + } + } + + j = job_new(unit, type); + if (!j) + return NULL; + + j->generation = 0; + j->marker = NULL; + j->matters_to_anchor = false; + j->irreversible = tr->irreversible; + + LIST_PREPEND(transaction, f, j); + + if (hashmap_replace(tr->jobs, unit, f) < 0) { + LIST_REMOVE(transaction, f, j); + job_free(j); + return NULL; + } + + if (is_new) + *is_new = true; + + /* log_debug("Added job %s/%s to transaction.", unit->id, job_type_to_string(type)); */ + + return j; +} + +static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies) { + assert(tr); + assert(j); + + if (j->transaction_prev) + j->transaction_prev->transaction_next = j->transaction_next; + else if (j->transaction_next) + hashmap_replace(tr->jobs, j->unit, j->transaction_next); + else + hashmap_remove_value(tr->jobs, j->unit, j); + + if (j->transaction_next) + j->transaction_next->transaction_prev = j->transaction_prev; + + j->transaction_prev = j->transaction_next = NULL; + + while (j->subject_list) + job_dependency_free(j->subject_list); + + while (j->object_list) { + Job *other = j->object_list->matters ? j->object_list->subject : NULL; + + job_dependency_free(j->object_list); + + if (other && delete_dependencies) { + log_unit_debug(other->unit, + "Deleting job %s/%s as dependency of job %s/%s", + other->unit->id, job_type_to_string(other->type), + j->unit->id, job_type_to_string(j->type)); + transaction_delete_job(tr, other, delete_dependencies); + } + } +} + +int transaction_add_job_and_dependencies( + Transaction *tr, + JobType type, + Unit *unit, + Job *by, + bool matters, + bool conflicts, + bool ignore_requirements, + bool ignore_order, + sd_bus_error *e) { + Job *ret; + Iterator i; + Unit *dep; + int r; + bool is_new; + + assert(tr); + assert(type < _JOB_TYPE_MAX); + assert(type < _JOB_TYPE_MAX_IN_TRANSACTION); + assert(unit); + + /* Before adding jobs for this unit, let's ensure that its state has been loaded + * This matters when jobs are spawned as part of coldplugging itself (see e. g. path_coldplug()). + * This way, we "recursively" coldplug units, ensuring that we do not look at state of + * not-yet-coldplugged units. */ + if (MANAGER_IS_RELOADING(unit->manager)) + unit_coldplug(unit); + + /* log_debug("Pulling in %s/%s from %s/%s", */ + /* unit->id, job_type_to_string(type), */ + /* by ? by->unit->id : "NA", */ + /* by ? job_type_to_string(by->type) : "NA"); */ + + if (!IN_SET(unit->load_state, UNIT_LOADED, UNIT_ERROR, UNIT_NOT_FOUND, UNIT_MASKED)) + return sd_bus_error_setf(e, BUS_ERROR_LOAD_FAILED, "Unit %s is not loaded properly.", unit->id); + + if (type != JOB_STOP) { + r = bus_unit_check_load_state(unit, e); + if (r < 0) + return r; + } + + if (!unit_job_is_applicable(unit, type)) + return sd_bus_error_setf(e, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE, + "Job type %s is not applicable for unit %s.", + job_type_to_string(type), unit->id); + + + /* First add the job. */ + ret = transaction_add_one_job(tr, type, unit, &is_new); + if (!ret) + return -ENOMEM; + + ret->ignore_order = ret->ignore_order || ignore_order; + + /* Then, add a link to the job. */ + if (by) { + if (!job_dependency_new(by, ret, matters, conflicts)) + return -ENOMEM; + } else { + /* If the job has no parent job, it is the anchor job. */ + assert(!tr->anchor_job); + tr->anchor_job = ret; + } + + if (is_new && !ignore_requirements && type != JOB_NOP) { + Set *following; + + /* If we are following some other unit, make sure we + * add all dependencies of everybody following. */ + if (unit_following_set(ret->unit, &following) > 0) { + SET_FOREACH(dep, following, i) { + r = transaction_add_job_and_dependencies(tr, type, dep, ret, false, false, false, ignore_order, e); + if (r < 0) { + log_unit_warning(dep, "Cannot add dependency job for, ignoring: %s", bus_error_message(e, r)); + sd_bus_error_free(e); + } + } + + set_free(following); + } + + /* Finally, recursively add in all dependencies. */ + if (type == JOB_START || type == JOB_RESTART) { + SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES], i) { + r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, false, false, ignore_order, e); + if (r < 0) { + if (r != -EBADR) /* job type not applicable */ + goto fail; + + sd_bus_error_free(e); + } + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_BINDS_TO], i) { + r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, false, false, ignore_order, e); + if (r < 0) { + if (r != -EBADR) /* job type not applicable */ + goto fail; + + sd_bus_error_free(e); + } + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_WANTS], i) { + r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, false, false, false, ignore_order, e); + if (r < 0) { + /* unit masked, job type not applicable and unit not found are not considered as errors. */ + log_unit_full(dep, + IN_SET(r, -ERFKILL, -EBADR, -ENOENT) ? LOG_DEBUG : LOG_WARNING, + r, "Cannot add dependency job, ignoring: %s", + bus_error_message(e, r)); + sd_bus_error_free(e); + } + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE], i) { + r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, true, false, false, ignore_order, e); + if (r < 0) { + if (r != -EBADR) /* job type not applicable */ + goto fail; + + sd_bus_error_free(e); + } + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTS], i) { + r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, true, true, false, ignore_order, e); + if (r < 0) { + if (r != -EBADR) /* job type not applicable */ + goto fail; + + sd_bus_error_free(e); + } + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTED_BY], i) { + r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, false, false, false, ignore_order, e); + if (r < 0) { + log_unit_warning(dep, + "Cannot add dependency job, ignoring: %s", + bus_error_message(e, r)); + sd_bus_error_free(e); + } + } + + } + + if (type == JOB_STOP || type == JOB_RESTART) { + static const UnitDependency propagate_deps[] = { + UNIT_REQUIRED_BY, + UNIT_REQUISITE_OF, + UNIT_BOUND_BY, + UNIT_CONSISTS_OF, + }; + + JobType ptype; + unsigned j; + + /* We propagate STOP as STOP, but RESTART only + * as TRY_RESTART, in order not to start + * dependencies that are not around. */ + ptype = type == JOB_RESTART ? JOB_TRY_RESTART : type; + + for (j = 0; j < ELEMENTSOF(propagate_deps); j++) + SET_FOREACH(dep, ret->unit->dependencies[propagate_deps[j]], i) { + JobType nt; + + nt = job_type_collapse(ptype, dep); + if (nt == JOB_NOP) + continue; + + r = transaction_add_job_and_dependencies(tr, nt, dep, ret, true, false, false, ignore_order, e); + if (r < 0) { + if (r != -EBADR) /* job type not applicable */ + goto fail; + + sd_bus_error_free(e); + } + } + } + + if (type == JOB_RELOAD) { + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_PROPAGATES_RELOAD_TO], i) { + JobType nt; + + nt = job_type_collapse(JOB_TRY_RELOAD, dep); + if (nt == JOB_NOP) + continue; + + r = transaction_add_job_and_dependencies(tr, nt, dep, ret, false, false, false, ignore_order, e); + if (r < 0) { + log_unit_warning(dep, + "Cannot add dependency reload job, ignoring: %s", + bus_error_message(e, r)); + sd_bus_error_free(e); + } + } + } + + /* JOB_VERIFY_STARTED require no dependency handling */ + } + + return 0; + +fail: + return r; +} + +int transaction_add_isolate_jobs(Transaction *tr, Manager *m) { + Iterator i; + Unit *u; + char *k; + int r; + + assert(tr); + assert(m); + + HASHMAP_FOREACH_KEY(u, k, m->units, i) { + + /* ignore aliases */ + if (u->id != k) + continue; + + if (u->ignore_on_isolate) + continue; + + /* No need to stop inactive jobs */ + if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(u)) && !u->job) + continue; + + /* Is there already something listed for this? */ + if (hashmap_get(tr->jobs, u)) + continue; + + r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, tr->anchor_job, true, false, false, false, NULL); + if (r < 0) + log_unit_warning_errno(u, r, "Cannot add isolate job, ignoring: %m"); + } + + return 0; +} + +Transaction *transaction_new(bool irreversible) { + Transaction *tr; + + tr = new0(Transaction, 1); + if (!tr) + return NULL; + + tr->jobs = hashmap_new(NULL); + if (!tr->jobs) { + free(tr); + return NULL; + } + + tr->irreversible = irreversible; + + return tr; +} + +void transaction_free(Transaction *tr) { + assert(hashmap_isempty(tr->jobs)); + hashmap_free(tr->jobs); + free(tr); +} diff --git a/src/libcore/transaction.h b/src/libcore/transaction.h new file mode 100644 index 0000000000..6a3f927b0f --- /dev/null +++ b/src/libcore/transaction.h @@ -0,0 +1,51 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct Transaction Transaction; + +#include "hashmap.h" +#include "job.h" +#include "manager.h" +#include "unit.h" + +struct Transaction { + /* Jobs to be added */ + Hashmap *jobs; /* Unit object => Job object list 1:1 */ + Job *anchor_job; /* the job the user asked for */ + bool irreversible; +}; + +Transaction *transaction_new(bool irreversible); +void transaction_free(Transaction *tr); + +int transaction_add_job_and_dependencies( + Transaction *tr, + JobType type, + Unit *unit, + Job *by, + bool matters, + bool conflicts, + bool ignore_requirements, + bool ignore_order, + sd_bus_error *e); +int transaction_activate(Transaction *tr, Manager *m, JobMode mode, sd_bus_error *e); +int transaction_add_isolate_jobs(Transaction *tr, Manager *m); +void transaction_abort(Transaction *tr); diff --git a/src/libcore/umount.c b/src/libcore/umount.c new file mode 100644 index 0000000000..c21a2be54e --- /dev/null +++ b/src/libcore/umount.c @@ -0,0 +1,614 @@ +/*** + This file is part of systemd. + + Copyright 2010 ProFUSION embedded systems + + 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 +#include +#include +#include +#include +#include + +#include "libudev.h" + +#include "alloc-util.h" +#include "escape.h" +#include "fd-util.h" +#include "fstab-util.h" +#include "list.h" +#include "mount-setup.h" +#include "path-util.h" +#include "string-util.h" +#include "udev-util.h" +#include "umount.h" +#include "util.h" +#include "virt.h" + +typedef struct MountPoint { + char *path; + char *options; + dev_t devnum; + LIST_FIELDS(struct MountPoint, mount_point); +} MountPoint; + +static void mount_point_free(MountPoint **head, MountPoint *m) { + assert(head); + assert(m); + + LIST_REMOVE(mount_point, *head, m); + + free(m->path); + free(m); +} + +static void mount_points_list_free(MountPoint **head) { + assert(head); + + while (*head) + mount_point_free(head, *head); +} + +static int mount_points_list_get(MountPoint **head) { + _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; + unsigned int i; + int r; + + assert(head); + + proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); + if (!proc_self_mountinfo) + return -errno; + + for (i = 1;; i++) { + _cleanup_free_ char *path = NULL, *options = NULL; + char *p = NULL; + MountPoint *m; + int k; + + k = fscanf(proc_self_mountinfo, + "%*s " /* (1) mount id */ + "%*s " /* (2) parent id */ + "%*s " /* (3) major:minor */ + "%*s " /* (4) root */ + "%ms " /* (5) mount point */ + "%*s" /* (6) mount flags */ + "%*[^-]" /* (7) optional fields */ + "- " /* (8) separator */ + "%*s " /* (9) file system type */ + "%*s" /* (10) mount source */ + "%ms" /* (11) mount options */ + "%*[^\n]", /* some rubbish at the end */ + &path, &options); + if (k != 2) { + if (k == EOF) + break; + + log_warning("Failed to parse /proc/self/mountinfo:%u.", i); + continue; + } + + r = cunescape(path, UNESCAPE_RELAX, &p); + if (r < 0) + return r; + + /* Ignore mount points we can't unmount because they + * are API or because we are keeping them open (like + * /dev/console). Also, ignore all mounts below API + * file systems, since they are likely virtual too, + * and hence not worth spending time on. Also, in + * unprivileged containers we might lack the rights to + * unmount these things, hence don't bother. */ + if (mount_point_is_api(p) || + mount_point_ignore(p) || + path_startswith(p, "/dev") || + path_startswith(p, "/sys") || + path_startswith(p, "/proc")) { + free(p); + continue; + } + + m = new0(MountPoint, 1); + if (!m) { + free(p); + return -ENOMEM; + } + + m->path = p; + m->options = options; + options = NULL; + + LIST_PREPEND(mount_point, *head, m); + } + + return 0; +} + +static int swap_list_get(MountPoint **head) { + _cleanup_fclose_ FILE *proc_swaps = NULL; + unsigned int i; + int r; + + assert(head); + + proc_swaps = fopen("/proc/swaps", "re"); + if (!proc_swaps) + return (errno == ENOENT) ? 0 : -errno; + + (void) fscanf(proc_swaps, "%*s %*s %*s %*s %*s\n"); + + for (i = 2;; i++) { + MountPoint *swap; + char *dev = NULL, *d; + int k; + + k = fscanf(proc_swaps, + "%ms " /* device/file */ + "%*s " /* type of swap */ + "%*s " /* swap size */ + "%*s " /* used */ + "%*s\n", /* priority */ + &dev); + + if (k != 1) { + if (k == EOF) + break; + + log_warning("Failed to parse /proc/swaps:%u.", i); + free(dev); + continue; + } + + if (endswith(dev, " (deleted)")) { + free(dev); + continue; + } + + r = cunescape(dev, UNESCAPE_RELAX, &d); + free(dev); + if (r < 0) + return r; + + swap = new0(MountPoint, 1); + if (!swap) { + free(d); + return -ENOMEM; + } + + swap->path = d; + LIST_PREPEND(mount_point, *head, swap); + } + + return 0; +} + +static int loopback_list_get(MountPoint **head) { + _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + _cleanup_udev_unref_ struct udev *udev = NULL; + int r; + + assert(head); + + udev = udev_new(); + if (!udev) + return -ENOMEM; + + e = udev_enumerate_new(udev); + if (!e) + return -ENOMEM; + + r = udev_enumerate_add_match_subsystem(e, "block"); + if (r < 0) + return r; + + r = udev_enumerate_add_match_sysname(e, "loop*"); + if (r < 0) + return r; + + r = udev_enumerate_add_match_sysattr(e, "loop/backing_file", NULL); + if (r < 0) + return r; + + r = udev_enumerate_scan_devices(e); + if (r < 0) + return r; + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) { + MountPoint *lb; + _cleanup_udev_device_unref_ struct udev_device *d; + char *loop; + const char *dn; + + d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); + if (!d) + return -ENOMEM; + + dn = udev_device_get_devnode(d); + if (!dn) + continue; + + loop = strdup(dn); + if (!loop) + return -ENOMEM; + + lb = new0(MountPoint, 1); + if (!lb) { + free(loop); + return -ENOMEM; + } + + lb->path = loop; + LIST_PREPEND(mount_point, *head, lb); + } + + return 0; +} + +static int dm_list_get(MountPoint **head) { + _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + _cleanup_udev_unref_ struct udev *udev = NULL; + int r; + + assert(head); + + udev = udev_new(); + if (!udev) + return -ENOMEM; + + e = udev_enumerate_new(udev); + if (!e) + return -ENOMEM; + + r = udev_enumerate_add_match_subsystem(e, "block"); + if (r < 0) + return r; + + r = udev_enumerate_add_match_sysname(e, "dm-*"); + if (r < 0) + return r; + + r = udev_enumerate_scan_devices(e); + if (r < 0) + return r; + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) { + MountPoint *m; + _cleanup_udev_device_unref_ struct udev_device *d; + dev_t devnum; + char *node; + const char *dn; + + d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); + if (!d) + return -ENOMEM; + + devnum = udev_device_get_devnum(d); + dn = udev_device_get_devnode(d); + if (major(devnum) == 0 || !dn) + continue; + + node = strdup(dn); + if (!node) + return -ENOMEM; + + m = new(MountPoint, 1); + if (!m) { + free(node); + return -ENOMEM; + } + + m->path = node; + m->devnum = devnum; + LIST_PREPEND(mount_point, *head, m); + } + + return 0; +} + +static int delete_loopback(const char *device) { + _cleanup_close_ int fd = -1; + int r; + + fd = open(device, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return errno == ENOENT ? 0 : -errno; + + r = ioctl(fd, LOOP_CLR_FD, 0); + if (r >= 0) + return 1; + + /* ENXIO: not bound, so no error */ + if (errno == ENXIO) + return 0; + + return -errno; +} + +static int delete_dm(dev_t devnum) { + _cleanup_close_ int fd = -1; + int r; + struct dm_ioctl dm = { + .version = {DM_VERSION_MAJOR, + DM_VERSION_MINOR, + DM_VERSION_PATCHLEVEL}, + .data_size = sizeof(dm), + .dev = devnum, + }; + + assert(major(devnum) != 0); + + fd = open("/dev/mapper/control", O_RDWR|O_CLOEXEC); + if (fd < 0) + return -errno; + + r = ioctl(fd, DM_DEV_REMOVE, &dm); + return r >= 0 ? 0 : -errno; +} + +static int mount_points_list_umount(MountPoint **head, bool *changed, bool log_error) { + MountPoint *m, *n; + int n_failed = 0; + + assert(head); + + LIST_FOREACH_SAFE(mount_point, m, n, *head) { + + /* If we are in a container, don't attempt to + read-only mount anything as that brings no real + benefits, but might confuse the host, as we remount + the superblock here, not the bind mound. */ + if (detect_container() <= 0) { + _cleanup_free_ char *options = NULL; + /* MS_REMOUNT requires that the data parameter + * should be the same from the original mount + * except for the desired changes. Since we want + * to remount read-only, we should filter out + * rw (and ro too, because it confuses the kernel) */ + (void) fstab_filter_options(m->options, "rw\0ro\0", NULL, NULL, &options); + + /* We always try to remount directories + * read-only first, before we go on and umount + * them. + * + * Mount points can be stacked. If a mount + * point is stacked below / or /usr, we + * cannot umount or remount it directly, + * since there is no way to refer to the + * underlying mount. There's nothing we can do + * about it for the general case, but we can + * do something about it if it is aliased + * somehwere else via a bind mount. If we + * explicitly remount the super block of that + * alias read-only we hence should be + * relatively safe regarding keeping the fs we + * can otherwise not see dirty. */ + log_info("Remounting '%s' read-only with options '%s'.", m->path, options); + (void) mount(NULL, m->path, NULL, MS_REMOUNT|MS_RDONLY, options); + } + + /* Skip / and /usr since we cannot unmount that + * anyway, since we are running from it. They have + * already been remounted ro. */ + if (path_equal(m->path, "/") +#ifndef HAVE_SPLIT_USR + || path_equal(m->path, "/usr") +#endif + || path_startswith(m->path, "/run/initramfs") + ) + continue; + + /* Trying to umount. We don't force here since we rely + * on busy NFS and FUSE file systems to return EBUSY + * until we closed everything on top of them. */ + log_info("Unmounting %s.", m->path); + if (umount2(m->path, 0) == 0) { + if (changed) + *changed = true; + + mount_point_free(head, m); + } else if (log_error) { + log_warning_errno(errno, "Could not unmount %s: %m", m->path); + n_failed++; + } + } + + return n_failed; +} + +static int swap_points_list_off(MountPoint **head, bool *changed) { + MountPoint *m, *n; + int n_failed = 0; + + assert(head); + + LIST_FOREACH_SAFE(mount_point, m, n, *head) { + log_info("Deactivating swap %s.", m->path); + if (swapoff(m->path) == 0) { + if (changed) + *changed = true; + + mount_point_free(head, m); + } else { + log_warning_errno(errno, "Could not deactivate swap %s: %m", m->path); + n_failed++; + } + } + + return n_failed; +} + +static int loopback_points_list_detach(MountPoint **head, bool *changed) { + MountPoint *m, *n; + int n_failed = 0, k; + struct stat root_st; + + assert(head); + + k = lstat("/", &root_st); + + LIST_FOREACH_SAFE(mount_point, m, n, *head) { + int r; + struct stat loopback_st; + + if (k >= 0 && + major(root_st.st_dev) != 0 && + lstat(m->path, &loopback_st) >= 0 && + root_st.st_dev == loopback_st.st_rdev) { + n_failed++; + continue; + } + + log_info("Detaching loopback %s.", m->path); + r = delete_loopback(m->path); + if (r >= 0) { + if (r > 0 && changed) + *changed = true; + + mount_point_free(head, m); + } else { + log_warning_errno(errno, "Could not detach loopback %s: %m", m->path); + n_failed++; + } + } + + return n_failed; +} + +static int dm_points_list_detach(MountPoint **head, bool *changed) { + MountPoint *m, *n; + int n_failed = 0, k; + struct stat root_st; + + assert(head); + + k = lstat("/", &root_st); + + LIST_FOREACH_SAFE(mount_point, m, n, *head) { + int r; + + if (k >= 0 && + major(root_st.st_dev) != 0 && + root_st.st_dev == m->devnum) { + n_failed++; + continue; + } + + log_info("Detaching DM %u:%u.", major(m->devnum), minor(m->devnum)); + r = delete_dm(m->devnum); + if (r >= 0) { + if (changed) + *changed = true; + + mount_point_free(head, m); + } else { + log_warning_errno(errno, "Could not detach DM %s: %m", m->path); + n_failed++; + } + } + + return n_failed; +} + +int umount_all(bool *changed) { + int r; + bool umount_changed; + LIST_HEAD(MountPoint, mp_list_head); + + LIST_HEAD_INIT(mp_list_head); + r = mount_points_list_get(&mp_list_head); + if (r < 0) + goto end; + + /* retry umount, until nothing can be umounted anymore */ + do { + umount_changed = false; + + mount_points_list_umount(&mp_list_head, &umount_changed, false); + if (umount_changed) + *changed = true; + + } while (umount_changed); + + /* umount one more time with logging enabled */ + r = mount_points_list_umount(&mp_list_head, &umount_changed, true); + if (r <= 0) + goto end; + + end: + mount_points_list_free(&mp_list_head); + + return r; +} + +int swapoff_all(bool *changed) { + int r; + LIST_HEAD(MountPoint, swap_list_head); + + LIST_HEAD_INIT(swap_list_head); + + r = swap_list_get(&swap_list_head); + if (r < 0) + goto end; + + r = swap_points_list_off(&swap_list_head, changed); + + end: + mount_points_list_free(&swap_list_head); + + return r; +} + +int loopback_detach_all(bool *changed) { + int r; + LIST_HEAD(MountPoint, loopback_list_head); + + LIST_HEAD_INIT(loopback_list_head); + + r = loopback_list_get(&loopback_list_head); + if (r < 0) + goto end; + + r = loopback_points_list_detach(&loopback_list_head, changed); + + end: + mount_points_list_free(&loopback_list_head); + + return r; +} + +int dm_detach_all(bool *changed) { + int r; + LIST_HEAD(MountPoint, dm_list_head); + + LIST_HEAD_INIT(dm_list_head); + + r = dm_list_get(&dm_list_head); + if (r < 0) + goto end; + + r = dm_points_list_detach(&dm_list_head, changed); + + end: + mount_points_list_free(&dm_list_head); + + return r; +} diff --git a/src/libcore/umount.h b/src/libcore/umount.h new file mode 100644 index 0000000000..4e2215a47d --- /dev/null +++ b/src/libcore/umount.h @@ -0,0 +1,28 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 ProFUSION embedded systems + + 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 . +***/ + +int umount_all(bool *changed); + +int swapoff_all(bool *changed); + +int loopback_detach_all(bool *changed); + +int dm_detach_all(bool *changed); diff --git a/src/libcore/unit-printf.c b/src/libcore/unit-printf.c new file mode 100644 index 0000000000..f11df42af3 --- /dev/null +++ b/src/libcore/unit-printf.c @@ -0,0 +1,304 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 "alloc-util.h" +#include "cgroup-util.h" +#include "formats-util.h" +#include "macro.h" +#include "specifier.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "unit-printf.h" +#include "unit.h" +#include "user-util.h" + +static int specifier_prefix_and_instance(char specifier, void *data, void *userdata, char **ret) { + Unit *u = userdata; + + assert(u); + + return unit_name_to_prefix_and_instance(u->id, ret); +} + +static int specifier_prefix(char specifier, void *data, void *userdata, char **ret) { + Unit *u = userdata; + + assert(u); + + return unit_name_to_prefix(u->id, ret); +} + +static int specifier_prefix_unescaped(char specifier, void *data, void *userdata, char **ret) { + _cleanup_free_ char *p = NULL; + Unit *u = userdata; + int r; + + assert(u); + + r = unit_name_to_prefix(u->id, &p); + if (r < 0) + return r; + + return unit_name_unescape(p, ret); +} + +static int specifier_instance_unescaped(char specifier, void *data, void *userdata, char **ret) { + Unit *u = userdata; + + assert(u); + + return unit_name_unescape(strempty(u->instance), ret); +} + +static int specifier_filename(char specifier, void *data, void *userdata, char **ret) { + Unit *u = userdata; + + assert(u); + + if (u->instance) + return unit_name_path_unescape(u->instance, ret); + else + return unit_name_to_path(u->id, ret); +} + +static int specifier_cgroup(char specifier, void *data, void *userdata, char **ret) { + Unit *u = userdata; + char *n; + + assert(u); + + if (u->cgroup_path) + n = strdup(u->cgroup_path); + else + n = unit_default_cgroup_path(u); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +static int specifier_cgroup_root(char specifier, void *data, void *userdata, char **ret) { + Unit *u = userdata; + char *n; + + assert(u); + + n = strdup(u->manager->cgroup_root); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +static int specifier_cgroup_slice(char specifier, void *data, void *userdata, char **ret) { + Unit *u = userdata; + char *n; + + assert(u); + + if (UNIT_ISSET(u->slice)) { + Unit *slice; + + slice = UNIT_DEREF(u->slice); + + if (slice->cgroup_path) + n = strdup(slice->cgroup_path); + else + n = unit_default_cgroup_path(slice); + } else + n = strdup(u->manager->cgroup_root); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +static int specifier_runtime(char specifier, void *data, void *userdata, char **ret) { + Unit *u = userdata; + const char *e; + char *n = NULL; + + assert(u); + + e = manager_get_runtime_prefix(u->manager); + if (!e) + return -EOPNOTSUPP; + n = strdup(e); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +static int specifier_user_name(char specifier, void *data, void *userdata, char **ret) { + char *t; + + /* If we are UID 0 (root), this will not result in NSS, + * otherwise it might. This is good, as we want to be able to + * run this in PID 1, where our user ID is 0, but where NSS + * lookups are not allowed. */ + + t = getusername_malloc(); + if (!t) + return -ENOMEM; + + *ret = t; + return 0; +} + +static int specifier_user_id(char specifier, void *data, void *userdata, char **ret) { + + if (asprintf(ret, UID_FMT, getuid()) < 0) + return -ENOMEM; + + return 0; +} + +static int specifier_user_home(char specifier, void *data, void *userdata, char **ret) { + + /* On PID 1 (which runs as root) this will not result in NSS, + * which is good. See above */ + + return get_home_dir(ret); +} + +static int specifier_user_shell(char specifier, void *data, void *userdata, char **ret) { + + /* On PID 1 (which runs as root) this will not result in NSS, + * which is good. See above */ + + return get_shell(ret); +} + +int unit_name_printf(Unit *u, const char* format, char **ret) { + + /* + * This will use the passed string as format string and + * replace the following specifiers: + * + * %n: the full id of the unit (foo@bar.waldo) + * %N: the id of the unit without the suffix (foo@bar) + * %p: the prefix (foo) + * %i: the instance (bar) + */ + + const Specifier table[] = { + { 'n', specifier_string, u->id }, + { 'N', specifier_prefix_and_instance, NULL }, + { 'p', specifier_prefix, NULL }, + { 'i', specifier_string, u->instance }, + { 0, NULL, NULL } + }; + + assert(u); + assert(format); + assert(ret); + + return specifier_printf(format, table, u, ret); +} + +int unit_full_printf(Unit *u, const char *format, char **ret) { + + /* This is similar to unit_name_printf() but also supports + * unescaping. Also, adds a couple of additional codes: + * + * %f the instance if set, otherwise the id + * %c cgroup path of unit + * %r where units in this slice are placed in the cgroup tree + * %R the root of this systemd's instance tree + * %t the runtime directory to place sockets in (e.g. "/run" or $XDG_RUNTIME_DIR) + * %U the UID of the running user + * %u the username of the running user + * %h the homedir of the running user + * %s the shell of the running user + * %m the machine ID of the running system + * %H the host name of the running system + * %b the boot ID of the running system + * %v `uname -r` of the running system + */ + + const Specifier table[] = { + { 'n', specifier_string, u->id }, + { 'N', specifier_prefix_and_instance, NULL }, + { 'p', specifier_prefix, NULL }, + { 'P', specifier_prefix_unescaped, NULL }, + { 'i', specifier_string, u->instance }, + { 'I', specifier_instance_unescaped, NULL }, + + { 'f', specifier_filename, NULL }, + { 'c', specifier_cgroup, NULL }, + { 'r', specifier_cgroup_slice, NULL }, + { 'R', specifier_cgroup_root, NULL }, + { 't', specifier_runtime, NULL }, + + { 'U', specifier_user_id, NULL }, + { 'u', specifier_user_name, NULL }, + { 'h', specifier_user_home, NULL }, + { 's', specifier_user_shell, NULL }, + + { 'm', specifier_machine_id, NULL }, + { 'H', specifier_host_name, NULL }, + { 'b', specifier_boot_id, NULL }, + { 'v', specifier_kernel_release, NULL }, + {} + }; + + assert(u); + assert(format); + assert(ret); + + return specifier_printf(format, table, u, ret); +} + +int unit_full_printf_strv(Unit *u, char **l, char ***ret) { + size_t n; + char **r, **i, **j; + int q; + + /* Applies unit_full_printf to every entry in l */ + + assert(u); + + n = strv_length(l); + r = new(char*, n+1); + if (!r) + return -ENOMEM; + + for (i = l, j = r; *i; i++, j++) { + q = unit_full_printf(u, *i, j); + if (q < 0) + goto fail; + } + + *j = NULL; + *ret = r; + return 0; + +fail: + for (j--; j >= r; j--) + free(*j); + + free(r); + return q; +} diff --git a/src/libcore/unit-printf.h b/src/libcore/unit-printf.h new file mode 100644 index 0000000000..4fc8531228 --- /dev/null +++ b/src/libcore/unit-printf.h @@ -0,0 +1,26 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "unit.h" + +int unit_name_printf(Unit *u, const char* text, char **ret); +int unit_full_printf(Unit *u, const char *text, char **ret); +int unit_full_printf_strv(Unit *u, char **l, char ***ret); diff --git a/src/libcore/unit.c b/src/libcore/unit.c new file mode 100644 index 0000000000..8bd39f87f9 --- /dev/null +++ b/src/libcore/unit.c @@ -0,0 +1,3818 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include + +#include +#include + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "dbus-unit.h" +#include "dbus.h" +#include "dropin.h" +#include "escape.h" +#include "execute.h" +#include "fileio-label.h" +#include "formats-util.h" +#include "load-dropin.h" +#include "load-fragment.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "set.h" +#include "signal-util.h" +#include "special.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "umask-util.h" +#include "unit-name.h" +#include "unit.h" +#include "user-util.h" +#include "virt.h" + +const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { + [UNIT_SERVICE] = &service_vtable, + [UNIT_SOCKET] = &socket_vtable, + [UNIT_BUSNAME] = &busname_vtable, + [UNIT_TARGET] = &target_vtable, + [UNIT_DEVICE] = &device_vtable, + [UNIT_MOUNT] = &mount_vtable, + [UNIT_AUTOMOUNT] = &automount_vtable, + [UNIT_SWAP] = &swap_vtable, + [UNIT_TIMER] = &timer_vtable, + [UNIT_PATH] = &path_vtable, + [UNIT_SLICE] = &slice_vtable, + [UNIT_SCOPE] = &scope_vtable +}; + +static void maybe_warn_about_dependency(Unit *u, const char *other, UnitDependency dependency); + +Unit *unit_new(Manager *m, size_t size) { + Unit *u; + + assert(m); + assert(size >= sizeof(Unit)); + + u = malloc0(size); + if (!u) + return NULL; + + u->names = set_new(&string_hash_ops); + if (!u->names) { + free(u); + return NULL; + } + + u->manager = m; + u->type = _UNIT_TYPE_INVALID; + u->default_dependencies = true; + u->unit_file_state = _UNIT_FILE_STATE_INVALID; + u->unit_file_preset = -1; + u->on_failure_job_mode = JOB_REPLACE; + u->cgroup_inotify_wd = -1; + u->job_timeout = USEC_INFINITY; + + RATELIMIT_INIT(u->start_limit, m->default_start_limit_interval, m->default_start_limit_burst); + RATELIMIT_INIT(u->auto_stop_ratelimit, 10 * USEC_PER_SEC, 16); + + return u; +} + +bool unit_has_name(Unit *u, const char *name) { + assert(u); + assert(name); + + return !!set_get(u->names, (char*) name); +} + +static void unit_init(Unit *u) { + CGroupContext *cc; + ExecContext *ec; + KillContext *kc; + + assert(u); + assert(u->manager); + assert(u->type >= 0); + + cc = unit_get_cgroup_context(u); + if (cc) { + cgroup_context_init(cc); + + /* Copy in the manager defaults into the cgroup + * context, _before_ the rest of the settings have + * been initialized */ + + cc->cpu_accounting = u->manager->default_cpu_accounting; + cc->io_accounting = u->manager->default_io_accounting; + cc->blockio_accounting = u->manager->default_blockio_accounting; + cc->memory_accounting = u->manager->default_memory_accounting; + cc->tasks_accounting = u->manager->default_tasks_accounting; + + if (u->type != UNIT_SLICE) + cc->tasks_max = u->manager->default_tasks_max; + } + + ec = unit_get_exec_context(u); + if (ec) + exec_context_init(ec); + + kc = unit_get_kill_context(u); + if (kc) + kill_context_init(kc); + + if (UNIT_VTABLE(u)->init) + UNIT_VTABLE(u)->init(u); +} + +int unit_add_name(Unit *u, const char *text) { + _cleanup_free_ char *s = NULL, *i = NULL; + UnitType t; + int r; + + assert(u); + assert(text); + + if (unit_name_is_valid(text, UNIT_NAME_TEMPLATE)) { + + if (!u->instance) + return -EINVAL; + + r = unit_name_replace_instance(text, u->instance, &s); + if (r < 0) + return r; + } else { + s = strdup(text); + if (!s) + return -ENOMEM; + } + + if (set_contains(u->names, s)) + return 0; + if (hashmap_contains(u->manager->units, s)) + return -EEXIST; + + if (!unit_name_is_valid(s, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) + return -EINVAL; + + t = unit_name_to_type(s); + if (t < 0) + return -EINVAL; + + if (u->type != _UNIT_TYPE_INVALID && t != u->type) + return -EINVAL; + + r = unit_name_to_instance(s, &i); + if (r < 0) + return r; + + if (i && !unit_type_may_template(t)) + return -EINVAL; + + /* Ensure that this unit is either instanced or not instanced, + * but not both. Note that we do allow names with different + * instance names however! */ + if (u->type != _UNIT_TYPE_INVALID && !u->instance != !i) + return -EINVAL; + + if (!unit_type_may_alias(t) && !set_isempty(u->names)) + return -EEXIST; + + if (hashmap_size(u->manager->units) >= MANAGER_MAX_NAMES) + return -E2BIG; + + r = set_put(u->names, s); + if (r < 0) + return r; + assert(r > 0); + + r = hashmap_put(u->manager->units, s, u); + if (r < 0) { + (void) set_remove(u->names, s); + return r; + } + + if (u->type == _UNIT_TYPE_INVALID) { + u->type = t; + u->id = s; + u->instance = i; + + LIST_PREPEND(units_by_type, u->manager->units_by_type[t], u); + + unit_init(u); + + i = NULL; + } + + s = NULL; + + unit_add_to_dbus_queue(u); + return 0; +} + +int unit_choose_id(Unit *u, const char *name) { + _cleanup_free_ char *t = NULL; + char *s, *i; + int r; + + assert(u); + assert(name); + + if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { + + if (!u->instance) + return -EINVAL; + + r = unit_name_replace_instance(name, u->instance, &t); + if (r < 0) + return r; + + name = t; + } + + /* Selects one of the names of this unit as the id */ + s = set_get(u->names, (char*) name); + if (!s) + return -ENOENT; + + /* Determine the new instance from the new id */ + r = unit_name_to_instance(s, &i); + if (r < 0) + return r; + + u->id = s; + + free(u->instance); + u->instance = i; + + unit_add_to_dbus_queue(u); + + return 0; +} + +int unit_set_description(Unit *u, const char *description) { + char *s; + + assert(u); + + if (isempty(description)) + s = NULL; + else { + s = strdup(description); + if (!s) + return -ENOMEM; + } + + free(u->description); + u->description = s; + + unit_add_to_dbus_queue(u); + return 0; +} + +bool unit_check_gc(Unit *u) { + UnitActiveState state; + assert(u); + + if (u->job) + return true; + + if (u->nop_job) + return true; + + state = unit_active_state(u); + + /* If the unit is inactive and failed and no job is queued for + * it, then release its runtime resources */ + if (UNIT_IS_INACTIVE_OR_FAILED(state) && + UNIT_VTABLE(u)->release_resources) + UNIT_VTABLE(u)->release_resources(u); + + /* But we keep the unit object around for longer when it is + * referenced or configured to not be gc'ed */ + if (state != UNIT_INACTIVE) + return true; + + if (u->no_gc) + return true; + + if (u->refs) + return true; + + if (UNIT_VTABLE(u)->check_gc) + if (UNIT_VTABLE(u)->check_gc(u)) + return true; + + return false; +} + +void unit_add_to_load_queue(Unit *u) { + assert(u); + assert(u->type != _UNIT_TYPE_INVALID); + + if (u->load_state != UNIT_STUB || u->in_load_queue) + return; + + LIST_PREPEND(load_queue, u->manager->load_queue, u); + u->in_load_queue = true; +} + +void unit_add_to_cleanup_queue(Unit *u) { + assert(u); + + if (u->in_cleanup_queue) + return; + + LIST_PREPEND(cleanup_queue, u->manager->cleanup_queue, u); + u->in_cleanup_queue = true; +} + +void unit_add_to_gc_queue(Unit *u) { + assert(u); + + if (u->in_gc_queue || u->in_cleanup_queue) + return; + + if (unit_check_gc(u)) + return; + + LIST_PREPEND(gc_queue, u->manager->gc_queue, u); + u->in_gc_queue = true; + + u->manager->n_in_gc_queue++; +} + +void unit_add_to_dbus_queue(Unit *u) { + assert(u); + assert(u->type != _UNIT_TYPE_INVALID); + + if (u->load_state == UNIT_STUB || u->in_dbus_queue) + return; + + /* Shortcut things if nobody cares */ + if (sd_bus_track_count(u->manager->subscribed) <= 0 && + set_isempty(u->manager->private_buses)) { + u->sent_dbus_new_signal = true; + return; + } + + LIST_PREPEND(dbus_queue, u->manager->dbus_unit_queue, u); + u->in_dbus_queue = true; +} + +static void bidi_set_free(Unit *u, Set *s) { + Iterator i; + Unit *other; + + assert(u); + + /* Frees the set and makes sure we are dropped from the + * inverse pointers */ + + SET_FOREACH(other, s, i) { + UnitDependency d; + + for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) + set_remove(other->dependencies[d], u); + + unit_add_to_gc_queue(other); + } + + set_free(s); +} + +static void unit_remove_transient(Unit *u) { + char **i; + + assert(u); + + if (!u->transient) + return; + + if (u->fragment_path) + (void) unlink(u->fragment_path); + + STRV_FOREACH(i, u->dropin_paths) { + _cleanup_free_ char *p = NULL, *pp = NULL; + + p = dirname_malloc(*i); /* Get the drop-in directory from the drop-in file */ + if (!p) + continue; + + pp = dirname_malloc(p); /* Get the config directory from the drop-in directory */ + if (!pp) + continue; + + /* Only drop transient drop-ins */ + if (!path_equal(u->manager->lookup_paths.transient, pp)) + continue; + + (void) unlink(*i); + (void) rmdir(p); + } +} + +static void unit_free_requires_mounts_for(Unit *u) { + char **j; + + STRV_FOREACH(j, u->requires_mounts_for) { + char s[strlen(*j) + 1]; + + PATH_FOREACH_PREFIX_MORE(s, *j) { + char *y; + Set *x; + + x = hashmap_get2(u->manager->units_requiring_mounts_for, s, (void**) &y); + if (!x) + continue; + + set_remove(x, u); + + if (set_isempty(x)) { + hashmap_remove(u->manager->units_requiring_mounts_for, y); + free(y); + set_free(x); + } + } + } + + u->requires_mounts_for = strv_free(u->requires_mounts_for); +} + +static void unit_done(Unit *u) { + ExecContext *ec; + CGroupContext *cc; + + assert(u); + + if (u->type < 0) + return; + + if (UNIT_VTABLE(u)->done) + UNIT_VTABLE(u)->done(u); + + ec = unit_get_exec_context(u); + if (ec) + exec_context_done(ec); + + cc = unit_get_cgroup_context(u); + if (cc) + cgroup_context_done(cc); +} + +void unit_free(Unit *u) { + UnitDependency d; + Iterator i; + char *t; + + assert(u); + + if (u->transient_file) + fclose(u->transient_file); + + if (!MANAGER_IS_RELOADING(u->manager)) + unit_remove_transient(u); + + bus_unit_send_removed_signal(u); + + unit_done(u); + + sd_bus_slot_unref(u->match_bus_slot); + + unit_free_requires_mounts_for(u); + + SET_FOREACH(t, u->names, i) + hashmap_remove_value(u->manager->units, t, u); + + if (u->job) { + Job *j = u->job; + job_uninstall(j); + job_free(j); + } + + if (u->nop_job) { + Job *j = u->nop_job; + job_uninstall(j); + job_free(j); + } + + for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) + bidi_set_free(u, u->dependencies[d]); + + if (u->type != _UNIT_TYPE_INVALID) + LIST_REMOVE(units_by_type, u->manager->units_by_type[u->type], u); + + if (u->in_load_queue) + LIST_REMOVE(load_queue, u->manager->load_queue, u); + + if (u->in_dbus_queue) + LIST_REMOVE(dbus_queue, u->manager->dbus_unit_queue, u); + + if (u->in_cleanup_queue) + LIST_REMOVE(cleanup_queue, u->manager->cleanup_queue, u); + + if (u->in_gc_queue) { + LIST_REMOVE(gc_queue, u->manager->gc_queue, u); + u->manager->n_in_gc_queue--; + } + + if (u->in_cgroup_queue) + LIST_REMOVE(cgroup_queue, u->manager->cgroup_queue, u); + + unit_release_cgroup(u); + + (void) manager_update_failed_units(u->manager, u, false); + set_remove(u->manager->startup_units, u); + + free(u->description); + strv_free(u->documentation); + free(u->fragment_path); + free(u->source_path); + strv_free(u->dropin_paths); + free(u->instance); + + free(u->job_timeout_reboot_arg); + + set_free_free(u->names); + + unit_unwatch_all_pids(u); + + condition_free_list(u->conditions); + condition_free_list(u->asserts); + + free(u->reboot_arg); + + unit_ref_unset(&u->slice); + + while (u->refs) + unit_ref_unset(u->refs); + + free(u); +} + +UnitActiveState unit_active_state(Unit *u) { + assert(u); + + if (u->load_state == UNIT_MERGED) + return unit_active_state(unit_follow_merge(u)); + + /* After a reload it might happen that a unit is not correctly + * loaded but still has a process around. That's why we won't + * shortcut failed loading to UNIT_INACTIVE_FAILED. */ + + return UNIT_VTABLE(u)->active_state(u); +} + +const char* unit_sub_state_to_string(Unit *u) { + assert(u); + + return UNIT_VTABLE(u)->sub_state_to_string(u); +} + +static int complete_move(Set **s, Set **other) { + int r; + + assert(s); + assert(other); + + if (!*other) + return 0; + + if (*s) { + r = set_move(*s, *other); + if (r < 0) + return r; + } else { + *s = *other; + *other = NULL; + } + + return 0; +} + +static int merge_names(Unit *u, Unit *other) { + char *t; + Iterator i; + int r; + + assert(u); + assert(other); + + r = complete_move(&u->names, &other->names); + if (r < 0) + return r; + + set_free_free(other->names); + other->names = NULL; + other->id = NULL; + + SET_FOREACH(t, u->names, i) + assert_se(hashmap_replace(u->manager->units, t, u) == 0); + + return 0; +} + +static int reserve_dependencies(Unit *u, Unit *other, UnitDependency d) { + unsigned n_reserve; + + assert(u); + assert(other); + assert(d < _UNIT_DEPENDENCY_MAX); + + /* + * If u does not have this dependency set allocated, there is no need + * to reserve anything. In that case other's set will be transferred + * as a whole to u by complete_move(). + */ + if (!u->dependencies[d]) + return 0; + + /* merge_dependencies() will skip a u-on-u dependency */ + n_reserve = set_size(other->dependencies[d]) - !!set_get(other->dependencies[d], u); + + return set_reserve(u->dependencies[d], n_reserve); +} + +static void merge_dependencies(Unit *u, Unit *other, const char *other_id, UnitDependency d) { + Iterator i; + Unit *back; + int r; + + assert(u); + assert(other); + assert(d < _UNIT_DEPENDENCY_MAX); + + /* Fix backwards pointers */ + SET_FOREACH(back, other->dependencies[d], i) { + UnitDependency k; + + for (k = 0; k < _UNIT_DEPENDENCY_MAX; k++) { + /* Do not add dependencies between u and itself */ + if (back == u) { + if (set_remove(back->dependencies[k], other)) + maybe_warn_about_dependency(u, other_id, k); + } else { + r = set_remove_and_put(back->dependencies[k], other, u); + if (r == -EEXIST) + set_remove(back->dependencies[k], other); + else + assert(r >= 0 || r == -ENOENT); + } + } + } + + /* Also do not move dependencies on u to itself */ + back = set_remove(other->dependencies[d], u); + if (back) + maybe_warn_about_dependency(u, other_id, d); + + /* The move cannot fail. The caller must have performed a reservation. */ + assert_se(complete_move(&u->dependencies[d], &other->dependencies[d]) == 0); + + other->dependencies[d] = set_free(other->dependencies[d]); +} + +int unit_merge(Unit *u, Unit *other) { + UnitDependency d; + const char *other_id = NULL; + int r; + + assert(u); + assert(other); + assert(u->manager == other->manager); + assert(u->type != _UNIT_TYPE_INVALID); + + other = unit_follow_merge(other); + + if (other == u) + return 0; + + if (u->type != other->type) + return -EINVAL; + + if (!u->instance != !other->instance) + return -EINVAL; + + if (!unit_type_may_alias(u->type)) /* Merging only applies to unit names that support aliases */ + return -EEXIST; + + if (other->load_state != UNIT_STUB && + other->load_state != UNIT_NOT_FOUND) + return -EEXIST; + + if (other->job) + return -EEXIST; + + if (other->nop_job) + return -EEXIST; + + if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) + return -EEXIST; + + if (other->id) + other_id = strdupa(other->id); + + /* Make reservations to ensure merge_dependencies() won't fail */ + for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) { + r = reserve_dependencies(u, other, d); + /* + * We don't rollback reservations if we fail. We don't have + * a way to undo reservations. A reservation is not a leak. + */ + if (r < 0) + return r; + } + + /* Merge names */ + r = merge_names(u, other); + if (r < 0) + return r; + + /* Redirect all references */ + while (other->refs) + unit_ref_set(other->refs, u); + + /* Merge dependencies */ + for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) + merge_dependencies(u, other, other_id, d); + + other->load_state = UNIT_MERGED; + other->merged_into = u; + + /* If there is still some data attached to the other node, we + * don't need it anymore, and can free it. */ + if (other->load_state != UNIT_STUB) + if (UNIT_VTABLE(other)->done) + UNIT_VTABLE(other)->done(other); + + unit_add_to_dbus_queue(u); + unit_add_to_cleanup_queue(other); + + return 0; +} + +int unit_merge_by_name(Unit *u, const char *name) { + _cleanup_free_ char *s = NULL; + Unit *other; + int r; + + assert(u); + assert(name); + + if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { + if (!u->instance) + return -EINVAL; + + r = unit_name_replace_instance(name, u->instance, &s); + if (r < 0) + return r; + + name = s; + } + + other = manager_get_unit(u->manager, name); + if (other) + return unit_merge(u, other); + + return unit_add_name(u, name); +} + +Unit* unit_follow_merge(Unit *u) { + assert(u); + + while (u->load_state == UNIT_MERGED) + assert_se(u = u->merged_into); + + return u; +} + +int unit_add_exec_dependencies(Unit *u, ExecContext *c) { + int r; + + assert(u); + assert(c); + + if (c->working_directory) { + r = unit_require_mounts_for(u, c->working_directory); + if (r < 0) + return r; + } + + if (c->root_directory) { + r = unit_require_mounts_for(u, c->root_directory); + if (r < 0) + return r; + } + + if (!MANAGER_IS_SYSTEM(u->manager)) + return 0; + + if (c->private_tmp) { + r = unit_require_mounts_for(u, "/tmp"); + if (r < 0) + return r; + + r = unit_require_mounts_for(u, "/var/tmp"); + if (r < 0) + return r; + } + + if (c->std_output != EXEC_OUTPUT_KMSG && + c->std_output != EXEC_OUTPUT_SYSLOG && + c->std_output != EXEC_OUTPUT_JOURNAL && + c->std_output != EXEC_OUTPUT_KMSG_AND_CONSOLE && + c->std_output != EXEC_OUTPUT_SYSLOG_AND_CONSOLE && + c->std_output != EXEC_OUTPUT_JOURNAL_AND_CONSOLE && + c->std_error != EXEC_OUTPUT_KMSG && + c->std_error != EXEC_OUTPUT_SYSLOG && + c->std_error != EXEC_OUTPUT_JOURNAL && + c->std_error != EXEC_OUTPUT_KMSG_AND_CONSOLE && + c->std_error != EXEC_OUTPUT_JOURNAL_AND_CONSOLE && + c->std_error != EXEC_OUTPUT_SYSLOG_AND_CONSOLE) + return 0; + + /* If syslog or kernel logging is requested, make sure our own + * logging daemon is run first. */ + + r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, NULL, true); + if (r < 0) + return r; + + return 0; +} + +const char *unit_description(Unit *u) { + assert(u); + + if (u->description) + return u->description; + + return strna(u->id); +} + +void unit_dump(Unit *u, FILE *f, const char *prefix) { + char *t, **j; + UnitDependency d; + Iterator i; + const char *prefix2; + char + timestamp0[FORMAT_TIMESTAMP_MAX], + timestamp1[FORMAT_TIMESTAMP_MAX], + timestamp2[FORMAT_TIMESTAMP_MAX], + timestamp3[FORMAT_TIMESTAMP_MAX], + timestamp4[FORMAT_TIMESTAMP_MAX], + timespan[FORMAT_TIMESPAN_MAX]; + Unit *following; + _cleanup_set_free_ Set *following_set = NULL; + int r; + + assert(u); + assert(u->type >= 0); + + prefix = strempty(prefix); + prefix2 = strjoina(prefix, "\t"); + + fprintf(f, + "%s-> Unit %s:\n" + "%s\tDescription: %s\n" + "%s\tInstance: %s\n" + "%s\tUnit Load State: %s\n" + "%s\tUnit Active State: %s\n" + "%s\tState Change Timestamp: %s\n" + "%s\tInactive Exit Timestamp: %s\n" + "%s\tActive Enter Timestamp: %s\n" + "%s\tActive Exit Timestamp: %s\n" + "%s\tInactive Enter Timestamp: %s\n" + "%s\tGC Check Good: %s\n" + "%s\tNeed Daemon Reload: %s\n" + "%s\tTransient: %s\n" + "%s\tSlice: %s\n" + "%s\tCGroup: %s\n" + "%s\tCGroup realized: %s\n" + "%s\tCGroup mask: 0x%x\n" + "%s\tCGroup members mask: 0x%x\n", + prefix, u->id, + prefix, unit_description(u), + prefix, strna(u->instance), + prefix, unit_load_state_to_string(u->load_state), + prefix, unit_active_state_to_string(unit_active_state(u)), + prefix, strna(format_timestamp(timestamp0, sizeof(timestamp0), u->state_change_timestamp.realtime)), + prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->inactive_exit_timestamp.realtime)), + prefix, strna(format_timestamp(timestamp2, sizeof(timestamp2), u->active_enter_timestamp.realtime)), + prefix, strna(format_timestamp(timestamp3, sizeof(timestamp3), u->active_exit_timestamp.realtime)), + prefix, strna(format_timestamp(timestamp4, sizeof(timestamp4), u->inactive_enter_timestamp.realtime)), + prefix, yes_no(unit_check_gc(u)), + prefix, yes_no(unit_need_daemon_reload(u)), + prefix, yes_no(u->transient), + prefix, strna(unit_slice_name(u)), + prefix, strna(u->cgroup_path), + prefix, yes_no(u->cgroup_realized), + prefix, u->cgroup_realized_mask, + prefix, u->cgroup_members_mask); + + SET_FOREACH(t, u->names, i) + fprintf(f, "%s\tName: %s\n", prefix, t); + + STRV_FOREACH(j, u->documentation) + fprintf(f, "%s\tDocumentation: %s\n", prefix, *j); + + following = unit_following(u); + if (following) + fprintf(f, "%s\tFollowing: %s\n", prefix, following->id); + + r = unit_following_set(u, &following_set); + if (r >= 0) { + Unit *other; + + SET_FOREACH(other, following_set, i) + fprintf(f, "%s\tFollowing Set Member: %s\n", prefix, other->id); + } + + if (u->fragment_path) + fprintf(f, "%s\tFragment Path: %s\n", prefix, u->fragment_path); + + if (u->source_path) + fprintf(f, "%s\tSource Path: %s\n", prefix, u->source_path); + + STRV_FOREACH(j, u->dropin_paths) + fprintf(f, "%s\tDropIn Path: %s\n", prefix, *j); + + if (u->job_timeout != USEC_INFINITY) + fprintf(f, "%s\tJob Timeout: %s\n", prefix, format_timespan(timespan, sizeof(timespan), u->job_timeout, 0)); + + if (u->job_timeout_action != FAILURE_ACTION_NONE) + fprintf(f, "%s\tJob Timeout Action: %s\n", prefix, failure_action_to_string(u->job_timeout_action)); + + if (u->job_timeout_reboot_arg) + fprintf(f, "%s\tJob Timeout Reboot Argument: %s\n", prefix, u->job_timeout_reboot_arg); + + condition_dump_list(u->conditions, f, prefix, condition_type_to_string); + condition_dump_list(u->asserts, f, prefix, assert_type_to_string); + + if (dual_timestamp_is_set(&u->condition_timestamp)) + fprintf(f, + "%s\tCondition Timestamp: %s\n" + "%s\tCondition Result: %s\n", + prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->condition_timestamp.realtime)), + prefix, yes_no(u->condition_result)); + + if (dual_timestamp_is_set(&u->assert_timestamp)) + fprintf(f, + "%s\tAssert Timestamp: %s\n" + "%s\tAssert Result: %s\n", + prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->assert_timestamp.realtime)), + prefix, yes_no(u->assert_result)); + + for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) { + Unit *other; + + SET_FOREACH(other, u->dependencies[d], i) + fprintf(f, "%s\t%s: %s\n", prefix, unit_dependency_to_string(d), other->id); + } + + if (!strv_isempty(u->requires_mounts_for)) { + fprintf(f, + "%s\tRequiresMountsFor:", prefix); + + STRV_FOREACH(j, u->requires_mounts_for) + fprintf(f, " %s", *j); + + fputs("\n", f); + } + + if (u->load_state == UNIT_LOADED) { + + fprintf(f, + "%s\tStopWhenUnneeded: %s\n" + "%s\tRefuseManualStart: %s\n" + "%s\tRefuseManualStop: %s\n" + "%s\tDefaultDependencies: %s\n" + "%s\tOnFailureJobMode: %s\n" + "%s\tIgnoreOnIsolate: %s\n", + prefix, yes_no(u->stop_when_unneeded), + prefix, yes_no(u->refuse_manual_start), + prefix, yes_no(u->refuse_manual_stop), + prefix, yes_no(u->default_dependencies), + prefix, job_mode_to_string(u->on_failure_job_mode), + prefix, yes_no(u->ignore_on_isolate)); + + if (UNIT_VTABLE(u)->dump) + UNIT_VTABLE(u)->dump(u, f, prefix2); + + } else if (u->load_state == UNIT_MERGED) + fprintf(f, + "%s\tMerged into: %s\n", + prefix, u->merged_into->id); + else if (u->load_state == UNIT_ERROR) + fprintf(f, "%s\tLoad Error Code: %s\n", prefix, strerror(-u->load_error)); + + + if (u->job) + job_dump(u->job, f, prefix2); + + if (u->nop_job) + job_dump(u->nop_job, f, prefix2); + +} + +/* Common implementation for multiple backends */ +int unit_load_fragment_and_dropin(Unit *u) { + int r; + + assert(u); + + /* Load a .{service,socket,...} file */ + r = unit_load_fragment(u); + if (r < 0) + return r; + + if (u->load_state == UNIT_STUB) + return -ENOENT; + + /* Load drop-in directory data */ + r = unit_load_dropin(unit_follow_merge(u)); + if (r < 0) + return r; + + return 0; +} + +/* Common implementation for multiple backends */ +int unit_load_fragment_and_dropin_optional(Unit *u) { + int r; + + assert(u); + + /* Same as unit_load_fragment_and_dropin(), but whether + * something can be loaded or not doesn't matter. */ + + /* Load a .service file */ + r = unit_load_fragment(u); + if (r < 0) + return r; + + if (u->load_state == UNIT_STUB) + u->load_state = UNIT_LOADED; + + /* Load drop-in directory data */ + r = unit_load_dropin(unit_follow_merge(u)); + if (r < 0) + return r; + + return 0; +} + +int unit_add_default_target_dependency(Unit *u, Unit *target) { + assert(u); + assert(target); + + if (target->type != UNIT_TARGET) + return 0; + + /* Only add the dependency if both units are loaded, so that + * that loop check below is reliable */ + if (u->load_state != UNIT_LOADED || + target->load_state != UNIT_LOADED) + return 0; + + /* If either side wants no automatic dependencies, then let's + * skip this */ + if (!u->default_dependencies || + !target->default_dependencies) + return 0; + + /* Don't create loops */ + if (set_get(target->dependencies[UNIT_BEFORE], u)) + return 0; + + return unit_add_dependency(target, UNIT_AFTER, u, true); +} + +static int unit_add_target_dependencies(Unit *u) { + + static const UnitDependency deps[] = { + UNIT_REQUIRED_BY, + UNIT_REQUISITE_OF, + UNIT_WANTED_BY, + UNIT_BOUND_BY + }; + + Unit *target; + Iterator i; + unsigned k; + int r = 0; + + assert(u); + + for (k = 0; k < ELEMENTSOF(deps); k++) + SET_FOREACH(target, u->dependencies[deps[k]], i) { + r = unit_add_default_target_dependency(u, target); + if (r < 0) + return r; + } + + return r; +} + +static int unit_add_slice_dependencies(Unit *u) { + assert(u); + + if (!UNIT_HAS_CGROUP_CONTEXT(u)) + return 0; + + if (UNIT_ISSET(u->slice)) + return unit_add_two_dependencies(u, UNIT_AFTER, UNIT_REQUIRES, UNIT_DEREF(u->slice), true); + + if (unit_has_name(u, SPECIAL_ROOT_SLICE)) + return 0; + + return unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_REQUIRES, SPECIAL_ROOT_SLICE, NULL, true); +} + +static int unit_add_mount_dependencies(Unit *u) { + char **i; + int r; + + assert(u); + + STRV_FOREACH(i, u->requires_mounts_for) { + char prefix[strlen(*i) + 1]; + + PATH_FOREACH_PREFIX_MORE(prefix, *i) { + _cleanup_free_ char *p = NULL; + Unit *m; + + r = unit_name_from_path(prefix, ".mount", &p); + if (r < 0) + return r; + + m = manager_get_unit(u->manager, p); + if (!m) { + /* Make sure to load the mount unit if + * it exists. If so the dependencies + * on this unit will be added later + * during the loading of the mount + * unit. */ + (void) manager_load_unit_prepare(u->manager, p, NULL, NULL, &m); + continue; + } + if (m == u) + continue; + + if (m->load_state != UNIT_LOADED) + continue; + + r = unit_add_dependency(u, UNIT_AFTER, m, true); + if (r < 0) + return r; + + if (m->fragment_path) { + r = unit_add_dependency(u, UNIT_REQUIRES, m, true); + if (r < 0) + return r; + } + } + } + + return 0; +} + +static int unit_add_startup_units(Unit *u) { + CGroupContext *c; + int r; + + c = unit_get_cgroup_context(u); + if (!c) + return 0; + + if (c->startup_cpu_shares == CGROUP_CPU_SHARES_INVALID && + c->startup_io_weight == CGROUP_WEIGHT_INVALID && + c->startup_blockio_weight == CGROUP_BLKIO_WEIGHT_INVALID) + return 0; + + r = set_ensure_allocated(&u->manager->startup_units, NULL); + if (r < 0) + return r; + + return set_put(u->manager->startup_units, u); +} + +int unit_load(Unit *u) { + int r; + + assert(u); + + if (u->in_load_queue) { + LIST_REMOVE(load_queue, u->manager->load_queue, u); + u->in_load_queue = false; + } + + if (u->type == _UNIT_TYPE_INVALID) + return -EINVAL; + + if (u->load_state != UNIT_STUB) + return 0; + + if (u->transient_file) { + r = fflush_and_check(u->transient_file); + if (r < 0) + goto fail; + + fclose(u->transient_file); + u->transient_file = NULL; + + u->fragment_mtime = now(CLOCK_REALTIME); + } + + if (UNIT_VTABLE(u)->load) { + r = UNIT_VTABLE(u)->load(u); + if (r < 0) + goto fail; + } + + if (u->load_state == UNIT_STUB) { + r = -ENOENT; + goto fail; + } + + if (u->load_state == UNIT_LOADED) { + + r = unit_add_target_dependencies(u); + if (r < 0) + goto fail; + + r = unit_add_slice_dependencies(u); + if (r < 0) + goto fail; + + r = unit_add_mount_dependencies(u); + if (r < 0) + goto fail; + + r = unit_add_startup_units(u); + if (r < 0) + goto fail; + + if (u->on_failure_job_mode == JOB_ISOLATE && set_size(u->dependencies[UNIT_ON_FAILURE]) > 1) { + log_unit_error(u, "More than one OnFailure= dependencies specified but OnFailureJobMode=isolate set. Refusing."); + r = -EINVAL; + goto fail; + } + + unit_update_cgroup_members_masks(u); + } + + assert((u->load_state != UNIT_MERGED) == !u->merged_into); + + unit_add_to_dbus_queue(unit_follow_merge(u)); + unit_add_to_gc_queue(u); + + return 0; + +fail: + u->load_state = u->load_state == UNIT_STUB ? UNIT_NOT_FOUND : UNIT_ERROR; + u->load_error = r; + unit_add_to_dbus_queue(u); + unit_add_to_gc_queue(u); + + log_unit_debug_errno(u, r, "Failed to load configuration: %m"); + + return r; +} + +static bool unit_condition_test_list(Unit *u, Condition *first, const char *(*to_string)(ConditionType t)) { + Condition *c; + int triggered = -1; + + assert(u); + assert(to_string); + + /* If the condition list is empty, then it is true */ + if (!first) + return true; + + /* Otherwise, if all of the non-trigger conditions apply and + * if any of the trigger conditions apply (unless there are + * none) we return true */ + LIST_FOREACH(conditions, c, first) { + int r; + + r = condition_test(c); + if (r < 0) + log_unit_warning(u, + "Couldn't determine result for %s=%s%s%s, assuming failed: %m", + to_string(c->type), + c->trigger ? "|" : "", + c->negate ? "!" : "", + c->parameter); + else + log_unit_debug(u, + "%s=%s%s%s %s.", + to_string(c->type), + c->trigger ? "|" : "", + c->negate ? "!" : "", + c->parameter, + condition_result_to_string(c->result)); + + if (!c->trigger && r <= 0) + return false; + + if (c->trigger && triggered <= 0) + triggered = r > 0; + } + + return triggered != 0; +} + +static bool unit_condition_test(Unit *u) { + assert(u); + + dual_timestamp_get(&u->condition_timestamp); + u->condition_result = unit_condition_test_list(u, u->conditions, condition_type_to_string); + + return u->condition_result; +} + +static bool unit_assert_test(Unit *u) { + assert(u); + + dual_timestamp_get(&u->assert_timestamp); + u->assert_result = unit_condition_test_list(u, u->asserts, assert_type_to_string); + + return u->assert_result; +} + +void unit_status_printf(Unit *u, const char *status, const char *unit_status_msg_format) { + DISABLE_WARNING_FORMAT_NONLITERAL; + manager_status_printf(u->manager, STATUS_TYPE_NORMAL, status, unit_status_msg_format, unit_description(u)); + REENABLE_WARNING; +} + +_pure_ static const char* unit_get_status_message_format(Unit *u, JobType t) { + const char *format; + const UnitStatusMessageFormats *format_table; + + assert(u); + assert(IN_SET(t, JOB_START, JOB_STOP, JOB_RELOAD)); + + if (t != JOB_RELOAD) { + format_table = &UNIT_VTABLE(u)->status_message_formats; + if (format_table) { + format = format_table->starting_stopping[t == JOB_STOP]; + if (format) + return format; + } + } + + /* Return generic strings */ + if (t == JOB_START) + return "Starting %s."; + else if (t == JOB_STOP) + return "Stopping %s."; + else + return "Reloading %s."; +} + +static void unit_status_print_starting_stopping(Unit *u, JobType t) { + const char *format; + + assert(u); + + /* Reload status messages have traditionally not been printed to console. */ + if (!IN_SET(t, JOB_START, JOB_STOP)) + return; + + format = unit_get_status_message_format(u, t); + + DISABLE_WARNING_FORMAT_NONLITERAL; + unit_status_printf(u, "", format); + REENABLE_WARNING; +} + +static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) { + const char *format; + char buf[LINE_MAX]; + sd_id128_t mid; + + assert(u); + + if (!IN_SET(t, JOB_START, JOB_STOP, JOB_RELOAD)) + return; + + if (log_on_console()) + return; + + /* We log status messages for all units and all operations. */ + + format = unit_get_status_message_format(u, t); + + DISABLE_WARNING_FORMAT_NONLITERAL; + xsprintf(buf, format, unit_description(u)); + REENABLE_WARNING; + + mid = t == JOB_START ? SD_MESSAGE_UNIT_STARTING : + t == JOB_STOP ? SD_MESSAGE_UNIT_STOPPING : + SD_MESSAGE_UNIT_RELOADING; + + /* Note that we deliberately use LOG_MESSAGE() instead of + * LOG_UNIT_MESSAGE() here, since this is supposed to mimic + * closely what is written to screen using the status output, + * which is supposed the highest level, friendliest output + * possible, which means we should avoid the low-level unit + * name. */ + log_struct(LOG_INFO, + LOG_MESSAGE_ID(mid), + LOG_UNIT_ID(u), + LOG_MESSAGE("%s", buf), + NULL); +} + +void unit_status_emit_starting_stopping_reloading(Unit *u, JobType t) { + assert(u); + assert(t >= 0); + assert(t < _JOB_TYPE_MAX); + + unit_status_log_starting_stopping_reloading(u, t); + unit_status_print_starting_stopping(u, t); +} + +int unit_start_limit_test(Unit *u) { + assert(u); + + if (ratelimit_test(&u->start_limit)) { + u->start_limit_hit = false; + return 0; + } + + log_unit_warning(u, "Start request repeated too quickly."); + u->start_limit_hit = true; + + return failure_action(u->manager, u->start_limit_action, u->reboot_arg); +} + +/* Errors: + * -EBADR: This unit type does not support starting. + * -EALREADY: Unit is already started. + * -EAGAIN: An operation is already in progress. Retry later. + * -ECANCELED: Too many requests for now. + * -EPROTO: Assert failed + * -EINVAL: Unit not loaded + * -EOPNOTSUPP: Unit type not supported + */ +int unit_start(Unit *u) { + UnitActiveState state; + Unit *following; + + assert(u); + + /* If this is already started, then this will succeed. Note + * that this will even succeed if this unit is not startable + * by the user. This is relied on to detect when we need to + * wait for units and when waiting is finished. */ + state = unit_active_state(u); + if (UNIT_IS_ACTIVE_OR_RELOADING(state)) + return -EALREADY; + + /* Units that aren't loaded cannot be started */ + if (u->load_state != UNIT_LOADED) + return -EINVAL; + + /* If the conditions failed, don't do anything at all. If we + * already are activating this call might still be useful to + * speed up activation in case there is some hold-off time, + * but we don't want to recheck the condition in that case. */ + if (state != UNIT_ACTIVATING && + !unit_condition_test(u)) { + log_unit_debug(u, "Starting requested but condition failed. Not starting unit."); + return -EALREADY; + } + + /* If the asserts failed, fail the entire job */ + if (state != UNIT_ACTIVATING && + !unit_assert_test(u)) { + log_unit_notice(u, "Starting requested but asserts failed."); + return -EPROTO; + } + + /* Units of types that aren't supported cannot be + * started. Note that we do this test only after the condition + * checks, so that we rather return condition check errors + * (which are usually not considered a true failure) than "not + * supported" errors (which are considered a failure). + */ + if (!unit_supported(u)) + return -EOPNOTSUPP; + + /* Forward to the main object, if we aren't it. */ + following = unit_following(u); + if (following) { + log_unit_debug(u, "Redirecting start request from %s to %s.", u->id, following->id); + return unit_start(following); + } + + /* If it is stopped, but we cannot start it, then fail */ + if (!UNIT_VTABLE(u)->start) + return -EBADR; + + /* We don't suppress calls to ->start() here when we are + * already starting, to allow this request to be used as a + * "hurry up" call, for example when the unit is in some "auto + * restart" state where it waits for a holdoff timer to elapse + * before it will start again. */ + + unit_add_to_dbus_queue(u); + + return UNIT_VTABLE(u)->start(u); +} + +bool unit_can_start(Unit *u) { + assert(u); + + if (u->load_state != UNIT_LOADED) + return false; + + if (!unit_supported(u)) + return false; + + return !!UNIT_VTABLE(u)->start; +} + +bool unit_can_isolate(Unit *u) { + assert(u); + + return unit_can_start(u) && + u->allow_isolate; +} + +/* Errors: + * -EBADR: This unit type does not support stopping. + * -EALREADY: Unit is already stopped. + * -EAGAIN: An operation is already in progress. Retry later. + */ +int unit_stop(Unit *u) { + UnitActiveState state; + Unit *following; + + assert(u); + + state = unit_active_state(u); + if (UNIT_IS_INACTIVE_OR_FAILED(state)) + return -EALREADY; + + following = unit_following(u); + if (following) { + log_unit_debug(u, "Redirecting stop request from %s to %s.", u->id, following->id); + return unit_stop(following); + } + + if (!UNIT_VTABLE(u)->stop) + return -EBADR; + + unit_add_to_dbus_queue(u); + + return UNIT_VTABLE(u)->stop(u); +} + +/* Errors: + * -EBADR: This unit type does not support reloading. + * -ENOEXEC: Unit is not started. + * -EAGAIN: An operation is already in progress. Retry later. + */ +int unit_reload(Unit *u) { + UnitActiveState state; + Unit *following; + + assert(u); + + if (u->load_state != UNIT_LOADED) + return -EINVAL; + + if (!unit_can_reload(u)) + return -EBADR; + + state = unit_active_state(u); + if (state == UNIT_RELOADING) + return -EALREADY; + + if (state != UNIT_ACTIVE) { + log_unit_warning(u, "Unit cannot be reloaded because it is inactive."); + return -ENOEXEC; + } + + following = unit_following(u); + if (following) { + log_unit_debug(u, "Redirecting reload request from %s to %s.", u->id, following->id); + return unit_reload(following); + } + + unit_add_to_dbus_queue(u); + + return UNIT_VTABLE(u)->reload(u); +} + +bool unit_can_reload(Unit *u) { + assert(u); + + if (!UNIT_VTABLE(u)->reload) + return false; + + if (!UNIT_VTABLE(u)->can_reload) + return true; + + return UNIT_VTABLE(u)->can_reload(u); +} + +static void unit_check_unneeded(Unit *u) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + static const UnitDependency needed_dependencies[] = { + UNIT_REQUIRED_BY, + UNIT_REQUISITE_OF, + UNIT_WANTED_BY, + UNIT_BOUND_BY, + }; + + Unit *other; + Iterator i; + unsigned j; + int r; + + assert(u); + + /* If this service shall be shut down when unneeded then do + * so. */ + + if (!u->stop_when_unneeded) + return; + + if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) + return; + + for (j = 0; j < ELEMENTSOF(needed_dependencies); j++) + SET_FOREACH(other, u->dependencies[needed_dependencies[j]], i) + if (unit_active_or_pending(other)) + return; + + /* If stopping a unit fails continously we might enter a stop + * loop here, hence stop acting on the service being + * unnecessary after a while. */ + if (!ratelimit_test(&u->auto_stop_ratelimit)) { + log_unit_warning(u, "Unit not needed anymore, but not stopping since we tried this too often recently."); + return; + } + + log_unit_info(u, "Unit not needed anymore. Stopping."); + + /* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */ + r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, &error, NULL); + if (r < 0) + log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r)); +} + +static void unit_check_binds_to(Unit *u) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + bool stop = false; + Unit *other; + Iterator i; + int r; + + assert(u); + + if (u->job) + return; + + if (unit_active_state(u) != UNIT_ACTIVE) + return; + + SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i) { + if (other->job) + continue; + + if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) + continue; + + stop = true; + break; + } + + if (!stop) + return; + + /* If stopping a unit fails continously we might enter a stop + * loop here, hence stop acting on the service being + * unnecessary after a while. */ + if (!ratelimit_test(&u->auto_stop_ratelimit)) { + log_unit_warning(u, "Unit is bound to inactive unit %s, but not stopping since we tried this too often recently.", other->id); + return; + } + + assert(other); + log_unit_info(u, "Unit is bound to inactive unit %s. Stopping, too.", other->id); + + /* A unit we need to run is gone. Sniff. Let's stop this. */ + r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, &error, NULL); + if (r < 0) + log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r)); +} + +static void retroactively_start_dependencies(Unit *u) { + Iterator i; + Unit *other; + + assert(u); + assert(UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))); + + SET_FOREACH(other, u->dependencies[UNIT_REQUIRES], i) + if (!set_get(u->dependencies[UNIT_AFTER], other) && + !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, NULL, NULL); + + SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i) + if (!set_get(u->dependencies[UNIT_AFTER], other) && + !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, NULL, NULL); + + SET_FOREACH(other, u->dependencies[UNIT_WANTS], i) + if (!set_get(u->dependencies[UNIT_AFTER], other) && + !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_START, other, JOB_FAIL, NULL, NULL); + + SET_FOREACH(other, u->dependencies[UNIT_CONFLICTS], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL); + + SET_FOREACH(other, u->dependencies[UNIT_CONFLICTED_BY], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL); +} + +static void retroactively_stop_dependencies(Unit *u) { + Iterator i; + Unit *other; + + assert(u); + assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))); + + /* Pull down units which are bound to us recursively if enabled */ + SET_FOREACH(other, u->dependencies[UNIT_BOUND_BY], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL); +} + +static void check_unneeded_dependencies(Unit *u) { + Iterator i; + Unit *other; + + assert(u); + assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))); + + /* Garbage collect services that might not be needed anymore, if enabled */ + SET_FOREACH(other, u->dependencies[UNIT_REQUIRES], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + unit_check_unneeded(other); + SET_FOREACH(other, u->dependencies[UNIT_WANTS], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + unit_check_unneeded(other); + SET_FOREACH(other, u->dependencies[UNIT_REQUISITE], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + unit_check_unneeded(other); + SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + unit_check_unneeded(other); +} + +void unit_start_on_failure(Unit *u) { + Unit *other; + Iterator i; + + assert(u); + + if (set_size(u->dependencies[UNIT_ON_FAILURE]) <= 0) + return; + + log_unit_info(u, "Triggering OnFailure= dependencies."); + + SET_FOREACH(other, u->dependencies[UNIT_ON_FAILURE], i) { + int r; + + r = manager_add_job(u->manager, JOB_START, other, u->on_failure_job_mode, NULL, NULL); + if (r < 0) + log_unit_error_errno(u, r, "Failed to enqueue OnFailure= job: %m"); + } +} + +void unit_trigger_notify(Unit *u) { + Unit *other; + Iterator i; + + assert(u); + + SET_FOREACH(other, u->dependencies[UNIT_TRIGGERED_BY], i) + if (UNIT_VTABLE(other)->trigger_notify) + UNIT_VTABLE(other)->trigger_notify(other, u); +} + +void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success) { + Manager *m; + bool unexpected; + + assert(u); + assert(os < _UNIT_ACTIVE_STATE_MAX); + assert(ns < _UNIT_ACTIVE_STATE_MAX); + + /* Note that this is called for all low-level state changes, + * even if they might map to the same high-level + * UnitActiveState! That means that ns == os is an expected + * behavior here. For example: if a mount point is remounted + * this function will be called too! */ + + m = u->manager; + + /* Update timestamps for state changes */ + if (!MANAGER_IS_RELOADING(m)) { + dual_timestamp_get(&u->state_change_timestamp); + + if (UNIT_IS_INACTIVE_OR_FAILED(os) && !UNIT_IS_INACTIVE_OR_FAILED(ns)) + u->inactive_exit_timestamp = u->state_change_timestamp; + else if (!UNIT_IS_INACTIVE_OR_FAILED(os) && UNIT_IS_INACTIVE_OR_FAILED(ns)) + u->inactive_enter_timestamp = u->state_change_timestamp; + + if (!UNIT_IS_ACTIVE_OR_RELOADING(os) && UNIT_IS_ACTIVE_OR_RELOADING(ns)) + u->active_enter_timestamp = u->state_change_timestamp; + else if (UNIT_IS_ACTIVE_OR_RELOADING(os) && !UNIT_IS_ACTIVE_OR_RELOADING(ns)) + u->active_exit_timestamp = u->state_change_timestamp; + } + + /* Keep track of failed units */ + (void) manager_update_failed_units(u->manager, u, ns == UNIT_FAILED); + + /* Make sure the cgroup is always removed when we become inactive */ + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) + unit_prune_cgroup(u); + + /* Note that this doesn't apply to RemainAfterExit services exiting + * successfully, since there's no change of state in that case. Which is + * why it is handled in service_set_state() */ + if (UNIT_IS_INACTIVE_OR_FAILED(os) != UNIT_IS_INACTIVE_OR_FAILED(ns)) { + ExecContext *ec; + + ec = unit_get_exec_context(u); + if (ec && exec_context_may_touch_console(ec)) { + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) { + m->n_on_console--; + + if (m->n_on_console == 0) + /* unset no_console_output flag, since the console is free */ + m->no_console_output = false; + } else + m->n_on_console++; + } + } + + if (u->job) { + unexpected = false; + + if (u->job->state == JOB_WAITING) + + /* So we reached a different state for this + * job. Let's see if we can run it now if it + * failed previously due to EAGAIN. */ + job_add_to_run_queue(u->job); + + /* Let's check whether this state change constitutes a + * finished job, or maybe contradicts a running job and + * hence needs to invalidate jobs. */ + + switch (u->job->type) { + + case JOB_START: + case JOB_VERIFY_ACTIVE: + + if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) + job_finish_and_invalidate(u->job, JOB_DONE, true, false); + else if (u->job->state == JOB_RUNNING && ns != UNIT_ACTIVATING) { + unexpected = true; + + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) + job_finish_and_invalidate(u->job, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true, false); + } + + break; + + case JOB_RELOAD: + case JOB_RELOAD_OR_START: + case JOB_TRY_RELOAD: + + if (u->job->state == JOB_RUNNING) { + if (ns == UNIT_ACTIVE) + job_finish_and_invalidate(u->job, reload_success ? JOB_DONE : JOB_FAILED, true, false); + else if (ns != UNIT_ACTIVATING && ns != UNIT_RELOADING) { + unexpected = true; + + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) + job_finish_and_invalidate(u->job, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true, false); + } + } + + break; + + case JOB_STOP: + case JOB_RESTART: + case JOB_TRY_RESTART: + + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) + job_finish_and_invalidate(u->job, JOB_DONE, true, false); + else if (u->job->state == JOB_RUNNING && ns != UNIT_DEACTIVATING) { + unexpected = true; + job_finish_and_invalidate(u->job, JOB_FAILED, true, false); + } + + break; + + default: + assert_not_reached("Job type unknown"); + } + + } else + unexpected = true; + + if (!MANAGER_IS_RELOADING(m)) { + + /* If this state change happened without being + * requested by a job, then let's retroactively start + * or stop dependencies. We skip that step when + * deserializing, since we don't want to create any + * additional jobs just because something is already + * activated. */ + + if (unexpected) { + if (UNIT_IS_INACTIVE_OR_FAILED(os) && UNIT_IS_ACTIVE_OR_ACTIVATING(ns)) + retroactively_start_dependencies(u); + else if (UNIT_IS_ACTIVE_OR_ACTIVATING(os) && UNIT_IS_INACTIVE_OR_DEACTIVATING(ns)) + retroactively_stop_dependencies(u); + } + + /* stop unneeded units regardless if going down was expected or not */ + if (UNIT_IS_INACTIVE_OR_DEACTIVATING(ns)) + check_unneeded_dependencies(u); + + if (ns != os && ns == UNIT_FAILED) { + log_unit_notice(u, "Unit entered failed state."); + unit_start_on_failure(u); + } + } + + /* Some names are special */ + if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) { + + if (unit_has_name(u, SPECIAL_DBUS_SERVICE)) + /* The bus might have just become available, + * hence try to connect to it, if we aren't + * yet connected. */ + bus_init(m, true); + + if (u->type == UNIT_SERVICE && + !UNIT_IS_ACTIVE_OR_RELOADING(os) && + !MANAGER_IS_RELOADING(m)) { + /* Write audit record if we have just finished starting up */ + manager_send_unit_audit(m, u, AUDIT_SERVICE_START, true); + u->in_audit = true; + } + + if (!UNIT_IS_ACTIVE_OR_RELOADING(os)) + manager_send_unit_plymouth(m, u); + + } else { + + /* We don't care about D-Bus here, since we'll get an + * asynchronous notification for it anyway. */ + + if (u->type == UNIT_SERVICE && + UNIT_IS_INACTIVE_OR_FAILED(ns) && + !UNIT_IS_INACTIVE_OR_FAILED(os) && + !MANAGER_IS_RELOADING(m)) { + + /* Hmm, if there was no start record written + * write it now, so that we always have a nice + * pair */ + if (!u->in_audit) { + manager_send_unit_audit(m, u, AUDIT_SERVICE_START, ns == UNIT_INACTIVE); + + if (ns == UNIT_INACTIVE) + manager_send_unit_audit(m, u, AUDIT_SERVICE_STOP, true); + } else + /* Write audit record if we have just finished shutting down */ + manager_send_unit_audit(m, u, AUDIT_SERVICE_STOP, ns == UNIT_INACTIVE); + + u->in_audit = false; + } + } + + manager_recheck_journal(m); + unit_trigger_notify(u); + + if (!MANAGER_IS_RELOADING(u->manager)) { + /* Maybe we finished startup and are now ready for + * being stopped because unneeded? */ + unit_check_unneeded(u); + + /* Maybe we finished startup, but something we needed + * has vanished? Let's die then. (This happens when + * something BindsTo= to a Type=oneshot unit, as these + * units go directly from starting to inactive, + * without ever entering started.) */ + unit_check_binds_to(u); + } + + unit_add_to_dbus_queue(u); + unit_add_to_gc_queue(u); +} + +int unit_watch_pid(Unit *u, pid_t pid) { + int q, r; + + assert(u); + assert(pid >= 1); + + /* Watch a specific PID. We only support one or two units + * watching each PID for now, not more. */ + + r = set_ensure_allocated(&u->pids, NULL); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&u->manager->watch_pids1, NULL); + if (r < 0) + return r; + + r = hashmap_put(u->manager->watch_pids1, PID_TO_PTR(pid), u); + if (r == -EEXIST) { + r = hashmap_ensure_allocated(&u->manager->watch_pids2, NULL); + if (r < 0) + return r; + + r = hashmap_put(u->manager->watch_pids2, PID_TO_PTR(pid), u); + } + + q = set_put(u->pids, PID_TO_PTR(pid)); + if (q < 0) + return q; + + return r; +} + +void unit_unwatch_pid(Unit *u, pid_t pid) { + assert(u); + assert(pid >= 1); + + (void) hashmap_remove_value(u->manager->watch_pids1, PID_TO_PTR(pid), u); + (void) hashmap_remove_value(u->manager->watch_pids2, PID_TO_PTR(pid), u); + (void) set_remove(u->pids, PID_TO_PTR(pid)); +} + +void unit_unwatch_all_pids(Unit *u) { + assert(u); + + while (!set_isempty(u->pids)) + unit_unwatch_pid(u, PTR_TO_PID(set_first(u->pids))); + + u->pids = set_free(u->pids); +} + +void unit_tidy_watch_pids(Unit *u, pid_t except1, pid_t except2) { + Iterator i; + void *e; + + assert(u); + + /* Cleans dead PIDs from our list */ + + SET_FOREACH(e, u->pids, i) { + pid_t pid = PTR_TO_PID(e); + + if (pid == except1 || pid == except2) + continue; + + if (!pid_is_unwaited(pid)) + unit_unwatch_pid(u, pid); + } +} + +bool unit_job_is_applicable(Unit *u, JobType j) { + assert(u); + assert(j >= 0 && j < _JOB_TYPE_MAX); + + switch (j) { + + case JOB_VERIFY_ACTIVE: + case JOB_START: + case JOB_STOP: + case JOB_NOP: + return true; + + case JOB_RESTART: + case JOB_TRY_RESTART: + return unit_can_start(u); + + case JOB_RELOAD: + case JOB_TRY_RELOAD: + return unit_can_reload(u); + + case JOB_RELOAD_OR_START: + return unit_can_reload(u) && unit_can_start(u); + + default: + assert_not_reached("Invalid job type"); + } +} + +static void maybe_warn_about_dependency(Unit *u, const char *other, UnitDependency dependency) { + assert(u); + + /* Only warn about some unit types */ + if (!IN_SET(dependency, UNIT_CONFLICTS, UNIT_CONFLICTED_BY, UNIT_BEFORE, UNIT_AFTER, UNIT_ON_FAILURE, UNIT_TRIGGERS, UNIT_TRIGGERED_BY)) + return; + + if (streq_ptr(u->id, other)) + log_unit_warning(u, "Dependency %s=%s dropped", unit_dependency_to_string(dependency), u->id); + else + log_unit_warning(u, "Dependency %s=%s dropped, merged into %s", unit_dependency_to_string(dependency), strna(other), u->id); +} + +int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_reference) { + + static const UnitDependency inverse_table[_UNIT_DEPENDENCY_MAX] = { + [UNIT_REQUIRES] = UNIT_REQUIRED_BY, + [UNIT_WANTS] = UNIT_WANTED_BY, + [UNIT_REQUISITE] = UNIT_REQUISITE_OF, + [UNIT_BINDS_TO] = UNIT_BOUND_BY, + [UNIT_PART_OF] = UNIT_CONSISTS_OF, + [UNIT_REQUIRED_BY] = UNIT_REQUIRES, + [UNIT_REQUISITE_OF] = UNIT_REQUISITE, + [UNIT_WANTED_BY] = UNIT_WANTS, + [UNIT_BOUND_BY] = UNIT_BINDS_TO, + [UNIT_CONSISTS_OF] = UNIT_PART_OF, + [UNIT_CONFLICTS] = UNIT_CONFLICTED_BY, + [UNIT_CONFLICTED_BY] = UNIT_CONFLICTS, + [UNIT_BEFORE] = UNIT_AFTER, + [UNIT_AFTER] = UNIT_BEFORE, + [UNIT_ON_FAILURE] = _UNIT_DEPENDENCY_INVALID, + [UNIT_REFERENCES] = UNIT_REFERENCED_BY, + [UNIT_REFERENCED_BY] = UNIT_REFERENCES, + [UNIT_TRIGGERS] = UNIT_TRIGGERED_BY, + [UNIT_TRIGGERED_BY] = UNIT_TRIGGERS, + [UNIT_PROPAGATES_RELOAD_TO] = UNIT_RELOAD_PROPAGATED_FROM, + [UNIT_RELOAD_PROPAGATED_FROM] = UNIT_PROPAGATES_RELOAD_TO, + [UNIT_JOINS_NAMESPACE_OF] = UNIT_JOINS_NAMESPACE_OF, + }; + int r, q = 0, v = 0, w = 0; + Unit *orig_u = u, *orig_other = other; + + assert(u); + assert(d >= 0 && d < _UNIT_DEPENDENCY_MAX); + assert(other); + + u = unit_follow_merge(u); + other = unit_follow_merge(other); + + /* We won't allow dependencies on ourselves. We will not + * consider them an error however. */ + if (u == other) { + maybe_warn_about_dependency(orig_u, orig_other->id, d); + return 0; + } + + r = set_ensure_allocated(&u->dependencies[d], NULL); + if (r < 0) + return r; + + if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID) { + r = set_ensure_allocated(&other->dependencies[inverse_table[d]], NULL); + if (r < 0) + return r; + } + + if (add_reference) { + r = set_ensure_allocated(&u->dependencies[UNIT_REFERENCES], NULL); + if (r < 0) + return r; + + r = set_ensure_allocated(&other->dependencies[UNIT_REFERENCED_BY], NULL); + if (r < 0) + return r; + } + + q = set_put(u->dependencies[d], other); + if (q < 0) + return q; + + if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID && inverse_table[d] != d) { + v = set_put(other->dependencies[inverse_table[d]], u); + if (v < 0) { + r = v; + goto fail; + } + } + + if (add_reference) { + w = set_put(u->dependencies[UNIT_REFERENCES], other); + if (w < 0) { + r = w; + goto fail; + } + + r = set_put(other->dependencies[UNIT_REFERENCED_BY], u); + if (r < 0) + goto fail; + } + + unit_add_to_dbus_queue(u); + return 0; + +fail: + if (q > 0) + set_remove(u->dependencies[d], other); + + if (v > 0) + set_remove(other->dependencies[inverse_table[d]], u); + + if (w > 0) + set_remove(u->dependencies[UNIT_REFERENCES], other); + + return r; +} + +int unit_add_two_dependencies(Unit *u, UnitDependency d, UnitDependency e, Unit *other, bool add_reference) { + int r; + + assert(u); + + r = unit_add_dependency(u, d, other, add_reference); + if (r < 0) + return r; + + return unit_add_dependency(u, e, other, add_reference); +} + +static int resolve_template(Unit *u, const char *name, const char*path, char **buf, const char **ret) { + int r; + + assert(u); + assert(name || path); + assert(buf); + assert(ret); + + if (!name) + name = basename(path); + + if (!unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { + *buf = NULL; + *ret = name; + return 0; + } + + if (u->instance) + r = unit_name_replace_instance(name, u->instance, buf); + else { + _cleanup_free_ char *i = NULL; + + r = unit_name_to_prefix(u->id, &i); + if (r < 0) + return r; + + r = unit_name_replace_instance(name, i, buf); + } + if (r < 0) + return r; + + *ret = *buf; + return 0; +} + +int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, const char *path, bool add_reference) { + _cleanup_free_ char *buf = NULL; + Unit *other; + int r; + + assert(u); + assert(name || path); + + r = resolve_template(u, name, path, &buf, &name); + if (r < 0) + return r; + + r = manager_load_unit(u->manager, name, path, NULL, &other); + if (r < 0) + return r; + + return unit_add_dependency(u, d, other, add_reference); +} + +int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference) { + _cleanup_free_ char *buf = NULL; + Unit *other; + int r; + + assert(u); + assert(name || path); + + r = resolve_template(u, name, path, &buf, &name); + if (r < 0) + return r; + + r = manager_load_unit(u->manager, name, path, NULL, &other); + if (r < 0) + return r; + + return unit_add_two_dependencies(u, d, e, other, add_reference); +} + +int set_unit_path(const char *p) { + /* This is mostly for debug purposes */ + if (setenv("SYSTEMD_UNIT_PATH", p, 1) < 0) + return -errno; + + return 0; +} + +char *unit_dbus_path(Unit *u) { + assert(u); + + if (!u->id) + return NULL; + + return unit_dbus_path_from_name(u->id); +} + +int unit_set_slice(Unit *u, Unit *slice) { + assert(u); + assert(slice); + + /* Sets the unit slice if it has not been set before. Is extra + * careful, to only allow this for units that actually have a + * cgroup context. Also, we don't allow to set this for slices + * (since the parent slice is derived from the name). Make + * sure the unit we set is actually a slice. */ + + if (!UNIT_HAS_CGROUP_CONTEXT(u)) + return -EOPNOTSUPP; + + if (u->type == UNIT_SLICE) + return -EINVAL; + + if (unit_active_state(u) != UNIT_INACTIVE) + return -EBUSY; + + if (slice->type != UNIT_SLICE) + return -EINVAL; + + if (unit_has_name(u, SPECIAL_INIT_SCOPE) && + !unit_has_name(slice, SPECIAL_ROOT_SLICE)) + return -EPERM; + + if (UNIT_DEREF(u->slice) == slice) + return 0; + + /* Disallow slice changes if @u is already bound to cgroups */ + if (UNIT_ISSET(u->slice) && u->cgroup_realized) + return -EBUSY; + + unit_ref_unset(&u->slice); + unit_ref_set(&u->slice, slice); + return 1; +} + +int unit_set_default_slice(Unit *u) { + _cleanup_free_ char *b = NULL; + const char *slice_name; + Unit *slice; + int r; + + assert(u); + + if (UNIT_ISSET(u->slice)) + return 0; + + if (u->instance) { + _cleanup_free_ char *prefix = NULL, *escaped = NULL; + + /* Implicitly place all instantiated units in their + * own per-template slice */ + + r = unit_name_to_prefix(u->id, &prefix); + if (r < 0) + return r; + + /* The prefix is already escaped, but it might include + * "-" which has a special meaning for slice units, + * hence escape it here extra. */ + escaped = unit_name_escape(prefix); + if (!escaped) + return -ENOMEM; + + if (MANAGER_IS_SYSTEM(u->manager)) + b = strjoin("system-", escaped, ".slice", NULL); + else + b = strappend(escaped, ".slice"); + if (!b) + return -ENOMEM; + + slice_name = b; + } else + slice_name = + MANAGER_IS_SYSTEM(u->manager) && !unit_has_name(u, SPECIAL_INIT_SCOPE) + ? SPECIAL_SYSTEM_SLICE + : SPECIAL_ROOT_SLICE; + + r = manager_load_unit(u->manager, slice_name, NULL, NULL, &slice); + if (r < 0) + return r; + + return unit_set_slice(u, slice); +} + +const char *unit_slice_name(Unit *u) { + assert(u); + + if (!UNIT_ISSET(u->slice)) + return NULL; + + return UNIT_DEREF(u->slice)->id; +} + +int unit_load_related_unit(Unit *u, const char *type, Unit **_found) { + _cleanup_free_ char *t = NULL; + int r; + + assert(u); + assert(type); + assert(_found); + + r = unit_name_change_suffix(u->id, type, &t); + if (r < 0) + return r; + if (unit_has_name(u, t)) + return -EINVAL; + + r = manager_load_unit(u->manager, t, NULL, NULL, _found); + assert(r < 0 || *_found != u); + return r; +} + +static int signal_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) { + const char *name, *old_owner, *new_owner; + Unit *u = userdata; + int r; + + assert(message); + assert(u); + + r = sd_bus_message_read(message, "sss", &name, &old_owner, &new_owner); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (UNIT_VTABLE(u)->bus_name_owner_change) + UNIT_VTABLE(u)->bus_name_owner_change(u, name, old_owner, new_owner); + + return 0; +} + +int unit_install_bus_match(Unit *u, sd_bus *bus, const char *name) { + const char *match; + + assert(u); + assert(bus); + assert(name); + + if (u->match_bus_slot) + return -EBUSY; + + match = strjoina("type='signal'," + "sender='org.freedesktop.DBus'," + "path='/org/freedesktop/DBus'," + "interface='org.freedesktop.DBus'," + "member='NameOwnerChanged'," + "arg0='", name, "'"); + + return sd_bus_add_match(bus, &u->match_bus_slot, match, signal_name_owner_changed, u); +} + +int unit_watch_bus_name(Unit *u, const char *name) { + int r; + + assert(u); + assert(name); + + /* Watch a specific name on the bus. We only support one unit + * watching each name for now. */ + + if (u->manager->api_bus) { + /* If the bus is already available, install the match directly. + * Otherwise, just put the name in the list. bus_setup_api() will take care later. */ + r = unit_install_bus_match(u, u->manager->api_bus, name); + if (r < 0) + return log_warning_errno(r, "Failed to subscribe to NameOwnerChanged signal for '%s': %m", name); + } + + r = hashmap_put(u->manager->watch_bus, name, u); + if (r < 0) { + u->match_bus_slot = sd_bus_slot_unref(u->match_bus_slot); + return log_warning_errno(r, "Failed to put bus name to hashmap: %m"); + } + + return 0; +} + +void unit_unwatch_bus_name(Unit *u, const char *name) { + assert(u); + assert(name); + + hashmap_remove_value(u->manager->watch_bus, name, u); + u->match_bus_slot = sd_bus_slot_unref(u->match_bus_slot); +} + +bool unit_can_serialize(Unit *u) { + assert(u); + + return UNIT_VTABLE(u)->serialize && UNIT_VTABLE(u)->deserialize_item; +} + +int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { + int r; + + assert(u); + assert(f); + assert(fds); + + if (unit_can_serialize(u)) { + ExecRuntime *rt; + + r = UNIT_VTABLE(u)->serialize(u, f, fds); + if (r < 0) + return r; + + rt = unit_get_exec_runtime(u); + if (rt) { + r = exec_runtime_serialize(u, rt, f, fds); + if (r < 0) + return r; + } + } + + dual_timestamp_serialize(f, "state-change-timestamp", &u->state_change_timestamp); + + dual_timestamp_serialize(f, "inactive-exit-timestamp", &u->inactive_exit_timestamp); + dual_timestamp_serialize(f, "active-enter-timestamp", &u->active_enter_timestamp); + dual_timestamp_serialize(f, "active-exit-timestamp", &u->active_exit_timestamp); + dual_timestamp_serialize(f, "inactive-enter-timestamp", &u->inactive_enter_timestamp); + + dual_timestamp_serialize(f, "condition-timestamp", &u->condition_timestamp); + dual_timestamp_serialize(f, "assert-timestamp", &u->assert_timestamp); + + if (dual_timestamp_is_set(&u->condition_timestamp)) + unit_serialize_item(u, f, "condition-result", yes_no(u->condition_result)); + + if (dual_timestamp_is_set(&u->assert_timestamp)) + unit_serialize_item(u, f, "assert-result", yes_no(u->assert_result)); + + unit_serialize_item(u, f, "transient", yes_no(u->transient)); + unit_serialize_item_format(u, f, "cpuacct-usage-base", "%" PRIu64, u->cpuacct_usage_base); + + if (u->cgroup_path) + unit_serialize_item(u, f, "cgroup", u->cgroup_path); + unit_serialize_item(u, f, "cgroup-realized", yes_no(u->cgroup_realized)); + + if (serialize_jobs) { + if (u->job) { + fprintf(f, "job\n"); + job_serialize(u->job, f, fds); + } + + if (u->nop_job) { + fprintf(f, "job\n"); + job_serialize(u->nop_job, f, fds); + } + } + + /* End marker */ + fputc('\n', f); + return 0; +} + +int unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value) { + assert(u); + assert(f); + assert(key); + + if (!value) + return 0; + + fputs(key, f); + fputc('=', f); + fputs(value, f); + fputc('\n', f); + + return 1; +} + +int unit_serialize_item_escaped(Unit *u, FILE *f, const char *key, const char *value) { + _cleanup_free_ char *c = NULL; + + assert(u); + assert(f); + assert(key); + + if (!value) + return 0; + + c = cescape(value); + if (!c) + return -ENOMEM; + + fputs(key, f); + fputc('=', f); + fputs(c, f); + fputc('\n', f); + + return 1; +} + +int unit_serialize_item_fd(Unit *u, FILE *f, FDSet *fds, const char *key, int fd) { + int copy; + + assert(u); + assert(f); + assert(key); + + if (fd < 0) + return 0; + + copy = fdset_put_dup(fds, fd); + if (copy < 0) + return copy; + + fprintf(f, "%s=%i\n", key, copy); + return 1; +} + +void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *format, ...) { + va_list ap; + + assert(u); + assert(f); + assert(key); + assert(format); + + fputs(key, f); + fputc('=', f); + + va_start(ap, format); + vfprintf(f, format, ap); + va_end(ap); + + fputc('\n', f); +} + +int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { + ExecRuntime **rt = NULL; + size_t offset; + int r; + + assert(u); + assert(f); + assert(fds); + + offset = UNIT_VTABLE(u)->exec_runtime_offset; + if (offset > 0) + rt = (ExecRuntime**) ((uint8_t*) u + offset); + + for (;;) { + char line[LINE_MAX], *l, *v; + size_t k; + + if (!fgets(line, sizeof(line), f)) { + if (feof(f)) + return 0; + return -errno; + } + + char_array_0(line); + l = strstrip(line); + + /* End marker */ + if (isempty(l)) + break; + + k = strcspn(l, "="); + + if (l[k] == '=') { + l[k] = 0; + v = l+k+1; + } else + v = l+k; + + if (streq(l, "job")) { + if (v[0] == '\0') { + /* new-style serialized job */ + Job *j; + + j = job_new_raw(u); + if (!j) + return log_oom(); + + r = job_deserialize(j, f, fds); + if (r < 0) { + job_free(j); + return r; + } + + r = hashmap_put(u->manager->jobs, UINT32_TO_PTR(j->id), j); + if (r < 0) { + job_free(j); + return r; + } + + r = job_install_deserialized(j); + if (r < 0) { + hashmap_remove(u->manager->jobs, UINT32_TO_PTR(j->id)); + job_free(j); + return r; + } + } else /* legacy for pre-44 */ + log_unit_warning(u, "Update from too old systemd versions are unsupported, cannot deserialize job: %s", v); + continue; + } else if (streq(l, "state-change-timestamp")) { + dual_timestamp_deserialize(v, &u->state_change_timestamp); + continue; + } else if (streq(l, "inactive-exit-timestamp")) { + dual_timestamp_deserialize(v, &u->inactive_exit_timestamp); + continue; + } else if (streq(l, "active-enter-timestamp")) { + dual_timestamp_deserialize(v, &u->active_enter_timestamp); + continue; + } else if (streq(l, "active-exit-timestamp")) { + dual_timestamp_deserialize(v, &u->active_exit_timestamp); + continue; + } else if (streq(l, "inactive-enter-timestamp")) { + dual_timestamp_deserialize(v, &u->inactive_enter_timestamp); + continue; + } else if (streq(l, "condition-timestamp")) { + dual_timestamp_deserialize(v, &u->condition_timestamp); + continue; + } else if (streq(l, "assert-timestamp")) { + dual_timestamp_deserialize(v, &u->assert_timestamp); + continue; + } else if (streq(l, "condition-result")) { + + r = parse_boolean(v); + if (r < 0) + log_unit_debug(u, "Failed to parse condition result value %s, ignoring.", v); + else + u->condition_result = r; + + continue; + + } else if (streq(l, "assert-result")) { + + r = parse_boolean(v); + if (r < 0) + log_unit_debug(u, "Failed to parse assert result value %s, ignoring.", v); + else + u->assert_result = r; + + continue; + + } else if (streq(l, "transient")) { + + r = parse_boolean(v); + if (r < 0) + log_unit_debug(u, "Failed to parse transient bool %s, ignoring.", v); + else + u->transient = r; + + continue; + + } else if (streq(l, "cpuacct-usage-base")) { + + r = safe_atou64(v, &u->cpuacct_usage_base); + if (r < 0) + log_unit_debug(u, "Failed to parse CPU usage %s, ignoring.", v); + + continue; + + } else if (streq(l, "cgroup")) { + + r = unit_set_cgroup_path(u, v); + if (r < 0) + log_unit_debug_errno(u, r, "Failed to set cgroup path %s, ignoring: %m", v); + + (void) unit_watch_cgroup(u); + + continue; + } else if (streq(l, "cgroup-realized")) { + int b; + + b = parse_boolean(v); + if (b < 0) + log_unit_debug(u, "Failed to parse cgroup-realized bool %s, ignoring.", v); + else + u->cgroup_realized = b; + + continue; + } + + if (unit_can_serialize(u)) { + if (rt) { + r = exec_runtime_deserialize_item(u, rt, l, v, fds); + if (r < 0) { + log_unit_warning(u, "Failed to deserialize runtime parameter '%s', ignoring.", l); + continue; + } + + /* Returns positive if key was handled by the call */ + if (r > 0) + continue; + } + + r = UNIT_VTABLE(u)->deserialize_item(u, l, v, fds); + if (r < 0) + log_unit_warning(u, "Failed to deserialize unit parameter '%s', ignoring.", l); + } + } + + /* Versions before 228 did not carry a state change timestamp. In this case, take the current time. This is + * useful, so that timeouts based on this timestamp don't trigger too early, and is in-line with the logic from + * before 228 where the base for timeouts was not persistent across reboots. */ + + if (!dual_timestamp_is_set(&u->state_change_timestamp)) + dual_timestamp_get(&u->state_change_timestamp); + + return 0; +} + +int unit_add_node_link(Unit *u, const char *what, bool wants, UnitDependency dep) { + Unit *device; + _cleanup_free_ char *e = NULL; + int r; + + assert(u); + + /* Adds in links to the device node that this unit is based on */ + if (isempty(what)) + return 0; + + if (!is_device_path(what)) + return 0; + + /* When device units aren't supported (such as in a + * container), don't create dependencies on them. */ + if (!unit_type_supported(UNIT_DEVICE)) + return 0; + + r = unit_name_from_path(what, ".device", &e); + if (r < 0) + return r; + + r = manager_load_unit(u->manager, e, NULL, NULL, &device); + if (r < 0) + return r; + + r = unit_add_two_dependencies(u, UNIT_AFTER, + MANAGER_IS_SYSTEM(u->manager) ? dep : UNIT_WANTS, + device, true); + if (r < 0) + return r; + + if (wants) { + r = unit_add_dependency(device, UNIT_WANTS, u, false); + if (r < 0) + return r; + } + + return 0; +} + +int unit_coldplug(Unit *u) { + int r = 0, q = 0; + + assert(u); + + /* Make sure we don't enter a loop, when coldplugging + * recursively. */ + if (u->coldplugged) + return 0; + + u->coldplugged = true; + + if (UNIT_VTABLE(u)->coldplug) + r = UNIT_VTABLE(u)->coldplug(u); + + if (u->job) + q = job_coldplug(u->job); + + if (r < 0) + return r; + if (q < 0) + return q; + + return 0; +} + +static bool fragment_mtime_newer(const char *path, usec_t mtime) { + struct stat st; + + if (!path) + return false; + + if (stat(path, &st) < 0) + /* What, cannot access this anymore? */ + return true; + + if (mtime > 0) + /* For non-empty files check the mtime */ + return timespec_load(&st.st_mtim) > mtime; + else if (!null_or_empty(&st)) + /* For masked files check if they are still so */ + return true; + + return false; +} + +bool unit_need_daemon_reload(Unit *u) { + _cleanup_strv_free_ char **t = NULL; + char **path; + + assert(u); + + if (fragment_mtime_newer(u->fragment_path, u->fragment_mtime)) + return true; + + if (fragment_mtime_newer(u->source_path, u->source_mtime)) + return true; + + (void) unit_find_dropin_paths(u, &t); + if (!strv_equal(u->dropin_paths, t)) + return true; + + STRV_FOREACH(path, u->dropin_paths) + if (fragment_mtime_newer(*path, u->dropin_mtime)) + return true; + + return false; +} + +void unit_reset_failed(Unit *u) { + assert(u); + + if (UNIT_VTABLE(u)->reset_failed) + UNIT_VTABLE(u)->reset_failed(u); + + RATELIMIT_RESET(u->start_limit); + u->start_limit_hit = false; +} + +Unit *unit_following(Unit *u) { + assert(u); + + if (UNIT_VTABLE(u)->following) + return UNIT_VTABLE(u)->following(u); + + return NULL; +} + +bool unit_stop_pending(Unit *u) { + assert(u); + + /* This call does check the current state of the unit. It's + * hence useful to be called from state change calls of the + * unit itself, where the state isn't updated yet. This is + * different from unit_inactive_or_pending() which checks both + * the current state and for a queued job. */ + + return u->job && u->job->type == JOB_STOP; +} + +bool unit_inactive_or_pending(Unit *u) { + assert(u); + + /* Returns true if the unit is inactive or going down */ + + if (UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))) + return true; + + if (unit_stop_pending(u)) + return true; + + return false; +} + +bool unit_active_or_pending(Unit *u) { + assert(u); + + /* Returns true if the unit is active or going up */ + + if (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) + return true; + + if (u->job && + (u->job->type == JOB_START || + u->job->type == JOB_RELOAD_OR_START || + u->job->type == JOB_RESTART)) + return true; + + return false; +} + +int unit_kill(Unit *u, KillWho w, int signo, sd_bus_error *error) { + assert(u); + assert(w >= 0 && w < _KILL_WHO_MAX); + assert(SIGNAL_VALID(signo)); + + if (!UNIT_VTABLE(u)->kill) + return -EOPNOTSUPP; + + return UNIT_VTABLE(u)->kill(u, w, signo, error); +} + +static Set *unit_pid_set(pid_t main_pid, pid_t control_pid) { + Set *pid_set; + int r; + + pid_set = set_new(NULL); + if (!pid_set) + return NULL; + + /* Exclude the main/control pids from being killed via the cgroup */ + if (main_pid > 0) { + r = set_put(pid_set, PID_TO_PTR(main_pid)); + if (r < 0) + goto fail; + } + + if (control_pid > 0) { + r = set_put(pid_set, PID_TO_PTR(control_pid)); + if (r < 0) + goto fail; + } + + return pid_set; + +fail: + set_free(pid_set); + return NULL; +} + +int unit_kill_common( + Unit *u, + KillWho who, + int signo, + pid_t main_pid, + pid_t control_pid, + sd_bus_error *error) { + + int r = 0; + bool killed = false; + + if (IN_SET(who, KILL_MAIN, KILL_MAIN_FAIL)) { + if (main_pid < 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no main processes", unit_type_to_string(u->type)); + else if (main_pid == 0) + return sd_bus_error_set_const(error, BUS_ERROR_NO_SUCH_PROCESS, "No main process to kill"); + } + + if (IN_SET(who, KILL_CONTROL, KILL_CONTROL_FAIL)) { + if (control_pid < 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no control processes", unit_type_to_string(u->type)); + else if (control_pid == 0) + return sd_bus_error_set_const(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); + } + + if (IN_SET(who, KILL_CONTROL, KILL_CONTROL_FAIL, KILL_ALL, KILL_ALL_FAIL)) + if (control_pid > 0) { + if (kill(control_pid, signo) < 0) + r = -errno; + else + killed = true; + } + + if (IN_SET(who, KILL_MAIN, KILL_MAIN_FAIL, KILL_ALL, KILL_ALL_FAIL)) + if (main_pid > 0) { + if (kill(main_pid, signo) < 0) + r = -errno; + else + killed = true; + } + + if (IN_SET(who, KILL_ALL, KILL_ALL_FAIL) && u->cgroup_path) { + _cleanup_set_free_ Set *pid_set = NULL; + int q; + + /* Exclude the main/control pids from being killed via the cgroup */ + pid_set = unit_pid_set(main_pid, control_pid); + if (!pid_set) + return -ENOMEM; + + q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, signo, false, false, false, pid_set); + if (q < 0 && q != -EAGAIN && q != -ESRCH && q != -ENOENT) + r = q; + else + killed = true; + } + + if (r == 0 && !killed && IN_SET(who, KILL_ALL_FAIL, KILL_CONTROL_FAIL)) + return -ESRCH; + + return r; +} + +int unit_following_set(Unit *u, Set **s) { + assert(u); + assert(s); + + if (UNIT_VTABLE(u)->following_set) + return UNIT_VTABLE(u)->following_set(u, s); + + *s = NULL; + return 0; +} + +UnitFileState unit_get_unit_file_state(Unit *u) { + int r; + + assert(u); + + if (u->unit_file_state < 0 && u->fragment_path) { + r = unit_file_get_state( + u->manager->unit_file_scope, + NULL, + basename(u->fragment_path), + &u->unit_file_state); + if (r < 0) + u->unit_file_state = UNIT_FILE_BAD; + } + + return u->unit_file_state; +} + +int unit_get_unit_file_preset(Unit *u) { + assert(u); + + if (u->unit_file_preset < 0 && u->fragment_path) + u->unit_file_preset = unit_file_query_preset( + u->manager->unit_file_scope, + NULL, + basename(u->fragment_path)); + + return u->unit_file_preset; +} + +Unit* unit_ref_set(UnitRef *ref, Unit *u) { + assert(ref); + assert(u); + + if (ref->unit) + unit_ref_unset(ref); + + ref->unit = u; + LIST_PREPEND(refs, u->refs, ref); + return u; +} + +void unit_ref_unset(UnitRef *ref) { + assert(ref); + + if (!ref->unit) + return; + + /* We are about to drop a reference to the unit, make sure the garbage collection has a look at it as it might + * be unreferenced now. */ + unit_add_to_gc_queue(ref->unit); + + LIST_REMOVE(refs, ref->unit->refs, ref); + ref->unit = NULL; +} + +int unit_patch_contexts(Unit *u) { + CGroupContext *cc; + ExecContext *ec; + unsigned i; + int r; + + assert(u); + + /* Patch in the manager defaults into the exec and cgroup + * contexts, _after_ the rest of the settings have been + * initialized */ + + ec = unit_get_exec_context(u); + if (ec) { + /* This only copies in the ones that need memory */ + for (i = 0; i < _RLIMIT_MAX; i++) + if (u->manager->rlimit[i] && !ec->rlimit[i]) { + ec->rlimit[i] = newdup(struct rlimit, u->manager->rlimit[i], 1); + if (!ec->rlimit[i]) + return -ENOMEM; + } + + if (MANAGER_IS_USER(u->manager) && + !ec->working_directory) { + + r = get_home_dir(&ec->working_directory); + if (r < 0) + return r; + + /* Allow user services to run, even if the + * home directory is missing */ + ec->working_directory_missing_ok = true; + } + + if (MANAGER_IS_USER(u->manager) && + (ec->syscall_whitelist || + !set_isempty(ec->syscall_filter) || + !set_isempty(ec->syscall_archs) || + ec->address_families_whitelist || + !set_isempty(ec->address_families))) + ec->no_new_privileges = true; + + if (ec->private_devices) + ec->capability_bounding_set &= ~(UINT64_C(1) << CAP_MKNOD); + } + + cc = unit_get_cgroup_context(u); + if (cc) { + + if (ec && + ec->private_devices && + cc->device_policy == CGROUP_AUTO) + cc->device_policy = CGROUP_CLOSED; + } + + return 0; +} + +ExecContext *unit_get_exec_context(Unit *u) { + size_t offset; + assert(u); + + if (u->type < 0) + return NULL; + + offset = UNIT_VTABLE(u)->exec_context_offset; + if (offset <= 0) + return NULL; + + return (ExecContext*) ((uint8_t*) u + offset); +} + +KillContext *unit_get_kill_context(Unit *u) { + size_t offset; + assert(u); + + if (u->type < 0) + return NULL; + + offset = UNIT_VTABLE(u)->kill_context_offset; + if (offset <= 0) + return NULL; + + return (KillContext*) ((uint8_t*) u + offset); +} + +CGroupContext *unit_get_cgroup_context(Unit *u) { + size_t offset; + + if (u->type < 0) + return NULL; + + offset = UNIT_VTABLE(u)->cgroup_context_offset; + if (offset <= 0) + return NULL; + + return (CGroupContext*) ((uint8_t*) u + offset); +} + +ExecRuntime *unit_get_exec_runtime(Unit *u) { + size_t offset; + + if (u->type < 0) + return NULL; + + offset = UNIT_VTABLE(u)->exec_runtime_offset; + if (offset <= 0) + return NULL; + + return *(ExecRuntime**) ((uint8_t*) u + offset); +} + +static const char* unit_drop_in_dir(Unit *u, UnitSetPropertiesMode mode) { + assert(u); + + if (!IN_SET(mode, UNIT_RUNTIME, UNIT_PERSISTENT)) + return NULL; + + if (u->transient) /* Redirect drop-ins for transient units always into the transient directory. */ + return u->manager->lookup_paths.transient; + + if (mode == UNIT_RUNTIME) + return u->manager->lookup_paths.runtime_control; + + if (mode == UNIT_PERSISTENT) + return u->manager->lookup_paths.persistent_control; + + return NULL; +} + +int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) { + _cleanup_free_ char *p = NULL, *q = NULL; + const char *dir, *prefixed; + int r; + + assert(u); + + if (u->transient_file) { + /* When this is a transient unit file in creation, then let's not create a new drop-in but instead + * write to the transient unit file. */ + fputs(data, u->transient_file); + return 0; + } + + if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) + return 0; + + dir = unit_drop_in_dir(u, mode); + if (!dir) + return -EINVAL; + + prefixed = strjoina("# This is a drop-in unit file extension, created via \"systemctl set-property\" or an equivalent operation. Do not edit.\n", + data); + + r = drop_in_file(dir, u->id, 50, name, &p, &q); + if (r < 0) + return r; + + (void) mkdir_p(p, 0755); + r = write_string_file_atomic_label(q, prefixed); + if (r < 0) + return r; + + r = strv_push(&u->dropin_paths, q); + if (r < 0) + return r; + q = NULL; + + strv_uniq(u->dropin_paths); + + u->dropin_mtime = now(CLOCK_REALTIME); + + return 0; +} + +int unit_write_drop_in_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) { + _cleanup_free_ char *p = NULL; + va_list ap; + int r; + + assert(u); + assert(name); + assert(format); + + if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) + return 0; + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return unit_write_drop_in(u, mode, name, p); +} + +int unit_write_drop_in_private(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) { + const char *ndata; + + assert(u); + assert(name); + assert(data); + + if (!UNIT_VTABLE(u)->private_section) + return -EINVAL; + + if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) + return 0; + + ndata = strjoina("[", UNIT_VTABLE(u)->private_section, "]\n", data); + + return unit_write_drop_in(u, mode, name, ndata); +} + +int unit_write_drop_in_private_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) { + _cleanup_free_ char *p = NULL; + va_list ap; + int r; + + assert(u); + assert(name); + assert(format); + + if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) + return 0; + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return unit_write_drop_in_private(u, mode, name, p); +} + +int unit_make_transient(Unit *u) { + FILE *f; + char *path; + + assert(u); + + if (!UNIT_VTABLE(u)->can_transient) + return -EOPNOTSUPP; + + path = strjoin(u->manager->lookup_paths.transient, "/", u->id, NULL); + if (!path) + return -ENOMEM; + + /* Let's open the file we'll write the transient settings into. This file is kept open as long as we are + * creating the transient, and is closed in unit_load(), as soon as we start loading the file. */ + + RUN_WITH_UMASK(0022) { + f = fopen(path, "we"); + if (!f) { + free(path); + return -errno; + } + } + + if (u->transient_file) + fclose(u->transient_file); + u->transient_file = f; + + free(u->fragment_path); + u->fragment_path = path; + + u->source_path = mfree(u->source_path); + u->dropin_paths = strv_free(u->dropin_paths); + u->fragment_mtime = u->source_mtime = u->dropin_mtime = 0; + + u->load_state = UNIT_STUB; + u->load_error = 0; + u->transient = true; + + unit_add_to_dbus_queue(u); + unit_add_to_gc_queue(u); + unit_add_to_load_queue(u); + + fputs("# This is a transient unit file, created programmatically via the systemd API. Do not edit.\n", + u->transient_file); + + return 0; +} + +int unit_kill_context( + Unit *u, + KillContext *c, + KillOperation k, + pid_t main_pid, + pid_t control_pid, + bool main_pid_alien) { + + bool wait_for_exit = false; + int sig, r; + + assert(u); + assert(c); + + if (c->kill_mode == KILL_NONE) + return 0; + + switch (k) { + case KILL_KILL: + sig = SIGKILL; + break; + case KILL_ABORT: + sig = SIGABRT; + break; + case KILL_TERMINATE: + sig = c->kill_signal; + break; + default: + assert_not_reached("KillOperation unknown"); + } + + if (main_pid > 0) { + r = kill_and_sigcont(main_pid, sig); + + if (r < 0 && r != -ESRCH) { + _cleanup_free_ char *comm = NULL; + get_process_comm(main_pid, &comm); + + log_unit_warning_errno(u, r, "Failed to kill main process " PID_FMT " (%s), ignoring: %m", main_pid, strna(comm)); + } else { + if (!main_pid_alien) + wait_for_exit = true; + + if (c->send_sighup && k == KILL_TERMINATE) + (void) kill(main_pid, SIGHUP); + } + } + + if (control_pid > 0) { + r = kill_and_sigcont(control_pid, sig); + + if (r < 0 && r != -ESRCH) { + _cleanup_free_ char *comm = NULL; + get_process_comm(control_pid, &comm); + + log_unit_warning_errno(u, r, "Failed to kill control process " PID_FMT " (%s), ignoring: %m", control_pid, strna(comm)); + } else { + wait_for_exit = true; + + if (c->send_sighup && k == KILL_TERMINATE) + (void) kill(control_pid, SIGHUP); + } + } + + if (u->cgroup_path && + (c->kill_mode == KILL_CONTROL_GROUP || (c->kill_mode == KILL_MIXED && k == KILL_KILL))) { + _cleanup_set_free_ Set *pid_set = NULL; + + /* Exclude the main/control pids from being killed via the cgroup */ + pid_set = unit_pid_set(main_pid, control_pid); + if (!pid_set) + return -ENOMEM; + + r = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, sig, true, k != KILL_TERMINATE, false, pid_set); + if (r < 0) { + if (r != -EAGAIN && r != -ESRCH && r != -ENOENT) + log_unit_warning_errno(u, r, "Failed to kill control group %s, ignoring: %m", u->cgroup_path); + + } else if (r > 0) { + + /* FIXME: For now, on the legacy hierarchy, we + * will not wait for the cgroup members to die + * if we are running in a container or if this + * is a delegation unit, simply because cgroup + * notification is unreliable in these + * cases. It doesn't work at all in + * containers, and outside of containers it + * can be confused easily by left-over + * directories in the cgroup — which however + * should not exist in non-delegated units. On + * the unified hierarchy that's different, + * there we get proper events. Hence rely on + * them.*/ + + if (cg_unified() > 0 || + (detect_container() == 0 && !unit_cgroup_delegate(u))) + wait_for_exit = true; + + if (c->send_sighup && k != KILL_KILL) { + set_free(pid_set); + + pid_set = unit_pid_set(main_pid, control_pid); + if (!pid_set) + return -ENOMEM; + + cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, SIGHUP, false, true, false, pid_set); + } + } + } + + return wait_for_exit; +} + +int unit_require_mounts_for(Unit *u, const char *path) { + char prefix[strlen(path) + 1], *p; + int r; + + assert(u); + assert(path); + + /* Registers a unit for requiring a certain path and all its + * prefixes. We keep a simple array of these paths in the + * unit, since its usually short. However, we build a prefix + * table for all possible prefixes so that new appearing mount + * units can easily determine which units to make themselves a + * dependency of. */ + + if (!path_is_absolute(path)) + return -EINVAL; + + p = strdup(path); + if (!p) + return -ENOMEM; + + path_kill_slashes(p); + + if (!path_is_safe(p)) { + free(p); + return -EPERM; + } + + if (strv_contains(u->requires_mounts_for, p)) { + free(p); + return 0; + } + + r = strv_consume(&u->requires_mounts_for, p); + if (r < 0) + return r; + + PATH_FOREACH_PREFIX_MORE(prefix, p) { + Set *x; + + x = hashmap_get(u->manager->units_requiring_mounts_for, prefix); + if (!x) { + char *q; + + r = hashmap_ensure_allocated(&u->manager->units_requiring_mounts_for, &string_hash_ops); + if (r < 0) + return r; + + q = strdup(prefix); + if (!q) + return -ENOMEM; + + x = set_new(NULL); + if (!x) { + free(q); + return -ENOMEM; + } + + r = hashmap_put(u->manager->units_requiring_mounts_for, q, x); + if (r < 0) { + free(q); + set_free(x); + return r; + } + } + + r = set_put(x, u); + if (r < 0) + return r; + } + + return 0; +} + +int unit_setup_exec_runtime(Unit *u) { + ExecRuntime **rt; + size_t offset; + Iterator i; + Unit *other; + + offset = UNIT_VTABLE(u)->exec_runtime_offset; + assert(offset > 0); + + /* Check if there already is an ExecRuntime for this unit? */ + rt = (ExecRuntime**) ((uint8_t*) u + offset); + if (*rt) + return 0; + + /* Try to get it from somebody else */ + SET_FOREACH(other, u->dependencies[UNIT_JOINS_NAMESPACE_OF], i) { + + *rt = unit_get_exec_runtime(other); + if (*rt) { + exec_runtime_ref(*rt); + return 0; + } + } + + return exec_runtime_make(rt, unit_get_exec_context(u), u->id); +} + +bool unit_type_supported(UnitType t) { + if (_unlikely_(t < 0)) + return false; + if (_unlikely_(t >= _UNIT_TYPE_MAX)) + return false; + + if (!unit_vtable[t]->supported) + return true; + + return unit_vtable[t]->supported(); +} + +void unit_warn_if_dir_nonempty(Unit *u, const char* where) { + int r; + + assert(u); + assert(where); + + r = dir_is_empty(where); + if (r > 0) + return; + if (r < 0) { + log_unit_warning_errno(u, r, "Failed to check directory %s: %m", where); + return; + } + + log_struct(LOG_NOTICE, + LOG_MESSAGE_ID(SD_MESSAGE_OVERMOUNTING), + LOG_UNIT_ID(u), + LOG_UNIT_MESSAGE(u, "Directory %s to mount over is not empty, mounting anyway.", where), + "WHERE=%s", where, + NULL); +} + +int unit_fail_if_symlink(Unit *u, const char* where) { + int r; + + assert(u); + assert(where); + + r = is_symlink(where); + if (r < 0) { + log_unit_debug_errno(u, r, "Failed to check symlink %s, ignoring: %m", where); + return 0; + } + if (r == 0) + return 0; + + log_struct(LOG_ERR, + LOG_MESSAGE_ID(SD_MESSAGE_OVERMOUNTING), + LOG_UNIT_ID(u), + LOG_UNIT_MESSAGE(u, "Mount on symlink %s not allowed.", where), + "WHERE=%s", where, + NULL); + + return -ELOOP; +} + +bool unit_is_pristine(Unit *u) { + assert(u); + + /* Check if the unit already exists or is already around, + * in a number of different ways. Note that to cater for unit + * types such as slice, we are generally fine with units that + * are marked UNIT_LOADED even even though nothing was + * actually loaded, as those unit types don't require a file + * on disk to validly load. */ + + return !(!IN_SET(u->load_state, UNIT_NOT_FOUND, UNIT_LOADED) || + u->fragment_path || + u->source_path || + !strv_isempty(u->dropin_paths) || + u->job || + u->merged_into); +} + +pid_t unit_control_pid(Unit *u) { + assert(u); + + if (UNIT_VTABLE(u)->control_pid) + return UNIT_VTABLE(u)->control_pid(u); + + return 0; +} + +pid_t unit_main_pid(Unit *u) { + assert(u); + + if (UNIT_VTABLE(u)->main_pid) + return UNIT_VTABLE(u)->main_pid(u); + + return 0; +} diff --git a/src/libcore/unit.h b/src/libcore/unit.h new file mode 100644 index 0000000000..08a927962d --- /dev/null +++ b/src/libcore/unit.h @@ -0,0 +1,639 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include + +typedef struct Unit Unit; +typedef struct UnitVTable UnitVTable; +typedef struct UnitRef UnitRef; +typedef struct UnitStatusMessageFormats UnitStatusMessageFormats; + +#include "condition.h" +#include "failure-action.h" +#include "install.h" +#include "list.h" +#include "unit-name.h" + +typedef enum KillOperation { + KILL_TERMINATE, + KILL_KILL, + KILL_ABORT, + _KILL_OPERATION_MAX, + _KILL_OPERATION_INVALID = -1 +} KillOperation; + +static inline bool UNIT_IS_ACTIVE_OR_RELOADING(UnitActiveState t) { + return t == UNIT_ACTIVE || t == UNIT_RELOADING; +} + +static inline bool UNIT_IS_ACTIVE_OR_ACTIVATING(UnitActiveState t) { + return t == UNIT_ACTIVE || t == UNIT_ACTIVATING || t == UNIT_RELOADING; +} + +static inline bool UNIT_IS_INACTIVE_OR_DEACTIVATING(UnitActiveState t) { + return t == UNIT_INACTIVE || t == UNIT_FAILED || t == UNIT_DEACTIVATING; +} + +static inline bool UNIT_IS_INACTIVE_OR_FAILED(UnitActiveState t) { + return t == UNIT_INACTIVE || t == UNIT_FAILED; +} + +#include "job.h" + +struct UnitRef { + /* Keeps tracks of references to a unit. This is useful so + * that we can merge two units if necessary and correct all + * references to them */ + + Unit* unit; + LIST_FIELDS(UnitRef, refs); +}; + +struct Unit { + Manager *manager; + + UnitType type; + UnitLoadState load_state; + Unit *merged_into; + + char *id; /* One name is special because we use it for identification. Points to an entry in the names set */ + char *instance; + + Set *names; + Set *dependencies[_UNIT_DEPENDENCY_MAX]; + + char **requires_mounts_for; + + char *description; + char **documentation; + + char *fragment_path; /* if loaded from a config file this is the primary path to it */ + char *source_path; /* if converted, the source file */ + char **dropin_paths; + + usec_t fragment_mtime; + usec_t source_mtime; + usec_t dropin_mtime; + + /* If this is a transient unit we are currently writing, this is where we are writing it to */ + FILE *transient_file; + + /* If there is something to do with this unit, then this is the installed job for it */ + Job *job; + + /* JOB_NOP jobs are special and can be installed without disturbing the real job. */ + Job *nop_job; + + /* The slot used for watching NameOwnerChanged signals */ + sd_bus_slot *match_bus_slot; + + /* Job timeout and action to take */ + usec_t job_timeout; + FailureAction job_timeout_action; + char *job_timeout_reboot_arg; + + /* References to this */ + LIST_HEAD(UnitRef, refs); + + /* Conditions to check */ + LIST_HEAD(Condition, conditions); + LIST_HEAD(Condition, asserts); + + dual_timestamp condition_timestamp; + dual_timestamp assert_timestamp; + + /* Updated whenever the low-level state changes */ + dual_timestamp state_change_timestamp; + + /* Updated whenever the (high-level) active state enters or leaves the active or inactive states */ + dual_timestamp inactive_exit_timestamp; + dual_timestamp active_enter_timestamp; + dual_timestamp active_exit_timestamp; + dual_timestamp inactive_enter_timestamp; + + UnitRef slice; + + /* Per type list */ + LIST_FIELDS(Unit, units_by_type); + + /* All units which have requires_mounts_for set */ + LIST_FIELDS(Unit, has_requires_mounts_for); + + /* Load queue */ + LIST_FIELDS(Unit, load_queue); + + /* D-Bus queue */ + LIST_FIELDS(Unit, dbus_queue); + + /* Cleanup queue */ + LIST_FIELDS(Unit, cleanup_queue); + + /* GC queue */ + LIST_FIELDS(Unit, gc_queue); + + /* CGroup realize members queue */ + LIST_FIELDS(Unit, cgroup_queue); + + /* Units with the same CGroup netclass */ + LIST_FIELDS(Unit, cgroup_netclass); + + /* PIDs we keep an eye on. Note that a unit might have many + * more, but these are the ones we care enough about to + * process SIGCHLD for */ + Set *pids; + + /* Used during GC sweeps */ + unsigned gc_marker; + + /* Error code when we didn't manage to load the unit (negative) */ + int load_error; + + /* Put a ratelimit on unit starting */ + RateLimit start_limit; + FailureAction start_limit_action; + char *reboot_arg; + + /* Make sure we never enter endless loops with the check unneeded logic, or the BindsTo= logic */ + RateLimit auto_stop_ratelimit; + + /* Cached unit file state and preset */ + UnitFileState unit_file_state; + int unit_file_preset; + + /* Where the cpuacct.usage cgroup counter was at the time the unit was started */ + nsec_t cpuacct_usage_base; + + /* Counterparts in the cgroup filesystem */ + char *cgroup_path; + CGroupMask cgroup_realized_mask; + CGroupMask cgroup_enabled_mask; + CGroupMask cgroup_subtree_mask; + CGroupMask cgroup_members_mask; + int cgroup_inotify_wd; + + uint32_t cgroup_netclass_id; + + /* How to start OnFailure units */ + JobMode on_failure_job_mode; + + /* Garbage collect us we nobody wants or requires us anymore */ + bool stop_when_unneeded; + + /* Create default dependencies */ + bool default_dependencies; + + /* Refuse manual starting, allow starting only indirectly via dependency. */ + bool refuse_manual_start; + + /* Don't allow the user to stop this unit manually, allow stopping only indirectly via dependency. */ + bool refuse_manual_stop; + + /* Allow isolation requests */ + bool allow_isolate; + + /* Ignore this unit when isolating */ + bool ignore_on_isolate; + + /* Did the last condition check succeed? */ + bool condition_result; + bool assert_result; + + /* Is this a transient unit? */ + bool transient; + + bool in_load_queue:1; + bool in_dbus_queue:1; + bool in_cleanup_queue:1; + bool in_gc_queue:1; + bool in_cgroup_queue:1; + + bool sent_dbus_new_signal:1; + + bool no_gc:1; + + bool in_audit:1; + + bool cgroup_realized:1; + bool cgroup_members_mask_valid:1; + bool cgroup_subtree_mask_valid:1; + + bool start_limit_hit:1; + + /* Did we already invoke unit_coldplug() for this unit? */ + bool coldplugged:1; +}; + +struct UnitStatusMessageFormats { + const char *starting_stopping[2]; + const char *finished_start_job[_JOB_RESULT_MAX]; + const char *finished_stop_job[_JOB_RESULT_MAX]; +}; + +typedef enum UnitSetPropertiesMode { + UNIT_CHECK = 0, + UNIT_RUNTIME = 1, + UNIT_PERSISTENT = 2, +} UnitSetPropertiesMode; + +#include "automount.h" +#include "busname.h" +#include "device.h" +#include "path.h" +#include "scope.h" +#include "slice.h" +#include "socket.h" +#include "swap.h" +#include "target.h" +#include "timer.h" + +struct UnitVTable { + /* How much memory does an object of this unit type need */ + size_t object_size; + + /* If greater than 0, the offset into the object where + * ExecContext is found, if the unit type has that */ + size_t exec_context_offset; + + /* If greater than 0, the offset into the object where + * CGroupContext is found, if the unit type has that */ + size_t cgroup_context_offset; + + /* If greater than 0, the offset into the object where + * KillContext is found, if the unit type has that */ + size_t kill_context_offset; + + /* If greater than 0, the offset into the object where the + * pointer to ExecRuntime is found, if the unit type has + * that */ + size_t exec_runtime_offset; + + /* The name of the configuration file section with the private settings of this unit */ + const char *private_section; + + /* Config file sections this unit type understands, separated + * by NUL chars */ + const char *sections; + + /* This should reset all type-specific variables. This should + * not allocate memory, and is called with zero-initialized + * data. It should hence only initialize variables that need + * to be set != 0. */ + void (*init)(Unit *u); + + /* This should free all type-specific variables. It should be + * idempotent. */ + void (*done)(Unit *u); + + /* Actually load data from disk. This may fail, and should set + * load_state to UNIT_LOADED, UNIT_MERGED or leave it at + * UNIT_STUB if no configuration could be found. */ + int (*load)(Unit *u); + + /* If a lot of units got created via enumerate(), this is + * where to actually set the state and call unit_notify(). */ + int (*coldplug)(Unit *u); + + void (*dump)(Unit *u, FILE *f, const char *prefix); + + int (*start)(Unit *u); + int (*stop)(Unit *u); + int (*reload)(Unit *u); + + int (*kill)(Unit *u, KillWho w, int signo, sd_bus_error *error); + + bool (*can_reload)(Unit *u); + + /* Write all data that cannot be restored from other sources + * away using unit_serialize_item() */ + int (*serialize)(Unit *u, FILE *f, FDSet *fds); + + /* Restore one item from the serialization */ + int (*deserialize_item)(Unit *u, const char *key, const char *data, FDSet *fds); + + /* Try to match up fds with what we need for this unit */ + void (*distribute_fds)(Unit *u, FDSet *fds); + + /* Boils down the more complex internal state of this unit to + * a simpler one that the engine can understand */ + UnitActiveState (*active_state)(Unit *u); + + /* Returns the substate specific to this unit type as + * string. This is purely information so that we can give the + * user a more fine grained explanation in which actual state a + * unit is in. */ + const char* (*sub_state_to_string)(Unit *u); + + /* Return true when there is reason to keep this entry around + * even nothing references it and it isn't active in any + * way */ + bool (*check_gc)(Unit *u); + + /* When the unit is not running and no job for it queued we + * shall release its runtime resources */ + void (*release_resources)(Unit *u); + + /* Invoked on every child that died */ + void (*sigchld_event)(Unit *u, pid_t pid, int code, int status); + + /* Reset failed state if we are in failed state */ + void (*reset_failed)(Unit *u); + + /* Called whenever any of the cgroups this unit watches for + * ran empty */ + void (*notify_cgroup_empty)(Unit *u); + + /* Called whenever a process of this unit sends us a message */ + void (*notify_message)(Unit *u, pid_t pid, char **tags, FDSet *fds); + + /* Called whenever a name this Unit registered for comes or + * goes away. */ + void (*bus_name_owner_change)(Unit *u, const char *name, const char *old_owner, const char *new_owner); + + /* Called for each property that is being set */ + int (*bus_set_property)(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); + + /* Called after at least one property got changed to apply the necessary change */ + int (*bus_commit_properties)(Unit *u); + + /* Return the unit this unit is following */ + Unit *(*following)(Unit *u); + + /* Return the set of units that are following each other */ + int (*following_set)(Unit *u, Set **s); + + /* Invoked each time a unit this unit is triggering changes + * state or gains/loses a job */ + void (*trigger_notify)(Unit *u, Unit *trigger); + + /* Called whenever CLOCK_REALTIME made a jump */ + void (*time_change)(Unit *u); + + /* Returns the next timeout of a unit */ + int (*get_timeout)(Unit *u, usec_t *timeout); + + /* Returns the main PID if there is any defined, or 0. */ + pid_t (*main_pid)(Unit *u); + + /* Returns the main PID if there is any defined, or 0. */ + pid_t (*control_pid)(Unit *u); + + /* This is called for each unit type and should be used to + * enumerate existing devices and load them. However, + * everything that is loaded here should still stay in + * inactive state. It is the job of the coldplug() call above + * to put the units into the initial state. */ + void (*enumerate)(Manager *m); + + /* Type specific cleanups. */ + void (*shutdown)(Manager *m); + + /* If this function is set and return false all jobs for units + * of this type will immediately fail. */ + bool (*supported)(void); + + /* The bus vtable */ + const sd_bus_vtable *bus_vtable; + + /* The strings to print in status messages */ + UnitStatusMessageFormats status_message_formats; + + /* True if transient units of this type are OK */ + bool can_transient:1; +}; + +extern const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX]; + +#define UNIT_VTABLE(u) unit_vtable[(u)->type] + +/* For casting a unit into the various unit types */ +#define DEFINE_CAST(UPPERCASE, MixedCase) \ + static inline MixedCase* UPPERCASE(Unit *u) { \ + if (_unlikely_(!u || u->type != UNIT_##UPPERCASE)) \ + return NULL; \ + \ + return (MixedCase*) u; \ + } + +/* For casting the various unit types into a unit */ +#define UNIT(u) (&(u)->meta) + +#define UNIT_HAS_EXEC_CONTEXT(u) (UNIT_VTABLE(u)->exec_context_offset > 0) +#define UNIT_HAS_CGROUP_CONTEXT(u) (UNIT_VTABLE(u)->cgroup_context_offset > 0) +#define UNIT_HAS_KILL_CONTEXT(u) (UNIT_VTABLE(u)->kill_context_offset > 0) + +#define UNIT_TRIGGER(u) ((Unit*) set_first((u)->dependencies[UNIT_TRIGGERS])) + +DEFINE_CAST(SERVICE, Service); +DEFINE_CAST(SOCKET, Socket); +DEFINE_CAST(BUSNAME, BusName); +DEFINE_CAST(TARGET, Target); +DEFINE_CAST(DEVICE, Device); +DEFINE_CAST(MOUNT, Mount); +DEFINE_CAST(AUTOMOUNT, Automount); +DEFINE_CAST(SWAP, Swap); +DEFINE_CAST(TIMER, Timer); +DEFINE_CAST(PATH, Path); +DEFINE_CAST(SLICE, Slice); +DEFINE_CAST(SCOPE, Scope); + +Unit *unit_new(Manager *m, size_t size); +void unit_free(Unit *u); + +int unit_add_name(Unit *u, const char *name); + +int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_reference); +int unit_add_two_dependencies(Unit *u, UnitDependency d, UnitDependency e, Unit *other, bool add_reference); + +int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, const char *filename, bool add_reference); +int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference); + +int unit_add_exec_dependencies(Unit *u, ExecContext *c); + +int unit_choose_id(Unit *u, const char *name); +int unit_set_description(Unit *u, const char *description); + +bool unit_check_gc(Unit *u); + +void unit_add_to_load_queue(Unit *u); +void unit_add_to_dbus_queue(Unit *u); +void unit_add_to_cleanup_queue(Unit *u); +void unit_add_to_gc_queue(Unit *u); + +int unit_merge(Unit *u, Unit *other); +int unit_merge_by_name(Unit *u, const char *other); + +Unit *unit_follow_merge(Unit *u) _pure_; + +int unit_load_fragment_and_dropin(Unit *u); +int unit_load_fragment_and_dropin_optional(Unit *u); +int unit_load(Unit *unit); + +int unit_set_slice(Unit *u, Unit *slice); +int unit_set_default_slice(Unit *u); + +const char *unit_description(Unit *u) _pure_; + +bool unit_has_name(Unit *u, const char *name); + +UnitActiveState unit_active_state(Unit *u); + +const char* unit_sub_state_to_string(Unit *u); + +void unit_dump(Unit *u, FILE *f, const char *prefix); + +bool unit_can_reload(Unit *u) _pure_; +bool unit_can_start(Unit *u) _pure_; +bool unit_can_isolate(Unit *u) _pure_; + +int unit_start(Unit *u); +int unit_stop(Unit *u); +int unit_reload(Unit *u); + +int unit_kill(Unit *u, KillWho w, int signo, sd_bus_error *error); +int unit_kill_common(Unit *u, KillWho who, int signo, pid_t main_pid, pid_t control_pid, sd_bus_error *error); + +void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success); + +int unit_watch_pid(Unit *u, pid_t pid); +void unit_unwatch_pid(Unit *u, pid_t pid); +void unit_unwatch_all_pids(Unit *u); + +void unit_tidy_watch_pids(Unit *u, pid_t except1, pid_t except2); + +int unit_install_bus_match(Unit *u, sd_bus *bus, const char *name); +int unit_watch_bus_name(Unit *u, const char *name); +void unit_unwatch_bus_name(Unit *u, const char *name); + +bool unit_job_is_applicable(Unit *u, JobType j); + +int set_unit_path(const char *p); + +char *unit_dbus_path(Unit *u); + +int unit_load_related_unit(Unit *u, const char *type, Unit **_found); + +bool unit_can_serialize(Unit *u) _pure_; + +int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs); +int unit_deserialize(Unit *u, FILE *f, FDSet *fds); + +int unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value); +int unit_serialize_item_escaped(Unit *u, FILE *f, const char *key, const char *value); +int unit_serialize_item_fd(Unit *u, FILE *f, FDSet *fds, const char *key, int fd); +void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *value, ...) _printf_(4,5); + +int unit_add_node_link(Unit *u, const char *what, bool wants, UnitDependency d); + +int unit_coldplug(Unit *u); + +void unit_status_printf(Unit *u, const char *status, const char *unit_status_msg_format) _printf_(3, 0); +void unit_status_emit_starting_stopping_reloading(Unit *u, JobType t); + +bool unit_need_daemon_reload(Unit *u); + +void unit_reset_failed(Unit *u); + +Unit *unit_following(Unit *u); +int unit_following_set(Unit *u, Set **s); + +const char *unit_slice_name(Unit *u); + +bool unit_stop_pending(Unit *u) _pure_; +bool unit_inactive_or_pending(Unit *u) _pure_; +bool unit_active_or_pending(Unit *u); + +int unit_add_default_target_dependency(Unit *u, Unit *target); + +void unit_start_on_failure(Unit *u); +void unit_trigger_notify(Unit *u); + +UnitFileState unit_get_unit_file_state(Unit *u); +int unit_get_unit_file_preset(Unit *u); + +Unit* unit_ref_set(UnitRef *ref, Unit *u); +void unit_ref_unset(UnitRef *ref); + +#define UNIT_DEREF(ref) ((ref).unit) +#define UNIT_ISSET(ref) (!!(ref).unit) + +int unit_patch_contexts(Unit *u); + +ExecContext *unit_get_exec_context(Unit *u) _pure_; +KillContext *unit_get_kill_context(Unit *u) _pure_; +CGroupContext *unit_get_cgroup_context(Unit *u) _pure_; + +ExecRuntime *unit_get_exec_runtime(Unit *u) _pure_; + +int unit_setup_exec_runtime(Unit *u); + +int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data); +int unit_write_drop_in_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) _printf_(4,5); + +int unit_write_drop_in_private(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data); +int unit_write_drop_in_private_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) _printf_(4,5); + +int unit_kill_context(Unit *u, KillContext *c, KillOperation k, pid_t main_pid, pid_t control_pid, bool main_pid_alien); + +int unit_make_transient(Unit *u); + +int unit_require_mounts_for(Unit *u, const char *path); + +bool unit_type_supported(UnitType t); + +bool unit_is_pristine(Unit *u); + +pid_t unit_control_pid(Unit *u); +pid_t unit_main_pid(Unit *u); + +static inline bool unit_supported(Unit *u) { + return unit_type_supported(u->type); +} + +void unit_warn_if_dir_nonempty(Unit *u, const char* where); +int unit_fail_if_symlink(Unit *u, const char* where); + +int unit_start_limit_test(Unit *u); + +/* Macros which append UNIT= or USER_UNIT= to the message */ + +#define log_unit_full(unit, level, error, ...) \ + ({ \ + Unit *_u = (unit); \ + _u ? log_object_internal(level, error, __FILE__, __LINE__, __func__, _u->manager->unit_log_field, _u->id, ##__VA_ARGS__) : \ + log_internal(level, error, __FILE__, __LINE__, __func__, ##__VA_ARGS__); \ + }) + +#define log_unit_debug(unit, ...) log_unit_full(unit, LOG_DEBUG, 0, ##__VA_ARGS__) +#define log_unit_info(unit, ...) log_unit_full(unit, LOG_INFO, 0, ##__VA_ARGS__) +#define log_unit_notice(unit, ...) log_unit_full(unit, LOG_NOTICE, 0, ##__VA_ARGS__) +#define log_unit_warning(unit, ...) log_unit_full(unit, LOG_WARNING, 0, ##__VA_ARGS__) +#define log_unit_error(unit, ...) log_unit_full(unit, LOG_ERR, 0, ##__VA_ARGS__) + +#define log_unit_debug_errno(unit, error, ...) log_unit_full(unit, LOG_DEBUG, error, ##__VA_ARGS__) +#define log_unit_info_errno(unit, error, ...) log_unit_full(unit, LOG_INFO, error, ##__VA_ARGS__) +#define log_unit_notice_errno(unit, error, ...) log_unit_full(unit, LOG_NOTICE, error, ##__VA_ARGS__) +#define log_unit_warning_errno(unit, error, ...) log_unit_full(unit, LOG_WARNING, error, ##__VA_ARGS__) +#define log_unit_error_errno(unit, error, ...) log_unit_full(unit, LOG_ERR, error, ##__VA_ARGS__) + +#define LOG_UNIT_MESSAGE(unit, fmt, ...) "MESSAGE=%s: " fmt, (unit)->id, ##__VA_ARGS__ +#define LOG_UNIT_ID(unit) (unit)->manager->unit_log_format_string, (unit)->id diff --git a/src/libfirewall/Makefile b/src/libfirewall/Makefile new file mode 100644 index 0000000000..ced0f7e476 --- /dev/null +++ b/src/libfirewall/Makefile @@ -0,0 +1,42 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(HAVE_LIBIPTC),) +noinst_LTLIBRARIES += \ + libfirewall.la + +libfirewall_la_SOURCES = \ + src/shared/firewall-util.h \ + src/shared/firewall-util.c + +libfirewall_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(LIBIPTC_CFLAGS) + +libfirewall_la_LIBADD = \ + $(LIBIPTC_LIBS) +endif # HAVE_LIBIPTC + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libfirewall/firewall-util.c b/src/libfirewall/firewall-util.c new file mode 100644 index 0000000000..f73108eaa3 --- /dev/null +++ b/src/libfirewall/firewall-util.c @@ -0,0 +1,357 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#warning "Temporary work-around for broken glibc vs. linux kernel header definitions" +#warning "This really should be removed sooner rather than later, when this is fixed upstream" +#define _NET_IF_H 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef IFNAMSIZ +#define IFNAMSIZ 16 +#endif +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "firewall-util.h" +#include "in-addr-util.h" +#include "macro.h" +#include "socket-util.h" + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct xtc_handle*, iptc_free); + +static int entry_fill_basics( + struct ipt_entry *entry, + int protocol, + const char *in_interface, + const union in_addr_union *source, + unsigned source_prefixlen, + const char *out_interface, + const union in_addr_union *destination, + unsigned destination_prefixlen) { + + assert(entry); + + if (out_interface && !ifname_valid(out_interface)) + return -EINVAL; + if (in_interface && !ifname_valid(in_interface)) + return -EINVAL; + + entry->ip.proto = protocol; + + if (in_interface) { + strcpy(entry->ip.iniface, in_interface); + memset(entry->ip.iniface_mask, 0xFF, strlen(in_interface)+1); + } + if (source) { + entry->ip.src = source->in; + in_addr_prefixlen_to_netmask(&entry->ip.smsk, source_prefixlen); + } + + if (out_interface) { + strcpy(entry->ip.outiface, out_interface); + memset(entry->ip.outiface_mask, 0xFF, strlen(out_interface)+1); + } + if (destination) { + entry->ip.dst = destination->in; + in_addr_prefixlen_to_netmask(&entry->ip.dmsk, destination_prefixlen); + } + + return 0; +} + +int fw_add_masquerade( + bool add, + int af, + int protocol, + const union in_addr_union *source, + unsigned source_prefixlen, + const char *out_interface, + const union in_addr_union *destination, + unsigned destination_prefixlen) { + + _cleanup_(iptc_freep) struct xtc_handle *h = NULL; + struct ipt_entry *entry, *mask; + struct ipt_entry_target *t; + size_t sz; + struct nf_nat_ipv4_multi_range_compat *mr; + int r; + + if (af != AF_INET) + return -EOPNOTSUPP; + + if (protocol != 0 && protocol != IPPROTO_TCP && protocol != IPPROTO_UDP) + return -EOPNOTSUPP; + + h = iptc_init("nat"); + if (!h) + return -errno; + + sz = XT_ALIGN(sizeof(struct ipt_entry)) + + XT_ALIGN(sizeof(struct ipt_entry_target)) + + XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); + + /* Put together the entry we want to add or remove */ + entry = alloca0(sz); + entry->next_offset = sz; + entry->target_offset = XT_ALIGN(sizeof(struct ipt_entry)); + r = entry_fill_basics(entry, protocol, NULL, source, source_prefixlen, out_interface, destination, destination_prefixlen); + if (r < 0) + return r; + + /* Fill in target part */ + t = ipt_get_target(entry); + t->u.target_size = + XT_ALIGN(sizeof(struct ipt_entry_target)) + + XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); + strncpy(t->u.user.name, "MASQUERADE", sizeof(t->u.user.name)); + mr = (struct nf_nat_ipv4_multi_range_compat*) t->data; + mr->rangesize = 1; + + /* Create a search mask entry */ + mask = alloca(sz); + memset(mask, 0xFF, sz); + + if (add) { + if (iptc_check_entry("POSTROUTING", entry, (unsigned char*) mask, h)) + return 0; + if (errno != ENOENT) /* if other error than not existing yet, fail */ + return -errno; + + if (!iptc_insert_entry("POSTROUTING", entry, 0, h)) + return -errno; + } else { + if (!iptc_delete_entry("POSTROUTING", entry, (unsigned char*) mask, h)) { + if (errno == ENOENT) /* if it's already gone, all is good! */ + return 0; + + return -errno; + } + } + + if (!iptc_commit(h)) + return -errno; + + return 0; +} + +int fw_add_local_dnat( + bool add, + int af, + int protocol, + const char *in_interface, + const union in_addr_union *source, + unsigned source_prefixlen, + const union in_addr_union *destination, + unsigned destination_prefixlen, + uint16_t local_port, + const union in_addr_union *remote, + uint16_t remote_port, + const union in_addr_union *previous_remote) { + + + _cleanup_(iptc_freep) struct xtc_handle *h = NULL; + struct ipt_entry *entry, *mask; + struct ipt_entry_target *t; + struct ipt_entry_match *m; + struct xt_addrtype_info_v1 *at; + struct nf_nat_ipv4_multi_range_compat *mr; + size_t sz, msz; + int r; + + assert(add || !previous_remote); + + if (af != AF_INET) + return -EOPNOTSUPP; + + if (protocol != IPPROTO_TCP && protocol != IPPROTO_UDP) + return -EOPNOTSUPP; + + if (local_port <= 0) + return -EINVAL; + + if (remote_port <= 0) + return -EINVAL; + + h = iptc_init("nat"); + if (!h) + return -errno; + + sz = XT_ALIGN(sizeof(struct ipt_entry)) + + XT_ALIGN(sizeof(struct ipt_entry_match)) + + XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) + + XT_ALIGN(sizeof(struct ipt_entry_target)) + + XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); + + if (protocol == IPPROTO_TCP) + msz = XT_ALIGN(sizeof(struct ipt_entry_match)) + + XT_ALIGN(sizeof(struct xt_tcp)); + else + msz = XT_ALIGN(sizeof(struct ipt_entry_match)) + + XT_ALIGN(sizeof(struct xt_udp)); + + sz += msz; + + /* Fill in basic part */ + entry = alloca0(sz); + entry->next_offset = sz; + entry->target_offset = + XT_ALIGN(sizeof(struct ipt_entry)) + + XT_ALIGN(sizeof(struct ipt_entry_match)) + + XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) + + msz; + r = entry_fill_basics(entry, protocol, in_interface, source, source_prefixlen, NULL, destination, destination_prefixlen); + if (r < 0) + return r; + + /* Fill in first match */ + m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry))); + m->u.match_size = msz; + if (protocol == IPPROTO_TCP) { + struct xt_tcp *tcp; + + strncpy(m->u.user.name, "tcp", sizeof(m->u.user.name)); + tcp = (struct xt_tcp*) m->data; + tcp->dpts[0] = tcp->dpts[1] = local_port; + tcp->spts[0] = 0; + tcp->spts[1] = 0xFFFF; + + } else { + struct xt_udp *udp; + + strncpy(m->u.user.name, "udp", sizeof(m->u.user.name)); + udp = (struct xt_udp*) m->data; + udp->dpts[0] = udp->dpts[1] = local_port; + udp->spts[0] = 0; + udp->spts[1] = 0xFFFF; + } + + /* Fill in second match */ + m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry)) + msz); + m->u.match_size = + XT_ALIGN(sizeof(struct ipt_entry_match)) + + XT_ALIGN(sizeof(struct xt_addrtype_info_v1)); + strncpy(m->u.user.name, "addrtype", sizeof(m->u.user.name)); + m->u.user.revision = 1; + at = (struct xt_addrtype_info_v1*) m->data; + at->dest = XT_ADDRTYPE_LOCAL; + + /* Fill in target part */ + t = ipt_get_target(entry); + t->u.target_size = + XT_ALIGN(sizeof(struct ipt_entry_target)) + + XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); + strncpy(t->u.user.name, "DNAT", sizeof(t->u.user.name)); + mr = (struct nf_nat_ipv4_multi_range_compat*) t->data; + mr->rangesize = 1; + mr->range[0].flags = NF_NAT_RANGE_PROTO_SPECIFIED|NF_NAT_RANGE_MAP_IPS; + mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr; + if (protocol == IPPROTO_TCP) + mr->range[0].min.tcp.port = mr->range[0].max.tcp.port = htons(remote_port); + else + mr->range[0].min.udp.port = mr->range[0].max.udp.port = htons(remote_port); + + mask = alloca0(sz); + memset(mask, 0xFF, sz); + + if (add) { + /* Add the PREROUTING rule, if it is missing so far */ + if (!iptc_check_entry("PREROUTING", entry, (unsigned char*) mask, h)) { + if (errno != ENOENT) + return -EINVAL; + + if (!iptc_insert_entry("PREROUTING", entry, 0, h)) + return -errno; + } + + /* If a previous remote is set, remove its entry */ + if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) { + mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr; + + if (!iptc_delete_entry("PREROUTING", entry, (unsigned char*) mask, h)) { + if (errno != ENOENT) + return -errno; + } + + mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr; + } + + /* Add the OUTPUT rule, if it is missing so far */ + if (!in_interface) { + + /* Don't apply onto loopback addresses */ + if (!destination) { + entry->ip.dst.s_addr = htobe32(0x7F000000); + entry->ip.dmsk.s_addr = htobe32(0xFF000000); + entry->ip.invflags = IPT_INV_DSTIP; + } + + if (!iptc_check_entry("OUTPUT", entry, (unsigned char*) mask, h)) { + if (errno != ENOENT) + return -errno; + + if (!iptc_insert_entry("OUTPUT", entry, 0, h)) + return -errno; + } + + /* If a previous remote is set, remove its entry */ + if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) { + mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr; + + if (!iptc_delete_entry("OUTPUT", entry, (unsigned char*) mask, h)) { + if (errno != ENOENT) + return -errno; + } + } + } + } else { + if (!iptc_delete_entry("PREROUTING", entry, (unsigned char*) mask, h)) { + if (errno != ENOENT) + return -errno; + } + + if (!in_interface) { + if (!destination) { + entry->ip.dst.s_addr = htobe32(0x7F000000); + entry->ip.dmsk.s_addr = htobe32(0xFF000000); + entry->ip.invflags = IPT_INV_DSTIP; + } + + if (!iptc_delete_entry("OUTPUT", entry, (unsigned char*) mask, h)) { + if (errno != ENOENT) + return -errno; + } + } + } + + if (!iptc_commit(h)) + return -errno; + + return 0; +} diff --git a/src/libfirewall/firewall-util.h b/src/libfirewall/firewall-util.h new file mode 100644 index 0000000000..c39b34cf8f --- /dev/null +++ b/src/libfirewall/firewall-util.h @@ -0,0 +1,83 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 + +#include "in-addr-util.h" + +#ifdef HAVE_LIBIPTC + +int fw_add_masquerade( + bool add, + int af, + int protocol, + const union in_addr_union *source, + unsigned source_prefixlen, + const char *out_interface, + const union in_addr_union *destination, + unsigned destination_prefixlen); + +int fw_add_local_dnat( + bool add, + int af, + int protocol, + const char *in_interface, + const union in_addr_union *source, + unsigned source_prefixlen, + const union in_addr_union *destination, + unsigned destination_prefixlen, + uint16_t local_port, + const union in_addr_union *remote, + uint16_t remote_port, + const union in_addr_union *previous_remote); + +#else + +static inline int fw_add_masquerade( + bool add, + int af, + int protocol, + const union in_addr_union *source, + unsigned source_prefixlen, + const char *out_interface, + const union in_addr_union *destination, + unsigned destination_prefixlen) { + return -EOPNOTSUPP; +} + +static inline int fw_add_local_dnat( + bool add, + int af, + int protocol, + const char *in_interface, + const union in_addr_union *source, + unsigned source_prefixlen, + const union in_addr_union *destination, + unsigned destination_prefixlen, + uint16_t local_port, + const union in_addr_union *remote, + uint16_t remote_port, + const union in_addr_union *previous_remote) { + return -EOPNOTSUPP; +} + +#endif diff --git a/src/libshared/Makefile b/src/libshared/Makefile new file mode 100644 index 0000000000..67a19f4e21 --- /dev/null +++ b/src/libshared/Makefile @@ -0,0 +1,149 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +noinst_LTLIBRARIES += \ + libshared.la + +libshared_la_SOURCES = \ + src/shared/output-mode.h \ + src/shared/output-mode.c \ + src/shared/gpt.h \ + src/shared/udev-util.h \ + src/shared/linux/auto_dev-ioctl.h \ + src/shared/initreq.h \ + src/shared/dns-domain.c \ + src/shared/dns-domain.h \ + src/shared/efivars.c \ + src/shared/efivars.h \ + src/shared/fstab-util.c \ + src/shared/fstab-util.h \ + src/shared/sleep-config.c \ + src/shared/sleep-config.h \ + src/shared/conf-parser.c \ + src/shared/conf-parser.h \ + src/shared/pager.c \ + src/shared/pager.h \ + src/shared/spawn-polkit-agent.c \ + src/shared/spawn-polkit-agent.h \ + src/shared/apparmor-util.c \ + src/shared/apparmor-util.h \ + src/shared/ima-util.c \ + src/shared/ima-util.h \ + src/shared/ptyfwd.c \ + src/shared/ptyfwd.h \ + src/shared/base-filesystem.c \ + src/shared/base-filesystem.h \ + src/shared/uid-range.c \ + src/shared/uid-range.h \ + src/shared/install.c \ + src/shared/install.h \ + src/shared/install-printf.c \ + src/shared/install-printf.h \ + src/shared/path-lookup.c \ + src/shared/path-lookup.h \ + src/shared/specifier.c \ + src/shared/specifier.h \ + src/shared/dev-setup.c \ + src/shared/dev-setup.h \ + src/shared/dropin.c \ + src/shared/dropin.h \ + src/shared/condition.c \ + src/shared/condition.h \ + src/shared/clean-ipc.c \ + src/shared/clean-ipc.h \ + src/shared/generator.h \ + src/shared/generator.c \ + src/shared/acpi-fpdt.h \ + src/shared/acpi-fpdt.c \ + src/shared/boot-timestamps.h \ + src/shared/boot-timestamps.c \ + src/shared/cgroup-show.c \ + src/shared/cgroup-show.h \ + src/shared/utmp-wtmp.h \ + src/shared/watchdog.c \ + src/shared/watchdog.h \ + src/shared/spawn-ask-password-agent.c \ + src/shared/spawn-ask-password-agent.h \ + src/shared/ask-password-api.c \ + src/shared/ask-password-api.h \ + src/shared/switch-root.h \ + src/shared/switch-root.c \ + src/shared/import-util.c \ + src/shared/import-util.h \ + src/shared/sysctl-util.c \ + src/shared/sysctl-util.h \ + src/shared/bus-util.c \ + src/shared/bus-util.h \ + src/shared/logs-show.c \ + src/shared/logs-show.h \ + src/shared/machine-image.c \ + src/shared/machine-image.h \ + src/shared/machine-pool.c \ + src/shared/machine-pool.h \ + src/shared/resolve-util.c \ + src/shared/resolve-util.h \ + src/shared/bus-unit-util.c \ + src/shared/bus-unit-util.h \ + src/shared/tests.h \ + src/shared/tests.c + +ifneq ($(HAVE_UTMP),) +libshared_la_SOURCES += \ + src/shared/utmp-wtmp.c +endif # HAVE_UTMP + +ifneq ($(HAVE_SECCOMP),) +libshared_la_SOURCES += \ + src/shared/seccomp-util.h \ + src/shared/seccomp-util.c +endif # HAVE_SECCOMP + +ifneq ($(HAVE_ACL),) +libshared_la_SOURCES += \ + src/shared/acl-util.c \ + src/shared/acl-util.h +endif # HAVE_ACL + +libshared_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(ACL_CFLAGS) \ + $(LIBIDN_CFLAGS) \ + $(SECCOMP_CFLAGS) + +libshared_la_LIBADD = \ + libsystemd-internal.la \ + libsystemd-journal-internal.la \ + libudev-internal.la \ + $(ACL_LIBS) \ + $(LIBIDN_LIBS) \ + $(SECCOMP_LIBS) + +test_local_addresses_SOURCES = \ + src/libsystemd/sd-netlink/test-local-addresses.c + +test_local_addresses_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libshared/acl-util.c b/src/libshared/acl-util.c new file mode 100644 index 0000000000..2aa951fce9 --- /dev/null +++ b/src/libshared/acl-util.c @@ -0,0 +1,429 @@ +/*** + This file is part of systemd. + + Copyright 2011,2013 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 + +#include "acl-util.h" +#include "alloc-util.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" +#include "util.h" + +int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry) { + acl_entry_t i; + int r; + + assert(acl); + assert(entry); + + for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i); + r > 0; + r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) { + + acl_tag_t tag; + uid_t *u; + bool b; + + if (acl_get_tag_type(i, &tag) < 0) + return -errno; + + if (tag != ACL_USER) + continue; + + u = acl_get_qualifier(i); + if (!u) + return -errno; + + b = *u == uid; + acl_free(u); + + if (b) { + *entry = i; + return 1; + } + } + if (r < 0) + return -errno; + + return 0; +} + +int calc_acl_mask_if_needed(acl_t *acl_p) { + acl_entry_t i; + int r; + bool need = false; + + assert(acl_p); + + for (r = acl_get_entry(*acl_p, ACL_FIRST_ENTRY, &i); + r > 0; + r = acl_get_entry(*acl_p, ACL_NEXT_ENTRY, &i)) { + acl_tag_t tag; + + if (acl_get_tag_type(i, &tag) < 0) + return -errno; + + if (tag == ACL_MASK) + return 0; + + if (IN_SET(tag, ACL_USER, ACL_GROUP)) + need = true; + } + if (r < 0) + return -errno; + + if (need && acl_calc_mask(acl_p) < 0) + return -errno; + + return need; +} + +int add_base_acls_if_needed(acl_t *acl_p, const char *path) { + acl_entry_t i; + int r; + bool have_user_obj = false, have_group_obj = false, have_other = false; + struct stat st; + _cleanup_(acl_freep) acl_t basic = NULL; + + assert(acl_p); + + for (r = acl_get_entry(*acl_p, ACL_FIRST_ENTRY, &i); + r > 0; + r = acl_get_entry(*acl_p, ACL_NEXT_ENTRY, &i)) { + acl_tag_t tag; + + if (acl_get_tag_type(i, &tag) < 0) + return -errno; + + if (tag == ACL_USER_OBJ) + have_user_obj = true; + else if (tag == ACL_GROUP_OBJ) + have_group_obj = true; + else if (tag == ACL_OTHER) + have_other = true; + if (have_user_obj && have_group_obj && have_other) + return 0; + } + if (r < 0) + return -errno; + + r = stat(path, &st); + if (r < 0) + return -errno; + + basic = acl_from_mode(st.st_mode); + if (!basic) + return -errno; + + for (r = acl_get_entry(basic, ACL_FIRST_ENTRY, &i); + r > 0; + r = acl_get_entry(basic, ACL_NEXT_ENTRY, &i)) { + acl_tag_t tag; + acl_entry_t dst; + + if (acl_get_tag_type(i, &tag) < 0) + return -errno; + + if ((tag == ACL_USER_OBJ && have_user_obj) || + (tag == ACL_GROUP_OBJ && have_group_obj) || + (tag == ACL_OTHER && have_other)) + continue; + + r = acl_create_entry(acl_p, &dst); + if (r < 0) + return -errno; + + r = acl_copy_entry(dst, i); + if (r < 0) + return -errno; + } + if (r < 0) + return -errno; + return 0; +} + +int acl_search_groups(const char *path, char ***ret_groups) { + _cleanup_strv_free_ char **g = NULL; + _cleanup_(acl_free) acl_t acl = NULL; + bool ret = false; + acl_entry_t entry; + int r; + + assert(path); + + acl = acl_get_file(path, ACL_TYPE_DEFAULT); + if (!acl) + return -errno; + + r = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); + for (;;) { + _cleanup_(acl_free_gid_tpp) gid_t *gid = NULL; + acl_tag_t tag; + + if (r < 0) + return -errno; + if (r == 0) + break; + + if (acl_get_tag_type(entry, &tag) < 0) + return -errno; + + if (tag != ACL_GROUP) + goto next; + + gid = acl_get_qualifier(entry); + if (!gid) + return -errno; + + if (in_gid(*gid) > 0) { + if (!ret_groups) + return true; + + ret = true; + } + + if (ret_groups) { + char *name; + + name = gid_to_name(*gid); + if (!name) + return -ENOMEM; + + r = strv_consume(&g, name); + if (r < 0) + return r; + } + + next: + r = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry); + } + + if (ret_groups) { + *ret_groups = g; + g = NULL; + } + + return ret; +} + +int parse_acl(const char *text, acl_t *acl_access, acl_t *acl_default, bool want_mask) { + _cleanup_free_ char **a = NULL, **d = NULL; /* strings are not be freed */ + _cleanup_strv_free_ char **split; + char **entry; + int r = -EINVAL; + _cleanup_(acl_freep) acl_t a_acl = NULL, d_acl = NULL; + + split = strv_split(text, ","); + if (!split) + return -ENOMEM; + + STRV_FOREACH(entry, split) { + char *p; + + p = startswith(*entry, "default:"); + if (!p) + p = startswith(*entry, "d:"); + + if (p) + r = strv_push(&d, p); + else + r = strv_push(&a, *entry); + if (r < 0) + return r; + } + + if (!strv_isempty(a)) { + _cleanup_free_ char *join; + + join = strv_join(a, ","); + if (!join) + return -ENOMEM; + + a_acl = acl_from_text(join); + if (!a_acl) + return -errno; + + if (want_mask) { + r = calc_acl_mask_if_needed(&a_acl); + if (r < 0) + return r; + } + } + + if (!strv_isempty(d)) { + _cleanup_free_ char *join; + + join = strv_join(d, ","); + if (!join) + return -ENOMEM; + + d_acl = acl_from_text(join); + if (!d_acl) + return -errno; + + if (want_mask) { + r = calc_acl_mask_if_needed(&d_acl); + if (r < 0) + return r; + } + } + + *acl_access = a_acl; + *acl_default = d_acl; + a_acl = d_acl = NULL; + + return 0; +} + +static int acl_entry_equal(acl_entry_t a, acl_entry_t b) { + acl_tag_t tag_a, tag_b; + + if (acl_get_tag_type(a, &tag_a) < 0) + return -errno; + + if (acl_get_tag_type(b, &tag_b) < 0) + return -errno; + + if (tag_a != tag_b) + return false; + + switch (tag_a) { + case ACL_USER_OBJ: + case ACL_GROUP_OBJ: + case ACL_MASK: + case ACL_OTHER: + /* can have only one of those */ + return true; + case ACL_USER: { + _cleanup_(acl_free_uid_tpp) uid_t *uid_a = NULL, *uid_b = NULL; + + uid_a = acl_get_qualifier(a); + if (!uid_a) + return -errno; + + uid_b = acl_get_qualifier(b); + if (!uid_b) + return -errno; + + return *uid_a == *uid_b; + } + case ACL_GROUP: { + _cleanup_(acl_free_gid_tpp) gid_t *gid_a = NULL, *gid_b = NULL; + + gid_a = acl_get_qualifier(a); + if (!gid_a) + return -errno; + + gid_b = acl_get_qualifier(b); + if (!gid_b) + return -errno; + + return *gid_a == *gid_b; + } + default: + assert_not_reached("Unknown acl tag type"); + } +} + +static int find_acl_entry(acl_t acl, acl_entry_t entry, acl_entry_t *out) { + acl_entry_t i; + int r; + + for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i); + r > 0; + r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) { + + r = acl_entry_equal(i, entry); + if (r < 0) + return r; + if (r > 0) { + *out = i; + return 1; + } + } + if (r < 0) + return -errno; + return 0; +} + +int acls_for_file(const char *path, acl_type_t type, acl_t new, acl_t *acl) { + _cleanup_(acl_freep) acl_t old; + acl_entry_t i; + int r; + + old = acl_get_file(path, type); + if (!old) + return -errno; + + for (r = acl_get_entry(new, ACL_FIRST_ENTRY, &i); + r > 0; + r = acl_get_entry(new, ACL_NEXT_ENTRY, &i)) { + + acl_entry_t j; + + r = find_acl_entry(old, i, &j); + if (r < 0) + return r; + if (r == 0) + if (acl_create_entry(&old, &j) < 0) + return -errno; + + if (acl_copy_entry(j, i) < 0) + return -errno; + } + if (r < 0) + return -errno; + + *acl = old; + old = NULL; + return 0; +} + +int add_acls_for_user(int fd, uid_t uid) { + _cleanup_(acl_freep) acl_t acl = NULL; + acl_entry_t entry; + acl_permset_t permset; + int r; + + acl = acl_get_fd(fd); + if (!acl) + return -errno; + + r = acl_find_uid(acl, uid, &entry); + if (r <= 0) { + if (acl_create_entry(&acl, &entry) < 0 || + acl_set_tag_type(entry, ACL_USER) < 0 || + acl_set_qualifier(entry, &uid) < 0) + return -errno; + } + + /* We do not recalculate the mask unconditionally here, + * so that the fchmod() mask above stays intact. */ + if (acl_get_permset(entry, &permset) < 0 || + acl_add_perm(permset, ACL_READ) < 0) + return -errno; + + r = calc_acl_mask_if_needed(&acl); + if (r < 0) + return r; + + return acl_set_fd(fd, acl); +} diff --git a/src/libshared/acl-util.h b/src/libshared/acl-util.h new file mode 100644 index 0000000000..396e9e067e --- /dev/null +++ b/src/libshared/acl-util.h @@ -0,0 +1,48 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#ifdef HAVE_ACL + +#include +#include +#include + +#include "macro.h" + +int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry); +int calc_acl_mask_if_needed(acl_t *acl_p); +int add_base_acls_if_needed(acl_t *acl_p, const char *path); +int acl_search_groups(const char* path, char ***ret_groups); +int parse_acl(const char *text, acl_t *acl_access, acl_t *acl_default, bool want_mask); +int acls_for_file(const char *path, acl_type_t type, acl_t new, acl_t *acl); +int add_acls_for_user(int fd, uid_t uid); + +/* acl_free takes multiple argument types. + * Multiple cleanup functions are necessary. */ +DEFINE_TRIVIAL_CLEANUP_FUNC(acl_t, acl_free); +#define acl_free_charp acl_free +DEFINE_TRIVIAL_CLEANUP_FUNC(char*, acl_free_charp); +#define acl_free_uid_tp acl_free +DEFINE_TRIVIAL_CLEANUP_FUNC(uid_t*, acl_free_uid_tp); +#define acl_free_gid_tp acl_free +DEFINE_TRIVIAL_CLEANUP_FUNC(gid_t*, acl_free_gid_tp); + +#endif diff --git a/src/libshared/acpi-fpdt.c b/src/libshared/acpi-fpdt.c new file mode 100644 index 0000000000..6779691c28 --- /dev/null +++ b/src/libshared/acpi-fpdt.c @@ -0,0 +1,164 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 +#include +#include +#include +#include +#include + +#include "acpi-fpdt.h" +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "time-util.h" + +struct acpi_table_header { + char signature[4]; + uint32_t length; + uint8_t revision; + uint8_t checksum; + char oem_id[6]; + char oem_table_id[8]; + uint32_t oem_revision; + char asl_compiler_id[4]; + uint32_t asl_compiler_revision; +}; + +enum { + ACPI_FPDT_TYPE_BOOT = 0, + ACPI_FPDT_TYPE_S3PERF = 1, +}; + +struct acpi_fpdt_header { + uint16_t type; + uint8_t length; + uint8_t revision; + uint8_t reserved[4]; + uint64_t ptr; +}; + +struct acpi_fpdt_boot_header { + char signature[4]; + uint32_t length; +}; + +enum { + ACPI_FPDT_S3PERF_RESUME_REC = 0, + ACPI_FPDT_S3PERF_SUSPEND_REC = 1, + ACPI_FPDT_BOOT_REC = 2, +}; + +struct acpi_fpdt_boot { + uint16_t type; + uint8_t length; + uint8_t revision; + uint8_t reserved[4]; + uint64_t reset_end; + uint64_t load_start; + uint64_t startup_start; + uint64_t exit_services_entry; + uint64_t exit_services_exit; +}; + +int acpi_get_boot_usec(usec_t *loader_start, usec_t *loader_exit) { + _cleanup_free_ char *buf = NULL; + struct acpi_table_header *tbl; + size_t l = 0; + struct acpi_fpdt_header *rec; + int r; + uint64_t ptr = 0; + _cleanup_close_ int fd = -1; + struct acpi_fpdt_boot_header hbrec; + struct acpi_fpdt_boot brec; + + r = read_full_file("/sys/firmware/acpi/tables/FPDT", &buf, &l); + if (r < 0) + return r; + + if (l < sizeof(struct acpi_table_header) + sizeof(struct acpi_fpdt_header)) + return -EINVAL; + + tbl = (struct acpi_table_header *)buf; + if (l != tbl->length) + return -EINVAL; + + if (memcmp(tbl->signature, "FPDT", 4) != 0) + return -EINVAL; + + /* find Firmware Basic Boot Performance Pointer Record */ + for (rec = (struct acpi_fpdt_header *)(buf + sizeof(struct acpi_table_header)); + (char *)rec < buf + l; + rec = (struct acpi_fpdt_header *)((char *)rec + rec->length)) { + if (rec->length <= 0) + break; + if (rec->type != ACPI_FPDT_TYPE_BOOT) + continue; + if (rec->length != sizeof(struct acpi_fpdt_header)) + continue; + + ptr = rec->ptr; + break; + } + + if (ptr == 0) + return -ENODATA; + + /* read Firmware Basic Boot Performance Data Record */ + fd = open("/dev/mem", O_CLOEXEC|O_RDONLY); + if (fd < 0) + return -errno; + + l = pread(fd, &hbrec, sizeof(struct acpi_fpdt_boot_header), ptr); + if (l != sizeof(struct acpi_fpdt_boot_header)) + return -EINVAL; + + if (memcmp(hbrec.signature, "FBPT", 4) != 0) + return -EINVAL; + + if (hbrec.length < sizeof(struct acpi_fpdt_boot_header) + sizeof(struct acpi_fpdt_boot)) + return -EINVAL; + + l = pread(fd, &brec, sizeof(struct acpi_fpdt_boot), ptr + sizeof(struct acpi_fpdt_boot_header)); + if (l != sizeof(struct acpi_fpdt_boot)) + return -EINVAL; + + if (brec.length != sizeof(struct acpi_fpdt_boot)) + return -EINVAL; + + if (brec.type != ACPI_FPDT_BOOT_REC) + return -EINVAL; + + if (brec.exit_services_exit == 0) + /* Non-UEFI compatible boot. */ + return -ENODATA; + + if (brec.startup_start == 0 || brec.exit_services_exit < brec.startup_start) + return -EINVAL; + if (brec.exit_services_exit > NSEC_PER_HOUR) + return -EINVAL; + + if (loader_start) + *loader_start = brec.startup_start / 1000; + if (loader_exit) + *loader_exit = brec.exit_services_exit / 1000; + + return 0; +} diff --git a/src/libshared/acpi-fpdt.h b/src/libshared/acpi-fpdt.h new file mode 100644 index 0000000000..fc28175d0a --- /dev/null +++ b/src/libshared/acpi-fpdt.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 + +int acpi_get_boot_usec(usec_t *loader_start, usec_t *loader_exit); diff --git a/src/libshared/apparmor-util.c b/src/libshared/apparmor-util.c new file mode 100644 index 0000000000..edd695fd23 --- /dev/null +++ b/src/libshared/apparmor-util.c @@ -0,0 +1,39 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "alloc-util.h" +#include "apparmor-util.h" +#include "fileio.h" +#include "parse-util.h" + +bool mac_apparmor_use(void) { + static int cached_use = -1; + + if (cached_use < 0) { + _cleanup_free_ char *p = NULL; + + cached_use = + read_one_line_file("/sys/module/apparmor/parameters/enabled", &p) >= 0 && + parse_boolean(p) > 0; + } + + return cached_use; +} diff --git a/src/libshared/apparmor-util.h b/src/libshared/apparmor-util.h new file mode 100644 index 0000000000..524f740152 --- /dev/null +++ b/src/libshared/apparmor-util.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 + +bool mac_apparmor_use(void); diff --git a/src/libshared/ask-password-api.c b/src/libshared/ask-password-api.c new file mode 100644 index 0000000000..4a4bd8d3b8 --- /dev/null +++ b/src/libshared/ask-password-api.c @@ -0,0 +1,736 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "ask-password-api.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "io-util.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "random-util.h" +#include "signal-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "time-util.h" +#include "umask-util.h" +#include "utf8.h" +#include "util.h" + +#define KEYRING_TIMEOUT_USEC ((5 * USEC_PER_MINUTE) / 2) + +static int lookup_key(const char *keyname, key_serial_t *ret) { + key_serial_t serial; + + assert(keyname); + assert(ret); + + serial = request_key("user", keyname, NULL, 0); + if (serial == -1) + return negative_errno(); + + *ret = serial; + return 0; +} + +static int retrieve_key(key_serial_t serial, char ***ret) { + _cleanup_free_ char *p = NULL; + long m = 100, n; + char **l; + + assert(ret); + + for (;;) { + p = new(char, m); + if (!p) + return -ENOMEM; + + n = keyctl(KEYCTL_READ, (unsigned long) serial, (unsigned long) p, (unsigned long) m, 0); + if (n < 0) + return -errno; + + if (n < m) + break; + + memory_erase(p, n); + free(p); + m *= 2; + } + + l = strv_parse_nulstr(p, n); + if (!l) + return -ENOMEM; + + memory_erase(p, n); + + *ret = l; + return 0; +} + +static int add_to_keyring(const char *keyname, AskPasswordFlags flags, char **passwords) { + _cleanup_strv_free_erase_ char **l = NULL; + _cleanup_free_ char *p = NULL; + key_serial_t serial; + size_t n; + int r; + + assert(keyname); + assert(passwords); + + if (!(flags & ASK_PASSWORD_PUSH_CACHE)) + return 0; + + r = lookup_key(keyname, &serial); + if (r >= 0) { + r = retrieve_key(serial, &l); + if (r < 0) + return r; + } else if (r != -ENOKEY) + return r; + + r = strv_extend_strv(&l, passwords, true); + if (r <= 0) + return r; + + r = strv_make_nulstr(l, &p, &n); + if (r < 0) + return r; + + /* Truncate trailing NUL */ + assert(n > 0); + assert(p[n-1] == 0); + + serial = add_key("user", keyname, p, n-1, KEY_SPEC_USER_KEYRING); + memory_erase(p, n); + if (serial == -1) + return -errno; + + if (keyctl(KEYCTL_SET_TIMEOUT, + (unsigned long) serial, + (unsigned long) DIV_ROUND_UP(KEYRING_TIMEOUT_USEC, USEC_PER_SEC), 0, 0) < 0) + log_debug_errno(errno, "Failed to adjust timeout: %m"); + + log_debug("Added key to keyring as %" PRIi32 ".", serial); + + return 1; +} + +static int add_to_keyring_and_log(const char *keyname, AskPasswordFlags flags, char **passwords) { + int r; + + assert(keyname); + assert(passwords); + + r = add_to_keyring(keyname, flags, passwords); + if (r < 0) + return log_debug_errno(r, "Failed to add password to keyring: %m"); + + return 0; +} + +int ask_password_keyring(const char *keyname, AskPasswordFlags flags, char ***ret) { + + key_serial_t serial; + int r; + + assert(keyname); + assert(ret); + + if (!(flags & ASK_PASSWORD_ACCEPT_CACHED)) + return -EUNATCH; + + r = lookup_key(keyname, &serial); + if (r == -ENOSYS) /* when retrieving the distinction doesn't matter */ + return -ENOKEY; + if (r < 0) + return r; + + return retrieve_key(serial, ret); +} + +static void backspace_chars(int ttyfd, size_t p) { + + if (ttyfd < 0) + return; + + while (p > 0) { + p--; + + loop_write(ttyfd, "\b \b", 3, false); + } +} + +int ask_password_tty( + const char *message, + const char *keyname, + usec_t until, + AskPasswordFlags flags, + const char *flag_file, + char **ret) { + + struct termios old_termios, new_termios; + char passphrase[LINE_MAX + 1] = {}, *x; + size_t p = 0, codepoint = 0; + int r; + _cleanup_close_ int ttyfd = -1, notify = -1; + struct pollfd pollfd[2]; + bool reset_tty = false; + bool dirty = false; + enum { + POLL_TTY, + POLL_INOTIFY + }; + + assert(ret); + + if (flags & ASK_PASSWORD_NO_TTY) + return -EUNATCH; + + if (!message) + message = "Password:"; + + if (flag_file) { + notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK); + if (notify < 0) { + r = -errno; + goto finish; + } + + if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) { + r = -errno; + goto finish; + } + } + + ttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC); + if (ttyfd >= 0) { + + if (tcgetattr(ttyfd, &old_termios) < 0) { + r = -errno; + goto finish; + } + + loop_write(ttyfd, ANSI_HIGHLIGHT, strlen(ANSI_HIGHLIGHT), false); + loop_write(ttyfd, message, strlen(message), false); + loop_write(ttyfd, " ", 1, false); + loop_write(ttyfd, ANSI_NORMAL, strlen(ANSI_NORMAL), false); + + new_termios = old_termios; + new_termios.c_lflag &= ~(ICANON|ECHO); + new_termios.c_cc[VMIN] = 1; + new_termios.c_cc[VTIME] = 0; + + if (tcsetattr(ttyfd, TCSADRAIN, &new_termios) < 0) { + r = -errno; + goto finish; + } + + reset_tty = true; + } + + zero(pollfd); + pollfd[POLL_TTY].fd = ttyfd >= 0 ? ttyfd : STDIN_FILENO; + pollfd[POLL_TTY].events = POLLIN; + pollfd[POLL_INOTIFY].fd = notify; + pollfd[POLL_INOTIFY].events = POLLIN; + + for (;;) { + char c; + int sleep_for = -1, k; + ssize_t n; + + if (until > 0) { + usec_t y; + + y = now(CLOCK_MONOTONIC); + + if (y > until) { + r = -ETIME; + goto finish; + } + + sleep_for = (int) ((until - y) / USEC_PER_MSEC); + } + + if (flag_file) + if (access(flag_file, F_OK) < 0) { + r = -errno; + goto finish; + } + + k = poll(pollfd, notify >= 0 ? 2 : 1, sleep_for); + if (k < 0) { + if (errno == EINTR) + continue; + + r = -errno; + goto finish; + } else if (k == 0) { + r = -ETIME; + goto finish; + } + + if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0) + flush_fd(notify); + + if (pollfd[POLL_TTY].revents == 0) + continue; + + n = read(ttyfd >= 0 ? ttyfd : STDIN_FILENO, &c, 1); + if (n < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + + r = -errno; + goto finish; + + } else if (n == 0) + break; + + if (c == '\n') + break; + else if (c == 21) { /* C-u */ + + if (!(flags & ASK_PASSWORD_SILENT)) + backspace_chars(ttyfd, p); + p = 0; + + } else if (c == '\b' || c == 127) { + + if (p > 0) { + + if (!(flags & ASK_PASSWORD_SILENT)) + backspace_chars(ttyfd, 1); + + p--; + } else if (!dirty && !(flags & ASK_PASSWORD_SILENT)) { + + flags |= ASK_PASSWORD_SILENT; + + /* There are two ways to enter silent + * mode. Either by pressing backspace + * as first key (and only as first + * key), or ... */ + if (ttyfd >= 0) + loop_write(ttyfd, "(no echo) ", 10, false); + + } else if (ttyfd >= 0) + loop_write(ttyfd, "\a", 1, false); + + } else if (c == '\t' && !(flags & ASK_PASSWORD_SILENT)) { + + backspace_chars(ttyfd, p); + flags |= ASK_PASSWORD_SILENT; + + /* ... or by pressing TAB at any time. */ + + if (ttyfd >= 0) + loop_write(ttyfd, "(no echo) ", 10, false); + } else { + if (p >= sizeof(passphrase)-1) { + loop_write(ttyfd, "\a", 1, false); + continue; + } + + passphrase[p++] = c; + + if (!(flags & ASK_PASSWORD_SILENT) && ttyfd >= 0) { + n = utf8_encoded_valid_unichar(passphrase + codepoint); + if (n >= 0) { + codepoint = p; + loop_write(ttyfd, (flags & ASK_PASSWORD_ECHO) ? &c : "*", 1, false); + } + } + + dirty = true; + } + + c = 'x'; + } + + x = strndup(passphrase, p); + memory_erase(passphrase, p); + if (!x) { + r = -ENOMEM; + goto finish; + } + + if (keyname) + (void) add_to_keyring_and_log(keyname, flags, STRV_MAKE(x)); + + *ret = x; + r = 0; + +finish: + if (ttyfd >= 0 && reset_tty) { + loop_write(ttyfd, "\n", 1, false); + tcsetattr(ttyfd, TCSADRAIN, &old_termios); + } + + return r; +} + +static int create_socket(char **name) { + union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + }; + _cleanup_close_ int fd = -1; + static const int one = 1; + char *c; + int r; + + assert(name); + + fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) + return -errno; + + snprintf(sa.un.sun_path, sizeof(sa.un.sun_path)-1, "/run/systemd/ask-password/sck.%" PRIx64, random_u64()); + + RUN_WITH_UMASK(0177) { + if (bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) + return -errno; + } + + if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) + return -errno; + + c = strdup(sa.un.sun_path); + if (!c) + return -ENOMEM; + + *name = c; + + r = fd; + fd = -1; + + return r; +} + +int ask_password_agent( + const char *message, + const char *icon, + const char *id, + const char *keyname, + usec_t until, + AskPasswordFlags flags, + char ***ret) { + + enum { + FD_SOCKET, + FD_SIGNAL, + _FD_MAX + }; + + _cleanup_close_ int socket_fd = -1, signal_fd = -1, fd = -1; + char temp[] = "/run/systemd/ask-password/tmp.XXXXXX"; + char final[sizeof(temp)] = ""; + _cleanup_free_ char *socket_name = NULL; + _cleanup_strv_free_ char **l = NULL; + _cleanup_fclose_ FILE *f = NULL; + struct pollfd pollfd[_FD_MAX]; + sigset_t mask, oldmask; + int r; + + assert(ret); + + if (flags & ASK_PASSWORD_NO_AGENT) + return -EUNATCH; + + assert_se(sigemptyset(&mask) >= 0); + assert_se(sigset_add_many(&mask, SIGINT, SIGTERM, -1) >= 0); + assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) >= 0); + + (void) mkdir_p_label("/run/systemd/ask-password", 0755); + + fd = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC); + if (fd < 0) { + r = fd; + goto finish; + } + + (void) fchmod(fd, 0644); + + f = fdopen(fd, "w"); + if (!f) { + r = -errno; + goto finish; + } + + fd = -1; + + signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); + if (signal_fd < 0) { + r = -errno; + goto finish; + } + + socket_fd = create_socket(&socket_name); + if (socket_fd < 0) { + r = socket_fd; + goto finish; + } + + fprintf(f, + "[Ask]\n" + "PID="PID_FMT"\n" + "Socket=%s\n" + "AcceptCached=%i\n" + "Echo=%i\n" + "NotAfter="USEC_FMT"\n", + getpid(), + socket_name, + (flags & ASK_PASSWORD_ACCEPT_CACHED) ? 1 : 0, + (flags & ASK_PASSWORD_ECHO) ? 1 : 0, + until); + + if (message) + fprintf(f, "Message=%s\n", message); + + if (icon) + fprintf(f, "Icon=%s\n", icon); + + if (id) + fprintf(f, "Id=%s\n", id); + + r = fflush_and_check(f); + if (r < 0) + goto finish; + + memcpy(final, temp, sizeof(temp)); + + final[sizeof(final)-11] = 'a'; + final[sizeof(final)-10] = 's'; + final[sizeof(final)-9] = 'k'; + + if (rename(temp, final) < 0) { + r = -errno; + goto finish; + } + + zero(pollfd); + pollfd[FD_SOCKET].fd = socket_fd; + pollfd[FD_SOCKET].events = POLLIN; + pollfd[FD_SIGNAL].fd = signal_fd; + pollfd[FD_SIGNAL].events = POLLIN; + + for (;;) { + char passphrase[LINE_MAX+1]; + struct msghdr msghdr; + struct iovec iovec; + struct ucred *ucred; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; + } control; + ssize_t n; + int k; + usec_t t; + + t = now(CLOCK_MONOTONIC); + + if (until > 0 && until <= t) { + r = -ETIME; + goto finish; + } + + k = poll(pollfd, _FD_MAX, until > 0 ? (int) ((until-t)/USEC_PER_MSEC) : -1); + if (k < 0) { + if (errno == EINTR) + continue; + + r = -errno; + goto finish; + } + + if (k <= 0) { + r = -ETIME; + goto finish; + } + + if (pollfd[FD_SIGNAL].revents & POLLIN) { + r = -EINTR; + goto finish; + } + + if (pollfd[FD_SOCKET].revents != POLLIN) { + r = -EIO; + goto finish; + } + + zero(iovec); + iovec.iov_base = passphrase; + iovec.iov_len = sizeof(passphrase); + + zero(control); + zero(msghdr); + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + msghdr.msg_control = &control; + msghdr.msg_controllen = sizeof(control); + + n = recvmsg(socket_fd, &msghdr, 0); + if (n < 0) { + if (errno == EAGAIN || + errno == EINTR) + continue; + + r = -errno; + goto finish; + } + + cmsg_close_all(&msghdr); + + if (n <= 0) { + log_debug("Message too short"); + continue; + } + + if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) || + control.cmsghdr.cmsg_level != SOL_SOCKET || + control.cmsghdr.cmsg_type != SCM_CREDENTIALS || + control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) { + log_debug("Received message without credentials. Ignoring."); + continue; + } + + ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr); + if (ucred->uid != 0) { + log_debug("Got request from unprivileged user. Ignoring."); + continue; + } + + if (passphrase[0] == '+') { + /* An empty message refers to the empty password */ + if (n == 1) + l = strv_new("", NULL); + else + l = strv_parse_nulstr(passphrase+1, n-1); + memory_erase(passphrase, n); + if (!l) { + r = -ENOMEM; + goto finish; + } + + if (strv_length(l) <= 0) { + l = strv_free(l); + log_debug("Invalid packet"); + continue; + } + + break; + } + + if (passphrase[0] == '-') { + r = -ECANCELED; + goto finish; + } + + log_debug("Invalid packet"); + } + + if (keyname) + (void) add_to_keyring_and_log(keyname, flags, l); + + *ret = l; + l = NULL; + r = 0; + +finish: + if (socket_name) + (void) unlink(socket_name); + + (void) unlink(temp); + + if (final[0]) + (void) unlink(final); + + assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0); + return r; +} + +int ask_password_auto( + const char *message, + const char *icon, + const char *id, + const char *keyname, + usec_t until, + AskPasswordFlags flags, + char ***ret) { + + int r; + + assert(ret); + + if ((flags & ASK_PASSWORD_ACCEPT_CACHED) && keyname) { + r = ask_password_keyring(keyname, flags, ret); + if (r != -ENOKEY) + return r; + } + + if (!(flags & ASK_PASSWORD_NO_TTY) && isatty(STDIN_FILENO)) { + char *s = NULL, **l = NULL; + + r = ask_password_tty(message, keyname, until, flags, NULL, &s); + if (r < 0) + return r; + + r = strv_push(&l, s); + if (r < 0) { + string_erase(s); + free(s); + return -ENOMEM; + } + + *ret = l; + return 0; + } + + if (!(flags & ASK_PASSWORD_NO_AGENT)) + return ask_password_agent(message, icon, id, keyname, until, flags, ret); + + return -EUNATCH; +} diff --git a/src/libshared/ask-password-api.h b/src/libshared/ask-password-api.h new file mode 100644 index 0000000000..9d7f65130c --- /dev/null +++ b/src/libshared/ask-password-api.h @@ -0,0 +1,38 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "time-util.h" + +typedef enum AskPasswordFlags { + ASK_PASSWORD_ACCEPT_CACHED = 1, + ASK_PASSWORD_PUSH_CACHE = 2, + ASK_PASSWORD_ECHO = 4, /* show the password literally while reading, instead of "*" */ + ASK_PASSWORD_SILENT = 8, /* do no show any password at all while reading */ + ASK_PASSWORD_NO_TTY = 16, + ASK_PASSWORD_NO_AGENT = 32, +} AskPasswordFlags; + +int ask_password_tty(const char *message, const char *keyname, usec_t until, AskPasswordFlags flags, const char *flag_file, char **ret); +int ask_password_agent(const char *message, const char *icon, const char *id, const char *keyname, usec_t until, AskPasswordFlags flag, char ***ret); +int ask_password_keyring(const char *keyname, AskPasswordFlags flags, char ***ret); +int ask_password_auto(const char *message, const char *icon, const char *id, const char *keyname, usec_t until, AskPasswordFlags flag, char ***ret); diff --git a/src/libshared/base-filesystem.c b/src/libshared/base-filesystem.c new file mode 100644 index 0000000000..59a34a9d11 --- /dev/null +++ b/src/libshared/base-filesystem.c @@ -0,0 +1,129 @@ +/*** + 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 +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "base-filesystem.h" +#include "fd-util.h" +#include "log.h" +#include "macro.h" +#include "string-util.h" +#include "umask-util.h" +#include "user-util.h" +#include "util.h" + +typedef struct BaseFilesystem { + const char *dir; + mode_t mode; + const char *target; + const char *exists; + bool ignore_failure; +} BaseFilesystem; + +static const BaseFilesystem table[] = { + { "bin", 0, "usr/bin\0", NULL }, + { "lib", 0, "usr/lib\0", NULL }, + { "root", 0755, NULL, NULL, true }, + { "sbin", 0, "usr/sbin\0", NULL }, + { "usr", 0755, NULL, NULL }, + { "var", 0755, NULL, NULL }, + { "etc", 0755, NULL, NULL }, +#if defined(__i386__) || defined(__x86_64__) + { "lib64", 0, "usr/lib/x86_64-linux-gnu\0" + "usr/lib64\0", "ld-linux-x86-64.so.2" }, +#endif +}; + +int base_filesystem_create(const char *root, uid_t uid, gid_t gid) { + _cleanup_close_ int fd = -1; + unsigned i; + int r = 0; + + fd = open(root, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (fd < 0) + return log_error_errno(errno, "Failed to open root file system: %m"); + + for (i = 0; i < ELEMENTSOF(table); i ++) { + if (faccessat(fd, table[i].dir, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) + continue; + + if (table[i].target) { + const char *target = NULL, *s; + + /* check if one of the targets exists */ + NULSTR_FOREACH(s, table[i].target) { + if (faccessat(fd, s, F_OK, AT_SYMLINK_NOFOLLOW) < 0) + continue; + + /* check if a specific file exists at the target path */ + if (table[i].exists) { + _cleanup_free_ char *p = NULL; + + p = strjoin(s, "/", table[i].exists, NULL); + if (!p) + return log_oom(); + + if (faccessat(fd, p, F_OK, AT_SYMLINK_NOFOLLOW) < 0) + continue; + } + + target = s; + break; + } + + if (!target) + continue; + + r = symlinkat(target, fd, table[i].dir); + if (r < 0 && errno != EEXIST) + return log_error_errno(errno, "Failed to create symlink at %s/%s: %m", root, table[i].dir); + + if (uid != UID_INVALID || gid != UID_INVALID) { + if (fchownat(fd, table[i].dir, uid, gid, AT_SYMLINK_NOFOLLOW) < 0) + return log_error_errno(errno, "Failed to chown symlink at %s/%s: %m", root, table[i].dir); + } + + continue; + } + + RUN_WITH_UMASK(0000) + r = mkdirat(fd, table[i].dir, table[i].mode); + if (r < 0 && errno != EEXIST) { + log_full_errno(table[i].ignore_failure ? LOG_DEBUG : LOG_ERR, errno, + "Failed to create directory at %s/%s: %m", root, table[i].dir); + + if (!table[i].ignore_failure) + return -errno; + } + + if (uid != UID_INVALID || gid != UID_INVALID) { + if (fchownat(fd, table[i].dir, uid, gid, AT_SYMLINK_NOFOLLOW) < 0) + return log_error_errno(errno, "Failed to chown directory at %s/%s: %m", root, table[i].dir); + } + } + + return 0; +} diff --git a/src/libshared/base-filesystem.h b/src/libshared/base-filesystem.h new file mode 100644 index 0000000000..49599f0a60 --- /dev/null +++ b/src/libshared/base-filesystem.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + 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 + +int base_filesystem_create(const char *root, uid_t uid, gid_t gid); diff --git a/src/libshared/boot-timestamps.c b/src/libshared/boot-timestamps.c new file mode 100644 index 0000000000..7e0152761c --- /dev/null +++ b/src/libshared/boot-timestamps.c @@ -0,0 +1,64 @@ +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + Copyright 2013 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 "acpi-fpdt.h" +#include "boot-timestamps.h" +#include "efivars.h" +#include "macro.h" +#include "time-util.h" + +int boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader) { + usec_t x = 0, y = 0, a; + int r; + dual_timestamp _n; + + assert(firmware); + assert(loader); + + if (!n) { + dual_timestamp_get(&_n); + n = &_n; + } + + r = acpi_get_boot_usec(&x, &y); + if (r < 0) { + r = efi_loader_get_boot_usec(&x, &y); + if (r < 0) + return r; + } + + /* Let's convert this to timestamps where the firmware + * began/loader began working. To make this more confusing: + * since usec_t is unsigned and the kernel's monotonic clock + * begins at kernel initialization we'll actually initialize + * the monotonic timestamps here as negative of the actual + * value. */ + + firmware->monotonic = y; + loader->monotonic = y - x; + + a = n->monotonic + firmware->monotonic; + firmware->realtime = n->realtime > a ? n->realtime - a : 0; + + a = n->monotonic + loader->monotonic; + loader->realtime = n->realtime > a ? n->realtime - a : 0; + + return 0; +} diff --git a/src/libshared/boot-timestamps.h b/src/libshared/boot-timestamps.h new file mode 100644 index 0000000000..6f691026be --- /dev/null +++ b/src/libshared/boot-timestamps.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + Copyright 2013 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 + +int boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader); diff --git a/src/libshared/bus-unit-util.c b/src/libshared/bus-unit-util.c new file mode 100644 index 0000000000..f68c4a41ac --- /dev/null +++ b/src/libshared/bus-unit-util.c @@ -0,0 +1,1307 @@ +/*** + This file is part of systemd. + + Copyright 2016 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 "alloc-util.h" +#include "bus-internal.h" +#include "bus-unit-util.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "env-util.h" +#include "escape.h" +#include "hashmap.h" +#include "list.h" +#include "locale-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "rlimit-util.h" +#include "signal-util.h" +#include "string-util.h" +#include "syslog-util.h" +#include "terminal-util.h" +#include "utf8.h" +#include "util.h" + +int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u) { + assert(message); + assert(u); + + u->machine = NULL; + + return sd_bus_message_read( + message, + "(ssssssouso)", + &u->id, + &u->description, + &u->load_state, + &u->active_state, + &u->sub_state, + &u->following, + &u->unit_path, + &u->job_id, + &u->job_type, + &u->job_path); +} + +int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignment) { + const char *eq, *field; + int r, rl; + + assert(m); + assert(assignment); + + eq = strchr(assignment, '='); + if (!eq) { + log_error("Not an assignment: %s", assignment); + return -EINVAL; + } + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv"); + if (r < 0) + return bus_log_create_error(r); + + field = strndupa(assignment, eq - assignment); + eq++; + + if (streq(field, "CPUQuota")) { + + if (isempty(eq)) + r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", USEC_INFINITY); + else if (endswith(eq, "%")) { + double percent; + + if (sscanf(eq, "%lf%%", &percent) != 1 || percent <= 0) { + log_error("CPU quota '%s' invalid.", eq); + return -EINVAL; + } + + r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", (usec_t) percent * USEC_PER_SEC / 100); + } else { + log_error("CPU quota needs to be in percent."); + return -EINVAL; + } + + goto finish; + + } else if (streq(field, "EnvironmentFile")) { + + r = sd_bus_message_append(m, "sv", "EnvironmentFiles", "a(sb)", 1, + eq[0] == '-' ? eq + 1 : eq, + eq[0] == '-'); + goto finish; + + } else if (STR_IN_SET(field, "AccuracySec", "RandomizedDelaySec", "RuntimeMaxSec")) { + char *n; + usec_t t; + size_t l; + r = parse_sec(eq, &t); + if (r < 0) + return log_error_errno(r, "Failed to parse %s= parameter: %s", field, eq); + + l = strlen(field); + n = newa(char, l + 2); + if (!n) + return log_oom(); + + /* Change suffix Sec → USec */ + strcpy(mempcpy(n, field, l - 3), "USec"); + r = sd_bus_message_append(m, "sv", n, "t", t); + goto finish; + } + + r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field); + if (r < 0) + return bus_log_create_error(r); + + rl = rlimit_from_string(field); + if (rl >= 0) { + const char *sn; + struct rlimit l; + + r = rlimit_parse(rl, eq, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse resource limit: %s", eq); + + r = sd_bus_message_append(m, "v", "t", l.rlim_max); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv"); + if (r < 0) + return bus_log_create_error(r); + + sn = strjoina(field, "Soft"); + r = sd_bus_message_append(m, "sv", sn, "t", l.rlim_cur); + + } else if (STR_IN_SET(field, + "CPUAccounting", "MemoryAccounting", "IOAccounting", "BlockIOAccounting", "TasksAccounting", + "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies", + "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit", + "PrivateTmp", "PrivateDevices", "PrivateNetwork", "NoNewPrivileges", + "SyslogLevelPrefix", "Delegate", "RemainAfterElapse")) { + + r = parse_boolean(eq); + if (r < 0) + return log_error_errno(r, "Failed to parse boolean assignment %s.", assignment); + + r = sd_bus_message_append(m, "v", "b", r); + + } else if (streq(field, "MemoryLimit")) { + uint64_t bytes; + + if (isempty(eq) || streq(eq, "infinity")) + bytes = (uint64_t) -1; + else { + r = parse_size(eq, 1024, &bytes); + if (r < 0) { + log_error("Failed to parse bytes specification %s", assignment); + return -EINVAL; + } + } + + r = sd_bus_message_append(m, "v", "t", bytes); + + } else if (streq(field, "TasksMax")) { + uint64_t n; + + if (isempty(eq) || streq(eq, "infinity")) + n = (uint64_t) -1; + else { + r = safe_atou64(eq, &n); + if (r < 0) { + log_error("Failed to parse maximum tasks specification %s", assignment); + return -EINVAL; + } + } + + r = sd_bus_message_append(m, "v", "t", n); + + } else if (STR_IN_SET(field, "CPUShares", "StartupCPUShares")) { + uint64_t u; + + r = cg_cpu_shares_parse(eq, &u); + if (r < 0) { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "t", u); + + } else if (STR_IN_SET(field, "IOWeight", "StartupIOWeight")) { + uint64_t u; + + r = cg_weight_parse(eq, &u); + if (r < 0) { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "t", u); + + } else if (STR_IN_SET(field, "BlockIOWeight", "StartupBlockIOWeight")) { + uint64_t u; + + r = cg_blkio_weight_parse(eq, &u); + if (r < 0) { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "t", u); + + } else if (STR_IN_SET(field, + "User", "Group", "DevicePolicy", "KillMode", + "UtmpIdentifier", "UtmpMode", "PAMName", "TTYPath", + "StandardInput", "StandardOutput", "StandardError", + "Description", "Slice", "Type", "WorkingDirectory", + "RootDirectory", "SyslogIdentifier", "ProtectSystem", + "ProtectHome", "SELinuxContext")) + r = sd_bus_message_append(m, "v", "s", eq); + + else if (streq(field, "SyslogLevel")) { + int level; + + level = log_level_from_string(eq); + if (level < 0) { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "i", level); + + } else if (streq(field, "SyslogFacility")) { + int facility; + + facility = log_facility_unshifted_from_string(eq); + if (facility < 0) { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "i", facility); + + } else if (streq(field, "DeviceAllow")) { + + if (isempty(eq)) + r = sd_bus_message_append(m, "v", "a(ss)", 0); + else { + const char *path, *rwm, *e; + + e = strchr(eq, ' '); + if (e) { + path = strndupa(eq, e - eq); + rwm = e+1; + } else { + path = eq; + rwm = ""; + } + + if (!path_startswith(path, "/dev")) { + log_error("%s is not a device file in /dev.", path); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "a(ss)", 1, path, rwm); + } + + } else if (cgroup_io_limit_type_from_string(field) >= 0 || STR_IN_SET(field, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) { + + if (isempty(eq)) + r = sd_bus_message_append(m, "v", "a(st)", 0); + else { + const char *path, *bandwidth, *e; + uint64_t bytes; + + e = strchr(eq, ' '); + if (e) { + path = strndupa(eq, e - eq); + bandwidth = e+1; + } else { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + if (!path_startswith(path, "/dev")) { + log_error("%s is not a device file in /dev.", path); + return -EINVAL; + } + + if (streq(bandwidth, "max")) { + bytes = CGROUP_LIMIT_MAX; + } else { + r = parse_size(bandwidth, 1000, &bytes); + if (r < 0) { + log_error("Failed to parse byte value %s.", bandwidth); + return -EINVAL; + } + } + + r = sd_bus_message_append(m, "v", "a(st)", 1, path, bytes); + } + + } else if (STR_IN_SET(field, "IODeviceWeight", "BlockIODeviceWeight")) { + + if (isempty(eq)) + r = sd_bus_message_append(m, "v", "a(st)", 0); + else { + const char *path, *weight, *e; + uint64_t u; + + e = strchr(eq, ' '); + if (e) { + path = strndupa(eq, e - eq); + weight = e+1; + } else { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + if (!path_startswith(path, "/dev")) { + log_error("%s is not a device file in /dev.", path); + return -EINVAL; + } + + r = safe_atou64(weight, &u); + if (r < 0) { + log_error("Failed to parse %s value %s.", field, weight); + return -EINVAL; + } + r = sd_bus_message_append(m, "v", "a(st)", 1, path, u); + } + + } else if (streq(field, "Nice")) { + int32_t i; + + r = safe_atoi32(eq, &i); + if (r < 0) { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "i", i); + + } else if (STR_IN_SET(field, "Environment", "PassEnvironment")) { + const char *p; + + r = sd_bus_message_open_container(m, 'v', "as"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return bus_log_create_error(r); + + p = eq; + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE); + if (r < 0) { + log_error("Failed to parse Environment value %s", eq); + return -EINVAL; + } + if (r == 0) + break; + + if (streq(field, "Environment")) { + if (!env_assignment_is_valid(word)) { + log_error("Invalid environment assignment: %s", word); + return -EINVAL; + } + } else { /* PassEnvironment */ + if (!env_name_is_valid(word)) { + log_error("Invalid environment variable name: %s", word); + return -EINVAL; + } + } + + r = sd_bus_message_append_basic(m, 's', word); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + + } else if (streq(field, "KillSignal")) { + int sig; + + sig = signal_from_string_try_harder(eq); + if (sig < 0) { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "i", sig); + + } else if (streq(field, "TimerSlackNSec")) { + nsec_t n; + + r = parse_nsec(eq, &n); + if (r < 0) { + log_error("Failed to parse %s value %s", field, eq); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "t", n); + } else if (streq(field, "OOMScoreAdjust")) { + int oa; + + r = safe_atoi(eq, &oa); + if (r < 0) { + log_error("Failed to parse %s value %s", field, eq); + return -EINVAL; + } + + if (!oom_score_adjust_is_valid(oa)) { + log_error("OOM score adjust value out of range"); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "i", oa); + } else if (STR_IN_SET(field, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories")) { + const char *p; + + r = sd_bus_message_open_container(m, 'v', "as"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return bus_log_create_error(r); + + p = eq; + + for (;;) { + _cleanup_free_ char *word = NULL; + int offset; + + r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES); + if (r < 0) { + log_error("Failed to parse %s value %s", field, eq); + return -EINVAL; + } + if (r == 0) + break; + + if (!utf8_is_valid(word)) { + log_error("Failed to parse %s value %s", field, eq); + return -EINVAL; + } + + offset = word[0] == '-'; + if (!path_is_absolute(word + offset)) { + log_error("Failed to parse %s value %s", field, eq); + return -EINVAL; + } + + path_kill_slashes(word + offset); + + r = sd_bus_message_append_basic(m, 's', word); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + + } else if (streq(field, "RuntimeDirectory")) { + const char *p; + + r = sd_bus_message_open_container(m, 'v', "as"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return bus_log_create_error(r); + + p = eq; + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES); + if (r < 0) + return log_error_errno(r, "Failed to parse %s value %s", field, eq); + + if (r == 0) + break; + + r = sd_bus_message_append_basic(m, 's', word); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + + } else { + log_error("Unknown assignment %s.", assignment); + return -EINVAL; + } + +finish: + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + return 0; +} + +typedef struct BusWaitForJobs { + sd_bus *bus; + Set *jobs; + + char *name; + char *result; + + sd_bus_slot *slot_job_removed; + sd_bus_slot *slot_disconnected; +} BusWaitForJobs; + +static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) { + assert(m); + + log_error("Warning! D-Bus connection terminated."); + sd_bus_close(sd_bus_message_get_bus(m)); + + return 0; +} + +static int match_job_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { + const char *path, *unit, *result; + BusWaitForJobs *d = userdata; + uint32_t id; + char *found; + int r; + + assert(m); + assert(d); + + r = sd_bus_message_read(m, "uoss", &id, &path, &unit, &result); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + found = set_remove(d->jobs, (char*) path); + if (!found) + return 0; + + free(found); + + if (!isempty(result)) + d->result = strdup(result); + + if (!isempty(unit)) + d->name = strdup(unit); + + return 0; +} + +void bus_wait_for_jobs_free(BusWaitForJobs *d) { + if (!d) + return; + + set_free_free(d->jobs); + + sd_bus_slot_unref(d->slot_disconnected); + sd_bus_slot_unref(d->slot_job_removed); + + sd_bus_unref(d->bus); + + free(d->name); + free(d->result); + + free(d); +} + +int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret) { + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *d = NULL; + int r; + + assert(bus); + assert(ret); + + d = new0(BusWaitForJobs, 1); + if (!d) + return -ENOMEM; + + d->bus = sd_bus_ref(bus); + + /* When we are a bus client we match by sender. Direct + * connections OTOH have no initialized sender field, and + * hence we ignore the sender then */ + r = sd_bus_add_match( + bus, + &d->slot_job_removed, + bus->bus_client ? + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='JobRemoved'," + "path='/org/freedesktop/systemd1'" : + "type='signal'," + "interface='org.freedesktop.systemd1.Manager'," + "member='JobRemoved'," + "path='/org/freedesktop/systemd1'", + match_job_removed, d); + if (r < 0) + return r; + + r = sd_bus_add_match( + bus, + &d->slot_disconnected, + "type='signal'," + "sender='org.freedesktop.DBus.Local'," + "interface='org.freedesktop.DBus.Local'," + "member='Disconnected'", + match_disconnected, d); + if (r < 0) + return r; + + *ret = d; + d = NULL; + + return 0; +} + +static int bus_process_wait(sd_bus *bus) { + int r; + + for (;;) { + r = sd_bus_process(bus, NULL); + if (r < 0) + return r; + if (r > 0) + return 0; + + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) + return r; + } +} + +static int bus_job_get_service_result(BusWaitForJobs *d, char **result) { + _cleanup_free_ char *dbus_path = NULL; + + assert(d); + assert(d->name); + assert(result); + + dbus_path = unit_dbus_path_from_name(d->name); + if (!dbus_path) + return -ENOMEM; + + return sd_bus_get_property_string(d->bus, + "org.freedesktop.systemd1", + dbus_path, + "org.freedesktop.systemd1.Service", + "Result", + NULL, + result); +} + +static const struct { + const char *result, *explanation; +} explanations [] = { + { "resources", "of unavailable resources or another system error" }, + { "timeout", "a timeout was exceeded" }, + { "exit-code", "the control process exited with error code" }, + { "signal", "a fatal signal was delivered to the control process" }, + { "core-dump", "a fatal signal was delivered causing the control process to dump core" }, + { "watchdog", "the service failed to send watchdog ping" }, + { "start-limit", "start of the service was attempted too often" } +}; + +static void log_job_error_with_service_result(const char* service, const char *result, const char* const* extra_args) { + _cleanup_free_ char *service_shell_quoted = NULL; + const char *systemctl = "systemctl", *journalctl = "journalctl"; + + assert(service); + + service_shell_quoted = shell_maybe_quote(service); + + if (extra_args && extra_args[1]) { + _cleanup_free_ char *t; + + t = strv_join((char**) extra_args, " "); + systemctl = strjoina("systemctl ", t ? : ""); + journalctl = strjoina("journalctl ", t ? : ""); + } + + if (!isempty(result)) { + unsigned i; + + for (i = 0; i < ELEMENTSOF(explanations); ++i) + if (streq(result, explanations[i].result)) + break; + + if (i < ELEMENTSOF(explanations)) { + log_error("Job for %s failed because %s.\n" + "See \"%s status %s\" and \"%s -xe\" for details.\n", + service, + explanations[i].explanation, + systemctl, + service_shell_quoted ?: "", + journalctl); + goto finish; + } + } + + log_error("Job for %s failed.\n" + "See \"%s status %s\" and \"%s -xe\" for details.\n", + service, + systemctl, + service_shell_quoted ?: "", + journalctl); + +finish: + /* For some results maybe additional explanation is required */ + if (streq_ptr(result, "start-limit")) + log_info("To force a start use \"%1$s reset-failed %2$s\"\n" + "followed by \"%1$s start %2$s\" again.", + systemctl, + service_shell_quoted ?: ""); +} + +static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const* extra_args) { + int r = 0; + + assert(d->result); + + if (!quiet) { + if (streq(d->result, "canceled")) + log_error("Job for %s canceled.", strna(d->name)); + else if (streq(d->result, "timeout")) + log_error("Job for %s timed out.", strna(d->name)); + else if (streq(d->result, "dependency")) + log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d->name)); + else if (streq(d->result, "invalid")) + log_error("%s is not active, cannot reload.", strna(d->name)); + else if (streq(d->result, "assert")) + log_error("Assertion failed on job for %s.", strna(d->name)); + else if (streq(d->result, "unsupported")) + log_error("Operation on or unit type of %s not supported on this system.", strna(d->name)); + else if (!streq(d->result, "done") && !streq(d->result, "skipped")) { + if (d->name) { + int q; + _cleanup_free_ char *result = NULL; + + q = bus_job_get_service_result(d, &result); + if (q < 0) + log_debug_errno(q, "Failed to get Result property of service %s: %m", d->name); + + log_job_error_with_service_result(d->name, result, extra_args); + } else + log_error("Job failed. See \"journalctl -xe\" for details."); + } + } + + if (streq(d->result, "canceled")) + r = -ECANCELED; + else if (streq(d->result, "timeout")) + r = -ETIME; + else if (streq(d->result, "dependency")) + r = -EIO; + else if (streq(d->result, "invalid")) + r = -ENOEXEC; + else if (streq(d->result, "assert")) + r = -EPROTO; + else if (streq(d->result, "unsupported")) + r = -EOPNOTSUPP; + else if (!streq(d->result, "done") && !streq(d->result, "skipped")) + r = -EIO; + + return r; +} + +int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char* const* extra_args) { + int r = 0; + + assert(d); + + while (!set_isempty(d->jobs)) { + int q; + + q = bus_process_wait(d->bus); + if (q < 0) + return log_error_errno(q, "Failed to wait for response: %m"); + + if (d->result) { + q = check_wait_response(d, quiet, extra_args); + /* Return the first error as it is most likely to be + * meaningful. */ + if (q < 0 && r == 0) + r = q; + + log_debug_errno(q, "Got result %s/%m for job %s", strna(d->result), strna(d->name)); + } + + d->name = mfree(d->name); + d->result = mfree(d->result); + } + + return r; +} + +int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path) { + int r; + + assert(d); + + r = set_ensure_allocated(&d->jobs, &string_hash_ops); + if (r < 0) + return r; + + return set_put_strdup(d->jobs, path); +} + +int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet) { + int r; + + r = bus_wait_for_jobs_add(d, path); + if (r < 0) + return log_oom(); + + return bus_wait_for_jobs(d, quiet, NULL); +} + +int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, UnitFileChange **changes, unsigned *n_changes) { + const char *type, *path, *source; + int r; + + /* changes is dereferenced when calling unit_file_dump_changes() later, + * so we have to make sure this is not NULL. */ + assert(changes); + assert(n_changes); + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sss)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(sss)", &type, &path, &source)) > 0) { + /* We expect only "success" changes to be sent over the bus. + Hence, reject anything negative. */ + UnitFileChangeType ch = unit_file_change_type_from_string(type); + + if (ch < 0) { + log_notice("Manager reported unknown change type \"%s\" for path \"%s\", ignoring.", type, path); + continue; + } + + r = unit_file_changes_add(changes, n_changes, ch, path, source); + if (r < 0) + return r; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + unit_file_dump_changes(0, NULL, *changes, *n_changes, false); + return 0; +} + +struct CGroupInfo { + char *cgroup_path; + bool is_const; /* If false, cgroup_path should be free()'d */ + + Hashmap *pids; /* PID → process name */ + bool done; + + struct CGroupInfo *parent; + LIST_FIELDS(struct CGroupInfo, siblings); + LIST_HEAD(struct CGroupInfo, children); + size_t n_children; +}; + +static bool IS_ROOT(const char *p) { + return isempty(p) || streq(p, "/"); +} + +static int add_cgroup(Hashmap *cgroups, const char *path, bool is_const, struct CGroupInfo **ret) { + struct CGroupInfo *parent = NULL, *cg; + int r; + + assert(cgroups); + assert(ret); + + if (IS_ROOT(path)) + path = "/"; + + cg = hashmap_get(cgroups, path); + if (cg) { + *ret = cg; + return 0; + } + + if (!IS_ROOT(path)) { + const char *e, *pp; + + e = strrchr(path, '/'); + if (!e) + return -EINVAL; + + pp = strndupa(path, e - path); + if (!pp) + return -ENOMEM; + + r = add_cgroup(cgroups, pp, false, &parent); + if (r < 0) + return r; + } + + cg = new0(struct CGroupInfo, 1); + if (!cg) + return -ENOMEM; + + if (is_const) + cg->cgroup_path = (char*) path; + else { + cg->cgroup_path = strdup(path); + if (!cg->cgroup_path) { + free(cg); + return -ENOMEM; + } + } + + cg->is_const = is_const; + cg->parent = parent; + + r = hashmap_put(cgroups, cg->cgroup_path, cg); + if (r < 0) { + if (!is_const) + free(cg->cgroup_path); + free(cg); + return r; + } + + if (parent) { + LIST_PREPEND(siblings, parent->children, cg); + parent->n_children++; + } + + *ret = cg; + return 1; +} + +static int add_process( + Hashmap *cgroups, + const char *path, + pid_t pid, + const char *name) { + + struct CGroupInfo *cg; + int r; + + assert(cgroups); + assert(name); + assert(pid > 0); + + r = add_cgroup(cgroups, path, true, &cg); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&cg->pids, &trivial_hash_ops); + if (r < 0) + return r; + + return hashmap_put(cg->pids, PID_TO_PTR(pid), (void*) name); +} + +static void remove_cgroup(Hashmap *cgroups, struct CGroupInfo *cg) { + assert(cgroups); + assert(cg); + + while (cg->children) + remove_cgroup(cgroups, cg->children); + + hashmap_remove(cgroups, cg->cgroup_path); + + if (!cg->is_const) + free(cg->cgroup_path); + + hashmap_free(cg->pids); + + if (cg->parent) + LIST_REMOVE(siblings, cg->parent->children, cg); + + free(cg); +} + +static int cgroup_info_compare_func(const void *a, const void *b) { + const struct CGroupInfo *x = *(const struct CGroupInfo* const*) a, *y = *(const struct CGroupInfo* const*) b; + + assert(x); + assert(y); + + return strcmp(x->cgroup_path, y->cgroup_path); +} + +static int dump_processes( + Hashmap *cgroups, + const char *cgroup_path, + const char *prefix, + unsigned n_columns, + OutputFlags flags) { + + struct CGroupInfo *cg; + int r; + + assert(prefix); + + if (IS_ROOT(cgroup_path)) + cgroup_path = "/"; + + cg = hashmap_get(cgroups, cgroup_path); + if (!cg) + return 0; + + if (!hashmap_isempty(cg->pids)) { + const char *name; + size_t n = 0, i; + pid_t *pids; + void *pidp; + Iterator j; + int width; + + /* Order processes by their PID */ + pids = newa(pid_t, hashmap_size(cg->pids)); + + HASHMAP_FOREACH_KEY(name, pidp, cg->pids, j) + pids[n++] = PTR_TO_PID(pidp); + + assert(n == hashmap_size(cg->pids)); + qsort_safe(pids, n, sizeof(pid_t), pid_compare_func); + + width = DECIMAL_STR_WIDTH(pids[n-1]); + + for (i = 0; i < n; i++) { + _cleanup_free_ char *e = NULL; + const char *special; + bool more; + + name = hashmap_get(cg->pids, PID_TO_PTR(pids[i])); + assert(name); + + if (n_columns != 0) { + unsigned k; + + k = MAX(LESS_BY(n_columns, 2U + width + 1U), 20U); + + e = ellipsize(name, k, 100); + if (e) + name = e; + } + + more = i+1 < n || cg->children; + special = special_glyph(more ? TREE_BRANCH : TREE_RIGHT); + + fprintf(stdout, "%s%s%*"PID_PRI" %s\n", + prefix, + special, + width, pids[i], + name); + } + } + + if (cg->children) { + struct CGroupInfo **children, *child; + size_t n = 0, i; + + /* Order subcgroups by their name */ + children = newa(struct CGroupInfo*, cg->n_children); + LIST_FOREACH(siblings, child, cg->children) + children[n++] = child; + assert(n == cg->n_children); + qsort_safe(children, n, sizeof(struct CGroupInfo*), cgroup_info_compare_func); + + n_columns = MAX(LESS_BY(n_columns, 2U), 20U); + + for (i = 0; i < n; i++) { + _cleanup_free_ char *pp = NULL; + const char *name, *special; + bool more; + + child = children[i]; + + name = strrchr(child->cgroup_path, '/'); + if (!name) + return -EINVAL; + name++; + + more = i+1 < n; + special = special_glyph(more ? TREE_BRANCH : TREE_RIGHT); + + fputs(prefix, stdout); + fputs(special, stdout); + fputs(name, stdout); + fputc('\n', stdout); + + special = special_glyph(more ? TREE_VERTICAL : TREE_SPACE); + + pp = strappend(prefix, special); + if (!pp) + return -ENOMEM; + + r = dump_processes(cgroups, child->cgroup_path, pp, n_columns, flags); + if (r < 0) + return r; + } + } + + cg->done = true; + return 0; +} + +static int dump_extra_processes( + Hashmap *cgroups, + const char *prefix, + unsigned n_columns, + OutputFlags flags) { + + _cleanup_free_ pid_t *pids = NULL; + _cleanup_hashmap_free_ Hashmap *names = NULL; + struct CGroupInfo *cg; + size_t n_allocated = 0, n = 0, k; + Iterator i; + int width, r; + + /* Prints the extra processes, i.e. those that are in cgroups we haven't displayed yet. We show them as + * combined, sorted, linear list. */ + + HASHMAP_FOREACH(cg, cgroups, i) { + const char *name; + void *pidp; + Iterator j; + + if (cg->done) + continue; + + if (hashmap_isempty(cg->pids)) + continue; + + r = hashmap_ensure_allocated(&names, &trivial_hash_ops); + if (r < 0) + return r; + + if (!GREEDY_REALLOC(pids, n_allocated, n + hashmap_size(cg->pids))) + return -ENOMEM; + + HASHMAP_FOREACH_KEY(name, pidp, cg->pids, j) { + pids[n++] = PTR_TO_PID(pidp); + + r = hashmap_put(names, pidp, (void*) name); + if (r < 0) + return r; + } + } + + if (n == 0) + return 0; + + qsort_safe(pids, n, sizeof(pid_t), pid_compare_func); + width = DECIMAL_STR_WIDTH(pids[n-1]); + + for (k = 0; k < n; k++) { + _cleanup_free_ char *e = NULL; + const char *name; + + name = hashmap_get(names, PID_TO_PTR(pids[k])); + assert(name); + + if (n_columns != 0) { + unsigned z; + + z = MAX(LESS_BY(n_columns, 2U + width + 1U), 20U); + + e = ellipsize(name, z, 100); + if (e) + name = e; + } + + fprintf(stdout, "%s%s %*" PID_PRI " %s\n", + prefix, + special_glyph(TRIANGULAR_BULLET), + width, pids[k], + name); + } + + return 0; +} + +int unit_show_processes( + sd_bus *bus, + const char *unit, + const char *cgroup_path, + const char *prefix, + unsigned n_columns, + OutputFlags flags, + sd_bus_error *error) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Hashmap *cgroups = NULL; + struct CGroupInfo *cg; + int r; + + assert(bus); + assert(unit); + + if (flags & OUTPUT_FULL_WIDTH) + n_columns = 0; + else if (n_columns <= 0) + n_columns = columns(); + + prefix = strempty(prefix); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnitProcesses", + error, + &reply, + "s", + unit); + if (r < 0) + return r; + + cgroups = hashmap_new(&string_hash_ops); + if (!cgroups) + return -ENOMEM; + + r = sd_bus_message_enter_container(reply, 'a', "(sus)"); + if (r < 0) + goto finish; + + for (;;) { + const char *path = NULL, *name = NULL; + uint32_t pid; + + r = sd_bus_message_read(reply, "(sus)", &path, &pid, &name); + if (r < 0) + goto finish; + if (r == 0) + break; + + r = add_process(cgroups, path, pid, name); + if (r < 0) + goto finish; + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + goto finish; + + r = dump_processes(cgroups, cgroup_path, prefix, n_columns, flags); + if (r < 0) + goto finish; + + r = dump_extra_processes(cgroups, prefix, n_columns, flags); + +finish: + while ((cg = hashmap_first(cgroups))) + remove_cgroup(cgroups, cg); + + hashmap_free(cgroups); + + return r; +} diff --git a/src/libshared/bus-unit-util.h b/src/libshared/bus-unit-util.h new file mode 100644 index 0000000000..8327189a63 --- /dev/null +++ b/src/libshared/bus-unit-util.h @@ -0,0 +1,57 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 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 "output-mode.h" +#include "install.h" + +typedef struct UnitInfo { + const char *machine; + const char *id; + const char *description; + const char *load_state; + const char *active_state; + const char *sub_state; + const char *following; + const char *unit_path; + uint32_t job_id; + const char *job_type; + const char *job_path; +} UnitInfo; + +int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u); + +int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignment); + +typedef struct BusWaitForJobs BusWaitForJobs; + +int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret); +void bus_wait_for_jobs_free(BusWaitForJobs *d); +int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path); +int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char* const* extra_args); +int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet); + +DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForJobs*, bus_wait_for_jobs_free); + +int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, UnitFileChange **changes, unsigned *n_changes); + +int unit_show_processes(sd_bus *bus, const char *unit, const char *cgroup_path, const char *prefix, unsigned n_columns, OutputFlags flags, sd_bus_error *error); diff --git a/src/libshared/bus-util.c b/src/libshared/bus-util.c new file mode 100644 index 0000000000..62b5585e84 --- /dev/null +++ b/src/libshared/bus-util.c @@ -0,0 +1,1571 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "bus-internal.h" +#include "bus-label.h" +#include "bus-message.h" +#include "bus-util.h" +#include "def.h" +#include "escape.h" +#include "fd-util.h" +#include "missing.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "rlimit-util.h" +#include "stdio-util.h" +#include "strv.h" +#include "user-util.h" + +static int name_owner_change_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + sd_event *e = userdata; + + assert(m); + assert(e); + + sd_bus_close(sd_bus_message_get_bus(m)); + sd_event_exit(e, 0); + + return 1; +} + +int bus_async_unregister_and_exit(sd_event *e, sd_bus *bus, const char *name) { + _cleanup_free_ char *match = NULL; + const char *unique; + int r; + + assert(e); + assert(bus); + assert(name); + + /* We unregister the name here and then wait for the + * NameOwnerChanged signal for this event to arrive before we + * quit. We do this in order to make sure that any queued + * requests are still processed before we really exit. */ + + r = sd_bus_get_unique_name(bus, &unique); + if (r < 0) + return r; + + r = asprintf(&match, + "sender='org.freedesktop.DBus'," + "type='signal'," + "interface='org.freedesktop.DBus'," + "member='NameOwnerChanged'," + "path='/org/freedesktop/DBus'," + "arg0='%s'," + "arg1='%s'," + "arg2=''", name, unique); + if (r < 0) + return -ENOMEM; + + r = sd_bus_add_match(bus, NULL, match, name_owner_change_callback, e); + if (r < 0) + return r; + + r = sd_bus_release_name(bus, name); + if (r < 0) + return r; + + return 0; +} + +int bus_event_loop_with_idle( + sd_event *e, + sd_bus *bus, + const char *name, + usec_t timeout, + check_idle_t check_idle, + void *userdata) { + bool exiting = false; + int r, code; + + assert(e); + assert(bus); + assert(name); + + for (;;) { + bool idle; + + r = sd_event_get_state(e); + if (r < 0) + return r; + if (r == SD_EVENT_FINISHED) + break; + + if (check_idle) + idle = check_idle(userdata); + else + idle = true; + + r = sd_event_run(e, exiting || !idle ? (uint64_t) -1 : timeout); + if (r < 0) + return r; + + if (r == 0 && !exiting && idle) { + + r = sd_bus_try_close(bus); + if (r == -EBUSY) + continue; + + /* Fallback for dbus1 connections: we + * unregister the name and wait for the + * response to come through for it */ + if (r == -EOPNOTSUPP) { + + /* Inform the service manager that we + * are going down, so that it will + * queue all further start requests, + * instead of assuming we are already + * running. */ + sd_notify(false, "STOPPING=1"); + + r = bus_async_unregister_and_exit(e, bus, name); + if (r < 0) + return r; + + exiting = true; + continue; + } + + if (r < 0) + return r; + + sd_event_exit(e, 0); + break; + } + } + + r = sd_event_get_exit_code(e, &code); + if (r < 0) + return r; + + return code; +} + +int bus_name_has_owner(sd_bus *c, const char *name, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *rep = NULL; + int r, has_owner = 0; + + assert(c); + assert(name); + + r = sd_bus_call_method(c, + "org.freedesktop.DBus", + "/org/freedesktop/dbus", + "org.freedesktop.DBus", + "NameHasOwner", + error, + &rep, + "s", + name); + if (r < 0) + return r; + + r = sd_bus_message_read_basic(rep, 'b', &has_owner); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + return has_owner; +} + +static int check_good_user(sd_bus_message *m, uid_t good_user) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + uid_t sender_uid; + int r; + + assert(m); + + if (good_user == UID_INVALID) + return 0; + + r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &creds); + if (r < 0) + return r; + + /* Don't trust augmented credentials for authorization */ + assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_EUID) == 0, -EPERM); + + r = sd_bus_creds_get_euid(creds, &sender_uid); + if (r < 0) + return r; + + return sender_uid == good_user; +} + +int bus_test_polkit( + sd_bus_message *call, + int capability, + const char *action, + const char **details, + uid_t good_user, + bool *_challenge, + sd_bus_error *e) { + + int r; + + assert(call); + assert(action); + + /* Tests non-interactively! */ + + r = check_good_user(call, good_user); + if (r != 0) + return r; + + r = sd_bus_query_sender_privilege(call, capability); + if (r < 0) + return r; + else if (r > 0) + return 1; +#ifdef ENABLE_POLKIT + else { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *request = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int authorized = false, challenge = false; + const char *sender, **k, **v; + + sender = sd_bus_message_get_sender(call); + if (!sender) + return -EBADMSG; + + r = sd_bus_message_new_method_call( + call->bus, + &request, + "org.freedesktop.PolicyKit1", + "/org/freedesktop/PolicyKit1/Authority", + "org.freedesktop.PolicyKit1.Authority", + "CheckAuthorization"); + if (r < 0) + return r; + + r = sd_bus_message_append( + request, + "(sa{sv})s", + "system-bus-name", 1, "name", "s", sender, + action); + if (r < 0) + return r; + + r = sd_bus_message_open_container(request, 'a', "{ss}"); + if (r < 0) + return r; + + STRV_FOREACH_PAIR(k, v, details) { + r = sd_bus_message_append(request, "{ss}", *k, *v); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(request); + if (r < 0) + return r; + + r = sd_bus_message_append(request, "us", 0, NULL); + if (r < 0) + return r; + + r = sd_bus_call(call->bus, request, 0, e, &reply); + if (r < 0) { + /* Treat no PK available as access denied */ + if (sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN)) { + sd_bus_error_free(e); + return -EACCES; + } + + return r; + } + + r = sd_bus_message_enter_container(reply, 'r', "bba{ss}"); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "bb", &authorized, &challenge); + if (r < 0) + return r; + + if (authorized) + return 1; + + if (_challenge) { + *_challenge = challenge; + return 0; + } + } +#endif + + return -EACCES; +} + +#ifdef ENABLE_POLKIT + +typedef struct AsyncPolkitQuery { + sd_bus_message *request, *reply; + sd_bus_message_handler_t callback; + void *userdata; + sd_bus_slot *slot; + Hashmap *registry; +} AsyncPolkitQuery; + +static void async_polkit_query_free(AsyncPolkitQuery *q) { + + if (!q) + return; + + sd_bus_slot_unref(q->slot); + + if (q->registry && q->request) + hashmap_remove(q->registry, q->request); + + sd_bus_message_unref(q->request); + sd_bus_message_unref(q->reply); + + free(q); +} + +static int async_polkit_callback(sd_bus_message *reply, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; + AsyncPolkitQuery *q = userdata; + int r; + + assert(reply); + assert(q); + + q->slot = sd_bus_slot_unref(q->slot); + q->reply = sd_bus_message_ref(reply); + + r = sd_bus_message_rewind(q->request, true); + if (r < 0) { + r = sd_bus_reply_method_errno(q->request, r, NULL); + goto finish; + } + + r = q->callback(q->request, q->userdata, &error_buffer); + r = bus_maybe_reply_error(q->request, r, &error_buffer); + +finish: + async_polkit_query_free(q); + + return r; +} + +#endif + +int bus_verify_polkit_async( + sd_bus_message *call, + int capability, + const char *action, + const char **details, + bool interactive, + uid_t good_user, + Hashmap **registry, + sd_bus_error *error) { + +#ifdef ENABLE_POLKIT + _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL; + AsyncPolkitQuery *q; + const char *sender, **k, **v; + sd_bus_message_handler_t callback; + void *userdata; + int c; +#endif + int r; + + assert(call); + assert(action); + assert(registry); + + r = check_good_user(call, good_user); + if (r != 0) + return r; + +#ifdef ENABLE_POLKIT + q = hashmap_get(*registry, call); + if (q) { + int authorized, challenge; + + /* This is the second invocation of this function, and + * there's already a response from polkit, let's + * process it */ + assert(q->reply); + + if (sd_bus_message_is_method_error(q->reply, NULL)) { + const sd_bus_error *e; + + /* Copy error from polkit reply */ + e = sd_bus_message_get_error(q->reply); + sd_bus_error_copy(error, e); + + /* Treat no PK available as access denied */ + if (sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN)) + return -EACCES; + + return -sd_bus_error_get_errno(e); + } + + r = sd_bus_message_enter_container(q->reply, 'r', "bba{ss}"); + if (r >= 0) + r = sd_bus_message_read(q->reply, "bb", &authorized, &challenge); + + if (r < 0) + return r; + + if (authorized) + return 1; + + if (challenge) + return sd_bus_error_set(error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED, "Interactive authentication required."); + + return -EACCES; + } +#endif + + r = sd_bus_query_sender_privilege(call, capability); + if (r < 0) + return r; + else if (r > 0) + return 1; + +#ifdef ENABLE_POLKIT + if (sd_bus_get_current_message(call->bus) != call) + return -EINVAL; + + callback = sd_bus_get_current_handler(call->bus); + if (!callback) + return -EINVAL; + + userdata = sd_bus_get_current_userdata(call->bus); + + sender = sd_bus_message_get_sender(call); + if (!sender) + return -EBADMSG; + + c = sd_bus_message_get_allow_interactive_authorization(call); + if (c < 0) + return c; + if (c > 0) + interactive = true; + + r = hashmap_ensure_allocated(registry, NULL); + if (r < 0) + return r; + + r = sd_bus_message_new_method_call( + call->bus, + &pk, + "org.freedesktop.PolicyKit1", + "/org/freedesktop/PolicyKit1/Authority", + "org.freedesktop.PolicyKit1.Authority", + "CheckAuthorization"); + if (r < 0) + return r; + + r = sd_bus_message_append( + pk, + "(sa{sv})s", + "system-bus-name", 1, "name", "s", sender, + action); + if (r < 0) + return r; + + r = sd_bus_message_open_container(pk, 'a', "{ss}"); + if (r < 0) + return r; + + STRV_FOREACH_PAIR(k, v, details) { + r = sd_bus_message_append(pk, "{ss}", *k, *v); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(pk); + if (r < 0) + return r; + + r = sd_bus_message_append(pk, "us", !!interactive, NULL); + if (r < 0) + return r; + + q = new0(AsyncPolkitQuery, 1); + if (!q) + return -ENOMEM; + + q->request = sd_bus_message_ref(call); + q->callback = callback; + q->userdata = userdata; + + r = hashmap_put(*registry, call, q); + if (r < 0) { + async_polkit_query_free(q); + return r; + } + + q->registry = *registry; + + r = sd_bus_call_async(call->bus, &q->slot, pk, async_polkit_callback, q, 0); + if (r < 0) { + async_polkit_query_free(q); + return r; + } + + return 0; +#endif + + return -EACCES; +} + +void bus_verify_polkit_async_registry_free(Hashmap *registry) { +#ifdef ENABLE_POLKIT + AsyncPolkitQuery *q; + + while ((q = hashmap_steal_first(registry))) + async_polkit_query_free(q); + + hashmap_free(registry); +#endif +} + +int bus_check_peercred(sd_bus *c) { + struct ucred ucred; + socklen_t l; + int fd; + + assert(c); + + fd = sd_bus_get_fd(c); + if (fd < 0) + return fd; + + l = sizeof(struct ucred); + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l) < 0) + return -errno; + + if (l != sizeof(struct ucred)) + return -E2BIG; + + if (ucred.uid != 0 && ucred.uid != geteuid()) + return -EPERM; + + return 1; +} + +int bus_connect_system_systemd(sd_bus **_bus) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + int r; + + assert(_bus); + + if (geteuid() != 0) + return sd_bus_default_system(_bus); + + /* If we are root and kdbus is not available, then let's talk + * directly to the system instance, instead of going via the + * bus */ + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + r = sd_bus_set_address(bus, KERNEL_SYSTEM_BUS_ADDRESS); + if (r < 0) + return r; + + bus->bus_client = true; + + r = sd_bus_start(bus); + if (r >= 0) { + *_bus = bus; + bus = NULL; + return 0; + } + + bus = sd_bus_unref(bus); + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + r = sd_bus_set_address(bus, "unix:path=/run/systemd/private"); + if (r < 0) + return r; + + r = sd_bus_start(bus); + if (r < 0) + return sd_bus_default_system(_bus); + + r = bus_check_peercred(bus); + if (r < 0) + return r; + + *_bus = bus; + bus = NULL; + + return 0; +} + +int bus_connect_user_systemd(sd_bus **_bus) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_free_ char *ee = NULL; + const char *e; + int r; + + /* Try via kdbus first, and then directly */ + + assert(_bus); + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + if (asprintf(&bus->address, KERNEL_USER_BUS_ADDRESS_FMT, getuid()) < 0) + return -ENOMEM; + + bus->bus_client = true; + + r = sd_bus_start(bus); + if (r >= 0) { + *_bus = bus; + bus = NULL; + return 0; + } + + bus = sd_bus_unref(bus); + + e = secure_getenv("XDG_RUNTIME_DIR"); + if (!e) + return sd_bus_default_user(_bus); + + ee = bus_address_escape(e); + if (!ee) + return -ENOMEM; + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + bus->address = strjoin("unix:path=", ee, "/systemd/private", NULL); + if (!bus->address) + return -ENOMEM; + + r = sd_bus_start(bus); + if (r < 0) + return sd_bus_default_user(_bus); + + r = bus_check_peercred(bus); + if (r < 0) + return r; + + *_bus = bus; + bus = NULL; + + return 0; +} + +#define print_property(name, fmt, ...) \ + do { \ + if (value) \ + printf(fmt "\n", __VA_ARGS__); \ + else \ + printf("%s=" fmt "\n", name, __VA_ARGS__); \ + } while(0) + +int bus_print_property(const char *name, sd_bus_message *property, bool value, bool all) { + char type; + const char *contents; + int r; + + assert(name); + assert(property); + + r = sd_bus_message_peek_type(property, &type, &contents); + if (r < 0) + return r; + + switch (type) { + + case SD_BUS_TYPE_STRING: { + const char *s; + + r = sd_bus_message_read_basic(property, type, &s); + if (r < 0) + return r; + + if (all || !isempty(s)) { + _cleanup_free_ char *escaped = NULL; + + escaped = xescape(s, "\n"); + if (!escaped) + return -ENOMEM; + + print_property(name, "%s", escaped); + } + + return 1; + } + + case SD_BUS_TYPE_BOOLEAN: { + int b; + + r = sd_bus_message_read_basic(property, type, &b); + if (r < 0) + return r; + + print_property(name, "%s", yes_no(b)); + + return 1; + } + + case SD_BUS_TYPE_UINT64: { + uint64_t u; + + r = sd_bus_message_read_basic(property, type, &u); + if (r < 0) + return r; + + /* Yes, heuristics! But we can change this check + * should it turn out to not be sufficient */ + + if (endswith(name, "Timestamp")) { + char timestamp[FORMAT_TIMESTAMP_MAX], *t; + + t = format_timestamp(timestamp, sizeof(timestamp), u); + if (t || all) + print_property(name, "%s", strempty(t)); + + } else if (strstr(name, "USec")) { + char timespan[FORMAT_TIMESPAN_MAX]; + + print_property(name, "%s", format_timespan(timespan, sizeof(timespan), u, 0)); + } else + print_property(name, "%"PRIu64, u); + + return 1; + } + + case SD_BUS_TYPE_INT64: { + int64_t i; + + r = sd_bus_message_read_basic(property, type, &i); + if (r < 0) + return r; + + print_property(name, "%"PRIi64, i); + + return 1; + } + + case SD_BUS_TYPE_UINT32: { + uint32_t u; + + r = sd_bus_message_read_basic(property, type, &u); + if (r < 0) + return r; + + if (strstr(name, "UMask") || strstr(name, "Mode")) + print_property(name, "%04o", u); + else + print_property(name, "%"PRIu32, u); + + return 1; + } + + case SD_BUS_TYPE_INT32: { + int32_t i; + + r = sd_bus_message_read_basic(property, type, &i); + if (r < 0) + return r; + + print_property(name, "%"PRIi32, i); + return 1; + } + + case SD_BUS_TYPE_DOUBLE: { + double d; + + r = sd_bus_message_read_basic(property, type, &d); + if (r < 0) + return r; + + print_property(name, "%g", d); + return 1; + } + + case SD_BUS_TYPE_ARRAY: + if (streq(contents, "s")) { + bool first = true; + const char *str; + + r = sd_bus_message_enter_container(property, SD_BUS_TYPE_ARRAY, contents); + if (r < 0) + return r; + + while ((r = sd_bus_message_read_basic(property, SD_BUS_TYPE_STRING, &str)) > 0) { + _cleanup_free_ char *escaped = NULL; + + if (first && !value) + printf("%s=", name); + + escaped = xescape(str, "\n "); + if (!escaped) + return -ENOMEM; + + printf("%s%s", first ? "" : " ", escaped); + + first = false; + } + if (r < 0) + return r; + + if (first && all && !value) + printf("%s=", name); + if (!first || all) + puts(""); + + r = sd_bus_message_exit_container(property); + if (r < 0) + return r; + + return 1; + + } else if (streq(contents, "y")) { + const uint8_t *u; + size_t n; + + r = sd_bus_message_read_array(property, SD_BUS_TYPE_BYTE, (const void**) &u, &n); + if (r < 0) + return r; + + if (all || n > 0) { + unsigned int i; + + if (!value) + printf("%s=", name); + + for (i = 0; i < n; i++) + printf("%02x", u[i]); + + puts(""); + } + + return 1; + + } else if (streq(contents, "u")) { + uint32_t *u; + size_t n; + + r = sd_bus_message_read_array(property, SD_BUS_TYPE_UINT32, (const void**) &u, &n); + if (r < 0) + return r; + + if (all || n > 0) { + unsigned int i; + + if (!value) + printf("%s=", name); + + for (i = 0; i < n; i++) + printf("%08x", u[i]); + + puts(""); + } + + return 1; + } + + break; + } + + return 0; +} + +int bus_print_all_properties(sd_bus *bus, const char *dest, const char *path, char **filter, bool value, bool all) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(path); + + r = sd_bus_call_method(bus, + dest, + path, + "org.freedesktop.DBus.Properties", + "GetAll", + &error, + &reply, + "s", ""); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sv}"); + if (r < 0) + return r; + + while ((r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { + const char *name; + const char *contents; + + r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_STRING, &name); + if (r < 0) + return r; + + if (!filter || strv_find(filter, name)) { + r = sd_bus_message_peek_type(reply, NULL, &contents); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_VARIANT, contents); + if (r < 0) + return r; + + r = bus_print_property(name, reply, value, all); + if (r < 0) + return r; + if (r == 0) { + if (all) + printf("%s=[unprintable]\n", name); + /* skip what we didn't read */ + r = sd_bus_message_skip(reply, contents); + if (r < 0) + return r; + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + } else { + r = sd_bus_message_skip(reply, "v"); + if (r < 0) + return r; + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + + return 0; +} + +int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { + sd_id128_t *p = userdata; + const void *v; + size_t n; + int r; + + r = sd_bus_message_read_array(m, SD_BUS_TYPE_BYTE, &v, &n); + if (r < 0) + return r; + + if (n == 0) + *p = SD_ID128_NULL; + else if (n == 16) + memcpy((*p).bytes, v, n); + else + return -EINVAL; + + return 0; +} + +static int map_basic(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { + char type; + int r; + + r = sd_bus_message_peek_type(m, &type, NULL); + if (r < 0) + return r; + + switch (type) { + case SD_BUS_TYPE_STRING: { + const char *s; + char **p = userdata; + + r = sd_bus_message_read_basic(m, type, &s); + if (r < 0) + break; + + if (isempty(s)) + break; + + r = free_and_strdup(p, s); + break; + } + + case SD_BUS_TYPE_ARRAY: { + _cleanup_strv_free_ char **l = NULL; + char ***p = userdata; + + r = bus_message_read_strv_extend(m, &l); + if (r < 0) + break; + + strv_free(*p); + *p = l; + l = NULL; + + break; + } + + case SD_BUS_TYPE_BOOLEAN: { + unsigned b; + bool *p = userdata; + + r = sd_bus_message_read_basic(m, type, &b); + if (r < 0) + break; + + *p = b; + + break; + } + + case SD_BUS_TYPE_UINT32: { + uint32_t u; + uint32_t *p = userdata; + + r = sd_bus_message_read_basic(m, type, &u); + if (r < 0) + break; + + *p = u; + + break; + } + + case SD_BUS_TYPE_UINT64: { + uint64_t t; + uint64_t *p = userdata; + + r = sd_bus_message_read_basic(m, type, &t); + if (r < 0) + break; + + *p = t; + + break; + } + + default: + break; + } + + return r; +} + +int bus_message_map_all_properties( + sd_bus_message *m, + const struct bus_properties_map *map, + void *userdata) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(m); + assert(map); + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}"); + if (r < 0) + return r; + + while ((r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { + const struct bus_properties_map *prop; + const char *member; + const char *contents; + void *v; + unsigned i; + + r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &member); + if (r < 0) + return r; + + for (i = 0, prop = NULL; map[i].member; i++) + if (streq(map[i].member, member)) { + prop = &map[i]; + break; + } + + if (prop) { + r = sd_bus_message_peek_type(m, NULL, &contents); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, contents); + if (r < 0) + return r; + + v = (uint8_t *)userdata + prop->offset; + if (map[i].set) + r = prop->set(sd_bus_message_get_bus(m), member, m, &error, v); + else + r = map_basic(sd_bus_message_get_bus(m), member, m, &error, v); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + } else { + r = sd_bus_message_skip(m, "v"); + if (r < 0) + return r; + } + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + } + if (r < 0) + return r; + + return sd_bus_message_exit_container(m); +} + +int bus_message_map_properties_changed( + sd_bus_message *m, + const struct bus_properties_map *map, + void *userdata) { + + const char *member; + int r, invalidated, i; + + assert(m); + assert(map); + + r = bus_message_map_all_properties(m, map, userdata); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "s"); + if (r < 0) + return r; + + invalidated = 0; + while ((r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &member)) > 0) + for (i = 0; map[i].member; i++) + if (streq(map[i].member, member)) { + ++invalidated; + break; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + return invalidated; +} + +int bus_map_all_properties( + sd_bus *bus, + const char *destination, + const char *path, + const struct bus_properties_map *map, + void *userdata) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(destination); + assert(path); + assert(map); + + r = sd_bus_call_method( + bus, + destination, + path, + "org.freedesktop.DBus.Properties", + "GetAll", + &error, + &m, + "s", ""); + if (r < 0) + return r; + + return bus_message_map_all_properties(m, map, userdata); +} + +int bus_connect_transport(BusTransport transport, const char *host, bool user, sd_bus **bus) { + int r; + + assert(transport >= 0); + assert(transport < _BUS_TRANSPORT_MAX); + assert(bus); + + assert_return((transport == BUS_TRANSPORT_LOCAL) == !host, -EINVAL); + assert_return(transport == BUS_TRANSPORT_LOCAL || !user, -EOPNOTSUPP); + + switch (transport) { + + case BUS_TRANSPORT_LOCAL: + if (user) + r = sd_bus_default_user(bus); + else + r = sd_bus_default_system(bus); + + break; + + case BUS_TRANSPORT_REMOTE: + r = sd_bus_open_system_remote(bus, host); + break; + + case BUS_TRANSPORT_MACHINE: + r = sd_bus_open_system_machine(bus, host); + break; + + default: + assert_not_reached("Hmm, unknown transport type."); + } + + return r; +} + +int bus_connect_transport_systemd(BusTransport transport, const char *host, bool user, sd_bus **bus) { + int r; + + assert(transport >= 0); + assert(transport < _BUS_TRANSPORT_MAX); + assert(bus); + + assert_return((transport == BUS_TRANSPORT_LOCAL) == !host, -EINVAL); + assert_return(transport == BUS_TRANSPORT_LOCAL || !user, -EOPNOTSUPP); + + switch (transport) { + + case BUS_TRANSPORT_LOCAL: + if (user) + r = bus_connect_user_systemd(bus); + else + r = bus_connect_system_systemd(bus); + + break; + + case BUS_TRANSPORT_REMOTE: + r = sd_bus_open_system_remote(bus, host); + break; + + case BUS_TRANSPORT_MACHINE: + r = sd_bus_open_system_machine(bus, host); + break; + + default: + assert_not_reached("Hmm, unknown transport type."); + } + + return r; +} + +int bus_property_get_bool( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + int b = *(bool*) userdata; + + return sd_bus_message_append_basic(reply, 'b', &b); +} + +#if __SIZEOF_SIZE_T__ != 8 +int bus_property_get_size( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + uint64_t sz = *(size_t*) userdata; + + return sd_bus_message_append_basic(reply, 't', &sz); +} +#endif + +#if __SIZEOF_LONG__ != 8 +int bus_property_get_long( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + int64_t l = *(long*) userdata; + + return sd_bus_message_append_basic(reply, 'x', &l); +} + +int bus_property_get_ulong( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + uint64_t ul = *(unsigned long*) userdata; + + return sd_bus_message_append_basic(reply, 't', &ul); +} +#endif + +int bus_log_parse_error(int r) { + return log_error_errno(r, "Failed to parse bus message: %m"); +} + +int bus_log_create_error(int r) { + return log_error_errno(r, "Failed to create bus message: %m"); +} + +/** + * bus_path_encode_unique() - encode unique object path + * @b: bus connection or NULL + * @prefix: object path prefix + * @sender_id: unique-name of client, or NULL + * @external_id: external ID to be chosen by client, or NULL + * @ret_path: storage for encoded object path pointer + * + * Whenever we provide a bus API that allows clients to create and manage + * server-side objects, we need to provide a unique name for these objects. If + * we let the server choose the name, we suffer from a race condition: If a + * client creates an object asynchronously, it cannot destroy that object until + * it received the method reply. It cannot know the name of the new object, + * thus, it cannot destroy it. Furthermore, it enforces a round-trip. + * + * Therefore, many APIs allow the client to choose the unique name for newly + * created objects. There're two problems to solve, though: + * 1) Object names are usually defined via dbus object paths, which are + * usually globally namespaced. Therefore, multiple clients must be able + * to choose unique object names without interference. + * 2) If multiple libraries share the same bus connection, they must be + * able to choose unique object names without interference. + * The first problem is solved easily by prefixing a name with the + * unique-bus-name of a connection. The server side must enforce this and + * reject any other name. The second problem is solved by providing unique + * suffixes from within sd-bus. + * + * This helper allows clients to create unique object-paths. It uses the + * template '/prefix/sender_id/external_id' and returns the new path in + * @ret_path (must be freed by the caller). + * If @sender_id is NULL, the unique-name of @b is used. If @external_id is + * NULL, this function allocates a unique suffix via @b (by requesting a new + * cookie). If both @sender_id and @external_id are given, @b can be passed as + * NULL. + * + * Returns: 0 on success, negative error code on failure. + */ +int bus_path_encode_unique(sd_bus *b, const char *prefix, const char *sender_id, const char *external_id, char **ret_path) { + _cleanup_free_ char *sender_label = NULL, *external_label = NULL; + char external_buf[DECIMAL_STR_MAX(uint64_t)], *p; + int r; + + assert_return(b || (sender_id && external_id), -EINVAL); + assert_return(object_path_is_valid(prefix), -EINVAL); + assert_return(ret_path, -EINVAL); + + if (!sender_id) { + r = sd_bus_get_unique_name(b, &sender_id); + if (r < 0) + return r; + } + + if (!external_id) { + xsprintf(external_buf, "%"PRIu64, ++b->cookie); + external_id = external_buf; + } + + sender_label = bus_label_escape(sender_id); + if (!sender_label) + return -ENOMEM; + + external_label = bus_label_escape(external_id); + if (!external_label) + return -ENOMEM; + + p = strjoin(prefix, "/", sender_label, "/", external_label, NULL); + if (!p) + return -ENOMEM; + + *ret_path = p; + return 0; +} + +/** + * bus_path_decode_unique() - decode unique object path + * @path: object path to decode + * @prefix: object path prefix + * @ret_sender: output parameter for sender-id label + * @ret_external: output parameter for external-id label + * + * This does the reverse of bus_path_encode_unique() (see its description for + * details). Both trailing labels, sender-id and external-id, are unescaped and + * returned in the given output parameters (the caller must free them). + * + * Note that this function returns 0 if the path does not match the template + * (see bus_path_encode_unique()), 1 if it matched. + * + * Returns: Negative error code on failure, 0 if the given object path does not + * match the template (return parameters are set to NULL), 1 if it was + * parsed successfully (return parameters contain allocated labels). + */ +int bus_path_decode_unique(const char *path, const char *prefix, char **ret_sender, char **ret_external) { + const char *p, *q; + char *sender, *external; + + assert(object_path_is_valid(path)); + assert(object_path_is_valid(prefix)); + assert(ret_sender); + assert(ret_external); + + p = object_path_startswith(path, prefix); + if (!p) { + *ret_sender = NULL; + *ret_external = NULL; + return 0; + } + + q = strchr(p, '/'); + if (!q) { + *ret_sender = NULL; + *ret_external = NULL; + return 0; + } + + sender = bus_label_unescape_n(p, q - p); + external = bus_label_unescape(q + 1); + if (!sender || !external) { + free(sender); + free(external); + return -ENOMEM; + } + + *ret_sender = sender; + *ret_external = external; + return 1; +} + +bool is_kdbus_wanted(void) { + _cleanup_free_ char *value = NULL; +#ifdef ENABLE_KDBUS + const bool configured = true; +#else + const bool configured = false; +#endif + + int r; + + if (get_proc_cmdline_key("kdbus", NULL) > 0) + return true; + + r = get_proc_cmdline_key("kdbus=", &value); + if (r <= 0) + return configured; + + return parse_boolean(value) == 1; +} + +bool is_kdbus_available(void) { + _cleanup_close_ int fd = -1; + struct kdbus_cmd cmd = { .size = sizeof(cmd), .flags = KDBUS_FLAG_NEGOTIATE }; + + if (!is_kdbus_wanted()) + return false; + + fd = open("/sys/fs/kdbus/control", O_RDWR | O_CLOEXEC | O_NONBLOCK | O_NOCTTY); + if (fd < 0) + return false; + + return ioctl(fd, KDBUS_CMD_BUS_MAKE, &cmd) >= 0; +} + +int bus_property_get_rlimit( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + struct rlimit *rl; + uint64_t u; + rlim_t x; + const char *is_soft; + + assert(bus); + assert(reply); + assert(userdata); + + is_soft = endswith(property, "Soft"); + rl = *(struct rlimit**) userdata; + if (rl) + x = is_soft ? rl->rlim_cur : rl->rlim_max; + else { + struct rlimit buf = {}; + int z; + const char *s; + + s = is_soft ? strndupa(property, is_soft - property) : property; + + z = rlimit_from_string(strstr(s, "Limit")); + assert(z >= 0); + + getrlimit(z, &buf); + x = is_soft ? buf.rlim_cur : buf.rlim_max; + } + + /* rlim_t might have different sizes, let's map + * RLIMIT_INFINITY to (uint64_t) -1, so that it is the same on + * all archs */ + u = x == RLIM_INFINITY ? (uint64_t) -1 : (uint64_t) x; + + return sd_bus_message_append(reply, "t", u); +} diff --git a/src/libshared/bus-util.h b/src/libshared/bus-util.h new file mode 100644 index 0000000000..f2b46530ba --- /dev/null +++ b/src/libshared/bus-util.h @@ -0,0 +1,163 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 +#include +#include + +#include +#include + +#include "hashmap.h" +#include "macro.h" +#include "string-util.h" + +typedef enum BusTransport { + BUS_TRANSPORT_LOCAL, + BUS_TRANSPORT_REMOTE, + BUS_TRANSPORT_MACHINE, + _BUS_TRANSPORT_MAX, + _BUS_TRANSPORT_INVALID = -1 +} BusTransport; + +typedef int (*bus_property_set_t) (sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata); + +struct bus_properties_map { + const char *member; + const char *signature; + bus_property_set_t set; + size_t offset; +}; + +int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata); + +int bus_message_map_all_properties(sd_bus_message *m, const struct bus_properties_map *map, void *userdata); +int bus_message_map_properties_changed(sd_bus_message *m, const struct bus_properties_map *map, void *userdata); +int bus_map_all_properties(sd_bus *bus, const char *destination, const char *path, const struct bus_properties_map *map, void *userdata); + +int bus_async_unregister_and_exit(sd_event *e, sd_bus *bus, const char *name); + +typedef bool (*check_idle_t)(void *userdata); + +int bus_event_loop_with_idle(sd_event *e, sd_bus *bus, const char *name, usec_t timeout, check_idle_t check_idle, void *userdata); + +int bus_name_has_owner(sd_bus *c, const char *name, sd_bus_error *error); + +int bus_check_peercred(sd_bus *c); + +int bus_test_polkit(sd_bus_message *call, int capability, const char *action, const char **details, uid_t good_user, bool *_challenge, sd_bus_error *e); + +int bus_verify_polkit_async(sd_bus_message *call, int capability, const char *action, const char **details, bool interactive, uid_t good_user, Hashmap **registry, sd_bus_error *error); +void bus_verify_polkit_async_registry_free(Hashmap *registry); + +int bus_connect_system_systemd(sd_bus **_bus); +int bus_connect_user_systemd(sd_bus **_bus); + +int bus_connect_transport(BusTransport transport, const char *host, bool user, sd_bus **bus); +int bus_connect_transport_systemd(BusTransport transport, const char *host, bool user, sd_bus **bus); + +int bus_print_property(const char *name, sd_bus_message *property, bool value, bool all); +int bus_print_all_properties(sd_bus *bus, const char *dest, const char *path, char **filter, bool value, bool all); + +int bus_property_get_bool(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); + +#define bus_property_get_usec ((sd_bus_property_get_t) NULL) +#define bus_property_set_usec ((sd_bus_property_set_t) NULL) + +assert_cc(sizeof(int) == sizeof(int32_t)); +#define bus_property_get_int ((sd_bus_property_get_t) NULL) + +assert_cc(sizeof(unsigned) == sizeof(unsigned)); +#define bus_property_get_unsigned ((sd_bus_property_get_t) NULL) + +/* On 64bit machines we can use the default serializer for size_t and + * friends, otherwise we need to cast this manually */ +#if __SIZEOF_SIZE_T__ == 8 +#define bus_property_get_size ((sd_bus_property_get_t) NULL) +#else +int bus_property_get_size(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); +#endif + +#if __SIZEOF_LONG__ == 8 +#define bus_property_get_long ((sd_bus_property_get_t) NULL) +#define bus_property_get_ulong ((sd_bus_property_get_t) NULL) +#else +int bus_property_get_long(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); +int bus_property_get_ulong(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); +#endif + +/* uid_t and friends on Linux 32 bit. This means we can just use the + * default serializer for 32bit unsigned, for serializing it, and map + * it to NULL here */ +assert_cc(sizeof(uid_t) == sizeof(uint32_t)); +#define bus_property_get_uid ((sd_bus_property_get_t) NULL) + +assert_cc(sizeof(gid_t) == sizeof(uint32_t)); +#define bus_property_get_gid ((sd_bus_property_get_t) NULL) + +assert_cc(sizeof(pid_t) == sizeof(uint32_t)); +#define bus_property_get_pid ((sd_bus_property_get_t) NULL) + +assert_cc(sizeof(mode_t) == sizeof(uint32_t)); +#define bus_property_get_mode ((sd_bus_property_get_t) NULL) + +int bus_log_parse_error(int r); +int bus_log_create_error(int r); + +#define BUS_DEFINE_PROPERTY_GET_ENUM(function, name, type) \ + int function(sd_bus *bus, \ + const char *path, \ + const char *interface, \ + const char *property, \ + sd_bus_message *reply, \ + void *userdata, \ + sd_bus_error *error) { \ + \ + const char *value; \ + type *field = userdata; \ + int r; \ + \ + assert(bus); \ + assert(reply); \ + assert(field); \ + \ + value = strempty(name##_to_string(*field)); \ + \ + r = sd_bus_message_append_basic(reply, 's', value); \ + if (r < 0) \ + return r; \ + \ + return 1; \ + } \ + struct __useless_struct_to_allow_trailing_semicolon__ + +#define BUS_PROPERTY_DUAL_TIMESTAMP(name, offset, flags) \ + SD_BUS_PROPERTY(name, "t", bus_property_get_usec, (offset) + offsetof(struct dual_timestamp, realtime), (flags)), \ + SD_BUS_PROPERTY(name "Monotonic", "t", bus_property_get_usec, (offset) + offsetof(struct dual_timestamp, monotonic), (flags)) + +int bus_path_encode_unique(sd_bus *b, const char *prefix, const char *sender_id, const char *external_id, char **ret_path); +int bus_path_decode_unique(const char *path, const char *prefix, char **ret_sender, char **ret_external); + +bool is_kdbus_wanted(void); +bool is_kdbus_available(void); + +int bus_property_get_rlimit(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); diff --git a/src/libshared/cgroup-show.c b/src/libshared/cgroup-show.c new file mode 100644 index 0000000000..3e451db715 --- /dev/null +++ b/src/libshared/cgroup-show.c @@ -0,0 +1,312 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include + +#include "alloc-util.h" +#include "cgroup-show.h" +#include "cgroup-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "locale-util.h" +#include "macro.h" +#include "output-mode.h" +#include "path-util.h" +#include "process-util.h" +#include "string-util.h" +#include "terminal-util.h" + +static void show_pid_array( + pid_t pids[], + unsigned n_pids, + const char *prefix, + unsigned n_columns, + bool extra, + bool more, + OutputFlags flags) { + + unsigned i, j, pid_width; + + if (n_pids == 0) + return; + + qsort(pids, n_pids, sizeof(pid_t), pid_compare_func); + + /* Filter duplicates */ + for (j = 0, i = 1; i < n_pids; i++) { + if (pids[i] == pids[j]) + continue; + pids[++j] = pids[i]; + } + n_pids = j + 1; + pid_width = DECIMAL_STR_WIDTH(pids[j]); + + if (flags & OUTPUT_FULL_WIDTH) + n_columns = 0; + else { + if (n_columns > pid_width+2) + n_columns -= pid_width+2; + else + n_columns = 20; + } + for (i = 0; i < n_pids; i++) { + _cleanup_free_ char *t = NULL; + + get_process_cmdline(pids[i], n_columns, true, &t); + + if (extra) + printf("%s%s ", prefix, special_glyph(TRIANGULAR_BULLET)); + else + printf("%s%s", prefix, special_glyph(((more || i < n_pids-1) ? TREE_BRANCH : TREE_RIGHT))); + + printf("%*"PID_PRI" %s\n", pid_width, pids[i], strna(t)); + } +} + +static int show_cgroup_one_by_path( + const char *path, + const char *prefix, + unsigned n_columns, + bool more, + OutputFlags flags) { + + char *fn; + _cleanup_fclose_ FILE *f = NULL; + size_t n = 0, n_allocated = 0; + _cleanup_free_ pid_t *pids = NULL; + _cleanup_free_ char *p = NULL; + pid_t pid; + int r; + + r = cg_mangle_path(path, &p); + if (r < 0) + return r; + + fn = strjoina(p, "/cgroup.procs"); + f = fopen(fn, "re"); + if (!f) + return -errno; + + while ((r = cg_read_pid(f, &pid)) > 0) { + + if (!(flags & OUTPUT_KERNEL_THREADS) && is_kernel_thread(pid) > 0) + continue; + + if (!GREEDY_REALLOC(pids, n_allocated, n + 1)) + return -ENOMEM; + + assert(n < n_allocated); + pids[n++] = pid; + } + + if (r < 0) + return r; + + show_pid_array(pids, n, prefix, n_columns, false, more, flags); + + return 0; +} + +int show_cgroup_by_path( + const char *path, + const char *prefix, + unsigned n_columns, + OutputFlags flags) { + + _cleanup_free_ char *fn = NULL, *p1 = NULL, *last = NULL, *p2 = NULL; + _cleanup_closedir_ DIR *d = NULL; + char *gn = NULL; + bool shown_pids = false; + int r; + + assert(path); + + if (n_columns <= 0) + n_columns = columns(); + + prefix = strempty(prefix); + + r = cg_mangle_path(path, &fn); + if (r < 0) + return r; + + d = opendir(fn); + if (!d) + return -errno; + + while ((r = cg_read_subgroup(d, &gn)) > 0) { + _cleanup_free_ char *k = NULL; + + k = strjoin(fn, "/", gn, NULL); + free(gn); + if (!k) + return -ENOMEM; + + if (!(flags & OUTPUT_SHOW_ALL) && cg_is_empty_recursive(NULL, k) > 0) + continue; + + if (!shown_pids) { + show_cgroup_one_by_path(path, prefix, n_columns, true, flags); + shown_pids = true; + } + + if (last) { + printf("%s%s%s\n", prefix, special_glyph(TREE_BRANCH), cg_unescape(basename(last))); + + if (!p1) { + p1 = strappend(prefix, special_glyph(TREE_VERTICAL)); + if (!p1) + return -ENOMEM; + } + + show_cgroup_by_path(last, p1, n_columns-2, flags); + free(last); + } + + last = k; + k = NULL; + } + + if (r < 0) + return r; + + if (!shown_pids) + show_cgroup_one_by_path(path, prefix, n_columns, !!last, flags); + + if (last) { + printf("%s%s%s\n", prefix, special_glyph(TREE_RIGHT), cg_unescape(basename(last))); + + if (!p2) { + p2 = strappend(prefix, " "); + if (!p2) + return -ENOMEM; + } + + show_cgroup_by_path(last, p2, n_columns-2, flags); + } + + return 0; +} + +int show_cgroup(const char *controller, + const char *path, + const char *prefix, + unsigned n_columns, + OutputFlags flags) { + _cleanup_free_ char *p = NULL; + int r; + + assert(path); + + r = cg_get_path(controller, path, NULL, &p); + if (r < 0) + return r; + + return show_cgroup_by_path(p, prefix, n_columns, flags); +} + +static int show_extra_pids( + const char *controller, + const char *path, + const char *prefix, + unsigned n_columns, + const pid_t pids[], + unsigned n_pids, + OutputFlags flags) { + + _cleanup_free_ pid_t *copy = NULL; + unsigned i, j; + int r; + + assert(path); + + if (n_pids <= 0) + return 0; + + if (n_columns <= 0) + n_columns = columns(); + + prefix = strempty(prefix); + + copy = new(pid_t, n_pids); + if (!copy) + return -ENOMEM; + + for (i = 0, j = 0; i < n_pids; i++) { + _cleanup_free_ char *k = NULL; + + r = cg_pid_get_path(controller, pids[i], &k); + if (r < 0) + return r; + + if (path_startswith(k, path)) + continue; + + copy[j++] = pids[i]; + } + + show_pid_array(copy, j, prefix, n_columns, true, false, flags); + + return 0; +} + +int show_cgroup_and_extra( + const char *controller, + const char *path, + const char *prefix, + unsigned n_columns, + const pid_t extra_pids[], + unsigned n_extra_pids, + OutputFlags flags) { + + int r; + + assert(path); + + r = show_cgroup(controller, path, prefix, n_columns, flags); + if (r < 0) + return r; + + return show_extra_pids(controller, path, prefix, n_columns, extra_pids, n_extra_pids, flags); +} + +int show_cgroup_and_extra_by_spec( + const char *spec, + const char *prefix, + unsigned n_columns, + const pid_t extra_pids[], + unsigned n_extra_pids, + OutputFlags flags) { + + _cleanup_free_ char *controller = NULL, *path = NULL; + int r; + + assert(spec); + + r = cg_split_spec(spec, &controller, &path); + if (r < 0) + return r; + + return show_cgroup_and_extra(controller, path, prefix, n_columns, extra_pids, n_extra_pids, flags); +} diff --git a/src/libshared/cgroup-show.h b/src/libshared/cgroup-show.h new file mode 100644 index 0000000000..5c1d6e6d98 --- /dev/null +++ b/src/libshared/cgroup-show.h @@ -0,0 +1,32 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 + +#include "logs-show.h" +#include "output-mode.h" + +int show_cgroup_by_path(const char *path, const char *prefix, unsigned columns, OutputFlags flags); +int show_cgroup(const char *controller, const char *path, const char *prefix, unsigned columns, OutputFlags flags); + +int show_cgroup_and_extra_by_spec(const char *spec, const char *prefix, unsigned n_columns, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags); +int show_cgroup_and_extra(const char *controller, const char *path, const char *prefix, unsigned n_columns, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags); diff --git a/src/libshared/clean-ipc.c b/src/libshared/clean-ipc.c new file mode 100644 index 0000000000..a3ac7aeb82 --- /dev/null +++ b/src/libshared/clean-ipc.c @@ -0,0 +1,365 @@ +/*** + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clean-ipc.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "log.h" +#include "macro.h" +#include "string-util.h" +#include "strv.h" + +static int clean_sysvipc_shm(uid_t delete_uid) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + bool first = true; + int ret = 0; + + f = fopen("/proc/sysvipc/shm", "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + return log_warning_errno(errno, "Failed to open /proc/sysvipc/shm: %m"); + } + + FOREACH_LINE(line, f, goto fail) { + unsigned n_attached; + pid_t cpid, lpid; + uid_t uid, cuid; + gid_t gid, cgid; + int shmid; + + if (first) { + first = false; + continue; + } + + truncate_nl(line); + + if (sscanf(line, "%*i %i %*o %*u " PID_FMT " " PID_FMT " %u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT, + &shmid, &cpid, &lpid, &n_attached, &uid, &gid, &cuid, &cgid) != 8) + continue; + + if (n_attached > 0) + continue; + + if (uid != delete_uid) + continue; + + if (shmctl(shmid, IPC_RMID, NULL) < 0) { + + /* Ignore entries that are already deleted */ + if (errno == EIDRM || errno == EINVAL) + continue; + + ret = log_warning_errno(errno, + "Failed to remove SysV shared memory segment %i: %m", + shmid); + } + } + + return ret; + +fail: + return log_warning_errno(errno, "Failed to read /proc/sysvipc/shm: %m"); +} + +static int clean_sysvipc_sem(uid_t delete_uid) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + bool first = true; + int ret = 0; + + f = fopen("/proc/sysvipc/sem", "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + return log_warning_errno(errno, "Failed to open /proc/sysvipc/sem: %m"); + } + + FOREACH_LINE(line, f, goto fail) { + uid_t uid, cuid; + gid_t gid, cgid; + int semid; + + if (first) { + first = false; + continue; + } + + truncate_nl(line); + + if (sscanf(line, "%*i %i %*o %*u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT, + &semid, &uid, &gid, &cuid, &cgid) != 5) + continue; + + if (uid != delete_uid) + continue; + + if (semctl(semid, 0, IPC_RMID) < 0) { + + /* Ignore entries that are already deleted */ + if (errno == EIDRM || errno == EINVAL) + continue; + + ret = log_warning_errno(errno, + "Failed to remove SysV semaphores object %i: %m", + semid); + } + } + + return ret; + +fail: + return log_warning_errno(errno, "Failed to read /proc/sysvipc/sem: %m"); +} + +static int clean_sysvipc_msg(uid_t delete_uid) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + bool first = true; + int ret = 0; + + f = fopen("/proc/sysvipc/msg", "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + return log_warning_errno(errno, "Failed to open /proc/sysvipc/msg: %m"); + } + + FOREACH_LINE(line, f, goto fail) { + uid_t uid, cuid; + gid_t gid, cgid; + pid_t cpid, lpid; + int msgid; + + if (first) { + first = false; + continue; + } + + truncate_nl(line); + + if (sscanf(line, "%*i %i %*o %*u %*u " PID_FMT " " PID_FMT " " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT, + &msgid, &cpid, &lpid, &uid, &gid, &cuid, &cgid) != 7) + continue; + + if (uid != delete_uid) + continue; + + if (msgctl(msgid, IPC_RMID, NULL) < 0) { + + /* Ignore entries that are already deleted */ + if (errno == EIDRM || errno == EINVAL) + continue; + + ret = log_warning_errno(errno, + "Failed to remove SysV message queue %i: %m", + msgid); + } + } + + return ret; + +fail: + return log_warning_errno(errno, "Failed to read /proc/sysvipc/msg: %m"); +} + +static int clean_posix_shm_internal(DIR *dir, uid_t uid) { + struct dirent *de; + int ret = 0, r; + + assert(dir); + + FOREACH_DIRENT(de, dir, goto fail) { + struct stat st; + + if (STR_IN_SET(de->d_name, "..", ".")) + continue; + + if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { + if (errno == ENOENT) + continue; + + log_warning_errno(errno, "Failed to stat() POSIX shared memory segment %s: %m", de->d_name); + ret = -errno; + continue; + } + + if (st.st_uid != uid) + continue; + + if (S_ISDIR(st.st_mode)) { + _cleanup_closedir_ DIR *kid; + + kid = xopendirat(dirfd(dir), de->d_name, O_NOFOLLOW|O_NOATIME); + if (!kid) { + if (errno != ENOENT) { + log_warning_errno(errno, "Failed to enter shared memory directory %s: %m", de->d_name); + ret = -errno; + } + } else { + r = clean_posix_shm_internal(kid, uid); + if (r < 0) + ret = r; + } + + if (unlinkat(dirfd(dir), de->d_name, AT_REMOVEDIR) < 0) { + + if (errno == ENOENT) + continue; + + log_warning_errno(errno, "Failed to remove POSIX shared memory directory %s: %m", de->d_name); + ret = -errno; + } + } else { + + if (unlinkat(dirfd(dir), de->d_name, 0) < 0) { + + if (errno == ENOENT) + continue; + + log_warning_errno(errno, "Failed to remove POSIX shared memory segment %s: %m", de->d_name); + ret = -errno; + } + } + } + + return ret; + +fail: + log_warning_errno(errno, "Failed to read /dev/shm: %m"); + return -errno; +} + +static int clean_posix_shm(uid_t uid) { + _cleanup_closedir_ DIR *dir = NULL; + + dir = opendir("/dev/shm"); + if (!dir) { + if (errno == ENOENT) + return 0; + + return log_warning_errno(errno, "Failed to open /dev/shm: %m"); + } + + return clean_posix_shm_internal(dir, uid); +} + +static int clean_posix_mq(uid_t uid) { + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *de; + int ret = 0; + + dir = opendir("/dev/mqueue"); + if (!dir) { + if (errno == ENOENT) + return 0; + + return log_warning_errno(errno, "Failed to open /dev/mqueue: %m"); + } + + FOREACH_DIRENT(de, dir, goto fail) { + struct stat st; + char fn[1+strlen(de->d_name)+1]; + + if (STR_IN_SET(de->d_name, "..", ".")) + continue; + + if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { + if (errno == ENOENT) + continue; + + ret = log_warning_errno(errno, + "Failed to stat() MQ segment %s: %m", + de->d_name); + continue; + } + + if (st.st_uid != uid) + continue; + + fn[0] = '/'; + strcpy(fn+1, de->d_name); + + if (mq_unlink(fn) < 0) { + if (errno == ENOENT) + continue; + + ret = log_warning_errno(errno, + "Failed to unlink POSIX message queue %s: %m", + fn); + } + } + + return ret; + +fail: + return log_warning_errno(errno, "Failed to read /dev/mqueue: %m"); +} + +int clean_ipc(uid_t uid) { + int ret = 0, r; + + /* Refuse to clean IPC of the root and system users */ + if (uid <= SYSTEM_UID_MAX) + return 0; + + r = clean_sysvipc_shm(uid); + if (r < 0) + ret = r; + + r = clean_sysvipc_sem(uid); + if (r < 0) + ret = r; + + r = clean_sysvipc_msg(uid); + if (r < 0) + ret = r; + + r = clean_posix_shm(uid); + if (r < 0) + ret = r; + + r = clean_posix_mq(uid); + if (r < 0) + ret = r; + + return ret; +} diff --git a/src/libshared/clean-ipc.h b/src/libshared/clean-ipc.h new file mode 100644 index 0000000000..44a83afcf7 --- /dev/null +++ b/src/libshared/clean-ipc.h @@ -0,0 +1,24 @@ +#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 + +int clean_ipc(uid_t uid); diff --git a/src/libshared/condition.c b/src/libshared/condition.c new file mode 100644 index 0000000000..33ca6e029e --- /dev/null +++ b/src/libshared/condition.c @@ -0,0 +1,541 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "apparmor-util.h" +#include "architecture.h" +#include "audit-util.h" +#include "cap-list.h" +#include "condition.h" +#include "extract-word.h" +#include "fd-util.h" +#include "glob-util.h" +#include "hostname-util.h" +#include "ima-util.h" +#include "list.h" +#include "macro.h" +#include "mount-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "proc-cmdline.h" +#include "selinux-util.h" +#include "smack-util.h" +#include "stat-util.h" +#include "string-table.h" +#include "string-util.h" +#include "util.h" +#include "virt.h" + +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); + if (!c) + return NULL; + + c->type = type; + c->trigger = trigger; + c->negate = negate; + + r = free_and_strdup(&c->parameter, parameter); + if (r < 0) { + free(c); + return NULL; + } + + return c; +} + +void condition_free(Condition *c) { + assert(c); + + free(c->parameter); + free(c); +} + +Condition* condition_free_list(Condition *first) { + Condition *c, *n; + + LIST_FOREACH_SAFE(conditions, c, n, first) + condition_free(c); + + return NULL; +} + +static int condition_test_kernel_command_line(Condition *c) { + _cleanup_free_ char *line = NULL; + const char *p; + bool equal; + int r; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_KERNEL_COMMAND_LINE); + + r = proc_cmdline(&line); + if (r < 0) + return r; + + equal = !!strchr(c->parameter, '='); + p = line; + + for (;;) { + _cleanup_free_ char *word = NULL; + bool found; + + r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); + if (r < 0) + return r; + if (r == 0) + break; + + if (equal) + found = streq(word, c->parameter); + else { + const char *f; + + f = startswith(word, c->parameter); + found = f && (*f == '=' || *f == 0); + } + + if (found) + return true; + } + + return false; +} + +static int condition_test_virtualization(Condition *c) { + int b, v; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_VIRTUALIZATION); + + v = detect_virtualization(); + if (v < 0) + return v; + + /* First, compare with yes/no */ + b = parse_boolean(c->parameter); + + if (v > 0 && b > 0) + return true; + + if (v == 0 && b == 0) + return true; + + /* Then, compare categorization */ + if (VIRTUALIZATION_IS_VM(v) && streq(c->parameter, "vm")) + return true; + + if (VIRTUALIZATION_IS_CONTAINER(v) && streq(c->parameter, "container")) + return true; + + /* Finally compare id */ + return v != VIRTUALIZATION_NONE && streq(c->parameter, virtualization_to_string(v)); +} + +static int condition_test_architecture(Condition *c) { + int a, b; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_ARCHITECTURE); + + a = uname_architecture(); + if (a < 0) + return a; + + if (streq(c->parameter, "native")) + b = native_architecture(); + else + b = architecture_from_string(c->parameter); + if (b < 0) + return b; + + return a == b; +} + +static int condition_test_host(Condition *c) { + _cleanup_free_ char *h = NULL; + sd_id128_t x, y; + int r; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_HOST); + + if (sd_id128_from_string(c->parameter, &x) >= 0) { + + r = sd_id128_get_machine(&y); + if (r < 0) + return r; + + return sd_id128_equal(x, y); + } + + h = gethostname_malloc(); + if (!h) + return -ENOMEM; + + return fnmatch(c->parameter, h, FNM_CASEFOLD) == 0; +} + +static int condition_test_ac_power(Condition *c) { + int r; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_AC_POWER); + + r = parse_boolean(c->parameter); + if (r < 0) + return r; + + return (on_ac_power() != 0) == !!r; +} + +static int condition_test_security(Condition *c) { + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_SECURITY); + + if (streq(c->parameter, "selinux")) + return mac_selinux_have(); + if (streq(c->parameter, "smack")) + return mac_smack_use(); + if (streq(c->parameter, "apparmor")) + return mac_apparmor_use(); + if (streq(c->parameter, "audit")) + return use_audit(); + if (streq(c->parameter, "ima")) + return use_ima(); + + return false; +} + +static int condition_test_capability(Condition *c) { + _cleanup_fclose_ FILE *f = NULL; + int value; + char line[LINE_MAX]; + unsigned long long capabilities = -1; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_CAPABILITY); + + /* If it's an invalid capability, we don't have it */ + value = capability_from_name(c->parameter); + if (value < 0) + return -EINVAL; + + /* If it's a valid capability we default to assume + * that we have it */ + + f = fopen("/proc/self/status", "re"); + if (!f) + return -errno; + + while (fgets(line, sizeof(line), f)) { + truncate_nl(line); + + if (startswith(line, "CapBnd:")) { + (void) sscanf(line+7, "%llx", &capabilities); + break; + } + } + + return !!(capabilities & (1ULL << value)); +} + +static int condition_test_needs_update(Condition *c) { + const char *p; + struct stat usr, other; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_NEEDS_UPDATE); + + /* If the file system is read-only we shouldn't suggest an update */ + if (path_is_read_only_fs(c->parameter) > 0) + return false; + + /* Any other failure means we should allow the condition to be true, + * so that we rather invoke too many update tools than too + * few. */ + + if (!path_is_absolute(c->parameter)) + return true; + + p = strjoina(c->parameter, "/.updated"); + if (lstat(p, &other) < 0) + return true; + + if (lstat("/usr/", &usr) < 0) + return true; + + return usr.st_mtim.tv_sec > other.st_mtim.tv_sec || + (usr.st_mtim.tv_sec == other.st_mtim.tv_sec && usr.st_mtim.tv_nsec > other.st_mtim.tv_nsec); +} + +static int condition_test_first_boot(Condition *c) { + int r; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_FIRST_BOOT); + + r = parse_boolean(c->parameter); + if (r < 0) + return r; + + return (access("/run/systemd/first-boot", F_OK) >= 0) == !!r; +} + +static int condition_test_path_exists(Condition *c) { + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_PATH_EXISTS); + + return access(c->parameter, F_OK) >= 0; +} + +static int condition_test_path_exists_glob(Condition *c) { + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_PATH_EXISTS_GLOB); + + return glob_exists(c->parameter) > 0; +} + +static int condition_test_path_is_directory(Condition *c) { + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_PATH_IS_DIRECTORY); + + return is_dir(c->parameter, true) > 0; +} + +static int condition_test_path_is_symbolic_link(Condition *c) { + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_PATH_IS_SYMBOLIC_LINK); + + return is_symlink(c->parameter) > 0; +} + +static int condition_test_path_is_mount_point(Condition *c) { + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_PATH_IS_MOUNT_POINT); + + return path_is_mount_point(c->parameter, AT_SYMLINK_FOLLOW) > 0; +} + +static int condition_test_path_is_read_write(Condition *c) { + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_PATH_IS_READ_WRITE); + + return path_is_read_only_fs(c->parameter) <= 0; +} + +static int condition_test_directory_not_empty(Condition *c) { + int r; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_DIRECTORY_NOT_EMPTY); + + r = dir_is_empty(c->parameter); + return r <= 0 && r != -ENOENT; +} + +static int condition_test_file_not_empty(Condition *c) { + struct stat st; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_FILE_NOT_EMPTY); + + return (stat(c->parameter, &st) >= 0 && + S_ISREG(st.st_mode) && + st.st_size > 0); +} + +static int condition_test_file_is_executable(Condition *c) { + struct stat st; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_FILE_IS_EXECUTABLE); + + return (stat(c->parameter, &st) >= 0 && + S_ISREG(st.st_mode) && + (st.st_mode & 0111)); +} + +static int condition_test_null(Condition *c) { + assert(c); + assert(c->type == CONDITION_NULL); + + /* Note that during parsing we already evaluate the string and + * store it in c->negate */ + return true; +} + +int condition_test(Condition *c) { + + static int (*const condition_tests[_CONDITION_TYPE_MAX])(Condition *c) = { + [CONDITION_PATH_EXISTS] = condition_test_path_exists, + [CONDITION_PATH_EXISTS_GLOB] = condition_test_path_exists_glob, + [CONDITION_PATH_IS_DIRECTORY] = condition_test_path_is_directory, + [CONDITION_PATH_IS_SYMBOLIC_LINK] = condition_test_path_is_symbolic_link, + [CONDITION_PATH_IS_MOUNT_POINT] = condition_test_path_is_mount_point, + [CONDITION_PATH_IS_READ_WRITE] = condition_test_path_is_read_write, + [CONDITION_DIRECTORY_NOT_EMPTY] = condition_test_directory_not_empty, + [CONDITION_FILE_NOT_EMPTY] = condition_test_file_not_empty, + [CONDITION_FILE_IS_EXECUTABLE] = condition_test_file_is_executable, + [CONDITION_KERNEL_COMMAND_LINE] = condition_test_kernel_command_line, + [CONDITION_VIRTUALIZATION] = condition_test_virtualization, + [CONDITION_SECURITY] = condition_test_security, + [CONDITION_CAPABILITY] = condition_test_capability, + [CONDITION_HOST] = condition_test_host, + [CONDITION_AC_POWER] = condition_test_ac_power, + [CONDITION_ARCHITECTURE] = condition_test_architecture, + [CONDITION_NEEDS_UPDATE] = condition_test_needs_update, + [CONDITION_FIRST_BOOT] = condition_test_first_boot, + [CONDITION_NULL] = condition_test_null, + }; + + int r, b; + + assert(c); + assert(c->type >= 0); + assert(c->type < _CONDITION_TYPE_MAX); + + r = condition_tests[c->type](c); + if (r < 0) { + c->result = CONDITION_ERROR; + return r; + } + + b = (r > 0) == !c->negate; + c->result = b ? CONDITION_SUCCEEDED : CONDITION_FAILED; + return b; +} + +void condition_dump(Condition *c, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)) { + assert(c); + assert(f); + + if (!prefix) + prefix = ""; + + fprintf(f, + "%s\t%s: %s%s%s %s\n", + prefix, + to_string(c->type), + c->trigger ? "|" : "", + c->negate ? "!" : "", + c->parameter, + condition_result_to_string(c->result)); +} + +void condition_dump_list(Condition *first, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)) { + Condition *c; + + LIST_FOREACH(conditions, c, first) + condition_dump(c, f, prefix, to_string); +} + +static const char* const condition_type_table[_CONDITION_TYPE_MAX] = { + [CONDITION_ARCHITECTURE] = "ConditionArchitecture", + [CONDITION_VIRTUALIZATION] = "ConditionVirtualization", + [CONDITION_HOST] = "ConditionHost", + [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine", + [CONDITION_SECURITY] = "ConditionSecurity", + [CONDITION_CAPABILITY] = "ConditionCapability", + [CONDITION_AC_POWER] = "ConditionACPower", + [CONDITION_NEEDS_UPDATE] = "ConditionNeedsUpdate", + [CONDITION_FIRST_BOOT] = "ConditionFirstBoot", + [CONDITION_PATH_EXISTS] = "ConditionPathExists", + [CONDITION_PATH_EXISTS_GLOB] = "ConditionPathExistsGlob", + [CONDITION_PATH_IS_DIRECTORY] = "ConditionPathIsDirectory", + [CONDITION_PATH_IS_SYMBOLIC_LINK] = "ConditionPathIsSymbolicLink", + [CONDITION_PATH_IS_MOUNT_POINT] = "ConditionPathIsMountPoint", + [CONDITION_PATH_IS_READ_WRITE] = "ConditionPathIsReadWrite", + [CONDITION_DIRECTORY_NOT_EMPTY] = "ConditionDirectoryNotEmpty", + [CONDITION_FILE_NOT_EMPTY] = "ConditionFileNotEmpty", + [CONDITION_FILE_IS_EXECUTABLE] = "ConditionFileIsExecutable", + [CONDITION_NULL] = "ConditionNull" +}; + +DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType); + +static const char* const assert_type_table[_CONDITION_TYPE_MAX] = { + [CONDITION_ARCHITECTURE] = "AssertArchitecture", + [CONDITION_VIRTUALIZATION] = "AssertVirtualization", + [CONDITION_HOST] = "AssertHost", + [CONDITION_KERNEL_COMMAND_LINE] = "AssertKernelCommandLine", + [CONDITION_SECURITY] = "AssertSecurity", + [CONDITION_CAPABILITY] = "AssertCapability", + [CONDITION_AC_POWER] = "AssertACPower", + [CONDITION_NEEDS_UPDATE] = "AssertNeedsUpdate", + [CONDITION_FIRST_BOOT] = "AssertFirstBoot", + [CONDITION_PATH_EXISTS] = "AssertPathExists", + [CONDITION_PATH_EXISTS_GLOB] = "AssertPathExistsGlob", + [CONDITION_PATH_IS_DIRECTORY] = "AssertPathIsDirectory", + [CONDITION_PATH_IS_SYMBOLIC_LINK] = "AssertPathIsSymbolicLink", + [CONDITION_PATH_IS_MOUNT_POINT] = "AssertPathIsMountPoint", + [CONDITION_PATH_IS_READ_WRITE] = "AssertPathIsReadWrite", + [CONDITION_DIRECTORY_NOT_EMPTY] = "AssertDirectoryNotEmpty", + [CONDITION_FILE_NOT_EMPTY] = "AssertFileNotEmpty", + [CONDITION_FILE_IS_EXECUTABLE] = "AssertFileIsExecutable", + [CONDITION_NULL] = "AssertNull" +}; + +DEFINE_STRING_TABLE_LOOKUP(assert_type, ConditionType); + +static const char* const condition_result_table[_CONDITION_RESULT_MAX] = { + [CONDITION_UNTESTED] = "untested", + [CONDITION_SUCCEEDED] = "succeeded", + [CONDITION_FAILED] = "failed", + [CONDITION_ERROR] = "error", +}; + +DEFINE_STRING_TABLE_LOOKUP(condition_result, ConditionResult); diff --git a/src/libshared/condition.h b/src/libshared/condition.h new file mode 100644 index 0000000000..bdda04b770 --- /dev/null +++ b/src/libshared/condition.h @@ -0,0 +1,94 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 + +#include "list.h" +#include "macro.h" + +typedef enum ConditionType { + CONDITION_ARCHITECTURE, + CONDITION_VIRTUALIZATION, + CONDITION_HOST, + CONDITION_KERNEL_COMMAND_LINE, + CONDITION_SECURITY, + CONDITION_CAPABILITY, + CONDITION_AC_POWER, + + CONDITION_NEEDS_UPDATE, + CONDITION_FIRST_BOOT, + + CONDITION_PATH_EXISTS, + CONDITION_PATH_EXISTS_GLOB, + CONDITION_PATH_IS_DIRECTORY, + CONDITION_PATH_IS_SYMBOLIC_LINK, + CONDITION_PATH_IS_MOUNT_POINT, + CONDITION_PATH_IS_READ_WRITE, + CONDITION_DIRECTORY_NOT_EMPTY, + CONDITION_FILE_NOT_EMPTY, + CONDITION_FILE_IS_EXECUTABLE, + + CONDITION_NULL, + + _CONDITION_TYPE_MAX, + _CONDITION_TYPE_INVALID = -1 +} ConditionType; + +typedef enum ConditionResult { + CONDITION_UNTESTED, + CONDITION_SUCCEEDED, + CONDITION_FAILED, + CONDITION_ERROR, + _CONDITION_RESULT_MAX, + _CONDITION_RESULT_INVALID = -1 +} ConditionResult; + +typedef struct Condition { + ConditionType type:8; + + bool trigger:1; + bool negate:1; + + ConditionResult result:6; + + char *parameter; + + LIST_FIELDS(struct Condition, conditions); +} Condition; + +Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate); +void condition_free(Condition *c); +Condition* condition_free_list(Condition *c); + +int condition_test(Condition *c); + +void condition_dump(Condition *c, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)); +void condition_dump_list(Condition *c, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)); + +const char* condition_type_to_string(ConditionType t) _const_; +ConditionType condition_type_from_string(const char *s) _pure_; + +const char* assert_type_to_string(ConditionType t) _const_; +ConditionType assert_type_from_string(const char *s) _pure_; + +const char* condition_result_to_string(ConditionResult r) _const_; +ConditionResult condition_result_from_string(const char *s) _pure_; diff --git a/src/libshared/conf-parser.c b/src/libshared/conf-parser.c new file mode 100644 index 0000000000..83be79a4f5 --- /dev/null +++ b/src/libshared/conf-parser.c @@ -0,0 +1,913 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "conf-files.h" +#include "conf-parser.h" +#include "extract-word.h" +#include "fd-util.h" +#include "fs-util.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "signal-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "strv.h" +#include "syslog-util.h" +#include "time-util.h" +#include "utf8.h" + +int config_item_table_lookup( + const void *table, + const char *section, + const char *lvalue, + ConfigParserCallback *func, + int *ltype, + void **data, + void *userdata) { + + const ConfigTableItem *t; + + assert(table); + assert(lvalue); + assert(func); + assert(ltype); + assert(data); + + for (t = table; t->lvalue; t++) { + + if (!streq(lvalue, t->lvalue)) + continue; + + if (!streq_ptr(section, t->section)) + continue; + + *func = t->parse; + *ltype = t->ltype; + *data = t->data; + return 1; + } + + return 0; +} + +int config_item_perf_lookup( + const void *table, + const char *section, + const char *lvalue, + ConfigParserCallback *func, + int *ltype, + void **data, + void *userdata) { + + ConfigPerfItemLookup lookup = (ConfigPerfItemLookup) table; + const ConfigPerfItem *p; + + assert(table); + assert(lvalue); + assert(func); + assert(ltype); + assert(data); + + if (!section) + p = lookup(lvalue, strlen(lvalue)); + else { + char *key; + + key = strjoin(section, ".", lvalue, NULL); + if (!key) + return -ENOMEM; + + p = lookup(key, strlen(key)); + free(key); + } + + if (!p) + return 0; + + *func = p->parse; + *ltype = p->ltype; + *data = (uint8_t*) userdata + p->offset; + return 1; +} + +/* Run the user supplied parser for an assignment */ +static int next_assignment(const char *unit, + const char *filename, + unsigned line, + ConfigItemLookup lookup, + const void *table, + const char *section, + unsigned section_line, + const char *lvalue, + const char *rvalue, + bool relaxed, + void *userdata) { + + ConfigParserCallback func = NULL; + int ltype = 0; + void *data = NULL; + int r; + + assert(filename); + assert(line > 0); + assert(lookup); + assert(lvalue); + assert(rvalue); + + r = lookup(table, section, lvalue, &func, <ype, &data, userdata); + if (r < 0) + return r; + + if (r > 0) { + if (func) + return func(unit, filename, line, section, section_line, + lvalue, ltype, rvalue, data, userdata); + + return 0; + } + + /* Warn about unknown non-extension fields. */ + if (!relaxed && !startswith(lvalue, "X-")) + log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown lvalue '%s' in section '%s'", lvalue, section); + + return 0; +} + +/* Parse a variable assignment line */ +static int parse_line(const char* unit, + const char *filename, + unsigned line, + const char *sections, + ConfigItemLookup lookup, + const void *table, + bool relaxed, + bool allow_include, + char **section, + unsigned *section_line, + bool *section_ignored, + char *l, + void *userdata) { + + char *e; + + assert(filename); + assert(line > 0); + assert(lookup); + assert(l); + + l = strstrip(l); + + if (!*l) + return 0; + + if (strchr(COMMENTS "\n", *l)) + return 0; + + if (startswith(l, ".include ")) { + _cleanup_free_ char *fn = NULL; + + /* .includes are a bad idea, we only support them here + * for historical reasons. They create cyclic include + * problems and make it difficult to detect + * configuration file changes with an easy + * stat(). Better approaches, such as .d/ drop-in + * snippets exist. + * + * Support for them should be eventually removed. */ + + if (!allow_include) { + log_syntax(unit, LOG_ERR, filename, line, 0, ".include not allowed here. Ignoring."); + return 0; + } + + fn = file_in_same_dir(filename, strstrip(l+9)); + if (!fn) + return -ENOMEM; + + return config_parse(unit, fn, NULL, sections, lookup, table, relaxed, false, false, userdata); + } + + if (*l == '[') { + size_t k; + char *n; + + k = strlen(l); + assert(k > 0); + + if (l[k-1] != ']') { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid section header '%s'", l); + return -EBADMSG; + } + + n = strndup(l+1, k-2); + if (!n) + return -ENOMEM; + + if (sections && !nulstr_contains(sections, n)) { + + if (!relaxed && !startswith(n, "X-")) + log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown section '%s'. Ignoring.", n); + + free(n); + *section = mfree(*section); + *section_line = 0; + *section_ignored = true; + } else { + free(*section); + *section = n; + *section_line = line; + *section_ignored = false; + } + + return 0; + } + + if (sections && !*section) { + + if (!relaxed && !*section_ignored) + log_syntax(unit, LOG_WARNING, filename, line, 0, "Assignment outside of section. Ignoring."); + + return 0; + } + + e = strchr(l, '='); + if (!e) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Missing '='."); + return -EINVAL; + } + + *e = 0; + e++; + + return next_assignment(unit, + filename, + line, + lookup, + table, + *section, + *section_line, + strstrip(l), + strstrip(e), + relaxed, + userdata); +} + +/* Go through the file and parse each line */ +int config_parse(const char *unit, + const char *filename, + FILE *f, + const char *sections, + ConfigItemLookup lookup, + const void *table, + bool relaxed, + bool allow_include, + bool warn, + void *userdata) { + + _cleanup_free_ char *section = NULL, *continuation = NULL; + _cleanup_fclose_ FILE *ours = NULL; + unsigned line = 0, section_line = 0; + bool section_ignored = false, allow_bom = true; + int r; + + assert(filename); + assert(lookup); + + if (!f) { + f = ours = fopen(filename, "re"); + if (!f) { + /* Only log on request, except for ENOENT, + * since we return 0 to the caller. */ + if (warn || errno == ENOENT) + log_full(errno == ENOENT ? LOG_DEBUG : LOG_ERR, + "Failed to open configuration file '%s': %m", filename); + return errno == ENOENT ? 0 : -errno; + } + } + + fd_warn_permissions(filename, fileno(f)); + + for (;;) { + char buf[LINE_MAX], *l, *p, *c = NULL, *e; + bool escaped = false; + + if (!fgets(buf, sizeof buf, f)) { + if (feof(f)) + break; + + log_error_errno(errno, "Failed to read configuration file '%s': %m", filename); + return -errno; + } + + l = buf; + if (allow_bom && startswith(l, UTF8_BYTE_ORDER_MARK)) + l += strlen(UTF8_BYTE_ORDER_MARK); + allow_bom = false; + + truncate_nl(l); + + if (continuation) { + c = strappend(continuation, l); + if (!c) { + if (warn) + log_oom(); + return -ENOMEM; + } + + continuation = mfree(continuation); + p = c; + } else + p = l; + + for (e = p; *e; e++) { + if (escaped) + escaped = false; + else if (*e == '\\') + escaped = true; + } + + if (escaped) { + *(e-1) = ' '; + + if (c) + continuation = c; + else { + continuation = strdup(l); + if (!continuation) { + if (warn) + log_oom(); + return -ENOMEM; + } + } + + continue; + } + + r = parse_line(unit, + filename, + ++line, + sections, + lookup, + table, + relaxed, + allow_include, + §ion, + §ion_line, + §ion_ignored, + p, + userdata); + free(c); + + if (r < 0) { + if (warn) + log_warning_errno(r, "Failed to parse file '%s': %m", + filename); + return r; + } + } + + return 0; +} + +/* Parse each config file in the specified directories. */ +int config_parse_many(const char *conf_file, + const char *conf_file_dirs, + const char *sections, + ConfigItemLookup lookup, + const void *table, + bool relaxed, + void *userdata) { + _cleanup_strv_free_ char **files = NULL; + char **fn; + int r; + + r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs); + if (r < 0) + return r; + + if (conf_file) { + r = config_parse(NULL, conf_file, NULL, sections, lookup, table, relaxed, false, true, userdata); + if (r < 0) + return r; + } + + STRV_FOREACH(fn, files) { + r = config_parse(NULL, *fn, NULL, sections, lookup, table, relaxed, false, true, userdata); + if (r < 0) + return r; + } + + return 0; +} + +#define DEFINE_PARSER(type, vartype, conv_func) \ + int config_parse_##type( \ + 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) { \ + \ + vartype *i = data; \ + int r; \ + \ + assert(filename); \ + assert(lvalue); \ + assert(rvalue); \ + assert(data); \ + \ + r = conv_func(rvalue, i); \ + if (r < 0) \ + log_syntax(unit, LOG_ERR, filename, line, r, \ + "Failed to parse %s value, ignoring: %s", \ + #type, rvalue); \ + \ + return 0; \ + } \ + struct __useless_struct_to_allow_trailing_semicolon__ + +DEFINE_PARSER(int, int, safe_atoi); +DEFINE_PARSER(long, long, safe_atoli); +DEFINE_PARSER(uint32, uint32_t, safe_atou32); +DEFINE_PARSER(uint64, uint64_t, safe_atou64); +DEFINE_PARSER(unsigned, unsigned, safe_atou); +DEFINE_PARSER(double, double, safe_atod); +DEFINE_PARSER(nsec, nsec_t, parse_nsec); +DEFINE_PARSER(sec, usec_t, parse_sec); +DEFINE_PARSER(mode, mode_t, parse_mode); + +int config_parse_iec_size(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) { + + size_t *sz = data; + uint64_t v; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = parse_size(rvalue, 1024, &v); + if (r < 0 || (uint64_t) (size_t) v != v) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue); + return 0; + } + + *sz = (size_t) v; + return 0; +} + +int config_parse_si_size(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) { + + size_t *sz = data; + uint64_t v; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = parse_size(rvalue, 1000, &v); + if (r < 0 || (uint64_t) (size_t) v != v) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue); + return 0; + } + + *sz = (size_t) v; + return 0; +} + +int config_parse_iec_uint64(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) { + + uint64_t *bytes = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = parse_size(rvalue, 1024, bytes); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue); + + return 0; +} + +int config_parse_bool(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) { + + int k; + bool *b = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + k = parse_boolean(rvalue); + if (k < 0) { + log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse boolean value, ignoring: %s", rvalue); + return 0; + } + + *b = !!k; + return 0; +} + +int config_parse_tristate( + 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) { + + int k, *t = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + /* A tristate is pretty much a boolean, except that it can + * also take the special value -1, indicating "uninitialized", + * much like NULL is for a pointer type. */ + + k = parse_boolean(rvalue); + if (k < 0) { + log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse boolean value, ignoring: %s", rvalue); + return 0; + } + + *t = !!k; + return 0; +} + +int config_parse_string( + 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) { + + char **s = data, *n; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (!utf8_is_valid(rvalue)) { + log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); + return 0; + } + + if (isempty(rvalue)) + n = NULL; + else { + n = strdup(rvalue); + if (!n) + return log_oom(); + } + + free(*s); + *s = n; + + return 0; +} + +int config_parse_path( + 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) { + + char **s = data, *n; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (!utf8_is_valid(rvalue)) { + log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); + return 0; + } + + if (!path_is_absolute(rvalue)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute path, ignoring: %s", rvalue); + return 0; + } + + n = strdup(rvalue); + if (!n) + return log_oom(); + + path_kill_slashes(n); + + free(*s); + *s = n; + + return 0; +} + +int config_parse_strv(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) { + + char ***sv = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + char **empty; + + /* Empty assignment resets the list. As a special rule + * we actually fill in a real empty array here rather + * than NULL, since some code wants to know if + * something was set at all... */ + empty = strv_new(NULL, NULL); + if (!empty) + return log_oom(); + + strv_free(*sv); + *sv = empty; + return 0; + } + + for (;;) { + char *word = NULL; + int r; + r = extract_first_word(&rvalue, &word, WHITESPACE, EXTRACT_QUOTES|EXTRACT_RETAIN_ESCAPE); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue); + break; + } + + if (!utf8_is_valid(word)) { + log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); + free(word); + continue; + } + r = strv_consume(sv, word); + if (r < 0) + return log_oom(); + } + + return 0; +} + +int config_parse_log_facility( + 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) { + + + int *o = data, x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + x = log_facility_unshifted_from_string(rvalue); + if (x < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse log facility, ignoring: %s", rvalue); + return 0; + } + + *o = (x << 3) | LOG_PRI(*o); + + return 0; +} + +int config_parse_log_level( + 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) { + + + int *o = data, x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + x = log_level_from_string(rvalue); + if (x < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse log level, ignoring: %s", rvalue); + return 0; + } + + *o = (*o & LOG_FACMASK) | x; + return 0; +} + +int config_parse_signal( + 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) { + + int *sig = data, r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(sig); + + r = signal_from_string_try_harder(rvalue); + if (r <= 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse signal name, ignoring: %s", rvalue); + return 0; + } + + *sig = r; + return 0; +} + +int config_parse_personality( + 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 long *personality = data, p; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(personality); + + p = personality_from_string(rvalue); + if (p == PERSONALITY_INVALID) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse personality, ignoring: %s", rvalue); + return 0; + } + + *personality = p; + return 0; +} + +int config_parse_ifname( + 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) { + + char **s = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + *s = mfree(*s); + return 0; + } + + if (!ifname_valid(rvalue)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is not valid or too long, ignoring assignment: %s", rvalue); + return 0; + } + + r = free_and_strdup(s, rvalue); + if (r < 0) + return log_oom(); + + return 0; +} diff --git a/src/libshared/conf-parser.h b/src/libshared/conf-parser.h new file mode 100644 index 0000000000..f6964e3fd4 --- /dev/null +++ b/src/libshared/conf-parser.h @@ -0,0 +1,229 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include + +#include "alloc-util.h" +#include "log.h" +#include "macro.h" + +/* An abstract parser for simple, line based, shallow configuration + * files consisting of variable assignments only. */ + +/* Prototype for a parser for a specific configuration setting */ +typedef int (*ConfigParserCallback)(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); + +/* Wraps information for parsing a specific configuration variable, to + * be stored in a simple array */ +typedef struct ConfigTableItem { + const char *section; /* Section */ + const char *lvalue; /* Name of the variable */ + ConfigParserCallback parse; /* Function that is called to parse the variable's value */ + int ltype; /* Distinguish different variables passed to the same callback */ + void *data; /* Where to store the variable's data */ +} ConfigTableItem; + +/* Wraps information for parsing a specific configuration variable, to + * be stored in a gperf perfect hashtable */ +typedef struct ConfigPerfItem { + const char *section_and_lvalue; /* Section + "." + name of the variable */ + ConfigParserCallback parse; /* Function that is called to parse the variable's value */ + int ltype; /* Distinguish different variables passed to the same callback */ + size_t offset; /* Offset where to store data, from the beginning of userdata */ +} ConfigPerfItem; + +/* Prototype for a low-level gperf lookup function */ +typedef const ConfigPerfItem* (*ConfigPerfItemLookup)(const char *section_and_lvalue, unsigned length); + +/* Prototype for a generic high-level lookup function */ +typedef int (*ConfigItemLookup)( + const void *table, + const char *section, + const char *lvalue, + ConfigParserCallback *func, + int *ltype, + void **data, + void *userdata); + +/* Linear table search implementation of ConfigItemLookup, based on + * ConfigTableItem arrays */ +int config_item_table_lookup(const void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata); + +/* gperf implementation of ConfigItemLookup, based on gperf + * ConfigPerfItem tables */ +int config_item_perf_lookup(const void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata); + +int config_parse(const char *unit, + const char *filename, + FILE *f, + const char *sections, /* nulstr */ + ConfigItemLookup lookup, + const void *table, + bool relaxed, + bool allow_include, + bool warn, + void *userdata); + +int config_parse_many(const char *conf_file, /* possibly NULL */ + const char *conf_file_dirs, /* nulstr */ + const char *sections, /* nulstr */ + ConfigItemLookup lookup, + const void *table, + bool relaxed, + void *userdata); + +/* Generic parsers */ +int config_parse_int(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); +int config_parse_unsigned(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); +int config_parse_long(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); +int config_parse_uint32(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); +int config_parse_uint64(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); +int config_parse_double(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); +int config_parse_iec_size(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); +int config_parse_si_size(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); +int config_parse_iec_uint64(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); +int config_parse_bool(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); +int config_parse_tristate(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); +int config_parse_string(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); +int config_parse_path(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); +int config_parse_strv(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); +int config_parse_sec(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); +int config_parse_nsec(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); +int config_parse_mode(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); +int config_parse_log_facility(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); +int config_parse_log_level(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); +int config_parse_signal(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); +int config_parse_personality(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); +int config_parse_ifname(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); + +#define DEFINE_CONFIG_PARSE_ENUM(function,name,type,msg) \ + int function(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) { \ + \ + type *i = data, x; \ + \ + assert(filename); \ + assert(lvalue); \ + assert(rvalue); \ + assert(data); \ + \ + if ((x = name##_from_string(rvalue)) < 0) { \ + log_syntax(unit, LOG_ERR, filename, line, -x, \ + msg ", ignoring: %s", rvalue); \ + return 0; \ + } \ + \ + *i = x; \ + return 0; \ + } + +#define DEFINE_CONFIG_PARSE_ENUMV(function,name,type,invalid,msg) \ + int function(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) { \ + \ + type **enums = data, x, *ys; \ + _cleanup_free_ type *xs = NULL; \ + const char *word, *state; \ + size_t l, i = 0; \ + \ + assert(filename); \ + assert(lvalue); \ + assert(rvalue); \ + assert(data); \ + \ + xs = new0(type, 1); \ + if (!xs) \ + return -ENOMEM; \ + \ + *xs = invalid; \ + \ + FOREACH_WORD(word, l, rvalue, state) { \ + _cleanup_free_ char *en = NULL; \ + type *new_xs; \ + \ + en = strndup(word, l); \ + if (!en) \ + return -ENOMEM; \ + \ + if ((x = name##_from_string(en)) < 0) { \ + log_syntax(unit, LOG_ERR, filename, line, \ + -x, msg ", ignoring: %s", en); \ + continue; \ + } \ + \ + for (ys = xs; x != invalid && *ys != invalid; ys++) { \ + if (*ys == x) { \ + log_syntax(unit, LOG_ERR, filename, \ + line, -x, \ + "Duplicate entry, ignoring: %s", \ + en); \ + x = invalid; \ + } \ + } \ + \ + if (x == invalid) \ + continue; \ + \ + *(xs + i) = x; \ + new_xs = realloc(xs, (++i + 1) * sizeof(type)); \ + if (new_xs) \ + xs = new_xs; \ + else \ + return -ENOMEM; \ + \ + *(xs + i) = invalid; \ + } \ + \ + free(*enums); \ + *enums = xs; \ + xs = NULL; \ + \ + return 0; \ + } diff --git a/src/libshared/dev-setup.c b/src/libshared/dev-setup.c new file mode 100644 index 0000000000..b2d464c117 --- /dev/null +++ b/src/libshared/dev-setup.c @@ -0,0 +1,73 @@ +/*** + This file is part of systemd. + + Copyright 2010-2012 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 +#include + +#include "alloc-util.h" +#include "dev-setup.h" +#include "label.h" +#include "log.h" +#include "path-util.h" +#include "user-util.h" +#include "util.h" + +int dev_setup(const char *prefix, uid_t uid, gid_t gid) { + static const char symlinks[] = + "-/proc/kcore\0" "/dev/core\0" + "/proc/self/fd\0" "/dev/fd\0" + "/proc/self/fd/0\0" "/dev/stdin\0" + "/proc/self/fd/1\0" "/dev/stdout\0" + "/proc/self/fd/2\0" "/dev/stderr\0"; + + const char *j, *k; + int r; + + NULSTR_FOREACH_PAIR(j, k, symlinks) { + _cleanup_free_ char *link_name = NULL; + const char *n; + + if (j[0] == '-') { + j++; + + if (access(j, F_OK) < 0) + continue; + } + + if (prefix) { + link_name = prefix_root(prefix, k); + if (!link_name) + return -ENOMEM; + + n = link_name; + } else + n = k; + + r = symlink_label(j, n); + if (r < 0) + log_debug_errno(r, "Failed to symlink %s to %s: %m", j, n); + + if (uid != UID_INVALID || gid != GID_INVALID) + if (lchown(n, uid, gid) < 0) + log_debug_errno(errno, "Failed to chown %s: %m", n); + } + + return 0; +} diff --git a/src/libshared/dev-setup.h b/src/libshared/dev-setup.h new file mode 100644 index 0000000000..5766a62060 --- /dev/null +++ b/src/libshared/dev-setup.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-2012 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 + +int dev_setup(const char *prefix, uid_t uid, gid_t gid); diff --git a/src/libshared/dns-domain.c b/src/libshared/dns-domain.c new file mode 100644 index 0000000000..835557c6b2 --- /dev/null +++ b/src/libshared/dns-domain.c @@ -0,0 +1,1322 @@ +/*** + 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 . + ***/ + +#ifdef HAVE_LIBIDN +#include +#include +#endif + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "dns-domain.h" +#include "hashmap.h" +#include "hexdecoct.h" +#include "in-addr-util.h" +#include "macro.h" +#include "parse-util.h" +#include "string-util.h" +#include "strv.h" +#include "utf8.h" + +int dns_label_unescape(const char **name, char *dest, size_t sz) { + const char *n; + char *d; + int r = 0; + + assert(name); + assert(*name); + + n = *name; + d = dest; + + for (;;) { + if (*n == '.') { + n++; + break; + } + + if (*n == 0) + break; + + if (r >= DNS_LABEL_MAX) + return -EINVAL; + + if (sz <= 0) + return -ENOBUFS; + + if (*n == '\\') { + /* Escaped character */ + + n++; + + if (*n == 0) + /* Ending NUL */ + return -EINVAL; + + else if (*n == '\\' || *n == '.') { + /* Escaped backslash or dot */ + + if (d) + *(d++) = *n; + sz--; + r++; + n++; + + } else if (n[0] >= '0' && n[0] <= '9') { + unsigned k; + + /* Escaped literal ASCII character */ + + if (!(n[1] >= '0' && n[1] <= '9') || + !(n[2] >= '0' && n[2] <= '9')) + return -EINVAL; + + k = ((unsigned) (n[0] - '0') * 100) + + ((unsigned) (n[1] - '0') * 10) + + ((unsigned) (n[2] - '0')); + + /* Don't allow anything that doesn't + * fit in 8bit. Note that we do allow + * control characters, as some servers + * (e.g. cloudflare) are happy to + * generate labels with them + * inside. */ + if (k > 255) + return -EINVAL; + + if (d) + *(d++) = (char) k; + sz--; + r++; + + n += 3; + } else + return -EINVAL; + + } else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) { + + /* Normal character */ + + if (d) + *(d++) = *n; + sz--; + r++; + n++; + } else + return -EINVAL; + } + + /* Empty label that is not at the end? */ + if (r == 0 && *n) + return -EINVAL; + + if (sz >= 1 && d) + *d = 0; + + *name = n; + return r; +} + +/* @label_terminal: terminal character of a label, updated to point to the terminal character of + * the previous label (always skipping one dot) or to NULL if there are no more + * labels. */ +int dns_label_unescape_suffix(const char *name, const char **label_terminal, char *dest, size_t sz) { + const char *terminal; + int r; + + assert(name); + assert(label_terminal); + assert(dest); + + /* no more labels */ + if (!*label_terminal) { + if (sz >= 1) + *dest = 0; + + return 0; + } + + terminal = *label_terminal; + assert(*terminal == '.' || *terminal == 0); + + /* Skip current terminal character (and accept domain names ending it ".") */ + if (*terminal == 0) + terminal--; + if (terminal >= name && *terminal == '.') + terminal--; + + /* Point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */ + for (;;) { + if (terminal < name) { + /* Reached the first label, so indicate that there are no more */ + terminal = NULL; + break; + } + + /* Find the start of the last label */ + if (*terminal == '.') { + const char *y; + unsigned slashes = 0; + + for (y = terminal - 1; y >= name && *y == '\\'; y--) + slashes++; + + if (slashes % 2 == 0) { + /* The '.' was not escaped */ + name = terminal + 1; + break; + } else { + terminal = y; + continue; + } + } + + terminal--; + } + + r = dns_label_unescape(&name, dest, sz); + if (r < 0) + return r; + + *label_terminal = terminal; + + return r; +} + +int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) { + char *q; + + /* DNS labels must be between 1 and 63 characters long. A + * zero-length label does not exist. See RFC 2182, Section + * 11. */ + + if (l <= 0 || l > DNS_LABEL_MAX) + return -EINVAL; + if (sz < 1) + return -ENOBUFS; + + assert(p); + assert(dest); + + q = dest; + while (l > 0) { + + if (*p == '.' || *p == '\\') { + + /* Dot or backslash */ + + if (sz < 3) + return -ENOBUFS; + + *(q++) = '\\'; + *(q++) = *p; + + sz -= 2; + + } else if (*p == '_' || + *p == '-' || + (*p >= '0' && *p <= '9') || + (*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z')) { + + /* Proper character */ + + if (sz < 2) + return -ENOBUFS; + + *(q++) = *p; + sz -= 1; + + } else { + + /* Everything else */ + + if (sz < 5) + return -ENOBUFS; + + *(q++) = '\\'; + *(q++) = '0' + (char) ((uint8_t) *p / 100); + *(q++) = '0' + (char) (((uint8_t) *p / 10) % 10); + *(q++) = '0' + (char) ((uint8_t) *p % 10); + + sz -= 4; + } + + p++; + l--; + } + + *q = 0; + return (int) (q - dest); +} + +int dns_label_escape_new(const char *p, size_t l, char **ret) { + _cleanup_free_ char *s = NULL; + int r; + + assert(p); + assert(ret); + + if (l <= 0 || l > DNS_LABEL_MAX) + return -EINVAL; + + s = new(char, DNS_LABEL_ESCAPED_MAX); + if (!s) + return -ENOMEM; + + r = dns_label_escape(p, l, s, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + *ret = s; + s = NULL; + + return r; +} + +int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) { +#ifdef HAVE_LIBIDN + _cleanup_free_ uint32_t *input = NULL; + size_t input_size, l; + const char *p; + bool contains_8bit = false; + char buffer[DNS_LABEL_MAX+1]; + + assert(encoded); + assert(decoded); + + /* Converts an U-label into an A-label */ + + if (encoded_size <= 0) + return -EINVAL; + + for (p = encoded; p < encoded + encoded_size; p++) + if ((uint8_t) *p > 127) + contains_8bit = true; + + if (!contains_8bit) { + if (encoded_size > DNS_LABEL_MAX) + return -EINVAL; + + return 0; + } + + input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size); + if (!input) + return -ENOMEM; + + if (idna_to_ascii_4i(input, input_size, buffer, 0) != 0) + return -EINVAL; + + l = strlen(buffer); + + /* Verify that the result is not longer than one DNS label. */ + if (l <= 0 || l > DNS_LABEL_MAX) + return -EINVAL; + if (l > decoded_max) + return -ENOBUFS; + + memcpy(decoded, buffer, l); + + /* If there's room, append a trailing NUL byte, but only then */ + if (decoded_max > l) + decoded[l] = 0; + + return (int) l; +#else + return 0; +#endif +} + +int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) { +#ifdef HAVE_LIBIDN + size_t input_size, output_size; + _cleanup_free_ uint32_t *input = NULL; + _cleanup_free_ char *result = NULL; + uint32_t *output = NULL; + size_t w; + + /* To be invoked after unescaping. Converts an A-label into an U-label. */ + + assert(encoded); + assert(decoded); + + if (encoded_size <= 0 || encoded_size > DNS_LABEL_MAX) + return -EINVAL; + + if (encoded_size < sizeof(IDNA_ACE_PREFIX)-1) + return 0; + + if (memcmp(encoded, IDNA_ACE_PREFIX, sizeof(IDNA_ACE_PREFIX) -1) != 0) + return 0; + + input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size); + if (!input) + return -ENOMEM; + + output_size = input_size; + output = newa(uint32_t, output_size); + + idna_to_unicode_44i(input, input_size, output, &output_size, 0); + + result = stringprep_ucs4_to_utf8(output, output_size, NULL, &w); + if (!result) + return -ENOMEM; + if (w <= 0) + return -EINVAL; + if (w > decoded_max) + return -ENOBUFS; + + memcpy(decoded, result, w); + + /* Append trailing NUL byte if there's space, but only then. */ + if (decoded_max > w) + decoded[w] = 0; + + return w; +#else + return 0; +#endif +} + +int dns_name_concat(const char *a, const char *b, char **_ret) { + _cleanup_free_ char *ret = NULL; + size_t n = 0, allocated = 0; + const char *p; + bool first = true; + int r; + + if (a) + p = a; + else if (b) { + p = b; + b = NULL; + } else + goto finish; + + for (;;) { + char label[DNS_LABEL_MAX]; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r < 0) + return r; + if (r == 0) { + if (*p != 0) + return -EINVAL; + + if (b) { + /* Now continue with the second string, if there is one */ + p = b; + b = NULL; + continue; + } + + break; + } + + if (_ret) { + if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + r = dns_label_escape(label, r, ret + n + !first, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + if (!first) + ret[n] = '.'; + } else { + char escaped[DNS_LABEL_ESCAPED_MAX]; + + r = dns_label_escape(label, r, escaped, sizeof(escaped)); + if (r < 0) + return r; + } + + if (!first) + n++; + else + first = false; + + n += r; + } + +finish: + if (n > DNS_HOSTNAME_MAX) + return -EINVAL; + + if (_ret) { + if (n == 0) { + /* Nothing appended? If so, generate at least a single dot, to indicate the DNS root domain */ + if (!GREEDY_REALLOC(ret, allocated, 2)) + return -ENOMEM; + + ret[n++] = '.'; + } else { + if (!GREEDY_REALLOC(ret, allocated, n + 1)) + return -ENOMEM; + } + + ret[n] = 0; + *_ret = ret; + ret = NULL; + } + + return 0; +} + +void dns_name_hash_func(const void *s, struct siphash *state) { + const char *p = s; + int r; + + assert(p); + + for (;;) { + char label[DNS_LABEL_MAX+1]; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r < 0) + break; + if (r == 0) + break; + + ascii_strlower_n(label, r); + siphash24_compress(label, r, state); + siphash24_compress_byte(0, state); /* make sure foobar and foo.bar result in different hashes */ + } + + /* enforce that all names are terminated by the empty label */ + string_hash_func("", state); +} + +int dns_name_compare_func(const void *a, const void *b) { + const char *x, *y; + int r, q; + + assert(a); + assert(b); + + x = (const char *) a + strlen(a); + y = (const char *) b + strlen(b); + + for (;;) { + char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + + if (x == NULL && y == NULL) + return 0; + + r = dns_label_unescape_suffix(a, &x, la, sizeof(la)); + q = dns_label_unescape_suffix(b, &y, lb, sizeof(lb)); + if (r < 0 || q < 0) + return r - q; + + r = ascii_strcasecmp_nn(la, r, lb, q); + if (r != 0) + return r; + } +} + +const struct hash_ops dns_name_hash_ops = { + .hash = dns_name_hash_func, + .compare = dns_name_compare_func +}; + +int dns_name_equal(const char *x, const char *y) { + int r, q; + + assert(x); + assert(y); + + for (;;) { + char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + + r = dns_label_unescape(&x, la, sizeof(la)); + if (r < 0) + return r; + + q = dns_label_unescape(&y, lb, sizeof(lb)); + if (q < 0) + return q; + + if (r != q) + return false; + if (r == 0) + return true; + + if (ascii_strcasecmp_n(la, lb, r) != 0) + return false; + } +} + +int dns_name_endswith(const char *name, const char *suffix) { + const char *n, *s, *saved_n = NULL; + int r, q; + + assert(name); + assert(suffix); + + n = name; + s = suffix; + + for (;;) { + char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; + + r = dns_label_unescape(&n, ln, sizeof(ln)); + if (r < 0) + return r; + + if (!saved_n) + saved_n = n; + + q = dns_label_unescape(&s, ls, sizeof(ls)); + if (q < 0) + return q; + + if (r == 0 && q == 0) + return true; + if (r == 0 && saved_n == n) + return false; + + if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) { + + /* Not the same, let's jump back, and try with the next label again */ + s = suffix; + n = saved_n; + saved_n = NULL; + } + } +} + +int dns_name_startswith(const char *name, const char *prefix) { + const char *n, *p; + int r, q; + + assert(name); + assert(prefix); + + n = name; + p = prefix; + + for (;;) { + char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX]; + + r = dns_label_unescape(&p, lp, sizeof(lp)); + if (r < 0) + return r; + if (r == 0) + return true; + + q = dns_label_unescape(&n, ln, sizeof(ln)); + if (q < 0) + return q; + + if (r != q) + return false; + if (ascii_strcasecmp_n(ln, lp, r) != 0) + return false; + } +} + +int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret) { + const char *n, *s, *saved_before = NULL, *saved_after = NULL, *prefix; + int r, q; + + assert(name); + assert(old_suffix); + assert(new_suffix); + assert(ret); + + n = name; + s = old_suffix; + + for (;;) { + char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; + + if (!saved_before) + saved_before = n; + + r = dns_label_unescape(&n, ln, sizeof(ln)); + if (r < 0) + return r; + + if (!saved_after) + saved_after = n; + + q = dns_label_unescape(&s, ls, sizeof(ls)); + if (q < 0) + return q; + + if (r == 0 && q == 0) + break; + if (r == 0 && saved_after == n) { + *ret = NULL; /* doesn't match */ + return 0; + } + + if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) { + + /* Not the same, let's jump back, and try with the next label again */ + s = old_suffix; + n = saved_after; + saved_after = saved_before = NULL; + } + } + + /* Found it! Now generate the new name */ + prefix = strndupa(name, saved_before - name); + + r = dns_name_concat(prefix, new_suffix, ret); + if (r < 0) + return r; + + return 1; +} + +int dns_name_between(const char *a, const char *b, const char *c) { + int n; + + /* Determine if b is strictly greater than a and strictly smaller than c. + We consider the order of names to be circular, so that if a is + strictly greater than c, we consider b to be between them if it is + either greater than a or smaller than c. This is how the canonical + DNS name order used in NSEC records work. */ + + n = dns_name_compare_func(a, c); + if (n == 0) + return -EINVAL; + else if (n < 0) + /* a<---b--->c */ + return dns_name_compare_func(a, b) < 0 && + dns_name_compare_func(b, c) < 0; + else + /* <--b--c a--b--> */ + return dns_name_compare_func(b, c) < 0 || + dns_name_compare_func(a, b) < 0; +} + +int dns_name_reverse(int family, const union in_addr_union *a, char **ret) { + const uint8_t *p; + int r; + + assert(a); + assert(ret); + + p = (const uint8_t*) a; + + if (family == AF_INET) + r = asprintf(ret, "%u.%u.%u.%u.in-addr.arpa", p[3], p[2], p[1], p[0]); + else if (family == AF_INET6) + r = asprintf(ret, "%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.ip6.arpa", + hexchar(p[15] & 0xF), hexchar(p[15] >> 4), hexchar(p[14] & 0xF), hexchar(p[14] >> 4), + hexchar(p[13] & 0xF), hexchar(p[13] >> 4), hexchar(p[12] & 0xF), hexchar(p[12] >> 4), + hexchar(p[11] & 0xF), hexchar(p[11] >> 4), hexchar(p[10] & 0xF), hexchar(p[10] >> 4), + hexchar(p[ 9] & 0xF), hexchar(p[ 9] >> 4), hexchar(p[ 8] & 0xF), hexchar(p[ 8] >> 4), + hexchar(p[ 7] & 0xF), hexchar(p[ 7] >> 4), hexchar(p[ 6] & 0xF), hexchar(p[ 6] >> 4), + hexchar(p[ 5] & 0xF), hexchar(p[ 5] >> 4), hexchar(p[ 4] & 0xF), hexchar(p[ 4] >> 4), + hexchar(p[ 3] & 0xF), hexchar(p[ 3] >> 4), hexchar(p[ 2] & 0xF), hexchar(p[ 2] >> 4), + hexchar(p[ 1] & 0xF), hexchar(p[ 1] >> 4), hexchar(p[ 0] & 0xF), hexchar(p[ 0] >> 4)); + else + return -EAFNOSUPPORT; + if (r < 0) + return -ENOMEM; + + return 0; +} + +int dns_name_address(const char *p, int *family, union in_addr_union *address) { + int r; + + assert(p); + assert(family); + assert(address); + + r = dns_name_endswith(p, "in-addr.arpa"); + if (r < 0) + return r; + if (r > 0) { + uint8_t a[4]; + unsigned i; + + for (i = 0; i < ELEMENTSOF(a); i++) { + char label[DNS_LABEL_MAX+1]; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + if (r > 3) + return -EINVAL; + + r = safe_atou8(label, &a[i]); + if (r < 0) + return r; + } + + r = dns_name_equal(p, "in-addr.arpa"); + if (r <= 0) + return r; + + *family = AF_INET; + address->in.s_addr = htobe32(((uint32_t) a[3] << 24) | + ((uint32_t) a[2] << 16) | + ((uint32_t) a[1] << 8) | + (uint32_t) a[0]); + + return 1; + } + + r = dns_name_endswith(p, "ip6.arpa"); + if (r < 0) + return r; + if (r > 0) { + struct in6_addr a; + unsigned i; + + for (i = 0; i < ELEMENTSOF(a.s6_addr); i++) { + char label[DNS_LABEL_MAX+1]; + int x, y; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r <= 0) + return r; + if (r != 1) + return -EINVAL; + x = unhexchar(label[0]); + if (x < 0) + return -EINVAL; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r <= 0) + return r; + if (r != 1) + return -EINVAL; + y = unhexchar(label[0]); + if (y < 0) + return -EINVAL; + + a.s6_addr[ELEMENTSOF(a.s6_addr) - i - 1] = (uint8_t) y << 4 | (uint8_t) x; + } + + r = dns_name_equal(p, "ip6.arpa"); + if (r <= 0) + return r; + + *family = AF_INET6; + address->in6 = a; + return 1; + } + + return 0; +} + +bool dns_name_is_root(const char *name) { + + assert(name); + + /* There are exactly two ways to encode the root domain name: + * as empty string, or with a single dot. */ + + return STR_IN_SET(name, "", "."); +} + +bool dns_name_is_single_label(const char *name) { + int r; + + assert(name); + + r = dns_name_parent(&name); + if (r <= 0) + return false; + + return dns_name_is_root(name); +} + +/* Encode a domain name according to RFC 1035 Section 3.1, without compression */ +int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical) { + uint8_t *label_length, *out; + int r; + + assert(domain); + assert(buffer); + + out = buffer; + + do { + /* Reserve a byte for label length */ + if (len <= 0) + return -ENOBUFS; + len--; + label_length = out; + out++; + + /* Convert and copy a single label. Note that + * dns_label_unescape() returns 0 when it hits the end + * of the domain name, which we rely on here to encode + * the trailing NUL byte. */ + r = dns_label_unescape(&domain, (char *) out, len); + if (r < 0) + return r; + + /* Optionally, output the name in DNSSEC canonical + * format, as described in RFC 4034, section 6.2. Or + * in other words: in lower-case. */ + if (canonical) + ascii_strlower_n((char*) out, (size_t) r); + + /* Fill label length, move forward */ + *label_length = r; + out += r; + len -= r; + + } while (r != 0); + + /* Verify the maximum size of the encoded name. The trailing + * dot + NUL byte account are included this time, hence + * compare against DNS_HOSTNAME_MAX + 2 (which is 255) this + * time. */ + if (out - buffer > DNS_HOSTNAME_MAX + 2) + return -EINVAL; + + return out - buffer; +} + +static bool srv_type_label_is_valid(const char *label, size_t n) { + size_t k; + + assert(label); + + if (n < 2) /* Label needs to be at least 2 chars long */ + return false; + + if (label[0] != '_') /* First label char needs to be underscore */ + return false; + + /* Second char must be a letter */ + if (!(label[1] >= 'A' && label[1] <= 'Z') && + !(label[1] >= 'a' && label[1] <= 'z')) + return false; + + /* Third and further chars must be alphanumeric or a hyphen */ + for (k = 2; k < n; k++) { + if (!(label[k] >= 'A' && label[k] <= 'Z') && + !(label[k] >= 'a' && label[k] <= 'z') && + !(label[k] >= '0' && label[k] <= '9') && + label[k] != '-') + return false; + } + + return true; +} + +bool dns_srv_type_is_valid(const char *name) { + unsigned c = 0; + int r; + + if (!name) + return false; + + for (;;) { + char label[DNS_LABEL_MAX]; + + /* This more or less implements RFC 6335, Section 5.1 */ + + r = dns_label_unescape(&name, label, sizeof(label)); + if (r < 0) + return false; + if (r == 0) + break; + + if (c >= 2) + return false; + + if (!srv_type_label_is_valid(label, r)) + return false; + + c++; + } + + return c == 2; /* exactly two labels */ +} + +bool dns_service_name_is_valid(const char *name) { + size_t l; + + /* This more or less implements RFC 6763, Section 4.1.1 */ + + if (!name) + return false; + + if (!utf8_is_valid(name)) + return false; + + if (string_has_cc(name, NULL)) + return false; + + l = strlen(name); + if (l <= 0) + return false; + if (l > 63) + return false; + + return true; +} + +int dns_service_join(const char *name, const char *type, const char *domain, char **ret) { + char escaped[DNS_LABEL_ESCAPED_MAX]; + _cleanup_free_ char *n = NULL; + int r; + + assert(type); + assert(domain); + assert(ret); + + if (!dns_srv_type_is_valid(type)) + return -EINVAL; + + if (!name) + return dns_name_concat(type, domain, ret); + + if (!dns_service_name_is_valid(name)) + return -EINVAL; + + r = dns_label_escape(name, strlen(name), escaped, sizeof(escaped)); + if (r < 0) + return r; + + r = dns_name_concat(type, domain, &n); + if (r < 0) + return r; + + return dns_name_concat(escaped, n, ret); +} + +static bool dns_service_name_label_is_valid(const char *label, size_t n) { + char *s; + + assert(label); + + if (memchr(label, 0, n)) + return false; + + s = strndupa(label, n); + return dns_service_name_is_valid(s); +} + +int dns_service_split(const char *joined, char **_name, char **_type, char **_domain) { + _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL; + const char *p = joined, *q = NULL, *d = NULL; + char a[DNS_LABEL_MAX], b[DNS_LABEL_MAX], c[DNS_LABEL_MAX]; + int an, bn, cn, r; + unsigned x = 0; + + assert(joined); + + /* Get first label from the full name */ + an = dns_label_unescape(&p, a, sizeof(a)); + if (an < 0) + return an; + + if (an > 0) { + x++; + + /* If there was a first label, try to get the second one */ + bn = dns_label_unescape(&p, b, sizeof(b)); + if (bn < 0) + return bn; + + if (bn > 0) { + x++; + + /* If there was a second label, try to get the third one */ + q = p; + cn = dns_label_unescape(&p, c, sizeof(c)); + if (cn < 0) + return cn; + + if (cn > 0) + x++; + } else + cn = 0; + } else + an = 0; + + if (x >= 2 && srv_type_label_is_valid(b, bn)) { + + if (x >= 3 && srv_type_label_is_valid(c, cn)) { + + if (dns_service_name_label_is_valid(a, an)) { + /* OK, got . . . */ + + name = strndup(a, an); + if (!name) + return -ENOMEM; + + type = strjoin(b, ".", c, NULL); + if (!type) + return -ENOMEM; + + d = p; + goto finish; + } + + } else if (srv_type_label_is_valid(a, an)) { + + /* OK, got . . */ + + name = NULL; + + type = strjoin(a, ".", b, NULL); + if (!type) + return -ENOMEM; + + d = q; + goto finish; + } + } + + name = NULL; + type = NULL; + d = joined; + +finish: + r = dns_name_normalize(d, &domain); + if (r < 0) + return r; + + if (_domain) { + *_domain = domain; + domain = NULL; + } + + if (_type) { + *_type = type; + type = NULL; + } + + if (_name) { + *_name = name; + name = NULL; + } + + return 0; +} + +static int dns_name_build_suffix_table(const char *name, const char*table[]) { + const char *p; + unsigned n = 0; + int r; + + assert(name); + assert(table); + + p = name; + for (;;) { + if (n > DNS_N_LABELS_MAX) + return -EINVAL; + + table[n] = p; + r = dns_name_parent(&p); + if (r < 0) + return r; + if (r == 0) + break; + + n++; + } + + return (int) n; +} + +int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) { + const char* labels[DNS_N_LABELS_MAX+1]; + int n; + + assert(name); + assert(ret); + + n = dns_name_build_suffix_table(name, labels); + if (n < 0) + return n; + + if ((unsigned) n < n_labels) + return -EINVAL; + + *ret = labels[n - n_labels]; + return (int) (n - n_labels); +} + +int dns_name_skip(const char *a, unsigned n_labels, const char **ret) { + int r; + + assert(a); + assert(ret); + + for (; n_labels > 0; n_labels--) { + r = dns_name_parent(&a); + if (r < 0) + return r; + if (r == 0) { + *ret = ""; + return 0; + } + } + + *ret = a; + return 1; +} + +int dns_name_count_labels(const char *name) { + unsigned n = 0; + const char *p; + int r; + + assert(name); + + p = name; + for (;;) { + r = dns_name_parent(&p); + if (r < 0) + return r; + if (r == 0) + break; + + if (n >= DNS_N_LABELS_MAX) + return -EINVAL; + + n++; + } + + return (int) n; +} + +int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) { + int r; + + assert(a); + assert(b); + + r = dns_name_skip(a, n_labels, &a); + if (r <= 0) + return r; + + return dns_name_equal(a, b); +} + +int dns_name_common_suffix(const char *a, const char *b, const char **ret) { + const char *a_labels[DNS_N_LABELS_MAX+1], *b_labels[DNS_N_LABELS_MAX+1]; + int n = 0, m = 0, k = 0, r, q; + + assert(a); + assert(b); + assert(ret); + + /* Determines the common suffix of domain names a and b */ + + n = dns_name_build_suffix_table(a, a_labels); + if (n < 0) + return n; + + m = dns_name_build_suffix_table(b, b_labels); + if (m < 0) + return m; + + for (;;) { + char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + const char *x, *y; + + if (k >= n || k >= m) { + *ret = a_labels[n - k]; + return 0; + } + + x = a_labels[n - 1 - k]; + r = dns_label_unescape(&x, la, sizeof(la)); + if (r < 0) + return r; + + y = b_labels[m - 1 - k]; + q = dns_label_unescape(&y, lb, sizeof(lb)); + if (q < 0) + return q; + + if (r != q || ascii_strcasecmp_n(la, lb, r) != 0) { + *ret = a_labels[n - k]; + return 0; + } + + k++; + } +} + +int dns_name_apply_idna(const char *name, char **ret) { + _cleanup_free_ char *buf = NULL; + size_t n = 0, allocated = 0; + bool first = true; + int r, q; + + assert(name); + assert(ret); + + for (;;) { + char label[DNS_LABEL_MAX]; + + r = dns_label_unescape(&name, label, sizeof(label)); + if (r < 0) + return r; + if (r == 0) + break; + + q = dns_label_apply_idna(label, r, label, sizeof(label)); + if (q < 0) + return q; + if (q > 0) + r = q; + + if (!GREEDY_REALLOC(buf, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + r = dns_label_escape(label, r, buf + n + !first, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + if (first) + first = false; + else + buf[n++] = '.'; + + n +=r; + } + + if (n > DNS_HOSTNAME_MAX) + return -EINVAL; + + if (!GREEDY_REALLOC(buf, allocated, n + 1)) + return -ENOMEM; + + buf[n] = 0; + *ret = buf; + buf = NULL; + + return (int) n; +} diff --git a/src/libshared/dns-domain.h b/src/libshared/dns-domain.h new file mode 100644 index 0000000000..af780f0b8b --- /dev/null +++ b/src/libshared/dns-domain.h @@ -0,0 +1,109 @@ +#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 +#include +#include +#include + +#include "hashmap.h" +#include "in-addr-util.h" + +/* Length of a single label, with all escaping removed, excluding any trailing dot or NUL byte */ +#define DNS_LABEL_MAX 63 + +/* Worst case length of a single label, with all escaping applied and room for a trailing NUL byte. */ +#define DNS_LABEL_ESCAPED_MAX (DNS_LABEL_MAX*4+1) + +/* Maximum length of a full hostname, consisting of a series of unescaped labels, and no trailing dot or NUL byte */ +#define DNS_HOSTNAME_MAX 253 + +/* Maximum length of a full hostname, on the wire, including the final NUL byte */ +#define DNS_WIRE_FOMAT_HOSTNAME_MAX 255 + +/* Maximum number of labels per valid hostname */ +#define DNS_N_LABELS_MAX 127 + +int dns_label_unescape(const char **name, char *dest, size_t sz); +int dns_label_unescape_suffix(const char *name, const char **label_end, char *dest, size_t sz); +int dns_label_escape(const char *p, size_t l, char *dest, size_t sz); +int dns_label_escape_new(const char *p, size_t l, char **ret); + +static inline int dns_name_parent(const char **name) { + return dns_label_unescape(name, NULL, DNS_LABEL_MAX); +} + +int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); +int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); + +int dns_name_concat(const char *a, const char *b, char **ret); + +static inline int dns_name_normalize(const char *s, char **ret) { + /* dns_name_concat() normalizes as a side-effect */ + return dns_name_concat(s, NULL, ret); +} + +static inline int dns_name_is_valid(const char *s) { + int r; + + /* dns_name_normalize() verifies as a side effect */ + r = dns_name_normalize(s, NULL); + if (r == -EINVAL) + return 0; + if (r < 0) + return r; + return 1; +} + +void dns_name_hash_func(const void *s, struct siphash *state); +int dns_name_compare_func(const void *a, const void *b); +extern const struct hash_ops dns_name_hash_ops; + +int dns_name_between(const char *a, const char *b, const char *c); +int dns_name_equal(const char *x, const char *y); +int dns_name_endswith(const char *name, const char *suffix); +int dns_name_startswith(const char *name, const char *prefix); + +int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret); + +int dns_name_reverse(int family, const union in_addr_union *a, char **ret); +int dns_name_address(const char *p, int *family, union in_addr_union *a); + +bool dns_name_is_root(const char *name); +bool dns_name_is_single_label(const char *name); + +int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical); + +bool dns_srv_type_is_valid(const char *name); +bool dns_service_name_is_valid(const char *name); + +int dns_service_join(const char *name, const char *type, const char *domain, char **ret); +int dns_service_split(const char *joined, char **name, char **type, char **domain); + +int dns_name_suffix(const char *name, unsigned n_labels, const char **ret); +int dns_name_count_labels(const char *name); + +int dns_name_skip(const char *a, unsigned n_labels, const char **ret); +int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b); + +int dns_name_common_suffix(const char *a, const char *b, const char **ret); + +int dns_name_apply_idna(const char *name, char **ret); diff --git a/src/libshared/dropin.c b/src/libshared/dropin.c new file mode 100644 index 0000000000..b9cd952ac8 --- /dev/null +++ b/src/libshared/dropin.c @@ -0,0 +1,251 @@ +/*** + This file is part of systemd. + + Copyright 2014 Zbigniew Jędrzejewski-Szmek + + 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 +#include +#include +#include + +#include "alloc-util.h" +#include "conf-files.h" +#include "dropin.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio-label.h" +#include "hashmap.h" +#include "log.h" +#include "macro.h" +#include "mkdir.h" +#include "path-util.h" +#include "set.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" + +int drop_in_file(const char *dir, const char *unit, unsigned level, + const char *name, char **_p, char **_q) { + + _cleanup_free_ char *b = NULL; + char *p, *q; + + char prefix[DECIMAL_STR_MAX(unsigned)]; + + assert(unit); + assert(name); + assert(_p); + assert(_q); + + sprintf(prefix, "%u", level); + + b = xescape(name, "/."); + if (!b) + return -ENOMEM; + + if (!filename_is_valid(b)) + return -EINVAL; + + p = strjoin(dir, "/", unit, ".d", NULL); + if (!p) + return -ENOMEM; + + q = strjoin(p, "/", prefix, "-", b, ".conf", NULL); + if (!q) { + free(p); + return -ENOMEM; + } + + *_p = p; + *_q = q; + return 0; +} + +int write_drop_in(const char *dir, const char *unit, unsigned level, + const char *name, const char *data) { + + _cleanup_free_ char *p = NULL, *q = NULL; + int r; + + assert(dir); + assert(unit); + assert(name); + assert(data); + + r = drop_in_file(dir, unit, level, name, &p, &q); + if (r < 0) + return r; + + (void) mkdir_p(p, 0755); + return write_string_file_atomic_label(q, data); +} + +int write_drop_in_format(const char *dir, const char *unit, unsigned level, + const char *name, const char *format, ...) { + _cleanup_free_ char *p = NULL; + va_list ap; + int r; + + assert(dir); + assert(unit); + assert(name); + assert(format); + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return write_drop_in(dir, unit, level, name, p); +} + +static int iterate_dir( + const char *path, + UnitDependency dependency, + dependency_consumer_t consumer, + void *arg, + char ***strv) { + + _cleanup_closedir_ DIR *d = NULL; + int r; + + assert(path); + + /* The config directories are special, since the order of the + * drop-ins matters */ + if (dependency < 0) { + r = strv_extend(strv, path); + if (r < 0) + return log_oom(); + + return 0; + } + + assert(consumer); + + d = opendir(path); + if (!d) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open directory %s: %m", path); + } + + for (;;) { + struct dirent *de; + _cleanup_free_ char *f = NULL; + + errno = 0; + de = readdir(d); + if (!de && errno > 0) + return log_error_errno(errno, "Failed to read directory %s: %m", path); + + if (!de) + break; + + if (hidden_or_backup_file(de->d_name)) + continue; + + f = strjoin(path, "/", de->d_name, NULL); + if (!f) + return log_oom(); + + r = consumer(dependency, de->d_name, f, arg); + if (r < 0) + return r; + } + + return 0; +} + +int unit_file_process_dir( + Set *unit_path_cache, + const char *unit_path, + const char *name, + const char *suffix, + UnitDependency dependency, + dependency_consumer_t consumer, + void *arg, + char ***strv) { + + _cleanup_free_ char *path = NULL; + int r; + + assert(unit_path); + assert(name); + assert(suffix); + + path = strjoin(unit_path, "/", name, suffix, NULL); + if (!path) + return log_oom(); + + if (!unit_path_cache || set_get(unit_path_cache, path)) + (void) iterate_dir(path, dependency, consumer, arg, strv); + + if (unit_name_is_valid(name, UNIT_NAME_INSTANCE)) { + _cleanup_free_ char *template = NULL, *p = NULL; + /* Also try the template dir */ + + r = unit_name_template(name, &template); + if (r < 0) + return log_error_errno(r, "Failed to generate template from unit name: %m"); + + p = strjoin(unit_path, "/", template, suffix, NULL); + if (!p) + return log_oom(); + + if (!unit_path_cache || set_get(unit_path_cache, p)) + (void) iterate_dir(p, dependency, consumer, arg, strv); + } + + return 0; +} + +int unit_file_find_dropin_paths( + char **lookup_path, + Set *unit_path_cache, + Set *names, + char ***paths) { + + _cleanup_strv_free_ char **strv = NULL, **ans = NULL; + Iterator i; + char *t; + int r; + + assert(paths); + + SET_FOREACH(t, names, i) { + char **p; + + STRV_FOREACH(p, lookup_path) + unit_file_process_dir(unit_path_cache, *p, t, ".d", _UNIT_DEPENDENCY_INVALID, NULL, NULL, &strv); + } + + if (strv_isempty(strv)) + return 0; + + r = conf_files_list_strv(&ans, ".conf", NULL, (const char**) strv); + if (r < 0) + return log_warning_errno(r, "Failed to get list of configuration files: %m"); + + *paths = ans; + ans = NULL; + return 1; +} diff --git a/src/libshared/dropin.h b/src/libshared/dropin.h new file mode 100644 index 0000000000..c1936f397b --- /dev/null +++ b/src/libshared/dropin.h @@ -0,0 +1,61 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Zbigniew Jędrzejewski-Szmek + + 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 "hashmap.h" +#include "macro.h" +#include "set.h" +#include "unit-name.h" + +int drop_in_file(const char *dir, const char *unit, unsigned level, + const char *name, char **_p, char **_q); + +int write_drop_in(const char *dir, const char *unit, unsigned level, + const char *name, const char *data); + +int write_drop_in_format(const char *dir, const char *unit, unsigned level, + const char *name, const char *format, ...) _printf_(5, 6); + +/** + * This callback will be called for each directory entry @entry, + * with @filepath being the full path to the entry. + * + * If return value is negative, loop will be aborted. + */ +typedef int (*dependency_consumer_t)(UnitDependency dependency, + const char *entry, + const char* filepath, + void *arg); + +int unit_file_process_dir( + Set * unit_path_cache, + const char *unit_path, + const char *name, + const char *suffix, + UnitDependency dependency, + dependency_consumer_t consumer, + void *arg, + char ***strv); + +int unit_file_find_dropin_paths( + char **lookup_path, + Set *unit_path_cache, + Set *names, + char ***paths); diff --git a/src/libshared/efivars.c b/src/libshared/efivars.c new file mode 100644 index 0000000000..5073c61740 --- /dev/null +++ b/src/libshared/efivars.c @@ -0,0 +1,715 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "dirent-util.h" +#include "efivars.h" +#include "fd-util.h" +#include "io-util.h" +#include "macro.h" +#include "parse-util.h" +#include "stdio-util.h" +#include "time-util.h" +#include "utf8.h" +#include "util.h" +#include "virt.h" + +#ifdef ENABLE_EFI + +#define LOAD_OPTION_ACTIVE 0x00000001 +#define MEDIA_DEVICE_PATH 0x04 +#define MEDIA_HARDDRIVE_DP 0x01 +#define MEDIA_FILEPATH_DP 0x04 +#define SIGNATURE_TYPE_GUID 0x02 +#define MBR_TYPE_EFI_PARTITION_TABLE_HEADER 0x02 +#define END_DEVICE_PATH_TYPE 0x7f +#define END_ENTIRE_DEVICE_PATH_SUBTYPE 0xff +#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001 + +struct boot_option { + uint32_t attr; + uint16_t path_len; + uint16_t title[]; +} _packed_; + +struct drive_path { + uint32_t part_nr; + uint64_t part_start; + uint64_t part_size; + char signature[16]; + uint8_t mbr_type; + uint8_t signature_type; +} _packed_; + +struct device_path { + uint8_t type; + uint8_t sub_type; + uint16_t length; + union { + uint16_t path[0]; + struct drive_path drive; + }; +} _packed_; + +bool is_efi_boot(void) { + return access("/sys/firmware/efi", F_OK) >= 0; +} + +static int read_flag(const char *varname) { + int r; + _cleanup_free_ void *v = NULL; + size_t s; + uint8_t b; + + r = efi_get_variable(EFI_VENDOR_GLOBAL, varname, NULL, &v, &s); + if (r < 0) + return r; + + if (s != 1) + return -EINVAL; + + b = *(uint8_t *)v; + r = b > 0; + return r; +} + +bool is_efi_secure_boot(void) { + return read_flag("SecureBoot") > 0; +} + +bool is_efi_secure_boot_setup_mode(void) { + return read_flag("SetupMode") > 0; +} + +int efi_reboot_to_firmware_supported(void) { + int r; + size_t s; + uint64_t b; + _cleanup_free_ void *v = NULL; + + if (!is_efi_boot() || detect_container() > 0) + return -EOPNOTSUPP; + + r = efi_get_variable(EFI_VENDOR_GLOBAL, "OsIndicationsSupported", NULL, &v, &s); + if (r < 0) + return r; + else if (s != sizeof(uint64_t)) + return -EINVAL; + + b = *(uint64_t *)v; + b &= EFI_OS_INDICATIONS_BOOT_TO_FW_UI; + return b > 0 ? 0 : -EOPNOTSUPP; +} + +static int get_os_indications(uint64_t *os_indication) { + int r; + size_t s; + _cleanup_free_ void *v = NULL; + + r = efi_reboot_to_firmware_supported(); + if (r < 0) + return r; + + r = efi_get_variable(EFI_VENDOR_GLOBAL, "OsIndications", NULL, &v, &s); + if (r == -ENOENT) { + /* Some firmware implementations that do support + * OsIndications and report that with + * OsIndicationsSupported will remove the + * OsIndications variable when it is unset. Let's + * pretend it's 0 then, to hide this implementation + * detail. Note that this call will return -ENOENT + * then only if the support for OsIndications is + * missing entirely, as determined by + * efi_reboot_to_firmware_supported() above. */ + *os_indication = 0; + return 0; + } else if (r < 0) + return r; + else if (s != sizeof(uint64_t)) + return -EINVAL; + + *os_indication = *(uint64_t *)v; + return 0; +} + +int efi_get_reboot_to_firmware(void) { + int r; + uint64_t b; + + r = get_os_indications(&b); + if (r < 0) + return r; + + return !!(b & EFI_OS_INDICATIONS_BOOT_TO_FW_UI); +} + +int efi_set_reboot_to_firmware(bool value) { + int r; + uint64_t b, b_new; + + r = get_os_indications(&b); + if (r < 0) + return r; + + if (value) + b_new = b | EFI_OS_INDICATIONS_BOOT_TO_FW_UI; + else + b_new = b & ~EFI_OS_INDICATIONS_BOOT_TO_FW_UI; + + /* Avoid writing to efi vars store if we can due to firmware bugs. */ + if (b != b_new) + return efi_set_variable(EFI_VENDOR_GLOBAL, "OsIndications", &b_new, sizeof(uint64_t)); + + return 0; +} + +int efi_get_variable( + sd_id128_t vendor, + const char *name, + uint32_t *attribute, + void **value, + size_t *size) { + + _cleanup_close_ int fd = -1; + _cleanup_free_ char *p = NULL; + uint32_t a; + ssize_t n; + struct stat st; + _cleanup_free_ void *buf = NULL; + + assert(name); + assert(value); + assert(size); + + if (asprintf(&p, + "/sys/firmware/efi/efivars/%s-%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + name, SD_ID128_FORMAT_VAL(vendor)) < 0) + return -ENOMEM; + + fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return -errno; + + if (fstat(fd, &st) < 0) + return -errno; + if (st.st_size < 4) + return -EIO; + if (st.st_size > 4*1024*1024 + 4) + return -E2BIG; + + n = read(fd, &a, sizeof(a)); + if (n < 0) + return -errno; + if (n != sizeof(a)) + return -EIO; + + buf = malloc(st.st_size - 4 + 2); + if (!buf) + return -ENOMEM; + + n = read(fd, buf, (size_t) st.st_size - 4); + if (n < 0) + return -errno; + if (n != (ssize_t) st.st_size - 4) + return -EIO; + + /* Always NUL terminate (2 bytes, to protect UTF-16) */ + ((char*) buf)[st.st_size - 4] = 0; + ((char*) buf)[st.st_size - 4 + 1] = 0; + + *value = buf; + buf = NULL; + *size = (size_t) st.st_size - 4; + + if (attribute) + *attribute = a; + + return 0; +} + +int efi_set_variable( + sd_id128_t vendor, + const char *name, + const void *value, + size_t size) { + + struct var { + uint32_t attr; + char buf[]; + } _packed_ * _cleanup_free_ buf = NULL; + _cleanup_free_ char *p = NULL; + _cleanup_close_ int fd = -1; + + assert(name); + + if (asprintf(&p, + "/sys/firmware/efi/efivars/%s-%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + name, SD_ID128_FORMAT_VAL(vendor)) < 0) + return -ENOMEM; + + if (size == 0) { + if (unlink(p) < 0) + return -errno; + return 0; + } + + fd = open(p, O_WRONLY|O_CREAT|O_NOCTTY|O_CLOEXEC, 0644); + if (fd < 0) + return -errno; + + buf = malloc(sizeof(uint32_t) + size); + if (!buf) + return -ENOMEM; + + buf->attr = EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS; + memcpy(buf->buf, value, size); + + return loop_write(fd, buf, sizeof(uint32_t) + size, false); +} + +int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p) { + _cleanup_free_ void *s = NULL; + size_t ss = 0; + int r; + char *x; + + r = efi_get_variable(vendor, name, NULL, &s, &ss); + if (r < 0) + return r; + + x = utf16_to_utf8(s, ss); + if (!x) + return -ENOMEM; + + *p = x; + return 0; +} + +static size_t utf16_size(const uint16_t *s) { + size_t l = 0; + + while (s[l] > 0) + l++; + + return (l+1) * sizeof(uint16_t); +} + +static void efi_guid_to_id128(const void *guid, sd_id128_t *id128) { + struct uuid { + uint32_t u1; + uint16_t u2; + uint16_t u3; + uint8_t u4[8]; + } _packed_; + const struct uuid *uuid = guid; + + id128->bytes[0] = (uuid->u1 >> 24) & 0xff; + id128->bytes[1] = (uuid->u1 >> 16) & 0xff; + id128->bytes[2] = (uuid->u1 >> 8) & 0xff; + id128->bytes[3] = (uuid->u1) & 0xff; + id128->bytes[4] = (uuid->u2 >> 8) & 0xff; + id128->bytes[5] = (uuid->u2) & 0xff; + id128->bytes[6] = (uuid->u3 >> 8) & 0xff; + id128->bytes[7] = (uuid->u3) & 0xff; + memcpy(&id128->bytes[8], uuid->u4, sizeof(uuid->u4)); +} + +int efi_get_boot_option( + uint16_t id, + char **title, + sd_id128_t *part_uuid, + char **path, + bool *active) { + + char boot_id[9]; + _cleanup_free_ uint8_t *buf = NULL; + size_t l; + struct boot_option *header; + size_t title_size; + _cleanup_free_ char *s = NULL, *p = NULL; + sd_id128_t p_uuid = SD_ID128_NULL; + int r; + + xsprintf(boot_id, "Boot%04X", id); + r = efi_get_variable(EFI_VENDOR_GLOBAL, boot_id, NULL, (void **)&buf, &l); + if (r < 0) + return r; + if (l < sizeof(struct boot_option)) + return -ENOENT; + + header = (struct boot_option *)buf; + title_size = utf16_size(header->title); + if (title_size > l - offsetof(struct boot_option, title)) + return -EINVAL; + + if (title) { + s = utf16_to_utf8(header->title, title_size); + if (!s) + return -ENOMEM; + } + + if (header->path_len > 0) { + uint8_t *dbuf; + size_t dnext; + + dbuf = buf + offsetof(struct boot_option, title) + title_size; + dnext = 0; + while (dnext < header->path_len) { + struct device_path *dpath; + + dpath = (struct device_path *)(dbuf + dnext); + if (dpath->length < 4) + break; + + /* Type 0x7F – End of Hardware Device Path, Sub-Type 0xFF – End Entire Device Path */ + if (dpath->type == END_DEVICE_PATH_TYPE && dpath->sub_type == END_ENTIRE_DEVICE_PATH_SUBTYPE) + break; + + dnext += dpath->length; + + /* Type 0x04 – Media Device Path */ + if (dpath->type != MEDIA_DEVICE_PATH) + continue; + + /* Sub-Type 1 – Hard Drive */ + if (dpath->sub_type == MEDIA_HARDDRIVE_DP) { + /* 0x02 – GUID Partition Table */ + if (dpath->drive.mbr_type != MBR_TYPE_EFI_PARTITION_TABLE_HEADER) + continue; + + /* 0x02 – GUID signature */ + if (dpath->drive.signature_type != SIGNATURE_TYPE_GUID) + continue; + + if (part_uuid) + efi_guid_to_id128(dpath->drive.signature, &p_uuid); + continue; + } + + /* Sub-Type 4 – File Path */ + if (dpath->sub_type == MEDIA_FILEPATH_DP && !p && path) { + p = utf16_to_utf8(dpath->path, dpath->length-4); + efi_tilt_backslashes(p); + continue; + } + } + } + + if (title) { + *title = s; + s = NULL; + } + if (part_uuid) + *part_uuid = p_uuid; + if (path) { + *path = p; + p = NULL; + } + if (active) + *active = !!(header->attr & LOAD_OPTION_ACTIVE); + + return 0; +} + +static void to_utf16(uint16_t *dest, const char *src) { + int i; + + for (i = 0; src[i] != '\0'; i++) + dest[i] = src[i]; + dest[i] = '\0'; +} + +struct guid { + uint32_t u1; + uint16_t u2; + uint16_t u3; + uint8_t u4[8]; +} _packed_; + +static void id128_to_efi_guid(sd_id128_t id, void *guid) { + struct guid *uuid = guid; + + uuid->u1 = id.bytes[0] << 24 | id.bytes[1] << 16 | id.bytes[2] << 8 | id.bytes[3]; + uuid->u2 = id.bytes[4] << 8 | id.bytes[5]; + uuid->u3 = id.bytes[6] << 8 | id.bytes[7]; + memcpy(uuid->u4, id.bytes+8, sizeof(uuid->u4)); +} + +static uint16_t *tilt_slashes(uint16_t *s) { + uint16_t *p; + + for (p = s; *p; p++) + if (*p == '/') + *p = '\\'; + + return s; +} + +int efi_add_boot_option(uint16_t id, const char *title, + uint32_t part, uint64_t pstart, uint64_t psize, + sd_id128_t part_uuid, const char *path) { + char boot_id[9]; + size_t size; + size_t title_len; + size_t path_len; + struct boot_option *option; + struct device_path *devicep; + _cleanup_free_ char *buf = NULL; + + title_len = (strlen(title)+1) * 2; + path_len = (strlen(path)+1) * 2; + + buf = calloc(sizeof(struct boot_option) + title_len + + sizeof(struct drive_path) + + sizeof(struct device_path) + path_len, 1); + if (!buf) + return -ENOMEM; + + /* header */ + option = (struct boot_option *)buf; + option->attr = LOAD_OPTION_ACTIVE; + option->path_len = offsetof(struct device_path, drive) + sizeof(struct drive_path) + + offsetof(struct device_path, path) + path_len + + offsetof(struct device_path, path); + to_utf16(option->title, title); + size = offsetof(struct boot_option, title) + title_len; + + /* partition info */ + devicep = (struct device_path *)(buf + size); + devicep->type = MEDIA_DEVICE_PATH; + devicep->sub_type = MEDIA_HARDDRIVE_DP; + devicep->length = offsetof(struct device_path, drive) + sizeof(struct drive_path); + devicep->drive.part_nr = part; + devicep->drive.part_start = pstart; + devicep->drive.part_size = psize; + devicep->drive.signature_type = SIGNATURE_TYPE_GUID; + devicep->drive.mbr_type = MBR_TYPE_EFI_PARTITION_TABLE_HEADER; + id128_to_efi_guid(part_uuid, devicep->drive.signature); + size += devicep->length; + + /* path to loader */ + devicep = (struct device_path *)(buf + size); + devicep->type = MEDIA_DEVICE_PATH; + devicep->sub_type = MEDIA_FILEPATH_DP; + devicep->length = offsetof(struct device_path, path) + path_len; + to_utf16(devicep->path, path); + tilt_slashes(devicep->path); + size += devicep->length; + + /* end of path */ + devicep = (struct device_path *)(buf + size); + devicep->type = END_DEVICE_PATH_TYPE; + devicep->sub_type = END_ENTIRE_DEVICE_PATH_SUBTYPE; + devicep->length = offsetof(struct device_path, path); + size += devicep->length; + + xsprintf(boot_id, "Boot%04X", id); + return efi_set_variable(EFI_VENDOR_GLOBAL, boot_id, buf, size); +} + +int efi_remove_boot_option(uint16_t id) { + char boot_id[9]; + + xsprintf(boot_id, "Boot%04X", id); + return efi_set_variable(EFI_VENDOR_GLOBAL, boot_id, NULL, 0); +} + +int efi_get_boot_order(uint16_t **order) { + _cleanup_free_ void *buf = NULL; + size_t l; + int r; + + r = efi_get_variable(EFI_VENDOR_GLOBAL, "BootOrder", NULL, &buf, &l); + if (r < 0) + return r; + + if (l <= 0) + return -ENOENT; + + if (l % sizeof(uint16_t) > 0 || + l / sizeof(uint16_t) > INT_MAX) + return -EINVAL; + + *order = buf; + buf = NULL; + return (int) (l / sizeof(uint16_t)); +} + +int efi_set_boot_order(uint16_t *order, size_t n) { + return efi_set_variable(EFI_VENDOR_GLOBAL, "BootOrder", order, n * sizeof(uint16_t)); +} + +static int boot_id_hex(const char s[4]) { + int i; + int id = 0; + + for (i = 0; i < 4; i++) + if (s[i] >= '0' && s[i] <= '9') + id |= (s[i] - '0') << (3 - i) * 4; + else if (s[i] >= 'A' && s[i] <= 'F') + id |= (s[i] - 'A' + 10) << (3 - i) * 4; + else + return -EINVAL; + + return id; +} + +static int cmp_uint16(const void *_a, const void *_b) { + const uint16_t *a = _a, *b = _b; + + return (int)*a - (int)*b; +} + +int efi_get_boot_options(uint16_t **options) { + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *de; + _cleanup_free_ uint16_t *list = NULL; + size_t alloc = 0; + int count = 0; + + assert(options); + + dir = opendir("/sys/firmware/efi/efivars/"); + if (!dir) + return -errno; + + FOREACH_DIRENT(de, dir, return -errno) { + int id; + + if (strncmp(de->d_name, "Boot", 4) != 0) + continue; + + if (strlen(de->d_name) != 45) + continue; + + if (strcmp(de->d_name + 8, "-8be4df61-93ca-11d2-aa0d-00e098032b8c") != 0) + continue; + + id = boot_id_hex(de->d_name + 4); + if (id < 0) + continue; + + if (!GREEDY_REALLOC(list, alloc, count + 1)) + return -ENOMEM; + + list[count++] = id; + } + + qsort_safe(list, count, sizeof(uint16_t), cmp_uint16); + + *options = list; + list = NULL; + return count; +} + +static int read_usec(sd_id128_t vendor, const char *name, usec_t *u) { + _cleanup_free_ char *j = NULL; + int r; + uint64_t x = 0; + + assert(name); + assert(u); + + r = efi_get_variable_string(EFI_VENDOR_LOADER, name, &j); + if (r < 0) + return r; + + r = safe_atou64(j, &x); + if (r < 0) + return r; + + *u = x; + return 0; +} + +int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader) { + uint64_t x, y; + int r; + + assert(firmware); + assert(loader); + + r = read_usec(EFI_VENDOR_LOADER, "LoaderTimeInitUSec", &x); + if (r < 0) + return r; + + r = read_usec(EFI_VENDOR_LOADER, "LoaderTimeExecUSec", &y); + if (r < 0) + return r; + + if (y == 0 || y < x) + return -EIO; + + if (y > USEC_PER_HOUR) + return -EIO; + + *firmware = x; + *loader = y; + + return 0; +} + +int efi_loader_get_device_part_uuid(sd_id128_t *u) { + _cleanup_free_ char *p = NULL; + int r, parsed[16]; + + r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderDevicePartUUID", &p); + if (r < 0) + return r; + + if (sscanf(p, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + &parsed[0], &parsed[1], &parsed[2], &parsed[3], + &parsed[4], &parsed[5], &parsed[6], &parsed[7], + &parsed[8], &parsed[9], &parsed[10], &parsed[11], + &parsed[12], &parsed[13], &parsed[14], &parsed[15]) != 16) + return -EIO; + + if (u) { + unsigned i; + + for (i = 0; i < ELEMENTSOF(parsed); i++) + u->bytes[i] = parsed[i]; + } + + return 0; +} + +#endif + +char *efi_tilt_backslashes(char *s) { + char *p; + + for (p = s; *p; p++) + if (*p == '\\') + *p = '/'; + + return s; +} diff --git a/src/libshared/efivars.h b/src/libshared/efivars.h new file mode 100644 index 0000000000..243151f922 --- /dev/null +++ b/src/libshared/efivars.h @@ -0,0 +1,131 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 +#include + +#include + +#include "time-util.h" + +#define EFI_VENDOR_LOADER SD_ID128_MAKE(4a,67,b0,82,0a,4c,41,cf,b6,c7,44,0b,29,bb,8c,4f) +#define EFI_VENDOR_GLOBAL SD_ID128_MAKE(8b,e4,df,61,93,ca,11,d2,aa,0d,00,e0,98,03,2b,8c) +#define EFI_VARIABLE_NON_VOLATILE 0x0000000000000001 +#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x0000000000000002 +#define EFI_VARIABLE_RUNTIME_ACCESS 0x0000000000000004 + +#ifdef ENABLE_EFI + +bool is_efi_boot(void); +bool is_efi_secure_boot(void); +bool is_efi_secure_boot_setup_mode(void); +int efi_reboot_to_firmware_supported(void); +int efi_get_reboot_to_firmware(void); +int efi_set_reboot_to_firmware(bool value); + +int efi_get_variable(sd_id128_t vendor, const char *name, uint32_t *attribute, void **value, size_t *size); +int efi_set_variable(sd_id128_t vendor, const char *name, const void *value, size_t size); +int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p); + +int efi_get_boot_option(uint16_t nr, char **title, sd_id128_t *part_uuid, char **path, bool *active); +int efi_add_boot_option(uint16_t id, const char *title, uint32_t part, uint64_t pstart, uint64_t psize, sd_id128_t part_uuid, const char *path); +int efi_remove_boot_option(uint16_t id); +int efi_get_boot_order(uint16_t **order); +int efi_set_boot_order(uint16_t *order, size_t n); +int efi_get_boot_options(uint16_t **options); + +int efi_loader_get_device_part_uuid(sd_id128_t *u); +int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader); + +#else + +static inline bool is_efi_boot(void) { + return false; +} + +static inline bool is_efi_secure_boot(void) { + return false; +} + +static inline bool is_efi_secure_boot_setup_mode(void) { + return false; +} + +static inline int efi_reboot_to_firmware_supported(void) { + return -EOPNOTSUPP; +} + +static inline int efi_get_reboot_to_firmware(void) { + return -EOPNOTSUPP; +} + +static inline int efi_set_reboot_to_firmware(bool value) { + return -EOPNOTSUPP; +} + +static inline int efi_get_variable(sd_id128_t vendor, const char *name, uint32_t *attribute, void **value, size_t *size) { + return -EOPNOTSUPP; +} + +static inline int efi_set_variable(sd_id128_t vendor, const char *name, const void *value, size_t size) { + return -EOPNOTSUPP; +} + +static inline int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p) { + return -EOPNOTSUPP; +} + +static inline int efi_get_boot_option(uint16_t nr, char **title, sd_id128_t *part_uuid, char **path, bool *active) { + return -EOPNOTSUPP; +} + +static inline int efi_add_boot_option(uint16_t id, const char *title, uint32_t part, uint64_t pstart, uint64_t psize, sd_id128_t part_uuid, const char *path) { + return -EOPNOTSUPP; +} + +static inline int efi_remove_boot_option(uint16_t id) { + return -EOPNOTSUPP; +} + +static inline int efi_get_boot_order(uint16_t **order) { + return -EOPNOTSUPP; +} + +static inline int efi_set_boot_order(uint16_t *order, size_t n) { + return -EOPNOTSUPP; +} + +static inline int efi_get_boot_options(uint16_t **options) { + return -EOPNOTSUPP; +} + +static inline int efi_loader_get_device_part_uuid(sd_id128_t *u) { + return -EOPNOTSUPP; +} + +static inline int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader) { + return -EOPNOTSUPP; +} + +#endif + +char *efi_tilt_backslashes(char *s); diff --git a/src/libshared/fstab-util.c b/src/libshared/fstab-util.c new file mode 100644 index 0000000000..a4e0cd3267 --- /dev/null +++ b/src/libshared/fstab-util.c @@ -0,0 +1,263 @@ +/*** + This file is part of systemd. + + Copyright 2015 Zbigniew Jędrzejewski-Szmek + + 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 +#include +#include +#include + +#include "alloc-util.h" +#include "device-nodes.h" +#include "fstab-util.h" +#include "macro.h" +#include "mount-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +bool fstab_is_mount_point(const char *mount) { + _cleanup_endmntent_ FILE *f = NULL; + struct mntent *m; + + f = setmntent("/etc/fstab", "r"); + if (!f) + return false; + + while ((m = getmntent(f))) + if (path_equal(m->mnt_dir, mount)) + return true; + + return false; +} + +int fstab_filter_options(const char *opts, const char *names, + const char **namefound, char **value, char **filtered) { + const char *name, *n = NULL, *x; + _cleanup_strv_free_ char **stor = NULL; + _cleanup_free_ char *v = NULL, **strv = NULL; + + assert(names && *names); + + if (!opts) + goto answer; + + /* If !value and !filtered, this function is not allowed to fail. */ + + if (!filtered) { + const char *word, *state; + size_t l; + + FOREACH_WORD_SEPARATOR(word, l, opts, ",", state) + NULSTR_FOREACH(name, names) { + if (l < strlen(name)) + continue; + if (!strneq(word, name, strlen(name))) + continue; + + /* we know that the string is NUL + * terminated, so *x is valid */ + x = word + strlen(name); + if (IN_SET(*x, '\0', '=', ',')) { + n = name; + if (value) { + free(v); + if (IN_SET(*x, '\0', ',')) + v = NULL; + else { + assert(*x == '='); + x++; + v = strndup(x, l - strlen(name) - 1); + if (!v) + return -ENOMEM; + } + } + } + } + } else { + char **t, **s; + + stor = strv_split(opts, ","); + if (!stor) + return -ENOMEM; + strv = memdup(stor, sizeof(char*) * (strv_length(stor) + 1)); + if (!strv) + return -ENOMEM; + + for (s = t = strv; *s; s++) { + NULSTR_FOREACH(name, names) { + x = startswith(*s, name); + if (x && IN_SET(*x, '\0', '=')) + goto found; + } + + *t = *s; + t++; + continue; + found: + /* Keep the last occurence found */ + n = name; + if (value) { + free(v); + if (*x == '\0') + v = NULL; + else { + assert(*x == '='); + x++; + v = strdup(x); + if (!v) + return -ENOMEM; + } + } + } + *t = NULL; + } + +answer: + if (namefound) + *namefound = n; + if (filtered) { + char *f; + + f = strv_join(strv, ","); + if (!f) + return -ENOMEM; + + *filtered = f; + } + if (value) { + *value = v; + v = NULL; + } + + return !!n; +} + +int fstab_extract_values(const char *opts, const char *name, char ***values) { + _cleanup_strv_free_ char **optsv = NULL, **res = NULL; + char **s; + + assert(opts); + assert(name); + assert(values); + + optsv = strv_split(opts, ","); + if (!optsv) + return -ENOMEM; + + STRV_FOREACH(s, optsv) { + char *arg; + int r; + + arg = startswith(*s, name); + if (!arg || *arg != '=') + continue; + r = strv_extend(&res, arg + 1); + if (r < 0) + return r; + } + + *values = res; + res = NULL; + + return !!*values; +} + +int fstab_find_pri(const char *options, int *ret) { + _cleanup_free_ char *opt = NULL; + int r; + unsigned pri; + + assert(ret); + + r = fstab_filter_options(options, "pri\0", NULL, &opt, NULL); + if (r < 0) + return r; + if (r == 0 || !opt) + return 0; + + r = safe_atou(opt, &pri); + if (r < 0) + return r; + + if ((int) pri < 0) + return -ERANGE; + + *ret = (int) pri; + return 1; +} + +static char *unquote(const char *s, const char* quotes) { + size_t l; + assert(s); + + /* This is rather stupid, simply removes the heading and + * trailing quotes if there is one. Doesn't care about + * escaping or anything. + * + * DON'T USE THIS FOR NEW CODE ANYMORE!*/ + + l = strlen(s); + if (l < 2) + return strdup(s); + + if (strchr(quotes, s[0]) && s[l-1] == s[0]) + return strndup(s+1, l-2); + + return strdup(s); +} + +static char *tag_to_udev_node(const char *tagvalue, const char *by) { + _cleanup_free_ char *t = NULL, *u = NULL; + size_t enc_len; + + u = unquote(tagvalue, QUOTES); + if (!u) + return NULL; + + enc_len = strlen(u) * 4 + 1; + t = new(char, enc_len); + if (!t) + return NULL; + + if (encode_devnode_name(u, t, enc_len) < 0) + return NULL; + + return strjoin("/dev/disk/by-", by, "/", t, NULL); +} + +char *fstab_node_to_udev_node(const char *p) { + assert(p); + + if (startswith(p, "LABEL=")) + return tag_to_udev_node(p+6, "label"); + + if (startswith(p, "UUID=")) + return tag_to_udev_node(p+5, "uuid"); + + if (startswith(p, "PARTUUID=")) + return tag_to_udev_node(p+9, "partuuid"); + + if (startswith(p, "PARTLABEL=")) + return tag_to_udev_node(p+10, "partlabel"); + + return strdup(p); +} diff --git a/src/libshared/fstab-util.h b/src/libshared/fstab-util.h new file mode 100644 index 0000000000..679f6902f7 --- /dev/null +++ b/src/libshared/fstab-util.h @@ -0,0 +1,52 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Zbigniew Jędrzejewski-Szmek + + 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 + +#include "macro.h" + +bool fstab_is_mount_point(const char *mount); + +int fstab_filter_options(const char *opts, const char *names, const char **namefound, char **value, char **filtered); + +int fstab_extract_values(const char *opts, const char *name, char ***values); + +static inline bool fstab_test_option(const char *opts, const char *names) { + return !!fstab_filter_options(opts, names, NULL, NULL, NULL); +} + +int fstab_find_pri(const char *options, int *ret); + +static inline bool fstab_test_yes_no_option(const char *opts, const char *yes_no) { + int r; + const char *opt; + + /* If first name given is last, return 1. + * If second name given is last or neither is found, return 0. */ + + r = fstab_filter_options(opts, yes_no, &opt, NULL, NULL); + assert(r >= 0); + + return opt == yes_no; +} + +char *fstab_node_to_udev_node(const char *p); diff --git a/src/libshared/gcrypt-util.c b/src/libshared/gcrypt-util.c new file mode 100644 index 0000000000..39b544b6f0 --- /dev/null +++ b/src/libshared/gcrypt-util.c @@ -0,0 +1,71 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#ifdef HAVE_GCRYPT +#include + +#include "gcrypt-util.h" +#include "hexdecoct.h" + +void initialize_libgcrypt(bool secmem) { + const char *p; + if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) + return; + + p = gcry_check_version("1.4.5"); + assert(p); + + /* Turn off "secmem". Clients which wish to make use of this + * feature should initialize the library manually */ + if (!secmem) + gcry_control(GCRYCTL_DISABLE_SECMEM); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); +} + +int string_hashsum(const char *s, size_t len, int md_algorithm, char **out) { + gcry_md_hd_t md = NULL; + size_t hash_size; + void *hash; + char *enc; + + initialize_libgcrypt(false); + + hash_size = gcry_md_get_algo_dlen(md_algorithm); + assert(hash_size > 0); + + gcry_md_open(&md, md_algorithm, 0); + if (!md) + return -EIO; + + gcry_md_write(md, s, len); + + hash = gcry_md_read(md, 0); + if (!hash) + return -EIO; + + enc = hexmem(hash, hash_size); + if (!enc) + return -ENOMEM; + + *out = enc; + return 0; +} +#endif diff --git a/src/libshared/gcrypt-util.h b/src/libshared/gcrypt-util.h new file mode 100644 index 0000000000..cf33b3c59c --- /dev/null +++ b/src/libshared/gcrypt-util.h @@ -0,0 +1,39 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2016 Zbigniew Jędrzejewski-Szmek + + 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 +#include + +#ifdef HAVE_GCRYPT +#include + +void initialize_libgcrypt(bool secmem); +int string_hashsum(const char *s, size_t len, int md_algorithm, char **out); +#endif + +static inline int string_hashsum_sha224(const char *s, size_t len, char **out) { +#ifdef HAVE_GCRYPT + return string_hashsum(s, len, GCRY_MD_SHA224, out); +#else + return -EOPNOTSUPP; +#endif +} diff --git a/src/libshared/generator.c b/src/libshared/generator.c new file mode 100644 index 0000000000..70afc6a285 --- /dev/null +++ b/src/libshared/generator.c @@ -0,0 +1,207 @@ +/*** + 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 + +#include "alloc-util.h" +#include "dropin.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "fstab-util.h" +#include "generator.h" +#include "log.h" +#include "macro.h" +#include "mkdir.h" +#include "path-util.h" +#include "special.h" +#include "string-util.h" +#include "time-util.h" +#include "unit-name.h" +#include "util.h" + +static int write_fsck_sysroot_service(const char *dir, const char *what) { + _cleanup_free_ char *device = NULL, *escaped = NULL; + _cleanup_fclose_ FILE *f = NULL; + const char *unit; + int r; + + escaped = cescape(what); + if (!escaped) + return log_oom(); + + unit = strjoina(dir, "/systemd-fsck-root.service"); + log_debug("Creating %s", unit); + + r = unit_name_from_path(what, ".device", &device); + if (r < 0) + return log_error_errno(r, "Failed to convert device \"%s\" to unit name: %m", what); + + f = fopen(unit, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to create unit file %s: %m", unit); + + fprintf(f, + "# Automatically generated by %1$s\n\n" + "[Unit]\n" + "Documentation=man:systemd-fsck-root.service(8)\n" + "Description=File System Check on %2$s\n" + "DefaultDependencies=no\n" + "BindsTo=%3$s\n" + "After=initrd-root-device.target local-fs-pre.target\n" + "Before=shutdown.target\n" + "\n" + "[Service]\n" + "Type=oneshot\n" + "RemainAfterExit=yes\n" + "ExecStart=" SYSTEMD_FSCK_PATH " %4$s\n" + "TimeoutSec=0\n", + program_invocation_short_name, + what, + device, + escaped); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit file %s: %m", unit); + + return 0; +} + +int generator_write_fsck_deps( + FILE *f, + const char *dir, + const char *what, + const char *where, + const char *fstype) { + + int r; + + assert(f); + assert(dir); + assert(what); + assert(where); + + if (!is_device_path(what)) { + log_warning("Checking was requested for \"%s\", but it is not a device.", what); + return 0; + } + + if (!isempty(fstype) && !streq(fstype, "auto")) { + r = fsck_exists(fstype); + if (r < 0) + log_warning_errno(r, "Checking was requested for %s, but couldn't detect if fsck.%s may be used, proceeding: %m", what, fstype); + else if (r == 0) { + /* treat missing check as essentially OK */ + log_debug("Checking was requested for %s, but fsck.%s does not exist.", what, fstype); + return 0; + } + } + + if (path_equal(where, "/")) { + const char *lnk; + + lnk = strjoina(dir, "/" SPECIAL_LOCAL_FS_TARGET ".wants/systemd-fsck-root.service"); + + mkdir_parents(lnk, 0755); + if (symlink(SYSTEM_DATA_UNIT_PATH "/systemd-fsck-root.service", lnk) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); + + } else { + _cleanup_free_ char *_fsck = NULL; + const char *fsck; + + if (in_initrd() && path_equal(where, "/sysroot")) { + r = write_fsck_sysroot_service(dir, what); + if (r < 0) + return r; + + fsck = "systemd-fsck-root.service"; + } else { + r = unit_name_from_path_instance("systemd-fsck", what, ".service", &_fsck); + if (r < 0) + return log_error_errno(r, "Failed to create fsck service name: %m"); + + fsck = _fsck; + } + + fprintf(f, + "Requires=%1$s\n" + "After=%1$s\n", + fsck); + } + + return 0; +} + +int generator_write_timeouts( + const char *dir, + const char *what, + const char *where, + const char *opts, + char **filtered) { + + /* Allow configuration how long we wait for a device that + * backs a mount point to show up. This is useful to support + * endless device timeouts for devices that show up only after + * user input, like crypto devices. */ + + _cleanup_free_ char *node = NULL, *unit = NULL, *timeout = NULL; + usec_t u; + int r; + + r = fstab_filter_options(opts, "comment=systemd.device-timeout\0" "x-systemd.device-timeout\0", + NULL, &timeout, filtered); + if (r <= 0) + return r; + + r = parse_sec(timeout, &u); + if (r < 0) { + log_warning("Failed to parse timeout for %s, ignoring: %s", where, timeout); + return 0; + } + + node = fstab_node_to_udev_node(what); + if (!node) + return log_oom(); + + r = unit_name_from_path(node, ".device", &unit); + if (r < 0) + return log_error_errno(r, "Failed to make unit name from path: %m"); + + return write_drop_in_format(dir, unit, 50, "device-timeout", + "# Automatically generated by %s\n\n" + "[Unit]\nJobTimeoutSec=%s", + program_invocation_short_name, timeout); +} + +int generator_write_initrd_root_device_deps(const char *dir, const char *what) { + _cleanup_free_ char *unit = NULL; + int r; + + r = unit_name_from_path(what, ".device", &unit); + if (r < 0) + return log_error_errno(r, "Failed to make unit name from path: %m"); + + return write_drop_in_format(dir, SPECIAL_INITRD_ROOT_DEVICE_TARGET, 50, "root-device", + "# Automatically generated by %s\n\n" + "[Unit]\nRequires=%s\nAfter=%s", + program_invocation_short_name, unit, unit); +} diff --git a/src/libshared/generator.h b/src/libshared/generator.h new file mode 100644 index 0000000000..a6017c1b76 --- /dev/null +++ b/src/libshared/generator.h @@ -0,0 +1,40 @@ +#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 + +int generator_write_fsck_deps( + FILE *f, + const char *dir, + const char *what, + const char *where, + const char *type); + +int generator_write_timeouts( + const char *dir, + const char *what, + const char *where, + const char *opts, + char **filtered); + +int generator_write_initrd_root_device_deps( + const char *dir, + const char *what); diff --git a/src/libshared/gpt.h b/src/libshared/gpt.h new file mode 100644 index 0000000000..07153b51f4 --- /dev/null +++ b/src/libshared/gpt.h @@ -0,0 +1,66 @@ +#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 + +#include + +/* We only support root disk discovery for x86, x86-64, Itanium and ARM for + * now, since EFI for anything else doesn't really exist, and we only + * care for root partitions on the same disk as the EFI ESP. */ + +#define GPT_ROOT_X86 SD_ID128_MAKE(44,47,95,40,f2,97,41,b2,9a,f7,d1,31,d5,f0,45,8a) +#define GPT_ROOT_X86_64 SD_ID128_MAKE(4f,68,bc,e3,e8,cd,4d,b1,96,e7,fb,ca,f9,84,b7,09) +#define GPT_ROOT_ARM SD_ID128_MAKE(69,da,d7,10,2c,e4,4e,3c,b1,6c,21,a1,d4,9a,be,d3) +#define GPT_ROOT_ARM_64 SD_ID128_MAKE(b9,21,b0,45,1d,f0,41,c3,af,44,4c,6f,28,0d,3f,ae) +#define GPT_ROOT_IA64 SD_ID128_MAKE(99,3d,8d,3d,f8,0e,42,25,85,5a,9d,af,8e,d7,ea,97) + +#define GPT_ESP SD_ID128_MAKE(c1,2a,73,28,f8,1f,11,d2,ba,4b,00,a0,c9,3e,c9,3b) +#define GPT_SWAP SD_ID128_MAKE(06,57,fd,6d,a4,ab,43,c4,84,e5,09,33,c8,4b,4f,4f) +#define GPT_HOME SD_ID128_MAKE(93,3a,c7,e1,2e,b4,4f,13,b8,44,0e,14,e2,ae,f9,15) +#define GPT_SRV SD_ID128_MAKE(3b,8f,84,25,20,e0,4f,3b,90,7f,1a,25,a7,6f,98,e8) + +#if defined(__x86_64__) +# define GPT_ROOT_NATIVE GPT_ROOT_X86_64 +# define GPT_ROOT_SECONDARY GPT_ROOT_X86 +#elif defined(__i386__) +# define GPT_ROOT_NATIVE GPT_ROOT_X86 +#endif + +#if defined(__ia64__) +# define GPT_ROOT_NATIVE GPT_ROOT_IA64 +#endif + +#if defined(__aarch64__) && (__BYTE_ORDER != __BIG_ENDIAN) +# define GPT_ROOT_NATIVE GPT_ROOT_ARM_64 +# define GPT_ROOT_SECONDARY GPT_ROOT_ARM +#elif defined(__arm__) && (__BYTE_ORDER != __BIG_ENDIAN) +# define GPT_ROOT_NATIVE GPT_ROOT_ARM +#endif + +/* Flags we recognize on the root, swap, home and srv partitions when + * doing auto-discovery. These happen to be identical to what + * Microsoft defines for its own Basic Data Partitions, but that's + * just because we saw no point in defining any other values here. */ +#define GPT_FLAG_READ_ONLY (1ULL << 60) +#define GPT_FLAG_NO_AUTO (1ULL << 63) + +#define GPT_LINUX_GENERIC SD_ID128_MAKE(0f,c6,3d,af,84,83,47,72,8e,79,3d,69,d8,47,7d,e4) diff --git a/src/libshared/ima-util.c b/src/libshared/ima-util.c new file mode 100644 index 0000000000..789064d653 --- /dev/null +++ b/src/libshared/ima-util.c @@ -0,0 +1,32 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "ima-util.h" + +static int use_ima_cached = -1; + +bool use_ima(void) { + + if (use_ima_cached < 0) + use_ima_cached = access("/sys/kernel/security/ima/", F_OK) >= 0; + + return use_ima_cached; +} diff --git a/src/libshared/ima-util.h b/src/libshared/ima-util.h new file mode 100644 index 0000000000..5be94761fd --- /dev/null +++ b/src/libshared/ima-util.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 + +bool use_ima(void); diff --git a/src/libshared/import-util.c b/src/libshared/import-util.c new file mode 100644 index 0000000000..ab701ad8b2 --- /dev/null +++ b/src/libshared/import-util.c @@ -0,0 +1,185 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 + +#include "alloc-util.h" +#include "btrfs-util.h" +#include "import-util.h" +#include "log.h" +#include "macro.h" +#include "path-util.h" +#include "string-table.h" +#include "string-util.h" +#include "util.h" + +int import_url_last_component(const char *url, char **ret) { + const char *e, *p; + char *s; + + e = strchrnul(url, '?'); + + while (e > url && e[-1] == '/') + e--; + + p = e; + while (p > url && p[-1] != '/') + p--; + + if (e <= p) + return -EINVAL; + + s = strndup(p, e - p); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + + +int import_url_change_last_component(const char *url, const char *suffix, char **ret) { + const char *e; + char *s; + + assert(url); + assert(ret); + + e = strchrnul(url, '?'); + + while (e > url && e[-1] == '/') + e--; + + while (e > url && e[-1] != '/') + e--; + + if (e <= url) + return -EINVAL; + + s = new(char, (e - url) + strlen(suffix) + 1); + if (!s) + return -ENOMEM; + + strcpy(mempcpy(s, url, e - url), suffix); + *ret = s; + return 0; +} + +static const char* const import_verify_table[_IMPORT_VERIFY_MAX] = { + [IMPORT_VERIFY_NO] = "no", + [IMPORT_VERIFY_CHECKSUM] = "checksum", + [IMPORT_VERIFY_SIGNATURE] = "signature", +}; + +DEFINE_STRING_TABLE_LOOKUP(import_verify, ImportVerify); + +int tar_strip_suffixes(const char *name, char **ret) { + const char *e; + char *s; + + e = endswith(name, ".tar"); + if (!e) + e = endswith(name, ".tar.xz"); + if (!e) + e = endswith(name, ".tar.gz"); + if (!e) + e = endswith(name, ".tar.bz2"); + if (!e) + e = endswith(name, ".tgz"); + if (!e) + e = strchr(name, 0); + + if (e <= name) + return -EINVAL; + + s = strndup(name, e - name); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +int raw_strip_suffixes(const char *p, char **ret) { + + static const char suffixes[] = + ".xz\0" + ".gz\0" + ".bz2\0" + ".raw\0" + ".qcow2\0" + ".img\0" + ".bin\0"; + + _cleanup_free_ char *q = NULL; + + q = strdup(p); + if (!q) + return -ENOMEM; + + for (;;) { + const char *sfx; + bool changed = false; + + NULSTR_FOREACH(sfx, suffixes) { + char *e; + + e = endswith(q, sfx); + if (e) { + *e = 0; + changed = true; + } + } + + if (!changed) + break; + } + + *ret = q; + q = NULL; + + return 0; +} + +int import_assign_pool_quota_and_warn(const char *path) { + int r; + + r = btrfs_subvol_auto_qgroup("/var/lib/machines", 0, true); + if (r == -ENOTTY) { + log_debug_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines, as directory is not on btrfs or not a subvolume. Ignoring."); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines: %m"); + if (r > 0) + log_info("Set up default quota hierarchy for /var/lib/machines."); + + r = btrfs_subvol_auto_qgroup(path, 0, true); + if (r == -ENOTTY) { + log_debug_errno(r, "Failed to set up quota hierarchy for %s, as directory is not on btrfs or not a subvolume. Ignoring.", path); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to set up default quota hierarchy for %s: %m", path); + if (r > 0) + log_info("Set up default quota hierarchy for %s.", path); + + return 0; +} diff --git a/src/libshared/import-util.h b/src/libshared/import-util.h new file mode 100644 index 0000000000..77b17d91f3 --- /dev/null +++ b/src/libshared/import-util.h @@ -0,0 +1,43 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 "macro.h" + +typedef enum ImportVerify { + IMPORT_VERIFY_NO, + IMPORT_VERIFY_CHECKSUM, + IMPORT_VERIFY_SIGNATURE, + _IMPORT_VERIFY_MAX, + _IMPORT_VERIFY_INVALID = -1, +} ImportVerify; + +int import_url_last_component(const char *url, char **ret); +int import_url_change_last_component(const char *url, const char *suffix, char **ret); + +const char* import_verify_to_string(ImportVerify v) _const_; +ImportVerify import_verify_from_string(const char *s) _pure_; + +int tar_strip_suffixes(const char *name, char **ret); +int raw_strip_suffixes(const char *name, char **ret); + +int import_assign_pool_quota_and_warn(const char *path); diff --git a/src/libshared/initreq.h b/src/libshared/initreq.h new file mode 100644 index 0000000000..710037d84b --- /dev/null +++ b/src/libshared/initreq.h @@ -0,0 +1,77 @@ +/* + * initreq.h Interface to talk to init through /dev/initctl. + * + * Copyright (C) 1995-2004 Miquel van Smoorenburg + * + * This library 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 of the License, or (at your option) any later version. + * + * Version: @(#)initreq.h 1.28 31-Mar-2004 MvS + * + */ +#ifndef _INITREQ_H +#define _INITREQ_H + +#include + +#if defined(__FreeBSD_kernel__) +# define INIT_FIFO "/etc/.initctl" +#else +# define INIT_FIFO "/dev/initctl" +#endif + +#define INIT_MAGIC 0x03091969 +#define INIT_CMD_START 0 +#define INIT_CMD_RUNLVL 1 +#define INIT_CMD_POWERFAIL 2 +#define INIT_CMD_POWERFAILNOW 3 +#define INIT_CMD_POWEROK 4 +#define INIT_CMD_BSD 5 +#define INIT_CMD_SETENV 6 +#define INIT_CMD_UNSETENV 7 + +#define INIT_CMD_CHANGECONS 12345 + +#ifdef MAXHOSTNAMELEN +# define INITRQ_HLEN MAXHOSTNAMELEN +#else +# define INITRQ_HLEN 64 +#endif + +/* + * This is what BSD 4.4 uses when talking to init. + * Linux doesn't use this right now. + */ +struct init_request_bsd { + char gen_id[8]; /* Beats me.. telnetd uses "fe" */ + char tty_id[16]; /* Tty name minus /dev/tty */ + char host[INITRQ_HLEN]; /* Hostname */ + char term_type[16]; /* Terminal type */ + int signal; /* Signal to send */ + int pid; /* Process to send to */ + char exec_name[128]; /* Program to execute */ + char reserved[128]; /* For future expansion. */ +}; + + +/* + * Because of legacy interfaces, "runlevel" and "sleeptime" + * aren't in a separate struct in the union. + * + * The weird sizes are because init expects the whole + * struct to be 384 bytes. + */ +struct init_request { + int magic; /* Magic number */ + int cmd; /* What kind of request */ + int runlevel; /* Runlevel to change to */ + int sleeptime; /* Time between TERM and KILL */ + union { + struct init_request_bsd bsd; + char data[368]; + } i; +}; + +#endif diff --git a/src/libshared/install-printf.c b/src/libshared/install-printf.c new file mode 100644 index 0000000000..88143361da --- /dev/null +++ b/src/libshared/install-printf.c @@ -0,0 +1,133 @@ +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + 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 +#include +#include + +#include "formats-util.h" +#include "install-printf.h" +#include "install.h" +#include "macro.h" +#include "specifier.h" +#include "unit-name.h" +#include "user-util.h" + +static int specifier_prefix_and_instance(char specifier, void *data, void *userdata, char **ret) { + UnitFileInstallInfo *i = userdata; + + assert(i); + + return unit_name_to_prefix_and_instance(i->name, ret); +} + +static int specifier_prefix(char specifier, void *data, void *userdata, char **ret) { + UnitFileInstallInfo *i = userdata; + + assert(i); + + return unit_name_to_prefix(i->name, ret); +} + +static int specifier_instance(char specifier, void *data, void *userdata, char **ret) { + UnitFileInstallInfo *i = userdata; + char *instance; + int r; + + assert(i); + + r = unit_name_to_instance(i->name, &instance); + if (r < 0) + return r; + + if (!instance) { + instance = strdup(""); + if (!instance) + return -ENOMEM; + } + + *ret = instance; + return 0; +} + +static int specifier_user_name(char specifier, void *data, void *userdata, char **ret) { + char *t; + + /* If we are UID 0 (root), this will not result in NSS, + * otherwise it might. This is good, as we want to be able to + * run this in PID 1, where our user ID is 0, but where NSS + * lookups are not allowed. */ + + t = getusername_malloc(); + if (!t) + return -ENOMEM; + + *ret = t; + return 0; +} + +static int specifier_user_id(char specifier, void *data, void *userdata, char **ret) { + + if (asprintf(ret, UID_FMT, getuid()) < 0) + return -ENOMEM; + + return 0; +} + +int install_full_printf(UnitFileInstallInfo *i, const char *format, char **ret) { + + /* This is similar to unit_full_printf() but does not support + * anything path-related. + * + * %n: the full id of the unit (foo@bar.waldo) + * %N: the id of the unit without the suffix (foo@bar) + * %p: the prefix (foo) + * %i: the instance (bar) + + * %U the UID of the running user + * %u the username of running user + * %m the machine ID of the running system + * %H the host name of the running system + * %b the boot ID of the running system + * %v `uname -r` of the running system + */ + + const Specifier table[] = { + { 'n', specifier_string, i->name }, + { 'N', specifier_prefix_and_instance, NULL }, + { 'p', specifier_prefix, NULL }, + { 'i', specifier_instance, NULL }, + + { 'U', specifier_user_id, NULL }, + { 'u', specifier_user_name, NULL }, + + { 'm', specifier_machine_id, NULL }, + { 'H', specifier_host_name, NULL }, + { 'b', specifier_boot_id, NULL }, + { 'v', specifier_kernel_release, NULL }, + {} + }; + + assert(i); + assert(format); + assert(ret); + + return specifier_printf(format, table, i, ret); +} diff --git a/src/libshared/install-printf.h b/src/libshared/install-printf.h new file mode 100644 index 0000000000..8a570fc265 --- /dev/null +++ b/src/libshared/install-printf.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + 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 "install.h" + +int install_full_printf(UnitFileInstallInfo *i, const char *format, char **ret); diff --git a/src/libshared/install.c b/src/libshared/install.c new file mode 100644 index 0000000000..64d66a45d3 --- /dev/null +++ b/src/libshared/install.c @@ -0,0 +1,2953 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "conf-files.h" +#include "conf-parser.h" +#include "dirent-util.h" +#include "extract-word.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "hashmap.h" +#include "install-printf.h" +#include "install.h" +#include "locale-util.h" +#include "log.h" +#include "macro.h" +#include "mkdir.h" +#include "path-lookup.h" +#include "path-util.h" +#include "rm-rf.h" +#include "set.h" +#include "special.h" +#include "stat-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" + +#define UNIT_FILE_FOLLOW_SYMLINK_MAX 64 + +typedef enum SearchFlags { + SEARCH_LOAD = 1, + SEARCH_FOLLOW_CONFIG_SYMLINKS = 2, +} SearchFlags; + +typedef struct { + OrderedHashmap *will_process; + OrderedHashmap *have_processed; +} InstallContext; + +typedef enum { + PRESET_UNKNOWN, + PRESET_ENABLE, + PRESET_DISABLE, +} PresetAction; + +typedef struct { + char *pattern; + PresetAction action; +} PresetRule; + +typedef struct { + PresetRule *rules; + size_t n_rules; +} Presets; + +static inline void presets_freep(Presets *p) { + size_t i; + + if (!p) + return; + + for (i = 0; i < p->n_rules; i++) + free(p->rules[i].pattern); + + free(p->rules); + p->n_rules = 0; +} + +static int unit_file_lookup_state(UnitFileScope scope, const LookupPaths *paths, const char *name, UnitFileState *ret); + +bool unit_type_may_alias(UnitType type) { + return IN_SET(type, + UNIT_SERVICE, + UNIT_SOCKET, + UNIT_TARGET, + UNIT_DEVICE, + UNIT_TIMER, + UNIT_PATH); +} + +bool unit_type_may_template(UnitType type) { + return IN_SET(type, + UNIT_SERVICE, + UNIT_SOCKET, + UNIT_TARGET, + UNIT_TIMER, + UNIT_PATH); +} + +static const char *unit_file_type_table[_UNIT_FILE_TYPE_MAX] = { + [UNIT_FILE_TYPE_REGULAR] = "regular", + [UNIT_FILE_TYPE_SYMLINK] = "symlink", + [UNIT_FILE_TYPE_MASKED] = "masked", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(unit_file_type, UnitFileType); + +static int in_search_path(const LookupPaths *p, const char *path) { + _cleanup_free_ char *parent = NULL; + char **i; + + assert(path); + + parent = dirname_malloc(path); + if (!parent) + return -ENOMEM; + + STRV_FOREACH(i, p->search_path) + if (path_equal(parent, *i)) + return true; + + return false; +} + +static const char* skip_root(const LookupPaths *p, const char *path) { + char *e; + + assert(p); + assert(path); + + if (!p->root_dir) + return path; + + e = path_startswith(path, p->root_dir); + if (!e) + return NULL; + + /* Make sure the returned path starts with a slash */ + if (e[0] != '/') { + if (e == path || e[-1] != '/') + return NULL; + + e--; + } + + return e; +} + +static int path_is_generator(const LookupPaths *p, const char *path) { + _cleanup_free_ char *parent = NULL; + + assert(p); + assert(path); + + parent = dirname_malloc(path); + if (!parent) + return -ENOMEM; + + return path_equal_ptr(parent, p->generator) || + path_equal_ptr(parent, p->generator_early) || + path_equal_ptr(parent, p->generator_late); +} + +static int path_is_transient(const LookupPaths *p, const char *path) { + _cleanup_free_ char *parent = NULL; + + assert(p); + assert(path); + + parent = dirname_malloc(path); + if (!parent) + return -ENOMEM; + + return path_equal_ptr(parent, p->transient); +} + +static int path_is_control(const LookupPaths *p, const char *path) { + _cleanup_free_ char *parent = NULL; + + assert(p); + assert(path); + + parent = dirname_malloc(path); + if (!parent) + return -ENOMEM; + + return path_equal_ptr(parent, p->persistent_control) || + path_equal_ptr(parent, p->runtime_control); +} + +static int path_is_config(const LookupPaths *p, const char *path) { + _cleanup_free_ char *parent = NULL; + + assert(p); + assert(path); + + /* Note that we do *not* have generic checks for /etc or /run in place, since with them we couldn't discern + * configuration from transient or generated units */ + + parent = dirname_malloc(path); + if (!parent) + return -ENOMEM; + + return path_equal_ptr(parent, p->persistent_config) || + path_equal_ptr(parent, p->runtime_config); +} + +static int path_is_runtime(const LookupPaths *p, const char *path) { + _cleanup_free_ char *parent = NULL; + const char *rpath; + + assert(p); + assert(path); + + /* Everything in /run is considered runtime. On top of that we also add explicit checks for the various runtime + * directories, as safety net. */ + + rpath = skip_root(p, path); + if (rpath && path_startswith(rpath, "/run")) + return true; + + parent = dirname_malloc(path); + if (!parent) + return -ENOMEM; + + return path_equal_ptr(parent, p->runtime_config) || + path_equal_ptr(parent, p->generator) || + path_equal_ptr(parent, p->generator_early) || + path_equal_ptr(parent, p->generator_late) || + path_equal_ptr(parent, p->transient) || + path_equal_ptr(parent, p->runtime_control); +} + +static int path_is_vendor(const LookupPaths *p, const char *path) { + const char *rpath; + + assert(p); + assert(path); + + rpath = skip_root(p, path); + if (!rpath) + return 0; + + if (path_startswith(rpath, "/usr")) + return true; + +#ifdef HAVE_SPLIT_USR + if (path_startswith(rpath, "/lib")) + return true; +#endif + + return path_equal(rpath, SYSTEM_DATA_UNIT_PATH); +} + +int unit_file_changes_add( + UnitFileChange **changes, + unsigned *n_changes, + UnitFileChangeType type, + const char *path, + const char *source) { + + _cleanup_free_ char *p = NULL, *s = NULL; + UnitFileChange *c; + + assert(path); + assert(!changes == !n_changes); + + if (!changes) + return 0; + + c = realloc(*changes, (*n_changes + 1) * sizeof(UnitFileChange)); + if (!c) + return -ENOMEM; + *changes = c; + + p = strdup(path); + if (source) + s = strdup(source); + + if (!p || (source && !s)) + return -ENOMEM; + + path_kill_slashes(p); + if (s) + path_kill_slashes(s); + + c[*n_changes] = (UnitFileChange) { type, p, s }; + p = s = NULL; + (*n_changes) ++; + return 0; +} + +void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes) { + unsigned i; + + assert(changes || n_changes == 0); + + for (i = 0; i < n_changes; i++) { + free(changes[i].path); + free(changes[i].source); + } + + free(changes); +} + +void unit_file_dump_changes(int r, const char *verb, const UnitFileChange *changes, unsigned n_changes, bool quiet) { + unsigned i; + bool logged = false; + + assert(changes || n_changes == 0); + /* If verb is not specified, errors are not allowed! */ + assert(verb || r >= 0); + + for (i = 0; i < n_changes; i++) { + assert(verb || changes[i].type >= 0); + + switch(changes[i].type) { + case UNIT_FILE_SYMLINK: + if (!quiet) + log_info("Created symlink %s %s %s.", + changes[i].path, + special_glyph(ARROW), + changes[i].source); + break; + case UNIT_FILE_UNLINK: + if (!quiet) + log_info("Removed %s.", changes[i].path); + break; + case UNIT_FILE_IS_MASKED: + if (!quiet) + log_info("Unit %s is masked, ignoring.", changes[i].path); + break; + case UNIT_FILE_IS_DANGLING: + if (!quiet) + log_info("Unit %s is an alias to a unit that is not present, ignoring.", + changes[i].path); + break; + case -EEXIST: + if (changes[i].source) + log_error_errno(changes[i].type, + "Failed to %s unit, file %s already exists and is a symlink to %s.", + verb, changes[i].path, changes[i].source); + else + log_error_errno(changes[i].type, + "Failed to %s unit, file %s already exists.", + verb, changes[i].path); + logged = true; + break; + case -ERFKILL: + log_error_errno(changes[i].type, "Failed to %s unit, unit %s is masked.", + verb, changes[i].path); + logged = true; + break; + case -EADDRNOTAVAIL: + log_error_errno(changes[i].type, "Failed to %s unit, unit %s is transient or generated.", + verb, changes[i].path); + logged = true; + break; + case -ELOOP: + log_error_errno(changes[i].type, "Failed to %s unit, refusing to operate on linked unit file %s", + verb, changes[i].path); + logged = true; + break; + default: + assert(changes[i].type < 0); + log_error_errno(changes[i].type, "Failed to %s unit, file %s: %m.", + verb, changes[i].path); + logged = true; + } + } + + if (r < 0 && !logged) + log_error_errno(r, "Failed to %s: %m.", verb); +} + +static int create_symlink( + const char *old_path, + const char *new_path, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_free_ char *dest = NULL; + int r; + + assert(old_path); + assert(new_path); + + /* Actually create a symlink, and remember that we did. Is + * smart enough to check if there's already a valid symlink in + * place. + * + * Returns 1 if a symlink was created or already exists and points to + * the right place, or negative on error. + */ + + mkdir_parents_label(new_path, 0755); + + if (symlink(old_path, new_path) >= 0) { + unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path); + return 1; + } + + if (errno != EEXIST) { + unit_file_changes_add(changes, n_changes, -errno, new_path, NULL); + return -errno; + } + + r = readlink_malloc(new_path, &dest); + if (r < 0) { + /* translate EINVAL (non-symlink exists) to EEXIST */ + if (r == -EINVAL) + r = -EEXIST; + + unit_file_changes_add(changes, n_changes, r, new_path, NULL); + return r; + } + + if (path_equal(dest, old_path)) + return 1; + + if (!force) { + unit_file_changes_add(changes, n_changes, -EEXIST, new_path, dest); + return -EEXIST; + } + + r = symlink_atomic(old_path, new_path); + if (r < 0) { + unit_file_changes_add(changes, n_changes, r, new_path, NULL); + return r; + } + + unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, new_path, NULL); + unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path); + + return 1; +} + +static int mark_symlink_for_removal( + Set **remove_symlinks_to, + const char *p) { + + char *n; + int r; + + assert(p); + + r = set_ensure_allocated(remove_symlinks_to, &string_hash_ops); + if (r < 0) + return r; + + n = strdup(p); + if (!n) + return -ENOMEM; + + path_kill_slashes(n); + + r = set_consume(*remove_symlinks_to, n); + if (r == -EEXIST) + return 0; + if (r < 0) + return r; + + return 1; +} + +static int remove_marked_symlinks_fd( + Set *remove_symlinks_to, + int fd, + const char *path, + const char *config_path, + const LookupPaths *lp, + bool *restart, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r = 0; + + assert(remove_symlinks_to); + assert(fd >= 0); + assert(path); + assert(config_path); + assert(lp); + assert(restart); + + d = fdopendir(fd); + if (!d) { + safe_close(fd); + return -errno; + } + + rewinddir(d); + + FOREACH_DIRENT(de, d, return -errno) { + + dirent_ensure_type(d, de); + + if (de->d_type == DT_DIR) { + _cleanup_free_ char *p = NULL; + int nfd, q; + + nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (nfd < 0) { + if (errno == ENOENT) + continue; + + if (r == 0) + r = -errno; + continue; + } + + p = path_make_absolute(de->d_name, path); + if (!p) { + safe_close(nfd); + return -ENOMEM; + } + + /* This will close nfd, regardless whether it succeeds or not */ + q = remove_marked_symlinks_fd(remove_symlinks_to, nfd, p, config_path, lp, restart, changes, n_changes); + if (q < 0 && r == 0) + r = q; + + } else if (de->d_type == DT_LNK) { + _cleanup_free_ char *p = NULL, *dest = NULL; + const char *rp; + bool found; + int q; + + if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) + continue; + + p = path_make_absolute(de->d_name, path); + if (!p) + return -ENOMEM; + path_kill_slashes(p); + + q = readlink_malloc(p, &dest); + if (q == -ENOENT) + continue; + if (q < 0) { + if (r == 0) + r = q; + continue; + } + + /* We remove all links pointing to a file or path that is marked, as well as all files sharing + * the same name as a file that is marked. */ + + found = set_contains(remove_symlinks_to, dest) || + set_contains(remove_symlinks_to, basename(dest)) || + set_contains(remove_symlinks_to, de->d_name); + + if (!found) + continue; + + if (unlinkat(fd, de->d_name, 0) < 0 && errno != ENOENT) { + if (r == 0) + r = -errno; + unit_file_changes_add(changes, n_changes, -errno, p, NULL); + continue; + } + + (void) rmdir_parents(p, config_path); + + unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, p, NULL); + + /* Now, remember the full path (but with the root prefix removed) of + * the symlink we just removed, and remove any symlinks to it, too. */ + + rp = skip_root(lp, p); + q = mark_symlink_for_removal(&remove_symlinks_to, rp ?: p); + if (q < 0) + return q; + if (q > 0) + *restart = true; + } + } + + return r; +} + +static int remove_marked_symlinks( + Set *remove_symlinks_to, + const char *config_path, + const LookupPaths *lp, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_close_ int fd = -1; + bool restart; + int r = 0; + + assert(config_path); + assert(lp); + + if (set_size(remove_symlinks_to) <= 0) + return 0; + + fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (fd < 0) + return -errno; + + do { + int q, cfd; + restart = false; + + cfd = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (cfd < 0) + return -errno; + + /* This takes possession of cfd and closes it */ + q = remove_marked_symlinks_fd(remove_symlinks_to, cfd, config_path, config_path, lp, &restart, changes, n_changes); + if (r == 0) + r = q; + } while (restart); + + return r; +} + +static int find_symlinks_fd( + const char *root_dir, + const char *name, + int fd, + const char *path, + const char *config_path, + const LookupPaths *lp, + bool *same_name_link) { + + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r = 0; + + assert(name); + assert(fd >= 0); + assert(path); + assert(config_path); + assert(lp); + assert(same_name_link); + + d = fdopendir(fd); + if (!d) { + safe_close(fd); + return -errno; + } + + FOREACH_DIRENT(de, d, return -errno) { + + dirent_ensure_type(d, de); + + if (de->d_type == DT_DIR) { + _cleanup_free_ char *p = NULL; + int nfd, q; + + nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (nfd < 0) { + if (errno == ENOENT) + continue; + + if (r == 0) + r = -errno; + continue; + } + + p = path_make_absolute(de->d_name, path); + if (!p) { + safe_close(nfd); + return -ENOMEM; + } + + /* This will close nfd, regardless whether it succeeds or not */ + q = find_symlinks_fd(root_dir, name, nfd, p, config_path, lp, same_name_link); + if (q > 0) + return 1; + if (r == 0) + r = q; + + } else if (de->d_type == DT_LNK) { + _cleanup_free_ char *p = NULL, *dest = NULL; + bool found_path, found_dest, b = false; + int q; + + /* Acquire symlink name */ + p = path_make_absolute(de->d_name, path); + if (!p) + return -ENOMEM; + + /* Acquire symlink destination */ + q = readlink_malloc(p, &dest); + if (q == -ENOENT) + continue; + if (q < 0) { + if (r == 0) + r = q; + continue; + } + + /* Make absolute */ + if (!path_is_absolute(dest)) { + char *x; + + x = prefix_root(root_dir, dest); + if (!x) + return -ENOMEM; + + free(dest); + dest = x; + } + + /* Check if the symlink itself matches what we + * are looking for */ + if (path_is_absolute(name)) + found_path = path_equal(p, name); + else + found_path = streq(de->d_name, name); + + /* Check if what the symlink points to + * matches what we are looking for */ + if (path_is_absolute(name)) + found_dest = path_equal(dest, name); + else + found_dest = streq(basename(dest), name); + + if (found_path && found_dest) { + _cleanup_free_ char *t = NULL; + + /* Filter out same name links in the main + * config path */ + t = path_make_absolute(name, config_path); + if (!t) + return -ENOMEM; + + b = path_equal(t, p); + } + + if (b) + *same_name_link = true; + else if (found_path || found_dest) + return 1; + } + } + + return r; +} + +static int find_symlinks( + const char *root_dir, + const char *name, + const char *config_path, + const LookupPaths *lp, + bool *same_name_link) { + + int fd; + + assert(name); + assert(config_path); + assert(same_name_link); + + fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (fd < 0) { + if (errno == ENOENT) + return 0; + return -errno; + } + + /* This takes possession of fd and closes it */ + return find_symlinks_fd(root_dir, name, fd, config_path, config_path, lp, same_name_link); +} + +static int find_symlinks_in_scope( + UnitFileScope scope, + const LookupPaths *paths, + const char *name, + UnitFileState *state) { + + bool same_name_link_runtime = false, same_name_link = false; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(paths); + assert(name); + + /* First look in the persistent config path */ + r = find_symlinks(paths->root_dir, name, paths->persistent_config, paths, &same_name_link); + if (r < 0) + return r; + if (r > 0) { + *state = UNIT_FILE_ENABLED; + return r; + } + + /* Then look in runtime config path */ + r = find_symlinks(paths->root_dir, name, paths->runtime_config, paths, &same_name_link_runtime); + if (r < 0) + return r; + if (r > 0) { + *state = UNIT_FILE_ENABLED_RUNTIME; + return r; + } + + /* Hmm, we didn't find it, but maybe we found the same name + * link? */ + if (same_name_link) { + *state = UNIT_FILE_LINKED; + return 1; + } + if (same_name_link_runtime) { + *state = UNIT_FILE_LINKED_RUNTIME; + return 1; + } + + return 0; +} + +static void install_info_free(UnitFileInstallInfo *i) { + + if (!i) + return; + + free(i->name); + free(i->path); + strv_free(i->aliases); + strv_free(i->wanted_by); + strv_free(i->required_by); + strv_free(i->also); + free(i->default_instance); + free(i->symlink_target); + free(i); +} + +static OrderedHashmap* install_info_hashmap_free(OrderedHashmap *m) { + UnitFileInstallInfo *i; + + if (!m) + return NULL; + + while ((i = ordered_hashmap_steal_first(m))) + install_info_free(i); + + return ordered_hashmap_free(m); +} + +static void install_context_done(InstallContext *c) { + assert(c); + + c->will_process = install_info_hashmap_free(c->will_process); + c->have_processed = install_info_hashmap_free(c->have_processed); +} + +static UnitFileInstallInfo *install_info_find(InstallContext *c, const char *name) { + UnitFileInstallInfo *i; + + i = ordered_hashmap_get(c->have_processed, name); + if (i) + return i; + + return ordered_hashmap_get(c->will_process, name); +} + +static int install_info_may_process( + UnitFileInstallInfo *i, + const LookupPaths *paths, + UnitFileChange **changes, + unsigned *n_changes) { + assert(i); + assert(paths); + + /* Checks whether the loaded unit file is one we should process, or is masked, transient or generated and thus + * not subject to enable/disable operations. */ + + if (i->type == UNIT_FILE_TYPE_MASKED) { + unit_file_changes_add(changes, n_changes, -ERFKILL, i->path, NULL); + return -ERFKILL; + } + if (path_is_generator(paths, i->path) || + path_is_transient(paths, i->path)) { + unit_file_changes_add(changes, n_changes, -EADDRNOTAVAIL, i->path, NULL); + return -EADDRNOTAVAIL; + } + + return 0; +} + +static int install_info_add( + InstallContext *c, + const char *name, + const char *path, + UnitFileInstallInfo **ret) { + + UnitFileInstallInfo *i = NULL; + int r; + + assert(c); + assert(name || path); + + if (!name) + name = basename(path); + + if (!unit_name_is_valid(name, UNIT_NAME_ANY)) + return -EINVAL; + + i = install_info_find(c, name); + if (i) { + if (ret) + *ret = i; + return 0; + } + + r = ordered_hashmap_ensure_allocated(&c->will_process, &string_hash_ops); + if (r < 0) + return r; + + i = new0(UnitFileInstallInfo, 1); + if (!i) + return -ENOMEM; + i->type = _UNIT_FILE_TYPE_INVALID; + + i->name = strdup(name); + if (!i->name) { + r = -ENOMEM; + goto fail; + } + + if (path) { + i->path = strdup(path); + if (!i->path) { + r = -ENOMEM; + goto fail; + } + } + + r = ordered_hashmap_put(c->will_process, i->name, i); + if (r < 0) + goto fail; + + if (ret) + *ret = i; + + return 0; + +fail: + install_info_free(i); + return r; +} + +static int config_parse_alias( + 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) { + + const char *name; + UnitType type; + + assert(filename); + assert(lvalue); + assert(rvalue); + + name = basename(filename); + type = unit_name_to_type(name); + if (!unit_type_may_alias(type)) + return log_syntax(unit, LOG_WARNING, filename, line, 0, + "Aliases are not allowed for %s units, ignoring.", + unit_type_to_string(type)); + + return config_parse_strv(unit, filename, line, section, section_line, + lvalue, ltype, rvalue, data, userdata); +} + +static int config_parse_also( + 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) { + + UnitFileInstallInfo *i = userdata; + InstallContext *c = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&rvalue, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + r = install_info_add(c, word, NULL, NULL); + if (r < 0) + return r; + + r = strv_push(&i->also, word); + if (r < 0) + return r; + + word = NULL; + } + + return 0; +} + +static int config_parse_default_instance( + 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) { + + UnitFileInstallInfo *i = data; + const char *name; + char *printed; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + name = basename(filename); + if (unit_name_is_valid(name, UNIT_NAME_INSTANCE)) + /* When enabling an instance, we might be using a template unit file, + * but we should ignore DefaultInstance silently. */ + return 0; + if (!unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) + return log_syntax(unit, LOG_WARNING, filename, line, 0, + "DefaultInstance only makes sense for template units, ignoring."); + + r = install_full_printf(i, rvalue, &printed); + if (r < 0) + return r; + + if (!unit_instance_is_valid(printed)) { + free(printed); + return -EINVAL; + } + + free(i->default_instance); + i->default_instance = printed; + + return 0; +} + +static int unit_file_load( + InstallContext *c, + UnitFileInstallInfo *info, + const char *path, + SearchFlags flags) { + + const ConfigTableItem items[] = { + { "Install", "Alias", config_parse_alias, 0, &info->aliases }, + { "Install", "WantedBy", config_parse_strv, 0, &info->wanted_by }, + { "Install", "RequiredBy", config_parse_strv, 0, &info->required_by }, + { "Install", "DefaultInstance", config_parse_default_instance, 0, info }, + { "Install", "Also", config_parse_also, 0, c }, + {} + }; + + const char *name; + UnitType type; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_close_ int fd = -1; + struct stat st; + int r; + + assert(c); + assert(info); + assert(path); + + name = basename(path); + type = unit_name_to_type(name); + if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE|UNIT_NAME_INSTANCE) && + !unit_type_may_template(type)) + return log_error_errno(EINVAL, "Unit type %s cannot be templated.", unit_type_to_string(type)); + + if (!(flags & SEARCH_LOAD)) { + r = lstat(path, &st); + if (r < 0) + return -errno; + + if (null_or_empty(&st)) + info->type = UNIT_FILE_TYPE_MASKED; + else if (S_ISREG(st.st_mode)) + info->type = UNIT_FILE_TYPE_REGULAR; + else if (S_ISLNK(st.st_mode)) + return -ELOOP; + else if (S_ISDIR(st.st_mode)) + return -EISDIR; + else + return -ENOTTY; + + return 0; + } + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + if (fstat(fd, &st) < 0) + return -errno; + if (null_or_empty(&st)) { + info->type = UNIT_FILE_TYPE_MASKED; + return 0; + } + if (S_ISDIR(st.st_mode)) + return -EISDIR; + if (!S_ISREG(st.st_mode)) + return -ENOTTY; + + f = fdopen(fd, "re"); + if (!f) + return -errno; + fd = -1; + + r = config_parse(NULL, path, f, + NULL, + config_item_table_lookup, items, + true, true, false, info); + if (r < 0) + return r; + + info->type = UNIT_FILE_TYPE_REGULAR; + + return + (int) strv_length(info->aliases) + + (int) strv_length(info->wanted_by) + + (int) strv_length(info->required_by); +} + +static int unit_file_load_or_readlink( + InstallContext *c, + UnitFileInstallInfo *info, + const char *path, + const char *root_dir, + SearchFlags flags) { + + _cleanup_free_ char *target = NULL; + int r; + + r = unit_file_load(c, info, path, flags); + if (r != -ELOOP) + return r; + + /* This is a symlink, let's read it. */ + + r = readlink_malloc(path, &target); + if (r < 0) + return r; + + if (path_equal(target, "/dev/null")) + info->type = UNIT_FILE_TYPE_MASKED; + else { + const char *bn; + UnitType a, b; + + bn = basename(target); + + if (unit_name_is_valid(info->name, UNIT_NAME_PLAIN)) { + + if (!unit_name_is_valid(bn, UNIT_NAME_PLAIN)) + return -EINVAL; + + } else if (unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) { + + if (!unit_name_is_valid(bn, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) + return -EINVAL; + + } else if (unit_name_is_valid(info->name, UNIT_NAME_TEMPLATE)) { + + if (!unit_name_is_valid(bn, UNIT_NAME_TEMPLATE)) + return -EINVAL; + } else + return -EINVAL; + + /* Enforce that the symlink destination does not + * change the unit file type. */ + + a = unit_name_to_type(info->name); + b = unit_name_to_type(bn); + if (a < 0 || b < 0 || a != b) + return -EINVAL; + + if (path_is_absolute(target)) + /* This is an absolute path, prefix the root so that we always deal with fully qualified paths */ + info->symlink_target = prefix_root(root_dir, target); + else + /* This is a relative path, take it relative to the dir the symlink is located in. */ + info->symlink_target = file_in_same_dir(path, target); + if (!info->symlink_target) + return -ENOMEM; + + info->type = UNIT_FILE_TYPE_SYMLINK; + } + + return 0; +} + +static int unit_file_search( + InstallContext *c, + UnitFileInstallInfo *info, + const LookupPaths *paths, + SearchFlags flags) { + + _cleanup_free_ char *template = NULL; + char **p; + int r; + + assert(c); + assert(info); + assert(paths); + + /* Was this unit already loaded? */ + if (info->type != _UNIT_FILE_TYPE_INVALID) + return 0; + + if (info->path) + return unit_file_load_or_readlink(c, info, info->path, paths->root_dir, flags); + + assert(info->name); + + STRV_FOREACH(p, paths->search_path) { + _cleanup_free_ char *path = NULL; + + path = strjoin(*p, "/", info->name, NULL); + if (!path) + return -ENOMEM; + + r = unit_file_load_or_readlink(c, info, path, paths->root_dir, flags); + if (r >= 0) { + info->path = path; + path = NULL; + return r; + } else if (r != -ENOENT) + return r; + } + + if (unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) { + /* Unit file doesn't exist, however instance + * enablement was requested. We will check if it is + * possible to load template unit file. */ + + r = unit_name_template(info->name, &template); + if (r < 0) + return r; + + STRV_FOREACH(p, paths->search_path) { + _cleanup_free_ char *path = NULL; + + path = strjoin(*p, "/", template, NULL); + if (!path) + return -ENOMEM; + + r = unit_file_load_or_readlink(c, info, path, paths->root_dir, flags); + if (r >= 0) { + info->path = path; + path = NULL; + return r; + } else if (r != -ENOENT) + return r; + } + } + + log_debug("Cannot find unit %s%s%s.", info->name, template ? " or " : "", strempty(template)); + return -ENOENT; +} + +static int install_info_follow( + InstallContext *c, + UnitFileInstallInfo *i, + const char *root_dir, + SearchFlags flags) { + + assert(c); + assert(i); + + if (i->type != UNIT_FILE_TYPE_SYMLINK) + return -EINVAL; + if (!i->symlink_target) + return -EINVAL; + + /* If the basename doesn't match, the caller should add a + * complete new entry for this. */ + + if (!streq(basename(i->symlink_target), i->name)) + return -EXDEV; + + free(i->path); + i->path = i->symlink_target; + i->symlink_target = NULL; + i->type = _UNIT_FILE_TYPE_INVALID; + + return unit_file_load_or_readlink(c, i, i->path, root_dir, flags); +} + +/** + * Search for the unit file. If the unit name is a symlink, + * follow the symlink to the target, maybe more than once. + * Propagate the instance name if present. + */ +static int install_info_traverse( + UnitFileScope scope, + InstallContext *c, + const LookupPaths *paths, + UnitFileInstallInfo *start, + SearchFlags flags, + UnitFileInstallInfo **ret) { + + UnitFileInstallInfo *i; + unsigned k = 0; + int r; + + assert(paths); + assert(start); + assert(c); + + r = unit_file_search(c, start, paths, flags); + if (r < 0) + return r; + + i = start; + while (i->type == UNIT_FILE_TYPE_SYMLINK) { + /* Follow the symlink */ + + if (++k > UNIT_FILE_FOLLOW_SYMLINK_MAX) + return -ELOOP; + + if (!(flags & SEARCH_FOLLOW_CONFIG_SYMLINKS)) { + r = path_is_config(paths, i->path); + if (r < 0) + return r; + if (r > 0) + return -ELOOP; + } + + r = install_info_follow(c, i, paths->root_dir, flags); + if (r == -EXDEV) { + _cleanup_free_ char *buffer = NULL; + const char *bn; + + /* Target has a different name, create a new + * install info object for that, and continue + * with that. */ + + bn = basename(i->symlink_target); + + if (unit_name_is_valid(i->name, UNIT_NAME_INSTANCE) && + unit_name_is_valid(bn, UNIT_NAME_TEMPLATE)) { + + _cleanup_free_ char *instance = NULL; + + r = unit_name_to_instance(i->name, &instance); + if (r < 0) + return r; + + r = unit_name_replace_instance(bn, instance, &buffer); + if (r < 0) + return r; + + bn = buffer; + } + + r = install_info_add(c, bn, NULL, &i); + if (r < 0) + return r; + + /* Try again, with the new target we found. */ + r = unit_file_search(c, i, paths, flags); + if (r == -ENOENT) + /* Translate error code to highlight this specific case */ + return -ENOLINK; + } + + if (r < 0) + return r; + } + + if (ret) + *ret = i; + + return 0; +} + +static int install_info_add_auto( + InstallContext *c, + const LookupPaths *paths, + const char *name_or_path, + UnitFileInstallInfo **ret) { + + assert(c); + assert(name_or_path); + + if (path_is_absolute(name_or_path)) { + const char *pp; + + pp = prefix_roota(paths->root_dir, name_or_path); + + return install_info_add(c, NULL, pp, ret); + } else + return install_info_add(c, name_or_path, NULL, ret); +} + +static int install_info_discover( + UnitFileScope scope, + InstallContext *c, + const LookupPaths *paths, + const char *name, + SearchFlags flags, + UnitFileInstallInfo **ret) { + + UnitFileInstallInfo *i; + int r; + + assert(c); + assert(paths); + assert(name); + + r = install_info_add_auto(c, paths, name, &i); + if (r < 0) + return r; + + return install_info_traverse(scope, c, paths, i, flags, ret); +} + +static int install_info_symlink_alias( + UnitFileInstallInfo *i, + const LookupPaths *paths, + const char *config_path, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + char **s; + int r = 0, q; + + assert(i); + assert(paths); + assert(config_path); + + STRV_FOREACH(s, i->aliases) { + _cleanup_free_ char *alias_path = NULL, *dst = NULL; + const char *rp; + + q = install_full_printf(i, *s, &dst); + if (q < 0) + return q; + + alias_path = path_make_absolute(dst, config_path); + if (!alias_path) + return -ENOMEM; + + rp = skip_root(paths, i->path); + + q = create_symlink(rp ?: i->path, alias_path, force, changes, n_changes); + if (r == 0) + r = q; + } + + return r; +} + +static int install_info_symlink_wants( + UnitFileInstallInfo *i, + const LookupPaths *paths, + const char *config_path, + char **list, + const char *suffix, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_free_ char *buf = NULL; + const char *n; + char **s; + int r = 0, q; + + assert(i); + assert(paths); + assert(config_path); + + if (unit_name_is_valid(i->name, UNIT_NAME_TEMPLATE)) { + + /* Don't install any symlink if there's no default + * instance configured */ + + if (!i->default_instance) + return 0; + + r = unit_name_replace_instance(i->name, i->default_instance, &buf); + if (r < 0) + return r; + + n = buf; + } else + n = i->name; + + STRV_FOREACH(s, list) { + _cleanup_free_ char *path = NULL, *dst = NULL; + const char *rp; + + q = install_full_printf(i, *s, &dst); + if (q < 0) + return q; + + if (!unit_name_is_valid(dst, UNIT_NAME_ANY)) { + r = -EINVAL; + continue; + } + + path = strjoin(config_path, "/", dst, suffix, n, NULL); + if (!path) + return -ENOMEM; + + rp = skip_root(paths, i->path); + + q = create_symlink(rp ?: i->path, path, true, changes, n_changes); + if (r == 0) + r = q; + } + + return r; +} + +static int install_info_symlink_link( + UnitFileInstallInfo *i, + const LookupPaths *paths, + const char *config_path, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_free_ char *path = NULL; + const char *rp; + int r; + + assert(i); + assert(paths); + assert(config_path); + assert(i->path); + + r = in_search_path(paths, i->path); + if (r < 0) + return r; + if (r > 0) + return 0; + + path = strjoin(config_path, "/", i->name, NULL); + if (!path) + return -ENOMEM; + + rp = skip_root(paths, i->path); + + return create_symlink(rp ?: i->path, path, force, changes, n_changes); +} + +static int install_info_apply( + UnitFileInstallInfo *i, + const LookupPaths *paths, + const char *config_path, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + int r, q; + + assert(i); + assert(paths); + assert(config_path); + + if (i->type != UNIT_FILE_TYPE_REGULAR) + return 0; + + r = install_info_symlink_alias(i, paths, config_path, force, changes, n_changes); + + q = install_info_symlink_wants(i, paths, config_path, i->wanted_by, ".wants/", changes, n_changes); + if (r == 0) + r = q; + + q = install_info_symlink_wants(i, paths, config_path, i->required_by, ".requires/", changes, n_changes); + if (r == 0) + r = q; + + q = install_info_symlink_link(i, paths, config_path, force, changes, n_changes); + /* Do not count links to the unit file towards the "carries_install_info" count */ + if (r == 0 && q < 0) + r = q; + + return r; +} + +static int install_context_apply( + UnitFileScope scope, + InstallContext *c, + const LookupPaths *paths, + const char *config_path, + bool force, + SearchFlags flags, + UnitFileChange **changes, + unsigned *n_changes) { + + UnitFileInstallInfo *i; + int r; + + assert(c); + assert(paths); + assert(config_path); + + if (ordered_hashmap_isempty(c->will_process)) + return 0; + + r = ordered_hashmap_ensure_allocated(&c->have_processed, &string_hash_ops); + if (r < 0) + return r; + + r = 0; + while ((i = ordered_hashmap_first(c->will_process))) { + int q; + + q = ordered_hashmap_move_one(c->have_processed, c->will_process, i->name); + if (q < 0) + return q; + + r = install_info_traverse(scope, c, paths, i, flags, NULL); + if (r < 0) + return r; + + if (i->type != UNIT_FILE_TYPE_REGULAR) + continue; + + q = install_info_apply(i, paths, config_path, force, changes, n_changes); + if (r >= 0) { + if (q < 0) + r = q; + else + r += q; + } + } + + return r; +} + +static int install_context_mark_for_removal( + UnitFileScope scope, + InstallContext *c, + const LookupPaths *paths, + Set **remove_symlinks_to, + const char *config_path) { + + UnitFileInstallInfo *i; + int r; + + assert(c); + assert(paths); + assert(config_path); + + /* Marks all items for removal */ + + if (ordered_hashmap_isempty(c->will_process)) + return 0; + + r = ordered_hashmap_ensure_allocated(&c->have_processed, &string_hash_ops); + if (r < 0) + return r; + + while ((i = ordered_hashmap_first(c->will_process))) { + + r = ordered_hashmap_move_one(c->have_processed, c->will_process, i->name); + if (r < 0) + return r; + + r = install_info_traverse(scope, c, paths, i, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, NULL); + if (r == -ENOLINK) + return 0; + else if (r < 0) + return r; + + if (i->type != UNIT_FILE_TYPE_REGULAR) { + log_debug("Unit %s has type %s, ignoring.", + i->name, + unit_file_type_to_string(i->type) ?: "invalid"); + continue; + } + + r = mark_symlink_for_removal(remove_symlinks_to, i->name); + if (r < 0) + return r; + } + + return 0; +} + +int unit_file_mask( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + const char *config_path; + char **i; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + r = lookup_paths_init(&paths, scope, 0, root_dir); + if (r < 0) + return r; + + config_path = runtime ? paths.runtime_config : paths.persistent_config; + + STRV_FOREACH(i, files) { + _cleanup_free_ char *path = NULL; + int q; + + if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) { + if (r == 0) + r = -EINVAL; + continue; + } + + path = path_make_absolute(*i, config_path); + if (!path) + return -ENOMEM; + + q = create_symlink("/dev/null", path, force, changes, n_changes); + if (q < 0 && r >= 0) + r = q; + } + + return r; +} + +int unit_file_unmask( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; + _cleanup_free_ char **todo = NULL; + size_t n_todo = 0, n_allocated = 0; + const char *config_path; + char **i; + int r, q; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + r = lookup_paths_init(&paths, scope, 0, root_dir); + if (r < 0) + return r; + + config_path = runtime ? paths.runtime_config : paths.persistent_config; + + STRV_FOREACH(i, files) { + _cleanup_free_ char *path = NULL; + + if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) + return -EINVAL; + + path = path_make_absolute(*i, config_path); + if (!path) + return -ENOMEM; + + r = null_or_empty_path(path); + if (r == -ENOENT) + continue; + if (r < 0) + return r; + if (r == 0) + continue; + + if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2)) + return -ENOMEM; + + todo[n_todo++] = *i; + } + + strv_uniq(todo); + + r = 0; + STRV_FOREACH(i, todo) { + _cleanup_free_ char *path = NULL; + const char *rp; + + path = path_make_absolute(*i, config_path); + if (!path) + return -ENOMEM; + + if (unlink(path) < 0) { + if (errno != ENOENT) { + if (r >= 0) + r = -errno; + unit_file_changes_add(changes, n_changes, -errno, path, NULL); + } + + continue; + } + + unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, path, NULL); + + rp = skip_root(&paths, path); + q = mark_symlink_for_removal(&remove_symlinks_to, rp ?: path); + if (q < 0) + return q; + } + + q = remove_marked_symlinks(remove_symlinks_to, config_path, &paths, changes, n_changes); + if (r >= 0) + r = q; + + return r; +} + +int unit_file_link( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_free_ char **todo = NULL; + size_t n_todo = 0, n_allocated = 0; + const char *config_path; + char **i; + int r, q; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + r = lookup_paths_init(&paths, scope, 0, root_dir); + if (r < 0) + return r; + + config_path = runtime ? paths.runtime_config : paths.persistent_config; + + STRV_FOREACH(i, files) { + _cleanup_free_ char *full = NULL; + struct stat st; + char *fn; + + if (!path_is_absolute(*i)) + return -EINVAL; + + fn = basename(*i); + if (!unit_name_is_valid(fn, UNIT_NAME_ANY)) + return -EINVAL; + + full = prefix_root(paths.root_dir, *i); + if (!full) + return -ENOMEM; + + if (lstat(full, &st) < 0) + return -errno; + if (S_ISLNK(st.st_mode)) + return -ELOOP; + if (S_ISDIR(st.st_mode)) + return -EISDIR; + if (!S_ISREG(st.st_mode)) + return -ENOTTY; + + q = in_search_path(&paths, *i); + if (q < 0) + return q; + if (q > 0) + continue; + + if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2)) + return -ENOMEM; + + todo[n_todo++] = *i; + } + + strv_uniq(todo); + + r = 0; + STRV_FOREACH(i, todo) { + _cleanup_free_ char *new_path = NULL; + const char *old_path; + + old_path = skip_root(&paths, *i); + new_path = path_make_absolute(basename(*i), config_path); + if (!new_path) + return -ENOMEM; + + q = create_symlink(old_path ?: *i, new_path, force, changes, n_changes); + if (q < 0 && r >= 0) + r = q; + } + + return r; +} + +static int path_shall_revert(const LookupPaths *paths, const char *path) { + int r; + + assert(paths); + assert(path); + + /* Checks whether the path is one where the drop-in directories shall be removed. */ + + r = path_is_config(paths, path); + if (r != 0) + return r; + + r = path_is_control(paths, path); + if (r != 0) + return r; + + return path_is_transient(paths, path); +} + +int unit_file_revert( + UnitFileScope scope, + const char *root_dir, + char **files, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; + /* _cleanup_(install_context_done) InstallContext c = {}; */ + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_strv_free_ char **todo = NULL; + size_t n_todo = 0, n_allocated = 0; + char **i; + int r, q; + + /* Puts a unit file back into vendor state. This means: + * + * a) we remove all drop-in snippets added by the user ("config"), add to transient units ("transient"), and + * added via "systemctl set-property" ("control"), but not if the drop-in is generated ("generated"). + * + * c) if there's a vendor unit file (i.e. one in /usr) we remove any configured overriding unit files (i.e. in + * "config", but not in "transient" or "control" or even "generated"). + * + * We remove all that in both the runtime and the persistent directories, if that applies. + */ + + r = lookup_paths_init(&paths, scope, 0, root_dir); + if (r < 0) + return r; + + STRV_FOREACH(i, files) { + bool has_vendor = false; + char **p; + + if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) + return -EINVAL; + + STRV_FOREACH(p, paths.search_path) { + _cleanup_free_ char *path = NULL, *dropin = NULL; + struct stat st; + + path = path_make_absolute(*i, *p); + if (!path) + return -ENOMEM; + + r = lstat(path, &st); + if (r < 0) { + if (errno != ENOENT) + return -errno; + } else if (S_ISREG(st.st_mode)) { + /* Check if there's a vendor version */ + r = path_is_vendor(&paths, path); + if (r < 0) + return r; + if (r > 0) + has_vendor = true; + } + + dropin = strappend(path, ".d"); + if (!dropin) + return -ENOMEM; + + r = lstat(dropin, &st); + if (r < 0) { + if (errno != ENOENT) + return -errno; + } else if (S_ISDIR(st.st_mode)) { + /* Remove the drop-ins */ + r = path_shall_revert(&paths, dropin); + if (r < 0) + return r; + if (r > 0) { + if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2)) + return -ENOMEM; + + todo[n_todo++] = dropin; + dropin = NULL; + } + } + } + + if (!has_vendor) + continue; + + /* OK, there's a vendor version, hence drop all configuration versions */ + STRV_FOREACH(p, paths.search_path) { + _cleanup_free_ char *path = NULL; + struct stat st; + + path = path_make_absolute(*i, *p); + if (!path) + return -ENOMEM; + + r = lstat(path, &st); + if (r < 0) { + if (errno != ENOENT) + return -errno; + } else if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { + r = path_is_config(&paths, path); + if (r < 0) + return r; + if (r > 0) { + if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2)) + return -ENOMEM; + + todo[n_todo++] = path; + path = NULL; + } + } + } + } + + strv_uniq(todo); + + r = 0; + STRV_FOREACH(i, todo) { + _cleanup_strv_free_ char **fs = NULL; + const char *rp; + char **j; + + (void) get_files_in_directory(*i, &fs); + + q = rm_rf(*i, REMOVE_ROOT|REMOVE_PHYSICAL); + if (q < 0 && q != -ENOENT && r >= 0) { + r = q; + continue; + } + + STRV_FOREACH(j, fs) { + _cleanup_free_ char *t = NULL; + + t = strjoin(*i, "/", *j, NULL); + if (!t) + return -ENOMEM; + + unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, t, NULL); + } + + unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, *i, NULL); + + rp = skip_root(&paths, *i); + q = mark_symlink_for_removal(&remove_symlinks_to, rp ?: *i); + if (q < 0) + return q; + } + + q = remove_marked_symlinks(remove_symlinks_to, paths.runtime_config, &paths, changes, n_changes); + if (r >= 0) + r = q; + + q = remove_marked_symlinks(remove_symlinks_to, paths.persistent_config, &paths, changes, n_changes); + if (r >= 0) + r = q; + + return r; +} + +int unit_file_add_dependency( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + const char *target, + UnitDependency dep, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_(install_context_done) InstallContext c = {}; + UnitFileInstallInfo *i, *target_info; + const char *config_path; + char **f; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(target); + + if (!IN_SET(dep, UNIT_WANTS, UNIT_REQUIRES)) + return -EINVAL; + + if (!unit_name_is_valid(target, UNIT_NAME_ANY)) + return -EINVAL; + + r = lookup_paths_init(&paths, scope, 0, root_dir); + if (r < 0) + return r; + + config_path = runtime ? paths.runtime_config : paths.persistent_config; + + r = install_info_discover(scope, &c, &paths, target, SEARCH_FOLLOW_CONFIG_SYMLINKS, &target_info); + if (r < 0) + return r; + r = install_info_may_process(target_info, &paths, changes, n_changes); + if (r < 0) + return r; + + assert(target_info->type == UNIT_FILE_TYPE_REGULAR); + + STRV_FOREACH(f, files) { + char ***l; + + r = install_info_discover(scope, &c, &paths, *f, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + if (r < 0) + return r; + r = install_info_may_process(i, &paths, changes, n_changes); + if (r < 0) + return r; + + assert(i->type == UNIT_FILE_TYPE_REGULAR); + + /* We didn't actually load anything from the unit + * file, but instead just add in our new symlink to + * create. */ + + if (dep == UNIT_WANTS) + l = &i->wanted_by; + else + l = &i->required_by; + + strv_free(*l); + *l = strv_new(target_info->name, NULL); + if (!*l) + return -ENOMEM; + } + + return install_context_apply(scope, &c, &paths, config_path, force, SEARCH_FOLLOW_CONFIG_SYMLINKS, changes, n_changes); +} + +int unit_file_enable( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_(install_context_done) InstallContext c = {}; + const char *config_path; + UnitFileInstallInfo *i; + char **f; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + r = lookup_paths_init(&paths, scope, 0, root_dir); + if (r < 0) + return r; + + config_path = runtime ? paths.runtime_config : paths.persistent_config; + + STRV_FOREACH(f, files) { + r = install_info_discover(scope, &c, &paths, *f, SEARCH_LOAD, &i); + if (r < 0) + return r; + r = install_info_may_process(i, &paths, changes, n_changes); + if (r < 0) + return r; + + assert(i->type == UNIT_FILE_TYPE_REGULAR); + } + + /* This will return the number of symlink rules that were + supposed to be created, not the ones actually created. This + is useful to determine whether the passed files had any + installation data at all. */ + + return install_context_apply(scope, &c, &paths, config_path, force, SEARCH_LOAD, changes, n_changes); +} + +int unit_file_disable( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_(install_context_done) InstallContext c = {}; + _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; + const char *config_path; + char **i; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + r = lookup_paths_init(&paths, scope, 0, root_dir); + if (r < 0) + return r; + + config_path = runtime ? paths.runtime_config : paths.persistent_config; + + STRV_FOREACH(i, files) { + if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) + return -EINVAL; + + r = install_info_add(&c, *i, NULL, NULL); + if (r < 0) + return r; + } + + r = install_context_mark_for_removal(scope, &c, &paths, &remove_symlinks_to, config_path); + if (r < 0) + return r; + + return remove_marked_symlinks(remove_symlinks_to, config_path, &paths, changes, n_changes); +} + +int unit_file_reenable( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + char **n; + int r; + size_t l, i; + + /* First, we invoke the disable command with only the basename... */ + l = strv_length(files); + n = newa(char*, l+1); + for (i = 0; i < l; i++) + n[i] = basename(files[i]); + n[i] = NULL; + + r = unit_file_disable(scope, runtime, root_dir, n, changes, n_changes); + if (r < 0) + return r; + + /* But the enable command with the full name */ + return unit_file_enable(scope, runtime, root_dir, files, force, changes, n_changes); +} + +int unit_file_set_default( + UnitFileScope scope, + const char *root_dir, + const char *name, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_(install_context_done) InstallContext c = {}; + UnitFileInstallInfo *i; + const char *new_path, *old_path; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(name); + + if (unit_name_to_type(name) != UNIT_TARGET) /* this also validates the name */ + return -EINVAL; + if (streq(name, SPECIAL_DEFAULT_TARGET)) + return -EINVAL; + + r = lookup_paths_init(&paths, scope, 0, root_dir); + if (r < 0) + return r; + + r = install_info_discover(scope, &c, &paths, name, 0, &i); + if (r < 0) + return r; + r = install_info_may_process(i, &paths, changes, n_changes); + if (r < 0) + return r; + + old_path = skip_root(&paths, i->path); + new_path = strjoina(paths.persistent_config, "/" SPECIAL_DEFAULT_TARGET); + + return create_symlink(old_path ?: i->path, new_path, force, changes, n_changes); +} + +int unit_file_get_default( + UnitFileScope scope, + const char *root_dir, + char **name) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_(install_context_done) InstallContext c = {}; + UnitFileInstallInfo *i; + char *n; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(name); + + r = lookup_paths_init(&paths, scope, 0, root_dir); + if (r < 0) + return r; + + r = install_info_discover(scope, &c, &paths, SPECIAL_DEFAULT_TARGET, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + if (r < 0) + return r; + r = install_info_may_process(i, &paths, NULL, 0); + if (r < 0) + return r; + + n = strdup(i->name); + if (!n) + return -ENOMEM; + + *name = n; + return 0; +} + +static int unit_file_lookup_state( + UnitFileScope scope, + const LookupPaths *paths, + const char *name, + UnitFileState *ret) { + + _cleanup_(install_context_done) InstallContext c = {}; + UnitFileInstallInfo *i; + UnitFileState state; + int r; + + assert(paths); + assert(name); + + if (!unit_name_is_valid(name, UNIT_NAME_ANY)) + return -EINVAL; + + r = install_info_discover(scope, &c, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + if (r < 0) + return r; + + /* Shortcut things, if the caller just wants to know if this unit exists. */ + if (!ret) + return 0; + + switch (i->type) { + + case UNIT_FILE_TYPE_MASKED: + r = path_is_runtime(paths, i->path); + if (r < 0) + return r; + + state = r > 0 ? UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED; + break; + + case UNIT_FILE_TYPE_REGULAR: + r = path_is_generator(paths, i->path); + if (r < 0) + return r; + if (r > 0) { + state = UNIT_FILE_GENERATED; + break; + } + + r = path_is_transient(paths, i->path); + if (r < 0) + return r; + if (r > 0) { + state = UNIT_FILE_TRANSIENT; + break; + } + + r = find_symlinks_in_scope(scope, paths, i->name, &state); + if (r < 0) + return r; + if (r == 0) { + if (UNIT_FILE_INSTALL_INFO_HAS_RULES(i)) + state = UNIT_FILE_DISABLED; + else if (UNIT_FILE_INSTALL_INFO_HAS_ALSO(i)) + state = UNIT_FILE_INDIRECT; + else + state = UNIT_FILE_STATIC; + } + + break; + + default: + assert_not_reached("Unexpect unit file type."); + } + + *ret = state; + return 0; +} + +int unit_file_get_state( + UnitFileScope scope, + const char *root_dir, + const char *name, + UnitFileState *ret) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(name); + + r = lookup_paths_init(&paths, scope, 0, root_dir); + if (r < 0) + return r; + + return unit_file_lookup_state(scope, &paths, name, ret); +} + +int unit_file_exists(UnitFileScope scope, const LookupPaths *paths, const char *name) { + _cleanup_(install_context_done) InstallContext c = {}; + int r; + + assert(paths); + assert(name); + + if (!unit_name_is_valid(name, UNIT_NAME_ANY)) + return -EINVAL; + + r = install_info_discover(scope, &c, paths, name, 0, NULL); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + return 1; +} + +static int read_presets(UnitFileScope scope, const char *root_dir, Presets *presets) { + _cleanup_(presets_freep) Presets ps = {}; + size_t n_allocated = 0; + _cleanup_strv_free_ char **files = NULL; + char **p; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(presets); + + if (scope == UNIT_FILE_SYSTEM) + r = conf_files_list(&files, ".preset", root_dir, + "/etc/systemd/system-preset", + "/usr/local/lib/systemd/system-preset", + "/usr/lib/systemd/system-preset", +#ifdef HAVE_SPLIT_USR + "/lib/systemd/system-preset", +#endif + NULL); + else if (scope == UNIT_FILE_GLOBAL) + r = conf_files_list(&files, ".preset", root_dir, + "/etc/systemd/user-preset", + "/usr/local/lib/systemd/user-preset", + "/usr/lib/systemd/user-preset", + NULL); + else { + *presets = (Presets){}; + + return 0; + } + + if (r < 0) + return r; + + STRV_FOREACH(p, files) { + _cleanup_fclose_ FILE *f; + char line[LINE_MAX]; + int n = 0; + + f = fopen(*p, "re"); + if (!f) { + if (errno == ENOENT) + continue; + + return -errno; + } + + FOREACH_LINE(line, f, return -errno) { + PresetRule rule = {}; + const char *parameter; + char *l; + + l = strstrip(line); + n++; + + if (isempty(l)) + continue; + if (strchr(COMMENTS, *l)) + continue; + + parameter = first_word(l, "enable"); + if (parameter) { + char *pattern; + + pattern = strdup(parameter); + if (!pattern) + return -ENOMEM; + + rule = (PresetRule) { + .pattern = pattern, + .action = PRESET_ENABLE, + }; + } + + parameter = first_word(l, "disable"); + if (parameter) { + char *pattern; + + pattern = strdup(parameter); + if (!pattern) + return -ENOMEM; + + rule = (PresetRule) { + .pattern = pattern, + .action = PRESET_DISABLE, + }; + } + + if (rule.action) { + if (!GREEDY_REALLOC(ps.rules, n_allocated, ps.n_rules + 1)) + return -ENOMEM; + + ps.rules[ps.n_rules++] = rule; + continue; + } + + log_syntax(NULL, LOG_WARNING, *p, n, 0, "Couldn't parse line '%s'. Ignoring.", line); + } + } + + *presets = ps; + ps = (Presets){}; + + return 0; +} + +static int query_presets(const char *name, const Presets presets) { + PresetAction action = PRESET_UNKNOWN; + size_t i; + + if (!unit_name_is_valid(name, UNIT_NAME_ANY)) + return -EINVAL; + + for (i = 0; i < presets.n_rules; i++) + if (fnmatch(presets.rules[i].pattern, name, FNM_NOESCAPE) == 0) { + action = presets.rules[i].action; + break; + } + + switch (action) { + case PRESET_UNKNOWN: + log_debug("Preset files don't specify rule for %s. Enabling.", name); + return 1; + case PRESET_ENABLE: + log_debug("Preset files say enable %s.", name); + return 1; + case PRESET_DISABLE: + log_debug("Preset files say disable %s.", name); + return 0; + default: + assert_not_reached("invalid preset action"); + } +} + +int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char *name) { + _cleanup_(presets_freep) Presets presets = {}; + int r; + + r = read_presets(scope, root_dir, &presets); + if (r < 0) + return r; + + return query_presets(name, presets); +} + +static int execute_preset( + UnitFileScope scope, + InstallContext *plus, + InstallContext *minus, + const LookupPaths *paths, + const char *config_path, + char **files, + UnitFilePresetMode mode, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + int r; + + assert(plus); + assert(minus); + assert(paths); + assert(config_path); + + if (mode != UNIT_FILE_PRESET_ENABLE_ONLY) { + _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; + + r = install_context_mark_for_removal(scope, minus, paths, &remove_symlinks_to, config_path); + if (r < 0) + return r; + + r = remove_marked_symlinks(remove_symlinks_to, config_path, paths, changes, n_changes); + } else + r = 0; + + if (mode != UNIT_FILE_PRESET_DISABLE_ONLY) { + int q; + + /* Returns number of symlinks that where supposed to be installed. */ + q = install_context_apply(scope, plus, paths, config_path, force, SEARCH_LOAD, changes, n_changes); + if (r >= 0) { + if (q < 0) + r = q; + else + r += q; + } + } + + return r; +} + +static int preset_prepare_one( + UnitFileScope scope, + InstallContext *plus, + InstallContext *minus, + LookupPaths *paths, + UnitFilePresetMode mode, + const char *name, + Presets presets, + UnitFileChange **changes, + unsigned *n_changes) { + + UnitFileInstallInfo *i; + int r; + + if (install_info_find(plus, name) || + install_info_find(minus, name)) + return 0; + + r = query_presets(name, presets); + if (r < 0) + return r; + + if (r > 0) { + r = install_info_discover(scope, plus, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + if (r < 0) + return r; + + r = install_info_may_process(i, paths, changes, n_changes); + if (r < 0) + return r; + } else + r = install_info_discover(scope, minus, paths, name, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + + return r; +} + +int unit_file_preset( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + UnitFilePresetMode mode, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_(install_context_done) InstallContext plus = {}, minus = {}; + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_(presets_freep) Presets presets = {}; + const char *config_path; + char **i; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(mode < _UNIT_FILE_PRESET_MAX); + + r = lookup_paths_init(&paths, scope, 0, root_dir); + if (r < 0) + return r; + + config_path = runtime ? paths.runtime_config : paths.persistent_config; + + r = read_presets(scope, root_dir, &presets); + if (r < 0) + return r; + + STRV_FOREACH(i, files) { + r = preset_prepare_one(scope, &plus, &minus, &paths, mode, *i, presets, changes, n_changes); + if (r < 0) + return r; + } + + return execute_preset(scope, &plus, &minus, &paths, config_path, files, mode, force, changes, n_changes); +} + +int unit_file_preset_all( + UnitFileScope scope, + bool runtime, + const char *root_dir, + UnitFilePresetMode mode, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_(install_context_done) InstallContext plus = {}, minus = {}; + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_(presets_freep) Presets presets = {}; + const char *config_path = NULL; + char **i; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(mode < _UNIT_FILE_PRESET_MAX); + + r = lookup_paths_init(&paths, scope, 0, root_dir); + if (r < 0) + return r; + + config_path = runtime ? paths.runtime_config : paths.persistent_config; + + r = read_presets(scope, root_dir, &presets); + if (r < 0) + return r; + + STRV_FOREACH(i, paths.search_path) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + + d = opendir(*i); + if (!d) { + if (errno == ENOENT) + continue; + + return -errno; + } + + FOREACH_DIRENT(de, d, return -errno) { + + if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) + continue; + + dirent_ensure_type(d, de); + + if (!IN_SET(de->d_type, DT_LNK, DT_REG)) + continue; + + /* we don't pass changes[] in, because we want to handle errors on our own */ + r = preset_prepare_one(scope, &plus, &minus, &paths, mode, de->d_name, presets, NULL, 0); + if (r == -ERFKILL) + r = unit_file_changes_add(changes, n_changes, + UNIT_FILE_IS_MASKED, de->d_name, NULL); + else if (r == -ENOLINK) + r = unit_file_changes_add(changes, n_changes, + UNIT_FILE_IS_DANGLING, de->d_name, NULL); + if (r < 0) + return r; + } + } + + return execute_preset(scope, &plus, &minus, &paths, config_path, NULL, mode, force, changes, n_changes); +} + +static void unit_file_list_free_one(UnitFileList *f) { + if (!f) + return; + + free(f->path); + free(f); +} + +Hashmap* unit_file_list_free(Hashmap *h) { + UnitFileList *i; + + while ((i = hashmap_steal_first(h))) + unit_file_list_free_one(i); + + return hashmap_free(h); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(UnitFileList*, unit_file_list_free_one); + +int unit_file_get_list( + UnitFileScope scope, + const char *root_dir, + Hashmap *h, + char **states, + char **patterns) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + char **i; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(h); + + r = lookup_paths_init(&paths, scope, 0, root_dir); + if (r < 0) + return r; + + STRV_FOREACH(i, paths.search_path) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + + d = opendir(*i); + if (!d) { + if (errno == ENOENT) + continue; + + return -errno; + } + + FOREACH_DIRENT(de, d, return -errno) { + _cleanup_(unit_file_list_free_onep) UnitFileList *f = NULL; + + if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) + continue; + + if (!strv_fnmatch_or_empty(patterns, de->d_name, FNM_NOESCAPE)) + continue; + + if (hashmap_get(h, de->d_name)) + continue; + + dirent_ensure_type(d, de); + + if (!IN_SET(de->d_type, DT_LNK, DT_REG)) + continue; + + f = new0(UnitFileList, 1); + if (!f) + return -ENOMEM; + + f->path = path_make_absolute(de->d_name, *i); + if (!f->path) + return -ENOMEM; + + r = unit_file_lookup_state(scope, &paths, de->d_name, &f->state); + if (r < 0) + f->state = UNIT_FILE_BAD; + + if (!strv_isempty(states) && + !strv_contains(states, unit_file_state_to_string(f->state))) + continue; + + r = hashmap_put(h, basename(f->path), f); + if (r < 0) + return r; + + f = NULL; /* prevent cleanup */ + } + } + + return 0; +} + +static const char* const unit_file_state_table[_UNIT_FILE_STATE_MAX] = { + [UNIT_FILE_ENABLED] = "enabled", + [UNIT_FILE_ENABLED_RUNTIME] = "enabled-runtime", + [UNIT_FILE_LINKED] = "linked", + [UNIT_FILE_LINKED_RUNTIME] = "linked-runtime", + [UNIT_FILE_MASKED] = "masked", + [UNIT_FILE_MASKED_RUNTIME] = "masked-runtime", + [UNIT_FILE_STATIC] = "static", + [UNIT_FILE_DISABLED] = "disabled", + [UNIT_FILE_INDIRECT] = "indirect", + [UNIT_FILE_GENERATED] = "generated", + [UNIT_FILE_TRANSIENT] = "transient", + [UNIT_FILE_BAD] = "bad", +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_file_state, UnitFileState); + +static const char* const unit_file_change_type_table[_UNIT_FILE_CHANGE_TYPE_MAX] = { + [UNIT_FILE_SYMLINK] = "symlink", + [UNIT_FILE_UNLINK] = "unlink", + [UNIT_FILE_IS_MASKED] = "masked", + [UNIT_FILE_IS_DANGLING] = "dangling", +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_file_change_type, UnitFileChangeType); + +static const char* const unit_file_preset_mode_table[_UNIT_FILE_PRESET_MAX] = { + [UNIT_FILE_PRESET_FULL] = "full", + [UNIT_FILE_PRESET_ENABLE_ONLY] = "enable-only", + [UNIT_FILE_PRESET_DISABLE_ONLY] = "disable-only", +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_file_preset_mode, UnitFilePresetMode); diff --git a/src/libshared/install.h b/src/libshared/install.h new file mode 100644 index 0000000000..c6aa4f6ef1 --- /dev/null +++ b/src/libshared/install.h @@ -0,0 +1,256 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +typedef enum UnitFileScope UnitFileScope; +typedef enum UnitFileState UnitFileState; +typedef enum UnitFilePresetMode UnitFilePresetMode; +typedef enum UnitFileChangeType UnitFileChangeType; +typedef enum UnitFileType UnitFileType; +typedef struct UnitFileChange UnitFileChange; +typedef struct UnitFileList UnitFileList; +typedef struct UnitFileInstallInfo UnitFileInstallInfo; + +#include + +#include "hashmap.h" +#include "macro.h" +#include "path-lookup.h" +#include "strv.h" +#include "unit-name.h" + +enum UnitFileScope { + UNIT_FILE_SYSTEM, + UNIT_FILE_GLOBAL, + UNIT_FILE_USER, + _UNIT_FILE_SCOPE_MAX, + _UNIT_FILE_SCOPE_INVALID = -1 +}; + +enum UnitFileState { + UNIT_FILE_ENABLED, + UNIT_FILE_ENABLED_RUNTIME, + UNIT_FILE_LINKED, + UNIT_FILE_LINKED_RUNTIME, + UNIT_FILE_MASKED, + UNIT_FILE_MASKED_RUNTIME, + UNIT_FILE_STATIC, + UNIT_FILE_DISABLED, + UNIT_FILE_INDIRECT, + UNIT_FILE_GENERATED, + UNIT_FILE_TRANSIENT, + UNIT_FILE_BAD, + _UNIT_FILE_STATE_MAX, + _UNIT_FILE_STATE_INVALID = -1 +}; + +enum UnitFilePresetMode { + UNIT_FILE_PRESET_FULL, + UNIT_FILE_PRESET_ENABLE_ONLY, + UNIT_FILE_PRESET_DISABLE_ONLY, + _UNIT_FILE_PRESET_MAX, + _UNIT_FILE_PRESET_INVALID = -1 +}; + +enum UnitFileChangeType { + UNIT_FILE_SYMLINK, + UNIT_FILE_UNLINK, + UNIT_FILE_IS_MASKED, + UNIT_FILE_IS_DANGLING, + _UNIT_FILE_CHANGE_TYPE_MAX, + _UNIT_FILE_CHANGE_INVALID = INT_MIN +}; + +/* type can either one of the UnitFileChangeTypes listed above, or a negative error. + * If source is specified, it should be the contents of the path symlink. + * In case of an error, source should be the existing symlink contents or NULL + */ +struct UnitFileChange { + int type; /* UnitFileChangeType or bust */ + char *path; + char *source; +}; + +static inline bool unit_file_changes_have_modification(const UnitFileChange* changes, unsigned n_changes) { + unsigned i; + for (i = 0; i < n_changes; i++) + if (IN_SET(changes[i].type, UNIT_FILE_SYMLINK, UNIT_FILE_UNLINK)) + return true; + return false; +} + +struct UnitFileList { + char *path; + UnitFileState state; +}; + +enum UnitFileType { + UNIT_FILE_TYPE_REGULAR, + UNIT_FILE_TYPE_SYMLINK, + UNIT_FILE_TYPE_MASKED, + _UNIT_FILE_TYPE_MAX, + _UNIT_FILE_TYPE_INVALID = -1, +}; + +struct UnitFileInstallInfo { + char *name; + char *path; + + char **aliases; + char **wanted_by; + char **required_by; + char **also; + + char *default_instance; + + UnitFileType type; + + char *symlink_target; +}; + +static inline bool UNIT_FILE_INSTALL_INFO_HAS_RULES(UnitFileInstallInfo *i) { + assert(i); + + return !strv_isempty(i->aliases) || + !strv_isempty(i->wanted_by) || + !strv_isempty(i->required_by); +} + +static inline bool UNIT_FILE_INSTALL_INFO_HAS_ALSO(UnitFileInstallInfo *i) { + assert(i); + + return !strv_isempty(i->also); +} + +bool unit_type_may_alias(UnitType type) _const_; +bool unit_type_may_template(UnitType type) _const_; + +int unit_file_enable( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + bool force, + UnitFileChange **changes, + unsigned *n_changes); +int unit_file_disable( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + UnitFileChange **changes, + unsigned *n_changes); +int unit_file_reenable( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + bool force, + UnitFileChange **changes, + unsigned *n_changes); +int unit_file_preset( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + UnitFilePresetMode mode, + bool force, + UnitFileChange **changes, + unsigned *n_changes); +int unit_file_preset_all( + UnitFileScope scope, + bool runtime, + const char *root_dir, + UnitFilePresetMode mode, + bool force, + UnitFileChange **changes, + unsigned *n_changes); +int unit_file_mask( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + bool force, + UnitFileChange **changes, + unsigned *n_changes); +int unit_file_unmask( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + UnitFileChange **changes, + unsigned *n_changes); +int unit_file_link( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + bool force, + UnitFileChange **changes, + unsigned *n_changes); +int unit_file_revert( + UnitFileScope scope, + const char *root_dir, + char **files, + UnitFileChange **changes, + unsigned *n_changes); +int unit_file_set_default( + UnitFileScope scope, + const char *root_dir, + const char *file, + bool force, + UnitFileChange **changes, + unsigned *n_changes); +int unit_file_get_default( + UnitFileScope scope, + const char *root_dir, + char **name); +int unit_file_add_dependency( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + const char *target, + UnitDependency dep, + bool force, + UnitFileChange **changes, + unsigned *n_changes); + +int unit_file_get_state(UnitFileScope scope, const char *root_dir, const char *filename, UnitFileState *ret); +int unit_file_exists(UnitFileScope scope, const LookupPaths *paths, const char *name); + +int unit_file_get_list(UnitFileScope scope, const char *root_dir, Hashmap *h, char **states, char **patterns); +Hashmap* unit_file_list_free(Hashmap *h); + +int unit_file_changes_add(UnitFileChange **changes, unsigned *n_changes, UnitFileChangeType type, const char *path, const char *source); +void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes); +void unit_file_dump_changes(int r, const char *verb, const UnitFileChange *changes, unsigned n_changes, bool quiet); + +int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char *name); + +const char *unit_file_state_to_string(UnitFileState s) _const_; +UnitFileState unit_file_state_from_string(const char *s) _pure_; +/* from_string conversion is unreliable because of the overlap between -EPERM and -1 for error. */ + +const char *unit_file_change_type_to_string(UnitFileChangeType s) _const_; +UnitFileChangeType unit_file_change_type_from_string(const char *s) _pure_; + +const char *unit_file_preset_mode_to_string(UnitFilePresetMode m) _const_; +UnitFilePresetMode unit_file_preset_mode_from_string(const char *s) _pure_; diff --git a/src/libshared/local-addresses.c b/src/libshared/local-addresses.c new file mode 100644 index 0000000000..1abce75b01 --- /dev/null +++ b/src/libshared/local-addresses.c @@ -0,0 +1,275 @@ +/*** + This file is part of systemd. + + Copyright 2008-2011 Lennart Poettering + Copyright 2014 Tom Gundersen + + 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 "alloc-util.h" +#include "local-addresses.h" +#include "macro.h" +#include "netlink-util.h" + +static int address_compare(const void *_a, const void *_b) { + const struct local_address *a = _a, *b = _b; + + /* Order lowest scope first, IPv4 before IPv6, lowest interface index first */ + + if (a->family == AF_INET && b->family == AF_INET6) + return -1; + if (a->family == AF_INET6 && b->family == AF_INET) + return 1; + + if (a->scope < b->scope) + return -1; + if (a->scope > b->scope) + return 1; + + if (a->metric < b->metric) + return -1; + if (a->metric > b->metric) + return 1; + + if (a->ifindex < b->ifindex) + return -1; + if (a->ifindex > b->ifindex) + return 1; + + return memcmp(&a->address, &b->address, FAMILY_ADDRESS_SIZE(a->family)); +} + +int local_addresses(sd_netlink *context, int ifindex, int af, struct local_address **ret) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_free_ struct local_address *list = NULL; + size_t n_list = 0, n_allocated = 0; + sd_netlink_message *m; + int r; + + assert(ret); + + if (context) + rtnl = sd_netlink_ref(context); + else { + r = sd_netlink_open(&rtnl); + if (r < 0) + return r; + } + + r = sd_rtnl_message_new_addr(rtnl, &req, RTM_GETADDR, 0, af); + if (r < 0) + return r; + + r = sd_netlink_call(rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (m = reply; m; m = sd_netlink_message_next(m)) { + struct local_address *a; + unsigned char flags; + uint16_t type; + int ifi, family; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + return r; + + r = sd_netlink_message_get_type(m, &type); + if (r < 0) + return r; + if (type != RTM_NEWADDR) + continue; + + r = sd_rtnl_message_addr_get_ifindex(m, &ifi); + if (r < 0) + return r; + if (ifindex > 0 && ifi != ifindex) + continue; + + r = sd_rtnl_message_addr_get_family(m, &family); + if (r < 0) + return r; + if (af != AF_UNSPEC && af != family) + continue; + + r = sd_rtnl_message_addr_get_flags(m, &flags); + if (r < 0) + return r; + if (flags & IFA_F_DEPRECATED) + continue; + + if (!GREEDY_REALLOC0(list, n_allocated, n_list+1)) + return -ENOMEM; + + a = list + n_list; + + r = sd_rtnl_message_addr_get_scope(m, &a->scope); + if (r < 0) + return r; + + if (ifindex == 0 && (a->scope == RT_SCOPE_HOST || a->scope == RT_SCOPE_NOWHERE)) + continue; + + switch (family) { + + case AF_INET: + r = sd_netlink_message_read_in_addr(m, IFA_LOCAL, &a->address.in); + if (r < 0) { + r = sd_netlink_message_read_in_addr(m, IFA_ADDRESS, &a->address.in); + if (r < 0) + continue; + } + break; + + case AF_INET6: + r = sd_netlink_message_read_in6_addr(m, IFA_LOCAL, &a->address.in6); + if (r < 0) { + r = sd_netlink_message_read_in6_addr(m, IFA_ADDRESS, &a->address.in6); + if (r < 0) + continue; + } + break; + + default: + continue; + } + + a->ifindex = ifi; + a->family = family; + + n_list++; + }; + + qsort_safe(list, n_list, sizeof(struct local_address), address_compare); + + *ret = list; + list = NULL; + + return (int) n_list; +} + +int local_gateways(sd_netlink *context, int ifindex, int af, struct local_address **ret) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_free_ struct local_address *list = NULL; + sd_netlink_message *m = NULL; + size_t n_list = 0, n_allocated = 0; + int r; + + assert(ret); + + if (context) + rtnl = sd_netlink_ref(context); + else { + r = sd_netlink_open(&rtnl); + if (r < 0) + return r; + } + + r = sd_rtnl_message_new_route(rtnl, &req, RTM_GETROUTE, af, RTPROT_UNSPEC); + if (r < 0) + return r; + + r = sd_netlink_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_netlink_call(rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (m = reply; m; m = sd_netlink_message_next(m)) { + struct local_address *a; + uint16_t type; + unsigned char dst_len, src_len; + uint32_t ifi; + int family; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + return r; + + r = sd_netlink_message_get_type(m, &type); + if (r < 0) + return r; + if (type != RTM_NEWROUTE) + continue; + + /* We only care for default routes */ + r = sd_rtnl_message_route_get_dst_prefixlen(m, &dst_len); + if (r < 0) + return r; + if (dst_len != 0) + continue; + + r = sd_rtnl_message_route_get_src_prefixlen(m, &src_len); + if (r < 0) + return r; + if (src_len != 0) + continue; + + r = sd_netlink_message_read_u32(m, RTA_OIF, &ifi); + if (r < 0) + return r; + if (ifindex > 0 && (int) ifi != ifindex) + continue; + + r = sd_rtnl_message_route_get_family(m, &family); + if (r < 0) + return r; + if (af != AF_UNSPEC && af != family) + continue; + + if (!GREEDY_REALLOC0(list, n_allocated, n_list + 1)) + return -ENOMEM; + + a = list + n_list; + + switch (family) { + case AF_INET: + r = sd_netlink_message_read_in_addr(m, RTA_GATEWAY, &a->address.in); + if (r < 0) + continue; + + break; + case AF_INET6: + r = sd_netlink_message_read_in6_addr(m, RTA_GATEWAY, &a->address.in6); + if (r < 0) + continue; + + break; + default: + continue; + } + + sd_netlink_message_read_u32(m, RTA_PRIORITY, &a->metric); + + a->ifindex = ifi; + a->family = family; + + n_list++; + } + + if (n_list > 0) + qsort(list, n_list, sizeof(struct local_address), address_compare); + + *ret = list; + list = NULL; + + return (int) n_list; +} diff --git a/src/libshared/local-addresses.h b/src/libshared/local-addresses.h new file mode 100644 index 0000000000..1ddc50ace5 --- /dev/null +++ b/src/libshared/local-addresses.h @@ -0,0 +1,36 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2008-2011 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 "in-addr-util.h" + +struct local_address { + int family, ifindex; + unsigned char scope; + uint32_t metric; + union in_addr_union address; +}; + +int local_addresses(sd_netlink *rtnl, int ifindex, int af, struct local_address **ret); + +int local_gateways(sd_netlink *rtnl, int ifindex, int af, struct local_address **ret); diff --git a/src/libshared/logs-show.c b/src/libshared/logs-show.c new file mode 100644 index 0000000000..294fa3bede --- /dev/null +++ b/src/libshared/logs-show.c @@ -0,0 +1,1310 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "hashmap.h" +#include "hostname-util.h" +#include "io-util.h" +#include "journal-internal.h" +#include "log.h" +#include "logs-show.h" +#include "macro.h" +#include "output-mode.h" +#include "parse-util.h" +#include "process-util.h" +#include "sparse-endian.h" +#include "string-table.h" +#include "string-util.h" +#include "terminal-util.h" +#include "time-util.h" +#include "utf8.h" +#include "util.h" + +/* up to three lines (each up to 100 characters) or 300 characters, whichever is less */ +#define PRINT_LINE_THRESHOLD 3 +#define PRINT_CHAR_THRESHOLD 300 + +#define JSON_THRESHOLD 4096 + +static int print_catalog(FILE *f, sd_journal *j) { + int r; + _cleanup_free_ char *t = NULL, *z = NULL; + + + r = sd_journal_get_catalog(j, &t); + if (r < 0) + return r; + + z = strreplace(strstrip(t), "\n", "\n-- "); + if (!z) + return log_oom(); + + fputs("-- ", f); + fputs(z, f); + fputc('\n', f); + + return 0; +} + +static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) { + size_t fl, nl; + char *buf; + + assert(data); + assert(field); + assert(target); + + fl = strlen(field); + if (length < fl) + return 0; + + if (memcmp(data, field, fl)) + return 0; + + nl = length - fl; + buf = new(char, nl+1); + if (!buf) + return log_oom(); + + memcpy(buf, (const char*) data + fl, nl); + buf[nl] = 0; + + free(*target); + *target = buf; + + if (target_size) + *target_size = nl; + + return 1; +} + +static bool shall_print(const char *p, size_t l, OutputFlags flags) { + assert(p); + + if (flags & OUTPUT_SHOW_ALL) + return true; + + if (l >= PRINT_CHAR_THRESHOLD) + return false; + + if (!utf8_is_printable(p, l)) + return false; + + return true; +} + +static bool print_multiline(FILE *f, unsigned prefix, unsigned n_columns, OutputFlags flags, int priority, const char* message, size_t message_len) { + const char *color_on = "", *color_off = ""; + const char *pos, *end; + bool ellipsized = false; + int line = 0; + + if (flags & OUTPUT_COLOR) { + if (priority <= LOG_ERR) { + color_on = ANSI_HIGHLIGHT_RED; + color_off = ANSI_NORMAL; + } else if (priority <= LOG_NOTICE) { + color_on = ANSI_HIGHLIGHT; + color_off = ANSI_NORMAL; + } + } + + /* A special case: make sure that we print a newline when + the message is empty. */ + if (message_len == 0) + fputs("\n", f); + + for (pos = message; + pos < message + message_len; + pos = end + 1, line++) { + bool continuation = line > 0; + bool tail_line; + int len; + for (end = pos; end < message + message_len && *end != '\n'; end++) + ; + len = end - pos; + assert(len >= 0); + + /* We need to figure out when we are showing not-last line, *and* + * will skip subsequent lines. In that case, we will put the dots + * at the end of the line, instead of putting dots in the middle + * or not at all. + */ + tail_line = + line + 1 == PRINT_LINE_THRESHOLD || + end + 1 >= message + PRINT_CHAR_THRESHOLD; + + if (flags & (OUTPUT_FULL_WIDTH | OUTPUT_SHOW_ALL) || + (prefix + len + 1 < n_columns && !tail_line)) { + fprintf(f, "%*s%s%.*s%s\n", + continuation * prefix, "", + color_on, len, pos, color_off); + continue; + } + + /* Beyond this point, ellipsization will happen. */ + ellipsized = true; + + if (prefix < n_columns && n_columns - prefix >= 3) { + if (n_columns - prefix > (unsigned) len + 3) + fprintf(f, "%*s%s%.*s...%s\n", + continuation * prefix, "", + color_on, len, pos, color_off); + else { + _cleanup_free_ char *e; + + e = ellipsize_mem(pos, len, n_columns - prefix, + tail_line ? 100 : 90); + if (!e) + fprintf(f, "%*s%s%.*s%s\n", + continuation * prefix, "", + color_on, len, pos, color_off); + else + fprintf(f, "%*s%s%s%s\n", + continuation * prefix, "", + color_on, e, color_off); + } + } else + fputs("...\n", f); + + if (tail_line) + break; + } + + return ellipsized; +} + +static int output_short( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags) { + + int r; + const void *data; + size_t length; + size_t n = 0; + _cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, *message = NULL, *realtime = NULL, *monotonic = NULL, *priority = NULL; + size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0, realtime_len = 0, monotonic_len = 0, priority_len = 0; + int p = LOG_INFO; + bool ellipsized = false; + + assert(f); + assert(j); + + /* Set the threshold to one bigger than the actual print + * threshold, so that if the line is actually longer than what + * we're willing to print, ellipsization will occur. This way + * we won't output a misleading line without any indication of + * truncation. + */ + sd_journal_set_data_threshold(j, flags & (OUTPUT_SHOW_ALL|OUTPUT_FULL_WIDTH) ? 0 : PRINT_CHAR_THRESHOLD + 1); + + JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { + + r = parse_field(data, length, "PRIORITY=", &priority, &priority_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "_HOSTNAME=", &hostname, &hostname_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "SYSLOG_IDENTIFIER=", &identifier, &identifier_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "_COMM=", &comm, &comm_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "_PID=", &pid, &pid_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "SYSLOG_PID=", &fake_pid, &fake_pid_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "MESSAGE=", &message, &message_len); + if (r < 0) + return r; + } + if (r == -EBADMSG) { + log_debug_errno(r, "Skipping message we can't read: %m"); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to get journal fields: %m"); + + if (!message) { + log_debug("Skipping message without MESSAGE= field."); + return 0; + } + + if (!(flags & OUTPUT_SHOW_ALL)) + strip_tab_ansi(&message, &message_len); + + if (priority_len == 1 && *priority >= '0' && *priority <= '7') + p = *priority - '0'; + + if (mode == OUTPUT_SHORT_MONOTONIC) { + uint64_t t; + sd_id128_t boot_id; + + r = -ENOENT; + + if (monotonic) + r = safe_atou64(monotonic, &t); + + if (r < 0) + r = sd_journal_get_monotonic_usec(j, &t, &boot_id); + + if (r < 0) + return log_error_errno(r, "Failed to get monotonic timestamp: %m"); + + fprintf(f, "[%5llu.%06llu]", + (unsigned long long) (t / USEC_PER_SEC), + (unsigned long long) (t % USEC_PER_SEC)); + + n += 1 + 5 + 1 + 6 + 1; + + } else { + char buf[64]; + uint64_t x; + time_t t; + struct tm tm; + struct tm *(*gettime_r)(const time_t *, struct tm *); + + r = -ENOENT; + gettime_r = (flags & OUTPUT_UTC) ? gmtime_r : localtime_r; + + if (realtime) + r = safe_atou64(realtime, &x); + + if (r < 0) + r = sd_journal_get_realtime_usec(j, &x); + + if (r < 0) + return log_error_errno(r, "Failed to get realtime timestamp: %m"); + + t = (time_t) (x / USEC_PER_SEC); + + switch (mode) { + + case OUTPUT_SHORT_UNIX: + r = snprintf(buf, sizeof(buf), "%10llu.%06llu", (unsigned long long) t, (unsigned long long) (x % USEC_PER_SEC)); + break; + + case OUTPUT_SHORT_ISO: + r = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", gettime_r(&t, &tm)); + break; + + case OUTPUT_SHORT_PRECISE: + r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm)); + if (r > 0) + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ".%06llu", (unsigned long long) (x % USEC_PER_SEC)); + break; + + default: + r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm)); + } + + if (r <= 0) { + log_error("Failed to format time."); + return -EINVAL; + } + + fputs(buf, f); + n += strlen(buf); + } + + if (hostname && (flags & OUTPUT_NO_HOSTNAME)) { + /* Suppress display of the hostname if this is requested. */ + hostname = NULL; + hostname_len = 0; + } + + if (hostname && shall_print(hostname, hostname_len, flags)) { + fprintf(f, " %.*s", (int) hostname_len, hostname); + n += hostname_len + 1; + } + + if (identifier && shall_print(identifier, identifier_len, flags)) { + fprintf(f, " %.*s", (int) identifier_len, identifier); + n += identifier_len + 1; + } else if (comm && shall_print(comm, comm_len, flags)) { + fprintf(f, " %.*s", (int) comm_len, comm); + n += comm_len + 1; + } else + fputs(" unknown", f); + + if (pid && shall_print(pid, pid_len, flags)) { + fprintf(f, "[%.*s]", (int) pid_len, pid); + n += pid_len + 2; + } else if (fake_pid && shall_print(fake_pid, fake_pid_len, flags)) { + fprintf(f, "[%.*s]", (int) fake_pid_len, fake_pid); + n += fake_pid_len + 2; + } + + if (!(flags & OUTPUT_SHOW_ALL) && !utf8_is_printable(message, message_len)) { + char bytes[FORMAT_BYTES_MAX]; + fprintf(f, ": [%s blob data]\n", format_bytes(bytes, sizeof(bytes), message_len)); + } else { + fputs(": ", f); + ellipsized |= + print_multiline(f, n + 2, n_columns, flags, p, message, message_len); + } + + if (flags & OUTPUT_CATALOG) + print_catalog(f, j); + + return ellipsized; +} + +static int output_verbose( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags) { + + const void *data; + size_t length; + _cleanup_free_ char *cursor = NULL; + uint64_t realtime = 0; + char ts[FORMAT_TIMESTAMP_MAX + 7]; + int r; + + assert(f); + assert(j); + + sd_journal_set_data_threshold(j, 0); + + r = sd_journal_get_data(j, "_SOURCE_REALTIME_TIMESTAMP", &data, &length); + if (r == -ENOENT) + log_debug("Source realtime timestamp not found"); + else if (r < 0) + return log_full_errno(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_ERR, r, "Failed to get source realtime timestamp: %m"); + else { + _cleanup_free_ char *value = NULL; + + r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &value, NULL); + if (r < 0) + return r; + assert(r > 0); + + r = safe_atou64(value, &realtime); + if (r < 0) + log_debug_errno(r, "Failed to parse realtime timestamp: %m"); + } + + if (r < 0) { + r = sd_journal_get_realtime_usec(j, &realtime); + if (r < 0) + return log_full_errno(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_ERR, r, "Failed to get realtime timestamp: %m"); + } + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0) + return log_error_errno(r, "Failed to get cursor: %m"); + + fprintf(f, "%s [%s]\n", + flags & OUTPUT_UTC ? + format_timestamp_us_utc(ts, sizeof(ts), realtime) : + format_timestamp_us(ts, sizeof(ts), realtime), + cursor); + + JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { + const char *c; + int fieldlen; + const char *on = "", *off = ""; + + c = memchr(data, '=', length); + if (!c) { + log_error("Invalid field."); + return -EINVAL; + } + fieldlen = c - (const char*) data; + + if (flags & OUTPUT_COLOR && startswith(data, "MESSAGE=")) { + on = ANSI_HIGHLIGHT; + off = ANSI_NORMAL; + } + + if (flags & OUTPUT_SHOW_ALL || + (((length < PRINT_CHAR_THRESHOLD) || flags & OUTPUT_FULL_WIDTH) + && utf8_is_printable(data, length))) { + fprintf(f, " %s%.*s=", on, fieldlen, (const char*)data); + print_multiline(f, 4 + fieldlen + 1, 0, OUTPUT_FULL_WIDTH, 0, c + 1, length - fieldlen - 1); + fputs(off, f); + } else { + char bytes[FORMAT_BYTES_MAX]; + + fprintf(f, " %s%.*s=[%s blob data]%s\n", + on, + (int) (c - (const char*) data), + (const char*) data, + format_bytes(bytes, sizeof(bytes), length - (c - (const char *) data) - 1), + off); + } + } + + if (r < 0) + return r; + + if (flags & OUTPUT_CATALOG) + print_catalog(f, j); + + return 0; +} + +static int output_export( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags) { + + sd_id128_t boot_id; + char sid[33]; + int r; + usec_t realtime, monotonic; + _cleanup_free_ char *cursor = NULL; + const void *data; + size_t length; + + assert(j); + + sd_journal_set_data_threshold(j, 0); + + r = sd_journal_get_realtime_usec(j, &realtime); + if (r < 0) + return log_error_errno(r, "Failed to get realtime timestamp: %m"); + + r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get monotonic timestamp: %m"); + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0) + return log_error_errno(r, "Failed to get cursor: %m"); + + fprintf(f, + "__CURSOR=%s\n" + "__REALTIME_TIMESTAMP="USEC_FMT"\n" + "__MONOTONIC_TIMESTAMP="USEC_FMT"\n" + "_BOOT_ID=%s\n", + cursor, + realtime, + monotonic, + sd_id128_to_string(boot_id, sid)); + + JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { + + /* We already printed the boot id, from the data in + * the header, hence let's suppress it here */ + if (length >= 9 && + startswith(data, "_BOOT_ID=")) + continue; + + if (utf8_is_printable_newline(data, length, false)) + fwrite(data, length, 1, f); + else { + const char *c; + uint64_t le64; + + c = memchr(data, '=', length); + if (!c) { + log_error("Invalid field."); + return -EINVAL; + } + + fwrite(data, c - (const char*) data, 1, f); + fputc('\n', f); + le64 = htole64(length - (c - (const char*) data) - 1); + fwrite(&le64, sizeof(le64), 1, f); + fwrite(c + 1, length - (c - (const char*) data) - 1, 1, f); + } + + fputc('\n', f); + } + + if (r < 0) + return r; + + fputc('\n', f); + + return 0; +} + +void json_escape( + FILE *f, + const char* p, + size_t l, + OutputFlags flags) { + + assert(f); + assert(p); + + if (!(flags & OUTPUT_SHOW_ALL) && l >= JSON_THRESHOLD) + fputs("null", f); + + else if (!utf8_is_printable(p, l)) { + bool not_first = false; + + fputs("[ ", f); + + while (l > 0) { + if (not_first) + fprintf(f, ", %u", (uint8_t) *p); + else { + not_first = true; + fprintf(f, "%u", (uint8_t) *p); + } + + p++; + l--; + } + + fputs(" ]", f); + } else { + fputc('\"', f); + + while (l > 0) { + if (*p == '"' || *p == '\\') { + fputc('\\', f); + fputc(*p, f); + } else if (*p == '\n') + fputs("\\n", f); + else if ((uint8_t) *p < ' ') + fprintf(f, "\\u%04x", (uint8_t) *p); + else + fputc(*p, f); + + p++; + l--; + } + + fputc('\"', f); + } +} + +static int output_json( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags) { + + uint64_t realtime, monotonic; + _cleanup_free_ char *cursor = NULL; + const void *data; + size_t length; + sd_id128_t boot_id; + char sid[33], *k; + int r; + Hashmap *h = NULL; + bool done, separator; + + assert(j); + + sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD); + + r = sd_journal_get_realtime_usec(j, &realtime); + if (r < 0) + return log_error_errno(r, "Failed to get realtime timestamp: %m"); + + r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get monotonic timestamp: %m"); + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0) + return log_error_errno(r, "Failed to get cursor: %m"); + + if (mode == OUTPUT_JSON_PRETTY) + fprintf(f, + "{\n" + "\t\"__CURSOR\" : \"%s\",\n" + "\t\"__REALTIME_TIMESTAMP\" : \""USEC_FMT"\",\n" + "\t\"__MONOTONIC_TIMESTAMP\" : \""USEC_FMT"\",\n" + "\t\"_BOOT_ID\" : \"%s\"", + cursor, + realtime, + monotonic, + sd_id128_to_string(boot_id, sid)); + else { + if (mode == OUTPUT_JSON_SSE) + fputs("data: ", f); + + fprintf(f, + "{ \"__CURSOR\" : \"%s\", " + "\"__REALTIME_TIMESTAMP\" : \""USEC_FMT"\", " + "\"__MONOTONIC_TIMESTAMP\" : \""USEC_FMT"\", " + "\"_BOOT_ID\" : \"%s\"", + cursor, + realtime, + monotonic, + sd_id128_to_string(boot_id, sid)); + } + + h = hashmap_new(&string_hash_ops); + if (!h) + return log_oom(); + + /* First round, iterate through the entry and count how often each field appears */ + JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { + const char *eq; + char *n; + unsigned u; + + if (length >= 9 && + memcmp(data, "_BOOT_ID=", 9) == 0) + continue; + + eq = memchr(data, '=', length); + if (!eq) + continue; + + n = strndup(data, eq - (const char*) data); + if (!n) { + r = log_oom(); + goto finish; + } + + u = PTR_TO_UINT(hashmap_get(h, n)); + if (u == 0) { + r = hashmap_put(h, n, UINT_TO_PTR(1)); + if (r < 0) { + free(n); + log_oom(); + goto finish; + } + } else { + r = hashmap_update(h, n, UINT_TO_PTR(u + 1)); + free(n); + if (r < 0) { + log_oom(); + goto finish; + } + } + } + + if (r < 0) + return r; + + separator = true; + do { + done = true; + + SD_JOURNAL_FOREACH_DATA(j, data, length) { + const char *eq; + char *kk, *n; + size_t m; + unsigned u; + + /* We already printed the boot id, from the data in + * the header, hence let's suppress it here */ + if (length >= 9 && + memcmp(data, "_BOOT_ID=", 9) == 0) + continue; + + eq = memchr(data, '=', length); + if (!eq) + continue; + + if (separator) { + if (mode == OUTPUT_JSON_PRETTY) + fputs(",\n\t", f); + else + fputs(", ", f); + } + + m = eq - (const char*) data; + + n = strndup(data, m); + if (!n) { + r = log_oom(); + goto finish; + } + + u = PTR_TO_UINT(hashmap_get2(h, n, (void**) &kk)); + if (u == 0) { + /* We already printed this, let's jump to the next */ + free(n); + separator = false; + + continue; + } else if (u == 1) { + /* Field only appears once, output it directly */ + + json_escape(f, data, m, flags); + fputs(" : ", f); + + json_escape(f, eq + 1, length - m - 1, flags); + + hashmap_remove(h, n); + free(kk); + free(n); + + separator = true; + + continue; + + } else { + /* Field appears multiple times, output it as array */ + json_escape(f, data, m, flags); + fputs(" : [ ", f); + json_escape(f, eq + 1, length - m - 1, flags); + + /* Iterate through the end of the list */ + + while (sd_journal_enumerate_data(j, &data, &length) > 0) { + if (length < m + 1) + continue; + + if (memcmp(data, n, m) != 0) + continue; + + if (((const char*) data)[m] != '=') + continue; + + fputs(", ", f); + json_escape(f, (const char*) data + m + 1, length - m - 1, flags); + } + + fputs(" ]", f); + + hashmap_remove(h, n); + free(kk); + free(n); + + /* Iterate data fields form the beginning */ + done = false; + separator = true; + + break; + } + } + + } while (!done); + + if (mode == OUTPUT_JSON_PRETTY) + fputs("\n}\n", f); + else if (mode == OUTPUT_JSON_SSE) + fputs("}\n\n", f); + else + fputs(" }\n", f); + + r = 0; + +finish: + while ((k = hashmap_steal_first_key(h))) + free(k); + + hashmap_free(h); + + return r; +} + +static int output_cat( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags) { + + const void *data; + size_t l; + int r; + + assert(j); + assert(f); + + sd_journal_set_data_threshold(j, 0); + + r = sd_journal_get_data(j, "MESSAGE", &data, &l); + if (r < 0) { + /* An entry without MESSAGE=? */ + if (r == -ENOENT) + return 0; + + return log_error_errno(r, "Failed to get data: %m"); + } + + assert(l >= 8); + + fwrite((const char*) data + 8, 1, l - 8, f); + fputc('\n', f); + + return 0; +} + +static int (*output_funcs[_OUTPUT_MODE_MAX])( + FILE *f, + sd_journal*j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags) = { + + [OUTPUT_SHORT] = output_short, + [OUTPUT_SHORT_ISO] = output_short, + [OUTPUT_SHORT_PRECISE] = output_short, + [OUTPUT_SHORT_MONOTONIC] = output_short, + [OUTPUT_SHORT_UNIX] = output_short, + [OUTPUT_VERBOSE] = output_verbose, + [OUTPUT_EXPORT] = output_export, + [OUTPUT_JSON] = output_json, + [OUTPUT_JSON_PRETTY] = output_json, + [OUTPUT_JSON_SSE] = output_json, + [OUTPUT_CAT] = output_cat +}; + +int output_journal( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags, + bool *ellipsized) { + + int ret; + assert(mode >= 0); + assert(mode < _OUTPUT_MODE_MAX); + + if (n_columns <= 0) + n_columns = columns(); + + ret = output_funcs[mode](f, j, mode, n_columns, flags); + fflush(stdout); + + if (ellipsized && ret > 0) + *ellipsized = true; + + return ret; +} + +static int maybe_print_begin_newline(FILE *f, OutputFlags *flags) { + assert(f); + assert(flags); + + if (!(*flags & OUTPUT_BEGIN_NEWLINE)) + return 0; + + /* Print a beginning new line if that's request, but only once + * on the first line we print. */ + + fputc('\n', f); + *flags &= ~OUTPUT_BEGIN_NEWLINE; + return 0; +} + +static int show_journal(FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + usec_t not_before, + unsigned how_many, + OutputFlags flags, + bool *ellipsized) { + + int r; + unsigned line = 0; + bool need_seek = false; + int warn_cutoff = flags & OUTPUT_WARN_CUTOFF; + + assert(j); + assert(mode >= 0); + assert(mode < _OUTPUT_MODE_MAX); + + /* Seek to end */ + r = sd_journal_seek_tail(j); + if (r < 0) + return log_error_errno(r, "Failed to seek to tail: %m"); + + r = sd_journal_previous_skip(j, how_many); + if (r < 0) + return log_error_errno(r, "Failed to skip previous: %m"); + + for (;;) { + for (;;) { + usec_t usec; + + if (need_seek) { + r = sd_journal_next(j); + if (r < 0) + return log_error_errno(r, "Failed to iterate through journal: %m"); + } + + if (r == 0) + break; + + need_seek = true; + + if (not_before > 0) { + r = sd_journal_get_monotonic_usec(j, &usec, NULL); + + /* -ESTALE is returned if the + timestamp is not from this boot */ + if (r == -ESTALE) + continue; + else if (r < 0) + return log_error_errno(r, "Failed to get journal time: %m"); + + if (usec < not_before) + continue; + } + + line++; + maybe_print_begin_newline(f, &flags); + + r = output_journal(f, j, mode, n_columns, flags, ellipsized); + if (r < 0) + return r; + } + + if (warn_cutoff && line < how_many && not_before > 0) { + sd_id128_t boot_id; + usec_t cutoff = 0; + + /* Check whether the cutoff line is too early */ + + r = sd_id128_get_boot(&boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get boot id: %m"); + + r = sd_journal_get_cutoff_monotonic_usec(j, boot_id, &cutoff, NULL); + if (r < 0) + return log_error_errno(r, "Failed to get journal cutoff time: %m"); + + if (r > 0 && not_before < cutoff) { + maybe_print_begin_newline(f, &flags); + fprintf(f, "Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable.\n"); + } + + warn_cutoff = false; + } + + if (!(flags & OUTPUT_FOLLOW)) + break; + + r = sd_journal_wait(j, USEC_INFINITY); + if (r < 0) + return log_error_errno(r, "Failed to wait for journal: %m"); + + } + + return 0; +} + +int add_matches_for_unit(sd_journal *j, const char *unit) { + const char *m1, *m2, *m3, *m4; + int r; + + assert(j); + assert(unit); + + m1 = strjoina("_SYSTEMD_UNIT=", unit); + m2 = strjoina("COREDUMP_UNIT=", unit); + m3 = strjoina("UNIT=", unit); + m4 = strjoina("OBJECT_SYSTEMD_UNIT=", unit); + + (void)( + /* Look for messages from the service itself */ + (r = sd_journal_add_match(j, m1, 0)) || + + /* Look for coredumps of the service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", 0)) || + (r = sd_journal_add_match(j, "_UID=0", 0)) || + (r = sd_journal_add_match(j, m2, 0)) || + + /* Look for messages from PID 1 about this service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, "_PID=1", 0)) || + (r = sd_journal_add_match(j, m3, 0)) || + + /* Look for messages from authorized daemons about this service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, "_UID=0", 0)) || + (r = sd_journal_add_match(j, m4, 0)) + ); + + if (r == 0 && endswith(unit, ".slice")) { + const char *m5; + + m5 = strjoina("_SYSTEMD_SLICE=", unit); + + /* Show all messages belonging to a slice */ + (void)( + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m5, 0)) + ); + } + + return r; +} + +int add_matches_for_user_unit(sd_journal *j, const char *unit, uid_t uid) { + int r; + char *m1, *m2, *m3, *m4; + char muid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)]; + + assert(j); + assert(unit); + + m1 = strjoina("_SYSTEMD_USER_UNIT=", unit); + m2 = strjoina("USER_UNIT=", unit); + m3 = strjoina("COREDUMP_USER_UNIT=", unit); + m4 = strjoina("OBJECT_SYSTEMD_USER_UNIT=", unit); + sprintf(muid, "_UID="UID_FMT, uid); + + (void) ( + /* Look for messages from the user service itself */ + (r = sd_journal_add_match(j, m1, 0)) || + (r = sd_journal_add_match(j, muid, 0)) || + + /* Look for messages from systemd about this service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m2, 0)) || + (r = sd_journal_add_match(j, muid, 0)) || + + /* Look for coredumps of the service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m3, 0)) || + (r = sd_journal_add_match(j, muid, 0)) || + (r = sd_journal_add_match(j, "_UID=0", 0)) || + + /* Look for messages from authorized daemons about this service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m4, 0)) || + (r = sd_journal_add_match(j, muid, 0)) || + (r = sd_journal_add_match(j, "_UID=0", 0)) + ); + + if (r == 0 && endswith(unit, ".slice")) { + const char *m5; + + m5 = strjoina("_SYSTEMD_SLICE=", unit); + + /* Show all messages belonging to a slice */ + (void)( + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m5, 0)) || + (r = sd_journal_add_match(j, muid, 0)) + ); + } + + return r; +} + +static int get_boot_id_for_machine(const char *machine, sd_id128_t *boot_id) { + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, rootfd = -1; + pid_t pid, child; + siginfo_t si; + char buf[37]; + ssize_t k; + int r; + + assert(machine); + assert(boot_id); + + if (!machine_name_is_valid(machine)) + return -EINVAL; + + r = container_get_leader(machine, &pid); + if (r < 0) + return r; + + r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, NULL, &rootfd); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) + return -errno; + + child = fork(); + if (child < 0) + return -errno; + + if (child == 0) { + int fd; + + pair[0] = safe_close(pair[0]); + + r = namespace_enter(pidnsfd, mntnsfd, -1, -1, rootfd); + if (r < 0) + _exit(EXIT_FAILURE); + + fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + _exit(EXIT_FAILURE); + + r = loop_read_exact(fd, buf, 36, false); + safe_close(fd); + if (r < 0) + _exit(EXIT_FAILURE); + + k = send(pair[1], buf, 36, MSG_NOSIGNAL); + if (k != 36) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + + r = wait_for_terminate(child, &si); + if (r < 0 || si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) + return r < 0 ? r : -EIO; + + k = recv(pair[0], buf, 36, 0); + if (k != 36) + return -EIO; + + buf[36] = 0; + r = sd_id128_from_string(buf, boot_id); + if (r < 0) + return r; + + return 0; +} + +int add_match_this_boot(sd_journal *j, const char *machine) { + char match[9+32+1] = "_BOOT_ID="; + sd_id128_t boot_id; + int r; + + assert(j); + + if (machine) { + r = get_boot_id_for_machine(machine, &boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get boot id of container %s: %m", machine); + } else { + r = sd_id128_get_boot(&boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get boot id: %m"); + } + + sd_id128_to_string(boot_id, match + 9); + r = sd_journal_add_match(j, match, strlen(match)); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + + r = sd_journal_add_conjunction(j); + if (r < 0) + return log_error_errno(r, "Failed to add conjunction: %m"); + + return 0; +} + +int show_journal_by_unit( + FILE *f, + const char *unit, + OutputMode mode, + unsigned n_columns, + usec_t not_before, + unsigned how_many, + uid_t uid, + OutputFlags flags, + int journal_open_flags, + bool system_unit, + bool *ellipsized) { + + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + int r; + + assert(mode >= 0); + assert(mode < _OUTPUT_MODE_MAX); + assert(unit); + + if (how_many <= 0) + return 0; + + r = sd_journal_open(&j, journal_open_flags); + if (r < 0) + return log_error_errno(r, "Failed to open journal: %m"); + + r = add_match_this_boot(j, NULL); + if (r < 0) + return r; + + if (system_unit) + r = add_matches_for_unit(j, unit); + else + r = add_matches_for_user_unit(j, unit, uid); + if (r < 0) + return log_error_errno(r, "Failed to add unit matches: %m"); + + if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) { + _cleanup_free_ char *filter; + + filter = journal_make_match_string(j); + if (!filter) + return log_oom(); + + log_debug("Journal filter: %s", filter); + } + + return show_journal(f, j, mode, n_columns, not_before, how_many, flags, ellipsized); +} diff --git a/src/libshared/logs-show.h b/src/libshared/logs-show.h new file mode 100644 index 0000000000..15fe5b6e5c --- /dev/null +++ b/src/libshared/logs-show.h @@ -0,0 +1,70 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 +#include +#include + +#include + +#include "macro.h" +#include "output-mode.h" +#include "time-util.h" +#include "util.h" + +int output_journal( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags, + bool *ellipsized); + +int add_match_this_boot(sd_journal *j, const char *machine); + +int add_matches_for_unit( + sd_journal *j, + const char *unit); + +int add_matches_for_user_unit( + sd_journal *j, + const char *unit, + uid_t uid); + +int show_journal_by_unit( + FILE *f, + const char *unit, + OutputMode mode, + unsigned n_columns, + usec_t not_before, + unsigned how_many, + uid_t uid, + OutputFlags flags, + int journal_open_flags, + bool system_unit, + bool *ellipsized); + +void json_escape( + FILE *f, + const char* p, + size_t l, + OutputFlags flags); diff --git a/src/libshared/machine-image.c b/src/libshared/machine-image.c new file mode 100644 index 0000000000..529d89ee2a --- /dev/null +++ b/src/libshared/machine-image.c @@ -0,0 +1,818 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include "alloc-util.h" +#include "btrfs-util.h" +#include "chattr-util.h" +#include "copy.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "hashmap.h" +#include "lockfile-util.h" +#include "log.h" +#include "macro.h" +#include "machine-image.h" +#include "mkdir.h" +#include "path-util.h" +#include "rm-rf.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "utf8.h" +#include "util.h" +#include "xattr-util.h" + +static const char image_search_path[] = + "/var/lib/machines\0" + "/var/lib/container\0" /* legacy */ + "/usr/local/lib/machines\0" + "/usr/lib/machines\0"; + +Image *image_unref(Image *i) { + if (!i) + return NULL; + + free(i->name); + free(i->path); + free(i); + return NULL; +} + +static char **image_settings_path(Image *image) { + _cleanup_strv_free_ char **l = NULL; + char **ret; + const char *fn, *s; + unsigned i = 0; + + assert(image); + + l = new0(char*, 4); + if (!l) + return NULL; + + fn = strjoina(image->name, ".nspawn"); + + FOREACH_STRING(s, "/etc/systemd/nspawn/", "/run/systemd/nspawn/") { + l[i] = strappend(s, fn); + if (!l[i]) + return NULL; + + i++; + } + + l[i] = file_in_same_dir(image->path, fn); + if (!l[i]) + return NULL; + + ret = l; + l = NULL; + + return ret; +} + +static int image_new( + ImageType t, + const char *pretty, + const char *path, + const char *filename, + bool read_only, + usec_t crtime, + usec_t mtime, + Image **ret) { + + _cleanup_(image_unrefp) Image *i = NULL; + + assert(t >= 0); + assert(t < _IMAGE_TYPE_MAX); + assert(pretty); + assert(filename); + assert(ret); + + i = new0(Image, 1); + if (!i) + return -ENOMEM; + + i->type = t; + i->read_only = read_only; + i->crtime = crtime; + i->mtime = mtime; + i->usage = i->usage_exclusive = (uint64_t) -1; + i->limit = i->limit_exclusive = (uint64_t) -1; + + i->name = strdup(pretty); + if (!i->name) + return -ENOMEM; + + if (path) + i->path = strjoin(path, "/", filename, NULL); + else + i->path = strdup(filename); + + if (!i->path) + return -ENOMEM; + + path_kill_slashes(i->path); + + *ret = i; + i = NULL; + + return 0; +} + +static int image_make( + const char *pretty, + int dfd, + const char *path, + const char *filename, + Image **ret) { + + struct stat st; + bool read_only; + int r; + + assert(filename); + + /* We explicitly *do* follow symlinks here, since we want to + * allow symlinking trees into /var/lib/machines/, and treat + * them normally. */ + + if (fstatat(dfd, filename, &st, 0) < 0) + return -errno; + + read_only = + (path && path_startswith(path, "/usr")) || + (faccessat(dfd, filename, W_OK, AT_EACCESS) < 0 && errno == EROFS); + + if (S_ISDIR(st.st_mode)) { + _cleanup_close_ int fd = -1; + unsigned file_attr = 0; + + if (!ret) + return 1; + + if (!pretty) + pretty = filename; + + fd = openat(dfd, filename, O_CLOEXEC|O_NOCTTY|O_DIRECTORY); + if (fd < 0) + return -errno; + + /* btrfs subvolumes have inode 256 */ + if (st.st_ino == 256) { + + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (r) { + BtrfsSubvolInfo info; + + /* It's a btrfs subvolume */ + + r = btrfs_subvol_get_info_fd(fd, 0, &info); + if (r < 0) + return r; + + r = image_new(IMAGE_SUBVOLUME, + pretty, + path, + filename, + info.read_only || read_only, + info.otime, + 0, + ret); + if (r < 0) + return r; + + if (btrfs_quota_scan_ongoing(fd) == 0) { + BtrfsQuotaInfo quota; + + r = btrfs_subvol_get_subtree_quota_fd(fd, 0, "a); + if (r >= 0) { + (*ret)->usage = quota.referenced; + (*ret)->usage_exclusive = quota.exclusive; + + (*ret)->limit = quota.referenced_max; + (*ret)->limit_exclusive = quota.exclusive_max; + } + } + + return 1; + } + } + + /* If the IMMUTABLE bit is set, we consider the + * directory read-only. Since the ioctl is not + * supported everywhere we ignore failures. */ + (void) read_attr_fd(fd, &file_attr); + + /* It's just a normal directory. */ + r = image_new(IMAGE_DIRECTORY, + pretty, + path, + filename, + read_only || (file_attr & FS_IMMUTABLE_FL), + 0, + 0, + ret); + if (r < 0) + return r; + + return 1; + + } else if (S_ISREG(st.st_mode) && endswith(filename, ".raw")) { + usec_t crtime = 0; + + /* It's a RAW disk image */ + + if (!ret) + return 1; + + fd_getcrtime_at(dfd, filename, &crtime, 0); + + if (!pretty) + pretty = strndupa(filename, strlen(filename) - 4); + + r = image_new(IMAGE_RAW, + pretty, + path, + filename, + !(st.st_mode & 0222) || read_only, + crtime, + timespec_load(&st.st_mtim), + ret); + if (r < 0) + return r; + + (*ret)->usage = (*ret)->usage_exclusive = st.st_blocks * 512; + (*ret)->limit = (*ret)->limit_exclusive = st.st_size; + + return 1; + } + + return 0; +} + +int image_find(const char *name, Image **ret) { + const char *path; + int r; + + assert(name); + + /* There are no images with invalid names */ + if (!image_name_is_valid(name)) + return 0; + + NULSTR_FOREACH(path, image_search_path) { + _cleanup_closedir_ DIR *d = NULL; + + d = opendir(path); + if (!d) { + if (errno == ENOENT) + continue; + + return -errno; + } + + r = image_make(NULL, dirfd(d), path, name, ret); + if (r == 0 || r == -ENOENT) { + _cleanup_free_ char *raw = NULL; + + raw = strappend(name, ".raw"); + if (!raw) + return -ENOMEM; + + r = image_make(NULL, dirfd(d), path, raw, ret); + if (r == 0 || r == -ENOENT) + continue; + } + if (r < 0) + return r; + + return 1; + } + + if (streq(name, ".host")) + return image_make(".host", AT_FDCWD, NULL, "/", ret); + + return 0; +}; + +int image_discover(Hashmap *h) { + const char *path; + int r; + + assert(h); + + NULSTR_FOREACH(path, image_search_path) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + + d = opendir(path); + if (!d) { + if (errno == ENOENT) + continue; + + return -errno; + } + + FOREACH_DIRENT_ALL(de, d, return -errno) { + _cleanup_(image_unrefp) Image *image = NULL; + + if (!image_name_is_valid(de->d_name)) + continue; + + if (hashmap_contains(h, de->d_name)) + continue; + + r = image_make(NULL, dirfd(d), path, de->d_name, &image); + if (r == 0 || r == -ENOENT) + continue; + if (r < 0) + return r; + + r = hashmap_put(h, image->name, image); + if (r < 0) + return r; + + image = NULL; + } + } + + if (!hashmap_contains(h, ".host")) { + _cleanup_(image_unrefp) Image *image = NULL; + + r = image_make(".host", AT_FDCWD, NULL, "/", &image); + if (r < 0) + return r; + + r = hashmap_put(h, image->name, image); + if (r < 0) + return r; + + image = NULL; + + } + + return 0; +} + +void image_hashmap_free(Hashmap *map) { + Image *i; + + while ((i = hashmap_steal_first(map))) + image_unref(i); + + hashmap_free(map); +} + +int image_remove(Image *i) { + _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT; + _cleanup_strv_free_ char **settings = NULL; + char **j; + int r; + + assert(i); + + if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i)) + return -EROFS; + + settings = image_settings_path(i); + if (!settings) + return -ENOMEM; + + /* Make sure we don't interfere with a running nspawn */ + r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock); + if (r < 0) + return r; + + switch (i->type) { + + case IMAGE_SUBVOLUME: + r = btrfs_subvol_remove(i->path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); + if (r < 0) + return r; + break; + + case IMAGE_DIRECTORY: + /* Allow deletion of read-only directories */ + (void) chattr_path(i->path, 0, FS_IMMUTABLE_FL); + r = rm_rf(i->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); + if (r < 0) + return r; + + break; + + case IMAGE_RAW: + if (unlink(i->path) < 0) + return -errno; + break; + + default: + return -EOPNOTSUPP; + } + + STRV_FOREACH(j, settings) { + if (unlink(*j) < 0 && errno != ENOENT) + log_debug_errno(errno, "Failed to unlink %s, ignoring: %m", *j); + } + + return 0; +} + +static int rename_settings_file(const char *path, const char *new_name) { + _cleanup_free_ char *rs = NULL; + const char *fn; + + fn = strjoina(new_name, ".nspawn"); + + rs = file_in_same_dir(path, fn); + if (!rs) + return -ENOMEM; + + return rename_noreplace(AT_FDCWD, path, AT_FDCWD, rs); +} + +int image_rename(Image *i, const char *new_name) { + _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT, name_lock = LOCK_FILE_INIT; + _cleanup_free_ char *new_path = NULL, *nn = NULL; + _cleanup_strv_free_ char **settings = NULL; + unsigned file_attr = 0; + char **j; + int r; + + assert(i); + + if (!image_name_is_valid(new_name)) + return -EINVAL; + + if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i)) + return -EROFS; + + settings = image_settings_path(i); + if (!settings) + return -ENOMEM; + + /* Make sure we don't interfere with a running nspawn */ + r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock); + if (r < 0) + return r; + + /* Make sure nobody takes the new name, between the time we + * checked it is currently unused in all search paths, and the + * time we take possession of it */ + r = image_name_lock(new_name, LOCK_EX|LOCK_NB, &name_lock); + if (r < 0) + return r; + + r = image_find(new_name, NULL); + if (r < 0) + return r; + if (r > 0) + return -EEXIST; + + switch (i->type) { + + case IMAGE_DIRECTORY: + /* Turn of the immutable bit while we rename the image, so that we can rename it */ + (void) read_attr_path(i->path, &file_attr); + + if (file_attr & FS_IMMUTABLE_FL) + (void) chattr_path(i->path, 0, FS_IMMUTABLE_FL); + + /* fall through */ + + case IMAGE_SUBVOLUME: + new_path = file_in_same_dir(i->path, new_name); + break; + + case IMAGE_RAW: { + const char *fn; + + fn = strjoina(new_name, ".raw"); + new_path = file_in_same_dir(i->path, fn); + break; + } + + default: + return -EOPNOTSUPP; + } + + if (!new_path) + return -ENOMEM; + + nn = strdup(new_name); + if (!nn) + return -ENOMEM; + + r = rename_noreplace(AT_FDCWD, i->path, AT_FDCWD, new_path); + if (r < 0) + return r; + + /* Restore the immutable bit, if it was set before */ + if (file_attr & FS_IMMUTABLE_FL) + (void) chattr_path(new_path, FS_IMMUTABLE_FL, FS_IMMUTABLE_FL); + + free(i->path); + i->path = new_path; + new_path = NULL; + + free(i->name); + i->name = nn; + nn = NULL; + + STRV_FOREACH(j, settings) { + r = rename_settings_file(*j, new_name); + if (r < 0 && r != -ENOENT) + log_debug_errno(r, "Failed to rename settings file %s, ignoring: %m", *j); + } + + return 0; +} + +static int clone_settings_file(const char *path, const char *new_name) { + _cleanup_free_ char *rs = NULL; + const char *fn; + + fn = strjoina(new_name, ".nspawn"); + + rs = file_in_same_dir(path, fn); + if (!rs) + return -ENOMEM; + + return copy_file_atomic(path, rs, 0664, false, 0); +} + +int image_clone(Image *i, const char *new_name, bool read_only) { + _cleanup_release_lock_file_ LockFile name_lock = LOCK_FILE_INIT; + _cleanup_strv_free_ char **settings = NULL; + const char *new_path; + char **j; + int r; + + assert(i); + + if (!image_name_is_valid(new_name)) + return -EINVAL; + + settings = image_settings_path(i); + if (!settings) + return -ENOMEM; + + /* Make sure nobody takes the new name, between the time we + * checked it is currently unused in all search paths, and the + * time we take possession of it */ + r = image_name_lock(new_name, LOCK_EX|LOCK_NB, &name_lock); + if (r < 0) + return r; + + r = image_find(new_name, NULL); + if (r < 0) + return r; + if (r > 0) + return -EEXIST; + + switch (i->type) { + + case IMAGE_SUBVOLUME: + case IMAGE_DIRECTORY: + /* If we can we'll always try to create a new btrfs subvolume here, even if the source is a plain + * directory.*/ + + new_path = strjoina("/var/lib/machines/", new_name); + + r = btrfs_subvol_snapshot(i->path, new_path, (read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA); + if (r == -EOPNOTSUPP) { + /* No btrfs snapshots supported, create a normal directory then. */ + + r = copy_directory(i->path, new_path, false); + if (r >= 0) + (void) chattr_path(new_path, read_only ? FS_IMMUTABLE_FL : 0, FS_IMMUTABLE_FL); + } else if (r >= 0) + /* Enable "subtree" quotas for the copy, if we didn't copy any quota from the source. */ + (void) btrfs_subvol_auto_qgroup(new_path, 0, true); + + break; + + case IMAGE_RAW: + new_path = strjoina("/var/lib/machines/", new_name, ".raw"); + + r = copy_file_atomic(i->path, new_path, read_only ? 0444 : 0644, false, FS_NOCOW_FL); + break; + + default: + return -EOPNOTSUPP; + } + + if (r < 0) + return r; + + STRV_FOREACH(j, settings) { + r = clone_settings_file(*j, new_name); + if (r < 0 && r != -ENOENT) + log_debug_errno(r, "Failed to clone settings %s, ignoring: %m", *j); + } + + return 0; +} + +int image_read_only(Image *i, bool b) { + _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT; + int r; + assert(i); + + if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i)) + return -EROFS; + + /* Make sure we don't interfere with a running nspawn */ + r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock); + if (r < 0) + return r; + + switch (i->type) { + + case IMAGE_SUBVOLUME: + + /* Note that we set the flag only on the top-level + * subvolume of the image. */ + + r = btrfs_subvol_set_read_only(i->path, b); + if (r < 0) + return r; + + break; + + case IMAGE_DIRECTORY: + /* For simple directory trees we cannot use the access + mode of the top-level directory, since it has an + effect on the container itself. However, we can + use the "immutable" flag, to at least make the + top-level directory read-only. It's not as good as + a read-only subvolume, but at least something, and + we can read the value back.*/ + + r = chattr_path(i->path, b ? FS_IMMUTABLE_FL : 0, FS_IMMUTABLE_FL); + if (r < 0) + return r; + + break; + + case IMAGE_RAW: { + struct stat st; + + if (stat(i->path, &st) < 0) + return -errno; + + if (chmod(i->path, (st.st_mode & 0444) | (b ? 0000 : 0200)) < 0) + return -errno; + + /* If the images is now read-only, it's a good time to + * defrag it, given that no write patterns will + * fragment it again. */ + if (b) + (void) btrfs_defrag(i->path); + break; + } + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +int image_path_lock(const char *path, int operation, LockFile *global, LockFile *local) { + _cleanup_free_ char *p = NULL; + LockFile t = LOCK_FILE_INIT; + struct stat st; + int r; + + assert(path); + assert(global); + assert(local); + + /* Locks an image path. This actually creates two locks: one + * "local" one, next to the image path itself, which might be + * shared via NFS. And another "global" one, in /run, that + * uses the device/inode number. This has the benefit that we + * can even lock a tree that is a mount point, correctly. */ + + if (path_equal(path, "/")) + return -EBUSY; + + if (!path_is_absolute(path)) + return -EINVAL; + + if (stat(path, &st) >= 0) { + if (asprintf(&p, "/run/systemd/nspawn/locks/inode-%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino) < 0) + return -ENOMEM; + } + + r = make_lock_file_for(path, operation, &t); + if (r < 0) + return r; + + if (p) { + mkdir_p("/run/systemd/nspawn/locks", 0700); + + r = make_lock_file(p, operation, global); + if (r < 0) { + release_lock_file(&t); + return r; + } + } + + *local = t; + return 0; +} + +int image_set_limit(Image *i, uint64_t referenced_max) { + assert(i); + + if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i)) + return -EROFS; + + if (i->type != IMAGE_SUBVOLUME) + return -EOPNOTSUPP; + + /* We set the quota both for the subvolume as well as for the + * subtree. The latter is mostly for historical reasons, since + * we didn't use to have a concept of subtree quota, and hence + * only modified the subvolume quota. */ + + (void) btrfs_qgroup_set_limit(i->path, 0, referenced_max); + (void) btrfs_subvol_auto_qgroup(i->path, 0, true); + return btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max); +} + +int image_name_lock(const char *name, int operation, LockFile *ret) { + const char *p; + + assert(name); + assert(ret); + + /* Locks an image name, regardless of the precise path used. */ + + if (!image_name_is_valid(name)) + return -EINVAL; + + if (streq(name, ".host")) + return -EBUSY; + + mkdir_p("/run/systemd/nspawn/locks", 0700); + p = strjoina("/run/systemd/nspawn/locks/name-", name); + + return make_lock_file(p, operation, ret); +} + +bool image_name_is_valid(const char *s) { + if (!filename_is_valid(s)) + return false; + + if (string_has_cc(s, NULL)) + return false; + + if (!utf8_is_valid(s)) + return false; + + /* Temporary files for atomically creating new files */ + if (startswith(s, ".#")) + return false; + + return true; +} + +static const char* const image_type_table[_IMAGE_TYPE_MAX] = { + [IMAGE_DIRECTORY] = "directory", + [IMAGE_SUBVOLUME] = "subvolume", + [IMAGE_RAW] = "raw", +}; + +DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType); diff --git a/src/libshared/machine-image.h b/src/libshared/machine-image.h new file mode 100644 index 0000000000..7410168c4f --- /dev/null +++ b/src/libshared/machine-image.h @@ -0,0 +1,103 @@ +#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 +#include + +#include "hashmap.h" +#include "lockfile-util.h" +#include "macro.h" +#include "path-util.h" +#include "string-util.h" +#include "time-util.h" + +typedef enum ImageType { + IMAGE_DIRECTORY, + IMAGE_SUBVOLUME, + IMAGE_RAW, + _IMAGE_TYPE_MAX, + _IMAGE_TYPE_INVALID = -1 +} ImageType; + +typedef struct Image { + ImageType type; + char *name; + char *path; + bool read_only; + + usec_t crtime; + usec_t mtime; + + uint64_t usage; + uint64_t usage_exclusive; + uint64_t limit; + uint64_t limit_exclusive; + + void *userdata; +} Image; + +Image *image_unref(Image *i); +void image_hashmap_free(Hashmap *map); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Image*, image_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, image_hashmap_free); + +int image_find(const char *name, Image **ret); +int image_discover(Hashmap *map); + +int image_remove(Image *i); +int image_rename(Image *i, const char *new_name); +int image_clone(Image *i, const char *new_name, bool read_only); +int image_read_only(Image *i, bool b); + +const char* image_type_to_string(ImageType t) _const_; +ImageType image_type_from_string(const char *s) _pure_; + +bool image_name_is_valid(const char *s) _pure_; + +int image_path_lock(const char *path, int operation, LockFile *global, LockFile *local); +int image_name_lock(const char *name, int operation, LockFile *ret); + +int image_set_limit(Image *i, uint64_t referenced_max); + +static inline bool IMAGE_IS_HIDDEN(const struct Image *i) { + assert(i); + + return i->name && i->name[0] == '.'; +} + +static inline bool IMAGE_IS_VENDOR(const struct Image *i) { + assert(i); + + return i->path && path_startswith(i->path, "/usr"); +} + +static inline bool IMAGE_IS_HOST(const struct Image *i) { + assert(i); + + if (i->name && streq(i->name, ".host")) + return true; + + if (i->path && path_equal(i->path, "/")) + return true; + + return false; +} diff --git a/src/libshared/machine-pool.c b/src/libshared/machine-pool.c new file mode 100644 index 0000000000..c36efa0102 --- /dev/null +++ b/src/libshared/machine-pool.c @@ -0,0 +1,426 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "alloc-util.h" +#include "btrfs-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "lockfile-util.h" +#include "log.h" +#include "machine-pool.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "mount-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "signal-util.h" +#include "stat-util.h" +#include "string-util.h" + +#define VAR_LIB_MACHINES_SIZE_START (1024UL*1024UL*500UL) +#define VAR_LIB_MACHINES_FREE_MIN (1024UL*1024UL*750UL) + +static int check_btrfs(void) { + struct statfs sfs; + + if (statfs("/var/lib/machines", &sfs) < 0) { + if (errno != ENOENT) + return -errno; + + if (statfs("/var/lib", &sfs) < 0) + return -errno; + } + + return F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC); +} + +static int setup_machine_raw(uint64_t size, sd_bus_error *error) { + _cleanup_free_ char *tmp = NULL; + _cleanup_close_ int fd = -1; + struct statvfs ss; + pid_t pid = 0; + siginfo_t si; + int r; + + /* We want to be able to make use of btrfs-specific file + * system features, in particular subvolumes, reflinks and + * quota. Hence, if we detect that /var/lib/machines.raw is + * not located on btrfs, let's create a loopback file, place a + * btrfs file system into it, and mount it to + * /var/lib/machines. */ + + fd = open("/var/lib/machines.raw", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (fd >= 0) { + r = fd; + fd = -1; + return r; + } + + if (errno != ENOENT) + return sd_bus_error_set_errnof(error, errno, "Failed to open /var/lib/machines.raw: %m"); + + r = tempfn_xxxxxx("/var/lib/machines.raw", NULL, &tmp); + if (r < 0) + return r; + + (void) mkdir_p_label("/var/lib", 0755); + fd = open(tmp, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0600); + if (fd < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to create /var/lib/machines.raw: %m"); + + if (fstatvfs(fd, &ss) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to determine free space on /var/lib/machines.raw: %m"); + goto fail; + } + + if (ss.f_bsize * ss.f_bavail < VAR_LIB_MACHINES_FREE_MIN) { + r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Not enough free disk space to set up /var/lib/machines."); + goto fail; + } + + if (ftruncate(fd, size) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to enlarge /var/lib/machines.raw: %m"); + goto fail; + } + + pid = fork(); + if (pid < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to fork mkfs.btrfs: %m"); + goto fail; + } + + if (pid == 0) { + + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + fd = safe_close(fd); + + execlp("mkfs.btrfs", "-Lvar-lib-machines", tmp, NULL); + if (errno == ENOENT) + _exit(99); + + _exit(EXIT_FAILURE); + } + + r = wait_for_terminate(pid, &si); + if (r < 0) { + sd_bus_error_set_errnof(error, r, "Failed to wait for mkfs.btrfs: %m"); + goto fail; + } + + pid = 0; + + if (si.si_code != CLD_EXITED) { + r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "mkfs.btrfs died abnormally."); + goto fail; + } + if (si.si_status == 99) { + r = sd_bus_error_set_errnof(error, ENOENT, "Cannot set up /var/lib/machines, mkfs.btrfs is missing"); + goto fail; + } + if (si.si_status != 0) { + r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "mkfs.btrfs failed with error code %i", si.si_status); + goto fail; + } + + r = rename_noreplace(AT_FDCWD, tmp, AT_FDCWD, "/var/lib/machines.raw"); + if (r < 0) { + sd_bus_error_set_errnof(error, r, "Failed to move /var/lib/machines.raw into place: %m"); + goto fail; + } + + r = fd; + fd = -1; + + return r; + +fail: + unlink_noerrno(tmp); + + if (pid > 1) + kill_and_sigcont(pid, SIGKILL); + + return r; +} + +int setup_machine_directory(uint64_t size, sd_bus_error *error) { + _cleanup_release_lock_file_ LockFile lock_file = LOCK_FILE_INIT; + struct loop_info64 info = { + .lo_flags = LO_FLAGS_AUTOCLEAR, + }; + _cleanup_close_ int fd = -1, control = -1, loop = -1; + _cleanup_free_ char* loopdev = NULL; + char tmpdir[] = "/tmp/machine-pool.XXXXXX", *mntdir = NULL; + bool tmpdir_made = false, mntdir_made = false, mntdir_mounted = false; + char buf[FORMAT_BYTES_MAX]; + int r, nr = -1; + + /* btrfs cannot handle file systems < 16M, hence use this as minimum */ + if (size == (uint64_t) -1) + size = VAR_LIB_MACHINES_SIZE_START; + else if (size < 16*1024*1024) + size = 16*1024*1024; + + /* Make sure we only set the directory up once at a time */ + r = make_lock_file("/run/systemd/machines.lock", LOCK_EX, &lock_file); + if (r < 0) + return r; + + r = check_btrfs(); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to determine whether /var/lib/machines is located on btrfs: %m"); + if (r > 0) { + (void) btrfs_subvol_make_label("/var/lib/machines"); + + r = btrfs_quota_enable("/var/lib/machines", true); + if (r < 0) + log_warning_errno(r, "Failed to enable quota for /var/lib/machines, ignoring: %m"); + + r = btrfs_subvol_auto_qgroup("/var/lib/machines", 0, true); + if (r < 0) + log_warning_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines, ignoring: %m"); + + return 1; + } + + if (path_is_mount_point("/var/lib/machines", AT_SYMLINK_FOLLOW) > 0) { + log_debug("/var/lib/machines is already a mount point, not creating loopback file for it."); + return 0; + } + + r = dir_is_populated("/var/lib/machines"); + if (r < 0 && r != -ENOENT) + return r; + if (r > 0) { + log_debug("/var/log/machines is already populated, not creating loopback file for it."); + return 0; + } + + r = mkfs_exists("btrfs"); + if (r == 0) + return sd_bus_error_set_errnof(error, ENOENT, "Cannot set up /var/lib/machines, mkfs.btrfs is missing"); + if (r < 0) + return r; + + fd = setup_machine_raw(size, error); + if (fd < 0) + return fd; + + control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + if (control < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to open /dev/loop-control: %m"); + + nr = ioctl(control, LOOP_CTL_GET_FREE); + if (nr < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to allocate loop device: %m"); + + if (asprintf(&loopdev, "/dev/loop%i", nr) < 0) { + r = -ENOMEM; + goto fail; + } + + loop = open(loopdev, O_CLOEXEC|O_RDWR|O_NOCTTY|O_NONBLOCK); + if (loop < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to open loopback device: %m"); + goto fail; + } + + if (ioctl(loop, LOOP_SET_FD, fd) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to bind loopback device: %m"); + goto fail; + } + + if (ioctl(loop, LOOP_SET_STATUS64, &info) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to enable auto-clear for loopback device: %m"); + goto fail; + } + + /* We need to make sure the new /var/lib/machines directory + * has an access mode of 0700 at the time it is first made + * available. mkfs will create it with 0755 however. Hence, + * let's mount the directory into an inaccessible directory + * below /tmp first, fix the access mode, and move it to the + * public place then. */ + + if (!mkdtemp(tmpdir)) { + r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount parent directory: %m"); + goto fail; + } + tmpdir_made = true; + + mntdir = strjoina(tmpdir, "/mnt"); + if (mkdir(mntdir, 0700) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount directory: %m"); + goto fail; + } + mntdir_made = true; + + if (mount(loopdev, mntdir, "btrfs", 0, NULL) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to mount loopback device: %m"); + goto fail; + } + mntdir_mounted = true; + + r = btrfs_quota_enable(mntdir, true); + if (r < 0) + log_warning_errno(r, "Failed to enable quota, ignoring: %m"); + + r = btrfs_subvol_auto_qgroup(mntdir, 0, true); + if (r < 0) + log_warning_errno(r, "Failed to set up default quota hierarchy, ignoring: %m"); + + if (chmod(mntdir, 0700) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to fix owner: %m"); + goto fail; + } + + (void) mkdir_p_label("/var/lib/machines", 0700); + + if (mount(mntdir, "/var/lib/machines", NULL, MS_BIND, NULL) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to mount directory into right place: %m"); + goto fail; + } + + (void) syncfs(fd); + + log_info("Set up /var/lib/machines as btrfs loopback file system of size %s mounted on /var/lib/machines.raw.", format_bytes(buf, sizeof(buf), size)); + + (void) umount2(mntdir, MNT_DETACH); + (void) rmdir(mntdir); + (void) rmdir(tmpdir); + + return 1; + +fail: + if (mntdir_mounted) + (void) umount2(mntdir, MNT_DETACH); + + if (mntdir_made) + (void) rmdir(mntdir); + if (tmpdir_made) + (void) rmdir(tmpdir); + + if (loop >= 0) { + (void) ioctl(loop, LOOP_CLR_FD); + loop = safe_close(loop); + } + + if (control >= 0 && nr >= 0) + (void) ioctl(control, LOOP_CTL_REMOVE, nr); + + return r; +} + +static int sync_path(const char *p) { + _cleanup_close_ int fd = -1; + + fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return -errno; + + if (syncfs(fd) < 0) + return -errno; + + return 0; +} + +int grow_machine_directory(void) { + char buf[FORMAT_BYTES_MAX]; + struct statvfs a, b; + uint64_t old_size, new_size, max_add; + int r; + + /* Ensure the disk space data is accurate */ + sync_path("/var/lib/machines"); + sync_path("/var/lib/machines.raw"); + + if (statvfs("/var/lib/machines.raw", &a) < 0) + return -errno; + + if (statvfs("/var/lib/machines", &b) < 0) + return -errno; + + /* Don't grow if not enough disk space is available on the host */ + if (((uint64_t) a.f_bavail * (uint64_t) a.f_bsize) <= VAR_LIB_MACHINES_FREE_MIN) + return 0; + + /* Don't grow if at least 1/3th of the fs is still free */ + if (b.f_bavail > b.f_blocks / 3) + return 0; + + /* Calculate how much we are willing to add at most */ + max_add = ((uint64_t) a.f_bavail * (uint64_t) a.f_bsize) - VAR_LIB_MACHINES_FREE_MIN; + + /* Calculate the old size */ + old_size = (uint64_t) b.f_blocks * (uint64_t) b.f_bsize; + + /* Calculate the new size as three times the size of what is used right now */ + new_size = ((uint64_t) b.f_blocks - (uint64_t) b.f_bavail) * (uint64_t) b.f_bsize * 3; + + /* Always, grow at least to the start size */ + if (new_size < VAR_LIB_MACHINES_SIZE_START) + new_size = VAR_LIB_MACHINES_SIZE_START; + + /* If the new size is smaller than the old size, don't grow */ + if (new_size < old_size) + return 0; + + /* Ensure we never add more than the maximum */ + if (new_size > old_size + max_add) + new_size = old_size + max_add; + + r = btrfs_resize_loopback("/var/lib/machines", new_size, true); + if (r <= 0) + return r; + + /* Also bump the quota, of both the subvolume leaf qgroup, as + * well as of any subtree quota group by the same id but a + * higher level, if it exists. */ + (void) btrfs_qgroup_set_limit("/var/lib/machines", 0, new_size); + (void) btrfs_subvol_set_subtree_quota_limit("/var/lib/machines", 0, new_size); + + log_info("Grew /var/lib/machines btrfs loopback file system to %s.", format_bytes(buf, sizeof(buf), new_size)); + return 1; +} diff --git a/src/libshared/machine-pool.h b/src/libshared/machine-pool.h new file mode 100644 index 0000000000..fe99b7e0ae --- /dev/null +++ b/src/libshared/machine-pool.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 + +/* Grow the /var/lib/machines directory after each 10MiB written */ +#define GROW_INTERVAL_BYTES (UINT64_C(10) * UINT64_C(1024) * UINT64_C(1024)) + +int setup_machine_directory(uint64_t size, sd_bus_error *error); +int grow_machine_directory(void); diff --git a/src/libshared/output-mode.c b/src/libshared/output-mode.c new file mode 100644 index 0000000000..bec53ee0ae --- /dev/null +++ b/src/libshared/output-mode.c @@ -0,0 +1,37 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 "output-mode.h" +#include "string-table.h" + +static const char *const output_mode_table[_OUTPUT_MODE_MAX] = { + [OUTPUT_SHORT] = "short", + [OUTPUT_SHORT_ISO] = "short-iso", + [OUTPUT_SHORT_PRECISE] = "short-precise", + [OUTPUT_SHORT_MONOTONIC] = "short-monotonic", + [OUTPUT_SHORT_UNIX] = "short-unix", + [OUTPUT_VERBOSE] = "verbose", + [OUTPUT_EXPORT] = "export", + [OUTPUT_JSON] = "json", + [OUTPUT_JSON_PRETTY] = "json-pretty", + [OUTPUT_JSON_SSE] = "json-sse", + [OUTPUT_CAT] = "cat" +}; + +DEFINE_STRING_TABLE_LOOKUP(output_mode, OutputMode); diff --git a/src/libshared/output-mode.h b/src/libshared/output-mode.h new file mode 100644 index 0000000000..f37189e57f --- /dev/null +++ b/src/libshared/output-mode.h @@ -0,0 +1,57 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 "macro.h" + +typedef enum OutputMode { + OUTPUT_SHORT, + OUTPUT_SHORT_ISO, + OUTPUT_SHORT_PRECISE, + OUTPUT_SHORT_MONOTONIC, + OUTPUT_SHORT_UNIX, + OUTPUT_VERBOSE, + OUTPUT_EXPORT, + OUTPUT_JSON, + OUTPUT_JSON_PRETTY, + OUTPUT_JSON_SSE, + OUTPUT_CAT, + _OUTPUT_MODE_MAX, + _OUTPUT_MODE_INVALID = -1 +} OutputMode; + +/* The output flags definitions are shared by the logs and process tree output. Some apply to both, some only to the + * logs output, others only to the process tree output. */ + +typedef enum OutputFlags { + OUTPUT_SHOW_ALL = 1 << 0, + OUTPUT_FOLLOW = 1 << 1, + OUTPUT_WARN_CUTOFF = 1 << 2, + OUTPUT_FULL_WIDTH = 1 << 3, + OUTPUT_COLOR = 1 << 4, + OUTPUT_CATALOG = 1 << 5, + OUTPUT_BEGIN_NEWLINE = 1 << 6, + OUTPUT_UTC = 1 << 7, + OUTPUT_KERNEL_THREADS = 1 << 8, + OUTPUT_NO_HOSTNAME = 1 << 9, +} OutputFlags; + +const char* output_mode_to_string(OutputMode m) _const_; +OutputMode output_mode_from_string(const char *s) _pure_; diff --git a/src/libshared/pager.c b/src/libshared/pager.c new file mode 100644 index 0000000000..c16bc027be --- /dev/null +++ b/src/libshared/pager.c @@ -0,0 +1,226 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include + +#include "copy.h" +#include "fd-util.h" +#include "locale-util.h" +#include "log.h" +#include "macro.h" +#include "pager.h" +#include "process-util.h" +#include "signal-util.h" +#include "string-util.h" +#include "terminal-util.h" + +static pid_t pager_pid = 0; + +noreturn static void pager_fallback(void) { + int r; + + r = copy_bytes(STDIN_FILENO, STDOUT_FILENO, (uint64_t) -1, false); + if (r < 0) { + log_error_errno(r, "Internal pager failed: %m"); + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); +} + +int pager_open(bool no_pager, bool jump_to_end) { + _cleanup_close_pair_ int fd[2] = { -1, -1 }; + const char *pager; + pid_t parent_pid; + + if (no_pager) + return 0; + + if (pager_pid > 0) + return 1; + + if (!on_tty()) + return 0; + + pager = getenv("SYSTEMD_PAGER"); + if (!pager) + pager = getenv("PAGER"); + + /* If the pager is explicitly turned off, honour it */ + if (pager && (pager[0] == 0 || streq(pager, "cat"))) + return 0; + + /* Determine and cache number of columns before we spawn the + * pager so that we get the value from the actual tty */ + (void) columns(); + + if (pipe(fd) < 0) + return log_error_errno(errno, "Failed to create pager pipe: %m"); + + parent_pid = getpid(); + + pager_pid = fork(); + if (pager_pid < 0) + return log_error_errno(errno, "Failed to fork pager: %m"); + + /* In the child start the pager */ + if (pager_pid == 0) { + const char* less_opts, *less_charset; + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + (void) dup2(fd[0], STDIN_FILENO); + safe_close_pair(fd); + + /* Initialize a good set of less options */ + less_opts = getenv("SYSTEMD_LESS"); + if (!less_opts) + less_opts = "FRSXMK"; + if (jump_to_end) + less_opts = strjoina(less_opts, " +G"); + setenv("LESS", less_opts, 1); + + /* Initialize a good charset for less. This is + * particularly important if we output UTF-8 + * characters. */ + less_charset = getenv("SYSTEMD_LESSCHARSET"); + if (!less_charset && is_locale_utf8()) + less_charset = "utf-8"; + if (less_charset) + setenv("LESSCHARSET", less_charset, 1); + + /* Make sure the pager goes away when the parent dies */ + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + _exit(EXIT_FAILURE); + + /* Check whether our parent died before we were able + * to set the death signal */ + if (getppid() != parent_pid) + _exit(EXIT_SUCCESS); + + if (pager) { + execlp(pager, pager, NULL); + execl("/bin/sh", "sh", "-c", pager, NULL); + } + + /* Debian's alternatives command for pagers is + * called 'pager'. Note that we do not call + * sensible-pagers here, since that is just a + * shell script that implements a logic that + * is similar to this one anyway, but is + * Debian-specific. */ + execlp("pager", "pager", NULL); + + execlp("less", "less", NULL); + execlp("more", "more", NULL); + + pager_fallback(); + /* not reached */ + } + + /* Return in the parent */ + if (dup2(fd[1], STDOUT_FILENO) < 0) + return log_error_errno(errno, "Failed to duplicate pager pipe: %m"); + if (dup2(fd[1], STDERR_FILENO) < 0) + return log_error_errno(errno, "Failed to duplicate pager pipe: %m"); + + return 1; +} + +void pager_close(void) { + + if (pager_pid <= 0) + return; + + /* Inform pager that we are done */ + stdout = safe_fclose(stdout); + stderr = safe_fclose(stderr); + + (void) kill(pager_pid, SIGCONT); + (void) wait_for_terminate(pager_pid, NULL); + pager_pid = 0; +} + +bool pager_have(void) { + return pager_pid > 0; +} + +int show_man_page(const char *desc, bool null_stdio) { + const char *args[4] = { "man", NULL, NULL, NULL }; + char *e = NULL; + pid_t pid; + size_t k; + int r; + siginfo_t status; + + k = strlen(desc); + + if (desc[k-1] == ')') + e = strrchr(desc, '('); + + if (e) { + char *page = NULL, *section = NULL; + + page = strndupa(desc, e - desc); + section = strndupa(e + 1, desc + k - e - 2); + + args[1] = section; + args[2] = page; + } else + args[1] = desc; + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + + if (pid == 0) { + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + if (null_stdio) { + r = make_null_stdio(); + if (r < 0) { + log_error_errno(r, "Failed to kill stdio: %m"); + _exit(EXIT_FAILURE); + } + } + + execvp(args[0], (char**) args); + log_error_errno(errno, "Failed to execute man: %m"); + _exit(EXIT_FAILURE); + } + + r = wait_for_terminate(pid, &status); + if (r < 0) + return r; + + log_debug("Exit code %i status %i", status.si_code, status.si_status); + return status.si_status; +} diff --git a/src/libshared/pager.h b/src/libshared/pager.h new file mode 100644 index 0000000000..893e1d2bb6 --- /dev/null +++ b/src/libshared/pager.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 "macro.h" + +int pager_open(bool no_pager, bool jump_to_end); +void pager_close(void); +bool pager_have(void) _pure_; + +int show_man_page(const char *page, bool null_stdio); diff --git a/src/libshared/path-lookup.c b/src/libshared/path-lookup.c new file mode 100644 index 0000000000..ca593b6963 --- /dev/null +++ b/src/libshared/path-lookup.c @@ -0,0 +1,822 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include + +#include "alloc-util.h" +#include "install.h" +#include "log.h" +#include "macro.h" +#include "mkdir.h" +#include "path-lookup.h" +#include "path-util.h" +#include "rm-rf.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +static int user_runtime_dir(char **ret, const char *suffix) { + const char *e; + char *j; + + assert(ret); + assert(suffix); + + e = getenv("XDG_RUNTIME_DIR"); + if (!e) + return -ENXIO; + + j = strappend(e, suffix); + if (!j) + return -ENOMEM; + + *ret = j; + return 0; +} + +static int user_config_dir(char **ret, const char *suffix) { + const char *e; + char *j; + + assert(ret); + + e = getenv("XDG_CONFIG_HOME"); + if (e) + j = strappend(e, suffix); + else { + const char *home; + + home = getenv("HOME"); + if (!home) + return -ENXIO; + + j = strjoin(home, "/.config", suffix, NULL); + } + + if (!j) + return -ENOMEM; + + *ret = j; + return 0; +} + +static int user_data_dir(char **ret, const char *suffix) { + const char *e; + char *j; + + assert(ret); + assert(suffix); + + /* We don't treat /etc/xdg/systemd here as the spec + * suggests because we assume that that is a link to + * /etc/systemd/ anyway. */ + + e = getenv("XDG_DATA_HOME"); + if (e) + j = strappend(e, suffix); + else { + const char *home; + + home = getenv("HOME"); + if (!home) + return -ENXIO; + + + j = strjoin(home, "/.local/share", suffix, NULL); + } + if (!j) + return -ENOMEM; + + *ret = j; + return 1; +} + +static char** user_dirs( + const char *persistent_config, + const char *runtime_config, + const char *generator, + const char *generator_early, + const char *generator_late, + const char *transient, + const char *persistent_control, + const char *runtime_control) { + + const char * const config_unit_paths[] = { + USER_CONFIG_UNIT_PATH, + "/etc/systemd/user", + NULL + }; + + const char * const data_unit_paths[] = { + "/usr/local/lib/systemd/user", + "/usr/local/share/systemd/user", + USER_DATA_UNIT_PATH, + "/usr/lib/systemd/user", + "/usr/share/systemd/user", + NULL + }; + + const char *e; + _cleanup_strv_free_ char **config_dirs = NULL, **data_dirs = NULL; + _cleanup_free_ char *data_home = NULL; + _cleanup_free_ char **res = NULL; + char **tmp; + int r; + + /* Implement the mechanisms defined in + * + * http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html + * + * We look in both the config and the data dirs because we + * want to encourage that distributors ship their unit files + * as data, and allow overriding as configuration. + */ + + e = getenv("XDG_CONFIG_DIRS"); + if (e) { + config_dirs = strv_split(e, ":"); + if (!config_dirs) + return NULL; + } + + r = user_data_dir(&data_home, "/systemd/user"); + if (r < 0 && r != -ENXIO) + return NULL; + + e = getenv("XDG_DATA_DIRS"); + if (e) + data_dirs = strv_split(e, ":"); + else + data_dirs = strv_new("/usr/local/share", + "/usr/share", + NULL); + if (!data_dirs) + return NULL; + + /* Now merge everything we found. */ + if (strv_extend(&res, persistent_control) < 0) + return NULL; + + if (strv_extend(&res, runtime_control) < 0) + return NULL; + + if (strv_extend(&res, transient) < 0) + return NULL; + + if (strv_extend(&res, generator_early) < 0) + return NULL; + + if (!strv_isempty(config_dirs)) + if (strv_extend_strv_concat(&res, config_dirs, "/systemd/user") < 0) + return NULL; + + if (strv_extend(&res, persistent_config) < 0) + return NULL; + + if (strv_extend_strv(&res, (char**) config_unit_paths, false) < 0) + return NULL; + + if (strv_extend(&res, runtime_config) < 0) + return NULL; + + if (strv_extend(&res, generator) < 0) + return NULL; + + if (strv_extend(&res, data_home) < 0) + return NULL; + + if (!strv_isempty(data_dirs)) + if (strv_extend_strv_concat(&res, data_dirs, "/systemd/user") < 0) + return NULL; + + if (strv_extend_strv(&res, (char**) data_unit_paths, false) < 0) + return NULL; + + if (strv_extend(&res, generator_late) < 0) + return NULL; + + if (path_strv_make_absolute_cwd(res) < 0) + return NULL; + + tmp = res; + res = NULL; + return tmp; +} + +static int acquire_generator_dirs( + UnitFileScope scope, + char **generator, + char **generator_early, + char **generator_late) { + + _cleanup_free_ char *x = NULL, *y = NULL, *z = NULL; + const char *prefix; + + assert(generator); + assert(generator_early); + assert(generator_late); + + switch (scope) { + + case UNIT_FILE_SYSTEM: + prefix = "/run/systemd/"; + break; + + case UNIT_FILE_USER: { + const char *e; + + e = getenv("XDG_RUNTIME_DIR"); + if (!e) + return -ENXIO; + + prefix = strjoina(e, "/systemd/"); + break; + } + + case UNIT_FILE_GLOBAL: + return -EOPNOTSUPP; + + default: + assert_not_reached("Hmm, unexpected scope value."); + } + + x = strappend(prefix, "generator"); + if (!x) + return -ENOMEM; + + y = strappend(prefix, "generator.early"); + if (!y) + return -ENOMEM; + + z = strappend(prefix, "generator.late"); + if (!z) + return -ENOMEM; + + *generator = x; + *generator_early = y; + *generator_late = z; + + x = y = z = NULL; + return 0; +} + +static int acquire_transient_dir(UnitFileScope scope, char **ret) { + assert(ret); + + switch (scope) { + + case UNIT_FILE_SYSTEM: { + char *transient; + + transient = strdup("/run/systemd/transient"); + if (!transient) + return -ENOMEM; + + *ret = transient; + return 0; + } + + case UNIT_FILE_USER: + return user_runtime_dir(ret, "/systemd/transient"); + + case UNIT_FILE_GLOBAL: + return -EOPNOTSUPP; + + default: + assert_not_reached("Hmm, unexpected scope value."); + } +} + +static int acquire_config_dirs(UnitFileScope scope, char **persistent, char **runtime) { + _cleanup_free_ char *a = NULL, *b = NULL; + int r; + + assert(persistent); + assert(runtime); + + switch (scope) { + + case UNIT_FILE_SYSTEM: + a = strdup(SYSTEM_CONFIG_UNIT_PATH); + b = strdup("/run/systemd/system"); + break; + + case UNIT_FILE_GLOBAL: + a = strdup(USER_CONFIG_UNIT_PATH); + b = strdup("/run/systemd/user"); + break; + + case UNIT_FILE_USER: + r = user_config_dir(&a, "/systemd/user"); + if (r < 0) + return r; + + r = user_runtime_dir(runtime, "/systemd/user"); + if (r < 0) + return r; + + *persistent = a; + a = NULL; + + return 0; + + default: + assert_not_reached("Hmm, unexpected scope value."); + } + + if (!a || !b) + return -ENOMEM; + + *persistent = a; + *runtime = b; + a = b = NULL; + + return 0; +} + +static int acquire_control_dirs(UnitFileScope scope, char **persistent, char **runtime) { + _cleanup_free_ char *a = NULL; + int r; + + assert(persistent); + assert(runtime); + + switch (scope) { + + case UNIT_FILE_SYSTEM: { + _cleanup_free_ char *b = NULL; + + a = strdup("/etc/systemd/system.control"); + if (!a) + return -ENOMEM; + + b = strdup("/run/systemd/system.control"); + if (!b) + return -ENOMEM; + + *runtime = b; + b = NULL; + + break; + } + + case UNIT_FILE_USER: + r = user_config_dir(&a, "/systemd/system.control"); + if (r < 0) + return r; + + r = user_runtime_dir(runtime, "/systemd/system.control"); + if (r < 0) + return r; + + break; + + case UNIT_FILE_GLOBAL: + return -EOPNOTSUPP; + + default: + assert_not_reached("Hmm, unexpected scope value."); + } + + *persistent = a; + a = NULL; + + return 0; +} + +static int patch_root_prefix(char **p, const char *root_dir) { + char *c; + + assert(p); + + if (!*p) + return 0; + + c = prefix_root(root_dir, *p); + if (!c) + return -ENOMEM; + + free(*p); + *p = c; + + return 0; +} + +static int patch_root_prefix_strv(char **l, const char *root_dir) { + char **i; + int r; + + if (!root_dir) + return 0; + + STRV_FOREACH(i, l) { + r = patch_root_prefix(i, root_dir); + if (r < 0) + return r; + } + + return 0; +} + +int lookup_paths_init( + LookupPaths *p, + UnitFileScope scope, + LookupPathsFlags flags, + const char *root_dir) { + + _cleanup_free_ char + *root = NULL, + *persistent_config = NULL, *runtime_config = NULL, + *generator = NULL, *generator_early = NULL, *generator_late = NULL, + *transient = NULL, + *persistent_control = NULL, *runtime_control = NULL; + bool append = false; /* Add items from SYSTEMD_UNIT_PATH before normal directories */ + _cleanup_strv_free_ char **paths = NULL; + const char *e; + int r; + + assert(p); + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + if (!isempty(root_dir) && !path_equal(root_dir, "/")) { + if (scope == UNIT_FILE_USER) + return -EINVAL; + + r = is_dir(root_dir, true); + if (r < 0) + return r; + if (r == 0) + return -ENOTDIR; + + root = strdup(root_dir); + if (!root) + return -ENOMEM; + } + + r = acquire_config_dirs(scope, &persistent_config, &runtime_config); + if (r < 0 && r != -ENXIO) + return r; + + if ((flags & LOOKUP_PATHS_EXCLUDE_GENERATED) == 0) { + r = acquire_generator_dirs(scope, &generator, &generator_early, &generator_late); + if (r < 0 && r != -EOPNOTSUPP && r != -ENXIO) + return r; + } + + r = acquire_transient_dir(scope, &transient); + if (r < 0 && r != -EOPNOTSUPP && r != -ENXIO) + return r; + + r = acquire_control_dirs(scope, &persistent_control, &runtime_control); + if (r < 0 && r != -EOPNOTSUPP && r != -ENXIO) + return r; + + /* First priority is whatever has been passed to us via env vars */ + e = getenv("SYSTEMD_UNIT_PATH"); + if (e) { + const char *k; + + k = endswith(e, ":"); + if (k) { + e = strndupa(e, k - e); + append = true; + } + + /* FIXME: empty components in other places should be + * rejected. */ + + r = path_split_and_make_absolute(e, &paths); + if (r < 0) + return r; + } + + if (!paths || append) { + /* Let's figure something out. */ + + _cleanup_strv_free_ char **add = NULL; + + /* For the user units we include share/ in the search + * path in order to comply with the XDG basedir spec. + * For the system stuff we avoid such nonsense. OTOH + * we include /lib in the search path for the system + * stuff but avoid it for user stuff. */ + + switch (scope) { + + case UNIT_FILE_SYSTEM: + add = strv_new( + /* If you modify this you also want to modify + * systemdsystemunitpath= in systemd.pc.in! */ + STRV_IFNOTNULL(persistent_control), + STRV_IFNOTNULL(runtime_control), + STRV_IFNOTNULL(transient), + STRV_IFNOTNULL(generator_early), + persistent_config, + SYSTEM_CONFIG_UNIT_PATH, + "/etc/systemd/system", + runtime_config, + "/run/systemd/system", + STRV_IFNOTNULL(generator), + "/usr/local/lib/systemd/system", + SYSTEM_DATA_UNIT_PATH, + "/usr/lib/systemd/system", +#ifdef HAVE_SPLIT_USR + "/lib/systemd/system", +#endif + STRV_IFNOTNULL(generator_late), + NULL); + break; + + case UNIT_FILE_GLOBAL: + add = strv_new( + /* If you modify this you also want to modify + * systemduserunitpath= in systemd.pc.in, and + * the arrays in user_dirs() above! */ + STRV_IFNOTNULL(persistent_control), + STRV_IFNOTNULL(runtime_control), + STRV_IFNOTNULL(transient), + STRV_IFNOTNULL(generator_early), + persistent_config, + USER_CONFIG_UNIT_PATH, + "/etc/systemd/user", + runtime_config, + "/run/systemd/user", + STRV_IFNOTNULL(generator), + "/usr/local/lib/systemd/user", + "/usr/local/share/systemd/user", + USER_DATA_UNIT_PATH, + "/usr/lib/systemd/user", + "/usr/share/systemd/user", + STRV_IFNOTNULL(generator_late), + NULL); + break; + + case UNIT_FILE_USER: + add = user_dirs(persistent_config, runtime_config, + generator, generator_early, generator_late, + transient, + persistent_config, runtime_control); + break; + + default: + assert_not_reached("Hmm, unexpected scope?"); + } + + if (!add) + return -ENOMEM; + + if (paths) { + r = strv_extend_strv(&paths, add, true); + if (r < 0) + return r; + } else { + /* Small optimization: if paths is NULL (and it usually is), we can simply assign 'add' to it, + * and don't have to copy anything */ + paths = add; + add = NULL; + } + } + + r = patch_root_prefix(&persistent_config, root); + if (r < 0) + return r; + r = patch_root_prefix(&runtime_config, root); + if (r < 0) + return r; + + r = patch_root_prefix(&generator, root); + if (r < 0) + return r; + r = patch_root_prefix(&generator_early, root); + if (r < 0) + return r; + r = patch_root_prefix(&generator_late, root); + if (r < 0) + return r; + + r = patch_root_prefix(&transient, root); + if (r < 0) + return r; + + r = patch_root_prefix(&persistent_control, root); + if (r < 0) + return r; + + r = patch_root_prefix(&runtime_control, root); + if (r < 0) + return r; + + r = patch_root_prefix_strv(paths, root); + if (r < 0) + return -ENOMEM; + + p->search_path = strv_uniq(paths); + paths = NULL; + + p->persistent_config = persistent_config; + p->runtime_config = runtime_config; + persistent_config = runtime_config = NULL; + + p->generator = generator; + p->generator_early = generator_early; + p->generator_late = generator_late; + generator = generator_early = generator_late = NULL; + + p->transient = transient; + transient = NULL; + + p->persistent_control = persistent_control; + p->runtime_control = runtime_control; + persistent_control = runtime_control = NULL; + + p->root_dir = root; + root = NULL; + + return 0; +} + +void lookup_paths_free(LookupPaths *p) { + if (!p) + return; + + p->search_path = strv_free(p->search_path); + + p->persistent_config = mfree(p->persistent_config); + p->runtime_config = mfree(p->runtime_config); + + p->generator = mfree(p->generator); + p->generator_early = mfree(p->generator_early); + p->generator_late = mfree(p->generator_late); + + p->transient = mfree(p->transient); + + p->persistent_control = mfree(p->persistent_control); + p->runtime_control = mfree(p->runtime_control); + + p->root_dir = mfree(p->root_dir); +} + +int lookup_paths_reduce(LookupPaths *p) { + _cleanup_free_ struct stat *stats = NULL; + size_t n_stats = 0, allocated = 0; + unsigned c = 0; + int r; + + assert(p); + + /* Drop duplicates and non-existing directories from the search path. We figure out whether two directories are + * the same by comparing their device and inode numbers. Note one special tweak: when we have a root path set, + * we do not follow symlinks when retrieving them, because the kernel wouldn't take the root prefix into + * account when following symlinks. When we have no root path set this restriction does not apply however. */ + + if (!p->search_path) + return 0; + + while (p->search_path[c]) { + struct stat st; + unsigned k; + + if (p->root_dir) + r = lstat(p->search_path[c], &st); + else + r = stat(p->search_path[c], &st); + if (r < 0) { + if (errno == ENOENT) + goto remove_item; + + /* If something we don't grok happened, let's better leave it in. */ + log_debug_errno(errno, "Failed to stat %s: %m", p->search_path[c]); + c++; + continue; + } + + for (k = 0; k < n_stats; k++) { + if (stats[k].st_dev == st.st_dev && + stats[k].st_ino == st.st_ino) + break; + } + + if (k < n_stats) /* Is there already an entry with the same device/inode? */ + goto remove_item; + + if (!GREEDY_REALLOC(stats, allocated, n_stats+1)) + return -ENOMEM; + + stats[n_stats++] = st; + c++; + continue; + + remove_item: + free(p->search_path[c]); + memmove(p->search_path + c, + p->search_path + c + 1, + (strv_length(p->search_path + c + 1) + 1) * sizeof(char*)); + } + + if (strv_isempty(p->search_path)) { + log_debug("Ignoring unit files."); + p->search_path = strv_free(p->search_path); + } else { + _cleanup_free_ char *t; + + t = strv_join(p->search_path, "\n\t"); + if (!t) + return -ENOMEM; + + log_debug("Looking for unit files in (higher priority first):\n\t%s", t); + } + + return 0; +} + +int lookup_paths_mkdir_generator(LookupPaths *p) { + int r, q; + + assert(p); + + if (!p->generator || !p->generator_early || !p->generator_late) + return -EINVAL; + + r = mkdir_p_label(p->generator, 0755); + + q = mkdir_p_label(p->generator_early, 0755); + if (q < 0 && r >= 0) + r = q; + + q = mkdir_p_label(p->generator_late, 0755); + if (q < 0 && r >= 0) + r = q; + + return r; +} + +void lookup_paths_trim_generator(LookupPaths *p) { + assert(p); + + /* Trim empty dirs */ + + if (p->generator) + (void) rmdir(p->generator); + if (p->generator_early) + (void) rmdir(p->generator_early); + if (p->generator_late) + (void) rmdir(p->generator_late); +} + +void lookup_paths_flush_generator(LookupPaths *p) { + assert(p); + + /* Flush the generated unit files in full */ + + if (p->generator) + (void) rm_rf(p->generator, REMOVE_ROOT); + if (p->generator_early) + (void) rm_rf(p->generator_early, REMOVE_ROOT); + if (p->generator_late) + (void) rm_rf(p->generator_late, REMOVE_ROOT); +} + +char **generator_binary_paths(UnitFileScope scope) { + + switch (scope) { + + case UNIT_FILE_SYSTEM: + return strv_new("/run/systemd/system-generators", + "/etc/systemd/system-generators", + "/usr/local/lib/systemd/system-generators", + SYSTEM_GENERATOR_PATH, + NULL); + + case UNIT_FILE_GLOBAL: + case UNIT_FILE_USER: + return strv_new("/run/systemd/user-generators", + "/etc/systemd/user-generators", + "/usr/local/lib/systemd/user-generators", + USER_GENERATOR_PATH, + NULL); + + default: + assert_not_reached("Hmm, unexpected scope."); + } +} diff --git a/src/libshared/path-lookup.h b/src/libshared/path-lookup.h new file mode 100644 index 0000000000..f9bb2fe237 --- /dev/null +++ b/src/libshared/path-lookup.h @@ -0,0 +1,76 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 + +typedef struct LookupPaths LookupPaths; + +#include "install.h" +#include "macro.h" + +typedef enum LookupPathsFlags { + LOOKUP_PATHS_EXCLUDE_GENERATED = 1, +} LookupPathsFlags; + +struct LookupPaths { + /* Where we look for unit files. This includes the individual special paths below, but also any vendor + * supplied, static unit file paths. */ + char **search_path; + + /* Where we shall create or remove our installation symlinks, aka "configuration", and where the user/admin + * shall place his own unit files. */ + char *persistent_config; + char *runtime_config; + + /* Where to place generated unit files (i.e. those a "generator" tool generated). Note the special semantics of + * this directory: the generators are flushed each time a "systemctl daemon-reload" is issued. The user should + * not alter these directories directly. */ + char *generator; + char *generator_early; + char *generator_late; + + /* Where to place transient unit files (i.e. those created dynamically via the bus API). Note the special + * semantics of this directory: all units created transiently have their unit files removed as the transient + * unit is unloaded. The user should not alter this directory directly. */ + char *transient; + + /* Where the snippets created by "systemctl set-property" are placed. Note that for transient units, the + * snippets are placed in the transient directory though (see above). The user should not alter this directory + * directly. */ + char *persistent_control; + char *runtime_control; + + /* The root directory prepended to all items above, or NULL */ + char *root_dir; +}; + +int lookup_paths_init(LookupPaths *p, UnitFileScope scope, LookupPathsFlags flags, const char *root_dir); + +int lookup_paths_reduce(LookupPaths *p); + +int lookup_paths_mkdir_generator(LookupPaths *p); +void lookup_paths_trim_generator(LookupPaths *p); +void lookup_paths_flush_generator(LookupPaths *p); + +void lookup_paths_free(LookupPaths *p); +#define _cleanup_lookup_paths_free_ _cleanup_(lookup_paths_free) + +char **generator_binary_paths(UnitFileScope scope); diff --git a/src/libshared/ptyfwd.c b/src/libshared/ptyfwd.c new file mode 100644 index 0000000000..9629b50ed9 --- /dev/null +++ b/src/libshared/ptyfwd.c @@ -0,0 +1,484 @@ +/*** + This file is part of systemd. + + Copyright 2010-2013 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "log.h" +#include "macro.h" +#include "ptyfwd.h" +#include "time-util.h" + +struct PTYForward { + sd_event *event; + + int master; + + PTYForwardFlags flags; + + sd_event_source *stdin_event_source; + sd_event_source *stdout_event_source; + sd_event_source *master_event_source; + + sd_event_source *sigwinch_event_source; + + struct termios saved_stdin_attr; + struct termios saved_stdout_attr; + + bool saved_stdin:1; + bool saved_stdout:1; + + bool stdin_readable:1; + bool stdin_hangup:1; + bool stdout_writable:1; + bool stdout_hangup:1; + bool master_readable:1; + bool master_writable:1; + bool master_hangup:1; + + bool read_from_master:1; + + bool last_char_set:1; + char last_char; + + char in_buffer[LINE_MAX], out_buffer[LINE_MAX]; + size_t in_buffer_full, out_buffer_full; + + usec_t escape_timestamp; + unsigned escape_counter; +}; + +#define ESCAPE_USEC (1*USEC_PER_SEC) + +static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) { + const char *p; + + assert(f); + assert(buffer); + assert(n > 0); + + for (p = buffer; p < buffer + n; p++) { + + /* Check for ^] */ + if (*p == 0x1D) { + usec_t nw = now(CLOCK_MONOTONIC); + + if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) { + f->escape_timestamp = nw; + f->escape_counter = 1; + } else { + (f->escape_counter)++; + + if (f->escape_counter >= 3) + return true; + } + } else { + f->escape_timestamp = 0; + f->escape_counter = 0; + } + } + + return false; +} + +static bool ignore_vhangup(PTYForward *f) { + assert(f); + + if (f->flags & PTY_FORWARD_IGNORE_VHANGUP) + return true; + + if ((f->flags & PTY_FORWARD_IGNORE_INITIAL_VHANGUP) && !f->read_from_master) + return true; + + return false; +} + +static int shovel(PTYForward *f) { + ssize_t k; + + assert(f); + + while ((f->stdin_readable && f->in_buffer_full <= 0) || + (f->master_writable && f->in_buffer_full > 0) || + (f->master_readable && f->out_buffer_full <= 0) || + (f->stdout_writable && f->out_buffer_full > 0)) { + + if (f->stdin_readable && f->in_buffer_full < LINE_MAX) { + + k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full); + if (k < 0) { + + if (errno == EAGAIN) + f->stdin_readable = false; + else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) { + f->stdin_readable = false; + f->stdin_hangup = true; + + f->stdin_event_source = sd_event_source_unref(f->stdin_event_source); + } else { + log_error_errno(errno, "read(): %m"); + return sd_event_exit(f->event, EXIT_FAILURE); + } + } else if (k == 0) { + /* EOF on stdin */ + f->stdin_readable = false; + f->stdin_hangup = true; + + f->stdin_event_source = sd_event_source_unref(f->stdin_event_source); + } else { + /* Check if ^] has been + * pressed three times within + * one second. If we get this + * we quite immediately. */ + if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k)) + return sd_event_exit(f->event, EXIT_FAILURE); + + f->in_buffer_full += (size_t) k; + } + } + + if (f->master_writable && f->in_buffer_full > 0) { + + k = write(f->master, f->in_buffer, f->in_buffer_full); + if (k < 0) { + + if (errno == EAGAIN || errno == EIO) + f->master_writable = false; + else if (errno == EPIPE || errno == ECONNRESET) { + f->master_writable = f->master_readable = false; + f->master_hangup = true; + + f->master_event_source = sd_event_source_unref(f->master_event_source); + } else { + log_error_errno(errno, "write(): %m"); + return sd_event_exit(f->event, EXIT_FAILURE); + } + } else { + assert(f->in_buffer_full >= (size_t) k); + memmove(f->in_buffer, f->in_buffer + k, f->in_buffer_full - k); + f->in_buffer_full -= k; + } + } + + if (f->master_readable && f->out_buffer_full < LINE_MAX) { + + k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full); + if (k < 0) { + + /* Note that EIO on the master device + * might be caused by vhangup() or + * temporary closing of everything on + * the other side, we treat it like + * EAGAIN here and try again, unless + * ignore_vhangup is off. */ + + if (errno == EAGAIN || (errno == EIO && ignore_vhangup(f))) + f->master_readable = false; + else if (errno == EPIPE || errno == ECONNRESET || errno == EIO) { + f->master_readable = f->master_writable = false; + f->master_hangup = true; + + f->master_event_source = sd_event_source_unref(f->master_event_source); + } else { + log_error_errno(errno, "read(): %m"); + return sd_event_exit(f->event, EXIT_FAILURE); + } + } else { + f->read_from_master = true; + f->out_buffer_full += (size_t) k; + } + } + + if (f->stdout_writable && f->out_buffer_full > 0) { + + k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full); + if (k < 0) { + + if (errno == EAGAIN) + f->stdout_writable = false; + else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) { + f->stdout_writable = false; + f->stdout_hangup = true; + f->stdout_event_source = sd_event_source_unref(f->stdout_event_source); + } else { + log_error_errno(errno, "write(): %m"); + return sd_event_exit(f->event, EXIT_FAILURE); + } + + } else { + + if (k > 0) { + f->last_char = f->out_buffer[k-1]; + f->last_char_set = true; + } + + assert(f->out_buffer_full >= (size_t) k); + memmove(f->out_buffer, f->out_buffer + k, f->out_buffer_full - k); + f->out_buffer_full -= k; + } + } + } + + if (f->stdin_hangup || f->stdout_hangup || f->master_hangup) { + /* Exit the loop if any side hung up and if there's + * nothing more to write or nothing we could write. */ + + if ((f->out_buffer_full <= 0 || f->stdout_hangup) && + (f->in_buffer_full <= 0 || f->master_hangup)) + return sd_event_exit(f->event, EXIT_SUCCESS); + } + + return 0; +} + +static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) { + PTYForward *f = userdata; + + assert(f); + assert(e); + assert(e == f->master_event_source); + assert(fd >= 0); + assert(fd == f->master); + + if (revents & (EPOLLIN|EPOLLHUP)) + f->master_readable = true; + + if (revents & (EPOLLOUT|EPOLLHUP)) + f->master_writable = true; + + return shovel(f); +} + +static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) { + PTYForward *f = userdata; + + assert(f); + assert(e); + assert(e == f->stdin_event_source); + assert(fd >= 0); + assert(fd == STDIN_FILENO); + + if (revents & (EPOLLIN|EPOLLHUP)) + f->stdin_readable = true; + + return shovel(f); +} + +static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) { + PTYForward *f = userdata; + + assert(f); + assert(e); + assert(e == f->stdout_event_source); + assert(fd >= 0); + assert(fd == STDOUT_FILENO); + + if (revents & (EPOLLOUT|EPOLLHUP)) + f->stdout_writable = true; + + return shovel(f); +} + +static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) { + PTYForward *f = userdata; + struct winsize ws; + + assert(f); + assert(e); + assert(e == f->sigwinch_event_source); + + /* The window size changed, let's forward that. */ + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0) + (void) ioctl(f->master, TIOCSWINSZ, &ws); + + return 0; +} + +int pty_forward_new( + sd_event *event, + int master, + PTYForwardFlags flags, + PTYForward **ret) { + + _cleanup_(pty_forward_freep) PTYForward *f = NULL; + struct winsize ws; + int r; + + f = new0(PTYForward, 1); + if (!f) + return -ENOMEM; + + f->flags = flags; + + if (event) + f->event = sd_event_ref(event); + else { + r = sd_event_default(&f->event); + if (r < 0) + return r; + } + + if (!(flags & PTY_FORWARD_READ_ONLY)) { + r = fd_nonblock(STDIN_FILENO, true); + if (r < 0) + return r; + + r = fd_nonblock(STDOUT_FILENO, true); + if (r < 0) + return r; + } + + r = fd_nonblock(master, true); + if (r < 0) + return r; + + f->master = master; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0) + (void) ioctl(master, TIOCSWINSZ, &ws); + + if (!(flags & PTY_FORWARD_READ_ONLY)) { + if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) { + struct termios raw_stdin_attr; + + f->saved_stdin = true; + + raw_stdin_attr = f->saved_stdin_attr; + cfmakeraw(&raw_stdin_attr); + raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag; + tcsetattr(STDIN_FILENO, TCSANOW, &raw_stdin_attr); + } + + if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) { + struct termios raw_stdout_attr; + + f->saved_stdout = true; + + raw_stdout_attr = f->saved_stdout_attr; + cfmakeraw(&raw_stdout_attr); + raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag; + raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag; + tcsetattr(STDOUT_FILENO, TCSANOW, &raw_stdout_attr); + } + + r = sd_event_add_io(f->event, &f->stdin_event_source, STDIN_FILENO, EPOLLIN|EPOLLET, on_stdin_event, f); + if (r < 0 && r != -EPERM) + return r; + } + + r = sd_event_add_io(f->event, &f->stdout_event_source, STDOUT_FILENO, EPOLLOUT|EPOLLET, on_stdout_event, f); + if (r == -EPERM) + /* stdout without epoll support. Likely redirected to regular file. */ + f->stdout_writable = true; + else if (r < 0) + return r; + + r = sd_event_add_io(f->event, &f->master_event_source, master, EPOLLIN|EPOLLOUT|EPOLLET, on_master_event, f); + if (r < 0) + return r; + + r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f); + if (r < 0) + return r; + + *ret = f; + f = NULL; + + return 0; +} + +PTYForward *pty_forward_free(PTYForward *f) { + + if (f) { + sd_event_source_unref(f->stdin_event_source); + sd_event_source_unref(f->stdout_event_source); + sd_event_source_unref(f->master_event_source); + sd_event_source_unref(f->sigwinch_event_source); + sd_event_unref(f->event); + + if (f->saved_stdout) + tcsetattr(STDOUT_FILENO, TCSANOW, &f->saved_stdout_attr); + if (f->saved_stdin) + tcsetattr(STDIN_FILENO, TCSANOW, &f->saved_stdin_attr); + + free(f); + } + + /* STDIN/STDOUT should not be nonblocking normally, so let's + * unconditionally reset it */ + fd_nonblock(STDIN_FILENO, false); + fd_nonblock(STDOUT_FILENO, false); + + return NULL; +} + +int pty_forward_get_last_char(PTYForward *f, char *ch) { + assert(f); + assert(ch); + + if (!f->last_char_set) + return -ENXIO; + + *ch = f->last_char; + return 0; +} + +int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) { + int r; + + assert(f); + + if (!!(f->flags & PTY_FORWARD_IGNORE_VHANGUP) == b) + return 0; + + SET_FLAG(f->flags, PTY_FORWARD_IGNORE_VHANGUP, b); + + if (!ignore_vhangup(f)) { + + /* We shall now react to vhangup()s? Let's check + * immediately if we might be in one */ + + f->master_readable = true; + r = shovel(f); + if (r < 0) + return r; + } + + return 0; +} + +int pty_forward_get_ignore_vhangup(PTYForward *f) { + assert(f); + + return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP); +} diff --git a/src/libshared/ptyfwd.h b/src/libshared/ptyfwd.h new file mode 100644 index 0000000000..83c1f60970 --- /dev/null +++ b/src/libshared/ptyfwd.h @@ -0,0 +1,48 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-2013 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 + +#include "macro.h" + +typedef struct PTYForward PTYForward; + +typedef enum PTYForwardFlags { + PTY_FORWARD_READ_ONLY = 1, + + /* Continue reading after hangup? */ + PTY_FORWARD_IGNORE_VHANGUP = 2, + + /* Continue reading after hangup but only if we never read anything else? */ + PTY_FORWARD_IGNORE_INITIAL_VHANGUP = 4, +} PTYForwardFlags; + +int pty_forward_new(sd_event *event, int master, PTYForwardFlags flags, PTYForward **f); +PTYForward *pty_forward_free(PTYForward *f); + +int pty_forward_get_last_char(PTYForward *f, char *ch); + +int pty_forward_set_ignore_vhangup(PTYForward *f, bool ignore_vhangup); +int pty_forward_get_ignore_vhangup(PTYForward *f); + +DEFINE_TRIVIAL_CLEANUP_FUNC(PTYForward*, pty_forward_free); diff --git a/src/libshared/resolve-util.c b/src/libshared/resolve-util.c new file mode 100644 index 0000000000..e2da81bab7 --- /dev/null +++ b/src/libshared/resolve-util.c @@ -0,0 +1,39 @@ +/*** + This file is part of systemd. + + Copyright 2016 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 "conf-parser.h" +#include "resolve-util.h" +#include "string-table.h" + +DEFINE_CONFIG_PARSE_ENUM(config_parse_resolve_support, resolve_support, ResolveSupport, "Failed to parse resolve support setting"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_dnssec_mode, dnssec_mode, DnssecMode, "Failed to parse DNSSEC mode setting"); + +static const char* const resolve_support_table[_RESOLVE_SUPPORT_MAX] = { + [RESOLVE_SUPPORT_NO] = "no", + [RESOLVE_SUPPORT_YES] = "yes", + [RESOLVE_SUPPORT_RESOLVE] = "resolve", +}; +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(resolve_support, ResolveSupport, RESOLVE_SUPPORT_YES); + +static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = { + [DNSSEC_NO] = "no", + [DNSSEC_ALLOW_DOWNGRADE] = "allow-downgrade", + [DNSSEC_YES] = "yes", +}; +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dnssec_mode, DnssecMode, DNSSEC_YES); diff --git a/src/libshared/resolve-util.h b/src/libshared/resolve-util.h new file mode 100644 index 0000000000..8636a6c134 --- /dev/null +++ b/src/libshared/resolve-util.h @@ -0,0 +1,60 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 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 "macro.h" + +typedef enum ResolveSupport ResolveSupport; +typedef enum DnssecMode DnssecMode; + +enum ResolveSupport { + RESOLVE_SUPPORT_NO, + RESOLVE_SUPPORT_YES, + RESOLVE_SUPPORT_RESOLVE, + _RESOLVE_SUPPORT_MAX, + _RESOLVE_SUPPORT_INVALID = -1 +}; + +enum DnssecMode { + /* No DNSSEC validation is done */ + DNSSEC_NO, + + /* Validate locally, if the server knows DO, but if not, + * don't. Don't trust the AD bit. If the server doesn't do + * DNSSEC properly, downgrade to non-DNSSEC operation. Of + * course, we then are vulnerable to a downgrade attack, but + * that's life and what is configured. */ + DNSSEC_ALLOW_DOWNGRADE, + + /* Insist on DNSSEC server support, and rather fail than downgrading. */ + DNSSEC_YES, + + _DNSSEC_MODE_MAX, + _DNSSEC_MODE_INVALID = -1 +}; + +int config_parse_resolve_support(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); +int config_parse_dnssec_mode(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); + +const char* resolve_support_to_string(ResolveSupport p) _const_; +ResolveSupport resolve_support_from_string(const char *s) _pure_; + +const char* dnssec_mode_to_string(DnssecMode p) _const_; +DnssecMode dnssec_mode_from_string(const char *s) _pure_; diff --git a/src/libshared/seccomp-util.c b/src/libshared/seccomp-util.c new file mode 100644 index 0000000000..cebe0fce2a --- /dev/null +++ b/src/libshared/seccomp-util.c @@ -0,0 +1,90 @@ +/*** + 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 +#include + +#include "macro.h" +#include "seccomp-util.h" +#include "string-util.h" + +const char* seccomp_arch_to_string(uint32_t c) { + + if (c == SCMP_ARCH_NATIVE) + return "native"; + if (c == SCMP_ARCH_X86) + return "x86"; + if (c == SCMP_ARCH_X86_64) + return "x86-64"; + if (c == SCMP_ARCH_X32) + return "x32"; + if (c == SCMP_ARCH_ARM) + return "arm"; + + return NULL; +} + +int seccomp_arch_from_string(const char *n, uint32_t *ret) { + if (!n) + return -EINVAL; + + assert(ret); + + if (streq(n, "native")) + *ret = SCMP_ARCH_NATIVE; + else if (streq(n, "x86")) + *ret = SCMP_ARCH_X86; + else if (streq(n, "x86-64")) + *ret = SCMP_ARCH_X86_64; + else if (streq(n, "x32")) + *ret = SCMP_ARCH_X32; + else if (streq(n, "arm")) + *ret = SCMP_ARCH_ARM; + else + return -EINVAL; + + return 0; +} + +int seccomp_add_secondary_archs(scmp_filter_ctx *c) { + +#if defined(__i386__) || defined(__x86_64__) + int r; + + /* Add in all possible secondary archs we are aware of that + * this kernel might support. */ + + r = seccomp_arch_add(c, SCMP_ARCH_X86); + if (r < 0 && r != -EEXIST) + return r; + + r = seccomp_arch_add(c, SCMP_ARCH_X86_64); + if (r < 0 && r != -EEXIST) + return r; + + r = seccomp_arch_add(c, SCMP_ARCH_X32); + if (r < 0 && r != -EEXIST) + return r; + +#endif + + return 0; + +} diff --git a/src/libshared/seccomp-util.h b/src/libshared/seccomp-util.h new file mode 100644 index 0000000000..4ed2afc1b2 --- /dev/null +++ b/src/libshared/seccomp-util.h @@ -0,0 +1,28 @@ +#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 +#include + +const char* seccomp_arch_to_string(uint32_t c); +int seccomp_arch_from_string(const char *n, uint32_t *ret); + +int seccomp_add_secondary_archs(scmp_filter_ctx *c); diff --git a/src/libshared/sleep-config.c b/src/libshared/sleep-config.c new file mode 100644 index 0000000000..f00624d0f2 --- /dev/null +++ b/src/libshared/sleep-config.c @@ -0,0 +1,278 @@ +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + 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 +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "conf-parser.h" +#include "def.h" +#include "env-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "sleep-config.h" +#include "string-util.h" +#include "strv.h" + +#define USE(x, y) do { (x) = (y); (y) = NULL; } while (0) + +int parse_sleep_config(const char *verb, char ***_modes, char ***_states) { + + _cleanup_strv_free_ char + **suspend_mode = NULL, **suspend_state = NULL, + **hibernate_mode = NULL, **hibernate_state = NULL, + **hybrid_mode = NULL, **hybrid_state = NULL; + char **modes, **states; + + const ConfigTableItem items[] = { + { "Sleep", "SuspendMode", config_parse_strv, 0, &suspend_mode }, + { "Sleep", "SuspendState", config_parse_strv, 0, &suspend_state }, + { "Sleep", "HibernateMode", config_parse_strv, 0, &hibernate_mode }, + { "Sleep", "HibernateState", config_parse_strv, 0, &hibernate_state }, + { "Sleep", "HybridSleepMode", config_parse_strv, 0, &hybrid_mode }, + { "Sleep", "HybridSleepState", config_parse_strv, 0, &hybrid_state }, + {} + }; + + config_parse_many(PKGSYSCONFDIR "/sleep.conf", + CONF_PATHS_NULSTR("systemd/sleep.conf.d"), + "Sleep\0", config_item_table_lookup, items, + false, NULL); + + if (streq(verb, "suspend")) { + /* empty by default */ + USE(modes, suspend_mode); + + if (suspend_state) + USE(states, suspend_state); + else + states = strv_new("mem", "standby", "freeze", NULL); + + } else if (streq(verb, "hibernate")) { + if (hibernate_mode) + USE(modes, hibernate_mode); + else + modes = strv_new("platform", "shutdown", NULL); + + if (hibernate_state) + USE(states, hibernate_state); + else + states = strv_new("disk", NULL); + + } else if (streq(verb, "hybrid-sleep")) { + if (hybrid_mode) + USE(modes, hybrid_mode); + else + modes = strv_new("suspend", "platform", "shutdown", NULL); + + if (hybrid_state) + USE(states, hybrid_state); + else + states = strv_new("disk", NULL); + + } else + assert_not_reached("what verb"); + + if ((!modes && !streq(verb, "suspend")) || !states) { + strv_free(modes); + strv_free(states); + return log_oom(); + } + + *_modes = modes; + *_states = states; + return 0; +} + +int can_sleep_state(char **types) { + char **type; + int r; + _cleanup_free_ char *p = NULL; + + if (strv_isempty(types)) + return true; + + /* If /sys is read-only we cannot sleep */ + if (access("/sys/power/state", W_OK) < 0) + return false; + + r = read_one_line_file("/sys/power/state", &p); + if (r < 0) + return false; + + STRV_FOREACH(type, types) { + const char *word, *state; + size_t l, k; + + k = strlen(*type); + FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) + if (l == k && memcmp(word, *type, l) == 0) + return true; + } + + return false; +} + +int can_sleep_disk(char **types) { + char **type; + int r; + _cleanup_free_ char *p = NULL; + + if (strv_isempty(types)) + return true; + + /* If /sys is read-only we cannot sleep */ + if (access("/sys/power/disk", W_OK) < 0) + return false; + + r = read_one_line_file("/sys/power/disk", &p); + if (r < 0) + return false; + + STRV_FOREACH(type, types) { + const char *word, *state; + size_t l, k; + + k = strlen(*type); + FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) { + if (l == k && memcmp(word, *type, l) == 0) + return true; + + if (l == k + 2 && + word[0] == '[' && + memcmp(word + 1, *type, l - 2) == 0 && + word[l-1] == ']') + return true; + } + } + + return false; +} + +#define HIBERNATION_SWAP_THRESHOLD 0.98 + +static int hibernation_partition_size(size_t *size, size_t *used) { + _cleanup_fclose_ FILE *f; + unsigned i; + + assert(size); + assert(used); + + f = fopen("/proc/swaps", "re"); + if (!f) { + log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, + "Failed to retrieve open /proc/swaps: %m"); + assert(errno > 0); + return -errno; + } + + (void) fscanf(f, "%*s %*s %*s %*s %*s\n"); + + for (i = 1;; i++) { + _cleanup_free_ char *dev = NULL, *type = NULL; + size_t size_field, used_field; + int k; + + k = fscanf(f, + "%ms " /* device/file */ + "%ms " /* type of swap */ + "%zu " /* swap size */ + "%zu " /* used */ + "%*i\n", /* priority */ + &dev, &type, &size_field, &used_field); + if (k != 4) { + if (k == EOF) + break; + + log_warning("Failed to parse /proc/swaps:%u", i); + continue; + } + + if (streq(type, "partition") && endswith(dev, "\\040(deleted)")) { + log_warning("Ignoring deleted swapfile '%s'.", dev); + continue; + } + + *size = size_field; + *used = used_field; + return 0; + } + + log_debug("No swap partitions were found."); + return -ENOSYS; +} + +static bool enough_memory_for_hibernation(void) { + _cleanup_free_ char *active = NULL; + unsigned long long act = 0; + size_t size = 0, used = 0; + int r; + + if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0) + return true; + + r = hibernation_partition_size(&size, &used); + if (r < 0) + return false; + + r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active); + if (r < 0) { + log_error_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m"); + return false; + } + + r = safe_atollu(active, &act); + if (r < 0) { + log_error_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m", + active); + return false; + } + + r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD; + log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%", + r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD); + + return r; +} + +int can_sleep(const char *verb) { + _cleanup_strv_free_ char **modes = NULL, **states = NULL; + int r; + + assert(streq(verb, "suspend") || + streq(verb, "hibernate") || + streq(verb, "hybrid-sleep")); + + r = parse_sleep_config(verb, &modes, &states); + if (r < 0) + return false; + + if (!can_sleep_state(states) || !can_sleep_disk(modes)) + return false; + + return streq(verb, "suspend") || enough_memory_for_hibernation(); +} diff --git a/src/libshared/sleep-config.h b/src/libshared/sleep-config.h new file mode 100644 index 0000000000..ad10039ff4 --- /dev/null +++ b/src/libshared/sleep-config.h @@ -0,0 +1,26 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + 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 . +***/ + +int parse_sleep_config(const char *verb, char ***modes, char ***states); + +int can_sleep(const char *verb); +int can_sleep_disk(char **types); +int can_sleep_state(char **types); diff --git a/src/libshared/spawn-ask-password-agent.c b/src/libshared/spawn-ask-password-agent.c new file mode 100644 index 0000000000..a46b7525f0 --- /dev/null +++ b/src/libshared/spawn-ask-password-agent.c @@ -0,0 +1,62 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include + +#include "log.h" +#include "process-util.h" +#include "spawn-ask-password-agent.h" +#include "util.h" + +static pid_t agent_pid = 0; + +int ask_password_agent_open(void) { + int r; + + if (agent_pid > 0) + return 0; + + /* We check STDIN here, not STDOUT, since this is about input, + * not output */ + if (!isatty(STDIN_FILENO)) + return 0; + + r = fork_agent(&agent_pid, + NULL, 0, + SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, + SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, "--watch", NULL); + if (r < 0) + return log_error_errno(r, "Failed to fork TTY ask password agent: %m"); + + return 1; +} + +void ask_password_agent_close(void) { + + if (agent_pid <= 0) + return; + + /* Inform agent that we are done */ + (void) kill(agent_pid, SIGTERM); + (void) kill(agent_pid, SIGCONT); + (void) wait_for_terminate(agent_pid, NULL); + agent_pid = 0; +} diff --git a/src/libshared/spawn-ask-password-agent.h b/src/libshared/spawn-ask-password-agent.h new file mode 100644 index 0000000000..fb0749b13f --- /dev/null +++ b/src/libshared/spawn-ask-password-agent.h @@ -0,0 +1,23 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +int ask_password_agent_open(void); +void ask_password_agent_close(void); diff --git a/src/libshared/spawn-polkit-agent.c b/src/libshared/spawn-polkit-agent.c new file mode 100644 index 0000000000..7dae4d14fe --- /dev/null +++ b/src/libshared/spawn-polkit-agent.c @@ -0,0 +1,102 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include +#include +#include + +#include "fd-util.h" +#include "io-util.h" +#include "log.h" +#include "macro.h" +#include "process-util.h" +#include "spawn-polkit-agent.h" +#include "stdio-util.h" +#include "time-util.h" +#include "util.h" + +#ifdef ENABLE_POLKIT +static pid_t agent_pid = 0; + +int polkit_agent_open(void) { + int r; + int pipe_fd[2]; + char notify_fd[DECIMAL_STR_MAX(int) + 1]; + + if (agent_pid > 0) + return 0; + + /* Clients that run as root don't need to activate/query polkit */ + if (geteuid() == 0) + return 0; + + /* We check STDIN here, not STDOUT, since this is about input, + * not output */ + if (!isatty(STDIN_FILENO)) + return 0; + + if (pipe2(pipe_fd, 0) < 0) + return -errno; + + xsprintf(notify_fd, "%i", pipe_fd[1]); + + r = fork_agent(&agent_pid, + &pipe_fd[1], 1, + POLKIT_AGENT_BINARY_PATH, + POLKIT_AGENT_BINARY_PATH, "--notify-fd", notify_fd, "--fallback", NULL); + + /* Close the writing side, because that's the one for the agent */ + safe_close(pipe_fd[1]); + + if (r < 0) + log_error_errno(r, "Failed to fork TTY ask password agent: %m"); + else + /* Wait until the agent closes the fd */ + fd_wait_for_event(pipe_fd[0], POLLHUP, USEC_INFINITY); + + safe_close(pipe_fd[0]); + + return r; +} + +void polkit_agent_close(void) { + + if (agent_pid <= 0) + return; + + /* Inform agent that we are done */ + (void) kill(agent_pid, SIGTERM); + (void) kill(agent_pid, SIGCONT); + + (void) wait_for_terminate(agent_pid, NULL); + agent_pid = 0; +} + +#else + +int polkit_agent_open(void) { + return 0; +} + +void polkit_agent_close(void) { +} + +#endif diff --git a/src/libshared/spawn-polkit-agent.h b/src/libshared/spawn-polkit-agent.h new file mode 100644 index 0000000000..42b2989ded --- /dev/null +++ b/src/libshared/spawn-polkit-agent.h @@ -0,0 +1,23 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +int polkit_agent_open(void); +void polkit_agent_close(void); diff --git a/src/libshared/specifier.c b/src/libshared/specifier.c new file mode 100644 index 0000000000..303ddf0401 --- /dev/null +++ b/src/libshared/specifier.c @@ -0,0 +1,188 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "hostname-util.h" +#include "macro.h" +#include "specifier.h" +#include "string-util.h" + +/* + * Generic infrastructure for replacing %x style specifiers in + * strings. Will call a callback for each replacement. + * + */ + +int specifier_printf(const char *text, const Specifier table[], void *userdata, char **_ret) { + char *ret, *t; + const char *f; + bool percent = false; + size_t l; + int r; + + assert(text); + assert(table); + + l = strlen(text); + ret = new(char, l+1); + if (!ret) + return -ENOMEM; + + t = ret; + + for (f = text; *f; f++, l--) { + + if (percent) { + if (*f == '%') + *(t++) = '%'; + else { + const Specifier *i; + + for (i = table; i->specifier; i++) + if (i->specifier == *f) + break; + + if (i->lookup) { + _cleanup_free_ char *w = NULL; + char *n; + size_t k, j; + + r = i->lookup(i->specifier, i->data, userdata, &w); + if (r < 0) { + free(ret); + return r; + } + + j = t - ret; + k = strlen(w); + + n = new(char, j + k + l + 1); + if (!n) { + free(ret); + return -ENOMEM; + } + + memcpy(n, ret, j); + memcpy(n + j, w, k); + + free(ret); + + ret = n; + t = n + j + k; + } else { + *(t++) = '%'; + *(t++) = *f; + } + } + + percent = false; + } else if (*f == '%') + percent = true; + else + *(t++) = *f; + } + + *t = 0; + *_ret = ret; + return 0; +} + +/* Generic handler for simple string replacements */ + +int specifier_string(char specifier, void *data, void *userdata, char **ret) { + char *n; + + n = strdup(strempty(data)); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +int specifier_machine_id(char specifier, void *data, void *userdata, char **ret) { + sd_id128_t id; + char *n; + int r; + + r = sd_id128_get_machine(&id); + if (r < 0) + return r; + + n = new(char, 33); + if (!n) + return -ENOMEM; + + *ret = sd_id128_to_string(id, n); + return 0; +} + +int specifier_boot_id(char specifier, void *data, void *userdata, char **ret) { + sd_id128_t id; + char *n; + int r; + + r = sd_id128_get_boot(&id); + if (r < 0) + return r; + + n = new(char, 33); + if (!n) + return -ENOMEM; + + *ret = sd_id128_to_string(id, n); + return 0; +} + +int specifier_host_name(char specifier, void *data, void *userdata, char **ret) { + char *n; + + n = gethostname_malloc(); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +int specifier_kernel_release(char specifier, void *data, void *userdata, char **ret) { + struct utsname uts; + char *n; + int r; + + r = uname(&uts); + if (r < 0) + return -errno; + + n = strdup(uts.release); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} diff --git a/src/libshared/specifier.h b/src/libshared/specifier.h new file mode 100644 index 0000000000..6b1623ee61 --- /dev/null +++ b/src/libshared/specifier.h @@ -0,0 +1,37 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef int (*SpecifierCallback)(char specifier, void *data, void *userdata, char **ret); + +typedef struct Specifier { + const char specifier; + const SpecifierCallback lookup; + void *data; +} Specifier; + +int specifier_printf(const char *text, const Specifier table[], void *userdata, char **ret); + +int specifier_string(char specifier, void *data, void *userdata, char **ret); + +int specifier_machine_id(char specifier, void *data, void *userdata, char **ret); +int specifier_boot_id(char specifier, void *data, void *userdata, char **ret); +int specifier_host_name(char specifier, void *data, void *userdata, char **ret); +int specifier_kernel_release(char specifier, void *data, void *userdata, char **ret); diff --git a/src/libshared/switch-root.c b/src/libshared/switch-root.c new file mode 100644 index 0000000000..47d3a5a1fa --- /dev/null +++ b/src/libshared/switch-root.c @@ -0,0 +1,156 @@ +/*** + This file is part of systemd. + + Copyright 2012 Harald Hoyer, 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 +#include +#include +#include +#include +#include +#include + +#include "base-filesystem.h" +#include "fd-util.h" +#include "log.h" +#include "missing.h" +#include "mkdir.h" +#include "path-util.h" +#include "rm-rf.h" +#include "stdio-util.h" +#include "string-util.h" +#include "switch-root.h" +#include "user-util.h" +#include "util.h" + +int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, unsigned long mountflags) { + + /* Don't try to unmount/move the old "/", there's no way to do it. */ + static const char move_mounts[] = + "/dev\0" + "/proc\0" + "/sys\0" + "/run\0"; + + _cleanup_close_ int old_root_fd = -1; + struct stat new_root_stat; + bool old_root_remove; + const char *i, *temporary_old_root; + + if (path_equal(new_root, "/")) + return 0; + + temporary_old_root = strjoina(new_root, oldroot); + mkdir_p_label(temporary_old_root, 0755); + + old_root_remove = in_initrd(); + + if (stat(new_root, &new_root_stat) < 0) + return log_error_errno(errno, "Failed to stat directory %s: %m", new_root); + + /* Work-around for kernel design: the kernel refuses switching + * root if any file systems are mounted MS_SHARED. Hence + * remount them MS_PRIVATE here as a work-around. + * + * https://bugzilla.redhat.com/show_bug.cgi?id=847418 */ + if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0) + log_warning_errno(errno, "Failed to make \"/\" private mount: %m"); + + NULSTR_FOREACH(i, move_mounts) { + char new_mount[PATH_MAX]; + struct stat sb; + + xsprintf(new_mount, "%s%s", new_root, i); + + mkdir_p_label(new_mount, 0755); + + if ((stat(new_mount, &sb) < 0) || + sb.st_dev != new_root_stat.st_dev) { + + /* Mount point seems to be mounted already or + * stat failed. Unmount the old mount + * point. */ + if (umount2(i, MNT_DETACH) < 0) + log_warning_errno(errno, "Failed to unmount %s: %m", i); + continue; + } + + if (mount(i, new_mount, NULL, mountflags, NULL) < 0) { + if (mountflags & MS_MOVE) { + log_error_errno(errno, "Failed to move mount %s to %s, forcing unmount: %m", i, new_mount); + + if (umount2(i, MNT_FORCE) < 0) + log_warning_errno(errno, "Failed to unmount %s: %m", i); + } + if (mountflags & MS_BIND) + log_error_errno(errno, "Failed to bind mount %s to %s: %m", i, new_mount); + + } + } + + /* Do not fail, if base_filesystem_create() fails. Not all + * switch roots are like base_filesystem_create() wants them + * to look like. They might even boot, if they are RO and + * don't have the FS layout. Just ignore the error and + * switch_root() nevertheless. */ + (void) base_filesystem_create(new_root, UID_INVALID, GID_INVALID); + + if (chdir(new_root) < 0) + return log_error_errno(errno, "Failed to change directory to %s: %m", new_root); + + if (old_root_remove) { + old_root_fd = open("/", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY|O_DIRECTORY); + if (old_root_fd < 0) + log_warning_errno(errno, "Failed to open root directory: %m"); + } + + /* We first try a pivot_root() so that we can umount the old + * root dir. In many cases (i.e. where rootfs is /), that's + * not possible however, and hence we simply overmount root */ + if (pivot_root(new_root, temporary_old_root) >= 0) { + + /* Immediately get rid of the old root, if detach_oldroot is set. + * Since we are running off it we need to do this lazily. */ + if (detach_oldroot && umount2(oldroot, MNT_DETACH) < 0) + log_error_errno(errno, "Failed to lazily umount old root dir %s, %s: %m", + oldroot, + errno == ENOENT ? "ignoring" : "leaving it around"); + + } else if (mount(new_root, "/", NULL, MS_MOVE, NULL) < 0) + return log_error_errno(errno, "Failed to mount moving %s to /: %m", new_root); + + if (chroot(".") < 0) + return log_error_errno(errno, "Failed to change root: %m"); + + if (chdir("/") < 0) + return log_error_errno(errno, "Failed to change directory: %m"); + + if (old_root_fd >= 0) { + struct stat rb; + + if (fstat(old_root_fd, &rb) < 0) + log_warning_errno(errno, "Failed to stat old root directory, leaving: %m"); + else { + (void) rm_rf_children(old_root_fd, 0, &rb); + old_root_fd = -1; + } + } + + return 0; +} diff --git a/src/libshared/switch-root.h b/src/libshared/switch-root.h new file mode 100644 index 0000000000..a7a080b3e8 --- /dev/null +++ b/src/libshared/switch-root.h @@ -0,0 +1,23 @@ +#pragma once + +#include +/*** + This file is part of systemd. + + Copyright 2012 Harald Hoyer, 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 . +***/ + +int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, unsigned long mountflags); diff --git a/src/libshared/sysctl-util.c b/src/libshared/sysctl-util.c new file mode 100644 index 0000000000..e1ccb3294c --- /dev/null +++ b/src/libshared/sysctl-util.c @@ -0,0 +1,73 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 + +#include "fileio.h" +#include "log.h" +#include "macro.h" +#include "string-util.h" +#include "sysctl-util.h" + +char *sysctl_normalize(char *s) { + char *n; + + n = strpbrk(s, "/."); + /* If the first separator is a slash, the path is + * assumed to be normalized and slashes remain slashes + * and dots remains dots. */ + if (!n || *n == '/') + return s; + + /* Otherwise, dots become slashes and slashes become + * dots. Fun. */ + while (n) { + if (*n == '.') + *n = '/'; + else + *n = '.'; + + n = strpbrk(n + 1, "/."); + } + + return s; +} + +int sysctl_write(const char *property, const char *value) { + char *p; + + assert(property); + assert(value); + + log_debug("Setting '%s' to '%s'", property, value); + + p = strjoina("/proc/sys/", property); + return write_string_file(p, value, 0); +} + +int sysctl_read(const char *property, char **content) { + char *p; + + assert(property); + assert(content); + + p = strjoina("/proc/sys/", property); + return read_full_file(p, content, NULL); +} diff --git a/src/libshared/sysctl-util.h b/src/libshared/sysctl-util.h new file mode 100644 index 0000000000..2decb39f58 --- /dev/null +++ b/src/libshared/sysctl-util.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +char *sysctl_normalize(char *s); +int sysctl_read(const char *property, char **value); +int sysctl_write(const char *property, const char *value); + diff --git a/src/libshared/test-local-addresses.c b/src/libshared/test-local-addresses.c new file mode 100644 index 0000000000..e0e28cc0cc --- /dev/null +++ b/src/libshared/test-local-addresses.c @@ -0,0 +1,56 @@ +/*** + 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 "af-list.h" +#include "alloc-util.h" +#include "in-addr-util.h" +#include "local-addresses.h" + +static void print_local_addresses(struct local_address *a, unsigned n) { + unsigned i; + + for (i = 0; i < n; i++) { + _cleanup_free_ char *b = NULL; + + assert_se(in_addr_to_string(a[i].family, &a[i].address, &b) >= 0); + printf("%s if%i scope=%i metric=%u address=%s\n", af_to_name(a[i].family), a[i].ifindex, a[i].scope, a[i].metric, b); + } +} + +int main(int argc, char *argv[]) { + struct local_address *a; + int n; + + a = NULL; + n = local_addresses(NULL, 0, AF_UNSPEC, &a); + assert_se(n >= 0); + + printf("Local Addresses:\n"); + print_local_addresses(a, (unsigned) n); + a = mfree(a); + + n = local_gateways(NULL, 0, AF_UNSPEC, &a); + assert_se(n >= 0); + + printf("Local Gateways:\n"); + print_local_addresses(a, (unsigned) n); + free(a); + + return 0; +} diff --git a/src/libshared/test-tables.h b/src/libshared/test-tables.h new file mode 100644 index 0000000000..228e510104 --- /dev/null +++ b/src/libshared/test-tables.h @@ -0,0 +1,60 @@ +/*** + This file is part of systemd + + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + 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 + +typedef const char* (*lookup_t)(int); +typedef int (*reverse_t)(const char*); + +static inline void _test_table(const char *name, + lookup_t lookup, + reverse_t reverse, + int size, + bool sparse) { + int i, boring = 0; + + for (i = -1; i < size + 1; i++) { + const char* val = lookup(i); + int rev; + + if (val) { + rev = reverse(val); + boring = 0; + } else { + rev = reverse("--no-such--value----"); + boring += i >= 0; + } + + if (boring < 1 || i == size) + printf("%s: %d → %s → %d\n", name, i, val, rev); + else if (boring == 1) + printf("%*s ...\n", (int) strlen(name), ""); + + assert_se(!(i >= 0 && i < size ? + sparse ? rev != i && rev != -1 : val == NULL || rev != i : + val != NULL || rev != -1)); + } +} + +#define test_table(lower, upper) \ + _test_table(STRINGIFY(lower), lower##_to_string, lower##_from_string, _##upper##_MAX, false) + +#define test_table_sparse(lower, upper) \ + _test_table(STRINGIFY(lower), lower##_to_string, lower##_from_string, _##upper##_MAX, true) diff --git a/src/libshared/tests.c b/src/libshared/tests.c new file mode 100644 index 0000000000..409116290d --- /dev/null +++ b/src/libshared/tests.c @@ -0,0 +1,33 @@ +/*** + This file is part of systemd. + + Copyright 2016 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 + +#include "tests.h" + +char* setup_fake_runtime_dir(void) { + char t[] = "/tmp/fake-xdg-runtime-XXXXXX", *p; + + assert_se(mkdtemp(t)); + assert_se(setenv("XDG_RUNTIME_DIR", t, 1) >= 0); + assert_se(p = strdup(t)); + + return p; +} diff --git a/src/libshared/tests.h b/src/libshared/tests.h new file mode 100644 index 0000000000..93f09013a1 --- /dev/null +++ b/src/libshared/tests.h @@ -0,0 +1,22 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 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 . +***/ + +char* setup_fake_runtime_dir(void); diff --git a/src/libshared/udev-util.h b/src/libshared/udev-util.h new file mode 100644 index 0000000000..ca0889f8a6 --- /dev/null +++ b/src/libshared/udev-util.h @@ -0,0 +1,44 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + 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 "udev.h" +#include "util.h" + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev*, udev_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_device*, udev_device_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_enumerate*, udev_enumerate_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_event*, udev_event_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_rules*, udev_rules_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_ctrl*, udev_ctrl_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_ctrl_connection*, udev_ctrl_connection_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_ctrl_msg*, udev_ctrl_msg_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_monitor*, udev_monitor_unref); + +#define _cleanup_udev_unref_ _cleanup_(udev_unrefp) +#define _cleanup_udev_device_unref_ _cleanup_(udev_device_unrefp) +#define _cleanup_udev_enumerate_unref_ _cleanup_(udev_enumerate_unrefp) +#define _cleanup_udev_event_unref_ _cleanup_(udev_event_unrefp) +#define _cleanup_udev_rules_unref_ _cleanup_(udev_rules_unrefp) +#define _cleanup_udev_ctrl_unref_ _cleanup_(udev_ctrl_unrefp) +#define _cleanup_udev_ctrl_connection_unref_ _cleanup_(udev_ctrl_connection_unrefp) +#define _cleanup_udev_ctrl_msg_unref_ _cleanup_(udev_ctrl_msg_unrefp) +#define _cleanup_udev_monitor_unref_ _cleanup_(udev_monitor_unrefp) +#define _cleanup_udev_list_cleanup_ _cleanup_(udev_list_cleanup) diff --git a/src/libshared/uid-range.c b/src/libshared/uid-range.c new file mode 100644 index 0000000000..b6ec474390 --- /dev/null +++ b/src/libshared/uid-range.c @@ -0,0 +1,208 @@ +/*** + 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 +#include + +#include "macro.h" +#include "uid-range.h" +#include "user-util.h" + +static bool uid_range_intersect(UidRange *range, uid_t start, uid_t nr) { + assert(range); + + return range->start <= start + nr && + range->start + range->nr >= start; +} + +static void uid_range_coalesce(UidRange **p, unsigned *n) { + unsigned i, j; + + assert(p); + assert(n); + + for (i = 0; i < *n; i++) { + for (j = i + 1; j < *n; j++) { + UidRange *x = (*p)+i, *y = (*p)+j; + + if (uid_range_intersect(x, y->start, y->nr)) { + uid_t begin, end; + + begin = MIN(x->start, y->start); + end = MAX(x->start + x->nr, y->start + y->nr); + + x->start = begin; + x->nr = end - begin; + + if (*n > j+1) + memmove(y, y+1, sizeof(UidRange) * (*n - j -1)); + + (*n)--; + j--; + } + } + } + +} + +static int uid_range_compare(const void *a, const void *b) { + const UidRange *x = a, *y = b; + + if (x->start < y->start) + return -1; + if (x->start > y->start) + return 1; + + if (x->nr < y->nr) + return -1; + if (x->nr > y->nr) + return 1; + + return 0; +} + +int uid_range_add(UidRange **p, unsigned *n, uid_t start, uid_t nr) { + bool found = false; + UidRange *x; + unsigned i; + + assert(p); + assert(n); + + if (nr <= 0) + return 0; + + for (i = 0; i < *n; i++) { + x = (*p) + i; + if (uid_range_intersect(x, start, nr)) { + found = true; + break; + } + } + + if (found) { + uid_t begin, end; + + begin = MIN(x->start, start); + end = MAX(x->start + x->nr, start + nr); + + x->start = begin; + x->nr = end - begin; + } else { + UidRange *t; + + t = realloc(*p, sizeof(UidRange) * (*n + 1)); + if (!t) + return -ENOMEM; + + *p = t; + x = t + ((*n) ++); + + x->start = start; + x->nr = nr; + } + + qsort(*p, *n, sizeof(UidRange), uid_range_compare); + uid_range_coalesce(p, n); + + return *n; +} + +int uid_range_add_str(UidRange **p, unsigned *n, const char *s) { + uid_t start, nr; + const char *t; + int r; + + assert(p); + assert(n); + assert(s); + + t = strchr(s, '-'); + if (t) { + char *b; + uid_t end; + + b = strndupa(s, t - s); + r = parse_uid(b, &start); + if (r < 0) + return r; + + r = parse_uid(t+1, &end); + if (r < 0) + return r; + + if (end < start) + return -EINVAL; + + nr = end - start + 1; + } else { + r = parse_uid(s, &start); + if (r < 0) + return r; + + nr = 1; + } + + return uid_range_add(p, n, start, nr); +} + +int uid_range_next_lower(const UidRange *p, unsigned n, uid_t *uid) { + uid_t closest = UID_INVALID, candidate; + unsigned i; + + assert(p); + assert(uid); + + candidate = *uid - 1; + + for (i = 0; i < n; i++) { + uid_t begin, end; + + begin = p[i].start; + end = p[i].start + p[i].nr - 1; + + if (candidate >= begin && candidate <= end) { + *uid = candidate; + return 1; + } + + if (end < candidate) + closest = end; + } + + if (closest == UID_INVALID) + return -EBUSY; + + *uid = closest; + return 1; +} + +bool uid_range_contains(const UidRange *p, unsigned n, uid_t uid) { + unsigned i; + + assert(p); + assert(uid); + + for (i = 0; i < n; i++) + if (uid >= p[i].start && uid < p[i].start + p[i].nr) + return true; + + return false; +} diff --git a/src/libshared/uid-range.h b/src/libshared/uid-range.h new file mode 100644 index 0000000000..4044eb4c9c --- /dev/null +++ b/src/libshared/uid-range.h @@ -0,0 +1,33 @@ +#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 +#include + +typedef struct UidRange { + uid_t start, nr; +} UidRange; + +int uid_range_add(UidRange **p, unsigned *n, uid_t start, uid_t nr); +int uid_range_add_str(UidRange **p, unsigned *n, const char *s); + +int uid_range_next_lower(const UidRange *p, unsigned n, uid_t *uid); +bool uid_range_contains(const UidRange *p, unsigned n, uid_t uid); diff --git a/src/libshared/utmp-wtmp.c b/src/libshared/utmp-wtmp.c new file mode 100644 index 0000000000..9750dcd817 --- /dev/null +++ b/src/libshared/utmp-wtmp.c @@ -0,0 +1,445 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "hostname-util.h" +#include "macro.h" +#include "path-util.h" +#include "string-util.h" +#include "terminal-util.h" +#include "time-util.h" +#include "user-util.h" +#include "util.h" +#include "utmp-wtmp.h" + +int utmp_get_runlevel(int *runlevel, int *previous) { + struct utmpx *found, lookup = { .ut_type = RUN_LVL }; + int r; + const char *e; + + assert(runlevel); + + /* If these values are set in the environment this takes + * precedence. Presumably, sysvinit does this to work around a + * race condition that would otherwise exist where we'd always + * go to disk and hence might read runlevel data that might be + * very new and does not apply to the current script being + * executed. */ + + e = getenv("RUNLEVEL"); + if (e && e[0] > 0) { + *runlevel = e[0]; + + if (previous) { + /* $PREVLEVEL seems to be an Upstart thing */ + + e = getenv("PREVLEVEL"); + if (e && e[0] > 0) + *previous = e[0]; + else + *previous = 0; + } + + return 0; + } + + if (utmpxname(_PATH_UTMPX) < 0) + return -errno; + + setutxent(); + + found = getutxid(&lookup); + if (!found) + r = -errno; + else { + int a, b; + + a = found->ut_pid & 0xFF; + b = (found->ut_pid >> 8) & 0xFF; + + *runlevel = a; + if (previous) + *previous = b; + + r = 0; + } + + endutxent(); + + return r; +} + +static void init_timestamp(struct utmpx *store, usec_t t) { + assert(store); + + if (t <= 0) + t = now(CLOCK_REALTIME); + + store->ut_tv.tv_sec = t / USEC_PER_SEC; + store->ut_tv.tv_usec = t % USEC_PER_SEC; +} + +static void init_entry(struct utmpx *store, usec_t t) { + struct utsname uts = {}; + + assert(store); + + init_timestamp(store, t); + + if (uname(&uts) >= 0) + strncpy(store->ut_host, uts.release, sizeof(store->ut_host)); + + strncpy(store->ut_line, "~", sizeof(store->ut_line)); /* or ~~ ? */ + strncpy(store->ut_id, "~~", sizeof(store->ut_id)); +} + +static int write_entry_utmp(const struct utmpx *store) { + int r; + + assert(store); + + /* utmp is similar to wtmp, but there is only one entry for + * each entry type resp. user; i.e. basically a key/value + * table. */ + + if (utmpxname(_PATH_UTMPX) < 0) + return -errno; + + setutxent(); + + if (!pututxline(store)) + r = -errno; + else + r = 0; + + endutxent(); + + return r; +} + +static int write_entry_wtmp(const struct utmpx *store) { + assert(store); + + /* wtmp is a simple append-only file where each entry is + simply appended to the end; i.e. basically a log. */ + + errno = 0; + updwtmpx(_PATH_WTMPX, store); + return -errno; +} + +static int write_utmp_wtmp(const struct utmpx *store_utmp, const struct utmpx *store_wtmp) { + int r, s; + + r = write_entry_utmp(store_utmp); + s = write_entry_wtmp(store_wtmp); + + if (r >= 0) + r = s; + + /* If utmp/wtmp have been disabled, that's a good thing, hence + * ignore the errors */ + if (r == -ENOENT) + r = 0; + + return r; +} + +static int write_entry_both(const struct utmpx *store) { + return write_utmp_wtmp(store, store); +} + +int utmp_put_shutdown(void) { + struct utmpx store = {}; + + init_entry(&store, 0); + + store.ut_type = RUN_LVL; + strncpy(store.ut_user, "shutdown", sizeof(store.ut_user)); + + return write_entry_both(&store); +} + +int utmp_put_reboot(usec_t t) { + struct utmpx store = {}; + + init_entry(&store, t); + + store.ut_type = BOOT_TIME; + strncpy(store.ut_user, "reboot", sizeof(store.ut_user)); + + return write_entry_both(&store); +} + +_pure_ static const char *sanitize_id(const char *id) { + size_t l; + + assert(id); + l = strlen(id); + + if (l <= sizeof(((struct utmpx*) NULL)->ut_id)) + return id; + + return id + l - sizeof(((struct utmpx*) NULL)->ut_id); +} + +int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user) { + struct utmpx store = { + .ut_type = INIT_PROCESS, + .ut_pid = pid, + .ut_session = sid, + }; + int r; + + assert(id); + + init_timestamp(&store, 0); + + /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */ + strncpy(store.ut_id, sanitize_id(id), sizeof(store.ut_id)); + + if (line) + strncpy(store.ut_line, basename(line), sizeof(store.ut_line)); + + r = write_entry_both(&store); + if (r < 0) + return r; + + if (ut_type == LOGIN_PROCESS || ut_type == USER_PROCESS) { + store.ut_type = LOGIN_PROCESS; + r = write_entry_both(&store); + if (r < 0) + return r; + } + + if (ut_type == USER_PROCESS) { + store.ut_type = USER_PROCESS; + strncpy(store.ut_user, user, sizeof(store.ut_user)-1); + r = write_entry_both(&store); + if (r < 0) + return r; + } + + return 0; +} + +int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) { + struct utmpx lookup = { + .ut_type = INIT_PROCESS /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */ + }, store, store_wtmp, *found; + + assert(id); + + setutxent(); + + /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */ + strncpy(lookup.ut_id, sanitize_id(id), sizeof(lookup.ut_id)); + + found = getutxid(&lookup); + if (!found) + return 0; + + if (found->ut_pid != pid) + return 0; + + memcpy(&store, found, sizeof(store)); + store.ut_type = DEAD_PROCESS; + store.ut_exit.e_termination = code; + store.ut_exit.e_exit = status; + + zero(store.ut_user); + zero(store.ut_host); + zero(store.ut_tv); + + memcpy(&store_wtmp, &store, sizeof(store_wtmp)); + /* wtmp wants the current time */ + init_timestamp(&store_wtmp, 0); + + return write_utmp_wtmp(&store, &store_wtmp); +} + + +int utmp_put_runlevel(int runlevel, int previous) { + struct utmpx store = {}; + int r; + + assert(runlevel > 0); + + if (previous <= 0) { + /* Find the old runlevel automatically */ + + r = utmp_get_runlevel(&previous, NULL); + if (r < 0) { + if (r != -ESRCH) + return r; + + previous = 0; + } + } + + if (previous == runlevel) + return 0; + + init_entry(&store, 0); + + store.ut_type = RUN_LVL; + store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8); + strncpy(store.ut_user, "runlevel", sizeof(store.ut_user)); + + return write_entry_both(&store); +} + +#define TIMEOUT_MSEC 50 + +static int write_to_terminal(const char *tty, const char *message) { + _cleanup_close_ int fd = -1; + const char *p; + size_t left; + usec_t end; + + assert(tty); + assert(message); + + fd = open(tty, O_WRONLY|O_NDELAY|O_NOCTTY|O_CLOEXEC); + if (fd < 0 || !isatty(fd)) + return -errno; + + p = message; + left = strlen(message); + + end = now(CLOCK_MONOTONIC) + TIMEOUT_MSEC*USEC_PER_MSEC; + + while (left > 0) { + ssize_t n; + struct pollfd pollfd = { + .fd = fd, + .events = POLLOUT, + }; + usec_t t; + int k; + + t = now(CLOCK_MONOTONIC); + + if (t >= end) + return -ETIME; + + k = poll(&pollfd, 1, (end - t) / USEC_PER_MSEC); + if (k < 0) + return -errno; + + if (k == 0) + return -ETIME; + + n = write(fd, p, left); + if (n < 0) { + if (errno == EAGAIN) + continue; + + return -errno; + } + + assert((size_t) n <= left); + + p += n; + left -= n; + } + + return 0; +} + +int utmp_wall( + const char *message, + const char *username, + const char *origin_tty, + bool (*match_tty)(const char *tty, void *userdata), + void *userdata) { + + _cleanup_free_ char *text = NULL, *hn = NULL, *un = NULL, *stdin_tty = NULL; + char date[FORMAT_TIMESTAMP_MAX]; + struct utmpx *u; + int r; + + hn = gethostname_malloc(); + if (!hn) + return -ENOMEM; + if (!username) { + un = getlogname_malloc(); + if (!un) + return -ENOMEM; + } + + if (!origin_tty) { + getttyname_harder(STDIN_FILENO, &stdin_tty); + origin_tty = stdin_tty; + } + + if (asprintf(&text, + "\a\r\n" + "Broadcast message from %s@%s%s%s (%s):\r\n\r\n" + "%s\r\n\r\n", + un ?: username, hn, + origin_tty ? " on " : "", strempty(origin_tty), + format_timestamp(date, sizeof(date), now(CLOCK_REALTIME)), + message) < 0) + return -ENOMEM; + + setutxent(); + + r = 0; + + while ((u = getutxent())) { + _cleanup_free_ char *buf = NULL; + const char *path; + int q; + + if (u->ut_type != USER_PROCESS || u->ut_user[0] == 0) + continue; + + /* this access is fine, because strlen("/dev/") << 32 (UT_LINESIZE) */ + if (path_startswith(u->ut_line, "/dev/")) + path = u->ut_line; + else { + if (asprintf(&buf, "/dev/%.*s", (int) sizeof(u->ut_line), u->ut_line) < 0) + return -ENOMEM; + + path = buf; + } + + if (!match_tty || match_tty(path, userdata)) { + q = write_to_terminal(path, text); + if (q < 0) + r = q; + } + } + + return r; +} diff --git a/src/libshared/utmp-wtmp.h b/src/libshared/utmp-wtmp.h new file mode 100644 index 0000000000..438e270a26 --- /dev/null +++ b/src/libshared/utmp-wtmp.h @@ -0,0 +1,74 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 + +#include "time-util.h" +#include "util.h" + +#ifdef HAVE_UTMP +int utmp_get_runlevel(int *runlevel, int *previous); + +int utmp_put_shutdown(void); +int utmp_put_reboot(usec_t timestamp); +int utmp_put_runlevel(int runlevel, int previous); + +int utmp_put_dead_process(const char *id, pid_t pid, int code, int status); +int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user); + +int utmp_wall( + const char *message, + const char *username, + const char *origin_tty, + bool (*match_tty)(const char *tty, void *userdata), + void *userdata); + +#else /* HAVE_UTMP */ + +static inline int utmp_get_runlevel(int *runlevel, int *previous) { + return -ESRCH; +} +static inline int utmp_put_shutdown(void) { + return 0; +} +static inline int utmp_put_reboot(usec_t timestamp) { + return 0; +} +static inline int utmp_put_runlevel(int runlevel, int previous) { + return 0; +} +static inline int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) { + return 0; +} +static inline int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user) { + return 0; +} +static inline int utmp_wall( + const char *message, + const char *username, + const char *origin_tty, + bool (*match_tty)(const char *tty, void *userdata), + void *userdata) { + return 0; +} + +#endif /* HAVE_UTMP */ diff --git a/src/libshared/watchdog.c b/src/libshared/watchdog.c new file mode 100644 index 0000000000..4f3e0125f3 --- /dev/null +++ b/src/libshared/watchdog.c @@ -0,0 +1,164 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 +#include +#include +#include +#include + +#include "fd-util.h" +#include "log.h" +#include "time-util.h" +#include "watchdog.h" + +static int watchdog_fd = -1; +static usec_t watchdog_timeout = USEC_INFINITY; + +static int update_timeout(void) { + int r; + + if (watchdog_fd < 0) + return 0; + + if (watchdog_timeout == USEC_INFINITY) + return 0; + else if (watchdog_timeout == 0) { + int flags; + + flags = WDIOS_DISABLECARD; + r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); + if (r < 0) + return log_warning_errno(errno, "Failed to disable hardware watchdog: %m"); + } else { + int sec, flags; + char buf[FORMAT_TIMESPAN_MAX]; + + sec = (int) ((watchdog_timeout + USEC_PER_SEC - 1) / USEC_PER_SEC); + r = ioctl(watchdog_fd, WDIOC_SETTIMEOUT, &sec); + if (r < 0) + return log_warning_errno(errno, "Failed to set timeout to %is: %m", sec); + + watchdog_timeout = (usec_t) sec * USEC_PER_SEC; + log_info("Set hardware watchdog to %s.", format_timespan(buf, sizeof(buf), watchdog_timeout, 0)); + + flags = WDIOS_ENABLECARD; + r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); + if (r < 0) { + /* ENOTTY means the watchdog is always enabled so we're fine */ + log_full(errno == ENOTTY ? LOG_DEBUG : LOG_WARNING, + "Failed to enable hardware watchdog: %m"); + if (errno != ENOTTY) + return -errno; + } + + r = ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0); + if (r < 0) + return log_warning_errno(errno, "Failed to ping hardware watchdog: %m"); + } + + return 0; +} + +static int open_watchdog(void) { + struct watchdog_info ident; + + if (watchdog_fd >= 0) + return 0; + + watchdog_fd = open("/dev/watchdog", O_WRONLY|O_CLOEXEC); + if (watchdog_fd < 0) + return -errno; + + if (ioctl(watchdog_fd, WDIOC_GETSUPPORT, &ident) >= 0) + log_info("Hardware watchdog '%s', version %x", + ident.identity, + ident.firmware_version); + + return update_timeout(); +} + +int watchdog_set_timeout(usec_t *usec) { + int r; + + watchdog_timeout = *usec; + + /* If we didn't open the watchdog yet and didn't get any + * explicit timeout value set, don't do anything */ + if (watchdog_fd < 0 && watchdog_timeout == USEC_INFINITY) + return 0; + + if (watchdog_fd < 0) + r = open_watchdog(); + else + r = update_timeout(); + + *usec = watchdog_timeout; + + return r; +} + +int watchdog_ping(void) { + int r; + + if (watchdog_fd < 0) { + r = open_watchdog(); + if (r < 0) + return r; + } + + r = ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0); + if (r < 0) + return log_warning_errno(errno, "Failed to ping hardware watchdog: %m"); + + return 0; +} + +void watchdog_close(bool disarm) { + int r; + + if (watchdog_fd < 0) + return; + + if (disarm) { + int flags; + + /* Explicitly disarm it */ + flags = WDIOS_DISABLECARD; + r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); + if (r < 0) + log_warning_errno(errno, "Failed to disable hardware watchdog: %m"); + + /* To be sure, use magic close logic, too */ + for (;;) { + static const char v = 'V'; + + if (write(watchdog_fd, &v, 1) > 0) + break; + + if (errno != EINTR) { + log_error_errno(errno, "Failed to disarm watchdog timer: %m"); + break; + } + } + } + + watchdog_fd = safe_close(watchdog_fd); +} diff --git a/src/libshared/watchdog.h b/src/libshared/watchdog.h new file mode 100644 index 0000000000..f6ec178ea1 --- /dev/null +++ b/src/libshared/watchdog.h @@ -0,0 +1,29 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 "time-util.h" +#include "util.h" + +int watchdog_set_timeout(usec_t *usec); +int watchdog_ping(void); +void watchdog_close(bool disarm); diff --git a/src/libsystemd-network/Makefile b/src/libsystemd-network/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/libsystemd-network/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/libsystemd-network/Makefile b/src/libsystemd-network/Makefile new file mode 100644 index 0000000000..6b16906c17 --- /dev/null +++ b/src/libsystemd-network/Makefile @@ -0,0 +1,173 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +noinst_LTLIBRARIES += \ + libsystemd-network.la + +libsystemd_network_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(KMOD_CFLAGS) + +libsystemd_network_la_SOURCES = \ + src/systemd/sd-dhcp-client.h \ + src/systemd/sd-dhcp-server.h \ + src/systemd/sd-dhcp-lease.h \ + src/systemd/sd-ipv4ll.h \ + src/systemd/sd-ipv4acd.h \ + src/systemd/sd-ndisc.h \ + src/systemd/sd-dhcp6-client.h \ + src/systemd/sd-dhcp6-lease.h \ + src/systemd/sd-lldp.h \ + src/libsystemd-network/sd-dhcp-client.c \ + src/libsystemd-network/sd-dhcp-server.c \ + src/libsystemd-network/dhcp-network.c \ + src/libsystemd-network/dhcp-option.c \ + src/libsystemd-network/dhcp-packet.c \ + src/libsystemd-network/dhcp-internal.h \ + src/libsystemd-network/dhcp-server-internal.h \ + src/libsystemd-network/dhcp-protocol.h \ + src/libsystemd-network/dhcp-lease-internal.h \ + src/libsystemd-network/sd-dhcp-lease.c \ + src/libsystemd-network/sd-ipv4ll.c \ + src/libsystemd-network/sd-ipv4acd.c \ + src/libsystemd-network/arp-util.h \ + src/libsystemd-network/arp-util.c \ + src/libsystemd-network/network-internal.c \ + src/libsystemd-network/network-internal.h \ + src/libsystemd-network/sd-ndisc.c \ + src/libsystemd-network/icmp6-util.h \ + src/libsystemd-network/icmp6-util.c \ + src/libsystemd-network/sd-dhcp6-client.c \ + src/libsystemd-network/dhcp6-internal.h \ + src/libsystemd-network/dhcp6-protocol.h \ + src/libsystemd-network/dhcp6-network.c \ + src/libsystemd-network/dhcp6-option.c \ + src/libsystemd-network/dhcp6-lease-internal.h \ + src/libsystemd-network/sd-dhcp6-lease.c \ + src/libsystemd-network/dhcp-identifier.h \ + src/libsystemd-network/dhcp-identifier.c \ + src/libsystemd-network/lldp-internal.h \ + src/libsystemd-network/lldp-network.h \ + src/libsystemd-network/lldp-network.c \ + src/libsystemd-network/lldp-neighbor.h \ + src/libsystemd-network/lldp-neighbor.c \ + src/libsystemd-network/sd-lldp.c + +libsystemd_network_la_LIBADD = \ + $(KMOD_LIBS) + +test_dhcp_option_SOURCES = \ + src/libsystemd-network/dhcp-protocol.h \ + src/libsystemd-network/dhcp-internal.h \ + src/libsystemd-network/test-dhcp-option.c + +test_dhcp_option_LDADD = \ + libsystemd-network.la \ + libshared.la + +test_dhcp_client_SOURCES = \ + src/systemd/sd-dhcp-client.h \ + src/libsystemd-network/dhcp-protocol.h \ + src/libsystemd-network/dhcp-internal.h \ + src/libsystemd-network/test-dhcp-client.c + +test_dhcp_client_LDADD = \ + libsystemd-network.la \ + libshared.la + +test_dhcp_server_SOURCES = \ + src/libsystemd-network/test-dhcp-server.c + +test_dhcp_server_LDADD = \ + libsystemd-network.la \ + libshared.la + +test_ipv4ll_SOURCES = \ + src/systemd/sd-ipv4ll.h \ + src/libsystemd-network/arp-util.h \ + src/libsystemd-network/test-ipv4ll.c + +test_ipv4ll_LDADD = \ + libsystemd-network.la \ + libshared.la + +test_ipv4ll_manual_SOURCES = \ + src/systemd/sd-ipv4ll.h \ + src/libsystemd-network/test-ipv4ll-manual.c + +test_ipv4ll_manual_LDADD = \ + libsystemd-network.la \ + libshared.la + +test_acd_SOURCES = \ + src/systemd/sd-ipv4acd.h \ + src/libsystemd-network/test-acd.c + +test_acd_LDADD = \ + libsystemd-network.la \ + libshared.la + +test_ndisc_rs_SOURCES = \ + src/systemd/sd-dhcp6-client.h \ + src/systemd/sd-ndisc.h \ + src/libsystemd-network/icmp6-util.h \ + src/libsystemd-network/test-ndisc-rs.c \ + src/libsystemd-network/dhcp-identifier.h \ + src/libsystemd-network/dhcp-identifier.c + +test_ndisc_rs_LDADD = \ + libsystemd-network.la \ + libudev.la \ + libshared.la + +test_dhcp6_client_SOURCES = \ + src/systemd/sd-dhcp6-client.h \ + src/libsystemd-network/dhcp6-internal.h \ + src/libsystemd-network/test-dhcp6-client.c \ + src/libsystemd-network/dhcp-identifier.h \ + src/libsystemd-network/dhcp-identifier.c + +test_dhcp6_client_LDADD = \ + libsystemd-network.la \ + libudev.la \ + libshared.la + +test_lldp_SOURCES = \ + src/libsystemd-network/test-lldp.c + +test_lldp_LDADD = \ + libsystemd-network.la \ + libshared.la + +tests += \ + test-dhcp-option \ + test-dhcp-client \ + test-dhcp-server \ + test-ipv4ll \ + test-ndisc-rs \ + test-dhcp6-client \ + test-lldp + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libsystemd-network/dhcp-identifier.c b/src/libsystemd-network/dhcp-identifier.c index a21efc4d06..906d960171 100644 --- a/src/libsystemd-network/dhcp-identifier.c +++ b/src/libsystemd-network/dhcp-identifier.c @@ -18,7 +18,7 @@ ***/ #include "libudev.h" -#include "sd-id128.h" +#include #include "dhcp-identifier.h" #include "dhcp6-protocol.h" diff --git a/src/libsystemd-network/dhcp-identifier.h b/src/libsystemd-network/dhcp-identifier.h index 1cc0f9fb71..802a0d6bc2 100644 --- a/src/libsystemd-network/dhcp-identifier.h +++ b/src/libsystemd-network/dhcp-identifier.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-id128.h" +#include #include "macro.h" #include "sparse-endian.h" diff --git a/src/libsystemd-network/dhcp-internal.h b/src/libsystemd-network/dhcp-internal.h index 4662b0d847..dda4c13919 100644 --- a/src/libsystemd-network/dhcp-internal.h +++ b/src/libsystemd-network/dhcp-internal.h @@ -25,7 +25,7 @@ #include #include -#include "sd-dhcp-client.h" +#include #include "dhcp-protocol.h" #include "socket-util.h" diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h index 82cae2300a..646e612cee 100644 --- a/src/libsystemd-network/dhcp-lease-internal.h +++ b/src/libsystemd-network/dhcp-lease-internal.h @@ -23,7 +23,7 @@ #include #include -#include "sd-dhcp-client.h" +#include #include "dhcp-protocol.h" #include "list.h" diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h index 0c76956fad..7ba1e72155 100644 --- a/src/libsystemd-network/dhcp-server-internal.h +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -20,8 +20,8 @@ along with systemd; If not, see . ***/ -#include "sd-dhcp-server.h" -#include "sd-event.h" +#include +#include #include "dhcp-internal.h" #include "hashmap.h" diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index 945c3b9721..4228053ade 100644 --- a/src/libsystemd-network/dhcp6-internal.h +++ b/src/libsystemd-network/dhcp6-internal.h @@ -22,7 +22,7 @@ #include #include -#include "sd-event.h" +#include #include "list.h" #include "macro.h" diff --git a/src/libsystemd-network/dhcp6-lease-internal.h b/src/libsystemd-network/dhcp6-lease-internal.h index 14e708ef63..8e9a6f8e8c 100644 --- a/src/libsystemd-network/dhcp6-lease-internal.h +++ b/src/libsystemd-network/dhcp6-lease-internal.h @@ -22,7 +22,7 @@ #include -#include "sd-dhcp6-lease.h" +#include #include "dhcp6-internal.h" diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index 5462e03476..0ae381ad22 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -21,7 +21,7 @@ #include #include -#include "sd-dhcp6-client.h" +#include #include "alloc-util.h" #include "dhcp6-internal.h" diff --git a/src/libsystemd-network/lldp-internal.h b/src/libsystemd-network/lldp-internal.h index 7592bc4305..a6be995e3b 100644 --- a/src/libsystemd-network/lldp-internal.h +++ b/src/libsystemd-network/lldp-internal.h @@ -20,8 +20,8 @@ along with systemd; If not, see . ***/ -#include "sd-event.h" -#include "sd-lldp.h" +#include +#include #include "hashmap.h" #include "log.h" diff --git a/src/libsystemd-network/lldp-neighbor.h b/src/libsystemd-network/lldp-neighbor.h index f203bfa604..050159ab73 100644 --- a/src/libsystemd-network/lldp-neighbor.h +++ b/src/libsystemd-network/lldp-neighbor.h @@ -23,7 +23,7 @@ #include #include -#include "sd-lldp.h" +#include #include "hash-funcs.h" #include "lldp-internal.h" diff --git a/src/libsystemd-network/lldp-network.h b/src/libsystemd-network/lldp-network.h index c4cf8c79f1..43ed54b3b2 100644 --- a/src/libsystemd-network/lldp-network.h +++ b/src/libsystemd-network/lldp-network.h @@ -20,6 +20,6 @@ along with systemd; If not, see . ***/ -#include "sd-event.h" +#include int lldp_network_bind_raw_socket(int ifindex); diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c index 046b0f9393..d57baf8fff 100644 --- a/src/libsystemd-network/network-internal.c +++ b/src/libsystemd-network/network-internal.c @@ -21,7 +21,7 @@ #include #include -#include "sd-ndisc.h" +#include #include "alloc-util.h" #include "condition.h" diff --git a/src/libsystemd-network/network-internal.h b/src/libsystemd-network/network-internal.h index 5bcd577167..1cafb0747f 100644 --- a/src/libsystemd-network/network-internal.h +++ b/src/libsystemd-network/network-internal.h @@ -21,7 +21,7 @@ #include -#include "sd-dhcp-lease.h" +#include #include "condition.h" #include "udev.h" diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index ad79c6cc2c..193f31880f 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -26,7 +26,7 @@ #include #include -#include "sd-dhcp-client.h" +#include #include "alloc-util.h" #include "async.h" diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index ef50ed17a1..d4c680d485 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -24,7 +24,7 @@ #include #include -#include "sd-dhcp-lease.h" +#include #include "alloc-util.h" #include "dhcp-lease-internal.h" diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index fb335337c4..5e8b4e4823 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -20,7 +20,7 @@ #include -#include "sd-dhcp-server.h" +#include #include "alloc-util.h" #include "dhcp-internal.h" diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 05972e01c9..2760b039ba 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -22,7 +22,7 @@ #include #include -#include "sd-dhcp6-client.h" +#include #include "alloc-util.h" #include "dhcp-identifier.h" diff --git a/src/libsystemd-network/sd-ipv4acd.c b/src/libsystemd-network/sd-ipv4acd.c index cc7436db6b..f1ed7ca747 100644 --- a/src/libsystemd-network/sd-ipv4acd.c +++ b/src/libsystemd-network/sd-ipv4acd.c @@ -24,7 +24,7 @@ #include #include -#include "sd-ipv4acd.h" +#include #include "alloc-util.h" #include "arp-util.h" diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c index 2a06418c53..fc27408989 100644 --- a/src/libsystemd-network/sd-ipv4ll.c +++ b/src/libsystemd-network/sd-ipv4ll.c @@ -24,8 +24,8 @@ #include #include -#include "sd-ipv4acd.h" -#include "sd-ipv4ll.h" +#include +#include #include "alloc-util.h" #include "in-addr-util.h" diff --git a/src/libsystemd-network/sd-lldp.c b/src/libsystemd-network/sd-lldp.c index 9d4587c80e..5a7380cd3f 100644 --- a/src/libsystemd-network/sd-lldp.c +++ b/src/libsystemd-network/sd-lldp.c @@ -20,7 +20,7 @@ #include -#include "sd-lldp.h" +#include #include "alloc-util.h" #include "fd-util.h" diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c index fb4ef55673..1c0d300cb3 100644 --- a/src/libsystemd-network/sd-ndisc.c +++ b/src/libsystemd-network/sd-ndisc.c @@ -24,7 +24,7 @@ #include #include -#include "sd-ndisc.h" +#include #include "alloc-util.h" #include "async.h" diff --git a/src/libsystemd-network/test-acd.c b/src/libsystemd-network/test-acd.c index 75564615b9..53ddfc3b62 100644 --- a/src/libsystemd-network/test-acd.c +++ b/src/libsystemd-network/test-acd.c @@ -24,9 +24,9 @@ #include #include -#include "sd-event.h" -#include "sd-ipv4acd.h" -#include "sd-netlink.h" +#include +#include +#include #include "in-addr-util.h" #include "netlink-util.h" diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index c3c08fef5e..478b370c4c 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -22,8 +22,8 @@ #include #include -#include "sd-dhcp-client.h" -#include "sd-event.h" +#include +#include #include "alloc-util.h" #include "dhcp-identifier.h" diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index e81c508c7f..7dc315c07f 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -20,8 +20,8 @@ #include -#include "sd-dhcp-server.h" -#include "sd-event.h" +#include +#include #include "dhcp-server-internal.h" diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index e74c8c72db..0548e8381e 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -24,8 +24,8 @@ #include #include -#include "sd-dhcp6-client.h" -#include "sd-event.h" +#include +#include #include "dhcp6-internal.h" #include "dhcp6-lease-internal.h" diff --git a/src/libsystemd-network/test-ipv4ll-manual.c b/src/libsystemd-network/test-ipv4ll-manual.c index 85dd61470d..caa315b210 100644 --- a/src/libsystemd-network/test-ipv4ll-manual.c +++ b/src/libsystemd-network/test-ipv4ll-manual.c @@ -23,9 +23,9 @@ #include #include -#include "sd-event.h" -#include "sd-ipv4ll.h" -#include "sd-netlink.h" +#include +#include +#include #include "alloc-util.h" #include "in-addr-util.h" diff --git a/src/libsystemd-network/test-ipv4ll.c b/src/libsystemd-network/test-ipv4ll.c index a233e0378c..b7278834f2 100644 --- a/src/libsystemd-network/test-ipv4ll.c +++ b/src/libsystemd-network/test-ipv4ll.c @@ -25,7 +25,7 @@ #include #include -#include "sd-ipv4ll.h" +#include #include "arp-util.h" #include "fd-util.h" diff --git a/src/libsystemd-network/test-lldp.c b/src/libsystemd-network/test-lldp.c index 1aae2253c0..8c6d214d6f 100644 --- a/src/libsystemd-network/test-lldp.c +++ b/src/libsystemd-network/test-lldp.c @@ -24,8 +24,8 @@ #include #include -#include "sd-event.h" -#include "sd-lldp.h" +#include +#include #include "alloc-util.h" #include "fd-util.h" diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c index f7b2eb8050..863a76637c 100644 --- a/src/libsystemd-network/test-ndisc-rs.c +++ b/src/libsystemd-network/test-ndisc-rs.c @@ -19,7 +19,7 @@ #include -#include "sd-ndisc.h" +#include #include "icmp6-util.h" #include "socket-util.h" diff --git a/src/libsystemd/Makefile b/src/libsystemd/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/libsystemd/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/libsystemd/Makefile b/src/libsystemd/Makefile new file mode 100644 index 0000000000..2c6505918b --- /dev/null +++ b/src/libsystemd/Makefile @@ -0,0 +1,110 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +LIBSYSTEMD_CURRENT=15 +LIBSYSTEMD_REVISION=0 +LIBSYSTEMD_AGE=15 + +EXTRA_DIST += \ + src/libsystemd/libsystemd.pc.in \ + src/libsystemd/sd-bus/DIFFERENCES \ + src/libsystemd/sd-bus/GVARIANT-SERIALIZATION + +libsystemd_la_SOURCES = \ + $(libsystemd_internal_la_SOURCES) \ + $(libsystemd_journal_internal_la_SOURCES) + +nodist_libsystemd_la_SOURCES = \ + $(nodist_libsystemd_internal_la_SOURCES) + +libsystemd_la_CFLAGS = \ + $(libsystemd_internal_la_CFLAGS) \ + $(libsystemd_journal_internal_la_CFLAGS) + +libsystemd_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -version-info $(LIBSYSTEMD_CURRENT):$(LIBSYSTEMD_REVISION):$(LIBSYSTEMD_AGE) \ + -Wl,--version-script=$(srcdir)/libsystemd.sym + +libsystemd_la_LIBADD = \ + $(libsystemd_internal_la_LIBADD) \ + $(libsystemd_journal_internal_la_LIBADD) + +pkgconfiglib_DATA += \ + src/libsystemd/libsystemd.pc + +pkginclude_HEADERS += \ + src/systemd/sd-bus.h \ + src/systemd/sd-bus-protocol.h \ + src/systemd/sd-bus-vtable.h \ + src/systemd/sd-event.h \ + src/systemd/sd-login.h \ + src/systemd/sd-id128.h \ + src/systemd/sd-daemon.h + +lib_LTLIBRARIES += \ + libsystemd.la + +# ------------------------------------------------------------------------------ + +tests += \ + test-bus-marshal \ + test-bus-signature \ + test-bus-benchmark \ + test-bus-chat \ + test-bus-cleanup \ + test-bus-server \ + test-bus-match \ + test-bus-kernel \ + test-bus-kernel-bloom \ + test-bus-zero-copy \ + test-bus-introspect \ + test-bus-objects \ + test-bus-error \ + test-bus-creds \ + test-bus-gvariant \ + test-event \ + test-netlink \ + test-local-addresses \ + test-resolve + +test-libsystemd-sym.c: \ + $(top_builddir)/src/libsystemd/libsystemd.sym \ + src/systemd/sd-journal.h \ + src/systemd/sd-daemon.h \ + src/systemd/sd-login.h \ + src/systemd/sd-bus.h \ + src/systemd/sd-utf8.h \ + src/systemd/sd-resolve.h \ + src/systemd/sd-path.h \ + src/systemd/sd-event.h + $(generate-sym-test) + +nodist_test_libsystemd_sym_SOURCES = \ + test-libsystemd-sym.c +test_libsystemd_sym_LDADD = \ + libsystemd.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libsystemd/include/systemd/_sd-common.h b/src/libsystemd/include/systemd/_sd-common.h new file mode 100644 index 0000000000..3bb886be75 --- /dev/null +++ b/src/libsystemd/include/systemd/_sd-common.h @@ -0,0 +1,83 @@ +#ifndef foosdcommonhfoo +#define foosdcommonhfoo + +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +/* This is a private header; never even think of including this directly! */ + +#if __INCLUDE_LEVEL__ <= 1 +#error "Do not include _sd-common.h directly; it is a private header." +#endif + +#ifndef _sd_printf_ +# if __GNUC__ >= 4 +# define _sd_printf_(a,b) __attribute__ ((format (printf, a, b))) +# else +# define _sd_printf_(a,b) +# endif +#endif + +#ifndef _sd_sentinel_ +# define _sd_sentinel_ __attribute__((sentinel)) +#endif + +#ifndef _sd_packed_ +# define _sd_packed_ __attribute__((packed)) +#endif + +#ifndef _sd_pure_ +# define _sd_pure_ __attribute__((pure)) +#endif + +#ifndef _SD_STRINGIFY +# define _SD_XSTRINGIFY(x) #x +# define _SD_STRINGIFY(x) _SD_XSTRINGIFY(x) +#endif + +#ifndef _SD_BEGIN_DECLARATIONS +# ifdef __cplusplus +# define _SD_BEGIN_DECLARATIONS \ + extern "C" { \ + struct _sd_useless_struct_to_allow_trailing_semicolon_ +# else +# define _SD_BEGIN_DECLARATIONS \ + struct _sd_useless_struct_to_allow_trailing_semicolon_ +# endif +#endif + +#ifndef _SD_END_DECLARATIONS +# ifdef __cplusplus +# define _SD_END_DECLARATIONS \ + } \ + struct _sd_useless_cpp_struct_to_allow_trailing_semicolon_ +# else +# define _SD_END_DECLARATIONS \ + struct _sd_useless_struct_to_allow_trailing_semicolon_ +# endif +#endif + +#define _SD_DEFINE_POINTER_CLEANUP_FUNC(type, func) \ + static __inline__ void func##p(type **p) { \ + if (*p) \ + func(*p); \ + } \ + struct _sd_useless_struct_to_allow_trailing_semicolon_ + +#endif diff --git a/src/libsystemd/include/systemd/sd-bus-protocol.h b/src/libsystemd/include/systemd/sd-bus-protocol.h new file mode 100644 index 0000000000..623cee0c50 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-bus-protocol.h @@ -0,0 +1,102 @@ +#ifndef foosdbusprotocolhfoo +#define foosdbusprotocolhfoo + +/*** + This file is part of systemd. + + Copyright 2013 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 "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* Types of message */ + +enum { + _SD_BUS_MESSAGE_TYPE_INVALID = 0, + SD_BUS_MESSAGE_METHOD_CALL, + SD_BUS_MESSAGE_METHOD_RETURN, + SD_BUS_MESSAGE_METHOD_ERROR, + SD_BUS_MESSAGE_SIGNAL, + _SD_BUS_MESSAGE_TYPE_MAX +}; + +/* Primitive types */ + +enum { + _SD_BUS_TYPE_INVALID = 0, + SD_BUS_TYPE_BYTE = 'y', + SD_BUS_TYPE_BOOLEAN = 'b', + SD_BUS_TYPE_INT16 = 'n', + SD_BUS_TYPE_UINT16 = 'q', + SD_BUS_TYPE_INT32 = 'i', + SD_BUS_TYPE_UINT32 = 'u', + SD_BUS_TYPE_INT64 = 'x', + SD_BUS_TYPE_UINT64 = 't', + SD_BUS_TYPE_DOUBLE = 'd', + SD_BUS_TYPE_STRING = 's', + SD_BUS_TYPE_OBJECT_PATH = 'o', + SD_BUS_TYPE_SIGNATURE = 'g', + SD_BUS_TYPE_UNIX_FD = 'h', + SD_BUS_TYPE_ARRAY = 'a', + SD_BUS_TYPE_VARIANT = 'v', + SD_BUS_TYPE_STRUCT = 'r', /* not actually used in signatures */ + SD_BUS_TYPE_STRUCT_BEGIN = '(', + SD_BUS_TYPE_STRUCT_END = ')', + SD_BUS_TYPE_DICT_ENTRY = 'e', /* not actually used in signatures */ + SD_BUS_TYPE_DICT_ENTRY_BEGIN = '{', + SD_BUS_TYPE_DICT_ENTRY_END = '}' +}; + +/* Well-known errors. Note that this is only a sanitized subset of the + * errors that the reference implementation generates. */ + +#define SD_BUS_ERROR_FAILED "org.freedesktop.DBus.Error.Failed" +#define SD_BUS_ERROR_NO_MEMORY "org.freedesktop.DBus.Error.NoMemory" +#define SD_BUS_ERROR_SERVICE_UNKNOWN "org.freedesktop.DBus.Error.ServiceUnknown" +#define SD_BUS_ERROR_NAME_HAS_NO_OWNER "org.freedesktop.DBus.Error.NameHasNoOwner" +#define SD_BUS_ERROR_NO_REPLY "org.freedesktop.DBus.Error.NoReply" +#define SD_BUS_ERROR_IO_ERROR "org.freedesktop.DBus.Error.IOError" +#define SD_BUS_ERROR_BAD_ADDRESS "org.freedesktop.DBus.Error.BadAddress" +#define SD_BUS_ERROR_NOT_SUPPORTED "org.freedesktop.DBus.Error.NotSupported" +#define SD_BUS_ERROR_LIMITS_EXCEEDED "org.freedesktop.DBus.Error.LimitsExceeded" +#define SD_BUS_ERROR_ACCESS_DENIED "org.freedesktop.DBus.Error.AccessDenied" +#define SD_BUS_ERROR_AUTH_FAILED "org.freedesktop.DBus.Error.AuthFailed" +#define SD_BUS_ERROR_NO_SERVER "org.freedesktop.DBus.Error.NoServer" +#define SD_BUS_ERROR_TIMEOUT "org.freedesktop.DBus.Error.Timeout" +#define SD_BUS_ERROR_NO_NETWORK "org.freedesktop.DBus.Error.NoNetwork" +#define SD_BUS_ERROR_ADDRESS_IN_USE "org.freedesktop.DBus.Error.AddressInUse" +#define SD_BUS_ERROR_DISCONNECTED "org.freedesktop.DBus.Error.Disconnected" +#define SD_BUS_ERROR_INVALID_ARGS "org.freedesktop.DBus.Error.InvalidArgs" +#define SD_BUS_ERROR_FILE_NOT_FOUND "org.freedesktop.DBus.Error.FileNotFound" +#define SD_BUS_ERROR_FILE_EXISTS "org.freedesktop.DBus.Error.FileExists" +#define SD_BUS_ERROR_UNKNOWN_METHOD "org.freedesktop.DBus.Error.UnknownMethod" +#define SD_BUS_ERROR_UNKNOWN_OBJECT "org.freedesktop.DBus.Error.UnknownObject" +#define SD_BUS_ERROR_UNKNOWN_INTERFACE "org.freedesktop.DBus.Error.UnknownInterface" +#define SD_BUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty" +#define SD_BUS_ERROR_PROPERTY_READ_ONLY "org.freedesktop.DBus.Error.PropertyReadOnly" +#define SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN "org.freedesktop.DBus.Error.UnixProcessIdUnknown" +#define SD_BUS_ERROR_INVALID_SIGNATURE "org.freedesktop.DBus.Error.InvalidSignature" +#define SD_BUS_ERROR_INCONSISTENT_MESSAGE "org.freedesktop.DBus.Error.InconsistentMessage" +#define SD_BUS_ERROR_MATCH_RULE_NOT_FOUND "org.freedesktop.DBus.Error.MatchRuleNotFound" +#define SD_BUS_ERROR_MATCH_RULE_INVALID "org.freedesktop.DBus.Error.MatchRuleInvalid" +#define SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED \ + "org.freedesktop.DBus.Error.InteractiveAuthorizationRequired" + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-bus-vtable.h b/src/libsystemd/include/systemd/sd-bus-vtable.h new file mode 100644 index 0000000000..2b684b5678 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-bus-vtable.h @@ -0,0 +1,141 @@ +#ifndef foosdbusvtablehfoo +#define foosdbusvtablehfoo + +/*** + This file is part of systemd. + + Copyright 2013 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 "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_bus_vtable sd_bus_vtable; + +#include + +enum { + _SD_BUS_VTABLE_START = '<', + _SD_BUS_VTABLE_END = '>', + _SD_BUS_VTABLE_METHOD = 'M', + _SD_BUS_VTABLE_SIGNAL = 'S', + _SD_BUS_VTABLE_PROPERTY = 'P', + _SD_BUS_VTABLE_WRITABLE_PROPERTY = 'W' +}; + +enum { + SD_BUS_VTABLE_DEPRECATED = 1ULL << 0, + SD_BUS_VTABLE_HIDDEN = 1ULL << 1, + SD_BUS_VTABLE_UNPRIVILEGED = 1ULL << 2, + SD_BUS_VTABLE_METHOD_NO_REPLY = 1ULL << 3, + SD_BUS_VTABLE_PROPERTY_CONST = 1ULL << 4, + SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE = 1ULL << 5, + SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION = 1ULL << 6, + SD_BUS_VTABLE_PROPERTY_EXPLICIT = 1ULL << 7, + _SD_BUS_VTABLE_CAPABILITY_MASK = 0xFFFFULL << 40 +}; + +#define SD_BUS_VTABLE_CAPABILITY(x) ((uint64_t) (((x)+1) & 0xFFFF) << 40) + +struct sd_bus_vtable { + /* Please do not initialize this structure directly, use the + * macros below instead */ + + uint8_t type:8; + uint64_t flags:56; + union { + struct { + size_t element_size; + } start; + struct { + const char *member; + const char *signature; + const char *result; + sd_bus_message_handler_t handler; + size_t offset; + } method; + struct { + const char *member; + const char *signature; + } signal; + struct { + const char *member; + const char *signature; + sd_bus_property_get_t get; + sd_bus_property_set_t set; + size_t offset; + } property; + } x; +}; + +#define SD_BUS_VTABLE_START(_flags) \ + { \ + .type = _SD_BUS_VTABLE_START, \ + .flags = _flags, \ + .x.start.element_size = sizeof(sd_bus_vtable), \ + } + +#define SD_BUS_METHOD_WITH_OFFSET(_member, _signature, _result, _handler, _offset, _flags) \ + { \ + .type = _SD_BUS_VTABLE_METHOD, \ + .flags = _flags, \ + .x.method.member = _member, \ + .x.method.signature = _signature, \ + .x.method.result = _result, \ + .x.method.handler = _handler, \ + .x.method.offset = _offset, \ + } +#define SD_BUS_METHOD(_member, _signature, _result, _handler, _flags) \ + SD_BUS_METHOD_WITH_OFFSET(_member, _signature, _result, _handler, 0, _flags) + +#define SD_BUS_SIGNAL(_member, _signature, _flags) \ + { \ + .type = _SD_BUS_VTABLE_SIGNAL, \ + .flags = _flags, \ + .x.signal.member = _member, \ + .x.signal.signature = _signature, \ + } + +#define SD_BUS_PROPERTY(_member, _signature, _get, _offset, _flags) \ + { \ + .type = _SD_BUS_VTABLE_PROPERTY, \ + .flags = _flags, \ + .x.property.member = _member, \ + .x.property.signature = _signature, \ + .x.property.get = _get, \ + .x.property.offset = _offset, \ + } + +#define SD_BUS_WRITABLE_PROPERTY(_member, _signature, _get, _set, _offset, _flags) \ + { \ + .type = _SD_BUS_VTABLE_WRITABLE_PROPERTY, \ + .flags = _flags, \ + .x.property.member = _member, \ + .x.property.signature = _signature, \ + .x.property.get = _get, \ + .x.property.set = _set, \ + .x.property.offset = _offset, \ + } + +#define SD_BUS_VTABLE_END \ + { \ + .type = _SD_BUS_VTABLE_END, \ + } + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-bus.h b/src/libsystemd/include/systemd/sd-bus.h new file mode 100644 index 0000000000..3c1b4b97a4 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-bus.h @@ -0,0 +1,456 @@ +#ifndef foosdbushfoo +#define foosdbushfoo + +/*** + This file is part of systemd. + + Copyright 2013 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 +#include +#include + +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* Types */ + +typedef struct sd_bus sd_bus; +typedef struct sd_bus_message sd_bus_message; +typedef struct sd_bus_slot sd_bus_slot; +typedef struct sd_bus_creds sd_bus_creds; +typedef struct sd_bus_track sd_bus_track; + +typedef struct { + const char *name; + const char *message; + int _need_free; +} sd_bus_error; + +typedef struct { + const char* name; + int code; +} sd_bus_error_map; + +/* Flags */ + +enum { + SD_BUS_CREDS_PID = 1ULL << 0, + SD_BUS_CREDS_TID = 1ULL << 1, + SD_BUS_CREDS_PPID = 1ULL << 2, + SD_BUS_CREDS_UID = 1ULL << 3, + SD_BUS_CREDS_EUID = 1ULL << 4, + SD_BUS_CREDS_SUID = 1ULL << 5, + SD_BUS_CREDS_FSUID = 1ULL << 6, + SD_BUS_CREDS_GID = 1ULL << 7, + SD_BUS_CREDS_EGID = 1ULL << 8, + SD_BUS_CREDS_SGID = 1ULL << 9, + SD_BUS_CREDS_FSGID = 1ULL << 10, + SD_BUS_CREDS_SUPPLEMENTARY_GIDS = 1ULL << 11, + SD_BUS_CREDS_COMM = 1ULL << 12, + SD_BUS_CREDS_TID_COMM = 1ULL << 13, + SD_BUS_CREDS_EXE = 1ULL << 14, + SD_BUS_CREDS_CMDLINE = 1ULL << 15, + SD_BUS_CREDS_CGROUP = 1ULL << 16, + SD_BUS_CREDS_UNIT = 1ULL << 17, + SD_BUS_CREDS_SLICE = 1ULL << 18, + SD_BUS_CREDS_USER_UNIT = 1ULL << 19, + SD_BUS_CREDS_USER_SLICE = 1ULL << 20, + SD_BUS_CREDS_SESSION = 1ULL << 21, + SD_BUS_CREDS_OWNER_UID = 1ULL << 22, + SD_BUS_CREDS_EFFECTIVE_CAPS = 1ULL << 23, + SD_BUS_CREDS_PERMITTED_CAPS = 1ULL << 24, + SD_BUS_CREDS_INHERITABLE_CAPS = 1ULL << 25, + SD_BUS_CREDS_BOUNDING_CAPS = 1ULL << 26, + SD_BUS_CREDS_SELINUX_CONTEXT = 1ULL << 27, + SD_BUS_CREDS_AUDIT_SESSION_ID = 1ULL << 28, + SD_BUS_CREDS_AUDIT_LOGIN_UID = 1ULL << 29, + SD_BUS_CREDS_TTY = 1ULL << 30, + SD_BUS_CREDS_UNIQUE_NAME = 1ULL << 31, + SD_BUS_CREDS_WELL_KNOWN_NAMES = 1ULL << 32, + SD_BUS_CREDS_DESCRIPTION = 1ULL << 33, + SD_BUS_CREDS_AUGMENT = 1ULL << 63, /* special flag, if on sd-bus will augment creds struct, in a potentially race-full way. */ + _SD_BUS_CREDS_ALL = (1ULL << 34) -1 +}; + +enum { + SD_BUS_NAME_REPLACE_EXISTING = 1ULL << 0, + SD_BUS_NAME_ALLOW_REPLACEMENT = 1ULL << 1, + SD_BUS_NAME_QUEUE = 1ULL << 2 +}; + +/* Callbacks */ + +typedef int (*sd_bus_message_handler_t)(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +typedef int (*sd_bus_property_get_t) (sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); +typedef int (*sd_bus_property_set_t) (sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *value, void *userdata, sd_bus_error *ret_error); +typedef int (*sd_bus_object_find_t) (sd_bus *bus, const char *path, const char *interface, void *userdata, void **ret_found, sd_bus_error *ret_error); +typedef int (*sd_bus_node_enumerator_t) (sd_bus *bus, const char *prefix, void *userdata, char ***ret_nodes, sd_bus_error *ret_error); +typedef int (*sd_bus_track_handler_t) (sd_bus_track *track, void *userdata); + +#include +#include + +/* Connections */ + +int sd_bus_default(sd_bus **ret); +int sd_bus_default_user(sd_bus **ret); +int sd_bus_default_system(sd_bus **ret); + +int sd_bus_open(sd_bus **ret); +int sd_bus_open_user(sd_bus **ret); +int sd_bus_open_system(sd_bus **ret); +int sd_bus_open_system_remote(sd_bus **ret, const char *host); +int sd_bus_open_system_machine(sd_bus **ret, const char *machine); + +int sd_bus_new(sd_bus **ret); + +int sd_bus_set_address(sd_bus *bus, const char *address); +int sd_bus_set_fd(sd_bus *bus, int input_fd, int output_fd); +int sd_bus_set_exec(sd_bus *bus, const char *path, char *const argv[]); +int sd_bus_get_address(sd_bus *bus, const char **address); +int sd_bus_set_bus_client(sd_bus *bus, int b); +int sd_bus_is_bus_client(sd_bus *bus); +int sd_bus_set_server(sd_bus *bus, int b, sd_id128_t bus_id); +int sd_bus_is_server(sd_bus *bus); +int sd_bus_set_anonymous(sd_bus *bus, int b); +int sd_bus_is_anonymous(sd_bus *bus); +int sd_bus_set_trusted(sd_bus *bus, int b); +int sd_bus_is_trusted(sd_bus *bus); +int sd_bus_set_monitor(sd_bus *bus, int b); +int sd_bus_is_monitor(sd_bus *bus); +int sd_bus_set_description(sd_bus *bus, const char *description); +int sd_bus_get_description(sd_bus *bus, const char **description); +int sd_bus_negotiate_creds(sd_bus *bus, int b, uint64_t creds_mask); +int sd_bus_negotiate_timestamp(sd_bus *bus, int b); +int sd_bus_negotiate_fds(sd_bus *bus, int b); +int sd_bus_can_send(sd_bus *bus, char type); +int sd_bus_get_creds_mask(sd_bus *bus, uint64_t *creds_mask); +int sd_bus_set_allow_interactive_authorization(sd_bus *bus, int b); +int sd_bus_get_allow_interactive_authorization(sd_bus *bus); + +int sd_bus_start(sd_bus *ret); + +int sd_bus_try_close(sd_bus *bus); +void sd_bus_close(sd_bus *bus); + +sd_bus *sd_bus_ref(sd_bus *bus); +sd_bus *sd_bus_unref(sd_bus *bus); +sd_bus *sd_bus_flush_close_unref(sd_bus *bus); + +void sd_bus_default_flush_close(void); + +int sd_bus_is_open(sd_bus *bus); + +int sd_bus_get_bus_id(sd_bus *bus, sd_id128_t *id); +int sd_bus_get_scope(sd_bus *bus, const char **scope); +int sd_bus_get_tid(sd_bus *bus, pid_t *tid); +int sd_bus_get_owner_creds(sd_bus *bus, uint64_t creds_mask, sd_bus_creds **ret); + +int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie); +int sd_bus_send_to(sd_bus *bus, sd_bus_message *m, const char *destination, uint64_t *cookie); +int sd_bus_call(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply); +int sd_bus_call_async(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec); + +int sd_bus_get_fd(sd_bus *bus); +int sd_bus_get_events(sd_bus *bus); +int sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec); +int sd_bus_process(sd_bus *bus, sd_bus_message **r); +int sd_bus_process_priority(sd_bus *bus, int64_t max_priority, sd_bus_message **r); +int sd_bus_wait(sd_bus *bus, uint64_t timeout_usec); +int sd_bus_flush(sd_bus *bus); + +sd_bus_slot* sd_bus_get_current_slot(sd_bus *bus); +sd_bus_message* sd_bus_get_current_message(sd_bus *bus); +sd_bus_message_handler_t sd_bus_get_current_handler(sd_bus *bus); +void* sd_bus_get_current_userdata(sd_bus *bus); + +int sd_bus_attach_event(sd_bus *bus, sd_event *e, int priority); +int sd_bus_detach_event(sd_bus *bus); +sd_event *sd_bus_get_event(sd_bus *bus); + +int sd_bus_add_filter(sd_bus *bus, sd_bus_slot **slot, sd_bus_message_handler_t callback, void *userdata); +int sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata); +int sd_bus_add_object(sd_bus *bus, sd_bus_slot **slot, const char *path, sd_bus_message_handler_t callback, void *userdata); +int sd_bus_add_fallback(sd_bus *bus, sd_bus_slot **slot, const char *prefix, sd_bus_message_handler_t callback, void *userdata); +int sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata); +int sd_bus_add_fallback_vtable(sd_bus *bus, sd_bus_slot **slot, const char *prefix, const char *interface, const sd_bus_vtable *vtable, sd_bus_object_find_t find, void *userdata); +int sd_bus_add_node_enumerator(sd_bus *bus, sd_bus_slot **slot, const char *path, sd_bus_node_enumerator_t callback, void *userdata); +int sd_bus_add_object_manager(sd_bus *bus, sd_bus_slot **slot, const char *path); + +/* Slot object */ + +sd_bus_slot* sd_bus_slot_ref(sd_bus_slot *slot); +sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot); + +sd_bus* sd_bus_slot_get_bus(sd_bus_slot *slot); +void *sd_bus_slot_get_userdata(sd_bus_slot *slot); +void *sd_bus_slot_set_userdata(sd_bus_slot *slot, void *userdata); +int sd_bus_slot_set_description(sd_bus_slot *slot, const char *description); +int sd_bus_slot_get_description(sd_bus_slot *slot, const char **description); + +sd_bus_message* sd_bus_slot_get_current_message(sd_bus_slot *slot); +sd_bus_message_handler_t sd_bus_slot_get_current_handler(sd_bus_slot *bus); +void *sd_bus_slot_get_current_userdata(sd_bus_slot *slot); + +/* Message object */ + +int sd_bus_message_new_signal(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member); +int sd_bus_message_new_method_call(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member); +int sd_bus_message_new_method_return(sd_bus_message *call, sd_bus_message **m); +int sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e); +int sd_bus_message_new_method_errorf(sd_bus_message *call, sd_bus_message **m, const char *name, const char *format, ...) _sd_printf_(4, 5); +int sd_bus_message_new_method_errno(sd_bus_message *call, sd_bus_message **m, int error, const sd_bus_error *e); +int sd_bus_message_new_method_errnof(sd_bus_message *call, sd_bus_message **m, int error, const char *format, ...) _sd_printf_(4, 5); + +sd_bus_message* sd_bus_message_ref(sd_bus_message *m); +sd_bus_message* sd_bus_message_unref(sd_bus_message *m); + +int sd_bus_message_get_type(sd_bus_message *m, uint8_t *type); +int sd_bus_message_get_cookie(sd_bus_message *m, uint64_t *cookie); +int sd_bus_message_get_reply_cookie(sd_bus_message *m, uint64_t *cookie); +int sd_bus_message_get_priority(sd_bus_message *m, int64_t *priority); + +int sd_bus_message_get_expect_reply(sd_bus_message *m); +int sd_bus_message_get_auto_start(sd_bus_message *m); +int sd_bus_message_get_allow_interactive_authorization(sd_bus_message *m); + +const char *sd_bus_message_get_signature(sd_bus_message *m, int complete); +const char *sd_bus_message_get_path(sd_bus_message *m); +const char *sd_bus_message_get_interface(sd_bus_message *m); +const char *sd_bus_message_get_member(sd_bus_message *m); +const char *sd_bus_message_get_destination(sd_bus_message *m); +const char *sd_bus_message_get_sender(sd_bus_message *m); +const sd_bus_error *sd_bus_message_get_error(sd_bus_message *m); +int sd_bus_message_get_errno(sd_bus_message *m); + +int sd_bus_message_get_monotonic_usec(sd_bus_message *m, uint64_t *usec); +int sd_bus_message_get_realtime_usec(sd_bus_message *m, uint64_t *usec); +int sd_bus_message_get_seqnum(sd_bus_message *m, uint64_t* seqnum); + +sd_bus* sd_bus_message_get_bus(sd_bus_message *m); +sd_bus_creds *sd_bus_message_get_creds(sd_bus_message *m); /* do not unref the result */ + +int sd_bus_message_is_signal(sd_bus_message *m, const char *interface, const char *member); +int sd_bus_message_is_method_call(sd_bus_message *m, const char *interface, const char *member); +int sd_bus_message_is_method_error(sd_bus_message *m, const char *name); +int sd_bus_message_is_empty(sd_bus_message *m); +int sd_bus_message_has_signature(sd_bus_message *m, const char *signature); + +int sd_bus_message_set_expect_reply(sd_bus_message *m, int b); +int sd_bus_message_set_auto_start(sd_bus_message *m, int b); +int sd_bus_message_set_allow_interactive_authorization(sd_bus_message *m, int b); + +int sd_bus_message_set_destination(sd_bus_message *m, const char *destination); +int sd_bus_message_set_priority(sd_bus_message *m, int64_t priority); + +int sd_bus_message_append(sd_bus_message *m, const char *types, ...); +int sd_bus_message_append_basic(sd_bus_message *m, char type, const void *p); +int sd_bus_message_append_array(sd_bus_message *m, char type, const void *ptr, size_t size); +int sd_bus_message_append_array_space(sd_bus_message *m, char type, size_t size, void **ptr); +int sd_bus_message_append_array_iovec(sd_bus_message *m, char type, const struct iovec *iov, unsigned n); +int sd_bus_message_append_array_memfd(sd_bus_message *m, char type, int memfd, uint64_t offset, uint64_t size); +int sd_bus_message_append_string_space(sd_bus_message *m, size_t size, char **s); +int sd_bus_message_append_string_iovec(sd_bus_message *m, const struct iovec *iov, unsigned n); +int sd_bus_message_append_string_memfd(sd_bus_message *m, int memfd, uint64_t offset, uint64_t size); +int sd_bus_message_append_strv(sd_bus_message *m, char **l); +int sd_bus_message_open_container(sd_bus_message *m, char type, const char *contents); +int sd_bus_message_close_container(sd_bus_message *m); +int sd_bus_message_copy(sd_bus_message *m, sd_bus_message *source, int all); + +int sd_bus_message_read(sd_bus_message *m, const char *types, ...); +int sd_bus_message_read_basic(sd_bus_message *m, char type, void *p); +int sd_bus_message_read_array(sd_bus_message *m, char type, const void **ptr, size_t *size); +int sd_bus_message_read_strv(sd_bus_message *m, char ***l); /* free the result! */ +int sd_bus_message_skip(sd_bus_message *m, const char *types); +int sd_bus_message_enter_container(sd_bus_message *m, char type, const char *contents); +int sd_bus_message_exit_container(sd_bus_message *m); +int sd_bus_message_peek_type(sd_bus_message *m, char *type, const char **contents); +int sd_bus_message_verify_type(sd_bus_message *m, char type, const char *contents); +int sd_bus_message_at_end(sd_bus_message *m, int complete); +int sd_bus_message_rewind(sd_bus_message *m, int complete); + +/* Bus management */ + +int sd_bus_get_unique_name(sd_bus *bus, const char **unique); +int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags); +int sd_bus_release_name(sd_bus *bus, const char *name); +int sd_bus_list_names(sd_bus *bus, char ***acquired, char ***activatable); /* free the results */ +int sd_bus_get_name_creds(sd_bus *bus, const char *name, uint64_t mask, sd_bus_creds **creds); /* unref the result! */ +int sd_bus_get_name_machine_id(sd_bus *bus, const char *name, sd_id128_t *machine); + +/* Convenience calls */ + +int sd_bus_call_method(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, sd_bus_message **reply, const char *types, ...); +int sd_bus_call_method_async(sd_bus *bus, sd_bus_slot **slot, const char *destination, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *userdata, const char *types, ...); +int sd_bus_get_property(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, sd_bus_message **reply, const char *type); +int sd_bus_get_property_trivial(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, char type, void *ret_ptr); +int sd_bus_get_property_string(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, char **ret); /* free the result! */ +int sd_bus_get_property_strv(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, char ***ret); /* free the result! */ +int sd_bus_set_property(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, const char *type, ...); + +int sd_bus_reply_method_return(sd_bus_message *call, const char *types, ...); +int sd_bus_reply_method_error(sd_bus_message *call, const sd_bus_error *e); +int sd_bus_reply_method_errorf(sd_bus_message *call, const char *name, const char *format, ...) _sd_printf_(3, 4); +int sd_bus_reply_method_errno(sd_bus_message *call, int error, const sd_bus_error *e); +int sd_bus_reply_method_errnof(sd_bus_message *call, int error, const char *format, ...) _sd_printf_(3, 4); + +int sd_bus_emit_signal(sd_bus *bus, const char *path, const char *interface, const char *member, const char *types, ...); + +int sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names); +int sd_bus_emit_properties_changed(sd_bus *bus, const char *path, const char *interface, const char *name, ...) _sd_sentinel_; + +int sd_bus_emit_object_added(sd_bus *bus, const char *path); +int sd_bus_emit_object_removed(sd_bus *bus, const char *path); +int sd_bus_emit_interfaces_added_strv(sd_bus *bus, const char *path, char **interfaces); +int sd_bus_emit_interfaces_added(sd_bus *bus, const char *path, const char *interface, ...) _sd_sentinel_; +int sd_bus_emit_interfaces_removed_strv(sd_bus *bus, const char *path, char **interfaces); +int sd_bus_emit_interfaces_removed(sd_bus *bus, const char *path, const char *interface, ...) _sd_sentinel_; + +int sd_bus_query_sender_creds(sd_bus_message *call, uint64_t mask, sd_bus_creds **creds); +int sd_bus_query_sender_privilege(sd_bus_message *call, int capability); + +/* Credential handling */ + +int sd_bus_creds_new_from_pid(sd_bus_creds **ret, pid_t pid, uint64_t creds_mask); +sd_bus_creds *sd_bus_creds_ref(sd_bus_creds *c); +sd_bus_creds *sd_bus_creds_unref(sd_bus_creds *c); +uint64_t sd_bus_creds_get_mask(const sd_bus_creds *c); +uint64_t sd_bus_creds_get_augmented_mask(const sd_bus_creds *c); + +int sd_bus_creds_get_pid(sd_bus_creds *c, pid_t *pid); +int sd_bus_creds_get_ppid(sd_bus_creds *c, pid_t *ppid); +int sd_bus_creds_get_tid(sd_bus_creds *c, pid_t *tid); +int sd_bus_creds_get_uid(sd_bus_creds *c, uid_t *uid); +int sd_bus_creds_get_euid(sd_bus_creds *c, uid_t *euid); +int sd_bus_creds_get_suid(sd_bus_creds *c, uid_t *suid); +int sd_bus_creds_get_fsuid(sd_bus_creds *c, uid_t *fsuid); +int sd_bus_creds_get_gid(sd_bus_creds *c, gid_t *gid); +int sd_bus_creds_get_egid(sd_bus_creds *c, gid_t *egid); +int sd_bus_creds_get_sgid(sd_bus_creds *c, gid_t *sgid); +int sd_bus_creds_get_fsgid(sd_bus_creds *c, gid_t *fsgid); +int sd_bus_creds_get_supplementary_gids(sd_bus_creds *c, const gid_t **gids); +int sd_bus_creds_get_comm(sd_bus_creds *c, const char **comm); +int sd_bus_creds_get_tid_comm(sd_bus_creds *c, const char **comm); +int sd_bus_creds_get_exe(sd_bus_creds *c, const char **exe); +int sd_bus_creds_get_cmdline(sd_bus_creds *c, char ***cmdline); +int sd_bus_creds_get_cgroup(sd_bus_creds *c, const char **cgroup); +int sd_bus_creds_get_unit(sd_bus_creds *c, const char **unit); +int sd_bus_creds_get_slice(sd_bus_creds *c, const char **slice); +int sd_bus_creds_get_user_unit(sd_bus_creds *c, const char **unit); +int sd_bus_creds_get_user_slice(sd_bus_creds *c, const char **slice); +int sd_bus_creds_get_session(sd_bus_creds *c, const char **session); +int sd_bus_creds_get_owner_uid(sd_bus_creds *c, uid_t *uid); +int sd_bus_creds_has_effective_cap(sd_bus_creds *c, int capability); +int sd_bus_creds_has_permitted_cap(sd_bus_creds *c, int capability); +int sd_bus_creds_has_inheritable_cap(sd_bus_creds *c, int capability); +int sd_bus_creds_has_bounding_cap(sd_bus_creds *c, int capability); +int sd_bus_creds_get_selinux_context(sd_bus_creds *c, const char **context); +int sd_bus_creds_get_audit_session_id(sd_bus_creds *c, uint32_t *sessionid); +int sd_bus_creds_get_audit_login_uid(sd_bus_creds *c, uid_t *loginuid); +int sd_bus_creds_get_tty(sd_bus_creds *c, const char **tty); +int sd_bus_creds_get_unique_name(sd_bus_creds *c, const char **name); +int sd_bus_creds_get_well_known_names(sd_bus_creds *c, char ***names); +int sd_bus_creds_get_description(sd_bus_creds *c, const char **name); + +/* Error structures */ + +#define SD_BUS_ERROR_MAKE_CONST(name, message) ((const sd_bus_error) {(name), (message), 0}) +#define SD_BUS_ERROR_NULL SD_BUS_ERROR_MAKE_CONST(NULL, NULL) + +void sd_bus_error_free(sd_bus_error *e); +int sd_bus_error_set(sd_bus_error *e, const char *name, const char *message); +int sd_bus_error_setf(sd_bus_error *e, const char *name, const char *format, ...) _sd_printf_(3, 4); +int sd_bus_error_set_const(sd_bus_error *e, const char *name, const char *message); +int sd_bus_error_set_errno(sd_bus_error *e, int error); +int sd_bus_error_set_errnof(sd_bus_error *e, int error, const char *format, ...) _sd_printf_(3, 4); +int sd_bus_error_set_errnofv(sd_bus_error *e, int error, const char *format, va_list ap) _sd_printf_(3,0); +int sd_bus_error_get_errno(const sd_bus_error *e); +int sd_bus_error_copy(sd_bus_error *dest, const sd_bus_error *e); +int sd_bus_error_is_set(const sd_bus_error *e); +int sd_bus_error_has_name(const sd_bus_error *e, const char *name); + +#define SD_BUS_ERROR_MAP(_name, _code) \ + { \ + .name = _name, \ + .code = _code, \ + } +#define SD_BUS_ERROR_MAP_END \ + { \ + .name = NULL, \ + .code = - 'x', \ + } + +int sd_bus_error_add_map(const sd_bus_error_map *map); + +/* Auxiliary macros */ + +#define SD_BUS_MESSAGE_APPEND_ID128(x) 16, \ + (x).bytes[0], (x).bytes[1], (x).bytes[2], (x).bytes[3], \ + (x).bytes[4], (x).bytes[5], (x).bytes[6], (x).bytes[7], \ + (x).bytes[8], (x).bytes[9], (x).bytes[10], (x).bytes[11], \ + (x).bytes[12], (x).bytes[13], (x).bytes[14], (x).bytes[15] + +#define SD_BUS_MESSAGE_READ_ID128(x) 16, \ + &(x).bytes[0], &(x).bytes[1], &(x).bytes[2], &(x).bytes[3], \ + &(x).bytes[4], &(x).bytes[5], &(x).bytes[6], &(x).bytes[7], \ + &(x).bytes[8], &(x).bytes[9], &(x).bytes[10], &(x).bytes[11], \ + &(x).bytes[12], &(x).bytes[13], &(x).bytes[14], &(x).bytes[15] + +/* Label escaping */ + +int sd_bus_path_encode(const char *prefix, const char *external_id, char **ret_path); +int sd_bus_path_encode_many(char **out, const char *path_template, ...); +int sd_bus_path_decode(const char *path, const char *prefix, char **ret_external_id); +int sd_bus_path_decode_many(const char *path, const char *path_template, ...); + +/* Tracking peers */ + +int sd_bus_track_new(sd_bus *bus, sd_bus_track **track, sd_bus_track_handler_t handler, void *userdata); +sd_bus_track* sd_bus_track_ref(sd_bus_track *track); +sd_bus_track* sd_bus_track_unref(sd_bus_track *track); + +sd_bus* sd_bus_track_get_bus(sd_bus_track *track); +void *sd_bus_track_get_userdata(sd_bus_track *track); +void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata); + +int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m); +int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m); +int sd_bus_track_add_name(sd_bus_track *track, const char *name); +int sd_bus_track_remove_name(sd_bus_track *track, const char *name); + +unsigned sd_bus_track_count(sd_bus_track *track); +const char* sd_bus_track_contains(sd_bus_track *track, const char *names); +const char* sd_bus_track_first(sd_bus_track *track); +const char* sd_bus_track_next(sd_bus_track *track); + +/* Define helpers so that __attribute__((cleanup(sd_bus_unrefp))) and similar may be used. */ +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus, sd_bus_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus, sd_bus_flush_close_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus_slot, sd_bus_slot_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus_message, sd_bus_message_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus_creds, sd_bus_creds_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus_track, sd_bus_track_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-daemon.h b/src/libsystemd/include/systemd/sd-daemon.h new file mode 100644 index 0000000000..e6787b0a64 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-daemon.h @@ -0,0 +1,289 @@ +#ifndef foosddaemonhfoo +#define foosddaemonhfoo + +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* + The following functionality is provided: + + - Support for logging with log levels on stderr + - File descriptor passing for socket-based activation + - Daemon startup and status notification + - Detection of systemd boots + + See sd-daemon(3) for more information. +*/ + +/* + Log levels for usage on stderr: + + fprintf(stderr, SD_NOTICE "Hello World!\n"); + + This is similar to printk() usage in the kernel. +*/ +#define SD_EMERG "<0>" /* system is unusable */ +#define SD_ALERT "<1>" /* action must be taken immediately */ +#define SD_CRIT "<2>" /* critical conditions */ +#define SD_ERR "<3>" /* error conditions */ +#define SD_WARNING "<4>" /* warning conditions */ +#define SD_NOTICE "<5>" /* normal but significant condition */ +#define SD_INFO "<6>" /* informational */ +#define SD_DEBUG "<7>" /* debug-level messages */ + +/* The first passed file descriptor is fd 3 */ +#define SD_LISTEN_FDS_START 3 + +/* + Returns how many file descriptors have been passed, or a negative + errno code on failure. Optionally, removes the $LISTEN_FDS and + $LISTEN_PID file descriptors from the environment (recommended, but + problematic in threaded environments). If r is the return value of + this function you'll find the file descriptors passed as fds + SD_LISTEN_FDS_START to SD_LISTEN_FDS_START+r-1. Returns a negative + errno style error code on failure. This function call ensures that + the FD_CLOEXEC flag is set for the passed file descriptors, to make + sure they are not passed on to child processes. If FD_CLOEXEC shall + not be set, the caller needs to unset it after this call for all file + descriptors that are used. + + See sd_listen_fds(3) for more information. +*/ +int sd_listen_fds(int unset_environment); + +int sd_listen_fds_with_names(int unset_environment, char ***names); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a FIFO in the file system stored under the + specified path, 0 otherwise. If path is NULL a path name check will + not be done and the call only verifies if the file descriptor + refers to a FIFO. Returns a negative errno style error code on + failure. + + See sd_is_fifo(3) for more information. +*/ +int sd_is_fifo(int fd, const char *path); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a special character device on the file + system stored under the specified path, 0 otherwise. + If path is NULL a path name check will not be done and the call + only verifies if the file descriptor refers to a special character. + Returns a negative errno style error code on failure. + + See sd_is_special(3) for more information. +*/ +int sd_is_special(int fd, const char *path); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a socket of the specified family (AF_INET, + ...) and type (SOCK_DGRAM, SOCK_STREAM, ...), 0 otherwise. If + family is 0 a socket family check will not be done. If type is 0 a + socket type check will not be done and the call only verifies if + the file descriptor refers to a socket. If listening is > 0 it is + verified that the socket is in listening mode. (i.e. listen() has + been called) If listening is == 0 it is verified that the socket is + not in listening mode. If listening is < 0 no listening mode check + is done. Returns a negative errno style error code on failure. + + See sd_is_socket(3) for more information. +*/ +int sd_is_socket(int fd, int family, int type, int listening); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is an Internet socket, of the specified family + (either AF_INET or AF_INET6) and the specified type (SOCK_DGRAM, + SOCK_STREAM, ...), 0 otherwise. If version is 0 a protocol version + check is not done. If type is 0 a socket type check will not be + done. If port is 0 a socket port check will not be done. The + listening flag is used the same way as in sd_is_socket(). Returns a + negative errno style error code on failure. + + See sd_is_socket_inet(3) for more information. +*/ +int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is an AF_UNIX socket of the specified type + (SOCK_DGRAM, SOCK_STREAM, ...) and path, 0 otherwise. If type is 0 + a socket type check will not be done. If path is NULL a socket path + check will not be done. For normal AF_UNIX sockets set length to + 0. For abstract namespace sockets set length to the length of the + socket name (including the initial 0 byte), and pass the full + socket path in path (including the initial 0 byte). The listening + flag is used the same way as in sd_is_socket(). Returns a negative + errno style error code on failure. + + See sd_is_socket_unix(3) for more information. +*/ +int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a POSIX Message Queue of the specified name, + 0 otherwise. If path is NULL a message queue name check is not + done. Returns a negative errno style error code on failure. + + See sd_is_mq(3) for more information. +*/ +int sd_is_mq(int fd, const char *path); + +/* + Informs systemd about changed daemon state. This takes a number of + newline separated environment-style variable assignments in a + string. The following variables are known: + + READY=1 Tells systemd that daemon startup is finished (only + relevant for services of Type=notify). The passed + argument is a boolean "1" or "0". Since there is + little value in signaling non-readiness the only + value daemons should send is "READY=1". + + STATUS=... Passes a single-line status string back to systemd + that describes the daemon state. This is free-form + and can be used for various purposes: general state + feedback, fsck-like programs could pass completion + percentages and failing programs could pass a human + readable error message. Example: "STATUS=Completed + 66% of file system check..." + + ERRNO=... If a daemon fails, the errno-style error code, + formatted as string. Example: "ERRNO=2" for ENOENT. + + BUSERROR=... If a daemon fails, the D-Bus error-style error + code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut" + + MAINPID=... The main pid of a daemon, in case systemd did not + fork off the process itself. Example: "MAINPID=4711" + + WATCHDOG=1 Tells systemd to update the watchdog timestamp. + Services using this feature should do this in + regular intervals. A watchdog framework can use the + timestamps to detect failed services. Also see + sd_watchdog_enabled() below. + + FDSTORE=1 Store the file descriptors passed along with the + message in the per-service file descriptor store, + and pass them to the main process again on next + invocation. This variable is only supported with + sd_pid_notify_with_fds(). + + Daemons can choose to send additional variables. However, it is + recommended to prefix variable names not listed above with X_. + + Returns a negative errno-style error code on failure. Returns > 0 + if systemd could be notified, 0 if it couldn't possibly because + systemd is not running. + + Example: When a daemon finished starting up, it could issue this + call to notify systemd about it: + + sd_notify(0, "READY=1"); + + See sd_notifyf() for more complete examples. + + See sd_notify(3) for more information. +*/ +int sd_notify(int unset_environment, const char *state); + +/* + Similar to sd_notify() but takes a format string. + + Example 1: A daemon could send the following after initialization: + + sd_notifyf(0, "READY=1\n" + "STATUS=Processing requests...\n" + "MAINPID=%lu", + (unsigned long) getpid()); + + Example 2: A daemon could send the following shortly before + exiting, on failure: + + sd_notifyf(0, "STATUS=Failed to start up: %s\n" + "ERRNO=%i", + strerror(errno), + errno); + + See sd_notifyf(3) for more information. +*/ +int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_(2,3); + +/* + Similar to sd_notify(), but send the message on behalf of another + process, if the appropriate permissions are available. +*/ +int sd_pid_notify(pid_t pid, int unset_environment, const char *state); + +/* + Similar to sd_notifyf(), but send the message on behalf of another + process, if the appropriate permissions are available. +*/ +int sd_pid_notifyf(pid_t pid, int unset_environment, const char *format, ...) _sd_printf_(3,4); + +/* + Similar to sd_pid_notify(), but also passes the specified fd array + to the service manager for storage. This is particularly useful for + FDSTORE=1 messages. +*/ +int sd_pid_notify_with_fds(pid_t pid, int unset_environment, const char *state, const int *fds, unsigned n_fds); + +/* + Returns > 0 if the system was booted with systemd. Returns < 0 on + error. Returns 0 if the system was not booted with systemd. Note + that all of the functions above handle non-systemd boots just + fine. You should NOT protect them with a call to this function. Also + note that this function checks whether the system, not the user + session is controlled by systemd. However the functions above work + for both user and system services. + + See sd_booted(3) for more information. +*/ +int sd_booted(void); + +/* + Returns > 0 if the service manager expects watchdog keep-alive + events to be sent regularly via sd_notify(0, "WATCHDOG=1"). Returns + 0 if it does not expect this. If the usec argument is non-NULL + returns the watchdog timeout in µs after which the service manager + will act on a process that has not sent a watchdog keep alive + message. This function is useful to implement services that + recognize automatically if they are being run under supervision of + systemd with WatchdogSec= set. It is recommended for clients to + generate keep-alive pings via sd_notify(0, "WATCHDOG=1") every half + of the returned time. + + See sd_watchdog_enabled(3) for more information. +*/ +int sd_watchdog_enabled(int unset_environment, uint64_t *usec); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-device.h b/src/libsystemd/include/systemd/sd-device.h new file mode 100644 index 0000000000..c1d07561d7 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-device.h @@ -0,0 +1,101 @@ +#ifndef foosddevicehfoo +#define foosddevicehfoo + +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2014-2015 Tom Gundersen + + 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 +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_device sd_device; +typedef struct sd_device_enumerator sd_device_enumerator; + +/* device */ + +sd_device *sd_device_ref(sd_device *device); +sd_device *sd_device_unref(sd_device *device); + +int sd_device_new_from_syspath(sd_device **ret, const char *syspath); +int sd_device_new_from_devnum(sd_device **ret, char type, dev_t devnum); +int sd_device_new_from_subsystem_sysname(sd_device **ret, const char *subsystem, const char *sysname); +int sd_device_new_from_device_id(sd_device **ret, const char *id); + +int sd_device_get_parent(sd_device *child, sd_device **ret); +int sd_device_get_parent_with_subsystem_devtype(sd_device *child, const char *subsystem, const char *devtype, sd_device **ret); + +int sd_device_get_syspath(sd_device *device, const char **ret); +int sd_device_get_subsystem(sd_device *device, const char **ret); +int sd_device_get_devtype(sd_device *device, const char **ret); +int sd_device_get_devnum(sd_device *device, dev_t *devnum); +int sd_device_get_ifindex(sd_device *device, int *ifindex); +int sd_device_get_driver(sd_device *device, const char **ret); +int sd_device_get_devpath(sd_device *device, const char **ret); +int sd_device_get_devname(sd_device *device, const char **ret); +int sd_device_get_sysname(sd_device *device, const char **ret); +int sd_device_get_sysnum(sd_device *device, const char **ret); + +int sd_device_get_is_initialized(sd_device *device, int *initialized); +int sd_device_get_usec_since_initialized(sd_device *device, uint64_t *usec); + +const char *sd_device_get_tag_first(sd_device *device); +const char *sd_device_get_tag_next(sd_device *device); +const char *sd_device_get_devlink_first(sd_device *device); +const char *sd_device_get_devlink_next(sd_device *device); +const char *sd_device_get_property_first(sd_device *device, const char **value); +const char *sd_device_get_property_next(sd_device *device, const char **value); +const char *sd_device_get_sysattr_first(sd_device *device); +const char *sd_device_get_sysattr_next(sd_device *device); + +int sd_device_has_tag(sd_device *device, const char *tag); +int sd_device_get_property_value(sd_device *device, const char *key, const char **value); +int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **_value); + +int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, char *value); + +/* device enumerator */ + +int sd_device_enumerator_new(sd_device_enumerator **ret); +sd_device_enumerator *sd_device_enumerator_ref(sd_device_enumerator *enumerator); +sd_device_enumerator *sd_device_enumerator_unref(sd_device_enumerator *enumerator); + +sd_device *sd_device_enumerator_get_device_first(sd_device_enumerator *enumerator); +sd_device *sd_device_enumerator_get_device_next(sd_device_enumerator *enumerator); +sd_device *sd_device_enumerator_get_subsystem_first(sd_device_enumerator *enumerator); +sd_device *sd_device_enumerator_get_subsystem_next(sd_device_enumerator *enumerator); + +int sd_device_enumerator_add_match_subsystem(sd_device_enumerator *enumerator, const char *subsystem, int match); +int sd_device_enumerator_add_match_sysattr(sd_device_enumerator *enumerator, const char *sysattr, const char *value, int match); +int sd_device_enumerator_add_match_property(sd_device_enumerator *enumerator, const char *property, const char *value); +int sd_device_enumerator_add_match_sysname(sd_device_enumerator *enumerator, const char *sysname); +int sd_device_enumerator_add_match_tag(sd_device_enumerator *enumerator, const char *tag); +int sd_device_enumerator_add_match_parent(sd_device_enumerator *enumerator, sd_device *parent); +int sd_device_enumerator_allow_uninitialized(sd_device_enumerator *enumerator); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_device, sd_device_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_device_enumerator, sd_device_enumerator_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-dhcp-client.h b/src/libsystemd/include/systemd/sd-dhcp-client.h new file mode 100644 index 0000000000..f7bd5c4b7a --- /dev/null +++ b/src/libsystemd/include/systemd/sd-dhcp-client.h @@ -0,0 +1,158 @@ +#ifndef foosddhcpclienthfoo +#define foosddhcpclienthfoo + +/*** + This file is part of systemd. + + Copyright (C) 2013 Intel Corporation. All rights reserved. + + 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 +#include +#include + +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_DHCP_CLIENT_EVENT_STOP = 0, + SD_DHCP_CLIENT_EVENT_IP_ACQUIRE = 1, + SD_DHCP_CLIENT_EVENT_IP_CHANGE = 2, + SD_DHCP_CLIENT_EVENT_EXPIRED = 3, + SD_DHCP_CLIENT_EVENT_RENEW = 4, +}; + +enum { + SD_DHCP_OPTION_PAD = 0, + SD_DHCP_OPTION_SUBNET_MASK = 1, + SD_DHCP_OPTION_TIME_OFFSET = 2, + SD_DHCP_OPTION_ROUTER = 3, + SD_DHCP_OPTION_DOMAIN_NAME_SERVER = 6, + SD_DHCP_OPTION_HOST_NAME = 12, + SD_DHCP_OPTION_BOOT_FILE_SIZE = 13, + SD_DHCP_OPTION_DOMAIN_NAME = 15, + SD_DHCP_OPTION_ROOT_PATH = 17, + SD_DHCP_OPTION_ENABLE_IP_FORWARDING = 19, + SD_DHCP_OPTION_ENABLE_IP_FORWARDING_NL = 20, + SD_DHCP_OPTION_POLICY_FILTER = 21, + SD_DHCP_OPTION_INTERFACE_MDR = 22, + SD_DHCP_OPTION_INTERFACE_TTL = 23, + SD_DHCP_OPTION_INTERFACE_MTU_AGING_TIMEOUT = 24, + SD_DHCP_OPTION_INTERFACE_MTU = 26, + SD_DHCP_OPTION_BROADCAST = 28, + SD_DHCP_OPTION_STATIC_ROUTE = 33, + SD_DHCP_OPTION_NTP_SERVER = 42, + SD_DHCP_OPTION_VENDOR_SPECIFIC = 43, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS = 50, + SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME = 51, + SD_DHCP_OPTION_OVERLOAD = 52, + SD_DHCP_OPTION_MESSAGE_TYPE = 53, + SD_DHCP_OPTION_SERVER_IDENTIFIER = 54, + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST = 55, + SD_DHCP_OPTION_ERROR_MESSAGE = 56, + SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE = 57, + SD_DHCP_OPTION_RENEWAL_T1_TIME = 58, + SD_DHCP_OPTION_REBINDING_T2_TIME = 59, + SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER = 60, + SD_DHCP_OPTION_CLIENT_IDENTIFIER = 61, + SD_DHCP_OPTION_FQDN = 81, + SD_DHCP_OPTION_NEW_POSIX_TIMEZONE = 100, + SD_DHCP_OPTION_NEW_TZDB_TIMEZONE = 101, + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE = 121, + SD_DHCP_OPTION_PRIVATE_BASE = 224, + SD_DHCP_OPTION_PRIVATE_LAST = 254, + SD_DHCP_OPTION_END = 255, +}; + +typedef struct sd_dhcp_client sd_dhcp_client; + +typedef void (*sd_dhcp_client_callback_t)(sd_dhcp_client *client, int event, void *userdata); +int sd_dhcp_client_set_callback( + sd_dhcp_client *client, + sd_dhcp_client_callback_t cb, + void *userdata); + +int sd_dhcp_client_set_request_option( + sd_dhcp_client *client, + uint8_t option); +int sd_dhcp_client_set_request_address( + sd_dhcp_client *client, + const struct in_addr *last_address); +int sd_dhcp_client_set_request_broadcast( + sd_dhcp_client *client, + int broadcast); +int sd_dhcp_client_set_index( + sd_dhcp_client *client, + int interface_index); +int sd_dhcp_client_set_mac( + sd_dhcp_client *client, + const uint8_t *addr, + size_t addr_len, + uint16_t arp_type); +int sd_dhcp_client_set_client_id( + sd_dhcp_client *client, + uint8_t type, + const uint8_t *data, + size_t data_len); +int sd_dhcp_client_set_iaid_duid( + sd_dhcp_client *client, + uint32_t iaid, + uint16_t duid_type, + const void *duid, + size_t duid_len); +int sd_dhcp_client_get_client_id( + sd_dhcp_client *client, + uint8_t *type, + const uint8_t **data, + size_t *data_len); +int sd_dhcp_client_set_mtu( + sd_dhcp_client *client, + uint32_t mtu); +int sd_dhcp_client_set_hostname( + sd_dhcp_client *client, + const char *hostname); +int sd_dhcp_client_set_vendor_class_identifier( + sd_dhcp_client *client, + const char *vci); +int sd_dhcp_client_get_lease( + sd_dhcp_client *client, + sd_dhcp_lease **ret); + +int sd_dhcp_client_stop(sd_dhcp_client *client); +int sd_dhcp_client_start(sd_dhcp_client *client); + +sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client); +sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client); + +int sd_dhcp_client_new(sd_dhcp_client **ret); + +int sd_dhcp_client_attach_event( + sd_dhcp_client *client, + sd_event *event, + int64_t priority); +int sd_dhcp_client_detach_event(sd_dhcp_client *client); +sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_client, sd_dhcp_client_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-dhcp-lease.h b/src/libsystemd/include/systemd/sd-dhcp-lease.h new file mode 100644 index 0000000000..2f565ca825 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-dhcp-lease.h @@ -0,0 +1,67 @@ +#ifndef foosddhcpleasehfoo +#define foosddhcpleasehfoo + +/*** + This file is part of systemd. + + Copyright (C) 2013 Intel Corporation. All rights reserved. + Copyright (C) 2014 Tom Gundersen + + 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 +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dhcp_lease sd_dhcp_lease; +typedef struct sd_dhcp_route sd_dhcp_route; + +sd_dhcp_lease *sd_dhcp_lease_ref(sd_dhcp_lease *lease); +sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease); + +int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint32_t *lifetime); +int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint32_t *t1); +int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint32_t *t2); +int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu); +int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname); +int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname); +int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path); +int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes); +int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len); +int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len); +int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **timezone); + +int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination); +int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length); +int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_lease, sd_dhcp_lease_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-dhcp-server.h b/src/libsystemd/include/systemd/sd-dhcp-server.h new file mode 100644 index 0000000000..bbb2bb203c --- /dev/null +++ b/src/libsystemd/include/systemd/sd-dhcp-server.h @@ -0,0 +1,65 @@ +#ifndef foosddhcpserverhfoo +#define foosddhcpserverhfoo + +/*** + This file is part of systemd. + + Copyright (C) 2013 Intel Corporation. All rights reserved. + Copyright (C) 2014 Tom Gundersen + + 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 + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dhcp_server sd_dhcp_server; + +int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex); + +sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server); +sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server); + +int sd_dhcp_server_attach_event(sd_dhcp_server *client, sd_event *event, int64_t priority); +int sd_dhcp_server_detach_event(sd_dhcp_server *client); +sd_event *sd_dhcp_server_get_event(sd_dhcp_server *client); + +int sd_dhcp_server_is_running(sd_dhcp_server *server); + +int sd_dhcp_server_start(sd_dhcp_server *server); +int sd_dhcp_server_stop(sd_dhcp_server *server); + +int sd_dhcp_server_configure_pool(sd_dhcp_server *server, struct in_addr *address, unsigned char prefixlen, uint32_t offset, uint32_t size); + +int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *timezone); +int sd_dhcp_server_set_dns(sd_dhcp_server *server, const struct in_addr ntp[], unsigned n); +int sd_dhcp_server_set_ntp(sd_dhcp_server *server, const struct in_addr dns[], unsigned n); +int sd_dhcp_server_set_emit_router(sd_dhcp_server *server, int enabled); + +int sd_dhcp_server_set_max_lease_time(sd_dhcp_server *server, uint32_t t); +int sd_dhcp_server_set_default_lease_time(sd_dhcp_server *server, uint32_t t); + +int sd_dhcp_server_forcerenew(sd_dhcp_server *server); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_server, sd_dhcp_server_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-dhcp6-client.h b/src/libsystemd/include/systemd/sd-dhcp6-client.h new file mode 100644 index 0000000000..6bcd9862c9 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-dhcp6-client.h @@ -0,0 +1,135 @@ +#ifndef foosddhcp6clienthfoo +#define foosddhcp6clienthfoo + +/*** + This file is part of systemd. + + Copyright (C) 2014 Intel Corporation. All rights reserved. + + 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 +#include + +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_DHCP6_CLIENT_EVENT_STOP = 0, + SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE = 10, + SD_DHCP6_CLIENT_EVENT_RETRANS_MAX = 11, + SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE = 12, + SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST = 13, +}; + +enum { + SD_DHCP6_OPTION_CLIENTID = 1, + SD_DHCP6_OPTION_SERVERID = 2, + SD_DHCP6_OPTION_IA_NA = 3, + SD_DHCP6_OPTION_IA_TA = 4, + SD_DHCP6_OPTION_IAADDR = 5, + SD_DHCP6_OPTION_ORO = 6, + SD_DHCP6_OPTION_PREFERENCE = 7, + SD_DHCP6_OPTION_ELAPSED_TIME = 8, + SD_DHCP6_OPTION_RELAY_MSG = 9, + /* option code 10 is unassigned */ + SD_DHCP6_OPTION_AUTH = 11, + SD_DHCP6_OPTION_UNICAST = 12, + SD_DHCP6_OPTION_STATUS_CODE = 13, + SD_DHCP6_OPTION_RAPID_COMMIT = 14, + SD_DHCP6_OPTION_USER_CLASS = 15, + SD_DHCP6_OPTION_VENDOR_CLASS = 16, + SD_DHCP6_OPTION_VENDOR_OPTS = 17, + SD_DHCP6_OPTION_INTERFACE_ID = 18, + SD_DHCP6_OPTION_RECONF_MSG = 19, + SD_DHCP6_OPTION_RECONF_ACCEPT = 20, + + SD_DHCP6_OPTION_DNS_SERVERS = 23, /* RFC 3646 */ + SD_DHCP6_OPTION_DOMAIN_LIST = 24, /* RFC 3646 */ + + SD_DHCP6_OPTION_SNTP_SERVERS = 31, /* RFC 4075, deprecated */ + + /* option code 35 is unassigned */ + + SD_DHCP6_OPTION_NTP_SERVER = 56, /* RFC 5908 */ + + /* option codes 89-142 are unassigned */ + /* option codes 144-65535 are unassigned */ +}; + +typedef struct sd_dhcp6_client sd_dhcp6_client; + +typedef void (*sd_dhcp6_client_callback_t)(sd_dhcp6_client *client, int event, void *userdata); +int sd_dhcp6_client_set_callback( + sd_dhcp6_client *client, + sd_dhcp6_client_callback_t cb, + void *userdata); + +int sd_dhcp6_client_set_index( + sd_dhcp6_client *client, + int interface_index); +int sd_dhcp6_client_set_local_address( + sd_dhcp6_client *client, + const struct in6_addr *local_address); +int sd_dhcp6_client_set_mac( + sd_dhcp6_client *client, + const uint8_t *addr, + size_t addr_len, + uint16_t arp_type); +int sd_dhcp6_client_set_duid( + sd_dhcp6_client *client, + uint16_t duid_type, + const void *duid, + size_t duid_len); +int sd_dhcp6_client_set_iaid( + sd_dhcp6_client *client, + uint32_t iaid); +int sd_dhcp6_client_set_information_request( + sd_dhcp6_client *client, + int enabled); +int sd_dhcp6_client_get_information_request( + sd_dhcp6_client *client, + int *enabled); +int sd_dhcp6_client_set_request_option( + sd_dhcp6_client *client, + uint16_t option); + +int sd_dhcp6_client_get_lease( + sd_dhcp6_client *client, + sd_dhcp6_lease **ret); + +int sd_dhcp6_client_stop(sd_dhcp6_client *client); +int sd_dhcp6_client_start(sd_dhcp6_client *client); +int sd_dhcp6_client_is_running(sd_dhcp6_client *client); +int sd_dhcp6_client_attach_event( + sd_dhcp6_client *client, + sd_event *event, + int64_t priority); +int sd_dhcp6_client_detach_event(sd_dhcp6_client *client); +sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client); +sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client); +sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client); +int sd_dhcp6_client_new(sd_dhcp6_client **ret); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_client, sd_dhcp6_client_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-dhcp6-lease.h b/src/libsystemd/include/systemd/sd-dhcp6-lease.h new file mode 100644 index 0000000000..184fbb8e0d --- /dev/null +++ b/src/libsystemd/include/systemd/sd-dhcp6-lease.h @@ -0,0 +1,52 @@ +#ifndef foosddhcp6leasehfoo +#define foosddhcp6leasehfoo + +/*** + This file is part of systemd. + + Copyright (C) 2014 Tom Gundersen + Copyright (C) 2014-2015 Intel Corporation. All rights reserved. + + 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 + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dhcp6_lease sd_dhcp6_lease; + +void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease); +int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, + struct in6_addr *addr, + uint32_t *lifetime_preferred, + uint32_t *lifetime_valid); + +int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, struct in6_addr **addrs); +int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains); +int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, + struct in6_addr **addrs); +int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn); + +sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease); +sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_lease, sd_dhcp6_lease_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-event.h b/src/libsystemd/include/systemd/sd-event.h new file mode 100644 index 0000000000..531ace1c34 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-event.h @@ -0,0 +1,142 @@ +#ifndef foosdeventhfoo +#define foosdeventhfoo + +/*** + This file is part of systemd. + + Copyright 2013 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 +#include +#include +#include + +#include "_sd-common.h" + +/* + Why is this better than pure epoll? + + - Supports event source prioritization + - Scales better with a large number of time events because it does not require one timerfd each + - Automatically tries to coalesce timer events system-wide + - Handles signals and child PIDs +*/ + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_event sd_event; +typedef struct sd_event_source sd_event_source; + +enum { + SD_EVENT_OFF = 0, + SD_EVENT_ON = 1, + SD_EVENT_ONESHOT = -1 +}; + +enum { + SD_EVENT_INITIAL, + SD_EVENT_ARMED, + SD_EVENT_PENDING, + SD_EVENT_RUNNING, + SD_EVENT_EXITING, + SD_EVENT_FINISHED, + SD_EVENT_PREPARING +}; + +enum { + /* And everything in-between and outside is good too */ + SD_EVENT_PRIORITY_IMPORTANT = -100, + SD_EVENT_PRIORITY_NORMAL = 0, + SD_EVENT_PRIORITY_IDLE = 100 +}; + +typedef int (*sd_event_handler_t)(sd_event_source *s, void *userdata); +typedef int (*sd_event_io_handler_t)(sd_event_source *s, int fd, uint32_t revents, void *userdata); +typedef int (*sd_event_time_handler_t)(sd_event_source *s, uint64_t usec, void *userdata); +typedef int (*sd_event_signal_handler_t)(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata); +#if defined __USE_POSIX199309 || defined __USE_XOPEN_EXTENDED +typedef int (*sd_event_child_handler_t)(sd_event_source *s, const siginfo_t *si, void *userdata); +#else +typedef void* sd_event_child_handler_t; +#endif + +int sd_event_default(sd_event **e); + +int sd_event_new(sd_event **e); +sd_event* sd_event_ref(sd_event *e); +sd_event* sd_event_unref(sd_event *e); + +int sd_event_add_io(sd_event *e, sd_event_source **s, int fd, uint32_t events, sd_event_io_handler_t callback, void *userdata); +int sd_event_add_time(sd_event *e, sd_event_source **s, clockid_t clock, uint64_t usec, uint64_t accuracy, sd_event_time_handler_t callback, void *userdata); +int sd_event_add_signal(sd_event *e, sd_event_source **s, int sig, sd_event_signal_handler_t callback, void *userdata); +int sd_event_add_child(sd_event *e, sd_event_source **s, pid_t pid, int options, sd_event_child_handler_t callback, void *userdata); +int sd_event_add_defer(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata); +int sd_event_add_post(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata); +int sd_event_add_exit(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata); + +int sd_event_prepare(sd_event *e); +int sd_event_wait(sd_event *e, uint64_t usec); +int sd_event_dispatch(sd_event *e); +int sd_event_run(sd_event *e, uint64_t usec); +int sd_event_loop(sd_event *e); +int sd_event_exit(sd_event *e, int code); + +int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec); + +int sd_event_get_fd(sd_event *e); +int sd_event_get_state(sd_event *e); +int sd_event_get_tid(sd_event *e, pid_t *tid); +int sd_event_get_exit_code(sd_event *e, int *code); +int sd_event_set_watchdog(sd_event *e, int b); +int sd_event_get_watchdog(sd_event *e); + +sd_event_source* sd_event_source_ref(sd_event_source *s); +sd_event_source* sd_event_source_unref(sd_event_source *s); + +sd_event *sd_event_source_get_event(sd_event_source *s); +void* sd_event_source_get_userdata(sd_event_source *s); +void* sd_event_source_set_userdata(sd_event_source *s, void *userdata); + +int sd_event_source_set_description(sd_event_source *s, const char *description); +int sd_event_source_get_description(sd_event_source *s, const char **description); +int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t callback); +int sd_event_source_get_pending(sd_event_source *s); +int sd_event_source_get_priority(sd_event_source *s, int64_t *priority); +int sd_event_source_set_priority(sd_event_source *s, int64_t priority); +int sd_event_source_get_enabled(sd_event_source *s, int *enabled); +int sd_event_source_set_enabled(sd_event_source *s, int enabled); +int sd_event_source_get_io_fd(sd_event_source *s); +int sd_event_source_set_io_fd(sd_event_source *s, int fd); +int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events); +int sd_event_source_set_io_events(sd_event_source *s, uint32_t events); +int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revents); +int sd_event_source_get_time(sd_event_source *s, uint64_t *usec); +int sd_event_source_set_time(sd_event_source *s, uint64_t usec); +int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *usec); +int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec); +int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock); +int sd_event_source_get_signal(sd_event_source *s); +int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid); + +/* Define helpers so that __attribute__((cleanup(sd_event_unrefp))) and similar may be used. */ +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event, sd_event_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event_source, sd_event_source_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-hwdb.h b/src/libsystemd/include/systemd/sd-hwdb.h new file mode 100644 index 0000000000..7105920492 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-hwdb.h @@ -0,0 +1,49 @@ +#ifndef foosdhwdbhfoo +#define foosdhwdbhfoo + +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2014 Tom Gundersen + + 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 "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_hwdb sd_hwdb; + +sd_hwdb *sd_hwdb_ref(sd_hwdb *hwdb); +sd_hwdb *sd_hwdb_unref(sd_hwdb *hwdb); + +int sd_hwdb_new(sd_hwdb **ret); + +int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **value); + +int sd_hwdb_seek(sd_hwdb *hwdb, const char *modalias); +int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **key, const char **value); + +/* the inverse condition avoids ambiguity of dangling 'else' after the macro */ +#define SD_HWDB_FOREACH_PROPERTY(hwdb, modalias, key, value) \ + if (sd_hwdb_seek(hwdb, modalias) < 0) { } \ + else while (sd_hwdb_enumerate(hwdb, &(key), &(value)) > 0) + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_hwdb, sd_hwdb_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-id128.h b/src/libsystemd/include/systemd/sd-id128.h new file mode 100644 index 0000000000..4dff0b9b81 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-id128.h @@ -0,0 +1,115 @@ +#ifndef foosdid128hfoo +#define foosdid128hfoo + +/*** + This file is part of systemd. + + Copyright 2011 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 + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* 128-bit ID APIs. See sd-id128(3) for more information. */ + +typedef union sd_id128 sd_id128_t; + +union sd_id128 { + uint8_t bytes[16]; + uint64_t qwords[2]; +}; + +#define SD_ID128_STRING_MAX 33 + +char *sd_id128_to_string(sd_id128_t id, char s[SD_ID128_STRING_MAX]); + +int sd_id128_from_string(const char *s, sd_id128_t *ret); + +int sd_id128_randomize(sd_id128_t *ret); + +int sd_id128_get_machine(sd_id128_t *ret); + +int sd_id128_get_boot(sd_id128_t *ret); + +#define SD_ID128_MAKE(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \ + ((const sd_id128_t) { .bytes = { 0x##v0, 0x##v1, 0x##v2, 0x##v3, 0x##v4, 0x##v5, 0x##v6, 0x##v7, \ + 0x##v8, 0x##v9, 0x##v10, 0x##v11, 0x##v12, 0x##v13, 0x##v14, 0x##v15 }}) + +#define SD_ID128_ARRAY(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \ + { .bytes = { 0x##v0, 0x##v1, 0x##v2, 0x##v3, 0x##v4, 0x##v5, 0x##v6, 0x##v7, \ + 0x##v8, 0x##v9, 0x##v10, 0x##v11, 0x##v12, 0x##v13, 0x##v14, 0x##v15 }} + +/* Note that SD_ID128_FORMAT_VAL will evaluate the passed argument 16 + * times. It is hence not a good idea to call this macro with an + * expensive function as parameter or an expression with side + * effects */ + +#define SD_ID128_FORMAT_STR "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" +#define SD_ID128_FORMAT_VAL(x) (x).bytes[0], (x).bytes[1], (x).bytes[2], (x).bytes[3], (x).bytes[4], (x).bytes[5], (x).bytes[6], (x).bytes[7], (x).bytes[8], (x).bytes[9], (x).bytes[10], (x).bytes[11], (x).bytes[12], (x).bytes[13], (x).bytes[14], (x).bytes[15] + +#define SD_ID128_CONST_STR(x) \ + ((const char[SD_ID128_STRING_MAX]) { \ + ((x).bytes[0] >> 4) >= 10 ? 'a' + ((x).bytes[0] >> 4) - 10 : '0' + ((x).bytes[0] >> 4), \ + ((x).bytes[0] & 15) >= 10 ? 'a' + ((x).bytes[0] & 15) - 10 : '0' + ((x).bytes[0] & 15), \ + ((x).bytes[1] >> 4) >= 10 ? 'a' + ((x).bytes[1] >> 4) - 10 : '0' + ((x).bytes[1] >> 4), \ + ((x).bytes[1] & 15) >= 10 ? 'a' + ((x).bytes[1] & 15) - 10 : '0' + ((x).bytes[1] & 15), \ + ((x).bytes[2] >> 4) >= 10 ? 'a' + ((x).bytes[2] >> 4) - 10 : '0' + ((x).bytes[2] >> 4), \ + ((x).bytes[2] & 15) >= 10 ? 'a' + ((x).bytes[2] & 15) - 10 : '0' + ((x).bytes[2] & 15), \ + ((x).bytes[3] >> 4) >= 10 ? 'a' + ((x).bytes[3] >> 4) - 10 : '0' + ((x).bytes[3] >> 4), \ + ((x).bytes[3] & 15) >= 10 ? 'a' + ((x).bytes[3] & 15) - 10 : '0' + ((x).bytes[3] & 15), \ + ((x).bytes[4] >> 4) >= 10 ? 'a' + ((x).bytes[4] >> 4) - 10 : '0' + ((x).bytes[4] >> 4), \ + ((x).bytes[4] & 15) >= 10 ? 'a' + ((x).bytes[4] & 15) - 10 : '0' + ((x).bytes[4] & 15), \ + ((x).bytes[5] >> 4) >= 10 ? 'a' + ((x).bytes[5] >> 4) - 10 : '0' + ((x).bytes[5] >> 4), \ + ((x).bytes[5] & 15) >= 10 ? 'a' + ((x).bytes[5] & 15) - 10 : '0' + ((x).bytes[5] & 15), \ + ((x).bytes[6] >> 4) >= 10 ? 'a' + ((x).bytes[6] >> 4) - 10 : '0' + ((x).bytes[6] >> 4), \ + ((x).bytes[6] & 15) >= 10 ? 'a' + ((x).bytes[6] & 15) - 10 : '0' + ((x).bytes[6] & 15), \ + ((x).bytes[7] >> 4) >= 10 ? 'a' + ((x).bytes[7] >> 4) - 10 : '0' + ((x).bytes[7] >> 4), \ + ((x).bytes[7] & 15) >= 10 ? 'a' + ((x).bytes[7] & 15) - 10 : '0' + ((x).bytes[7] & 15), \ + ((x).bytes[8] >> 4) >= 10 ? 'a' + ((x).bytes[8] >> 4) - 10 : '0' + ((x).bytes[8] >> 4), \ + ((x).bytes[8] & 15) >= 10 ? 'a' + ((x).bytes[8] & 15) - 10 : '0' + ((x).bytes[8] & 15), \ + ((x).bytes[9] >> 4) >= 10 ? 'a' + ((x).bytes[9] >> 4) - 10 : '0' + ((x).bytes[9] >> 4), \ + ((x).bytes[9] & 15) >= 10 ? 'a' + ((x).bytes[9] & 15) - 10 : '0' + ((x).bytes[9] & 15), \ + ((x).bytes[10] >> 4) >= 10 ? 'a' + ((x).bytes[10] >> 4) - 10 : '0' + ((x).bytes[10] >> 4), \ + ((x).bytes[10] & 15) >= 10 ? 'a' + ((x).bytes[10] & 15) - 10 : '0' + ((x).bytes[10] & 15), \ + ((x).bytes[11] >> 4) >= 10 ? 'a' + ((x).bytes[11] >> 4) - 10 : '0' + ((x).bytes[11] >> 4), \ + ((x).bytes[11] & 15) >= 10 ? 'a' + ((x).bytes[11] & 15) - 10 : '0' + ((x).bytes[11] & 15), \ + ((x).bytes[12] >> 4) >= 10 ? 'a' + ((x).bytes[12] >> 4) - 10 : '0' + ((x).bytes[12] >> 4), \ + ((x).bytes[12] & 15) >= 10 ? 'a' + ((x).bytes[12] & 15) - 10 : '0' + ((x).bytes[12] & 15), \ + ((x).bytes[13] >> 4) >= 10 ? 'a' + ((x).bytes[13] >> 4) - 10 : '0' + ((x).bytes[13] >> 4), \ + ((x).bytes[13] & 15) >= 10 ? 'a' + ((x).bytes[13] & 15) - 10 : '0' + ((x).bytes[13] & 15), \ + ((x).bytes[14] >> 4) >= 10 ? 'a' + ((x).bytes[14] >> 4) - 10 : '0' + ((x).bytes[14] >> 4), \ + ((x).bytes[14] & 15) >= 10 ? 'a' + ((x).bytes[14] & 15) - 10 : '0' + ((x).bytes[14] & 15), \ + ((x).bytes[15] >> 4) >= 10 ? 'a' + ((x).bytes[15] >> 4) - 10 : '0' + ((x).bytes[15] >> 4), \ + ((x).bytes[15] & 15) >= 10 ? 'a' + ((x).bytes[15] & 15) - 10 : '0' + ((x).bytes[15] & 15), \ + 0 }) + +_sd_pure_ static __inline__ int sd_id128_equal(sd_id128_t a, sd_id128_t b) { + return memcmp(&a, &b, 16) == 0; +} + +_sd_pure_ static __inline__ int sd_id128_is_null(sd_id128_t a) { + return a.qwords[0] == 0 && a.qwords[1] == 0; +} + +#define SD_ID128_NULL ((const sd_id128_t) { .qwords = { 0, 0 }}) + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-ipv4acd.h b/src/libsystemd/include/systemd/sd-ipv4acd.h new file mode 100644 index 0000000000..93db7a4a6c --- /dev/null +++ b/src/libsystemd/include/systemd/sd-ipv4acd.h @@ -0,0 +1,60 @@ +#ifndef foosdipv4acdfoo +#define foosdipv4acdfoo + +/*** + This file is part of systemd. + + Copyright (C) 2014 Axis Communications AB. All rights reserved. + Copyright (C) 2015 Tom Gundersen + + 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 + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_IPV4ACD_EVENT_STOP = 0, + SD_IPV4ACD_EVENT_BIND = 1, + SD_IPV4ACD_EVENT_CONFLICT = 2, +}; + +typedef struct sd_ipv4acd sd_ipv4acd; +typedef void (*sd_ipv4acd_callback_t)(sd_ipv4acd *ll, int event, void *userdata); + +int sd_ipv4acd_detach_event(sd_ipv4acd *ll); +int sd_ipv4acd_attach_event(sd_ipv4acd *ll, sd_event *event, int64_t priority); +int sd_ipv4acd_get_address(sd_ipv4acd *ll, struct in_addr *address); +int sd_ipv4acd_set_callback(sd_ipv4acd *ll, sd_ipv4acd_callback_t cb, void *userdata); +int sd_ipv4acd_set_mac(sd_ipv4acd *ll, const struct ether_addr *addr); +int sd_ipv4acd_set_index(sd_ipv4acd *ll, int interface_index); +int sd_ipv4acd_set_address(sd_ipv4acd *ll, const struct in_addr *address); +int sd_ipv4acd_is_running(sd_ipv4acd *ll); +int sd_ipv4acd_start(sd_ipv4acd *ll); +int sd_ipv4acd_stop(sd_ipv4acd *ll); +sd_ipv4acd *sd_ipv4acd_ref(sd_ipv4acd *ll); +sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *ll); +int sd_ipv4acd_new(sd_ipv4acd **ret); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ipv4acd, sd_ipv4acd_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-ipv4ll.h b/src/libsystemd/include/systemd/sd-ipv4ll.h new file mode 100644 index 0000000000..9167623167 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-ipv4ll.h @@ -0,0 +1,60 @@ +#ifndef foosdipv4llfoo +#define foosdipv4llfoo + +/*** + This file is part of systemd. + + Copyright (C) 2014 Axis Communications AB. All rights reserved. + + 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 + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_IPV4LL_EVENT_STOP = 0, + SD_IPV4LL_EVENT_BIND = 1, + SD_IPV4LL_EVENT_CONFLICT = 2, +}; + +typedef struct sd_ipv4ll sd_ipv4ll; +typedef void (*sd_ipv4ll_callback_t)(sd_ipv4ll *ll, int event, void *userdata); + +int sd_ipv4ll_detach_event(sd_ipv4ll *ll); +int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority); +int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address); +int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata); +int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr); +int sd_ipv4ll_set_index(sd_ipv4ll *ll, int interface_index); +int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address); +int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, unsigned seed); +int sd_ipv4ll_is_running(sd_ipv4ll *ll); +int sd_ipv4ll_start(sd_ipv4ll *ll); +int sd_ipv4ll_stop(sd_ipv4ll *ll); +sd_ipv4ll *sd_ipv4ll_ref(sd_ipv4ll *ll); +sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll); +int sd_ipv4ll_new (sd_ipv4ll **ret); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ipv4ll, sd_ipv4ll_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-journal.h b/src/libsystemd/include/systemd/sd-journal.h new file mode 100644 index 0000000000..3e61feb81f --- /dev/null +++ b/src/libsystemd/include/systemd/sd-journal.h @@ -0,0 +1,175 @@ +#ifndef foosdjournalhfoo +#define foosdjournalhfoo + +/*** + This file is part of systemd. + + Copyright 2011 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 +#include +#include +#include + +#include + +#include "_sd-common.h" + +/* Journal APIs. See sd-journal(3) for more information. */ + +_SD_BEGIN_DECLARATIONS; + +/* Write to daemon */ +int sd_journal_print(int priority, const char *format, ...) _sd_printf_(2, 3); +int sd_journal_printv(int priority, const char *format, va_list ap) _sd_printf_(2, 0); +int sd_journal_send(const char *format, ...) _sd_printf_(1, 0) _sd_sentinel_; +int sd_journal_sendv(const struct iovec *iov, int n); +int sd_journal_perror(const char *message); + +/* Used by the macros below. You probably don't want to call this directly. */ +int sd_journal_print_with_location(int priority, const char *file, const char *line, const char *func, const char *format, ...) _sd_printf_(5, 6); +int sd_journal_printv_with_location(int priority, const char *file, const char *line, const char *func, const char *format, va_list ap) _sd_printf_(5, 0); +int sd_journal_send_with_location(const char *file, const char *line, const char *func, const char *format, ...) _sd_printf_(4, 0) _sd_sentinel_; +int sd_journal_sendv_with_location(const char *file, const char *line, const char *func, const struct iovec *iov, int n); +int sd_journal_perror_with_location(const char *file, const char *line, const char *func, const char *message); + +/* implicitly add code location to messages sent, if this is enabled */ +#ifndef SD_JOURNAL_SUPPRESS_LOCATION + +#define sd_journal_print(priority, ...) sd_journal_print_with_location(priority, "CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, __VA_ARGS__) +#define sd_journal_printv(priority, format, ap) sd_journal_printv_with_location(priority, "CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, format, ap) +#define sd_journal_send(...) sd_journal_send_with_location("CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, __VA_ARGS__) +#define sd_journal_sendv(iovec, n) sd_journal_sendv_with_location("CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, iovec, n) +#define sd_journal_perror(message) sd_journal_perror_with_location("CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, message) + +#endif + +int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix); + +/* Browse journal stream */ + +typedef struct sd_journal sd_journal; + +/* Open flags */ +enum { + SD_JOURNAL_LOCAL_ONLY = 1 << 0, + SD_JOURNAL_RUNTIME_ONLY = 1 << 1, + SD_JOURNAL_SYSTEM = 1 << 2, + SD_JOURNAL_CURRENT_USER = 1 << 3, + SD_JOURNAL_OS_ROOT = 1 << 4, + + SD_JOURNAL_SYSTEM_ONLY = SD_JOURNAL_SYSTEM /* deprecated name */ +}; + +/* Wakeup event types */ +enum { + SD_JOURNAL_NOP, + SD_JOURNAL_APPEND, + SD_JOURNAL_INVALIDATE +}; + +int sd_journal_open(sd_journal **ret, int flags); +int sd_journal_open_directory(sd_journal **ret, const char *path, int flags); +int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags); +int sd_journal_open_files(sd_journal **ret, const char **paths, int flags); +int sd_journal_open_files_fd(sd_journal **ret, int fds[], unsigned n_fds, int flags); +int sd_journal_open_container(sd_journal **ret, const char *machine, int flags); /* deprecated */ +void sd_journal_close(sd_journal *j); + +int sd_journal_previous(sd_journal *j); +int sd_journal_next(sd_journal *j); + +int sd_journal_previous_skip(sd_journal *j, uint64_t skip); +int sd_journal_next_skip(sd_journal *j, uint64_t skip); + +int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret); +int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id); + +int sd_journal_set_data_threshold(sd_journal *j, size_t sz); +int sd_journal_get_data_threshold(sd_journal *j, size_t *sz); + +int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *l); +int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *l); +void sd_journal_restart_data(sd_journal *j); + +int sd_journal_add_match(sd_journal *j, const void *data, size_t size); +int sd_journal_add_disjunction(sd_journal *j); +int sd_journal_add_conjunction(sd_journal *j); +void sd_journal_flush_matches(sd_journal *j); + +int sd_journal_seek_head(sd_journal *j); +int sd_journal_seek_tail(sd_journal *j); +int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec); +int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec); +int sd_journal_seek_cursor(sd_journal *j, const char *cursor); + +int sd_journal_get_cursor(sd_journal *j, char **cursor); +int sd_journal_test_cursor(sd_journal *j, const char *cursor); + +int sd_journal_get_cutoff_realtime_usec(sd_journal *j, uint64_t *from, uint64_t *to); +int sd_journal_get_cutoff_monotonic_usec(sd_journal *j, const sd_id128_t boot_id, uint64_t *from, uint64_t *to); + +int sd_journal_get_usage(sd_journal *j, uint64_t *bytes); + +int sd_journal_query_unique(sd_journal *j, const char *field); +int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l); +void sd_journal_restart_unique(sd_journal *j); + +int sd_journal_enumerate_fields(sd_journal *j, const char **field); +void sd_journal_restart_fields(sd_journal *j); + +int sd_journal_get_fd(sd_journal *j); +int sd_journal_get_events(sd_journal *j); +int sd_journal_get_timeout(sd_journal *j, uint64_t *timeout_usec); +int sd_journal_process(sd_journal *j); +int sd_journal_wait(sd_journal *j, uint64_t timeout_usec); +int sd_journal_reliable_fd(sd_journal *j); + +int sd_journal_get_catalog(sd_journal *j, char **text); +int sd_journal_get_catalog_for_message_id(sd_id128_t id, char **text); + +int sd_journal_has_runtime_files(sd_journal *j); +int sd_journal_has_persistent_files(sd_journal *j); + +/* The inverse condition avoids ambiguity of dangling 'else' after the macro */ +#define SD_JOURNAL_FOREACH(j) \ + if (sd_journal_seek_head(j) < 0) { } \ + else while (sd_journal_next(j) > 0) + +/* The inverse condition avoids ambiguity of dangling 'else' after the macro */ +#define SD_JOURNAL_FOREACH_BACKWARDS(j) \ + if (sd_journal_seek_tail(j) < 0) { } \ + else while (sd_journal_previous(j) > 0) + +/* Iterate through the data fields of the current journal entry */ +#define SD_JOURNAL_FOREACH_DATA(j, data, l) \ + for (sd_journal_restart_data(j); sd_journal_enumerate_data((j), &(data), &(l)) > 0; ) + +/* Iterate through the all known values of a specific field */ +#define SD_JOURNAL_FOREACH_UNIQUE(j, data, l) \ + for (sd_journal_restart_unique(j); sd_journal_enumerate_unique((j), &(data), &(l)) > 0; ) + +/* Iterate through all known field names */ +#define SD_JOURNAL_FOREACH_FIELD(j, field) \ + for (sd_journal_restart_fields(j); sd_journal_enumerate_fields((j), &(field)) > 0; ) + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_journal, sd_journal_close); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-lldp.h b/src/libsystemd/include/systemd/sd-lldp.h new file mode 100644 index 0000000000..391e7c2a2e --- /dev/null +++ b/src/libsystemd/include/systemd/sd-lldp.h @@ -0,0 +1,177 @@ +#ifndef foosdlldphfoo +#define foosdlldphfoo + +/*** + This file is part of systemd. + + Copyright (C) 2014 Tom Gundersen + Copyright (C) 2014 Susant Sahani + + 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 + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_lldp sd_lldp; +typedef struct sd_lldp_neighbor sd_lldp_neighbor; + +/* IEEE 802.3AB Clause 9: TLV Types */ +enum { + SD_LLDP_TYPE_END = 0, + SD_LLDP_TYPE_CHASSIS_ID = 1, + SD_LLDP_TYPE_PORT_ID = 2, + SD_LLDP_TYPE_TTL = 3, + SD_LLDP_TYPE_PORT_DESCRIPTION = 4, + SD_LLDP_TYPE_SYSTEM_NAME = 5, + SD_LLDP_TYPE_SYSTEM_DESCRIPTION = 6, + SD_LLDP_TYPE_SYSTEM_CAPABILITIES = 7, + SD_LLDP_TYPE_MGMT_ADDRESS = 8, + SD_LLDP_TYPE_PRIVATE = 127, +}; + +/* IEEE 802.3AB Clause 9.5.2: Chassis subtypes */ +enum { + SD_LLDP_CHASSIS_SUBTYPE_RESERVED = 0, + SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT = 1, + SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS = 2, + SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT = 3, + SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS = 4, + SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS = 5, + SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME = 6, + SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED = 7, +}; + +/* IEEE 802.3AB Clause 9.5.3: Port subtype */ +enum { + SD_LLDP_PORT_SUBTYPE_RESERVED = 0, + SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS = 1, + SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT = 2, + SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS = 3, + SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS = 4, + SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME = 5, + SD_LLDP_PORT_SUBTYPE_AGENT_CIRCUIT_ID = 6, + SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED = 7, +}; + +enum { + SD_LLDP_SYSTEM_CAPABILITIES_OTHER = 1 << 0, + SD_LLDP_SYSTEM_CAPABILITIES_REPEATER = 1 << 1, + SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE = 1 << 2, + SD_LLDP_SYSTEM_CAPABILITIES_WLAN_AP = 1 << 3, + SD_LLDP_SYSTEM_CAPABILITIES_ROUTER = 1 << 4, + SD_LLDP_SYSTEM_CAPABILITIES_PHONE = 1 << 5, + SD_LLDP_SYSTEM_CAPABILITIES_DOCSIS = 1 << 6, + SD_LLDP_SYSTEM_CAPABILITIES_STATION = 1 << 7, + SD_LLDP_SYSTEM_CAPABILITIES_CVLAN = 1 << 8, + SD_LLDP_SYSTEM_CAPABILITIES_SVLAN = 1 << 9, + SD_LLDP_SYSTEM_CAPABILITIES_TPMR = 1 << 10, +}; + +#define SD_LLDP_SYSTEM_CAPABILITIES_ALL ((uint16_t) -1) + +#define SD_LLDP_SYSTEM_CAPABILITIES_ALL_ROUTERS \ + ((uint16_t) \ + (SD_LLDP_SYSTEM_CAPABILITIES_REPEATER| \ + SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE| \ + SD_LLDP_SYSTEM_CAPABILITIES_WLAN_AP| \ + SD_LLDP_SYSTEM_CAPABILITIES_ROUTER| \ + SD_LLDP_SYSTEM_CAPABILITIES_DOCSIS| \ + SD_LLDP_SYSTEM_CAPABILITIES_CVLAN| \ + SD_LLDP_SYSTEM_CAPABILITIES_SVLAN| \ + SD_LLDP_SYSTEM_CAPABILITIES_TPMR)) + +#define SD_LLDP_OUI_802_1 (uint8_t[]) { 0x00, 0x80, 0xc2 } +#define SD_LLDP_OUI_802_3 (uint8_t[]) { 0x00, 0x12, 0x0f } + +enum { + SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID = 1, + SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID = 2, + SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME = 3, + SD_LLDP_OUI_802_1_SUBTYPE_PROTOCOL_IDENTITY = 4, + SD_LLDP_OUI_802_1_SUBTYPE_VID_USAGE_DIGEST = 5, + SD_LLDP_OUI_802_1_SUBTYPE_MANAGEMENT_VID = 6, + SD_LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION = 7, +}; + +typedef enum sd_lldp_event { + SD_LLDP_EVENT_ADDED = 'a', + SD_LLDP_EVENT_REMOVED = 'r', + SD_LLDP_EVENT_UPDATED = 'u', + SD_LLDP_EVENT_REFRESHED = 'f', +} sd_lldp_event; + +typedef void (*sd_lldp_callback_t)(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata); + +int sd_lldp_new(sd_lldp **ret, int ifindex); +sd_lldp* sd_lldp_unref(sd_lldp *lldp); + +int sd_lldp_start(sd_lldp *lldp); +int sd_lldp_stop(sd_lldp *lldp); + +int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority); +int sd_lldp_detach_event(sd_lldp *lldp); + +int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata); + +/* Controls how much and what to store in the neighbors database */ +int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t n); +int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask); +int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *address); + +int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***neighbors); + +int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size); +sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n); +sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n); + +/* Access to LLDP frame metadata */ +int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address); +int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address); +int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size); + +/* High-level, direct, parsed out field access. These fields exist at most once, hence may be queried directly. */ +int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size); +int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size); +int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret); +int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret); +int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret); + +/* Low-level, iterative TLV access. This is for evertyhing else, it iteratively goes through all available TLVs + * (including the ones covered with the calls above), and allows multiple TLVs for the same fields. */ +int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n); +int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n); +int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type); +int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type); +int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[3], uint8_t *subtype); +int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[3], uint8_t subtype); +int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp, sd_lldp_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp_neighbor, sd_lldp_neighbor_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-login.h b/src/libsystemd/include/systemd/sd-login.h new file mode 100644 index 0000000000..e3ecbd8378 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-login.h @@ -0,0 +1,245 @@ +#ifndef foosdloginhfoo +#define foosdloginhfoo + +/*** + This file is part of systemd. + + Copyright 2011 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 + +#include "_sd-common.h" + +/* + * A few points: + * + * Instead of returning an empty string array or empty uid array, we + * may return NULL. + * + * Free the data the library returns with libc free(). String arrays + * are NULL terminated, and you need to free the array itself, in + * addition to the strings contained. + * + * We return error codes as negative errno, kernel-style. On success, we + * return 0 or positive. + * + * These functions access data in /proc, /sys/fs/cgroup, and /run. All + * of these are virtual file systems; therefore, accesses are + * relatively cheap. + * + * See sd-login(3) for more information. + */ + +_SD_BEGIN_DECLARATIONS; + +/* Get session from PID. Note that 'shared' processes of a user are + * not attached to a session, but only attached to a user. This will + * return an error for system processes and 'shared' processes of a + * user. */ +int sd_pid_get_session(pid_t pid, char **session); + +/* Get UID of the owner of the session of the PID (or in case the + * process is a 'shared' user process, the UID of that user is + * returned). This will not return the UID of the process, but rather + * the UID of the owner of the cgroup that the process is in. This will + * return an error for system processes. */ +int sd_pid_get_owner_uid(pid_t pid, uid_t *uid); + +/* Get systemd non-slice unit (i.e. service) name from PID, for system + * services. This will return an error for non-service processes. */ +int sd_pid_get_unit(pid_t pid, char **unit); + +/* Get systemd non-slice unit (i.e. service) name from PID, for user + * services. This will return an error for non-user-service + * processes. */ +int sd_pid_get_user_unit(pid_t pid, char **unit); + +/* Get slice name from PID. */ +int sd_pid_get_slice(pid_t pid, char **slice); + +/* Get user slice name from PID. */ +int sd_pid_get_user_slice(pid_t pid, char **slice); + +/* Get machine name from PID, for processes assigned to a VM or + * container. This will return an error for non-machine processes. */ +int sd_pid_get_machine_name(pid_t pid, char **machine); + +/* Get the control group from a PID, relative to the root of the + * hierarchy. */ +int sd_pid_get_cgroup(pid_t pid, char **cgroup); + +/* Similar to sd_pid_get_session(), but retrieves data about the peer + * of a connected AF_UNIX socket */ +int sd_peer_get_session(int fd, char **session); + +/* Similar to sd_pid_get_owner_uid(), but retrieves data about the peer of + * a connected AF_UNIX socket */ +int sd_peer_get_owner_uid(int fd, uid_t *uid); + +/* Similar to sd_pid_get_unit(), but retrieves data about the peer of + * a connected AF_UNIX socket */ +int sd_peer_get_unit(int fd, char **unit); + +/* Similar to sd_pid_get_user_unit(), but retrieves data about the peer of + * a connected AF_UNIX socket */ +int sd_peer_get_user_unit(int fd, char **unit); + +/* Similar to sd_pid_get_slice(), but retrieves data about the peer of + * a connected AF_UNIX socket */ +int sd_peer_get_slice(int fd, char **slice); + +/* Similar to sd_pid_get_user_slice(), but retrieves data about the peer of + * a connected AF_UNIX socket */ +int sd_peer_get_user_slice(int fd, char **slice); + +/* Similar to sd_pid_get_machine_name(), but retrieves data about the + * peer of a connected AF_UNIX socket */ +int sd_peer_get_machine_name(int fd, char **machine); + +/* Similar to sd_pid_get_cgroup(), but retrieves data about the peer + * of a connected AF_UNIX socket. */ +int sd_peer_get_cgroup(pid_t pid, char **cgroup); + +/* Get state from UID. Possible states: offline, lingering, online, active, closing */ +int sd_uid_get_state(uid_t uid, char **state); + +/* Return primary session of user, if there is any */ +int sd_uid_get_display(uid_t uid, char **session); + +/* Return 1 if UID has session on seat. If require_active is true, this will + * look for active sessions only. */ +int sd_uid_is_on_seat(uid_t uid, int require_active, const char *seat); + +/* Return sessions of user. If require_active is true, this will look for + * active sessions only. Returns the number of sessions. + * If sessions is NULL, this will just return the number of sessions. */ +int sd_uid_get_sessions(uid_t uid, int require_active, char ***sessions); + +/* Return seats of user is on. If require_active is true, this will look for + * active seats only. Returns the number of seats. + * If seats is NULL, this will just return the number of seats. */ +int sd_uid_get_seats(uid_t uid, int require_active, char ***seats); + +/* Return 1 if the session is active. */ +int sd_session_is_active(const char *session); + +/* Return 1 if the session is remote. */ +int sd_session_is_remote(const char *session); + +/* Get state from session. Possible states: online, active, closing. + * This function is a more generic version of sd_session_is_active(). */ +int sd_session_get_state(const char *session, char **state); + +/* Determine user ID of session */ +int sd_session_get_uid(const char *session, uid_t *uid); + +/* Determine seat of session */ +int sd_session_get_seat(const char *session, char **seat); + +/* Determine the (PAM) service name this session was registered by. */ +int sd_session_get_service(const char *session, char **service); + +/* Determine the type of this session, i.e. one of "tty", "x11", "wayland", "mir" or "unspecified". */ +int sd_session_get_type(const char *session, char **type); + +/* Determine the class of this session, i.e. one of "user", "greeter" or "lock-screen". */ +int sd_session_get_class(const char *session, char **clazz); + +/* Determine the desktop brand of this session, i.e. something like "GNOME", "KDE" or "systemd-console". */ +int sd_session_get_desktop(const char *session, char **desktop); + +/* Determine the X11 display of this session. */ +int sd_session_get_display(const char *session, char **display); + +/* Determine the remote host of this session. */ +int sd_session_get_remote_host(const char *session, char **remote_host); + +/* Determine the remote user of this session (if provided by PAM). */ +int sd_session_get_remote_user(const char *session, char **remote_user); + +/* Determine the TTY of this session. */ +int sd_session_get_tty(const char *session, char **display); + +/* Determine the VT number of this session. */ +int sd_session_get_vt(const char *session, unsigned *vtnr); + +/* Return active session and user of seat */ +int sd_seat_get_active(const char *seat, char **session, uid_t *uid); + +/* Return sessions and users on seat. Returns number of sessions. + * If sessions is NULL, this returns only the number of sessions. */ +int sd_seat_get_sessions(const char *seat, char ***sessions, uid_t **uid, unsigned *n_uids); + +/* Return whether the seat is multi-session capable */ +int sd_seat_can_multi_session(const char *seat); + +/* Return whether the seat is TTY capable, i.e. suitable for showing console UIs */ +int sd_seat_can_tty(const char *seat); + +/* Return whether the seat is graphics capable, i.e. suitable for showing graphical UIs */ +int sd_seat_can_graphical(const char *seat); + +/* Return the class of machine */ +int sd_machine_get_class(const char *machine, char **clazz); + +/* Return the list if host-side network interface indices of a machine */ +int sd_machine_get_ifindices(const char *machine, int **ifindices); + +/* Get all seats, store in *seats. Returns the number of seats. If + * seats is NULL, this only returns the number of seats. */ +int sd_get_seats(char ***seats); + +/* Get all sessions, store in *sessions. Returns the number of + * sessions. If sessions is NULL, this only returns the number of sessions. */ +int sd_get_sessions(char ***sessions); + +/* Get all logged in users, store in *users. Returns the number of + * users. If users is NULL, this only returns the number of users. */ +int sd_get_uids(uid_t **users); + +/* Get all running virtual machines/containers */ +int sd_get_machine_names(char ***machines); + +/* Monitor object */ +typedef struct sd_login_monitor sd_login_monitor; + +/* Create a new monitor. Category must be NULL, "seat", "session", + * "uid", or "machine" to get monitor events for the specific category + * (or all). */ +int sd_login_monitor_new(const char *category, sd_login_monitor** ret); + +/* Destroys the passed monitor. Returns NULL. */ +sd_login_monitor* sd_login_monitor_unref(sd_login_monitor *m); + +/* Flushes the monitor */ +int sd_login_monitor_flush(sd_login_monitor *m); + +/* Get FD from monitor */ +int sd_login_monitor_get_fd(sd_login_monitor *m); + +/* Get poll() mask to monitor */ +int sd_login_monitor_get_events(sd_login_monitor *m); + +/* Get timeout for poll(), as usec value relative to CLOCK_MONOTONIC's epoch */ +int sd_login_monitor_get_timeout(sd_login_monitor *m, uint64_t *timeout_usec); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_login_monitor, sd_login_monitor_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-messages.h b/src/libsystemd/include/systemd/sd-messages.h new file mode 100644 index 0000000000..1865e0492f --- /dev/null +++ b/src/libsystemd/include/systemd/sd-messages.h @@ -0,0 +1,91 @@ +#ifndef foosdmessageshfoo +#define foosdmessageshfoo + +/*** + This file is part of systemd. + + Copyright 2012 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 "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* Hey! If you add a new message here, you *must* also update the + * message catalog with an appropriate explanation */ + +/* And if you add a new ID here, make sure to generate a random one + * with journalctl --new-id128. Do not use any other IDs, and do not + * count them up manually. */ + +#define SD_MESSAGE_JOURNAL_START SD_ID128_MAKE(f7,73,79,a8,49,0b,40,8b,be,5f,69,40,50,5a,77,7b) +#define SD_MESSAGE_JOURNAL_STOP SD_ID128_MAKE(d9,3f,b3,c9,c2,4d,45,1a,97,ce,a6,15,ce,59,c0,0b) +#define SD_MESSAGE_JOURNAL_DROPPED SD_ID128_MAKE(a5,96,d6,fe,7b,fa,49,94,82,8e,72,30,9e,95,d6,1e) +#define SD_MESSAGE_JOURNAL_MISSED SD_ID128_MAKE(e9,bf,28,e6,e8,34,48,1b,b6,f4,8f,54,8a,d1,36,06) +#define SD_MESSAGE_JOURNAL_USAGE SD_ID128_MAKE(ec,38,7f,57,7b,84,4b,8f,a9,48,f3,3c,ad,9a,75,e6) + +#define SD_MESSAGE_COREDUMP SD_ID128_MAKE(fc,2e,22,bc,6e,e6,47,b6,b9,07,29,ab,34,a2,50,b1) + +#define SD_MESSAGE_SESSION_START SD_ID128_MAKE(8d,45,62,0c,1a,43,48,db,b1,74,10,da,57,c6,0c,66) +#define SD_MESSAGE_SESSION_STOP SD_ID128_MAKE(33,54,93,94,24,b4,45,6d,98,02,ca,83,33,ed,42,4a) +#define SD_MESSAGE_SEAT_START SD_ID128_MAKE(fc,be,fc,5d,a2,3d,42,80,93,f9,7c,82,a9,29,0f,7b) +#define SD_MESSAGE_SEAT_STOP SD_ID128_MAKE(e7,85,2b,fe,46,78,4e,d0,ac,cd,e0,4b,c8,64,c2,d5) +#define SD_MESSAGE_MACHINE_START SD_ID128_MAKE(24,d8,d4,45,25,73,40,24,96,06,83,81,a6,31,2d,f2) +#define SD_MESSAGE_MACHINE_STOP SD_ID128_MAKE(58,43,2b,d3,ba,ce,47,7c,b5,14,b5,63,81,b8,a7,58) + +#define SD_MESSAGE_TIME_CHANGE SD_ID128_MAKE(c7,a7,87,07,9b,35,4e,aa,a9,e7,7b,37,18,93,cd,27) +#define SD_MESSAGE_TIMEZONE_CHANGE SD_ID128_MAKE(45,f8,2f,4a,ef,7a,4b,bf,94,2c,e8,61,d1,f2,09,90) + +#define SD_MESSAGE_STARTUP_FINISHED SD_ID128_MAKE(b0,7a,24,9c,d0,24,41,4a,82,dd,00,cd,18,13,78,ff) + +#define SD_MESSAGE_SLEEP_START SD_ID128_MAKE(6b,bd,95,ee,97,79,41,e4,97,c4,8b,e2,7c,25,41,28) +#define SD_MESSAGE_SLEEP_STOP SD_ID128_MAKE(88,11,e6,df,2a,8e,40,f5,8a,94,ce,a2,6f,8e,bf,14) + +#define SD_MESSAGE_SHUTDOWN SD_ID128_MAKE(98,26,88,66,d1,d5,4a,49,9c,4e,98,92,1d,93,bc,40) + +#define SD_MESSAGE_UNIT_STARTING SD_ID128_MAKE(7d,49,58,e8,42,da,4a,75,8f,6c,1c,dc,7b,36,dc,c5) +#define SD_MESSAGE_UNIT_STARTED SD_ID128_MAKE(39,f5,34,79,d3,a0,45,ac,8e,11,78,62,48,23,1f,bf) +#define SD_MESSAGE_UNIT_STOPPING SD_ID128_MAKE(de,5b,42,6a,63,be,47,a7,b6,ac,3e,aa,c8,2e,2f,6f) +#define SD_MESSAGE_UNIT_STOPPED SD_ID128_MAKE(9d,1a,aa,27,d6,01,40,bd,96,36,54,38,aa,d2,02,86) +#define SD_MESSAGE_UNIT_FAILED SD_ID128_MAKE(be,02,cf,68,55,d2,42,8b,a4,0d,f7,e9,d0,22,f0,3d) +#define SD_MESSAGE_UNIT_RELOADING SD_ID128_MAKE(d3,4d,03,7f,ff,18,47,e6,ae,66,9a,37,0e,69,47,25) +#define SD_MESSAGE_UNIT_RELOADED SD_ID128_MAKE(7b,05,eb,c6,68,38,42,22,ba,a8,88,11,79,cf,da,54) + +#define SD_MESSAGE_SPAWN_FAILED SD_ID128_MAKE(64,12,57,65,1c,1b,4e,c9,a8,62,4d,7a,40,a9,e1,e7) + +#define SD_MESSAGE_FORWARD_SYSLOG_MISSED SD_ID128_MAKE(00,27,22,9c,a0,64,41,81,a7,6c,4e,92,45,8a,fa,2e) + +#define SD_MESSAGE_OVERMOUNTING SD_ID128_MAKE(1d,ee,03,69,c7,fc,47,36,b7,09,9b,38,ec,b4,6e,e7) + +#define SD_MESSAGE_LID_OPENED SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,6f) +#define SD_MESSAGE_LID_CLOSED SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,70) +#define SD_MESSAGE_SYSTEM_DOCKED SD_ID128_MAKE(f5,f4,16,b8,62,07,4b,28,92,7a,48,c3,ba,7d,51,ff) +#define SD_MESSAGE_SYSTEM_UNDOCKED SD_ID128_MAKE(51,e1,71,bd,58,52,48,56,81,10,14,4c,51,7c,ca,53) +#define SD_MESSAGE_POWER_KEY SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,71) +#define SD_MESSAGE_SUSPEND_KEY SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,72) +#define SD_MESSAGE_HIBERNATE_KEY SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,73) + +#define SD_MESSAGE_INVALID_CONFIGURATION SD_ID128_MAKE(c7,72,d2,4e,9a,88,4c,be,b9,ea,12,62,5c,30,6c,01) + +#define SD_MESSAGE_DNSSEC_FAILURE SD_ID128_MAKE(16,75,d7,f1,72,17,40,98,b1,10,8b,f8,c7,dc,8f,5d) +#define SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED SD_ID128_MAKE(4d,44,08,cf,d0,d1,44,85,91,84,d1,e6,5d,7c,8a,65) +#define SD_MESSAGE_DNSSEC_DOWNGRADE SD_ID128_MAKE(36,db,2d,fa,5a,90,45,e1,bd,4a,f5,f9,3e,1c,f0,57) + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-ndisc.h b/src/libsystemd/include/systemd/sd-ndisc.h new file mode 100644 index 0000000000..c77a435d17 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-ndisc.h @@ -0,0 +1,84 @@ +#ifndef foosdndiscfoo +#define foosdndiscfoo + +/*** + This file is part of systemd. + + Copyright (C) 2014 Intel Corporation. All rights reserved. + + 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 + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_NDISC_EVENT_STOP = 0, + SD_NDISC_EVENT_TIMEOUT = 1, +}; + +typedef struct sd_ndisc sd_ndisc; + +typedef void(*sd_ndisc_router_callback_t)(sd_ndisc *nd, uint8_t flags, const struct in6_addr *gateway, unsigned lifetime, int pref, void *userdata); +typedef void(*sd_ndisc_prefix_onlink_callback_t)(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen, + unsigned lifetime, void *userdata); +typedef void(*sd_ndisc_prefix_autonomous_callback_t)(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen, + unsigned lifetime_prefered, unsigned lifetime_valid, void *userdata); +typedef void(*sd_ndisc_callback_t)(sd_ndisc *nd, int event, void *userdata); + +int sd_ndisc_set_callback(sd_ndisc *nd, + sd_ndisc_router_callback_t rcb, + sd_ndisc_prefix_onlink_callback_t plcb, + sd_ndisc_prefix_autonomous_callback_t pacb, + sd_ndisc_callback_t cb, + void *userdata); +int sd_ndisc_set_index(sd_ndisc *nd, int interface_index); +int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr); + +int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority); +int sd_ndisc_detach_event(sd_ndisc *nd); +sd_event *sd_ndisc_get_event(sd_ndisc *nd); + +sd_ndisc *sd_ndisc_ref(sd_ndisc *nd); +sd_ndisc *sd_ndisc_unref(sd_ndisc *nd); +int sd_ndisc_new(sd_ndisc **ret); + +int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu); + +int sd_ndisc_stop(sd_ndisc *nd); +int sd_ndisc_router_discovery_start(sd_ndisc *nd); + +#define SD_NDISC_ADDRESS_FORMAT_STR "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x" + +#define SD_NDISC_ADDRESS_FORMAT_VAL(address) \ + be16toh((address).s6_addr16[0]), \ + be16toh((address).s6_addr16[1]), \ + be16toh((address).s6_addr16[2]), \ + be16toh((address).s6_addr16[3]), \ + be16toh((address).s6_addr16[4]), \ + be16toh((address).s6_addr16[5]), \ + be16toh((address).s6_addr16[6]), \ + be16toh((address).s6_addr16[7]) + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc, sd_ndisc_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-netlink.h b/src/libsystemd/include/systemd/sd-netlink.h new file mode 100644 index 0000000000..c4cefe4e30 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-netlink.h @@ -0,0 +1,163 @@ +#ifndef foosdnetlinkhfoo +#define foosdnetlinkhfoo + +/*** + This file is part of systemd. + + Copyright 2013 Tom Gundersen + + 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 +#include +#include +#include + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_netlink sd_netlink; +typedef struct sd_netlink_message sd_netlink_message; + +/* callback */ + +typedef int (*sd_netlink_message_handler_t)(sd_netlink *nl, sd_netlink_message *m, void *userdata); + +/* bus */ +int sd_netlink_new_from_netlink(sd_netlink **nl, int fd); +int sd_netlink_open(sd_netlink **nl); +int sd_netlink_open_fd(sd_netlink **nl, int fd); +int sd_netlink_inc_rcvbuf(const sd_netlink *const rtnl, const int size); + +sd_netlink *sd_netlink_ref(sd_netlink *nl); +sd_netlink *sd_netlink_unref(sd_netlink *nl); + +int sd_netlink_send(sd_netlink *nl, sd_netlink_message *message, uint32_t *serial); +int sd_netlink_call_async(sd_netlink *nl, sd_netlink_message *message, + sd_netlink_message_handler_t callback, + void *userdata, uint64_t usec, uint32_t *serial); +int sd_netlink_call_async_cancel(sd_netlink *nl, uint32_t serial); +int sd_netlink_call(sd_netlink *nl, sd_netlink_message *message, uint64_t timeout, + sd_netlink_message **reply); + +int sd_netlink_get_events(sd_netlink *nl); +int sd_netlink_get_timeout(sd_netlink *nl, uint64_t *timeout); +int sd_netlink_process(sd_netlink *nl, sd_netlink_message **ret); +int sd_netlink_wait(sd_netlink *nl, uint64_t timeout); + +int sd_netlink_add_match(sd_netlink *nl, uint16_t match, sd_netlink_message_handler_t c, void *userdata); +int sd_netlink_remove_match(sd_netlink *nl, uint16_t match, sd_netlink_message_handler_t c, void *userdata); + +int sd_netlink_attach_event(sd_netlink *nl, sd_event *e, int64_t priority); +int sd_netlink_detach_event(sd_netlink *nl); + +int sd_netlink_message_append_string(sd_netlink_message *m, unsigned short type, const char *data); +int sd_netlink_message_append_flag(sd_netlink_message *m, unsigned short type); +int sd_netlink_message_append_u8(sd_netlink_message *m, unsigned short type, uint8_t data); +int sd_netlink_message_append_u16(sd_netlink_message *m, unsigned short type, uint16_t data); +int sd_netlink_message_append_u32(sd_netlink_message *m, unsigned short type, uint32_t data); +int sd_netlink_message_append_data(sd_netlink_message *m, unsigned short type, const void *data, size_t len); +int sd_netlink_message_append_in_addr(sd_netlink_message *m, unsigned short type, const struct in_addr *data); +int sd_netlink_message_append_in6_addr(sd_netlink_message *m, unsigned short type, const struct in6_addr *data); +int sd_netlink_message_append_ether_addr(sd_netlink_message *m, unsigned short type, const struct ether_addr *data); +int sd_netlink_message_append_cache_info(sd_netlink_message *m, unsigned short type, const struct ifa_cacheinfo *info); + +int sd_netlink_message_open_container(sd_netlink_message *m, unsigned short type); +int sd_netlink_message_open_container_union(sd_netlink_message *m, unsigned short type, const char *key); +int sd_netlink_message_close_container(sd_netlink_message *m); + +int sd_netlink_message_read_string(sd_netlink_message *m, unsigned short type, const char **data); +int sd_netlink_message_read_u8(sd_netlink_message *m, unsigned short type, uint8_t *data); +int sd_netlink_message_read_u16(sd_netlink_message *m, unsigned short type, uint16_t *data); +int sd_netlink_message_read_u32(sd_netlink_message *m, unsigned short type, uint32_t *data); +int sd_netlink_message_read_ether_addr(sd_netlink_message *m, unsigned short type, struct ether_addr *data); +int sd_netlink_message_read_cache_info(sd_netlink_message *m, unsigned short type, struct ifa_cacheinfo *info); +int sd_netlink_message_read_in_addr(sd_netlink_message *m, unsigned short type, struct in_addr *data); +int sd_netlink_message_read_in6_addr(sd_netlink_message *m, unsigned short type, struct in6_addr *data); +int sd_netlink_message_enter_container(sd_netlink_message *m, unsigned short type); +int sd_netlink_message_exit_container(sd_netlink_message *m); + +int sd_netlink_message_rewind(sd_netlink_message *m); + +sd_netlink_message *sd_netlink_message_next(sd_netlink_message *m); + +sd_netlink_message *sd_netlink_message_ref(sd_netlink_message *m); +sd_netlink_message *sd_netlink_message_unref(sd_netlink_message *m); + +int sd_netlink_message_request_dump(sd_netlink_message *m, int dump); +int sd_netlink_message_is_error(sd_netlink_message *m); +int sd_netlink_message_get_errno(sd_netlink_message *m); +int sd_netlink_message_get_type(sd_netlink_message *m, uint16_t *type); +int sd_netlink_message_set_flags(sd_netlink_message *m, uint16_t flags); +int sd_netlink_message_is_broadcast(sd_netlink_message *m); + +/* rtnl */ + +int sd_rtnl_message_new_link(sd_netlink *nl, sd_netlink_message **ret, uint16_t msg_type, int index); +int sd_rtnl_message_new_addr_update(sd_netlink *nl, sd_netlink_message **ret, int index, int family); +int sd_rtnl_message_new_addr(sd_netlink *nl, sd_netlink_message **ret, uint16_t msg_type, int index, int family); +int sd_rtnl_message_new_route(sd_netlink *nl, sd_netlink_message **ret, uint16_t nlmsg_type, int rtm_family, unsigned char rtm_protocol); +int sd_rtnl_message_new_neigh(sd_netlink *nl, sd_netlink_message **ret, uint16_t msg_type, int index, int nda_family); + +int sd_rtnl_message_get_family(sd_netlink_message *m, int *family); + +int sd_rtnl_message_addr_set_prefixlen(sd_netlink_message *m, unsigned char prefixlen); +int sd_rtnl_message_addr_set_scope(sd_netlink_message *m, unsigned char scope); +int sd_rtnl_message_addr_set_flags(sd_netlink_message *m, unsigned char flags); +int sd_rtnl_message_addr_get_family(sd_netlink_message *m, int *family); +int sd_rtnl_message_addr_get_prefixlen(sd_netlink_message *m, unsigned char *prefixlen); +int sd_rtnl_message_addr_get_scope(sd_netlink_message *m, unsigned char *scope); +int sd_rtnl_message_addr_get_flags(sd_netlink_message *m, unsigned char *flags); +int sd_rtnl_message_addr_get_ifindex(sd_netlink_message *m, int *ifindex); + +int sd_rtnl_message_link_set_flags(sd_netlink_message *m, unsigned flags, unsigned change); +int sd_rtnl_message_link_set_type(sd_netlink_message *m, unsigned type); +int sd_rtnl_message_link_set_family(sd_netlink_message *m, unsigned family); +int sd_rtnl_message_link_get_ifindex(sd_netlink_message *m, int *ifindex); +int sd_rtnl_message_link_get_flags(sd_netlink_message *m, unsigned *flags); +int sd_rtnl_message_link_get_type(sd_netlink_message *m, unsigned short *type); + +int sd_rtnl_message_route_set_dst_prefixlen(sd_netlink_message *m, unsigned char prefixlen); +int sd_rtnl_message_route_set_src_prefixlen(sd_netlink_message *m, unsigned char prefixlen); +int sd_rtnl_message_route_set_scope(sd_netlink_message *m, unsigned char scope); +int sd_rtnl_message_route_set_flags(sd_netlink_message *m, unsigned flags); +int sd_rtnl_message_route_set_table(sd_netlink_message *m, unsigned char table); +int sd_rtnl_message_route_get_flags(sd_netlink_message *m, unsigned *flags); +int sd_rtnl_message_route_get_family(sd_netlink_message *m, int *family); +int sd_rtnl_message_route_set_family(sd_netlink_message *m, int family); +int sd_rtnl_message_route_get_protocol(sd_netlink_message *m, unsigned char *protocol); +int sd_rtnl_message_route_get_scope(sd_netlink_message *m, unsigned char *scope); +int sd_rtnl_message_route_get_tos(sd_netlink_message *m, unsigned char *tos); +int sd_rtnl_message_route_get_table(sd_netlink_message *m, unsigned char *table); +int sd_rtnl_message_route_get_dst_prefixlen(sd_netlink_message *m, unsigned char *dst_len); +int sd_rtnl_message_route_get_src_prefixlen(sd_netlink_message *m, unsigned char *src_len); + +int sd_rtnl_message_neigh_set_flags(sd_netlink_message *m, uint8_t flags); +int sd_rtnl_message_neigh_set_state(sd_netlink_message *m, uint16_t state); +int sd_rtnl_message_neigh_get_family(sd_netlink_message *m, int *family); +int sd_rtnl_message_neigh_get_ifindex(sd_netlink_message *m, int *family); +int sd_rtnl_message_neigh_get_state(sd_netlink_message *m, uint16_t *state); +int sd_rtnl_message_neigh_get_flags(sd_netlink_message *m, uint8_t *flags); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_netlink, sd_netlink_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_netlink_message, sd_netlink_message_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-network.h b/src/libsystemd/include/systemd/sd-network.h new file mode 100644 index 0000000000..0f13e2bae7 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-network.h @@ -0,0 +1,176 @@ +#ifndef foosdnetworkhfoo +#define foosdnetworkhfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + Copyright 2014 Tom Gundersen + + 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 + +#include "_sd-common.h" + +/* + * A few points: + * + * Instead of returning an empty string array or empty integer array, we + * may return NULL. + * + * Free the data the library returns with libc free(). String arrays + * are NULL terminated, and you need to free the array itself in + * addition to the strings contained. + * + * We return error codes as negative errno, kernel-style. On success, we + * return 0 or positive. + * + * These functions access data in /run. This is a virtual file system; + * therefore, accesses are relatively cheap. + * + * See sd-network(3) for more information. + */ + +_SD_BEGIN_DECLARATIONS; + +/* Get overall operational state + * Possible states: down, up, dormant, carrier, degraded, routable + * Possible return codes: + * -ENODATA: networkd is not aware of any links + */ +int sd_network_get_operational_state(char **state); + +/* Get DNS entries for all links. These are string representations of + * IP addresses */ +int sd_network_get_dns(char ***dns); + +/* Get NTP entries for all links. These are domain names or string + * representations of IP addresses */ +int sd_network_get_ntp(char ***ntp); + +/* Get the search domains for all links. */ +int sd_network_get_search_domains(char ***domains); + +/* Get the search domains for all links. */ +int sd_network_get_route_domains(char ***domains); + +/* Get setup state from ifindex. + * Possible states: + * pending: udev is still processing the link, we don't yet know if we will manage it + * failed: networkd failed to manage the link + * configuring: in the process of retrieving configuration or configuring the link + * configured: link configured successfully + * unmanaged: networkd is not handling the link + * linger: the link is gone, but has not yet been dropped by networkd + * Possible return codes: + * -ENODATA: networkd is not aware of the link + */ +int sd_network_link_get_setup_state(int ifindex, char **state); + +/* Get operational state from ifindex. + * Possible states: + * off: the device is powered down + * no-carrier: the device is powered up, but it does not yet have a carrier + * dormant: the device has a carrier, but is not yet ready for normal traffic + * carrier: the link has a carrier + * degraded: the link has carrier and addresses valid on the local link configured + * routable: the link has carrier and routable address configured + * Possible return codes: + * -ENODATA: networkd is not aware of the link + */ +int sd_network_link_get_operational_state(int ifindex, char **state); + +/* Get path to .network file applied to link */ +int sd_network_link_get_network_file(int ifindex, char **filename); + +/* Get DNS entries for a given link. These are string representations of + * IP addresses */ +int sd_network_link_get_dns(int ifindex, char ***ret); + +/* Get NTP entries for a given link. These are domain names or string + * representations of IP addresses */ +int sd_network_link_get_ntp(int ifindex, char ***ret); + +/* Indicates whether or not LLMNR should be enabled for the link + * Possible levels of support: yes, no, resolve + * Possible return codes: + * -ENODATA: networkd is not aware of the link + */ +int sd_network_link_get_llmnr(int ifindex, char **llmnr); + +/* Indicates whether or not MulticastDNS should be enabled for the + * link. + * Possible levels of support: yes, no, resolve + * Possible return codes: + * -ENODATA: networkd is not aware of the link + */ +int sd_network_link_get_mdns(int ifindex, char **mdns); + +/* Indicates whether or not DNSSEC should be enabled for the link + * Possible levels of support: yes, no, allow-downgrade + * Possible return codes: + * -ENODATA: networkd is not aware of the link + */ +int sd_network_link_get_dnssec(int ifindex, char **dnssec); + +/* Returns the list of per-interface DNSSEC negative trust anchors + * Possible return codes: + * -ENODATA: networkd is not aware of the link, or has no such data + */ +int sd_network_link_get_dnssec_negative_trust_anchors(int ifindex, char ***nta); + +/* Get the search DNS domain names for a given link. */ +int sd_network_link_get_search_domains(int ifindex, char ***domains); + +/* Get the route DNS domain names for a given link. */ +int sd_network_link_get_route_domains(int ifindex, char ***domains); + +/* Get the carrier interface indexes to which current link is bound to. */ +int sd_network_link_get_carrier_bound_to(int ifindex, int **ifindexes); + +/* Get the CARRIERS that are bound to current link. */ +int sd_network_link_get_carrier_bound_by(int ifindex, int **ifindexes); + +/* Get the timezone that was learnt on a specific link. */ +int sd_network_link_get_timezone(int ifindex, char **timezone); + +/* Monitor object */ +typedef struct sd_network_monitor sd_network_monitor; + +/* Create a new monitor. Category must be NULL, "links" or "leases". */ +int sd_network_monitor_new(sd_network_monitor **ret, const char *category); + +/* Destroys the passed monitor. Returns NULL. */ +sd_network_monitor* sd_network_monitor_unref(sd_network_monitor *m); + +/* Flushes the monitor */ +int sd_network_monitor_flush(sd_network_monitor *m); + +/* Get FD from monitor */ +int sd_network_monitor_get_fd(sd_network_monitor *m); + +/* Get poll() mask to monitor */ +int sd_network_monitor_get_events(sd_network_monitor *m); + +/* Get timeout for poll(), as usec value relative to CLOCK_MONOTONIC's epoch */ +int sd_network_monitor_get_timeout(sd_network_monitor *m, uint64_t *timeout_usec); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_network_monitor, sd_network_monitor_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-path.h b/src/libsystemd/include/systemd/sd-path.h new file mode 100644 index 0000000000..be6abdcd03 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-path.h @@ -0,0 +1,91 @@ +#ifndef foosdpathhfoo +#define foosdpathhfoo + +/*** + 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 "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + /* Temporary files */ + SD_PATH_TEMPORARY = 0x0ULL, + SD_PATH_TEMPORARY_LARGE, + + /* Vendor supplied data */ + SD_PATH_SYSTEM_BINARIES, + SD_PATH_SYSTEM_INCLUDE, + SD_PATH_SYSTEM_LIBRARY_PRIVATE, + SD_PATH_SYSTEM_LIBRARY_ARCH, + SD_PATH_SYSTEM_SHARED, + SD_PATH_SYSTEM_CONFIGURATION_FACTORY, + SD_PATH_SYSTEM_STATE_FACTORY, + + /* System configuration, runtime, state, ... */ + SD_PATH_SYSTEM_CONFIGURATION, + SD_PATH_SYSTEM_RUNTIME, + SD_PATH_SYSTEM_RUNTIME_LOGS, + SD_PATH_SYSTEM_STATE_PRIVATE, + SD_PATH_SYSTEM_STATE_LOGS, + SD_PATH_SYSTEM_STATE_CACHE, + SD_PATH_SYSTEM_STATE_SPOOL, + + /* Vendor supplied data */ + SD_PATH_USER_BINARIES, + SD_PATH_USER_LIBRARY_PRIVATE, + SD_PATH_USER_LIBRARY_ARCH, + SD_PATH_USER_SHARED, + + /* User configuration, state, runtime ... */ + SD_PATH_USER_CONFIGURATION, /* takes both actual configuration (like /etc) and state (like /var/lib) */ + SD_PATH_USER_RUNTIME, + SD_PATH_USER_STATE_CACHE, + + /* User resources */ + SD_PATH_USER, /* $HOME itself */ + SD_PATH_USER_DOCUMENTS, + SD_PATH_USER_MUSIC, + SD_PATH_USER_PICTURES, + SD_PATH_USER_VIDEOS, + SD_PATH_USER_DOWNLOAD, + SD_PATH_USER_PUBLIC, + SD_PATH_USER_TEMPLATES, + SD_PATH_USER_DESKTOP, + + /* Search paths */ + SD_PATH_SEARCH_BINARIES, + SD_PATH_SEARCH_LIBRARY_PRIVATE, + SD_PATH_SEARCH_LIBRARY_ARCH, + SD_PATH_SEARCH_SHARED, + SD_PATH_SEARCH_CONFIGURATION_FACTORY, + SD_PATH_SEARCH_STATE_FACTORY, + SD_PATH_SEARCH_CONFIGURATION, + + _SD_PATH_MAX, +}; + +int sd_path_home(uint64_t type, const char *suffix, char **path); +int sd_path_search(uint64_t type, const char *suffix, char ***paths); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-resolve.h b/src/libsystemd/include/systemd/sd-resolve.h new file mode 100644 index 0000000000..fe3b910671 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-resolve.h @@ -0,0 +1,117 @@ +#ifndef foosdresolvehfoo +#define foosdresolvehfoo + +/*** + This file is part of systemd. + + Copyright 2005-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 +#include +#include + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* An opaque sd-resolve session structure */ +typedef struct sd_resolve sd_resolve; + +/* An opaque sd-resolve query structure */ +typedef struct sd_resolve_query sd_resolve_query; + +/* A callback on completion */ +typedef int (*sd_resolve_getaddrinfo_handler_t)(sd_resolve_query *q, int ret, const struct addrinfo *ai, void *userdata); +typedef int (*sd_resolve_getnameinfo_handler_t)(sd_resolve_query *q, int ret, const char *host, const char *serv, void *userdata); + +enum { + SD_RESOLVE_GET_HOST = UINT64_C(1), + SD_RESOLVE_GET_SERVICE = UINT64_C(2), + SD_RESOLVE_GET_BOTH = UINT64_C(3), +}; + +int sd_resolve_default(sd_resolve **ret); + +/* Allocate a new sd-resolve session. */ +int sd_resolve_new(sd_resolve **ret); + +/* Free a sd-resolve session. This destroys all attached + * sd_resolve_query objects automatically. */ +sd_resolve* sd_resolve_unref(sd_resolve *resolve); +sd_resolve* sd_resolve_ref(sd_resolve *resolve); + +/* Return the UNIX file descriptor to poll() for events on. Use this + * function to integrate sd-resolve with your custom main loop. */ +int sd_resolve_get_fd(sd_resolve *resolve); + +/* Return the poll() events (a combination of flags like POLLIN, + * POLLOUT, ...) to check for. */ +int sd_resolve_get_events(sd_resolve *resolve); + +/* Return the poll() timeout to pass. Returns (uint64_t) -1 as + * timeout if no timeout is needed. */ +int sd_resolve_get_timeout(sd_resolve *resolve, uint64_t *timeout_usec); + +/* Process pending responses. After this function is called, you can + * get the next completed query object(s) using + * sd_resolve_get_next(). */ +int sd_resolve_process(sd_resolve *resolve); + +/* Wait for a resolve event to complete. */ +int sd_resolve_wait(sd_resolve *resolve, uint64_t timeout_usec); + +int sd_resolve_get_tid(sd_resolve *resolve, pid_t *tid); + +int sd_resolve_attach_event(sd_resolve *resolve, sd_event *e, int64_t priority); +int sd_resolve_detach_event(sd_resolve *resolve); +sd_event *sd_resolve_get_event(sd_resolve *resolve); + +/* Issue a name-to-address query on the specified session. The + * arguments are compatible with those of libc's + * getaddrinfo(3). The function returns a new query object. When the + * query is completed, you may retrieve the results using + * sd_resolve_getaddrinfo_done(). */ +int sd_resolve_getaddrinfo(sd_resolve *resolve, sd_resolve_query **q, const char *node, const char *service, const struct addrinfo *hints, sd_resolve_getaddrinfo_handler_t callback, void *userdata); + +/* Issue an address-to-name query on the specified session. The + * arguments are compatible with those of libc's + * getnameinfo(3). The function returns a new query object. When the + * query is completed, you may retrieve the results using + * sd_resolve_getnameinfo_done(). Set gethost (resp. getserv) to non-zero + * if you want to query the hostname (resp. the service name). */ +int sd_resolve_getnameinfo(sd_resolve *resolve, sd_resolve_query **q, const struct sockaddr *sa, socklen_t salen, int flags, uint64_t get, sd_resolve_getnameinfo_handler_t callback, void *userdata); + +sd_resolve_query *sd_resolve_query_ref(sd_resolve_query* q); +sd_resolve_query *sd_resolve_query_unref(sd_resolve_query* q); + +/* Returns non-zero when the query operation specified by q has been completed. */ +int sd_resolve_query_is_done(sd_resolve_query*q); + +void *sd_resolve_query_get_userdata(sd_resolve_query *q); +void *sd_resolve_query_set_userdata(sd_resolve_query *q, void *userdata); + +sd_resolve *sd_resolve_query_get_resolve(sd_resolve_query *q); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_resolve, sd_resolve_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_resolve_query, sd_resolve_query_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-utf8.h b/src/libsystemd/include/systemd/sd-utf8.h new file mode 100644 index 0000000000..6781983878 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-utf8.h @@ -0,0 +1,32 @@ +#ifndef foosdutf8hfoo +#define foosdutf8hfoo + +/*** + This file is part of systemd. + + Copyright 2013 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 "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +_sd_pure_ const char *sd_utf8_is_valid(const char *s); +_sd_pure_ const char *sd_ascii_is_valid(const char *s); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/libsystemd-internal/Makefile b/src/libsystemd/libsystemd-internal/Makefile new file mode 100644 index 0000000000..6bf0488bf5 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/Makefile @@ -0,0 +1,247 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +libsystemd_internal_la_SOURCES = \ + src/systemd/sd-bus.h \ + src/systemd/sd-bus-protocol.h \ + src/systemd/sd-bus-vtable.h \ + src/systemd/sd-utf8.h \ + src/systemd/sd-event.h \ + src/systemd/sd-netlink.h \ + src/systemd/sd-resolve.h \ + src/systemd/sd-login.h \ + src/systemd/sd-id128.h \ + src/systemd/sd-daemon.h \ + src/systemd/sd-path.h \ + src/systemd/sd-network.h \ + src/systemd/sd-hwdb.h \ + src/systemd/sd-device.h \ + src/libsystemd/libsystemd.sym \ + src/libsystemd/sd-bus/sd-bus.c \ + src/libsystemd/sd-bus/bus-control.c \ + src/libsystemd/sd-bus/bus-control.h \ + src/libsystemd/sd-bus/bus-error.c \ + src/libsystemd/sd-bus/bus-error.h \ + src/libsystemd/sd-bus/bus-common-errors.h \ + src/libsystemd/sd-bus/bus-common-errors.c \ + src/libsystemd/sd-bus/bus-internal.c \ + src/libsystemd/sd-bus/bus-internal.h \ + src/libsystemd/sd-bus/bus-socket.c \ + src/libsystemd/sd-bus/bus-socket.h \ + src/libsystemd/sd-bus/bus-kernel.c \ + src/libsystemd/sd-bus/bus-kernel.h \ + src/libsystemd/sd-bus/bus-container.c \ + src/libsystemd/sd-bus/bus-container.h \ + src/libsystemd/sd-bus/bus-message.c \ + src/libsystemd/sd-bus/bus-message.h \ + src/libsystemd/sd-bus/bus-creds.c \ + src/libsystemd/sd-bus/bus-creds.h \ + src/libsystemd/sd-bus/bus-signature.c \ + src/libsystemd/sd-bus/bus-signature.h \ + src/libsystemd/sd-bus/bus-type.c \ + src/libsystemd/sd-bus/bus-type.h \ + src/libsystemd/sd-bus/bus-match.c \ + src/libsystemd/sd-bus/bus-match.h \ + src/libsystemd/sd-bus/bus-bloom.c \ + src/libsystemd/sd-bus/bus-bloom.h \ + src/libsystemd/sd-bus/bus-introspect.c \ + src/libsystemd/sd-bus/bus-introspect.h \ + src/libsystemd/sd-bus/bus-objects.c \ + src/libsystemd/sd-bus/bus-objects.h \ + src/libsystemd/sd-bus/bus-gvariant.c \ + src/libsystemd/sd-bus/bus-gvariant.h \ + src/libsystemd/sd-bus/bus-convenience.c \ + src/libsystemd/sd-bus/bus-track.c \ + src/libsystemd/sd-bus/bus-track.h \ + src/libsystemd/sd-bus/bus-slot.c \ + src/libsystemd/sd-bus/bus-slot.h \ + src/libsystemd/sd-bus/bus-protocol.h \ + src/libsystemd/sd-bus/kdbus.h \ + src/libsystemd/sd-bus/bus-dump.c \ + src/libsystemd/sd-bus/bus-dump.h \ + src/libsystemd/sd-utf8/sd-utf8.c \ + src/libsystemd/sd-event/sd-event.c \ + src/libsystemd/sd-netlink/sd-netlink.c \ + src/libsystemd/sd-netlink/netlink-internal.h \ + src/libsystemd/sd-netlink/netlink-message.c \ + src/libsystemd/sd-netlink/netlink-socket.c \ + src/libsystemd/sd-netlink/rtnl-message.c \ + src/libsystemd/sd-netlink/netlink-types.h \ + src/libsystemd/sd-netlink/netlink-types.c \ + src/libsystemd/sd-netlink/netlink-util.h \ + src/libsystemd/sd-netlink/netlink-util.c \ + src/libsystemd/sd-netlink/local-addresses.h \ + src/libsystemd/sd-netlink/local-addresses.c \ + src/libsystemd/sd-id128/sd-id128.c \ + src/libsystemd/sd-daemon/sd-daemon.c \ + src/libsystemd/sd-login/sd-login.c \ + src/libsystemd/sd-path/sd-path.c \ + src/libsystemd/sd-network/sd-network.c \ + src/libsystemd/sd-network/network-util.h \ + src/libsystemd/sd-network/network-util.c \ + src/libsystemd/sd-hwdb/sd-hwdb.c \ + src/libsystemd/sd-hwdb/hwdb-util.h \ + src/libsystemd/sd-hwdb/hwdb-internal.h \ + src/libsystemd/sd-device/device-internal.h \ + src/libsystemd/sd-device/device-util.h \ + src/libsystemd/sd-device/device-enumerator.c \ + src/libsystemd/sd-device/device-enumerator-private.h \ + src/libsystemd/sd-device/sd-device.c \ + src/libsystemd/sd-device/device-private.c \ + src/libsystemd/sd-device/device-private.h \ + src/libsystemd/sd-resolve/sd-resolve.c + +libsystemd_internal_la_LIBADD = \ + libbasic.la \ + -lresolv + +noinst_LTLIBRARIES += \ + libsystemd-internal.la + +test_bus_marshal_SOURCES = \ + src/libsystemd/sd-bus/test-bus-marshal.c + +test_bus_marshal_LDADD = \ + libshared.la \ + $(GLIB_LIBS) \ + $(DBUS_LIBS) + +test_bus_marshal_CFLAGS = \ + $(AM_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(DBUS_CFLAGS) + +test_bus_signature_SOURCES = \ + src/libsystemd/sd-bus/test-bus-signature.c + +test_bus_signature_LDADD = \ + libshared.la + +test_bus_chat_SOURCES = \ + src/libsystemd/sd-bus/test-bus-chat.c + +test_bus_chat_LDADD = \ + libshared.la + +test_bus_cleanup_SOURCES = \ + src/libsystemd/sd-bus/test-bus-cleanup.c + +test_bus_cleanup_CFLAGS = \ + $(AM_CFLAGS) \ + $(SECCOMP_CFLAGS) + +test_bus_cleanup_LDADD = \ + libshared.la + +test_bus_server_SOURCES = \ + src/libsystemd/sd-bus/test-bus-server.c + +test_bus_server_LDADD = \ + libshared.la + +test_bus_objects_SOURCES = \ + src/libsystemd/sd-bus/test-bus-objects.c + +test_bus_objects_LDADD = \ + libshared.la + +test_bus_error_SOURCES = \ + src/libsystemd/sd-bus/test-bus-error.c + +test_bus_error_LDADD = \ + libshared.la + +test_bus_gvariant_SOURCES = \ + src/libsystemd/sd-bus/test-bus-gvariant.c + +test_bus_gvariant_LDADD = \ + libshared.la \ + $(GLIB_LIBS) + +test_bus_gvariant_CFLAGS = \ + $(AM_CFLAGS) \ + $(GLIB_CFLAGS) + +test_bus_creds_SOURCES = \ + src/libsystemd/sd-bus/test-bus-creds.c + +test_bus_creds_LDADD = \ + libshared.la + +test_bus_match_SOURCES = \ + src/libsystemd/sd-bus/test-bus-match.c + +test_bus_match_LDADD = \ + libshared.la + +test_bus_kernel_SOURCES = \ + src/libsystemd/sd-bus/test-bus-kernel.c + +test_bus_kernel_LDADD = \ + libshared.la + +test_bus_kernel_bloom_SOURCES = \ + src/libsystemd/sd-bus/test-bus-kernel-bloom.c + +test_bus_kernel_bloom_LDADD = \ + libshared.la + +test_bus_benchmark_SOURCES = \ + src/libsystemd/sd-bus/test-bus-benchmark.c + +test_bus_benchmark_LDADD = \ + libshared.la + +test_bus_zero_copy_SOURCES = \ + src/libsystemd/sd-bus/test-bus-zero-copy.c + +test_bus_zero_copy_LDADD = \ + libshared.la + +test_bus_introspect_SOURCES = \ + src/libsystemd/sd-bus/test-bus-introspect.c + +test_bus_introspect_LDADD = \ + libshared.la + +test_event_SOURCES = \ + src/libsystemd/sd-event/test-event.c + +test_event_LDADD = \ + libshared.la + +test_netlink_SOURCES = \ + src/libsystemd/sd-netlink/test-netlink.c + +test_netlink_LDADD = \ + libshared.la + +test_resolve_SOURCES = \ + src/libsystemd/sd-resolve/test-resolve.c + +test_resolve_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libsystemd/libsystemd-internal/sd-bus/DIFFERENCES b/src/libsystemd/libsystemd-internal/sd-bus/DIFFERENCES new file mode 100644 index 0000000000..db269675a7 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/DIFFERENCES @@ -0,0 +1,25 @@ +Known differences between dbus1 and kdbus: + +- NameAcquired/NameLost is gone entirely on kdbus backends if + libsystemd is used. It is still added in by systemd-bus-proxyd + for old dbus1 clients, and it is available if libsystemd is used + against the classic dbus1 daemon. If you want to write compatible + code with libsystem-bus you need to explicitly subscribe to + NameOwnerChanged signals and just ignore NameAcquired/NameLost + +- Applications have to deal with spurious signals they didn't expect, + due to the probabilistic bloom filters. They need to handle this + anyway, given that any client can send anything to arbitrary clients + anyway, even in dbus1, so not much changes. + +- clients of the system bus when kdbus is used must roll their own + security. Only legacy dbus1 clients get the old XML policy enforced, + which is implemented by systemd-bus-proxyd. + +- Serial numbers of synthesized messages are always (uint32_t) -1. + +- NameOwnerChanged is a synthetic message, generated locally and not + by the driver. On dbus1 only the Disconnected message was + synthesized like this. + +- There's no standard per-session bus anymore. Only a per-user bus. diff --git a/src/libsystemd/libsystemd-internal/sd-bus/GVARIANT-SERIALIZATION b/src/libsystemd/libsystemd-internal/sd-bus/GVARIANT-SERIALIZATION new file mode 100644 index 0000000000..6aeb11364a --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/GVARIANT-SERIALIZATION @@ -0,0 +1,110 @@ +How we use GVariant for serializing D-Bus messages +-------------------------------------------------- + +We stay close to the original dbus1 framing as possible, but make +certain changes to adapt for GVariant. dbus1 has the following +framing: + + 1. A fixed header of "yyyyuu" + 2. Additional header fields of "a(yv)" + 3. Padding with NUL bytes to pad up to next 8byte boundary + 4. The body + +Note that the body is not padded at the end, the complete message +hence might have a non-aligned size. Reading multiple messages at once +will hence result in possibly unaligned messages in memory. + +The header consists of the following: + + y Endianness, 'l' or 'B' + y Message Type + y Flags + y Protocol version, '1' + u Length of the body, i.e. the length of part 4 above + u 32bit Serial number + + = 12 bytes + +This header is then followed by the fields array, whose first value is +a 32bit array size. + +When using GVariant we keep the basic structure in place, only +slightly alter the header, and define protocol version '2'. The new +header: + + y Endianness, 'l' or 'B' + y Message Type + y Flags + y Protocol version, '2' + u Reserved, must be 0 + t 64bit Cookie + + = 16 bytes + +This is then followed by the GVariant fields array ("a{tv}"), and +finally the actual body as variant (v). Putting this altogether a +packet on dbus2 hence qualifies as a fully compliant GVariant +structure of (yyyyuta{tv}v). + +For details on gvariant, see: + +https://people.gnome.org/~desrt/gvariant-serialisation.pdf + +Regarding the framing of dbus2, also see: + +https://wiki.gnome.org/Projects/GLib/GDBus/Version2 + +The first four bytes of the header are defined the same way for dbus1 +and dbus2. The first bytes contain the endianess field and the +protocol version, so that the remainder of the message can be safely +made sense of just by looking at the first 32bit. + +Note that the length of the body is no longer included in the header +on dbus2! In fact, the message size must be known in advance, from the +underlying transport in order to parse dbus2 messages, while it is +directly included in dbus1 message headers. This change of semantics +is an effect of GVariant's basic design. + +The serial number has been renamed cookie and has been extended from +32bit to 64bit. It is recommended to avoid the higher 32bit of the +cookie field though, to simplify compatibility with dbus1 peers. Note +that not only the cookie/serial field in the fixed header, but also +the reply_cookie/reply_serial additional header field has been +increased from 32bit to 64bit, too! + +The header field identifiers have been extended from 8bit to +64bit. This has been done to simplify things (as kdbus otherwise uses +exclusively 64bit types, unless there is a strong reason not to), and +has no effect on the serialization size, as due to alignment for each +8bit header field identifier 56 bits of padding had to be added. + +Note that the header size changed, due to these changes. However, +consider that on dbus1 the beginning of the fields array contains the +32bit array size (since that is how arrays are encoded on dbus1), +thus, if one considers that size part of the header, instead of the +array, the size of the header on dbus1 and dbus2 stays identical, at +16 bytes. + + 0 4 8 12 16 + Common: | E | T | F | V | ... + + dbus1: | (as above) | Body Length | Serial | Fields Length | Fields array ... + + gvariant: | (as above) | Reserved | Cookie | Fields array ... + +And that's already it. + +Note: to simplify parsing, valid kdbus/dbus2 messages must include the +entire fixed header and additional header fields in a single non-memfd +message part. Also, the signature string of the body variant all the +way to the end of the message must be in a single non-memfd part +too. The parts for this extended header and footer can be the same +one, and can also continue any amount of additional body bytes. + +Note: on kdbus only native endian messages marshalled in gvariant may + be sent. If a client receives a message in non-native endianness + or in dbus1 marshalling it shall ignore the message. + +Note: The GVariant "MAYBE" type is not supported, so that messages can + be fully converted forth and back between dbus1 and gvariant + representations. diff --git a/src/libsystemd/libsystemd-internal/sd-bus/PORTING-DBUS1 b/src/libsystemd/libsystemd-internal/sd-bus/PORTING-DBUS1 new file mode 100644 index 0000000000..2dedb28bcf --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/PORTING-DBUS1 @@ -0,0 +1,535 @@ +A few hints on supporting kdbus as backend in your favorite D-Bus library. + +~~~ + +Before you read this, have a look at the DIFFERENCES and +GVARIANT_SERIALIZATION texts you find in the same directory where you +found this. + +We invite you to port your favorite D-Bus protocol implementation +over to kdbus. However, there are a couple of complexities +involved. On kdbus we only speak GVariant marshaling, kdbus clients +ignore traffic in dbus1 marshaling. Thus, you need to add a second, +GVariant compatible marshaler to your library first. + +After you have done that: here's the basic principle how kdbus works: + +You connect to a bus by opening its bus node in /sys/fs/kdbus/. All +buses have a device node there, it starts with a numeric UID of the +owner of the bus, followed by a dash and a string identifying the +bus. The system bus is thus called /sys/fs/kdbus/0-system, and for user +buses the device node is /sys/fs/kdbus/1000-user (if 1000 is your user +id). + +(Before we proceed, please always keep a copy of libsystemd next +to you, ultimately that's where the details are, this document simply +is a rough overview to help you grok things.) + +CONNECTING + +To connect to a bus, simply open() its device node and issue the +KDBUS_CMD_HELLO call. That's it. Now you are connected. Do not send +Hello messages or so (as you would on dbus1), that does not exist for +kdbus. + +The structure you pass to the ioctl will contain a couple of +parameters that you need to know, to operate on the bus. + +There are two flags fields, one indicating features of the kdbus +kernel side ("conn_flags"), the other one ("bus_flags") indicating +features of the bus owner (i.e. systemd). Both flags fields are 64bit +in width. + +When calling into the ioctl, you need to place your own supported +feature bits into these fields. This tells the kernel about the +features you support. When the ioctl returns, it will contain the +features the kernel supports. + +If any of the higher 32bit are set on the two flags fields and your +client does not know what they mean, it must disconnect. The upper +32bit are used to indicate "incompatible" feature additions on the bus +system, the lower 32bit indicate "compatible" feature additions. A +client that does not support a "compatible" feature addition can go on +communicating with the bus, however a client that does not support an +"incompatible" feature must not proceed with the connection. When a +client encountes such an "incompatible" feature it should immediately +try the next bus address configured in the bus address string. + +The hello structure also contains another flags field "attach_flags" +which indicates metadata that is optionally attached to all incoming +messages. You probably want to set KDBUS_ATTACH_NAMES unconditionally +in it. This has the effect that all well-known names of a sender are +attached to all incoming messages. You need this information to +implement matches that match on a message sender name correctly. Of +course, you should only request the attachment of as little metadata +fields as you need. + +The kernel will return in the "id" field your unique id. This is a +simple numeric value. For compatibility with classic dbus1 simply +format this as string and prefix ":1.". + +The kernel will also return the bloom filter size and bloom filter +hash function number used for the signal broadcast bloom filter (see +below). + +The kernel will also return the bus ID of the bus in a 128bit field. + +The pool size field specifies the size of the memory mapped buffer. +After the calling the hello ioctl, you should memory map the kdbus +fd. In this memory mapped region, the kernel will place all your incoming +messages. + +SENDING MESSAGES + +Use the MSG_SEND ioctl to send a message to another peer. The ioctl +takes a structure that contains a variety of fields: + +The flags field corresponds closely to the old dbus1 message header +flags field, though the DONT_EXPECT_REPLY field got inverted into +EXPECT_REPLY. + +The dst_id/src_id field contains the unique id of the destination and +the sender. The sender field is overridden by the kernel usually, hence +you shouldn't fill it in. The destination field can also take the +special value KDBUS_DST_ID_BROADCAST for broadcast messages. For +messages intended to a well-known name set the field to +KDBUS_DST_ID_NAME, and attach the name in a special "items" entry to +the message (see below). + +The payload field indicates the payload. For all dbus traffic it +should carry the value 0x4442757344427573ULL. (Which encodes +'DBusDBus'). + +The cookie field corresponds with the "serial" field of classic +dbus1. We simply renamed it here (and extended it to 64bit) since we +didn't want to imply the monotonicity of the assignment the way the +word "serial" indicates it. + +When sending a message that expects a reply, you need to set the +EXPECT_REPLY flag in the message flag field. In this case you should +also fill out the "timeout_ns" value which indicates the timeout in +nsec for this call. If the peer does not respond in this time you will +get a notification of a timeout. Note that this is also used for +security purposes: a single reply messages is only allowed through the +bus as long as the timeout has not ended. With this timeout value you +hence "open a time window" in which the peer might respond to your +request and the policy allows the response to go through. + +When sending a message that is a reply, you need to fill in the +cookie_reply field, which is similar to the reply_serial field of +dbus1. Note that a message cannot have EXPECT_REPLY and a reply_serial +at the same time! + +This pretty much explains the ioctl header. The actual payload of the +data is now referenced in additional items that are attached to this +ioctl header structure at the end. When sending a message, you attach +items of the type PAYLOAD_VEC, PAYLOAD_MEMFD, FDS, BLOOM_FILTER, +DST_NAME to it: + + KDBUS_ITEM_PAYLOAD_VEC: contains a pointer + length pair for + referencing arbitrary user memory. This is how you reference most + of your data. It's a lot like the good old iovec structure of glibc. + + KDBUS_ITEM_PAYLOAD_MEMFD: for large data blocks it is preferable + to send prepared "memfds" (see below) over. This item contains an + fd for a memfd plus a size. + + KDBUS_ITEM_FDS: for sending over fds attach an item of this type with + an array of fds. + + KDBUS_ITEM_BLOOM_FILTER: the calculated bloom filter of this message, + only for undirected (broadcast) message. + + KDBUS_ITEM_DST_NAME: for messages that are directed to a well-known + name (instead of a unique name), this item contains the well-known + name field. + +A single message may consists of no, one or more payload items of type +PAYLOAD_VEC or PAYLOAD_MEMFD. D-Bus protocol implementations should +treat them as a single block that just happens to be split up into +multiple items. Some restrictions apply however: + + The message header in its entirety must be contained in a single + PAYLOAD_VEC item. + + You may only split your message up right in front of each GVariant + contained in the payload, as well is immediately before framing of a + Gvariant, as well after as any padding bytes if there are any. The + padding bytes must be wholly contained in the preceding + PAYLOAD_VEC/PAYLOAD_MEMFD item. You may not split up basic types + nor arrays of fixed types. The latter is necessary to allow APIs + to return direct pointers to linear arrays of numeric + values. Examples: The basic types "u", "s", "t" have to be in the + same payload item. The array of fixed types "ay", "ai" have to be + fully in contained in the same payload item. For an array "as" or + "a(si)" the only restriction however is to keep each string + individually in an uninterrupted item, to keep the framing of each + element and the array in a single uninterrupted item, however the + various strings might end up in different items. + +Note again, that splitting up messages into separate items is up to the +implementation. Also note that the kdbus kernel side might merge +separate items if it deems this to be useful. However, the order in +which items are contained in the message is left untouched. + +PAYLOAD_MEMFD items allow zero-copy data transfer (see below regarding +the memfd concept). Note however that the overhead of mapping these +makes them relatively expensive, and only worth the trouble for memory +blocks > 512K (this value appears to be quite universal across +architectures, as we tested). Thus we recommend sending PAYLOAD_VEC +items over for small messages and restore to PAYLOAD_MEMFD items for +messages > 512K. Since while building up the message you might not +know yet whether it will grow beyond this boundary a good approach is +to simply build the message unconditionally in a memfd +object. However, when the message is sealed to be sent away check for +the size limit. If the size of the message is < 512K, then simply send +the data as PAYLOAD_VEC and reuse the memfd. If it is >= 512K, seal +the memfd and send it as PAYLOAD_MEMFD, and allocate a new memfd for +the next message. + +RECEIVING MESSAGES + +Use the MSG_RECV ioctl to read a message from kdbus. This will return +an offset into the pool memory map, relative to its beginning. + +The received message structure more or less follows the structure of +the message originally sent. However, certain changes have been +made. In the header the src_id field will be filled in. + +The payload items might have gotten merged and PAYLOAD_VEC items are +not used. Instead, you will only find PAYLOAD_OFF and PAYLOAD_MEMFD +items. The former contain an offset and size into your memory mapped +pool where you find the payload. + +If during the HELLO ioctl you asked for getting metadata attached to +your message, you will find additional KDBUS_ITEM_CREDS, +KDBUS_ITEM_PID_COMM, KDBUS_ITEM_TID_COMM, KDBUS_ITEM_TIMESTAMP, +KDBUS_ITEM_EXE, KDBUS_ITEM_CMDLINE, KDBUS_ITEM_CGROUP, +KDBUS_ITEM_CAPS, KDBUS_ITEM_SECLABEL, KDBUS_ITEM_AUDIT items that +contain this metadata. This metadata will be gathered from the sender +at the point in time it sends the message. This information is +uncached, and since it is appended by the kernel, trustable. The +KDBUS_ITEM_SECLABEL item usually contains the SELinux security label, +if it is used. + +After processing the message you need to call the KDBUS_CMD_FREE +ioctl, which releases the message from the pool, and allows the kernel +to store another message there. Note that the memory used by the pool +is ordinary anonymous, swappable memory that is backed by tmpfs. Hence +there is no need to copy the message out of it quickly, instead you +can just leave it there as long as you need it and release it via the +FREE ioctl only after that's done. + +BLOOM FILTERS + +The kernel does not understand dbus marshaling, it will not look into +the message payload. To allow clients to subscribe to specific subsets +of the broadcast matches we employ bloom filters. + +When broadcasting messages, a bloom filter needs to be attached to the +message in a KDBUS_ITEM_BLOOM item (and only for broadcasting +messages!). If you don't know what bloom filters are, read up now on +Wikipedia. In short: they are a very efficient way how to +probabilistically check whether a certain word is contained in a +vocabulary. It knows no false negatives, but it does know false +positives. + +The parameters for the bloom filters that need to be included in +broadcast message is communicated to userspace as part of the hello +response structure (see above). By default it has the parameters m=512 +(bits in the filter), k=8 (nr of hash functions). Note however, that +this is subject to change in later versions, and userspace +implementations must be capable of handling m values between at least +m=8 and m=2^32, and k values between at least k=1 and k=32. The +underlying hash function is SipHash-2-4. It is used with a number of +constant (yet originally randomly generated) 128bit hash keys, more +specifically: + + b9,66,0b,f0,46,70,47,c1,88,75,c4,9c,54,b9,bd,15, + aa,a1,54,a2,e0,71,4b,39,bf,e1,dd,2e,9f,c5,4a,3b, + 63,fd,ae,be,cd,82,48,12,a1,6e,41,26,cb,fa,a0,c8, + 23,be,45,29,32,d2,46,2d,82,03,52,28,fe,37,17,f5, + 56,3b,bf,ee,5a,4f,43,39,af,aa,94,08,df,f0,fc,10, + 31,80,c8,73,c7,ea,46,d3,aa,25,75,0f,9e,4c,09,29, + 7d,f7,18,4b,7b,a4,44,d5,85,3c,06,e0,65,53,96,6d, + f2,77,e9,6f,93,b5,4e,71,9a,0c,34,88,39,25,bf,35 + +When calculating the first bit index into the bloom filter, the +SipHash-2-4 hash value is calculated for the input data and the first +16 bytes of the array above as hash key. Of the resulting 8 bytes of +output, as many full bytes are taken for the bit index as necessary, +starting from the output's first byte. For the second bit index the +same hash value is used, continuing with the next unused output byte, +and so on. Each time the bytes returned by the hash function are +depleted it is recalculated with the next 16 byte hash key from the +array above and the same input data. + +For each message to send across the bus we populate the bloom filter +with all possible matchable strings. If a client then wants to +subscribe to messages of this type, it simply tells the kernel to test +its own calculated bit mask against the bloom filter of each message. + +More specifically, the following strings are added to the bloom filter +of each message that is broadcasted: + + The string "interface:" suffixed by the interface name + + The string "member:" suffixed by the member name + + The string "path:" suffixed by the path name + + The string "path-slash-prefix:" suffixed with the path name, and + also all prefixes of the path name (cut off at "/"), also prefixed + with "path-slash-prefix". + + The string "message-type:" suffixed with the strings "signal", + "method_call", "error" or "method_return" for the respective message + type of the message. + + If the first argument of the message is a string, "arg0:" suffixed + with the first argument. + + If the first argument of the message is a string, "arg0-dot-prefix" + suffixed with the first argument, and also all prefixes of the + argument (cut off at "."), also prefixed with "arg0-dot-prefix". + + If the first argument of the message is a string, + "arg0-slash-prefix" suffixed with the first argument, and also all + prefixes of the argument (cut off at "/"), also prefixed with + "arg0-slash-prefix". + + Similar for all further arguments that are strings up to 63, for the + arguments and their "dot" and "slash" prefixes. On the first + argument that is not a string, addition to the bloom filter should be + stopped however. + +(Note that the bloom filter does not contain sender nor receiver +names!) + +When a client wants to subscribe to messages matching a certain +expression, it should calculate the bloom mask following the same +algorithm. The kernel will then simply test the mask against the +attached bloom filters. + +Note that bloom filters are probabilistic, which means that clients +might get messages they did not expect. Your bus protocol +implementation must be capable of dealing with these unexpected +messages (which it needs to anyway, given that transfers are +relatively unrestricted on kdbus and people can send you all kinds of +non-sense). + +If a client connects to a bus whose bloom filter metrics (i.e. filter +size and number of hash functions) are outside of the range the client +supports it must immediately disconnect and continue connection with +the next bus address of the bus connection string. + +INSTALLING MATCHES + +To install matches for broadcast messages, use the KDBUS_CMD_ADD_MATCH +ioctl. It takes a structure that contains an encoded match expression, +and that is followed by one or more items, which are combined in an +AND way. (Meaning: a message is matched exactly when all items +attached to the original ioctl struct match). + +To match against other user messages add a KDBUS_ITEM_BLOOM item in +the match (see above). Note that the bloom filter does not include +matches to the sender names. To additionally check against sender +names, use the KDBUS_ITEM_ID (for unique id matches) and +KDBUS_ITEM_NAME (for well-known name matches) item types. + +To match against kernel generated messages (see below) you should add +items of the same type as the kernel messages include, +i.e. KDBUS_ITEM_NAME_ADD, KDBUS_ITEM_NAME_REMOVE, +KDBUS_ITEM_NAME_CHANGE, KDBUS_ITEM_ID_ADD, KDBUS_ITEM_ID_REMOVE and +fill them out. Note however, that you have some wildcards in this +case, for example the .id field of KDBUS_ITEM_ID_ADD/KDBUS_ITEM_ID_REMOVE +structures may be set to 0 to match against any id addition/removal. + +Note that dbus match strings do no map 1:1 to these ioctl() calls. In +many cases (where the match string is "underspecified") you might need +to issue up to six different ioctl() calls for the same match. For +example, the empty match (which matches against all messages), would +translate into one KDBUS_ITEM_BLOOM ioctl, one KDBUS_ITEM_NAME_ADD, +one KDBUS_ITEM_NAME_CHANGE, one KDBUS_ITEM_NAME_REMOVE, one +KDBUS_ITEM_ID_ADD and one KDBUS_ITEM_ID_REMOVE. + +When creating a match, you may attach a "cookie" value to them, which +is used for deleting this match again. The cookie can be selected freely +by the client. When issuing KDBUS_CMD_REMOVE_MATCH, simply pass the +same cookie as before and all matches matching the same "cookie" value +will be removed. This is particularly handy for the case where multiple +ioctl()s are added for a single match strings. + +MEMFDS + +memfds may be sent across kdbus via KDBUS_ITEM_PAYLOAD_MEMFD items +attached to messages. If this is done, the data included in the memfd +is considered part of the payload stream of a message, and are treated +the same way as KDBUS_ITEM_PAYLOAD_VEC by the receiving side. It is +possible to interleave KDBUS_ITEM_PAYLOAD_MEMFD and +KDBUS_ITEM_PAYLOAD_VEC items freely, by the reader they will be +considered a single stream of bytes in the order these items appear in +the message, that just happens to be split up at various places +(regarding rules how they may be split up, see above). The kernel will +refuse taking KDBUS_ITEM_PAYLOAD_MEMFD items that refer to memfds that +are not sealed. + +Note that sealed memfds may be unsealed again if they are not mapped +you have the only fd reference to them. + +Alternatively to sending memfds as KDBUS_ITEM_PAYLOAD_MEMFD items +(where they are just a part of the payload stream of a message) you can +also simply attach any memfd to a message using +KDBUS_ITEM_PAYLOAD_FDS. In this case, the memfd contents is not +considered part of the payload stream of the message, but simply fds +like any other, that happen to be attached to the message. + +MESSAGES FROM THE KERNEL + +A couple of messages previously generated by the dbus1 bus driver are +now generated by the kernel. Since the kernel does not understand the +payload marshaling, they are generated by the kernel in a different +format. This is indicated with the "payload type" field of the +messages set to 0. Library implementations should take these messages +and synthesize traditional driver messages for them on reception. + +More specifically: + + Instead of the NameOwnerChanged, NameLost, NameAcquired signals + there are kernel messages containing KDBUS_ITEM_NAME_ADD, + KDBUS_ITEM_NAME_REMOVE, KDBUS_ITEM_NAME_CHANGE, KDBUS_ITEM_ID_ADD, + KDBUS_ITEM_ID_REMOVE items are generated (each message will contain + exactly one of these items). Note that in libsystemd we have + obsoleted NameLost/NameAcquired messages, since they are entirely + redundant to NameOwnerChanged. This library will hence only + synthesize NameOwnerChanged messages from these kernel messages, + and never generate NameLost/NameAcquired. If your library needs to + stay compatible to the old dbus1 userspace, you possibly might need + to synthesize both a NameOwnerChanged and NameLost/NameAcquired + message from the same kernel message. + + When a method call times out, a KDBUS_ITEM_REPLY_TIMEOUT message is + generated. This should be synthesized into a method error reply + message to the original call. + + When a method call fails because the peer terminated the connection + before responding, a KDBUS_ITEM_REPLY_DEAD message is + generated. Similarly, it should be synthesized into a method error + reply message. + +For synthesized messages we recommend setting the cookie field to +(uint32_t) -1 (and not (uint64_t) -1!), so that the cookie is not 0 +(which the dbus1 spec does not allow), but clearly recognizable as +synthetic. + +Note that the KDBUS_ITEM_NAME_XYZ messages will actually inform you +about all kinds of names, including activatable ones. Classic dbus1 +NameOwnerChanged messages OTOH are only generated when a name is +really acquired on the bus and not just simply activatable. This means +you must explicitly check for the case where an activatable name +becomes acquired or an acquired name is lost and returns to be +activatable. + +NAME REGISTRY + +To acquire names on the bus, use the KDBUS_CMD_NAME_ACQUIRE ioctl(). It +takes a flags field similar to dbus1's RequestName() bus driver call, +however the NO_QUEUE flag got inverted into a QUEUE flag instead. + +To release a previously acquired name use the KDBUS_CMD_NAME_RELEASE +ioctl(). + +To list acquired names use the KDBUS_CMD_CONN_INFO ioctl. It may be +used to list unique names, well known names as well as activatable +names and clients currently queuing for ownership of a well-known +name. The ioctl will return an offset into the memory pool. After +reading all the data you need, you need to release this via the +KDBUS_CMD_FREE ioctl(), similar how you release a received message. + +CREDENTIALS + +kdbus can optionally attach various kinds of metadata about the sender at +the point of time of sending ("credentials") to messages, on request +of the receiver. This is both supported on directed and undirected +(broadcast) messages. The metadata to attach is selected at time of +the HELLO ioctl of the receiver via a flags field (see above). Note +that clients must be able to handle that messages contain more +metadata than they asked for themselves, to simplify implementation of +broadcasting in the kernel. The receiver should not rely on this data +to be around though, even though it will be correct if it happens to +be attached. In order to avoid programming errors in applications, we +recommend though not passing this data on to clients that did not +explicitly ask for it. + +Credentials may also be queried for a well-known or unique name. Use +the KDBUS_CMD_CONN_INFO for this. It will return an offset to the pool +area again, which will contain the same credential items as messages +have attached. Note that when issuing the ioctl, you can select a +different set of credentials to gather, than what was originally requested +for being attached to incoming messages. + +Credentials are always specific to the sender's domain that was +current at the time of sending, and of the process that opened the +bus connection at the time of opening it. Note that this latter data +is cached! + +POLICY + +The kernel enforces only very limited policy on names. It will not do +access filtering by userspace payload, and thus not by interface or +method name. + +This ultimately means that most fine-grained policy enforcement needs +to be done by the receiving process. We recommend using PolicyKit for +any more complex checks. However, libraries should make simple static +policy decisions regarding privileged/unprivileged method calls +easy. We recommend doing this by enabling KDBUS_ATTACH_CAPS and +KDBUS_ATTACH_CREDS for incoming messages, and then discerning client +access by some capability, or if sender and receiver UIDs match. + +BUS ADDRESSES + +When connecting to kdbus use the "kernel:" protocol prefix in DBus +address strings. The device node path is encoded in its "path=" +parameter. + +Client libraries should use the following connection string when +connecting to the system bus: + + kernel:path=/sys/fs/kdbus/0-system/bus;unix:path=/var/run/dbus/system_bus_socket + +This will ensure that kdbus is preferred over the legacy AF_UNIX +socket, but compatibility is kept. For the user bus use: + + kernel:path=/sys/fs/kdbus/$UID-user/bus;unix:path=$XDG_RUNTIME_DIR/bus + +With $UID replaced by the callers numer user ID, and $XDG_RUNTIME_DIR +following the XDG basedir spec. + +Of course the $DBUS_SYSTEM_BUS_ADDRESS and $DBUS_SESSION_BUS_ADDRESS +variables should still take precedence. + +DBUS SERVICE FILES + +Activatable services for kdbus may not use classic dbus1 service +activation files. Instead, programs should drop in native systemd +.service and .busname unit files, so that they are treated uniformly +with other types of units and activation of the system. + +Note that this results in a major difference to classic dbus1: +activatable bus names can be established at any time in the boot process. +This is unlike dbus1 where activatable names are unconditionally available +as long as dbus-daemon is running. Being able to control when +activatable names are established is essential to allow usage of kdbus +during early boot and in initrds, without the risk of triggering +services too early. + +DISCLAIMER + +This all is so far just the status quo. We are putting this together, because +we are quite confident that further API changes will be smaller, but +to make this very clear: this is all subject to change, still! + +We invite you to port over your favorite dbus library to this new +scheme, but please be prepared to make minor changes when we still +change these interfaces! diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-bloom.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-bloom.c new file mode 100644 index 0000000000..112769fcb6 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-bloom.c @@ -0,0 +1,156 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "bus-bloom.h" +#include "siphash24.h" +#include "util.h" + +static inline void set_bit(uint64_t filter[], unsigned long b) { + filter[b >> 6] |= 1ULL << (b & 63); +} + +static const sd_id128_t hash_keys[] = { + SD_ID128_ARRAY(b9,66,0b,f0,46,70,47,c1,88,75,c4,9c,54,b9,bd,15), + SD_ID128_ARRAY(aa,a1,54,a2,e0,71,4b,39,bf,e1,dd,2e,9f,c5,4a,3b), + SD_ID128_ARRAY(63,fd,ae,be,cd,82,48,12,a1,6e,41,26,cb,fa,a0,c8), + SD_ID128_ARRAY(23,be,45,29,32,d2,46,2d,82,03,52,28,fe,37,17,f5), + SD_ID128_ARRAY(56,3b,bf,ee,5a,4f,43,39,af,aa,94,08,df,f0,fc,10), + SD_ID128_ARRAY(31,80,c8,73,c7,ea,46,d3,aa,25,75,0f,9e,4c,09,29), + SD_ID128_ARRAY(7d,f7,18,4b,7b,a4,44,d5,85,3c,06,e0,65,53,96,6d), + SD_ID128_ARRAY(f2,77,e9,6f,93,b5,4e,71,9a,0c,34,88,39,25,bf,35), +}; + +static void bloom_add_data( + uint64_t filter[], /* The filter bits */ + size_t size, /* Size of the filter in bytes */ + unsigned k, /* Number of hash functions */ + const void *data, /* Data to hash */ + size_t n) { /* Size of data to hash in bytes */ + + uint64_t h; + uint64_t m; + unsigned w, i, c = 0; + unsigned hash_index; + + assert(size > 0); + assert(k > 0); + + /* Determine bits in filter */ + m = size * 8; + + /* Determine how many bytes we need to generate a bit index 0..m for this filter */ + w = (u64log2(m) + 7) / 8; + + assert(w <= sizeof(uint64_t)); + + /* Make sure we have enough hash keys to generate m * k bits + * of hash value. Note that SipHash24 generates 64 bits of + * hash value for each 128 bits of hash key. */ + assert(k * w <= ELEMENTSOF(hash_keys) * 8); + + for (i = 0, hash_index = 0; i < k; i++) { + uint64_t p = 0; + unsigned d; + + for (d = 0; d < w; d++) { + if (c <= 0) { + h = siphash24(data, n, hash_keys[hash_index++].bytes); + c += 8; + } + + p = (p << 8ULL) | (uint64_t) ((uint8_t *)&h)[8 - c]; + c--; + } + + p &= m - 1; + set_bit(filter, p); + } + + /* log_debug("bloom: adding <%.*s>", (int) n, (char*) data); */ +} + +void bloom_add_pair(uint64_t filter[], size_t size, unsigned k, const char *a, const char *b) { + size_t n; + char *c; + + assert(filter); + assert(a); + assert(b); + + n = strlen(a) + 1 + strlen(b); + c = alloca(n + 1); + strcpy(stpcpy(stpcpy(c, a), ":"), b); + + bloom_add_data(filter, size, k, c, n); +} + +void bloom_add_prefixes(uint64_t filter[], size_t size, unsigned k, const char *a, const char *b, char sep) { + size_t n; + char *c, *p; + + assert(filter); + assert(a); + assert(b); + + n = strlen(a) + 1 + strlen(b); + c = alloca(n + 1); + + p = stpcpy(stpcpy(c, a), ":"); + strcpy(p, b); + + bloom_add_data(filter, size, k, c, n); + + for (;;) { + char *e; + + e = strrchr(p, sep); + if (!e) + break; + + *(e + 1) = 0; + bloom_add_data(filter, size, k, c, e - c + 1); + + if (e == p) + break; + + *e = 0; + bloom_add_data(filter, size, k, c, e - c); + } +} + +bool bloom_validate_parameters(size_t size, unsigned k) { + uint64_t m; + unsigned w; + + if (size <= 0) + return false; + + if (k <= 0) + return false; + + m = size * 8; + w = (u64log2(m) + 7) / 8; + if (w > sizeof(uint64_t)) + return false; + + if (k * w > ELEMENTSOF(hash_keys) * 8) + return false; + + return true; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-bloom.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-bloom.h new file mode 100644 index 0000000000..c824622b95 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-bloom.h @@ -0,0 +1,43 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 +#include + +/* + * Our default bloom filter has the following parameters: + * + * m=512 (bits in the filter) + * k=8 (hash functions) + * + * We use SipHash24 as hash function with a number of (originally + * randomized) but fixed hash keys. + * + */ + +#define DEFAULT_BLOOM_SIZE (512/8) /* m: filter size */ +#define DEFAULT_BLOOM_N_HASH 8 /* k: number of hash functions */ + +void bloom_add_pair(uint64_t filter[], size_t size, unsigned n_hash, const char *a, const char *b); +void bloom_add_prefixes(uint64_t filter[], size_t size, unsigned n_hash, const char *a, const char *b, char sep); + +bool bloom_validate_parameters(size_t size, unsigned n_hash); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-common-errors.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-common-errors.c new file mode 100644 index 0000000000..a19e98e94b --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-common-errors.c @@ -0,0 +1,87 @@ +/*** + This file is part of systemd. + + Copyright 2014 Zbigniew Jędrzejewski-Szmek + + 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 + +#include "bus-common-errors.h" +#include "bus-error.h" + +BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_UNIT, ENOENT), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_UNIT_FOR_PID, ESRCH), + SD_BUS_ERROR_MAP(BUS_ERROR_UNIT_EXISTS, EEXIST), + SD_BUS_ERROR_MAP(BUS_ERROR_LOAD_FAILED, EIO), + SD_BUS_ERROR_MAP(BUS_ERROR_JOB_FAILED, EREMOTEIO), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_JOB, ENOENT), + SD_BUS_ERROR_MAP(BUS_ERROR_NOT_SUBSCRIBED, EINVAL), + SD_BUS_ERROR_MAP(BUS_ERROR_ALREADY_SUBSCRIBED, EINVAL), + SD_BUS_ERROR_MAP(BUS_ERROR_ONLY_BY_DEPENDENCY, EINVAL), + SD_BUS_ERROR_MAP(BUS_ERROR_TRANSACTION_JOBS_CONFLICTING, EDEADLK), + SD_BUS_ERROR_MAP(BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC, EDEADLK), + SD_BUS_ERROR_MAP(BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE, EDEADLK), + SD_BUS_ERROR_MAP(BUS_ERROR_UNIT_MASKED, ESHUTDOWN), + SD_BUS_ERROR_MAP(BUS_ERROR_UNIT_GENERATED, EADDRNOTAVAIL), + SD_BUS_ERROR_MAP(BUS_ERROR_UNIT_LINKED, ELOOP), + SD_BUS_ERROR_MAP(BUS_ERROR_JOB_TYPE_NOT_APPLICABLE, EBADR), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_ISOLATION, EPERM), + SD_BUS_ERROR_MAP(BUS_ERROR_SHUTTING_DOWN, ECANCELED), + SD_BUS_ERROR_MAP(BUS_ERROR_SCOPE_NOT_RUNNING, EHOSTDOWN), + + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_MACHINE, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_IMAGE, ENOENT), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_MACHINE_FOR_PID, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_MACHINE_EXISTS, EEXIST), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_PRIVATE_NETWORKING, ENOSYS), + + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SESSION, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SESSION_FOR_PID, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_USER, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_USER_FOR_PID, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SEAT, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_SESSION_NOT_ON_SEAT, EINVAL), + SD_BUS_ERROR_MAP(BUS_ERROR_NOT_IN_CONTROL, EINVAL), + SD_BUS_ERROR_MAP(BUS_ERROR_DEVICE_IS_TAKEN, EINVAL), + SD_BUS_ERROR_MAP(BUS_ERROR_DEVICE_NOT_TAKEN, EINVAL), + SD_BUS_ERROR_MAP(BUS_ERROR_OPERATION_IN_PROGRESS, EINPROGRESS), + SD_BUS_ERROR_MAP(BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, EOPNOTSUPP), + + SD_BUS_ERROR_MAP(BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, EALREADY), + + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_PROCESS, ESRCH), + + SD_BUS_ERROR_MAP(BUS_ERROR_NO_NAME_SERVERS, ESRCH), + SD_BUS_ERROR_MAP(BUS_ERROR_INVALID_REPLY, EINVAL), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_RR, ENOENT), + SD_BUS_ERROR_MAP(BUS_ERROR_CNAME_LOOP, EDEADLK), + SD_BUS_ERROR_MAP(BUS_ERROR_ABORTED, ECANCELED), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SERVICE, EUNATCH), + SD_BUS_ERROR_MAP(BUS_ERROR_DNSSEC_FAILED, EHOSTUNREACH), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_TRUST_ANCHOR, EHOSTUNREACH), + SD_BUS_ERROR_MAP(BUS_ERROR_RR_TYPE_UNSUPPORTED, EOPNOTSUPP), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_LINK, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_LINK_BUSY, EBUSY), + SD_BUS_ERROR_MAP(BUS_ERROR_NETWORK_DOWN, ENETDOWN), + + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_TRANSFER, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_TRANSFER_IN_PROGRESS, EBUSY), + + SD_BUS_ERROR_MAP_END +}; diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-common-errors.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-common-errors.h new file mode 100644 index 0000000000..c8f369cb78 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-common-errors.h @@ -0,0 +1,86 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 "bus-error.h" + +#define BUS_ERROR_NO_SUCH_UNIT "org.freedesktop.systemd1.NoSuchUnit" +#define BUS_ERROR_NO_UNIT_FOR_PID "org.freedesktop.systemd1.NoUnitForPID" +#define BUS_ERROR_UNIT_EXISTS "org.freedesktop.systemd1.UnitExists" +#define BUS_ERROR_LOAD_FAILED "org.freedesktop.systemd1.LoadFailed" +#define BUS_ERROR_JOB_FAILED "org.freedesktop.systemd1.JobFailed" +#define BUS_ERROR_NO_SUCH_JOB "org.freedesktop.systemd1.NoSuchJob" +#define BUS_ERROR_NOT_SUBSCRIBED "org.freedesktop.systemd1.NotSubscribed" +#define BUS_ERROR_ALREADY_SUBSCRIBED "org.freedesktop.systemd1.AlreadySubscribed" +#define BUS_ERROR_ONLY_BY_DEPENDENCY "org.freedesktop.systemd1.OnlyByDependency" +#define BUS_ERROR_TRANSACTION_JOBS_CONFLICTING "org.freedesktop.systemd1.TransactionJobsConflicting" +#define BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC "org.freedesktop.systemd1.TransactionOrderIsCyclic" +#define BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE "org.freedesktop.systemd1.TransactionIsDestructive" +#define BUS_ERROR_UNIT_MASKED "org.freedesktop.systemd1.UnitMasked" +#define BUS_ERROR_UNIT_GENERATED "org.freedesktop.systemd1.UnitGenerated" +#define BUS_ERROR_UNIT_LINKED "org.freedesktop.systemd1.UnitLinked" +#define BUS_ERROR_JOB_TYPE_NOT_APPLICABLE "org.freedesktop.systemd1.JobTypeNotApplicable" +#define BUS_ERROR_NO_ISOLATION "org.freedesktop.systemd1.NoIsolation" +#define BUS_ERROR_SHUTTING_DOWN "org.freedesktop.systemd1.ShuttingDown" +#define BUS_ERROR_SCOPE_NOT_RUNNING "org.freedesktop.systemd1.ScopeNotRunning" + +#define BUS_ERROR_NO_SUCH_MACHINE "org.freedesktop.machine1.NoSuchMachine" +#define BUS_ERROR_NO_SUCH_IMAGE "org.freedesktop.machine1.NoSuchImage" +#define BUS_ERROR_NO_MACHINE_FOR_PID "org.freedesktop.machine1.NoMachineForPID" +#define BUS_ERROR_MACHINE_EXISTS "org.freedesktop.machine1.MachineExists" +#define BUS_ERROR_NO_PRIVATE_NETWORKING "org.freedesktop.machine1.NoPrivateNetworking" +#define BUS_ERROR_NO_SUCH_USER_MAPPING "org.freedesktop.machine1.NoSuchUserMapping" +#define BUS_ERROR_NO_SUCH_GROUP_MAPPING "org.freedesktop.machine1.NoSuchGroupMapping" + +#define BUS_ERROR_NO_SUCH_SESSION "org.freedesktop.login1.NoSuchSession" +#define BUS_ERROR_NO_SESSION_FOR_PID "org.freedesktop.login1.NoSessionForPID" +#define BUS_ERROR_NO_SUCH_USER "org.freedesktop.login1.NoSuchUser" +#define BUS_ERROR_NO_USER_FOR_PID "org.freedesktop.login1.NoUserForPID" +#define BUS_ERROR_NO_SUCH_SEAT "org.freedesktop.login1.NoSuchSeat" +#define BUS_ERROR_SESSION_NOT_ON_SEAT "org.freedesktop.login1.SessionNotOnSeat" +#define BUS_ERROR_NOT_IN_CONTROL "org.freedesktop.login1.NotInControl" +#define BUS_ERROR_DEVICE_IS_TAKEN "org.freedesktop.login1.DeviceIsTaken" +#define BUS_ERROR_DEVICE_NOT_TAKEN "org.freedesktop.login1.DeviceNotTaken" +#define BUS_ERROR_OPERATION_IN_PROGRESS "org.freedesktop.login1.OperationInProgress" +#define BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED "org.freedesktop.login1.SleepVerbNotSupported" +#define BUS_ERROR_SESSION_BUSY "org.freedesktop.login1.SessionBusy" + +#define BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED "org.freedesktop.timedate1.AutomaticTimeSyncEnabled" + +#define BUS_ERROR_NO_SUCH_PROCESS "org.freedesktop.systemd1.NoSuchProcess" + +#define BUS_ERROR_NO_NAME_SERVERS "org.freedesktop.resolve1.NoNameServers" +#define BUS_ERROR_INVALID_REPLY "org.freedesktop.resolve1.InvalidReply" +#define BUS_ERROR_NO_SUCH_RR "org.freedesktop.resolve1.NoSuchRR" +#define BUS_ERROR_CNAME_LOOP "org.freedesktop.resolve1.CNameLoop" +#define BUS_ERROR_ABORTED "org.freedesktop.resolve1.Aborted" +#define BUS_ERROR_NO_SUCH_SERVICE "org.freedesktop.resolve1.NoSuchService" +#define BUS_ERROR_DNSSEC_FAILED "org.freedesktop.resolve1.DnssecFailed" +#define BUS_ERROR_NO_TRUST_ANCHOR "org.freedesktop.resolve1.NoTrustAnchor" +#define BUS_ERROR_RR_TYPE_UNSUPPORTED "org.freedesktop.resolve1.ResourceRecordTypeUnsupported" +#define BUS_ERROR_NO_SUCH_LINK "org.freedesktop.resolve1.NoSuchLink" +#define BUS_ERROR_LINK_BUSY "org.freedesktop.resolve1.LinkBusy" +#define BUS_ERROR_NETWORK_DOWN "org.freedesktop.resolve1.NetworkDown" +#define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError." + +#define BUS_ERROR_NO_SUCH_TRANSFER "org.freedesktop.import1.NoSuchTransfer" +#define BUS_ERROR_TRANSFER_IN_PROGRESS "org.freedesktop.import1.TransferInProgress" + +BUS_ERROR_MAP_ELF_USE(bus_common_errors); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-container.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-container.c new file mode 100644 index 0000000000..3191d27ded --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-container.c @@ -0,0 +1,277 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include "bus-container.h" +#include "bus-internal.h" +#include "bus-socket.h" +#include "fd-util.h" +#include "process-util.h" +#include "util.h" + +int bus_container_connect_socket(sd_bus *b) { + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1; + pid_t child; + siginfo_t si; + int r, error_buf = 0; + ssize_t n; + + assert(b); + assert(b->input_fd < 0); + assert(b->output_fd < 0); + assert(b->nspid > 0 || b->machine); + + if (b->nspid <= 0) { + r = container_get_leader(b->machine, &b->nspid); + if (r < 0) + return r; + } + + r = namespace_open(b->nspid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); + if (r < 0) + return r; + + b->input_fd = socket(b->sockaddr.sa.sa_family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (b->input_fd < 0) + return -errno; + + b->output_fd = b->input_fd; + + bus_socket_setup(b); + + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) + return -errno; + + child = fork(); + if (child < 0) + return -errno; + + if (child == 0) { + pid_t grandchild; + + pair[0] = safe_close(pair[0]); + + r = namespace_enter(pidnsfd, mntnsfd, -1, usernsfd, rootfd); + if (r < 0) + _exit(EXIT_FAILURE); + + /* We just changed PID namespace, however it will only + * take effect on the children we now fork. Hence, + * let's fork another time, and connect from this + * grandchild, so that SO_PEERCRED of our connection + * comes from a process from within the container, and + * not outside of it */ + + grandchild = fork(); + if (grandchild < 0) + _exit(EXIT_FAILURE); + + if (grandchild == 0) { + + r = connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size); + if (r < 0) { + /* Try to send error up */ + error_buf = errno; + (void) write(pair[1], &error_buf, sizeof(error_buf)); + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); + } + + r = wait_for_terminate(grandchild, &si); + if (r < 0) + _exit(EXIT_FAILURE); + + if (si.si_code != CLD_EXITED) + _exit(EXIT_FAILURE); + + _exit(si.si_status); + } + + pair[1] = safe_close(pair[1]); + + r = wait_for_terminate(child, &si); + if (r < 0) + return r; + + n = read(pair[0], &error_buf, sizeof(error_buf)); + if (n < 0) + return -errno; + + if (n > 0) { + if (n != sizeof(error_buf)) + return -EIO; + + if (error_buf < 0) + return -EIO; + + if (error_buf == EINPROGRESS) + return 1; + + if (error_buf > 0) + return -error_buf; + } + + if (si.si_code != CLD_EXITED) + return -EIO; + + if (si.si_status != EXIT_SUCCESS) + return -EIO; + + return bus_socket_start_auth(b); +} + +int bus_container_connect_kernel(sd_bus *b) { + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + int error_buf = 0; + struct iovec iov = { + .iov_base = &error_buf, + .iov_len = sizeof(error_buf), + }; + struct msghdr mh = { + .msg_control = &control, + .msg_controllen = sizeof(control), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + struct cmsghdr *cmsg; + pid_t child; + siginfo_t si; + int r, fd = -1; + ssize_t n; + + assert(b); + assert(b->input_fd < 0); + assert(b->output_fd < 0); + assert(b->nspid > 0 || b->machine); + + if (b->nspid <= 0) { + r = container_get_leader(b->machine, &b->nspid); + if (r < 0) + return r; + } + + r = namespace_open(b->nspid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) + return -errno; + + child = fork(); + if (child < 0) + return -errno; + + if (child == 0) { + pid_t grandchild; + + pair[0] = safe_close(pair[0]); + + r = namespace_enter(pidnsfd, mntnsfd, -1, usernsfd, rootfd); + if (r < 0) + _exit(EXIT_FAILURE); + + /* We just changed PID namespace, however it will only + * take effect on the children we now fork. Hence, + * let's fork another time, and connect from this + * grandchild, so that kdbus only sees the credentials + * of this process which comes from within the + * container, and not outside of it */ + + grandchild = fork(); + if (grandchild < 0) + _exit(EXIT_FAILURE); + + if (grandchild == 0) { + fd = open(b->kernel, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) { + /* Try to send error up */ + error_buf = errno; + (void) write(pair[1], &error_buf, sizeof(error_buf)); + _exit(EXIT_FAILURE); + } + + r = send_one_fd(pair[1], fd, 0); + if (r < 0) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + r = wait_for_terminate(grandchild, &si); + if (r < 0) + _exit(EXIT_FAILURE); + + if (si.si_code != CLD_EXITED) + _exit(EXIT_FAILURE); + + _exit(si.si_status); + } + + pair[1] = safe_close(pair[1]); + + r = wait_for_terminate(child, &si); + if (r < 0) + return r; + + n = recvmsg(pair[0], &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); + if (n < 0) + return -errno; + + CMSG_FOREACH(cmsg, &mh) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + int *fds; + unsigned n_fds; + + assert(fd < 0); + + fds = (int*) CMSG_DATA(cmsg); + n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + + if (n_fds != 1) { + close_many(fds, n_fds); + return -EIO; + } + + fd = fds[0]; + } + } + + /* If there's an fd passed, we are good. */ + if (fd >= 0) { + b->input_fd = b->output_fd = fd; + return bus_kernel_take_fd(b); + } + + /* If there's an error passed, use it */ + if (n == sizeof(error_buf) && error_buf > 0) + return -error_buf; + + /* Otherwise, we have no clue */ + return -EIO; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-container.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-container.h new file mode 100644 index 0000000000..5cd6d15ede --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-container.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 + +int bus_container_connect_socket(sd_bus *b); +int bus_container_connect_kernel(sd_bus *b); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-control.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-control.c new file mode 100644 index 0000000000..00de530d58 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-control.c @@ -0,0 +1,1588 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include +#endif + +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-bloom.h" +#include "bus-control.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-util.h" +#include "capability-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" + +_public_ int sd_bus_get_unique_name(sd_bus *bus, const char **unique) { + int r; + + assert_return(bus, -EINVAL); + assert_return(unique, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!bus->bus_client) + return -EINVAL; + + r = bus_ensure_running(bus); + if (r < 0) + return r; + + *unique = bus->unique_name; + return 0; +} + +static int bus_request_name_kernel(sd_bus *bus, const char *name, uint64_t flags) { + struct kdbus_cmd *n; + size_t size, l; + int r; + + assert(bus); + assert(name); + + l = strlen(name) + 1; + size = offsetof(struct kdbus_cmd, items) + KDBUS_ITEM_SIZE(l); + n = alloca0_align(size, 8); + n->size = size; + n->flags = request_name_flags_to_kdbus(flags); + + n->items[0].size = KDBUS_ITEM_HEADER_SIZE + l; + n->items[0].type = KDBUS_ITEM_NAME; + memcpy(n->items[0].str, name, l); + +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(n, n->size); +#endif + + r = ioctl(bus->input_fd, KDBUS_CMD_NAME_ACQUIRE, n); + if (r < 0) + return -errno; + + if (n->return_flags & KDBUS_NAME_IN_QUEUE) + return 0; + + return 1; +} + +static int bus_request_name_dbus1(sd_bus *bus, const char *name, uint64_t flags) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + uint32_t ret, param = 0; + int r; + + assert(bus); + assert(name); + + if (flags & SD_BUS_NAME_ALLOW_REPLACEMENT) + param |= BUS_NAME_ALLOW_REPLACEMENT; + if (flags & SD_BUS_NAME_REPLACE_EXISTING) + param |= BUS_NAME_REPLACE_EXISTING; + if (!(flags & SD_BUS_NAME_QUEUE)) + param |= BUS_NAME_DO_NOT_QUEUE; + + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "RequestName", + NULL, + &reply, + "su", + name, + param); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "u", &ret); + if (r < 0) + return r; + + if (ret == BUS_NAME_ALREADY_OWNER) + return -EALREADY; + else if (ret == BUS_NAME_EXISTS) + return -EEXIST; + else if (ret == BUS_NAME_IN_QUEUE) + return 0; + else if (ret == BUS_NAME_PRIMARY_OWNER) + return 1; + + return -EIO; +} + +_public_ int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags) { + assert_return(bus, -EINVAL); + assert_return(name, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + assert_return(!(flags & ~(SD_BUS_NAME_ALLOW_REPLACEMENT|SD_BUS_NAME_REPLACE_EXISTING|SD_BUS_NAME_QUEUE)), -EINVAL); + assert_return(service_name_is_valid(name), -EINVAL); + assert_return(name[0] != ':', -EINVAL); + + if (!bus->bus_client) + return -EINVAL; + + /* Don't allow requesting the special driver and local names */ + if (STR_IN_SET(name, "org.freedesktop.DBus", "org.freedesktop.DBus.Local")) + return -EINVAL; + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (bus->is_kernel) + return bus_request_name_kernel(bus, name, flags); + else + return bus_request_name_dbus1(bus, name, flags); +} + +static int bus_release_name_kernel(sd_bus *bus, const char *name) { + struct kdbus_cmd *n; + size_t size, l; + int r; + + assert(bus); + assert(name); + + l = strlen(name) + 1; + size = offsetof(struct kdbus_cmd, items) + KDBUS_ITEM_SIZE(l); + n = alloca0_align(size, 8); + n->size = size; + + n->items[0].size = KDBUS_ITEM_HEADER_SIZE + l; + n->items[0].type = KDBUS_ITEM_NAME; + memcpy(n->items[0].str, name, l); + +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(n, n->size); +#endif + r = ioctl(bus->input_fd, KDBUS_CMD_NAME_RELEASE, n); + if (r < 0) + return -errno; + + return 0; +} + +static int bus_release_name_dbus1(sd_bus *bus, const char *name) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + uint32_t ret; + int r; + + assert(bus); + assert(name); + + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "ReleaseName", + NULL, + &reply, + "s", + name); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "u", &ret); + if (r < 0) + return r; + if (ret == BUS_NAME_NON_EXISTENT) + return -ESRCH; + if (ret == BUS_NAME_NOT_OWNER) + return -EADDRINUSE; + if (ret == BUS_NAME_RELEASED) + return 0; + + return -EINVAL; +} + +_public_ int sd_bus_release_name(sd_bus *bus, const char *name) { + assert_return(bus, -EINVAL); + assert_return(name, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + assert_return(service_name_is_valid(name), -EINVAL); + assert_return(name[0] != ':', -EINVAL); + + if (!bus->bus_client) + return -EINVAL; + + /* Don't allow releasing the special driver and local names */ + if (STR_IN_SET(name, "org.freedesktop.DBus", "org.freedesktop.DBus.Local")) + return -EINVAL; + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (bus->is_kernel) + return bus_release_name_kernel(bus, name); + else + return bus_release_name_dbus1(bus, name); +} + +static int kernel_get_list(sd_bus *bus, uint64_t flags, char ***x) { + struct kdbus_cmd_list cmd = { + .size = sizeof(cmd), + .flags = flags, + }; + struct kdbus_info *name_list, *name; + uint64_t previous_id = 0; + int r; + + /* Caller will free half-constructed list on failure... */ + + r = ioctl(bus->input_fd, KDBUS_CMD_LIST, &cmd); + if (r < 0) + return -errno; + + name_list = (struct kdbus_info *) ((uint8_t *) bus->kdbus_buffer + cmd.offset); + + KDBUS_FOREACH(name, name_list, cmd.list_size) { + struct kdbus_item *item; + + if ((flags & KDBUS_LIST_UNIQUE) && name->id != previous_id && !(name->flags & KDBUS_HELLO_ACTIVATOR)) { + char *n; + + if (asprintf(&n, ":1.%llu", (unsigned long long) name->id) < 0) { + r = -ENOMEM; + goto fail; + } + + r = strv_consume(x, n); + if (r < 0) + goto fail; + + previous_id = name->id; + } + + KDBUS_ITEM_FOREACH(item, name, items) { + if (item->type == KDBUS_ITEM_OWNED_NAME) { + if (service_name_is_valid(item->name.name)) { + r = strv_extend(x, item->name.name); + if (r < 0) { + r = -ENOMEM; + goto fail; + } + } + } + } + } + + r = 0; + +fail: + bus_kernel_cmd_free(bus, cmd.offset); + return r; +} + +static int bus_list_names_kernel(sd_bus *bus, char ***acquired, char ***activatable) { + _cleanup_strv_free_ char **x = NULL, **y = NULL; + int r; + + if (acquired) { + r = kernel_get_list(bus, KDBUS_LIST_UNIQUE | KDBUS_LIST_NAMES, &x); + if (r < 0) + return r; + } + + if (activatable) { + r = kernel_get_list(bus, KDBUS_LIST_ACTIVATORS, &y); + if (r < 0) + return r; + + *activatable = y; + y = NULL; + } + + if (acquired) { + *acquired = x; + x = NULL; + } + + return 0; +} + +static int bus_list_names_dbus1(sd_bus *bus, char ***acquired, char ***activatable) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_strv_free_ char **x = NULL, **y = NULL; + int r; + + if (acquired) { + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "ListNames", + NULL, + &reply, + NULL); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(reply, &x); + if (r < 0) + return r; + + reply = sd_bus_message_unref(reply); + } + + if (activatable) { + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "ListActivatableNames", + NULL, + &reply, + NULL); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(reply, &y); + if (r < 0) + return r; + + *activatable = y; + y = NULL; + } + + if (acquired) { + *acquired = x; + x = NULL; + } + + return 0; +} + +_public_ int sd_bus_list_names(sd_bus *bus, char ***acquired, char ***activatable) { + assert_return(bus, -EINVAL); + assert_return(acquired || activatable, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!bus->bus_client) + return -EINVAL; + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (bus->is_kernel) + return bus_list_names_kernel(bus, acquired, activatable); + else + return bus_list_names_dbus1(bus, acquired, activatable); +} + +static int bus_populate_creds_from_items( + sd_bus *bus, + struct kdbus_info *info, + uint64_t mask, + sd_bus_creds *c) { + + struct kdbus_item *item; + uint64_t m; + int r; + + assert(bus); + assert(info); + assert(c); + + KDBUS_ITEM_FOREACH(item, info, items) { + + switch (item->type) { + + case KDBUS_ITEM_PIDS: + + if (mask & SD_BUS_CREDS_PID && item->pids.pid > 0) { + c->pid = (pid_t) item->pids.pid; + c->mask |= SD_BUS_CREDS_PID; + } + + if (mask & SD_BUS_CREDS_TID && item->pids.tid > 0) { + c->tid = (pid_t) item->pids.tid; + c->mask |= SD_BUS_CREDS_TID; + } + + if (mask & SD_BUS_CREDS_PPID) { + if (item->pids.ppid > 0) { + c->ppid = (pid_t) item->pids.ppid; + c->mask |= SD_BUS_CREDS_PPID; + } else if (item->pids.pid == 1) { + /* The structure doesn't + * really distinguish the case + * where a process has no + * parent and where we don't + * know it because it could + * not be translated due to + * namespaces. However, we + * know that PID 1 has no + * parent process, hence let's + * patch that in, manually. */ + c->ppid = 0; + c->mask |= SD_BUS_CREDS_PPID; + } + } + + break; + + case KDBUS_ITEM_CREDS: + + if (mask & SD_BUS_CREDS_UID && (uid_t) item->creds.uid != UID_INVALID) { + c->uid = (uid_t) item->creds.uid; + c->mask |= SD_BUS_CREDS_UID; + } + + if (mask & SD_BUS_CREDS_EUID && (uid_t) item->creds.euid != UID_INVALID) { + c->euid = (uid_t) item->creds.euid; + c->mask |= SD_BUS_CREDS_EUID; + } + + if (mask & SD_BUS_CREDS_SUID && (uid_t) item->creds.suid != UID_INVALID) { + c->suid = (uid_t) item->creds.suid; + c->mask |= SD_BUS_CREDS_SUID; + } + + if (mask & SD_BUS_CREDS_FSUID && (uid_t) item->creds.fsuid != UID_INVALID) { + c->fsuid = (uid_t) item->creds.fsuid; + c->mask |= SD_BUS_CREDS_FSUID; + } + + if (mask & SD_BUS_CREDS_GID && (gid_t) item->creds.gid != GID_INVALID) { + c->gid = (gid_t) item->creds.gid; + c->mask |= SD_BUS_CREDS_GID; + } + + if (mask & SD_BUS_CREDS_EGID && (gid_t) item->creds.egid != GID_INVALID) { + c->egid = (gid_t) item->creds.egid; + c->mask |= SD_BUS_CREDS_EGID; + } + + if (mask & SD_BUS_CREDS_SGID && (gid_t) item->creds.sgid != GID_INVALID) { + c->sgid = (gid_t) item->creds.sgid; + c->mask |= SD_BUS_CREDS_SGID; + } + + if (mask & SD_BUS_CREDS_FSGID && (gid_t) item->creds.fsgid != GID_INVALID) { + c->fsgid = (gid_t) item->creds.fsgid; + c->mask |= SD_BUS_CREDS_FSGID; + } + + break; + + case KDBUS_ITEM_PID_COMM: + if (mask & SD_BUS_CREDS_COMM) { + r = free_and_strdup(&c->comm, item->str); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_COMM; + } + break; + + case KDBUS_ITEM_TID_COMM: + if (mask & SD_BUS_CREDS_TID_COMM) { + r = free_and_strdup(&c->tid_comm, item->str); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_TID_COMM; + } + break; + + case KDBUS_ITEM_EXE: + if (mask & SD_BUS_CREDS_EXE) { + r = free_and_strdup(&c->exe, item->str); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_EXE; + } + break; + + case KDBUS_ITEM_CMDLINE: + if (mask & SD_BUS_CREDS_CMDLINE) { + c->cmdline_size = item->size - offsetof(struct kdbus_item, data); + c->cmdline = memdup(item->data, c->cmdline_size); + if (!c->cmdline) + return -ENOMEM; + + c->mask |= SD_BUS_CREDS_CMDLINE; + } + break; + + case KDBUS_ITEM_CGROUP: + m = (SD_BUS_CREDS_CGROUP | SD_BUS_CREDS_UNIT | + SD_BUS_CREDS_USER_UNIT | SD_BUS_CREDS_SLICE | + SD_BUS_CREDS_SESSION | SD_BUS_CREDS_OWNER_UID) & mask; + + if (m) { + r = free_and_strdup(&c->cgroup, item->str); + if (r < 0) + return r; + + r = bus_get_root_path(bus); + if (r < 0) + return r; + + r = free_and_strdup(&c->cgroup_root, bus->cgroup_root); + if (r < 0) + return r; + + c->mask |= m; + } + break; + + case KDBUS_ITEM_CAPS: + m = (SD_BUS_CREDS_EFFECTIVE_CAPS | SD_BUS_CREDS_PERMITTED_CAPS | + SD_BUS_CREDS_INHERITABLE_CAPS | SD_BUS_CREDS_BOUNDING_CAPS) & mask; + + if (m) { + if (item->caps.last_cap != cap_last_cap() || + item->size - offsetof(struct kdbus_item, caps.caps) < DIV_ROUND_UP(item->caps.last_cap, 32U) * 4 * 4) + return -EBADMSG; + + c->capability = memdup(item->caps.caps, item->size - offsetof(struct kdbus_item, caps.caps)); + if (!c->capability) + return -ENOMEM; + + c->mask |= m; + } + break; + + case KDBUS_ITEM_SECLABEL: + if (mask & SD_BUS_CREDS_SELINUX_CONTEXT) { + r = free_and_strdup(&c->label, item->str); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; + } + break; + + case KDBUS_ITEM_AUDIT: + if (mask & SD_BUS_CREDS_AUDIT_SESSION_ID) { + c->audit_session_id = (uint32_t) item->audit.sessionid; + c->mask |= SD_BUS_CREDS_AUDIT_SESSION_ID; + } + + if (mask & SD_BUS_CREDS_AUDIT_LOGIN_UID) { + c->audit_login_uid = (uid_t) item->audit.loginuid; + c->mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID; + } + break; + + case KDBUS_ITEM_OWNED_NAME: + if ((mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) && service_name_is_valid(item->name.name)) { + r = strv_extend(&c->well_known_names, item->name.name); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES; + } + break; + + case KDBUS_ITEM_CONN_DESCRIPTION: + if (mask & SD_BUS_CREDS_DESCRIPTION) { + r = free_and_strdup(&c->description, item->str); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_DESCRIPTION; + } + break; + + case KDBUS_ITEM_AUXGROUPS: + if (mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { + size_t i, n; + uid_t *g; + + n = (item->size - offsetof(struct kdbus_item, data64)) / sizeof(uint64_t); + g = new(gid_t, n); + if (!g) + return -ENOMEM; + + for (i = 0; i < n; i++) + g[i] = item->data64[i]; + + free(c->supplementary_gids); + c->supplementary_gids = g; + c->n_supplementary_gids = n; + + c->mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS; + } + break; + } + } + + return 0; +} + +int bus_get_name_creds_kdbus( + sd_bus *bus, + const char *name, + uint64_t mask, + bool allow_activator, + sd_bus_creds **creds) { + + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; + struct kdbus_cmd_info *cmd; + struct kdbus_info *conn_info; + size_t size, l; + uint64_t id; + int r; + + if (streq(name, "org.freedesktop.DBus")) + return -EOPNOTSUPP; + + r = bus_kernel_parse_unique_name(name, &id); + if (r < 0) + return r; + if (r > 0) { + size = offsetof(struct kdbus_cmd_info, items); + cmd = alloca0_align(size, 8); + cmd->id = id; + } else { + l = strlen(name) + 1; + size = offsetof(struct kdbus_cmd_info, items) + KDBUS_ITEM_SIZE(l); + cmd = alloca0_align(size, 8); + cmd->items[0].size = KDBUS_ITEM_HEADER_SIZE + l; + cmd->items[0].type = KDBUS_ITEM_NAME; + memcpy(cmd->items[0].str, name, l); + } + + /* If augmentation is on, and the bus didn't provide us + * the bits we want, then ask for the PID/TID so that we + * can read the rest from /proc. */ + if ((mask & SD_BUS_CREDS_AUGMENT) && + (mask & (SD_BUS_CREDS_PPID| + SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| + SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| + SD_BUS_CREDS_SUPPLEMENTARY_GIDS| + SD_BUS_CREDS_COMM|SD_BUS_CREDS_TID_COMM|SD_BUS_CREDS_EXE|SD_BUS_CREDS_CMDLINE| + SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID| + SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS| + SD_BUS_CREDS_SELINUX_CONTEXT| + SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID))) + mask |= SD_BUS_CREDS_PID; + + cmd->size = size; + cmd->attach_flags = attach_flags_to_kdbus(mask); + + r = ioctl(bus->input_fd, KDBUS_CMD_CONN_INFO, cmd); + if (r < 0) + return -errno; + + conn_info = (struct kdbus_info *) ((uint8_t *) bus->kdbus_buffer + cmd->offset); + + /* Non-activated names are considered not available */ + if (!allow_activator && (conn_info->flags & KDBUS_HELLO_ACTIVATOR)) { + if (name[0] == ':') + r = -ENXIO; + else + r = -ESRCH; + goto fail; + } + + c = bus_creds_new(); + if (!c) { + r = -ENOMEM; + goto fail; + } + + if (mask & SD_BUS_CREDS_UNIQUE_NAME) { + if (asprintf(&c->unique_name, ":1.%llu", (unsigned long long) conn_info->id) < 0) { + r = -ENOMEM; + goto fail; + } + + c->mask |= SD_BUS_CREDS_UNIQUE_NAME; + } + + /* If KDBUS_ITEM_OWNED_NAME is requested then we'll get 0 of + them in case the service has no names. This does not mean + however that the list of owned names could not be + acquired. Hence, let's explicitly clarify that the data is + complete. */ + c->mask |= mask & SD_BUS_CREDS_WELL_KNOWN_NAMES; + + r = bus_populate_creds_from_items(bus, conn_info, mask, c); + if (r < 0) + goto fail; + + r = bus_creds_add_more(c, mask, 0, 0); + if (r < 0) + goto fail; + + if (creds) { + *creds = c; + c = NULL; + } + + r = 0; + +fail: + bus_kernel_cmd_free(bus, cmd->offset); + return r; +} + +static int bus_get_name_creds_dbus1( + sd_bus *bus, + const char *name, + uint64_t mask, + sd_bus_creds **creds) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply_unique = NULL, *reply = NULL; + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; + const char *unique = NULL; + pid_t pid = 0; + int r; + + /* Only query the owner if the caller wants to know it or if + * the caller just wants to check whether a name exists */ + if ((mask & SD_BUS_CREDS_UNIQUE_NAME) || mask == 0) { + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetNameOwner", + NULL, + &reply_unique, + "s", + name); + if (r < 0) + return r; + + r = sd_bus_message_read(reply_unique, "s", &unique); + if (r < 0) + return r; + } + + if (mask != 0) { + c = bus_creds_new(); + if (!c) + return -ENOMEM; + + if ((mask & SD_BUS_CREDS_UNIQUE_NAME) && unique) { + c->unique_name = strdup(unique); + if (!c->unique_name) + return -ENOMEM; + + c->mask |= SD_BUS_CREDS_UNIQUE_NAME; + } + + if ((mask & SD_BUS_CREDS_PID) || + ((mask & SD_BUS_CREDS_AUGMENT) && + (mask & (SD_BUS_CREDS_UID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| + SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| + SD_BUS_CREDS_SUPPLEMENTARY_GIDS| + SD_BUS_CREDS_COMM|SD_BUS_CREDS_EXE|SD_BUS_CREDS_CMDLINE| + SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID| + SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS| + SD_BUS_CREDS_SELINUX_CONTEXT| + SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID)))) { + + uint32_t u; + + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetConnectionUnixProcessID", + NULL, + &reply, + "s", + unique ? unique : name); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "u", &u); + if (r < 0) + return r; + + pid = u; + if (mask & SD_BUS_CREDS_PID) { + c->pid = u; + c->mask |= SD_BUS_CREDS_PID; + } + + reply = sd_bus_message_unref(reply); + } + + if (mask & SD_BUS_CREDS_EUID) { + uint32_t u; + + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetConnectionUnixUser", + NULL, + &reply, + "s", + unique ? unique : name); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "u", &u); + if (r < 0) + return r; + + c->euid = u; + c->mask |= SD_BUS_CREDS_EUID; + + reply = sd_bus_message_unref(reply); + } + + if (mask & SD_BUS_CREDS_SELINUX_CONTEXT) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const void *p = NULL; + size_t sz = 0; + + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetConnectionSELinuxSecurityContext", + &error, + &reply, + "s", + unique ? unique : name); + if (r < 0) { + if (!sd_bus_error_has_name(&error, "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown")) + return r; + } else { + r = sd_bus_message_read_array(reply, 'y', &p, &sz); + if (r < 0) + return r; + + c->label = strndup(p, sz); + if (!c->label) + return -ENOMEM; + + c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; + } + } + + r = bus_creds_add_more(c, mask, pid, 0); + if (r < 0) + return r; + } + + if (creds) { + *creds = c; + c = NULL; + } + + return 0; +} + +_public_ int sd_bus_get_name_creds( + sd_bus *bus, + const char *name, + uint64_t mask, + sd_bus_creds **creds) { + + assert_return(bus, -EINVAL); + assert_return(name, -EINVAL); + assert_return((mask & ~SD_BUS_CREDS_AUGMENT) <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP); + assert_return(mask == 0 || creds, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + assert_return(service_name_is_valid(name), -EINVAL); + + if (!bus->bus_client) + return -EINVAL; + + if (streq(name, "org.freedesktop.DBus.Local")) + return -EINVAL; + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (bus->is_kernel) + return bus_get_name_creds_kdbus(bus, name, mask, false, creds); + else + return bus_get_name_creds_dbus1(bus, name, mask, creds); +} + +static int bus_get_owner_creds_kdbus(sd_bus *bus, uint64_t mask, sd_bus_creds **ret) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; + struct kdbus_cmd_info cmd = { + .size = sizeof(struct kdbus_cmd_info), + }; + struct kdbus_info *creator_info; + pid_t pid = 0; + int r; + + c = bus_creds_new(); + if (!c) + return -ENOMEM; + + /* If augmentation is on, and the bus doesn't didn't allow us + * to get the bits we want, then ask for the PID/TID so that we + * can read the rest from /proc. */ + if ((mask & SD_BUS_CREDS_AUGMENT) && + (mask & (SD_BUS_CREDS_PPID| + SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| + SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| + SD_BUS_CREDS_SUPPLEMENTARY_GIDS| + SD_BUS_CREDS_COMM|SD_BUS_CREDS_TID_COMM|SD_BUS_CREDS_EXE|SD_BUS_CREDS_CMDLINE| + SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID| + SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS| + SD_BUS_CREDS_SELINUX_CONTEXT| + SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID))) + mask |= SD_BUS_CREDS_PID; + + cmd.attach_flags = attach_flags_to_kdbus(mask); + + r = ioctl(bus->input_fd, KDBUS_CMD_BUS_CREATOR_INFO, &cmd); + if (r < 0) + return -errno; + + creator_info = (struct kdbus_info *) ((uint8_t *) bus->kdbus_buffer + cmd.offset); + + r = bus_populate_creds_from_items(bus, creator_info, mask, c); + bus_kernel_cmd_free(bus, cmd.offset); + if (r < 0) + return r; + + r = bus_creds_add_more(c, mask, pid, 0); + if (r < 0) + return r; + + *ret = c; + c = NULL; + return 0; +} + +static int bus_get_owner_creds_dbus1(sd_bus *bus, uint64_t mask, sd_bus_creds **ret) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; + pid_t pid = 0; + bool do_label; + int r; + + assert(bus); + + do_label = bus->label && (mask & SD_BUS_CREDS_SELINUX_CONTEXT); + + /* Avoid allocating anything if we have no chance of returning useful data */ + if (!bus->ucred_valid && !do_label) + return -ENODATA; + + c = bus_creds_new(); + if (!c) + return -ENOMEM; + + if (bus->ucred_valid) { + if (bus->ucred.pid > 0) { + pid = c->pid = bus->ucred.pid; + c->mask |= SD_BUS_CREDS_PID & mask; + } + + if (bus->ucred.uid != UID_INVALID) { + c->euid = bus->ucred.uid; + c->mask |= SD_BUS_CREDS_EUID & mask; + } + + if (bus->ucred.gid != GID_INVALID) { + c->egid = bus->ucred.gid; + c->mask |= SD_BUS_CREDS_EGID & mask; + } + } + + if (do_label) { + c->label = strdup(bus->label); + if (!c->label) + return -ENOMEM; + + c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; + } + + r = bus_creds_add_more(c, mask, pid, 0); + if (r < 0) + return r; + + *ret = c; + c = NULL; + return 0; +} + +_public_ int sd_bus_get_owner_creds(sd_bus *bus, uint64_t mask, sd_bus_creds **ret) { + assert_return(bus, -EINVAL); + assert_return((mask & ~SD_BUS_CREDS_AUGMENT) <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP); + assert_return(ret, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (bus->is_kernel) + return bus_get_owner_creds_kdbus(bus, mask, ret); + else + return bus_get_owner_creds_dbus1(bus, mask, ret); +} + +static int add_name_change_match(sd_bus *bus, + uint64_t cookie, + const char *name, + const char *old_owner, + const char *new_owner) { + + uint64_t name_id = KDBUS_MATCH_ID_ANY, old_owner_id = 0, new_owner_id = 0; + int is_name_id = -1, r; + struct kdbus_item *item; + + assert(bus); + + /* If we encounter a match that could match against + * NameOwnerChanged messages, then we need to create + * KDBUS_ITEM_NAME_{ADD,REMOVE,CHANGE} and + * KDBUS_ITEM_ID_{ADD,REMOVE} matches for it, possibly + * multiple if the match is underspecified. + * + * The NameOwnerChanged signals take three parameters with + * unique or well-known names, but only some forms actually + * exist: + * + * WELLKNOWN, "", UNIQUE → KDBUS_ITEM_NAME_ADD + * WELLKNOWN, UNIQUE, "" → KDBUS_ITEM_NAME_REMOVE + * WELLKNOWN, UNIQUE, UNIQUE → KDBUS_ITEM_NAME_CHANGE + * UNIQUE, "", UNIQUE → KDBUS_ITEM_ID_ADD + * UNIQUE, UNIQUE, "" → KDBUS_ITEM_ID_REMOVE + * + * For the latter two the two unique names must be identical. + * + * */ + + if (name) { + is_name_id = bus_kernel_parse_unique_name(name, &name_id); + if (is_name_id < 0) + return 0; + } + + if (!isempty(old_owner)) { + r = bus_kernel_parse_unique_name(old_owner, &old_owner_id); + if (r < 0) + return 0; + if (r == 0) + return 0; + if (is_name_id > 0 && old_owner_id != name_id) + return 0; + } else + old_owner_id = KDBUS_MATCH_ID_ANY; + + if (!isempty(new_owner)) { + r = bus_kernel_parse_unique_name(new_owner, &new_owner_id); + if (r < 0) + return r; + if (r == 0) + return 0; + if (is_name_id > 0 && new_owner_id != name_id) + return 0; + } else + new_owner_id = KDBUS_MATCH_ID_ANY; + + if (is_name_id <= 0) { + struct kdbus_cmd_match *m; + size_t sz, l; + + /* If the name argument is missing or is a well-known + * name, then add KDBUS_ITEM_NAME_{ADD,REMOVE,CHANGE} + * matches for it */ + + l = name ? strlen(name) + 1 : 0; + + sz = ALIGN8(offsetof(struct kdbus_cmd_match, items) + + offsetof(struct kdbus_item, name_change) + + offsetof(struct kdbus_notify_name_change, name) + + l); + + m = alloca0_align(sz, 8); + m->size = sz; + m->cookie = cookie; + + item = m->items; + item->size = + offsetof(struct kdbus_item, name_change) + + offsetof(struct kdbus_notify_name_change, name) + + l; + + item->name_change.old_id.id = old_owner_id; + item->name_change.new_id.id = new_owner_id; + + memcpy_safe(item->name_change.name, name, l); + + /* If the old name is unset or empty, then + * this can match against added names */ + if (isempty(old_owner)) { + item->type = KDBUS_ITEM_NAME_ADD; + + r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); + if (r < 0) + return -errno; + } + + /* If the new name is unset or empty, then + * this can match against removed names */ + if (isempty(new_owner)) { + item->type = KDBUS_ITEM_NAME_REMOVE; + + r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); + if (r < 0) + return -errno; + } + + /* The CHANGE match we need in either case, because + * what is reported as a name change by the kernel + * might just be an owner change between starter and + * normal clients. For userspace such a change should + * be considered a removal/addition, hence let's + * subscribe to this unconditionally. */ + item->type = KDBUS_ITEM_NAME_CHANGE; + r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); + if (r < 0) + return -errno; + } + + if (is_name_id != 0) { + struct kdbus_cmd_match *m; + uint64_t sz; + + /* If the name argument is missing or is a unique + * name, then add KDBUS_ITEM_ID_{ADD,REMOVE} matches + * for it */ + + sz = ALIGN8(offsetof(struct kdbus_cmd_match, items) + + offsetof(struct kdbus_item, id_change) + + sizeof(struct kdbus_notify_id_change)); + + m = alloca0_align(sz, 8); + m->size = sz; + m->cookie = cookie; + + item = m->items; + item->size = + offsetof(struct kdbus_item, id_change) + + sizeof(struct kdbus_notify_id_change); + item->id_change.id = name_id; + + /* If the old name is unset or empty, then this can + * match against added ids */ + if (isempty(old_owner)) { + item->type = KDBUS_ITEM_ID_ADD; + if (!isempty(new_owner)) + item->id_change.id = new_owner_id; + + r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); + if (r < 0) + return -errno; + } + + /* If thew new name is unset or empty, then this can + * match against removed ids */ + if (isempty(new_owner)) { + item->type = KDBUS_ITEM_ID_REMOVE; + if (!isempty(old_owner)) + item->id_change.id = old_owner_id; + + r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); + if (r < 0) + return -errno; + } + } + + return 0; +} + +int bus_add_match_internal_kernel( + sd_bus *bus, + struct bus_match_component *components, + unsigned n_components, + uint64_t cookie) { + + struct kdbus_cmd_match *m; + struct kdbus_item *item; + uint64_t *bloom; + size_t sz; + const char *sender = NULL; + size_t sender_length = 0; + uint64_t src_id = KDBUS_MATCH_ID_ANY, dst_id = KDBUS_MATCH_ID_ANY; + bool using_bloom = false; + unsigned i; + bool matches_name_change = true; + const char *name_change_arg[3] = {}; + int r; + + assert(bus); + + /* Monitor streams don't support matches, make this a NOP */ + if (bus->hello_flags & KDBUS_HELLO_MONITOR) + return 0; + + bloom = alloca0(bus->bloom_size); + + sz = ALIGN8(offsetof(struct kdbus_cmd_match, items)); + + for (i = 0; i < n_components; i++) { + struct bus_match_component *c = &components[i]; + + switch (c->type) { + + case BUS_MATCH_SENDER: + if (!streq(c->value_str, "org.freedesktop.DBus")) + matches_name_change = false; + + r = bus_kernel_parse_unique_name(c->value_str, &src_id); + if (r < 0) + return r; + else if (r > 0) + sz += ALIGN8(offsetof(struct kdbus_item, id) + sizeof(uint64_t)); + else { + sender = c->value_str; + sender_length = strlen(sender); + sz += ALIGN8(offsetof(struct kdbus_item, str) + sender_length + 1); + } + + break; + + case BUS_MATCH_MESSAGE_TYPE: + if (c->value_u8 != SD_BUS_MESSAGE_SIGNAL) + matches_name_change = false; + + bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "message-type", bus_message_type_to_string(c->value_u8)); + using_bloom = true; + break; + + case BUS_MATCH_INTERFACE: + if (!streq(c->value_str, "org.freedesktop.DBus")) + matches_name_change = false; + + bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "interface", c->value_str); + using_bloom = true; + break; + + case BUS_MATCH_MEMBER: + if (!streq(c->value_str, "NameOwnerChanged")) + matches_name_change = false; + + bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "member", c->value_str); + using_bloom = true; + break; + + case BUS_MATCH_PATH: + if (!streq(c->value_str, "/org/freedesktop/DBus")) + matches_name_change = false; + + bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "path", c->value_str); + using_bloom = true; + break; + + case BUS_MATCH_PATH_NAMESPACE: + bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "path-slash-prefix", c->value_str); + using_bloom = true; + break; + + case BUS_MATCH_ARG...BUS_MATCH_ARG_LAST: { + char buf[sizeof("arg")-1 + 2 + 1]; + + if (c->type - BUS_MATCH_ARG < 3) + name_change_arg[c->type - BUS_MATCH_ARG] = c->value_str; + + xsprintf(buf, "arg%i", c->type - BUS_MATCH_ARG); + bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, buf, c->value_str); + using_bloom = true; + break; + } + + case BUS_MATCH_ARG_HAS...BUS_MATCH_ARG_HAS_LAST: { + char buf[sizeof("arg")-1 + 2 + sizeof("-has")]; + + xsprintf(buf, "arg%i-has", c->type - BUS_MATCH_ARG_HAS); + bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, buf, c->value_str); + using_bloom = true; + break; + } + + case BUS_MATCH_ARG_PATH...BUS_MATCH_ARG_PATH_LAST: + /* + * XXX: DBus spec defines arg[0..63]path= matching to be + * a two-way glob. That is, if either string is a prefix + * of the other, it matches. + * This is really hard to realize in bloom-filters, as + * we would have to create a bloom-match for each prefix + * of @c->value_str. This is excessive, hence we just + * ignore all those matches and accept everything from + * the kernel. People should really avoid those matches. + * If they're used in real-life some day, we will have + * to properly support multiple-matches here. + */ + break; + + case BUS_MATCH_ARG_NAMESPACE...BUS_MATCH_ARG_NAMESPACE_LAST: { + char buf[sizeof("arg")-1 + 2 + sizeof("-dot-prefix")]; + + xsprintf(buf, "arg%i-dot-prefix", c->type - BUS_MATCH_ARG_NAMESPACE); + bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, buf, c->value_str); + using_bloom = true; + break; + } + + case BUS_MATCH_DESTINATION: + /* + * Kernel only supports matching on destination IDs, but + * not on destination names. So just skip the + * destination name restriction and verify it in + * user-space on retrieval. + */ + r = bus_kernel_parse_unique_name(c->value_str, &dst_id); + if (r < 0) + return r; + else if (r > 0) + sz += ALIGN8(offsetof(struct kdbus_item, id) + sizeof(uint64_t)); + + /* if not a broadcast, it cannot be a name-change */ + if (r <= 0 || dst_id != KDBUS_DST_ID_BROADCAST) + matches_name_change = false; + + break; + + case BUS_MATCH_ROOT: + case BUS_MATCH_VALUE: + case BUS_MATCH_LEAF: + case _BUS_MATCH_NODE_TYPE_MAX: + case _BUS_MATCH_NODE_TYPE_INVALID: + assert_not_reached("Invalid match type?"); + } + } + + if (using_bloom) + sz += ALIGN8(offsetof(struct kdbus_item, data64) + bus->bloom_size); + + m = alloca0_align(sz, 8); + m->size = sz; + m->cookie = cookie; + + item = m->items; + + if (src_id != KDBUS_MATCH_ID_ANY) { + item->size = offsetof(struct kdbus_item, id) + sizeof(uint64_t); + item->type = KDBUS_ITEM_ID; + item->id = src_id; + item = KDBUS_ITEM_NEXT(item); + } + + if (dst_id != KDBUS_MATCH_ID_ANY) { + item->size = offsetof(struct kdbus_item, id) + sizeof(uint64_t); + item->type = KDBUS_ITEM_DST_ID; + item->id = dst_id; + item = KDBUS_ITEM_NEXT(item); + } + + if (using_bloom) { + item->size = offsetof(struct kdbus_item, data64) + bus->bloom_size; + item->type = KDBUS_ITEM_BLOOM_MASK; + memcpy(item->data64, bloom, bus->bloom_size); + item = KDBUS_ITEM_NEXT(item); + } + + if (sender) { + item->size = offsetof(struct kdbus_item, str) + sender_length + 1; + item->type = KDBUS_ITEM_NAME; + memcpy(item->str, sender, sender_length + 1); + } + + r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); + if (r < 0) + return -errno; + + if (matches_name_change) { + + /* If this match could theoretically match + * NameOwnerChanged messages, we need to + * install a second non-bloom filter explitly + * for it */ + + r = add_name_change_match(bus, cookie, name_change_arg[0], name_change_arg[1], name_change_arg[2]); + if (r < 0) + return r; + } + + return 0; +} + +#define internal_match(bus, m) \ + ((bus)->hello_flags & KDBUS_HELLO_MONITOR \ + ? (isempty(m) ? "eavesdrop='true'" : strjoina((m), ",eavesdrop='true'")) \ + : (m)) + +static int bus_add_match_internal_dbus1( + sd_bus *bus, + const char *match) { + + const char *e; + + assert(bus); + assert(match); + + e = internal_match(bus, match); + + return sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "AddMatch", + NULL, + NULL, + "s", + e); +} + +int bus_add_match_internal( + sd_bus *bus, + const char *match, + struct bus_match_component *components, + unsigned n_components, + uint64_t cookie) { + + assert(bus); + + if (!bus->bus_client) + return -EINVAL; + + if (bus->is_kernel) + return bus_add_match_internal_kernel(bus, components, n_components, cookie); + else + return bus_add_match_internal_dbus1(bus, match); +} + +int bus_remove_match_internal_kernel( + sd_bus *bus, + uint64_t cookie) { + + struct kdbus_cmd_match m = { + .size = offsetof(struct kdbus_cmd_match, items), + .cookie = cookie, + }; + int r; + + assert(bus); + + /* Monitor streams don't support matches, make this a NOP */ + if (bus->hello_flags & KDBUS_HELLO_MONITOR) + return 0; + + r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_REMOVE, &m); + if (r < 0) + return -errno; + + return 0; +} + +static int bus_remove_match_internal_dbus1( + sd_bus *bus, + const char *match) { + + const char *e; + + assert(bus); + assert(match); + + e = internal_match(bus, match); + + return sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "RemoveMatch", + NULL, + NULL, + "s", + e); +} + +int bus_remove_match_internal( + sd_bus *bus, + const char *match, + uint64_t cookie) { + + assert(bus); + + if (!bus->bus_client) + return -EINVAL; + + if (bus->is_kernel) + return bus_remove_match_internal_kernel(bus, cookie); + else + return bus_remove_match_internal_dbus1(bus, match); +} + +_public_ int sd_bus_get_name_machine_id(sd_bus *bus, const char *name, sd_id128_t *machine) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; + const char *mid; + int r; + + assert_return(bus, -EINVAL); + assert_return(name, -EINVAL); + assert_return(machine, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + assert_return(service_name_is_valid(name), -EINVAL); + + if (!bus->bus_client) + return -EINVAL; + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (streq_ptr(name, bus->unique_name)) + return sd_id128_get_machine(machine); + + r = sd_bus_message_new_method_call( + bus, + &m, + name, + "/", + "org.freedesktop.DBus.Peer", + "GetMachineId"); + if (r < 0) + return r; + + r = sd_bus_message_set_auto_start(m, false); + if (r < 0) + return r; + + r = sd_bus_call(bus, m, 0, NULL, &reply); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "s", &mid); + if (r < 0) + return r; + + return sd_id128_from_string(mid, machine); +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-control.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-control.h new file mode 100644 index 0000000000..229c95efb0 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-control.h @@ -0,0 +1,32 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 "bus-match.h" + +int bus_add_match_internal(sd_bus *bus, const char *match, struct bus_match_component *components, unsigned n_components, uint64_t cookie); +int bus_remove_match_internal(sd_bus *bus, const char *match, uint64_t cookie); + +int bus_add_match_internal_kernel(sd_bus *bus, struct bus_match_component *components, unsigned n_components, uint64_t cookie); +int bus_remove_match_internal_kernel(sd_bus *bus, uint64_t cookie); + +int bus_get_name_creds_kdbus(sd_bus *bus, const char *name, uint64_t mask, bool allow_activator, sd_bus_creds **creds); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-convenience.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-convenience.c new file mode 100644 index 0000000000..2d06bf541f --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-convenience.c @@ -0,0 +1,626 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "bus-internal.h" +#include "bus-message.h" +#include "bus-signature.h" +#include "bus-type.h" +#include "bus-util.h" +#include "string-util.h" + +_public_ int sd_bus_emit_signal( + sd_bus *bus, + const char *path, + const char *interface, + const char *member, + const char *types, ...) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + r = sd_bus_message_new_signal(bus, &m, path, interface, member); + if (r < 0) + return r; + + if (!isempty(types)) { + va_list ap; + + va_start(ap, types); + r = bus_message_append_ap(m, types, ap); + va_end(ap); + if (r < 0) + return r; + } + + return sd_bus_send(bus, m, NULL); +} + +_public_ int sd_bus_call_method_async( + sd_bus *bus, + sd_bus_slot **slot, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_message_handler_t callback, + void *userdata, + const char *types, ...) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + r = sd_bus_message_new_method_call(bus, &m, destination, path, interface, member); + if (r < 0) + return r; + + if (!isempty(types)) { + va_list ap; + + va_start(ap, types); + r = bus_message_append_ap(m, types, ap); + va_end(ap); + if (r < 0) + return r; + } + + return sd_bus_call_async(bus, slot, m, callback, userdata, 0); +} + +_public_ int sd_bus_call_method( + sd_bus *bus, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_error *error, + sd_bus_message **reply, + const char *types, ...) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + bus_assert_return(bus, -EINVAL, error); + bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); + + if (!BUS_IS_OPEN(bus->state)) { + r = -ENOTCONN; + goto fail; + } + + r = sd_bus_message_new_method_call(bus, &m, destination, path, interface, member); + if (r < 0) + goto fail; + + if (!isempty(types)) { + va_list ap; + + va_start(ap, types); + r = bus_message_append_ap(m, types, ap); + va_end(ap); + if (r < 0) + goto fail; + } + + return sd_bus_call(bus, m, 0, error, reply); + +fail: + return sd_bus_error_set_errno(error, r); +} + +_public_ int sd_bus_reply_method_return( + sd_bus_message *call, + const char *types, ...) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert_return(call, -EINVAL); + assert_return(call->sealed, -EPERM); + assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); + assert_return(call->bus, -EINVAL); + assert_return(!bus_pid_changed(call->bus), -ECHILD); + + if (!BUS_IS_OPEN(call->bus->state)) + return -ENOTCONN; + + if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) + return 0; + + r = sd_bus_message_new_method_return(call, &m); + if (r < 0) + return r; + + if (!isempty(types)) { + va_list ap; + + va_start(ap, types); + r = bus_message_append_ap(m, types, ap); + va_end(ap); + if (r < 0) + return r; + } + + return sd_bus_send(call->bus, m, NULL); +} + +_public_ int sd_bus_reply_method_error( + sd_bus_message *call, + const sd_bus_error *e) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert_return(call, -EINVAL); + assert_return(call->sealed, -EPERM); + assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); + assert_return(sd_bus_error_is_set(e), -EINVAL); + assert_return(call->bus, -EINVAL); + assert_return(!bus_pid_changed(call->bus), -ECHILD); + + if (!BUS_IS_OPEN(call->bus->state)) + return -ENOTCONN; + + if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) + return 0; + + r = sd_bus_message_new_method_error(call, &m, e); + if (r < 0) + return r; + + return sd_bus_send(call->bus, m, NULL); +} + +_public_ int sd_bus_reply_method_errorf( + sd_bus_message *call, + const char *name, + const char *format, + ...) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + va_list ap; + + assert_return(call, -EINVAL); + assert_return(call->sealed, -EPERM); + assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); + assert_return(call->bus, -EINVAL); + assert_return(!bus_pid_changed(call->bus), -ECHILD); + + if (!BUS_IS_OPEN(call->bus->state)) + return -ENOTCONN; + + if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) + return 0; + + va_start(ap, format); + bus_error_setfv(&error, name, format, ap); + va_end(ap); + + return sd_bus_reply_method_error(call, &error); +} + +_public_ int sd_bus_reply_method_errno( + sd_bus_message *call, + int error, + const sd_bus_error *p) { + + _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL; + + assert_return(call, -EINVAL); + assert_return(call->sealed, -EPERM); + assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); + assert_return(call->bus, -EINVAL); + assert_return(!bus_pid_changed(call->bus), -ECHILD); + + if (!BUS_IS_OPEN(call->bus->state)) + return -ENOTCONN; + + if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) + return 0; + + if (sd_bus_error_is_set(p)) + return sd_bus_reply_method_error(call, p); + + sd_bus_error_set_errno(&berror, error); + + return sd_bus_reply_method_error(call, &berror); +} + +_public_ int sd_bus_reply_method_errnof( + sd_bus_message *call, + int error, + const char *format, + ...) { + + _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL; + va_list ap; + + assert_return(call, -EINVAL); + assert_return(call->sealed, -EPERM); + assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); + assert_return(call->bus, -EINVAL); + assert_return(!bus_pid_changed(call->bus), -ECHILD); + + if (!BUS_IS_OPEN(call->bus->state)) + return -ENOTCONN; + + if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) + return 0; + + va_start(ap, format); + sd_bus_error_set_errnofv(&berror, error, format, ap); + va_end(ap); + + return sd_bus_reply_method_error(call, &berror); +} + +_public_ int sd_bus_get_property( + sd_bus *bus, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_error *error, + sd_bus_message **reply, + const char *type) { + + sd_bus_message *rep = NULL; + int r; + + bus_assert_return(bus, -EINVAL, error); + bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); + bus_assert_return(member_name_is_valid(member), -EINVAL, error); + bus_assert_return(reply, -EINVAL, error); + bus_assert_return(signature_is_single(type, false), -EINVAL, error); + bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); + + if (!BUS_IS_OPEN(bus->state)) { + r = -ENOTCONN; + goto fail; + } + + r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &rep, "ss", strempty(interface), member); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(rep, 'v', type); + if (r < 0) { + sd_bus_message_unref(rep); + goto fail; + } + + *reply = rep; + return 0; + +fail: + return sd_bus_error_set_errno(error, r); +} + +_public_ int sd_bus_get_property_trivial( + sd_bus *bus, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_error *error, + char type, void *ptr) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + bus_assert_return(bus, -EINVAL, error); + bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); + bus_assert_return(member_name_is_valid(member), -EINVAL, error); + bus_assert_return(bus_type_is_trivial(type), -EINVAL, error); + bus_assert_return(ptr, -EINVAL, error); + bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); + + if (!BUS_IS_OPEN(bus->state)) { + r = -ENOTCONN; + goto fail; + } + + r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &reply, "ss", strempty(interface), member); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'v', CHAR_TO_STR(type)); + if (r < 0) + goto fail; + + r = sd_bus_message_read_basic(reply, type, ptr); + if (r < 0) + goto fail; + + return 0; + +fail: + return sd_bus_error_set_errno(error, r); +} + +_public_ int sd_bus_get_property_string( + sd_bus *bus, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_error *error, + char **ret) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *s; + char *n; + int r; + + bus_assert_return(bus, -EINVAL, error); + bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); + bus_assert_return(member_name_is_valid(member), -EINVAL, error); + bus_assert_return(ret, -EINVAL, error); + bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); + + if (!BUS_IS_OPEN(bus->state)) { + r = -ENOTCONN; + goto fail; + } + + r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &reply, "ss", strempty(interface), member); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'v', "s"); + if (r < 0) + goto fail; + + r = sd_bus_message_read_basic(reply, 's', &s); + if (r < 0) + goto fail; + + n = strdup(s); + if (!n) { + r = -ENOMEM; + goto fail; + } + + *ret = n; + return 0; + +fail: + return sd_bus_error_set_errno(error, r); +} + +_public_ int sd_bus_get_property_strv( + sd_bus *bus, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_error *error, + char ***ret) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + bus_assert_return(bus, -EINVAL, error); + bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); + bus_assert_return(member_name_is_valid(member), -EINVAL, error); + bus_assert_return(ret, -EINVAL, error); + bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); + + if (!BUS_IS_OPEN(bus->state)) { + r = -ENOTCONN; + goto fail; + } + + r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &reply, "ss", strempty(interface), member); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'v', NULL); + if (r < 0) + goto fail; + + r = sd_bus_message_read_strv(reply, ret); + if (r < 0) + goto fail; + + return 0; + +fail: + return sd_bus_error_set_errno(error, r); +} + +_public_ int sd_bus_set_property( + sd_bus *bus, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_error *error, + const char *type, ...) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + va_list ap; + int r; + + bus_assert_return(bus, -EINVAL, error); + bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); + bus_assert_return(member_name_is_valid(member), -EINVAL, error); + bus_assert_return(signature_is_single(type, false), -EINVAL, error); + bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); + + if (!BUS_IS_OPEN(bus->state)) { + r = -ENOTCONN; + goto fail; + } + + r = sd_bus_message_new_method_call(bus, &m, destination, path, "org.freedesktop.DBus.Properties", "Set"); + if (r < 0) + goto fail; + + r = sd_bus_message_append(m, "ss", strempty(interface), member); + if (r < 0) + goto fail; + + r = sd_bus_message_open_container(m, 'v', type); + if (r < 0) + goto fail; + + va_start(ap, type); + r = bus_message_append_ap(m, type, ap); + va_end(ap); + if (r < 0) + goto fail; + + r = sd_bus_message_close_container(m); + if (r < 0) + goto fail; + + return sd_bus_call(bus, m, 0, error, NULL); + +fail: + return sd_bus_error_set_errno(error, r); +} + +_public_ int sd_bus_query_sender_creds(sd_bus_message *call, uint64_t mask, sd_bus_creds **creds) { + sd_bus_creds *c; + + assert_return(call, -EINVAL); + assert_return(call->sealed, -EPERM); + assert_return(call->bus, -EINVAL); + assert_return(!bus_pid_changed(call->bus), -ECHILD); + + if (!BUS_IS_OPEN(call->bus->state)) + return -ENOTCONN; + + c = sd_bus_message_get_creds(call); + + /* All data we need? */ + if (c && (mask & ~c->mask) == 0) { + *creds = sd_bus_creds_ref(c); + return 0; + } + + /* No data passed? Or not enough data passed to retrieve the missing bits? */ + if (!c || !(c->mask & SD_BUS_CREDS_PID)) { + /* We couldn't read anything from the call, let's try + * to get it from the sender or peer. */ + + if (call->sender) + /* There's a sender, but the creds are + * missing. This means we are talking via + * dbus1, or are getting a message that was + * sent to us via kdbus, but was converted + * from a dbus1 message by the bus-proxy and + * thus also lacks the creds. */ + return sd_bus_get_name_creds(call->bus, call->sender, mask, creds); + else + /* There's no sender, hence we are on a dbus1 + * direct connection. For direct connections + * the credentials of the AF_UNIX peer matter, + * which may be queried via + * sd_bus_get_owner_creds(). */ + return sd_bus_get_owner_creds(call->bus, mask, creds); + } + + return bus_creds_extend_by_pid(c, mask, creds); +} + +_public_ int sd_bus_query_sender_privilege(sd_bus_message *call, int capability) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + uid_t our_uid; + bool know_caps = false; + int r; + + assert_return(call, -EINVAL); + assert_return(call->sealed, -EPERM); + assert_return(call->bus, -EINVAL); + assert_return(!bus_pid_changed(call->bus), -ECHILD); + + if (!BUS_IS_OPEN(call->bus->state)) + return -ENOTCONN; + + if (capability >= 0) { + + r = sd_bus_query_sender_creds(call, SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_EFFECTIVE_CAPS, &creds); + if (r < 0) + return r; + + /* We cannot use augmented caps for authorization, + * since then data is acquired raceful from + * /proc. This can never actually happen, but let's + * better be safe than sorry, and do an extra check + * here. */ + assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_EFFECTIVE_CAPS) == 0, -EPERM); + + /* Note that not even on kdbus we might have the caps + * field, due to faked identities, or namespace + * translation issues. */ + r = sd_bus_creds_has_effective_cap(creds, capability); + if (r > 0) + return 1; + if (r == 0) + know_caps = true; + } else { + r = sd_bus_query_sender_creds(call, SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID, &creds); + if (r < 0) + return r; + } + + /* Now, check the UID, but only if the capability check wasn't + * sufficient */ + our_uid = getuid(); + if (our_uid != 0 || !know_caps || capability < 0) { + uid_t sender_uid; + + /* We cannot use augmented uid/euid for authorization, + * since then data is acquired raceful from + * /proc. This can never actually happen, but let's + * better be safe than sorry, and do an extra check + * here. */ + assert_return((sd_bus_creds_get_augmented_mask(creds) & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID)) == 0, -EPERM); + + /* Try to use the EUID, if we have it. */ + r = sd_bus_creds_get_euid(creds, &sender_uid); + if (r < 0) + r = sd_bus_creds_get_uid(creds, &sender_uid); + + if (r >= 0) { + /* Sender has same UID as us, then let's grant access */ + if (sender_uid == our_uid) + return 1; + + /* Sender is root, we are not root. */ + if (our_uid != 0 && sender_uid == 0) + return 1; + } + } + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-creds.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-creds.c new file mode 100644 index 0000000000..c4f693dee9 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-creds.c @@ -0,0 +1,1349 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include "alloc-util.h" +#include "audit-util.h" +#include "bus-creds.h" +#include "bus-label.h" +#include "bus-message.h" +#include "bus-util.h" +#include "capability-util.h" +#include "cgroup-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "hexdecoct.h" +#include "parse-util.h" +#include "process-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "user-util.h" +#include "util.h" + +enum { + CAP_OFFSET_INHERITABLE = 0, + CAP_OFFSET_PERMITTED = 1, + CAP_OFFSET_EFFECTIVE = 2, + CAP_OFFSET_BOUNDING = 3 +}; + +void bus_creds_done(sd_bus_creds *c) { + assert(c); + + /* For internal bus cred structures that are allocated by + * something else */ + + free(c->session); + free(c->unit); + free(c->user_unit); + free(c->slice); + free(c->user_slice); + free(c->unescaped_description); + free(c->supplementary_gids); + free(c->tty); + + free(c->well_known_names); /* note that this is an strv, but + * we only free the array, not the + * strings the array points to. The + * full strv we only free if + * c->allocated is set, see + * below. */ + + strv_free(c->cmdline_array); +} + +_public_ sd_bus_creds *sd_bus_creds_ref(sd_bus_creds *c) { + + if (!c) + return NULL; + + if (c->allocated) { + assert(c->n_ref > 0); + c->n_ref++; + } else { + sd_bus_message *m; + + /* If this is an embedded creds structure, then + * forward ref counting to the message */ + m = container_of(c, sd_bus_message, creds); + sd_bus_message_ref(m); + } + + return c; +} + +_public_ sd_bus_creds *sd_bus_creds_unref(sd_bus_creds *c) { + + if (!c) + return NULL; + + if (c->allocated) { + assert(c->n_ref > 0); + c->n_ref--; + + if (c->n_ref == 0) { + free(c->comm); + free(c->tid_comm); + free(c->exe); + free(c->cmdline); + free(c->cgroup); + free(c->capability); + free(c->label); + free(c->unique_name); + free(c->cgroup_root); + free(c->description); + + c->supplementary_gids = mfree(c->supplementary_gids); + + c->well_known_names = strv_free(c->well_known_names); + + bus_creds_done(c); + + free(c); + } + } else { + sd_bus_message *m; + + m = container_of(c, sd_bus_message, creds); + sd_bus_message_unref(m); + } + + + return NULL; +} + +_public_ uint64_t sd_bus_creds_get_mask(const sd_bus_creds *c) { + assert_return(c, 0); + + return c->mask; +} + +_public_ uint64_t sd_bus_creds_get_augmented_mask(const sd_bus_creds *c) { + assert_return(c, 0); + + return c->augmented; +} + +sd_bus_creds* bus_creds_new(void) { + sd_bus_creds *c; + + c = new0(sd_bus_creds, 1); + if (!c) + return NULL; + + c->allocated = true; + c->n_ref = 1; + return c; +} + +_public_ int sd_bus_creds_new_from_pid(sd_bus_creds **ret, pid_t pid, uint64_t mask) { + sd_bus_creds *c; + int r; + + assert_return(pid >= 0, -EINVAL); + assert_return(mask <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP); + assert_return(ret, -EINVAL); + + if (pid == 0) + pid = getpid(); + + c = bus_creds_new(); + if (!c) + return -ENOMEM; + + r = bus_creds_add_more(c, mask | SD_BUS_CREDS_AUGMENT, pid, 0); + if (r < 0) { + sd_bus_creds_unref(c); + return r; + } + + /* Check if the process existed at all, in case we haven't + * figured that out already */ + if (!pid_is_alive(pid)) { + sd_bus_creds_unref(c); + return -ESRCH; + } + + *ret = c; + return 0; +} + +_public_ int sd_bus_creds_get_uid(sd_bus_creds *c, uid_t *uid) { + assert_return(c, -EINVAL); + assert_return(uid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_UID)) + return -ENODATA; + + *uid = c->uid; + return 0; +} + +_public_ int sd_bus_creds_get_euid(sd_bus_creds *c, uid_t *euid) { + assert_return(c, -EINVAL); + assert_return(euid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_EUID)) + return -ENODATA; + + *euid = c->euid; + return 0; +} + +_public_ int sd_bus_creds_get_suid(sd_bus_creds *c, uid_t *suid) { + assert_return(c, -EINVAL); + assert_return(suid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_SUID)) + return -ENODATA; + + *suid = c->suid; + return 0; +} + + +_public_ int sd_bus_creds_get_fsuid(sd_bus_creds *c, uid_t *fsuid) { + assert_return(c, -EINVAL); + assert_return(fsuid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_FSUID)) + return -ENODATA; + + *fsuid = c->fsuid; + return 0; +} + +_public_ int sd_bus_creds_get_gid(sd_bus_creds *c, gid_t *gid) { + assert_return(c, -EINVAL); + assert_return(gid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_GID)) + return -ENODATA; + + *gid = c->gid; + return 0; +} + +_public_ int sd_bus_creds_get_egid(sd_bus_creds *c, gid_t *egid) { + assert_return(c, -EINVAL); + assert_return(egid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_EGID)) + return -ENODATA; + + *egid = c->egid; + return 0; +} + +_public_ int sd_bus_creds_get_sgid(sd_bus_creds *c, gid_t *sgid) { + assert_return(c, -EINVAL); + assert_return(sgid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_SGID)) + return -ENODATA; + + *sgid = c->sgid; + return 0; +} + +_public_ int sd_bus_creds_get_fsgid(sd_bus_creds *c, gid_t *fsgid) { + assert_return(c, -EINVAL); + assert_return(fsgid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_FSGID)) + return -ENODATA; + + *fsgid = c->fsgid; + return 0; +} + +_public_ int sd_bus_creds_get_supplementary_gids(sd_bus_creds *c, const gid_t **gids) { + assert_return(c, -EINVAL); + assert_return(gids, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS)) + return -ENODATA; + + *gids = c->supplementary_gids; + return (int) c->n_supplementary_gids; +} + +_public_ int sd_bus_creds_get_pid(sd_bus_creds *c, pid_t *pid) { + assert_return(c, -EINVAL); + assert_return(pid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_PID)) + return -ENODATA; + + assert(c->pid > 0); + *pid = c->pid; + return 0; +} + +_public_ int sd_bus_creds_get_ppid(sd_bus_creds *c, pid_t *ppid) { + assert_return(c, -EINVAL); + assert_return(ppid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_PPID)) + return -ENODATA; + + /* PID 1 has no parent process. Let's distinguish the case of + * not knowing and not having a parent process by the returned + * error code. */ + if (c->ppid == 0) + return -ENXIO; + + *ppid = c->ppid; + return 0; +} + +_public_ int sd_bus_creds_get_tid(sd_bus_creds *c, pid_t *tid) { + assert_return(c, -EINVAL); + assert_return(tid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_TID)) + return -ENODATA; + + assert(c->tid > 0); + *tid = c->tid; + return 0; +} + +_public_ int sd_bus_creds_get_selinux_context(sd_bus_creds *c, const char **ret) { + assert_return(c, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_SELINUX_CONTEXT)) + return -ENODATA; + + assert(c->label); + *ret = c->label; + return 0; +} + +_public_ int sd_bus_creds_get_comm(sd_bus_creds *c, const char **ret) { + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_COMM)) + return -ENODATA; + + assert(c->comm); + *ret = c->comm; + return 0; +} + +_public_ int sd_bus_creds_get_tid_comm(sd_bus_creds *c, const char **ret) { + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_TID_COMM)) + return -ENODATA; + + assert(c->tid_comm); + *ret = c->tid_comm; + return 0; +} + +_public_ int sd_bus_creds_get_exe(sd_bus_creds *c, const char **ret) { + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_EXE)) + return -ENODATA; + + if (!c->exe) + return -ENXIO; + + *ret = c->exe; + return 0; +} + +_public_ int sd_bus_creds_get_cgroup(sd_bus_creds *c, const char **ret) { + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_CGROUP)) + return -ENODATA; + + assert(c->cgroup); + *ret = c->cgroup; + return 0; +} + +_public_ int sd_bus_creds_get_unit(sd_bus_creds *c, const char **ret) { + int r; + + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_UNIT)) + return -ENODATA; + + assert(c->cgroup); + + if (!c->unit) { + const char *shifted; + + r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); + if (r < 0) + return r; + + r = cg_path_get_unit(shifted, (char**) &c->unit); + if (r < 0) + return r; + } + + *ret = c->unit; + return 0; +} + +_public_ int sd_bus_creds_get_user_unit(sd_bus_creds *c, const char **ret) { + int r; + + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_USER_UNIT)) + return -ENODATA; + + assert(c->cgroup); + + if (!c->user_unit) { + const char *shifted; + + r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); + if (r < 0) + return r; + + r = cg_path_get_user_unit(shifted, (char**) &c->user_unit); + if (r < 0) + return r; + } + + *ret = c->user_unit; + return 0; +} + +_public_ int sd_bus_creds_get_slice(sd_bus_creds *c, const char **ret) { + int r; + + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_SLICE)) + return -ENODATA; + + assert(c->cgroup); + + if (!c->slice) { + const char *shifted; + + r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); + if (r < 0) + return r; + + r = cg_path_get_slice(shifted, (char**) &c->slice); + if (r < 0) + return r; + } + + *ret = c->slice; + return 0; +} + +_public_ int sd_bus_creds_get_user_slice(sd_bus_creds *c, const char **ret) { + int r; + + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_USER_SLICE)) + return -ENODATA; + + assert(c->cgroup); + + if (!c->user_slice) { + const char *shifted; + + r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); + if (r < 0) + return r; + + r = cg_path_get_user_slice(shifted, (char**) &c->user_slice); + if (r < 0) + return r; + } + + *ret = c->user_slice; + return 0; +} + +_public_ int sd_bus_creds_get_session(sd_bus_creds *c, const char **ret) { + int r; + + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_SESSION)) + return -ENODATA; + + assert(c->cgroup); + + if (!c->session) { + const char *shifted; + + r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); + if (r < 0) + return r; + + r = cg_path_get_session(shifted, (char**) &c->session); + if (r < 0) + return r; + } + + *ret = c->session; + return 0; +} + +_public_ int sd_bus_creds_get_owner_uid(sd_bus_creds *c, uid_t *uid) { + const char *shifted; + int r; + + assert_return(c, -EINVAL); + assert_return(uid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_OWNER_UID)) + return -ENODATA; + + assert(c->cgroup); + + r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); + if (r < 0) + return r; + + return cg_path_get_owner_uid(shifted, uid); +} + +_public_ int sd_bus_creds_get_cmdline(sd_bus_creds *c, char ***cmdline) { + assert_return(c, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_CMDLINE)) + return -ENODATA; + + if (!c->cmdline) + return -ENXIO; + + if (!c->cmdline_array) { + c->cmdline_array = strv_parse_nulstr(c->cmdline, c->cmdline_size); + if (!c->cmdline_array) + return -ENOMEM; + } + + *cmdline = c->cmdline_array; + return 0; +} + +_public_ int sd_bus_creds_get_audit_session_id(sd_bus_creds *c, uint32_t *sessionid) { + assert_return(c, -EINVAL); + assert_return(sessionid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_AUDIT_SESSION_ID)) + return -ENODATA; + + if (c->audit_session_id == AUDIT_SESSION_INVALID) + return -ENXIO; + + *sessionid = c->audit_session_id; + return 0; +} + +_public_ int sd_bus_creds_get_audit_login_uid(sd_bus_creds *c, uid_t *uid) { + assert_return(c, -EINVAL); + assert_return(uid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_AUDIT_LOGIN_UID)) + return -ENODATA; + + if (c->audit_login_uid == UID_INVALID) + return -ENXIO; + + *uid = c->audit_login_uid; + return 0; +} + +_public_ int sd_bus_creds_get_tty(sd_bus_creds *c, const char **ret) { + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_TTY)) + return -ENODATA; + + if (!c->tty) + return -ENXIO; + + *ret = c->tty; + return 0; +} + +_public_ int sd_bus_creds_get_unique_name(sd_bus_creds *c, const char **unique_name) { + assert_return(c, -EINVAL); + assert_return(unique_name, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_UNIQUE_NAME)) + return -ENODATA; + + *unique_name = c->unique_name; + return 0; +} + +_public_ int sd_bus_creds_get_well_known_names(sd_bus_creds *c, char ***well_known_names) { + assert_return(c, -EINVAL); + assert_return(well_known_names, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_WELL_KNOWN_NAMES)) + return -ENODATA; + + /* As a special hack we return the bus driver as well-known + * names list when this is requested. */ + if (c->well_known_names_driver) { + static const char* const wkn[] = { + "org.freedesktop.DBus", + NULL + }; + + *well_known_names = (char**) wkn; + return 0; + } + + if (c->well_known_names_local) { + static const char* const wkn[] = { + "org.freedesktop.DBus.Local", + NULL + }; + + *well_known_names = (char**) wkn; + return 0; + } + + *well_known_names = c->well_known_names; + return 0; +} + +_public_ int sd_bus_creds_get_description(sd_bus_creds *c, const char **ret) { + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_DESCRIPTION)) + return -ENODATA; + + assert(c->description); + + if (!c->unescaped_description) { + c->unescaped_description = bus_label_unescape(c->description); + if (!c->unescaped_description) + return -ENOMEM; + } + + *ret = c->unescaped_description; + return 0; +} + +static int has_cap(sd_bus_creds *c, unsigned offset, int capability) { + size_t sz; + + assert(c); + assert(capability >= 0); + assert(c->capability); + + if ((unsigned) capability > cap_last_cap()) + return 0; + + sz = DIV_ROUND_UP(cap_last_cap(), 32U); + + return !!(c->capability[offset * sz + CAP_TO_INDEX(capability)] & CAP_TO_MASK(capability)); +} + +_public_ int sd_bus_creds_has_effective_cap(sd_bus_creds *c, int capability) { + assert_return(c, -EINVAL); + assert_return(capability >= 0, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_EFFECTIVE_CAPS)) + return -ENODATA; + + return has_cap(c, CAP_OFFSET_EFFECTIVE, capability); +} + +_public_ int sd_bus_creds_has_permitted_cap(sd_bus_creds *c, int capability) { + assert_return(c, -EINVAL); + assert_return(capability >= 0, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_PERMITTED_CAPS)) + return -ENODATA; + + return has_cap(c, CAP_OFFSET_PERMITTED, capability); +} + +_public_ int sd_bus_creds_has_inheritable_cap(sd_bus_creds *c, int capability) { + assert_return(c, -EINVAL); + assert_return(capability >= 0, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_INHERITABLE_CAPS)) + return -ENODATA; + + return has_cap(c, CAP_OFFSET_INHERITABLE, capability); +} + +_public_ int sd_bus_creds_has_bounding_cap(sd_bus_creds *c, int capability) { + assert_return(c, -EINVAL); + assert_return(capability >= 0, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_BOUNDING_CAPS)) + return -ENODATA; + + return has_cap(c, CAP_OFFSET_BOUNDING, capability); +} + +static int parse_caps(sd_bus_creds *c, unsigned offset, const char *p) { + size_t sz, max; + unsigned i, j; + + assert(c); + assert(p); + + max = DIV_ROUND_UP(cap_last_cap(), 32U); + p += strspn(p, WHITESPACE); + + sz = strlen(p); + if (sz % 8 != 0) + return -EINVAL; + + sz /= 8; + if (sz > max) + return -EINVAL; + + if (!c->capability) { + c->capability = new0(uint32_t, max * 4); + if (!c->capability) + return -ENOMEM; + } + + for (i = 0; i < sz; i ++) { + uint32_t v = 0; + + for (j = 0; j < 8; ++j) { + int t; + + t = unhexchar(*p++); + if (t < 0) + return -EINVAL; + + v = (v << 4) | t; + } + + c->capability[offset * max + (sz - i - 1)] = v; + } + + return 0; +} + +int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { + uint64_t missing; + int r; + + assert(c); + assert(c->allocated); + + if (!(mask & SD_BUS_CREDS_AUGMENT)) + return 0; + + /* Try to retrieve PID from creds if it wasn't passed to us */ + if (pid > 0) { + c->pid = pid; + c->mask |= SD_BUS_CREDS_PID; + } else if (c->mask & SD_BUS_CREDS_PID) + pid = c->pid; + else + /* Without pid we cannot do much... */ + return 0; + + /* Try to retrieve TID from creds if it wasn't passed to us */ + if (tid <= 0 && (c->mask & SD_BUS_CREDS_TID)) + tid = c->tid; + + /* Calculate what we shall and can add */ + missing = mask & ~(c->mask|SD_BUS_CREDS_PID|SD_BUS_CREDS_TID|SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_DESCRIPTION|SD_BUS_CREDS_AUGMENT); + if (missing == 0) + return 0; + + if (tid > 0) { + c->tid = tid; + c->mask |= SD_BUS_CREDS_TID; + } + + if (missing & (SD_BUS_CREDS_PPID | + SD_BUS_CREDS_UID | SD_BUS_CREDS_EUID | SD_BUS_CREDS_SUID | SD_BUS_CREDS_FSUID | + SD_BUS_CREDS_GID | SD_BUS_CREDS_EGID | SD_BUS_CREDS_SGID | SD_BUS_CREDS_FSGID | + SD_BUS_CREDS_SUPPLEMENTARY_GIDS | + SD_BUS_CREDS_EFFECTIVE_CAPS | SD_BUS_CREDS_INHERITABLE_CAPS | + SD_BUS_CREDS_PERMITTED_CAPS | SD_BUS_CREDS_BOUNDING_CAPS)) { + + _cleanup_fclose_ FILE *f = NULL; + const char *p; + + p = procfs_file_alloca(pid, "status"); + + f = fopen(p, "re"); + if (!f) { + if (errno == ENOENT) + return -ESRCH; + else if (errno != EPERM && errno != EACCES) + return -errno; + } else { + char line[LINE_MAX]; + + FOREACH_LINE(line, f, return -errno) { + truncate_nl(line); + + if (missing & SD_BUS_CREDS_PPID) { + p = startswith(line, "PPid:"); + if (p) { + p += strspn(p, WHITESPACE); + + /* Explicitly check for PPID 0 (which is the case for PID 1) */ + if (!streq(p, "0")) { + r = parse_pid(p, &c->ppid); + if (r < 0) + return r; + + } else + c->ppid = 0; + + c->mask |= SD_BUS_CREDS_PPID; + continue; + } + } + + if (missing & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID)) { + p = startswith(line, "Uid:"); + if (p) { + unsigned long uid, euid, suid, fsuid; + + p += strspn(p, WHITESPACE); + if (sscanf(p, "%lu %lu %lu %lu", &uid, &euid, &suid, &fsuid) != 4) + return -EIO; + + if (missing & SD_BUS_CREDS_UID) + c->uid = (uid_t) uid; + if (missing & SD_BUS_CREDS_EUID) + c->euid = (uid_t) euid; + if (missing & SD_BUS_CREDS_SUID) + c->suid = (uid_t) suid; + if (missing & SD_BUS_CREDS_FSUID) + c->fsuid = (uid_t) fsuid; + + c->mask |= missing & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID); + continue; + } + } + + if (missing & (SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID)) { + p = startswith(line, "Gid:"); + if (p) { + unsigned long gid, egid, sgid, fsgid; + + p += strspn(p, WHITESPACE); + if (sscanf(p, "%lu %lu %lu %lu", &gid, &egid, &sgid, &fsgid) != 4) + return -EIO; + + if (missing & SD_BUS_CREDS_GID) + c->gid = (gid_t) gid; + if (missing & SD_BUS_CREDS_EGID) + c->egid = (gid_t) egid; + if (missing & SD_BUS_CREDS_SGID) + c->sgid = (gid_t) sgid; + if (missing & SD_BUS_CREDS_FSGID) + c->fsgid = (gid_t) fsgid; + + c->mask |= missing & (SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID); + continue; + } + } + + if (missing & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { + p = startswith(line, "Groups:"); + if (p) { + size_t allocated = 0; + + for (;;) { + unsigned long g; + int n = 0; + + p += strspn(p, WHITESPACE); + if (*p == 0) + break; + + if (sscanf(p, "%lu%n", &g, &n) != 1) + return -EIO; + + if (!GREEDY_REALLOC(c->supplementary_gids, allocated, c->n_supplementary_gids+1)) + return -ENOMEM; + + c->supplementary_gids[c->n_supplementary_gids++] = (gid_t) g; + p += n; + } + + c->mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS; + continue; + } + } + + if (missing & SD_BUS_CREDS_EFFECTIVE_CAPS) { + p = startswith(line, "CapEff:"); + if (p) { + r = parse_caps(c, CAP_OFFSET_EFFECTIVE, p); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_EFFECTIVE_CAPS; + continue; + } + } + + if (missing & SD_BUS_CREDS_PERMITTED_CAPS) { + p = startswith(line, "CapPrm:"); + if (p) { + r = parse_caps(c, CAP_OFFSET_PERMITTED, p); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_PERMITTED_CAPS; + continue; + } + } + + if (missing & SD_BUS_CREDS_INHERITABLE_CAPS) { + p = startswith(line, "CapInh:"); + if (p) { + r = parse_caps(c, CAP_OFFSET_INHERITABLE, p); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_INHERITABLE_CAPS; + continue; + } + } + + if (missing & SD_BUS_CREDS_BOUNDING_CAPS) { + p = startswith(line, "CapBnd:"); + if (p) { + r = parse_caps(c, CAP_OFFSET_BOUNDING, p); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_BOUNDING_CAPS; + continue; + } + } + } + } + } + + if (missing & SD_BUS_CREDS_SELINUX_CONTEXT) { + const char *p; + + p = procfs_file_alloca(pid, "attr/current"); + r = read_one_line_file(p, &c->label); + if (r < 0) { + if (r != -ENOENT && r != -EINVAL && r != -EPERM && r != -EACCES) + return r; + } else + c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; + } + + if (missing & SD_BUS_CREDS_COMM) { + r = get_process_comm(pid, &c->comm); + if (r < 0) { + if (r != -EPERM && r != -EACCES) + return r; + } else + c->mask |= SD_BUS_CREDS_COMM; + } + + if (missing & SD_BUS_CREDS_EXE) { + r = get_process_exe(pid, &c->exe); + if (r == -ESRCH) { + /* Unfortunately we cannot really distinguish + * the case here where the process does not + * exist, and /proc/$PID/exe being unreadable + * because $PID is a kernel thread. Hence, + * assume it is a kernel thread, and rely on + * that this case is caught with a later + * call. */ + c->exe = NULL; + c->mask |= SD_BUS_CREDS_EXE; + } else if (r < 0) { + if (r != -EPERM && r != -EACCES) + return r; + } else + c->mask |= SD_BUS_CREDS_EXE; + } + + if (missing & SD_BUS_CREDS_CMDLINE) { + const char *p; + + p = procfs_file_alloca(pid, "cmdline"); + r = read_full_file(p, &c->cmdline, &c->cmdline_size); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) { + if (r != -EPERM && r != -EACCES) + return r; + } else { + if (c->cmdline_size == 0) + c->cmdline = mfree(c->cmdline); + + c->mask |= SD_BUS_CREDS_CMDLINE; + } + } + + if (tid > 0 && (missing & SD_BUS_CREDS_TID_COMM)) { + _cleanup_free_ char *p = NULL; + + if (asprintf(&p, "/proc/"PID_FMT"/task/"PID_FMT"/comm", pid, tid) < 0) + return -ENOMEM; + + r = read_one_line_file(p, &c->tid_comm); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) { + if (r != -EPERM && r != -EACCES) + return r; + } else + c->mask |= SD_BUS_CREDS_TID_COMM; + } + + if (missing & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID)) { + + if (!c->cgroup) { + r = cg_pid_get_path(NULL, pid, &c->cgroup); + if (r < 0) { + if (r != -EPERM && r != -EACCES) + return r; + } + } + + if (!c->cgroup_root) { + r = cg_get_root_path(&c->cgroup_root); + if (r < 0) + return r; + } + + if (c->cgroup) + c->mask |= missing & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID); + } + + if (missing & SD_BUS_CREDS_AUDIT_SESSION_ID) { + r = audit_session_from_pid(pid, &c->audit_session_id); + if (r == -ENODATA) { + /* ENODATA means: no audit session id assigned */ + c->audit_session_id = AUDIT_SESSION_INVALID; + c->mask |= SD_BUS_CREDS_AUDIT_SESSION_ID; + } else if (r < 0) { + if (r != -EOPNOTSUPP && r != -ENOENT && r != -EPERM && r != -EACCES) + return r; + } else + c->mask |= SD_BUS_CREDS_AUDIT_SESSION_ID; + } + + if (missing & SD_BUS_CREDS_AUDIT_LOGIN_UID) { + r = audit_loginuid_from_pid(pid, &c->audit_login_uid); + if (r == -ENODATA) { + /* ENODATA means: no audit login uid assigned */ + c->audit_login_uid = UID_INVALID; + c->mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID; + } else if (r < 0) { + if (r != -EOPNOTSUPP && r != -ENOENT && r != -EPERM && r != -EACCES) + return r; + } else + c->mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID; + } + + if (missing & SD_BUS_CREDS_TTY) { + r = get_ctty(pid, NULL, &c->tty); + if (r == -ENXIO) { + /* ENXIO means: process has no controlling TTY */ + c->tty = NULL; + c->mask |= SD_BUS_CREDS_TTY; + } else if (r < 0) { + if (r != -EPERM && r != -EACCES && r != -ENOENT) + return r; + } else + c->mask |= SD_BUS_CREDS_TTY; + } + + /* In case only the exe path was to be read we cannot + * distinguish the case where the exe path was unreadable + * because the process was a kernel thread, or when the + * process didn't exist at all. Hence, let's do a final check, + * to be sure. */ + if (!pid_is_alive(pid)) + return -ESRCH; + + if (tid > 0 && tid != pid && !pid_is_unwaited(tid)) + return -ESRCH; + + c->augmented = missing & c->mask; + + return 0; +} + +int bus_creds_extend_by_pid(sd_bus_creds *c, uint64_t mask, sd_bus_creds **ret) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *n = NULL; + int r; + + assert(c); + assert(ret); + + if ((mask & ~c->mask) == 0 || (!(mask & SD_BUS_CREDS_AUGMENT))) { + /* There's already all data we need, or augmentation + * wasn't turned on. */ + + *ret = sd_bus_creds_ref(c); + return 0; + } + + n = bus_creds_new(); + if (!n) + return -ENOMEM; + + /* Copy the original data over */ + + if (c->mask & mask & SD_BUS_CREDS_PID) { + n->pid = c->pid; + n->mask |= SD_BUS_CREDS_PID; + } + + if (c->mask & mask & SD_BUS_CREDS_TID) { + n->tid = c->tid; + n->mask |= SD_BUS_CREDS_TID; + } + + if (c->mask & mask & SD_BUS_CREDS_PPID) { + n->ppid = c->ppid; + n->mask |= SD_BUS_CREDS_PPID; + } + + if (c->mask & mask & SD_BUS_CREDS_UID) { + n->uid = c->uid; + n->mask |= SD_BUS_CREDS_UID; + } + + if (c->mask & mask & SD_BUS_CREDS_EUID) { + n->euid = c->euid; + n->mask |= SD_BUS_CREDS_EUID; + } + + if (c->mask & mask & SD_BUS_CREDS_SUID) { + n->suid = c->suid; + n->mask |= SD_BUS_CREDS_SUID; + } + + if (c->mask & mask & SD_BUS_CREDS_FSUID) { + n->fsuid = c->fsuid; + n->mask |= SD_BUS_CREDS_FSUID; + } + + if (c->mask & mask & SD_BUS_CREDS_GID) { + n->gid = c->gid; + n->mask |= SD_BUS_CREDS_GID; + } + + if (c->mask & mask & SD_BUS_CREDS_EGID) { + n->egid = c->egid; + n->mask |= SD_BUS_CREDS_EGID; + } + + if (c->mask & mask & SD_BUS_CREDS_SGID) { + n->sgid = c->sgid; + n->mask |= SD_BUS_CREDS_SGID; + } + + if (c->mask & mask & SD_BUS_CREDS_FSGID) { + n->fsgid = c->fsgid; + n->mask |= SD_BUS_CREDS_FSGID; + } + + if (c->mask & mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { + if (c->supplementary_gids) { + n->supplementary_gids = newdup(gid_t, c->supplementary_gids, c->n_supplementary_gids); + if (!n->supplementary_gids) + return -ENOMEM; + n->n_supplementary_gids = c->n_supplementary_gids; + } else { + n->supplementary_gids = NULL; + n->n_supplementary_gids = 0; + } + + n->mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS; + } + + if (c->mask & mask & SD_BUS_CREDS_COMM) { + assert(c->comm); + + n->comm = strdup(c->comm); + if (!n->comm) + return -ENOMEM; + + n->mask |= SD_BUS_CREDS_COMM; + } + + if (c->mask & mask & SD_BUS_CREDS_TID_COMM) { + assert(c->tid_comm); + + n->tid_comm = strdup(c->tid_comm); + if (!n->tid_comm) + return -ENOMEM; + + n->mask |= SD_BUS_CREDS_TID_COMM; + } + + if (c->mask & mask & SD_BUS_CREDS_EXE) { + if (c->exe) { + n->exe = strdup(c->exe); + if (!n->exe) + return -ENOMEM; + } else + n->exe = NULL; + + n->mask |= SD_BUS_CREDS_EXE; + } + + if (c->mask & mask & SD_BUS_CREDS_CMDLINE) { + if (c->cmdline) { + n->cmdline = memdup(c->cmdline, c->cmdline_size); + if (!n->cmdline) + return -ENOMEM; + + n->cmdline_size = c->cmdline_size; + } else { + n->cmdline = NULL; + n->cmdline_size = 0; + } + + n->mask |= SD_BUS_CREDS_CMDLINE; + } + + if (c->mask & mask & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_OWNER_UID)) { + assert(c->cgroup); + + n->cgroup = strdup(c->cgroup); + if (!n->cgroup) + return -ENOMEM; + + n->cgroup_root = strdup(c->cgroup_root); + if (!n->cgroup_root) + return -ENOMEM; + + n->mask |= mask & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_OWNER_UID); + } + + if (c->mask & mask & (SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS)) { + assert(c->capability); + + n->capability = memdup(c->capability, DIV_ROUND_UP(cap_last_cap(), 32U) * 4 * 4); + if (!n->capability) + return -ENOMEM; + + n->mask |= c->mask & mask & (SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS); + } + + if (c->mask & mask & SD_BUS_CREDS_SELINUX_CONTEXT) { + assert(c->label); + + n->label = strdup(c->label); + if (!n->label) + return -ENOMEM; + n->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; + } + + if (c->mask & mask & SD_BUS_CREDS_AUDIT_SESSION_ID) { + n->audit_session_id = c->audit_session_id; + n->mask |= SD_BUS_CREDS_AUDIT_SESSION_ID; + } + if (c->mask & mask & SD_BUS_CREDS_AUDIT_LOGIN_UID) { + n->audit_login_uid = c->audit_login_uid; + n->mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID; + } + + if (c->mask & mask & SD_BUS_CREDS_TTY) { + if (c->tty) { + n->tty = strdup(c->tty); + if (!n->tty) + return -ENOMEM; + } else + n->tty = NULL; + n->mask |= SD_BUS_CREDS_TTY; + } + + if (c->mask & mask & SD_BUS_CREDS_UNIQUE_NAME) { + assert(c->unique_name); + + n->unique_name = strdup(c->unique_name); + if (!n->unique_name) + return -ENOMEM; + n->mask |= SD_BUS_CREDS_UNIQUE_NAME; + } + + if (c->mask & mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) { + if (strv_isempty(c->well_known_names)) + n->well_known_names = NULL; + else { + n->well_known_names = strv_copy(c->well_known_names); + if (!n->well_known_names) + return -ENOMEM; + } + n->well_known_names_driver = c->well_known_names_driver; + n->well_known_names_local = c->well_known_names_local; + n->mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES; + } + + if (c->mask & mask & SD_BUS_CREDS_DESCRIPTION) { + assert(c->description); + n->description = strdup(c->description); + if (!n->description) + return -ENOMEM; + n->mask |= SD_BUS_CREDS_DESCRIPTION; + } + + n->augmented = c->augmented & n->mask; + + /* Get more data */ + + r = bus_creds_add_more(n, mask, 0, 0); + if (r < 0) + return r; + + *ret = n; + n = NULL; + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-creds.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-creds.h new file mode 100644 index 0000000000..3e2311f91d --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-creds.h @@ -0,0 +1,90 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 + +struct sd_bus_creds { + bool allocated; + unsigned n_ref; + + uint64_t mask; + uint64_t augmented; + + uid_t uid; + uid_t euid; + uid_t suid; + uid_t fsuid; + gid_t gid; + gid_t egid; + gid_t sgid; + gid_t fsgid; + + gid_t *supplementary_gids; + unsigned n_supplementary_gids; + + pid_t ppid; + pid_t pid; + pid_t tid; + + char *comm; + char *tid_comm; + char *exe; + + char *cmdline; + size_t cmdline_size; + char **cmdline_array; + + char *cgroup; + char *session; + char *unit; + char *user_unit; + char *slice; + char *user_slice; + + char *tty; + + uint32_t *capability; + + uint32_t audit_session_id; + uid_t audit_login_uid; + + char *label; + + char *unique_name; + + char **well_known_names; + bool well_known_names_driver:1; + bool well_known_names_local:1; + + char *cgroup_root; + + char *description, *unescaped_description; +}; + +sd_bus_creds* bus_creds_new(void); + +void bus_creds_done(sd_bus_creds *c); + +int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid); + +int bus_creds_extend_by_pid(sd_bus_creds *c, uint64_t mask, sd_bus_creds **ret); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-dump.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-dump.c new file mode 100644 index 0000000000..21a6b20a11 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-dump.c @@ -0,0 +1,602 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "alloc-util.h" +#include "bus-dump.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-type.h" +#include "cap-list.h" +#include "capability-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "locale-util.h" +#include "macro.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "util.h" + +static char *indent(unsigned level, unsigned flags) { + char *p; + unsigned n, i = 0; + + n = 0; + + if (flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY && level > 0) + level -= 1; + + if (flags & BUS_MESSAGE_DUMP_WITH_HEADER) + n += 2; + + p = new(char, n + level*8 + 1); + if (!p) + return NULL; + + if (flags & BUS_MESSAGE_DUMP_WITH_HEADER) { + p[i++] = ' '; + p[i++] = ' '; + } + + memset(p + i, ' ', level*8); + p[i + level*8] = 0; + + return p; +} + +int bus_message_dump(sd_bus_message *m, FILE *f, unsigned flags) { + unsigned level = 1; + int r; + + assert(m); + + if (!f) + f = stdout; + + if (flags & BUS_MESSAGE_DUMP_WITH_HEADER) { + fprintf(f, + "%s%s%s Type=%s%s%s Endian=%c Flags=%u Version=%u Priority=%"PRIi64, + m->header->type == SD_BUS_MESSAGE_METHOD_ERROR ? ansi_highlight_red() : + m->header->type == SD_BUS_MESSAGE_METHOD_RETURN ? ansi_highlight_green() : + m->header->type != SD_BUS_MESSAGE_SIGNAL ? ansi_highlight() : "", special_glyph(TRIANGULAR_BULLET), ansi_normal(), + ansi_highlight(), bus_message_type_to_string(m->header->type), ansi_normal(), + m->header->endian, + m->header->flags, + m->header->version, + m->priority); + + /* Display synthetic message serial number in a more readable + * format than (uint32_t) -1 */ + if (BUS_MESSAGE_COOKIE(m) == 0xFFFFFFFFULL) + fprintf(f, " Cookie=-1"); + else + fprintf(f, " Cookie=%" PRIu64, BUS_MESSAGE_COOKIE(m)); + + if (m->reply_cookie != 0) + fprintf(f, " ReplyCookie=%" PRIu64, m->reply_cookie); + + fputs("\n", f); + + if (m->sender) + fprintf(f, " Sender=%s%s%s", ansi_highlight(), m->sender, ansi_normal()); + if (m->destination) + fprintf(f, " Destination=%s%s%s", ansi_highlight(), m->destination, ansi_normal()); + if (m->path) + fprintf(f, " Path=%s%s%s", ansi_highlight(), m->path, ansi_normal()); + if (m->interface) + fprintf(f, " Interface=%s%s%s", ansi_highlight(), m->interface, ansi_normal()); + if (m->member) + fprintf(f, " Member=%s%s%s", ansi_highlight(), m->member, ansi_normal()); + + if (m->sender || m->destination || m->path || m->interface || m->member) + fputs("\n", f); + + if (sd_bus_error_is_set(&m->error)) + fprintf(f, + " ErrorName=%s%s%s" + " ErrorMessage=%s\"%s\"%s\n", + ansi_highlight_red(), strna(m->error.name), ansi_normal(), + ansi_highlight_red(), strna(m->error.message), ansi_normal()); + + if (m->monotonic != 0) + fprintf(f, " Monotonic="USEC_FMT, m->monotonic); + if (m->realtime != 0) + fprintf(f, " Realtime="USEC_FMT, m->realtime); + if (m->seqnum != 0) + fprintf(f, " SequenceNumber=%"PRIu64, m->seqnum); + + if (m->monotonic != 0 || m->realtime != 0 || m->seqnum != 0) + fputs("\n", f); + + bus_creds_dump(&m->creds, f, true); + } + + r = sd_bus_message_rewind(m, !(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY)); + if (r < 0) + return log_error_errno(r, "Failed to rewind: %m"); + + if (!(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY)) { + _cleanup_free_ char *prefix = NULL; + + prefix = indent(0, flags); + if (!prefix) + return log_oom(); + + fprintf(f, "%sMESSAGE \"%s\" {\n", prefix, strempty(m->root_container.signature)); + } + + for (;;) { + _cleanup_free_ char *prefix = NULL; + const char *contents = NULL; + char type; + union { + uint8_t u8; + uint16_t u16; + int16_t s16; + uint32_t u32; + int32_t s32; + uint64_t u64; + int64_t s64; + double d64; + const char *string; + int i; + } basic; + + r = sd_bus_message_peek_type(m, &type, &contents); + if (r < 0) + return log_error_errno(r, "Failed to peek type: %m"); + + if (r == 0) { + if (level <= 1) + break; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return log_error_errno(r, "Failed to exit container: %m"); + + level--; + + prefix = indent(level, flags); + if (!prefix) + return log_oom(); + + fprintf(f, "%s};\n", prefix); + continue; + } + + prefix = indent(level, flags); + if (!prefix) + return log_oom(); + + if (bus_type_is_container(type) > 0) { + r = sd_bus_message_enter_container(m, type, contents); + if (r < 0) + return log_error_errno(r, "Failed to enter container: %m"); + + if (type == SD_BUS_TYPE_ARRAY) + fprintf(f, "%sARRAY \"%s\" {\n", prefix, contents); + else if (type == SD_BUS_TYPE_VARIANT) + fprintf(f, "%sVARIANT \"%s\" {\n", prefix, contents); + else if (type == SD_BUS_TYPE_STRUCT) + fprintf(f, "%sSTRUCT \"%s\" {\n", prefix, contents); + else if (type == SD_BUS_TYPE_DICT_ENTRY) + fprintf(f, "%sDICT_ENTRY \"%s\" {\n", prefix, contents); + + level++; + + continue; + } + + r = sd_bus_message_read_basic(m, type, &basic); + if (r < 0) + return log_error_errno(r, "Failed to get basic: %m"); + + assert(r > 0); + + switch (type) { + + case SD_BUS_TYPE_BYTE: + fprintf(f, "%sBYTE %s%u%s;\n", prefix, ansi_highlight(), basic.u8, ansi_normal()); + break; + + case SD_BUS_TYPE_BOOLEAN: + fprintf(f, "%sBOOLEAN %s%s%s;\n", prefix, ansi_highlight(), true_false(basic.i), ansi_normal()); + break; + + case SD_BUS_TYPE_INT16: + fprintf(f, "%sINT16 %s%i%s;\n", prefix, ansi_highlight(), basic.s16, ansi_normal()); + break; + + case SD_BUS_TYPE_UINT16: + fprintf(f, "%sUINT16 %s%u%s;\n", prefix, ansi_highlight(), basic.u16, ansi_normal()); + break; + + case SD_BUS_TYPE_INT32: + fprintf(f, "%sINT32 %s%i%s;\n", prefix, ansi_highlight(), basic.s32, ansi_normal()); + break; + + case SD_BUS_TYPE_UINT32: + fprintf(f, "%sUINT32 %s%u%s;\n", prefix, ansi_highlight(), basic.u32, ansi_normal()); + break; + + case SD_BUS_TYPE_INT64: + fprintf(f, "%sINT64 %s%"PRIi64"%s;\n", prefix, ansi_highlight(), basic.s64, ansi_normal()); + break; + + case SD_BUS_TYPE_UINT64: + fprintf(f, "%sUINT64 %s%"PRIu64"%s;\n", prefix, ansi_highlight(), basic.u64, ansi_normal()); + break; + + case SD_BUS_TYPE_DOUBLE: + fprintf(f, "%sDOUBLE %s%g%s;\n", prefix, ansi_highlight(), basic.d64, ansi_normal()); + break; + + case SD_BUS_TYPE_STRING: + fprintf(f, "%sSTRING \"%s%s%s\";\n", prefix, ansi_highlight(), basic.string, ansi_normal()); + break; + + case SD_BUS_TYPE_OBJECT_PATH: + fprintf(f, "%sOBJECT_PATH \"%s%s%s\";\n", prefix, ansi_highlight(), basic.string, ansi_normal()); + break; + + case SD_BUS_TYPE_SIGNATURE: + fprintf(f, "%sSIGNATURE \"%s%s%s\";\n", prefix, ansi_highlight(), basic.string, ansi_normal()); + break; + + case SD_BUS_TYPE_UNIX_FD: + fprintf(f, "%sUNIX_FD %s%i%s;\n", prefix, ansi_highlight(), basic.i, ansi_normal()); + break; + + default: + assert_not_reached("Unknown basic type."); + } + } + + if (!(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY)) { + _cleanup_free_ char *prefix = NULL; + + prefix = indent(0, flags); + if (!prefix) + return log_oom(); + + fprintf(f, "%s};\n\n", prefix); + } + + return 0; +} + +static void dump_capabilities( + sd_bus_creds *c, + FILE *f, + const char *name, + bool terse, + int (*has)(sd_bus_creds *c, int capability)) { + + unsigned long i, last_cap; + unsigned n = 0; + int r; + + assert(c); + assert(f); + assert(name); + assert(has); + + i = 0; + r = has(c, i); + if (r < 0) + return; + + fprintf(f, "%s%s=%s", terse ? " " : "", name, terse ? "" : ansi_highlight()); + last_cap = cap_last_cap(); + + for (;;) { + if (r > 0) { + + if (n > 0) + fputc(' ', f); + if (n % 4 == 3) + fprintf(f, terse ? "\n " : "\n "); + + fprintf(f, "%s", strna(capability_to_name(i))); + n++; + } + + i++; + + if (i > last_cap) + break; + + r = has(c, i); + } + + fputs("\n", f); + + if (!terse) + fputs(ansi_normal(), f); +} + +int bus_creds_dump(sd_bus_creds *c, FILE *f, bool terse) { + uid_t owner, audit_loginuid; + uint32_t audit_sessionid; + char **cmdline = NULL, **well_known = NULL; + const char *prefix, *color, *suffix, *s; + int r, q, v, w, z; + + assert(c); + + if (!f) + f = stdout; + + if (terse) { + prefix = " "; + suffix = ""; + color = ""; + } else { + const char *off; + + prefix = ""; + color = ansi_highlight(); + + off = ansi_normal(); + suffix = strjoina(off, "\n"); + } + + if (c->mask & SD_BUS_CREDS_PID) + fprintf(f, "%sPID=%s"PID_FMT"%s", prefix, color, c->pid, suffix); + if (c->mask & SD_BUS_CREDS_TID) + fprintf(f, "%sTID=%s"PID_FMT"%s", prefix, color, c->tid, suffix); + if (c->mask & SD_BUS_CREDS_PPID) { + if (c->ppid == 0) + fprintf(f, "%sPPID=%sn/a%s", prefix, color, suffix); + else + fprintf(f, "%sPPID=%s"PID_FMT"%s", prefix, color, c->ppid, suffix); + } + if (c->mask & SD_BUS_CREDS_TTY) + fprintf(f, "%sTTY=%s%s%s", prefix, color, strna(c->tty), suffix); + + if (terse && ((c->mask & (SD_BUS_CREDS_PID|SD_BUS_CREDS_TID|SD_BUS_CREDS_PPID|SD_BUS_CREDS_TTY)))) + fputs("\n", f); + + if (c->mask & SD_BUS_CREDS_UID) + fprintf(f, "%sUID=%s"UID_FMT"%s", prefix, color, c->uid, suffix); + if (c->mask & SD_BUS_CREDS_EUID) + fprintf(f, "%sEUID=%s"UID_FMT"%s", prefix, color, c->euid, suffix); + if (c->mask & SD_BUS_CREDS_SUID) + fprintf(f, "%sSUID=%s"UID_FMT"%s", prefix, color, c->suid, suffix); + if (c->mask & SD_BUS_CREDS_FSUID) + fprintf(f, "%sFSUID=%s"UID_FMT"%s", prefix, color, c->fsuid, suffix); + r = sd_bus_creds_get_owner_uid(c, &owner); + if (r >= 0) + fprintf(f, "%sOwnerUID=%s"UID_FMT"%s", prefix, color, owner, suffix); + if (c->mask & SD_BUS_CREDS_GID) + fprintf(f, "%sGID=%s"GID_FMT"%s", prefix, color, c->gid, suffix); + if (c->mask & SD_BUS_CREDS_EGID) + fprintf(f, "%sEGID=%s"GID_FMT"%s", prefix, color, c->egid, suffix); + if (c->mask & SD_BUS_CREDS_SGID) + fprintf(f, "%sSGID=%s"GID_FMT"%s", prefix, color, c->sgid, suffix); + if (c->mask & SD_BUS_CREDS_FSGID) + fprintf(f, "%sFSGID=%s"GID_FMT"%s", prefix, color, c->fsgid, suffix); + + if (c->mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { + unsigned i; + + fprintf(f, "%sSupplementaryGIDs=%s", prefix, color); + for (i = 0; i < c->n_supplementary_gids; i++) + fprintf(f, "%s" GID_FMT, i > 0 ? " " : "", c->supplementary_gids[i]); + fprintf(f, "%s", suffix); + } + + if (terse && ((c->mask & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| + SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| + SD_BUS_CREDS_SUPPLEMENTARY_GIDS)) || r >= 0)) + fputs("\n", f); + + if (c->mask & SD_BUS_CREDS_COMM) + fprintf(f, "%sComm=%s%s%s", prefix, color, c->comm, suffix); + if (c->mask & SD_BUS_CREDS_TID_COMM) + fprintf(f, "%sTIDComm=%s%s%s", prefix, color, c->tid_comm, suffix); + if (c->mask & SD_BUS_CREDS_EXE) + fprintf(f, "%sExe=%s%s%s", prefix, color, strna(c->exe), suffix); + + if (terse && (c->mask & (SD_BUS_CREDS_EXE|SD_BUS_CREDS_COMM|SD_BUS_CREDS_TID_COMM))) + fputs("\n", f); + + r = sd_bus_creds_get_cmdline(c, &cmdline); + if (r >= 0) { + char **i; + + fprintf(f, "%sCommandLine=%s", prefix, color); + STRV_FOREACH(i, cmdline) { + if (i != cmdline) + fputc(' ', f); + + fputs(*i, f); + } + + fprintf(f, "%s", suffix); + } else if (r != -ENODATA) + fprintf(f, "%sCommandLine=%sn/a%s", prefix, color, suffix); + + if (c->mask & SD_BUS_CREDS_SELINUX_CONTEXT) + fprintf(f, "%sLabel=%s%s%s", prefix, color, c->label, suffix); + if (c->mask & SD_BUS_CREDS_DESCRIPTION) + fprintf(f, "%sDescription=%s%s%s", prefix, color, c->description, suffix); + + if (terse && (c->mask & (SD_BUS_CREDS_SELINUX_CONTEXT|SD_BUS_CREDS_DESCRIPTION))) + fputs("\n", f); + + if (c->mask & SD_BUS_CREDS_CGROUP) + fprintf(f, "%sCGroup=%s%s%s", prefix, color, c->cgroup, suffix); + s = NULL; + r = sd_bus_creds_get_unit(c, &s); + if (r != -ENODATA) + fprintf(f, "%sUnit=%s%s%s", prefix, color, strna(s), suffix); + s = NULL; + v = sd_bus_creds_get_slice(c, &s); + if (v != -ENODATA) + fprintf(f, "%sSlice=%s%s%s", prefix, color, strna(s), suffix); + s = NULL; + q = sd_bus_creds_get_user_unit(c, &s); + if (q != -ENODATA) + fprintf(f, "%sUserUnit=%s%s%s", prefix, color, strna(s), suffix); + s = NULL; + w = sd_bus_creds_get_user_slice(c, &s); + if (w != -ENODATA) + fprintf(f, "%sUserSlice=%s%s%s", prefix, color, strna(s), suffix); + s = NULL; + z = sd_bus_creds_get_session(c, &s); + if (z != -ENODATA) + fprintf(f, "%sSession=%s%s%s", prefix, color, strna(s), suffix); + + if (terse && ((c->mask & SD_BUS_CREDS_CGROUP) || r != -ENODATA || q != -ENODATA || v != -ENODATA || w != -ENODATA || z != -ENODATA)) + fputs("\n", f); + + r = sd_bus_creds_get_audit_login_uid(c, &audit_loginuid); + if (r >= 0) + fprintf(f, "%sAuditLoginUID=%s"UID_FMT"%s", prefix, color, audit_loginuid, suffix); + else if (r != -ENODATA) + fprintf(f, "%sAuditLoginUID=%sn/a%s", prefix, color, suffix); + q = sd_bus_creds_get_audit_session_id(c, &audit_sessionid); + if (q >= 0) + fprintf(f, "%sAuditSessionID=%s%"PRIu32"%s", prefix, color, audit_sessionid, suffix); + else if (q != -ENODATA) + fprintf(f, "%sAuditSessionID=%sn/a%s", prefix, color, suffix); + + if (terse && (r != -ENODATA || q != -ENODATA)) + fputs("\n", f); + + if (c->mask & SD_BUS_CREDS_UNIQUE_NAME) + fprintf(f, "%sUniqueName=%s%s%s", prefix, color, c->unique_name, suffix); + + if (sd_bus_creds_get_well_known_names(c, &well_known) >= 0) { + char **i; + + fprintf(f, "%sWellKnownNames=%s", prefix, color); + STRV_FOREACH(i, well_known) { + if (i != well_known) + fputc(' ', f); + + fputs(*i, f); + } + + fprintf(f, "%s", suffix); + } + + if (terse && (c->mask & SD_BUS_CREDS_UNIQUE_NAME || well_known)) + fputc('\n', f); + + dump_capabilities(c, f, "EffectiveCapabilities", terse, sd_bus_creds_has_effective_cap); + dump_capabilities(c, f, "PermittedCapabilities", terse, sd_bus_creds_has_permitted_cap); + dump_capabilities(c, f, "InheritableCapabilities", terse, sd_bus_creds_has_inheritable_cap); + dump_capabilities(c, f, "BoundingCapabilities", terse, sd_bus_creds_has_bounding_cap); + + return 0; +} + +/* + * For details about the file format, see: + * + * http://wiki.wireshark.org/Development/LibpcapFileFormat + */ + +typedef struct _packed_ pcap_hdr_s { + uint32_t magic_number; /* magic number */ + uint16_t version_major; /* major version number */ + uint16_t version_minor; /* minor version number */ + int32_t thiszone; /* GMT to local correction */ + uint32_t sigfigs; /* accuracy of timestamps */ + uint32_t snaplen; /* max length of captured packets, in octets */ + uint32_t network; /* data link type */ +} pcap_hdr_t ; + +typedef struct _packed_ pcaprec_hdr_s { + uint32_t ts_sec; /* timestamp seconds */ + uint32_t ts_usec; /* timestamp microseconds */ + uint32_t incl_len; /* number of octets of packet saved in file */ + uint32_t orig_len; /* actual length of packet */ +} pcaprec_hdr_t; + +int bus_pcap_header(size_t snaplen, FILE *f) { + + pcap_hdr_t hdr = { + .magic_number = 0xa1b2c3d4U, + .version_major = 2, + .version_minor = 4, + .thiszone = 0, /* UTC */ + .sigfigs = 0, + .network = 231, /* D-Bus */ + }; + + if (!f) + f = stdout; + + assert(snaplen > 0); + assert((size_t) (uint32_t) snaplen == snaplen); + + hdr.snaplen = (uint32_t) snaplen; + + fwrite(&hdr, 1, sizeof(hdr), f); + + return fflush_and_check(f); +} + +int bus_message_pcap_frame(sd_bus_message *m, size_t snaplen, FILE *f) { + struct bus_body_part *part; + pcaprec_hdr_t hdr = {}; + struct timeval tv; + unsigned i; + size_t w; + + if (!f) + f = stdout; + + assert(m); + assert(snaplen > 0); + assert((size_t) (uint32_t) snaplen == snaplen); + + if (m->realtime != 0) + timeval_store(&tv, m->realtime); + else + assert_se(gettimeofday(&tv, NULL) >= 0); + + hdr.ts_sec = tv.tv_sec; + hdr.ts_usec = tv.tv_usec; + hdr.orig_len = BUS_MESSAGE_SIZE(m); + hdr.incl_len = MIN(hdr.orig_len, snaplen); + + /* write the pcap header */ + fwrite(&hdr, 1, sizeof(hdr), f); + + /* write the dbus header */ + w = MIN(BUS_MESSAGE_BODY_BEGIN(m), snaplen); + fwrite(m->header, 1, w, f); + snaplen -= w; + + /* write the dbus body */ + MESSAGE_FOREACH_PART(part, i, m) { + if (snaplen <= 0) + break; + + w = MIN(part->size, snaplen); + fwrite(part->data, 1, w, f); + snaplen -= w; + } + + return fflush_and_check(f); +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-dump.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-dump.h new file mode 100644 index 0000000000..68fa043786 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-dump.h @@ -0,0 +1,37 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include + +enum { + BUS_MESSAGE_DUMP_WITH_HEADER = 1, + BUS_MESSAGE_DUMP_SUBTREE_ONLY = 2, +}; + +int bus_message_dump(sd_bus_message *m, FILE *f, unsigned flags); + +int bus_creds_dump(sd_bus_creds *c, FILE *f, bool terse); + +int bus_pcap_header(size_t snaplen, FILE *f); +int bus_message_pcap_frame(sd_bus_message *m, size_t snaplen, FILE *f); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-error.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-error.c new file mode 100644 index 0000000000..b6bb0c4a83 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-error.c @@ -0,0 +1,608 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "errno-list.h" +#include "string-util.h" +#include "util.h" + +BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_standard_errors[] = { + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Failed", EACCES), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoMemory", ENOMEM), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.ServiceUnknown", EHOSTUNREACH), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NameHasNoOwner", ENXIO), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoReply", ETIMEDOUT), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.IOError", EIO), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.BadAddress", EADDRNOTAVAIL), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NotSupported", EOPNOTSUPP), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.LimitsExceeded", ENOBUFS), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AccessDenied", EACCES), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AuthFailed", EACCES), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InteractiveAuthorizationRequired", EACCES), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoServer", EHOSTDOWN), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Timeout", ETIMEDOUT), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoNetwork", ENONET), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AddressInUse", EADDRINUSE), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Disconnected", ECONNRESET), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidArgs", EINVAL), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.FileNotFound", ENOENT), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.FileExists", EEXIST), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownMethod", EBADR), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownObject", EBADR), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownInterface", EBADR), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownProperty", EBADR), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.PropertyReadOnly", EROFS), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnixProcessIdUnknown", ESRCH), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidSignature", EINVAL), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InconsistentMessage", EBADMSG), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.TimedOut", ETIMEDOUT), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.MatchRuleInvalid", EINVAL), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidFileContent", EINVAL), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.MatchRuleNotFound", ENOENT), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown", ESRCH), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.ObjectPathInUse", EBUSY), + SD_BUS_ERROR_MAP_END +}; + +/* GCC maps this magically to the beginning and end of the BUS_ERROR_MAP section. + * Hide them; for currently unknown reasons they get exported to the shared libries + * even without being listed in the sym file. */ +extern const sd_bus_error_map __start_BUS_ERROR_MAP[] _hidden_; +extern const sd_bus_error_map __stop_BUS_ERROR_MAP[] _hidden_; + +/* Additional maps registered with sd_bus_error_add_map() are in this + * NULL terminated array */ +static const sd_bus_error_map **additional_error_maps = NULL; + +static int bus_error_name_to_errno(const char *name) { + const sd_bus_error_map **map, *m; + const char *p; + int r; + + if (!name) + return EINVAL; + + p = startswith(name, "System.Error."); + if (p) { + r = errno_from_name(p); + if (r < 0) + return EIO; + + return r; + } + + if (additional_error_maps) + for (map = additional_error_maps; *map; map++) + for (m = *map;; m++) { + /* For additional error maps the end marker is actually the end marker */ + if (m->code == BUS_ERROR_MAP_END_MARKER) + break; + + if (streq(m->name, name)) + return m->code; + } + + m = __start_BUS_ERROR_MAP; + while (m < __stop_BUS_ERROR_MAP) { + /* For magic ELF error maps, the end marker might + * appear in the middle of things, since multiple maps + * might appear in the same section. Hence, let's skip + * over it, but realign the pointer to the next 8 byte + * boundary, which is the selected alignment for the + * arrays. */ + if (m->code == BUS_ERROR_MAP_END_MARKER) { + m = ALIGN8_PTR(m+1); + continue; + } + + if (streq(m->name, name)) + return m->code; + + m++; + } + + return EIO; +} + +static sd_bus_error errno_to_bus_error_const(int error) { + + if (error < 0) + error = -error; + + switch (error) { + + case ENOMEM: + return BUS_ERROR_OOM; + + case EPERM: + case EACCES: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_ACCESS_DENIED, "Access denied"); + + case EINVAL: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid argument"); + + case ESRCH: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN, "No such process"); + + case ENOENT: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FILE_NOT_FOUND, "File not found"); + + case EEXIST: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FILE_EXISTS, "File exists"); + + case ETIMEDOUT: + case ETIME: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_TIMEOUT, "Timed out"); + + case EIO: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_IO_ERROR, "Input/output error"); + + case ENETRESET: + case ECONNABORTED: + case ECONNRESET: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_DISCONNECTED, "Disconnected"); + + case EOPNOTSUPP: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NOT_SUPPORTED, "Not supported"); + + case EADDRNOTAVAIL: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_BAD_ADDRESS, "Address not available"); + + case ENOBUFS: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_LIMITS_EXCEEDED, "Limits exceeded"); + + case EADDRINUSE: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_ADDRESS_IN_USE, "Address in use"); + + case EBADMSG: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Inconsistent message"); + } + + return SD_BUS_ERROR_NULL; +} + +static int errno_to_bus_error_name_new(int error, char **ret) { + const char *name; + char *n; + + if (error < 0) + error = -error; + + name = errno_to_name(error); + if (!name) + return 0; + + n = strappend("System.Error.", name); + if (!n) + return -ENOMEM; + + *ret = n; + return 1; +} + +bool bus_error_is_dirty(sd_bus_error *e) { + if (!e) + return false; + + return e->name || e->message || e->_need_free != 0; +} + +_public_ void sd_bus_error_free(sd_bus_error *e) { + if (!e) + return; + + if (e->_need_free > 0) { + free((void*) e->name); + free((void*) e->message); + } + + e->name = e->message = NULL; + e->_need_free = 0; +} + +_public_ int sd_bus_error_set(sd_bus_error *e, const char *name, const char *message) { + + if (!name) + return 0; + if (!e) + goto finish; + + assert_return(!bus_error_is_dirty(e), -EINVAL); + + e->name = strdup(name); + if (!e->name) { + *e = BUS_ERROR_OOM; + return -ENOMEM; + } + + if (message) + e->message = strdup(message); + + e->_need_free = 1; + +finish: + return -bus_error_name_to_errno(name); +} + +int bus_error_setfv(sd_bus_error *e, const char *name, const char *format, va_list ap) { + + if (!name) + return 0; + + if (e) { + assert_return(!bus_error_is_dirty(e), -EINVAL); + + e->name = strdup(name); + if (!e->name) { + *e = BUS_ERROR_OOM; + return -ENOMEM; + } + + /* If we hit OOM on formatting the pretty message, we ignore + * this, since we at least managed to write the error name */ + if (format) + (void) vasprintf((char**) &e->message, format, ap); + + e->_need_free = 1; + } + + return -bus_error_name_to_errno(name); +} + +_public_ int sd_bus_error_setf(sd_bus_error *e, const char *name, const char *format, ...) { + + if (format) { + int r; + va_list ap; + + va_start(ap, format); + r = bus_error_setfv(e, name, format, ap); + va_end(ap); + + return r; + } + + return sd_bus_error_set(e, name, NULL); +} + +_public_ int sd_bus_error_copy(sd_bus_error *dest, const sd_bus_error *e) { + + if (!sd_bus_error_is_set(e)) + return 0; + if (!dest) + goto finish; + + assert_return(!bus_error_is_dirty(dest), -EINVAL); + + /* + * _need_free < 0 indicates that the error is temporarily const, needs deep copying + * _need_free == 0 indicates that the error is perpetually const, needs no deep copying + * _need_free > 0 indicates that the error is fully dynamic, needs deep copying + */ + + if (e->_need_free == 0) + *dest = *e; + else { + dest->name = strdup(e->name); + if (!dest->name) { + *dest = BUS_ERROR_OOM; + return -ENOMEM; + } + + if (e->message) + dest->message = strdup(e->message); + + dest->_need_free = 1; + } + +finish: + return -bus_error_name_to_errno(e->name); +} + +_public_ int sd_bus_error_set_const(sd_bus_error *e, const char *name, const char *message) { + if (!name) + return 0; + if (!e) + goto finish; + + assert_return(!bus_error_is_dirty(e), -EINVAL); + + *e = SD_BUS_ERROR_MAKE_CONST(name, message); + +finish: + return -bus_error_name_to_errno(name); +} + +_public_ int sd_bus_error_is_set(const sd_bus_error *e) { + if (!e) + return 0; + + return !!e->name; +} + +_public_ int sd_bus_error_has_name(const sd_bus_error *e, const char *name) { + if (!e) + return 0; + + return streq_ptr(e->name, name); +} + +_public_ int sd_bus_error_get_errno(const sd_bus_error* e) { + if (!e) + return 0; + + if (!e->name) + return 0; + + return bus_error_name_to_errno(e->name); +} + +static void bus_error_strerror(sd_bus_error *e, int error) { + size_t k = 64; + char *m; + + assert(e); + + for (;;) { + char *x; + + m = new(char, k); + if (!m) + return; + + errno = 0; + x = strerror_r(error, m, k); + if (errno == ERANGE || strlen(x) >= k - 1) { + free(m); + k *= 2; + continue; + } + + if (errno) { + free(m); + return; + } + + if (x == m) { + if (e->_need_free > 0) { + /* Error is already dynamic, let's just update the message */ + free((char*) e->message); + e->message = x; + + } else { + char *t; + /* Error was const so far, let's make it dynamic, if we can */ + + t = strdup(e->name); + if (!t) { + free(m); + return; + } + + e->_need_free = 1; + e->name = t; + e->message = x; + } + } else { + free(m); + + if (e->_need_free > 0) { + char *t; + + /* Error is dynamic, let's hence make the message also dynamic */ + t = strdup(x); + if (!t) + return; + + free((char*) e->message); + e->message = t; + } else { + /* Error is const, hence we can just override */ + e->message = x; + } + } + + return; + } +} + +_public_ int sd_bus_error_set_errno(sd_bus_error *e, int error) { + + if (error < 0) + error = -error; + + if (!e) + return -error; + if (error == 0) + return -error; + + assert_return(!bus_error_is_dirty(e), -EINVAL); + + /* First, try a const translation */ + *e = errno_to_bus_error_const(error); + + if (!sd_bus_error_is_set(e)) { + int k; + + /* If that didn't work, try a dynamic one. */ + + k = errno_to_bus_error_name_new(error, (char**) &e->name); + if (k > 0) + e->_need_free = 1; + else if (k < 0) { + *e = BUS_ERROR_OOM; + return -error; + } else + *e = BUS_ERROR_FAILED; + } + + /* Now, fill in the message from strerror() if we can */ + bus_error_strerror(e, error); + return -error; +} + +_public_ int sd_bus_error_set_errnofv(sd_bus_error *e, int error, const char *format, va_list ap) { + PROTECT_ERRNO; + int r; + + if (error < 0) + error = -error; + + if (!e) + return -error; + if (error == 0) + return 0; + + assert_return(!bus_error_is_dirty(e), -EINVAL); + + /* First, try a const translation */ + *e = errno_to_bus_error_const(error); + + if (!sd_bus_error_is_set(e)) { + int k; + + /* If that didn't work, try a dynamic one */ + + k = errno_to_bus_error_name_new(error, (char**) &e->name); + if (k > 0) + e->_need_free = 1; + else if (k < 0) { + *e = BUS_ERROR_OOM; + return -ENOMEM; + } else + *e = BUS_ERROR_FAILED; + } + + if (format) { + char *m; + + /* Then, let's try to fill in the supplied message */ + + errno = error; /* Make sure that %m resolves to the specified error */ + r = vasprintf(&m, format, ap); + if (r >= 0) { + + if (e->_need_free <= 0) { + char *t; + + t = strdup(e->name); + if (t) { + e->_need_free = 1; + e->name = t; + e->message = m; + return -error; + } + + free(m); + } else { + free((char*) e->message); + e->message = m; + return -error; + } + } + } + + /* If that didn't work, use strerror() for the message */ + bus_error_strerror(e, error); + return -error; +} + +_public_ int sd_bus_error_set_errnof(sd_bus_error *e, int error, const char *format, ...) { + int r; + + if (error < 0) + error = -error; + + if (!e) + return -error; + if (error == 0) + return 0; + + assert_return(!bus_error_is_dirty(e), -EINVAL); + + if (format) { + va_list ap; + + va_start(ap, format); + r = sd_bus_error_set_errnofv(e, error, format, ap); + va_end(ap); + + return r; + } + + return sd_bus_error_set_errno(e, error); +} + +const char *bus_error_message(const sd_bus_error *e, int error) { + + if (e) { + /* Sometimes, the D-Bus server is a little bit too verbose with + * its error messages, so let's override them here */ + if (sd_bus_error_has_name(e, SD_BUS_ERROR_ACCESS_DENIED)) + return "Access denied"; + + if (e->message) + return e->message; + } + + if (error < 0) + error = -error; + + return strerror(error); +} + +static bool map_ok(const sd_bus_error_map *map) { + for (; map->code != BUS_ERROR_MAP_END_MARKER; map++) + if (!map->name || map->code <=0) + return false; + return true; +} + +_public_ int sd_bus_error_add_map(const sd_bus_error_map *map) { + const sd_bus_error_map **maps = NULL; + unsigned n = 0; + + assert_return(map, -EINVAL); + assert_return(map_ok(map), -EINVAL); + + if (additional_error_maps) + for (; additional_error_maps[n] != NULL; n++) + if (additional_error_maps[n] == map) + return 0; + + maps = realloc_multiply(additional_error_maps, sizeof(struct sd_bus_error_map*), n + 2); + if (!maps) + return -ENOMEM; + + maps[n] = map; + maps[n+1] = NULL; + + additional_error_maps = maps; + return 1; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-error.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-error.h new file mode 100644 index 0000000000..b7aedf406d --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-error.h @@ -0,0 +1,64 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include "macro.h" + +bool bus_error_is_dirty(sd_bus_error *e); + +const char *bus_error_message(const sd_bus_error *e, int error); + +int bus_error_setfv(sd_bus_error *e, const char *name, const char *format, va_list ap) _printf_(3,0); +int bus_error_set_errnofv(sd_bus_error *e, int error, const char *format, va_list ap) _printf_(3,0); + +#define BUS_ERROR_OOM SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_MEMORY, "Out of memory") +#define BUS_ERROR_FAILED SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FAILED, "Operation failed") + +/* + * There are two ways to register error maps with the error translation + * logic: by using BUS_ERROR_MAP_ELF_REGISTER, which however only + * works when linked into the same ELF module, or via + * sd_bus_error_add_map() which is the official, external API, that + * works from any module. + * + * Note that BUS_ERROR_MAP_ELF_REGISTER has to be used as decorator in + * the bus error table, and BUS_ERROR_MAP_ELF_USE has to be used at + * least once per compilation unit (i.e. per library), to ensure that + * the error map is really added to the final binary. + */ + +#define BUS_ERROR_MAP_ELF_REGISTER \ + __attribute__ ((__section__("BUS_ERROR_MAP"))) \ + __attribute__ ((__used__)) \ + __attribute__ ((aligned(8))) + +#define BUS_ERROR_MAP_ELF_USE(errors) \ + extern const sd_bus_error_map errors[]; \ + __attribute__ ((used)) static const sd_bus_error_map * const CONCATENATE(errors ## _copy_, __COUNTER__) = errors; + +/* We use something exotic as end marker, to ensure people build the + * maps using the macsd-ros. */ +#define BUS_ERROR_MAP_END_MARKER -'x' + +BUS_ERROR_MAP_ELF_USE(bus_standard_errors); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-gvariant.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-gvariant.c new file mode 100644 index 0000000000..58782767fa --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-gvariant.c @@ -0,0 +1,311 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "bus-gvariant.h" +#include "bus-signature.h" +#include "bus-type.h" + +int bus_gvariant_get_size(const char *signature) { + const char *p; + int sum = 0, r; + + /* For fixed size structs. Fails for variable size structs. */ + + p = signature; + while (*p != 0) { + size_t n; + + r = signature_element_length(p, &n); + if (r < 0) + return r; + else { + char t[n+1]; + + memcpy(t, p, n); + t[n] = 0; + + r = bus_gvariant_get_alignment(t); + if (r < 0) + return r; + + sum = ALIGN_TO(sum, r); + } + + switch (*p) { + + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_BYTE: + sum += 1; + break; + + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + sum += 2; + break; + + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_UNIX_FD: + sum += 4; + break; + + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + sum += 8; + break; + + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { + if (n == 2) { + /* unary type () has fixed size of 1 */ + r = 1; + } else { + char t[n-1]; + + memcpy(t, p + 1, n - 2); + t[n - 2] = 0; + + r = bus_gvariant_get_size(t); + if (r < 0) + return r; + } + + sum += r; + break; + } + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: + case SD_BUS_TYPE_ARRAY: + case SD_BUS_TYPE_VARIANT: + return -EINVAL; + + default: + assert_not_reached("Unknown signature type"); + } + + p += n; + } + + r = bus_gvariant_get_alignment(signature); + if (r < 0) + return r; + + return ALIGN_TO(sum, r); +} + +int bus_gvariant_get_alignment(const char *signature) { + size_t alignment = 1; + const char *p; + int r; + + p = signature; + while (*p != 0 && alignment < 8) { + size_t n; + int a; + + r = signature_element_length(p, &n); + if (r < 0) + return r; + + switch (*p) { + + case SD_BUS_TYPE_BYTE: + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: + a = 1; + break; + + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + a = 2; + break; + + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_UNIX_FD: + a = 4; + break; + + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + case SD_BUS_TYPE_VARIANT: + a = 8; + break; + + case SD_BUS_TYPE_ARRAY: { + char t[n]; + + memcpy(t, p + 1, n - 1); + t[n - 1] = 0; + + a = bus_gvariant_get_alignment(t); + break; + } + + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { + char t[n-1]; + + memcpy(t, p + 1, n - 2); + t[n - 2] = 0; + + a = bus_gvariant_get_alignment(t); + break; + } + + default: + assert_not_reached("Unknown signature type"); + } + + if (a < 0) + return a; + + assert(a > 0 && a <= 8); + if ((size_t) a > alignment) + alignment = (size_t) a; + + p += n; + } + + return alignment; +} + +int bus_gvariant_is_fixed_size(const char *signature) { + const char *p; + int r; + + assert(signature); + + p = signature; + while (*p != 0) { + size_t n; + + r = signature_element_length(p, &n); + if (r < 0) + return r; + + switch (*p) { + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: + case SD_BUS_TYPE_ARRAY: + case SD_BUS_TYPE_VARIANT: + return 0; + + case SD_BUS_TYPE_BYTE: + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_UNIX_FD: + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + break; + + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { + char t[n-1]; + + memcpy(t, p + 1, n - 2); + t[n - 2] = 0; + + r = bus_gvariant_is_fixed_size(t); + if (r <= 0) + return r; + break; + } + + default: + assert_not_reached("Unknown signature type"); + } + + p += n; + } + + return true; +} + +size_t bus_gvariant_determine_word_size(size_t sz, size_t extra) { + if (sz + extra <= 0xFF) + return 1; + else if (sz + extra*2 <= 0xFFFF) + return 2; + else if (sz + extra*4 <= 0xFFFFFFFF) + return 4; + else + return 8; +} + +size_t bus_gvariant_read_word_le(void *p, size_t sz) { + union { + uint16_t u16; + uint32_t u32; + uint64_t u64; + } x; + + assert(p); + + if (sz == 1) + return *(uint8_t*) p; + + memcpy(&x, p, sz); + + if (sz == 2) + return le16toh(x.u16); + else if (sz == 4) + return le32toh(x.u32); + else if (sz == 8) + return le64toh(x.u64); + + assert_not_reached("unknown word width"); +} + +void bus_gvariant_write_word_le(void *p, size_t sz, size_t value) { + union { + uint16_t u16; + uint32_t u32; + uint64_t u64; + } x; + + assert(p); + assert(sz == 8 || (value < (1ULL << (sz*8)))); + + if (sz == 1) { + *(uint8_t*) p = value; + return; + } else if (sz == 2) + x.u16 = htole16((uint16_t) value); + else if (sz == 4) + x.u32 = htole32((uint32_t) value); + else if (sz == 8) + x.u64 = htole64((uint64_t) value); + else + assert_not_reached("unknown word width"); + + memcpy(p, &x, sz); +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-gvariant.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-gvariant.h new file mode 100644 index 0000000000..6da637fb05 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-gvariant.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 "macro.h" + +int bus_gvariant_get_size(const char *signature) _pure_; +int bus_gvariant_get_alignment(const char *signature) _pure_; +int bus_gvariant_is_fixed_size(const char *signature) _pure_; + +size_t bus_gvariant_determine_word_size(size_t sz, size_t extra); +void bus_gvariant_write_word_le(void *p, size_t sz, size_t value); +size_t bus_gvariant_read_word_le(void *p, size_t sz); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-internal.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-internal.c new file mode 100644 index 0000000000..caca679086 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-internal.c @@ -0,0 +1,374 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "alloc-util.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "hexdecoct.h" +#include "string-util.h" + +bool object_path_is_valid(const char *p) { + const char *q; + bool slash; + + if (!p) + return false; + + if (p[0] != '/') + return false; + + if (p[1] == 0) + return true; + + for (slash = true, q = p+1; *q; q++) + if (*q == '/') { + if (slash) + return false; + + slash = true; + } else { + bool good; + + good = + (*q >= 'a' && *q <= 'z') || + (*q >= 'A' && *q <= 'Z') || + (*q >= '0' && *q <= '9') || + *q == '_'; + + if (!good) + return false; + + slash = false; + } + + if (slash) + return false; + + return true; +} + +char* object_path_startswith(const char *a, const char *b) { + const char *p; + + if (!object_path_is_valid(a) || + !object_path_is_valid(b)) + return NULL; + + if (streq(b, "/")) + return (char*) a + 1; + + p = startswith(a, b); + if (!p) + return NULL; + + if (*p == 0) + return (char*) p; + + if (*p == '/') + return (char*) p + 1; + + return NULL; +} + +bool interface_name_is_valid(const char *p) { + const char *q; + bool dot, found_dot = false; + + if (isempty(p)) + return false; + + for (dot = true, q = p; *q; q++) + if (*q == '.') { + if (dot) + return false; + + found_dot = dot = true; + } else { + bool good; + + good = + (*q >= 'a' && *q <= 'z') || + (*q >= 'A' && *q <= 'Z') || + (!dot && *q >= '0' && *q <= '9') || + *q == '_'; + + if (!good) + return false; + + dot = false; + } + + if (q - p > 255) + return false; + + if (dot) + return false; + + if (!found_dot) + return false; + + return true; +} + +bool service_name_is_valid(const char *p) { + const char *q; + bool dot, found_dot = false, unique; + + if (isempty(p)) + return false; + + unique = p[0] == ':'; + + for (dot = true, q = unique ? p+1 : p; *q; q++) + if (*q == '.') { + if (dot) + return false; + + found_dot = dot = true; + } else { + bool good; + + good = + (*q >= 'a' && *q <= 'z') || + (*q >= 'A' && *q <= 'Z') || + ((!dot || unique) && *q >= '0' && *q <= '9') || + *q == '_' || *q == '-'; + + if (!good) + return false; + + dot = false; + } + + if (q - p > 255) + return false; + + if (dot) + return false; + + if (!found_dot) + return false; + + return true; +} + +char* service_name_startswith(const char *a, const char *b) { + const char *p; + + if (!service_name_is_valid(a) || + !service_name_is_valid(b)) + return NULL; + + p = startswith(a, b); + if (!p) + return NULL; + + if (*p == 0) + return (char*) p; + + if (*p == '.') + return (char*) p + 1; + + return NULL; +} + +bool member_name_is_valid(const char *p) { + const char *q; + + if (isempty(p)) + return false; + + for (q = p; *q; q++) { + bool good; + + good = + (*q >= 'a' && *q <= 'z') || + (*q >= 'A' && *q <= 'Z') || + (*q >= '0' && *q <= '9') || + *q == '_'; + + if (!good) + return false; + } + + if (q - p > 255) + return false; + + return true; +} + +/* + * Complex pattern match + * This checks whether @a is a 'complex-prefix' of @b, or @b is a + * 'complex-prefix' of @a, based on strings that consist of labels with @c as + * spearator. This function returns true if: + * - both strings are equal + * - either is a prefix of the other and ends with @c + * The second rule makes sure that either string needs to be fully included in + * the other, and the string which is considered the prefix needs to end with a + * separator. + */ +static bool complex_pattern_check(char c, const char *a, const char *b) { + bool separator = false; + + if (!a && !b) + return true; + + if (!a || !b) + return false; + + for (;;) { + if (*a != *b) + return (separator && (*a == 0 || *b == 0)); + + if (*a == 0) + return true; + + separator = *a == c; + + a++, b++; + } +} + +bool namespace_complex_pattern(const char *pattern, const char *value) { + return complex_pattern_check('.', pattern, value); +} + +bool path_complex_pattern(const char *pattern, const char *value) { + return complex_pattern_check('/', pattern, value); +} + +/* + * Simple pattern match + * This checks whether @a is a 'simple-prefix' of @b, based on strings that + * consist of labels with @c as separator. This function returns true, if: + * - if @a and @b are equal + * - if @a is a prefix of @b, and the first following character in @b (or the + * last character in @a) is @c + * The second rule basically makes sure that if @a is a prefix of @b, then @b + * must follow with a new label separated by @c. It cannot extend the label. + */ +static bool simple_pattern_check(char c, const char *a, const char *b) { + bool separator = false; + + if (!a && !b) + return true; + + if (!a || !b) + return false; + + for (;;) { + if (*a != *b) + return *a == 0 && (*b == c || separator); + + if (*a == 0) + return true; + + separator = *a == c; + + a++, b++; + } +} + +bool namespace_simple_pattern(const char *pattern, const char *value) { + return simple_pattern_check('.', pattern, value); +} + +bool path_simple_pattern(const char *pattern, const char *value) { + return simple_pattern_check('/', pattern, value); +} + +int bus_message_type_from_string(const char *s, uint8_t *u) { + if (streq(s, "signal")) + *u = SD_BUS_MESSAGE_SIGNAL; + else if (streq(s, "method_call")) + *u = SD_BUS_MESSAGE_METHOD_CALL; + else if (streq(s, "error")) + *u = SD_BUS_MESSAGE_METHOD_ERROR; + else if (streq(s, "method_return")) + *u = SD_BUS_MESSAGE_METHOD_RETURN; + else + return -EINVAL; + + return 0; +} + +const char *bus_message_type_to_string(uint8_t u) { + if (u == SD_BUS_MESSAGE_SIGNAL) + return "signal"; + else if (u == SD_BUS_MESSAGE_METHOD_CALL) + return "method_call"; + else if (u == SD_BUS_MESSAGE_METHOD_ERROR) + return "error"; + else if (u == SD_BUS_MESSAGE_METHOD_RETURN) + return "method_return"; + else + return NULL; +} + +char *bus_address_escape(const char *v) { + const char *a; + char *r, *b; + + r = new(char, strlen(v)*3+1); + if (!r) + return NULL; + + for (a = v, b = r; *a; a++) { + + if ((*a >= '0' && *a <= '9') || + (*a >= 'a' && *a <= 'z') || + (*a >= 'A' && *a <= 'Z') || + strchr("_-/.", *a)) + *(b++) = *a; + else { + *(b++) = '%'; + *(b++) = hexchar(*a >> 4); + *(b++) = hexchar(*a & 0xF); + } + } + + *b = 0; + return r; +} + +int bus_maybe_reply_error(sd_bus_message *m, int r, sd_bus_error *error) { + assert(m); + + if (r < 0) { + if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) + sd_bus_reply_method_errno(m, r, error); + + } else if (sd_bus_error_is_set(error)) { + if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) + sd_bus_reply_method_error(m, error); + } else + return r; + + log_debug("Failed to process message [type=%s sender=%s path=%s interface=%s member=%s signature=%s]: %s", + bus_message_type_to_string(m->header->type), + strna(m->sender), + strna(m->path), + strna(m->interface), + strna(m->member), + strna(m->root_container.signature), + bus_error_message(error, r)); + + return 1; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-internal.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-internal.h new file mode 100644 index 0000000000..8c4c6fa772 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-internal.h @@ -0,0 +1,399 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include + +#include "bus-error.h" +#include "bus-kernel.h" +#include "bus-match.h" +#include "hashmap.h" +#include "kdbus.h" +#include "list.h" +#include "prioq.h" +#include "refcnt.h" +#include "socket-util.h" +#include "util.h" + +struct reply_callback { + sd_bus_message_handler_t callback; + usec_t timeout; + uint64_t cookie; + unsigned prioq_idx; +}; + +struct filter_callback { + sd_bus_message_handler_t callback; + + unsigned last_iteration; + + LIST_FIELDS(struct filter_callback, callbacks); +}; + +struct match_callback { + sd_bus_message_handler_t callback; + + uint64_t cookie; + unsigned last_iteration; + + char *match_string; + + struct bus_match_node *match_node; +}; + +struct node { + char *path; + struct node *parent; + LIST_HEAD(struct node, child); + LIST_FIELDS(struct node, siblings); + + LIST_HEAD(struct node_callback, callbacks); + LIST_HEAD(struct node_vtable, vtables); + LIST_HEAD(struct node_enumerator, enumerators); + LIST_HEAD(struct node_object_manager, object_managers); +}; + +struct node_callback { + struct node *node; + + bool is_fallback; + sd_bus_message_handler_t callback; + + unsigned last_iteration; + + LIST_FIELDS(struct node_callback, callbacks); +}; + +struct node_enumerator { + struct node *node; + + sd_bus_node_enumerator_t callback; + + unsigned last_iteration; + + LIST_FIELDS(struct node_enumerator, enumerators); +}; + +struct node_object_manager { + struct node *node; + + LIST_FIELDS(struct node_object_manager, object_managers); +}; + +struct node_vtable { + struct node *node; + + char *interface; + bool is_fallback; + const sd_bus_vtable *vtable; + sd_bus_object_find_t find; + + unsigned last_iteration; + + LIST_FIELDS(struct node_vtable, vtables); +}; + +struct vtable_member { + const char *path; + const char *interface; + const char *member; + struct node_vtable *parent; + unsigned last_iteration; + const sd_bus_vtable *vtable; +}; + +typedef enum BusSlotType { + BUS_REPLY_CALLBACK, + BUS_FILTER_CALLBACK, + BUS_MATCH_CALLBACK, + BUS_NODE_CALLBACK, + BUS_NODE_ENUMERATOR, + BUS_NODE_VTABLE, + BUS_NODE_OBJECT_MANAGER, + _BUS_SLOT_INVALID = -1, +} BusSlotType; + +struct sd_bus_slot { + unsigned n_ref; + sd_bus *bus; + void *userdata; + BusSlotType type:5; + bool floating:1; + bool match_added:1; + char *description; + + LIST_FIELDS(sd_bus_slot, slots); + + union { + struct reply_callback reply_callback; + struct filter_callback filter_callback; + struct match_callback match_callback; + struct node_callback node_callback; + struct node_enumerator node_enumerator; + struct node_object_manager node_object_manager; + struct node_vtable node_vtable; + }; +}; + +enum bus_state { + BUS_UNSET, + BUS_OPENING, + BUS_AUTHENTICATING, + BUS_HELLO, + BUS_RUNNING, + BUS_CLOSING, + BUS_CLOSED +}; + +static inline bool BUS_IS_OPEN(enum bus_state state) { + return state > BUS_UNSET && state < BUS_CLOSING; +} + +enum bus_auth { + _BUS_AUTH_INVALID, + BUS_AUTH_EXTERNAL, + BUS_AUTH_ANONYMOUS +}; + +struct sd_bus { + /* We use atomic ref counting here since sd_bus_message + objects retain references to their originating sd_bus but + we want to allow them to be processed in a different + thread. We won't provide full thread safety, but only the + bare minimum that makes it possible to use sd_bus and + sd_bus_message objects independently and on different + threads as long as each object is used only once at the + same time. */ + RefCount n_ref; + + enum bus_state state; + int input_fd, output_fd; + int message_version; + int message_endian; + + bool is_kernel:1; + bool can_fds:1; + bool bus_client:1; + bool ucred_valid:1; + bool is_server:1; + bool anonymous_auth:1; + bool prefer_readv:1; + bool prefer_writev:1; + bool match_callbacks_modified:1; + bool filter_callbacks_modified:1; + bool nodes_modified:1; + bool trusted:1; + bool fake_creds_valid:1; + bool fake_pids_valid:1; + bool manual_peer_interface:1; + bool is_system:1; + bool is_user:1; + bool allow_interactive_authorization:1; + + int use_memfd; + + void *rbuffer; + size_t rbuffer_size; + + sd_bus_message **rqueue; + unsigned rqueue_size; + size_t rqueue_allocated; + + sd_bus_message **wqueue; + unsigned wqueue_size; + size_t windex; + size_t wqueue_allocated; + + uint64_t cookie; + + char *unique_name; + uint64_t unique_id; + + struct bus_match_node match_callbacks; + Prioq *reply_callbacks_prioq; + OrderedHashmap *reply_callbacks; + LIST_HEAD(struct filter_callback, filter_callbacks); + + Hashmap *nodes; + Hashmap *vtable_methods; + Hashmap *vtable_properties; + + union sockaddr_union sockaddr; + socklen_t sockaddr_size; + + char *kernel; + char *machine; + pid_t nspid; + + sd_id128_t server_id; + + char *address; + unsigned address_index; + + int last_connect_error; + + enum bus_auth auth; + size_t auth_rbegin; + struct iovec auth_iovec[3]; + unsigned auth_index; + char *auth_buffer; + usec_t auth_timeout; + + struct ucred ucred; + char *label; + + uint64_t creds_mask; + + int *fds; + unsigned n_fds; + + char *exec_path; + char **exec_argv; + + unsigned iteration_counter; + + void *kdbus_buffer; + + /* We do locking around the memfd cache, since we want to + * allow people to process a sd_bus_message in a different + * thread then it was generated on and free it there. Since + * adding something to the memfd cache might happen when a + * message is released, we hence need to protect this bit with + * a mutex. */ + pthread_mutex_t memfd_cache_mutex; + struct memfd_cache memfd_cache[MEMFD_CACHE_MAX]; + unsigned n_memfd_cache; + + pid_t original_pid; + + uint64_t hello_flags; + uint64_t attach_flags; + + uint64_t match_cookie; + + sd_event_source *input_io_event_source; + sd_event_source *output_io_event_source; + sd_event_source *time_event_source; + sd_event_source *quit_event_source; + sd_event *event; + int event_priority; + + sd_bus_message *current_message; + sd_bus_slot *current_slot; + sd_bus_message_handler_t current_handler; + void *current_userdata; + + sd_bus **default_bus_ptr; + pid_t tid; + + struct kdbus_creds fake_creds; + struct kdbus_pids fake_pids; + char *fake_label; + + char *cgroup_root; + + char *description; + + size_t bloom_size; + unsigned bloom_n_hash; + + sd_bus_track *track_queue; + + LIST_HEAD(sd_bus_slot, slots); +}; + +#define BUS_DEFAULT_TIMEOUT ((usec_t) (25 * USEC_PER_SEC)) + +#define BUS_WQUEUE_MAX 1024 +#define BUS_RQUEUE_MAX 64*1024 + +#define BUS_MESSAGE_SIZE_MAX (64*1024*1024) +#define BUS_AUTH_SIZE_MAX (64*1024) + +#define BUS_CONTAINER_DEPTH 128 + +/* Defined by the specification as maximum size of an array in + * bytes */ +#define BUS_ARRAY_MAX_SIZE 67108864 + +#define BUS_FDS_MAX 1024 + +#define BUS_EXEC_ARGV_MAX 256 + +bool interface_name_is_valid(const char *p) _pure_; +bool service_name_is_valid(const char *p) _pure_; +char* service_name_startswith(const char *a, const char *b); +bool member_name_is_valid(const char *p) _pure_; +bool object_path_is_valid(const char *p) _pure_; +char *object_path_startswith(const char *a, const char *b) _pure_; + +bool namespace_complex_pattern(const char *pattern, const char *value) _pure_; +bool path_complex_pattern(const char *pattern, const char *value) _pure_; + +bool namespace_simple_pattern(const char *pattern, const char *value) _pure_; +bool path_simple_pattern(const char *pattern, const char *value) _pure_; + +int bus_message_type_from_string(const char *s, uint8_t *u) _pure_; +const char *bus_message_type_to_string(uint8_t u) _pure_; + +#define error_name_is_valid interface_name_is_valid + +int bus_ensure_running(sd_bus *bus); +int bus_start_running(sd_bus *bus); +int bus_next_address(sd_bus *bus); + +int bus_seal_synthetic_message(sd_bus *b, sd_bus_message *m); + +int bus_rqueue_make_room(sd_bus *bus); + +bool bus_pid_changed(sd_bus *bus); + +char *bus_address_escape(const char *v); + +#define OBJECT_PATH_FOREACH_PREFIX(prefix, path) \ + for (char *_slash = ({ strcpy((prefix), (path)); streq((prefix), "/") ? NULL : strrchr((prefix), '/'); }) ; \ + _slash && !(_slash[(_slash) == (prefix)] = 0); \ + _slash = streq((prefix), "/") ? NULL : strrchr((prefix), '/')) + +/* If we are invoking callbacks of a bus object, ensure unreffing the + * bus from the callback doesn't destroy the object we are working + * on */ +#define BUS_DONT_DESTROY(bus) \ + _cleanup_(sd_bus_unrefp) _unused_ sd_bus *_dont_destroy_##bus = sd_bus_ref(bus) + +int bus_set_address_system(sd_bus *bus); +int bus_set_address_user(sd_bus *bus); +int bus_set_address_system_remote(sd_bus *b, const char *host); +int bus_set_address_system_machine(sd_bus *b, const char *machine); + +int bus_remove_match_by_string(sd_bus *bus, const char *match, sd_bus_message_handler_t callback, void *userdata); + +int bus_get_root_path(sd_bus *bus); + +int bus_maybe_reply_error(sd_bus_message *m, int r, sd_bus_error *error); + +#define bus_assert_return(expr, r, error) \ + do { \ + if (!assert_log(expr, #expr)) \ + return sd_bus_error_set_errno(error, r); \ + } while (false) diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-introspect.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-introspect.c new file mode 100644 index 0000000000..8f93edb8da --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-introspect.c @@ -0,0 +1,212 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "bus-internal.h" +#include "bus-introspect.h" +#include "bus-protocol.h" +#include "bus-signature.h" +#include "fd-util.h" +#include "fileio.h" +#include "string-util.h" +#include "util.h" + +int introspect_begin(struct introspect *i, bool trusted) { + assert(i); + + zero(*i); + i->trusted = trusted; + + i->f = open_memstream(&i->introspection, &i->size); + if (!i->f) + return -ENOMEM; + + fputs(BUS_INTROSPECT_DOCTYPE + "\n", i->f); + + return 0; +} + +int introspect_write_default_interfaces(struct introspect *i, bool object_manager) { + assert(i); + + fputs(BUS_INTROSPECT_INTERFACE_PEER + BUS_INTROSPECT_INTERFACE_INTROSPECTABLE + BUS_INTROSPECT_INTERFACE_PROPERTIES, i->f); + + if (object_manager) + fputs(BUS_INTROSPECT_INTERFACE_OBJECT_MANAGER, i->f); + + return 0; +} + +int introspect_write_child_nodes(struct introspect *i, Set *s, const char *prefix) { + char *node; + + assert(i); + assert(prefix); + + while ((node = set_steal_first(s))) { + const char *e; + + e = object_path_startswith(node, prefix); + if (e && e[0]) + fprintf(i->f, " \n", e); + + free(node); + } + + return 0; +} + +static void introspect_write_flags(struct introspect *i, int type, int flags) { + if (flags & SD_BUS_VTABLE_DEPRECATED) + fputs(" \n", i->f); + + if (type == _SD_BUS_VTABLE_METHOD && (flags & SD_BUS_VTABLE_METHOD_NO_REPLY)) + fputs(" \n", i->f); + + if (type == _SD_BUS_VTABLE_PROPERTY || type == _SD_BUS_VTABLE_WRITABLE_PROPERTY) { + if (flags & SD_BUS_VTABLE_PROPERTY_EXPLICIT) + fputs(" \n", i->f); + + if (flags & SD_BUS_VTABLE_PROPERTY_CONST) + fputs(" \n", i->f); + else if (flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) + fputs(" \n", i->f); + else if (!(flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) + fputs(" \n", i->f); + } + + if (!i->trusted && + (type == _SD_BUS_VTABLE_METHOD || type == _SD_BUS_VTABLE_WRITABLE_PROPERTY) && + !(flags & SD_BUS_VTABLE_UNPRIVILEGED)) + fputs(" \n", i->f); +} + +static int introspect_write_arguments(struct introspect *i, const char *signature, const char *direction) { + int r; + + for (;;) { + size_t l; + + if (!*signature) + return 0; + + r = signature_element_length(signature, &l); + if (r < 0) + return r; + + fprintf(i->f, " f, " direction=\"%s\"/>\n", direction); + else + fputs("/>\n", i->f); + + signature += l; + } +} + +int introspect_write_interface(struct introspect *i, const sd_bus_vtable *v) { + assert(i); + assert(v); + + for (; v->type != _SD_BUS_VTABLE_END; v++) { + + /* Ignore methods, signals and properties that are + * marked "hidden", but do show the interface + * itself */ + + if (v->type != _SD_BUS_VTABLE_START && (v->flags & SD_BUS_VTABLE_HIDDEN)) + continue; + + switch (v->type) { + + case _SD_BUS_VTABLE_START: + if (v->flags & SD_BUS_VTABLE_DEPRECATED) + fputs(" \n", i->f); + break; + + case _SD_BUS_VTABLE_METHOD: + fprintf(i->f, " \n", v->x.method.member); + introspect_write_arguments(i, strempty(v->x.method.signature), "in"); + introspect_write_arguments(i, strempty(v->x.method.result), "out"); + introspect_write_flags(i, v->type, v->flags); + fputs(" \n", i->f); + break; + + case _SD_BUS_VTABLE_PROPERTY: + case _SD_BUS_VTABLE_WRITABLE_PROPERTY: + fprintf(i->f, " \n", + v->x.property.member, + v->x.property.signature, + v->type == _SD_BUS_VTABLE_WRITABLE_PROPERTY ? "readwrite" : "read"); + introspect_write_flags(i, v->type, v->flags); + fputs(" \n", i->f); + break; + + case _SD_BUS_VTABLE_SIGNAL: + fprintf(i->f, " \n", v->x.signal.member); + introspect_write_arguments(i, strempty(v->x.signal.signature), NULL); + introspect_write_flags(i, v->type, v->flags); + fputs(" \n", i->f); + break; + } + + } + + return 0; +} + +int introspect_finish(struct introspect *i, sd_bus *bus, sd_bus_message *m, sd_bus_message **reply) { + sd_bus_message *q; + int r; + + assert(i); + assert(m); + assert(reply); + + fputs("\n", i->f); + + r = fflush_and_check(i->f); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(m, &q); + if (r < 0) + return r; + + r = sd_bus_message_append(q, "s", i->introspection); + if (r < 0) { + sd_bus_message_unref(q); + return r; + } + + *reply = q; + return 0; +} + +void introspect_free(struct introspect *i) { + assert(i); + + safe_fclose(i->f); + + free(i->introspection); + zero(*i); +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-introspect.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-introspect.h new file mode 100644 index 0000000000..87ac03b26a --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-introspect.h @@ -0,0 +1,40 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include "set.h" + +struct introspect { + FILE *f; + char *introspection; + size_t size; + bool trusted; +}; + +int introspect_begin(struct introspect *i, bool trusted); +int introspect_write_default_interfaces(struct introspect *i, bool object_manager); +int introspect_write_child_nodes(struct introspect *i, Set *s, const char *prefix); +int introspect_write_interface(struct introspect *i, const sd_bus_vtable *v); +int introspect_finish(struct introspect *i, sd_bus *bus, sd_bus_message *m, sd_bus_message **reply); +void introspect_free(struct introspect *i); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-kernel.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-kernel.c new file mode 100644 index 0000000000..59398b841d --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-kernel.c @@ -0,0 +1,1782 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include +#endif + +#include +#include +#include +#include + +/* When we include libgen.h because we need dirname() we immediately + * undefine basename() since libgen.h defines it as a macro to the POSIX + * version which is really broken. We prefer GNU basename(). */ +#include +#undef basename + +#include "alloc-util.h" +#include "bus-bloom.h" +#include "bus-internal.h" +#include "bus-kernel.h" +#include "bus-label.h" +#include "bus-message.h" +#include "bus-util.h" +#include "capability-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "memfd-util.h" +#include "parse-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" +#include "util.h" + +#define UNIQUE_NAME_MAX (3+DECIMAL_STR_MAX(uint64_t)) + +int bus_kernel_parse_unique_name(const char *s, uint64_t *id) { + int r; + + assert(s); + assert(id); + + if (!startswith(s, ":1.")) + return 0; + + r = safe_atou64(s + 3, id); + if (r < 0) + return r; + + return 1; +} + +static void append_payload_vec(struct kdbus_item **d, const void *p, size_t sz) { + assert(d); + assert(sz > 0); + + *d = ALIGN8_PTR(*d); + + /* Note that p can be NULL, which encodes a region full of + * zeroes, which is useful to optimize certain padding + * conditions */ + + (*d)->size = offsetof(struct kdbus_item, vec) + sizeof(struct kdbus_vec); + (*d)->type = KDBUS_ITEM_PAYLOAD_VEC; + (*d)->vec.address = PTR_TO_UINT64(p); + (*d)->vec.size = sz; + + *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); +} + +static void append_payload_memfd(struct kdbus_item **d, int memfd, size_t start, size_t sz) { + assert(d); + assert(memfd >= 0); + assert(sz > 0); + + *d = ALIGN8_PTR(*d); + (*d)->size = offsetof(struct kdbus_item, memfd) + sizeof(struct kdbus_memfd); + (*d)->type = KDBUS_ITEM_PAYLOAD_MEMFD; + (*d)->memfd.fd = memfd; + (*d)->memfd.start = start; + (*d)->memfd.size = sz; + + *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); +} + +static void append_destination(struct kdbus_item **d, const char *s, size_t length) { + assert(d); + assert(s); + + *d = ALIGN8_PTR(*d); + + (*d)->size = offsetof(struct kdbus_item, str) + length + 1; + (*d)->type = KDBUS_ITEM_DST_NAME; + memcpy((*d)->str, s, length + 1); + + *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); +} + +static struct kdbus_bloom_filter *append_bloom(struct kdbus_item **d, size_t length) { + struct kdbus_item *i; + + assert(d); + + i = ALIGN8_PTR(*d); + + i->size = offsetof(struct kdbus_item, bloom_filter) + + offsetof(struct kdbus_bloom_filter, data) + + length; + i->type = KDBUS_ITEM_BLOOM_FILTER; + + *d = (struct kdbus_item *) ((uint8_t*) i + i->size); + + return &i->bloom_filter; +} + +static void append_fds(struct kdbus_item **d, const int fds[], unsigned n_fds) { + assert(d); + assert(fds); + assert(n_fds > 0); + + *d = ALIGN8_PTR(*d); + (*d)->size = offsetof(struct kdbus_item, fds) + sizeof(int) * n_fds; + (*d)->type = KDBUS_ITEM_FDS; + memcpy((*d)->fds, fds, sizeof(int) * n_fds); + + *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); +} + +static void add_bloom_arg(void *data, size_t size, unsigned n_hash, unsigned i, const char *t) { + char buf[sizeof("arg")-1 + 2 + sizeof("-slash-prefix")]; + char *e; + + assert(data); + assert(size > 0); + assert(i < 64); + assert(t); + + e = stpcpy(buf, "arg"); + if (i < 10) + *(e++) = '0' + (char) i; + else { + *(e++) = '0' + (char) (i / 10); + *(e++) = '0' + (char) (i % 10); + } + + *e = 0; + bloom_add_pair(data, size, n_hash, buf, t); + + strcpy(e, "-dot-prefix"); + bloom_add_prefixes(data, size, n_hash, buf, t, '.'); + strcpy(e, "-slash-prefix"); + bloom_add_prefixes(data, size, n_hash, buf, t, '/'); +} + +static void add_bloom_arg_has(void *data, size_t size, unsigned n_hash, unsigned i, const char *t) { + char buf[sizeof("arg")-1 + 2 + sizeof("-has")]; + char *e; + + assert(data); + assert(size > 0); + assert(i < 64); + assert(t); + + e = stpcpy(buf, "arg"); + if (i < 10) + *(e++) = '0' + (char) i; + else { + *(e++) = '0' + (char) (i / 10); + *(e++) = '0' + (char) (i % 10); + } + + strcpy(e, "-has"); + bloom_add_pair(data, size, n_hash, buf, t); +} + +static int bus_message_setup_bloom(sd_bus_message *m, struct kdbus_bloom_filter *bloom) { + void *data; + unsigned i; + int r; + + assert(m); + assert(bloom); + + data = bloom->data; + memzero(data, m->bus->bloom_size); + bloom->generation = 0; + + bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "message-type", bus_message_type_to_string(m->header->type)); + + if (m->interface) + bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "interface", m->interface); + if (m->member) + bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "member", m->member); + if (m->path) { + bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "path", m->path); + bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "path-slash-prefix", m->path); + bloom_add_prefixes(data, m->bus->bloom_size, m->bus->bloom_n_hash, "path-slash-prefix", m->path, '/'); + } + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + for (i = 0; i < 64; i++) { + const char *t, *contents; + char type; + + r = sd_bus_message_peek_type(m, &type, &contents); + if (r < 0) + return r; + + if (IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE)) { + + /* The bloom filter includes simple strings of any kind */ + r = sd_bus_message_read_basic(m, type, &t); + if (r < 0) + return r; + + add_bloom_arg(data, m->bus->bloom_size, m->bus->bloom_n_hash, i, t); + } + + if (type == SD_BUS_TYPE_ARRAY && STR_IN_SET(contents, "s", "o", "g")) { + + /* As well as array of simple strings of any kinds */ + r = sd_bus_message_enter_container(m, type, contents); + if (r < 0) + return r; + + while ((r = sd_bus_message_read_basic(m, contents[0], &t)) > 0) + add_bloom_arg_has(data, m->bus->bloom_size, m->bus->bloom_n_hash, i, t); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + } else + /* Stop adding to bloom filter as soon as we + * run into the first argument we cannot add + * to it. */ + break; + } + + return 0; +} + +static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) { + struct bus_body_part *part; + struct kdbus_item *d; + const char *destination; + bool well_known = false; + uint64_t dst_id; + size_t sz, dl; + unsigned i; + int r; + + assert(b); + assert(m); + assert(m->sealed); + + /* We put this together only once, if this message is reused + * we reuse the earlier-built version */ + if (m->kdbus) + return 0; + + destination = m->destination ?: m->destination_ptr; + + if (destination) { + r = bus_kernel_parse_unique_name(destination, &dst_id); + if (r < 0) + return r; + if (r == 0) { + well_known = true; + + /* verify_destination_id will usually be 0, which makes the kernel + * driver only look at the provided well-known name. Otherwise, + * the kernel will make sure the provided destination id matches + * the owner of the provided well-known-name, and fail if they + * differ. Currently, this is only needed for bus-proxyd. */ + dst_id = m->verify_destination_id; + } + } else + dst_id = KDBUS_DST_ID_BROADCAST; + + sz = offsetof(struct kdbus_msg, items); + + /* Add in fixed header, fields header and payload */ + sz += (1 + m->n_body_parts) * ALIGN8(offsetof(struct kdbus_item, vec) + + MAX(sizeof(struct kdbus_vec), + sizeof(struct kdbus_memfd))); + + /* Add space for bloom filter */ + sz += ALIGN8(offsetof(struct kdbus_item, bloom_filter) + + offsetof(struct kdbus_bloom_filter, data) + + m->bus->bloom_size); + + /* Add in well-known destination header */ + if (well_known) { + dl = strlen(destination); + sz += ALIGN8(offsetof(struct kdbus_item, str) + dl + 1); + } + + /* Add space for unix fds */ + if (m->n_fds > 0) + sz += ALIGN8(offsetof(struct kdbus_item, fds) + sizeof(int)*m->n_fds); + + m->kdbus = memalign(8, sz); + if (!m->kdbus) { + r = -ENOMEM; + goto fail; + } + + m->free_kdbus = true; + memzero(m->kdbus, sz); + + m->kdbus->flags = + ((m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) ? 0 : KDBUS_MSG_EXPECT_REPLY) | + ((m->header->flags & BUS_MESSAGE_NO_AUTO_START) ? KDBUS_MSG_NO_AUTO_START : 0) | + ((m->header->type == SD_BUS_MESSAGE_SIGNAL) ? KDBUS_MSG_SIGNAL : 0); + + m->kdbus->dst_id = dst_id; + m->kdbus->payload_type = KDBUS_PAYLOAD_DBUS; + m->kdbus->cookie = m->header->dbus2.cookie; + m->kdbus->priority = m->priority; + + if (m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) + m->kdbus->cookie_reply = m->reply_cookie; + else { + struct timespec now; + + assert_se(clock_gettime(CLOCK_MONOTONIC_COARSE, &now) == 0); + m->kdbus->timeout_ns = now.tv_sec * NSEC_PER_SEC + now.tv_nsec + + m->timeout * NSEC_PER_USEC; + } + + d = m->kdbus->items; + + if (well_known) + append_destination(&d, destination, dl); + + append_payload_vec(&d, m->header, BUS_MESSAGE_BODY_BEGIN(m)); + + MESSAGE_FOREACH_PART(part, i, m) { + if (part->is_zero) { + /* If this is padding then simply send a + * vector with a NULL data pointer which the + * kernel will just pass through. This is the + * most efficient way to encode zeroes */ + + append_payload_vec(&d, NULL, part->size); + continue; + } + + if (part->memfd >= 0 && part->sealed && destination) { + /* Try to send a memfd, if the part is + * sealed and this is not a broadcast. Since we can only */ + + append_payload_memfd(&d, part->memfd, part->memfd_offset, part->size); + continue; + } + + /* Otherwise, let's send a vector to the actual data. + * For that, we need to map it first. */ + r = bus_body_part_map(part); + if (r < 0) + goto fail; + + append_payload_vec(&d, part->data, part->size); + } + + if (m->header->type == SD_BUS_MESSAGE_SIGNAL) { + struct kdbus_bloom_filter *bloom; + + bloom = append_bloom(&d, m->bus->bloom_size); + r = bus_message_setup_bloom(m, bloom); + if (r < 0) + goto fail; + } + + if (m->n_fds > 0) + append_fds(&d, m->fds, m->n_fds); + + m->kdbus->size = (uint8_t*) d - (uint8_t*) m->kdbus; + assert(m->kdbus->size <= sz); + + return 0; + +fail: + m->poisoned = true; + return r; +} + +static void unset_memfds(struct sd_bus_message *m) { + struct bus_body_part *part; + unsigned i; + + assert(m); + + /* Make sure the memfds are not freed twice */ + MESSAGE_FOREACH_PART(part, i, m) + if (part->memfd >= 0) + part->memfd = -1; +} + +static void message_set_timestamp(sd_bus *bus, sd_bus_message *m, const struct kdbus_timestamp *ts) { + assert(bus); + assert(m); + + if (!ts) + return; + + if (!(bus->attach_flags & KDBUS_ATTACH_TIMESTAMP)) + return; + + m->realtime = ts->realtime_ns / NSEC_PER_USEC; + m->monotonic = ts->monotonic_ns / NSEC_PER_USEC; + m->seqnum = ts->seqnum; +} + +static int bus_kernel_make_message(sd_bus *bus, struct kdbus_msg *k) { + sd_bus_message *m = NULL; + struct kdbus_item *d; + unsigned n_fds = 0; + _cleanup_free_ int *fds = NULL; + struct bus_header *header = NULL; + void *footer = NULL; + size_t header_size = 0, footer_size = 0; + size_t n_bytes = 0, idx = 0; + const char *destination = NULL, *seclabel = NULL; + bool last_was_memfd = false; + int r; + + assert(bus); + assert(k); + assert(k->payload_type == KDBUS_PAYLOAD_DBUS); + + KDBUS_ITEM_FOREACH(d, k, items) { + size_t l; + + l = d->size - offsetof(struct kdbus_item, data); + + switch (d->type) { + + case KDBUS_ITEM_PAYLOAD_OFF: + if (!header) { + header = (struct bus_header*)((uint8_t*) k + d->vec.offset); + header_size = d->vec.size; + } + + footer = (uint8_t*) k + d->vec.offset; + footer_size = d->vec.size; + + n_bytes += d->vec.size; + last_was_memfd = false; + break; + + case KDBUS_ITEM_PAYLOAD_MEMFD: + if (!header) /* memfd cannot be first part */ + return -EBADMSG; + + n_bytes += d->memfd.size; + last_was_memfd = true; + break; + + case KDBUS_ITEM_FDS: { + int *f; + unsigned j; + + j = l / sizeof(int); + f = realloc(fds, sizeof(int) * (n_fds + j)); + if (!f) + return -ENOMEM; + + fds = f; + memcpy(fds + n_fds, d->fds, sizeof(int) * j); + n_fds += j; + break; + } + + case KDBUS_ITEM_SECLABEL: + seclabel = d->str; + break; + } + } + + if (last_was_memfd) /* memfd cannot be last part */ + return -EBADMSG; + + if (!header) + return -EBADMSG; + + if (header_size < sizeof(struct bus_header)) + return -EBADMSG; + + /* on kdbus we only speak native endian gvariant, never dbus1 + * marshalling or reverse endian */ + if (header->version != 2 || + header->endian != BUS_NATIVE_ENDIAN) + return -EPROTOTYPE; + + r = bus_message_from_header( + bus, + header, header_size, + footer, footer_size, + n_bytes, + fds, n_fds, + seclabel, 0, &m); + if (r < 0) + return r; + + /* The well-known names list is different from the other + credentials. If we asked for it, but nothing is there, this + means that the list of well-known names is simply empty, not + that we lack any data */ + + m->creds.mask |= (SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_WELL_KNOWN_NAMES) & bus->creds_mask; + + KDBUS_ITEM_FOREACH(d, k, items) { + size_t l; + + l = d->size - offsetof(struct kdbus_item, data); + + switch (d->type) { + + case KDBUS_ITEM_PAYLOAD_OFF: { + size_t begin_body; + + begin_body = BUS_MESSAGE_BODY_BEGIN(m); + + if (idx + d->vec.size > begin_body) { + struct bus_body_part *part; + + /* Contains body material */ + + part = message_append_part(m); + if (!part) { + r = -ENOMEM; + goto fail; + } + + /* A -1 offset is NUL padding. */ + part->is_zero = d->vec.offset == ~0ULL; + + if (idx >= begin_body) { + if (!part->is_zero) + part->data = (uint8_t* )k + d->vec.offset; + part->size = d->vec.size; + } else { + if (!part->is_zero) + part->data = (uint8_t*) k + d->vec.offset + (begin_body - idx); + part->size = d->vec.size - (begin_body - idx); + } + + part->sealed = true; + } + + idx += d->vec.size; + break; + } + + case KDBUS_ITEM_PAYLOAD_MEMFD: { + struct bus_body_part *part; + + if (idx < BUS_MESSAGE_BODY_BEGIN(m)) { + r = -EBADMSG; + goto fail; + } + + part = message_append_part(m); + if (!part) { + r = -ENOMEM; + goto fail; + } + + part->memfd = d->memfd.fd; + part->memfd_offset = d->memfd.start; + part->size = d->memfd.size; + part->sealed = true; + + idx += d->memfd.size; + break; + } + + case KDBUS_ITEM_PIDS: + + /* The PID/TID might be missing, when the data + * is faked by a bus proxy and it lacks that + * information about the real client (since + * SO_PEERCRED is used for that). Also kernel + * namespacing might make some of this data + * unavailable when untranslatable. */ + + if (d->pids.pid > 0) { + m->creds.pid = (pid_t) d->pids.pid; + m->creds.mask |= SD_BUS_CREDS_PID & bus->creds_mask; + } + + if (d->pids.tid > 0) { + m->creds.tid = (pid_t) d->pids.tid; + m->creds.mask |= SD_BUS_CREDS_TID & bus->creds_mask; + } + + if (d->pids.ppid > 0) { + m->creds.ppid = (pid_t) d->pids.ppid; + m->creds.mask |= SD_BUS_CREDS_PPID & bus->creds_mask; + } else if (d->pids.pid == 1) { + m->creds.ppid = 0; + m->creds.mask |= SD_BUS_CREDS_PPID & bus->creds_mask; + } + + break; + + case KDBUS_ITEM_CREDS: + + /* EUID/SUID/FSUID/EGID/SGID/FSGID might be + * missing too (see above). */ + + if ((uid_t) d->creds.uid != UID_INVALID) { + m->creds.uid = (uid_t) d->creds.uid; + m->creds.mask |= SD_BUS_CREDS_UID & bus->creds_mask; + } + + if ((uid_t) d->creds.euid != UID_INVALID) { + m->creds.euid = (uid_t) d->creds.euid; + m->creds.mask |= SD_BUS_CREDS_EUID & bus->creds_mask; + } + + if ((uid_t) d->creds.suid != UID_INVALID) { + m->creds.suid = (uid_t) d->creds.suid; + m->creds.mask |= SD_BUS_CREDS_SUID & bus->creds_mask; + } + + if ((uid_t) d->creds.fsuid != UID_INVALID) { + m->creds.fsuid = (uid_t) d->creds.fsuid; + m->creds.mask |= SD_BUS_CREDS_FSUID & bus->creds_mask; + } + + if ((gid_t) d->creds.gid != GID_INVALID) { + m->creds.gid = (gid_t) d->creds.gid; + m->creds.mask |= SD_BUS_CREDS_GID & bus->creds_mask; + } + + if ((gid_t) d->creds.egid != GID_INVALID) { + m->creds.egid = (gid_t) d->creds.egid; + m->creds.mask |= SD_BUS_CREDS_EGID & bus->creds_mask; + } + + if ((gid_t) d->creds.sgid != GID_INVALID) { + m->creds.sgid = (gid_t) d->creds.sgid; + m->creds.mask |= SD_BUS_CREDS_SGID & bus->creds_mask; + } + + if ((gid_t) d->creds.fsgid != GID_INVALID) { + m->creds.fsgid = (gid_t) d->creds.fsgid; + m->creds.mask |= SD_BUS_CREDS_FSGID & bus->creds_mask; + } + + break; + + case KDBUS_ITEM_TIMESTAMP: + message_set_timestamp(bus, m, &d->timestamp); + break; + + case KDBUS_ITEM_PID_COMM: + m->creds.comm = d->str; + m->creds.mask |= SD_BUS_CREDS_COMM & bus->creds_mask; + break; + + case KDBUS_ITEM_TID_COMM: + m->creds.tid_comm = d->str; + m->creds.mask |= SD_BUS_CREDS_TID_COMM & bus->creds_mask; + break; + + case KDBUS_ITEM_EXE: + m->creds.exe = d->str; + m->creds.mask |= SD_BUS_CREDS_EXE & bus->creds_mask; + break; + + case KDBUS_ITEM_CMDLINE: + m->creds.cmdline = d->str; + m->creds.cmdline_size = l; + m->creds.mask |= SD_BUS_CREDS_CMDLINE & bus->creds_mask; + break; + + case KDBUS_ITEM_CGROUP: + m->creds.cgroup = d->str; + m->creds.mask |= (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID) & bus->creds_mask; + + r = bus_get_root_path(bus); + if (r < 0) + goto fail; + + m->creds.cgroup_root = bus->cgroup_root; + break; + + case KDBUS_ITEM_AUDIT: + m->creds.audit_session_id = (uint32_t) d->audit.sessionid; + m->creds.mask |= SD_BUS_CREDS_AUDIT_SESSION_ID & bus->creds_mask; + + m->creds.audit_login_uid = (uid_t) d->audit.loginuid; + m->creds.mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID & bus->creds_mask; + break; + + case KDBUS_ITEM_CAPS: + if (d->caps.last_cap != cap_last_cap() || + d->size - offsetof(struct kdbus_item, caps.caps) < DIV_ROUND_UP(d->caps.last_cap, 32U) * 4 * 4) { + r = -EBADMSG; + goto fail; + } + + m->creds.capability = d->caps.caps; + m->creds.mask |= (SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS) & bus->creds_mask; + break; + + case KDBUS_ITEM_DST_NAME: + if (!service_name_is_valid(d->str)) { + r = -EBADMSG; + goto fail; + } + + destination = d->str; + break; + + case KDBUS_ITEM_OWNED_NAME: + if (!service_name_is_valid(d->name.name)) { + r = -EBADMSG; + goto fail; + } + + if (bus->creds_mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) { + char **wkn; + size_t n; + + /* We just extend the array here, but + * do not allocate the strings inside + * of it, instead we just point to our + * buffer directly. */ + n = strv_length(m->creds.well_known_names); + wkn = realloc(m->creds.well_known_names, (n + 2) * sizeof(char*)); + if (!wkn) { + r = -ENOMEM; + goto fail; + } + + wkn[n] = d->name.name; + wkn[n+1] = NULL; + m->creds.well_known_names = wkn; + + m->creds.mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES; + } + break; + + case KDBUS_ITEM_CONN_DESCRIPTION: + m->creds.description = d->str; + m->creds.mask |= SD_BUS_CREDS_DESCRIPTION & bus->creds_mask; + break; + + case KDBUS_ITEM_AUXGROUPS: + + if (bus->creds_mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { + size_t i, n; + gid_t *g; + + n = (d->size - offsetof(struct kdbus_item, data64)) / sizeof(uint64_t); + g = new(gid_t, n); + if (!g) { + r = -ENOMEM; + goto fail; + } + + for (i = 0; i < n; i++) + g[i] = d->data64[i]; + + m->creds.supplementary_gids = g; + m->creds.n_supplementary_gids = n; + m->creds.mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS; + } + + break; + + case KDBUS_ITEM_FDS: + case KDBUS_ITEM_SECLABEL: + case KDBUS_ITEM_BLOOM_FILTER: + break; + + default: + log_debug("Got unknown field from kernel %llu", d->type); + } + } + + /* If we requested the list of well-known names to be appended + * and the sender had none no item for it will be + * attached. However, this does *not* mean that the kernel + * didn't want to provide this information to us. Hence, let's + * explicitly mark this information as available if it was + * requested. */ + m->creds.mask |= bus->creds_mask & SD_BUS_CREDS_WELL_KNOWN_NAMES; + + r = bus_message_parse_fields(m); + if (r < 0) + goto fail; + + /* Refuse messages if kdbus and dbus1 cookie doesn't match up */ + if ((uint64_t) m->header->dbus2.cookie != k->cookie) { + r = -EBADMSG; + goto fail; + } + + /* Refuse messages where the reply flag doesn't match up */ + if (!(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) != !!(k->flags & KDBUS_MSG_EXPECT_REPLY)) { + r = -EBADMSG; + goto fail; + } + + /* Refuse reply messages where the reply cookie doesn't match up */ + if ((m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) && m->reply_cookie != k->cookie_reply) { + r = -EBADMSG; + goto fail; + } + + /* Refuse messages where the autostart flag doesn't match up */ + if (!(m->header->flags & BUS_MESSAGE_NO_AUTO_START) != !(k->flags & KDBUS_MSG_NO_AUTO_START)) { + r = -EBADMSG; + goto fail; + } + + /* Override information from the user header with data from the kernel */ + if (k->src_id == KDBUS_SRC_ID_KERNEL) + bus_message_set_sender_driver(bus, m); + else { + xsprintf(m->sender_buffer, ":1.%llu", + (unsigned long long)k->src_id); + m->sender = m->creds.unique_name = m->sender_buffer; + } + + if (destination) + m->destination = destination; + else if (k->dst_id == KDBUS_DST_ID_BROADCAST) + m->destination = NULL; + else if (k->dst_id == KDBUS_DST_ID_NAME) + m->destination = bus->unique_name; /* fill in unique name if the well-known name is missing */ + else { + xsprintf(m->destination_buffer, ":1.%llu", + (unsigned long long)k->dst_id); + m->destination = m->destination_buffer; + } + + /* We take possession of the kmsg struct now */ + m->kdbus = k; + m->release_kdbus = true; + m->free_fds = true; + fds = NULL; + + bus->rqueue[bus->rqueue_size++] = m; + + return 1; + +fail: + unset_memfds(m); + sd_bus_message_unref(m); + + return r; +} + +int bus_kernel_take_fd(sd_bus *b) { + struct kdbus_bloom_parameter *bloom = NULL; + struct kdbus_item *items, *item; + struct kdbus_cmd_hello *hello; + _cleanup_free_ char *g = NULL; + const char *name; + size_t l = 0, m = 0, sz; + int r; + + assert(b); + + if (b->is_server) + return -EINVAL; + + b->use_memfd = 1; + + if (b->description) { + g = bus_label_escape(b->description); + if (!g) + return -ENOMEM; + + name = g; + } else { + char pr[17] = {}; + + /* If no name is explicitly set, we'll include a hint + * indicating the library implementation, a hint which + * kind of bus this is and the thread name */ + + assert_se(prctl(PR_GET_NAME, (unsigned long) pr) >= 0); + + if (isempty(pr)) { + name = b->is_system ? "sd-system" : + b->is_user ? "sd-user" : "sd"; + } else { + _cleanup_free_ char *e = NULL; + + e = bus_label_escape(pr); + if (!e) + return -ENOMEM; + + g = strappend(b->is_system ? "sd-system-" : + b->is_user ? "sd-user-" : "sd-", + e); + if (!g) + return -ENOMEM; + + name = g; + } + + b->description = bus_label_unescape(name); + if (!b->description) + return -ENOMEM; + } + + m = strlen(name); + + sz = ALIGN8(offsetof(struct kdbus_cmd_hello, items)) + + ALIGN8(offsetof(struct kdbus_item, str) + m + 1); + + if (b->fake_creds_valid) + sz += ALIGN8(offsetof(struct kdbus_item, creds) + sizeof(struct kdbus_creds)); + + if (b->fake_pids_valid) + sz += ALIGN8(offsetof(struct kdbus_item, pids) + sizeof(struct kdbus_pids)); + + if (b->fake_label) { + l = strlen(b->fake_label); + sz += ALIGN8(offsetof(struct kdbus_item, str) + l + 1); + } + + hello = alloca0_align(sz, 8); + hello->size = sz; + hello->flags = b->hello_flags; + hello->attach_flags_send = _KDBUS_ATTACH_ANY; + hello->attach_flags_recv = b->attach_flags; + hello->pool_size = KDBUS_POOL_SIZE; + + item = hello->items; + + item->size = offsetof(struct kdbus_item, str) + m + 1; + item->type = KDBUS_ITEM_CONN_DESCRIPTION; + memcpy(item->str, name, m + 1); + item = KDBUS_ITEM_NEXT(item); + + if (b->fake_creds_valid) { + item->size = offsetof(struct kdbus_item, creds) + sizeof(struct kdbus_creds); + item->type = KDBUS_ITEM_CREDS; + item->creds = b->fake_creds; + + item = KDBUS_ITEM_NEXT(item); + } + + if (b->fake_pids_valid) { + item->size = offsetof(struct kdbus_item, pids) + sizeof(struct kdbus_pids); + item->type = KDBUS_ITEM_PIDS; + item->pids = b->fake_pids; + + item = KDBUS_ITEM_NEXT(item); + } + + if (b->fake_label) { + item->size = offsetof(struct kdbus_item, str) + l + 1; + item->type = KDBUS_ITEM_SECLABEL; + memcpy(item->str, b->fake_label, l+1); + } + + r = ioctl(b->input_fd, KDBUS_CMD_HELLO, hello); + if (r < 0) { + if (errno == ENOTTY) + /* If the ioctl is not supported we assume that the + * API version changed in a major incompatible way, + * let's indicate an API incompatibility in this + * case. */ + return -ESOCKTNOSUPPORT; + + return -errno; + } + + if (!b->kdbus_buffer) { + b->kdbus_buffer = mmap(NULL, KDBUS_POOL_SIZE, PROT_READ, MAP_SHARED, b->input_fd, 0); + if (b->kdbus_buffer == MAP_FAILED) { + b->kdbus_buffer = NULL; + r = -errno; + goto fail; + } + } + + /* The higher 32bit of the bus_flags fields are considered + * 'incompatible flags'. Refuse them all for now. */ + if (hello->bus_flags > 0xFFFFFFFFULL) { + r = -ESOCKTNOSUPPORT; + goto fail; + } + + /* extract bloom parameters from items */ + items = (void*)((uint8_t*)b->kdbus_buffer + hello->offset); + KDBUS_FOREACH(item, items, hello->items_size) { + switch (item->type) { + case KDBUS_ITEM_BLOOM_PARAMETER: + bloom = &item->bloom_parameter; + break; + } + } + + if (!bloom || !bloom_validate_parameters((size_t) bloom->size, (unsigned) bloom->n_hash)) { + r = -EOPNOTSUPP; + goto fail; + } + + b->bloom_size = (size_t) bloom->size; + b->bloom_n_hash = (unsigned) bloom->n_hash; + + if (asprintf(&b->unique_name, ":1.%llu", (unsigned long long) hello->id) < 0) { + r = -ENOMEM; + goto fail; + } + + b->unique_id = hello->id; + + b->is_kernel = true; + b->bus_client = true; + b->can_fds = !!(hello->flags & KDBUS_HELLO_ACCEPT_FD); + b->message_version = 2; + b->message_endian = BUS_NATIVE_ENDIAN; + + /* the kernel told us the UUID of the underlying bus */ + memcpy(b->server_id.bytes, hello->id128, sizeof(b->server_id.bytes)); + + /* free returned items */ + (void) bus_kernel_cmd_free(b, hello->offset); + return bus_start_running(b); + +fail: + (void) bus_kernel_cmd_free(b, hello->offset); + return r; +} + +int bus_kernel_connect(sd_bus *b) { + assert(b); + assert(b->input_fd < 0); + assert(b->output_fd < 0); + assert(b->kernel); + + if (b->is_server) + return -EINVAL; + + b->input_fd = open(b->kernel, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (b->input_fd < 0) + return -errno; + + b->output_fd = b->input_fd; + + return bus_kernel_take_fd(b); +} + +int bus_kernel_cmd_free(sd_bus *bus, uint64_t offset) { + struct kdbus_cmd_free cmd = { + .size = sizeof(cmd), + .offset = offset, + }; + int r; + + assert(bus); + assert(bus->is_kernel); + + r = ioctl(bus->input_fd, KDBUS_CMD_FREE, &cmd); + if (r < 0) + return -errno; + + return 0; +} + +static void close_kdbus_msg(sd_bus *bus, struct kdbus_msg *k) { + struct kdbus_item *d; + + assert(bus); + assert(k); + + KDBUS_ITEM_FOREACH(d, k, items) { + if (d->type == KDBUS_ITEM_FDS) + close_many(d->fds, (d->size - offsetof(struct kdbus_item, fds)) / sizeof(int)); + else if (d->type == KDBUS_ITEM_PAYLOAD_MEMFD) + safe_close(d->memfd.fd); + } + + bus_kernel_cmd_free(bus, (uint8_t*) k - (uint8_t*) bus->kdbus_buffer); +} + +int bus_kernel_write_message(sd_bus *bus, sd_bus_message *m, bool hint_sync_call) { + struct kdbus_cmd_send cmd = { }; + int r; + + assert(bus); + assert(m); + assert(bus->state == BUS_RUNNING); + + /* If we can't deliver, we want room for the error message */ + r = bus_rqueue_make_room(bus); + if (r < 0) + return r; + + r = bus_message_setup_kmsg(bus, m); + if (r < 0) + return r; + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)m->kdbus; + + /* If this is a synchronous method call, then let's tell the + * kernel, so that it can pass CPU time/scheduling to the + * destination for the time, if it wants to. If we + * synchronously wait for the result anyway, we won't need CPU + * anyway. */ + if (hint_sync_call) { + m->kdbus->flags |= KDBUS_MSG_EXPECT_REPLY; + cmd.flags |= KDBUS_SEND_SYNC_REPLY; + } + + r = ioctl(bus->output_fd, KDBUS_CMD_SEND, &cmd); + if (r < 0) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *reply; + + if (errno == EAGAIN || errno == EINTR) + return 0; + else if (errno == ENXIO || errno == ESRCH) { + + /* ENXIO: unique name not known + * ESRCH: well-known name not known */ + + if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) + sd_bus_error_setf(&error, SD_BUS_ERROR_SERVICE_UNKNOWN, "Destination %s not known", m->destination); + else { + log_debug("Could not deliver message to %s as destination is not known. Ignoring.", m->destination); + return 0; + } + + } else if (errno == EADDRNOTAVAIL) { + + /* EADDRNOTAVAIL: activation is possible, but turned off in request flags */ + + if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) + sd_bus_error_setf(&error, SD_BUS_ERROR_SERVICE_UNKNOWN, "Activation of %s not requested", m->destination); + else { + log_debug("Could not deliver message to %s as destination is not activated. Ignoring.", m->destination); + return 0; + } + } else + return -errno; + + r = bus_message_new_synthetic_error( + bus, + BUS_MESSAGE_COOKIE(m), + &error, + &reply); + + if (r < 0) + return r; + + r = bus_seal_synthetic_message(bus, reply); + if (r < 0) + return r; + + bus->rqueue[bus->rqueue_size++] = reply; + + } else if (hint_sync_call) { + struct kdbus_msg *k; + + k = (struct kdbus_msg *)((uint8_t *)bus->kdbus_buffer + cmd.reply.offset); + assert(k); + + if (k->payload_type == KDBUS_PAYLOAD_DBUS) { + + r = bus_kernel_make_message(bus, k); + if (r < 0) { + close_kdbus_msg(bus, k); + + /* Anybody can send us invalid messages, let's just drop them. */ + if (r == -EBADMSG || r == -EPROTOTYPE) + log_debug_errno(r, "Ignoring invalid synchronous reply: %m"); + else + return r; + } + } else { + log_debug("Ignoring message with unknown payload type %llu.", (unsigned long long) k->payload_type); + close_kdbus_msg(bus, k); + } + } + + return 1; +} + +static int push_name_owner_changed( + sd_bus *bus, + const char *name, + const char *old_owner, + const char *new_owner, + const struct kdbus_timestamp *ts) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert(bus); + + r = sd_bus_message_new_signal( + bus, + &m, + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "NameOwnerChanged"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "sss", name, old_owner, new_owner); + if (r < 0) + return r; + + bus_message_set_sender_driver(bus, m); + message_set_timestamp(bus, m, ts); + + r = bus_seal_synthetic_message(bus, m); + if (r < 0) + return r; + + bus->rqueue[bus->rqueue_size++] = m; + m = NULL; + + return 1; +} + +static int translate_name_change( + sd_bus *bus, + const struct kdbus_msg *k, + const struct kdbus_item *d, + const struct kdbus_timestamp *ts) { + + char new_owner[UNIQUE_NAME_MAX], old_owner[UNIQUE_NAME_MAX]; + + assert(bus); + assert(k); + assert(d); + + if (d->type == KDBUS_ITEM_NAME_ADD || (d->name_change.old_id.flags & (KDBUS_NAME_IN_QUEUE|KDBUS_NAME_ACTIVATOR))) + old_owner[0] = 0; + else + sprintf(old_owner, ":1.%llu", (unsigned long long) d->name_change.old_id.id); + + if (d->type == KDBUS_ITEM_NAME_REMOVE || (d->name_change.new_id.flags & (KDBUS_NAME_IN_QUEUE|KDBUS_NAME_ACTIVATOR))) { + + if (isempty(old_owner)) + return 0; + + new_owner[0] = 0; + } else + sprintf(new_owner, ":1.%llu", (unsigned long long) d->name_change.new_id.id); + + return push_name_owner_changed(bus, d->name_change.name, old_owner, new_owner, ts); +} + +static int translate_id_change( + sd_bus *bus, + const struct kdbus_msg *k, + const struct kdbus_item *d, + const struct kdbus_timestamp *ts) { + + char owner[UNIQUE_NAME_MAX]; + + assert(bus); + assert(k); + assert(d); + + sprintf(owner, ":1.%llu", d->id_change.id); + + return push_name_owner_changed( + bus, owner, + d->type == KDBUS_ITEM_ID_ADD ? NULL : owner, + d->type == KDBUS_ITEM_ID_ADD ? owner : NULL, + ts); +} + +static int translate_reply( + sd_bus *bus, + const struct kdbus_msg *k, + const struct kdbus_item *d, + const struct kdbus_timestamp *ts) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert(bus); + assert(k); + assert(d); + + r = bus_message_new_synthetic_error( + bus, + k->cookie_reply, + d->type == KDBUS_ITEM_REPLY_TIMEOUT ? + &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Method call timed out") : + &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Method call peer died"), + &m); + if (r < 0) + return r; + + message_set_timestamp(bus, m, ts); + + r = bus_seal_synthetic_message(bus, m); + if (r < 0) + return r; + + bus->rqueue[bus->rqueue_size++] = m; + m = NULL; + + return 1; +} + +static int bus_kernel_translate_message(sd_bus *bus, struct kdbus_msg *k) { + static int (* const translate[])(sd_bus *bus, const struct kdbus_msg *k, const struct kdbus_item *d, const struct kdbus_timestamp *ts) = { + [KDBUS_ITEM_NAME_ADD - _KDBUS_ITEM_KERNEL_BASE] = translate_name_change, + [KDBUS_ITEM_NAME_REMOVE - _KDBUS_ITEM_KERNEL_BASE] = translate_name_change, + [KDBUS_ITEM_NAME_CHANGE - _KDBUS_ITEM_KERNEL_BASE] = translate_name_change, + + [KDBUS_ITEM_ID_ADD - _KDBUS_ITEM_KERNEL_BASE] = translate_id_change, + [KDBUS_ITEM_ID_REMOVE - _KDBUS_ITEM_KERNEL_BASE] = translate_id_change, + + [KDBUS_ITEM_REPLY_TIMEOUT - _KDBUS_ITEM_KERNEL_BASE] = translate_reply, + [KDBUS_ITEM_REPLY_DEAD - _KDBUS_ITEM_KERNEL_BASE] = translate_reply, + }; + + struct kdbus_item *d, *found = NULL; + struct kdbus_timestamp *ts = NULL; + + assert(bus); + assert(k); + assert(k->payload_type == KDBUS_PAYLOAD_KERNEL); + + KDBUS_ITEM_FOREACH(d, k, items) { + if (d->type == KDBUS_ITEM_TIMESTAMP) + ts = &d->timestamp; + else if (d->type >= _KDBUS_ITEM_KERNEL_BASE && d->type < _KDBUS_ITEM_KERNEL_BASE + ELEMENTSOF(translate)) { + if (found) + return -EBADMSG; + found = d; + } else + log_debug("Got unknown field from kernel %llu", d->type); + } + + if (!found) { + log_debug("Didn't find a kernel message to translate."); + return 0; + } + + return translate[found->type - _KDBUS_ITEM_KERNEL_BASE](bus, k, found, ts); +} + +int bus_kernel_read_message(sd_bus *bus, bool hint_priority, int64_t priority) { + struct kdbus_cmd_recv recv = { .size = sizeof(recv) }; + struct kdbus_msg *k; + int r; + + assert(bus); + + r = bus_rqueue_make_room(bus); + if (r < 0) + return r; + + if (hint_priority) { + recv.flags |= KDBUS_RECV_USE_PRIORITY; + recv.priority = priority; + } + + r = ioctl(bus->input_fd, KDBUS_CMD_RECV, &recv); + if (recv.return_flags & KDBUS_RECV_RETURN_DROPPED_MSGS) + log_debug("%s: kdbus reports %" PRIu64 " dropped broadcast messages, ignoring.", strna(bus->description), (uint64_t) recv.dropped_msgs); + if (r < 0) { + if (errno == EAGAIN) + return 0; + + return -errno; + } + + k = (struct kdbus_msg *)((uint8_t *)bus->kdbus_buffer + recv.msg.offset); + if (k->payload_type == KDBUS_PAYLOAD_DBUS) { + r = bus_kernel_make_message(bus, k); + + /* Anybody can send us invalid messages, let's just drop them. */ + if (r == -EBADMSG || r == -EPROTOTYPE) { + log_debug_errno(r, "Ignoring invalid message: %m"); + r = 0; + } + + if (r <= 0) + close_kdbus_msg(bus, k); + } else if (k->payload_type == KDBUS_PAYLOAD_KERNEL) { + r = bus_kernel_translate_message(bus, k); + close_kdbus_msg(bus, k); + } else { + log_debug("Ignoring message with unknown payload type %llu.", (unsigned long long) k->payload_type); + r = 0; + close_kdbus_msg(bus, k); + } + + return r < 0 ? r : 1; +} + +int bus_kernel_pop_memfd(sd_bus *bus, void **address, size_t *mapped, size_t *allocated) { + struct memfd_cache *c; + int fd; + + assert(address); + assert(mapped); + assert(allocated); + + if (!bus || !bus->is_kernel) + return -EOPNOTSUPP; + + assert_se(pthread_mutex_lock(&bus->memfd_cache_mutex) == 0); + + if (bus->n_memfd_cache <= 0) { + int r; + + assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) == 0); + + r = memfd_new(bus->description); + if (r < 0) + return r; + + *address = NULL; + *mapped = 0; + *allocated = 0; + return r; + } + + c = &bus->memfd_cache[--bus->n_memfd_cache]; + + assert(c->fd >= 0); + assert(c->mapped == 0 || c->address); + + *address = c->address; + *mapped = c->mapped; + *allocated = c->allocated; + fd = c->fd; + + assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) == 0); + + return fd; +} + +static void close_and_munmap(int fd, void *address, size_t size) { + if (size > 0) + assert_se(munmap(address, PAGE_ALIGN(size)) >= 0); + + safe_close(fd); +} + +void bus_kernel_push_memfd(sd_bus *bus, int fd, void *address, size_t mapped, size_t allocated) { + struct memfd_cache *c; + uint64_t max_mapped = PAGE_ALIGN(MEMFD_CACHE_ITEM_SIZE_MAX); + + assert(fd >= 0); + assert(mapped == 0 || address); + + if (!bus || !bus->is_kernel) { + close_and_munmap(fd, address, mapped); + return; + } + + assert_se(pthread_mutex_lock(&bus->memfd_cache_mutex) == 0); + + if (bus->n_memfd_cache >= ELEMENTSOF(bus->memfd_cache)) { + assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) == 0); + + close_and_munmap(fd, address, mapped); + return; + } + + c = &bus->memfd_cache[bus->n_memfd_cache++]; + c->fd = fd; + c->address = address; + + /* If overly long, let's return a bit to the OS */ + if (mapped > max_mapped) { + assert_se(memfd_set_size(fd, max_mapped) >= 0); + assert_se(munmap((uint8_t*) address + max_mapped, PAGE_ALIGN(mapped - max_mapped)) >= 0); + c->mapped = c->allocated = max_mapped; + } else { + c->mapped = mapped; + c->allocated = allocated; + } + + assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) == 0); +} + +void bus_kernel_flush_memfd(sd_bus *b) { + unsigned i; + + assert(b); + + for (i = 0; i < b->n_memfd_cache; i++) + close_and_munmap(b->memfd_cache[i].fd, b->memfd_cache[i].address, b->memfd_cache[i].mapped); +} + +uint64_t request_name_flags_to_kdbus(uint64_t flags) { + uint64_t f = 0; + + if (flags & SD_BUS_NAME_ALLOW_REPLACEMENT) + f |= KDBUS_NAME_ALLOW_REPLACEMENT; + + if (flags & SD_BUS_NAME_REPLACE_EXISTING) + f |= KDBUS_NAME_REPLACE_EXISTING; + + if (flags & SD_BUS_NAME_QUEUE) + f |= KDBUS_NAME_QUEUE; + + return f; +} + +uint64_t attach_flags_to_kdbus(uint64_t mask) { + uint64_t m = 0; + + if (mask & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| + SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID)) + m |= KDBUS_ATTACH_CREDS; + + if (mask & (SD_BUS_CREDS_PID|SD_BUS_CREDS_TID|SD_BUS_CREDS_PPID)) + m |= KDBUS_ATTACH_PIDS; + + if (mask & SD_BUS_CREDS_COMM) + m |= KDBUS_ATTACH_PID_COMM; + + if (mask & SD_BUS_CREDS_TID_COMM) + m |= KDBUS_ATTACH_TID_COMM; + + if (mask & SD_BUS_CREDS_EXE) + m |= KDBUS_ATTACH_EXE; + + if (mask & SD_BUS_CREDS_CMDLINE) + m |= KDBUS_ATTACH_CMDLINE; + + if (mask & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID)) + m |= KDBUS_ATTACH_CGROUP; + + if (mask & (SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS)) + m |= KDBUS_ATTACH_CAPS; + + if (mask & SD_BUS_CREDS_SELINUX_CONTEXT) + m |= KDBUS_ATTACH_SECLABEL; + + if (mask & (SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID)) + m |= KDBUS_ATTACH_AUDIT; + + if (mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) + m |= KDBUS_ATTACH_NAMES; + + if (mask & SD_BUS_CREDS_DESCRIPTION) + m |= KDBUS_ATTACH_CONN_DESCRIPTION; + + if (mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) + m |= KDBUS_ATTACH_AUXGROUPS; + + return m; +} + +int bus_kernel_create_bus(const char *name, bool world, char **s) { + struct kdbus_cmd *make; + struct kdbus_item *n; + size_t l; + int fd; + + assert(name); + assert(s); + + fd = open("/sys/fs/kdbus/control", O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return -errno; + + l = strlen(name); + make = alloca0_align(offsetof(struct kdbus_cmd, items) + + ALIGN8(offsetof(struct kdbus_item, bloom_parameter) + sizeof(struct kdbus_bloom_parameter)) + + ALIGN8(offsetof(struct kdbus_item, data64) + sizeof(uint64_t)) + + ALIGN8(offsetof(struct kdbus_item, str) + DECIMAL_STR_MAX(uid_t) + 1 + l + 1), + 8); + + make->size = offsetof(struct kdbus_cmd, items); + + /* Set the bloom parameters */ + n = make->items; + n->size = offsetof(struct kdbus_item, bloom_parameter) + + sizeof(struct kdbus_bloom_parameter); + n->type = KDBUS_ITEM_BLOOM_PARAMETER; + n->bloom_parameter.size = DEFAULT_BLOOM_SIZE; + n->bloom_parameter.n_hash = DEFAULT_BLOOM_N_HASH; + + assert_cc(DEFAULT_BLOOM_SIZE > 0); + assert_cc(DEFAULT_BLOOM_N_HASH > 0); + + make->size += ALIGN8(n->size); + + /* Provide all metadata via bus-owner queries */ + n = KDBUS_ITEM_NEXT(n); + n->type = KDBUS_ITEM_ATTACH_FLAGS_SEND; + n->size = offsetof(struct kdbus_item, data64) + sizeof(uint64_t); + n->data64[0] = _KDBUS_ATTACH_ANY; + make->size += ALIGN8(n->size); + + /* Set the a good name */ + n = KDBUS_ITEM_NEXT(n); + sprintf(n->str, UID_FMT "-%s", getuid(), name); + n->size = offsetof(struct kdbus_item, str) + strlen(n->str) + 1; + n->type = KDBUS_ITEM_MAKE_NAME; + make->size += ALIGN8(n->size); + + make->flags = world ? KDBUS_MAKE_ACCESS_WORLD : 0; + + if (ioctl(fd, KDBUS_CMD_BUS_MAKE, make) < 0) { + safe_close(fd); + + /* Major API change? then the ioctls got shuffled around. */ + if (errno == ENOTTY) + return -ESOCKTNOSUPPORT; + + return -errno; + } + + if (s) { + char *p; + + p = strjoin("/sys/fs/kdbus/", n->str, "/bus", NULL); + if (!p) { + safe_close(fd); + return -ENOMEM; + } + + *s = p; + } + + return fd; +} + +int bus_kernel_open_bus_fd(const char *bus, char **path) { + char *p; + int fd; + size_t len; + + assert(bus); + + len = strlen("/sys/fs/kdbus/") + DECIMAL_STR_MAX(uid_t) + 1 + strlen(bus) + strlen("/bus") + 1; + + if (path) { + p = new(char, len); + if (!p) + return -ENOMEM; + } else + p = newa(char, len); + + sprintf(p, "/sys/fs/kdbus/" UID_FMT "-%s/bus", getuid(), bus); + + fd = open(p, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) { + if (path) + free(p); + + return -errno; + } + + if (path) + *path = p; + + return fd; +} + +int bus_kernel_try_close(sd_bus *bus) { + struct kdbus_cmd byebye = { .size = sizeof(byebye) }; + + assert(bus); + assert(bus->is_kernel); + + if (ioctl(bus->input_fd, KDBUS_CMD_BYEBYE, &byebye) < 0) + return -errno; + + return 0; +} + +int bus_kernel_drop_one(int fd) { + struct kdbus_cmd_recv recv = { + .size = sizeof(recv), + .flags = KDBUS_RECV_DROP, + }; + + assert(fd >= 0); + + if (ioctl(fd, KDBUS_CMD_RECV, &recv) < 0) + return -errno; + + return 0; +} + +int bus_kernel_realize_attach_flags(sd_bus *bus) { + struct kdbus_cmd *update; + struct kdbus_item *n; + + assert(bus); + assert(bus->is_kernel); + + update = alloca0_align(offsetof(struct kdbus_cmd, items) + + ALIGN8(offsetof(struct kdbus_item, data64) + sizeof(uint64_t)), + 8); + + n = update->items; + n->type = KDBUS_ITEM_ATTACH_FLAGS_RECV; + n->size = offsetof(struct kdbus_item, data64) + sizeof(uint64_t); + n->data64[0] = bus->attach_flags; + + update->size = + offsetof(struct kdbus_cmd, items) + + ALIGN8(n->size); + + if (ioctl(bus->input_fd, KDBUS_CMD_UPDATE, update) < 0) + return -errno; + + return 0; +} + +int bus_kernel_get_bus_name(sd_bus *bus, char **name) { + struct kdbus_cmd_info cmd = { + .size = sizeof(struct kdbus_cmd_info), + }; + struct kdbus_info *info; + struct kdbus_item *item; + char *n = NULL; + int r; + + assert(bus); + assert(name); + assert(bus->is_kernel); + + r = ioctl(bus->input_fd, KDBUS_CMD_BUS_CREATOR_INFO, &cmd); + if (r < 0) + return -errno; + + info = (struct kdbus_info*) ((uint8_t*) bus->kdbus_buffer + cmd.offset); + + KDBUS_ITEM_FOREACH(item, info, items) + if (item->type == KDBUS_ITEM_MAKE_NAME) { + r = free_and_strdup(&n, item->str); + break; + } + + bus_kernel_cmd_free(bus, cmd.offset); + + if (r < 0) + return r; + if (!n) + return -EIO; + + *name = n; + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-kernel.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-kernel.h new file mode 100644 index 0000000000..2927ba26a5 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-kernel.h @@ -0,0 +1,93 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 + +#define KDBUS_ITEM_NEXT(item) \ + (typeof(item))(((uint8_t *)item) + ALIGN8((item)->size)) + +#define KDBUS_ITEM_FOREACH(part, head, first) \ + for (part = (head)->first; \ + ((uint8_t *)(part) < (uint8_t *)(head) + (head)->size) && \ + ((uint8_t *) part >= (uint8_t *) head); \ + part = KDBUS_ITEM_NEXT(part)) +#define KDBUS_FOREACH(iter, first, _size) \ + for (iter = (first); \ + ((uint8_t *)(iter) < (uint8_t *)(first) + (_size)) && \ + ((uint8_t *)(iter) >= (uint8_t *)(first)); \ + iter = (void*)(((uint8_t *)iter) + ALIGN8((iter)->size))) + +#define KDBUS_ITEM_HEADER_SIZE offsetof(struct kdbus_item, data) +#define KDBUS_ITEM_SIZE(s) ALIGN8((s) + KDBUS_ITEM_HEADER_SIZE) + +#define MEMFD_CACHE_MAX 32 + +/* When we cache a memfd block for reuse, we will truncate blocks + * longer than this in order not to keep too much data around. */ +#define MEMFD_CACHE_ITEM_SIZE_MAX (128*1024) + +/* This determines at which minimum size we prefer sending memfds over + * sending vectors */ +#define MEMFD_MIN_SIZE (512*1024) + +/* The size of the per-connection memory pool that we set up and where + * the kernel places our incoming messages */ +#define KDBUS_POOL_SIZE (16*1024*1024) + +struct memfd_cache { + int fd; + void *address; + size_t mapped; + size_t allocated; +}; + +int bus_kernel_connect(sd_bus *b); +int bus_kernel_take_fd(sd_bus *b); + +int bus_kernel_write_message(sd_bus *bus, sd_bus_message *m, bool hint_sync_call); +int bus_kernel_read_message(sd_bus *bus, bool hint_priority, int64_t priority); + +int bus_kernel_open_bus_fd(const char *bus, char **path); + +int bus_kernel_create_bus(const char *name, bool world, char **s); +int bus_kernel_create_endpoint(const char *bus_name, const char *ep_name, char **path); + +int bus_kernel_pop_memfd(sd_bus *bus, void **address, size_t *mapped, size_t *allocated); +void bus_kernel_push_memfd(sd_bus *bus, int fd, void *address, size_t mapped, size_t allocated); + +void bus_kernel_flush_memfd(sd_bus *bus); + +int bus_kernel_parse_unique_name(const char *s, uint64_t *id); + +uint64_t request_name_flags_to_kdbus(uint64_t sd_bus_flags); +uint64_t attach_flags_to_kdbus(uint64_t sd_bus_flags); + +int bus_kernel_try_close(sd_bus *bus); + +int bus_kernel_drop_one(int fd); + +int bus_kernel_realize_attach_flags(sd_bus *bus); + +int bus_kernel_get_bus_name(sd_bus *bus, char **name); + +int bus_kernel_cmd_free(sd_bus *bus, uint64_t offset); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-match.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-match.c new file mode 100644 index 0000000000..397baf6f33 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-match.c @@ -0,0 +1,1218 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "alloc-util.h" +#include "bus-internal.h" +#include "bus-match.h" +#include "bus-message.h" +#include "bus-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "hexdecoct.h" +#include "string-util.h" +#include "strv.h" + +/* Example: + * + * A: type=signal,sender=foo,interface=bar + * B: type=signal,sender=quux,interface=fips + * C: type=signal,sender=quux,interface=waldo + * D: type=signal,member=test + * E: sender=miau + * F: type=signal + * G: type=signal + * + * results in this tree: + * + * BUS_MATCH_ROOT + * + BUS_MATCH_MESSAGE_TYPE + * | ` BUS_MATCH_VALUE: value == signal + * | + DBUS_MATCH_SENDER + * | | + BUS_MATCH_VALUE: value == foo + * | | | ` DBUS_MATCH_INTERFACE + * | | | ` BUS_MATCH_VALUE: value == bar + * | | | ` BUS_MATCH_LEAF: A + * | | ` BUS_MATCH_VALUE: value == quux + * | | ` DBUS_MATCH_INTERFACE + * | | | BUS_MATCH_VALUE: value == fips + * | | | ` BUS_MATCH_LEAF: B + * | | ` BUS_MATCH_VALUE: value == waldo + * | | ` BUS_MATCH_LEAF: C + * | + DBUS_MATCH_MEMBER + * | | ` BUS_MATCH_VALUE: value == test + * | | ` BUS_MATCH_LEAF: D + * | + BUS_MATCH_LEAF: F + * | ` BUS_MATCH_LEAF: G + * ` BUS_MATCH_SENDER + * ` BUS_MATCH_VALUE: value == miau + * ` BUS_MATCH_LEAF: E + */ + +static inline bool BUS_MATCH_IS_COMPARE(enum bus_match_node_type t) { + return t >= BUS_MATCH_SENDER && t <= BUS_MATCH_ARG_HAS_LAST; +} + +static inline bool BUS_MATCH_CAN_HASH(enum bus_match_node_type t) { + return (t >= BUS_MATCH_MESSAGE_TYPE && t <= BUS_MATCH_PATH) || + (t >= BUS_MATCH_ARG && t <= BUS_MATCH_ARG_LAST) || + (t >= BUS_MATCH_ARG_HAS && t <= BUS_MATCH_ARG_HAS_LAST); +} + +static void bus_match_node_free(struct bus_match_node *node) { + assert(node); + assert(node->parent); + assert(!node->child); + assert(node->type != BUS_MATCH_ROOT); + assert(node->type < _BUS_MATCH_NODE_TYPE_MAX); + + if (node->parent->child) { + /* We are apparently linked into the parent's child + * list. Let's remove us from there. */ + if (node->prev) { + assert(node->prev->next == node); + node->prev->next = node->next; + } else { + assert(node->parent->child == node); + node->parent->child = node->next; + } + + if (node->next) + node->next->prev = node->prev; + } + + if (node->type == BUS_MATCH_VALUE) { + /* We might be in the parent's hash table, so clean + * this up */ + + if (node->parent->type == BUS_MATCH_MESSAGE_TYPE) + hashmap_remove(node->parent->compare.children, UINT_TO_PTR(node->value.u8)); + else if (BUS_MATCH_CAN_HASH(node->parent->type) && node->value.str) + hashmap_remove(node->parent->compare.children, node->value.str); + + free(node->value.str); + } + + if (BUS_MATCH_IS_COMPARE(node->type)) { + assert(hashmap_isempty(node->compare.children)); + hashmap_free(node->compare.children); + } + + free(node); +} + +static bool bus_match_node_maybe_free(struct bus_match_node *node) { + assert(node); + + if (node->type == BUS_MATCH_ROOT) + return false; + + if (node->child) + return false; + + if (BUS_MATCH_IS_COMPARE(node->type) && !hashmap_isempty(node->compare.children)) + return true; + + bus_match_node_free(node); + return true; +} + +static bool value_node_test( + struct bus_match_node *node, + enum bus_match_node_type parent_type, + uint8_t value_u8, + const char *value_str, + char **value_strv, + sd_bus_message *m) { + + assert(node); + assert(node->type == BUS_MATCH_VALUE); + + /* Tests parameters against this value node, doing prefix + * magic and stuff. */ + + switch (parent_type) { + + case BUS_MATCH_MESSAGE_TYPE: + return node->value.u8 == value_u8; + + case BUS_MATCH_SENDER: + if (streq_ptr(node->value.str, value_str)) + return true; + + if (m->creds.mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) { + char **i; + + /* on kdbus we have the well known names list + * in the credentials, let's make use of that + * for an accurate match */ + + STRV_FOREACH(i, m->creds.well_known_names) + if (streq_ptr(node->value.str, *i)) + return true; + + } else { + + /* If we don't have kdbus, we don't know the + * well-known names of the senders. In that, + * let's just hope that dbus-daemon doesn't + * send us stuff we didn't want. */ + + if (node->value.str[0] != ':' && value_str && value_str[0] == ':') + return true; + } + + return false; + + case BUS_MATCH_DESTINATION: + case BUS_MATCH_INTERFACE: + case BUS_MATCH_MEMBER: + case BUS_MATCH_PATH: + case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: + + if (value_str) + return streq_ptr(node->value.str, value_str); + + return false; + + case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST: { + char **i; + + STRV_FOREACH(i, value_strv) + if (streq_ptr(node->value.str, *i)) + return true; + + return false; + } + + case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: + if (value_str) + return namespace_simple_pattern(node->value.str, value_str); + + return false; + + case BUS_MATCH_PATH_NAMESPACE: + return path_simple_pattern(node->value.str, value_str); + + case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: + if (value_str) + return path_complex_pattern(node->value.str, value_str); + + return false; + + default: + assert_not_reached("Invalid node type"); + } +} + +static bool value_node_same( + struct bus_match_node *node, + enum bus_match_node_type parent_type, + uint8_t value_u8, + const char *value_str) { + + /* Tests parameters against this value node, not doing prefix + * magic and stuff, i.e. this one actually compares the match + * itself. */ + + assert(node); + assert(node->type == BUS_MATCH_VALUE); + + switch (parent_type) { + + case BUS_MATCH_MESSAGE_TYPE: + return node->value.u8 == value_u8; + + case BUS_MATCH_SENDER: + case BUS_MATCH_DESTINATION: + case BUS_MATCH_INTERFACE: + case BUS_MATCH_MEMBER: + case BUS_MATCH_PATH: + case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: + case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST: + case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: + case BUS_MATCH_PATH_NAMESPACE: + case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: + return streq(node->value.str, value_str); + + default: + assert_not_reached("Invalid node type"); + } +} + +int bus_match_run( + sd_bus *bus, + struct bus_match_node *node, + sd_bus_message *m) { + + _cleanup_strv_free_ char **test_strv = NULL; + const char *test_str = NULL; + uint8_t test_u8 = 0; + int r; + + assert(m); + + if (!node) + return 0; + + if (bus && bus->match_callbacks_modified) + return 0; + + /* Not these special semantics: when traversing the tree we + * usually let bus_match_run() when called for a node + * recursively invoke bus_match_run(). There's are two + * exceptions here though, which are BUS_NODE_ROOT (which + * cannot have a sibling), and BUS_NODE_VALUE (whose siblings + * are invoked anyway by its parent. */ + + switch (node->type) { + + case BUS_MATCH_ROOT: + + /* Run all children. Since we cannot have any siblings + * we won't call any. The children of the root node + * are compares or leaves, they will automatically + * call their siblings. */ + return bus_match_run(bus, node->child, m); + + case BUS_MATCH_VALUE: + + /* Run all children. We don't execute any siblings, we + * assume our caller does that. The children of value + * nodes are compares or leaves, they will + * automatically call their siblings */ + + assert(node->child); + return bus_match_run(bus, node->child, m); + + case BUS_MATCH_LEAF: + + if (bus) { + if (node->leaf.callback->last_iteration == bus->iteration_counter) + return 0; + + node->leaf.callback->last_iteration = bus->iteration_counter; + } + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + /* Run the callback. And then invoke siblings. */ + if (node->leaf.callback->callback) { + _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; + sd_bus_slot *slot; + + slot = container_of(node->leaf.callback, sd_bus_slot, match_callback); + if (bus) { + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_handler = node->leaf.callback->callback; + bus->current_userdata = slot->userdata; + } + r = node->leaf.callback->callback(m, slot->userdata, &error_buffer); + if (bus) { + bus->current_userdata = NULL; + bus->current_handler = NULL; + bus->current_slot = sd_bus_slot_unref(slot); + } + + r = bus_maybe_reply_error(m, r, &error_buffer); + if (r != 0) + return r; + + if (bus && bus->match_callbacks_modified) + return 0; + } + + return bus_match_run(bus, node->next, m); + + case BUS_MATCH_MESSAGE_TYPE: + test_u8 = m->header->type; + break; + + case BUS_MATCH_SENDER: + test_str = m->sender; + /* FIXME: resolve test_str from a well-known to a unique name first */ + break; + + case BUS_MATCH_DESTINATION: + test_str = m->destination; + break; + + case BUS_MATCH_INTERFACE: + test_str = m->interface; + break; + + case BUS_MATCH_MEMBER: + test_str = m->member; + break; + + case BUS_MATCH_PATH: + case BUS_MATCH_PATH_NAMESPACE: + test_str = m->path; + break; + + case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: + (void) bus_message_get_arg(m, node->type - BUS_MATCH_ARG, &test_str); + break; + + case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: + (void) bus_message_get_arg(m, node->type - BUS_MATCH_ARG_PATH, &test_str); + break; + + case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: + (void) bus_message_get_arg(m, node->type - BUS_MATCH_ARG_NAMESPACE, &test_str); + break; + + case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST: + (void) bus_message_get_arg_strv(m, node->type - BUS_MATCH_ARG_HAS, &test_strv); + break; + + default: + assert_not_reached("Unknown match type."); + } + + if (BUS_MATCH_CAN_HASH(node->type)) { + struct bus_match_node *found; + + /* Lookup via hash table, nice! So let's jump directly. */ + + if (test_str) + found = hashmap_get(node->compare.children, test_str); + else if (test_strv) { + char **i; + + STRV_FOREACH(i, test_strv) { + found = hashmap_get(node->compare.children, *i); + if (found) { + r = bus_match_run(bus, found, m); + if (r != 0) + return r; + } + } + + found = NULL; + } else if (node->type == BUS_MATCH_MESSAGE_TYPE) + found = hashmap_get(node->compare.children, UINT_TO_PTR(test_u8)); + else + found = NULL; + + if (found) { + r = bus_match_run(bus, found, m); + if (r != 0) + return r; + } + } else { + struct bus_match_node *c; + + /* No hash table, so let's iterate manually... */ + + for (c = node->child; c; c = c->next) { + if (!value_node_test(c, node->type, test_u8, test_str, test_strv, m)) + continue; + + r = bus_match_run(bus, c, m); + if (r != 0) + return r; + } + } + + if (bus && bus->match_callbacks_modified) + return 0; + + /* And now, let's invoke our siblings */ + return bus_match_run(bus, node->next, m); +} + +static int bus_match_add_compare_value( + struct bus_match_node *where, + enum bus_match_node_type t, + uint8_t value_u8, + const char *value_str, + struct bus_match_node **ret) { + + struct bus_match_node *c = NULL, *n = NULL; + int r; + + assert(where); + assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); + assert(BUS_MATCH_IS_COMPARE(t)); + assert(ret); + + for (c = where->child; c && c->type != t; c = c->next) + ; + + if (c) { + /* Comparison node already exists? Then let's see if + * the value node exists too. */ + + if (t == BUS_MATCH_MESSAGE_TYPE) + n = hashmap_get(c->compare.children, UINT_TO_PTR(value_u8)); + else if (BUS_MATCH_CAN_HASH(t)) + n = hashmap_get(c->compare.children, value_str); + else { + for (n = c->child; n && !value_node_same(n, t, value_u8, value_str); n = n->next) + ; + } + + if (n) { + *ret = n; + return 0; + } + } else { + /* Comparison node, doesn't exist yet? Then let's + * create it. */ + + c = new0(struct bus_match_node, 1); + if (!c) { + r = -ENOMEM; + goto fail; + } + + c->type = t; + c->parent = where; + c->next = where->child; + if (c->next) + c->next->prev = c; + where->child = c; + + if (t == BUS_MATCH_MESSAGE_TYPE) { + c->compare.children = hashmap_new(NULL); + if (!c->compare.children) { + r = -ENOMEM; + goto fail; + } + } else if (BUS_MATCH_CAN_HASH(t)) { + c->compare.children = hashmap_new(&string_hash_ops); + if (!c->compare.children) { + r = -ENOMEM; + goto fail; + } + } + } + + n = new0(struct bus_match_node, 1); + if (!n) { + r = -ENOMEM; + goto fail; + } + + n->type = BUS_MATCH_VALUE; + n->value.u8 = value_u8; + if (value_str) { + n->value.str = strdup(value_str); + if (!n->value.str) { + r = -ENOMEM; + goto fail; + } + } + + n->parent = c; + if (c->compare.children) { + + if (t == BUS_MATCH_MESSAGE_TYPE) + r = hashmap_put(c->compare.children, UINT_TO_PTR(value_u8), n); + else + r = hashmap_put(c->compare.children, n->value.str, n); + + if (r < 0) + goto fail; + } else { + n->next = c->child; + if (n->next) + n->next->prev = n; + c->child = n; + } + + *ret = n; + return 1; + +fail: + if (c) + bus_match_node_maybe_free(c); + + if (n) { + free(n->value.str); + free(n); + } + + return r; +} + +static int bus_match_find_compare_value( + struct bus_match_node *where, + enum bus_match_node_type t, + uint8_t value_u8, + const char *value_str, + struct bus_match_node **ret) { + + struct bus_match_node *c, *n; + + assert(where); + assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); + assert(BUS_MATCH_IS_COMPARE(t)); + assert(ret); + + for (c = where->child; c && c->type != t; c = c->next) + ; + + if (!c) + return 0; + + if (t == BUS_MATCH_MESSAGE_TYPE) + n = hashmap_get(c->compare.children, UINT_TO_PTR(value_u8)); + else if (BUS_MATCH_CAN_HASH(t)) + n = hashmap_get(c->compare.children, value_str); + else { + for (n = c->child; n && !value_node_same(n, t, value_u8, value_str); n = n->next) + ; + } + + if (n) { + *ret = n; + return 1; + } + + return 0; +} + +static int bus_match_add_leaf( + struct bus_match_node *where, + struct match_callback *callback) { + + struct bus_match_node *n; + + assert(where); + assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); + assert(callback); + + n = new0(struct bus_match_node, 1); + if (!n) + return -ENOMEM; + + n->type = BUS_MATCH_LEAF; + n->parent = where; + n->next = where->child; + if (n->next) + n->next->prev = n; + + n->leaf.callback = callback; + callback->match_node = n; + + where->child = n; + + return 1; +} + +static int bus_match_find_leaf( + struct bus_match_node *where, + sd_bus_message_handler_t callback, + void *userdata, + struct bus_match_node **ret) { + + struct bus_match_node *c; + + assert(where); + assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); + assert(ret); + + for (c = where->child; c; c = c->next) { + sd_bus_slot *s; + + s = container_of(c->leaf.callback, sd_bus_slot, match_callback); + + if (c->type == BUS_MATCH_LEAF && + c->leaf.callback->callback == callback && + s->userdata == userdata) { + *ret = c; + return 1; + } + } + + return 0; +} + +enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n) { + assert(k); + + if (n == 4 && startswith(k, "type")) + return BUS_MATCH_MESSAGE_TYPE; + if (n == 6 && startswith(k, "sender")) + return BUS_MATCH_SENDER; + if (n == 11 && startswith(k, "destination")) + return BUS_MATCH_DESTINATION; + if (n == 9 && startswith(k, "interface")) + return BUS_MATCH_INTERFACE; + if (n == 6 && startswith(k, "member")) + return BUS_MATCH_MEMBER; + if (n == 4 && startswith(k, "path")) + return BUS_MATCH_PATH; + if (n == 14 && startswith(k, "path_namespace")) + return BUS_MATCH_PATH_NAMESPACE; + + if (n == 4 && startswith(k, "arg")) { + int j; + + j = undecchar(k[3]); + if (j < 0) + return -EINVAL; + + return BUS_MATCH_ARG + j; + } + + if (n == 5 && startswith(k, "arg")) { + int a, b; + enum bus_match_node_type t; + + a = undecchar(k[3]); + b = undecchar(k[4]); + if (a <= 0 || b < 0) + return -EINVAL; + + t = BUS_MATCH_ARG + a * 10 + b; + if (t > BUS_MATCH_ARG_LAST) + return -EINVAL; + + return t; + } + + if (n == 8 && startswith(k, "arg") && startswith(k + 4, "path")) { + int j; + + j = undecchar(k[3]); + if (j < 0) + return -EINVAL; + + return BUS_MATCH_ARG_PATH + j; + } + + if (n == 9 && startswith(k, "arg") && startswith(k + 5, "path")) { + enum bus_match_node_type t; + int a, b; + + a = undecchar(k[3]); + b = undecchar(k[4]); + if (a <= 0 || b < 0) + return -EINVAL; + + t = BUS_MATCH_ARG_PATH + a * 10 + b; + if (t > BUS_MATCH_ARG_PATH_LAST) + return -EINVAL; + + return t; + } + + if (n == 13 && startswith(k, "arg") && startswith(k + 4, "namespace")) { + int j; + + j = undecchar(k[3]); + if (j < 0) + return -EINVAL; + + return BUS_MATCH_ARG_NAMESPACE + j; + } + + if (n == 14 && startswith(k, "arg") && startswith(k + 5, "namespace")) { + enum bus_match_node_type t; + int a, b; + + a = undecchar(k[3]); + b = undecchar(k[4]); + if (a <= 0 || b < 0) + return -EINVAL; + + t = BUS_MATCH_ARG_NAMESPACE + a * 10 + b; + if (t > BUS_MATCH_ARG_NAMESPACE_LAST) + return -EINVAL; + + return t; + } + + if (n == 7 && startswith(k, "arg") && startswith(k + 4, "has")) { + int j; + + j = undecchar(k[3]); + if (j < 0) + return -EINVAL; + + return BUS_MATCH_ARG_HAS + j; + } + + if (n == 8 && startswith(k, "arg") && startswith(k + 5, "has")) { + enum bus_match_node_type t; + int a, b; + + a = undecchar(k[3]); + b = undecchar(k[4]); + if (a <= 0 || b < 0) + return -EINVAL; + + t = BUS_MATCH_ARG_HAS + a * 10 + b; + if (t > BUS_MATCH_ARG_HAS_LAST) + return -EINVAL; + + return t; + } + + return -EINVAL; +} + +static int match_component_compare(const void *a, const void *b) { + const struct bus_match_component *x = a, *y = b; + + if (x->type < y->type) + return -1; + if (x->type > y->type) + return 1; + + return 0; +} + +void bus_match_parse_free(struct bus_match_component *components, unsigned n_components) { + unsigned i; + + for (i = 0; i < n_components; i++) + free(components[i].value_str); + + free(components); +} + +int bus_match_parse( + const char *match, + struct bus_match_component **_components, + unsigned *_n_components) { + + const char *p = match; + struct bus_match_component *components = NULL; + size_t components_allocated = 0; + unsigned n_components = 0, i; + _cleanup_free_ char *value = NULL; + int r; + + assert(match); + assert(_components); + assert(_n_components); + + while (*p != 0) { + const char *eq, *q; + enum bus_match_node_type t; + unsigned j = 0; + size_t value_allocated = 0; + bool escaped = false, quoted; + uint8_t u; + + /* Avahi's match rules appear to include whitespace, skip over it */ + p += strspn(p, " "); + + eq = strchr(p, '='); + if (!eq) + return -EINVAL; + + t = bus_match_node_type_from_string(p, eq - p); + if (t < 0) + return -EINVAL; + + quoted = eq[1] == '\''; + + for (q = eq + 1 + quoted;; q++) { + + if (*q == 0) { + + if (quoted) { + r = -EINVAL; + goto fail; + } else { + if (value) + value[j] = 0; + break; + } + } + + if (!escaped) { + if (*q == '\\') { + escaped = true; + continue; + } + + if (quoted) { + if (*q == '\'') { + if (value) + value[j] = 0; + break; + } + } else { + if (*q == ',') { + if (value) + value[j] = 0; + + break; + } + } + } + + if (!GREEDY_REALLOC(value, value_allocated, j + 2)) { + r = -ENOMEM; + goto fail; + } + + value[j++] = *q; + escaped = false; + } + + if (!value) { + value = strdup(""); + if (!value) { + r = -ENOMEM; + goto fail; + } + } + + if (t == BUS_MATCH_MESSAGE_TYPE) { + r = bus_message_type_from_string(value, &u); + if (r < 0) + goto fail; + + value = mfree(value); + } else + u = 0; + + if (!GREEDY_REALLOC(components, components_allocated, n_components + 1)) { + r = -ENOMEM; + goto fail; + } + + components[n_components].type = t; + components[n_components].value_str = value; + components[n_components].value_u8 = u; + n_components++; + + value = NULL; + + if (q[quoted] == 0) + break; + + if (q[quoted] != ',') { + r = -EINVAL; + goto fail; + } + + p = q + 1 + quoted; + } + + /* Order the whole thing, so that we always generate the same tree */ + qsort_safe(components, n_components, sizeof(struct bus_match_component), match_component_compare); + + /* Check for duplicates */ + for (i = 0; i+1 < n_components; i++) + if (components[i].type == components[i+1].type) { + r = -EINVAL; + goto fail; + } + + *_components = components; + *_n_components = n_components; + + return 0; + +fail: + bus_match_parse_free(components, n_components); + return r; +} + +char *bus_match_to_string(struct bus_match_component *components, unsigned n_components) { + _cleanup_fclose_ FILE *f = NULL; + char *buffer = NULL; + size_t size = 0; + unsigned i; + int r; + + if (n_components <= 0) + return strdup(""); + + assert(components); + + f = open_memstream(&buffer, &size); + if (!f) + return NULL; + + for (i = 0; i < n_components; i++) { + char buf[32]; + + if (i != 0) + fputc(',', f); + + fputs(bus_match_node_type_to_string(components[i].type, buf, sizeof(buf)), f); + fputc('=', f); + fputc('\'', f); + + if (components[i].type == BUS_MATCH_MESSAGE_TYPE) + fputs(bus_message_type_to_string(components[i].value_u8), f); + else + fputs(components[i].value_str, f); + + fputc('\'', f); + } + + r = fflush_and_check(f); + if (r < 0) + return NULL; + + return buffer; +} + +int bus_match_add( + struct bus_match_node *root, + struct bus_match_component *components, + unsigned n_components, + struct match_callback *callback) { + + unsigned i; + struct bus_match_node *n; + int r; + + assert(root); + assert(callback); + + n = root; + for (i = 0; i < n_components; i++) { + r = bus_match_add_compare_value( + n, components[i].type, + components[i].value_u8, components[i].value_str, &n); + if (r < 0) + return r; + } + + return bus_match_add_leaf(n, callback); +} + +int bus_match_remove( + struct bus_match_node *root, + struct match_callback *callback) { + + struct bus_match_node *node, *pp; + + assert(root); + assert(callback); + + node = callback->match_node; + if (!node) + return 0; + + assert(node->type == BUS_MATCH_LEAF); + + callback->match_node = NULL; + + /* Free the leaf */ + pp = node->parent; + bus_match_node_free(node); + + /* Prune the tree above */ + while (pp) { + node = pp; + pp = node->parent; + + if (!bus_match_node_maybe_free(node)) + break; + } + + return 1; +} + +int bus_match_find( + struct bus_match_node *root, + struct bus_match_component *components, + unsigned n_components, + sd_bus_message_handler_t callback, + void *userdata, + struct match_callback **ret) { + + struct bus_match_node *n, **gc; + unsigned i; + int r; + + assert(root); + assert(ret); + + gc = newa(struct bus_match_node*, n_components); + + n = root; + for (i = 0; i < n_components; i++) { + r = bus_match_find_compare_value( + n, components[i].type, + components[i].value_u8, components[i].value_str, + &n); + if (r <= 0) + return r; + + gc[i] = n; + } + + r = bus_match_find_leaf(n, callback, userdata, &n); + if (r <= 0) + return r; + + *ret = n->leaf.callback; + return 1; +} + +void bus_match_free(struct bus_match_node *node) { + struct bus_match_node *c; + + if (!node) + return; + + if (BUS_MATCH_CAN_HASH(node->type)) { + Iterator i; + + HASHMAP_FOREACH(c, node->compare.children, i) + bus_match_free(c); + + assert(hashmap_isempty(node->compare.children)); + } + + while ((c = node->child)) + bus_match_free(c); + + if (node->type != BUS_MATCH_ROOT) + bus_match_node_free(node); +} + +const char* bus_match_node_type_to_string(enum bus_match_node_type t, char buf[], size_t l) { + switch (t) { + + case BUS_MATCH_ROOT: + return "root"; + + case BUS_MATCH_VALUE: + return "value"; + + case BUS_MATCH_LEAF: + return "leaf"; + + case BUS_MATCH_MESSAGE_TYPE: + return "type"; + + case BUS_MATCH_SENDER: + return "sender"; + + case BUS_MATCH_DESTINATION: + return "destination"; + + case BUS_MATCH_INTERFACE: + return "interface"; + + case BUS_MATCH_MEMBER: + return "member"; + + case BUS_MATCH_PATH: + return "path"; + + case BUS_MATCH_PATH_NAMESPACE: + return "path_namespace"; + + case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: + snprintf(buf, l, "arg%i", t - BUS_MATCH_ARG); + return buf; + + case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: + snprintf(buf, l, "arg%ipath", t - BUS_MATCH_ARG_PATH); + return buf; + + case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: + snprintf(buf, l, "arg%inamespace", t - BUS_MATCH_ARG_NAMESPACE); + return buf; + + case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST: + snprintf(buf, l, "arg%ihas", t - BUS_MATCH_ARG_HAS); + return buf; + + default: + return NULL; + } +} + +void bus_match_dump(struct bus_match_node *node, unsigned level) { + struct bus_match_node *c; + _cleanup_free_ char *pfx = NULL; + char buf[32]; + + if (!node) + return; + + pfx = strrep(" ", level); + printf("%s[%s]", strempty(pfx), bus_match_node_type_to_string(node->type, buf, sizeof(buf))); + + if (node->type == BUS_MATCH_VALUE) { + if (node->parent->type == BUS_MATCH_MESSAGE_TYPE) + printf(" <%u>\n", node->value.u8); + else + printf(" <%s>\n", node->value.str); + } else if (node->type == BUS_MATCH_ROOT) + puts(" root"); + else if (node->type == BUS_MATCH_LEAF) + printf(" %p/%p\n", node->leaf.callback->callback, container_of(node->leaf.callback, sd_bus_slot, match_callback)->userdata); + else + putchar('\n'); + + if (BUS_MATCH_CAN_HASH(node->type)) { + Iterator i; + + HASHMAP_FOREACH(c, node->compare.children, i) + bus_match_dump(c, level + 1); + } + + for (c = node->child; c; c = c->next) + bus_match_dump(c, level + 1); +} + +enum bus_match_scope bus_match_get_scope(const struct bus_match_component *components, unsigned n_components) { + bool found_driver = false; + unsigned i; + + if (n_components <= 0) + return BUS_MATCH_GENERIC; + + assert(components); + + /* Checks whether the specified match can only match the + * pseudo-service for local messages, which we detect by + * sender, interface or path. If a match is not restricted to + * local messages, then we check if it only matches on the + * driver. */ + + for (i = 0; i < n_components; i++) { + const struct bus_match_component *c = components + i; + + if (c->type == BUS_MATCH_SENDER) { + if (streq_ptr(c->value_str, "org.freedesktop.DBus.Local")) + return BUS_MATCH_LOCAL; + + if (streq_ptr(c->value_str, "org.freedesktop.DBus")) + found_driver = true; + } + + if (c->type == BUS_MATCH_INTERFACE && streq_ptr(c->value_str, "org.freedesktop.DBus.Local")) + return BUS_MATCH_LOCAL; + + if (c->type == BUS_MATCH_PATH && streq_ptr(c->value_str, "/org/freedesktop/DBus/Local")) + return BUS_MATCH_LOCAL; + } + + return found_driver ? BUS_MATCH_DRIVER : BUS_MATCH_GENERIC; + +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-match.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-match.h new file mode 100644 index 0000000000..3f71720185 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-match.h @@ -0,0 +1,100 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 "hashmap.h" + +enum bus_match_node_type { + BUS_MATCH_ROOT, + BUS_MATCH_VALUE, + BUS_MATCH_LEAF, + + /* The following are all different kinds of compare nodes */ + BUS_MATCH_SENDER, + BUS_MATCH_MESSAGE_TYPE, + BUS_MATCH_DESTINATION, + BUS_MATCH_INTERFACE, + BUS_MATCH_MEMBER, + BUS_MATCH_PATH, + BUS_MATCH_PATH_NAMESPACE, + BUS_MATCH_ARG, + BUS_MATCH_ARG_LAST = BUS_MATCH_ARG + 63, + BUS_MATCH_ARG_PATH, + BUS_MATCH_ARG_PATH_LAST = BUS_MATCH_ARG_PATH + 63, + BUS_MATCH_ARG_NAMESPACE, + BUS_MATCH_ARG_NAMESPACE_LAST = BUS_MATCH_ARG_NAMESPACE + 63, + BUS_MATCH_ARG_HAS, + BUS_MATCH_ARG_HAS_LAST = BUS_MATCH_ARG_HAS + 63, + _BUS_MATCH_NODE_TYPE_MAX, + _BUS_MATCH_NODE_TYPE_INVALID = -1 +}; + +struct bus_match_node { + enum bus_match_node_type type; + struct bus_match_node *parent, *next, *prev, *child; + + union { + struct { + char *str; + uint8_t u8; + } value; + struct { + struct match_callback *callback; + } leaf; + struct { + /* If this is set, then the child is NULL */ + Hashmap *children; + } compare; + }; +}; + +struct bus_match_component { + enum bus_match_node_type type; + uint8_t value_u8; + char *value_str; +}; + +enum bus_match_scope { + BUS_MATCH_GENERIC, + BUS_MATCH_LOCAL, + BUS_MATCH_DRIVER, +}; + +int bus_match_run(sd_bus *bus, struct bus_match_node *root, sd_bus_message *m); + +int bus_match_add(struct bus_match_node *root, struct bus_match_component *components, unsigned n_components, struct match_callback *callback); +int bus_match_remove(struct bus_match_node *root, struct match_callback *callback); + +int bus_match_find(struct bus_match_node *root, struct bus_match_component *components, unsigned n_components, sd_bus_message_handler_t callback, void *userdata, struct match_callback **ret); + +void bus_match_free(struct bus_match_node *node); + +void bus_match_dump(struct bus_match_node *node, unsigned level); + +const char* bus_match_node_type_to_string(enum bus_match_node_type t, char buf[], size_t l); +enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n); + +int bus_match_parse(const char *match, struct bus_match_component **_components, unsigned *_n_components); +void bus_match_parse_free(struct bus_match_component *components, unsigned n_components); +char *bus_match_to_string(struct bus_match_component *components, unsigned n_components); + +enum bus_match_scope bus_match_get_scope(const struct bus_match_component *components, unsigned n_components); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-message.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-message.c new file mode 100644 index 0000000000..a9359c1528 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-message.c @@ -0,0 +1,5939 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 +#include + +#include + +#include "alloc-util.h" +#include "bus-gvariant.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-signature.h" +#include "bus-type.h" +#include "bus-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "memfd-util.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "utf8.h" +#include "util.h" + +static int message_append_basic(sd_bus_message *m, char type, const void *p, const void **stored); + +static void *adjust_pointer(const void *p, void *old_base, size_t sz, void *new_base) { + + if (p == NULL) + return NULL; + + if (old_base == new_base) + return (void*) p; + + if ((uint8_t*) p < (uint8_t*) old_base) + return (void*) p; + + if ((uint8_t*) p >= (uint8_t*) old_base + sz) + return (void*) p; + + return (uint8_t*) new_base + ((uint8_t*) p - (uint8_t*) old_base); +} + +static void message_free_part(sd_bus_message *m, struct bus_body_part *part) { + assert(m); + assert(part); + + if (part->memfd >= 0) { + /* If we can reuse the memfd, try that. For that it + * can't be sealed yet. */ + + if (!part->sealed) { + assert(part->memfd_offset == 0); + assert(part->data == part->mmap_begin); + bus_kernel_push_memfd(m->bus, part->memfd, part->data, part->mapped, part->allocated); + } else { + if (part->mapped > 0) + assert_se(munmap(part->mmap_begin, part->mapped) == 0); + + safe_close(part->memfd); + } + + } else if (part->munmap_this) + munmap(part->mmap_begin, part->mapped); + else if (part->free_this) + free(part->data); + + if (part != &m->body) + free(part); +} + +static void message_reset_parts(sd_bus_message *m) { + struct bus_body_part *part; + + assert(m); + + part = &m->body; + while (m->n_body_parts > 0) { + struct bus_body_part *next = part->next; + message_free_part(m, part); + part = next; + m->n_body_parts--; + } + + m->body_end = NULL; + + m->cached_rindex_part = NULL; + m->cached_rindex_part_begin = 0; +} + +static void message_reset_containers(sd_bus_message *m) { + unsigned i; + + assert(m); + + for (i = 0; i < m->n_containers; i++) { + free(m->containers[i].signature); + free(m->containers[i].offsets); + } + + m->containers = mfree(m->containers); + + m->n_containers = m->containers_allocated = 0; + m->root_container.index = 0; +} + +static void message_free(sd_bus_message *m) { + assert(m); + + if (m->free_header) + free(m->header); + + message_reset_parts(m); + + if (m->release_kdbus) + bus_kernel_cmd_free(m->bus, (uint8_t *) m->kdbus - (uint8_t *) m->bus->kdbus_buffer); + + if (m->free_kdbus) + free(m->kdbus); + + sd_bus_unref(m->bus); + + if (m->free_fds) { + close_many(m->fds, m->n_fds); + free(m->fds); + } + + if (m->iovec != m->iovec_fixed) + free(m->iovec); + + m->destination_ptr = mfree(m->destination_ptr); + message_reset_containers(m); + free(m->root_container.signature); + free(m->root_container.offsets); + + free(m->root_container.peeked_signature); + + bus_creds_done(&m->creds); + free(m); +} + +static void *message_extend_fields(sd_bus_message *m, size_t align, size_t sz, bool add_offset) { + void *op, *np; + size_t old_size, new_size, start; + + assert(m); + + if (m->poisoned) + return NULL; + + old_size = sizeof(struct bus_header) + m->fields_size; + start = ALIGN_TO(old_size, align); + new_size = start + sz; + + if (new_size < start || + new_size > (size_t) ((uint32_t) -1)) + goto poison; + + if (old_size == new_size) + return (uint8_t*) m->header + old_size; + + if (m->free_header) { + np = realloc(m->header, ALIGN8(new_size)); + if (!np) + goto poison; + } else { + /* Initially, the header is allocated as part of of + * the sd_bus_message itself, let's replace it by + * dynamic data */ + + np = malloc(ALIGN8(new_size)); + if (!np) + goto poison; + + memcpy(np, m->header, sizeof(struct bus_header)); + } + + /* Zero out padding */ + if (start > old_size) + memzero((uint8_t*) np + old_size, start - old_size); + + op = m->header; + m->header = np; + m->fields_size = new_size - sizeof(struct bus_header); + + /* Adjust quick access pointers */ + m->path = adjust_pointer(m->path, op, old_size, m->header); + m->interface = adjust_pointer(m->interface, op, old_size, m->header); + m->member = adjust_pointer(m->member, op, old_size, m->header); + m->destination = adjust_pointer(m->destination, op, old_size, m->header); + m->sender = adjust_pointer(m->sender, op, old_size, m->header); + m->error.name = adjust_pointer(m->error.name, op, old_size, m->header); + + m->free_header = true; + + if (add_offset) { + if (m->n_header_offsets >= ELEMENTSOF(m->header_offsets)) + goto poison; + + m->header_offsets[m->n_header_offsets++] = new_size - sizeof(struct bus_header); + } + + return (uint8_t*) np + start; + +poison: + m->poisoned = true; + return NULL; +} + +static int message_append_field_string( + sd_bus_message *m, + uint64_t h, + char type, + const char *s, + const char **ret) { + + size_t l; + uint8_t *p; + + assert(m); + + /* dbus1 only allows 8bit header field ids */ + if (h > 0xFF) + return -EINVAL; + + /* dbus1 doesn't allow strings over 32bit, let's enforce this + * globally, to not risk convertability */ + l = strlen(s); + if (l > (size_t) (uint32_t) -1) + return -EINVAL; + + /* Signature "(yv)" where the variant contains "s" */ + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + + /* (field id 64bit, ((string + NUL) + NUL + signature string 's') */ + p = message_extend_fields(m, 8, 8 + l + 1 + 1 + 1, true); + if (!p) + return -ENOMEM; + + *((uint64_t*) p) = h; + memcpy(p+8, s, l); + p[8+l] = 0; + p[8+l+1] = 0; + p[8+l+2] = type; + + if (ret) + *ret = (char*) p + 8; + + } else { + /* (field id byte + (signature length + signature 's' + NUL) + (string length + string + NUL)) */ + p = message_extend_fields(m, 8, 4 + 4 + l + 1, false); + if (!p) + return -ENOMEM; + + p[0] = (uint8_t) h; + p[1] = 1; + p[2] = type; + p[3] = 0; + + ((uint32_t*) p)[1] = l; + memcpy(p + 8, s, l + 1); + + if (ret) + *ret = (char*) p + 8; + } + + return 0; +} + +static int message_append_field_signature( + sd_bus_message *m, + uint64_t h, + const char *s, + const char **ret) { + + size_t l; + uint8_t *p; + + assert(m); + + /* dbus1 only allows 8bit header field ids */ + if (h > 0xFF) + return -EINVAL; + + /* dbus1 doesn't allow signatures over 8bit, let's enforce + * this globally, to not risk convertability */ + l = strlen(s); + if (l > 255) + return -EINVAL; + + /* Signature "(yv)" where the variant contains "g" */ + + if (BUS_MESSAGE_IS_GVARIANT(m)) + /* For gvariant the serialization is the same as for normal strings */ + return message_append_field_string(m, h, 'g', s, ret); + else { + /* (field id byte + (signature length + signature 'g' + NUL) + (string length + string + NUL)) */ + p = message_extend_fields(m, 8, 4 + 1 + l + 1, false); + if (!p) + return -ENOMEM; + + p[0] = (uint8_t) h; + p[1] = 1; + p[2] = SD_BUS_TYPE_SIGNATURE; + p[3] = 0; + p[4] = l; + memcpy(p + 5, s, l + 1); + + if (ret) + *ret = (const char*) p + 5; + } + + return 0; +} + +static int message_append_field_uint32(sd_bus_message *m, uint64_t h, uint32_t x) { + uint8_t *p; + + assert(m); + + /* dbus1 only allows 8bit header field ids */ + if (h > 0xFF) + return -EINVAL; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + /* (field id 64bit + ((value + NUL + signature string 'u') */ + + p = message_extend_fields(m, 8, 8 + 4 + 1 + 1, true); + if (!p) + return -ENOMEM; + + *((uint64_t*) p) = h; + *((uint32_t*) (p + 8)) = x; + p[12] = 0; + p[13] = 'u'; + } else { + /* (field id byte + (signature length + signature 'u' + NUL) + value) */ + p = message_extend_fields(m, 8, 4 + 4, false); + if (!p) + return -ENOMEM; + + p[0] = (uint8_t) h; + p[1] = 1; + p[2] = 'u'; + p[3] = 0; + + ((uint32_t*) p)[1] = x; + } + + return 0; +} + +static int message_append_field_uint64(sd_bus_message *m, uint64_t h, uint64_t x) { + uint8_t *p; + + assert(m); + + /* dbus1 only allows 8bit header field ids */ + if (h > 0xFF) + return -EINVAL; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + /* (field id 64bit + ((value + NUL + signature string 't') */ + + p = message_extend_fields(m, 8, 8 + 8 + 1 + 1, true); + if (!p) + return -ENOMEM; + + *((uint64_t*) p) = h; + *((uint64_t*) (p + 8)) = x; + p[16] = 0; + p[17] = 't'; + } else { + /* (field id byte + (signature length + signature 't' + NUL) + 4 byte padding + value) */ + p = message_extend_fields(m, 8, 4 + 4 + 8, false); + if (!p) + return -ENOMEM; + + p[0] = (uint8_t) h; + p[1] = 1; + p[2] = 't'; + p[3] = 0; + p[4] = 0; + p[5] = 0; + p[6] = 0; + p[7] = 0; + + ((uint64_t*) p)[1] = x; + } + + return 0; +} + +static int message_append_reply_cookie(sd_bus_message *m, uint64_t cookie) { + assert(m); + + if (BUS_MESSAGE_IS_GVARIANT(m)) + return message_append_field_uint64(m, BUS_MESSAGE_HEADER_REPLY_SERIAL, cookie); + else { + /* 64bit cookies are not supported on dbus1 */ + if (cookie > 0xffffffffUL) + return -EOPNOTSUPP; + + return message_append_field_uint32(m, BUS_MESSAGE_HEADER_REPLY_SERIAL, (uint32_t) cookie); + } +} + +int bus_message_from_header( + sd_bus *bus, + void *header, + size_t header_accessible, + void *footer, + size_t footer_accessible, + size_t message_size, + int *fds, + unsigned n_fds, + const char *label, + size_t extra, + sd_bus_message **ret) { + + _cleanup_free_ sd_bus_message *m = NULL; + struct bus_header *h; + size_t a, label_sz; + + assert(bus); + assert(header || header_accessible <= 0); + assert(footer || footer_accessible <= 0); + assert(fds || n_fds <= 0); + assert(ret); + + if (header_accessible < sizeof(struct bus_header)) + return -EBADMSG; + + if (header_accessible > message_size) + return -EBADMSG; + if (footer_accessible > message_size) + return -EBADMSG; + + h = header; + if (!IN_SET(h->version, 1, 2)) + return -EBADMSG; + + if (h->type == _SD_BUS_MESSAGE_TYPE_INVALID) + return -EBADMSG; + + if (!IN_SET(h->endian, BUS_LITTLE_ENDIAN, BUS_BIG_ENDIAN)) + return -EBADMSG; + + /* Note that we are happy with unknown flags in the flags header! */ + + a = ALIGN(sizeof(sd_bus_message)) + ALIGN(extra); + + if (label) { + label_sz = strlen(label); + a += label_sz + 1; + } + + m = malloc0(a); + if (!m) + return -ENOMEM; + + m->n_ref = 1; + m->sealed = true; + m->header = header; + m->header_accessible = header_accessible; + m->footer = footer; + m->footer_accessible = footer_accessible; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + size_t ws; + + if (h->dbus2.cookie == 0) + return -EBADMSG; + + /* dbus2 derives the sizes from the message size and + the offset table at the end, since it is formatted as + gvariant "yyyyuta{tv}v". Since the message itself is a + structure with precisely to variable sized entries, + there's only one offset in the table, which marks the + end of the fields array. */ + + ws = bus_gvariant_determine_word_size(message_size, 0); + if (footer_accessible < ws) + return -EBADMSG; + + m->fields_size = bus_gvariant_read_word_le((uint8_t*) footer + footer_accessible - ws, ws); + if (ALIGN8(m->fields_size) > message_size - ws) + return -EBADMSG; + if (m->fields_size < sizeof(struct bus_header)) + return -EBADMSG; + + m->fields_size -= sizeof(struct bus_header); + m->body_size = message_size - (sizeof(struct bus_header) + ALIGN8(m->fields_size)); + } else { + if (h->dbus1.serial == 0) + return -EBADMSG; + + /* dbus1 has the sizes in the header */ + m->fields_size = BUS_MESSAGE_BSWAP32(m, h->dbus1.fields_size); + m->body_size = BUS_MESSAGE_BSWAP32(m, h->dbus1.body_size); + + if (sizeof(struct bus_header) + ALIGN8(m->fields_size) + m->body_size != message_size) + return -EBADMSG; + } + + m->fds = fds; + m->n_fds = n_fds; + + if (label) { + m->creds.label = (char*) m + ALIGN(sizeof(sd_bus_message)) + ALIGN(extra); + memcpy(m->creds.label, label, label_sz + 1); + + m->creds.mask |= SD_BUS_CREDS_SELINUX_CONTEXT; + } + + m->bus = sd_bus_ref(bus); + *ret = m; + m = NULL; + + return 0; +} + +int bus_message_from_malloc( + sd_bus *bus, + void *buffer, + size_t length, + int *fds, + unsigned n_fds, + const char *label, + sd_bus_message **ret) { + + sd_bus_message *m; + size_t sz; + int r; + + r = bus_message_from_header( + bus, + buffer, length, /* in this case the initial bytes and the final bytes are the same */ + buffer, length, + length, + fds, n_fds, + label, + 0, &m); + if (r < 0) + return r; + + sz = length - sizeof(struct bus_header) - ALIGN8(m->fields_size); + if (sz > 0) { + m->n_body_parts = 1; + m->body.data = (uint8_t*) buffer + sizeof(struct bus_header) + ALIGN8(m->fields_size); + m->body.size = sz; + m->body.sealed = true; + m->body.memfd = -1; + } + + m->n_iovec = 1; + m->iovec = m->iovec_fixed; + m->iovec[0].iov_base = buffer; + m->iovec[0].iov_len = length; + + r = bus_message_parse_fields(m); + if (r < 0) + goto fail; + + /* We take possession of the memory and fds now */ + m->free_header = true; + m->free_fds = true; + + *ret = m; + return 0; + +fail: + message_free(m); + return r; +} + +static sd_bus_message *message_new(sd_bus *bus, uint8_t type) { + sd_bus_message *m; + + assert(bus); + + m = malloc0(ALIGN(sizeof(sd_bus_message)) + sizeof(struct bus_header)); + if (!m) + return NULL; + + m->n_ref = 1; + m->header = (struct bus_header*) ((uint8_t*) m + ALIGN(sizeof(struct sd_bus_message))); + m->header->endian = BUS_NATIVE_ENDIAN; + m->header->type = type; + m->header->version = bus->message_version; + m->allow_fds = bus->can_fds || (bus->state != BUS_HELLO && bus->state != BUS_RUNNING); + m->root_container.need_offsets = BUS_MESSAGE_IS_GVARIANT(m); + m->bus = sd_bus_ref(bus); + + if (bus->allow_interactive_authorization) + m->header->flags |= BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION; + + return m; +} + +_public_ int sd_bus_message_new_signal( + sd_bus *bus, + sd_bus_message **m, + const char *path, + const char *interface, + const char *member) { + + sd_bus_message *t; + int r; + + assert_return(bus, -ENOTCONN); + assert_return(bus->state != BUS_UNSET, -ENOTCONN); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(interface_name_is_valid(interface), -EINVAL); + assert_return(member_name_is_valid(member), -EINVAL); + assert_return(m, -EINVAL); + + t = message_new(bus, SD_BUS_MESSAGE_SIGNAL); + if (!t) + return -ENOMEM; + + t->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED; + + r = message_append_field_string(t, BUS_MESSAGE_HEADER_PATH, SD_BUS_TYPE_OBJECT_PATH, path, &t->path); + if (r < 0) + goto fail; + r = message_append_field_string(t, BUS_MESSAGE_HEADER_INTERFACE, SD_BUS_TYPE_STRING, interface, &t->interface); + if (r < 0) + goto fail; + r = message_append_field_string(t, BUS_MESSAGE_HEADER_MEMBER, SD_BUS_TYPE_STRING, member, &t->member); + if (r < 0) + goto fail; + + *m = t; + return 0; + +fail: + sd_bus_message_unref(t); + return r; +} + +_public_ int sd_bus_message_new_method_call( + sd_bus *bus, + sd_bus_message **m, + const char *destination, + const char *path, + const char *interface, + const char *member) { + + sd_bus_message *t; + int r; + + assert_return(bus, -ENOTCONN); + assert_return(bus->state != BUS_UNSET, -ENOTCONN); + assert_return(!destination || service_name_is_valid(destination), -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(!interface || interface_name_is_valid(interface), -EINVAL); + assert_return(member_name_is_valid(member), -EINVAL); + assert_return(m, -EINVAL); + + t = message_new(bus, SD_BUS_MESSAGE_METHOD_CALL); + if (!t) + return -ENOMEM; + + r = message_append_field_string(t, BUS_MESSAGE_HEADER_PATH, SD_BUS_TYPE_OBJECT_PATH, path, &t->path); + if (r < 0) + goto fail; + r = message_append_field_string(t, BUS_MESSAGE_HEADER_MEMBER, SD_BUS_TYPE_STRING, member, &t->member); + if (r < 0) + goto fail; + + if (interface) { + r = message_append_field_string(t, BUS_MESSAGE_HEADER_INTERFACE, SD_BUS_TYPE_STRING, interface, &t->interface); + if (r < 0) + goto fail; + } + + if (destination) { + r = message_append_field_string(t, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, destination, &t->destination); + if (r < 0) + goto fail; + } + + *m = t; + return 0; + +fail: + message_free(t); + return r; +} + +static int message_new_reply( + sd_bus_message *call, + uint8_t type, + sd_bus_message **m) { + + sd_bus_message *t; + int r; + + assert_return(call, -EINVAL); + assert_return(call->sealed, -EPERM); + assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); + assert_return(call->bus->state != BUS_UNSET, -ENOTCONN); + assert_return(m, -EINVAL); + + t = message_new(call->bus, type); + if (!t) + return -ENOMEM; + + t->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED; + t->reply_cookie = BUS_MESSAGE_COOKIE(call); + if (t->reply_cookie == 0) + return -EOPNOTSUPP; + + r = message_append_reply_cookie(t, t->reply_cookie); + if (r < 0) + goto fail; + + if (call->sender) { + r = message_append_field_string(t, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, call->sender, &t->destination); + if (r < 0) + goto fail; + } + + t->dont_send = !!(call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED); + t->enforced_reply_signature = call->enforced_reply_signature; + + *m = t; + return 0; + +fail: + message_free(t); + return r; +} + +_public_ int sd_bus_message_new_method_return( + sd_bus_message *call, + sd_bus_message **m) { + + return message_new_reply(call, SD_BUS_MESSAGE_METHOD_RETURN, m); +} + +_public_ int sd_bus_message_new_method_error( + sd_bus_message *call, + sd_bus_message **m, + const sd_bus_error *e) { + + sd_bus_message *t; + int r; + + assert_return(sd_bus_error_is_set(e), -EINVAL); + assert_return(m, -EINVAL); + + r = message_new_reply(call, SD_BUS_MESSAGE_METHOD_ERROR, &t); + if (r < 0) + return r; + + r = message_append_field_string(t, BUS_MESSAGE_HEADER_ERROR_NAME, SD_BUS_TYPE_STRING, e->name, &t->error.name); + if (r < 0) + goto fail; + + if (e->message) { + r = message_append_basic(t, SD_BUS_TYPE_STRING, e->message, (const void**) &t->error.message); + if (r < 0) + goto fail; + } + + t->error._need_free = -1; + + *m = t; + return 0; + +fail: + message_free(t); + return r; +} + +_public_ int sd_bus_message_new_method_errorf( + sd_bus_message *call, + sd_bus_message **m, + const char *name, + const char *format, + ...) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + va_list ap; + + assert_return(name, -EINVAL); + assert_return(m, -EINVAL); + + va_start(ap, format); + bus_error_setfv(&error, name, format, ap); + va_end(ap); + + return sd_bus_message_new_method_error(call, m, &error); +} + +_public_ int sd_bus_message_new_method_errno( + sd_bus_message *call, + sd_bus_message **m, + int error, + const sd_bus_error *p) { + + _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL; + + if (sd_bus_error_is_set(p)) + return sd_bus_message_new_method_error(call, m, p); + + sd_bus_error_set_errno(&berror, error); + + return sd_bus_message_new_method_error(call, m, &berror); +} + +_public_ int sd_bus_message_new_method_errnof( + sd_bus_message *call, + sd_bus_message **m, + int error, + const char *format, + ...) { + + _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL; + va_list ap; + + va_start(ap, format); + sd_bus_error_set_errnofv(&berror, error, format, ap); + va_end(ap); + + return sd_bus_message_new_method_error(call, m, &berror); +} + +void bus_message_set_sender_local(sd_bus *bus, sd_bus_message *m) { + assert(bus); + assert(m); + + m->sender = m->creds.unique_name = (char*) "org.freedesktop.DBus.Local"; + m->creds.well_known_names_local = true; + m->creds.mask |= (SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_WELL_KNOWN_NAMES) & bus->creds_mask; +} + +void bus_message_set_sender_driver(sd_bus *bus, sd_bus_message *m) { + assert(bus); + assert(m); + + m->sender = m->creds.unique_name = (char*) "org.freedesktop.DBus"; + m->creds.well_known_names_driver = true; + m->creds.mask |= (SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_WELL_KNOWN_NAMES) & bus->creds_mask; +} + +int bus_message_new_synthetic_error( + sd_bus *bus, + uint64_t cookie, + const sd_bus_error *e, + sd_bus_message **m) { + + sd_bus_message *t; + int r; + + assert(bus); + assert(sd_bus_error_is_set(e)); + assert(m); + + t = message_new(bus, SD_BUS_MESSAGE_METHOD_ERROR); + if (!t) + return -ENOMEM; + + t->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED; + t->reply_cookie = cookie; + + r = message_append_reply_cookie(t, t->reply_cookie); + if (r < 0) + goto fail; + + if (bus && bus->unique_name) { + r = message_append_field_string(t, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, bus->unique_name, &t->destination); + if (r < 0) + goto fail; + } + + r = message_append_field_string(t, BUS_MESSAGE_HEADER_ERROR_NAME, SD_BUS_TYPE_STRING, e->name, &t->error.name); + if (r < 0) + goto fail; + + if (e->message) { + r = message_append_basic(t, SD_BUS_TYPE_STRING, e->message, (const void**) &t->error.message); + if (r < 0) + goto fail; + } + + t->error._need_free = -1; + + bus_message_set_sender_driver(bus, t); + + *m = t; + return 0; + +fail: + message_free(t); + return r; +} + +_public_ sd_bus_message* sd_bus_message_ref(sd_bus_message *m) { + + if (!m) + return NULL; + + assert(m->n_ref > 0); + m->n_ref++; + + return m; +} + +_public_ sd_bus_message* sd_bus_message_unref(sd_bus_message *m) { + + if (!m) + return NULL; + + assert(m->n_ref > 0); + m->n_ref--; + + if (m->n_ref > 0) + return NULL; + + message_free(m); + return NULL; +} + +_public_ int sd_bus_message_get_type(sd_bus_message *m, uint8_t *type) { + assert_return(m, -EINVAL); + assert_return(type, -EINVAL); + + *type = m->header->type; + return 0; +} + +_public_ int sd_bus_message_get_cookie(sd_bus_message *m, uint64_t *cookie) { + uint64_t c; + + assert_return(m, -EINVAL); + assert_return(cookie, -EINVAL); + + c = BUS_MESSAGE_COOKIE(m); + if (c == 0) + return -ENODATA; + + *cookie = BUS_MESSAGE_COOKIE(m); + return 0; +} + +_public_ int sd_bus_message_get_reply_cookie(sd_bus_message *m, uint64_t *cookie) { + assert_return(m, -EINVAL); + assert_return(cookie, -EINVAL); + + if (m->reply_cookie == 0) + return -ENODATA; + + *cookie = m->reply_cookie; + return 0; +} + +_public_ int sd_bus_message_get_expect_reply(sd_bus_message *m) { + assert_return(m, -EINVAL); + + return m->header->type == SD_BUS_MESSAGE_METHOD_CALL && + !(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED); +} + +_public_ int sd_bus_message_get_auto_start(sd_bus_message *m) { + assert_return(m, -EINVAL); + + return !(m->header->flags & BUS_MESSAGE_NO_AUTO_START); +} + +_public_ int sd_bus_message_get_allow_interactive_authorization(sd_bus_message *m) { + assert_return(m, -EINVAL); + + return m->header->type == SD_BUS_MESSAGE_METHOD_CALL && + (m->header->flags & BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION); +} + +_public_ const char *sd_bus_message_get_path(sd_bus_message *m) { + assert_return(m, NULL); + + return m->path; +} + +_public_ const char *sd_bus_message_get_interface(sd_bus_message *m) { + assert_return(m, NULL); + + return m->interface; +} + +_public_ const char *sd_bus_message_get_member(sd_bus_message *m) { + assert_return(m, NULL); + + return m->member; +} + +_public_ const char *sd_bus_message_get_destination(sd_bus_message *m) { + assert_return(m, NULL); + + return m->destination; +} + +_public_ const char *sd_bus_message_get_sender(sd_bus_message *m) { + assert_return(m, NULL); + + return m->sender; +} + +_public_ const sd_bus_error *sd_bus_message_get_error(sd_bus_message *m) { + assert_return(m, NULL); + + if (!sd_bus_error_is_set(&m->error)) + return NULL; + + return &m->error; +} + +_public_ int sd_bus_message_get_monotonic_usec(sd_bus_message *m, uint64_t *usec) { + assert_return(m, -EINVAL); + assert_return(usec, -EINVAL); + + if (m->monotonic <= 0) + return -ENODATA; + + *usec = m->monotonic; + return 0; +} + +_public_ int sd_bus_message_get_realtime_usec(sd_bus_message *m, uint64_t *usec) { + assert_return(m, -EINVAL); + assert_return(usec, -EINVAL); + + if (m->realtime <= 0) + return -ENODATA; + + *usec = m->realtime; + return 0; +} + +_public_ int sd_bus_message_get_seqnum(sd_bus_message *m, uint64_t *seqnum) { + assert_return(m, -EINVAL); + assert_return(seqnum, -EINVAL); + + if (m->seqnum <= 0) + return -ENODATA; + + *seqnum = m->seqnum; + return 0; +} + +_public_ sd_bus_creds *sd_bus_message_get_creds(sd_bus_message *m) { + assert_return(m, NULL); + + if (m->creds.mask == 0) + return NULL; + + return &m->creds; +} + +_public_ int sd_bus_message_is_signal( + sd_bus_message *m, + const char *interface, + const char *member) { + + assert_return(m, -EINVAL); + + if (m->header->type != SD_BUS_MESSAGE_SIGNAL) + return 0; + + if (interface && (!m->interface || !streq(m->interface, interface))) + return 0; + + if (member && (!m->member || !streq(m->member, member))) + return 0; + + return 1; +} + +_public_ int sd_bus_message_is_method_call( + sd_bus_message *m, + const char *interface, + const char *member) { + + assert_return(m, -EINVAL); + + if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL) + return 0; + + if (interface && (!m->interface || !streq(m->interface, interface))) + return 0; + + if (member && (!m->member || !streq(m->member, member))) + return 0; + + return 1; +} + +_public_ int sd_bus_message_is_method_error(sd_bus_message *m, const char *name) { + assert_return(m, -EINVAL); + + if (m->header->type != SD_BUS_MESSAGE_METHOD_ERROR) + return 0; + + if (name && (!m->error.name || !streq(m->error.name, name))) + return 0; + + return 1; +} + +_public_ int sd_bus_message_set_expect_reply(sd_bus_message *m, int b) { + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(m->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EPERM); + + SET_FLAG(m->header->flags, BUS_MESSAGE_NO_REPLY_EXPECTED, !b); + + return 0; +} + +_public_ int sd_bus_message_set_auto_start(sd_bus_message *m, int b) { + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + SET_FLAG(m->header->flags, BUS_MESSAGE_NO_AUTO_START, !b); + + return 0; +} + +_public_ int sd_bus_message_set_allow_interactive_authorization(sd_bus_message *m, int b) { + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + SET_FLAG(m->header->flags, BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION, b); + + return 0; +} + +static struct bus_container *message_get_container(sd_bus_message *m) { + assert(m); + + if (m->n_containers == 0) + return &m->root_container; + + assert(m->containers); + return m->containers + m->n_containers - 1; +} + +struct bus_body_part *message_append_part(sd_bus_message *m) { + struct bus_body_part *part; + + assert(m); + + if (m->poisoned) + return NULL; + + if (m->n_body_parts <= 0) { + part = &m->body; + zero(*part); + } else { + assert(m->body_end); + + part = new0(struct bus_body_part, 1); + if (!part) { + m->poisoned = true; + return NULL; + } + + m->body_end->next = part; + } + + part->memfd = -1; + m->body_end = part; + m->n_body_parts++; + + return part; +} + +static void part_zero(struct bus_body_part *part, size_t sz) { + assert(part); + assert(sz > 0); + assert(sz < 8); + + /* All other fields can be left in their defaults */ + assert(!part->data); + assert(part->memfd < 0); + + part->size = sz; + part->is_zero = true; + part->sealed = true; +} + +static int part_make_space( + struct sd_bus_message *m, + struct bus_body_part *part, + size_t sz, + void **q) { + + void *n; + int r; + + assert(m); + assert(part); + assert(!part->sealed); + + if (m->poisoned) + return -ENOMEM; + + if (!part->data && part->memfd < 0) { + part->memfd = bus_kernel_pop_memfd(m->bus, &part->data, &part->mapped, &part->allocated); + part->mmap_begin = part->data; + } + + if (part->memfd >= 0) { + + if (part->allocated == 0 || sz > part->allocated) { + uint64_t new_allocated; + + new_allocated = PAGE_ALIGN(sz > 0 ? 2 * sz : 1); + r = memfd_set_size(part->memfd, new_allocated); + if (r < 0) { + m->poisoned = true; + return r; + } + + part->allocated = new_allocated; + } + + if (!part->data || sz > part->mapped) { + size_t psz; + + psz = PAGE_ALIGN(sz > 0 ? sz : 1); + if (part->mapped <= 0) + n = mmap(NULL, psz, PROT_READ|PROT_WRITE, MAP_SHARED, part->memfd, 0); + else + n = mremap(part->mmap_begin, part->mapped, psz, MREMAP_MAYMOVE); + + if (n == MAP_FAILED) { + m->poisoned = true; + return -errno; + } + + part->mmap_begin = part->data = n; + part->mapped = psz; + part->memfd_offset = 0; + } + + part->munmap_this = true; + } else { + if (part->allocated == 0 || sz > part->allocated) { + size_t new_allocated; + + new_allocated = sz > 0 ? 2 * sz : 64; + n = realloc(part->data, new_allocated); + if (!n) { + m->poisoned = true; + return -ENOMEM; + } + + part->data = n; + part->allocated = new_allocated; + part->free_this = true; + } + } + + if (q) + *q = part->data ? (uint8_t*) part->data + part->size : NULL; + + part->size = sz; + return 0; +} + +static int message_add_offset(sd_bus_message *m, size_t offset) { + struct bus_container *c; + + assert(m); + assert(BUS_MESSAGE_IS_GVARIANT(m)); + + /* Add offset to current container, unless this is the first + * item in it, which will have the 0 offset, which we can + * ignore. */ + c = message_get_container(m); + + if (!c->need_offsets) + return 0; + + if (!GREEDY_REALLOC(c->offsets, c->offsets_allocated, c->n_offsets + 1)) + return -ENOMEM; + + c->offsets[c->n_offsets++] = offset; + return 0; +} + +static void message_extend_containers(sd_bus_message *m, size_t expand) { + struct bus_container *c; + + assert(m); + + if (expand <= 0) + return; + + /* Update counters */ + for (c = m->containers; c < m->containers + m->n_containers; c++) { + + if (c->array_size) + *c->array_size += expand; + } +} + +static void *message_extend_body( + sd_bus_message *m, + size_t align, + size_t sz, + bool add_offset, + bool force_inline) { + + size_t start_body, end_body, padding, added; + void *p; + int r; + + assert(m); + assert(align > 0); + assert(!m->sealed); + + if (m->poisoned) + return NULL; + + start_body = ALIGN_TO((size_t) m->body_size, align); + end_body = start_body + sz; + + padding = start_body - m->body_size; + added = padding + sz; + + /* Check for 32bit overflows */ + if (end_body > (size_t) ((uint32_t) -1) || + end_body < start_body) { + m->poisoned = true; + return NULL; + } + + if (added > 0) { + struct bus_body_part *part = NULL; + bool add_new_part; + + add_new_part = + m->n_body_parts <= 0 || + m->body_end->sealed || + (padding != ALIGN_TO(m->body_end->size, align) - m->body_end->size) || + (force_inline && m->body_end->size > MEMFD_MIN_SIZE); /* if this must be an inlined extension, let's create a new part if the previous part is large enough to be inlined */ + + if (add_new_part) { + if (padding > 0) { + part = message_append_part(m); + if (!part) + return NULL; + + part_zero(part, padding); + } + + part = message_append_part(m); + if (!part) + return NULL; + + r = part_make_space(m, part, sz, &p); + if (r < 0) + return NULL; + } else { + struct bus_container *c; + void *op; + size_t os, start_part, end_part; + + part = m->body_end; + op = part->data; + os = part->size; + + start_part = ALIGN_TO(part->size, align); + end_part = start_part + sz; + + r = part_make_space(m, part, end_part, &p); + if (r < 0) + return NULL; + + if (padding > 0) { + memzero(p, padding); + p = (uint8_t*) p + padding; + } + + /* Readjust pointers */ + for (c = m->containers; c < m->containers + m->n_containers; c++) + c->array_size = adjust_pointer(c->array_size, op, os, part->data); + + m->error.message = (const char*) adjust_pointer(m->error.message, op, os, part->data); + } + } else + /* Return something that is not NULL and is aligned */ + p = (uint8_t *) NULL + align; + + m->body_size = end_body; + message_extend_containers(m, added); + + if (add_offset) { + r = message_add_offset(m, end_body); + if (r < 0) { + m->poisoned = true; + return NULL; + } + } + + return p; +} + +static int message_push_fd(sd_bus_message *m, int fd) { + int *f, copy; + + assert(m); + + if (fd < 0) + return -EINVAL; + + if (!m->allow_fds) + return -EOPNOTSUPP; + + copy = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (copy < 0) + return -errno; + + f = realloc(m->fds, sizeof(int) * (m->n_fds + 1)); + if (!f) { + m->poisoned = true; + safe_close(copy); + return -ENOMEM; + } + + m->fds = f; + m->fds[m->n_fds] = copy; + m->free_fds = true; + + return copy; +} + +int message_append_basic(sd_bus_message *m, char type, const void *p, const void **stored) { + _cleanup_close_ int fd = -1; + struct bus_container *c; + ssize_t align, sz; + void *a; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(bus_type_is_basic(type), -EINVAL); + assert_return(!m->poisoned, -ESTALE); + + c = message_get_container(m); + + if (c->signature && c->signature[c->index]) { + /* Container signature is already set */ + + if (c->signature[c->index] != type) + return -ENXIO; + } else { + char *e; + + /* Maybe we can append to the signature? But only if this is the top-level container */ + if (c->enclosing != 0) + return -ENXIO; + + e = strextend(&c->signature, CHAR_TO_STR(type), NULL); + if (!e) { + m->poisoned = true; + return -ENOMEM; + } + } + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + uint8_t u8; + uint32_t u32; + + switch (type) { + + case SD_BUS_TYPE_SIGNATURE: + case SD_BUS_TYPE_STRING: + p = strempty(p); + + /* Fall through... */ + case SD_BUS_TYPE_OBJECT_PATH: + if (!p) + return -EINVAL; + + align = 1; + sz = strlen(p) + 1; + break; + + case SD_BUS_TYPE_BOOLEAN: + + u8 = p && *(int*) p; + p = &u8; + + align = sz = 1; + break; + + case SD_BUS_TYPE_UNIX_FD: + + if (!p) + return -EINVAL; + + fd = message_push_fd(m, *(int*) p); + if (fd < 0) + return fd; + + u32 = m->n_fds; + p = &u32; + + align = sz = 4; + break; + + default: + align = bus_gvariant_get_alignment(CHAR_TO_STR(type)); + sz = bus_gvariant_get_size(CHAR_TO_STR(type)); + break; + } + + assert(align > 0); + assert(sz > 0); + + a = message_extend_body(m, align, sz, true, false); + if (!a) + return -ENOMEM; + + memcpy(a, p, sz); + + if (stored) + *stored = (const uint8_t*) a; + + } else { + uint32_t u32; + + switch (type) { + + case SD_BUS_TYPE_STRING: + /* To make things easy we'll serialize a NULL string + * into the empty string */ + p = strempty(p); + + /* Fall through... */ + case SD_BUS_TYPE_OBJECT_PATH: + + if (!p) + return -EINVAL; + + align = 4; + sz = 4 + strlen(p) + 1; + break; + + case SD_BUS_TYPE_SIGNATURE: + + p = strempty(p); + + align = 1; + sz = 1 + strlen(p) + 1; + break; + + case SD_BUS_TYPE_BOOLEAN: + + u32 = p && *(int*) p; + p = &u32; + + align = sz = 4; + break; + + case SD_BUS_TYPE_UNIX_FD: + + if (!p) + return -EINVAL; + + fd = message_push_fd(m, *(int*) p); + if (fd < 0) + return fd; + + u32 = m->n_fds; + p = &u32; + + align = sz = 4; + break; + + default: + align = bus_type_get_alignment(type); + sz = bus_type_get_size(type); + break; + } + + assert(align > 0); + assert(sz > 0); + + a = message_extend_body(m, align, sz, false, false); + if (!a) + return -ENOMEM; + + if (type == SD_BUS_TYPE_STRING || type == SD_BUS_TYPE_OBJECT_PATH) { + *(uint32_t*) a = sz - 5; + memcpy((uint8_t*) a + 4, p, sz - 4); + + if (stored) + *stored = (const uint8_t*) a + 4; + + } else if (type == SD_BUS_TYPE_SIGNATURE) { + *(uint8_t*) a = sz - 2; + memcpy((uint8_t*) a + 1, p, sz - 1); + + if (stored) + *stored = (const uint8_t*) a + 1; + } else { + memcpy(a, p, sz); + + if (stored) + *stored = a; + } + } + + if (type == SD_BUS_TYPE_UNIX_FD) + m->n_fds++; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index++; + + fd = -1; + return 0; +} + +_public_ int sd_bus_message_append_basic(sd_bus_message *m, char type, const void *p) { + return message_append_basic(m, type, p, NULL); +} + +_public_ int sd_bus_message_append_string_space( + sd_bus_message *m, + size_t size, + char **s) { + + struct bus_container *c; + void *a; + + assert_return(m, -EINVAL); + assert_return(s, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(!m->poisoned, -ESTALE); + + c = message_get_container(m); + + if (c->signature && c->signature[c->index]) { + /* Container signature is already set */ + + if (c->signature[c->index] != SD_BUS_TYPE_STRING) + return -ENXIO; + } else { + char *e; + + /* Maybe we can append to the signature? But only if this is the top-level container */ + if (c->enclosing != 0) + return -ENXIO; + + e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_STRING), NULL); + if (!e) { + m->poisoned = true; + return -ENOMEM; + } + } + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + a = message_extend_body(m, 1, size + 1, true, false); + if (!a) + return -ENOMEM; + + *s = a; + } else { + a = message_extend_body(m, 4, 4 + size + 1, false, false); + if (!a) + return -ENOMEM; + + *(uint32_t*) a = size; + *s = (char*) a + 4; + } + + (*s)[size] = 0; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index++; + + return 0; +} + +_public_ int sd_bus_message_append_string_iovec( + sd_bus_message *m, + const struct iovec *iov, + unsigned n) { + + size_t size; + unsigned i; + char *p; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(iov || n == 0, -EINVAL); + assert_return(!m->poisoned, -ESTALE); + + size = IOVEC_TOTAL_SIZE(iov, n); + + r = sd_bus_message_append_string_space(m, size, &p); + if (r < 0) + return r; + + for (i = 0; i < n; i++) { + + if (iov[i].iov_base) + memcpy(p, iov[i].iov_base, iov[i].iov_len); + else + memset(p, ' ', iov[i].iov_len); + + p += iov[i].iov_len; + } + + return 0; +} + +static int bus_message_open_array( + sd_bus_message *m, + struct bus_container *c, + const char *contents, + uint32_t **array_size, + size_t *begin, + bool *need_offsets) { + + unsigned nindex; + int alignment, r; + + assert(m); + assert(c); + assert(contents); + assert(array_size); + assert(begin); + assert(need_offsets); + + if (!signature_is_single(contents, true)) + return -EINVAL; + + if (c->signature && c->signature[c->index]) { + + /* Verify the existing signature */ + + if (c->signature[c->index] != SD_BUS_TYPE_ARRAY) + return -ENXIO; + + if (!startswith(c->signature + c->index + 1, contents)) + return -ENXIO; + + nindex = c->index + 1 + strlen(contents); + } else { + char *e; + + if (c->enclosing != 0) + return -ENXIO; + + /* Extend the existing signature */ + + e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_ARRAY), contents, NULL); + if (!e) { + m->poisoned = true; + return -ENOMEM; + } + + nindex = e - c->signature; + } + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + alignment = bus_gvariant_get_alignment(contents); + if (alignment < 0) + return alignment; + + /* Add alignment padding and add to offset list */ + if (!message_extend_body(m, alignment, 0, false, false)) + return -ENOMEM; + + r = bus_gvariant_is_fixed_size(contents); + if (r < 0) + return r; + + *begin = m->body_size; + *need_offsets = r == 0; + } else { + void *a, *op; + size_t os; + struct bus_body_part *o; + + alignment = bus_type_get_alignment(contents[0]); + if (alignment < 0) + return alignment; + + a = message_extend_body(m, 4, 4, false, false); + if (!a) + return -ENOMEM; + + o = m->body_end; + op = m->body_end->data; + os = m->body_end->size; + + /* Add alignment between size and first element */ + if (!message_extend_body(m, alignment, 0, false, false)) + return -ENOMEM; + + /* location of array size might have changed so let's readjust a */ + if (o == m->body_end) + a = adjust_pointer(a, op, os, m->body_end->data); + + *(uint32_t*) a = 0; + *array_size = a; + } + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index = nindex; + + return 0; +} + +static int bus_message_open_variant( + sd_bus_message *m, + struct bus_container *c, + const char *contents) { + + assert(m); + assert(c); + assert(contents); + + if (!signature_is_single(contents, false)) + return -EINVAL; + + if (*contents == SD_BUS_TYPE_DICT_ENTRY_BEGIN) + return -EINVAL; + + if (c->signature && c->signature[c->index]) { + + if (c->signature[c->index] != SD_BUS_TYPE_VARIANT) + return -ENXIO; + + } else { + char *e; + + if (c->enclosing != 0) + return -ENXIO; + + e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_VARIANT), NULL); + if (!e) { + m->poisoned = true; + return -ENOMEM; + } + } + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + /* Variants are always aligned to 8 */ + + if (!message_extend_body(m, 8, 0, false, false)) + return -ENOMEM; + + } else { + size_t l; + void *a; + + l = strlen(contents); + a = message_extend_body(m, 1, 1 + l + 1, false, false); + if (!a) + return -ENOMEM; + + *(uint8_t*) a = l; + memcpy((uint8_t*) a + 1, contents, l + 1); + } + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index++; + + return 0; +} + +static int bus_message_open_struct( + sd_bus_message *m, + struct bus_container *c, + const char *contents, + size_t *begin, + bool *need_offsets) { + + size_t nindex; + int r; + + assert(m); + assert(c); + assert(contents); + assert(begin); + assert(need_offsets); + + if (!signature_is_valid(contents, false)) + return -EINVAL; + + if (c->signature && c->signature[c->index]) { + size_t l; + + l = strlen(contents); + + if (c->signature[c->index] != SD_BUS_TYPE_STRUCT_BEGIN || + !startswith(c->signature + c->index + 1, contents) || + c->signature[c->index + 1 + l] != SD_BUS_TYPE_STRUCT_END) + return -ENXIO; + + nindex = c->index + 1 + l + 1; + } else { + char *e; + + if (c->enclosing != 0) + return -ENXIO; + + e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_STRUCT_BEGIN), contents, CHAR_TO_STR(SD_BUS_TYPE_STRUCT_END), NULL); + if (!e) { + m->poisoned = true; + return -ENOMEM; + } + + nindex = e - c->signature; + } + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + int alignment; + + alignment = bus_gvariant_get_alignment(contents); + if (alignment < 0) + return alignment; + + if (!message_extend_body(m, alignment, 0, false, false)) + return -ENOMEM; + + r = bus_gvariant_is_fixed_size(contents); + if (r < 0) + return r; + + *begin = m->body_size; + *need_offsets = r == 0; + } else { + /* Align contents to 8 byte boundary */ + if (!message_extend_body(m, 8, 0, false, false)) + return -ENOMEM; + } + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index = nindex; + + return 0; +} + +static int bus_message_open_dict_entry( + sd_bus_message *m, + struct bus_container *c, + const char *contents, + size_t *begin, + bool *need_offsets) { + + int r; + + assert(m); + assert(c); + assert(contents); + assert(begin); + assert(need_offsets); + + if (!signature_is_pair(contents)) + return -EINVAL; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + return -ENXIO; + + if (c->signature && c->signature[c->index]) { + size_t l; + + l = strlen(contents); + + if (c->signature[c->index] != SD_BUS_TYPE_DICT_ENTRY_BEGIN || + !startswith(c->signature + c->index + 1, contents) || + c->signature[c->index + 1 + l] != SD_BUS_TYPE_DICT_ENTRY_END) + return -ENXIO; + } else + return -ENXIO; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + int alignment; + + alignment = bus_gvariant_get_alignment(contents); + if (alignment < 0) + return alignment; + + if (!message_extend_body(m, alignment, 0, false, false)) + return -ENOMEM; + + r = bus_gvariant_is_fixed_size(contents); + if (r < 0) + return r; + + *begin = m->body_size; + *need_offsets = r == 0; + } else { + /* Align contents to 8 byte boundary */ + if (!message_extend_body(m, 8, 0, false, false)) + return -ENOMEM; + } + + return 0; +} + +_public_ int sd_bus_message_open_container( + sd_bus_message *m, + char type, + const char *contents) { + + struct bus_container *c, *w; + uint32_t *array_size = NULL; + char *signature; + size_t before, begin = 0; + bool need_offsets = false; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(contents, -EINVAL); + assert_return(!m->poisoned, -ESTALE); + + /* Make sure we have space for one more container */ + if (!GREEDY_REALLOC(m->containers, m->containers_allocated, m->n_containers + 1)) { + m->poisoned = true; + return -ENOMEM; + } + + c = message_get_container(m); + + signature = strdup(contents); + if (!signature) { + m->poisoned = true; + return -ENOMEM; + } + + /* Save old index in the parent container, in case we have to + * abort this container */ + c->saved_index = c->index; + before = m->body_size; + + if (type == SD_BUS_TYPE_ARRAY) + r = bus_message_open_array(m, c, contents, &array_size, &begin, &need_offsets); + else if (type == SD_BUS_TYPE_VARIANT) + r = bus_message_open_variant(m, c, contents); + else if (type == SD_BUS_TYPE_STRUCT) + r = bus_message_open_struct(m, c, contents, &begin, &need_offsets); + else if (type == SD_BUS_TYPE_DICT_ENTRY) + r = bus_message_open_dict_entry(m, c, contents, &begin, &need_offsets); + else + r = -EINVAL; + + if (r < 0) { + free(signature); + return r; + } + + /* OK, let's fill it in */ + w = m->containers + m->n_containers++; + w->enclosing = type; + w->signature = signature; + w->index = 0; + w->array_size = array_size; + w->before = before; + w->begin = begin; + w->n_offsets = w->offsets_allocated = 0; + w->offsets = NULL; + w->need_offsets = need_offsets; + + return 0; +} + +static int bus_message_close_array(sd_bus_message *m, struct bus_container *c) { + + assert(m); + assert(c); + + if (!BUS_MESSAGE_IS_GVARIANT(m)) + return 0; + + if (c->need_offsets) { + size_t payload, sz, i; + uint8_t *a; + + /* Variable-width arrays */ + + payload = c->n_offsets > 0 ? c->offsets[c->n_offsets-1] - c->begin : 0; + sz = bus_gvariant_determine_word_size(payload, c->n_offsets); + + a = message_extend_body(m, 1, sz * c->n_offsets, true, false); + if (!a) + return -ENOMEM; + + for (i = 0; i < c->n_offsets; i++) + bus_gvariant_write_word_le(a + sz*i, sz, c->offsets[i] - c->begin); + } else { + void *a; + + /* Fixed-width or empty arrays */ + + a = message_extend_body(m, 1, 0, true, false); /* let's add offset to parent */ + if (!a) + return -ENOMEM; + } + + return 0; +} + +static int bus_message_close_variant(sd_bus_message *m, struct bus_container *c) { + uint8_t *a; + size_t l; + + assert(m); + assert(c); + assert(c->signature); + + if (!BUS_MESSAGE_IS_GVARIANT(m)) + return 0; + + l = strlen(c->signature); + + a = message_extend_body(m, 1, 1 + l, true, false); + if (!a) + return -ENOMEM; + + a[0] = 0; + memcpy(a+1, c->signature, l); + + return 0; +} + +static int bus_message_close_struct(sd_bus_message *m, struct bus_container *c, bool add_offset) { + bool fixed_size = true; + size_t n_variable = 0; + unsigned i = 0; + const char *p; + uint8_t *a; + int r; + + assert(m); + assert(c); + + if (!BUS_MESSAGE_IS_GVARIANT(m)) + return 0; + + p = strempty(c->signature); + while (*p != 0) { + size_t n; + + r = signature_element_length(p, &n); + if (r < 0) + return r; + else { + char t[n+1]; + + memcpy(t, p, n); + t[n] = 0; + + r = bus_gvariant_is_fixed_size(t); + if (r < 0) + return r; + } + + assert(!c->need_offsets || i <= c->n_offsets); + + /* We need to add an offset for each item that has a + * variable size and that is not the last one in the + * list */ + if (r == 0) + fixed_size = false; + if (r == 0 && p[n] != 0) + n_variable++; + + i++; + p += n; + } + + assert(!c->need_offsets || i == c->n_offsets); + assert(c->need_offsets || n_variable == 0); + + if (isempty(c->signature)) { + /* The unary type is encoded as fixed 1 byte padding */ + a = message_extend_body(m, 1, 1, add_offset, false); + if (!a) + return -ENOMEM; + + *a = 0; + } else if (n_variable <= 0) { + int alignment = 1; + + /* Structures with fixed-size members only have to be + * fixed-size themselves. But gvariant requires all fixed-size + * elements to be sized a multiple of their alignment. Hence, + * we must *always* add final padding after the last member so + * the overall size of the structure is properly aligned. */ + if (fixed_size) + alignment = bus_gvariant_get_alignment(strempty(c->signature)); + + assert(alignment > 0); + + a = message_extend_body(m, alignment, 0, add_offset, false); + if (!a) + return -ENOMEM; + } else { + size_t sz; + unsigned j; + + assert(c->offsets[c->n_offsets-1] == m->body_size); + + sz = bus_gvariant_determine_word_size(m->body_size - c->begin, n_variable); + + a = message_extend_body(m, 1, sz * n_variable, add_offset, false); + if (!a) + return -ENOMEM; + + p = strempty(c->signature); + for (i = 0, j = 0; i < c->n_offsets; i++) { + unsigned k; + size_t n; + + r = signature_element_length(p, &n); + if (r < 0) + return r; + else { + char t[n+1]; + + memcpy(t, p, n); + t[n] = 0; + + p += n; + + r = bus_gvariant_is_fixed_size(t); + if (r < 0) + return r; + if (r > 0 || p[0] == 0) + continue; + } + + k = n_variable - 1 - j; + + bus_gvariant_write_word_le(a + k * sz, sz, c->offsets[i] - c->begin); + + j++; + } + } + + return 0; +} + +_public_ int sd_bus_message_close_container(sd_bus_message *m) { + struct bus_container *c; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(m->n_containers > 0, -EINVAL); + assert_return(!m->poisoned, -ESTALE); + + c = message_get_container(m); + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + if (c->signature && c->signature[c->index] != 0) + return -EINVAL; + + m->n_containers--; + + if (c->enclosing == SD_BUS_TYPE_ARRAY) + r = bus_message_close_array(m, c); + else if (c->enclosing == SD_BUS_TYPE_VARIANT) + r = bus_message_close_variant(m, c); + else if (c->enclosing == SD_BUS_TYPE_STRUCT || c->enclosing == SD_BUS_TYPE_DICT_ENTRY) + r = bus_message_close_struct(m, c, true); + else + assert_not_reached("Unknown container type"); + + free(c->signature); + free(c->offsets); + + return r; +} + +typedef struct { + const char *types; + unsigned n_struct; + unsigned n_array; +} TypeStack; + +static int type_stack_push(TypeStack *stack, unsigned max, unsigned *i, const char *types, unsigned n_struct, unsigned n_array) { + assert(stack); + assert(max > 0); + + if (*i >= max) + return -EINVAL; + + stack[*i].types = types; + stack[*i].n_struct = n_struct; + stack[*i].n_array = n_array; + (*i)++; + + return 0; +} + +static int type_stack_pop(TypeStack *stack, unsigned max, unsigned *i, const char **types, unsigned *n_struct, unsigned *n_array) { + assert(stack); + assert(max > 0); + assert(types); + assert(n_struct); + assert(n_array); + + if (*i <= 0) + return 0; + + (*i)--; + *types = stack[*i].types; + *n_struct = stack[*i].n_struct; + *n_array = stack[*i].n_array; + + return 1; +} + +int bus_message_append_ap( + sd_bus_message *m, + const char *types, + va_list ap) { + + unsigned n_array, n_struct; + TypeStack stack[BUS_CONTAINER_DEPTH]; + unsigned stack_ptr = 0; + int r; + + assert(m); + + if (!types) + return 0; + + n_array = (unsigned) -1; + n_struct = strlen(types); + + for (;;) { + const char *t; + + if (n_array == 0 || (n_array == (unsigned) -1 && n_struct == 0)) { + r = type_stack_pop(stack, ELEMENTSOF(stack), &stack_ptr, &types, &n_struct, &n_array); + if (r < 0) + return r; + if (r == 0) + break; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + continue; + } + + t = types; + if (n_array != (unsigned) -1) + n_array--; + else { + types++; + n_struct--; + } + + switch (*t) { + + case SD_BUS_TYPE_BYTE: { + uint8_t x; + + x = (uint8_t) va_arg(ap, int); + r = sd_bus_message_append_basic(m, *t, &x); + break; + } + + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_UNIX_FD: { + uint32_t x; + + /* We assume a boolean is the same as int32_t */ + assert_cc(sizeof(int32_t) == sizeof(int)); + + x = va_arg(ap, uint32_t); + r = sd_bus_message_append_basic(m, *t, &x); + break; + } + + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: { + uint16_t x; + + x = (uint16_t) va_arg(ap, int); + r = sd_bus_message_append_basic(m, *t, &x); + break; + } + + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: { + uint64_t x; + + x = va_arg(ap, uint64_t); + r = sd_bus_message_append_basic(m, *t, &x); + break; + } + + case SD_BUS_TYPE_DOUBLE: { + double x; + + x = va_arg(ap, double); + r = sd_bus_message_append_basic(m, *t, &x); + break; + } + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: { + const char *x; + + x = va_arg(ap, const char*); + r = sd_bus_message_append_basic(m, *t, x); + break; + } + + case SD_BUS_TYPE_ARRAY: { + size_t k; + + r = signature_element_length(t + 1, &k); + if (r < 0) + return r; + + { + char s[k + 1]; + memcpy(s, t + 1, k); + s[k] = 0; + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, s); + if (r < 0) + return r; + } + + if (n_array == (unsigned) -1) { + types += k; + n_struct -= k; + } + + r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); + if (r < 0) + return r; + + types = t + 1; + n_struct = k; + n_array = va_arg(ap, unsigned); + + break; + } + + case SD_BUS_TYPE_VARIANT: { + const char *s; + + s = va_arg(ap, const char*); + if (!s) + return -EINVAL; + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT, s); + if (r < 0) + return r; + + r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); + if (r < 0) + return r; + + types = s; + n_struct = strlen(s); + n_array = (unsigned) -1; + + break; + } + + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { + size_t k; + + r = signature_element_length(t, &k); + if (r < 0) + return r; + + { + char s[k - 1]; + + memcpy(s, t + 1, k - 2); + s[k - 2] = 0; + + r = sd_bus_message_open_container(m, *t == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s); + if (r < 0) + return r; + } + + if (n_array == (unsigned) -1) { + types += k - 1; + n_struct -= k - 1; + } + + r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); + if (r < 0) + return r; + + types = t + 1; + n_struct = k - 2; + n_array = (unsigned) -1; + + break; + } + + default: + r = -EINVAL; + } + + if (r < 0) + return r; + } + + return 1; +} + +_public_ int sd_bus_message_append(sd_bus_message *m, const char *types, ...) { + va_list ap; + int r; + + assert_return(m, -EINVAL); + assert_return(types, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(!m->poisoned, -ESTALE); + + va_start(ap, types); + r = bus_message_append_ap(m, types, ap); + va_end(ap); + + return r; +} + +_public_ int sd_bus_message_append_array_space( + sd_bus_message *m, + char type, + size_t size, + void **ptr) { + + ssize_t align, sz; + void *a; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(bus_type_is_trivial(type) && type != SD_BUS_TYPE_BOOLEAN, -EINVAL); + assert_return(ptr || size == 0, -EINVAL); + assert_return(!m->poisoned, -ESTALE); + + /* alignment and size of the trivial types (except bool) is + * identical for gvariant and dbus1 marshalling */ + align = bus_type_get_alignment(type); + sz = bus_type_get_size(type); + + assert_se(align > 0); + assert_se(sz > 0); + + if (size % sz != 0) + return -EINVAL; + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, CHAR_TO_STR(type)); + if (r < 0) + return r; + + a = message_extend_body(m, align, size, false, false); + if (!a) + return -ENOMEM; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + *ptr = a; + return 0; +} + +_public_ int sd_bus_message_append_array( + sd_bus_message *m, + char type, + const void *ptr, + size_t size) { + int r; + void *p; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(bus_type_is_trivial(type), -EINVAL); + assert_return(ptr || size == 0, -EINVAL); + assert_return(!m->poisoned, -ESTALE); + + r = sd_bus_message_append_array_space(m, type, size, &p); + if (r < 0) + return r; + + memcpy_safe(p, ptr, size); + + return 0; +} + +_public_ int sd_bus_message_append_array_iovec( + sd_bus_message *m, + char type, + const struct iovec *iov, + unsigned n) { + + size_t size; + unsigned i; + void *p; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(bus_type_is_trivial(type), -EINVAL); + assert_return(iov || n == 0, -EINVAL); + assert_return(!m->poisoned, -ESTALE); + + size = IOVEC_TOTAL_SIZE(iov, n); + + r = sd_bus_message_append_array_space(m, type, size, &p); + if (r < 0) + return r; + + for (i = 0; i < n; i++) { + + if (iov[i].iov_base) + memcpy(p, iov[i].iov_base, iov[i].iov_len); + else + memzero(p, iov[i].iov_len); + + p = (uint8_t*) p + iov[i].iov_len; + } + + return 0; +} + +_public_ int sd_bus_message_append_array_memfd( + sd_bus_message *m, + char type, + int memfd, + uint64_t offset, + uint64_t size) { + + _cleanup_close_ int copy_fd = -1; + struct bus_body_part *part; + ssize_t align, sz; + uint64_t real_size; + void *a; + int r; + + assert_return(m, -EINVAL); + assert_return(memfd >= 0, -EBADF); + assert_return(bus_type_is_trivial(type), -EINVAL); + assert_return(size > 0, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(!m->poisoned, -ESTALE); + + r = memfd_set_sealed(memfd); + if (r < 0) + return r; + + copy_fd = dup(memfd); + if (copy_fd < 0) + return copy_fd; + + r = memfd_get_size(memfd, &real_size); + if (r < 0) + return r; + + if (offset == 0 && size == (uint64_t) -1) + size = real_size; + else if (offset + size > real_size) + return -EMSGSIZE; + + align = bus_type_get_alignment(type); + sz = bus_type_get_size(type); + + assert_se(align > 0); + assert_se(sz > 0); + + if (offset % align != 0) + return -EINVAL; + + if (size % sz != 0) + return -EINVAL; + + if (size > (uint64_t) (uint32_t) -1) + return -EINVAL; + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, CHAR_TO_STR(type)); + if (r < 0) + return r; + + a = message_extend_body(m, align, 0, false, false); + if (!a) + return -ENOMEM; + + part = message_append_part(m); + if (!part) + return -ENOMEM; + + part->memfd = copy_fd; + part->memfd_offset = offset; + part->sealed = true; + part->size = size; + copy_fd = -1; + + m->body_size += size; + message_extend_containers(m, size); + + return sd_bus_message_close_container(m); +} + +_public_ int sd_bus_message_append_string_memfd( + sd_bus_message *m, + int memfd, + uint64_t offset, + uint64_t size) { + + _cleanup_close_ int copy_fd = -1; + struct bus_body_part *part; + struct bus_container *c; + uint64_t real_size; + void *a; + int r; + + assert_return(m, -EINVAL); + assert_return(memfd >= 0, -EBADF); + assert_return(size > 0, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(!m->poisoned, -ESTALE); + + r = memfd_set_sealed(memfd); + if (r < 0) + return r; + + copy_fd = dup(memfd); + if (copy_fd < 0) + return copy_fd; + + r = memfd_get_size(memfd, &real_size); + if (r < 0) + return r; + + if (offset == 0 && size == (uint64_t) -1) + size = real_size; + else if (offset + size > real_size) + return -EMSGSIZE; + + /* We require this to be NUL terminated */ + if (size == 0) + return -EINVAL; + + if (size > (uint64_t) (uint32_t) -1) + return -EINVAL; + + c = message_get_container(m); + if (c->signature && c->signature[c->index]) { + /* Container signature is already set */ + + if (c->signature[c->index] != SD_BUS_TYPE_STRING) + return -ENXIO; + } else { + char *e; + + /* Maybe we can append to the signature? But only if this is the top-level container */ + if (c->enclosing != 0) + return -ENXIO; + + e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_STRING), NULL); + if (!e) { + m->poisoned = true; + return -ENOMEM; + } + } + + if (!BUS_MESSAGE_IS_GVARIANT(m)) { + a = message_extend_body(m, 4, 4, false, false); + if (!a) + return -ENOMEM; + + *(uint32_t*) a = size - 1; + } + + part = message_append_part(m); + if (!part) + return -ENOMEM; + + part->memfd = copy_fd; + part->memfd_offset = offset; + part->sealed = true; + part->size = size; + copy_fd = -1; + + m->body_size += size; + message_extend_containers(m, size); + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + r = message_add_offset(m, m->body_size); + if (r < 0) { + m->poisoned = true; + return -ENOMEM; + } + } + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index++; + + return 0; +} + +_public_ int sd_bus_message_append_strv(sd_bus_message *m, char **l) { + char **i; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(!m->poisoned, -ESTALE); + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return r; + + STRV_FOREACH(i, l) { + r = sd_bus_message_append_basic(m, 's', *i); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(m); +} + +static int bus_message_close_header(sd_bus_message *m) { + + assert(m); + + /* The actual user data is finished now, we just complete the + variant and struct now (at least on gvariant). Remember + this position, so that during parsing we know where to to + put the outer container end. */ + m->user_body_size = m->body_size; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + const char *signature; + size_t sz, l; + void *d; + + /* Add offset table to end of fields array */ + if (m->n_header_offsets >= 1) { + uint8_t *a; + unsigned i; + + assert(m->fields_size == m->header_offsets[m->n_header_offsets-1]); + + sz = bus_gvariant_determine_word_size(m->fields_size, m->n_header_offsets); + a = message_extend_fields(m, 1, sz * m->n_header_offsets, false); + if (!a) + return -ENOMEM; + + for (i = 0; i < m->n_header_offsets; i++) + bus_gvariant_write_word_le(a + sz*i, sz, m->header_offsets[i]); + } + + /* Add gvariant NUL byte plus signature to the end of + * the body, followed by the final offset pointing to + * the end of the fields array */ + + signature = strempty(m->root_container.signature); + l = strlen(signature); + + sz = bus_gvariant_determine_word_size(sizeof(struct bus_header) + ALIGN8(m->fields_size) + m->body_size + 1 + l + 2, 1); + d = message_extend_body(m, 1, 1 + l + 2 + sz, false, true); + if (!d) + return -ENOMEM; + + *(uint8_t*) d = 0; + *((uint8_t*) d + 1) = SD_BUS_TYPE_STRUCT_BEGIN; + memcpy((uint8_t*) d + 2, signature, l); + *((uint8_t*) d + 1 + l + 1) = SD_BUS_TYPE_STRUCT_END; + + bus_gvariant_write_word_le((uint8_t*) d + 1 + l + 2, sz, sizeof(struct bus_header) + m->fields_size); + + m->footer = d; + m->footer_accessible = 1 + l + 2 + sz; + } else { + m->header->dbus1.fields_size = m->fields_size; + m->header->dbus1.body_size = m->body_size; + } + + return 0; +} + +int bus_message_seal(sd_bus_message *m, uint64_t cookie, usec_t timeout) { + struct bus_body_part *part; + size_t a; + unsigned i; + int r; + + assert(m); + + if (m->sealed) + return -EPERM; + + if (m->n_containers > 0) + return -EBADMSG; + + if (m->poisoned) + return -ESTALE; + + if (cookie > 0xffffffffULL && + !BUS_MESSAGE_IS_GVARIANT(m)) + return -EOPNOTSUPP; + + /* In vtables the return signature of method calls is listed, + * let's check if they match if this is a response */ + if (m->header->type == SD_BUS_MESSAGE_METHOD_RETURN && + m->enforced_reply_signature && + !streq(strempty(m->root_container.signature), m->enforced_reply_signature)) + return -ENOMSG; + + /* If gvariant marshalling is used we need to close the body structure */ + r = bus_message_close_struct(m, &m->root_container, false); + if (r < 0) + return r; + + /* If there's a non-trivial signature set, then add it in + * here, but only on dbus1 */ + if (!isempty(m->root_container.signature) && !BUS_MESSAGE_IS_GVARIANT(m)) { + r = message_append_field_signature(m, BUS_MESSAGE_HEADER_SIGNATURE, m->root_container.signature, NULL); + if (r < 0) + return r; + } + + if (m->n_fds > 0) { + r = message_append_field_uint32(m, BUS_MESSAGE_HEADER_UNIX_FDS, m->n_fds); + if (r < 0) + return r; + } + + r = bus_message_close_header(m); + if (r < 0) + return r; + + if (BUS_MESSAGE_IS_GVARIANT(m)) + m->header->dbus2.cookie = cookie; + else + m->header->dbus1.serial = (uint32_t) cookie; + + m->timeout = m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED ? 0 : timeout; + + /* Add padding at the end of the fields part, since we know + * the body needs to start at an 8 byte alignment. We made + * sure we allocated enough space for this, so all we need to + * do here is to zero it out. */ + a = ALIGN8(m->fields_size) - m->fields_size; + if (a > 0) + memzero((uint8_t*) BUS_MESSAGE_FIELDS(m) + m->fields_size, a); + + /* If this is something we can send as memfd, then let's seal + the memfd now. Note that we can send memfds as payload only + for directed messages, and not for broadcasts. */ + if (m->destination && m->bus->use_memfd) { + MESSAGE_FOREACH_PART(part, i, m) + if (part->memfd >= 0 && + !part->sealed && + (part->size > MEMFD_MIN_SIZE || m->bus->use_memfd < 0) && + part != m->body_end) { /* The last part may never be sent as memfd */ + uint64_t sz; + + /* Try to seal it if that makes + * sense. First, unmap our own map to + * make sure we don't keep it busy. */ + bus_body_part_unmap(part); + + /* Then, sync up real memfd size */ + sz = part->size; + r = memfd_set_size(part->memfd, sz); + if (r < 0) + return r; + + /* Finally, try to seal */ + if (memfd_set_sealed(part->memfd) >= 0) + part->sealed = true; + } + } + + m->root_container.end = m->user_body_size; + m->root_container.index = 0; + m->root_container.offset_index = 0; + m->root_container.item_size = m->root_container.n_offsets > 0 ? m->root_container.offsets[0] : 0; + + m->sealed = true; + + return 0; +} + +int bus_body_part_map(struct bus_body_part *part) { + void *p; + size_t psz, shift; + + assert_se(part); + + if (part->data) + return 0; + + if (part->size <= 0) + return 0; + + /* For smaller zero parts (as used for padding) we don't need to map anything... */ + if (part->memfd < 0 && part->is_zero && part->size < 8) { + static const uint8_t zeroes[7] = { }; + part->data = (void*) zeroes; + return 0; + } + + shift = part->memfd_offset - ((part->memfd_offset / page_size()) * page_size()); + psz = PAGE_ALIGN(part->size + shift); + + if (part->memfd >= 0) + p = mmap(NULL, psz, PROT_READ, MAP_PRIVATE, part->memfd, part->memfd_offset - shift); + else if (part->is_zero) + p = mmap(NULL, psz, PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + else + return -EINVAL; + + if (p == MAP_FAILED) + return -errno; + + part->mapped = psz; + part->mmap_begin = p; + part->data = (uint8_t*) p + shift; + part->munmap_this = true; + + return 0; +} + +void bus_body_part_unmap(struct bus_body_part *part) { + + assert_se(part); + + if (part->memfd < 0) + return; + + if (!part->mmap_begin) + return; + + if (!part->munmap_this) + return; + + assert_se(munmap(part->mmap_begin, part->mapped) == 0); + + part->mmap_begin = NULL; + part->data = NULL; + part->mapped = 0; + part->munmap_this = false; + + return; +} + +static int buffer_peek(const void *p, uint32_t sz, size_t *rindex, size_t align, size_t nbytes, void **r) { + size_t k, start, end; + + assert(rindex); + assert(align > 0); + + start = ALIGN_TO((size_t) *rindex, align); + end = start + nbytes; + + if (end > sz) + return -EBADMSG; + + /* Verify that padding is 0 */ + for (k = *rindex; k < start; k++) + if (((const uint8_t*) p)[k] != 0) + return -EBADMSG; + + if (r) + *r = (uint8_t*) p + start; + + *rindex = end; + + return 1; +} + +static bool message_end_of_signature(sd_bus_message *m) { + struct bus_container *c; + + assert(m); + + c = message_get_container(m); + return !c->signature || c->signature[c->index] == 0; +} + +static bool message_end_of_array(sd_bus_message *m, size_t index) { + struct bus_container *c; + + assert(m); + + c = message_get_container(m); + if (c->enclosing != SD_BUS_TYPE_ARRAY) + return false; + + if (BUS_MESSAGE_IS_GVARIANT(m)) + return index >= c->end; + else { + assert(c->array_size); + return index >= c->begin + BUS_MESSAGE_BSWAP32(m, *c->array_size); + } +} + +_public_ int sd_bus_message_at_end(sd_bus_message *m, int complete) { + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + + if (complete && m->n_containers > 0) + return false; + + if (message_end_of_signature(m)) + return true; + + if (message_end_of_array(m, m->rindex)) + return true; + + return false; +} + +static struct bus_body_part* find_part(sd_bus_message *m, size_t index, size_t sz, void **p) { + struct bus_body_part *part; + size_t begin; + int r; + + assert(m); + + if (m->cached_rindex_part && index >= m->cached_rindex_part_begin) { + part = m->cached_rindex_part; + begin = m->cached_rindex_part_begin; + } else { + part = &m->body; + begin = 0; + } + + while (part) { + if (index < begin) + return NULL; + + if (index + sz <= begin + part->size) { + + r = bus_body_part_map(part); + if (r < 0) + return NULL; + + if (p) + *p = (uint8_t*) part->data + index - begin; + + m->cached_rindex_part = part; + m->cached_rindex_part_begin = begin; + + return part; + } + + begin += part->size; + part = part->next; + } + + return NULL; +} + +static int container_next_item(sd_bus_message *m, struct bus_container *c, size_t *rindex) { + int r; + + assert(m); + assert(c); + assert(rindex); + + if (!BUS_MESSAGE_IS_GVARIANT(m)) + return 0; + + if (c->enclosing == SD_BUS_TYPE_ARRAY) { + int sz; + + sz = bus_gvariant_get_size(c->signature); + if (sz < 0) { + int alignment; + + if (c->offset_index+1 >= c->n_offsets) + goto end; + + /* Variable-size array */ + + alignment = bus_gvariant_get_alignment(c->signature); + assert(alignment > 0); + + *rindex = ALIGN_TO(c->offsets[c->offset_index], alignment); + c->item_size = c->offsets[c->offset_index+1] - *rindex; + } else { + + if (c->offset_index+1 >= (c->end-c->begin)/sz) + goto end; + + /* Fixed-size array */ + *rindex = c->begin + (c->offset_index+1) * sz; + c->item_size = sz; + } + + c->offset_index++; + + } else if (c->enclosing == 0 || + c->enclosing == SD_BUS_TYPE_STRUCT || + c->enclosing == SD_BUS_TYPE_DICT_ENTRY) { + + int alignment; + size_t n, j; + + if (c->offset_index+1 >= c->n_offsets) + goto end; + + r = signature_element_length(c->signature + c->index, &n); + if (r < 0) + return r; + + r = signature_element_length(c->signature + c->index + n, &j); + if (r < 0) + return r; + else { + char t[j+1]; + memcpy(t, c->signature + c->index + n, j); + t[j] = 0; + + alignment = bus_gvariant_get_alignment(t); + } + + assert(alignment > 0); + + *rindex = ALIGN_TO(c->offsets[c->offset_index], alignment); + c->item_size = c->offsets[c->offset_index+1] - *rindex; + + c->offset_index++; + + } else if (c->enclosing == SD_BUS_TYPE_VARIANT) + goto end; + else + assert_not_reached("Unknown container type"); + + return 0; + +end: + /* Reached the end */ + *rindex = c->end; + c->item_size = 0; + return 0; +} + + +static int message_peek_body( + sd_bus_message *m, + size_t *rindex, + size_t align, + size_t nbytes, + void **ret) { + + size_t k, start, end, padding; + struct bus_body_part *part; + uint8_t *q; + + assert(m); + assert(rindex); + assert(align > 0); + + start = ALIGN_TO((size_t) *rindex, align); + padding = start - *rindex; + end = start + nbytes; + + if (end > m->user_body_size) + return -EBADMSG; + + part = find_part(m, *rindex, padding, (void**) &q); + if (!part) + return -EBADMSG; + + if (q) { + /* Verify padding */ + for (k = 0; k < padding; k++) + if (q[k] != 0) + return -EBADMSG; + } + + part = find_part(m, start, nbytes, (void**) &q); + if (!part || (nbytes > 0 && !q)) + return -EBADMSG; + + *rindex = end; + + if (ret) + *ret = q; + + return 0; +} + +static bool validate_nul(const char *s, size_t l) { + + /* Check for NUL chars in the string */ + if (memchr(s, 0, l)) + return false; + + /* Check for NUL termination */ + if (s[l] != 0) + return false; + + return true; +} + +static bool validate_string(const char *s, size_t l) { + + if (!validate_nul(s, l)) + return false; + + /* Check if valid UTF8 */ + if (!utf8_is_valid(s)) + return false; + + return true; +} + +static bool validate_signature(const char *s, size_t l) { + + if (!validate_nul(s, l)) + return false; + + /* Check if valid signature */ + if (!signature_is_valid(s, true)) + return false; + + return true; +} + +static bool validate_object_path(const char *s, size_t l) { + + if (!validate_nul(s, l)) + return false; + + if (!object_path_is_valid(s)) + return false; + + return true; +} + +_public_ int sd_bus_message_read_basic(sd_bus_message *m, char type, void *p) { + struct bus_container *c; + size_t rindex; + void *q; + int r; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + assert_return(bus_type_is_basic(type), -EINVAL); + + if (message_end_of_signature(m)) + return -ENXIO; + + if (message_end_of_array(m, m->rindex)) + return 0; + + c = message_get_container(m); + if (c->signature[c->index] != type) + return -ENXIO; + + rindex = m->rindex; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + + if (IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE)) { + bool ok; + + r = message_peek_body(m, &rindex, 1, c->item_size, &q); + if (r < 0) + return r; + + if (type == SD_BUS_TYPE_STRING) + ok = validate_string(q, c->item_size-1); + else if (type == SD_BUS_TYPE_OBJECT_PATH) + ok = validate_object_path(q, c->item_size-1); + else + ok = validate_signature(q, c->item_size-1); + + if (!ok) + return -EBADMSG; + + if (p) + *(const char**) p = q; + } else { + int sz, align; + + sz = bus_gvariant_get_size(CHAR_TO_STR(type)); + assert(sz > 0); + if ((size_t) sz != c->item_size) + return -EBADMSG; + + align = bus_gvariant_get_alignment(CHAR_TO_STR(type)); + assert(align > 0); + + r = message_peek_body(m, &rindex, align, c->item_size, &q); + if (r < 0) + return r; + + switch (type) { + + case SD_BUS_TYPE_BYTE: + if (p) + *(uint8_t*) p = *(uint8_t*) q; + break; + + case SD_BUS_TYPE_BOOLEAN: + if (p) + *(int*) p = !!*(uint8_t*) q; + break; + + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + if (p) + *(uint16_t*) p = BUS_MESSAGE_BSWAP16(m, *(uint16_t*) q); + break; + + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + if (p) + *(uint32_t*) p = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); + break; + + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + if (p) + *(uint64_t*) p = BUS_MESSAGE_BSWAP64(m, *(uint64_t*) q); + break; + + case SD_BUS_TYPE_UNIX_FD: { + uint32_t j; + + j = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); + if (j >= m->n_fds) + return -EBADMSG; + + if (p) + *(int*) p = m->fds[j]; + + break; + } + + default: + assert_not_reached("unexpected type"); + } + } + + r = container_next_item(m, c, &rindex); + if (r < 0) + return r; + } else { + + if (IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH)) { + uint32_t l; + bool ok; + + r = message_peek_body(m, &rindex, 4, 4, &q); + if (r < 0) + return r; + + l = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); + r = message_peek_body(m, &rindex, 1, l+1, &q); + if (r < 0) + return r; + + if (type == SD_BUS_TYPE_OBJECT_PATH) + ok = validate_object_path(q, l); + else + ok = validate_string(q, l); + if (!ok) + return -EBADMSG; + + if (p) + *(const char**) p = q; + + } else if (type == SD_BUS_TYPE_SIGNATURE) { + uint8_t l; + + r = message_peek_body(m, &rindex, 1, 1, &q); + if (r < 0) + return r; + + l = *(uint8_t*) q; + r = message_peek_body(m, &rindex, 1, l+1, &q); + if (r < 0) + return r; + + if (!validate_signature(q, l)) + return -EBADMSG; + + if (p) + *(const char**) p = q; + + } else { + ssize_t sz, align; + + align = bus_type_get_alignment(type); + assert(align > 0); + + sz = bus_type_get_size(type); + assert(sz > 0); + + r = message_peek_body(m, &rindex, align, sz, &q); + if (r < 0) + return r; + + switch (type) { + + case SD_BUS_TYPE_BYTE: + if (p) + *(uint8_t*) p = *(uint8_t*) q; + break; + + case SD_BUS_TYPE_BOOLEAN: + if (p) + *(int*) p = !!*(uint32_t*) q; + break; + + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + if (p) + *(uint16_t*) p = BUS_MESSAGE_BSWAP16(m, *(uint16_t*) q); + break; + + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + if (p) + *(uint32_t*) p = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); + break; + + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + if (p) + *(uint64_t*) p = BUS_MESSAGE_BSWAP64(m, *(uint64_t*) q); + break; + + case SD_BUS_TYPE_UNIX_FD: { + uint32_t j; + + j = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); + if (j >= m->n_fds) + return -EBADMSG; + + if (p) + *(int*) p = m->fds[j]; + break; + } + + default: + assert_not_reached("Unknown basic type..."); + } + } + } + + m->rindex = rindex; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index++; + + return 1; +} + +static int bus_message_enter_array( + sd_bus_message *m, + struct bus_container *c, + const char *contents, + uint32_t **array_size, + size_t *item_size, + size_t **offsets, + size_t *n_offsets) { + + size_t rindex; + void *q; + int r, alignment; + + assert(m); + assert(c); + assert(contents); + assert(array_size); + assert(item_size); + assert(offsets); + assert(n_offsets); + + if (!signature_is_single(contents, true)) + return -EINVAL; + + if (!c->signature || c->signature[c->index] == 0) + return -ENXIO; + + if (c->signature[c->index] != SD_BUS_TYPE_ARRAY) + return -ENXIO; + + if (!startswith(c->signature + c->index + 1, contents)) + return -ENXIO; + + rindex = m->rindex; + + if (!BUS_MESSAGE_IS_GVARIANT(m)) { + /* dbus1 */ + + r = message_peek_body(m, &rindex, 4, 4, &q); + if (r < 0) + return r; + + if (BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q) > BUS_ARRAY_MAX_SIZE) + return -EBADMSG; + + alignment = bus_type_get_alignment(contents[0]); + if (alignment < 0) + return alignment; + + r = message_peek_body(m, &rindex, alignment, 0, NULL); + if (r < 0) + return r; + + *array_size = (uint32_t*) q; + + } else if (c->item_size <= 0) { + + /* gvariant: empty array */ + *item_size = 0; + *offsets = NULL; + *n_offsets = 0; + + } else if (bus_gvariant_is_fixed_size(contents)) { + + /* gvariant: fixed length array */ + *item_size = bus_gvariant_get_size(contents); + *offsets = NULL; + *n_offsets = 0; + + } else { + size_t where, p = 0, framing, sz; + unsigned i; + + /* gvariant: variable length array */ + sz = bus_gvariant_determine_word_size(c->item_size, 0); + + where = rindex + c->item_size - sz; + r = message_peek_body(m, &where, 1, sz, &q); + if (r < 0) + return r; + + framing = bus_gvariant_read_word_le(q, sz); + if (framing > c->item_size - sz) + return -EBADMSG; + if ((c->item_size - framing) % sz != 0) + return -EBADMSG; + + *n_offsets = (c->item_size - framing) / sz; + + where = rindex + framing; + r = message_peek_body(m, &where, 1, *n_offsets * sz, &q); + if (r < 0) + return r; + + *offsets = new(size_t, *n_offsets); + if (!*offsets) + return -ENOMEM; + + for (i = 0; i < *n_offsets; i++) { + size_t x; + + x = bus_gvariant_read_word_le((uint8_t*) q + i * sz, sz); + if (x > c->item_size - sz) + return -EBADMSG; + if (x < p) + return -EBADMSG; + + (*offsets)[i] = rindex + x; + p = x; + } + + *item_size = (*offsets)[0] - rindex; + } + + m->rindex = rindex; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index += 1 + strlen(contents); + + return 1; +} + +static int bus_message_enter_variant( + sd_bus_message *m, + struct bus_container *c, + const char *contents, + size_t *item_size) { + + size_t rindex; + uint8_t l; + void *q; + int r; + + assert(m); + assert(c); + assert(contents); + assert(item_size); + + if (!signature_is_single(contents, false)) + return -EINVAL; + + if (*contents == SD_BUS_TYPE_DICT_ENTRY_BEGIN) + return -EINVAL; + + if (!c->signature || c->signature[c->index] == 0) + return -ENXIO; + + if (c->signature[c->index] != SD_BUS_TYPE_VARIANT) + return -ENXIO; + + rindex = m->rindex; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + size_t k, where; + + k = strlen(contents); + if (1+k > c->item_size) + return -EBADMSG; + + where = rindex + c->item_size - (1+k); + r = message_peek_body(m, &where, 1, 1+k, &q); + if (r < 0) + return r; + + if (*(char*) q != 0) + return -EBADMSG; + + if (memcmp((uint8_t*) q+1, contents, k)) + return -ENXIO; + + *item_size = c->item_size - (1+k); + + } else { + r = message_peek_body(m, &rindex, 1, 1, &q); + if (r < 0) + return r; + + l = *(uint8_t*) q; + r = message_peek_body(m, &rindex, 1, l+1, &q); + if (r < 0) + return r; + + if (!validate_signature(q, l)) + return -EBADMSG; + + if (!streq(q, contents)) + return -ENXIO; + } + + m->rindex = rindex; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index++; + + return 1; +} + +static int build_struct_offsets( + sd_bus_message *m, + const char *signature, + size_t size, + size_t *item_size, + size_t **offsets, + size_t *n_offsets) { + + unsigned n_variable = 0, n_total = 0, v; + size_t previous = 0, where; + const char *p; + size_t sz; + void *q; + int r; + + assert(m); + assert(item_size); + assert(offsets); + assert(n_offsets); + + if (isempty(signature)) { + /* Unary type is encoded as *fixed* 1 byte padding */ + r = message_peek_body(m, &m->rindex, 1, 1, &q); + if (r < 0) + return r; + + if (*(uint8_t *) q != 0) + return -EBADMSG; + + *item_size = 0; + *offsets = NULL; + *n_offsets = 0; + return 0; + } + + sz = bus_gvariant_determine_word_size(size, 0); + if (sz <= 0) + return -EBADMSG; + + /* First, loop over signature and count variable elements and + * elements in general. We use this to know how large the + * offset array is at the end of the structure. Note that + * GVariant only stores offsets for all variable size elements + * that are not the last item. */ + + p = signature; + while (*p != 0) { + size_t n; + + r = signature_element_length(p, &n); + if (r < 0) + return r; + else { + char t[n+1]; + + memcpy(t, p, n); + t[n] = 0; + + r = bus_gvariant_is_fixed_size(t); + } + + if (r < 0) + return r; + if (r == 0 && p[n] != 0) /* except the last item */ + n_variable++; + n_total++; + + p += n; + } + + if (size < n_variable * sz) + return -EBADMSG; + + where = m->rindex + size - (n_variable * sz); + r = message_peek_body(m, &where, 1, n_variable * sz, &q); + if (r < 0) + return r; + + v = n_variable; + + *offsets = new(size_t, n_total); + if (!*offsets) + return -ENOMEM; + + *n_offsets = 0; + + /* Second, loop again and build an offset table */ + p = signature; + while (*p != 0) { + size_t n, offset; + int k; + + r = signature_element_length(p, &n); + if (r < 0) + return r; + else { + char t[n+1]; + + memcpy(t, p, n); + t[n] = 0; + + k = bus_gvariant_get_size(t); + if (k < 0) { + size_t x; + + /* variable size */ + if (v > 0) { + v--; + + x = bus_gvariant_read_word_le((uint8_t*) q + v*sz, sz); + if (x >= size) + return -EBADMSG; + if (m->rindex + x < previous) + return -EBADMSG; + } else + /* The last item's end + * is determined from + * the start of the + * offset array */ + x = size - (n_variable * sz); + + offset = m->rindex + x; + + } else { + size_t align; + + /* fixed size */ + align = bus_gvariant_get_alignment(t); + assert(align > 0); + + offset = (*n_offsets == 0 ? m->rindex : ALIGN_TO((*offsets)[*n_offsets-1], align)) + k; + } + } + + previous = (*offsets)[(*n_offsets)++] = offset; + p += n; + } + + assert(v == 0); + assert(*n_offsets == n_total); + + *item_size = (*offsets)[0] - m->rindex; + return 0; +} + +static int enter_struct_or_dict_entry( + sd_bus_message *m, + struct bus_container *c, + const char *contents, + size_t *item_size, + size_t **offsets, + size_t *n_offsets) { + + int r; + + assert(m); + assert(c); + assert(contents); + assert(item_size); + assert(offsets); + assert(n_offsets); + + if (!BUS_MESSAGE_IS_GVARIANT(m)) { + + /* dbus1 */ + r = message_peek_body(m, &m->rindex, 8, 0, NULL); + if (r < 0) + return r; + + } else + /* gvariant with contents */ + return build_struct_offsets(m, contents, c->item_size, item_size, offsets, n_offsets); + + return 0; +} + +static int bus_message_enter_struct( + sd_bus_message *m, + struct bus_container *c, + const char *contents, + size_t *item_size, + size_t **offsets, + size_t *n_offsets) { + + size_t l; + int r; + + assert(m); + assert(c); + assert(contents); + assert(item_size); + assert(offsets); + assert(n_offsets); + + if (!signature_is_valid(contents, false)) + return -EINVAL; + + if (!c->signature || c->signature[c->index] == 0) + return -ENXIO; + + l = strlen(contents); + + if (c->signature[c->index] != SD_BUS_TYPE_STRUCT_BEGIN || + !startswith(c->signature + c->index + 1, contents) || + c->signature[c->index + 1 + l] != SD_BUS_TYPE_STRUCT_END) + return -ENXIO; + + r = enter_struct_or_dict_entry(m, c, contents, item_size, offsets, n_offsets); + if (r < 0) + return r; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index += 1 + l + 1; + + return 1; +} + +static int bus_message_enter_dict_entry( + sd_bus_message *m, + struct bus_container *c, + const char *contents, + size_t *item_size, + size_t **offsets, + size_t *n_offsets) { + + size_t l; + int r; + + assert(m); + assert(c); + assert(contents); + + if (!signature_is_pair(contents)) + return -EINVAL; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + return -ENXIO; + + if (!c->signature || c->signature[c->index] == 0) + return 0; + + l = strlen(contents); + + if (c->signature[c->index] != SD_BUS_TYPE_DICT_ENTRY_BEGIN || + !startswith(c->signature + c->index + 1, contents) || + c->signature[c->index + 1 + l] != SD_BUS_TYPE_DICT_ENTRY_END) + return -ENXIO; + + r = enter_struct_or_dict_entry(m, c, contents, item_size, offsets, n_offsets); + if (r < 0) + return r; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index += 1 + l + 1; + + return 1; +} + +_public_ int sd_bus_message_enter_container(sd_bus_message *m, + char type, + const char *contents) { + struct bus_container *c, *w; + uint32_t *array_size = NULL; + char *signature; + size_t before; + size_t *offsets = NULL; + size_t n_offsets = 0, item_size = 0; + int r; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + assert_return(type != 0 || !contents, -EINVAL); + + if (type == 0 || !contents) { + const char *cc; + char tt; + + /* Allow entering into anonymous containers */ + r = sd_bus_message_peek_type(m, &tt, &cc); + if (r < 0) + return r; + + if (type != 0 && type != tt) + return -ENXIO; + + if (contents && !streq(contents, cc)) + return -ENXIO; + + type = tt; + contents = cc; + } + + /* + * We enforce a global limit on container depth, that is much + * higher than the 32 structs and 32 arrays the specification + * mandates. This is simpler to implement for us, and we need + * this only to ensure our container array doesn't grow + * without bounds. We are happy to return any data from a + * message as long as the data itself is valid, even if the + * overall message might be not. + * + * Note that the message signature is validated when + * parsing the headers, and that validation does check the + * 32/32 limit. + * + * Note that the specification defines no limits on the depth + * of stacked variants, but we do. + */ + if (m->n_containers >= BUS_CONTAINER_DEPTH) + return -EBADMSG; + + if (!GREEDY_REALLOC(m->containers, m->containers_allocated, m->n_containers + 1)) + return -ENOMEM; + + if (message_end_of_signature(m)) + return -ENXIO; + + if (message_end_of_array(m, m->rindex)) + return 0; + + c = message_get_container(m); + + signature = strdup(contents); + if (!signature) + return -ENOMEM; + + c->saved_index = c->index; + before = m->rindex; + + if (type == SD_BUS_TYPE_ARRAY) + r = bus_message_enter_array(m, c, contents, &array_size, &item_size, &offsets, &n_offsets); + else if (type == SD_BUS_TYPE_VARIANT) + r = bus_message_enter_variant(m, c, contents, &item_size); + else if (type == SD_BUS_TYPE_STRUCT) + r = bus_message_enter_struct(m, c, contents, &item_size, &offsets, &n_offsets); + else if (type == SD_BUS_TYPE_DICT_ENTRY) + r = bus_message_enter_dict_entry(m, c, contents, &item_size, &offsets, &n_offsets); + else + r = -EINVAL; + + if (r <= 0) { + free(signature); + free(offsets); + return r; + } + + /* OK, let's fill it in */ + w = m->containers + m->n_containers++; + w->enclosing = type; + w->signature = signature; + w->peeked_signature = NULL; + w->index = 0; + + w->before = before; + w->begin = m->rindex; + + /* Unary type has fixed size of 1, but virtual size of 0 */ + if (BUS_MESSAGE_IS_GVARIANT(m) && + type == SD_BUS_TYPE_STRUCT && + isempty(signature)) + w->end = m->rindex + 0; + else + w->end = m->rindex + c->item_size; + + w->array_size = array_size; + w->item_size = item_size; + w->offsets = offsets; + w->n_offsets = n_offsets; + w->offset_index = 0; + + return 1; +} + +_public_ int sd_bus_message_exit_container(sd_bus_message *m) { + struct bus_container *c; + unsigned saved; + int r; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + assert_return(m->n_containers > 0, -ENXIO); + + c = message_get_container(m); + + if (c->enclosing != SD_BUS_TYPE_ARRAY) { + if (c->signature && c->signature[c->index] != 0) + return -EBUSY; + } + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + if (m->rindex < c->end) + return -EBUSY; + + } else if (c->enclosing == SD_BUS_TYPE_ARRAY) { + uint32_t l; + + l = BUS_MESSAGE_BSWAP32(m, *c->array_size); + if (c->begin + l != m->rindex) + return -EBUSY; + } + + free(c->signature); + free(c->peeked_signature); + free(c->offsets); + m->n_containers--; + + c = message_get_container(m); + + saved = c->index; + c->index = c->saved_index; + r = container_next_item(m, c, &m->rindex); + c->index = saved; + if (r < 0) + return r; + + return 1; +} + +static void message_quit_container(sd_bus_message *m) { + struct bus_container *c; + + assert(m); + assert(m->sealed); + assert(m->n_containers > 0); + + c = message_get_container(m); + + /* Undo seeks */ + assert(m->rindex >= c->before); + m->rindex = c->before; + + /* Free container */ + free(c->signature); + free(c->offsets); + m->n_containers--; + + /* Correct index of new top-level container */ + c = message_get_container(m); + c->index = c->saved_index; +} + +_public_ int sd_bus_message_peek_type(sd_bus_message *m, char *type, const char **contents) { + struct bus_container *c; + int r; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + + if (message_end_of_signature(m)) + goto eof; + + if (message_end_of_array(m, m->rindex)) + goto eof; + + c = message_get_container(m); + + if (bus_type_is_basic(c->signature[c->index])) { + if (contents) + *contents = NULL; + if (type) + *type = c->signature[c->index]; + return 1; + } + + if (c->signature[c->index] == SD_BUS_TYPE_ARRAY) { + + if (contents) { + size_t l; + char *sig; + + r = signature_element_length(c->signature+c->index+1, &l); + if (r < 0) + return r; + + assert(l >= 1); + + sig = strndup(c->signature + c->index + 1, l); + if (!sig) + return -ENOMEM; + + free(c->peeked_signature); + *contents = c->peeked_signature = sig; + } + + if (type) + *type = SD_BUS_TYPE_ARRAY; + + return 1; + } + + if (c->signature[c->index] == SD_BUS_TYPE_STRUCT_BEGIN || + c->signature[c->index] == SD_BUS_TYPE_DICT_ENTRY_BEGIN) { + + if (contents) { + size_t l; + char *sig; + + r = signature_element_length(c->signature+c->index, &l); + if (r < 0) + return r; + + assert(l >= 2); + sig = strndup(c->signature + c->index + 1, l - 2); + if (!sig) + return -ENOMEM; + + free(c->peeked_signature); + *contents = c->peeked_signature = sig; + } + + if (type) + *type = c->signature[c->index] == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY; + + return 1; + } + + if (c->signature[c->index] == SD_BUS_TYPE_VARIANT) { + if (contents) { + void *q; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + size_t k; + + if (c->item_size < 2) + return -EBADMSG; + + /* Look for the NUL delimiter that + separates the payload from the + signature. Since the body might be + in a different part that then the + signature we map byte by byte. */ + + for (k = 2; k <= c->item_size; k++) { + size_t where; + + where = m->rindex + c->item_size - k; + r = message_peek_body(m, &where, 1, k, &q); + if (r < 0) + return r; + + if (*(char*) q == 0) + break; + } + + if (k > c->item_size) + return -EBADMSG; + + free(c->peeked_signature); + c->peeked_signature = strndup((char*) q + 1, k - 1); + if (!c->peeked_signature) + return -ENOMEM; + + if (!signature_is_valid(c->peeked_signature, true)) + return -EBADMSG; + + *contents = c->peeked_signature; + } else { + size_t rindex, l; + + rindex = m->rindex; + r = message_peek_body(m, &rindex, 1, 1, &q); + if (r < 0) + return r; + + l = *(uint8_t*) q; + r = message_peek_body(m, &rindex, 1, l+1, &q); + if (r < 0) + return r; + + if (!validate_signature(q, l)) + return -EBADMSG; + + *contents = q; + } + } + + if (type) + *type = SD_BUS_TYPE_VARIANT; + + return 1; + } + + return -EINVAL; + +eof: + if (type) + *type = 0; + if (contents) + *contents = NULL; + return 0; +} + +_public_ int sd_bus_message_rewind(sd_bus_message *m, int complete) { + struct bus_container *c; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + + if (complete) { + message_reset_containers(m); + m->rindex = 0; + + c = message_get_container(m); + } else { + c = message_get_container(m); + + c->offset_index = 0; + c->index = 0; + m->rindex = c->begin; + } + + c->offset_index = 0; + c->item_size = (c->n_offsets > 0 ? c->offsets[0] : c->end) - c->begin; + + return !isempty(c->signature); +} + +static int message_read_ap( + sd_bus_message *m, + const char *types, + va_list ap) { + + unsigned n_array, n_struct; + TypeStack stack[BUS_CONTAINER_DEPTH]; + unsigned stack_ptr = 0; + unsigned n_loop = 0; + int r; + + assert(m); + + if (isempty(types)) + return 0; + + /* Ideally, we'd just call ourselves recursively on every + * complex type. However, the state of a va_list that is + * passed to a function is undefined after that function + * returns. This means we need to docode the va_list linearly + * in a single stackframe. We hence implement our own + * home-grown stack in an array. */ + + n_array = (unsigned) -1; /* length of current array entries */ + n_struct = strlen(types); /* length of current struct contents signature */ + + for (;;) { + const char *t; + + n_loop++; + + if (n_array == 0 || (n_array == (unsigned) -1 && n_struct == 0)) { + r = type_stack_pop(stack, ELEMENTSOF(stack), &stack_ptr, &types, &n_struct, &n_array); + if (r < 0) + return r; + if (r == 0) + break; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + continue; + } + + t = types; + if (n_array != (unsigned) -1) + n_array--; + else { + types++; + n_struct--; + } + + switch (*t) { + + case SD_BUS_TYPE_BYTE: + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: + case SD_BUS_TYPE_UNIX_FD: { + void *p; + + p = va_arg(ap, void*); + r = sd_bus_message_read_basic(m, *t, p); + if (r < 0) + return r; + if (r == 0) { + if (n_loop <= 1) + return 0; + + return -ENXIO; + } + + break; + } + + case SD_BUS_TYPE_ARRAY: { + size_t k; + + r = signature_element_length(t + 1, &k); + if (r < 0) + return r; + + { + char s[k + 1]; + memcpy(s, t + 1, k); + s[k] = 0; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, s); + if (r < 0) + return r; + if (r == 0) { + if (n_loop <= 1) + return 0; + + return -ENXIO; + } + } + + if (n_array == (unsigned) -1) { + types += k; + n_struct -= k; + } + + r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); + if (r < 0) + return r; + + types = t + 1; + n_struct = k; + n_array = va_arg(ap, unsigned); + + break; + } + + case SD_BUS_TYPE_VARIANT: { + const char *s; + + s = va_arg(ap, const char *); + if (!s) + return -EINVAL; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, s); + if (r < 0) + return r; + if (r == 0) { + if (n_loop <= 1) + return 0; + + return -ENXIO; + } + + r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); + if (r < 0) + return r; + + types = s; + n_struct = strlen(s); + n_array = (unsigned) -1; + + break; + } + + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { + size_t k; + + r = signature_element_length(t, &k); + if (r < 0) + return r; + + { + char s[k - 1]; + memcpy(s, t + 1, k - 2); + s[k - 2] = 0; + + r = sd_bus_message_enter_container(m, *t == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s); + if (r < 0) + return r; + if (r == 0) { + if (n_loop <= 1) + return 0; + return -ENXIO; + } + } + + if (n_array == (unsigned) -1) { + types += k - 1; + n_struct -= k - 1; + } + + r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); + if (r < 0) + return r; + + types = t + 1; + n_struct = k - 2; + n_array = (unsigned) -1; + + break; + } + + default: + return -EINVAL; + } + } + + return 1; +} + +_public_ int sd_bus_message_read(sd_bus_message *m, const char *types, ...) { + va_list ap; + int r; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + assert_return(types, -EINVAL); + + va_start(ap, types); + r = message_read_ap(m, types, ap); + va_end(ap); + + return r; +} + +_public_ int sd_bus_message_skip(sd_bus_message *m, const char *types) { + int r; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + + /* If types is NULL, read exactly one element */ + if (!types) { + struct bus_container *c; + size_t l; + + if (message_end_of_signature(m)) + return -ENXIO; + + if (message_end_of_array(m, m->rindex)) + return 0; + + c = message_get_container(m); + + r = signature_element_length(c->signature + c->index, &l); + if (r < 0) + return r; + + types = strndupa(c->signature + c->index, l); + } + + switch (*types) { + + case 0: /* Nothing to drop */ + return 0; + + case SD_BUS_TYPE_BYTE: + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: + case SD_BUS_TYPE_UNIX_FD: + + r = sd_bus_message_read_basic(m, *types, NULL); + if (r <= 0) + return r; + + r = sd_bus_message_skip(m, types + 1); + if (r < 0) + return r; + + return 1; + + case SD_BUS_TYPE_ARRAY: { + size_t k; + + r = signature_element_length(types + 1, &k); + if (r < 0) + return r; + + { + char s[k+1]; + memcpy(s, types+1, k); + s[k] = 0; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, s); + if (r <= 0) + return r; + + for (;;) { + r = sd_bus_message_skip(m, s); + if (r < 0) + return r; + if (r == 0) + break; + } + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + } + + r = sd_bus_message_skip(m, types + 1 + k); + if (r < 0) + return r; + + return 1; + } + + case SD_BUS_TYPE_VARIANT: { + const char *contents; + char x; + + r = sd_bus_message_peek_type(m, &x, &contents); + if (r <= 0) + return r; + + if (x != SD_BUS_TYPE_VARIANT) + return -ENXIO; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, contents); + if (r <= 0) + return r; + + r = sd_bus_message_skip(m, contents); + if (r < 0) + return r; + assert(r != 0); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + r = sd_bus_message_skip(m, types + 1); + if (r < 0) + return r; + + return 1; + } + + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { + size_t k; + + r = signature_element_length(types, &k); + if (r < 0) + return r; + + { + char s[k-1]; + memcpy(s, types+1, k-2); + s[k-2] = 0; + + r = sd_bus_message_enter_container(m, *types == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s); + if (r <= 0) + return r; + + r = sd_bus_message_skip(m, s); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + } + + r = sd_bus_message_skip(m, types + k); + if (r < 0) + return r; + + return 1; + } + + default: + return -EINVAL; + } +} + +_public_ int sd_bus_message_read_array( + sd_bus_message *m, + char type, + const void **ptr, + size_t *size) { + + struct bus_container *c; + void *p; + size_t sz; + ssize_t align; + int r; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + assert_return(bus_type_is_trivial(type), -EINVAL); + assert_return(ptr, -EINVAL); + assert_return(size, -EINVAL); + assert_return(!BUS_MESSAGE_NEED_BSWAP(m), -EOPNOTSUPP); + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, CHAR_TO_STR(type)); + if (r <= 0) + return r; + + c = message_get_container(m); + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + align = bus_gvariant_get_alignment(CHAR_TO_STR(type)); + if (align < 0) + return align; + + sz = c->end - c->begin; + } else { + align = bus_type_get_alignment(type); + if (align < 0) + return align; + + sz = BUS_MESSAGE_BSWAP32(m, *c->array_size); + } + + if (sz == 0) + /* Zero length array, let's return some aligned + * pointer that is not NULL */ + p = (uint8_t*) NULL + align; + else { + r = message_peek_body(m, &m->rindex, align, sz, &p); + if (r < 0) + goto fail; + } + + r = sd_bus_message_exit_container(m); + if (r < 0) + goto fail; + + *ptr = (const void*) p; + *size = sz; + + return 1; + +fail: + message_quit_container(m); + return r; +} + +static int message_peek_fields( + sd_bus_message *m, + size_t *rindex, + size_t align, + size_t nbytes, + void **ret) { + + assert(m); + assert(rindex); + assert(align > 0); + + return buffer_peek(BUS_MESSAGE_FIELDS(m), m->fields_size, rindex, align, nbytes, ret); +} + +static int message_peek_field_uint32( + sd_bus_message *m, + size_t *ri, + size_t item_size, + uint32_t *ret) { + + int r; + void *q; + + assert(m); + assert(ri); + + if (BUS_MESSAGE_IS_GVARIANT(m) && item_size != 4) + return -EBADMSG; + + /* identical for gvariant and dbus1 */ + + r = message_peek_fields(m, ri, 4, 4, &q); + if (r < 0) + return r; + + if (ret) + *ret = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); + + return 0; +} + +static int message_peek_field_uint64( + sd_bus_message *m, + size_t *ri, + size_t item_size, + uint64_t *ret) { + + int r; + void *q; + + assert(m); + assert(ri); + + if (BUS_MESSAGE_IS_GVARIANT(m) && item_size != 8) + return -EBADMSG; + + /* identical for gvariant and dbus1 */ + + r = message_peek_fields(m, ri, 8, 8, &q); + if (r < 0) + return r; + + if (ret) + *ret = BUS_MESSAGE_BSWAP64(m, *(uint64_t*) q); + + return 0; +} + +static int message_peek_field_string( + sd_bus_message *m, + bool (*validate)(const char *p), + size_t *ri, + size_t item_size, + const char **ret) { + + uint32_t l; + int r; + void *q; + + assert(m); + assert(ri); + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + + if (item_size <= 0) + return -EBADMSG; + + r = message_peek_fields(m, ri, 1, item_size, &q); + if (r < 0) + return r; + + l = item_size - 1; + } else { + r = message_peek_field_uint32(m, ri, 4, &l); + if (r < 0) + return r; + + r = message_peek_fields(m, ri, 1, l+1, &q); + if (r < 0) + return r; + } + + if (validate) { + if (!validate_nul(q, l)) + return -EBADMSG; + + if (!validate(q)) + return -EBADMSG; + } else { + if (!validate_string(q, l)) + return -EBADMSG; + } + + if (ret) + *ret = q; + + return 0; +} + +static int message_peek_field_signature( + sd_bus_message *m, + size_t *ri, + size_t item_size, + const char **ret) { + + size_t l; + int r; + void *q; + + assert(m); + assert(ri); + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + + if (item_size <= 0) + return -EBADMSG; + + r = message_peek_fields(m, ri, 1, item_size, &q); + if (r < 0) + return r; + + l = item_size - 1; + } else { + r = message_peek_fields(m, ri, 1, 1, &q); + if (r < 0) + return r; + + l = *(uint8_t*) q; + r = message_peek_fields(m, ri, 1, l+1, &q); + if (r < 0) + return r; + } + + if (!validate_signature(q, l)) + return -EBADMSG; + + if (ret) + *ret = q; + + return 0; +} + +static int message_skip_fields( + sd_bus_message *m, + size_t *ri, + uint32_t array_size, + const char **signature) { + + size_t original_index; + int r; + + assert(m); + assert(ri); + assert(signature); + assert(!BUS_MESSAGE_IS_GVARIANT(m)); + + original_index = *ri; + + for (;;) { + char t; + size_t l; + + if (array_size != (uint32_t) -1 && + array_size <= *ri - original_index) + return 0; + + t = **signature; + if (!t) + return 0; + + if (t == SD_BUS_TYPE_STRING) { + + r = message_peek_field_string(m, NULL, ri, 0, NULL); + if (r < 0) + return r; + + (*signature)++; + + } else if (t == SD_BUS_TYPE_OBJECT_PATH) { + + r = message_peek_field_string(m, object_path_is_valid, ri, 0, NULL); + if (r < 0) + return r; + + (*signature)++; + + } else if (t == SD_BUS_TYPE_SIGNATURE) { + + r = message_peek_field_signature(m, ri, 0, NULL); + if (r < 0) + return r; + + (*signature)++; + + } else if (bus_type_is_basic(t)) { + ssize_t align, k; + + align = bus_type_get_alignment(t); + k = bus_type_get_size(t); + assert(align > 0 && k > 0); + + r = message_peek_fields(m, ri, align, k, NULL); + if (r < 0) + return r; + + (*signature)++; + + } else if (t == SD_BUS_TYPE_ARRAY) { + + r = signature_element_length(*signature+1, &l); + if (r < 0) + return r; + + assert(l >= 1); + { + char sig[l-1], *s; + uint32_t nas; + int alignment; + + strncpy(sig, *signature + 1, l-1); + s = sig; + + alignment = bus_type_get_alignment(sig[0]); + if (alignment < 0) + return alignment; + + r = message_peek_field_uint32(m, ri, 0, &nas); + if (r < 0) + return r; + if (nas > BUS_ARRAY_MAX_SIZE) + return -EBADMSG; + + r = message_peek_fields(m, ri, alignment, 0, NULL); + if (r < 0) + return r; + + r = message_skip_fields(m, ri, nas, (const char**) &s); + if (r < 0) + return r; + } + + (*signature) += 1 + l; + + } else if (t == SD_BUS_TYPE_VARIANT) { + const char *s; + + r = message_peek_field_signature(m, ri, 0, &s); + if (r < 0) + return r; + + r = message_skip_fields(m, ri, (uint32_t) -1, (const char**) &s); + if (r < 0) + return r; + + (*signature)++; + + } else if (t == SD_BUS_TYPE_STRUCT || + t == SD_BUS_TYPE_DICT_ENTRY) { + + r = signature_element_length(*signature, &l); + if (r < 0) + return r; + + assert(l >= 2); + { + char sig[l-1], *s; + strncpy(sig, *signature + 1, l-1); + s = sig; + + r = message_skip_fields(m, ri, (uint32_t) -1, (const char**) &s); + if (r < 0) + return r; + } + + *signature += l; + } else + return -EINVAL; + } +} + +int bus_message_parse_fields(sd_bus_message *m) { + size_t ri; + int r; + uint32_t unix_fds = 0; + bool unix_fds_set = false; + void *offsets = NULL; + unsigned n_offsets = 0; + size_t sz = 0; + unsigned i = 0; + + assert(m); + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + char *p; + + /* Read the signature from the end of the body variant first */ + sz = bus_gvariant_determine_word_size(BUS_MESSAGE_SIZE(m), 0); + if (m->footer_accessible < 1 + sz) + return -EBADMSG; + + p = (char*) m->footer + m->footer_accessible - (1 + sz); + for (;;) { + if (p < (char*) m->footer) + return -EBADMSG; + + if (*p == 0) { + size_t l; + char *c; + + /* We found the beginning of the signature + * string, yay! We require the body to be a + * structure, so verify it and then strip the + * opening/closing brackets. */ + + l = ((char*) m->footer + m->footer_accessible) - p - (1 + sz); + if (l < 2 || + p[1] != SD_BUS_TYPE_STRUCT_BEGIN || + p[1 + l - 1] != SD_BUS_TYPE_STRUCT_END) + return -EBADMSG; + + c = strndup(p + 1 + 1, l - 2); + if (!c) + return -ENOMEM; + + free(m->root_container.signature); + m->root_container.signature = c; + break; + } + + p--; + } + + /* Calculate the actual user body size, by removing + * the trailing variant signature and struct offset + * table */ + m->user_body_size = m->body_size - ((char*) m->footer + m->footer_accessible - p); + + /* Pull out the offset table for the fields array */ + sz = bus_gvariant_determine_word_size(m->fields_size, 0); + if (sz > 0) { + size_t framing; + void *q; + + ri = m->fields_size - sz; + r = message_peek_fields(m, &ri, 1, sz, &q); + if (r < 0) + return r; + + framing = bus_gvariant_read_word_le(q, sz); + if (framing >= m->fields_size - sz) + return -EBADMSG; + if ((m->fields_size - framing) % sz != 0) + return -EBADMSG; + + ri = framing; + r = message_peek_fields(m, &ri, 1, m->fields_size - framing, &offsets); + if (r < 0) + return r; + + n_offsets = (m->fields_size - framing) / sz; + } + } else + m->user_body_size = m->body_size; + + ri = 0; + while (ri < m->fields_size) { + _cleanup_free_ char *sig = NULL; + const char *signature; + uint64_t field_type; + size_t item_size = (size_t) -1; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + uint64_t *u64; + + if (i >= n_offsets) + break; + + if (i == 0) + ri = 0; + else + ri = ALIGN_TO(bus_gvariant_read_word_le((uint8_t*) offsets + (i-1)*sz, sz), 8); + + r = message_peek_fields(m, &ri, 8, 8, (void**) &u64); + if (r < 0) + return r; + + field_type = BUS_MESSAGE_BSWAP64(m, *u64); + } else { + uint8_t *u8; + + r = message_peek_fields(m, &ri, 8, 1, (void**) &u8); + if (r < 0) + return r; + + field_type = *u8; + } + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + size_t where, end; + char *b; + void *q; + + end = bus_gvariant_read_word_le((uint8_t*) offsets + i*sz, sz); + + if (end < ri) + return -EBADMSG; + + where = ri = ALIGN_TO(ri, 8); + item_size = end - ri; + r = message_peek_fields(m, &where, 1, item_size, &q); + if (r < 0) + return r; + + b = memrchr(q, 0, item_size); + if (!b) + return -EBADMSG; + + sig = strndup(b+1, item_size - (b+1-(char*) q)); + if (!sig) + return -ENOMEM; + + signature = sig; + item_size = b - (char*) q; + } else { + r = message_peek_field_signature(m, &ri, 0, &signature); + if (r < 0) + return r; + } + + switch (field_type) { + + case _BUS_MESSAGE_HEADER_INVALID: + return -EBADMSG; + + case BUS_MESSAGE_HEADER_PATH: + + if (m->path) + return -EBADMSG; + + if (!streq(signature, "o")) + return -EBADMSG; + + r = message_peek_field_string(m, object_path_is_valid, &ri, item_size, &m->path); + break; + + case BUS_MESSAGE_HEADER_INTERFACE: + + if (m->interface) + return -EBADMSG; + + if (!streq(signature, "s")) + return -EBADMSG; + + r = message_peek_field_string(m, interface_name_is_valid, &ri, item_size, &m->interface); + break; + + case BUS_MESSAGE_HEADER_MEMBER: + + if (m->member) + return -EBADMSG; + + if (!streq(signature, "s")) + return -EBADMSG; + + r = message_peek_field_string(m, member_name_is_valid, &ri, item_size, &m->member); + break; + + case BUS_MESSAGE_HEADER_ERROR_NAME: + + if (m->error.name) + return -EBADMSG; + + if (!streq(signature, "s")) + return -EBADMSG; + + r = message_peek_field_string(m, error_name_is_valid, &ri, item_size, &m->error.name); + if (r >= 0) + m->error._need_free = -1; + + break; + + case BUS_MESSAGE_HEADER_DESTINATION: + + if (m->destination) + return -EBADMSG; + + if (!streq(signature, "s")) + return -EBADMSG; + + r = message_peek_field_string(m, service_name_is_valid, &ri, item_size, &m->destination); + break; + + case BUS_MESSAGE_HEADER_SENDER: + + if (m->sender) + return -EBADMSG; + + if (!streq(signature, "s")) + return -EBADMSG; + + r = message_peek_field_string(m, service_name_is_valid, &ri, item_size, &m->sender); + + if (r >= 0 && m->sender[0] == ':' && m->bus->bus_client && !m->bus->is_kernel) { + m->creds.unique_name = (char*) m->sender; + m->creds.mask |= SD_BUS_CREDS_UNIQUE_NAME & m->bus->creds_mask; + } + + break; + + + case BUS_MESSAGE_HEADER_SIGNATURE: { + const char *s; + char *c; + + if (BUS_MESSAGE_IS_GVARIANT(m)) /* only applies to dbus1 */ + return -EBADMSG; + + if (m->root_container.signature) + return -EBADMSG; + + if (!streq(signature, "g")) + return -EBADMSG; + + r = message_peek_field_signature(m, &ri, item_size, &s); + if (r < 0) + return r; + + c = strdup(s); + if (!c) + return -ENOMEM; + + free(m->root_container.signature); + m->root_container.signature = c; + break; + } + + case BUS_MESSAGE_HEADER_REPLY_SERIAL: + + if (m->reply_cookie != 0) + return -EBADMSG; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + /* 64bit on dbus2 */ + + if (!streq(signature, "t")) + return -EBADMSG; + + r = message_peek_field_uint64(m, &ri, item_size, &m->reply_cookie); + if (r < 0) + return r; + } else { + /* 32bit on dbus1 */ + uint32_t serial; + + if (!streq(signature, "u")) + return -EBADMSG; + + r = message_peek_field_uint32(m, &ri, item_size, &serial); + if (r < 0) + return r; + + m->reply_cookie = serial; + } + + if (m->reply_cookie == 0) + return -EBADMSG; + + break; + + case BUS_MESSAGE_HEADER_UNIX_FDS: + if (unix_fds_set) + return -EBADMSG; + + if (!streq(signature, "u")) + return -EBADMSG; + + r = message_peek_field_uint32(m, &ri, item_size, &unix_fds); + if (r < 0) + return -EBADMSG; + + unix_fds_set = true; + break; + + default: + if (!BUS_MESSAGE_IS_GVARIANT(m)) + r = message_skip_fields(m, &ri, (uint32_t) -1, (const char **) &signature); + } + + if (r < 0) + return r; + + i++; + } + + if (m->n_fds != unix_fds) + return -EBADMSG; + + switch (m->header->type) { + + case SD_BUS_MESSAGE_SIGNAL: + if (!m->path || !m->interface || !m->member) + return -EBADMSG; + + if (m->reply_cookie != 0) + return -EBADMSG; + + break; + + case SD_BUS_MESSAGE_METHOD_CALL: + + if (!m->path || !m->member) + return -EBADMSG; + + if (m->reply_cookie != 0) + return -EBADMSG; + + break; + + case SD_BUS_MESSAGE_METHOD_RETURN: + + if (m->reply_cookie == 0) + return -EBADMSG; + break; + + case SD_BUS_MESSAGE_METHOD_ERROR: + + if (m->reply_cookie == 0 || !m->error.name) + return -EBADMSG; + break; + } + + /* Refuse non-local messages that claim they are local */ + if (streq_ptr(m->path, "/org/freedesktop/DBus/Local")) + return -EBADMSG; + if (streq_ptr(m->interface, "org.freedesktop.DBus.Local")) + return -EBADMSG; + if (streq_ptr(m->sender, "org.freedesktop.DBus.Local")) + return -EBADMSG; + + m->root_container.end = m->user_body_size; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + r = build_struct_offsets( + m, + m->root_container.signature, + m->user_body_size, + &m->root_container.item_size, + &m->root_container.offsets, + &m->root_container.n_offsets); + if (r < 0) + return r; + } + + /* Try to read the error message, but if we can't it's a non-issue */ + if (m->header->type == SD_BUS_MESSAGE_METHOD_ERROR) + (void) sd_bus_message_read(m, "s", &m->error.message); + + return 0; +} + +_public_ int sd_bus_message_set_destination(sd_bus_message *m, const char *destination) { + assert_return(m, -EINVAL); + assert_return(destination, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(!m->destination, -EEXIST); + + return message_append_field_string(m, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, destination, &m->destination); +} + +int bus_message_get_blob(sd_bus_message *m, void **buffer, size_t *sz) { + size_t total; + void *p, *e; + unsigned i; + struct bus_body_part *part; + + assert(m); + assert(buffer); + assert(sz); + + total = BUS_MESSAGE_SIZE(m); + + p = malloc(total); + if (!p) + return -ENOMEM; + + e = mempcpy(p, m->header, BUS_MESSAGE_BODY_BEGIN(m)); + MESSAGE_FOREACH_PART(part, i, m) + e = mempcpy(e, part->data, part->size); + + assert(total == (size_t) ((uint8_t*) e - (uint8_t*) p)); + + *buffer = p; + *sz = total; + + return 0; +} + +int bus_message_read_strv_extend(sd_bus_message *m, char ***l) { + const char *s; + int r; + + assert(m); + assert(l); + + r = sd_bus_message_enter_container(m, 'a', "s"); + if (r <= 0) + return r; + + while ((r = sd_bus_message_read_basic(m, 's', &s)) > 0) { + r = strv_extend(l, s); + if (r < 0) + return r; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + return 1; +} + +_public_ int sd_bus_message_read_strv(sd_bus_message *m, char ***l) { + char **strv = NULL; + int r; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + assert_return(l, -EINVAL); + + r = bus_message_read_strv_extend(m, &strv); + if (r <= 0) { + strv_free(strv); + return r; + } + + *l = strv; + return 1; +} + +static int bus_message_get_arg_skip( + sd_bus_message *m, + unsigned i, + char *_type, + const char **_contents) { + + unsigned j; + int r; + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + for (j = 0;; j++) { + const char *contents; + char type; + + r = sd_bus_message_peek_type(m, &type, &contents); + if (r < 0) + return r; + if (r == 0) + return -ENXIO; + + /* Don't match against arguments after the first one we don't understand */ + if (!IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE) && + !(type == SD_BUS_TYPE_ARRAY && STR_IN_SET(contents, "s", "o", "g"))) + return -ENXIO; + + if (j >= i) { + if (_contents) + *_contents = contents; + if (_type) + *_type = type; + return 0; + } + + r = sd_bus_message_skip(m, NULL); + if (r < 0) + return r; + } + +} + +int bus_message_get_arg(sd_bus_message *m, unsigned i, const char **str) { + char type; + int r; + + assert(m); + assert(str); + + r = bus_message_get_arg_skip(m, i, &type, NULL); + if (r < 0) + return r; + + if (!IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE)) + return -ENXIO; + + return sd_bus_message_read_basic(m, type, str); +} + +int bus_message_get_arg_strv(sd_bus_message *m, unsigned i, char ***strv) { + const char *contents; + char type; + int r; + + assert(m); + assert(strv); + + r = bus_message_get_arg_skip(m, i, &type, &contents); + if (r < 0) + return r; + + if (type != SD_BUS_TYPE_ARRAY) + return -ENXIO; + if (!STR_IN_SET(contents, "s", "o", "g")) + return -ENXIO; + + return sd_bus_message_read_strv(m, strv); +} + +_public_ int sd_bus_message_get_errno(sd_bus_message *m) { + assert_return(m, EINVAL); + + if (m->header->type != SD_BUS_MESSAGE_METHOD_ERROR) + return 0; + + return sd_bus_error_get_errno(&m->error); +} + +_public_ const char* sd_bus_message_get_signature(sd_bus_message *m, int complete) { + struct bus_container *c; + + assert_return(m, NULL); + + c = complete ? &m->root_container : message_get_container(m); + return strempty(c->signature); +} + +_public_ int sd_bus_message_is_empty(sd_bus_message *m) { + assert_return(m, -EINVAL); + + return isempty(m->root_container.signature); +} + +_public_ int sd_bus_message_has_signature(sd_bus_message *m, const char *signature) { + assert_return(m, -EINVAL); + + return streq(strempty(m->root_container.signature), strempty(signature)); +} + +_public_ int sd_bus_message_copy(sd_bus_message *m, sd_bus_message *source, int all) { + bool done_something = false; + int r; + + assert_return(m, -EINVAL); + assert_return(source, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(source->sealed, -EPERM); + + do { + const char *contents; + char type; + union { + uint8_t u8; + uint16_t u16; + int16_t s16; + uint32_t u32; + int32_t s32; + uint64_t u64; + int64_t s64; + double d64; + const char *string; + int i; + } basic; + + r = sd_bus_message_peek_type(source, &type, &contents); + if (r < 0) + return r; + if (r == 0) + break; + + done_something = true; + + if (bus_type_is_container(type) > 0) { + + r = sd_bus_message_enter_container(source, type, contents); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, type, contents); + if (r < 0) + return r; + + r = sd_bus_message_copy(m, source, true); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(source); + if (r < 0) + return r; + + continue; + } + + r = sd_bus_message_read_basic(source, type, &basic); + if (r < 0) + return r; + + assert(r > 0); + + if (type == SD_BUS_TYPE_OBJECT_PATH || + type == SD_BUS_TYPE_SIGNATURE || + type == SD_BUS_TYPE_STRING) + r = sd_bus_message_append_basic(m, type, basic.string); + else + r = sd_bus_message_append_basic(m, type, &basic); + + if (r < 0) + return r; + + } while (all); + + return done_something; +} + +_public_ int sd_bus_message_verify_type(sd_bus_message *m, char type, const char *contents) { + const char *c; + char t; + int r; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + assert_return(!type || bus_type_is_valid(type), -EINVAL); + assert_return(!contents || signature_is_valid(contents, true), -EINVAL); + assert_return(type || contents, -EINVAL); + assert_return(!contents || !type || bus_type_is_container(type), -EINVAL); + + r = sd_bus_message_peek_type(m, &t, &c); + if (r <= 0) + return r; + + if (type != 0 && type != t) + return 0; + + if (contents && !streq_ptr(contents, c)) + return 0; + + return 1; +} + +_public_ sd_bus *sd_bus_message_get_bus(sd_bus_message *m) { + assert_return(m, NULL); + + return m->bus; +} + +int bus_message_remarshal(sd_bus *bus, sd_bus_message **m) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *n = NULL; + usec_t timeout; + int r; + + assert(bus); + assert(m); + assert(*m); + + switch ((*m)->header->type) { + + case SD_BUS_MESSAGE_SIGNAL: + r = sd_bus_message_new_signal(bus, &n, (*m)->path, (*m)->interface, (*m)->member); + if (r < 0) + return r; + + break; + + case SD_BUS_MESSAGE_METHOD_CALL: + r = sd_bus_message_new_method_call(bus, &n, (*m)->destination, (*m)->path, (*m)->interface, (*m)->member); + if (r < 0) + return r; + + break; + + case SD_BUS_MESSAGE_METHOD_RETURN: + case SD_BUS_MESSAGE_METHOD_ERROR: + + n = message_new(bus, (*m)->header->type); + if (!n) + return -ENOMEM; + + n->reply_cookie = (*m)->reply_cookie; + + r = message_append_reply_cookie(n, n->reply_cookie); + if (r < 0) + return r; + + if ((*m)->header->type == SD_BUS_MESSAGE_METHOD_ERROR && (*m)->error.name) { + r = message_append_field_string(n, BUS_MESSAGE_HEADER_ERROR_NAME, SD_BUS_TYPE_STRING, (*m)->error.name, &n->error.message); + if (r < 0) + return r; + + n->error._need_free = -1; + } + + break; + + default: + return -EINVAL; + } + + if ((*m)->destination && !n->destination) { + r = message_append_field_string(n, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, (*m)->destination, &n->destination); + if (r < 0) + return r; + } + + if ((*m)->sender && !n->sender) { + r = message_append_field_string(n, BUS_MESSAGE_HEADER_SENDER, SD_BUS_TYPE_STRING, (*m)->sender, &n->sender); + if (r < 0) + return r; + } + + n->header->flags |= (*m)->header->flags & (BUS_MESSAGE_NO_REPLY_EXPECTED|BUS_MESSAGE_NO_AUTO_START); + + r = sd_bus_message_copy(n, *m, true); + if (r < 0) + return r; + + timeout = (*m)->timeout; + if (timeout == 0 && !((*m)->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED)) + timeout = BUS_DEFAULT_TIMEOUT; + + r = bus_message_seal(n, BUS_MESSAGE_COOKIE(*m), timeout); + if (r < 0) + return r; + + sd_bus_message_unref(*m); + *m = n; + n = NULL; + + return 0; +} + +int bus_message_append_sender(sd_bus_message *m, const char *sender) { + assert(m); + assert(sender); + + assert_return(!m->sealed, -EPERM); + assert_return(!m->sender, -EPERM); + + return message_append_field_string(m, BUS_MESSAGE_HEADER_SENDER, SD_BUS_TYPE_STRING, sender, &m->sender); +} + +_public_ int sd_bus_message_get_priority(sd_bus_message *m, int64_t *priority) { + assert_return(m, -EINVAL); + assert_return(priority, -EINVAL); + + *priority = m->priority; + return 0; +} + +_public_ int sd_bus_message_set_priority(sd_bus_message *m, int64_t priority) { + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + m->priority = priority; + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-message.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-message.h new file mode 100644 index 0000000000..6a2c2d533c --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-message.h @@ -0,0 +1,244 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 +#include + +#include + +#include "bus-creds.h" +#include "bus-protocol.h" +#include "macro.h" +#include "time-util.h" + +struct bus_container { + char enclosing; + bool need_offsets:1; + + /* Indexes into the signature string */ + unsigned index, saved_index; + char *signature; + + size_t before, begin, end; + + /* dbus1: pointer to the array size value, if this is a value */ + uint32_t *array_size; + + /* gvariant: list of offsets to end of children if this is struct/dict entry/array */ + size_t *offsets, n_offsets, offsets_allocated, offset_index; + size_t item_size; + + char *peeked_signature; +}; + +struct bus_body_part { + struct bus_body_part *next; + void *data; + void *mmap_begin; + size_t size; + size_t mapped; + size_t allocated; + uint64_t memfd_offset; + int memfd; + bool free_this:1; + bool munmap_this:1; + bool sealed:1; + bool is_zero:1; +}; + +struct sd_bus_message { + unsigned n_ref; + + sd_bus *bus; + + uint64_t reply_cookie; + + const char *path; + const char *interface; + const char *member; + const char *destination; + const char *sender; + + sd_bus_error error; + + sd_bus_creds creds; + + usec_t monotonic; + usec_t realtime; + uint64_t seqnum; + int64_t priority; + uint64_t verify_destination_id; + + bool sealed:1; + bool dont_send:1; + bool allow_fds:1; + bool free_header:1; + bool free_kdbus:1; + bool free_fds:1; + bool release_kdbus:1; + bool poisoned:1; + + /* The first and last bytes of the message */ + struct bus_header *header; + void *footer; + + /* How many bytes are accessible in the above pointers */ + size_t header_accessible; + size_t footer_accessible; + + size_t fields_size; + size_t body_size; + size_t user_body_size; + + struct bus_body_part body; + struct bus_body_part *body_end; + unsigned n_body_parts; + + size_t rindex; + struct bus_body_part *cached_rindex_part; + size_t cached_rindex_part_begin; + + uint32_t n_fds; + int *fds; + + struct bus_container root_container, *containers; + size_t n_containers; + size_t containers_allocated; + + struct iovec *iovec; + struct iovec iovec_fixed[2]; + unsigned n_iovec; + + struct kdbus_msg *kdbus; + + char *peeked_signature; + + /* If set replies to this message must carry the signature + * specified here to successfully seal. This is initialized + * from the vtable data */ + const char *enforced_reply_signature; + + usec_t timeout; + + char sender_buffer[3 + DECIMAL_STR_MAX(uint64_t) + 1]; + char destination_buffer[3 + DECIMAL_STR_MAX(uint64_t) + 1]; + char *destination_ptr; + + size_t header_offsets[_BUS_MESSAGE_HEADER_MAX]; + unsigned n_header_offsets; +}; + +static inline bool BUS_MESSAGE_NEED_BSWAP(sd_bus_message *m) { + return m->header->endian != BUS_NATIVE_ENDIAN; +} + +static inline uint16_t BUS_MESSAGE_BSWAP16(sd_bus_message *m, uint16_t u) { + return BUS_MESSAGE_NEED_BSWAP(m) ? bswap_16(u) : u; +} + +static inline uint32_t BUS_MESSAGE_BSWAP32(sd_bus_message *m, uint32_t u) { + return BUS_MESSAGE_NEED_BSWAP(m) ? bswap_32(u) : u; +} + +static inline uint64_t BUS_MESSAGE_BSWAP64(sd_bus_message *m, uint64_t u) { + return BUS_MESSAGE_NEED_BSWAP(m) ? bswap_64(u) : u; +} + +static inline uint64_t BUS_MESSAGE_COOKIE(sd_bus_message *m) { + if (m->header->version == 2) + return BUS_MESSAGE_BSWAP64(m, m->header->dbus2.cookie); + + return BUS_MESSAGE_BSWAP32(m, m->header->dbus1.serial); +} + +static inline size_t BUS_MESSAGE_SIZE(sd_bus_message *m) { + return + sizeof(struct bus_header) + + ALIGN8(m->fields_size) + + m->body_size; +} + +static inline size_t BUS_MESSAGE_BODY_BEGIN(sd_bus_message *m) { + return + sizeof(struct bus_header) + + ALIGN8(m->fields_size); +} + +static inline void* BUS_MESSAGE_FIELDS(sd_bus_message *m) { + return (uint8_t*) m->header + sizeof(struct bus_header); +} + +static inline bool BUS_MESSAGE_IS_GVARIANT(sd_bus_message *m) { + return m->header->version == 2; +} + +int bus_message_seal(sd_bus_message *m, uint64_t serial, usec_t timeout); +int bus_message_get_blob(sd_bus_message *m, void **buffer, size_t *sz); +int bus_message_read_strv_extend(sd_bus_message *m, char ***l); + +int bus_message_from_header( + sd_bus *bus, + void *header, + size_t header_accessible, + void *footer, + size_t footer_accessible, + size_t message_size, + int *fds, + unsigned n_fds, + const char *label, + size_t extra, + sd_bus_message **ret); + +int bus_message_from_malloc( + sd_bus *bus, + void *buffer, + size_t length, + int *fds, + unsigned n_fds, + const char *label, + sd_bus_message **ret); + +int bus_message_get_arg(sd_bus_message *m, unsigned i, const char **str); +int bus_message_get_arg_strv(sd_bus_message *m, unsigned i, char ***strv); + +int bus_message_append_ap(sd_bus_message *m, const char *types, va_list ap); + +int bus_message_parse_fields(sd_bus_message *m); + +struct bus_body_part *message_append_part(sd_bus_message *m); + +#define MESSAGE_FOREACH_PART(part, i, m) \ + for ((i) = 0, (part) = &(m)->body; (i) < (m)->n_body_parts; (i)++, (part) = (part)->next) + +int bus_body_part_map(struct bus_body_part *part); +void bus_body_part_unmap(struct bus_body_part *part); + +int bus_message_to_errno(sd_bus_message *m); + +int bus_message_new_synthetic_error(sd_bus *bus, uint64_t serial, const sd_bus_error *e, sd_bus_message **m); + +int bus_message_remarshal(sd_bus *bus, sd_bus_message **m); + +int bus_message_append_sender(sd_bus_message *m, const char *sender); + +void bus_message_set_sender_driver(sd_bus *bus, sd_bus_message *m); +void bus_message_set_sender_local(sd_bus *bus, sd_bus_message *m); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-objects.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-objects.c new file mode 100644 index 0000000000..9bd07ffcab --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-objects.c @@ -0,0 +1,2806 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "alloc-util.h" +#include "bus-internal.h" +#include "bus-introspect.h" +#include "bus-message.h" +#include "bus-objects.h" +#include "bus-signature.h" +#include "bus-slot.h" +#include "bus-type.h" +#include "bus-util.h" +#include "set.h" +#include "string-util.h" +#include "strv.h" + +static int node_vtable_get_userdata( + sd_bus *bus, + const char *path, + struct node_vtable *c, + void **userdata, + sd_bus_error *error) { + + sd_bus_slot *s; + void *u; + int r; + + assert(bus); + assert(path); + assert(c); + + s = container_of(c, sd_bus_slot, node_vtable); + u = s->userdata; + if (c->find) { + bus->current_slot = sd_bus_slot_ref(s); + bus->current_userdata = u; + r = c->find(bus, path, c->interface, u, &u, error); + bus->current_userdata = NULL; + bus->current_slot = sd_bus_slot_unref(s); + + if (r < 0) + return r; + if (sd_bus_error_is_set(error)) + return -sd_bus_error_get_errno(error); + if (r == 0) + return r; + } + + if (userdata) + *userdata = u; + + return 1; +} + +static void *vtable_method_convert_userdata(const sd_bus_vtable *p, void *u) { + assert(p); + + return (uint8_t*) u + p->x.method.offset; +} + +static void *vtable_property_convert_userdata(const sd_bus_vtable *p, void *u) { + assert(p); + + return (uint8_t*) u + p->x.property.offset; +} + +static int vtable_property_get_userdata( + sd_bus *bus, + const char *path, + struct vtable_member *p, + void **userdata, + sd_bus_error *error) { + + void *u; + int r; + + assert(bus); + assert(path); + assert(p); + assert(userdata); + + r = node_vtable_get_userdata(bus, path, p->parent, &u, error); + if (r <= 0) + return r; + if (bus->nodes_modified) + return 0; + + *userdata = vtable_property_convert_userdata(p->vtable, u); + return 1; +} + +static int add_enumerated_to_set( + sd_bus *bus, + const char *prefix, + struct node_enumerator *first, + Set *s, + sd_bus_error *error) { + + struct node_enumerator *c; + int r; + + assert(bus); + assert(prefix); + assert(s); + + LIST_FOREACH(enumerators, c, first) { + char **children = NULL, **k; + sd_bus_slot *slot; + + if (bus->nodes_modified) + return 0; + + slot = container_of(c, sd_bus_slot, node_enumerator); + + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_userdata = slot->userdata; + r = c->callback(bus, prefix, slot->userdata, &children, error); + bus->current_userdata = NULL; + bus->current_slot = sd_bus_slot_unref(slot); + + if (r < 0) + return r; + if (sd_bus_error_is_set(error)) + return -sd_bus_error_get_errno(error); + + STRV_FOREACH(k, children) { + if (r < 0) { + free(*k); + continue; + } + + if (!object_path_is_valid(*k)) { + free(*k); + r = -EINVAL; + continue; + } + + if (!object_path_startswith(*k, prefix)) { + free(*k); + continue; + } + + r = set_consume(s, *k); + if (r == -EEXIST) + r = 0; + } + + free(children); + if (r < 0) + return r; + } + + return 0; +} + +enum { + /* if set, add_subtree() works recursively */ + CHILDREN_RECURSIVE = (1U << 1), + /* if set, add_subtree() scans object-manager hierarchies recursively */ + CHILDREN_SUBHIERARCHIES = (1U << 0), +}; + +static int add_subtree_to_set( + sd_bus *bus, + const char *prefix, + struct node *n, + unsigned int flags, + Set *s, + sd_bus_error *error) { + + struct node *i; + int r; + + assert(bus); + assert(prefix); + assert(n); + assert(s); + + r = add_enumerated_to_set(bus, prefix, n->enumerators, s, error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + + LIST_FOREACH(siblings, i, n->child) { + char *t; + + if (!object_path_startswith(i->path, prefix)) + continue; + + t = strdup(i->path); + if (!t) + return -ENOMEM; + + r = set_consume(s, t); + if (r < 0 && r != -EEXIST) + return r; + + if ((flags & CHILDREN_RECURSIVE) && + ((flags & CHILDREN_SUBHIERARCHIES) || !i->object_managers)) { + r = add_subtree_to_set(bus, prefix, i, flags, s, error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + } + } + + return 0; +} + +static int get_child_nodes( + sd_bus *bus, + const char *prefix, + struct node *n, + unsigned int flags, + Set **_s, + sd_bus_error *error) { + + Set *s = NULL; + int r; + + assert(bus); + assert(prefix); + assert(n); + assert(_s); + + s = set_new(&string_hash_ops); + if (!s) + return -ENOMEM; + + r = add_subtree_to_set(bus, prefix, n, flags, s, error); + if (r < 0) { + set_free_free(s); + return r; + } + + *_s = s; + return 0; +} + +static int node_callbacks_run( + sd_bus *bus, + sd_bus_message *m, + struct node_callback *first, + bool require_fallback, + bool *found_object) { + + struct node_callback *c; + int r; + + assert(bus); + assert(m); + assert(found_object); + + LIST_FOREACH(callbacks, c, first) { + _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; + sd_bus_slot *slot; + + if (bus->nodes_modified) + return 0; + + if (require_fallback && !c->is_fallback) + continue; + + *found_object = true; + + if (c->last_iteration == bus->iteration_counter) + continue; + + c->last_iteration = bus->iteration_counter; + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + slot = container_of(c, sd_bus_slot, node_callback); + + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_handler = c->callback; + bus->current_userdata = slot->userdata; + r = c->callback(m, slot->userdata, &error_buffer); + bus->current_userdata = NULL; + bus->current_handler = NULL; + bus->current_slot = sd_bus_slot_unref(slot); + + r = bus_maybe_reply_error(m, r, &error_buffer); + if (r != 0) + return r; + } + + return 0; +} + +#define CAPABILITY_SHIFT(x) (((x) >> __builtin_ctzll(_SD_BUS_VTABLE_CAPABILITY_MASK)) & 0xFFFF) + +static int check_access(sd_bus *bus, sd_bus_message *m, struct vtable_member *c, sd_bus_error *error) { + uint64_t cap; + int r; + + assert(bus); + assert(m); + assert(c); + + /* If the entire bus is trusted let's grant access */ + if (bus->trusted) + return 0; + + /* If the member is marked UNPRIVILEGED let's grant access */ + if (c->vtable->flags & SD_BUS_VTABLE_UNPRIVILEGED) + return 0; + + /* Check have the caller has the requested capability + * set. Note that the flags value contains the capability + * number plus one, which we need to subtract here. We do this + * so that we have 0 as special value for "default + * capability". */ + cap = CAPABILITY_SHIFT(c->vtable->flags); + if (cap == 0) + cap = CAPABILITY_SHIFT(c->parent->vtable[0].flags); + if (cap == 0) + cap = CAP_SYS_ADMIN; + else + cap--; + + r = sd_bus_query_sender_privilege(m, cap); + if (r < 0) + return r; + if (r > 0) + return 0; + + return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Access to %s.%s() not permitted.", c->interface, c->member); +} + +static int method_callbacks_run( + sd_bus *bus, + sd_bus_message *m, + struct vtable_member *c, + bool require_fallback, + bool *found_object) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *signature; + void *u; + int r; + + assert(bus); + assert(m); + assert(c); + assert(found_object); + + if (require_fallback && !c->parent->is_fallback) + return 0; + + r = check_access(bus, m, c, &error); + if (r < 0) + return bus_maybe_reply_error(m, r, &error); + + r = node_vtable_get_userdata(bus, m->path, c->parent, &u, &error); + if (r <= 0) + return bus_maybe_reply_error(m, r, &error); + if (bus->nodes_modified) + return 0; + + u = vtable_method_convert_userdata(c->vtable, u); + + *found_object = true; + + if (c->last_iteration == bus->iteration_counter) + return 0; + + c->last_iteration = bus->iteration_counter; + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + signature = sd_bus_message_get_signature(m, true); + if (!signature) + return -EINVAL; + + if (!streq(strempty(c->vtable->x.method.signature), signature)) + return sd_bus_reply_method_errorf( + m, + SD_BUS_ERROR_INVALID_ARGS, + "Invalid arguments '%s' to call %s.%s(), expecting '%s'.", + signature, c->interface, c->member, strempty(c->vtable->x.method.signature)); + + /* Keep track what the signature of the reply to this message + * should be, so that this can be enforced when sealing the + * reply. */ + m->enforced_reply_signature = strempty(c->vtable->x.method.result); + + if (c->vtable->x.method.handler) { + sd_bus_slot *slot; + + slot = container_of(c->parent, sd_bus_slot, node_vtable); + + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_handler = c->vtable->x.method.handler; + bus->current_userdata = u; + r = c->vtable->x.method.handler(m, u, &error); + bus->current_userdata = NULL; + bus->current_handler = NULL; + bus->current_slot = sd_bus_slot_unref(slot); + + return bus_maybe_reply_error(m, r, &error); + } + + /* If the method callback is NULL, make this a successful NOP */ + r = sd_bus_reply_method_return(m, NULL); + if (r < 0) + return r; + + return 1; +} + +static int invoke_property_get( + sd_bus *bus, + sd_bus_slot *slot, + const sd_bus_vtable *v, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + const void *p; + int r; + + assert(bus); + assert(slot); + assert(v); + assert(path); + assert(interface); + assert(property); + assert(reply); + + if (v->x.property.get) { + + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_userdata = userdata; + r = v->x.property.get(bus, path, interface, property, reply, userdata, error); + bus->current_userdata = NULL; + bus->current_slot = sd_bus_slot_unref(slot); + + if (r < 0) + return r; + if (sd_bus_error_is_set(error)) + return -sd_bus_error_get_errno(error); + return r; + } + + /* Automatic handling if no callback is defined. */ + + if (streq(v->x.property.signature, "as")) + return sd_bus_message_append_strv(reply, *(char***) userdata); + + assert(signature_is_single(v->x.property.signature, false)); + assert(bus_type_is_basic(v->x.property.signature[0])); + + switch (v->x.property.signature[0]) { + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_SIGNATURE: + p = strempty(*(char**) userdata); + break; + + case SD_BUS_TYPE_OBJECT_PATH: + p = *(char**) userdata; + assert(p); + break; + + default: + p = userdata; + break; + } + + return sd_bus_message_append_basic(reply, v->x.property.signature[0], p); +} + +static int invoke_property_set( + sd_bus *bus, + sd_bus_slot *slot, + const sd_bus_vtable *v, + const char *path, + const char *interface, + const char *property, + sd_bus_message *value, + void *userdata, + sd_bus_error *error) { + + int r; + + assert(bus); + assert(slot); + assert(v); + assert(path); + assert(interface); + assert(property); + assert(value); + + if (v->x.property.set) { + + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_userdata = userdata; + r = v->x.property.set(bus, path, interface, property, value, userdata, error); + bus->current_userdata = NULL; + bus->current_slot = sd_bus_slot_unref(slot); + + if (r < 0) + return r; + if (sd_bus_error_is_set(error)) + return -sd_bus_error_get_errno(error); + return r; + } + + /* Automatic handling if no callback is defined. */ + + assert(signature_is_single(v->x.property.signature, false)); + assert(bus_type_is_basic(v->x.property.signature[0])); + + switch (v->x.property.signature[0]) { + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: { + const char *p; + char *n; + + r = sd_bus_message_read_basic(value, v->x.property.signature[0], &p); + if (r < 0) + return r; + + n = strdup(p); + if (!n) + return -ENOMEM; + + free(*(char**) userdata); + *(char**) userdata = n; + + break; + } + + default: + r = sd_bus_message_read_basic(value, v->x.property.signature[0], userdata); + if (r < 0) + return r; + + break; + } + + return 1; +} + +static int property_get_set_callbacks_run( + sd_bus *bus, + sd_bus_message *m, + struct vtable_member *c, + bool require_fallback, + bool is_get, + bool *found_object) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + sd_bus_slot *slot; + void *u = NULL; + int r; + + assert(bus); + assert(m); + assert(c); + assert(found_object); + + if (require_fallback && !c->parent->is_fallback) + return 0; + + r = vtable_property_get_userdata(bus, m->path, c, &u, &error); + if (r <= 0) + return bus_maybe_reply_error(m, r, &error); + if (bus->nodes_modified) + return 0; + + slot = container_of(c->parent, sd_bus_slot, node_vtable); + + *found_object = true; + + r = sd_bus_message_new_method_return(m, &reply); + if (r < 0) + return r; + + if (is_get) { + /* Note that we do not protect against reexecution + * here (using the last_iteration check, see below), + * should the node tree have changed and we got called + * again. We assume that property Get() calls are + * ultimately without side-effects or if they aren't + * then at least idempotent. */ + + r = sd_bus_message_open_container(reply, 'v', c->vtable->x.property.signature); + if (r < 0) + return r; + + /* Note that we do not do an access check here. Read + * access to properties is always unrestricted, since + * PropertiesChanged signals broadcast contents + * anyway. */ + + r = invoke_property_get(bus, slot, c->vtable, m->path, c->interface, c->member, reply, u, &error); + if (r < 0) + return bus_maybe_reply_error(m, r, &error); + + if (bus->nodes_modified) + return 0; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + } else { + const char *signature = NULL; + char type = 0; + + if (c->vtable->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY) + return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_PROPERTY_READ_ONLY, "Property '%s' is not writable.", c->member); + + /* Avoid that we call the set routine more than once + * if the processing of this message got restarted + * because the node tree changed. */ + if (c->last_iteration == bus->iteration_counter) + return 0; + + c->last_iteration = bus->iteration_counter; + + r = sd_bus_message_peek_type(m, &type, &signature); + if (r < 0) + return r; + + if (type != 'v' || !streq(strempty(signature), strempty(c->vtable->x.property.signature))) + return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Incorrect parameters for property '%s', expected '%s', got '%s'.", c->member, strempty(c->vtable->x.property.signature), strempty(signature)); + + r = sd_bus_message_enter_container(m, 'v', c->vtable->x.property.signature); + if (r < 0) + return r; + + r = check_access(bus, m, c, &error); + if (r < 0) + return bus_maybe_reply_error(m, r, &error); + + r = invoke_property_set(bus, slot, c->vtable, m->path, c->interface, c->member, m, u, &error); + if (r < 0) + return bus_maybe_reply_error(m, r, &error); + + if (bus->nodes_modified) + return 0; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + } + + r = sd_bus_send(bus, reply, NULL); + if (r < 0) + return r; + + return 1; +} + +static int vtable_append_one_property( + sd_bus *bus, + sd_bus_message *reply, + const char *path, + struct node_vtable *c, + const sd_bus_vtable *v, + void *userdata, + sd_bus_error *error) { + + sd_bus_slot *slot; + int r; + + assert(bus); + assert(reply); + assert(path); + assert(c); + assert(v); + + r = sd_bus_message_open_container(reply, 'e', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "s", v->x.property.member); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'v', v->x.property.signature); + if (r < 0) + return r; + + slot = container_of(c, sd_bus_slot, node_vtable); + + r = invoke_property_get(bus, slot, v, path, c->interface, v->x.property.member, reply, vtable_property_convert_userdata(v, userdata), error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return 0; +} + +static int vtable_append_all_properties( + sd_bus *bus, + sd_bus_message *reply, + const char *path, + struct node_vtable *c, + void *userdata, + sd_bus_error *error) { + + const sd_bus_vtable *v; + int r; + + assert(bus); + assert(reply); + assert(path); + assert(c); + + if (c->vtable[0].flags & SD_BUS_VTABLE_HIDDEN) + return 1; + + for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; v++) { + if (v->type != _SD_BUS_VTABLE_PROPERTY && v->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY) + continue; + + if (v->flags & SD_BUS_VTABLE_HIDDEN) + continue; + + if (v->flags & SD_BUS_VTABLE_PROPERTY_EXPLICIT) + continue; + + r = vtable_append_one_property(bus, reply, path, c, v, userdata, error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + } + + return 1; +} + +static int property_get_all_callbacks_run( + sd_bus *bus, + sd_bus_message *m, + struct node_vtable *first, + bool require_fallback, + const char *iface, + bool *found_object) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + struct node_vtable *c; + bool found_interface; + int r; + + assert(bus); + assert(m); + assert(found_object); + + r = sd_bus_message_new_method_return(m, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "{sv}"); + if (r < 0) + return r; + + found_interface = !iface || + streq(iface, "org.freedesktop.DBus.Properties") || + streq(iface, "org.freedesktop.DBus.Peer") || + streq(iface, "org.freedesktop.DBus.Introspectable"); + + LIST_FOREACH(vtables, c, first) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + void *u; + + if (require_fallback && !c->is_fallback) + continue; + + r = node_vtable_get_userdata(bus, m->path, c, &u, &error); + if (r < 0) + return bus_maybe_reply_error(m, r, &error); + if (bus->nodes_modified) + return 0; + if (r == 0) + continue; + + *found_object = true; + + if (iface && !streq(c->interface, iface)) + continue; + found_interface = true; + + r = vtable_append_all_properties(bus, reply, m->path, c, u, &error); + if (r < 0) + return bus_maybe_reply_error(m, r, &error); + if (bus->nodes_modified) + return 0; + } + + if (!found_interface) { + r = sd_bus_reply_method_errorf( + m, + SD_BUS_ERROR_UNKNOWN_INTERFACE, + "Unknown interface '%s'.", iface); + if (r < 0) + return r; + + return 1; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + r = sd_bus_send(bus, reply, NULL); + if (r < 0) + return r; + + return 1; +} + +static int bus_node_exists( + sd_bus *bus, + struct node *n, + const char *path, + bool require_fallback) { + + struct node_vtable *c; + struct node_callback *k; + int r; + + assert(bus); + assert(n); + assert(path); + + /* Tests if there's anything attached directly to this node + * for the specified path */ + + if (!require_fallback && (n->enumerators || n->object_managers)) + return true; + + LIST_FOREACH(callbacks, k, n->callbacks) { + if (require_fallback && !k->is_fallback) + continue; + + return 1; + } + + LIST_FOREACH(vtables, c, n->vtables) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + if (require_fallback && !c->is_fallback) + continue; + + r = node_vtable_get_userdata(bus, path, c, NULL, &error); + if (r != 0) + return r; + if (bus->nodes_modified) + return 0; + } + + return 0; +} + +static int process_introspect( + sd_bus *bus, + sd_bus_message *m, + struct node *n, + bool require_fallback, + bool *found_object) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_set_free_free_ Set *s = NULL; + const char *previous_interface = NULL; + struct introspect intro; + struct node_vtable *c; + bool empty; + int r; + + assert(bus); + assert(m); + assert(n); + assert(found_object); + + r = get_child_nodes(bus, m->path, n, 0, &s, &error); + if (r < 0) + return bus_maybe_reply_error(m, r, &error); + if (bus->nodes_modified) + return 0; + + r = introspect_begin(&intro, bus->trusted); + if (r < 0) + return r; + + r = introspect_write_default_interfaces(&intro, !require_fallback && n->object_managers); + if (r < 0) + return r; + + empty = set_isempty(s); + + LIST_FOREACH(vtables, c, n->vtables) { + if (require_fallback && !c->is_fallback) + continue; + + r = node_vtable_get_userdata(bus, m->path, c, NULL, &error); + if (r < 0) { + r = bus_maybe_reply_error(m, r, &error); + goto finish; + } + if (bus->nodes_modified) { + r = 0; + goto finish; + } + if (r == 0) + continue; + + empty = false; + + if (c->vtable[0].flags & SD_BUS_VTABLE_HIDDEN) + continue; + + if (!streq_ptr(previous_interface, c->interface)) { + + if (previous_interface) + fputs("
\n", intro.f); + + fprintf(intro.f, " \n", c->interface); + } + + r = introspect_write_interface(&intro, c->vtable); + if (r < 0) + goto finish; + + previous_interface = c->interface; + } + + if (previous_interface) + fputs(" \n", intro.f); + + if (empty) { + /* Nothing?, let's see if we exist at all, and if not + * refuse to do anything */ + r = bus_node_exists(bus, n, m->path, require_fallback); + if (r <= 0) + goto finish; + if (bus->nodes_modified) { + r = 0; + goto finish; + } + } + + *found_object = true; + + r = introspect_write_child_nodes(&intro, s, m->path); + if (r < 0) + goto finish; + + r = introspect_finish(&intro, bus, m, &reply); + if (r < 0) + goto finish; + + r = sd_bus_send(bus, reply, NULL); + if (r < 0) + goto finish; + + r = 1; + +finish: + introspect_free(&intro); + return r; +} + +static int object_manager_serialize_path( + sd_bus *bus, + sd_bus_message *reply, + const char *prefix, + const char *path, + bool require_fallback, + sd_bus_error *error) { + + const char *previous_interface = NULL; + bool found_something = false; + struct node_vtable *i; + struct node *n; + int r; + + assert(bus); + assert(reply); + assert(prefix); + assert(path); + assert(error); + + n = hashmap_get(bus->nodes, prefix); + if (!n) + return 0; + + LIST_FOREACH(vtables, i, n->vtables) { + void *u; + + if (require_fallback && !i->is_fallback) + continue; + + r = node_vtable_get_userdata(bus, path, i, &u, error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + if (r == 0) + continue; + + if (!found_something) { + + /* Open the object part */ + + r = sd_bus_message_open_container(reply, 'e', "oa{sa{sv}}"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "o", path); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "{sa{sv}}"); + if (r < 0) + return r; + + found_something = true; + } + + if (!streq_ptr(previous_interface, i->interface)) { + + /* Maybe close the previous interface part */ + + if (previous_interface) { + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + } + + /* Open the new interface part */ + + r = sd_bus_message_open_container(reply, 'e', "sa{sv}"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "s", i->interface); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "{sv}"); + if (r < 0) + return r; + } + + r = vtable_append_all_properties(bus, reply, path, i, u, error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + + previous_interface = i->interface; + } + + if (previous_interface) { + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + } + + if (found_something) { + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + } + + return 1; +} + +static int object_manager_serialize_path_and_fallbacks( + sd_bus *bus, + sd_bus_message *reply, + const char *path, + sd_bus_error *error) { + + char *prefix; + int r; + + assert(bus); + assert(reply); + assert(path); + assert(error); + + /* First, add all vtables registered for this path */ + r = object_manager_serialize_path(bus, reply, path, path, false, error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + + /* Second, add fallback vtables registered for any of the prefixes */ + prefix = alloca(strlen(path) + 1); + OBJECT_PATH_FOREACH_PREFIX(prefix, path) { + r = object_manager_serialize_path(bus, reply, prefix, path, true, error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + } + + return 0; +} + +static int process_get_managed_objects( + sd_bus *bus, + sd_bus_message *m, + struct node *n, + bool require_fallback, + bool *found_object) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_set_free_free_ Set *s = NULL; + Iterator i; + char *path; + int r; + + assert(bus); + assert(m); + assert(n); + assert(found_object); + + /* Spec says, GetManagedObjects() is only implemented on the root of a + * sub-tree. Therefore, we require a registered object-manager on + * exactly the queried path, otherwise, we refuse to respond. */ + + if (require_fallback || !n->object_managers) + return 0; + + r = get_child_nodes(bus, m->path, n, CHILDREN_RECURSIVE, &s, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + + r = sd_bus_message_new_method_return(m, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "{oa{sa{sv}}}"); + if (r < 0) + return r; + + SET_FOREACH(path, s, i) { + r = object_manager_serialize_path_and_fallbacks(bus, reply, path, &error); + if (r < 0) + return r; + + if (bus->nodes_modified) + return 0; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + r = sd_bus_send(bus, reply, NULL); + if (r < 0) + return r; + + return 1; +} + +static int object_find_and_run( + sd_bus *bus, + sd_bus_message *m, + const char *p, + bool require_fallback, + bool *found_object) { + + struct node *n; + struct vtable_member vtable_key, *v; + int r; + + assert(bus); + assert(m); + assert(p); + assert(found_object); + + n = hashmap_get(bus->nodes, p); + if (!n) + return 0; + + /* First, try object callbacks */ + r = node_callbacks_run(bus, m, n->callbacks, require_fallback, found_object); + if (r != 0) + return r; + if (bus->nodes_modified) + return 0; + + if (!m->interface || !m->member) + return 0; + + /* Then, look for a known method */ + vtable_key.path = (char*) p; + vtable_key.interface = m->interface; + vtable_key.member = m->member; + + v = hashmap_get(bus->vtable_methods, &vtable_key); + if (v) { + r = method_callbacks_run(bus, m, v, require_fallback, found_object); + if (r != 0) + return r; + if (bus->nodes_modified) + return 0; + } + + /* Then, look for a known property */ + if (streq(m->interface, "org.freedesktop.DBus.Properties")) { + bool get = false; + + get = streq(m->member, "Get"); + + if (get || streq(m->member, "Set")) { + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + vtable_key.path = (char*) p; + + r = sd_bus_message_read(m, "ss", &vtable_key.interface, &vtable_key.member); + if (r < 0) + return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected interface and member parameters"); + + v = hashmap_get(bus->vtable_properties, &vtable_key); + if (v) { + r = property_get_set_callbacks_run(bus, m, v, require_fallback, get, found_object); + if (r != 0) + return r; + } + + } else if (streq(m->member, "GetAll")) { + const char *iface; + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + r = sd_bus_message_read(m, "s", &iface); + if (r < 0) + return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected interface parameter"); + + if (iface[0] == 0) + iface = NULL; + + r = property_get_all_callbacks_run(bus, m, n->vtables, require_fallback, iface, found_object); + if (r != 0) + return r; + } + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { + + if (!isempty(sd_bus_message_get_signature(m, true))) + return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected no parameters"); + + r = process_introspect(bus, m, n, require_fallback, found_object); + if (r != 0) + return r; + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.ObjectManager", "GetManagedObjects")) { + + if (!isempty(sd_bus_message_get_signature(m, true))) + return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected no parameters"); + + r = process_get_managed_objects(bus, m, n, require_fallback, found_object); + if (r != 0) + return r; + } + + if (bus->nodes_modified) + return 0; + + if (!*found_object) { + r = bus_node_exists(bus, n, m->path, require_fallback); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + if (r > 0) + *found_object = true; + } + + return 0; +} + +int bus_process_object(sd_bus *bus, sd_bus_message *m) { + int r; + size_t pl; + bool found_object = false; + + assert(bus); + assert(m); + + if (bus->hello_flags & KDBUS_HELLO_MONITOR) + return 0; + + if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL) + return 0; + + if (hashmap_isempty(bus->nodes)) + return 0; + + /* Never respond to broadcast messages */ + if (bus->bus_client && !m->destination) + return 0; + + assert(m->path); + assert(m->member); + + pl = strlen(m->path); + do { + char prefix[pl+1]; + + bus->nodes_modified = false; + + r = object_find_and_run(bus, m, m->path, false, &found_object); + if (r != 0) + return r; + + /* Look for fallback prefixes */ + OBJECT_PATH_FOREACH_PREFIX(prefix, m->path) { + + if (bus->nodes_modified) + break; + + r = object_find_and_run(bus, m, prefix, true, &found_object); + if (r != 0) + return r; + } + + } while (bus->nodes_modified); + + if (!found_object) + return 0; + + if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Get") || + sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Set")) + r = sd_bus_reply_method_errorf( + m, + SD_BUS_ERROR_UNKNOWN_PROPERTY, + "Unknown property or interface."); + else + r = sd_bus_reply_method_errorf( + m, + SD_BUS_ERROR_UNKNOWN_METHOD, + "Unknown method '%s' or interface '%s'.", m->member, m->interface); + + if (r < 0) + return r; + + return 1; +} + +static struct node *bus_node_allocate(sd_bus *bus, const char *path) { + struct node *n, *parent; + const char *e; + _cleanup_free_ char *s = NULL; + char *p; + int r; + + assert(bus); + assert(path); + assert(path[0] == '/'); + + n = hashmap_get(bus->nodes, path); + if (n) + return n; + + r = hashmap_ensure_allocated(&bus->nodes, &string_hash_ops); + if (r < 0) + return NULL; + + s = strdup(path); + if (!s) + return NULL; + + if (streq(path, "/")) + parent = NULL; + else { + e = strrchr(path, '/'); + assert(e); + + p = strndupa(path, MAX(1, e - path)); + + parent = bus_node_allocate(bus, p); + if (!parent) + return NULL; + } + + n = new0(struct node, 1); + if (!n) + return NULL; + + n->parent = parent; + n->path = s; + s = NULL; /* do not free */ + + r = hashmap_put(bus->nodes, n->path, n); + if (r < 0) { + free(n->path); + free(n); + return NULL; + } + + if (parent) + LIST_PREPEND(siblings, parent->child, n); + + return n; +} + +void bus_node_gc(sd_bus *b, struct node *n) { + assert(b); + + if (!n) + return; + + if (n->child || + n->callbacks || + n->vtables || + n->enumerators || + n->object_managers) + return; + + assert(hashmap_remove(b->nodes, n->path) == n); + + if (n->parent) + LIST_REMOVE(siblings, n->parent->child, n); + + free(n->path); + bus_node_gc(b, n->parent); + free(n); +} + +static int bus_find_parent_object_manager(sd_bus *bus, struct node **out, const char *path) { + struct node *n; + + assert(bus); + assert(path); + + n = hashmap_get(bus->nodes, path); + if (!n) { + char *prefix; + + prefix = alloca(strlen(path) + 1); + OBJECT_PATH_FOREACH_PREFIX(prefix, path) { + n = hashmap_get(bus->nodes, prefix); + if (n) + break; + } + } + + while (n && !n->object_managers) + n = n->parent; + + if (out) + *out = n; + return !!n; +} + +static int bus_add_object( + sd_bus *bus, + sd_bus_slot **slot, + bool fallback, + const char *path, + sd_bus_message_handler_t callback, + void *userdata) { + + sd_bus_slot *s; + struct node *n; + int r; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + n = bus_node_allocate(bus, path); + if (!n) + return -ENOMEM; + + s = bus_slot_allocate(bus, !slot, BUS_NODE_CALLBACK, sizeof(struct node_callback), userdata); + if (!s) { + r = -ENOMEM; + goto fail; + } + + s->node_callback.callback = callback; + s->node_callback.is_fallback = fallback; + + s->node_callback.node = n; + LIST_PREPEND(callbacks, n->callbacks, &s->node_callback); + bus->nodes_modified = true; + + if (slot) + *slot = s; + + return 0; + +fail: + sd_bus_slot_unref(s); + bus_node_gc(bus, n); + + return r; +} + +_public_ int sd_bus_add_object( + sd_bus *bus, + sd_bus_slot **slot, + const char *path, + sd_bus_message_handler_t callback, + void *userdata) { + + return bus_add_object(bus, slot, false, path, callback, userdata); +} + +_public_ int sd_bus_add_fallback( + sd_bus *bus, + sd_bus_slot **slot, + const char *prefix, + sd_bus_message_handler_t callback, + void *userdata) { + + return bus_add_object(bus, slot, true, prefix, callback, userdata); +} + +static void vtable_member_hash_func(const void *a, struct siphash *state) { + const struct vtable_member *m = a; + + assert(m); + + string_hash_func(m->path, state); + string_hash_func(m->interface, state); + string_hash_func(m->member, state); +} + +static int vtable_member_compare_func(const void *a, const void *b) { + const struct vtable_member *x = a, *y = b; + int r; + + assert(x); + assert(y); + + r = strcmp(x->path, y->path); + if (r != 0) + return r; + + r = strcmp(x->interface, y->interface); + if (r != 0) + return r; + + return strcmp(x->member, y->member); +} + +static const struct hash_ops vtable_member_hash_ops = { + .hash = vtable_member_hash_func, + .compare = vtable_member_compare_func +}; + +static int add_object_vtable_internal( + sd_bus *bus, + sd_bus_slot **slot, + const char *path, + const char *interface, + const sd_bus_vtable *vtable, + bool fallback, + sd_bus_object_find_t find, + void *userdata) { + + sd_bus_slot *s = NULL; + struct node_vtable *i, *existing = NULL; + const sd_bus_vtable *v; + struct node *n; + int r; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(interface_name_is_valid(interface), -EINVAL); + assert_return(vtable, -EINVAL); + assert_return(vtable[0].type == _SD_BUS_VTABLE_START, -EINVAL); + assert_return(vtable[0].x.start.element_size == sizeof(struct sd_bus_vtable), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + assert_return(!streq(interface, "org.freedesktop.DBus.Properties") && + !streq(interface, "org.freedesktop.DBus.Introspectable") && + !streq(interface, "org.freedesktop.DBus.Peer") && + !streq(interface, "org.freedesktop.DBus.ObjectManager"), -EINVAL); + + r = hashmap_ensure_allocated(&bus->vtable_methods, &vtable_member_hash_ops); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&bus->vtable_properties, &vtable_member_hash_ops); + if (r < 0) + return r; + + n = bus_node_allocate(bus, path); + if (!n) + return -ENOMEM; + + LIST_FOREACH(vtables, i, n->vtables) { + if (i->is_fallback != fallback) { + r = -EPROTOTYPE; + goto fail; + } + + if (streq(i->interface, interface)) { + + if (i->vtable == vtable) { + r = -EEXIST; + goto fail; + } + + existing = i; + } + } + + s = bus_slot_allocate(bus, !slot, BUS_NODE_VTABLE, sizeof(struct node_vtable), userdata); + if (!s) { + r = -ENOMEM; + goto fail; + } + + s->node_vtable.is_fallback = fallback; + s->node_vtable.vtable = vtable; + s->node_vtable.find = find; + + s->node_vtable.interface = strdup(interface); + if (!s->node_vtable.interface) { + r = -ENOMEM; + goto fail; + } + + for (v = s->node_vtable.vtable+1; v->type != _SD_BUS_VTABLE_END; v++) { + + switch (v->type) { + + case _SD_BUS_VTABLE_METHOD: { + struct vtable_member *m; + + if (!member_name_is_valid(v->x.method.member) || + !signature_is_valid(strempty(v->x.method.signature), false) || + !signature_is_valid(strempty(v->x.method.result), false) || + !(v->x.method.handler || (isempty(v->x.method.signature) && isempty(v->x.method.result))) || + v->flags & (SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) { + r = -EINVAL; + goto fail; + } + + m = new0(struct vtable_member, 1); + if (!m) { + r = -ENOMEM; + goto fail; + } + + m->parent = &s->node_vtable; + m->path = n->path; + m->interface = s->node_vtable.interface; + m->member = v->x.method.member; + m->vtable = v; + + r = hashmap_put(bus->vtable_methods, m, m); + if (r < 0) { + free(m); + goto fail; + } + + break; + } + + case _SD_BUS_VTABLE_WRITABLE_PROPERTY: + + if (!(v->x.property.set || bus_type_is_basic(v->x.property.signature[0]))) { + r = -EINVAL; + goto fail; + } + + if (v->flags & SD_BUS_VTABLE_PROPERTY_CONST) { + r = -EINVAL; + goto fail; + } + + /* Fall through */ + + case _SD_BUS_VTABLE_PROPERTY: { + struct vtable_member *m; + + if (!member_name_is_valid(v->x.property.member) || + !signature_is_single(v->x.property.signature, false) || + !(v->x.property.get || bus_type_is_basic(v->x.property.signature[0]) || streq(v->x.property.signature, "as")) || + (v->flags & SD_BUS_VTABLE_METHOD_NO_REPLY) || + (!!(v->flags & SD_BUS_VTABLE_PROPERTY_CONST) + !!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) + !!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) > 1 || + ((v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) && (v->flags & SD_BUS_VTABLE_PROPERTY_EXPLICIT)) || + (v->flags & SD_BUS_VTABLE_UNPRIVILEGED && v->type == _SD_BUS_VTABLE_PROPERTY)) { + r = -EINVAL; + goto fail; + } + + m = new0(struct vtable_member, 1); + if (!m) { + r = -ENOMEM; + goto fail; + } + + m->parent = &s->node_vtable; + m->path = n->path; + m->interface = s->node_vtable.interface; + m->member = v->x.property.member; + m->vtable = v; + + r = hashmap_put(bus->vtable_properties, m, m); + if (r < 0) { + free(m); + goto fail; + } + + break; + } + + case _SD_BUS_VTABLE_SIGNAL: + + if (!member_name_is_valid(v->x.signal.member) || + !signature_is_valid(strempty(v->x.signal.signature), false) || + v->flags & SD_BUS_VTABLE_UNPRIVILEGED) { + r = -EINVAL; + goto fail; + } + + break; + + default: + r = -EINVAL; + goto fail; + } + } + + s->node_vtable.node = n; + LIST_INSERT_AFTER(vtables, n->vtables, existing, &s->node_vtable); + bus->nodes_modified = true; + + if (slot) + *slot = s; + + return 0; + +fail: + sd_bus_slot_unref(s); + bus_node_gc(bus, n); + + return r; +} + +_public_ int sd_bus_add_object_vtable( + sd_bus *bus, + sd_bus_slot **slot, + const char *path, + const char *interface, + const sd_bus_vtable *vtable, + void *userdata) { + + return add_object_vtable_internal(bus, slot, path, interface, vtable, false, NULL, userdata); +} + +_public_ int sd_bus_add_fallback_vtable( + sd_bus *bus, + sd_bus_slot **slot, + const char *prefix, + const char *interface, + const sd_bus_vtable *vtable, + sd_bus_object_find_t find, + void *userdata) { + + return add_object_vtable_internal(bus, slot, prefix, interface, vtable, true, find, userdata); +} + +_public_ int sd_bus_add_node_enumerator( + sd_bus *bus, + sd_bus_slot **slot, + const char *path, + sd_bus_node_enumerator_t callback, + void *userdata) { + + sd_bus_slot *s; + struct node *n; + int r; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + n = bus_node_allocate(bus, path); + if (!n) + return -ENOMEM; + + s = bus_slot_allocate(bus, !slot, BUS_NODE_ENUMERATOR, sizeof(struct node_enumerator), userdata); + if (!s) { + r = -ENOMEM; + goto fail; + } + + s->node_enumerator.callback = callback; + + s->node_enumerator.node = n; + LIST_PREPEND(enumerators, n->enumerators, &s->node_enumerator); + bus->nodes_modified = true; + + if (slot) + *slot = s; + + return 0; + +fail: + sd_bus_slot_unref(s); + bus_node_gc(bus, n); + + return r; +} + +static int emit_properties_changed_on_interface( + sd_bus *bus, + const char *prefix, + const char *path, + const char *interface, + bool require_fallback, + bool *found_interface, + char **names) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + bool has_invalidating = false, has_changing = false; + struct vtable_member key = {}; + struct node_vtable *c; + struct node *n; + char **property; + void *u = NULL; + int r; + + assert(bus); + assert(prefix); + assert(path); + assert(interface); + assert(found_interface); + + n = hashmap_get(bus->nodes, prefix); + if (!n) + return 0; + + r = sd_bus_message_new_signal(bus, &m, path, "org.freedesktop.DBus.Properties", "PropertiesChanged"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", interface); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "{sv}"); + if (r < 0) + return r; + + key.path = prefix; + key.interface = interface; + + LIST_FOREACH(vtables, c, n->vtables) { + if (require_fallback && !c->is_fallback) + continue; + + if (!streq(c->interface, interface)) + continue; + + r = node_vtable_get_userdata(bus, path, c, &u, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + if (r == 0) + continue; + + *found_interface = true; + + if (names) { + /* If the caller specified a list of + * properties we include exactly those in the + * PropertiesChanged message */ + + STRV_FOREACH(property, names) { + struct vtable_member *v; + + assert_return(member_name_is_valid(*property), -EINVAL); + + key.member = *property; + v = hashmap_get(bus->vtable_properties, &key); + if (!v) + return -ENOENT; + + /* If there are two vtables for the same + * interface, let's handle this property when + * we come to that vtable. */ + if (c != v->parent) + continue; + + assert_return(v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE || + v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION, -EDOM); + + assert_return(!(v->vtable->flags & SD_BUS_VTABLE_HIDDEN), -EDOM); + + if (v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) { + has_invalidating = true; + continue; + } + + has_changing = true; + + r = vtable_append_one_property(bus, m, m->path, c, v->vtable, u, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + } + } else { + const sd_bus_vtable *v; + + /* If the caller specified no properties list + * we include all properties that are marked + * as changing in the message. */ + + for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; v++) { + if (v->type != _SD_BUS_VTABLE_PROPERTY && v->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY) + continue; + + if (v->flags & SD_BUS_VTABLE_HIDDEN) + continue; + + if (v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) { + has_invalidating = true; + continue; + } + + if (!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) + continue; + + has_changing = true; + + r = vtable_append_one_property(bus, m, m->path, c, v, u, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + } + } + } + + if (!has_invalidating && !has_changing) + return 0; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return r; + + if (has_invalidating) { + LIST_FOREACH(vtables, c, n->vtables) { + if (require_fallback && !c->is_fallback) + continue; + + if (!streq(c->interface, interface)) + continue; + + r = node_vtable_get_userdata(bus, path, c, &u, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + if (r == 0) + continue; + + if (names) { + STRV_FOREACH(property, names) { + struct vtable_member *v; + + key.member = *property; + assert_se(v = hashmap_get(bus->vtable_properties, &key)); + assert(c == v->parent); + + if (!(v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) + continue; + + r = sd_bus_message_append(m, "s", *property); + if (r < 0) + return r; + } + } else { + const sd_bus_vtable *v; + + for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; v++) { + if (v->type != _SD_BUS_VTABLE_PROPERTY && v->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY) + continue; + + if (v->flags & SD_BUS_VTABLE_HIDDEN) + continue; + + if (!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) + continue; + + r = sd_bus_message_append(m, "s", v->x.property.member); + if (r < 0) + return r; + } + } + } + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_send(bus, m, NULL); + if (r < 0) + return r; + + return 1; +} + +_public_ int sd_bus_emit_properties_changed_strv( + sd_bus *bus, + const char *path, + const char *interface, + char **names) { + + BUS_DONT_DESTROY(bus); + bool found_interface = false; + char *prefix; + int r; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(interface_name_is_valid(interface), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + /* A non-NULL but empty names list means nothing needs to be + generated. A NULL list OTOH indicates that all properties + that are set to EMITS_CHANGE or EMITS_INVALIDATION shall be + included in the PropertiesChanged message. */ + if (names && names[0] == NULL) + return 0; + + do { + bus->nodes_modified = false; + + r = emit_properties_changed_on_interface(bus, path, path, interface, false, &found_interface, names); + if (r != 0) + return r; + if (bus->nodes_modified) + continue; + + prefix = alloca(strlen(path) + 1); + OBJECT_PATH_FOREACH_PREFIX(prefix, path) { + r = emit_properties_changed_on_interface(bus, prefix, path, interface, true, &found_interface, names); + if (r != 0) + return r; + if (bus->nodes_modified) + break; + } + + } while (bus->nodes_modified); + + return found_interface ? 0 : -ENOENT; +} + +_public_ int sd_bus_emit_properties_changed( + sd_bus *bus, + const char *path, + const char *interface, + const char *name, ...) { + + char **names; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(interface_name_is_valid(interface), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (!name) + return 0; + + names = strv_from_stdarg_alloca(name); + + return sd_bus_emit_properties_changed_strv(bus, path, interface, names); +} + +static int object_added_append_all_prefix( + sd_bus *bus, + sd_bus_message *m, + Set *s, + const char *prefix, + const char *path, + bool require_fallback) { + + const char *previous_interface = NULL; + struct node_vtable *c; + struct node *n; + int r; + + assert(bus); + assert(m); + assert(s); + assert(prefix); + assert(path); + + n = hashmap_get(bus->nodes, prefix); + if (!n) + return 0; + + LIST_FOREACH(vtables, c, n->vtables) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + void *u = NULL; + + if (require_fallback && !c->is_fallback) + continue; + + r = node_vtable_get_userdata(bus, path, c, &u, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + if (r == 0) + continue; + + if (!streq_ptr(c->interface, previous_interface)) { + /* If a child-node already handled this interface, we + * skip it on any of its parents. The child vtables + * always fully override any conflicting vtables of + * any parent node. */ + if (set_get(s, c->interface)) + continue; + + r = set_put(s, c->interface); + if (r < 0) + return r; + + if (previous_interface) { + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + } + + r = sd_bus_message_open_container(m, 'e', "sa{sv}"); + if (r < 0) + return r; + r = sd_bus_message_append(m, "s", c->interface); + if (r < 0) + return r; + r = sd_bus_message_open_container(m, 'a', "{sv}"); + if (r < 0) + return r; + + previous_interface = c->interface; + } + + r = vtable_append_all_properties(bus, m, path, c, u, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + } + + if (previous_interface) { + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + } + + return 0; +} + +static int object_added_append_all(sd_bus *bus, sd_bus_message *m, const char *path) { + _cleanup_set_free_ Set *s = NULL; + char *prefix; + int r; + + assert(bus); + assert(m); + assert(path); + + /* + * This appends all interfaces registered on path @path. We first add + * the builtin interfaces, which are always available and handled by + * sd-bus. Then, we add all interfaces registered on the exact node, + * followed by all fallback interfaces registered on any parent prefix. + * + * If an interface is registered multiple times on the same node with + * different vtables, we merge all the properties across all vtables. + * However, if a child node has the same interface registered as one of + * its parent nodes has as fallback, we make the child overwrite the + * parent instead of extending it. Therefore, we keep a "Set" of all + * handled interfaces during parent traversal, so we skip interfaces on + * a parent that were overwritten by a child. + */ + + s = set_new(&string_hash_ops); + if (!s) + return -ENOMEM; + + r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.Peer", 0); + if (r < 0) + return r; + r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.Introspectable", 0); + if (r < 0) + return r; + r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.Properties", 0); + if (r < 0) + return r; + r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.ObjectManager", 0); + if (r < 0) + return r; + + r = object_added_append_all_prefix(bus, m, s, path, path, false); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + + prefix = alloca(strlen(path) + 1); + OBJECT_PATH_FOREACH_PREFIX(prefix, path) { + r = object_added_append_all_prefix(bus, m, s, prefix, path, true); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + } + + return 0; +} + +_public_ int sd_bus_emit_object_added(sd_bus *bus, const char *path) { + BUS_DONT_DESTROY(bus); + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + struct node *object_manager; + int r; + + /* + * This emits an InterfacesAdded signal on the given path, by iterating + * all registered vtables and fallback vtables on the path. All + * properties are queried and included in the signal. + * This call is equivalent to sd_bus_emit_interfaces_added() with an + * explicit list of registered interfaces. However, unlike + * interfaces_added(), this call can figure out the list of supported + * interfaces itself. Furthermore, it properly adds the builtin + * org.freedesktop.DBus.* interfaces. + */ + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + r = bus_find_parent_object_manager(bus, &object_manager, path); + if (r < 0) + return r; + if (r == 0) + return -ESRCH; + + do { + bus->nodes_modified = false; + m = sd_bus_message_unref(m); + + r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded"); + if (r < 0) + return r; + + r = sd_bus_message_append_basic(m, 'o', path); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "{sa{sv}}"); + if (r < 0) + return r; + + r = object_added_append_all(bus, m, path); + if (r < 0) + return r; + + if (bus->nodes_modified) + continue; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + } while (bus->nodes_modified); + + return sd_bus_send(bus, m, NULL); +} + +static int object_removed_append_all_prefix( + sd_bus *bus, + sd_bus_message *m, + Set *s, + const char *prefix, + const char *path, + bool require_fallback) { + + const char *previous_interface = NULL; + struct node_vtable *c; + struct node *n; + int r; + + assert(bus); + assert(m); + assert(s); + assert(prefix); + assert(path); + + n = hashmap_get(bus->nodes, prefix); + if (!n) + return 0; + + LIST_FOREACH(vtables, c, n->vtables) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + void *u = NULL; + + if (require_fallback && !c->is_fallback) + continue; + if (streq_ptr(c->interface, previous_interface)) + continue; + + /* If a child-node already handled this interface, we + * skip it on any of its parents. The child vtables + * always fully override any conflicting vtables of + * any parent node. */ + if (set_get(s, c->interface)) + continue; + + r = node_vtable_get_userdata(bus, path, c, &u, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + if (r == 0) + continue; + + r = set_put(s, c->interface); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", c->interface); + if (r < 0) + return r; + + previous_interface = c->interface; + } + + return 0; +} + +static int object_removed_append_all(sd_bus *bus, sd_bus_message *m, const char *path) { + _cleanup_set_free_ Set *s = NULL; + char *prefix; + int r; + + assert(bus); + assert(m); + assert(path); + + /* see sd_bus_emit_object_added() for details */ + + s = set_new(&string_hash_ops); + if (!s) + return -ENOMEM; + + r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.Peer"); + if (r < 0) + return r; + r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.Introspectable"); + if (r < 0) + return r; + r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.Properties"); + if (r < 0) + return r; + r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.ObjectManager"); + if (r < 0) + return r; + + r = object_removed_append_all_prefix(bus, m, s, path, path, false); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + + prefix = alloca(strlen(path) + 1); + OBJECT_PATH_FOREACH_PREFIX(prefix, path) { + r = object_removed_append_all_prefix(bus, m, s, prefix, path, true); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + } + + return 0; +} + +_public_ int sd_bus_emit_object_removed(sd_bus *bus, const char *path) { + BUS_DONT_DESTROY(bus); + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + struct node *object_manager; + int r; + + /* + * This is like sd_bus_emit_object_added(), but emits an + * InterfacesRemoved signal on the given path. This only includes any + * registered interfaces but skips the properties. Note that this will + * call into the find() callbacks of any registered vtable. Therefore, + * you must call this function before destroying/unlinking your object. + * Otherwise, the list of interfaces will be incomplete. However, note + * that this will *NOT* call into any property callback. Therefore, the + * object might be in an "destructed" state, as long as we can find it. + */ + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + r = bus_find_parent_object_manager(bus, &object_manager, path); + if (r < 0) + return r; + if (r == 0) + return -ESRCH; + + do { + bus->nodes_modified = false; + m = sd_bus_message_unref(m); + + r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved"); + if (r < 0) + return r; + + r = sd_bus_message_append_basic(m, 'o', path); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return r; + + r = object_removed_append_all(bus, m, path); + if (r < 0) + return r; + + if (bus->nodes_modified) + continue; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + } while (bus->nodes_modified); + + return sd_bus_send(bus, m, NULL); +} + +static int interfaces_added_append_one_prefix( + sd_bus *bus, + sd_bus_message *m, + const char *prefix, + const char *path, + const char *interface, + bool require_fallback) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + bool found_interface = false; + struct node_vtable *c; + struct node *n; + void *u = NULL; + int r; + + assert(bus); + assert(m); + assert(prefix); + assert(path); + assert(interface); + + n = hashmap_get(bus->nodes, prefix); + if (!n) + return 0; + + LIST_FOREACH(vtables, c, n->vtables) { + if (require_fallback && !c->is_fallback) + continue; + + if (!streq(c->interface, interface)) + continue; + + r = node_vtable_get_userdata(bus, path, c, &u, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + if (r == 0) + continue; + + if (!found_interface) { + r = sd_bus_message_append_basic(m, 's', interface); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "{sv}"); + if (r < 0) + return r; + + found_interface = true; + } + + r = vtable_append_all_properties(bus, m, path, c, u, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + } + + if (found_interface) { + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + } + + return found_interface; +} + +static int interfaces_added_append_one( + sd_bus *bus, + sd_bus_message *m, + const char *path, + const char *interface) { + + char *prefix; + int r; + + assert(bus); + assert(m); + assert(path); + assert(interface); + + r = interfaces_added_append_one_prefix(bus, m, path, path, interface, false); + if (r != 0) + return r; + if (bus->nodes_modified) + return 0; + + prefix = alloca(strlen(path) + 1); + OBJECT_PATH_FOREACH_PREFIX(prefix, path) { + r = interfaces_added_append_one_prefix(bus, m, prefix, path, interface, true); + if (r != 0) + return r; + if (bus->nodes_modified) + return 0; + } + + return -ENOENT; +} + +_public_ int sd_bus_emit_interfaces_added_strv(sd_bus *bus, const char *path, char **interfaces) { + BUS_DONT_DESTROY(bus); + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + struct node *object_manager; + char **i; + int r; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (strv_isempty(interfaces)) + return 0; + + r = bus_find_parent_object_manager(bus, &object_manager, path); + if (r < 0) + return r; + if (r == 0) + return -ESRCH; + + do { + bus->nodes_modified = false; + m = sd_bus_message_unref(m); + + r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded"); + if (r < 0) + return r; + + r = sd_bus_message_append_basic(m, 'o', path); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "{sa{sv}}"); + if (r < 0) + return r; + + STRV_FOREACH(i, interfaces) { + assert_return(interface_name_is_valid(*i), -EINVAL); + + r = sd_bus_message_open_container(m, 'e', "sa{sv}"); + if (r < 0) + return r; + + r = interfaces_added_append_one(bus, m, path, *i); + if (r < 0) + return r; + + if (bus->nodes_modified) + break; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + } + + if (bus->nodes_modified) + continue; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + } while (bus->nodes_modified); + + return sd_bus_send(bus, m, NULL); +} + +_public_ int sd_bus_emit_interfaces_added(sd_bus *bus, const char *path, const char *interface, ...) { + char **interfaces; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + interfaces = strv_from_stdarg_alloca(interface); + + return sd_bus_emit_interfaces_added_strv(bus, path, interfaces); +} + +_public_ int sd_bus_emit_interfaces_removed_strv(sd_bus *bus, const char *path, char **interfaces) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + struct node *object_manager; + int r; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (strv_isempty(interfaces)) + return 0; + + r = bus_find_parent_object_manager(bus, &object_manager, path); + if (r < 0) + return r; + if (r == 0) + return -ESRCH; + + r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved"); + if (r < 0) + return r; + + r = sd_bus_message_append_basic(m, 'o', path); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(m, interfaces); + if (r < 0) + return r; + + return sd_bus_send(bus, m, NULL); +} + +_public_ int sd_bus_emit_interfaces_removed(sd_bus *bus, const char *path, const char *interface, ...) { + char **interfaces; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + interfaces = strv_from_stdarg_alloca(interface); + + return sd_bus_emit_interfaces_removed_strv(bus, path, interfaces); +} + +_public_ int sd_bus_add_object_manager(sd_bus *bus, sd_bus_slot **slot, const char *path) { + sd_bus_slot *s; + struct node *n; + int r; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + n = bus_node_allocate(bus, path); + if (!n) + return -ENOMEM; + + s = bus_slot_allocate(bus, !slot, BUS_NODE_OBJECT_MANAGER, sizeof(struct node_object_manager), NULL); + if (!s) { + r = -ENOMEM; + goto fail; + } + + s->node_object_manager.node = n; + LIST_PREPEND(object_managers, n->object_managers, &s->node_object_manager); + bus->nodes_modified = true; + + if (slot) + *slot = s; + + return 0; + +fail: + sd_bus_slot_unref(s); + bus_node_gc(bus, n); + + return r; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-objects.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-objects.h new file mode 100644 index 0000000000..e0b8c534ed --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-objects.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 "bus-internal.h" + +int bus_process_object(sd_bus *bus, sd_bus_message *m); +void bus_node_gc(sd_bus *b, struct node *n); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-protocol.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-protocol.h new file mode 100644 index 0000000000..9d180cb284 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-protocol.h @@ -0,0 +1,180 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 "macro.h" + +/* Packet header */ + +struct _packed_ bus_header { + /* The first four fields are identical for dbus1, and dbus2 */ + uint8_t endian; + uint8_t type; + uint8_t flags; + uint8_t version; + + union _packed_ { + /* dbus1: Used for SOCK_STREAM connections */ + struct _packed_ { + uint32_t body_size; + + /* Note that what the bus spec calls "serial" we'll call + "cookie" instead, because we don't want to imply that the + cookie was in any way monotonically increasing. */ + uint32_t serial; + uint32_t fields_size; + } dbus1; + + /* dbus2: Used for kdbus connections */ + struct _packed_ { + uint32_t _reserved; + uint64_t cookie; + } dbus2; + + /* Note that both header versions have the same size! */ + }; +}; + +/* Endianness */ + +enum { + _BUS_INVALID_ENDIAN = 0, + BUS_LITTLE_ENDIAN = 'l', + BUS_BIG_ENDIAN = 'B', +#if __BYTE_ORDER == __BIG_ENDIAN + BUS_NATIVE_ENDIAN = BUS_BIG_ENDIAN, + BUS_REVERSE_ENDIAN = BUS_LITTLE_ENDIAN +#else + BUS_NATIVE_ENDIAN = BUS_LITTLE_ENDIAN, + BUS_REVERSE_ENDIAN = BUS_BIG_ENDIAN +#endif +}; + +/* Flags */ + +enum { + BUS_MESSAGE_NO_REPLY_EXPECTED = 1, + BUS_MESSAGE_NO_AUTO_START = 2, + BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION = 4, +}; + +/* Header fields */ + +enum { + _BUS_MESSAGE_HEADER_INVALID = 0, + BUS_MESSAGE_HEADER_PATH, + BUS_MESSAGE_HEADER_INTERFACE, + BUS_MESSAGE_HEADER_MEMBER, + BUS_MESSAGE_HEADER_ERROR_NAME, + BUS_MESSAGE_HEADER_REPLY_SERIAL, + BUS_MESSAGE_HEADER_DESTINATION, + BUS_MESSAGE_HEADER_SENDER, + BUS_MESSAGE_HEADER_SIGNATURE, + BUS_MESSAGE_HEADER_UNIX_FDS, + _BUS_MESSAGE_HEADER_MAX +}; + +/* RequestName parameters */ + +enum { + BUS_NAME_ALLOW_REPLACEMENT = 1, + BUS_NAME_REPLACE_EXISTING = 2, + BUS_NAME_DO_NOT_QUEUE = 4 +}; + +/* RequestName returns */ +enum { + BUS_NAME_PRIMARY_OWNER = 1, + BUS_NAME_IN_QUEUE = 2, + BUS_NAME_EXISTS = 3, + BUS_NAME_ALREADY_OWNER = 4 +}; + +/* ReleaseName returns */ +enum { + BUS_NAME_RELEASED = 1, + BUS_NAME_NON_EXISTENT = 2, + BUS_NAME_NOT_OWNER = 3, +}; + +/* StartServiceByName returns */ +enum { + BUS_START_REPLY_SUCCESS = 1, + BUS_START_REPLY_ALREADY_RUNNING = 2, +}; + +#define BUS_INTROSPECT_DOCTYPE \ + "\n" + +#define BUS_INTROSPECT_INTERFACE_PEER \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define BUS_INTROSPECT_INTERFACE_INTROSPECTABLE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define BUS_INTROSPECT_INTERFACE_PROPERTIES \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define BUS_INTROSPECT_INTERFACE_OBJECT_MANAGER \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-signature.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-signature.c new file mode 100644 index 0000000000..7bc243494a --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-signature.c @@ -0,0 +1,158 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "bus-signature.h" +#include "bus-type.h" + +static int signature_element_length_internal( + const char *s, + bool allow_dict_entry, + unsigned array_depth, + unsigned struct_depth, + size_t *l) { + + int r; + + if (!s) + return -EINVAL; + + assert(l); + + if (bus_type_is_basic(*s) || *s == SD_BUS_TYPE_VARIANT) { + *l = 1; + return 0; + } + + if (*s == SD_BUS_TYPE_ARRAY) { + size_t t; + + if (array_depth >= 32) + return -EINVAL; + + r = signature_element_length_internal(s + 1, true, array_depth+1, struct_depth, &t); + if (r < 0) + return r; + + *l = t + 1; + return 0; + } + + if (*s == SD_BUS_TYPE_STRUCT_BEGIN) { + const char *p = s + 1; + + if (struct_depth >= 32) + return -EINVAL; + + while (*p != SD_BUS_TYPE_STRUCT_END) { + size_t t; + + r = signature_element_length_internal(p, false, array_depth, struct_depth+1, &t); + if (r < 0) + return r; + + p += t; + } + + *l = p - s + 1; + return 0; + } + + if (*s == SD_BUS_TYPE_DICT_ENTRY_BEGIN && allow_dict_entry) { + const char *p = s + 1; + unsigned n = 0; + + if (struct_depth >= 32) + return -EINVAL; + + while (*p != SD_BUS_TYPE_DICT_ENTRY_END) { + size_t t; + + if (n == 0 && !bus_type_is_basic(*p)) + return -EINVAL; + + r = signature_element_length_internal(p, false, array_depth, struct_depth+1, &t); + if (r < 0) + return r; + + p += t; + n++; + } + + if (n != 2) + return -EINVAL; + + *l = p - s + 1; + return 0; + } + + return -EINVAL; +} + + +int signature_element_length(const char *s, size_t *l) { + return signature_element_length_internal(s, true, 0, 0, l); +} + +bool signature_is_single(const char *s, bool allow_dict_entry) { + int r; + size_t t; + + if (!s) + return false; + + r = signature_element_length_internal(s, allow_dict_entry, 0, 0, &t); + if (r < 0) + return false; + + return s[t] == 0; +} + +bool signature_is_pair(const char *s) { + + if (!s) + return false; + + if (!bus_type_is_basic(*s)) + return false; + + return signature_is_single(s + 1, false); +} + +bool signature_is_valid(const char *s, bool allow_dict_entry) { + const char *p; + int r; + + if (!s) + return false; + + p = s; + while (*p) { + size_t t; + + r = signature_element_length_internal(p, allow_dict_entry, 0, 0, &t); + if (r < 0) + return false; + + p += t; + } + + return p - s <= 255; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-signature.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-signature.h new file mode 100644 index 0000000000..1e0cd7f587 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-signature.h @@ -0,0 +1,28 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 + +bool signature_is_single(const char *s, bool allow_dict_entry); +bool signature_is_pair(const char *s); +bool signature_is_valid(const char *s, bool allow_dict_entry); + +int signature_element_length(const char *s, size_t *l); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-slot.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-slot.c new file mode 100644 index 0000000000..75c1692bf5 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-slot.c @@ -0,0 +1,286 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "alloc-util.h" +#include "bus-control.h" +#include "bus-objects.h" +#include "bus-slot.h" +#include "string-util.h" + +sd_bus_slot *bus_slot_allocate( + sd_bus *bus, + bool floating, + BusSlotType type, + size_t extra, + void *userdata) { + + sd_bus_slot *slot; + + assert(bus); + + slot = malloc0(offsetof(sd_bus_slot, reply_callback) + extra); + if (!slot) + return NULL; + + slot->n_ref = 1; + slot->type = type; + slot->bus = bus; + slot->floating = floating; + slot->userdata = userdata; + + if (!floating) + sd_bus_ref(bus); + + LIST_PREPEND(slots, bus->slots, slot); + + return slot; +} + +_public_ sd_bus_slot* sd_bus_slot_ref(sd_bus_slot *slot) { + + if (!slot) + return NULL; + + assert(slot->n_ref > 0); + + slot->n_ref++; + return slot; +} + +void bus_slot_disconnect(sd_bus_slot *slot) { + sd_bus *bus; + + assert(slot); + + if (!slot->bus) + return; + + switch (slot->type) { + + case BUS_REPLY_CALLBACK: + + if (slot->reply_callback.cookie != 0) + ordered_hashmap_remove(slot->bus->reply_callbacks, &slot->reply_callback.cookie); + + if (slot->reply_callback.timeout != 0) + prioq_remove(slot->bus->reply_callbacks_prioq, &slot->reply_callback, &slot->reply_callback.prioq_idx); + + break; + + case BUS_FILTER_CALLBACK: + slot->bus->filter_callbacks_modified = true; + LIST_REMOVE(callbacks, slot->bus->filter_callbacks, &slot->filter_callback); + break; + + case BUS_MATCH_CALLBACK: + + if (slot->match_added) + bus_remove_match_internal(slot->bus, slot->match_callback.match_string, slot->match_callback.cookie); + + slot->bus->match_callbacks_modified = true; + bus_match_remove(&slot->bus->match_callbacks, &slot->match_callback); + + free(slot->match_callback.match_string); + + break; + + case BUS_NODE_CALLBACK: + + if (slot->node_callback.node) { + LIST_REMOVE(callbacks, slot->node_callback.node->callbacks, &slot->node_callback); + slot->bus->nodes_modified = true; + + bus_node_gc(slot->bus, slot->node_callback.node); + } + + break; + + case BUS_NODE_ENUMERATOR: + + if (slot->node_enumerator.node) { + LIST_REMOVE(enumerators, slot->node_enumerator.node->enumerators, &slot->node_enumerator); + slot->bus->nodes_modified = true; + + bus_node_gc(slot->bus, slot->node_enumerator.node); + } + + break; + + case BUS_NODE_OBJECT_MANAGER: + + if (slot->node_object_manager.node) { + LIST_REMOVE(object_managers, slot->node_object_manager.node->object_managers, &slot->node_object_manager); + slot->bus->nodes_modified = true; + + bus_node_gc(slot->bus, slot->node_object_manager.node); + } + + break; + + case BUS_NODE_VTABLE: + + if (slot->node_vtable.node && slot->node_vtable.interface && slot->node_vtable.vtable) { + const sd_bus_vtable *v; + + for (v = slot->node_vtable.vtable; v->type != _SD_BUS_VTABLE_END; v++) { + struct vtable_member *x = NULL; + + switch (v->type) { + + case _SD_BUS_VTABLE_METHOD: { + struct vtable_member key; + + key.path = slot->node_vtable.node->path; + key.interface = slot->node_vtable.interface; + key.member = v->x.method.member; + + x = hashmap_remove(slot->bus->vtable_methods, &key); + break; + } + + case _SD_BUS_VTABLE_PROPERTY: + case _SD_BUS_VTABLE_WRITABLE_PROPERTY: { + struct vtable_member key; + + key.path = slot->node_vtable.node->path; + key.interface = slot->node_vtable.interface; + key.member = v->x.method.member; + + + x = hashmap_remove(slot->bus->vtable_properties, &key); + break; + }} + + free(x); + } + } + + free(slot->node_vtable.interface); + + if (slot->node_vtable.node) { + LIST_REMOVE(vtables, slot->node_vtable.node->vtables, &slot->node_vtable); + slot->bus->nodes_modified = true; + + bus_node_gc(slot->bus, slot->node_vtable.node); + } + + break; + + default: + assert_not_reached("Wut? Unknown slot type?"); + } + + bus = slot->bus; + + slot->type = _BUS_SLOT_INVALID; + slot->bus = NULL; + LIST_REMOVE(slots, bus->slots, slot); + + if (!slot->floating) + sd_bus_unref(bus); +} + +_public_ sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) { + + if (!slot) + return NULL; + + assert(slot->n_ref > 0); + + if (slot->n_ref > 1) { + slot->n_ref--; + return NULL; + } + + bus_slot_disconnect(slot); + free(slot->description); + free(slot); + + return NULL; +} + +_public_ sd_bus* sd_bus_slot_get_bus(sd_bus_slot *slot) { + assert_return(slot, NULL); + + return slot->bus; +} + +_public_ void *sd_bus_slot_get_userdata(sd_bus_slot *slot) { + assert_return(slot, NULL); + + return slot->userdata; +} + +_public_ void *sd_bus_slot_set_userdata(sd_bus_slot *slot, void *userdata) { + void *ret; + + assert_return(slot, NULL); + + ret = slot->userdata; + slot->userdata = userdata; + + return ret; +} + +_public_ sd_bus_message *sd_bus_slot_get_current_message(sd_bus_slot *slot) { + assert_return(slot, NULL); + assert_return(slot->type >= 0, NULL); + + if (slot->bus->current_slot != slot) + return NULL; + + return slot->bus->current_message; +} + +_public_ sd_bus_message_handler_t sd_bus_slot_get_current_handler(sd_bus_slot *slot) { + assert_return(slot, NULL); + assert_return(slot->type >= 0, NULL); + + if (slot->bus->current_slot != slot) + return NULL; + + return slot->bus->current_handler; +} + +_public_ void* sd_bus_slot_get_current_userdata(sd_bus_slot *slot) { + assert_return(slot, NULL); + assert_return(slot->type >= 0, NULL); + + if (slot->bus->current_slot != slot) + return NULL; + + return slot->bus->current_userdata; +} + +_public_ int sd_bus_slot_set_description(sd_bus_slot *slot, const char *description) { + assert_return(slot, -EINVAL); + + return free_and_strdup(&slot->description, description); +} + +_public_ int sd_bus_slot_get_description(sd_bus_slot *slot, const char **description) { + assert_return(slot, -EINVAL); + assert_return(description, -EINVAL); + assert_return(slot->description, -ENXIO); + + *description = slot->description; + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-slot.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-slot.h new file mode 100644 index 0000000000..b862799d1c --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-slot.h @@ -0,0 +1,28 @@ +#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 + +#include "bus-internal.h" + +sd_bus_slot *bus_slot_allocate(sd_bus *bus, bool floating, BusSlotType type, size_t extra, void *userdata); + +void bus_slot_disconnect(sd_bus_slot *slot); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-socket.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-socket.c new file mode 100644 index 0000000000..1486d7cd55 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-socket.c @@ -0,0 +1,1064 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 +#include +#include + +#include +#include + +#include "alloc-util.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-socket.h" +#include "fd-util.h" +#include "formats-util.h" +#include "hexdecoct.h" +#include "macro.h" +#include "missing.h" +#include "selinux-util.h" +#include "signal-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "user-util.h" +#include "utf8.h" +#include "util.h" + +#define SNDBUF_SIZE (8*1024*1024) + +static void iovec_advance(struct iovec iov[], unsigned *idx, size_t size) { + + while (size > 0) { + struct iovec *i = iov + *idx; + + if (i->iov_len > size) { + i->iov_base = (uint8_t*) i->iov_base + size; + i->iov_len -= size; + return; + } + + size -= i->iov_len; + + i->iov_base = NULL; + i->iov_len = 0; + + (*idx)++; + } +} + +static int append_iovec(sd_bus_message *m, const void *p, size_t sz) { + assert(m); + assert(p); + assert(sz > 0); + + m->iovec[m->n_iovec].iov_base = (void*) p; + m->iovec[m->n_iovec].iov_len = sz; + m->n_iovec++; + + return 0; +} + +static int bus_message_setup_iovec(sd_bus_message *m) { + struct bus_body_part *part; + unsigned n, i; + int r; + + assert(m); + assert(m->sealed); + + if (m->n_iovec > 0) + return 0; + + assert(!m->iovec); + + n = 1 + m->n_body_parts; + if (n < ELEMENTSOF(m->iovec_fixed)) + m->iovec = m->iovec_fixed; + else { + m->iovec = new(struct iovec, n); + if (!m->iovec) { + r = -ENOMEM; + goto fail; + } + } + + r = append_iovec(m, m->header, BUS_MESSAGE_BODY_BEGIN(m)); + if (r < 0) + goto fail; + + MESSAGE_FOREACH_PART(part, i, m) { + r = bus_body_part_map(part); + if (r < 0) + goto fail; + + r = append_iovec(m, part->data, part->size); + if (r < 0) + goto fail; + } + + assert(n == m->n_iovec); + + return 0; + +fail: + m->poisoned = true; + return r; +} + +bool bus_socket_auth_needs_write(sd_bus *b) { + + unsigned i; + + if (b->auth_index >= ELEMENTSOF(b->auth_iovec)) + return false; + + for (i = b->auth_index; i < ELEMENTSOF(b->auth_iovec); i++) { + struct iovec *j = b->auth_iovec + i; + + if (j->iov_len > 0) + return true; + } + + return false; +} + +static int bus_socket_write_auth(sd_bus *b) { + ssize_t k; + + assert(b); + assert(b->state == BUS_AUTHENTICATING); + + if (!bus_socket_auth_needs_write(b)) + return 0; + + if (b->prefer_writev) + k = writev(b->output_fd, b->auth_iovec + b->auth_index, ELEMENTSOF(b->auth_iovec) - b->auth_index); + else { + struct msghdr mh; + zero(mh); + + mh.msg_iov = b->auth_iovec + b->auth_index; + mh.msg_iovlen = ELEMENTSOF(b->auth_iovec) - b->auth_index; + + k = sendmsg(b->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); + if (k < 0 && errno == ENOTSOCK) { + b->prefer_writev = true; + k = writev(b->output_fd, b->auth_iovec + b->auth_index, ELEMENTSOF(b->auth_iovec) - b->auth_index); + } + } + + if (k < 0) + return errno == EAGAIN ? 0 : -errno; + + iovec_advance(b->auth_iovec, &b->auth_index, (size_t) k); + return 1; +} + +static int bus_socket_auth_verify_client(sd_bus *b) { + char *e, *f, *start; + sd_id128_t peer; + unsigned i; + int r; + + assert(b); + + /* We expect two response lines: "OK" and possibly + * "AGREE_UNIX_FD" */ + + e = memmem_safe(b->rbuffer, b->rbuffer_size, "\r\n", 2); + if (!e) + return 0; + + if (b->hello_flags & KDBUS_HELLO_ACCEPT_FD) { + f = memmem(e + 2, b->rbuffer_size - (e - (char*) b->rbuffer) - 2, "\r\n", 2); + if (!f) + return 0; + + start = f + 2; + } else { + f = NULL; + start = e + 2; + } + + /* Nice! We got all the lines we need. First check the OK + * line */ + + if (e - (char*) b->rbuffer != 3 + 32) + return -EPERM; + + if (memcmp(b->rbuffer, "OK ", 3)) + return -EPERM; + + b->auth = b->anonymous_auth ? BUS_AUTH_ANONYMOUS : BUS_AUTH_EXTERNAL; + + for (i = 0; i < 32; i += 2) { + int x, y; + + x = unhexchar(((char*) b->rbuffer)[3 + i]); + y = unhexchar(((char*) b->rbuffer)[3 + i + 1]); + + if (x < 0 || y < 0) + return -EINVAL; + + peer.bytes[i/2] = ((uint8_t) x << 4 | (uint8_t) y); + } + + if (!sd_id128_equal(b->server_id, SD_ID128_NULL) && + !sd_id128_equal(b->server_id, peer)) + return -EPERM; + + b->server_id = peer; + + /* And possibly check the second line, too */ + + if (f) + b->can_fds = + (f - e == strlen("\r\nAGREE_UNIX_FD")) && + memcmp(e + 2, "AGREE_UNIX_FD", strlen("AGREE_UNIX_FD")) == 0; + + b->rbuffer_size -= (start - (char*) b->rbuffer); + memmove(b->rbuffer, start, b->rbuffer_size); + + r = bus_start_running(b); + if (r < 0) + return r; + + return 1; +} + +static bool line_equals(const char *s, size_t m, const char *line) { + size_t l; + + l = strlen(line); + if (l != m) + return false; + + return memcmp(s, line, l) == 0; +} + +static bool line_begins(const char *s, size_t m, const char *word) { + size_t l; + + l = strlen(word); + if (m < l) + return false; + + if (memcmp(s, word, l) != 0) + return false; + + return m == l || (m > l && s[l] == ' '); +} + +static int verify_anonymous_token(sd_bus *b, const char *p, size_t l) { + _cleanup_free_ char *token = NULL; + size_t len; + int r; + + if (!b->anonymous_auth) + return 0; + + if (l <= 0) + return 1; + + assert(p[0] == ' '); + p++; l--; + + if (l % 2 != 0) + return 0; + + r = unhexmem(p, l, (void **) &token, &len); + if (r < 0) + return 0; + + if (memchr(token, 0, len)) + return 0; + + return !!utf8_is_valid(token); +} + +static int verify_external_token(sd_bus *b, const char *p, size_t l) { + _cleanup_free_ char *token = NULL; + size_t len; + uid_t u; + int r; + + /* We don't do any real authentication here. Instead, we if + * the owner of this bus wanted authentication he should have + * checked SO_PEERCRED before even creating the bus object. */ + + if (!b->anonymous_auth && !b->ucred_valid) + return 0; + + if (l <= 0) + return 1; + + assert(p[0] == ' '); + p++; l--; + + if (l % 2 != 0) + return 0; + + r = unhexmem(p, l, (void**) &token, &len); + if (r < 0) + return 0; + + if (memchr(token, 0, len)) + return 0; + + r = parse_uid(token, &u); + if (r < 0) + return 0; + + /* We ignore the passed value if anonymous authentication is + * on anyway. */ + if (!b->anonymous_auth && u != b->ucred.uid) + return 0; + + return 1; +} + +static int bus_socket_auth_write(sd_bus *b, const char *t) { + char *p; + size_t l; + + assert(b); + assert(t); + + /* We only make use of the first iovec */ + assert(b->auth_index == 0 || b->auth_index == 1); + + l = strlen(t); + p = malloc(b->auth_iovec[0].iov_len + l); + if (!p) + return -ENOMEM; + + memcpy_safe(p, b->auth_iovec[0].iov_base, b->auth_iovec[0].iov_len); + memcpy(p + b->auth_iovec[0].iov_len, t, l); + + b->auth_iovec[0].iov_base = p; + b->auth_iovec[0].iov_len += l; + + free(b->auth_buffer); + b->auth_buffer = p; + b->auth_index = 0; + return 0; +} + +static int bus_socket_auth_write_ok(sd_bus *b) { + char t[3 + 32 + 2 + 1]; + + assert(b); + + xsprintf(t, "OK " SD_ID128_FORMAT_STR "\r\n", SD_ID128_FORMAT_VAL(b->server_id)); + + return bus_socket_auth_write(b, t); +} + +static int bus_socket_auth_verify_server(sd_bus *b) { + char *e; + const char *line; + size_t l; + bool processed = false; + int r; + + assert(b); + + if (b->rbuffer_size < 1) + return 0; + + /* First char must be a NUL byte */ + if (*(char*) b->rbuffer != 0) + return -EIO; + + if (b->rbuffer_size < 3) + return 0; + + /* Begin with the first line */ + if (b->auth_rbegin <= 0) + b->auth_rbegin = 1; + + for (;;) { + /* Check if line is complete */ + line = (char*) b->rbuffer + b->auth_rbegin; + e = memmem(line, b->rbuffer_size - b->auth_rbegin, "\r\n", 2); + if (!e) + return processed; + + l = e - line; + + if (line_begins(line, l, "AUTH ANONYMOUS")) { + + r = verify_anonymous_token(b, line + 14, l - 14); + if (r < 0) + return r; + if (r == 0) + r = bus_socket_auth_write(b, "REJECTED\r\n"); + else { + b->auth = BUS_AUTH_ANONYMOUS; + r = bus_socket_auth_write_ok(b); + } + + } else if (line_begins(line, l, "AUTH EXTERNAL")) { + + r = verify_external_token(b, line + 13, l - 13); + if (r < 0) + return r; + if (r == 0) + r = bus_socket_auth_write(b, "REJECTED\r\n"); + else { + b->auth = BUS_AUTH_EXTERNAL; + r = bus_socket_auth_write_ok(b); + } + + } else if (line_begins(line, l, "AUTH")) + r = bus_socket_auth_write(b, "REJECTED EXTERNAL ANONYMOUS\r\n"); + else if (line_equals(line, l, "CANCEL") || + line_begins(line, l, "ERROR")) { + + b->auth = _BUS_AUTH_INVALID; + r = bus_socket_auth_write(b, "REJECTED\r\n"); + + } else if (line_equals(line, l, "BEGIN")) { + + if (b->auth == _BUS_AUTH_INVALID) + r = bus_socket_auth_write(b, "ERROR\r\n"); + else { + /* We can't leave from the auth phase + * before we haven't written + * everything queued, so let's check + * that */ + + if (bus_socket_auth_needs_write(b)) + return 1; + + b->rbuffer_size -= (e + 2 - (char*) b->rbuffer); + memmove(b->rbuffer, e + 2, b->rbuffer_size); + return bus_start_running(b); + } + + } else if (line_begins(line, l, "DATA")) { + + if (b->auth == _BUS_AUTH_INVALID) + r = bus_socket_auth_write(b, "ERROR\r\n"); + else { + if (b->auth == BUS_AUTH_ANONYMOUS) + r = verify_anonymous_token(b, line + 4, l - 4); + else + r = verify_external_token(b, line + 4, l - 4); + + if (r < 0) + return r; + if (r == 0) { + b->auth = _BUS_AUTH_INVALID; + r = bus_socket_auth_write(b, "REJECTED\r\n"); + } else + r = bus_socket_auth_write_ok(b); + } + } else if (line_equals(line, l, "NEGOTIATE_UNIX_FD")) { + if (b->auth == _BUS_AUTH_INVALID || !(b->hello_flags & KDBUS_HELLO_ACCEPT_FD)) + r = bus_socket_auth_write(b, "ERROR\r\n"); + else { + b->can_fds = true; + r = bus_socket_auth_write(b, "AGREE_UNIX_FD\r\n"); + } + } else + r = bus_socket_auth_write(b, "ERROR\r\n"); + + if (r < 0) + return r; + + b->auth_rbegin = e + 2 - (char*) b->rbuffer; + + processed = true; + } +} + +static int bus_socket_auth_verify(sd_bus *b) { + assert(b); + + if (b->is_server) + return bus_socket_auth_verify_server(b); + else + return bus_socket_auth_verify_client(b); +} + +static int bus_socket_read_auth(sd_bus *b) { + struct msghdr mh; + struct iovec iov = {}; + size_t n; + ssize_t k; + int r; + void *p; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int) * BUS_FDS_MAX)]; + } control; + bool handle_cmsg = false; + + assert(b); + assert(b->state == BUS_AUTHENTICATING); + + r = bus_socket_auth_verify(b); + if (r != 0) + return r; + + n = MAX(256u, b->rbuffer_size * 2); + + if (n > BUS_AUTH_SIZE_MAX) + n = BUS_AUTH_SIZE_MAX; + + if (b->rbuffer_size >= n) + return -ENOBUFS; + + p = realloc(b->rbuffer, n); + if (!p) + return -ENOMEM; + + b->rbuffer = p; + + iov.iov_base = (uint8_t*) b->rbuffer + b->rbuffer_size; + iov.iov_len = n - b->rbuffer_size; + + if (b->prefer_readv) + k = readv(b->input_fd, &iov, 1); + else { + zero(mh); + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_control = &control; + mh.msg_controllen = sizeof(control); + + k = recvmsg(b->input_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); + if (k < 0 && errno == ENOTSOCK) { + b->prefer_readv = true; + k = readv(b->input_fd, &iov, 1); + } else + handle_cmsg = true; + } + if (k < 0) + return errno == EAGAIN ? 0 : -errno; + if (k == 0) + return -ECONNRESET; + + b->rbuffer_size += k; + + if (handle_cmsg) { + struct cmsghdr *cmsg; + + CMSG_FOREACH(cmsg, &mh) + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + int j; + + /* Whut? We received fds during the auth + * protocol? Somebody is playing games with + * us. Close them all, and fail */ + j = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + close_many((int*) CMSG_DATA(cmsg), j); + return -EIO; + } else + log_debug("Got unexpected auxiliary data with level=%d and type=%d", + cmsg->cmsg_level, cmsg->cmsg_type); + } + + r = bus_socket_auth_verify(b); + if (r != 0) + return r; + + return 1; +} + +void bus_socket_setup(sd_bus *b) { + assert(b); + + /* Increase the buffers to 8 MB */ + fd_inc_rcvbuf(b->input_fd, SNDBUF_SIZE); + fd_inc_sndbuf(b->output_fd, SNDBUF_SIZE); + + b->is_kernel = false; + b->message_version = 1; + b->message_endian = 0; +} + +static void bus_get_peercred(sd_bus *b) { + int r; + + assert(b); + + /* Get the peer for socketpair() sockets */ + b->ucred_valid = getpeercred(b->input_fd, &b->ucred) >= 0; + + /* Get the SELinux context of the peer */ + if (mac_selinux_have()) { + r = getpeersec(b->input_fd, &b->label); + if (r < 0 && r != -EOPNOTSUPP) + log_debug_errno(r, "Failed to determine peer security context: %m"); + } +} + +static int bus_socket_start_auth_client(sd_bus *b) { + size_t l; + const char *auth_suffix, *auth_prefix; + + assert(b); + + if (b->anonymous_auth) { + auth_prefix = "\0AUTH ANONYMOUS "; + + /* For ANONYMOUS auth we send some arbitrary "trace" string */ + l = 9; + b->auth_buffer = hexmem("anonymous", l); + } else { + char text[DECIMAL_STR_MAX(uid_t) + 1]; + + auth_prefix = "\0AUTH EXTERNAL "; + + xsprintf(text, UID_FMT, geteuid()); + + l = strlen(text); + b->auth_buffer = hexmem(text, l); + } + + if (!b->auth_buffer) + return -ENOMEM; + + if (b->hello_flags & KDBUS_HELLO_ACCEPT_FD) + auth_suffix = "\r\nNEGOTIATE_UNIX_FD\r\nBEGIN\r\n"; + else + auth_suffix = "\r\nBEGIN\r\n"; + + b->auth_iovec[0].iov_base = (void*) auth_prefix; + b->auth_iovec[0].iov_len = 1 + strlen(auth_prefix + 1); + b->auth_iovec[1].iov_base = (void*) b->auth_buffer; + b->auth_iovec[1].iov_len = l * 2; + b->auth_iovec[2].iov_base = (void*) auth_suffix; + b->auth_iovec[2].iov_len = strlen(auth_suffix); + + return bus_socket_write_auth(b); +} + +int bus_socket_start_auth(sd_bus *b) { + assert(b); + + bus_get_peercred(b); + + b->state = BUS_AUTHENTICATING; + b->auth_timeout = now(CLOCK_MONOTONIC) + BUS_DEFAULT_TIMEOUT; + + if (sd_is_socket(b->input_fd, AF_UNIX, 0, 0) <= 0) + b->hello_flags &= ~KDBUS_HELLO_ACCEPT_FD; + + if (b->output_fd != b->input_fd) + if (sd_is_socket(b->output_fd, AF_UNIX, 0, 0) <= 0) + b->hello_flags &= ~KDBUS_HELLO_ACCEPT_FD; + + if (b->is_server) + return bus_socket_read_auth(b); + else + return bus_socket_start_auth_client(b); +} + +int bus_socket_connect(sd_bus *b) { + int r; + + assert(b); + assert(b->input_fd < 0); + assert(b->output_fd < 0); + assert(b->sockaddr.sa.sa_family != AF_UNSPEC); + + b->input_fd = socket(b->sockaddr.sa.sa_family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (b->input_fd < 0) + return -errno; + + b->output_fd = b->input_fd; + + bus_socket_setup(b); + + r = connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size); + if (r < 0) { + if (errno == EINPROGRESS) + return 1; + + return -errno; + } + + return bus_socket_start_auth(b); +} + +int bus_socket_exec(sd_bus *b) { + int s[2], r; + pid_t pid; + + assert(b); + assert(b->input_fd < 0); + assert(b->output_fd < 0); + assert(b->exec_path); + + r = socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, s); + if (r < 0) + return -errno; + + pid = fork(); + if (pid < 0) { + safe_close_pair(s); + return -errno; + } + if (pid == 0) { + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + close_all_fds(s+1, 1); + + assert_se(dup3(s[1], STDIN_FILENO, 0) == STDIN_FILENO); + assert_se(dup3(s[1], STDOUT_FILENO, 0) == STDOUT_FILENO); + + if (s[1] != STDIN_FILENO && s[1] != STDOUT_FILENO) + safe_close(s[1]); + + fd_cloexec(STDIN_FILENO, false); + fd_cloexec(STDOUT_FILENO, false); + fd_nonblock(STDIN_FILENO, false); + fd_nonblock(STDOUT_FILENO, false); + + if (b->exec_argv) + execvp(b->exec_path, b->exec_argv); + else { + const char *argv[] = { b->exec_path, NULL }; + execvp(b->exec_path, (char**) argv); + } + + _exit(EXIT_FAILURE); + } + + safe_close(s[1]); + b->output_fd = b->input_fd = s[0]; + + bus_socket_setup(b); + + return bus_socket_start_auth(b); +} + +int bus_socket_take_fd(sd_bus *b) { + assert(b); + + bus_socket_setup(b); + + return bus_socket_start_auth(b); +} + +int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx) { + struct iovec *iov; + ssize_t k; + size_t n; + unsigned j; + int r; + + assert(bus); + assert(m); + assert(idx); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + if (*idx >= BUS_MESSAGE_SIZE(m)) + return 0; + + r = bus_message_setup_iovec(m); + if (r < 0) + return r; + + n = m->n_iovec * sizeof(struct iovec); + iov = alloca(n); + memcpy_safe(iov, m->iovec, n); + + j = 0; + iovec_advance(iov, &j, *idx); + + if (bus->prefer_writev) + k = writev(bus->output_fd, iov, m->n_iovec); + else { + struct msghdr mh = { + .msg_iov = iov, + .msg_iovlen = m->n_iovec, + }; + + if (m->n_fds > 0) { + struct cmsghdr *control; + + mh.msg_control = control = alloca(CMSG_SPACE(sizeof(int) * m->n_fds)); + mh.msg_controllen = control->cmsg_len = CMSG_LEN(sizeof(int) * m->n_fds); + control->cmsg_level = SOL_SOCKET; + control->cmsg_type = SCM_RIGHTS; + memcpy(CMSG_DATA(control), m->fds, sizeof(int) * m->n_fds); + } + + k = sendmsg(bus->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); + if (k < 0 && errno == ENOTSOCK) { + bus->prefer_writev = true; + k = writev(bus->output_fd, iov, m->n_iovec); + } + } + + if (k < 0) + return errno == EAGAIN ? 0 : -errno; + + *idx += (size_t) k; + return 1; +} + +static int bus_socket_read_message_need(sd_bus *bus, size_t *need) { + uint32_t a, b; + uint8_t e; + uint64_t sum; + + assert(bus); + assert(need); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + if (bus->rbuffer_size < sizeof(struct bus_header)) { + *need = sizeof(struct bus_header) + 8; + + /* Minimum message size: + * + * Header + + * + * Method Call: +2 string headers + * Signal: +3 string headers + * Method Error: +1 string headers + * +1 uint32 headers + * Method Reply: +1 uint32 headers + * + * A string header is at least 9 bytes + * A uint32 header is at least 8 bytes + * + * Hence the minimum message size of a valid message + * is header + 8 bytes */ + + return 0; + } + + a = ((const uint32_t*) bus->rbuffer)[1]; + b = ((const uint32_t*) bus->rbuffer)[3]; + + e = ((const uint8_t*) bus->rbuffer)[0]; + if (e == BUS_LITTLE_ENDIAN) { + a = le32toh(a); + b = le32toh(b); + } else if (e == BUS_BIG_ENDIAN) { + a = be32toh(a); + b = be32toh(b); + } else + return -EBADMSG; + + sum = (uint64_t) sizeof(struct bus_header) + (uint64_t) ALIGN_TO(b, 8) + (uint64_t) a; + if (sum >= BUS_MESSAGE_SIZE_MAX) + return -ENOBUFS; + + *need = (size_t) sum; + return 0; +} + +static int bus_socket_make_message(sd_bus *bus, size_t size) { + sd_bus_message *t; + void *b; + int r; + + assert(bus); + assert(bus->rbuffer_size >= size); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + r = bus_rqueue_make_room(bus); + if (r < 0) + return r; + + if (bus->rbuffer_size > size) { + b = memdup((const uint8_t*) bus->rbuffer + size, + bus->rbuffer_size - size); + if (!b) + return -ENOMEM; + } else + b = NULL; + + r = bus_message_from_malloc(bus, + bus->rbuffer, size, + bus->fds, bus->n_fds, + NULL, + &t); + if (r < 0) { + free(b); + return r; + } + + bus->rbuffer = b; + bus->rbuffer_size -= size; + + bus->fds = NULL; + bus->n_fds = 0; + + bus->rqueue[bus->rqueue_size++] = t; + + return 1; +} + +int bus_socket_read_message(sd_bus *bus) { + struct msghdr mh; + struct iovec iov = {}; + ssize_t k; + size_t need; + int r; + void *b; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int) * BUS_FDS_MAX)]; + } control; + bool handle_cmsg = false; + + assert(bus); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + r = bus_socket_read_message_need(bus, &need); + if (r < 0) + return r; + + if (bus->rbuffer_size >= need) + return bus_socket_make_message(bus, need); + + b = realloc(bus->rbuffer, need); + if (!b) + return -ENOMEM; + + bus->rbuffer = b; + + iov.iov_base = (uint8_t*) bus->rbuffer + bus->rbuffer_size; + iov.iov_len = need - bus->rbuffer_size; + + if (bus->prefer_readv) + k = readv(bus->input_fd, &iov, 1); + else { + zero(mh); + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_control = &control; + mh.msg_controllen = sizeof(control); + + k = recvmsg(bus->input_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); + if (k < 0 && errno == ENOTSOCK) { + bus->prefer_readv = true; + k = readv(bus->input_fd, &iov, 1); + } else + handle_cmsg = true; + } + if (k < 0) + return errno == EAGAIN ? 0 : -errno; + if (k == 0) + return -ECONNRESET; + + bus->rbuffer_size += k; + + if (handle_cmsg) { + struct cmsghdr *cmsg; + + CMSG_FOREACH(cmsg, &mh) + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + int n, *f; + + n = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + + if (!bus->can_fds) { + /* Whut? We received fds but this + * isn't actually enabled? Close them, + * and fail */ + + close_many((int*) CMSG_DATA(cmsg), n); + return -EIO; + } + + f = realloc(bus->fds, sizeof(int) * (bus->n_fds + n)); + if (!f) { + close_many((int*) CMSG_DATA(cmsg), n); + return -ENOMEM; + } + + memcpy_safe(f + bus->n_fds, CMSG_DATA(cmsg), n * sizeof(int)); + bus->fds = f; + bus->n_fds += n; + } else + log_debug("Got unexpected auxiliary data with level=%d and type=%d", + cmsg->cmsg_level, cmsg->cmsg_type); + } + + r = bus_socket_read_message_need(bus, &need); + if (r < 0) + return r; + + if (bus->rbuffer_size >= need) + return bus_socket_make_message(bus, need); + + return 1; +} + +int bus_socket_process_opening(sd_bus *b) { + int error = 0; + socklen_t slen = sizeof(error); + struct pollfd p = { + .fd = b->output_fd, + .events = POLLOUT, + }; + int r; + + assert(b->state == BUS_OPENING); + + r = poll(&p, 1, 0); + if (r < 0) + return -errno; + + if (!(p.revents & (POLLOUT|POLLERR|POLLHUP))) + return 0; + + r = getsockopt(b->output_fd, SOL_SOCKET, SO_ERROR, &error, &slen); + if (r < 0) + b->last_connect_error = errno; + else if (error != 0) + b->last_connect_error = error; + else if (p.revents & (POLLERR|POLLHUP)) + b->last_connect_error = ECONNREFUSED; + else + return bus_socket_start_auth(b); + + return bus_next_address(b); +} + +int bus_socket_process_authenticating(sd_bus *b) { + int r; + + assert(b); + assert(b->state == BUS_AUTHENTICATING); + + if (now(CLOCK_MONOTONIC) >= b->auth_timeout) + return -ETIMEDOUT; + + r = bus_socket_write_auth(b); + if (r != 0) + return r; + + return bus_socket_read_auth(b); +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-socket.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-socket.h new file mode 100644 index 0000000000..6e1d32e6a7 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-socket.h @@ -0,0 +1,37 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 + +void bus_socket_setup(sd_bus *b); + +int bus_socket_connect(sd_bus *b); +int bus_socket_exec(sd_bus *b); +int bus_socket_take_fd(sd_bus *b); +int bus_socket_start_auth(sd_bus *b); + +int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx); +int bus_socket_read_message(sd_bus *bus); + +int bus_socket_process_opening(sd_bus *b); +int bus_socket_process_authenticating(sd_bus *b); + +bool bus_socket_auth_needs_write(sd_bus *b); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-track.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-track.c new file mode 100644 index 0000000000..81e6d22816 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-track.c @@ -0,0 +1,337 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "alloc-util.h" +#include "bus-internal.h" +#include "bus-track.h" +#include "bus-util.h" + +struct sd_bus_track { + unsigned n_ref; + sd_bus *bus; + sd_bus_track_handler_t handler; + void *userdata; + Hashmap *names; + LIST_FIELDS(sd_bus_track, queue); + Iterator iterator; + bool in_queue; + bool modified; +}; + +#define MATCH_PREFIX \ + "type='signal'," \ + "sender='org.freedesktop.DBus'," \ + "path='/org/freedesktop/DBus'," \ + "interface='org.freedesktop.DBus'," \ + "member='NameOwnerChanged'," \ + "arg0='" + +#define MATCH_SUFFIX \ + "'" + +#define MATCH_FOR_NAME(name) \ + ({ \ + char *_x; \ + size_t _l = strlen(name); \ + _x = alloca(strlen(MATCH_PREFIX)+_l+strlen(MATCH_SUFFIX)+1); \ + strcpy(stpcpy(stpcpy(_x, MATCH_PREFIX), name), MATCH_SUFFIX); \ + _x; \ + }) + +static void bus_track_add_to_queue(sd_bus_track *track) { + assert(track); + + if (track->in_queue) + return; + + if (!track->handler) + return; + + LIST_PREPEND(queue, track->bus->track_queue, track); + track->in_queue = true; +} + +static void bus_track_remove_from_queue(sd_bus_track *track) { + assert(track); + + if (!track->in_queue) + return; + + LIST_REMOVE(queue, track->bus->track_queue, track); + track->in_queue = false; +} + +_public_ int sd_bus_track_new( + sd_bus *bus, + sd_bus_track **track, + sd_bus_track_handler_t handler, + void *userdata) { + + sd_bus_track *t; + + assert_return(bus, -EINVAL); + assert_return(track, -EINVAL); + + if (!bus->bus_client) + return -EINVAL; + + t = new0(sd_bus_track, 1); + if (!t) + return -ENOMEM; + + t->n_ref = 1; + t->handler = handler; + t->userdata = userdata; + t->bus = sd_bus_ref(bus); + + bus_track_add_to_queue(t); + + *track = t; + return 0; +} + +_public_ sd_bus_track* sd_bus_track_ref(sd_bus_track *track) { + + if (!track) + return NULL; + + assert(track->n_ref > 0); + + track->n_ref++; + + return track; +} + +_public_ sd_bus_track* sd_bus_track_unref(sd_bus_track *track) { + const char *n; + + if (!track) + return NULL; + + assert(track->n_ref > 0); + + if (track->n_ref > 1) { + track->n_ref--; + return NULL; + } + + while ((n = hashmap_first_key(track->names))) + sd_bus_track_remove_name(track, n); + + bus_track_remove_from_queue(track); + hashmap_free(track->names); + sd_bus_unref(track->bus); + free(track); + + return NULL; +} + +static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) { + sd_bus_track *track = userdata; + const char *name, *old, *new; + int r; + + assert(message); + assert(track); + + r = sd_bus_message_read(message, "sss", &name, &old, &new); + if (r < 0) + return 0; + + sd_bus_track_remove_name(track, name); + return 0; +} + +_public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) { + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; + _cleanup_free_ char *n = NULL; + const char *match; + int r; + + assert_return(track, -EINVAL); + assert_return(service_name_is_valid(name), -EINVAL); + + r = hashmap_ensure_allocated(&track->names, &string_hash_ops); + if (r < 0) + return r; + + n = strdup(name); + if (!n) + return -ENOMEM; + + /* First, subscribe to this name */ + match = MATCH_FOR_NAME(n); + r = sd_bus_add_match(track->bus, &slot, match, on_name_owner_changed, track); + if (r < 0) + return r; + + r = hashmap_put(track->names, n, slot); + if (r == -EEXIST) + return 0; + if (r < 0) + return r; + + /* Second, check if it is currently existing, or maybe + * doesn't, or maybe disappeared already. */ + r = sd_bus_get_name_creds(track->bus, n, 0, NULL); + if (r < 0) { + hashmap_remove(track->names, n); + return r; + } + + n = NULL; + slot = NULL; + + bus_track_remove_from_queue(track); + track->modified = true; + + return 1; +} + +_public_ int sd_bus_track_remove_name(sd_bus_track *track, const char *name) { + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; + _cleanup_free_ char *n = NULL; + + assert_return(name, -EINVAL); + + if (!track) + return 0; + + slot = hashmap_remove2(track->names, (char*) name, (void**) &n); + if (!slot) + return 0; + + if (hashmap_isempty(track->names)) + bus_track_add_to_queue(track); + + track->modified = true; + + return 1; +} + +_public_ unsigned sd_bus_track_count(sd_bus_track *track) { + if (!track) + return 0; + + return hashmap_size(track->names); +} + +_public_ const char* sd_bus_track_contains(sd_bus_track *track, const char *name) { + assert_return(track, NULL); + assert_return(name, NULL); + + return hashmap_get(track->names, (void*) name) ? name : NULL; +} + +_public_ const char* sd_bus_track_first(sd_bus_track *track) { + const char *n = NULL; + + if (!track) + return NULL; + + track->modified = false; + track->iterator = ITERATOR_FIRST; + + hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n); + return n; +} + +_public_ const char* sd_bus_track_next(sd_bus_track *track) { + const char *n = NULL; + + if (!track) + return NULL; + + if (track->modified) + return NULL; + + hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n); + return n; +} + +_public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) { + const char *sender; + + assert_return(track, -EINVAL); + assert_return(m, -EINVAL); + + sender = sd_bus_message_get_sender(m); + if (!sender) + return -EINVAL; + + return sd_bus_track_add_name(track, sender); +} + +_public_ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m) { + const char *sender; + + assert_return(track, -EINVAL); + assert_return(m, -EINVAL); + + sender = sd_bus_message_get_sender(m); + if (!sender) + return -EINVAL; + + return sd_bus_track_remove_name(track, sender); +} + +_public_ sd_bus* sd_bus_track_get_bus(sd_bus_track *track) { + assert_return(track, NULL); + + return track->bus; +} + +void bus_track_dispatch(sd_bus_track *track) { + int r; + + assert(track); + assert(track->in_queue); + assert(track->handler); + + bus_track_remove_from_queue(track); + + sd_bus_track_ref(track); + + r = track->handler(track, track->userdata); + if (r < 0) + log_debug_errno(r, "Failed to process track handler: %m"); + else if (r == 0) + bus_track_add_to_queue(track); + + sd_bus_track_unref(track); +} + +_public_ void *sd_bus_track_get_userdata(sd_bus_track *track) { + assert_return(track, NULL); + + return track->userdata; +} + +_public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) { + void *ret; + + assert_return(track, NULL); + + ret = track->userdata; + track->userdata = userdata; + + return ret; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-track.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-track.h new file mode 100644 index 0000000000..7d93a727d6 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-track.h @@ -0,0 +1,22 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +void bus_track_dispatch(sd_bus_track *track); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-type.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-type.c new file mode 100644 index 0000000000..c692afc580 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-type.c @@ -0,0 +1,176 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "bus-type.h" + +bool bus_type_is_valid(char c) { + static const char valid[] = { + SD_BUS_TYPE_BYTE, + SD_BUS_TYPE_BOOLEAN, + SD_BUS_TYPE_INT16, + SD_BUS_TYPE_UINT16, + SD_BUS_TYPE_INT32, + SD_BUS_TYPE_UINT32, + SD_BUS_TYPE_INT64, + SD_BUS_TYPE_UINT64, + SD_BUS_TYPE_DOUBLE, + SD_BUS_TYPE_STRING, + SD_BUS_TYPE_OBJECT_PATH, + SD_BUS_TYPE_SIGNATURE, + SD_BUS_TYPE_ARRAY, + SD_BUS_TYPE_VARIANT, + SD_BUS_TYPE_STRUCT, + SD_BUS_TYPE_DICT_ENTRY, + SD_BUS_TYPE_UNIX_FD + }; + + return !!memchr(valid, c, sizeof(valid)); +} + +bool bus_type_is_valid_in_signature(char c) { + static const char valid[] = { + SD_BUS_TYPE_BYTE, + SD_BUS_TYPE_BOOLEAN, + SD_BUS_TYPE_INT16, + SD_BUS_TYPE_UINT16, + SD_BUS_TYPE_INT32, + SD_BUS_TYPE_UINT32, + SD_BUS_TYPE_INT64, + SD_BUS_TYPE_UINT64, + SD_BUS_TYPE_DOUBLE, + SD_BUS_TYPE_STRING, + SD_BUS_TYPE_OBJECT_PATH, + SD_BUS_TYPE_SIGNATURE, + SD_BUS_TYPE_ARRAY, + SD_BUS_TYPE_VARIANT, + SD_BUS_TYPE_STRUCT_BEGIN, + SD_BUS_TYPE_STRUCT_END, + SD_BUS_TYPE_DICT_ENTRY_BEGIN, + SD_BUS_TYPE_DICT_ENTRY_END, + SD_BUS_TYPE_UNIX_FD + }; + + return !!memchr(valid, c, sizeof(valid)); +} + +bool bus_type_is_basic(char c) { + static const char valid[] = { + SD_BUS_TYPE_BYTE, + SD_BUS_TYPE_BOOLEAN, + SD_BUS_TYPE_INT16, + SD_BUS_TYPE_UINT16, + SD_BUS_TYPE_INT32, + SD_BUS_TYPE_UINT32, + SD_BUS_TYPE_INT64, + SD_BUS_TYPE_UINT64, + SD_BUS_TYPE_DOUBLE, + SD_BUS_TYPE_STRING, + SD_BUS_TYPE_OBJECT_PATH, + SD_BUS_TYPE_SIGNATURE, + SD_BUS_TYPE_UNIX_FD + }; + + return !!memchr(valid, c, sizeof(valid)); +} + +bool bus_type_is_trivial(char c) { + static const char valid[] = { + SD_BUS_TYPE_BYTE, + SD_BUS_TYPE_BOOLEAN, + SD_BUS_TYPE_INT16, + SD_BUS_TYPE_UINT16, + SD_BUS_TYPE_INT32, + SD_BUS_TYPE_UINT32, + SD_BUS_TYPE_INT64, + SD_BUS_TYPE_UINT64, + SD_BUS_TYPE_DOUBLE + }; + + return !!memchr(valid, c, sizeof(valid)); +} + +bool bus_type_is_container(char c) { + static const char valid[] = { + SD_BUS_TYPE_ARRAY, + SD_BUS_TYPE_VARIANT, + SD_BUS_TYPE_STRUCT, + SD_BUS_TYPE_DICT_ENTRY + }; + + return !!memchr(valid, c, sizeof(valid)); +} + +int bus_type_get_alignment(char c) { + + switch (c) { + case SD_BUS_TYPE_BYTE: + case SD_BUS_TYPE_SIGNATURE: + case SD_BUS_TYPE_VARIANT: + return 1; + + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + return 2; + + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_ARRAY: + case SD_BUS_TYPE_UNIX_FD: + return 4; + + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + case SD_BUS_TYPE_STRUCT: + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: + return 8; + } + + return -EINVAL; +} + +int bus_type_get_size(char c) { + + switch (c) { + case SD_BUS_TYPE_BYTE: + return 1; + + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + return 2; + + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_UNIX_FD: + return 4; + + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + return 8; + } + + return -EINVAL; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-type.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-type.h new file mode 100644 index 0000000000..7169b0f765 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-type.h @@ -0,0 +1,37 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include "macro.h" + +bool bus_type_is_valid(char c) _const_; +bool bus_type_is_valid_in_signature(char c) _const_; +bool bus_type_is_basic(char c) _const_; +/* "trivial" is systemd's term for what the D-Bus Specification calls + * a "fixed type": that is, a basic type of fixed length */ +bool bus_type_is_trivial(char c) _const_; +bool bus_type_is_container(char c) _const_; + +int bus_type_get_alignment(char c) _const_; +int bus_type_get_size(char c) _const_; diff --git a/src/libsystemd/libsystemd-internal/sd-bus/kdbus.h b/src/libsystemd/libsystemd-internal/sd-bus/kdbus.h new file mode 100644 index 0000000000..ecffc6b13c --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/kdbus.h @@ -0,0 +1,980 @@ +/* + * kdbus 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. + */ + +#ifndef _UAPI_KDBUS_H_ +#define _UAPI_KDBUS_H_ + +#include +#include + +#define KDBUS_IOCTL_MAGIC 0x95 +#define KDBUS_SRC_ID_KERNEL (0) +#define KDBUS_DST_ID_NAME (0) +#define KDBUS_MATCH_ID_ANY (~0ULL) +#define KDBUS_DST_ID_BROADCAST (~0ULL) +#define KDBUS_FLAG_NEGOTIATE (1ULL << 63) + +/** + * struct kdbus_notify_id_change - name registry change message + * @id: New or former owner of the name + * @flags: flags field from KDBUS_HELLO_* + * + * Sent from kernel to userspace when the owner or activator of + * a well-known name changes. + * + * Attached to: + * KDBUS_ITEM_ID_ADD + * KDBUS_ITEM_ID_REMOVE + */ +struct kdbus_notify_id_change { + __u64 id; + __u64 flags; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_notify_name_change - name registry change message + * @old_id: ID and flags of former owner of a name + * @new_id: ID and flags of new owner of a name + * @name: Well-known name + * + * Sent from kernel to userspace when the owner or activator of + * a well-known name changes. + * + * Attached to: + * KDBUS_ITEM_NAME_ADD + * KDBUS_ITEM_NAME_REMOVE + * KDBUS_ITEM_NAME_CHANGE + */ +struct kdbus_notify_name_change { + struct kdbus_notify_id_change old_id; + struct kdbus_notify_id_change new_id; + char name[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_creds - process credentials + * @uid: User ID + * @euid: Effective UID + * @suid: Saved UID + * @fsuid: Filesystem UID + * @gid: Group ID + * @egid: Effective GID + * @sgid: Saved GID + * @fsgid: Filesystem GID + * + * Attached to: + * KDBUS_ITEM_CREDS + */ +struct kdbus_creds { + __u64 uid; + __u64 euid; + __u64 suid; + __u64 fsuid; + __u64 gid; + __u64 egid; + __u64 sgid; + __u64 fsgid; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_pids - process identifiers + * @pid: Process ID + * @tid: Thread ID + * @ppid: Parent process ID + * + * The PID and TID of a process. + * + * Attached to: + * KDBUS_ITEM_PIDS + */ +struct kdbus_pids { + __u64 pid; + __u64 tid; + __u64 ppid; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_caps - process capabilities + * @last_cap: Highest currently known capability bit + * @caps: Variable number of 32-bit capabilities flags + * + * Contains a variable number of 32-bit capabilities flags. + * + * Attached to: + * KDBUS_ITEM_CAPS + */ +struct kdbus_caps { + __u32 last_cap; + __u32 caps[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_audit - audit information + * @sessionid: The audit session ID + * @loginuid: The audit login uid + * + * Attached to: + * KDBUS_ITEM_AUDIT + */ +struct kdbus_audit { + __u32 sessionid; + __u32 loginuid; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_timestamp + * @seqnum: Global per-domain message sequence number + * @monotonic_ns: Monotonic timestamp, in nanoseconds + * @realtime_ns: Realtime timestamp, in nanoseconds + * + * Attached to: + * KDBUS_ITEM_TIMESTAMP + */ +struct kdbus_timestamp { + __u64 seqnum; + __u64 monotonic_ns; + __u64 realtime_ns; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_vec - I/O vector for kdbus payload items + * @size: The size of the vector + * @address: Memory address of data buffer + * @offset: Offset in the in-message payload memory, + * relative to the message head + * + * Attached to: + * KDBUS_ITEM_PAYLOAD_VEC, KDBUS_ITEM_PAYLOAD_OFF + */ +struct kdbus_vec { + __u64 size; + union { + __u64 address; + __u64 offset; + }; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_bloom_parameter - bus-wide bloom parameters + * @size: Size of the bit field in bytes (m / 8) + * @n_hash: Number of hash functions used (k) + */ +struct kdbus_bloom_parameter { + __u64 size; + __u64 n_hash; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_bloom_filter - bloom filter containing n elements + * @generation: Generation of the element set in the filter + * @data: Bit field, multiple of 8 bytes + */ +struct kdbus_bloom_filter { + __u64 generation; + __u64 data[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_memfd - a kdbus memfd + * @start: The offset into the memfd where the segment starts + * @size: The size of the memfd segment + * @fd: The file descriptor number + * @__pad: Padding to ensure proper alignment and size + * + * Attached to: + * KDBUS_ITEM_PAYLOAD_MEMFD + */ +struct kdbus_memfd { + __u64 start; + __u64 size; + int fd; + __u32 __pad; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_name - a registered well-known name with its flags + * @flags: Flags from KDBUS_NAME_* + * @name: Well-known name + * + * Attached to: + * KDBUS_ITEM_OWNED_NAME + */ +struct kdbus_name { + __u64 flags; + char name[0]; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_policy_access_type - permissions of a policy record + * @_KDBUS_POLICY_ACCESS_NULL: Uninitialized/invalid + * @KDBUS_POLICY_ACCESS_USER: Grant access to a uid + * @KDBUS_POLICY_ACCESS_GROUP: Grant access to gid + * @KDBUS_POLICY_ACCESS_WORLD: World-accessible + */ +enum kdbus_policy_access_type { + _KDBUS_POLICY_ACCESS_NULL, + KDBUS_POLICY_ACCESS_USER, + KDBUS_POLICY_ACCESS_GROUP, + KDBUS_POLICY_ACCESS_WORLD, +}; + +/** + * enum kdbus_policy_access_flags - mode flags + * @KDBUS_POLICY_OWN: Allow to own a well-known name + * Implies KDBUS_POLICY_TALK and KDBUS_POLICY_SEE + * @KDBUS_POLICY_TALK: Allow communication to a well-known name + * Implies KDBUS_POLICY_SEE + * @KDBUS_POLICY_SEE: Allow to see a well-known name + */ +enum kdbus_policy_type { + KDBUS_POLICY_SEE = 0, + KDBUS_POLICY_TALK, + KDBUS_POLICY_OWN, +}; + +/** + * struct kdbus_policy_access - policy access item + * @type: One of KDBUS_POLICY_ACCESS_* types + * @access: Access to grant + * @id: For KDBUS_POLICY_ACCESS_USER, the uid + * For KDBUS_POLICY_ACCESS_GROUP, the gid + */ +struct kdbus_policy_access { + __u64 type; /* USER, GROUP, WORLD */ + __u64 access; /* OWN, TALK, SEE */ + __u64 id; /* uid, gid, 0 */ +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_attach_flags - flags for metadata attachments + * @KDBUS_ATTACH_TIMESTAMP: Timestamp + * @KDBUS_ATTACH_CREDS: Credentials + * @KDBUS_ATTACH_PIDS: PIDs + * @KDBUS_ATTACH_AUXGROUPS: Auxiliary groups + * @KDBUS_ATTACH_NAMES: Well-known names + * @KDBUS_ATTACH_TID_COMM: The "comm" process identifier of the TID + * @KDBUS_ATTACH_PID_COMM: The "comm" process identifier of the PID + * @KDBUS_ATTACH_EXE: The path of the executable + * @KDBUS_ATTACH_CMDLINE: The process command line + * @KDBUS_ATTACH_CGROUP: The croup membership + * @KDBUS_ATTACH_CAPS: The process capabilities + * @KDBUS_ATTACH_SECLABEL: The security label + * @KDBUS_ATTACH_AUDIT: The audit IDs + * @KDBUS_ATTACH_CONN_DESCRIPTION: The human-readable connection name + * @_KDBUS_ATTACH_ALL: All of the above + * @_KDBUS_ATTACH_ANY: Wildcard match to enable any kind of + * metatdata. + */ +enum kdbus_attach_flags { + KDBUS_ATTACH_TIMESTAMP = 1ULL << 0, + KDBUS_ATTACH_CREDS = 1ULL << 1, + KDBUS_ATTACH_PIDS = 1ULL << 2, + KDBUS_ATTACH_AUXGROUPS = 1ULL << 3, + KDBUS_ATTACH_NAMES = 1ULL << 4, + KDBUS_ATTACH_TID_COMM = 1ULL << 5, + KDBUS_ATTACH_PID_COMM = 1ULL << 6, + KDBUS_ATTACH_EXE = 1ULL << 7, + KDBUS_ATTACH_CMDLINE = 1ULL << 8, + KDBUS_ATTACH_CGROUP = 1ULL << 9, + KDBUS_ATTACH_CAPS = 1ULL << 10, + KDBUS_ATTACH_SECLABEL = 1ULL << 11, + KDBUS_ATTACH_AUDIT = 1ULL << 12, + KDBUS_ATTACH_CONN_DESCRIPTION = 1ULL << 13, + _KDBUS_ATTACH_ALL = (1ULL << 14) - 1, + _KDBUS_ATTACH_ANY = ~0ULL +}; + +/** + * enum kdbus_item_type - item types to chain data in a list + * @_KDBUS_ITEM_NULL: Uninitialized/invalid + * @_KDBUS_ITEM_USER_BASE: Start of user items + * @KDBUS_ITEM_NEGOTIATE: Negotiate supported items + * @KDBUS_ITEM_PAYLOAD_VEC: Vector to data + * @KDBUS_ITEM_PAYLOAD_OFF: Data at returned offset to message head + * @KDBUS_ITEM_PAYLOAD_MEMFD: Data as sealed memfd + * @KDBUS_ITEM_FDS: Attached file descriptors + * @KDBUS_ITEM_CANCEL_FD: FD used to cancel a synchronous + * operation by writing to it from + * userspace + * @KDBUS_ITEM_BLOOM_PARAMETER: Bus-wide bloom parameters, used with + * KDBUS_CMD_BUS_MAKE, carries a + * struct kdbus_bloom_parameter + * @KDBUS_ITEM_BLOOM_FILTER: Bloom filter carried with a message, + * used to match against a bloom mask of a + * connection, carries a struct + * kdbus_bloom_filter + * @KDBUS_ITEM_BLOOM_MASK: Bloom mask used to match against a + * message'sbloom filter + * @KDBUS_ITEM_DST_NAME: Destination's well-known name + * @KDBUS_ITEM_MAKE_NAME: Name of domain, bus, endpoint + * @KDBUS_ITEM_ATTACH_FLAGS_SEND: Attach-flags, used for updating which + * metadata a connection opts in to send + * @KDBUS_ITEM_ATTACH_FLAGS_RECV: Attach-flags, used for updating which + * metadata a connection requests to + * receive for each reeceived message + * @KDBUS_ITEM_ID: Connection ID + * @KDBUS_ITEM_NAME: Well-know name with flags + * @_KDBUS_ITEM_ATTACH_BASE: Start of metadata attach items + * @KDBUS_ITEM_TIMESTAMP: Timestamp + * @KDBUS_ITEM_CREDS: Process credentials + * @KDBUS_ITEM_PIDS: Process identifiers + * @KDBUS_ITEM_AUXGROUPS: Auxiliary process groups + * @KDBUS_ITEM_OWNED_NAME: A name owned by the associated + * connection + * @KDBUS_ITEM_TID_COMM: Thread ID "comm" identifier + * (Don't trust this, see below.) + * @KDBUS_ITEM_PID_COMM: Process ID "comm" identifier + * (Don't trust this, see below.) + * @KDBUS_ITEM_EXE: The path of the executable + * (Don't trust this, see below.) + * @KDBUS_ITEM_CMDLINE: The process command line + * (Don't trust this, see below.) + * @KDBUS_ITEM_CGROUP: The croup membership + * @KDBUS_ITEM_CAPS: The process capabilities + * @KDBUS_ITEM_SECLABEL: The security label + * @KDBUS_ITEM_AUDIT: The audit IDs + * @KDBUS_ITEM_CONN_DESCRIPTION: The connection's human-readable name + * (debugging) + * @_KDBUS_ITEM_POLICY_BASE: Start of policy items + * @KDBUS_ITEM_POLICY_ACCESS: Policy access block + * @_KDBUS_ITEM_KERNEL_BASE: Start of kernel-generated message items + * @KDBUS_ITEM_NAME_ADD: Notification in kdbus_notify_name_change + * @KDBUS_ITEM_NAME_REMOVE: Notification in kdbus_notify_name_change + * @KDBUS_ITEM_NAME_CHANGE: Notification in kdbus_notify_name_change + * @KDBUS_ITEM_ID_ADD: Notification in kdbus_notify_id_change + * @KDBUS_ITEM_ID_REMOVE: Notification in kdbus_notify_id_change + * @KDBUS_ITEM_REPLY_TIMEOUT: Timeout has been reached + * @KDBUS_ITEM_REPLY_DEAD: Destination died + * + * N.B: The process and thread COMM fields, as well as the CMDLINE and + * EXE fields may be altered by unprivileged processes und should + * hence *not* used for security decisions. Peers should make use of + * these items only for informational purposes, such as generating log + * records. + */ +enum kdbus_item_type { + _KDBUS_ITEM_NULL, + _KDBUS_ITEM_USER_BASE, + KDBUS_ITEM_NEGOTIATE = _KDBUS_ITEM_USER_BASE, + KDBUS_ITEM_PAYLOAD_VEC, + KDBUS_ITEM_PAYLOAD_OFF, + KDBUS_ITEM_PAYLOAD_MEMFD, + KDBUS_ITEM_FDS, + KDBUS_ITEM_CANCEL_FD, + KDBUS_ITEM_BLOOM_PARAMETER, + KDBUS_ITEM_BLOOM_FILTER, + KDBUS_ITEM_BLOOM_MASK, + KDBUS_ITEM_DST_NAME, + KDBUS_ITEM_MAKE_NAME, + KDBUS_ITEM_ATTACH_FLAGS_SEND, + KDBUS_ITEM_ATTACH_FLAGS_RECV, + KDBUS_ITEM_ID, + KDBUS_ITEM_NAME, + KDBUS_ITEM_DST_ID, + + /* keep these item types in sync with KDBUS_ATTACH_* flags */ + _KDBUS_ITEM_ATTACH_BASE = 0x1000, + KDBUS_ITEM_TIMESTAMP = _KDBUS_ITEM_ATTACH_BASE, + KDBUS_ITEM_CREDS, + KDBUS_ITEM_PIDS, + KDBUS_ITEM_AUXGROUPS, + KDBUS_ITEM_OWNED_NAME, + KDBUS_ITEM_TID_COMM, + KDBUS_ITEM_PID_COMM, + KDBUS_ITEM_EXE, + KDBUS_ITEM_CMDLINE, + KDBUS_ITEM_CGROUP, + KDBUS_ITEM_CAPS, + KDBUS_ITEM_SECLABEL, + KDBUS_ITEM_AUDIT, + KDBUS_ITEM_CONN_DESCRIPTION, + + _KDBUS_ITEM_POLICY_BASE = 0x2000, + KDBUS_ITEM_POLICY_ACCESS = _KDBUS_ITEM_POLICY_BASE, + + _KDBUS_ITEM_KERNEL_BASE = 0x8000, + KDBUS_ITEM_NAME_ADD = _KDBUS_ITEM_KERNEL_BASE, + KDBUS_ITEM_NAME_REMOVE, + KDBUS_ITEM_NAME_CHANGE, + KDBUS_ITEM_ID_ADD, + KDBUS_ITEM_ID_REMOVE, + KDBUS_ITEM_REPLY_TIMEOUT, + KDBUS_ITEM_REPLY_DEAD, +}; + +/** + * struct kdbus_item - chain of data blocks + * @size: Overall data record size + * @type: Kdbus_item type of data + * @data: Generic bytes + * @data32: Generic 32 bit array + * @data64: Generic 64 bit array + * @str: Generic string + * @id: Connection ID + * @vec: KDBUS_ITEM_PAYLOAD_VEC + * @creds: KDBUS_ITEM_CREDS + * @audit: KDBUS_ITEM_AUDIT + * @timestamp: KDBUS_ITEM_TIMESTAMP + * @name: KDBUS_ITEM_NAME + * @bloom_parameter: KDBUS_ITEM_BLOOM_PARAMETER + * @bloom_filter: KDBUS_ITEM_BLOOM_FILTER + * @memfd: KDBUS_ITEM_PAYLOAD_MEMFD + * @name_change: KDBUS_ITEM_NAME_ADD + * KDBUS_ITEM_NAME_REMOVE + * KDBUS_ITEM_NAME_CHANGE + * @id_change: KDBUS_ITEM_ID_ADD + * KDBUS_ITEM_ID_REMOVE + * @policy: KDBUS_ITEM_POLICY_ACCESS + */ +struct kdbus_item { + __u64 size; + __u64 type; + union { + __u8 data[0]; + __u32 data32[0]; + __u64 data64[0]; + char str[0]; + + __u64 id; + struct kdbus_vec vec; + struct kdbus_creds creds; + struct kdbus_pids pids; + struct kdbus_audit audit; + struct kdbus_caps caps; + struct kdbus_timestamp timestamp; + struct kdbus_name name; + struct kdbus_bloom_parameter bloom_parameter; + struct kdbus_bloom_filter bloom_filter; + struct kdbus_memfd memfd; + int fds[0]; + struct kdbus_notify_name_change name_change; + struct kdbus_notify_id_change id_change; + struct kdbus_policy_access policy_access; + }; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_msg_flags - type of message + * @KDBUS_MSG_EXPECT_REPLY: Expect a reply message, used for + * method calls. The userspace-supplied + * cookie identifies the message and the + * respective reply carries the cookie + * in cookie_reply + * @KDBUS_MSG_NO_AUTO_START: Do not start a service if the addressed + * name is not currently active. This flag is + * not looked at by the kernel but only + * serves as hint for userspace implementations. + * @KDBUS_MSG_SIGNAL: Treat this message as signal + */ +enum kdbus_msg_flags { + KDBUS_MSG_EXPECT_REPLY = 1ULL << 0, + KDBUS_MSG_NO_AUTO_START = 1ULL << 1, + KDBUS_MSG_SIGNAL = 1ULL << 2, +}; + +/** + * enum kdbus_payload_type - type of payload carried by message + * @KDBUS_PAYLOAD_KERNEL: Kernel-generated simple message + * @KDBUS_PAYLOAD_DBUS: D-Bus marshalling "DBusDBus" + * + * Any payload-type is accepted. Common types will get added here once + * established. + */ +enum kdbus_payload_type { + KDBUS_PAYLOAD_KERNEL, + KDBUS_PAYLOAD_DBUS = 0x4442757344427573ULL, +}; + +/** + * struct kdbus_msg - the representation of a kdbus message + * @size: Total size of the message + * @flags: Message flags (KDBUS_MSG_*), userspace → kernel + * @priority: Message queue priority value + * @dst_id: 64-bit ID of the destination connection + * @src_id: 64-bit ID of the source connection + * @payload_type: Payload type (KDBUS_PAYLOAD_*) + * @cookie: Userspace-supplied cookie, for the connection + * to identify its messages + * @timeout_ns: The time to wait for a message reply from the peer. + * If there is no reply, and the send command is + * executed asynchronously, a kernel-generated message + * with an attached KDBUS_ITEM_REPLY_TIMEOUT item + * is sent to @src_id. For synchronously executed send + * command, the value denotes the maximum time the call + * blocks to wait for a reply. The timeout is expected in + * nanoseconds and as absolute CLOCK_MONOTONIC value. + * @cookie_reply: A reply to the requesting message with the same + * cookie. The requesting connection can match its + * request and the reply with this value + * @items: A list of kdbus_items containing the message payload + */ +struct kdbus_msg { + __u64 size; + __u64 flags; + __s64 priority; + __u64 dst_id; + __u64 src_id; + __u64 payload_type; + __u64 cookie; + union { + __u64 timeout_ns; + __u64 cookie_reply; + }; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_msg_info - returned message container + * @offset: Offset of kdbus_msg slice in pool + * @msg_size: Copy of the kdbus_msg.size field + * @return_flags: Command return flags, kernel → userspace + */ +struct kdbus_msg_info { + __u64 offset; + __u64 msg_size; + __u64 return_flags; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_send_flags - flags for sending messages + * @KDBUS_SEND_SYNC_REPLY: Wait for destination connection to + * reply to this message. The + * KDBUS_CMD_SEND ioctl() will block + * until the reply is received, and + * reply in struct kdbus_cmd_send will + * yield the offset in the sender's pool + * where the reply can be found. + * This flag is only valid if + * @KDBUS_MSG_EXPECT_REPLY is set as well. + */ +enum kdbus_send_flags { + KDBUS_SEND_SYNC_REPLY = 1ULL << 0, +}; + +/** + * struct kdbus_cmd_send - send message + * @size: Overall size of this structure + * @flags: Flags to change send behavior (KDBUS_SEND_*) + * @return_flags: Command return flags, kernel → userspace + * @msg_address: Storage address of the kdbus_msg to send + * @reply: Storage for message reply if KDBUS_SEND_SYNC_REPLY + * was given + * @items: Additional items for this command + */ +struct kdbus_cmd_send { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 msg_address; + struct kdbus_msg_info reply; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_recv_flags - flags for de-queuing messages + * @KDBUS_RECV_PEEK: Return the next queued message without + * actually de-queuing it, and without installing + * any file descriptors or other resources. It is + * usually used to determine the activating + * connection of a bus name. + * @KDBUS_RECV_DROP: Drop and free the next queued message and all + * its resources without actually receiving it. + * @KDBUS_RECV_USE_PRIORITY: Only de-queue messages with the specified or + * higher priority (lowest values); if not set, + * the priority value is ignored. + */ +enum kdbus_recv_flags { + KDBUS_RECV_PEEK = 1ULL << 0, + KDBUS_RECV_DROP = 1ULL << 1, + KDBUS_RECV_USE_PRIORITY = 1ULL << 2, +}; + +/** + * enum kdbus_recv_return_flags - return flags for message receive commands + * @KDBUS_RECV_RETURN_INCOMPLETE_FDS: One or more file descriptors could not + * be installed. These descriptors in + * KDBUS_ITEM_FDS will carry the value -1. + * @KDBUS_RECV_RETURN_DROPPED_MSGS: There have been dropped messages since + * the last time a message was received. + * The 'dropped_msgs' counter contains the + * number of messages dropped pool + * overflows or other missed broadcasts. + */ +enum kdbus_recv_return_flags { + KDBUS_RECV_RETURN_INCOMPLETE_FDS = 1ULL << 0, + KDBUS_RECV_RETURN_DROPPED_MSGS = 1ULL << 1, +}; + +/** + * struct kdbus_cmd_recv - struct to de-queue a buffered message + * @size: Overall size of this object + * @flags: KDBUS_RECV_* flags, userspace → kernel + * @return_flags: Command return flags, kernel → userspace + * @priority: Minimum priority of the messages to de-queue. Lowest + * values have the highest priority. + * @dropped_msgs: In case there were any dropped messages since the last + * time a message was received, this will be set to the + * number of lost messages and + * KDBUS_RECV_RETURN_DROPPED_MSGS will be set in + * 'return_flags'. This can only happen if the ioctl + * returns 0 or EAGAIN. + * @msg: Return storage for received message. + * @items: Additional items for this command. + * + * This struct is used with the KDBUS_CMD_RECV ioctl. + */ +struct kdbus_cmd_recv { + __u64 size; + __u64 flags; + __u64 return_flags; + __s64 priority; + __u64 dropped_msgs; + struct kdbus_msg_info msg; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_cmd_free - struct to free a slice of memory in the pool + * @size: Overall size of this structure + * @flags: Flags for the free command, userspace → kernel + * @return_flags: Command return flags, kernel → userspace + * @offset: The offset of the memory slice, as returned by other + * ioctls + * @items: Additional items to modify the behavior + * + * This struct is used with the KDBUS_CMD_FREE ioctl. + */ +struct kdbus_cmd_free { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 offset; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_hello_flags - flags for struct kdbus_cmd_hello + * @KDBUS_HELLO_ACCEPT_FD: The connection allows the reception of + * any passed file descriptors + * @KDBUS_HELLO_ACTIVATOR: Special-purpose connection which registers + * a well-know name for a process to be started + * when traffic arrives + * @KDBUS_HELLO_POLICY_HOLDER: Special-purpose connection which registers + * policy entries for a name. The provided name + * is not activated and not registered with the + * name database, it only allows unprivileged + * connections to acquire a name, talk or discover + * a service + * @KDBUS_HELLO_MONITOR: Special-purpose connection to monitor + * bus traffic + */ +enum kdbus_hello_flags { + KDBUS_HELLO_ACCEPT_FD = 1ULL << 0, + KDBUS_HELLO_ACTIVATOR = 1ULL << 1, + KDBUS_HELLO_POLICY_HOLDER = 1ULL << 2, + KDBUS_HELLO_MONITOR = 1ULL << 3, +}; + +/** + * struct kdbus_cmd_hello - struct to say hello to kdbus + * @size: The total size of the structure + * @flags: Connection flags (KDBUS_HELLO_*), userspace → kernel + * @return_flags: Command return flags, kernel → userspace + * @attach_flags_send: Mask of metadata to attach to each message sent + * off by this connection (KDBUS_ATTACH_*) + * @attach_flags_recv: Mask of metadata to attach to each message receieved + * by the new connection (KDBUS_ATTACH_*) + * @bus_flags: The flags field copied verbatim from the original + * KDBUS_CMD_BUS_MAKE ioctl. It's intended to be useful + * to do negotiation of features of the payload that is + * transferred (kernel → userspace) + * @id: The ID of this connection (kernel → userspace) + * @pool_size: Size of the connection's buffer where the received + * messages are placed + * @offset: Pool offset where items are returned to report + * additional information about the bus and the newly + * created connection. + * @items_size: Size of buffer returned in the pool slice at @offset. + * @id128: Unique 128-bit ID of the bus (kernel → userspace) + * @items: A list of items + * + * This struct is used with the KDBUS_CMD_HELLO ioctl. + */ +struct kdbus_cmd_hello { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 attach_flags_send; + __u64 attach_flags_recv; + __u64 bus_flags; + __u64 id; + __u64 pool_size; + __u64 offset; + __u64 items_size; + __u8 id128[16]; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_info - connection information + * @size: total size of the struct + * @id: 64bit object ID + * @flags: object creation flags + * @items: list of items + * + * Note that the user is responsible for freeing the allocated memory with + * the KDBUS_CMD_FREE ioctl. + */ +struct kdbus_info { + __u64 size; + __u64 id; + __u64 flags; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_list_flags - what to include into the returned list + * @KDBUS_LIST_UNIQUE: active connections + * @KDBUS_LIST_ACTIVATORS: activator connections + * @KDBUS_LIST_NAMES: known well-known names + * @KDBUS_LIST_QUEUED: queued-up names + */ +enum kdbus_list_flags { + KDBUS_LIST_UNIQUE = 1ULL << 0, + KDBUS_LIST_NAMES = 1ULL << 1, + KDBUS_LIST_ACTIVATORS = 1ULL << 2, + KDBUS_LIST_QUEUED = 1ULL << 3, +}; + +/** + * struct kdbus_cmd_list - list connections + * @size: overall size of this object + * @flags: flags for the query (KDBUS_LIST_*), userspace → kernel + * @return_flags: command return flags, kernel → userspace + * @offset: Offset in the caller's pool buffer where an array of + * kdbus_info objects is stored. + * The user must use KDBUS_CMD_FREE to free the + * allocated memory. + * @list_size: size of returned list in bytes + * @items: Items for the command. Reserved for future use. + * + * This structure is used with the KDBUS_CMD_LIST ioctl. + */ +struct kdbus_cmd_list { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 offset; + __u64 list_size; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_cmd_info - struct used for KDBUS_CMD_CONN_INFO ioctl + * @size: The total size of the struct + * @flags: Flags for this ioctl, userspace → kernel + * @return_flags: Command return flags, kernel → userspace + * @id: The 64-bit ID of the connection. If set to zero, passing + * @name is required. kdbus will look up the name to + * determine the ID in this case. + * @attach_flags: Set of attach flags to specify the set of information + * to receive, userspace → kernel + * @offset: Returned offset in the caller's pool buffer where the + * kdbus_info struct result is stored. The user must + * use KDBUS_CMD_FREE to free the allocated memory. + * @info_size: Output buffer to report size of data at @offset. + * @items: The optional item list, containing the + * well-known name to look up as a KDBUS_ITEM_NAME. + * Only needed in case @id is zero. + * + * On success, the KDBUS_CMD_CONN_INFO ioctl will return 0 and @offset will + * tell the user the offset in the connection pool buffer at which to find the + * result in a struct kdbus_info. + */ +struct kdbus_cmd_info { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 id; + __u64 attach_flags; + __u64 offset; + __u64 info_size; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_cmd_match_flags - flags to control the KDBUS_CMD_MATCH_ADD ioctl + * @KDBUS_MATCH_REPLACE: If entries with the supplied cookie already + * exists, remove them before installing the new + * matches. + */ +enum kdbus_cmd_match_flags { + KDBUS_MATCH_REPLACE = 1ULL << 0, +}; + +/** + * struct kdbus_cmd_match - struct to add or remove matches + * @size: The total size of the struct + * @flags: Flags for match command (KDBUS_MATCH_*), + * userspace → kernel + * @return_flags: Command return flags, kernel → userspace + * @cookie: Userspace supplied cookie. When removing, the cookie + * identifies the match to remove + * @items: A list of items for additional information + * + * This structure is used with the KDBUS_CMD_MATCH_ADD and + * KDBUS_CMD_MATCH_REMOVE ioctl. + */ +struct kdbus_cmd_match { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 cookie; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_make_flags - Flags for KDBUS_CMD_{BUS,ENDPOINT}_MAKE + * @KDBUS_MAKE_ACCESS_GROUP: Make the bus or endpoint node group-accessible + * @KDBUS_MAKE_ACCESS_WORLD: Make the bus or endpoint node world-accessible + */ +enum kdbus_make_flags { + KDBUS_MAKE_ACCESS_GROUP = 1ULL << 0, + KDBUS_MAKE_ACCESS_WORLD = 1ULL << 1, +}; + +/** + * enum kdbus_name_flags - flags for KDBUS_CMD_NAME_ACQUIRE + * @KDBUS_NAME_REPLACE_EXISTING: Try to replace name of other connections + * @KDBUS_NAME_ALLOW_REPLACEMENT: Allow the replacement of the name + * @KDBUS_NAME_QUEUE: Name should be queued if busy + * @KDBUS_NAME_IN_QUEUE: Name is queued + * @KDBUS_NAME_ACTIVATOR: Name is owned by a activator connection + */ +enum kdbus_name_flags { + KDBUS_NAME_REPLACE_EXISTING = 1ULL << 0, + KDBUS_NAME_ALLOW_REPLACEMENT = 1ULL << 1, + KDBUS_NAME_QUEUE = 1ULL << 2, + KDBUS_NAME_IN_QUEUE = 1ULL << 3, + KDBUS_NAME_ACTIVATOR = 1ULL << 4, +}; + +/** + * struct kdbus_cmd - generic ioctl payload + * @size: Overall size of this structure + * @flags: Flags for this ioctl, userspace → kernel + * @return_flags: Ioctl return flags, kernel → userspace + * @items: Additional items to modify the behavior + * + * This is a generic ioctl payload object. It's used by all ioctls that only + * take flags and items as input. + */ +struct kdbus_cmd { + __u64 size; + __u64 flags; + __u64 return_flags; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * Ioctl API + * + * KDBUS_CMD_BUS_MAKE: After opening the "control" node, this command + * creates a new bus with the specified + * name. The bus is immediately shut down and + * cleaned up when the opened file descriptor is + * closed. + * + * KDBUS_CMD_ENDPOINT_MAKE: Creates a new named special endpoint to talk to + * the bus. Such endpoints usually carry a more + * restrictive policy and grant restricted access + * to specific applications. + * KDBUS_CMD_ENDPOINT_UPDATE: Update the properties of a custom enpoint. Used + * to update the policy. + * + * KDBUS_CMD_HELLO: By opening the bus node, a connection is + * created. After a HELLO the opened connection + * becomes an active peer on the bus. + * KDBUS_CMD_UPDATE: Update the properties of a connection. Used to + * update the metadata subscription mask and + * policy. + * KDBUS_CMD_BYEBYE: Disconnect a connection. If there are no + * messages queued up in the connection's pool, + * the call succeeds, and the handle is rendered + * unusable. Otherwise, -EBUSY is returned without + * any further side-effects. + * KDBUS_CMD_FREE: Release the allocated memory in the receiver's + * pool. + * KDBUS_CMD_CONN_INFO: Retrieve credentials and properties of the + * initial creator of the connection. The data was + * stored at registration time and does not + * necessarily represent the connected process or + * the actual state of the process. + * KDBUS_CMD_BUS_CREATOR_INFO: Retrieve information of the creator of the bus + * a connection is attached to. + * + * KDBUS_CMD_SEND: Send a message and pass data from userspace to + * the kernel. + * KDBUS_CMD_RECV: Receive a message from the kernel which is + * placed in the receiver's pool. + * + * KDBUS_CMD_NAME_ACQUIRE: Request a well-known bus name to associate with + * the connection. Well-known names are used to + * address a peer on the bus. + * KDBUS_CMD_NAME_RELEASE: Release a well-known name the connection + * currently owns. + * KDBUS_CMD_LIST: Retrieve the list of all currently registered + * well-known and unique names. + * + * KDBUS_CMD_MATCH_ADD: Install a match which broadcast messages should + * be delivered to the connection. + * KDBUS_CMD_MATCH_REMOVE: Remove a current match for broadcast messages. + */ +enum kdbus_ioctl_type { + /* bus owner (00-0f) */ + KDBUS_CMD_BUS_MAKE = _IOW(KDBUS_IOCTL_MAGIC, 0x00, + struct kdbus_cmd), + + /* endpoint owner (10-1f) */ + KDBUS_CMD_ENDPOINT_MAKE = _IOW(KDBUS_IOCTL_MAGIC, 0x10, + struct kdbus_cmd), + KDBUS_CMD_ENDPOINT_UPDATE = _IOW(KDBUS_IOCTL_MAGIC, 0x11, + struct kdbus_cmd), + + /* connection owner (80-ff) */ + KDBUS_CMD_HELLO = _IOWR(KDBUS_IOCTL_MAGIC, 0x80, + struct kdbus_cmd_hello), + KDBUS_CMD_UPDATE = _IOW(KDBUS_IOCTL_MAGIC, 0x81, + struct kdbus_cmd), + KDBUS_CMD_BYEBYE = _IOW(KDBUS_IOCTL_MAGIC, 0x82, + struct kdbus_cmd), + KDBUS_CMD_FREE = _IOW(KDBUS_IOCTL_MAGIC, 0x83, + struct kdbus_cmd_free), + KDBUS_CMD_CONN_INFO = _IOR(KDBUS_IOCTL_MAGIC, 0x84, + struct kdbus_cmd_info), + KDBUS_CMD_BUS_CREATOR_INFO = _IOR(KDBUS_IOCTL_MAGIC, 0x85, + struct kdbus_cmd_info), + KDBUS_CMD_LIST = _IOR(KDBUS_IOCTL_MAGIC, 0x86, + struct kdbus_cmd_list), + + KDBUS_CMD_SEND = _IOW(KDBUS_IOCTL_MAGIC, 0x90, + struct kdbus_cmd_send), + KDBUS_CMD_RECV = _IOR(KDBUS_IOCTL_MAGIC, 0x91, + struct kdbus_cmd_recv), + + KDBUS_CMD_NAME_ACQUIRE = _IOW(KDBUS_IOCTL_MAGIC, 0xa0, + struct kdbus_cmd), + KDBUS_CMD_NAME_RELEASE = _IOW(KDBUS_IOCTL_MAGIC, 0xa1, + struct kdbus_cmd), + + KDBUS_CMD_MATCH_ADD = _IOW(KDBUS_IOCTL_MAGIC, 0xb0, + struct kdbus_cmd_match), + KDBUS_CMD_MATCH_REMOVE = _IOW(KDBUS_IOCTL_MAGIC, 0xb1, + struct kdbus_cmd_match), +}; + +#endif /* _UAPI_KDBUS_H_ */ diff --git a/src/libsystemd/libsystemd-internal/sd-bus/sd-bus.c b/src/libsystemd/libsystemd-internal/sd-bus/sd-bus.c new file mode 100644 index 0000000000..d3c194e135 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/sd-bus.c @@ -0,0 +1,3791 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-container.h" +#include "bus-control.h" +#include "bus-internal.h" +#include "bus-kernel.h" +#include "bus-label.h" +#include "bus-message.h" +#include "bus-objects.h" +#include "bus-protocol.h" +#include "bus-slot.h" +#include "bus-socket.h" +#include "bus-track.h" +#include "bus-type.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "def.h" +#include "fd-util.h" +#include "hexdecoct.h" +#include "hostname-util.h" +#include "macro.h" +#include "missing.h" +#include "parse-util.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +#define log_debug_bus_message(m) \ + do { \ + sd_bus_message *_mm = (m); \ + log_debug("Got message type=%s sender=%s destination=%s object=%s interface=%s member=%s cookie=%" PRIu64 " reply_cookie=%" PRIu64 " error=%s", \ + bus_message_type_to_string(_mm->header->type), \ + strna(sd_bus_message_get_sender(_mm)), \ + strna(sd_bus_message_get_destination(_mm)), \ + strna(sd_bus_message_get_path(_mm)), \ + strna(sd_bus_message_get_interface(_mm)), \ + strna(sd_bus_message_get_member(_mm)), \ + BUS_MESSAGE_COOKIE(_mm), \ + _mm->reply_cookie, \ + strna(_mm->error.message)); \ + } while (false) + +static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec); +static int attach_io_events(sd_bus *b); +static void detach_io_events(sd_bus *b); + +static thread_local sd_bus *default_system_bus = NULL; +static thread_local sd_bus *default_user_bus = NULL; +static thread_local sd_bus *default_starter_bus = NULL; + +static void bus_close_fds(sd_bus *b) { + assert(b); + + detach_io_events(b); + + if (b->input_fd != b->output_fd) + safe_close(b->output_fd); + b->output_fd = b->input_fd = safe_close(b->input_fd); +} + +static void bus_reset_queues(sd_bus *b) { + assert(b); + + while (b->rqueue_size > 0) + sd_bus_message_unref(b->rqueue[--b->rqueue_size]); + + b->rqueue = mfree(b->rqueue); + b->rqueue_allocated = 0; + + while (b->wqueue_size > 0) + sd_bus_message_unref(b->wqueue[--b->wqueue_size]); + + b->wqueue = mfree(b->wqueue); + b->wqueue_allocated = 0; +} + +static void bus_free(sd_bus *b) { + sd_bus_slot *s; + + assert(b); + assert(!b->track_queue); + + b->state = BUS_CLOSED; + + sd_bus_detach_event(b); + + while ((s = b->slots)) { + /* At this point only floating slots can still be + * around, because the non-floating ones keep a + * reference to the bus, and we thus couldn't be + * destructing right now... We forcibly disconnect the + * slots here, so that they still can be referenced by + * apps, but are dead. */ + + assert(s->floating); + bus_slot_disconnect(s); + sd_bus_slot_unref(s); + } + + if (b->default_bus_ptr) + *b->default_bus_ptr = NULL; + + bus_close_fds(b); + + if (b->kdbus_buffer) + munmap(b->kdbus_buffer, KDBUS_POOL_SIZE); + + free(b->label); + free(b->rbuffer); + free(b->unique_name); + free(b->auth_buffer); + free(b->address); + free(b->kernel); + free(b->machine); + free(b->fake_label); + free(b->cgroup_root); + free(b->description); + + free(b->exec_path); + strv_free(b->exec_argv); + + close_many(b->fds, b->n_fds); + free(b->fds); + + bus_reset_queues(b); + + ordered_hashmap_free_free(b->reply_callbacks); + prioq_free(b->reply_callbacks_prioq); + + assert(b->match_callbacks.type == BUS_MATCH_ROOT); + bus_match_free(&b->match_callbacks); + + hashmap_free_free(b->vtable_methods); + hashmap_free_free(b->vtable_properties); + + assert(hashmap_isempty(b->nodes)); + hashmap_free(b->nodes); + + bus_kernel_flush_memfd(b); + + assert_se(pthread_mutex_destroy(&b->memfd_cache_mutex) == 0); + + free(b); +} + +_public_ int sd_bus_new(sd_bus **ret) { + sd_bus *r; + + assert_return(ret, -EINVAL); + + r = new0(sd_bus, 1); + if (!r) + return -ENOMEM; + + r->n_ref = REFCNT_INIT; + r->input_fd = r->output_fd = -1; + r->message_version = 1; + r->creds_mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_UNIQUE_NAME; + r->hello_flags |= KDBUS_HELLO_ACCEPT_FD; + r->attach_flags |= KDBUS_ATTACH_NAMES; + r->original_pid = getpid(); + + assert_se(pthread_mutex_init(&r->memfd_cache_mutex, NULL) == 0); + + /* We guarantee that wqueue always has space for at least one + * entry */ + if (!GREEDY_REALLOC(r->wqueue, r->wqueue_allocated, 1)) { + free(r); + return -ENOMEM; + } + + *ret = r; + return 0; +} + +_public_ int sd_bus_set_address(sd_bus *bus, const char *address) { + char *a; + + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(address, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + a = strdup(address); + if (!a) + return -ENOMEM; + + free(bus->address); + bus->address = a; + + return 0; +} + +_public_ int sd_bus_set_fd(sd_bus *bus, int input_fd, int output_fd) { + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(input_fd >= 0, -EBADF); + assert_return(output_fd >= 0, -EBADF); + assert_return(!bus_pid_changed(bus), -ECHILD); + + bus->input_fd = input_fd; + bus->output_fd = output_fd; + return 0; +} + +_public_ int sd_bus_set_exec(sd_bus *bus, const char *path, char *const argv[]) { + char *p, **a; + + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(path, -EINVAL); + assert_return(!strv_isempty(argv), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + p = strdup(path); + if (!p) + return -ENOMEM; + + a = strv_copy(argv); + if (!a) { + free(p); + return -ENOMEM; + } + + free(bus->exec_path); + strv_free(bus->exec_argv); + + bus->exec_path = p; + bus->exec_argv = a; + + return 0; +} + +_public_ int sd_bus_set_bus_client(sd_bus *bus, int b) { + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + bus->bus_client = !!b; + return 0; +} + +_public_ int sd_bus_set_monitor(sd_bus *bus, int b) { + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + SET_FLAG(bus->hello_flags, KDBUS_HELLO_MONITOR, b); + return 0; +} + +_public_ int sd_bus_negotiate_fds(sd_bus *bus, int b) { + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + SET_FLAG(bus->hello_flags, KDBUS_HELLO_ACCEPT_FD, b); + return 0; +} + +_public_ int sd_bus_negotiate_timestamp(sd_bus *bus, int b) { + uint64_t new_flags; + assert_return(bus, -EINVAL); + assert_return(!IN_SET(bus->state, BUS_CLOSING, BUS_CLOSED), -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + new_flags = bus->attach_flags; + SET_FLAG(new_flags, KDBUS_ATTACH_TIMESTAMP, b); + + if (bus->attach_flags == new_flags) + return 0; + + bus->attach_flags = new_flags; + if (bus->state != BUS_UNSET && bus->is_kernel) + bus_kernel_realize_attach_flags(bus); + + return 0; +} + +_public_ int sd_bus_negotiate_creds(sd_bus *bus, int b, uint64_t mask) { + uint64_t new_flags; + + assert_return(bus, -EINVAL); + assert_return(mask <= _SD_BUS_CREDS_ALL, -EINVAL); + assert_return(!IN_SET(bus->state, BUS_CLOSING, BUS_CLOSED), -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + SET_FLAG(bus->creds_mask, mask, b); + + /* The well knowns we need unconditionally, so that matches can work */ + bus->creds_mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_UNIQUE_NAME; + + /* Make sure we don't lose the timestamp flag */ + new_flags = (bus->attach_flags & KDBUS_ATTACH_TIMESTAMP) | attach_flags_to_kdbus(bus->creds_mask); + if (bus->attach_flags == new_flags) + return 0; + + bus->attach_flags = new_flags; + if (bus->state != BUS_UNSET && bus->is_kernel) + bus_kernel_realize_attach_flags(bus); + + return 0; +} + +_public_ int sd_bus_set_server(sd_bus *bus, int b, sd_id128_t server_id) { + assert_return(bus, -EINVAL); + assert_return(b || sd_id128_equal(server_id, SD_ID128_NULL), -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + bus->is_server = !!b; + bus->server_id = server_id; + return 0; +} + +_public_ int sd_bus_set_anonymous(sd_bus *bus, int b) { + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + bus->anonymous_auth = !!b; + return 0; +} + +_public_ int sd_bus_set_trusted(sd_bus *bus, int b) { + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + bus->trusted = !!b; + return 0; +} + +_public_ int sd_bus_set_description(sd_bus *bus, const char *description) { + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + return free_and_strdup(&bus->description, description); +} + +_public_ int sd_bus_set_allow_interactive_authorization(sd_bus *bus, int b) { + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + bus->allow_interactive_authorization = !!b; + return 0; +} + +_public_ int sd_bus_get_allow_interactive_authorization(sd_bus *bus) { + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + return bus->allow_interactive_authorization; +} + +static int hello_callback(sd_bus_message *reply, void *userdata, sd_bus_error *error) { + const char *s; + sd_bus *bus; + int r; + + assert(reply); + bus = reply->bus; + assert(bus); + assert(bus->state == BUS_HELLO || bus->state == BUS_CLOSING); + + r = sd_bus_message_get_errno(reply); + if (r > 0) + return -r; + + r = sd_bus_message_read(reply, "s", &s); + if (r < 0) + return r; + + if (!service_name_is_valid(s) || s[0] != ':') + return -EBADMSG; + + bus->unique_name = strdup(s); + if (!bus->unique_name) + return -ENOMEM; + + if (bus->state == BUS_HELLO) + bus->state = BUS_RUNNING; + + return 1; +} + +static int bus_send_hello(sd_bus *bus) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert(bus); + + if (!bus->bus_client || bus->is_kernel) + return 0; + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "Hello"); + if (r < 0) + return r; + + return sd_bus_call_async(bus, NULL, m, hello_callback, NULL, 0); +} + +int bus_start_running(sd_bus *bus) { + assert(bus); + + if (bus->bus_client && !bus->is_kernel) { + bus->state = BUS_HELLO; + return 1; + } + + bus->state = BUS_RUNNING; + return 1; +} + +static int parse_address_key(const char **p, const char *key, char **value) { + size_t l, n = 0, allocated = 0; + const char *a; + char *r = NULL; + + assert(p); + assert(*p); + assert(value); + + if (key) { + l = strlen(key); + if (strncmp(*p, key, l) != 0) + return 0; + + if ((*p)[l] != '=') + return 0; + + if (*value) + return -EINVAL; + + a = *p + l + 1; + } else + a = *p; + + while (*a != ';' && *a != ',' && *a != 0) { + char c; + + if (*a == '%') { + int x, y; + + x = unhexchar(a[1]); + if (x < 0) { + free(r); + return x; + } + + y = unhexchar(a[2]); + if (y < 0) { + free(r); + return y; + } + + c = (char) ((x << 4) | y); + a += 3; + } else { + c = *a; + a++; + } + + if (!GREEDY_REALLOC(r, allocated, n + 2)) + return -ENOMEM; + + r[n++] = c; + } + + if (!r) { + r = strdup(""); + if (!r) + return -ENOMEM; + } else + r[n] = 0; + + if (*a == ',') + a++; + + *p = a; + + free(*value); + *value = r; + + return 1; +} + +static void skip_address_key(const char **p) { + assert(p); + assert(*p); + + *p += strcspn(*p, ","); + + if (**p == ',') + (*p)++; +} + +static int parse_unix_address(sd_bus *b, const char **p, char **guid) { + _cleanup_free_ char *path = NULL, *abstract = NULL; + size_t l; + int r; + + assert(b); + assert(p); + assert(*p); + assert(guid); + + while (**p != 0 && **p != ';') { + r = parse_address_key(p, "guid", guid); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "path", &path); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "abstract", &abstract); + if (r < 0) + return r; + else if (r > 0) + continue; + + skip_address_key(p); + } + + if (!path && !abstract) + return -EINVAL; + + if (path && abstract) + return -EINVAL; + + if (path) { + l = strlen(path); + if (l > sizeof(b->sockaddr.un.sun_path)) + return -E2BIG; + + b->sockaddr.un.sun_family = AF_UNIX; + strncpy(b->sockaddr.un.sun_path, path, sizeof(b->sockaddr.un.sun_path)); + b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + l; + } else if (abstract) { + l = strlen(abstract); + if (l > sizeof(b->sockaddr.un.sun_path) - 1) + return -E2BIG; + + b->sockaddr.un.sun_family = AF_UNIX; + b->sockaddr.un.sun_path[0] = 0; + strncpy(b->sockaddr.un.sun_path+1, abstract, sizeof(b->sockaddr.un.sun_path)-1); + b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + 1 + l; + } + + return 0; +} + +static int parse_tcp_address(sd_bus *b, const char **p, char **guid) { + _cleanup_free_ char *host = NULL, *port = NULL, *family = NULL; + int r; + struct addrinfo *result, hints = { + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_ADDRCONFIG, + }; + + assert(b); + assert(p); + assert(*p); + assert(guid); + + while (**p != 0 && **p != ';') { + r = parse_address_key(p, "guid", guid); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "host", &host); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "port", &port); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "family", &family); + if (r < 0) + return r; + else if (r > 0) + continue; + + skip_address_key(p); + } + + if (!host || !port) + return -EINVAL; + + if (family) { + if (streq(family, "ipv4")) + hints.ai_family = AF_INET; + else if (streq(family, "ipv6")) + hints.ai_family = AF_INET6; + else + return -EINVAL; + } + + r = getaddrinfo(host, port, &hints, &result); + if (r == EAI_SYSTEM) + return -errno; + else if (r != 0) + return -EADDRNOTAVAIL; + + memcpy(&b->sockaddr, result->ai_addr, result->ai_addrlen); + b->sockaddr_size = result->ai_addrlen; + + freeaddrinfo(result); + + return 0; +} + +static int parse_exec_address(sd_bus *b, const char **p, char **guid) { + char *path = NULL; + unsigned n_argv = 0, j; + char **argv = NULL; + size_t allocated = 0; + int r; + + assert(b); + assert(p); + assert(*p); + assert(guid); + + while (**p != 0 && **p != ';') { + r = parse_address_key(p, "guid", guid); + if (r < 0) + goto fail; + else if (r > 0) + continue; + + r = parse_address_key(p, "path", &path); + if (r < 0) + goto fail; + else if (r > 0) + continue; + + if (startswith(*p, "argv")) { + unsigned ul; + + errno = 0; + ul = strtoul(*p + 4, (char**) p, 10); + if (errno > 0 || **p != '=' || ul > 256) { + r = -EINVAL; + goto fail; + } + + (*p)++; + + if (ul >= n_argv) { + if (!GREEDY_REALLOC0(argv, allocated, ul + 2)) { + r = -ENOMEM; + goto fail; + } + + n_argv = ul + 1; + } + + r = parse_address_key(p, NULL, argv + ul); + if (r < 0) + goto fail; + + continue; + } + + skip_address_key(p); + } + + if (!path) { + r = -EINVAL; + goto fail; + } + + /* Make sure there are no holes in the array, with the + * exception of argv[0] */ + for (j = 1; j < n_argv; j++) + if (!argv[j]) { + r = -EINVAL; + goto fail; + } + + if (argv && argv[0] == NULL) { + argv[0] = strdup(path); + if (!argv[0]) { + r = -ENOMEM; + goto fail; + } + } + + b->exec_path = path; + b->exec_argv = argv; + return 0; + +fail: + for (j = 0; j < n_argv; j++) + free(argv[j]); + + free(argv); + free(path); + return r; +} + +static int parse_kernel_address(sd_bus *b, const char **p, char **guid) { + _cleanup_free_ char *path = NULL; + int r; + + assert(b); + assert(p); + assert(*p); + assert(guid); + + while (**p != 0 && **p != ';') { + r = parse_address_key(p, "guid", guid); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "path", &path); + if (r < 0) + return r; + else if (r > 0) + continue; + + skip_address_key(p); + } + + if (!path) + return -EINVAL; + + free(b->kernel); + b->kernel = path; + path = NULL; + + return 0; +} + +static int parse_container_unix_address(sd_bus *b, const char **p, char **guid) { + _cleanup_free_ char *machine = NULL, *pid = NULL; + int r; + + assert(b); + assert(p); + assert(*p); + assert(guid); + + while (**p != 0 && **p != ';') { + r = parse_address_key(p, "guid", guid); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "machine", &machine); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "pid", &pid); + if (r < 0) + return r; + else if (r > 0) + continue; + + skip_address_key(p); + } + + if (!machine == !pid) + return -EINVAL; + + if (machine) { + if (!machine_name_is_valid(machine)) + return -EINVAL; + + free(b->machine); + b->machine = machine; + machine = NULL; + } else { + b->machine = mfree(b->machine); + } + + if (pid) { + r = parse_pid(pid, &b->nspid); + if (r < 0) + return r; + } else + b->nspid = 0; + + b->sockaddr.un.sun_family = AF_UNIX; + strncpy(b->sockaddr.un.sun_path, "/var/run/dbus/system_bus_socket", sizeof(b->sockaddr.un.sun_path)); + b->sockaddr_size = SOCKADDR_UN_LEN(b->sockaddr.un); + + return 0; +} + +static int parse_container_kernel_address(sd_bus *b, const char **p, char **guid) { + _cleanup_free_ char *machine = NULL, *pid = NULL; + int r; + + assert(b); + assert(p); + assert(*p); + assert(guid); + + while (**p != 0 && **p != ';') { + r = parse_address_key(p, "guid", guid); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "machine", &machine); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "pid", &pid); + if (r < 0) + return r; + else if (r > 0) + continue; + + skip_address_key(p); + } + + if (!machine == !pid) + return -EINVAL; + + if (machine) { + if (!machine_name_is_valid(machine)) + return -EINVAL; + + free(b->machine); + b->machine = machine; + machine = NULL; + } else { + b->machine = mfree(b->machine); + } + + if (pid) { + r = parse_pid(pid, &b->nspid); + if (r < 0) + return r; + } else + b->nspid = 0; + + r = free_and_strdup(&b->kernel, "/sys/fs/kdbus/0-system/bus"); + if (r < 0) + return r; + + return 0; +} + +static void bus_reset_parsed_address(sd_bus *b) { + assert(b); + + zero(b->sockaddr); + b->sockaddr_size = 0; + b->exec_argv = strv_free(b->exec_argv); + b->exec_path = mfree(b->exec_path); + b->server_id = SD_ID128_NULL; + b->kernel = mfree(b->kernel); + b->machine = mfree(b->machine); + b->nspid = 0; +} + +static int bus_parse_next_address(sd_bus *b) { + _cleanup_free_ char *guid = NULL; + const char *a; + int r; + + assert(b); + + if (!b->address) + return 0; + if (b->address[b->address_index] == 0) + return 0; + + bus_reset_parsed_address(b); + + a = b->address + b->address_index; + + while (*a != 0) { + + if (*a == ';') { + a++; + continue; + } + + if (startswith(a, "unix:")) { + a += 5; + + r = parse_unix_address(b, &a, &guid); + if (r < 0) + return r; + break; + + } else if (startswith(a, "tcp:")) { + + a += 4; + r = parse_tcp_address(b, &a, &guid); + if (r < 0) + return r; + + break; + + } else if (startswith(a, "unixexec:")) { + + a += 9; + r = parse_exec_address(b, &a, &guid); + if (r < 0) + return r; + + break; + + } else if (startswith(a, "kernel:")) { + + a += 7; + r = parse_kernel_address(b, &a, &guid); + if (r < 0) + return r; + + break; + } else if (startswith(a, "x-machine-unix:")) { + + a += 15; + r = parse_container_unix_address(b, &a, &guid); + if (r < 0) + return r; + + break; + } else if (startswith(a, "x-machine-kernel:")) { + + a += 17; + r = parse_container_kernel_address(b, &a, &guid); + if (r < 0) + return r; + + break; + } + + a = strchr(a, ';'); + if (!a) + return 0; + } + + if (guid) { + r = sd_id128_from_string(guid, &b->server_id); + if (r < 0) + return r; + } + + b->address_index = a - b->address; + return 1; +} + +static int bus_start_address(sd_bus *b) { + bool container_kdbus_available = false; + bool kdbus_available = false; + int r; + + assert(b); + + for (;;) { + bool skipped = false; + + bus_close_fds(b); + + /* + * Usually, if you provide multiple different bus-addresses, we + * try all of them in order. We use the first one that + * succeeds. However, if you mix kernel and unix addresses, we + * never try unix-addresses if a previous kernel address was + * tried and kdbus was available. This is required to prevent + * clients to fallback to the bus-proxy if kdbus is available + * but failed (eg., too many connections). + */ + + if (b->exec_path) + r = bus_socket_exec(b); + else if ((b->nspid > 0 || b->machine) && b->kernel) { + r = bus_container_connect_kernel(b); + if (r < 0 && !IN_SET(r, -ENOENT, -ESOCKTNOSUPPORT)) + container_kdbus_available = true; + + } else if ((b->nspid > 0 || b->machine) && b->sockaddr.sa.sa_family != AF_UNSPEC) { + if (!container_kdbus_available) + r = bus_container_connect_socket(b); + else + skipped = true; + + } else if (b->kernel) { + r = bus_kernel_connect(b); + if (r < 0 && !IN_SET(r, -ENOENT, -ESOCKTNOSUPPORT)) + kdbus_available = true; + + } else if (b->sockaddr.sa.sa_family != AF_UNSPEC) { + if (!kdbus_available) + r = bus_socket_connect(b); + else + skipped = true; + } else + skipped = true; + + if (!skipped) { + if (r >= 0) { + r = attach_io_events(b); + if (r >= 0) + return r; + } + + b->last_connect_error = -r; + } + + r = bus_parse_next_address(b); + if (r < 0) + return r; + if (r == 0) + return b->last_connect_error ? -b->last_connect_error : -ECONNREFUSED; + } +} + +int bus_next_address(sd_bus *b) { + assert(b); + + bus_reset_parsed_address(b); + return bus_start_address(b); +} + +static int bus_start_fd(sd_bus *b) { + struct stat st; + int r; + + assert(b); + assert(b->input_fd >= 0); + assert(b->output_fd >= 0); + + r = fd_nonblock(b->input_fd, true); + if (r < 0) + return r; + + r = fd_cloexec(b->input_fd, true); + if (r < 0) + return r; + + if (b->input_fd != b->output_fd) { + r = fd_nonblock(b->output_fd, true); + if (r < 0) + return r; + + r = fd_cloexec(b->output_fd, true); + if (r < 0) + return r; + } + + if (fstat(b->input_fd, &st) < 0) + return -errno; + + if (S_ISCHR(b->input_fd)) + return bus_kernel_take_fd(b); + else + return bus_socket_take_fd(b); +} + +_public_ int sd_bus_start(sd_bus *bus) { + int r; + + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + bus->state = BUS_OPENING; + + if (bus->is_server && bus->bus_client) + return -EINVAL; + + if (bus->input_fd >= 0) + r = bus_start_fd(bus); + else if (bus->address || bus->sockaddr.sa.sa_family != AF_UNSPEC || bus->exec_path || bus->kernel || bus->machine) + r = bus_start_address(bus); + else + return -EINVAL; + + if (r < 0) { + sd_bus_close(bus); + return r; + } + + return bus_send_hello(bus); +} + +_public_ int sd_bus_open(sd_bus **ret) { + const char *e; + sd_bus *b; + int r; + + assert_return(ret, -EINVAL); + + /* Let's connect to the starter bus if it is set, and + * otherwise to the bus that is appropropriate for the scope + * we are running in */ + + e = secure_getenv("DBUS_STARTER_BUS_TYPE"); + if (e) { + if (streq(e, "system")) + return sd_bus_open_system(ret); + else if (STR_IN_SET(e, "session", "user")) + return sd_bus_open_user(ret); + } + + e = secure_getenv("DBUS_STARTER_ADDRESS"); + if (!e) { + if (cg_pid_get_owner_uid(0, NULL) >= 0) + return sd_bus_open_user(ret); + else + return sd_bus_open_system(ret); + } + + r = sd_bus_new(&b); + if (r < 0) + return r; + + r = sd_bus_set_address(b, e); + if (r < 0) + goto fail; + + b->bus_client = true; + + /* We don't know whether the bus is trusted or not, so better + * be safe, and authenticate everything */ + b->trusted = false; + b->attach_flags |= KDBUS_ATTACH_CAPS | KDBUS_ATTACH_CREDS; + b->creds_mask |= SD_BUS_CREDS_UID | SD_BUS_CREDS_EUID | SD_BUS_CREDS_EFFECTIVE_CAPS; + + r = sd_bus_start(b); + if (r < 0) + goto fail; + + *ret = b; + return 0; + +fail: + bus_free(b); + return r; +} + +int bus_set_address_system(sd_bus *b) { + const char *e; + assert(b); + + e = secure_getenv("DBUS_SYSTEM_BUS_ADDRESS"); + if (e) + return sd_bus_set_address(b, e); + + return sd_bus_set_address(b, DEFAULT_SYSTEM_BUS_ADDRESS); +} + +_public_ int sd_bus_open_system(sd_bus **ret) { + sd_bus *b; + int r; + + assert_return(ret, -EINVAL); + + r = sd_bus_new(&b); + if (r < 0) + return r; + + r = bus_set_address_system(b); + if (r < 0) + goto fail; + + b->bus_client = true; + b->is_system = true; + + /* Let's do per-method access control on the system bus. We + * need the caller's UID and capability set for that. */ + b->trusted = false; + b->attach_flags |= KDBUS_ATTACH_CAPS | KDBUS_ATTACH_CREDS; + b->creds_mask |= SD_BUS_CREDS_UID | SD_BUS_CREDS_EUID | SD_BUS_CREDS_EFFECTIVE_CAPS; + + r = sd_bus_start(b); + if (r < 0) + goto fail; + + *ret = b; + return 0; + +fail: + bus_free(b); + return r; +} + +int bus_set_address_user(sd_bus *b) { + const char *e; + uid_t uid; + int r; + + assert(b); + + e = secure_getenv("DBUS_SESSION_BUS_ADDRESS"); + if (e) + return sd_bus_set_address(b, e); + + r = cg_pid_get_owner_uid(0, &uid); + if (r < 0) + uid = getuid(); + + e = secure_getenv("XDG_RUNTIME_DIR"); + if (e) { + _cleanup_free_ char *ee = NULL; + + ee = bus_address_escape(e); + if (!ee) + return -ENOMEM; + + (void) asprintf(&b->address, KERNEL_USER_BUS_ADDRESS_FMT ";" UNIX_USER_BUS_ADDRESS_FMT, uid, ee); + } else + (void) asprintf(&b->address, KERNEL_USER_BUS_ADDRESS_FMT, uid); + + if (!b->address) + return -ENOMEM; + + return 0; +} + +_public_ int sd_bus_open_user(sd_bus **ret) { + sd_bus *b; + int r; + + assert_return(ret, -EINVAL); + + r = sd_bus_new(&b); + if (r < 0) + return r; + + r = bus_set_address_user(b); + if (r < 0) + return r; + + b->bus_client = true; + b->is_user = true; + + /* We don't do any per-method access control on the user + * bus. */ + b->trusted = true; + + r = sd_bus_start(b); + if (r < 0) + goto fail; + + *ret = b; + return 0; + +fail: + bus_free(b); + return r; +} + +int bus_set_address_system_remote(sd_bus *b, const char *host) { + _cleanup_free_ char *e = NULL; + char *m = NULL, *c = NULL; + + assert(b); + assert(host); + + /* Let's see if we shall enter some container */ + m = strchr(host, ':'); + if (m) { + m++; + + /* Let's make sure this is not a port of some kind, + * and is a valid machine name. */ + if (!in_charset(m, "0123456789") && machine_name_is_valid(m)) { + char *t; + + /* Cut out the host part */ + t = strndupa(host, m - host - 1); + e = bus_address_escape(t); + if (!e) + return -ENOMEM; + + c = strjoina(",argv4=--machine=", m); + } + } + + if (!e) { + e = bus_address_escape(host); + if (!e) + return -ENOMEM; + } + + b->address = strjoin("unixexec:path=ssh,argv1=-xT,argv2=", e, ",argv3=systemd-stdio-bridge", c, NULL); + if (!b->address) + return -ENOMEM; + + return 0; + } + +_public_ int sd_bus_open_system_remote(sd_bus **ret, const char *host) { + sd_bus *bus; + int r; + + assert_return(host, -EINVAL); + assert_return(ret, -EINVAL); + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + r = bus_set_address_system_remote(bus, host); + if (r < 0) + goto fail; + + bus->bus_client = true; + bus->trusted = false; + bus->is_system = true; + + r = sd_bus_start(bus); + if (r < 0) + goto fail; + + *ret = bus; + return 0; + +fail: + bus_free(bus); + return r; +} + +int bus_set_address_system_machine(sd_bus *b, const char *machine) { + _cleanup_free_ char *e = NULL; + + assert(b); + assert(machine); + + e = bus_address_escape(machine); + if (!e) + return -ENOMEM; + + b->address = strjoin("x-machine-kernel:machine=", e, ";x-machine-unix:machine=", e, NULL); + if (!b->address) + return -ENOMEM; + + return 0; +} + +_public_ int sd_bus_open_system_machine(sd_bus **ret, const char *machine) { + sd_bus *bus; + int r; + + assert_return(machine, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(machine_name_is_valid(machine), -EINVAL); + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + r = bus_set_address_system_machine(bus, machine); + if (r < 0) + goto fail; + + bus->bus_client = true; + bus->trusted = false; + bus->is_system = true; + + r = sd_bus_start(bus); + if (r < 0) + goto fail; + + *ret = bus; + return 0; + +fail: + bus_free(bus); + return r; +} + +_public_ void sd_bus_close(sd_bus *bus) { + + if (!bus) + return; + if (bus->state == BUS_CLOSED) + return; + if (bus_pid_changed(bus)) + return; + + bus->state = BUS_CLOSED; + + sd_bus_detach_event(bus); + + /* Drop all queued messages so that they drop references to + * the bus object and the bus may be freed */ + bus_reset_queues(bus); + + if (!bus->is_kernel) + bus_close_fds(bus); + + /* We'll leave the fd open in case this is a kernel bus, since + * there might still be memblocks around that reference this + * bus, and they might need to invoke the KDBUS_CMD_FREE + * ioctl on the fd when they are freed. */ +} + +_public_ sd_bus* sd_bus_flush_close_unref(sd_bus *bus) { + + if (!bus) + return NULL; + + sd_bus_flush(bus); + sd_bus_close(bus); + + return sd_bus_unref(bus); +} + +static void bus_enter_closing(sd_bus *bus) { + assert(bus); + + if (bus->state != BUS_OPENING && + bus->state != BUS_AUTHENTICATING && + bus->state != BUS_HELLO && + bus->state != BUS_RUNNING) + return; + + bus->state = BUS_CLOSING; +} + +_public_ sd_bus *sd_bus_ref(sd_bus *bus) { + + if (!bus) + return NULL; + + assert_se(REFCNT_INC(bus->n_ref) >= 2); + + return bus; +} + +_public_ sd_bus *sd_bus_unref(sd_bus *bus) { + unsigned i; + + if (!bus) + return NULL; + + i = REFCNT_DEC(bus->n_ref); + if (i > 0) + return NULL; + + bus_free(bus); + return NULL; +} + +_public_ int sd_bus_is_open(sd_bus *bus) { + + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + return BUS_IS_OPEN(bus->state); +} + +_public_ int sd_bus_can_send(sd_bus *bus, char type) { + int r; + + assert_return(bus, -EINVAL); + assert_return(bus->state != BUS_UNSET, -ENOTCONN); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (bus->hello_flags & KDBUS_HELLO_MONITOR) + return 0; + + if (type == SD_BUS_TYPE_UNIX_FD) { + if (!(bus->hello_flags & KDBUS_HELLO_ACCEPT_FD)) + return 0; + + r = bus_ensure_running(bus); + if (r < 0) + return r; + + return bus->can_fds; + } + + return bus_type_is_valid(type); +} + +_public_ int sd_bus_get_bus_id(sd_bus *bus, sd_id128_t *id) { + int r; + + assert_return(bus, -EINVAL); + assert_return(id, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + r = bus_ensure_running(bus); + if (r < 0) + return r; + + *id = bus->server_id; + return 0; +} + +static int bus_seal_message(sd_bus *b, sd_bus_message *m, usec_t timeout) { + assert(b); + assert(m); + + if (m->sealed) { + /* If we copy the same message to multiple + * destinations, avoid using the same cookie + * numbers. */ + b->cookie = MAX(b->cookie, BUS_MESSAGE_COOKIE(m)); + return 0; + } + + if (timeout == 0) + timeout = BUS_DEFAULT_TIMEOUT; + + return bus_message_seal(m, ++b->cookie, timeout); +} + +static int bus_remarshal_message(sd_bus *b, sd_bus_message **m) { + bool remarshal = false; + + assert(b); + + /* wrong packet version */ + if (b->message_version != 0 && b->message_version != (*m)->header->version) + remarshal = true; + + /* wrong packet endianness */ + if (b->message_endian != 0 && b->message_endian != (*m)->header->endian) + remarshal = true; + + /* TODO: kdbus-messages received from the kernel contain data which is + * not allowed to be passed to KDBUS_CMD_SEND. Therefore, we have to + * force remarshaling of the message. Technically, we could just + * recreate the kdbus message, but that is non-trivial as other parts of + * the message refer to m->kdbus already. This should be fixed! */ + if ((*m)->kdbus && (*m)->release_kdbus) + remarshal = true; + + return remarshal ? bus_message_remarshal(b, m) : 0; +} + +int bus_seal_synthetic_message(sd_bus *b, sd_bus_message *m) { + assert(b); + assert(m); + + /* Fake some timestamps, if they were requested, and not + * already initialized */ + if (b->attach_flags & KDBUS_ATTACH_TIMESTAMP) { + if (m->realtime <= 0) + m->realtime = now(CLOCK_REALTIME); + + if (m->monotonic <= 0) + m->monotonic = now(CLOCK_MONOTONIC); + } + + /* The bus specification says the serial number cannot be 0, + * hence let's fill something in for synthetic messages. Since + * synthetic messages might have a fake sender and we don't + * want to interfere with the real sender's serial numbers we + * pick a fixed, artificial one. We use (uint32_t) -1 rather + * than (uint64_t) -1 since dbus1 only had 32bit identifiers, + * even though kdbus can do 64bit. */ + return bus_message_seal(m, 0xFFFFFFFFULL, 0); +} + +static int bus_write_message(sd_bus *bus, sd_bus_message *m, bool hint_sync_call, size_t *idx) { + int r; + + assert(bus); + assert(m); + + if (bus->is_kernel) + r = bus_kernel_write_message(bus, m, hint_sync_call); + else + r = bus_socket_write_message(bus, m, idx); + + if (r <= 0) + return r; + + if (bus->is_kernel || *idx >= BUS_MESSAGE_SIZE(m)) + log_debug("Sent message type=%s sender=%s destination=%s object=%s interface=%s member=%s cookie=%" PRIu64 " reply_cookie=%" PRIu64 " error=%s", + bus_message_type_to_string(m->header->type), + strna(sd_bus_message_get_sender(m)), + strna(sd_bus_message_get_destination(m)), + strna(sd_bus_message_get_path(m)), + strna(sd_bus_message_get_interface(m)), + strna(sd_bus_message_get_member(m)), + BUS_MESSAGE_COOKIE(m), + m->reply_cookie, + strna(m->error.message)); + + return r; +} + +static int dispatch_wqueue(sd_bus *bus) { + int r, ret = 0; + + assert(bus); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + while (bus->wqueue_size > 0) { + + r = bus_write_message(bus, bus->wqueue[0], false, &bus->windex); + if (r < 0) + return r; + else if (r == 0) + /* Didn't do anything this time */ + return ret; + else if (bus->is_kernel || bus->windex >= BUS_MESSAGE_SIZE(bus->wqueue[0])) { + /* Fully written. Let's drop the entry from + * the queue. + * + * This isn't particularly optimized, but + * well, this is supposed to be our worst-case + * buffer only, and the socket buffer is + * supposed to be our primary buffer, and if + * it got full, then all bets are off + * anyway. */ + + bus->wqueue_size--; + sd_bus_message_unref(bus->wqueue[0]); + memmove(bus->wqueue, bus->wqueue + 1, sizeof(sd_bus_message*) * bus->wqueue_size); + bus->windex = 0; + + ret = 1; + } + } + + return ret; +} + +static int bus_read_message(sd_bus *bus, bool hint_priority, int64_t priority) { + assert(bus); + + if (bus->is_kernel) + return bus_kernel_read_message(bus, hint_priority, priority); + else + return bus_socket_read_message(bus); +} + +int bus_rqueue_make_room(sd_bus *bus) { + assert(bus); + + if (bus->rqueue_size >= BUS_RQUEUE_MAX) + return -ENOBUFS; + + if (!GREEDY_REALLOC(bus->rqueue, bus->rqueue_allocated, bus->rqueue_size + 1)) + return -ENOMEM; + + return 0; +} + +static int dispatch_rqueue(sd_bus *bus, bool hint_priority, int64_t priority, sd_bus_message **m) { + int r, ret = 0; + + assert(bus); + assert(m); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + /* Note that the priority logic is only available on kdbus, + * where the rqueue is unused. We check the rqueue here + * anyway, because it's simple... */ + + for (;;) { + if (bus->rqueue_size > 0) { + /* Dispatch a queued message */ + + *m = bus->rqueue[0]; + bus->rqueue_size--; + memmove(bus->rqueue, bus->rqueue + 1, sizeof(sd_bus_message*) * bus->rqueue_size); + return 1; + } + + /* Try to read a new message */ + r = bus_read_message(bus, hint_priority, priority); + if (r < 0) + return r; + if (r == 0) + return ret; + + ret = 1; + } +} + +static int bus_send_internal(sd_bus *bus, sd_bus_message *_m, uint64_t *cookie, bool hint_sync_call) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m); + int r; + + assert_return(m, -EINVAL); + + if (!bus) + bus = m->bus; + + assert_return(!bus_pid_changed(bus), -ECHILD); + assert_return(!bus->is_kernel || !(bus->hello_flags & KDBUS_HELLO_MONITOR), -EROFS); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (m->n_fds > 0) { + r = sd_bus_can_send(bus, SD_BUS_TYPE_UNIX_FD); + if (r < 0) + return r; + if (r == 0) + return -EOPNOTSUPP; + } + + /* If the cookie number isn't kept, then we know that no reply + * is expected */ + if (!cookie && !m->sealed) + m->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED; + + r = bus_seal_message(bus, m, 0); + if (r < 0) + return r; + + /* Remarshall if we have to. This will possibly unref the + * message and place a replacement in m */ + r = bus_remarshal_message(bus, &m); + if (r < 0) + return r; + + /* If this is a reply and no reply was requested, then let's + * suppress this, if we can */ + if (m->dont_send) + goto finish; + + if ((bus->state == BUS_RUNNING || bus->state == BUS_HELLO) && bus->wqueue_size <= 0) { + size_t idx = 0; + + r = bus_write_message(bus, m, hint_sync_call, &idx); + if (r < 0) { + if (IN_SET(r, -ENOTCONN, -ECONNRESET, -EPIPE, -ESHUTDOWN)) { + bus_enter_closing(bus); + return -ECONNRESET; + } + + return r; + } + + if (!bus->is_kernel && idx < BUS_MESSAGE_SIZE(m)) { + /* Wasn't fully written. So let's remember how + * much was written. Note that the first entry + * of the wqueue array is always allocated so + * that we always can remember how much was + * written. */ + bus->wqueue[0] = sd_bus_message_ref(m); + bus->wqueue_size = 1; + bus->windex = idx; + } + + } else { + /* Just append it to the queue. */ + + if (bus->wqueue_size >= BUS_WQUEUE_MAX) + return -ENOBUFS; + + if (!GREEDY_REALLOC(bus->wqueue, bus->wqueue_allocated, bus->wqueue_size + 1)) + return -ENOMEM; + + bus->wqueue[bus->wqueue_size++] = sd_bus_message_ref(m); + } + +finish: + if (cookie) + *cookie = BUS_MESSAGE_COOKIE(m); + + return 1; +} + +_public_ int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie) { + return bus_send_internal(bus, m, cookie, false); +} + +_public_ int sd_bus_send_to(sd_bus *bus, sd_bus_message *m, const char *destination, uint64_t *cookie) { + int r; + + assert_return(m, -EINVAL); + + if (!bus) + bus = m->bus; + + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (!streq_ptr(m->destination, destination)) { + + if (!destination) + return -EEXIST; + + r = sd_bus_message_set_destination(m, destination); + if (r < 0) + return r; + } + + return sd_bus_send(bus, m, cookie); +} + +static usec_t calc_elapse(uint64_t usec) { + if (usec == (uint64_t) -1) + return 0; + + return now(CLOCK_MONOTONIC) + usec; +} + +static int timeout_compare(const void *a, const void *b) { + const struct reply_callback *x = a, *y = b; + + if (x->timeout != 0 && y->timeout == 0) + return -1; + + if (x->timeout == 0 && y->timeout != 0) + return 1; + + if (x->timeout < y->timeout) + return -1; + + if (x->timeout > y->timeout) + return 1; + + return 0; +} + +_public_ int sd_bus_call_async( + sd_bus *bus, + sd_bus_slot **slot, + sd_bus_message *_m, + sd_bus_message_handler_t callback, + void *userdata, + uint64_t usec) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m); + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *s = NULL; + int r; + + assert_return(m, -EINVAL); + assert_return(m->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); + assert_return(!(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED), -EINVAL); + assert_return(callback, -EINVAL); + + if (!bus) + bus = m->bus; + + assert_return(!bus_pid_changed(bus), -ECHILD); + assert_return(!bus->is_kernel || !(bus->hello_flags & KDBUS_HELLO_MONITOR), -EROFS); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + r = ordered_hashmap_ensure_allocated(&bus->reply_callbacks, &uint64_hash_ops); + if (r < 0) + return r; + + r = prioq_ensure_allocated(&bus->reply_callbacks_prioq, timeout_compare); + if (r < 0) + return r; + + r = bus_seal_message(bus, m, usec); + if (r < 0) + return r; + + r = bus_remarshal_message(bus, &m); + if (r < 0) + return r; + + s = bus_slot_allocate(bus, !slot, BUS_REPLY_CALLBACK, sizeof(struct reply_callback), userdata); + if (!s) + return -ENOMEM; + + s->reply_callback.callback = callback; + + s->reply_callback.cookie = BUS_MESSAGE_COOKIE(m); + r = ordered_hashmap_put(bus->reply_callbacks, &s->reply_callback.cookie, &s->reply_callback); + if (r < 0) { + s->reply_callback.cookie = 0; + return r; + } + + s->reply_callback.timeout = calc_elapse(m->timeout); + if (s->reply_callback.timeout != 0) { + r = prioq_put(bus->reply_callbacks_prioq, &s->reply_callback, &s->reply_callback.prioq_idx); + if (r < 0) { + s->reply_callback.timeout = 0; + return r; + } + } + + r = sd_bus_send(bus, m, &s->reply_callback.cookie); + if (r < 0) + return r; + + if (slot) + *slot = s; + s = NULL; + + return r; +} + +int bus_ensure_running(sd_bus *bus) { + int r; + + assert(bus); + + if (bus->state == BUS_UNSET || bus->state == BUS_CLOSED || bus->state == BUS_CLOSING) + return -ENOTCONN; + if (bus->state == BUS_RUNNING) + return 1; + + for (;;) { + r = sd_bus_process(bus, NULL); + if (r < 0) + return r; + if (bus->state == BUS_RUNNING) + return 1; + if (r > 0) + continue; + + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) + return r; + } +} + +_public_ int sd_bus_call( + sd_bus *bus, + sd_bus_message *_m, + uint64_t usec, + sd_bus_error *error, + sd_bus_message **reply) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m); + usec_t timeout; + uint64_t cookie; + unsigned i; + int r; + + bus_assert_return(m, -EINVAL, error); + bus_assert_return(m->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL, error); + bus_assert_return(!(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED), -EINVAL, error); + bus_assert_return(!bus_error_is_dirty(error), -EINVAL, error); + + if (!bus) + bus = m->bus; + + bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); + bus_assert_return(!bus->is_kernel || !(bus->hello_flags & KDBUS_HELLO_MONITOR), -EROFS, error); + + if (!BUS_IS_OPEN(bus->state)) { + r = -ENOTCONN; + goto fail; + } + + r = bus_ensure_running(bus); + if (r < 0) + goto fail; + + i = bus->rqueue_size; + + r = bus_seal_message(bus, m, usec); + if (r < 0) + goto fail; + + r = bus_remarshal_message(bus, &m); + if (r < 0) + goto fail; + + r = bus_send_internal(bus, m, &cookie, true); + if (r < 0) + goto fail; + + timeout = calc_elapse(m->timeout); + + for (;;) { + usec_t left; + + while (i < bus->rqueue_size) { + sd_bus_message *incoming = NULL; + + incoming = bus->rqueue[i]; + + if (incoming->reply_cookie == cookie) { + /* Found a match! */ + + memmove(bus->rqueue + i, bus->rqueue + i + 1, sizeof(sd_bus_message*) * (bus->rqueue_size - i - 1)); + bus->rqueue_size--; + log_debug_bus_message(incoming); + + if (incoming->header->type == SD_BUS_MESSAGE_METHOD_RETURN) { + + if (incoming->n_fds <= 0 || (bus->hello_flags & KDBUS_HELLO_ACCEPT_FD)) { + if (reply) + *reply = incoming; + else + sd_bus_message_unref(incoming); + + return 1; + } + + r = sd_bus_error_setf(error, SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Reply message contained file descriptors which I couldn't accept. Sorry."); + sd_bus_message_unref(incoming); + return r; + + } else if (incoming->header->type == SD_BUS_MESSAGE_METHOD_ERROR) { + r = sd_bus_error_copy(error, &incoming->error); + sd_bus_message_unref(incoming); + return r; + } else { + r = -EIO; + goto fail; + } + + } else if (BUS_MESSAGE_COOKIE(incoming) == cookie && + bus->unique_name && + incoming->sender && + streq(bus->unique_name, incoming->sender)) { + + memmove(bus->rqueue + i, bus->rqueue + i + 1, sizeof(sd_bus_message*) * (bus->rqueue_size - i - 1)); + bus->rqueue_size--; + + /* Our own message? Somebody is trying + * to send its own client a message, + * let's not dead-lock, let's fail + * immediately. */ + + sd_bus_message_unref(incoming); + r = -ELOOP; + goto fail; + } + + /* Try to read more, right-away */ + i++; + } + + r = bus_read_message(bus, false, 0); + if (r < 0) { + if (IN_SET(r, -ENOTCONN, -ECONNRESET, -EPIPE, -ESHUTDOWN)) { + bus_enter_closing(bus); + r = -ECONNRESET; + } + + goto fail; + } + if (r > 0) + continue; + + if (timeout > 0) { + usec_t n; + + n = now(CLOCK_MONOTONIC); + if (n >= timeout) { + r = -ETIMEDOUT; + goto fail; + } + + left = timeout - n; + } else + left = (uint64_t) -1; + + r = bus_poll(bus, true, left); + if (r < 0) + goto fail; + if (r == 0) { + r = -ETIMEDOUT; + goto fail; + } + + r = dispatch_wqueue(bus); + if (r < 0) { + if (IN_SET(r, -ENOTCONN, -ECONNRESET, -EPIPE, -ESHUTDOWN)) { + bus_enter_closing(bus); + r = -ECONNRESET; + } + + goto fail; + } + } + +fail: + return sd_bus_error_set_errno(error, r); +} + +_public_ int sd_bus_get_fd(sd_bus *bus) { + + assert_return(bus, -EINVAL); + assert_return(bus->input_fd == bus->output_fd, -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + return bus->input_fd; +} + +_public_ int sd_bus_get_events(sd_bus *bus) { + int flags = 0; + + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state) && bus->state != BUS_CLOSING) + return -ENOTCONN; + + if (bus->state == BUS_OPENING) + flags |= POLLOUT; + else if (bus->state == BUS_AUTHENTICATING) { + + if (bus_socket_auth_needs_write(bus)) + flags |= POLLOUT; + + flags |= POLLIN; + + } else if (bus->state == BUS_RUNNING || bus->state == BUS_HELLO) { + if (bus->rqueue_size <= 0) + flags |= POLLIN; + if (bus->wqueue_size > 0) + flags |= POLLOUT; + } + + return flags; +} + +_public_ int sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec) { + struct reply_callback *c; + + assert_return(bus, -EINVAL); + assert_return(timeout_usec, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state) && bus->state != BUS_CLOSING) + return -ENOTCONN; + + if (bus->track_queue) { + *timeout_usec = 0; + return 1; + } + + if (bus->state == BUS_CLOSING) { + *timeout_usec = 0; + return 1; + } + + if (bus->state == BUS_AUTHENTICATING) { + *timeout_usec = bus->auth_timeout; + return 1; + } + + if (bus->state != BUS_RUNNING && bus->state != BUS_HELLO) { + *timeout_usec = (uint64_t) -1; + return 0; + } + + if (bus->rqueue_size > 0) { + *timeout_usec = 0; + return 1; + } + + c = prioq_peek(bus->reply_callbacks_prioq); + if (!c) { + *timeout_usec = (uint64_t) -1; + return 0; + } + + if (c->timeout == 0) { + *timeout_usec = (uint64_t) -1; + return 0; + } + + *timeout_usec = c->timeout; + return 1; +} + +static int process_timeout(sd_bus *bus) { + _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message* m = NULL; + struct reply_callback *c; + sd_bus_slot *slot; + usec_t n; + int r; + + assert(bus); + + c = prioq_peek(bus->reply_callbacks_prioq); + if (!c) + return 0; + + n = now(CLOCK_MONOTONIC); + if (c->timeout > n) + return 0; + + r = bus_message_new_synthetic_error( + bus, + c->cookie, + &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Method call timed out"), + &m); + if (r < 0) + return r; + + r = bus_seal_synthetic_message(bus, m); + if (r < 0) + return r; + + assert_se(prioq_pop(bus->reply_callbacks_prioq) == c); + c->timeout = 0; + + ordered_hashmap_remove(bus->reply_callbacks, &c->cookie); + c->cookie = 0; + + slot = container_of(c, sd_bus_slot, reply_callback); + + bus->iteration_counter++; + + bus->current_message = m; + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_handler = c->callback; + bus->current_userdata = slot->userdata; + r = c->callback(m, slot->userdata, &error_buffer); + bus->current_userdata = NULL; + bus->current_handler = NULL; + bus->current_slot = NULL; + bus->current_message = NULL; + + if (slot->floating) { + bus_slot_disconnect(slot); + sd_bus_slot_unref(slot); + } + + sd_bus_slot_unref(slot); + + return bus_maybe_reply_error(m, r, &error_buffer); +} + +static int process_hello(sd_bus *bus, sd_bus_message *m) { + assert(bus); + assert(m); + + if (bus->state != BUS_HELLO) + return 0; + + /* Let's make sure the first message on the bus is the HELLO + * reply. But note that we don't actually parse the message + * here (we leave that to the usual handling), we just verify + * we don't let any earlier msg through. */ + + if (m->header->type != SD_BUS_MESSAGE_METHOD_RETURN && + m->header->type != SD_BUS_MESSAGE_METHOD_ERROR) + return -EIO; + + if (m->reply_cookie != 1) + return -EIO; + + return 0; +} + +static int process_reply(sd_bus *bus, sd_bus_message *m) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *synthetic_reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; + struct reply_callback *c; + sd_bus_slot *slot; + int r; + + assert(bus); + assert(m); + + if (m->header->type != SD_BUS_MESSAGE_METHOD_RETURN && + m->header->type != SD_BUS_MESSAGE_METHOD_ERROR) + return 0; + + if (bus->is_kernel && (bus->hello_flags & KDBUS_HELLO_MONITOR)) + return 0; + + if (m->destination && bus->unique_name && !streq_ptr(m->destination, bus->unique_name)) + return 0; + + c = ordered_hashmap_remove(bus->reply_callbacks, &m->reply_cookie); + if (!c) + return 0; + + c->cookie = 0; + + slot = container_of(c, sd_bus_slot, reply_callback); + + if (m->n_fds > 0 && !(bus->hello_flags & KDBUS_HELLO_ACCEPT_FD)) { + + /* If the reply contained a file descriptor which we + * didn't want we pass an error instead. */ + + r = bus_message_new_synthetic_error( + bus, + m->reply_cookie, + &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Reply message contained file descriptor"), + &synthetic_reply); + if (r < 0) + return r; + + /* Copy over original timestamp */ + synthetic_reply->realtime = m->realtime; + synthetic_reply->monotonic = m->monotonic; + synthetic_reply->seqnum = m->seqnum; + + r = bus_seal_synthetic_message(bus, synthetic_reply); + if (r < 0) + return r; + + m = synthetic_reply; + } else { + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + } + + if (c->timeout != 0) { + prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx); + c->timeout = 0; + } + + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_handler = c->callback; + bus->current_userdata = slot->userdata; + r = c->callback(m, slot->userdata, &error_buffer); + bus->current_userdata = NULL; + bus->current_handler = NULL; + bus->current_slot = NULL; + + if (slot->floating) { + bus_slot_disconnect(slot); + sd_bus_slot_unref(slot); + } + + sd_bus_slot_unref(slot); + + return bus_maybe_reply_error(m, r, &error_buffer); +} + +static int process_filter(sd_bus *bus, sd_bus_message *m) { + _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; + struct filter_callback *l; + int r; + + assert(bus); + assert(m); + + do { + bus->filter_callbacks_modified = false; + + LIST_FOREACH(callbacks, l, bus->filter_callbacks) { + sd_bus_slot *slot; + + if (bus->filter_callbacks_modified) + break; + + /* Don't run this more than once per iteration */ + if (l->last_iteration == bus->iteration_counter) + continue; + + l->last_iteration = bus->iteration_counter; + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + slot = container_of(l, sd_bus_slot, filter_callback); + + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_handler = l->callback; + bus->current_userdata = slot->userdata; + r = l->callback(m, slot->userdata, &error_buffer); + bus->current_userdata = NULL; + bus->current_handler = NULL; + bus->current_slot = sd_bus_slot_unref(slot); + + r = bus_maybe_reply_error(m, r, &error_buffer); + if (r != 0) + return r; + + } + + } while (bus->filter_callbacks_modified); + + return 0; +} + +static int process_match(sd_bus *bus, sd_bus_message *m) { + int r; + + assert(bus); + assert(m); + + do { + bus->match_callbacks_modified = false; + + r = bus_match_run(bus, &bus->match_callbacks, m); + if (r != 0) + return r; + + } while (bus->match_callbacks_modified); + + return 0; +} + +static int process_builtin(sd_bus *bus, sd_bus_message *m) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + assert(bus); + assert(m); + + if (bus->hello_flags & KDBUS_HELLO_MONITOR) + return 0; + + if (bus->manual_peer_interface) + return 0; + + if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL) + return 0; + + if (!streq_ptr(m->interface, "org.freedesktop.DBus.Peer")) + return 0; + + if (m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) + return 1; + + if (streq_ptr(m->member, "Ping")) + r = sd_bus_message_new_method_return(m, &reply); + else if (streq_ptr(m->member, "GetMachineId")) { + sd_id128_t id; + char sid[33]; + + r = sd_id128_get_machine(&id); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(m, &reply); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "s", sd_id128_to_string(id, sid)); + } else { + r = sd_bus_message_new_method_errorf( + m, &reply, + SD_BUS_ERROR_UNKNOWN_METHOD, + "Unknown method '%s' on interface '%s'.", m->member, m->interface); + } + + if (r < 0) + return r; + + r = sd_bus_send(bus, reply, NULL); + if (r < 0) + return r; + + return 1; +} + +static int process_fd_check(sd_bus *bus, sd_bus_message *m) { + assert(bus); + assert(m); + + /* If we got a message with a file descriptor which we didn't + * want to accept, then let's drop it. How can this even + * happen? For example, when the kernel queues a message into + * an activatable names's queue which allows fds, and then is + * delivered to us later even though we ourselves did not + * negotiate it. */ + + if (bus->hello_flags & KDBUS_HELLO_MONITOR) + return 0; + + if (m->n_fds <= 0) + return 0; + + if (bus->hello_flags & KDBUS_HELLO_ACCEPT_FD) + return 0; + + if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL) + return 1; /* just eat it up */ + + return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Message contains file descriptors, which I cannot accept. Sorry."); +} + +static int process_message(sd_bus *bus, sd_bus_message *m) { + int r; + + assert(bus); + assert(m); + + bus->current_message = m; + bus->iteration_counter++; + + log_debug_bus_message(m); + + r = process_hello(bus, m); + if (r != 0) + goto finish; + + r = process_reply(bus, m); + if (r != 0) + goto finish; + + r = process_fd_check(bus, m); + if (r != 0) + goto finish; + + r = process_filter(bus, m); + if (r != 0) + goto finish; + + r = process_match(bus, m); + if (r != 0) + goto finish; + + r = process_builtin(bus, m); + if (r != 0) + goto finish; + + r = bus_process_object(bus, m); + +finish: + bus->current_message = NULL; + return r; +} + +static int dispatch_track(sd_bus *bus) { + assert(bus); + + if (!bus->track_queue) + return 0; + + bus_track_dispatch(bus->track_queue); + return 1; +} + +static int process_running(sd_bus *bus, bool hint_priority, int64_t priority, sd_bus_message **ret) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert(bus); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + r = process_timeout(bus); + if (r != 0) + goto null_message; + + r = dispatch_wqueue(bus); + if (r != 0) + goto null_message; + + r = dispatch_track(bus); + if (r != 0) + goto null_message; + + r = dispatch_rqueue(bus, hint_priority, priority, &m); + if (r < 0) + return r; + if (!m) + goto null_message; + + r = process_message(bus, m); + if (r != 0) + goto null_message; + + if (ret) { + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + *ret = m; + m = NULL; + return 1; + } + + if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) { + + log_debug("Unprocessed message call sender=%s object=%s interface=%s member=%s", + strna(sd_bus_message_get_sender(m)), + strna(sd_bus_message_get_path(m)), + strna(sd_bus_message_get_interface(m)), + strna(sd_bus_message_get_member(m))); + + r = sd_bus_reply_method_errorf( + m, + SD_BUS_ERROR_UNKNOWN_OBJECT, + "Unknown object '%s'.", m->path); + if (r < 0) + return r; + } + + return 1; + +null_message: + if (r >= 0 && ret) + *ret = NULL; + + return r; +} + +static int process_closing(sd_bus *bus, sd_bus_message **ret) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + struct reply_callback *c; + int r; + + assert(bus); + assert(bus->state == BUS_CLOSING); + + c = ordered_hashmap_first(bus->reply_callbacks); + if (c) { + _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; + sd_bus_slot *slot; + + /* First, fail all outstanding method calls */ + r = bus_message_new_synthetic_error( + bus, + c->cookie, + &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Connection terminated"), + &m); + if (r < 0) + return r; + + r = bus_seal_synthetic_message(bus, m); + if (r < 0) + return r; + + if (c->timeout != 0) { + prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx); + c->timeout = 0; + } + + ordered_hashmap_remove(bus->reply_callbacks, &c->cookie); + c->cookie = 0; + + slot = container_of(c, sd_bus_slot, reply_callback); + + bus->iteration_counter++; + + bus->current_message = m; + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_handler = c->callback; + bus->current_userdata = slot->userdata; + r = c->callback(m, slot->userdata, &error_buffer); + bus->current_userdata = NULL; + bus->current_handler = NULL; + bus->current_slot = NULL; + bus->current_message = NULL; + + if (slot->floating) { + bus_slot_disconnect(slot); + sd_bus_slot_unref(slot); + } + + sd_bus_slot_unref(slot); + + return bus_maybe_reply_error(m, r, &error_buffer); + } + + /* Then, synthesize a Disconnected message */ + r = sd_bus_message_new_signal( + bus, + &m, + "/org/freedesktop/DBus/Local", + "org.freedesktop.DBus.Local", + "Disconnected"); + if (r < 0) + return r; + + bus_message_set_sender_local(bus, m); + + r = bus_seal_synthetic_message(bus, m); + if (r < 0) + return r; + + sd_bus_close(bus); + + bus->current_message = m; + bus->iteration_counter++; + + r = process_filter(bus, m); + if (r != 0) + goto finish; + + r = process_match(bus, m); + if (r != 0) + goto finish; + + if (ret) { + *ret = m; + m = NULL; + } + + r = 1; + +finish: + bus->current_message = NULL; + + return r; +} + +static int bus_process_internal(sd_bus *bus, bool hint_priority, int64_t priority, sd_bus_message **ret) { + BUS_DONT_DESTROY(bus); + int r; + + /* Returns 0 when we didn't do anything. This should cause the + * caller to invoke sd_bus_wait() before returning the next + * time. Returns > 0 when we did something, which possibly + * means *ret is filled in with an unprocessed message. */ + + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + /* We don't allow recursively invoking sd_bus_process(). */ + assert_return(!bus->current_message, -EBUSY); + assert(!bus->current_slot); + + switch (bus->state) { + + case BUS_UNSET: + return -ENOTCONN; + + case BUS_CLOSED: + return -ECONNRESET; + + case BUS_OPENING: + r = bus_socket_process_opening(bus); + if (IN_SET(r, -ENOTCONN, -ECONNRESET, -EPIPE, -ESHUTDOWN)) { + bus_enter_closing(bus); + r = 1; + } else if (r < 0) + return r; + if (ret) + *ret = NULL; + return r; + + case BUS_AUTHENTICATING: + r = bus_socket_process_authenticating(bus); + if (IN_SET(r, -ENOTCONN, -ECONNRESET, -EPIPE, -ESHUTDOWN)) { + bus_enter_closing(bus); + r = 1; + } else if (r < 0) + return r; + + if (ret) + *ret = NULL; + + return r; + + case BUS_RUNNING: + case BUS_HELLO: + r = process_running(bus, hint_priority, priority, ret); + if (IN_SET(r, -ENOTCONN, -ECONNRESET, -EPIPE, -ESHUTDOWN)) { + bus_enter_closing(bus); + r = 1; + + if (ret) + *ret = NULL; + } + + return r; + + case BUS_CLOSING: + return process_closing(bus, ret); + } + + assert_not_reached("Unknown state"); +} + +_public_ int sd_bus_process(sd_bus *bus, sd_bus_message **ret) { + return bus_process_internal(bus, false, 0, ret); +} + +_public_ int sd_bus_process_priority(sd_bus *bus, int64_t priority, sd_bus_message **ret) { + return bus_process_internal(bus, true, priority, ret); +} + +static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec) { + struct pollfd p[2] = {}; + int r, e, n; + struct timespec ts; + usec_t m = USEC_INFINITY; + + assert(bus); + + if (bus->state == BUS_CLOSING) + return 1; + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + e = sd_bus_get_events(bus); + if (e < 0) + return e; + + if (need_more) + /* The caller really needs some more data, he doesn't + * care about what's already read, or any timeouts + * except its own. */ + e |= POLLIN; + else { + usec_t until; + /* The caller wants to process if there's something to + * process, but doesn't care otherwise */ + + r = sd_bus_get_timeout(bus, &until); + if (r < 0) + return r; + if (r > 0) { + usec_t nw; + nw = now(CLOCK_MONOTONIC); + m = until > nw ? until - nw : 0; + } + } + + if (timeout_usec != (uint64_t) -1 && (m == (uint64_t) -1 || timeout_usec < m)) + m = timeout_usec; + + p[0].fd = bus->input_fd; + if (bus->output_fd == bus->input_fd) { + p[0].events = e; + n = 1; + } else { + p[0].events = e & POLLIN; + p[1].fd = bus->output_fd; + p[1].events = e & POLLOUT; + n = 2; + } + + r = ppoll(p, n, m == (uint64_t) -1 ? NULL : timespec_store(&ts, m), NULL); + if (r < 0) + return -errno; + + return r > 0 ? 1 : 0; +} + +_public_ int sd_bus_wait(sd_bus *bus, uint64_t timeout_usec) { + + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (bus->state == BUS_CLOSING) + return 0; + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (bus->rqueue_size > 0) + return 0; + + return bus_poll(bus, false, timeout_usec); +} + +_public_ int sd_bus_flush(sd_bus *bus) { + int r; + + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (bus->state == BUS_CLOSING) + return 0; + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + r = bus_ensure_running(bus); + if (r < 0) + return r; + + if (bus->wqueue_size <= 0) + return 0; + + for (;;) { + r = dispatch_wqueue(bus); + if (r < 0) { + if (IN_SET(r, -ENOTCONN, -ECONNRESET, -EPIPE, -ESHUTDOWN)) { + bus_enter_closing(bus); + return -ECONNRESET; + } + + return r; + } + + if (bus->wqueue_size <= 0) + return 0; + + r = bus_poll(bus, false, (uint64_t) -1); + if (r < 0) + return r; + } +} + +_public_ int sd_bus_add_filter( + sd_bus *bus, + sd_bus_slot **slot, + sd_bus_message_handler_t callback, + void *userdata) { + + sd_bus_slot *s; + + assert_return(bus, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + s = bus_slot_allocate(bus, !slot, BUS_FILTER_CALLBACK, sizeof(struct filter_callback), userdata); + if (!s) + return -ENOMEM; + + s->filter_callback.callback = callback; + + bus->filter_callbacks_modified = true; + LIST_PREPEND(callbacks, bus->filter_callbacks, &s->filter_callback); + + if (slot) + *slot = s; + + return 0; +} + +_public_ int sd_bus_add_match( + sd_bus *bus, + sd_bus_slot **slot, + const char *match, + sd_bus_message_handler_t callback, + void *userdata) { + + struct bus_match_component *components = NULL; + unsigned n_components = 0; + sd_bus_slot *s = NULL; + int r = 0; + + assert_return(bus, -EINVAL); + assert_return(match, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + r = bus_match_parse(match, &components, &n_components); + if (r < 0) + goto finish; + + s = bus_slot_allocate(bus, !slot, BUS_MATCH_CALLBACK, sizeof(struct match_callback), userdata); + if (!s) { + r = -ENOMEM; + goto finish; + } + + s->match_callback.callback = callback; + s->match_callback.cookie = ++bus->match_cookie; + + if (bus->bus_client) { + enum bus_match_scope scope; + + scope = bus_match_get_scope(components, n_components); + + /* Do not install server-side matches for matches + * against the local service, interface or bus + * path. */ + if (scope != BUS_MATCH_LOCAL) { + + if (!bus->is_kernel) { + /* When this is not a kernel transport, we + * store the original match string, so that we + * can use it to remove the match again */ + + s->match_callback.match_string = strdup(match); + if (!s->match_callback.match_string) { + r = -ENOMEM; + goto finish; + } + } + + r = bus_add_match_internal(bus, s->match_callback.match_string, components, n_components, s->match_callback.cookie); + if (r < 0) + goto finish; + + s->match_added = true; + } + } + + bus->match_callbacks_modified = true; + r = bus_match_add(&bus->match_callbacks, components, n_components, &s->match_callback); + if (r < 0) + goto finish; + + if (slot) + *slot = s; + s = NULL; + +finish: + bus_match_parse_free(components, n_components); + sd_bus_slot_unref(s); + + return r; +} + +int bus_remove_match_by_string( + sd_bus *bus, + const char *match, + sd_bus_message_handler_t callback, + void *userdata) { + + struct bus_match_component *components = NULL; + unsigned n_components = 0; + struct match_callback *c; + int r = 0; + + assert_return(bus, -EINVAL); + assert_return(match, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + r = bus_match_parse(match, &components, &n_components); + if (r < 0) + goto finish; + + r = bus_match_find(&bus->match_callbacks, components, n_components, NULL, NULL, &c); + if (r <= 0) + goto finish; + + sd_bus_slot_unref(container_of(c, sd_bus_slot, match_callback)); + +finish: + bus_match_parse_free(components, n_components); + + return r; +} + +bool bus_pid_changed(sd_bus *bus) { + assert(bus); + + /* We don't support people creating a bus connection and + * keeping it around over a fork(). Let's complain. */ + + return bus->original_pid != getpid(); +} + +static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_bus *bus = userdata; + int r; + + assert(bus); + + r = sd_bus_process(bus, NULL); + if (r < 0) + return r; + + return 1; +} + +static int time_callback(sd_event_source *s, uint64_t usec, void *userdata) { + sd_bus *bus = userdata; + int r; + + assert(bus); + + r = sd_bus_process(bus, NULL); + if (r < 0) + return r; + + return 1; +} + +static int prepare_callback(sd_event_source *s, void *userdata) { + sd_bus *bus = userdata; + int r, e; + usec_t until; + + assert(s); + assert(bus); + + e = sd_bus_get_events(bus); + if (e < 0) + return e; + + if (bus->output_fd != bus->input_fd) { + + r = sd_event_source_set_io_events(bus->input_io_event_source, e & POLLIN); + if (r < 0) + return r; + + r = sd_event_source_set_io_events(bus->output_io_event_source, e & POLLOUT); + if (r < 0) + return r; + } else { + r = sd_event_source_set_io_events(bus->input_io_event_source, e); + if (r < 0) + return r; + } + + r = sd_bus_get_timeout(bus, &until); + if (r < 0) + return r; + if (r > 0) { + int j; + + j = sd_event_source_set_time(bus->time_event_source, until); + if (j < 0) + return j; + } + + r = sd_event_source_set_enabled(bus->time_event_source, r > 0); + if (r < 0) + return r; + + return 1; +} + +static int quit_callback(sd_event_source *event, void *userdata) { + sd_bus *bus = userdata; + + assert(event); + + sd_bus_flush(bus); + sd_bus_close(bus); + + return 1; +} + +static int attach_io_events(sd_bus *bus) { + int r; + + assert(bus); + + if (bus->input_fd < 0) + return 0; + + if (!bus->event) + return 0; + + if (!bus->input_io_event_source) { + r = sd_event_add_io(bus->event, &bus->input_io_event_source, bus->input_fd, 0, io_callback, bus); + if (r < 0) + return r; + + r = sd_event_source_set_prepare(bus->input_io_event_source, prepare_callback); + if (r < 0) + return r; + + r = sd_event_source_set_priority(bus->input_io_event_source, bus->event_priority); + if (r < 0) + return r; + + r = sd_event_source_set_description(bus->input_io_event_source, "bus-input"); + } else + r = sd_event_source_set_io_fd(bus->input_io_event_source, bus->input_fd); + + if (r < 0) + return r; + + if (bus->output_fd != bus->input_fd) { + assert(bus->output_fd >= 0); + + if (!bus->output_io_event_source) { + r = sd_event_add_io(bus->event, &bus->output_io_event_source, bus->output_fd, 0, io_callback, bus); + if (r < 0) + return r; + + r = sd_event_source_set_priority(bus->output_io_event_source, bus->event_priority); + if (r < 0) + return r; + + r = sd_event_source_set_description(bus->input_io_event_source, "bus-output"); + } else + r = sd_event_source_set_io_fd(bus->output_io_event_source, bus->output_fd); + + if (r < 0) + return r; + } + + return 0; +} + +static void detach_io_events(sd_bus *bus) { + assert(bus); + + if (bus->input_io_event_source) { + sd_event_source_set_enabled(bus->input_io_event_source, SD_EVENT_OFF); + bus->input_io_event_source = sd_event_source_unref(bus->input_io_event_source); + } + + if (bus->output_io_event_source) { + sd_event_source_set_enabled(bus->output_io_event_source, SD_EVENT_OFF); + bus->output_io_event_source = sd_event_source_unref(bus->output_io_event_source); + } +} + +_public_ int sd_bus_attach_event(sd_bus *bus, sd_event *event, int priority) { + int r; + + assert_return(bus, -EINVAL); + assert_return(!bus->event, -EBUSY); + + assert(!bus->input_io_event_source); + assert(!bus->output_io_event_source); + assert(!bus->time_event_source); + + if (event) + bus->event = sd_event_ref(event); + else { + r = sd_event_default(&bus->event); + if (r < 0) + return r; + } + + bus->event_priority = priority; + + r = sd_event_add_time(bus->event, &bus->time_event_source, CLOCK_MONOTONIC, 0, 0, time_callback, bus); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(bus->time_event_source, priority); + if (r < 0) + goto fail; + + r = sd_event_source_set_description(bus->time_event_source, "bus-time"); + if (r < 0) + goto fail; + + r = sd_event_add_exit(bus->event, &bus->quit_event_source, quit_callback, bus); + if (r < 0) + goto fail; + + r = sd_event_source_set_description(bus->quit_event_source, "bus-exit"); + if (r < 0) + goto fail; + + r = attach_io_events(bus); + if (r < 0) + goto fail; + + return 0; + +fail: + sd_bus_detach_event(bus); + return r; +} + +_public_ int sd_bus_detach_event(sd_bus *bus) { + assert_return(bus, -EINVAL); + + if (!bus->event) + return 0; + + detach_io_events(bus); + + if (bus->time_event_source) { + sd_event_source_set_enabled(bus->time_event_source, SD_EVENT_OFF); + bus->time_event_source = sd_event_source_unref(bus->time_event_source); + } + + if (bus->quit_event_source) { + sd_event_source_set_enabled(bus->quit_event_source, SD_EVENT_OFF); + bus->quit_event_source = sd_event_source_unref(bus->quit_event_source); + } + + bus->event = sd_event_unref(bus->event); + return 1; +} + +_public_ sd_event* sd_bus_get_event(sd_bus *bus) { + assert_return(bus, NULL); + + return bus->event; +} + +_public_ sd_bus_message* sd_bus_get_current_message(sd_bus *bus) { + assert_return(bus, NULL); + + return bus->current_message; +} + +_public_ sd_bus_slot* sd_bus_get_current_slot(sd_bus *bus) { + assert_return(bus, NULL); + + return bus->current_slot; +} + +_public_ sd_bus_message_handler_t sd_bus_get_current_handler(sd_bus *bus) { + assert_return(bus, NULL); + + return bus->current_handler; +} + +_public_ void* sd_bus_get_current_userdata(sd_bus *bus) { + assert_return(bus, NULL); + + return bus->current_userdata; +} + +static int bus_default(int (*bus_open)(sd_bus **), sd_bus **default_bus, sd_bus **ret) { + sd_bus *b = NULL; + int r; + + assert(bus_open); + assert(default_bus); + + if (!ret) + return !!*default_bus; + + if (*default_bus) { + *ret = sd_bus_ref(*default_bus); + return 0; + } + + r = bus_open(&b); + if (r < 0) + return r; + + b->default_bus_ptr = default_bus; + b->tid = gettid(); + *default_bus = b; + + *ret = b; + return 1; +} + +_public_ int sd_bus_default_system(sd_bus **ret) { + return bus_default(sd_bus_open_system, &default_system_bus, ret); +} + + +_public_ int sd_bus_default_user(sd_bus **ret) { + return bus_default(sd_bus_open_user, &default_user_bus, ret); +} + +_public_ int sd_bus_default(sd_bus **ret) { + + const char *e; + + /* Let's try our best to reuse another cached connection. If + * the starter bus type is set, connect via our normal + * connection logic, ignoring $DBUS_STARTER_ADDRESS, so that + * we can share the connection with the user/system default + * bus. */ + + e = secure_getenv("DBUS_STARTER_BUS_TYPE"); + if (e) { + if (streq(e, "system")) + return sd_bus_default_system(ret); + else if (STR_IN_SET(e, "user", "session")) + return sd_bus_default_user(ret); + } + + /* No type is specified, so we have not other option than to + * use the starter address if it is set. */ + + e = secure_getenv("DBUS_STARTER_ADDRESS"); + if (e) { + + return bus_default(sd_bus_open, &default_starter_bus, ret); + } + + /* Finally, if nothing is set use the cached connection for + * the right scope */ + + if (cg_pid_get_owner_uid(0, NULL) >= 0) + return sd_bus_default_user(ret); + else + return sd_bus_default_system(ret); +} + +_public_ int sd_bus_get_tid(sd_bus *b, pid_t *tid) { + assert_return(b, -EINVAL); + assert_return(tid, -EINVAL); + assert_return(!bus_pid_changed(b), -ECHILD); + + if (b->tid != 0) { + *tid = b->tid; + return 0; + } + + if (b->event) + return sd_event_get_tid(b->event, tid); + + return -ENXIO; +} + +_public_ int sd_bus_path_encode(const char *prefix, const char *external_id, char **ret_path) { + _cleanup_free_ char *e = NULL; + char *ret; + + assert_return(object_path_is_valid(prefix), -EINVAL); + assert_return(external_id, -EINVAL); + assert_return(ret_path, -EINVAL); + + e = bus_label_escape(external_id); + if (!e) + return -ENOMEM; + + ret = strjoin(prefix, "/", e, NULL); + if (!ret) + return -ENOMEM; + + *ret_path = ret; + return 0; +} + +_public_ int sd_bus_path_decode(const char *path, const char *prefix, char **external_id) { + const char *e; + char *ret; + + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(object_path_is_valid(prefix), -EINVAL); + assert_return(external_id, -EINVAL); + + e = object_path_startswith(path, prefix); + if (!e) { + *external_id = NULL; + return 0; + } + + ret = bus_label_unescape(e); + if (!ret) + return -ENOMEM; + + *external_id = ret; + return 1; +} + +_public_ int sd_bus_path_encode_many(char **out, const char *path_template, ...) { + _cleanup_strv_free_ char **labels = NULL; + char *path, *path_pos, **label_pos; + const char *sep, *template_pos; + size_t path_length; + va_list list; + int r; + + assert_return(out, -EINVAL); + assert_return(path_template, -EINVAL); + + path_length = strlen(path_template); + + va_start(list, path_template); + for (sep = strchr(path_template, '%'); sep; sep = strchr(sep + 1, '%')) { + const char *arg; + char *label; + + arg = va_arg(list, const char *); + if (!arg) { + va_end(list); + return -EINVAL; + } + + label = bus_label_escape(arg); + if (!label) { + va_end(list); + return -ENOMEM; + } + + r = strv_consume(&labels, label); + if (r < 0) { + va_end(list); + return r; + } + + /* add label length, but account for the format character */ + path_length += strlen(label) - 1; + } + va_end(list); + + path = malloc(path_length + 1); + if (!path) + return -ENOMEM; + + path_pos = path; + label_pos = labels; + + for (template_pos = path_template; *template_pos; ) { + sep = strchrnul(template_pos, '%'); + path_pos = mempcpy(path_pos, template_pos, sep - template_pos); + if (!*sep) + break; + + path_pos = stpcpy(path_pos, *label_pos++); + template_pos = sep + 1; + } + + *path_pos = 0; + *out = path; + return 0; +} + +_public_ int sd_bus_path_decode_many(const char *path, const char *path_template, ...) { + _cleanup_strv_free_ char **labels = NULL; + const char *template_pos, *path_pos; + char **label_pos; + va_list list; + int r; + + /* + * This decodes an object-path based on a template argument. The + * template consists of a verbatim path, optionally including special + * directives: + * + * - Each occurrence of '%' in the template matches an arbitrary + * substring of a label in the given path. At most one such + * directive is allowed per label. For each such directive, the + * caller must provide an output parameter (char **) via va_arg. If + * NULL is passed, the given label is verified, but not returned. + * For each matched label, the *decoded* label is stored in the + * passed output argument, and the caller is responsible to free + * it. Note that the output arguments are only modified if the + * actualy path matched the template. Otherwise, they're left + * untouched. + * + * This function returns <0 on error, 0 if the path does not match the + * template, 1 if it matched. + */ + + assert_return(path, -EINVAL); + assert_return(path_template, -EINVAL); + + path_pos = path; + + for (template_pos = path_template; *template_pos; ) { + const char *sep; + size_t length; + char *label; + + /* verify everything until the next '%' matches verbatim */ + sep = strchrnul(template_pos, '%'); + length = sep - template_pos; + if (strncmp(path_pos, template_pos, length)) + return 0; + + path_pos += length; + template_pos += length; + + if (!*template_pos) + break; + + /* We found the next '%' character. Everything up until here + * matched. We now skip ahead to the end of this label and make + * sure it matches the tail of the label in the path. Then we + * decode the string in-between and save it for later use. */ + + ++template_pos; /* skip over '%' */ + + sep = strchrnul(template_pos, '/'); + length = sep - template_pos; /* length of suffix to match verbatim */ + + /* verify the suffixes match */ + sep = strchrnul(path_pos, '/'); + if (sep - path_pos < (ssize_t)length || + strncmp(sep - length, template_pos, length)) + return 0; + + template_pos += length; /* skip over matched label */ + length = sep - path_pos - length; /* length of sub-label to decode */ + + /* store unescaped label for later use */ + label = bus_label_unescape_n(path_pos, length); + if (!label) + return -ENOMEM; + + r = strv_consume(&labels, label); + if (r < 0) + return r; + + path_pos = sep; /* skip decoded label and suffix */ + } + + /* end of template must match end of path */ + if (*path_pos) + return 0; + + /* copy the labels over to the caller */ + va_start(list, path_template); + for (label_pos = labels; label_pos && *label_pos; ++label_pos) { + char **arg; + + arg = va_arg(list, char **); + if (arg) + *arg = *label_pos; + else + free(*label_pos); + } + va_end(list); + + free(labels); + labels = NULL; + return 1; +} + +_public_ int sd_bus_try_close(sd_bus *bus) { + int r; + + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!bus->is_kernel) + return -EOPNOTSUPP; + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (bus->rqueue_size > 0) + return -EBUSY; + + if (bus->wqueue_size > 0) + return -EBUSY; + + r = bus_kernel_try_close(bus); + if (r < 0) + return r; + + sd_bus_close(bus); + return 0; +} + +_public_ int sd_bus_get_description(sd_bus *bus, const char **description) { + assert_return(bus, -EINVAL); + assert_return(description, -EINVAL); + assert_return(bus->description, -ENXIO); + assert_return(!bus_pid_changed(bus), -ECHILD); + + *description = bus->description; + return 0; +} + +int bus_get_root_path(sd_bus *bus) { + int r; + + if (bus->cgroup_root) + return 0; + + r = cg_get_root_path(&bus->cgroup_root); + if (r == -ENOENT) { + bus->cgroup_root = strdup("/"); + if (!bus->cgroup_root) + return -ENOMEM; + + r = 0; + } + + return r; +} + +_public_ int sd_bus_get_scope(sd_bus *bus, const char **scope) { + int r; + + assert_return(bus, -EINVAL); + assert_return(scope, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (bus->is_kernel) { + _cleanup_free_ char *n = NULL; + const char *dash; + + r = bus_kernel_get_bus_name(bus, &n); + if (r < 0) + return r; + + if (streq(n, "0-system")) { + *scope = "system"; + return 0; + } + + dash = strchr(n, '-'); + if (streq_ptr(dash, "-user")) { + *scope = "user"; + return 0; + } + } + + if (bus->is_user) { + *scope = "user"; + return 0; + } + + if (bus->is_system) { + *scope = "system"; + return 0; + } + + return -ENODATA; +} + +_public_ int sd_bus_get_address(sd_bus *bus, const char **address) { + + assert_return(bus, -EINVAL); + assert_return(address, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (bus->address) { + *address = bus->address; + return 0; + } + + return -ENODATA; +} + +_public_ int sd_bus_get_creds_mask(sd_bus *bus, uint64_t *mask) { + assert_return(bus, -EINVAL); + assert_return(mask, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + *mask = bus->creds_mask; + return 0; +} + +_public_ int sd_bus_is_bus_client(sd_bus *bus) { + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + return bus->bus_client; +} + +_public_ int sd_bus_is_server(sd_bus *bus) { + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + return bus->is_server; +} + +_public_ int sd_bus_is_anonymous(sd_bus *bus) { + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + return bus->anonymous_auth; +} + +_public_ int sd_bus_is_trusted(sd_bus *bus) { + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + return bus->trusted; +} + +_public_ int sd_bus_is_monitor(sd_bus *bus) { + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + return !!(bus->hello_flags & KDBUS_HELLO_MONITOR); +} + +static void flush_close(sd_bus *bus) { + if (!bus) + return; + + /* Flushes and closes the specified bus. We take a ref before, + * to ensure the flushing does not cause the bus to be + * unreferenced. */ + + sd_bus_flush_close_unref(sd_bus_ref(bus)); +} + +_public_ void sd_bus_default_flush_close(void) { + flush_close(default_starter_bus); + flush_close(default_user_bus); + flush_close(default_system_bus); +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-benchmark.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-benchmark.c new file mode 100644 index 0000000000..a222d36bb4 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-benchmark.c @@ -0,0 +1,371 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include "alloc-util.h" +#include "bus-internal.h" +#include "bus-kernel.h" +#include "bus-util.h" +#include "def.h" +#include "fd-util.h" +#include "time-util.h" +#include "util.h" + +#define MAX_SIZE (2*1024*1024) + +static usec_t arg_loop_usec = 100 * USEC_PER_MSEC; + +typedef enum Type { + TYPE_KDBUS, + TYPE_LEGACY, + TYPE_DIRECT, +} Type; + +static void server(sd_bus *b, size_t *result) { + int r; + + for (;;) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + r = sd_bus_process(b, &m); + assert_se(r >= 0); + + if (r == 0) + assert_se(sd_bus_wait(b, USEC_INFINITY) >= 0); + if (!m) + continue; + + if (sd_bus_message_is_method_call(m, "benchmark.server", "Ping")) + assert_se(sd_bus_reply_method_return(m, NULL) >= 0); + else if (sd_bus_message_is_method_call(m, "benchmark.server", "Work")) { + const void *p; + size_t sz; + + /* Make sure the mmap is mapped */ + assert_se(sd_bus_message_read_array(m, 'y', &p, &sz) > 0); + + r = sd_bus_reply_method_return(m, NULL); + assert_se(r >= 0); + } else if (sd_bus_message_is_method_call(m, "benchmark.server", "Exit")) { + uint64_t res; + assert_se(sd_bus_message_read(m, "t", &res) > 0); + + *result = res; + return; + + } else if (!sd_bus_message_is_signal(m, NULL, NULL)) + assert_not_reached("Unknown method"); + } +} + +static void transaction(sd_bus *b, size_t sz, const char *server_name) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + uint8_t *p; + + assert_se(sd_bus_message_new_method_call(b, &m, server_name, "/", "benchmark.server", "Work") >= 0); + assert_se(sd_bus_message_append_array_space(m, 'y', sz, (void**) &p) >= 0); + + memset(p, 0x80, sz); + + assert_se(sd_bus_call(b, m, 0, NULL, &reply) >= 0); +} + +static void client_bisect(const char *address, const char *server_name) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *x = NULL; + size_t lsize, rsize, csize; + sd_bus *b; + int r; + + r = sd_bus_new(&b); + assert_se(r >= 0); + + r = sd_bus_set_address(b, address); + assert_se(r >= 0); + + r = sd_bus_start(b); + assert_se(r >= 0); + + r = sd_bus_call_method(b, server_name, "/", "benchmark.server", "Ping", NULL, NULL, NULL); + assert_se(r >= 0); + + lsize = 1; + rsize = MAX_SIZE; + + printf("SIZE\tCOPY\tMEMFD\n"); + + for (;;) { + usec_t t; + unsigned n_copying, n_memfd; + + csize = (lsize + rsize) / 2; + + if (csize <= lsize) + break; + + if (csize <= 0) + break; + + printf("%zu\t", csize); + + b->use_memfd = 0; + + t = now(CLOCK_MONOTONIC); + for (n_copying = 0;; n_copying++) { + transaction(b, csize, server_name); + if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) + break; + } + printf("%u\t", (unsigned) ((n_copying * USEC_PER_SEC) / arg_loop_usec)); + + b->use_memfd = -1; + + t = now(CLOCK_MONOTONIC); + for (n_memfd = 0;; n_memfd++) { + transaction(b, csize, server_name); + if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) + break; + } + printf("%u\n", (unsigned) ((n_memfd * USEC_PER_SEC) / arg_loop_usec)); + + if (n_copying == n_memfd) + break; + + if (n_copying > n_memfd) + lsize = csize; + else + rsize = csize; + } + + b->use_memfd = 1; + assert_se(sd_bus_message_new_method_call(b, &x, server_name, "/", "benchmark.server", "Exit") >= 0); + assert_se(sd_bus_message_append(x, "t", csize) >= 0); + assert_se(sd_bus_send(b, x, NULL) >= 0); + + sd_bus_unref(b); +} + +static void client_chart(Type type, const char *address, const char *server_name, int fd) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *x = NULL; + size_t csize; + sd_bus *b; + int r; + + r = sd_bus_new(&b); + assert_se(r >= 0); + + if (type == TYPE_DIRECT) { + r = sd_bus_set_fd(b, fd, fd); + assert_se(r >= 0); + } else { + r = sd_bus_set_address(b, address); + assert_se(r >= 0); + + r = sd_bus_set_bus_client(b, true); + assert_se(r >= 0); + } + + r = sd_bus_start(b); + assert_se(r >= 0); + + r = sd_bus_call_method(b, server_name, "/", "benchmark.server", "Ping", NULL, NULL, NULL); + assert_se(r >= 0); + + switch (type) { + case TYPE_KDBUS: + printf("SIZE\tCOPY\tMEMFD\n"); + break; + case TYPE_LEGACY: + printf("SIZE\tLEGACY\n"); + break; + case TYPE_DIRECT: + printf("SIZE\tDIRECT\n"); + break; + } + + for (csize = 1; csize <= MAX_SIZE; csize *= 2) { + usec_t t; + unsigned n_copying, n_memfd; + + printf("%zu\t", csize); + + if (type == TYPE_KDBUS) { + b->use_memfd = 0; + + t = now(CLOCK_MONOTONIC); + for (n_copying = 0;; n_copying++) { + transaction(b, csize, server_name); + if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) + break; + } + + printf("%u\t", (unsigned) ((n_copying * USEC_PER_SEC) / arg_loop_usec)); + + b->use_memfd = -1; + } + + t = now(CLOCK_MONOTONIC); + for (n_memfd = 0;; n_memfd++) { + transaction(b, csize, server_name); + if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) + break; + } + + printf("%u\n", (unsigned) ((n_memfd * USEC_PER_SEC) / arg_loop_usec)); + } + + b->use_memfd = 1; + assert_se(sd_bus_message_new_method_call(b, &x, server_name, "/", "benchmark.server", "Exit") >= 0); + assert_se(sd_bus_message_append(x, "t", csize) >= 0); + assert_se(sd_bus_send(b, x, NULL) >= 0); + + sd_bus_unref(b); +} + +int main(int argc, char *argv[]) { + enum { + MODE_BISECT, + MODE_CHART, + } mode = MODE_BISECT; + Type type = TYPE_KDBUS; + int i, pair[2] = { -1, -1 }; + _cleanup_free_ char *name = NULL, *bus_name = NULL, *address = NULL, *server_name = NULL; + _cleanup_close_ int bus_ref = -1; + const char *unique; + cpu_set_t cpuset; + size_t result; + sd_bus *b; + pid_t pid; + int r; + + for (i = 1; i < argc; i++) { + if (streq(argv[i], "chart")) { + mode = MODE_CHART; + continue; + } else if (streq(argv[i], "legacy")) { + type = TYPE_LEGACY; + continue; + } else if (streq(argv[i], "direct")) { + type = TYPE_DIRECT; + continue; + } + + assert_se(parse_sec(argv[i], &arg_loop_usec) >= 0); + } + + assert_se(!MODE_BISECT || TYPE_KDBUS); + + assert_se(arg_loop_usec > 0); + + if (type == TYPE_KDBUS) { + assert_se(asprintf(&name, "deine-mutter-%u", (unsigned) getpid()) >= 0); + + bus_ref = bus_kernel_create_bus(name, false, &bus_name); + if (bus_ref == -ENOENT) + exit(EXIT_TEST_SKIP); + + assert_se(bus_ref >= 0); + + address = strappend("kernel:path=", bus_name); + assert_se(address); + } else if (type == TYPE_LEGACY) { + const char *e; + + e = secure_getenv("DBUS_SESSION_BUS_ADDRESS"); + assert_se(e); + + address = strdup(e); + assert_se(address); + } + + r = sd_bus_new(&b); + assert_se(r >= 0); + + if (type == TYPE_DIRECT) { + assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) >= 0); + + r = sd_bus_set_fd(b, pair[0], pair[0]); + assert_se(r >= 0); + + r = sd_bus_set_server(b, true, SD_ID128_NULL); + assert_se(r >= 0); + } else { + r = sd_bus_set_address(b, address); + assert_se(r >= 0); + + r = sd_bus_set_bus_client(b, true); + assert_se(r >= 0); + } + + r = sd_bus_start(b); + assert_se(r >= 0); + + if (type != TYPE_DIRECT) { + r = sd_bus_get_unique_name(b, &unique); + assert_se(r >= 0); + + server_name = strdup(unique); + assert_se(server_name); + } + + sync(); + setpriority(PRIO_PROCESS, 0, -19); + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { + CPU_ZERO(&cpuset); + CPU_SET(0, &cpuset); + pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); + + safe_close(bus_ref); + sd_bus_unref(b); + + switch (mode) { + case MODE_BISECT: + client_bisect(address, server_name); + break; + + case MODE_CHART: + client_chart(type, address, server_name, pair[1]); + break; + } + + _exit(0); + } + + CPU_ZERO(&cpuset); + CPU_SET(1, &cpuset); + pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); + + server(b, &result); + + if (mode == MODE_BISECT) + printf("Copying/memfd are equally fast at %zu bytes\n", result); + + assert_se(waitpid(pid, NULL, 0) == pid); + + safe_close(pair[1]); + sd_bus_unref(b); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-chat.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-chat.c new file mode 100644 index 0000000000..1f028d2b23 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-chat.c @@ -0,0 +1,560 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-internal.h" +#include "bus-match.h" +#include "bus-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "log.h" +#include "macro.h" +#include "util.h" + +static int match_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + log_info("Match triggered! interface=%s member=%s", strna(sd_bus_message_get_interface(m)), strna(sd_bus_message_get_member(m))); + return 0; +} + +static int object_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + int r; + + if (sd_bus_message_is_method_error(m, NULL)) + return 0; + + if (sd_bus_message_is_method_call(m, "org.object.test", "Foobar")) { + log_info("Invoked Foobar() on %s", sd_bus_message_get_path(m)); + + r = sd_bus_reply_method_return(m, NULL); + if (r < 0) + return log_error_errno(r, "Failed to send reply: %m"); + + return 1; + } + + return 0; +} + +static int server_init(sd_bus **_bus) { + sd_bus *bus = NULL; + sd_id128_t id; + int r; + const char *unique; + + assert_se(_bus); + + r = sd_bus_open_user(&bus); + if (r < 0) { + log_error_errno(r, "Failed to connect to user bus: %m"); + goto fail; + } + + r = sd_bus_get_bus_id(bus, &id); + if (r < 0) { + log_error_errno(r, "Failed to get server ID: %m"); + goto fail; + } + + r = sd_bus_get_unique_name(bus, &unique); + if (r < 0) { + log_error_errno(r, "Failed to get unique name: %m"); + goto fail; + } + + log_info("Peer ID is " SD_ID128_FORMAT_STR ".", SD_ID128_FORMAT_VAL(id)); + log_info("Unique ID: %s", unique); + log_info("Can send file handles: %i", sd_bus_can_send(bus, 'h')); + + r = sd_bus_request_name(bus, "org.freedesktop.systemd.test", 0); + if (r < 0) { + log_error_errno(r, "Failed to acquire name: %m"); + goto fail; + } + + r = sd_bus_add_fallback(bus, NULL, "/foo/bar", object_callback, NULL); + if (r < 0) { + log_error_errno(r, "Failed to add object: %m"); + goto fail; + } + + r = sd_bus_add_match(bus, NULL, "type='signal',interface='foo.bar',member='Notify'", match_callback, NULL); + if (r < 0) { + log_error_errno(r, "Failed to add match: %m"); + goto fail; + } + + r = sd_bus_add_match(bus, NULL, "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged'", match_callback, NULL); + if (r < 0) { + log_error_errno(r, "Failed to add match: %m"); + goto fail; + } + + bus_match_dump(&bus->match_callbacks, 0); + + *_bus = bus; + return 0; + +fail: + sd_bus_unref(bus); + return r; +} + +static int server(sd_bus *bus) { + int r; + bool client1_gone = false, client2_gone = false; + + while (!client1_gone || !client2_gone) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + pid_t pid = 0; + const char *label = NULL; + + r = sd_bus_process(bus, &m); + if (r < 0) { + log_error_errno(r, "Failed to process requests: %m"); + goto fail; + } + + if (r == 0) { + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) { + log_error_errno(r, "Failed to wait: %m"); + goto fail; + } + + continue; + } + + if (!m) + continue; + + sd_bus_creds_get_pid(sd_bus_message_get_creds(m), &pid); + sd_bus_creds_get_selinux_context(sd_bus_message_get_creds(m), &label); + log_info("Got message! member=%s pid="PID_FMT" label=%s", + strna(sd_bus_message_get_member(m)), + pid, + strna(label)); + /* bus_message_dump(m); */ + /* sd_bus_message_rewind(m, true); */ + + if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "LowerCase")) { + const char *hello; + _cleanup_free_ char *lowercase = NULL; + + r = sd_bus_message_read(m, "s", &hello); + if (r < 0) { + log_error_errno(r, "Failed to get parameter: %m"); + goto fail; + } + + lowercase = strdup(hello); + if (!lowercase) { + r = log_oom(); + goto fail; + } + + ascii_strlower(lowercase); + + r = sd_bus_reply_method_return(m, "s", lowercase); + if (r < 0) { + log_error_errno(r, "Failed to send reply: %m"); + goto fail; + } + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "ExitClient1")) { + + r = sd_bus_reply_method_return(m, NULL); + if (r < 0) { + log_error_errno(r, "Failed to send reply: %m"); + goto fail; + } + + client1_gone = true; + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "ExitClient2")) { + + r = sd_bus_reply_method_return(m, NULL); + if (r < 0) { + log_error_errno(r, "Failed to send reply: %m"); + goto fail; + } + + client2_gone = true; + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "Slow")) { + + sleep(1); + + r = sd_bus_reply_method_return(m, NULL); + if (r < 0) { + log_error_errno(r, "Failed to send reply: %m"); + goto fail; + } + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "FileDescriptor")) { + int fd; + static const char x = 'X'; + + r = sd_bus_message_read(m, "h", &fd); + if (r < 0) { + log_error_errno(r, "Failed to get parameter: %m"); + goto fail; + } + + log_info("Received fd=%d", fd); + + if (write(fd, &x, 1) < 0) { + log_error_errno(errno, "Failed to write to fd: %m"); + safe_close(fd); + goto fail; + } + + r = sd_bus_reply_method_return(m, NULL); + if (r < 0) { + log_error_errno(r, "Failed to send reply: %m"); + goto fail; + } + + } else if (sd_bus_message_is_method_call(m, NULL, NULL)) { + + r = sd_bus_reply_method_error( + m, + &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_UNKNOWN_METHOD, "Unknown method.")); + if (r < 0) { + log_error_errno(r, "Failed to send reply: %m"); + goto fail; + } + } + } + + r = 0; + +fail: + if (bus) { + sd_bus_flush(bus); + sd_bus_unref(bus); + } + + return r; +} + +static void* client1(void*p) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *hello; + int r; + _cleanup_close_pair_ int pp[2] = { -1, -1 }; + char x; + + r = sd_bus_open_user(&bus); + if (r < 0) { + log_error_errno(r, "Failed to connect to user bus: %m"); + goto finish; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "LowerCase", + &error, + &reply, + "s", + "HELLO"); + if (r < 0) { + log_error_errno(r, "Failed to issue method call: %m"); + goto finish; + } + + r = sd_bus_message_read(reply, "s", &hello); + if (r < 0) { + log_error_errno(r, "Failed to get string: %m"); + goto finish; + } + + assert_se(streq(hello, "hello")); + + if (pipe2(pp, O_CLOEXEC|O_NONBLOCK) < 0) { + log_error_errno(errno, "Failed to allocate pipe: %m"); + r = -errno; + goto finish; + } + + log_info("Sending fd=%d", pp[1]); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "FileDescriptor", + &error, + NULL, + "h", + pp[1]); + if (r < 0) { + log_error_errno(r, "Failed to issue method call: %m"); + goto finish; + } + + errno = 0; + if (read(pp[0], &x, 1) <= 0) { + log_error("Failed to read from pipe: %s", errno ? strerror(errno) : "early read"); + goto finish; + } + + r = 0; + +finish: + if (bus) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *q; + + r = sd_bus_message_new_method_call( + bus, + &q, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "ExitClient1"); + if (r < 0) + log_error_errno(r, "Failed to allocate method call: %m"); + else + sd_bus_send(bus, q, NULL); + + } + + return INT_TO_PTR(r); +} + +static int quit_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + bool *x = userdata; + + log_error("Quit callback: %s", strerror(sd_bus_message_get_errno(m))); + + *x = 1; + return 1; +} + +static void* client2(void*p) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + bool quit = false; + const char *mid; + int r; + + r = sd_bus_open_user(&bus); + if (r < 0) { + log_error_errno(r, "Failed to connect to user bus: %m"); + goto finish; + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd.test", + "/foo/bar/waldo/piep", + "org.object.test", + "Foobar"); + if (r < 0) { + log_error_errno(r, "Failed to allocate method call: %m"); + goto finish; + } + + r = sd_bus_send(bus, m, NULL); + if (r < 0) { + log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); + goto finish; + } + + m = sd_bus_message_unref(m); + + r = sd_bus_message_new_signal( + bus, + &m, + "/foobar", + "foo.bar", + "Notify"); + if (r < 0) { + log_error_errno(r, "Failed to allocate signal: %m"); + goto finish; + } + + r = sd_bus_send(bus, m, NULL); + if (r < 0) { + log_error("Failed to issue signal: %s", bus_error_message(&error, -r)); + goto finish; + } + + m = sd_bus_message_unref(m); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.DBus.Peer", + "GetMachineId"); + if (r < 0) { + log_error_errno(r, "Failed to allocate method call: %m"); + goto finish; + } + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); + goto finish; + } + + r = sd_bus_message_read(reply, "s", &mid); + if (r < 0) { + log_error_errno(r, "Failed to parse machine ID: %m"); + goto finish; + } + + log_info("Machine ID is %s.", mid); + + m = sd_bus_message_unref(m); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "Slow"); + if (r < 0) { + log_error_errno(r, "Failed to allocate method call: %m"); + goto finish; + } + + reply = sd_bus_message_unref(reply); + + r = sd_bus_call(bus, m, 200 * USEC_PER_MSEC, &error, &reply); + if (r < 0) + log_info("Failed to issue method call: %s", bus_error_message(&error, -r)); + else + log_info("Slow call succeed."); + + m = sd_bus_message_unref(m); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "Slow"); + if (r < 0) { + log_error_errno(r, "Failed to allocate method call: %m"); + goto finish; + } + + r = sd_bus_call_async(bus, NULL, m, quit_callback, &quit, 200 * USEC_PER_MSEC); + if (r < 0) { + log_info("Failed to issue method call: %s", bus_error_message(&error, -r)); + goto finish; + } + + while (!quit) { + r = sd_bus_process(bus, NULL); + if (r < 0) { + log_error_errno(r, "Failed to process requests: %m"); + goto finish; + } + if (r == 0) { + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) { + log_error_errno(r, "Failed to wait: %m"); + goto finish; + } + } + } + + r = 0; + +finish: + if (bus) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *q; + + r = sd_bus_message_new_method_call( + bus, + &q, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "ExitClient2"); + if (r < 0) { + log_error_errno(r, "Failed to allocate method call: %m"); + goto finish; + } + + (void) sd_bus_send(bus, q, NULL); + } + + return INT_TO_PTR(r); +} + +int main(int argc, char *argv[]) { + pthread_t c1, c2; + sd_bus *bus; + void *p; + int q, r; + + r = server_init(&bus); + if (r < 0) { + log_info("Failed to connect to bus, skipping tests."); + return EXIT_TEST_SKIP; + } + + log_info("Initialized..."); + + r = pthread_create(&c1, NULL, client1, bus); + if (r != 0) + return EXIT_FAILURE; + + r = pthread_create(&c2, NULL, client2, bus); + if (r != 0) + return EXIT_FAILURE; + + r = server(bus); + + q = pthread_join(c1, &p); + if (q != 0) + return EXIT_FAILURE; + if (PTR_TO_INT(p) < 0) + return EXIT_FAILURE; + + q = pthread_join(c2, &p); + if (q != 0) + return EXIT_FAILURE; + if (PTR_TO_INT(p) < 0) + return EXIT_FAILURE; + + if (r < 0) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-cleanup.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-cleanup.c new file mode 100644 index 0000000000..bd4a3fbf34 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-cleanup.c @@ -0,0 +1,95 @@ +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + 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 + +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-util.h" +#include "refcnt.h" + +static void test_bus_new(void) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + + assert_se(sd_bus_new(&bus) == 0); + printf("after new: refcount %u\n", REFCNT_GET(bus->n_ref)); +} + +static int test_bus_open(void) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + r = sd_bus_open_system(&bus); + if (r == -ECONNREFUSED || r == -ENOENT) + return r; + + assert_se(r >= 0); + printf("after open: refcount %u\n", REFCNT_GET(bus->n_ref)); + + return 0; +} + +static void test_bus_new_method_call(void) { + sd_bus *bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + assert_se(sd_bus_open_system(&bus) >= 0); + + assert_se(sd_bus_message_new_method_call(bus, &m, "a.service.name", "/an/object/path", "an.interface.name", "AMethodName") >= 0); + + printf("after message_new_method_call: refcount %u\n", REFCNT_GET(bus->n_ref)); + + sd_bus_flush_close_unref(bus); + printf("after bus_flush_close_unref: refcount %u\n", m->n_ref); +} + +static void test_bus_new_signal(void) { + sd_bus *bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + assert_se(sd_bus_open_system(&bus) >= 0); + + assert_se(sd_bus_message_new_signal(bus, &m, "/an/object/path", "an.interface.name", "Name") >= 0); + + printf("after message_new_signal: refcount %u\n", REFCNT_GET(bus->n_ref)); + + sd_bus_flush_close_unref(bus); + printf("after bus_flush_close_unref: refcount %u\n", m->n_ref); +} + +int main(int argc, char **argv) { + int r; + + log_parse_environment(); + log_open(); + + test_bus_new(); + r = test_bus_open(); + if (r < 0) { + log_info("Failed to connect to bus, skipping tests."); + return EXIT_TEST_SKIP; + } + + test_bus_new_method_call(); + test_bus_new_signal(); + + return EXIT_SUCCESS; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-creds.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-creds.c new file mode 100644 index 0000000000..c58b76c258 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-creds.c @@ -0,0 +1,50 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "bus-dump.h" +#include "bus-util.h" +#include "cgroup-util.h" + +int main(int argc, char *argv[]) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + int r; + + if (cg_unified() == -ENOMEDIUM) { + puts("Skipping test: /sys/fs/cgroup/ not available"); + return EXIT_TEST_SKIP; + } + + r = sd_bus_creds_new_from_pid(&creds, 0, _SD_BUS_CREDS_ALL); + assert_se(r >= 0); + + bus_creds_dump(creds, NULL, true); + + creds = sd_bus_creds_unref(creds); + + r = sd_bus_creds_new_from_pid(&creds, 1, _SD_BUS_CREDS_ALL); + if (r != -EACCES) { + assert_se(r >= 0); + putchar('\n'); + bus_creds_dump(creds, NULL, true); + } + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-error.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-error.c new file mode 100644 index 0000000000..bce3cc31c9 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-error.c @@ -0,0 +1,232 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "bus-common-errors.h" +#include "bus-error.h" +#include "bus-util.h" +#include "errno-list.h" + +static void test_error(void) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL, second = SD_BUS_ERROR_NULL; + const sd_bus_error const_error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FILE_EXISTS, "const error"); + const sd_bus_error temporarily_const_error = { + .name = SD_BUS_ERROR_ACCESS_DENIED, + .message = "oh! no", + ._need_free = -1 + }; + + assert_se(!sd_bus_error_is_set(&error)); + assert_se(sd_bus_error_set(&error, SD_BUS_ERROR_NOT_SUPPORTED, "xxx") == -EOPNOTSUPP); + assert_se(streq(error.name, SD_BUS_ERROR_NOT_SUPPORTED)); + assert_se(streq(error.message, "xxx")); + assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_NOT_SUPPORTED)); + assert_se(sd_bus_error_get_errno(&error) == EOPNOTSUPP); + assert_se(sd_bus_error_is_set(&error)); + sd_bus_error_free(&error); + + /* Check with no error */ + assert_se(!sd_bus_error_is_set(&error)); + assert_se(sd_bus_error_setf(&error, NULL, "yyy %i", -1) == 0); + assert_se(error.name == NULL); + assert_se(error.message == NULL); + assert_se(!sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND)); + assert_se(sd_bus_error_get_errno(&error) == 0); + assert_se(!sd_bus_error_is_set(&error)); + + assert_se(sd_bus_error_setf(&error, SD_BUS_ERROR_FILE_NOT_FOUND, "yyy %i", -1) == -ENOENT); + assert_se(streq(error.name, SD_BUS_ERROR_FILE_NOT_FOUND)); + assert_se(streq(error.message, "yyy -1")); + assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND)); + assert_se(sd_bus_error_get_errno(&error) == ENOENT); + assert_se(sd_bus_error_is_set(&error)); + + assert_se(!sd_bus_error_is_set(&second)); + assert_se(second._need_free == 0); + assert_se(error._need_free > 0); + assert_se(sd_bus_error_copy(&second, &error) == -ENOENT); + assert_se(second._need_free > 0); + assert_se(streq(error.name, second.name)); + assert_se(streq(error.message, second.message)); + assert_se(sd_bus_error_get_errno(&second) == ENOENT); + assert_se(sd_bus_error_has_name(&second, SD_BUS_ERROR_FILE_NOT_FOUND)); + assert_se(sd_bus_error_is_set(&second)); + + sd_bus_error_free(&error); + sd_bus_error_free(&second); + + assert_se(!sd_bus_error_is_set(&second)); + assert_se(const_error._need_free == 0); + assert_se(sd_bus_error_copy(&second, &const_error) == -EEXIST); + assert_se(second._need_free == 0); + assert_se(streq(const_error.name, second.name)); + assert_se(streq(const_error.message, second.message)); + assert_se(sd_bus_error_get_errno(&second) == EEXIST); + assert_se(sd_bus_error_has_name(&second, SD_BUS_ERROR_FILE_EXISTS)); + assert_se(sd_bus_error_is_set(&second)); + sd_bus_error_free(&second); + + assert_se(!sd_bus_error_is_set(&second)); + assert_se(temporarily_const_error._need_free < 0); + assert_se(sd_bus_error_copy(&second, &temporarily_const_error) == -EACCES); + assert_se(second._need_free > 0); + assert_se(streq(temporarily_const_error.name, second.name)); + assert_se(streq(temporarily_const_error.message, second.message)); + assert_se(sd_bus_error_get_errno(&second) == EACCES); + assert_se(sd_bus_error_has_name(&second, SD_BUS_ERROR_ACCESS_DENIED)); + assert_se(sd_bus_error_is_set(&second)); + + assert_se(!sd_bus_error_is_set(&error)); + assert_se(sd_bus_error_set_const(&error, "System.Error.EUCLEAN", "Hallo") == -EUCLEAN); + assert_se(streq(error.name, "System.Error.EUCLEAN")); + assert_se(streq(error.message, "Hallo")); + assert_se(sd_bus_error_has_name(&error, "System.Error.EUCLEAN")); + assert_se(sd_bus_error_get_errno(&error) == EUCLEAN); + assert_se(sd_bus_error_is_set(&error)); + sd_bus_error_free(&error); + + assert_se(!sd_bus_error_is_set(&error)); + assert_se(sd_bus_error_set_errno(&error, EBUSY) == -EBUSY); + assert_se(streq(error.name, "System.Error.EBUSY")); + assert_se(streq(error.message, strerror(EBUSY))); + assert_se(sd_bus_error_has_name(&error, "System.Error.EBUSY")); + assert_se(sd_bus_error_get_errno(&error) == EBUSY); + assert_se(sd_bus_error_is_set(&error)); + sd_bus_error_free(&error); + + assert_se(!sd_bus_error_is_set(&error)); + assert_se(sd_bus_error_set_errnof(&error, EIO, "Waldi %c", 'X') == -EIO); + assert_se(streq(error.name, SD_BUS_ERROR_IO_ERROR)); + assert_se(streq(error.message, "Waldi X")); + assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_IO_ERROR)); + assert_se(sd_bus_error_get_errno(&error) == EIO); + assert_se(sd_bus_error_is_set(&error)); + sd_bus_error_free(&error); + + /* Check with no error */ + assert_se(!sd_bus_error_is_set(&error)); + assert_se(sd_bus_error_set_errnof(&error, 0, "Waldi %c", 'X') == 0); + assert_se(error.name == NULL); + assert_se(error.message == NULL); + assert_se(!sd_bus_error_has_name(&error, SD_BUS_ERROR_IO_ERROR)); + assert_se(sd_bus_error_get_errno(&error) == 0); + assert_se(!sd_bus_error_is_set(&error)); +} + +extern const sd_bus_error_map __start_BUS_ERROR_MAP[]; +extern const sd_bus_error_map __stop_BUS_ERROR_MAP[]; + +static void dump_mapping_table(void) { + const sd_bus_error_map *m; + + printf("----- errno mappings ------\n"); + m = __start_BUS_ERROR_MAP; + while (m < __stop_BUS_ERROR_MAP) { + + if (m->code == BUS_ERROR_MAP_END_MARKER) { + m = ALIGN8_PTR(m+1); + continue; + } + + printf("%s -> %i/%s\n", strna(m->name), m->code, strna(errno_to_name(m->code))); + m++; + } + printf("---------------------------\n"); +} + +static void test_errno_mapping_standard(void) { + assert_se(sd_bus_error_set(NULL, "System.Error.EUCLEAN", NULL) == -EUCLEAN); + assert_se(sd_bus_error_set(NULL, "System.Error.EBUSY", NULL) == -EBUSY); + assert_se(sd_bus_error_set(NULL, "System.Error.EINVAL", NULL) == -EINVAL); + assert_se(sd_bus_error_set(NULL, "System.Error.WHATSIT", NULL) == -EIO); +} + +BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map test_errors[] = { + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error", 5), + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-2", 52), + SD_BUS_ERROR_MAP_END +}; + +BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map test_errors2[] = { + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-3", 33), + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-4", 44), + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-33", 333), + SD_BUS_ERROR_MAP_END +}; + +static const sd_bus_error_map test_errors3[] = { + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-88", 888), + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-99", 999), + SD_BUS_ERROR_MAP_END +}; + +static const sd_bus_error_map test_errors4[] = { + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-77", 777), + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-78", 778), + SD_BUS_ERROR_MAP_END +}; + +static const sd_bus_error_map test_errors_bad1[] = { + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-1", 0), + SD_BUS_ERROR_MAP_END +}; + +static const sd_bus_error_map test_errors_bad2[] = { + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-1", -1), + SD_BUS_ERROR_MAP_END +}; + +static void test_errno_mapping_custom(void) { + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error", NULL) == -5); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-2", NULL) == -52); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-x", NULL) == -EIO); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-33", NULL) == -333); + + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-88", NULL) == -EIO); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-99", NULL) == -EIO); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-77", NULL) == -EIO); + + assert_se(sd_bus_error_add_map(test_errors3) > 0); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-88", NULL) == -888); + assert_se(sd_bus_error_add_map(test_errors4) > 0); + assert_se(sd_bus_error_add_map(test_errors4) == 0); + assert_se(sd_bus_error_add_map(test_errors3) == 0); + + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-99", NULL) == -999); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-77", NULL) == -777); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-78", NULL) == -778); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-2", NULL) == -52); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-y", NULL) == -EIO); + + assert_se(sd_bus_error_set(NULL, BUS_ERROR_NO_SUCH_UNIT, NULL) == -ENOENT); + + assert_se(sd_bus_error_add_map(test_errors_bad1) == -EINVAL); + assert_se(sd_bus_error_add_map(test_errors_bad2) == -EINVAL); +} + +int main(int argc, char *argv[]) { + dump_mapping_table(); + + test_error(); + test_errno_mapping_standard(); + test_errno_mapping_custom(); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-gvariant.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-gvariant.c new file mode 100644 index 0000000000..3c9ec9fef0 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-gvariant.c @@ -0,0 +1,224 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +#ifdef HAVE_GLIB +#include +#endif + +#include + +#include "alloc-util.h" +#include "bus-dump.h" +#include "bus-gvariant.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-util.h" +#include "macro.h" +#include "util.h" + +static void test_bus_gvariant_is_fixed_size(void) { + assert_se(bus_gvariant_is_fixed_size("") > 0); + assert_se(bus_gvariant_is_fixed_size("()") > 0); + assert_se(bus_gvariant_is_fixed_size("y") > 0); + assert_se(bus_gvariant_is_fixed_size("u") > 0); + assert_se(bus_gvariant_is_fixed_size("b") > 0); + assert_se(bus_gvariant_is_fixed_size("n") > 0); + assert_se(bus_gvariant_is_fixed_size("q") > 0); + assert_se(bus_gvariant_is_fixed_size("i") > 0); + assert_se(bus_gvariant_is_fixed_size("t") > 0); + assert_se(bus_gvariant_is_fixed_size("d") > 0); + assert_se(bus_gvariant_is_fixed_size("s") == 0); + assert_se(bus_gvariant_is_fixed_size("o") == 0); + assert_se(bus_gvariant_is_fixed_size("g") == 0); + assert_se(bus_gvariant_is_fixed_size("h") > 0); + assert_se(bus_gvariant_is_fixed_size("ay") == 0); + assert_se(bus_gvariant_is_fixed_size("v") == 0); + assert_se(bus_gvariant_is_fixed_size("(u)") > 0); + assert_se(bus_gvariant_is_fixed_size("(uuuuy)") > 0); + assert_se(bus_gvariant_is_fixed_size("(uusuuy)") == 0); + assert_se(bus_gvariant_is_fixed_size("a{ss}") == 0); + assert_se(bus_gvariant_is_fixed_size("((u)yyy(b(iiii)))") > 0); + assert_se(bus_gvariant_is_fixed_size("((u)yyy(b(iiivi)))") == 0); +} + +static void test_bus_gvariant_get_size(void) { + assert_se(bus_gvariant_get_size("") == 0); + assert_se(bus_gvariant_get_size("()") == 1); + assert_se(bus_gvariant_get_size("y") == 1); + assert_se(bus_gvariant_get_size("u") == 4); + assert_se(bus_gvariant_get_size("b") == 1); + assert_se(bus_gvariant_get_size("n") == 2); + assert_se(bus_gvariant_get_size("q") == 2); + assert_se(bus_gvariant_get_size("i") == 4); + assert_se(bus_gvariant_get_size("t") == 8); + assert_se(bus_gvariant_get_size("d") == 8); + assert_se(bus_gvariant_get_size("s") < 0); + assert_se(bus_gvariant_get_size("o") < 0); + assert_se(bus_gvariant_get_size("g") < 0); + assert_se(bus_gvariant_get_size("h") == 4); + assert_se(bus_gvariant_get_size("ay") < 0); + assert_se(bus_gvariant_get_size("v") < 0); + assert_se(bus_gvariant_get_size("(u)") == 4); + assert_se(bus_gvariant_get_size("(uuuuy)") == 20); + assert_se(bus_gvariant_get_size("(uusuuy)") < 0); + assert_se(bus_gvariant_get_size("a{ss}") < 0); + assert_se(bus_gvariant_get_size("((u)yyy(b(iiii)))") == 28); + assert_se(bus_gvariant_get_size("((u)yyy(b(iiivi)))") < 0); + assert_se(bus_gvariant_get_size("((b)(t))") == 16); + assert_se(bus_gvariant_get_size("((b)(b)(t))") == 16); + assert_se(bus_gvariant_get_size("(bt)") == 16); + assert_se(bus_gvariant_get_size("((t)(b))") == 16); + assert_se(bus_gvariant_get_size("(tb)") == 16); + assert_se(bus_gvariant_get_size("((b)(b))") == 2); + assert_se(bus_gvariant_get_size("((t)(t))") == 16); +} + +static void test_bus_gvariant_get_alignment(void) { + assert_se(bus_gvariant_get_alignment("") == 1); + assert_se(bus_gvariant_get_alignment("()") == 1); + assert_se(bus_gvariant_get_alignment("y") == 1); + assert_se(bus_gvariant_get_alignment("b") == 1); + assert_se(bus_gvariant_get_alignment("u") == 4); + assert_se(bus_gvariant_get_alignment("s") == 1); + assert_se(bus_gvariant_get_alignment("o") == 1); + assert_se(bus_gvariant_get_alignment("g") == 1); + assert_se(bus_gvariant_get_alignment("v") == 8); + assert_se(bus_gvariant_get_alignment("h") == 4); + assert_se(bus_gvariant_get_alignment("i") == 4); + assert_se(bus_gvariant_get_alignment("t") == 8); + assert_se(bus_gvariant_get_alignment("x") == 8); + assert_se(bus_gvariant_get_alignment("q") == 2); + assert_se(bus_gvariant_get_alignment("n") == 2); + assert_se(bus_gvariant_get_alignment("d") == 8); + assert_se(bus_gvariant_get_alignment("ay") == 1); + assert_se(bus_gvariant_get_alignment("as") == 1); + assert_se(bus_gvariant_get_alignment("au") == 4); + assert_se(bus_gvariant_get_alignment("an") == 2); + assert_se(bus_gvariant_get_alignment("ans") == 2); + assert_se(bus_gvariant_get_alignment("ant") == 8); + assert_se(bus_gvariant_get_alignment("(ss)") == 1); + assert_se(bus_gvariant_get_alignment("(ssu)") == 4); + assert_se(bus_gvariant_get_alignment("a(ssu)") == 4); + assert_se(bus_gvariant_get_alignment("(u)") == 4); + assert_se(bus_gvariant_get_alignment("(uuuuy)") == 4); + assert_se(bus_gvariant_get_alignment("(uusuuy)") == 4); + assert_se(bus_gvariant_get_alignment("a{ss}") == 1); + assert_se(bus_gvariant_get_alignment("((u)yyy(b(iiii)))") == 4); + assert_se(bus_gvariant_get_alignment("((u)yyy(b(iiivi)))") == 8); + assert_se(bus_gvariant_get_alignment("((b)(t))") == 8); + assert_se(bus_gvariant_get_alignment("((b)(b)(t))") == 8); + assert_se(bus_gvariant_get_alignment("(bt)") == 8); + assert_se(bus_gvariant_get_alignment("((t)(b))") == 8); + assert_se(bus_gvariant_get_alignment("(tb)") == 8); + assert_se(bus_gvariant_get_alignment("((b)(b))") == 1); + assert_se(bus_gvariant_get_alignment("((t)(t))") == 8); +} + +static void test_marshal(void) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *n = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ void *blob; + size_t sz; + int r; + + r = sd_bus_open_system(&bus); + if (r < 0) + exit(EXIT_TEST_SKIP); + + bus->message_version = 2; /* dirty hack to enable gvariant */ + + assert_se(sd_bus_message_new_method_call(bus, &m, "a.service.name", "/an/object/path/which/is/really/really/long/so/that/we/hit/the/eight/bit/boundary/by/quite/some/margin/to/test/this/stuff/that/it/really/works", "an.interface.name", "AMethodName") >= 0); + + assert_cc(sizeof(struct bus_header) == 16); + + assert_se(sd_bus_message_append(m, + "a(usv)", 3, + 4711, "first-string-parameter", "(st)", "X", (uint64_t) 1111, + 4712, "second-string-parameter", "(a(si))", 2, "Y", 5, "Z", 6, + 4713, "third-string-parameter", "(uu)", 1, 2) >= 0); + + assert_se(bus_message_seal(m, 4711, 0) >= 0); + +#ifdef HAVE_GLIB + { + GVariant *v; + char *t; + +#if !defined(GLIB_VERSION_2_36) + g_type_init(); +#endif + + v = g_variant_new_from_data(G_VARIANT_TYPE("(yyyyuta{tv})"), m->header, sizeof(struct bus_header) + m->fields_size, false, NULL, NULL); + assert_se(g_variant_is_normal_form(v)); + t = g_variant_print(v, TRUE); + printf("%s\n", t); + g_free(t); + g_variant_unref(v); + + v = g_variant_new_from_data(G_VARIANT_TYPE("(a(usv))"), m->body.data, m->user_body_size, false, NULL, NULL); + assert_se(g_variant_is_normal_form(v)); + t = g_variant_print(v, TRUE); + printf("%s\n", t); + g_free(t); + g_variant_unref(v); + } +#endif + + assert_se(bus_message_dump(m, NULL, BUS_MESSAGE_DUMP_WITH_HEADER) >= 0); + + assert_se(bus_message_get_blob(m, &blob, &sz) >= 0); + +#ifdef HAVE_GLIB + { + GVariant *v; + char *t; + + v = g_variant_new_from_data(G_VARIANT_TYPE("(yyyyuta{tv}v)"), blob, sz, false, NULL, NULL); + assert_se(g_variant_is_normal_form(v)); + t = g_variant_print(v, TRUE); + printf("%s\n", t); + g_free(t); + g_variant_unref(v); + } +#endif + + assert_se(bus_message_from_malloc(bus, blob, sz, NULL, 0, NULL, &n) >= 0); + blob = NULL; + + assert_se(bus_message_dump(n, NULL, BUS_MESSAGE_DUMP_WITH_HEADER) >= 0); + + m = sd_bus_message_unref(m); + + assert_se(sd_bus_message_new_method_call(bus, &m, "a.x", "/a/x", "a.x", "Ax") >= 0); + + assert_se(sd_bus_message_append(m, "as", 0) >= 0); + + assert_se(bus_message_seal(m, 4712, 0) >= 0); + assert_se(bus_message_dump(m, NULL, BUS_MESSAGE_DUMP_WITH_HEADER) >= 0); +} + +int main(int argc, char *argv[]) { + + test_bus_gvariant_is_fixed_size(); + test_bus_gvariant_get_size(); + test_bus_gvariant_get_alignment(); + test_marshal(); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-introspect.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-introspect.c new file mode 100644 index 0000000000..4425cfae26 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-introspect.c @@ -0,0 +1,63 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "bus-introspect.h" +#include "log.h" + +static int prop_get(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { + return -EINVAL; +} + +static int prop_set(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { + return -EINVAL; +} + +static const sd_bus_vtable vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("Hello", "ssas", "a(uu)", NULL, 0), + SD_BUS_METHOD("DeprecatedHello", "", "", NULL, SD_BUS_VTABLE_DEPRECATED), + SD_BUS_METHOD("DeprecatedHelloNoReply", "", "", NULL, SD_BUS_VTABLE_DEPRECATED|SD_BUS_VTABLE_METHOD_NO_REPLY), + SD_BUS_SIGNAL("Wowza", "sss", 0), + SD_BUS_SIGNAL("DeprecatedWowza", "ut", SD_BUS_VTABLE_DEPRECATED), + SD_BUS_WRITABLE_PROPERTY("AProperty", "s", prop_get, prop_set, 0, 0), + SD_BUS_PROPERTY("AReadOnlyDeprecatedProperty", "(ut)", prop_get, 0, SD_BUS_VTABLE_DEPRECATED), + SD_BUS_PROPERTY("ChangingProperty", "t", prop_get, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Invalidating", "t", prop_get, 0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + SD_BUS_PROPERTY("Constant", "t", prop_get, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EXPLICIT), + SD_BUS_VTABLE_END +}; + +int main(int argc, char *argv[]) { + struct introspect intro; + + log_set_max_level(LOG_DEBUG); + + assert_se(introspect_begin(&intro, false) >= 0); + + fprintf(intro.f, " \n"); + assert_se(introspect_write_interface(&intro, vtable) >= 0); + fputs(" \n", intro.f); + + fflush(intro.f); + fputs(intro.introspection, stdout); + + introspect_free(&intro); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-kernel-bloom.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-kernel-bloom.c new file mode 100644 index 0000000000..f16e14a310 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-kernel-bloom.c @@ -0,0 +1,141 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "alloc-util.h" +#include "bus-kernel.h" +#include "bus-util.h" +#include "fd-util.h" +#include "log.h" +#include "util.h" + +static int test_match(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + int *found = userdata; + + *found = 1; + + return 0; +} + +static void test_one( + const char *path, + const char *interface, + const char *member, + bool as_list, + const char *arg0, + const char *match, + bool good) { + + _cleanup_close_ int bus_ref = -1; + _cleanup_free_ char *name = NULL, *bus_name = NULL, *address = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + sd_bus *a, *b; + int r, found = 0; + + assert_se(asprintf(&name, "deine-mutter-%u", (unsigned) getpid()) >= 0); + + bus_ref = bus_kernel_create_bus(name, false, &bus_name); + if (bus_ref == -ENOENT) + exit(EXIT_TEST_SKIP); + + assert_se(bus_ref >= 0); + + address = strappend("kernel:path=", bus_name); + assert_se(address); + + r = sd_bus_new(&a); + assert_se(r >= 0); + + r = sd_bus_new(&b); + assert_se(r >= 0); + + r = sd_bus_set_address(a, address); + assert_se(r >= 0); + + r = sd_bus_set_address(b, address); + assert_se(r >= 0); + + r = sd_bus_start(a); + assert_se(r >= 0); + + r = sd_bus_start(b); + assert_se(r >= 0); + + log_debug("match"); + r = sd_bus_add_match(b, NULL, match, test_match, &found); + assert_se(r >= 0); + + log_debug("signal"); + + if (as_list) + r = sd_bus_emit_signal(a, path, interface, member, "as", 1, arg0); + else + r = sd_bus_emit_signal(a, path, interface, member, "s", arg0); + assert_se(r >= 0); + + r = sd_bus_process(b, &m); + assert_se(r >= 0 && good == !!found); + + sd_bus_unref(a); + sd_bus_unref(b); +} + +int main(int argc, char *argv[]) { + log_set_max_level(LOG_DEBUG); + + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo/tuut'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "interface='waldo.com'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "member='Piep'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "member='Pi_ep'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "arg0='foobar'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "arg0='foo_bar'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", true, "foobar", "arg0='foobar'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", true, "foobar", "arg0='foo_bar'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", true, "foobar", "arg0has='foobar'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", true, "foobar", "arg0has='foo_bar'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo',interface='waldo.com',member='Piep',arg0='foobar'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo',interface='waldo.com',member='Piep',arg0='foobar2'", false); + + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo/quux'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo/bar/waldo'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo/bar'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/quux'", false); + test_one("/", "waldo.com", "Piep", false, "foobar", "path_namespace='/'", true); + + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo/'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo/bar/waldo/'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo/'", true); + + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "/foo/bar/waldo", "arg0path='/foo/'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "/foo", "arg0path='/foo'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "/foo", "arg0path='/foo/bar/waldo'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "/foo/", "arg0path='/foo/bar/waldo'", true); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-kernel.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-kernel.c new file mode 100644 index 0000000000..2a5ba60cc9 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-kernel.c @@ -0,0 +1,190 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include "alloc-util.h" +#include "bus-dump.h" +#include "bus-kernel.h" +#include "bus-util.h" +#include "fd-util.h" +#include "log.h" +#include "util.h" + +int main(int argc, char *argv[]) { + _cleanup_close_ int bus_ref = -1; + _cleanup_free_ char *name = NULL, *bus_name = NULL, *address = NULL, *bname = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *ua = NULL, *ub = NULL, *the_string = NULL; + sd_bus *a, *b; + int r, pipe_fds[2]; + const char *nn; + + log_set_max_level(LOG_DEBUG); + + assert_se(asprintf(&name, "deine-mutter-%u", (unsigned) getpid()) >= 0); + + bus_ref = bus_kernel_create_bus(name, false, &bus_name); + if (bus_ref == -ENOENT) + return EXIT_TEST_SKIP; + + assert_se(bus_ref >= 0); + + address = strappend("kernel:path=", bus_name); + assert_se(address); + + r = sd_bus_new(&a); + assert_se(r >= 0); + + r = sd_bus_new(&b); + assert_se(r >= 0); + + r = sd_bus_set_description(a, "a"); + assert_se(r >= 0); + + r = sd_bus_set_address(a, address); + assert_se(r >= 0); + + r = sd_bus_set_address(b, address); + assert_se(r >= 0); + + assert_se(sd_bus_negotiate_timestamp(a, 1) >= 0); + assert_se(sd_bus_negotiate_creds(a, true, _SD_BUS_CREDS_ALL) >= 0); + + assert_se(sd_bus_negotiate_timestamp(b, 0) >= 0); + assert_se(sd_bus_negotiate_creds(b, true, 0) >= 0); + + r = sd_bus_start(a); + assert_se(r >= 0); + + r = sd_bus_start(b); + assert_se(r >= 0); + + assert_se(sd_bus_negotiate_timestamp(b, 1) >= 0); + assert_se(sd_bus_negotiate_creds(b, true, _SD_BUS_CREDS_ALL) >= 0); + + r = sd_bus_get_unique_name(a, &ua); + assert_se(r >= 0); + printf("unique a: %s\n", ua); + + r = sd_bus_get_description(a, &nn); + assert_se(r >= 0); + printf("name of a: %s\n", nn); + + r = sd_bus_get_unique_name(b, &ub); + assert_se(r >= 0); + printf("unique b: %s\n", ub); + + r = sd_bus_get_description(b, &nn); + assert_se(r >= 0); + printf("name of b: %s\n", nn); + + assert_se(bus_kernel_get_bus_name(b, &bname) >= 0); + assert_se(endswith(bname, name)); + + r = sd_bus_call_method(a, "this.doesnt.exist", "/foo", "meh.mah", "muh", &error, NULL, "s", "yayayay"); + assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_SERVICE_UNKNOWN)); + assert_se(r == -EHOSTUNREACH); + + r = sd_bus_add_match(b, NULL, "interface='waldo.com',member='Piep'", NULL, NULL); + assert_se(r >= 0); + + r = sd_bus_emit_signal(a, "/foo/bar/waldo", "waldo.com", "Piep", "sss", "I am a string", "/this/is/a/path", "and.this.a.domain.name"); + assert_se(r >= 0); + + r = sd_bus_try_close(b); + assert_se(r == -EBUSY); + + r = sd_bus_process_priority(b, -10, &m); + assert_se(r == 0); + + r = sd_bus_process(b, &m); + assert_se(r > 0); + assert_se(m); + + bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + assert_se(sd_bus_message_rewind(m, true) >= 0); + + r = sd_bus_message_read(m, "s", &the_string); + assert_se(r >= 0); + assert_se(streq(the_string, "I am a string")); + + sd_bus_message_unref(m); + m = NULL; + + r = sd_bus_request_name(a, "net.x0pointer.foobar", 0); + assert_se(r >= 0); + + r = sd_bus_message_new_method_call(b, &m, "net.x0pointer.foobar", "/a/path", "an.inter.face", "AMethod"); + assert_se(r >= 0); + + assert_se(pipe2(pipe_fds, O_CLOEXEC) >= 0); + + assert_se(write(pipe_fds[1], "x", 1) == 1); + + pipe_fds[1] = safe_close(pipe_fds[1]); + + r = sd_bus_message_append(m, "h", pipe_fds[0]); + assert_se(r >= 0); + + pipe_fds[0] = safe_close(pipe_fds[0]); + + r = sd_bus_send(b, m, NULL); + assert_se(r >= 0); + + for (;;) { + sd_bus_message_unref(m); + m = NULL; + r = sd_bus_process(a, &m); + assert_se(r > 0); + assert_se(m); + + bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + assert_se(sd_bus_message_rewind(m, true) >= 0); + + if (sd_bus_message_is_method_call(m, "an.inter.face", "AMethod")) { + int fd; + char x; + + r = sd_bus_message_read(m, "h", &fd); + assert_se(r >= 0); + + assert_se(read(fd, &x, 1) == 1); + assert_se(x == 'x'); + break; + } + } + + r = sd_bus_release_name(a, "net.x0pointer.foobar"); + assert_se(r >= 0); + + r = sd_bus_release_name(a, "net.x0pointer.foobar"); + assert_se(r == -ESRCH); + + r = sd_bus_try_close(a); + assert_se(r >= 0); + + sd_bus_unref(a); + sd_bus_unref(b); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-marshal.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-marshal.c new file mode 100644 index 0000000000..45db4764a0 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-marshal.c @@ -0,0 +1,432 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 + +#ifdef HAVE_GLIB +#include +#endif + +#ifdef HAVE_DBUS +#include +#endif + +#include + +#include "alloc-util.h" +#include "bus-dump.h" +#include "bus-label.h" +#include "bus-message.h" +#include "bus-util.h" +#include "fd-util.h" +#include "hexdecoct.h" +#include "log.h" +#include "util.h" + +static void test_bus_path_encode_unique(void) { + _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL; + + assert_se(bus_path_encode_unique(NULL, "/foo/bar", "some.sender", "a.suffix", &a) >= 0 && streq_ptr(a, "/foo/bar/some_2esender/a_2esuffix")); + assert_se(bus_path_decode_unique(a, "/foo/bar", &b, &c) > 0 && streq_ptr(b, "some.sender") && streq_ptr(c, "a.suffix")); + assert_se(bus_path_decode_unique(a, "/bar/foo", &d, &d) == 0 && !d); + assert_se(bus_path_decode_unique("/foo/bar/onlyOneSuffix", "/foo/bar", &d, &d) == 0 && !d); + assert_se(bus_path_decode_unique("/foo/bar/_/_", "/foo/bar", &d, &e) > 0 && streq_ptr(d, "") && streq_ptr(e, "")); +} + +static void test_bus_path_encode(void) { + _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *f = NULL; + + assert_se(sd_bus_path_encode("/foo/bar", "waldo", &a) >= 0 && streq(a, "/foo/bar/waldo")); + assert_se(sd_bus_path_decode(a, "/waldo", &b) == 0 && b == NULL); + assert_se(sd_bus_path_decode(a, "/foo/bar", &b) > 0 && streq(b, "waldo")); + + assert_se(sd_bus_path_encode("xxxx", "waldo", &c) < 0); + assert_se(sd_bus_path_encode("/foo/", "waldo", &c) < 0); + + assert_se(sd_bus_path_encode("/foo/bar", "", &c) >= 0 && streq(c, "/foo/bar/_")); + assert_se(sd_bus_path_decode(c, "/foo/bar", &d) > 0 && streq(d, "")); + + assert_se(sd_bus_path_encode("/foo/bar", "foo.bar", &e) >= 0 && streq(e, "/foo/bar/foo_2ebar")); + assert_se(sd_bus_path_decode(e, "/foo/bar", &f) > 0 && streq(f, "foo.bar")); +} + +static void test_bus_path_encode_many(void) { + _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *f = NULL; + + assert_se(sd_bus_path_decode_many("/foo/bar", "/prefix/%", NULL) == 0); + assert_se(sd_bus_path_decode_many("/prefix/bar", "/prefix/%bar", NULL) == 1); + assert_se(sd_bus_path_decode_many("/foo/bar", "/prefix/%/suffix", NULL) == 0); + assert_se(sd_bus_path_decode_many("/prefix/foobar/suffix", "/prefix/%/suffix", &a) == 1 && streq_ptr(a, "foobar")); + assert_se(sd_bus_path_decode_many("/prefix/one_foo_two/mid/three_bar_four/suffix", "/prefix/one_%_two/mid/three_%_four/suffix", &b, &c) == 1 && streq_ptr(b, "foo") && streq_ptr(c, "bar")); + assert_se(sd_bus_path_decode_many("/prefix/one_foo_two/mid/three_bar_four/suffix", "/prefix/one_%_two/mid/three_%_four/suffix", NULL, &d) == 1 && streq_ptr(d, "bar")); + + assert_se(sd_bus_path_decode_many("/foo/bar", "/foo/bar/%", NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/bar%", NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/bar", NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%bar", NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/bar/suffix") == 1); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%%/suffix", NULL, NULL) == 0); /* multiple '%' are treated verbatim */ + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/suffi", NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/suffix", &e) == 1 && streq_ptr(e, "bar")); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/%", NULL, NULL) == 1); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/%/%", NULL, NULL, NULL) == 1); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "%/%/%", NULL, NULL, NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/%", NULL, NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/%/", NULL, NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/", NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%", NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "%", NULL) == 0); + + assert_se(sd_bus_path_encode_many(&f, "/prefix/one_%_two/mid/three_%_four/suffix", "foo", "bar") >= 0 && streq_ptr(f, "/prefix/one_foo_two/mid/three_bar_four/suffix")); +} + +static void test_bus_label_escape_one(const char *a, const char *b) { + _cleanup_free_ char *t = NULL, *x = NULL, *y = NULL; + + assert_se(t = bus_label_escape(a)); + assert_se(streq(t, b)); + + assert_se(x = bus_label_unescape(t)); + assert_se(streq(a, x)); + + assert_se(y = bus_label_unescape(b)); + assert_se(streq(a, y)); +} + +static void test_bus_label_escape(void) { + test_bus_label_escape_one("foo123bar", "foo123bar"); + test_bus_label_escape_one("foo.bar", "foo_2ebar"); + test_bus_label_escape_one("foo_2ebar", "foo_5f2ebar"); + test_bus_label_escape_one("", "_"); + test_bus_label_escape_one("_", "_5f"); + test_bus_label_escape_one("1", "_31"); + test_bus_label_escape_one(":1", "_3a1"); +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *copy = NULL; + int r, boolean; + const char *x, *x2, *y, *z, *a, *b, *c, *d, *a_signature; + uint8_t u, v; + void *buffer = NULL; + size_t sz; + char *h; + const int32_t integer_array[] = { -1, -2, 0, 1, 2 }, *return_array; + char *s; + _cleanup_free_ char *first = NULL, *second = NULL, *third = NULL; + _cleanup_fclose_ FILE *ms = NULL; + size_t first_size = 0, second_size = 0, third_size = 0; + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + double dbl; + uint64_t u64; + + r = sd_bus_default_system(&bus); + if (r < 0) + return EXIT_TEST_SKIP; + + r = sd_bus_message_new_method_call(bus, &m, "foobar.waldo", "/", "foobar.waldo", "Piep"); + assert_se(r >= 0); + + r = sd_bus_message_append(m, ""); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "s", "a string"); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "s", NULL); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "asg", 2, "string #1", "string #2", "sba(tt)ss"); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "sass", "foobar", 5, "foo", "bar", "waldo", "piep", "pap", "after"); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "a{yv}", 2, 3, "s", "foo", 5, "s", "waldo"); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "y(ty)y(yt)y", 8, 777ULL, 7, 9, 77, 7777ULL, 10); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "()"); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "ba(ss)", 255, 3, "aaa", "1", "bbb", "2", "ccc", "3"); + assert_se(r >= 0); + + r = sd_bus_message_open_container(m, 'a', "s"); + assert_se(r >= 0); + + r = sd_bus_message_append_basic(m, 's', "foobar"); + assert_se(r >= 0); + + r = sd_bus_message_append_basic(m, 's', "waldo"); + assert_se(r >= 0); + + r = sd_bus_message_close_container(m); + assert_se(r >= 0); + + r = sd_bus_message_append_string_space(m, 5, &s); + assert_se(r >= 0); + strcpy(s, "hallo"); + + r = sd_bus_message_append_array(m, 'i', integer_array, sizeof(integer_array)); + assert_se(r >= 0); + + r = sd_bus_message_append_array(m, 'u', NULL, 0); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "a(stdo)", 1, "foo", 815ULL, 47.0, "/"); + assert_se(r >= 0); + + r = bus_message_seal(m, 4711, 0); + assert_se(r >= 0); + + bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + ms = open_memstream(&first, &first_size); + bus_message_dump(m, ms, 0); + fflush(ms); + assert_se(!ferror(ms)); + + r = bus_message_get_blob(m, &buffer, &sz); + assert_se(r >= 0); + + h = hexmem(buffer, sz); + assert_se(h); + + log_info("message size = %zu, contents =\n%s", sz, h); + free(h); + +#ifdef HAVE_GLIB + { + GDBusMessage *g; + char *p; + +#if !defined(GLIB_VERSION_2_36) + g_type_init(); +#endif + + g = g_dbus_message_new_from_blob(buffer, sz, 0, NULL); + p = g_dbus_message_print(g, 0); + log_info("%s", p); + g_free(p); + g_object_unref(g); + } +#endif + +#ifdef HAVE_DBUS + { + DBusMessage *w; + DBusError error; + + dbus_error_init(&error); + + w = dbus_message_demarshal(buffer, sz, &error); + if (!w) + log_error("%s", error.message); + else + dbus_message_unref(w); + + dbus_error_free(&error); + } +#endif + + m = sd_bus_message_unref(m); + + r = bus_message_from_malloc(bus, buffer, sz, NULL, 0, NULL, &m); + assert_se(r >= 0); + + bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + fclose(ms); + ms = open_memstream(&second, &second_size); + bus_message_dump(m, ms, 0); + fflush(ms); + assert_se(!ferror(ms)); + assert_se(first_size == second_size); + assert_se(memcmp(first, second, first_size) == 0); + + assert_se(sd_bus_message_rewind(m, true) >= 0); + + r = sd_bus_message_read(m, "ssasg", &x, &x2, 2, &y, &z, &a_signature); + assert_se(r > 0); + assert_se(streq(x, "a string")); + assert_se(streq(x2, "")); + assert_se(streq(y, "string #1")); + assert_se(streq(z, "string #2")); + assert_se(streq(a_signature, "sba(tt)ss")); + + r = sd_bus_message_read(m, "sass", &x, 5, &y, &z, &a, &b, &c, &d); + assert_se(r > 0); + assert_se(streq(x, "foobar")); + assert_se(streq(y, "foo")); + assert_se(streq(z, "bar")); + assert_se(streq(a, "waldo")); + assert_se(streq(b, "piep")); + assert_se(streq(c, "pap")); + assert_se(streq(d, "after")); + + r = sd_bus_message_read(m, "a{yv}", 2, &u, "s", &x, &v, "s", &y); + assert_se(r > 0); + assert_se(u == 3); + assert_se(streq(x, "foo")); + assert_se(v == 5); + assert_se(streq(y, "waldo")); + + r = sd_bus_message_read(m, "y(ty)", &v, &u64, &u); + assert_se(r > 0); + assert_se(v == 8); + assert_se(u64 == 777); + assert_se(u == 7); + + r = sd_bus_message_read(m, "y(yt)", &v, &u, &u64); + assert_se(r > 0); + assert_se(v == 9); + assert_se(u == 77); + assert_se(u64 == 7777); + + r = sd_bus_message_read(m, "y", &v); + assert_se(r > 0); + assert_se(v == 10); + + r = sd_bus_message_read(m, "()"); + assert_se(r > 0); + + r = sd_bus_message_read(m, "ba(ss)", &boolean, 3, &x, &y, &a, &b, &c, &d); + assert_se(r > 0); + assert_se(boolean); + assert_se(streq(x, "aaa")); + assert_se(streq(y, "1")); + assert_se(streq(a, "bbb")); + assert_se(streq(b, "2")); + assert_se(streq(c, "ccc")); + assert_se(streq(d, "3")); + + assert_se(sd_bus_message_verify_type(m, 'a', "s") > 0); + + r = sd_bus_message_read(m, "as", 2, &x, &y); + assert_se(r > 0); + assert_se(streq(x, "foobar")); + assert_se(streq(y, "waldo")); + + r = sd_bus_message_read_basic(m, 's', &s); + assert_se(r > 0); + assert_se(streq(s, "hallo")); + + r = sd_bus_message_read_array(m, 'i', (const void**) &return_array, &sz); + assert_se(r > 0); + assert_se(sz == sizeof(integer_array)); + assert_se(memcmp(integer_array, return_array, sz) == 0); + + r = sd_bus_message_read_array(m, 'u', (const void**) &return_array, &sz); + assert_se(r > 0); + assert_se(sz == 0); + + r = sd_bus_message_read(m, "a(stdo)", 1, &x, &u64, &dbl, &y); + assert_se(r > 0); + assert_se(streq(x, "foo")); + assert_se(u64 == 815ULL); + assert_se(fabs(dbl - 47.0) < 0.1); + assert_se(streq(y, "/")); + + r = sd_bus_message_peek_type(m, NULL, NULL); + assert_se(r == 0); + + r = sd_bus_message_new_method_call(bus, ©, "foobar.waldo", "/", "foobar.waldo", "Piep"); + assert_se(r >= 0); + + r = sd_bus_message_rewind(m, true); + assert_se(r >= 0); + + r = sd_bus_message_copy(copy, m, true); + assert_se(r >= 0); + + r = bus_message_seal(copy, 4712, 0); + assert_se(r >= 0); + + fclose(ms); + ms = open_memstream(&third, &third_size); + bus_message_dump(copy, ms, 0); + fflush(ms); + assert_se(!ferror(ms)); + + printf("<%.*s>\n", (int) first_size, first); + printf("<%.*s>\n", (int) third_size, third); + + assert_se(first_size == third_size); + assert_se(memcmp(first, third, third_size) == 0); + + r = sd_bus_message_rewind(m, true); + assert_se(r >= 0); + + assert_se(sd_bus_message_verify_type(m, 's', NULL) > 0); + + r = sd_bus_message_skip(m, "ssasg"); + assert_se(r > 0); + + assert_se(sd_bus_message_verify_type(m, 's', NULL) > 0); + + r = sd_bus_message_skip(m, "sass"); + assert_se(r >= 0); + + assert_se(sd_bus_message_verify_type(m, 'a', "{yv}") > 0); + + r = sd_bus_message_skip(m, "a{yv}y(ty)y(yt)y()"); + assert_se(r >= 0); + + assert_se(sd_bus_message_verify_type(m, 'b', NULL) > 0); + + r = sd_bus_message_read(m, "b", &boolean); + assert_se(r > 0); + assert_se(boolean); + + r = sd_bus_message_enter_container(m, 0, NULL); + assert_se(r > 0); + + r = sd_bus_message_read(m, "(ss)", &x, &y); + assert_se(r > 0); + + r = sd_bus_message_read(m, "(ss)", &a, &b); + assert_se(r > 0); + + r = sd_bus_message_read(m, "(ss)", &c, &d); + assert_se(r > 0); + + r = sd_bus_message_read(m, "(ss)", &x, &y); + assert_se(r == 0); + + r = sd_bus_message_exit_container(m); + assert_se(r >= 0); + + assert_se(streq(x, "aaa")); + assert_se(streq(y, "1")); + assert_se(streq(a, "bbb")); + assert_se(streq(b, "2")); + assert_se(streq(c, "ccc")); + assert_se(streq(d, "3")); + + test_bus_label_escape(); + test_bus_path_encode(); + test_bus_path_encode_unique(); + test_bus_path_encode_many(); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-match.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-match.c new file mode 100644 index 0000000000..29c4529f95 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-match.c @@ -0,0 +1,159 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "bus-match.h" +#include "bus-message.h" +#include "bus-slot.h" +#include "bus-util.h" +#include "log.h" +#include "macro.h" + +static bool mask[32]; + +static int filter(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + log_info("Ran %u", PTR_TO_UINT(userdata)); + assert_se(PTR_TO_UINT(userdata) < ELEMENTSOF(mask)); + mask[PTR_TO_UINT(userdata)] = true; + return 0; +} + +static bool mask_contains(unsigned a[], unsigned n) { + unsigned i, j; + + for (i = 0; i < ELEMENTSOF(mask); i++) { + bool found = false; + + for (j = 0; j < n; j++) + if (a[j] == i) { + found = true; + break; + } + + if (found != mask[i]) + return false; + } + + return true; +} + +static int match_add(sd_bus_slot *slots, struct bus_match_node *root, const char *match, int value) { + struct bus_match_component *components = NULL; + unsigned n_components = 0; + sd_bus_slot *s; + int r; + + s = slots + value; + zero(*s); + + r = bus_match_parse(match, &components, &n_components); + if (r < 0) + return r; + + s->userdata = INT_TO_PTR(value); + s->match_callback.callback = filter; + + r = bus_match_add(root, components, n_components, &s->match_callback); + bus_match_parse_free(components, n_components); + + return r; +} + +static void test_match_scope(const char *match, enum bus_match_scope scope) { + struct bus_match_component *components = NULL; + unsigned n_components = 0; + + assert_se(bus_match_parse(match, &components, &n_components) >= 0); + assert_se(bus_match_get_scope(components, n_components) == scope); + bus_match_parse_free(components, n_components); +} + +int main(int argc, char *argv[]) { + struct bus_match_node root = { + .type = BUS_MATCH_ROOT, + }; + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + enum bus_match_node_type i; + sd_bus_slot slots[19]; + int r; + + r = sd_bus_open_system(&bus); + if (r < 0) + return EXIT_TEST_SKIP; + + assert_se(match_add(slots, &root, "arg2='wal\\'do',sender='foo',type='signal',interface='bar.x',", 1) >= 0); + assert_se(match_add(slots, &root, "arg2='wal\\'do2',sender='foo',type='signal',interface='bar.x',", 2) >= 0); + assert_se(match_add(slots, &root, "arg3='test',sender='foo',type='signal',interface='bar.x',", 3) >= 0); + assert_se(match_add(slots, &root, "arg3='test',sender='foo',type='method_call',interface='bar.x',", 4) >= 0); + assert_se(match_add(slots, &root, "", 5) >= 0); + assert_se(match_add(slots, &root, "interface='quux.x'", 6) >= 0); + assert_se(match_add(slots, &root, "interface='bar.x'", 7) >= 0); + assert_se(match_add(slots, &root, "member='waldo',path='/foo/bar'", 8) >= 0); + assert_se(match_add(slots, &root, "path='/foo/bar'", 9) >= 0); + assert_se(match_add(slots, &root, "path_namespace='/foo'", 10) >= 0); + assert_se(match_add(slots, &root, "path_namespace='/foo/quux'", 11) >= 0); + assert_se(match_add(slots, &root, "arg1='two'", 12) >= 0); + assert_se(match_add(slots, &root, "member='waldo',arg2path='/prefix/'", 13) >= 0); + assert_se(match_add(slots, &root, "member=waldo,path='/foo/bar',arg3namespace='prefix'", 14) >= 0); + assert_se(match_add(slots, &root, "arg4has='pi'", 15) >= 0); + assert_se(match_add(slots, &root, "arg4has='pa'", 16) >= 0); + assert_se(match_add(slots, &root, "arg4has='po'", 17) >= 0); + assert_se(match_add(slots, &root, "arg4='pi'", 18) >= 0); + + bus_match_dump(&root, 0); + + assert_se(sd_bus_message_new_signal(bus, &m, "/foo/bar", "bar.x", "waldo") >= 0); + assert_se(sd_bus_message_append(m, "ssssas", "one", "two", "/prefix/three", "prefix.four", 3, "pi", "pa", "po") >= 0); + assert_se(bus_message_seal(m, 1, 0) >= 0); + + zero(mask); + assert_se(bus_match_run(NULL, &root, m) == 0); + assert_se(mask_contains((unsigned[]) { 9, 8, 7, 5, 10, 12, 13, 14, 15, 16, 17 }, 11)); + + assert_se(bus_match_remove(&root, &slots[8].match_callback) >= 0); + assert_se(bus_match_remove(&root, &slots[13].match_callback) >= 0); + + bus_match_dump(&root, 0); + + zero(mask); + assert_se(bus_match_run(NULL, &root, m) == 0); + assert_se(mask_contains((unsigned[]) { 9, 5, 10, 12, 14, 7, 15, 16, 17 }, 9)); + + for (i = 0; i < _BUS_MATCH_NODE_TYPE_MAX; i++) { + char buf[32]; + const char *x; + + assert_se(x = bus_match_node_type_to_string(i, buf, sizeof(buf))); + + if (i >= BUS_MATCH_MESSAGE_TYPE) + assert_se(bus_match_node_type_from_string(x, strlen(x)) == i); + } + + bus_match_free(&root); + + test_match_scope("interface='foobar'", BUS_MATCH_GENERIC); + test_match_scope("", BUS_MATCH_GENERIC); + test_match_scope("interface='org.freedesktop.DBus.Local'", BUS_MATCH_LOCAL); + test_match_scope("sender='org.freedesktop.DBus.Local'", BUS_MATCH_LOCAL); + test_match_scope("member='gurke',path='/org/freedesktop/DBus/Local'", BUS_MATCH_LOCAL); + test_match_scope("arg2='piep',sender='org.freedesktop.DBus',member='waldo'", BUS_MATCH_DRIVER); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-objects.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-objects.c new file mode 100644 index 0000000000..e9bb655665 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-objects.c @@ -0,0 +1,555 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include + +#include "alloc-util.h" +#include "bus-dump.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-util.h" +#include "log.h" +#include "macro.h" +#include "strv.h" +#include "util.h" + +struct context { + int fds[2]; + bool quit; + char *something; + char *automatic_string_property; + uint32_t automatic_integer_property; +}; + +static int something_handler(sd_bus_message *m, void *userdata, sd_bus_error *error) { + struct context *c = userdata; + const char *s; + char *n = NULL; + int r; + + r = sd_bus_message_read(m, "s", &s); + assert_se(r > 0); + + n = strjoin("<<<", s, ">>>", NULL); + assert_se(n); + + free(c->something); + c->something = n; + + log_info("AlterSomething() called, got %s, returning %s", s, n); + + /* This should fail, since the return type doesn't match */ + assert_se(sd_bus_reply_method_return(m, "u", 4711) == -ENOMSG); + + r = sd_bus_reply_method_return(m, "s", n); + assert_se(r >= 0); + + return 1; +} + +static int exit_handler(sd_bus_message *m, void *userdata, sd_bus_error *error) { + struct context *c = userdata; + int r; + + c->quit = true; + + log_info("Exit called"); + + r = sd_bus_reply_method_return(m, ""); + assert_se(r >= 0); + + return 1; +} + +static int get_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { + struct context *c = userdata; + int r; + + log_info("property get for %s called, returning \"%s\".", property, c->something); + + r = sd_bus_message_append(reply, "s", c->something); + assert_se(r >= 0); + + return 1; +} + +static int set_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *value, void *userdata, sd_bus_error *error) { + struct context *c = userdata; + const char *s; + char *n; + int r; + + log_info("property set for %s called", property); + + r = sd_bus_message_read(value, "s", &s); + assert_se(r >= 0); + + n = strdup(s); + assert_se(n); + + free(c->something); + c->something = n; + + return 1; +} + +static int value_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *s = NULL; + const char *x; + int r; + + assert_se(asprintf(&s, "object %p, path %s", userdata, path) >= 0); + r = sd_bus_message_append(reply, "s", s); + assert_se(r >= 0); + + assert_se(x = startswith(path, "/value/")); + + assert_se(PTR_TO_UINT(userdata) == 30); + + return 1; +} + +static int notify_test(sd_bus_message *m, void *userdata, sd_bus_error *error) { + int r; + + assert_se(sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), m->path, "org.freedesktop.systemd.ValueTest", "Value", NULL) >= 0); + + r = sd_bus_reply_method_return(m, NULL); + assert_se(r >= 0); + + return 1; +} + +static int notify_test2(sd_bus_message *m, void *userdata, sd_bus_error *error) { + int r; + + assert_se(sd_bus_emit_properties_changed_strv(sd_bus_message_get_bus(m), m->path, "org.freedesktop.systemd.ValueTest", NULL) >= 0); + + r = sd_bus_reply_method_return(m, NULL); + assert_se(r >= 0); + + return 1; +} + +static int emit_interfaces_added(sd_bus_message *m, void *userdata, sd_bus_error *error) { + int r; + + assert_se(sd_bus_emit_interfaces_added(sd_bus_message_get_bus(m), "/value/a/x", "org.freedesktop.systemd.ValueTest", NULL) >= 0); + + r = sd_bus_reply_method_return(m, NULL); + assert_se(r >= 0); + + return 1; +} + +static int emit_interfaces_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { + int r; + + assert_se(sd_bus_emit_interfaces_removed(sd_bus_message_get_bus(m), "/value/a/x", "org.freedesktop.systemd.ValueTest", NULL) >= 0); + + r = sd_bus_reply_method_return(m, NULL); + assert_se(r >= 0); + + return 1; +} + +static int emit_object_added(sd_bus_message *m, void *userdata, sd_bus_error *error) { + int r; + + assert_se(sd_bus_emit_object_added(sd_bus_message_get_bus(m), "/value/a/x") >= 0); + + r = sd_bus_reply_method_return(m, NULL); + assert_se(r >= 0); + + return 1; +} + +static int emit_object_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { + int r; + + assert_se(sd_bus_emit_object_removed(sd_bus_message_get_bus(m), "/value/a/x") >= 0); + + r = sd_bus_reply_method_return(m, NULL); + assert_se(r >= 0); + + return 1; +} + +static const sd_bus_vtable vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("AlterSomething", "s", "s", something_handler, 0), + SD_BUS_METHOD("Exit", "", "", exit_handler, 0), + SD_BUS_WRITABLE_PROPERTY("Something", "s", get_handler, set_handler, 0, 0), + SD_BUS_WRITABLE_PROPERTY("AutomaticStringProperty", "s", NULL, NULL, offsetof(struct context, automatic_string_property), 0), + SD_BUS_WRITABLE_PROPERTY("AutomaticIntegerProperty", "u", NULL, NULL, offsetof(struct context, automatic_integer_property), 0), + SD_BUS_METHOD("NoOperation", NULL, NULL, NULL, 0), + SD_BUS_METHOD("EmitInterfacesAdded", NULL, NULL, emit_interfaces_added, 0), + SD_BUS_METHOD("EmitInterfacesRemoved", NULL, NULL, emit_interfaces_removed, 0), + SD_BUS_METHOD("EmitObjectAdded", NULL, NULL, emit_object_added, 0), + SD_BUS_METHOD("EmitObjectRemoved", NULL, NULL, emit_object_removed, 0), + SD_BUS_VTABLE_END +}; + +static const sd_bus_vtable vtable2[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("NotifyTest", "", "", notify_test, 0), + SD_BUS_METHOD("NotifyTest2", "", "", notify_test2, 0), + SD_BUS_PROPERTY("Value", "s", value_handler, 10, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Value2", "s", value_handler, 10, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + SD_BUS_PROPERTY("Value3", "s", value_handler, 10, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Value4", "s", value_handler, 10, 0), + SD_BUS_PROPERTY("AnExplicitProperty", "s", NULL, offsetof(struct context, something), SD_BUS_VTABLE_PROPERTY_EXPLICIT|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + SD_BUS_VTABLE_END +}; + +static int enumerator_callback(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + + if (object_path_startswith("/value", path)) + assert_se(*nodes = strv_new("/value/a", "/value/b", "/value/c", NULL)); + + return 1; +} + +static int enumerator2_callback(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + + if (object_path_startswith("/value/a", path)) + assert_se(*nodes = strv_new("/value/a/x", "/value/a/y", "/value/a/z", NULL)); + + return 1; +} + +static void *server(void *p) { + struct context *c = p; + sd_bus *bus = NULL; + sd_id128_t id; + int r; + + c->quit = false; + + assert_se(sd_id128_randomize(&id) >= 0); + + assert_se(sd_bus_new(&bus) >= 0); + assert_se(sd_bus_set_fd(bus, c->fds[0], c->fds[0]) >= 0); + assert_se(sd_bus_set_server(bus, 1, id) >= 0); + + assert_se(sd_bus_add_object_vtable(bus, NULL, "/foo", "org.freedesktop.systemd.test", vtable, c) >= 0); + assert_se(sd_bus_add_object_vtable(bus, NULL, "/foo", "org.freedesktop.systemd.test2", vtable, c) >= 0); + assert_se(sd_bus_add_fallback_vtable(bus, NULL, "/value", "org.freedesktop.systemd.ValueTest", vtable2, NULL, UINT_TO_PTR(20)) >= 0); + assert_se(sd_bus_add_node_enumerator(bus, NULL, "/value", enumerator_callback, NULL) >= 0); + assert_se(sd_bus_add_node_enumerator(bus, NULL, "/value/a", enumerator2_callback, NULL) >= 0); + assert_se(sd_bus_add_object_manager(bus, NULL, "/value") >= 0); + assert_se(sd_bus_add_object_manager(bus, NULL, "/value/a") >= 0); + + assert_se(sd_bus_start(bus) >= 0); + + log_error("Entering event loop on server"); + + while (!c->quit) { + log_error("Loop!"); + + r = sd_bus_process(bus, NULL); + if (r < 0) { + log_error_errno(r, "Failed to process requests: %m"); + goto fail; + } + + if (r == 0) { + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) { + log_error_errno(r, "Failed to wait: %m"); + goto fail; + } + + continue; + } + } + + r = 0; + +fail: + if (bus) { + sd_bus_flush(bus); + sd_bus_unref(bus); + } + + return INT_TO_PTR(r); +} + +static int client(struct context *c) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *s; + int r; + + assert_se(sd_bus_new(&bus) >= 0); + assert_se(sd_bus_set_fd(bus, c->fds[1], c->fds[1]) >= 0); + assert_se(sd_bus_start(bus) >= 0); + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "NoOperation", &error, NULL, NULL); + assert_se(r >= 0); + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AlterSomething", &error, &reply, "s", "hallo"); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + assert_se(streq(s, "<<>>")); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Doesntexist", &error, &reply, ""); + assert_se(r < 0); + assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)); + + sd_bus_error_free(&error); + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AlterSomething", &error, &reply, "as", 1, "hallo"); + assert_se(r < 0); + assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_INVALID_ARGS)); + + sd_bus_error_free(&error); + + r = sd_bus_get_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Something", &error, &reply, "s"); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + assert_se(streq(s, "<<>>")); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_set_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Something", &error, "s", "test"); + assert_se(r >= 0); + + r = sd_bus_get_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Something", &error, &reply, "s"); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + assert_se(streq(s, "test")); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_set_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AutomaticIntegerProperty", &error, "u", 815); + assert_se(r >= 0); + + assert_se(c->automatic_integer_property == 815); + + r = sd_bus_set_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AutomaticStringProperty", &error, "s", "Du Dödel, Du!"); + assert_se(r >= 0); + + assert_se(streq(c->automatic_string_property, "Du Dödel, Du!")); + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + fputs(s, stdout); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_get_property(bus, "org.freedesktop.systemd.test", "/value/xuzz", "org.freedesktop.systemd.ValueTest", "Value", &error, &reply, "s"); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + log_info("read %s", s); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + fputs(s, stdout); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + fputs(s, stdout); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + fputs(s, stdout); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.DBus.Properties", "GetAll", &error, &reply, "s", ""); + assert_se(r >= 0); + + bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.DBus.Properties", "GetAll", &error, &reply, "s", "org.freedesktop.systemd.ValueTest2"); + assert_se(r < 0); + assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_INTERFACE)); + sd_bus_error_free(&error); + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects", &error, &reply, ""); + assert_se(r < 0); + assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)); + sd_bus_error_free(&error); + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects", &error, &reply, ""); + assert_se(r >= 0); + + bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.systemd.ValueTest", "NotifyTest", &error, NULL, ""); + assert_se(r >= 0); + + r = sd_bus_process(bus, &reply); + assert_se(r > 0); + + assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.Properties", "PropertiesChanged")); + bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.systemd.ValueTest", "NotifyTest2", &error, NULL, ""); + assert_se(r >= 0); + + r = sd_bus_process(bus, &reply); + assert_se(r > 0); + + assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.Properties", "PropertiesChanged")); + bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitInterfacesAdded", &error, NULL, ""); + assert_se(r >= 0); + + r = sd_bus_process(bus, &reply); + assert_se(r > 0); + + assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded")); + bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitInterfacesRemoved", &error, NULL, ""); + assert_se(r >= 0); + + r = sd_bus_process(bus, &reply); + assert_se(r > 0); + + assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved")); + bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitObjectAdded", &error, NULL, ""); + assert_se(r >= 0); + + r = sd_bus_process(bus, &reply); + assert_se(r > 0); + + assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded")); + bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitObjectRemoved", &error, NULL, ""); + assert_se(r >= 0); + + r = sd_bus_process(bus, &reply); + assert_se(r > 0); + + assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved")); + bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Exit", &error, NULL, ""); + assert_se(r >= 0); + + sd_bus_flush(bus); + + return 0; +} + +int main(int argc, char *argv[]) { + struct context c = {}; + pthread_t s; + void *p; + int r, q; + + zero(c); + + c.automatic_integer_property = 4711; + assert_se(c.automatic_string_property = strdup("dudeldu")); + + assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, c.fds) >= 0); + + r = pthread_create(&s, NULL, server, &c); + if (r != 0) + return -r; + + r = client(&c); + + q = pthread_join(s, &p); + if (q != 0) + return -q; + + if (r < 0) + return r; + + if (PTR_TO_INT(p) < 0) + return PTR_TO_INT(p); + + free(c.something); + free(c.automatic_string_property); + + return EXIT_SUCCESS; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-server.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-server.c new file mode 100644 index 0000000000..190410674b --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-server.c @@ -0,0 +1,216 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include + +#include "bus-internal.h" +#include "bus-util.h" +#include "log.h" +#include "macro.h" +#include "util.h" + +struct context { + int fds[2]; + + bool client_negotiate_unix_fds; + bool server_negotiate_unix_fds; + + bool client_anonymous_auth; + bool server_anonymous_auth; +}; + +static void *server(void *p) { + struct context *c = p; + sd_bus *bus = NULL; + sd_id128_t id; + bool quit = false; + int r; + + assert_se(sd_id128_randomize(&id) >= 0); + + assert_se(sd_bus_new(&bus) >= 0); + assert_se(sd_bus_set_fd(bus, c->fds[0], c->fds[0]) >= 0); + assert_se(sd_bus_set_server(bus, 1, id) >= 0); + assert_se(sd_bus_set_anonymous(bus, c->server_anonymous_auth) >= 0); + assert_se(sd_bus_negotiate_fds(bus, c->server_negotiate_unix_fds) >= 0); + assert_se(sd_bus_start(bus) >= 0); + + while (!quit) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + + r = sd_bus_process(bus, &m); + if (r < 0) { + log_error_errno(r, "Failed to process requests: %m"); + goto fail; + } + + if (r == 0) { + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) { + log_error_errno(r, "Failed to wait: %m"); + goto fail; + } + + continue; + } + + if (!m) + continue; + + log_info("Got message! member=%s", strna(sd_bus_message_get_member(m))); + + if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "Exit")) { + + assert_se((sd_bus_can_send(bus, 'h') >= 1) == (c->server_negotiate_unix_fds && c->client_negotiate_unix_fds)); + + r = sd_bus_message_new_method_return(m, &reply); + if (r < 0) { + log_error_errno(r, "Failed to allocate return: %m"); + goto fail; + } + + quit = true; + + } else if (sd_bus_message_is_method_call(m, NULL, NULL)) { + r = sd_bus_message_new_method_error( + m, + &reply, + &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_UNKNOWN_METHOD, "Unknown method.")); + if (r < 0) { + log_error_errno(r, "Failed to allocate return: %m"); + goto fail; + } + } + + if (reply) { + r = sd_bus_send(bus, reply, NULL); + if (r < 0) { + log_error_errno(r, "Failed to send reply: %m"); + goto fail; + } + } + } + + r = 0; + +fail: + if (bus) { + sd_bus_flush(bus); + sd_bus_unref(bus); + } + + return INT_TO_PTR(r); +} + +static int client(struct context *c) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert_se(sd_bus_new(&bus) >= 0); + assert_se(sd_bus_set_fd(bus, c->fds[1], c->fds[1]) >= 0); + assert_se(sd_bus_negotiate_fds(bus, c->client_negotiate_unix_fds) >= 0); + assert_se(sd_bus_set_anonymous(bus, c->client_anonymous_auth) >= 0); + assert_se(sd_bus_start(bus) >= 0); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "Exit"); + if (r < 0) + return log_error_errno(r, "Failed to allocate method call: %m"); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int test_one(bool client_negotiate_unix_fds, bool server_negotiate_unix_fds, + bool client_anonymous_auth, bool server_anonymous_auth) { + + struct context c; + pthread_t s; + void *p; + int r, q; + + zero(c); + + assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, c.fds) >= 0); + + c.client_negotiate_unix_fds = client_negotiate_unix_fds; + c.server_negotiate_unix_fds = server_negotiate_unix_fds; + c.client_anonymous_auth = client_anonymous_auth; + c.server_anonymous_auth = server_anonymous_auth; + + r = pthread_create(&s, NULL, server, &c); + if (r != 0) + return -r; + + r = client(&c); + + q = pthread_join(s, &p); + if (q != 0) + return -q; + + if (r < 0) + return r; + + if (PTR_TO_INT(p) < 0) + return PTR_TO_INT(p); + + return 0; +} + +int main(int argc, char *argv[]) { + int r; + + r = test_one(true, true, false, false); + assert_se(r >= 0); + + r = test_one(true, false, false, false); + assert_se(r >= 0); + + r = test_one(false, true, false, false); + assert_se(r >= 0); + + r = test_one(false, false, false, false); + assert_se(r >= 0); + + r = test_one(true, true, true, true); + assert_se(r >= 0); + + r = test_one(true, true, false, true); + assert_se(r >= 0); + + r = test_one(true, true, true, false); + assert_se(r == -EPERM); + + return EXIT_SUCCESS; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-signature.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-signature.c new file mode 100644 index 0000000000..4f4fd093bf --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-signature.c @@ -0,0 +1,164 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "bus-internal.h" +#include "bus-signature.h" +#include "log.h" +#include "string-util.h" + +int main(int argc, char *argv[]) { + char prefix[256]; + int r; + + assert_se(signature_is_single("y", false)); + assert_se(signature_is_single("u", false)); + assert_se(signature_is_single("v", false)); + assert_se(signature_is_single("as", false)); + assert_se(signature_is_single("(ss)", false)); + assert_se(signature_is_single("()", false)); + assert_se(signature_is_single("(()()()()())", false)); + assert_se(signature_is_single("(((())))", false)); + assert_se(signature_is_single("((((s))))", false)); + assert_se(signature_is_single("{ss}", true)); + assert_se(signature_is_single("a{ss}", false)); + assert_se(!signature_is_single("uu", false)); + assert_se(!signature_is_single("", false)); + assert_se(!signature_is_single("(", false)); + assert_se(!signature_is_single(")", false)); + assert_se(!signature_is_single("())", false)); + assert_se(!signature_is_single("((())", false)); + assert_se(!signature_is_single("{)", false)); + assert_se(!signature_is_single("{}", true)); + assert_se(!signature_is_single("{sss}", true)); + assert_se(!signature_is_single("{s}", true)); + assert_se(!signature_is_single("{ss}", false)); + assert_se(!signature_is_single("{ass}", true)); + assert_se(!signature_is_single("a}", true)); + + assert_se(signature_is_pair("yy")); + assert_se(signature_is_pair("ss")); + assert_se(signature_is_pair("sas")); + assert_se(signature_is_pair("sv")); + assert_se(signature_is_pair("sa(vs)")); + assert_se(!signature_is_pair("")); + assert_se(!signature_is_pair("va")); + assert_se(!signature_is_pair("sss")); + assert_se(!signature_is_pair("{s}ss")); + + assert_se(signature_is_valid("ssa{ss}sssub", true)); + assert_se(signature_is_valid("ssa{ss}sssub", false)); + assert_se(signature_is_valid("{ss}", true)); + assert_se(!signature_is_valid("{ss}", false)); + assert_se(signature_is_valid("", true)); + assert_se(signature_is_valid("", false)); + + assert_se(signature_is_valid("sssusa(uuubbba(uu)uuuu)a{u(uuuvas)}", false)); + + assert_se(!signature_is_valid("a", false)); + assert_se(signature_is_valid("as", false)); + assert_se(signature_is_valid("aas", false)); + assert_se(signature_is_valid("aaas", false)); + assert_se(signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad", false)); + assert_se(signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaas", false)); + assert_se(!signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaau", false)); + + assert_se(signature_is_valid("(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))", false)); + assert_se(!signature_is_valid("((((((((((((((((((((((((((((((((()))))))))))))))))))))))))))))))))", false)); + + assert_se(namespace_complex_pattern("", "")); + assert_se(namespace_complex_pattern("foobar", "foobar")); + assert_se(namespace_complex_pattern("foobar.waldo", "foobar.waldo")); + assert_se(namespace_complex_pattern("foobar.", "foobar.waldo")); + assert_se(namespace_complex_pattern("foobar.waldo", "foobar.")); + assert_se(!namespace_complex_pattern("foobar.waldo", "foobar")); + assert_se(!namespace_complex_pattern("foobar", "foobar.waldo")); + assert_se(!namespace_complex_pattern("", "foo")); + assert_se(!namespace_complex_pattern("foo", "")); + assert_se(!namespace_complex_pattern("foo.", "")); + + assert_se(path_complex_pattern("", "")); + assert_se(!path_complex_pattern("", "/")); + assert_se(!path_complex_pattern("/", "")); + assert_se(path_complex_pattern("/", "/")); + assert_se(path_complex_pattern("/foobar/", "/")); + assert_se(!path_complex_pattern("/foobar/", "/foobar")); + assert_se(path_complex_pattern("/foobar", "/foobar")); + assert_se(!path_complex_pattern("/foobar", "/foobar/")); + assert_se(!path_complex_pattern("/foobar", "/foobar/waldo")); + assert_se(path_complex_pattern("/foobar/", "/foobar/waldo")); + assert_se(path_complex_pattern("/foobar/waldo", "/foobar/")); + + assert_se(path_simple_pattern("/foo/", "/foo/bar/waldo")); + + assert_se(namespace_simple_pattern("", "")); + assert_se(namespace_simple_pattern("", ".foobar")); + assert_se(namespace_simple_pattern("foobar", "foobar")); + assert_se(namespace_simple_pattern("foobar.waldo", "foobar.waldo")); + assert_se(namespace_simple_pattern("foobar", "foobar.waldo")); + assert_se(!namespace_simple_pattern("foobar.waldo", "foobar")); + assert_se(!namespace_simple_pattern("", "foo")); + assert_se(!namespace_simple_pattern("foo", "")); + assert_se(namespace_simple_pattern("foo.", "foo.bar.waldo")); + + assert_se(streq(object_path_startswith("/foo/bar", "/foo"), "bar")); + assert_se(streq(object_path_startswith("/foo", "/foo"), "")); + assert_se(streq(object_path_startswith("/foo", "/"), "foo")); + assert_se(streq(object_path_startswith("/", "/"), "")); + assert_se(!object_path_startswith("/foo", "/bar")); + assert_se(!object_path_startswith("/", "/bar")); + assert_se(!object_path_startswith("/foo", "")); + + assert_se(object_path_is_valid("/foo/bar")); + assert_se(object_path_is_valid("/foo")); + assert_se(object_path_is_valid("/")); + assert_se(object_path_is_valid("/foo5")); + assert_se(object_path_is_valid("/foo_5")); + assert_se(!object_path_is_valid("")); + assert_se(!object_path_is_valid("/foo/")); + assert_se(!object_path_is_valid("//")); + assert_se(!object_path_is_valid("//foo")); + assert_se(!object_path_is_valid("/foo//bar")); + assert_se(!object_path_is_valid("/foo/aaaäöä")); + + OBJECT_PATH_FOREACH_PREFIX(prefix, "/") { + log_info("<%s>", prefix); + assert_not_reached("???"); + } + + r = 0; + OBJECT_PATH_FOREACH_PREFIX(prefix, "/xxx") { + log_info("<%s>", prefix); + assert_se(streq(prefix, "/")); + assert_se(r == 0); + r++; + } + assert_se(r == 1); + + r = 0; + OBJECT_PATH_FOREACH_PREFIX(prefix, "/xxx/yyy/zzz") { + log_info("<%s>", prefix); + assert_se(r != 0 || streq(prefix, "/xxx/yyy")); + assert_se(r != 1 || streq(prefix, "/xxx")); + assert_se(r != 2 || streq(prefix, "/")); + r++; + } + assert_se(r == 3); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-zero-copy.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-zero-copy.c new file mode 100644 index 0000000000..9e20d67670 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-zero-copy.c @@ -0,0 +1,210 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include "alloc-util.h" +#include "bus-dump.h" +#include "bus-kernel.h" +#include "bus-message.h" +#include "fd-util.h" +#include "log.h" +#include "memfd-util.h" +#include "string-util.h" +#include "util.h" + +#define FIRST_ARRAY 17 +#define SECOND_ARRAY 33 + +#define STRING_SIZE 123 + +int main(int argc, char *argv[]) { + _cleanup_free_ char *name = NULL, *bus_name = NULL, *address = NULL; + const char *unique; + uint8_t *p; + sd_bus *a, *b; + int r, bus_ref; + sd_bus_message *m; + int f; + uint64_t sz; + uint32_t u32; + size_t i, l; + char *s; + _cleanup_close_ int sfd = -1; + + log_set_max_level(LOG_DEBUG); + + assert_se(asprintf(&name, "deine-mutter-%u", (unsigned) getpid()) >= 0); + + bus_ref = bus_kernel_create_bus(name, false, &bus_name); + if (bus_ref == -ENOENT) + return EXIT_TEST_SKIP; + + assert_se(bus_ref >= 0); + + address = strappend("kernel:path=", bus_name); + assert_se(address); + + r = sd_bus_new(&a); + assert_se(r >= 0); + + r = sd_bus_new(&b); + assert_se(r >= 0); + + r = sd_bus_set_address(a, address); + assert_se(r >= 0); + + r = sd_bus_set_address(b, address); + assert_se(r >= 0); + + r = sd_bus_start(a); + assert_se(r >= 0); + + r = sd_bus_start(b); + assert_se(r >= 0); + + r = sd_bus_get_unique_name(a, &unique); + assert_se(r >= 0); + + r = sd_bus_message_new_method_call(b, &m, unique, "/a/path", "an.inter.face", "AMethod"); + assert_se(r >= 0); + + r = sd_bus_message_open_container(m, 'r', "aysay"); + assert_se(r >= 0); + + r = sd_bus_message_append_array_space(m, 'y', FIRST_ARRAY, (void**) &p); + assert_se(r >= 0); + + p[0] = '<'; + memset(p+1, 'L', FIRST_ARRAY-2); + p[FIRST_ARRAY-1] = '>'; + + f = memfd_new_and_map(NULL, STRING_SIZE, (void**) &s); + assert_se(f >= 0); + + s[0] = '<'; + for (i = 1; i < STRING_SIZE-2; i++) + s[i] = '0' + (i % 10); + s[STRING_SIZE-2] = '>'; + s[STRING_SIZE-1] = 0; + munmap(s, STRING_SIZE); + + r = memfd_get_size(f, &sz); + assert_se(r >= 0); + assert_se(sz == STRING_SIZE); + + r = sd_bus_message_append_string_memfd(m, f, 0, (uint64_t) -1); + assert_se(r >= 0); + + close(f); + + f = memfd_new_and_map(NULL, SECOND_ARRAY, (void**) &p); + assert_se(f >= 0); + + p[0] = '<'; + memset(p+1, 'P', SECOND_ARRAY-2); + p[SECOND_ARRAY-1] = '>'; + munmap(p, SECOND_ARRAY); + + r = memfd_get_size(f, &sz); + assert_se(r >= 0); + assert_se(sz == SECOND_ARRAY); + + r = sd_bus_message_append_array_memfd(m, 'y', f, 0, (uint64_t) -1); + assert_se(r >= 0); + + close(f); + + r = sd_bus_message_close_container(m); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "u", 4711); + assert_se(r >= 0); + + assert_se((sfd = memfd_new_and_map(NULL, 6, (void**) &p)) >= 0); + memcpy(p, "abcd\0", 6); + munmap(p, 6); + assert_se(sd_bus_message_append_string_memfd(m, sfd, 1, 4) >= 0); + + r = bus_message_seal(m, 55, 99*USEC_PER_SEC); + assert_se(r >= 0); + + bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + r = sd_bus_send(b, m, NULL); + assert_se(r >= 0); + + sd_bus_message_unref(m); + + r = sd_bus_process(a, &m); + assert_se(r > 0); + + bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + sd_bus_message_rewind(m, true); + + r = sd_bus_message_enter_container(m, 'r', "aysay"); + assert_se(r > 0); + + r = sd_bus_message_read_array(m, 'y', (const void**) &p, &l); + assert_se(r > 0); + assert_se(l == FIRST_ARRAY); + + assert_se(p[0] == '<'); + for (i = 1; i < l-1; i++) + assert_se(p[i] == 'L'); + assert_se(p[l-1] == '>'); + + r = sd_bus_message_read(m, "s", &s); + assert_se(r > 0); + + assert_se(s[0] == '<'); + for (i = 1; i < STRING_SIZE-2; i++) + assert_se(s[i] == (char) ('0' + (i % 10))); + assert_se(s[STRING_SIZE-2] == '>'); + assert_se(s[STRING_SIZE-1] == 0); + + r = sd_bus_message_read_array(m, 'y', (const void**) &p, &l); + assert_se(r > 0); + assert_se(l == SECOND_ARRAY); + + assert_se(p[0] == '<'); + for (i = 1; i < l-1; i++) + assert_se(p[i] == 'P'); + assert_se(p[l-1] == '>'); + + r = sd_bus_message_exit_container(m); + assert_se(r > 0); + + r = sd_bus_message_read(m, "u", &u32); + assert_se(r > 0); + assert_se(u32 == 4711); + + r = sd_bus_message_read(m, "s", &s); + assert_se(r > 0); + assert_se(streq_ptr(s, "bcd")); + + sd_bus_message_unref(m); + + sd_bus_unref(a); + sd_bus_unref(b); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-daemon/sd-daemon.c b/src/libsystemd/libsystemd-internal/sd-daemon/sd-daemon.c new file mode 100644 index 0000000000..fa92199c43 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-daemon/sd-daemon.c @@ -0,0 +1,622 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "socket-util.h" +#include "strv.h" +#include "util.h" + +#define SNDBUF_SIZE (8*1024*1024) + +static void unsetenv_all(bool unset_environment) { + + if (!unset_environment) + return; + + unsetenv("LISTEN_PID"); + unsetenv("LISTEN_FDS"); + unsetenv("LISTEN_FDNAMES"); +} + +_public_ int sd_listen_fds(int unset_environment) { + const char *e; + int n, r, fd; + pid_t pid; + + e = getenv("LISTEN_PID"); + if (!e) { + r = 0; + goto finish; + } + + r = parse_pid(e, &pid); + if (r < 0) + goto finish; + + /* Is this for us? */ + if (getpid() != pid) { + r = 0; + goto finish; + } + + e = getenv("LISTEN_FDS"); + if (!e) { + r = 0; + goto finish; + } + + r = safe_atoi(e, &n); + if (r < 0) + goto finish; + + assert_cc(SD_LISTEN_FDS_START < INT_MAX); + if (n <= 0 || n > INT_MAX - SD_LISTEN_FDS_START) { + r = -EINVAL; + goto finish; + } + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) { + r = fd_cloexec(fd, true); + if (r < 0) + goto finish; + } + + r = n; + +finish: + unsetenv_all(unset_environment); + return r; +} + +_public_ int sd_listen_fds_with_names(int unset_environment, char ***names) { + _cleanup_strv_free_ char **l = NULL; + bool have_names; + int n_names = 0, n_fds; + const char *e; + int r; + + if (!names) + return sd_listen_fds(unset_environment); + + e = getenv("LISTEN_FDNAMES"); + if (e) { + n_names = strv_split_extract(&l, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (n_names < 0) { + unsetenv_all(unset_environment); + return n_names; + } + + have_names = true; + } else + have_names = false; + + n_fds = sd_listen_fds(unset_environment); + if (n_fds <= 0) + return n_fds; + + if (have_names) { + if (n_names != n_fds) + return -EINVAL; + } else { + r = strv_extend_n(&l, "unknown", n_fds); + if (r < 0) + return r; + } + + *names = l; + l = NULL; + + return n_fds; +} + +_public_ int sd_is_fifo(int fd, const char *path) { + struct stat st_fd; + + assert_return(fd >= 0, -EBADF); + + if (fstat(fd, &st_fd) < 0) + return -errno; + + if (!S_ISFIFO(st_fd.st_mode)) + return 0; + + if (path) { + struct stat st_path; + + if (stat(path, &st_path) < 0) { + + if (errno == ENOENT || errno == ENOTDIR) + return 0; + + return -errno; + } + + return + st_path.st_dev == st_fd.st_dev && + st_path.st_ino == st_fd.st_ino; + } + + return 1; +} + +_public_ int sd_is_special(int fd, const char *path) { + struct stat st_fd; + + assert_return(fd >= 0, -EBADF); + + if (fstat(fd, &st_fd) < 0) + return -errno; + + if (!S_ISREG(st_fd.st_mode) && !S_ISCHR(st_fd.st_mode)) + return 0; + + if (path) { + struct stat st_path; + + if (stat(path, &st_path) < 0) { + + if (errno == ENOENT || errno == ENOTDIR) + return 0; + + return -errno; + } + + if (S_ISREG(st_fd.st_mode) && S_ISREG(st_path.st_mode)) + return + st_path.st_dev == st_fd.st_dev && + st_path.st_ino == st_fd.st_ino; + else if (S_ISCHR(st_fd.st_mode) && S_ISCHR(st_path.st_mode)) + return st_path.st_rdev == st_fd.st_rdev; + else + return 0; + } + + return 1; +} + +static int sd_is_socket_internal(int fd, int type, int listening) { + struct stat st_fd; + + assert_return(fd >= 0, -EBADF); + assert_return(type >= 0, -EINVAL); + + if (fstat(fd, &st_fd) < 0) + return -errno; + + if (!S_ISSOCK(st_fd.st_mode)) + return 0; + + if (type != 0) { + int other_type = 0; + socklen_t l = sizeof(other_type); + + if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0) + return -errno; + + if (l != sizeof(other_type)) + return -EINVAL; + + if (other_type != type) + return 0; + } + + if (listening >= 0) { + int accepting = 0; + socklen_t l = sizeof(accepting); + + if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0) + return -errno; + + if (l != sizeof(accepting)) + return -EINVAL; + + if (!accepting != !listening) + return 0; + } + + return 1; +} + +_public_ int sd_is_socket(int fd, int family, int type, int listening) { + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(family >= 0, -EINVAL); + + r = sd_is_socket_internal(fd, type, listening); + if (r <= 0) + return r; + + if (family > 0) { + union sockaddr_union sockaddr = {}; + socklen_t l = sizeof(sockaddr); + + if (getsockname(fd, &sockaddr.sa, &l) < 0) + return -errno; + + if (l < sizeof(sa_family_t)) + return -EINVAL; + + return sockaddr.sa.sa_family == family; + } + + return 1; +} + +_public_ int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) { + union sockaddr_union sockaddr = {}; + socklen_t l = sizeof(sockaddr); + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(IN_SET(family, 0, AF_INET, AF_INET6), -EINVAL); + + r = sd_is_socket_internal(fd, type, listening); + if (r <= 0) + return r; + + if (getsockname(fd, &sockaddr.sa, &l) < 0) + return -errno; + + if (l < sizeof(sa_family_t)) + return -EINVAL; + + if (sockaddr.sa.sa_family != AF_INET && + sockaddr.sa.sa_family != AF_INET6) + return 0; + + if (family != 0) + if (sockaddr.sa.sa_family != family) + return 0; + + if (port > 0) { + if (sockaddr.sa.sa_family == AF_INET) { + if (l < sizeof(struct sockaddr_in)) + return -EINVAL; + + return htons(port) == sockaddr.in.sin_port; + } else { + if (l < sizeof(struct sockaddr_in6)) + return -EINVAL; + + return htons(port) == sockaddr.in6.sin6_port; + } + } + + return 1; +} + +_public_ int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) { + union sockaddr_union sockaddr = {}; + socklen_t l = sizeof(sockaddr); + int r; + + assert_return(fd >= 0, -EBADF); + + r = sd_is_socket_internal(fd, type, listening); + if (r <= 0) + return r; + + if (getsockname(fd, &sockaddr.sa, &l) < 0) + return -errno; + + if (l < sizeof(sa_family_t)) + return -EINVAL; + + if (sockaddr.sa.sa_family != AF_UNIX) + return 0; + + if (path) { + if (length == 0) + length = strlen(path); + + if (length == 0) + /* Unnamed socket */ + return l == offsetof(struct sockaddr_un, sun_path); + + if (path[0]) + /* Normal path socket */ + return + (l >= offsetof(struct sockaddr_un, sun_path) + length + 1) && + memcmp(path, sockaddr.un.sun_path, length+1) == 0; + else + /* Abstract namespace socket */ + return + (l == offsetof(struct sockaddr_un, sun_path) + length) && + memcmp(path, sockaddr.un.sun_path, length) == 0; + } + + return 1; +} + +_public_ int sd_is_mq(int fd, const char *path) { + struct mq_attr attr; + + /* Check that the fd is valid */ + assert_return(fcntl(fd, F_GETFD) >= 0, -errno); + + if (mq_getattr(fd, &attr) < 0) { + if (errno == EBADF) + /* A non-mq fd (or an invalid one, but we ruled that out above) */ + return 0; + return -errno; + } + + if (path) { + char fpath[PATH_MAX]; + struct stat a, b; + + assert_return(path_is_absolute(path), -EINVAL); + + if (fstat(fd, &a) < 0) + return -errno; + + strncpy(stpcpy(fpath, "/dev/mqueue"), path, sizeof(fpath) - 12); + fpath[sizeof(fpath)-1] = 0; + + if (stat(fpath, &b) < 0) + return -errno; + + if (a.st_dev != b.st_dev || + a.st_ino != b.st_ino) + return 0; + } + + return 1; +} + +_public_ int sd_pid_notify_with_fds(pid_t pid, int unset_environment, const char *state, const int *fds, unsigned n_fds) { + union sockaddr_union sockaddr = { + .sa.sa_family = AF_UNIX, + }; + struct iovec iovec = { + .iov_base = (char*) state, + }; + struct msghdr msghdr = { + .msg_iov = &iovec, + .msg_iovlen = 1, + .msg_name = &sockaddr, + }; + _cleanup_close_ int fd = -1; + struct cmsghdr *cmsg = NULL; + const char *e; + bool have_pid; + int r; + + if (!state) { + r = -EINVAL; + goto finish; + } + + if (n_fds > 0 && !fds) { + r = -EINVAL; + goto finish; + } + + e = getenv("NOTIFY_SOCKET"); + if (!e) + return 0; + + /* Must be an abstract socket, or an absolute path */ + if ((e[0] != '@' && e[0] != '/') || e[1] == 0) { + r = -EINVAL; + goto finish; + } + + if (strlen(e) > sizeof(sockaddr.un.sun_path)) { + r = -EINVAL; + goto finish; + } + + fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); + if (fd < 0) { + r = -errno; + goto finish; + } + + fd_inc_sndbuf(fd, SNDBUF_SIZE); + + iovec.iov_len = strlen(state); + + strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path)); + if (sockaddr.un.sun_path[0] == '@') + sockaddr.un.sun_path[0] = 0; + + msghdr.msg_namelen = SOCKADDR_UN_LEN(sockaddr.un); + + have_pid = pid != 0 && pid != getpid(); + + if (n_fds > 0 || have_pid) { + /* CMSG_SPACE(0) may return value different than zero, which results in miscalculated controllen. */ + msghdr.msg_controllen = + (n_fds > 0 ? CMSG_SPACE(sizeof(int) * n_fds) : 0) + + (have_pid ? CMSG_SPACE(sizeof(struct ucred)) : 0); + + msghdr.msg_control = alloca0(msghdr.msg_controllen); + + cmsg = CMSG_FIRSTHDR(&msghdr); + if (n_fds > 0) { + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int) * n_fds); + + memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * n_fds); + + if (have_pid) + assert_se(cmsg = CMSG_NXTHDR(&msghdr, cmsg)); + } + + if (have_pid) { + struct ucred *ucred; + + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_CREDENTIALS; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred)); + + ucred = (struct ucred*) CMSG_DATA(cmsg); + ucred->pid = pid; + ucred->uid = getuid(); + ucred->gid = getgid(); + } + } + + /* First try with fake ucred data, as requested */ + if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) >= 0) { + r = 1; + goto finish; + } + + /* If that failed, try with our own ucred instead */ + if (have_pid) { + msghdr.msg_controllen -= CMSG_SPACE(sizeof(struct ucred)); + if (msghdr.msg_controllen == 0) + msghdr.msg_control = NULL; + + if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) >= 0) { + r = 1; + goto finish; + } + } + + r = -errno; + +finish: + if (unset_environment) + unsetenv("NOTIFY_SOCKET"); + + return r; +} + +_public_ int sd_pid_notify(pid_t pid, int unset_environment, const char *state) { + return sd_pid_notify_with_fds(pid, unset_environment, state, NULL, 0); +} + +_public_ int sd_notify(int unset_environment, const char *state) { + return sd_pid_notify_with_fds(0, unset_environment, state, NULL, 0); +} + +_public_ int sd_pid_notifyf(pid_t pid, int unset_environment, const char *format, ...) { + _cleanup_free_ char *p = NULL; + int r; + + if (format) { + va_list ap; + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0 || !p) + return -ENOMEM; + } + + return sd_pid_notify(pid, unset_environment, p); +} + +_public_ int sd_notifyf(int unset_environment, const char *format, ...) { + _cleanup_free_ char *p = NULL; + int r; + + if (format) { + va_list ap; + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0 || !p) + return -ENOMEM; + } + + return sd_pid_notify(0, unset_environment, p); +} + +_public_ int sd_booted(void) { + /* We test whether the runtime unit file directory has been + * created. This takes place in mount-setup.c, so is + * guaranteed to happen very early during boot. */ + + return laccess("/run/systemd/system/", F_OK) >= 0; +} + +_public_ int sd_watchdog_enabled(int unset_environment, uint64_t *usec) { + const char *s, *p = ""; /* p is set to dummy value to do unsetting */ + uint64_t u; + int r = 0; + + s = getenv("WATCHDOG_USEC"); + if (!s) + goto finish; + + r = safe_atou64(s, &u); + if (r < 0) + goto finish; + if (u <= 0 || u >= USEC_INFINITY) { + r = -EINVAL; + goto finish; + } + + p = getenv("WATCHDOG_PID"); + if (p) { + pid_t pid; + + r = parse_pid(p, &pid); + if (r < 0) + goto finish; + + /* Is this for us? */ + if (getpid() != pid) { + r = 0; + goto finish; + } + } + + if (usec) + *usec = u; + + r = 1; + +finish: + if (unset_environment && s) + unsetenv("WATCHDOG_USEC"); + if (unset_environment && p) + unsetenv("WATCHDOG_PID"); + + return r; +} diff --git a/src/libsystemd/libsystemd-internal/sd-device/device-enumerator-private.h b/src/libsystemd/libsystemd-internal/sd-device/device-enumerator-private.h new file mode 100644 index 0000000000..d46e26b56e --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-device/device-enumerator-private.h @@ -0,0 +1,34 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Tom Gundersen + + 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 + +int device_enumerator_scan_devices(sd_device_enumerator *enumeartor); +int device_enumerator_scan_subsystems(sd_device_enumerator *enumeartor); +int device_enumerator_add_device(sd_device_enumerator *enumerator, sd_device *device); +int device_enumerator_add_match_is_initialized(sd_device_enumerator *enumerator); +sd_device *device_enumerator_get_first(sd_device_enumerator *enumerator); +sd_device *device_enumerator_get_next(sd_device_enumerator *enumerator); + +#define FOREACH_DEVICE_AND_SUBSYSTEM(enumerator, device) \ + for (device = device_enumerator_get_first(enumerator); \ + device; \ + device = device_enumerator_get_next(enumerator)) diff --git a/src/libsystemd/libsystemd-internal/sd-device/device-enumerator.c b/src/libsystemd/libsystemd-internal/sd-device/device-enumerator.c new file mode 100644 index 0000000000..796728ee0e --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-device/device-enumerator.c @@ -0,0 +1,986 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2014-2015 Tom Gundersen + + 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 "alloc-util.h" +#include "device-enumerator-private.h" +#include "device-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "prioq.h" +#include "set.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +#define DEVICE_ENUMERATE_MAX_DEPTH 256 + +typedef enum DeviceEnumerationType { + DEVICE_ENUMERATION_TYPE_DEVICES, + DEVICE_ENUMERATION_TYPE_SUBSYSTEMS, + _DEVICE_ENUMERATION_TYPE_MAX, + _DEVICE_ENUMERATION_TYPE_INVALID = -1, +} DeviceEnumerationType; + +struct sd_device_enumerator { + unsigned n_ref; + + DeviceEnumerationType type; + Prioq *devices; + bool scan_uptodate; + + Set *match_subsystem; + Set *nomatch_subsystem; + Hashmap *match_sysattr; + Hashmap *nomatch_sysattr; + Hashmap *match_property; + Set *match_sysname; + Set *match_tag; + sd_device *match_parent; + bool match_allow_uninitialized; +}; + +_public_ int sd_device_enumerator_new(sd_device_enumerator **ret) { + _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *enumerator = NULL; + + assert(ret); + + enumerator = new0(sd_device_enumerator, 1); + if (!enumerator) + return -ENOMEM; + + enumerator->n_ref = 1; + enumerator->type = _DEVICE_ENUMERATION_TYPE_INVALID; + + *ret = enumerator; + enumerator = NULL; + + return 0; +} + +_public_ sd_device_enumerator *sd_device_enumerator_ref(sd_device_enumerator *enumerator) { + assert_return(enumerator, NULL); + + assert_se((++ enumerator->n_ref) >= 2); + + return enumerator; +} + +_public_ sd_device_enumerator *sd_device_enumerator_unref(sd_device_enumerator *enumerator) { + if (enumerator && (-- enumerator->n_ref) == 0) { + sd_device *device; + + while ((device = prioq_pop(enumerator->devices))) + sd_device_unref(device); + + prioq_free(enumerator->devices); + + set_free_free(enumerator->match_subsystem); + set_free_free(enumerator->nomatch_subsystem); + hashmap_free_free_free(enumerator->match_sysattr); + hashmap_free_free_free(enumerator->nomatch_sysattr); + hashmap_free_free_free(enumerator->match_property); + set_free_free(enumerator->match_sysname); + set_free_free(enumerator->match_tag); + sd_device_unref(enumerator->match_parent); + + free(enumerator); + } + + return NULL; +} + +_public_ int sd_device_enumerator_add_match_subsystem(sd_device_enumerator *enumerator, const char *subsystem, int match) { + Set **set; + int r; + + assert_return(enumerator, -EINVAL); + assert_return(subsystem, -EINVAL); + + if (match) + set = &enumerator->match_subsystem; + else + set = &enumerator->nomatch_subsystem; + + r = set_ensure_allocated(set, NULL); + if (r < 0) + return r; + + r = set_put_strdup(*set, subsystem); + if (r < 0) + return r; + + enumerator->scan_uptodate = false; + + return 0; +} + +_public_ int sd_device_enumerator_add_match_sysattr(sd_device_enumerator *enumerator, const char *_sysattr, const char *_value, int match) { + _cleanup_free_ char *sysattr = NULL, *value = NULL; + Hashmap **hashmap; + int r; + + assert_return(enumerator, -EINVAL); + assert_return(_sysattr, -EINVAL); + + if (match) + hashmap = &enumerator->match_sysattr; + else + hashmap = &enumerator->nomatch_sysattr; + + r = hashmap_ensure_allocated(hashmap, NULL); + if (r < 0) + return r; + + sysattr = strdup(_sysattr); + if (!sysattr) + return -ENOMEM; + + if (_value) { + value = strdup(_value); + if (!value) + return -ENOMEM; + } + + r = hashmap_put(*hashmap, sysattr, value); + if (r < 0) + return r; + + sysattr = NULL; + value = NULL; + + enumerator->scan_uptodate = false; + + return 0; +} + +_public_ int sd_device_enumerator_add_match_property(sd_device_enumerator *enumerator, const char *_property, const char *_value) { + _cleanup_free_ char *property = NULL, *value = NULL; + int r; + + assert_return(enumerator, -EINVAL); + assert_return(_property, -EINVAL); + + r = hashmap_ensure_allocated(&enumerator->match_property, NULL); + if (r < 0) + return r; + + property = strdup(_property); + if (!property) + return -ENOMEM; + + if (_value) { + value = strdup(_value); + if (!value) + return -ENOMEM; + } + + r = hashmap_put(enumerator->match_property, property, value); + if (r < 0) + return r; + + property = NULL; + value = NULL; + + enumerator->scan_uptodate = false; + + return 0; +} + +_public_ int sd_device_enumerator_add_match_sysname(sd_device_enumerator *enumerator, const char *sysname) { + int r; + + assert_return(enumerator, -EINVAL); + assert_return(sysname, -EINVAL); + + r = set_ensure_allocated(&enumerator->match_sysname, NULL); + if (r < 0) + return r; + + r = set_put_strdup(enumerator->match_sysname, sysname); + if (r < 0) + return r; + + enumerator->scan_uptodate = false; + + return 0; +} + +_public_ int sd_device_enumerator_add_match_tag(sd_device_enumerator *enumerator, const char *tag) { + int r; + + assert_return(enumerator, -EINVAL); + assert_return(tag, -EINVAL); + + r = set_ensure_allocated(&enumerator->match_tag, NULL); + if (r < 0) + return r; + + r = set_put_strdup(enumerator->match_tag, tag); + if (r < 0) + return r; + + enumerator->scan_uptodate = false; + + return 0; +} + +_public_ int sd_device_enumerator_add_match_parent(sd_device_enumerator *enumerator, sd_device *parent) { + assert_return(enumerator, -EINVAL); + assert_return(parent, -EINVAL); + + sd_device_unref(enumerator->match_parent); + enumerator->match_parent = sd_device_ref(parent); + + enumerator->scan_uptodate = false; + + return 0; +} + +_public_ int sd_device_enumerator_allow_uninitialized(sd_device_enumerator *enumerator) { + assert_return(enumerator, -EINVAL); + + enumerator->match_allow_uninitialized = true; + + enumerator->scan_uptodate = false; + + return 0; +} + +int device_enumerator_add_match_is_initialized(sd_device_enumerator *enumerator) { + assert_return(enumerator, -EINVAL); + + enumerator->match_allow_uninitialized = false; + + enumerator->scan_uptodate = false; + + return 0; +} + +static int device_compare(const void *_a, const void *_b) { + sd_device *a = (sd_device *)_a, *b = (sd_device *)_b; + const char *devpath_a, *devpath_b, *sound_a; + bool delay_a, delay_b; + + assert_se(sd_device_get_devpath(a, &devpath_a) >= 0); + assert_se(sd_device_get_devpath(b, &devpath_b) >= 0); + + sound_a = strstr(devpath_a, "/sound/card"); + if (sound_a) { + /* For sound cards the control device must be enumerated last to + * make sure it's the final device node that gets ACLs applied. + * Applications rely on this fact and use ACL changes on the + * control node as an indicator that the ACL change of the + * entire sound card completed. The kernel makes this guarantee + * when creating those devices, and hence we should too when + * enumerating them. */ + sound_a += strlen("/sound/card"); + sound_a = strchr(sound_a, '/'); + + if (sound_a) { + unsigned prefix_len; + + prefix_len = sound_a - devpath_a; + + if (strncmp(devpath_a, devpath_b, prefix_len) == 0) { + const char *sound_b; + + sound_b = devpath_b + prefix_len; + + if (startswith(sound_a, "/controlC") && + !startswith(sound_b, "/contolC")) + return 1; + + if (!startswith(sound_a, "/controlC") && + startswith(sound_b, "/controlC")) + return -1; + } + } + } + + /* md and dm devices are enumerated after all other devices */ + delay_a = strstr(devpath_a, "/block/md") || strstr(devpath_a, "/block/dm-"); + delay_b = strstr(devpath_b, "/block/md") || strstr(devpath_b, "/block/dm-"); + if (delay_a != delay_b) + return delay_a - delay_b; + + return strcmp(devpath_a, devpath_b); +} + +int device_enumerator_add_device(sd_device_enumerator *enumerator, sd_device *device) { + int r; + + assert_return(enumerator, -EINVAL); + assert_return(device, -EINVAL); + + r = prioq_ensure_allocated(&enumerator->devices, device_compare); + if (r < 0) + return r; + + r = prioq_put(enumerator->devices, device, NULL); + if (r < 0) + return r; + + sd_device_ref(device); + + return 0; +} + +static bool match_sysattr_value(sd_device *device, const char *sysattr, const char *match_value) { + const char *value; + int r; + + assert(device); + assert(sysattr); + + r = sd_device_get_sysattr_value(device, sysattr, &value); + if (r < 0) + return false; + + if (!match_value) + return true; + + if (fnmatch(match_value, value, 0) == 0) + return true; + + return false; +} + +static bool match_sysattr(sd_device_enumerator *enumerator, sd_device *device) { + const char *sysattr; + const char *value; + Iterator i; + + assert(enumerator); + assert(device); + + HASHMAP_FOREACH_KEY(value, sysattr, enumerator->nomatch_sysattr, i) + if (match_sysattr_value(device, sysattr, value)) + return false; + + HASHMAP_FOREACH_KEY(value, sysattr, enumerator->match_sysattr, i) + if (!match_sysattr_value(device, sysattr, value)) + return false; + + return true; +} + +static bool match_property(sd_device_enumerator *enumerator, sd_device *device) { + const char *property; + const char *value; + Iterator i; + + assert(enumerator); + assert(device); + + if (hashmap_isempty(enumerator->match_property)) + return true; + + HASHMAP_FOREACH_KEY(value, property, enumerator->match_property, i) { + const char *property_dev, *value_dev; + + FOREACH_DEVICE_PROPERTY(device, property_dev, value_dev) { + if (fnmatch(property, property_dev, 0) != 0) + continue; + + if (!value && !value_dev) + return true; + + if (!value || !value_dev) + continue; + + if (fnmatch(value, value_dev, 0) == 0) + return true; + } + } + + return false; +} + +static bool match_tag(sd_device_enumerator *enumerator, sd_device *device) { + const char *tag; + Iterator i; + + assert(enumerator); + assert(device); + + SET_FOREACH(tag, enumerator->match_tag, i) + if (!sd_device_has_tag(device, tag)) + return false; + + return true; +} + +static bool match_parent(sd_device_enumerator *enumerator, sd_device *device) { + const char *devpath, *devpath_dev; + int r; + + assert(enumerator); + assert(device); + + if (!enumerator->match_parent) + return true; + + r = sd_device_get_devpath(enumerator->match_parent, &devpath); + assert(r >= 0); + + r = sd_device_get_devpath(device, &devpath_dev); + assert(r >= 0); + + return startswith(devpath_dev, devpath); +} + +static bool match_sysname(sd_device_enumerator *enumerator, const char *sysname) { + const char *sysname_match; + Iterator i; + + assert(enumerator); + assert(sysname); + + if (set_isempty(enumerator->match_sysname)) + return true; + + SET_FOREACH(sysname_match, enumerator->match_sysname, i) + if (fnmatch(sysname_match, sysname, 0) == 0) + return true; + + return false; +} + +static int enumerator_scan_dir_and_add_devices(sd_device_enumerator *enumerator, const char *basedir, const char *subdir1, const char *subdir2) { + _cleanup_closedir_ DIR *dir = NULL; + char *path; + struct dirent *dent; + int r = 0; + + assert(enumerator); + assert(basedir); + + path = strjoina("/sys/", basedir, "/"); + + if (subdir1) + path = strjoina(path, subdir1, "/"); + + if (subdir2) + path = strjoina(path, subdir2, "/"); + + dir = opendir(path); + if (!dir) + return -errno; + + FOREACH_DIRENT_ALL(dent, dir, return -errno) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + char syspath[strlen(path) + 1 + strlen(dent->d_name) + 1]; + dev_t devnum; + int ifindex, initialized, k; + + if (dent->d_name[0] == '.') + continue; + + if (!match_sysname(enumerator, dent->d_name)) + continue; + + (void)sprintf(syspath, "%s%s", path, dent->d_name); + + k = sd_device_new_from_syspath(&device, syspath); + if (k < 0) { + if (k != -ENODEV) + /* this is necessarily racey, so ignore missing devices */ + r = k; + + continue; + } + + k = sd_device_get_devnum(device, &devnum); + if (k < 0) { + r = k; + continue; + } + + k = sd_device_get_ifindex(device, &ifindex); + if (k < 0) { + r = k; + continue; + } + + k = sd_device_get_is_initialized(device, &initialized); + if (k < 0) { + r = k; + continue; + } + + /* + * All devices with a device node or network interfaces + * possibly need udev to adjust the device node permission + * or context, or rename the interface before it can be + * reliably used from other processes. + * + * For now, we can only check these types of devices, we + * might not store a database, and have no way to find out + * for all other types of devices. + */ + if (!enumerator->match_allow_uninitialized && + !initialized && + (major(devnum) > 0 || ifindex > 0)) + continue; + + if (!match_parent(enumerator, device)) + continue; + + if (!match_tag(enumerator, device)) + continue; + + if (!match_property(enumerator, device)) + continue; + + if (!match_sysattr(enumerator, device)) + continue; + + k = device_enumerator_add_device(enumerator, device); + if (k < 0) + r = k; + } + + return r; +} + +static bool match_subsystem(sd_device_enumerator *enumerator, const char *subsystem) { + const char *subsystem_match; + Iterator i; + + assert(enumerator); + + if (!subsystem) + return false; + + SET_FOREACH(subsystem_match, enumerator->nomatch_subsystem, i) + if (fnmatch(subsystem_match, subsystem, 0) == 0) + return false; + + if (set_isempty(enumerator->match_subsystem)) + return true; + + SET_FOREACH(subsystem_match, enumerator->match_subsystem, i) + if (fnmatch(subsystem_match, subsystem, 0) == 0) + return true; + + return false; +} + +static int enumerator_scan_dir(sd_device_enumerator *enumerator, const char *basedir, const char *subdir, const char *subsystem) { + _cleanup_closedir_ DIR *dir = NULL; + char *path; + struct dirent *dent; + int r = 0; + + path = strjoina("/sys/", basedir); + + dir = opendir(path); + if (!dir) + return -errno; + + log_debug(" device-enumerator: scanning %s", path); + + FOREACH_DIRENT_ALL(dent, dir, return -errno) { + int k; + + if (dent->d_name[0] == '.') + continue; + + if (!match_subsystem(enumerator, subsystem ? : dent->d_name)) + continue; + + k = enumerator_scan_dir_and_add_devices(enumerator, basedir, dent->d_name, subdir); + if (k < 0) + r = k; + } + + return r; +} + +static int enumerator_scan_devices_tag(sd_device_enumerator *enumerator, const char *tag) { + _cleanup_closedir_ DIR *dir = NULL; + char *path; + struct dirent *dent; + int r = 0; + + assert(enumerator); + assert(tag); + + path = strjoina("/run/udev/tags/", tag); + + dir = opendir(path); + if (!dir) { + if (errno == ENOENT) + return 0; + else { + log_error("sd-device-enumerator: could not open tags directory %s: %m", path); + return -errno; + } + } + + /* TODO: filter away subsystems? */ + + FOREACH_DIRENT_ALL(dent, dir, return -errno) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + const char *subsystem, *sysname; + int k; + + if (dent->d_name[0] == '.') + continue; + + k = sd_device_new_from_device_id(&device, dent->d_name); + if (k < 0) { + if (k != -ENODEV) + /* this is necessarily racy, so ignore missing devices */ + r = k; + + continue; + } + + k = sd_device_get_subsystem(device, &subsystem); + if (k < 0) { + r = k; + continue; + } + + if (!match_subsystem(enumerator, subsystem)) + continue; + + k = sd_device_get_sysname(device, &sysname); + if (k < 0) { + r = k; + continue; + } + + if (!match_sysname(enumerator, sysname)) + continue; + + if (!match_parent(enumerator, device)) + continue; + + if (!match_property(enumerator, device)) + continue; + + if (!match_sysattr(enumerator, device)) + continue; + + k = device_enumerator_add_device(enumerator, device); + if (k < 0) { + r = k; + continue; + } + } + + return r; +} + +static int enumerator_scan_devices_tags(sd_device_enumerator *enumerator) { + const char *tag; + Iterator i; + int r; + + assert(enumerator); + + SET_FOREACH(tag, enumerator->match_tag, i) { + r = enumerator_scan_devices_tag(enumerator, tag); + if (r < 0) + return r; + } + + return 0; +} + +static int parent_add_child(sd_device_enumerator *enumerator, const char *path) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + const char *subsystem, *sysname; + int r; + + r = sd_device_new_from_syspath(&device, path); + if (r == -ENODEV) + /* this is necessarily racy, so ignore missing devices */ + return 0; + else if (r < 0) + return r; + + r = sd_device_get_subsystem(device, &subsystem); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + if (!match_subsystem(enumerator, subsystem)) + return 0; + + r = sd_device_get_sysname(device, &sysname); + if (r < 0) + return r; + + if (!match_sysname(enumerator, sysname)) + return 0; + + if (!match_property(enumerator, device)) + return 0; + + if (!match_sysattr(enumerator, device)) + return 0; + + r = device_enumerator_add_device(enumerator, device); + if (r < 0) + return r; + + return 1; +} + +static int parent_crawl_children(sd_device_enumerator *enumerator, const char *path, unsigned maxdepth) { + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *dent; + int r = 0; + + dir = opendir(path); + if (!dir) { + log_debug("sd-device-enumerate: could not open parent directory %s: %m", path); + return -errno; + } + + FOREACH_DIRENT_ALL(dent, dir, return -errno) { + _cleanup_free_ char *child = NULL; + int k; + + if (dent->d_name[0] == '.') + continue; + + if (dent->d_type != DT_DIR) + continue; + + child = strjoin(path, "/", dent->d_name, NULL); + if (!child) + return -ENOMEM; + + k = parent_add_child(enumerator, child); + if (k < 0) + r = k; + + if (maxdepth > 0) + parent_crawl_children(enumerator, child, maxdepth - 1); + else + log_debug("device-enumerate: max depth reached, %s: ignoring devices", child); + } + + return r; +} + +static int enumerator_scan_devices_children(sd_device_enumerator *enumerator) { + const char *path; + int r = 0, k; + + r = sd_device_get_syspath(enumerator->match_parent, &path); + if (r < 0) + return r; + + k = parent_add_child(enumerator, path); + if (k < 0) + r = k; + + k = parent_crawl_children(enumerator, path, DEVICE_ENUMERATE_MAX_DEPTH); + if (k < 0) + r = k; + + return r; +} + +static int enumerator_scan_devices_all(sd_device_enumerator *enumerator) { + int r = 0; + + log_debug("device-enumerator: scan all dirs"); + + if (access("/sys/subsystem", F_OK) >= 0) { + /* we have /subsystem/, forget all the old stuff */ + r = enumerator_scan_dir(enumerator, "subsystem", "devices", NULL); + if (r < 0) + return log_debug_errno(r, "device-enumerator: failed to scan /sys/subsystem: %m"); + } else { + int k; + + k = enumerator_scan_dir(enumerator, "bus", "devices", NULL); + if (k < 0) { + log_debug_errno(k, "device-enumerator: failed to scan /sys/bus: %m"); + r = k; + } + + k = enumerator_scan_dir(enumerator, "class", NULL, NULL); + if (k < 0) { + log_debug_errno(k, "device-enumerator: failed to scan /sys/class: %m"); + r = k; + } + } + + return r; +} + +int device_enumerator_scan_devices(sd_device_enumerator *enumerator) { + sd_device *device; + int r; + + assert(enumerator); + + if (enumerator->scan_uptodate && + enumerator->type == DEVICE_ENUMERATION_TYPE_DEVICES) + return 0; + + while ((device = prioq_pop(enumerator->devices))) + sd_device_unref(device); + + if (!set_isempty(enumerator->match_tag)) { + r = enumerator_scan_devices_tags(enumerator); + if (r < 0) + return r; + } else if (enumerator->match_parent) { + r = enumerator_scan_devices_children(enumerator); + if (r < 0) + return r; + } else { + r = enumerator_scan_devices_all(enumerator); + if (r < 0) + return r; + } + + enumerator->scan_uptodate = true; + + return 0; +} + +_public_ sd_device *sd_device_enumerator_get_device_first(sd_device_enumerator *enumerator) { + int r; + + assert_return(enumerator, NULL); + + r = device_enumerator_scan_devices(enumerator); + if (r < 0) + return NULL; + + enumerator->type = DEVICE_ENUMERATION_TYPE_DEVICES; + + return prioq_peek(enumerator->devices); +} + +_public_ sd_device *sd_device_enumerator_get_device_next(sd_device_enumerator *enumerator) { + assert_return(enumerator, NULL); + + if (!enumerator->scan_uptodate || + enumerator->type != DEVICE_ENUMERATION_TYPE_DEVICES) + return NULL; + + sd_device_unref(prioq_pop(enumerator->devices)); + + return prioq_peek(enumerator->devices); +} + +int device_enumerator_scan_subsystems(sd_device_enumerator *enumerator) { + sd_device *device; + const char *subsysdir; + int r = 0, k; + + assert(enumerator); + + if (enumerator->scan_uptodate && + enumerator->type == DEVICE_ENUMERATION_TYPE_SUBSYSTEMS) + return 0; + + while ((device = prioq_pop(enumerator->devices))) + sd_device_unref(device); + + /* modules */ + if (match_subsystem(enumerator, "module")) { + k = enumerator_scan_dir_and_add_devices(enumerator, "module", NULL, NULL); + if (k < 0) { + log_debug_errno(k, "device-enumerator: failed to scan modules: %m"); + r = k; + } + } + + if (access("/sys/subsystem", F_OK) >= 0) + subsysdir = "subsystem"; + else + subsysdir = "bus"; + + /* subsystems (only buses support coldplug) */ + if (match_subsystem(enumerator, "subsystem")) { + k = enumerator_scan_dir_and_add_devices(enumerator, subsysdir, NULL, NULL); + if (k < 0) { + log_debug_errno(k, "device-enumerator: failed to scan subsystems: %m"); + r = k; + } + } + + /* subsystem drivers */ + if (match_subsystem(enumerator, "drivers")) { + k = enumerator_scan_dir(enumerator, subsysdir, "drivers", "drivers"); + if (k < 0) { + log_debug_errno(k, "device-enumerator: failed to scan drivers: %m"); + r = k; + } + } + + enumerator->scan_uptodate = true; + + return r; +} + +_public_ sd_device *sd_device_enumerator_get_subsystem_first(sd_device_enumerator *enumerator) { + int r; + + assert_return(enumerator, NULL); + + r = device_enumerator_scan_subsystems(enumerator); + if (r < 0) + return NULL; + + enumerator->type = DEVICE_ENUMERATION_TYPE_SUBSYSTEMS; + + return prioq_peek(enumerator->devices); +} + +_public_ sd_device *sd_device_enumerator_get_subsystem_next(sd_device_enumerator *enumerator) { + assert_return(enumerator, NULL); + + if (enumerator->scan_uptodate || + enumerator->type != DEVICE_ENUMERATION_TYPE_SUBSYSTEMS) + return NULL; + + sd_device_unref(prioq_pop(enumerator->devices)); + + return prioq_peek(enumerator->devices); +} + +sd_device *device_enumerator_get_first(sd_device_enumerator *enumerator) { + assert_return(enumerator, NULL); + + return prioq_peek(enumerator->devices); +} + +sd_device *device_enumerator_get_next(sd_device_enumerator *enumerator) { + assert_return(enumerator, NULL); + + sd_device_unref(prioq_pop(enumerator->devices)); + + return prioq_peek(enumerator->devices); +} diff --git a/src/libsystemd/libsystemd-internal/sd-device/device-internal.h b/src/libsystemd/libsystemd-internal/sd-device/device-internal.h new file mode 100644 index 0000000000..ab222e27de --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-device/device-internal.h @@ -0,0 +1,126 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2014 Tom Gundersen + + 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 "hashmap.h" +#include "set.h" + +struct sd_device { + uint64_t n_ref; + + sd_device *parent; + bool parent_set; /* no need to try to reload parent */ + + OrderedHashmap *properties; + Iterator properties_iterator; + uint64_t properties_generation; /* changes whenever the properties are changed */ + uint64_t properties_iterator_generation; /* generation when iteration was started */ + + /* the subset of the properties that should be written to the db*/ + OrderedHashmap *properties_db; + + Hashmap *sysattr_values; /* cached sysattr values */ + + Set *sysattrs; /* names of sysattrs */ + Iterator sysattrs_iterator; + bool sysattrs_read; /* don't try to re-read sysattrs once read */ + + Set *tags; + Iterator tags_iterator; + uint64_t tags_generation; /* changes whenever the tags are changed */ + uint64_t tags_iterator_generation; /* generation when iteration was started */ + bool property_tags_outdated; /* need to update TAGS= property */ + + Set *devlinks; + Iterator devlinks_iterator; + uint64_t devlinks_generation; /* changes whenever the devlinks are changed */ + uint64_t devlinks_iterator_generation; /* generation when iteration was started */ + bool property_devlinks_outdated; /* need to update DEVLINKS= property */ + int devlink_priority; + + char **properties_strv; /* the properties hashmap as a strv */ + uint8_t *properties_nulstr; /* the same as a nulstr */ + size_t properties_nulstr_len; + bool properties_buf_outdated; /* need to reread hashmap */ + + int watch_handle; + + char *syspath; + const char *devpath; + const char *sysnum; + char *sysname; + bool sysname_set; /* don't reread sysname */ + + char *devtype; + int ifindex; + char *devname; + dev_t devnum; + + char *subsystem; + bool subsystem_set; /* don't reread subsystem */ + char *driver; + bool driver_set; /* don't reread driver */ + + char *id_filename; + + bool is_initialized; + uint64_t usec_initialized; + + mode_t devmode; + uid_t devuid; + gid_t devgid; + + bool uevent_loaded; /* don't reread uevent */ + bool db_loaded; /* don't reread db */ + + bool sealed; /* don't read more information from uevent/db */ + bool db_persist; /* don't clean up the db when switching from initrd to real root */ +}; + +typedef enum DeviceAction { + DEVICE_ACTION_ADD, + DEVICE_ACTION_REMOVE, + DEVICE_ACTION_CHANGE, + DEVICE_ACTION_MOVE, + DEVICE_ACTION_ONLINE, + DEVICE_ACTION_OFFLINE, + _DEVICE_ACTION_MAX, + _DEVICE_ACTION_INVALID = -1, +} DeviceAction; + +int device_new_aux(sd_device **ret); +int device_add_property_aux(sd_device *device, const char *key, const char *value, bool db); +int device_add_property_internal(sd_device *device, const char *key, const char *value); +int device_read_uevent_file(sd_device *device); +int device_read_db_aux(sd_device *device, bool force); + +int device_set_syspath(sd_device *device, const char *_syspath, bool verify); +int device_set_ifindex(sd_device *device, const char *ifindex); +int device_set_devmode(sd_device *device, const char *devmode); +int device_set_devname(sd_device *device, const char *_devname); +int device_set_devtype(sd_device *device, const char *_devtype); +int device_set_devnum(sd_device *device, const char *major, const char *minor); +int device_set_subsystem(sd_device *device, const char *_subsystem); +int device_set_driver(sd_device *device, const char *_driver); +int device_set_usec_initialized(sd_device *device, const char *initialized); + +DeviceAction device_action_from_string(const char *s) _pure_; +const char *device_action_to_string(DeviceAction a) _const_; diff --git a/src/libsystemd/libsystemd-internal/sd-device/device-private.c b/src/libsystemd/libsystemd-internal/sd-device/device-private.c new file mode 100644 index 0000000000..51cabcb048 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-device/device-private.c @@ -0,0 +1,1119 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2014 Tom Gundersen + + 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 +#include + +#include + +#include "alloc-util.h" +#include "device-internal.h" +#include "device-private.h" +#include "device-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "hashmap.h" +#include "macro.h" +#include "mkdir.h" +#include "parse-util.h" +#include "path-util.h" +#include "refcnt.h" +#include "set.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "strxcpyx.h" +#include "user-util.h" +#include "util.h" + +int device_add_property(sd_device *device, const char *key, const char *value) { + int r; + + assert(device); + assert(key); + + r = device_add_property_aux(device, key, value, false); + if (r < 0) + return r; + + if (key[0] != '.') { + r = device_add_property_aux(device, key, value, true); + if (r < 0) + return r; + } + + return 0; +} + +static int device_add_property_internal_from_string(sd_device *device, const char *str) { + _cleanup_free_ char *key = NULL; + char *value; + + assert(device); + assert(str); + + key = strdup(str); + if (!key) + return -ENOMEM; + + value = strchr(key, '='); + if (!value) + return -EINVAL; + + *value = '\0'; + + if (isempty(++value)) + value = NULL; + + return device_add_property_internal(device, key, value); +} + +static int handle_db_line(sd_device *device, char key, const char *value) { + char *path; + int r; + + assert(device); + assert(value); + + switch (key) { + case 'S': + path = strjoina("/dev/", value); + r = device_add_devlink(device, path); + if (r < 0) + return r; + + break; + case 'L': + r = safe_atoi(value, &device->devlink_priority); + if (r < 0) + return r; + + break; + case 'E': + r = device_add_property_internal_from_string(device, value); + if (r < 0) + return r; + + break; + case 'G': + r = device_add_tag(device, value); + if (r < 0) + return r; + + break; + case 'W': + r = safe_atoi(value, &device->watch_handle); + if (r < 0) + return r; + + break; + case 'I': + r = device_set_usec_initialized(device, value); + if (r < 0) + return r; + + break; + default: + log_debug("device db: unknown key '%c'", key); + } + + return 0; +} + +void device_set_devlink_priority(sd_device *device, int priority) { + assert(device); + + device->devlink_priority = priority; +} + +void device_set_is_initialized(sd_device *device) { + assert(device); + + device->is_initialized = true; +} + +int device_ensure_usec_initialized(sd_device *device, sd_device *device_old) { + char num[DECIMAL_STR_MAX(usec_t)]; + usec_t usec_initialized; + int r; + + assert(device); + + if (device_old && device_old->usec_initialized > 0) + usec_initialized = device_old->usec_initialized; + else + usec_initialized = now(CLOCK_MONOTONIC); + + r = snprintf(num, sizeof(num), USEC_FMT, usec_initialized); + if (r < 0) + return -errno; + + r = device_set_usec_initialized(device, num); + if (r < 0) + return r; + + return 0; +} + +static int device_read_db(sd_device *device) { + _cleanup_free_ char *db = NULL; + char *path; + const char *id, *value; + char key; + size_t db_len; + unsigned i; + int r; + + enum { + PRE_KEY, + KEY, + PRE_VALUE, + VALUE, + INVALID_LINE, + } state = PRE_KEY; + + assert(device); + + if (device->db_loaded || device->sealed) + return 0; + + r = device_get_id_filename(device, &id); + if (r < 0) + return r; + + path = strjoina("/run/udev/data/", id); + + r = read_full_file(path, &db, &db_len); + if (r < 0) { + if (r == -ENOENT) + return 0; + else + return log_debug_errno(r, "sd-device: failed to read db '%s': %m", path); + } + + /* devices with a database entry are initialized */ + device_set_is_initialized(device); + + for (i = 0; i < db_len; i++) { + switch (state) { + case PRE_KEY: + if (!strchr(NEWLINE, db[i])) { + key = db[i]; + + state = KEY; + } + + break; + case KEY: + if (db[i] != ':') { + log_debug("sd-device: ignoring invalid db entry with key '%c'", key); + + state = INVALID_LINE; + } else { + db[i] = '\0'; + + state = PRE_VALUE; + } + + break; + case PRE_VALUE: + value = &db[i]; + + state = VALUE; + + break; + case INVALID_LINE: + if (strchr(NEWLINE, db[i])) + state = PRE_KEY; + + break; + case VALUE: + if (strchr(NEWLINE, db[i])) { + db[i] = '\0'; + r = handle_db_line(device, key, value); + if (r < 0) + log_debug_errno(r, "sd-device: failed to handle db entry '%c:%s': %m", key, value); + + state = PRE_KEY; + } + + break; + default: + assert_not_reached("invalid state when parsing db"); + } + } + + device->db_loaded = true; + + return 0; +} + +uint64_t device_get_properties_generation(sd_device *device) { + assert(device); + + return device->properties_generation; +} + +uint64_t device_get_tags_generation(sd_device *device) { + assert(device); + + return device->tags_generation; +} + +uint64_t device_get_devlinks_generation(sd_device *device) { + assert(device); + + return device->devlinks_generation; +} + +int device_get_devnode_mode(sd_device *device, mode_t *mode) { + int r; + + assert(device); + assert(mode); + + r = device_read_db(device); + if (r < 0) + return r; + + *mode = device->devmode; + + return 0; +} + +int device_get_devnode_uid(sd_device *device, uid_t *uid) { + int r; + + assert(device); + assert(uid); + + r = device_read_db(device); + if (r < 0) + return r; + + *uid = device->devuid; + + return 0; +} + +static int device_set_devuid(sd_device *device, const char *uid) { + unsigned u; + int r; + + assert(device); + assert(uid); + + r = safe_atou(uid, &u); + if (r < 0) + return r; + + r = device_add_property_internal(device, "DEVUID", uid); + if (r < 0) + return r; + + device->devuid = u; + + return 0; +} + +int device_get_devnode_gid(sd_device *device, gid_t *gid) { + int r; + + assert(device); + assert(gid); + + r = device_read_db(device); + if (r < 0) + return r; + + *gid = device->devgid; + + return 0; +} + +static int device_set_devgid(sd_device *device, const char *gid) { + unsigned g; + int r; + + assert(device); + assert(gid); + + r = safe_atou(gid, &g); + if (r < 0) + return r; + + r = device_add_property_internal(device, "DEVGID", gid); + if (r < 0) + return r; + + device->devgid = g; + + return 0; +} + +static int device_amend(sd_device *device, const char *key, const char *value) { + int r; + + assert(device); + assert(key); + assert(value); + + if (streq(key, "DEVPATH")) { + char *path; + + path = strjoina("/sys", value); + + /* the caller must verify or trust this data (e.g., if it comes from the kernel) */ + r = device_set_syspath(device, path, false); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set syspath to '%s': %m", path); + } else if (streq(key, "SUBSYSTEM")) { + r = device_set_subsystem(device, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set subsystem to '%s': %m", value); + } else if (streq(key, "DEVTYPE")) { + r = device_set_devtype(device, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set devtype to '%s': %m", value); + } else if (streq(key, "DEVNAME")) { + r = device_set_devname(device, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set devname to '%s': %m", value); + } else if (streq(key, "USEC_INITIALIZED")) { + r = device_set_usec_initialized(device, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set usec-initialized to '%s': %m", value); + } else if (streq(key, "DRIVER")) { + r = device_set_driver(device, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set driver to '%s': %m", value); + } else if (streq(key, "IFINDEX")) { + r = device_set_ifindex(device, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set ifindex to '%s': %m", value); + } else if (streq(key, "DEVMODE")) { + r = device_set_devmode(device, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set devmode to '%s': %m", value); + } else if (streq(key, "DEVUID")) { + r = device_set_devuid(device, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set devuid to '%s': %m", value); + } else if (streq(key, "DEVGID")) { + r = device_set_devgid(device, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set devgid to '%s': %m", value); + } else if (streq(key, "DEVLINKS")) { + const char *word, *state; + size_t l; + + FOREACH_WORD(word, l, value, state) { + char devlink[l + 1]; + + strncpy(devlink, word, l); + devlink[l] = '\0'; + + r = device_add_devlink(device, devlink); + if (r < 0) + return log_debug_errno(r, "sd-device: could not add devlink '%s': %m", devlink); + } + } else if (streq(key, "TAGS")) { + const char *word, *state; + size_t l; + + FOREACH_WORD_SEPARATOR(word, l, value, ":", state) { + char tag[l + 1]; + + (void)strncpy(tag, word, l); + tag[l] = '\0'; + + r = device_add_tag(device, tag); + if (r < 0) + return log_debug_errno(r, "sd-device: could not add tag '%s': %m", tag); + } + } else { + r = device_add_property_internal(device, key, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not add property '%s=%s': %m", key, value); + } + + return 0; +} + +static const char* const device_action_table[_DEVICE_ACTION_MAX] = { + [DEVICE_ACTION_ADD] = "add", + [DEVICE_ACTION_REMOVE] = "remove", + [DEVICE_ACTION_CHANGE] = "change", + [DEVICE_ACTION_MOVE] = "move", + [DEVICE_ACTION_ONLINE] = "online", + [DEVICE_ACTION_OFFLINE] = "offline", +}; + +DEFINE_STRING_TABLE_LOOKUP(device_action, DeviceAction); + +static int device_append(sd_device *device, char *key, const char **_major, const char **_minor, uint64_t *_seqnum, + DeviceAction *_action) { + DeviceAction action = _DEVICE_ACTION_INVALID; + uint64_t seqnum = 0; + const char *major = NULL, *minor = NULL; + char *value; + int r; + + assert(device); + assert(key); + assert(_major); + assert(_minor); + assert(_seqnum); + assert(_action); + + value = strchr(key, '='); + if (!value) { + log_debug("sd-device: not a key-value pair: '%s'", key); + return -EINVAL; + } + + *value = '\0'; + + value++; + + if (streq(key, "MAJOR")) + major = value; + else if (streq(key, "MINOR")) + minor = value; + else { + if (streq(key, "ACTION")) { + action = device_action_from_string(value); + if (action == _DEVICE_ACTION_INVALID) + return -EINVAL; + } else if (streq(key, "SEQNUM")) { + r = safe_atou64(value, &seqnum); + if (r < 0) + return r; + else if (seqnum == 0) + /* kernel only sends seqnum > 0 */ + return -EINVAL; + } + + r = device_amend(device, key, value); + if (r < 0) + return r; + } + + if (major != 0) + *_major = major; + + if (minor != 0) + *_minor = minor; + + if (action != _DEVICE_ACTION_INVALID) + *_action = action; + + if (seqnum > 0) + *_seqnum = seqnum; + + return 0; +} + +void device_seal(sd_device *device) { + assert(device); + + device->sealed = true; +} + +static int device_verify(sd_device *device, DeviceAction action, uint64_t seqnum) { + assert(device); + + if (!device->devpath || !device->subsystem || action == _DEVICE_ACTION_INVALID || seqnum == 0) { + log_debug("sd-device: device created from strv lacks devpath, subsystem, action or seqnum"); + return -EINVAL; + } + + device->sealed = true; + + return 0; +} + +int device_new_from_strv(sd_device **ret, char **strv) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + char **key; + const char *major = NULL, *minor = NULL; + DeviceAction action = _DEVICE_ACTION_INVALID; + uint64_t seqnum; + int r; + + assert(ret); + assert(strv); + + r = device_new_aux(&device); + if (r < 0) + return r; + + STRV_FOREACH(key, strv) { + r = device_append(device, *key, &major, &minor, &seqnum, &action); + if (r < 0) + return r; + } + + if (major) { + r = device_set_devnum(device, major, minor); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set devnum %s:%s: %m", major, minor); + } + + r = device_verify(device, action, seqnum); + if (r < 0) + return r; + + *ret = device; + device = NULL; + + return 0; +} + +int device_new_from_nulstr(sd_device **ret, uint8_t *nulstr, size_t len) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + const char *major = NULL, *minor = NULL; + DeviceAction action = _DEVICE_ACTION_INVALID; + uint64_t seqnum; + unsigned i = 0; + int r; + + assert(ret); + assert(nulstr); + assert(len); + + r = device_new_aux(&device); + if (r < 0) + return r; + + while (i < len) { + char *key; + const char *end; + + key = (char*)&nulstr[i]; + end = memchr(key, '\0', len - i); + if (!end) { + log_debug("sd-device: failed to parse nulstr"); + return -EINVAL; + } + i += end - key + 1; + + r = device_append(device, key, &major, &minor, &seqnum, &action); + if (r < 0) + return r; + } + + if (major) { + r = device_set_devnum(device, major, minor); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set devnum %s:%s: %m", major, minor); + } + + r = device_verify(device, action, seqnum); + if (r < 0) + return r; + + *ret = device; + device = NULL; + + return 0; +} + +static int device_update_properties_bufs(sd_device *device) { + const char *val, *prop; + _cleanup_free_ char **buf_strv = NULL; + _cleanup_free_ uint8_t *buf_nulstr = NULL; + size_t allocated_nulstr = 0; + size_t nulstr_len = 0, num = 0, i = 0; + + assert(device); + + if (!device->properties_buf_outdated) + return 0; + + FOREACH_DEVICE_PROPERTY(device, prop, val) { + size_t len = 0; + + len = strlen(prop) + 1 + strlen(val); + + buf_nulstr = GREEDY_REALLOC0(buf_nulstr, allocated_nulstr, nulstr_len + len + 2); + if (!buf_nulstr) + return -ENOMEM; + + strscpyl((char *)buf_nulstr + nulstr_len, len + 1, prop, "=", val, NULL); + nulstr_len += len + 1; + ++num; + } + + /* build buf_strv from buf_nulstr */ + buf_strv = new0(char *, num + 1); + if (!buf_strv) + return -ENOMEM; + + NULSTR_FOREACH(val, (char*) buf_nulstr) { + buf_strv[i] = (char *) val; + assert(i < num); + i++; + } + + free(device->properties_nulstr); + device->properties_nulstr = buf_nulstr; + buf_nulstr = NULL; + device->properties_nulstr_len = nulstr_len; + free(device->properties_strv); + device->properties_strv = buf_strv; + buf_strv = NULL; + + device->properties_buf_outdated = false; + + return 0; +} + +int device_get_properties_nulstr(sd_device *device, const uint8_t **nulstr, size_t *len) { + int r; + + assert(device); + assert(nulstr); + assert(len); + + r = device_update_properties_bufs(device); + if (r < 0) + return r; + + *nulstr = device->properties_nulstr; + *len = device->properties_nulstr_len; + + return 0; +} + +int device_get_properties_strv(sd_device *device, char ***strv) { + int r; + + assert(device); + assert(strv); + + r = device_update_properties_bufs(device); + if (r < 0) + return r; + + *strv = device->properties_strv; + + return 0; +} + +int device_get_devlink_priority(sd_device *device, int *priority) { + int r; + + assert(device); + assert(priority); + + r = device_read_db(device); + if (r < 0) + return r; + + *priority = device->devlink_priority; + + return 0; +} + +int device_get_watch_handle(sd_device *device, int *handle) { + int r; + + assert(device); + assert(handle); + + r = device_read_db(device); + if (r < 0) + return r; + + *handle = device->watch_handle; + + return 0; +} + +void device_set_watch_handle(sd_device *device, int handle) { + assert(device); + + device->watch_handle = handle; +} + +int device_rename(sd_device *device, const char *name) { + _cleanup_free_ char *dirname = NULL; + char *new_syspath; + const char *interface; + int r; + + assert(device); + assert(name); + + dirname = dirname_malloc(device->syspath); + if (!dirname) + return -ENOMEM; + + new_syspath = strjoina(dirname, "/", name); + + /* the user must trust that the new name is correct */ + r = device_set_syspath(device, new_syspath, false); + if (r < 0) + return r; + + r = sd_device_get_property_value(device, "INTERFACE", &interface); + if (r >= 0) { + r = device_add_property_internal(device, "INTERFACE", name); + if (r < 0) + return r; + + /* like DEVPATH_OLD, INTERFACE_OLD is not saved to the db, but only stays around for the current event */ + r = device_add_property_internal(device, "INTERFACE_OLD", interface); + if (r < 0) + return r; + } else if (r != -ENOENT) + return r; + + return 0; +} + +int device_shallow_clone(sd_device *old_device, sd_device **new_device) { + _cleanup_(sd_device_unrefp) sd_device *ret = NULL; + int r; + + assert(old_device); + assert(new_device); + + r = device_new_aux(&ret); + if (r < 0) + return r; + + r = device_set_syspath(ret, old_device->syspath, false); + if (r < 0) + return r; + + r = device_set_subsystem(ret, old_device->subsystem); + if (r < 0) + return r; + + ret->devnum = old_device->devnum; + + *new_device = ret; + ret = NULL; + + return 0; +} + +int device_clone_with_db(sd_device *old_device, sd_device **new_device) { + _cleanup_(sd_device_unrefp) sd_device *ret = NULL; + int r; + + assert(old_device); + assert(new_device); + + r = device_shallow_clone(old_device, &ret); + if (r < 0) + return r; + + r = device_read_db(ret); + if (r < 0) + return r; + + ret->sealed = true; + + *new_device = ret; + ret = NULL; + + return 0; +} + +int device_new_from_synthetic_event(sd_device **new_device, const char *syspath, const char *action) { + _cleanup_(sd_device_unrefp) sd_device *ret = NULL; + int r; + + assert(new_device); + assert(syspath); + assert(action); + + r = sd_device_new_from_syspath(&ret, syspath); + if (r < 0) + return r; + + r = device_read_uevent_file(ret); + if (r < 0) + return r; + + r = device_add_property_internal(ret, "ACTION", action); + if (r < 0) + return r; + + *new_device = ret; + ret = NULL; + + return 0; +} + +int device_copy_properties(sd_device *device_dst, sd_device *device_src) { + const char *property, *value; + int r; + + assert(device_dst); + assert(device_src); + + FOREACH_DEVICE_PROPERTY(device_src, property, value) { + r = device_add_property(device_dst, property, value); + if (r < 0) + return r; + } + + return 0; +} + +void device_cleanup_tags(sd_device *device) { + assert(device); + + set_free_free(device->tags); + device->tags = NULL; + device->property_tags_outdated = true; + device->tags_generation++; +} + +void device_cleanup_devlinks(sd_device *device) { + assert(device); + + set_free_free(device->devlinks); + device->devlinks = NULL; + device->property_devlinks_outdated = true; + device->devlinks_generation++; +} + +void device_remove_tag(sd_device *device, const char *tag) { + assert(device); + assert(tag); + + free(set_remove(device->tags, tag)); + device->property_tags_outdated = true; + device->tags_generation++; +} + +static int device_tag(sd_device *device, const char *tag, bool add) { + const char *id; + char *path; + int r; + + assert(device); + assert(tag); + + r = device_get_id_filename(device, &id); + if (r < 0) + return r; + + path = strjoina("/run/udev/tags/", tag, "/", id); + + if (add) { + r = touch_file(path, true, USEC_INFINITY, UID_INVALID, GID_INVALID, 0444); + if (r < 0) + return r; + } else { + r = unlink(path); + if (r < 0 && errno != ENOENT) + return -errno; + } + + return 0; +} + +int device_tag_index(sd_device *device, sd_device *device_old, bool add) { + const char *tag; + int r = 0, k; + + if (add && device_old) { + /* delete possible left-over tags */ + FOREACH_DEVICE_TAG(device_old, tag) { + if (!sd_device_has_tag(device, tag)) { + k = device_tag(device_old, tag, false); + if (r >= 0 && k < 0) + r = k; + } + } + } + + FOREACH_DEVICE_TAG(device, tag) { + k = device_tag(device, tag, add); + if (r >= 0 && k < 0) + r = k; + } + + return r; +} + +static bool device_has_info(sd_device *device) { + assert(device); + + if (!set_isempty(device->devlinks)) + return true; + + if (device->devlink_priority != 0) + return true; + + if (!ordered_hashmap_isempty(device->properties_db)) + return true; + + if (!set_isempty(device->tags)) + return true; + + if (device->watch_handle >= 0) + return true; + + return false; +} + +void device_set_db_persist(sd_device *device) { + assert(device); + + device->db_persist = true; +} + +int device_update_db(sd_device *device) { + const char *id; + char *path; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *path_tmp = NULL; + bool has_info; + int r; + + assert(device); + + has_info = device_has_info(device); + + r = device_get_id_filename(device, &id); + if (r < 0) + return r; + + path = strjoina("/run/udev/data/", id); + + /* do not store anything for otherwise empty devices */ + if (!has_info && major(device->devnum) == 0 && device->ifindex == 0) { + r = unlink(path); + if (r < 0 && errno != ENOENT) + return -errno; + + return 0; + } + + /* write a database file */ + r = mkdir_parents(path, 0755); + if (r < 0) + return r; + + r = fopen_temporary(path, &f, &path_tmp); + if (r < 0) + return r; + + /* + * set 'sticky' bit to indicate that we should not clean the + * database when we transition from initramfs to the real root + */ + if (device->db_persist) { + r = fchmod(fileno(f), 01644); + if (r < 0) { + r = -errno; + goto fail; + } + } else { + r = fchmod(fileno(f), 0644); + if (r < 0) { + r = -errno; + goto fail; + } + } + + if (has_info) { + const char *property, *value, *tag; + Iterator i; + + if (major(device->devnum) > 0) { + const char *devlink; + + FOREACH_DEVICE_DEVLINK(device, devlink) + fprintf(f, "S:%s\n", devlink + strlen("/dev/")); + + if (device->devlink_priority != 0) + fprintf(f, "L:%i\n", device->devlink_priority); + + if (device->watch_handle >= 0) + fprintf(f, "W:%i\n", device->watch_handle); + } + + if (device->usec_initialized > 0) + fprintf(f, "I:"USEC_FMT"\n", device->usec_initialized); + + ORDERED_HASHMAP_FOREACH_KEY(value, property, device->properties_db, i) + fprintf(f, "E:%s=%s\n", property, value); + + FOREACH_DEVICE_TAG(device, tag) + fprintf(f, "G:%s\n", tag); + } + + r = fflush_and_check(f); + if (r < 0) + goto fail; + + r = rename(path_tmp, path); + if (r < 0) { + r = -errno; + goto fail; + } + + log_debug("created %s file '%s' for '%s'", has_info ? "db" : "empty", + path, device->devpath); + + return 0; + +fail: + (void) unlink(path); + (void) unlink(path_tmp); + + return log_error_errno(r, "failed to create %s file '%s' for '%s'", has_info ? "db" : "empty", path, device->devpath); +} + +int device_delete_db(sd_device *device) { + const char *id; + char *path; + int r; + + assert(device); + + r = device_get_id_filename(device, &id); + if (r < 0) + return r; + + path = strjoina("/run/udev/data/", id); + + r = unlink(path); + if (r < 0 && errno != ENOENT) + return -errno; + + return 0; +} + +int device_read_db_force(sd_device *device) { + assert(device); + + return device_read_db_aux(device, true); +} diff --git a/src/libsystemd/libsystemd-internal/sd-device/device-private.h b/src/libsystemd/libsystemd-internal/sd-device/device-private.h new file mode 100644 index 0000000000..d6add2f7b2 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-device/device-private.h @@ -0,0 +1,68 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 +#include + +#include + +int device_new_from_nulstr(sd_device **ret, uint8_t *nulstr, size_t len); +int device_new_from_strv(sd_device **ret, char **strv); + +int device_get_id_filename(sd_device *device, const char **ret); + +int device_get_devlink_priority(sd_device *device, int *priority); +int device_get_watch_handle(sd_device *device, int *handle); +int device_get_devnode_mode(sd_device *device, mode_t *mode); +int device_get_devnode_uid(sd_device *device, uid_t *uid); +int device_get_devnode_gid(sd_device *device, gid_t *gid); + +void device_seal(sd_device *device); +void device_set_is_initialized(sd_device *device); +void device_set_watch_handle(sd_device *device, int fd); +void device_set_db_persist(sd_device *device); +void device_set_devlink_priority(sd_device *device, int priority); +int device_ensure_usec_initialized(sd_device *device, sd_device *device_old); +int device_add_devlink(sd_device *device, const char *devlink); +int device_add_property(sd_device *device, const char *property, const char *value); +int device_add_tag(sd_device *device, const char *tag); +void device_remove_tag(sd_device *device, const char *tag); +void device_cleanup_tags(sd_device *device); +void device_cleanup_devlinks(sd_device *device); + +uint64_t device_get_properties_generation(sd_device *device); +uint64_t device_get_tags_generation(sd_device *device); +uint64_t device_get_devlinks_generation(sd_device *device); + +int device_get_properties_nulstr(sd_device *device, const uint8_t **nulstr, size_t *len); +int device_get_properties_strv(sd_device *device, char ***strv); + +int device_rename(sd_device *device, const char *name); +int device_shallow_clone(sd_device *old_device, sd_device **new_device); +int device_clone_with_db(sd_device *old_device, sd_device **new_device); +int device_copy_properties(sd_device *device_dst, sd_device *device_src); +int device_new_from_synthetic_event(sd_device **new_device, const char *syspath, const char *action); + +int device_tag_index(sd_device *dev, sd_device *dev_old, bool add); +int device_update_db(sd_device *device); +int device_delete_db(sd_device *device); +int device_read_db_force(sd_device *device); diff --git a/src/libsystemd/libsystemd-internal/sd-device/device-util.h b/src/libsystemd/libsystemd-internal/sd-device/device-util.h new file mode 100644 index 0000000000..5b42e11de6 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-device/device-util.h @@ -0,0 +1,52 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014-2015 Tom Gundersen + + 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" + +#define FOREACH_DEVICE_PROPERTY(device, key, value) \ + for (key = sd_device_get_property_first(device, &(value)); \ + key; \ + key = sd_device_get_property_next(device, &(value))) + +#define FOREACH_DEVICE_TAG(device, tag) \ + for (tag = sd_device_get_tag_first(device); \ + tag; \ + tag = sd_device_get_tag_next(device)) + +#define FOREACH_DEVICE_SYSATTR(device, attr) \ + for (attr = sd_device_get_sysattr_first(device); \ + attr; \ + attr = sd_device_get_sysattr_next(device)) + +#define FOREACH_DEVICE_DEVLINK(device, devlink) \ + for (devlink = sd_device_get_devlink_first(device); \ + devlink; \ + devlink = sd_device_get_devlink_next(device)) + +#define FOREACH_DEVICE(enumerator, device) \ + for (device = sd_device_enumerator_get_device_first(enumerator); \ + device; \ + device = sd_device_enumerator_get_device_next(enumerator)) + +#define FOREACH_SUBSYSTEM(enumerator, device) \ + for (device = sd_device_enumerator_get_subsystem_first(enumerator); \ + device; \ + device = sd_device_enumerator_get_subsystem_next(enumerator)) diff --git a/src/libsystemd/libsystemd-internal/sd-device/sd-device.c b/src/libsystemd/libsystemd-internal/sd-device/sd-device.c new file mode 100644 index 0000000000..d21a67cd4c --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-device/sd-device.c @@ -0,0 +1,1884 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2014 Tom Gundersen + + 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 +#include + +#include + +#include "alloc-util.h" +#include "device-internal.h" +#include "device-private.h" +#include "device-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "hashmap.h" +#include "macro.h" +#include "parse-util.h" +#include "path-util.h" +#include "set.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "strxcpyx.h" +#include "util.h" + +int device_new_aux(sd_device **ret) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + + assert(ret); + + device = new0(sd_device, 1); + if (!device) + return -ENOMEM; + + device->n_ref = 1; + device->watch_handle = -1; + + *ret = device; + device = NULL; + + return 0; +} + +_public_ sd_device *sd_device_ref(sd_device *device) { + if (device) + assert_se(++ device->n_ref >= 2); + + return device; +} + +_public_ sd_device *sd_device_unref(sd_device *device) { + if (device && -- device->n_ref == 0) { + sd_device_unref(device->parent); + free(device->syspath); + free(device->sysname); + free(device->devtype); + free(device->devname); + free(device->subsystem); + free(device->driver); + free(device->id_filename); + free(device->properties_strv); + free(device->properties_nulstr); + + ordered_hashmap_free_free_free(device->properties); + ordered_hashmap_free_free_free(device->properties_db); + hashmap_free_free_free(device->sysattr_values); + set_free_free(device->sysattrs); + set_free_free(device->tags); + set_free_free(device->devlinks); + + free(device); + } + + return NULL; +} + +int device_add_property_aux(sd_device *device, const char *_key, const char *_value, bool db) { + OrderedHashmap **properties; + + assert(device); + assert(_key); + + if (db) + properties = &device->properties_db; + else + properties = &device->properties; + + if (_value) { + _cleanup_free_ char *key = NULL, *value = NULL, *old_key = NULL, *old_value = NULL; + int r; + + r = ordered_hashmap_ensure_allocated(properties, &string_hash_ops); + if (r < 0) + return r; + + key = strdup(_key); + if (!key) + return -ENOMEM; + + value = strdup(_value); + if (!value) + return -ENOMEM; + + old_value = ordered_hashmap_get2(*properties, key, (void**) &old_key); + + r = ordered_hashmap_replace(*properties, key, value); + if (r < 0) + return r; + + key = NULL; + value = NULL; + } else { + _cleanup_free_ char *key = NULL; + _cleanup_free_ char *value = NULL; + + value = ordered_hashmap_remove2(*properties, _key, (void**) &key); + } + + if (!db) { + device->properties_generation++; + device->properties_buf_outdated = true; + } + + return 0; +} + +int device_add_property_internal(sd_device *device, const char *key, const char *value) { + return device_add_property_aux(device, key, value, false); +} + +int device_set_syspath(sd_device *device, const char *_syspath, bool verify) { + _cleanup_free_ char *syspath = NULL; + const char *devpath; + int r; + + assert(device); + assert(_syspath); + + /* must be a subdirectory of /sys */ + if (!path_startswith(_syspath, "/sys/")) { + log_debug("sd-device: syspath '%s' is not a subdirectory of /sys", _syspath); + return -EINVAL; + } + + if (verify) { + r = readlink_and_canonicalize(_syspath, &syspath); + if (r == -ENOENT) + /* the device does not exist (any more?) */ + return -ENODEV; + else if (r == -EINVAL) { + /* not a symlink */ + syspath = canonicalize_file_name(_syspath); + if (!syspath) { + if (errno == ENOENT) + /* the device does not exist (any more?) */ + return -ENODEV; + + return log_debug_errno(errno, "sd-device: could not canonicalize '%s': %m", _syspath); + } + } else if (r < 0) { + log_debug_errno(r, "sd-device: could not get target of '%s': %m", _syspath); + return r; + } + + if (path_startswith(syspath, "/sys/devices/")) { + char *path; + + /* all 'devices' require an 'uevent' file */ + path = strjoina(syspath, "/uevent"); + r = access(path, F_OK); + if (r < 0) { + if (errno == ENOENT) + /* this is not a valid device */ + return -ENODEV; + + log_debug("sd-device: %s does not have an uevent file: %m", syspath); + return -errno; + } + } else { + /* everything else just just needs to be a directory */ + if (!is_dir(syspath, false)) + return -ENODEV; + } + } else { + syspath = strdup(_syspath); + if (!syspath) + return -ENOMEM; + } + + devpath = syspath + strlen("/sys"); + + r = device_add_property_internal(device, "DEVPATH", devpath); + if (r < 0) + return r; + + free(device->syspath); + device->syspath = syspath; + syspath = NULL; + + device->devpath = devpath; + + return 0; +} + +_public_ int sd_device_new_from_syspath(sd_device **ret, const char *syspath) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + int r; + + assert_return(ret, -EINVAL); + assert_return(syspath, -EINVAL); + + r = device_new_aux(&device); + if (r < 0) + return r; + + r = device_set_syspath(device, syspath, true); + if (r < 0) + return r; + + *ret = device; + device = NULL; + + return 0; +} + +_public_ int sd_device_new_from_devnum(sd_device **ret, char type, dev_t devnum) { + char *syspath; + char id[DECIMAL_STR_MAX(unsigned) * 2 + 1]; + + assert_return(ret, -EINVAL); + assert_return(type == 'b' || type == 'c', -EINVAL); + + /* use /sys/dev/{block,char}/: link */ + snprintf(id, sizeof(id), "%u:%u", major(devnum), minor(devnum)); + + syspath = strjoina("/sys/dev/", (type == 'b' ? "block" : "char"), "/", id); + + return sd_device_new_from_syspath(ret, syspath); +} + +_public_ int sd_device_new_from_subsystem_sysname(sd_device **ret, const char *subsystem, const char *sysname) { + char *syspath; + + assert_return(ret, -EINVAL); + assert_return(subsystem, -EINVAL); + assert_return(sysname, -EINVAL); + + if (streq(subsystem, "subsystem")) { + syspath = strjoina("/sys/subsystem/", sysname); + if (access(syspath, F_OK) >= 0) + return sd_device_new_from_syspath(ret, syspath); + + syspath = strjoina("/sys/bus/", sysname); + if (access(syspath, F_OK) >= 0) + return sd_device_new_from_syspath(ret, syspath); + + syspath = strjoina("/sys/class/", sysname); + if (access(syspath, F_OK) >= 0) + return sd_device_new_from_syspath(ret, syspath); + } else if (streq(subsystem, "module")) { + syspath = strjoina("/sys/module/", sysname); + if (access(syspath, F_OK) >= 0) + return sd_device_new_from_syspath(ret, syspath); + } else if (streq(subsystem, "drivers")) { + char subsys[PATH_MAX]; + char *driver; + + strscpy(subsys, sizeof(subsys), sysname); + driver = strchr(subsys, ':'); + if (driver) { + driver[0] = '\0'; + driver++; + + syspath = strjoina("/sys/subsystem/", subsys, "/drivers/", driver); + if (access(syspath, F_OK) >= 0) + return sd_device_new_from_syspath(ret, syspath); + + syspath = strjoina("/sys/bus/", subsys, "/drivers/", driver); + if (access(syspath, F_OK) >= 0) + return sd_device_new_from_syspath(ret, syspath); + } else + return -EINVAL; + } else { + char *name; + size_t len = 0; + + /* translate sysname back to sysfs filename */ + name = strdupa(sysname); + while (name[len] != '\0') { + if (name[len] == '/') + name[len] = '!'; + + len++; + } + + syspath = strjoina("/sys/subsystem/", subsystem, "/devices/", name); + if (access(syspath, F_OK) >= 0) + return sd_device_new_from_syspath(ret, syspath); + + syspath = strjoina("/sys/bus/", subsystem, "/devices/", name); + if (access(syspath, F_OK) >= 0) + return sd_device_new_from_syspath(ret, syspath); + + syspath = strjoina("/sys/class/", subsystem, "/", name); + if (access(syspath, F_OK) >= 0) + return sd_device_new_from_syspath(ret, syspath); + } + + return -ENODEV; +} + +int device_set_devtype(sd_device *device, const char *_devtype) { + _cleanup_free_ char *devtype = NULL; + int r; + + assert(device); + assert(_devtype); + + devtype = strdup(_devtype); + if (!devtype) + return -ENOMEM; + + r = device_add_property_internal(device, "DEVTYPE", devtype); + if (r < 0) + return r; + + free(device->devtype); + device->devtype = devtype; + devtype = NULL; + + return 0; +} + +int device_set_ifindex(sd_device *device, const char *_ifindex) { + int ifindex, r; + + assert(device); + assert(_ifindex); + + r = parse_ifindex(_ifindex, &ifindex); + if (r < 0) + return r; + + r = device_add_property_internal(device, "IFINDEX", _ifindex); + if (r < 0) + return r; + + device->ifindex = ifindex; + + return 0; +} + +int device_set_devname(sd_device *device, const char *_devname) { + _cleanup_free_ char *devname = NULL; + int r; + + assert(device); + assert(_devname); + + if (_devname[0] != '/') { + r = asprintf(&devname, "/dev/%s", _devname); + if (r < 0) + return -ENOMEM; + } else { + devname = strdup(_devname); + if (!devname) + return -ENOMEM; + } + + r = device_add_property_internal(device, "DEVNAME", devname); + if (r < 0) + return r; + + free(device->devname); + device->devname = devname; + devname = NULL; + + return 0; +} + +int device_set_devmode(sd_device *device, const char *_devmode) { + unsigned devmode; + int r; + + assert(device); + assert(_devmode); + + r = safe_atou(_devmode, &devmode); + if (r < 0) + return r; + + if (devmode > 07777) + return -EINVAL; + + r = device_add_property_internal(device, "DEVMODE", _devmode); + if (r < 0) + return r; + + device->devmode = devmode; + + return 0; +} + +int device_set_devnum(sd_device *device, const char *major, const char *minor) { + unsigned maj = 0, min = 0; + int r; + + assert(device); + assert(major); + + r = safe_atou(major, &maj); + if (r < 0) + return r; + if (!maj) + return 0; + + if (minor) { + r = safe_atou(minor, &min); + if (r < 0) + return r; + } + + r = device_add_property_internal(device, "MAJOR", major); + if (r < 0) + return r; + + if (minor) { + r = device_add_property_internal(device, "MINOR", minor); + if (r < 0) + return r; + } + + device->devnum = makedev(maj, min); + + return 0; +} + +static int handle_uevent_line(sd_device *device, const char *key, const char *value, const char **major, const char **minor) { + int r; + + assert(device); + assert(key); + assert(value); + assert(major); + assert(minor); + + if (streq(key, "DEVTYPE")) { + r = device_set_devtype(device, value); + if (r < 0) + return r; + } else if (streq(key, "IFINDEX")) { + r = device_set_ifindex(device, value); + if (r < 0) + return r; + } else if (streq(key, "DEVNAME")) { + r = device_set_devname(device, value); + if (r < 0) + return r; + } else if (streq(key, "DEVMODE")) { + r = device_set_devmode(device, value); + if (r < 0) + return r; + } else if (streq(key, "MAJOR")) + *major = value; + else if (streq(key, "MINOR")) + *minor = value; + else { + r = device_add_property_internal(device, key, value); + if (r < 0) + return r; + } + + return 0; +} + +int device_read_uevent_file(sd_device *device) { + _cleanup_free_ char *uevent = NULL; + const char *syspath, *key = NULL, *value = NULL, *major = NULL, *minor = NULL; + char *path; + size_t uevent_len; + unsigned i; + int r; + + enum { + PRE_KEY, + KEY, + PRE_VALUE, + VALUE, + INVALID_LINE, + } state = PRE_KEY; + + assert(device); + + if (device->uevent_loaded || device->sealed) + return 0; + + device->uevent_loaded = true; + + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + path = strjoina(syspath, "/uevent"); + + r = read_full_file(path, &uevent, &uevent_len); + if (r == -EACCES) + /* empty uevent files may be write-only */ + return 0; + else if (r == -ENOENT) + /* some devices may not have uevent files, see set_syspath() */ + return 0; + else if (r < 0) { + log_debug_errno(r, "sd-device: failed to read uevent file '%s': %m", path); + return r; + } + + for (i = 0; i < uevent_len; i++) { + switch (state) { + case PRE_KEY: + if (!strchr(NEWLINE, uevent[i])) { + key = &uevent[i]; + + state = KEY; + } + + break; + case KEY: + if (uevent[i] == '=') { + uevent[i] = '\0'; + + state = PRE_VALUE; + } else if (strchr(NEWLINE, uevent[i])) { + uevent[i] = '\0'; + log_debug("sd-device: ignoring invalid uevent line '%s'", key); + + state = PRE_KEY; + } + + break; + case PRE_VALUE: + value = &uevent[i]; + + state = VALUE; + + break; + case VALUE: + if (strchr(NEWLINE, uevent[i])) { + uevent[i] = '\0'; + + r = handle_uevent_line(device, key, value, &major, &minor); + if (r < 0) + log_debug_errno(r, "sd-device: failed to handle uevent entry '%s=%s': %m", key, value); + + state = PRE_KEY; + } + + break; + default: + assert_not_reached("invalid state when parsing uevent file"); + } + } + + if (major) { + r = device_set_devnum(device, major, minor); + if (r < 0) + log_debug_errno(r, "sd-device: could not set 'MAJOR=%s' or 'MINOR=%s' from '%s': %m", major, minor, path); + } + + return 0; +} + +_public_ int sd_device_get_ifindex(sd_device *device, int *ifindex) { + int r; + + assert_return(device, -EINVAL); + assert_return(ifindex, -EINVAL); + + r = device_read_uevent_file(device); + if (r < 0) + return r; + + *ifindex = device->ifindex; + + return 0; +} + +_public_ int sd_device_new_from_device_id(sd_device **ret, const char *id) { + int r; + + assert_return(ret, -EINVAL); + assert_return(id, -EINVAL); + + switch (id[0]) { + case 'b': + case 'c': + { + char type; + int maj, min; + + r = sscanf(id, "%c%i:%i", &type, &maj, &min); + if (r != 3) + return -EINVAL; + + return sd_device_new_from_devnum(ret, type, makedev(maj, min)); + } + case 'n': + { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + _cleanup_close_ int sk = -1; + struct ifreq ifr = {}; + int ifindex; + + r = parse_ifindex(&id[1], &ifr.ifr_ifindex); + if (r < 0) + return r; + + sk = socket(PF_INET, SOCK_DGRAM, 0); + if (sk < 0) + return -errno; + + r = ioctl(sk, SIOCGIFNAME, &ifr); + if (r < 0) + return -errno; + + r = sd_device_new_from_subsystem_sysname(&device, "net", ifr.ifr_name); + if (r < 0) + return r; + + r = sd_device_get_ifindex(device, &ifindex); + if (r < 0) + return r; + + /* this is racey, so we might end up with the wrong device */ + if (ifr.ifr_ifindex != ifindex) + return -ENODEV; + + *ret = device; + device = NULL; + + return 0; + } + case '+': + { + char subsys[PATH_MAX]; + char *sysname; + + (void)strscpy(subsys, sizeof(subsys), id + 1); + sysname = strchr(subsys, ':'); + if (!sysname) + return -EINVAL; + + sysname[0] = '\0'; + sysname++; + + return sd_device_new_from_subsystem_sysname(ret, subsys, sysname); + } + default: + return -EINVAL; + } +} + +_public_ int sd_device_get_syspath(sd_device *device, const char **ret) { + assert_return(device, -EINVAL); + assert_return(ret, -EINVAL); + + assert(path_startswith(device->syspath, "/sys/")); + + *ret = device->syspath; + + return 0; +} + +static int device_new_from_child(sd_device **ret, sd_device *child) { + _cleanup_free_ char *path = NULL; + const char *subdir, *syspath; + int r; + + assert(ret); + assert(child); + + r = sd_device_get_syspath(child, &syspath); + if (r < 0) + return r; + + path = strdup(syspath); + if (!path) + return -ENOMEM; + subdir = path + strlen("/sys"); + + for (;;) { + char *pos; + + pos = strrchr(subdir, '/'); + if (!pos || pos < subdir + 2) + break; + + *pos = '\0'; + + r = sd_device_new_from_syspath(ret, path); + if (r < 0) + continue; + + return 0; + } + + return -ENODEV; +} + +_public_ int sd_device_get_parent(sd_device *child, sd_device **ret) { + + assert_return(ret, -EINVAL); + assert_return(child, -EINVAL); + + if (!child->parent_set) { + child->parent_set = true; + + (void)device_new_from_child(&child->parent, child); + } + + if (!child->parent) + return -ENOENT; + + *ret = child->parent; + + return 0; +} + +int device_set_subsystem(sd_device *device, const char *_subsystem) { + _cleanup_free_ char *subsystem = NULL; + int r; + + assert(device); + assert(_subsystem); + + subsystem = strdup(_subsystem); + if (!subsystem) + return -ENOMEM; + + r = device_add_property_internal(device, "SUBSYSTEM", subsystem); + if (r < 0) + return r; + + free(device->subsystem); + device->subsystem = subsystem; + subsystem = NULL; + + device->subsystem_set = true; + + return 0; +} + +_public_ int sd_device_get_subsystem(sd_device *device, const char **ret) { + assert_return(ret, -EINVAL); + assert_return(device, -EINVAL); + + if (!device->subsystem_set) { + _cleanup_free_ char *subsystem = NULL; + const char *syspath; + char *path; + int r; + + /* read 'subsystem' link */ + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + path = strjoina(syspath, "/subsystem"); + r = readlink_value(path, &subsystem); + if (r >= 0) + r = device_set_subsystem(device, subsystem); + /* use implicit names */ + else if (path_startswith(device->devpath, "/module/")) + r = device_set_subsystem(device, "module"); + else if (strstr(device->devpath, "/drivers/")) + r = device_set_subsystem(device, "drivers"); + else if (path_startswith(device->devpath, "/subsystem/") || + path_startswith(device->devpath, "/class/") || + path_startswith(device->devpath, "/bus/")) + r = device_set_subsystem(device, "subsystem"); + if (r < 0 && r != -ENOENT) + return log_debug_errno(r, "sd-device: could not set subsystem for %s: %m", device->devpath); + + device->subsystem_set = true; + } + + if (!device->subsystem) + return -ENOENT; + + *ret = device->subsystem; + + return 0; +} + +_public_ int sd_device_get_devtype(sd_device *device, const char **devtype) { + int r; + + assert(devtype); + assert(device); + + r = device_read_uevent_file(device); + if (r < 0) + return r; + + *devtype = device->devtype; + + return 0; +} + +_public_ int sd_device_get_parent_with_subsystem_devtype(sd_device *child, const char *subsystem, const char *devtype, sd_device **ret) { + sd_device *parent = NULL; + int r; + + assert_return(child, -EINVAL); + assert_return(subsystem, -EINVAL); + + r = sd_device_get_parent(child, &parent); + while (r >= 0) { + const char *parent_subsystem = NULL; + const char *parent_devtype = NULL; + + (void)sd_device_get_subsystem(parent, &parent_subsystem); + if (streq_ptr(parent_subsystem, subsystem)) { + if (!devtype) + break; + + (void)sd_device_get_devtype(parent, &parent_devtype); + if (streq_ptr(parent_devtype, devtype)) + break; + } + r = sd_device_get_parent(parent, &parent); + } + + if (r < 0) + return r; + + *ret = parent; + + return 0; +} + +_public_ int sd_device_get_devnum(sd_device *device, dev_t *devnum) { + int r; + + assert_return(device, -EINVAL); + assert_return(devnum, -EINVAL); + + r = device_read_uevent_file(device); + if (r < 0) + return r; + + *devnum = device->devnum; + + return 0; +} + +int device_set_driver(sd_device *device, const char *_driver) { + _cleanup_free_ char *driver = NULL; + int r; + + assert(device); + assert(_driver); + + driver = strdup(_driver); + if (!driver) + return -ENOMEM; + + r = device_add_property_internal(device, "DRIVER", driver); + if (r < 0) + return r; + + free(device->driver); + device->driver = driver; + driver = NULL; + + device->driver_set = true; + + return 0; +} + +_public_ int sd_device_get_driver(sd_device *device, const char **ret) { + assert_return(device, -EINVAL); + assert_return(ret, -EINVAL); + + if (!device->driver_set) { + _cleanup_free_ char *driver = NULL; + const char *syspath; + char *path; + int r; + + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + path = strjoina(syspath, "/driver"); + r = readlink_value(path, &driver); + if (r >= 0) { + r = device_set_driver(device, driver); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set driver for %s: %m", device->devpath); + } else if (r == -ENOENT) + device->driver_set = true; + else + return log_debug_errno(r, "sd-device: could not set driver for %s: %m", device->devpath); + } + + if (!device->driver) + return -ENOENT; + + *ret = device->driver; + + return 0; +} + +_public_ int sd_device_get_devpath(sd_device *device, const char **devpath) { + assert_return(device, -EINVAL); + assert_return(devpath, -EINVAL); + + assert(device->devpath); + assert(device->devpath[0] == '/'); + + *devpath = device->devpath; + + return 0; +} + +_public_ int sd_device_get_devname(sd_device *device, const char **devname) { + int r; + + assert_return(device, -EINVAL); + assert_return(devname, -EINVAL); + + r = device_read_uevent_file(device); + if (r < 0) + return r; + + if (!device->devname) + return -ENOENT; + + assert(path_startswith(device->devname, "/dev/")); + + *devname = device->devname; + + return 0; +} + +static int device_set_sysname(sd_device *device) { + _cleanup_free_ char *sysname = NULL; + const char *sysnum = NULL; + const char *pos; + size_t len = 0; + + pos = strrchr(device->devpath, '/'); + if (!pos) + return -EINVAL; + pos++; + + /* devpath is not a root directory */ + if (*pos == '\0' || pos <= device->devpath) + return -EINVAL; + + sysname = strdup(pos); + if (!sysname) + return -ENOMEM; + + /* some devices have '!' in their name, change that to '/' */ + while (sysname[len] != '\0') { + if (sysname[len] == '!') + sysname[len] = '/'; + + len++; + } + + /* trailing number */ + while (len > 0 && isdigit(sysname[--len])) + sysnum = &sysname[len]; + + if (len == 0) + sysnum = NULL; + + free(device->sysname); + device->sysname = sysname; + sysname = NULL; + + device->sysnum = sysnum; + + device->sysname_set = true; + + return 0; +} + +_public_ int sd_device_get_sysname(sd_device *device, const char **ret) { + int r; + + assert_return(device, -EINVAL); + assert_return(ret, -EINVAL); + + if (!device->sysname_set) { + r = device_set_sysname(device); + if (r < 0) + return r; + } + + assert_return(device->sysname, -ENOENT); + + *ret = device->sysname; + + return 0; +} + +_public_ int sd_device_get_sysnum(sd_device *device, const char **ret) { + int r; + + assert_return(device, -EINVAL); + assert_return(ret, -EINVAL); + + if (!device->sysname_set) { + r = device_set_sysname(device); + if (r < 0) + return r; + } + + *ret = device->sysnum; + + return 0; +} + +static bool is_valid_tag(const char *tag) { + assert(tag); + + return !strchr(tag, ':') && !strchr(tag, ' '); +} + +int device_add_tag(sd_device *device, const char *tag) { + int r; + + assert(device); + assert(tag); + + if (!is_valid_tag(tag)) + return -EINVAL; + + r = set_ensure_allocated(&device->tags, &string_hash_ops); + if (r < 0) + return r; + + r = set_put_strdup(device->tags, tag); + if (r < 0) + return r; + + device->tags_generation++; + device->property_tags_outdated = true; + + return 0; +} + +int device_add_devlink(sd_device *device, const char *devlink) { + int r; + + assert(device); + assert(devlink); + + r = set_ensure_allocated(&device->devlinks, &string_hash_ops); + if (r < 0) + return r; + + r = set_put_strdup(device->devlinks, devlink); + if (r < 0) + return r; + + device->devlinks_generation++; + device->property_devlinks_outdated = true; + + return 0; +} + +static int device_add_property_internal_from_string(sd_device *device, const char *str) { + _cleanup_free_ char *key = NULL; + char *value; + + assert(device); + assert(str); + + key = strdup(str); + if (!key) + return -ENOMEM; + + value = strchr(key, '='); + if (!value) + return -EINVAL; + + *value = '\0'; + + if (isempty(++value)) + value = NULL; + + return device_add_property_internal(device, key, value); +} + +int device_set_usec_initialized(sd_device *device, const char *initialized) { + uint64_t usec_initialized; + int r; + + assert(device); + assert(initialized); + + r = safe_atou64(initialized, &usec_initialized); + if (r < 0) + return r; + + r = device_add_property_internal(device, "USEC_INITIALIZED", initialized); + if (r < 0) + return r; + + device->usec_initialized = usec_initialized; + + return 0; +} + +static int handle_db_line(sd_device *device, char key, const char *value) { + char *path; + int r; + + assert(device); + assert(value); + + switch (key) { + case 'G': + r = device_add_tag(device, value); + if (r < 0) + return r; + + break; + case 'S': + path = strjoina("/dev/", value); + r = device_add_devlink(device, path); + if (r < 0) + return r; + + break; + case 'E': + r = device_add_property_internal_from_string(device, value); + if (r < 0) + return r; + + break; + case 'I': + r = device_set_usec_initialized(device, value); + if (r < 0) + return r; + + break; + case 'L': + r = safe_atoi(value, &device->devlink_priority); + if (r < 0) + return r; + + break; + case 'W': + r = safe_atoi(value, &device->watch_handle); + if (r < 0) + return r; + + break; + default: + log_debug("device db: unknown key '%c'", key); + } + + return 0; +} + +int device_get_id_filename(sd_device *device, const char **ret) { + assert(device); + assert(ret); + + if (!device->id_filename) { + _cleanup_free_ char *id = NULL; + const char *subsystem; + dev_t devnum; + int ifindex, r; + + r = sd_device_get_subsystem(device, &subsystem); + if (r < 0) + return r; + + r = sd_device_get_devnum(device, &devnum); + if (r < 0) + return r; + + r = sd_device_get_ifindex(device, &ifindex); + if (r < 0) + return r; + + if (major(devnum) > 0) { + assert(subsystem); + + /* use dev_t — b259:131072, c254:0 */ + r = asprintf(&id, "%c%u:%u", + streq(subsystem, "block") ? 'b' : 'c', + major(devnum), minor(devnum)); + if (r < 0) + return -ENOMEM; + } else if (ifindex > 0) { + /* use netdev ifindex — n3 */ + r = asprintf(&id, "n%u", ifindex); + if (r < 0) + return -ENOMEM; + } else { + /* use $subsys:$sysname — pci:0000:00:1f.2 + * sysname() has '!' translated, get it from devpath + */ + const char *sysname; + + sysname = basename(device->devpath); + if (!sysname) + return -EINVAL; + + if (!subsystem) + return -EINVAL; + + r = asprintf(&id, "+%s:%s", subsystem, sysname); + if (r < 0) + return -ENOMEM; + } + + device->id_filename = id; + id = NULL; + } + + *ret = device->id_filename; + + return 0; +} + +int device_read_db_aux(sd_device *device, bool force) { + _cleanup_free_ char *db = NULL; + char *path; + const char *id, *value; + char key; + size_t db_len; + unsigned i; + int r; + + enum { + PRE_KEY, + KEY, + PRE_VALUE, + VALUE, + INVALID_LINE, + } state = PRE_KEY; + + if (device->db_loaded || (!force && device->sealed)) + return 0; + + device->db_loaded = true; + + r = device_get_id_filename(device, &id); + if (r < 0) + return r; + + path = strjoina("/run/udev/data/", id); + + r = read_full_file(path, &db, &db_len); + if (r < 0) { + if (r == -ENOENT) + return 0; + else + return log_debug_errno(r, "sd-device: failed to read db '%s': %m", path); + } + + /* devices with a database entry are initialized */ + device->is_initialized = true; + + for (i = 0; i < db_len; i++) { + switch (state) { + case PRE_KEY: + if (!strchr(NEWLINE, db[i])) { + key = db[i]; + + state = KEY; + } + + break; + case KEY: + if (db[i] != ':') { + log_debug("sd-device: ignoring invalid db entry with key '%c'", key); + + state = INVALID_LINE; + } else { + db[i] = '\0'; + + state = PRE_VALUE; + } + + break; + case PRE_VALUE: + value = &db[i]; + + state = VALUE; + + break; + case INVALID_LINE: + if (strchr(NEWLINE, db[i])) + state = PRE_KEY; + + break; + case VALUE: + if (strchr(NEWLINE, db[i])) { + db[i] = '\0'; + r = handle_db_line(device, key, value); + if (r < 0) + log_debug_errno(r, "sd-device: failed to handle db entry '%c:%s': %m", key, value); + + state = PRE_KEY; + } + + break; + default: + assert_not_reached("invalid state when parsing db"); + } + } + + return 0; +} + +static int device_read_db(sd_device *device) { + return device_read_db_aux(device, false); +} + +_public_ int sd_device_get_is_initialized(sd_device *device, int *initialized) { + int r; + + assert_return(device, -EINVAL); + assert_return(initialized, -EINVAL); + + r = device_read_db(device); + if (r < 0) + return r; + + *initialized = device->is_initialized; + + return 0; +} + +_public_ int sd_device_get_usec_since_initialized(sd_device *device, uint64_t *usec) { + usec_t now_ts; + int r; + + assert_return(device, -EINVAL); + assert_return(usec, -EINVAL); + + r = device_read_db(device); + if (r < 0) + return r; + + if (!device->is_initialized) + return -EBUSY; + + if (!device->usec_initialized) + return -ENODATA; + + now_ts = now(clock_boottime_or_monotonic()); + + if (now_ts < device->usec_initialized) + return -EIO; + + *usec = now_ts - device->usec_initialized; + + return 0; +} + +_public_ const char *sd_device_get_tag_first(sd_device *device) { + void *v; + + assert_return(device, NULL); + + (void) device_read_db(device); + + device->tags_iterator_generation = device->tags_generation; + device->tags_iterator = ITERATOR_FIRST; + + (void) set_iterate(device->tags, &device->tags_iterator, &v); + return v; +} + +_public_ const char *sd_device_get_tag_next(sd_device *device) { + void *v; + + assert_return(device, NULL); + + (void) device_read_db(device); + + if (device->tags_iterator_generation != device->tags_generation) + return NULL; + + (void) set_iterate(device->tags, &device->tags_iterator, &v); + return v; +} + +_public_ const char *sd_device_get_devlink_first(sd_device *device) { + void *v; + + assert_return(device, NULL); + + (void) device_read_db(device); + + device->devlinks_iterator_generation = device->devlinks_generation; + device->devlinks_iterator = ITERATOR_FIRST; + + (void) set_iterate(device->devlinks, &device->devlinks_iterator, &v); + return v; +} + +_public_ const char *sd_device_get_devlink_next(sd_device *device) { + void *v; + + assert_return(device, NULL); + + (void) device_read_db(device); + + if (device->devlinks_iterator_generation != device->devlinks_generation) + return NULL; + + (void) set_iterate(device->devlinks, &device->devlinks_iterator, &v); + return v; +} + +static int device_properties_prepare(sd_device *device) { + int r; + + assert(device); + + r = device_read_uevent_file(device); + if (r < 0) + return r; + + r = device_read_db(device); + if (r < 0) + return r; + + if (device->property_devlinks_outdated) { + _cleanup_free_ char *devlinks = NULL; + size_t devlinks_allocated = 0, devlinks_len = 0; + const char *devlink; + + for (devlink = sd_device_get_devlink_first(device); devlink; devlink = sd_device_get_devlink_next(device)) { + char *e; + + if (!GREEDY_REALLOC(devlinks, devlinks_allocated, devlinks_len + strlen(devlink) + 2)) + return -ENOMEM; + if (devlinks_len > 0) + stpcpy(devlinks + devlinks_len++, " "); + e = stpcpy(devlinks + devlinks_len, devlink); + devlinks_len = e - devlinks; + } + + r = device_add_property_internal(device, "DEVLINKS", devlinks); + if (r < 0) + return r; + + device->property_devlinks_outdated = false; + } + + if (device->property_tags_outdated) { + _cleanup_free_ char *tags = NULL; + size_t tags_allocated = 0, tags_len = 0; + const char *tag; + + if (!GREEDY_REALLOC(tags, tags_allocated, 2)) + return -ENOMEM; + stpcpy(tags, ":"); + tags_len++; + + for (tag = sd_device_get_tag_first(device); tag; tag = sd_device_get_tag_next(device)) { + char *e; + + if (!GREEDY_REALLOC(tags, tags_allocated, tags_len + strlen(tag) + 2)) + return -ENOMEM; + e = stpcpy(stpcpy(tags + tags_len, tag), ":"); + tags_len = e - tags; + } + + r = device_add_property_internal(device, "TAGS", tags); + if (r < 0) + return r; + + device->property_tags_outdated = false; + } + + return 0; +} + +_public_ const char *sd_device_get_property_first(sd_device *device, const char **_value) { + const char *key; + const char *value; + int r; + + assert_return(device, NULL); + + r = device_properties_prepare(device); + if (r < 0) + return NULL; + + device->properties_iterator_generation = device->properties_generation; + device->properties_iterator = ITERATOR_FIRST; + + ordered_hashmap_iterate(device->properties, &device->properties_iterator, (void**)&value, (const void**)&key); + + if (_value) + *_value = value; + + return key; +} + +_public_ const char *sd_device_get_property_next(sd_device *device, const char **_value) { + const char *key; + const char *value; + int r; + + assert_return(device, NULL); + + r = device_properties_prepare(device); + if (r < 0) + return NULL; + + if (device->properties_iterator_generation != device->properties_generation) + return NULL; + + ordered_hashmap_iterate(device->properties, &device->properties_iterator, (void**)&value, (const void**)&key); + + if (_value) + *_value = value; + + return key; +} + +static int device_sysattrs_read_all(sd_device *device) { + _cleanup_closedir_ DIR *dir = NULL; + const char *syspath; + struct dirent *dent; + int r; + + assert(device); + + if (device->sysattrs_read) + return 0; + + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + dir = opendir(syspath); + if (!dir) + return -errno; + + r = set_ensure_allocated(&device->sysattrs, &string_hash_ops); + if (r < 0) + return r; + + for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { + char *path; + struct stat statbuf; + + /* only handle symlinks and regular files */ + if (dent->d_type != DT_LNK && dent->d_type != DT_REG) + continue; + + path = strjoina(syspath, "/", dent->d_name); + + if (lstat(path, &statbuf) != 0) + continue; + + if (!(statbuf.st_mode & S_IRUSR)) + continue; + + r = set_put_strdup(device->sysattrs, dent->d_name); + if (r < 0) + return r; + } + + device->sysattrs_read = true; + + return 0; +} + +_public_ const char *sd_device_get_sysattr_first(sd_device *device) { + void *v; + int r; + + assert_return(device, NULL); + + if (!device->sysattrs_read) { + r = device_sysattrs_read_all(device); + if (r < 0) { + errno = -r; + return NULL; + } + } + + device->sysattrs_iterator = ITERATOR_FIRST; + + (void) set_iterate(device->sysattrs, &device->sysattrs_iterator, &v); + return v; +} + +_public_ const char *sd_device_get_sysattr_next(sd_device *device) { + void *v; + + assert_return(device, NULL); + + if (!device->sysattrs_read) + return NULL; + + (void) set_iterate(device->sysattrs, &device->sysattrs_iterator, &v); + return v; +} + +_public_ int sd_device_has_tag(sd_device *device, const char *tag) { + assert_return(device, -EINVAL); + assert_return(tag, -EINVAL); + + (void) device_read_db(device); + + return !!set_contains(device->tags, tag); +} + +_public_ int sd_device_get_property_value(sd_device *device, const char *key, const char **_value) { + char *value; + int r; + + assert_return(device, -EINVAL); + assert_return(key, -EINVAL); + assert_return(_value, -EINVAL); + + r = device_properties_prepare(device); + if (r < 0) + return r; + + value = ordered_hashmap_get(device->properties, key); + if (!value) + return -ENOENT; + + *_value = value; + + return 0; +} + +/* replaces the value if it already exists */ +static int device_add_sysattr_value(sd_device *device, const char *_key, char *value) { + _cleanup_free_ char *key = NULL; + _cleanup_free_ char *value_old = NULL; + int r; + + assert(device); + assert(_key); + + r = hashmap_ensure_allocated(&device->sysattr_values, &string_hash_ops); + if (r < 0) + return r; + + value_old = hashmap_remove2(device->sysattr_values, _key, (void **)&key); + if (!key) { + key = strdup(_key); + if (!key) + return -ENOMEM; + } + + r = hashmap_put(device->sysattr_values, key, value); + if (r < 0) + return r; + + key = NULL; + + return 0; +} + +static int device_get_sysattr_value(sd_device *device, const char *_key, const char **_value) { + const char *key = NULL, *value; + + assert(device); + assert(_key); + + value = hashmap_get2(device->sysattr_values, _key, (void **) &key); + if (!key) + return -ENOENT; + + if (_value) + *_value = value; + + return 0; +} + +/* We cache all sysattr lookups. If an attribute does not exist, it is stored + * with a NULL value in the cache, otherwise the returned string is stored */ +_public_ int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **_value) { + _cleanup_free_ char *value = NULL; + const char *syspath, *cached_value = NULL; + char *path; + struct stat statbuf; + int r; + + assert_return(device, -EINVAL); + assert_return(sysattr, -EINVAL); + + /* look for possibly already cached result */ + r = device_get_sysattr_value(device, sysattr, &cached_value); + if (r != -ENOENT) { + if (r < 0) + return r; + + if (!cached_value) + /* we looked up the sysattr before and it did not exist */ + return -ENOENT; + + if (_value) + *_value = cached_value; + + return 0; + } + + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + path = strjoina(syspath, "/", sysattr); + r = lstat(path, &statbuf); + if (r < 0) { + /* remember that we could not access the sysattr */ + r = device_add_sysattr_value(device, sysattr, NULL); + if (r < 0) + return r; + + return -ENOENT; + } else if (S_ISLNK(statbuf.st_mode)) { + /* Some core links return only the last element of the target path, + * these are just values, the paths should not be exposed. */ + if (STR_IN_SET(sysattr, "driver", "subsystem", "module")) { + r = readlink_value(path, &value); + if (r < 0) + return r; + } else + return -EINVAL; + } else if (S_ISDIR(statbuf.st_mode)) { + /* skip directories */ + return -EINVAL; + } else if (!(statbuf.st_mode & S_IRUSR)) { + /* skip non-readable files */ + return -EPERM; + } else { + size_t size; + + /* read attribute value */ + r = read_full_file(path, &value, &size); + if (r < 0) + return r; + + /* drop trailing newlines */ + while (size > 0 && value[--size] == '\n') + value[size] = '\0'; + } + + r = device_add_sysattr_value(device, sysattr, value); + if (r < 0) + return r; + + *_value = value; + value = NULL; + + return 0; +} + +static void device_remove_sysattr_value(sd_device *device, const char *_key) { + _cleanup_free_ char *key = NULL; + _cleanup_free_ char *value = NULL; + + assert(device); + assert(_key); + + value = hashmap_remove2(device->sysattr_values, _key, (void **) &key); + + return; +} + +/* set the attribute and save it in the cache. If a NULL value is passed the + * attribute is cleared from the cache */ +_public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, char *_value) { + _cleanup_close_ int fd = -1; + _cleanup_free_ char *value = NULL; + const char *syspath; + char *path; + struct stat statbuf; + size_t value_len = 0; + ssize_t size; + int r; + + assert_return(device, -EINVAL); + assert_return(sysattr, -EINVAL); + + if (!_value) { + device_remove_sysattr_value(device, sysattr); + + return 0; + } + + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + path = strjoina(syspath, "/", sysattr); + r = lstat(path, &statbuf); + if (r < 0) { + value = strdup(""); + if (!value) + return -ENOMEM; + + r = device_add_sysattr_value(device, sysattr, value); + if (r < 0) + return r; + + return -ENXIO; + } + + if (S_ISLNK(statbuf.st_mode)) + return -EINVAL; + + /* skip directories */ + if (S_ISDIR(statbuf.st_mode)) + return -EISDIR; + + /* skip non-readable files */ + if ((statbuf.st_mode & S_IRUSR) == 0) + return -EACCES; + + value_len = strlen(_value); + + /* drop trailing newlines */ + while (value_len > 0 && _value[value_len - 1] == '\n') + _value[--value_len] = '\0'; + + /* value length is limited to 4k */ + if (value_len > 4096) + return -EINVAL; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd < 0) + return -errno; + + value = strdup(_value); + if (!value) + return -ENOMEM; + + size = write(fd, value, value_len); + if (size < 0) + return -errno; + + if ((size_t)size != value_len) + return -EIO; + + r = device_add_sysattr_value(device, sysattr, value); + if (r < 0) + return r; + + value = NULL; + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-event/sd-event.c b/src/libsystemd/libsystemd-internal/sd-event/sd-event.c new file mode 100644 index 0000000000..7f6f485353 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-event/sd-event.c @@ -0,0 +1,2898 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 +#include + +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "list.h" +#include "macro.h" +#include "missing.h" +#include "prioq.h" +#include "process-util.h" +#include "set.h" +#include "signal-util.h" +#include "string-table.h" +#include "string-util.h" +#include "time-util.h" +#include "util.h" + +#define DEFAULT_ACCURACY_USEC (250 * USEC_PER_MSEC) + +typedef enum EventSourceType { + SOURCE_IO, + SOURCE_TIME_REALTIME, + SOURCE_TIME_BOOTTIME, + SOURCE_TIME_MONOTONIC, + SOURCE_TIME_REALTIME_ALARM, + SOURCE_TIME_BOOTTIME_ALARM, + SOURCE_SIGNAL, + SOURCE_CHILD, + SOURCE_DEFER, + SOURCE_POST, + SOURCE_EXIT, + SOURCE_WATCHDOG, + _SOURCE_EVENT_SOURCE_TYPE_MAX, + _SOURCE_EVENT_SOURCE_TYPE_INVALID = -1 +} EventSourceType; + +static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX] = { + [SOURCE_IO] = "io", + [SOURCE_TIME_REALTIME] = "realtime", + [SOURCE_TIME_BOOTTIME] = "bootime", + [SOURCE_TIME_MONOTONIC] = "monotonic", + [SOURCE_TIME_REALTIME_ALARM] = "realtime-alarm", + [SOURCE_TIME_BOOTTIME_ALARM] = "boottime-alarm", + [SOURCE_SIGNAL] = "signal", + [SOURCE_CHILD] = "child", + [SOURCE_DEFER] = "defer", + [SOURCE_POST] = "post", + [SOURCE_EXIT] = "exit", + [SOURCE_WATCHDOG] = "watchdog", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int); + +/* All objects we use in epoll events start with this value, so that + * we know how to dispatch it */ +typedef enum WakeupType { + WAKEUP_NONE, + WAKEUP_EVENT_SOURCE, + WAKEUP_CLOCK_DATA, + WAKEUP_SIGNAL_DATA, + _WAKEUP_TYPE_MAX, + _WAKEUP_TYPE_INVALID = -1, +} WakeupType; + +#define EVENT_SOURCE_IS_TIME(t) IN_SET((t), SOURCE_TIME_REALTIME, SOURCE_TIME_BOOTTIME, SOURCE_TIME_MONOTONIC, SOURCE_TIME_REALTIME_ALARM, SOURCE_TIME_BOOTTIME_ALARM) + +struct sd_event_source { + WakeupType wakeup; + + unsigned n_ref; + + sd_event *event; + void *userdata; + sd_event_handler_t prepare; + + char *description; + + EventSourceType type:5; + int enabled:3; + bool pending:1; + bool dispatching:1; + bool floating:1; + + int64_t priority; + unsigned pending_index; + unsigned prepare_index; + unsigned pending_iteration; + unsigned prepare_iteration; + + LIST_FIELDS(sd_event_source, sources); + + union { + struct { + sd_event_io_handler_t callback; + int fd; + uint32_t events; + uint32_t revents; + bool registered:1; + } io; + struct { + sd_event_time_handler_t callback; + usec_t next, accuracy; + unsigned earliest_index; + unsigned latest_index; + } time; + struct { + sd_event_signal_handler_t callback; + struct signalfd_siginfo siginfo; + int sig; + } signal; + struct { + sd_event_child_handler_t callback; + siginfo_t siginfo; + pid_t pid; + int options; + } child; + struct { + sd_event_handler_t callback; + } defer; + struct { + sd_event_handler_t callback; + } post; + struct { + sd_event_handler_t callback; + unsigned prioq_index; + } exit; + }; +}; + +struct clock_data { + WakeupType wakeup; + int fd; + + /* For all clocks we maintain two priority queues each, one + * ordered for the earliest times the events may be + * dispatched, and one ordered by the latest times they must + * have been dispatched. The range between the top entries in + * the two prioqs is the time window we can freely schedule + * wakeups in */ + + Prioq *earliest; + Prioq *latest; + usec_t next; + + bool needs_rearm:1; +}; + +struct signal_data { + WakeupType wakeup; + + /* For each priority we maintain one signal fd, so that we + * only have to dequeue a single event per priority at a + * time. */ + + int fd; + int64_t priority; + sigset_t sigset; + sd_event_source *current; +}; + +struct sd_event { + unsigned n_ref; + + int epoll_fd; + int watchdog_fd; + + Prioq *pending; + Prioq *prepare; + + /* timerfd_create() only supports these five clocks so far. We + * can add support for more clocks when the kernel learns to + * deal with them, too. */ + struct clock_data realtime; + struct clock_data boottime; + struct clock_data monotonic; + struct clock_data realtime_alarm; + struct clock_data boottime_alarm; + + usec_t perturb; + + sd_event_source **signal_sources; /* indexed by signal number */ + Hashmap *signal_data; /* indexed by priority */ + + Hashmap *child_sources; + unsigned n_enabled_child_sources; + + Set *post_sources; + + Prioq *exit; + + pid_t original_pid; + + unsigned iteration; + dual_timestamp timestamp; + usec_t timestamp_boottime; + int state; + + bool exit_requested:1; + bool need_process_child:1; + bool watchdog:1; + bool profile_delays:1; + + int exit_code; + + pid_t tid; + sd_event **default_event_ptr; + + usec_t watchdog_last, watchdog_period; + + unsigned n_sources; + + LIST_HEAD(sd_event_source, sources); + + usec_t last_run, last_log; + unsigned delays[sizeof(usec_t) * 8]; +}; + +static void source_disconnect(sd_event_source *s); + +static int pending_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + + assert(x->pending); + assert(y->pending); + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Lower priority values first */ + if (x->priority < y->priority) + return -1; + if (x->priority > y->priority) + return 1; + + /* Older entries first */ + if (x->pending_iteration < y->pending_iteration) + return -1; + if (x->pending_iteration > y->pending_iteration) + return 1; + + return 0; +} + +static int prepare_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + + assert(x->prepare); + assert(y->prepare); + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Move most recently prepared ones last, so that we can stop + * preparing as soon as we hit one that has already been + * prepared in the current iteration */ + if (x->prepare_iteration < y->prepare_iteration) + return -1; + if (x->prepare_iteration > y->prepare_iteration) + return 1; + + /* Lower priority values first */ + if (x->priority < y->priority) + return -1; + if (x->priority > y->priority) + return 1; + + return 0; +} + +static int earliest_time_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + + assert(EVENT_SOURCE_IS_TIME(x->type)); + assert(x->type == y->type); + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Move the pending ones to the end */ + if (!x->pending && y->pending) + return -1; + if (x->pending && !y->pending) + return 1; + + /* Order by time */ + if (x->time.next < y->time.next) + return -1; + if (x->time.next > y->time.next) + return 1; + + return 0; +} + +static usec_t time_event_source_latest(const sd_event_source *s) { + return usec_add(s->time.next, s->time.accuracy); +} + +static int latest_time_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + + assert(EVENT_SOURCE_IS_TIME(x->type)); + assert(x->type == y->type); + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Move the pending ones to the end */ + if (!x->pending && y->pending) + return -1; + if (x->pending && !y->pending) + return 1; + + /* Order by time */ + if (time_event_source_latest(x) < time_event_source_latest(y)) + return -1; + if (time_event_source_latest(x) > time_event_source_latest(y)) + return 1; + + return 0; +} + +static int exit_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + + assert(x->type == SOURCE_EXIT); + assert(y->type == SOURCE_EXIT); + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Lower priority values first */ + if (x->priority < y->priority) + return -1; + if (x->priority > y->priority) + return 1; + + return 0; +} + +static void free_clock_data(struct clock_data *d) { + assert(d); + assert(d->wakeup == WAKEUP_CLOCK_DATA); + + safe_close(d->fd); + prioq_free(d->earliest); + prioq_free(d->latest); +} + +static void event_free(sd_event *e) { + sd_event_source *s; + + assert(e); + + while ((s = e->sources)) { + assert(s->floating); + source_disconnect(s); + sd_event_source_unref(s); + } + + assert(e->n_sources == 0); + + if (e->default_event_ptr) + *(e->default_event_ptr) = NULL; + + safe_close(e->epoll_fd); + safe_close(e->watchdog_fd); + + free_clock_data(&e->realtime); + free_clock_data(&e->boottime); + free_clock_data(&e->monotonic); + free_clock_data(&e->realtime_alarm); + free_clock_data(&e->boottime_alarm); + + prioq_free(e->pending); + prioq_free(e->prepare); + prioq_free(e->exit); + + free(e->signal_sources); + hashmap_free(e->signal_data); + + hashmap_free(e->child_sources); + set_free(e->post_sources); + free(e); +} + +_public_ int sd_event_new(sd_event** ret) { + sd_event *e; + int r; + + assert_return(ret, -EINVAL); + + e = new0(sd_event, 1); + if (!e) + return -ENOMEM; + + e->n_ref = 1; + e->watchdog_fd = e->epoll_fd = e->realtime.fd = e->boottime.fd = e->monotonic.fd = e->realtime_alarm.fd = e->boottime_alarm.fd = -1; + e->realtime.next = e->boottime.next = e->monotonic.next = e->realtime_alarm.next = e->boottime_alarm.next = USEC_INFINITY; + e->realtime.wakeup = e->boottime.wakeup = e->monotonic.wakeup = e->realtime_alarm.wakeup = e->boottime_alarm.wakeup = WAKEUP_CLOCK_DATA; + e->original_pid = getpid(); + e->perturb = USEC_INFINITY; + + r = prioq_ensure_allocated(&e->pending, pending_prioq_compare); + if (r < 0) + goto fail; + + e->epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (e->epoll_fd < 0) { + r = -errno; + goto fail; + } + + if (secure_getenv("SD_EVENT_PROFILE_DELAYS")) { + log_debug("Event loop profiling enabled. Logarithmic histogram of event loop iterations in the range 2^0 ... 2^63 us will be logged every 5s."); + e->profile_delays = true; + } + + *ret = e; + return 0; + +fail: + event_free(e); + return r; +} + +_public_ sd_event* sd_event_ref(sd_event *e) { + + if (!e) + return NULL; + + assert(e->n_ref >= 1); + e->n_ref++; + + return e; +} + +_public_ sd_event* sd_event_unref(sd_event *e) { + + if (!e) + return NULL; + + assert(e->n_ref >= 1); + e->n_ref--; + + if (e->n_ref <= 0) + event_free(e); + + return NULL; +} + +static bool event_pid_changed(sd_event *e) { + assert(e); + + /* We don't support people creating an event loop and keeping + * it around over a fork(). Let's complain. */ + + return e->original_pid != getpid(); +} + +static void source_io_unregister(sd_event_source *s) { + int r; + + assert(s); + assert(s->type == SOURCE_IO); + + if (event_pid_changed(s->event)) + return; + + if (!s->io.registered) + return; + + r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->io.fd, NULL); + if (r < 0) + log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll: %m", + strna(s->description), event_source_type_to_string(s->type)); + + s->io.registered = false; +} + +static int source_io_register( + sd_event_source *s, + int enabled, + uint32_t events) { + + struct epoll_event ev = {}; + int r; + + assert(s); + assert(s->type == SOURCE_IO); + assert(enabled != SD_EVENT_OFF); + + ev.events = events; + ev.data.ptr = s; + + if (enabled == SD_EVENT_ONESHOT) + ev.events |= EPOLLONESHOT; + + if (s->io.registered) + r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_MOD, s->io.fd, &ev); + else + r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_ADD, s->io.fd, &ev); + if (r < 0) + return -errno; + + s->io.registered = true; + + return 0; +} + +static clockid_t event_source_type_to_clock(EventSourceType t) { + + switch (t) { + + case SOURCE_TIME_REALTIME: + return CLOCK_REALTIME; + + case SOURCE_TIME_BOOTTIME: + return CLOCK_BOOTTIME; + + case SOURCE_TIME_MONOTONIC: + return CLOCK_MONOTONIC; + + case SOURCE_TIME_REALTIME_ALARM: + return CLOCK_REALTIME_ALARM; + + case SOURCE_TIME_BOOTTIME_ALARM: + return CLOCK_BOOTTIME_ALARM; + + default: + return (clockid_t) -1; + } +} + +static EventSourceType clock_to_event_source_type(clockid_t clock) { + + switch (clock) { + + case CLOCK_REALTIME: + return SOURCE_TIME_REALTIME; + + case CLOCK_BOOTTIME: + return SOURCE_TIME_BOOTTIME; + + case CLOCK_MONOTONIC: + return SOURCE_TIME_MONOTONIC; + + case CLOCK_REALTIME_ALARM: + return SOURCE_TIME_REALTIME_ALARM; + + case CLOCK_BOOTTIME_ALARM: + return SOURCE_TIME_BOOTTIME_ALARM; + + default: + return _SOURCE_EVENT_SOURCE_TYPE_INVALID; + } +} + +static struct clock_data* event_get_clock_data(sd_event *e, EventSourceType t) { + assert(e); + + switch (t) { + + case SOURCE_TIME_REALTIME: + return &e->realtime; + + case SOURCE_TIME_BOOTTIME: + return &e->boottime; + + case SOURCE_TIME_MONOTONIC: + return &e->monotonic; + + case SOURCE_TIME_REALTIME_ALARM: + return &e->realtime_alarm; + + case SOURCE_TIME_BOOTTIME_ALARM: + return &e->boottime_alarm; + + default: + return NULL; + } +} + +static int event_make_signal_data( + sd_event *e, + int sig, + struct signal_data **ret) { + + struct epoll_event ev = {}; + struct signal_data *d; + bool added = false; + sigset_t ss_copy; + int64_t priority; + int r; + + assert(e); + + if (event_pid_changed(e)) + return -ECHILD; + + if (e->signal_sources && e->signal_sources[sig]) + priority = e->signal_sources[sig]->priority; + else + priority = 0; + + d = hashmap_get(e->signal_data, &priority); + if (d) { + if (sigismember(&d->sigset, sig) > 0) { + if (ret) + *ret = d; + return 0; + } + } else { + r = hashmap_ensure_allocated(&e->signal_data, &uint64_hash_ops); + if (r < 0) + return r; + + d = new0(struct signal_data, 1); + if (!d) + return -ENOMEM; + + d->wakeup = WAKEUP_SIGNAL_DATA; + d->fd = -1; + d->priority = priority; + + r = hashmap_put(e->signal_data, &d->priority, d); + if (r < 0) { + free(d); + return r; + } + + added = true; + } + + ss_copy = d->sigset; + assert_se(sigaddset(&ss_copy, sig) >= 0); + + r = signalfd(d->fd, &ss_copy, SFD_NONBLOCK|SFD_CLOEXEC); + if (r < 0) { + r = -errno; + goto fail; + } + + d->sigset = ss_copy; + + if (d->fd >= 0) { + if (ret) + *ret = d; + return 0; + } + + d->fd = r; + + ev.events = EPOLLIN; + ev.data.ptr = d; + + r = epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, d->fd, &ev); + if (r < 0) { + r = -errno; + goto fail; + } + + if (ret) + *ret = d; + + return 0; + +fail: + if (added) { + d->fd = safe_close(d->fd); + hashmap_remove(e->signal_data, &d->priority); + free(d); + } + + return r; +} + +static void event_unmask_signal_data(sd_event *e, struct signal_data *d, int sig) { + assert(e); + assert(d); + + /* Turns off the specified signal in the signal data + * object. If the signal mask of the object becomes empty that + * way removes it. */ + + if (sigismember(&d->sigset, sig) == 0) + return; + + assert_se(sigdelset(&d->sigset, sig) >= 0); + + if (sigisemptyset(&d->sigset)) { + + /* If all the mask is all-zero we can get rid of the structure */ + hashmap_remove(e->signal_data, &d->priority); + assert(!d->current); + safe_close(d->fd); + free(d); + return; + } + + assert(d->fd >= 0); + + if (signalfd(d->fd, &d->sigset, SFD_NONBLOCK|SFD_CLOEXEC) < 0) + log_debug_errno(errno, "Failed to unset signal bit, ignoring: %m"); +} + +static void event_gc_signal_data(sd_event *e, const int64_t *priority, int sig) { + struct signal_data *d; + static const int64_t zero_priority = 0; + + assert(e); + + /* Rechecks if the specified signal is still something we are + * interested in. If not, we'll unmask it, and possibly drop + * the signalfd for it. */ + + if (sig == SIGCHLD && + e->n_enabled_child_sources > 0) + return; + + if (e->signal_sources && + e->signal_sources[sig] && + e->signal_sources[sig]->enabled != SD_EVENT_OFF) + return; + + /* + * The specified signal might be enabled in three different queues: + * + * 1) the one that belongs to the priority passed (if it is non-NULL) + * 2) the one that belongs to the priority of the event source of the signal (if there is one) + * 3) the 0 priority (to cover the SIGCHLD case) + * + * Hence, let's remove it from all three here. + */ + + if (priority) { + d = hashmap_get(e->signal_data, priority); + if (d) + event_unmask_signal_data(e, d, sig); + } + + if (e->signal_sources && e->signal_sources[sig]) { + d = hashmap_get(e->signal_data, &e->signal_sources[sig]->priority); + if (d) + event_unmask_signal_data(e, d, sig); + } + + d = hashmap_get(e->signal_data, &zero_priority); + if (d) + event_unmask_signal_data(e, d, sig); +} + +static void source_disconnect(sd_event_source *s) { + sd_event *event; + + assert(s); + + if (!s->event) + return; + + assert(s->event->n_sources > 0); + + switch (s->type) { + + case SOURCE_IO: + if (s->io.fd >= 0) + source_io_unregister(s); + + break; + + case SOURCE_TIME_REALTIME: + case SOURCE_TIME_BOOTTIME: + case SOURCE_TIME_MONOTONIC: + case SOURCE_TIME_REALTIME_ALARM: + case SOURCE_TIME_BOOTTIME_ALARM: { + struct clock_data *d; + + d = event_get_clock_data(s->event, s->type); + assert(d); + + prioq_remove(d->earliest, s, &s->time.earliest_index); + prioq_remove(d->latest, s, &s->time.latest_index); + d->needs_rearm = true; + break; + } + + case SOURCE_SIGNAL: + if (s->signal.sig > 0) { + + if (s->event->signal_sources) + s->event->signal_sources[s->signal.sig] = NULL; + + event_gc_signal_data(s->event, &s->priority, s->signal.sig); + } + + break; + + case SOURCE_CHILD: + if (s->child.pid > 0) { + if (s->enabled != SD_EVENT_OFF) { + assert(s->event->n_enabled_child_sources > 0); + s->event->n_enabled_child_sources--; + } + + (void) hashmap_remove(s->event->child_sources, PID_TO_PTR(s->child.pid)); + event_gc_signal_data(s->event, &s->priority, SIGCHLD); + } + + break; + + case SOURCE_DEFER: + /* nothing */ + break; + + case SOURCE_POST: + set_remove(s->event->post_sources, s); + break; + + case SOURCE_EXIT: + prioq_remove(s->event->exit, s, &s->exit.prioq_index); + break; + + default: + assert_not_reached("Wut? I shouldn't exist."); + } + + if (s->pending) + prioq_remove(s->event->pending, s, &s->pending_index); + + if (s->prepare) + prioq_remove(s->event->prepare, s, &s->prepare_index); + + event = s->event; + + s->type = _SOURCE_EVENT_SOURCE_TYPE_INVALID; + s->event = NULL; + LIST_REMOVE(sources, event->sources, s); + event->n_sources--; + + if (!s->floating) + sd_event_unref(event); +} + +static void source_free(sd_event_source *s) { + assert(s); + + source_disconnect(s); + free(s->description); + free(s); +} + +static int source_set_pending(sd_event_source *s, bool b) { + int r; + + assert(s); + assert(s->type != SOURCE_EXIT); + + if (s->pending == b) + return 0; + + s->pending = b; + + if (b) { + s->pending_iteration = s->event->iteration; + + r = prioq_put(s->event->pending, s, &s->pending_index); + if (r < 0) { + s->pending = false; + return r; + } + } else + assert_se(prioq_remove(s->event->pending, s, &s->pending_index)); + + if (EVENT_SOURCE_IS_TIME(s->type)) { + struct clock_data *d; + + d = event_get_clock_data(s->event, s->type); + assert(d); + + prioq_reshuffle(d->earliest, s, &s->time.earliest_index); + prioq_reshuffle(d->latest, s, &s->time.latest_index); + d->needs_rearm = true; + } + + if (s->type == SOURCE_SIGNAL && !b) { + struct signal_data *d; + + d = hashmap_get(s->event->signal_data, &s->priority); + if (d && d->current == s) + d->current = NULL; + } + + return 0; +} + +static sd_event_source *source_new(sd_event *e, bool floating, EventSourceType type) { + sd_event_source *s; + + assert(e); + + s = new0(sd_event_source, 1); + if (!s) + return NULL; + + s->n_ref = 1; + s->event = e; + s->floating = floating; + s->type = type; + s->pending_index = s->prepare_index = PRIOQ_IDX_NULL; + + if (!floating) + sd_event_ref(e); + + LIST_PREPEND(sources, e->sources, s); + e->n_sources++; + + return s; +} + +_public_ int sd_event_add_io( + sd_event *e, + sd_event_source **ret, + int fd, + uint32_t events, + sd_event_io_handler_t callback, + void *userdata) { + + sd_event_source *s; + int r; + + assert_return(e, -EINVAL); + assert_return(fd >= 0, -EBADF); + assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL); + assert_return(callback, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + s = source_new(e, !ret, SOURCE_IO); + if (!s) + return -ENOMEM; + + s->wakeup = WAKEUP_EVENT_SOURCE; + s->io.fd = fd; + s->io.events = events; + s->io.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ON; + + r = source_io_register(s, s->enabled, events); + if (r < 0) { + source_free(s); + return r; + } + + if (ret) + *ret = s; + + return 0; +} + +static void initialize_perturb(sd_event *e) { + sd_id128_t bootid = {}; + + /* When we sleep for longer, we try to realign the wakeup to + the same time wihtin each minute/second/250ms, so that + events all across the system can be coalesced into a single + CPU wakeup. However, let's take some system-specific + randomness for this value, so that in a network of systems + with synced clocks timer events are distributed a + bit. Here, we calculate a perturbation usec offset from the + boot ID. */ + + if (_likely_(e->perturb != USEC_INFINITY)) + return; + + if (sd_id128_get_boot(&bootid) >= 0) + e->perturb = (bootid.qwords[0] ^ bootid.qwords[1]) % USEC_PER_MINUTE; +} + +static int event_setup_timer_fd( + sd_event *e, + struct clock_data *d, + clockid_t clock) { + + struct epoll_event ev = {}; + int r, fd; + + assert(e); + assert(d); + + if (_likely_(d->fd >= 0)) + return 0; + + fd = timerfd_create(clock, TFD_NONBLOCK|TFD_CLOEXEC); + if (fd < 0) + return -errno; + + ev.events = EPOLLIN; + ev.data.ptr = d; + + r = epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, fd, &ev); + if (r < 0) { + safe_close(fd); + return -errno; + } + + d->fd = fd; + return 0; +} + +static int time_exit_callback(sd_event_source *s, uint64_t usec, void *userdata) { + assert(s); + + return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); +} + +_public_ int sd_event_add_time( + sd_event *e, + sd_event_source **ret, + clockid_t clock, + uint64_t usec, + uint64_t accuracy, + sd_event_time_handler_t callback, + void *userdata) { + + EventSourceType type; + sd_event_source *s; + struct clock_data *d; + int r; + + assert_return(e, -EINVAL); + assert_return(accuracy != (uint64_t) -1, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + if (IN_SET(clock, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM) && + !clock_boottime_supported()) + return -EOPNOTSUPP; + + if (!callback) + callback = time_exit_callback; + + type = clock_to_event_source_type(clock); + assert_return(type >= 0, -EOPNOTSUPP); + + d = event_get_clock_data(e, type); + assert(d); + + r = prioq_ensure_allocated(&d->earliest, earliest_time_prioq_compare); + if (r < 0) + return r; + + r = prioq_ensure_allocated(&d->latest, latest_time_prioq_compare); + if (r < 0) + return r; + + if (d->fd < 0) { + r = event_setup_timer_fd(e, d, clock); + if (r < 0) + return r; + } + + s = source_new(e, !ret, type); + if (!s) + return -ENOMEM; + + s->time.next = usec; + s->time.accuracy = accuracy == 0 ? DEFAULT_ACCURACY_USEC : accuracy; + s->time.callback = callback; + s->time.earliest_index = s->time.latest_index = PRIOQ_IDX_NULL; + s->userdata = userdata; + s->enabled = SD_EVENT_ONESHOT; + + d->needs_rearm = true; + + r = prioq_put(d->earliest, s, &s->time.earliest_index); + if (r < 0) + goto fail; + + r = prioq_put(d->latest, s, &s->time.latest_index); + if (r < 0) + goto fail; + + if (ret) + *ret = s; + + return 0; + +fail: + source_free(s); + return r; +} + +static int signal_exit_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + assert(s); + + return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); +} + +_public_ int sd_event_add_signal( + sd_event *e, + sd_event_source **ret, + int sig, + sd_event_signal_handler_t callback, + void *userdata) { + + sd_event_source *s; + struct signal_data *d; + sigset_t ss; + int r; + + assert_return(e, -EINVAL); + assert_return(SIGNAL_VALID(sig), -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!callback) + callback = signal_exit_callback; + + r = pthread_sigmask(SIG_SETMASK, NULL, &ss); + if (r != 0) + return -r; + + if (!sigismember(&ss, sig)) + return -EBUSY; + + if (!e->signal_sources) { + e->signal_sources = new0(sd_event_source*, _NSIG); + if (!e->signal_sources) + return -ENOMEM; + } else if (e->signal_sources[sig]) + return -EBUSY; + + s = source_new(e, !ret, SOURCE_SIGNAL); + if (!s) + return -ENOMEM; + + s->signal.sig = sig; + s->signal.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ON; + + e->signal_sources[sig] = s; + + r = event_make_signal_data(e, sig, &d); + if (r < 0) { + source_free(s); + return r; + } + + /* Use the signal name as description for the event source by default */ + (void) sd_event_source_set_description(s, signal_to_string(sig)); + + if (ret) + *ret = s; + + return 0; +} + +_public_ int sd_event_add_child( + sd_event *e, + sd_event_source **ret, + pid_t pid, + int options, + sd_event_child_handler_t callback, + void *userdata) { + + sd_event_source *s; + int r; + + assert_return(e, -EINVAL); + assert_return(pid > 1, -EINVAL); + assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL); + assert_return(options != 0, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + r = hashmap_ensure_allocated(&e->child_sources, NULL); + if (r < 0) + return r; + + if (hashmap_contains(e->child_sources, PID_TO_PTR(pid))) + return -EBUSY; + + s = source_new(e, !ret, SOURCE_CHILD); + if (!s) + return -ENOMEM; + + s->child.pid = pid; + s->child.options = options; + s->child.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ONESHOT; + + r = hashmap_put(e->child_sources, PID_TO_PTR(pid), s); + if (r < 0) { + source_free(s); + return r; + } + + e->n_enabled_child_sources++; + + r = event_make_signal_data(e, SIGCHLD, NULL); + if (r < 0) { + e->n_enabled_child_sources--; + source_free(s); + return r; + } + + e->need_process_child = true; + + if (ret) + *ret = s; + + return 0; +} + +_public_ int sd_event_add_defer( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + sd_event_source *s; + int r; + + assert_return(e, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + s = source_new(e, !ret, SOURCE_DEFER); + if (!s) + return -ENOMEM; + + s->defer.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ONESHOT; + + r = source_set_pending(s, true); + if (r < 0) { + source_free(s); + return r; + } + + if (ret) + *ret = s; + + return 0; +} + +_public_ int sd_event_add_post( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + sd_event_source *s; + int r; + + assert_return(e, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + r = set_ensure_allocated(&e->post_sources, NULL); + if (r < 0) + return r; + + s = source_new(e, !ret, SOURCE_POST); + if (!s) + return -ENOMEM; + + s->post.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ON; + + r = set_put(e->post_sources, s); + if (r < 0) { + source_free(s); + return r; + } + + if (ret) + *ret = s; + + return 0; +} + +_public_ int sd_event_add_exit( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + sd_event_source *s; + int r; + + assert_return(e, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + r = prioq_ensure_allocated(&e->exit, exit_prioq_compare); + if (r < 0) + return r; + + s = source_new(e, !ret, SOURCE_EXIT); + if (!s) + return -ENOMEM; + + s->exit.callback = callback; + s->userdata = userdata; + s->exit.prioq_index = PRIOQ_IDX_NULL; + s->enabled = SD_EVENT_ONESHOT; + + r = prioq_put(s->event->exit, s, &s->exit.prioq_index); + if (r < 0) { + source_free(s); + return r; + } + + if (ret) + *ret = s; + + return 0; +} + +_public_ sd_event_source* sd_event_source_ref(sd_event_source *s) { + + if (!s) + return NULL; + + assert(s->n_ref >= 1); + s->n_ref++; + + return s; +} + +_public_ sd_event_source* sd_event_source_unref(sd_event_source *s) { + + if (!s) + return NULL; + + assert(s->n_ref >= 1); + s->n_ref--; + + if (s->n_ref <= 0) { + /* Here's a special hack: when we are called from a + * dispatch handler we won't free the event source + * immediately, but we will detach the fd from the + * epoll. This way it is safe for the caller to unref + * the event source and immediately close the fd, but + * we still retain a valid event source object after + * the callback. */ + + if (s->dispatching) { + if (s->type == SOURCE_IO) + source_io_unregister(s); + + source_disconnect(s); + } else + source_free(s); + } + + return NULL; +} + +_public_ int sd_event_source_set_description(sd_event_source *s, const char *description) { + assert_return(s, -EINVAL); + assert_return(!event_pid_changed(s->event), -ECHILD); + + return free_and_strdup(&s->description, description); +} + +_public_ int sd_event_source_get_description(sd_event_source *s, const char **description) { + assert_return(s, -EINVAL); + assert_return(description, -EINVAL); + assert_return(s->description, -ENXIO); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *description = s->description; + return 0; +} + +_public_ sd_event *sd_event_source_get_event(sd_event_source *s) { + assert_return(s, NULL); + + return s->event; +} + +_public_ int sd_event_source_get_pending(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type != SOURCE_EXIT, -EDOM); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + return s->pending; +} + +_public_ int sd_event_source_get_io_fd(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + return s->io.fd; +} + +_public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) { + int r; + + assert_return(s, -EINVAL); + assert_return(fd >= 0, -EBADF); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (s->io.fd == fd) + return 0; + + if (s->enabled == SD_EVENT_OFF) { + s->io.fd = fd; + s->io.registered = false; + } else { + int saved_fd; + + saved_fd = s->io.fd; + assert(s->io.registered); + + s->io.fd = fd; + s->io.registered = false; + + r = source_io_register(s, s->enabled, s->io.events); + if (r < 0) { + s->io.fd = saved_fd; + s->io.registered = true; + return r; + } + + epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, saved_fd, NULL); + } + + return 0; +} + +_public_ int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events) { + assert_return(s, -EINVAL); + assert_return(events, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *events = s->io.events; + return 0; +} + +_public_ int sd_event_source_set_io_events(sd_event_source *s, uint32_t events) { + int r; + + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + /* edge-triggered updates are never skipped, so we can reset edges */ + if (s->io.events == events && !(events & EPOLLET)) + return 0; + + if (s->enabled != SD_EVENT_OFF) { + r = source_io_register(s, s->enabled, events); + if (r < 0) + return r; + } + + s->io.events = events; + source_set_pending(s, false); + + return 0; +} + +_public_ int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revents) { + assert_return(s, -EINVAL); + assert_return(revents, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(s->pending, -ENODATA); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *revents = s->io.revents; + return 0; +} + +_public_ int sd_event_source_get_signal(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_SIGNAL, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + return s->signal.sig; +} + +_public_ int sd_event_source_get_priority(sd_event_source *s, int64_t *priority) { + assert_return(s, -EINVAL); + assert_return(!event_pid_changed(s->event), -ECHILD); + + return s->priority; +} + +_public_ int sd_event_source_set_priority(sd_event_source *s, int64_t priority) { + int r; + + assert_return(s, -EINVAL); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (s->priority == priority) + return 0; + + if (s->type == SOURCE_SIGNAL && s->enabled != SD_EVENT_OFF) { + struct signal_data *old, *d; + + /* Move us from the signalfd belonging to the old + * priority to the signalfd of the new priority */ + + assert_se(old = hashmap_get(s->event->signal_data, &s->priority)); + + s->priority = priority; + + r = event_make_signal_data(s->event, s->signal.sig, &d); + if (r < 0) { + s->priority = old->priority; + return r; + } + + event_unmask_signal_data(s->event, old, s->signal.sig); + } else + s->priority = priority; + + if (s->pending) + prioq_reshuffle(s->event->pending, s, &s->pending_index); + + if (s->prepare) + prioq_reshuffle(s->event->prepare, s, &s->prepare_index); + + if (s->type == SOURCE_EXIT) + prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index); + + return 0; +} + +_public_ int sd_event_source_get_enabled(sd_event_source *s, int *m) { + assert_return(s, -EINVAL); + assert_return(m, -EINVAL); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *m = s->enabled; + return 0; +} + +_public_ int sd_event_source_set_enabled(sd_event_source *s, int m) { + int r; + + assert_return(s, -EINVAL); + assert_return(m == SD_EVENT_OFF || m == SD_EVENT_ON || m == SD_EVENT_ONESHOT, -EINVAL); + assert_return(!event_pid_changed(s->event), -ECHILD); + + /* If we are dead anyway, we are fine with turning off + * sources, but everything else needs to fail. */ + if (s->event->state == SD_EVENT_FINISHED) + return m == SD_EVENT_OFF ? 0 : -ESTALE; + + if (s->enabled == m) + return 0; + + if (m == SD_EVENT_OFF) { + + switch (s->type) { + + case SOURCE_IO: + source_io_unregister(s); + s->enabled = m; + break; + + case SOURCE_TIME_REALTIME: + case SOURCE_TIME_BOOTTIME: + case SOURCE_TIME_MONOTONIC: + case SOURCE_TIME_REALTIME_ALARM: + case SOURCE_TIME_BOOTTIME_ALARM: { + struct clock_data *d; + + s->enabled = m; + d = event_get_clock_data(s->event, s->type); + assert(d); + + prioq_reshuffle(d->earliest, s, &s->time.earliest_index); + prioq_reshuffle(d->latest, s, &s->time.latest_index); + d->needs_rearm = true; + break; + } + + case SOURCE_SIGNAL: + s->enabled = m; + + event_gc_signal_data(s->event, &s->priority, s->signal.sig); + break; + + case SOURCE_CHILD: + s->enabled = m; + + assert(s->event->n_enabled_child_sources > 0); + s->event->n_enabled_child_sources--; + + event_gc_signal_data(s->event, &s->priority, SIGCHLD); + break; + + case SOURCE_EXIT: + s->enabled = m; + prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index); + break; + + case SOURCE_DEFER: + case SOURCE_POST: + s->enabled = m; + break; + + default: + assert_not_reached("Wut? I shouldn't exist."); + } + + } else { + switch (s->type) { + + case SOURCE_IO: + r = source_io_register(s, m, s->io.events); + if (r < 0) + return r; + + s->enabled = m; + break; + + case SOURCE_TIME_REALTIME: + case SOURCE_TIME_BOOTTIME: + case SOURCE_TIME_MONOTONIC: + case SOURCE_TIME_REALTIME_ALARM: + case SOURCE_TIME_BOOTTIME_ALARM: { + struct clock_data *d; + + s->enabled = m; + d = event_get_clock_data(s->event, s->type); + assert(d); + + prioq_reshuffle(d->earliest, s, &s->time.earliest_index); + prioq_reshuffle(d->latest, s, &s->time.latest_index); + d->needs_rearm = true; + break; + } + + case SOURCE_SIGNAL: + + s->enabled = m; + + r = event_make_signal_data(s->event, s->signal.sig, NULL); + if (r < 0) { + s->enabled = SD_EVENT_OFF; + event_gc_signal_data(s->event, &s->priority, s->signal.sig); + return r; + } + + break; + + case SOURCE_CHILD: + + if (s->enabled == SD_EVENT_OFF) + s->event->n_enabled_child_sources++; + + s->enabled = m; + + r = event_make_signal_data(s->event, SIGCHLD, NULL); + if (r < 0) { + s->enabled = SD_EVENT_OFF; + s->event->n_enabled_child_sources--; + event_gc_signal_data(s->event, &s->priority, SIGCHLD); + return r; + } + + break; + + case SOURCE_EXIT: + s->enabled = m; + prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index); + break; + + case SOURCE_DEFER: + case SOURCE_POST: + s->enabled = m; + break; + + default: + assert_not_reached("Wut? I shouldn't exist."); + } + } + + if (s->pending) + prioq_reshuffle(s->event->pending, s, &s->pending_index); + + if (s->prepare) + prioq_reshuffle(s->event->prepare, s, &s->prepare_index); + + return 0; +} + +_public_ int sd_event_source_get_time(sd_event_source *s, uint64_t *usec) { + assert_return(s, -EINVAL); + assert_return(usec, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *usec = s->time.next; + return 0; +} + +_public_ int sd_event_source_set_time(sd_event_source *s, uint64_t usec) { + struct clock_data *d; + + assert_return(s, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + s->time.next = usec; + + source_set_pending(s, false); + + d = event_get_clock_data(s->event, s->type); + assert(d); + + prioq_reshuffle(d->earliest, s, &s->time.earliest_index); + prioq_reshuffle(d->latest, s, &s->time.latest_index); + d->needs_rearm = true; + + return 0; +} + +_public_ int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *usec) { + assert_return(s, -EINVAL); + assert_return(usec, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *usec = s->time.accuracy; + return 0; +} + +_public_ int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec) { + struct clock_data *d; + + assert_return(s, -EINVAL); + assert_return(usec != (uint64_t) -1, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (usec == 0) + usec = DEFAULT_ACCURACY_USEC; + + s->time.accuracy = usec; + + source_set_pending(s, false); + + d = event_get_clock_data(s->event, s->type); + assert(d); + + prioq_reshuffle(d->latest, s, &s->time.latest_index); + d->needs_rearm = true; + + return 0; +} + +_public_ int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock) { + assert_return(s, -EINVAL); + assert_return(clock, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *clock = event_source_type_to_clock(s->type); + return 0; +} + +_public_ int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid) { + assert_return(s, -EINVAL); + assert_return(pid, -EINVAL); + assert_return(s->type == SOURCE_CHILD, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *pid = s->child.pid; + return 0; +} + +_public_ int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t callback) { + int r; + + assert_return(s, -EINVAL); + assert_return(s->type != SOURCE_EXIT, -EDOM); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (s->prepare == callback) + return 0; + + if (callback && s->prepare) { + s->prepare = callback; + return 0; + } + + r = prioq_ensure_allocated(&s->event->prepare, prepare_prioq_compare); + if (r < 0) + return r; + + s->prepare = callback; + + if (callback) { + r = prioq_put(s->event->prepare, s, &s->prepare_index); + if (r < 0) + return r; + } else + prioq_remove(s->event->prepare, s, &s->prepare_index); + + return 0; +} + +_public_ void* sd_event_source_get_userdata(sd_event_source *s) { + assert_return(s, NULL); + + return s->userdata; +} + +_public_ void *sd_event_source_set_userdata(sd_event_source *s, void *userdata) { + void *ret; + + assert_return(s, NULL); + + ret = s->userdata; + s->userdata = userdata; + + return ret; +} + +static usec_t sleep_between(sd_event *e, usec_t a, usec_t b) { + usec_t c; + assert(e); + assert(a <= b); + + if (a <= 0) + return 0; + if (a >= USEC_INFINITY) + return USEC_INFINITY; + + if (b <= a + 1) + return a; + + initialize_perturb(e); + + /* + Find a good time to wake up again between times a and b. We + have two goals here: + + a) We want to wake up as seldom as possible, hence prefer + later times over earlier times. + + b) But if we have to wake up, then let's make sure to + dispatch as much as possible on the entire system. + + We implement this by waking up everywhere at the same time + within any given minute if we can, synchronised via the + perturbation value determined from the boot ID. If we can't, + then we try to find the same spot in every 10s, then 1s and + then 250ms step. Otherwise, we pick the last possible time + to wake up. + */ + + c = (b / USEC_PER_MINUTE) * USEC_PER_MINUTE + e->perturb; + if (c >= b) { + if (_unlikely_(c < USEC_PER_MINUTE)) + return b; + + c -= USEC_PER_MINUTE; + } + + if (c >= a) + return c; + + c = (b / (USEC_PER_SEC*10)) * (USEC_PER_SEC*10) + (e->perturb % (USEC_PER_SEC*10)); + if (c >= b) { + if (_unlikely_(c < USEC_PER_SEC*10)) + return b; + + c -= USEC_PER_SEC*10; + } + + if (c >= a) + return c; + + c = (b / USEC_PER_SEC) * USEC_PER_SEC + (e->perturb % USEC_PER_SEC); + if (c >= b) { + if (_unlikely_(c < USEC_PER_SEC)) + return b; + + c -= USEC_PER_SEC; + } + + if (c >= a) + return c; + + c = (b / (USEC_PER_MSEC*250)) * (USEC_PER_MSEC*250) + (e->perturb % (USEC_PER_MSEC*250)); + if (c >= b) { + if (_unlikely_(c < USEC_PER_MSEC*250)) + return b; + + c -= USEC_PER_MSEC*250; + } + + if (c >= a) + return c; + + return b; +} + +static int event_arm_timer( + sd_event *e, + struct clock_data *d) { + + struct itimerspec its = {}; + sd_event_source *a, *b; + usec_t t; + int r; + + assert(e); + assert(d); + + if (!d->needs_rearm) + return 0; + else + d->needs_rearm = false; + + a = prioq_peek(d->earliest); + if (!a || a->enabled == SD_EVENT_OFF || a->time.next == USEC_INFINITY) { + + if (d->fd < 0) + return 0; + + if (d->next == USEC_INFINITY) + return 0; + + /* disarm */ + r = timerfd_settime(d->fd, TFD_TIMER_ABSTIME, &its, NULL); + if (r < 0) + return r; + + d->next = USEC_INFINITY; + return 0; + } + + b = prioq_peek(d->latest); + assert_se(b && b->enabled != SD_EVENT_OFF); + + t = sleep_between(e, a->time.next, time_event_source_latest(b)); + if (d->next == t) + return 0; + + assert_se(d->fd >= 0); + + if (t == 0) { + /* We don' want to disarm here, just mean some time looooong ago. */ + its.it_value.tv_sec = 0; + its.it_value.tv_nsec = 1; + } else + timespec_store(&its.it_value, t); + + r = timerfd_settime(d->fd, TFD_TIMER_ABSTIME, &its, NULL); + if (r < 0) + return -errno; + + d->next = t; + return 0; +} + +static int process_io(sd_event *e, sd_event_source *s, uint32_t revents) { + assert(e); + assert(s); + assert(s->type == SOURCE_IO); + + /* If the event source was already pending, we just OR in the + * new revents, otherwise we reset the value. The ORing is + * necessary to handle EPOLLONESHOT events properly where + * readability might happen independently of writability, and + * we need to keep track of both */ + + if (s->pending) + s->io.revents |= revents; + else + s->io.revents = revents; + + return source_set_pending(s, true); +} + +static int flush_timer(sd_event *e, int fd, uint32_t events, usec_t *next) { + uint64_t x; + ssize_t ss; + + assert(e); + assert(fd >= 0); + + assert_return(events == EPOLLIN, -EIO); + + ss = read(fd, &x, sizeof(x)); + if (ss < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return -errno; + } + + if (_unlikely_(ss != sizeof(x))) + return -EIO; + + if (next) + *next = USEC_INFINITY; + + return 0; +} + +static int process_timer( + sd_event *e, + usec_t n, + struct clock_data *d) { + + sd_event_source *s; + int r; + + assert(e); + assert(d); + + for (;;) { + s = prioq_peek(d->earliest); + if (!s || + s->time.next > n || + s->enabled == SD_EVENT_OFF || + s->pending) + break; + + r = source_set_pending(s, true); + if (r < 0) + return r; + + prioq_reshuffle(d->earliest, s, &s->time.earliest_index); + prioq_reshuffle(d->latest, s, &s->time.latest_index); + d->needs_rearm = true; + } + + return 0; +} + +static int process_child(sd_event *e) { + sd_event_source *s; + Iterator i; + int r; + + assert(e); + + e->need_process_child = false; + + /* + So, this is ugly. We iteratively invoke waitid() with P_PID + + WNOHANG for each PID we wait for, instead of using + P_ALL. This is because we only want to get child + information of very specific child processes, and not all + of them. We might not have processed the SIGCHLD even of a + previous invocation and we don't want to maintain a + unbounded *per-child* event queue, hence we really don't + want anything flushed out of the kernel's queue that we + don't care about. Since this is O(n) this means that if you + have a lot of processes you probably want to handle SIGCHLD + yourself. + + We do not reap the children here (by using WNOWAIT), this + is only done after the event source is dispatched so that + the callback still sees the process as a zombie. + */ + + HASHMAP_FOREACH(s, e->child_sources, i) { + assert(s->type == SOURCE_CHILD); + + if (s->pending) + continue; + + if (s->enabled == SD_EVENT_OFF) + continue; + + zero(s->child.siginfo); + r = waitid(P_PID, s->child.pid, &s->child.siginfo, + WNOHANG | (s->child.options & WEXITED ? WNOWAIT : 0) | s->child.options); + if (r < 0) + return -errno; + + if (s->child.siginfo.si_pid != 0) { + bool zombie = + s->child.siginfo.si_code == CLD_EXITED || + s->child.siginfo.si_code == CLD_KILLED || + s->child.siginfo.si_code == CLD_DUMPED; + + if (!zombie && (s->child.options & WEXITED)) { + /* If the child isn't dead then let's + * immediately remove the state change + * from the queue, since there's no + * benefit in leaving it queued */ + + assert(s->child.options & (WSTOPPED|WCONTINUED)); + waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG|(s->child.options & (WSTOPPED|WCONTINUED))); + } + + r = source_set_pending(s, true); + if (r < 0) + return r; + } + } + + return 0; +} + +static int process_signal(sd_event *e, struct signal_data *d, uint32_t events) { + bool read_one = false; + int r; + + assert(e); + assert_return(events == EPOLLIN, -EIO); + + /* If there's a signal queued on this priority and SIGCHLD is + on this priority too, then make sure to recheck the + children we watch. This is because we only ever dequeue + the first signal per priority, and if we dequeue one, and + SIGCHLD might be enqueued later we wouldn't know, but we + might have higher priority children we care about hence we + need to check that explicitly. */ + + if (sigismember(&d->sigset, SIGCHLD)) + e->need_process_child = true; + + /* If there's already an event source pending for this + * priority we don't read another */ + if (d->current) + return 0; + + for (;;) { + struct signalfd_siginfo si; + ssize_t n; + sd_event_source *s = NULL; + + n = read(d->fd, &si, sizeof(si)); + if (n < 0) { + if (errno == EAGAIN || errno == EINTR) + return read_one; + + return -errno; + } + + if (_unlikely_(n != sizeof(si))) + return -EIO; + + assert(SIGNAL_VALID(si.ssi_signo)); + + read_one = true; + + if (e->signal_sources) + s = e->signal_sources[si.ssi_signo]; + if (!s) + continue; + if (s->pending) + continue; + + s->signal.siginfo = si; + d->current = s; + + r = source_set_pending(s, true); + if (r < 0) + return r; + + return 1; + } +} + +static int source_dispatch(sd_event_source *s) { + int r = 0; + + assert(s); + assert(s->pending || s->type == SOURCE_EXIT); + + if (s->type != SOURCE_DEFER && s->type != SOURCE_EXIT) { + r = source_set_pending(s, false); + if (r < 0) + return r; + } + + if (s->type != SOURCE_POST) { + sd_event_source *z; + Iterator i; + + /* If we execute a non-post source, let's mark all + * post sources as pending */ + + SET_FOREACH(z, s->event->post_sources, i) { + if (z->enabled == SD_EVENT_OFF) + continue; + + r = source_set_pending(z, true); + if (r < 0) + return r; + } + } + + if (s->enabled == SD_EVENT_ONESHOT) { + r = sd_event_source_set_enabled(s, SD_EVENT_OFF); + if (r < 0) + return r; + } + + s->dispatching = true; + + switch (s->type) { + + case SOURCE_IO: + r = s->io.callback(s, s->io.fd, s->io.revents, s->userdata); + break; + + case SOURCE_TIME_REALTIME: + case SOURCE_TIME_BOOTTIME: + case SOURCE_TIME_MONOTONIC: + case SOURCE_TIME_REALTIME_ALARM: + case SOURCE_TIME_BOOTTIME_ALARM: + r = s->time.callback(s, s->time.next, s->userdata); + break; + + case SOURCE_SIGNAL: + r = s->signal.callback(s, &s->signal.siginfo, s->userdata); + break; + + case SOURCE_CHILD: { + bool zombie; + + zombie = s->child.siginfo.si_code == CLD_EXITED || + s->child.siginfo.si_code == CLD_KILLED || + s->child.siginfo.si_code == CLD_DUMPED; + + r = s->child.callback(s, &s->child.siginfo, s->userdata); + + /* Now, reap the PID for good. */ + if (zombie) + waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG|WEXITED); + + break; + } + + case SOURCE_DEFER: + r = s->defer.callback(s, s->userdata); + break; + + case SOURCE_POST: + r = s->post.callback(s, s->userdata); + break; + + case SOURCE_EXIT: + r = s->exit.callback(s, s->userdata); + break; + + case SOURCE_WATCHDOG: + case _SOURCE_EVENT_SOURCE_TYPE_MAX: + case _SOURCE_EVENT_SOURCE_TYPE_INVALID: + assert_not_reached("Wut? I shouldn't exist."); + } + + s->dispatching = false; + + if (r < 0) + log_debug_errno(r, "Event source %s (type %s) returned error, disabling: %m", + strna(s->description), event_source_type_to_string(s->type)); + + if (s->n_ref == 0) + source_free(s); + else if (r < 0) + sd_event_source_set_enabled(s, SD_EVENT_OFF); + + return 1; +} + +static int event_prepare(sd_event *e) { + int r; + + assert(e); + + for (;;) { + sd_event_source *s; + + s = prioq_peek(e->prepare); + if (!s || s->prepare_iteration == e->iteration || s->enabled == SD_EVENT_OFF) + break; + + s->prepare_iteration = e->iteration; + r = prioq_reshuffle(e->prepare, s, &s->prepare_index); + if (r < 0) + return r; + + assert(s->prepare); + + s->dispatching = true; + r = s->prepare(s, s->userdata); + s->dispatching = false; + + if (r < 0) + log_debug_errno(r, "Prepare callback of event source %s (type %s) returned error, disabling: %m", + strna(s->description), event_source_type_to_string(s->type)); + + if (s->n_ref == 0) + source_free(s); + else if (r < 0) + sd_event_source_set_enabled(s, SD_EVENT_OFF); + } + + return 0; +} + +static int dispatch_exit(sd_event *e) { + sd_event_source *p; + int r; + + assert(e); + + p = prioq_peek(e->exit); + if (!p || p->enabled == SD_EVENT_OFF) { + e->state = SD_EVENT_FINISHED; + return 0; + } + + sd_event_ref(e); + e->iteration++; + e->state = SD_EVENT_EXITING; + + r = source_dispatch(p); + + e->state = SD_EVENT_INITIAL; + sd_event_unref(e); + + return r; +} + +static sd_event_source* event_next_pending(sd_event *e) { + sd_event_source *p; + + assert(e); + + p = prioq_peek(e->pending); + if (!p) + return NULL; + + if (p->enabled == SD_EVENT_OFF) + return NULL; + + return p; +} + +static int arm_watchdog(sd_event *e) { + struct itimerspec its = {}; + usec_t t; + int r; + + assert(e); + assert(e->watchdog_fd >= 0); + + t = sleep_between(e, + e->watchdog_last + (e->watchdog_period / 2), + e->watchdog_last + (e->watchdog_period * 3 / 4)); + + timespec_store(&its.it_value, t); + + /* Make sure we never set the watchdog to 0, which tells the + * kernel to disable it. */ + if (its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0) + its.it_value.tv_nsec = 1; + + r = timerfd_settime(e->watchdog_fd, TFD_TIMER_ABSTIME, &its, NULL); + if (r < 0) + return -errno; + + return 0; +} + +static int process_watchdog(sd_event *e) { + assert(e); + + if (!e->watchdog) + return 0; + + /* Don't notify watchdog too often */ + if (e->watchdog_last + e->watchdog_period / 4 > e->timestamp.monotonic) + return 0; + + sd_notify(false, "WATCHDOG=1"); + e->watchdog_last = e->timestamp.monotonic; + + return arm_watchdog(e); +} + +_public_ int sd_event_prepare(sd_event *e) { + int r; + + assert_return(e, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); + + if (e->exit_requested) + goto pending; + + e->iteration++; + + e->state = SD_EVENT_PREPARING; + r = event_prepare(e); + e->state = SD_EVENT_INITIAL; + if (r < 0) + return r; + + r = event_arm_timer(e, &e->realtime); + if (r < 0) + return r; + + r = event_arm_timer(e, &e->boottime); + if (r < 0) + return r; + + r = event_arm_timer(e, &e->monotonic); + if (r < 0) + return r; + + r = event_arm_timer(e, &e->realtime_alarm); + if (r < 0) + return r; + + r = event_arm_timer(e, &e->boottime_alarm); + if (r < 0) + return r; + + if (event_next_pending(e) || e->need_process_child) + goto pending; + + e->state = SD_EVENT_ARMED; + + return 0; + +pending: + e->state = SD_EVENT_ARMED; + r = sd_event_wait(e, 0); + if (r == 0) + e->state = SD_EVENT_ARMED; + + return r; +} + +_public_ int sd_event_wait(sd_event *e, uint64_t timeout) { + struct epoll_event *ev_queue; + unsigned ev_queue_max; + int r, m, i; + + assert_return(e, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(e->state == SD_EVENT_ARMED, -EBUSY); + + if (e->exit_requested) { + e->state = SD_EVENT_PENDING; + return 1; + } + + ev_queue_max = MAX(e->n_sources, 1u); + ev_queue = newa(struct epoll_event, ev_queue_max); + + m = epoll_wait(e->epoll_fd, ev_queue, ev_queue_max, + timeout == (uint64_t) -1 ? -1 : (int) ((timeout + USEC_PER_MSEC - 1) / USEC_PER_MSEC)); + if (m < 0) { + if (errno == EINTR) { + e->state = SD_EVENT_PENDING; + return 1; + } + + r = -errno; + goto finish; + } + + dual_timestamp_get(&e->timestamp); + if (clock_boottime_supported()) + e->timestamp_boottime = now(CLOCK_BOOTTIME); + + for (i = 0; i < m; i++) { + + if (ev_queue[i].data.ptr == INT_TO_PTR(SOURCE_WATCHDOG)) + r = flush_timer(e, e->watchdog_fd, ev_queue[i].events, NULL); + else { + WakeupType *t = ev_queue[i].data.ptr; + + switch (*t) { + + case WAKEUP_EVENT_SOURCE: + r = process_io(e, ev_queue[i].data.ptr, ev_queue[i].events); + break; + + case WAKEUP_CLOCK_DATA: { + struct clock_data *d = ev_queue[i].data.ptr; + r = flush_timer(e, d->fd, ev_queue[i].events, &d->next); + break; + } + + case WAKEUP_SIGNAL_DATA: + r = process_signal(e, ev_queue[i].data.ptr, ev_queue[i].events); + break; + + default: + assert_not_reached("Invalid wake-up pointer"); + } + } + if (r < 0) + goto finish; + } + + r = process_watchdog(e); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp.realtime, &e->realtime); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp_boottime, &e->boottime); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp.monotonic, &e->monotonic); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp.realtime, &e->realtime_alarm); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp_boottime, &e->boottime_alarm); + if (r < 0) + goto finish; + + if (e->need_process_child) { + r = process_child(e); + if (r < 0) + goto finish; + } + + if (event_next_pending(e)) { + e->state = SD_EVENT_PENDING; + + return 1; + } + + r = 0; + +finish: + e->state = SD_EVENT_INITIAL; + + return r; +} + +_public_ int sd_event_dispatch(sd_event *e) { + sd_event_source *p; + int r; + + assert_return(e, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(e->state == SD_EVENT_PENDING, -EBUSY); + + if (e->exit_requested) + return dispatch_exit(e); + + p = event_next_pending(e); + if (p) { + sd_event_ref(e); + + e->state = SD_EVENT_RUNNING; + r = source_dispatch(p); + e->state = SD_EVENT_INITIAL; + + sd_event_unref(e); + + return r; + } + + e->state = SD_EVENT_INITIAL; + + return 1; +} + +static void event_log_delays(sd_event *e) { + char b[ELEMENTSOF(e->delays) * DECIMAL_STR_MAX(unsigned) + 1]; + unsigned i; + int o; + + for (i = o = 0; i < ELEMENTSOF(e->delays); i++) { + o += snprintf(&b[o], sizeof(b) - o, "%u ", e->delays[i]); + e->delays[i] = 0; + } + log_debug("Event loop iterations: %.*s", o, b); +} + +_public_ int sd_event_run(sd_event *e, uint64_t timeout) { + int r; + + assert_return(e, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); + + if (e->profile_delays && e->last_run) { + usec_t this_run; + unsigned l; + + this_run = now(CLOCK_MONOTONIC); + + l = u64log2(this_run - e->last_run); + assert(l < sizeof(e->delays)); + e->delays[l]++; + + if (this_run - e->last_log >= 5*USEC_PER_SEC) { + event_log_delays(e); + e->last_log = this_run; + } + } + + r = sd_event_prepare(e); + if (r == 0) + /* There was nothing? Then wait... */ + r = sd_event_wait(e, timeout); + + if (e->profile_delays) + e->last_run = now(CLOCK_MONOTONIC); + + if (r > 0) { + /* There's something now, then let's dispatch it */ + r = sd_event_dispatch(e); + if (r < 0) + return r; + + return 1; + } + + return r; +} + +_public_ int sd_event_loop(sd_event *e) { + int r; + + assert_return(e, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); + + sd_event_ref(e); + + while (e->state != SD_EVENT_FINISHED) { + r = sd_event_run(e, (uint64_t) -1); + if (r < 0) + goto finish; + } + + r = e->exit_code; + +finish: + sd_event_unref(e); + return r; +} + +_public_ int sd_event_get_fd(sd_event *e) { + + assert_return(e, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + + return e->epoll_fd; +} + +_public_ int sd_event_get_state(sd_event *e) { + assert_return(e, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + + return e->state; +} + +_public_ int sd_event_get_exit_code(sd_event *e, int *code) { + assert_return(e, -EINVAL); + assert_return(code, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!e->exit_requested) + return -ENODATA; + + *code = e->exit_code; + return 0; +} + +_public_ int sd_event_exit(sd_event *e, int code) { + assert_return(e, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + e->exit_requested = true; + e->exit_code = code; + + return 0; +} + +_public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) { + assert_return(e, -EINVAL); + assert_return(usec, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(IN_SET(clock, + CLOCK_REALTIME, + CLOCK_REALTIME_ALARM, + CLOCK_MONOTONIC, + CLOCK_BOOTTIME, + CLOCK_BOOTTIME_ALARM), -EOPNOTSUPP); + + if (IN_SET(clock, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM) && !clock_boottime_supported()) + return -EOPNOTSUPP; + + if (!dual_timestamp_is_set(&e->timestamp)) { + /* Implicitly fall back to now() if we never ran + * before and thus have no cached time. */ + *usec = now(clock); + return 1; + } + + switch (clock) { + + case CLOCK_REALTIME: + case CLOCK_REALTIME_ALARM: + *usec = e->timestamp.realtime; + break; + + case CLOCK_MONOTONIC: + *usec = e->timestamp.monotonic; + break; + + case CLOCK_BOOTTIME: + case CLOCK_BOOTTIME_ALARM: + *usec = e->timestamp_boottime; + break; + + default: + assert_not_reached("Unknown clock?"); + } + + return 0; +} + +_public_ int sd_event_default(sd_event **ret) { + + static thread_local sd_event *default_event = NULL; + sd_event *e = NULL; + int r; + + if (!ret) + return !!default_event; + + if (default_event) { + *ret = sd_event_ref(default_event); + return 0; + } + + r = sd_event_new(&e); + if (r < 0) + return r; + + e->default_event_ptr = &default_event; + e->tid = gettid(); + default_event = e; + + *ret = e; + return 1; +} + +_public_ int sd_event_get_tid(sd_event *e, pid_t *tid) { + assert_return(e, -EINVAL); + assert_return(tid, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + + if (e->tid != 0) { + *tid = e->tid; + return 0; + } + + return -ENXIO; +} + +_public_ int sd_event_set_watchdog(sd_event *e, int b) { + int r; + + assert_return(e, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + + if (e->watchdog == !!b) + return e->watchdog; + + if (b) { + struct epoll_event ev = {}; + + r = sd_watchdog_enabled(false, &e->watchdog_period); + if (r <= 0) + return r; + + /* Issue first ping immediately */ + sd_notify(false, "WATCHDOG=1"); + e->watchdog_last = now(CLOCK_MONOTONIC); + + e->watchdog_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC); + if (e->watchdog_fd < 0) + return -errno; + + r = arm_watchdog(e); + if (r < 0) + goto fail; + + ev.events = EPOLLIN; + ev.data.ptr = INT_TO_PTR(SOURCE_WATCHDOG); + + r = epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, e->watchdog_fd, &ev); + if (r < 0) { + r = -errno; + goto fail; + } + + } else { + if (e->watchdog_fd >= 0) { + epoll_ctl(e->epoll_fd, EPOLL_CTL_DEL, e->watchdog_fd, NULL); + e->watchdog_fd = safe_close(e->watchdog_fd); + } + } + + e->watchdog = !!b; + return e->watchdog; + +fail: + e->watchdog_fd = safe_close(e->watchdog_fd); + return r; +} + +_public_ int sd_event_get_watchdog(sd_event *e) { + assert_return(e, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + + return e->watchdog; +} diff --git a/src/libsystemd/libsystemd-internal/sd-event/test-event.c b/src/libsystemd/libsystemd-internal/sd-event/test-event.c new file mode 100644 index 0000000000..324ad7ee02 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-event/test-event.c @@ -0,0 +1,361 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "fd-util.h" +#include "log.h" +#include "macro.h" +#include "signal-util.h" +#include "util.h" + +static int prepare_handler(sd_event_source *s, void *userdata) { + log_info("preparing %c", PTR_TO_INT(userdata)); + return 1; +} + +static bool got_a, got_b, got_c, got_unref; +static unsigned got_d; + +static int unref_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_event_source_unref(s); + got_unref = true; + return 0; +} + +static int io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + + log_info("got IO on %c", PTR_TO_INT(userdata)); + + if (userdata == INT_TO_PTR('a')) { + assert_se(sd_event_source_set_enabled(s, SD_EVENT_OFF) >= 0); + assert_se(!got_a); + got_a = true; + } else if (userdata == INT_TO_PTR('b')) { + assert_se(!got_b); + got_b = true; + } else if (userdata == INT_TO_PTR('d')) { + got_d++; + if (got_d < 2) + assert_se(sd_event_source_set_enabled(s, SD_EVENT_ONESHOT) >= 0); + else + assert_se(sd_event_source_set_enabled(s, SD_EVENT_OFF) >= 0); + } else + assert_not_reached("Yuck!"); + + return 1; +} + +static int child_handler(sd_event_source *s, const siginfo_t *si, void *userdata) { + + assert_se(s); + assert_se(si); + + log_info("got child on %c", PTR_TO_INT(userdata)); + + assert_se(userdata == INT_TO_PTR('f')); + + assert_se(sd_event_exit(sd_event_source_get_event(s), 0) >= 0); + sd_event_source_unref(s); + + return 1; +} + +static int signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + sd_event_source *p = NULL; + pid_t pid; + + assert_se(s); + assert_se(si); + + log_info("got signal on %c", PTR_TO_INT(userdata)); + + assert_se(userdata == INT_TO_PTR('e')); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) + _exit(0); + + assert_se(sd_event_add_child(sd_event_source_get_event(s), &p, pid, WEXITED, child_handler, INT_TO_PTR('f')) >= 0); + assert_se(sd_event_source_set_enabled(p, SD_EVENT_ONESHOT) >= 0); + + sd_event_source_unref(s); + + return 1; +} + +static int defer_handler(sd_event_source *s, void *userdata) { + sd_event_source *p = NULL; + + assert_se(s); + + log_info("got defer on %c", PTR_TO_INT(userdata)); + + assert_se(userdata == INT_TO_PTR('d')); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGUSR1, -1) >= 0); + + assert_se(sd_event_add_signal(sd_event_source_get_event(s), &p, SIGUSR1, signal_handler, INT_TO_PTR('e')) >= 0); + assert_se(sd_event_source_set_enabled(p, SD_EVENT_ONESHOT) >= 0); + raise(SIGUSR1); + + sd_event_source_unref(s); + + return 1; +} + +static bool do_quit = false; + +static int time_handler(sd_event_source *s, uint64_t usec, void *userdata) { + log_info("got timer on %c", PTR_TO_INT(userdata)); + + if (userdata == INT_TO_PTR('c')) { + + if (do_quit) { + sd_event_source *p; + + assert_se(sd_event_add_defer(sd_event_source_get_event(s), &p, defer_handler, INT_TO_PTR('d')) >= 0); + assert_se(sd_event_source_set_enabled(p, SD_EVENT_ONESHOT) >= 0); + } else { + assert_se(!got_c); + got_c = true; + } + } else + assert_not_reached("Huh?"); + + return 2; +} + +static bool got_exit = false; + +static int exit_handler(sd_event_source *s, void *userdata) { + log_info("got quit handler on %c", PTR_TO_INT(userdata)); + + got_exit = true; + + return 3; +} + +static bool got_post = false; + +static int post_handler(sd_event_source *s, void *userdata) { + log_info("got post handler"); + + got_post = true; + + return 2; +} + +static void test_basic(void) { + sd_event *e = NULL; + sd_event_source *w = NULL, *x = NULL, *y = NULL, *z = NULL, *q = NULL, *t = NULL; + static const char ch = 'x'; + int a[2] = { -1, -1 }, b[2] = { -1, -1}, d[2] = { -1, -1}, k[2] = { -1, -1 }; + uint64_t event_now; + + assert_se(pipe(a) >= 0); + assert_se(pipe(b) >= 0); + assert_se(pipe(d) >= 0); + assert_se(pipe(k) >= 0); + + assert_se(sd_event_default(&e) >= 0); + assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) > 0); + + assert_se(sd_event_set_watchdog(e, true) >= 0); + + /* Test whether we cleanly can destroy an io event source from its own handler */ + got_unref = false; + assert_se(sd_event_add_io(e, &t, k[0], EPOLLIN, unref_handler, NULL) >= 0); + assert_se(write(k[1], &ch, 1) == 1); + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + assert_se(got_unref); + + got_a = false, got_b = false, got_c = false, got_d = 0; + + /* Add a oneshot handler, trigger it, re-enable it, and trigger + * it again. */ + assert_se(sd_event_add_io(e, &w, d[0], EPOLLIN, io_handler, INT_TO_PTR('d')) >= 0); + assert_se(sd_event_source_set_enabled(w, SD_EVENT_ONESHOT) >= 0); + assert_se(write(d[1], &ch, 1) >= 0); + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + assert_se(got_d == 1); + assert_se(write(d[1], &ch, 1) >= 0); + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + assert_se(got_d == 2); + + assert_se(sd_event_add_io(e, &x, a[0], EPOLLIN, io_handler, INT_TO_PTR('a')) >= 0); + assert_se(sd_event_add_io(e, &y, b[0], EPOLLIN, io_handler, INT_TO_PTR('b')) >= 0); + assert_se(sd_event_add_time(e, &z, CLOCK_MONOTONIC, 0, 0, time_handler, INT_TO_PTR('c')) >= 0); + assert_se(sd_event_add_exit(e, &q, exit_handler, INT_TO_PTR('g')) >= 0); + + assert_se(sd_event_source_set_priority(x, 99) >= 0); + assert_se(sd_event_source_set_enabled(y, SD_EVENT_ONESHOT) >= 0); + assert_se(sd_event_source_set_prepare(x, prepare_handler) >= 0); + assert_se(sd_event_source_set_priority(z, 50) >= 0); + assert_se(sd_event_source_set_enabled(z, SD_EVENT_ONESHOT) >= 0); + assert_se(sd_event_source_set_prepare(z, prepare_handler) >= 0); + + /* Test for floating event sources */ + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN+1, -1) >= 0); + assert_se(sd_event_add_signal(e, NULL, SIGRTMIN+1, NULL, NULL) >= 0); + + assert_se(write(a[1], &ch, 1) >= 0); + assert_se(write(b[1], &ch, 1) >= 0); + + assert_se(!got_a && !got_b && !got_c); + + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + + assert_se(!got_a && got_b && !got_c); + + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + + assert_se(!got_a && got_b && got_c); + + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + + assert_se(got_a && got_b && got_c); + + sd_event_source_unref(x); + sd_event_source_unref(y); + + do_quit = true; + assert_se(sd_event_add_post(e, NULL, post_handler, NULL) >= 0); + assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) == 0); + assert_se(sd_event_source_set_time(z, event_now + 200 * USEC_PER_MSEC) >= 0); + assert_se(sd_event_source_set_enabled(z, SD_EVENT_ONESHOT) >= 0); + + assert_se(sd_event_loop(e) >= 0); + assert_se(got_post); + assert_se(got_exit); + + sd_event_source_unref(z); + sd_event_source_unref(q); + + sd_event_source_unref(w); + + sd_event_unref(e); + + safe_close_pair(a); + safe_close_pair(b); + safe_close_pair(d); + safe_close_pair(k); +} + +static void test_sd_event_now(void) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + uint64_t event_now; + + assert_se(sd_event_new(&e) >= 0); + assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) > 0); + assert_se(sd_event_now(e, CLOCK_REALTIME, &event_now) > 0); + assert_se(sd_event_now(e, CLOCK_REALTIME_ALARM, &event_now) > 0); + if (clock_boottime_supported()) { + assert_se(sd_event_now(e, CLOCK_BOOTTIME, &event_now) > 0); + assert_se(sd_event_now(e, CLOCK_BOOTTIME_ALARM, &event_now) > 0); + } + assert_se(sd_event_now(e, -1, &event_now) == -EOPNOTSUPP); + assert_se(sd_event_now(e, 900 /* arbitrary big number */, &event_now) == -EOPNOTSUPP); + + assert_se(sd_event_run(e, 0) == 0); + + assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) == 0); + assert_se(sd_event_now(e, CLOCK_REALTIME, &event_now) == 0); + assert_se(sd_event_now(e, CLOCK_REALTIME_ALARM, &event_now) == 0); + if (clock_boottime_supported()) { + assert_se(sd_event_now(e, CLOCK_BOOTTIME, &event_now) == 0); + assert_se(sd_event_now(e, CLOCK_BOOTTIME_ALARM, &event_now) == 0); + } + assert_se(sd_event_now(e, -1, &event_now) == -EOPNOTSUPP); + assert_se(sd_event_now(e, 900 /* arbitrary big number */, &event_now) == -EOPNOTSUPP); +} + +static int last_rtqueue_sigval = 0; +static int n_rtqueue = 0; + +static int rtqueue_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + last_rtqueue_sigval = si->ssi_int; + n_rtqueue++; + return 0; +} + +static void test_rtqueue(void) { + sd_event_source *u = NULL, *v = NULL, *s = NULL; + sd_event *e = NULL; + + assert_se(sd_event_default(&e) >= 0); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN+2, SIGRTMIN+3, SIGUSR2, -1) >= 0); + assert_se(sd_event_add_signal(e, &u, SIGRTMIN+2, rtqueue_handler, NULL) >= 0); + assert_se(sd_event_add_signal(e, &v, SIGRTMIN+3, rtqueue_handler, NULL) >= 0); + assert_se(sd_event_add_signal(e, &s, SIGUSR2, rtqueue_handler, NULL) >= 0); + + assert_se(sd_event_source_set_priority(v, -10) >= 0); + + assert(sigqueue(getpid(), SIGRTMIN+2, (union sigval) { .sival_int = 1 }) >= 0); + assert(sigqueue(getpid(), SIGRTMIN+3, (union sigval) { .sival_int = 2 }) >= 0); + assert(sigqueue(getpid(), SIGUSR2, (union sigval) { .sival_int = 3 }) >= 0); + assert(sigqueue(getpid(), SIGRTMIN+3, (union sigval) { .sival_int = 4 }) >= 0); + assert(sigqueue(getpid(), SIGUSR2, (union sigval) { .sival_int = 5 }) >= 0); + + assert_se(n_rtqueue == 0); + assert_se(last_rtqueue_sigval == 0); + + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + assert_se(n_rtqueue == 1); + assert_se(last_rtqueue_sigval == 2); /* first SIGRTMIN+3 */ + + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + assert_se(n_rtqueue == 2); + assert_se(last_rtqueue_sigval == 4); /* second SIGRTMIN+3 */ + + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + assert_se(n_rtqueue == 3); + assert_se(last_rtqueue_sigval == 3); /* first SIGUSR2 */ + + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + assert_se(n_rtqueue == 4); + assert_se(last_rtqueue_sigval == 1); /* SIGRTMIN+2 */ + + assert_se(sd_event_run(e, 0) == 0); /* the other SIGUSR2 is dropped, because the first one was still queued */ + assert_se(n_rtqueue == 4); + assert_se(last_rtqueue_sigval == 1); + + sd_event_source_unref(u); + sd_event_source_unref(v); + sd_event_source_unref(s); + + sd_event_unref(e); +} + +int main(int argc, char *argv[]) { + + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + + test_basic(); + test_sd_event_now(); + test_rtqueue(); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-hwdb/hwdb-internal.h b/src/libsystemd/libsystemd-internal/sd-hwdb/hwdb-internal.h new file mode 100644 index 0000000000..8ffb5e5c74 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-hwdb/hwdb-internal.h @@ -0,0 +1,72 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 "sparse-endian.h" +#include "util.h" + +#define HWDB_SIG { 'K', 'S', 'L', 'P', 'H', 'H', 'R', 'H' } + +/* on-disk trie objects */ +struct trie_header_f { + uint8_t signature[8]; + + /* version of tool which created the file */ + le64_t tool_version; + le64_t file_size; + + /* size of structures to allow them to grow */ + le64_t header_size; + le64_t node_size; + le64_t child_entry_size; + le64_t value_entry_size; + + /* offset of the root trie node */ + le64_t nodes_root_off; + + /* size of the nodes and string section */ + le64_t nodes_len; + le64_t strings_len; +} _packed_; + +struct trie_node_f { + /* prefix of lookup string, shared by all children */ + le64_t prefix_off; + /* size of children entry array appended to the node */ + uint8_t children_count; + uint8_t padding[7]; + /* size of value entry array appended to the node */ + le64_t values_count; +} _packed_; + +/* array of child entries, follows directly the node record */ +struct trie_child_entry_f { + /* index of the child node */ + uint8_t c; + uint8_t padding[7]; + /* offset of the child node */ + le64_t child_off; +} _packed_; + +/* array of value entries, follows directly the node record/child array */ +struct trie_value_entry_f { + le64_t key_off; + le64_t value_off; +} _packed_; diff --git a/src/libsystemd/libsystemd-internal/sd-hwdb/hwdb-util.h b/src/libsystemd/libsystemd-internal/sd-hwdb/hwdb-util.h new file mode 100644 index 0000000000..05dc47962b --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-hwdb/hwdb-util.h @@ -0,0 +1,26 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 "util.h" + +bool hwdb_validate(sd_hwdb *hwdb); diff --git a/src/libsystemd/libsystemd-internal/sd-hwdb/sd-hwdb.c b/src/libsystemd/libsystemd-internal/sd-hwdb/sd-hwdb.c new file mode 100644 index 0000000000..7dcfe95b87 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-hwdb/sd-hwdb.c @@ -0,0 +1,470 @@ +/*** + This file is part of systemd. + + Copyright 2012 Kay Sievers + Copyright 2008 Alan Jenkins + Copyright 2014 Tom Gundersen + + 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 +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "hwdb-internal.h" +#include "hwdb-util.h" +#include "refcnt.h" +#include "string-util.h" + +struct sd_hwdb { + RefCount n_ref; + int refcount; + + FILE *f; + struct stat st; + union { + struct trie_header_f *head; + const char *map; + }; + + char *modalias; + + OrderedHashmap *properties; + Iterator properties_iterator; + bool properties_modified; +}; + +struct linebuf { + char bytes[LINE_MAX]; + size_t size; + size_t len; +}; + +static void linebuf_init(struct linebuf *buf) { + buf->size = 0; + buf->len = 0; +} + +static const char *linebuf_get(struct linebuf *buf) { + if (buf->len + 1 >= sizeof(buf->bytes)) + return NULL; + buf->bytes[buf->len] = '\0'; + return buf->bytes; +} + +static bool linebuf_add(struct linebuf *buf, const char *s, size_t len) { + if (buf->len + len >= sizeof(buf->bytes)) + return false; + memcpy(buf->bytes + buf->len, s, len); + buf->len += len; + return true; +} + +static bool linebuf_add_char(struct linebuf *buf, char c) { + if (buf->len + 1 >= sizeof(buf->bytes)) + return false; + buf->bytes[buf->len++] = c; + return true; +} + +static void linebuf_rem(struct linebuf *buf, size_t count) { + assert(buf->len >= count); + buf->len -= count; +} + +static void linebuf_rem_char(struct linebuf *buf) { + linebuf_rem(buf, 1); +} + +static const struct trie_child_entry_f *trie_node_children(sd_hwdb *hwdb, const struct trie_node_f *node) { + return (const struct trie_child_entry_f *)((const char *)node + le64toh(hwdb->head->node_size)); +} + +static const struct trie_value_entry_f *trie_node_values(sd_hwdb *hwdb, const struct trie_node_f *node) { + const char *base = (const char *)node; + + base += le64toh(hwdb->head->node_size); + base += node->children_count * le64toh(hwdb->head->child_entry_size); + return (const struct trie_value_entry_f *)base; +} + +static const struct trie_node_f *trie_node_from_off(sd_hwdb *hwdb, le64_t off) { + return (const struct trie_node_f *)(hwdb->map + le64toh(off)); +} + +static const char *trie_string(sd_hwdb *hwdb, le64_t off) { + return hwdb->map + le64toh(off); +} + +static int trie_children_cmp_f(const void *v1, const void *v2) { + const struct trie_child_entry_f *n1 = v1; + const struct trie_child_entry_f *n2 = v2; + + return n1->c - n2->c; +} + +static const struct trie_node_f *node_lookup_f(sd_hwdb *hwdb, const struct trie_node_f *node, uint8_t c) { + struct trie_child_entry_f *child; + struct trie_child_entry_f search; + + search.c = c; + child = bsearch(&search, trie_node_children(hwdb, node), node->children_count, + le64toh(hwdb->head->child_entry_size), trie_children_cmp_f); + if (child) + return trie_node_from_off(hwdb, child->child_off); + return NULL; +} + +static int hwdb_add_property(sd_hwdb *hwdb, const char *key, const char *value) { + int r; + + assert(hwdb); + assert(key); + assert(value); + + /* + * Silently ignore all properties which do not start with a + * space; future extensions might use additional prefixes. + */ + if (key[0] != ' ') + return 0; + + key++; + + r = ordered_hashmap_ensure_allocated(&hwdb->properties, &string_hash_ops); + if (r < 0) + return r; + + r = ordered_hashmap_replace(hwdb->properties, key, (char*)value); + if (r < 0) + return r; + + hwdb->properties_modified = true; + + return 0; +} + +static int trie_fnmatch_f(sd_hwdb *hwdb, const struct trie_node_f *node, size_t p, + struct linebuf *buf, const char *search) { + size_t len; + size_t i; + const char *prefix; + int err; + + prefix = trie_string(hwdb, node->prefix_off); + len = strlen(prefix + p); + linebuf_add(buf, prefix + p, len); + + for (i = 0; i < node->children_count; i++) { + const struct trie_child_entry_f *child = &trie_node_children(hwdb, node)[i]; + + linebuf_add_char(buf, child->c); + err = trie_fnmatch_f(hwdb, trie_node_from_off(hwdb, child->child_off), 0, buf, search); + if (err < 0) + return err; + linebuf_rem_char(buf); + } + + if (le64toh(node->values_count) && fnmatch(linebuf_get(buf), search, 0) == 0) + for (i = 0; i < le64toh(node->values_count); i++) { + err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[i].key_off), + trie_string(hwdb, trie_node_values(hwdb, node)[i].value_off)); + if (err < 0) + return err; + } + + linebuf_rem(buf, len); + return 0; +} + +static int trie_search_f(sd_hwdb *hwdb, const char *search) { + struct linebuf buf; + const struct trie_node_f *node; + size_t i = 0; + int err; + + linebuf_init(&buf); + + node = trie_node_from_off(hwdb, hwdb->head->nodes_root_off); + while (node) { + const struct trie_node_f *child; + size_t p = 0; + + if (node->prefix_off) { + uint8_t c; + + for (; (c = trie_string(hwdb, node->prefix_off)[p]); p++) { + if (c == '*' || c == '?' || c == '[') + return trie_fnmatch_f(hwdb, node, p, &buf, search + i + p); + if (c != search[i + p]) + return 0; + } + i += p; + } + + child = node_lookup_f(hwdb, node, '*'); + if (child) { + linebuf_add_char(&buf, '*'); + err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i); + if (err < 0) + return err; + linebuf_rem_char(&buf); + } + + child = node_lookup_f(hwdb, node, '?'); + if (child) { + linebuf_add_char(&buf, '?'); + err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i); + if (err < 0) + return err; + linebuf_rem_char(&buf); + } + + child = node_lookup_f(hwdb, node, '['); + if (child) { + linebuf_add_char(&buf, '['); + err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i); + if (err < 0) + return err; + linebuf_rem_char(&buf); + } + + if (search[i] == '\0') { + size_t n; + + for (n = 0; n < le64toh(node->values_count); n++) { + err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[n].key_off), + trie_string(hwdb, trie_node_values(hwdb, node)[n].value_off)); + if (err < 0) + return err; + } + return 0; + } + + child = node_lookup_f(hwdb, node, search[i]); + node = child; + i++; + } + return 0; +} + +static const char hwdb_bin_paths[] = + "/etc/systemd/hwdb/hwdb.bin\0" + "/etc/udev/hwdb.bin\0" + "/usr/lib/systemd/hwdb/hwdb.bin\0" +#ifdef HAVE_SPLIT_USR + "/lib/systemd/hwdb/hwdb.bin\0" +#endif + UDEVLIBEXECDIR "/hwdb.bin\0"; + +_public_ int sd_hwdb_new(sd_hwdb **ret) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + const char *hwdb_bin_path; + const char sig[] = HWDB_SIG; + + assert_return(ret, -EINVAL); + + hwdb = new0(sd_hwdb, 1); + if (!hwdb) + return -ENOMEM; + + hwdb->n_ref = REFCNT_INIT; + + /* find hwdb.bin in hwdb_bin_paths */ + NULSTR_FOREACH(hwdb_bin_path, hwdb_bin_paths) { + hwdb->f = fopen(hwdb_bin_path, "re"); + if (hwdb->f) + break; + else if (errno == ENOENT) + continue; + else + return log_debug_errno(errno, "error reading %s: %m", hwdb_bin_path); + } + + if (!hwdb->f) { + log_debug("hwdb.bin does not exist, please run udevadm hwdb --update"); + return -ENOENT; + } + + if (fstat(fileno(hwdb->f), &hwdb->st) < 0 || + (size_t)hwdb->st.st_size < offsetof(struct trie_header_f, strings_len) + 8) + return log_debug_errno(errno, "error reading %s: %m", hwdb_bin_path); + + hwdb->map = mmap(0, hwdb->st.st_size, PROT_READ, MAP_SHARED, fileno(hwdb->f), 0); + if (hwdb->map == MAP_FAILED) + return log_debug_errno(errno, "error mapping %s: %m", hwdb_bin_path); + + if (memcmp(hwdb->map, sig, sizeof(hwdb->head->signature)) != 0 || + (size_t)hwdb->st.st_size != le64toh(hwdb->head->file_size)) { + log_debug("error recognizing the format of %s", hwdb_bin_path); + return -EINVAL; + } + + log_debug("=== trie on-disk ==="); + log_debug("tool version: %"PRIu64, le64toh(hwdb->head->tool_version)); + log_debug("file size: %8"PRIi64" bytes", hwdb->st.st_size); + log_debug("header size %8"PRIu64" bytes", le64toh(hwdb->head->header_size)); + log_debug("strings %8"PRIu64" bytes", le64toh(hwdb->head->strings_len)); + log_debug("nodes %8"PRIu64" bytes", le64toh(hwdb->head->nodes_len)); + + *ret = hwdb; + hwdb = NULL; + + return 0; +} + +_public_ sd_hwdb *sd_hwdb_ref(sd_hwdb *hwdb) { + assert_return(hwdb, NULL); + + assert_se(REFCNT_INC(hwdb->n_ref) >= 2); + + return hwdb; +} + +_public_ sd_hwdb *sd_hwdb_unref(sd_hwdb *hwdb) { + if (hwdb && REFCNT_DEC(hwdb->n_ref) == 0) { + if (hwdb->map) + munmap((void *)hwdb->map, hwdb->st.st_size); + safe_fclose(hwdb->f); + free(hwdb->modalias); + ordered_hashmap_free(hwdb->properties); + free(hwdb); + } + + return NULL; +} + +bool hwdb_validate(sd_hwdb *hwdb) { + bool found = false; + const char* p; + struct stat st; + + if (!hwdb) + return false; + if (!hwdb->f) + return false; + + /* if hwdb.bin doesn't exist anywhere, we need to update */ + NULSTR_FOREACH(p, hwdb_bin_paths) { + if (stat(p, &st) >= 0) { + found = true; + break; + } + } + if (!found) + return true; + + if (timespec_load(&hwdb->st.st_mtim) != timespec_load(&st.st_mtim)) + return true; + return false; +} + +static int properties_prepare(sd_hwdb *hwdb, const char *modalias) { + _cleanup_free_ char *mod = NULL; + int r; + + assert(hwdb); + assert(modalias); + + if (streq_ptr(modalias, hwdb->modalias)) + return 0; + + mod = strdup(modalias); + if (!mod) + return -ENOMEM; + + ordered_hashmap_clear(hwdb->properties); + + hwdb->properties_modified = true; + + r = trie_search_f(hwdb, modalias); + if (r < 0) + return r; + + free(hwdb->modalias); + hwdb->modalias = mod; + mod = NULL; + + return 0; +} + +_public_ int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **_value) { + const char *value; + int r; + + assert_return(hwdb, -EINVAL); + assert_return(hwdb->f, -EINVAL); + assert_return(modalias, -EINVAL); + assert_return(_value, -EINVAL); + + r = properties_prepare(hwdb, modalias); + if (r < 0) + return r; + + value = ordered_hashmap_get(hwdb->properties, key); + if (!value) + return -ENOENT; + + *_value = value; + + return 0; +} + +_public_ int sd_hwdb_seek(sd_hwdb *hwdb, const char *modalias) { + int r; + + assert_return(hwdb, -EINVAL); + assert_return(hwdb->f, -EINVAL); + assert_return(modalias, -EINVAL); + + r = properties_prepare(hwdb, modalias); + if (r < 0) + return r; + + hwdb->properties_modified = false; + hwdb->properties_iterator = ITERATOR_FIRST; + + return 0; +} + +_public_ int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **key, const char **value) { + const void *k; + void *v; + + assert_return(hwdb, -EINVAL); + assert_return(key, -EINVAL); + assert_return(value, -EINVAL); + + if (hwdb->properties_modified) + return -EAGAIN; + + ordered_hashmap_iterate(hwdb->properties, &hwdb->properties_iterator, &v, &k); + if (!k) + return 0; + + *key = k; + *value = v; + + return 1; +} diff --git a/src/libsystemd/libsystemd-internal/sd-id128/sd-id128.c b/src/libsystemd/libsystemd-internal/sd-id128/sd-id128.c new file mode 100644 index 0000000000..cda3e9f0df --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-id128/sd-id128.c @@ -0,0 +1,225 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include + +#include + +#include "fd-util.h" +#include "hexdecoct.h" +#include "io-util.h" +#include "macro.h" +#include "random-util.h" +#include "util.h" + +_public_ char *sd_id128_to_string(sd_id128_t id, char s[SD_ID128_STRING_MAX]) { + unsigned n; + + assert_return(s, NULL); + + for (n = 0; n < 16; n++) { + s[n*2] = hexchar(id.bytes[n] >> 4); + s[n*2+1] = hexchar(id.bytes[n] & 0xF); + } + + s[32] = 0; + + return s; +} + +_public_ int sd_id128_from_string(const char s[], sd_id128_t *ret) { + unsigned n, i; + sd_id128_t t; + bool is_guid = false; + + assert_return(s, -EINVAL); + assert_return(ret, -EINVAL); + + for (n = 0, i = 0; n < 16;) { + int a, b; + + if (s[i] == '-') { + /* Is this a GUID? Then be nice, and skip over + * the dashes */ + + if (i == 8) + is_guid = true; + else if (i == 13 || i == 18 || i == 23) { + if (!is_guid) + return -EINVAL; + } else + return -EINVAL; + + i++; + continue; + } + + a = unhexchar(s[i++]); + if (a < 0) + return -EINVAL; + + b = unhexchar(s[i++]); + if (b < 0) + return -EINVAL; + + t.bytes[n++] = (a << 4) | b; + } + + if (i != (is_guid ? 36 : 32)) + return -EINVAL; + + if (s[i] != 0) + return -EINVAL; + + *ret = t; + return 0; +} + +static sd_id128_t make_v4_uuid(sd_id128_t id) { + /* Stolen from generate_random_uuid() of drivers/char/random.c + * in the kernel sources */ + + /* Set UUID version to 4 --- truly random generation */ + id.bytes[6] = (id.bytes[6] & 0x0F) | 0x40; + + /* Set the UUID variant to DCE */ + id.bytes[8] = (id.bytes[8] & 0x3F) | 0x80; + + return id; +} + +_public_ int sd_id128_get_machine(sd_id128_t *ret) { + static thread_local sd_id128_t saved_machine_id; + static thread_local bool saved_machine_id_valid = false; + _cleanup_close_ int fd = -1; + char buf[33]; + unsigned j; + sd_id128_t t; + int r; + + assert_return(ret, -EINVAL); + + if (saved_machine_id_valid) { + *ret = saved_machine_id; + return 0; + } + + fd = open("/etc/machine-id", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return -errno; + + r = loop_read_exact(fd, buf, 33, false); + if (r < 0) + return r; + if (buf[32] !='\n') + return -EIO; + + for (j = 0; j < 16; j++) { + int a, b; + + a = unhexchar(buf[j*2]); + b = unhexchar(buf[j*2+1]); + + if (a < 0 || b < 0) + return -EIO; + + t.bytes[j] = a << 4 | b; + } + + saved_machine_id = t; + saved_machine_id_valid = true; + + *ret = t; + return 0; +} + +_public_ int sd_id128_get_boot(sd_id128_t *ret) { + static thread_local sd_id128_t saved_boot_id; + static thread_local bool saved_boot_id_valid = false; + _cleanup_close_ int fd = -1; + char buf[36]; + unsigned j; + sd_id128_t t; + char *p; + int r; + + assert_return(ret, -EINVAL); + + if (saved_boot_id_valid) { + *ret = saved_boot_id; + return 0; + } + + fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return -errno; + + r = loop_read_exact(fd, buf, 36, false); + if (r < 0) + return r; + + for (j = 0, p = buf; j < 16; j++) { + int a, b; + + if (p >= buf + 35) + return -EIO; + + if (*p == '-') { + p++; + if (p >= buf + 35) + return -EIO; + } + + a = unhexchar(p[0]); + b = unhexchar(p[1]); + + if (a < 0 || b < 0) + return -EIO; + + t.bytes[j] = a << 4 | b; + + p += 2; + } + + saved_boot_id = t; + saved_boot_id_valid = true; + + *ret = t; + return 0; +} + +_public_ int sd_id128_randomize(sd_id128_t *ret) { + sd_id128_t t; + int r; + + assert_return(ret, -EINVAL); + + r = dev_urandom(&t, sizeof(t)); + if (r < 0) + return r; + + /* Turn this into a valid v4 UUID, to be nice. Note that we + * only guarantee this for newly generated UUIDs, not for + * pre-existing ones. */ + + *ret = make_v4_uuid(t); + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-login/sd-login.c b/src/libsystemd/libsystemd-internal/sd-login/sd-login.c new file mode 100644 index 0000000000..84831d5e95 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-login/sd-login.c @@ -0,0 +1,1062 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "cgroup-util.h" +#include "dirent-util.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "hostname-util.h" +#include "io-util.h" +#include "login-util.h" +#include "macro.h" +#include "parse-util.h" +#include "path-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" +#include "util.h" + +/* Error codes: + * + * invalid input parameters → -EINVAL + * invalid fd → -EBADF + * process does not exist → -ESRCH + * cgroup does not exist → -ENOENT + * machine, session does not exist → -ENXIO + * requested metadata on object is missing → -ENODATA + */ + +_public_ int sd_pid_get_session(pid_t pid, char **session) { + + assert_return(pid >= 0, -EINVAL); + assert_return(session, -EINVAL); + + return cg_pid_get_session(pid, session); +} + +_public_ int sd_pid_get_unit(pid_t pid, char **unit) { + + assert_return(pid >= 0, -EINVAL); + assert_return(unit, -EINVAL); + + return cg_pid_get_unit(pid, unit); +} + +_public_ int sd_pid_get_user_unit(pid_t pid, char **unit) { + + assert_return(pid >= 0, -EINVAL); + assert_return(unit, -EINVAL); + + return cg_pid_get_user_unit(pid, unit); +} + +_public_ int sd_pid_get_machine_name(pid_t pid, char **name) { + + assert_return(pid >= 0, -EINVAL); + assert_return(name, -EINVAL); + + return cg_pid_get_machine_name(pid, name); +} + +_public_ int sd_pid_get_slice(pid_t pid, char **slice) { + + assert_return(pid >= 0, -EINVAL); + assert_return(slice, -EINVAL); + + return cg_pid_get_slice(pid, slice); +} + +_public_ int sd_pid_get_user_slice(pid_t pid, char **slice) { + + assert_return(pid >= 0, -EINVAL); + assert_return(slice, -EINVAL); + + return cg_pid_get_user_slice(pid, slice); +} + +_public_ int sd_pid_get_owner_uid(pid_t pid, uid_t *uid) { + + assert_return(pid >= 0, -EINVAL); + assert_return(uid, -EINVAL); + + return cg_pid_get_owner_uid(pid, uid); +} + +_public_ int sd_pid_get_cgroup(pid_t pid, char **cgroup) { + char *c; + int r; + + assert_return(pid >= 0, -EINVAL); + assert_return(cgroup, -EINVAL); + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &c); + if (r < 0) + return r; + + /* The internal APIs return the empty string for the root + * cgroup, let's return the "/" in the public APIs instead, as + * that's easier and less ambigious for people to grok. */ + if (isempty(c)) { + free(c); + c = strdup("/"); + if (!c) + return -ENOMEM; + + } + + *cgroup = c; + return 0; +} + +_public_ int sd_peer_get_session(int fd, char **session) { + struct ucred ucred = {}; + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(session, -EINVAL); + + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + return cg_pid_get_session(ucred.pid, session); +} + +_public_ int sd_peer_get_owner_uid(int fd, uid_t *uid) { + struct ucred ucred; + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(uid, -EINVAL); + + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + return cg_pid_get_owner_uid(ucred.pid, uid); +} + +_public_ int sd_peer_get_unit(int fd, char **unit) { + struct ucred ucred; + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(unit, -EINVAL); + + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + return cg_pid_get_unit(ucred.pid, unit); +} + +_public_ int sd_peer_get_user_unit(int fd, char **unit) { + struct ucred ucred; + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(unit, -EINVAL); + + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + return cg_pid_get_user_unit(ucred.pid, unit); +} + +_public_ int sd_peer_get_machine_name(int fd, char **machine) { + struct ucred ucred; + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(machine, -EINVAL); + + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + return cg_pid_get_machine_name(ucred.pid, machine); +} + +_public_ int sd_peer_get_slice(int fd, char **slice) { + struct ucred ucred; + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(slice, -EINVAL); + + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + return cg_pid_get_slice(ucred.pid, slice); +} + +_public_ int sd_peer_get_user_slice(int fd, char **slice) { + struct ucred ucred; + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(slice, -EINVAL); + + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + return cg_pid_get_user_slice(ucred.pid, slice); +} + +_public_ int sd_peer_get_cgroup(int fd, char **cgroup) { + struct ucred ucred; + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(cgroup, -EINVAL); + + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + return sd_pid_get_cgroup(ucred.pid, cgroup); +} + +static int file_of_uid(uid_t uid, char **p) { + + assert_return(uid_is_valid(uid), -EINVAL); + assert(p); + + if (asprintf(p, "/run/systemd/users/" UID_FMT, uid) < 0) + return -ENOMEM; + + return 0; +} + +_public_ int sd_uid_get_state(uid_t uid, char**state) { + _cleanup_free_ char *p = NULL; + char *s = NULL; + int r; + + assert_return(state, -EINVAL); + + r = file_of_uid(uid, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, "STATE", &s, NULL); + if (r == -ENOENT) { + free(s); + s = strdup("offline"); + if (!s) + return -ENOMEM; + + } + if (r < 0) { + free(s); + return r; + } + if (isempty(s)) { + free(s); + return -EIO; + } + + *state = s; + return 0; +} + +_public_ int sd_uid_get_display(uid_t uid, char **session) { + _cleanup_free_ char *p = NULL, *s = NULL; + int r; + + assert_return(session, -EINVAL); + + r = file_of_uid(uid, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, "DISPLAY", &s, NULL); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + if (isempty(s)) + return -ENODATA; + + *session = s; + s = NULL; + + return 0; +} + +static int file_of_seat(const char *seat, char **_p) { + char *p; + int r; + + assert(_p); + + if (seat) { + if (!filename_is_valid(seat)) + return -EINVAL; + + p = strappend("/run/systemd/seats/", seat); + } else { + _cleanup_free_ char *buf = NULL; + + r = sd_session_get_seat(NULL, &buf); + if (r < 0) + return r; + + p = strappend("/run/systemd/seats/", buf); + } + + if (!p) + return -ENOMEM; + + *_p = p; + p = NULL; + return 0; +} + +_public_ int sd_uid_is_on_seat(uid_t uid, int require_active, const char *seat) { + _cleanup_free_ char *t = NULL, *s = NULL, *p = NULL; + size_t l; + int r; + const char *word, *variable, *state; + + assert_return(uid_is_valid(uid), -EINVAL); + + r = file_of_seat(seat, &p); + if (r < 0) + return r; + + variable = require_active ? "ACTIVE_UID" : "UIDS"; + + r = parse_env_file(p, NEWLINE, variable, &s, NULL); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + if (isempty(s)) + return 0; + + if (asprintf(&t, UID_FMT, uid) < 0) + return -ENOMEM; + + FOREACH_WORD(word, l, s, state) + if (strneq(t, word, l)) + return 1; + + return 0; +} + +static int uid_get_array(uid_t uid, const char *variable, char ***array) { + _cleanup_free_ char *p = NULL, *s = NULL; + char **a; + int r; + + assert(variable); + + r = file_of_uid(uid, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, variable, &s, NULL); + if (r == -ENOENT || (r >= 0 && isempty(s))) { + if (array) + *array = NULL; + return 0; + } + if (r < 0) + return r; + + a = strv_split(s, " "); + if (!a) + return -ENOMEM; + + strv_uniq(a); + r = strv_length(a); + + if (array) + *array = a; + else + strv_free(a); + + return r; +} + +_public_ int sd_uid_get_sessions(uid_t uid, int require_active, char ***sessions) { + return uid_get_array( + uid, + require_active == 0 ? "ONLINE_SESSIONS" : + require_active > 0 ? "ACTIVE_SESSIONS" : + "SESSIONS", + sessions); +} + +_public_ int sd_uid_get_seats(uid_t uid, int require_active, char ***seats) { + return uid_get_array( + uid, + require_active == 0 ? "ONLINE_SEATS" : + require_active > 0 ? "ACTIVE_SEATS" : + "SEATS", + seats); +} + +static int file_of_session(const char *session, char **_p) { + char *p; + int r; + + assert(_p); + + if (session) { + if (!session_id_valid(session)) + return -EINVAL; + + p = strappend("/run/systemd/sessions/", session); + } else { + _cleanup_free_ char *buf = NULL; + + r = sd_pid_get_session(0, &buf); + if (r < 0) + return r; + + p = strappend("/run/systemd/sessions/", buf); + } + + if (!p) + return -ENOMEM; + + *_p = p; + return 0; +} + +_public_ int sd_session_is_active(const char *session) { + _cleanup_free_ char *p = NULL, *s = NULL; + int r; + + r = file_of_session(session, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, "ACTIVE", &s, NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + if (isempty(s)) + return -EIO; + + return parse_boolean(s); +} + +_public_ int sd_session_is_remote(const char *session) { + _cleanup_free_ char *p = NULL, *s = NULL; + int r; + + r = file_of_session(session, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, "REMOTE", &s, NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + if (isempty(s)) + return -ENODATA; + + return parse_boolean(s); +} + +_public_ int sd_session_get_state(const char *session, char **state) { + _cleanup_free_ char *p = NULL, *s = NULL; + int r; + + assert_return(state, -EINVAL); + + r = file_of_session(session, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, "STATE", &s, NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + if (isempty(s)) + return -EIO; + + *state = s; + s = NULL; + + return 0; +} + +_public_ int sd_session_get_uid(const char *session, uid_t *uid) { + int r; + _cleanup_free_ char *p = NULL, *s = NULL; + + assert_return(uid, -EINVAL); + + r = file_of_session(session, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, "UID", &s, NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + if (isempty(s)) + return -EIO; + + return parse_uid(s, uid); +} + +static int session_get_string(const char *session, const char *field, char **value) { + _cleanup_free_ char *p = NULL, *s = NULL; + int r; + + assert_return(value, -EINVAL); + assert(field); + + r = file_of_session(session, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, field, &s, NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + if (isempty(s)) + return -ENODATA; + + *value = s; + s = NULL; + return 0; +} + +_public_ int sd_session_get_seat(const char *session, char **seat) { + return session_get_string(session, "SEAT", seat); +} + +_public_ int sd_session_get_tty(const char *session, char **tty) { + return session_get_string(session, "TTY", tty); +} + +_public_ int sd_session_get_vt(const char *session, unsigned *vtnr) { + _cleanup_free_ char *vtnr_string = NULL; + unsigned u; + int r; + + assert_return(vtnr, -EINVAL); + + r = session_get_string(session, "VTNR", &vtnr_string); + if (r < 0) + return r; + + r = safe_atou(vtnr_string, &u); + if (r < 0) + return r; + + *vtnr = u; + return 0; +} + +_public_ int sd_session_get_service(const char *session, char **service) { + return session_get_string(session, "SERVICE", service); +} + +_public_ int sd_session_get_type(const char *session, char **type) { + return session_get_string(session, "TYPE", type); +} + +_public_ int sd_session_get_class(const char *session, char **class) { + return session_get_string(session, "CLASS", class); +} + +_public_ int sd_session_get_desktop(const char *session, char **desktop) { + _cleanup_free_ char *escaped = NULL; + char *t; + int r; + + assert_return(desktop, -EINVAL); + + r = session_get_string(session, "DESKTOP", &escaped); + if (r < 0) + return r; + + r = cunescape(escaped, 0, &t); + if (r < 0) + return r; + + *desktop = t; + return 0; +} + +_public_ int sd_session_get_display(const char *session, char **display) { + return session_get_string(session, "DISPLAY", display); +} + +_public_ int sd_session_get_remote_user(const char *session, char **remote_user) { + return session_get_string(session, "REMOTE_USER", remote_user); +} + +_public_ int sd_session_get_remote_host(const char *session, char **remote_host) { + return session_get_string(session, "REMOTE_HOST", remote_host); +} + +_public_ int sd_seat_get_active(const char *seat, char **session, uid_t *uid) { + _cleanup_free_ char *p = NULL, *s = NULL, *t = NULL; + int r; + + assert_return(session || uid, -EINVAL); + + r = file_of_seat(seat, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, + "ACTIVE", &s, + "ACTIVE_UID", &t, + NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + + if (session && !s) + return -ENODATA; + + if (uid && !t) + return -ENODATA; + + if (uid && t) { + r = parse_uid(t, uid); + if (r < 0) + return r; + } + + if (session && s) { + *session = s; + s = NULL; + } + + return 0; +} + +_public_ int sd_seat_get_sessions(const char *seat, char ***sessions, uid_t **uids, unsigned *n_uids) { + _cleanup_free_ char *p = NULL, *s = NULL, *t = NULL; + _cleanup_strv_free_ char **a = NULL; + _cleanup_free_ uid_t *b = NULL; + unsigned n = 0; + int r; + + r = file_of_seat(seat, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, + "SESSIONS", &s, + "ACTIVE_SESSIONS", &t, + NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + + if (s) { + a = strv_split(s, " "); + if (!a) + return -ENOMEM; + } + + if (uids && t) { + const char *word, *state; + size_t l; + + FOREACH_WORD(word, l, t, state) + n++; + + if (n > 0) { + unsigned i = 0; + + b = new(uid_t, n); + if (!b) + return -ENOMEM; + + FOREACH_WORD(word, l, t, state) { + _cleanup_free_ char *k = NULL; + + k = strndup(word, l); + if (!k) + return -ENOMEM; + + r = parse_uid(k, b + i); + if (r < 0) + continue; + + i++; + } + } + } + + r = strv_length(a); + + if (sessions) { + *sessions = a; + a = NULL; + } + + if (uids) { + *uids = b; + b = NULL; + } + + if (n_uids) + *n_uids = n; + + return r; +} + +static int seat_get_can(const char *seat, const char *variable) { + _cleanup_free_ char *p = NULL, *s = NULL; + int r; + + assert(variable); + + r = file_of_seat(seat, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, + variable, &s, + NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + if (isempty(s)) + return -ENODATA; + + return parse_boolean(s); +} + +_public_ int sd_seat_can_multi_session(const char *seat) { + return seat_get_can(seat, "CAN_MULTI_SESSION"); +} + +_public_ int sd_seat_can_tty(const char *seat) { + return seat_get_can(seat, "CAN_TTY"); +} + +_public_ int sd_seat_can_graphical(const char *seat) { + return seat_get_can(seat, "CAN_GRAPHICAL"); +} + +_public_ int sd_get_seats(char ***seats) { + return get_files_in_directory("/run/systemd/seats/", seats); +} + +_public_ int sd_get_sessions(char ***sessions) { + return get_files_in_directory("/run/systemd/sessions/", sessions); +} + +_public_ int sd_get_uids(uid_t **users) { + _cleanup_closedir_ DIR *d; + int r = 0; + unsigned n = 0; + _cleanup_free_ uid_t *l = NULL; + + d = opendir("/run/systemd/users/"); + if (!d) + return -errno; + + for (;;) { + struct dirent *de; + int k; + uid_t uid; + + errno = 0; + de = readdir(d); + if (!de && errno > 0) + return -errno; + + if (!de) + break; + + dirent_ensure_type(d, de); + + if (!dirent_is_file(de)) + continue; + + k = parse_uid(de->d_name, &uid); + if (k < 0) + continue; + + if (users) { + if ((unsigned) r >= n) { + uid_t *t; + + n = MAX(16, 2*r); + t = realloc(l, sizeof(uid_t) * n); + if (!t) + return -ENOMEM; + + l = t; + } + + assert((unsigned) r < n); + l[r++] = uid; + } else + r++; + } + + if (users) { + *users = l; + l = NULL; + } + + return r; +} + +_public_ int sd_get_machine_names(char ***machines) { + char **l = NULL, **a, **b; + int r; + + assert_return(machines, -EINVAL); + + r = get_files_in_directory("/run/systemd/machines/", &l); + if (r < 0) + return r; + + if (l) { + r = 0; + + /* Filter out the unit: symlinks */ + for (a = l, b = l; *a; a++) { + if (startswith(*a, "unit:") || !machine_name_is_valid(*a)) + free(*a); + else { + *b = *a; + b++; + r++; + } + } + + *b = NULL; + } + + *machines = l; + return r; +} + +_public_ int sd_machine_get_class(const char *machine, char **class) { + _cleanup_free_ char *c = NULL; + const char *p; + int r; + + assert_return(machine_name_is_valid(machine), -EINVAL); + assert_return(class, -EINVAL); + + p = strjoina("/run/systemd/machines/", machine); + r = parse_env_file(p, NEWLINE, "CLASS", &c, NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + if (!c) + return -EIO; + + *class = c; + c = NULL; + + return 0; +} + +_public_ int sd_machine_get_ifindices(const char *machine, int **ifindices) { + _cleanup_free_ char *netif = NULL; + size_t l, allocated = 0, nr = 0; + int *ni = NULL; + const char *p, *word, *state; + int r; + + assert_return(machine_name_is_valid(machine), -EINVAL); + assert_return(ifindices, -EINVAL); + + p = strjoina("/run/systemd/machines/", machine); + r = parse_env_file(p, NEWLINE, "NETIF", &netif, NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + if (!netif) { + *ifindices = NULL; + return 0; + } + + FOREACH_WORD(word, l, netif, state) { + char buf[l+1]; + int ifi; + + *(char*) (mempcpy(buf, word, l)) = 0; + + if (parse_ifindex(buf, &ifi) < 0) + continue; + + if (!GREEDY_REALLOC(ni, allocated, nr+1)) { + free(ni); + return -ENOMEM; + } + + ni[nr++] = ifi; + } + + *ifindices = ni; + return nr; +} + +static inline int MONITOR_TO_FD(sd_login_monitor *m) { + return (int) (unsigned long) m - 1; +} + +static inline sd_login_monitor* FD_TO_MONITOR(int fd) { + return (sd_login_monitor*) (unsigned long) (fd + 1); +} + +_public_ int sd_login_monitor_new(const char *category, sd_login_monitor **m) { + int fd, k; + bool good = false; + + assert_return(m, -EINVAL); + + fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); + if (fd < 0) + return -errno; + + if (!category || streq(category, "seat")) { + k = inotify_add_watch(fd, "/run/systemd/seats/", IN_MOVED_TO|IN_DELETE); + if (k < 0) { + safe_close(fd); + return -errno; + } + + good = true; + } + + if (!category || streq(category, "session")) { + k = inotify_add_watch(fd, "/run/systemd/sessions/", IN_MOVED_TO|IN_DELETE); + if (k < 0) { + safe_close(fd); + return -errno; + } + + good = true; + } + + if (!category || streq(category, "uid")) { + k = inotify_add_watch(fd, "/run/systemd/users/", IN_MOVED_TO|IN_DELETE); + if (k < 0) { + safe_close(fd); + return -errno; + } + + good = true; + } + + if (!category || streq(category, "machine")) { + k = inotify_add_watch(fd, "/run/systemd/machines/", IN_MOVED_TO|IN_DELETE); + if (k < 0) { + safe_close(fd); + return -errno; + } + + good = true; + } + + if (!good) { + close_nointr(fd); + return -EINVAL; + } + + *m = FD_TO_MONITOR(fd); + return 0; +} + +_public_ sd_login_monitor* sd_login_monitor_unref(sd_login_monitor *m) { + int fd; + + if (!m) + return NULL; + + fd = MONITOR_TO_FD(m); + close_nointr(fd); + + return NULL; +} + +_public_ int sd_login_monitor_flush(sd_login_monitor *m) { + + assert_return(m, -EINVAL); + + return flush_fd(MONITOR_TO_FD(m)); +} + +_public_ int sd_login_monitor_get_fd(sd_login_monitor *m) { + + assert_return(m, -EINVAL); + + return MONITOR_TO_FD(m); +} + +_public_ int sd_login_monitor_get_events(sd_login_monitor *m) { + + assert_return(m, -EINVAL); + + /* For now we will only return POLLIN here, since we don't + * need anything else ever for inotify. However, let's have + * this API to keep our options open should we later on need + * it. */ + return POLLIN; +} + +_public_ int sd_login_monitor_get_timeout(sd_login_monitor *m, uint64_t *timeout_usec) { + + assert_return(m, -EINVAL); + assert_return(timeout_usec, -EINVAL); + + /* For now we will only return (uint64_t) -1, since we don't + * need any timeout. However, let's have this API to keep our + * options open should we later on need it. */ + *timeout_usec = (uint64_t) -1; + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-login/test-login.c b/src/libsystemd/libsystemd-internal/sd-login/test-login.c new file mode 100644 index 0000000000..994c76df4a --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-login/test-login.c @@ -0,0 +1,264 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +static void test_login(void) { + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + _cleanup_free_ char *pp = NULL, *qq = NULL; + int r, k; + uid_t u, u2; + char *seat, *type, *class, *display, *remote_user, *remote_host, *display_session, *cgroup; + char *session; + char *state; + char *session2; + char *t; + char **seats, **sessions, **machines; + uid_t *uids; + unsigned n; + struct pollfd pollfd; + sd_login_monitor *m = NULL; + + assert_se(sd_pid_get_session(0, &session) == 0); + printf("session = %s\n", session); + + assert_se(sd_pid_get_owner_uid(0, &u2) == 0); + printf("user = "UID_FMT"\n", u2); + + assert_se(sd_pid_get_cgroup(0, &cgroup) == 0); + printf("cgroup = %s\n", cgroup); + free(cgroup); + + display_session = NULL; + r = sd_uid_get_display(u2, &display_session); + assert_se(r >= 0 || r == -ENODATA); + printf("user's display session = %s\n", strna(display_session)); + free(display_session); + + assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == 0); + sd_peer_get_session(pair[0], &pp); + sd_peer_get_session(pair[1], &qq); + assert_se(streq_ptr(pp, qq)); + + r = sd_uid_get_sessions(u2, false, &sessions); + assert_se(r >= 0); + assert_se(r == (int) strv_length(sessions)); + assert_se(t = strv_join(sessions, ", ")); + strv_free(sessions); + printf("sessions = %s\n", t); + free(t); + + assert_se(r == sd_uid_get_sessions(u2, false, NULL)); + + r = sd_uid_get_seats(u2, false, &seats); + assert_se(r >= 0); + assert_se(r == (int) strv_length(seats)); + assert_se(t = strv_join(seats, ", ")); + strv_free(seats); + printf("seats = %s\n", t); + free(t); + + assert_se(r == sd_uid_get_seats(u2, false, NULL)); + + r = sd_session_is_active(session); + assert_se(r >= 0); + printf("active = %s\n", yes_no(r)); + + r = sd_session_is_remote(session); + assert_se(r >= 0); + printf("remote = %s\n", yes_no(r)); + + r = sd_session_get_state(session, &state); + assert_se(r >= 0); + printf("state = %s\n", state); + free(state); + + assert_se(sd_session_get_uid(session, &u) >= 0); + printf("uid = "UID_FMT"\n", u); + assert_se(u == u2); + + assert_se(sd_session_get_type(session, &type) >= 0); + printf("type = %s\n", type); + free(type); + + assert_se(sd_session_get_class(session, &class) >= 0); + printf("class = %s\n", class); + free(class); + + display = NULL; + r = sd_session_get_display(session, &display); + assert_se(r >= 0 || r == -ENODATA); + printf("display = %s\n", strna(display)); + free(display); + + remote_user = NULL; + r = sd_session_get_remote_user(session, &remote_user); + assert_se(r >= 0 || r == -ENODATA); + printf("remote_user = %s\n", strna(remote_user)); + free(remote_user); + + remote_host = NULL; + r = sd_session_get_remote_host(session, &remote_host); + assert_se(r >= 0 || r == -ENODATA); + printf("remote_host = %s\n", strna(remote_host)); + free(remote_host); + + assert_se(sd_session_get_seat(session, &seat) >= 0); + printf("seat = %s\n", seat); + + r = sd_seat_can_multi_session(seat); + assert_se(r >= 0); + printf("can do multi session = %s\n", yes_no(r)); + + r = sd_seat_can_tty(seat); + assert_se(r >= 0); + printf("can do tty = %s\n", yes_no(r)); + + r = sd_seat_can_graphical(seat); + assert_se(r >= 0); + printf("can do graphical = %s\n", yes_no(r)); + + assert_se(sd_uid_get_state(u, &state) >= 0); + printf("state = %s\n", state); + + assert_se(sd_uid_is_on_seat(u, 0, seat) > 0); + + k = sd_uid_is_on_seat(u, 1, seat); + assert_se(k >= 0); + assert_se(!!r == !!r); + + assert_se(sd_seat_get_active(seat, &session2, &u2) >= 0); + printf("session2 = %s\n", session2); + printf("uid2 = "UID_FMT"\n", u2); + + r = sd_seat_get_sessions(seat, &sessions, &uids, &n); + assert_se(r >= 0); + printf("n_sessions = %i\n", r); + assert_se(r == (int) strv_length(sessions)); + assert_se(t = strv_join(sessions, ", ")); + strv_free(sessions); + printf("sessions = %s\n", t); + free(t); + printf("uids ="); + for (k = 0; k < (int) n; k++) + printf(" "UID_FMT, uids[k]); + printf("\n"); + free(uids); + + assert_se(sd_seat_get_sessions(seat, NULL, NULL, NULL) == r); + + free(session); + free(state); + free(session2); + free(seat); + + r = sd_get_seats(&seats); + assert_se(r >= 0); + assert_se(r == (int) strv_length(seats)); + assert_se(t = strv_join(seats, ", ")); + strv_free(seats); + printf("n_seats = %i\n", r); + printf("seats = %s\n", t); + free(t); + + assert_se(sd_get_seats(NULL) == r); + + r = sd_seat_get_active(NULL, &t, NULL); + assert_se(r >= 0); + printf("active session on current seat = %s\n", t); + free(t); + + r = sd_get_sessions(&sessions); + assert_se(r >= 0); + assert_se(r == (int) strv_length(sessions)); + assert_se(t = strv_join(sessions, ", ")); + strv_free(sessions); + printf("n_sessions = %i\n", r); + printf("sessions = %s\n", t); + free(t); + + assert_se(sd_get_sessions(NULL) == r); + + r = sd_get_uids(&uids); + assert_se(r >= 0); + + printf("uids ="); + for (k = 0; k < r; k++) + printf(" "UID_FMT, uids[k]); + printf("\n"); + free(uids); + + printf("n_uids = %i\n", r); + assert_se(sd_get_uids(NULL) == r); + + r = sd_get_machine_names(&machines); + assert_se(r >= 0); + assert_se(r == (int) strv_length(machines)); + assert_se(t = strv_join(machines, ", ")); + strv_free(machines); + printf("n_machines = %i\n", r); + printf("machines = %s\n", t); + free(t); + + r = sd_login_monitor_new("session", &m); + assert_se(r >= 0); + + for (n = 0; n < 5; n++) { + usec_t timeout, nw; + + zero(pollfd); + assert_se((pollfd.fd = sd_login_monitor_get_fd(m)) >= 0); + assert_se((pollfd.events = sd_login_monitor_get_events(m)) >= 0); + + assert_se(sd_login_monitor_get_timeout(m, &timeout) >= 0); + + nw = now(CLOCK_MONOTONIC); + + r = poll(&pollfd, 1, + timeout == (uint64_t) -1 ? -1 : + timeout > nw ? (int) ((timeout - nw) / 1000) : + 0); + + assert_se(r >= 0); + + sd_login_monitor_flush(m); + printf("Wake!\n"); + } + + sd_login_monitor_unref(m); +} + +int main(int argc, char* argv[]) { + log_parse_environment(); + log_open(); + + test_login(); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/netlink-internal.h b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-internal.h new file mode 100644 index 0000000000..1d29c3a369 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-internal.h @@ -0,0 +1,137 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Tom Gundersen + + 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 + +#include "list.h" +#include "netlink-types.h" +#include "prioq.h" +#include "refcnt.h" + +#define RTNL_DEFAULT_TIMEOUT ((usec_t) (25 * USEC_PER_SEC)) + +#define RTNL_WQUEUE_MAX 1024 +#define RTNL_RQUEUE_MAX 64*1024 + +#define RTNL_CONTAINER_DEPTH 32 + +struct reply_callback { + sd_netlink_message_handler_t callback; + void *userdata; + usec_t timeout; + uint64_t serial; + unsigned prioq_idx; +}; + +struct match_callback { + sd_netlink_message_handler_t callback; + uint16_t type; + void *userdata; + + LIST_FIELDS(struct match_callback, match_callbacks); +}; + +struct sd_netlink { + RefCount n_ref; + + int fd; + + union { + struct sockaddr sa; + struct sockaddr_nl nl; + } sockaddr; + + Hashmap *broadcast_group_refs; + bool broadcast_group_dont_leave:1; /* until we can rely on 4.2 */ + + sd_netlink_message **rqueue; + unsigned rqueue_size; + size_t rqueue_allocated; + + sd_netlink_message **rqueue_partial; + unsigned rqueue_partial_size; + size_t rqueue_partial_allocated; + + struct nlmsghdr *rbuffer; + size_t rbuffer_allocated; + + bool processing:1; + + uint32_t serial; + + struct Prioq *reply_callbacks_prioq; + Hashmap *reply_callbacks; + + LIST_HEAD(struct match_callback, match_callbacks); + + pid_t original_pid; + + sd_event_source *io_event_source; + sd_event_source *time_event_source; + sd_event_source *exit_event_source; + sd_event *event; +}; + +struct netlink_attribute { + size_t offset; /* offset from hdr to attribute */ + bool nested:1; + bool net_byteorder:1; +}; + +struct netlink_container { + const struct NLTypeSystem *type_system; /* the type system of the container */ + size_t offset; /* offset from hdr to the start of the container */ + struct netlink_attribute *attributes; + unsigned short n_attributes; /* number of attributes in container */ +}; + +struct sd_netlink_message { + RefCount n_ref; + + sd_netlink *rtnl; + + struct nlmsghdr *hdr; + struct netlink_container containers[RTNL_CONTAINER_DEPTH]; + unsigned n_containers; /* number of containers */ + bool sealed:1; + bool broadcast:1; + + sd_netlink_message *next; /* next in a chain of multi-part messages */ +}; + +int message_new(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t type); +int message_new_empty(sd_netlink *rtnl, sd_netlink_message **ret); + +int socket_open(int family); +int socket_bind(sd_netlink *nl); +int socket_broadcast_group_ref(sd_netlink *nl, unsigned group); +int socket_broadcast_group_unref(sd_netlink *nl, unsigned group); +int socket_write_message(sd_netlink *nl, sd_netlink_message *m); +int socket_read_message(sd_netlink *nl); + +int rtnl_rqueue_make_room(sd_netlink *rtnl); +int rtnl_rqueue_partial_make_room(sd_netlink *rtnl); + +/* Make sure callbacks don't destroy the rtnl connection */ +#define NETLINK_DONT_DESTROY(rtnl) \ + _cleanup_(sd_netlink_unrefp) _unused_ sd_netlink *_dont_destroy_##rtnl = sd_netlink_ref(rtnl) diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/netlink-message.c b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-message.c new file mode 100644 index 0000000000..c00785ea41 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-message.c @@ -0,0 +1,961 @@ +/*** + This file is part of systemd. + + Copyright 2013 Tom Gundersen + + 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 +#include + +#include + +#include "alloc-util.h" +#include "formats-util.h" +#include "missing.h" +#include "netlink-internal.h" +#include "netlink-types.h" +#include "netlink-util.h" +#include "refcnt.h" +#include "socket-util.h" +#include "util.h" + +#define GET_CONTAINER(m, i) ((i) < (m)->n_containers ? (struct rtattr*)((uint8_t*)(m)->hdr + (m)->containers[i].offset) : NULL) +#define PUSH_CONTAINER(m, new) (m)->container_offsets[(m)->n_containers++] = (uint8_t*)(new) - (uint8_t*)(m)->hdr; + +#define RTA_TYPE(rta) ((rta)->rta_type & NLA_TYPE_MASK) +#define RTA_FLAGS(rta) ((rta)->rta_type & ~NLA_TYPE_MASK) + +int message_new_empty(sd_netlink *rtnl, sd_netlink_message **ret) { + sd_netlink_message *m; + + assert_return(ret, -EINVAL); + + /* Note that 'rtnl' is currently unused, if we start using it internally + we must take care to avoid problems due to mutual references between + buses and their queued messages. See sd-bus. + */ + + m = new0(sd_netlink_message, 1); + if (!m) + return -ENOMEM; + + m->n_ref = REFCNT_INIT; + + m->sealed = false; + + *ret = m; + + return 0; +} + +int message_new(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t type) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + const NLType *nl_type; + size_t size; + int r; + + r = type_system_get_type(&type_system_root, &nl_type, type); + if (r < 0) + return r; + + if (type_get_type(nl_type) != NETLINK_TYPE_NESTED) + return -EINVAL; + + r = message_new_empty(rtnl, &m); + if (r < 0) + return r; + + size = NLMSG_SPACE(type_get_size(nl_type)); + + assert(size >= sizeof(struct nlmsghdr)); + m->hdr = malloc0(size); + if (!m->hdr) + return -ENOMEM; + + m->hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + + type_get_type_system(nl_type, &m->containers[0].type_system); + m->hdr->nlmsg_len = size; + m->hdr->nlmsg_type = type; + + *ret = m; + m = NULL; + + return 0; +} + +int sd_netlink_message_request_dump(sd_netlink_message *m, int dump) { + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(m->hdr->nlmsg_type == RTM_GETLINK || + m->hdr->nlmsg_type == RTM_GETADDR || + m->hdr->nlmsg_type == RTM_GETROUTE || + m->hdr->nlmsg_type == RTM_GETNEIGH, + -EINVAL); + + SET_FLAG(m->hdr->nlmsg_flags, NLM_F_DUMP, dump); + + return 0; +} + +sd_netlink_message *sd_netlink_message_ref(sd_netlink_message *m) { + if (m) + assert_se(REFCNT_INC(m->n_ref) >= 2); + + return m; +} + +sd_netlink_message *sd_netlink_message_unref(sd_netlink_message *m) { + if (m && REFCNT_DEC(m->n_ref) == 0) { + unsigned i; + + free(m->hdr); + + for (i = 0; i <= m->n_containers; i++) + free(m->containers[i].attributes); + + sd_netlink_message_unref(m->next); + + free(m); + } + + return NULL; +} + +int sd_netlink_message_get_type(sd_netlink_message *m, uint16_t *type) { + assert_return(m, -EINVAL); + assert_return(type, -EINVAL); + + *type = m->hdr->nlmsg_type; + + return 0; +} + +int sd_netlink_message_set_flags(sd_netlink_message *m, uint16_t flags) { + assert_return(m, -EINVAL); + assert_return(flags, -EINVAL); + + m->hdr->nlmsg_flags = flags; + + return 0; +} + +int sd_netlink_message_is_broadcast(sd_netlink_message *m) { + assert_return(m, -EINVAL); + + return m->broadcast; +} + +/* If successful the updated message will be correctly aligned, if + unsuccessful the old message is untouched. */ +static int add_rtattr(sd_netlink_message *m, unsigned short type, const void *data, size_t data_length) { + uint32_t rta_length; + size_t message_length, padding_length; + struct nlmsghdr *new_hdr; + struct rtattr *rta; + char *padding; + unsigned i; + int offset; + + assert(m); + assert(m->hdr); + assert(!m->sealed); + assert(NLMSG_ALIGN(m->hdr->nlmsg_len) == m->hdr->nlmsg_len); + assert(!data || data_length); + + /* get offset of the new attribute */ + offset = m->hdr->nlmsg_len; + + /* get the size of the new rta attribute (with padding at the end) */ + rta_length = RTA_LENGTH(data_length); + + /* get the new message size (with padding at the end) */ + message_length = offset + RTA_ALIGN(rta_length); + + /* realloc to fit the new attribute */ + new_hdr = realloc(m->hdr, message_length); + if (!new_hdr) + return -ENOMEM; + m->hdr = new_hdr; + + /* get pointer to the attribute we are about to add */ + rta = (struct rtattr *) ((uint8_t *) m->hdr + offset); + + /* if we are inside containers, extend them */ + for (i = 0; i < m->n_containers; i++) + GET_CONTAINER(m, i)->rta_len += message_length - offset; + + /* fill in the attribute */ + rta->rta_type = type; + rta->rta_len = rta_length; + if (data) + /* we don't deal with the case where the user lies about the type + * and gives us too little data (so don't do that) + */ + padding = mempcpy(RTA_DATA(rta), data, data_length); + + else + /* if no data was passed, make sure we still initialize the padding + note that we can have data_length > 0 (used by some containers) */ + padding = RTA_DATA(rta); + + /* make sure also the padding at the end of the message is initialized */ + padding_length = (uint8_t*)m->hdr + message_length - (uint8_t*)padding; + memzero(padding, padding_length); + + /* update message size */ + m->hdr->nlmsg_len = message_length; + + return offset; +} + +static int message_attribute_has_type(sd_netlink_message *m, size_t *out_size, uint16_t attribute_type, uint16_t data_type) { + const NLType *type; + int r; + + assert(m); + + r = type_system_get_type(m->containers[m->n_containers].type_system, &type, attribute_type); + if (r < 0) + return r; + + if (type_get_type(type) != data_type) + return -EINVAL; + + if (out_size) + *out_size = type_get_size(type); + return 0; +} + +int sd_netlink_message_append_string(sd_netlink_message *m, unsigned short type, const char *data) { + size_t length, size; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(data, -EINVAL); + + r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_STRING); + if (r < 0) + return r; + + if (size) { + length = strnlen(data, size+1); + if (length > size) + return -EINVAL; + } else + length = strlen(data); + + r = add_rtattr(m, type, data, length + 1); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_flag(sd_netlink_message *m, unsigned short type) { + size_t size; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_FLAG); + if (r < 0) + return r; + + r = add_rtattr(m, type, NULL, 0); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_u8(sd_netlink_message *m, unsigned short type, uint8_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U8); + if (r < 0) + return r; + + r = add_rtattr(m, type, &data, sizeof(uint8_t)); + if (r < 0) + return r; + + return 0; +} + + +int sd_netlink_message_append_u16(sd_netlink_message *m, unsigned short type, uint16_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U16); + if (r < 0) + return r; + + r = add_rtattr(m, type, &data, sizeof(uint16_t)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_u32(sd_netlink_message *m, unsigned short type, uint32_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U32); + if (r < 0) + return r; + + r = add_rtattr(m, type, &data, sizeof(uint32_t)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_data(sd_netlink_message *m, unsigned short type, const void *data, size_t len) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = add_rtattr(m, type, data, len); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_in_addr(sd_netlink_message *m, unsigned short type, const struct in_addr *data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(data, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); + if (r < 0) + return r; + + r = add_rtattr(m, type, data, sizeof(struct in_addr)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_in6_addr(sd_netlink_message *m, unsigned short type, const struct in6_addr *data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(data, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); + if (r < 0) + return r; + + r = add_rtattr(m, type, data, sizeof(struct in6_addr)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_ether_addr(sd_netlink_message *m, unsigned short type, const struct ether_addr *data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(data, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_ETHER_ADDR); + if (r < 0) + return r; + + r = add_rtattr(m, type, data, ETH_ALEN); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_cache_info(sd_netlink_message *m, unsigned short type, const struct ifa_cacheinfo *info) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(info, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_CACHE_INFO); + if (r < 0) + return r; + + r = add_rtattr(m, type, info, sizeof(struct ifa_cacheinfo)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_open_container(sd_netlink_message *m, unsigned short type) { + size_t size; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(m->n_containers < RTNL_CONTAINER_DEPTH, -ERANGE); + + r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_NESTED); + if (r < 0) { + const NLTypeSystemUnion *type_system_union; + int family; + + r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_UNION); + if (r < 0) + return r; + + r = sd_rtnl_message_get_family(m, &family); + if (r < 0) + return r; + + r = type_system_get_type_system_union(m->containers[m->n_containers].type_system, &type_system_union, type); + if (r < 0) + return r; + + r = type_system_union_protocol_get_type_system(type_system_union, + &m->containers[m->n_containers + 1].type_system, + family); + if (r < 0) + return r; + } else { + r = type_system_get_type_system(m->containers[m->n_containers].type_system, + &m->containers[m->n_containers + 1].type_system, + type); + if (r < 0) + return r; + } + + r = add_rtattr(m, type | NLA_F_NESTED, NULL, size); + if (r < 0) + return r; + + m->containers[m->n_containers++].offset = r; + + return 0; +} + +int sd_netlink_message_open_container_union(sd_netlink_message *m, unsigned short type, const char *key) { + const NLTypeSystemUnion *type_system_union; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = type_system_get_type_system_union(m->containers[m->n_containers].type_system, &type_system_union, type); + if (r < 0) + return r; + + r = type_system_union_get_type_system(type_system_union, + &m->containers[m->n_containers + 1].type_system, + key); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(m, type_system_union->match, key); + if (r < 0) + return r; + + /* do we evere need non-null size */ + r = add_rtattr(m, type | NLA_F_NESTED, NULL, 0); + if (r < 0) + return r; + + m->containers[m->n_containers++].offset = r; + + return 0; +} + + +int sd_netlink_message_close_container(sd_netlink_message *m) { + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(m->n_containers > 0, -EINVAL); + + m->containers[m->n_containers].type_system = NULL; + m->n_containers--; + + return 0; +} + +static int netlink_message_read_internal(sd_netlink_message *m, unsigned short type, void **data, bool *net_byteorder) { + struct netlink_attribute *attribute; + struct rtattr *rta; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + assert_return(data, -EINVAL); + assert(m->n_containers < RTNL_CONTAINER_DEPTH); + assert(m->containers[m->n_containers].attributes); + assert(type < m->containers[m->n_containers].n_attributes); + + attribute = &m->containers[m->n_containers].attributes[type]; + + if (!attribute->offset) + return -ENODATA; + + rta = (struct rtattr*)((uint8_t *) m->hdr + attribute->offset); + + *data = RTA_DATA(rta); + + if (net_byteorder) + *net_byteorder = attribute->net_byteorder; + + return RTA_PAYLOAD(rta); +} + +int sd_netlink_message_read_string(sd_netlink_message *m, unsigned short type, const char **data) { + int r; + void *attr_data; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_STRING); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + else if (strnlen(attr_data, r) >= (size_t) r) + return -EIO; + + if (data) + *data = (const char *) attr_data; + + return 0; +} + +int sd_netlink_message_read_u8(sd_netlink_message *m, unsigned short type, uint8_t *data) { + int r; + void *attr_data; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U8); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + else if ((size_t) r < sizeof(uint8_t)) + return -EIO; + + if (data) + *data = *(uint8_t *) attr_data; + + return 0; +} + +int sd_netlink_message_read_u16(sd_netlink_message *m, unsigned short type, uint16_t *data) { + void *attr_data; + bool net_byteorder; + int r; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U16); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, &net_byteorder); + if (r < 0) + return r; + else if ((size_t) r < sizeof(uint16_t)) + return -EIO; + + if (data) { + if (net_byteorder) + *data = be16toh(*(uint16_t *) attr_data); + else + *data = *(uint16_t *) attr_data; + } + + return 0; +} + +int sd_netlink_message_read_u32(sd_netlink_message *m, unsigned short type, uint32_t *data) { + void *attr_data; + bool net_byteorder; + int r; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U32); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, &net_byteorder); + if (r < 0) + return r; + else if ((size_t)r < sizeof(uint32_t)) + return -EIO; + + if (data) { + if (net_byteorder) + *data = be32toh(*(uint32_t *) attr_data); + else + *data = *(uint32_t *) attr_data; + } + + return 0; +} + +int sd_netlink_message_read_ether_addr(sd_netlink_message *m, unsigned short type, struct ether_addr *data) { + int r; + void *attr_data; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_ETHER_ADDR); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + else if ((size_t)r < sizeof(struct ether_addr)) + return -EIO; + + if (data) + memcpy(data, attr_data, sizeof(struct ether_addr)); + + return 0; +} + +int sd_netlink_message_read_cache_info(sd_netlink_message *m, unsigned short type, struct ifa_cacheinfo *info) { + int r; + void *attr_data; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_CACHE_INFO); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + else if ((size_t)r < sizeof(struct ifa_cacheinfo)) + return -EIO; + + if (info) + memcpy(info, attr_data, sizeof(struct ifa_cacheinfo)); + + return 0; +} + +int sd_netlink_message_read_in_addr(sd_netlink_message *m, unsigned short type, struct in_addr *data) { + int r; + void *attr_data; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + else if ((size_t)r < sizeof(struct in_addr)) + return -EIO; + + if (data) + memcpy(data, attr_data, sizeof(struct in_addr)); + + return 0; +} + +int sd_netlink_message_read_in6_addr(sd_netlink_message *m, unsigned short type, struct in6_addr *data) { + int r; + void *attr_data; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + else if ((size_t)r < sizeof(struct in6_addr)) + return -EIO; + + if (data) + memcpy(data, attr_data, sizeof(struct in6_addr)); + + return 0; +} + +static int netlink_container_parse(sd_netlink_message *m, + struct netlink_container *container, + int count, + struct rtattr *rta, + unsigned int rt_len) { + _cleanup_free_ struct netlink_attribute *attributes = NULL; + + attributes = new0(struct netlink_attribute, count); + if (!attributes) + return -ENOMEM; + + for (; RTA_OK(rta, rt_len); rta = RTA_NEXT(rta, rt_len)) { + unsigned short type; + + type = RTA_TYPE(rta); + + /* if the kernel is newer than the headers we used + when building, we ignore out-of-range attributes */ + if (type >= count) + continue; + + if (attributes[type].offset) + log_debug("rtnl: message parse - overwriting repeated attribute"); + + attributes[type].offset = (uint8_t *) rta - (uint8_t *) m->hdr; + attributes[type].nested = RTA_FLAGS(rta) & NLA_F_NESTED; + attributes[type].net_byteorder = RTA_FLAGS(rta) & NLA_F_NET_BYTEORDER; + } + + container->attributes = attributes; + attributes = NULL; + container->n_attributes = count; + + return 0; +} + +int sd_netlink_message_enter_container(sd_netlink_message *m, unsigned short type_id) { + const NLType *nl_type; + const NLTypeSystem *type_system; + void *container; + uint16_t type; + size_t size; + int r; + + assert_return(m, -EINVAL); + assert_return(m->n_containers < RTNL_CONTAINER_DEPTH, -EINVAL); + + r = type_system_get_type(m->containers[m->n_containers].type_system, + &nl_type, + type_id); + if (r < 0) + return r; + + type = type_get_type(nl_type); + + if (type == NETLINK_TYPE_NESTED) { + r = type_system_get_type_system(m->containers[m->n_containers].type_system, + &type_system, + type_id); + if (r < 0) + return r; + } else if (type == NETLINK_TYPE_UNION) { + const NLTypeSystemUnion *type_system_union; + + r = type_system_get_type_system_union(m->containers[m->n_containers].type_system, + &type_system_union, + type_id); + if (r < 0) + return r; + + switch (type_system_union->match_type) { + case NL_MATCH_SIBLING: + { + const char *key; + + r = sd_netlink_message_read_string(m, type_system_union->match, &key); + if (r < 0) + return r; + + r = type_system_union_get_type_system(type_system_union, + &type_system, + key); + if (r < 0) + return r; + + break; + } + case NL_MATCH_PROTOCOL: + { + int family; + + r = sd_rtnl_message_get_family(m, &family); + if (r < 0) + return r; + + r = type_system_union_protocol_get_type_system(type_system_union, + &type_system, + family); + if (r < 0) + return r; + + break; + } + default: + assert_not_reached("sd-netlink: invalid type system union type"); + } + } else + return -EINVAL; + + r = netlink_message_read_internal(m, type_id, &container, NULL); + if (r < 0) + return r; + else + size = (size_t)r; + + m->n_containers++; + + r = netlink_container_parse(m, + &m->containers[m->n_containers], + type_system_get_count(type_system), + container, + size); + if (r < 0) { + m->n_containers--; + return r; + } + + m->containers[m->n_containers].type_system = type_system; + + return 0; +} + +int sd_netlink_message_exit_container(sd_netlink_message *m) { + assert_return(m, -EINVAL); + assert_return(m->sealed, -EINVAL); + assert_return(m->n_containers > 0, -EINVAL); + + m->containers[m->n_containers].attributes = mfree(m->containers[m->n_containers].attributes); + m->containers[m->n_containers].type_system = NULL; + + m->n_containers--; + + return 0; +} + +uint32_t rtnl_message_get_serial(sd_netlink_message *m) { + assert(m); + assert(m->hdr); + + return m->hdr->nlmsg_seq; +} + +int sd_netlink_message_is_error(sd_netlink_message *m) { + assert_return(m, 0); + assert_return(m->hdr, 0); + + return m->hdr->nlmsg_type == NLMSG_ERROR; +} + +int sd_netlink_message_get_errno(sd_netlink_message *m) { + struct nlmsgerr *err; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + + if (!sd_netlink_message_is_error(m)) + return 0; + + err = NLMSG_DATA(m->hdr); + + return err->error; +} + +int sd_netlink_message_rewind(sd_netlink_message *m) { + const NLType *nl_type; + uint16_t type; + size_t size; + unsigned i; + int r; + + assert_return(m, -EINVAL); + + /* don't allow appending to message once parsed */ + if (!m->sealed) + rtnl_message_seal(m); + + for (i = 1; i <= m->n_containers; i++) + m->containers[i].attributes = mfree(m->containers[i].attributes); + + m->n_containers = 0; + + if (m->containers[0].attributes) + /* top-level attributes have already been parsed */ + return 0; + + assert(m->hdr); + + r = type_system_get_type(&type_system_root, &nl_type, m->hdr->nlmsg_type); + if (r < 0) + return r; + + type = type_get_type(nl_type); + size = type_get_size(nl_type); + + if (type == NETLINK_TYPE_NESTED) { + const NLTypeSystem *type_system; + + type_get_type_system(nl_type, &type_system); + + m->containers[0].type_system = type_system; + + r = netlink_container_parse(m, + &m->containers[m->n_containers], + type_system_get_count(type_system), + (struct rtattr*)((uint8_t*)NLMSG_DATA(m->hdr) + NLMSG_ALIGN(size)), + NLMSG_PAYLOAD(m->hdr, size)); + if (r < 0) + return r; + } + + return 0; +} + +void rtnl_message_seal(sd_netlink_message *m) { + assert(m); + assert(!m->sealed); + + m->sealed = true; +} + +sd_netlink_message *sd_netlink_message_next(sd_netlink_message *m) { + assert_return(m, NULL); + + return m->next; +} diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/netlink-socket.c b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-socket.c new file mode 100644 index 0000000000..d28a413c65 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-socket.c @@ -0,0 +1,474 @@ +/*** + This file is part of systemd. + + Copyright 2013 Tom Gundersen + + 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 +#include + +#include + +#include "alloc-util.h" +#include "formats-util.h" +#include "missing.h" +#include "netlink-internal.h" +#include "netlink-types.h" +#include "netlink-util.h" +#include "refcnt.h" +#include "socket-util.h" +#include "util.h" + +int socket_open(int family) { + int fd; + + fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, family); + if (fd < 0) + return -errno; + + return fd; +} + +static int broadcast_groups_get(sd_netlink *nl) { + _cleanup_free_ uint32_t *groups = NULL; + socklen_t len = 0, old_len; + unsigned i, j; + int r; + + assert(nl); + assert(nl->fd >= 0); + + r = getsockopt(nl->fd, SOL_NETLINK, NETLINK_LIST_MEMBERSHIPS, NULL, &len); + if (r < 0) { + if (errno == ENOPROTOOPT) { + nl->broadcast_group_dont_leave = true; + return 0; + } else + return -errno; + } + + if (len == 0) + return 0; + + groups = new0(uint32_t, len); + if (!groups) + return -ENOMEM; + + old_len = len; + + r = getsockopt(nl->fd, SOL_NETLINK, NETLINK_LIST_MEMBERSHIPS, groups, &len); + if (r < 0) + return -errno; + + if (old_len != len) + return -EIO; + + r = hashmap_ensure_allocated(&nl->broadcast_group_refs, NULL); + if (r < 0) + return r; + + for (i = 0; i < len; i++) { + for (j = 0; j < sizeof(uint32_t) * 8; j++) { + uint32_t offset; + unsigned group; + + offset = 1U << j; + + if (!(groups[i] & offset)) + continue; + + group = i * sizeof(uint32_t) * 8 + j + 1; + + r = hashmap_put(nl->broadcast_group_refs, UINT_TO_PTR(group), UINT_TO_PTR(1)); + if (r < 0) + return r; + } + } + + return 0; +} + +int socket_bind(sd_netlink *nl) { + socklen_t addrlen; + int r, one = 1; + + r = setsockopt(nl->fd, SOL_NETLINK, NETLINK_PKTINFO, &one, sizeof(one)); + if (r < 0) + return -errno; + + addrlen = sizeof(nl->sockaddr); + + r = bind(nl->fd, &nl->sockaddr.sa, addrlen); + /* ignore EINVAL to allow opening an already bound socket */ + if (r < 0 && errno != EINVAL) + return -errno; + + r = getsockname(nl->fd, &nl->sockaddr.sa, &addrlen); + if (r < 0) + return -errno; + + r = broadcast_groups_get(nl); + if (r < 0) + return r; + + return 0; +} + +static unsigned broadcast_group_get_ref(sd_netlink *nl, unsigned group) { + assert(nl); + + return PTR_TO_UINT(hashmap_get(nl->broadcast_group_refs, UINT_TO_PTR(group))); +} + +static int broadcast_group_set_ref(sd_netlink *nl, unsigned group, unsigned n_ref) { + int r; + + assert(nl); + + r = hashmap_replace(nl->broadcast_group_refs, UINT_TO_PTR(group), UINT_TO_PTR(n_ref)); + if (r < 0) + return r; + + return 0; +} + +static int broadcast_group_join(sd_netlink *nl, unsigned group) { + int r; + + assert(nl); + assert(nl->fd >= 0); + assert(group > 0); + + r = setsockopt(nl->fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group)); + if (r < 0) + return -errno; + + return 0; +} + +int socket_broadcast_group_ref(sd_netlink *nl, unsigned group) { + unsigned n_ref; + int r; + + assert(nl); + + n_ref = broadcast_group_get_ref(nl, group); + + n_ref++; + + r = hashmap_ensure_allocated(&nl->broadcast_group_refs, NULL); + if (r < 0) + return r; + + r = broadcast_group_set_ref(nl, group, n_ref); + if (r < 0) + return r; + + if (n_ref > 1) + /* not yet in the group */ + return 0; + + r = broadcast_group_join(nl, group); + if (r < 0) + return r; + + return 0; +} + +static int broadcast_group_leave(sd_netlink *nl, unsigned group) { + int r; + + assert(nl); + assert(nl->fd >= 0); + assert(group > 0); + + if (nl->broadcast_group_dont_leave) + return 0; + + r = setsockopt(nl->fd, SOL_NETLINK, NETLINK_DROP_MEMBERSHIP, &group, sizeof(group)); + if (r < 0) + return -errno; + + return 0; +} + +int socket_broadcast_group_unref(sd_netlink *nl, unsigned group) { + unsigned n_ref; + int r; + + assert(nl); + + n_ref = broadcast_group_get_ref(nl, group); + + assert(n_ref > 0); + + n_ref--; + + r = broadcast_group_set_ref(nl, group, n_ref); + if (r < 0) + return r; + + if (n_ref > 0) + /* still refs left */ + return 0; + + r = broadcast_group_leave(nl, group); + if (r < 0) + return r; + + return 0; +} + +/* returns the number of bytes sent, or a negative error code */ +int socket_write_message(sd_netlink *nl, sd_netlink_message *m) { + union { + struct sockaddr sa; + struct sockaddr_nl nl; + } addr = { + .nl.nl_family = AF_NETLINK, + }; + ssize_t k; + + assert(nl); + assert(m); + assert(m->hdr); + + k = sendto(nl->fd, m->hdr, m->hdr->nlmsg_len, + 0, &addr.sa, sizeof(addr)); + if (k < 0) + return -errno; + + return k; +} + +static int socket_recv_message(int fd, struct iovec *iov, uint32_t *_group, bool peek) { + union sockaddr_union sender; + uint8_t cmsg_buffer[CMSG_SPACE(sizeof(struct nl_pktinfo))]; + struct msghdr msg = { + .msg_iov = iov, + .msg_iovlen = 1, + .msg_name = &sender, + .msg_namelen = sizeof(sender), + .msg_control = cmsg_buffer, + .msg_controllen = sizeof(cmsg_buffer), + }; + struct cmsghdr *cmsg; + uint32_t group = 0; + int r; + + assert(fd >= 0); + assert(iov); + + r = recvmsg(fd, &msg, MSG_TRUNC | (peek ? MSG_PEEK : 0)); + if (r < 0) { + /* no data */ + if (errno == ENOBUFS) + log_debug("rtnl: kernel receive buffer overrun"); + else if (errno == EAGAIN) + log_debug("rtnl: no data in socket"); + + return (errno == EAGAIN || errno == EINTR) ? 0 : -errno; + } + + if (sender.nl.nl_pid != 0) { + /* not from the kernel, ignore */ + log_debug("rtnl: ignoring message from portid %"PRIu32, sender.nl.nl_pid); + + if (peek) { + /* drop the message */ + r = recvmsg(fd, &msg, 0); + if (r < 0) + return (errno == EAGAIN || errno == EINTR) ? 0 : -errno; + } + + return 0; + } + + CMSG_FOREACH(cmsg, &msg) { + if (cmsg->cmsg_level == SOL_NETLINK && + cmsg->cmsg_type == NETLINK_PKTINFO && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct nl_pktinfo))) { + struct nl_pktinfo *pktinfo = (void *)CMSG_DATA(cmsg); + + /* multi-cast group */ + group = pktinfo->group; + } + } + + if (_group) + *_group = group; + + return r; +} + +/* On success, the number of bytes received is returned and *ret points to the received message + * which has a valid header and the correct size. + * If nothing useful was received 0 is returned. + * On failure, a negative error code is returned. + */ +int socket_read_message(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *first = NULL; + struct iovec iov = {}; + uint32_t group = 0; + bool multi_part = false, done = false; + struct nlmsghdr *new_msg; + size_t len; + int r; + unsigned i = 0; + + assert(rtnl); + assert(rtnl->rbuffer); + assert(rtnl->rbuffer_allocated >= sizeof(struct nlmsghdr)); + + /* read nothing, just get the pending message size */ + r = socket_recv_message(rtnl->fd, &iov, NULL, true); + if (r <= 0) + return r; + else + len = (size_t)r; + + /* make room for the pending message */ + if (!greedy_realloc((void **)&rtnl->rbuffer, + &rtnl->rbuffer_allocated, + len, sizeof(uint8_t))) + return -ENOMEM; + + iov.iov_base = rtnl->rbuffer; + iov.iov_len = rtnl->rbuffer_allocated; + + /* read the pending message */ + r = socket_recv_message(rtnl->fd, &iov, &group, false); + if (r <= 0) + return r; + else + len = (size_t)r; + + if (len > rtnl->rbuffer_allocated) + /* message did not fit in read buffer */ + return -EIO; + + if (NLMSG_OK(rtnl->rbuffer, len) && rtnl->rbuffer->nlmsg_flags & NLM_F_MULTI) { + multi_part = true; + + for (i = 0; i < rtnl->rqueue_partial_size; i++) { + if (rtnl_message_get_serial(rtnl->rqueue_partial[i]) == + rtnl->rbuffer->nlmsg_seq) { + first = rtnl->rqueue_partial[i]; + break; + } + } + } + + for (new_msg = rtnl->rbuffer; NLMSG_OK(new_msg, len) && !done; new_msg = NLMSG_NEXT(new_msg, len)) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + const NLType *nl_type; + + if (!group && new_msg->nlmsg_pid != rtnl->sockaddr.nl.nl_pid) + /* not broadcast and not for us */ + continue; + + if (new_msg->nlmsg_type == NLMSG_NOOP) + /* silently drop noop messages */ + continue; + + if (new_msg->nlmsg_type == NLMSG_DONE) { + /* finished reading multi-part message */ + done = true; + + /* if first is not defined, put NLMSG_DONE into the receive queue. */ + if (first) + continue; + } + + /* check that we support this message type */ + r = type_system_get_type(&type_system_root, &nl_type, new_msg->nlmsg_type); + if (r < 0) { + if (r == -EOPNOTSUPP) + log_debug("sd-netlink: ignored message with unknown type: %i", + new_msg->nlmsg_type); + + continue; + } + + /* check that the size matches the message type */ + if (new_msg->nlmsg_len < NLMSG_LENGTH(type_get_size(nl_type))) { + log_debug("sd-netlink: message larger than expected, dropping"); + continue; + } + + r = message_new_empty(rtnl, &m); + if (r < 0) + return r; + + m->broadcast = !!group; + + m->hdr = memdup(new_msg, new_msg->nlmsg_len); + if (!m->hdr) + return -ENOMEM; + + /* seal and parse the top-level message */ + r = sd_netlink_message_rewind(m); + if (r < 0) + return r; + + /* push the message onto the multi-part message stack */ + if (first) + m->next = first; + first = m; + m = NULL; + } + + if (len) + log_debug("sd-netlink: discarding %zu bytes of incoming message", len); + + if (!first) + return 0; + + if (!multi_part || done) { + /* we got a complete message, push it on the read queue */ + r = rtnl_rqueue_make_room(rtnl); + if (r < 0) + return r; + + rtnl->rqueue[rtnl->rqueue_size++] = first; + first = NULL; + + if (multi_part && (i < rtnl->rqueue_partial_size)) { + /* remove the message form the partial read queue */ + memmove(rtnl->rqueue_partial + i,rtnl->rqueue_partial + i + 1, + sizeof(sd_netlink_message*) * (rtnl->rqueue_partial_size - i - 1)); + rtnl->rqueue_partial_size--; + } + + return 1; + } else { + /* we only got a partial multi-part message, push it on the + partial read queue */ + if (i < rtnl->rqueue_partial_size) { + rtnl->rqueue_partial[i] = first; + } else { + r = rtnl_rqueue_partial_make_room(rtnl); + if (r < 0) + return r; + + rtnl->rqueue_partial[rtnl->rqueue_partial_size++] = first; + } + first = NULL; + + return 0; + } +} diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/netlink-types.c b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-types.c new file mode 100644 index 0000000000..3a4bac2ced --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-types.c @@ -0,0 +1,684 @@ +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "macro.h" +#include "missing.h" +#include "netlink-types.h" +#include "string-table.h" +#include "util.h" + +/* Maximum ARP IP target defined in kernel */ +#define BOND_MAX_ARP_TARGETS 16 + +typedef enum { + BOND_ARP_TARGETS_0, + BOND_ARP_TARGETS_1, + BOND_ARP_TARGETS_2, + BOND_ARP_TARGETS_3, + BOND_ARP_TARGETS_4, + BOND_ARP_TARGETS_5, + BOND_ARP_TARGETS_6, + BOND_ARP_TARGETS_7, + BOND_ARP_TARGETS_8, + BOND_ARP_TARGETS_9, + BOND_ARP_TARGETS_10, + BOND_ARP_TARGETS_11, + BOND_ARP_TARGETS_12, + BOND_ARP_TARGETS_13, + BOND_ARP_TARGETS_14, + BOND_ARP_TARGETS_MAX = BOND_MAX_ARP_TARGETS, +} BondArpTargets; + +struct NLType { + uint16_t type; + size_t size; + const NLTypeSystem *type_system; + const NLTypeSystemUnion *type_system_union; +}; + +struct NLTypeSystem { + uint16_t count; + const NLType *types; +}; + +static const NLTypeSystem rtnl_link_type_system; + +static const NLType empty_types[1] = { + /* fake array to avoid .types==NULL, which denotes invalid type-systems */ +}; + +static const NLTypeSystem empty_type_system = { + .count = 0, + .types = empty_types, +}; + +static const NLType rtnl_link_info_data_veth_types[] = { + [VETH_INFO_PEER] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, +}; + +static const NLType rtnl_link_info_data_ipvlan_types[] = { + [IFLA_IPVLAN_MODE] = { .type = NETLINK_TYPE_U16 }, +}; + +static const NLType rtnl_link_info_data_macvlan_types[] = { + [IFLA_MACVLAN_MODE] = { .type = NETLINK_TYPE_U32 }, + [IFLA_MACVLAN_FLAGS] = { .type = NETLINK_TYPE_U16 }, +}; + +static const NLType rtnl_link_info_data_bridge_types[] = { + [IFLA_BR_FORWARD_DELAY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_HELLO_TIME] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_MAX_AGE] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_AGEING_TIME] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_STP_STATE] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_PRIORITY] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_VLAN_FILTERING] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_VLAN_PROTOCOL] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_GROUP_FWD_MASK] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_ROOT_PORT] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_ROOT_PATH_COST] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_TOPOLOGY_CHANGE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_TOPOLOGY_CHANGE_DETECTED] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_HELLO_TIMER] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_TCN_TIMER] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_TOPOLOGY_CHANGE_TIMER] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_GC_TIMER] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BR_GROUP_ADDR] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_FDB_FLUSH] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_MCAST_ROUTER] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_MCAST_SNOOPING] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_MCAST_QUERY_USE_IFADDR] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_MCAST_QUERIER] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_MCAST_HASH_ELASTICITY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_MCAST_HASH_MAX] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_MCAST_LAST_MEMBER_CNT] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_MCAST_STARTUP_QUERY_CNT] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_MCAST_LAST_MEMBER_INTVL] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BR_MCAST_MEMBERSHIP_INTVL] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BR_MCAST_QUERIER_INTVL] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BR_MCAST_QUERY_INTVL] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BR_MCAST_QUERY_RESPONSE_INTVL] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BR_MCAST_STARTUP_QUERY_INTVL] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BR_NF_CALL_IPTABLES] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_NF_CALL_IP6TABLES] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_NF_CALL_ARPTABLES] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_VLAN_DEFAULT_PVID] = { .type = NETLINK_TYPE_U16 }, +}; + +static const NLType rtnl_link_info_data_vlan_types[] = { + [IFLA_VLAN_ID] = { .type = NETLINK_TYPE_U16 }, +/* + [IFLA_VLAN_FLAGS] = { .len = sizeof(struct ifla_vlan_flags) }, + [IFLA_VLAN_EGRESS_QOS] = { .type = NETLINK_TYPE_NESTED }, + [IFLA_VLAN_INGRESS_QOS] = { .type = NETLINK_TYPE_NESTED }, +*/ + [IFLA_VLAN_PROTOCOL] = { .type = NETLINK_TYPE_U16 }, +}; + +static const NLType rtnl_link_info_data_vxlan_types[] = { + [IFLA_VXLAN_ID] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VXLAN_GROUP] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_VXLAN_LINK] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VXLAN_LOCAL] = { .type = NETLINK_TYPE_U32}, + [IFLA_VXLAN_TTL] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_TOS] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_LEARNING] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_AGEING] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VXLAN_LIMIT] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VXLAN_PORT_RANGE] = { .type = NETLINK_TYPE_U32}, + [IFLA_VXLAN_PROXY] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_RSC] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_L2MISS] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_L3MISS] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_PORT] = { .type = NETLINK_TYPE_U16 }, + [IFLA_VXLAN_GROUP6] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_VXLAN_LOCAL6] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_VXLAN_UDP_CSUM] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_UDP_ZERO_CSUM6_TX] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_UDP_ZERO_CSUM6_RX] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_REMCSUM_TX] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_REMCSUM_RX] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_GBP] = { .type = NETLINK_TYPE_FLAG }, + [IFLA_VXLAN_REMCSUM_NOPARTIAL] = { .type = NETLINK_TYPE_FLAG }, +}; + +static const NLType rtnl_bond_arp_target_types[] = { + [BOND_ARP_TARGETS_0] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_1] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_2] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_3] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_4] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_5] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_6] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_7] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_8] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_9] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_10] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_11] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_12] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_13] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_14] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_MAX] = { .type = NETLINK_TYPE_U32 }, +}; + +static const NLTypeSystem rtnl_bond_arp_type_system = { + .count = ELEMENTSOF(rtnl_bond_arp_target_types), + .types = rtnl_bond_arp_target_types, +}; + +static const NLType rtnl_link_info_data_bond_types[] = { + [IFLA_BOND_MODE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_ACTIVE_SLAVE] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_MIIMON] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_UPDELAY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_DOWNDELAY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_USE_CARRIER] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_ARP_INTERVAL] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_ARP_IP_TARGET] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_bond_arp_type_system }, + [IFLA_BOND_ARP_VALIDATE] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_ARP_ALL_TARGETS] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_PRIMARY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_PRIMARY_RESELECT] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_FAIL_OVER_MAC] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_XMIT_HASH_POLICY] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_RESEND_IGMP] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_NUM_PEER_NOTIF] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_ALL_SLAVES_ACTIVE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_MIN_LINKS] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_LP_INTERVAL] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_PACKETS_PER_SLAVE] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_AD_LACP_RATE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_AD_SELECT] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_AD_INFO] = { .type = NETLINK_TYPE_NESTED }, +}; + +static const NLType rtnl_link_info_data_iptun_types[] = { + [IFLA_IPTUN_LINK] = { .type = NETLINK_TYPE_U32 }, + [IFLA_IPTUN_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_IPTUN_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_IPTUN_TTL] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_TOS] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_PMTUDISC] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_FLAGS] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_PROTO] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_6RD_PREFIX] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_IPTUN_6RD_RELAY_PREFIX] = { .type = NETLINK_TYPE_U32 }, + [IFLA_IPTUN_6RD_PREFIXLEN] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_6RD_RELAY_PREFIXLEN] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_ENCAP_TYPE] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_ENCAP_FLAGS] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_ENCAP_SPORT] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_ENCAP_DPORT] = { .type = NETLINK_TYPE_U16 }, +}; + +static const NLType rtnl_link_info_data_ipgre_types[] = { + [IFLA_GRE_LINK] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_IFLAGS] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_OFLAGS] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_IKEY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_OKEY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_GRE_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_GRE_TTL] = { .type = NETLINK_TYPE_U8 }, + [IFLA_GRE_TOS] = { .type = NETLINK_TYPE_U8 }, + [IFLA_GRE_PMTUDISC] = { .type = NETLINK_TYPE_U8 }, + [IFLA_GRE_FLOWINFO] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_FLAGS] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_ENCAP_TYPE] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_ENCAP_FLAGS] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_ENCAP_SPORT] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_ENCAP_DPORT] = { .type = NETLINK_TYPE_U16 }, +}; + +static const NLType rtnl_link_info_data_ipvti_types[] = { + [IFLA_VTI_LINK] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VTI_IKEY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VTI_OKEY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VTI_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_VTI_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, +}; + +static const NLType rtnl_link_info_data_ip6tnl_types[] = { + [IFLA_IPTUN_LINK] = { .type = NETLINK_TYPE_U32 }, + [IFLA_IPTUN_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_IPTUN_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_IPTUN_TTL] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_FLAGS] = { .type = NETLINK_TYPE_U32 }, + [IFLA_IPTUN_PROTO] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_ENCAP_LIMIT] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_FLOWINFO] = { .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_BRIDGE] = "bridge", + [NL_UNION_LINK_INFO_DATA_VLAN] = "vlan", + [NL_UNION_LINK_INFO_DATA_VETH] = "veth", + [NL_UNION_LINK_INFO_DATA_DUMMY] = "dummy", + [NL_UNION_LINK_INFO_DATA_MACVLAN] = "macvlan", + [NL_UNION_LINK_INFO_DATA_MACVTAP] = "macvtap", + [NL_UNION_LINK_INFO_DATA_IPVLAN] = "ipvlan", + [NL_UNION_LINK_INFO_DATA_VXLAN] = "vxlan", + [NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL] = "ipip", + [NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL] = "gre", + [NL_UNION_LINK_INFO_DATA_IPGRETAP_TUNNEL] = "gretap", + [NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL] = "ip6gre", + [NL_UNION_LINK_INFO_DATA_IP6GRETAP_TUNNEL] = "ip6gretap", + [NL_UNION_LINK_INFO_DATA_SIT_TUNNEL] = "sit", + [NL_UNION_LINK_INFO_DATA_VTI_TUNNEL] = "vti", + [NL_UNION_LINK_INFO_DATA_VTI6_TUNNEL] = "vti6", + [NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL] = "ip6tnl", +}; + +DEFINE_STRING_TABLE_LOOKUP(nl_union_link_info_data, NLUnionLinkInfoData); + +static const NLTypeSystem rtnl_link_info_data_type_systems[] = { + [NL_UNION_LINK_INFO_DATA_BOND] = { .count = ELEMENTSOF(rtnl_link_info_data_bond_types), + .types = rtnl_link_info_data_bond_types }, + [NL_UNION_LINK_INFO_DATA_BRIDGE] = { .count = ELEMENTSOF(rtnl_link_info_data_bridge_types), + .types = rtnl_link_info_data_bridge_types }, + [NL_UNION_LINK_INFO_DATA_VLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_vlan_types), + .types = rtnl_link_info_data_vlan_types }, + [NL_UNION_LINK_INFO_DATA_VETH] = { .count = ELEMENTSOF(rtnl_link_info_data_veth_types), + .types = rtnl_link_info_data_veth_types }, + [NL_UNION_LINK_INFO_DATA_MACVLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_macvlan_types), + .types = rtnl_link_info_data_macvlan_types }, + [NL_UNION_LINK_INFO_DATA_MACVTAP] = { .count = ELEMENTSOF(rtnl_link_info_data_macvlan_types), + .types = rtnl_link_info_data_macvlan_types }, + [NL_UNION_LINK_INFO_DATA_IPVLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvlan_types), + .types = rtnl_link_info_data_ipvlan_types }, + [NL_UNION_LINK_INFO_DATA_VXLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_vxlan_types), + .types = rtnl_link_info_data_vxlan_types }, + [NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_iptun_types), + .types = rtnl_link_info_data_iptun_types }, + [NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), + .types = rtnl_link_info_data_ipgre_types }, + [NL_UNION_LINK_INFO_DATA_IPGRETAP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), + .types = rtnl_link_info_data_ipgre_types }, + [NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), + .types = rtnl_link_info_data_ipgre_types }, + [NL_UNION_LINK_INFO_DATA_IP6GRETAP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), + .types = rtnl_link_info_data_ipgre_types }, + [NL_UNION_LINK_INFO_DATA_SIT_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_iptun_types), + .types = rtnl_link_info_data_iptun_types }, + [NL_UNION_LINK_INFO_DATA_VTI_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvti_types), + .types = rtnl_link_info_data_ipvti_types }, + [NL_UNION_LINK_INFO_DATA_VTI6_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvti_types), + .types = rtnl_link_info_data_ipvti_types }, + [NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ip6tnl_types), + .types = rtnl_link_info_data_ip6tnl_types }, + +}; + +static const NLTypeSystemUnion rtnl_link_info_data_type_system_union = { + .num = _NL_UNION_LINK_INFO_DATA_MAX, + .lookup = nl_union_link_info_data_from_string, + .type_systems = rtnl_link_info_data_type_systems, + .match_type = NL_MATCH_SIBLING, + .match = IFLA_INFO_KIND, +}; + +static const NLType rtnl_link_info_types[] = { + [IFLA_INFO_KIND] = { .type = NETLINK_TYPE_STRING }, + [IFLA_INFO_DATA] = { .type = NETLINK_TYPE_UNION, .type_system_union = &rtnl_link_info_data_type_system_union}, +/* + [IFLA_INFO_XSTATS], + [IFLA_INFO_SLAVE_KIND] = { .type = NETLINK_TYPE_STRING }, + [IFLA_INFO_SLAVE_DATA] = { .type = NETLINK_TYPE_NESTED }, +*/ +}; + +static const NLTypeSystem rtnl_link_info_type_system = { + .count = ELEMENTSOF(rtnl_link_info_types), + .types = rtnl_link_info_types, +}; + +static const struct NLType rtnl_prot_info_bridge_port_types[] = { + [IFLA_BRPORT_STATE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_COST] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BRPORT_PRIORITY] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BRPORT_MODE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_GUARD] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_PROTECT] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_FAST_LEAVE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_LEARNING] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_UNICAST_FLOOD] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_PROXYARP] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_LEARNING_SYNC] = { .type = NETLINK_TYPE_U8 }, +}; + +static const NLTypeSystem rtnl_prot_info_type_systems[] = { + [AF_BRIDGE] = { .count = ELEMENTSOF(rtnl_prot_info_bridge_port_types), + .types = rtnl_prot_info_bridge_port_types }, +}; + +static const NLTypeSystemUnion rtnl_prot_info_type_system_union = { + .num = AF_MAX, + .type_systems = rtnl_prot_info_type_systems, + .match_type = NL_MATCH_PROTOCOL, +}; + +static const struct NLType rtnl_af_spec_inet6_types[] = { + [IFLA_INET6_FLAGS] = { .type = NETLINK_TYPE_U32 }, +/* + IFLA_INET6_CONF, + IFLA_INET6_STATS, + IFLA_INET6_MCAST, + IFLA_INET6_CACHEINFO, + IFLA_INET6_ICMP6STATS, +*/ + [IFLA_INET6_TOKEN] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_INET6_ADDR_GEN_MODE] = { .type = NETLINK_TYPE_U8 }, +}; + +static const NLTypeSystem rtnl_af_spec_inet6_type_system = { + .count = ELEMENTSOF(rtnl_af_spec_inet6_types), + .types = rtnl_af_spec_inet6_types, +}; + +static const NLType rtnl_af_spec_types[] = { + [AF_INET6] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_af_spec_inet6_type_system }, +}; + +static const NLTypeSystem rtnl_af_spec_type_system = { + .count = ELEMENTSOF(rtnl_af_spec_types), + .types = rtnl_af_spec_types, +}; + +static const NLType rtnl_link_types[] = { + [IFLA_ADDRESS] = { .type = NETLINK_TYPE_ETHER_ADDR }, + [IFLA_BROADCAST] = { .type = NETLINK_TYPE_ETHER_ADDR }, + [IFLA_IFNAME] = { .type = NETLINK_TYPE_STRING, .size = IFNAMSIZ - 1 }, + [IFLA_MTU] = { .type = NETLINK_TYPE_U32 }, + [IFLA_LINK] = { .type = NETLINK_TYPE_U32 }, +/* + [IFLA_QDISC], + [IFLA_STATS], + [IFLA_COST], + [IFLA_PRIORITY], +*/ + [IFLA_MASTER] = { .type = NETLINK_TYPE_U32 }, +/* + [IFLA_WIRELESS], +*/ + [IFLA_PROTINFO] = { .type = NETLINK_TYPE_UNION, .type_system_union = &rtnl_prot_info_type_system_union }, + [IFLA_TXQLEN] = { .type = NETLINK_TYPE_U32 }, +/* + [IFLA_MAP] = { .len = sizeof(struct rtnl_link_ifmap) }, +*/ + [IFLA_WEIGHT] = { .type = NETLINK_TYPE_U32 }, + [IFLA_OPERSTATE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_LINKMODE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_LINKINFO] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_info_type_system }, + [IFLA_NET_NS_PID] = { .type = NETLINK_TYPE_U32 }, + [IFLA_IFALIAS] = { .type = NETLINK_TYPE_STRING, .size = IFALIASZ - 1 }, +/* + [IFLA_NUM_VF], + [IFLA_VFINFO_LIST] = {. type = NETLINK_TYPE_NESTED, }, + [IFLA_STATS64], + [IFLA_VF_PORTS] = { .type = NETLINK_TYPE_NESTED }, + [IFLA_PORT_SELF] = { .type = NETLINK_TYPE_NESTED }, +*/ + [IFLA_AF_SPEC] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_af_spec_type_system }, +/* + [IFLA_VF_PORTS], + [IFLA_PORT_SELF], + [IFLA_AF_SPEC], +*/ + [IFLA_GROUP] = { .type = NETLINK_TYPE_U32 }, + [IFLA_NET_NS_FD] = { .type = NETLINK_TYPE_U32 }, + [IFLA_EXT_MASK] = { .type = NETLINK_TYPE_U32 }, + [IFLA_PROMISCUITY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_NUM_TX_QUEUES] = { .type = NETLINK_TYPE_U32 }, + [IFLA_NUM_RX_QUEUES] = { .type = NETLINK_TYPE_U32 }, + [IFLA_CARRIER] = { .type = NETLINK_TYPE_U8 }, +/* + [IFLA_PHYS_PORT_ID] = { .type = NETLINK_TYPE_BINARY, .len = MAX_PHYS_PORT_ID_LEN }, +*/ +}; + +static const NLTypeSystem rtnl_link_type_system = { + .count = ELEMENTSOF(rtnl_link_types), + .types = rtnl_link_types, +}; + +/* IFA_FLAGS was defined in kernel 3.14, but we still support older + * kernels where IFA_MAX is lower. */ +static const NLType rtnl_address_types[] = { + [IFA_ADDRESS] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFA_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFA_LABEL] = { .type = NETLINK_TYPE_STRING, .size = IFNAMSIZ - 1 }, + [IFA_BROADCAST] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ + [IFA_CACHEINFO] = { .type = NETLINK_TYPE_CACHE_INFO, .size = sizeof(struct ifa_cacheinfo) }, +/* + [IFA_ANYCAST], + [IFA_MULTICAST], +*/ + [IFA_FLAGS] = { .type = NETLINK_TYPE_U32 }, +}; + +static const NLTypeSystem rtnl_address_type_system = { + .count = ELEMENTSOF(rtnl_address_types), + .types = rtnl_address_types, +}; + +static const NLType rtnl_route_types[] = { + [RTA_DST] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ + [RTA_SRC] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ + [RTA_IIF] = { .type = NETLINK_TYPE_U32 }, + [RTA_OIF] = { .type = NETLINK_TYPE_U32 }, + [RTA_GATEWAY] = { .type = NETLINK_TYPE_IN_ADDR }, + [RTA_PRIORITY] = { .type = NETLINK_TYPE_U32 }, + [RTA_PREFSRC] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ +/* + [RTA_METRICS] = { .type = NETLINK_TYPE_NESTED }, + [RTA_MULTIPATH] = { .len = sizeof(struct rtnexthop) }, +*/ + [RTA_FLOW] = { .type = NETLINK_TYPE_U32 }, /* 6? */ +/* + RTA_CACHEINFO, + RTA_TABLE, + RTA_MARK, + RTA_MFC_STATS, + RTA_VIA, + RTA_NEWDST, +*/ + [RTA_PREF] = { .type = NETLINK_TYPE_U8 }, + +}; + +static const NLTypeSystem rtnl_route_type_system = { + .count = ELEMENTSOF(rtnl_route_types), + .types = rtnl_route_types, +}; + +static const NLType rtnl_neigh_types[] = { + [NDA_DST] = { .type = NETLINK_TYPE_IN_ADDR }, + [NDA_LLADDR] = { .type = NETLINK_TYPE_ETHER_ADDR }, + [NDA_CACHEINFO] = { .type = NETLINK_TYPE_CACHE_INFO, .size = sizeof(struct nda_cacheinfo) }, + [NDA_PROBES] = { .type = NETLINK_TYPE_U32 }, + [NDA_VLAN] = { .type = NETLINK_TYPE_U16 }, + [NDA_PORT] = { .type = NETLINK_TYPE_U16 }, + [NDA_VNI] = { .type = NETLINK_TYPE_U32 }, + [NDA_IFINDEX] = { .type = NETLINK_TYPE_U32 }, +}; + +static const NLTypeSystem rtnl_neigh_type_system = { + .count = ELEMENTSOF(rtnl_neigh_types), + .types = rtnl_neigh_types, +}; + +static const NLType rtnl_types[] = { + [NLMSG_DONE] = { .type = NETLINK_TYPE_NESTED, .type_system = &empty_type_system, .size = 0 }, + [NLMSG_ERROR] = { .type = NETLINK_TYPE_NESTED, .type_system = &empty_type_system, .size = sizeof(struct nlmsgerr) }, + [RTM_NEWLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, + [RTM_DELLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, + [RTM_GETLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, + [RTM_SETLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, + [RTM_NEWADDR] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_address_type_system, .size = sizeof(struct ifaddrmsg) }, + [RTM_DELADDR] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_address_type_system, .size = sizeof(struct ifaddrmsg) }, + [RTM_GETADDR] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_address_type_system, .size = sizeof(struct ifaddrmsg) }, + [RTM_NEWROUTE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_route_type_system, .size = sizeof(struct rtmsg) }, + [RTM_DELROUTE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_route_type_system, .size = sizeof(struct rtmsg) }, + [RTM_GETROUTE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_route_type_system, .size = sizeof(struct rtmsg) }, + [RTM_NEWNEIGH] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_neigh_type_system, .size = sizeof(struct ndmsg) }, + [RTM_DELNEIGH] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_neigh_type_system, .size = sizeof(struct ndmsg) }, + [RTM_GETNEIGH] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_neigh_type_system, .size = sizeof(struct ndmsg) }, +}; + +const NLTypeSystem type_system_root = { + .count = ELEMENTSOF(rtnl_types), + .types = rtnl_types, +}; + +uint16_t type_get_type(const NLType *type) { + assert(type); + return type->type; +} + +size_t type_get_size(const NLType *type) { + assert(type); + return type->size; +} + +void type_get_type_system(const NLType *nl_type, const NLTypeSystem **ret) { + assert(nl_type); + assert(ret); + assert(nl_type->type == NETLINK_TYPE_NESTED); + assert(nl_type->type_system); + + *ret = nl_type->type_system; +} + +void type_get_type_system_union(const NLType *nl_type, const NLTypeSystemUnion **ret) { + assert(nl_type); + assert(ret); + assert(nl_type->type == NETLINK_TYPE_UNION); + assert(nl_type->type_system_union); + + *ret = nl_type->type_system_union; +} + +uint16_t type_system_get_count(const NLTypeSystem *type_system) { + assert(type_system); + return type_system->count; +} + +int type_system_get_type(const NLTypeSystem *type_system, const NLType **ret, uint16_t type) { + const NLType *nl_type; + + assert(ret); + assert(type_system); + assert(type_system->types); + + if (type >= type_system->count) + return -EOPNOTSUPP; + + nl_type = &type_system->types[type]; + + if (nl_type->type == NETLINK_TYPE_UNSPEC) + return -EOPNOTSUPP; + + *ret = nl_type; + + return 0; +} + +int type_system_get_type_system(const NLTypeSystem *type_system, const NLTypeSystem **ret, uint16_t type) { + const NLType *nl_type; + int r; + + assert(ret); + + r = type_system_get_type(type_system, &nl_type, type); + if (r < 0) + return r; + + type_get_type_system(nl_type, ret); + return 0; +} + +int type_system_get_type_system_union(const NLTypeSystem *type_system, const NLTypeSystemUnion **ret, uint16_t type) { + const NLType *nl_type; + int r; + + assert(ret); + + r = type_system_get_type(type_system, &nl_type, type); + if (r < 0) + return r; + + type_get_type_system_union(nl_type, ret); + return 0; +} + +int type_system_union_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, const char *key) { + int type; + + assert(type_system_union); + assert(type_system_union->match_type == NL_MATCH_SIBLING); + assert(type_system_union->lookup); + assert(type_system_union->type_systems); + assert(ret); + assert(key); + + type = type_system_union->lookup(key); + if (type < 0) + return -EOPNOTSUPP; + + assert(type < type_system_union->num); + + *ret = &type_system_union->type_systems[type]; + + return 0; +} + +int type_system_union_protocol_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, uint16_t protocol) { + const NLTypeSystem *type_system; + + assert(type_system_union); + assert(type_system_union->type_systems); + assert(type_system_union->match_type == NL_MATCH_PROTOCOL); + assert(ret); + + if (protocol >= type_system_union->num) + return -EOPNOTSUPP; + + type_system = &type_system_union->type_systems[protocol]; + if (!type_system->types) + return -EOPNOTSUPP; + + *ret = type_system; + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/netlink-types.h b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-types.h new file mode 100644 index 0000000000..ecb20bfcdc --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-types.h @@ -0,0 +1,94 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 "macro.h" + +enum { + NETLINK_TYPE_UNSPEC, + NETLINK_TYPE_U8, /* NLA_U8 */ + NETLINK_TYPE_U16, /* NLA_U16 */ + NETLINK_TYPE_U32, /* NLA_U32 */ + NETLINK_TYPE_U64, /* NLA_U64 */ + NETLINK_TYPE_STRING, /* NLA_STRING */ + NETLINK_TYPE_FLAG, /* NLA_FLAG */ + NETLINK_TYPE_IN_ADDR, + NETLINK_TYPE_ETHER_ADDR, + NETLINK_TYPE_CACHE_INFO, + NETLINK_TYPE_NESTED, /* NLA_NESTED */ + NETLINK_TYPE_UNION, +}; + +typedef enum NLMatchType { + NL_MATCH_SIBLING, + NL_MATCH_PROTOCOL, +} NLMatchType; + +typedef struct NLTypeSystemUnion NLTypeSystemUnion; +typedef struct NLTypeSystem NLTypeSystem; +typedef struct NLType NLType; + +struct NLTypeSystemUnion { + int num; + NLMatchType match_type; + uint16_t match; + int (*lookup)(const char *); + const NLTypeSystem *type_systems; +}; + +extern const NLTypeSystem type_system_root; + +uint16_t type_get_type(const NLType *type); +size_t type_get_size(const NLType *type); +void type_get_type_system(const NLType *type, const NLTypeSystem **ret); +void type_get_type_system_union(const NLType *type, const NLTypeSystemUnion **ret); + +uint16_t type_system_get_count(const NLTypeSystem *type_system); +int type_system_get_type(const NLTypeSystem *type_system, const NLType **ret, uint16_t type); +int type_system_get_type_system(const NLTypeSystem *type_system, const NLTypeSystem **ret, uint16_t type); +int type_system_get_type_system_union(const NLTypeSystem *type_system, const NLTypeSystemUnion **ret, uint16_t type); +int type_system_union_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, const char *key); +int type_system_union_protocol_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, uint16_t protocol); + +typedef enum NLUnionLinkInfoData { + NL_UNION_LINK_INFO_DATA_BOND, + NL_UNION_LINK_INFO_DATA_BRIDGE, + NL_UNION_LINK_INFO_DATA_VLAN, + NL_UNION_LINK_INFO_DATA_VETH, + NL_UNION_LINK_INFO_DATA_DUMMY, + NL_UNION_LINK_INFO_DATA_MACVLAN, + NL_UNION_LINK_INFO_DATA_MACVTAP, + NL_UNION_LINK_INFO_DATA_IPVLAN, + NL_UNION_LINK_INFO_DATA_VXLAN, + NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL, + NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL, + NL_UNION_LINK_INFO_DATA_IPGRETAP_TUNNEL, + NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL, + NL_UNION_LINK_INFO_DATA_IP6GRETAP_TUNNEL, + NL_UNION_LINK_INFO_DATA_SIT_TUNNEL, + NL_UNION_LINK_INFO_DATA_VTI_TUNNEL, + NL_UNION_LINK_INFO_DATA_VTI6_TUNNEL, + NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL, + _NL_UNION_LINK_INFO_DATA_MAX, + _NL_UNION_LINK_INFO_DATA_INVALID = -1 +} NLUnionLinkInfoData; + +const char *nl_union_link_info_data_to_string(NLUnionLinkInfoData p) _const_; +NLUnionLinkInfoData nl_union_link_info_data_from_string(const char *p) _pure_; diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/netlink-util.c b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-util.c new file mode 100644 index 0000000000..828ae7db7f --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-util.c @@ -0,0 +1,170 @@ +/*** + This file is part of systemd. + + Copyright (C) 2013 Tom Gundersen + + 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 "netlink-internal.h" +#include "netlink-util.h" + +int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + int r; + + assert(rtnl); + assert(ifindex > 0); + assert(name); + + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return r; + } + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(message, IFLA_IFNAME, name); + if (r < 0) + return r; + + r = sd_netlink_call(*rtnl, message, 0, NULL); + if (r < 0) + return r; + + return 0; +} + +int rtnl_set_link_properties(sd_netlink **rtnl, int ifindex, const char *alias, + const struct ether_addr *mac, unsigned mtu) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + int r; + + assert(rtnl); + assert(ifindex > 0); + + if (!alias && !mac && mtu == 0) + return 0; + + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return r; + } + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); + if (r < 0) + return r; + + if (alias) { + r = sd_netlink_message_append_string(message, IFLA_IFALIAS, alias); + if (r < 0) + return r; + } + + if (mac) { + r = sd_netlink_message_append_ether_addr(message, IFLA_ADDRESS, mac); + if (r < 0) + return r; + } + + if (mtu > 0) { + r = sd_netlink_message_append_u32(message, IFLA_MTU, mtu); + if (r < 0) + return r; + } + + r = sd_netlink_call(*rtnl, message, 0, NULL); + if (r < 0) + return r; + + return 0; +} + +int rtnl_message_new_synthetic_error(int error, uint32_t serial, sd_netlink_message **ret) { + struct nlmsgerr *err; + int r; + + assert(error <= 0); + + r = message_new(NULL, ret, NLMSG_ERROR); + if (r < 0) + return r; + + (*ret)->hdr->nlmsg_seq = serial; + + err = NLMSG_DATA((*ret)->hdr); + + err->error = error; + + return 0; +} + +bool rtnl_message_type_is_neigh(uint16_t type) { + switch (type) { + case RTM_NEWNEIGH: + case RTM_GETNEIGH: + case RTM_DELNEIGH: + return true; + default: + return false; + } +} + +bool rtnl_message_type_is_route(uint16_t type) { + switch (type) { + case RTM_NEWROUTE: + case RTM_GETROUTE: + case RTM_DELROUTE: + return true; + default: + return false; + } +} + +bool rtnl_message_type_is_link(uint16_t type) { + switch (type) { + case RTM_NEWLINK: + case RTM_SETLINK: + case RTM_GETLINK: + case RTM_DELLINK: + return true; + default: + return false; + } +} + +bool rtnl_message_type_is_addr(uint16_t type) { + switch (type) { + case RTM_NEWADDR: + case RTM_GETADDR: + case RTM_DELADDR: + return true; + default: + return false; + } +} + +int rtnl_log_parse_error(int r) { + return log_error_errno(r, "Failed to parse netlink message: %m"); +} + +int rtnl_log_create_error(int r) { + return log_error_errno(r, "Failed to create netlink message: %m"); +} diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/netlink-util.h b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-util.h new file mode 100644 index 0000000000..e8f932549f --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-util.h @@ -0,0 +1,39 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2013 Tom Gundersen + + 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 "util.h" + +int rtnl_message_new_synthetic_error(int error, uint32_t serial, sd_netlink_message **ret); +uint32_t rtnl_message_get_serial(sd_netlink_message *m); +void rtnl_message_seal(sd_netlink_message *m); + +bool rtnl_message_type_is_link(uint16_t type); +bool rtnl_message_type_is_addr(uint16_t type); +bool rtnl_message_type_is_route(uint16_t type); +bool rtnl_message_type_is_neigh(uint16_t type); + +int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name); +int rtnl_set_link_properties(sd_netlink **rtnl, int ifindex, const char *alias, const struct ether_addr *mac, unsigned mtu); + +int rtnl_log_parse_error(int r); +int rtnl_log_create_error(int r); diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/rtnl-message.c b/src/libsystemd/libsystemd-internal/sd-netlink/rtnl-message.c new file mode 100644 index 0000000000..f6482a6157 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/rtnl-message.c @@ -0,0 +1,702 @@ +/*** + This file is part of systemd. + + Copyright 2013 Tom Gundersen + + 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 +#include + +#include + +#include "formats-util.h" +#include "missing.h" +#include "netlink-internal.h" +#include "netlink-types.h" +#include "netlink-util.h" +#include "refcnt.h" +#include "socket-util.h" +#include "util.h" + +int sd_rtnl_message_route_set_dst_prefixlen(sd_netlink_message *m, unsigned char prefixlen) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + if ((rtm->rtm_family == AF_INET && prefixlen > 32) || + (rtm->rtm_family == AF_INET6 && prefixlen > 128)) + return -ERANGE; + + rtm->rtm_dst_len = prefixlen; + + return 0; +} + +int sd_rtnl_message_route_set_src_prefixlen(sd_netlink_message *m, unsigned char prefixlen) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + if ((rtm->rtm_family == AF_INET && prefixlen > 32) || + (rtm->rtm_family == AF_INET6 && prefixlen > 128)) + return -ERANGE; + + rtm->rtm_src_len = prefixlen; + + return 0; +} + +int sd_rtnl_message_route_set_scope(sd_netlink_message *m, unsigned char scope) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + rtm->rtm_scope = scope; + + return 0; +} + +int sd_rtnl_message_route_set_flags(sd_netlink_message *m, unsigned flags) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + rtm->rtm_flags = flags; + + return 0; +} + +int sd_rtnl_message_route_get_flags(sd_netlink_message *m, unsigned *flags) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(flags, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *flags = rtm->rtm_flags; + + return 0; +} + +int sd_rtnl_message_route_set_table(sd_netlink_message *m, unsigned char table) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + rtm->rtm_table = table; + + return 0; +} + +int sd_rtnl_message_route_get_family(sd_netlink_message *m, int *family) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(family, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *family = rtm->rtm_family; + + return 0; +} + +int sd_rtnl_message_route_set_family(sd_netlink_message *m, int family) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + rtm->rtm_family = family; + + return 0; +} + +int sd_rtnl_message_route_get_protocol(sd_netlink_message *m, unsigned char *protocol) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(protocol, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *protocol = rtm->rtm_protocol; + + return 0; +} + +int sd_rtnl_message_route_get_scope(sd_netlink_message *m, unsigned char *scope) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(scope, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *scope = rtm->rtm_scope; + + return 0; +} + +int sd_rtnl_message_route_get_tos(sd_netlink_message *m, unsigned char *tos) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(tos, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *tos = rtm->rtm_tos; + + return 0; +} + +int sd_rtnl_message_route_get_table(sd_netlink_message *m, unsigned char *table) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(table, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *table = rtm->rtm_table; + + return 0; +} + +int sd_rtnl_message_route_get_dst_prefixlen(sd_netlink_message *m, unsigned char *dst_len) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(dst_len, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *dst_len = rtm->rtm_dst_len; + + return 0; +} + +int sd_rtnl_message_route_get_src_prefixlen(sd_netlink_message *m, unsigned char *src_len) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(src_len, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *src_len = rtm->rtm_src_len; + + return 0; +} + +int sd_rtnl_message_new_route(sd_netlink *rtnl, sd_netlink_message **ret, + uint16_t nlmsg_type, int rtm_family, + unsigned char rtm_protocol) { + struct rtmsg *rtm; + int r; + + assert_return(rtnl_message_type_is_route(nlmsg_type), -EINVAL); + assert_return((nlmsg_type == RTM_GETROUTE && rtm_family == AF_UNSPEC) || + rtm_family == AF_INET || rtm_family == AF_INET6, -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (nlmsg_type == RTM_NEWROUTE) + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_APPEND; + + rtm = NLMSG_DATA((*ret)->hdr); + + rtm->rtm_family = rtm_family; + rtm->rtm_scope = RT_SCOPE_UNIVERSE; + rtm->rtm_type = RTN_UNICAST; + rtm->rtm_table = RT_TABLE_MAIN; + rtm->rtm_protocol = rtm_protocol; + + return 0; +} + +int sd_rtnl_message_neigh_set_flags(sd_netlink_message *m, uint8_t flags) { + struct ndmsg *ndm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); + + ndm = NLMSG_DATA(m->hdr); + ndm->ndm_flags |= flags; + + return 0; +} + +int sd_rtnl_message_neigh_set_state(sd_netlink_message *m, uint16_t state) { + struct ndmsg *ndm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); + + ndm = NLMSG_DATA(m->hdr); + ndm->ndm_state |= state; + + return 0; +} + +int sd_rtnl_message_neigh_get_flags(sd_netlink_message *m, uint8_t *flags) { + struct ndmsg *ndm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); + + ndm = NLMSG_DATA(m->hdr); + *flags = ndm->ndm_flags; + + return 0; +} + +int sd_rtnl_message_neigh_get_state(sd_netlink_message *m, uint16_t *state) { + struct ndmsg *ndm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); + + ndm = NLMSG_DATA(m->hdr); + *state = ndm->ndm_state; + + return 0; +} + +int sd_rtnl_message_neigh_get_family(sd_netlink_message *m, int *family) { + struct ndmsg *ndm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); + assert_return(family, -EINVAL); + + ndm = NLMSG_DATA(m->hdr); + + *family = ndm->ndm_family; + + return 0; +} + +int sd_rtnl_message_neigh_get_ifindex(sd_netlink_message *m, int *index) { + struct ndmsg *ndm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); + assert_return(index, -EINVAL); + + ndm = NLMSG_DATA(m->hdr); + + *index = ndm->ndm_ifindex; + + return 0; +} + +int sd_rtnl_message_new_neigh(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nlmsg_type, int index, int ndm_family) { + struct ndmsg *ndm; + int r; + + assert_return(rtnl_message_type_is_neigh(nlmsg_type), -EINVAL); + assert_return(ndm_family == AF_INET || + ndm_family == AF_INET6 || + ndm_family == PF_BRIDGE, -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (nlmsg_type == RTM_NEWNEIGH) + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_APPEND; + + ndm = NLMSG_DATA((*ret)->hdr); + + ndm->ndm_family = ndm_family; + ndm->ndm_ifindex = index; + + return 0; +} + +int sd_rtnl_message_link_set_flags(sd_netlink_message *m, unsigned flags, unsigned change) { + struct ifinfomsg *ifi; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); + assert_return(change, -EINVAL); + + ifi = NLMSG_DATA(m->hdr); + + ifi->ifi_flags = flags; + ifi->ifi_change = change; + + return 0; +} + +int sd_rtnl_message_link_set_type(sd_netlink_message *m, unsigned type) { + struct ifinfomsg *ifi; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); + + ifi = NLMSG_DATA(m->hdr); + + ifi->ifi_type = type; + + return 0; +} + +int sd_rtnl_message_link_set_family(sd_netlink_message *m, unsigned family) { + struct ifinfomsg *ifi; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); + + ifi = NLMSG_DATA(m->hdr); + + ifi->ifi_family = family; + + return 0; +} + +int sd_rtnl_message_new_link(sd_netlink *rtnl, sd_netlink_message **ret, + uint16_t nlmsg_type, int index) { + struct ifinfomsg *ifi; + int r; + + assert_return(rtnl_message_type_is_link(nlmsg_type), -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (nlmsg_type == RTM_NEWLINK) + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; + + ifi = NLMSG_DATA((*ret)->hdr); + + ifi->ifi_family = AF_UNSPEC; + ifi->ifi_index = index; + + return 0; +} + +int sd_rtnl_message_addr_set_prefixlen(sd_netlink_message *m, unsigned char prefixlen) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + if ((ifa->ifa_family == AF_INET && prefixlen > 32) || + (ifa->ifa_family == AF_INET6 && prefixlen > 128)) + return -ERANGE; + + ifa->ifa_prefixlen = prefixlen; + + return 0; +} + +int sd_rtnl_message_addr_set_flags(sd_netlink_message *m, unsigned char flags) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + ifa->ifa_flags = flags; + + return 0; +} + +int sd_rtnl_message_addr_set_scope(sd_netlink_message *m, unsigned char scope) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + ifa->ifa_scope = scope; + + return 0; +} + +int sd_rtnl_message_addr_get_family(sd_netlink_message *m, int *family) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + assert_return(family, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *family = ifa->ifa_family; + + return 0; +} + +int sd_rtnl_message_addr_get_prefixlen(sd_netlink_message *m, unsigned char *prefixlen) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + assert_return(prefixlen, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *prefixlen = ifa->ifa_prefixlen; + + return 0; +} + +int sd_rtnl_message_addr_get_scope(sd_netlink_message *m, unsigned char *scope) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + assert_return(scope, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *scope = ifa->ifa_scope; + + return 0; +} + +int sd_rtnl_message_addr_get_flags(sd_netlink_message *m, unsigned char *flags) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + assert_return(flags, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *flags = ifa->ifa_flags; + + return 0; +} + +int sd_rtnl_message_addr_get_ifindex(sd_netlink_message *m, int *ifindex) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + assert_return(ifindex, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *ifindex = ifa->ifa_index; + + return 0; +} + +int sd_rtnl_message_new_addr(sd_netlink *rtnl, sd_netlink_message **ret, + uint16_t nlmsg_type, int index, + int family) { + struct ifaddrmsg *ifa; + int r; + + assert_return(rtnl_message_type_is_addr(nlmsg_type), -EINVAL); + assert_return((nlmsg_type == RTM_GETADDR && index == 0) || + index > 0, -EINVAL); + assert_return((nlmsg_type == RTM_GETADDR && family == AF_UNSPEC) || + family == AF_INET || family == AF_INET6, -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (nlmsg_type == RTM_GETADDR) + (*ret)->hdr->nlmsg_flags |= NLM_F_DUMP; + + ifa = NLMSG_DATA((*ret)->hdr); + + ifa->ifa_index = index; + ifa->ifa_family = family; + if (family == AF_INET) + ifa->ifa_prefixlen = 32; + else if (family == AF_INET6) + ifa->ifa_prefixlen = 128; + + return 0; +} + +int sd_rtnl_message_new_addr_update(sd_netlink *rtnl, sd_netlink_message **ret, + int index, int family) { + int r; + + r = sd_rtnl_message_new_addr(rtnl, ret, RTM_NEWADDR, index, family); + if (r < 0) + return r; + + (*ret)->hdr->nlmsg_flags |= NLM_F_REPLACE; + + return 0; +} + +int sd_rtnl_message_link_get_ifindex(sd_netlink_message *m, int *ifindex) { + struct ifinfomsg *ifi; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); + assert_return(ifindex, -EINVAL); + + ifi = NLMSG_DATA(m->hdr); + + *ifindex = ifi->ifi_index; + + return 0; +} + +int sd_rtnl_message_link_get_flags(sd_netlink_message *m, unsigned *flags) { + struct ifinfomsg *ifi; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); + assert_return(flags, -EINVAL); + + ifi = NLMSG_DATA(m->hdr); + + *flags = ifi->ifi_flags; + + return 0; +} + +int sd_rtnl_message_link_get_type(sd_netlink_message *m, unsigned short *type) { + struct ifinfomsg *ifi; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); + assert_return(type, -EINVAL); + + ifi = NLMSG_DATA(m->hdr); + + *type = ifi->ifi_type; + + return 0; +} + +int sd_rtnl_message_get_family(sd_netlink_message *m, int *family) { + assert_return(m, -EINVAL); + assert_return(family, -EINVAL); + + assert(m->hdr); + + if (rtnl_message_type_is_link(m->hdr->nlmsg_type)) { + struct ifinfomsg *ifi; + + ifi = NLMSG_DATA(m->hdr); + + *family = ifi->ifi_family; + + return 0; + } else if (rtnl_message_type_is_route(m->hdr->nlmsg_type)) { + struct rtmsg *rtm; + + rtm = NLMSG_DATA(m->hdr); + + *family = rtm->rtm_family; + + return 0; + } else if (rtnl_message_type_is_neigh(m->hdr->nlmsg_type)) { + struct ndmsg *ndm; + + ndm = NLMSG_DATA(m->hdr); + + *family = ndm->ndm_family; + + return 0; + } else if (rtnl_message_type_is_addr(m->hdr->nlmsg_type)) { + struct ifaddrmsg *ifa; + + ifa = NLMSG_DATA(m->hdr); + + *family = ifa->ifa_family; + + return 0; + } + + return -EOPNOTSUPP; +} diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/sd-netlink.c b/src/libsystemd/libsystemd-internal/sd-netlink/sd-netlink.c new file mode 100644 index 0000000000..3c7488463c --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/sd-netlink.c @@ -0,0 +1,954 @@ +/*** + This file is part of systemd. + + Copyright 2013 Tom Gundersen + + 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 + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "macro.h" +#include "missing.h" +#include "netlink-internal.h" +#include "netlink-util.h" +#include "socket-util.h" +#include "util.h" + +static int sd_netlink_new(sd_netlink **ret) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + + assert_return(ret, -EINVAL); + + rtnl = new0(sd_netlink, 1); + if (!rtnl) + return -ENOMEM; + + rtnl->n_ref = REFCNT_INIT; + rtnl->fd = -1; + rtnl->sockaddr.nl.nl_family = AF_NETLINK; + rtnl->original_pid = getpid(); + + LIST_HEAD_INIT(rtnl->match_callbacks); + + /* We guarantee that the read buffer has at least space for + * a message header */ + if (!greedy_realloc((void**)&rtnl->rbuffer, &rtnl->rbuffer_allocated, + sizeof(struct nlmsghdr), sizeof(uint8_t))) + return -ENOMEM; + + /* Change notification responses have sequence 0, so we must + * start our request sequence numbers at 1, or we may confuse our + * responses with notifications from the kernel */ + rtnl->serial = 1; + + *ret = rtnl; + rtnl = NULL; + + return 0; +} + +int sd_netlink_new_from_netlink(sd_netlink **ret, int fd) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + socklen_t addrlen; + int r; + + assert_return(ret, -EINVAL); + + r = sd_netlink_new(&rtnl); + if (r < 0) + return r; + + addrlen = sizeof(rtnl->sockaddr); + + r = getsockname(fd, &rtnl->sockaddr.sa, &addrlen); + if (r < 0) + return -errno; + + if (rtnl->sockaddr.nl.nl_family != AF_NETLINK) + return -EINVAL; + + rtnl->fd = fd; + + *ret = rtnl; + rtnl = NULL; + + return 0; +} + +static bool rtnl_pid_changed(sd_netlink *rtnl) { + assert(rtnl); + + /* We don't support people creating an rtnl connection and + * keeping it around over a fork(). Let's complain. */ + + return rtnl->original_pid != getpid(); +} + +int sd_netlink_open_fd(sd_netlink **ret, int fd) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + int r; + + assert_return(ret, -EINVAL); + assert_return(fd >= 0, -EBADF); + + r = sd_netlink_new(&rtnl); + if (r < 0) + return r; + + rtnl->fd = fd; + + r = socket_bind(rtnl); + if (r < 0) { + rtnl->fd = -1; /* on failure, the caller remains owner of the fd, hence don't close it here */ + return r; + } + + *ret = rtnl; + rtnl = NULL; + + return 0; +} + +int sd_netlink_open(sd_netlink **ret) { + _cleanup_close_ int fd = -1; + int r; + + fd = socket_open(NETLINK_ROUTE); + if (fd < 0) + return fd; + + r = sd_netlink_open_fd(ret, fd); + if (r < 0) + return r; + + fd = -1; + + return 0; +} + +int sd_netlink_inc_rcvbuf(const sd_netlink *const rtnl, const int size) { + return fd_inc_rcvbuf(rtnl->fd, size); +} + +sd_netlink *sd_netlink_ref(sd_netlink *rtnl) { + assert_return(rtnl, NULL); + assert_return(!rtnl_pid_changed(rtnl), NULL); + + if (rtnl) + assert_se(REFCNT_INC(rtnl->n_ref) >= 2); + + return rtnl; +} + +sd_netlink *sd_netlink_unref(sd_netlink *rtnl) { + if (!rtnl) + return NULL; + + assert_return(!rtnl_pid_changed(rtnl), NULL); + + if (REFCNT_DEC(rtnl->n_ref) == 0) { + struct match_callback *f; + unsigned i; + + for (i = 0; i < rtnl->rqueue_size; i++) + sd_netlink_message_unref(rtnl->rqueue[i]); + free(rtnl->rqueue); + + for (i = 0; i < rtnl->rqueue_partial_size; i++) + sd_netlink_message_unref(rtnl->rqueue_partial[i]); + free(rtnl->rqueue_partial); + + free(rtnl->rbuffer); + + hashmap_free_free(rtnl->reply_callbacks); + prioq_free(rtnl->reply_callbacks_prioq); + + sd_event_source_unref(rtnl->io_event_source); + sd_event_source_unref(rtnl->time_event_source); + sd_event_unref(rtnl->event); + + while ((f = rtnl->match_callbacks)) { + sd_netlink_remove_match(rtnl, f->type, f->callback, f->userdata); + } + + hashmap_free(rtnl->broadcast_group_refs); + + safe_close(rtnl->fd); + free(rtnl); + } + + return NULL; +} + +static void rtnl_seal_message(sd_netlink *rtnl, sd_netlink_message *m) { + assert(rtnl); + assert(!rtnl_pid_changed(rtnl)); + assert(m); + assert(m->hdr); + + /* don't use seq == 0, as that is used for broadcasts, so we + would get confused by replies to such messages */ + m->hdr->nlmsg_seq = rtnl->serial++ ? : rtnl->serial++; + + rtnl_message_seal(m); + + return; +} + +int sd_netlink_send(sd_netlink *nl, + sd_netlink_message *message, + uint32_t *serial) { + int r; + + assert_return(nl, -EINVAL); + assert_return(!rtnl_pid_changed(nl), -ECHILD); + assert_return(message, -EINVAL); + assert_return(!message->sealed, -EPERM); + + rtnl_seal_message(nl, message); + + r = socket_write_message(nl, message); + if (r < 0) + return r; + + if (serial) + *serial = rtnl_message_get_serial(message); + + return 1; +} + +int rtnl_rqueue_make_room(sd_netlink *rtnl) { + assert(rtnl); + + if (rtnl->rqueue_size >= RTNL_RQUEUE_MAX) { + log_debug("rtnl: exhausted the read queue size (%d)", RTNL_RQUEUE_MAX); + return -ENOBUFS; + } + + if (!GREEDY_REALLOC(rtnl->rqueue, rtnl->rqueue_allocated, rtnl->rqueue_size + 1)) + return -ENOMEM; + + return 0; +} + +int rtnl_rqueue_partial_make_room(sd_netlink *rtnl) { + assert(rtnl); + + if (rtnl->rqueue_partial_size >= RTNL_RQUEUE_MAX) { + log_debug("rtnl: exhausted the partial read queue size (%d)", RTNL_RQUEUE_MAX); + return -ENOBUFS; + } + + if (!GREEDY_REALLOC(rtnl->rqueue_partial, rtnl->rqueue_partial_allocated, + rtnl->rqueue_partial_size + 1)) + return -ENOMEM; + + return 0; +} + +static int dispatch_rqueue(sd_netlink *rtnl, sd_netlink_message **message) { + int r; + + assert(rtnl); + assert(message); + + if (rtnl->rqueue_size <= 0) { + /* Try to read a new message */ + r = socket_read_message(rtnl); + if (r <= 0) + return r; + } + + /* Dispatch a queued message */ + *message = rtnl->rqueue[0]; + rtnl->rqueue_size--; + memmove(rtnl->rqueue, rtnl->rqueue + 1, sizeof(sd_netlink_message*) * rtnl->rqueue_size); + + return 1; +} + +static int process_timeout(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + struct reply_callback *c; + usec_t n; + int r; + + assert(rtnl); + + c = prioq_peek(rtnl->reply_callbacks_prioq); + if (!c) + return 0; + + n = now(CLOCK_MONOTONIC); + if (c->timeout > n) + return 0; + + r = rtnl_message_new_synthetic_error(-ETIMEDOUT, c->serial, &m); + if (r < 0) + return r; + + assert_se(prioq_pop(rtnl->reply_callbacks_prioq) == c); + hashmap_remove(rtnl->reply_callbacks, &c->serial); + + r = c->callback(rtnl, m, c->userdata); + if (r < 0) + log_debug_errno(r, "sd-netlink: timedout callback failed: %m"); + + free(c); + + return 1; +} + +static int process_reply(sd_netlink *rtnl, sd_netlink_message *m) { + _cleanup_free_ struct reply_callback *c = NULL; + uint64_t serial; + uint16_t type; + int r; + + assert(rtnl); + assert(m); + + serial = rtnl_message_get_serial(m); + c = hashmap_remove(rtnl->reply_callbacks, &serial); + if (!c) + return 0; + + if (c->timeout != 0) + prioq_remove(rtnl->reply_callbacks_prioq, c, &c->prioq_idx); + + r = sd_netlink_message_get_type(m, &type); + if (r < 0) + return 0; + + if (type == NLMSG_DONE) + m = NULL; + + r = c->callback(rtnl, m, c->userdata); + if (r < 0) + log_debug_errno(r, "sd-netlink: callback failed: %m"); + + return 1; +} + +static int process_match(sd_netlink *rtnl, sd_netlink_message *m) { + struct match_callback *c; + uint16_t type; + int r; + + assert(rtnl); + assert(m); + + r = sd_netlink_message_get_type(m, &type); + if (r < 0) + return r; + + LIST_FOREACH(match_callbacks, c, rtnl->match_callbacks) { + if (type == c->type) { + r = c->callback(rtnl, m, c->userdata); + if (r != 0) { + if (r < 0) + log_debug_errno(r, "sd-netlink: match callback failed: %m"); + + break; + } + } + } + + return 1; +} + +static int process_running(sd_netlink *rtnl, sd_netlink_message **ret) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(rtnl); + + r = process_timeout(rtnl); + if (r != 0) + goto null_message; + + r = dispatch_rqueue(rtnl, &m); + if (r < 0) + return r; + if (!m) + goto null_message; + + if (sd_netlink_message_is_broadcast(m)) { + r = process_match(rtnl, m); + if (r != 0) + goto null_message; + } else { + r = process_reply(rtnl, m); + if (r != 0) + goto null_message; + } + + if (ret) { + *ret = m; + m = NULL; + + return 1; + } + + return 1; + +null_message: + if (r >= 0 && ret) + *ret = NULL; + + return r; +} + +int sd_netlink_process(sd_netlink *rtnl, sd_netlink_message **ret) { + NETLINK_DONT_DESTROY(rtnl); + int r; + + assert_return(rtnl, -EINVAL); + assert_return(!rtnl_pid_changed(rtnl), -ECHILD); + assert_return(!rtnl->processing, -EBUSY); + + rtnl->processing = true; + r = process_running(rtnl, ret); + rtnl->processing = false; + + return r; +} + +static usec_t calc_elapse(uint64_t usec) { + if (usec == (uint64_t) -1) + return 0; + + if (usec == 0) + usec = RTNL_DEFAULT_TIMEOUT; + + return now(CLOCK_MONOTONIC) + usec; +} + +static int rtnl_poll(sd_netlink *rtnl, bool need_more, uint64_t timeout_usec) { + struct pollfd p[1] = {}; + struct timespec ts; + usec_t m = USEC_INFINITY; + int r, e; + + assert(rtnl); + + e = sd_netlink_get_events(rtnl); + if (e < 0) + return e; + + if (need_more) + /* Caller wants more data, and doesn't care about + * what's been read or any other timeouts. */ + e |= POLLIN; + else { + usec_t until; + /* Caller wants to process if there is something to + * process, but doesn't care otherwise */ + + r = sd_netlink_get_timeout(rtnl, &until); + if (r < 0) + return r; + if (r > 0) { + usec_t nw; + nw = now(CLOCK_MONOTONIC); + m = until > nw ? until - nw : 0; + } + } + + if (timeout_usec != (uint64_t) -1 && (m == (uint64_t) -1 || timeout_usec < m)) + m = timeout_usec; + + p[0].fd = rtnl->fd; + p[0].events = e; + + r = ppoll(p, 1, m == (uint64_t) -1 ? NULL : timespec_store(&ts, m), NULL); + if (r < 0) + return -errno; + + return r > 0 ? 1 : 0; +} + +int sd_netlink_wait(sd_netlink *nl, uint64_t timeout_usec) { + assert_return(nl, -EINVAL); + assert_return(!rtnl_pid_changed(nl), -ECHILD); + + if (nl->rqueue_size > 0) + return 0; + + return rtnl_poll(nl, false, timeout_usec); +} + +static int timeout_compare(const void *a, const void *b) { + const struct reply_callback *x = a, *y = b; + + if (x->timeout != 0 && y->timeout == 0) + return -1; + + if (x->timeout == 0 && y->timeout != 0) + return 1; + + if (x->timeout < y->timeout) + return -1; + + if (x->timeout > y->timeout) + return 1; + + return 0; +} + +int sd_netlink_call_async(sd_netlink *nl, + sd_netlink_message *m, + sd_netlink_message_handler_t callback, + void *userdata, + uint64_t usec, + uint32_t *serial) { + struct reply_callback *c; + uint32_t s; + int r, k; + + assert_return(nl, -EINVAL); + assert_return(m, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!rtnl_pid_changed(nl), -ECHILD); + + r = hashmap_ensure_allocated(&nl->reply_callbacks, &uint64_hash_ops); + if (r < 0) + return r; + + if (usec != (uint64_t) -1) { + r = prioq_ensure_allocated(&nl->reply_callbacks_prioq, timeout_compare); + if (r < 0) + return r; + } + + c = new0(struct reply_callback, 1); + if (!c) + return -ENOMEM; + + c->callback = callback; + c->userdata = userdata; + c->timeout = calc_elapse(usec); + + k = sd_netlink_send(nl, m, &s); + if (k < 0) { + free(c); + return k; + } + + c->serial = s; + + r = hashmap_put(nl->reply_callbacks, &c->serial, c); + if (r < 0) { + free(c); + return r; + } + + if (c->timeout != 0) { + r = prioq_put(nl->reply_callbacks_prioq, c, &c->prioq_idx); + if (r > 0) { + c->timeout = 0; + sd_netlink_call_async_cancel(nl, c->serial); + return r; + } + } + + if (serial) + *serial = s; + + return k; +} + +int sd_netlink_call_async_cancel(sd_netlink *nl, uint32_t serial) { + struct reply_callback *c; + uint64_t s = serial; + + assert_return(nl, -EINVAL); + assert_return(serial != 0, -EINVAL); + assert_return(!rtnl_pid_changed(nl), -ECHILD); + + c = hashmap_remove(nl->reply_callbacks, &s); + if (!c) + return 0; + + if (c->timeout != 0) + prioq_remove(nl->reply_callbacks_prioq, c, &c->prioq_idx); + + free(c); + return 1; +} + +int sd_netlink_call(sd_netlink *rtnl, + sd_netlink_message *message, + uint64_t usec, + sd_netlink_message **ret) { + usec_t timeout; + uint32_t serial; + int r; + + assert_return(rtnl, -EINVAL); + assert_return(!rtnl_pid_changed(rtnl), -ECHILD); + assert_return(message, -EINVAL); + + r = sd_netlink_send(rtnl, message, &serial); + if (r < 0) + return r; + + timeout = calc_elapse(usec); + + for (;;) { + usec_t left; + unsigned i; + + for (i = 0; i < rtnl->rqueue_size; i++) { + uint32_t received_serial; + + received_serial = rtnl_message_get_serial(rtnl->rqueue[i]); + + if (received_serial == serial) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *incoming = NULL; + uint16_t type; + + incoming = rtnl->rqueue[i]; + + /* found a match, remove from rqueue and return it */ + memmove(rtnl->rqueue + i,rtnl->rqueue + i + 1, + sizeof(sd_netlink_message*) * (rtnl->rqueue_size - i - 1)); + rtnl->rqueue_size--; + + r = sd_netlink_message_get_errno(incoming); + if (r < 0) + return r; + + r = sd_netlink_message_get_type(incoming, &type); + if (r < 0) + return r; + + if (type == NLMSG_DONE) { + *ret = NULL; + return 0; + } + + if (ret) { + *ret = incoming; + incoming = NULL; + } + + return 1; + } + } + + r = socket_read_message(rtnl); + if (r < 0) + return r; + if (r > 0) + /* received message, so try to process straight away */ + continue; + + if (timeout > 0) { + usec_t n; + + n = now(CLOCK_MONOTONIC); + if (n >= timeout) + return -ETIMEDOUT; + + left = timeout - n; + } else + left = (uint64_t) -1; + + r = rtnl_poll(rtnl, true, left); + if (r < 0) + return r; + else if (r == 0) + return -ETIMEDOUT; + } +} + +int sd_netlink_get_events(sd_netlink *rtnl) { + assert_return(rtnl, -EINVAL); + assert_return(!rtnl_pid_changed(rtnl), -ECHILD); + + if (rtnl->rqueue_size == 0) + return POLLIN; + else + return 0; +} + +int sd_netlink_get_timeout(sd_netlink *rtnl, uint64_t *timeout_usec) { + struct reply_callback *c; + + assert_return(rtnl, -EINVAL); + assert_return(timeout_usec, -EINVAL); + assert_return(!rtnl_pid_changed(rtnl), -ECHILD); + + if (rtnl->rqueue_size > 0) { + *timeout_usec = 0; + return 1; + } + + c = prioq_peek(rtnl->reply_callbacks_prioq); + if (!c) { + *timeout_usec = (uint64_t) -1; + return 0; + } + + *timeout_usec = c->timeout; + + return 1; +} + +static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_netlink *rtnl = userdata; + int r; + + assert(rtnl); + + r = sd_netlink_process(rtnl, NULL); + if (r < 0) + return r; + + return 1; +} + +static int time_callback(sd_event_source *s, uint64_t usec, void *userdata) { + sd_netlink *rtnl = userdata; + int r; + + assert(rtnl); + + r = sd_netlink_process(rtnl, NULL); + if (r < 0) + return r; + + return 1; +} + +static int prepare_callback(sd_event_source *s, void *userdata) { + sd_netlink *rtnl = userdata; + int r, e; + usec_t until; + + assert(s); + assert(rtnl); + + e = sd_netlink_get_events(rtnl); + if (e < 0) + return e; + + r = sd_event_source_set_io_events(rtnl->io_event_source, e); + if (r < 0) + return r; + + r = sd_netlink_get_timeout(rtnl, &until); + if (r < 0) + return r; + if (r > 0) { + int j; + + j = sd_event_source_set_time(rtnl->time_event_source, until); + if (j < 0) + return j; + } + + r = sd_event_source_set_enabled(rtnl->time_event_source, r > 0); + if (r < 0) + return r; + + return 1; +} + +int sd_netlink_attach_event(sd_netlink *rtnl, sd_event *event, int64_t priority) { + int r; + + assert_return(rtnl, -EINVAL); + assert_return(!rtnl->event, -EBUSY); + + assert(!rtnl->io_event_source); + assert(!rtnl->time_event_source); + + if (event) + rtnl->event = sd_event_ref(event); + else { + r = sd_event_default(&rtnl->event); + if (r < 0) + return r; + } + + r = sd_event_add_io(rtnl->event, &rtnl->io_event_source, rtnl->fd, 0, io_callback, rtnl); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(rtnl->io_event_source, priority); + if (r < 0) + goto fail; + + r = sd_event_source_set_description(rtnl->io_event_source, "rtnl-receive-message"); + if (r < 0) + goto fail; + + r = sd_event_source_set_prepare(rtnl->io_event_source, prepare_callback); + if (r < 0) + goto fail; + + r = sd_event_add_time(rtnl->event, &rtnl->time_event_source, CLOCK_MONOTONIC, 0, 0, time_callback, rtnl); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(rtnl->time_event_source, priority); + if (r < 0) + goto fail; + + r = sd_event_source_set_description(rtnl->time_event_source, "rtnl-timer"); + if (r < 0) + goto fail; + + return 0; + +fail: + sd_netlink_detach_event(rtnl); + return r; +} + +int sd_netlink_detach_event(sd_netlink *rtnl) { + assert_return(rtnl, -EINVAL); + assert_return(rtnl->event, -ENXIO); + + rtnl->io_event_source = sd_event_source_unref(rtnl->io_event_source); + + rtnl->time_event_source = sd_event_source_unref(rtnl->time_event_source); + + rtnl->event = sd_event_unref(rtnl->event); + + return 0; +} + +int sd_netlink_add_match(sd_netlink *rtnl, + uint16_t type, + sd_netlink_message_handler_t callback, + void *userdata) { + _cleanup_free_ struct match_callback *c = NULL; + int r; + + assert_return(rtnl, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!rtnl_pid_changed(rtnl), -ECHILD); + + c = new0(struct match_callback, 1); + if (!c) + return -ENOMEM; + + c->callback = callback; + c->type = type; + c->userdata = userdata; + + switch (type) { + case RTM_NEWLINK: + case RTM_DELLINK: + r = socket_broadcast_group_ref(rtnl, RTNLGRP_LINK); + if (r < 0) + return r; + + break; + case RTM_NEWADDR: + case RTM_DELADDR: + r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV4_IFADDR); + if (r < 0) + return r; + + r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV6_IFADDR); + if (r < 0) + return r; + + break; + case RTM_NEWROUTE: + case RTM_DELROUTE: + r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV4_ROUTE); + if (r < 0) + return r; + + r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV6_ROUTE); + if (r < 0) + return r; + break; + default: + return -EOPNOTSUPP; + } + + LIST_PREPEND(match_callbacks, rtnl->match_callbacks, c); + + c = NULL; + + return 0; +} + +int sd_netlink_remove_match(sd_netlink *rtnl, + uint16_t type, + sd_netlink_message_handler_t callback, + void *userdata) { + struct match_callback *c; + int r; + + assert_return(rtnl, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!rtnl_pid_changed(rtnl), -ECHILD); + + LIST_FOREACH(match_callbacks, c, rtnl->match_callbacks) + if (c->callback == callback && c->type == type && c->userdata == userdata) { + LIST_REMOVE(match_callbacks, rtnl->match_callbacks, c); + free(c); + + switch (type) { + case RTM_NEWLINK: + case RTM_DELLINK: + r = socket_broadcast_group_unref(rtnl, RTNLGRP_LINK); + if (r < 0) + return r; + + break; + case RTM_NEWADDR: + case RTM_DELADDR: + r = socket_broadcast_group_unref(rtnl, RTNLGRP_IPV4_IFADDR); + if (r < 0) + return r; + + r = socket_broadcast_group_unref(rtnl, RTNLGRP_IPV6_IFADDR); + if (r < 0) + return r; + + break; + case RTM_NEWROUTE: + case RTM_DELROUTE: + r = socket_broadcast_group_unref(rtnl, RTNLGRP_IPV4_ROUTE); + if (r < 0) + return r; + + r = socket_broadcast_group_unref(rtnl, RTNLGRP_IPV6_ROUTE); + if (r < 0) + return r; + break; + default: + return -EOPNOTSUPP; + } + + return 1; + } + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/test-netlink.c b/src/libsystemd/libsystemd-internal/sd-netlink/test-netlink.c new file mode 100644 index 0000000000..aadd0f06a8 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/test-netlink.c @@ -0,0 +1,440 @@ +/*** + This file is part of systemd. + + Copyright 2013 Tom Gundersen + + 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 + +#include + +#include "ether-addr-util.h" +#include "macro.h" +#include "missing.h" +#include "netlink-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "util.h" + +static void test_message_link_bridge(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + uint32_t cost; + + assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_NEWLINK, 1) >= 0); + assert_se(sd_rtnl_message_link_set_family(message, PF_BRIDGE) >= 0); + assert_se(sd_netlink_message_open_container(message, IFLA_PROTINFO) >= 0); + assert_se(sd_netlink_message_append_u32(message, IFLA_BRPORT_COST, 10) >= 0); + assert_se(sd_netlink_message_close_container(message) >= 0); + + assert_se(sd_netlink_message_rewind(message) >= 0); + + assert_se(sd_netlink_message_enter_container(message, IFLA_PROTINFO) >= 0); + assert_se(sd_netlink_message_read_u32(message, IFLA_BRPORT_COST, &cost) >= 0); + assert_se(cost == 10); + assert_se(sd_netlink_message_exit_container(message) >= 0); +} + +static void test_link_configure(sd_netlink *rtnl, int ifindex) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + const char *mac = "98:fe:94:3f:c6:18", *name = "test"; + char buffer[ETHER_ADDR_TO_STRING_MAX]; + unsigned int mtu = 1450, mtu_out; + const char *name_out; + struct ether_addr mac_out; + + /* we'd really like to test NEWLINK, but let's not mess with the running kernel */ + assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_GETLINK, ifindex) >= 0); + assert_se(sd_netlink_message_append_string(message, IFLA_IFNAME, name) >= 0); + assert_se(sd_netlink_message_append_ether_addr(message, IFLA_ADDRESS, ether_aton(mac)) >= 0); + assert_se(sd_netlink_message_append_u32(message, IFLA_MTU, mtu) >= 0); + + assert_se(sd_netlink_call(rtnl, message, 0, NULL) == 1); + assert_se(sd_netlink_message_rewind(message) >= 0); + + assert_se(sd_netlink_message_read_string(message, IFLA_IFNAME, &name_out) >= 0); + assert_se(streq(name, name_out)); + + assert_se(sd_netlink_message_read_ether_addr(message, IFLA_ADDRESS, &mac_out) >= 0); + assert_se(streq(mac, ether_addr_to_string(&mac_out, buffer))); + + assert_se(sd_netlink_message_read_u32(message, IFLA_MTU, &mtu_out) >= 0); + assert_se(mtu == mtu_out); +} + +static void test_link_get(sd_netlink *rtnl, int ifindex) { + sd_netlink_message *m; + sd_netlink_message *r; + unsigned int mtu = 1500; + const char *str_data; + uint8_t u8_data; + uint32_t u32_data; + struct ether_addr eth_data; + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); + assert_se(m); + + /* u8 test cases */ + assert_se(sd_netlink_message_append_u8(m, IFLA_CARRIER, 0) >= 0); + assert_se(sd_netlink_message_append_u8(m, IFLA_OPERSTATE, 0) >= 0); + assert_se(sd_netlink_message_append_u8(m, IFLA_LINKMODE, 0) >= 0); + + /* u32 test cases */ + assert_se(sd_netlink_message_append_u32(m, IFLA_MTU, mtu) >= 0); + assert_se(sd_netlink_message_append_u32(m, IFLA_GROUP, 0) >= 0); + assert_se(sd_netlink_message_append_u32(m, IFLA_TXQLEN, 0) >= 0); + assert_se(sd_netlink_message_append_u32(m, IFLA_NUM_TX_QUEUES, 0) >= 0); + assert_se(sd_netlink_message_append_u32(m, IFLA_NUM_RX_QUEUES, 0) >= 0); + + assert_se(sd_netlink_call(rtnl, m, -1, &r) == 1); + + assert_se(sd_netlink_message_read_string(r, IFLA_IFNAME, &str_data) == 0); + + assert_se(sd_netlink_message_read_u8(r, IFLA_CARRIER, &u8_data) == 0); + assert_se(sd_netlink_message_read_u8(r, IFLA_OPERSTATE, &u8_data) == 0); + assert_se(sd_netlink_message_read_u8(r, IFLA_LINKMODE, &u8_data) == 0); + + assert_se(sd_netlink_message_read_u32(r, IFLA_MTU, &u32_data) == 0); + assert_se(sd_netlink_message_read_u32(r, IFLA_GROUP, &u32_data) == 0); + assert_se(sd_netlink_message_read_u32(r, IFLA_TXQLEN, &u32_data) == 0); + assert_se(sd_netlink_message_read_u32(r, IFLA_NUM_TX_QUEUES, &u32_data) == 0); + assert_se(sd_netlink_message_read_u32(r, IFLA_NUM_RX_QUEUES, &u32_data) == 0); + + assert_se(sd_netlink_message_read_ether_addr(r, IFLA_ADDRESS, ð_data) == 0); + + assert_se((m = sd_netlink_message_unref(m)) == NULL); + assert_se((r = sd_netlink_message_unref(r)) == NULL); +} + + +static void test_address_get(sd_netlink *rtnl, int ifindex) { + sd_netlink_message *m; + sd_netlink_message *r; + struct in_addr in_data; + struct ifa_cacheinfo cache; + const char *label; + + assert_se(sd_rtnl_message_new_addr(rtnl, &m, RTM_GETADDR, ifindex, AF_INET) >= 0); + assert_se(m); + + assert_se(sd_netlink_call(rtnl, m, -1, &r) == 1); + + assert_se(sd_netlink_message_read_in_addr(r, IFA_LOCAL, &in_data) == 0); + assert_se(sd_netlink_message_read_in_addr(r, IFA_ADDRESS, &in_data) == 0); + assert_se(sd_netlink_message_read_string(r, IFA_LABEL, &label) == 0); + assert_se(sd_netlink_message_read_cache_info(r, IFA_CACHEINFO, &cache) == 0); + + assert_se((m = sd_netlink_message_unref(m)) == NULL); + assert_se((r = sd_netlink_message_unref(r)) == NULL); + +} + +static void test_route(void) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req; + struct in_addr addr, addr_data; + uint32_t index = 2, u32_data; + int r; + + r = sd_rtnl_message_new_route(NULL, &req, RTM_NEWROUTE, AF_INET, RTPROT_STATIC); + if (r < 0) { + log_error_errno(r, "Could not create RTM_NEWROUTE message: %m"); + return; + } + + addr.s_addr = htonl(INADDR_LOOPBACK); + + r = sd_netlink_message_append_in_addr(req, RTA_GATEWAY, &addr); + if (r < 0) { + log_error_errno(r, "Could not append RTA_GATEWAY attribute: %m"); + return; + } + + r = sd_netlink_message_append_u32(req, RTA_OIF, index); + if (r < 0) { + log_error_errno(r, "Could not append RTA_OIF attribute: %m"); + return; + } + + assert_se(sd_netlink_message_rewind(req) >= 0); + + assert_se(sd_netlink_message_read_in_addr(req, RTA_GATEWAY, &addr_data) >= 0); + assert_se(addr_data.s_addr == addr.s_addr); + + assert_se(sd_netlink_message_read_u32(req, RTA_OIF, &u32_data) >= 0); + assert_se(u32_data == index); + + assert_se((req = sd_netlink_message_unref(req)) == NULL); +} + +static void test_multiple(void) { + sd_netlink *rtnl1, *rtnl2; + + assert_se(sd_netlink_open(&rtnl1) >= 0); + assert_se(sd_netlink_open(&rtnl2) >= 0); + + rtnl1 = sd_netlink_unref(rtnl1); + rtnl2 = sd_netlink_unref(rtnl2); +} + +static int link_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { + char *ifname = userdata; + const char *data; + + assert_se(rtnl); + assert_se(m); + + log_info("got link info about %s", ifname); + free(ifname); + + assert_se(sd_netlink_message_read_string(m, IFLA_IFNAME, &data) >= 0); + assert_se(streq(data, "lo")); + + return 1; +} + +static void test_event_loop(int ifindex) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + char *ifname; + + ifname = strdup("lo2"); + assert_se(ifname); + + assert_se(sd_netlink_open(&rtnl) >= 0); + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); + + assert_se(sd_netlink_call_async(rtnl, m, link_handler, ifname, 0, NULL) >= 0); + + assert_se(sd_event_default(&event) >= 0); + + assert_se(sd_netlink_attach_event(rtnl, event, 0) >= 0); + + assert_se(sd_event_run(event, 0) >= 0); + + assert_se(sd_netlink_detach_event(rtnl) >= 0); + + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); +} + +static int pipe_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { + int *counter = userdata; + int r; + + (*counter)--; + + r = sd_netlink_message_get_errno(m); + + log_info_errno(r, "%d left in pipe. got reply: %m", *counter); + + assert_se(r >= 0); + + return 1; +} + +static void test_async(int ifindex) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *r = NULL; + uint32_t serial; + char *ifname; + + ifname = strdup("lo"); + assert_se(ifname); + + assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); + + assert_se(sd_netlink_call_async(rtnl, m, link_handler, ifname, 0, &serial) >= 0); + + assert_se(sd_netlink_wait(rtnl, 0) >= 0); + assert_se(sd_netlink_process(rtnl, &r) >= 0); + + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); +} + +static void test_pipe(int ifindex) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m1 = NULL, *m2 = NULL; + int counter = 0; + + assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_rtnl_message_new_link(rtnl, &m1, RTM_GETLINK, ifindex) >= 0); + assert_se(sd_rtnl_message_new_link(rtnl, &m2, RTM_GETLINK, ifindex) >= 0); + + counter++; + assert_se(sd_netlink_call_async(rtnl, m1, pipe_handler, &counter, 0, NULL) >= 0); + + counter++; + assert_se(sd_netlink_call_async(rtnl, m2, pipe_handler, &counter, 0, NULL) >= 0); + + while (counter > 0) { + assert_se(sd_netlink_wait(rtnl, 0) >= 0); + assert_se(sd_netlink_process(rtnl, NULL) >= 0); + } + + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); +} + +static void test_container(void) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + uint16_t u16_data; + uint32_t u32_data; + const char *string_data; + + assert_se(sd_rtnl_message_new_link(NULL, &m, RTM_NEWLINK, 0) >= 0); + + assert_se(sd_netlink_message_open_container(m, IFLA_LINKINFO) >= 0); + assert_se(sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "vlan") >= 0); + assert_se(sd_netlink_message_append_u16(m, IFLA_VLAN_ID, 100) >= 0); + assert_se(sd_netlink_message_close_container(m) >= 0); + assert_se(sd_netlink_message_append_string(m, IFLA_INFO_KIND, "vlan") >= 0); + assert_se(sd_netlink_message_close_container(m) >= 0); + assert_se(sd_netlink_message_close_container(m) == -EINVAL); + + assert_se(sd_netlink_message_rewind(m) >= 0); + + assert_se(sd_netlink_message_enter_container(m, IFLA_LINKINFO) >= 0); + assert_se(sd_netlink_message_read_string(m, IFLA_INFO_KIND, &string_data) >= 0); + assert_se(streq("vlan", string_data)); + + assert_se(sd_netlink_message_enter_container(m, IFLA_INFO_DATA) >= 0); + assert_se(sd_netlink_message_read_u16(m, IFLA_VLAN_ID, &u16_data) >= 0); + assert_se(sd_netlink_message_exit_container(m) >= 0); + + assert_se(sd_netlink_message_read_string(m, IFLA_INFO_KIND, &string_data) >= 0); + assert_se(streq("vlan", string_data)); + assert_se(sd_netlink_message_exit_container(m) >= 0); + + assert_se(sd_netlink_message_read_u32(m, IFLA_LINKINFO, &u32_data) < 0); + + assert_se(sd_netlink_message_exit_container(m) == -EINVAL); +} + +static void test_match(void) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + + assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_netlink_add_match(rtnl, RTM_NEWLINK, link_handler, NULL) >= 0); + assert_se(sd_netlink_add_match(rtnl, RTM_NEWLINK, link_handler, NULL) >= 0); + + assert_se(sd_netlink_remove_match(rtnl, RTM_NEWLINK, link_handler, NULL) == 1); + assert_se(sd_netlink_remove_match(rtnl, RTM_NEWLINK, link_handler, NULL) == 1); + assert_se(sd_netlink_remove_match(rtnl, RTM_NEWLINK, link_handler, NULL) == 0); + + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); +} + +static void test_get_addresses(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + sd_netlink_message *m; + + assert_se(sd_rtnl_message_new_addr(rtnl, &req, RTM_GETADDR, 0, AF_UNSPEC) >= 0); + + assert_se(sd_netlink_call(rtnl, req, 0, &reply) >= 0); + + for (m = reply; m; m = sd_netlink_message_next(m)) { + uint16_t type; + unsigned char scope, flags; + int family, ifindex; + + assert_se(sd_netlink_message_get_type(m, &type) >= 0); + assert_se(type == RTM_NEWADDR); + + assert_se(sd_rtnl_message_addr_get_ifindex(m, &ifindex) >= 0); + assert_se(sd_rtnl_message_addr_get_family(m, &family) >= 0); + assert_se(sd_rtnl_message_addr_get_scope(m, &scope) >= 0); + assert_se(sd_rtnl_message_addr_get_flags(m, &flags) >= 0); + + assert_se(ifindex > 0); + assert_se(family == AF_INET || family == AF_INET6); + + log_info("got IPv%u address on ifindex %i", family == AF_INET ? 4: 6, ifindex); + } +} + +static void test_message(void) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + + assert_se(rtnl_message_new_synthetic_error(-ETIMEDOUT, 1, &m) >= 0); + assert_se(sd_netlink_message_get_errno(m) == -ETIMEDOUT); +} + +int main(void) { + sd_netlink *rtnl; + sd_netlink_message *m; + sd_netlink_message *r; + const char *string_data; + int if_loopback; + uint16_t type; + + test_message(); + + test_match(); + + test_multiple(); + + test_route(); + + test_container(); + + assert_se(sd_netlink_open(&rtnl) >= 0); + assert_se(rtnl); + + if_loopback = (int) if_nametoindex("lo"); + assert_se(if_loopback > 0); + + test_async(if_loopback); + + test_pipe(if_loopback); + + test_event_loop(if_loopback); + + test_link_configure(rtnl, if_loopback); + + test_get_addresses(rtnl); + + test_message_link_bridge(rtnl); + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, if_loopback) >= 0); + assert_se(m); + + assert_se(sd_netlink_message_get_type(m, &type) >= 0); + assert_se(type == RTM_GETLINK); + + assert_se(sd_netlink_message_read_string(m, IFLA_IFNAME, &string_data) == -EPERM); + + assert_se(sd_netlink_call(rtnl, m, 0, &r) == 1); + assert_se(sd_netlink_message_get_type(r, &type) >= 0); + assert_se(type == RTM_NEWLINK); + + assert_se((r = sd_netlink_message_unref(r)) == NULL); + + assert_se(sd_netlink_call(rtnl, m, -1, &r) == -EPERM); + assert_se((m = sd_netlink_message_unref(m)) == NULL); + assert_se((r = sd_netlink_message_unref(r)) == NULL); + + test_link_get(rtnl, if_loopback); + test_address_get(rtnl, if_loopback); + + assert_se((m = sd_netlink_message_unref(m)) == NULL); + assert_se((r = sd_netlink_message_unref(r)) == NULL); + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); + + return EXIT_SUCCESS; +} diff --git a/src/libsystemd/libsystemd-internal/sd-network/network-util.c b/src/libsystemd/libsystemd-internal/sd-network/network-util.c new file mode 100644 index 0000000000..a0d9b5f1a4 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-network/network-util.c @@ -0,0 +1,37 @@ +/*** + 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 "alloc-util.h" +#include "fd-util.h" +#include "network-util.h" +#include "strv.h" + +bool network_is_online(void) { + _cleanup_free_ char *state = NULL; + int r; + + r = sd_network_get_operational_state(&state); + if (r < 0) /* if we don't know anything, we consider the system online */ + return true; + + if (STR_IN_SET(state, "routable", "degraded")) + return true; + + return false; +} diff --git a/src/libsystemd/libsystemd-internal/sd-network/network-util.h b/src/libsystemd/libsystemd-internal/sd-network/network-util.h new file mode 100644 index 0000000000..8c4dbc68b1 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-network/network-util.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Thomas Hindø Paabøl Andersen + + 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 + +bool network_is_online(void); diff --git a/src/libsystemd/libsystemd-internal/sd-network/sd-network.c b/src/libsystemd/libsystemd-internal/sd-network/sd-network.c new file mode 100644 index 0000000000..70083213f9 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-network/sd-network.c @@ -0,0 +1,400 @@ +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + Copyright 2014 Tom Gundersen + + 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 +#include +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "macro.h" +#include "parse-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +_public_ int sd_network_get_operational_state(char **state) { + _cleanup_free_ char *s = NULL; + int r; + + assert_return(state, -EINVAL); + + r = parse_env_file("/run/systemd/netif/state", NEWLINE, "OPER_STATE", &s, NULL); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + if (isempty(s)) + return -ENODATA; + + *state = s; + s = NULL; + + return 0; +} + +static int network_get_strv(const char *key, char ***ret) { + _cleanup_strv_free_ char **a = NULL; + _cleanup_free_ char *s = NULL; + int r; + + assert_return(ret, -EINVAL); + + r = parse_env_file("/run/systemd/netif/state", NEWLINE, key, &s, NULL); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + if (isempty(s)) { + *ret = NULL; + return 0; + } + + a = strv_split(s, " "); + if (!a) + return -ENOMEM; + + strv_uniq(a); + r = strv_length(a); + + *ret = a; + a = NULL; + + return r; +} + +_public_ int sd_network_get_dns(char ***ret) { + return network_get_strv("DNS", ret); +} + +_public_ int sd_network_get_ntp(char ***ret) { + return network_get_strv("NTP", ret); +} + +_public_ int sd_network_get_search_domains(char ***ret) { + return network_get_strv("DOMAINS", ret); +} + +_public_ int sd_network_get_route_domains(char ***ret) { + return network_get_strv("ROUTE_DOMAINS", ret); +} + +static int network_link_get_string(int ifindex, const char *field, char **ret) { + char path[strlen("/run/systemd/netif/links/") + DECIMAL_STR_MAX(ifindex) + 1]; + _cleanup_free_ char *s = NULL; + int r; + + assert_return(ifindex > 0, -EINVAL); + assert_return(ret, -EINVAL); + + xsprintf(path, "/run/systemd/netif/links/%i", ifindex); + + r = parse_env_file(path, NEWLINE, field, &s, NULL); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + if (isempty(s)) + return -ENODATA; + + *ret = s; + s = NULL; + + return 0; +} + +static int network_link_get_strv(int ifindex, const char *key, char ***ret) { + char path[strlen("/run/systemd/netif/links/") + DECIMAL_STR_MAX(ifindex) + 1]; + _cleanup_strv_free_ char **a = NULL; + _cleanup_free_ char *s = NULL; + int r; + + assert_return(ifindex > 0, -EINVAL); + assert_return(ret, -EINVAL); + + xsprintf(path, "/run/systemd/netif/links/%i", ifindex); + r = parse_env_file(path, NEWLINE, key, &s, NULL); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + if (isempty(s)) { + *ret = NULL; + return 0; + } + + a = strv_split(s, " "); + if (!a) + return -ENOMEM; + + strv_uniq(a); + r = strv_length(a); + + *ret = a; + a = NULL; + + return r; +} + +_public_ int sd_network_link_get_setup_state(int ifindex, char **state) { + return network_link_get_string(ifindex, "ADMIN_STATE", state); +} + +_public_ int sd_network_link_get_network_file(int ifindex, char **filename) { + return network_link_get_string(ifindex, "NETWORK_FILE", filename); +} + +_public_ int sd_network_link_get_operational_state(int ifindex, char **state) { + return network_link_get_string(ifindex, "OPER_STATE", state); +} + +_public_ int sd_network_link_get_llmnr(int ifindex, char **llmnr) { + return network_link_get_string(ifindex, "LLMNR", llmnr); +} + +_public_ int sd_network_link_get_mdns(int ifindex, char **mdns) { + return network_link_get_string(ifindex, "MDNS", mdns); +} + +_public_ int sd_network_link_get_dnssec(int ifindex, char **dnssec) { + return network_link_get_string(ifindex, "DNSSEC", dnssec); +} + +_public_ int sd_network_link_get_dnssec_negative_trust_anchors(int ifindex, char ***nta) { + return network_link_get_strv(ifindex, "DNSSEC_NTA", nta); +} + +_public_ int sd_network_link_get_timezone(int ifindex, char **ret) { + return network_link_get_string(ifindex, "TIMEZONE", ret); +} + +_public_ int sd_network_link_get_dns(int ifindex, char ***ret) { + return network_link_get_strv(ifindex, "DNS", ret); +} + +_public_ int sd_network_link_get_ntp(int ifindex, char ***ret) { + return network_link_get_strv(ifindex, "NTP", ret); +} + +_public_ int sd_network_link_get_search_domains(int ifindex, char ***ret) { + return network_link_get_strv(ifindex, "DOMAINS", ret); +} + +_public_ int sd_network_link_get_route_domains(int ifindex, char ***ret) { + return network_link_get_strv(ifindex, "ROUTE_DOMAINS", ret); +} + +static int network_link_get_ifindexes(int ifindex, const char *key, int **ret) { + char path[strlen("/run/systemd/netif/links/") + DECIMAL_STR_MAX(ifindex) + 1]; + _cleanup_free_ int *ifis = NULL; + _cleanup_free_ char *s = NULL; + size_t allocated = 0, c = 0; + const char *x; + int r; + + assert_return(ifindex > 0, -EINVAL); + assert_return(ret, -EINVAL); + + xsprintf(path, "/run/systemd/netif/links/%i", ifindex); + r = parse_env_file(path, NEWLINE, key, &s, NULL); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + if (isempty(s)) { + *ret = NULL; + return 0; + } + + x = s; + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&x, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + r = parse_ifindex(word, &ifindex); + if (r < 0) + return r; + + if (!GREEDY_REALLOC(ifis, allocated, c + 1)) + return -ENOMEM; + + ifis[c++] = ifindex; + } + + if (!GREEDY_REALLOC(ifis, allocated, c + 1)) + return -ENOMEM; + ifis[c] = 0; /* Let's add a 0 ifindex to the end, to be nice*/ + + *ret = ifis; + ifis = NULL; + + return c; +} + +_public_ int sd_network_link_get_carrier_bound_to(int ifindex, int **ret) { + return network_link_get_ifindexes(ifindex, "CARRIER_BOUND_TO", ret); +} + +_public_ int sd_network_link_get_carrier_bound_by(int ifindex, int **ret) { + return network_link_get_ifindexes(ifindex, "CARRIER_BOUND_BY", ret); +} + +static inline int MONITOR_TO_FD(sd_network_monitor *m) { + return (int) (unsigned long) m - 1; +} + +static inline sd_network_monitor* FD_TO_MONITOR(int fd) { + return (sd_network_monitor*) (unsigned long) (fd + 1); +} + +static int monitor_add_inotify_watch(int fd) { + int k; + + k = inotify_add_watch(fd, "/run/systemd/netif/links/", IN_MOVED_TO|IN_DELETE); + if (k >= 0) + return 0; + else if (errno != ENOENT) + return -errno; + + k = inotify_add_watch(fd, "/run/systemd/netif/", IN_CREATE|IN_ISDIR); + if (k >= 0) + return 0; + else if (errno != ENOENT) + return -errno; + + k = inotify_add_watch(fd, "/run/systemd/", IN_CREATE|IN_ISDIR); + if (k < 0) + return -errno; + + return 0; +} + +_public_ int sd_network_monitor_new(sd_network_monitor **m, const char *category) { + _cleanup_close_ int fd = -1; + int k; + bool good = false; + + assert_return(m, -EINVAL); + + fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); + if (fd < 0) + return -errno; + + if (!category || streq(category, "links")) { + k = monitor_add_inotify_watch(fd); + if (k < 0) + return k; + + good = true; + } + + if (!good) + return -EINVAL; + + *m = FD_TO_MONITOR(fd); + fd = -1; + + return 0; +} + +_public_ sd_network_monitor* sd_network_monitor_unref(sd_network_monitor *m) { + int fd; + + if (m) { + fd = MONITOR_TO_FD(m); + close_nointr(fd); + } + + return NULL; +} + +_public_ int sd_network_monitor_flush(sd_network_monitor *m) { + union inotify_event_buffer buffer; + struct inotify_event *e; + ssize_t l; + int fd, k; + + assert_return(m, -EINVAL); + + fd = MONITOR_TO_FD(m); + + l = read(fd, &buffer, sizeof(buffer)); + if (l < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return -errno; + } + + FOREACH_INOTIFY_EVENT(e, buffer, l) { + if (e->mask & IN_ISDIR) { + k = monitor_add_inotify_watch(fd); + if (k < 0) + return k; + + k = inotify_rm_watch(fd, e->wd); + if (k < 0) + return -errno; + } + } + + return 0; +} + +_public_ int sd_network_monitor_get_fd(sd_network_monitor *m) { + + assert_return(m, -EINVAL); + + return MONITOR_TO_FD(m); +} + +_public_ int sd_network_monitor_get_events(sd_network_monitor *m) { + + assert_return(m, -EINVAL); + + /* For now we will only return POLLIN here, since we don't + * need anything else ever for inotify. However, let's have + * this API to keep our options open should we later on need + * it. */ + return POLLIN; +} + +_public_ int sd_network_monitor_get_timeout(sd_network_monitor *m, uint64_t *timeout_usec) { + + assert_return(m, -EINVAL); + assert_return(timeout_usec, -EINVAL); + + /* For now we will only return (uint64_t) -1, since we don't + * need any timeout. However, let's have this API to keep our + * options open should we later on need it. */ + *timeout_usec = (uint64_t) -1; + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-path/sd-path.c b/src/libsystemd/libsystemd-internal/sd-path/sd-path.c new file mode 100644 index 0000000000..6d9f3e2a61 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-path/sd-path.c @@ -0,0 +1,638 @@ +/*** + 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 "alloc-util.h" +#include "architecture.h" +#include "fd-util.h" +#include "fileio.h" +#include "missing.h" +#include "path-util.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" +#include "util.h" + +static int from_environment(const char *envname, const char *fallback, const char **ret) { + assert(ret); + + if (envname) { + const char *e; + + e = secure_getenv(envname); + if (e && path_is_absolute(e)) { + *ret = e; + return 0; + } + } + + if (fallback) { + *ret = fallback; + return 0; + } + + return -ENXIO; +} + +static int from_home_dir(const char *envname, const char *suffix, char **buffer, const char **ret) { + _cleanup_free_ char *h = NULL; + char *cc = NULL; + int r; + + assert(suffix); + assert(buffer); + assert(ret); + + if (envname) { + const char *e = NULL; + + e = secure_getenv(envname); + if (e && path_is_absolute(e)) { + *ret = e; + return 0; + } + } + + r = get_home_dir(&h); + if (r < 0) + return r; + + if (endswith(h, "/")) + cc = strappend(h, suffix); + else + cc = strjoin(h, "/", suffix, NULL); + if (!cc) + return -ENOMEM; + + *buffer = cc; + *ret = cc; + return 0; +} + +static int from_user_dir(const char *field, char **buffer, const char **ret) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *b = NULL; + _cleanup_free_ const char *fn = NULL; + const char *c = NULL; + char line[LINE_MAX]; + size_t n; + int r; + + assert(field); + assert(buffer); + assert(ret); + + r = from_home_dir("XDG_CONFIG_HOME", ".config", &b, &c); + if (r < 0) + return r; + + fn = strappend(c, "/user-dirs.dirs"); + if (!fn) + return -ENOMEM; + + f = fopen(fn, "re"); + if (!f) { + if (errno == ENOENT) + goto fallback; + + return -errno; + } + + /* This is an awful parse, but it follows closely what + * xdg-user-dirs does upstream */ + + n = strlen(field); + FOREACH_LINE(line, f, return -errno) { + char *l, *p, *e; + + l = strstrip(line); + + if (!strneq(l, field, n)) + continue; + + p = l + n; + p += strspn(p, WHITESPACE); + + if (*p != '=') + continue; + p++; + + p += strspn(p, WHITESPACE); + + if (*p != '"') + continue; + p++; + + e = strrchr(p, '"'); + if (!e) + continue; + *e = 0; + + /* Three syntaxes permitted: relative to $HOME, $HOME itself, and absolute path */ + if (startswith(p, "$HOME/")) { + _cleanup_free_ char *h = NULL; + char *cc; + + r = get_home_dir(&h); + if (r < 0) + return r; + + cc = strappend(h, p+5); + if (!cc) + return -ENOMEM; + + *buffer = cc; + *ret = cc; + return 0; + } else if (streq(p, "$HOME")) { + + r = get_home_dir(buffer); + if (r < 0) + return r; + + *ret = *buffer; + return 0; + } else if (path_is_absolute(p)) { + char *copy; + + copy = strdup(p); + if (!copy) + return -ENOMEM; + + *buffer = copy; + *ret = copy; + return 0; + } + } + +fallback: + /* The desktop directory defaults to $HOME/Desktop, the others to $HOME */ + if (streq(field, "XDG_DESKTOP_DIR")) { + _cleanup_free_ char *h = NULL; + char *cc; + + r = get_home_dir(&h); + if (r < 0) + return r; + + cc = strappend(h, "/Desktop"); + if (!cc) + return -ENOMEM; + + *buffer = cc; + *ret = cc; + } else { + + r = get_home_dir(buffer); + if (r < 0) + return r; + + *ret = *buffer; + } + + return 0; +} + +static int get_path(uint64_t type, char **buffer, const char **ret) { + int r; + + assert(buffer); + assert(ret); + + switch (type) { + + case SD_PATH_TEMPORARY: + return from_environment("TMPDIR", "/tmp", ret); + + case SD_PATH_TEMPORARY_LARGE: + return from_environment("TMPDIR", "/var/tmp", ret); + + case SD_PATH_SYSTEM_BINARIES: + *ret = "/usr/bin"; + return 0; + + case SD_PATH_SYSTEM_INCLUDE: + *ret = "/usr/include"; + return 0; + + case SD_PATH_SYSTEM_LIBRARY_PRIVATE: + *ret = "/usr/lib"; + return 0; + + case SD_PATH_SYSTEM_LIBRARY_ARCH: + *ret = LIBDIR; + return 0; + + case SD_PATH_SYSTEM_SHARED: + *ret = "/usr/share"; + return 0; + + case SD_PATH_SYSTEM_CONFIGURATION_FACTORY: + *ret = "/usr/share/factory/etc"; + return 0; + + case SD_PATH_SYSTEM_STATE_FACTORY: + *ret = "/usr/share/factory/var"; + return 0; + + case SD_PATH_SYSTEM_CONFIGURATION: + *ret = "/etc"; + return 0; + + case SD_PATH_SYSTEM_RUNTIME: + *ret = "/run"; + return 0; + + case SD_PATH_SYSTEM_RUNTIME_LOGS: + *ret = "/run/log"; + return 0; + + case SD_PATH_SYSTEM_STATE_PRIVATE: + *ret = "/var/lib"; + return 0; + + case SD_PATH_SYSTEM_STATE_LOGS: + *ret = "/var/log"; + return 0; + + case SD_PATH_SYSTEM_STATE_CACHE: + *ret = "/var/cache"; + return 0; + + case SD_PATH_SYSTEM_STATE_SPOOL: + *ret = "/var/spool"; + return 0; + + case SD_PATH_USER_BINARIES: + return from_home_dir(NULL, ".local/bin", buffer, ret); + + case SD_PATH_USER_LIBRARY_PRIVATE: + return from_home_dir(NULL, ".local/lib", buffer, ret); + + case SD_PATH_USER_LIBRARY_ARCH: + return from_home_dir(NULL, ".local/lib/" LIB_ARCH_TUPLE, buffer, ret); + + case SD_PATH_USER_SHARED: + return from_home_dir("XDG_DATA_HOME", ".local/share", buffer, ret); + + case SD_PATH_USER_CONFIGURATION: + return from_home_dir("XDG_CONFIG_HOME", ".config", buffer, ret); + + case SD_PATH_USER_RUNTIME: + return from_environment("XDG_RUNTIME_DIR", NULL, ret); + + case SD_PATH_USER_STATE_CACHE: + return from_home_dir("XDG_CACHE_HOME", ".cache", buffer, ret); + + case SD_PATH_USER: + r = get_home_dir(buffer); + if (r < 0) + return r; + + *ret = *buffer; + return 0; + + case SD_PATH_USER_DOCUMENTS: + return from_user_dir("XDG_DOCUMENTS_DIR", buffer, ret); + + case SD_PATH_USER_MUSIC: + return from_user_dir("XDG_MUSIC_DIR", buffer, ret); + + case SD_PATH_USER_PICTURES: + return from_user_dir("XDG_PICTURES_DIR", buffer, ret); + + case SD_PATH_USER_VIDEOS: + return from_user_dir("XDG_VIDEOS_DIR", buffer, ret); + + case SD_PATH_USER_DOWNLOAD: + return from_user_dir("XDG_DOWNLOAD_DIR", buffer, ret); + + case SD_PATH_USER_PUBLIC: + return from_user_dir("XDG_PUBLICSHARE_DIR", buffer, ret); + + case SD_PATH_USER_TEMPLATES: + return from_user_dir("XDG_TEMPLATES_DIR", buffer, ret); + + case SD_PATH_USER_DESKTOP: + return from_user_dir("XDG_DESKTOP_DIR", buffer, ret); + } + + return -EOPNOTSUPP; +} + +_public_ int sd_path_home(uint64_t type, const char *suffix, char **path) { + char *buffer = NULL, *cc; + const char *ret; + int r; + + assert_return(path, -EINVAL); + + if (IN_SET(type, + SD_PATH_SEARCH_BINARIES, + SD_PATH_SEARCH_LIBRARY_PRIVATE, + SD_PATH_SEARCH_LIBRARY_ARCH, + SD_PATH_SEARCH_SHARED, + SD_PATH_SEARCH_CONFIGURATION_FACTORY, + SD_PATH_SEARCH_STATE_FACTORY, + SD_PATH_SEARCH_CONFIGURATION)) { + + _cleanup_strv_free_ char **l = NULL; + + r = sd_path_search(type, suffix, &l); + if (r < 0) + return r; + + buffer = strv_join(l, ":"); + if (!buffer) + return -ENOMEM; + + *path = buffer; + return 0; + } + + r = get_path(type, &buffer, &ret); + if (r < 0) + return r; + + if (!suffix) { + if (!buffer) { + buffer = strdup(ret); + if (!buffer) + return -ENOMEM; + } + + *path = buffer; + return 0; + } + + suffix += strspn(suffix, "/"); + + if (endswith(ret, "/")) + cc = strappend(ret, suffix); + else + cc = strjoin(ret, "/", suffix, NULL); + + free(buffer); + + if (!cc) + return -ENOMEM; + + *path = cc; + return 0; +} + +static int search_from_environment( + char ***list, + const char *env_home, + const char *home_suffix, + const char *env_search, + bool env_search_sufficient, + const char *first, ...) { + + const char *e; + char *h = NULL; + char **l = NULL; + int r; + + assert(list); + + if (env_search) { + e = secure_getenv(env_search); + if (e) { + l = strv_split(e, ":"); + if (!l) + return -ENOMEM; + + if (env_search_sufficient) { + *list = l; + return 0; + } + } + } + + if (!l && first) { + va_list ap; + + va_start(ap, first); + l = strv_new_ap(first, ap); + va_end(ap); + + if (!l) + return -ENOMEM; + } + + if (env_home) { + e = secure_getenv(env_home); + if (e && path_is_absolute(e)) { + h = strdup(e); + if (!h) { + strv_free(l); + return -ENOMEM; + } + } + } + + if (!h && home_suffix) { + e = secure_getenv("HOME"); + if (e && path_is_absolute(e)) { + if (endswith(e, "/")) + h = strappend(e, home_suffix); + else + h = strjoin(e, "/", home_suffix, NULL); + + if (!h) { + strv_free(l); + return -ENOMEM; + } + } + } + + if (h) { + r = strv_consume_prepend(&l, h); + if (r < 0) { + strv_free(l); + return -ENOMEM; + } + } + + *list = l; + return 0; +} + +static int get_search(uint64_t type, char ***list) { + + assert(list); + + switch(type) { + + case SD_PATH_SEARCH_BINARIES: + return search_from_environment(list, + NULL, + ".local/bin", + "PATH", + true, + "/usr/local/sbin", + "/usr/local/bin", + "/usr/sbin", + "/usr/bin", +#ifdef HAVE_SPLIT_USR + "/sbin", + "/bin", +#endif + NULL); + + case SD_PATH_SEARCH_LIBRARY_PRIVATE: + return search_from_environment(list, + NULL, + ".local/lib", + NULL, + false, + "/usr/local/lib", + "/usr/lib", +#ifdef HAVE_SPLIT_USR + "/lib", +#endif + NULL); + + case SD_PATH_SEARCH_LIBRARY_ARCH: + return search_from_environment(list, + NULL, + ".local/lib/" LIB_ARCH_TUPLE, + "LD_LIBRARY_PATH", + true, + LIBDIR, +#ifdef HAVE_SPLIT_USR + ROOTLIBDIR, +#endif + NULL); + + case SD_PATH_SEARCH_SHARED: + return search_from_environment(list, + "XDG_DATA_HOME", + ".local/share", + "XDG_DATA_DIRS", + false, + "/usr/local/share", + "/usr/share", + NULL); + + case SD_PATH_SEARCH_CONFIGURATION_FACTORY: + return search_from_environment(list, + NULL, + NULL, + NULL, + false, + "/usr/local/share/factory/etc", + "/usr/share/factory/etc", + NULL); + + case SD_PATH_SEARCH_STATE_FACTORY: + return search_from_environment(list, + NULL, + NULL, + NULL, + false, + "/usr/local/share/factory/var", + "/usr/share/factory/var", + NULL); + + case SD_PATH_SEARCH_CONFIGURATION: + return search_from_environment(list, + "XDG_CONFIG_HOME", + ".config", + "XDG_CONFIG_DIRS", + false, + "/etc", + NULL); + } + + return -EOPNOTSUPP; +} + +_public_ int sd_path_search(uint64_t type, const char *suffix, char ***paths) { + char **l, **i, **j, **n; + int r; + + assert_return(paths, -EINVAL); + + if (!IN_SET(type, + SD_PATH_SEARCH_BINARIES, + SD_PATH_SEARCH_LIBRARY_PRIVATE, + SD_PATH_SEARCH_LIBRARY_ARCH, + SD_PATH_SEARCH_SHARED, + SD_PATH_SEARCH_CONFIGURATION_FACTORY, + SD_PATH_SEARCH_STATE_FACTORY, + SD_PATH_SEARCH_CONFIGURATION)) { + + char *p; + + r = sd_path_home(type, suffix, &p); + if (r < 0) + return r; + + l = new(char*, 2); + if (!l) { + free(p); + return -ENOMEM; + } + + l[0] = p; + l[1] = NULL; + + *paths = l; + return 0; + } + + r = get_search(type, &l); + if (r < 0) + return r; + + if (!suffix) { + *paths = l; + return 0; + } + + n = new(char*, strv_length(l)+1); + if (!n) { + strv_free(l); + return -ENOMEM; + } + + j = n; + STRV_FOREACH(i, l) { + + if (endswith(*i, "/")) + *j = strappend(*i, suffix); + else + *j = strjoin(*i, "/", suffix, NULL); + + if (!*j) { + strv_free(l); + strv_free(n); + return -ENOMEM; + } + + j++; + } + + *j = NULL; + *paths = n; + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-resolve/sd-resolve.c b/src/libsystemd/libsystemd-internal/sd-resolve/sd-resolve.c new file mode 100644 index 0000000000..6eacf2b69a --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-resolve/sd-resolve.c @@ -0,0 +1,1245 @@ +/*** + This file is part of systemd. + + Copyright 2005-2008 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "list.h" +#include "missing.h" +#include "socket-util.h" +#include "util.h" + +#define WORKERS_MIN 1U +#define WORKERS_MAX 16U +#define QUERIES_MAX 256U +#define BUFSIZE 10240U + +typedef enum { + REQUEST_ADDRINFO, + RESPONSE_ADDRINFO, + REQUEST_NAMEINFO, + RESPONSE_NAMEINFO, + REQUEST_TERMINATE, + RESPONSE_DIED +} QueryType; + +enum { + REQUEST_RECV_FD, + REQUEST_SEND_FD, + RESPONSE_RECV_FD, + RESPONSE_SEND_FD, + _FD_MAX +}; + +struct sd_resolve { + unsigned n_ref; + + bool dead:1; + pid_t original_pid; + + int fds[_FD_MAX]; + + pthread_t workers[WORKERS_MAX]; + unsigned n_valid_workers; + + unsigned current_id; + sd_resolve_query* query_array[QUERIES_MAX]; + unsigned n_queries, n_done, n_outstanding; + + sd_event_source *event_source; + sd_event *event; + + sd_resolve_query *current; + + sd_resolve **default_resolve_ptr; + pid_t tid; + + LIST_HEAD(sd_resolve_query, queries); +}; + +struct sd_resolve_query { + unsigned n_ref; + + sd_resolve *resolve; + + QueryType type:4; + bool done:1; + bool floating:1; + unsigned id; + + int ret; + int _errno; + int _h_errno; + struct addrinfo *addrinfo; + char *serv, *host; + + union { + sd_resolve_getaddrinfo_handler_t getaddrinfo_handler; + sd_resolve_getnameinfo_handler_t getnameinfo_handler; + }; + + void *userdata; + + LIST_FIELDS(sd_resolve_query, queries); +}; + +typedef struct RHeader { + QueryType type; + unsigned id; + size_t length; +} RHeader; + +typedef struct AddrInfoRequest { + struct RHeader header; + bool hints_valid; + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + size_t node_len, service_len; +} AddrInfoRequest; + +typedef struct AddrInfoResponse { + struct RHeader header; + int ret; + int _errno; + int _h_errno; + /* followed by addrinfo_serialization[] */ +} AddrInfoResponse; + +typedef struct AddrInfoSerialization { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + size_t ai_addrlen; + size_t canonname_len; + /* Followed by ai_addr amd ai_canonname with variable lengths */ +} AddrInfoSerialization; + +typedef struct NameInfoRequest { + struct RHeader header; + int flags; + socklen_t sockaddr_len; + bool gethost:1, getserv:1; +} NameInfoRequest; + +typedef struct NameInfoResponse { + struct RHeader header; + size_t hostlen, servlen; + int ret; + int _errno; + int _h_errno; +} NameInfoResponse; + +typedef union Packet { + RHeader rheader; + AddrInfoRequest addrinfo_request; + AddrInfoResponse addrinfo_response; + NameInfoRequest nameinfo_request; + NameInfoResponse nameinfo_response; +} Packet; + +static int getaddrinfo_done(sd_resolve_query* q); +static int getnameinfo_done(sd_resolve_query *q); + +static void resolve_query_disconnect(sd_resolve_query *q); + +#define RESOLVE_DONT_DESTROY(resolve) \ + _cleanup_(sd_resolve_unrefp) _unused_ sd_resolve *_dont_destroy_##resolve = sd_resolve_ref(resolve) + +static int send_died(int out_fd) { + + RHeader rh = { + .type = RESPONSE_DIED, + .length = sizeof(RHeader), + }; + + assert(out_fd >= 0); + + if (send(out_fd, &rh, rh.length, MSG_NOSIGNAL) < 0) + return -errno; + + return 0; +} + +static void *serialize_addrinfo(void *p, const struct addrinfo *ai, size_t *length, size_t maxlength) { + AddrInfoSerialization s; + size_t cnl, l; + + assert(p); + assert(ai); + assert(length); + assert(*length <= maxlength); + + cnl = ai->ai_canonname ? strlen(ai->ai_canonname)+1 : 0; + l = sizeof(AddrInfoSerialization) + ai->ai_addrlen + cnl; + + if (*length + l > maxlength) + return NULL; + + s.ai_flags = ai->ai_flags; + s.ai_family = ai->ai_family; + s.ai_socktype = ai->ai_socktype; + s.ai_protocol = ai->ai_protocol; + s.ai_addrlen = ai->ai_addrlen; + s.canonname_len = cnl; + + memcpy((uint8_t*) p, &s, sizeof(AddrInfoSerialization)); + memcpy((uint8_t*) p + sizeof(AddrInfoSerialization), ai->ai_addr, ai->ai_addrlen); + memcpy_safe((char*) p + sizeof(AddrInfoSerialization) + ai->ai_addrlen, + ai->ai_canonname, cnl); + + *length += l; + return (uint8_t*) p + l; +} + +static int send_addrinfo_reply( + int out_fd, + unsigned id, + int ret, + struct addrinfo *ai, + int _errno, + int _h_errno) { + + AddrInfoResponse resp = { + .header.type = RESPONSE_ADDRINFO, + .header.id = id, + .header.length = sizeof(AddrInfoResponse), + .ret = ret, + ._errno = _errno, + ._h_errno = _h_errno, + }; + + struct msghdr mh = {}; + struct iovec iov[2]; + union { + AddrInfoSerialization ais; + uint8_t space[BUFSIZE]; + } buffer; + + assert(out_fd >= 0); + + if (ret == 0 && ai) { + void *p = &buffer; + struct addrinfo *k; + + for (k = ai; k; k = k->ai_next) { + p = serialize_addrinfo(p, k, &resp.header.length, (uint8_t*) &buffer + BUFSIZE - (uint8_t*) p); + if (!p) { + freeaddrinfo(ai); + return -ENOBUFS; + } + } + } + + if (ai) + freeaddrinfo(ai); + + iov[0] = (struct iovec) { .iov_base = &resp, .iov_len = sizeof(AddrInfoResponse) }; + iov[1] = (struct iovec) { .iov_base = &buffer, .iov_len = resp.header.length - sizeof(AddrInfoResponse) }; + + mh.msg_iov = iov; + mh.msg_iovlen = ELEMENTSOF(iov); + + if (sendmsg(out_fd, &mh, MSG_NOSIGNAL) < 0) + return -errno; + + return 0; +} + +static int send_nameinfo_reply( + int out_fd, + unsigned id, + int ret, + const char *host, + const char *serv, + int _errno, + int _h_errno) { + + NameInfoResponse resp = { + .header.type = RESPONSE_NAMEINFO, + .header.id = id, + .ret = ret, + ._errno = _errno, + ._h_errno = _h_errno, + }; + + struct msghdr mh = {}; + struct iovec iov[3]; + size_t hl, sl; + + assert(out_fd >= 0); + + sl = serv ? strlen(serv)+1 : 0; + hl = host ? strlen(host)+1 : 0; + + resp.header.length = sizeof(NameInfoResponse) + hl + sl; + resp.hostlen = hl; + resp.servlen = sl; + + iov[0] = (struct iovec) { .iov_base = &resp, .iov_len = sizeof(NameInfoResponse) }; + iov[1] = (struct iovec) { .iov_base = (void*) host, .iov_len = hl }; + iov[2] = (struct iovec) { .iov_base = (void*) serv, .iov_len = sl }; + + mh.msg_iov = iov; + mh.msg_iovlen = ELEMENTSOF(iov); + + if (sendmsg(out_fd, &mh, MSG_NOSIGNAL) < 0) + return -errno; + + return 0; +} + +static int handle_request(int out_fd, const Packet *packet, size_t length) { + const RHeader *req; + + assert(out_fd >= 0); + assert(packet); + + req = &packet->rheader; + + assert(length >= sizeof(RHeader)); + assert(length == req->length); + + switch (req->type) { + + case REQUEST_ADDRINFO: { + const AddrInfoRequest *ai_req = &packet->addrinfo_request; + struct addrinfo hints = {}, *result = NULL; + const char *node, *service; + int ret; + + assert(length >= sizeof(AddrInfoRequest)); + assert(length == sizeof(AddrInfoRequest) + ai_req->node_len + ai_req->service_len); + + hints.ai_flags = ai_req->ai_flags; + hints.ai_family = ai_req->ai_family; + hints.ai_socktype = ai_req->ai_socktype; + hints.ai_protocol = ai_req->ai_protocol; + + node = ai_req->node_len ? (const char*) ai_req + sizeof(AddrInfoRequest) : NULL; + service = ai_req->service_len ? (const char*) ai_req + sizeof(AddrInfoRequest) + ai_req->node_len : NULL; + + ret = getaddrinfo( + node, service, + ai_req->hints_valid ? &hints : NULL, + &result); + + /* send_addrinfo_reply() frees result */ + return send_addrinfo_reply(out_fd, req->id, ret, result, errno, h_errno); + } + + case REQUEST_NAMEINFO: { + const NameInfoRequest *ni_req = &packet->nameinfo_request; + char hostbuf[NI_MAXHOST], servbuf[NI_MAXSERV]; + union sockaddr_union sa; + int ret; + + assert(length >= sizeof(NameInfoRequest)); + assert(length == sizeof(NameInfoRequest) + ni_req->sockaddr_len); + assert(sizeof(sa) >= ni_req->sockaddr_len); + + memcpy(&sa, (const uint8_t *) ni_req + sizeof(NameInfoRequest), ni_req->sockaddr_len); + + ret = getnameinfo(&sa.sa, ni_req->sockaddr_len, + ni_req->gethost ? hostbuf : NULL, ni_req->gethost ? sizeof(hostbuf) : 0, + ni_req->getserv ? servbuf : NULL, ni_req->getserv ? sizeof(servbuf) : 0, + ni_req->flags); + + return send_nameinfo_reply(out_fd, req->id, ret, + ret == 0 && ni_req->gethost ? hostbuf : NULL, + ret == 0 && ni_req->getserv ? servbuf : NULL, + errno, h_errno); + } + + case REQUEST_TERMINATE: + /* Quit */ + return -ECONNRESET; + + default: + assert_not_reached("Unknown request"); + } + + return 0; +} + +static void* thread_worker(void *p) { + sd_resolve *resolve = p; + sigset_t fullset; + + /* No signals in this thread please */ + assert_se(sigfillset(&fullset) == 0); + assert_se(pthread_sigmask(SIG_BLOCK, &fullset, NULL) == 0); + + /* Assign a pretty name to this thread */ + (void) prctl(PR_SET_NAME, (unsigned long) "sd-resolve"); + + while (!resolve->dead) { + union { + Packet packet; + uint8_t space[BUFSIZE]; + } buf; + ssize_t length; + + length = recv(resolve->fds[REQUEST_RECV_FD], &buf, sizeof(buf), 0); + if (length < 0) { + if (errno == EINTR) + continue; + + break; + } + if (length == 0) + break; + + if (resolve->dead) + break; + + if (handle_request(resolve->fds[RESPONSE_SEND_FD], &buf.packet, (size_t) length) < 0) + break; + } + + send_died(resolve->fds[RESPONSE_SEND_FD]); + + return NULL; +} + +static int start_threads(sd_resolve *resolve, unsigned extra) { + unsigned n; + int r; + + n = resolve->n_outstanding + extra; + n = CLAMP(n, WORKERS_MIN, WORKERS_MAX); + + while (resolve->n_valid_workers < n) { + + r = pthread_create(&resolve->workers[resolve->n_valid_workers], NULL, thread_worker, resolve); + if (r != 0) + return -r; + + resolve->n_valid_workers++; + } + + return 0; +} + +static bool resolve_pid_changed(sd_resolve *r) { + assert(r); + + /* We don't support people creating a resolver and keeping it + * around after fork(). Let's complain. */ + + return r->original_pid != getpid(); +} + +_public_ int sd_resolve_new(sd_resolve **ret) { + sd_resolve *resolve = NULL; + int i, r; + + assert_return(ret, -EINVAL); + + resolve = new0(sd_resolve, 1); + if (!resolve) + return -ENOMEM; + + resolve->n_ref = 1; + resolve->original_pid = getpid(); + + for (i = 0; i < _FD_MAX; i++) + resolve->fds[i] = -1; + + r = socketpair(PF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, resolve->fds + REQUEST_RECV_FD); + if (r < 0) { + r = -errno; + goto fail; + } + + r = socketpair(PF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, resolve->fds + RESPONSE_RECV_FD); + if (r < 0) { + r = -errno; + goto fail; + } + + fd_inc_sndbuf(resolve->fds[REQUEST_SEND_FD], QUERIES_MAX * BUFSIZE); + fd_inc_rcvbuf(resolve->fds[REQUEST_RECV_FD], QUERIES_MAX * BUFSIZE); + fd_inc_sndbuf(resolve->fds[RESPONSE_SEND_FD], QUERIES_MAX * BUFSIZE); + fd_inc_rcvbuf(resolve->fds[RESPONSE_RECV_FD], QUERIES_MAX * BUFSIZE); + + fd_nonblock(resolve->fds[RESPONSE_RECV_FD], true); + + *ret = resolve; + return 0; + +fail: + sd_resolve_unref(resolve); + return r; +} + +_public_ int sd_resolve_default(sd_resolve **ret) { + + static thread_local sd_resolve *default_resolve = NULL; + sd_resolve *e = NULL; + int r; + + if (!ret) + return !!default_resolve; + + if (default_resolve) { + *ret = sd_resolve_ref(default_resolve); + return 0; + } + + r = sd_resolve_new(&e); + if (r < 0) + return r; + + e->default_resolve_ptr = &default_resolve; + e->tid = gettid(); + default_resolve = e; + + *ret = e; + return 1; +} + +_public_ int sd_resolve_get_tid(sd_resolve *resolve, pid_t *tid) { + assert_return(resolve, -EINVAL); + assert_return(tid, -EINVAL); + assert_return(!resolve_pid_changed(resolve), -ECHILD); + + if (resolve->tid != 0) { + *tid = resolve->tid; + return 0; + } + + if (resolve->event) + return sd_event_get_tid(resolve->event, tid); + + return -ENXIO; +} + +static void resolve_free(sd_resolve *resolve) { + PROTECT_ERRNO; + sd_resolve_query *q; + unsigned i; + + assert(resolve); + + while ((q = resolve->queries)) { + assert(q->floating); + resolve_query_disconnect(q); + sd_resolve_query_unref(q); + } + + if (resolve->default_resolve_ptr) + *(resolve->default_resolve_ptr) = NULL; + + resolve->dead = true; + + sd_resolve_detach_event(resolve); + + if (resolve->fds[REQUEST_SEND_FD] >= 0) { + + RHeader req = { + .type = REQUEST_TERMINATE, + .length = sizeof(req) + }; + + /* Send one termination packet for each worker */ + for (i = 0; i < resolve->n_valid_workers; i++) + (void) send(resolve->fds[REQUEST_SEND_FD], &req, req.length, MSG_NOSIGNAL); + } + + /* Now terminate them and wait until they are gone. + If we get an error than most likely the thread already exited. */ + for (i = 0; i < resolve->n_valid_workers; i++) + (void) pthread_join(resolve->workers[i], NULL); + + /* Close all communication channels */ + for (i = 0; i < _FD_MAX; i++) + safe_close(resolve->fds[i]); + + free(resolve); +} + +_public_ sd_resolve* sd_resolve_ref(sd_resolve *resolve) { + assert_return(resolve, NULL); + + assert(resolve->n_ref >= 1); + resolve->n_ref++; + + return resolve; +} + +_public_ sd_resolve* sd_resolve_unref(sd_resolve *resolve) { + + if (!resolve) + return NULL; + + assert(resolve->n_ref >= 1); + resolve->n_ref--; + + if (resolve->n_ref <= 0) + resolve_free(resolve); + + return NULL; +} + +_public_ int sd_resolve_get_fd(sd_resolve *resolve) { + assert_return(resolve, -EINVAL); + assert_return(!resolve_pid_changed(resolve), -ECHILD); + + return resolve->fds[RESPONSE_RECV_FD]; +} + +_public_ int sd_resolve_get_events(sd_resolve *resolve) { + assert_return(resolve, -EINVAL); + assert_return(!resolve_pid_changed(resolve), -ECHILD); + + return resolve->n_queries > resolve->n_done ? POLLIN : 0; +} + +_public_ int sd_resolve_get_timeout(sd_resolve *resolve, uint64_t *usec) { + assert_return(resolve, -EINVAL); + assert_return(usec, -EINVAL); + assert_return(!resolve_pid_changed(resolve), -ECHILD); + + *usec = (uint64_t) -1; + return 0; +} + +static sd_resolve_query *lookup_query(sd_resolve *resolve, unsigned id) { + sd_resolve_query *q; + + assert(resolve); + + q = resolve->query_array[id % QUERIES_MAX]; + if (q) + if (q->id == id) + return q; + + return NULL; +} + +static int complete_query(sd_resolve *resolve, sd_resolve_query *q) { + int r; + + assert(q); + assert(!q->done); + assert(q->resolve == resolve); + + q->done = true; + resolve->n_done++; + + resolve->current = sd_resolve_query_ref(q); + + switch (q->type) { + + case REQUEST_ADDRINFO: + r = getaddrinfo_done(q); + break; + + case REQUEST_NAMEINFO: + r = getnameinfo_done(q); + break; + + default: + assert_not_reached("Cannot complete unknown query type"); + } + + resolve->current = NULL; + + if (q->floating) { + resolve_query_disconnect(q); + sd_resolve_query_unref(q); + } + + sd_resolve_query_unref(q); + + return r; +} + +static int unserialize_addrinfo(const void **p, size_t *length, struct addrinfo **ret_ai) { + AddrInfoSerialization s; + size_t l; + struct addrinfo *ai; + + assert(p); + assert(*p); + assert(ret_ai); + assert(length); + + if (*length < sizeof(AddrInfoSerialization)) + return -EBADMSG; + + memcpy(&s, *p, sizeof(s)); + + l = sizeof(AddrInfoSerialization) + s.ai_addrlen + s.canonname_len; + if (*length < l) + return -EBADMSG; + + ai = new0(struct addrinfo, 1); + if (!ai) + return -ENOMEM; + + ai->ai_flags = s.ai_flags; + ai->ai_family = s.ai_family; + ai->ai_socktype = s.ai_socktype; + ai->ai_protocol = s.ai_protocol; + ai->ai_addrlen = s.ai_addrlen; + + if (s.ai_addrlen > 0) { + ai->ai_addr = memdup((const uint8_t*) *p + sizeof(AddrInfoSerialization), s.ai_addrlen); + if (!ai->ai_addr) { + free(ai); + return -ENOMEM; + } + } + + if (s.canonname_len > 0) { + ai->ai_canonname = memdup((const uint8_t*) *p + sizeof(AddrInfoSerialization) + s.ai_addrlen, s.canonname_len); + if (!ai->ai_canonname) { + free(ai->ai_addr); + free(ai); + return -ENOMEM; + } + } + + *length -= l; + *ret_ai = ai; + *p = ((const uint8_t*) *p) + l; + + return 0; +} + +static int handle_response(sd_resolve *resolve, const Packet *packet, size_t length) { + const RHeader *resp; + sd_resolve_query *q; + int r; + + assert(resolve); + + resp = &packet->rheader; + assert(resp); + assert(length >= sizeof(RHeader)); + assert(length == resp->length); + + if (resp->type == RESPONSE_DIED) { + resolve->dead = true; + return 0; + } + + assert(resolve->n_outstanding > 0); + resolve->n_outstanding--; + + q = lookup_query(resolve, resp->id); + if (!q) + return 0; + + switch (resp->type) { + + case RESPONSE_ADDRINFO: { + const AddrInfoResponse *ai_resp = &packet->addrinfo_response; + const void *p; + size_t l; + struct addrinfo *prev = NULL; + + assert(length >= sizeof(AddrInfoResponse)); + assert(q->type == REQUEST_ADDRINFO); + + q->ret = ai_resp->ret; + q->_errno = ai_resp->_errno; + q->_h_errno = ai_resp->_h_errno; + + l = length - sizeof(AddrInfoResponse); + p = (const uint8_t*) resp + sizeof(AddrInfoResponse); + + while (l > 0 && p) { + struct addrinfo *ai = NULL; + + r = unserialize_addrinfo(&p, &l, &ai); + if (r < 0) { + q->ret = EAI_SYSTEM; + q->_errno = -r; + q->_h_errno = 0; + freeaddrinfo(q->addrinfo); + q->addrinfo = NULL; + break; + } + + if (prev) + prev->ai_next = ai; + else + q->addrinfo = ai; + + prev = ai; + } + + return complete_query(resolve, q); + } + + case RESPONSE_NAMEINFO: { + const NameInfoResponse *ni_resp = &packet->nameinfo_response; + + assert(length >= sizeof(NameInfoResponse)); + assert(q->type == REQUEST_NAMEINFO); + + q->ret = ni_resp->ret; + q->_errno = ni_resp->_errno; + q->_h_errno = ni_resp->_h_errno; + + if (ni_resp->hostlen > 0) { + q->host = strndup((const char*) ni_resp + sizeof(NameInfoResponse), ni_resp->hostlen-1); + if (!q->host) { + q->ret = EAI_MEMORY; + q->_errno = ENOMEM; + q->_h_errno = 0; + } + } + + if (ni_resp->servlen > 0) { + q->serv = strndup((const char*) ni_resp + sizeof(NameInfoResponse) + ni_resp->hostlen, ni_resp->servlen-1); + if (!q->serv) { + q->ret = EAI_MEMORY; + q->_errno = ENOMEM; + q->_h_errno = 0; + } + } + + return complete_query(resolve, q); + } + + default: + return 0; + } +} + +_public_ int sd_resolve_process(sd_resolve *resolve) { + RESOLVE_DONT_DESTROY(resolve); + + union { + Packet packet; + uint8_t space[BUFSIZE]; + } buf; + ssize_t l; + int r; + + assert_return(resolve, -EINVAL); + assert_return(!resolve_pid_changed(resolve), -ECHILD); + + /* We don't allow recursively invoking sd_resolve_process(). */ + assert_return(!resolve->current, -EBUSY); + + l = recv(resolve->fds[RESPONSE_RECV_FD], &buf, sizeof(buf), 0); + if (l < 0) { + if (errno == EAGAIN) + return 0; + + return -errno; + } + if (l == 0) + return -ECONNREFUSED; + + r = handle_response(resolve, &buf.packet, (size_t) l); + if (r < 0) + return r; + + return 1; +} + +_public_ int sd_resolve_wait(sd_resolve *resolve, uint64_t timeout_usec) { + int r; + + assert_return(resolve, -EINVAL); + assert_return(!resolve_pid_changed(resolve), -ECHILD); + + if (resolve->n_done >= resolve->n_queries) + return 0; + + do { + r = fd_wait_for_event(resolve->fds[RESPONSE_RECV_FD], POLLIN, timeout_usec); + } while (r == -EINTR); + + if (r < 0) + return r; + + return sd_resolve_process(resolve); +} + +static int alloc_query(sd_resolve *resolve, bool floating, sd_resolve_query **_q) { + sd_resolve_query *q; + int r; + + assert(resolve); + assert(_q); + + if (resolve->n_queries >= QUERIES_MAX) + return -ENOBUFS; + + r = start_threads(resolve, 1); + if (r < 0) + return r; + + while (resolve->query_array[resolve->current_id % QUERIES_MAX]) + resolve->current_id++; + + q = resolve->query_array[resolve->current_id % QUERIES_MAX] = new0(sd_resolve_query, 1); + if (!q) + return -ENOMEM; + + q->n_ref = 1; + q->resolve = resolve; + q->floating = floating; + q->id = resolve->current_id++; + + if (!floating) + sd_resolve_ref(resolve); + + LIST_PREPEND(queries, resolve->queries, q); + resolve->n_queries++; + + *_q = q; + return 0; +} + +_public_ int sd_resolve_getaddrinfo( + sd_resolve *resolve, + sd_resolve_query **_q, + const char *node, const char *service, + const struct addrinfo *hints, + sd_resolve_getaddrinfo_handler_t callback, void *userdata) { + + AddrInfoRequest req = {}; + struct msghdr mh = {}; + struct iovec iov[3]; + sd_resolve_query *q; + int r; + + assert_return(resolve, -EINVAL); + assert_return(node || service, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!resolve_pid_changed(resolve), -ECHILD); + + r = alloc_query(resolve, !_q, &q); + if (r < 0) + return r; + + q->type = REQUEST_ADDRINFO; + q->getaddrinfo_handler = callback; + q->userdata = userdata; + + req.node_len = node ? strlen(node)+1 : 0; + req.service_len = service ? strlen(service)+1 : 0; + + req.header.id = q->id; + req.header.type = REQUEST_ADDRINFO; + req.header.length = sizeof(AddrInfoRequest) + req.node_len + req.service_len; + + if (hints) { + req.hints_valid = true; + req.ai_flags = hints->ai_flags; + req.ai_family = hints->ai_family; + req.ai_socktype = hints->ai_socktype; + req.ai_protocol = hints->ai_protocol; + } + + iov[mh.msg_iovlen++] = (struct iovec) { .iov_base = &req, .iov_len = sizeof(AddrInfoRequest) }; + if (node) + iov[mh.msg_iovlen++] = (struct iovec) { .iov_base = (void*) node, .iov_len = req.node_len }; + if (service) + iov[mh.msg_iovlen++] = (struct iovec) { .iov_base = (void*) service, .iov_len = req.service_len }; + mh.msg_iov = iov; + + if (sendmsg(resolve->fds[REQUEST_SEND_FD], &mh, MSG_NOSIGNAL) < 0) { + sd_resolve_query_unref(q); + return -errno; + } + + resolve->n_outstanding++; + + if (_q) + *_q = q; + + return 0; +} + +static int getaddrinfo_done(sd_resolve_query* q) { + assert(q); + assert(q->done); + assert(q->getaddrinfo_handler); + + errno = q->_errno; + h_errno = q->_h_errno; + + return q->getaddrinfo_handler(q, q->ret, q->addrinfo, q->userdata); +} + +_public_ int sd_resolve_getnameinfo( + sd_resolve *resolve, + sd_resolve_query**_q, + const struct sockaddr *sa, socklen_t salen, + int flags, + uint64_t get, + sd_resolve_getnameinfo_handler_t callback, + void *userdata) { + + NameInfoRequest req = {}; + struct msghdr mh = {}; + struct iovec iov[2]; + sd_resolve_query *q; + int r; + + assert_return(resolve, -EINVAL); + assert_return(sa, -EINVAL); + assert_return(salen >= sizeof(struct sockaddr), -EINVAL); + assert_return(salen <= sizeof(union sockaddr_union), -EINVAL); + assert_return((get & ~SD_RESOLVE_GET_BOTH) == 0, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!resolve_pid_changed(resolve), -ECHILD); + + r = alloc_query(resolve, !_q, &q); + if (r < 0) + return r; + + q->type = REQUEST_NAMEINFO; + q->getnameinfo_handler = callback; + q->userdata = userdata; + + req.header.id = q->id; + req.header.type = REQUEST_NAMEINFO; + req.header.length = sizeof(NameInfoRequest) + salen; + + req.flags = flags; + req.sockaddr_len = salen; + req.gethost = !!(get & SD_RESOLVE_GET_HOST); + req.getserv = !!(get & SD_RESOLVE_GET_SERVICE); + + iov[0] = (struct iovec) { .iov_base = &req, .iov_len = sizeof(NameInfoRequest) }; + iov[1] = (struct iovec) { .iov_base = (void*) sa, .iov_len = salen }; + + mh.msg_iov = iov; + mh.msg_iovlen = 2; + + if (sendmsg(resolve->fds[REQUEST_SEND_FD], &mh, MSG_NOSIGNAL) < 0) { + sd_resolve_query_unref(q); + return -errno; + } + + resolve->n_outstanding++; + + if (_q) + *_q = q; + + return 0; +} + +static int getnameinfo_done(sd_resolve_query *q) { + + assert(q); + assert(q->done); + assert(q->getnameinfo_handler); + + errno = q->_errno; + h_errno= q->_h_errno; + + return q->getnameinfo_handler(q, q->ret, q->host, q->serv, q->userdata); +} + +_public_ sd_resolve_query* sd_resolve_query_ref(sd_resolve_query *q) { + assert_return(q, NULL); + + assert(q->n_ref >= 1); + q->n_ref++; + + return q; +} + +static void resolve_freeaddrinfo(struct addrinfo *ai) { + while (ai) { + struct addrinfo *next = ai->ai_next; + + free(ai->ai_addr); + free(ai->ai_canonname); + free(ai); + ai = next; + } +} + +static void resolve_query_disconnect(sd_resolve_query *q) { + sd_resolve *resolve; + unsigned i; + + assert(q); + + if (!q->resolve) + return; + + resolve = q->resolve; + assert(resolve->n_queries > 0); + + if (q->done) { + assert(resolve->n_done > 0); + resolve->n_done--; + } + + i = q->id % QUERIES_MAX; + assert(resolve->query_array[i] == q); + resolve->query_array[i] = NULL; + LIST_REMOVE(queries, resolve->queries, q); + resolve->n_queries--; + + q->resolve = NULL; + if (!q->floating) + sd_resolve_unref(resolve); +} + +static void resolve_query_free(sd_resolve_query *q) { + assert(q); + + resolve_query_disconnect(q); + + resolve_freeaddrinfo(q->addrinfo); + free(q->host); + free(q->serv); + free(q); +} + +_public_ sd_resolve_query* sd_resolve_query_unref(sd_resolve_query* q) { + if (!q) + return NULL; + + assert(q->n_ref >= 1); + q->n_ref--; + + if (q->n_ref <= 0) + resolve_query_free(q); + + return NULL; +} + +_public_ int sd_resolve_query_is_done(sd_resolve_query *q) { + assert_return(q, -EINVAL); + assert_return(!resolve_pid_changed(q->resolve), -ECHILD); + + return q->done; +} + +_public_ void* sd_resolve_query_set_userdata(sd_resolve_query *q, void *userdata) { + void *ret; + + assert_return(q, NULL); + assert_return(!resolve_pid_changed(q->resolve), NULL); + + ret = q->userdata; + q->userdata = userdata; + + return ret; +} + +_public_ void* sd_resolve_query_get_userdata(sd_resolve_query *q) { + assert_return(q, NULL); + assert_return(!resolve_pid_changed(q->resolve), NULL); + + return q->userdata; +} + +_public_ sd_resolve *sd_resolve_query_get_resolve(sd_resolve_query *q) { + assert_return(q, NULL); + assert_return(!resolve_pid_changed(q->resolve), NULL); + + return q->resolve; +} + +static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_resolve *resolve = userdata; + int r; + + assert(resolve); + + r = sd_resolve_process(resolve); + if (r < 0) + return r; + + return 1; +} + +_public_ int sd_resolve_attach_event(sd_resolve *resolve, sd_event *event, int64_t priority) { + int r; + + assert_return(resolve, -EINVAL); + assert_return(!resolve->event, -EBUSY); + + assert(!resolve->event_source); + + if (event) + resolve->event = sd_event_ref(event); + else { + r = sd_event_default(&resolve->event); + if (r < 0) + return r; + } + + r = sd_event_add_io(resolve->event, &resolve->event_source, resolve->fds[RESPONSE_RECV_FD], POLLIN, io_callback, resolve); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(resolve->event_source, priority); + if (r < 0) + goto fail; + + return 0; + +fail: + sd_resolve_detach_event(resolve); + return r; +} + +_public_ int sd_resolve_detach_event(sd_resolve *resolve) { + assert_return(resolve, -EINVAL); + + if (!resolve->event) + return 0; + + if (resolve->event_source) { + sd_event_source_set_enabled(resolve->event_source, SD_EVENT_OFF); + resolve->event_source = sd_event_source_unref(resolve->event_source); + } + + resolve->event = sd_event_unref(resolve->event); + return 1; +} + +_public_ sd_event *sd_resolve_get_event(sd_resolve *resolve) { + assert_return(resolve, NULL); + + return resolve->event; +} diff --git a/src/libsystemd/libsystemd-internal/sd-resolve/test-resolve.c b/src/libsystemd/libsystemd-internal/sd-resolve/test-resolve.c new file mode 100644 index 0000000000..0209cc77e7 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-resolve/test-resolve.c @@ -0,0 +1,114 @@ +/*** + This file is part of systemd. + + Copyright 2005-2008 Lennart Poettering + Copyright 2014 Daniel Buch + + 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 +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "macro.h" +#include "socket-util.h" +#include "string-util.h" + +static int getaddrinfo_handler(sd_resolve_query *q, int ret, const struct addrinfo *ai, void *userdata) { + const struct addrinfo *i; + + assert_se(q); + + if (ret != 0) { + log_error("getaddrinfo error: %s %i", gai_strerror(ret), ret); + return 0; + } + + for (i = ai; i; i = i->ai_next) { + _cleanup_free_ char *addr = NULL; + + assert_se(sockaddr_pretty(i->ai_addr, i->ai_addrlen, false, true, &addr) == 0); + puts(addr); + } + + printf("canonical name: %s\n", strna(ai->ai_canonname)); + + return 0; +} + +static int getnameinfo_handler(sd_resolve_query *q, int ret, const char *host, const char *serv, void *userdata) { + assert_se(q); + + if (ret != 0) { + log_error("getnameinfo error: %s %i", gai_strerror(ret), ret); + return 0; + } + + printf("Host: %s — Serv: %s\n", strna(host), strna(serv)); + return 0; +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_resolve_query_unrefp) sd_resolve_query *q1 = NULL, *q2 = NULL; + _cleanup_(sd_resolve_unrefp) sd_resolve *resolve = NULL; + int r = 0; + + struct addrinfo hints = { + .ai_family = PF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_CANONNAME + }; + + struct sockaddr_in sa = { + .sin_family = AF_INET, + .sin_port = htons(80) + }; + + assert_se(sd_resolve_default(&resolve) >= 0); + + /* Test a floating resolver query */ + sd_resolve_getaddrinfo(resolve, NULL, "redhat.com", "http", NULL, getaddrinfo_handler, NULL); + + /* Make a name -> address query */ + r = sd_resolve_getaddrinfo(resolve, &q1, argc >= 2 ? argv[1] : "www.heise.de", NULL, &hints, getaddrinfo_handler, NULL); + if (r < 0) + log_error_errno(r, "sd_resolve_getaddrinfo(): %m"); + + /* Make an address -> name query */ + sa.sin_addr.s_addr = inet_addr(argc >= 3 ? argv[2] : "193.99.144.71"); + r = sd_resolve_getnameinfo(resolve, &q2, (struct sockaddr*) &sa, sizeof(sa), 0, SD_RESOLVE_GET_BOTH, getnameinfo_handler, NULL); + if (r < 0) + log_error_errno(r, "sd_resolve_getnameinfo(): %m"); + + /* Wait until all queries are completed */ + for (;;) { + r = sd_resolve_wait(resolve, (uint64_t) -1); + if (r == 0) + break; + if (r < 0) { + log_error_errno(r, "sd_resolve_wait(): %m"); + assert_not_reached("sd_resolve_wait() failed"); + } + } + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-utf8/sd-utf8.c b/src/libsystemd/libsystemd-internal/sd-utf8/sd-utf8.c new file mode 100644 index 0000000000..77be8e1996 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-utf8/sd-utf8.c @@ -0,0 +1,35 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "utf8.h" +#include "util.h" + +_public_ const char *sd_utf8_is_valid(const char *s) { + assert_return(s, NULL); + + return utf8_is_valid(s); +} + +_public_ const char *sd_ascii_is_valid(const char *s) { + assert_return(s, NULL); + + return ascii_is_valid(s); +} diff --git a/src/libsystemd/libsystemd-internal/subdir.mk b/src/libsystemd/libsystemd-internal/subdir.mk new file mode 100644 index 0000000000..35def00fdc --- /dev/null +++ b/src/libsystemd/libsystemd-internal/subdir.mk @@ -0,0 +1,29 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd.CPPFLAGS += -DLIBDIR=\"$(libdir)\" +systemd.CPPFLAGS += -DUDEVLIBEXECDIR=\"$(udevlibexecdir)\" + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libsystemd/libsystemd-journal-internal/Makefile b/src/libsystemd/libsystemd-journal-internal/Makefile new file mode 100644 index 0000000000..964e1b5b4f --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/Makefile @@ -0,0 +1,113 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +audit_list_includes = -include linux/audit.h -include missing.h +ifneq ($(HAVE_AUDIT),) +audit_list_includes += -include libaudit.h +endif # HAVE_AUDIT + +$(outdir)/audit_type-list.txt: + $(AM_V_GEN)$(CPP) $(ALL_CPPFLAGS) -dM $(audit_list_includes) - $@ + +$(outdir)/audit_type-to-name.h: $(outdir)/audit_type-list.txt + $(AM_V_GEN)$(AWK) 'BEGIN{ print "const char *audit_type_to_string(int type) {\n\tswitch(type) {" } {printf " case AUDIT_%s: return \"%s\";\n", $$1, $$1 } END{ print " default: return NULL;\n\t}\n}\n" }' <$< >$@ + +pkginclude_HEADERS += \ + src/systemd/sd-journal.h \ + src/systemd/sd-messages.h \ + src/systemd/_sd-common.h + +libsystemd_journal_internal_la_SOURCES = \ + src/journal/sd-journal.c \ + src/systemd/sd-journal.h \ + src/systemd/_sd-common.h \ + src/journal/journal-file.c \ + src/journal/journal-file.h \ + src/journal/journal-vacuum.c \ + src/journal/journal-vacuum.h \ + src/journal/journal-verify.c \ + src/journal/journal-verify.h \ + src/journal/lookup3.c \ + src/journal/lookup3.h \ + src/journal/journal-send.c \ + src/journal/journal-def.h \ + src/journal/compress.h \ + src/journal/catalog.c \ + src/journal/catalog.h \ + src/journal/mmap-cache.c \ + src/journal/mmap-cache.h \ + src/journal/compress.c \ + src/journal/audit-type.h \ + src/journal/audit-type.c \ + src/shared/gcrypt-util.h \ + src/shared/gcrypt-util.c + +nodist_libsystemd_journal_internal_la_SOURCES = \ + src/journal/audit_type-to-name.h + +gperf_txt_sources += \ + src/journal/audit_type-list.txt + +# using _CFLAGS = in the conditional below would suppress AM_CFLAGS +libsystemd_journal_internal_la_CFLAGS = \ + $(AM_CFLAGS) + +libsystemd_journal_internal_la_LIBADD = + +ifneq ($(HAVE_XZ),) +libsystemd_journal_internal_la_CFLAGS += \ + $(XZ_CFLAGS) + +libsystemd_journal_internal_la_LIBADD += \ + $(XZ_LIBS) +endif # HAVE_XZ + +ifneq ($(HAVE_LZ4),) +libsystemd_journal_internal_la_CFLAGS += \ + $(LZ4_CFLAGS) + +libsystemd_journal_internal_la_LIBADD += \ + $(LZ4_LIBS) +endif # HAVE_LZ4 + +ifneq ($(HAVE_GCRYPT),) +libsystemd_journal_internal_la_SOURCES += \ + src/journal/journal-authenticate.c \ + src/journal/journal-authenticate.h \ + src/journal/fsprg.c \ + src/journal/fsprg.h + +libsystemd_journal_internal_la_LIBADD += \ + $(GCRYPT_LIBS) + +# fsprg.c is a drop-in file using void pointer arithmetic +libsystemd_journal_internal_la_CFLAGS += \ + $(GCRYPT_CFLAGS) \ + -Wno-pointer-arith +endif # HAVE_GCRYPT + +noinst_LTLIBRARIES += \ + libsystemd-journal-internal.la +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libsystemd/libsystemd-journal-internal/audit-type.c b/src/libsystemd/libsystemd-journal-internal/audit-type.c new file mode 100644 index 0000000000..71e8790ca8 --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/audit-type.c @@ -0,0 +1,29 @@ +/*** + This file is part of systemd. + + Copyright 2015 Zbigniew Jędrzejewski-Szmek + + 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 +#ifdef HAVE_AUDIT +# include +#endif + +#include "missing.h" +#include "audit-type.h" +#include "audit_type-to-name.h" +#include "macro.h" diff --git a/src/libsystemd/libsystemd-journal-internal/audit-type.h b/src/libsystemd/libsystemd-journal-internal/audit-type.h new file mode 100644 index 0000000000..1dd2163707 --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/audit-type.h @@ -0,0 +1,37 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Zbigniew Jędrzejewski-Szmek + + 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 "macro.h" + +const char *audit_type_to_string(int type); +int audit_type_from_string(const char *s); + +/* This is inspired by DNS TYPEnnn formatting */ +#define audit_type_name_alloca(type) \ + ({ \ + const char *_s_; \ + _s_ = audit_type_to_string(type); \ + if (!_s_) { \ + _s_ = alloca(strlen("AUDIT") + DECIMAL_STR_MAX(int)); \ + sprintf((char*) _s_, "AUDIT%04i", type); \ + } \ + _s_; \ + }) diff --git a/src/libsystemd/libsystemd-journal-internal/catalog.c b/src/libsystemd/libsystemd-journal-internal/catalog.c new file mode 100644 index 0000000000..70838d958c --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/catalog.c @@ -0,0 +1,767 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "catalog.h" +#include "conf-files.h" +#include "fd-util.h" +#include "fileio.h" +#include "hashmap.h" +#include "log.h" +#include "mkdir.h" +#include "path-util.h" +#include "siphash24.h" +#include "sparse-endian.h" +#include "strbuf.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +const char * const catalog_file_dirs[] = { + "/usr/local/lib/systemd/catalog/", + "/usr/lib/systemd/catalog/", + NULL +}; + +#define CATALOG_SIGNATURE (uint8_t[]) { 'R', 'H', 'H', 'H', 'K', 'S', 'L', 'P' } + +typedef struct CatalogHeader { + uint8_t signature[8]; /* "RHHHKSLP" */ + le32_t compatible_flags; + le32_t incompatible_flags; + le64_t header_size; + le64_t n_items; + le64_t catalog_item_size; +} CatalogHeader; + +typedef struct CatalogItem { + sd_id128_t id; + char language[32]; + le64_t offset; +} CatalogItem; + +static void catalog_hash_func(const void *p, struct siphash *state) { + const CatalogItem *i = p; + + siphash24_compress(&i->id, sizeof(i->id), state); + siphash24_compress(i->language, strlen(i->language), state); +} + +static int catalog_compare_func(const void *a, const void *b) { + const CatalogItem *i = a, *j = b; + unsigned k; + + for (k = 0; k < ELEMENTSOF(j->id.bytes); k++) { + if (i->id.bytes[k] < j->id.bytes[k]) + return -1; + if (i->id.bytes[k] > j->id.bytes[k]) + return 1; + } + + return strcmp(i->language, j->language); +} + +const struct hash_ops catalog_hash_ops = { + .hash = catalog_hash_func, + .compare = catalog_compare_func +}; + +static bool next_header(const char **s) { + const char *e; + + e = strchr(*s, '\n'); + + /* Unexpected end */ + if (!e) + return false; + + /* End of headers */ + if (e == *s) + return false; + + *s = e + 1; + return true; +} + +static const char *skip_header(const char *s) { + while (next_header(&s)) + ; + return s; +} + +static char *combine_entries(const char *one, const char *two) { + const char *b1, *b2; + size_t l1, l2, n; + char *dest, *p; + + /* Find split point of headers to body */ + b1 = skip_header(one); + b2 = skip_header(two); + + l1 = strlen(one); + l2 = strlen(two); + dest = new(char, l1 + l2 + 1); + if (!dest) { + log_oom(); + return NULL; + } + + p = dest; + + /* Headers from @one */ + n = b1 - one; + p = mempcpy(p, one, n); + + /* Headers from @two, these will only be found if not present above */ + n = b2 - two; + p = mempcpy(p, two, n); + + /* Body from @one */ + n = l1 - (b1 - one); + if (n > 0) { + memcpy(p, b1, n); + p += n; + + /* Body from @two */ + } else { + n = l2 - (b2 - two); + memcpy(p, b2, n); + p += n; + } + + assert(p - dest <= (ptrdiff_t)(l1 + l2)); + p[0] = '\0'; + return dest; +} + +static int finish_item( + Hashmap *h, + sd_id128_t id, + const char *language, + char *payload, size_t payload_size) { + + _cleanup_free_ CatalogItem *i = NULL; + _cleanup_free_ char *prev = NULL, *combined = NULL; + + assert(h); + assert(payload); + assert(payload_size > 0); + + i = new0(CatalogItem, 1); + if (!i) + return log_oom(); + + i->id = id; + if (language) { + assert(strlen(language) > 1 && strlen(language) < 32); + strcpy(i->language, language); + } + + prev = hashmap_get(h, i); + if (prev) { + /* Already have such an item, combine them */ + combined = combine_entries(payload, prev); + if (!combined) + return log_oom(); + + if (hashmap_update(h, i, combined) < 0) + return log_oom(); + combined = NULL; + } else { + /* A new item */ + combined = memdup(payload, payload_size + 1); + if (!combined) + return log_oom(); + + if (hashmap_put(h, i, combined) < 0) + return log_oom(); + i = NULL; + combined = NULL; + } + + return 0; +} + +int catalog_file_lang(const char* filename, char **lang) { + char *beg, *end, *_lang; + + end = endswith(filename, ".catalog"); + if (!end) + return 0; + + beg = end - 1; + while (beg > filename && *beg != '.' && *beg != '/' && end - beg < 32) + beg--; + + if (*beg != '.' || end <= beg + 1) + return 0; + + _lang = strndup(beg + 1, end - beg - 1); + if (!_lang) + return -ENOMEM; + + *lang = _lang; + return 1; +} + +static int catalog_entry_lang(const char* filename, int line, + const char* t, const char* deflang, char **lang) { + size_t c; + + c = strlen(t); + if (c == 0) { + log_error("[%s:%u] Language too short.", filename, line); + return -EINVAL; + } + if (c > 31) { + log_error("[%s:%u] language too long.", filename, line); + return -EINVAL; + } + + if (deflang) { + if (streq(t, deflang)) { + log_warning("[%s:%u] language specified unnecessarily", + filename, line); + return 0; + } else + log_warning("[%s:%u] language differs from default for file", + filename, line); + } + + *lang = strdup(t); + if (!*lang) + return -ENOMEM; + + return 0; +} + +int catalog_import_file(Hashmap *h, const char *path) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *payload = NULL; + size_t payload_size = 0, payload_allocated = 0; + unsigned n = 0; + sd_id128_t id; + _cleanup_free_ char *deflang = NULL, *lang = NULL; + bool got_id = false, empty_line = true; + int r; + + assert(h); + assert(path); + + f = fopen(path, "re"); + if (!f) + return log_error_errno(errno, "Failed to open file %s: %m", path); + + r = catalog_file_lang(path, &deflang); + if (r < 0) + log_error_errno(r, "Failed to determine language for file %s: %m", path); + if (r == 1) + log_debug("File %s has language %s.", path, deflang); + + for (;;) { + char line[LINE_MAX]; + size_t line_len; + + if (!fgets(line, sizeof(line), f)) { + if (feof(f)) + break; + + return log_error_errno(errno, "Failed to read file %s: %m", path); + } + + n++; + + truncate_nl(line); + + if (line[0] == 0) { + empty_line = true; + continue; + } + + if (strchr(COMMENTS "\n", line[0])) + continue; + + if (empty_line && + strlen(line) >= 2+1+32 && + line[0] == '-' && + line[1] == '-' && + line[2] == ' ' && + (line[2+1+32] == ' ' || line[2+1+32] == '\0')) { + + bool with_language; + sd_id128_t jd; + + /* New entry */ + + with_language = line[2+1+32] != '\0'; + line[2+1+32] = '\0'; + + if (sd_id128_from_string(line + 2 + 1, &jd) >= 0) { + + if (got_id) { + if (payload_size == 0) { + log_error("[%s:%u] No payload text.", path, n); + return -EINVAL; + } + + r = finish_item(h, id, lang ?: deflang, payload, payload_size); + if (r < 0) + return r; + + lang = mfree(lang); + payload_size = 0; + } + + if (with_language) { + char *t; + + t = strstrip(line + 2 + 1 + 32 + 1); + r = catalog_entry_lang(path, n, t, deflang, &lang); + if (r < 0) + return r; + } + + got_id = true; + empty_line = false; + id = jd; + + continue; + } + } + + /* Payload */ + if (!got_id) { + log_error("[%s:%u] Got payload before ID.", path, n); + return -EINVAL; + } + + line_len = strlen(line); + if (!GREEDY_REALLOC(payload, payload_allocated, + payload_size + (empty_line ? 1 : 0) + line_len + 1 + 1)) + return log_oom(); + + if (empty_line) + payload[payload_size++] = '\n'; + memcpy(payload + payload_size, line, line_len); + payload_size += line_len; + payload[payload_size++] = '\n'; + payload[payload_size] = '\0'; + + empty_line = false; + } + + if (got_id) { + if (payload_size == 0) { + log_error("[%s:%u] No payload text.", path, n); + return -EINVAL; + } + + r = finish_item(h, id, lang ?: deflang, payload, payload_size); + if (r < 0) + return r; + } + + return 0; +} + +static int64_t write_catalog(const char *database, struct strbuf *sb, + CatalogItem *items, size_t n) { + CatalogHeader header; + _cleanup_fclose_ FILE *w = NULL; + int r; + _cleanup_free_ char *d, *p = NULL; + size_t k; + + d = dirname_malloc(database); + if (!d) + return log_oom(); + + r = mkdir_p(d, 0775); + if (r < 0) + return log_error_errno(r, "Recursive mkdir %s: %m", d); + + r = fopen_temporary(database, &w, &p); + if (r < 0) + return log_error_errno(r, "Failed to open database for writing: %s: %m", + database); + + zero(header); + memcpy(header.signature, CATALOG_SIGNATURE, sizeof(header.signature)); + header.header_size = htole64(ALIGN_TO(sizeof(CatalogHeader), 8)); + header.catalog_item_size = htole64(sizeof(CatalogItem)); + header.n_items = htole64(n); + + r = -EIO; + + k = fwrite(&header, 1, sizeof(header), w); + if (k != sizeof(header)) { + log_error("%s: failed to write header.", p); + goto error; + } + + k = fwrite(items, 1, n * sizeof(CatalogItem), w); + if (k != n * sizeof(CatalogItem)) { + log_error("%s: failed to write database.", p); + goto error; + } + + k = fwrite(sb->buf, 1, sb->len, w); + if (k != sb->len) { + log_error("%s: failed to write strings.", p); + goto error; + } + + r = fflush_and_check(w); + if (r < 0) { + log_error_errno(r, "%s: failed to write database: %m", p); + goto error; + } + + fchmod(fileno(w), 0644); + + if (rename(p, database) < 0) { + r = log_error_errno(errno, "rename (%s -> %s) failed: %m", p, database); + goto error; + } + + return ftello(w); + +error: + (void) unlink(p); + return r; +} + +int catalog_update(const char* database, const char* root, const char* const* dirs) { + _cleanup_strv_free_ char **files = NULL; + char **f; + struct strbuf *sb = NULL; + _cleanup_hashmap_free_free_free_ Hashmap *h = NULL; + _cleanup_free_ CatalogItem *items = NULL; + ssize_t offset; + char *payload; + CatalogItem *i; + Iterator j; + unsigned n; + int r; + int64_t sz; + + h = hashmap_new(&catalog_hash_ops); + sb = strbuf_new(); + + if (!h || !sb) { + r = log_oom(); + goto finish; + } + + r = conf_files_list_strv(&files, ".catalog", root, dirs); + if (r < 0) { + log_error_errno(r, "Failed to get catalog files: %m"); + goto finish; + } + + STRV_FOREACH(f, files) { + log_debug("Reading file '%s'", *f); + r = catalog_import_file(h, *f); + if (r < 0) { + log_error_errno(r, "Failed to import file '%s': %m", *f); + goto finish; + } + } + + if (hashmap_size(h) <= 0) { + log_info("No items in catalog."); + goto finish; + } else + log_debug("Found %u items in catalog.", hashmap_size(h)); + + items = new(CatalogItem, hashmap_size(h)); + if (!items) { + r = log_oom(); + goto finish; + } + + n = 0; + HASHMAP_FOREACH_KEY(payload, i, h, j) { + log_debug("Found " SD_ID128_FORMAT_STR ", language %s", + SD_ID128_FORMAT_VAL(i->id), + isempty(i->language) ? "C" : i->language); + + offset = strbuf_add_string(sb, payload, strlen(payload)); + if (offset < 0) { + r = log_oom(); + goto finish; + } + i->offset = htole64((uint64_t) offset); + items[n++] = *i; + } + + assert(n == hashmap_size(h)); + qsort_safe(items, n, sizeof(CatalogItem), catalog_compare_func); + + strbuf_complete(sb); + + sz = write_catalog(database, sb, items, n); + if (sz < 0) + r = log_error_errno(sz, "Failed to write %s: %m", database); + else { + r = 0; + log_debug("%s: wrote %u items, with %zu bytes of strings, %"PRIi64" total size.", + database, n, sb->len, sz); + } + +finish: + strbuf_cleanup(sb); + + return r; +} + +static int open_mmap(const char *database, int *_fd, struct stat *_st, void **_p) { + const CatalogHeader *h; + int fd; + void *p; + struct stat st; + + assert(_fd); + assert(_st); + assert(_p); + + fd = open(database, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return -errno; + + if (fstat(fd, &st) < 0) { + safe_close(fd); + return -errno; + } + + if (st.st_size < (off_t) sizeof(CatalogHeader)) { + safe_close(fd); + return -EINVAL; + } + + p = mmap(NULL, PAGE_ALIGN(st.st_size), PROT_READ, MAP_SHARED, fd, 0); + if (p == MAP_FAILED) { + safe_close(fd); + return -errno; + } + + h = p; + if (memcmp(h->signature, CATALOG_SIGNATURE, sizeof(h->signature)) != 0 || + le64toh(h->header_size) < sizeof(CatalogHeader) || + le64toh(h->catalog_item_size) < sizeof(CatalogItem) || + h->incompatible_flags != 0 || + le64toh(h->n_items) <= 0 || + st.st_size < (off_t) (le64toh(h->header_size) + le64toh(h->catalog_item_size) * le64toh(h->n_items))) { + safe_close(fd); + munmap(p, st.st_size); + return -EBADMSG; + } + + *_fd = fd; + *_st = st; + *_p = p; + + return 0; +} + +static const char *find_id(void *p, sd_id128_t id) { + CatalogItem key, *f = NULL; + const CatalogHeader *h = p; + const char *loc; + + zero(key); + key.id = id; + + loc = setlocale(LC_MESSAGES, NULL); + if (loc && loc[0] && !streq(loc, "C") && !streq(loc, "POSIX")) { + strncpy(key.language, loc, sizeof(key.language)); + key.language[strcspn(key.language, ".@")] = 0; + + f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func); + if (!f) { + char *e; + + e = strchr(key.language, '_'); + if (e) { + *e = 0; + f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func); + } + } + } + + if (!f) { + zero(key.language); + f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func); + } + + if (!f) + return NULL; + + return (const char*) p + + le64toh(h->header_size) + + le64toh(h->n_items) * le64toh(h->catalog_item_size) + + le64toh(f->offset); +} + +int catalog_get(const char* database, sd_id128_t id, char **_text) { + _cleanup_close_ int fd = -1; + void *p = NULL; + struct stat st = {}; + char *text = NULL; + int r; + const char *s; + + assert(_text); + + r = open_mmap(database, &fd, &st, &p); + if (r < 0) + return r; + + s = find_id(p, id); + if (!s) { + r = -ENOENT; + goto finish; + } + + text = strdup(s); + if (!text) { + r = -ENOMEM; + goto finish; + } + + *_text = text; + r = 0; + +finish: + if (p) + munmap(p, st.st_size); + + return r; +} + +static char *find_header(const char *s, const char *header) { + + for (;;) { + const char *v; + + v = startswith(s, header); + if (v) { + v += strspn(v, WHITESPACE); + return strndup(v, strcspn(v, NEWLINE)); + } + + if (!next_header(&s)) + return NULL; + } +} + +static void dump_catalog_entry(FILE *f, sd_id128_t id, const char *s, bool oneline) { + if (oneline) { + _cleanup_free_ char *subject = NULL, *defined_by = NULL; + + subject = find_header(s, "Subject:"); + defined_by = find_header(s, "Defined-By:"); + + fprintf(f, SD_ID128_FORMAT_STR " %s: %s\n", + SD_ID128_FORMAT_VAL(id), + strna(defined_by), strna(subject)); + } else + fprintf(f, "-- " SD_ID128_FORMAT_STR "\n%s\n", + SD_ID128_FORMAT_VAL(id), s); +} + + +int catalog_list(FILE *f, const char *database, bool oneline) { + _cleanup_close_ int fd = -1; + void *p = NULL; + struct stat st; + const CatalogHeader *h; + const CatalogItem *items; + int r; + unsigned n; + sd_id128_t last_id; + bool last_id_set = false; + + r = open_mmap(database, &fd, &st, &p); + if (r < 0) + return r; + + h = p; + items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size)); + + for (n = 0; n < le64toh(h->n_items); n++) { + const char *s; + + if (last_id_set && sd_id128_equal(last_id, items[n].id)) + continue; + + assert_se(s = find_id(p, items[n].id)); + + dump_catalog_entry(f, items[n].id, s, oneline); + + last_id_set = true; + last_id = items[n].id; + } + + munmap(p, st.st_size); + + return 0; +} + +int catalog_list_items(FILE *f, const char *database, bool oneline, char **items) { + char **item; + int r = 0; + + STRV_FOREACH(item, items) { + sd_id128_t id; + int k; + _cleanup_free_ char *msg = NULL; + + k = sd_id128_from_string(*item, &id); + if (k < 0) { + log_error_errno(k, "Failed to parse id128 '%s': %m", *item); + if (r == 0) + r = k; + continue; + } + + k = catalog_get(database, id, &msg); + if (k < 0) { + log_full_errno(k == -ENOENT ? LOG_NOTICE : LOG_ERR, k, + "Failed to retrieve catalog entry for '%s': %m", *item); + if (r == 0) + r = k; + continue; + } + + dump_catalog_entry(f, id, msg, oneline); + } + + return r; +} diff --git a/src/libsystemd/libsystemd-journal-internal/catalog.h b/src/libsystemd/libsystemd-journal-internal/catalog.h new file mode 100644 index 0000000000..b621de3068 --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/catalog.h @@ -0,0 +1,36 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 + +#include "hashmap.h" +#include "strbuf.h" + +int catalog_import_file(Hashmap *h, const char *path); +int catalog_update(const char* database, const char* root, const char* const* dirs); +int catalog_get(const char* database, sd_id128_t id, char **data); +int catalog_list(FILE *f, const char* database, bool oneline); +int catalog_list_items(FILE *f, const char* database, bool oneline, char **items); +int catalog_file_lang(const char *filename, char **lang); +extern const char * const catalog_file_dirs[]; +extern const struct hash_ops catalog_hash_ops; diff --git a/src/libsystemd/libsystemd-journal-internal/compress.c b/src/libsystemd/libsystemd-journal-internal/compress.c new file mode 100644 index 0000000000..ba734b5561 --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/compress.c @@ -0,0 +1,683 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include +#include +#include + +#ifdef HAVE_XZ +#include +#endif + +#ifdef HAVE_LZ4 +#include +#include +#endif + +#include "alloc-util.h" +#include "compress.h" +#include "fd-util.h" +#include "io-util.h" +#include "journal-def.h" +#include "macro.h" +#include "sparse-endian.h" +#include "string-table.h" +#include "string-util.h" +#include "util.h" + +#ifdef HAVE_LZ4 +DEFINE_TRIVIAL_CLEANUP_FUNC(LZ4F_compressionContext_t, LZ4F_freeCompressionContext); +DEFINE_TRIVIAL_CLEANUP_FUNC(LZ4F_decompressionContext_t, LZ4F_freeDecompressionContext); +#endif + +#define ALIGN_8(l) ALIGN_TO(l, sizeof(size_t)) + +static const char* const object_compressed_table[_OBJECT_COMPRESSED_MAX] = { + [OBJECT_COMPRESSED_XZ] = "XZ", + [OBJECT_COMPRESSED_LZ4] = "LZ4", +}; + +DEFINE_STRING_TABLE_LOOKUP(object_compressed, int); + +int compress_blob_xz(const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size) { +#ifdef HAVE_XZ + static const lzma_options_lzma opt = { + 1u << 20u, NULL, 0, LZMA_LC_DEFAULT, LZMA_LP_DEFAULT, + LZMA_PB_DEFAULT, LZMA_MODE_FAST, 128, LZMA_MF_HC3, 4 + }; + static const lzma_filter filters[] = { + { LZMA_FILTER_LZMA2, (lzma_options_lzma*) &opt }, + { LZMA_VLI_UNKNOWN, NULL } + }; + lzma_ret ret; + size_t out_pos = 0; + + assert(src); + assert(src_size > 0); + assert(dst); + assert(dst_alloc_size > 0); + assert(dst_size); + + /* Returns < 0 if we couldn't compress the data or the + * compressed result is longer than the original */ + + if (src_size < 80) + return -ENOBUFS; + + ret = lzma_stream_buffer_encode((lzma_filter*) filters, LZMA_CHECK_NONE, NULL, + src, src_size, dst, &out_pos, dst_alloc_size); + if (ret != LZMA_OK) + return -ENOBUFS; + + *dst_size = out_pos; + return 0; +#else + return -EPROTONOSUPPORT; +#endif +} + +int compress_blob_lz4(const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size) { +#ifdef HAVE_LZ4 + int r; + + assert(src); + assert(src_size > 0); + assert(dst); + assert(dst_alloc_size > 0); + assert(dst_size); + + /* Returns < 0 if we couldn't compress the data or the + * compressed result is longer than the original */ + + if (src_size < 9) + return -ENOBUFS; + + r = LZ4_compress_limitedOutput(src, (char*)dst + 8, src_size, (int) dst_alloc_size - 8); + if (r <= 0) + return -ENOBUFS; + + *(le64_t*) dst = htole64(src_size); + *dst_size = r + 8; + + return 0; +#else + return -EPROTONOSUPPORT; +#endif +} + + +int decompress_blob_xz(const void *src, uint64_t src_size, + void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max) { + +#ifdef HAVE_XZ + _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT; + lzma_ret ret; + size_t space; + + assert(src); + assert(src_size > 0); + assert(dst); + assert(dst_alloc_size); + assert(dst_size); + assert(*dst_alloc_size == 0 || *dst); + + ret = lzma_stream_decoder(&s, UINT64_MAX, 0); + if (ret != LZMA_OK) + return -ENOMEM; + + space = MIN(src_size * 2, dst_max ?: (size_t) -1); + if (!greedy_realloc(dst, dst_alloc_size, space, 1)) + return -ENOMEM; + + s.next_in = src; + s.avail_in = src_size; + + s.next_out = *dst; + s.avail_out = space; + + for (;;) { + size_t used; + + ret = lzma_code(&s, LZMA_FINISH); + + if (ret == LZMA_STREAM_END) + break; + else if (ret != LZMA_OK) + return -ENOMEM; + + if (dst_max > 0 && (space - s.avail_out) >= dst_max) + break; + else if (dst_max > 0 && space == dst_max) + return -ENOBUFS; + + used = space - s.avail_out; + space = MIN(2 * space, dst_max ?: (size_t) -1); + if (!greedy_realloc(dst, dst_alloc_size, space, 1)) + return -ENOMEM; + + s.avail_out = space - used; + s.next_out = *(uint8_t**)dst + used; + } + + *dst_size = space - s.avail_out; + return 0; +#else + return -EPROTONOSUPPORT; +#endif +} + +int decompress_blob_lz4(const void *src, uint64_t src_size, + void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max) { + +#ifdef HAVE_LZ4 + char* out; + int r, size; /* LZ4 uses int for size */ + + assert(src); + assert(src_size > 0); + assert(dst); + assert(dst_alloc_size); + assert(dst_size); + assert(*dst_alloc_size == 0 || *dst); + + if (src_size <= 8) + return -EBADMSG; + + size = le64toh( *(le64_t*)src ); + if (size < 0 || (unsigned) size != le64toh(*(le64_t*)src)) + return -EFBIG; + if ((size_t) size > *dst_alloc_size) { + out = realloc(*dst, size); + if (!out) + return -ENOMEM; + *dst = out; + *dst_alloc_size = size; + } else + out = *dst; + + r = LZ4_decompress_safe((char*)src + 8, out, src_size - 8, size); + if (r < 0 || r != size) + return -EBADMSG; + + *dst_size = size; + return 0; +#else + return -EPROTONOSUPPORT; +#endif +} + +int decompress_blob(int compression, + const void *src, uint64_t src_size, + void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max) { + if (compression == OBJECT_COMPRESSED_XZ) + return decompress_blob_xz(src, src_size, + dst, dst_alloc_size, dst_size, dst_max); + else if (compression == OBJECT_COMPRESSED_LZ4) + return decompress_blob_lz4(src, src_size, + dst, dst_alloc_size, dst_size, dst_max); + else + return -EBADMSG; +} + + +int decompress_startswith_xz(const void *src, uint64_t src_size, + void **buffer, size_t *buffer_size, + const void *prefix, size_t prefix_len, + uint8_t extra) { + +#ifdef HAVE_XZ + _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT; + lzma_ret ret; + + /* Checks whether the decompressed blob starts with the + * mentioned prefix. The byte extra needs to follow the + * prefix */ + + assert(src); + assert(src_size > 0); + assert(buffer); + assert(buffer_size); + assert(prefix); + assert(*buffer_size == 0 || *buffer); + + ret = lzma_stream_decoder(&s, UINT64_MAX, 0); + if (ret != LZMA_OK) + return -EBADMSG; + + if (!(greedy_realloc(buffer, buffer_size, ALIGN_8(prefix_len + 1), 1))) + return -ENOMEM; + + s.next_in = src; + s.avail_in = src_size; + + s.next_out = *buffer; + s.avail_out = *buffer_size; + + for (;;) { + ret = lzma_code(&s, LZMA_FINISH); + + if (ret != LZMA_STREAM_END && ret != LZMA_OK) + return -EBADMSG; + + if (*buffer_size - s.avail_out >= prefix_len + 1) + return memcmp(*buffer, prefix, prefix_len) == 0 && + ((const uint8_t*) *buffer)[prefix_len] == extra; + + if (ret == LZMA_STREAM_END) + return 0; + + s.avail_out += *buffer_size; + + if (!(greedy_realloc(buffer, buffer_size, *buffer_size * 2, 1))) + return -ENOMEM; + + s.next_out = *(uint8_t**)buffer + *buffer_size - s.avail_out; + } + +#else + return -EPROTONOSUPPORT; +#endif +} + +int decompress_startswith_lz4(const void *src, uint64_t src_size, + void **buffer, size_t *buffer_size, + const void *prefix, size_t prefix_len, + uint8_t extra) { +#ifdef HAVE_LZ4 + /* Checks whether the decompressed blob starts with the + * mentioned prefix. The byte extra needs to follow the + * prefix */ + + int r; + size_t size; + + assert(src); + assert(src_size > 0); + assert(buffer); + assert(buffer_size); + assert(prefix); + assert(*buffer_size == 0 || *buffer); + + if (src_size <= 8) + return -EBADMSG; + + if (!(greedy_realloc(buffer, buffer_size, ALIGN_8(prefix_len + 1), 1))) + return -ENOMEM; + + r = LZ4_decompress_safe_partial((char*)src + 8, *buffer, src_size - 8, + prefix_len + 1, *buffer_size); + if (r >= 0) + size = (unsigned) r; + else { + /* lz4 always tries to decode full "sequence", so in + * pathological cases might need to decompress the + * full field. */ + r = decompress_blob_lz4(src, src_size, buffer, buffer_size, &size, 0); + if (r < 0) + return r; + } + + if (size >= prefix_len + 1) + return memcmp(*buffer, prefix, prefix_len) == 0 && + ((const uint8_t*) *buffer)[prefix_len] == extra; + else + return 0; + +#else + return -EPROTONOSUPPORT; +#endif +} + +int decompress_startswith(int compression, + const void *src, uint64_t src_size, + void **buffer, size_t *buffer_size, + const void *prefix, size_t prefix_len, + uint8_t extra) { + if (compression == OBJECT_COMPRESSED_XZ) + return decompress_startswith_xz(src, src_size, + buffer, buffer_size, + prefix, prefix_len, + extra); + else if (compression == OBJECT_COMPRESSED_LZ4) + return decompress_startswith_lz4(src, src_size, + buffer, buffer_size, + prefix, prefix_len, + extra); + else + return -EBADMSG; +} + +int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes) { +#ifdef HAVE_XZ + _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT; + lzma_ret ret; + uint8_t buf[BUFSIZ], out[BUFSIZ]; + lzma_action action = LZMA_RUN; + + assert(fdf >= 0); + assert(fdt >= 0); + + ret = lzma_easy_encoder(&s, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64); + if (ret != LZMA_OK) { + log_error("Failed to initialize XZ encoder: code %u", ret); + return -EINVAL; + } + + for (;;) { + if (s.avail_in == 0 && action == LZMA_RUN) { + size_t m = sizeof(buf); + ssize_t n; + + if (max_bytes != (uint64_t) -1 && (uint64_t) m > max_bytes) + m = (size_t) max_bytes; + + n = read(fdf, buf, m); + if (n < 0) + return -errno; + if (n == 0) + action = LZMA_FINISH; + else { + s.next_in = buf; + s.avail_in = n; + + if (max_bytes != (uint64_t) -1) { + assert(max_bytes >= (uint64_t) n); + max_bytes -= n; + } + } + } + + if (s.avail_out == 0) { + s.next_out = out; + s.avail_out = sizeof(out); + } + + ret = lzma_code(&s, action); + if (ret != LZMA_OK && ret != LZMA_STREAM_END) { + log_error("Compression failed: code %u", ret); + return -EBADMSG; + } + + if (s.avail_out == 0 || ret == LZMA_STREAM_END) { + ssize_t n, k; + + n = sizeof(out) - s.avail_out; + + k = loop_write(fdt, out, n, false); + if (k < 0) + return k; + + if (ret == LZMA_STREAM_END) { + log_debug("XZ compression finished (%"PRIu64" -> %"PRIu64" bytes, %.1f%%)", + s.total_in, s.total_out, + (double) s.total_out / s.total_in * 100); + + return 0; + } + } + } +#else + return -EPROTONOSUPPORT; +#endif +} + +#define LZ4_BUFSIZE (512*1024u) + +int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) { + +#ifdef HAVE_LZ4 + LZ4F_errorCode_t c; + _cleanup_(LZ4F_freeCompressionContextp) LZ4F_compressionContext_t ctx = NULL; + _cleanup_free_ char *buf = NULL; + char *src = NULL; + size_t size, n, total_in = 0, total_out, offset = 0, frame_size; + struct stat st; + int r; + static const LZ4F_compressOptions_t options = { + .stableSrc = 1, + }; + static const LZ4F_preferences_t preferences = { + .frameInfo.blockSizeID = 5, + }; + + c = LZ4F_createCompressionContext(&ctx, LZ4F_VERSION); + if (LZ4F_isError(c)) + return -ENOMEM; + + if (fstat(fdf, &st) < 0) + return log_debug_errno(errno, "fstat() failed: %m"); + + frame_size = LZ4F_compressBound(LZ4_BUFSIZE, &preferences); + size = frame_size + 64*1024; /* add some space for header and trailer */ + buf = malloc(size); + if (!buf) + return -ENOMEM; + + n = offset = total_out = LZ4F_compressBegin(ctx, buf, size, &preferences); + if (LZ4F_isError(n)) + return -EINVAL; + + src = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fdf, 0); + if (src == MAP_FAILED) + return -errno; + + log_debug("Buffer size is %zu bytes, header size %zu bytes.", size, n); + + while (total_in < (size_t) st.st_size) { + ssize_t k; + + k = MIN(LZ4_BUFSIZE, st.st_size - total_in); + n = LZ4F_compressUpdate(ctx, buf + offset, size - offset, + src + total_in, k, &options); + if (LZ4F_isError(n)) { + r = -ENOTRECOVERABLE; + goto cleanup; + } + + total_in += k; + offset += n; + total_out += n; + + if (max_bytes != (uint64_t) -1 && total_out > (size_t) max_bytes) { + log_debug("Compressed stream longer than %"PRIu64" bytes", max_bytes); + return -EFBIG; + } + + if (size - offset < frame_size + 4) { + k = loop_write(fdt, buf, offset, false); + if (k < 0) { + r = k; + goto cleanup; + } + offset = 0; + } + } + + n = LZ4F_compressEnd(ctx, buf + offset, size - offset, &options); + if (LZ4F_isError(n)) { + r = -ENOTRECOVERABLE; + goto cleanup; + } + + offset += n; + total_out += n; + r = loop_write(fdt, buf, offset, false); + if (r < 0) + goto cleanup; + + log_debug("LZ4 compression finished (%zu -> %zu bytes, %.1f%%)", + total_in, total_out, + (double) total_out / total_in * 100); + cleanup: + munmap(src, st.st_size); + return r; +#else + return -EPROTONOSUPPORT; +#endif +} + +int decompress_stream_xz(int fdf, int fdt, uint64_t max_bytes) { + +#ifdef HAVE_XZ + _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT; + lzma_ret ret; + + uint8_t buf[BUFSIZ], out[BUFSIZ]; + lzma_action action = LZMA_RUN; + + assert(fdf >= 0); + assert(fdt >= 0); + + ret = lzma_stream_decoder(&s, UINT64_MAX, 0); + if (ret != LZMA_OK) { + log_debug("Failed to initialize XZ decoder: code %u", ret); + return -ENOMEM; + } + + for (;;) { + if (s.avail_in == 0 && action == LZMA_RUN) { + ssize_t n; + + n = read(fdf, buf, sizeof(buf)); + if (n < 0) + return -errno; + if (n == 0) + action = LZMA_FINISH; + else { + s.next_in = buf; + s.avail_in = n; + } + } + + if (s.avail_out == 0) { + s.next_out = out; + s.avail_out = sizeof(out); + } + + ret = lzma_code(&s, action); + if (ret != LZMA_OK && ret != LZMA_STREAM_END) { + log_debug("Decompression failed: code %u", ret); + return -EBADMSG; + } + + if (s.avail_out == 0 || ret == LZMA_STREAM_END) { + ssize_t n, k; + + n = sizeof(out) - s.avail_out; + + if (max_bytes != (uint64_t) -1) { + if (max_bytes < (uint64_t) n) + return -EFBIG; + + max_bytes -= n; + } + + k = loop_write(fdt, out, n, false); + if (k < 0) + return k; + + if (ret == LZMA_STREAM_END) { + log_debug("XZ decompression finished (%"PRIu64" -> %"PRIu64" bytes, %.1f%%)", + s.total_in, s.total_out, + (double) s.total_out / s.total_in * 100); + + return 0; + } + } + } +#else + log_debug("Cannot decompress file. Compiled without XZ support."); + return -EPROTONOSUPPORT; +#endif +} + +int decompress_stream_lz4(int in, int out, uint64_t max_bytes) { +#ifdef HAVE_LZ4 + size_t c; + _cleanup_(LZ4F_freeDecompressionContextp) LZ4F_decompressionContext_t ctx = NULL; + _cleanup_free_ char *buf = NULL; + char *src; + struct stat st; + int r = 0; + size_t total_in = 0, total_out = 0; + + c = LZ4F_createDecompressionContext(&ctx, LZ4F_VERSION); + if (LZ4F_isError(c)) + return -ENOMEM; + + if (fstat(in, &st) < 0) + return log_debug_errno(errno, "fstat() failed: %m"); + + buf = malloc(LZ4_BUFSIZE); + if (!buf) + return -ENOMEM; + + src = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, in, 0); + if (src == MAP_FAILED) + return -errno; + + while (total_in < (size_t) st.st_size) { + size_t produced = LZ4_BUFSIZE; + size_t used = st.st_size - total_in; + + c = LZ4F_decompress(ctx, buf, &produced, src + total_in, &used, NULL); + if (LZ4F_isError(c)) { + r = -EBADMSG; + goto cleanup; + } + + total_in += used; + total_out += produced; + + if (max_bytes != (uint64_t) -1 && total_out > (size_t) max_bytes) { + log_debug("Decompressed stream longer than %"PRIu64" bytes", max_bytes); + r = -EFBIG; + goto cleanup; + } + + r = loop_write(out, buf, produced, false); + if (r < 0) + goto cleanup; + } + + log_debug("LZ4 decompression finished (%zu -> %zu bytes, %.1f%%)", + total_in, total_out, + (double) total_out / total_in * 100); + cleanup: + munmap(src, st.st_size); + return r; +#else + log_debug("Cannot decompress file. Compiled without LZ4 support."); + return -EPROTONOSUPPORT; +#endif +} + +int decompress_stream(const char *filename, int fdf, int fdt, uint64_t max_bytes) { + + if (endswith(filename, ".lz4")) + return decompress_stream_lz4(fdf, fdt, max_bytes); + else if (endswith(filename, ".xz")) + return decompress_stream_xz(fdf, fdt, max_bytes); + else + return -EPROTONOSUPPORT; +} diff --git a/src/libsystemd/libsystemd-journal-internal/compress.h b/src/libsystemd/libsystemd-journal-internal/compress.h new file mode 100644 index 0000000000..c138099d9a --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/compress.h @@ -0,0 +1,85 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 "journal-def.h" + +const char* object_compressed_to_string(int compression); +int object_compressed_from_string(const char *compression); + +int compress_blob_xz(const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size); +int compress_blob_lz4(const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size); + +static inline int compress_blob(const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size) { + int r; +#ifdef HAVE_LZ4 + r = compress_blob_lz4(src, src_size, dst, dst_alloc_size, dst_size); + if (r == 0) + return OBJECT_COMPRESSED_LZ4; +#else + r = compress_blob_xz(src, src_size, dst, dst_alloc_size, dst_size); + if (r == 0) + return OBJECT_COMPRESSED_XZ; +#endif + return r; +} + +int decompress_blob_xz(const void *src, uint64_t src_size, + void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max); +int decompress_blob_lz4(const void *src, uint64_t src_size, + void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max); +int decompress_blob(int compression, + const void *src, uint64_t src_size, + void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max); + +int decompress_startswith_xz(const void *src, uint64_t src_size, + void **buffer, size_t *buffer_size, + const void *prefix, size_t prefix_len, + uint8_t extra); +int decompress_startswith_lz4(const void *src, uint64_t src_size, + void **buffer, size_t *buffer_size, + const void *prefix, size_t prefix_len, + uint8_t extra); +int decompress_startswith(int compression, + const void *src, uint64_t src_size, + void **buffer, size_t *buffer_size, + const void *prefix, size_t prefix_len, + uint8_t extra); + +int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes); +int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes); + +int decompress_stream_xz(int fdf, int fdt, uint64_t max_size); +int decompress_stream_lz4(int fdf, int fdt, uint64_t max_size); + +#ifdef HAVE_LZ4 +# define compress_stream compress_stream_lz4 +# define COMPRESSED_EXT ".lz4" +#else +# define compress_stream compress_stream_xz +# define COMPRESSED_EXT ".xz" +#endif + +int decompress_stream(const char *filename, int fdf, int fdt, uint64_t max_bytes); diff --git a/src/libsystemd/libsystemd-journal-internal/fsprg.c b/src/libsystemd/libsystemd-journal-internal/fsprg.c new file mode 100644 index 0000000000..612b10f3a9 --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/fsprg.c @@ -0,0 +1,374 @@ +/* + * fsprg v0.1 - (seekable) forward-secure pseudorandom generator + * Copyright (C) 2012 B. Poettering + * Contact: fsprg@point-at-infinity.org + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +/* + * See "Practical Secure Logging: Seekable Sequential Key Generators" + * by G. A. Marson, B. Poettering for details: + * + * http://eprint.iacr.org/2013/397 + */ + +#include +#include + +#include "fsprg.h" +#include "gcrypt-util.h" + +#define ISVALID_SECPAR(secpar) (((secpar) % 16 == 0) && ((secpar) >= 16) && ((secpar) <= 16384)) +#define VALIDATE_SECPAR(secpar) assert(ISVALID_SECPAR(secpar)); + +#define RND_HASH GCRY_MD_SHA256 +#define RND_GEN_P 0x01 +#define RND_GEN_Q 0x02 +#define RND_GEN_X 0x03 + +/******************************************************************************/ + +static void mpi_export(void *buf, size_t buflen, const gcry_mpi_t x) { + unsigned len; + size_t nwritten; + + assert(gcry_mpi_cmp_ui(x, 0) >= 0); + len = (gcry_mpi_get_nbits(x) + 7) / 8; + assert(len <= buflen); + memzero(buf, buflen); + gcry_mpi_print(GCRYMPI_FMT_USG, buf + (buflen - len), len, &nwritten, x); + assert(nwritten == len); +} + +static gcry_mpi_t mpi_import(const void *buf, size_t buflen) { + gcry_mpi_t h; + unsigned len; + + assert_se(gcry_mpi_scan(&h, GCRYMPI_FMT_USG, buf, buflen, NULL) == 0); + len = (gcry_mpi_get_nbits(h) + 7) / 8; + assert(len <= buflen); + assert(gcry_mpi_cmp_ui(h, 0) >= 0); + + return h; +} + +static void uint64_export(void *buf, size_t buflen, uint64_t x) { + assert(buflen == 8); + ((uint8_t*) buf)[0] = (x >> 56) & 0xff; + ((uint8_t*) buf)[1] = (x >> 48) & 0xff; + ((uint8_t*) buf)[2] = (x >> 40) & 0xff; + ((uint8_t*) buf)[3] = (x >> 32) & 0xff; + ((uint8_t*) buf)[4] = (x >> 24) & 0xff; + ((uint8_t*) buf)[5] = (x >> 16) & 0xff; + ((uint8_t*) buf)[6] = (x >> 8) & 0xff; + ((uint8_t*) buf)[7] = (x >> 0) & 0xff; +} + +_pure_ static uint64_t uint64_import(const void *buf, size_t buflen) { + assert(buflen == 8); + return + (uint64_t)(((uint8_t*) buf)[0]) << 56 | + (uint64_t)(((uint8_t*) buf)[1]) << 48 | + (uint64_t)(((uint8_t*) buf)[2]) << 40 | + (uint64_t)(((uint8_t*) buf)[3]) << 32 | + (uint64_t)(((uint8_t*) buf)[4]) << 24 | + (uint64_t)(((uint8_t*) buf)[5]) << 16 | + (uint64_t)(((uint8_t*) buf)[6]) << 8 | + (uint64_t)(((uint8_t*) buf)[7]) << 0; +} + +/* deterministically generate from seed/idx a string of buflen pseudorandom bytes */ +static void det_randomize(void *buf, size_t buflen, const void *seed, size_t seedlen, uint32_t idx) { + gcry_md_hd_t hd, hd2; + size_t olen, cpylen; + uint32_t ctr; + + olen = gcry_md_get_algo_dlen(RND_HASH); + gcry_md_open(&hd, RND_HASH, 0); + gcry_md_write(hd, seed, seedlen); + gcry_md_putc(hd, (idx >> 24) & 0xff); + gcry_md_putc(hd, (idx >> 16) & 0xff); + gcry_md_putc(hd, (idx >> 8) & 0xff); + gcry_md_putc(hd, (idx >> 0) & 0xff); + + for (ctr = 0; buflen; ctr++) { + gcry_md_copy(&hd2, hd); + gcry_md_putc(hd2, (ctr >> 24) & 0xff); + gcry_md_putc(hd2, (ctr >> 16) & 0xff); + gcry_md_putc(hd2, (ctr >> 8) & 0xff); + gcry_md_putc(hd2, (ctr >> 0) & 0xff); + gcry_md_final(hd2); + cpylen = (buflen < olen) ? buflen : olen; + memcpy(buf, gcry_md_read(hd2, RND_HASH), cpylen); + gcry_md_close(hd2); + buf += cpylen; + buflen -= cpylen; + } + gcry_md_close(hd); +} + +/* deterministically generate from seed/idx a prime of length `bits' that is 3 (mod 4) */ +static gcry_mpi_t genprime3mod4(int bits, const void *seed, size_t seedlen, uint32_t idx) { + size_t buflen = bits / 8; + uint8_t buf[buflen]; + gcry_mpi_t p; + + assert(bits % 8 == 0); + assert(buflen > 0); + + det_randomize(buf, buflen, seed, seedlen, idx); + buf[0] |= 0xc0; /* set upper two bits, so that n=pq has maximum size */ + buf[buflen - 1] |= 0x03; /* set lower two bits, to have result 3 (mod 4) */ + + p = mpi_import(buf, buflen); + while (gcry_prime_check(p, 0)) + gcry_mpi_add_ui(p, p, 4); + + return p; +} + +/* deterministically generate from seed/idx a quadratic residue (mod n) */ +static gcry_mpi_t gensquare(const gcry_mpi_t n, const void *seed, size_t seedlen, uint32_t idx, unsigned secpar) { + size_t buflen = secpar / 8; + uint8_t buf[buflen]; + gcry_mpi_t x; + + det_randomize(buf, buflen, seed, seedlen, idx); + buf[0] &= 0x7f; /* clear upper bit, so that we have x < n */ + x = mpi_import(buf, buflen); + assert(gcry_mpi_cmp(x, n) < 0); + gcry_mpi_mulm(x, x, x, n); + return x; +} + +/* compute 2^m (mod phi(p)), for a prime p */ +static gcry_mpi_t twopowmodphi(uint64_t m, const gcry_mpi_t p) { + gcry_mpi_t phi, r; + int n; + + phi = gcry_mpi_new(0); + gcry_mpi_sub_ui(phi, p, 1); + + /* count number of used bits in m */ + for (n = 0; (1ULL << n) <= m; n++) + ; + + r = gcry_mpi_new(0); + gcry_mpi_set_ui(r, 1); + while (n) { /* square and multiply algorithm for fast exponentiation */ + n--; + gcry_mpi_mulm(r, r, r, phi); + if (m & ((uint64_t)1 << n)) { + gcry_mpi_add(r, r, r); + if (gcry_mpi_cmp(r, phi) >= 0) + gcry_mpi_sub(r, r, phi); + } + } + + gcry_mpi_release(phi); + return r; +} + +/* Decompose $x \in Z_n$ into $(xp,xq) \in Z_p \times Z_q$ using Chinese Remainder Theorem */ +static void CRT_decompose(gcry_mpi_t *xp, gcry_mpi_t *xq, const gcry_mpi_t x, const gcry_mpi_t p, const gcry_mpi_t q) { + *xp = gcry_mpi_new(0); + *xq = gcry_mpi_new(0); + gcry_mpi_mod(*xp, x, p); + gcry_mpi_mod(*xq, x, q); +} + +/* Compose $(xp,xq) \in Z_p \times Z_q$ into $x \in Z_n$ using Chinese Remainder Theorem */ +static void CRT_compose(gcry_mpi_t *x, const gcry_mpi_t xp, const gcry_mpi_t xq, const gcry_mpi_t p, const gcry_mpi_t q) { + gcry_mpi_t a, u; + + a = gcry_mpi_new(0); + u = gcry_mpi_new(0); + *x = gcry_mpi_new(0); + gcry_mpi_subm(a, xq, xp, q); + gcry_mpi_invm(u, p, q); + gcry_mpi_mulm(a, a, u, q); /* a = (xq - xp) / p (mod q) */ + gcry_mpi_mul(*x, p, a); + gcry_mpi_add(*x, *x, xp); /* x = p * ((xq - xp) / p mod q) + xp */ + gcry_mpi_release(a); + gcry_mpi_release(u); +} + +/******************************************************************************/ + +size_t FSPRG_mskinbytes(unsigned _secpar) { + VALIDATE_SECPAR(_secpar); + return 2 + 2 * (_secpar / 2) / 8; /* to store header,p,q */ +} + +size_t FSPRG_mpkinbytes(unsigned _secpar) { + VALIDATE_SECPAR(_secpar); + return 2 + _secpar / 8; /* to store header,n */ +} + +size_t FSPRG_stateinbytes(unsigned _secpar) { + VALIDATE_SECPAR(_secpar); + return 2 + 2 * _secpar / 8 + 8; /* to store header,n,x,epoch */ +} + +static void store_secpar(void *buf, uint16_t secpar) { + secpar = secpar / 16 - 1; + ((uint8_t*) buf)[0] = (secpar >> 8) & 0xff; + ((uint8_t*) buf)[1] = (secpar >> 0) & 0xff; +} + +static uint16_t read_secpar(const void *buf) { + uint16_t secpar; + secpar = + (uint16_t)(((uint8_t*) buf)[0]) << 8 | + (uint16_t)(((uint8_t*) buf)[1]) << 0; + return 16 * (secpar + 1); +} + +void FSPRG_GenMK(void *msk, void *mpk, const void *seed, size_t seedlen, unsigned _secpar) { + uint8_t iseed[FSPRG_RECOMMENDED_SEEDLEN]; + gcry_mpi_t n, p, q; + uint16_t secpar; + + VALIDATE_SECPAR(_secpar); + secpar = _secpar; + + initialize_libgcrypt(false); + + if (!seed) { + gcry_randomize(iseed, FSPRG_RECOMMENDED_SEEDLEN, GCRY_STRONG_RANDOM); + seed = iseed; + seedlen = FSPRG_RECOMMENDED_SEEDLEN; + } + + p = genprime3mod4(secpar / 2, seed, seedlen, RND_GEN_P); + q = genprime3mod4(secpar / 2, seed, seedlen, RND_GEN_Q); + + if (msk) { + store_secpar(msk + 0, secpar); + mpi_export(msk + 2 + 0 * (secpar / 2) / 8, (secpar / 2) / 8, p); + mpi_export(msk + 2 + 1 * (secpar / 2) / 8, (secpar / 2) / 8, q); + } + + if (mpk) { + n = gcry_mpi_new(0); + gcry_mpi_mul(n, p, q); + assert(gcry_mpi_get_nbits(n) == secpar); + + store_secpar(mpk + 0, secpar); + mpi_export(mpk + 2, secpar / 8, n); + + gcry_mpi_release(n); + } + + gcry_mpi_release(p); + gcry_mpi_release(q); +} + +void FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seedlen) { + gcry_mpi_t n, x; + uint16_t secpar; + + initialize_libgcrypt(false); + + secpar = read_secpar(mpk + 0); + n = mpi_import(mpk + 2, secpar / 8); + x = gensquare(n, seed, seedlen, RND_GEN_X, secpar); + + memcpy(state, mpk, 2 + secpar / 8); + mpi_export(state + 2 + 1 * secpar / 8, secpar / 8, x); + memzero(state + 2 + 2 * secpar / 8, 8); + + gcry_mpi_release(n); + gcry_mpi_release(x); +} + +void FSPRG_Evolve(void *state) { + gcry_mpi_t n, x; + uint16_t secpar; + uint64_t epoch; + + initialize_libgcrypt(false); + + secpar = read_secpar(state + 0); + n = mpi_import(state + 2 + 0 * secpar / 8, secpar / 8); + x = mpi_import(state + 2 + 1 * secpar / 8, secpar / 8); + epoch = uint64_import(state + 2 + 2 * secpar / 8, 8); + + gcry_mpi_mulm(x, x, x, n); + epoch++; + + mpi_export(state + 2 + 1 * secpar / 8, secpar / 8, x); + uint64_export(state + 2 + 2 * secpar / 8, 8, epoch); + + gcry_mpi_release(n); + gcry_mpi_release(x); +} + +uint64_t FSPRG_GetEpoch(const void *state) { + uint16_t secpar; + secpar = read_secpar(state + 0); + return uint64_import(state + 2 + 2 * secpar / 8, 8); +} + +void FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, size_t seedlen) { + gcry_mpi_t p, q, n, x, xp, xq, kp, kq, xm; + uint16_t secpar; + + initialize_libgcrypt(false); + + secpar = read_secpar(msk + 0); + p = mpi_import(msk + 2 + 0 * (secpar / 2) / 8, (secpar / 2) / 8); + q = mpi_import(msk + 2 + 1 * (secpar / 2) / 8, (secpar / 2) / 8); + + n = gcry_mpi_new(0); + gcry_mpi_mul(n, p, q); + + x = gensquare(n, seed, seedlen, RND_GEN_X, secpar); + CRT_decompose(&xp, &xq, x, p, q); /* split (mod n) into (mod p) and (mod q) using CRT */ + + kp = twopowmodphi(epoch, p); /* compute 2^epoch (mod phi(p)) */ + kq = twopowmodphi(epoch, q); /* compute 2^epoch (mod phi(q)) */ + + gcry_mpi_powm(xp, xp, kp, p); /* compute x^(2^epoch) (mod p) */ + gcry_mpi_powm(xq, xq, kq, q); /* compute x^(2^epoch) (mod q) */ + + CRT_compose(&xm, xp, xq, p, q); /* combine (mod p) and (mod q) to (mod n) using CRT */ + + store_secpar(state + 0, secpar); + mpi_export(state + 2 + 0 * secpar / 8, secpar / 8, n); + mpi_export(state + 2 + 1 * secpar / 8, secpar / 8, xm); + uint64_export(state + 2 + 2 * secpar / 8, 8, epoch); + + gcry_mpi_release(p); + gcry_mpi_release(q); + gcry_mpi_release(n); + gcry_mpi_release(x); + gcry_mpi_release(xp); + gcry_mpi_release(xq); + gcry_mpi_release(kp); + gcry_mpi_release(kq); + gcry_mpi_release(xm); +} + +void FSPRG_GetKey(const void *state, void *key, size_t keylen, uint32_t idx) { + uint16_t secpar; + + initialize_libgcrypt(false); + + secpar = read_secpar(state + 0); + det_randomize(key, keylen, state + 2, 2 * secpar / 8 + 8, idx); +} diff --git a/src/libsystemd/libsystemd-journal-internal/fsprg.h b/src/libsystemd/libsystemd-journal-internal/fsprg.h new file mode 100644 index 0000000000..829b56e240 --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/fsprg.h @@ -0,0 +1,65 @@ +#ifndef __fsprgh__ +#define __fsprgh__ + +/* + * fsprg v0.1 - (seekable) forward-secure pseudorandom generator + * Copyright (C) 2012 B. Poettering + * Contact: fsprg@point-at-infinity.org + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include + +#include "macro.h" +#include "util.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define FSPRG_RECOMMENDED_SECPAR 1536 +#define FSPRG_RECOMMENDED_SEEDLEN (96/8) + +size_t FSPRG_mskinbytes(unsigned secpar) _const_; +size_t FSPRG_mpkinbytes(unsigned secpar) _const_; +size_t FSPRG_stateinbytes(unsigned secpar) _const_; + +/* Setup msk and mpk. Providing seed != NULL makes this algorithm deterministic. */ +void FSPRG_GenMK(void *msk, void *mpk, const void *seed, size_t seedlen, unsigned secpar); + +/* Initialize state deterministically in dependence on seed. */ +/* Note: in case one wants to run only one GenState0 per GenMK it is safe to use + the same seed for both GenMK and GenState0. +*/ +void FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seedlen); + +void FSPRG_Evolve(void *state); + +uint64_t FSPRG_GetEpoch(const void *state) _pure_; + +/* Seek to any arbitrary state (by providing msk together with seed from GenState0). */ +void FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, size_t seedlen); + +void FSPRG_GetKey(const void *state, void *key, size_t keylen, uint32_t idx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libsystemd/libsystemd-journal-internal/journal-authenticate.c b/src/libsystemd/libsystemd-journal-internal/journal-authenticate.c new file mode 100644 index 0000000000..d8af113d3f --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/journal-authenticate.c @@ -0,0 +1,551 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 + +#include "fd-util.h" +#include "fsprg.h" +#include "gcrypt-util.h" +#include "hexdecoct.h" +#include "journal-authenticate.h" +#include "journal-def.h" +#include "journal-file.h" + +static uint64_t journal_file_tag_seqnum(JournalFile *f) { + uint64_t r; + + assert(f); + + r = le64toh(f->header->n_tags) + 1; + f->header->n_tags = htole64(r); + + return r; +} + +int journal_file_append_tag(JournalFile *f) { + Object *o; + uint64_t p; + int r; + + assert(f); + + if (!f->seal) + return 0; + + if (!f->hmac_running) + return 0; + + assert(f->hmac); + + r = journal_file_append_object(f, OBJECT_TAG, sizeof(struct TagObject), &o, &p); + if (r < 0) + return r; + + o->tag.seqnum = htole64(journal_file_tag_seqnum(f)); + o->tag.epoch = htole64(FSPRG_GetEpoch(f->fsprg_state)); + + log_debug("Writing tag %"PRIu64" for epoch %"PRIu64"", + le64toh(o->tag.seqnum), + FSPRG_GetEpoch(f->fsprg_state)); + + /* Add the tag object itself, so that we can protect its + * header. This will exclude the actual hash value in it */ + r = journal_file_hmac_put_object(f, OBJECT_TAG, o, p); + if (r < 0) + return r; + + /* Get the HMAC tag and store it in the object */ + memcpy(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH); + f->hmac_running = false; + + return 0; +} + +int journal_file_hmac_start(JournalFile *f) { + uint8_t key[256 / 8]; /* Let's pass 256 bit from FSPRG to HMAC */ + assert(f); + + if (!f->seal) + return 0; + + if (f->hmac_running) + return 0; + + /* Prepare HMAC for next cycle */ + gcry_md_reset(f->hmac); + FSPRG_GetKey(f->fsprg_state, key, sizeof(key), 0); + gcry_md_setkey(f->hmac, key, sizeof(key)); + + f->hmac_running = true; + + return 0; +} + +static int journal_file_get_epoch(JournalFile *f, uint64_t realtime, uint64_t *epoch) { + uint64_t t; + + assert(f); + assert(epoch); + assert(f->seal); + + if (f->fss_start_usec == 0 || + f->fss_interval_usec == 0) + return -EOPNOTSUPP; + + if (realtime < f->fss_start_usec) + return -ESTALE; + + t = realtime - f->fss_start_usec; + t = t / f->fss_interval_usec; + + *epoch = t; + return 0; +} + +static int journal_file_fsprg_need_evolve(JournalFile *f, uint64_t realtime) { + uint64_t goal, epoch; + int r; + assert(f); + + if (!f->seal) + return 0; + + r = journal_file_get_epoch(f, realtime, &goal); + if (r < 0) + return r; + + epoch = FSPRG_GetEpoch(f->fsprg_state); + if (epoch > goal) + return -ESTALE; + + return epoch != goal; +} + +int journal_file_fsprg_evolve(JournalFile *f, uint64_t realtime) { + uint64_t goal, epoch; + int r; + + assert(f); + + if (!f->seal) + return 0; + + r = journal_file_get_epoch(f, realtime, &goal); + if (r < 0) + return r; + + epoch = FSPRG_GetEpoch(f->fsprg_state); + if (epoch < goal) + log_debug("Evolving FSPRG key from epoch %"PRIu64" to %"PRIu64".", epoch, goal); + + for (;;) { + if (epoch > goal) + return -ESTALE; + if (epoch == goal) + return 0; + + FSPRG_Evolve(f->fsprg_state); + epoch = FSPRG_GetEpoch(f->fsprg_state); + } +} + +int journal_file_fsprg_seek(JournalFile *f, uint64_t goal) { + void *msk; + uint64_t epoch; + + assert(f); + + if (!f->seal) + return 0; + + assert(f->fsprg_seed); + + if (f->fsprg_state) { + /* Cheaper... */ + + epoch = FSPRG_GetEpoch(f->fsprg_state); + if (goal == epoch) + return 0; + + if (goal == epoch+1) { + FSPRG_Evolve(f->fsprg_state); + return 0; + } + } else { + f->fsprg_state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR); + f->fsprg_state = malloc(f->fsprg_state_size); + + if (!f->fsprg_state) + return -ENOMEM; + } + + log_debug("Seeking FSPRG key to %"PRIu64".", goal); + + msk = alloca(FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR)); + FSPRG_GenMK(msk, NULL, f->fsprg_seed, f->fsprg_seed_size, FSPRG_RECOMMENDED_SECPAR); + FSPRG_Seek(f->fsprg_state, goal, msk, f->fsprg_seed, f->fsprg_seed_size); + return 0; +} + +int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime) { + int r; + + assert(f); + + if (!f->seal) + return 0; + + if (realtime <= 0) + realtime = now(CLOCK_REALTIME); + + r = journal_file_fsprg_need_evolve(f, realtime); + if (r <= 0) + return 0; + + r = journal_file_append_tag(f); + if (r < 0) + return r; + + r = journal_file_fsprg_evolve(f, realtime); + if (r < 0) + return r; + + return 0; +} + +int journal_file_hmac_put_object(JournalFile *f, ObjectType type, Object *o, uint64_t p) { + int r; + + assert(f); + + if (!f->seal) + return 0; + + r = journal_file_hmac_start(f); + if (r < 0) + return r; + + if (!o) { + r = journal_file_move_to_object(f, type, p, &o); + if (r < 0) + return r; + } else { + if (type > OBJECT_UNUSED && o->object.type != type) + return -EBADMSG; + } + + gcry_md_write(f->hmac, o, offsetof(ObjectHeader, payload)); + + switch (o->object.type) { + + case OBJECT_DATA: + /* All but hash and payload are mutable */ + gcry_md_write(f->hmac, &o->data.hash, sizeof(o->data.hash)); + gcry_md_write(f->hmac, o->data.payload, le64toh(o->object.size) - offsetof(DataObject, payload)); + break; + + case OBJECT_FIELD: + /* Same here */ + gcry_md_write(f->hmac, &o->field.hash, sizeof(o->field.hash)); + gcry_md_write(f->hmac, o->field.payload, le64toh(o->object.size) - offsetof(FieldObject, payload)); + break; + + case OBJECT_ENTRY: + /* All */ + gcry_md_write(f->hmac, &o->entry.seqnum, le64toh(o->object.size) - offsetof(EntryObject, seqnum)); + break; + + case OBJECT_FIELD_HASH_TABLE: + case OBJECT_DATA_HASH_TABLE: + case OBJECT_ENTRY_ARRAY: + /* Nothing: everything is mutable */ + break; + + case OBJECT_TAG: + /* All but the tag itself */ + gcry_md_write(f->hmac, &o->tag.seqnum, sizeof(o->tag.seqnum)); + gcry_md_write(f->hmac, &o->tag.epoch, sizeof(o->tag.epoch)); + break; + default: + return -EINVAL; + } + + return 0; +} + +int journal_file_hmac_put_header(JournalFile *f) { + int r; + + assert(f); + + if (!f->seal) + return 0; + + r = journal_file_hmac_start(f); + if (r < 0) + return r; + + /* All but state+reserved, boot_id, arena_size, + * tail_object_offset, n_objects, n_entries, + * tail_entry_seqnum, head_entry_seqnum, entry_array_offset, + * head_entry_realtime, tail_entry_realtime, + * tail_entry_monotonic, n_data, n_fields, n_tags, + * n_entry_arrays. */ + + gcry_md_write(f->hmac, f->header->signature, offsetof(Header, state) - offsetof(Header, signature)); + gcry_md_write(f->hmac, &f->header->file_id, offsetof(Header, boot_id) - offsetof(Header, file_id)); + gcry_md_write(f->hmac, &f->header->seqnum_id, offsetof(Header, arena_size) - offsetof(Header, seqnum_id)); + gcry_md_write(f->hmac, &f->header->data_hash_table_offset, offsetof(Header, tail_object_offset) - offsetof(Header, data_hash_table_offset)); + + return 0; +} + +int journal_file_fss_load(JournalFile *f) { + int r, fd = -1; + char *p = NULL; + struct stat st; + FSSHeader *m = NULL; + sd_id128_t machine; + + assert(f); + + if (!f->seal) + return 0; + + r = sd_id128_get_machine(&machine); + if (r < 0) + return r; + + if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss", + SD_ID128_FORMAT_VAL(machine)) < 0) + return -ENOMEM; + + fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY, 0600); + if (fd < 0) { + if (errno != ENOENT) + log_error_errno(errno, "Failed to open %s: %m", p); + + r = -errno; + goto finish; + } + + if (fstat(fd, &st) < 0) { + r = -errno; + goto finish; + } + + if (st.st_size < (off_t) sizeof(FSSHeader)) { + r = -ENODATA; + goto finish; + } + + m = mmap(NULL, PAGE_ALIGN(sizeof(FSSHeader)), PROT_READ, MAP_SHARED, fd, 0); + if (m == MAP_FAILED) { + m = NULL; + r = -errno; + goto finish; + } + + if (memcmp(m->signature, FSS_HEADER_SIGNATURE, 8) != 0) { + r = -EBADMSG; + goto finish; + } + + if (m->incompatible_flags != 0) { + r = -EPROTONOSUPPORT; + goto finish; + } + + if (le64toh(m->header_size) < sizeof(FSSHeader)) { + r = -EBADMSG; + goto finish; + } + + if (le64toh(m->fsprg_state_size) != FSPRG_stateinbytes(le16toh(m->fsprg_secpar))) { + r = -EBADMSG; + goto finish; + } + + f->fss_file_size = le64toh(m->header_size) + le64toh(m->fsprg_state_size); + if ((uint64_t) st.st_size < f->fss_file_size) { + r = -ENODATA; + goto finish; + } + + if (!sd_id128_equal(machine, m->machine_id)) { + r = -EHOSTDOWN; + goto finish; + } + + if (le64toh(m->start_usec) <= 0 || + le64toh(m->interval_usec) <= 0) { + r = -EBADMSG; + goto finish; + } + + f->fss_file = mmap(NULL, PAGE_ALIGN(f->fss_file_size), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + if (f->fss_file == MAP_FAILED) { + f->fss_file = NULL; + r = -errno; + goto finish; + } + + f->fss_start_usec = le64toh(f->fss_file->start_usec); + f->fss_interval_usec = le64toh(f->fss_file->interval_usec); + + f->fsprg_state = (uint8_t*) f->fss_file + le64toh(f->fss_file->header_size); + f->fsprg_state_size = le64toh(f->fss_file->fsprg_state_size); + + r = 0; + +finish: + if (m) + munmap(m, PAGE_ALIGN(sizeof(FSSHeader))); + + safe_close(fd); + free(p); + + return r; +} + +int journal_file_hmac_setup(JournalFile *f) { + gcry_error_t e; + + if (!f->seal) + return 0; + + initialize_libgcrypt(true); + + e = gcry_md_open(&f->hmac, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC); + if (e != 0) + return -EOPNOTSUPP; + + return 0; +} + +int journal_file_append_first_tag(JournalFile *f) { + int r; + uint64_t p; + + if (!f->seal) + return 0; + + log_debug("Calculating first tag..."); + + r = journal_file_hmac_put_header(f); + if (r < 0) + return r; + + p = le64toh(f->header->field_hash_table_offset); + if (p < offsetof(Object, hash_table.items)) + return -EINVAL; + p -= offsetof(Object, hash_table.items); + + r = journal_file_hmac_put_object(f, OBJECT_FIELD_HASH_TABLE, NULL, p); + if (r < 0) + return r; + + p = le64toh(f->header->data_hash_table_offset); + if (p < offsetof(Object, hash_table.items)) + return -EINVAL; + p -= offsetof(Object, hash_table.items); + + r = journal_file_hmac_put_object(f, OBJECT_DATA_HASH_TABLE, NULL, p); + if (r < 0) + return r; + + r = journal_file_append_tag(f); + if (r < 0) + return r; + + return 0; +} + +int journal_file_parse_verification_key(JournalFile *f, const char *key) { + uint8_t *seed; + size_t seed_size, c; + const char *k; + int r; + unsigned long long start, interval; + + seed_size = FSPRG_RECOMMENDED_SEEDLEN; + seed = malloc(seed_size); + if (!seed) + return -ENOMEM; + + k = key; + for (c = 0; c < seed_size; c++) { + int x, y; + + while (*k == '-') + k++; + + x = unhexchar(*k); + if (x < 0) { + free(seed); + return -EINVAL; + } + k++; + y = unhexchar(*k); + if (y < 0) { + free(seed); + return -EINVAL; + } + k++; + + seed[c] = (uint8_t) (x * 16 + y); + } + + if (*k != '/') { + free(seed); + return -EINVAL; + } + k++; + + r = sscanf(k, "%llx-%llx", &start, &interval); + if (r != 2) { + free(seed); + return -EINVAL; + } + + f->fsprg_seed = seed; + f->fsprg_seed_size = seed_size; + + f->fss_start_usec = start * interval; + f->fss_interval_usec = interval; + + return 0; +} + +bool journal_file_next_evolve_usec(JournalFile *f, usec_t *u) { + uint64_t epoch; + + assert(f); + assert(u); + + if (!f->seal) + return false; + + epoch = FSPRG_GetEpoch(f->fsprg_state); + + *u = (usec_t) (f->fss_start_usec + f->fss_interval_usec * epoch + f->fss_interval_usec); + + return true; +} diff --git a/src/libsystemd/libsystemd-journal-internal/journal-authenticate.h b/src/libsystemd/libsystemd-journal-internal/journal-authenticate.h new file mode 100644 index 0000000000..6c87319ede --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/journal-authenticate.h @@ -0,0 +1,41 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 "journal-file.h" + +int journal_file_append_tag(JournalFile *f); +int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime); +int journal_file_append_first_tag(JournalFile *f); + +int journal_file_hmac_setup(JournalFile *f); +int journal_file_hmac_start(JournalFile *f); +int journal_file_hmac_put_header(JournalFile *f); +int journal_file_hmac_put_object(JournalFile *f, ObjectType type, Object *o, uint64_t p); + +int journal_file_fss_load(JournalFile *f); +int journal_file_parse_verification_key(JournalFile *f, const char *key); + +int journal_file_fsprg_evolve(JournalFile *f, uint64_t realtime); +int journal_file_fsprg_seek(JournalFile *f, uint64_t epoch); + +bool journal_file_next_evolve_usec(JournalFile *f, usec_t *u); diff --git a/src/libsystemd/libsystemd-journal-internal/journal-def.h b/src/libsystemd/libsystemd-journal-internal/journal-def.h new file mode 100644 index 0000000000..a0a052ce4f --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/journal-def.h @@ -0,0 +1,237 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 "macro.h" +#include "sparse-endian.h" + +/* + * If you change this file you probably should also change its documentation: + * + * http://www.freedesktop.org/wiki/Software/systemd/journal-files + * + */ + +typedef struct Header Header; + +typedef struct ObjectHeader ObjectHeader; +typedef union Object Object; + +typedef struct DataObject DataObject; +typedef struct FieldObject FieldObject; +typedef struct EntryObject EntryObject; +typedef struct HashTableObject HashTableObject; +typedef struct EntryArrayObject EntryArrayObject; +typedef struct TagObject TagObject; + +typedef struct EntryItem EntryItem; +typedef struct HashItem HashItem; + +typedef struct FSSHeader FSSHeader; + +/* Object types */ +typedef enum ObjectType { + OBJECT_UNUSED, /* also serves as "any type" or "additional context" */ + OBJECT_DATA, + OBJECT_FIELD, + OBJECT_ENTRY, + OBJECT_DATA_HASH_TABLE, + OBJECT_FIELD_HASH_TABLE, + OBJECT_ENTRY_ARRAY, + OBJECT_TAG, + _OBJECT_TYPE_MAX +} ObjectType; + +/* Object flags */ +enum { + OBJECT_COMPRESSED_XZ = 1 << 0, + OBJECT_COMPRESSED_LZ4 = 1 << 1, + _OBJECT_COMPRESSED_MAX +}; + +#define OBJECT_COMPRESSION_MASK (OBJECT_COMPRESSED_XZ | OBJECT_COMPRESSED_LZ4) + +struct ObjectHeader { + uint8_t type; + uint8_t flags; + uint8_t reserved[6]; + le64_t size; + uint8_t payload[]; +} _packed_; + +struct DataObject { + ObjectHeader object; + le64_t hash; + le64_t next_hash_offset; + le64_t next_field_offset; + le64_t entry_offset; /* the first array entry we store inline */ + le64_t entry_array_offset; + le64_t n_entries; + uint8_t payload[]; +} _packed_; + +struct FieldObject { + ObjectHeader object; + le64_t hash; + le64_t next_hash_offset; + le64_t head_data_offset; + uint8_t payload[]; +} _packed_; + +struct EntryItem { + le64_t object_offset; + le64_t hash; +} _packed_; + +struct EntryObject { + ObjectHeader object; + le64_t seqnum; + le64_t realtime; + le64_t monotonic; + sd_id128_t boot_id; + le64_t xor_hash; + EntryItem items[]; +} _packed_; + +struct HashItem { + le64_t head_hash_offset; + le64_t tail_hash_offset; +} _packed_; + +struct HashTableObject { + ObjectHeader object; + HashItem items[]; +} _packed_; + +struct EntryArrayObject { + ObjectHeader object; + le64_t next_entry_array_offset; + le64_t items[]; +} _packed_; + +#define TAG_LENGTH (256/8) + +struct TagObject { + ObjectHeader object; + le64_t seqnum; + le64_t epoch; + uint8_t tag[TAG_LENGTH]; /* SHA-256 HMAC */ +} _packed_; + +union Object { + ObjectHeader object; + DataObject data; + FieldObject field; + EntryObject entry; + HashTableObject hash_table; + EntryArrayObject entry_array; + TagObject tag; +}; + +enum { + STATE_OFFLINE = 0, + STATE_ONLINE = 1, + STATE_ARCHIVED = 2, + _STATE_MAX +}; + +/* Header flags */ +enum { + HEADER_INCOMPATIBLE_COMPRESSED_XZ = 1 << 0, + HEADER_INCOMPATIBLE_COMPRESSED_LZ4 = 1 << 1, +}; + +#define HEADER_INCOMPATIBLE_ANY (HEADER_INCOMPATIBLE_COMPRESSED_XZ|HEADER_INCOMPATIBLE_COMPRESSED_LZ4) + +#if defined(HAVE_XZ) && defined(HAVE_LZ4) +# define HEADER_INCOMPATIBLE_SUPPORTED HEADER_INCOMPATIBLE_ANY +#elif defined(HAVE_XZ) +# define HEADER_INCOMPATIBLE_SUPPORTED HEADER_INCOMPATIBLE_COMPRESSED_XZ +#elif defined(HAVE_LZ4) +# define HEADER_INCOMPATIBLE_SUPPORTED HEADER_INCOMPATIBLE_COMPRESSED_LZ4 +#else +# define HEADER_INCOMPATIBLE_SUPPORTED 0 +#endif + +enum { + HEADER_COMPATIBLE_SEALED = 1 +}; + +#define HEADER_COMPATIBLE_ANY HEADER_COMPATIBLE_SEALED +#ifdef HAVE_GCRYPT +# define HEADER_COMPATIBLE_SUPPORTED HEADER_COMPATIBLE_SEALED +#else +# define HEADER_COMPATIBLE_SUPPORTED 0 +#endif + +#define HEADER_SIGNATURE ((char[]) { 'L', 'P', 'K', 'S', 'H', 'H', 'R', 'H' }) + +struct Header { + uint8_t signature[8]; /* "LPKSHHRH" */ + le32_t compatible_flags; + le32_t incompatible_flags; + uint8_t state; + uint8_t reserved[7]; + sd_id128_t file_id; + sd_id128_t machine_id; + sd_id128_t boot_id; /* last writer */ + sd_id128_t seqnum_id; + le64_t header_size; + le64_t arena_size; + le64_t data_hash_table_offset; + le64_t data_hash_table_size; + le64_t field_hash_table_offset; + le64_t field_hash_table_size; + le64_t tail_object_offset; + le64_t n_objects; + le64_t n_entries; + le64_t tail_entry_seqnum; + le64_t head_entry_seqnum; + le64_t entry_array_offset; + le64_t head_entry_realtime; + le64_t tail_entry_realtime; + le64_t tail_entry_monotonic; + /* Added in 187 */ + le64_t n_data; + le64_t n_fields; + /* Added in 189 */ + le64_t n_tags; + le64_t n_entry_arrays; + + /* Size: 240 */ +} _packed_; + +#define FSS_HEADER_SIGNATURE ((char[]) { 'K', 'S', 'H', 'H', 'R', 'H', 'L', 'P' }) + +struct FSSHeader { + uint8_t signature[8]; /* "KSHHRHLP" */ + le32_t compatible_flags; + le32_t incompatible_flags; + sd_id128_t machine_id; + sd_id128_t boot_id; /* last writer */ + le64_t header_size; + le64_t start_usec; + le64_t interval_usec; + le16_t fsprg_secpar; + le16_t reserved[3]; + le64_t fsprg_state_size; +} _packed_; diff --git a/src/libsystemd/libsystemd-journal-internal/journal-file.c b/src/libsystemd/libsystemd-journal-internal/journal-file.c new file mode 100644 index 0000000000..13db5c53de --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/journal-file.c @@ -0,0 +1,3616 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "btrfs-util.h" +#include "chattr-util.h" +#include "compress.h" +#include "fd-util.h" +#include "journal-authenticate.h" +#include "journal-def.h" +#include "journal-file.h" +#include "lookup3.h" +#include "parse-util.h" +#include "path-util.h" +#include "random-util.h" +#include +#include "set.h" +#include "string-util.h" +#include "xattr-util.h" + +#define DEFAULT_DATA_HASH_TABLE_SIZE (2047ULL*sizeof(HashItem)) +#define DEFAULT_FIELD_HASH_TABLE_SIZE (333ULL*sizeof(HashItem)) + +#define COMPRESSION_SIZE_THRESHOLD (512ULL) + +/* This is the minimum journal file size */ +#define JOURNAL_FILE_SIZE_MIN (512ULL*1024ULL) /* 512 KiB */ + +/* These are the lower and upper bounds if we deduce the max_use value + * from the file system size */ +#define DEFAULT_MAX_USE_LOWER (1ULL*1024ULL*1024ULL) /* 1 MiB */ +#define DEFAULT_MAX_USE_UPPER (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */ + +/* This is the default minimal use limit, how much we'll use even if keep_free suggests otherwise. */ +#define DEFAULT_MIN_USE (1ULL*1024ULL*1024ULL) /* 1 MiB */ + +/* This is the upper bound if we deduce max_size from max_use */ +#define DEFAULT_MAX_SIZE_UPPER (128ULL*1024ULL*1024ULL) /* 128 MiB */ + +/* This is the upper bound if we deduce the keep_free value from the + * file system size */ +#define DEFAULT_KEEP_FREE_UPPER (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */ + +/* This is the keep_free value when we can't determine the system + * size */ +#define DEFAULT_KEEP_FREE (1024ULL*1024ULL) /* 1 MB */ + +/* This is the default maximum number of journal files to keep around. */ +#define DEFAULT_N_MAX_FILES (100) + +/* n_data was the first entry we added after the initial file format design */ +#define HEADER_SIZE_MIN ALIGN64(offsetof(Header, n_data)) + +/* How many entries to keep in the entry array chain cache at max */ +#define CHAIN_CACHE_MAX 20 + +/* How much to increase the journal file size at once each time we allocate something new. */ +#define FILE_SIZE_INCREASE (8ULL*1024ULL*1024ULL) /* 8MB */ + +/* Reread fstat() of the file for detecting deletions at least this often */ +#define LAST_STAT_REFRESH_USEC (5*USEC_PER_SEC) + +/* The mmap context to use for the header we pick as one above the last defined typed */ +#define CONTEXT_HEADER _OBJECT_TYPE_MAX + +/* This may be called from a separate thread to prevent blocking the caller for the duration of fsync(). + * As a result we use atomic operations on f->offline_state for inter-thread communications with + * journal_file_set_offline() and journal_file_set_online(). */ +static void journal_file_set_offline_internal(JournalFile *f) { + assert(f); + assert(f->fd >= 0); + assert(f->header); + + for (;;) { + switch (f->offline_state) { + case OFFLINE_CANCEL: + if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_CANCEL, OFFLINE_DONE)) + continue; + return; + + case OFFLINE_AGAIN_FROM_SYNCING: + if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_AGAIN_FROM_SYNCING, OFFLINE_SYNCING)) + continue; + break; + + case OFFLINE_AGAIN_FROM_OFFLINING: + if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_AGAIN_FROM_OFFLINING, OFFLINE_SYNCING)) + continue; + break; + + case OFFLINE_SYNCING: + (void) fsync(f->fd); + + if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_SYNCING, OFFLINE_OFFLINING)) + continue; + + f->header->state = f->archive ? STATE_ARCHIVED : STATE_OFFLINE; + (void) fsync(f->fd); + break; + + case OFFLINE_OFFLINING: + if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_OFFLINING, OFFLINE_DONE)) + continue; + /* fall through */ + + case OFFLINE_DONE: + return; + + case OFFLINE_JOINED: + log_debug("OFFLINE_JOINED unexpected offline state for journal_file_set_offline_internal()"); + return; + } + } +} + +static void * journal_file_set_offline_thread(void *arg) { + JournalFile *f = arg; + + journal_file_set_offline_internal(f); + + return NULL; +} + +static int journal_file_set_offline_thread_join(JournalFile *f) { + int r; + + assert(f); + + if (f->offline_state == OFFLINE_JOINED) + return 0; + + r = pthread_join(f->offline_thread, NULL); + if (r) + return -r; + + f->offline_state = OFFLINE_JOINED; + + if (mmap_cache_got_sigbus(f->mmap, f->fd)) + return -EIO; + + return 0; +} + +/* Trigger a restart if the offline thread is mid-flight in a restartable state. */ +static bool journal_file_set_offline_try_restart(JournalFile *f) { + for (;;) { + switch (f->offline_state) { + case OFFLINE_AGAIN_FROM_SYNCING: + case OFFLINE_AGAIN_FROM_OFFLINING: + return true; + + case OFFLINE_CANCEL: + if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_CANCEL, OFFLINE_AGAIN_FROM_SYNCING)) + continue; + return true; + + case OFFLINE_SYNCING: + if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_SYNCING, OFFLINE_AGAIN_FROM_SYNCING)) + continue; + return true; + + case OFFLINE_OFFLINING: + if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_OFFLINING, OFFLINE_AGAIN_FROM_OFFLINING)) + continue; + return true; + + default: + return false; + } + } +} + +/* Sets a journal offline. + * + * If wait is false then an offline is dispatched in a separate thread for a + * subsequent journal_file_set_offline() or journal_file_set_online() of the + * same journal to synchronize with. + * + * If wait is true, then either an existing offline thread will be restarted + * and joined, or if none exists the offline is simply performed in this + * context without involving another thread. + */ +int journal_file_set_offline(JournalFile *f, bool wait) { + bool restarted; + int r; + + assert(f); + + if (!f->writable) + return -EPERM; + + if (!(f->fd >= 0 && f->header)) + return -EINVAL; + + /* An offlining journal is implicitly online and may modify f->header->state, + * we must also join any potentially lingering offline thread when not online. */ + if (!journal_file_is_offlining(f) && f->header->state != STATE_ONLINE) + return journal_file_set_offline_thread_join(f); + + /* Restart an in-flight offline thread and wait if needed, or join a lingering done one. */ + restarted = journal_file_set_offline_try_restart(f); + if ((restarted && wait) || !restarted) { + r = journal_file_set_offline_thread_join(f); + if (r < 0) + return r; + } + + if (restarted) + return 0; + + /* Initiate a new offline. */ + f->offline_state = OFFLINE_SYNCING; + + if (wait) /* Without using a thread if waiting. */ + journal_file_set_offline_internal(f); + else { + r = pthread_create(&f->offline_thread, NULL, journal_file_set_offline_thread, f); + if (r > 0) { + f->offline_state = OFFLINE_JOINED; + return -r; + } + } + + return 0; +} + +static int journal_file_set_online(JournalFile *f) { + bool joined = false; + + assert(f); + + if (!f->writable) + return -EPERM; + + if (!(f->fd >= 0 && f->header)) + return -EINVAL; + + while (!joined) { + switch (f->offline_state) { + case OFFLINE_JOINED: + /* No offline thread, no need to wait. */ + joined = true; + break; + + case OFFLINE_SYNCING: + if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_SYNCING, OFFLINE_CANCEL)) + continue; + /* Canceled syncing prior to offlining, no need to wait. */ + break; + + case OFFLINE_AGAIN_FROM_SYNCING: + if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_AGAIN_FROM_SYNCING, OFFLINE_CANCEL)) + continue; + /* Canceled restart from syncing, no need to wait. */ + break; + + case OFFLINE_AGAIN_FROM_OFFLINING: + if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_AGAIN_FROM_OFFLINING, OFFLINE_CANCEL)) + continue; + /* Canceled restart from offlining, must wait for offlining to complete however. */ + + /* fall through to wait */ + default: { + int r; + + r = journal_file_set_offline_thread_join(f); + if (r < 0) + return r; + + joined = true; + break; + } + } + } + + if (mmap_cache_got_sigbus(f->mmap, f->fd)) + return -EIO; + + switch (f->header->state) { + case STATE_ONLINE: + return 0; + + case STATE_OFFLINE: + f->header->state = STATE_ONLINE; + (void) fsync(f->fd); + return 0; + + default: + return -EINVAL; + } +} + +bool journal_file_is_offlining(JournalFile *f) { + assert(f); + + __sync_synchronize(); + + if (f->offline_state == OFFLINE_DONE || + f->offline_state == OFFLINE_JOINED) + return false; + + return true; +} + +JournalFile* journal_file_close(JournalFile *f) { + assert(f); + +#ifdef HAVE_GCRYPT + /* Write the final tag */ + if (f->seal && f->writable) + journal_file_append_tag(f); +#endif + + if (f->post_change_timer) { + int enabled; + + if (sd_event_source_get_enabled(f->post_change_timer, &enabled) >= 0) + if (enabled == SD_EVENT_ONESHOT) + journal_file_post_change(f); + + (void) sd_event_source_set_enabled(f->post_change_timer, SD_EVENT_OFF); + sd_event_source_unref(f->post_change_timer); + } + + journal_file_set_offline(f, true); + + if (f->mmap && f->fd >= 0) + mmap_cache_close_fd(f->mmap, f->fd); + + if (f->fd >= 0 && f->defrag_on_close) { + + /* Be friendly to btrfs: turn COW back on again now, + * and defragment the file. We won't write to the file + * ever again, hence remove all fragmentation, and + * reenable all the good bits COW usually provides + * (such as data checksumming). */ + + (void) chattr_fd(f->fd, 0, FS_NOCOW_FL); + (void) btrfs_defrag_fd(f->fd); + } + + if (f->close_fd) + safe_close(f->fd); + free(f->path); + + mmap_cache_unref(f->mmap); + + ordered_hashmap_free_free(f->chain_cache); + +#if defined(HAVE_XZ) || defined(HAVE_LZ4) + free(f->compress_buffer); +#endif + +#ifdef HAVE_GCRYPT + if (f->fss_file) + munmap(f->fss_file, PAGE_ALIGN(f->fss_file_size)); + else + free(f->fsprg_state); + + free(f->fsprg_seed); + + if (f->hmac) + gcry_md_close(f->hmac); +#endif + + free(f); + return NULL; +} + +void journal_file_close_set(Set *s) { + JournalFile *f; + + assert(s); + + while ((f = set_steal_first(s))) + (void) journal_file_close(f); +} + +static int journal_file_init_header(JournalFile *f, JournalFile *template) { + Header h = {}; + ssize_t k; + int r; + + assert(f); + + memcpy(h.signature, HEADER_SIGNATURE, 8); + h.header_size = htole64(ALIGN64(sizeof(h))); + + h.incompatible_flags |= htole32( + f->compress_xz * HEADER_INCOMPATIBLE_COMPRESSED_XZ | + f->compress_lz4 * HEADER_INCOMPATIBLE_COMPRESSED_LZ4); + + h.compatible_flags = htole32( + f->seal * HEADER_COMPATIBLE_SEALED); + + r = sd_id128_randomize(&h.file_id); + if (r < 0) + return r; + + if (template) { + h.seqnum_id = template->header->seqnum_id; + h.tail_entry_seqnum = template->header->tail_entry_seqnum; + } else + h.seqnum_id = h.file_id; + + k = pwrite(f->fd, &h, sizeof(h), 0); + if (k < 0) + return -errno; + + if (k != sizeof(h)) + return -EIO; + + return 0; +} + +static int fsync_directory_of_file(int fd) { + _cleanup_free_ char *path = NULL, *dn = NULL; + _cleanup_close_ int dfd = -1; + struct stat st; + int r; + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISREG(st.st_mode)) + return -EBADFD; + + r = fd_get_path(fd, &path); + if (r < 0) + return r; + + if (!path_is_absolute(path)) + return -EINVAL; + + dn = dirname_malloc(path); + if (!dn) + return -ENOMEM; + + dfd = open(dn, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (dfd < 0) + return -errno; + + if (fsync(dfd) < 0) + return -errno; + + return 0; +} + +static int journal_file_refresh_header(JournalFile *f) { + sd_id128_t boot_id; + int r; + + assert(f); + assert(f->header); + + r = sd_id128_get_machine(&f->header->machine_id); + if (r < 0) + return r; + + r = sd_id128_get_boot(&boot_id); + if (r < 0) + return r; + + if (sd_id128_equal(boot_id, f->header->boot_id)) + f->tail_entry_monotonic_valid = true; + + f->header->boot_id = boot_id; + + r = journal_file_set_online(f); + + /* Sync the online state to disk */ + (void) fsync(f->fd); + + /* We likely just created a new file, also sync the directory this file is located in. */ + (void) fsync_directory_of_file(f->fd); + + return r; +} + +static int journal_file_verify_header(JournalFile *f) { + uint32_t flags; + + assert(f); + assert(f->header); + + if (memcmp(f->header->signature, HEADER_SIGNATURE, 8)) + return -EBADMSG; + + /* In both read and write mode we refuse to open files with + * incompatible flags we don't know */ + flags = le32toh(f->header->incompatible_flags); + if (flags & ~HEADER_INCOMPATIBLE_SUPPORTED) { + if (flags & ~HEADER_INCOMPATIBLE_ANY) + log_debug("Journal file %s has unknown incompatible flags %"PRIx32, + f->path, flags & ~HEADER_INCOMPATIBLE_ANY); + flags = (flags & HEADER_INCOMPATIBLE_ANY) & ~HEADER_INCOMPATIBLE_SUPPORTED; + if (flags) + log_debug("Journal file %s uses incompatible flags %"PRIx32 + " disabled at compilation time.", f->path, flags); + return -EPROTONOSUPPORT; + } + + /* When open for writing we refuse to open files with + * compatible flags, too */ + flags = le32toh(f->header->compatible_flags); + if (f->writable && (flags & ~HEADER_COMPATIBLE_SUPPORTED)) { + if (flags & ~HEADER_COMPATIBLE_ANY) + log_debug("Journal file %s has unknown compatible flags %"PRIx32, + f->path, flags & ~HEADER_COMPATIBLE_ANY); + flags = (flags & HEADER_COMPATIBLE_ANY) & ~HEADER_COMPATIBLE_SUPPORTED; + if (flags) + log_debug("Journal file %s uses compatible flags %"PRIx32 + " disabled at compilation time.", f->path, flags); + return -EPROTONOSUPPORT; + } + + if (f->header->state >= _STATE_MAX) + return -EBADMSG; + + /* The first addition was n_data, so check that we are at least this large */ + if (le64toh(f->header->header_size) < HEADER_SIZE_MIN) + return -EBADMSG; + + if (JOURNAL_HEADER_SEALED(f->header) && !JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays)) + return -EBADMSG; + + if ((le64toh(f->header->header_size) + le64toh(f->header->arena_size)) > (uint64_t) f->last_stat.st_size) + return -ENODATA; + + if (le64toh(f->header->tail_object_offset) > (le64toh(f->header->header_size) + le64toh(f->header->arena_size))) + return -ENODATA; + + if (!VALID64(le64toh(f->header->data_hash_table_offset)) || + !VALID64(le64toh(f->header->field_hash_table_offset)) || + !VALID64(le64toh(f->header->tail_object_offset)) || + !VALID64(le64toh(f->header->entry_array_offset))) + return -ENODATA; + + if (f->writable) { + uint8_t state; + sd_id128_t machine_id; + int r; + + r = sd_id128_get_machine(&machine_id); + if (r < 0) + return r; + + if (!sd_id128_equal(machine_id, f->header->machine_id)) + return -EHOSTDOWN; + + state = f->header->state; + + if (state == STATE_ONLINE) { + log_debug("Journal file %s is already online. Assuming unclean closing.", f->path); + return -EBUSY; + } else if (state == STATE_ARCHIVED) + return -ESHUTDOWN; + else if (state != STATE_OFFLINE) { + log_debug("Journal file %s has unknown state %i.", f->path, state); + return -EBUSY; + } + } + + f->compress_xz = JOURNAL_HEADER_COMPRESSED_XZ(f->header); + f->compress_lz4 = JOURNAL_HEADER_COMPRESSED_LZ4(f->header); + + f->seal = JOURNAL_HEADER_SEALED(f->header); + + return 0; +} + +static int journal_file_fstat(JournalFile *f) { + assert(f); + assert(f->fd >= 0); + + if (fstat(f->fd, &f->last_stat) < 0) + return -errno; + + f->last_stat_usec = now(CLOCK_MONOTONIC); + + /* Refuse appending to files that are already deleted */ + if (f->last_stat.st_nlink <= 0) + return -EIDRM; + + return 0; +} + +static int journal_file_allocate(JournalFile *f, uint64_t offset, uint64_t size) { + uint64_t old_size, new_size; + int r; + + assert(f); + assert(f->header); + + /* We assume that this file is not sparse, and we know that + * for sure, since we always call posix_fallocate() + * ourselves */ + + if (mmap_cache_got_sigbus(f->mmap, f->fd)) + return -EIO; + + old_size = + le64toh(f->header->header_size) + + le64toh(f->header->arena_size); + + new_size = PAGE_ALIGN(offset + size); + if (new_size < le64toh(f->header->header_size)) + new_size = le64toh(f->header->header_size); + + if (new_size <= old_size) { + + /* We already pre-allocated enough space, but before + * we write to it, let's check with fstat() if the + * file got deleted, in order make sure we don't throw + * away the data immediately. Don't check fstat() for + * all writes though, but only once ever 10s. */ + + if (f->last_stat_usec + LAST_STAT_REFRESH_USEC > now(CLOCK_MONOTONIC)) + return 0; + + return journal_file_fstat(f); + } + + /* Allocate more space. */ + + if (f->metrics.max_size > 0 && new_size > f->metrics.max_size) + return -E2BIG; + + if (new_size > f->metrics.min_size && f->metrics.keep_free > 0) { + struct statvfs svfs; + + if (fstatvfs(f->fd, &svfs) >= 0) { + uint64_t available; + + available = LESS_BY((uint64_t) svfs.f_bfree * (uint64_t) svfs.f_bsize, f->metrics.keep_free); + + if (new_size - old_size > available) + return -E2BIG; + } + } + + /* Increase by larger blocks at once */ + new_size = ((new_size+FILE_SIZE_INCREASE-1) / FILE_SIZE_INCREASE) * FILE_SIZE_INCREASE; + if (f->metrics.max_size > 0 && new_size > f->metrics.max_size) + new_size = f->metrics.max_size; + + /* Note that the glibc fallocate() fallback is very + inefficient, hence we try to minimize the allocation area + as we can. */ + r = posix_fallocate(f->fd, old_size, new_size - old_size); + if (r != 0) + return -r; + + f->header->arena_size = htole64(new_size - le64toh(f->header->header_size)); + + return journal_file_fstat(f); +} + +static unsigned type_to_context(ObjectType type) { + /* One context for each type, plus one catch-all for the rest */ + assert_cc(_OBJECT_TYPE_MAX <= MMAP_CACHE_MAX_CONTEXTS); + assert_cc(CONTEXT_HEADER < MMAP_CACHE_MAX_CONTEXTS); + return type > OBJECT_UNUSED && type < _OBJECT_TYPE_MAX ? type : 0; +} + +static int journal_file_move_to(JournalFile *f, ObjectType type, bool keep_always, uint64_t offset, uint64_t size, void **ret) { + int r; + + assert(f); + assert(ret); + + if (size <= 0) + return -EINVAL; + + /* Avoid SIGBUS on invalid accesses */ + if (offset + size > (uint64_t) f->last_stat.st_size) { + /* Hmm, out of range? Let's refresh the fstat() data + * first, before we trust that check. */ + + r = journal_file_fstat(f); + if (r < 0) + return r; + + if (offset + size > (uint64_t) f->last_stat.st_size) + return -EADDRNOTAVAIL; + } + + return mmap_cache_get(f->mmap, f->fd, f->prot, type_to_context(type), keep_always, offset, size, &f->last_stat, ret); +} + +static uint64_t minimum_header_size(Object *o) { + + static const uint64_t table[] = { + [OBJECT_DATA] = sizeof(DataObject), + [OBJECT_FIELD] = sizeof(FieldObject), + [OBJECT_ENTRY] = sizeof(EntryObject), + [OBJECT_DATA_HASH_TABLE] = sizeof(HashTableObject), + [OBJECT_FIELD_HASH_TABLE] = sizeof(HashTableObject), + [OBJECT_ENTRY_ARRAY] = sizeof(EntryArrayObject), + [OBJECT_TAG] = sizeof(TagObject), + }; + + if (o->object.type >= ELEMENTSOF(table) || table[o->object.type] <= 0) + return sizeof(ObjectHeader); + + return table[o->object.type]; +} + +int journal_file_move_to_object(JournalFile *f, ObjectType type, uint64_t offset, Object **ret) { + int r; + void *t; + Object *o; + uint64_t s; + + assert(f); + assert(ret); + + /* Objects may only be located at multiple of 64 bit */ + if (!VALID64(offset)) + return -EBADMSG; + + /* Object may not be located in the file header */ + if (offset < le64toh(f->header->header_size)) + return -EBADMSG; + + r = journal_file_move_to(f, type, false, offset, sizeof(ObjectHeader), &t); + if (r < 0) + return r; + + o = (Object*) t; + s = le64toh(o->object.size); + + if (s < sizeof(ObjectHeader)) + return -EBADMSG; + + if (o->object.type <= OBJECT_UNUSED) + return -EBADMSG; + + if (s < minimum_header_size(o)) + return -EBADMSG; + + if (type > OBJECT_UNUSED && o->object.type != type) + return -EBADMSG; + + if (s > sizeof(ObjectHeader)) { + r = journal_file_move_to(f, type, false, offset, s, &t); + if (r < 0) + return r; + + o = (Object*) t; + } + + *ret = o; + return 0; +} + +static uint64_t journal_file_entry_seqnum(JournalFile *f, uint64_t *seqnum) { + uint64_t r; + + assert(f); + assert(f->header); + + r = le64toh(f->header->tail_entry_seqnum) + 1; + + if (seqnum) { + /* If an external seqnum counter was passed, we update + * both the local and the external one, and set it to + * the maximum of both */ + + if (*seqnum + 1 > r) + r = *seqnum + 1; + + *seqnum = r; + } + + f->header->tail_entry_seqnum = htole64(r); + + if (f->header->head_entry_seqnum == 0) + f->header->head_entry_seqnum = htole64(r); + + return r; +} + +int journal_file_append_object(JournalFile *f, ObjectType type, uint64_t size, Object **ret, uint64_t *offset) { + int r; + uint64_t p; + Object *tail, *o; + void *t; + + assert(f); + assert(f->header); + assert(type > OBJECT_UNUSED && type < _OBJECT_TYPE_MAX); + assert(size >= sizeof(ObjectHeader)); + assert(offset); + assert(ret); + + r = journal_file_set_online(f); + if (r < 0) + return r; + + p = le64toh(f->header->tail_object_offset); + if (p == 0) + p = le64toh(f->header->header_size); + else { + r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &tail); + if (r < 0) + return r; + + p += ALIGN64(le64toh(tail->object.size)); + } + + r = journal_file_allocate(f, p, size); + if (r < 0) + return r; + + r = journal_file_move_to(f, type, false, p, size, &t); + if (r < 0) + return r; + + o = (Object*) t; + + zero(o->object); + o->object.type = type; + o->object.size = htole64(size); + + f->header->tail_object_offset = htole64(p); + f->header->n_objects = htole64(le64toh(f->header->n_objects) + 1); + + *ret = o; + *offset = p; + + return 0; +} + +static int journal_file_setup_data_hash_table(JournalFile *f) { + uint64_t s, p; + Object *o; + int r; + + assert(f); + assert(f->header); + + /* We estimate that we need 1 hash table entry per 768 bytes + of journal file and we want to make sure we never get + beyond 75% fill level. Calculate the hash table size for + the maximum file size based on these metrics. */ + + s = (f->metrics.max_size * 4 / 768 / 3) * sizeof(HashItem); + if (s < DEFAULT_DATA_HASH_TABLE_SIZE) + s = DEFAULT_DATA_HASH_TABLE_SIZE; + + log_debug("Reserving %"PRIu64" entries in hash table.", s / sizeof(HashItem)); + + r = journal_file_append_object(f, + OBJECT_DATA_HASH_TABLE, + offsetof(Object, hash_table.items) + s, + &o, &p); + if (r < 0) + return r; + + memzero(o->hash_table.items, s); + + f->header->data_hash_table_offset = htole64(p + offsetof(Object, hash_table.items)); + f->header->data_hash_table_size = htole64(s); + + return 0; +} + +static int journal_file_setup_field_hash_table(JournalFile *f) { + uint64_t s, p; + Object *o; + int r; + + assert(f); + assert(f->header); + + /* We use a fixed size hash table for the fields as this + * number should grow very slowly only */ + + s = DEFAULT_FIELD_HASH_TABLE_SIZE; + r = journal_file_append_object(f, + OBJECT_FIELD_HASH_TABLE, + offsetof(Object, hash_table.items) + s, + &o, &p); + if (r < 0) + return r; + + memzero(o->hash_table.items, s); + + f->header->field_hash_table_offset = htole64(p + offsetof(Object, hash_table.items)); + f->header->field_hash_table_size = htole64(s); + + return 0; +} + +int journal_file_map_data_hash_table(JournalFile *f) { + uint64_t s, p; + void *t; + int r; + + assert(f); + assert(f->header); + + if (f->data_hash_table) + return 0; + + p = le64toh(f->header->data_hash_table_offset); + s = le64toh(f->header->data_hash_table_size); + + r = journal_file_move_to(f, + OBJECT_DATA_HASH_TABLE, + true, + p, s, + &t); + if (r < 0) + return r; + + f->data_hash_table = t; + return 0; +} + +int journal_file_map_field_hash_table(JournalFile *f) { + uint64_t s, p; + void *t; + int r; + + assert(f); + assert(f->header); + + if (f->field_hash_table) + return 0; + + p = le64toh(f->header->field_hash_table_offset); + s = le64toh(f->header->field_hash_table_size); + + r = journal_file_move_to(f, + OBJECT_FIELD_HASH_TABLE, + true, + p, s, + &t); + if (r < 0) + return r; + + f->field_hash_table = t; + return 0; +} + +static int journal_file_link_field( + JournalFile *f, + Object *o, + uint64_t offset, + uint64_t hash) { + + uint64_t p, h, m; + int r; + + assert(f); + assert(f->header); + assert(f->field_hash_table); + assert(o); + assert(offset > 0); + + if (o->object.type != OBJECT_FIELD) + return -EINVAL; + + m = le64toh(f->header->field_hash_table_size) / sizeof(HashItem); + if (m <= 0) + return -EBADMSG; + + /* This might alter the window we are looking at */ + o->field.next_hash_offset = o->field.head_data_offset = 0; + + h = hash % m; + p = le64toh(f->field_hash_table[h].tail_hash_offset); + if (p == 0) + f->field_hash_table[h].head_hash_offset = htole64(offset); + else { + r = journal_file_move_to_object(f, OBJECT_FIELD, p, &o); + if (r < 0) + return r; + + o->field.next_hash_offset = htole64(offset); + } + + f->field_hash_table[h].tail_hash_offset = htole64(offset); + + if (JOURNAL_HEADER_CONTAINS(f->header, n_fields)) + f->header->n_fields = htole64(le64toh(f->header->n_fields) + 1); + + return 0; +} + +static int journal_file_link_data( + JournalFile *f, + Object *o, + uint64_t offset, + uint64_t hash) { + + uint64_t p, h, m; + int r; + + assert(f); + assert(f->header); + assert(f->data_hash_table); + assert(o); + assert(offset > 0); + + if (o->object.type != OBJECT_DATA) + return -EINVAL; + + m = le64toh(f->header->data_hash_table_size) / sizeof(HashItem); + if (m <= 0) + return -EBADMSG; + + /* This might alter the window we are looking at */ + o->data.next_hash_offset = o->data.next_field_offset = 0; + o->data.entry_offset = o->data.entry_array_offset = 0; + o->data.n_entries = 0; + + h = hash % m; + p = le64toh(f->data_hash_table[h].tail_hash_offset); + if (p == 0) + /* Only entry in the hash table is easy */ + f->data_hash_table[h].head_hash_offset = htole64(offset); + else { + /* Move back to the previous data object, to patch in + * pointer */ + + r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); + if (r < 0) + return r; + + o->data.next_hash_offset = htole64(offset); + } + + f->data_hash_table[h].tail_hash_offset = htole64(offset); + + if (JOURNAL_HEADER_CONTAINS(f->header, n_data)) + f->header->n_data = htole64(le64toh(f->header->n_data) + 1); + + return 0; +} + +int journal_file_find_field_object_with_hash( + JournalFile *f, + const void *field, uint64_t size, uint64_t hash, + Object **ret, uint64_t *offset) { + + uint64_t p, osize, h, m; + int r; + + assert(f); + assert(f->header); + assert(field && size > 0); + + /* If the field hash table is empty, we can't find anything */ + if (le64toh(f->header->field_hash_table_size) <= 0) + return 0; + + /* Map the field hash table, if it isn't mapped yet. */ + r = journal_file_map_field_hash_table(f); + if (r < 0) + return r; + + osize = offsetof(Object, field.payload) + size; + + m = le64toh(f->header->field_hash_table_size) / sizeof(HashItem); + if (m <= 0) + return -EBADMSG; + + h = hash % m; + p = le64toh(f->field_hash_table[h].head_hash_offset); + + while (p > 0) { + Object *o; + + r = journal_file_move_to_object(f, OBJECT_FIELD, p, &o); + if (r < 0) + return r; + + if (le64toh(o->field.hash) == hash && + le64toh(o->object.size) == osize && + memcmp(o->field.payload, field, size) == 0) { + + if (ret) + *ret = o; + if (offset) + *offset = p; + + return 1; + } + + p = le64toh(o->field.next_hash_offset); + } + + return 0; +} + +int journal_file_find_field_object( + JournalFile *f, + const void *field, uint64_t size, + Object **ret, uint64_t *offset) { + + uint64_t hash; + + assert(f); + assert(field && size > 0); + + hash = hash64(field, size); + + return journal_file_find_field_object_with_hash(f, + field, size, hash, + ret, offset); +} + +int journal_file_find_data_object_with_hash( + JournalFile *f, + const void *data, uint64_t size, uint64_t hash, + Object **ret, uint64_t *offset) { + + uint64_t p, osize, h, m; + int r; + + assert(f); + assert(f->header); + assert(data || size == 0); + + /* If there's no data hash table, then there's no entry. */ + if (le64toh(f->header->data_hash_table_size) <= 0) + return 0; + + /* Map the data hash table, if it isn't mapped yet. */ + r = journal_file_map_data_hash_table(f); + if (r < 0) + return r; + + osize = offsetof(Object, data.payload) + size; + + m = le64toh(f->header->data_hash_table_size) / sizeof(HashItem); + if (m <= 0) + return -EBADMSG; + + h = hash % m; + p = le64toh(f->data_hash_table[h].head_hash_offset); + + while (p > 0) { + Object *o; + + r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); + if (r < 0) + return r; + + if (le64toh(o->data.hash) != hash) + goto next; + + if (o->object.flags & OBJECT_COMPRESSION_MASK) { +#if defined(HAVE_XZ) || defined(HAVE_LZ4) + uint64_t l; + size_t rsize = 0; + + l = le64toh(o->object.size); + if (l <= offsetof(Object, data.payload)) + return -EBADMSG; + + l -= offsetof(Object, data.payload); + + r = decompress_blob(o->object.flags & OBJECT_COMPRESSION_MASK, + o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize, 0); + if (r < 0) + return r; + + if (rsize == size && + memcmp(f->compress_buffer, data, size) == 0) { + + if (ret) + *ret = o; + + if (offset) + *offset = p; + + return 1; + } +#else + return -EPROTONOSUPPORT; +#endif + } else if (le64toh(o->object.size) == osize && + memcmp(o->data.payload, data, size) == 0) { + + if (ret) + *ret = o; + + if (offset) + *offset = p; + + return 1; + } + + next: + p = le64toh(o->data.next_hash_offset); + } + + return 0; +} + +int journal_file_find_data_object( + JournalFile *f, + const void *data, uint64_t size, + Object **ret, uint64_t *offset) { + + uint64_t hash; + + assert(f); + assert(data || size == 0); + + hash = hash64(data, size); + + return journal_file_find_data_object_with_hash(f, + data, size, hash, + ret, offset); +} + +static int journal_file_append_field( + JournalFile *f, + const void *field, uint64_t size, + Object **ret, uint64_t *offset) { + + uint64_t hash, p; + uint64_t osize; + Object *o; + int r; + + assert(f); + assert(field && size > 0); + + hash = hash64(field, size); + + r = journal_file_find_field_object_with_hash(f, field, size, hash, &o, &p); + if (r < 0) + return r; + else if (r > 0) { + + if (ret) + *ret = o; + + if (offset) + *offset = p; + + return 0; + } + + osize = offsetof(Object, field.payload) + size; + r = journal_file_append_object(f, OBJECT_FIELD, osize, &o, &p); + if (r < 0) + return r; + + o->field.hash = htole64(hash); + memcpy(o->field.payload, field, size); + + r = journal_file_link_field(f, o, p, hash); + if (r < 0) + return r; + + /* The linking might have altered the window, so let's + * refresh our pointer */ + r = journal_file_move_to_object(f, OBJECT_FIELD, p, &o); + if (r < 0) + return r; + +#ifdef HAVE_GCRYPT + r = journal_file_hmac_put_object(f, OBJECT_FIELD, o, p); + if (r < 0) + return r; +#endif + + if (ret) + *ret = o; + + if (offset) + *offset = p; + + return 0; +} + +static int journal_file_append_data( + JournalFile *f, + const void *data, uint64_t size, + Object **ret, uint64_t *offset) { + + uint64_t hash, p; + uint64_t osize; + Object *o; + int r, compression = 0; + const void *eq; + + assert(f); + assert(data || size == 0); + + hash = hash64(data, size); + + r = journal_file_find_data_object_with_hash(f, data, size, hash, &o, &p); + if (r < 0) + return r; + if (r > 0) { + + if (ret) + *ret = o; + + if (offset) + *offset = p; + + return 0; + } + + osize = offsetof(Object, data.payload) + size; + r = journal_file_append_object(f, OBJECT_DATA, osize, &o, &p); + if (r < 0) + return r; + + o->data.hash = htole64(hash); + +#if defined(HAVE_XZ) || defined(HAVE_LZ4) + if (JOURNAL_FILE_COMPRESS(f) && size >= COMPRESSION_SIZE_THRESHOLD) { + size_t rsize = 0; + + compression = compress_blob(data, size, o->data.payload, size - 1, &rsize); + + if (compression >= 0) { + o->object.size = htole64(offsetof(Object, data.payload) + rsize); + o->object.flags |= compression; + + log_debug("Compressed data object %"PRIu64" -> %zu using %s", + size, rsize, object_compressed_to_string(compression)); + } else + /* Compression didn't work, we don't really care why, let's continue without compression */ + compression = 0; + } +#endif + + if (compression == 0) + memcpy_safe(o->data.payload, data, size); + + r = journal_file_link_data(f, o, p, hash); + if (r < 0) + return r; + + /* The linking might have altered the window, so let's + * refresh our pointer */ + r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); + if (r < 0) + return r; + + if (!data) + eq = NULL; + else + eq = memchr(data, '=', size); + if (eq && eq > data) { + Object *fo = NULL; + uint64_t fp; + + /* Create field object ... */ + r = journal_file_append_field(f, data, (uint8_t*) eq - (uint8_t*) data, &fo, &fp); + if (r < 0) + return r; + + /* ... and link it in. */ + o->data.next_field_offset = fo->field.head_data_offset; + fo->field.head_data_offset = le64toh(p); + } + +#ifdef HAVE_GCRYPT + r = journal_file_hmac_put_object(f, OBJECT_DATA, o, p); + if (r < 0) + return r; +#endif + + if (ret) + *ret = o; + + if (offset) + *offset = p; + + return 0; +} + +uint64_t journal_file_entry_n_items(Object *o) { + assert(o); + + if (o->object.type != OBJECT_ENTRY) + return 0; + + return (le64toh(o->object.size) - offsetof(Object, entry.items)) / sizeof(EntryItem); +} + +uint64_t journal_file_entry_array_n_items(Object *o) { + assert(o); + + if (o->object.type != OBJECT_ENTRY_ARRAY) + return 0; + + return (le64toh(o->object.size) - offsetof(Object, entry_array.items)) / sizeof(uint64_t); +} + +uint64_t journal_file_hash_table_n_items(Object *o) { + assert(o); + + if (o->object.type != OBJECT_DATA_HASH_TABLE && + o->object.type != OBJECT_FIELD_HASH_TABLE) + return 0; + + return (le64toh(o->object.size) - offsetof(Object, hash_table.items)) / sizeof(HashItem); +} + +static int link_entry_into_array(JournalFile *f, + le64_t *first, + le64_t *idx, + uint64_t p) { + int r; + uint64_t n = 0, ap = 0, q, i, a, hidx; + Object *o; + + assert(f); + assert(f->header); + assert(first); + assert(idx); + assert(p > 0); + + a = le64toh(*first); + i = hidx = le64toh(*idx); + while (a > 0) { + + r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); + if (r < 0) + return r; + + n = journal_file_entry_array_n_items(o); + if (i < n) { + o->entry_array.items[i] = htole64(p); + *idx = htole64(hidx + 1); + return 0; + } + + i -= n; + ap = a; + a = le64toh(o->entry_array.next_entry_array_offset); + } + + if (hidx > n) + n = (hidx+1) * 2; + else + n = n * 2; + + if (n < 4) + n = 4; + + r = journal_file_append_object(f, OBJECT_ENTRY_ARRAY, + offsetof(Object, entry_array.items) + n * sizeof(uint64_t), + &o, &q); + if (r < 0) + return r; + +#ifdef HAVE_GCRYPT + r = journal_file_hmac_put_object(f, OBJECT_ENTRY_ARRAY, o, q); + if (r < 0) + return r; +#endif + + o->entry_array.items[i] = htole64(p); + + if (ap == 0) + *first = htole64(q); + else { + r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, ap, &o); + if (r < 0) + return r; + + o->entry_array.next_entry_array_offset = htole64(q); + } + + if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays)) + f->header->n_entry_arrays = htole64(le64toh(f->header->n_entry_arrays) + 1); + + *idx = htole64(hidx + 1); + + return 0; +} + +static int link_entry_into_array_plus_one(JournalFile *f, + le64_t *extra, + le64_t *first, + le64_t *idx, + uint64_t p) { + + int r; + + assert(f); + assert(extra); + assert(first); + assert(idx); + assert(p > 0); + + if (*idx == 0) + *extra = htole64(p); + else { + le64_t i; + + i = htole64(le64toh(*idx) - 1); + r = link_entry_into_array(f, first, &i, p); + if (r < 0) + return r; + } + + *idx = htole64(le64toh(*idx) + 1); + return 0; +} + +static int journal_file_link_entry_item(JournalFile *f, Object *o, uint64_t offset, uint64_t i) { + uint64_t p; + int r; + assert(f); + assert(o); + assert(offset > 0); + + p = le64toh(o->entry.items[i].object_offset); + if (p == 0) + return -EINVAL; + + r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); + if (r < 0) + return r; + + return link_entry_into_array_plus_one(f, + &o->data.entry_offset, + &o->data.entry_array_offset, + &o->data.n_entries, + offset); +} + +static int journal_file_link_entry(JournalFile *f, Object *o, uint64_t offset) { + uint64_t n, i; + int r; + + assert(f); + assert(f->header); + assert(o); + assert(offset > 0); + + if (o->object.type != OBJECT_ENTRY) + return -EINVAL; + + __sync_synchronize(); + + /* Link up the entry itself */ + r = link_entry_into_array(f, + &f->header->entry_array_offset, + &f->header->n_entries, + offset); + if (r < 0) + return r; + + /* log_debug("=> %s seqnr=%"PRIu64" n_entries=%"PRIu64, f->path, o->entry.seqnum, f->header->n_entries); */ + + if (f->header->head_entry_realtime == 0) + f->header->head_entry_realtime = o->entry.realtime; + + f->header->tail_entry_realtime = o->entry.realtime; + f->header->tail_entry_monotonic = o->entry.monotonic; + + f->tail_entry_monotonic_valid = true; + + /* Link up the items */ + n = journal_file_entry_n_items(o); + for (i = 0; i < n; i++) { + r = journal_file_link_entry_item(f, o, offset, i); + if (r < 0) + return r; + } + + return 0; +} + +static int journal_file_append_entry_internal( + JournalFile *f, + const dual_timestamp *ts, + uint64_t xor_hash, + const EntryItem items[], unsigned n_items, + uint64_t *seqnum, + Object **ret, uint64_t *offset) { + uint64_t np; + uint64_t osize; + Object *o; + int r; + + assert(f); + assert(f->header); + assert(items || n_items == 0); + assert(ts); + + osize = offsetof(Object, entry.items) + (n_items * sizeof(EntryItem)); + + r = journal_file_append_object(f, OBJECT_ENTRY, osize, &o, &np); + if (r < 0) + return r; + + o->entry.seqnum = htole64(journal_file_entry_seqnum(f, seqnum)); + memcpy_safe(o->entry.items, items, n_items * sizeof(EntryItem)); + o->entry.realtime = htole64(ts->realtime); + o->entry.monotonic = htole64(ts->monotonic); + o->entry.xor_hash = htole64(xor_hash); + o->entry.boot_id = f->header->boot_id; + +#ifdef HAVE_GCRYPT + r = journal_file_hmac_put_object(f, OBJECT_ENTRY, o, np); + if (r < 0) + return r; +#endif + + r = journal_file_link_entry(f, o, np); + if (r < 0) + return r; + + if (ret) + *ret = o; + + if (offset) + *offset = np; + + return 0; +} + +void journal_file_post_change(JournalFile *f) { + assert(f); + + /* inotify() does not receive IN_MODIFY events from file + * accesses done via mmap(). After each access we hence + * trigger IN_MODIFY by truncating the journal file to its + * current size which triggers IN_MODIFY. */ + + __sync_synchronize(); + + if (ftruncate(f->fd, f->last_stat.st_size) < 0) + log_debug_errno(errno, "Failed to truncate file to its own size: %m"); +} + +static int post_change_thunk(sd_event_source *timer, uint64_t usec, void *userdata) { + assert(userdata); + + journal_file_post_change(userdata); + + return 1; +} + +static void schedule_post_change(JournalFile *f) { + sd_event_source *timer; + int enabled, r; + uint64_t now; + + assert(f); + assert(f->post_change_timer); + + timer = f->post_change_timer; + + r = sd_event_source_get_enabled(timer, &enabled); + if (r < 0) { + log_debug_errno(r, "Failed to get ftruncate timer state: %m"); + goto fail; + } + + if (enabled == SD_EVENT_ONESHOT) + return; + + r = sd_event_now(sd_event_source_get_event(timer), CLOCK_MONOTONIC, &now); + if (r < 0) { + log_debug_errno(r, "Failed to get clock's now for scheduling ftruncate: %m"); + goto fail; + } + + r = sd_event_source_set_time(timer, now+f->post_change_timer_period); + if (r < 0) { + log_debug_errno(r, "Failed to set time for scheduling ftruncate: %m"); + goto fail; + } + + r = sd_event_source_set_enabled(timer, SD_EVENT_ONESHOT); + if (r < 0) { + log_debug_errno(r, "Failed to enable scheduled ftruncate: %m"); + goto fail; + } + + return; + +fail: + /* On failure, let's simply post the change immediately. */ + journal_file_post_change(f); +} + +/* Enable coalesced change posting in a timer on the provided sd_event instance */ +int journal_file_enable_post_change_timer(JournalFile *f, sd_event *e, usec_t t) { + _cleanup_(sd_event_source_unrefp) sd_event_source *timer = NULL; + int r; + + assert(f); + assert_return(!f->post_change_timer, -EINVAL); + assert(e); + assert(t); + + r = sd_event_add_time(e, &timer, CLOCK_MONOTONIC, 0, 0, post_change_thunk, f); + if (r < 0) + return r; + + r = sd_event_source_set_enabled(timer, SD_EVENT_OFF); + if (r < 0) + return r; + + f->post_change_timer = timer; + timer = NULL; + f->post_change_timer_period = t; + + return r; +} + +static int entry_item_cmp(const void *_a, const void *_b) { + const EntryItem *a = _a, *b = _b; + + if (le64toh(a->object_offset) < le64toh(b->object_offset)) + return -1; + if (le64toh(a->object_offset) > le64toh(b->object_offset)) + return 1; + return 0; +} + +int journal_file_append_entry(JournalFile *f, const dual_timestamp *ts, const struct iovec iovec[], unsigned n_iovec, uint64_t *seqnum, Object **ret, uint64_t *offset) { + unsigned i; + EntryItem *items; + int r; + uint64_t xor_hash = 0; + struct dual_timestamp _ts; + + assert(f); + assert(f->header); + assert(iovec || n_iovec == 0); + + if (!ts) { + dual_timestamp_get(&_ts); + ts = &_ts; + } + +#ifdef HAVE_GCRYPT + r = journal_file_maybe_append_tag(f, ts->realtime); + if (r < 0) + return r; +#endif + + /* alloca() can't take 0, hence let's allocate at least one */ + items = alloca(sizeof(EntryItem) * MAX(1u, n_iovec)); + + for (i = 0; i < n_iovec; i++) { + uint64_t p; + Object *o; + + r = journal_file_append_data(f, iovec[i].iov_base, iovec[i].iov_len, &o, &p); + if (r < 0) + return r; + + xor_hash ^= le64toh(o->data.hash); + items[i].object_offset = htole64(p); + items[i].hash = o->data.hash; + } + + /* Order by the position on disk, in order to improve seek + * times for rotating media. */ + qsort_safe(items, n_iovec, sizeof(EntryItem), entry_item_cmp); + + r = journal_file_append_entry_internal(f, ts, xor_hash, items, n_iovec, seqnum, ret, offset); + + /* If the memory mapping triggered a SIGBUS then we return an + * IO error and ignore the error code passed down to us, since + * it is very likely just an effect of a nullified replacement + * mapping page */ + + if (mmap_cache_got_sigbus(f->mmap, f->fd)) + r = -EIO; + + if (f->post_change_timer) + schedule_post_change(f); + else + journal_file_post_change(f); + + return r; +} + +typedef struct ChainCacheItem { + uint64_t first; /* the array at the beginning of the chain */ + uint64_t array; /* the cached array */ + uint64_t begin; /* the first item in the cached array */ + uint64_t total; /* the total number of items in all arrays before this one in the chain */ + uint64_t last_index; /* the last index we looked at, to optimize locality when bisecting */ +} ChainCacheItem; + +static void chain_cache_put( + OrderedHashmap *h, + ChainCacheItem *ci, + uint64_t first, + uint64_t array, + uint64_t begin, + uint64_t total, + uint64_t last_index) { + + if (!ci) { + /* If the chain item to cache for this chain is the + * first one it's not worth caching anything */ + if (array == first) + return; + + if (ordered_hashmap_size(h) >= CHAIN_CACHE_MAX) { + ci = ordered_hashmap_steal_first(h); + assert(ci); + } else { + ci = new(ChainCacheItem, 1); + if (!ci) + return; + } + + ci->first = first; + + if (ordered_hashmap_put(h, &ci->first, ci) < 0) { + free(ci); + return; + } + } else + assert(ci->first == first); + + ci->array = array; + ci->begin = begin; + ci->total = total; + ci->last_index = last_index; +} + +static int generic_array_get( + JournalFile *f, + uint64_t first, + uint64_t i, + Object **ret, uint64_t *offset) { + + Object *o; + uint64_t p = 0, a, t = 0; + int r; + ChainCacheItem *ci; + + assert(f); + + a = first; + + /* Try the chain cache first */ + ci = ordered_hashmap_get(f->chain_cache, &first); + if (ci && i > ci->total) { + a = ci->array; + i -= ci->total; + t = ci->total; + } + + while (a > 0) { + uint64_t k; + + r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); + if (r < 0) + return r; + + k = journal_file_entry_array_n_items(o); + if (i < k) { + p = le64toh(o->entry_array.items[i]); + goto found; + } + + i -= k; + t += k; + a = le64toh(o->entry_array.next_entry_array_offset); + } + + return 0; + +found: + /* Let's cache this item for the next invocation */ + chain_cache_put(f->chain_cache, ci, first, a, le64toh(o->entry_array.items[0]), t, i); + + r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o); + if (r < 0) + return r; + + if (ret) + *ret = o; + + if (offset) + *offset = p; + + return 1; +} + +static int generic_array_get_plus_one( + JournalFile *f, + uint64_t extra, + uint64_t first, + uint64_t i, + Object **ret, uint64_t *offset) { + + Object *o; + + assert(f); + + if (i == 0) { + int r; + + r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o); + if (r < 0) + return r; + + if (ret) + *ret = o; + + if (offset) + *offset = extra; + + return 1; + } + + return generic_array_get(f, first, i-1, ret, offset); +} + +enum { + TEST_FOUND, + TEST_LEFT, + TEST_RIGHT +}; + +static int generic_array_bisect( + JournalFile *f, + uint64_t first, + uint64_t n, + uint64_t needle, + int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle), + direction_t direction, + Object **ret, + uint64_t *offset, + uint64_t *idx) { + + uint64_t a, p, t = 0, i = 0, last_p = 0, last_index = (uint64_t) -1; + bool subtract_one = false; + Object *o, *array = NULL; + int r; + ChainCacheItem *ci; + + assert(f); + assert(test_object); + + /* Start with the first array in the chain */ + a = first; + + ci = ordered_hashmap_get(f->chain_cache, &first); + if (ci && n > ci->total) { + /* Ah, we have iterated this bisection array chain + * previously! Let's see if we can skip ahead in the + * chain, as far as the last time. But we can't jump + * backwards in the chain, so let's check that + * first. */ + + r = test_object(f, ci->begin, needle); + if (r < 0) + return r; + + if (r == TEST_LEFT) { + /* OK, what we are looking for is right of the + * begin of this EntryArray, so let's jump + * straight to previously cached array in the + * chain */ + + a = ci->array; + n -= ci->total; + t = ci->total; + last_index = ci->last_index; + } + } + + while (a > 0) { + uint64_t left, right, k, lp; + + r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &array); + if (r < 0) + return r; + + k = journal_file_entry_array_n_items(array); + right = MIN(k, n); + if (right <= 0) + return 0; + + i = right - 1; + lp = p = le64toh(array->entry_array.items[i]); + if (p <= 0) + r = -EBADMSG; + else + r = test_object(f, p, needle); + if (r == -EBADMSG) { + log_debug_errno(r, "Encountered invalid entry while bisecting, cutting algorithm short. (1)"); + n = i; + continue; + } + if (r < 0) + return r; + + if (r == TEST_FOUND) + r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT; + + if (r == TEST_RIGHT) { + left = 0; + right -= 1; + + if (last_index != (uint64_t) -1) { + assert(last_index <= right); + + /* If we cached the last index we + * looked at, let's try to not to jump + * too wildly around and see if we can + * limit the range to look at early to + * the immediate neighbors of the last + * index we looked at. */ + + if (last_index > 0) { + uint64_t x = last_index - 1; + + p = le64toh(array->entry_array.items[x]); + if (p <= 0) + return -EBADMSG; + + r = test_object(f, p, needle); + if (r < 0) + return r; + + if (r == TEST_FOUND) + r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT; + + if (r == TEST_RIGHT) + right = x; + else + left = x + 1; + } + + if (last_index < right) { + uint64_t y = last_index + 1; + + p = le64toh(array->entry_array.items[y]); + if (p <= 0) + return -EBADMSG; + + r = test_object(f, p, needle); + if (r < 0) + return r; + + if (r == TEST_FOUND) + r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT; + + if (r == TEST_RIGHT) + right = y; + else + left = y + 1; + } + } + + for (;;) { + if (left == right) { + if (direction == DIRECTION_UP) + subtract_one = true; + + i = left; + goto found; + } + + assert(left < right); + i = (left + right) / 2; + + p = le64toh(array->entry_array.items[i]); + if (p <= 0) + r = -EBADMSG; + else + r = test_object(f, p, needle); + if (r == -EBADMSG) { + log_debug_errno(r, "Encountered invalid entry while bisecting, cutting algorithm short. (2)"); + right = n = i; + continue; + } + if (r < 0) + return r; + + if (r == TEST_FOUND) + r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT; + + if (r == TEST_RIGHT) + right = i; + else + left = i + 1; + } + } + + if (k >= n) { + if (direction == DIRECTION_UP) { + i = n; + subtract_one = true; + goto found; + } + + return 0; + } + + last_p = lp; + + n -= k; + t += k; + last_index = (uint64_t) -1; + a = le64toh(array->entry_array.next_entry_array_offset); + } + + return 0; + +found: + if (subtract_one && t == 0 && i == 0) + return 0; + + /* Let's cache this item for the next invocation */ + chain_cache_put(f->chain_cache, ci, first, a, le64toh(array->entry_array.items[0]), t, subtract_one ? (i > 0 ? i-1 : (uint64_t) -1) : i); + + if (subtract_one && i == 0) + p = last_p; + else if (subtract_one) + p = le64toh(array->entry_array.items[i-1]); + else + p = le64toh(array->entry_array.items[i]); + + r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o); + if (r < 0) + return r; + + if (ret) + *ret = o; + + if (offset) + *offset = p; + + if (idx) + *idx = t + i + (subtract_one ? -1 : 0); + + return 1; +} + +static int generic_array_bisect_plus_one( + JournalFile *f, + uint64_t extra, + uint64_t first, + uint64_t n, + uint64_t needle, + int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle), + direction_t direction, + Object **ret, + uint64_t *offset, + uint64_t *idx) { + + int r; + bool step_back = false; + Object *o; + + assert(f); + assert(test_object); + + if (n <= 0) + return 0; + + /* This bisects the array in object 'first', but first checks + * an extra */ + r = test_object(f, extra, needle); + if (r < 0) + return r; + + if (r == TEST_FOUND) + r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT; + + /* if we are looking with DIRECTION_UP then we need to first + see if in the actual array there is a matching entry, and + return the last one of that. But if there isn't any we need + to return this one. Hence remember this, and return it + below. */ + if (r == TEST_LEFT) + step_back = direction == DIRECTION_UP; + + if (r == TEST_RIGHT) { + if (direction == DIRECTION_DOWN) + goto found; + else + return 0; + } + + r = generic_array_bisect(f, first, n-1, needle, test_object, direction, ret, offset, idx); + + if (r == 0 && step_back) + goto found; + + if (r > 0 && idx) + (*idx)++; + + return r; + +found: + r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o); + if (r < 0) + return r; + + if (ret) + *ret = o; + + if (offset) + *offset = extra; + + if (idx) + *idx = 0; + + return 1; +} + +_pure_ static int test_object_offset(JournalFile *f, uint64_t p, uint64_t needle) { + assert(f); + assert(p > 0); + + if (p == needle) + return TEST_FOUND; + else if (p < needle) + return TEST_LEFT; + else + return TEST_RIGHT; +} + +static int test_object_seqnum(JournalFile *f, uint64_t p, uint64_t needle) { + Object *o; + int r; + + assert(f); + assert(p > 0); + + r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o); + if (r < 0) + return r; + + if (le64toh(o->entry.seqnum) == needle) + return TEST_FOUND; + else if (le64toh(o->entry.seqnum) < needle) + return TEST_LEFT; + else + return TEST_RIGHT; +} + +int journal_file_move_to_entry_by_seqnum( + JournalFile *f, + uint64_t seqnum, + direction_t direction, + Object **ret, + uint64_t *offset) { + assert(f); + assert(f->header); + + return generic_array_bisect(f, + le64toh(f->header->entry_array_offset), + le64toh(f->header->n_entries), + seqnum, + test_object_seqnum, + direction, + ret, offset, NULL); +} + +static int test_object_realtime(JournalFile *f, uint64_t p, uint64_t needle) { + Object *o; + int r; + + assert(f); + assert(p > 0); + + r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o); + if (r < 0) + return r; + + if (le64toh(o->entry.realtime) == needle) + return TEST_FOUND; + else if (le64toh(o->entry.realtime) < needle) + return TEST_LEFT; + else + return TEST_RIGHT; +} + +int journal_file_move_to_entry_by_realtime( + JournalFile *f, + uint64_t realtime, + direction_t direction, + Object **ret, + uint64_t *offset) { + assert(f); + assert(f->header); + + return generic_array_bisect(f, + le64toh(f->header->entry_array_offset), + le64toh(f->header->n_entries), + realtime, + test_object_realtime, + direction, + ret, offset, NULL); +} + +static int test_object_monotonic(JournalFile *f, uint64_t p, uint64_t needle) { + Object *o; + int r; + + assert(f); + assert(p > 0); + + r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o); + if (r < 0) + return r; + + if (le64toh(o->entry.monotonic) == needle) + return TEST_FOUND; + else if (le64toh(o->entry.monotonic) < needle) + return TEST_LEFT; + else + return TEST_RIGHT; +} + +static int find_data_object_by_boot_id( + JournalFile *f, + sd_id128_t boot_id, + Object **o, + uint64_t *b) { + + char t[sizeof("_BOOT_ID=")-1 + 32 + 1] = "_BOOT_ID="; + + sd_id128_to_string(boot_id, t + 9); + return journal_file_find_data_object(f, t, sizeof(t) - 1, o, b); +} + +int journal_file_move_to_entry_by_monotonic( + JournalFile *f, + sd_id128_t boot_id, + uint64_t monotonic, + direction_t direction, + Object **ret, + uint64_t *offset) { + + Object *o; + int r; + + assert(f); + + r = find_data_object_by_boot_id(f, boot_id, &o, NULL); + if (r < 0) + return r; + if (r == 0) + return -ENOENT; + + return generic_array_bisect_plus_one(f, + le64toh(o->data.entry_offset), + le64toh(o->data.entry_array_offset), + le64toh(o->data.n_entries), + monotonic, + test_object_monotonic, + direction, + ret, offset, NULL); +} + +void journal_file_reset_location(JournalFile *f) { + f->location_type = LOCATION_HEAD; + f->current_offset = 0; + f->current_seqnum = 0; + f->current_realtime = 0; + f->current_monotonic = 0; + zero(f->current_boot_id); + f->current_xor_hash = 0; +} + +void journal_file_save_location(JournalFile *f, Object *o, uint64_t offset) { + f->location_type = LOCATION_SEEK; + f->current_offset = offset; + f->current_seqnum = le64toh(o->entry.seqnum); + f->current_realtime = le64toh(o->entry.realtime); + f->current_monotonic = le64toh(o->entry.monotonic); + f->current_boot_id = o->entry.boot_id; + f->current_xor_hash = le64toh(o->entry.xor_hash); +} + +int journal_file_compare_locations(JournalFile *af, JournalFile *bf) { + assert(af); + assert(af->header); + assert(bf); + assert(bf->header); + assert(af->location_type == LOCATION_SEEK); + assert(bf->location_type == LOCATION_SEEK); + + /* If contents and timestamps match, these entries are + * identical, even if the seqnum does not match */ + if (sd_id128_equal(af->current_boot_id, bf->current_boot_id) && + af->current_monotonic == bf->current_monotonic && + af->current_realtime == bf->current_realtime && + af->current_xor_hash == bf->current_xor_hash) + return 0; + + if (sd_id128_equal(af->header->seqnum_id, bf->header->seqnum_id)) { + + /* If this is from the same seqnum source, compare + * seqnums */ + if (af->current_seqnum < bf->current_seqnum) + return -1; + if (af->current_seqnum > bf->current_seqnum) + return 1; + + /* Wow! This is weird, different data but the same + * seqnums? Something is borked, but let's make the + * best of it and compare by time. */ + } + + if (sd_id128_equal(af->current_boot_id, bf->current_boot_id)) { + + /* If the boot id matches, compare monotonic time */ + if (af->current_monotonic < bf->current_monotonic) + return -1; + if (af->current_monotonic > bf->current_monotonic) + return 1; + } + + /* Otherwise, compare UTC time */ + if (af->current_realtime < bf->current_realtime) + return -1; + if (af->current_realtime > bf->current_realtime) + return 1; + + /* Finally, compare by contents */ + if (af->current_xor_hash < bf->current_xor_hash) + return -1; + if (af->current_xor_hash > bf->current_xor_hash) + return 1; + + return 0; +} + +int journal_file_next_entry( + JournalFile *f, + uint64_t p, + direction_t direction, + Object **ret, uint64_t *offset) { + + uint64_t i, n, ofs; + int r; + + assert(f); + assert(f->header); + + n = le64toh(f->header->n_entries); + if (n <= 0) + return 0; + + if (p == 0) + i = direction == DIRECTION_DOWN ? 0 : n - 1; + else { + r = generic_array_bisect(f, + le64toh(f->header->entry_array_offset), + le64toh(f->header->n_entries), + p, + test_object_offset, + DIRECTION_DOWN, + NULL, NULL, + &i); + if (r <= 0) + return r; + + if (direction == DIRECTION_DOWN) { + if (i >= n - 1) + return 0; + + i++; + } else { + if (i <= 0) + return 0; + + i--; + } + } + + /* And jump to it */ + r = generic_array_get(f, + le64toh(f->header->entry_array_offset), + i, + ret, &ofs); + if (r == -EBADMSG && direction == DIRECTION_DOWN) { + /* Special case: when we iterate throught the journal file linearly, and hit an entry we can't read, + * consider this the end of the journal file. */ + log_debug_errno(r, "Encountered entry we can't read while iterating through journal file. Considering this the end of the file."); + return 0; + } + if (r <= 0) + return r; + + if (p > 0 && + (direction == DIRECTION_DOWN ? ofs <= p : ofs >= p)) { + log_debug("%s: entry array corrupted at entry %" PRIu64, f->path, i); + return -EBADMSG; + } + + if (offset) + *offset = ofs; + + return 1; +} + +int journal_file_next_entry_for_data( + JournalFile *f, + Object *o, uint64_t p, + uint64_t data_offset, + direction_t direction, + Object **ret, uint64_t *offset) { + + uint64_t n, i; + int r; + Object *d; + + assert(f); + assert(p > 0 || !o); + + r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d); + if (r < 0) + return r; + + n = le64toh(d->data.n_entries); + if (n <= 0) + return n; + + if (!o) + i = direction == DIRECTION_DOWN ? 0 : n - 1; + else { + if (o->object.type != OBJECT_ENTRY) + return -EINVAL; + + r = generic_array_bisect_plus_one(f, + le64toh(d->data.entry_offset), + le64toh(d->data.entry_array_offset), + le64toh(d->data.n_entries), + p, + test_object_offset, + DIRECTION_DOWN, + NULL, NULL, + &i); + + if (r <= 0) + return r; + + if (direction == DIRECTION_DOWN) { + if (i >= n - 1) + return 0; + + i++; + } else { + if (i <= 0) + return 0; + + i--; + } + + } + + return generic_array_get_plus_one(f, + le64toh(d->data.entry_offset), + le64toh(d->data.entry_array_offset), + i, + ret, offset); +} + +int journal_file_move_to_entry_by_offset_for_data( + JournalFile *f, + uint64_t data_offset, + uint64_t p, + direction_t direction, + Object **ret, uint64_t *offset) { + + int r; + Object *d; + + assert(f); + + r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d); + if (r < 0) + return r; + + return generic_array_bisect_plus_one(f, + le64toh(d->data.entry_offset), + le64toh(d->data.entry_array_offset), + le64toh(d->data.n_entries), + p, + test_object_offset, + direction, + ret, offset, NULL); +} + +int journal_file_move_to_entry_by_monotonic_for_data( + JournalFile *f, + uint64_t data_offset, + sd_id128_t boot_id, + uint64_t monotonic, + direction_t direction, + Object **ret, uint64_t *offset) { + + Object *o, *d; + int r; + uint64_t b, z; + + assert(f); + + /* First, seek by time */ + r = find_data_object_by_boot_id(f, boot_id, &o, &b); + if (r < 0) + return r; + if (r == 0) + return -ENOENT; + + r = generic_array_bisect_plus_one(f, + le64toh(o->data.entry_offset), + le64toh(o->data.entry_array_offset), + le64toh(o->data.n_entries), + monotonic, + test_object_monotonic, + direction, + NULL, &z, NULL); + if (r <= 0) + return r; + + /* And now, continue seeking until we find an entry that + * exists in both bisection arrays */ + + for (;;) { + Object *qo; + uint64_t p, q; + + r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d); + if (r < 0) + return r; + + r = generic_array_bisect_plus_one(f, + le64toh(d->data.entry_offset), + le64toh(d->data.entry_array_offset), + le64toh(d->data.n_entries), + z, + test_object_offset, + direction, + NULL, &p, NULL); + if (r <= 0) + return r; + + r = journal_file_move_to_object(f, OBJECT_DATA, b, &o); + if (r < 0) + return r; + + r = generic_array_bisect_plus_one(f, + le64toh(o->data.entry_offset), + le64toh(o->data.entry_array_offset), + le64toh(o->data.n_entries), + p, + test_object_offset, + direction, + &qo, &q, NULL); + + if (r <= 0) + return r; + + if (p == q) { + if (ret) + *ret = qo; + if (offset) + *offset = q; + + return 1; + } + + z = q; + } +} + +int journal_file_move_to_entry_by_seqnum_for_data( + JournalFile *f, + uint64_t data_offset, + uint64_t seqnum, + direction_t direction, + Object **ret, uint64_t *offset) { + + Object *d; + int r; + + assert(f); + + r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d); + if (r < 0) + return r; + + return generic_array_bisect_plus_one(f, + le64toh(d->data.entry_offset), + le64toh(d->data.entry_array_offset), + le64toh(d->data.n_entries), + seqnum, + test_object_seqnum, + direction, + ret, offset, NULL); +} + +int journal_file_move_to_entry_by_realtime_for_data( + JournalFile *f, + uint64_t data_offset, + uint64_t realtime, + direction_t direction, + Object **ret, uint64_t *offset) { + + Object *d; + int r; + + assert(f); + + r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d); + if (r < 0) + return r; + + return generic_array_bisect_plus_one(f, + le64toh(d->data.entry_offset), + le64toh(d->data.entry_array_offset), + le64toh(d->data.n_entries), + realtime, + test_object_realtime, + direction, + ret, offset, NULL); +} + +void journal_file_dump(JournalFile *f) { + Object *o; + int r; + uint64_t p; + + assert(f); + assert(f->header); + + journal_file_print_header(f); + + p = le64toh(f->header->header_size); + while (p != 0) { + r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o); + if (r < 0) + goto fail; + + switch (o->object.type) { + + case OBJECT_UNUSED: + printf("Type: OBJECT_UNUSED\n"); + break; + + case OBJECT_DATA: + printf("Type: OBJECT_DATA\n"); + break; + + case OBJECT_FIELD: + printf("Type: OBJECT_FIELD\n"); + break; + + case OBJECT_ENTRY: + printf("Type: OBJECT_ENTRY seqnum=%"PRIu64" monotonic=%"PRIu64" realtime=%"PRIu64"\n", + le64toh(o->entry.seqnum), + le64toh(o->entry.monotonic), + le64toh(o->entry.realtime)); + break; + + case OBJECT_FIELD_HASH_TABLE: + printf("Type: OBJECT_FIELD_HASH_TABLE\n"); + break; + + case OBJECT_DATA_HASH_TABLE: + printf("Type: OBJECT_DATA_HASH_TABLE\n"); + break; + + case OBJECT_ENTRY_ARRAY: + printf("Type: OBJECT_ENTRY_ARRAY\n"); + break; + + case OBJECT_TAG: + printf("Type: OBJECT_TAG seqnum=%"PRIu64" epoch=%"PRIu64"\n", + le64toh(o->tag.seqnum), + le64toh(o->tag.epoch)); + break; + + default: + printf("Type: unknown (%i)\n", o->object.type); + break; + } + + if (o->object.flags & OBJECT_COMPRESSION_MASK) + printf("Flags: %s\n", + object_compressed_to_string(o->object.flags & OBJECT_COMPRESSION_MASK)); + + if (p == le64toh(f->header->tail_object_offset)) + p = 0; + else + p = p + ALIGN64(le64toh(o->object.size)); + } + + return; +fail: + log_error("File corrupt"); +} + +static const char* format_timestamp_safe(char *buf, size_t l, usec_t t) { + const char *x; + + x = format_timestamp(buf, l, t); + if (x) + return x; + return " --- "; +} + +void journal_file_print_header(JournalFile *f) { + char a[33], b[33], c[33], d[33]; + char x[FORMAT_TIMESTAMP_MAX], y[FORMAT_TIMESTAMP_MAX], z[FORMAT_TIMESTAMP_MAX]; + struct stat st; + char bytes[FORMAT_BYTES_MAX]; + + assert(f); + assert(f->header); + + printf("File Path: %s\n" + "File ID: %s\n" + "Machine ID: %s\n" + "Boot ID: %s\n" + "Sequential Number ID: %s\n" + "State: %s\n" + "Compatible Flags:%s%s\n" + "Incompatible Flags:%s%s%s\n" + "Header size: %"PRIu64"\n" + "Arena size: %"PRIu64"\n" + "Data Hash Table Size: %"PRIu64"\n" + "Field Hash Table Size: %"PRIu64"\n" + "Rotate Suggested: %s\n" + "Head Sequential Number: %"PRIu64" (%"PRIx64")\n" + "Tail Sequential Number: %"PRIu64" (%"PRIx64")\n" + "Head Realtime Timestamp: %s (%"PRIx64")\n" + "Tail Realtime Timestamp: %s (%"PRIx64")\n" + "Tail Monotonic Timestamp: %s (%"PRIx64")\n" + "Objects: %"PRIu64"\n" + "Entry Objects: %"PRIu64"\n", + f->path, + sd_id128_to_string(f->header->file_id, a), + sd_id128_to_string(f->header->machine_id, b), + sd_id128_to_string(f->header->boot_id, c), + sd_id128_to_string(f->header->seqnum_id, d), + f->header->state == STATE_OFFLINE ? "OFFLINE" : + f->header->state == STATE_ONLINE ? "ONLINE" : + f->header->state == STATE_ARCHIVED ? "ARCHIVED" : "UNKNOWN", + JOURNAL_HEADER_SEALED(f->header) ? " SEALED" : "", + (le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_ANY) ? " ???" : "", + JOURNAL_HEADER_COMPRESSED_XZ(f->header) ? " COMPRESSED-XZ" : "", + JOURNAL_HEADER_COMPRESSED_LZ4(f->header) ? " COMPRESSED-LZ4" : "", + (le32toh(f->header->incompatible_flags) & ~HEADER_INCOMPATIBLE_ANY) ? " ???" : "", + le64toh(f->header->header_size), + le64toh(f->header->arena_size), + le64toh(f->header->data_hash_table_size) / sizeof(HashItem), + le64toh(f->header->field_hash_table_size) / sizeof(HashItem), + yes_no(journal_file_rotate_suggested(f, 0)), + le64toh(f->header->head_entry_seqnum), le64toh(f->header->head_entry_seqnum), + le64toh(f->header->tail_entry_seqnum), le64toh(f->header->tail_entry_seqnum), + format_timestamp_safe(x, sizeof(x), le64toh(f->header->head_entry_realtime)), le64toh(f->header->head_entry_realtime), + format_timestamp_safe(y, sizeof(y), le64toh(f->header->tail_entry_realtime)), le64toh(f->header->tail_entry_realtime), + format_timespan(z, sizeof(z), le64toh(f->header->tail_entry_monotonic), USEC_PER_MSEC), le64toh(f->header->tail_entry_monotonic), + le64toh(f->header->n_objects), + le64toh(f->header->n_entries)); + + if (JOURNAL_HEADER_CONTAINS(f->header, n_data)) + printf("Data Objects: %"PRIu64"\n" + "Data Hash Table Fill: %.1f%%\n", + le64toh(f->header->n_data), + 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)))); + + if (JOURNAL_HEADER_CONTAINS(f->header, n_fields)) + printf("Field Objects: %"PRIu64"\n" + "Field Hash Table Fill: %.1f%%\n", + le64toh(f->header->n_fields), + 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)))); + + if (JOURNAL_HEADER_CONTAINS(f->header, n_tags)) + printf("Tag Objects: %"PRIu64"\n", + le64toh(f->header->n_tags)); + if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays)) + printf("Entry Array Objects: %"PRIu64"\n", + le64toh(f->header->n_entry_arrays)); + + if (fstat(f->fd, &st) >= 0) + printf("Disk usage: %s\n", format_bytes(bytes, sizeof(bytes), (uint64_t) st.st_blocks * 512ULL)); +} + +static int journal_file_warn_btrfs(JournalFile *f) { + unsigned attrs; + int r; + + assert(f); + + /* Before we write anything, check if the COW logic is turned + * off on btrfs. Given our write pattern that is quite + * unfriendly to COW file systems this should greatly improve + * performance on COW file systems, such as btrfs, at the + * expense of data integrity features (which shouldn't be too + * bad, given that we do our own checksumming). */ + + r = btrfs_is_filesystem(f->fd); + if (r < 0) + return log_warning_errno(r, "Failed to determine if journal is on btrfs: %m"); + if (!r) + return 0; + + r = read_attr_fd(f->fd, &attrs); + if (r < 0) + return log_warning_errno(r, "Failed to read file attributes: %m"); + + if (attrs & FS_NOCOW_FL) { + log_debug("Detected btrfs file system with copy-on-write disabled, all is good."); + return 0; + } + + log_notice("Creating journal file %s on a btrfs file system, and copy-on-write is enabled. " + "This is likely to slow down journal access substantially, please consider turning " + "off the copy-on-write file attribute on the journal directory, using chattr +C.", f->path); + + return 1; +} + +int journal_file_open( + int fd, + const char *fname, + int flags, + mode_t mode, + bool compress, + bool seal, + JournalMetrics *metrics, + MMapCache *mmap_cache, + Set *deferred_closes, + JournalFile *template, + JournalFile **ret) { + + bool newly_created = false; + JournalFile *f; + void *h; + int r; + + assert(ret); + assert(fd >= 0 || fname); + + if ((flags & O_ACCMODE) != O_RDONLY && + (flags & O_ACCMODE) != O_RDWR) + return -EINVAL; + + if (fname) { + if (!endswith(fname, ".journal") && + !endswith(fname, ".journal~")) + return -EINVAL; + } + + f = new0(JournalFile, 1); + if (!f) + return -ENOMEM; + + f->fd = fd; + f->mode = mode; + + f->flags = flags; + f->prot = prot_from_flags(flags); + f->writable = (flags & O_ACCMODE) != O_RDONLY; +#if defined(HAVE_LZ4) + f->compress_lz4 = compress; +#elif defined(HAVE_XZ) + f->compress_xz = compress; +#endif +#ifdef HAVE_GCRYPT + f->seal = seal; +#endif + + if (mmap_cache) + f->mmap = mmap_cache_ref(mmap_cache); + else { + f->mmap = mmap_cache_new(); + if (!f->mmap) { + r = -ENOMEM; + goto fail; + } + } + + if (fname) + f->path = strdup(fname); + else /* If we don't know the path, fill in something explanatory and vaguely useful */ + asprintf(&f->path, "/proc/self/%i", fd); + if (!f->path) { + r = -ENOMEM; + goto fail; + } + + f->chain_cache = ordered_hashmap_new(&uint64_hash_ops); + if (!f->chain_cache) { + r = -ENOMEM; + goto fail; + } + + if (f->fd < 0) { + f->fd = open(f->path, f->flags|O_CLOEXEC, f->mode); + if (f->fd < 0) { + r = -errno; + goto fail; + } + + /* fds we opened here by us should also be closed by us. */ + f->close_fd = true; + } + + r = journal_file_fstat(f); + if (r < 0) + goto fail; + + if (f->last_stat.st_size == 0 && f->writable) { + + (void) journal_file_warn_btrfs(f); + + /* Let's attach the creation time to the journal file, + * so that the vacuuming code knows the age of this + * file even if the file might end up corrupted one + * day... Ideally we'd just use the creation time many + * file systems maintain for each file, but there is + * currently no usable API to query this, hence let's + * emulate this via extended attributes. If extended + * attributes are not supported we'll just skip this, + * and rely solely on mtime/atime/ctime of the file. */ + + fd_setcrtime(f->fd, 0); + +#ifdef HAVE_GCRYPT + /* Try to load the FSPRG state, and if we can't, then + * just don't do sealing */ + if (f->seal) { + r = journal_file_fss_load(f); + if (r < 0) + f->seal = false; + } +#endif + + r = journal_file_init_header(f, template); + if (r < 0) + goto fail; + + r = journal_file_fstat(f); + if (r < 0) + goto fail; + + newly_created = true; + } + + if (f->last_stat.st_size < (off_t) HEADER_SIZE_MIN) { + r = -ENODATA; + goto fail; + } + + r = mmap_cache_get(f->mmap, f->fd, f->prot, CONTEXT_HEADER, true, 0, PAGE_ALIGN(sizeof(Header)), &f->last_stat, &h); + if (r < 0) + goto fail; + + f->header = h; + + if (!newly_created) { + if (deferred_closes) + journal_file_close_set(deferred_closes); + + r = journal_file_verify_header(f); + if (r < 0) + goto fail; + } + +#ifdef HAVE_GCRYPT + if (!newly_created && f->writable) { + r = journal_file_fss_load(f); + if (r < 0) + goto fail; + } +#endif + + if (f->writable) { + if (metrics) { + journal_default_metrics(metrics, f->fd); + f->metrics = *metrics; + } else if (template) + f->metrics = template->metrics; + + r = journal_file_refresh_header(f); + if (r < 0) + goto fail; + } + +#ifdef HAVE_GCRYPT + r = journal_file_hmac_setup(f); + if (r < 0) + goto fail; +#endif + + if (newly_created) { + r = journal_file_setup_field_hash_table(f); + if (r < 0) + goto fail; + + r = journal_file_setup_data_hash_table(f); + if (r < 0) + goto fail; + +#ifdef HAVE_GCRYPT + r = journal_file_append_first_tag(f); + if (r < 0) + goto fail; +#endif + } + + if (mmap_cache_got_sigbus(f->mmap, f->fd)) { + r = -EIO; + goto fail; + } + + if (template && template->post_change_timer) { + r = journal_file_enable_post_change_timer( + f, + sd_event_source_get_event(template->post_change_timer), + template->post_change_timer_period); + + if (r < 0) + goto fail; + } + + /* The file is opened now successfully, thus we take possession of any passed in fd. */ + f->close_fd = true; + + *ret = f; + return 0; + +fail: + if (f->fd >= 0 && mmap_cache_got_sigbus(f->mmap, f->fd)) + r = -EIO; + + (void) journal_file_close(f); + + return r; +} + +int journal_file_rotate(JournalFile **f, bool compress, bool seal, Set *deferred_closes) { + _cleanup_free_ char *p = NULL; + size_t l; + JournalFile *old_file, *new_file = NULL; + int r; + + assert(f); + assert(*f); + + old_file = *f; + + if (!old_file->writable) + return -EINVAL; + + /* Is this a journal file that was passed to us as fd? If so, we synthesized a path name for it, and we refuse + * rotation, since we don't know the actual path, and couldn't rename the file hence.*/ + if (path_startswith(old_file->path, "/proc/self/fd")) + return -EINVAL; + + if (!endswith(old_file->path, ".journal")) + return -EINVAL; + + l = strlen(old_file->path); + r = asprintf(&p, "%.*s@" SD_ID128_FORMAT_STR "-%016"PRIx64"-%016"PRIx64".journal", + (int) l - 8, old_file->path, + SD_ID128_FORMAT_VAL(old_file->header->seqnum_id), + le64toh((*f)->header->head_entry_seqnum), + le64toh((*f)->header->head_entry_realtime)); + if (r < 0) + return -ENOMEM; + + /* Try to rename the file to the archived version. If the file + * already was deleted, we'll get ENOENT, let's ignore that + * case. */ + r = rename(old_file->path, p); + if (r < 0 && errno != ENOENT) + return -errno; + + /* Sync the rename to disk */ + (void) fsync_directory_of_file(old_file->fd); + + /* Set as archive so offlining commits w/state=STATE_ARCHIVED. + * Previously we would set old_file->header->state to STATE_ARCHIVED directly here, + * but journal_file_set_offline() short-circuits when state != STATE_ONLINE, which + * would result in the rotated journal never getting fsync() called before closing. + * Now we simply queue the archive state by setting an archive bit, leaving the state + * as STATE_ONLINE so proper offlining occurs. */ + old_file->archive = true; + + /* Currently, btrfs is not very good with out write patterns + * and fragments heavily. Let's defrag our journal files when + * we archive them */ + old_file->defrag_on_close = true; + + r = journal_file_open(-1, old_file->path, old_file->flags, old_file->mode, compress, seal, NULL, old_file->mmap, deferred_closes, old_file, &new_file); + + if (deferred_closes && + set_put(deferred_closes, old_file) >= 0) + (void) journal_file_set_offline(old_file, false); + else + (void) journal_file_close(old_file); + + *f = new_file; + return r; +} + +int journal_file_open_reliably( + const char *fname, + int flags, + mode_t mode, + bool compress, + bool seal, + JournalMetrics *metrics, + MMapCache *mmap_cache, + Set *deferred_closes, + JournalFile *template, + JournalFile **ret) { + + int r; + size_t l; + _cleanup_free_ char *p = NULL; + + r = journal_file_open(-1, fname, flags, mode, compress, seal, metrics, mmap_cache, deferred_closes, template, ret); + if (!IN_SET(r, + -EBADMSG, /* corrupted */ + -ENODATA, /* truncated */ + -EHOSTDOWN, /* other machine */ + -EPROTONOSUPPORT, /* incompatible feature */ + -EBUSY, /* unclean shutdown */ + -ESHUTDOWN, /* already archived */ + -EIO, /* IO error, including SIGBUS on mmap */ + -EIDRM /* File has been deleted */)) + return r; + + if ((flags & O_ACCMODE) == O_RDONLY) + return r; + + if (!(flags & O_CREAT)) + return r; + + if (!endswith(fname, ".journal")) + return r; + + /* The file is corrupted. Rotate it away and try it again (but only once) */ + + l = strlen(fname); + if (asprintf(&p, "%.*s@%016"PRIx64 "-%016"PRIx64 ".journal~", + (int) l - 8, fname, + now(CLOCK_REALTIME), + random_u64()) < 0) + return -ENOMEM; + + if (rename(fname, p) < 0) + return -errno; + + /* btrfs doesn't cope well with our write pattern and + * fragments heavily. Let's defrag all files we rotate */ + + (void) chattr_path(p, 0, FS_NOCOW_FL); + (void) btrfs_defrag(p); + + log_warning_errno(r, "File %s corrupted or uncleanly shut down, renaming and replacing.", fname); + + return journal_file_open(-1, fname, flags, mode, compress, seal, metrics, mmap_cache, deferred_closes, template, ret); +} + +int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset) { + uint64_t i, n; + uint64_t q, xor_hash = 0; + int r; + EntryItem *items; + dual_timestamp ts; + + assert(from); + assert(to); + assert(o); + assert(p); + + if (!to->writable) + return -EPERM; + + ts.monotonic = le64toh(o->entry.monotonic); + ts.realtime = le64toh(o->entry.realtime); + + n = journal_file_entry_n_items(o); + /* alloca() can't take 0, hence let's allocate at least one */ + items = alloca(sizeof(EntryItem) * MAX(1u, n)); + + for (i = 0; i < n; i++) { + uint64_t l, h; + le64_t le_hash; + size_t t; + void *data; + Object *u; + + q = le64toh(o->entry.items[i].object_offset); + le_hash = o->entry.items[i].hash; + + r = journal_file_move_to_object(from, OBJECT_DATA, q, &o); + if (r < 0) + return r; + + if (le_hash != o->data.hash) + return -EBADMSG; + + l = le64toh(o->object.size) - offsetof(Object, data.payload); + t = (size_t) l; + + /* We hit the limit on 32bit machines */ + if ((uint64_t) t != l) + return -E2BIG; + + if (o->object.flags & OBJECT_COMPRESSION_MASK) { +#if defined(HAVE_XZ) || defined(HAVE_LZ4) + size_t rsize = 0; + + r = decompress_blob(o->object.flags & OBJECT_COMPRESSION_MASK, + o->data.payload, l, &from->compress_buffer, &from->compress_buffer_size, &rsize, 0); + if (r < 0) + return r; + + data = from->compress_buffer; + l = rsize; +#else + return -EPROTONOSUPPORT; +#endif + } else + data = o->data.payload; + + r = journal_file_append_data(to, data, l, &u, &h); + if (r < 0) + return r; + + xor_hash ^= le64toh(u->data.hash); + items[i].object_offset = htole64(h); + items[i].hash = u->data.hash; + + r = journal_file_move_to_object(from, OBJECT_ENTRY, p, &o); + if (r < 0) + return r; + } + + r = journal_file_append_entry_internal(to, &ts, xor_hash, items, n, seqnum, ret, offset); + + if (mmap_cache_got_sigbus(to->mmap, to->fd)) + return -EIO; + + return r; +} + +void journal_reset_metrics(JournalMetrics *m) { + assert(m); + + /* Set everything to "pick automatic values". */ + + *m = (JournalMetrics) { + .min_use = (uint64_t) -1, + .max_use = (uint64_t) -1, + .min_size = (uint64_t) -1, + .max_size = (uint64_t) -1, + .keep_free = (uint64_t) -1, + .n_max_files = (uint64_t) -1, + }; +} + +void journal_default_metrics(JournalMetrics *m, int fd) { + char a[FORMAT_BYTES_MAX], b[FORMAT_BYTES_MAX], c[FORMAT_BYTES_MAX], d[FORMAT_BYTES_MAX], e[FORMAT_BYTES_MAX]; + struct statvfs ss; + uint64_t fs_size; + + assert(m); + assert(fd >= 0); + + if (fstatvfs(fd, &ss) >= 0) + fs_size = ss.f_frsize * ss.f_blocks; + else { + log_debug_errno(errno, "Failed to detremine disk size: %m"); + fs_size = 0; + } + + if (m->max_use == (uint64_t) -1) { + + if (fs_size > 0) { + m->max_use = PAGE_ALIGN(fs_size / 10); /* 10% of file system size */ + + if (m->max_use > DEFAULT_MAX_USE_UPPER) + m->max_use = DEFAULT_MAX_USE_UPPER; + + if (m->max_use < DEFAULT_MAX_USE_LOWER) + m->max_use = DEFAULT_MAX_USE_LOWER; + } else + m->max_use = DEFAULT_MAX_USE_LOWER; + } else { + m->max_use = PAGE_ALIGN(m->max_use); + + if (m->max_use != 0 && m->max_use < JOURNAL_FILE_SIZE_MIN*2) + m->max_use = JOURNAL_FILE_SIZE_MIN*2; + } + + if (m->min_use == (uint64_t) -1) + m->min_use = DEFAULT_MIN_USE; + + if (m->min_use > m->max_use) + m->min_use = m->max_use; + + if (m->max_size == (uint64_t) -1) { + m->max_size = PAGE_ALIGN(m->max_use / 8); /* 8 chunks */ + + if (m->max_size > DEFAULT_MAX_SIZE_UPPER) + m->max_size = DEFAULT_MAX_SIZE_UPPER; + } else + m->max_size = PAGE_ALIGN(m->max_size); + + if (m->max_size != 0) { + if (m->max_size < JOURNAL_FILE_SIZE_MIN) + m->max_size = JOURNAL_FILE_SIZE_MIN; + + if (m->max_use != 0 && m->max_size*2 > m->max_use) + m->max_use = m->max_size*2; + } + + if (m->min_size == (uint64_t) -1) + m->min_size = JOURNAL_FILE_SIZE_MIN; + else { + m->min_size = PAGE_ALIGN(m->min_size); + + if (m->min_size < JOURNAL_FILE_SIZE_MIN) + m->min_size = JOURNAL_FILE_SIZE_MIN; + + if (m->max_size != 0 && m->min_size > m->max_size) + m->max_size = m->min_size; + } + + if (m->keep_free == (uint64_t) -1) { + + if (fs_size > 0) { + m->keep_free = PAGE_ALIGN(fs_size * 3 / 20); /* 15% of file system size */ + + if (m->keep_free > DEFAULT_KEEP_FREE_UPPER) + m->keep_free = DEFAULT_KEEP_FREE_UPPER; + + } else + m->keep_free = DEFAULT_KEEP_FREE; + } + + if (m->n_max_files == (uint64_t) -1) + m->n_max_files = DEFAULT_N_MAX_FILES; + + log_debug("Fixed min_use=%s max_use=%s max_size=%s min_size=%s keep_free=%s n_max_files=%" PRIu64, + format_bytes(a, sizeof(a), m->min_use), + format_bytes(b, sizeof(b), m->max_use), + format_bytes(c, sizeof(c), m->max_size), + format_bytes(d, sizeof(d), m->min_size), + format_bytes(e, sizeof(e), m->keep_free), + m->n_max_files); +} + +int journal_file_get_cutoff_realtime_usec(JournalFile *f, usec_t *from, usec_t *to) { + assert(f); + assert(f->header); + assert(from || to); + + if (from) { + if (f->header->head_entry_realtime == 0) + return -ENOENT; + + *from = le64toh(f->header->head_entry_realtime); + } + + if (to) { + if (f->header->tail_entry_realtime == 0) + return -ENOENT; + + *to = le64toh(f->header->tail_entry_realtime); + } + + return 1; +} + +int journal_file_get_cutoff_monotonic_usec(JournalFile *f, sd_id128_t boot_id, usec_t *from, usec_t *to) { + Object *o; + uint64_t p; + int r; + + assert(f); + assert(from || to); + + r = find_data_object_by_boot_id(f, boot_id, &o, &p); + if (r <= 0) + return r; + + if (le64toh(o->data.n_entries) <= 0) + return 0; + + if (from) { + r = journal_file_move_to_object(f, OBJECT_ENTRY, le64toh(o->data.entry_offset), &o); + if (r < 0) + return r; + + *from = le64toh(o->entry.monotonic); + } + + if (to) { + r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); + if (r < 0) + return r; + + r = generic_array_get_plus_one(f, + le64toh(o->data.entry_offset), + le64toh(o->data.entry_array_offset), + le64toh(o->data.n_entries)-1, + &o, NULL); + if (r <= 0) + return r; + + *to = le64toh(o->entry.monotonic); + } + + return 1; +} + +bool journal_file_rotate_suggested(JournalFile *f, usec_t max_file_usec) { + assert(f); + assert(f->header); + + /* If we gained new header fields we gained new features, + * hence suggest a rotation */ + if (le64toh(f->header->header_size) < sizeof(Header)) { + log_debug("%s uses an outdated header, suggesting rotation.", f->path); + return true; + } + + /* Let's check if the hash tables grew over a certain fill + * level (75%, borrowing this value from Java's hash table + * implementation), and if so suggest a rotation. To calculate + * the fill level we need the n_data field, which only exists + * in newer versions. */ + + if (JOURNAL_HEADER_CONTAINS(f->header, n_data)) + if (le64toh(f->header->n_data) * 4ULL > (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)) * 3ULL) { + log_debug("Data hash table of %s has a fill level at %.1f (%"PRIu64" of %"PRIu64" items, %llu file size, %"PRIu64" bytes per hash table item), suggesting rotation.", + f->path, + 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))), + le64toh(f->header->n_data), + le64toh(f->header->data_hash_table_size) / sizeof(HashItem), + (unsigned long long) f->last_stat.st_size, + f->last_stat.st_size / le64toh(f->header->n_data)); + return true; + } + + if (JOURNAL_HEADER_CONTAINS(f->header, n_fields)) + if (le64toh(f->header->n_fields) * 4ULL > (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)) * 3ULL) { + log_debug("Field hash table of %s has a fill level at %.1f (%"PRIu64" of %"PRIu64" items), suggesting rotation.", + f->path, + 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))), + le64toh(f->header->n_fields), + le64toh(f->header->field_hash_table_size) / sizeof(HashItem)); + return true; + } + + /* Are the data objects properly indexed by field objects? */ + if (JOURNAL_HEADER_CONTAINS(f->header, n_data) && + JOURNAL_HEADER_CONTAINS(f->header, n_fields) && + le64toh(f->header->n_data) > 0 && + le64toh(f->header->n_fields) == 0) + return true; + + if (max_file_usec > 0) { + usec_t t, h; + + h = le64toh(f->header->head_entry_realtime); + t = now(CLOCK_REALTIME); + + if (h > 0 && t > h + max_file_usec) + return true; + } + + return false; +} diff --git a/src/libsystemd/libsystemd-journal-internal/journal-file.h b/src/libsystemd/libsystemd-journal-internal/journal-file.h new file mode 100644 index 0000000000..e48e98f424 --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/journal-file.h @@ -0,0 +1,265 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 + +#ifdef HAVE_GCRYPT +#include +#endif + +#include + +#include "hashmap.h" +#include "journal-def.h" +#include "macro.h" +#include "mmap-cache.h" +#include +#include "sparse-endian.h" + +typedef struct JournalMetrics { + /* For all these: -1 means "pick automatically", and 0 means "no limit enforced" */ + uint64_t max_size; /* how large journal files grow at max */ + uint64_t min_size; /* how large journal files grow at least */ + uint64_t max_use; /* how much disk space to use in total at max, keep_free permitting */ + uint64_t min_use; /* how much disk space to use in total at least, even if keep_free says not to */ + uint64_t keep_free; /* how much to keep free on disk */ + uint64_t n_max_files; /* how many files to keep around at max */ +} JournalMetrics; + +typedef enum direction { + DIRECTION_UP, + DIRECTION_DOWN +} direction_t; + +typedef enum LocationType { + /* The first and last entries, resp. */ + LOCATION_HEAD, + LOCATION_TAIL, + + /* We already read the entry we currently point to, and the + * next one to read should probably not be this one again. */ + LOCATION_DISCRETE, + + /* We should seek to the precise location specified, and + * return it, as we haven't read it yet. */ + LOCATION_SEEK +} LocationType; + +typedef enum OfflineState { + OFFLINE_JOINED, + OFFLINE_SYNCING, + OFFLINE_OFFLINING, + OFFLINE_CANCEL, + OFFLINE_AGAIN_FROM_SYNCING, + OFFLINE_AGAIN_FROM_OFFLINING, + OFFLINE_DONE +} OfflineState; + +typedef struct JournalFile { + int fd; + + mode_t mode; + + int flags; + int prot; + bool writable:1; + bool compress_xz:1; + bool compress_lz4:1; + bool seal:1; + bool defrag_on_close:1; + bool close_fd:1; + bool archive:1; + + bool tail_entry_monotonic_valid:1; + + direction_t last_direction; + LocationType location_type; + uint64_t last_n_entries; + + char *path; + struct stat last_stat; + usec_t last_stat_usec; + + Header *header; + HashItem *data_hash_table; + HashItem *field_hash_table; + + uint64_t current_offset; + uint64_t current_seqnum; + uint64_t current_realtime; + uint64_t current_monotonic; + sd_id128_t current_boot_id; + uint64_t current_xor_hash; + + JournalMetrics metrics; + MMapCache *mmap; + + sd_event_source *post_change_timer; + usec_t post_change_timer_period; + + OrderedHashmap *chain_cache; + + pthread_t offline_thread; + volatile OfflineState offline_state; + +#if defined(HAVE_XZ) || defined(HAVE_LZ4) + void *compress_buffer; + size_t compress_buffer_size; +#endif + +#ifdef HAVE_GCRYPT + gcry_md_hd_t hmac; + bool hmac_running; + + FSSHeader *fss_file; + size_t fss_file_size; + + uint64_t fss_start_usec; + uint64_t fss_interval_usec; + + void *fsprg_state; + size_t fsprg_state_size; + + void *fsprg_seed; + size_t fsprg_seed_size; +#endif +} JournalFile; + +int journal_file_open( + int fd, + const char *fname, + int flags, + mode_t mode, + bool compress, + bool seal, + JournalMetrics *metrics, + MMapCache *mmap_cache, + Set *deferred_closes, + JournalFile *template, + JournalFile **ret); + +int journal_file_set_offline(JournalFile *f, bool wait); +bool journal_file_is_offlining(JournalFile *f); +JournalFile* journal_file_close(JournalFile *j); +void journal_file_close_set(Set *s); + +int journal_file_open_reliably( + const char *fname, + int flags, + mode_t mode, + bool compress, + bool seal, + JournalMetrics *metrics, + MMapCache *mmap_cache, + Set *deferred_closes, + JournalFile *template, + JournalFile **ret); + +#define ALIGN64(x) (((x) + 7ULL) & ~7ULL) +#define VALID64(x) (((x) & 7ULL) == 0ULL) + +/* Use six characters to cover the offsets common in smallish journal + * files without adding too many zeros. */ +#define OFSfmt "%06"PRIx64 + +static inline bool VALID_REALTIME(uint64_t u) { + /* This considers timestamps until the year 3112 valid. That should be plenty room... */ + return u > 0 && u < (1ULL << 55); +} + +static inline bool VALID_MONOTONIC(uint64_t u) { + /* This considers timestamps until 1142 years of runtime valid. */ + return u < (1ULL << 55); +} + +static inline bool VALID_EPOCH(uint64_t u) { + /* This allows changing the key for 1142 years, every usec. */ + return u < (1ULL << 55); +} + +#define JOURNAL_HEADER_CONTAINS(h, field) \ + (le64toh((h)->header_size) >= offsetof(Header, field) + sizeof((h)->field)) + +#define JOURNAL_HEADER_SEALED(h) \ + (!!(le32toh((h)->compatible_flags) & HEADER_COMPATIBLE_SEALED)) + +#define JOURNAL_HEADER_COMPRESSED_XZ(h) \ + (!!(le32toh((h)->incompatible_flags) & HEADER_INCOMPATIBLE_COMPRESSED_XZ)) + +#define JOURNAL_HEADER_COMPRESSED_LZ4(h) \ + (!!(le32toh((h)->incompatible_flags) & HEADER_INCOMPATIBLE_COMPRESSED_LZ4)) + +int journal_file_move_to_object(JournalFile *f, ObjectType type, uint64_t offset, Object **ret); + +uint64_t journal_file_entry_n_items(Object *o) _pure_; +uint64_t journal_file_entry_array_n_items(Object *o) _pure_; +uint64_t journal_file_hash_table_n_items(Object *o) _pure_; + +int journal_file_append_object(JournalFile *f, ObjectType type, uint64_t size, Object **ret, uint64_t *offset); +int journal_file_append_entry(JournalFile *f, const dual_timestamp *ts, const struct iovec iovec[], unsigned n_iovec, uint64_t *seqno, Object **ret, uint64_t *offset); + +int journal_file_find_data_object(JournalFile *f, const void *data, uint64_t size, Object **ret, uint64_t *offset); +int journal_file_find_data_object_with_hash(JournalFile *f, const void *data, uint64_t size, uint64_t hash, Object **ret, uint64_t *offset); + +int journal_file_find_field_object(JournalFile *f, const void *field, uint64_t size, Object **ret, uint64_t *offset); +int journal_file_find_field_object_with_hash(JournalFile *f, const void *field, uint64_t size, uint64_t hash, Object **ret, uint64_t *offset); + +void journal_file_reset_location(JournalFile *f); +void journal_file_save_location(JournalFile *f, Object *o, uint64_t offset); +int journal_file_compare_locations(JournalFile *af, JournalFile *bf); +int journal_file_next_entry(JournalFile *f, uint64_t p, direction_t direction, Object **ret, uint64_t *offset); + +int journal_file_next_entry_for_data(JournalFile *f, Object *o, uint64_t p, uint64_t data_offset, direction_t direction, Object **ret, uint64_t *offset); + +int journal_file_move_to_entry_by_seqnum(JournalFile *f, uint64_t seqnum, direction_t direction, Object **ret, uint64_t *offset); +int journal_file_move_to_entry_by_realtime(JournalFile *f, uint64_t realtime, direction_t direction, Object **ret, uint64_t *offset); +int journal_file_move_to_entry_by_monotonic(JournalFile *f, sd_id128_t boot_id, uint64_t monotonic, direction_t direction, Object **ret, uint64_t *offset); + +int journal_file_move_to_entry_by_offset_for_data(JournalFile *f, uint64_t data_offset, uint64_t p, direction_t direction, Object **ret, uint64_t *offset); +int journal_file_move_to_entry_by_seqnum_for_data(JournalFile *f, uint64_t data_offset, uint64_t seqnum, direction_t direction, Object **ret, uint64_t *offset); +int journal_file_move_to_entry_by_realtime_for_data(JournalFile *f, uint64_t data_offset, uint64_t realtime, direction_t direction, Object **ret, uint64_t *offset); +int journal_file_move_to_entry_by_monotonic_for_data(JournalFile *f, uint64_t data_offset, sd_id128_t boot_id, uint64_t monotonic, direction_t direction, Object **ret, uint64_t *offset); + +int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset); + +void journal_file_dump(JournalFile *f); +void journal_file_print_header(JournalFile *f); + +int journal_file_rotate(JournalFile **f, bool compress, bool seal, Set *deferred_closes); + +void journal_file_post_change(JournalFile *f); +int journal_file_enable_post_change_timer(JournalFile *f, sd_event *e, usec_t t); + +void journal_reset_metrics(JournalMetrics *m); +void journal_default_metrics(JournalMetrics *m, int fd); + +int journal_file_get_cutoff_realtime_usec(JournalFile *f, usec_t *from, usec_t *to); +int journal_file_get_cutoff_monotonic_usec(JournalFile *f, sd_id128_t boot, usec_t *from, usec_t *to); + +bool journal_file_rotate_suggested(JournalFile *f, usec_t max_file_usec); + +int journal_file_map_data_hash_table(JournalFile *f); +int journal_file_map_field_hash_table(JournalFile *f); + +static inline bool JOURNAL_FILE_COMPRESS(JournalFile *f) { + assert(f); + return f->compress_xz || f->compress_lz4; +} diff --git a/src/libsystemd/libsystemd-journal-internal/journal-internal.h b/src/libsystemd/libsystemd-journal-internal/journal-internal.h new file mode 100644 index 0000000000..d8eb11ad06 --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/journal-internal.h @@ -0,0 +1,143 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 +#include + +#include +#include + +#include "hashmap.h" +#include "journal-def.h" +#include "journal-file.h" +#include "list.h" +#include "set.h" + +typedef struct Match Match; +typedef struct Location Location; +typedef struct Directory Directory; + +typedef enum MatchType { + MATCH_DISCRETE, + MATCH_OR_TERM, + MATCH_AND_TERM +} MatchType; + +struct Match { + MatchType type; + Match *parent; + LIST_FIELDS(Match, matches); + + /* For concrete matches */ + char *data; + size_t size; + le64_t le_hash; + + /* For terms */ + LIST_HEAD(Match, matches); +}; + +struct Location { + LocationType type; + + bool seqnum_set; + bool realtime_set; + bool monotonic_set; + bool xor_hash_set; + + uint64_t seqnum; + sd_id128_t seqnum_id; + + uint64_t realtime; + + uint64_t monotonic; + sd_id128_t boot_id; + + uint64_t xor_hash; +}; + +struct Directory { + char *path; + int wd; + bool is_root; +}; + +struct sd_journal { + int toplevel_fd; + + char *path; + char *prefix; + + OrderedHashmap *files; + MMapCache *mmap; + + Location current_location; + + JournalFile *current_file; + uint64_t current_field; + + Match *level0, *level1, *level2; + + pid_t original_pid; + + int inotify_fd; + unsigned current_invalidate_counter, last_invalidate_counter; + usec_t last_process_usec; + + /* Iterating through unique fields and their data values */ + char *unique_field; + JournalFile *unique_file; + uint64_t unique_offset; + + /* Iterating through known fields */ + JournalFile *fields_file; + uint64_t fields_offset; + uint64_t fields_hash_table_index; + char *fields_buffer; + size_t fields_buffer_allocated; + + int flags; + + bool on_network:1; + bool no_new_files:1; + bool no_inotify:1; + bool unique_file_lost:1; /* File we were iterating over got + removed, and there were no more + files, so sd_j_enumerate_unique + will return a value equal to 0. */ + bool fields_file_lost:1; + bool has_runtime_files:1; + bool has_persistent_files:1; + + size_t data_threshold; + + Hashmap *directories_by_path; + Hashmap *directories_by_wd; + + Hashmap *errors; +}; + +char *journal_make_match_string(sd_journal *j); +void journal_print_header(sd_journal *j); + +#define JOURNAL_FOREACH_DATA_RETVAL(j, data, l, retval) \ + for (sd_journal_restart_data(j); ((retval) = sd_journal_enumerate_data((j), &(data), &(l))) > 0; ) diff --git a/src/libsystemd/libsystemd-journal-internal/journal-send.c b/src/libsystemd/libsystemd-journal-internal/journal-send.c new file mode 100644 index 0000000000..5a49211f85 --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/journal-send.c @@ -0,0 +1,559 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include +#include +#include +#include +#include + +#define SD_JOURNAL_SUPPRESS_LOCATION + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "io-util.h" +#include "memfd-util.h" +#include "socket-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "util.h" + +#define SNDBUF_SIZE (8*1024*1024) + +#define ALLOCA_CODE_FUNC(f, func) \ + do { \ + size_t _fl; \ + const char *_func = (func); \ + char **_f = &(f); \ + _fl = strlen(_func) + 1; \ + *_f = alloca(_fl + 10); \ + memcpy(*_f, "CODE_FUNC=", 10); \ + memcpy(*_f + 10, _func, _fl); \ + } while (false) + +/* We open a single fd, and we'll share it with the current process, + * all its threads, and all its subprocesses. This means we need to + * initialize it atomically, and need to operate on it atomically + * never assuming we are the only user */ + +static int journal_fd(void) { + int fd; + static int fd_plus_one = 0; + +retry: + if (fd_plus_one > 0) + return fd_plus_one - 1; + + fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); + if (fd < 0) + return -errno; + + fd_inc_sndbuf(fd, SNDBUF_SIZE); + + if (!__sync_bool_compare_and_swap(&fd_plus_one, 0, fd+1)) { + safe_close(fd); + goto retry; + } + + return fd; +} + +_public_ int sd_journal_print(int priority, const char *format, ...) { + int r; + va_list ap; + + va_start(ap, format); + r = sd_journal_printv(priority, format, ap); + va_end(ap); + + return r; +} + +_public_ int sd_journal_printv(int priority, const char *format, va_list ap) { + + /* FIXME: Instead of limiting things to LINE_MAX we could do a + C99 variable-length array on the stack here in a loop. */ + + char buffer[8 + LINE_MAX], p[sizeof("PRIORITY=")-1 + DECIMAL_STR_MAX(int) + 1]; + struct iovec iov[2]; + + assert_return(priority >= 0, -EINVAL); + assert_return(priority <= 7, -EINVAL); + assert_return(format, -EINVAL); + + xsprintf(p, "PRIORITY=%i", priority & LOG_PRIMASK); + + memcpy(buffer, "MESSAGE=", 8); + vsnprintf(buffer+8, sizeof(buffer) - 8, format, ap); + + zero(iov); + IOVEC_SET_STRING(iov[0], buffer); + IOVEC_SET_STRING(iov[1], p); + + return sd_journal_sendv(iov, 2); +} + +_printf_(1, 0) static int fill_iovec_sprintf(const char *format, va_list ap, int extra, struct iovec **_iov) { + PROTECT_ERRNO; + int r, n = 0, i = 0, j; + struct iovec *iov = NULL; + + assert(_iov); + + if (extra > 0) { + n = MAX(extra * 2, extra + 4); + iov = malloc0(n * sizeof(struct iovec)); + if (!iov) { + r = -ENOMEM; + goto fail; + } + + i = extra; + } + + while (format) { + struct iovec *c; + char *buffer; + va_list aq; + + if (i >= n) { + n = MAX(i*2, 4); + c = realloc(iov, n * sizeof(struct iovec)); + if (!c) { + r = -ENOMEM; + goto fail; + } + + iov = c; + } + + va_copy(aq, ap); + if (vasprintf(&buffer, format, aq) < 0) { + va_end(aq); + r = -ENOMEM; + goto fail; + } + va_end(aq); + + VA_FORMAT_ADVANCE(format, ap); + + IOVEC_SET_STRING(iov[i++], buffer); + + format = va_arg(ap, char *); + } + + *_iov = iov; + + return i; + +fail: + for (j = 0; j < i; j++) + free(iov[j].iov_base); + + free(iov); + + return r; +} + +_public_ int sd_journal_send(const char *format, ...) { + int r, i, j; + va_list ap; + struct iovec *iov = NULL; + + va_start(ap, format); + i = fill_iovec_sprintf(format, ap, 0, &iov); + va_end(ap); + + if (_unlikely_(i < 0)) { + r = i; + goto finish; + } + + r = sd_journal_sendv(iov, i); + +finish: + for (j = 0; j < i; j++) + free(iov[j].iov_base); + + free(iov); + + return r; +} + +_public_ int sd_journal_sendv(const struct iovec *iov, int n) { + PROTECT_ERRNO; + int fd, r; + _cleanup_close_ int buffer_fd = -1; + struct iovec *w; + uint64_t *l; + int i, j = 0; + static const union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/journal/socket", + }; + struct msghdr mh = { + .msg_name = (struct sockaddr*) &sa.sa, + .msg_namelen = SOCKADDR_UN_LEN(sa.un), + }; + ssize_t k; + bool have_syslog_identifier = false; + bool seal = true; + + assert_return(iov, -EINVAL); + assert_return(n > 0, -EINVAL); + + w = newa(struct iovec, n * 5 + 3); + l = newa(uint64_t, n); + + for (i = 0; i < n; i++) { + char *c, *nl; + + if (_unlikely_(!iov[i].iov_base || iov[i].iov_len <= 1)) + return -EINVAL; + + c = memchr(iov[i].iov_base, '=', iov[i].iov_len); + if (_unlikely_(!c || c == iov[i].iov_base)) + return -EINVAL; + + have_syslog_identifier = have_syslog_identifier || + (c == (char *) iov[i].iov_base + 17 && + startswith(iov[i].iov_base, "SYSLOG_IDENTIFIER")); + + nl = memchr(iov[i].iov_base, '\n', iov[i].iov_len); + if (nl) { + if (_unlikely_(nl < c)) + return -EINVAL; + + /* Already includes a newline? Bummer, then + * let's write the variable name, then a + * newline, then the size (64bit LE), followed + * by the data and a final newline */ + + w[j].iov_base = iov[i].iov_base; + w[j].iov_len = c - (char*) iov[i].iov_base; + j++; + + IOVEC_SET_STRING(w[j++], "\n"); + + l[i] = htole64(iov[i].iov_len - (c - (char*) iov[i].iov_base) - 1); + w[j].iov_base = &l[i]; + w[j].iov_len = sizeof(uint64_t); + j++; + + w[j].iov_base = c + 1; + w[j].iov_len = iov[i].iov_len - (c - (char*) iov[i].iov_base) - 1; + j++; + + } else + /* Nothing special? Then just add the line and + * append a newline */ + w[j++] = iov[i]; + + IOVEC_SET_STRING(w[j++], "\n"); + } + + if (!have_syslog_identifier && + string_is_safe(program_invocation_short_name)) { + + /* Implicitly add program_invocation_short_name, if it + * is not set explicitly. We only do this for + * program_invocation_short_name, and nothing else + * since everything else is much nicer to retrieve + * from the outside. */ + + IOVEC_SET_STRING(w[j++], "SYSLOG_IDENTIFIER="); + IOVEC_SET_STRING(w[j++], program_invocation_short_name); + IOVEC_SET_STRING(w[j++], "\n"); + } + + fd = journal_fd(); + if (_unlikely_(fd < 0)) + return fd; + + mh.msg_iov = w; + mh.msg_iovlen = j; + + k = sendmsg(fd, &mh, MSG_NOSIGNAL); + if (k >= 0) + return 0; + + /* Fail silently if the journal is not available */ + if (errno == ENOENT) + return 0; + + if (errno != EMSGSIZE && errno != ENOBUFS) + return -errno; + + /* Message doesn't fit... Let's dump the data in a memfd or + * temporary file and just pass a file descriptor of it to the + * other side. + * + * For the temporary files we use /dev/shm instead of /tmp + * here, since we want this to be a tmpfs, and one that is + * available from early boot on and where unprivileged users + * can create files. */ + buffer_fd = memfd_new(NULL); + if (buffer_fd < 0) { + if (buffer_fd == -ENOSYS) { + buffer_fd = open_tmpfile_unlinkable("/dev/shm", O_RDWR | O_CLOEXEC); + if (buffer_fd < 0) + return buffer_fd; + + seal = false; + } else + return buffer_fd; + } + + n = writev(buffer_fd, w, j); + if (n < 0) + return -errno; + + if (seal) { + r = memfd_set_sealed(buffer_fd); + if (r < 0) + return r; + } + + r = send_one_fd_sa(fd, buffer_fd, mh.msg_name, mh.msg_namelen, 0); + if (r == -ENOENT) + /* Fail silently if the journal is not available */ + return 0; + return r; +} + +static int fill_iovec_perror_and_send(const char *message, int skip, struct iovec iov[]) { + PROTECT_ERRNO; + size_t n, k; + + k = isempty(message) ? 0 : strlen(message) + 2; + n = 8 + k + 256 + 1; + + for (;;) { + char buffer[n]; + char* j; + + errno = 0; + j = strerror_r(_saved_errno_, buffer + 8 + k, n - 8 - k); + if (errno == 0) { + char error[sizeof("ERRNO=")-1 + DECIMAL_STR_MAX(int) + 1]; + + if (j != buffer + 8 + k) + memmove(buffer + 8 + k, j, strlen(j)+1); + + memcpy(buffer, "MESSAGE=", 8); + + if (k > 0) { + memcpy(buffer + 8, message, k - 2); + memcpy(buffer + 8 + k - 2, ": ", 2); + } + + xsprintf(error, "ERRNO=%i", _saved_errno_); + + assert_cc(3 == LOG_ERR); + IOVEC_SET_STRING(iov[skip+0], "PRIORITY=3"); + IOVEC_SET_STRING(iov[skip+1], buffer); + IOVEC_SET_STRING(iov[skip+2], error); + + return sd_journal_sendv(iov, skip + 3); + } + + if (errno != ERANGE) + return -errno; + + n *= 2; + } +} + +_public_ int sd_journal_perror(const char *message) { + struct iovec iovec[3]; + + return fill_iovec_perror_and_send(message, 0, iovec); +} + +_public_ int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix) { + static const union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/journal/stdout", + }; + _cleanup_close_ int fd = -1; + char *header; + size_t l; + int r; + + assert_return(priority >= 0, -EINVAL); + assert_return(priority <= 7, -EINVAL); + + fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + if (fd < 0) + return -errno; + + r = connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); + if (r < 0) + return -errno; + + if (shutdown(fd, SHUT_RD) < 0) + return -errno; + + fd_inc_sndbuf(fd, SNDBUF_SIZE); + + if (!identifier) + identifier = ""; + + l = strlen(identifier); + header = alloca(l + 1 + 1 + 2 + 2 + 2 + 2 + 2); + + memcpy(header, identifier, l); + header[l++] = '\n'; + header[l++] = '\n'; /* unit id */ + header[l++] = '0' + priority; + header[l++] = '\n'; + header[l++] = '0' + !!level_prefix; + header[l++] = '\n'; + header[l++] = '0'; + header[l++] = '\n'; + header[l++] = '0'; + header[l++] = '\n'; + header[l++] = '0'; + header[l++] = '\n'; + + r = loop_write(fd, header, l, false); + if (r < 0) + return r; + + r = fd; + fd = -1; + return r; +} + +_public_ int sd_journal_print_with_location(int priority, const char *file, const char *line, const char *func, const char *format, ...) { + int r; + va_list ap; + + va_start(ap, format); + r = sd_journal_printv_with_location(priority, file, line, func, format, ap); + va_end(ap); + + return r; +} + +_public_ int sd_journal_printv_with_location(int priority, const char *file, const char *line, const char *func, const char *format, va_list ap) { + char buffer[8 + LINE_MAX], p[sizeof("PRIORITY=")-1 + DECIMAL_STR_MAX(int) + 1]; + struct iovec iov[5]; + char *f; + + assert_return(priority >= 0, -EINVAL); + assert_return(priority <= 7, -EINVAL); + assert_return(format, -EINVAL); + + xsprintf(p, "PRIORITY=%i", priority & LOG_PRIMASK); + + memcpy(buffer, "MESSAGE=", 8); + vsnprintf(buffer+8, sizeof(buffer) - 8, format, ap); + + /* func is initialized from __func__ which is not a macro, but + * a static const char[], hence cannot easily be prefixed with + * CODE_FUNC=, hence let's do it manually here. */ + ALLOCA_CODE_FUNC(f, func); + + zero(iov); + IOVEC_SET_STRING(iov[0], buffer); + IOVEC_SET_STRING(iov[1], p); + IOVEC_SET_STRING(iov[2], file); + IOVEC_SET_STRING(iov[3], line); + IOVEC_SET_STRING(iov[4], f); + + return sd_journal_sendv(iov, ELEMENTSOF(iov)); +} + +_public_ int sd_journal_send_with_location(const char *file, const char *line, const char *func, const char *format, ...) { + int r, i, j; + va_list ap; + struct iovec *iov = NULL; + char *f; + + va_start(ap, format); + i = fill_iovec_sprintf(format, ap, 3, &iov); + va_end(ap); + + if (_unlikely_(i < 0)) { + r = i; + goto finish; + } + + ALLOCA_CODE_FUNC(f, func); + + IOVEC_SET_STRING(iov[0], file); + IOVEC_SET_STRING(iov[1], line); + IOVEC_SET_STRING(iov[2], f); + + r = sd_journal_sendv(iov, i); + +finish: + for (j = 3; j < i; j++) + free(iov[j].iov_base); + + free(iov); + + return r; +} + +_public_ int sd_journal_sendv_with_location( + const char *file, const char *line, + const char *func, + const struct iovec *iov, int n) { + + struct iovec *niov; + char *f; + + assert_return(iov, -EINVAL); + assert_return(n > 0, -EINVAL); + + niov = alloca(sizeof(struct iovec) * (n + 3)); + memcpy(niov, iov, sizeof(struct iovec) * n); + + ALLOCA_CODE_FUNC(f, func); + + IOVEC_SET_STRING(niov[n++], file); + IOVEC_SET_STRING(niov[n++], line); + IOVEC_SET_STRING(niov[n++], f); + + return sd_journal_sendv(niov, n); +} + +_public_ int sd_journal_perror_with_location( + const char *file, const char *line, + const char *func, + const char *message) { + + struct iovec iov[6]; + char *f; + + ALLOCA_CODE_FUNC(f, func); + + IOVEC_SET_STRING(iov[0], file); + IOVEC_SET_STRING(iov[1], line); + IOVEC_SET_STRING(iov[2], f); + + return fill_iovec_perror_and_send(message, 3, iov); +} diff --git a/src/libsystemd/libsystemd-journal-internal/journal-vacuum.c b/src/libsystemd/libsystemd-journal-internal/journal-vacuum.c new file mode 100644 index 0000000000..cd2676ab63 --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/journal-vacuum.c @@ -0,0 +1,349 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include + +#include + +#include "alloc-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "journal-def.h" +#include "journal-file.h" +#include "journal-vacuum.h" +#include "parse-util.h" +#include "string-util.h" +#include "util.h" +#include "xattr-util.h" + +struct vacuum_info { + uint64_t usage; + char *filename; + + uint64_t realtime; + + sd_id128_t seqnum_id; + uint64_t seqnum; + bool have_seqnum; +}; + +static int vacuum_compare(const void *_a, const void *_b) { + const struct vacuum_info *a, *b; + + a = _a; + b = _b; + + if (a->have_seqnum && b->have_seqnum && + sd_id128_equal(a->seqnum_id, b->seqnum_id)) { + if (a->seqnum < b->seqnum) + return -1; + else if (a->seqnum > b->seqnum) + return 1; + else + return 0; + } + + if (a->realtime < b->realtime) + return -1; + else if (a->realtime > b->realtime) + return 1; + else if (a->have_seqnum && b->have_seqnum) + return memcmp(&a->seqnum_id, &b->seqnum_id, 16); + else + return strcmp(a->filename, b->filename); +} + +static void patch_realtime( + int fd, + const char *fn, + const struct stat *st, + unsigned long long *realtime) { + + usec_t x, crtime = 0; + + /* The timestamp was determined by the file name, but let's + * see if the file might actually be older than the file name + * suggested... */ + + assert(fd >= 0); + assert(fn); + assert(st); + assert(realtime); + + x = timespec_load(&st->st_ctim); + if (x > 0 && x != USEC_INFINITY && x < *realtime) + *realtime = x; + + x = timespec_load(&st->st_atim); + if (x > 0 && x != USEC_INFINITY && x < *realtime) + *realtime = x; + + x = timespec_load(&st->st_mtim); + if (x > 0 && x != USEC_INFINITY && x < *realtime) + *realtime = x; + + /* Let's read the original creation time, if possible. Ideally + * we'd just query the creation time the FS might provide, but + * unfortunately there's currently no sane API to query + * it. Hence let's implement this manually... */ + + if (fd_getcrtime_at(fd, fn, &crtime, 0) >= 0) { + if (crtime < *realtime) + *realtime = crtime; + } +} + +static int journal_file_empty(int dir_fd, const char *name) { + _cleanup_close_ int fd; + struct stat st; + le64_t n_entries; + ssize_t n; + + fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK|O_NOATIME); + if (fd < 0) { + /* Maybe failed due to O_NOATIME and lack of privileges? */ + fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK); + if (fd < 0) + return -errno; + } + + if (fstat(fd, &st) < 0) + return -errno; + + /* If an offline file doesn't even have a header we consider it empty */ + if (st.st_size < (off_t) sizeof(Header)) + return 1; + + /* If the number of entries is empty, we consider it empty, too */ + n = pread(fd, &n_entries, sizeof(n_entries), offsetof(Header, n_entries)); + if (n < 0) + return -errno; + if (n != sizeof(n_entries)) + return -EIO; + + return le64toh(n_entries) <= 0; +} + +int journal_directory_vacuum( + const char *directory, + uint64_t max_use, + uint64_t n_max_files, + usec_t max_retention_usec, + usec_t *oldest_usec, + bool verbose) { + + _cleanup_closedir_ DIR *d = NULL; + struct vacuum_info *list = NULL; + unsigned n_list = 0, i, n_active_files = 0; + size_t n_allocated = 0; + uint64_t sum = 0, freed = 0; + usec_t retention_limit = 0; + char sbytes[FORMAT_BYTES_MAX]; + struct dirent *de; + int r; + + assert(directory); + + if (max_use <= 0 && max_retention_usec <= 0 && n_max_files <= 0) + return 0; + + if (max_retention_usec > 0) { + retention_limit = now(CLOCK_REALTIME); + if (retention_limit > max_retention_usec) + retention_limit -= max_retention_usec; + else + max_retention_usec = retention_limit = 0; + } + + d = opendir(directory); + if (!d) + return -errno; + + FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) { + + unsigned long long seqnum = 0, realtime; + _cleanup_free_ char *p = NULL; + sd_id128_t seqnum_id; + bool have_seqnum; + uint64_t size; + struct stat st; + size_t q; + + if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { + log_debug_errno(errno, "Failed to stat file %s while vacuuming, ignoring: %m", de->d_name); + continue; + } + + if (!S_ISREG(st.st_mode)) + continue; + + q = strlen(de->d_name); + + if (endswith(de->d_name, ".journal")) { + + /* Vacuum archived files. Active files are + * left around */ + + if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8) { + n_active_files++; + continue; + } + + if (de->d_name[q-8-16-1] != '-' || + de->d_name[q-8-16-1-16-1] != '-' || + de->d_name[q-8-16-1-16-1-32-1] != '@') { + n_active_files++; + continue; + } + + p = strdup(de->d_name); + if (!p) { + r = -ENOMEM; + goto finish; + } + + de->d_name[q-8-16-1-16-1] = 0; + if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) { + n_active_files++; + continue; + } + + if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) { + n_active_files++; + continue; + } + + have_seqnum = true; + + } else if (endswith(de->d_name, ".journal~")) { + unsigned long long tmp; + + /* Vacuum corrupted files */ + + if (q < 1 + 16 + 1 + 16 + 8 + 1) { + n_active_files++; + continue; + } + + if (de->d_name[q-1-8-16-1] != '-' || + de->d_name[q-1-8-16-1-16-1] != '@') { + n_active_files++; + continue; + } + + p = strdup(de->d_name); + if (!p) { + r = -ENOMEM; + goto finish; + } + + if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) { + n_active_files++; + continue; + } + + have_seqnum = false; + } else { + /* We do not vacuum unknown files! */ + log_debug("Not vacuuming unknown file %s.", de->d_name); + continue; + } + + size = 512UL * (uint64_t) st.st_blocks; + + r = journal_file_empty(dirfd(d), p); + if (r < 0) { + log_debug_errno(r, "Failed check if %s is empty, ignoring: %m", p); + continue; + } + if (r > 0) { + /* Always vacuum empty non-online files. */ + + if (unlinkat(dirfd(d), p, 0) >= 0) { + + log_full(verbose ? LOG_INFO : LOG_DEBUG, + "Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size)); + + freed += size; + } else if (errno != ENOENT) + log_warning_errno(errno, "Failed to delete empty archived journal %s/%s: %m", directory, p); + + continue; + } + + patch_realtime(dirfd(d), p, &st, &realtime); + + if (!GREEDY_REALLOC(list, n_allocated, n_list + 1)) { + r = -ENOMEM; + goto finish; + } + + list[n_list].filename = p; + list[n_list].usage = size; + list[n_list].seqnum = seqnum; + list[n_list].realtime = realtime; + list[n_list].seqnum_id = seqnum_id; + list[n_list].have_seqnum = have_seqnum; + n_list++; + + p = NULL; + sum += size; + } + + qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare); + + for (i = 0; i < n_list; i++) { + unsigned left; + + left = n_active_files + n_list - i; + + if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) && + (max_use <= 0 || sum <= max_use) && + (n_max_files <= 0 || left <= n_max_files)) + break; + + if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) { + log_full(verbose ? LOG_INFO : LOG_DEBUG, "Deleted archived journal %s/%s (%s).", directory, list[i].filename, format_bytes(sbytes, sizeof(sbytes), list[i].usage)); + freed += list[i].usage; + + if (list[i].usage < sum) + sum -= list[i].usage; + else + sum = 0; + + } else if (errno != ENOENT) + log_warning_errno(errno, "Failed to delete archived journal %s/%s: %m", directory, list[i].filename); + } + + if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec)) + *oldest_usec = list[i].realtime; + + r = 0; + +finish: + for (i = 0; i < n_list; i++) + free(list[i].filename); + free(list); + + log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes, sizeof(sbytes), freed)); + + return r; +} diff --git a/src/libsystemd/libsystemd-journal-internal/journal-vacuum.h b/src/libsystemd/libsystemd-journal-internal/journal-vacuum.h new file mode 100644 index 0000000000..1e750a2170 --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/journal-vacuum.h @@ -0,0 +1,27 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 + +#include "time-util.h" + +int journal_directory_vacuum(const char *directory, uint64_t max_use, uint64_t n_max_files, usec_t max_retention_usec, usec_t *oldest_usec, bool verbose); diff --git a/src/libsystemd/libsystemd-journal-internal/journal-verify.c b/src/libsystemd/libsystemd-journal-internal/journal-verify.c new file mode 100644 index 0000000000..26572ddd76 --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/journal-verify.c @@ -0,0 +1,1294 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 +#include +#include + +#include "alloc-util.h" +#include "compress.h" +#include "fd-util.h" +#include "fileio.h" +#include "journal-authenticate.h" +#include "journal-def.h" +#include "journal-file.h" +#include "journal-verify.h" +#include "lookup3.h" +#include "macro.h" +#include "terminal-util.h" +#include "util.h" + +static void draw_progress(uint64_t p, usec_t *last_usec) { + unsigned n, i, j, k; + usec_t z, x; + + if (!on_tty()) + return; + + z = now(CLOCK_MONOTONIC); + x = *last_usec; + + if (x != 0 && x + 40 * USEC_PER_MSEC > z) + return; + + *last_usec = z; + + n = (3 * columns()) / 4; + j = (n * (unsigned) p) / 65535ULL; + k = n - j; + + fputs("\r\x1B[?25l" ANSI_HIGHLIGHT_GREEN, stdout); + + for (i = 0; i < j; i++) + fputs("\xe2\x96\x88", stdout); + + fputs(ANSI_NORMAL, stdout); + + for (i = 0; i < k; i++) + fputs("\xe2\x96\x91", stdout); + + printf(" %3"PRIu64"%%", 100U * p / 65535U); + + fputs("\r\x1B[?25h", stdout); + fflush(stdout); +} + +static uint64_t scale_progress(uint64_t scale, uint64_t p, uint64_t m) { + + /* Calculates scale * p / m, but handles m == 0 safely, and saturates */ + + if (p >= m || m == 0) + return scale; + + return scale * p / m; +} + +static void flush_progress(void) { + unsigned n, i; + + if (!on_tty()) + return; + + n = (3 * columns()) / 4; + + putchar('\r'); + + for (i = 0; i < n + 5; i++) + putchar(' '); + + putchar('\r'); + fflush(stdout); +} + +#define debug(_offset, _fmt, ...) do { \ + flush_progress(); \ + log_debug(OFSfmt": " _fmt, _offset, ##__VA_ARGS__); \ + } while (0) + +#define warning(_offset, _fmt, ...) do { \ + flush_progress(); \ + log_warning(OFSfmt": " _fmt, _offset, ##__VA_ARGS__); \ + } while (0) + +#define error(_offset, _fmt, ...) do { \ + flush_progress(); \ + log_error(OFSfmt": " _fmt, (uint64_t)_offset, ##__VA_ARGS__); \ + } while (0) + +static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o) { + uint64_t i; + + assert(f); + assert(offset); + assert(o); + + /* This does various superficial tests about the length an + * possible field values. It does not follow any references to + * other objects. */ + + if ((o->object.flags & OBJECT_COMPRESSED_XZ) && + o->object.type != OBJECT_DATA) { + error(offset, "Found compressed object that isn't of type DATA, which is not allowed."); + return -EBADMSG; + } + + switch (o->object.type) { + + case OBJECT_DATA: { + uint64_t h1, h2; + int compression, r; + + if (le64toh(o->data.entry_offset) == 0) + warning(offset, "Unused data (entry_offset==0)"); + + if ((le64toh(o->data.entry_offset) == 0) ^ (le64toh(o->data.n_entries) == 0)) { + error(offset, "Bad n_entries: %"PRIu64, o->data.n_entries); + return -EBADMSG; + } + + if (le64toh(o->object.size) - offsetof(DataObject, payload) <= 0) { + error(offset, "Bad object size (<= %zu): %"PRIu64, + offsetof(DataObject, payload), + le64toh(o->object.size)); + return -EBADMSG; + } + + h1 = le64toh(o->data.hash); + + compression = o->object.flags & OBJECT_COMPRESSION_MASK; + if (compression) { + _cleanup_free_ void *b = NULL; + size_t alloc = 0, b_size; + + r = decompress_blob(compression, + o->data.payload, + le64toh(o->object.size) - offsetof(Object, data.payload), + &b, &alloc, &b_size, 0); + if (r < 0) { + error(offset, "%s decompression failed: %s", + object_compressed_to_string(compression), strerror(-r)); + return r; + } + + h2 = hash64(b, b_size); + } else + h2 = hash64(o->data.payload, le64toh(o->object.size) - offsetof(Object, data.payload)); + + if (h1 != h2) { + error(offset, "Invalid hash (%08"PRIx64" vs. %08"PRIx64, h1, h2); + return -EBADMSG; + } + + if (!VALID64(o->data.next_hash_offset) || + !VALID64(o->data.next_field_offset) || + !VALID64(o->data.entry_offset) || + !VALID64(o->data.entry_array_offset)) { + error(offset, "Invalid offset (next_hash_offset="OFSfmt", next_field_offset="OFSfmt", entry_offset="OFSfmt", entry_array_offset="OFSfmt, + o->data.next_hash_offset, + o->data.next_field_offset, + o->data.entry_offset, + o->data.entry_array_offset); + return -EBADMSG; + } + + break; + } + + case OBJECT_FIELD: + if (le64toh(o->object.size) - offsetof(FieldObject, payload) <= 0) { + error(offset, + "Bad field size (<= %zu): %"PRIu64, + offsetof(FieldObject, payload), + le64toh(o->object.size)); + return -EBADMSG; + } + + if (!VALID64(o->field.next_hash_offset) || + !VALID64(o->field.head_data_offset)) { + error(offset, + "Invalid offset (next_hash_offset="OFSfmt", head_data_offset="OFSfmt, + o->field.next_hash_offset, + o->field.head_data_offset); + return -EBADMSG; + } + break; + + case OBJECT_ENTRY: + if ((le64toh(o->object.size) - offsetof(EntryObject, items)) % sizeof(EntryItem) != 0) { + error(offset, + "Bad entry size (<= %zu): %"PRIu64, + offsetof(EntryObject, items), + le64toh(o->object.size)); + return -EBADMSG; + } + + if ((le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem) <= 0) { + error(offset, + "Invalid number items in entry: %"PRIu64, + (le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem)); + return -EBADMSG; + } + + if (le64toh(o->entry.seqnum) <= 0) { + error(offset, + "Invalid entry seqnum: %"PRIx64, + le64toh(o->entry.seqnum)); + return -EBADMSG; + } + + if (!VALID_REALTIME(le64toh(o->entry.realtime))) { + error(offset, + "Invalid entry realtime timestamp: %"PRIu64, + le64toh(o->entry.realtime)); + return -EBADMSG; + } + + if (!VALID_MONOTONIC(le64toh(o->entry.monotonic))) { + error(offset, + "Invalid entry monotonic timestamp: %"PRIu64, + le64toh(o->entry.monotonic)); + return -EBADMSG; + } + + for (i = 0; i < journal_file_entry_n_items(o); i++) { + if (o->entry.items[i].object_offset == 0 || + !VALID64(o->entry.items[i].object_offset)) { + error(offset, + "Invalid entry item (%"PRIu64"/%"PRIu64" offset: "OFSfmt, + i, journal_file_entry_n_items(o), + o->entry.items[i].object_offset); + return -EBADMSG; + } + } + + break; + + case OBJECT_DATA_HASH_TABLE: + case OBJECT_FIELD_HASH_TABLE: + if ((le64toh(o->object.size) - offsetof(HashTableObject, items)) % sizeof(HashItem) != 0 || + (le64toh(o->object.size) - offsetof(HashTableObject, items)) / sizeof(HashItem) <= 0) { + error(offset, + "Invalid %s hash table size: %"PRIu64, + o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field", + le64toh(o->object.size)); + return -EBADMSG; + } + + for (i = 0; i < journal_file_hash_table_n_items(o); i++) { + if (o->hash_table.items[i].head_hash_offset != 0 && + !VALID64(le64toh(o->hash_table.items[i].head_hash_offset))) { + error(offset, + "Invalid %s hash table item (%"PRIu64"/%"PRIu64") head_hash_offset: "OFSfmt, + o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field", + i, journal_file_hash_table_n_items(o), + le64toh(o->hash_table.items[i].head_hash_offset)); + return -EBADMSG; + } + if (o->hash_table.items[i].tail_hash_offset != 0 && + !VALID64(le64toh(o->hash_table.items[i].tail_hash_offset))) { + error(offset, + "Invalid %s hash table item (%"PRIu64"/%"PRIu64") tail_hash_offset: "OFSfmt, + o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field", + i, journal_file_hash_table_n_items(o), + le64toh(o->hash_table.items[i].tail_hash_offset)); + return -EBADMSG; + } + + if ((o->hash_table.items[i].head_hash_offset != 0) != + (o->hash_table.items[i].tail_hash_offset != 0)) { + error(offset, + "Invalid %s hash table item (%"PRIu64"/%"PRIu64"): head_hash_offset="OFSfmt" tail_hash_offset="OFSfmt, + o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field", + i, journal_file_hash_table_n_items(o), + le64toh(o->hash_table.items[i].head_hash_offset), + le64toh(o->hash_table.items[i].tail_hash_offset)); + return -EBADMSG; + } + } + + break; + + case OBJECT_ENTRY_ARRAY: + if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) % sizeof(le64_t) != 0 || + (le64toh(o->object.size) - offsetof(EntryArrayObject, items)) / sizeof(le64_t) <= 0) { + error(offset, + "Invalid object entry array size: %"PRIu64, + le64toh(o->object.size)); + return -EBADMSG; + } + + if (!VALID64(o->entry_array.next_entry_array_offset)) { + error(offset, + "Invalid object entry array next_entry_array_offset: "OFSfmt, + o->entry_array.next_entry_array_offset); + return -EBADMSG; + } + + for (i = 0; i < journal_file_entry_array_n_items(o); i++) + if (le64toh(o->entry_array.items[i]) != 0 && + !VALID64(le64toh(o->entry_array.items[i]))) { + error(offset, + "Invalid object entry array item (%"PRIu64"/%"PRIu64"): "OFSfmt, + i, journal_file_entry_array_n_items(o), + le64toh(o->entry_array.items[i])); + return -EBADMSG; + } + + break; + + case OBJECT_TAG: + if (le64toh(o->object.size) != sizeof(TagObject)) { + error(offset, + "Invalid object tag size: %"PRIu64, + le64toh(o->object.size)); + return -EBADMSG; + } + + if (!VALID_EPOCH(o->tag.epoch)) { + error(offset, + "Invalid object tag epoch: %"PRIu64, + o->tag.epoch); + return -EBADMSG; + } + + break; + } + + return 0; +} + +static int write_uint64(int fd, uint64_t p) { + ssize_t k; + + k = write(fd, &p, sizeof(p)); + if (k < 0) + return -errno; + if (k != sizeof(p)) + return -EIO; + + return 0; +} + +static int contains_uint64(MMapCache *m, int fd, uint64_t n, uint64_t p) { + uint64_t a, b; + int r; + + assert(m); + assert(fd >= 0); + + /* Bisection ... */ + + a = 0; b = n; + while (a < b) { + uint64_t c, *z; + + c = (a + b) / 2; + + r = mmap_cache_get(m, fd, PROT_READ|PROT_WRITE, 0, false, c * sizeof(uint64_t), sizeof(uint64_t), NULL, (void **) &z); + if (r < 0) + return r; + + if (*z == p) + return 1; + + if (a + 1 >= b) + return 0; + + if (p < *z) + b = c; + else + a = c; + } + + return 0; +} + +static int entry_points_to_data( + JournalFile *f, + int entry_fd, + uint64_t n_entries, + uint64_t entry_p, + uint64_t data_p) { + + int r; + uint64_t i, n, a; + Object *o; + bool found = false; + + assert(f); + assert(entry_fd >= 0); + + if (!contains_uint64(f->mmap, entry_fd, n_entries, entry_p)) { + error(data_p, "Data object references invalid entry at "OFSfmt, entry_p); + return -EBADMSG; + } + + r = journal_file_move_to_object(f, OBJECT_ENTRY, entry_p, &o); + if (r < 0) + return r; + + n = journal_file_entry_n_items(o); + for (i = 0; i < n; i++) + if (le64toh(o->entry.items[i].object_offset) == data_p) { + found = true; + break; + } + + if (!found) { + error(entry_p, "Data object at "OFSfmt" not referenced by linked entry", data_p); + return -EBADMSG; + } + + /* Check if this entry is also in main entry array. Since the + * main entry array has already been verified we can rely on + * its consistency. */ + + i = 0; + n = le64toh(f->header->n_entries); + a = le64toh(f->header->entry_array_offset); + + while (i < n) { + uint64_t m, u; + + r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); + if (r < 0) + return r; + + m = journal_file_entry_array_n_items(o); + u = MIN(n - i, m); + + if (entry_p <= le64toh(o->entry_array.items[u-1])) { + uint64_t x, y, z; + + x = 0; + y = u; + + while (x < y) { + z = (x + y) / 2; + + if (le64toh(o->entry_array.items[z]) == entry_p) + return 0; + + if (x + 1 >= y) + break; + + if (entry_p < le64toh(o->entry_array.items[z])) + y = z; + else + x = z; + } + + error(entry_p, "Entry object doesn't exist in main entry array"); + return -EBADMSG; + } + + i += u; + a = le64toh(o->entry_array.next_entry_array_offset); + } + + return 0; +} + +static int verify_data( + JournalFile *f, + Object *o, uint64_t p, + int entry_fd, uint64_t n_entries, + int entry_array_fd, uint64_t n_entry_arrays) { + + uint64_t i, n, a, last, q; + int r; + + assert(f); + assert(o); + assert(entry_fd >= 0); + assert(entry_array_fd >= 0); + + n = le64toh(o->data.n_entries); + a = le64toh(o->data.entry_array_offset); + + /* Entry array means at least two objects */ + if (a && n < 2) { + error(p, "Entry array present (entry_array_offset="OFSfmt", but n_entries=%"PRIu64")", a, n); + return -EBADMSG; + } + + if (n == 0) + return 0; + + /* We already checked that earlier */ + assert(o->data.entry_offset); + + last = q = le64toh(o->data.entry_offset); + r = entry_points_to_data(f, entry_fd, n_entries, q, p); + if (r < 0) + return r; + + i = 1; + while (i < n) { + uint64_t next, m, j; + + if (a == 0) { + error(p, "Array chain too short"); + return -EBADMSG; + } + + if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) { + error(p, "Invalid array offset "OFSfmt, a); + return -EBADMSG; + } + + r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); + if (r < 0) + return r; + + next = le64toh(o->entry_array.next_entry_array_offset); + if (next != 0 && next <= a) { + error(p, "Array chain has cycle (jumps back from "OFSfmt" to "OFSfmt")", a, next); + return -EBADMSG; + } + + m = journal_file_entry_array_n_items(o); + for (j = 0; i < n && j < m; i++, j++) { + + q = le64toh(o->entry_array.items[j]); + if (q <= last) { + error(p, "Data object's entry array not sorted"); + return -EBADMSG; + } + last = q; + + r = entry_points_to_data(f, entry_fd, n_entries, q, p); + if (r < 0) + return r; + + /* Pointer might have moved, reposition */ + r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); + if (r < 0) + return r; + } + + a = next; + } + + return 0; +} + +static int verify_hash_table( + JournalFile *f, + int data_fd, uint64_t n_data, + int entry_fd, uint64_t n_entries, + int entry_array_fd, uint64_t n_entry_arrays, + usec_t *last_usec, + bool show_progress) { + + uint64_t i, n; + int r; + + assert(f); + assert(data_fd >= 0); + assert(entry_fd >= 0); + assert(entry_array_fd >= 0); + assert(last_usec); + + n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem); + if (n <= 0) + return 0; + + r = journal_file_map_data_hash_table(f); + if (r < 0) + return log_error_errno(r, "Failed to map data hash table: %m"); + + for (i = 0; i < n; i++) { + uint64_t last = 0, p; + + if (show_progress) + draw_progress(0xC000 + scale_progress(0x3FFF, i, n), last_usec); + + p = le64toh(f->data_hash_table[i].head_hash_offset); + while (p != 0) { + Object *o; + uint64_t next; + + if (!contains_uint64(f->mmap, data_fd, n_data, p)) { + error(p, "Invalid data object at hash entry %"PRIu64" of %"PRIu64, i, n); + return -EBADMSG; + } + + r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); + if (r < 0) + return r; + + next = le64toh(o->data.next_hash_offset); + if (next != 0 && next <= p) { + error(p, "Hash chain has a cycle in hash entry %"PRIu64" of %"PRIu64, i, n); + return -EBADMSG; + } + + if (le64toh(o->data.hash) % n != i) { + error(p, "Hash value mismatch in hash entry %"PRIu64" of %"PRIu64, i, n); + return -EBADMSG; + } + + r = verify_data(f, o, p, entry_fd, n_entries, entry_array_fd, n_entry_arrays); + if (r < 0) + return r; + + last = p; + p = next; + } + + if (last != le64toh(f->data_hash_table[i].tail_hash_offset)) { + error(p, "Tail hash pointer mismatch in hash table"); + return -EBADMSG; + } + } + + return 0; +} + +static int data_object_in_hash_table(JournalFile *f, uint64_t hash, uint64_t p) { + uint64_t n, h, q; + int r; + assert(f); + + n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem); + if (n <= 0) + return 0; + + r = journal_file_map_data_hash_table(f); + if (r < 0) + return log_error_errno(r, "Failed to map data hash table: %m"); + + h = hash % n; + + q = le64toh(f->data_hash_table[h].head_hash_offset); + while (q != 0) { + Object *o; + + if (p == q) + return 1; + + r = journal_file_move_to_object(f, OBJECT_DATA, q, &o); + if (r < 0) + return r; + + q = le64toh(o->data.next_hash_offset); + } + + return 0; +} + +static int verify_entry( + JournalFile *f, + Object *o, uint64_t p, + int data_fd, uint64_t n_data) { + + uint64_t i, n; + int r; + + assert(f); + assert(o); + assert(data_fd >= 0); + + n = journal_file_entry_n_items(o); + for (i = 0; i < n; i++) { + uint64_t q, h; + Object *u; + + q = le64toh(o->entry.items[i].object_offset); + h = le64toh(o->entry.items[i].hash); + + if (!contains_uint64(f->mmap, data_fd, n_data, q)) { + error(p, "Invalid data object of entry"); + return -EBADMSG; + } + + r = journal_file_move_to_object(f, OBJECT_DATA, q, &u); + if (r < 0) + return r; + + if (le64toh(u->data.hash) != h) { + error(p, "Hash mismatch for data object of entry"); + return -EBADMSG; + } + + r = data_object_in_hash_table(f, h, q); + if (r < 0) + return r; + if (r == 0) { + error(p, "Data object missing from hash table"); + return -EBADMSG; + } + } + + return 0; +} + +static int verify_entry_array( + JournalFile *f, + int data_fd, uint64_t n_data, + int entry_fd, uint64_t n_entries, + int entry_array_fd, uint64_t n_entry_arrays, + usec_t *last_usec, + bool show_progress) { + + uint64_t i = 0, a, n, last = 0; + int r; + + assert(f); + assert(data_fd >= 0); + assert(entry_fd >= 0); + assert(entry_array_fd >= 0); + assert(last_usec); + + n = le64toh(f->header->n_entries); + a = le64toh(f->header->entry_array_offset); + while (i < n) { + uint64_t next, m, j; + Object *o; + + if (show_progress) + draw_progress(0x8000 + scale_progress(0x3FFF, i, n), last_usec); + + if (a == 0) { + error(a, "Array chain too short at %"PRIu64" of %"PRIu64, i, n); + return -EBADMSG; + } + + if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) { + error(a, "Invalid array %"PRIu64" of %"PRIu64, i, n); + return -EBADMSG; + } + + r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); + if (r < 0) + return r; + + next = le64toh(o->entry_array.next_entry_array_offset); + if (next != 0 && next <= a) { + error(a, "Array chain has cycle at %"PRIu64" of %"PRIu64" (jumps back from to "OFSfmt")", i, n, next); + return -EBADMSG; + } + + m = journal_file_entry_array_n_items(o); + for (j = 0; i < n && j < m; i++, j++) { + uint64_t p; + + p = le64toh(o->entry_array.items[j]); + if (p <= last) { + error(a, "Entry array not sorted at %"PRIu64" of %"PRIu64, i, n); + return -EBADMSG; + } + last = p; + + if (!contains_uint64(f->mmap, entry_fd, n_entries, p)) { + error(a, "Invalid array entry at %"PRIu64" of %"PRIu64, i, n); + return -EBADMSG; + } + + r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o); + if (r < 0) + return r; + + r = verify_entry(f, o, p, data_fd, n_data); + if (r < 0) + return r; + + /* Pointer might have moved, reposition */ + r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); + if (r < 0) + return r; + } + + a = next; + } + + return 0; +} + +int journal_file_verify( + JournalFile *f, + const char *key, + usec_t *first_contained, usec_t *last_validated, usec_t *last_contained, + bool show_progress) { + int r; + Object *o; + uint64_t p = 0, last_epoch = 0, last_tag_realtime = 0, last_sealed_realtime = 0; + + uint64_t entry_seqnum = 0, entry_monotonic = 0, entry_realtime = 0; + sd_id128_t entry_boot_id; + bool entry_seqnum_set = false, entry_monotonic_set = false, entry_realtime_set = false, found_main_entry_array = false; + uint64_t n_weird = 0, n_objects = 0, n_entries = 0, n_data = 0, n_fields = 0, n_data_hash_tables = 0, n_field_hash_tables = 0, n_entry_arrays = 0, n_tags = 0; + usec_t last_usec = 0; + int data_fd = -1, entry_fd = -1, entry_array_fd = -1; + unsigned i; + bool found_last = false; +#ifdef HAVE_GCRYPT + uint64_t last_tag = 0; +#endif + assert(f); + + if (key) { +#ifdef HAVE_GCRYPT + r = journal_file_parse_verification_key(f, key); + if (r < 0) { + log_error("Failed to parse seed."); + return r; + } +#else + return -EOPNOTSUPP; +#endif + } else if (f->seal) + return -ENOKEY; + + data_fd = open_tmpfile_unlinkable("/var/tmp", O_RDWR | O_CLOEXEC); + if (data_fd < 0) { + r = log_error_errno(data_fd, "Failed to create data file: %m"); + goto fail; + } + + entry_fd = open_tmpfile_unlinkable("/var/tmp", O_RDWR | O_CLOEXEC); + if (entry_fd < 0) { + r = log_error_errno(entry_fd, "Failed to create entry file: %m"); + goto fail; + } + + entry_array_fd = open_tmpfile_unlinkable("/var/tmp", O_RDWR | O_CLOEXEC); + if (entry_array_fd < 0) { + r = log_error_errno(entry_array_fd, + "Failed to create entry array file: %m"); + goto fail; + } + + if (le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_SUPPORTED) { + log_error("Cannot verify file with unknown extensions."); + r = -EOPNOTSUPP; + goto fail; + } + + for (i = 0; i < sizeof(f->header->reserved); i++) + if (f->header->reserved[i] != 0) { + error(offsetof(Header, reserved[i]), "Reserved field is non-zero"); + r = -EBADMSG; + goto fail; + } + + /* First iteration: we go through all objects, verify the + * superficial structure, headers, hashes. */ + + p = le64toh(f->header->header_size); + for (;;) { + /* Early exit if there are no objects in the file, at all */ + if (le64toh(f->header->tail_object_offset) == 0) + break; + + if (show_progress) + draw_progress(scale_progress(0x7FFF, p, le64toh(f->header->tail_object_offset)), &last_usec); + + r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o); + if (r < 0) { + error(p, "Invalid object"); + goto fail; + } + + if (p > le64toh(f->header->tail_object_offset)) { + error(offsetof(Header, tail_object_offset), "Invalid tail object pointer"); + r = -EBADMSG; + goto fail; + } + + n_objects++; + + r = journal_file_object_verify(f, p, o); + if (r < 0) { + error(p, "Invalid object contents: %s", strerror(-r)); + goto fail; + } + + if ((o->object.flags & OBJECT_COMPRESSED_XZ) && + (o->object.flags & OBJECT_COMPRESSED_LZ4)) { + error(p, "Objected with double compression"); + r = -EINVAL; + goto fail; + } + + if ((o->object.flags & OBJECT_COMPRESSED_XZ) && !JOURNAL_HEADER_COMPRESSED_XZ(f->header)) { + error(p, "XZ compressed object in file without XZ compression"); + r = -EBADMSG; + goto fail; + } + + if ((o->object.flags & OBJECT_COMPRESSED_LZ4) && !JOURNAL_HEADER_COMPRESSED_LZ4(f->header)) { + error(p, "LZ4 compressed object in file without LZ4 compression"); + r = -EBADMSG; + goto fail; + } + + switch (o->object.type) { + + case OBJECT_DATA: + r = write_uint64(data_fd, p); + if (r < 0) + goto fail; + + n_data++; + break; + + case OBJECT_FIELD: + n_fields++; + break; + + case OBJECT_ENTRY: + if (JOURNAL_HEADER_SEALED(f->header) && n_tags <= 0) { + error(p, "First entry before first tag"); + r = -EBADMSG; + goto fail; + } + + r = write_uint64(entry_fd, p); + if (r < 0) + goto fail; + + if (le64toh(o->entry.realtime) < last_tag_realtime) { + error(p, "Older entry after newer tag"); + r = -EBADMSG; + goto fail; + } + + if (!entry_seqnum_set && + le64toh(o->entry.seqnum) != le64toh(f->header->head_entry_seqnum)) { + error(p, "Head entry sequence number incorrect"); + r = -EBADMSG; + goto fail; + } + + if (entry_seqnum_set && + entry_seqnum >= le64toh(o->entry.seqnum)) { + error(p, "Entry sequence number out of synchronization"); + r = -EBADMSG; + goto fail; + } + + entry_seqnum = le64toh(o->entry.seqnum); + entry_seqnum_set = true; + + if (entry_monotonic_set && + sd_id128_equal(entry_boot_id, o->entry.boot_id) && + entry_monotonic > le64toh(o->entry.monotonic)) { + error(p, "Entry timestamp out of synchronization"); + r = -EBADMSG; + goto fail; + } + + entry_monotonic = le64toh(o->entry.monotonic); + entry_boot_id = o->entry.boot_id; + entry_monotonic_set = true; + + if (!entry_realtime_set && + le64toh(o->entry.realtime) != le64toh(f->header->head_entry_realtime)) { + error(p, "Head entry realtime timestamp incorrect"); + r = -EBADMSG; + goto fail; + } + + entry_realtime = le64toh(o->entry.realtime); + entry_realtime_set = true; + + n_entries++; + break; + + case OBJECT_DATA_HASH_TABLE: + if (n_data_hash_tables > 1) { + error(p, "More than one data hash table"); + r = -EBADMSG; + goto fail; + } + + if (le64toh(f->header->data_hash_table_offset) != p + offsetof(HashTableObject, items) || + le64toh(f->header->data_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) { + error(p, "header fields for data hash table invalid"); + r = -EBADMSG; + goto fail; + } + + n_data_hash_tables++; + break; + + case OBJECT_FIELD_HASH_TABLE: + if (n_field_hash_tables > 1) { + error(p, "More than one field hash table"); + r = -EBADMSG; + goto fail; + } + + if (le64toh(f->header->field_hash_table_offset) != p + offsetof(HashTableObject, items) || + le64toh(f->header->field_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) { + error(p, "Header fields for field hash table invalid"); + r = -EBADMSG; + goto fail; + } + + n_field_hash_tables++; + break; + + case OBJECT_ENTRY_ARRAY: + r = write_uint64(entry_array_fd, p); + if (r < 0) + goto fail; + + if (p == le64toh(f->header->entry_array_offset)) { + if (found_main_entry_array) { + error(p, "More than one main entry array"); + r = -EBADMSG; + goto fail; + } + + found_main_entry_array = true; + } + + n_entry_arrays++; + break; + + case OBJECT_TAG: + if (!JOURNAL_HEADER_SEALED(f->header)) { + error(p, "Tag object in file without sealing"); + r = -EBADMSG; + goto fail; + } + + if (le64toh(o->tag.seqnum) != n_tags + 1) { + error(p, "Tag sequence number out of synchronization"); + r = -EBADMSG; + goto fail; + } + + if (le64toh(o->tag.epoch) < last_epoch) { + error(p, "Epoch sequence out of synchronization"); + r = -EBADMSG; + goto fail; + } + +#ifdef HAVE_GCRYPT + if (f->seal) { + uint64_t q, rt; + + debug(p, "Checking tag %"PRIu64"...", le64toh(o->tag.seqnum)); + + rt = f->fss_start_usec + o->tag.epoch * f->fss_interval_usec; + if (entry_realtime_set && entry_realtime >= rt + f->fss_interval_usec) { + error(p, "tag/entry realtime timestamp out of synchronization"); + r = -EBADMSG; + goto fail; + } + + /* OK, now we know the epoch. So let's now set + * it, and calculate the HMAC for everything + * since the last tag. */ + r = journal_file_fsprg_seek(f, le64toh(o->tag.epoch)); + if (r < 0) + goto fail; + + r = journal_file_hmac_start(f); + if (r < 0) + goto fail; + + if (last_tag == 0) { + r = journal_file_hmac_put_header(f); + if (r < 0) + goto fail; + + q = le64toh(f->header->header_size); + } else + q = last_tag; + + while (q <= p) { + r = journal_file_move_to_object(f, OBJECT_UNUSED, q, &o); + if (r < 0) + goto fail; + + r = journal_file_hmac_put_object(f, OBJECT_UNUSED, o, q); + if (r < 0) + goto fail; + + q = q + ALIGN64(le64toh(o->object.size)); + } + + /* Position might have changed, let's reposition things */ + r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o); + if (r < 0) + goto fail; + + if (memcmp(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH) != 0) { + error(p, "Tag failed verification"); + r = -EBADMSG; + goto fail; + } + + f->hmac_running = false; + last_tag_realtime = rt; + last_sealed_realtime = entry_realtime; + } + + last_tag = p + ALIGN64(le64toh(o->object.size)); +#endif + + last_epoch = le64toh(o->tag.epoch); + + n_tags++; + break; + + default: + n_weird++; + } + + if (p == le64toh(f->header->tail_object_offset)) { + found_last = true; + break; + } + + p = p + ALIGN64(le64toh(o->object.size)); + }; + + if (!found_last && le64toh(f->header->tail_object_offset) != 0) { + error(le64toh(f->header->tail_object_offset), "Tail object pointer dead"); + r = -EBADMSG; + goto fail; + } + + if (n_objects != le64toh(f->header->n_objects)) { + error(offsetof(Header, n_objects), "Object number mismatch"); + r = -EBADMSG; + goto fail; + } + + if (n_entries != le64toh(f->header->n_entries)) { + error(offsetof(Header, n_entries), "Entry number mismatch"); + r = -EBADMSG; + goto fail; + } + + if (JOURNAL_HEADER_CONTAINS(f->header, n_data) && + n_data != le64toh(f->header->n_data)) { + error(offsetof(Header, n_data), "Data number mismatch"); + r = -EBADMSG; + goto fail; + } + + if (JOURNAL_HEADER_CONTAINS(f->header, n_fields) && + n_fields != le64toh(f->header->n_fields)) { + error(offsetof(Header, n_fields), "Field number mismatch"); + r = -EBADMSG; + goto fail; + } + + if (JOURNAL_HEADER_CONTAINS(f->header, n_tags) && + n_tags != le64toh(f->header->n_tags)) { + error(offsetof(Header, n_tags), "Tag number mismatch"); + r = -EBADMSG; + goto fail; + } + + if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays) && + n_entry_arrays != le64toh(f->header->n_entry_arrays)) { + error(offsetof(Header, n_entry_arrays), "Entry array number mismatch"); + r = -EBADMSG; + goto fail; + } + + if (!found_main_entry_array && le64toh(f->header->entry_array_offset) != 0) { + error(0, "Missing entry array"); + r = -EBADMSG; + goto fail; + } + + if (entry_seqnum_set && + entry_seqnum != le64toh(f->header->tail_entry_seqnum)) { + error(offsetof(Header, tail_entry_seqnum), "Invalid tail seqnum"); + r = -EBADMSG; + goto fail; + } + + if (entry_monotonic_set && + (!sd_id128_equal(entry_boot_id, f->header->boot_id) || + entry_monotonic != le64toh(f->header->tail_entry_monotonic))) { + error(0, "Invalid tail monotonic timestamp"); + r = -EBADMSG; + goto fail; + } + + if (entry_realtime_set && entry_realtime != le64toh(f->header->tail_entry_realtime)) { + error(0, "Invalid tail realtime timestamp"); + r = -EBADMSG; + goto fail; + } + + /* Second iteration: we follow all objects referenced from the + * two entry points: the object hash table and the entry + * array. We also check that everything referenced (directly + * or indirectly) in the data hash table also exists in the + * entry array, and vice versa. Note that we do not care for + * unreferenced objects. We only care that everything that is + * referenced is consistent. */ + + r = verify_entry_array(f, + data_fd, n_data, + entry_fd, n_entries, + entry_array_fd, n_entry_arrays, + &last_usec, + show_progress); + if (r < 0) + goto fail; + + r = verify_hash_table(f, + data_fd, n_data, + entry_fd, n_entries, + entry_array_fd, n_entry_arrays, + &last_usec, + show_progress); + if (r < 0) + goto fail; + + if (show_progress) + flush_progress(); + + mmap_cache_close_fd(f->mmap, data_fd); + mmap_cache_close_fd(f->mmap, entry_fd); + mmap_cache_close_fd(f->mmap, entry_array_fd); + + safe_close(data_fd); + safe_close(entry_fd); + safe_close(entry_array_fd); + + if (first_contained) + *first_contained = le64toh(f->header->head_entry_realtime); + if (last_validated) + *last_validated = last_sealed_realtime; + if (last_contained) + *last_contained = le64toh(f->header->tail_entry_realtime); + + return 0; + +fail: + if (show_progress) + flush_progress(); + + log_error("File corruption detected at %s:"OFSfmt" (of %llu bytes, %"PRIu64"%%).", + f->path, + p, + (unsigned long long) f->last_stat.st_size, + 100 * p / f->last_stat.st_size); + + if (data_fd >= 0) { + mmap_cache_close_fd(f->mmap, data_fd); + safe_close(data_fd); + } + + if (entry_fd >= 0) { + mmap_cache_close_fd(f->mmap, entry_fd); + safe_close(entry_fd); + } + + if (entry_array_fd >= 0) { + mmap_cache_close_fd(f->mmap, entry_array_fd); + safe_close(entry_array_fd); + } + + return r; +} diff --git a/src/libsystemd/libsystemd-journal-internal/journal-verify.h b/src/libsystemd/libsystemd-journal-internal/journal-verify.h new file mode 100644 index 0000000000..8f0eaf6daa --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/journal-verify.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 "journal-file.h" + +int journal_file_verify(JournalFile *f, const char *key, usec_t *first_contained, usec_t *last_validated, usec_t *last_contained, bool show_progress); diff --git a/src/libsystemd/libsystemd-journal-internal/lookup3.c b/src/libsystemd/libsystemd-journal-internal/lookup3.c new file mode 100644 index 0000000000..3d791234f4 --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/lookup3.c @@ -0,0 +1,1009 @@ +/* Slightly modified by Lennart Poettering, to avoid name clashes, and + * unexport a few functions. */ + +#include "lookup3.h" + +/* +------------------------------------------------------------------------------- +lookup3.c, by Bob Jenkins, May 2006, Public Domain. + +These are functions for producing 32-bit hashes for hash table lookup. +hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final() +are externally useful functions. Routines to test the hash are included +if SELF_TEST is defined. You can use this free for any purpose. It's in +the public domain. It has no warranty. + +You probably want to use hashlittle(). hashlittle() and hashbig() +hash byte arrays. hashlittle() is faster than hashbig() on +little-endian machines. Intel and AMD are little-endian machines. +On second thought, you probably want hashlittle2(), which is identical to +hashlittle() except it returns two 32-bit hashes for the price of one. +You could implement hashbig2() if you wanted but I haven't bothered here. + +If you want to find a hash of, say, exactly 7 integers, do + a = i1; b = i2; c = i3; + mix(a,b,c); + a += i4; b += i5; c += i6; + mix(a,b,c); + a += i7; + final(a,b,c); +then use c as the hash value. If you have a variable length array of +4-byte integers to hash, use hashword(). If you have a byte array (like +a character string), use hashlittle(). If you have several byte arrays, or +a mix of things, see the comments above hashlittle(). + +Why is this so big? I read 12 bytes at a time into 3 4-byte integers, +then mix those integers. This is fast (you can do a lot more thorough +mixing with 12*3 instructions on 3 integers than you can with 3 instructions +on 1 byte), but shoehorning those bytes into integers efficiently is messy. +------------------------------------------------------------------------------- +*/ +/* #define SELF_TEST 1 */ + +#include /* defines uint32_t etc */ +#include /* defines printf for tests */ +#include /* attempt to define endianness */ +#include /* defines time_t for timings in the test */ +#ifdef linux +# include /* attempt to define endianness */ +#endif + +/* + * My best guess at if you are big-endian or little-endian. This may + * need adjustment. + */ +#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ + __BYTE_ORDER == __LITTLE_ENDIAN) || \ + (defined(i386) || defined(__i386__) || defined(__i486__) || \ + defined(__i586__) || defined(__i686__) || defined(vax) || defined(MIPSEL)) +# define HASH_LITTLE_ENDIAN 1 +# define HASH_BIG_ENDIAN 0 +#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ + __BYTE_ORDER == __BIG_ENDIAN) || \ + (defined(sparc) || defined(POWERPC) || defined(mc68000) || defined(sel)) +# define HASH_LITTLE_ENDIAN 0 +# define HASH_BIG_ENDIAN 1 +#else +# define HASH_LITTLE_ENDIAN 0 +# define HASH_BIG_ENDIAN 0 +#endif + +#define hashsize(n) ((uint32_t)1<<(n)) +#define hashmask(n) (hashsize(n)-1) +#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + +/* +------------------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. + +This is reversible, so any information in (a,b,c) before mix() is +still in (a,b,c) after mix(). + +If four pairs of (a,b,c) inputs are run through mix(), or through +mix() in reverse, there are at least 32 bits of the output that +are sometimes the same for one pair and different for another pair. +This was tested for: +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that +satisfy this are + 4 6 8 16 19 4 + 9 15 3 18 27 15 + 14 9 3 7 17 3 +Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing +for "differ" defined as + with a one-bit base and a two-bit delta. I +used http://burtleburtle.net/bob/hash/avalanche.html to choose +the operations, constants, and arrangements of the variables. + +This does not achieve avalanche. There are input bits of (a,b,c) +that fail to affect some output bits of (a,b,c), especially of a. The +most thoroughly mixed value is c, but it doesn't really even achieve +avalanche in c. + +This allows some parallelism. Read-after-writes are good at doubling +the number of bits affected, so the goal of mixing pulls in the opposite +direction as the goal of parallelism. I did what I could. Rotates +seem to cost as much as shifts on every machine I could lay my hands +on, and rotates are much kinder to the top and bottom bits, so I used +rotates. +------------------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= c; a ^= rot(c, 4); c += b; \ + b -= a; b ^= rot(a, 6); a += c; \ + c -= b; c ^= rot(b, 8); b += a; \ + a -= c; a ^= rot(c,16); c += b; \ + b -= a; b ^= rot(a,19); a += c; \ + c -= b; c ^= rot(b, 4); b += a; \ +} + +/* +------------------------------------------------------------------------------- +final -- final mixing of 3 32-bit values (a,b,c) into c + +Pairs of (a,b,c) values differing in only a few bits will usually +produce values of c that look totally different. This was tested for +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +These constants passed: + 14 11 25 16 4 14 24 + 12 14 25 16 4 14 24 +and these came close: + 4 8 15 26 3 22 24 + 10 8 15 26 3 22 24 + 11 8 15 26 3 22 24 +------------------------------------------------------------------------------- +*/ +#define final(a,b,c) \ +{ \ + c ^= b; c -= rot(b,14); \ + a ^= c; a -= rot(c,11); \ + b ^= a; b -= rot(a,25); \ + c ^= b; c -= rot(b,16); \ + a ^= c; a -= rot(c,4); \ + b ^= a; b -= rot(a,14); \ + c ^= b; c -= rot(b,24); \ +} + +/* +-------------------------------------------------------------------- + This works on all machines. To be useful, it requires + -- that the key be an array of uint32_t's, and + -- that the length be the number of uint32_t's in the key + + The function hashword() is identical to hashlittle() on little-endian + machines, and identical to hashbig() on big-endian machines, + except that the length has to be measured in uint32_ts rather than in + bytes. hashlittle() is more complicated than hashword() only because + hashlittle() has to dance around fitting the key bytes into registers. +-------------------------------------------------------------------- +*/ +uint32_t jenkins_hashword( +const uint32_t *k, /* the key, an array of uint32_t values */ +size_t length, /* the length of the key, in uint32_ts */ +uint32_t initval) /* the previous hash, or an arbitrary value */ +{ + uint32_t a,b,c; + + /* Set up the internal state */ + a = b = c = 0xdeadbeef + (((uint32_t)length)<<2) + initval; + + /*------------------------------------------------- handle most of the key */ + while (length > 3) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 3; + k += 3; + } + + /*------------------------------------------- handle the last 3 uint32_t's */ + switch(length) /* all the case statements fall through */ + { + case 3 : c+=k[2]; + case 2 : b+=k[1]; + case 1 : a+=k[0]; + final(a,b,c); + case 0: /* case 0: nothing left to add */ + break; + } + /*------------------------------------------------------ report the result */ + return c; +} + + +/* +-------------------------------------------------------------------- +hashword2() -- same as hashword(), but take two seeds and return two +32-bit values. pc and pb must both be nonnull, and *pc and *pb must +both be initialized with seeds. If you pass in (*pb)==0, the output +(*pc) will be the same as the return value from hashword(). +-------------------------------------------------------------------- +*/ +void jenkins_hashword2 ( +const uint32_t *k, /* the key, an array of uint32_t values */ +size_t length, /* the length of the key, in uint32_ts */ +uint32_t *pc, /* IN: seed OUT: primary hash value */ +uint32_t *pb) /* IN: more seed OUT: secondary hash value */ +{ + uint32_t a,b,c; + + /* Set up the internal state */ + a = b = c = 0xdeadbeef + ((uint32_t)(length<<2)) + *pc; + c += *pb; + + /*------------------------------------------------- handle most of the key */ + while (length > 3) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 3; + k += 3; + } + + /*------------------------------------------- handle the last 3 uint32_t's */ + switch(length) /* all the case statements fall through */ + { + case 3 : c+=k[2]; + case 2 : b+=k[1]; + case 1 : a+=k[0]; + final(a,b,c); + case 0: /* case 0: nothing left to add */ + break; + } + /*------------------------------------------------------ report the result */ + *pc=c; *pb=b; +} + + +/* +------------------------------------------------------------------------------- +hashlittle() -- hash a variable-length key into a 32-bit value + k : the key (the unaligned variable-length array of bytes) + length : the length of the key, counting by bytes + initval : can be any 4-byte value +Returns a 32-bit value. Every bit of the key affects every bit of +the return value. Two keys differing by one or two bits will have +totally different hash values. + +The best hash table sizes are powers of 2. There is no need to do +mod a prime (mod is sooo slow!). If you need less than 32 bits, +use a bitmask. For example, if you need only 10 bits, do + h = (h & hashmask(10)); +In which case, the hash table should have hashsize(10) elements. + +If you are hashing n strings (uint8_t **)k, do it like this: + for (i=0, h=0; i 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; + } + + /*----------------------------- handle the last (probably partial) block */ + /* + * "k[2]&0xffffff" actually reads beyond the end of the string, but + * then masks off the part it's not allowed to read. Because the + * string is aligned, the masked-off tail is in the same word as the + * rest of the string. Every machine with memory protection I've seen + * does it on word boundaries, so is OK with this. But VALGRIND will + * still catch it and complain. The masking trick does make the hash + * noticeably faster for short strings (like English words). + */ +#ifndef VALGRIND + + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; + case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; + case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=k[1]&0xffffff; a+=k[0]; break; + case 6 : b+=k[1]&0xffff; a+=k[0]; break; + case 5 : b+=k[1]&0xff; a+=k[0]; break; + case 4 : a+=k[0]; break; + case 3 : a+=k[0]&0xffffff; break; + case 2 : a+=k[0]&0xffff; break; + case 1 : a+=k[0]&0xff; break; + case 0 : return c; /* zero length strings require no mixing */ + } + +#else /* make valgrind happy */ + { + const uint8_t *k8 = (const uint8_t *) k; + + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ + case 1 : a+=k8[0]; break; + case 0 : return c; + } + } + +#endif /* !valgrind */ + + } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { + const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ + const uint8_t *k8; + + /*--------------- all but last block: aligned reads and different mixing */ + while (length > 12) + { + a += k[0] + (((uint32_t)k[1])<<16); + b += k[2] + (((uint32_t)k[3])<<16); + c += k[4] + (((uint32_t)k[5])<<16); + mix(a,b,c); + length -= 12; + k += 6; + } + + /*----------------------------- handle the last (probably partial) block */ + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[4]+(((uint32_t)k[5])<<16); + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=k[4]; + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=k[2]; + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=k[0]; + break; + case 1 : a+=k8[0]; + break; + case 0 : return c; /* zero length requires no mixing */ + } + + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; + + /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + a += ((uint32_t)k[1])<<8; + a += ((uint32_t)k[2])<<16; + a += ((uint32_t)k[3])<<24; + b += k[4]; + b += ((uint32_t)k[5])<<8; + b += ((uint32_t)k[6])<<16; + b += ((uint32_t)k[7])<<24; + c += k[8]; + c += ((uint32_t)k[9])<<8; + c += ((uint32_t)k[10])<<16; + c += ((uint32_t)k[11])<<24; + mix(a,b,c); + length -= 12; + k += 12; + } + + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + case 12: c+=((uint32_t)k[11])<<24; + case 11: c+=((uint32_t)k[10])<<16; + case 10: c+=((uint32_t)k[9])<<8; + case 9 : c+=k[8]; + case 8 : b+=((uint32_t)k[7])<<24; + case 7 : b+=((uint32_t)k[6])<<16; + case 6 : b+=((uint32_t)k[5])<<8; + case 5 : b+=k[4]; + case 4 : a+=((uint32_t)k[3])<<24; + case 3 : a+=((uint32_t)k[2])<<16; + case 2 : a+=((uint32_t)k[1])<<8; + case 1 : a+=k[0]; + break; + case 0 : return c; + } + } + + final(a,b,c); + return c; +} + + +/* + * hashlittle2: return 2 32-bit hash values + * + * This is identical to hashlittle(), except it returns two 32-bit hash + * values instead of just one. This is good enough for hash table + * lookup with 2^^64 buckets, or if you want a second hash if you're not + * happy with the first, or if you want a probably-unique 64-bit ID for + * the key. *pc is better mixed than *pb, so use *pc first. If you want + * a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)". + */ +void jenkins_hashlittle2( + const void *key, /* the key to hash */ + size_t length, /* length of the key */ + uint32_t *pc, /* IN: primary initval, OUT: primary hash */ + uint32_t *pb) /* IN: secondary initval, OUT: secondary hash */ +{ + uint32_t a,b,c; /* internal state */ + union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */ + + /* Set up the internal state */ + a = b = c = 0xdeadbeef + ((uint32_t)length) + *pc; + c += *pb; + + u.ptr = key; + if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) { + const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */ + + /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; + } + + /*----------------------------- handle the last (probably partial) block */ + /* + * "k[2]&0xffffff" actually reads beyond the end of the string, but + * then masks off the part it's not allowed to read. Because the + * string is aligned, the masked-off tail is in the same word as the + * rest of the string. Every machine with memory protection I've seen + * does it on word boundaries, so is OK with this. But VALGRIND will + * still catch it and complain. The masking trick does make the hash + * noticeably faster for short strings (like English words). + */ +#ifndef VALGRIND + + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; + case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; + case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=k[1]&0xffffff; a+=k[0]; break; + case 6 : b+=k[1]&0xffff; a+=k[0]; break; + case 5 : b+=k[1]&0xff; a+=k[0]; break; + case 4 : a+=k[0]; break; + case 3 : a+=k[0]&0xffffff; break; + case 2 : a+=k[0]&0xffff; break; + case 1 : a+=k[0]&0xff; break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + +#else /* make valgrind happy */ + + { + const uint8_t *k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ + case 1 : a+=k8[0]; break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + } + +#endif /* !valgrind */ + + } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { + const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ + const uint8_t *k8; + + /*--------------- all but last block: aligned reads and different mixing */ + while (length > 12) + { + a += k[0] + (((uint32_t)k[1])<<16); + b += k[2] + (((uint32_t)k[3])<<16); + c += k[4] + (((uint32_t)k[5])<<16); + mix(a,b,c); + length -= 12; + k += 6; + } + + /*----------------------------- handle the last (probably partial) block */ + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[4]+(((uint32_t)k[5])<<16); + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=k[4]; + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=k[2]; + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=k[0]; + break; + case 1 : a+=k8[0]; + break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; + + /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + a += ((uint32_t)k[1])<<8; + a += ((uint32_t)k[2])<<16; + a += ((uint32_t)k[3])<<24; + b += k[4]; + b += ((uint32_t)k[5])<<8; + b += ((uint32_t)k[6])<<16; + b += ((uint32_t)k[7])<<24; + c += k[8]; + c += ((uint32_t)k[9])<<8; + c += ((uint32_t)k[10])<<16; + c += ((uint32_t)k[11])<<24; + mix(a,b,c); + length -= 12; + k += 12; + } + + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + case 12: c+=((uint32_t)k[11])<<24; + case 11: c+=((uint32_t)k[10])<<16; + case 10: c+=((uint32_t)k[9])<<8; + case 9 : c+=k[8]; + case 8 : b+=((uint32_t)k[7])<<24; + case 7 : b+=((uint32_t)k[6])<<16; + case 6 : b+=((uint32_t)k[5])<<8; + case 5 : b+=k[4]; + case 4 : a+=((uint32_t)k[3])<<24; + case 3 : a+=((uint32_t)k[2])<<16; + case 2 : a+=((uint32_t)k[1])<<8; + case 1 : a+=k[0]; + break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + } + + final(a,b,c); + *pc=c; *pb=b; +} + + + +/* + * hashbig(): + * This is the same as hashword() on big-endian machines. It is different + * from hashlittle() on all machines. hashbig() takes advantage of + * big-endian byte ordering. + */ +uint32_t jenkins_hashbig( const void *key, size_t length, uint32_t initval) +{ + uint32_t a,b,c; + union { const void *ptr; size_t i; } u; /* to cast key to (size_t) happily */ + + /* Set up the internal state */ + a = b = c = 0xdeadbeef + ((uint32_t)length) + initval; + + u.ptr = key; + if (HASH_BIG_ENDIAN && ((u.i & 0x3) == 0)) { + const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */ + + /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; + } + + /*----------------------------- handle the last (probably partial) block */ + /* + * "k[2]<<8" actually reads beyond the end of the string, but + * then shifts out the part it's not allowed to read. Because the + * string is aligned, the illegal read is in the same word as the + * rest of the string. Every machine with memory protection I've seen + * does it on word boundaries, so is OK with this. But VALGRIND will + * still catch it and complain. The masking trick does make the hash + * noticeably faster for short strings (like English words). + */ +#ifndef VALGRIND + + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=k[2]&0xffffff00; b+=k[1]; a+=k[0]; break; + case 10: c+=k[2]&0xffff0000; b+=k[1]; a+=k[0]; break; + case 9 : c+=k[2]&0xff000000; b+=k[1]; a+=k[0]; break; + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=k[1]&0xffffff00; a+=k[0]; break; + case 6 : b+=k[1]&0xffff0000; a+=k[0]; break; + case 5 : b+=k[1]&0xff000000; a+=k[0]; break; + case 4 : a+=k[0]; break; + case 3 : a+=k[0]&0xffffff00; break; + case 2 : a+=k[0]&0xffff0000; break; + case 1 : a+=k[0]&0xff000000; break; + case 0 : return c; /* zero length strings require no mixing */ + } + +#else /* make valgrind happy */ + + { + const uint8_t *k8 = (const uint8_t *)k; + switch(length) /* all the case statements fall through */ + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<8; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<16; /* fall through */ + case 9 : c+=((uint32_t)k8[8])<<24; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<8; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<16; /* fall through */ + case 5 : b+=((uint32_t)k8[4])<<24; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<8; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<16; /* fall through */ + case 1 : a+=((uint32_t)k8[0])<<24; break; + case 0 : return c; + } + } + +#endif /* !VALGRIND */ + + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; + + /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) + { + a += ((uint32_t)k[0])<<24; + a += ((uint32_t)k[1])<<16; + a += ((uint32_t)k[2])<<8; + a += ((uint32_t)k[3]); + b += ((uint32_t)k[4])<<24; + b += ((uint32_t)k[5])<<16; + b += ((uint32_t)k[6])<<8; + b += ((uint32_t)k[7]); + c += ((uint32_t)k[8])<<24; + c += ((uint32_t)k[9])<<16; + c += ((uint32_t)k[10])<<8; + c += ((uint32_t)k[11]); + mix(a,b,c); + length -= 12; + k += 12; + } + + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + case 12: c+=k[11]; + case 11: c+=((uint32_t)k[10])<<8; + case 10: c+=((uint32_t)k[9])<<16; + case 9 : c+=((uint32_t)k[8])<<24; + case 8 : b+=k[7]; + case 7 : b+=((uint32_t)k[6])<<8; + case 6 : b+=((uint32_t)k[5])<<16; + case 5 : b+=((uint32_t)k[4])<<24; + case 4 : a+=k[3]; + case 3 : a+=((uint32_t)k[2])<<8; + case 2 : a+=((uint32_t)k[1])<<16; + case 1 : a+=((uint32_t)k[0])<<24; + break; + case 0 : return c; + } + } + + final(a,b,c); + return c; +} + + +#ifdef SELF_TEST + +/* used for timings */ +void driver1() +{ + uint8_t buf[256]; + uint32_t i; + uint32_t h=0; + time_t a,z; + + time(&a); + for (i=0; i<256; ++i) buf[i] = 'x'; + for (i=0; i<1; ++i) + { + h = hashlittle(&buf[0],1,h); + } + time(&z); + if (z-a > 0) printf("time %d %.8x\n", z-a, h); +} + +/* check that every input bit changes every output bit half the time */ +#define HASHSTATE 1 +#define HASHLEN 1 +#define MAXPAIR 60 +#define MAXLEN 70 +void driver2() +{ + uint8_t qa[MAXLEN+1], qb[MAXLEN+2], *a = &qa[0], *b = &qb[1]; + uint32_t c[HASHSTATE], d[HASHSTATE], i=0, j=0, k, l, m=0, z; + uint32_t e[HASHSTATE],f[HASHSTATE],g[HASHSTATE],h[HASHSTATE]; + uint32_t x[HASHSTATE],y[HASHSTATE]; + uint32_t hlen; + + printf("No more than %d trials should ever be needed \n",MAXPAIR/2); + for (hlen=0; hlen < MAXLEN; ++hlen) + { + z=0; + for (i=0; i>(8-j)); + c[0] = hashlittle(a, hlen, m); + b[i] ^= ((k+1)<>(8-j)); + d[0] = hashlittle(b, hlen, m); + /* check every bit is 1, 0, set, and not set at least once */ + for (l=0; lz) z=k; + if (k==MAXPAIR) + { + printf("Some bit didn't change: "); + printf("%.8x %.8x %.8x %.8x %.8x %.8x ", + e[0],f[0],g[0],h[0],x[0],y[0]); + printf("i %d j %d m %d len %d\n", i, j, m, hlen); + } + if (z==MAXPAIR) goto done; + } + } + } + done: + if (z < MAXPAIR) + { + printf("Mix success %2d bytes %2d initvals ",i,m); + printf("required %d trials\n", z/2); + } + } + printf("\n"); +} + +/* Check for reading beyond the end of the buffer and alignment problems */ +void driver3() +{ + uint8_t buf[MAXLEN+20], *b; + uint32_t len; + uint8_t q[] = "This is the time for all good men to come to the aid of their country..."; + uint32_t h; + uint8_t qq[] = "xThis is the time for all good men to come to the aid of their country..."; + uint32_t i; + uint8_t qqq[] = "xxThis is the time for all good men to come to the aid of their country..."; + uint32_t j; + uint8_t qqqq[] = "xxxThis is the time for all good men to come to the aid of their country..."; + uint32_t ref,x,y; + uint8_t *p; + + printf("Endianness. These lines should all be the same (for values filled in):\n"); + printf("%.8x %.8x %.8x\n", + hashword((const uint32_t *)q, (sizeof(q)-1)/4, 13), + hashword((const uint32_t *)q, (sizeof(q)-5)/4, 13), + hashword((const uint32_t *)q, (sizeof(q)-9)/4, 13)); + p = q; + printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n", + hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13), + hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13), + hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13), + hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13), + hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13), + hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13)); + p = &qq[1]; + printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n", + hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13), + hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13), + hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13), + hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13), + hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13), + hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13)); + p = &qqq[2]; + printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n", + hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13), + hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13), + hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13), + hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13), + hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13), + hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13)); + p = &qqqq[3]; + printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n", + hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13), + hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13), + hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13), + hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13), + hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13), + hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13)); + printf("\n"); + + /* check that hashlittle2 and hashlittle produce the same results */ + i=47; j=0; + hashlittle2(q, sizeof(q), &i, &j); + if (hashlittle(q, sizeof(q), 47) != i) + printf("hashlittle2 and hashlittle mismatch\n"); + + /* check that hashword2 and hashword produce the same results */ + len = 0xdeadbeef; + i=47, j=0; + hashword2(&len, 1, &i, &j); + if (hashword(&len, 1, 47) != i) + printf("hashword2 and hashword mismatch %x %x\n", + i, hashword(&len, 1, 47)); + + /* check hashlittle doesn't read before or after the ends of the string */ + for (h=0, b=buf+1; h<8; ++h, ++b) + { + for (i=0; i +#include + +#include "macro.h" + +uint32_t jenkins_hashword(const uint32_t *k, size_t length, uint32_t initval) _pure_; +void jenkins_hashword2(const uint32_t *k, size_t length, uint32_t *pc, uint32_t *pb); + +uint32_t jenkins_hashlittle(const void *key, size_t length, uint32_t initval) _pure_; +void jenkins_hashlittle2(const void *key, size_t length, uint32_t *pc, uint32_t *pb); + +uint32_t jenkins_hashbig(const void *key, size_t length, uint32_t initval) _pure_; + +static inline uint64_t hash64(const void *data, size_t length) { + uint32_t a = 0, b = 0; + + jenkins_hashlittle2(data, length, &a, &b); + + return ((uint64_t) a << 32ULL) | (uint64_t) b; +} diff --git a/src/libsystemd/libsystemd-journal-internal/mmap-cache.c b/src/libsystemd/libsystemd-journal-internal/mmap-cache.c new file mode 100644 index 0000000000..6bcd9b6ac8 --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/mmap-cache.c @@ -0,0 +1,725 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "list.h" +#include "log.h" +#include "macro.h" +#include "mmap-cache.h" +#include "sigbus.h" +#include "util.h" + +typedef struct Window Window; +typedef struct Context Context; +typedef struct FileDescriptor FileDescriptor; + +struct Window { + MMapCache *cache; + + bool invalidated:1; + bool keep_always:1; + bool in_unused:1; + + int prot; + void *ptr; + uint64_t offset; + size_t size; + + FileDescriptor *fd; + + LIST_FIELDS(Window, by_fd); + LIST_FIELDS(Window, unused); + + LIST_HEAD(Context, contexts); +}; + +struct Context { + MMapCache *cache; + unsigned id; + Window *window; + + LIST_FIELDS(Context, by_window); +}; + +struct FileDescriptor { + MMapCache *cache; + int fd; + bool sigbus; + LIST_HEAD(Window, windows); +}; + +struct MMapCache { + int n_ref; + unsigned n_windows; + + unsigned n_hit, n_missed; + + Hashmap *fds; + Context *contexts[MMAP_CACHE_MAX_CONTEXTS]; + + LIST_HEAD(Window, unused); + Window *last_unused; +}; + +#define WINDOWS_MIN 64 + +#ifdef ENABLE_DEBUG_MMAP_CACHE +/* Tiny windows increase mmap activity and the chance of exposing unsafe use. */ +# define WINDOW_SIZE (page_size()) +#else +# define WINDOW_SIZE (8ULL*1024ULL*1024ULL) +#endif + +MMapCache* mmap_cache_new(void) { + MMapCache *m; + + m = new0(MMapCache, 1); + if (!m) + return NULL; + + m->n_ref = 1; + return m; +} + +MMapCache* mmap_cache_ref(MMapCache *m) { + assert(m); + assert(m->n_ref > 0); + + m->n_ref++; + return m; +} + +static void window_unlink(Window *w) { + Context *c; + + assert(w); + + if (w->ptr) + munmap(w->ptr, w->size); + + if (w->fd) + LIST_REMOVE(by_fd, w->fd->windows, w); + + if (w->in_unused) { + if (w->cache->last_unused == w) + w->cache->last_unused = w->unused_prev; + + LIST_REMOVE(unused, w->cache->unused, w); + } + + LIST_FOREACH(by_window, c, w->contexts) { + assert(c->window == w); + c->window = NULL; + } +} + +static void window_invalidate(Window *w) { + assert(w); + + if (w->invalidated) + return; + + /* Replace the window with anonymous pages. This is useful + * when we hit a SIGBUS and want to make sure the file cannot + * trigger any further SIGBUS, possibly overrunning the sigbus + * queue. */ + + assert_se(mmap(w->ptr, w->size, w->prot, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0) == w->ptr); + w->invalidated = true; +} + +static void window_free(Window *w) { + assert(w); + + window_unlink(w); + w->cache->n_windows--; + free(w); +} + +_pure_ static bool window_matches(Window *w, int fd, int prot, uint64_t offset, size_t size) { + assert(w); + assert(fd >= 0); + assert(size > 0); + + return + w->fd && + fd == w->fd->fd && + prot == w->prot && + offset >= w->offset && + offset + size <= w->offset + w->size; +} + +static Window *window_add(MMapCache *m, FileDescriptor *fd, int prot, bool keep_always, uint64_t offset, size_t size, void *ptr) { + Window *w; + + assert(m); + assert(fd); + + if (!m->last_unused || m->n_windows <= WINDOWS_MIN) { + + /* Allocate a new window */ + w = new0(Window, 1); + if (!w) + return NULL; + m->n_windows++; + } else { + + /* Reuse an existing one */ + w = m->last_unused; + window_unlink(w); + zero(*w); + } + + w->cache = m; + w->fd = fd; + w->prot = prot; + w->keep_always = keep_always; + w->offset = offset; + w->size = size; + w->ptr = ptr; + + LIST_PREPEND(by_fd, fd->windows, w); + + return w; +} + +static void context_detach_window(Context *c) { + Window *w; + + assert(c); + + if (!c->window) + return; + + w = c->window; + c->window = NULL; + LIST_REMOVE(by_window, w->contexts, c); + + if (!w->contexts && !w->keep_always) { + /* Not used anymore? */ +#ifdef ENABLE_DEBUG_MMAP_CACHE + /* Unmap unused windows immediately to expose use-after-unmap + * by SIGSEGV. */ + window_free(w); +#else + LIST_PREPEND(unused, c->cache->unused, w); + if (!c->cache->last_unused) + c->cache->last_unused = w; + + w->in_unused = true; +#endif + } +} + +static void context_attach_window(Context *c, Window *w) { + assert(c); + assert(w); + + if (c->window == w) + return; + + context_detach_window(c); + + if (w->in_unused) { + /* Used again? */ + LIST_REMOVE(unused, c->cache->unused, w); + if (c->cache->last_unused == w) + c->cache->last_unused = w->unused_prev; + + w->in_unused = false; + } + + c->window = w; + LIST_PREPEND(by_window, w->contexts, c); +} + +static Context *context_add(MMapCache *m, unsigned id) { + Context *c; + + assert(m); + + c = m->contexts[id]; + if (c) + return c; + + c = new0(Context, 1); + if (!c) + return NULL; + + c->cache = m; + c->id = id; + + assert(!m->contexts[id]); + m->contexts[id] = c; + + return c; +} + +static void context_free(Context *c) { + assert(c); + + context_detach_window(c); + + if (c->cache) { + assert(c->cache->contexts[c->id] == c); + c->cache->contexts[c->id] = NULL; + } + + free(c); +} + +static void fd_free(FileDescriptor *f) { + assert(f); + + while (f->windows) + window_free(f->windows); + + if (f->cache) + assert_se(hashmap_remove(f->cache->fds, FD_TO_PTR(f->fd))); + + free(f); +} + +static FileDescriptor* fd_add(MMapCache *m, int fd) { + FileDescriptor *f; + int r; + + assert(m); + assert(fd >= 0); + + f = hashmap_get(m->fds, FD_TO_PTR(fd)); + if (f) + return f; + + r = hashmap_ensure_allocated(&m->fds, NULL); + if (r < 0) + return NULL; + + f = new0(FileDescriptor, 1); + if (!f) + return NULL; + + f->cache = m; + f->fd = fd; + + r = hashmap_put(m->fds, FD_TO_PTR(fd), f); + if (r < 0) { + free(f); + return NULL; + } + + return f; +} + +static void mmap_cache_free(MMapCache *m) { + FileDescriptor *f; + int i; + + assert(m); + + for (i = 0; i < MMAP_CACHE_MAX_CONTEXTS; i++) + if (m->contexts[i]) + context_free(m->contexts[i]); + + while ((f = hashmap_first(m->fds))) + fd_free(f); + + hashmap_free(m->fds); + + while (m->unused) + window_free(m->unused); + + free(m); +} + +MMapCache* mmap_cache_unref(MMapCache *m) { + + if (!m) + return NULL; + + assert(m->n_ref > 0); + + m->n_ref--; + if (m->n_ref == 0) + mmap_cache_free(m); + + return NULL; +} + +static int make_room(MMapCache *m) { + assert(m); + + if (!m->last_unused) + return 0; + + window_free(m->last_unused); + return 1; +} + +static int try_context( + MMapCache *m, + int fd, + int prot, + unsigned context, + bool keep_always, + uint64_t offset, + size_t size, + void **ret) { + + Context *c; + + assert(m); + assert(m->n_ref > 0); + assert(fd >= 0); + assert(size > 0); + assert(ret); + + c = m->contexts[context]; + if (!c) + return 0; + + assert(c->id == context); + + if (!c->window) + return 0; + + if (!window_matches(c->window, fd, prot, offset, size)) { + + /* Drop the reference to the window, since it's unnecessary now */ + context_detach_window(c); + return 0; + } + + if (c->window->fd->sigbus) + return -EIO; + + c->window->keep_always = c->window->keep_always || keep_always; + + *ret = (uint8_t*) c->window->ptr + (offset - c->window->offset); + return 1; +} + +static int find_mmap( + MMapCache *m, + int fd, + int prot, + unsigned context, + bool keep_always, + uint64_t offset, + size_t size, + void **ret) { + + FileDescriptor *f; + Window *w; + Context *c; + + assert(m); + assert(m->n_ref > 0); + assert(fd >= 0); + assert(size > 0); + + f = hashmap_get(m->fds, FD_TO_PTR(fd)); + if (!f) + return 0; + + assert(f->fd == fd); + + if (f->sigbus) + return -EIO; + + LIST_FOREACH(by_fd, w, f->windows) + if (window_matches(w, fd, prot, offset, size)) + break; + + if (!w) + return 0; + + c = context_add(m, context); + if (!c) + return -ENOMEM; + + context_attach_window(c, w); + w->keep_always = w->keep_always || keep_always; + + *ret = (uint8_t*) w->ptr + (offset - w->offset); + return 1; +} + +static int mmap_try_harder(MMapCache *m, void *addr, int fd, int prot, int flags, uint64_t offset, size_t size, void **res) { + void *ptr; + + assert(m); + assert(fd >= 0); + assert(res); + + for (;;) { + int r; + + ptr = mmap(addr, size, prot, flags, fd, offset); + if (ptr != MAP_FAILED) + break; + if (errno != ENOMEM) + return -errno; + + r = make_room(m); + if (r < 0) + return r; + if (r == 0) + return -ENOMEM; + } + + *res = ptr; + return 0; +} + +static int add_mmap( + MMapCache *m, + int fd, + int prot, + unsigned context, + bool keep_always, + uint64_t offset, + size_t size, + struct stat *st, + void **ret) { + + uint64_t woffset, wsize; + Context *c; + FileDescriptor *f; + Window *w; + void *d; + int r; + + assert(m); + assert(m->n_ref > 0); + assert(fd >= 0); + assert(size > 0); + assert(ret); + + woffset = offset & ~((uint64_t) page_size() - 1ULL); + wsize = size + (offset - woffset); + wsize = PAGE_ALIGN(wsize); + + if (wsize < WINDOW_SIZE) { + uint64_t delta; + + delta = PAGE_ALIGN((WINDOW_SIZE - wsize) / 2); + + if (delta > offset) + woffset = 0; + else + woffset -= delta; + + wsize = WINDOW_SIZE; + } + + if (st) { + /* Memory maps that are larger then the files + underneath have undefined behavior. Hence, clamp + things to the file size if we know it */ + + if (woffset >= (uint64_t) st->st_size) + return -EADDRNOTAVAIL; + + if (woffset + wsize > (uint64_t) st->st_size) + wsize = PAGE_ALIGN(st->st_size - woffset); + } + + r = mmap_try_harder(m, NULL, fd, prot, MAP_SHARED, woffset, wsize, &d); + if (r < 0) + return r; + + c = context_add(m, context); + if (!c) + goto outofmem; + + f = fd_add(m, fd); + if (!f) + goto outofmem; + + w = window_add(m, f, prot, keep_always, woffset, wsize, d); + if (!w) + goto outofmem; + + context_detach_window(c); + c->window = w; + LIST_PREPEND(by_window, w->contexts, c); + + *ret = (uint8_t*) w->ptr + (offset - w->offset); + return 1; + +outofmem: + munmap(d, wsize); + return -ENOMEM; +} + +int mmap_cache_get( + MMapCache *m, + int fd, + int prot, + unsigned context, + bool keep_always, + uint64_t offset, + size_t size, + struct stat *st, + void **ret) { + + int r; + + assert(m); + assert(m->n_ref > 0); + assert(fd >= 0); + assert(size > 0); + assert(ret); + assert(context < MMAP_CACHE_MAX_CONTEXTS); + + /* Check whether the current context is the right one already */ + r = try_context(m, fd, prot, context, keep_always, offset, size, ret); + if (r != 0) { + m->n_hit++; + return r; + } + + /* Search for a matching mmap */ + r = find_mmap(m, fd, prot, context, keep_always, offset, size, ret); + if (r != 0) { + m->n_hit++; + return r; + } + + m->n_missed++; + + /* Create a new mmap */ + return add_mmap(m, fd, prot, context, keep_always, offset, size, st, ret); +} + +unsigned mmap_cache_get_hit(MMapCache *m) { + assert(m); + + return m->n_hit; +} + +unsigned mmap_cache_get_missed(MMapCache *m) { + assert(m); + + return m->n_missed; +} + +static void mmap_cache_process_sigbus(MMapCache *m) { + bool found = false; + FileDescriptor *f; + Iterator i; + int r; + + assert(m); + + /* Iterate through all triggered pages and mark their files as + * invalidated */ + for (;;) { + bool ours; + void *addr; + + r = sigbus_pop(&addr); + if (_likely_(r == 0)) + break; + if (r < 0) { + log_error_errno(r, "SIGBUS handling failed: %m"); + abort(); + } + + ours = false; + HASHMAP_FOREACH(f, m->fds, i) { + Window *w; + + LIST_FOREACH(by_fd, w, f->windows) { + if ((uint8_t*) addr >= (uint8_t*) w->ptr && + (uint8_t*) addr < (uint8_t*) w->ptr + w->size) { + found = ours = f->sigbus = true; + break; + } + } + + if (ours) + break; + } + + /* Didn't find a matching window, give up */ + if (!ours) { + log_error("Unknown SIGBUS page, aborting."); + abort(); + } + } + + /* The list of triggered pages is now empty. Now, let's remap + * all windows of the triggered file to anonymous maps, so + * that no page of the file in question is triggered again, so + * that we can be sure not to hit the queue size limit. */ + if (_likely_(!found)) + return; + + HASHMAP_FOREACH(f, m->fds, i) { + Window *w; + + if (!f->sigbus) + continue; + + LIST_FOREACH(by_fd, w, f->windows) + window_invalidate(w); + } +} + +bool mmap_cache_got_sigbus(MMapCache *m, int fd) { + FileDescriptor *f; + + assert(m); + assert(fd >= 0); + + mmap_cache_process_sigbus(m); + + f = hashmap_get(m->fds, FD_TO_PTR(fd)); + if (!f) + return false; + + return f->sigbus; +} + +void mmap_cache_close_fd(MMapCache *m, int fd) { + FileDescriptor *f; + + assert(m); + assert(fd >= 0); + + /* Make sure that any queued SIGBUS are first dispatched, so + * that we don't end up with a SIGBUS entry we cannot relate + * to any existing memory map */ + + mmap_cache_process_sigbus(m); + + f = hashmap_get(m->fds, FD_TO_PTR(fd)); + if (!f) + return; + + fd_free(f); +} diff --git a/src/libsystemd/libsystemd-journal-internal/mmap-cache.h b/src/libsystemd/libsystemd-journal-internal/mmap-cache.h new file mode 100644 index 0000000000..199d944647 --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/mmap-cache.h @@ -0,0 +1,49 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 + +/* One context per object type, plus one of the header, plus one "additional" one */ +#define MMAP_CACHE_MAX_CONTEXTS 9 + +typedef struct MMapCache MMapCache; + +MMapCache* mmap_cache_new(void); +MMapCache* mmap_cache_ref(MMapCache *m); +MMapCache* mmap_cache_unref(MMapCache *m); + +int mmap_cache_get( + MMapCache *m, + int fd, + int prot, + unsigned context, + bool keep_always, + uint64_t offset, + size_t size, + struct stat *st, + void **ret); +void mmap_cache_close_fd(MMapCache *m, int fd); + +unsigned mmap_cache_get_hit(MMapCache *m); +unsigned mmap_cache_get_missed(MMapCache *m); + +bool mmap_cache_got_sigbus(MMapCache *m, int fd); diff --git a/src/libsystemd/libsystemd-journal-internal/sd-journal.c b/src/libsystemd/libsystemd-journal-internal/sd-journal.c new file mode 100644 index 0000000000..930486d65f --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/sd-journal.c @@ -0,0 +1,2985 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "catalog.h" +#include "compress.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "hashmap.h" +#include "hostname-util.h" +#include "io-util.h" +#include "journal-def.h" +#include "journal-file.h" +#include "journal-internal.h" +#include "list.h" +#include "lookup3.h" +#include "missing.h" +#include "path-util.h" +#include "replace-var.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" + +#define JOURNAL_FILES_MAX 7168 + +#define JOURNAL_FILES_RECHECK_USEC (2 * USEC_PER_SEC) + +#define REPLACE_VAR_MAX 256 + +#define DEFAULT_DATA_THRESHOLD (64*1024) + +static void remove_file_real(sd_journal *j, JournalFile *f); + +static bool journal_pid_changed(sd_journal *j) { + assert(j); + + /* We don't support people creating a journal object and + * keeping it around over a fork(). Let's complain. */ + + return j->original_pid != getpid(); +} + +static int journal_put_error(sd_journal *j, int r, const char *path) { + char *copy; + int k; + + /* Memorize an error we encountered, and store which + * file/directory it was generated from. Note that we store + * only *one* path per error code, as the error code is the + * key into the hashmap, and the path is the value. This means + * we keep track only of all error kinds, but not of all error + * locations. This has the benefit that the hashmap cannot + * grow beyond bounds. + * + * We return an error here only if we didn't manage to + * memorize the real error. */ + + if (r >= 0) + return r; + + k = hashmap_ensure_allocated(&j->errors, NULL); + if (k < 0) + return k; + + if (path) { + copy = strdup(path); + if (!copy) + return -ENOMEM; + } else + copy = NULL; + + k = hashmap_put(j->errors, INT_TO_PTR(r), copy); + if (k < 0) { + free(copy); + + if (k == -EEXIST) + return 0; + + return k; + } + + return 0; +} + +static void detach_location(sd_journal *j) { + Iterator i; + JournalFile *f; + + assert(j); + + j->current_file = NULL; + j->current_field = 0; + + ORDERED_HASHMAP_FOREACH(f, j->files, i) + journal_file_reset_location(f); +} + +static void reset_location(sd_journal *j) { + assert(j); + + detach_location(j); + zero(j->current_location); +} + +static void init_location(Location *l, LocationType type, JournalFile *f, Object *o) { + assert(l); + assert(type == LOCATION_DISCRETE || type == LOCATION_SEEK); + assert(f); + assert(o->object.type == OBJECT_ENTRY); + + l->type = type; + l->seqnum = le64toh(o->entry.seqnum); + l->seqnum_id = f->header->seqnum_id; + l->realtime = le64toh(o->entry.realtime); + l->monotonic = le64toh(o->entry.monotonic); + l->boot_id = o->entry.boot_id; + l->xor_hash = le64toh(o->entry.xor_hash); + + l->seqnum_set = l->realtime_set = l->monotonic_set = l->xor_hash_set = true; +} + +static void set_location(sd_journal *j, JournalFile *f, Object *o) { + assert(j); + assert(f); + assert(o); + + init_location(&j->current_location, LOCATION_DISCRETE, f, o); + + j->current_file = f; + j->current_field = 0; + + /* Let f know its candidate entry was picked. */ + assert(f->location_type == LOCATION_SEEK); + f->location_type = LOCATION_DISCRETE; +} + +static int match_is_valid(const void *data, size_t size) { + const char *b, *p; + + assert(data); + + if (size < 2) + return false; + + if (startswith(data, "__")) + return false; + + b = data; + for (p = b; p < b + size; p++) { + + if (*p == '=') + return p > b; + + if (*p == '_') + continue; + + if (*p >= 'A' && *p <= 'Z') + continue; + + if (*p >= '0' && *p <= '9') + continue; + + return false; + } + + return false; +} + +static bool same_field(const void *_a, size_t s, const void *_b, size_t t) { + const uint8_t *a = _a, *b = _b; + size_t j; + + for (j = 0; j < s && j < t; j++) { + + if (a[j] != b[j]) + return false; + + if (a[j] == '=') + return true; + } + + assert_not_reached("\"=\" not found"); +} + +static Match *match_new(Match *p, MatchType t) { + Match *m; + + m = new0(Match, 1); + if (!m) + return NULL; + + m->type = t; + + if (p) { + m->parent = p; + LIST_PREPEND(matches, p->matches, m); + } + + return m; +} + +static void match_free(Match *m) { + assert(m); + + while (m->matches) + match_free(m->matches); + + if (m->parent) + LIST_REMOVE(matches, m->parent->matches, m); + + free(m->data); + free(m); +} + +static void match_free_if_empty(Match *m) { + if (!m || m->matches) + return; + + match_free(m); +} + +_public_ int sd_journal_add_match(sd_journal *j, const void *data, size_t size) { + Match *l3, *l4, *add_here = NULL, *m; + le64_t le_hash; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(data, -EINVAL); + + if (size == 0) + size = strlen(data); + + assert_return(match_is_valid(data, size), -EINVAL); + + /* level 0: AND term + * level 1: OR terms + * level 2: AND terms + * level 3: OR terms + * level 4: concrete matches */ + + if (!j->level0) { + j->level0 = match_new(NULL, MATCH_AND_TERM); + if (!j->level0) + return -ENOMEM; + } + + if (!j->level1) { + j->level1 = match_new(j->level0, MATCH_OR_TERM); + if (!j->level1) + return -ENOMEM; + } + + if (!j->level2) { + j->level2 = match_new(j->level1, MATCH_AND_TERM); + if (!j->level2) + return -ENOMEM; + } + + assert(j->level0->type == MATCH_AND_TERM); + assert(j->level1->type == MATCH_OR_TERM); + assert(j->level2->type == MATCH_AND_TERM); + + le_hash = htole64(hash64(data, size)); + + LIST_FOREACH(matches, l3, j->level2->matches) { + assert(l3->type == MATCH_OR_TERM); + + LIST_FOREACH(matches, l4, l3->matches) { + assert(l4->type == MATCH_DISCRETE); + + /* Exactly the same match already? Then ignore + * this addition */ + if (l4->le_hash == le_hash && + l4->size == size && + memcmp(l4->data, data, size) == 0) + return 0; + + /* Same field? Then let's add this to this OR term */ + if (same_field(data, size, l4->data, l4->size)) { + add_here = l3; + break; + } + } + + if (add_here) + break; + } + + if (!add_here) { + add_here = match_new(j->level2, MATCH_OR_TERM); + if (!add_here) + goto fail; + } + + m = match_new(add_here, MATCH_DISCRETE); + if (!m) + goto fail; + + m->le_hash = le_hash; + m->size = size; + m->data = memdup(data, size); + if (!m->data) + goto fail; + + detach_location(j); + + return 0; + +fail: + match_free_if_empty(add_here); + match_free_if_empty(j->level2); + match_free_if_empty(j->level1); + match_free_if_empty(j->level0); + + return -ENOMEM; +} + +_public_ int sd_journal_add_conjunction(sd_journal *j) { + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + if (!j->level0) + return 0; + + if (!j->level1) + return 0; + + if (!j->level1->matches) + return 0; + + j->level1 = NULL; + j->level2 = NULL; + + return 0; +} + +_public_ int sd_journal_add_disjunction(sd_journal *j) { + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + if (!j->level0) + return 0; + + if (!j->level1) + return 0; + + if (!j->level2) + return 0; + + if (!j->level2->matches) + return 0; + + j->level2 = NULL; + return 0; +} + +static char *match_make_string(Match *m) { + char *p, *r; + Match *i; + bool enclose = false; + + if (!m) + return strdup("none"); + + if (m->type == MATCH_DISCRETE) + return strndup(m->data, m->size); + + p = NULL; + LIST_FOREACH(matches, i, m->matches) { + char *t, *k; + + t = match_make_string(i); + if (!t) { + free(p); + return NULL; + } + + if (p) { + k = strjoin(p, m->type == MATCH_OR_TERM ? " OR " : " AND ", t, NULL); + free(p); + free(t); + + if (!k) + return NULL; + + p = k; + + enclose = true; + } else + p = t; + } + + if (enclose) { + r = strjoin("(", p, ")", NULL); + free(p); + return r; + } + + return p; +} + +char *journal_make_match_string(sd_journal *j) { + assert(j); + + return match_make_string(j->level0); +} + +_public_ void sd_journal_flush_matches(sd_journal *j) { + if (!j) + return; + + if (j->level0) + match_free(j->level0); + + j->level0 = j->level1 = j->level2 = NULL; + + detach_location(j); +} + +_pure_ static int compare_with_location(JournalFile *f, Location *l) { + assert(f); + assert(l); + assert(f->location_type == LOCATION_SEEK); + assert(l->type == LOCATION_DISCRETE || l->type == LOCATION_SEEK); + + if (l->monotonic_set && + sd_id128_equal(f->current_boot_id, l->boot_id) && + l->realtime_set && + f->current_realtime == l->realtime && + l->xor_hash_set && + f->current_xor_hash == l->xor_hash) + return 0; + + if (l->seqnum_set && + sd_id128_equal(f->header->seqnum_id, l->seqnum_id)) { + + if (f->current_seqnum < l->seqnum) + return -1; + if (f->current_seqnum > l->seqnum) + return 1; + } + + if (l->monotonic_set && + sd_id128_equal(f->current_boot_id, l->boot_id)) { + + if (f->current_monotonic < l->monotonic) + return -1; + if (f->current_monotonic > l->monotonic) + return 1; + } + + if (l->realtime_set) { + + if (f->current_realtime < l->realtime) + return -1; + if (f->current_realtime > l->realtime) + return 1; + } + + if (l->xor_hash_set) { + + if (f->current_xor_hash < l->xor_hash) + return -1; + if (f->current_xor_hash > l->xor_hash) + return 1; + } + + return 0; +} + +static int next_for_match( + sd_journal *j, + Match *m, + JournalFile *f, + uint64_t after_offset, + direction_t direction, + Object **ret, + uint64_t *offset) { + + int r; + uint64_t np = 0; + Object *n; + + assert(j); + assert(m); + assert(f); + + if (m->type == MATCH_DISCRETE) { + uint64_t dp; + + r = journal_file_find_data_object_with_hash(f, m->data, m->size, le64toh(m->le_hash), NULL, &dp); + if (r <= 0) + return r; + + return journal_file_move_to_entry_by_offset_for_data(f, dp, after_offset, direction, ret, offset); + + } else if (m->type == MATCH_OR_TERM) { + Match *i; + + /* Find the earliest match beyond after_offset */ + + LIST_FOREACH(matches, i, m->matches) { + uint64_t cp; + + r = next_for_match(j, i, f, after_offset, direction, NULL, &cp); + if (r < 0) + return r; + else if (r > 0) { + if (np == 0 || (direction == DIRECTION_DOWN ? cp < np : cp > np)) + np = cp; + } + } + + if (np == 0) + return 0; + + } else if (m->type == MATCH_AND_TERM) { + Match *i, *last_moved; + + /* Always jump to the next matching entry and repeat + * this until we find an offset that matches for all + * matches. */ + + if (!m->matches) + return 0; + + r = next_for_match(j, m->matches, f, after_offset, direction, NULL, &np); + if (r <= 0) + return r; + + assert(direction == DIRECTION_DOWN ? np >= after_offset : np <= after_offset); + last_moved = m->matches; + + LIST_LOOP_BUT_ONE(matches, i, m->matches, last_moved) { + uint64_t cp; + + r = next_for_match(j, i, f, np, direction, NULL, &cp); + if (r <= 0) + return r; + + assert(direction == DIRECTION_DOWN ? cp >= np : cp <= np); + if (direction == DIRECTION_DOWN ? cp > np : cp < np) { + np = cp; + last_moved = i; + } + } + } + + assert(np > 0); + + r = journal_file_move_to_object(f, OBJECT_ENTRY, np, &n); + if (r < 0) + return r; + + if (ret) + *ret = n; + if (offset) + *offset = np; + + return 1; +} + +static int find_location_for_match( + sd_journal *j, + Match *m, + JournalFile *f, + direction_t direction, + Object **ret, + uint64_t *offset) { + + int r; + + assert(j); + assert(m); + assert(f); + + if (m->type == MATCH_DISCRETE) { + uint64_t dp; + + r = journal_file_find_data_object_with_hash(f, m->data, m->size, le64toh(m->le_hash), NULL, &dp); + if (r <= 0) + return r; + + /* FIXME: missing: find by monotonic */ + + if (j->current_location.type == LOCATION_HEAD) + return journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_DOWN, ret, offset); + if (j->current_location.type == LOCATION_TAIL) + return journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_UP, ret, offset); + if (j->current_location.seqnum_set && sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id)) + return journal_file_move_to_entry_by_seqnum_for_data(f, dp, j->current_location.seqnum, direction, ret, offset); + if (j->current_location.monotonic_set) { + r = journal_file_move_to_entry_by_monotonic_for_data(f, dp, j->current_location.boot_id, j->current_location.monotonic, direction, ret, offset); + if (r != -ENOENT) + return r; + } + if (j->current_location.realtime_set) + return journal_file_move_to_entry_by_realtime_for_data(f, dp, j->current_location.realtime, direction, ret, offset); + + return journal_file_next_entry_for_data(f, NULL, 0, dp, direction, ret, offset); + + } else if (m->type == MATCH_OR_TERM) { + uint64_t np = 0; + Object *n; + Match *i; + + /* Find the earliest match */ + + LIST_FOREACH(matches, i, m->matches) { + uint64_t cp; + + r = find_location_for_match(j, i, f, direction, NULL, &cp); + if (r < 0) + return r; + else if (r > 0) { + if (np == 0 || (direction == DIRECTION_DOWN ? np > cp : np < cp)) + np = cp; + } + } + + if (np == 0) + return 0; + + r = journal_file_move_to_object(f, OBJECT_ENTRY, np, &n); + if (r < 0) + return r; + + if (ret) + *ret = n; + if (offset) + *offset = np; + + return 1; + + } else { + Match *i; + uint64_t np = 0; + + assert(m->type == MATCH_AND_TERM); + + /* First jump to the last match, and then find the + * next one where all matches match */ + + if (!m->matches) + return 0; + + LIST_FOREACH(matches, i, m->matches) { + uint64_t cp; + + r = find_location_for_match(j, i, f, direction, NULL, &cp); + if (r <= 0) + return r; + + if (np == 0 || (direction == DIRECTION_DOWN ? cp > np : cp < np)) + np = cp; + } + + return next_for_match(j, m, f, np, direction, ret, offset); + } +} + +static int find_location_with_matches( + sd_journal *j, + JournalFile *f, + direction_t direction, + Object **ret, + uint64_t *offset) { + + int r; + + assert(j); + assert(f); + assert(ret); + assert(offset); + + if (!j->level0) { + /* No matches is simple */ + + if (j->current_location.type == LOCATION_HEAD) + return journal_file_next_entry(f, 0, DIRECTION_DOWN, ret, offset); + if (j->current_location.type == LOCATION_TAIL) + return journal_file_next_entry(f, 0, DIRECTION_UP, ret, offset); + if (j->current_location.seqnum_set && sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id)) + return journal_file_move_to_entry_by_seqnum(f, j->current_location.seqnum, direction, ret, offset); + if (j->current_location.monotonic_set) { + r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, ret, offset); + if (r != -ENOENT) + return r; + } + if (j->current_location.realtime_set) + return journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, ret, offset); + + return journal_file_next_entry(f, 0, direction, ret, offset); + } else + return find_location_for_match(j, j->level0, f, direction, ret, offset); +} + +static int next_with_matches( + sd_journal *j, + JournalFile *f, + direction_t direction, + Object **ret, + uint64_t *offset) { + + assert(j); + assert(f); + assert(ret); + assert(offset); + + /* No matches is easy. We simple advance the file + * pointer by one. */ + if (!j->level0) + return journal_file_next_entry(f, f->current_offset, direction, ret, offset); + + /* If we have a match then we look for the next matching entry + * with an offset at least one step larger */ + return next_for_match(j, j->level0, f, + direction == DIRECTION_DOWN ? f->current_offset + 1 + : f->current_offset - 1, + direction, ret, offset); +} + +static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direction) { + Object *c; + uint64_t cp, n_entries; + int r; + + assert(j); + assert(f); + + n_entries = le64toh(f->header->n_entries); + + /* If we hit EOF before, we don't need to look into this file again + * unless direction changed or new entries appeared. */ + if (f->last_direction == direction && f->location_type == LOCATION_TAIL && + n_entries == f->last_n_entries) + return 0; + + f->last_n_entries = n_entries; + + if (f->last_direction == direction && f->current_offset > 0) { + /* LOCATION_SEEK here means we did the work in a previous + * iteration and the current location already points to a + * candidate entry. */ + if (f->location_type != LOCATION_SEEK) { + r = next_with_matches(j, f, direction, &c, &cp); + if (r <= 0) + return r; + + journal_file_save_location(f, c, cp); + } + } else { + f->last_direction = direction; + + r = find_location_with_matches(j, f, direction, &c, &cp); + if (r <= 0) + return r; + + journal_file_save_location(f, c, cp); + } + + /* OK, we found the spot, now let's advance until an entry + * that is actually different from what we were previously + * looking at. This is necessary to handle entries which exist + * in two (or more) journal files, and which shall all be + * suppressed but one. */ + + for (;;) { + bool found; + + if (j->current_location.type == LOCATION_DISCRETE) { + int k; + + k = compare_with_location(f, &j->current_location); + + found = direction == DIRECTION_DOWN ? k > 0 : k < 0; + } else + found = true; + + if (found) + return 1; + + r = next_with_matches(j, f, direction, &c, &cp); + if (r <= 0) + return r; + + journal_file_save_location(f, c, cp); + } +} + +static int real_journal_next(sd_journal *j, direction_t direction) { + JournalFile *f, *new_file = NULL; + Iterator i; + Object *o; + int r; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + ORDERED_HASHMAP_FOREACH(f, j->files, i) { + bool found; + + r = next_beyond_location(j, f, direction); + if (r < 0) { + log_debug_errno(r, "Can't iterate through %s, ignoring: %m", f->path); + remove_file_real(j, f); + continue; + } else if (r == 0) { + f->location_type = LOCATION_TAIL; + continue; + } + + if (!new_file) + found = true; + else { + int k; + + k = journal_file_compare_locations(f, new_file); + + found = direction == DIRECTION_DOWN ? k < 0 : k > 0; + } + + if (found) + new_file = f; + } + + if (!new_file) + return 0; + + r = journal_file_move_to_object(new_file, OBJECT_ENTRY, new_file->current_offset, &o); + if (r < 0) + return r; + + set_location(j, new_file, o); + + return 1; +} + +_public_ int sd_journal_next(sd_journal *j) { + return real_journal_next(j, DIRECTION_DOWN); +} + +_public_ int sd_journal_previous(sd_journal *j) { + return real_journal_next(j, DIRECTION_UP); +} + +static int real_journal_next_skip(sd_journal *j, direction_t direction, uint64_t skip) { + int c = 0, r; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + if (skip == 0) { + /* If this is not a discrete skip, then at least + * resolve the current location */ + if (j->current_location.type != LOCATION_DISCRETE) + return real_journal_next(j, direction); + + return 0; + } + + do { + r = real_journal_next(j, direction); + if (r < 0) + return r; + + if (r == 0) + return c; + + skip--; + c++; + } while (skip > 0); + + return c; +} + +_public_ int sd_journal_next_skip(sd_journal *j, uint64_t skip) { + return real_journal_next_skip(j, DIRECTION_DOWN, skip); +} + +_public_ int sd_journal_previous_skip(sd_journal *j, uint64_t skip) { + return real_journal_next_skip(j, DIRECTION_UP, skip); +} + +_public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) { + Object *o; + int r; + char bid[33], sid[33]; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(cursor, -EINVAL); + + if (!j->current_file || j->current_file->current_offset <= 0) + return -EADDRNOTAVAIL; + + r = journal_file_move_to_object(j->current_file, OBJECT_ENTRY, j->current_file->current_offset, &o); + if (r < 0) + return r; + + sd_id128_to_string(j->current_file->header->seqnum_id, sid); + sd_id128_to_string(o->entry.boot_id, bid); + + if (asprintf(cursor, + "s=%s;i=%"PRIx64";b=%s;m=%"PRIx64";t=%"PRIx64";x=%"PRIx64, + sid, le64toh(o->entry.seqnum), + bid, le64toh(o->entry.monotonic), + le64toh(o->entry.realtime), + le64toh(o->entry.xor_hash)) < 0) + return -ENOMEM; + + return 0; +} + +_public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) { + const char *word, *state; + size_t l; + unsigned long long seqnum, monotonic, realtime, xor_hash; + bool + seqnum_id_set = false, + seqnum_set = false, + boot_id_set = false, + monotonic_set = false, + realtime_set = false, + xor_hash_set = false; + sd_id128_t seqnum_id, boot_id; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(!isempty(cursor), -EINVAL); + + FOREACH_WORD_SEPARATOR(word, l, cursor, ";", state) { + char *item; + int k = 0; + + if (l < 2 || word[1] != '=') + return -EINVAL; + + item = strndup(word, l); + if (!item) + return -ENOMEM; + + switch (word[0]) { + + case 's': + seqnum_id_set = true; + k = sd_id128_from_string(item+2, &seqnum_id); + break; + + case 'i': + seqnum_set = true; + if (sscanf(item+2, "%llx", &seqnum) != 1) + k = -EINVAL; + break; + + case 'b': + boot_id_set = true; + k = sd_id128_from_string(item+2, &boot_id); + break; + + case 'm': + monotonic_set = true; + if (sscanf(item+2, "%llx", &monotonic) != 1) + k = -EINVAL; + break; + + case 't': + realtime_set = true; + if (sscanf(item+2, "%llx", &realtime) != 1) + k = -EINVAL; + break; + + case 'x': + xor_hash_set = true; + if (sscanf(item+2, "%llx", &xor_hash) != 1) + k = -EINVAL; + break; + } + + free(item); + + if (k < 0) + return k; + } + + if ((!seqnum_set || !seqnum_id_set) && + (!monotonic_set || !boot_id_set) && + !realtime_set) + return -EINVAL; + + reset_location(j); + + j->current_location.type = LOCATION_SEEK; + + if (realtime_set) { + j->current_location.realtime = (uint64_t) realtime; + j->current_location.realtime_set = true; + } + + if (seqnum_set && seqnum_id_set) { + j->current_location.seqnum = (uint64_t) seqnum; + j->current_location.seqnum_id = seqnum_id; + j->current_location.seqnum_set = true; + } + + if (monotonic_set && boot_id_set) { + j->current_location.monotonic = (uint64_t) monotonic; + j->current_location.boot_id = boot_id; + j->current_location.monotonic_set = true; + } + + if (xor_hash_set) { + j->current_location.xor_hash = (uint64_t) xor_hash; + j->current_location.xor_hash_set = true; + } + + return 0; +} + +_public_ int sd_journal_test_cursor(sd_journal *j, const char *cursor) { + int r; + Object *o; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(!isempty(cursor), -EINVAL); + + if (!j->current_file || j->current_file->current_offset <= 0) + return -EADDRNOTAVAIL; + + r = journal_file_move_to_object(j->current_file, OBJECT_ENTRY, j->current_file->current_offset, &o); + if (r < 0) + return r; + + for (;;) { + _cleanup_free_ char *item = NULL; + unsigned long long ll; + sd_id128_t id; + int k = 0; + + r = extract_first_word(&cursor, &item, ";", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + + if (r == 0) + break; + + if (strlen(item) < 2 || item[1] != '=') + return -EINVAL; + + switch (item[0]) { + + case 's': + k = sd_id128_from_string(item+2, &id); + if (k < 0) + return k; + if (!sd_id128_equal(id, j->current_file->header->seqnum_id)) + return 0; + break; + + case 'i': + if (sscanf(item+2, "%llx", &ll) != 1) + return -EINVAL; + if (ll != le64toh(o->entry.seqnum)) + return 0; + break; + + case 'b': + k = sd_id128_from_string(item+2, &id); + if (k < 0) + return k; + if (!sd_id128_equal(id, o->entry.boot_id)) + return 0; + break; + + case 'm': + if (sscanf(item+2, "%llx", &ll) != 1) + return -EINVAL; + if (ll != le64toh(o->entry.monotonic)) + return 0; + break; + + case 't': + if (sscanf(item+2, "%llx", &ll) != 1) + return -EINVAL; + if (ll != le64toh(o->entry.realtime)) + return 0; + break; + + case 'x': + if (sscanf(item+2, "%llx", &ll) != 1) + return -EINVAL; + if (ll != le64toh(o->entry.xor_hash)) + return 0; + break; + } + } + + return 1; +} + + +_public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) { + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + reset_location(j); + j->current_location.type = LOCATION_SEEK; + j->current_location.boot_id = boot_id; + j->current_location.monotonic = usec; + j->current_location.monotonic_set = true; + + return 0; +} + +_public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) { + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + reset_location(j); + j->current_location.type = LOCATION_SEEK; + j->current_location.realtime = usec; + j->current_location.realtime_set = true; + + return 0; +} + +_public_ int sd_journal_seek_head(sd_journal *j) { + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + reset_location(j); + j->current_location.type = LOCATION_HEAD; + + return 0; +} + +_public_ int sd_journal_seek_tail(sd_journal *j) { + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + reset_location(j); + j->current_location.type = LOCATION_TAIL; + + return 0; +} + +static void check_network(sd_journal *j, int fd) { + struct statfs sfs; + + assert(j); + + if (j->on_network) + return; + + if (fstatfs(fd, &sfs) < 0) + return; + + j->on_network = + F_TYPE_EQUAL(sfs.f_type, CIFS_MAGIC_NUMBER) || + F_TYPE_EQUAL(sfs.f_type, CODA_SUPER_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, NCP_SUPER_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, NFS_SUPER_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, SMB_SUPER_MAGIC); +} + +static bool file_has_type_prefix(const char *prefix, const char *filename) { + const char *full, *tilded, *atted; + + full = strjoina(prefix, ".journal"); + tilded = strjoina(full, "~"); + atted = strjoina(prefix, "@"); + + return streq(filename, full) || + streq(filename, tilded) || + startswith(filename, atted); +} + +static bool file_type_wanted(int flags, const char *filename) { + assert(filename); + + if (!endswith(filename, ".journal") && !endswith(filename, ".journal~")) + return false; + + /* no flags set → every type is OK */ + if (!(flags & (SD_JOURNAL_SYSTEM | SD_JOURNAL_CURRENT_USER))) + return true; + + if (flags & SD_JOURNAL_SYSTEM && file_has_type_prefix("system", filename)) + return true; + + if (flags & SD_JOURNAL_CURRENT_USER) { + char prefix[5 + DECIMAL_STR_MAX(uid_t) + 1]; + + xsprintf(prefix, "user-"UID_FMT, getuid()); + + if (file_has_type_prefix(prefix, filename)) + return true; + } + + return false; +} + +static bool path_has_prefix(sd_journal *j, const char *path, const char *prefix) { + assert(j); + assert(path); + assert(prefix); + + if (j->toplevel_fd >= 0) + return false; + + return path_startswith(path, prefix); +} + +static const char *skip_slash(const char *p) { + + if (!p) + return NULL; + + while (*p == '/') + p++; + + return p; +} + +static int add_any_file(sd_journal *j, int fd, const char *path) { + JournalFile *f = NULL; + bool close_fd = false; + int r, k; + + assert(j); + assert(fd >= 0 || path); + + if (path && ordered_hashmap_get(j->files, path)) + return 0; + + if (ordered_hashmap_size(j->files) >= JOURNAL_FILES_MAX) { + log_debug("Too many open journal files, not adding %s.", path); + r = -ETOOMANYREFS; + goto fail; + } + + if (fd < 0 && j->toplevel_fd >= 0) { + + /* If there's a top-level fd defined, open the file relative to this now. (Make the path relative, + * explicitly, since otherwise openat() ignores the first argument.) */ + + fd = openat(j->toplevel_fd, skip_slash(path), O_RDONLY|O_CLOEXEC); + if (fd < 0) { + r = log_debug_errno(errno, "Failed to open journal file %s: %m", path); + goto fail; + } + + close_fd = true; + } + + r = journal_file_open(fd, path, O_RDONLY, 0, false, false, NULL, j->mmap, NULL, NULL, &f); + if (r < 0) { + if (close_fd) + safe_close(fd); + log_debug_errno(r, "Failed to open journal file %s: %m", path); + goto fail; + } + + /* journal_file_dump(f); */ + + r = ordered_hashmap_put(j->files, f->path, f); + if (r < 0) { + f->close_fd = close_fd; + (void) journal_file_close(f); + goto fail; + } + + if (!j->has_runtime_files && path_has_prefix(j, f->path, "/run")) + j->has_runtime_files = true; + else if (!j->has_persistent_files && path_has_prefix(j, f->path, "/var")) + j->has_persistent_files = true; + + log_debug("File %s added.", f->path); + + check_network(j, f->fd); + + j->current_invalidate_counter++; + + return 0; + +fail: + k = journal_put_error(j, r, path); + if (k < 0) + return k; + + return r; +} + +static int add_file(sd_journal *j, const char *prefix, const char *filename) { + const char *path; + + assert(j); + assert(prefix); + assert(filename); + + if (j->no_new_files) + return 0; + + if (!file_type_wanted(j->flags, filename)) + return 0; + + path = strjoina(prefix, "/", filename); + return add_any_file(j, -1, path); +} + +static void remove_file(sd_journal *j, const char *prefix, const char *filename) { + const char *path; + JournalFile *f; + + assert(j); + assert(prefix); + assert(filename); + + path = strjoina(prefix, "/", filename); + f = ordered_hashmap_get(j->files, path); + if (!f) + return; + + remove_file_real(j, f); +} + +static void remove_file_real(sd_journal *j, JournalFile *f) { + assert(j); + assert(f); + + ordered_hashmap_remove(j->files, f->path); + + log_debug("File %s removed.", f->path); + + if (j->current_file == f) { + j->current_file = NULL; + j->current_field = 0; + } + + if (j->unique_file == f) { + /* Jump to the next unique_file or NULL if that one was last */ + j->unique_file = ordered_hashmap_next(j->files, j->unique_file->path); + j->unique_offset = 0; + if (!j->unique_file) + j->unique_file_lost = true; + } + + if (j->fields_file == f) { + j->fields_file = ordered_hashmap_next(j->files, j->fields_file->path); + j->fields_offset = 0; + if (!j->fields_file) + j->fields_file_lost = true; + } + + (void) journal_file_close(f); + + j->current_invalidate_counter++; +} + +static int dirname_is_machine_id(const char *fn) { + sd_id128_t id, machine; + int r; + + r = sd_id128_get_machine(&machine); + if (r < 0) + return r; + + r = sd_id128_from_string(fn, &id); + if (r < 0) + return r; + + return sd_id128_equal(id, machine); +} + +static int add_directory(sd_journal *j, const char *prefix, const char *dirname) { + _cleanup_free_ char *path = NULL; + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de = NULL; + Directory *m; + int r, k; + + assert(j); + assert(prefix); + + /* Adds a journal file directory to watch. If the directory is already tracked this updates the inotify watch + * and reenumerates directory contents */ + + if (dirname) + path = strjoin(prefix, "/", dirname, NULL); + else + path = strdup(prefix); + if (!path) { + r = -ENOMEM; + goto fail; + } + + log_debug("Considering directory %s.", path); + + /* We consider everything local that is in a directory for the local machine ID, or that is stored in /run */ + if ((j->flags & SD_JOURNAL_LOCAL_ONLY) && + !((dirname && dirname_is_machine_id(dirname) > 0) || path_has_prefix(j, path, "/run"))) + return 0; + + + if (j->toplevel_fd < 0) + d = opendir(path); + else + /* Open the specified directory relative to the the toplevel fd. Enforce that the path specified is + * relative, by dropping the initial slash */ + d = xopendirat(j->toplevel_fd, skip_slash(path), 0); + if (!d) { + r = log_debug_errno(errno, "Failed to open directory %s: %m", path); + goto fail; + } + + m = hashmap_get(j->directories_by_path, path); + if (!m) { + m = new0(Directory, 1); + if (!m) { + r = -ENOMEM; + goto fail; + } + + m->is_root = false; + m->path = path; + + if (hashmap_put(j->directories_by_path, m->path, m) < 0) { + free(m); + r = -ENOMEM; + goto fail; + } + + path = NULL; /* avoid freeing in cleanup */ + j->current_invalidate_counter++; + + log_debug("Directory %s added.", m->path); + + } else if (m->is_root) + return 0; + + if (m->wd <= 0 && j->inotify_fd >= 0) { + /* Watch this directory, if it not being watched yet. */ + + m->wd = inotify_add_watch_fd(j->inotify_fd, dirfd(d), + IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE| + IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT|IN_MOVED_FROM| + IN_ONLYDIR); + + if (m->wd > 0 && hashmap_put(j->directories_by_wd, INT_TO_PTR(m->wd), m) < 0) + inotify_rm_watch(j->inotify_fd, m->wd); + } + + FOREACH_DIRENT_ALL(de, d, r = log_debug_errno(errno, "Failed to read directory %s: %m", m->path); goto fail) { + + if (dirent_is_file_with_suffix(de, ".journal") || + dirent_is_file_with_suffix(de, ".journal~")) + (void) add_file(j, m->path, de->d_name); + } + + check_network(j, dirfd(d)); + + return 0; + +fail: + k = journal_put_error(j, r, path ?: prefix); + if (k < 0) + return k; + + return r; +} + +static int add_root_directory(sd_journal *j, const char *p, bool missing_ok) { + + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + Directory *m; + int r, k; + + assert(j); + + /* Adds a root directory to our set of directories to use. If the root directory is already in the set, we + * update the inotify logic, and renumerate the directory entries. This call may hence be called to initially + * populate the set, as well as to update it later. */ + + if (p) { + /* If there's a path specified, use it. */ + + if ((j->flags & SD_JOURNAL_RUNTIME_ONLY) && + !path_has_prefix(j, p, "/run")) + return -EINVAL; + + if (j->prefix) + p = strjoina(j->prefix, p); + + if (j->toplevel_fd < 0) + d = opendir(p); + else + d = xopendirat(j->toplevel_fd, skip_slash(p), 0); + + if (!d) { + if (errno == ENOENT && missing_ok) + return 0; + + r = log_debug_errno(errno, "Failed to open root directory %s: %m", p); + goto fail; + } + } else { + int dfd; + + /* If there's no path specified, then we use the top-level fd itself. We duplicate the fd here, since + * opendir() will take possession of the fd, and close it, which we don't want. */ + + p = "."; /* store this as "." in the directories hashmap */ + + dfd = fcntl(j->toplevel_fd, F_DUPFD_CLOEXEC, 3); + if (dfd < 0) { + r = -errno; + goto fail; + } + + d = fdopendir(dfd); + if (!d) { + r = -errno; + safe_close(dfd); + goto fail; + } + + rewinddir(d); + } + + m = hashmap_get(j->directories_by_path, p); + if (!m) { + m = new0(Directory, 1); + if (!m) { + r = -ENOMEM; + goto fail; + } + + m->is_root = true; + + m->path = strdup(p); + if (!m->path) { + free(m); + r = -ENOMEM; + goto fail; + } + + if (hashmap_put(j->directories_by_path, m->path, m) < 0) { + free(m->path); + free(m); + r = -ENOMEM; + goto fail; + } + + j->current_invalidate_counter++; + + log_debug("Root directory %s added.", m->path); + + } else if (!m->is_root) + return 0; + + if (m->wd <= 0 && j->inotify_fd >= 0) { + + m->wd = inotify_add_watch_fd(j->inotify_fd, dirfd(d), + IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE| + IN_ONLYDIR); + + if (m->wd > 0 && hashmap_put(j->directories_by_wd, INT_TO_PTR(m->wd), m) < 0) + inotify_rm_watch(j->inotify_fd, m->wd); + } + + if (j->no_new_files) + return 0; + + FOREACH_DIRENT_ALL(de, d, r = log_debug_errno(errno, "Failed to read directory %s: %m", m->path); goto fail) { + sd_id128_t id; + + if (dirent_is_file_with_suffix(de, ".journal") || + dirent_is_file_with_suffix(de, ".journal~")) + (void) add_file(j, m->path, de->d_name); + else if (IN_SET(de->d_type, DT_DIR, DT_LNK, DT_UNKNOWN) && + sd_id128_from_string(de->d_name, &id) >= 0) + (void) add_directory(j, m->path, de->d_name); + } + + check_network(j, dirfd(d)); + + return 0; + +fail: + k = journal_put_error(j, r, p); + if (k < 0) + return k; + + return r; +} + +static void remove_directory(sd_journal *j, Directory *d) { + assert(j); + + if (d->wd > 0) { + hashmap_remove(j->directories_by_wd, INT_TO_PTR(d->wd)); + + if (j->inotify_fd >= 0) + inotify_rm_watch(j->inotify_fd, d->wd); + } + + hashmap_remove(j->directories_by_path, d->path); + + if (d->is_root) + log_debug("Root directory %s removed.", d->path); + else + log_debug("Directory %s removed.", d->path); + + free(d->path); + free(d); +} + +static int add_search_paths(sd_journal *j) { + + static const char search_paths[] = + "/run/log/journal\0" + "/var/log/journal\0"; + const char *p; + + assert(j); + + /* We ignore most errors here, since the idea is to only open + * what's actually accessible, and ignore the rest. */ + + NULSTR_FOREACH(p, search_paths) + (void) add_root_directory(j, p, true); + + return 0; +} + +static int add_current_paths(sd_journal *j) { + Iterator i; + JournalFile *f; + + assert(j); + assert(j->no_new_files); + + /* Simply adds all directories for files we have open as directories. We don't expect errors here, so we + * treat them as fatal. */ + + ORDERED_HASHMAP_FOREACH(f, j->files, i) { + _cleanup_free_ char *dir; + int r; + + dir = dirname_malloc(f->path); + if (!dir) + return -ENOMEM; + + r = add_directory(j, dir, NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int allocate_inotify(sd_journal *j) { + assert(j); + + if (j->inotify_fd < 0) { + j->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); + if (j->inotify_fd < 0) + return -errno; + } + + return hashmap_ensure_allocated(&j->directories_by_wd, NULL); +} + +static sd_journal *journal_new(int flags, const char *path) { + sd_journal *j; + + j = new0(sd_journal, 1); + if (!j) + return NULL; + + j->original_pid = getpid(); + j->toplevel_fd = -1; + j->inotify_fd = -1; + j->flags = flags; + j->data_threshold = DEFAULT_DATA_THRESHOLD; + + if (path) { + j->path = strdup(path); + if (!j->path) + goto fail; + } + + j->files = ordered_hashmap_new(&string_hash_ops); + j->directories_by_path = hashmap_new(&string_hash_ops); + j->mmap = mmap_cache_new(); + if (!j->files || !j->directories_by_path || !j->mmap) + goto fail; + + return j; + +fail: + sd_journal_close(j); + return NULL; +} + +_public_ int sd_journal_open(sd_journal **ret, int flags) { + sd_journal *j; + int r; + + assert_return(ret, -EINVAL); + assert_return((flags & ~(SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_RUNTIME_ONLY|SD_JOURNAL_SYSTEM|SD_JOURNAL_CURRENT_USER)) == 0, -EINVAL); + + j = journal_new(flags, NULL); + if (!j) + return -ENOMEM; + + r = add_search_paths(j); + if (r < 0) + goto fail; + + *ret = j; + return 0; + +fail: + sd_journal_close(j); + + return r; +} + +_public_ int sd_journal_open_container(sd_journal **ret, const char *machine, int flags) { + _cleanup_free_ char *root = NULL, *class = NULL; + sd_journal *j; + char *p; + int r; + + /* This is pretty much deprecated, people should use machined's OpenMachineRootDirectory() call instead in + * combination with sd_journal_open_directory_fd(). */ + + assert_return(machine, -EINVAL); + assert_return(ret, -EINVAL); + assert_return((flags & ~(SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM)) == 0, -EINVAL); + assert_return(machine_name_is_valid(machine), -EINVAL); + + p = strjoina("/run/systemd/machines/", machine); + r = parse_env_file(p, NEWLINE, "ROOT", &root, "CLASS", &class, NULL); + if (r == -ENOENT) + return -EHOSTDOWN; + if (r < 0) + return r; + if (!root) + return -ENODATA; + + if (!streq_ptr(class, "container")) + return -EIO; + + j = journal_new(flags, NULL); + if (!j) + return -ENOMEM; + + j->prefix = root; + root = NULL; + + r = add_search_paths(j); + if (r < 0) + goto fail; + + *ret = j; + return 0; + +fail: + sd_journal_close(j); + return r; +} + +_public_ int sd_journal_open_directory(sd_journal **ret, const char *path, int flags) { + sd_journal *j; + int r; + + assert_return(ret, -EINVAL); + assert_return(path, -EINVAL); + assert_return((flags & ~SD_JOURNAL_OS_ROOT) == 0, -EINVAL); + + j = journal_new(flags, path); + if (!j) + return -ENOMEM; + + if (flags & SD_JOURNAL_OS_ROOT) + r = add_search_paths(j); + else + r = add_root_directory(j, path, false); + if (r < 0) + goto fail; + + *ret = j; + return 0; + +fail: + sd_journal_close(j); + return r; +} + +_public_ int sd_journal_open_files(sd_journal **ret, const char **paths, int flags) { + sd_journal *j; + const char **path; + int r; + + assert_return(ret, -EINVAL); + assert_return(flags == 0, -EINVAL); + + j = journal_new(flags, NULL); + if (!j) + return -ENOMEM; + + STRV_FOREACH(path, paths) { + r = add_any_file(j, -1, *path); + if (r < 0) + goto fail; + } + + j->no_new_files = true; + + *ret = j; + return 0; + +fail: + sd_journal_close(j); + return r; +} + +_public_ int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags) { + sd_journal *j; + struct stat st; + int r; + + assert_return(ret, -EINVAL); + assert_return(fd >= 0, -EBADF); + assert_return((flags & ~SD_JOURNAL_OS_ROOT) == 0, -EINVAL); + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode)) + return -EBADFD; + + j = journal_new(flags, NULL); + if (!j) + return -ENOMEM; + + j->toplevel_fd = fd; + + if (flags & SD_JOURNAL_OS_ROOT) + r = add_search_paths(j); + else + r = add_root_directory(j, NULL, false); + if (r < 0) + goto fail; + + *ret = j; + return 0; + +fail: + sd_journal_close(j); + return r; +} + +_public_ int sd_journal_open_files_fd(sd_journal **ret, int fds[], unsigned n_fds, int flags) { + Iterator iterator; + JournalFile *f; + sd_journal *j; + unsigned i; + int r; + + assert_return(ret, -EINVAL); + assert_return(n_fds > 0, -EBADF); + assert_return(flags == 0, -EINVAL); + + j = journal_new(flags, NULL); + if (!j) + return -ENOMEM; + + for (i = 0; i < n_fds; i++) { + struct stat st; + + if (fds[i] < 0) { + r = -EBADF; + goto fail; + } + + if (fstat(fds[i], &st) < 0) { + r = -errno; + goto fail; + } + + if (!S_ISREG(st.st_mode)) { + r = -EBADFD; + goto fail; + } + + r = add_any_file(j, fds[i], NULL); + if (r < 0) + goto fail; + } + + j->no_new_files = true; + j->no_inotify = true; + + *ret = j; + return 0; + +fail: + /* If we fail, make sure we don't take possession of the files we managed to make use of successfully, and they + * remain open */ + ORDERED_HASHMAP_FOREACH(f, j->files, iterator) + f->close_fd = false; + + sd_journal_close(j); + return r; +} + +_public_ void sd_journal_close(sd_journal *j) { + Directory *d; + JournalFile *f; + char *p; + + if (!j) + return; + + sd_journal_flush_matches(j); + + while ((f = ordered_hashmap_steal_first(j->files))) + (void) journal_file_close(f); + + ordered_hashmap_free(j->files); + + while ((d = hashmap_first(j->directories_by_path))) + remove_directory(j, d); + + while ((d = hashmap_first(j->directories_by_wd))) + remove_directory(j, d); + + hashmap_free(j->directories_by_path); + hashmap_free(j->directories_by_wd); + + safe_close(j->inotify_fd); + + if (j->mmap) { + log_debug("mmap cache statistics: %u hit, %u miss", mmap_cache_get_hit(j->mmap), mmap_cache_get_missed(j->mmap)); + mmap_cache_unref(j->mmap); + } + + while ((p = hashmap_steal_first(j->errors))) + free(p); + hashmap_free(j->errors); + + free(j->path); + free(j->prefix); + free(j->unique_field); + free(j->fields_buffer); + free(j); +} + +_public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) { + Object *o; + JournalFile *f; + int r; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(ret, -EINVAL); + + f = j->current_file; + if (!f) + return -EADDRNOTAVAIL; + + if (f->current_offset <= 0) + return -EADDRNOTAVAIL; + + r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); + if (r < 0) + return r; + + *ret = le64toh(o->entry.realtime); + return 0; +} + +_public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id) { + Object *o; + JournalFile *f; + int r; + sd_id128_t id; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + f = j->current_file; + if (!f) + return -EADDRNOTAVAIL; + + if (f->current_offset <= 0) + return -EADDRNOTAVAIL; + + r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); + if (r < 0) + return r; + + if (ret_boot_id) + *ret_boot_id = o->entry.boot_id; + else { + r = sd_id128_get_boot(&id); + if (r < 0) + return r; + + if (!sd_id128_equal(id, o->entry.boot_id)) + return -ESTALE; + } + + if (ret) + *ret = le64toh(o->entry.monotonic); + + return 0; +} + +static bool field_is_valid(const char *field) { + const char *p; + + assert(field); + + if (isempty(field)) + return false; + + if (startswith(field, "__")) + return false; + + for (p = field; *p; p++) { + + if (*p == '_') + continue; + + if (*p >= 'A' && *p <= 'Z') + continue; + + if (*p >= '0' && *p <= '9') + continue; + + return false; + } + + return true; +} + +_public_ int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *size) { + JournalFile *f; + uint64_t i, n; + size_t field_length; + int r; + Object *o; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(field, -EINVAL); + assert_return(data, -EINVAL); + assert_return(size, -EINVAL); + assert_return(field_is_valid(field), -EINVAL); + + f = j->current_file; + if (!f) + return -EADDRNOTAVAIL; + + if (f->current_offset <= 0) + return -EADDRNOTAVAIL; + + r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); + if (r < 0) + return r; + + field_length = strlen(field); + + n = journal_file_entry_n_items(o); + for (i = 0; i < n; i++) { + uint64_t p, l; + le64_t le_hash; + size_t t; + int compression; + + p = le64toh(o->entry.items[i].object_offset); + le_hash = o->entry.items[i].hash; + r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); + if (r < 0) + return r; + + if (le_hash != o->data.hash) + return -EBADMSG; + + l = le64toh(o->object.size) - offsetof(Object, data.payload); + + compression = o->object.flags & OBJECT_COMPRESSION_MASK; + if (compression) { +#if defined(HAVE_XZ) || defined(HAVE_LZ4) + r = decompress_startswith(compression, + o->data.payload, l, + &f->compress_buffer, &f->compress_buffer_size, + field, field_length, '='); + if (r < 0) + log_debug_errno(r, "Cannot decompress %s object of length %"PRIu64" at offset "OFSfmt": %m", + object_compressed_to_string(compression), l, p); + else if (r > 0) { + + size_t rsize; + + r = decompress_blob(compression, + o->data.payload, l, + &f->compress_buffer, &f->compress_buffer_size, &rsize, + j->data_threshold); + if (r < 0) + return r; + + *data = f->compress_buffer; + *size = (size_t) rsize; + + return 0; + } +#else + return -EPROTONOSUPPORT; +#endif + } else if (l >= field_length+1 && + memcmp(o->data.payload, field, field_length) == 0 && + o->data.payload[field_length] == '=') { + + t = (size_t) l; + + if ((uint64_t) t != l) + return -E2BIG; + + *data = o->data.payload; + *size = t; + + return 0; + } + + r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); + if (r < 0) + return r; + } + + return -ENOENT; +} + +static int return_data(sd_journal *j, JournalFile *f, Object *o, const void **data, size_t *size) { + size_t t; + uint64_t l; + int compression; + + l = le64toh(o->object.size) - offsetof(Object, data.payload); + t = (size_t) l; + + /* We can't read objects larger than 4G on a 32bit machine */ + if ((uint64_t) t != l) + return -E2BIG; + + compression = o->object.flags & OBJECT_COMPRESSION_MASK; + if (compression) { +#if defined(HAVE_XZ) || defined(HAVE_LZ4) + size_t rsize; + int r; + + r = decompress_blob(compression, + o->data.payload, l, &f->compress_buffer, + &f->compress_buffer_size, &rsize, j->data_threshold); + if (r < 0) + return r; + + *data = f->compress_buffer; + *size = (size_t) rsize; +#else + return -EPROTONOSUPPORT; +#endif + } else { + *data = o->data.payload; + *size = t; + } + + return 0; +} + +_public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *size) { + JournalFile *f; + uint64_t p, n; + le64_t le_hash; + int r; + Object *o; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(data, -EINVAL); + assert_return(size, -EINVAL); + + f = j->current_file; + if (!f) + return -EADDRNOTAVAIL; + + if (f->current_offset <= 0) + return -EADDRNOTAVAIL; + + r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); + if (r < 0) + return r; + + n = journal_file_entry_n_items(o); + if (j->current_field >= n) + return 0; + + p = le64toh(o->entry.items[j->current_field].object_offset); + le_hash = o->entry.items[j->current_field].hash; + r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); + if (r < 0) + return r; + + if (le_hash != o->data.hash) + return -EBADMSG; + + r = return_data(j, f, o, data, size); + if (r < 0) + return r; + + j->current_field++; + + return 1; +} + +_public_ void sd_journal_restart_data(sd_journal *j) { + if (!j) + return; + + j->current_field = 0; +} + +_public_ int sd_journal_get_fd(sd_journal *j) { + int r; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + if (j->no_inotify) + return -EMEDIUMTYPE; + + if (j->inotify_fd >= 0) + return j->inotify_fd; + + r = allocate_inotify(j); + if (r < 0) + return r; + + log_debug("Reiterating files to get inotify watches established"); + + /* Iterate through all dirs again, to add them to the + * inotify */ + if (j->no_new_files) + r = add_current_paths(j); + else if (j->toplevel_fd >= 0) + r = add_root_directory(j, NULL, false); + else if (j->path) + r = add_root_directory(j, j->path, true); + else + r = add_search_paths(j); + if (r < 0) + return r; + + return j->inotify_fd; +} + +_public_ int sd_journal_get_events(sd_journal *j) { + int fd; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + fd = sd_journal_get_fd(j); + if (fd < 0) + return fd; + + return POLLIN; +} + +_public_ int sd_journal_get_timeout(sd_journal *j, uint64_t *timeout_usec) { + int fd; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(timeout_usec, -EINVAL); + + fd = sd_journal_get_fd(j); + if (fd < 0) + return fd; + + if (!j->on_network) { + *timeout_usec = (uint64_t) -1; + return 0; + } + + /* If we are on the network we need to regularly check for + * changes manually */ + + *timeout_usec = j->last_process_usec + JOURNAL_FILES_RECHECK_USEC; + return 1; +} + +static void process_inotify_event(sd_journal *j, struct inotify_event *e) { + Directory *d; + + assert(j); + assert(e); + + /* Is this a subdirectory we watch? */ + d = hashmap_get(j->directories_by_wd, INT_TO_PTR(e->wd)); + if (d) { + sd_id128_t id; + + if (!(e->mask & IN_ISDIR) && e->len > 0 && + (endswith(e->name, ".journal") || + endswith(e->name, ".journal~"))) { + + /* Event for a journal file */ + + if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) + (void) add_file(j, d->path, e->name); + else if (e->mask & (IN_DELETE|IN_MOVED_FROM|IN_UNMOUNT)) + remove_file(j, d->path, e->name); + + } else if (!d->is_root && e->len == 0) { + + /* Event for a subdirectory */ + + if (e->mask & (IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT)) + remove_directory(j, d); + + } else if (d->is_root && (e->mask & IN_ISDIR) && e->len > 0 && sd_id128_from_string(e->name, &id) >= 0) { + + /* Event for root directory */ + + if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) + (void) add_directory(j, d->path, e->name); + } + + return; + } + + if (e->mask & IN_IGNORED) + return; + + log_debug("Unknown inotify event."); +} + +static int determine_change(sd_journal *j) { + bool b; + + assert(j); + + b = j->current_invalidate_counter != j->last_invalidate_counter; + j->last_invalidate_counter = j->current_invalidate_counter; + + return b ? SD_JOURNAL_INVALIDATE : SD_JOURNAL_APPEND; +} + +_public_ int sd_journal_process(sd_journal *j) { + bool got_something = false; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + j->last_process_usec = now(CLOCK_MONOTONIC); + + for (;;) { + union inotify_event_buffer buffer; + struct inotify_event *e; + ssize_t l; + + l = read(j->inotify_fd, &buffer, sizeof(buffer)); + if (l < 0) { + if (errno == EAGAIN || errno == EINTR) + return got_something ? determine_change(j) : SD_JOURNAL_NOP; + + return -errno; + } + + got_something = true; + + FOREACH_INOTIFY_EVENT(e, buffer, l) + process_inotify_event(j, e); + } +} + +_public_ int sd_journal_wait(sd_journal *j, uint64_t timeout_usec) { + int r; + uint64_t t; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + if (j->inotify_fd < 0) { + + /* This is the first invocation, hence create the + * inotify watch */ + r = sd_journal_get_fd(j); + if (r < 0) + return r; + + /* The journal might have changed since the context + * object was created and we weren't watching before, + * hence don't wait for anything, and return + * immediately. */ + return determine_change(j); + } + + r = sd_journal_get_timeout(j, &t); + if (r < 0) + return r; + + if (t != (uint64_t) -1) { + usec_t n; + + n = now(CLOCK_MONOTONIC); + t = t > n ? t - n : 0; + + if (timeout_usec == (uint64_t) -1 || timeout_usec > t) + timeout_usec = t; + } + + do { + r = fd_wait_for_event(j->inotify_fd, POLLIN, timeout_usec); + } while (r == -EINTR); + + if (r < 0) + return r; + + return sd_journal_process(j); +} + +_public_ int sd_journal_get_cutoff_realtime_usec(sd_journal *j, uint64_t *from, uint64_t *to) { + Iterator i; + JournalFile *f; + bool first = true; + uint64_t fmin = 0, tmax = 0; + int r; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(from || to, -EINVAL); + assert_return(from != to, -EINVAL); + + ORDERED_HASHMAP_FOREACH(f, j->files, i) { + usec_t fr, t; + + r = journal_file_get_cutoff_realtime_usec(f, &fr, &t); + if (r == -ENOENT) + continue; + if (r < 0) + return r; + if (r == 0) + continue; + + if (first) { + fmin = fr; + tmax = t; + first = false; + } else { + fmin = MIN(fr, fmin); + tmax = MAX(t, tmax); + } + } + + if (from) + *from = fmin; + if (to) + *to = tmax; + + return first ? 0 : 1; +} + +_public_ int sd_journal_get_cutoff_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t *from, uint64_t *to) { + Iterator i; + JournalFile *f; + bool found = false; + int r; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(from || to, -EINVAL); + assert_return(from != to, -EINVAL); + + ORDERED_HASHMAP_FOREACH(f, j->files, i) { + usec_t fr, t; + + r = journal_file_get_cutoff_monotonic_usec(f, boot_id, &fr, &t); + if (r == -ENOENT) + continue; + if (r < 0) + return r; + if (r == 0) + continue; + + if (found) { + if (from) + *from = MIN(fr, *from); + if (to) + *to = MAX(t, *to); + } else { + if (from) + *from = fr; + if (to) + *to = t; + found = true; + } + } + + return found; +} + +void journal_print_header(sd_journal *j) { + Iterator i; + JournalFile *f; + bool newline = false; + + assert(j); + + ORDERED_HASHMAP_FOREACH(f, j->files, i) { + if (newline) + putchar('\n'); + else + newline = true; + + journal_file_print_header(f); + } +} + +_public_ int sd_journal_get_usage(sd_journal *j, uint64_t *bytes) { + Iterator i; + JournalFile *f; + uint64_t sum = 0; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(bytes, -EINVAL); + + ORDERED_HASHMAP_FOREACH(f, j->files, i) { + struct stat st; + + if (fstat(f->fd, &st) < 0) + return -errno; + + sum += (uint64_t) st.st_blocks * 512ULL; + } + + *bytes = sum; + return 0; +} + +_public_ int sd_journal_query_unique(sd_journal *j, const char *field) { + char *f; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(!isempty(field), -EINVAL); + assert_return(field_is_valid(field), -EINVAL); + + f = strdup(field); + if (!f) + return -ENOMEM; + + free(j->unique_field); + j->unique_field = f; + j->unique_file = NULL; + j->unique_offset = 0; + j->unique_file_lost = false; + + return 0; +} + +_public_ int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l) { + size_t k; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(data, -EINVAL); + assert_return(l, -EINVAL); + assert_return(j->unique_field, -EINVAL); + + k = strlen(j->unique_field); + + if (!j->unique_file) { + if (j->unique_file_lost) + return 0; + + j->unique_file = ordered_hashmap_first(j->files); + if (!j->unique_file) + return 0; + + j->unique_offset = 0; + } + + for (;;) { + JournalFile *of; + Iterator i; + Object *o; + const void *odata; + size_t ol; + bool found; + int r; + + /* Proceed to next data object in the field's linked list */ + if (j->unique_offset == 0) { + r = journal_file_find_field_object(j->unique_file, j->unique_field, k, &o, NULL); + if (r < 0) + return r; + + j->unique_offset = r > 0 ? le64toh(o->field.head_data_offset) : 0; + } else { + r = journal_file_move_to_object(j->unique_file, OBJECT_DATA, j->unique_offset, &o); + if (r < 0) + return r; + + j->unique_offset = le64toh(o->data.next_field_offset); + } + + /* We reached the end of the list? Then start again, with the next file */ + if (j->unique_offset == 0) { + j->unique_file = ordered_hashmap_next(j->files, j->unique_file->path); + if (!j->unique_file) + return 0; + + continue; + } + + /* We do not use OBJECT_DATA context here, but OBJECT_UNUSED + * instead, so that we can look at this data object at the same + * time as one on another file */ + r = journal_file_move_to_object(j->unique_file, OBJECT_UNUSED, j->unique_offset, &o); + if (r < 0) + return r; + + /* Let's do the type check by hand, since we used 0 context above. */ + if (o->object.type != OBJECT_DATA) { + log_debug("%s:offset " OFSfmt ": object has type %d, expected %d", + j->unique_file->path, j->unique_offset, + o->object.type, OBJECT_DATA); + return -EBADMSG; + } + + r = return_data(j, j->unique_file, o, &odata, &ol); + if (r < 0) + return r; + + /* Check if we have at least the field name and "=". */ + if (ol <= k) { + log_debug("%s:offset " OFSfmt ": object has size %zu, expected at least %zu", + j->unique_file->path, j->unique_offset, + ol, k + 1); + return -EBADMSG; + } + + if (memcmp(odata, j->unique_field, k) || ((const char*) odata)[k] != '=') { + log_debug("%s:offset " OFSfmt ": object does not start with \"%s=\"", + j->unique_file->path, j->unique_offset, + j->unique_field); + return -EBADMSG; + } + + /* OK, now let's see if we already returned this data + * object by checking if it exists in the earlier + * traversed files. */ + found = false; + ORDERED_HASHMAP_FOREACH(of, j->files, i) { + if (of == j->unique_file) + break; + + /* Skip this file it didn't have any fields indexed */ + if (JOURNAL_HEADER_CONTAINS(of->header, n_fields) && le64toh(of->header->n_fields) <= 0) + continue; + + r = journal_file_find_data_object_with_hash(of, odata, ol, le64toh(o->data.hash), NULL, NULL); + if (r < 0) + return r; + if (r > 0) { + found = true; + break; + } + } + + if (found) + continue; + + r = return_data(j, j->unique_file, o, data, l); + if (r < 0) + return r; + + return 1; + } +} + +_public_ void sd_journal_restart_unique(sd_journal *j) { + if (!j) + return; + + j->unique_file = NULL; + j->unique_offset = 0; + j->unique_file_lost = false; +} + +_public_ int sd_journal_enumerate_fields(sd_journal *j, const char **field) { + int r; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(field, -EINVAL); + + if (!j->fields_file) { + if (j->fields_file_lost) + return 0; + + j->fields_file = ordered_hashmap_first(j->files); + if (!j->fields_file) + return 0; + + j->fields_hash_table_index = 0; + j->fields_offset = 0; + } + + for (;;) { + JournalFile *f, *of; + Iterator i; + uint64_t m; + Object *o; + size_t sz; + bool found; + + f = j->fields_file; + + if (j->fields_offset == 0) { + bool eof = false; + + /* We are not yet positioned at any field. Let's pick the first one */ + r = journal_file_map_field_hash_table(f); + if (r < 0) + return r; + + m = le64toh(f->header->field_hash_table_size) / sizeof(HashItem); + for (;;) { + if (j->fields_hash_table_index >= m) { + /* Reached the end of the hash table, go to the next file. */ + eof = true; + break; + } + + j->fields_offset = le64toh(f->field_hash_table[j->fields_hash_table_index].head_hash_offset); + + if (j->fields_offset != 0) + break; + + /* Empty hash table bucket, go to next one */ + j->fields_hash_table_index++; + } + + if (eof) { + /* Proceed with next file */ + j->fields_file = ordered_hashmap_next(j->files, f->path); + if (!j->fields_file) { + *field = NULL; + return 0; + } + + j->fields_offset = 0; + j->fields_hash_table_index = 0; + continue; + } + + } else { + /* We are already positioned at a field. If so, let's figure out the next field from it */ + + r = journal_file_move_to_object(f, OBJECT_FIELD, j->fields_offset, &o); + if (r < 0) + return r; + + j->fields_offset = le64toh(o->field.next_hash_offset); + if (j->fields_offset == 0) { + /* Reached the end of the hash table chain */ + j->fields_hash_table_index++; + continue; + } + } + + /* We use OBJECT_UNUSED here, so that the iterator below doesn't remove our mmap window */ + r = journal_file_move_to_object(f, OBJECT_UNUSED, j->fields_offset, &o); + if (r < 0) + return r; + + /* Because we used OBJECT_UNUSED above, we need to do our type check manually */ + if (o->object.type != OBJECT_FIELD) { + log_debug("%s:offset " OFSfmt ": object has type %i, expected %i", f->path, j->fields_offset, o->object.type, OBJECT_FIELD); + return -EBADMSG; + } + + sz = le64toh(o->object.size) - offsetof(Object, field.payload); + + /* Let's see if we already returned this field name before. */ + found = false; + ORDERED_HASHMAP_FOREACH(of, j->files, i) { + if (of == f) + break; + + /* Skip this file it didn't have any fields indexed */ + if (JOURNAL_HEADER_CONTAINS(of->header, n_fields) && le64toh(of->header->n_fields) <= 0) + continue; + + r = journal_file_find_field_object_with_hash(of, o->field.payload, sz, le64toh(o->field.hash), NULL, NULL); + if (r < 0) + return r; + if (r > 0) { + found = true; + break; + } + } + + if (found) + continue; + + /* Check if this is really a valid string containing no NUL byte */ + if (memchr(o->field.payload, 0, sz)) + return -EBADMSG; + + if (sz > j->data_threshold) + sz = j->data_threshold; + + if (!GREEDY_REALLOC(j->fields_buffer, j->fields_buffer_allocated, sz + 1)) + return -ENOMEM; + + memcpy(j->fields_buffer, o->field.payload, sz); + j->fields_buffer[sz] = 0; + + if (!field_is_valid(j->fields_buffer)) + return -EBADMSG; + + *field = j->fields_buffer; + return 1; + } +} + +_public_ void sd_journal_restart_fields(sd_journal *j) { + if (!j) + return; + + j->fields_file = NULL; + j->fields_hash_table_index = 0; + j->fields_offset = 0; + j->fields_file_lost = false; +} + +_public_ int sd_journal_reliable_fd(sd_journal *j) { + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + return !j->on_network; +} + +static char *lookup_field(const char *field, void *userdata) { + sd_journal *j = userdata; + const void *data; + size_t size, d; + int r; + + assert(field); + assert(j); + + r = sd_journal_get_data(j, field, &data, &size); + if (r < 0 || + size > REPLACE_VAR_MAX) + return strdup(field); + + d = strlen(field) + 1; + + return strndup((const char*) data + d, size - d); +} + +_public_ int sd_journal_get_catalog(sd_journal *j, char **ret) { + const void *data; + size_t size; + sd_id128_t id; + _cleanup_free_ char *text = NULL, *cid = NULL; + char *t; + int r; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(ret, -EINVAL); + + r = sd_journal_get_data(j, "MESSAGE_ID", &data, &size); + if (r < 0) + return r; + + cid = strndup((const char*) data + 11, size - 11); + if (!cid) + return -ENOMEM; + + r = sd_id128_from_string(cid, &id); + if (r < 0) + return r; + + r = catalog_get(CATALOG_DATABASE, id, &text); + if (r < 0) + return r; + + t = replace_var(text, lookup_field, j); + if (!t) + return -ENOMEM; + + *ret = t; + return 0; +} + +_public_ int sd_journal_get_catalog_for_message_id(sd_id128_t id, char **ret) { + assert_return(ret, -EINVAL); + + return catalog_get(CATALOG_DATABASE, id, ret); +} + +_public_ int sd_journal_set_data_threshold(sd_journal *j, size_t sz) { + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + j->data_threshold = sz; + return 0; +} + +_public_ int sd_journal_get_data_threshold(sd_journal *j, size_t *sz) { + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(sz, -EINVAL); + + *sz = j->data_threshold; + return 0; +} + +_public_ int sd_journal_has_runtime_files(sd_journal *j) { + assert_return(j, -EINVAL); + + return j->has_runtime_files; +} + +_public_ int sd_journal_has_persistent_files(sd_journal *j) { + assert_return(j, -EINVAL); + + return j->has_persistent_files; +} diff --git a/src/libsystemd/sd-bus/DIFFERENCES b/src/libsystemd/sd-bus/DIFFERENCES deleted file mode 100644 index db269675a7..0000000000 --- a/src/libsystemd/sd-bus/DIFFERENCES +++ /dev/null @@ -1,25 +0,0 @@ -Known differences between dbus1 and kdbus: - -- NameAcquired/NameLost is gone entirely on kdbus backends if - libsystemd is used. It is still added in by systemd-bus-proxyd - for old dbus1 clients, and it is available if libsystemd is used - against the classic dbus1 daemon. If you want to write compatible - code with libsystem-bus you need to explicitly subscribe to - NameOwnerChanged signals and just ignore NameAcquired/NameLost - -- Applications have to deal with spurious signals they didn't expect, - due to the probabilistic bloom filters. They need to handle this - anyway, given that any client can send anything to arbitrary clients - anyway, even in dbus1, so not much changes. - -- clients of the system bus when kdbus is used must roll their own - security. Only legacy dbus1 clients get the old XML policy enforced, - which is implemented by systemd-bus-proxyd. - -- Serial numbers of synthesized messages are always (uint32_t) -1. - -- NameOwnerChanged is a synthetic message, generated locally and not - by the driver. On dbus1 only the Disconnected message was - synthesized like this. - -- There's no standard per-session bus anymore. Only a per-user bus. diff --git a/src/libsystemd/sd-bus/GVARIANT-SERIALIZATION b/src/libsystemd/sd-bus/GVARIANT-SERIALIZATION deleted file mode 100644 index 6aeb11364a..0000000000 --- a/src/libsystemd/sd-bus/GVARIANT-SERIALIZATION +++ /dev/null @@ -1,110 +0,0 @@ -How we use GVariant for serializing D-Bus messages --------------------------------------------------- - -We stay close to the original dbus1 framing as possible, but make -certain changes to adapt for GVariant. dbus1 has the following -framing: - - 1. A fixed header of "yyyyuu" - 2. Additional header fields of "a(yv)" - 3. Padding with NUL bytes to pad up to next 8byte boundary - 4. The body - -Note that the body is not padded at the end, the complete message -hence might have a non-aligned size. Reading multiple messages at once -will hence result in possibly unaligned messages in memory. - -The header consists of the following: - - y Endianness, 'l' or 'B' - y Message Type - y Flags - y Protocol version, '1' - u Length of the body, i.e. the length of part 4 above - u 32bit Serial number - - = 12 bytes - -This header is then followed by the fields array, whose first value is -a 32bit array size. - -When using GVariant we keep the basic structure in place, only -slightly alter the header, and define protocol version '2'. The new -header: - - y Endianness, 'l' or 'B' - y Message Type - y Flags - y Protocol version, '2' - u Reserved, must be 0 - t 64bit Cookie - - = 16 bytes - -This is then followed by the GVariant fields array ("a{tv}"), and -finally the actual body as variant (v). Putting this altogether a -packet on dbus2 hence qualifies as a fully compliant GVariant -structure of (yyyyuta{tv}v). - -For details on gvariant, see: - -https://people.gnome.org/~desrt/gvariant-serialisation.pdf - -Regarding the framing of dbus2, also see: - -https://wiki.gnome.org/Projects/GLib/GDBus/Version2 - -The first four bytes of the header are defined the same way for dbus1 -and dbus2. The first bytes contain the endianess field and the -protocol version, so that the remainder of the message can be safely -made sense of just by looking at the first 32bit. - -Note that the length of the body is no longer included in the header -on dbus2! In fact, the message size must be known in advance, from the -underlying transport in order to parse dbus2 messages, while it is -directly included in dbus1 message headers. This change of semantics -is an effect of GVariant's basic design. - -The serial number has been renamed cookie and has been extended from -32bit to 64bit. It is recommended to avoid the higher 32bit of the -cookie field though, to simplify compatibility with dbus1 peers. Note -that not only the cookie/serial field in the fixed header, but also -the reply_cookie/reply_serial additional header field has been -increased from 32bit to 64bit, too! - -The header field identifiers have been extended from 8bit to -64bit. This has been done to simplify things (as kdbus otherwise uses -exclusively 64bit types, unless there is a strong reason not to), and -has no effect on the serialization size, as due to alignment for each -8bit header field identifier 56 bits of padding had to be added. - -Note that the header size changed, due to these changes. However, -consider that on dbus1 the beginning of the fields array contains the -32bit array size (since that is how arrays are encoded on dbus1), -thus, if one considers that size part of the header, instead of the -array, the size of the header on dbus1 and dbus2 stays identical, at -16 bytes. - - 0 4 8 12 16 - Common: | E | T | F | V | ... - - dbus1: | (as above) | Body Length | Serial | Fields Length | Fields array ... - - gvariant: | (as above) | Reserved | Cookie | Fields array ... - -And that's already it. - -Note: to simplify parsing, valid kdbus/dbus2 messages must include the -entire fixed header and additional header fields in a single non-memfd -message part. Also, the signature string of the body variant all the -way to the end of the message must be in a single non-memfd part -too. The parts for this extended header and footer can be the same -one, and can also continue any amount of additional body bytes. - -Note: on kdbus only native endian messages marshalled in gvariant may - be sent. If a client receives a message in non-native endianness - or in dbus1 marshalling it shall ignore the message. - -Note: The GVariant "MAYBE" type is not supported, so that messages can - be fully converted forth and back between dbus1 and gvariant - representations. diff --git a/src/libsystemd/sd-bus/Makefile b/src/libsystemd/sd-bus/Makefile deleted file mode 120000 index 94aaae2c4d..0000000000 --- a/src/libsystemd/sd-bus/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-bus/PORTING-DBUS1 b/src/libsystemd/sd-bus/PORTING-DBUS1 deleted file mode 100644 index 2dedb28bcf..0000000000 --- a/src/libsystemd/sd-bus/PORTING-DBUS1 +++ /dev/null @@ -1,535 +0,0 @@ -A few hints on supporting kdbus as backend in your favorite D-Bus library. - -~~~ - -Before you read this, have a look at the DIFFERENCES and -GVARIANT_SERIALIZATION texts you find in the same directory where you -found this. - -We invite you to port your favorite D-Bus protocol implementation -over to kdbus. However, there are a couple of complexities -involved. On kdbus we only speak GVariant marshaling, kdbus clients -ignore traffic in dbus1 marshaling. Thus, you need to add a second, -GVariant compatible marshaler to your library first. - -After you have done that: here's the basic principle how kdbus works: - -You connect to a bus by opening its bus node in /sys/fs/kdbus/. All -buses have a device node there, it starts with a numeric UID of the -owner of the bus, followed by a dash and a string identifying the -bus. The system bus is thus called /sys/fs/kdbus/0-system, and for user -buses the device node is /sys/fs/kdbus/1000-user (if 1000 is your user -id). - -(Before we proceed, please always keep a copy of libsystemd next -to you, ultimately that's where the details are, this document simply -is a rough overview to help you grok things.) - -CONNECTING - -To connect to a bus, simply open() its device node and issue the -KDBUS_CMD_HELLO call. That's it. Now you are connected. Do not send -Hello messages or so (as you would on dbus1), that does not exist for -kdbus. - -The structure you pass to the ioctl will contain a couple of -parameters that you need to know, to operate on the bus. - -There are two flags fields, one indicating features of the kdbus -kernel side ("conn_flags"), the other one ("bus_flags") indicating -features of the bus owner (i.e. systemd). Both flags fields are 64bit -in width. - -When calling into the ioctl, you need to place your own supported -feature bits into these fields. This tells the kernel about the -features you support. When the ioctl returns, it will contain the -features the kernel supports. - -If any of the higher 32bit are set on the two flags fields and your -client does not know what they mean, it must disconnect. The upper -32bit are used to indicate "incompatible" feature additions on the bus -system, the lower 32bit indicate "compatible" feature additions. A -client that does not support a "compatible" feature addition can go on -communicating with the bus, however a client that does not support an -"incompatible" feature must not proceed with the connection. When a -client encountes such an "incompatible" feature it should immediately -try the next bus address configured in the bus address string. - -The hello structure also contains another flags field "attach_flags" -which indicates metadata that is optionally attached to all incoming -messages. You probably want to set KDBUS_ATTACH_NAMES unconditionally -in it. This has the effect that all well-known names of a sender are -attached to all incoming messages. You need this information to -implement matches that match on a message sender name correctly. Of -course, you should only request the attachment of as little metadata -fields as you need. - -The kernel will return in the "id" field your unique id. This is a -simple numeric value. For compatibility with classic dbus1 simply -format this as string and prefix ":1.". - -The kernel will also return the bloom filter size and bloom filter -hash function number used for the signal broadcast bloom filter (see -below). - -The kernel will also return the bus ID of the bus in a 128bit field. - -The pool size field specifies the size of the memory mapped buffer. -After the calling the hello ioctl, you should memory map the kdbus -fd. In this memory mapped region, the kernel will place all your incoming -messages. - -SENDING MESSAGES - -Use the MSG_SEND ioctl to send a message to another peer. The ioctl -takes a structure that contains a variety of fields: - -The flags field corresponds closely to the old dbus1 message header -flags field, though the DONT_EXPECT_REPLY field got inverted into -EXPECT_REPLY. - -The dst_id/src_id field contains the unique id of the destination and -the sender. The sender field is overridden by the kernel usually, hence -you shouldn't fill it in. The destination field can also take the -special value KDBUS_DST_ID_BROADCAST for broadcast messages. For -messages intended to a well-known name set the field to -KDBUS_DST_ID_NAME, and attach the name in a special "items" entry to -the message (see below). - -The payload field indicates the payload. For all dbus traffic it -should carry the value 0x4442757344427573ULL. (Which encodes -'DBusDBus'). - -The cookie field corresponds with the "serial" field of classic -dbus1. We simply renamed it here (and extended it to 64bit) since we -didn't want to imply the monotonicity of the assignment the way the -word "serial" indicates it. - -When sending a message that expects a reply, you need to set the -EXPECT_REPLY flag in the message flag field. In this case you should -also fill out the "timeout_ns" value which indicates the timeout in -nsec for this call. If the peer does not respond in this time you will -get a notification of a timeout. Note that this is also used for -security purposes: a single reply messages is only allowed through the -bus as long as the timeout has not ended. With this timeout value you -hence "open a time window" in which the peer might respond to your -request and the policy allows the response to go through. - -When sending a message that is a reply, you need to fill in the -cookie_reply field, which is similar to the reply_serial field of -dbus1. Note that a message cannot have EXPECT_REPLY and a reply_serial -at the same time! - -This pretty much explains the ioctl header. The actual payload of the -data is now referenced in additional items that are attached to this -ioctl header structure at the end. When sending a message, you attach -items of the type PAYLOAD_VEC, PAYLOAD_MEMFD, FDS, BLOOM_FILTER, -DST_NAME to it: - - KDBUS_ITEM_PAYLOAD_VEC: contains a pointer + length pair for - referencing arbitrary user memory. This is how you reference most - of your data. It's a lot like the good old iovec structure of glibc. - - KDBUS_ITEM_PAYLOAD_MEMFD: for large data blocks it is preferable - to send prepared "memfds" (see below) over. This item contains an - fd for a memfd plus a size. - - KDBUS_ITEM_FDS: for sending over fds attach an item of this type with - an array of fds. - - KDBUS_ITEM_BLOOM_FILTER: the calculated bloom filter of this message, - only for undirected (broadcast) message. - - KDBUS_ITEM_DST_NAME: for messages that are directed to a well-known - name (instead of a unique name), this item contains the well-known - name field. - -A single message may consists of no, one or more payload items of type -PAYLOAD_VEC or PAYLOAD_MEMFD. D-Bus protocol implementations should -treat them as a single block that just happens to be split up into -multiple items. Some restrictions apply however: - - The message header in its entirety must be contained in a single - PAYLOAD_VEC item. - - You may only split your message up right in front of each GVariant - contained in the payload, as well is immediately before framing of a - Gvariant, as well after as any padding bytes if there are any. The - padding bytes must be wholly contained in the preceding - PAYLOAD_VEC/PAYLOAD_MEMFD item. You may not split up basic types - nor arrays of fixed types. The latter is necessary to allow APIs - to return direct pointers to linear arrays of numeric - values. Examples: The basic types "u", "s", "t" have to be in the - same payload item. The array of fixed types "ay", "ai" have to be - fully in contained in the same payload item. For an array "as" or - "a(si)" the only restriction however is to keep each string - individually in an uninterrupted item, to keep the framing of each - element and the array in a single uninterrupted item, however the - various strings might end up in different items. - -Note again, that splitting up messages into separate items is up to the -implementation. Also note that the kdbus kernel side might merge -separate items if it deems this to be useful. However, the order in -which items are contained in the message is left untouched. - -PAYLOAD_MEMFD items allow zero-copy data transfer (see below regarding -the memfd concept). Note however that the overhead of mapping these -makes them relatively expensive, and only worth the trouble for memory -blocks > 512K (this value appears to be quite universal across -architectures, as we tested). Thus we recommend sending PAYLOAD_VEC -items over for small messages and restore to PAYLOAD_MEMFD items for -messages > 512K. Since while building up the message you might not -know yet whether it will grow beyond this boundary a good approach is -to simply build the message unconditionally in a memfd -object. However, when the message is sealed to be sent away check for -the size limit. If the size of the message is < 512K, then simply send -the data as PAYLOAD_VEC and reuse the memfd. If it is >= 512K, seal -the memfd and send it as PAYLOAD_MEMFD, and allocate a new memfd for -the next message. - -RECEIVING MESSAGES - -Use the MSG_RECV ioctl to read a message from kdbus. This will return -an offset into the pool memory map, relative to its beginning. - -The received message structure more or less follows the structure of -the message originally sent. However, certain changes have been -made. In the header the src_id field will be filled in. - -The payload items might have gotten merged and PAYLOAD_VEC items are -not used. Instead, you will only find PAYLOAD_OFF and PAYLOAD_MEMFD -items. The former contain an offset and size into your memory mapped -pool where you find the payload. - -If during the HELLO ioctl you asked for getting metadata attached to -your message, you will find additional KDBUS_ITEM_CREDS, -KDBUS_ITEM_PID_COMM, KDBUS_ITEM_TID_COMM, KDBUS_ITEM_TIMESTAMP, -KDBUS_ITEM_EXE, KDBUS_ITEM_CMDLINE, KDBUS_ITEM_CGROUP, -KDBUS_ITEM_CAPS, KDBUS_ITEM_SECLABEL, KDBUS_ITEM_AUDIT items that -contain this metadata. This metadata will be gathered from the sender -at the point in time it sends the message. This information is -uncached, and since it is appended by the kernel, trustable. The -KDBUS_ITEM_SECLABEL item usually contains the SELinux security label, -if it is used. - -After processing the message you need to call the KDBUS_CMD_FREE -ioctl, which releases the message from the pool, and allows the kernel -to store another message there. Note that the memory used by the pool -is ordinary anonymous, swappable memory that is backed by tmpfs. Hence -there is no need to copy the message out of it quickly, instead you -can just leave it there as long as you need it and release it via the -FREE ioctl only after that's done. - -BLOOM FILTERS - -The kernel does not understand dbus marshaling, it will not look into -the message payload. To allow clients to subscribe to specific subsets -of the broadcast matches we employ bloom filters. - -When broadcasting messages, a bloom filter needs to be attached to the -message in a KDBUS_ITEM_BLOOM item (and only for broadcasting -messages!). If you don't know what bloom filters are, read up now on -Wikipedia. In short: they are a very efficient way how to -probabilistically check whether a certain word is contained in a -vocabulary. It knows no false negatives, but it does know false -positives. - -The parameters for the bloom filters that need to be included in -broadcast message is communicated to userspace as part of the hello -response structure (see above). By default it has the parameters m=512 -(bits in the filter), k=8 (nr of hash functions). Note however, that -this is subject to change in later versions, and userspace -implementations must be capable of handling m values between at least -m=8 and m=2^32, and k values between at least k=1 and k=32. The -underlying hash function is SipHash-2-4. It is used with a number of -constant (yet originally randomly generated) 128bit hash keys, more -specifically: - - b9,66,0b,f0,46,70,47,c1,88,75,c4,9c,54,b9,bd,15, - aa,a1,54,a2,e0,71,4b,39,bf,e1,dd,2e,9f,c5,4a,3b, - 63,fd,ae,be,cd,82,48,12,a1,6e,41,26,cb,fa,a0,c8, - 23,be,45,29,32,d2,46,2d,82,03,52,28,fe,37,17,f5, - 56,3b,bf,ee,5a,4f,43,39,af,aa,94,08,df,f0,fc,10, - 31,80,c8,73,c7,ea,46,d3,aa,25,75,0f,9e,4c,09,29, - 7d,f7,18,4b,7b,a4,44,d5,85,3c,06,e0,65,53,96,6d, - f2,77,e9,6f,93,b5,4e,71,9a,0c,34,88,39,25,bf,35 - -When calculating the first bit index into the bloom filter, the -SipHash-2-4 hash value is calculated for the input data and the first -16 bytes of the array above as hash key. Of the resulting 8 bytes of -output, as many full bytes are taken for the bit index as necessary, -starting from the output's first byte. For the second bit index the -same hash value is used, continuing with the next unused output byte, -and so on. Each time the bytes returned by the hash function are -depleted it is recalculated with the next 16 byte hash key from the -array above and the same input data. - -For each message to send across the bus we populate the bloom filter -with all possible matchable strings. If a client then wants to -subscribe to messages of this type, it simply tells the kernel to test -its own calculated bit mask against the bloom filter of each message. - -More specifically, the following strings are added to the bloom filter -of each message that is broadcasted: - - The string "interface:" suffixed by the interface name - - The string "member:" suffixed by the member name - - The string "path:" suffixed by the path name - - The string "path-slash-prefix:" suffixed with the path name, and - also all prefixes of the path name (cut off at "/"), also prefixed - with "path-slash-prefix". - - The string "message-type:" suffixed with the strings "signal", - "method_call", "error" or "method_return" for the respective message - type of the message. - - If the first argument of the message is a string, "arg0:" suffixed - with the first argument. - - If the first argument of the message is a string, "arg0-dot-prefix" - suffixed with the first argument, and also all prefixes of the - argument (cut off at "."), also prefixed with "arg0-dot-prefix". - - If the first argument of the message is a string, - "arg0-slash-prefix" suffixed with the first argument, and also all - prefixes of the argument (cut off at "/"), also prefixed with - "arg0-slash-prefix". - - Similar for all further arguments that are strings up to 63, for the - arguments and their "dot" and "slash" prefixes. On the first - argument that is not a string, addition to the bloom filter should be - stopped however. - -(Note that the bloom filter does not contain sender nor receiver -names!) - -When a client wants to subscribe to messages matching a certain -expression, it should calculate the bloom mask following the same -algorithm. The kernel will then simply test the mask against the -attached bloom filters. - -Note that bloom filters are probabilistic, which means that clients -might get messages they did not expect. Your bus protocol -implementation must be capable of dealing with these unexpected -messages (which it needs to anyway, given that transfers are -relatively unrestricted on kdbus and people can send you all kinds of -non-sense). - -If a client connects to a bus whose bloom filter metrics (i.e. filter -size and number of hash functions) are outside of the range the client -supports it must immediately disconnect and continue connection with -the next bus address of the bus connection string. - -INSTALLING MATCHES - -To install matches for broadcast messages, use the KDBUS_CMD_ADD_MATCH -ioctl. It takes a structure that contains an encoded match expression, -and that is followed by one or more items, which are combined in an -AND way. (Meaning: a message is matched exactly when all items -attached to the original ioctl struct match). - -To match against other user messages add a KDBUS_ITEM_BLOOM item in -the match (see above). Note that the bloom filter does not include -matches to the sender names. To additionally check against sender -names, use the KDBUS_ITEM_ID (for unique id matches) and -KDBUS_ITEM_NAME (for well-known name matches) item types. - -To match against kernel generated messages (see below) you should add -items of the same type as the kernel messages include, -i.e. KDBUS_ITEM_NAME_ADD, KDBUS_ITEM_NAME_REMOVE, -KDBUS_ITEM_NAME_CHANGE, KDBUS_ITEM_ID_ADD, KDBUS_ITEM_ID_REMOVE and -fill them out. Note however, that you have some wildcards in this -case, for example the .id field of KDBUS_ITEM_ID_ADD/KDBUS_ITEM_ID_REMOVE -structures may be set to 0 to match against any id addition/removal. - -Note that dbus match strings do no map 1:1 to these ioctl() calls. In -many cases (where the match string is "underspecified") you might need -to issue up to six different ioctl() calls for the same match. For -example, the empty match (which matches against all messages), would -translate into one KDBUS_ITEM_BLOOM ioctl, one KDBUS_ITEM_NAME_ADD, -one KDBUS_ITEM_NAME_CHANGE, one KDBUS_ITEM_NAME_REMOVE, one -KDBUS_ITEM_ID_ADD and one KDBUS_ITEM_ID_REMOVE. - -When creating a match, you may attach a "cookie" value to them, which -is used for deleting this match again. The cookie can be selected freely -by the client. When issuing KDBUS_CMD_REMOVE_MATCH, simply pass the -same cookie as before and all matches matching the same "cookie" value -will be removed. This is particularly handy for the case where multiple -ioctl()s are added for a single match strings. - -MEMFDS - -memfds may be sent across kdbus via KDBUS_ITEM_PAYLOAD_MEMFD items -attached to messages. If this is done, the data included in the memfd -is considered part of the payload stream of a message, and are treated -the same way as KDBUS_ITEM_PAYLOAD_VEC by the receiving side. It is -possible to interleave KDBUS_ITEM_PAYLOAD_MEMFD and -KDBUS_ITEM_PAYLOAD_VEC items freely, by the reader they will be -considered a single stream of bytes in the order these items appear in -the message, that just happens to be split up at various places -(regarding rules how they may be split up, see above). The kernel will -refuse taking KDBUS_ITEM_PAYLOAD_MEMFD items that refer to memfds that -are not sealed. - -Note that sealed memfds may be unsealed again if they are not mapped -you have the only fd reference to them. - -Alternatively to sending memfds as KDBUS_ITEM_PAYLOAD_MEMFD items -(where they are just a part of the payload stream of a message) you can -also simply attach any memfd to a message using -KDBUS_ITEM_PAYLOAD_FDS. In this case, the memfd contents is not -considered part of the payload stream of the message, but simply fds -like any other, that happen to be attached to the message. - -MESSAGES FROM THE KERNEL - -A couple of messages previously generated by the dbus1 bus driver are -now generated by the kernel. Since the kernel does not understand the -payload marshaling, they are generated by the kernel in a different -format. This is indicated with the "payload type" field of the -messages set to 0. Library implementations should take these messages -and synthesize traditional driver messages for them on reception. - -More specifically: - - Instead of the NameOwnerChanged, NameLost, NameAcquired signals - there are kernel messages containing KDBUS_ITEM_NAME_ADD, - KDBUS_ITEM_NAME_REMOVE, KDBUS_ITEM_NAME_CHANGE, KDBUS_ITEM_ID_ADD, - KDBUS_ITEM_ID_REMOVE items are generated (each message will contain - exactly one of these items). Note that in libsystemd we have - obsoleted NameLost/NameAcquired messages, since they are entirely - redundant to NameOwnerChanged. This library will hence only - synthesize NameOwnerChanged messages from these kernel messages, - and never generate NameLost/NameAcquired. If your library needs to - stay compatible to the old dbus1 userspace, you possibly might need - to synthesize both a NameOwnerChanged and NameLost/NameAcquired - message from the same kernel message. - - When a method call times out, a KDBUS_ITEM_REPLY_TIMEOUT message is - generated. This should be synthesized into a method error reply - message to the original call. - - When a method call fails because the peer terminated the connection - before responding, a KDBUS_ITEM_REPLY_DEAD message is - generated. Similarly, it should be synthesized into a method error - reply message. - -For synthesized messages we recommend setting the cookie field to -(uint32_t) -1 (and not (uint64_t) -1!), so that the cookie is not 0 -(which the dbus1 spec does not allow), but clearly recognizable as -synthetic. - -Note that the KDBUS_ITEM_NAME_XYZ messages will actually inform you -about all kinds of names, including activatable ones. Classic dbus1 -NameOwnerChanged messages OTOH are only generated when a name is -really acquired on the bus and not just simply activatable. This means -you must explicitly check for the case where an activatable name -becomes acquired or an acquired name is lost and returns to be -activatable. - -NAME REGISTRY - -To acquire names on the bus, use the KDBUS_CMD_NAME_ACQUIRE ioctl(). It -takes a flags field similar to dbus1's RequestName() bus driver call, -however the NO_QUEUE flag got inverted into a QUEUE flag instead. - -To release a previously acquired name use the KDBUS_CMD_NAME_RELEASE -ioctl(). - -To list acquired names use the KDBUS_CMD_CONN_INFO ioctl. It may be -used to list unique names, well known names as well as activatable -names and clients currently queuing for ownership of a well-known -name. The ioctl will return an offset into the memory pool. After -reading all the data you need, you need to release this via the -KDBUS_CMD_FREE ioctl(), similar how you release a received message. - -CREDENTIALS - -kdbus can optionally attach various kinds of metadata about the sender at -the point of time of sending ("credentials") to messages, on request -of the receiver. This is both supported on directed and undirected -(broadcast) messages. The metadata to attach is selected at time of -the HELLO ioctl of the receiver via a flags field (see above). Note -that clients must be able to handle that messages contain more -metadata than they asked for themselves, to simplify implementation of -broadcasting in the kernel. The receiver should not rely on this data -to be around though, even though it will be correct if it happens to -be attached. In order to avoid programming errors in applications, we -recommend though not passing this data on to clients that did not -explicitly ask for it. - -Credentials may also be queried for a well-known or unique name. Use -the KDBUS_CMD_CONN_INFO for this. It will return an offset to the pool -area again, which will contain the same credential items as messages -have attached. Note that when issuing the ioctl, you can select a -different set of credentials to gather, than what was originally requested -for being attached to incoming messages. - -Credentials are always specific to the sender's domain that was -current at the time of sending, and of the process that opened the -bus connection at the time of opening it. Note that this latter data -is cached! - -POLICY - -The kernel enforces only very limited policy on names. It will not do -access filtering by userspace payload, and thus not by interface or -method name. - -This ultimately means that most fine-grained policy enforcement needs -to be done by the receiving process. We recommend using PolicyKit for -any more complex checks. However, libraries should make simple static -policy decisions regarding privileged/unprivileged method calls -easy. We recommend doing this by enabling KDBUS_ATTACH_CAPS and -KDBUS_ATTACH_CREDS for incoming messages, and then discerning client -access by some capability, or if sender and receiver UIDs match. - -BUS ADDRESSES - -When connecting to kdbus use the "kernel:" protocol prefix in DBus -address strings. The device node path is encoded in its "path=" -parameter. - -Client libraries should use the following connection string when -connecting to the system bus: - - kernel:path=/sys/fs/kdbus/0-system/bus;unix:path=/var/run/dbus/system_bus_socket - -This will ensure that kdbus is preferred over the legacy AF_UNIX -socket, but compatibility is kept. For the user bus use: - - kernel:path=/sys/fs/kdbus/$UID-user/bus;unix:path=$XDG_RUNTIME_DIR/bus - -With $UID replaced by the callers numer user ID, and $XDG_RUNTIME_DIR -following the XDG basedir spec. - -Of course the $DBUS_SYSTEM_BUS_ADDRESS and $DBUS_SESSION_BUS_ADDRESS -variables should still take precedence. - -DBUS SERVICE FILES - -Activatable services for kdbus may not use classic dbus1 service -activation files. Instead, programs should drop in native systemd -.service and .busname unit files, so that they are treated uniformly -with other types of units and activation of the system. - -Note that this results in a major difference to classic dbus1: -activatable bus names can be established at any time in the boot process. -This is unlike dbus1 where activatable names are unconditionally available -as long as dbus-daemon is running. Being able to control when -activatable names are established is essential to allow usage of kdbus -during early boot and in initrds, without the risk of triggering -services too early. - -DISCLAIMER - -This all is so far just the status quo. We are putting this together, because -we are quite confident that further API changes will be smaller, but -to make this very clear: this is all subject to change, still! - -We invite you to port over your favorite dbus library to this new -scheme, but please be prepared to make minor changes when we still -change these interfaces! diff --git a/src/libsystemd/sd-bus/bus-bloom.c b/src/libsystemd/sd-bus/bus-bloom.c deleted file mode 100644 index 112769fcb6..0000000000 --- a/src/libsystemd/sd-bus/bus-bloom.c +++ /dev/null @@ -1,156 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "bus-bloom.h" -#include "siphash24.h" -#include "util.h" - -static inline void set_bit(uint64_t filter[], unsigned long b) { - filter[b >> 6] |= 1ULL << (b & 63); -} - -static const sd_id128_t hash_keys[] = { - SD_ID128_ARRAY(b9,66,0b,f0,46,70,47,c1,88,75,c4,9c,54,b9,bd,15), - SD_ID128_ARRAY(aa,a1,54,a2,e0,71,4b,39,bf,e1,dd,2e,9f,c5,4a,3b), - SD_ID128_ARRAY(63,fd,ae,be,cd,82,48,12,a1,6e,41,26,cb,fa,a0,c8), - SD_ID128_ARRAY(23,be,45,29,32,d2,46,2d,82,03,52,28,fe,37,17,f5), - SD_ID128_ARRAY(56,3b,bf,ee,5a,4f,43,39,af,aa,94,08,df,f0,fc,10), - SD_ID128_ARRAY(31,80,c8,73,c7,ea,46,d3,aa,25,75,0f,9e,4c,09,29), - SD_ID128_ARRAY(7d,f7,18,4b,7b,a4,44,d5,85,3c,06,e0,65,53,96,6d), - SD_ID128_ARRAY(f2,77,e9,6f,93,b5,4e,71,9a,0c,34,88,39,25,bf,35), -}; - -static void bloom_add_data( - uint64_t filter[], /* The filter bits */ - size_t size, /* Size of the filter in bytes */ - unsigned k, /* Number of hash functions */ - const void *data, /* Data to hash */ - size_t n) { /* Size of data to hash in bytes */ - - uint64_t h; - uint64_t m; - unsigned w, i, c = 0; - unsigned hash_index; - - assert(size > 0); - assert(k > 0); - - /* Determine bits in filter */ - m = size * 8; - - /* Determine how many bytes we need to generate a bit index 0..m for this filter */ - w = (u64log2(m) + 7) / 8; - - assert(w <= sizeof(uint64_t)); - - /* Make sure we have enough hash keys to generate m * k bits - * of hash value. Note that SipHash24 generates 64 bits of - * hash value for each 128 bits of hash key. */ - assert(k * w <= ELEMENTSOF(hash_keys) * 8); - - for (i = 0, hash_index = 0; i < k; i++) { - uint64_t p = 0; - unsigned d; - - for (d = 0; d < w; d++) { - if (c <= 0) { - h = siphash24(data, n, hash_keys[hash_index++].bytes); - c += 8; - } - - p = (p << 8ULL) | (uint64_t) ((uint8_t *)&h)[8 - c]; - c--; - } - - p &= m - 1; - set_bit(filter, p); - } - - /* log_debug("bloom: adding <%.*s>", (int) n, (char*) data); */ -} - -void bloom_add_pair(uint64_t filter[], size_t size, unsigned k, const char *a, const char *b) { - size_t n; - char *c; - - assert(filter); - assert(a); - assert(b); - - n = strlen(a) + 1 + strlen(b); - c = alloca(n + 1); - strcpy(stpcpy(stpcpy(c, a), ":"), b); - - bloom_add_data(filter, size, k, c, n); -} - -void bloom_add_prefixes(uint64_t filter[], size_t size, unsigned k, const char *a, const char *b, char sep) { - size_t n; - char *c, *p; - - assert(filter); - assert(a); - assert(b); - - n = strlen(a) + 1 + strlen(b); - c = alloca(n + 1); - - p = stpcpy(stpcpy(c, a), ":"); - strcpy(p, b); - - bloom_add_data(filter, size, k, c, n); - - for (;;) { - char *e; - - e = strrchr(p, sep); - if (!e) - break; - - *(e + 1) = 0; - bloom_add_data(filter, size, k, c, e - c + 1); - - if (e == p) - break; - - *e = 0; - bloom_add_data(filter, size, k, c, e - c); - } -} - -bool bloom_validate_parameters(size_t size, unsigned k) { - uint64_t m; - unsigned w; - - if (size <= 0) - return false; - - if (k <= 0) - return false; - - m = size * 8; - w = (u64log2(m) + 7) / 8; - if (w > sizeof(uint64_t)) - return false; - - if (k * w > ELEMENTSOF(hash_keys) * 8) - return false; - - return true; -} diff --git a/src/libsystemd/sd-bus/bus-bloom.h b/src/libsystemd/sd-bus/bus-bloom.h deleted file mode 100644 index c824622b95..0000000000 --- a/src/libsystemd/sd-bus/bus-bloom.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 -#include - -/* - * Our default bloom filter has the following parameters: - * - * m=512 (bits in the filter) - * k=8 (hash functions) - * - * We use SipHash24 as hash function with a number of (originally - * randomized) but fixed hash keys. - * - */ - -#define DEFAULT_BLOOM_SIZE (512/8) /* m: filter size */ -#define DEFAULT_BLOOM_N_HASH 8 /* k: number of hash functions */ - -void bloom_add_pair(uint64_t filter[], size_t size, unsigned n_hash, const char *a, const char *b); -void bloom_add_prefixes(uint64_t filter[], size_t size, unsigned n_hash, const char *a, const char *b, char sep); - -bool bloom_validate_parameters(size_t size, unsigned n_hash); diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c deleted file mode 100644 index 02e3bf904c..0000000000 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ /dev/null @@ -1,87 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Zbigniew Jędrzejewski-Szmek - - 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 "sd-bus.h" - -#include "bus-common-errors.h" -#include "bus-error.h" - -BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_UNIT, ENOENT), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_UNIT_FOR_PID, ESRCH), - SD_BUS_ERROR_MAP(BUS_ERROR_UNIT_EXISTS, EEXIST), - SD_BUS_ERROR_MAP(BUS_ERROR_LOAD_FAILED, EIO), - SD_BUS_ERROR_MAP(BUS_ERROR_JOB_FAILED, EREMOTEIO), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_JOB, ENOENT), - SD_BUS_ERROR_MAP(BUS_ERROR_NOT_SUBSCRIBED, EINVAL), - SD_BUS_ERROR_MAP(BUS_ERROR_ALREADY_SUBSCRIBED, EINVAL), - SD_BUS_ERROR_MAP(BUS_ERROR_ONLY_BY_DEPENDENCY, EINVAL), - SD_BUS_ERROR_MAP(BUS_ERROR_TRANSACTION_JOBS_CONFLICTING, EDEADLK), - SD_BUS_ERROR_MAP(BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC, EDEADLK), - SD_BUS_ERROR_MAP(BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE, EDEADLK), - SD_BUS_ERROR_MAP(BUS_ERROR_UNIT_MASKED, ESHUTDOWN), - SD_BUS_ERROR_MAP(BUS_ERROR_UNIT_GENERATED, EADDRNOTAVAIL), - SD_BUS_ERROR_MAP(BUS_ERROR_UNIT_LINKED, ELOOP), - SD_BUS_ERROR_MAP(BUS_ERROR_JOB_TYPE_NOT_APPLICABLE, EBADR), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_ISOLATION, EPERM), - SD_BUS_ERROR_MAP(BUS_ERROR_SHUTTING_DOWN, ECANCELED), - SD_BUS_ERROR_MAP(BUS_ERROR_SCOPE_NOT_RUNNING, EHOSTDOWN), - - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_MACHINE, ENXIO), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_IMAGE, ENOENT), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_MACHINE_FOR_PID, ENXIO), - SD_BUS_ERROR_MAP(BUS_ERROR_MACHINE_EXISTS, EEXIST), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_PRIVATE_NETWORKING, ENOSYS), - - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SESSION, ENXIO), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SESSION_FOR_PID, ENXIO), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_USER, ENXIO), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_USER_FOR_PID, ENXIO), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SEAT, ENXIO), - SD_BUS_ERROR_MAP(BUS_ERROR_SESSION_NOT_ON_SEAT, EINVAL), - SD_BUS_ERROR_MAP(BUS_ERROR_NOT_IN_CONTROL, EINVAL), - SD_BUS_ERROR_MAP(BUS_ERROR_DEVICE_IS_TAKEN, EINVAL), - SD_BUS_ERROR_MAP(BUS_ERROR_DEVICE_NOT_TAKEN, EINVAL), - SD_BUS_ERROR_MAP(BUS_ERROR_OPERATION_IN_PROGRESS, EINPROGRESS), - SD_BUS_ERROR_MAP(BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, EOPNOTSUPP), - - SD_BUS_ERROR_MAP(BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, EALREADY), - - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_PROCESS, ESRCH), - - SD_BUS_ERROR_MAP(BUS_ERROR_NO_NAME_SERVERS, ESRCH), - SD_BUS_ERROR_MAP(BUS_ERROR_INVALID_REPLY, EINVAL), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_RR, ENOENT), - SD_BUS_ERROR_MAP(BUS_ERROR_CNAME_LOOP, EDEADLK), - SD_BUS_ERROR_MAP(BUS_ERROR_ABORTED, ECANCELED), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SERVICE, EUNATCH), - SD_BUS_ERROR_MAP(BUS_ERROR_DNSSEC_FAILED, EHOSTUNREACH), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_TRUST_ANCHOR, EHOSTUNREACH), - SD_BUS_ERROR_MAP(BUS_ERROR_RR_TYPE_UNSUPPORTED, EOPNOTSUPP), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_LINK, ENXIO), - SD_BUS_ERROR_MAP(BUS_ERROR_LINK_BUSY, EBUSY), - SD_BUS_ERROR_MAP(BUS_ERROR_NETWORK_DOWN, ENETDOWN), - - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_TRANSFER, ENXIO), - SD_BUS_ERROR_MAP(BUS_ERROR_TRANSFER_IN_PROGRESS, EBUSY), - - SD_BUS_ERROR_MAP_END -}; diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h deleted file mode 100644 index c8f369cb78..0000000000 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "bus-error.h" - -#define BUS_ERROR_NO_SUCH_UNIT "org.freedesktop.systemd1.NoSuchUnit" -#define BUS_ERROR_NO_UNIT_FOR_PID "org.freedesktop.systemd1.NoUnitForPID" -#define BUS_ERROR_UNIT_EXISTS "org.freedesktop.systemd1.UnitExists" -#define BUS_ERROR_LOAD_FAILED "org.freedesktop.systemd1.LoadFailed" -#define BUS_ERROR_JOB_FAILED "org.freedesktop.systemd1.JobFailed" -#define BUS_ERROR_NO_SUCH_JOB "org.freedesktop.systemd1.NoSuchJob" -#define BUS_ERROR_NOT_SUBSCRIBED "org.freedesktop.systemd1.NotSubscribed" -#define BUS_ERROR_ALREADY_SUBSCRIBED "org.freedesktop.systemd1.AlreadySubscribed" -#define BUS_ERROR_ONLY_BY_DEPENDENCY "org.freedesktop.systemd1.OnlyByDependency" -#define BUS_ERROR_TRANSACTION_JOBS_CONFLICTING "org.freedesktop.systemd1.TransactionJobsConflicting" -#define BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC "org.freedesktop.systemd1.TransactionOrderIsCyclic" -#define BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE "org.freedesktop.systemd1.TransactionIsDestructive" -#define BUS_ERROR_UNIT_MASKED "org.freedesktop.systemd1.UnitMasked" -#define BUS_ERROR_UNIT_GENERATED "org.freedesktop.systemd1.UnitGenerated" -#define BUS_ERROR_UNIT_LINKED "org.freedesktop.systemd1.UnitLinked" -#define BUS_ERROR_JOB_TYPE_NOT_APPLICABLE "org.freedesktop.systemd1.JobTypeNotApplicable" -#define BUS_ERROR_NO_ISOLATION "org.freedesktop.systemd1.NoIsolation" -#define BUS_ERROR_SHUTTING_DOWN "org.freedesktop.systemd1.ShuttingDown" -#define BUS_ERROR_SCOPE_NOT_RUNNING "org.freedesktop.systemd1.ScopeNotRunning" - -#define BUS_ERROR_NO_SUCH_MACHINE "org.freedesktop.machine1.NoSuchMachine" -#define BUS_ERROR_NO_SUCH_IMAGE "org.freedesktop.machine1.NoSuchImage" -#define BUS_ERROR_NO_MACHINE_FOR_PID "org.freedesktop.machine1.NoMachineForPID" -#define BUS_ERROR_MACHINE_EXISTS "org.freedesktop.machine1.MachineExists" -#define BUS_ERROR_NO_PRIVATE_NETWORKING "org.freedesktop.machine1.NoPrivateNetworking" -#define BUS_ERROR_NO_SUCH_USER_MAPPING "org.freedesktop.machine1.NoSuchUserMapping" -#define BUS_ERROR_NO_SUCH_GROUP_MAPPING "org.freedesktop.machine1.NoSuchGroupMapping" - -#define BUS_ERROR_NO_SUCH_SESSION "org.freedesktop.login1.NoSuchSession" -#define BUS_ERROR_NO_SESSION_FOR_PID "org.freedesktop.login1.NoSessionForPID" -#define BUS_ERROR_NO_SUCH_USER "org.freedesktop.login1.NoSuchUser" -#define BUS_ERROR_NO_USER_FOR_PID "org.freedesktop.login1.NoUserForPID" -#define BUS_ERROR_NO_SUCH_SEAT "org.freedesktop.login1.NoSuchSeat" -#define BUS_ERROR_SESSION_NOT_ON_SEAT "org.freedesktop.login1.SessionNotOnSeat" -#define BUS_ERROR_NOT_IN_CONTROL "org.freedesktop.login1.NotInControl" -#define BUS_ERROR_DEVICE_IS_TAKEN "org.freedesktop.login1.DeviceIsTaken" -#define BUS_ERROR_DEVICE_NOT_TAKEN "org.freedesktop.login1.DeviceNotTaken" -#define BUS_ERROR_OPERATION_IN_PROGRESS "org.freedesktop.login1.OperationInProgress" -#define BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED "org.freedesktop.login1.SleepVerbNotSupported" -#define BUS_ERROR_SESSION_BUSY "org.freedesktop.login1.SessionBusy" - -#define BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED "org.freedesktop.timedate1.AutomaticTimeSyncEnabled" - -#define BUS_ERROR_NO_SUCH_PROCESS "org.freedesktop.systemd1.NoSuchProcess" - -#define BUS_ERROR_NO_NAME_SERVERS "org.freedesktop.resolve1.NoNameServers" -#define BUS_ERROR_INVALID_REPLY "org.freedesktop.resolve1.InvalidReply" -#define BUS_ERROR_NO_SUCH_RR "org.freedesktop.resolve1.NoSuchRR" -#define BUS_ERROR_CNAME_LOOP "org.freedesktop.resolve1.CNameLoop" -#define BUS_ERROR_ABORTED "org.freedesktop.resolve1.Aborted" -#define BUS_ERROR_NO_SUCH_SERVICE "org.freedesktop.resolve1.NoSuchService" -#define BUS_ERROR_DNSSEC_FAILED "org.freedesktop.resolve1.DnssecFailed" -#define BUS_ERROR_NO_TRUST_ANCHOR "org.freedesktop.resolve1.NoTrustAnchor" -#define BUS_ERROR_RR_TYPE_UNSUPPORTED "org.freedesktop.resolve1.ResourceRecordTypeUnsupported" -#define BUS_ERROR_NO_SUCH_LINK "org.freedesktop.resolve1.NoSuchLink" -#define BUS_ERROR_LINK_BUSY "org.freedesktop.resolve1.LinkBusy" -#define BUS_ERROR_NETWORK_DOWN "org.freedesktop.resolve1.NetworkDown" -#define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError." - -#define BUS_ERROR_NO_SUCH_TRANSFER "org.freedesktop.import1.NoSuchTransfer" -#define BUS_ERROR_TRANSFER_IN_PROGRESS "org.freedesktop.import1.TransferInProgress" - -BUS_ERROR_MAP_ELF_USE(bus_common_errors); diff --git a/src/libsystemd/sd-bus/bus-container.c b/src/libsystemd/sd-bus/bus-container.c deleted file mode 100644 index 3191d27ded..0000000000 --- a/src/libsystemd/sd-bus/bus-container.c +++ /dev/null @@ -1,277 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 - -#include "bus-container.h" -#include "bus-internal.h" -#include "bus-socket.h" -#include "fd-util.h" -#include "process-util.h" -#include "util.h" - -int bus_container_connect_socket(sd_bus *b) { - _cleanup_close_pair_ int pair[2] = { -1, -1 }; - _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1; - pid_t child; - siginfo_t si; - int r, error_buf = 0; - ssize_t n; - - assert(b); - assert(b->input_fd < 0); - assert(b->output_fd < 0); - assert(b->nspid > 0 || b->machine); - - if (b->nspid <= 0) { - r = container_get_leader(b->machine, &b->nspid); - if (r < 0) - return r; - } - - r = namespace_open(b->nspid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); - if (r < 0) - return r; - - b->input_fd = socket(b->sockaddr.sa.sa_family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (b->input_fd < 0) - return -errno; - - b->output_fd = b->input_fd; - - bus_socket_setup(b); - - if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) - return -errno; - - child = fork(); - if (child < 0) - return -errno; - - if (child == 0) { - pid_t grandchild; - - pair[0] = safe_close(pair[0]); - - r = namespace_enter(pidnsfd, mntnsfd, -1, usernsfd, rootfd); - if (r < 0) - _exit(EXIT_FAILURE); - - /* We just changed PID namespace, however it will only - * take effect on the children we now fork. Hence, - * let's fork another time, and connect from this - * grandchild, so that SO_PEERCRED of our connection - * comes from a process from within the container, and - * not outside of it */ - - grandchild = fork(); - if (grandchild < 0) - _exit(EXIT_FAILURE); - - if (grandchild == 0) { - - r = connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size); - if (r < 0) { - /* Try to send error up */ - error_buf = errno; - (void) write(pair[1], &error_buf, sizeof(error_buf)); - _exit(EXIT_FAILURE); - } - - _exit(EXIT_SUCCESS); - } - - r = wait_for_terminate(grandchild, &si); - if (r < 0) - _exit(EXIT_FAILURE); - - if (si.si_code != CLD_EXITED) - _exit(EXIT_FAILURE); - - _exit(si.si_status); - } - - pair[1] = safe_close(pair[1]); - - r = wait_for_terminate(child, &si); - if (r < 0) - return r; - - n = read(pair[0], &error_buf, sizeof(error_buf)); - if (n < 0) - return -errno; - - if (n > 0) { - if (n != sizeof(error_buf)) - return -EIO; - - if (error_buf < 0) - return -EIO; - - if (error_buf == EINPROGRESS) - return 1; - - if (error_buf > 0) - return -error_buf; - } - - if (si.si_code != CLD_EXITED) - return -EIO; - - if (si.si_status != EXIT_SUCCESS) - return -EIO; - - return bus_socket_start_auth(b); -} - -int bus_container_connect_kernel(sd_bus *b) { - _cleanup_close_pair_ int pair[2] = { -1, -1 }; - _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1; - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(int))]; - } control = {}; - int error_buf = 0; - struct iovec iov = { - .iov_base = &error_buf, - .iov_len = sizeof(error_buf), - }; - struct msghdr mh = { - .msg_control = &control, - .msg_controllen = sizeof(control), - .msg_iov = &iov, - .msg_iovlen = 1, - }; - struct cmsghdr *cmsg; - pid_t child; - siginfo_t si; - int r, fd = -1; - ssize_t n; - - assert(b); - assert(b->input_fd < 0); - assert(b->output_fd < 0); - assert(b->nspid > 0 || b->machine); - - if (b->nspid <= 0) { - r = container_get_leader(b->machine, &b->nspid); - if (r < 0) - return r; - } - - r = namespace_open(b->nspid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); - if (r < 0) - return r; - - if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) - return -errno; - - child = fork(); - if (child < 0) - return -errno; - - if (child == 0) { - pid_t grandchild; - - pair[0] = safe_close(pair[0]); - - r = namespace_enter(pidnsfd, mntnsfd, -1, usernsfd, rootfd); - if (r < 0) - _exit(EXIT_FAILURE); - - /* We just changed PID namespace, however it will only - * take effect on the children we now fork. Hence, - * let's fork another time, and connect from this - * grandchild, so that kdbus only sees the credentials - * of this process which comes from within the - * container, and not outside of it */ - - grandchild = fork(); - if (grandchild < 0) - _exit(EXIT_FAILURE); - - if (grandchild == 0) { - fd = open(b->kernel, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) { - /* Try to send error up */ - error_buf = errno; - (void) write(pair[1], &error_buf, sizeof(error_buf)); - _exit(EXIT_FAILURE); - } - - r = send_one_fd(pair[1], fd, 0); - if (r < 0) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); - } - - r = wait_for_terminate(grandchild, &si); - if (r < 0) - _exit(EXIT_FAILURE); - - if (si.si_code != CLD_EXITED) - _exit(EXIT_FAILURE); - - _exit(si.si_status); - } - - pair[1] = safe_close(pair[1]); - - r = wait_for_terminate(child, &si); - if (r < 0) - return r; - - n = recvmsg(pair[0], &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); - if (n < 0) - return -errno; - - CMSG_FOREACH(cmsg, &mh) { - if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { - int *fds; - unsigned n_fds; - - assert(fd < 0); - - fds = (int*) CMSG_DATA(cmsg); - n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); - - if (n_fds != 1) { - close_many(fds, n_fds); - return -EIO; - } - - fd = fds[0]; - } - } - - /* If there's an fd passed, we are good. */ - if (fd >= 0) { - b->input_fd = b->output_fd = fd; - return bus_kernel_take_fd(b); - } - - /* If there's an error passed, use it */ - if (n == sizeof(error_buf) && error_buf > 0) - return -error_buf; - - /* Otherwise, we have no clue */ - return -EIO; -} diff --git a/src/libsystemd/sd-bus/bus-container.h b/src/libsystemd/sd-bus/bus-container.h deleted file mode 100644 index 509ef45624..0000000000 --- a/src/libsystemd/sd-bus/bus-container.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -int bus_container_connect_socket(sd_bus *b); -int bus_container_connect_kernel(sd_bus *b); diff --git a/src/libsystemd/sd-bus/bus-control.c b/src/libsystemd/sd-bus/bus-control.c deleted file mode 100644 index 52128e7b5c..0000000000 --- a/src/libsystemd/sd-bus/bus-control.c +++ /dev/null @@ -1,1588 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -#ifdef HAVE_VALGRIND_MEMCHECK_H -#include -#endif - -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-bloom.h" -#include "bus-control.h" -#include "bus-internal.h" -#include "bus-message.h" -#include "bus-util.h" -#include "capability-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "strv.h" -#include "user-util.h" - -_public_ int sd_bus_get_unique_name(sd_bus *bus, const char **unique) { - int r; - - assert_return(bus, -EINVAL); - assert_return(unique, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!bus->bus_client) - return -EINVAL; - - r = bus_ensure_running(bus); - if (r < 0) - return r; - - *unique = bus->unique_name; - return 0; -} - -static int bus_request_name_kernel(sd_bus *bus, const char *name, uint64_t flags) { - struct kdbus_cmd *n; - size_t size, l; - int r; - - assert(bus); - assert(name); - - l = strlen(name) + 1; - size = offsetof(struct kdbus_cmd, items) + KDBUS_ITEM_SIZE(l); - n = alloca0_align(size, 8); - n->size = size; - n->flags = request_name_flags_to_kdbus(flags); - - n->items[0].size = KDBUS_ITEM_HEADER_SIZE + l; - n->items[0].type = KDBUS_ITEM_NAME; - memcpy(n->items[0].str, name, l); - -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(n, n->size); -#endif - - r = ioctl(bus->input_fd, KDBUS_CMD_NAME_ACQUIRE, n); - if (r < 0) - return -errno; - - if (n->return_flags & KDBUS_NAME_IN_QUEUE) - return 0; - - return 1; -} - -static int bus_request_name_dbus1(sd_bus *bus, const char *name, uint64_t flags) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - uint32_t ret, param = 0; - int r; - - assert(bus); - assert(name); - - if (flags & SD_BUS_NAME_ALLOW_REPLACEMENT) - param |= BUS_NAME_ALLOW_REPLACEMENT; - if (flags & SD_BUS_NAME_REPLACE_EXISTING) - param |= BUS_NAME_REPLACE_EXISTING; - if (!(flags & SD_BUS_NAME_QUEUE)) - param |= BUS_NAME_DO_NOT_QUEUE; - - r = sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "RequestName", - NULL, - &reply, - "su", - name, - param); - if (r < 0) - return r; - - r = sd_bus_message_read(reply, "u", &ret); - if (r < 0) - return r; - - if (ret == BUS_NAME_ALREADY_OWNER) - return -EALREADY; - else if (ret == BUS_NAME_EXISTS) - return -EEXIST; - else if (ret == BUS_NAME_IN_QUEUE) - return 0; - else if (ret == BUS_NAME_PRIMARY_OWNER) - return 1; - - return -EIO; -} - -_public_ int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags) { - assert_return(bus, -EINVAL); - assert_return(name, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - assert_return(!(flags & ~(SD_BUS_NAME_ALLOW_REPLACEMENT|SD_BUS_NAME_REPLACE_EXISTING|SD_BUS_NAME_QUEUE)), -EINVAL); - assert_return(service_name_is_valid(name), -EINVAL); - assert_return(name[0] != ':', -EINVAL); - - if (!bus->bus_client) - return -EINVAL; - - /* Don't allow requesting the special driver and local names */ - if (STR_IN_SET(name, "org.freedesktop.DBus", "org.freedesktop.DBus.Local")) - return -EINVAL; - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (bus->is_kernel) - return bus_request_name_kernel(bus, name, flags); - else - return bus_request_name_dbus1(bus, name, flags); -} - -static int bus_release_name_kernel(sd_bus *bus, const char *name) { - struct kdbus_cmd *n; - size_t size, l; - int r; - - assert(bus); - assert(name); - - l = strlen(name) + 1; - size = offsetof(struct kdbus_cmd, items) + KDBUS_ITEM_SIZE(l); - n = alloca0_align(size, 8); - n->size = size; - - n->items[0].size = KDBUS_ITEM_HEADER_SIZE + l; - n->items[0].type = KDBUS_ITEM_NAME; - memcpy(n->items[0].str, name, l); - -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(n, n->size); -#endif - r = ioctl(bus->input_fd, KDBUS_CMD_NAME_RELEASE, n); - if (r < 0) - return -errno; - - return 0; -} - -static int bus_release_name_dbus1(sd_bus *bus, const char *name) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - uint32_t ret; - int r; - - assert(bus); - assert(name); - - r = sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "ReleaseName", - NULL, - &reply, - "s", - name); - if (r < 0) - return r; - - r = sd_bus_message_read(reply, "u", &ret); - if (r < 0) - return r; - if (ret == BUS_NAME_NON_EXISTENT) - return -ESRCH; - if (ret == BUS_NAME_NOT_OWNER) - return -EADDRINUSE; - if (ret == BUS_NAME_RELEASED) - return 0; - - return -EINVAL; -} - -_public_ int sd_bus_release_name(sd_bus *bus, const char *name) { - assert_return(bus, -EINVAL); - assert_return(name, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - assert_return(service_name_is_valid(name), -EINVAL); - assert_return(name[0] != ':', -EINVAL); - - if (!bus->bus_client) - return -EINVAL; - - /* Don't allow releasing the special driver and local names */ - if (STR_IN_SET(name, "org.freedesktop.DBus", "org.freedesktop.DBus.Local")) - return -EINVAL; - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (bus->is_kernel) - return bus_release_name_kernel(bus, name); - else - return bus_release_name_dbus1(bus, name); -} - -static int kernel_get_list(sd_bus *bus, uint64_t flags, char ***x) { - struct kdbus_cmd_list cmd = { - .size = sizeof(cmd), - .flags = flags, - }; - struct kdbus_info *name_list, *name; - uint64_t previous_id = 0; - int r; - - /* Caller will free half-constructed list on failure... */ - - r = ioctl(bus->input_fd, KDBUS_CMD_LIST, &cmd); - if (r < 0) - return -errno; - - name_list = (struct kdbus_info *) ((uint8_t *) bus->kdbus_buffer + cmd.offset); - - KDBUS_FOREACH(name, name_list, cmd.list_size) { - struct kdbus_item *item; - - if ((flags & KDBUS_LIST_UNIQUE) && name->id != previous_id && !(name->flags & KDBUS_HELLO_ACTIVATOR)) { - char *n; - - if (asprintf(&n, ":1.%llu", (unsigned long long) name->id) < 0) { - r = -ENOMEM; - goto fail; - } - - r = strv_consume(x, n); - if (r < 0) - goto fail; - - previous_id = name->id; - } - - KDBUS_ITEM_FOREACH(item, name, items) { - if (item->type == KDBUS_ITEM_OWNED_NAME) { - if (service_name_is_valid(item->name.name)) { - r = strv_extend(x, item->name.name); - if (r < 0) { - r = -ENOMEM; - goto fail; - } - } - } - } - } - - r = 0; - -fail: - bus_kernel_cmd_free(bus, cmd.offset); - return r; -} - -static int bus_list_names_kernel(sd_bus *bus, char ***acquired, char ***activatable) { - _cleanup_strv_free_ char **x = NULL, **y = NULL; - int r; - - if (acquired) { - r = kernel_get_list(bus, KDBUS_LIST_UNIQUE | KDBUS_LIST_NAMES, &x); - if (r < 0) - return r; - } - - if (activatable) { - r = kernel_get_list(bus, KDBUS_LIST_ACTIVATORS, &y); - if (r < 0) - return r; - - *activatable = y; - y = NULL; - } - - if (acquired) { - *acquired = x; - x = NULL; - } - - return 0; -} - -static int bus_list_names_dbus1(sd_bus *bus, char ***acquired, char ***activatable) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_strv_free_ char **x = NULL, **y = NULL; - int r; - - if (acquired) { - r = sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "ListNames", - NULL, - &reply, - NULL); - if (r < 0) - return r; - - r = sd_bus_message_read_strv(reply, &x); - if (r < 0) - return r; - - reply = sd_bus_message_unref(reply); - } - - if (activatable) { - r = sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "ListActivatableNames", - NULL, - &reply, - NULL); - if (r < 0) - return r; - - r = sd_bus_message_read_strv(reply, &y); - if (r < 0) - return r; - - *activatable = y; - y = NULL; - } - - if (acquired) { - *acquired = x; - x = NULL; - } - - return 0; -} - -_public_ int sd_bus_list_names(sd_bus *bus, char ***acquired, char ***activatable) { - assert_return(bus, -EINVAL); - assert_return(acquired || activatable, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!bus->bus_client) - return -EINVAL; - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (bus->is_kernel) - return bus_list_names_kernel(bus, acquired, activatable); - else - return bus_list_names_dbus1(bus, acquired, activatable); -} - -static int bus_populate_creds_from_items( - sd_bus *bus, - struct kdbus_info *info, - uint64_t mask, - sd_bus_creds *c) { - - struct kdbus_item *item; - uint64_t m; - int r; - - assert(bus); - assert(info); - assert(c); - - KDBUS_ITEM_FOREACH(item, info, items) { - - switch (item->type) { - - case KDBUS_ITEM_PIDS: - - if (mask & SD_BUS_CREDS_PID && item->pids.pid > 0) { - c->pid = (pid_t) item->pids.pid; - c->mask |= SD_BUS_CREDS_PID; - } - - if (mask & SD_BUS_CREDS_TID && item->pids.tid > 0) { - c->tid = (pid_t) item->pids.tid; - c->mask |= SD_BUS_CREDS_TID; - } - - if (mask & SD_BUS_CREDS_PPID) { - if (item->pids.ppid > 0) { - c->ppid = (pid_t) item->pids.ppid; - c->mask |= SD_BUS_CREDS_PPID; - } else if (item->pids.pid == 1) { - /* The structure doesn't - * really distinguish the case - * where a process has no - * parent and where we don't - * know it because it could - * not be translated due to - * namespaces. However, we - * know that PID 1 has no - * parent process, hence let's - * patch that in, manually. */ - c->ppid = 0; - c->mask |= SD_BUS_CREDS_PPID; - } - } - - break; - - case KDBUS_ITEM_CREDS: - - if (mask & SD_BUS_CREDS_UID && (uid_t) item->creds.uid != UID_INVALID) { - c->uid = (uid_t) item->creds.uid; - c->mask |= SD_BUS_CREDS_UID; - } - - if (mask & SD_BUS_CREDS_EUID && (uid_t) item->creds.euid != UID_INVALID) { - c->euid = (uid_t) item->creds.euid; - c->mask |= SD_BUS_CREDS_EUID; - } - - if (mask & SD_BUS_CREDS_SUID && (uid_t) item->creds.suid != UID_INVALID) { - c->suid = (uid_t) item->creds.suid; - c->mask |= SD_BUS_CREDS_SUID; - } - - if (mask & SD_BUS_CREDS_FSUID && (uid_t) item->creds.fsuid != UID_INVALID) { - c->fsuid = (uid_t) item->creds.fsuid; - c->mask |= SD_BUS_CREDS_FSUID; - } - - if (mask & SD_BUS_CREDS_GID && (gid_t) item->creds.gid != GID_INVALID) { - c->gid = (gid_t) item->creds.gid; - c->mask |= SD_BUS_CREDS_GID; - } - - if (mask & SD_BUS_CREDS_EGID && (gid_t) item->creds.egid != GID_INVALID) { - c->egid = (gid_t) item->creds.egid; - c->mask |= SD_BUS_CREDS_EGID; - } - - if (mask & SD_BUS_CREDS_SGID && (gid_t) item->creds.sgid != GID_INVALID) { - c->sgid = (gid_t) item->creds.sgid; - c->mask |= SD_BUS_CREDS_SGID; - } - - if (mask & SD_BUS_CREDS_FSGID && (gid_t) item->creds.fsgid != GID_INVALID) { - c->fsgid = (gid_t) item->creds.fsgid; - c->mask |= SD_BUS_CREDS_FSGID; - } - - break; - - case KDBUS_ITEM_PID_COMM: - if (mask & SD_BUS_CREDS_COMM) { - r = free_and_strdup(&c->comm, item->str); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_COMM; - } - break; - - case KDBUS_ITEM_TID_COMM: - if (mask & SD_BUS_CREDS_TID_COMM) { - r = free_and_strdup(&c->tid_comm, item->str); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_TID_COMM; - } - break; - - case KDBUS_ITEM_EXE: - if (mask & SD_BUS_CREDS_EXE) { - r = free_and_strdup(&c->exe, item->str); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_EXE; - } - break; - - case KDBUS_ITEM_CMDLINE: - if (mask & SD_BUS_CREDS_CMDLINE) { - c->cmdline_size = item->size - offsetof(struct kdbus_item, data); - c->cmdline = memdup(item->data, c->cmdline_size); - if (!c->cmdline) - return -ENOMEM; - - c->mask |= SD_BUS_CREDS_CMDLINE; - } - break; - - case KDBUS_ITEM_CGROUP: - m = (SD_BUS_CREDS_CGROUP | SD_BUS_CREDS_UNIT | - SD_BUS_CREDS_USER_UNIT | SD_BUS_CREDS_SLICE | - SD_BUS_CREDS_SESSION | SD_BUS_CREDS_OWNER_UID) & mask; - - if (m) { - r = free_and_strdup(&c->cgroup, item->str); - if (r < 0) - return r; - - r = bus_get_root_path(bus); - if (r < 0) - return r; - - r = free_and_strdup(&c->cgroup_root, bus->cgroup_root); - if (r < 0) - return r; - - c->mask |= m; - } - break; - - case KDBUS_ITEM_CAPS: - m = (SD_BUS_CREDS_EFFECTIVE_CAPS | SD_BUS_CREDS_PERMITTED_CAPS | - SD_BUS_CREDS_INHERITABLE_CAPS | SD_BUS_CREDS_BOUNDING_CAPS) & mask; - - if (m) { - if (item->caps.last_cap != cap_last_cap() || - item->size - offsetof(struct kdbus_item, caps.caps) < DIV_ROUND_UP(item->caps.last_cap, 32U) * 4 * 4) - return -EBADMSG; - - c->capability = memdup(item->caps.caps, item->size - offsetof(struct kdbus_item, caps.caps)); - if (!c->capability) - return -ENOMEM; - - c->mask |= m; - } - break; - - case KDBUS_ITEM_SECLABEL: - if (mask & SD_BUS_CREDS_SELINUX_CONTEXT) { - r = free_and_strdup(&c->label, item->str); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; - } - break; - - case KDBUS_ITEM_AUDIT: - if (mask & SD_BUS_CREDS_AUDIT_SESSION_ID) { - c->audit_session_id = (uint32_t) item->audit.sessionid; - c->mask |= SD_BUS_CREDS_AUDIT_SESSION_ID; - } - - if (mask & SD_BUS_CREDS_AUDIT_LOGIN_UID) { - c->audit_login_uid = (uid_t) item->audit.loginuid; - c->mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID; - } - break; - - case KDBUS_ITEM_OWNED_NAME: - if ((mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) && service_name_is_valid(item->name.name)) { - r = strv_extend(&c->well_known_names, item->name.name); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES; - } - break; - - case KDBUS_ITEM_CONN_DESCRIPTION: - if (mask & SD_BUS_CREDS_DESCRIPTION) { - r = free_and_strdup(&c->description, item->str); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_DESCRIPTION; - } - break; - - case KDBUS_ITEM_AUXGROUPS: - if (mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { - size_t i, n; - uid_t *g; - - n = (item->size - offsetof(struct kdbus_item, data64)) / sizeof(uint64_t); - g = new(gid_t, n); - if (!g) - return -ENOMEM; - - for (i = 0; i < n; i++) - g[i] = item->data64[i]; - - free(c->supplementary_gids); - c->supplementary_gids = g; - c->n_supplementary_gids = n; - - c->mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS; - } - break; - } - } - - return 0; -} - -int bus_get_name_creds_kdbus( - sd_bus *bus, - const char *name, - uint64_t mask, - bool allow_activator, - sd_bus_creds **creds) { - - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; - struct kdbus_cmd_info *cmd; - struct kdbus_info *conn_info; - size_t size, l; - uint64_t id; - int r; - - if (streq(name, "org.freedesktop.DBus")) - return -EOPNOTSUPP; - - r = bus_kernel_parse_unique_name(name, &id); - if (r < 0) - return r; - if (r > 0) { - size = offsetof(struct kdbus_cmd_info, items); - cmd = alloca0_align(size, 8); - cmd->id = id; - } else { - l = strlen(name) + 1; - size = offsetof(struct kdbus_cmd_info, items) + KDBUS_ITEM_SIZE(l); - cmd = alloca0_align(size, 8); - cmd->items[0].size = KDBUS_ITEM_HEADER_SIZE + l; - cmd->items[0].type = KDBUS_ITEM_NAME; - memcpy(cmd->items[0].str, name, l); - } - - /* If augmentation is on, and the bus didn't provide us - * the bits we want, then ask for the PID/TID so that we - * can read the rest from /proc. */ - if ((mask & SD_BUS_CREDS_AUGMENT) && - (mask & (SD_BUS_CREDS_PPID| - SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| - SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| - SD_BUS_CREDS_SUPPLEMENTARY_GIDS| - SD_BUS_CREDS_COMM|SD_BUS_CREDS_TID_COMM|SD_BUS_CREDS_EXE|SD_BUS_CREDS_CMDLINE| - SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID| - SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS| - SD_BUS_CREDS_SELINUX_CONTEXT| - SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID))) - mask |= SD_BUS_CREDS_PID; - - cmd->size = size; - cmd->attach_flags = attach_flags_to_kdbus(mask); - - r = ioctl(bus->input_fd, KDBUS_CMD_CONN_INFO, cmd); - if (r < 0) - return -errno; - - conn_info = (struct kdbus_info *) ((uint8_t *) bus->kdbus_buffer + cmd->offset); - - /* Non-activated names are considered not available */ - if (!allow_activator && (conn_info->flags & KDBUS_HELLO_ACTIVATOR)) { - if (name[0] == ':') - r = -ENXIO; - else - r = -ESRCH; - goto fail; - } - - c = bus_creds_new(); - if (!c) { - r = -ENOMEM; - goto fail; - } - - if (mask & SD_BUS_CREDS_UNIQUE_NAME) { - if (asprintf(&c->unique_name, ":1.%llu", (unsigned long long) conn_info->id) < 0) { - r = -ENOMEM; - goto fail; - } - - c->mask |= SD_BUS_CREDS_UNIQUE_NAME; - } - - /* If KDBUS_ITEM_OWNED_NAME is requested then we'll get 0 of - them in case the service has no names. This does not mean - however that the list of owned names could not be - acquired. Hence, let's explicitly clarify that the data is - complete. */ - c->mask |= mask & SD_BUS_CREDS_WELL_KNOWN_NAMES; - - r = bus_populate_creds_from_items(bus, conn_info, mask, c); - if (r < 0) - goto fail; - - r = bus_creds_add_more(c, mask, 0, 0); - if (r < 0) - goto fail; - - if (creds) { - *creds = c; - c = NULL; - } - - r = 0; - -fail: - bus_kernel_cmd_free(bus, cmd->offset); - return r; -} - -static int bus_get_name_creds_dbus1( - sd_bus *bus, - const char *name, - uint64_t mask, - sd_bus_creds **creds) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply_unique = NULL, *reply = NULL; - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; - const char *unique = NULL; - pid_t pid = 0; - int r; - - /* Only query the owner if the caller wants to know it or if - * the caller just wants to check whether a name exists */ - if ((mask & SD_BUS_CREDS_UNIQUE_NAME) || mask == 0) { - r = sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "GetNameOwner", - NULL, - &reply_unique, - "s", - name); - if (r < 0) - return r; - - r = sd_bus_message_read(reply_unique, "s", &unique); - if (r < 0) - return r; - } - - if (mask != 0) { - c = bus_creds_new(); - if (!c) - return -ENOMEM; - - if ((mask & SD_BUS_CREDS_UNIQUE_NAME) && unique) { - c->unique_name = strdup(unique); - if (!c->unique_name) - return -ENOMEM; - - c->mask |= SD_BUS_CREDS_UNIQUE_NAME; - } - - if ((mask & SD_BUS_CREDS_PID) || - ((mask & SD_BUS_CREDS_AUGMENT) && - (mask & (SD_BUS_CREDS_UID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| - SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| - SD_BUS_CREDS_SUPPLEMENTARY_GIDS| - SD_BUS_CREDS_COMM|SD_BUS_CREDS_EXE|SD_BUS_CREDS_CMDLINE| - SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID| - SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS| - SD_BUS_CREDS_SELINUX_CONTEXT| - SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID)))) { - - uint32_t u; - - r = sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "GetConnectionUnixProcessID", - NULL, - &reply, - "s", - unique ? unique : name); - if (r < 0) - return r; - - r = sd_bus_message_read(reply, "u", &u); - if (r < 0) - return r; - - pid = u; - if (mask & SD_BUS_CREDS_PID) { - c->pid = u; - c->mask |= SD_BUS_CREDS_PID; - } - - reply = sd_bus_message_unref(reply); - } - - if (mask & SD_BUS_CREDS_EUID) { - uint32_t u; - - r = sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "GetConnectionUnixUser", - NULL, - &reply, - "s", - unique ? unique : name); - if (r < 0) - return r; - - r = sd_bus_message_read(reply, "u", &u); - if (r < 0) - return r; - - c->euid = u; - c->mask |= SD_BUS_CREDS_EUID; - - reply = sd_bus_message_unref(reply); - } - - if (mask & SD_BUS_CREDS_SELINUX_CONTEXT) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const void *p = NULL; - size_t sz = 0; - - r = sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "GetConnectionSELinuxSecurityContext", - &error, - &reply, - "s", - unique ? unique : name); - if (r < 0) { - if (!sd_bus_error_has_name(&error, "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown")) - return r; - } else { - r = sd_bus_message_read_array(reply, 'y', &p, &sz); - if (r < 0) - return r; - - c->label = strndup(p, sz); - if (!c->label) - return -ENOMEM; - - c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; - } - } - - r = bus_creds_add_more(c, mask, pid, 0); - if (r < 0) - return r; - } - - if (creds) { - *creds = c; - c = NULL; - } - - return 0; -} - -_public_ int sd_bus_get_name_creds( - sd_bus *bus, - const char *name, - uint64_t mask, - sd_bus_creds **creds) { - - assert_return(bus, -EINVAL); - assert_return(name, -EINVAL); - assert_return((mask & ~SD_BUS_CREDS_AUGMENT) <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP); - assert_return(mask == 0 || creds, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - assert_return(service_name_is_valid(name), -EINVAL); - - if (!bus->bus_client) - return -EINVAL; - - if (streq(name, "org.freedesktop.DBus.Local")) - return -EINVAL; - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (bus->is_kernel) - return bus_get_name_creds_kdbus(bus, name, mask, false, creds); - else - return bus_get_name_creds_dbus1(bus, name, mask, creds); -} - -static int bus_get_owner_creds_kdbus(sd_bus *bus, uint64_t mask, sd_bus_creds **ret) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; - struct kdbus_cmd_info cmd = { - .size = sizeof(struct kdbus_cmd_info), - }; - struct kdbus_info *creator_info; - pid_t pid = 0; - int r; - - c = bus_creds_new(); - if (!c) - return -ENOMEM; - - /* If augmentation is on, and the bus doesn't didn't allow us - * to get the bits we want, then ask for the PID/TID so that we - * can read the rest from /proc. */ - if ((mask & SD_BUS_CREDS_AUGMENT) && - (mask & (SD_BUS_CREDS_PPID| - SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| - SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| - SD_BUS_CREDS_SUPPLEMENTARY_GIDS| - SD_BUS_CREDS_COMM|SD_BUS_CREDS_TID_COMM|SD_BUS_CREDS_EXE|SD_BUS_CREDS_CMDLINE| - SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID| - SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS| - SD_BUS_CREDS_SELINUX_CONTEXT| - SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID))) - mask |= SD_BUS_CREDS_PID; - - cmd.attach_flags = attach_flags_to_kdbus(mask); - - r = ioctl(bus->input_fd, KDBUS_CMD_BUS_CREATOR_INFO, &cmd); - if (r < 0) - return -errno; - - creator_info = (struct kdbus_info *) ((uint8_t *) bus->kdbus_buffer + cmd.offset); - - r = bus_populate_creds_from_items(bus, creator_info, mask, c); - bus_kernel_cmd_free(bus, cmd.offset); - if (r < 0) - return r; - - r = bus_creds_add_more(c, mask, pid, 0); - if (r < 0) - return r; - - *ret = c; - c = NULL; - return 0; -} - -static int bus_get_owner_creds_dbus1(sd_bus *bus, uint64_t mask, sd_bus_creds **ret) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; - pid_t pid = 0; - bool do_label; - int r; - - assert(bus); - - do_label = bus->label && (mask & SD_BUS_CREDS_SELINUX_CONTEXT); - - /* Avoid allocating anything if we have no chance of returning useful data */ - if (!bus->ucred_valid && !do_label) - return -ENODATA; - - c = bus_creds_new(); - if (!c) - return -ENOMEM; - - if (bus->ucred_valid) { - if (bus->ucred.pid > 0) { - pid = c->pid = bus->ucred.pid; - c->mask |= SD_BUS_CREDS_PID & mask; - } - - if (bus->ucred.uid != UID_INVALID) { - c->euid = bus->ucred.uid; - c->mask |= SD_BUS_CREDS_EUID & mask; - } - - if (bus->ucred.gid != GID_INVALID) { - c->egid = bus->ucred.gid; - c->mask |= SD_BUS_CREDS_EGID & mask; - } - } - - if (do_label) { - c->label = strdup(bus->label); - if (!c->label) - return -ENOMEM; - - c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; - } - - r = bus_creds_add_more(c, mask, pid, 0); - if (r < 0) - return r; - - *ret = c; - c = NULL; - return 0; -} - -_public_ int sd_bus_get_owner_creds(sd_bus *bus, uint64_t mask, sd_bus_creds **ret) { - assert_return(bus, -EINVAL); - assert_return((mask & ~SD_BUS_CREDS_AUGMENT) <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP); - assert_return(ret, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (bus->is_kernel) - return bus_get_owner_creds_kdbus(bus, mask, ret); - else - return bus_get_owner_creds_dbus1(bus, mask, ret); -} - -static int add_name_change_match(sd_bus *bus, - uint64_t cookie, - const char *name, - const char *old_owner, - const char *new_owner) { - - uint64_t name_id = KDBUS_MATCH_ID_ANY, old_owner_id = 0, new_owner_id = 0; - int is_name_id = -1, r; - struct kdbus_item *item; - - assert(bus); - - /* If we encounter a match that could match against - * NameOwnerChanged messages, then we need to create - * KDBUS_ITEM_NAME_{ADD,REMOVE,CHANGE} and - * KDBUS_ITEM_ID_{ADD,REMOVE} matches for it, possibly - * multiple if the match is underspecified. - * - * The NameOwnerChanged signals take three parameters with - * unique or well-known names, but only some forms actually - * exist: - * - * WELLKNOWN, "", UNIQUE → KDBUS_ITEM_NAME_ADD - * WELLKNOWN, UNIQUE, "" → KDBUS_ITEM_NAME_REMOVE - * WELLKNOWN, UNIQUE, UNIQUE → KDBUS_ITEM_NAME_CHANGE - * UNIQUE, "", UNIQUE → KDBUS_ITEM_ID_ADD - * UNIQUE, UNIQUE, "" → KDBUS_ITEM_ID_REMOVE - * - * For the latter two the two unique names must be identical. - * - * */ - - if (name) { - is_name_id = bus_kernel_parse_unique_name(name, &name_id); - if (is_name_id < 0) - return 0; - } - - if (!isempty(old_owner)) { - r = bus_kernel_parse_unique_name(old_owner, &old_owner_id); - if (r < 0) - return 0; - if (r == 0) - return 0; - if (is_name_id > 0 && old_owner_id != name_id) - return 0; - } else - old_owner_id = KDBUS_MATCH_ID_ANY; - - if (!isempty(new_owner)) { - r = bus_kernel_parse_unique_name(new_owner, &new_owner_id); - if (r < 0) - return r; - if (r == 0) - return 0; - if (is_name_id > 0 && new_owner_id != name_id) - return 0; - } else - new_owner_id = KDBUS_MATCH_ID_ANY; - - if (is_name_id <= 0) { - struct kdbus_cmd_match *m; - size_t sz, l; - - /* If the name argument is missing or is a well-known - * name, then add KDBUS_ITEM_NAME_{ADD,REMOVE,CHANGE} - * matches for it */ - - l = name ? strlen(name) + 1 : 0; - - sz = ALIGN8(offsetof(struct kdbus_cmd_match, items) + - offsetof(struct kdbus_item, name_change) + - offsetof(struct kdbus_notify_name_change, name) + - l); - - m = alloca0_align(sz, 8); - m->size = sz; - m->cookie = cookie; - - item = m->items; - item->size = - offsetof(struct kdbus_item, name_change) + - offsetof(struct kdbus_notify_name_change, name) + - l; - - item->name_change.old_id.id = old_owner_id; - item->name_change.new_id.id = new_owner_id; - - memcpy_safe(item->name_change.name, name, l); - - /* If the old name is unset or empty, then - * this can match against added names */ - if (isempty(old_owner)) { - item->type = KDBUS_ITEM_NAME_ADD; - - r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); - if (r < 0) - return -errno; - } - - /* If the new name is unset or empty, then - * this can match against removed names */ - if (isempty(new_owner)) { - item->type = KDBUS_ITEM_NAME_REMOVE; - - r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); - if (r < 0) - return -errno; - } - - /* The CHANGE match we need in either case, because - * what is reported as a name change by the kernel - * might just be an owner change between starter and - * normal clients. For userspace such a change should - * be considered a removal/addition, hence let's - * subscribe to this unconditionally. */ - item->type = KDBUS_ITEM_NAME_CHANGE; - r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); - if (r < 0) - return -errno; - } - - if (is_name_id != 0) { - struct kdbus_cmd_match *m; - uint64_t sz; - - /* If the name argument is missing or is a unique - * name, then add KDBUS_ITEM_ID_{ADD,REMOVE} matches - * for it */ - - sz = ALIGN8(offsetof(struct kdbus_cmd_match, items) + - offsetof(struct kdbus_item, id_change) + - sizeof(struct kdbus_notify_id_change)); - - m = alloca0_align(sz, 8); - m->size = sz; - m->cookie = cookie; - - item = m->items; - item->size = - offsetof(struct kdbus_item, id_change) + - sizeof(struct kdbus_notify_id_change); - item->id_change.id = name_id; - - /* If the old name is unset or empty, then this can - * match against added ids */ - if (isempty(old_owner)) { - item->type = KDBUS_ITEM_ID_ADD; - if (!isempty(new_owner)) - item->id_change.id = new_owner_id; - - r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); - if (r < 0) - return -errno; - } - - /* If thew new name is unset or empty, then this can - * match against removed ids */ - if (isempty(new_owner)) { - item->type = KDBUS_ITEM_ID_REMOVE; - if (!isempty(old_owner)) - item->id_change.id = old_owner_id; - - r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); - if (r < 0) - return -errno; - } - } - - return 0; -} - -int bus_add_match_internal_kernel( - sd_bus *bus, - struct bus_match_component *components, - unsigned n_components, - uint64_t cookie) { - - struct kdbus_cmd_match *m; - struct kdbus_item *item; - uint64_t *bloom; - size_t sz; - const char *sender = NULL; - size_t sender_length = 0; - uint64_t src_id = KDBUS_MATCH_ID_ANY, dst_id = KDBUS_MATCH_ID_ANY; - bool using_bloom = false; - unsigned i; - bool matches_name_change = true; - const char *name_change_arg[3] = {}; - int r; - - assert(bus); - - /* Monitor streams don't support matches, make this a NOP */ - if (bus->hello_flags & KDBUS_HELLO_MONITOR) - return 0; - - bloom = alloca0(bus->bloom_size); - - sz = ALIGN8(offsetof(struct kdbus_cmd_match, items)); - - for (i = 0; i < n_components; i++) { - struct bus_match_component *c = &components[i]; - - switch (c->type) { - - case BUS_MATCH_SENDER: - if (!streq(c->value_str, "org.freedesktop.DBus")) - matches_name_change = false; - - r = bus_kernel_parse_unique_name(c->value_str, &src_id); - if (r < 0) - return r; - else if (r > 0) - sz += ALIGN8(offsetof(struct kdbus_item, id) + sizeof(uint64_t)); - else { - sender = c->value_str; - sender_length = strlen(sender); - sz += ALIGN8(offsetof(struct kdbus_item, str) + sender_length + 1); - } - - break; - - case BUS_MATCH_MESSAGE_TYPE: - if (c->value_u8 != SD_BUS_MESSAGE_SIGNAL) - matches_name_change = false; - - bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "message-type", bus_message_type_to_string(c->value_u8)); - using_bloom = true; - break; - - case BUS_MATCH_INTERFACE: - if (!streq(c->value_str, "org.freedesktop.DBus")) - matches_name_change = false; - - bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "interface", c->value_str); - using_bloom = true; - break; - - case BUS_MATCH_MEMBER: - if (!streq(c->value_str, "NameOwnerChanged")) - matches_name_change = false; - - bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "member", c->value_str); - using_bloom = true; - break; - - case BUS_MATCH_PATH: - if (!streq(c->value_str, "/org/freedesktop/DBus")) - matches_name_change = false; - - bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "path", c->value_str); - using_bloom = true; - break; - - case BUS_MATCH_PATH_NAMESPACE: - bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "path-slash-prefix", c->value_str); - using_bloom = true; - break; - - case BUS_MATCH_ARG...BUS_MATCH_ARG_LAST: { - char buf[sizeof("arg")-1 + 2 + 1]; - - if (c->type - BUS_MATCH_ARG < 3) - name_change_arg[c->type - BUS_MATCH_ARG] = c->value_str; - - xsprintf(buf, "arg%i", c->type - BUS_MATCH_ARG); - bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, buf, c->value_str); - using_bloom = true; - break; - } - - case BUS_MATCH_ARG_HAS...BUS_MATCH_ARG_HAS_LAST: { - char buf[sizeof("arg")-1 + 2 + sizeof("-has")]; - - xsprintf(buf, "arg%i-has", c->type - BUS_MATCH_ARG_HAS); - bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, buf, c->value_str); - using_bloom = true; - break; - } - - case BUS_MATCH_ARG_PATH...BUS_MATCH_ARG_PATH_LAST: - /* - * XXX: DBus spec defines arg[0..63]path= matching to be - * a two-way glob. That is, if either string is a prefix - * of the other, it matches. - * This is really hard to realize in bloom-filters, as - * we would have to create a bloom-match for each prefix - * of @c->value_str. This is excessive, hence we just - * ignore all those matches and accept everything from - * the kernel. People should really avoid those matches. - * If they're used in real-life some day, we will have - * to properly support multiple-matches here. - */ - break; - - case BUS_MATCH_ARG_NAMESPACE...BUS_MATCH_ARG_NAMESPACE_LAST: { - char buf[sizeof("arg")-1 + 2 + sizeof("-dot-prefix")]; - - xsprintf(buf, "arg%i-dot-prefix", c->type - BUS_MATCH_ARG_NAMESPACE); - bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, buf, c->value_str); - using_bloom = true; - break; - } - - case BUS_MATCH_DESTINATION: - /* - * Kernel only supports matching on destination IDs, but - * not on destination names. So just skip the - * destination name restriction and verify it in - * user-space on retrieval. - */ - r = bus_kernel_parse_unique_name(c->value_str, &dst_id); - if (r < 0) - return r; - else if (r > 0) - sz += ALIGN8(offsetof(struct kdbus_item, id) + sizeof(uint64_t)); - - /* if not a broadcast, it cannot be a name-change */ - if (r <= 0 || dst_id != KDBUS_DST_ID_BROADCAST) - matches_name_change = false; - - break; - - case BUS_MATCH_ROOT: - case BUS_MATCH_VALUE: - case BUS_MATCH_LEAF: - case _BUS_MATCH_NODE_TYPE_MAX: - case _BUS_MATCH_NODE_TYPE_INVALID: - assert_not_reached("Invalid match type?"); - } - } - - if (using_bloom) - sz += ALIGN8(offsetof(struct kdbus_item, data64) + bus->bloom_size); - - m = alloca0_align(sz, 8); - m->size = sz; - m->cookie = cookie; - - item = m->items; - - if (src_id != KDBUS_MATCH_ID_ANY) { - item->size = offsetof(struct kdbus_item, id) + sizeof(uint64_t); - item->type = KDBUS_ITEM_ID; - item->id = src_id; - item = KDBUS_ITEM_NEXT(item); - } - - if (dst_id != KDBUS_MATCH_ID_ANY) { - item->size = offsetof(struct kdbus_item, id) + sizeof(uint64_t); - item->type = KDBUS_ITEM_DST_ID; - item->id = dst_id; - item = KDBUS_ITEM_NEXT(item); - } - - if (using_bloom) { - item->size = offsetof(struct kdbus_item, data64) + bus->bloom_size; - item->type = KDBUS_ITEM_BLOOM_MASK; - memcpy(item->data64, bloom, bus->bloom_size); - item = KDBUS_ITEM_NEXT(item); - } - - if (sender) { - item->size = offsetof(struct kdbus_item, str) + sender_length + 1; - item->type = KDBUS_ITEM_NAME; - memcpy(item->str, sender, sender_length + 1); - } - - r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); - if (r < 0) - return -errno; - - if (matches_name_change) { - - /* If this match could theoretically match - * NameOwnerChanged messages, we need to - * install a second non-bloom filter explitly - * for it */ - - r = add_name_change_match(bus, cookie, name_change_arg[0], name_change_arg[1], name_change_arg[2]); - if (r < 0) - return r; - } - - return 0; -} - -#define internal_match(bus, m) \ - ((bus)->hello_flags & KDBUS_HELLO_MONITOR \ - ? (isempty(m) ? "eavesdrop='true'" : strjoina((m), ",eavesdrop='true'")) \ - : (m)) - -static int bus_add_match_internal_dbus1( - sd_bus *bus, - const char *match) { - - const char *e; - - assert(bus); - assert(match); - - e = internal_match(bus, match); - - return sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "AddMatch", - NULL, - NULL, - "s", - e); -} - -int bus_add_match_internal( - sd_bus *bus, - const char *match, - struct bus_match_component *components, - unsigned n_components, - uint64_t cookie) { - - assert(bus); - - if (!bus->bus_client) - return -EINVAL; - - if (bus->is_kernel) - return bus_add_match_internal_kernel(bus, components, n_components, cookie); - else - return bus_add_match_internal_dbus1(bus, match); -} - -int bus_remove_match_internal_kernel( - sd_bus *bus, - uint64_t cookie) { - - struct kdbus_cmd_match m = { - .size = offsetof(struct kdbus_cmd_match, items), - .cookie = cookie, - }; - int r; - - assert(bus); - - /* Monitor streams don't support matches, make this a NOP */ - if (bus->hello_flags & KDBUS_HELLO_MONITOR) - return 0; - - r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_REMOVE, &m); - if (r < 0) - return -errno; - - return 0; -} - -static int bus_remove_match_internal_dbus1( - sd_bus *bus, - const char *match) { - - const char *e; - - assert(bus); - assert(match); - - e = internal_match(bus, match); - - return sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "RemoveMatch", - NULL, - NULL, - "s", - e); -} - -int bus_remove_match_internal( - sd_bus *bus, - const char *match, - uint64_t cookie) { - - assert(bus); - - if (!bus->bus_client) - return -EINVAL; - - if (bus->is_kernel) - return bus_remove_match_internal_kernel(bus, cookie); - else - return bus_remove_match_internal_dbus1(bus, match); -} - -_public_ int sd_bus_get_name_machine_id(sd_bus *bus, const char *name, sd_id128_t *machine) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; - const char *mid; - int r; - - assert_return(bus, -EINVAL); - assert_return(name, -EINVAL); - assert_return(machine, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - assert_return(service_name_is_valid(name), -EINVAL); - - if (!bus->bus_client) - return -EINVAL; - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (streq_ptr(name, bus->unique_name)) - return sd_id128_get_machine(machine); - - r = sd_bus_message_new_method_call( - bus, - &m, - name, - "/", - "org.freedesktop.DBus.Peer", - "GetMachineId"); - if (r < 0) - return r; - - r = sd_bus_message_set_auto_start(m, false); - if (r < 0) - return r; - - r = sd_bus_call(bus, m, 0, NULL, &reply); - if (r < 0) - return r; - - r = sd_bus_message_read(reply, "s", &mid); - if (r < 0) - return r; - - return sd_id128_from_string(mid, machine); -} diff --git a/src/libsystemd/sd-bus/bus-control.h b/src/libsystemd/sd-bus/bus-control.h deleted file mode 100644 index c181aa7959..0000000000 --- a/src/libsystemd/sd-bus/bus-control.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -#include "bus-match.h" - -int bus_add_match_internal(sd_bus *bus, const char *match, struct bus_match_component *components, unsigned n_components, uint64_t cookie); -int bus_remove_match_internal(sd_bus *bus, const char *match, uint64_t cookie); - -int bus_add_match_internal_kernel(sd_bus *bus, struct bus_match_component *components, unsigned n_components, uint64_t cookie); -int bus_remove_match_internal_kernel(sd_bus *bus, uint64_t cookie); - -int bus_get_name_creds_kdbus(sd_bus *bus, const char *name, uint64_t mask, bool allow_activator, sd_bus_creds **creds); diff --git a/src/libsystemd/sd-bus/bus-convenience.c b/src/libsystemd/sd-bus/bus-convenience.c deleted file mode 100644 index 2d06bf541f..0000000000 --- a/src/libsystemd/sd-bus/bus-convenience.c +++ /dev/null @@ -1,626 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "bus-internal.h" -#include "bus-message.h" -#include "bus-signature.h" -#include "bus-type.h" -#include "bus-util.h" -#include "string-util.h" - -_public_ int sd_bus_emit_signal( - sd_bus *bus, - const char *path, - const char *interface, - const char *member, - const char *types, ...) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - r = sd_bus_message_new_signal(bus, &m, path, interface, member); - if (r < 0) - return r; - - if (!isempty(types)) { - va_list ap; - - va_start(ap, types); - r = bus_message_append_ap(m, types, ap); - va_end(ap); - if (r < 0) - return r; - } - - return sd_bus_send(bus, m, NULL); -} - -_public_ int sd_bus_call_method_async( - sd_bus *bus, - sd_bus_slot **slot, - const char *destination, - const char *path, - const char *interface, - const char *member, - sd_bus_message_handler_t callback, - void *userdata, - const char *types, ...) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - r = sd_bus_message_new_method_call(bus, &m, destination, path, interface, member); - if (r < 0) - return r; - - if (!isempty(types)) { - va_list ap; - - va_start(ap, types); - r = bus_message_append_ap(m, types, ap); - va_end(ap); - if (r < 0) - return r; - } - - return sd_bus_call_async(bus, slot, m, callback, userdata, 0); -} - -_public_ int sd_bus_call_method( - sd_bus *bus, - const char *destination, - const char *path, - const char *interface, - const char *member, - sd_bus_error *error, - sd_bus_message **reply, - const char *types, ...) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - bus_assert_return(bus, -EINVAL, error); - bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); - - if (!BUS_IS_OPEN(bus->state)) { - r = -ENOTCONN; - goto fail; - } - - r = sd_bus_message_new_method_call(bus, &m, destination, path, interface, member); - if (r < 0) - goto fail; - - if (!isempty(types)) { - va_list ap; - - va_start(ap, types); - r = bus_message_append_ap(m, types, ap); - va_end(ap); - if (r < 0) - goto fail; - } - - return sd_bus_call(bus, m, 0, error, reply); - -fail: - return sd_bus_error_set_errno(error, r); -} - -_public_ int sd_bus_reply_method_return( - sd_bus_message *call, - const char *types, ...) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - assert_return(call, -EINVAL); - assert_return(call->sealed, -EPERM); - assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); - assert_return(call->bus, -EINVAL); - assert_return(!bus_pid_changed(call->bus), -ECHILD); - - if (!BUS_IS_OPEN(call->bus->state)) - return -ENOTCONN; - - if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) - return 0; - - r = sd_bus_message_new_method_return(call, &m); - if (r < 0) - return r; - - if (!isempty(types)) { - va_list ap; - - va_start(ap, types); - r = bus_message_append_ap(m, types, ap); - va_end(ap); - if (r < 0) - return r; - } - - return sd_bus_send(call->bus, m, NULL); -} - -_public_ int sd_bus_reply_method_error( - sd_bus_message *call, - const sd_bus_error *e) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - assert_return(call, -EINVAL); - assert_return(call->sealed, -EPERM); - assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); - assert_return(sd_bus_error_is_set(e), -EINVAL); - assert_return(call->bus, -EINVAL); - assert_return(!bus_pid_changed(call->bus), -ECHILD); - - if (!BUS_IS_OPEN(call->bus->state)) - return -ENOTCONN; - - if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) - return 0; - - r = sd_bus_message_new_method_error(call, &m, e); - if (r < 0) - return r; - - return sd_bus_send(call->bus, m, NULL); -} - -_public_ int sd_bus_reply_method_errorf( - sd_bus_message *call, - const char *name, - const char *format, - ...) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - va_list ap; - - assert_return(call, -EINVAL); - assert_return(call->sealed, -EPERM); - assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); - assert_return(call->bus, -EINVAL); - assert_return(!bus_pid_changed(call->bus), -ECHILD); - - if (!BUS_IS_OPEN(call->bus->state)) - return -ENOTCONN; - - if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) - return 0; - - va_start(ap, format); - bus_error_setfv(&error, name, format, ap); - va_end(ap); - - return sd_bus_reply_method_error(call, &error); -} - -_public_ int sd_bus_reply_method_errno( - sd_bus_message *call, - int error, - const sd_bus_error *p) { - - _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL; - - assert_return(call, -EINVAL); - assert_return(call->sealed, -EPERM); - assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); - assert_return(call->bus, -EINVAL); - assert_return(!bus_pid_changed(call->bus), -ECHILD); - - if (!BUS_IS_OPEN(call->bus->state)) - return -ENOTCONN; - - if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) - return 0; - - if (sd_bus_error_is_set(p)) - return sd_bus_reply_method_error(call, p); - - sd_bus_error_set_errno(&berror, error); - - return sd_bus_reply_method_error(call, &berror); -} - -_public_ int sd_bus_reply_method_errnof( - sd_bus_message *call, - int error, - const char *format, - ...) { - - _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL; - va_list ap; - - assert_return(call, -EINVAL); - assert_return(call->sealed, -EPERM); - assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); - assert_return(call->bus, -EINVAL); - assert_return(!bus_pid_changed(call->bus), -ECHILD); - - if (!BUS_IS_OPEN(call->bus->state)) - return -ENOTCONN; - - if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) - return 0; - - va_start(ap, format); - sd_bus_error_set_errnofv(&berror, error, format, ap); - va_end(ap); - - return sd_bus_reply_method_error(call, &berror); -} - -_public_ int sd_bus_get_property( - sd_bus *bus, - const char *destination, - const char *path, - const char *interface, - const char *member, - sd_bus_error *error, - sd_bus_message **reply, - const char *type) { - - sd_bus_message *rep = NULL; - int r; - - bus_assert_return(bus, -EINVAL, error); - bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); - bus_assert_return(member_name_is_valid(member), -EINVAL, error); - bus_assert_return(reply, -EINVAL, error); - bus_assert_return(signature_is_single(type, false), -EINVAL, error); - bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); - - if (!BUS_IS_OPEN(bus->state)) { - r = -ENOTCONN; - goto fail; - } - - r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &rep, "ss", strempty(interface), member); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(rep, 'v', type); - if (r < 0) { - sd_bus_message_unref(rep); - goto fail; - } - - *reply = rep; - return 0; - -fail: - return sd_bus_error_set_errno(error, r); -} - -_public_ int sd_bus_get_property_trivial( - sd_bus *bus, - const char *destination, - const char *path, - const char *interface, - const char *member, - sd_bus_error *error, - char type, void *ptr) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int r; - - bus_assert_return(bus, -EINVAL, error); - bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); - bus_assert_return(member_name_is_valid(member), -EINVAL, error); - bus_assert_return(bus_type_is_trivial(type), -EINVAL, error); - bus_assert_return(ptr, -EINVAL, error); - bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); - - if (!BUS_IS_OPEN(bus->state)) { - r = -ENOTCONN; - goto fail; - } - - r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &reply, "ss", strempty(interface), member); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(reply, 'v', CHAR_TO_STR(type)); - if (r < 0) - goto fail; - - r = sd_bus_message_read_basic(reply, type, ptr); - if (r < 0) - goto fail; - - return 0; - -fail: - return sd_bus_error_set_errno(error, r); -} - -_public_ int sd_bus_get_property_string( - sd_bus *bus, - const char *destination, - const char *path, - const char *interface, - const char *member, - sd_bus_error *error, - char **ret) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *s; - char *n; - int r; - - bus_assert_return(bus, -EINVAL, error); - bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); - bus_assert_return(member_name_is_valid(member), -EINVAL, error); - bus_assert_return(ret, -EINVAL, error); - bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); - - if (!BUS_IS_OPEN(bus->state)) { - r = -ENOTCONN; - goto fail; - } - - r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &reply, "ss", strempty(interface), member); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(reply, 'v', "s"); - if (r < 0) - goto fail; - - r = sd_bus_message_read_basic(reply, 's', &s); - if (r < 0) - goto fail; - - n = strdup(s); - if (!n) { - r = -ENOMEM; - goto fail; - } - - *ret = n; - return 0; - -fail: - return sd_bus_error_set_errno(error, r); -} - -_public_ int sd_bus_get_property_strv( - sd_bus *bus, - const char *destination, - const char *path, - const char *interface, - const char *member, - sd_bus_error *error, - char ***ret) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int r; - - bus_assert_return(bus, -EINVAL, error); - bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); - bus_assert_return(member_name_is_valid(member), -EINVAL, error); - bus_assert_return(ret, -EINVAL, error); - bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); - - if (!BUS_IS_OPEN(bus->state)) { - r = -ENOTCONN; - goto fail; - } - - r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &reply, "ss", strempty(interface), member); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(reply, 'v', NULL); - if (r < 0) - goto fail; - - r = sd_bus_message_read_strv(reply, ret); - if (r < 0) - goto fail; - - return 0; - -fail: - return sd_bus_error_set_errno(error, r); -} - -_public_ int sd_bus_set_property( - sd_bus *bus, - const char *destination, - const char *path, - const char *interface, - const char *member, - sd_bus_error *error, - const char *type, ...) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - va_list ap; - int r; - - bus_assert_return(bus, -EINVAL, error); - bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); - bus_assert_return(member_name_is_valid(member), -EINVAL, error); - bus_assert_return(signature_is_single(type, false), -EINVAL, error); - bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); - - if (!BUS_IS_OPEN(bus->state)) { - r = -ENOTCONN; - goto fail; - } - - r = sd_bus_message_new_method_call(bus, &m, destination, path, "org.freedesktop.DBus.Properties", "Set"); - if (r < 0) - goto fail; - - r = sd_bus_message_append(m, "ss", strempty(interface), member); - if (r < 0) - goto fail; - - r = sd_bus_message_open_container(m, 'v', type); - if (r < 0) - goto fail; - - va_start(ap, type); - r = bus_message_append_ap(m, type, ap); - va_end(ap); - if (r < 0) - goto fail; - - r = sd_bus_message_close_container(m); - if (r < 0) - goto fail; - - return sd_bus_call(bus, m, 0, error, NULL); - -fail: - return sd_bus_error_set_errno(error, r); -} - -_public_ int sd_bus_query_sender_creds(sd_bus_message *call, uint64_t mask, sd_bus_creds **creds) { - sd_bus_creds *c; - - assert_return(call, -EINVAL); - assert_return(call->sealed, -EPERM); - assert_return(call->bus, -EINVAL); - assert_return(!bus_pid_changed(call->bus), -ECHILD); - - if (!BUS_IS_OPEN(call->bus->state)) - return -ENOTCONN; - - c = sd_bus_message_get_creds(call); - - /* All data we need? */ - if (c && (mask & ~c->mask) == 0) { - *creds = sd_bus_creds_ref(c); - return 0; - } - - /* No data passed? Or not enough data passed to retrieve the missing bits? */ - if (!c || !(c->mask & SD_BUS_CREDS_PID)) { - /* We couldn't read anything from the call, let's try - * to get it from the sender or peer. */ - - if (call->sender) - /* There's a sender, but the creds are - * missing. This means we are talking via - * dbus1, or are getting a message that was - * sent to us via kdbus, but was converted - * from a dbus1 message by the bus-proxy and - * thus also lacks the creds. */ - return sd_bus_get_name_creds(call->bus, call->sender, mask, creds); - else - /* There's no sender, hence we are on a dbus1 - * direct connection. For direct connections - * the credentials of the AF_UNIX peer matter, - * which may be queried via - * sd_bus_get_owner_creds(). */ - return sd_bus_get_owner_creds(call->bus, mask, creds); - } - - return bus_creds_extend_by_pid(c, mask, creds); -} - -_public_ int sd_bus_query_sender_privilege(sd_bus_message *call, int capability) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - uid_t our_uid; - bool know_caps = false; - int r; - - assert_return(call, -EINVAL); - assert_return(call->sealed, -EPERM); - assert_return(call->bus, -EINVAL); - assert_return(!bus_pid_changed(call->bus), -ECHILD); - - if (!BUS_IS_OPEN(call->bus->state)) - return -ENOTCONN; - - if (capability >= 0) { - - r = sd_bus_query_sender_creds(call, SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_EFFECTIVE_CAPS, &creds); - if (r < 0) - return r; - - /* We cannot use augmented caps for authorization, - * since then data is acquired raceful from - * /proc. This can never actually happen, but let's - * better be safe than sorry, and do an extra check - * here. */ - assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_EFFECTIVE_CAPS) == 0, -EPERM); - - /* Note that not even on kdbus we might have the caps - * field, due to faked identities, or namespace - * translation issues. */ - r = sd_bus_creds_has_effective_cap(creds, capability); - if (r > 0) - return 1; - if (r == 0) - know_caps = true; - } else { - r = sd_bus_query_sender_creds(call, SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID, &creds); - if (r < 0) - return r; - } - - /* Now, check the UID, but only if the capability check wasn't - * sufficient */ - our_uid = getuid(); - if (our_uid != 0 || !know_caps || capability < 0) { - uid_t sender_uid; - - /* We cannot use augmented uid/euid for authorization, - * since then data is acquired raceful from - * /proc. This can never actually happen, but let's - * better be safe than sorry, and do an extra check - * here. */ - assert_return((sd_bus_creds_get_augmented_mask(creds) & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID)) == 0, -EPERM); - - /* Try to use the EUID, if we have it. */ - r = sd_bus_creds_get_euid(creds, &sender_uid); - if (r < 0) - r = sd_bus_creds_get_uid(creds, &sender_uid); - - if (r >= 0) { - /* Sender has same UID as us, then let's grant access */ - if (sender_uid == our_uid) - return 1; - - /* Sender is root, we are not root. */ - if (our_uid != 0 && sender_uid == 0) - return 1; - } - } - - return 0; -} diff --git a/src/libsystemd/sd-bus/bus-creds.c b/src/libsystemd/sd-bus/bus-creds.c deleted file mode 100644 index c4f693dee9..0000000000 --- a/src/libsystemd/sd-bus/bus-creds.c +++ /dev/null @@ -1,1349 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 - -#include "alloc-util.h" -#include "audit-util.h" -#include "bus-creds.h" -#include "bus-label.h" -#include "bus-message.h" -#include "bus-util.h" -#include "capability-util.h" -#include "cgroup-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "hexdecoct.h" -#include "parse-util.h" -#include "process-util.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "user-util.h" -#include "util.h" - -enum { - CAP_OFFSET_INHERITABLE = 0, - CAP_OFFSET_PERMITTED = 1, - CAP_OFFSET_EFFECTIVE = 2, - CAP_OFFSET_BOUNDING = 3 -}; - -void bus_creds_done(sd_bus_creds *c) { - assert(c); - - /* For internal bus cred structures that are allocated by - * something else */ - - free(c->session); - free(c->unit); - free(c->user_unit); - free(c->slice); - free(c->user_slice); - free(c->unescaped_description); - free(c->supplementary_gids); - free(c->tty); - - free(c->well_known_names); /* note that this is an strv, but - * we only free the array, not the - * strings the array points to. The - * full strv we only free if - * c->allocated is set, see - * below. */ - - strv_free(c->cmdline_array); -} - -_public_ sd_bus_creds *sd_bus_creds_ref(sd_bus_creds *c) { - - if (!c) - return NULL; - - if (c->allocated) { - assert(c->n_ref > 0); - c->n_ref++; - } else { - sd_bus_message *m; - - /* If this is an embedded creds structure, then - * forward ref counting to the message */ - m = container_of(c, sd_bus_message, creds); - sd_bus_message_ref(m); - } - - return c; -} - -_public_ sd_bus_creds *sd_bus_creds_unref(sd_bus_creds *c) { - - if (!c) - return NULL; - - if (c->allocated) { - assert(c->n_ref > 0); - c->n_ref--; - - if (c->n_ref == 0) { - free(c->comm); - free(c->tid_comm); - free(c->exe); - free(c->cmdline); - free(c->cgroup); - free(c->capability); - free(c->label); - free(c->unique_name); - free(c->cgroup_root); - free(c->description); - - c->supplementary_gids = mfree(c->supplementary_gids); - - c->well_known_names = strv_free(c->well_known_names); - - bus_creds_done(c); - - free(c); - } - } else { - sd_bus_message *m; - - m = container_of(c, sd_bus_message, creds); - sd_bus_message_unref(m); - } - - - return NULL; -} - -_public_ uint64_t sd_bus_creds_get_mask(const sd_bus_creds *c) { - assert_return(c, 0); - - return c->mask; -} - -_public_ uint64_t sd_bus_creds_get_augmented_mask(const sd_bus_creds *c) { - assert_return(c, 0); - - return c->augmented; -} - -sd_bus_creds* bus_creds_new(void) { - sd_bus_creds *c; - - c = new0(sd_bus_creds, 1); - if (!c) - return NULL; - - c->allocated = true; - c->n_ref = 1; - return c; -} - -_public_ int sd_bus_creds_new_from_pid(sd_bus_creds **ret, pid_t pid, uint64_t mask) { - sd_bus_creds *c; - int r; - - assert_return(pid >= 0, -EINVAL); - assert_return(mask <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP); - assert_return(ret, -EINVAL); - - if (pid == 0) - pid = getpid(); - - c = bus_creds_new(); - if (!c) - return -ENOMEM; - - r = bus_creds_add_more(c, mask | SD_BUS_CREDS_AUGMENT, pid, 0); - if (r < 0) { - sd_bus_creds_unref(c); - return r; - } - - /* Check if the process existed at all, in case we haven't - * figured that out already */ - if (!pid_is_alive(pid)) { - sd_bus_creds_unref(c); - return -ESRCH; - } - - *ret = c; - return 0; -} - -_public_ int sd_bus_creds_get_uid(sd_bus_creds *c, uid_t *uid) { - assert_return(c, -EINVAL); - assert_return(uid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_UID)) - return -ENODATA; - - *uid = c->uid; - return 0; -} - -_public_ int sd_bus_creds_get_euid(sd_bus_creds *c, uid_t *euid) { - assert_return(c, -EINVAL); - assert_return(euid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_EUID)) - return -ENODATA; - - *euid = c->euid; - return 0; -} - -_public_ int sd_bus_creds_get_suid(sd_bus_creds *c, uid_t *suid) { - assert_return(c, -EINVAL); - assert_return(suid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_SUID)) - return -ENODATA; - - *suid = c->suid; - return 0; -} - - -_public_ int sd_bus_creds_get_fsuid(sd_bus_creds *c, uid_t *fsuid) { - assert_return(c, -EINVAL); - assert_return(fsuid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_FSUID)) - return -ENODATA; - - *fsuid = c->fsuid; - return 0; -} - -_public_ int sd_bus_creds_get_gid(sd_bus_creds *c, gid_t *gid) { - assert_return(c, -EINVAL); - assert_return(gid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_GID)) - return -ENODATA; - - *gid = c->gid; - return 0; -} - -_public_ int sd_bus_creds_get_egid(sd_bus_creds *c, gid_t *egid) { - assert_return(c, -EINVAL); - assert_return(egid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_EGID)) - return -ENODATA; - - *egid = c->egid; - return 0; -} - -_public_ int sd_bus_creds_get_sgid(sd_bus_creds *c, gid_t *sgid) { - assert_return(c, -EINVAL); - assert_return(sgid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_SGID)) - return -ENODATA; - - *sgid = c->sgid; - return 0; -} - -_public_ int sd_bus_creds_get_fsgid(sd_bus_creds *c, gid_t *fsgid) { - assert_return(c, -EINVAL); - assert_return(fsgid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_FSGID)) - return -ENODATA; - - *fsgid = c->fsgid; - return 0; -} - -_public_ int sd_bus_creds_get_supplementary_gids(sd_bus_creds *c, const gid_t **gids) { - assert_return(c, -EINVAL); - assert_return(gids, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS)) - return -ENODATA; - - *gids = c->supplementary_gids; - return (int) c->n_supplementary_gids; -} - -_public_ int sd_bus_creds_get_pid(sd_bus_creds *c, pid_t *pid) { - assert_return(c, -EINVAL); - assert_return(pid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_PID)) - return -ENODATA; - - assert(c->pid > 0); - *pid = c->pid; - return 0; -} - -_public_ int sd_bus_creds_get_ppid(sd_bus_creds *c, pid_t *ppid) { - assert_return(c, -EINVAL); - assert_return(ppid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_PPID)) - return -ENODATA; - - /* PID 1 has no parent process. Let's distinguish the case of - * not knowing and not having a parent process by the returned - * error code. */ - if (c->ppid == 0) - return -ENXIO; - - *ppid = c->ppid; - return 0; -} - -_public_ int sd_bus_creds_get_tid(sd_bus_creds *c, pid_t *tid) { - assert_return(c, -EINVAL); - assert_return(tid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_TID)) - return -ENODATA; - - assert(c->tid > 0); - *tid = c->tid; - return 0; -} - -_public_ int sd_bus_creds_get_selinux_context(sd_bus_creds *c, const char **ret) { - assert_return(c, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_SELINUX_CONTEXT)) - return -ENODATA; - - assert(c->label); - *ret = c->label; - return 0; -} - -_public_ int sd_bus_creds_get_comm(sd_bus_creds *c, const char **ret) { - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_COMM)) - return -ENODATA; - - assert(c->comm); - *ret = c->comm; - return 0; -} - -_public_ int sd_bus_creds_get_tid_comm(sd_bus_creds *c, const char **ret) { - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_TID_COMM)) - return -ENODATA; - - assert(c->tid_comm); - *ret = c->tid_comm; - return 0; -} - -_public_ int sd_bus_creds_get_exe(sd_bus_creds *c, const char **ret) { - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_EXE)) - return -ENODATA; - - if (!c->exe) - return -ENXIO; - - *ret = c->exe; - return 0; -} - -_public_ int sd_bus_creds_get_cgroup(sd_bus_creds *c, const char **ret) { - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_CGROUP)) - return -ENODATA; - - assert(c->cgroup); - *ret = c->cgroup; - return 0; -} - -_public_ int sd_bus_creds_get_unit(sd_bus_creds *c, const char **ret) { - int r; - - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_UNIT)) - return -ENODATA; - - assert(c->cgroup); - - if (!c->unit) { - const char *shifted; - - r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); - if (r < 0) - return r; - - r = cg_path_get_unit(shifted, (char**) &c->unit); - if (r < 0) - return r; - } - - *ret = c->unit; - return 0; -} - -_public_ int sd_bus_creds_get_user_unit(sd_bus_creds *c, const char **ret) { - int r; - - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_USER_UNIT)) - return -ENODATA; - - assert(c->cgroup); - - if (!c->user_unit) { - const char *shifted; - - r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); - if (r < 0) - return r; - - r = cg_path_get_user_unit(shifted, (char**) &c->user_unit); - if (r < 0) - return r; - } - - *ret = c->user_unit; - return 0; -} - -_public_ int sd_bus_creds_get_slice(sd_bus_creds *c, const char **ret) { - int r; - - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_SLICE)) - return -ENODATA; - - assert(c->cgroup); - - if (!c->slice) { - const char *shifted; - - r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); - if (r < 0) - return r; - - r = cg_path_get_slice(shifted, (char**) &c->slice); - if (r < 0) - return r; - } - - *ret = c->slice; - return 0; -} - -_public_ int sd_bus_creds_get_user_slice(sd_bus_creds *c, const char **ret) { - int r; - - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_USER_SLICE)) - return -ENODATA; - - assert(c->cgroup); - - if (!c->user_slice) { - const char *shifted; - - r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); - if (r < 0) - return r; - - r = cg_path_get_user_slice(shifted, (char**) &c->user_slice); - if (r < 0) - return r; - } - - *ret = c->user_slice; - return 0; -} - -_public_ int sd_bus_creds_get_session(sd_bus_creds *c, const char **ret) { - int r; - - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_SESSION)) - return -ENODATA; - - assert(c->cgroup); - - if (!c->session) { - const char *shifted; - - r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); - if (r < 0) - return r; - - r = cg_path_get_session(shifted, (char**) &c->session); - if (r < 0) - return r; - } - - *ret = c->session; - return 0; -} - -_public_ int sd_bus_creds_get_owner_uid(sd_bus_creds *c, uid_t *uid) { - const char *shifted; - int r; - - assert_return(c, -EINVAL); - assert_return(uid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_OWNER_UID)) - return -ENODATA; - - assert(c->cgroup); - - r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); - if (r < 0) - return r; - - return cg_path_get_owner_uid(shifted, uid); -} - -_public_ int sd_bus_creds_get_cmdline(sd_bus_creds *c, char ***cmdline) { - assert_return(c, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_CMDLINE)) - return -ENODATA; - - if (!c->cmdline) - return -ENXIO; - - if (!c->cmdline_array) { - c->cmdline_array = strv_parse_nulstr(c->cmdline, c->cmdline_size); - if (!c->cmdline_array) - return -ENOMEM; - } - - *cmdline = c->cmdline_array; - return 0; -} - -_public_ int sd_bus_creds_get_audit_session_id(sd_bus_creds *c, uint32_t *sessionid) { - assert_return(c, -EINVAL); - assert_return(sessionid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_AUDIT_SESSION_ID)) - return -ENODATA; - - if (c->audit_session_id == AUDIT_SESSION_INVALID) - return -ENXIO; - - *sessionid = c->audit_session_id; - return 0; -} - -_public_ int sd_bus_creds_get_audit_login_uid(sd_bus_creds *c, uid_t *uid) { - assert_return(c, -EINVAL); - assert_return(uid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_AUDIT_LOGIN_UID)) - return -ENODATA; - - if (c->audit_login_uid == UID_INVALID) - return -ENXIO; - - *uid = c->audit_login_uid; - return 0; -} - -_public_ int sd_bus_creds_get_tty(sd_bus_creds *c, const char **ret) { - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_TTY)) - return -ENODATA; - - if (!c->tty) - return -ENXIO; - - *ret = c->tty; - return 0; -} - -_public_ int sd_bus_creds_get_unique_name(sd_bus_creds *c, const char **unique_name) { - assert_return(c, -EINVAL); - assert_return(unique_name, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_UNIQUE_NAME)) - return -ENODATA; - - *unique_name = c->unique_name; - return 0; -} - -_public_ int sd_bus_creds_get_well_known_names(sd_bus_creds *c, char ***well_known_names) { - assert_return(c, -EINVAL); - assert_return(well_known_names, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_WELL_KNOWN_NAMES)) - return -ENODATA; - - /* As a special hack we return the bus driver as well-known - * names list when this is requested. */ - if (c->well_known_names_driver) { - static const char* const wkn[] = { - "org.freedesktop.DBus", - NULL - }; - - *well_known_names = (char**) wkn; - return 0; - } - - if (c->well_known_names_local) { - static const char* const wkn[] = { - "org.freedesktop.DBus.Local", - NULL - }; - - *well_known_names = (char**) wkn; - return 0; - } - - *well_known_names = c->well_known_names; - return 0; -} - -_public_ int sd_bus_creds_get_description(sd_bus_creds *c, const char **ret) { - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_DESCRIPTION)) - return -ENODATA; - - assert(c->description); - - if (!c->unescaped_description) { - c->unescaped_description = bus_label_unescape(c->description); - if (!c->unescaped_description) - return -ENOMEM; - } - - *ret = c->unescaped_description; - return 0; -} - -static int has_cap(sd_bus_creds *c, unsigned offset, int capability) { - size_t sz; - - assert(c); - assert(capability >= 0); - assert(c->capability); - - if ((unsigned) capability > cap_last_cap()) - return 0; - - sz = DIV_ROUND_UP(cap_last_cap(), 32U); - - return !!(c->capability[offset * sz + CAP_TO_INDEX(capability)] & CAP_TO_MASK(capability)); -} - -_public_ int sd_bus_creds_has_effective_cap(sd_bus_creds *c, int capability) { - assert_return(c, -EINVAL); - assert_return(capability >= 0, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_EFFECTIVE_CAPS)) - return -ENODATA; - - return has_cap(c, CAP_OFFSET_EFFECTIVE, capability); -} - -_public_ int sd_bus_creds_has_permitted_cap(sd_bus_creds *c, int capability) { - assert_return(c, -EINVAL); - assert_return(capability >= 0, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_PERMITTED_CAPS)) - return -ENODATA; - - return has_cap(c, CAP_OFFSET_PERMITTED, capability); -} - -_public_ int sd_bus_creds_has_inheritable_cap(sd_bus_creds *c, int capability) { - assert_return(c, -EINVAL); - assert_return(capability >= 0, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_INHERITABLE_CAPS)) - return -ENODATA; - - return has_cap(c, CAP_OFFSET_INHERITABLE, capability); -} - -_public_ int sd_bus_creds_has_bounding_cap(sd_bus_creds *c, int capability) { - assert_return(c, -EINVAL); - assert_return(capability >= 0, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_BOUNDING_CAPS)) - return -ENODATA; - - return has_cap(c, CAP_OFFSET_BOUNDING, capability); -} - -static int parse_caps(sd_bus_creds *c, unsigned offset, const char *p) { - size_t sz, max; - unsigned i, j; - - assert(c); - assert(p); - - max = DIV_ROUND_UP(cap_last_cap(), 32U); - p += strspn(p, WHITESPACE); - - sz = strlen(p); - if (sz % 8 != 0) - return -EINVAL; - - sz /= 8; - if (sz > max) - return -EINVAL; - - if (!c->capability) { - c->capability = new0(uint32_t, max * 4); - if (!c->capability) - return -ENOMEM; - } - - for (i = 0; i < sz; i ++) { - uint32_t v = 0; - - for (j = 0; j < 8; ++j) { - int t; - - t = unhexchar(*p++); - if (t < 0) - return -EINVAL; - - v = (v << 4) | t; - } - - c->capability[offset * max + (sz - i - 1)] = v; - } - - return 0; -} - -int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { - uint64_t missing; - int r; - - assert(c); - assert(c->allocated); - - if (!(mask & SD_BUS_CREDS_AUGMENT)) - return 0; - - /* Try to retrieve PID from creds if it wasn't passed to us */ - if (pid > 0) { - c->pid = pid; - c->mask |= SD_BUS_CREDS_PID; - } else if (c->mask & SD_BUS_CREDS_PID) - pid = c->pid; - else - /* Without pid we cannot do much... */ - return 0; - - /* Try to retrieve TID from creds if it wasn't passed to us */ - if (tid <= 0 && (c->mask & SD_BUS_CREDS_TID)) - tid = c->tid; - - /* Calculate what we shall and can add */ - missing = mask & ~(c->mask|SD_BUS_CREDS_PID|SD_BUS_CREDS_TID|SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_DESCRIPTION|SD_BUS_CREDS_AUGMENT); - if (missing == 0) - return 0; - - if (tid > 0) { - c->tid = tid; - c->mask |= SD_BUS_CREDS_TID; - } - - if (missing & (SD_BUS_CREDS_PPID | - SD_BUS_CREDS_UID | SD_BUS_CREDS_EUID | SD_BUS_CREDS_SUID | SD_BUS_CREDS_FSUID | - SD_BUS_CREDS_GID | SD_BUS_CREDS_EGID | SD_BUS_CREDS_SGID | SD_BUS_CREDS_FSGID | - SD_BUS_CREDS_SUPPLEMENTARY_GIDS | - SD_BUS_CREDS_EFFECTIVE_CAPS | SD_BUS_CREDS_INHERITABLE_CAPS | - SD_BUS_CREDS_PERMITTED_CAPS | SD_BUS_CREDS_BOUNDING_CAPS)) { - - _cleanup_fclose_ FILE *f = NULL; - const char *p; - - p = procfs_file_alloca(pid, "status"); - - f = fopen(p, "re"); - if (!f) { - if (errno == ENOENT) - return -ESRCH; - else if (errno != EPERM && errno != EACCES) - return -errno; - } else { - char line[LINE_MAX]; - - FOREACH_LINE(line, f, return -errno) { - truncate_nl(line); - - if (missing & SD_BUS_CREDS_PPID) { - p = startswith(line, "PPid:"); - if (p) { - p += strspn(p, WHITESPACE); - - /* Explicitly check for PPID 0 (which is the case for PID 1) */ - if (!streq(p, "0")) { - r = parse_pid(p, &c->ppid); - if (r < 0) - return r; - - } else - c->ppid = 0; - - c->mask |= SD_BUS_CREDS_PPID; - continue; - } - } - - if (missing & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID)) { - p = startswith(line, "Uid:"); - if (p) { - unsigned long uid, euid, suid, fsuid; - - p += strspn(p, WHITESPACE); - if (sscanf(p, "%lu %lu %lu %lu", &uid, &euid, &suid, &fsuid) != 4) - return -EIO; - - if (missing & SD_BUS_CREDS_UID) - c->uid = (uid_t) uid; - if (missing & SD_BUS_CREDS_EUID) - c->euid = (uid_t) euid; - if (missing & SD_BUS_CREDS_SUID) - c->suid = (uid_t) suid; - if (missing & SD_BUS_CREDS_FSUID) - c->fsuid = (uid_t) fsuid; - - c->mask |= missing & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID); - continue; - } - } - - if (missing & (SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID)) { - p = startswith(line, "Gid:"); - if (p) { - unsigned long gid, egid, sgid, fsgid; - - p += strspn(p, WHITESPACE); - if (sscanf(p, "%lu %lu %lu %lu", &gid, &egid, &sgid, &fsgid) != 4) - return -EIO; - - if (missing & SD_BUS_CREDS_GID) - c->gid = (gid_t) gid; - if (missing & SD_BUS_CREDS_EGID) - c->egid = (gid_t) egid; - if (missing & SD_BUS_CREDS_SGID) - c->sgid = (gid_t) sgid; - if (missing & SD_BUS_CREDS_FSGID) - c->fsgid = (gid_t) fsgid; - - c->mask |= missing & (SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID); - continue; - } - } - - if (missing & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { - p = startswith(line, "Groups:"); - if (p) { - size_t allocated = 0; - - for (;;) { - unsigned long g; - int n = 0; - - p += strspn(p, WHITESPACE); - if (*p == 0) - break; - - if (sscanf(p, "%lu%n", &g, &n) != 1) - return -EIO; - - if (!GREEDY_REALLOC(c->supplementary_gids, allocated, c->n_supplementary_gids+1)) - return -ENOMEM; - - c->supplementary_gids[c->n_supplementary_gids++] = (gid_t) g; - p += n; - } - - c->mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS; - continue; - } - } - - if (missing & SD_BUS_CREDS_EFFECTIVE_CAPS) { - p = startswith(line, "CapEff:"); - if (p) { - r = parse_caps(c, CAP_OFFSET_EFFECTIVE, p); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_EFFECTIVE_CAPS; - continue; - } - } - - if (missing & SD_BUS_CREDS_PERMITTED_CAPS) { - p = startswith(line, "CapPrm:"); - if (p) { - r = parse_caps(c, CAP_OFFSET_PERMITTED, p); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_PERMITTED_CAPS; - continue; - } - } - - if (missing & SD_BUS_CREDS_INHERITABLE_CAPS) { - p = startswith(line, "CapInh:"); - if (p) { - r = parse_caps(c, CAP_OFFSET_INHERITABLE, p); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_INHERITABLE_CAPS; - continue; - } - } - - if (missing & SD_BUS_CREDS_BOUNDING_CAPS) { - p = startswith(line, "CapBnd:"); - if (p) { - r = parse_caps(c, CAP_OFFSET_BOUNDING, p); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_BOUNDING_CAPS; - continue; - } - } - } - } - } - - if (missing & SD_BUS_CREDS_SELINUX_CONTEXT) { - const char *p; - - p = procfs_file_alloca(pid, "attr/current"); - r = read_one_line_file(p, &c->label); - if (r < 0) { - if (r != -ENOENT && r != -EINVAL && r != -EPERM && r != -EACCES) - return r; - } else - c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; - } - - if (missing & SD_BUS_CREDS_COMM) { - r = get_process_comm(pid, &c->comm); - if (r < 0) { - if (r != -EPERM && r != -EACCES) - return r; - } else - c->mask |= SD_BUS_CREDS_COMM; - } - - if (missing & SD_BUS_CREDS_EXE) { - r = get_process_exe(pid, &c->exe); - if (r == -ESRCH) { - /* Unfortunately we cannot really distinguish - * the case here where the process does not - * exist, and /proc/$PID/exe being unreadable - * because $PID is a kernel thread. Hence, - * assume it is a kernel thread, and rely on - * that this case is caught with a later - * call. */ - c->exe = NULL; - c->mask |= SD_BUS_CREDS_EXE; - } else if (r < 0) { - if (r != -EPERM && r != -EACCES) - return r; - } else - c->mask |= SD_BUS_CREDS_EXE; - } - - if (missing & SD_BUS_CREDS_CMDLINE) { - const char *p; - - p = procfs_file_alloca(pid, "cmdline"); - r = read_full_file(p, &c->cmdline, &c->cmdline_size); - if (r == -ENOENT) - return -ESRCH; - if (r < 0) { - if (r != -EPERM && r != -EACCES) - return r; - } else { - if (c->cmdline_size == 0) - c->cmdline = mfree(c->cmdline); - - c->mask |= SD_BUS_CREDS_CMDLINE; - } - } - - if (tid > 0 && (missing & SD_BUS_CREDS_TID_COMM)) { - _cleanup_free_ char *p = NULL; - - if (asprintf(&p, "/proc/"PID_FMT"/task/"PID_FMT"/comm", pid, tid) < 0) - return -ENOMEM; - - r = read_one_line_file(p, &c->tid_comm); - if (r == -ENOENT) - return -ESRCH; - if (r < 0) { - if (r != -EPERM && r != -EACCES) - return r; - } else - c->mask |= SD_BUS_CREDS_TID_COMM; - } - - if (missing & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID)) { - - if (!c->cgroup) { - r = cg_pid_get_path(NULL, pid, &c->cgroup); - if (r < 0) { - if (r != -EPERM && r != -EACCES) - return r; - } - } - - if (!c->cgroup_root) { - r = cg_get_root_path(&c->cgroup_root); - if (r < 0) - return r; - } - - if (c->cgroup) - c->mask |= missing & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID); - } - - if (missing & SD_BUS_CREDS_AUDIT_SESSION_ID) { - r = audit_session_from_pid(pid, &c->audit_session_id); - if (r == -ENODATA) { - /* ENODATA means: no audit session id assigned */ - c->audit_session_id = AUDIT_SESSION_INVALID; - c->mask |= SD_BUS_CREDS_AUDIT_SESSION_ID; - } else if (r < 0) { - if (r != -EOPNOTSUPP && r != -ENOENT && r != -EPERM && r != -EACCES) - return r; - } else - c->mask |= SD_BUS_CREDS_AUDIT_SESSION_ID; - } - - if (missing & SD_BUS_CREDS_AUDIT_LOGIN_UID) { - r = audit_loginuid_from_pid(pid, &c->audit_login_uid); - if (r == -ENODATA) { - /* ENODATA means: no audit login uid assigned */ - c->audit_login_uid = UID_INVALID; - c->mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID; - } else if (r < 0) { - if (r != -EOPNOTSUPP && r != -ENOENT && r != -EPERM && r != -EACCES) - return r; - } else - c->mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID; - } - - if (missing & SD_BUS_CREDS_TTY) { - r = get_ctty(pid, NULL, &c->tty); - if (r == -ENXIO) { - /* ENXIO means: process has no controlling TTY */ - c->tty = NULL; - c->mask |= SD_BUS_CREDS_TTY; - } else if (r < 0) { - if (r != -EPERM && r != -EACCES && r != -ENOENT) - return r; - } else - c->mask |= SD_BUS_CREDS_TTY; - } - - /* In case only the exe path was to be read we cannot - * distinguish the case where the exe path was unreadable - * because the process was a kernel thread, or when the - * process didn't exist at all. Hence, let's do a final check, - * to be sure. */ - if (!pid_is_alive(pid)) - return -ESRCH; - - if (tid > 0 && tid != pid && !pid_is_unwaited(tid)) - return -ESRCH; - - c->augmented = missing & c->mask; - - return 0; -} - -int bus_creds_extend_by_pid(sd_bus_creds *c, uint64_t mask, sd_bus_creds **ret) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *n = NULL; - int r; - - assert(c); - assert(ret); - - if ((mask & ~c->mask) == 0 || (!(mask & SD_BUS_CREDS_AUGMENT))) { - /* There's already all data we need, or augmentation - * wasn't turned on. */ - - *ret = sd_bus_creds_ref(c); - return 0; - } - - n = bus_creds_new(); - if (!n) - return -ENOMEM; - - /* Copy the original data over */ - - if (c->mask & mask & SD_BUS_CREDS_PID) { - n->pid = c->pid; - n->mask |= SD_BUS_CREDS_PID; - } - - if (c->mask & mask & SD_BUS_CREDS_TID) { - n->tid = c->tid; - n->mask |= SD_BUS_CREDS_TID; - } - - if (c->mask & mask & SD_BUS_CREDS_PPID) { - n->ppid = c->ppid; - n->mask |= SD_BUS_CREDS_PPID; - } - - if (c->mask & mask & SD_BUS_CREDS_UID) { - n->uid = c->uid; - n->mask |= SD_BUS_CREDS_UID; - } - - if (c->mask & mask & SD_BUS_CREDS_EUID) { - n->euid = c->euid; - n->mask |= SD_BUS_CREDS_EUID; - } - - if (c->mask & mask & SD_BUS_CREDS_SUID) { - n->suid = c->suid; - n->mask |= SD_BUS_CREDS_SUID; - } - - if (c->mask & mask & SD_BUS_CREDS_FSUID) { - n->fsuid = c->fsuid; - n->mask |= SD_BUS_CREDS_FSUID; - } - - if (c->mask & mask & SD_BUS_CREDS_GID) { - n->gid = c->gid; - n->mask |= SD_BUS_CREDS_GID; - } - - if (c->mask & mask & SD_BUS_CREDS_EGID) { - n->egid = c->egid; - n->mask |= SD_BUS_CREDS_EGID; - } - - if (c->mask & mask & SD_BUS_CREDS_SGID) { - n->sgid = c->sgid; - n->mask |= SD_BUS_CREDS_SGID; - } - - if (c->mask & mask & SD_BUS_CREDS_FSGID) { - n->fsgid = c->fsgid; - n->mask |= SD_BUS_CREDS_FSGID; - } - - if (c->mask & mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { - if (c->supplementary_gids) { - n->supplementary_gids = newdup(gid_t, c->supplementary_gids, c->n_supplementary_gids); - if (!n->supplementary_gids) - return -ENOMEM; - n->n_supplementary_gids = c->n_supplementary_gids; - } else { - n->supplementary_gids = NULL; - n->n_supplementary_gids = 0; - } - - n->mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS; - } - - if (c->mask & mask & SD_BUS_CREDS_COMM) { - assert(c->comm); - - n->comm = strdup(c->comm); - if (!n->comm) - return -ENOMEM; - - n->mask |= SD_BUS_CREDS_COMM; - } - - if (c->mask & mask & SD_BUS_CREDS_TID_COMM) { - assert(c->tid_comm); - - n->tid_comm = strdup(c->tid_comm); - if (!n->tid_comm) - return -ENOMEM; - - n->mask |= SD_BUS_CREDS_TID_COMM; - } - - if (c->mask & mask & SD_BUS_CREDS_EXE) { - if (c->exe) { - n->exe = strdup(c->exe); - if (!n->exe) - return -ENOMEM; - } else - n->exe = NULL; - - n->mask |= SD_BUS_CREDS_EXE; - } - - if (c->mask & mask & SD_BUS_CREDS_CMDLINE) { - if (c->cmdline) { - n->cmdline = memdup(c->cmdline, c->cmdline_size); - if (!n->cmdline) - return -ENOMEM; - - n->cmdline_size = c->cmdline_size; - } else { - n->cmdline = NULL; - n->cmdline_size = 0; - } - - n->mask |= SD_BUS_CREDS_CMDLINE; - } - - if (c->mask & mask & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_OWNER_UID)) { - assert(c->cgroup); - - n->cgroup = strdup(c->cgroup); - if (!n->cgroup) - return -ENOMEM; - - n->cgroup_root = strdup(c->cgroup_root); - if (!n->cgroup_root) - return -ENOMEM; - - n->mask |= mask & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_OWNER_UID); - } - - if (c->mask & mask & (SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS)) { - assert(c->capability); - - n->capability = memdup(c->capability, DIV_ROUND_UP(cap_last_cap(), 32U) * 4 * 4); - if (!n->capability) - return -ENOMEM; - - n->mask |= c->mask & mask & (SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS); - } - - if (c->mask & mask & SD_BUS_CREDS_SELINUX_CONTEXT) { - assert(c->label); - - n->label = strdup(c->label); - if (!n->label) - return -ENOMEM; - n->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; - } - - if (c->mask & mask & SD_BUS_CREDS_AUDIT_SESSION_ID) { - n->audit_session_id = c->audit_session_id; - n->mask |= SD_BUS_CREDS_AUDIT_SESSION_ID; - } - if (c->mask & mask & SD_BUS_CREDS_AUDIT_LOGIN_UID) { - n->audit_login_uid = c->audit_login_uid; - n->mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID; - } - - if (c->mask & mask & SD_BUS_CREDS_TTY) { - if (c->tty) { - n->tty = strdup(c->tty); - if (!n->tty) - return -ENOMEM; - } else - n->tty = NULL; - n->mask |= SD_BUS_CREDS_TTY; - } - - if (c->mask & mask & SD_BUS_CREDS_UNIQUE_NAME) { - assert(c->unique_name); - - n->unique_name = strdup(c->unique_name); - if (!n->unique_name) - return -ENOMEM; - n->mask |= SD_BUS_CREDS_UNIQUE_NAME; - } - - if (c->mask & mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) { - if (strv_isempty(c->well_known_names)) - n->well_known_names = NULL; - else { - n->well_known_names = strv_copy(c->well_known_names); - if (!n->well_known_names) - return -ENOMEM; - } - n->well_known_names_driver = c->well_known_names_driver; - n->well_known_names_local = c->well_known_names_local; - n->mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES; - } - - if (c->mask & mask & SD_BUS_CREDS_DESCRIPTION) { - assert(c->description); - n->description = strdup(c->description); - if (!n->description) - return -ENOMEM; - n->mask |= SD_BUS_CREDS_DESCRIPTION; - } - - n->augmented = c->augmented & n->mask; - - /* Get more data */ - - r = bus_creds_add_more(n, mask, 0, 0); - if (r < 0) - return r; - - *ret = n; - n = NULL; - return 0; -} diff --git a/src/libsystemd/sd-bus/bus-creds.h b/src/libsystemd/sd-bus/bus-creds.h deleted file mode 100644 index df8a1f1005..0000000000 --- a/src/libsystemd/sd-bus/bus-creds.h +++ /dev/null @@ -1,90 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -struct sd_bus_creds { - bool allocated; - unsigned n_ref; - - uint64_t mask; - uint64_t augmented; - - uid_t uid; - uid_t euid; - uid_t suid; - uid_t fsuid; - gid_t gid; - gid_t egid; - gid_t sgid; - gid_t fsgid; - - gid_t *supplementary_gids; - unsigned n_supplementary_gids; - - pid_t ppid; - pid_t pid; - pid_t tid; - - char *comm; - char *tid_comm; - char *exe; - - char *cmdline; - size_t cmdline_size; - char **cmdline_array; - - char *cgroup; - char *session; - char *unit; - char *user_unit; - char *slice; - char *user_slice; - - char *tty; - - uint32_t *capability; - - uint32_t audit_session_id; - uid_t audit_login_uid; - - char *label; - - char *unique_name; - - char **well_known_names; - bool well_known_names_driver:1; - bool well_known_names_local:1; - - char *cgroup_root; - - char *description, *unescaped_description; -}; - -sd_bus_creds* bus_creds_new(void); - -void bus_creds_done(sd_bus_creds *c); - -int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid); - -int bus_creds_extend_by_pid(sd_bus_creds *c, uint64_t mask, sd_bus_creds **ret); diff --git a/src/libsystemd/sd-bus/bus-dump.c b/src/libsystemd/sd-bus/bus-dump.c deleted file mode 100644 index 21a6b20a11..0000000000 --- a/src/libsystemd/sd-bus/bus-dump.c +++ /dev/null @@ -1,602 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "alloc-util.h" -#include "bus-dump.h" -#include "bus-internal.h" -#include "bus-message.h" -#include "bus-type.h" -#include "cap-list.h" -#include "capability-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "locale-util.h" -#include "macro.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "util.h" - -static char *indent(unsigned level, unsigned flags) { - char *p; - unsigned n, i = 0; - - n = 0; - - if (flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY && level > 0) - level -= 1; - - if (flags & BUS_MESSAGE_DUMP_WITH_HEADER) - n += 2; - - p = new(char, n + level*8 + 1); - if (!p) - return NULL; - - if (flags & BUS_MESSAGE_DUMP_WITH_HEADER) { - p[i++] = ' '; - p[i++] = ' '; - } - - memset(p + i, ' ', level*8); - p[i + level*8] = 0; - - return p; -} - -int bus_message_dump(sd_bus_message *m, FILE *f, unsigned flags) { - unsigned level = 1; - int r; - - assert(m); - - if (!f) - f = stdout; - - if (flags & BUS_MESSAGE_DUMP_WITH_HEADER) { - fprintf(f, - "%s%s%s Type=%s%s%s Endian=%c Flags=%u Version=%u Priority=%"PRIi64, - m->header->type == SD_BUS_MESSAGE_METHOD_ERROR ? ansi_highlight_red() : - m->header->type == SD_BUS_MESSAGE_METHOD_RETURN ? ansi_highlight_green() : - m->header->type != SD_BUS_MESSAGE_SIGNAL ? ansi_highlight() : "", special_glyph(TRIANGULAR_BULLET), ansi_normal(), - ansi_highlight(), bus_message_type_to_string(m->header->type), ansi_normal(), - m->header->endian, - m->header->flags, - m->header->version, - m->priority); - - /* Display synthetic message serial number in a more readable - * format than (uint32_t) -1 */ - if (BUS_MESSAGE_COOKIE(m) == 0xFFFFFFFFULL) - fprintf(f, " Cookie=-1"); - else - fprintf(f, " Cookie=%" PRIu64, BUS_MESSAGE_COOKIE(m)); - - if (m->reply_cookie != 0) - fprintf(f, " ReplyCookie=%" PRIu64, m->reply_cookie); - - fputs("\n", f); - - if (m->sender) - fprintf(f, " Sender=%s%s%s", ansi_highlight(), m->sender, ansi_normal()); - if (m->destination) - fprintf(f, " Destination=%s%s%s", ansi_highlight(), m->destination, ansi_normal()); - if (m->path) - fprintf(f, " Path=%s%s%s", ansi_highlight(), m->path, ansi_normal()); - if (m->interface) - fprintf(f, " Interface=%s%s%s", ansi_highlight(), m->interface, ansi_normal()); - if (m->member) - fprintf(f, " Member=%s%s%s", ansi_highlight(), m->member, ansi_normal()); - - if (m->sender || m->destination || m->path || m->interface || m->member) - fputs("\n", f); - - if (sd_bus_error_is_set(&m->error)) - fprintf(f, - " ErrorName=%s%s%s" - " ErrorMessage=%s\"%s\"%s\n", - ansi_highlight_red(), strna(m->error.name), ansi_normal(), - ansi_highlight_red(), strna(m->error.message), ansi_normal()); - - if (m->monotonic != 0) - fprintf(f, " Monotonic="USEC_FMT, m->monotonic); - if (m->realtime != 0) - fprintf(f, " Realtime="USEC_FMT, m->realtime); - if (m->seqnum != 0) - fprintf(f, " SequenceNumber=%"PRIu64, m->seqnum); - - if (m->monotonic != 0 || m->realtime != 0 || m->seqnum != 0) - fputs("\n", f); - - bus_creds_dump(&m->creds, f, true); - } - - r = sd_bus_message_rewind(m, !(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY)); - if (r < 0) - return log_error_errno(r, "Failed to rewind: %m"); - - if (!(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY)) { - _cleanup_free_ char *prefix = NULL; - - prefix = indent(0, flags); - if (!prefix) - return log_oom(); - - fprintf(f, "%sMESSAGE \"%s\" {\n", prefix, strempty(m->root_container.signature)); - } - - for (;;) { - _cleanup_free_ char *prefix = NULL; - const char *contents = NULL; - char type; - union { - uint8_t u8; - uint16_t u16; - int16_t s16; - uint32_t u32; - int32_t s32; - uint64_t u64; - int64_t s64; - double d64; - const char *string; - int i; - } basic; - - r = sd_bus_message_peek_type(m, &type, &contents); - if (r < 0) - return log_error_errno(r, "Failed to peek type: %m"); - - if (r == 0) { - if (level <= 1) - break; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return log_error_errno(r, "Failed to exit container: %m"); - - level--; - - prefix = indent(level, flags); - if (!prefix) - return log_oom(); - - fprintf(f, "%s};\n", prefix); - continue; - } - - prefix = indent(level, flags); - if (!prefix) - return log_oom(); - - if (bus_type_is_container(type) > 0) { - r = sd_bus_message_enter_container(m, type, contents); - if (r < 0) - return log_error_errno(r, "Failed to enter container: %m"); - - if (type == SD_BUS_TYPE_ARRAY) - fprintf(f, "%sARRAY \"%s\" {\n", prefix, contents); - else if (type == SD_BUS_TYPE_VARIANT) - fprintf(f, "%sVARIANT \"%s\" {\n", prefix, contents); - else if (type == SD_BUS_TYPE_STRUCT) - fprintf(f, "%sSTRUCT \"%s\" {\n", prefix, contents); - else if (type == SD_BUS_TYPE_DICT_ENTRY) - fprintf(f, "%sDICT_ENTRY \"%s\" {\n", prefix, contents); - - level++; - - continue; - } - - r = sd_bus_message_read_basic(m, type, &basic); - if (r < 0) - return log_error_errno(r, "Failed to get basic: %m"); - - assert(r > 0); - - switch (type) { - - case SD_BUS_TYPE_BYTE: - fprintf(f, "%sBYTE %s%u%s;\n", prefix, ansi_highlight(), basic.u8, ansi_normal()); - break; - - case SD_BUS_TYPE_BOOLEAN: - fprintf(f, "%sBOOLEAN %s%s%s;\n", prefix, ansi_highlight(), true_false(basic.i), ansi_normal()); - break; - - case SD_BUS_TYPE_INT16: - fprintf(f, "%sINT16 %s%i%s;\n", prefix, ansi_highlight(), basic.s16, ansi_normal()); - break; - - case SD_BUS_TYPE_UINT16: - fprintf(f, "%sUINT16 %s%u%s;\n", prefix, ansi_highlight(), basic.u16, ansi_normal()); - break; - - case SD_BUS_TYPE_INT32: - fprintf(f, "%sINT32 %s%i%s;\n", prefix, ansi_highlight(), basic.s32, ansi_normal()); - break; - - case SD_BUS_TYPE_UINT32: - fprintf(f, "%sUINT32 %s%u%s;\n", prefix, ansi_highlight(), basic.u32, ansi_normal()); - break; - - case SD_BUS_TYPE_INT64: - fprintf(f, "%sINT64 %s%"PRIi64"%s;\n", prefix, ansi_highlight(), basic.s64, ansi_normal()); - break; - - case SD_BUS_TYPE_UINT64: - fprintf(f, "%sUINT64 %s%"PRIu64"%s;\n", prefix, ansi_highlight(), basic.u64, ansi_normal()); - break; - - case SD_BUS_TYPE_DOUBLE: - fprintf(f, "%sDOUBLE %s%g%s;\n", prefix, ansi_highlight(), basic.d64, ansi_normal()); - break; - - case SD_BUS_TYPE_STRING: - fprintf(f, "%sSTRING \"%s%s%s\";\n", prefix, ansi_highlight(), basic.string, ansi_normal()); - break; - - case SD_BUS_TYPE_OBJECT_PATH: - fprintf(f, "%sOBJECT_PATH \"%s%s%s\";\n", prefix, ansi_highlight(), basic.string, ansi_normal()); - break; - - case SD_BUS_TYPE_SIGNATURE: - fprintf(f, "%sSIGNATURE \"%s%s%s\";\n", prefix, ansi_highlight(), basic.string, ansi_normal()); - break; - - case SD_BUS_TYPE_UNIX_FD: - fprintf(f, "%sUNIX_FD %s%i%s;\n", prefix, ansi_highlight(), basic.i, ansi_normal()); - break; - - default: - assert_not_reached("Unknown basic type."); - } - } - - if (!(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY)) { - _cleanup_free_ char *prefix = NULL; - - prefix = indent(0, flags); - if (!prefix) - return log_oom(); - - fprintf(f, "%s};\n\n", prefix); - } - - return 0; -} - -static void dump_capabilities( - sd_bus_creds *c, - FILE *f, - const char *name, - bool terse, - int (*has)(sd_bus_creds *c, int capability)) { - - unsigned long i, last_cap; - unsigned n = 0; - int r; - - assert(c); - assert(f); - assert(name); - assert(has); - - i = 0; - r = has(c, i); - if (r < 0) - return; - - fprintf(f, "%s%s=%s", terse ? " " : "", name, terse ? "" : ansi_highlight()); - last_cap = cap_last_cap(); - - for (;;) { - if (r > 0) { - - if (n > 0) - fputc(' ', f); - if (n % 4 == 3) - fprintf(f, terse ? "\n " : "\n "); - - fprintf(f, "%s", strna(capability_to_name(i))); - n++; - } - - i++; - - if (i > last_cap) - break; - - r = has(c, i); - } - - fputs("\n", f); - - if (!terse) - fputs(ansi_normal(), f); -} - -int bus_creds_dump(sd_bus_creds *c, FILE *f, bool terse) { - uid_t owner, audit_loginuid; - uint32_t audit_sessionid; - char **cmdline = NULL, **well_known = NULL; - const char *prefix, *color, *suffix, *s; - int r, q, v, w, z; - - assert(c); - - if (!f) - f = stdout; - - if (terse) { - prefix = " "; - suffix = ""; - color = ""; - } else { - const char *off; - - prefix = ""; - color = ansi_highlight(); - - off = ansi_normal(); - suffix = strjoina(off, "\n"); - } - - if (c->mask & SD_BUS_CREDS_PID) - fprintf(f, "%sPID=%s"PID_FMT"%s", prefix, color, c->pid, suffix); - if (c->mask & SD_BUS_CREDS_TID) - fprintf(f, "%sTID=%s"PID_FMT"%s", prefix, color, c->tid, suffix); - if (c->mask & SD_BUS_CREDS_PPID) { - if (c->ppid == 0) - fprintf(f, "%sPPID=%sn/a%s", prefix, color, suffix); - else - fprintf(f, "%sPPID=%s"PID_FMT"%s", prefix, color, c->ppid, suffix); - } - if (c->mask & SD_BUS_CREDS_TTY) - fprintf(f, "%sTTY=%s%s%s", prefix, color, strna(c->tty), suffix); - - if (terse && ((c->mask & (SD_BUS_CREDS_PID|SD_BUS_CREDS_TID|SD_BUS_CREDS_PPID|SD_BUS_CREDS_TTY)))) - fputs("\n", f); - - if (c->mask & SD_BUS_CREDS_UID) - fprintf(f, "%sUID=%s"UID_FMT"%s", prefix, color, c->uid, suffix); - if (c->mask & SD_BUS_CREDS_EUID) - fprintf(f, "%sEUID=%s"UID_FMT"%s", prefix, color, c->euid, suffix); - if (c->mask & SD_BUS_CREDS_SUID) - fprintf(f, "%sSUID=%s"UID_FMT"%s", prefix, color, c->suid, suffix); - if (c->mask & SD_BUS_CREDS_FSUID) - fprintf(f, "%sFSUID=%s"UID_FMT"%s", prefix, color, c->fsuid, suffix); - r = sd_bus_creds_get_owner_uid(c, &owner); - if (r >= 0) - fprintf(f, "%sOwnerUID=%s"UID_FMT"%s", prefix, color, owner, suffix); - if (c->mask & SD_BUS_CREDS_GID) - fprintf(f, "%sGID=%s"GID_FMT"%s", prefix, color, c->gid, suffix); - if (c->mask & SD_BUS_CREDS_EGID) - fprintf(f, "%sEGID=%s"GID_FMT"%s", prefix, color, c->egid, suffix); - if (c->mask & SD_BUS_CREDS_SGID) - fprintf(f, "%sSGID=%s"GID_FMT"%s", prefix, color, c->sgid, suffix); - if (c->mask & SD_BUS_CREDS_FSGID) - fprintf(f, "%sFSGID=%s"GID_FMT"%s", prefix, color, c->fsgid, suffix); - - if (c->mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { - unsigned i; - - fprintf(f, "%sSupplementaryGIDs=%s", prefix, color); - for (i = 0; i < c->n_supplementary_gids; i++) - fprintf(f, "%s" GID_FMT, i > 0 ? " " : "", c->supplementary_gids[i]); - fprintf(f, "%s", suffix); - } - - if (terse && ((c->mask & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| - SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| - SD_BUS_CREDS_SUPPLEMENTARY_GIDS)) || r >= 0)) - fputs("\n", f); - - if (c->mask & SD_BUS_CREDS_COMM) - fprintf(f, "%sComm=%s%s%s", prefix, color, c->comm, suffix); - if (c->mask & SD_BUS_CREDS_TID_COMM) - fprintf(f, "%sTIDComm=%s%s%s", prefix, color, c->tid_comm, suffix); - if (c->mask & SD_BUS_CREDS_EXE) - fprintf(f, "%sExe=%s%s%s", prefix, color, strna(c->exe), suffix); - - if (terse && (c->mask & (SD_BUS_CREDS_EXE|SD_BUS_CREDS_COMM|SD_BUS_CREDS_TID_COMM))) - fputs("\n", f); - - r = sd_bus_creds_get_cmdline(c, &cmdline); - if (r >= 0) { - char **i; - - fprintf(f, "%sCommandLine=%s", prefix, color); - STRV_FOREACH(i, cmdline) { - if (i != cmdline) - fputc(' ', f); - - fputs(*i, f); - } - - fprintf(f, "%s", suffix); - } else if (r != -ENODATA) - fprintf(f, "%sCommandLine=%sn/a%s", prefix, color, suffix); - - if (c->mask & SD_BUS_CREDS_SELINUX_CONTEXT) - fprintf(f, "%sLabel=%s%s%s", prefix, color, c->label, suffix); - if (c->mask & SD_BUS_CREDS_DESCRIPTION) - fprintf(f, "%sDescription=%s%s%s", prefix, color, c->description, suffix); - - if (terse && (c->mask & (SD_BUS_CREDS_SELINUX_CONTEXT|SD_BUS_CREDS_DESCRIPTION))) - fputs("\n", f); - - if (c->mask & SD_BUS_CREDS_CGROUP) - fprintf(f, "%sCGroup=%s%s%s", prefix, color, c->cgroup, suffix); - s = NULL; - r = sd_bus_creds_get_unit(c, &s); - if (r != -ENODATA) - fprintf(f, "%sUnit=%s%s%s", prefix, color, strna(s), suffix); - s = NULL; - v = sd_bus_creds_get_slice(c, &s); - if (v != -ENODATA) - fprintf(f, "%sSlice=%s%s%s", prefix, color, strna(s), suffix); - s = NULL; - q = sd_bus_creds_get_user_unit(c, &s); - if (q != -ENODATA) - fprintf(f, "%sUserUnit=%s%s%s", prefix, color, strna(s), suffix); - s = NULL; - w = sd_bus_creds_get_user_slice(c, &s); - if (w != -ENODATA) - fprintf(f, "%sUserSlice=%s%s%s", prefix, color, strna(s), suffix); - s = NULL; - z = sd_bus_creds_get_session(c, &s); - if (z != -ENODATA) - fprintf(f, "%sSession=%s%s%s", prefix, color, strna(s), suffix); - - if (terse && ((c->mask & SD_BUS_CREDS_CGROUP) || r != -ENODATA || q != -ENODATA || v != -ENODATA || w != -ENODATA || z != -ENODATA)) - fputs("\n", f); - - r = sd_bus_creds_get_audit_login_uid(c, &audit_loginuid); - if (r >= 0) - fprintf(f, "%sAuditLoginUID=%s"UID_FMT"%s", prefix, color, audit_loginuid, suffix); - else if (r != -ENODATA) - fprintf(f, "%sAuditLoginUID=%sn/a%s", prefix, color, suffix); - q = sd_bus_creds_get_audit_session_id(c, &audit_sessionid); - if (q >= 0) - fprintf(f, "%sAuditSessionID=%s%"PRIu32"%s", prefix, color, audit_sessionid, suffix); - else if (q != -ENODATA) - fprintf(f, "%sAuditSessionID=%sn/a%s", prefix, color, suffix); - - if (terse && (r != -ENODATA || q != -ENODATA)) - fputs("\n", f); - - if (c->mask & SD_BUS_CREDS_UNIQUE_NAME) - fprintf(f, "%sUniqueName=%s%s%s", prefix, color, c->unique_name, suffix); - - if (sd_bus_creds_get_well_known_names(c, &well_known) >= 0) { - char **i; - - fprintf(f, "%sWellKnownNames=%s", prefix, color); - STRV_FOREACH(i, well_known) { - if (i != well_known) - fputc(' ', f); - - fputs(*i, f); - } - - fprintf(f, "%s", suffix); - } - - if (terse && (c->mask & SD_BUS_CREDS_UNIQUE_NAME || well_known)) - fputc('\n', f); - - dump_capabilities(c, f, "EffectiveCapabilities", terse, sd_bus_creds_has_effective_cap); - dump_capabilities(c, f, "PermittedCapabilities", terse, sd_bus_creds_has_permitted_cap); - dump_capabilities(c, f, "InheritableCapabilities", terse, sd_bus_creds_has_inheritable_cap); - dump_capabilities(c, f, "BoundingCapabilities", terse, sd_bus_creds_has_bounding_cap); - - return 0; -} - -/* - * For details about the file format, see: - * - * http://wiki.wireshark.org/Development/LibpcapFileFormat - */ - -typedef struct _packed_ pcap_hdr_s { - uint32_t magic_number; /* magic number */ - uint16_t version_major; /* major version number */ - uint16_t version_minor; /* minor version number */ - int32_t thiszone; /* GMT to local correction */ - uint32_t sigfigs; /* accuracy of timestamps */ - uint32_t snaplen; /* max length of captured packets, in octets */ - uint32_t network; /* data link type */ -} pcap_hdr_t ; - -typedef struct _packed_ pcaprec_hdr_s { - uint32_t ts_sec; /* timestamp seconds */ - uint32_t ts_usec; /* timestamp microseconds */ - uint32_t incl_len; /* number of octets of packet saved in file */ - uint32_t orig_len; /* actual length of packet */ -} pcaprec_hdr_t; - -int bus_pcap_header(size_t snaplen, FILE *f) { - - pcap_hdr_t hdr = { - .magic_number = 0xa1b2c3d4U, - .version_major = 2, - .version_minor = 4, - .thiszone = 0, /* UTC */ - .sigfigs = 0, - .network = 231, /* D-Bus */ - }; - - if (!f) - f = stdout; - - assert(snaplen > 0); - assert((size_t) (uint32_t) snaplen == snaplen); - - hdr.snaplen = (uint32_t) snaplen; - - fwrite(&hdr, 1, sizeof(hdr), f); - - return fflush_and_check(f); -} - -int bus_message_pcap_frame(sd_bus_message *m, size_t snaplen, FILE *f) { - struct bus_body_part *part; - pcaprec_hdr_t hdr = {}; - struct timeval tv; - unsigned i; - size_t w; - - if (!f) - f = stdout; - - assert(m); - assert(snaplen > 0); - assert((size_t) (uint32_t) snaplen == snaplen); - - if (m->realtime != 0) - timeval_store(&tv, m->realtime); - else - assert_se(gettimeofday(&tv, NULL) >= 0); - - hdr.ts_sec = tv.tv_sec; - hdr.ts_usec = tv.tv_usec; - hdr.orig_len = BUS_MESSAGE_SIZE(m); - hdr.incl_len = MIN(hdr.orig_len, snaplen); - - /* write the pcap header */ - fwrite(&hdr, 1, sizeof(hdr), f); - - /* write the dbus header */ - w = MIN(BUS_MESSAGE_BODY_BEGIN(m), snaplen); - fwrite(m->header, 1, w, f); - snaplen -= w; - - /* write the dbus body */ - MESSAGE_FOREACH_PART(part, i, m) { - if (snaplen <= 0) - break; - - w = MIN(part->size, snaplen); - fwrite(part->data, 1, w, f); - snaplen -= w; - } - - return fflush_and_check(f); -} diff --git a/src/libsystemd/sd-bus/bus-dump.h b/src/libsystemd/sd-bus/bus-dump.h deleted file mode 100644 index 874e86d09c..0000000000 --- a/src/libsystemd/sd-bus/bus-dump.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 - -#include "sd-bus.h" - -enum { - BUS_MESSAGE_DUMP_WITH_HEADER = 1, - BUS_MESSAGE_DUMP_SUBTREE_ONLY = 2, -}; - -int bus_message_dump(sd_bus_message *m, FILE *f, unsigned flags); - -int bus_creds_dump(sd_bus_creds *c, FILE *f, bool terse); - -int bus_pcap_header(size_t snaplen, FILE *f); -int bus_message_pcap_frame(sd_bus_message *m, size_t snaplen, FILE *f); diff --git a/src/libsystemd/sd-bus/bus-error.c b/src/libsystemd/sd-bus/bus-error.c deleted file mode 100644 index 26219bdeed..0000000000 --- a/src/libsystemd/sd-bus/bus-error.c +++ /dev/null @@ -1,608 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 -#include -#include -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "errno-list.h" -#include "string-util.h" -#include "util.h" - -BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_standard_errors[] = { - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Failed", EACCES), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoMemory", ENOMEM), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.ServiceUnknown", EHOSTUNREACH), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NameHasNoOwner", ENXIO), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoReply", ETIMEDOUT), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.IOError", EIO), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.BadAddress", EADDRNOTAVAIL), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NotSupported", EOPNOTSUPP), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.LimitsExceeded", ENOBUFS), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AccessDenied", EACCES), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AuthFailed", EACCES), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InteractiveAuthorizationRequired", EACCES), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoServer", EHOSTDOWN), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Timeout", ETIMEDOUT), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoNetwork", ENONET), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AddressInUse", EADDRINUSE), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Disconnected", ECONNRESET), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidArgs", EINVAL), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.FileNotFound", ENOENT), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.FileExists", EEXIST), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownMethod", EBADR), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownObject", EBADR), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownInterface", EBADR), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownProperty", EBADR), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.PropertyReadOnly", EROFS), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnixProcessIdUnknown", ESRCH), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidSignature", EINVAL), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InconsistentMessage", EBADMSG), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.TimedOut", ETIMEDOUT), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.MatchRuleInvalid", EINVAL), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidFileContent", EINVAL), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.MatchRuleNotFound", ENOENT), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown", ESRCH), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.ObjectPathInUse", EBUSY), - SD_BUS_ERROR_MAP_END -}; - -/* GCC maps this magically to the beginning and end of the BUS_ERROR_MAP section. - * Hide them; for currently unknown reasons they get exported to the shared libries - * even without being listed in the sym file. */ -extern const sd_bus_error_map __start_BUS_ERROR_MAP[] _hidden_; -extern const sd_bus_error_map __stop_BUS_ERROR_MAP[] _hidden_; - -/* Additional maps registered with sd_bus_error_add_map() are in this - * NULL terminated array */ -static const sd_bus_error_map **additional_error_maps = NULL; - -static int bus_error_name_to_errno(const char *name) { - const sd_bus_error_map **map, *m; - const char *p; - int r; - - if (!name) - return EINVAL; - - p = startswith(name, "System.Error."); - if (p) { - r = errno_from_name(p); - if (r < 0) - return EIO; - - return r; - } - - if (additional_error_maps) - for (map = additional_error_maps; *map; map++) - for (m = *map;; m++) { - /* For additional error maps the end marker is actually the end marker */ - if (m->code == BUS_ERROR_MAP_END_MARKER) - break; - - if (streq(m->name, name)) - return m->code; - } - - m = __start_BUS_ERROR_MAP; - while (m < __stop_BUS_ERROR_MAP) { - /* For magic ELF error maps, the end marker might - * appear in the middle of things, since multiple maps - * might appear in the same section. Hence, let's skip - * over it, but realign the pointer to the next 8 byte - * boundary, which is the selected alignment for the - * arrays. */ - if (m->code == BUS_ERROR_MAP_END_MARKER) { - m = ALIGN8_PTR(m+1); - continue; - } - - if (streq(m->name, name)) - return m->code; - - m++; - } - - return EIO; -} - -static sd_bus_error errno_to_bus_error_const(int error) { - - if (error < 0) - error = -error; - - switch (error) { - - case ENOMEM: - return BUS_ERROR_OOM; - - case EPERM: - case EACCES: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_ACCESS_DENIED, "Access denied"); - - case EINVAL: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid argument"); - - case ESRCH: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN, "No such process"); - - case ENOENT: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FILE_NOT_FOUND, "File not found"); - - case EEXIST: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FILE_EXISTS, "File exists"); - - case ETIMEDOUT: - case ETIME: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_TIMEOUT, "Timed out"); - - case EIO: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_IO_ERROR, "Input/output error"); - - case ENETRESET: - case ECONNABORTED: - case ECONNRESET: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_DISCONNECTED, "Disconnected"); - - case EOPNOTSUPP: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NOT_SUPPORTED, "Not supported"); - - case EADDRNOTAVAIL: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_BAD_ADDRESS, "Address not available"); - - case ENOBUFS: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_LIMITS_EXCEEDED, "Limits exceeded"); - - case EADDRINUSE: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_ADDRESS_IN_USE, "Address in use"); - - case EBADMSG: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Inconsistent message"); - } - - return SD_BUS_ERROR_NULL; -} - -static int errno_to_bus_error_name_new(int error, char **ret) { - const char *name; - char *n; - - if (error < 0) - error = -error; - - name = errno_to_name(error); - if (!name) - return 0; - - n = strappend("System.Error.", name); - if (!n) - return -ENOMEM; - - *ret = n; - return 1; -} - -bool bus_error_is_dirty(sd_bus_error *e) { - if (!e) - return false; - - return e->name || e->message || e->_need_free != 0; -} - -_public_ void sd_bus_error_free(sd_bus_error *e) { - if (!e) - return; - - if (e->_need_free > 0) { - free((void*) e->name); - free((void*) e->message); - } - - e->name = e->message = NULL; - e->_need_free = 0; -} - -_public_ int sd_bus_error_set(sd_bus_error *e, const char *name, const char *message) { - - if (!name) - return 0; - if (!e) - goto finish; - - assert_return(!bus_error_is_dirty(e), -EINVAL); - - e->name = strdup(name); - if (!e->name) { - *e = BUS_ERROR_OOM; - return -ENOMEM; - } - - if (message) - e->message = strdup(message); - - e->_need_free = 1; - -finish: - return -bus_error_name_to_errno(name); -} - -int bus_error_setfv(sd_bus_error *e, const char *name, const char *format, va_list ap) { - - if (!name) - return 0; - - if (e) { - assert_return(!bus_error_is_dirty(e), -EINVAL); - - e->name = strdup(name); - if (!e->name) { - *e = BUS_ERROR_OOM; - return -ENOMEM; - } - - /* If we hit OOM on formatting the pretty message, we ignore - * this, since we at least managed to write the error name */ - if (format) - (void) vasprintf((char**) &e->message, format, ap); - - e->_need_free = 1; - } - - return -bus_error_name_to_errno(name); -} - -_public_ int sd_bus_error_setf(sd_bus_error *e, const char *name, const char *format, ...) { - - if (format) { - int r; - va_list ap; - - va_start(ap, format); - r = bus_error_setfv(e, name, format, ap); - va_end(ap); - - return r; - } - - return sd_bus_error_set(e, name, NULL); -} - -_public_ int sd_bus_error_copy(sd_bus_error *dest, const sd_bus_error *e) { - - if (!sd_bus_error_is_set(e)) - return 0; - if (!dest) - goto finish; - - assert_return(!bus_error_is_dirty(dest), -EINVAL); - - /* - * _need_free < 0 indicates that the error is temporarily const, needs deep copying - * _need_free == 0 indicates that the error is perpetually const, needs no deep copying - * _need_free > 0 indicates that the error is fully dynamic, needs deep copying - */ - - if (e->_need_free == 0) - *dest = *e; - else { - dest->name = strdup(e->name); - if (!dest->name) { - *dest = BUS_ERROR_OOM; - return -ENOMEM; - } - - if (e->message) - dest->message = strdup(e->message); - - dest->_need_free = 1; - } - -finish: - return -bus_error_name_to_errno(e->name); -} - -_public_ int sd_bus_error_set_const(sd_bus_error *e, const char *name, const char *message) { - if (!name) - return 0; - if (!e) - goto finish; - - assert_return(!bus_error_is_dirty(e), -EINVAL); - - *e = SD_BUS_ERROR_MAKE_CONST(name, message); - -finish: - return -bus_error_name_to_errno(name); -} - -_public_ int sd_bus_error_is_set(const sd_bus_error *e) { - if (!e) - return 0; - - return !!e->name; -} - -_public_ int sd_bus_error_has_name(const sd_bus_error *e, const char *name) { - if (!e) - return 0; - - return streq_ptr(e->name, name); -} - -_public_ int sd_bus_error_get_errno(const sd_bus_error* e) { - if (!e) - return 0; - - if (!e->name) - return 0; - - return bus_error_name_to_errno(e->name); -} - -static void bus_error_strerror(sd_bus_error *e, int error) { - size_t k = 64; - char *m; - - assert(e); - - for (;;) { - char *x; - - m = new(char, k); - if (!m) - return; - - errno = 0; - x = strerror_r(error, m, k); - if (errno == ERANGE || strlen(x) >= k - 1) { - free(m); - k *= 2; - continue; - } - - if (errno) { - free(m); - return; - } - - if (x == m) { - if (e->_need_free > 0) { - /* Error is already dynamic, let's just update the message */ - free((char*) e->message); - e->message = x; - - } else { - char *t; - /* Error was const so far, let's make it dynamic, if we can */ - - t = strdup(e->name); - if (!t) { - free(m); - return; - } - - e->_need_free = 1; - e->name = t; - e->message = x; - } - } else { - free(m); - - if (e->_need_free > 0) { - char *t; - - /* Error is dynamic, let's hence make the message also dynamic */ - t = strdup(x); - if (!t) - return; - - free((char*) e->message); - e->message = t; - } else { - /* Error is const, hence we can just override */ - e->message = x; - } - } - - return; - } -} - -_public_ int sd_bus_error_set_errno(sd_bus_error *e, int error) { - - if (error < 0) - error = -error; - - if (!e) - return -error; - if (error == 0) - return -error; - - assert_return(!bus_error_is_dirty(e), -EINVAL); - - /* First, try a const translation */ - *e = errno_to_bus_error_const(error); - - if (!sd_bus_error_is_set(e)) { - int k; - - /* If that didn't work, try a dynamic one. */ - - k = errno_to_bus_error_name_new(error, (char**) &e->name); - if (k > 0) - e->_need_free = 1; - else if (k < 0) { - *e = BUS_ERROR_OOM; - return -error; - } else - *e = BUS_ERROR_FAILED; - } - - /* Now, fill in the message from strerror() if we can */ - bus_error_strerror(e, error); - return -error; -} - -_public_ int sd_bus_error_set_errnofv(sd_bus_error *e, int error, const char *format, va_list ap) { - PROTECT_ERRNO; - int r; - - if (error < 0) - error = -error; - - if (!e) - return -error; - if (error == 0) - return 0; - - assert_return(!bus_error_is_dirty(e), -EINVAL); - - /* First, try a const translation */ - *e = errno_to_bus_error_const(error); - - if (!sd_bus_error_is_set(e)) { - int k; - - /* If that didn't work, try a dynamic one */ - - k = errno_to_bus_error_name_new(error, (char**) &e->name); - if (k > 0) - e->_need_free = 1; - else if (k < 0) { - *e = BUS_ERROR_OOM; - return -ENOMEM; - } else - *e = BUS_ERROR_FAILED; - } - - if (format) { - char *m; - - /* Then, let's try to fill in the supplied message */ - - errno = error; /* Make sure that %m resolves to the specified error */ - r = vasprintf(&m, format, ap); - if (r >= 0) { - - if (e->_need_free <= 0) { - char *t; - - t = strdup(e->name); - if (t) { - e->_need_free = 1; - e->name = t; - e->message = m; - return -error; - } - - free(m); - } else { - free((char*) e->message); - e->message = m; - return -error; - } - } - } - - /* If that didn't work, use strerror() for the message */ - bus_error_strerror(e, error); - return -error; -} - -_public_ int sd_bus_error_set_errnof(sd_bus_error *e, int error, const char *format, ...) { - int r; - - if (error < 0) - error = -error; - - if (!e) - return -error; - if (error == 0) - return 0; - - assert_return(!bus_error_is_dirty(e), -EINVAL); - - if (format) { - va_list ap; - - va_start(ap, format); - r = sd_bus_error_set_errnofv(e, error, format, ap); - va_end(ap); - - return r; - } - - return sd_bus_error_set_errno(e, error); -} - -const char *bus_error_message(const sd_bus_error *e, int error) { - - if (e) { - /* Sometimes, the D-Bus server is a little bit too verbose with - * its error messages, so let's override them here */ - if (sd_bus_error_has_name(e, SD_BUS_ERROR_ACCESS_DENIED)) - return "Access denied"; - - if (e->message) - return e->message; - } - - if (error < 0) - error = -error; - - return strerror(error); -} - -static bool map_ok(const sd_bus_error_map *map) { - for (; map->code != BUS_ERROR_MAP_END_MARKER; map++) - if (!map->name || map->code <=0) - return false; - return true; -} - -_public_ int sd_bus_error_add_map(const sd_bus_error_map *map) { - const sd_bus_error_map **maps = NULL; - unsigned n = 0; - - assert_return(map, -EINVAL); - assert_return(map_ok(map), -EINVAL); - - if (additional_error_maps) - for (; additional_error_maps[n] != NULL; n++) - if (additional_error_maps[n] == map) - return 0; - - maps = realloc_multiply(additional_error_maps, sizeof(struct sd_bus_error_map*), n + 2); - if (!maps) - return -ENOMEM; - - maps[n] = map; - maps[n+1] = NULL; - - additional_error_maps = maps; - return 1; -} diff --git a/src/libsystemd/sd-bus/bus-error.h b/src/libsystemd/sd-bus/bus-error.h deleted file mode 100644 index e2c4cf4b3f..0000000000 --- a/src/libsystemd/sd-bus/bus-error.h +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -#include "macro.h" - -bool bus_error_is_dirty(sd_bus_error *e); - -const char *bus_error_message(const sd_bus_error *e, int error); - -int bus_error_setfv(sd_bus_error *e, const char *name, const char *format, va_list ap) _printf_(3,0); -int bus_error_set_errnofv(sd_bus_error *e, int error, const char *format, va_list ap) _printf_(3,0); - -#define BUS_ERROR_OOM SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_MEMORY, "Out of memory") -#define BUS_ERROR_FAILED SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FAILED, "Operation failed") - -/* - * There are two ways to register error maps with the error translation - * logic: by using BUS_ERROR_MAP_ELF_REGISTER, which however only - * works when linked into the same ELF module, or via - * sd_bus_error_add_map() which is the official, external API, that - * works from any module. - * - * Note that BUS_ERROR_MAP_ELF_REGISTER has to be used as decorator in - * the bus error table, and BUS_ERROR_MAP_ELF_USE has to be used at - * least once per compilation unit (i.e. per library), to ensure that - * the error map is really added to the final binary. - */ - -#define BUS_ERROR_MAP_ELF_REGISTER \ - __attribute__ ((__section__("BUS_ERROR_MAP"))) \ - __attribute__ ((__used__)) \ - __attribute__ ((aligned(8))) - -#define BUS_ERROR_MAP_ELF_USE(errors) \ - extern const sd_bus_error_map errors[]; \ - __attribute__ ((used)) static const sd_bus_error_map * const CONCATENATE(errors ## _copy_, __COUNTER__) = errors; - -/* We use something exotic as end marker, to ensure people build the - * maps using the macsd-ros. */ -#define BUS_ERROR_MAP_END_MARKER -'x' - -BUS_ERROR_MAP_ELF_USE(bus_standard_errors); diff --git a/src/libsystemd/sd-bus/bus-gvariant.c b/src/libsystemd/sd-bus/bus-gvariant.c deleted file mode 100644 index 58782767fa..0000000000 --- a/src/libsystemd/sd-bus/bus-gvariant.c +++ /dev/null @@ -1,311 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "bus-gvariant.h" -#include "bus-signature.h" -#include "bus-type.h" - -int bus_gvariant_get_size(const char *signature) { - const char *p; - int sum = 0, r; - - /* For fixed size structs. Fails for variable size structs. */ - - p = signature; - while (*p != 0) { - size_t n; - - r = signature_element_length(p, &n); - if (r < 0) - return r; - else { - char t[n+1]; - - memcpy(t, p, n); - t[n] = 0; - - r = bus_gvariant_get_alignment(t); - if (r < 0) - return r; - - sum = ALIGN_TO(sum, r); - } - - switch (*p) { - - case SD_BUS_TYPE_BOOLEAN: - case SD_BUS_TYPE_BYTE: - sum += 1; - break; - - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: - sum += 2; - break; - - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - case SD_BUS_TYPE_UNIX_FD: - sum += 4; - break; - - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: - case SD_BUS_TYPE_DOUBLE: - sum += 8; - break; - - case SD_BUS_TYPE_STRUCT_BEGIN: - case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { - if (n == 2) { - /* unary type () has fixed size of 1 */ - r = 1; - } else { - char t[n-1]; - - memcpy(t, p + 1, n - 2); - t[n - 2] = 0; - - r = bus_gvariant_get_size(t); - if (r < 0) - return r; - } - - sum += r; - break; - } - - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_SIGNATURE: - case SD_BUS_TYPE_ARRAY: - case SD_BUS_TYPE_VARIANT: - return -EINVAL; - - default: - assert_not_reached("Unknown signature type"); - } - - p += n; - } - - r = bus_gvariant_get_alignment(signature); - if (r < 0) - return r; - - return ALIGN_TO(sum, r); -} - -int bus_gvariant_get_alignment(const char *signature) { - size_t alignment = 1; - const char *p; - int r; - - p = signature; - while (*p != 0 && alignment < 8) { - size_t n; - int a; - - r = signature_element_length(p, &n); - if (r < 0) - return r; - - switch (*p) { - - case SD_BUS_TYPE_BYTE: - case SD_BUS_TYPE_BOOLEAN: - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_SIGNATURE: - a = 1; - break; - - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: - a = 2; - break; - - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - case SD_BUS_TYPE_UNIX_FD: - a = 4; - break; - - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: - case SD_BUS_TYPE_DOUBLE: - case SD_BUS_TYPE_VARIANT: - a = 8; - break; - - case SD_BUS_TYPE_ARRAY: { - char t[n]; - - memcpy(t, p + 1, n - 1); - t[n - 1] = 0; - - a = bus_gvariant_get_alignment(t); - break; - } - - case SD_BUS_TYPE_STRUCT_BEGIN: - case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { - char t[n-1]; - - memcpy(t, p + 1, n - 2); - t[n - 2] = 0; - - a = bus_gvariant_get_alignment(t); - break; - } - - default: - assert_not_reached("Unknown signature type"); - } - - if (a < 0) - return a; - - assert(a > 0 && a <= 8); - if ((size_t) a > alignment) - alignment = (size_t) a; - - p += n; - } - - return alignment; -} - -int bus_gvariant_is_fixed_size(const char *signature) { - const char *p; - int r; - - assert(signature); - - p = signature; - while (*p != 0) { - size_t n; - - r = signature_element_length(p, &n); - if (r < 0) - return r; - - switch (*p) { - - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_SIGNATURE: - case SD_BUS_TYPE_ARRAY: - case SD_BUS_TYPE_VARIANT: - return 0; - - case SD_BUS_TYPE_BYTE: - case SD_BUS_TYPE_BOOLEAN: - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - case SD_BUS_TYPE_UNIX_FD: - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: - case SD_BUS_TYPE_DOUBLE: - break; - - case SD_BUS_TYPE_STRUCT_BEGIN: - case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { - char t[n-1]; - - memcpy(t, p + 1, n - 2); - t[n - 2] = 0; - - r = bus_gvariant_is_fixed_size(t); - if (r <= 0) - return r; - break; - } - - default: - assert_not_reached("Unknown signature type"); - } - - p += n; - } - - return true; -} - -size_t bus_gvariant_determine_word_size(size_t sz, size_t extra) { - if (sz + extra <= 0xFF) - return 1; - else if (sz + extra*2 <= 0xFFFF) - return 2; - else if (sz + extra*4 <= 0xFFFFFFFF) - return 4; - else - return 8; -} - -size_t bus_gvariant_read_word_le(void *p, size_t sz) { - union { - uint16_t u16; - uint32_t u32; - uint64_t u64; - } x; - - assert(p); - - if (sz == 1) - return *(uint8_t*) p; - - memcpy(&x, p, sz); - - if (sz == 2) - return le16toh(x.u16); - else if (sz == 4) - return le32toh(x.u32); - else if (sz == 8) - return le64toh(x.u64); - - assert_not_reached("unknown word width"); -} - -void bus_gvariant_write_word_le(void *p, size_t sz, size_t value) { - union { - uint16_t u16; - uint32_t u32; - uint64_t u64; - } x; - - assert(p); - assert(sz == 8 || (value < (1ULL << (sz*8)))); - - if (sz == 1) { - *(uint8_t*) p = value; - return; - } else if (sz == 2) - x.u16 = htole16((uint16_t) value); - else if (sz == 4) - x.u32 = htole32((uint32_t) value); - else if (sz == 8) - x.u64 = htole64((uint64_t) value); - else - assert_not_reached("unknown word width"); - - memcpy(p, &x, sz); -} diff --git a/src/libsystemd/sd-bus/bus-gvariant.h b/src/libsystemd/sd-bus/bus-gvariant.h deleted file mode 100644 index 6da637fb05..0000000000 --- a/src/libsystemd/sd-bus/bus-gvariant.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "macro.h" - -int bus_gvariant_get_size(const char *signature) _pure_; -int bus_gvariant_get_alignment(const char *signature) _pure_; -int bus_gvariant_is_fixed_size(const char *signature) _pure_; - -size_t bus_gvariant_determine_word_size(size_t sz, size_t extra); -void bus_gvariant_write_word_le(void *p, size_t sz, size_t value); -size_t bus_gvariant_read_word_le(void *p, size_t sz); diff --git a/src/libsystemd/sd-bus/bus-internal.c b/src/libsystemd/sd-bus/bus-internal.c deleted file mode 100644 index caca679086..0000000000 --- a/src/libsystemd/sd-bus/bus-internal.c +++ /dev/null @@ -1,374 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "alloc-util.h" -#include "bus-internal.h" -#include "bus-message.h" -#include "hexdecoct.h" -#include "string-util.h" - -bool object_path_is_valid(const char *p) { - const char *q; - bool slash; - - if (!p) - return false; - - if (p[0] != '/') - return false; - - if (p[1] == 0) - return true; - - for (slash = true, q = p+1; *q; q++) - if (*q == '/') { - if (slash) - return false; - - slash = true; - } else { - bool good; - - good = - (*q >= 'a' && *q <= 'z') || - (*q >= 'A' && *q <= 'Z') || - (*q >= '0' && *q <= '9') || - *q == '_'; - - if (!good) - return false; - - slash = false; - } - - if (slash) - return false; - - return true; -} - -char* object_path_startswith(const char *a, const char *b) { - const char *p; - - if (!object_path_is_valid(a) || - !object_path_is_valid(b)) - return NULL; - - if (streq(b, "/")) - return (char*) a + 1; - - p = startswith(a, b); - if (!p) - return NULL; - - if (*p == 0) - return (char*) p; - - if (*p == '/') - return (char*) p + 1; - - return NULL; -} - -bool interface_name_is_valid(const char *p) { - const char *q; - bool dot, found_dot = false; - - if (isempty(p)) - return false; - - for (dot = true, q = p; *q; q++) - if (*q == '.') { - if (dot) - return false; - - found_dot = dot = true; - } else { - bool good; - - good = - (*q >= 'a' && *q <= 'z') || - (*q >= 'A' && *q <= 'Z') || - (!dot && *q >= '0' && *q <= '9') || - *q == '_'; - - if (!good) - return false; - - dot = false; - } - - if (q - p > 255) - return false; - - if (dot) - return false; - - if (!found_dot) - return false; - - return true; -} - -bool service_name_is_valid(const char *p) { - const char *q; - bool dot, found_dot = false, unique; - - if (isempty(p)) - return false; - - unique = p[0] == ':'; - - for (dot = true, q = unique ? p+1 : p; *q; q++) - if (*q == '.') { - if (dot) - return false; - - found_dot = dot = true; - } else { - bool good; - - good = - (*q >= 'a' && *q <= 'z') || - (*q >= 'A' && *q <= 'Z') || - ((!dot || unique) && *q >= '0' && *q <= '9') || - *q == '_' || *q == '-'; - - if (!good) - return false; - - dot = false; - } - - if (q - p > 255) - return false; - - if (dot) - return false; - - if (!found_dot) - return false; - - return true; -} - -char* service_name_startswith(const char *a, const char *b) { - const char *p; - - if (!service_name_is_valid(a) || - !service_name_is_valid(b)) - return NULL; - - p = startswith(a, b); - if (!p) - return NULL; - - if (*p == 0) - return (char*) p; - - if (*p == '.') - return (char*) p + 1; - - return NULL; -} - -bool member_name_is_valid(const char *p) { - const char *q; - - if (isempty(p)) - return false; - - for (q = p; *q; q++) { - bool good; - - good = - (*q >= 'a' && *q <= 'z') || - (*q >= 'A' && *q <= 'Z') || - (*q >= '0' && *q <= '9') || - *q == '_'; - - if (!good) - return false; - } - - if (q - p > 255) - return false; - - return true; -} - -/* - * Complex pattern match - * This checks whether @a is a 'complex-prefix' of @b, or @b is a - * 'complex-prefix' of @a, based on strings that consist of labels with @c as - * spearator. This function returns true if: - * - both strings are equal - * - either is a prefix of the other and ends with @c - * The second rule makes sure that either string needs to be fully included in - * the other, and the string which is considered the prefix needs to end with a - * separator. - */ -static bool complex_pattern_check(char c, const char *a, const char *b) { - bool separator = false; - - if (!a && !b) - return true; - - if (!a || !b) - return false; - - for (;;) { - if (*a != *b) - return (separator && (*a == 0 || *b == 0)); - - if (*a == 0) - return true; - - separator = *a == c; - - a++, b++; - } -} - -bool namespace_complex_pattern(const char *pattern, const char *value) { - return complex_pattern_check('.', pattern, value); -} - -bool path_complex_pattern(const char *pattern, const char *value) { - return complex_pattern_check('/', pattern, value); -} - -/* - * Simple pattern match - * This checks whether @a is a 'simple-prefix' of @b, based on strings that - * consist of labels with @c as separator. This function returns true, if: - * - if @a and @b are equal - * - if @a is a prefix of @b, and the first following character in @b (or the - * last character in @a) is @c - * The second rule basically makes sure that if @a is a prefix of @b, then @b - * must follow with a new label separated by @c. It cannot extend the label. - */ -static bool simple_pattern_check(char c, const char *a, const char *b) { - bool separator = false; - - if (!a && !b) - return true; - - if (!a || !b) - return false; - - for (;;) { - if (*a != *b) - return *a == 0 && (*b == c || separator); - - if (*a == 0) - return true; - - separator = *a == c; - - a++, b++; - } -} - -bool namespace_simple_pattern(const char *pattern, const char *value) { - return simple_pattern_check('.', pattern, value); -} - -bool path_simple_pattern(const char *pattern, const char *value) { - return simple_pattern_check('/', pattern, value); -} - -int bus_message_type_from_string(const char *s, uint8_t *u) { - if (streq(s, "signal")) - *u = SD_BUS_MESSAGE_SIGNAL; - else if (streq(s, "method_call")) - *u = SD_BUS_MESSAGE_METHOD_CALL; - else if (streq(s, "error")) - *u = SD_BUS_MESSAGE_METHOD_ERROR; - else if (streq(s, "method_return")) - *u = SD_BUS_MESSAGE_METHOD_RETURN; - else - return -EINVAL; - - return 0; -} - -const char *bus_message_type_to_string(uint8_t u) { - if (u == SD_BUS_MESSAGE_SIGNAL) - return "signal"; - else if (u == SD_BUS_MESSAGE_METHOD_CALL) - return "method_call"; - else if (u == SD_BUS_MESSAGE_METHOD_ERROR) - return "error"; - else if (u == SD_BUS_MESSAGE_METHOD_RETURN) - return "method_return"; - else - return NULL; -} - -char *bus_address_escape(const char *v) { - const char *a; - char *r, *b; - - r = new(char, strlen(v)*3+1); - if (!r) - return NULL; - - for (a = v, b = r; *a; a++) { - - if ((*a >= '0' && *a <= '9') || - (*a >= 'a' && *a <= 'z') || - (*a >= 'A' && *a <= 'Z') || - strchr("_-/.", *a)) - *(b++) = *a; - else { - *(b++) = '%'; - *(b++) = hexchar(*a >> 4); - *(b++) = hexchar(*a & 0xF); - } - } - - *b = 0; - return r; -} - -int bus_maybe_reply_error(sd_bus_message *m, int r, sd_bus_error *error) { - assert(m); - - if (r < 0) { - if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) - sd_bus_reply_method_errno(m, r, error); - - } else if (sd_bus_error_is_set(error)) { - if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) - sd_bus_reply_method_error(m, error); - } else - return r; - - log_debug("Failed to process message [type=%s sender=%s path=%s interface=%s member=%s signature=%s]: %s", - bus_message_type_to_string(m->header->type), - strna(m->sender), - strna(m->path), - strna(m->interface), - strna(m->member), - strna(m->root_container.signature), - bus_error_message(error, r)); - - return 1; -} diff --git a/src/libsystemd/sd-bus/bus-internal.h b/src/libsystemd/sd-bus/bus-internal.h deleted file mode 100644 index 216d9f62bc..0000000000 --- a/src/libsystemd/sd-bus/bus-internal.h +++ /dev/null @@ -1,399 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 - -#include "sd-bus.h" - -#include "bus-error.h" -#include "bus-kernel.h" -#include "bus-match.h" -#include "hashmap.h" -#include "kdbus.h" -#include "list.h" -#include "prioq.h" -#include "refcnt.h" -#include "socket-util.h" -#include "util.h" - -struct reply_callback { - sd_bus_message_handler_t callback; - usec_t timeout; - uint64_t cookie; - unsigned prioq_idx; -}; - -struct filter_callback { - sd_bus_message_handler_t callback; - - unsigned last_iteration; - - LIST_FIELDS(struct filter_callback, callbacks); -}; - -struct match_callback { - sd_bus_message_handler_t callback; - - uint64_t cookie; - unsigned last_iteration; - - char *match_string; - - struct bus_match_node *match_node; -}; - -struct node { - char *path; - struct node *parent; - LIST_HEAD(struct node, child); - LIST_FIELDS(struct node, siblings); - - LIST_HEAD(struct node_callback, callbacks); - LIST_HEAD(struct node_vtable, vtables); - LIST_HEAD(struct node_enumerator, enumerators); - LIST_HEAD(struct node_object_manager, object_managers); -}; - -struct node_callback { - struct node *node; - - bool is_fallback; - sd_bus_message_handler_t callback; - - unsigned last_iteration; - - LIST_FIELDS(struct node_callback, callbacks); -}; - -struct node_enumerator { - struct node *node; - - sd_bus_node_enumerator_t callback; - - unsigned last_iteration; - - LIST_FIELDS(struct node_enumerator, enumerators); -}; - -struct node_object_manager { - struct node *node; - - LIST_FIELDS(struct node_object_manager, object_managers); -}; - -struct node_vtable { - struct node *node; - - char *interface; - bool is_fallback; - const sd_bus_vtable *vtable; - sd_bus_object_find_t find; - - unsigned last_iteration; - - LIST_FIELDS(struct node_vtable, vtables); -}; - -struct vtable_member { - const char *path; - const char *interface; - const char *member; - struct node_vtable *parent; - unsigned last_iteration; - const sd_bus_vtable *vtable; -}; - -typedef enum BusSlotType { - BUS_REPLY_CALLBACK, - BUS_FILTER_CALLBACK, - BUS_MATCH_CALLBACK, - BUS_NODE_CALLBACK, - BUS_NODE_ENUMERATOR, - BUS_NODE_VTABLE, - BUS_NODE_OBJECT_MANAGER, - _BUS_SLOT_INVALID = -1, -} BusSlotType; - -struct sd_bus_slot { - unsigned n_ref; - sd_bus *bus; - void *userdata; - BusSlotType type:5; - bool floating:1; - bool match_added:1; - char *description; - - LIST_FIELDS(sd_bus_slot, slots); - - union { - struct reply_callback reply_callback; - struct filter_callback filter_callback; - struct match_callback match_callback; - struct node_callback node_callback; - struct node_enumerator node_enumerator; - struct node_object_manager node_object_manager; - struct node_vtable node_vtable; - }; -}; - -enum bus_state { - BUS_UNSET, - BUS_OPENING, - BUS_AUTHENTICATING, - BUS_HELLO, - BUS_RUNNING, - BUS_CLOSING, - BUS_CLOSED -}; - -static inline bool BUS_IS_OPEN(enum bus_state state) { - return state > BUS_UNSET && state < BUS_CLOSING; -} - -enum bus_auth { - _BUS_AUTH_INVALID, - BUS_AUTH_EXTERNAL, - BUS_AUTH_ANONYMOUS -}; - -struct sd_bus { - /* We use atomic ref counting here since sd_bus_message - objects retain references to their originating sd_bus but - we want to allow them to be processed in a different - thread. We won't provide full thread safety, but only the - bare minimum that makes it possible to use sd_bus and - sd_bus_message objects independently and on different - threads as long as each object is used only once at the - same time. */ - RefCount n_ref; - - enum bus_state state; - int input_fd, output_fd; - int message_version; - int message_endian; - - bool is_kernel:1; - bool can_fds:1; - bool bus_client:1; - bool ucred_valid:1; - bool is_server:1; - bool anonymous_auth:1; - bool prefer_readv:1; - bool prefer_writev:1; - bool match_callbacks_modified:1; - bool filter_callbacks_modified:1; - bool nodes_modified:1; - bool trusted:1; - bool fake_creds_valid:1; - bool fake_pids_valid:1; - bool manual_peer_interface:1; - bool is_system:1; - bool is_user:1; - bool allow_interactive_authorization:1; - - int use_memfd; - - void *rbuffer; - size_t rbuffer_size; - - sd_bus_message **rqueue; - unsigned rqueue_size; - size_t rqueue_allocated; - - sd_bus_message **wqueue; - unsigned wqueue_size; - size_t windex; - size_t wqueue_allocated; - - uint64_t cookie; - - char *unique_name; - uint64_t unique_id; - - struct bus_match_node match_callbacks; - Prioq *reply_callbacks_prioq; - OrderedHashmap *reply_callbacks; - LIST_HEAD(struct filter_callback, filter_callbacks); - - Hashmap *nodes; - Hashmap *vtable_methods; - Hashmap *vtable_properties; - - union sockaddr_union sockaddr; - socklen_t sockaddr_size; - - char *kernel; - char *machine; - pid_t nspid; - - sd_id128_t server_id; - - char *address; - unsigned address_index; - - int last_connect_error; - - enum bus_auth auth; - size_t auth_rbegin; - struct iovec auth_iovec[3]; - unsigned auth_index; - char *auth_buffer; - usec_t auth_timeout; - - struct ucred ucred; - char *label; - - uint64_t creds_mask; - - int *fds; - unsigned n_fds; - - char *exec_path; - char **exec_argv; - - unsigned iteration_counter; - - void *kdbus_buffer; - - /* We do locking around the memfd cache, since we want to - * allow people to process a sd_bus_message in a different - * thread then it was generated on and free it there. Since - * adding something to the memfd cache might happen when a - * message is released, we hence need to protect this bit with - * a mutex. */ - pthread_mutex_t memfd_cache_mutex; - struct memfd_cache memfd_cache[MEMFD_CACHE_MAX]; - unsigned n_memfd_cache; - - pid_t original_pid; - - uint64_t hello_flags; - uint64_t attach_flags; - - uint64_t match_cookie; - - sd_event_source *input_io_event_source; - sd_event_source *output_io_event_source; - sd_event_source *time_event_source; - sd_event_source *quit_event_source; - sd_event *event; - int event_priority; - - sd_bus_message *current_message; - sd_bus_slot *current_slot; - sd_bus_message_handler_t current_handler; - void *current_userdata; - - sd_bus **default_bus_ptr; - pid_t tid; - - struct kdbus_creds fake_creds; - struct kdbus_pids fake_pids; - char *fake_label; - - char *cgroup_root; - - char *description; - - size_t bloom_size; - unsigned bloom_n_hash; - - sd_bus_track *track_queue; - - LIST_HEAD(sd_bus_slot, slots); -}; - -#define BUS_DEFAULT_TIMEOUT ((usec_t) (25 * USEC_PER_SEC)) - -#define BUS_WQUEUE_MAX 1024 -#define BUS_RQUEUE_MAX 64*1024 - -#define BUS_MESSAGE_SIZE_MAX (64*1024*1024) -#define BUS_AUTH_SIZE_MAX (64*1024) - -#define BUS_CONTAINER_DEPTH 128 - -/* Defined by the specification as maximum size of an array in - * bytes */ -#define BUS_ARRAY_MAX_SIZE 67108864 - -#define BUS_FDS_MAX 1024 - -#define BUS_EXEC_ARGV_MAX 256 - -bool interface_name_is_valid(const char *p) _pure_; -bool service_name_is_valid(const char *p) _pure_; -char* service_name_startswith(const char *a, const char *b); -bool member_name_is_valid(const char *p) _pure_; -bool object_path_is_valid(const char *p) _pure_; -char *object_path_startswith(const char *a, const char *b) _pure_; - -bool namespace_complex_pattern(const char *pattern, const char *value) _pure_; -bool path_complex_pattern(const char *pattern, const char *value) _pure_; - -bool namespace_simple_pattern(const char *pattern, const char *value) _pure_; -bool path_simple_pattern(const char *pattern, const char *value) _pure_; - -int bus_message_type_from_string(const char *s, uint8_t *u) _pure_; -const char *bus_message_type_to_string(uint8_t u) _pure_; - -#define error_name_is_valid interface_name_is_valid - -int bus_ensure_running(sd_bus *bus); -int bus_start_running(sd_bus *bus); -int bus_next_address(sd_bus *bus); - -int bus_seal_synthetic_message(sd_bus *b, sd_bus_message *m); - -int bus_rqueue_make_room(sd_bus *bus); - -bool bus_pid_changed(sd_bus *bus); - -char *bus_address_escape(const char *v); - -#define OBJECT_PATH_FOREACH_PREFIX(prefix, path) \ - for (char *_slash = ({ strcpy((prefix), (path)); streq((prefix), "/") ? NULL : strrchr((prefix), '/'); }) ; \ - _slash && !(_slash[(_slash) == (prefix)] = 0); \ - _slash = streq((prefix), "/") ? NULL : strrchr((prefix), '/')) - -/* If we are invoking callbacks of a bus object, ensure unreffing the - * bus from the callback doesn't destroy the object we are working - * on */ -#define BUS_DONT_DESTROY(bus) \ - _cleanup_(sd_bus_unrefp) _unused_ sd_bus *_dont_destroy_##bus = sd_bus_ref(bus) - -int bus_set_address_system(sd_bus *bus); -int bus_set_address_user(sd_bus *bus); -int bus_set_address_system_remote(sd_bus *b, const char *host); -int bus_set_address_system_machine(sd_bus *b, const char *machine); - -int bus_remove_match_by_string(sd_bus *bus, const char *match, sd_bus_message_handler_t callback, void *userdata); - -int bus_get_root_path(sd_bus *bus); - -int bus_maybe_reply_error(sd_bus_message *m, int r, sd_bus_error *error); - -#define bus_assert_return(expr, r, error) \ - do { \ - if (!assert_log(expr, #expr)) \ - return sd_bus_error_set_errno(error, r); \ - } while (false) diff --git a/src/libsystemd/sd-bus/bus-introspect.c b/src/libsystemd/sd-bus/bus-introspect.c deleted file mode 100644 index 8f93edb8da..0000000000 --- a/src/libsystemd/sd-bus/bus-introspect.c +++ /dev/null @@ -1,212 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "bus-internal.h" -#include "bus-introspect.h" -#include "bus-protocol.h" -#include "bus-signature.h" -#include "fd-util.h" -#include "fileio.h" -#include "string-util.h" -#include "util.h" - -int introspect_begin(struct introspect *i, bool trusted) { - assert(i); - - zero(*i); - i->trusted = trusted; - - i->f = open_memstream(&i->introspection, &i->size); - if (!i->f) - return -ENOMEM; - - fputs(BUS_INTROSPECT_DOCTYPE - "\n", i->f); - - return 0; -} - -int introspect_write_default_interfaces(struct introspect *i, bool object_manager) { - assert(i); - - fputs(BUS_INTROSPECT_INTERFACE_PEER - BUS_INTROSPECT_INTERFACE_INTROSPECTABLE - BUS_INTROSPECT_INTERFACE_PROPERTIES, i->f); - - if (object_manager) - fputs(BUS_INTROSPECT_INTERFACE_OBJECT_MANAGER, i->f); - - return 0; -} - -int introspect_write_child_nodes(struct introspect *i, Set *s, const char *prefix) { - char *node; - - assert(i); - assert(prefix); - - while ((node = set_steal_first(s))) { - const char *e; - - e = object_path_startswith(node, prefix); - if (e && e[0]) - fprintf(i->f, " \n", e); - - free(node); - } - - return 0; -} - -static void introspect_write_flags(struct introspect *i, int type, int flags) { - if (flags & SD_BUS_VTABLE_DEPRECATED) - fputs(" \n", i->f); - - if (type == _SD_BUS_VTABLE_METHOD && (flags & SD_BUS_VTABLE_METHOD_NO_REPLY)) - fputs(" \n", i->f); - - if (type == _SD_BUS_VTABLE_PROPERTY || type == _SD_BUS_VTABLE_WRITABLE_PROPERTY) { - if (flags & SD_BUS_VTABLE_PROPERTY_EXPLICIT) - fputs(" \n", i->f); - - if (flags & SD_BUS_VTABLE_PROPERTY_CONST) - fputs(" \n", i->f); - else if (flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) - fputs(" \n", i->f); - else if (!(flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) - fputs(" \n", i->f); - } - - if (!i->trusted && - (type == _SD_BUS_VTABLE_METHOD || type == _SD_BUS_VTABLE_WRITABLE_PROPERTY) && - !(flags & SD_BUS_VTABLE_UNPRIVILEGED)) - fputs(" \n", i->f); -} - -static int introspect_write_arguments(struct introspect *i, const char *signature, const char *direction) { - int r; - - for (;;) { - size_t l; - - if (!*signature) - return 0; - - r = signature_element_length(signature, &l); - if (r < 0) - return r; - - fprintf(i->f, " f, " direction=\"%s\"/>\n", direction); - else - fputs("/>\n", i->f); - - signature += l; - } -} - -int introspect_write_interface(struct introspect *i, const sd_bus_vtable *v) { - assert(i); - assert(v); - - for (; v->type != _SD_BUS_VTABLE_END; v++) { - - /* Ignore methods, signals and properties that are - * marked "hidden", but do show the interface - * itself */ - - if (v->type != _SD_BUS_VTABLE_START && (v->flags & SD_BUS_VTABLE_HIDDEN)) - continue; - - switch (v->type) { - - case _SD_BUS_VTABLE_START: - if (v->flags & SD_BUS_VTABLE_DEPRECATED) - fputs(" \n", i->f); - break; - - case _SD_BUS_VTABLE_METHOD: - fprintf(i->f, " \n", v->x.method.member); - introspect_write_arguments(i, strempty(v->x.method.signature), "in"); - introspect_write_arguments(i, strempty(v->x.method.result), "out"); - introspect_write_flags(i, v->type, v->flags); - fputs(" \n", i->f); - break; - - case _SD_BUS_VTABLE_PROPERTY: - case _SD_BUS_VTABLE_WRITABLE_PROPERTY: - fprintf(i->f, " \n", - v->x.property.member, - v->x.property.signature, - v->type == _SD_BUS_VTABLE_WRITABLE_PROPERTY ? "readwrite" : "read"); - introspect_write_flags(i, v->type, v->flags); - fputs(" \n", i->f); - break; - - case _SD_BUS_VTABLE_SIGNAL: - fprintf(i->f, " \n", v->x.signal.member); - introspect_write_arguments(i, strempty(v->x.signal.signature), NULL); - introspect_write_flags(i, v->type, v->flags); - fputs(" \n", i->f); - break; - } - - } - - return 0; -} - -int introspect_finish(struct introspect *i, sd_bus *bus, sd_bus_message *m, sd_bus_message **reply) { - sd_bus_message *q; - int r; - - assert(i); - assert(m); - assert(reply); - - fputs("\n", i->f); - - r = fflush_and_check(i->f); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(m, &q); - if (r < 0) - return r; - - r = sd_bus_message_append(q, "s", i->introspection); - if (r < 0) { - sd_bus_message_unref(q); - return r; - } - - *reply = q; - return 0; -} - -void introspect_free(struct introspect *i) { - assert(i); - - safe_fclose(i->f); - - free(i->introspection); - zero(*i); -} diff --git a/src/libsystemd/sd-bus/bus-introspect.h b/src/libsystemd/sd-bus/bus-introspect.h deleted file mode 100644 index 8e2f3800ca..0000000000 --- a/src/libsystemd/sd-bus/bus-introspect.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -#include "set.h" - -struct introspect { - FILE *f; - char *introspection; - size_t size; - bool trusted; -}; - -int introspect_begin(struct introspect *i, bool trusted); -int introspect_write_default_interfaces(struct introspect *i, bool object_manager); -int introspect_write_child_nodes(struct introspect *i, Set *s, const char *prefix); -int introspect_write_interface(struct introspect *i, const sd_bus_vtable *v); -int introspect_finish(struct introspect *i, sd_bus *bus, sd_bus_message *m, sd_bus_message **reply); -void introspect_free(struct introspect *i); diff --git a/src/libsystemd/sd-bus/bus-kernel.c b/src/libsystemd/sd-bus/bus-kernel.c deleted file mode 100644 index 59398b841d..0000000000 --- a/src/libsystemd/sd-bus/bus-kernel.c +++ /dev/null @@ -1,1782 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -#ifdef HAVE_VALGRIND_MEMCHECK_H -#include -#endif - -#include -#include -#include -#include - -/* When we include libgen.h because we need dirname() we immediately - * undefine basename() since libgen.h defines it as a macro to the POSIX - * version which is really broken. We prefer GNU basename(). */ -#include -#undef basename - -#include "alloc-util.h" -#include "bus-bloom.h" -#include "bus-internal.h" -#include "bus-kernel.h" -#include "bus-label.h" -#include "bus-message.h" -#include "bus-util.h" -#include "capability-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "memfd-util.h" -#include "parse-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "strv.h" -#include "user-util.h" -#include "util.h" - -#define UNIQUE_NAME_MAX (3+DECIMAL_STR_MAX(uint64_t)) - -int bus_kernel_parse_unique_name(const char *s, uint64_t *id) { - int r; - - assert(s); - assert(id); - - if (!startswith(s, ":1.")) - return 0; - - r = safe_atou64(s + 3, id); - if (r < 0) - return r; - - return 1; -} - -static void append_payload_vec(struct kdbus_item **d, const void *p, size_t sz) { - assert(d); - assert(sz > 0); - - *d = ALIGN8_PTR(*d); - - /* Note that p can be NULL, which encodes a region full of - * zeroes, which is useful to optimize certain padding - * conditions */ - - (*d)->size = offsetof(struct kdbus_item, vec) + sizeof(struct kdbus_vec); - (*d)->type = KDBUS_ITEM_PAYLOAD_VEC; - (*d)->vec.address = PTR_TO_UINT64(p); - (*d)->vec.size = sz; - - *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); -} - -static void append_payload_memfd(struct kdbus_item **d, int memfd, size_t start, size_t sz) { - assert(d); - assert(memfd >= 0); - assert(sz > 0); - - *d = ALIGN8_PTR(*d); - (*d)->size = offsetof(struct kdbus_item, memfd) + sizeof(struct kdbus_memfd); - (*d)->type = KDBUS_ITEM_PAYLOAD_MEMFD; - (*d)->memfd.fd = memfd; - (*d)->memfd.start = start; - (*d)->memfd.size = sz; - - *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); -} - -static void append_destination(struct kdbus_item **d, const char *s, size_t length) { - assert(d); - assert(s); - - *d = ALIGN8_PTR(*d); - - (*d)->size = offsetof(struct kdbus_item, str) + length + 1; - (*d)->type = KDBUS_ITEM_DST_NAME; - memcpy((*d)->str, s, length + 1); - - *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); -} - -static struct kdbus_bloom_filter *append_bloom(struct kdbus_item **d, size_t length) { - struct kdbus_item *i; - - assert(d); - - i = ALIGN8_PTR(*d); - - i->size = offsetof(struct kdbus_item, bloom_filter) + - offsetof(struct kdbus_bloom_filter, data) + - length; - i->type = KDBUS_ITEM_BLOOM_FILTER; - - *d = (struct kdbus_item *) ((uint8_t*) i + i->size); - - return &i->bloom_filter; -} - -static void append_fds(struct kdbus_item **d, const int fds[], unsigned n_fds) { - assert(d); - assert(fds); - assert(n_fds > 0); - - *d = ALIGN8_PTR(*d); - (*d)->size = offsetof(struct kdbus_item, fds) + sizeof(int) * n_fds; - (*d)->type = KDBUS_ITEM_FDS; - memcpy((*d)->fds, fds, sizeof(int) * n_fds); - - *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); -} - -static void add_bloom_arg(void *data, size_t size, unsigned n_hash, unsigned i, const char *t) { - char buf[sizeof("arg")-1 + 2 + sizeof("-slash-prefix")]; - char *e; - - assert(data); - assert(size > 0); - assert(i < 64); - assert(t); - - e = stpcpy(buf, "arg"); - if (i < 10) - *(e++) = '0' + (char) i; - else { - *(e++) = '0' + (char) (i / 10); - *(e++) = '0' + (char) (i % 10); - } - - *e = 0; - bloom_add_pair(data, size, n_hash, buf, t); - - strcpy(e, "-dot-prefix"); - bloom_add_prefixes(data, size, n_hash, buf, t, '.'); - strcpy(e, "-slash-prefix"); - bloom_add_prefixes(data, size, n_hash, buf, t, '/'); -} - -static void add_bloom_arg_has(void *data, size_t size, unsigned n_hash, unsigned i, const char *t) { - char buf[sizeof("arg")-1 + 2 + sizeof("-has")]; - char *e; - - assert(data); - assert(size > 0); - assert(i < 64); - assert(t); - - e = stpcpy(buf, "arg"); - if (i < 10) - *(e++) = '0' + (char) i; - else { - *(e++) = '0' + (char) (i / 10); - *(e++) = '0' + (char) (i % 10); - } - - strcpy(e, "-has"); - bloom_add_pair(data, size, n_hash, buf, t); -} - -static int bus_message_setup_bloom(sd_bus_message *m, struct kdbus_bloom_filter *bloom) { - void *data; - unsigned i; - int r; - - assert(m); - assert(bloom); - - data = bloom->data; - memzero(data, m->bus->bloom_size); - bloom->generation = 0; - - bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "message-type", bus_message_type_to_string(m->header->type)); - - if (m->interface) - bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "interface", m->interface); - if (m->member) - bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "member", m->member); - if (m->path) { - bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "path", m->path); - bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "path-slash-prefix", m->path); - bloom_add_prefixes(data, m->bus->bloom_size, m->bus->bloom_n_hash, "path-slash-prefix", m->path, '/'); - } - - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - for (i = 0; i < 64; i++) { - const char *t, *contents; - char type; - - r = sd_bus_message_peek_type(m, &type, &contents); - if (r < 0) - return r; - - if (IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE)) { - - /* The bloom filter includes simple strings of any kind */ - r = sd_bus_message_read_basic(m, type, &t); - if (r < 0) - return r; - - add_bloom_arg(data, m->bus->bloom_size, m->bus->bloom_n_hash, i, t); - } - - if (type == SD_BUS_TYPE_ARRAY && STR_IN_SET(contents, "s", "o", "g")) { - - /* As well as array of simple strings of any kinds */ - r = sd_bus_message_enter_container(m, type, contents); - if (r < 0) - return r; - - while ((r = sd_bus_message_read_basic(m, contents[0], &t)) > 0) - add_bloom_arg_has(data, m->bus->bloom_size, m->bus->bloom_n_hash, i, t); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - } else - /* Stop adding to bloom filter as soon as we - * run into the first argument we cannot add - * to it. */ - break; - } - - return 0; -} - -static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) { - struct bus_body_part *part; - struct kdbus_item *d; - const char *destination; - bool well_known = false; - uint64_t dst_id; - size_t sz, dl; - unsigned i; - int r; - - assert(b); - assert(m); - assert(m->sealed); - - /* We put this together only once, if this message is reused - * we reuse the earlier-built version */ - if (m->kdbus) - return 0; - - destination = m->destination ?: m->destination_ptr; - - if (destination) { - r = bus_kernel_parse_unique_name(destination, &dst_id); - if (r < 0) - return r; - if (r == 0) { - well_known = true; - - /* verify_destination_id will usually be 0, which makes the kernel - * driver only look at the provided well-known name. Otherwise, - * the kernel will make sure the provided destination id matches - * the owner of the provided well-known-name, and fail if they - * differ. Currently, this is only needed for bus-proxyd. */ - dst_id = m->verify_destination_id; - } - } else - dst_id = KDBUS_DST_ID_BROADCAST; - - sz = offsetof(struct kdbus_msg, items); - - /* Add in fixed header, fields header and payload */ - sz += (1 + m->n_body_parts) * ALIGN8(offsetof(struct kdbus_item, vec) + - MAX(sizeof(struct kdbus_vec), - sizeof(struct kdbus_memfd))); - - /* Add space for bloom filter */ - sz += ALIGN8(offsetof(struct kdbus_item, bloom_filter) + - offsetof(struct kdbus_bloom_filter, data) + - m->bus->bloom_size); - - /* Add in well-known destination header */ - if (well_known) { - dl = strlen(destination); - sz += ALIGN8(offsetof(struct kdbus_item, str) + dl + 1); - } - - /* Add space for unix fds */ - if (m->n_fds > 0) - sz += ALIGN8(offsetof(struct kdbus_item, fds) + sizeof(int)*m->n_fds); - - m->kdbus = memalign(8, sz); - if (!m->kdbus) { - r = -ENOMEM; - goto fail; - } - - m->free_kdbus = true; - memzero(m->kdbus, sz); - - m->kdbus->flags = - ((m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) ? 0 : KDBUS_MSG_EXPECT_REPLY) | - ((m->header->flags & BUS_MESSAGE_NO_AUTO_START) ? KDBUS_MSG_NO_AUTO_START : 0) | - ((m->header->type == SD_BUS_MESSAGE_SIGNAL) ? KDBUS_MSG_SIGNAL : 0); - - m->kdbus->dst_id = dst_id; - m->kdbus->payload_type = KDBUS_PAYLOAD_DBUS; - m->kdbus->cookie = m->header->dbus2.cookie; - m->kdbus->priority = m->priority; - - if (m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) - m->kdbus->cookie_reply = m->reply_cookie; - else { - struct timespec now; - - assert_se(clock_gettime(CLOCK_MONOTONIC_COARSE, &now) == 0); - m->kdbus->timeout_ns = now.tv_sec * NSEC_PER_SEC + now.tv_nsec + - m->timeout * NSEC_PER_USEC; - } - - d = m->kdbus->items; - - if (well_known) - append_destination(&d, destination, dl); - - append_payload_vec(&d, m->header, BUS_MESSAGE_BODY_BEGIN(m)); - - MESSAGE_FOREACH_PART(part, i, m) { - if (part->is_zero) { - /* If this is padding then simply send a - * vector with a NULL data pointer which the - * kernel will just pass through. This is the - * most efficient way to encode zeroes */ - - append_payload_vec(&d, NULL, part->size); - continue; - } - - if (part->memfd >= 0 && part->sealed && destination) { - /* Try to send a memfd, if the part is - * sealed and this is not a broadcast. Since we can only */ - - append_payload_memfd(&d, part->memfd, part->memfd_offset, part->size); - continue; - } - - /* Otherwise, let's send a vector to the actual data. - * For that, we need to map it first. */ - r = bus_body_part_map(part); - if (r < 0) - goto fail; - - append_payload_vec(&d, part->data, part->size); - } - - if (m->header->type == SD_BUS_MESSAGE_SIGNAL) { - struct kdbus_bloom_filter *bloom; - - bloom = append_bloom(&d, m->bus->bloom_size); - r = bus_message_setup_bloom(m, bloom); - if (r < 0) - goto fail; - } - - if (m->n_fds > 0) - append_fds(&d, m->fds, m->n_fds); - - m->kdbus->size = (uint8_t*) d - (uint8_t*) m->kdbus; - assert(m->kdbus->size <= sz); - - return 0; - -fail: - m->poisoned = true; - return r; -} - -static void unset_memfds(struct sd_bus_message *m) { - struct bus_body_part *part; - unsigned i; - - assert(m); - - /* Make sure the memfds are not freed twice */ - MESSAGE_FOREACH_PART(part, i, m) - if (part->memfd >= 0) - part->memfd = -1; -} - -static void message_set_timestamp(sd_bus *bus, sd_bus_message *m, const struct kdbus_timestamp *ts) { - assert(bus); - assert(m); - - if (!ts) - return; - - if (!(bus->attach_flags & KDBUS_ATTACH_TIMESTAMP)) - return; - - m->realtime = ts->realtime_ns / NSEC_PER_USEC; - m->monotonic = ts->monotonic_ns / NSEC_PER_USEC; - m->seqnum = ts->seqnum; -} - -static int bus_kernel_make_message(sd_bus *bus, struct kdbus_msg *k) { - sd_bus_message *m = NULL; - struct kdbus_item *d; - unsigned n_fds = 0; - _cleanup_free_ int *fds = NULL; - struct bus_header *header = NULL; - void *footer = NULL; - size_t header_size = 0, footer_size = 0; - size_t n_bytes = 0, idx = 0; - const char *destination = NULL, *seclabel = NULL; - bool last_was_memfd = false; - int r; - - assert(bus); - assert(k); - assert(k->payload_type == KDBUS_PAYLOAD_DBUS); - - KDBUS_ITEM_FOREACH(d, k, items) { - size_t l; - - l = d->size - offsetof(struct kdbus_item, data); - - switch (d->type) { - - case KDBUS_ITEM_PAYLOAD_OFF: - if (!header) { - header = (struct bus_header*)((uint8_t*) k + d->vec.offset); - header_size = d->vec.size; - } - - footer = (uint8_t*) k + d->vec.offset; - footer_size = d->vec.size; - - n_bytes += d->vec.size; - last_was_memfd = false; - break; - - case KDBUS_ITEM_PAYLOAD_MEMFD: - if (!header) /* memfd cannot be first part */ - return -EBADMSG; - - n_bytes += d->memfd.size; - last_was_memfd = true; - break; - - case KDBUS_ITEM_FDS: { - int *f; - unsigned j; - - j = l / sizeof(int); - f = realloc(fds, sizeof(int) * (n_fds + j)); - if (!f) - return -ENOMEM; - - fds = f; - memcpy(fds + n_fds, d->fds, sizeof(int) * j); - n_fds += j; - break; - } - - case KDBUS_ITEM_SECLABEL: - seclabel = d->str; - break; - } - } - - if (last_was_memfd) /* memfd cannot be last part */ - return -EBADMSG; - - if (!header) - return -EBADMSG; - - if (header_size < sizeof(struct bus_header)) - return -EBADMSG; - - /* on kdbus we only speak native endian gvariant, never dbus1 - * marshalling or reverse endian */ - if (header->version != 2 || - header->endian != BUS_NATIVE_ENDIAN) - return -EPROTOTYPE; - - r = bus_message_from_header( - bus, - header, header_size, - footer, footer_size, - n_bytes, - fds, n_fds, - seclabel, 0, &m); - if (r < 0) - return r; - - /* The well-known names list is different from the other - credentials. If we asked for it, but nothing is there, this - means that the list of well-known names is simply empty, not - that we lack any data */ - - m->creds.mask |= (SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_WELL_KNOWN_NAMES) & bus->creds_mask; - - KDBUS_ITEM_FOREACH(d, k, items) { - size_t l; - - l = d->size - offsetof(struct kdbus_item, data); - - switch (d->type) { - - case KDBUS_ITEM_PAYLOAD_OFF: { - size_t begin_body; - - begin_body = BUS_MESSAGE_BODY_BEGIN(m); - - if (idx + d->vec.size > begin_body) { - struct bus_body_part *part; - - /* Contains body material */ - - part = message_append_part(m); - if (!part) { - r = -ENOMEM; - goto fail; - } - - /* A -1 offset is NUL padding. */ - part->is_zero = d->vec.offset == ~0ULL; - - if (idx >= begin_body) { - if (!part->is_zero) - part->data = (uint8_t* )k + d->vec.offset; - part->size = d->vec.size; - } else { - if (!part->is_zero) - part->data = (uint8_t*) k + d->vec.offset + (begin_body - idx); - part->size = d->vec.size - (begin_body - idx); - } - - part->sealed = true; - } - - idx += d->vec.size; - break; - } - - case KDBUS_ITEM_PAYLOAD_MEMFD: { - struct bus_body_part *part; - - if (idx < BUS_MESSAGE_BODY_BEGIN(m)) { - r = -EBADMSG; - goto fail; - } - - part = message_append_part(m); - if (!part) { - r = -ENOMEM; - goto fail; - } - - part->memfd = d->memfd.fd; - part->memfd_offset = d->memfd.start; - part->size = d->memfd.size; - part->sealed = true; - - idx += d->memfd.size; - break; - } - - case KDBUS_ITEM_PIDS: - - /* The PID/TID might be missing, when the data - * is faked by a bus proxy and it lacks that - * information about the real client (since - * SO_PEERCRED is used for that). Also kernel - * namespacing might make some of this data - * unavailable when untranslatable. */ - - if (d->pids.pid > 0) { - m->creds.pid = (pid_t) d->pids.pid; - m->creds.mask |= SD_BUS_CREDS_PID & bus->creds_mask; - } - - if (d->pids.tid > 0) { - m->creds.tid = (pid_t) d->pids.tid; - m->creds.mask |= SD_BUS_CREDS_TID & bus->creds_mask; - } - - if (d->pids.ppid > 0) { - m->creds.ppid = (pid_t) d->pids.ppid; - m->creds.mask |= SD_BUS_CREDS_PPID & bus->creds_mask; - } else if (d->pids.pid == 1) { - m->creds.ppid = 0; - m->creds.mask |= SD_BUS_CREDS_PPID & bus->creds_mask; - } - - break; - - case KDBUS_ITEM_CREDS: - - /* EUID/SUID/FSUID/EGID/SGID/FSGID might be - * missing too (see above). */ - - if ((uid_t) d->creds.uid != UID_INVALID) { - m->creds.uid = (uid_t) d->creds.uid; - m->creds.mask |= SD_BUS_CREDS_UID & bus->creds_mask; - } - - if ((uid_t) d->creds.euid != UID_INVALID) { - m->creds.euid = (uid_t) d->creds.euid; - m->creds.mask |= SD_BUS_CREDS_EUID & bus->creds_mask; - } - - if ((uid_t) d->creds.suid != UID_INVALID) { - m->creds.suid = (uid_t) d->creds.suid; - m->creds.mask |= SD_BUS_CREDS_SUID & bus->creds_mask; - } - - if ((uid_t) d->creds.fsuid != UID_INVALID) { - m->creds.fsuid = (uid_t) d->creds.fsuid; - m->creds.mask |= SD_BUS_CREDS_FSUID & bus->creds_mask; - } - - if ((gid_t) d->creds.gid != GID_INVALID) { - m->creds.gid = (gid_t) d->creds.gid; - m->creds.mask |= SD_BUS_CREDS_GID & bus->creds_mask; - } - - if ((gid_t) d->creds.egid != GID_INVALID) { - m->creds.egid = (gid_t) d->creds.egid; - m->creds.mask |= SD_BUS_CREDS_EGID & bus->creds_mask; - } - - if ((gid_t) d->creds.sgid != GID_INVALID) { - m->creds.sgid = (gid_t) d->creds.sgid; - m->creds.mask |= SD_BUS_CREDS_SGID & bus->creds_mask; - } - - if ((gid_t) d->creds.fsgid != GID_INVALID) { - m->creds.fsgid = (gid_t) d->creds.fsgid; - m->creds.mask |= SD_BUS_CREDS_FSGID & bus->creds_mask; - } - - break; - - case KDBUS_ITEM_TIMESTAMP: - message_set_timestamp(bus, m, &d->timestamp); - break; - - case KDBUS_ITEM_PID_COMM: - m->creds.comm = d->str; - m->creds.mask |= SD_BUS_CREDS_COMM & bus->creds_mask; - break; - - case KDBUS_ITEM_TID_COMM: - m->creds.tid_comm = d->str; - m->creds.mask |= SD_BUS_CREDS_TID_COMM & bus->creds_mask; - break; - - case KDBUS_ITEM_EXE: - m->creds.exe = d->str; - m->creds.mask |= SD_BUS_CREDS_EXE & bus->creds_mask; - break; - - case KDBUS_ITEM_CMDLINE: - m->creds.cmdline = d->str; - m->creds.cmdline_size = l; - m->creds.mask |= SD_BUS_CREDS_CMDLINE & bus->creds_mask; - break; - - case KDBUS_ITEM_CGROUP: - m->creds.cgroup = d->str; - m->creds.mask |= (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID) & bus->creds_mask; - - r = bus_get_root_path(bus); - if (r < 0) - goto fail; - - m->creds.cgroup_root = bus->cgroup_root; - break; - - case KDBUS_ITEM_AUDIT: - m->creds.audit_session_id = (uint32_t) d->audit.sessionid; - m->creds.mask |= SD_BUS_CREDS_AUDIT_SESSION_ID & bus->creds_mask; - - m->creds.audit_login_uid = (uid_t) d->audit.loginuid; - m->creds.mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID & bus->creds_mask; - break; - - case KDBUS_ITEM_CAPS: - if (d->caps.last_cap != cap_last_cap() || - d->size - offsetof(struct kdbus_item, caps.caps) < DIV_ROUND_UP(d->caps.last_cap, 32U) * 4 * 4) { - r = -EBADMSG; - goto fail; - } - - m->creds.capability = d->caps.caps; - m->creds.mask |= (SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS) & bus->creds_mask; - break; - - case KDBUS_ITEM_DST_NAME: - if (!service_name_is_valid(d->str)) { - r = -EBADMSG; - goto fail; - } - - destination = d->str; - break; - - case KDBUS_ITEM_OWNED_NAME: - if (!service_name_is_valid(d->name.name)) { - r = -EBADMSG; - goto fail; - } - - if (bus->creds_mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) { - char **wkn; - size_t n; - - /* We just extend the array here, but - * do not allocate the strings inside - * of it, instead we just point to our - * buffer directly. */ - n = strv_length(m->creds.well_known_names); - wkn = realloc(m->creds.well_known_names, (n + 2) * sizeof(char*)); - if (!wkn) { - r = -ENOMEM; - goto fail; - } - - wkn[n] = d->name.name; - wkn[n+1] = NULL; - m->creds.well_known_names = wkn; - - m->creds.mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES; - } - break; - - case KDBUS_ITEM_CONN_DESCRIPTION: - m->creds.description = d->str; - m->creds.mask |= SD_BUS_CREDS_DESCRIPTION & bus->creds_mask; - break; - - case KDBUS_ITEM_AUXGROUPS: - - if (bus->creds_mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { - size_t i, n; - gid_t *g; - - n = (d->size - offsetof(struct kdbus_item, data64)) / sizeof(uint64_t); - g = new(gid_t, n); - if (!g) { - r = -ENOMEM; - goto fail; - } - - for (i = 0; i < n; i++) - g[i] = d->data64[i]; - - m->creds.supplementary_gids = g; - m->creds.n_supplementary_gids = n; - m->creds.mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS; - } - - break; - - case KDBUS_ITEM_FDS: - case KDBUS_ITEM_SECLABEL: - case KDBUS_ITEM_BLOOM_FILTER: - break; - - default: - log_debug("Got unknown field from kernel %llu", d->type); - } - } - - /* If we requested the list of well-known names to be appended - * and the sender had none no item for it will be - * attached. However, this does *not* mean that the kernel - * didn't want to provide this information to us. Hence, let's - * explicitly mark this information as available if it was - * requested. */ - m->creds.mask |= bus->creds_mask & SD_BUS_CREDS_WELL_KNOWN_NAMES; - - r = bus_message_parse_fields(m); - if (r < 0) - goto fail; - - /* Refuse messages if kdbus and dbus1 cookie doesn't match up */ - if ((uint64_t) m->header->dbus2.cookie != k->cookie) { - r = -EBADMSG; - goto fail; - } - - /* Refuse messages where the reply flag doesn't match up */ - if (!(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) != !!(k->flags & KDBUS_MSG_EXPECT_REPLY)) { - r = -EBADMSG; - goto fail; - } - - /* Refuse reply messages where the reply cookie doesn't match up */ - if ((m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) && m->reply_cookie != k->cookie_reply) { - r = -EBADMSG; - goto fail; - } - - /* Refuse messages where the autostart flag doesn't match up */ - if (!(m->header->flags & BUS_MESSAGE_NO_AUTO_START) != !(k->flags & KDBUS_MSG_NO_AUTO_START)) { - r = -EBADMSG; - goto fail; - } - - /* Override information from the user header with data from the kernel */ - if (k->src_id == KDBUS_SRC_ID_KERNEL) - bus_message_set_sender_driver(bus, m); - else { - xsprintf(m->sender_buffer, ":1.%llu", - (unsigned long long)k->src_id); - m->sender = m->creds.unique_name = m->sender_buffer; - } - - if (destination) - m->destination = destination; - else if (k->dst_id == KDBUS_DST_ID_BROADCAST) - m->destination = NULL; - else if (k->dst_id == KDBUS_DST_ID_NAME) - m->destination = bus->unique_name; /* fill in unique name if the well-known name is missing */ - else { - xsprintf(m->destination_buffer, ":1.%llu", - (unsigned long long)k->dst_id); - m->destination = m->destination_buffer; - } - - /* We take possession of the kmsg struct now */ - m->kdbus = k; - m->release_kdbus = true; - m->free_fds = true; - fds = NULL; - - bus->rqueue[bus->rqueue_size++] = m; - - return 1; - -fail: - unset_memfds(m); - sd_bus_message_unref(m); - - return r; -} - -int bus_kernel_take_fd(sd_bus *b) { - struct kdbus_bloom_parameter *bloom = NULL; - struct kdbus_item *items, *item; - struct kdbus_cmd_hello *hello; - _cleanup_free_ char *g = NULL; - const char *name; - size_t l = 0, m = 0, sz; - int r; - - assert(b); - - if (b->is_server) - return -EINVAL; - - b->use_memfd = 1; - - if (b->description) { - g = bus_label_escape(b->description); - if (!g) - return -ENOMEM; - - name = g; - } else { - char pr[17] = {}; - - /* If no name is explicitly set, we'll include a hint - * indicating the library implementation, a hint which - * kind of bus this is and the thread name */ - - assert_se(prctl(PR_GET_NAME, (unsigned long) pr) >= 0); - - if (isempty(pr)) { - name = b->is_system ? "sd-system" : - b->is_user ? "sd-user" : "sd"; - } else { - _cleanup_free_ char *e = NULL; - - e = bus_label_escape(pr); - if (!e) - return -ENOMEM; - - g = strappend(b->is_system ? "sd-system-" : - b->is_user ? "sd-user-" : "sd-", - e); - if (!g) - return -ENOMEM; - - name = g; - } - - b->description = bus_label_unescape(name); - if (!b->description) - return -ENOMEM; - } - - m = strlen(name); - - sz = ALIGN8(offsetof(struct kdbus_cmd_hello, items)) + - ALIGN8(offsetof(struct kdbus_item, str) + m + 1); - - if (b->fake_creds_valid) - sz += ALIGN8(offsetof(struct kdbus_item, creds) + sizeof(struct kdbus_creds)); - - if (b->fake_pids_valid) - sz += ALIGN8(offsetof(struct kdbus_item, pids) + sizeof(struct kdbus_pids)); - - if (b->fake_label) { - l = strlen(b->fake_label); - sz += ALIGN8(offsetof(struct kdbus_item, str) + l + 1); - } - - hello = alloca0_align(sz, 8); - hello->size = sz; - hello->flags = b->hello_flags; - hello->attach_flags_send = _KDBUS_ATTACH_ANY; - hello->attach_flags_recv = b->attach_flags; - hello->pool_size = KDBUS_POOL_SIZE; - - item = hello->items; - - item->size = offsetof(struct kdbus_item, str) + m + 1; - item->type = KDBUS_ITEM_CONN_DESCRIPTION; - memcpy(item->str, name, m + 1); - item = KDBUS_ITEM_NEXT(item); - - if (b->fake_creds_valid) { - item->size = offsetof(struct kdbus_item, creds) + sizeof(struct kdbus_creds); - item->type = KDBUS_ITEM_CREDS; - item->creds = b->fake_creds; - - item = KDBUS_ITEM_NEXT(item); - } - - if (b->fake_pids_valid) { - item->size = offsetof(struct kdbus_item, pids) + sizeof(struct kdbus_pids); - item->type = KDBUS_ITEM_PIDS; - item->pids = b->fake_pids; - - item = KDBUS_ITEM_NEXT(item); - } - - if (b->fake_label) { - item->size = offsetof(struct kdbus_item, str) + l + 1; - item->type = KDBUS_ITEM_SECLABEL; - memcpy(item->str, b->fake_label, l+1); - } - - r = ioctl(b->input_fd, KDBUS_CMD_HELLO, hello); - if (r < 0) { - if (errno == ENOTTY) - /* If the ioctl is not supported we assume that the - * API version changed in a major incompatible way, - * let's indicate an API incompatibility in this - * case. */ - return -ESOCKTNOSUPPORT; - - return -errno; - } - - if (!b->kdbus_buffer) { - b->kdbus_buffer = mmap(NULL, KDBUS_POOL_SIZE, PROT_READ, MAP_SHARED, b->input_fd, 0); - if (b->kdbus_buffer == MAP_FAILED) { - b->kdbus_buffer = NULL; - r = -errno; - goto fail; - } - } - - /* The higher 32bit of the bus_flags fields are considered - * 'incompatible flags'. Refuse them all for now. */ - if (hello->bus_flags > 0xFFFFFFFFULL) { - r = -ESOCKTNOSUPPORT; - goto fail; - } - - /* extract bloom parameters from items */ - items = (void*)((uint8_t*)b->kdbus_buffer + hello->offset); - KDBUS_FOREACH(item, items, hello->items_size) { - switch (item->type) { - case KDBUS_ITEM_BLOOM_PARAMETER: - bloom = &item->bloom_parameter; - break; - } - } - - if (!bloom || !bloom_validate_parameters((size_t) bloom->size, (unsigned) bloom->n_hash)) { - r = -EOPNOTSUPP; - goto fail; - } - - b->bloom_size = (size_t) bloom->size; - b->bloom_n_hash = (unsigned) bloom->n_hash; - - if (asprintf(&b->unique_name, ":1.%llu", (unsigned long long) hello->id) < 0) { - r = -ENOMEM; - goto fail; - } - - b->unique_id = hello->id; - - b->is_kernel = true; - b->bus_client = true; - b->can_fds = !!(hello->flags & KDBUS_HELLO_ACCEPT_FD); - b->message_version = 2; - b->message_endian = BUS_NATIVE_ENDIAN; - - /* the kernel told us the UUID of the underlying bus */ - memcpy(b->server_id.bytes, hello->id128, sizeof(b->server_id.bytes)); - - /* free returned items */ - (void) bus_kernel_cmd_free(b, hello->offset); - return bus_start_running(b); - -fail: - (void) bus_kernel_cmd_free(b, hello->offset); - return r; -} - -int bus_kernel_connect(sd_bus *b) { - assert(b); - assert(b->input_fd < 0); - assert(b->output_fd < 0); - assert(b->kernel); - - if (b->is_server) - return -EINVAL; - - b->input_fd = open(b->kernel, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (b->input_fd < 0) - return -errno; - - b->output_fd = b->input_fd; - - return bus_kernel_take_fd(b); -} - -int bus_kernel_cmd_free(sd_bus *bus, uint64_t offset) { - struct kdbus_cmd_free cmd = { - .size = sizeof(cmd), - .offset = offset, - }; - int r; - - assert(bus); - assert(bus->is_kernel); - - r = ioctl(bus->input_fd, KDBUS_CMD_FREE, &cmd); - if (r < 0) - return -errno; - - return 0; -} - -static void close_kdbus_msg(sd_bus *bus, struct kdbus_msg *k) { - struct kdbus_item *d; - - assert(bus); - assert(k); - - KDBUS_ITEM_FOREACH(d, k, items) { - if (d->type == KDBUS_ITEM_FDS) - close_many(d->fds, (d->size - offsetof(struct kdbus_item, fds)) / sizeof(int)); - else if (d->type == KDBUS_ITEM_PAYLOAD_MEMFD) - safe_close(d->memfd.fd); - } - - bus_kernel_cmd_free(bus, (uint8_t*) k - (uint8_t*) bus->kdbus_buffer); -} - -int bus_kernel_write_message(sd_bus *bus, sd_bus_message *m, bool hint_sync_call) { - struct kdbus_cmd_send cmd = { }; - int r; - - assert(bus); - assert(m); - assert(bus->state == BUS_RUNNING); - - /* If we can't deliver, we want room for the error message */ - r = bus_rqueue_make_room(bus); - if (r < 0) - return r; - - r = bus_message_setup_kmsg(bus, m); - if (r < 0) - return r; - - cmd.size = sizeof(cmd); - cmd.msg_address = (uintptr_t)m->kdbus; - - /* If this is a synchronous method call, then let's tell the - * kernel, so that it can pass CPU time/scheduling to the - * destination for the time, if it wants to. If we - * synchronously wait for the result anyway, we won't need CPU - * anyway. */ - if (hint_sync_call) { - m->kdbus->flags |= KDBUS_MSG_EXPECT_REPLY; - cmd.flags |= KDBUS_SEND_SYNC_REPLY; - } - - r = ioctl(bus->output_fd, KDBUS_CMD_SEND, &cmd); - if (r < 0) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus_message *reply; - - if (errno == EAGAIN || errno == EINTR) - return 0; - else if (errno == ENXIO || errno == ESRCH) { - - /* ENXIO: unique name not known - * ESRCH: well-known name not known */ - - if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) - sd_bus_error_setf(&error, SD_BUS_ERROR_SERVICE_UNKNOWN, "Destination %s not known", m->destination); - else { - log_debug("Could not deliver message to %s as destination is not known. Ignoring.", m->destination); - return 0; - } - - } else if (errno == EADDRNOTAVAIL) { - - /* EADDRNOTAVAIL: activation is possible, but turned off in request flags */ - - if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) - sd_bus_error_setf(&error, SD_BUS_ERROR_SERVICE_UNKNOWN, "Activation of %s not requested", m->destination); - else { - log_debug("Could not deliver message to %s as destination is not activated. Ignoring.", m->destination); - return 0; - } - } else - return -errno; - - r = bus_message_new_synthetic_error( - bus, - BUS_MESSAGE_COOKIE(m), - &error, - &reply); - - if (r < 0) - return r; - - r = bus_seal_synthetic_message(bus, reply); - if (r < 0) - return r; - - bus->rqueue[bus->rqueue_size++] = reply; - - } else if (hint_sync_call) { - struct kdbus_msg *k; - - k = (struct kdbus_msg *)((uint8_t *)bus->kdbus_buffer + cmd.reply.offset); - assert(k); - - if (k->payload_type == KDBUS_PAYLOAD_DBUS) { - - r = bus_kernel_make_message(bus, k); - if (r < 0) { - close_kdbus_msg(bus, k); - - /* Anybody can send us invalid messages, let's just drop them. */ - if (r == -EBADMSG || r == -EPROTOTYPE) - log_debug_errno(r, "Ignoring invalid synchronous reply: %m"); - else - return r; - } - } else { - log_debug("Ignoring message with unknown payload type %llu.", (unsigned long long) k->payload_type); - close_kdbus_msg(bus, k); - } - } - - return 1; -} - -static int push_name_owner_changed( - sd_bus *bus, - const char *name, - const char *old_owner, - const char *new_owner, - const struct kdbus_timestamp *ts) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - assert(bus); - - r = sd_bus_message_new_signal( - bus, - &m, - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "NameOwnerChanged"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "sss", name, old_owner, new_owner); - if (r < 0) - return r; - - bus_message_set_sender_driver(bus, m); - message_set_timestamp(bus, m, ts); - - r = bus_seal_synthetic_message(bus, m); - if (r < 0) - return r; - - bus->rqueue[bus->rqueue_size++] = m; - m = NULL; - - return 1; -} - -static int translate_name_change( - sd_bus *bus, - const struct kdbus_msg *k, - const struct kdbus_item *d, - const struct kdbus_timestamp *ts) { - - char new_owner[UNIQUE_NAME_MAX], old_owner[UNIQUE_NAME_MAX]; - - assert(bus); - assert(k); - assert(d); - - if (d->type == KDBUS_ITEM_NAME_ADD || (d->name_change.old_id.flags & (KDBUS_NAME_IN_QUEUE|KDBUS_NAME_ACTIVATOR))) - old_owner[0] = 0; - else - sprintf(old_owner, ":1.%llu", (unsigned long long) d->name_change.old_id.id); - - if (d->type == KDBUS_ITEM_NAME_REMOVE || (d->name_change.new_id.flags & (KDBUS_NAME_IN_QUEUE|KDBUS_NAME_ACTIVATOR))) { - - if (isempty(old_owner)) - return 0; - - new_owner[0] = 0; - } else - sprintf(new_owner, ":1.%llu", (unsigned long long) d->name_change.new_id.id); - - return push_name_owner_changed(bus, d->name_change.name, old_owner, new_owner, ts); -} - -static int translate_id_change( - sd_bus *bus, - const struct kdbus_msg *k, - const struct kdbus_item *d, - const struct kdbus_timestamp *ts) { - - char owner[UNIQUE_NAME_MAX]; - - assert(bus); - assert(k); - assert(d); - - sprintf(owner, ":1.%llu", d->id_change.id); - - return push_name_owner_changed( - bus, owner, - d->type == KDBUS_ITEM_ID_ADD ? NULL : owner, - d->type == KDBUS_ITEM_ID_ADD ? owner : NULL, - ts); -} - -static int translate_reply( - sd_bus *bus, - const struct kdbus_msg *k, - const struct kdbus_item *d, - const struct kdbus_timestamp *ts) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - assert(bus); - assert(k); - assert(d); - - r = bus_message_new_synthetic_error( - bus, - k->cookie_reply, - d->type == KDBUS_ITEM_REPLY_TIMEOUT ? - &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Method call timed out") : - &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Method call peer died"), - &m); - if (r < 0) - return r; - - message_set_timestamp(bus, m, ts); - - r = bus_seal_synthetic_message(bus, m); - if (r < 0) - return r; - - bus->rqueue[bus->rqueue_size++] = m; - m = NULL; - - return 1; -} - -static int bus_kernel_translate_message(sd_bus *bus, struct kdbus_msg *k) { - static int (* const translate[])(sd_bus *bus, const struct kdbus_msg *k, const struct kdbus_item *d, const struct kdbus_timestamp *ts) = { - [KDBUS_ITEM_NAME_ADD - _KDBUS_ITEM_KERNEL_BASE] = translate_name_change, - [KDBUS_ITEM_NAME_REMOVE - _KDBUS_ITEM_KERNEL_BASE] = translate_name_change, - [KDBUS_ITEM_NAME_CHANGE - _KDBUS_ITEM_KERNEL_BASE] = translate_name_change, - - [KDBUS_ITEM_ID_ADD - _KDBUS_ITEM_KERNEL_BASE] = translate_id_change, - [KDBUS_ITEM_ID_REMOVE - _KDBUS_ITEM_KERNEL_BASE] = translate_id_change, - - [KDBUS_ITEM_REPLY_TIMEOUT - _KDBUS_ITEM_KERNEL_BASE] = translate_reply, - [KDBUS_ITEM_REPLY_DEAD - _KDBUS_ITEM_KERNEL_BASE] = translate_reply, - }; - - struct kdbus_item *d, *found = NULL; - struct kdbus_timestamp *ts = NULL; - - assert(bus); - assert(k); - assert(k->payload_type == KDBUS_PAYLOAD_KERNEL); - - KDBUS_ITEM_FOREACH(d, k, items) { - if (d->type == KDBUS_ITEM_TIMESTAMP) - ts = &d->timestamp; - else if (d->type >= _KDBUS_ITEM_KERNEL_BASE && d->type < _KDBUS_ITEM_KERNEL_BASE + ELEMENTSOF(translate)) { - if (found) - return -EBADMSG; - found = d; - } else - log_debug("Got unknown field from kernel %llu", d->type); - } - - if (!found) { - log_debug("Didn't find a kernel message to translate."); - return 0; - } - - return translate[found->type - _KDBUS_ITEM_KERNEL_BASE](bus, k, found, ts); -} - -int bus_kernel_read_message(sd_bus *bus, bool hint_priority, int64_t priority) { - struct kdbus_cmd_recv recv = { .size = sizeof(recv) }; - struct kdbus_msg *k; - int r; - - assert(bus); - - r = bus_rqueue_make_room(bus); - if (r < 0) - return r; - - if (hint_priority) { - recv.flags |= KDBUS_RECV_USE_PRIORITY; - recv.priority = priority; - } - - r = ioctl(bus->input_fd, KDBUS_CMD_RECV, &recv); - if (recv.return_flags & KDBUS_RECV_RETURN_DROPPED_MSGS) - log_debug("%s: kdbus reports %" PRIu64 " dropped broadcast messages, ignoring.", strna(bus->description), (uint64_t) recv.dropped_msgs); - if (r < 0) { - if (errno == EAGAIN) - return 0; - - return -errno; - } - - k = (struct kdbus_msg *)((uint8_t *)bus->kdbus_buffer + recv.msg.offset); - if (k->payload_type == KDBUS_PAYLOAD_DBUS) { - r = bus_kernel_make_message(bus, k); - - /* Anybody can send us invalid messages, let's just drop them. */ - if (r == -EBADMSG || r == -EPROTOTYPE) { - log_debug_errno(r, "Ignoring invalid message: %m"); - r = 0; - } - - if (r <= 0) - close_kdbus_msg(bus, k); - } else if (k->payload_type == KDBUS_PAYLOAD_KERNEL) { - r = bus_kernel_translate_message(bus, k); - close_kdbus_msg(bus, k); - } else { - log_debug("Ignoring message with unknown payload type %llu.", (unsigned long long) k->payload_type); - r = 0; - close_kdbus_msg(bus, k); - } - - return r < 0 ? r : 1; -} - -int bus_kernel_pop_memfd(sd_bus *bus, void **address, size_t *mapped, size_t *allocated) { - struct memfd_cache *c; - int fd; - - assert(address); - assert(mapped); - assert(allocated); - - if (!bus || !bus->is_kernel) - return -EOPNOTSUPP; - - assert_se(pthread_mutex_lock(&bus->memfd_cache_mutex) == 0); - - if (bus->n_memfd_cache <= 0) { - int r; - - assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) == 0); - - r = memfd_new(bus->description); - if (r < 0) - return r; - - *address = NULL; - *mapped = 0; - *allocated = 0; - return r; - } - - c = &bus->memfd_cache[--bus->n_memfd_cache]; - - assert(c->fd >= 0); - assert(c->mapped == 0 || c->address); - - *address = c->address; - *mapped = c->mapped; - *allocated = c->allocated; - fd = c->fd; - - assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) == 0); - - return fd; -} - -static void close_and_munmap(int fd, void *address, size_t size) { - if (size > 0) - assert_se(munmap(address, PAGE_ALIGN(size)) >= 0); - - safe_close(fd); -} - -void bus_kernel_push_memfd(sd_bus *bus, int fd, void *address, size_t mapped, size_t allocated) { - struct memfd_cache *c; - uint64_t max_mapped = PAGE_ALIGN(MEMFD_CACHE_ITEM_SIZE_MAX); - - assert(fd >= 0); - assert(mapped == 0 || address); - - if (!bus || !bus->is_kernel) { - close_and_munmap(fd, address, mapped); - return; - } - - assert_se(pthread_mutex_lock(&bus->memfd_cache_mutex) == 0); - - if (bus->n_memfd_cache >= ELEMENTSOF(bus->memfd_cache)) { - assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) == 0); - - close_and_munmap(fd, address, mapped); - return; - } - - c = &bus->memfd_cache[bus->n_memfd_cache++]; - c->fd = fd; - c->address = address; - - /* If overly long, let's return a bit to the OS */ - if (mapped > max_mapped) { - assert_se(memfd_set_size(fd, max_mapped) >= 0); - assert_se(munmap((uint8_t*) address + max_mapped, PAGE_ALIGN(mapped - max_mapped)) >= 0); - c->mapped = c->allocated = max_mapped; - } else { - c->mapped = mapped; - c->allocated = allocated; - } - - assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) == 0); -} - -void bus_kernel_flush_memfd(sd_bus *b) { - unsigned i; - - assert(b); - - for (i = 0; i < b->n_memfd_cache; i++) - close_and_munmap(b->memfd_cache[i].fd, b->memfd_cache[i].address, b->memfd_cache[i].mapped); -} - -uint64_t request_name_flags_to_kdbus(uint64_t flags) { - uint64_t f = 0; - - if (flags & SD_BUS_NAME_ALLOW_REPLACEMENT) - f |= KDBUS_NAME_ALLOW_REPLACEMENT; - - if (flags & SD_BUS_NAME_REPLACE_EXISTING) - f |= KDBUS_NAME_REPLACE_EXISTING; - - if (flags & SD_BUS_NAME_QUEUE) - f |= KDBUS_NAME_QUEUE; - - return f; -} - -uint64_t attach_flags_to_kdbus(uint64_t mask) { - uint64_t m = 0; - - if (mask & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| - SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID)) - m |= KDBUS_ATTACH_CREDS; - - if (mask & (SD_BUS_CREDS_PID|SD_BUS_CREDS_TID|SD_BUS_CREDS_PPID)) - m |= KDBUS_ATTACH_PIDS; - - if (mask & SD_BUS_CREDS_COMM) - m |= KDBUS_ATTACH_PID_COMM; - - if (mask & SD_BUS_CREDS_TID_COMM) - m |= KDBUS_ATTACH_TID_COMM; - - if (mask & SD_BUS_CREDS_EXE) - m |= KDBUS_ATTACH_EXE; - - if (mask & SD_BUS_CREDS_CMDLINE) - m |= KDBUS_ATTACH_CMDLINE; - - if (mask & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID)) - m |= KDBUS_ATTACH_CGROUP; - - if (mask & (SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS)) - m |= KDBUS_ATTACH_CAPS; - - if (mask & SD_BUS_CREDS_SELINUX_CONTEXT) - m |= KDBUS_ATTACH_SECLABEL; - - if (mask & (SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID)) - m |= KDBUS_ATTACH_AUDIT; - - if (mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) - m |= KDBUS_ATTACH_NAMES; - - if (mask & SD_BUS_CREDS_DESCRIPTION) - m |= KDBUS_ATTACH_CONN_DESCRIPTION; - - if (mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) - m |= KDBUS_ATTACH_AUXGROUPS; - - return m; -} - -int bus_kernel_create_bus(const char *name, bool world, char **s) { - struct kdbus_cmd *make; - struct kdbus_item *n; - size_t l; - int fd; - - assert(name); - assert(s); - - fd = open("/sys/fs/kdbus/control", O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return -errno; - - l = strlen(name); - make = alloca0_align(offsetof(struct kdbus_cmd, items) + - ALIGN8(offsetof(struct kdbus_item, bloom_parameter) + sizeof(struct kdbus_bloom_parameter)) + - ALIGN8(offsetof(struct kdbus_item, data64) + sizeof(uint64_t)) + - ALIGN8(offsetof(struct kdbus_item, str) + DECIMAL_STR_MAX(uid_t) + 1 + l + 1), - 8); - - make->size = offsetof(struct kdbus_cmd, items); - - /* Set the bloom parameters */ - n = make->items; - n->size = offsetof(struct kdbus_item, bloom_parameter) + - sizeof(struct kdbus_bloom_parameter); - n->type = KDBUS_ITEM_BLOOM_PARAMETER; - n->bloom_parameter.size = DEFAULT_BLOOM_SIZE; - n->bloom_parameter.n_hash = DEFAULT_BLOOM_N_HASH; - - assert_cc(DEFAULT_BLOOM_SIZE > 0); - assert_cc(DEFAULT_BLOOM_N_HASH > 0); - - make->size += ALIGN8(n->size); - - /* Provide all metadata via bus-owner queries */ - n = KDBUS_ITEM_NEXT(n); - n->type = KDBUS_ITEM_ATTACH_FLAGS_SEND; - n->size = offsetof(struct kdbus_item, data64) + sizeof(uint64_t); - n->data64[0] = _KDBUS_ATTACH_ANY; - make->size += ALIGN8(n->size); - - /* Set the a good name */ - n = KDBUS_ITEM_NEXT(n); - sprintf(n->str, UID_FMT "-%s", getuid(), name); - n->size = offsetof(struct kdbus_item, str) + strlen(n->str) + 1; - n->type = KDBUS_ITEM_MAKE_NAME; - make->size += ALIGN8(n->size); - - make->flags = world ? KDBUS_MAKE_ACCESS_WORLD : 0; - - if (ioctl(fd, KDBUS_CMD_BUS_MAKE, make) < 0) { - safe_close(fd); - - /* Major API change? then the ioctls got shuffled around. */ - if (errno == ENOTTY) - return -ESOCKTNOSUPPORT; - - return -errno; - } - - if (s) { - char *p; - - p = strjoin("/sys/fs/kdbus/", n->str, "/bus", NULL); - if (!p) { - safe_close(fd); - return -ENOMEM; - } - - *s = p; - } - - return fd; -} - -int bus_kernel_open_bus_fd(const char *bus, char **path) { - char *p; - int fd; - size_t len; - - assert(bus); - - len = strlen("/sys/fs/kdbus/") + DECIMAL_STR_MAX(uid_t) + 1 + strlen(bus) + strlen("/bus") + 1; - - if (path) { - p = new(char, len); - if (!p) - return -ENOMEM; - } else - p = newa(char, len); - - sprintf(p, "/sys/fs/kdbus/" UID_FMT "-%s/bus", getuid(), bus); - - fd = open(p, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) { - if (path) - free(p); - - return -errno; - } - - if (path) - *path = p; - - return fd; -} - -int bus_kernel_try_close(sd_bus *bus) { - struct kdbus_cmd byebye = { .size = sizeof(byebye) }; - - assert(bus); - assert(bus->is_kernel); - - if (ioctl(bus->input_fd, KDBUS_CMD_BYEBYE, &byebye) < 0) - return -errno; - - return 0; -} - -int bus_kernel_drop_one(int fd) { - struct kdbus_cmd_recv recv = { - .size = sizeof(recv), - .flags = KDBUS_RECV_DROP, - }; - - assert(fd >= 0); - - if (ioctl(fd, KDBUS_CMD_RECV, &recv) < 0) - return -errno; - - return 0; -} - -int bus_kernel_realize_attach_flags(sd_bus *bus) { - struct kdbus_cmd *update; - struct kdbus_item *n; - - assert(bus); - assert(bus->is_kernel); - - update = alloca0_align(offsetof(struct kdbus_cmd, items) + - ALIGN8(offsetof(struct kdbus_item, data64) + sizeof(uint64_t)), - 8); - - n = update->items; - n->type = KDBUS_ITEM_ATTACH_FLAGS_RECV; - n->size = offsetof(struct kdbus_item, data64) + sizeof(uint64_t); - n->data64[0] = bus->attach_flags; - - update->size = - offsetof(struct kdbus_cmd, items) + - ALIGN8(n->size); - - if (ioctl(bus->input_fd, KDBUS_CMD_UPDATE, update) < 0) - return -errno; - - return 0; -} - -int bus_kernel_get_bus_name(sd_bus *bus, char **name) { - struct kdbus_cmd_info cmd = { - .size = sizeof(struct kdbus_cmd_info), - }; - struct kdbus_info *info; - struct kdbus_item *item; - char *n = NULL; - int r; - - assert(bus); - assert(name); - assert(bus->is_kernel); - - r = ioctl(bus->input_fd, KDBUS_CMD_BUS_CREATOR_INFO, &cmd); - if (r < 0) - return -errno; - - info = (struct kdbus_info*) ((uint8_t*) bus->kdbus_buffer + cmd.offset); - - KDBUS_ITEM_FOREACH(item, info, items) - if (item->type == KDBUS_ITEM_MAKE_NAME) { - r = free_and_strdup(&n, item->str); - break; - } - - bus_kernel_cmd_free(bus, cmd.offset); - - if (r < 0) - return r; - if (!n) - return -EIO; - - *name = n; - return 0; -} diff --git a/src/libsystemd/sd-bus/bus-kernel.h b/src/libsystemd/sd-bus/bus-kernel.h deleted file mode 100644 index 53ba3bdcf3..0000000000 --- a/src/libsystemd/sd-bus/bus-kernel.h +++ /dev/null @@ -1,93 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -#define KDBUS_ITEM_NEXT(item) \ - (typeof(item))(((uint8_t *)item) + ALIGN8((item)->size)) - -#define KDBUS_ITEM_FOREACH(part, head, first) \ - for (part = (head)->first; \ - ((uint8_t *)(part) < (uint8_t *)(head) + (head)->size) && \ - ((uint8_t *) part >= (uint8_t *) head); \ - part = KDBUS_ITEM_NEXT(part)) -#define KDBUS_FOREACH(iter, first, _size) \ - for (iter = (first); \ - ((uint8_t *)(iter) < (uint8_t *)(first) + (_size)) && \ - ((uint8_t *)(iter) >= (uint8_t *)(first)); \ - iter = (void*)(((uint8_t *)iter) + ALIGN8((iter)->size))) - -#define KDBUS_ITEM_HEADER_SIZE offsetof(struct kdbus_item, data) -#define KDBUS_ITEM_SIZE(s) ALIGN8((s) + KDBUS_ITEM_HEADER_SIZE) - -#define MEMFD_CACHE_MAX 32 - -/* When we cache a memfd block for reuse, we will truncate blocks - * longer than this in order not to keep too much data around. */ -#define MEMFD_CACHE_ITEM_SIZE_MAX (128*1024) - -/* This determines at which minimum size we prefer sending memfds over - * sending vectors */ -#define MEMFD_MIN_SIZE (512*1024) - -/* The size of the per-connection memory pool that we set up and where - * the kernel places our incoming messages */ -#define KDBUS_POOL_SIZE (16*1024*1024) - -struct memfd_cache { - int fd; - void *address; - size_t mapped; - size_t allocated; -}; - -int bus_kernel_connect(sd_bus *b); -int bus_kernel_take_fd(sd_bus *b); - -int bus_kernel_write_message(sd_bus *bus, sd_bus_message *m, bool hint_sync_call); -int bus_kernel_read_message(sd_bus *bus, bool hint_priority, int64_t priority); - -int bus_kernel_open_bus_fd(const char *bus, char **path); - -int bus_kernel_create_bus(const char *name, bool world, char **s); -int bus_kernel_create_endpoint(const char *bus_name, const char *ep_name, char **path); - -int bus_kernel_pop_memfd(sd_bus *bus, void **address, size_t *mapped, size_t *allocated); -void bus_kernel_push_memfd(sd_bus *bus, int fd, void *address, size_t mapped, size_t allocated); - -void bus_kernel_flush_memfd(sd_bus *bus); - -int bus_kernel_parse_unique_name(const char *s, uint64_t *id); - -uint64_t request_name_flags_to_kdbus(uint64_t sd_bus_flags); -uint64_t attach_flags_to_kdbus(uint64_t sd_bus_flags); - -int bus_kernel_try_close(sd_bus *bus); - -int bus_kernel_drop_one(int fd); - -int bus_kernel_realize_attach_flags(sd_bus *bus); - -int bus_kernel_get_bus_name(sd_bus *bus, char **name); - -int bus_kernel_cmd_free(sd_bus *bus, uint64_t offset); diff --git a/src/libsystemd/sd-bus/bus-match.c b/src/libsystemd/sd-bus/bus-match.c deleted file mode 100644 index 397baf6f33..0000000000 --- a/src/libsystemd/sd-bus/bus-match.c +++ /dev/null @@ -1,1218 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "alloc-util.h" -#include "bus-internal.h" -#include "bus-match.h" -#include "bus-message.h" -#include "bus-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "hexdecoct.h" -#include "string-util.h" -#include "strv.h" - -/* Example: - * - * A: type=signal,sender=foo,interface=bar - * B: type=signal,sender=quux,interface=fips - * C: type=signal,sender=quux,interface=waldo - * D: type=signal,member=test - * E: sender=miau - * F: type=signal - * G: type=signal - * - * results in this tree: - * - * BUS_MATCH_ROOT - * + BUS_MATCH_MESSAGE_TYPE - * | ` BUS_MATCH_VALUE: value == signal - * | + DBUS_MATCH_SENDER - * | | + BUS_MATCH_VALUE: value == foo - * | | | ` DBUS_MATCH_INTERFACE - * | | | ` BUS_MATCH_VALUE: value == bar - * | | | ` BUS_MATCH_LEAF: A - * | | ` BUS_MATCH_VALUE: value == quux - * | | ` DBUS_MATCH_INTERFACE - * | | | BUS_MATCH_VALUE: value == fips - * | | | ` BUS_MATCH_LEAF: B - * | | ` BUS_MATCH_VALUE: value == waldo - * | | ` BUS_MATCH_LEAF: C - * | + DBUS_MATCH_MEMBER - * | | ` BUS_MATCH_VALUE: value == test - * | | ` BUS_MATCH_LEAF: D - * | + BUS_MATCH_LEAF: F - * | ` BUS_MATCH_LEAF: G - * ` BUS_MATCH_SENDER - * ` BUS_MATCH_VALUE: value == miau - * ` BUS_MATCH_LEAF: E - */ - -static inline bool BUS_MATCH_IS_COMPARE(enum bus_match_node_type t) { - return t >= BUS_MATCH_SENDER && t <= BUS_MATCH_ARG_HAS_LAST; -} - -static inline bool BUS_MATCH_CAN_HASH(enum bus_match_node_type t) { - return (t >= BUS_MATCH_MESSAGE_TYPE && t <= BUS_MATCH_PATH) || - (t >= BUS_MATCH_ARG && t <= BUS_MATCH_ARG_LAST) || - (t >= BUS_MATCH_ARG_HAS && t <= BUS_MATCH_ARG_HAS_LAST); -} - -static void bus_match_node_free(struct bus_match_node *node) { - assert(node); - assert(node->parent); - assert(!node->child); - assert(node->type != BUS_MATCH_ROOT); - assert(node->type < _BUS_MATCH_NODE_TYPE_MAX); - - if (node->parent->child) { - /* We are apparently linked into the parent's child - * list. Let's remove us from there. */ - if (node->prev) { - assert(node->prev->next == node); - node->prev->next = node->next; - } else { - assert(node->parent->child == node); - node->parent->child = node->next; - } - - if (node->next) - node->next->prev = node->prev; - } - - if (node->type == BUS_MATCH_VALUE) { - /* We might be in the parent's hash table, so clean - * this up */ - - if (node->parent->type == BUS_MATCH_MESSAGE_TYPE) - hashmap_remove(node->parent->compare.children, UINT_TO_PTR(node->value.u8)); - else if (BUS_MATCH_CAN_HASH(node->parent->type) && node->value.str) - hashmap_remove(node->parent->compare.children, node->value.str); - - free(node->value.str); - } - - if (BUS_MATCH_IS_COMPARE(node->type)) { - assert(hashmap_isempty(node->compare.children)); - hashmap_free(node->compare.children); - } - - free(node); -} - -static bool bus_match_node_maybe_free(struct bus_match_node *node) { - assert(node); - - if (node->type == BUS_MATCH_ROOT) - return false; - - if (node->child) - return false; - - if (BUS_MATCH_IS_COMPARE(node->type) && !hashmap_isempty(node->compare.children)) - return true; - - bus_match_node_free(node); - return true; -} - -static bool value_node_test( - struct bus_match_node *node, - enum bus_match_node_type parent_type, - uint8_t value_u8, - const char *value_str, - char **value_strv, - sd_bus_message *m) { - - assert(node); - assert(node->type == BUS_MATCH_VALUE); - - /* Tests parameters against this value node, doing prefix - * magic and stuff. */ - - switch (parent_type) { - - case BUS_MATCH_MESSAGE_TYPE: - return node->value.u8 == value_u8; - - case BUS_MATCH_SENDER: - if (streq_ptr(node->value.str, value_str)) - return true; - - if (m->creds.mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) { - char **i; - - /* on kdbus we have the well known names list - * in the credentials, let's make use of that - * for an accurate match */ - - STRV_FOREACH(i, m->creds.well_known_names) - if (streq_ptr(node->value.str, *i)) - return true; - - } else { - - /* If we don't have kdbus, we don't know the - * well-known names of the senders. In that, - * let's just hope that dbus-daemon doesn't - * send us stuff we didn't want. */ - - if (node->value.str[0] != ':' && value_str && value_str[0] == ':') - return true; - } - - return false; - - case BUS_MATCH_DESTINATION: - case BUS_MATCH_INTERFACE: - case BUS_MATCH_MEMBER: - case BUS_MATCH_PATH: - case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: - - if (value_str) - return streq_ptr(node->value.str, value_str); - - return false; - - case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST: { - char **i; - - STRV_FOREACH(i, value_strv) - if (streq_ptr(node->value.str, *i)) - return true; - - return false; - } - - case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: - if (value_str) - return namespace_simple_pattern(node->value.str, value_str); - - return false; - - case BUS_MATCH_PATH_NAMESPACE: - return path_simple_pattern(node->value.str, value_str); - - case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: - if (value_str) - return path_complex_pattern(node->value.str, value_str); - - return false; - - default: - assert_not_reached("Invalid node type"); - } -} - -static bool value_node_same( - struct bus_match_node *node, - enum bus_match_node_type parent_type, - uint8_t value_u8, - const char *value_str) { - - /* Tests parameters against this value node, not doing prefix - * magic and stuff, i.e. this one actually compares the match - * itself. */ - - assert(node); - assert(node->type == BUS_MATCH_VALUE); - - switch (parent_type) { - - case BUS_MATCH_MESSAGE_TYPE: - return node->value.u8 == value_u8; - - case BUS_MATCH_SENDER: - case BUS_MATCH_DESTINATION: - case BUS_MATCH_INTERFACE: - case BUS_MATCH_MEMBER: - case BUS_MATCH_PATH: - case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: - case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST: - case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: - case BUS_MATCH_PATH_NAMESPACE: - case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: - return streq(node->value.str, value_str); - - default: - assert_not_reached("Invalid node type"); - } -} - -int bus_match_run( - sd_bus *bus, - struct bus_match_node *node, - sd_bus_message *m) { - - _cleanup_strv_free_ char **test_strv = NULL; - const char *test_str = NULL; - uint8_t test_u8 = 0; - int r; - - assert(m); - - if (!node) - return 0; - - if (bus && bus->match_callbacks_modified) - return 0; - - /* Not these special semantics: when traversing the tree we - * usually let bus_match_run() when called for a node - * recursively invoke bus_match_run(). There's are two - * exceptions here though, which are BUS_NODE_ROOT (which - * cannot have a sibling), and BUS_NODE_VALUE (whose siblings - * are invoked anyway by its parent. */ - - switch (node->type) { - - case BUS_MATCH_ROOT: - - /* Run all children. Since we cannot have any siblings - * we won't call any. The children of the root node - * are compares or leaves, they will automatically - * call their siblings. */ - return bus_match_run(bus, node->child, m); - - case BUS_MATCH_VALUE: - - /* Run all children. We don't execute any siblings, we - * assume our caller does that. The children of value - * nodes are compares or leaves, they will - * automatically call their siblings */ - - assert(node->child); - return bus_match_run(bus, node->child, m); - - case BUS_MATCH_LEAF: - - if (bus) { - if (node->leaf.callback->last_iteration == bus->iteration_counter) - return 0; - - node->leaf.callback->last_iteration = bus->iteration_counter; - } - - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - /* Run the callback. And then invoke siblings. */ - if (node->leaf.callback->callback) { - _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; - sd_bus_slot *slot; - - slot = container_of(node->leaf.callback, sd_bus_slot, match_callback); - if (bus) { - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_handler = node->leaf.callback->callback; - bus->current_userdata = slot->userdata; - } - r = node->leaf.callback->callback(m, slot->userdata, &error_buffer); - if (bus) { - bus->current_userdata = NULL; - bus->current_handler = NULL; - bus->current_slot = sd_bus_slot_unref(slot); - } - - r = bus_maybe_reply_error(m, r, &error_buffer); - if (r != 0) - return r; - - if (bus && bus->match_callbacks_modified) - return 0; - } - - return bus_match_run(bus, node->next, m); - - case BUS_MATCH_MESSAGE_TYPE: - test_u8 = m->header->type; - break; - - case BUS_MATCH_SENDER: - test_str = m->sender; - /* FIXME: resolve test_str from a well-known to a unique name first */ - break; - - case BUS_MATCH_DESTINATION: - test_str = m->destination; - break; - - case BUS_MATCH_INTERFACE: - test_str = m->interface; - break; - - case BUS_MATCH_MEMBER: - test_str = m->member; - break; - - case BUS_MATCH_PATH: - case BUS_MATCH_PATH_NAMESPACE: - test_str = m->path; - break; - - case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: - (void) bus_message_get_arg(m, node->type - BUS_MATCH_ARG, &test_str); - break; - - case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: - (void) bus_message_get_arg(m, node->type - BUS_MATCH_ARG_PATH, &test_str); - break; - - case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: - (void) bus_message_get_arg(m, node->type - BUS_MATCH_ARG_NAMESPACE, &test_str); - break; - - case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST: - (void) bus_message_get_arg_strv(m, node->type - BUS_MATCH_ARG_HAS, &test_strv); - break; - - default: - assert_not_reached("Unknown match type."); - } - - if (BUS_MATCH_CAN_HASH(node->type)) { - struct bus_match_node *found; - - /* Lookup via hash table, nice! So let's jump directly. */ - - if (test_str) - found = hashmap_get(node->compare.children, test_str); - else if (test_strv) { - char **i; - - STRV_FOREACH(i, test_strv) { - found = hashmap_get(node->compare.children, *i); - if (found) { - r = bus_match_run(bus, found, m); - if (r != 0) - return r; - } - } - - found = NULL; - } else if (node->type == BUS_MATCH_MESSAGE_TYPE) - found = hashmap_get(node->compare.children, UINT_TO_PTR(test_u8)); - else - found = NULL; - - if (found) { - r = bus_match_run(bus, found, m); - if (r != 0) - return r; - } - } else { - struct bus_match_node *c; - - /* No hash table, so let's iterate manually... */ - - for (c = node->child; c; c = c->next) { - if (!value_node_test(c, node->type, test_u8, test_str, test_strv, m)) - continue; - - r = bus_match_run(bus, c, m); - if (r != 0) - return r; - } - } - - if (bus && bus->match_callbacks_modified) - return 0; - - /* And now, let's invoke our siblings */ - return bus_match_run(bus, node->next, m); -} - -static int bus_match_add_compare_value( - struct bus_match_node *where, - enum bus_match_node_type t, - uint8_t value_u8, - const char *value_str, - struct bus_match_node **ret) { - - struct bus_match_node *c = NULL, *n = NULL; - int r; - - assert(where); - assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); - assert(BUS_MATCH_IS_COMPARE(t)); - assert(ret); - - for (c = where->child; c && c->type != t; c = c->next) - ; - - if (c) { - /* Comparison node already exists? Then let's see if - * the value node exists too. */ - - if (t == BUS_MATCH_MESSAGE_TYPE) - n = hashmap_get(c->compare.children, UINT_TO_PTR(value_u8)); - else if (BUS_MATCH_CAN_HASH(t)) - n = hashmap_get(c->compare.children, value_str); - else { - for (n = c->child; n && !value_node_same(n, t, value_u8, value_str); n = n->next) - ; - } - - if (n) { - *ret = n; - return 0; - } - } else { - /* Comparison node, doesn't exist yet? Then let's - * create it. */ - - c = new0(struct bus_match_node, 1); - if (!c) { - r = -ENOMEM; - goto fail; - } - - c->type = t; - c->parent = where; - c->next = where->child; - if (c->next) - c->next->prev = c; - where->child = c; - - if (t == BUS_MATCH_MESSAGE_TYPE) { - c->compare.children = hashmap_new(NULL); - if (!c->compare.children) { - r = -ENOMEM; - goto fail; - } - } else if (BUS_MATCH_CAN_HASH(t)) { - c->compare.children = hashmap_new(&string_hash_ops); - if (!c->compare.children) { - r = -ENOMEM; - goto fail; - } - } - } - - n = new0(struct bus_match_node, 1); - if (!n) { - r = -ENOMEM; - goto fail; - } - - n->type = BUS_MATCH_VALUE; - n->value.u8 = value_u8; - if (value_str) { - n->value.str = strdup(value_str); - if (!n->value.str) { - r = -ENOMEM; - goto fail; - } - } - - n->parent = c; - if (c->compare.children) { - - if (t == BUS_MATCH_MESSAGE_TYPE) - r = hashmap_put(c->compare.children, UINT_TO_PTR(value_u8), n); - else - r = hashmap_put(c->compare.children, n->value.str, n); - - if (r < 0) - goto fail; - } else { - n->next = c->child; - if (n->next) - n->next->prev = n; - c->child = n; - } - - *ret = n; - return 1; - -fail: - if (c) - bus_match_node_maybe_free(c); - - if (n) { - free(n->value.str); - free(n); - } - - return r; -} - -static int bus_match_find_compare_value( - struct bus_match_node *where, - enum bus_match_node_type t, - uint8_t value_u8, - const char *value_str, - struct bus_match_node **ret) { - - struct bus_match_node *c, *n; - - assert(where); - assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); - assert(BUS_MATCH_IS_COMPARE(t)); - assert(ret); - - for (c = where->child; c && c->type != t; c = c->next) - ; - - if (!c) - return 0; - - if (t == BUS_MATCH_MESSAGE_TYPE) - n = hashmap_get(c->compare.children, UINT_TO_PTR(value_u8)); - else if (BUS_MATCH_CAN_HASH(t)) - n = hashmap_get(c->compare.children, value_str); - else { - for (n = c->child; n && !value_node_same(n, t, value_u8, value_str); n = n->next) - ; - } - - if (n) { - *ret = n; - return 1; - } - - return 0; -} - -static int bus_match_add_leaf( - struct bus_match_node *where, - struct match_callback *callback) { - - struct bus_match_node *n; - - assert(where); - assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); - assert(callback); - - n = new0(struct bus_match_node, 1); - if (!n) - return -ENOMEM; - - n->type = BUS_MATCH_LEAF; - n->parent = where; - n->next = where->child; - if (n->next) - n->next->prev = n; - - n->leaf.callback = callback; - callback->match_node = n; - - where->child = n; - - return 1; -} - -static int bus_match_find_leaf( - struct bus_match_node *where, - sd_bus_message_handler_t callback, - void *userdata, - struct bus_match_node **ret) { - - struct bus_match_node *c; - - assert(where); - assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); - assert(ret); - - for (c = where->child; c; c = c->next) { - sd_bus_slot *s; - - s = container_of(c->leaf.callback, sd_bus_slot, match_callback); - - if (c->type == BUS_MATCH_LEAF && - c->leaf.callback->callback == callback && - s->userdata == userdata) { - *ret = c; - return 1; - } - } - - return 0; -} - -enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n) { - assert(k); - - if (n == 4 && startswith(k, "type")) - return BUS_MATCH_MESSAGE_TYPE; - if (n == 6 && startswith(k, "sender")) - return BUS_MATCH_SENDER; - if (n == 11 && startswith(k, "destination")) - return BUS_MATCH_DESTINATION; - if (n == 9 && startswith(k, "interface")) - return BUS_MATCH_INTERFACE; - if (n == 6 && startswith(k, "member")) - return BUS_MATCH_MEMBER; - if (n == 4 && startswith(k, "path")) - return BUS_MATCH_PATH; - if (n == 14 && startswith(k, "path_namespace")) - return BUS_MATCH_PATH_NAMESPACE; - - if (n == 4 && startswith(k, "arg")) { - int j; - - j = undecchar(k[3]); - if (j < 0) - return -EINVAL; - - return BUS_MATCH_ARG + j; - } - - if (n == 5 && startswith(k, "arg")) { - int a, b; - enum bus_match_node_type t; - - a = undecchar(k[3]); - b = undecchar(k[4]); - if (a <= 0 || b < 0) - return -EINVAL; - - t = BUS_MATCH_ARG + a * 10 + b; - if (t > BUS_MATCH_ARG_LAST) - return -EINVAL; - - return t; - } - - if (n == 8 && startswith(k, "arg") && startswith(k + 4, "path")) { - int j; - - j = undecchar(k[3]); - if (j < 0) - return -EINVAL; - - return BUS_MATCH_ARG_PATH + j; - } - - if (n == 9 && startswith(k, "arg") && startswith(k + 5, "path")) { - enum bus_match_node_type t; - int a, b; - - a = undecchar(k[3]); - b = undecchar(k[4]); - if (a <= 0 || b < 0) - return -EINVAL; - - t = BUS_MATCH_ARG_PATH + a * 10 + b; - if (t > BUS_MATCH_ARG_PATH_LAST) - return -EINVAL; - - return t; - } - - if (n == 13 && startswith(k, "arg") && startswith(k + 4, "namespace")) { - int j; - - j = undecchar(k[3]); - if (j < 0) - return -EINVAL; - - return BUS_MATCH_ARG_NAMESPACE + j; - } - - if (n == 14 && startswith(k, "arg") && startswith(k + 5, "namespace")) { - enum bus_match_node_type t; - int a, b; - - a = undecchar(k[3]); - b = undecchar(k[4]); - if (a <= 0 || b < 0) - return -EINVAL; - - t = BUS_MATCH_ARG_NAMESPACE + a * 10 + b; - if (t > BUS_MATCH_ARG_NAMESPACE_LAST) - return -EINVAL; - - return t; - } - - if (n == 7 && startswith(k, "arg") && startswith(k + 4, "has")) { - int j; - - j = undecchar(k[3]); - if (j < 0) - return -EINVAL; - - return BUS_MATCH_ARG_HAS + j; - } - - if (n == 8 && startswith(k, "arg") && startswith(k + 5, "has")) { - enum bus_match_node_type t; - int a, b; - - a = undecchar(k[3]); - b = undecchar(k[4]); - if (a <= 0 || b < 0) - return -EINVAL; - - t = BUS_MATCH_ARG_HAS + a * 10 + b; - if (t > BUS_MATCH_ARG_HAS_LAST) - return -EINVAL; - - return t; - } - - return -EINVAL; -} - -static int match_component_compare(const void *a, const void *b) { - const struct bus_match_component *x = a, *y = b; - - if (x->type < y->type) - return -1; - if (x->type > y->type) - return 1; - - return 0; -} - -void bus_match_parse_free(struct bus_match_component *components, unsigned n_components) { - unsigned i; - - for (i = 0; i < n_components; i++) - free(components[i].value_str); - - free(components); -} - -int bus_match_parse( - const char *match, - struct bus_match_component **_components, - unsigned *_n_components) { - - const char *p = match; - struct bus_match_component *components = NULL; - size_t components_allocated = 0; - unsigned n_components = 0, i; - _cleanup_free_ char *value = NULL; - int r; - - assert(match); - assert(_components); - assert(_n_components); - - while (*p != 0) { - const char *eq, *q; - enum bus_match_node_type t; - unsigned j = 0; - size_t value_allocated = 0; - bool escaped = false, quoted; - uint8_t u; - - /* Avahi's match rules appear to include whitespace, skip over it */ - p += strspn(p, " "); - - eq = strchr(p, '='); - if (!eq) - return -EINVAL; - - t = bus_match_node_type_from_string(p, eq - p); - if (t < 0) - return -EINVAL; - - quoted = eq[1] == '\''; - - for (q = eq + 1 + quoted;; q++) { - - if (*q == 0) { - - if (quoted) { - r = -EINVAL; - goto fail; - } else { - if (value) - value[j] = 0; - break; - } - } - - if (!escaped) { - if (*q == '\\') { - escaped = true; - continue; - } - - if (quoted) { - if (*q == '\'') { - if (value) - value[j] = 0; - break; - } - } else { - if (*q == ',') { - if (value) - value[j] = 0; - - break; - } - } - } - - if (!GREEDY_REALLOC(value, value_allocated, j + 2)) { - r = -ENOMEM; - goto fail; - } - - value[j++] = *q; - escaped = false; - } - - if (!value) { - value = strdup(""); - if (!value) { - r = -ENOMEM; - goto fail; - } - } - - if (t == BUS_MATCH_MESSAGE_TYPE) { - r = bus_message_type_from_string(value, &u); - if (r < 0) - goto fail; - - value = mfree(value); - } else - u = 0; - - if (!GREEDY_REALLOC(components, components_allocated, n_components + 1)) { - r = -ENOMEM; - goto fail; - } - - components[n_components].type = t; - components[n_components].value_str = value; - components[n_components].value_u8 = u; - n_components++; - - value = NULL; - - if (q[quoted] == 0) - break; - - if (q[quoted] != ',') { - r = -EINVAL; - goto fail; - } - - p = q + 1 + quoted; - } - - /* Order the whole thing, so that we always generate the same tree */ - qsort_safe(components, n_components, sizeof(struct bus_match_component), match_component_compare); - - /* Check for duplicates */ - for (i = 0; i+1 < n_components; i++) - if (components[i].type == components[i+1].type) { - r = -EINVAL; - goto fail; - } - - *_components = components; - *_n_components = n_components; - - return 0; - -fail: - bus_match_parse_free(components, n_components); - return r; -} - -char *bus_match_to_string(struct bus_match_component *components, unsigned n_components) { - _cleanup_fclose_ FILE *f = NULL; - char *buffer = NULL; - size_t size = 0; - unsigned i; - int r; - - if (n_components <= 0) - return strdup(""); - - assert(components); - - f = open_memstream(&buffer, &size); - if (!f) - return NULL; - - for (i = 0; i < n_components; i++) { - char buf[32]; - - if (i != 0) - fputc(',', f); - - fputs(bus_match_node_type_to_string(components[i].type, buf, sizeof(buf)), f); - fputc('=', f); - fputc('\'', f); - - if (components[i].type == BUS_MATCH_MESSAGE_TYPE) - fputs(bus_message_type_to_string(components[i].value_u8), f); - else - fputs(components[i].value_str, f); - - fputc('\'', f); - } - - r = fflush_and_check(f); - if (r < 0) - return NULL; - - return buffer; -} - -int bus_match_add( - struct bus_match_node *root, - struct bus_match_component *components, - unsigned n_components, - struct match_callback *callback) { - - unsigned i; - struct bus_match_node *n; - int r; - - assert(root); - assert(callback); - - n = root; - for (i = 0; i < n_components; i++) { - r = bus_match_add_compare_value( - n, components[i].type, - components[i].value_u8, components[i].value_str, &n); - if (r < 0) - return r; - } - - return bus_match_add_leaf(n, callback); -} - -int bus_match_remove( - struct bus_match_node *root, - struct match_callback *callback) { - - struct bus_match_node *node, *pp; - - assert(root); - assert(callback); - - node = callback->match_node; - if (!node) - return 0; - - assert(node->type == BUS_MATCH_LEAF); - - callback->match_node = NULL; - - /* Free the leaf */ - pp = node->parent; - bus_match_node_free(node); - - /* Prune the tree above */ - while (pp) { - node = pp; - pp = node->parent; - - if (!bus_match_node_maybe_free(node)) - break; - } - - return 1; -} - -int bus_match_find( - struct bus_match_node *root, - struct bus_match_component *components, - unsigned n_components, - sd_bus_message_handler_t callback, - void *userdata, - struct match_callback **ret) { - - struct bus_match_node *n, **gc; - unsigned i; - int r; - - assert(root); - assert(ret); - - gc = newa(struct bus_match_node*, n_components); - - n = root; - for (i = 0; i < n_components; i++) { - r = bus_match_find_compare_value( - n, components[i].type, - components[i].value_u8, components[i].value_str, - &n); - if (r <= 0) - return r; - - gc[i] = n; - } - - r = bus_match_find_leaf(n, callback, userdata, &n); - if (r <= 0) - return r; - - *ret = n->leaf.callback; - return 1; -} - -void bus_match_free(struct bus_match_node *node) { - struct bus_match_node *c; - - if (!node) - return; - - if (BUS_MATCH_CAN_HASH(node->type)) { - Iterator i; - - HASHMAP_FOREACH(c, node->compare.children, i) - bus_match_free(c); - - assert(hashmap_isempty(node->compare.children)); - } - - while ((c = node->child)) - bus_match_free(c); - - if (node->type != BUS_MATCH_ROOT) - bus_match_node_free(node); -} - -const char* bus_match_node_type_to_string(enum bus_match_node_type t, char buf[], size_t l) { - switch (t) { - - case BUS_MATCH_ROOT: - return "root"; - - case BUS_MATCH_VALUE: - return "value"; - - case BUS_MATCH_LEAF: - return "leaf"; - - case BUS_MATCH_MESSAGE_TYPE: - return "type"; - - case BUS_MATCH_SENDER: - return "sender"; - - case BUS_MATCH_DESTINATION: - return "destination"; - - case BUS_MATCH_INTERFACE: - return "interface"; - - case BUS_MATCH_MEMBER: - return "member"; - - case BUS_MATCH_PATH: - return "path"; - - case BUS_MATCH_PATH_NAMESPACE: - return "path_namespace"; - - case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: - snprintf(buf, l, "arg%i", t - BUS_MATCH_ARG); - return buf; - - case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: - snprintf(buf, l, "arg%ipath", t - BUS_MATCH_ARG_PATH); - return buf; - - case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: - snprintf(buf, l, "arg%inamespace", t - BUS_MATCH_ARG_NAMESPACE); - return buf; - - case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST: - snprintf(buf, l, "arg%ihas", t - BUS_MATCH_ARG_HAS); - return buf; - - default: - return NULL; - } -} - -void bus_match_dump(struct bus_match_node *node, unsigned level) { - struct bus_match_node *c; - _cleanup_free_ char *pfx = NULL; - char buf[32]; - - if (!node) - return; - - pfx = strrep(" ", level); - printf("%s[%s]", strempty(pfx), bus_match_node_type_to_string(node->type, buf, sizeof(buf))); - - if (node->type == BUS_MATCH_VALUE) { - if (node->parent->type == BUS_MATCH_MESSAGE_TYPE) - printf(" <%u>\n", node->value.u8); - else - printf(" <%s>\n", node->value.str); - } else if (node->type == BUS_MATCH_ROOT) - puts(" root"); - else if (node->type == BUS_MATCH_LEAF) - printf(" %p/%p\n", node->leaf.callback->callback, container_of(node->leaf.callback, sd_bus_slot, match_callback)->userdata); - else - putchar('\n'); - - if (BUS_MATCH_CAN_HASH(node->type)) { - Iterator i; - - HASHMAP_FOREACH(c, node->compare.children, i) - bus_match_dump(c, level + 1); - } - - for (c = node->child; c; c = c->next) - bus_match_dump(c, level + 1); -} - -enum bus_match_scope bus_match_get_scope(const struct bus_match_component *components, unsigned n_components) { - bool found_driver = false; - unsigned i; - - if (n_components <= 0) - return BUS_MATCH_GENERIC; - - assert(components); - - /* Checks whether the specified match can only match the - * pseudo-service for local messages, which we detect by - * sender, interface or path. If a match is not restricted to - * local messages, then we check if it only matches on the - * driver. */ - - for (i = 0; i < n_components; i++) { - const struct bus_match_component *c = components + i; - - if (c->type == BUS_MATCH_SENDER) { - if (streq_ptr(c->value_str, "org.freedesktop.DBus.Local")) - return BUS_MATCH_LOCAL; - - if (streq_ptr(c->value_str, "org.freedesktop.DBus")) - found_driver = true; - } - - if (c->type == BUS_MATCH_INTERFACE && streq_ptr(c->value_str, "org.freedesktop.DBus.Local")) - return BUS_MATCH_LOCAL; - - if (c->type == BUS_MATCH_PATH && streq_ptr(c->value_str, "/org/freedesktop/DBus/Local")) - return BUS_MATCH_LOCAL; - } - - return found_driver ? BUS_MATCH_DRIVER : BUS_MATCH_GENERIC; - -} diff --git a/src/libsystemd/sd-bus/bus-match.h b/src/libsystemd/sd-bus/bus-match.h deleted file mode 100644 index 8cbbb63b11..0000000000 --- a/src/libsystemd/sd-bus/bus-match.h +++ /dev/null @@ -1,100 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -#include "hashmap.h" - -enum bus_match_node_type { - BUS_MATCH_ROOT, - BUS_MATCH_VALUE, - BUS_MATCH_LEAF, - - /* The following are all different kinds of compare nodes */ - BUS_MATCH_SENDER, - BUS_MATCH_MESSAGE_TYPE, - BUS_MATCH_DESTINATION, - BUS_MATCH_INTERFACE, - BUS_MATCH_MEMBER, - BUS_MATCH_PATH, - BUS_MATCH_PATH_NAMESPACE, - BUS_MATCH_ARG, - BUS_MATCH_ARG_LAST = BUS_MATCH_ARG + 63, - BUS_MATCH_ARG_PATH, - BUS_MATCH_ARG_PATH_LAST = BUS_MATCH_ARG_PATH + 63, - BUS_MATCH_ARG_NAMESPACE, - BUS_MATCH_ARG_NAMESPACE_LAST = BUS_MATCH_ARG_NAMESPACE + 63, - BUS_MATCH_ARG_HAS, - BUS_MATCH_ARG_HAS_LAST = BUS_MATCH_ARG_HAS + 63, - _BUS_MATCH_NODE_TYPE_MAX, - _BUS_MATCH_NODE_TYPE_INVALID = -1 -}; - -struct bus_match_node { - enum bus_match_node_type type; - struct bus_match_node *parent, *next, *prev, *child; - - union { - struct { - char *str; - uint8_t u8; - } value; - struct { - struct match_callback *callback; - } leaf; - struct { - /* If this is set, then the child is NULL */ - Hashmap *children; - } compare; - }; -}; - -struct bus_match_component { - enum bus_match_node_type type; - uint8_t value_u8; - char *value_str; -}; - -enum bus_match_scope { - BUS_MATCH_GENERIC, - BUS_MATCH_LOCAL, - BUS_MATCH_DRIVER, -}; - -int bus_match_run(sd_bus *bus, struct bus_match_node *root, sd_bus_message *m); - -int bus_match_add(struct bus_match_node *root, struct bus_match_component *components, unsigned n_components, struct match_callback *callback); -int bus_match_remove(struct bus_match_node *root, struct match_callback *callback); - -int bus_match_find(struct bus_match_node *root, struct bus_match_component *components, unsigned n_components, sd_bus_message_handler_t callback, void *userdata, struct match_callback **ret); - -void bus_match_free(struct bus_match_node *node); - -void bus_match_dump(struct bus_match_node *node, unsigned level); - -const char* bus_match_node_type_to_string(enum bus_match_node_type t, char buf[], size_t l); -enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n); - -int bus_match_parse(const char *match, struct bus_match_component **_components, unsigned *_n_components); -void bus_match_parse_free(struct bus_match_component *components, unsigned n_components); -char *bus_match_to_string(struct bus_match_component *components, unsigned n_components); - -enum bus_match_scope bus_match_get_scope(const struct bus_match_component *components, unsigned n_components); diff --git a/src/libsystemd/sd-bus/bus-message.c b/src/libsystemd/sd-bus/bus-message.c deleted file mode 100644 index b8958ec7bb..0000000000 --- a/src/libsystemd/sd-bus/bus-message.c +++ /dev/null @@ -1,5939 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-gvariant.h" -#include "bus-internal.h" -#include "bus-message.h" -#include "bus-signature.h" -#include "bus-type.h" -#include "bus-util.h" -#include "fd-util.h" -#include "io-util.h" -#include "memfd-util.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" -#include "utf8.h" -#include "util.h" - -static int message_append_basic(sd_bus_message *m, char type, const void *p, const void **stored); - -static void *adjust_pointer(const void *p, void *old_base, size_t sz, void *new_base) { - - if (p == NULL) - return NULL; - - if (old_base == new_base) - return (void*) p; - - if ((uint8_t*) p < (uint8_t*) old_base) - return (void*) p; - - if ((uint8_t*) p >= (uint8_t*) old_base + sz) - return (void*) p; - - return (uint8_t*) new_base + ((uint8_t*) p - (uint8_t*) old_base); -} - -static void message_free_part(sd_bus_message *m, struct bus_body_part *part) { - assert(m); - assert(part); - - if (part->memfd >= 0) { - /* If we can reuse the memfd, try that. For that it - * can't be sealed yet. */ - - if (!part->sealed) { - assert(part->memfd_offset == 0); - assert(part->data == part->mmap_begin); - bus_kernel_push_memfd(m->bus, part->memfd, part->data, part->mapped, part->allocated); - } else { - if (part->mapped > 0) - assert_se(munmap(part->mmap_begin, part->mapped) == 0); - - safe_close(part->memfd); - } - - } else if (part->munmap_this) - munmap(part->mmap_begin, part->mapped); - else if (part->free_this) - free(part->data); - - if (part != &m->body) - free(part); -} - -static void message_reset_parts(sd_bus_message *m) { - struct bus_body_part *part; - - assert(m); - - part = &m->body; - while (m->n_body_parts > 0) { - struct bus_body_part *next = part->next; - message_free_part(m, part); - part = next; - m->n_body_parts--; - } - - m->body_end = NULL; - - m->cached_rindex_part = NULL; - m->cached_rindex_part_begin = 0; -} - -static void message_reset_containers(sd_bus_message *m) { - unsigned i; - - assert(m); - - for (i = 0; i < m->n_containers; i++) { - free(m->containers[i].signature); - free(m->containers[i].offsets); - } - - m->containers = mfree(m->containers); - - m->n_containers = m->containers_allocated = 0; - m->root_container.index = 0; -} - -static void message_free(sd_bus_message *m) { - assert(m); - - if (m->free_header) - free(m->header); - - message_reset_parts(m); - - if (m->release_kdbus) - bus_kernel_cmd_free(m->bus, (uint8_t *) m->kdbus - (uint8_t *) m->bus->kdbus_buffer); - - if (m->free_kdbus) - free(m->kdbus); - - sd_bus_unref(m->bus); - - if (m->free_fds) { - close_many(m->fds, m->n_fds); - free(m->fds); - } - - if (m->iovec != m->iovec_fixed) - free(m->iovec); - - m->destination_ptr = mfree(m->destination_ptr); - message_reset_containers(m); - free(m->root_container.signature); - free(m->root_container.offsets); - - free(m->root_container.peeked_signature); - - bus_creds_done(&m->creds); - free(m); -} - -static void *message_extend_fields(sd_bus_message *m, size_t align, size_t sz, bool add_offset) { - void *op, *np; - size_t old_size, new_size, start; - - assert(m); - - if (m->poisoned) - return NULL; - - old_size = sizeof(struct bus_header) + m->fields_size; - start = ALIGN_TO(old_size, align); - new_size = start + sz; - - if (new_size < start || - new_size > (size_t) ((uint32_t) -1)) - goto poison; - - if (old_size == new_size) - return (uint8_t*) m->header + old_size; - - if (m->free_header) { - np = realloc(m->header, ALIGN8(new_size)); - if (!np) - goto poison; - } else { - /* Initially, the header is allocated as part of of - * the sd_bus_message itself, let's replace it by - * dynamic data */ - - np = malloc(ALIGN8(new_size)); - if (!np) - goto poison; - - memcpy(np, m->header, sizeof(struct bus_header)); - } - - /* Zero out padding */ - if (start > old_size) - memzero((uint8_t*) np + old_size, start - old_size); - - op = m->header; - m->header = np; - m->fields_size = new_size - sizeof(struct bus_header); - - /* Adjust quick access pointers */ - m->path = adjust_pointer(m->path, op, old_size, m->header); - m->interface = adjust_pointer(m->interface, op, old_size, m->header); - m->member = adjust_pointer(m->member, op, old_size, m->header); - m->destination = adjust_pointer(m->destination, op, old_size, m->header); - m->sender = adjust_pointer(m->sender, op, old_size, m->header); - m->error.name = adjust_pointer(m->error.name, op, old_size, m->header); - - m->free_header = true; - - if (add_offset) { - if (m->n_header_offsets >= ELEMENTSOF(m->header_offsets)) - goto poison; - - m->header_offsets[m->n_header_offsets++] = new_size - sizeof(struct bus_header); - } - - return (uint8_t*) np + start; - -poison: - m->poisoned = true; - return NULL; -} - -static int message_append_field_string( - sd_bus_message *m, - uint64_t h, - char type, - const char *s, - const char **ret) { - - size_t l; - uint8_t *p; - - assert(m); - - /* dbus1 only allows 8bit header field ids */ - if (h > 0xFF) - return -EINVAL; - - /* dbus1 doesn't allow strings over 32bit, let's enforce this - * globally, to not risk convertability */ - l = strlen(s); - if (l > (size_t) (uint32_t) -1) - return -EINVAL; - - /* Signature "(yv)" where the variant contains "s" */ - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - - /* (field id 64bit, ((string + NUL) + NUL + signature string 's') */ - p = message_extend_fields(m, 8, 8 + l + 1 + 1 + 1, true); - if (!p) - return -ENOMEM; - - *((uint64_t*) p) = h; - memcpy(p+8, s, l); - p[8+l] = 0; - p[8+l+1] = 0; - p[8+l+2] = type; - - if (ret) - *ret = (char*) p + 8; - - } else { - /* (field id byte + (signature length + signature 's' + NUL) + (string length + string + NUL)) */ - p = message_extend_fields(m, 8, 4 + 4 + l + 1, false); - if (!p) - return -ENOMEM; - - p[0] = (uint8_t) h; - p[1] = 1; - p[2] = type; - p[3] = 0; - - ((uint32_t*) p)[1] = l; - memcpy(p + 8, s, l + 1); - - if (ret) - *ret = (char*) p + 8; - } - - return 0; -} - -static int message_append_field_signature( - sd_bus_message *m, - uint64_t h, - const char *s, - const char **ret) { - - size_t l; - uint8_t *p; - - assert(m); - - /* dbus1 only allows 8bit header field ids */ - if (h > 0xFF) - return -EINVAL; - - /* dbus1 doesn't allow signatures over 8bit, let's enforce - * this globally, to not risk convertability */ - l = strlen(s); - if (l > 255) - return -EINVAL; - - /* Signature "(yv)" where the variant contains "g" */ - - if (BUS_MESSAGE_IS_GVARIANT(m)) - /* For gvariant the serialization is the same as for normal strings */ - return message_append_field_string(m, h, 'g', s, ret); - else { - /* (field id byte + (signature length + signature 'g' + NUL) + (string length + string + NUL)) */ - p = message_extend_fields(m, 8, 4 + 1 + l + 1, false); - if (!p) - return -ENOMEM; - - p[0] = (uint8_t) h; - p[1] = 1; - p[2] = SD_BUS_TYPE_SIGNATURE; - p[3] = 0; - p[4] = l; - memcpy(p + 5, s, l + 1); - - if (ret) - *ret = (const char*) p + 5; - } - - return 0; -} - -static int message_append_field_uint32(sd_bus_message *m, uint64_t h, uint32_t x) { - uint8_t *p; - - assert(m); - - /* dbus1 only allows 8bit header field ids */ - if (h > 0xFF) - return -EINVAL; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - /* (field id 64bit + ((value + NUL + signature string 'u') */ - - p = message_extend_fields(m, 8, 8 + 4 + 1 + 1, true); - if (!p) - return -ENOMEM; - - *((uint64_t*) p) = h; - *((uint32_t*) (p + 8)) = x; - p[12] = 0; - p[13] = 'u'; - } else { - /* (field id byte + (signature length + signature 'u' + NUL) + value) */ - p = message_extend_fields(m, 8, 4 + 4, false); - if (!p) - return -ENOMEM; - - p[0] = (uint8_t) h; - p[1] = 1; - p[2] = 'u'; - p[3] = 0; - - ((uint32_t*) p)[1] = x; - } - - return 0; -} - -static int message_append_field_uint64(sd_bus_message *m, uint64_t h, uint64_t x) { - uint8_t *p; - - assert(m); - - /* dbus1 only allows 8bit header field ids */ - if (h > 0xFF) - return -EINVAL; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - /* (field id 64bit + ((value + NUL + signature string 't') */ - - p = message_extend_fields(m, 8, 8 + 8 + 1 + 1, true); - if (!p) - return -ENOMEM; - - *((uint64_t*) p) = h; - *((uint64_t*) (p + 8)) = x; - p[16] = 0; - p[17] = 't'; - } else { - /* (field id byte + (signature length + signature 't' + NUL) + 4 byte padding + value) */ - p = message_extend_fields(m, 8, 4 + 4 + 8, false); - if (!p) - return -ENOMEM; - - p[0] = (uint8_t) h; - p[1] = 1; - p[2] = 't'; - p[3] = 0; - p[4] = 0; - p[5] = 0; - p[6] = 0; - p[7] = 0; - - ((uint64_t*) p)[1] = x; - } - - return 0; -} - -static int message_append_reply_cookie(sd_bus_message *m, uint64_t cookie) { - assert(m); - - if (BUS_MESSAGE_IS_GVARIANT(m)) - return message_append_field_uint64(m, BUS_MESSAGE_HEADER_REPLY_SERIAL, cookie); - else { - /* 64bit cookies are not supported on dbus1 */ - if (cookie > 0xffffffffUL) - return -EOPNOTSUPP; - - return message_append_field_uint32(m, BUS_MESSAGE_HEADER_REPLY_SERIAL, (uint32_t) cookie); - } -} - -int bus_message_from_header( - sd_bus *bus, - void *header, - size_t header_accessible, - void *footer, - size_t footer_accessible, - size_t message_size, - int *fds, - unsigned n_fds, - const char *label, - size_t extra, - sd_bus_message **ret) { - - _cleanup_free_ sd_bus_message *m = NULL; - struct bus_header *h; - size_t a, label_sz; - - assert(bus); - assert(header || header_accessible <= 0); - assert(footer || footer_accessible <= 0); - assert(fds || n_fds <= 0); - assert(ret); - - if (header_accessible < sizeof(struct bus_header)) - return -EBADMSG; - - if (header_accessible > message_size) - return -EBADMSG; - if (footer_accessible > message_size) - return -EBADMSG; - - h = header; - if (!IN_SET(h->version, 1, 2)) - return -EBADMSG; - - if (h->type == _SD_BUS_MESSAGE_TYPE_INVALID) - return -EBADMSG; - - if (!IN_SET(h->endian, BUS_LITTLE_ENDIAN, BUS_BIG_ENDIAN)) - return -EBADMSG; - - /* Note that we are happy with unknown flags in the flags header! */ - - a = ALIGN(sizeof(sd_bus_message)) + ALIGN(extra); - - if (label) { - label_sz = strlen(label); - a += label_sz + 1; - } - - m = malloc0(a); - if (!m) - return -ENOMEM; - - m->n_ref = 1; - m->sealed = true; - m->header = header; - m->header_accessible = header_accessible; - m->footer = footer; - m->footer_accessible = footer_accessible; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - size_t ws; - - if (h->dbus2.cookie == 0) - return -EBADMSG; - - /* dbus2 derives the sizes from the message size and - the offset table at the end, since it is formatted as - gvariant "yyyyuta{tv}v". Since the message itself is a - structure with precisely to variable sized entries, - there's only one offset in the table, which marks the - end of the fields array. */ - - ws = bus_gvariant_determine_word_size(message_size, 0); - if (footer_accessible < ws) - return -EBADMSG; - - m->fields_size = bus_gvariant_read_word_le((uint8_t*) footer + footer_accessible - ws, ws); - if (ALIGN8(m->fields_size) > message_size - ws) - return -EBADMSG; - if (m->fields_size < sizeof(struct bus_header)) - return -EBADMSG; - - m->fields_size -= sizeof(struct bus_header); - m->body_size = message_size - (sizeof(struct bus_header) + ALIGN8(m->fields_size)); - } else { - if (h->dbus1.serial == 0) - return -EBADMSG; - - /* dbus1 has the sizes in the header */ - m->fields_size = BUS_MESSAGE_BSWAP32(m, h->dbus1.fields_size); - m->body_size = BUS_MESSAGE_BSWAP32(m, h->dbus1.body_size); - - if (sizeof(struct bus_header) + ALIGN8(m->fields_size) + m->body_size != message_size) - return -EBADMSG; - } - - m->fds = fds; - m->n_fds = n_fds; - - if (label) { - m->creds.label = (char*) m + ALIGN(sizeof(sd_bus_message)) + ALIGN(extra); - memcpy(m->creds.label, label, label_sz + 1); - - m->creds.mask |= SD_BUS_CREDS_SELINUX_CONTEXT; - } - - m->bus = sd_bus_ref(bus); - *ret = m; - m = NULL; - - return 0; -} - -int bus_message_from_malloc( - sd_bus *bus, - void *buffer, - size_t length, - int *fds, - unsigned n_fds, - const char *label, - sd_bus_message **ret) { - - sd_bus_message *m; - size_t sz; - int r; - - r = bus_message_from_header( - bus, - buffer, length, /* in this case the initial bytes and the final bytes are the same */ - buffer, length, - length, - fds, n_fds, - label, - 0, &m); - if (r < 0) - return r; - - sz = length - sizeof(struct bus_header) - ALIGN8(m->fields_size); - if (sz > 0) { - m->n_body_parts = 1; - m->body.data = (uint8_t*) buffer + sizeof(struct bus_header) + ALIGN8(m->fields_size); - m->body.size = sz; - m->body.sealed = true; - m->body.memfd = -1; - } - - m->n_iovec = 1; - m->iovec = m->iovec_fixed; - m->iovec[0].iov_base = buffer; - m->iovec[0].iov_len = length; - - r = bus_message_parse_fields(m); - if (r < 0) - goto fail; - - /* We take possession of the memory and fds now */ - m->free_header = true; - m->free_fds = true; - - *ret = m; - return 0; - -fail: - message_free(m); - return r; -} - -static sd_bus_message *message_new(sd_bus *bus, uint8_t type) { - sd_bus_message *m; - - assert(bus); - - m = malloc0(ALIGN(sizeof(sd_bus_message)) + sizeof(struct bus_header)); - if (!m) - return NULL; - - m->n_ref = 1; - m->header = (struct bus_header*) ((uint8_t*) m + ALIGN(sizeof(struct sd_bus_message))); - m->header->endian = BUS_NATIVE_ENDIAN; - m->header->type = type; - m->header->version = bus->message_version; - m->allow_fds = bus->can_fds || (bus->state != BUS_HELLO && bus->state != BUS_RUNNING); - m->root_container.need_offsets = BUS_MESSAGE_IS_GVARIANT(m); - m->bus = sd_bus_ref(bus); - - if (bus->allow_interactive_authorization) - m->header->flags |= BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION; - - return m; -} - -_public_ int sd_bus_message_new_signal( - sd_bus *bus, - sd_bus_message **m, - const char *path, - const char *interface, - const char *member) { - - sd_bus_message *t; - int r; - - assert_return(bus, -ENOTCONN); - assert_return(bus->state != BUS_UNSET, -ENOTCONN); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(interface_name_is_valid(interface), -EINVAL); - assert_return(member_name_is_valid(member), -EINVAL); - assert_return(m, -EINVAL); - - t = message_new(bus, SD_BUS_MESSAGE_SIGNAL); - if (!t) - return -ENOMEM; - - t->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED; - - r = message_append_field_string(t, BUS_MESSAGE_HEADER_PATH, SD_BUS_TYPE_OBJECT_PATH, path, &t->path); - if (r < 0) - goto fail; - r = message_append_field_string(t, BUS_MESSAGE_HEADER_INTERFACE, SD_BUS_TYPE_STRING, interface, &t->interface); - if (r < 0) - goto fail; - r = message_append_field_string(t, BUS_MESSAGE_HEADER_MEMBER, SD_BUS_TYPE_STRING, member, &t->member); - if (r < 0) - goto fail; - - *m = t; - return 0; - -fail: - sd_bus_message_unref(t); - return r; -} - -_public_ int sd_bus_message_new_method_call( - sd_bus *bus, - sd_bus_message **m, - const char *destination, - const char *path, - const char *interface, - const char *member) { - - sd_bus_message *t; - int r; - - assert_return(bus, -ENOTCONN); - assert_return(bus->state != BUS_UNSET, -ENOTCONN); - assert_return(!destination || service_name_is_valid(destination), -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(!interface || interface_name_is_valid(interface), -EINVAL); - assert_return(member_name_is_valid(member), -EINVAL); - assert_return(m, -EINVAL); - - t = message_new(bus, SD_BUS_MESSAGE_METHOD_CALL); - if (!t) - return -ENOMEM; - - r = message_append_field_string(t, BUS_MESSAGE_HEADER_PATH, SD_BUS_TYPE_OBJECT_PATH, path, &t->path); - if (r < 0) - goto fail; - r = message_append_field_string(t, BUS_MESSAGE_HEADER_MEMBER, SD_BUS_TYPE_STRING, member, &t->member); - if (r < 0) - goto fail; - - if (interface) { - r = message_append_field_string(t, BUS_MESSAGE_HEADER_INTERFACE, SD_BUS_TYPE_STRING, interface, &t->interface); - if (r < 0) - goto fail; - } - - if (destination) { - r = message_append_field_string(t, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, destination, &t->destination); - if (r < 0) - goto fail; - } - - *m = t; - return 0; - -fail: - message_free(t); - return r; -} - -static int message_new_reply( - sd_bus_message *call, - uint8_t type, - sd_bus_message **m) { - - sd_bus_message *t; - int r; - - assert_return(call, -EINVAL); - assert_return(call->sealed, -EPERM); - assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); - assert_return(call->bus->state != BUS_UNSET, -ENOTCONN); - assert_return(m, -EINVAL); - - t = message_new(call->bus, type); - if (!t) - return -ENOMEM; - - t->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED; - t->reply_cookie = BUS_MESSAGE_COOKIE(call); - if (t->reply_cookie == 0) - return -EOPNOTSUPP; - - r = message_append_reply_cookie(t, t->reply_cookie); - if (r < 0) - goto fail; - - if (call->sender) { - r = message_append_field_string(t, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, call->sender, &t->destination); - if (r < 0) - goto fail; - } - - t->dont_send = !!(call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED); - t->enforced_reply_signature = call->enforced_reply_signature; - - *m = t; - return 0; - -fail: - message_free(t); - return r; -} - -_public_ int sd_bus_message_new_method_return( - sd_bus_message *call, - sd_bus_message **m) { - - return message_new_reply(call, SD_BUS_MESSAGE_METHOD_RETURN, m); -} - -_public_ int sd_bus_message_new_method_error( - sd_bus_message *call, - sd_bus_message **m, - const sd_bus_error *e) { - - sd_bus_message *t; - int r; - - assert_return(sd_bus_error_is_set(e), -EINVAL); - assert_return(m, -EINVAL); - - r = message_new_reply(call, SD_BUS_MESSAGE_METHOD_ERROR, &t); - if (r < 0) - return r; - - r = message_append_field_string(t, BUS_MESSAGE_HEADER_ERROR_NAME, SD_BUS_TYPE_STRING, e->name, &t->error.name); - if (r < 0) - goto fail; - - if (e->message) { - r = message_append_basic(t, SD_BUS_TYPE_STRING, e->message, (const void**) &t->error.message); - if (r < 0) - goto fail; - } - - t->error._need_free = -1; - - *m = t; - return 0; - -fail: - message_free(t); - return r; -} - -_public_ int sd_bus_message_new_method_errorf( - sd_bus_message *call, - sd_bus_message **m, - const char *name, - const char *format, - ...) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - va_list ap; - - assert_return(name, -EINVAL); - assert_return(m, -EINVAL); - - va_start(ap, format); - bus_error_setfv(&error, name, format, ap); - va_end(ap); - - return sd_bus_message_new_method_error(call, m, &error); -} - -_public_ int sd_bus_message_new_method_errno( - sd_bus_message *call, - sd_bus_message **m, - int error, - const sd_bus_error *p) { - - _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL; - - if (sd_bus_error_is_set(p)) - return sd_bus_message_new_method_error(call, m, p); - - sd_bus_error_set_errno(&berror, error); - - return sd_bus_message_new_method_error(call, m, &berror); -} - -_public_ int sd_bus_message_new_method_errnof( - sd_bus_message *call, - sd_bus_message **m, - int error, - const char *format, - ...) { - - _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL; - va_list ap; - - va_start(ap, format); - sd_bus_error_set_errnofv(&berror, error, format, ap); - va_end(ap); - - return sd_bus_message_new_method_error(call, m, &berror); -} - -void bus_message_set_sender_local(sd_bus *bus, sd_bus_message *m) { - assert(bus); - assert(m); - - m->sender = m->creds.unique_name = (char*) "org.freedesktop.DBus.Local"; - m->creds.well_known_names_local = true; - m->creds.mask |= (SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_WELL_KNOWN_NAMES) & bus->creds_mask; -} - -void bus_message_set_sender_driver(sd_bus *bus, sd_bus_message *m) { - assert(bus); - assert(m); - - m->sender = m->creds.unique_name = (char*) "org.freedesktop.DBus"; - m->creds.well_known_names_driver = true; - m->creds.mask |= (SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_WELL_KNOWN_NAMES) & bus->creds_mask; -} - -int bus_message_new_synthetic_error( - sd_bus *bus, - uint64_t cookie, - const sd_bus_error *e, - sd_bus_message **m) { - - sd_bus_message *t; - int r; - - assert(bus); - assert(sd_bus_error_is_set(e)); - assert(m); - - t = message_new(bus, SD_BUS_MESSAGE_METHOD_ERROR); - if (!t) - return -ENOMEM; - - t->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED; - t->reply_cookie = cookie; - - r = message_append_reply_cookie(t, t->reply_cookie); - if (r < 0) - goto fail; - - if (bus && bus->unique_name) { - r = message_append_field_string(t, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, bus->unique_name, &t->destination); - if (r < 0) - goto fail; - } - - r = message_append_field_string(t, BUS_MESSAGE_HEADER_ERROR_NAME, SD_BUS_TYPE_STRING, e->name, &t->error.name); - if (r < 0) - goto fail; - - if (e->message) { - r = message_append_basic(t, SD_BUS_TYPE_STRING, e->message, (const void**) &t->error.message); - if (r < 0) - goto fail; - } - - t->error._need_free = -1; - - bus_message_set_sender_driver(bus, t); - - *m = t; - return 0; - -fail: - message_free(t); - return r; -} - -_public_ sd_bus_message* sd_bus_message_ref(sd_bus_message *m) { - - if (!m) - return NULL; - - assert(m->n_ref > 0); - m->n_ref++; - - return m; -} - -_public_ sd_bus_message* sd_bus_message_unref(sd_bus_message *m) { - - if (!m) - return NULL; - - assert(m->n_ref > 0); - m->n_ref--; - - if (m->n_ref > 0) - return NULL; - - message_free(m); - return NULL; -} - -_public_ int sd_bus_message_get_type(sd_bus_message *m, uint8_t *type) { - assert_return(m, -EINVAL); - assert_return(type, -EINVAL); - - *type = m->header->type; - return 0; -} - -_public_ int sd_bus_message_get_cookie(sd_bus_message *m, uint64_t *cookie) { - uint64_t c; - - assert_return(m, -EINVAL); - assert_return(cookie, -EINVAL); - - c = BUS_MESSAGE_COOKIE(m); - if (c == 0) - return -ENODATA; - - *cookie = BUS_MESSAGE_COOKIE(m); - return 0; -} - -_public_ int sd_bus_message_get_reply_cookie(sd_bus_message *m, uint64_t *cookie) { - assert_return(m, -EINVAL); - assert_return(cookie, -EINVAL); - - if (m->reply_cookie == 0) - return -ENODATA; - - *cookie = m->reply_cookie; - return 0; -} - -_public_ int sd_bus_message_get_expect_reply(sd_bus_message *m) { - assert_return(m, -EINVAL); - - return m->header->type == SD_BUS_MESSAGE_METHOD_CALL && - !(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED); -} - -_public_ int sd_bus_message_get_auto_start(sd_bus_message *m) { - assert_return(m, -EINVAL); - - return !(m->header->flags & BUS_MESSAGE_NO_AUTO_START); -} - -_public_ int sd_bus_message_get_allow_interactive_authorization(sd_bus_message *m) { - assert_return(m, -EINVAL); - - return m->header->type == SD_BUS_MESSAGE_METHOD_CALL && - (m->header->flags & BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION); -} - -_public_ const char *sd_bus_message_get_path(sd_bus_message *m) { - assert_return(m, NULL); - - return m->path; -} - -_public_ const char *sd_bus_message_get_interface(sd_bus_message *m) { - assert_return(m, NULL); - - return m->interface; -} - -_public_ const char *sd_bus_message_get_member(sd_bus_message *m) { - assert_return(m, NULL); - - return m->member; -} - -_public_ const char *sd_bus_message_get_destination(sd_bus_message *m) { - assert_return(m, NULL); - - return m->destination; -} - -_public_ const char *sd_bus_message_get_sender(sd_bus_message *m) { - assert_return(m, NULL); - - return m->sender; -} - -_public_ const sd_bus_error *sd_bus_message_get_error(sd_bus_message *m) { - assert_return(m, NULL); - - if (!sd_bus_error_is_set(&m->error)) - return NULL; - - return &m->error; -} - -_public_ int sd_bus_message_get_monotonic_usec(sd_bus_message *m, uint64_t *usec) { - assert_return(m, -EINVAL); - assert_return(usec, -EINVAL); - - if (m->monotonic <= 0) - return -ENODATA; - - *usec = m->monotonic; - return 0; -} - -_public_ int sd_bus_message_get_realtime_usec(sd_bus_message *m, uint64_t *usec) { - assert_return(m, -EINVAL); - assert_return(usec, -EINVAL); - - if (m->realtime <= 0) - return -ENODATA; - - *usec = m->realtime; - return 0; -} - -_public_ int sd_bus_message_get_seqnum(sd_bus_message *m, uint64_t *seqnum) { - assert_return(m, -EINVAL); - assert_return(seqnum, -EINVAL); - - if (m->seqnum <= 0) - return -ENODATA; - - *seqnum = m->seqnum; - return 0; -} - -_public_ sd_bus_creds *sd_bus_message_get_creds(sd_bus_message *m) { - assert_return(m, NULL); - - if (m->creds.mask == 0) - return NULL; - - return &m->creds; -} - -_public_ int sd_bus_message_is_signal( - sd_bus_message *m, - const char *interface, - const char *member) { - - assert_return(m, -EINVAL); - - if (m->header->type != SD_BUS_MESSAGE_SIGNAL) - return 0; - - if (interface && (!m->interface || !streq(m->interface, interface))) - return 0; - - if (member && (!m->member || !streq(m->member, member))) - return 0; - - return 1; -} - -_public_ int sd_bus_message_is_method_call( - sd_bus_message *m, - const char *interface, - const char *member) { - - assert_return(m, -EINVAL); - - if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL) - return 0; - - if (interface && (!m->interface || !streq(m->interface, interface))) - return 0; - - if (member && (!m->member || !streq(m->member, member))) - return 0; - - return 1; -} - -_public_ int sd_bus_message_is_method_error(sd_bus_message *m, const char *name) { - assert_return(m, -EINVAL); - - if (m->header->type != SD_BUS_MESSAGE_METHOD_ERROR) - return 0; - - if (name && (!m->error.name || !streq(m->error.name, name))) - return 0; - - return 1; -} - -_public_ int sd_bus_message_set_expect_reply(sd_bus_message *m, int b) { - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(m->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EPERM); - - SET_FLAG(m->header->flags, BUS_MESSAGE_NO_REPLY_EXPECTED, !b); - - return 0; -} - -_public_ int sd_bus_message_set_auto_start(sd_bus_message *m, int b) { - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - - SET_FLAG(m->header->flags, BUS_MESSAGE_NO_AUTO_START, !b); - - return 0; -} - -_public_ int sd_bus_message_set_allow_interactive_authorization(sd_bus_message *m, int b) { - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - - SET_FLAG(m->header->flags, BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION, b); - - return 0; -} - -static struct bus_container *message_get_container(sd_bus_message *m) { - assert(m); - - if (m->n_containers == 0) - return &m->root_container; - - assert(m->containers); - return m->containers + m->n_containers - 1; -} - -struct bus_body_part *message_append_part(sd_bus_message *m) { - struct bus_body_part *part; - - assert(m); - - if (m->poisoned) - return NULL; - - if (m->n_body_parts <= 0) { - part = &m->body; - zero(*part); - } else { - assert(m->body_end); - - part = new0(struct bus_body_part, 1); - if (!part) { - m->poisoned = true; - return NULL; - } - - m->body_end->next = part; - } - - part->memfd = -1; - m->body_end = part; - m->n_body_parts++; - - return part; -} - -static void part_zero(struct bus_body_part *part, size_t sz) { - assert(part); - assert(sz > 0); - assert(sz < 8); - - /* All other fields can be left in their defaults */ - assert(!part->data); - assert(part->memfd < 0); - - part->size = sz; - part->is_zero = true; - part->sealed = true; -} - -static int part_make_space( - struct sd_bus_message *m, - struct bus_body_part *part, - size_t sz, - void **q) { - - void *n; - int r; - - assert(m); - assert(part); - assert(!part->sealed); - - if (m->poisoned) - return -ENOMEM; - - if (!part->data && part->memfd < 0) { - part->memfd = bus_kernel_pop_memfd(m->bus, &part->data, &part->mapped, &part->allocated); - part->mmap_begin = part->data; - } - - if (part->memfd >= 0) { - - if (part->allocated == 0 || sz > part->allocated) { - uint64_t new_allocated; - - new_allocated = PAGE_ALIGN(sz > 0 ? 2 * sz : 1); - r = memfd_set_size(part->memfd, new_allocated); - if (r < 0) { - m->poisoned = true; - return r; - } - - part->allocated = new_allocated; - } - - if (!part->data || sz > part->mapped) { - size_t psz; - - psz = PAGE_ALIGN(sz > 0 ? sz : 1); - if (part->mapped <= 0) - n = mmap(NULL, psz, PROT_READ|PROT_WRITE, MAP_SHARED, part->memfd, 0); - else - n = mremap(part->mmap_begin, part->mapped, psz, MREMAP_MAYMOVE); - - if (n == MAP_FAILED) { - m->poisoned = true; - return -errno; - } - - part->mmap_begin = part->data = n; - part->mapped = psz; - part->memfd_offset = 0; - } - - part->munmap_this = true; - } else { - if (part->allocated == 0 || sz > part->allocated) { - size_t new_allocated; - - new_allocated = sz > 0 ? 2 * sz : 64; - n = realloc(part->data, new_allocated); - if (!n) { - m->poisoned = true; - return -ENOMEM; - } - - part->data = n; - part->allocated = new_allocated; - part->free_this = true; - } - } - - if (q) - *q = part->data ? (uint8_t*) part->data + part->size : NULL; - - part->size = sz; - return 0; -} - -static int message_add_offset(sd_bus_message *m, size_t offset) { - struct bus_container *c; - - assert(m); - assert(BUS_MESSAGE_IS_GVARIANT(m)); - - /* Add offset to current container, unless this is the first - * item in it, which will have the 0 offset, which we can - * ignore. */ - c = message_get_container(m); - - if (!c->need_offsets) - return 0; - - if (!GREEDY_REALLOC(c->offsets, c->offsets_allocated, c->n_offsets + 1)) - return -ENOMEM; - - c->offsets[c->n_offsets++] = offset; - return 0; -} - -static void message_extend_containers(sd_bus_message *m, size_t expand) { - struct bus_container *c; - - assert(m); - - if (expand <= 0) - return; - - /* Update counters */ - for (c = m->containers; c < m->containers + m->n_containers; c++) { - - if (c->array_size) - *c->array_size += expand; - } -} - -static void *message_extend_body( - sd_bus_message *m, - size_t align, - size_t sz, - bool add_offset, - bool force_inline) { - - size_t start_body, end_body, padding, added; - void *p; - int r; - - assert(m); - assert(align > 0); - assert(!m->sealed); - - if (m->poisoned) - return NULL; - - start_body = ALIGN_TO((size_t) m->body_size, align); - end_body = start_body + sz; - - padding = start_body - m->body_size; - added = padding + sz; - - /* Check for 32bit overflows */ - if (end_body > (size_t) ((uint32_t) -1) || - end_body < start_body) { - m->poisoned = true; - return NULL; - } - - if (added > 0) { - struct bus_body_part *part = NULL; - bool add_new_part; - - add_new_part = - m->n_body_parts <= 0 || - m->body_end->sealed || - (padding != ALIGN_TO(m->body_end->size, align) - m->body_end->size) || - (force_inline && m->body_end->size > MEMFD_MIN_SIZE); /* if this must be an inlined extension, let's create a new part if the previous part is large enough to be inlined */ - - if (add_new_part) { - if (padding > 0) { - part = message_append_part(m); - if (!part) - return NULL; - - part_zero(part, padding); - } - - part = message_append_part(m); - if (!part) - return NULL; - - r = part_make_space(m, part, sz, &p); - if (r < 0) - return NULL; - } else { - struct bus_container *c; - void *op; - size_t os, start_part, end_part; - - part = m->body_end; - op = part->data; - os = part->size; - - start_part = ALIGN_TO(part->size, align); - end_part = start_part + sz; - - r = part_make_space(m, part, end_part, &p); - if (r < 0) - return NULL; - - if (padding > 0) { - memzero(p, padding); - p = (uint8_t*) p + padding; - } - - /* Readjust pointers */ - for (c = m->containers; c < m->containers + m->n_containers; c++) - c->array_size = adjust_pointer(c->array_size, op, os, part->data); - - m->error.message = (const char*) adjust_pointer(m->error.message, op, os, part->data); - } - } else - /* Return something that is not NULL and is aligned */ - p = (uint8_t *) NULL + align; - - m->body_size = end_body; - message_extend_containers(m, added); - - if (add_offset) { - r = message_add_offset(m, end_body); - if (r < 0) { - m->poisoned = true; - return NULL; - } - } - - return p; -} - -static int message_push_fd(sd_bus_message *m, int fd) { - int *f, copy; - - assert(m); - - if (fd < 0) - return -EINVAL; - - if (!m->allow_fds) - return -EOPNOTSUPP; - - copy = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (copy < 0) - return -errno; - - f = realloc(m->fds, sizeof(int) * (m->n_fds + 1)); - if (!f) { - m->poisoned = true; - safe_close(copy); - return -ENOMEM; - } - - m->fds = f; - m->fds[m->n_fds] = copy; - m->free_fds = true; - - return copy; -} - -int message_append_basic(sd_bus_message *m, char type, const void *p, const void **stored) { - _cleanup_close_ int fd = -1; - struct bus_container *c; - ssize_t align, sz; - void *a; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(bus_type_is_basic(type), -EINVAL); - assert_return(!m->poisoned, -ESTALE); - - c = message_get_container(m); - - if (c->signature && c->signature[c->index]) { - /* Container signature is already set */ - - if (c->signature[c->index] != type) - return -ENXIO; - } else { - char *e; - - /* Maybe we can append to the signature? But only if this is the top-level container */ - if (c->enclosing != 0) - return -ENXIO; - - e = strextend(&c->signature, CHAR_TO_STR(type), NULL); - if (!e) { - m->poisoned = true; - return -ENOMEM; - } - } - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - uint8_t u8; - uint32_t u32; - - switch (type) { - - case SD_BUS_TYPE_SIGNATURE: - case SD_BUS_TYPE_STRING: - p = strempty(p); - - /* Fall through... */ - case SD_BUS_TYPE_OBJECT_PATH: - if (!p) - return -EINVAL; - - align = 1; - sz = strlen(p) + 1; - break; - - case SD_BUS_TYPE_BOOLEAN: - - u8 = p && *(int*) p; - p = &u8; - - align = sz = 1; - break; - - case SD_BUS_TYPE_UNIX_FD: - - if (!p) - return -EINVAL; - - fd = message_push_fd(m, *(int*) p); - if (fd < 0) - return fd; - - u32 = m->n_fds; - p = &u32; - - align = sz = 4; - break; - - default: - align = bus_gvariant_get_alignment(CHAR_TO_STR(type)); - sz = bus_gvariant_get_size(CHAR_TO_STR(type)); - break; - } - - assert(align > 0); - assert(sz > 0); - - a = message_extend_body(m, align, sz, true, false); - if (!a) - return -ENOMEM; - - memcpy(a, p, sz); - - if (stored) - *stored = (const uint8_t*) a; - - } else { - uint32_t u32; - - switch (type) { - - case SD_BUS_TYPE_STRING: - /* To make things easy we'll serialize a NULL string - * into the empty string */ - p = strempty(p); - - /* Fall through... */ - case SD_BUS_TYPE_OBJECT_PATH: - - if (!p) - return -EINVAL; - - align = 4; - sz = 4 + strlen(p) + 1; - break; - - case SD_BUS_TYPE_SIGNATURE: - - p = strempty(p); - - align = 1; - sz = 1 + strlen(p) + 1; - break; - - case SD_BUS_TYPE_BOOLEAN: - - u32 = p && *(int*) p; - p = &u32; - - align = sz = 4; - break; - - case SD_BUS_TYPE_UNIX_FD: - - if (!p) - return -EINVAL; - - fd = message_push_fd(m, *(int*) p); - if (fd < 0) - return fd; - - u32 = m->n_fds; - p = &u32; - - align = sz = 4; - break; - - default: - align = bus_type_get_alignment(type); - sz = bus_type_get_size(type); - break; - } - - assert(align > 0); - assert(sz > 0); - - a = message_extend_body(m, align, sz, false, false); - if (!a) - return -ENOMEM; - - if (type == SD_BUS_TYPE_STRING || type == SD_BUS_TYPE_OBJECT_PATH) { - *(uint32_t*) a = sz - 5; - memcpy((uint8_t*) a + 4, p, sz - 4); - - if (stored) - *stored = (const uint8_t*) a + 4; - - } else if (type == SD_BUS_TYPE_SIGNATURE) { - *(uint8_t*) a = sz - 2; - memcpy((uint8_t*) a + 1, p, sz - 1); - - if (stored) - *stored = (const uint8_t*) a + 1; - } else { - memcpy(a, p, sz); - - if (stored) - *stored = a; - } - } - - if (type == SD_BUS_TYPE_UNIX_FD) - m->n_fds++; - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index++; - - fd = -1; - return 0; -} - -_public_ int sd_bus_message_append_basic(sd_bus_message *m, char type, const void *p) { - return message_append_basic(m, type, p, NULL); -} - -_public_ int sd_bus_message_append_string_space( - sd_bus_message *m, - size_t size, - char **s) { - - struct bus_container *c; - void *a; - - assert_return(m, -EINVAL); - assert_return(s, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(!m->poisoned, -ESTALE); - - c = message_get_container(m); - - if (c->signature && c->signature[c->index]) { - /* Container signature is already set */ - - if (c->signature[c->index] != SD_BUS_TYPE_STRING) - return -ENXIO; - } else { - char *e; - - /* Maybe we can append to the signature? But only if this is the top-level container */ - if (c->enclosing != 0) - return -ENXIO; - - e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_STRING), NULL); - if (!e) { - m->poisoned = true; - return -ENOMEM; - } - } - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - a = message_extend_body(m, 1, size + 1, true, false); - if (!a) - return -ENOMEM; - - *s = a; - } else { - a = message_extend_body(m, 4, 4 + size + 1, false, false); - if (!a) - return -ENOMEM; - - *(uint32_t*) a = size; - *s = (char*) a + 4; - } - - (*s)[size] = 0; - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index++; - - return 0; -} - -_public_ int sd_bus_message_append_string_iovec( - sd_bus_message *m, - const struct iovec *iov, - unsigned n) { - - size_t size; - unsigned i; - char *p; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(iov || n == 0, -EINVAL); - assert_return(!m->poisoned, -ESTALE); - - size = IOVEC_TOTAL_SIZE(iov, n); - - r = sd_bus_message_append_string_space(m, size, &p); - if (r < 0) - return r; - - for (i = 0; i < n; i++) { - - if (iov[i].iov_base) - memcpy(p, iov[i].iov_base, iov[i].iov_len); - else - memset(p, ' ', iov[i].iov_len); - - p += iov[i].iov_len; - } - - return 0; -} - -static int bus_message_open_array( - sd_bus_message *m, - struct bus_container *c, - const char *contents, - uint32_t **array_size, - size_t *begin, - bool *need_offsets) { - - unsigned nindex; - int alignment, r; - - assert(m); - assert(c); - assert(contents); - assert(array_size); - assert(begin); - assert(need_offsets); - - if (!signature_is_single(contents, true)) - return -EINVAL; - - if (c->signature && c->signature[c->index]) { - - /* Verify the existing signature */ - - if (c->signature[c->index] != SD_BUS_TYPE_ARRAY) - return -ENXIO; - - if (!startswith(c->signature + c->index + 1, contents)) - return -ENXIO; - - nindex = c->index + 1 + strlen(contents); - } else { - char *e; - - if (c->enclosing != 0) - return -ENXIO; - - /* Extend the existing signature */ - - e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_ARRAY), contents, NULL); - if (!e) { - m->poisoned = true; - return -ENOMEM; - } - - nindex = e - c->signature; - } - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - alignment = bus_gvariant_get_alignment(contents); - if (alignment < 0) - return alignment; - - /* Add alignment padding and add to offset list */ - if (!message_extend_body(m, alignment, 0, false, false)) - return -ENOMEM; - - r = bus_gvariant_is_fixed_size(contents); - if (r < 0) - return r; - - *begin = m->body_size; - *need_offsets = r == 0; - } else { - void *a, *op; - size_t os; - struct bus_body_part *o; - - alignment = bus_type_get_alignment(contents[0]); - if (alignment < 0) - return alignment; - - a = message_extend_body(m, 4, 4, false, false); - if (!a) - return -ENOMEM; - - o = m->body_end; - op = m->body_end->data; - os = m->body_end->size; - - /* Add alignment between size and first element */ - if (!message_extend_body(m, alignment, 0, false, false)) - return -ENOMEM; - - /* location of array size might have changed so let's readjust a */ - if (o == m->body_end) - a = adjust_pointer(a, op, os, m->body_end->data); - - *(uint32_t*) a = 0; - *array_size = a; - } - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index = nindex; - - return 0; -} - -static int bus_message_open_variant( - sd_bus_message *m, - struct bus_container *c, - const char *contents) { - - assert(m); - assert(c); - assert(contents); - - if (!signature_is_single(contents, false)) - return -EINVAL; - - if (*contents == SD_BUS_TYPE_DICT_ENTRY_BEGIN) - return -EINVAL; - - if (c->signature && c->signature[c->index]) { - - if (c->signature[c->index] != SD_BUS_TYPE_VARIANT) - return -ENXIO; - - } else { - char *e; - - if (c->enclosing != 0) - return -ENXIO; - - e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_VARIANT), NULL); - if (!e) { - m->poisoned = true; - return -ENOMEM; - } - } - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - /* Variants are always aligned to 8 */ - - if (!message_extend_body(m, 8, 0, false, false)) - return -ENOMEM; - - } else { - size_t l; - void *a; - - l = strlen(contents); - a = message_extend_body(m, 1, 1 + l + 1, false, false); - if (!a) - return -ENOMEM; - - *(uint8_t*) a = l; - memcpy((uint8_t*) a + 1, contents, l + 1); - } - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index++; - - return 0; -} - -static int bus_message_open_struct( - sd_bus_message *m, - struct bus_container *c, - const char *contents, - size_t *begin, - bool *need_offsets) { - - size_t nindex; - int r; - - assert(m); - assert(c); - assert(contents); - assert(begin); - assert(need_offsets); - - if (!signature_is_valid(contents, false)) - return -EINVAL; - - if (c->signature && c->signature[c->index]) { - size_t l; - - l = strlen(contents); - - if (c->signature[c->index] != SD_BUS_TYPE_STRUCT_BEGIN || - !startswith(c->signature + c->index + 1, contents) || - c->signature[c->index + 1 + l] != SD_BUS_TYPE_STRUCT_END) - return -ENXIO; - - nindex = c->index + 1 + l + 1; - } else { - char *e; - - if (c->enclosing != 0) - return -ENXIO; - - e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_STRUCT_BEGIN), contents, CHAR_TO_STR(SD_BUS_TYPE_STRUCT_END), NULL); - if (!e) { - m->poisoned = true; - return -ENOMEM; - } - - nindex = e - c->signature; - } - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - int alignment; - - alignment = bus_gvariant_get_alignment(contents); - if (alignment < 0) - return alignment; - - if (!message_extend_body(m, alignment, 0, false, false)) - return -ENOMEM; - - r = bus_gvariant_is_fixed_size(contents); - if (r < 0) - return r; - - *begin = m->body_size; - *need_offsets = r == 0; - } else { - /* Align contents to 8 byte boundary */ - if (!message_extend_body(m, 8, 0, false, false)) - return -ENOMEM; - } - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index = nindex; - - return 0; -} - -static int bus_message_open_dict_entry( - sd_bus_message *m, - struct bus_container *c, - const char *contents, - size_t *begin, - bool *need_offsets) { - - int r; - - assert(m); - assert(c); - assert(contents); - assert(begin); - assert(need_offsets); - - if (!signature_is_pair(contents)) - return -EINVAL; - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - return -ENXIO; - - if (c->signature && c->signature[c->index]) { - size_t l; - - l = strlen(contents); - - if (c->signature[c->index] != SD_BUS_TYPE_DICT_ENTRY_BEGIN || - !startswith(c->signature + c->index + 1, contents) || - c->signature[c->index + 1 + l] != SD_BUS_TYPE_DICT_ENTRY_END) - return -ENXIO; - } else - return -ENXIO; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - int alignment; - - alignment = bus_gvariant_get_alignment(contents); - if (alignment < 0) - return alignment; - - if (!message_extend_body(m, alignment, 0, false, false)) - return -ENOMEM; - - r = bus_gvariant_is_fixed_size(contents); - if (r < 0) - return r; - - *begin = m->body_size; - *need_offsets = r == 0; - } else { - /* Align contents to 8 byte boundary */ - if (!message_extend_body(m, 8, 0, false, false)) - return -ENOMEM; - } - - return 0; -} - -_public_ int sd_bus_message_open_container( - sd_bus_message *m, - char type, - const char *contents) { - - struct bus_container *c, *w; - uint32_t *array_size = NULL; - char *signature; - size_t before, begin = 0; - bool need_offsets = false; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(contents, -EINVAL); - assert_return(!m->poisoned, -ESTALE); - - /* Make sure we have space for one more container */ - if (!GREEDY_REALLOC(m->containers, m->containers_allocated, m->n_containers + 1)) { - m->poisoned = true; - return -ENOMEM; - } - - c = message_get_container(m); - - signature = strdup(contents); - if (!signature) { - m->poisoned = true; - return -ENOMEM; - } - - /* Save old index in the parent container, in case we have to - * abort this container */ - c->saved_index = c->index; - before = m->body_size; - - if (type == SD_BUS_TYPE_ARRAY) - r = bus_message_open_array(m, c, contents, &array_size, &begin, &need_offsets); - else if (type == SD_BUS_TYPE_VARIANT) - r = bus_message_open_variant(m, c, contents); - else if (type == SD_BUS_TYPE_STRUCT) - r = bus_message_open_struct(m, c, contents, &begin, &need_offsets); - else if (type == SD_BUS_TYPE_DICT_ENTRY) - r = bus_message_open_dict_entry(m, c, contents, &begin, &need_offsets); - else - r = -EINVAL; - - if (r < 0) { - free(signature); - return r; - } - - /* OK, let's fill it in */ - w = m->containers + m->n_containers++; - w->enclosing = type; - w->signature = signature; - w->index = 0; - w->array_size = array_size; - w->before = before; - w->begin = begin; - w->n_offsets = w->offsets_allocated = 0; - w->offsets = NULL; - w->need_offsets = need_offsets; - - return 0; -} - -static int bus_message_close_array(sd_bus_message *m, struct bus_container *c) { - - assert(m); - assert(c); - - if (!BUS_MESSAGE_IS_GVARIANT(m)) - return 0; - - if (c->need_offsets) { - size_t payload, sz, i; - uint8_t *a; - - /* Variable-width arrays */ - - payload = c->n_offsets > 0 ? c->offsets[c->n_offsets-1] - c->begin : 0; - sz = bus_gvariant_determine_word_size(payload, c->n_offsets); - - a = message_extend_body(m, 1, sz * c->n_offsets, true, false); - if (!a) - return -ENOMEM; - - for (i = 0; i < c->n_offsets; i++) - bus_gvariant_write_word_le(a + sz*i, sz, c->offsets[i] - c->begin); - } else { - void *a; - - /* Fixed-width or empty arrays */ - - a = message_extend_body(m, 1, 0, true, false); /* let's add offset to parent */ - if (!a) - return -ENOMEM; - } - - return 0; -} - -static int bus_message_close_variant(sd_bus_message *m, struct bus_container *c) { - uint8_t *a; - size_t l; - - assert(m); - assert(c); - assert(c->signature); - - if (!BUS_MESSAGE_IS_GVARIANT(m)) - return 0; - - l = strlen(c->signature); - - a = message_extend_body(m, 1, 1 + l, true, false); - if (!a) - return -ENOMEM; - - a[0] = 0; - memcpy(a+1, c->signature, l); - - return 0; -} - -static int bus_message_close_struct(sd_bus_message *m, struct bus_container *c, bool add_offset) { - bool fixed_size = true; - size_t n_variable = 0; - unsigned i = 0; - const char *p; - uint8_t *a; - int r; - - assert(m); - assert(c); - - if (!BUS_MESSAGE_IS_GVARIANT(m)) - return 0; - - p = strempty(c->signature); - while (*p != 0) { - size_t n; - - r = signature_element_length(p, &n); - if (r < 0) - return r; - else { - char t[n+1]; - - memcpy(t, p, n); - t[n] = 0; - - r = bus_gvariant_is_fixed_size(t); - if (r < 0) - return r; - } - - assert(!c->need_offsets || i <= c->n_offsets); - - /* We need to add an offset for each item that has a - * variable size and that is not the last one in the - * list */ - if (r == 0) - fixed_size = false; - if (r == 0 && p[n] != 0) - n_variable++; - - i++; - p += n; - } - - assert(!c->need_offsets || i == c->n_offsets); - assert(c->need_offsets || n_variable == 0); - - if (isempty(c->signature)) { - /* The unary type is encoded as fixed 1 byte padding */ - a = message_extend_body(m, 1, 1, add_offset, false); - if (!a) - return -ENOMEM; - - *a = 0; - } else if (n_variable <= 0) { - int alignment = 1; - - /* Structures with fixed-size members only have to be - * fixed-size themselves. But gvariant requires all fixed-size - * elements to be sized a multiple of their alignment. Hence, - * we must *always* add final padding after the last member so - * the overall size of the structure is properly aligned. */ - if (fixed_size) - alignment = bus_gvariant_get_alignment(strempty(c->signature)); - - assert(alignment > 0); - - a = message_extend_body(m, alignment, 0, add_offset, false); - if (!a) - return -ENOMEM; - } else { - size_t sz; - unsigned j; - - assert(c->offsets[c->n_offsets-1] == m->body_size); - - sz = bus_gvariant_determine_word_size(m->body_size - c->begin, n_variable); - - a = message_extend_body(m, 1, sz * n_variable, add_offset, false); - if (!a) - return -ENOMEM; - - p = strempty(c->signature); - for (i = 0, j = 0; i < c->n_offsets; i++) { - unsigned k; - size_t n; - - r = signature_element_length(p, &n); - if (r < 0) - return r; - else { - char t[n+1]; - - memcpy(t, p, n); - t[n] = 0; - - p += n; - - r = bus_gvariant_is_fixed_size(t); - if (r < 0) - return r; - if (r > 0 || p[0] == 0) - continue; - } - - k = n_variable - 1 - j; - - bus_gvariant_write_word_le(a + k * sz, sz, c->offsets[i] - c->begin); - - j++; - } - } - - return 0; -} - -_public_ int sd_bus_message_close_container(sd_bus_message *m) { - struct bus_container *c; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(m->n_containers > 0, -EINVAL); - assert_return(!m->poisoned, -ESTALE); - - c = message_get_container(m); - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - if (c->signature && c->signature[c->index] != 0) - return -EINVAL; - - m->n_containers--; - - if (c->enclosing == SD_BUS_TYPE_ARRAY) - r = bus_message_close_array(m, c); - else if (c->enclosing == SD_BUS_TYPE_VARIANT) - r = bus_message_close_variant(m, c); - else if (c->enclosing == SD_BUS_TYPE_STRUCT || c->enclosing == SD_BUS_TYPE_DICT_ENTRY) - r = bus_message_close_struct(m, c, true); - else - assert_not_reached("Unknown container type"); - - free(c->signature); - free(c->offsets); - - return r; -} - -typedef struct { - const char *types; - unsigned n_struct; - unsigned n_array; -} TypeStack; - -static int type_stack_push(TypeStack *stack, unsigned max, unsigned *i, const char *types, unsigned n_struct, unsigned n_array) { - assert(stack); - assert(max > 0); - - if (*i >= max) - return -EINVAL; - - stack[*i].types = types; - stack[*i].n_struct = n_struct; - stack[*i].n_array = n_array; - (*i)++; - - return 0; -} - -static int type_stack_pop(TypeStack *stack, unsigned max, unsigned *i, const char **types, unsigned *n_struct, unsigned *n_array) { - assert(stack); - assert(max > 0); - assert(types); - assert(n_struct); - assert(n_array); - - if (*i <= 0) - return 0; - - (*i)--; - *types = stack[*i].types; - *n_struct = stack[*i].n_struct; - *n_array = stack[*i].n_array; - - return 1; -} - -int bus_message_append_ap( - sd_bus_message *m, - const char *types, - va_list ap) { - - unsigned n_array, n_struct; - TypeStack stack[BUS_CONTAINER_DEPTH]; - unsigned stack_ptr = 0; - int r; - - assert(m); - - if (!types) - return 0; - - n_array = (unsigned) -1; - n_struct = strlen(types); - - for (;;) { - const char *t; - - if (n_array == 0 || (n_array == (unsigned) -1 && n_struct == 0)) { - r = type_stack_pop(stack, ELEMENTSOF(stack), &stack_ptr, &types, &n_struct, &n_array); - if (r < 0) - return r; - if (r == 0) - break; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - continue; - } - - t = types; - if (n_array != (unsigned) -1) - n_array--; - else { - types++; - n_struct--; - } - - switch (*t) { - - case SD_BUS_TYPE_BYTE: { - uint8_t x; - - x = (uint8_t) va_arg(ap, int); - r = sd_bus_message_append_basic(m, *t, &x); - break; - } - - case SD_BUS_TYPE_BOOLEAN: - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - case SD_BUS_TYPE_UNIX_FD: { - uint32_t x; - - /* We assume a boolean is the same as int32_t */ - assert_cc(sizeof(int32_t) == sizeof(int)); - - x = va_arg(ap, uint32_t); - r = sd_bus_message_append_basic(m, *t, &x); - break; - } - - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: { - uint16_t x; - - x = (uint16_t) va_arg(ap, int); - r = sd_bus_message_append_basic(m, *t, &x); - break; - } - - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: { - uint64_t x; - - x = va_arg(ap, uint64_t); - r = sd_bus_message_append_basic(m, *t, &x); - break; - } - - case SD_BUS_TYPE_DOUBLE: { - double x; - - x = va_arg(ap, double); - r = sd_bus_message_append_basic(m, *t, &x); - break; - } - - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_SIGNATURE: { - const char *x; - - x = va_arg(ap, const char*); - r = sd_bus_message_append_basic(m, *t, x); - break; - } - - case SD_BUS_TYPE_ARRAY: { - size_t k; - - r = signature_element_length(t + 1, &k); - if (r < 0) - return r; - - { - char s[k + 1]; - memcpy(s, t + 1, k); - s[k] = 0; - - r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, s); - if (r < 0) - return r; - } - - if (n_array == (unsigned) -1) { - types += k; - n_struct -= k; - } - - r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); - if (r < 0) - return r; - - types = t + 1; - n_struct = k; - n_array = va_arg(ap, unsigned); - - break; - } - - case SD_BUS_TYPE_VARIANT: { - const char *s; - - s = va_arg(ap, const char*); - if (!s) - return -EINVAL; - - r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT, s); - if (r < 0) - return r; - - r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); - if (r < 0) - return r; - - types = s; - n_struct = strlen(s); - n_array = (unsigned) -1; - - break; - } - - case SD_BUS_TYPE_STRUCT_BEGIN: - case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { - size_t k; - - r = signature_element_length(t, &k); - if (r < 0) - return r; - - { - char s[k - 1]; - - memcpy(s, t + 1, k - 2); - s[k - 2] = 0; - - r = sd_bus_message_open_container(m, *t == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s); - if (r < 0) - return r; - } - - if (n_array == (unsigned) -1) { - types += k - 1; - n_struct -= k - 1; - } - - r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); - if (r < 0) - return r; - - types = t + 1; - n_struct = k - 2; - n_array = (unsigned) -1; - - break; - } - - default: - r = -EINVAL; - } - - if (r < 0) - return r; - } - - return 1; -} - -_public_ int sd_bus_message_append(sd_bus_message *m, const char *types, ...) { - va_list ap; - int r; - - assert_return(m, -EINVAL); - assert_return(types, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(!m->poisoned, -ESTALE); - - va_start(ap, types); - r = bus_message_append_ap(m, types, ap); - va_end(ap); - - return r; -} - -_public_ int sd_bus_message_append_array_space( - sd_bus_message *m, - char type, - size_t size, - void **ptr) { - - ssize_t align, sz; - void *a; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(bus_type_is_trivial(type) && type != SD_BUS_TYPE_BOOLEAN, -EINVAL); - assert_return(ptr || size == 0, -EINVAL); - assert_return(!m->poisoned, -ESTALE); - - /* alignment and size of the trivial types (except bool) is - * identical for gvariant and dbus1 marshalling */ - align = bus_type_get_alignment(type); - sz = bus_type_get_size(type); - - assert_se(align > 0); - assert_se(sz > 0); - - if (size % sz != 0) - return -EINVAL; - - r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, CHAR_TO_STR(type)); - if (r < 0) - return r; - - a = message_extend_body(m, align, size, false, false); - if (!a) - return -ENOMEM; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - *ptr = a; - return 0; -} - -_public_ int sd_bus_message_append_array( - sd_bus_message *m, - char type, - const void *ptr, - size_t size) { - int r; - void *p; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(bus_type_is_trivial(type), -EINVAL); - assert_return(ptr || size == 0, -EINVAL); - assert_return(!m->poisoned, -ESTALE); - - r = sd_bus_message_append_array_space(m, type, size, &p); - if (r < 0) - return r; - - memcpy_safe(p, ptr, size); - - return 0; -} - -_public_ int sd_bus_message_append_array_iovec( - sd_bus_message *m, - char type, - const struct iovec *iov, - unsigned n) { - - size_t size; - unsigned i; - void *p; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(bus_type_is_trivial(type), -EINVAL); - assert_return(iov || n == 0, -EINVAL); - assert_return(!m->poisoned, -ESTALE); - - size = IOVEC_TOTAL_SIZE(iov, n); - - r = sd_bus_message_append_array_space(m, type, size, &p); - if (r < 0) - return r; - - for (i = 0; i < n; i++) { - - if (iov[i].iov_base) - memcpy(p, iov[i].iov_base, iov[i].iov_len); - else - memzero(p, iov[i].iov_len); - - p = (uint8_t*) p + iov[i].iov_len; - } - - return 0; -} - -_public_ int sd_bus_message_append_array_memfd( - sd_bus_message *m, - char type, - int memfd, - uint64_t offset, - uint64_t size) { - - _cleanup_close_ int copy_fd = -1; - struct bus_body_part *part; - ssize_t align, sz; - uint64_t real_size; - void *a; - int r; - - assert_return(m, -EINVAL); - assert_return(memfd >= 0, -EBADF); - assert_return(bus_type_is_trivial(type), -EINVAL); - assert_return(size > 0, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(!m->poisoned, -ESTALE); - - r = memfd_set_sealed(memfd); - if (r < 0) - return r; - - copy_fd = dup(memfd); - if (copy_fd < 0) - return copy_fd; - - r = memfd_get_size(memfd, &real_size); - if (r < 0) - return r; - - if (offset == 0 && size == (uint64_t) -1) - size = real_size; - else if (offset + size > real_size) - return -EMSGSIZE; - - align = bus_type_get_alignment(type); - sz = bus_type_get_size(type); - - assert_se(align > 0); - assert_se(sz > 0); - - if (offset % align != 0) - return -EINVAL; - - if (size % sz != 0) - return -EINVAL; - - if (size > (uint64_t) (uint32_t) -1) - return -EINVAL; - - r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, CHAR_TO_STR(type)); - if (r < 0) - return r; - - a = message_extend_body(m, align, 0, false, false); - if (!a) - return -ENOMEM; - - part = message_append_part(m); - if (!part) - return -ENOMEM; - - part->memfd = copy_fd; - part->memfd_offset = offset; - part->sealed = true; - part->size = size; - copy_fd = -1; - - m->body_size += size; - message_extend_containers(m, size); - - return sd_bus_message_close_container(m); -} - -_public_ int sd_bus_message_append_string_memfd( - sd_bus_message *m, - int memfd, - uint64_t offset, - uint64_t size) { - - _cleanup_close_ int copy_fd = -1; - struct bus_body_part *part; - struct bus_container *c; - uint64_t real_size; - void *a; - int r; - - assert_return(m, -EINVAL); - assert_return(memfd >= 0, -EBADF); - assert_return(size > 0, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(!m->poisoned, -ESTALE); - - r = memfd_set_sealed(memfd); - if (r < 0) - return r; - - copy_fd = dup(memfd); - if (copy_fd < 0) - return copy_fd; - - r = memfd_get_size(memfd, &real_size); - if (r < 0) - return r; - - if (offset == 0 && size == (uint64_t) -1) - size = real_size; - else if (offset + size > real_size) - return -EMSGSIZE; - - /* We require this to be NUL terminated */ - if (size == 0) - return -EINVAL; - - if (size > (uint64_t) (uint32_t) -1) - return -EINVAL; - - c = message_get_container(m); - if (c->signature && c->signature[c->index]) { - /* Container signature is already set */ - - if (c->signature[c->index] != SD_BUS_TYPE_STRING) - return -ENXIO; - } else { - char *e; - - /* Maybe we can append to the signature? But only if this is the top-level container */ - if (c->enclosing != 0) - return -ENXIO; - - e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_STRING), NULL); - if (!e) { - m->poisoned = true; - return -ENOMEM; - } - } - - if (!BUS_MESSAGE_IS_GVARIANT(m)) { - a = message_extend_body(m, 4, 4, false, false); - if (!a) - return -ENOMEM; - - *(uint32_t*) a = size - 1; - } - - part = message_append_part(m); - if (!part) - return -ENOMEM; - - part->memfd = copy_fd; - part->memfd_offset = offset; - part->sealed = true; - part->size = size; - copy_fd = -1; - - m->body_size += size; - message_extend_containers(m, size); - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - r = message_add_offset(m, m->body_size); - if (r < 0) { - m->poisoned = true; - return -ENOMEM; - } - } - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index++; - - return 0; -} - -_public_ int sd_bus_message_append_strv(sd_bus_message *m, char **l) { - char **i; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(!m->poisoned, -ESTALE); - - r = sd_bus_message_open_container(m, 'a', "s"); - if (r < 0) - return r; - - STRV_FOREACH(i, l) { - r = sd_bus_message_append_basic(m, 's', *i); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(m); -} - -static int bus_message_close_header(sd_bus_message *m) { - - assert(m); - - /* The actual user data is finished now, we just complete the - variant and struct now (at least on gvariant). Remember - this position, so that during parsing we know where to to - put the outer container end. */ - m->user_body_size = m->body_size; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - const char *signature; - size_t sz, l; - void *d; - - /* Add offset table to end of fields array */ - if (m->n_header_offsets >= 1) { - uint8_t *a; - unsigned i; - - assert(m->fields_size == m->header_offsets[m->n_header_offsets-1]); - - sz = bus_gvariant_determine_word_size(m->fields_size, m->n_header_offsets); - a = message_extend_fields(m, 1, sz * m->n_header_offsets, false); - if (!a) - return -ENOMEM; - - for (i = 0; i < m->n_header_offsets; i++) - bus_gvariant_write_word_le(a + sz*i, sz, m->header_offsets[i]); - } - - /* Add gvariant NUL byte plus signature to the end of - * the body, followed by the final offset pointing to - * the end of the fields array */ - - signature = strempty(m->root_container.signature); - l = strlen(signature); - - sz = bus_gvariant_determine_word_size(sizeof(struct bus_header) + ALIGN8(m->fields_size) + m->body_size + 1 + l + 2, 1); - d = message_extend_body(m, 1, 1 + l + 2 + sz, false, true); - if (!d) - return -ENOMEM; - - *(uint8_t*) d = 0; - *((uint8_t*) d + 1) = SD_BUS_TYPE_STRUCT_BEGIN; - memcpy((uint8_t*) d + 2, signature, l); - *((uint8_t*) d + 1 + l + 1) = SD_BUS_TYPE_STRUCT_END; - - bus_gvariant_write_word_le((uint8_t*) d + 1 + l + 2, sz, sizeof(struct bus_header) + m->fields_size); - - m->footer = d; - m->footer_accessible = 1 + l + 2 + sz; - } else { - m->header->dbus1.fields_size = m->fields_size; - m->header->dbus1.body_size = m->body_size; - } - - return 0; -} - -int bus_message_seal(sd_bus_message *m, uint64_t cookie, usec_t timeout) { - struct bus_body_part *part; - size_t a; - unsigned i; - int r; - - assert(m); - - if (m->sealed) - return -EPERM; - - if (m->n_containers > 0) - return -EBADMSG; - - if (m->poisoned) - return -ESTALE; - - if (cookie > 0xffffffffULL && - !BUS_MESSAGE_IS_GVARIANT(m)) - return -EOPNOTSUPP; - - /* In vtables the return signature of method calls is listed, - * let's check if they match if this is a response */ - if (m->header->type == SD_BUS_MESSAGE_METHOD_RETURN && - m->enforced_reply_signature && - !streq(strempty(m->root_container.signature), m->enforced_reply_signature)) - return -ENOMSG; - - /* If gvariant marshalling is used we need to close the body structure */ - r = bus_message_close_struct(m, &m->root_container, false); - if (r < 0) - return r; - - /* If there's a non-trivial signature set, then add it in - * here, but only on dbus1 */ - if (!isempty(m->root_container.signature) && !BUS_MESSAGE_IS_GVARIANT(m)) { - r = message_append_field_signature(m, BUS_MESSAGE_HEADER_SIGNATURE, m->root_container.signature, NULL); - if (r < 0) - return r; - } - - if (m->n_fds > 0) { - r = message_append_field_uint32(m, BUS_MESSAGE_HEADER_UNIX_FDS, m->n_fds); - if (r < 0) - return r; - } - - r = bus_message_close_header(m); - if (r < 0) - return r; - - if (BUS_MESSAGE_IS_GVARIANT(m)) - m->header->dbus2.cookie = cookie; - else - m->header->dbus1.serial = (uint32_t) cookie; - - m->timeout = m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED ? 0 : timeout; - - /* Add padding at the end of the fields part, since we know - * the body needs to start at an 8 byte alignment. We made - * sure we allocated enough space for this, so all we need to - * do here is to zero it out. */ - a = ALIGN8(m->fields_size) - m->fields_size; - if (a > 0) - memzero((uint8_t*) BUS_MESSAGE_FIELDS(m) + m->fields_size, a); - - /* If this is something we can send as memfd, then let's seal - the memfd now. Note that we can send memfds as payload only - for directed messages, and not for broadcasts. */ - if (m->destination && m->bus->use_memfd) { - MESSAGE_FOREACH_PART(part, i, m) - if (part->memfd >= 0 && - !part->sealed && - (part->size > MEMFD_MIN_SIZE || m->bus->use_memfd < 0) && - part != m->body_end) { /* The last part may never be sent as memfd */ - uint64_t sz; - - /* Try to seal it if that makes - * sense. First, unmap our own map to - * make sure we don't keep it busy. */ - bus_body_part_unmap(part); - - /* Then, sync up real memfd size */ - sz = part->size; - r = memfd_set_size(part->memfd, sz); - if (r < 0) - return r; - - /* Finally, try to seal */ - if (memfd_set_sealed(part->memfd) >= 0) - part->sealed = true; - } - } - - m->root_container.end = m->user_body_size; - m->root_container.index = 0; - m->root_container.offset_index = 0; - m->root_container.item_size = m->root_container.n_offsets > 0 ? m->root_container.offsets[0] : 0; - - m->sealed = true; - - return 0; -} - -int bus_body_part_map(struct bus_body_part *part) { - void *p; - size_t psz, shift; - - assert_se(part); - - if (part->data) - return 0; - - if (part->size <= 0) - return 0; - - /* For smaller zero parts (as used for padding) we don't need to map anything... */ - if (part->memfd < 0 && part->is_zero && part->size < 8) { - static const uint8_t zeroes[7] = { }; - part->data = (void*) zeroes; - return 0; - } - - shift = part->memfd_offset - ((part->memfd_offset / page_size()) * page_size()); - psz = PAGE_ALIGN(part->size + shift); - - if (part->memfd >= 0) - p = mmap(NULL, psz, PROT_READ, MAP_PRIVATE, part->memfd, part->memfd_offset - shift); - else if (part->is_zero) - p = mmap(NULL, psz, PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); - else - return -EINVAL; - - if (p == MAP_FAILED) - return -errno; - - part->mapped = psz; - part->mmap_begin = p; - part->data = (uint8_t*) p + shift; - part->munmap_this = true; - - return 0; -} - -void bus_body_part_unmap(struct bus_body_part *part) { - - assert_se(part); - - if (part->memfd < 0) - return; - - if (!part->mmap_begin) - return; - - if (!part->munmap_this) - return; - - assert_se(munmap(part->mmap_begin, part->mapped) == 0); - - part->mmap_begin = NULL; - part->data = NULL; - part->mapped = 0; - part->munmap_this = false; - - return; -} - -static int buffer_peek(const void *p, uint32_t sz, size_t *rindex, size_t align, size_t nbytes, void **r) { - size_t k, start, end; - - assert(rindex); - assert(align > 0); - - start = ALIGN_TO((size_t) *rindex, align); - end = start + nbytes; - - if (end > sz) - return -EBADMSG; - - /* Verify that padding is 0 */ - for (k = *rindex; k < start; k++) - if (((const uint8_t*) p)[k] != 0) - return -EBADMSG; - - if (r) - *r = (uint8_t*) p + start; - - *rindex = end; - - return 1; -} - -static bool message_end_of_signature(sd_bus_message *m) { - struct bus_container *c; - - assert(m); - - c = message_get_container(m); - return !c->signature || c->signature[c->index] == 0; -} - -static bool message_end_of_array(sd_bus_message *m, size_t index) { - struct bus_container *c; - - assert(m); - - c = message_get_container(m); - if (c->enclosing != SD_BUS_TYPE_ARRAY) - return false; - - if (BUS_MESSAGE_IS_GVARIANT(m)) - return index >= c->end; - else { - assert(c->array_size); - return index >= c->begin + BUS_MESSAGE_BSWAP32(m, *c->array_size); - } -} - -_public_ int sd_bus_message_at_end(sd_bus_message *m, int complete) { - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - - if (complete && m->n_containers > 0) - return false; - - if (message_end_of_signature(m)) - return true; - - if (message_end_of_array(m, m->rindex)) - return true; - - return false; -} - -static struct bus_body_part* find_part(sd_bus_message *m, size_t index, size_t sz, void **p) { - struct bus_body_part *part; - size_t begin; - int r; - - assert(m); - - if (m->cached_rindex_part && index >= m->cached_rindex_part_begin) { - part = m->cached_rindex_part; - begin = m->cached_rindex_part_begin; - } else { - part = &m->body; - begin = 0; - } - - while (part) { - if (index < begin) - return NULL; - - if (index + sz <= begin + part->size) { - - r = bus_body_part_map(part); - if (r < 0) - return NULL; - - if (p) - *p = (uint8_t*) part->data + index - begin; - - m->cached_rindex_part = part; - m->cached_rindex_part_begin = begin; - - return part; - } - - begin += part->size; - part = part->next; - } - - return NULL; -} - -static int container_next_item(sd_bus_message *m, struct bus_container *c, size_t *rindex) { - int r; - - assert(m); - assert(c); - assert(rindex); - - if (!BUS_MESSAGE_IS_GVARIANT(m)) - return 0; - - if (c->enclosing == SD_BUS_TYPE_ARRAY) { - int sz; - - sz = bus_gvariant_get_size(c->signature); - if (sz < 0) { - int alignment; - - if (c->offset_index+1 >= c->n_offsets) - goto end; - - /* Variable-size array */ - - alignment = bus_gvariant_get_alignment(c->signature); - assert(alignment > 0); - - *rindex = ALIGN_TO(c->offsets[c->offset_index], alignment); - c->item_size = c->offsets[c->offset_index+1] - *rindex; - } else { - - if (c->offset_index+1 >= (c->end-c->begin)/sz) - goto end; - - /* Fixed-size array */ - *rindex = c->begin + (c->offset_index+1) * sz; - c->item_size = sz; - } - - c->offset_index++; - - } else if (c->enclosing == 0 || - c->enclosing == SD_BUS_TYPE_STRUCT || - c->enclosing == SD_BUS_TYPE_DICT_ENTRY) { - - int alignment; - size_t n, j; - - if (c->offset_index+1 >= c->n_offsets) - goto end; - - r = signature_element_length(c->signature + c->index, &n); - if (r < 0) - return r; - - r = signature_element_length(c->signature + c->index + n, &j); - if (r < 0) - return r; - else { - char t[j+1]; - memcpy(t, c->signature + c->index + n, j); - t[j] = 0; - - alignment = bus_gvariant_get_alignment(t); - } - - assert(alignment > 0); - - *rindex = ALIGN_TO(c->offsets[c->offset_index], alignment); - c->item_size = c->offsets[c->offset_index+1] - *rindex; - - c->offset_index++; - - } else if (c->enclosing == SD_BUS_TYPE_VARIANT) - goto end; - else - assert_not_reached("Unknown container type"); - - return 0; - -end: - /* Reached the end */ - *rindex = c->end; - c->item_size = 0; - return 0; -} - - -static int message_peek_body( - sd_bus_message *m, - size_t *rindex, - size_t align, - size_t nbytes, - void **ret) { - - size_t k, start, end, padding; - struct bus_body_part *part; - uint8_t *q; - - assert(m); - assert(rindex); - assert(align > 0); - - start = ALIGN_TO((size_t) *rindex, align); - padding = start - *rindex; - end = start + nbytes; - - if (end > m->user_body_size) - return -EBADMSG; - - part = find_part(m, *rindex, padding, (void**) &q); - if (!part) - return -EBADMSG; - - if (q) { - /* Verify padding */ - for (k = 0; k < padding; k++) - if (q[k] != 0) - return -EBADMSG; - } - - part = find_part(m, start, nbytes, (void**) &q); - if (!part || (nbytes > 0 && !q)) - return -EBADMSG; - - *rindex = end; - - if (ret) - *ret = q; - - return 0; -} - -static bool validate_nul(const char *s, size_t l) { - - /* Check for NUL chars in the string */ - if (memchr(s, 0, l)) - return false; - - /* Check for NUL termination */ - if (s[l] != 0) - return false; - - return true; -} - -static bool validate_string(const char *s, size_t l) { - - if (!validate_nul(s, l)) - return false; - - /* Check if valid UTF8 */ - if (!utf8_is_valid(s)) - return false; - - return true; -} - -static bool validate_signature(const char *s, size_t l) { - - if (!validate_nul(s, l)) - return false; - - /* Check if valid signature */ - if (!signature_is_valid(s, true)) - return false; - - return true; -} - -static bool validate_object_path(const char *s, size_t l) { - - if (!validate_nul(s, l)) - return false; - - if (!object_path_is_valid(s)) - return false; - - return true; -} - -_public_ int sd_bus_message_read_basic(sd_bus_message *m, char type, void *p) { - struct bus_container *c; - size_t rindex; - void *q; - int r; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - assert_return(bus_type_is_basic(type), -EINVAL); - - if (message_end_of_signature(m)) - return -ENXIO; - - if (message_end_of_array(m, m->rindex)) - return 0; - - c = message_get_container(m); - if (c->signature[c->index] != type) - return -ENXIO; - - rindex = m->rindex; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - - if (IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE)) { - bool ok; - - r = message_peek_body(m, &rindex, 1, c->item_size, &q); - if (r < 0) - return r; - - if (type == SD_BUS_TYPE_STRING) - ok = validate_string(q, c->item_size-1); - else if (type == SD_BUS_TYPE_OBJECT_PATH) - ok = validate_object_path(q, c->item_size-1); - else - ok = validate_signature(q, c->item_size-1); - - if (!ok) - return -EBADMSG; - - if (p) - *(const char**) p = q; - } else { - int sz, align; - - sz = bus_gvariant_get_size(CHAR_TO_STR(type)); - assert(sz > 0); - if ((size_t) sz != c->item_size) - return -EBADMSG; - - align = bus_gvariant_get_alignment(CHAR_TO_STR(type)); - assert(align > 0); - - r = message_peek_body(m, &rindex, align, c->item_size, &q); - if (r < 0) - return r; - - switch (type) { - - case SD_BUS_TYPE_BYTE: - if (p) - *(uint8_t*) p = *(uint8_t*) q; - break; - - case SD_BUS_TYPE_BOOLEAN: - if (p) - *(int*) p = !!*(uint8_t*) q; - break; - - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: - if (p) - *(uint16_t*) p = BUS_MESSAGE_BSWAP16(m, *(uint16_t*) q); - break; - - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - if (p) - *(uint32_t*) p = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); - break; - - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: - case SD_BUS_TYPE_DOUBLE: - if (p) - *(uint64_t*) p = BUS_MESSAGE_BSWAP64(m, *(uint64_t*) q); - break; - - case SD_BUS_TYPE_UNIX_FD: { - uint32_t j; - - j = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); - if (j >= m->n_fds) - return -EBADMSG; - - if (p) - *(int*) p = m->fds[j]; - - break; - } - - default: - assert_not_reached("unexpected type"); - } - } - - r = container_next_item(m, c, &rindex); - if (r < 0) - return r; - } else { - - if (IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH)) { - uint32_t l; - bool ok; - - r = message_peek_body(m, &rindex, 4, 4, &q); - if (r < 0) - return r; - - l = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); - r = message_peek_body(m, &rindex, 1, l+1, &q); - if (r < 0) - return r; - - if (type == SD_BUS_TYPE_OBJECT_PATH) - ok = validate_object_path(q, l); - else - ok = validate_string(q, l); - if (!ok) - return -EBADMSG; - - if (p) - *(const char**) p = q; - - } else if (type == SD_BUS_TYPE_SIGNATURE) { - uint8_t l; - - r = message_peek_body(m, &rindex, 1, 1, &q); - if (r < 0) - return r; - - l = *(uint8_t*) q; - r = message_peek_body(m, &rindex, 1, l+1, &q); - if (r < 0) - return r; - - if (!validate_signature(q, l)) - return -EBADMSG; - - if (p) - *(const char**) p = q; - - } else { - ssize_t sz, align; - - align = bus_type_get_alignment(type); - assert(align > 0); - - sz = bus_type_get_size(type); - assert(sz > 0); - - r = message_peek_body(m, &rindex, align, sz, &q); - if (r < 0) - return r; - - switch (type) { - - case SD_BUS_TYPE_BYTE: - if (p) - *(uint8_t*) p = *(uint8_t*) q; - break; - - case SD_BUS_TYPE_BOOLEAN: - if (p) - *(int*) p = !!*(uint32_t*) q; - break; - - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: - if (p) - *(uint16_t*) p = BUS_MESSAGE_BSWAP16(m, *(uint16_t*) q); - break; - - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - if (p) - *(uint32_t*) p = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); - break; - - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: - case SD_BUS_TYPE_DOUBLE: - if (p) - *(uint64_t*) p = BUS_MESSAGE_BSWAP64(m, *(uint64_t*) q); - break; - - case SD_BUS_TYPE_UNIX_FD: { - uint32_t j; - - j = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); - if (j >= m->n_fds) - return -EBADMSG; - - if (p) - *(int*) p = m->fds[j]; - break; - } - - default: - assert_not_reached("Unknown basic type..."); - } - } - } - - m->rindex = rindex; - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index++; - - return 1; -} - -static int bus_message_enter_array( - sd_bus_message *m, - struct bus_container *c, - const char *contents, - uint32_t **array_size, - size_t *item_size, - size_t **offsets, - size_t *n_offsets) { - - size_t rindex; - void *q; - int r, alignment; - - assert(m); - assert(c); - assert(contents); - assert(array_size); - assert(item_size); - assert(offsets); - assert(n_offsets); - - if (!signature_is_single(contents, true)) - return -EINVAL; - - if (!c->signature || c->signature[c->index] == 0) - return -ENXIO; - - if (c->signature[c->index] != SD_BUS_TYPE_ARRAY) - return -ENXIO; - - if (!startswith(c->signature + c->index + 1, contents)) - return -ENXIO; - - rindex = m->rindex; - - if (!BUS_MESSAGE_IS_GVARIANT(m)) { - /* dbus1 */ - - r = message_peek_body(m, &rindex, 4, 4, &q); - if (r < 0) - return r; - - if (BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q) > BUS_ARRAY_MAX_SIZE) - return -EBADMSG; - - alignment = bus_type_get_alignment(contents[0]); - if (alignment < 0) - return alignment; - - r = message_peek_body(m, &rindex, alignment, 0, NULL); - if (r < 0) - return r; - - *array_size = (uint32_t*) q; - - } else if (c->item_size <= 0) { - - /* gvariant: empty array */ - *item_size = 0; - *offsets = NULL; - *n_offsets = 0; - - } else if (bus_gvariant_is_fixed_size(contents)) { - - /* gvariant: fixed length array */ - *item_size = bus_gvariant_get_size(contents); - *offsets = NULL; - *n_offsets = 0; - - } else { - size_t where, p = 0, framing, sz; - unsigned i; - - /* gvariant: variable length array */ - sz = bus_gvariant_determine_word_size(c->item_size, 0); - - where = rindex + c->item_size - sz; - r = message_peek_body(m, &where, 1, sz, &q); - if (r < 0) - return r; - - framing = bus_gvariant_read_word_le(q, sz); - if (framing > c->item_size - sz) - return -EBADMSG; - if ((c->item_size - framing) % sz != 0) - return -EBADMSG; - - *n_offsets = (c->item_size - framing) / sz; - - where = rindex + framing; - r = message_peek_body(m, &where, 1, *n_offsets * sz, &q); - if (r < 0) - return r; - - *offsets = new(size_t, *n_offsets); - if (!*offsets) - return -ENOMEM; - - for (i = 0; i < *n_offsets; i++) { - size_t x; - - x = bus_gvariant_read_word_le((uint8_t*) q + i * sz, sz); - if (x > c->item_size - sz) - return -EBADMSG; - if (x < p) - return -EBADMSG; - - (*offsets)[i] = rindex + x; - p = x; - } - - *item_size = (*offsets)[0] - rindex; - } - - m->rindex = rindex; - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index += 1 + strlen(contents); - - return 1; -} - -static int bus_message_enter_variant( - sd_bus_message *m, - struct bus_container *c, - const char *contents, - size_t *item_size) { - - size_t rindex; - uint8_t l; - void *q; - int r; - - assert(m); - assert(c); - assert(contents); - assert(item_size); - - if (!signature_is_single(contents, false)) - return -EINVAL; - - if (*contents == SD_BUS_TYPE_DICT_ENTRY_BEGIN) - return -EINVAL; - - if (!c->signature || c->signature[c->index] == 0) - return -ENXIO; - - if (c->signature[c->index] != SD_BUS_TYPE_VARIANT) - return -ENXIO; - - rindex = m->rindex; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - size_t k, where; - - k = strlen(contents); - if (1+k > c->item_size) - return -EBADMSG; - - where = rindex + c->item_size - (1+k); - r = message_peek_body(m, &where, 1, 1+k, &q); - if (r < 0) - return r; - - if (*(char*) q != 0) - return -EBADMSG; - - if (memcmp((uint8_t*) q+1, contents, k)) - return -ENXIO; - - *item_size = c->item_size - (1+k); - - } else { - r = message_peek_body(m, &rindex, 1, 1, &q); - if (r < 0) - return r; - - l = *(uint8_t*) q; - r = message_peek_body(m, &rindex, 1, l+1, &q); - if (r < 0) - return r; - - if (!validate_signature(q, l)) - return -EBADMSG; - - if (!streq(q, contents)) - return -ENXIO; - } - - m->rindex = rindex; - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index++; - - return 1; -} - -static int build_struct_offsets( - sd_bus_message *m, - const char *signature, - size_t size, - size_t *item_size, - size_t **offsets, - size_t *n_offsets) { - - unsigned n_variable = 0, n_total = 0, v; - size_t previous = 0, where; - const char *p; - size_t sz; - void *q; - int r; - - assert(m); - assert(item_size); - assert(offsets); - assert(n_offsets); - - if (isempty(signature)) { - /* Unary type is encoded as *fixed* 1 byte padding */ - r = message_peek_body(m, &m->rindex, 1, 1, &q); - if (r < 0) - return r; - - if (*(uint8_t *) q != 0) - return -EBADMSG; - - *item_size = 0; - *offsets = NULL; - *n_offsets = 0; - return 0; - } - - sz = bus_gvariant_determine_word_size(size, 0); - if (sz <= 0) - return -EBADMSG; - - /* First, loop over signature and count variable elements and - * elements in general. We use this to know how large the - * offset array is at the end of the structure. Note that - * GVariant only stores offsets for all variable size elements - * that are not the last item. */ - - p = signature; - while (*p != 0) { - size_t n; - - r = signature_element_length(p, &n); - if (r < 0) - return r; - else { - char t[n+1]; - - memcpy(t, p, n); - t[n] = 0; - - r = bus_gvariant_is_fixed_size(t); - } - - if (r < 0) - return r; - if (r == 0 && p[n] != 0) /* except the last item */ - n_variable++; - n_total++; - - p += n; - } - - if (size < n_variable * sz) - return -EBADMSG; - - where = m->rindex + size - (n_variable * sz); - r = message_peek_body(m, &where, 1, n_variable * sz, &q); - if (r < 0) - return r; - - v = n_variable; - - *offsets = new(size_t, n_total); - if (!*offsets) - return -ENOMEM; - - *n_offsets = 0; - - /* Second, loop again and build an offset table */ - p = signature; - while (*p != 0) { - size_t n, offset; - int k; - - r = signature_element_length(p, &n); - if (r < 0) - return r; - else { - char t[n+1]; - - memcpy(t, p, n); - t[n] = 0; - - k = bus_gvariant_get_size(t); - if (k < 0) { - size_t x; - - /* variable size */ - if (v > 0) { - v--; - - x = bus_gvariant_read_word_le((uint8_t*) q + v*sz, sz); - if (x >= size) - return -EBADMSG; - if (m->rindex + x < previous) - return -EBADMSG; - } else - /* The last item's end - * is determined from - * the start of the - * offset array */ - x = size - (n_variable * sz); - - offset = m->rindex + x; - - } else { - size_t align; - - /* fixed size */ - align = bus_gvariant_get_alignment(t); - assert(align > 0); - - offset = (*n_offsets == 0 ? m->rindex : ALIGN_TO((*offsets)[*n_offsets-1], align)) + k; - } - } - - previous = (*offsets)[(*n_offsets)++] = offset; - p += n; - } - - assert(v == 0); - assert(*n_offsets == n_total); - - *item_size = (*offsets)[0] - m->rindex; - return 0; -} - -static int enter_struct_or_dict_entry( - sd_bus_message *m, - struct bus_container *c, - const char *contents, - size_t *item_size, - size_t **offsets, - size_t *n_offsets) { - - int r; - - assert(m); - assert(c); - assert(contents); - assert(item_size); - assert(offsets); - assert(n_offsets); - - if (!BUS_MESSAGE_IS_GVARIANT(m)) { - - /* dbus1 */ - r = message_peek_body(m, &m->rindex, 8, 0, NULL); - if (r < 0) - return r; - - } else - /* gvariant with contents */ - return build_struct_offsets(m, contents, c->item_size, item_size, offsets, n_offsets); - - return 0; -} - -static int bus_message_enter_struct( - sd_bus_message *m, - struct bus_container *c, - const char *contents, - size_t *item_size, - size_t **offsets, - size_t *n_offsets) { - - size_t l; - int r; - - assert(m); - assert(c); - assert(contents); - assert(item_size); - assert(offsets); - assert(n_offsets); - - if (!signature_is_valid(contents, false)) - return -EINVAL; - - if (!c->signature || c->signature[c->index] == 0) - return -ENXIO; - - l = strlen(contents); - - if (c->signature[c->index] != SD_BUS_TYPE_STRUCT_BEGIN || - !startswith(c->signature + c->index + 1, contents) || - c->signature[c->index + 1 + l] != SD_BUS_TYPE_STRUCT_END) - return -ENXIO; - - r = enter_struct_or_dict_entry(m, c, contents, item_size, offsets, n_offsets); - if (r < 0) - return r; - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index += 1 + l + 1; - - return 1; -} - -static int bus_message_enter_dict_entry( - sd_bus_message *m, - struct bus_container *c, - const char *contents, - size_t *item_size, - size_t **offsets, - size_t *n_offsets) { - - size_t l; - int r; - - assert(m); - assert(c); - assert(contents); - - if (!signature_is_pair(contents)) - return -EINVAL; - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - return -ENXIO; - - if (!c->signature || c->signature[c->index] == 0) - return 0; - - l = strlen(contents); - - if (c->signature[c->index] != SD_BUS_TYPE_DICT_ENTRY_BEGIN || - !startswith(c->signature + c->index + 1, contents) || - c->signature[c->index + 1 + l] != SD_BUS_TYPE_DICT_ENTRY_END) - return -ENXIO; - - r = enter_struct_or_dict_entry(m, c, contents, item_size, offsets, n_offsets); - if (r < 0) - return r; - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index += 1 + l + 1; - - return 1; -} - -_public_ int sd_bus_message_enter_container(sd_bus_message *m, - char type, - const char *contents) { - struct bus_container *c, *w; - uint32_t *array_size = NULL; - char *signature; - size_t before; - size_t *offsets = NULL; - size_t n_offsets = 0, item_size = 0; - int r; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - assert_return(type != 0 || !contents, -EINVAL); - - if (type == 0 || !contents) { - const char *cc; - char tt; - - /* Allow entering into anonymous containers */ - r = sd_bus_message_peek_type(m, &tt, &cc); - if (r < 0) - return r; - - if (type != 0 && type != tt) - return -ENXIO; - - if (contents && !streq(contents, cc)) - return -ENXIO; - - type = tt; - contents = cc; - } - - /* - * We enforce a global limit on container depth, that is much - * higher than the 32 structs and 32 arrays the specification - * mandates. This is simpler to implement for us, and we need - * this only to ensure our container array doesn't grow - * without bounds. We are happy to return any data from a - * message as long as the data itself is valid, even if the - * overall message might be not. - * - * Note that the message signature is validated when - * parsing the headers, and that validation does check the - * 32/32 limit. - * - * Note that the specification defines no limits on the depth - * of stacked variants, but we do. - */ - if (m->n_containers >= BUS_CONTAINER_DEPTH) - return -EBADMSG; - - if (!GREEDY_REALLOC(m->containers, m->containers_allocated, m->n_containers + 1)) - return -ENOMEM; - - if (message_end_of_signature(m)) - return -ENXIO; - - if (message_end_of_array(m, m->rindex)) - return 0; - - c = message_get_container(m); - - signature = strdup(contents); - if (!signature) - return -ENOMEM; - - c->saved_index = c->index; - before = m->rindex; - - if (type == SD_BUS_TYPE_ARRAY) - r = bus_message_enter_array(m, c, contents, &array_size, &item_size, &offsets, &n_offsets); - else if (type == SD_BUS_TYPE_VARIANT) - r = bus_message_enter_variant(m, c, contents, &item_size); - else if (type == SD_BUS_TYPE_STRUCT) - r = bus_message_enter_struct(m, c, contents, &item_size, &offsets, &n_offsets); - else if (type == SD_BUS_TYPE_DICT_ENTRY) - r = bus_message_enter_dict_entry(m, c, contents, &item_size, &offsets, &n_offsets); - else - r = -EINVAL; - - if (r <= 0) { - free(signature); - free(offsets); - return r; - } - - /* OK, let's fill it in */ - w = m->containers + m->n_containers++; - w->enclosing = type; - w->signature = signature; - w->peeked_signature = NULL; - w->index = 0; - - w->before = before; - w->begin = m->rindex; - - /* Unary type has fixed size of 1, but virtual size of 0 */ - if (BUS_MESSAGE_IS_GVARIANT(m) && - type == SD_BUS_TYPE_STRUCT && - isempty(signature)) - w->end = m->rindex + 0; - else - w->end = m->rindex + c->item_size; - - w->array_size = array_size; - w->item_size = item_size; - w->offsets = offsets; - w->n_offsets = n_offsets; - w->offset_index = 0; - - return 1; -} - -_public_ int sd_bus_message_exit_container(sd_bus_message *m) { - struct bus_container *c; - unsigned saved; - int r; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - assert_return(m->n_containers > 0, -ENXIO); - - c = message_get_container(m); - - if (c->enclosing != SD_BUS_TYPE_ARRAY) { - if (c->signature && c->signature[c->index] != 0) - return -EBUSY; - } - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - if (m->rindex < c->end) - return -EBUSY; - - } else if (c->enclosing == SD_BUS_TYPE_ARRAY) { - uint32_t l; - - l = BUS_MESSAGE_BSWAP32(m, *c->array_size); - if (c->begin + l != m->rindex) - return -EBUSY; - } - - free(c->signature); - free(c->peeked_signature); - free(c->offsets); - m->n_containers--; - - c = message_get_container(m); - - saved = c->index; - c->index = c->saved_index; - r = container_next_item(m, c, &m->rindex); - c->index = saved; - if (r < 0) - return r; - - return 1; -} - -static void message_quit_container(sd_bus_message *m) { - struct bus_container *c; - - assert(m); - assert(m->sealed); - assert(m->n_containers > 0); - - c = message_get_container(m); - - /* Undo seeks */ - assert(m->rindex >= c->before); - m->rindex = c->before; - - /* Free container */ - free(c->signature); - free(c->offsets); - m->n_containers--; - - /* Correct index of new top-level container */ - c = message_get_container(m); - c->index = c->saved_index; -} - -_public_ int sd_bus_message_peek_type(sd_bus_message *m, char *type, const char **contents) { - struct bus_container *c; - int r; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - - if (message_end_of_signature(m)) - goto eof; - - if (message_end_of_array(m, m->rindex)) - goto eof; - - c = message_get_container(m); - - if (bus_type_is_basic(c->signature[c->index])) { - if (contents) - *contents = NULL; - if (type) - *type = c->signature[c->index]; - return 1; - } - - if (c->signature[c->index] == SD_BUS_TYPE_ARRAY) { - - if (contents) { - size_t l; - char *sig; - - r = signature_element_length(c->signature+c->index+1, &l); - if (r < 0) - return r; - - assert(l >= 1); - - sig = strndup(c->signature + c->index + 1, l); - if (!sig) - return -ENOMEM; - - free(c->peeked_signature); - *contents = c->peeked_signature = sig; - } - - if (type) - *type = SD_BUS_TYPE_ARRAY; - - return 1; - } - - if (c->signature[c->index] == SD_BUS_TYPE_STRUCT_BEGIN || - c->signature[c->index] == SD_BUS_TYPE_DICT_ENTRY_BEGIN) { - - if (contents) { - size_t l; - char *sig; - - r = signature_element_length(c->signature+c->index, &l); - if (r < 0) - return r; - - assert(l >= 2); - sig = strndup(c->signature + c->index + 1, l - 2); - if (!sig) - return -ENOMEM; - - free(c->peeked_signature); - *contents = c->peeked_signature = sig; - } - - if (type) - *type = c->signature[c->index] == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY; - - return 1; - } - - if (c->signature[c->index] == SD_BUS_TYPE_VARIANT) { - if (contents) { - void *q; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - size_t k; - - if (c->item_size < 2) - return -EBADMSG; - - /* Look for the NUL delimiter that - separates the payload from the - signature. Since the body might be - in a different part that then the - signature we map byte by byte. */ - - for (k = 2; k <= c->item_size; k++) { - size_t where; - - where = m->rindex + c->item_size - k; - r = message_peek_body(m, &where, 1, k, &q); - if (r < 0) - return r; - - if (*(char*) q == 0) - break; - } - - if (k > c->item_size) - return -EBADMSG; - - free(c->peeked_signature); - c->peeked_signature = strndup((char*) q + 1, k - 1); - if (!c->peeked_signature) - return -ENOMEM; - - if (!signature_is_valid(c->peeked_signature, true)) - return -EBADMSG; - - *contents = c->peeked_signature; - } else { - size_t rindex, l; - - rindex = m->rindex; - r = message_peek_body(m, &rindex, 1, 1, &q); - if (r < 0) - return r; - - l = *(uint8_t*) q; - r = message_peek_body(m, &rindex, 1, l+1, &q); - if (r < 0) - return r; - - if (!validate_signature(q, l)) - return -EBADMSG; - - *contents = q; - } - } - - if (type) - *type = SD_BUS_TYPE_VARIANT; - - return 1; - } - - return -EINVAL; - -eof: - if (type) - *type = 0; - if (contents) - *contents = NULL; - return 0; -} - -_public_ int sd_bus_message_rewind(sd_bus_message *m, int complete) { - struct bus_container *c; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - - if (complete) { - message_reset_containers(m); - m->rindex = 0; - - c = message_get_container(m); - } else { - c = message_get_container(m); - - c->offset_index = 0; - c->index = 0; - m->rindex = c->begin; - } - - c->offset_index = 0; - c->item_size = (c->n_offsets > 0 ? c->offsets[0] : c->end) - c->begin; - - return !isempty(c->signature); -} - -static int message_read_ap( - sd_bus_message *m, - const char *types, - va_list ap) { - - unsigned n_array, n_struct; - TypeStack stack[BUS_CONTAINER_DEPTH]; - unsigned stack_ptr = 0; - unsigned n_loop = 0; - int r; - - assert(m); - - if (isempty(types)) - return 0; - - /* Ideally, we'd just call ourselves recursively on every - * complex type. However, the state of a va_list that is - * passed to a function is undefined after that function - * returns. This means we need to docode the va_list linearly - * in a single stackframe. We hence implement our own - * home-grown stack in an array. */ - - n_array = (unsigned) -1; /* length of current array entries */ - n_struct = strlen(types); /* length of current struct contents signature */ - - for (;;) { - const char *t; - - n_loop++; - - if (n_array == 0 || (n_array == (unsigned) -1 && n_struct == 0)) { - r = type_stack_pop(stack, ELEMENTSOF(stack), &stack_ptr, &types, &n_struct, &n_array); - if (r < 0) - return r; - if (r == 0) - break; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - continue; - } - - t = types; - if (n_array != (unsigned) -1) - n_array--; - else { - types++; - n_struct--; - } - - switch (*t) { - - case SD_BUS_TYPE_BYTE: - case SD_BUS_TYPE_BOOLEAN: - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: - case SD_BUS_TYPE_DOUBLE: - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_SIGNATURE: - case SD_BUS_TYPE_UNIX_FD: { - void *p; - - p = va_arg(ap, void*); - r = sd_bus_message_read_basic(m, *t, p); - if (r < 0) - return r; - if (r == 0) { - if (n_loop <= 1) - return 0; - - return -ENXIO; - } - - break; - } - - case SD_BUS_TYPE_ARRAY: { - size_t k; - - r = signature_element_length(t + 1, &k); - if (r < 0) - return r; - - { - char s[k + 1]; - memcpy(s, t + 1, k); - s[k] = 0; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, s); - if (r < 0) - return r; - if (r == 0) { - if (n_loop <= 1) - return 0; - - return -ENXIO; - } - } - - if (n_array == (unsigned) -1) { - types += k; - n_struct -= k; - } - - r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); - if (r < 0) - return r; - - types = t + 1; - n_struct = k; - n_array = va_arg(ap, unsigned); - - break; - } - - case SD_BUS_TYPE_VARIANT: { - const char *s; - - s = va_arg(ap, const char *); - if (!s) - return -EINVAL; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, s); - if (r < 0) - return r; - if (r == 0) { - if (n_loop <= 1) - return 0; - - return -ENXIO; - } - - r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); - if (r < 0) - return r; - - types = s; - n_struct = strlen(s); - n_array = (unsigned) -1; - - break; - } - - case SD_BUS_TYPE_STRUCT_BEGIN: - case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { - size_t k; - - r = signature_element_length(t, &k); - if (r < 0) - return r; - - { - char s[k - 1]; - memcpy(s, t + 1, k - 2); - s[k - 2] = 0; - - r = sd_bus_message_enter_container(m, *t == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s); - if (r < 0) - return r; - if (r == 0) { - if (n_loop <= 1) - return 0; - return -ENXIO; - } - } - - if (n_array == (unsigned) -1) { - types += k - 1; - n_struct -= k - 1; - } - - r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); - if (r < 0) - return r; - - types = t + 1; - n_struct = k - 2; - n_array = (unsigned) -1; - - break; - } - - default: - return -EINVAL; - } - } - - return 1; -} - -_public_ int sd_bus_message_read(sd_bus_message *m, const char *types, ...) { - va_list ap; - int r; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - assert_return(types, -EINVAL); - - va_start(ap, types); - r = message_read_ap(m, types, ap); - va_end(ap); - - return r; -} - -_public_ int sd_bus_message_skip(sd_bus_message *m, const char *types) { - int r; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - - /* If types is NULL, read exactly one element */ - if (!types) { - struct bus_container *c; - size_t l; - - if (message_end_of_signature(m)) - return -ENXIO; - - if (message_end_of_array(m, m->rindex)) - return 0; - - c = message_get_container(m); - - r = signature_element_length(c->signature + c->index, &l); - if (r < 0) - return r; - - types = strndupa(c->signature + c->index, l); - } - - switch (*types) { - - case 0: /* Nothing to drop */ - return 0; - - case SD_BUS_TYPE_BYTE: - case SD_BUS_TYPE_BOOLEAN: - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: - case SD_BUS_TYPE_DOUBLE: - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_SIGNATURE: - case SD_BUS_TYPE_UNIX_FD: - - r = sd_bus_message_read_basic(m, *types, NULL); - if (r <= 0) - return r; - - r = sd_bus_message_skip(m, types + 1); - if (r < 0) - return r; - - return 1; - - case SD_BUS_TYPE_ARRAY: { - size_t k; - - r = signature_element_length(types + 1, &k); - if (r < 0) - return r; - - { - char s[k+1]; - memcpy(s, types+1, k); - s[k] = 0; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, s); - if (r <= 0) - return r; - - for (;;) { - r = sd_bus_message_skip(m, s); - if (r < 0) - return r; - if (r == 0) - break; - } - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - } - - r = sd_bus_message_skip(m, types + 1 + k); - if (r < 0) - return r; - - return 1; - } - - case SD_BUS_TYPE_VARIANT: { - const char *contents; - char x; - - r = sd_bus_message_peek_type(m, &x, &contents); - if (r <= 0) - return r; - - if (x != SD_BUS_TYPE_VARIANT) - return -ENXIO; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, contents); - if (r <= 0) - return r; - - r = sd_bus_message_skip(m, contents); - if (r < 0) - return r; - assert(r != 0); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - r = sd_bus_message_skip(m, types + 1); - if (r < 0) - return r; - - return 1; - } - - case SD_BUS_TYPE_STRUCT_BEGIN: - case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { - size_t k; - - r = signature_element_length(types, &k); - if (r < 0) - return r; - - { - char s[k-1]; - memcpy(s, types+1, k-2); - s[k-2] = 0; - - r = sd_bus_message_enter_container(m, *types == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s); - if (r <= 0) - return r; - - r = sd_bus_message_skip(m, s); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - } - - r = sd_bus_message_skip(m, types + k); - if (r < 0) - return r; - - return 1; - } - - default: - return -EINVAL; - } -} - -_public_ int sd_bus_message_read_array( - sd_bus_message *m, - char type, - const void **ptr, - size_t *size) { - - struct bus_container *c; - void *p; - size_t sz; - ssize_t align; - int r; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - assert_return(bus_type_is_trivial(type), -EINVAL); - assert_return(ptr, -EINVAL); - assert_return(size, -EINVAL); - assert_return(!BUS_MESSAGE_NEED_BSWAP(m), -EOPNOTSUPP); - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, CHAR_TO_STR(type)); - if (r <= 0) - return r; - - c = message_get_container(m); - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - align = bus_gvariant_get_alignment(CHAR_TO_STR(type)); - if (align < 0) - return align; - - sz = c->end - c->begin; - } else { - align = bus_type_get_alignment(type); - if (align < 0) - return align; - - sz = BUS_MESSAGE_BSWAP32(m, *c->array_size); - } - - if (sz == 0) - /* Zero length array, let's return some aligned - * pointer that is not NULL */ - p = (uint8_t*) NULL + align; - else { - r = message_peek_body(m, &m->rindex, align, sz, &p); - if (r < 0) - goto fail; - } - - r = sd_bus_message_exit_container(m); - if (r < 0) - goto fail; - - *ptr = (const void*) p; - *size = sz; - - return 1; - -fail: - message_quit_container(m); - return r; -} - -static int message_peek_fields( - sd_bus_message *m, - size_t *rindex, - size_t align, - size_t nbytes, - void **ret) { - - assert(m); - assert(rindex); - assert(align > 0); - - return buffer_peek(BUS_MESSAGE_FIELDS(m), m->fields_size, rindex, align, nbytes, ret); -} - -static int message_peek_field_uint32( - sd_bus_message *m, - size_t *ri, - size_t item_size, - uint32_t *ret) { - - int r; - void *q; - - assert(m); - assert(ri); - - if (BUS_MESSAGE_IS_GVARIANT(m) && item_size != 4) - return -EBADMSG; - - /* identical for gvariant and dbus1 */ - - r = message_peek_fields(m, ri, 4, 4, &q); - if (r < 0) - return r; - - if (ret) - *ret = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); - - return 0; -} - -static int message_peek_field_uint64( - sd_bus_message *m, - size_t *ri, - size_t item_size, - uint64_t *ret) { - - int r; - void *q; - - assert(m); - assert(ri); - - if (BUS_MESSAGE_IS_GVARIANT(m) && item_size != 8) - return -EBADMSG; - - /* identical for gvariant and dbus1 */ - - r = message_peek_fields(m, ri, 8, 8, &q); - if (r < 0) - return r; - - if (ret) - *ret = BUS_MESSAGE_BSWAP64(m, *(uint64_t*) q); - - return 0; -} - -static int message_peek_field_string( - sd_bus_message *m, - bool (*validate)(const char *p), - size_t *ri, - size_t item_size, - const char **ret) { - - uint32_t l; - int r; - void *q; - - assert(m); - assert(ri); - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - - if (item_size <= 0) - return -EBADMSG; - - r = message_peek_fields(m, ri, 1, item_size, &q); - if (r < 0) - return r; - - l = item_size - 1; - } else { - r = message_peek_field_uint32(m, ri, 4, &l); - if (r < 0) - return r; - - r = message_peek_fields(m, ri, 1, l+1, &q); - if (r < 0) - return r; - } - - if (validate) { - if (!validate_nul(q, l)) - return -EBADMSG; - - if (!validate(q)) - return -EBADMSG; - } else { - if (!validate_string(q, l)) - return -EBADMSG; - } - - if (ret) - *ret = q; - - return 0; -} - -static int message_peek_field_signature( - sd_bus_message *m, - size_t *ri, - size_t item_size, - const char **ret) { - - size_t l; - int r; - void *q; - - assert(m); - assert(ri); - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - - if (item_size <= 0) - return -EBADMSG; - - r = message_peek_fields(m, ri, 1, item_size, &q); - if (r < 0) - return r; - - l = item_size - 1; - } else { - r = message_peek_fields(m, ri, 1, 1, &q); - if (r < 0) - return r; - - l = *(uint8_t*) q; - r = message_peek_fields(m, ri, 1, l+1, &q); - if (r < 0) - return r; - } - - if (!validate_signature(q, l)) - return -EBADMSG; - - if (ret) - *ret = q; - - return 0; -} - -static int message_skip_fields( - sd_bus_message *m, - size_t *ri, - uint32_t array_size, - const char **signature) { - - size_t original_index; - int r; - - assert(m); - assert(ri); - assert(signature); - assert(!BUS_MESSAGE_IS_GVARIANT(m)); - - original_index = *ri; - - for (;;) { - char t; - size_t l; - - if (array_size != (uint32_t) -1 && - array_size <= *ri - original_index) - return 0; - - t = **signature; - if (!t) - return 0; - - if (t == SD_BUS_TYPE_STRING) { - - r = message_peek_field_string(m, NULL, ri, 0, NULL); - if (r < 0) - return r; - - (*signature)++; - - } else if (t == SD_BUS_TYPE_OBJECT_PATH) { - - r = message_peek_field_string(m, object_path_is_valid, ri, 0, NULL); - if (r < 0) - return r; - - (*signature)++; - - } else if (t == SD_BUS_TYPE_SIGNATURE) { - - r = message_peek_field_signature(m, ri, 0, NULL); - if (r < 0) - return r; - - (*signature)++; - - } else if (bus_type_is_basic(t)) { - ssize_t align, k; - - align = bus_type_get_alignment(t); - k = bus_type_get_size(t); - assert(align > 0 && k > 0); - - r = message_peek_fields(m, ri, align, k, NULL); - if (r < 0) - return r; - - (*signature)++; - - } else if (t == SD_BUS_TYPE_ARRAY) { - - r = signature_element_length(*signature+1, &l); - if (r < 0) - return r; - - assert(l >= 1); - { - char sig[l-1], *s; - uint32_t nas; - int alignment; - - strncpy(sig, *signature + 1, l-1); - s = sig; - - alignment = bus_type_get_alignment(sig[0]); - if (alignment < 0) - return alignment; - - r = message_peek_field_uint32(m, ri, 0, &nas); - if (r < 0) - return r; - if (nas > BUS_ARRAY_MAX_SIZE) - return -EBADMSG; - - r = message_peek_fields(m, ri, alignment, 0, NULL); - if (r < 0) - return r; - - r = message_skip_fields(m, ri, nas, (const char**) &s); - if (r < 0) - return r; - } - - (*signature) += 1 + l; - - } else if (t == SD_BUS_TYPE_VARIANT) { - const char *s; - - r = message_peek_field_signature(m, ri, 0, &s); - if (r < 0) - return r; - - r = message_skip_fields(m, ri, (uint32_t) -1, (const char**) &s); - if (r < 0) - return r; - - (*signature)++; - - } else if (t == SD_BUS_TYPE_STRUCT || - t == SD_BUS_TYPE_DICT_ENTRY) { - - r = signature_element_length(*signature, &l); - if (r < 0) - return r; - - assert(l >= 2); - { - char sig[l-1], *s; - strncpy(sig, *signature + 1, l-1); - s = sig; - - r = message_skip_fields(m, ri, (uint32_t) -1, (const char**) &s); - if (r < 0) - return r; - } - - *signature += l; - } else - return -EINVAL; - } -} - -int bus_message_parse_fields(sd_bus_message *m) { - size_t ri; - int r; - uint32_t unix_fds = 0; - bool unix_fds_set = false; - void *offsets = NULL; - unsigned n_offsets = 0; - size_t sz = 0; - unsigned i = 0; - - assert(m); - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - char *p; - - /* Read the signature from the end of the body variant first */ - sz = bus_gvariant_determine_word_size(BUS_MESSAGE_SIZE(m), 0); - if (m->footer_accessible < 1 + sz) - return -EBADMSG; - - p = (char*) m->footer + m->footer_accessible - (1 + sz); - for (;;) { - if (p < (char*) m->footer) - return -EBADMSG; - - if (*p == 0) { - size_t l; - char *c; - - /* We found the beginning of the signature - * string, yay! We require the body to be a - * structure, so verify it and then strip the - * opening/closing brackets. */ - - l = ((char*) m->footer + m->footer_accessible) - p - (1 + sz); - if (l < 2 || - p[1] != SD_BUS_TYPE_STRUCT_BEGIN || - p[1 + l - 1] != SD_BUS_TYPE_STRUCT_END) - return -EBADMSG; - - c = strndup(p + 1 + 1, l - 2); - if (!c) - return -ENOMEM; - - free(m->root_container.signature); - m->root_container.signature = c; - break; - } - - p--; - } - - /* Calculate the actual user body size, by removing - * the trailing variant signature and struct offset - * table */ - m->user_body_size = m->body_size - ((char*) m->footer + m->footer_accessible - p); - - /* Pull out the offset table for the fields array */ - sz = bus_gvariant_determine_word_size(m->fields_size, 0); - if (sz > 0) { - size_t framing; - void *q; - - ri = m->fields_size - sz; - r = message_peek_fields(m, &ri, 1, sz, &q); - if (r < 0) - return r; - - framing = bus_gvariant_read_word_le(q, sz); - if (framing >= m->fields_size - sz) - return -EBADMSG; - if ((m->fields_size - framing) % sz != 0) - return -EBADMSG; - - ri = framing; - r = message_peek_fields(m, &ri, 1, m->fields_size - framing, &offsets); - if (r < 0) - return r; - - n_offsets = (m->fields_size - framing) / sz; - } - } else - m->user_body_size = m->body_size; - - ri = 0; - while (ri < m->fields_size) { - _cleanup_free_ char *sig = NULL; - const char *signature; - uint64_t field_type; - size_t item_size = (size_t) -1; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - uint64_t *u64; - - if (i >= n_offsets) - break; - - if (i == 0) - ri = 0; - else - ri = ALIGN_TO(bus_gvariant_read_word_le((uint8_t*) offsets + (i-1)*sz, sz), 8); - - r = message_peek_fields(m, &ri, 8, 8, (void**) &u64); - if (r < 0) - return r; - - field_type = BUS_MESSAGE_BSWAP64(m, *u64); - } else { - uint8_t *u8; - - r = message_peek_fields(m, &ri, 8, 1, (void**) &u8); - if (r < 0) - return r; - - field_type = *u8; - } - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - size_t where, end; - char *b; - void *q; - - end = bus_gvariant_read_word_le((uint8_t*) offsets + i*sz, sz); - - if (end < ri) - return -EBADMSG; - - where = ri = ALIGN_TO(ri, 8); - item_size = end - ri; - r = message_peek_fields(m, &where, 1, item_size, &q); - if (r < 0) - return r; - - b = memrchr(q, 0, item_size); - if (!b) - return -EBADMSG; - - sig = strndup(b+1, item_size - (b+1-(char*) q)); - if (!sig) - return -ENOMEM; - - signature = sig; - item_size = b - (char*) q; - } else { - r = message_peek_field_signature(m, &ri, 0, &signature); - if (r < 0) - return r; - } - - switch (field_type) { - - case _BUS_MESSAGE_HEADER_INVALID: - return -EBADMSG; - - case BUS_MESSAGE_HEADER_PATH: - - if (m->path) - return -EBADMSG; - - if (!streq(signature, "o")) - return -EBADMSG; - - r = message_peek_field_string(m, object_path_is_valid, &ri, item_size, &m->path); - break; - - case BUS_MESSAGE_HEADER_INTERFACE: - - if (m->interface) - return -EBADMSG; - - if (!streq(signature, "s")) - return -EBADMSG; - - r = message_peek_field_string(m, interface_name_is_valid, &ri, item_size, &m->interface); - break; - - case BUS_MESSAGE_HEADER_MEMBER: - - if (m->member) - return -EBADMSG; - - if (!streq(signature, "s")) - return -EBADMSG; - - r = message_peek_field_string(m, member_name_is_valid, &ri, item_size, &m->member); - break; - - case BUS_MESSAGE_HEADER_ERROR_NAME: - - if (m->error.name) - return -EBADMSG; - - if (!streq(signature, "s")) - return -EBADMSG; - - r = message_peek_field_string(m, error_name_is_valid, &ri, item_size, &m->error.name); - if (r >= 0) - m->error._need_free = -1; - - break; - - case BUS_MESSAGE_HEADER_DESTINATION: - - if (m->destination) - return -EBADMSG; - - if (!streq(signature, "s")) - return -EBADMSG; - - r = message_peek_field_string(m, service_name_is_valid, &ri, item_size, &m->destination); - break; - - case BUS_MESSAGE_HEADER_SENDER: - - if (m->sender) - return -EBADMSG; - - if (!streq(signature, "s")) - return -EBADMSG; - - r = message_peek_field_string(m, service_name_is_valid, &ri, item_size, &m->sender); - - if (r >= 0 && m->sender[0] == ':' && m->bus->bus_client && !m->bus->is_kernel) { - m->creds.unique_name = (char*) m->sender; - m->creds.mask |= SD_BUS_CREDS_UNIQUE_NAME & m->bus->creds_mask; - } - - break; - - - case BUS_MESSAGE_HEADER_SIGNATURE: { - const char *s; - char *c; - - if (BUS_MESSAGE_IS_GVARIANT(m)) /* only applies to dbus1 */ - return -EBADMSG; - - if (m->root_container.signature) - return -EBADMSG; - - if (!streq(signature, "g")) - return -EBADMSG; - - r = message_peek_field_signature(m, &ri, item_size, &s); - if (r < 0) - return r; - - c = strdup(s); - if (!c) - return -ENOMEM; - - free(m->root_container.signature); - m->root_container.signature = c; - break; - } - - case BUS_MESSAGE_HEADER_REPLY_SERIAL: - - if (m->reply_cookie != 0) - return -EBADMSG; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - /* 64bit on dbus2 */ - - if (!streq(signature, "t")) - return -EBADMSG; - - r = message_peek_field_uint64(m, &ri, item_size, &m->reply_cookie); - if (r < 0) - return r; - } else { - /* 32bit on dbus1 */ - uint32_t serial; - - if (!streq(signature, "u")) - return -EBADMSG; - - r = message_peek_field_uint32(m, &ri, item_size, &serial); - if (r < 0) - return r; - - m->reply_cookie = serial; - } - - if (m->reply_cookie == 0) - return -EBADMSG; - - break; - - case BUS_MESSAGE_HEADER_UNIX_FDS: - if (unix_fds_set) - return -EBADMSG; - - if (!streq(signature, "u")) - return -EBADMSG; - - r = message_peek_field_uint32(m, &ri, item_size, &unix_fds); - if (r < 0) - return -EBADMSG; - - unix_fds_set = true; - break; - - default: - if (!BUS_MESSAGE_IS_GVARIANT(m)) - r = message_skip_fields(m, &ri, (uint32_t) -1, (const char **) &signature); - } - - if (r < 0) - return r; - - i++; - } - - if (m->n_fds != unix_fds) - return -EBADMSG; - - switch (m->header->type) { - - case SD_BUS_MESSAGE_SIGNAL: - if (!m->path || !m->interface || !m->member) - return -EBADMSG; - - if (m->reply_cookie != 0) - return -EBADMSG; - - break; - - case SD_BUS_MESSAGE_METHOD_CALL: - - if (!m->path || !m->member) - return -EBADMSG; - - if (m->reply_cookie != 0) - return -EBADMSG; - - break; - - case SD_BUS_MESSAGE_METHOD_RETURN: - - if (m->reply_cookie == 0) - return -EBADMSG; - break; - - case SD_BUS_MESSAGE_METHOD_ERROR: - - if (m->reply_cookie == 0 || !m->error.name) - return -EBADMSG; - break; - } - - /* Refuse non-local messages that claim they are local */ - if (streq_ptr(m->path, "/org/freedesktop/DBus/Local")) - return -EBADMSG; - if (streq_ptr(m->interface, "org.freedesktop.DBus.Local")) - return -EBADMSG; - if (streq_ptr(m->sender, "org.freedesktop.DBus.Local")) - return -EBADMSG; - - m->root_container.end = m->user_body_size; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - r = build_struct_offsets( - m, - m->root_container.signature, - m->user_body_size, - &m->root_container.item_size, - &m->root_container.offsets, - &m->root_container.n_offsets); - if (r < 0) - return r; - } - - /* Try to read the error message, but if we can't it's a non-issue */ - if (m->header->type == SD_BUS_MESSAGE_METHOD_ERROR) - (void) sd_bus_message_read(m, "s", &m->error.message); - - return 0; -} - -_public_ int sd_bus_message_set_destination(sd_bus_message *m, const char *destination) { - assert_return(m, -EINVAL); - assert_return(destination, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(!m->destination, -EEXIST); - - return message_append_field_string(m, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, destination, &m->destination); -} - -int bus_message_get_blob(sd_bus_message *m, void **buffer, size_t *sz) { - size_t total; - void *p, *e; - unsigned i; - struct bus_body_part *part; - - assert(m); - assert(buffer); - assert(sz); - - total = BUS_MESSAGE_SIZE(m); - - p = malloc(total); - if (!p) - return -ENOMEM; - - e = mempcpy(p, m->header, BUS_MESSAGE_BODY_BEGIN(m)); - MESSAGE_FOREACH_PART(part, i, m) - e = mempcpy(e, part->data, part->size); - - assert(total == (size_t) ((uint8_t*) e - (uint8_t*) p)); - - *buffer = p; - *sz = total; - - return 0; -} - -int bus_message_read_strv_extend(sd_bus_message *m, char ***l) { - const char *s; - int r; - - assert(m); - assert(l); - - r = sd_bus_message_enter_container(m, 'a', "s"); - if (r <= 0) - return r; - - while ((r = sd_bus_message_read_basic(m, 's', &s)) > 0) { - r = strv_extend(l, s); - if (r < 0) - return r; - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - return 1; -} - -_public_ int sd_bus_message_read_strv(sd_bus_message *m, char ***l) { - char **strv = NULL; - int r; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - assert_return(l, -EINVAL); - - r = bus_message_read_strv_extend(m, &strv); - if (r <= 0) { - strv_free(strv); - return r; - } - - *l = strv; - return 1; -} - -static int bus_message_get_arg_skip( - sd_bus_message *m, - unsigned i, - char *_type, - const char **_contents) { - - unsigned j; - int r; - - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - for (j = 0;; j++) { - const char *contents; - char type; - - r = sd_bus_message_peek_type(m, &type, &contents); - if (r < 0) - return r; - if (r == 0) - return -ENXIO; - - /* Don't match against arguments after the first one we don't understand */ - if (!IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE) && - !(type == SD_BUS_TYPE_ARRAY && STR_IN_SET(contents, "s", "o", "g"))) - return -ENXIO; - - if (j >= i) { - if (_contents) - *_contents = contents; - if (_type) - *_type = type; - return 0; - } - - r = sd_bus_message_skip(m, NULL); - if (r < 0) - return r; - } - -} - -int bus_message_get_arg(sd_bus_message *m, unsigned i, const char **str) { - char type; - int r; - - assert(m); - assert(str); - - r = bus_message_get_arg_skip(m, i, &type, NULL); - if (r < 0) - return r; - - if (!IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE)) - return -ENXIO; - - return sd_bus_message_read_basic(m, type, str); -} - -int bus_message_get_arg_strv(sd_bus_message *m, unsigned i, char ***strv) { - const char *contents; - char type; - int r; - - assert(m); - assert(strv); - - r = bus_message_get_arg_skip(m, i, &type, &contents); - if (r < 0) - return r; - - if (type != SD_BUS_TYPE_ARRAY) - return -ENXIO; - if (!STR_IN_SET(contents, "s", "o", "g")) - return -ENXIO; - - return sd_bus_message_read_strv(m, strv); -} - -_public_ int sd_bus_message_get_errno(sd_bus_message *m) { - assert_return(m, EINVAL); - - if (m->header->type != SD_BUS_MESSAGE_METHOD_ERROR) - return 0; - - return sd_bus_error_get_errno(&m->error); -} - -_public_ const char* sd_bus_message_get_signature(sd_bus_message *m, int complete) { - struct bus_container *c; - - assert_return(m, NULL); - - c = complete ? &m->root_container : message_get_container(m); - return strempty(c->signature); -} - -_public_ int sd_bus_message_is_empty(sd_bus_message *m) { - assert_return(m, -EINVAL); - - return isempty(m->root_container.signature); -} - -_public_ int sd_bus_message_has_signature(sd_bus_message *m, const char *signature) { - assert_return(m, -EINVAL); - - return streq(strempty(m->root_container.signature), strempty(signature)); -} - -_public_ int sd_bus_message_copy(sd_bus_message *m, sd_bus_message *source, int all) { - bool done_something = false; - int r; - - assert_return(m, -EINVAL); - assert_return(source, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(source->sealed, -EPERM); - - do { - const char *contents; - char type; - union { - uint8_t u8; - uint16_t u16; - int16_t s16; - uint32_t u32; - int32_t s32; - uint64_t u64; - int64_t s64; - double d64; - const char *string; - int i; - } basic; - - r = sd_bus_message_peek_type(source, &type, &contents); - if (r < 0) - return r; - if (r == 0) - break; - - done_something = true; - - if (bus_type_is_container(type) > 0) { - - r = sd_bus_message_enter_container(source, type, contents); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, type, contents); - if (r < 0) - return r; - - r = sd_bus_message_copy(m, source, true); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(source); - if (r < 0) - return r; - - continue; - } - - r = sd_bus_message_read_basic(source, type, &basic); - if (r < 0) - return r; - - assert(r > 0); - - if (type == SD_BUS_TYPE_OBJECT_PATH || - type == SD_BUS_TYPE_SIGNATURE || - type == SD_BUS_TYPE_STRING) - r = sd_bus_message_append_basic(m, type, basic.string); - else - r = sd_bus_message_append_basic(m, type, &basic); - - if (r < 0) - return r; - - } while (all); - - return done_something; -} - -_public_ int sd_bus_message_verify_type(sd_bus_message *m, char type, const char *contents) { - const char *c; - char t; - int r; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - assert_return(!type || bus_type_is_valid(type), -EINVAL); - assert_return(!contents || signature_is_valid(contents, true), -EINVAL); - assert_return(type || contents, -EINVAL); - assert_return(!contents || !type || bus_type_is_container(type), -EINVAL); - - r = sd_bus_message_peek_type(m, &t, &c); - if (r <= 0) - return r; - - if (type != 0 && type != t) - return 0; - - if (contents && !streq_ptr(contents, c)) - return 0; - - return 1; -} - -_public_ sd_bus *sd_bus_message_get_bus(sd_bus_message *m) { - assert_return(m, NULL); - - return m->bus; -} - -int bus_message_remarshal(sd_bus *bus, sd_bus_message **m) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *n = NULL; - usec_t timeout; - int r; - - assert(bus); - assert(m); - assert(*m); - - switch ((*m)->header->type) { - - case SD_BUS_MESSAGE_SIGNAL: - r = sd_bus_message_new_signal(bus, &n, (*m)->path, (*m)->interface, (*m)->member); - if (r < 0) - return r; - - break; - - case SD_BUS_MESSAGE_METHOD_CALL: - r = sd_bus_message_new_method_call(bus, &n, (*m)->destination, (*m)->path, (*m)->interface, (*m)->member); - if (r < 0) - return r; - - break; - - case SD_BUS_MESSAGE_METHOD_RETURN: - case SD_BUS_MESSAGE_METHOD_ERROR: - - n = message_new(bus, (*m)->header->type); - if (!n) - return -ENOMEM; - - n->reply_cookie = (*m)->reply_cookie; - - r = message_append_reply_cookie(n, n->reply_cookie); - if (r < 0) - return r; - - if ((*m)->header->type == SD_BUS_MESSAGE_METHOD_ERROR && (*m)->error.name) { - r = message_append_field_string(n, BUS_MESSAGE_HEADER_ERROR_NAME, SD_BUS_TYPE_STRING, (*m)->error.name, &n->error.message); - if (r < 0) - return r; - - n->error._need_free = -1; - } - - break; - - default: - return -EINVAL; - } - - if ((*m)->destination && !n->destination) { - r = message_append_field_string(n, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, (*m)->destination, &n->destination); - if (r < 0) - return r; - } - - if ((*m)->sender && !n->sender) { - r = message_append_field_string(n, BUS_MESSAGE_HEADER_SENDER, SD_BUS_TYPE_STRING, (*m)->sender, &n->sender); - if (r < 0) - return r; - } - - n->header->flags |= (*m)->header->flags & (BUS_MESSAGE_NO_REPLY_EXPECTED|BUS_MESSAGE_NO_AUTO_START); - - r = sd_bus_message_copy(n, *m, true); - if (r < 0) - return r; - - timeout = (*m)->timeout; - if (timeout == 0 && !((*m)->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED)) - timeout = BUS_DEFAULT_TIMEOUT; - - r = bus_message_seal(n, BUS_MESSAGE_COOKIE(*m), timeout); - if (r < 0) - return r; - - sd_bus_message_unref(*m); - *m = n; - n = NULL; - - return 0; -} - -int bus_message_append_sender(sd_bus_message *m, const char *sender) { - assert(m); - assert(sender); - - assert_return(!m->sealed, -EPERM); - assert_return(!m->sender, -EPERM); - - return message_append_field_string(m, BUS_MESSAGE_HEADER_SENDER, SD_BUS_TYPE_STRING, sender, &m->sender); -} - -_public_ int sd_bus_message_get_priority(sd_bus_message *m, int64_t *priority) { - assert_return(m, -EINVAL); - assert_return(priority, -EINVAL); - - *priority = m->priority; - return 0; -} - -_public_ int sd_bus_message_set_priority(sd_bus_message *m, int64_t priority) { - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - - m->priority = priority; - return 0; -} diff --git a/src/libsystemd/sd-bus/bus-message.h b/src/libsystemd/sd-bus/bus-message.h deleted file mode 100644 index 4710c106b9..0000000000 --- a/src/libsystemd/sd-bus/bus-message.h +++ /dev/null @@ -1,244 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 -#include - -#include "sd-bus.h" - -#include "bus-creds.h" -#include "bus-protocol.h" -#include "macro.h" -#include "time-util.h" - -struct bus_container { - char enclosing; - bool need_offsets:1; - - /* Indexes into the signature string */ - unsigned index, saved_index; - char *signature; - - size_t before, begin, end; - - /* dbus1: pointer to the array size value, if this is a value */ - uint32_t *array_size; - - /* gvariant: list of offsets to end of children if this is struct/dict entry/array */ - size_t *offsets, n_offsets, offsets_allocated, offset_index; - size_t item_size; - - char *peeked_signature; -}; - -struct bus_body_part { - struct bus_body_part *next; - void *data; - void *mmap_begin; - size_t size; - size_t mapped; - size_t allocated; - uint64_t memfd_offset; - int memfd; - bool free_this:1; - bool munmap_this:1; - bool sealed:1; - bool is_zero:1; -}; - -struct sd_bus_message { - unsigned n_ref; - - sd_bus *bus; - - uint64_t reply_cookie; - - const char *path; - const char *interface; - const char *member; - const char *destination; - const char *sender; - - sd_bus_error error; - - sd_bus_creds creds; - - usec_t monotonic; - usec_t realtime; - uint64_t seqnum; - int64_t priority; - uint64_t verify_destination_id; - - bool sealed:1; - bool dont_send:1; - bool allow_fds:1; - bool free_header:1; - bool free_kdbus:1; - bool free_fds:1; - bool release_kdbus:1; - bool poisoned:1; - - /* The first and last bytes of the message */ - struct bus_header *header; - void *footer; - - /* How many bytes are accessible in the above pointers */ - size_t header_accessible; - size_t footer_accessible; - - size_t fields_size; - size_t body_size; - size_t user_body_size; - - struct bus_body_part body; - struct bus_body_part *body_end; - unsigned n_body_parts; - - size_t rindex; - struct bus_body_part *cached_rindex_part; - size_t cached_rindex_part_begin; - - uint32_t n_fds; - int *fds; - - struct bus_container root_container, *containers; - size_t n_containers; - size_t containers_allocated; - - struct iovec *iovec; - struct iovec iovec_fixed[2]; - unsigned n_iovec; - - struct kdbus_msg *kdbus; - - char *peeked_signature; - - /* If set replies to this message must carry the signature - * specified here to successfully seal. This is initialized - * from the vtable data */ - const char *enforced_reply_signature; - - usec_t timeout; - - char sender_buffer[3 + DECIMAL_STR_MAX(uint64_t) + 1]; - char destination_buffer[3 + DECIMAL_STR_MAX(uint64_t) + 1]; - char *destination_ptr; - - size_t header_offsets[_BUS_MESSAGE_HEADER_MAX]; - unsigned n_header_offsets; -}; - -static inline bool BUS_MESSAGE_NEED_BSWAP(sd_bus_message *m) { - return m->header->endian != BUS_NATIVE_ENDIAN; -} - -static inline uint16_t BUS_MESSAGE_BSWAP16(sd_bus_message *m, uint16_t u) { - return BUS_MESSAGE_NEED_BSWAP(m) ? bswap_16(u) : u; -} - -static inline uint32_t BUS_MESSAGE_BSWAP32(sd_bus_message *m, uint32_t u) { - return BUS_MESSAGE_NEED_BSWAP(m) ? bswap_32(u) : u; -} - -static inline uint64_t BUS_MESSAGE_BSWAP64(sd_bus_message *m, uint64_t u) { - return BUS_MESSAGE_NEED_BSWAP(m) ? bswap_64(u) : u; -} - -static inline uint64_t BUS_MESSAGE_COOKIE(sd_bus_message *m) { - if (m->header->version == 2) - return BUS_MESSAGE_BSWAP64(m, m->header->dbus2.cookie); - - return BUS_MESSAGE_BSWAP32(m, m->header->dbus1.serial); -} - -static inline size_t BUS_MESSAGE_SIZE(sd_bus_message *m) { - return - sizeof(struct bus_header) + - ALIGN8(m->fields_size) + - m->body_size; -} - -static inline size_t BUS_MESSAGE_BODY_BEGIN(sd_bus_message *m) { - return - sizeof(struct bus_header) + - ALIGN8(m->fields_size); -} - -static inline void* BUS_MESSAGE_FIELDS(sd_bus_message *m) { - return (uint8_t*) m->header + sizeof(struct bus_header); -} - -static inline bool BUS_MESSAGE_IS_GVARIANT(sd_bus_message *m) { - return m->header->version == 2; -} - -int bus_message_seal(sd_bus_message *m, uint64_t serial, usec_t timeout); -int bus_message_get_blob(sd_bus_message *m, void **buffer, size_t *sz); -int bus_message_read_strv_extend(sd_bus_message *m, char ***l); - -int bus_message_from_header( - sd_bus *bus, - void *header, - size_t header_accessible, - void *footer, - size_t footer_accessible, - size_t message_size, - int *fds, - unsigned n_fds, - const char *label, - size_t extra, - sd_bus_message **ret); - -int bus_message_from_malloc( - sd_bus *bus, - void *buffer, - size_t length, - int *fds, - unsigned n_fds, - const char *label, - sd_bus_message **ret); - -int bus_message_get_arg(sd_bus_message *m, unsigned i, const char **str); -int bus_message_get_arg_strv(sd_bus_message *m, unsigned i, char ***strv); - -int bus_message_append_ap(sd_bus_message *m, const char *types, va_list ap); - -int bus_message_parse_fields(sd_bus_message *m); - -struct bus_body_part *message_append_part(sd_bus_message *m); - -#define MESSAGE_FOREACH_PART(part, i, m) \ - for ((i) = 0, (part) = &(m)->body; (i) < (m)->n_body_parts; (i)++, (part) = (part)->next) - -int bus_body_part_map(struct bus_body_part *part); -void bus_body_part_unmap(struct bus_body_part *part); - -int bus_message_to_errno(sd_bus_message *m); - -int bus_message_new_synthetic_error(sd_bus *bus, uint64_t serial, const sd_bus_error *e, sd_bus_message **m); - -int bus_message_remarshal(sd_bus *bus, sd_bus_message **m); - -int bus_message_append_sender(sd_bus_message *m, const char *sender); - -void bus_message_set_sender_driver(sd_bus *bus, sd_bus_message *m); -void bus_message_set_sender_local(sd_bus *bus, sd_bus_message *m); diff --git a/src/libsystemd/sd-bus/bus-objects.c b/src/libsystemd/sd-bus/bus-objects.c deleted file mode 100644 index 9bd07ffcab..0000000000 --- a/src/libsystemd/sd-bus/bus-objects.c +++ /dev/null @@ -1,2806 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "alloc-util.h" -#include "bus-internal.h" -#include "bus-introspect.h" -#include "bus-message.h" -#include "bus-objects.h" -#include "bus-signature.h" -#include "bus-slot.h" -#include "bus-type.h" -#include "bus-util.h" -#include "set.h" -#include "string-util.h" -#include "strv.h" - -static int node_vtable_get_userdata( - sd_bus *bus, - const char *path, - struct node_vtable *c, - void **userdata, - sd_bus_error *error) { - - sd_bus_slot *s; - void *u; - int r; - - assert(bus); - assert(path); - assert(c); - - s = container_of(c, sd_bus_slot, node_vtable); - u = s->userdata; - if (c->find) { - bus->current_slot = sd_bus_slot_ref(s); - bus->current_userdata = u; - r = c->find(bus, path, c->interface, u, &u, error); - bus->current_userdata = NULL; - bus->current_slot = sd_bus_slot_unref(s); - - if (r < 0) - return r; - if (sd_bus_error_is_set(error)) - return -sd_bus_error_get_errno(error); - if (r == 0) - return r; - } - - if (userdata) - *userdata = u; - - return 1; -} - -static void *vtable_method_convert_userdata(const sd_bus_vtable *p, void *u) { - assert(p); - - return (uint8_t*) u + p->x.method.offset; -} - -static void *vtable_property_convert_userdata(const sd_bus_vtable *p, void *u) { - assert(p); - - return (uint8_t*) u + p->x.property.offset; -} - -static int vtable_property_get_userdata( - sd_bus *bus, - const char *path, - struct vtable_member *p, - void **userdata, - sd_bus_error *error) { - - void *u; - int r; - - assert(bus); - assert(path); - assert(p); - assert(userdata); - - r = node_vtable_get_userdata(bus, path, p->parent, &u, error); - if (r <= 0) - return r; - if (bus->nodes_modified) - return 0; - - *userdata = vtable_property_convert_userdata(p->vtable, u); - return 1; -} - -static int add_enumerated_to_set( - sd_bus *bus, - const char *prefix, - struct node_enumerator *first, - Set *s, - sd_bus_error *error) { - - struct node_enumerator *c; - int r; - - assert(bus); - assert(prefix); - assert(s); - - LIST_FOREACH(enumerators, c, first) { - char **children = NULL, **k; - sd_bus_slot *slot; - - if (bus->nodes_modified) - return 0; - - slot = container_of(c, sd_bus_slot, node_enumerator); - - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_userdata = slot->userdata; - r = c->callback(bus, prefix, slot->userdata, &children, error); - bus->current_userdata = NULL; - bus->current_slot = sd_bus_slot_unref(slot); - - if (r < 0) - return r; - if (sd_bus_error_is_set(error)) - return -sd_bus_error_get_errno(error); - - STRV_FOREACH(k, children) { - if (r < 0) { - free(*k); - continue; - } - - if (!object_path_is_valid(*k)) { - free(*k); - r = -EINVAL; - continue; - } - - if (!object_path_startswith(*k, prefix)) { - free(*k); - continue; - } - - r = set_consume(s, *k); - if (r == -EEXIST) - r = 0; - } - - free(children); - if (r < 0) - return r; - } - - return 0; -} - -enum { - /* if set, add_subtree() works recursively */ - CHILDREN_RECURSIVE = (1U << 1), - /* if set, add_subtree() scans object-manager hierarchies recursively */ - CHILDREN_SUBHIERARCHIES = (1U << 0), -}; - -static int add_subtree_to_set( - sd_bus *bus, - const char *prefix, - struct node *n, - unsigned int flags, - Set *s, - sd_bus_error *error) { - - struct node *i; - int r; - - assert(bus); - assert(prefix); - assert(n); - assert(s); - - r = add_enumerated_to_set(bus, prefix, n->enumerators, s, error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - - LIST_FOREACH(siblings, i, n->child) { - char *t; - - if (!object_path_startswith(i->path, prefix)) - continue; - - t = strdup(i->path); - if (!t) - return -ENOMEM; - - r = set_consume(s, t); - if (r < 0 && r != -EEXIST) - return r; - - if ((flags & CHILDREN_RECURSIVE) && - ((flags & CHILDREN_SUBHIERARCHIES) || !i->object_managers)) { - r = add_subtree_to_set(bus, prefix, i, flags, s, error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - } - } - - return 0; -} - -static int get_child_nodes( - sd_bus *bus, - const char *prefix, - struct node *n, - unsigned int flags, - Set **_s, - sd_bus_error *error) { - - Set *s = NULL; - int r; - - assert(bus); - assert(prefix); - assert(n); - assert(_s); - - s = set_new(&string_hash_ops); - if (!s) - return -ENOMEM; - - r = add_subtree_to_set(bus, prefix, n, flags, s, error); - if (r < 0) { - set_free_free(s); - return r; - } - - *_s = s; - return 0; -} - -static int node_callbacks_run( - sd_bus *bus, - sd_bus_message *m, - struct node_callback *first, - bool require_fallback, - bool *found_object) { - - struct node_callback *c; - int r; - - assert(bus); - assert(m); - assert(found_object); - - LIST_FOREACH(callbacks, c, first) { - _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; - sd_bus_slot *slot; - - if (bus->nodes_modified) - return 0; - - if (require_fallback && !c->is_fallback) - continue; - - *found_object = true; - - if (c->last_iteration == bus->iteration_counter) - continue; - - c->last_iteration = bus->iteration_counter; - - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - slot = container_of(c, sd_bus_slot, node_callback); - - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_handler = c->callback; - bus->current_userdata = slot->userdata; - r = c->callback(m, slot->userdata, &error_buffer); - bus->current_userdata = NULL; - bus->current_handler = NULL; - bus->current_slot = sd_bus_slot_unref(slot); - - r = bus_maybe_reply_error(m, r, &error_buffer); - if (r != 0) - return r; - } - - return 0; -} - -#define CAPABILITY_SHIFT(x) (((x) >> __builtin_ctzll(_SD_BUS_VTABLE_CAPABILITY_MASK)) & 0xFFFF) - -static int check_access(sd_bus *bus, sd_bus_message *m, struct vtable_member *c, sd_bus_error *error) { - uint64_t cap; - int r; - - assert(bus); - assert(m); - assert(c); - - /* If the entire bus is trusted let's grant access */ - if (bus->trusted) - return 0; - - /* If the member is marked UNPRIVILEGED let's grant access */ - if (c->vtable->flags & SD_BUS_VTABLE_UNPRIVILEGED) - return 0; - - /* Check have the caller has the requested capability - * set. Note that the flags value contains the capability - * number plus one, which we need to subtract here. We do this - * so that we have 0 as special value for "default - * capability". */ - cap = CAPABILITY_SHIFT(c->vtable->flags); - if (cap == 0) - cap = CAPABILITY_SHIFT(c->parent->vtable[0].flags); - if (cap == 0) - cap = CAP_SYS_ADMIN; - else - cap--; - - r = sd_bus_query_sender_privilege(m, cap); - if (r < 0) - return r; - if (r > 0) - return 0; - - return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Access to %s.%s() not permitted.", c->interface, c->member); -} - -static int method_callbacks_run( - sd_bus *bus, - sd_bus_message *m, - struct vtable_member *c, - bool require_fallback, - bool *found_object) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *signature; - void *u; - int r; - - assert(bus); - assert(m); - assert(c); - assert(found_object); - - if (require_fallback && !c->parent->is_fallback) - return 0; - - r = check_access(bus, m, c, &error); - if (r < 0) - return bus_maybe_reply_error(m, r, &error); - - r = node_vtable_get_userdata(bus, m->path, c->parent, &u, &error); - if (r <= 0) - return bus_maybe_reply_error(m, r, &error); - if (bus->nodes_modified) - return 0; - - u = vtable_method_convert_userdata(c->vtable, u); - - *found_object = true; - - if (c->last_iteration == bus->iteration_counter) - return 0; - - c->last_iteration = bus->iteration_counter; - - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - signature = sd_bus_message_get_signature(m, true); - if (!signature) - return -EINVAL; - - if (!streq(strempty(c->vtable->x.method.signature), signature)) - return sd_bus_reply_method_errorf( - m, - SD_BUS_ERROR_INVALID_ARGS, - "Invalid arguments '%s' to call %s.%s(), expecting '%s'.", - signature, c->interface, c->member, strempty(c->vtable->x.method.signature)); - - /* Keep track what the signature of the reply to this message - * should be, so that this can be enforced when sealing the - * reply. */ - m->enforced_reply_signature = strempty(c->vtable->x.method.result); - - if (c->vtable->x.method.handler) { - sd_bus_slot *slot; - - slot = container_of(c->parent, sd_bus_slot, node_vtable); - - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_handler = c->vtable->x.method.handler; - bus->current_userdata = u; - r = c->vtable->x.method.handler(m, u, &error); - bus->current_userdata = NULL; - bus->current_handler = NULL; - bus->current_slot = sd_bus_slot_unref(slot); - - return bus_maybe_reply_error(m, r, &error); - } - - /* If the method callback is NULL, make this a successful NOP */ - r = sd_bus_reply_method_return(m, NULL); - if (r < 0) - return r; - - return 1; -} - -static int invoke_property_get( - sd_bus *bus, - sd_bus_slot *slot, - const sd_bus_vtable *v, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - const void *p; - int r; - - assert(bus); - assert(slot); - assert(v); - assert(path); - assert(interface); - assert(property); - assert(reply); - - if (v->x.property.get) { - - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_userdata = userdata; - r = v->x.property.get(bus, path, interface, property, reply, userdata, error); - bus->current_userdata = NULL; - bus->current_slot = sd_bus_slot_unref(slot); - - if (r < 0) - return r; - if (sd_bus_error_is_set(error)) - return -sd_bus_error_get_errno(error); - return r; - } - - /* Automatic handling if no callback is defined. */ - - if (streq(v->x.property.signature, "as")) - return sd_bus_message_append_strv(reply, *(char***) userdata); - - assert(signature_is_single(v->x.property.signature, false)); - assert(bus_type_is_basic(v->x.property.signature[0])); - - switch (v->x.property.signature[0]) { - - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_SIGNATURE: - p = strempty(*(char**) userdata); - break; - - case SD_BUS_TYPE_OBJECT_PATH: - p = *(char**) userdata; - assert(p); - break; - - default: - p = userdata; - break; - } - - return sd_bus_message_append_basic(reply, v->x.property.signature[0], p); -} - -static int invoke_property_set( - sd_bus *bus, - sd_bus_slot *slot, - const sd_bus_vtable *v, - const char *path, - const char *interface, - const char *property, - sd_bus_message *value, - void *userdata, - sd_bus_error *error) { - - int r; - - assert(bus); - assert(slot); - assert(v); - assert(path); - assert(interface); - assert(property); - assert(value); - - if (v->x.property.set) { - - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_userdata = userdata; - r = v->x.property.set(bus, path, interface, property, value, userdata, error); - bus->current_userdata = NULL; - bus->current_slot = sd_bus_slot_unref(slot); - - if (r < 0) - return r; - if (sd_bus_error_is_set(error)) - return -sd_bus_error_get_errno(error); - return r; - } - - /* Automatic handling if no callback is defined. */ - - assert(signature_is_single(v->x.property.signature, false)); - assert(bus_type_is_basic(v->x.property.signature[0])); - - switch (v->x.property.signature[0]) { - - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_SIGNATURE: { - const char *p; - char *n; - - r = sd_bus_message_read_basic(value, v->x.property.signature[0], &p); - if (r < 0) - return r; - - n = strdup(p); - if (!n) - return -ENOMEM; - - free(*(char**) userdata); - *(char**) userdata = n; - - break; - } - - default: - r = sd_bus_message_read_basic(value, v->x.property.signature[0], userdata); - if (r < 0) - return r; - - break; - } - - return 1; -} - -static int property_get_set_callbacks_run( - sd_bus *bus, - sd_bus_message *m, - struct vtable_member *c, - bool require_fallback, - bool is_get, - bool *found_object) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - sd_bus_slot *slot; - void *u = NULL; - int r; - - assert(bus); - assert(m); - assert(c); - assert(found_object); - - if (require_fallback && !c->parent->is_fallback) - return 0; - - r = vtable_property_get_userdata(bus, m->path, c, &u, &error); - if (r <= 0) - return bus_maybe_reply_error(m, r, &error); - if (bus->nodes_modified) - return 0; - - slot = container_of(c->parent, sd_bus_slot, node_vtable); - - *found_object = true; - - r = sd_bus_message_new_method_return(m, &reply); - if (r < 0) - return r; - - if (is_get) { - /* Note that we do not protect against reexecution - * here (using the last_iteration check, see below), - * should the node tree have changed and we got called - * again. We assume that property Get() calls are - * ultimately without side-effects or if they aren't - * then at least idempotent. */ - - r = sd_bus_message_open_container(reply, 'v', c->vtable->x.property.signature); - if (r < 0) - return r; - - /* Note that we do not do an access check here. Read - * access to properties is always unrestricted, since - * PropertiesChanged signals broadcast contents - * anyway. */ - - r = invoke_property_get(bus, slot, c->vtable, m->path, c->interface, c->member, reply, u, &error); - if (r < 0) - return bus_maybe_reply_error(m, r, &error); - - if (bus->nodes_modified) - return 0; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - } else { - const char *signature = NULL; - char type = 0; - - if (c->vtable->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY) - return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_PROPERTY_READ_ONLY, "Property '%s' is not writable.", c->member); - - /* Avoid that we call the set routine more than once - * if the processing of this message got restarted - * because the node tree changed. */ - if (c->last_iteration == bus->iteration_counter) - return 0; - - c->last_iteration = bus->iteration_counter; - - r = sd_bus_message_peek_type(m, &type, &signature); - if (r < 0) - return r; - - if (type != 'v' || !streq(strempty(signature), strempty(c->vtable->x.property.signature))) - return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Incorrect parameters for property '%s', expected '%s', got '%s'.", c->member, strempty(c->vtable->x.property.signature), strempty(signature)); - - r = sd_bus_message_enter_container(m, 'v', c->vtable->x.property.signature); - if (r < 0) - return r; - - r = check_access(bus, m, c, &error); - if (r < 0) - return bus_maybe_reply_error(m, r, &error); - - r = invoke_property_set(bus, slot, c->vtable, m->path, c->interface, c->member, m, u, &error); - if (r < 0) - return bus_maybe_reply_error(m, r, &error); - - if (bus->nodes_modified) - return 0; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - } - - r = sd_bus_send(bus, reply, NULL); - if (r < 0) - return r; - - return 1; -} - -static int vtable_append_one_property( - sd_bus *bus, - sd_bus_message *reply, - const char *path, - struct node_vtable *c, - const sd_bus_vtable *v, - void *userdata, - sd_bus_error *error) { - - sd_bus_slot *slot; - int r; - - assert(bus); - assert(reply); - assert(path); - assert(c); - assert(v); - - r = sd_bus_message_open_container(reply, 'e', "sv"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "s", v->x.property.member); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'v', v->x.property.signature); - if (r < 0) - return r; - - slot = container_of(c, sd_bus_slot, node_vtable); - - r = invoke_property_get(bus, slot, v, path, c->interface, v->x.property.member, reply, vtable_property_convert_userdata(v, userdata), error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return 0; -} - -static int vtable_append_all_properties( - sd_bus *bus, - sd_bus_message *reply, - const char *path, - struct node_vtable *c, - void *userdata, - sd_bus_error *error) { - - const sd_bus_vtable *v; - int r; - - assert(bus); - assert(reply); - assert(path); - assert(c); - - if (c->vtable[0].flags & SD_BUS_VTABLE_HIDDEN) - return 1; - - for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; v++) { - if (v->type != _SD_BUS_VTABLE_PROPERTY && v->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY) - continue; - - if (v->flags & SD_BUS_VTABLE_HIDDEN) - continue; - - if (v->flags & SD_BUS_VTABLE_PROPERTY_EXPLICIT) - continue; - - r = vtable_append_one_property(bus, reply, path, c, v, userdata, error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - } - - return 1; -} - -static int property_get_all_callbacks_run( - sd_bus *bus, - sd_bus_message *m, - struct node_vtable *first, - bool require_fallback, - const char *iface, - bool *found_object) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - struct node_vtable *c; - bool found_interface; - int r; - - assert(bus); - assert(m); - assert(found_object); - - r = sd_bus_message_new_method_return(m, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "{sv}"); - if (r < 0) - return r; - - found_interface = !iface || - streq(iface, "org.freedesktop.DBus.Properties") || - streq(iface, "org.freedesktop.DBus.Peer") || - streq(iface, "org.freedesktop.DBus.Introspectable"); - - LIST_FOREACH(vtables, c, first) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - void *u; - - if (require_fallback && !c->is_fallback) - continue; - - r = node_vtable_get_userdata(bus, m->path, c, &u, &error); - if (r < 0) - return bus_maybe_reply_error(m, r, &error); - if (bus->nodes_modified) - return 0; - if (r == 0) - continue; - - *found_object = true; - - if (iface && !streq(c->interface, iface)) - continue; - found_interface = true; - - r = vtable_append_all_properties(bus, reply, m->path, c, u, &error); - if (r < 0) - return bus_maybe_reply_error(m, r, &error); - if (bus->nodes_modified) - return 0; - } - - if (!found_interface) { - r = sd_bus_reply_method_errorf( - m, - SD_BUS_ERROR_UNKNOWN_INTERFACE, - "Unknown interface '%s'.", iface); - if (r < 0) - return r; - - return 1; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - r = sd_bus_send(bus, reply, NULL); - if (r < 0) - return r; - - return 1; -} - -static int bus_node_exists( - sd_bus *bus, - struct node *n, - const char *path, - bool require_fallback) { - - struct node_vtable *c; - struct node_callback *k; - int r; - - assert(bus); - assert(n); - assert(path); - - /* Tests if there's anything attached directly to this node - * for the specified path */ - - if (!require_fallback && (n->enumerators || n->object_managers)) - return true; - - LIST_FOREACH(callbacks, k, n->callbacks) { - if (require_fallback && !k->is_fallback) - continue; - - return 1; - } - - LIST_FOREACH(vtables, c, n->vtables) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - - if (require_fallback && !c->is_fallback) - continue; - - r = node_vtable_get_userdata(bus, path, c, NULL, &error); - if (r != 0) - return r; - if (bus->nodes_modified) - return 0; - } - - return 0; -} - -static int process_introspect( - sd_bus *bus, - sd_bus_message *m, - struct node *n, - bool require_fallback, - bool *found_object) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_set_free_free_ Set *s = NULL; - const char *previous_interface = NULL; - struct introspect intro; - struct node_vtable *c; - bool empty; - int r; - - assert(bus); - assert(m); - assert(n); - assert(found_object); - - r = get_child_nodes(bus, m->path, n, 0, &s, &error); - if (r < 0) - return bus_maybe_reply_error(m, r, &error); - if (bus->nodes_modified) - return 0; - - r = introspect_begin(&intro, bus->trusted); - if (r < 0) - return r; - - r = introspect_write_default_interfaces(&intro, !require_fallback && n->object_managers); - if (r < 0) - return r; - - empty = set_isempty(s); - - LIST_FOREACH(vtables, c, n->vtables) { - if (require_fallback && !c->is_fallback) - continue; - - r = node_vtable_get_userdata(bus, m->path, c, NULL, &error); - if (r < 0) { - r = bus_maybe_reply_error(m, r, &error); - goto finish; - } - if (bus->nodes_modified) { - r = 0; - goto finish; - } - if (r == 0) - continue; - - empty = false; - - if (c->vtable[0].flags & SD_BUS_VTABLE_HIDDEN) - continue; - - if (!streq_ptr(previous_interface, c->interface)) { - - if (previous_interface) - fputs("
\n", intro.f); - - fprintf(intro.f, " \n", c->interface); - } - - r = introspect_write_interface(&intro, c->vtable); - if (r < 0) - goto finish; - - previous_interface = c->interface; - } - - if (previous_interface) - fputs(" \n", intro.f); - - if (empty) { - /* Nothing?, let's see if we exist at all, and if not - * refuse to do anything */ - r = bus_node_exists(bus, n, m->path, require_fallback); - if (r <= 0) - goto finish; - if (bus->nodes_modified) { - r = 0; - goto finish; - } - } - - *found_object = true; - - r = introspect_write_child_nodes(&intro, s, m->path); - if (r < 0) - goto finish; - - r = introspect_finish(&intro, bus, m, &reply); - if (r < 0) - goto finish; - - r = sd_bus_send(bus, reply, NULL); - if (r < 0) - goto finish; - - r = 1; - -finish: - introspect_free(&intro); - return r; -} - -static int object_manager_serialize_path( - sd_bus *bus, - sd_bus_message *reply, - const char *prefix, - const char *path, - bool require_fallback, - sd_bus_error *error) { - - const char *previous_interface = NULL; - bool found_something = false; - struct node_vtable *i; - struct node *n; - int r; - - assert(bus); - assert(reply); - assert(prefix); - assert(path); - assert(error); - - n = hashmap_get(bus->nodes, prefix); - if (!n) - return 0; - - LIST_FOREACH(vtables, i, n->vtables) { - void *u; - - if (require_fallback && !i->is_fallback) - continue; - - r = node_vtable_get_userdata(bus, path, i, &u, error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - if (r == 0) - continue; - - if (!found_something) { - - /* Open the object part */ - - r = sd_bus_message_open_container(reply, 'e', "oa{sa{sv}}"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "o", path); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "{sa{sv}}"); - if (r < 0) - return r; - - found_something = true; - } - - if (!streq_ptr(previous_interface, i->interface)) { - - /* Maybe close the previous interface part */ - - if (previous_interface) { - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - } - - /* Open the new interface part */ - - r = sd_bus_message_open_container(reply, 'e', "sa{sv}"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "s", i->interface); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "{sv}"); - if (r < 0) - return r; - } - - r = vtable_append_all_properties(bus, reply, path, i, u, error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - - previous_interface = i->interface; - } - - if (previous_interface) { - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - } - - if (found_something) { - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - } - - return 1; -} - -static int object_manager_serialize_path_and_fallbacks( - sd_bus *bus, - sd_bus_message *reply, - const char *path, - sd_bus_error *error) { - - char *prefix; - int r; - - assert(bus); - assert(reply); - assert(path); - assert(error); - - /* First, add all vtables registered for this path */ - r = object_manager_serialize_path(bus, reply, path, path, false, error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - - /* Second, add fallback vtables registered for any of the prefixes */ - prefix = alloca(strlen(path) + 1); - OBJECT_PATH_FOREACH_PREFIX(prefix, path) { - r = object_manager_serialize_path(bus, reply, prefix, path, true, error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - } - - return 0; -} - -static int process_get_managed_objects( - sd_bus *bus, - sd_bus_message *m, - struct node *n, - bool require_fallback, - bool *found_object) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_set_free_free_ Set *s = NULL; - Iterator i; - char *path; - int r; - - assert(bus); - assert(m); - assert(n); - assert(found_object); - - /* Spec says, GetManagedObjects() is only implemented on the root of a - * sub-tree. Therefore, we require a registered object-manager on - * exactly the queried path, otherwise, we refuse to respond. */ - - if (require_fallback || !n->object_managers) - return 0; - - r = get_child_nodes(bus, m->path, n, CHILDREN_RECURSIVE, &s, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - - r = sd_bus_message_new_method_return(m, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "{oa{sa{sv}}}"); - if (r < 0) - return r; - - SET_FOREACH(path, s, i) { - r = object_manager_serialize_path_and_fallbacks(bus, reply, path, &error); - if (r < 0) - return r; - - if (bus->nodes_modified) - return 0; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - r = sd_bus_send(bus, reply, NULL); - if (r < 0) - return r; - - return 1; -} - -static int object_find_and_run( - sd_bus *bus, - sd_bus_message *m, - const char *p, - bool require_fallback, - bool *found_object) { - - struct node *n; - struct vtable_member vtable_key, *v; - int r; - - assert(bus); - assert(m); - assert(p); - assert(found_object); - - n = hashmap_get(bus->nodes, p); - if (!n) - return 0; - - /* First, try object callbacks */ - r = node_callbacks_run(bus, m, n->callbacks, require_fallback, found_object); - if (r != 0) - return r; - if (bus->nodes_modified) - return 0; - - if (!m->interface || !m->member) - return 0; - - /* Then, look for a known method */ - vtable_key.path = (char*) p; - vtable_key.interface = m->interface; - vtable_key.member = m->member; - - v = hashmap_get(bus->vtable_methods, &vtable_key); - if (v) { - r = method_callbacks_run(bus, m, v, require_fallback, found_object); - if (r != 0) - return r; - if (bus->nodes_modified) - return 0; - } - - /* Then, look for a known property */ - if (streq(m->interface, "org.freedesktop.DBus.Properties")) { - bool get = false; - - get = streq(m->member, "Get"); - - if (get || streq(m->member, "Set")) { - - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - vtable_key.path = (char*) p; - - r = sd_bus_message_read(m, "ss", &vtable_key.interface, &vtable_key.member); - if (r < 0) - return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected interface and member parameters"); - - v = hashmap_get(bus->vtable_properties, &vtable_key); - if (v) { - r = property_get_set_callbacks_run(bus, m, v, require_fallback, get, found_object); - if (r != 0) - return r; - } - - } else if (streq(m->member, "GetAll")) { - const char *iface; - - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - r = sd_bus_message_read(m, "s", &iface); - if (r < 0) - return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected interface parameter"); - - if (iface[0] == 0) - iface = NULL; - - r = property_get_all_callbacks_run(bus, m, n->vtables, require_fallback, iface, found_object); - if (r != 0) - return r; - } - - } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { - - if (!isempty(sd_bus_message_get_signature(m, true))) - return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected no parameters"); - - r = process_introspect(bus, m, n, require_fallback, found_object); - if (r != 0) - return r; - - } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.ObjectManager", "GetManagedObjects")) { - - if (!isempty(sd_bus_message_get_signature(m, true))) - return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected no parameters"); - - r = process_get_managed_objects(bus, m, n, require_fallback, found_object); - if (r != 0) - return r; - } - - if (bus->nodes_modified) - return 0; - - if (!*found_object) { - r = bus_node_exists(bus, n, m->path, require_fallback); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - if (r > 0) - *found_object = true; - } - - return 0; -} - -int bus_process_object(sd_bus *bus, sd_bus_message *m) { - int r; - size_t pl; - bool found_object = false; - - assert(bus); - assert(m); - - if (bus->hello_flags & KDBUS_HELLO_MONITOR) - return 0; - - if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL) - return 0; - - if (hashmap_isempty(bus->nodes)) - return 0; - - /* Never respond to broadcast messages */ - if (bus->bus_client && !m->destination) - return 0; - - assert(m->path); - assert(m->member); - - pl = strlen(m->path); - do { - char prefix[pl+1]; - - bus->nodes_modified = false; - - r = object_find_and_run(bus, m, m->path, false, &found_object); - if (r != 0) - return r; - - /* Look for fallback prefixes */ - OBJECT_PATH_FOREACH_PREFIX(prefix, m->path) { - - if (bus->nodes_modified) - break; - - r = object_find_and_run(bus, m, prefix, true, &found_object); - if (r != 0) - return r; - } - - } while (bus->nodes_modified); - - if (!found_object) - return 0; - - if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Get") || - sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Set")) - r = sd_bus_reply_method_errorf( - m, - SD_BUS_ERROR_UNKNOWN_PROPERTY, - "Unknown property or interface."); - else - r = sd_bus_reply_method_errorf( - m, - SD_BUS_ERROR_UNKNOWN_METHOD, - "Unknown method '%s' or interface '%s'.", m->member, m->interface); - - if (r < 0) - return r; - - return 1; -} - -static struct node *bus_node_allocate(sd_bus *bus, const char *path) { - struct node *n, *parent; - const char *e; - _cleanup_free_ char *s = NULL; - char *p; - int r; - - assert(bus); - assert(path); - assert(path[0] == '/'); - - n = hashmap_get(bus->nodes, path); - if (n) - return n; - - r = hashmap_ensure_allocated(&bus->nodes, &string_hash_ops); - if (r < 0) - return NULL; - - s = strdup(path); - if (!s) - return NULL; - - if (streq(path, "/")) - parent = NULL; - else { - e = strrchr(path, '/'); - assert(e); - - p = strndupa(path, MAX(1, e - path)); - - parent = bus_node_allocate(bus, p); - if (!parent) - return NULL; - } - - n = new0(struct node, 1); - if (!n) - return NULL; - - n->parent = parent; - n->path = s; - s = NULL; /* do not free */ - - r = hashmap_put(bus->nodes, n->path, n); - if (r < 0) { - free(n->path); - free(n); - return NULL; - } - - if (parent) - LIST_PREPEND(siblings, parent->child, n); - - return n; -} - -void bus_node_gc(sd_bus *b, struct node *n) { - assert(b); - - if (!n) - return; - - if (n->child || - n->callbacks || - n->vtables || - n->enumerators || - n->object_managers) - return; - - assert(hashmap_remove(b->nodes, n->path) == n); - - if (n->parent) - LIST_REMOVE(siblings, n->parent->child, n); - - free(n->path); - bus_node_gc(b, n->parent); - free(n); -} - -static int bus_find_parent_object_manager(sd_bus *bus, struct node **out, const char *path) { - struct node *n; - - assert(bus); - assert(path); - - n = hashmap_get(bus->nodes, path); - if (!n) { - char *prefix; - - prefix = alloca(strlen(path) + 1); - OBJECT_PATH_FOREACH_PREFIX(prefix, path) { - n = hashmap_get(bus->nodes, prefix); - if (n) - break; - } - } - - while (n && !n->object_managers) - n = n->parent; - - if (out) - *out = n; - return !!n; -} - -static int bus_add_object( - sd_bus *bus, - sd_bus_slot **slot, - bool fallback, - const char *path, - sd_bus_message_handler_t callback, - void *userdata) { - - sd_bus_slot *s; - struct node *n; - int r; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(callback, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - n = bus_node_allocate(bus, path); - if (!n) - return -ENOMEM; - - s = bus_slot_allocate(bus, !slot, BUS_NODE_CALLBACK, sizeof(struct node_callback), userdata); - if (!s) { - r = -ENOMEM; - goto fail; - } - - s->node_callback.callback = callback; - s->node_callback.is_fallback = fallback; - - s->node_callback.node = n; - LIST_PREPEND(callbacks, n->callbacks, &s->node_callback); - bus->nodes_modified = true; - - if (slot) - *slot = s; - - return 0; - -fail: - sd_bus_slot_unref(s); - bus_node_gc(bus, n); - - return r; -} - -_public_ int sd_bus_add_object( - sd_bus *bus, - sd_bus_slot **slot, - const char *path, - sd_bus_message_handler_t callback, - void *userdata) { - - return bus_add_object(bus, slot, false, path, callback, userdata); -} - -_public_ int sd_bus_add_fallback( - sd_bus *bus, - sd_bus_slot **slot, - const char *prefix, - sd_bus_message_handler_t callback, - void *userdata) { - - return bus_add_object(bus, slot, true, prefix, callback, userdata); -} - -static void vtable_member_hash_func(const void *a, struct siphash *state) { - const struct vtable_member *m = a; - - assert(m); - - string_hash_func(m->path, state); - string_hash_func(m->interface, state); - string_hash_func(m->member, state); -} - -static int vtable_member_compare_func(const void *a, const void *b) { - const struct vtable_member *x = a, *y = b; - int r; - - assert(x); - assert(y); - - r = strcmp(x->path, y->path); - if (r != 0) - return r; - - r = strcmp(x->interface, y->interface); - if (r != 0) - return r; - - return strcmp(x->member, y->member); -} - -static const struct hash_ops vtable_member_hash_ops = { - .hash = vtable_member_hash_func, - .compare = vtable_member_compare_func -}; - -static int add_object_vtable_internal( - sd_bus *bus, - sd_bus_slot **slot, - const char *path, - const char *interface, - const sd_bus_vtable *vtable, - bool fallback, - sd_bus_object_find_t find, - void *userdata) { - - sd_bus_slot *s = NULL; - struct node_vtable *i, *existing = NULL; - const sd_bus_vtable *v; - struct node *n; - int r; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(interface_name_is_valid(interface), -EINVAL); - assert_return(vtable, -EINVAL); - assert_return(vtable[0].type == _SD_BUS_VTABLE_START, -EINVAL); - assert_return(vtable[0].x.start.element_size == sizeof(struct sd_bus_vtable), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - assert_return(!streq(interface, "org.freedesktop.DBus.Properties") && - !streq(interface, "org.freedesktop.DBus.Introspectable") && - !streq(interface, "org.freedesktop.DBus.Peer") && - !streq(interface, "org.freedesktop.DBus.ObjectManager"), -EINVAL); - - r = hashmap_ensure_allocated(&bus->vtable_methods, &vtable_member_hash_ops); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&bus->vtable_properties, &vtable_member_hash_ops); - if (r < 0) - return r; - - n = bus_node_allocate(bus, path); - if (!n) - return -ENOMEM; - - LIST_FOREACH(vtables, i, n->vtables) { - if (i->is_fallback != fallback) { - r = -EPROTOTYPE; - goto fail; - } - - if (streq(i->interface, interface)) { - - if (i->vtable == vtable) { - r = -EEXIST; - goto fail; - } - - existing = i; - } - } - - s = bus_slot_allocate(bus, !slot, BUS_NODE_VTABLE, sizeof(struct node_vtable), userdata); - if (!s) { - r = -ENOMEM; - goto fail; - } - - s->node_vtable.is_fallback = fallback; - s->node_vtable.vtable = vtable; - s->node_vtable.find = find; - - s->node_vtable.interface = strdup(interface); - if (!s->node_vtable.interface) { - r = -ENOMEM; - goto fail; - } - - for (v = s->node_vtable.vtable+1; v->type != _SD_BUS_VTABLE_END; v++) { - - switch (v->type) { - - case _SD_BUS_VTABLE_METHOD: { - struct vtable_member *m; - - if (!member_name_is_valid(v->x.method.member) || - !signature_is_valid(strempty(v->x.method.signature), false) || - !signature_is_valid(strempty(v->x.method.result), false) || - !(v->x.method.handler || (isempty(v->x.method.signature) && isempty(v->x.method.result))) || - v->flags & (SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) { - r = -EINVAL; - goto fail; - } - - m = new0(struct vtable_member, 1); - if (!m) { - r = -ENOMEM; - goto fail; - } - - m->parent = &s->node_vtable; - m->path = n->path; - m->interface = s->node_vtable.interface; - m->member = v->x.method.member; - m->vtable = v; - - r = hashmap_put(bus->vtable_methods, m, m); - if (r < 0) { - free(m); - goto fail; - } - - break; - } - - case _SD_BUS_VTABLE_WRITABLE_PROPERTY: - - if (!(v->x.property.set || bus_type_is_basic(v->x.property.signature[0]))) { - r = -EINVAL; - goto fail; - } - - if (v->flags & SD_BUS_VTABLE_PROPERTY_CONST) { - r = -EINVAL; - goto fail; - } - - /* Fall through */ - - case _SD_BUS_VTABLE_PROPERTY: { - struct vtable_member *m; - - if (!member_name_is_valid(v->x.property.member) || - !signature_is_single(v->x.property.signature, false) || - !(v->x.property.get || bus_type_is_basic(v->x.property.signature[0]) || streq(v->x.property.signature, "as")) || - (v->flags & SD_BUS_VTABLE_METHOD_NO_REPLY) || - (!!(v->flags & SD_BUS_VTABLE_PROPERTY_CONST) + !!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) + !!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) > 1 || - ((v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) && (v->flags & SD_BUS_VTABLE_PROPERTY_EXPLICIT)) || - (v->flags & SD_BUS_VTABLE_UNPRIVILEGED && v->type == _SD_BUS_VTABLE_PROPERTY)) { - r = -EINVAL; - goto fail; - } - - m = new0(struct vtable_member, 1); - if (!m) { - r = -ENOMEM; - goto fail; - } - - m->parent = &s->node_vtable; - m->path = n->path; - m->interface = s->node_vtable.interface; - m->member = v->x.property.member; - m->vtable = v; - - r = hashmap_put(bus->vtable_properties, m, m); - if (r < 0) { - free(m); - goto fail; - } - - break; - } - - case _SD_BUS_VTABLE_SIGNAL: - - if (!member_name_is_valid(v->x.signal.member) || - !signature_is_valid(strempty(v->x.signal.signature), false) || - v->flags & SD_BUS_VTABLE_UNPRIVILEGED) { - r = -EINVAL; - goto fail; - } - - break; - - default: - r = -EINVAL; - goto fail; - } - } - - s->node_vtable.node = n; - LIST_INSERT_AFTER(vtables, n->vtables, existing, &s->node_vtable); - bus->nodes_modified = true; - - if (slot) - *slot = s; - - return 0; - -fail: - sd_bus_slot_unref(s); - bus_node_gc(bus, n); - - return r; -} - -_public_ int sd_bus_add_object_vtable( - sd_bus *bus, - sd_bus_slot **slot, - const char *path, - const char *interface, - const sd_bus_vtable *vtable, - void *userdata) { - - return add_object_vtable_internal(bus, slot, path, interface, vtable, false, NULL, userdata); -} - -_public_ int sd_bus_add_fallback_vtable( - sd_bus *bus, - sd_bus_slot **slot, - const char *prefix, - const char *interface, - const sd_bus_vtable *vtable, - sd_bus_object_find_t find, - void *userdata) { - - return add_object_vtable_internal(bus, slot, prefix, interface, vtable, true, find, userdata); -} - -_public_ int sd_bus_add_node_enumerator( - sd_bus *bus, - sd_bus_slot **slot, - const char *path, - sd_bus_node_enumerator_t callback, - void *userdata) { - - sd_bus_slot *s; - struct node *n; - int r; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(callback, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - n = bus_node_allocate(bus, path); - if (!n) - return -ENOMEM; - - s = bus_slot_allocate(bus, !slot, BUS_NODE_ENUMERATOR, sizeof(struct node_enumerator), userdata); - if (!s) { - r = -ENOMEM; - goto fail; - } - - s->node_enumerator.callback = callback; - - s->node_enumerator.node = n; - LIST_PREPEND(enumerators, n->enumerators, &s->node_enumerator); - bus->nodes_modified = true; - - if (slot) - *slot = s; - - return 0; - -fail: - sd_bus_slot_unref(s); - bus_node_gc(bus, n); - - return r; -} - -static int emit_properties_changed_on_interface( - sd_bus *bus, - const char *prefix, - const char *path, - const char *interface, - bool require_fallback, - bool *found_interface, - char **names) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - bool has_invalidating = false, has_changing = false; - struct vtable_member key = {}; - struct node_vtable *c; - struct node *n; - char **property; - void *u = NULL; - int r; - - assert(bus); - assert(prefix); - assert(path); - assert(interface); - assert(found_interface); - - n = hashmap_get(bus->nodes, prefix); - if (!n) - return 0; - - r = sd_bus_message_new_signal(bus, &m, path, "org.freedesktop.DBus.Properties", "PropertiesChanged"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "s", interface); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'a', "{sv}"); - if (r < 0) - return r; - - key.path = prefix; - key.interface = interface; - - LIST_FOREACH(vtables, c, n->vtables) { - if (require_fallback && !c->is_fallback) - continue; - - if (!streq(c->interface, interface)) - continue; - - r = node_vtable_get_userdata(bus, path, c, &u, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - if (r == 0) - continue; - - *found_interface = true; - - if (names) { - /* If the caller specified a list of - * properties we include exactly those in the - * PropertiesChanged message */ - - STRV_FOREACH(property, names) { - struct vtable_member *v; - - assert_return(member_name_is_valid(*property), -EINVAL); - - key.member = *property; - v = hashmap_get(bus->vtable_properties, &key); - if (!v) - return -ENOENT; - - /* If there are two vtables for the same - * interface, let's handle this property when - * we come to that vtable. */ - if (c != v->parent) - continue; - - assert_return(v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE || - v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION, -EDOM); - - assert_return(!(v->vtable->flags & SD_BUS_VTABLE_HIDDEN), -EDOM); - - if (v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) { - has_invalidating = true; - continue; - } - - has_changing = true; - - r = vtable_append_one_property(bus, m, m->path, c, v->vtable, u, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - } - } else { - const sd_bus_vtable *v; - - /* If the caller specified no properties list - * we include all properties that are marked - * as changing in the message. */ - - for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; v++) { - if (v->type != _SD_BUS_VTABLE_PROPERTY && v->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY) - continue; - - if (v->flags & SD_BUS_VTABLE_HIDDEN) - continue; - - if (v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) { - has_invalidating = true; - continue; - } - - if (!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) - continue; - - has_changing = true; - - r = vtable_append_one_property(bus, m, m->path, c, v, u, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - } - } - } - - if (!has_invalidating && !has_changing) - return 0; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'a', "s"); - if (r < 0) - return r; - - if (has_invalidating) { - LIST_FOREACH(vtables, c, n->vtables) { - if (require_fallback && !c->is_fallback) - continue; - - if (!streq(c->interface, interface)) - continue; - - r = node_vtable_get_userdata(bus, path, c, &u, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - if (r == 0) - continue; - - if (names) { - STRV_FOREACH(property, names) { - struct vtable_member *v; - - key.member = *property; - assert_se(v = hashmap_get(bus->vtable_properties, &key)); - assert(c == v->parent); - - if (!(v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) - continue; - - r = sd_bus_message_append(m, "s", *property); - if (r < 0) - return r; - } - } else { - const sd_bus_vtable *v; - - for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; v++) { - if (v->type != _SD_BUS_VTABLE_PROPERTY && v->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY) - continue; - - if (v->flags & SD_BUS_VTABLE_HIDDEN) - continue; - - if (!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) - continue; - - r = sd_bus_message_append(m, "s", v->x.property.member); - if (r < 0) - return r; - } - } - } - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - r = sd_bus_send(bus, m, NULL); - if (r < 0) - return r; - - return 1; -} - -_public_ int sd_bus_emit_properties_changed_strv( - sd_bus *bus, - const char *path, - const char *interface, - char **names) { - - BUS_DONT_DESTROY(bus); - bool found_interface = false; - char *prefix; - int r; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(interface_name_is_valid(interface), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - /* A non-NULL but empty names list means nothing needs to be - generated. A NULL list OTOH indicates that all properties - that are set to EMITS_CHANGE or EMITS_INVALIDATION shall be - included in the PropertiesChanged message. */ - if (names && names[0] == NULL) - return 0; - - do { - bus->nodes_modified = false; - - r = emit_properties_changed_on_interface(bus, path, path, interface, false, &found_interface, names); - if (r != 0) - return r; - if (bus->nodes_modified) - continue; - - prefix = alloca(strlen(path) + 1); - OBJECT_PATH_FOREACH_PREFIX(prefix, path) { - r = emit_properties_changed_on_interface(bus, prefix, path, interface, true, &found_interface, names); - if (r != 0) - return r; - if (bus->nodes_modified) - break; - } - - } while (bus->nodes_modified); - - return found_interface ? 0 : -ENOENT; -} - -_public_ int sd_bus_emit_properties_changed( - sd_bus *bus, - const char *path, - const char *interface, - const char *name, ...) { - - char **names; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(interface_name_is_valid(interface), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (!name) - return 0; - - names = strv_from_stdarg_alloca(name); - - return sd_bus_emit_properties_changed_strv(bus, path, interface, names); -} - -static int object_added_append_all_prefix( - sd_bus *bus, - sd_bus_message *m, - Set *s, - const char *prefix, - const char *path, - bool require_fallback) { - - const char *previous_interface = NULL; - struct node_vtable *c; - struct node *n; - int r; - - assert(bus); - assert(m); - assert(s); - assert(prefix); - assert(path); - - n = hashmap_get(bus->nodes, prefix); - if (!n) - return 0; - - LIST_FOREACH(vtables, c, n->vtables) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - void *u = NULL; - - if (require_fallback && !c->is_fallback) - continue; - - r = node_vtable_get_userdata(bus, path, c, &u, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - if (r == 0) - continue; - - if (!streq_ptr(c->interface, previous_interface)) { - /* If a child-node already handled this interface, we - * skip it on any of its parents. The child vtables - * always fully override any conflicting vtables of - * any parent node. */ - if (set_get(s, c->interface)) - continue; - - r = set_put(s, c->interface); - if (r < 0) - return r; - - if (previous_interface) { - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - } - - r = sd_bus_message_open_container(m, 'e', "sa{sv}"); - if (r < 0) - return r; - r = sd_bus_message_append(m, "s", c->interface); - if (r < 0) - return r; - r = sd_bus_message_open_container(m, 'a', "{sv}"); - if (r < 0) - return r; - - previous_interface = c->interface; - } - - r = vtable_append_all_properties(bus, m, path, c, u, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - } - - if (previous_interface) { - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - } - - return 0; -} - -static int object_added_append_all(sd_bus *bus, sd_bus_message *m, const char *path) { - _cleanup_set_free_ Set *s = NULL; - char *prefix; - int r; - - assert(bus); - assert(m); - assert(path); - - /* - * This appends all interfaces registered on path @path. We first add - * the builtin interfaces, which are always available and handled by - * sd-bus. Then, we add all interfaces registered on the exact node, - * followed by all fallback interfaces registered on any parent prefix. - * - * If an interface is registered multiple times on the same node with - * different vtables, we merge all the properties across all vtables. - * However, if a child node has the same interface registered as one of - * its parent nodes has as fallback, we make the child overwrite the - * parent instead of extending it. Therefore, we keep a "Set" of all - * handled interfaces during parent traversal, so we skip interfaces on - * a parent that were overwritten by a child. - */ - - s = set_new(&string_hash_ops); - if (!s) - return -ENOMEM; - - r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.Peer", 0); - if (r < 0) - return r; - r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.Introspectable", 0); - if (r < 0) - return r; - r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.Properties", 0); - if (r < 0) - return r; - r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.ObjectManager", 0); - if (r < 0) - return r; - - r = object_added_append_all_prefix(bus, m, s, path, path, false); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - - prefix = alloca(strlen(path) + 1); - OBJECT_PATH_FOREACH_PREFIX(prefix, path) { - r = object_added_append_all_prefix(bus, m, s, prefix, path, true); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - } - - return 0; -} - -_public_ int sd_bus_emit_object_added(sd_bus *bus, const char *path) { - BUS_DONT_DESTROY(bus); - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - struct node *object_manager; - int r; - - /* - * This emits an InterfacesAdded signal on the given path, by iterating - * all registered vtables and fallback vtables on the path. All - * properties are queried and included in the signal. - * This call is equivalent to sd_bus_emit_interfaces_added() with an - * explicit list of registered interfaces. However, unlike - * interfaces_added(), this call can figure out the list of supported - * interfaces itself. Furthermore, it properly adds the builtin - * org.freedesktop.DBus.* interfaces. - */ - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - r = bus_find_parent_object_manager(bus, &object_manager, path); - if (r < 0) - return r; - if (r == 0) - return -ESRCH; - - do { - bus->nodes_modified = false; - m = sd_bus_message_unref(m); - - r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded"); - if (r < 0) - return r; - - r = sd_bus_message_append_basic(m, 'o', path); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'a', "{sa{sv}}"); - if (r < 0) - return r; - - r = object_added_append_all(bus, m, path); - if (r < 0) - return r; - - if (bus->nodes_modified) - continue; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - } while (bus->nodes_modified); - - return sd_bus_send(bus, m, NULL); -} - -static int object_removed_append_all_prefix( - sd_bus *bus, - sd_bus_message *m, - Set *s, - const char *prefix, - const char *path, - bool require_fallback) { - - const char *previous_interface = NULL; - struct node_vtable *c; - struct node *n; - int r; - - assert(bus); - assert(m); - assert(s); - assert(prefix); - assert(path); - - n = hashmap_get(bus->nodes, prefix); - if (!n) - return 0; - - LIST_FOREACH(vtables, c, n->vtables) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - void *u = NULL; - - if (require_fallback && !c->is_fallback) - continue; - if (streq_ptr(c->interface, previous_interface)) - continue; - - /* If a child-node already handled this interface, we - * skip it on any of its parents. The child vtables - * always fully override any conflicting vtables of - * any parent node. */ - if (set_get(s, c->interface)) - continue; - - r = node_vtable_get_userdata(bus, path, c, &u, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - if (r == 0) - continue; - - r = set_put(s, c->interface); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "s", c->interface); - if (r < 0) - return r; - - previous_interface = c->interface; - } - - return 0; -} - -static int object_removed_append_all(sd_bus *bus, sd_bus_message *m, const char *path) { - _cleanup_set_free_ Set *s = NULL; - char *prefix; - int r; - - assert(bus); - assert(m); - assert(path); - - /* see sd_bus_emit_object_added() for details */ - - s = set_new(&string_hash_ops); - if (!s) - return -ENOMEM; - - r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.Peer"); - if (r < 0) - return r; - r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.Introspectable"); - if (r < 0) - return r; - r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.Properties"); - if (r < 0) - return r; - r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.ObjectManager"); - if (r < 0) - return r; - - r = object_removed_append_all_prefix(bus, m, s, path, path, false); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - - prefix = alloca(strlen(path) + 1); - OBJECT_PATH_FOREACH_PREFIX(prefix, path) { - r = object_removed_append_all_prefix(bus, m, s, prefix, path, true); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - } - - return 0; -} - -_public_ int sd_bus_emit_object_removed(sd_bus *bus, const char *path) { - BUS_DONT_DESTROY(bus); - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - struct node *object_manager; - int r; - - /* - * This is like sd_bus_emit_object_added(), but emits an - * InterfacesRemoved signal on the given path. This only includes any - * registered interfaces but skips the properties. Note that this will - * call into the find() callbacks of any registered vtable. Therefore, - * you must call this function before destroying/unlinking your object. - * Otherwise, the list of interfaces will be incomplete. However, note - * that this will *NOT* call into any property callback. Therefore, the - * object might be in an "destructed" state, as long as we can find it. - */ - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - r = bus_find_parent_object_manager(bus, &object_manager, path); - if (r < 0) - return r; - if (r == 0) - return -ESRCH; - - do { - bus->nodes_modified = false; - m = sd_bus_message_unref(m); - - r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved"); - if (r < 0) - return r; - - r = sd_bus_message_append_basic(m, 'o', path); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'a', "s"); - if (r < 0) - return r; - - r = object_removed_append_all(bus, m, path); - if (r < 0) - return r; - - if (bus->nodes_modified) - continue; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - } while (bus->nodes_modified); - - return sd_bus_send(bus, m, NULL); -} - -static int interfaces_added_append_one_prefix( - sd_bus *bus, - sd_bus_message *m, - const char *prefix, - const char *path, - const char *interface, - bool require_fallback) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - bool found_interface = false; - struct node_vtable *c; - struct node *n; - void *u = NULL; - int r; - - assert(bus); - assert(m); - assert(prefix); - assert(path); - assert(interface); - - n = hashmap_get(bus->nodes, prefix); - if (!n) - return 0; - - LIST_FOREACH(vtables, c, n->vtables) { - if (require_fallback && !c->is_fallback) - continue; - - if (!streq(c->interface, interface)) - continue; - - r = node_vtable_get_userdata(bus, path, c, &u, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - if (r == 0) - continue; - - if (!found_interface) { - r = sd_bus_message_append_basic(m, 's', interface); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'a', "{sv}"); - if (r < 0) - return r; - - found_interface = true; - } - - r = vtable_append_all_properties(bus, m, path, c, u, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - } - - if (found_interface) { - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - } - - return found_interface; -} - -static int interfaces_added_append_one( - sd_bus *bus, - sd_bus_message *m, - const char *path, - const char *interface) { - - char *prefix; - int r; - - assert(bus); - assert(m); - assert(path); - assert(interface); - - r = interfaces_added_append_one_prefix(bus, m, path, path, interface, false); - if (r != 0) - return r; - if (bus->nodes_modified) - return 0; - - prefix = alloca(strlen(path) + 1); - OBJECT_PATH_FOREACH_PREFIX(prefix, path) { - r = interfaces_added_append_one_prefix(bus, m, prefix, path, interface, true); - if (r != 0) - return r; - if (bus->nodes_modified) - return 0; - } - - return -ENOENT; -} - -_public_ int sd_bus_emit_interfaces_added_strv(sd_bus *bus, const char *path, char **interfaces) { - BUS_DONT_DESTROY(bus); - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - struct node *object_manager; - char **i; - int r; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (strv_isempty(interfaces)) - return 0; - - r = bus_find_parent_object_manager(bus, &object_manager, path); - if (r < 0) - return r; - if (r == 0) - return -ESRCH; - - do { - bus->nodes_modified = false; - m = sd_bus_message_unref(m); - - r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded"); - if (r < 0) - return r; - - r = sd_bus_message_append_basic(m, 'o', path); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'a', "{sa{sv}}"); - if (r < 0) - return r; - - STRV_FOREACH(i, interfaces) { - assert_return(interface_name_is_valid(*i), -EINVAL); - - r = sd_bus_message_open_container(m, 'e', "sa{sv}"); - if (r < 0) - return r; - - r = interfaces_added_append_one(bus, m, path, *i); - if (r < 0) - return r; - - if (bus->nodes_modified) - break; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - } - - if (bus->nodes_modified) - continue; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - } while (bus->nodes_modified); - - return sd_bus_send(bus, m, NULL); -} - -_public_ int sd_bus_emit_interfaces_added(sd_bus *bus, const char *path, const char *interface, ...) { - char **interfaces; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - interfaces = strv_from_stdarg_alloca(interface); - - return sd_bus_emit_interfaces_added_strv(bus, path, interfaces); -} - -_public_ int sd_bus_emit_interfaces_removed_strv(sd_bus *bus, const char *path, char **interfaces) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - struct node *object_manager; - int r; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (strv_isempty(interfaces)) - return 0; - - r = bus_find_parent_object_manager(bus, &object_manager, path); - if (r < 0) - return r; - if (r == 0) - return -ESRCH; - - r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved"); - if (r < 0) - return r; - - r = sd_bus_message_append_basic(m, 'o', path); - if (r < 0) - return r; - - r = sd_bus_message_append_strv(m, interfaces); - if (r < 0) - return r; - - return sd_bus_send(bus, m, NULL); -} - -_public_ int sd_bus_emit_interfaces_removed(sd_bus *bus, const char *path, const char *interface, ...) { - char **interfaces; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - interfaces = strv_from_stdarg_alloca(interface); - - return sd_bus_emit_interfaces_removed_strv(bus, path, interfaces); -} - -_public_ int sd_bus_add_object_manager(sd_bus *bus, sd_bus_slot **slot, const char *path) { - sd_bus_slot *s; - struct node *n; - int r; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - n = bus_node_allocate(bus, path); - if (!n) - return -ENOMEM; - - s = bus_slot_allocate(bus, !slot, BUS_NODE_OBJECT_MANAGER, sizeof(struct node_object_manager), NULL); - if (!s) { - r = -ENOMEM; - goto fail; - } - - s->node_object_manager.node = n; - LIST_PREPEND(object_managers, n->object_managers, &s->node_object_manager); - bus->nodes_modified = true; - - if (slot) - *slot = s; - - return 0; - -fail: - sd_bus_slot_unref(s); - bus_node_gc(bus, n); - - return r; -} diff --git a/src/libsystemd/sd-bus/bus-objects.h b/src/libsystemd/sd-bus/bus-objects.h deleted file mode 100644 index e0b8c534ed..0000000000 --- a/src/libsystemd/sd-bus/bus-objects.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "bus-internal.h" - -int bus_process_object(sd_bus *bus, sd_bus_message *m); -void bus_node_gc(sd_bus *b, struct node *n); diff --git a/src/libsystemd/sd-bus/bus-protocol.h b/src/libsystemd/sd-bus/bus-protocol.h deleted file mode 100644 index 9d180cb284..0000000000 --- a/src/libsystemd/sd-bus/bus-protocol.h +++ /dev/null @@ -1,180 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "macro.h" - -/* Packet header */ - -struct _packed_ bus_header { - /* The first four fields are identical for dbus1, and dbus2 */ - uint8_t endian; - uint8_t type; - uint8_t flags; - uint8_t version; - - union _packed_ { - /* dbus1: Used for SOCK_STREAM connections */ - struct _packed_ { - uint32_t body_size; - - /* Note that what the bus spec calls "serial" we'll call - "cookie" instead, because we don't want to imply that the - cookie was in any way monotonically increasing. */ - uint32_t serial; - uint32_t fields_size; - } dbus1; - - /* dbus2: Used for kdbus connections */ - struct _packed_ { - uint32_t _reserved; - uint64_t cookie; - } dbus2; - - /* Note that both header versions have the same size! */ - }; -}; - -/* Endianness */ - -enum { - _BUS_INVALID_ENDIAN = 0, - BUS_LITTLE_ENDIAN = 'l', - BUS_BIG_ENDIAN = 'B', -#if __BYTE_ORDER == __BIG_ENDIAN - BUS_NATIVE_ENDIAN = BUS_BIG_ENDIAN, - BUS_REVERSE_ENDIAN = BUS_LITTLE_ENDIAN -#else - BUS_NATIVE_ENDIAN = BUS_LITTLE_ENDIAN, - BUS_REVERSE_ENDIAN = BUS_BIG_ENDIAN -#endif -}; - -/* Flags */ - -enum { - BUS_MESSAGE_NO_REPLY_EXPECTED = 1, - BUS_MESSAGE_NO_AUTO_START = 2, - BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION = 4, -}; - -/* Header fields */ - -enum { - _BUS_MESSAGE_HEADER_INVALID = 0, - BUS_MESSAGE_HEADER_PATH, - BUS_MESSAGE_HEADER_INTERFACE, - BUS_MESSAGE_HEADER_MEMBER, - BUS_MESSAGE_HEADER_ERROR_NAME, - BUS_MESSAGE_HEADER_REPLY_SERIAL, - BUS_MESSAGE_HEADER_DESTINATION, - BUS_MESSAGE_HEADER_SENDER, - BUS_MESSAGE_HEADER_SIGNATURE, - BUS_MESSAGE_HEADER_UNIX_FDS, - _BUS_MESSAGE_HEADER_MAX -}; - -/* RequestName parameters */ - -enum { - BUS_NAME_ALLOW_REPLACEMENT = 1, - BUS_NAME_REPLACE_EXISTING = 2, - BUS_NAME_DO_NOT_QUEUE = 4 -}; - -/* RequestName returns */ -enum { - BUS_NAME_PRIMARY_OWNER = 1, - BUS_NAME_IN_QUEUE = 2, - BUS_NAME_EXISTS = 3, - BUS_NAME_ALREADY_OWNER = 4 -}; - -/* ReleaseName returns */ -enum { - BUS_NAME_RELEASED = 1, - BUS_NAME_NON_EXISTENT = 2, - BUS_NAME_NOT_OWNER = 3, -}; - -/* StartServiceByName returns */ -enum { - BUS_START_REPLY_SUCCESS = 1, - BUS_START_REPLY_ALREADY_RUNNING = 2, -}; - -#define BUS_INTROSPECT_DOCTYPE \ - "\n" - -#define BUS_INTROSPECT_INTERFACE_PEER \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" - -#define BUS_INTROSPECT_INTERFACE_INTROSPECTABLE \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" - -#define BUS_INTROSPECT_INTERFACE_PROPERTIES \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" - -#define BUS_INTROSPECT_INTERFACE_OBJECT_MANAGER \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" diff --git a/src/libsystemd/sd-bus/bus-signature.c b/src/libsystemd/sd-bus/bus-signature.c deleted file mode 100644 index 7bc243494a..0000000000 --- a/src/libsystemd/sd-bus/bus-signature.c +++ /dev/null @@ -1,158 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "bus-signature.h" -#include "bus-type.h" - -static int signature_element_length_internal( - const char *s, - bool allow_dict_entry, - unsigned array_depth, - unsigned struct_depth, - size_t *l) { - - int r; - - if (!s) - return -EINVAL; - - assert(l); - - if (bus_type_is_basic(*s) || *s == SD_BUS_TYPE_VARIANT) { - *l = 1; - return 0; - } - - if (*s == SD_BUS_TYPE_ARRAY) { - size_t t; - - if (array_depth >= 32) - return -EINVAL; - - r = signature_element_length_internal(s + 1, true, array_depth+1, struct_depth, &t); - if (r < 0) - return r; - - *l = t + 1; - return 0; - } - - if (*s == SD_BUS_TYPE_STRUCT_BEGIN) { - const char *p = s + 1; - - if (struct_depth >= 32) - return -EINVAL; - - while (*p != SD_BUS_TYPE_STRUCT_END) { - size_t t; - - r = signature_element_length_internal(p, false, array_depth, struct_depth+1, &t); - if (r < 0) - return r; - - p += t; - } - - *l = p - s + 1; - return 0; - } - - if (*s == SD_BUS_TYPE_DICT_ENTRY_BEGIN && allow_dict_entry) { - const char *p = s + 1; - unsigned n = 0; - - if (struct_depth >= 32) - return -EINVAL; - - while (*p != SD_BUS_TYPE_DICT_ENTRY_END) { - size_t t; - - if (n == 0 && !bus_type_is_basic(*p)) - return -EINVAL; - - r = signature_element_length_internal(p, false, array_depth, struct_depth+1, &t); - if (r < 0) - return r; - - p += t; - n++; - } - - if (n != 2) - return -EINVAL; - - *l = p - s + 1; - return 0; - } - - return -EINVAL; -} - - -int signature_element_length(const char *s, size_t *l) { - return signature_element_length_internal(s, true, 0, 0, l); -} - -bool signature_is_single(const char *s, bool allow_dict_entry) { - int r; - size_t t; - - if (!s) - return false; - - r = signature_element_length_internal(s, allow_dict_entry, 0, 0, &t); - if (r < 0) - return false; - - return s[t] == 0; -} - -bool signature_is_pair(const char *s) { - - if (!s) - return false; - - if (!bus_type_is_basic(*s)) - return false; - - return signature_is_single(s + 1, false); -} - -bool signature_is_valid(const char *s, bool allow_dict_entry) { - const char *p; - int r; - - if (!s) - return false; - - p = s; - while (*p) { - size_t t; - - r = signature_element_length_internal(p, allow_dict_entry, 0, 0, &t); - if (r < 0) - return false; - - p += t; - } - - return p - s <= 255; -} diff --git a/src/libsystemd/sd-bus/bus-signature.h b/src/libsystemd/sd-bus/bus-signature.h deleted file mode 100644 index 1e0cd7f587..0000000000 --- a/src/libsystemd/sd-bus/bus-signature.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 - -bool signature_is_single(const char *s, bool allow_dict_entry); -bool signature_is_pair(const char *s); -bool signature_is_valid(const char *s, bool allow_dict_entry); - -int signature_element_length(const char *s, size_t *l); diff --git a/src/libsystemd/sd-bus/bus-slot.c b/src/libsystemd/sd-bus/bus-slot.c deleted file mode 100644 index 8e9074c7df..0000000000 --- a/src/libsystemd/sd-bus/bus-slot.c +++ /dev/null @@ -1,286 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -#include "alloc-util.h" -#include "bus-control.h" -#include "bus-objects.h" -#include "bus-slot.h" -#include "string-util.h" - -sd_bus_slot *bus_slot_allocate( - sd_bus *bus, - bool floating, - BusSlotType type, - size_t extra, - void *userdata) { - - sd_bus_slot *slot; - - assert(bus); - - slot = malloc0(offsetof(sd_bus_slot, reply_callback) + extra); - if (!slot) - return NULL; - - slot->n_ref = 1; - slot->type = type; - slot->bus = bus; - slot->floating = floating; - slot->userdata = userdata; - - if (!floating) - sd_bus_ref(bus); - - LIST_PREPEND(slots, bus->slots, slot); - - return slot; -} - -_public_ sd_bus_slot* sd_bus_slot_ref(sd_bus_slot *slot) { - - if (!slot) - return NULL; - - assert(slot->n_ref > 0); - - slot->n_ref++; - return slot; -} - -void bus_slot_disconnect(sd_bus_slot *slot) { - sd_bus *bus; - - assert(slot); - - if (!slot->bus) - return; - - switch (slot->type) { - - case BUS_REPLY_CALLBACK: - - if (slot->reply_callback.cookie != 0) - ordered_hashmap_remove(slot->bus->reply_callbacks, &slot->reply_callback.cookie); - - if (slot->reply_callback.timeout != 0) - prioq_remove(slot->bus->reply_callbacks_prioq, &slot->reply_callback, &slot->reply_callback.prioq_idx); - - break; - - case BUS_FILTER_CALLBACK: - slot->bus->filter_callbacks_modified = true; - LIST_REMOVE(callbacks, slot->bus->filter_callbacks, &slot->filter_callback); - break; - - case BUS_MATCH_CALLBACK: - - if (slot->match_added) - bus_remove_match_internal(slot->bus, slot->match_callback.match_string, slot->match_callback.cookie); - - slot->bus->match_callbacks_modified = true; - bus_match_remove(&slot->bus->match_callbacks, &slot->match_callback); - - free(slot->match_callback.match_string); - - break; - - case BUS_NODE_CALLBACK: - - if (slot->node_callback.node) { - LIST_REMOVE(callbacks, slot->node_callback.node->callbacks, &slot->node_callback); - slot->bus->nodes_modified = true; - - bus_node_gc(slot->bus, slot->node_callback.node); - } - - break; - - case BUS_NODE_ENUMERATOR: - - if (slot->node_enumerator.node) { - LIST_REMOVE(enumerators, slot->node_enumerator.node->enumerators, &slot->node_enumerator); - slot->bus->nodes_modified = true; - - bus_node_gc(slot->bus, slot->node_enumerator.node); - } - - break; - - case BUS_NODE_OBJECT_MANAGER: - - if (slot->node_object_manager.node) { - LIST_REMOVE(object_managers, slot->node_object_manager.node->object_managers, &slot->node_object_manager); - slot->bus->nodes_modified = true; - - bus_node_gc(slot->bus, slot->node_object_manager.node); - } - - break; - - case BUS_NODE_VTABLE: - - if (slot->node_vtable.node && slot->node_vtable.interface && slot->node_vtable.vtable) { - const sd_bus_vtable *v; - - for (v = slot->node_vtable.vtable; v->type != _SD_BUS_VTABLE_END; v++) { - struct vtable_member *x = NULL; - - switch (v->type) { - - case _SD_BUS_VTABLE_METHOD: { - struct vtable_member key; - - key.path = slot->node_vtable.node->path; - key.interface = slot->node_vtable.interface; - key.member = v->x.method.member; - - x = hashmap_remove(slot->bus->vtable_methods, &key); - break; - } - - case _SD_BUS_VTABLE_PROPERTY: - case _SD_BUS_VTABLE_WRITABLE_PROPERTY: { - struct vtable_member key; - - key.path = slot->node_vtable.node->path; - key.interface = slot->node_vtable.interface; - key.member = v->x.method.member; - - - x = hashmap_remove(slot->bus->vtable_properties, &key); - break; - }} - - free(x); - } - } - - free(slot->node_vtable.interface); - - if (slot->node_vtable.node) { - LIST_REMOVE(vtables, slot->node_vtable.node->vtables, &slot->node_vtable); - slot->bus->nodes_modified = true; - - bus_node_gc(slot->bus, slot->node_vtable.node); - } - - break; - - default: - assert_not_reached("Wut? Unknown slot type?"); - } - - bus = slot->bus; - - slot->type = _BUS_SLOT_INVALID; - slot->bus = NULL; - LIST_REMOVE(slots, bus->slots, slot); - - if (!slot->floating) - sd_bus_unref(bus); -} - -_public_ sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) { - - if (!slot) - return NULL; - - assert(slot->n_ref > 0); - - if (slot->n_ref > 1) { - slot->n_ref--; - return NULL; - } - - bus_slot_disconnect(slot); - free(slot->description); - free(slot); - - return NULL; -} - -_public_ sd_bus* sd_bus_slot_get_bus(sd_bus_slot *slot) { - assert_return(slot, NULL); - - return slot->bus; -} - -_public_ void *sd_bus_slot_get_userdata(sd_bus_slot *slot) { - assert_return(slot, NULL); - - return slot->userdata; -} - -_public_ void *sd_bus_slot_set_userdata(sd_bus_slot *slot, void *userdata) { - void *ret; - - assert_return(slot, NULL); - - ret = slot->userdata; - slot->userdata = userdata; - - return ret; -} - -_public_ sd_bus_message *sd_bus_slot_get_current_message(sd_bus_slot *slot) { - assert_return(slot, NULL); - assert_return(slot->type >= 0, NULL); - - if (slot->bus->current_slot != slot) - return NULL; - - return slot->bus->current_message; -} - -_public_ sd_bus_message_handler_t sd_bus_slot_get_current_handler(sd_bus_slot *slot) { - assert_return(slot, NULL); - assert_return(slot->type >= 0, NULL); - - if (slot->bus->current_slot != slot) - return NULL; - - return slot->bus->current_handler; -} - -_public_ void* sd_bus_slot_get_current_userdata(sd_bus_slot *slot) { - assert_return(slot, NULL); - assert_return(slot->type >= 0, NULL); - - if (slot->bus->current_slot != slot) - return NULL; - - return slot->bus->current_userdata; -} - -_public_ int sd_bus_slot_set_description(sd_bus_slot *slot, const char *description) { - assert_return(slot, -EINVAL); - - return free_and_strdup(&slot->description, description); -} - -_public_ int sd_bus_slot_get_description(sd_bus_slot *slot, const char **description) { - assert_return(slot, -EINVAL); - assert_return(description, -EINVAL); - assert_return(slot->description, -ENXIO); - - *description = slot->description; - return 0; -} diff --git a/src/libsystemd/sd-bus/bus-slot.h b/src/libsystemd/sd-bus/bus-slot.h deleted file mode 100644 index 3b8b94dc6b..0000000000 --- a/src/libsystemd/sd-bus/bus-slot.h +++ /dev/null @@ -1,28 +0,0 @@ -#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 "sd-bus.h" - -#include "bus-internal.h" - -sd_bus_slot *bus_slot_allocate(sd_bus *bus, bool floating, BusSlotType type, size_t extra, void *userdata); - -void bus_slot_disconnect(sd_bus_slot *slot); diff --git a/src/libsystemd/sd-bus/bus-socket.c b/src/libsystemd/sd-bus/bus-socket.c deleted file mode 100644 index f1e2a06050..0000000000 --- a/src/libsystemd/sd-bus/bus-socket.c +++ /dev/null @@ -1,1064 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 -#include -#include - -#include "sd-bus.h" -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "bus-internal.h" -#include "bus-message.h" -#include "bus-socket.h" -#include "fd-util.h" -#include "formats-util.h" -#include "hexdecoct.h" -#include "macro.h" -#include "missing.h" -#include "selinux-util.h" -#include "signal-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "user-util.h" -#include "utf8.h" -#include "util.h" - -#define SNDBUF_SIZE (8*1024*1024) - -static void iovec_advance(struct iovec iov[], unsigned *idx, size_t size) { - - while (size > 0) { - struct iovec *i = iov + *idx; - - if (i->iov_len > size) { - i->iov_base = (uint8_t*) i->iov_base + size; - i->iov_len -= size; - return; - } - - size -= i->iov_len; - - i->iov_base = NULL; - i->iov_len = 0; - - (*idx)++; - } -} - -static int append_iovec(sd_bus_message *m, const void *p, size_t sz) { - assert(m); - assert(p); - assert(sz > 0); - - m->iovec[m->n_iovec].iov_base = (void*) p; - m->iovec[m->n_iovec].iov_len = sz; - m->n_iovec++; - - return 0; -} - -static int bus_message_setup_iovec(sd_bus_message *m) { - struct bus_body_part *part; - unsigned n, i; - int r; - - assert(m); - assert(m->sealed); - - if (m->n_iovec > 0) - return 0; - - assert(!m->iovec); - - n = 1 + m->n_body_parts; - if (n < ELEMENTSOF(m->iovec_fixed)) - m->iovec = m->iovec_fixed; - else { - m->iovec = new(struct iovec, n); - if (!m->iovec) { - r = -ENOMEM; - goto fail; - } - } - - r = append_iovec(m, m->header, BUS_MESSAGE_BODY_BEGIN(m)); - if (r < 0) - goto fail; - - MESSAGE_FOREACH_PART(part, i, m) { - r = bus_body_part_map(part); - if (r < 0) - goto fail; - - r = append_iovec(m, part->data, part->size); - if (r < 0) - goto fail; - } - - assert(n == m->n_iovec); - - return 0; - -fail: - m->poisoned = true; - return r; -} - -bool bus_socket_auth_needs_write(sd_bus *b) { - - unsigned i; - - if (b->auth_index >= ELEMENTSOF(b->auth_iovec)) - return false; - - for (i = b->auth_index; i < ELEMENTSOF(b->auth_iovec); i++) { - struct iovec *j = b->auth_iovec + i; - - if (j->iov_len > 0) - return true; - } - - return false; -} - -static int bus_socket_write_auth(sd_bus *b) { - ssize_t k; - - assert(b); - assert(b->state == BUS_AUTHENTICATING); - - if (!bus_socket_auth_needs_write(b)) - return 0; - - if (b->prefer_writev) - k = writev(b->output_fd, b->auth_iovec + b->auth_index, ELEMENTSOF(b->auth_iovec) - b->auth_index); - else { - struct msghdr mh; - zero(mh); - - mh.msg_iov = b->auth_iovec + b->auth_index; - mh.msg_iovlen = ELEMENTSOF(b->auth_iovec) - b->auth_index; - - k = sendmsg(b->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); - if (k < 0 && errno == ENOTSOCK) { - b->prefer_writev = true; - k = writev(b->output_fd, b->auth_iovec + b->auth_index, ELEMENTSOF(b->auth_iovec) - b->auth_index); - } - } - - if (k < 0) - return errno == EAGAIN ? 0 : -errno; - - iovec_advance(b->auth_iovec, &b->auth_index, (size_t) k); - return 1; -} - -static int bus_socket_auth_verify_client(sd_bus *b) { - char *e, *f, *start; - sd_id128_t peer; - unsigned i; - int r; - - assert(b); - - /* We expect two response lines: "OK" and possibly - * "AGREE_UNIX_FD" */ - - e = memmem_safe(b->rbuffer, b->rbuffer_size, "\r\n", 2); - if (!e) - return 0; - - if (b->hello_flags & KDBUS_HELLO_ACCEPT_FD) { - f = memmem(e + 2, b->rbuffer_size - (e - (char*) b->rbuffer) - 2, "\r\n", 2); - if (!f) - return 0; - - start = f + 2; - } else { - f = NULL; - start = e + 2; - } - - /* Nice! We got all the lines we need. First check the OK - * line */ - - if (e - (char*) b->rbuffer != 3 + 32) - return -EPERM; - - if (memcmp(b->rbuffer, "OK ", 3)) - return -EPERM; - - b->auth = b->anonymous_auth ? BUS_AUTH_ANONYMOUS : BUS_AUTH_EXTERNAL; - - for (i = 0; i < 32; i += 2) { - int x, y; - - x = unhexchar(((char*) b->rbuffer)[3 + i]); - y = unhexchar(((char*) b->rbuffer)[3 + i + 1]); - - if (x < 0 || y < 0) - return -EINVAL; - - peer.bytes[i/2] = ((uint8_t) x << 4 | (uint8_t) y); - } - - if (!sd_id128_equal(b->server_id, SD_ID128_NULL) && - !sd_id128_equal(b->server_id, peer)) - return -EPERM; - - b->server_id = peer; - - /* And possibly check the second line, too */ - - if (f) - b->can_fds = - (f - e == strlen("\r\nAGREE_UNIX_FD")) && - memcmp(e + 2, "AGREE_UNIX_FD", strlen("AGREE_UNIX_FD")) == 0; - - b->rbuffer_size -= (start - (char*) b->rbuffer); - memmove(b->rbuffer, start, b->rbuffer_size); - - r = bus_start_running(b); - if (r < 0) - return r; - - return 1; -} - -static bool line_equals(const char *s, size_t m, const char *line) { - size_t l; - - l = strlen(line); - if (l != m) - return false; - - return memcmp(s, line, l) == 0; -} - -static bool line_begins(const char *s, size_t m, const char *word) { - size_t l; - - l = strlen(word); - if (m < l) - return false; - - if (memcmp(s, word, l) != 0) - return false; - - return m == l || (m > l && s[l] == ' '); -} - -static int verify_anonymous_token(sd_bus *b, const char *p, size_t l) { - _cleanup_free_ char *token = NULL; - size_t len; - int r; - - if (!b->anonymous_auth) - return 0; - - if (l <= 0) - return 1; - - assert(p[0] == ' '); - p++; l--; - - if (l % 2 != 0) - return 0; - - r = unhexmem(p, l, (void **) &token, &len); - if (r < 0) - return 0; - - if (memchr(token, 0, len)) - return 0; - - return !!utf8_is_valid(token); -} - -static int verify_external_token(sd_bus *b, const char *p, size_t l) { - _cleanup_free_ char *token = NULL; - size_t len; - uid_t u; - int r; - - /* We don't do any real authentication here. Instead, we if - * the owner of this bus wanted authentication he should have - * checked SO_PEERCRED before even creating the bus object. */ - - if (!b->anonymous_auth && !b->ucred_valid) - return 0; - - if (l <= 0) - return 1; - - assert(p[0] == ' '); - p++; l--; - - if (l % 2 != 0) - return 0; - - r = unhexmem(p, l, (void**) &token, &len); - if (r < 0) - return 0; - - if (memchr(token, 0, len)) - return 0; - - r = parse_uid(token, &u); - if (r < 0) - return 0; - - /* We ignore the passed value if anonymous authentication is - * on anyway. */ - if (!b->anonymous_auth && u != b->ucred.uid) - return 0; - - return 1; -} - -static int bus_socket_auth_write(sd_bus *b, const char *t) { - char *p; - size_t l; - - assert(b); - assert(t); - - /* We only make use of the first iovec */ - assert(b->auth_index == 0 || b->auth_index == 1); - - l = strlen(t); - p = malloc(b->auth_iovec[0].iov_len + l); - if (!p) - return -ENOMEM; - - memcpy_safe(p, b->auth_iovec[0].iov_base, b->auth_iovec[0].iov_len); - memcpy(p + b->auth_iovec[0].iov_len, t, l); - - b->auth_iovec[0].iov_base = p; - b->auth_iovec[0].iov_len += l; - - free(b->auth_buffer); - b->auth_buffer = p; - b->auth_index = 0; - return 0; -} - -static int bus_socket_auth_write_ok(sd_bus *b) { - char t[3 + 32 + 2 + 1]; - - assert(b); - - xsprintf(t, "OK " SD_ID128_FORMAT_STR "\r\n", SD_ID128_FORMAT_VAL(b->server_id)); - - return bus_socket_auth_write(b, t); -} - -static int bus_socket_auth_verify_server(sd_bus *b) { - char *e; - const char *line; - size_t l; - bool processed = false; - int r; - - assert(b); - - if (b->rbuffer_size < 1) - return 0; - - /* First char must be a NUL byte */ - if (*(char*) b->rbuffer != 0) - return -EIO; - - if (b->rbuffer_size < 3) - return 0; - - /* Begin with the first line */ - if (b->auth_rbegin <= 0) - b->auth_rbegin = 1; - - for (;;) { - /* Check if line is complete */ - line = (char*) b->rbuffer + b->auth_rbegin; - e = memmem(line, b->rbuffer_size - b->auth_rbegin, "\r\n", 2); - if (!e) - return processed; - - l = e - line; - - if (line_begins(line, l, "AUTH ANONYMOUS")) { - - r = verify_anonymous_token(b, line + 14, l - 14); - if (r < 0) - return r; - if (r == 0) - r = bus_socket_auth_write(b, "REJECTED\r\n"); - else { - b->auth = BUS_AUTH_ANONYMOUS; - r = bus_socket_auth_write_ok(b); - } - - } else if (line_begins(line, l, "AUTH EXTERNAL")) { - - r = verify_external_token(b, line + 13, l - 13); - if (r < 0) - return r; - if (r == 0) - r = bus_socket_auth_write(b, "REJECTED\r\n"); - else { - b->auth = BUS_AUTH_EXTERNAL; - r = bus_socket_auth_write_ok(b); - } - - } else if (line_begins(line, l, "AUTH")) - r = bus_socket_auth_write(b, "REJECTED EXTERNAL ANONYMOUS\r\n"); - else if (line_equals(line, l, "CANCEL") || - line_begins(line, l, "ERROR")) { - - b->auth = _BUS_AUTH_INVALID; - r = bus_socket_auth_write(b, "REJECTED\r\n"); - - } else if (line_equals(line, l, "BEGIN")) { - - if (b->auth == _BUS_AUTH_INVALID) - r = bus_socket_auth_write(b, "ERROR\r\n"); - else { - /* We can't leave from the auth phase - * before we haven't written - * everything queued, so let's check - * that */ - - if (bus_socket_auth_needs_write(b)) - return 1; - - b->rbuffer_size -= (e + 2 - (char*) b->rbuffer); - memmove(b->rbuffer, e + 2, b->rbuffer_size); - return bus_start_running(b); - } - - } else if (line_begins(line, l, "DATA")) { - - if (b->auth == _BUS_AUTH_INVALID) - r = bus_socket_auth_write(b, "ERROR\r\n"); - else { - if (b->auth == BUS_AUTH_ANONYMOUS) - r = verify_anonymous_token(b, line + 4, l - 4); - else - r = verify_external_token(b, line + 4, l - 4); - - if (r < 0) - return r; - if (r == 0) { - b->auth = _BUS_AUTH_INVALID; - r = bus_socket_auth_write(b, "REJECTED\r\n"); - } else - r = bus_socket_auth_write_ok(b); - } - } else if (line_equals(line, l, "NEGOTIATE_UNIX_FD")) { - if (b->auth == _BUS_AUTH_INVALID || !(b->hello_flags & KDBUS_HELLO_ACCEPT_FD)) - r = bus_socket_auth_write(b, "ERROR\r\n"); - else { - b->can_fds = true; - r = bus_socket_auth_write(b, "AGREE_UNIX_FD\r\n"); - } - } else - r = bus_socket_auth_write(b, "ERROR\r\n"); - - if (r < 0) - return r; - - b->auth_rbegin = e + 2 - (char*) b->rbuffer; - - processed = true; - } -} - -static int bus_socket_auth_verify(sd_bus *b) { - assert(b); - - if (b->is_server) - return bus_socket_auth_verify_server(b); - else - return bus_socket_auth_verify_client(b); -} - -static int bus_socket_read_auth(sd_bus *b) { - struct msghdr mh; - struct iovec iov = {}; - size_t n; - ssize_t k; - int r; - void *p; - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(int) * BUS_FDS_MAX)]; - } control; - bool handle_cmsg = false; - - assert(b); - assert(b->state == BUS_AUTHENTICATING); - - r = bus_socket_auth_verify(b); - if (r != 0) - return r; - - n = MAX(256u, b->rbuffer_size * 2); - - if (n > BUS_AUTH_SIZE_MAX) - n = BUS_AUTH_SIZE_MAX; - - if (b->rbuffer_size >= n) - return -ENOBUFS; - - p = realloc(b->rbuffer, n); - if (!p) - return -ENOMEM; - - b->rbuffer = p; - - iov.iov_base = (uint8_t*) b->rbuffer + b->rbuffer_size; - iov.iov_len = n - b->rbuffer_size; - - if (b->prefer_readv) - k = readv(b->input_fd, &iov, 1); - else { - zero(mh); - mh.msg_iov = &iov; - mh.msg_iovlen = 1; - mh.msg_control = &control; - mh.msg_controllen = sizeof(control); - - k = recvmsg(b->input_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); - if (k < 0 && errno == ENOTSOCK) { - b->prefer_readv = true; - k = readv(b->input_fd, &iov, 1); - } else - handle_cmsg = true; - } - if (k < 0) - return errno == EAGAIN ? 0 : -errno; - if (k == 0) - return -ECONNRESET; - - b->rbuffer_size += k; - - if (handle_cmsg) { - struct cmsghdr *cmsg; - - CMSG_FOREACH(cmsg, &mh) - if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SCM_RIGHTS) { - int j; - - /* Whut? We received fds during the auth - * protocol? Somebody is playing games with - * us. Close them all, and fail */ - j = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); - close_many((int*) CMSG_DATA(cmsg), j); - return -EIO; - } else - log_debug("Got unexpected auxiliary data with level=%d and type=%d", - cmsg->cmsg_level, cmsg->cmsg_type); - } - - r = bus_socket_auth_verify(b); - if (r != 0) - return r; - - return 1; -} - -void bus_socket_setup(sd_bus *b) { - assert(b); - - /* Increase the buffers to 8 MB */ - fd_inc_rcvbuf(b->input_fd, SNDBUF_SIZE); - fd_inc_sndbuf(b->output_fd, SNDBUF_SIZE); - - b->is_kernel = false; - b->message_version = 1; - b->message_endian = 0; -} - -static void bus_get_peercred(sd_bus *b) { - int r; - - assert(b); - - /* Get the peer for socketpair() sockets */ - b->ucred_valid = getpeercred(b->input_fd, &b->ucred) >= 0; - - /* Get the SELinux context of the peer */ - if (mac_selinux_have()) { - r = getpeersec(b->input_fd, &b->label); - if (r < 0 && r != -EOPNOTSUPP) - log_debug_errno(r, "Failed to determine peer security context: %m"); - } -} - -static int bus_socket_start_auth_client(sd_bus *b) { - size_t l; - const char *auth_suffix, *auth_prefix; - - assert(b); - - if (b->anonymous_auth) { - auth_prefix = "\0AUTH ANONYMOUS "; - - /* For ANONYMOUS auth we send some arbitrary "trace" string */ - l = 9; - b->auth_buffer = hexmem("anonymous", l); - } else { - char text[DECIMAL_STR_MAX(uid_t) + 1]; - - auth_prefix = "\0AUTH EXTERNAL "; - - xsprintf(text, UID_FMT, geteuid()); - - l = strlen(text); - b->auth_buffer = hexmem(text, l); - } - - if (!b->auth_buffer) - return -ENOMEM; - - if (b->hello_flags & KDBUS_HELLO_ACCEPT_FD) - auth_suffix = "\r\nNEGOTIATE_UNIX_FD\r\nBEGIN\r\n"; - else - auth_suffix = "\r\nBEGIN\r\n"; - - b->auth_iovec[0].iov_base = (void*) auth_prefix; - b->auth_iovec[0].iov_len = 1 + strlen(auth_prefix + 1); - b->auth_iovec[1].iov_base = (void*) b->auth_buffer; - b->auth_iovec[1].iov_len = l * 2; - b->auth_iovec[2].iov_base = (void*) auth_suffix; - b->auth_iovec[2].iov_len = strlen(auth_suffix); - - return bus_socket_write_auth(b); -} - -int bus_socket_start_auth(sd_bus *b) { - assert(b); - - bus_get_peercred(b); - - b->state = BUS_AUTHENTICATING; - b->auth_timeout = now(CLOCK_MONOTONIC) + BUS_DEFAULT_TIMEOUT; - - if (sd_is_socket(b->input_fd, AF_UNIX, 0, 0) <= 0) - b->hello_flags &= ~KDBUS_HELLO_ACCEPT_FD; - - if (b->output_fd != b->input_fd) - if (sd_is_socket(b->output_fd, AF_UNIX, 0, 0) <= 0) - b->hello_flags &= ~KDBUS_HELLO_ACCEPT_FD; - - if (b->is_server) - return bus_socket_read_auth(b); - else - return bus_socket_start_auth_client(b); -} - -int bus_socket_connect(sd_bus *b) { - int r; - - assert(b); - assert(b->input_fd < 0); - assert(b->output_fd < 0); - assert(b->sockaddr.sa.sa_family != AF_UNSPEC); - - b->input_fd = socket(b->sockaddr.sa.sa_family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (b->input_fd < 0) - return -errno; - - b->output_fd = b->input_fd; - - bus_socket_setup(b); - - r = connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size); - if (r < 0) { - if (errno == EINPROGRESS) - return 1; - - return -errno; - } - - return bus_socket_start_auth(b); -} - -int bus_socket_exec(sd_bus *b) { - int s[2], r; - pid_t pid; - - assert(b); - assert(b->input_fd < 0); - assert(b->output_fd < 0); - assert(b->exec_path); - - r = socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, s); - if (r < 0) - return -errno; - - pid = fork(); - if (pid < 0) { - safe_close_pair(s); - return -errno; - } - if (pid == 0) { - /* Child */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - close_all_fds(s+1, 1); - - assert_se(dup3(s[1], STDIN_FILENO, 0) == STDIN_FILENO); - assert_se(dup3(s[1], STDOUT_FILENO, 0) == STDOUT_FILENO); - - if (s[1] != STDIN_FILENO && s[1] != STDOUT_FILENO) - safe_close(s[1]); - - fd_cloexec(STDIN_FILENO, false); - fd_cloexec(STDOUT_FILENO, false); - fd_nonblock(STDIN_FILENO, false); - fd_nonblock(STDOUT_FILENO, false); - - if (b->exec_argv) - execvp(b->exec_path, b->exec_argv); - else { - const char *argv[] = { b->exec_path, NULL }; - execvp(b->exec_path, (char**) argv); - } - - _exit(EXIT_FAILURE); - } - - safe_close(s[1]); - b->output_fd = b->input_fd = s[0]; - - bus_socket_setup(b); - - return bus_socket_start_auth(b); -} - -int bus_socket_take_fd(sd_bus *b) { - assert(b); - - bus_socket_setup(b); - - return bus_socket_start_auth(b); -} - -int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx) { - struct iovec *iov; - ssize_t k; - size_t n; - unsigned j; - int r; - - assert(bus); - assert(m); - assert(idx); - assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); - - if (*idx >= BUS_MESSAGE_SIZE(m)) - return 0; - - r = bus_message_setup_iovec(m); - if (r < 0) - return r; - - n = m->n_iovec * sizeof(struct iovec); - iov = alloca(n); - memcpy_safe(iov, m->iovec, n); - - j = 0; - iovec_advance(iov, &j, *idx); - - if (bus->prefer_writev) - k = writev(bus->output_fd, iov, m->n_iovec); - else { - struct msghdr mh = { - .msg_iov = iov, - .msg_iovlen = m->n_iovec, - }; - - if (m->n_fds > 0) { - struct cmsghdr *control; - - mh.msg_control = control = alloca(CMSG_SPACE(sizeof(int) * m->n_fds)); - mh.msg_controllen = control->cmsg_len = CMSG_LEN(sizeof(int) * m->n_fds); - control->cmsg_level = SOL_SOCKET; - control->cmsg_type = SCM_RIGHTS; - memcpy(CMSG_DATA(control), m->fds, sizeof(int) * m->n_fds); - } - - k = sendmsg(bus->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); - if (k < 0 && errno == ENOTSOCK) { - bus->prefer_writev = true; - k = writev(bus->output_fd, iov, m->n_iovec); - } - } - - if (k < 0) - return errno == EAGAIN ? 0 : -errno; - - *idx += (size_t) k; - return 1; -} - -static int bus_socket_read_message_need(sd_bus *bus, size_t *need) { - uint32_t a, b; - uint8_t e; - uint64_t sum; - - assert(bus); - assert(need); - assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); - - if (bus->rbuffer_size < sizeof(struct bus_header)) { - *need = sizeof(struct bus_header) + 8; - - /* Minimum message size: - * - * Header + - * - * Method Call: +2 string headers - * Signal: +3 string headers - * Method Error: +1 string headers - * +1 uint32 headers - * Method Reply: +1 uint32 headers - * - * A string header is at least 9 bytes - * A uint32 header is at least 8 bytes - * - * Hence the minimum message size of a valid message - * is header + 8 bytes */ - - return 0; - } - - a = ((const uint32_t*) bus->rbuffer)[1]; - b = ((const uint32_t*) bus->rbuffer)[3]; - - e = ((const uint8_t*) bus->rbuffer)[0]; - if (e == BUS_LITTLE_ENDIAN) { - a = le32toh(a); - b = le32toh(b); - } else if (e == BUS_BIG_ENDIAN) { - a = be32toh(a); - b = be32toh(b); - } else - return -EBADMSG; - - sum = (uint64_t) sizeof(struct bus_header) + (uint64_t) ALIGN_TO(b, 8) + (uint64_t) a; - if (sum >= BUS_MESSAGE_SIZE_MAX) - return -ENOBUFS; - - *need = (size_t) sum; - return 0; -} - -static int bus_socket_make_message(sd_bus *bus, size_t size) { - sd_bus_message *t; - void *b; - int r; - - assert(bus); - assert(bus->rbuffer_size >= size); - assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); - - r = bus_rqueue_make_room(bus); - if (r < 0) - return r; - - if (bus->rbuffer_size > size) { - b = memdup((const uint8_t*) bus->rbuffer + size, - bus->rbuffer_size - size); - if (!b) - return -ENOMEM; - } else - b = NULL; - - r = bus_message_from_malloc(bus, - bus->rbuffer, size, - bus->fds, bus->n_fds, - NULL, - &t); - if (r < 0) { - free(b); - return r; - } - - bus->rbuffer = b; - bus->rbuffer_size -= size; - - bus->fds = NULL; - bus->n_fds = 0; - - bus->rqueue[bus->rqueue_size++] = t; - - return 1; -} - -int bus_socket_read_message(sd_bus *bus) { - struct msghdr mh; - struct iovec iov = {}; - ssize_t k; - size_t need; - int r; - void *b; - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(int) * BUS_FDS_MAX)]; - } control; - bool handle_cmsg = false; - - assert(bus); - assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); - - r = bus_socket_read_message_need(bus, &need); - if (r < 0) - return r; - - if (bus->rbuffer_size >= need) - return bus_socket_make_message(bus, need); - - b = realloc(bus->rbuffer, need); - if (!b) - return -ENOMEM; - - bus->rbuffer = b; - - iov.iov_base = (uint8_t*) bus->rbuffer + bus->rbuffer_size; - iov.iov_len = need - bus->rbuffer_size; - - if (bus->prefer_readv) - k = readv(bus->input_fd, &iov, 1); - else { - zero(mh); - mh.msg_iov = &iov; - mh.msg_iovlen = 1; - mh.msg_control = &control; - mh.msg_controllen = sizeof(control); - - k = recvmsg(bus->input_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); - if (k < 0 && errno == ENOTSOCK) { - bus->prefer_readv = true; - k = readv(bus->input_fd, &iov, 1); - } else - handle_cmsg = true; - } - if (k < 0) - return errno == EAGAIN ? 0 : -errno; - if (k == 0) - return -ECONNRESET; - - bus->rbuffer_size += k; - - if (handle_cmsg) { - struct cmsghdr *cmsg; - - CMSG_FOREACH(cmsg, &mh) - if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SCM_RIGHTS) { - int n, *f; - - n = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); - - if (!bus->can_fds) { - /* Whut? We received fds but this - * isn't actually enabled? Close them, - * and fail */ - - close_many((int*) CMSG_DATA(cmsg), n); - return -EIO; - } - - f = realloc(bus->fds, sizeof(int) * (bus->n_fds + n)); - if (!f) { - close_many((int*) CMSG_DATA(cmsg), n); - return -ENOMEM; - } - - memcpy_safe(f + bus->n_fds, CMSG_DATA(cmsg), n * sizeof(int)); - bus->fds = f; - bus->n_fds += n; - } else - log_debug("Got unexpected auxiliary data with level=%d and type=%d", - cmsg->cmsg_level, cmsg->cmsg_type); - } - - r = bus_socket_read_message_need(bus, &need); - if (r < 0) - return r; - - if (bus->rbuffer_size >= need) - return bus_socket_make_message(bus, need); - - return 1; -} - -int bus_socket_process_opening(sd_bus *b) { - int error = 0; - socklen_t slen = sizeof(error); - struct pollfd p = { - .fd = b->output_fd, - .events = POLLOUT, - }; - int r; - - assert(b->state == BUS_OPENING); - - r = poll(&p, 1, 0); - if (r < 0) - return -errno; - - if (!(p.revents & (POLLOUT|POLLERR|POLLHUP))) - return 0; - - r = getsockopt(b->output_fd, SOL_SOCKET, SO_ERROR, &error, &slen); - if (r < 0) - b->last_connect_error = errno; - else if (error != 0) - b->last_connect_error = error; - else if (p.revents & (POLLERR|POLLHUP)) - b->last_connect_error = ECONNREFUSED; - else - return bus_socket_start_auth(b); - - return bus_next_address(b); -} - -int bus_socket_process_authenticating(sd_bus *b) { - int r; - - assert(b); - assert(b->state == BUS_AUTHENTICATING); - - if (now(CLOCK_MONOTONIC) >= b->auth_timeout) - return -ETIMEDOUT; - - r = bus_socket_write_auth(b); - if (r != 0) - return r; - - return bus_socket_read_auth(b); -} diff --git a/src/libsystemd/sd-bus/bus-socket.h b/src/libsystemd/sd-bus/bus-socket.h deleted file mode 100644 index 684feead74..0000000000 --- a/src/libsystemd/sd-bus/bus-socket.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -void bus_socket_setup(sd_bus *b); - -int bus_socket_connect(sd_bus *b); -int bus_socket_exec(sd_bus *b); -int bus_socket_take_fd(sd_bus *b); -int bus_socket_start_auth(sd_bus *b); - -int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx); -int bus_socket_read_message(sd_bus *bus); - -int bus_socket_process_opening(sd_bus *b); -int bus_socket_process_authenticating(sd_bus *b); - -bool bus_socket_auth_needs_write(sd_bus *b); diff --git a/src/libsystemd/sd-bus/bus-track.c b/src/libsystemd/sd-bus/bus-track.c deleted file mode 100644 index 1f436fe560..0000000000 --- a/src/libsystemd/sd-bus/bus-track.c +++ /dev/null @@ -1,337 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -#include "alloc-util.h" -#include "bus-internal.h" -#include "bus-track.h" -#include "bus-util.h" - -struct sd_bus_track { - unsigned n_ref; - sd_bus *bus; - sd_bus_track_handler_t handler; - void *userdata; - Hashmap *names; - LIST_FIELDS(sd_bus_track, queue); - Iterator iterator; - bool in_queue; - bool modified; -}; - -#define MATCH_PREFIX \ - "type='signal'," \ - "sender='org.freedesktop.DBus'," \ - "path='/org/freedesktop/DBus'," \ - "interface='org.freedesktop.DBus'," \ - "member='NameOwnerChanged'," \ - "arg0='" - -#define MATCH_SUFFIX \ - "'" - -#define MATCH_FOR_NAME(name) \ - ({ \ - char *_x; \ - size_t _l = strlen(name); \ - _x = alloca(strlen(MATCH_PREFIX)+_l+strlen(MATCH_SUFFIX)+1); \ - strcpy(stpcpy(stpcpy(_x, MATCH_PREFIX), name), MATCH_SUFFIX); \ - _x; \ - }) - -static void bus_track_add_to_queue(sd_bus_track *track) { - assert(track); - - if (track->in_queue) - return; - - if (!track->handler) - return; - - LIST_PREPEND(queue, track->bus->track_queue, track); - track->in_queue = true; -} - -static void bus_track_remove_from_queue(sd_bus_track *track) { - assert(track); - - if (!track->in_queue) - return; - - LIST_REMOVE(queue, track->bus->track_queue, track); - track->in_queue = false; -} - -_public_ int sd_bus_track_new( - sd_bus *bus, - sd_bus_track **track, - sd_bus_track_handler_t handler, - void *userdata) { - - sd_bus_track *t; - - assert_return(bus, -EINVAL); - assert_return(track, -EINVAL); - - if (!bus->bus_client) - return -EINVAL; - - t = new0(sd_bus_track, 1); - if (!t) - return -ENOMEM; - - t->n_ref = 1; - t->handler = handler; - t->userdata = userdata; - t->bus = sd_bus_ref(bus); - - bus_track_add_to_queue(t); - - *track = t; - return 0; -} - -_public_ sd_bus_track* sd_bus_track_ref(sd_bus_track *track) { - - if (!track) - return NULL; - - assert(track->n_ref > 0); - - track->n_ref++; - - return track; -} - -_public_ sd_bus_track* sd_bus_track_unref(sd_bus_track *track) { - const char *n; - - if (!track) - return NULL; - - assert(track->n_ref > 0); - - if (track->n_ref > 1) { - track->n_ref--; - return NULL; - } - - while ((n = hashmap_first_key(track->names))) - sd_bus_track_remove_name(track, n); - - bus_track_remove_from_queue(track); - hashmap_free(track->names); - sd_bus_unref(track->bus); - free(track); - - return NULL; -} - -static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) { - sd_bus_track *track = userdata; - const char *name, *old, *new; - int r; - - assert(message); - assert(track); - - r = sd_bus_message_read(message, "sss", &name, &old, &new); - if (r < 0) - return 0; - - sd_bus_track_remove_name(track, name); - return 0; -} - -_public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) { - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; - _cleanup_free_ char *n = NULL; - const char *match; - int r; - - assert_return(track, -EINVAL); - assert_return(service_name_is_valid(name), -EINVAL); - - r = hashmap_ensure_allocated(&track->names, &string_hash_ops); - if (r < 0) - return r; - - n = strdup(name); - if (!n) - return -ENOMEM; - - /* First, subscribe to this name */ - match = MATCH_FOR_NAME(n); - r = sd_bus_add_match(track->bus, &slot, match, on_name_owner_changed, track); - if (r < 0) - return r; - - r = hashmap_put(track->names, n, slot); - if (r == -EEXIST) - return 0; - if (r < 0) - return r; - - /* Second, check if it is currently existing, or maybe - * doesn't, or maybe disappeared already. */ - r = sd_bus_get_name_creds(track->bus, n, 0, NULL); - if (r < 0) { - hashmap_remove(track->names, n); - return r; - } - - n = NULL; - slot = NULL; - - bus_track_remove_from_queue(track); - track->modified = true; - - return 1; -} - -_public_ int sd_bus_track_remove_name(sd_bus_track *track, const char *name) { - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; - _cleanup_free_ char *n = NULL; - - assert_return(name, -EINVAL); - - if (!track) - return 0; - - slot = hashmap_remove2(track->names, (char*) name, (void**) &n); - if (!slot) - return 0; - - if (hashmap_isempty(track->names)) - bus_track_add_to_queue(track); - - track->modified = true; - - return 1; -} - -_public_ unsigned sd_bus_track_count(sd_bus_track *track) { - if (!track) - return 0; - - return hashmap_size(track->names); -} - -_public_ const char* sd_bus_track_contains(sd_bus_track *track, const char *name) { - assert_return(track, NULL); - assert_return(name, NULL); - - return hashmap_get(track->names, (void*) name) ? name : NULL; -} - -_public_ const char* sd_bus_track_first(sd_bus_track *track) { - const char *n = NULL; - - if (!track) - return NULL; - - track->modified = false; - track->iterator = ITERATOR_FIRST; - - hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n); - return n; -} - -_public_ const char* sd_bus_track_next(sd_bus_track *track) { - const char *n = NULL; - - if (!track) - return NULL; - - if (track->modified) - return NULL; - - hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n); - return n; -} - -_public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) { - const char *sender; - - assert_return(track, -EINVAL); - assert_return(m, -EINVAL); - - sender = sd_bus_message_get_sender(m); - if (!sender) - return -EINVAL; - - return sd_bus_track_add_name(track, sender); -} - -_public_ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m) { - const char *sender; - - assert_return(track, -EINVAL); - assert_return(m, -EINVAL); - - sender = sd_bus_message_get_sender(m); - if (!sender) - return -EINVAL; - - return sd_bus_track_remove_name(track, sender); -} - -_public_ sd_bus* sd_bus_track_get_bus(sd_bus_track *track) { - assert_return(track, NULL); - - return track->bus; -} - -void bus_track_dispatch(sd_bus_track *track) { - int r; - - assert(track); - assert(track->in_queue); - assert(track->handler); - - bus_track_remove_from_queue(track); - - sd_bus_track_ref(track); - - r = track->handler(track, track->userdata); - if (r < 0) - log_debug_errno(r, "Failed to process track handler: %m"); - else if (r == 0) - bus_track_add_to_queue(track); - - sd_bus_track_unref(track); -} - -_public_ void *sd_bus_track_get_userdata(sd_bus_track *track) { - assert_return(track, NULL); - - return track->userdata; -} - -_public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) { - void *ret; - - assert_return(track, NULL); - - ret = track->userdata; - track->userdata = userdata; - - return ret; -} diff --git a/src/libsystemd/sd-bus/bus-track.h b/src/libsystemd/sd-bus/bus-track.h deleted file mode 100644 index 7d93a727d6..0000000000 --- a/src/libsystemd/sd-bus/bus-track.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -void bus_track_dispatch(sd_bus_track *track); diff --git a/src/libsystemd/sd-bus/bus-type.c b/src/libsystemd/sd-bus/bus-type.c deleted file mode 100644 index c692afc580..0000000000 --- a/src/libsystemd/sd-bus/bus-type.c +++ /dev/null @@ -1,176 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "bus-type.h" - -bool bus_type_is_valid(char c) { - static const char valid[] = { - SD_BUS_TYPE_BYTE, - SD_BUS_TYPE_BOOLEAN, - SD_BUS_TYPE_INT16, - SD_BUS_TYPE_UINT16, - SD_BUS_TYPE_INT32, - SD_BUS_TYPE_UINT32, - SD_BUS_TYPE_INT64, - SD_BUS_TYPE_UINT64, - SD_BUS_TYPE_DOUBLE, - SD_BUS_TYPE_STRING, - SD_BUS_TYPE_OBJECT_PATH, - SD_BUS_TYPE_SIGNATURE, - SD_BUS_TYPE_ARRAY, - SD_BUS_TYPE_VARIANT, - SD_BUS_TYPE_STRUCT, - SD_BUS_TYPE_DICT_ENTRY, - SD_BUS_TYPE_UNIX_FD - }; - - return !!memchr(valid, c, sizeof(valid)); -} - -bool bus_type_is_valid_in_signature(char c) { - static const char valid[] = { - SD_BUS_TYPE_BYTE, - SD_BUS_TYPE_BOOLEAN, - SD_BUS_TYPE_INT16, - SD_BUS_TYPE_UINT16, - SD_BUS_TYPE_INT32, - SD_BUS_TYPE_UINT32, - SD_BUS_TYPE_INT64, - SD_BUS_TYPE_UINT64, - SD_BUS_TYPE_DOUBLE, - SD_BUS_TYPE_STRING, - SD_BUS_TYPE_OBJECT_PATH, - SD_BUS_TYPE_SIGNATURE, - SD_BUS_TYPE_ARRAY, - SD_BUS_TYPE_VARIANT, - SD_BUS_TYPE_STRUCT_BEGIN, - SD_BUS_TYPE_STRUCT_END, - SD_BUS_TYPE_DICT_ENTRY_BEGIN, - SD_BUS_TYPE_DICT_ENTRY_END, - SD_BUS_TYPE_UNIX_FD - }; - - return !!memchr(valid, c, sizeof(valid)); -} - -bool bus_type_is_basic(char c) { - static const char valid[] = { - SD_BUS_TYPE_BYTE, - SD_BUS_TYPE_BOOLEAN, - SD_BUS_TYPE_INT16, - SD_BUS_TYPE_UINT16, - SD_BUS_TYPE_INT32, - SD_BUS_TYPE_UINT32, - SD_BUS_TYPE_INT64, - SD_BUS_TYPE_UINT64, - SD_BUS_TYPE_DOUBLE, - SD_BUS_TYPE_STRING, - SD_BUS_TYPE_OBJECT_PATH, - SD_BUS_TYPE_SIGNATURE, - SD_BUS_TYPE_UNIX_FD - }; - - return !!memchr(valid, c, sizeof(valid)); -} - -bool bus_type_is_trivial(char c) { - static const char valid[] = { - SD_BUS_TYPE_BYTE, - SD_BUS_TYPE_BOOLEAN, - SD_BUS_TYPE_INT16, - SD_BUS_TYPE_UINT16, - SD_BUS_TYPE_INT32, - SD_BUS_TYPE_UINT32, - SD_BUS_TYPE_INT64, - SD_BUS_TYPE_UINT64, - SD_BUS_TYPE_DOUBLE - }; - - return !!memchr(valid, c, sizeof(valid)); -} - -bool bus_type_is_container(char c) { - static const char valid[] = { - SD_BUS_TYPE_ARRAY, - SD_BUS_TYPE_VARIANT, - SD_BUS_TYPE_STRUCT, - SD_BUS_TYPE_DICT_ENTRY - }; - - return !!memchr(valid, c, sizeof(valid)); -} - -int bus_type_get_alignment(char c) { - - switch (c) { - case SD_BUS_TYPE_BYTE: - case SD_BUS_TYPE_SIGNATURE: - case SD_BUS_TYPE_VARIANT: - return 1; - - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: - return 2; - - case SD_BUS_TYPE_BOOLEAN: - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_ARRAY: - case SD_BUS_TYPE_UNIX_FD: - return 4; - - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: - case SD_BUS_TYPE_DOUBLE: - case SD_BUS_TYPE_STRUCT: - case SD_BUS_TYPE_STRUCT_BEGIN: - case SD_BUS_TYPE_DICT_ENTRY: - case SD_BUS_TYPE_DICT_ENTRY_BEGIN: - return 8; - } - - return -EINVAL; -} - -int bus_type_get_size(char c) { - - switch (c) { - case SD_BUS_TYPE_BYTE: - return 1; - - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: - return 2; - - case SD_BUS_TYPE_BOOLEAN: - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - case SD_BUS_TYPE_UNIX_FD: - return 4; - - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: - case SD_BUS_TYPE_DOUBLE: - return 8; - } - - return -EINVAL; -} diff --git a/src/libsystemd/sd-bus/bus-type.h b/src/libsystemd/sd-bus/bus-type.h deleted file mode 100644 index 5c87eb5f08..0000000000 --- a/src/libsystemd/sd-bus/bus-type.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -#include "macro.h" - -bool bus_type_is_valid(char c) _const_; -bool bus_type_is_valid_in_signature(char c) _const_; -bool bus_type_is_basic(char c) _const_; -/* "trivial" is systemd's term for what the D-Bus Specification calls - * a "fixed type": that is, a basic type of fixed length */ -bool bus_type_is_trivial(char c) _const_; -bool bus_type_is_container(char c) _const_; - -int bus_type_get_alignment(char c) _const_; -int bus_type_get_size(char c) _const_; diff --git a/src/libsystemd/sd-bus/busctl-introspect.c b/src/libsystemd/sd-bus/busctl-introspect.c deleted file mode 100644 index b09509f8e1..0000000000 --- a/src/libsystemd/sd-bus/busctl-introspect.c +++ /dev/null @@ -1,790 +0,0 @@ -/*** - 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 "sd-bus.h" - -#include "alloc-util.h" -#include "busctl-introspect.h" -#include "string-util.h" -#include "util.h" -#include "xml.h" - -#define NODE_DEPTH_MAX 16 - -typedef struct Context { - const XMLIntrospectOps *ops; - void *userdata; - - char *interface_name; - uint64_t interface_flags; - - char *member_name; - char *member_signature; - char *member_result; - uint64_t member_flags; - bool member_writable; - - const char *current; - void *xml_state; -} Context; - -static void context_reset_member(Context *c) { - free(c->member_name); - free(c->member_signature); - free(c->member_result); - - c->member_name = c->member_signature = c->member_result = NULL; - c->member_flags = 0; - c->member_writable = false; -} - -static void context_reset_interface(Context *c) { - c->interface_name = mfree(c->interface_name); - c->interface_flags = 0; - - context_reset_member(c); -} - -static int parse_xml_annotation(Context *context, uint64_t *flags) { - - enum { - STATE_ANNOTATION, - STATE_NAME, - STATE_VALUE - } state = STATE_ANNOTATION; - - _cleanup_free_ char *field = NULL, *value = NULL; - - assert(context); - - for (;;) { - _cleanup_free_ char *name = NULL; - - int t; - - t = xml_tokenize(&context->current, &name, &context->xml_state, NULL); - if (t < 0) { - log_error("XML parse error."); - return t; - } - - if (t == XML_END) { - log_error("Premature end of XML data."); - return -EBADMSG; - } - - switch (state) { - - case STATE_ANNOTATION: - - if (t == XML_ATTRIBUTE_NAME) { - - if (streq_ptr(name, "name")) - state = STATE_NAME; - - else if (streq_ptr(name, "value")) - state = STATE_VALUE; - - else { - log_error("Unexpected attribute %s.", name); - return -EBADMSG; - } - - } else if (t == XML_TAG_CLOSE_EMPTY || - (t == XML_TAG_CLOSE && streq_ptr(name, "annotation"))) { - - if (flags) { - if (streq_ptr(field, "org.freedesktop.DBus.Deprecated")) { - - if (streq_ptr(value, "true")) - *flags |= SD_BUS_VTABLE_DEPRECATED; - - } else if (streq_ptr(field, "org.freedesktop.DBus.Method.NoReply")) { - - if (streq_ptr(value, "true")) - *flags |= SD_BUS_VTABLE_METHOD_NO_REPLY; - - } else if (streq_ptr(field, "org.freedesktop.DBus.Property.EmitsChangedSignal")) { - - if (streq_ptr(value, "const")) - *flags = (*flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) | SD_BUS_VTABLE_PROPERTY_CONST; - else if (streq_ptr(value, "invalidates")) - *flags = (*flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_CONST)) | SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION; - else if (streq_ptr(value, "false")) - *flags = *flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION); - } - } - - return 0; - - } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { - log_error("Unexpected token in . (1)"); - return -EINVAL; - } - - break; - - case STATE_NAME: - - if (t == XML_ATTRIBUTE_VALUE) { - free(field); - field = name; - name = NULL; - - state = STATE_ANNOTATION; - } else { - log_error("Unexpected token in . (2)"); - return -EINVAL; - } - - break; - - case STATE_VALUE: - - if (t == XML_ATTRIBUTE_VALUE) { - free(value); - value = name; - name = NULL; - - state = STATE_ANNOTATION; - } else { - log_error("Unexpected token in . (3)"); - return -EINVAL; - } - - break; - - default: - assert_not_reached("Bad state"); - } - } -} - -static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth) { - - enum { - STATE_NODE, - STATE_NODE_NAME, - STATE_INTERFACE, - STATE_INTERFACE_NAME, - STATE_METHOD, - STATE_METHOD_NAME, - STATE_METHOD_ARG, - STATE_METHOD_ARG_NAME, - STATE_METHOD_ARG_TYPE, - STATE_METHOD_ARG_DIRECTION, - STATE_SIGNAL, - STATE_SIGNAL_NAME, - STATE_SIGNAL_ARG, - STATE_SIGNAL_ARG_NAME, - STATE_SIGNAL_ARG_TYPE, - STATE_PROPERTY, - STATE_PROPERTY_NAME, - STATE_PROPERTY_TYPE, - STATE_PROPERTY_ACCESS, - } state = STATE_NODE; - - _cleanup_free_ char *node_path = NULL, *argument_type = NULL, *argument_direction = NULL; - const char *np = prefix; - int r; - - assert(context); - assert(prefix); - - if (n_depth > NODE_DEPTH_MAX) { - log_error(" depth too high."); - return -EINVAL; - } - - for (;;) { - _cleanup_free_ char *name = NULL; - int t; - - t = xml_tokenize(&context->current, &name, &context->xml_state, NULL); - if (t < 0) { - log_error("XML parse error."); - return t; - } - - if (t == XML_END) { - log_error("Premature end of XML data."); - return -EBADMSG; - } - - switch (state) { - - case STATE_NODE: - if (t == XML_ATTRIBUTE_NAME) { - - if (streq_ptr(name, "name")) - state = STATE_NODE_NAME; - else { - log_error("Unexpected attribute %s.", name); - return -EBADMSG; - } - - } else if (t == XML_TAG_OPEN) { - - if (streq_ptr(name, "interface")) - state = STATE_INTERFACE; - else if (streq_ptr(name, "node")) { - - r = parse_xml_node(context, np, n_depth+1); - if (r < 0) - return r; - } else { - log_error("Unexpected tag %s.", name); - return -EBADMSG; - } - - } else if (t == XML_TAG_CLOSE_EMPTY || - (t == XML_TAG_CLOSE && streq_ptr(name, "node"))) { - - if (context->ops->on_path) { - r = context->ops->on_path(node_path ? node_path : np, context->userdata); - if (r < 0) - return r; - } - - return 0; - - } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { - log_error("Unexpected token in . (1)"); - return -EINVAL; - } - - break; - - case STATE_NODE_NAME: - - if (t == XML_ATTRIBUTE_VALUE) { - - free(node_path); - - if (name[0] == '/') { - node_path = name; - name = NULL; - } else { - - if (endswith(prefix, "/")) - node_path = strappend(prefix, name); - else - node_path = strjoin(prefix, "/", name, NULL); - if (!node_path) - return log_oom(); - } - - np = node_path; - state = STATE_NODE; - } else { - log_error("Unexpected token in . (2)"); - return -EINVAL; - } - - break; - - case STATE_INTERFACE: - - if (t == XML_ATTRIBUTE_NAME) { - if (streq_ptr(name, "name")) - state = STATE_INTERFACE_NAME; - else { - log_error("Unexpected attribute %s.", name); - return -EBADMSG; - } - - } else if (t == XML_TAG_OPEN) { - if (streq_ptr(name, "method")) - state = STATE_METHOD; - else if (streq_ptr(name, "signal")) - state = STATE_SIGNAL; - else if (streq_ptr(name, "property")) { - context->member_flags |= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE; - state = STATE_PROPERTY; - } else if (streq_ptr(name, "annotation")) { - r = parse_xml_annotation(context, &context->interface_flags); - if (r < 0) - return r; - } else { - log_error("Unexpected tag %s.", name); - return -EINVAL; - } - } else if (t == XML_TAG_CLOSE_EMPTY || - (t == XML_TAG_CLOSE && streq_ptr(name, "interface"))) { - - if (n_depth == 0) { - if (context->ops->on_interface) { - r = context->ops->on_interface(context->interface_name, context->interface_flags, context->userdata); - if (r < 0) - return r; - } - - context_reset_interface(context); - } - - state = STATE_NODE; - - } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { - log_error("Unexpected token in . (1)"); - return -EINVAL; - } - - break; - - case STATE_INTERFACE_NAME: - - if (t == XML_ATTRIBUTE_VALUE) { - if (n_depth == 0) { - free(context->interface_name); - context->interface_name = name; - name = NULL; - } - - state = STATE_INTERFACE; - } else { - log_error("Unexpected token in . (2)"); - return -EINVAL; - } - - break; - - case STATE_METHOD: - - if (t == XML_ATTRIBUTE_NAME) { - if (streq_ptr(name, "name")) - state = STATE_METHOD_NAME; - else { - log_error("Unexpected attribute %s", name); - return -EBADMSG; - } - } else if (t == XML_TAG_OPEN) { - if (streq_ptr(name, "arg")) - state = STATE_METHOD_ARG; - else if (streq_ptr(name, "annotation")) { - r = parse_xml_annotation(context, &context->member_flags); - if (r < 0) - return r; - } else { - log_error("Unexpected tag %s.", name); - return -EINVAL; - } - } else if (t == XML_TAG_CLOSE_EMPTY || - (t == XML_TAG_CLOSE && streq_ptr(name, "method"))) { - - if (n_depth == 0) { - if (context->ops->on_method) { - r = context->ops->on_method(context->interface_name, context->member_name, context->member_signature, context->member_result, context->member_flags, context->userdata); - if (r < 0) - return r; - } - - context_reset_member(context); - } - - state = STATE_INTERFACE; - - } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { - log_error("Unexpected token in (1)."); - return -EINVAL; - } - - break; - - case STATE_METHOD_NAME: - - if (t == XML_ATTRIBUTE_VALUE) { - - if (n_depth == 0) { - free(context->member_name); - context->member_name = name; - name = NULL; - } - - state = STATE_METHOD; - } else { - log_error("Unexpected token in (2)."); - return -EINVAL; - } - - break; - - case STATE_METHOD_ARG: - - if (t == XML_ATTRIBUTE_NAME) { - if (streq_ptr(name, "name")) - state = STATE_METHOD_ARG_NAME; - else if (streq_ptr(name, "type")) - state = STATE_METHOD_ARG_TYPE; - else if (streq_ptr(name, "direction")) - state = STATE_METHOD_ARG_DIRECTION; - else { - log_error("Unexpected method attribute %s.", name); - return -EBADMSG; - } - } else if (t == XML_TAG_OPEN) { - if (streq_ptr(name, "annotation")) { - r = parse_xml_annotation(context, NULL); - if (r < 0) - return r; - } else { - log_error("Unexpected method tag %s.", name); - return -EINVAL; - } - } else if (t == XML_TAG_CLOSE_EMPTY || - (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) { - - if (n_depth == 0) { - - if (argument_type) { - if (!argument_direction || streq(argument_direction, "in")) { - if (!strextend(&context->member_signature, argument_type, NULL)) - return log_oom(); - } else if (streq(argument_direction, "out")) { - if (!strextend(&context->member_result, argument_type, NULL)) - return log_oom(); - } - } - - argument_type = mfree(argument_type); - argument_direction = mfree(argument_direction); - } - - state = STATE_METHOD; - } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { - log_error("Unexpected token in method . (1)"); - return -EINVAL; - } - - break; - - case STATE_METHOD_ARG_NAME: - - if (t == XML_ATTRIBUTE_VALUE) - state = STATE_METHOD_ARG; - else { - log_error("Unexpected token in method . (2)"); - return -EINVAL; - } - - break; - - case STATE_METHOD_ARG_TYPE: - - if (t == XML_ATTRIBUTE_VALUE) { - free(argument_type); - argument_type = name; - name = NULL; - - state = STATE_METHOD_ARG; - } else { - log_error("Unexpected token in method . (3)"); - return -EINVAL; - } - - break; - - case STATE_METHOD_ARG_DIRECTION: - - if (t == XML_ATTRIBUTE_VALUE) { - free(argument_direction); - argument_direction = name; - name = NULL; - - state = STATE_METHOD_ARG; - } else { - log_error("Unexpected token in method . (4)"); - return -EINVAL; - } - - break; - - case STATE_SIGNAL: - - if (t == XML_ATTRIBUTE_NAME) { - if (streq_ptr(name, "name")) - state = STATE_SIGNAL_NAME; - else { - log_error("Unexpected attribute %s.", name); - return -EBADMSG; - } - } else if (t == XML_TAG_OPEN) { - if (streq_ptr(name, "arg")) - state = STATE_SIGNAL_ARG; - else if (streq_ptr(name, "annotation")) { - r = parse_xml_annotation(context, &context->member_flags); - if (r < 0) - return r; - } else { - log_error("Unexpected tag %s.", name); - return -EINVAL; - } - } else if (t == XML_TAG_CLOSE_EMPTY || - (t == XML_TAG_CLOSE && streq_ptr(name, "signal"))) { - - if (n_depth == 0) { - if (context->ops->on_signal) { - r = context->ops->on_signal(context->interface_name, context->member_name, context->member_signature, context->member_flags, context->userdata); - if (r < 0) - return r; - } - - context_reset_member(context); - } - - state = STATE_INTERFACE; - - } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { - log_error("Unexpected token in . (1)"); - return -EINVAL; - } - - break; - - case STATE_SIGNAL_NAME: - - if (t == XML_ATTRIBUTE_VALUE) { - - if (n_depth == 0) { - free(context->member_name); - context->member_name = name; - name = NULL; - } - - state = STATE_SIGNAL; - } else { - log_error("Unexpected token in . (2)"); - return -EINVAL; - } - - break; - - - case STATE_SIGNAL_ARG: - - if (t == XML_ATTRIBUTE_NAME) { - if (streq_ptr(name, "name")) - state = STATE_SIGNAL_ARG_NAME; - else if (streq_ptr(name, "type")) - state = STATE_SIGNAL_ARG_TYPE; - else { - log_error("Unexpected signal attribute %s.", name); - return -EBADMSG; - } - } else if (t == XML_TAG_OPEN) { - if (streq_ptr(name, "annotation")) { - r = parse_xml_annotation(context, NULL); - if (r < 0) - return r; - } else { - log_error("Unexpected signal tag %s.", name); - return -EINVAL; - } - } else if (t == XML_TAG_CLOSE_EMPTY || - (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) { - - if (argument_type) { - if (!strextend(&context->member_signature, argument_type, NULL)) - return log_oom(); - - argument_type = mfree(argument_type); - } - - state = STATE_SIGNAL; - } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { - log_error("Unexpected token in signal (1)."); - return -EINVAL; - } - - break; - - case STATE_SIGNAL_ARG_NAME: - - if (t == XML_ATTRIBUTE_VALUE) - state = STATE_SIGNAL_ARG; - else { - log_error("Unexpected token in signal (2)."); - return -EINVAL; - } - - break; - - case STATE_SIGNAL_ARG_TYPE: - - if (t == XML_ATTRIBUTE_VALUE) { - free(argument_type); - argument_type = name; - name = NULL; - - state = STATE_SIGNAL_ARG; - } else { - log_error("Unexpected token in signal (3)."); - return -EINVAL; - } - - break; - - case STATE_PROPERTY: - - if (t == XML_ATTRIBUTE_NAME) { - if (streq_ptr(name, "name")) - state = STATE_PROPERTY_NAME; - else if (streq_ptr(name, "type")) - state = STATE_PROPERTY_TYPE; - else if (streq_ptr(name, "access")) - state = STATE_PROPERTY_ACCESS; - else { - log_error("Unexpected attribute %s.", name); - return -EBADMSG; - } - } else if (t == XML_TAG_OPEN) { - - if (streq_ptr(name, "annotation")) { - r = parse_xml_annotation(context, &context->member_flags); - if (r < 0) - return r; - } else { - log_error("Unexpected tag %s.", name); - return -EINVAL; - } - - } else if (t == XML_TAG_CLOSE_EMPTY || - (t == XML_TAG_CLOSE && streq_ptr(name, "property"))) { - - if (n_depth == 0) { - if (context->ops->on_property) { - r = context->ops->on_property(context->interface_name, context->member_name, context->member_signature, context->member_writable, context->member_flags, context->userdata); - if (r < 0) - return r; - } - - context_reset_member(context); - } - - state = STATE_INTERFACE; - - } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { - log_error("Unexpected token in . (1)"); - return -EINVAL; - } - - break; - - case STATE_PROPERTY_NAME: - - if (t == XML_ATTRIBUTE_VALUE) { - - if (n_depth == 0) { - free(context->member_name); - context->member_name = name; - name = NULL; - } - state = STATE_PROPERTY; - } else { - log_error("Unexpected token in . (2)"); - return -EINVAL; - } - - break; - - case STATE_PROPERTY_TYPE: - - if (t == XML_ATTRIBUTE_VALUE) { - - if (n_depth == 0) { - free(context->member_signature); - context->member_signature = name; - name = NULL; - } - - state = STATE_PROPERTY; - } else { - log_error("Unexpected token in . (3)"); - return -EINVAL; - } - - break; - - case STATE_PROPERTY_ACCESS: - - if (t == XML_ATTRIBUTE_VALUE) { - - if (streq(name, "readwrite") || streq(name, "write")) - context->member_writable = true; - - state = STATE_PROPERTY; - } else { - log_error("Unexpected token in . (4)"); - return -EINVAL; - } - - break; - } - } -} - -int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospectOps *ops, void *userdata) { - Context context = { - .ops = ops, - .userdata = userdata, - .current = xml, - }; - - int r; - - assert(prefix); - assert(xml); - assert(ops); - - for (;;) { - _cleanup_free_ char *name = NULL; - - r = xml_tokenize(&context.current, &name, &context.xml_state, NULL); - if (r < 0) { - log_error("XML parse error"); - goto finish; - } - - if (r == XML_END) { - r = 0; - break; - } - - if (r == XML_TAG_OPEN) { - - if (streq(name, "node")) { - r = parse_xml_node(&context, prefix, 0); - if (r < 0) - goto finish; - } else { - log_error("Unexpected tag '%s' in introspection data.", name); - r = -EBADMSG; - goto finish; - } - } else if (r != XML_TEXT || !in_charset(name, WHITESPACE)) { - log_error("Unexpected token."); - r = -EBADMSG; - goto finish; - } - } - -finish: - context_reset_interface(&context); - - return r; -} diff --git a/src/libsystemd/sd-bus/busctl-introspect.h b/src/libsystemd/sd-bus/busctl-introspect.h deleted file mode 100644 index d922e352db..0000000000 --- a/src/libsystemd/sd-bus/busctl-introspect.h +++ /dev/null @@ -1,32 +0,0 @@ -#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 - -typedef struct XMLIntrospectOps { - int (*on_path)(const char *path, void *userdata); - int (*on_interface)(const char *name, uint64_t flags, void *userdata); - int (*on_method)(const char *interface, const char *name, const char *signature, const char *result, uint64_t flags, void *userdata); - int (*on_signal)(const char *interface, const char *name, const char *signature, uint64_t flags, void *userdata); - int (*on_property)(const char *interface, const char *name, const char *signature, bool writable, uint64_t flags, void *userdata); -} XMLIntrospectOps; - -int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospectOps *ops, void *userdata); diff --git a/src/libsystemd/sd-bus/busctl.c b/src/libsystemd/sd-bus/busctl.c deleted file mode 100644 index bfe967bfb0..0000000000 --- a/src/libsystemd/sd-bus/busctl.c +++ /dev/null @@ -1,2086 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -#include "alloc-util.h" -#include "bus-dump.h" -#include "bus-internal.h" -#include "bus-signature.h" -#include "bus-type.h" -#include "bus-util.h" -#include "busctl-introspect.h" -#include "escape.h" -#include "fd-util.h" -#include "locale-util.h" -#include "log.h" -#include "pager.h" -#include "parse-util.h" -#include "path-util.h" -#include "set.h" -#include "strv.h" -#include "terminal-util.h" -#include "user-util.h" -#include "util.h" - -static bool arg_no_pager = false; -static bool arg_legend = true; -static char *arg_address = NULL; -static bool arg_unique = false; -static bool arg_acquired = false; -static bool arg_activatable = false; -static bool arg_show_machine = false; -static char **arg_matches = NULL; -static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; -static char *arg_host = NULL; -static bool arg_user = false; -static size_t arg_snaplen = 4096; -static bool arg_list = false; -static bool arg_quiet = false; -static bool arg_verbose = false; -static bool arg_expect_reply = true; -static bool arg_auto_start = true; -static bool arg_allow_interactive_authorization = true; -static bool arg_augment_creds = true; -static usec_t arg_timeout = 0; - -#define NAME_IS_ACQUIRED INT_TO_PTR(1) -#define NAME_IS_ACTIVATABLE INT_TO_PTR(2) - -static int list_bus_names(sd_bus *bus, char **argv) { - _cleanup_strv_free_ char **acquired = NULL, **activatable = NULL; - _cleanup_free_ char **merged = NULL; - _cleanup_hashmap_free_ Hashmap *names = NULL; - char **i; - int r; - size_t max_i = 0; - unsigned n = 0; - void *v; - char *k; - Iterator iterator; - - assert(bus); - - if (!arg_unique && !arg_acquired && !arg_activatable) - arg_unique = arg_acquired = arg_activatable = true; - - r = sd_bus_list_names(bus, (arg_acquired || arg_unique) ? &acquired : NULL, arg_activatable ? &activatable : NULL); - if (r < 0) - return log_error_errno(r, "Failed to list names: %m"); - - pager_open(arg_no_pager, false); - - names = hashmap_new(&string_hash_ops); - if (!names) - return log_oom(); - - STRV_FOREACH(i, acquired) { - max_i = MAX(max_i, strlen(*i)); - - r = hashmap_put(names, *i, NAME_IS_ACQUIRED); - if (r < 0) - return log_error_errno(r, "Failed to add to hashmap: %m"); - } - - STRV_FOREACH(i, activatable) { - max_i = MAX(max_i, strlen(*i)); - - r = hashmap_put(names, *i, NAME_IS_ACTIVATABLE); - if (r < 0 && r != -EEXIST) - return log_error_errno(r, "Failed to add to hashmap: %m"); - } - - merged = new(char*, hashmap_size(names) + 1); - HASHMAP_FOREACH_KEY(v, k, names, iterator) - merged[n++] = k; - - merged[n] = NULL; - strv_sort(merged); - - if (arg_legend) { - printf("%-*s %*s %-*s %-*s %-*s %-*s %-*s %-*s", - (int) max_i, "NAME", 10, "PID", 15, "PROCESS", 16, "USER", 13, "CONNECTION", 25, "UNIT", 10, "SESSION", 19, "DESCRIPTION"); - - if (arg_show_machine) - puts(" MACHINE"); - else - putchar('\n'); - } - - STRV_FOREACH(i, merged) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - sd_id128_t mid; - - if (hashmap_get(names, *i) == NAME_IS_ACTIVATABLE) { - /* Activatable */ - - printf("%-*s", (int) max_i, *i); - printf(" - - - (activatable) - - "); - if (arg_show_machine) - puts(" -"); - else - putchar('\n'); - continue; - - } - - if (!arg_unique && (*i)[0] == ':') - continue; - - if (!arg_acquired && (*i)[0] != ':') - continue; - - printf("%-*s", (int) max_i, *i); - - r = sd_bus_get_name_creds( - bus, *i, - (arg_augment_creds ? SD_BUS_CREDS_AUGMENT : 0) | - SD_BUS_CREDS_EUID|SD_BUS_CREDS_PID|SD_BUS_CREDS_COMM| - SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_SESSION| - SD_BUS_CREDS_DESCRIPTION, &creds); - if (r >= 0) { - const char *unique, *session, *unit, *cn; - pid_t pid; - uid_t uid; - - r = sd_bus_creds_get_pid(creds, &pid); - if (r >= 0) { - const char *comm = NULL; - - sd_bus_creds_get_comm(creds, &comm); - - printf(" %10lu %-15s", (unsigned long) pid, strna(comm)); - } else - fputs(" - - ", stdout); - - r = sd_bus_creds_get_euid(creds, &uid); - if (r >= 0) { - _cleanup_free_ char *u = NULL; - - u = uid_to_name(uid); - if (!u) - return log_oom(); - - if (strlen(u) > 16) - u[16] = 0; - - printf(" %-16s", u); - } else - fputs(" - ", stdout); - - r = sd_bus_creds_get_unique_name(creds, &unique); - if (r >= 0) - printf(" %-13s", unique); - else - fputs(" - ", stdout); - - r = sd_bus_creds_get_unit(creds, &unit); - if (r >= 0) { - _cleanup_free_ char *e; - - e = ellipsize(unit, 25, 100); - if (!e) - return log_oom(); - - printf(" %-25s", e); - } else - fputs(" - ", stdout); - - r = sd_bus_creds_get_session(creds, &session); - if (r >= 0) - printf(" %-10s", session); - else - fputs(" - ", stdout); - - r = sd_bus_creds_get_description(creds, &cn); - if (r >= 0) - printf(" %-19s", cn); - else - fputs(" - ", stdout); - - } else - printf(" - - - - - - - "); - - if (arg_show_machine) { - r = sd_bus_get_name_machine_id(bus, *i, &mid); - if (r >= 0) { - char m[SD_ID128_STRING_MAX]; - printf(" %s\n", sd_id128_to_string(mid, m)); - } else - puts(" -"); - } else - putchar('\n'); - } - - return 0; -} - -static void print_subtree(const char *prefix, const char *path, char **l) { - const char *vertical, *space; - char **n; - - /* We assume the list is sorted. Let's first skip over the - * entry we are looking at. */ - for (;;) { - if (!*l) - return; - - if (!streq(*l, path)) - break; - - l++; - } - - vertical = strjoina(prefix, special_glyph(TREE_VERTICAL)); - space = strjoina(prefix, special_glyph(TREE_SPACE)); - - for (;;) { - bool has_more = false; - - if (!*l || !path_startswith(*l, path)) - break; - - n = l + 1; - for (;;) { - if (!*n || !path_startswith(*n, path)) - break; - - if (!path_startswith(*n, *l)) { - has_more = true; - break; - } - - n++; - } - - printf("%s%s%s\n", prefix, special_glyph(has_more ? TREE_BRANCH : TREE_RIGHT), *l); - - print_subtree(has_more ? vertical : space, *l, l); - l = n; - } -} - -static void print_tree(const char *prefix, char **l) { - - pager_open(arg_no_pager, false); - - prefix = strempty(prefix); - - if (arg_list) { - char **i; - - STRV_FOREACH(i, l) - printf("%s%s\n", prefix, *i); - return; - } - - if (strv_isempty(l)) { - printf("No objects discovered.\n"); - return; - } - - if (streq(l[0], "/") && !l[1]) { - printf("Only root object discovered.\n"); - return; - } - - print_subtree(prefix, "/", l); -} - -static int on_path(const char *path, void *userdata) { - Set *paths = userdata; - int r; - - assert(paths); - - r = set_put_strdup(paths, path); - if (r < 0) - return log_oom(); - - return 0; -} - -static int find_nodes(sd_bus *bus, const char *service, const char *path, Set *paths, bool many) { - static const XMLIntrospectOps ops = { - .on_path = on_path, - }; - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *xml; - int r; - - r = sd_bus_call_method(bus, service, path, "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); - if (r < 0) { - if (many) - printf("Failed to introspect object %s of service %s: %s\n", path, service, bus_error_message(&error, r)); - else - log_error("Failed to introspect object %s of service %s: %s", path, service, bus_error_message(&error, r)); - return r; - } - - r = sd_bus_message_read(reply, "s", &xml); - if (r < 0) - return bus_log_parse_error(r); - - return parse_xml_introspect(path, xml, &ops, paths); -} - -static int tree_one(sd_bus *bus, const char *service, const char *prefix, bool many) { - _cleanup_set_free_free_ Set *paths = NULL, *done = NULL, *failed = NULL; - _cleanup_free_ char **l = NULL; - char *m; - int r; - - paths = set_new(&string_hash_ops); - if (!paths) - return log_oom(); - - done = set_new(&string_hash_ops); - if (!done) - return log_oom(); - - failed = set_new(&string_hash_ops); - if (!failed) - return log_oom(); - - m = strdup("/"); - if (!m) - return log_oom(); - - r = set_put(paths, m); - if (r < 0) { - free(m); - return log_oom(); - } - - for (;;) { - _cleanup_free_ char *p = NULL; - int q; - - p = set_steal_first(paths); - if (!p) - break; - - if (set_contains(done, p) || - set_contains(failed, p)) - continue; - - q = find_nodes(bus, service, p, paths, many); - if (q < 0) { - if (r >= 0) - r = q; - - q = set_put(failed, p); - } else - q = set_put(done, p); - - if (q < 0) - return log_oom(); - - assert(q != 0); - p = NULL; - } - - pager_open(arg_no_pager, false); - - l = set_get_strv(done); - if (!l) - return log_oom(); - - strv_sort(l); - print_tree(prefix, l); - - fflush(stdout); - - return r; -} - -static int tree(sd_bus *bus, char **argv) { - char **i; - int r = 0; - - if (!arg_unique && !arg_acquired) - arg_acquired = true; - - if (strv_length(argv) <= 1) { - _cleanup_strv_free_ char **names = NULL; - bool not_first = false; - - r = sd_bus_list_names(bus, &names, NULL); - if (r < 0) - return log_error_errno(r, "Failed to get name list: %m"); - - pager_open(arg_no_pager, false); - - STRV_FOREACH(i, names) { - int q; - - if (!arg_unique && (*i)[0] == ':') - continue; - - if (!arg_acquired && (*i)[0] == ':') - continue; - - if (not_first) - printf("\n"); - - printf("Service %s%s%s:\n", ansi_highlight(), *i, ansi_normal()); - - q = tree_one(bus, *i, NULL, true); - if (q < 0 && r >= 0) - r = q; - - not_first = true; - } - } else { - STRV_FOREACH(i, argv+1) { - int q; - - if (i > argv+1) - printf("\n"); - - if (argv[2]) { - pager_open(arg_no_pager, false); - printf("Service %s%s%s:\n", ansi_highlight(), *i, ansi_normal()); - } - - q = tree_one(bus, *i, NULL, !!argv[2]); - if (q < 0 && r >= 0) - r = q; - } - } - - return r; -} - -static int format_cmdline(sd_bus_message *m, FILE *f, bool needs_space) { - int r; - - for (;;) { - const char *contents = NULL; - char type; - union { - uint8_t u8; - uint16_t u16; - int16_t s16; - uint32_t u32; - int32_t s32; - uint64_t u64; - int64_t s64; - double d64; - const char *string; - int i; - } basic; - - r = sd_bus_message_peek_type(m, &type, &contents); - if (r < 0) - return r; - if (r == 0) - return needs_space; - - if (bus_type_is_container(type) > 0) { - - r = sd_bus_message_enter_container(m, type, contents); - if (r < 0) - return r; - - if (type == SD_BUS_TYPE_ARRAY) { - unsigned n = 0; - - /* count array entries */ - for (;;) { - - r = sd_bus_message_skip(m, contents); - if (r < 0) - return r; - if (r == 0) - break; - - n++; - } - - r = sd_bus_message_rewind(m, false); - if (r < 0) - return r; - - if (needs_space) - fputc(' ', f); - - fprintf(f, "%u", n); - needs_space = true; - - } else if (type == SD_BUS_TYPE_VARIANT) { - - if (needs_space) - fputc(' ', f); - - fprintf(f, "%s", contents); - needs_space = true; - } - - r = format_cmdline(m, f, needs_space); - if (r < 0) - return r; - - needs_space = r > 0; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - continue; - } - - r = sd_bus_message_read_basic(m, type, &basic); - if (r < 0) - return r; - - if (needs_space) - fputc(' ', f); - - switch (type) { - case SD_BUS_TYPE_BYTE: - fprintf(f, "%u", basic.u8); - break; - - case SD_BUS_TYPE_BOOLEAN: - fputs(true_false(basic.i), f); - break; - - case SD_BUS_TYPE_INT16: - fprintf(f, "%i", basic.s16); - break; - - case SD_BUS_TYPE_UINT16: - fprintf(f, "%u", basic.u16); - break; - - case SD_BUS_TYPE_INT32: - fprintf(f, "%i", basic.s32); - break; - - case SD_BUS_TYPE_UINT32: - fprintf(f, "%u", basic.u32); - break; - - case SD_BUS_TYPE_INT64: - fprintf(f, "%" PRIi64, basic.s64); - break; - - case SD_BUS_TYPE_UINT64: - fprintf(f, "%" PRIu64, basic.u64); - break; - - case SD_BUS_TYPE_DOUBLE: - fprintf(f, "%g", basic.d64); - break; - - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_SIGNATURE: { - _cleanup_free_ char *b = NULL; - - b = cescape(basic.string); - if (!b) - return -ENOMEM; - - fprintf(f, "\"%s\"", b); - break; - } - - case SD_BUS_TYPE_UNIX_FD: - fprintf(f, "%i", basic.i); - break; - - default: - assert_not_reached("Unknown basic type."); - } - - needs_space = true; - } -} - -typedef struct Member { - const char *type; - char *interface; - char *name; - char *signature; - char *result; - char *value; - bool writable; - uint64_t flags; -} Member; - -static void member_hash_func(const void *p, struct siphash *state) { - const Member *m = p; - uint64_t arity = 1; - - assert(m); - assert(m->type); - - string_hash_func(m->type, state); - - arity += !!m->name + !!m->interface; - - uint64_hash_func(&arity, state); - - if (m->name) - string_hash_func(m->name, state); - - if (m->interface) - string_hash_func(m->interface, state); -} - -static int member_compare_func(const void *a, const void *b) { - const Member *x = a, *y = b; - int d; - - assert(x); - assert(y); - assert(x->type); - assert(y->type); - - d = strcmp_ptr(x->interface, y->interface); - if (d != 0) - return d; - - d = strcmp(x->type, y->type); - if (d != 0) - return d; - - return strcmp_ptr(x->name, y->name); -} - -static int member_compare_funcp(const void *a, const void *b) { - const Member *const * x = (const Member *const *) a, * const *y = (const Member *const *) b; - - return member_compare_func(*x, *y); -} - -static void member_free(Member *m) { - if (!m) - return; - - free(m->interface); - free(m->name); - free(m->signature); - free(m->result); - free(m->value); - free(m); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(Member*, member_free); - -static void member_set_free(Set *s) { - Member *m; - - while ((m = set_steal_first(s))) - member_free(m); - - set_free(s); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, member_set_free); - -static int on_interface(const char *interface, uint64_t flags, void *userdata) { - _cleanup_(member_freep) Member *m; - Set *members = userdata; - int r; - - assert(interface); - assert(members); - - m = new0(Member, 1); - if (!m) - return log_oom(); - - m->type = "interface"; - m->flags = flags; - - r = free_and_strdup(&m->interface, interface); - if (r < 0) - return log_oom(); - - r = set_put(members, m); - if (r <= 0) { - log_error("Duplicate interface"); - return -EINVAL; - } - - m = NULL; - return 0; -} - -static int on_method(const char *interface, const char *name, const char *signature, const char *result, uint64_t flags, void *userdata) { - _cleanup_(member_freep) Member *m; - Set *members = userdata; - int r; - - assert(interface); - assert(name); - - m = new0(Member, 1); - if (!m) - return log_oom(); - - m->type = "method"; - m->flags = flags; - - r = free_and_strdup(&m->interface, interface); - if (r < 0) - return log_oom(); - - r = free_and_strdup(&m->name, name); - if (r < 0) - return log_oom(); - - r = free_and_strdup(&m->signature, signature); - if (r < 0) - return log_oom(); - - r = free_and_strdup(&m->result, result); - if (r < 0) - return log_oom(); - - r = set_put(members, m); - if (r <= 0) { - log_error("Duplicate method"); - return -EINVAL; - } - - m = NULL; - return 0; -} - -static int on_signal(const char *interface, const char *name, const char *signature, uint64_t flags, void *userdata) { - _cleanup_(member_freep) Member *m; - Set *members = userdata; - int r; - - assert(interface); - assert(name); - - m = new0(Member, 1); - if (!m) - return log_oom(); - - m->type = "signal"; - m->flags = flags; - - r = free_and_strdup(&m->interface, interface); - if (r < 0) - return log_oom(); - - r = free_and_strdup(&m->name, name); - if (r < 0) - return log_oom(); - - r = free_and_strdup(&m->signature, signature); - if (r < 0) - return log_oom(); - - r = set_put(members, m); - if (r <= 0) { - log_error("Duplicate signal"); - return -EINVAL; - } - - m = NULL; - return 0; -} - -static int on_property(const char *interface, const char *name, const char *signature, bool writable, uint64_t flags, void *userdata) { - _cleanup_(member_freep) Member *m; - Set *members = userdata; - int r; - - assert(interface); - assert(name); - - m = new0(Member, 1); - if (!m) - return log_oom(); - - m->type = "property"; - m->flags = flags; - m->writable = writable; - - r = free_and_strdup(&m->interface, interface); - if (r < 0) - return log_oom(); - - r = free_and_strdup(&m->name, name); - if (r < 0) - return log_oom(); - - r = free_and_strdup(&m->signature, signature); - if (r < 0) - return log_oom(); - - r = set_put(members, m); - if (r <= 0) { - log_error("Duplicate property"); - return -EINVAL; - } - - m = NULL; - return 0; -} - -static const char *strdash(const char *x) { - return isempty(x) ? "-" : x; -} - -static int introspect(sd_bus *bus, char **argv) { - static const struct hash_ops member_hash_ops = { - .hash = member_hash_func, - .compare = member_compare_func, - }; - - static const XMLIntrospectOps ops = { - .on_interface = on_interface, - .on_method = on_method, - .on_signal = on_signal, - .on_property = on_property, - }; - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(member_set_freep) Set *members = NULL; - Iterator i; - Member *m; - const char *xml; - int r; - unsigned name_width, type_width, signature_width, result_width; - Member **sorted = NULL; - unsigned k = 0, j, n_args; - - n_args = strv_length(argv); - if (n_args < 3) { - log_error("Requires service and object path argument."); - return -EINVAL; - } - - if (n_args > 4) { - log_error("Too many arguments."); - return -EINVAL; - } - - members = set_new(&member_hash_ops); - if (!members) - return log_oom(); - - r = sd_bus_call_method(bus, argv[1], argv[2], "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); - if (r < 0) { - log_error("Failed to introspect object %s of service %s: %s", argv[2], argv[1], bus_error_message(&error, r)); - return r; - } - - r = sd_bus_message_read(reply, "s", &xml); - if (r < 0) - return bus_log_parse_error(r); - - /* First, get list of all properties */ - r = parse_xml_introspect(argv[2], xml, &ops, members); - if (r < 0) - return r; - - /* Second, find the current values for them */ - SET_FOREACH(m, members, i) { - - if (!streq(m->type, "property")) - continue; - - if (m->value) - continue; - - if (argv[3] && !streq(argv[3], m->interface)) - continue; - - r = sd_bus_call_method(bus, argv[1], argv[2], "org.freedesktop.DBus.Properties", "GetAll", &error, &reply, "s", m->interface); - if (r < 0) { - log_error("%s", bus_error_message(&error, r)); - return r; - } - - r = sd_bus_message_enter_container(reply, 'a', "{sv}"); - if (r < 0) - return bus_log_parse_error(r); - - for (;;) { - Member *z; - _cleanup_free_ char *buf = NULL; - _cleanup_fclose_ FILE *mf = NULL; - size_t sz = 0; - const char *name; - - r = sd_bus_message_enter_container(reply, 'e', "sv"); - if (r < 0) - return bus_log_parse_error(r); - - if (r == 0) - break; - - r = sd_bus_message_read(reply, "s", &name); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_enter_container(reply, 'v', NULL); - if (r < 0) - return bus_log_parse_error(r); - - mf = open_memstream(&buf, &sz); - if (!mf) - return log_oom(); - - r = format_cmdline(reply, mf, false); - if (r < 0) - return bus_log_parse_error(r); - - fclose(mf); - mf = NULL; - - z = set_get(members, &((Member) { - .type = "property", - .interface = m->interface, - .name = (char*) name })); - if (z) { - free(z->value); - z->value = buf; - buf = NULL; - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - } - - pager_open(arg_no_pager, false); - - name_width = strlen("NAME"); - type_width = strlen("TYPE"); - signature_width = strlen("SIGNATURE"); - result_width = strlen("RESULT/VALUE"); - - sorted = newa(Member*, set_size(members)); - - SET_FOREACH(m, members, i) { - - if (argv[3] && !streq(argv[3], m->interface)) - continue; - - if (m->interface) - name_width = MAX(name_width, strlen(m->interface)); - if (m->name) - name_width = MAX(name_width, strlen(m->name) + 1); - if (m->type) - type_width = MAX(type_width, strlen(m->type)); - if (m->signature) - signature_width = MAX(signature_width, strlen(m->signature)); - if (m->result) - result_width = MAX(result_width, strlen(m->result)); - if (m->value) - result_width = MAX(result_width, strlen(m->value)); - - sorted[k++] = m; - } - - if (result_width > 40) - result_width = 40; - - qsort(sorted, k, sizeof(Member*), member_compare_funcp); - - if (arg_legend) { - printf("%-*s %-*s %-*s %-*s %s\n", - (int) name_width, "NAME", - (int) type_width, "TYPE", - (int) signature_width, "SIGNATURE", - (int) result_width, "RESULT/VALUE", - "FLAGS"); - } - - for (j = 0; j < k; j++) { - _cleanup_free_ char *ellipsized = NULL; - const char *rv; - bool is_interface; - - m = sorted[j]; - - if (argv[3] && !streq(argv[3], m->interface)) - continue; - - is_interface = streq(m->type, "interface"); - - if (argv[3] && is_interface) - continue; - - if (m->value) { - ellipsized = ellipsize(m->value, result_width, 100); - if (!ellipsized) - return log_oom(); - - rv = ellipsized; - } else - rv = strdash(m->result); - - printf("%s%s%-*s%s %-*s %-*s %-*s%s%s%s%s%s%s\n", - is_interface ? ansi_highlight() : "", - is_interface ? "" : ".", - - !is_interface + (int) name_width, strdash(streq_ptr(m->type, "interface") ? m->interface : m->name), - is_interface ? ansi_normal() : "", - (int) type_width, strdash(m->type), - (int) signature_width, strdash(m->signature), - (int) result_width, rv, - (m->flags & SD_BUS_VTABLE_DEPRECATED) ? " deprecated" : (m->flags || m->writable ? "" : " -"), - (m->flags & SD_BUS_VTABLE_METHOD_NO_REPLY) ? " no-reply" : "", - (m->flags & SD_BUS_VTABLE_PROPERTY_CONST) ? " const" : "", - (m->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) ? " emits-change" : "", - (m->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) ? " emits-invalidation" : "", - m->writable ? " writable" : ""); - } - - return 0; -} - -static int message_dump(sd_bus_message *m, FILE *f) { - return bus_message_dump(m, f, BUS_MESSAGE_DUMP_WITH_HEADER); -} - -static int message_pcap(sd_bus_message *m, FILE *f) { - return bus_message_pcap_frame(m, arg_snaplen, f); -} - -static int monitor(sd_bus *bus, char *argv[], int (*dump)(sd_bus_message *m, FILE *f)) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *message = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char **i; - uint32_t flags = 0; - int r; - - /* upgrade connection; it's not used for anything else after this call */ - r = sd_bus_message_new_method_call(bus, &message, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus.Monitoring", "BecomeMonitor"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(message, 'a', "s"); - if (r < 0) - return bus_log_create_error(r); - - STRV_FOREACH(i, argv+1) { - _cleanup_free_ char *m = NULL; - - if (!service_name_is_valid(*i)) { - log_error("Invalid service name '%s'", *i); - return -EINVAL; - } - - m = strjoin("sender='", *i, "'", NULL); - if (!m) - return log_oom(); - - r = sd_bus_message_append_basic(message, 's', m); - if (r < 0) - return bus_log_create_error(r); - - free(m); - m = strjoin("destination='", *i, "'", NULL); - if (!m) - return log_oom(); - - r = sd_bus_message_append_basic(message, 's', m); - if (r < 0) - return bus_log_create_error(r); - } - - STRV_FOREACH(i, arg_matches) { - r = sd_bus_message_append_basic(message, 's', *i); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_message_close_container(message); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_basic(message, 'u', &flags); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, message, arg_timeout, &error, NULL); - if (r < 0) { - log_error("%s", bus_error_message(&error, r)); - return r; - } - - log_info("Monitoring bus message stream."); - - for (;;) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - r = sd_bus_process(bus, &m); - if (r < 0) - return log_error_errno(r, "Failed to process bus: %m"); - - if (m) { - dump(m, stdout); - fflush(stdout); - - if (sd_bus_message_is_signal(m, "org.freedesktop.DBus.Local", "Disconnected") > 0) { - log_info("Connection terminated, exiting."); - return 0; - } - - continue; - } - - if (r > 0) - continue; - - r = sd_bus_wait(bus, (uint64_t) -1); - if (r < 0) - return log_error_errno(r, "Failed to wait for bus: %m"); - } -} - -static int capture(sd_bus *bus, char *argv[]) { - int r; - - if (isatty(fileno(stdout)) > 0) { - log_error("Refusing to write message data to console, please redirect output to a file."); - return -EINVAL; - } - - bus_pcap_header(arg_snaplen, stdout); - - r = monitor(bus, argv, message_pcap); - if (r < 0) - return r; - - if (ferror(stdout)) { - log_error("Couldn't write capture file."); - return -EIO; - } - - return r; -} - -static int status(sd_bus *bus, char *argv[]) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - pid_t pid; - int r; - - assert(bus); - - if (strv_length(argv) > 2) { - log_error("Expects no or one argument."); - return -EINVAL; - } - - if (argv[1]) { - r = parse_pid(argv[1], &pid); - if (r < 0) - r = sd_bus_get_name_creds( - bus, - argv[1], - (arg_augment_creds ? SD_BUS_CREDS_AUGMENT : 0) | _SD_BUS_CREDS_ALL, - &creds); - else - r = sd_bus_creds_new_from_pid( - &creds, - pid, - _SD_BUS_CREDS_ALL); - } else { - const char *scope, *address; - sd_id128_t bus_id; - - r = sd_bus_get_address(bus, &address); - if (r >= 0) - printf("BusAddress=%s%s%s\n", ansi_highlight(), address, ansi_normal()); - - r = sd_bus_get_scope(bus, &scope); - if (r >= 0) - printf("BusScope=%s%s%s\n", ansi_highlight(), scope, ansi_normal()); - - r = sd_bus_get_bus_id(bus, &bus_id); - if (r >= 0) - printf("BusID=%s" SD_ID128_FORMAT_STR "%s\n", ansi_highlight(), SD_ID128_FORMAT_VAL(bus_id), ansi_normal()); - - r = sd_bus_get_owner_creds( - bus, - (arg_augment_creds ? SD_BUS_CREDS_AUGMENT : 0) | _SD_BUS_CREDS_ALL, - &creds); - } - - if (r < 0) - return log_error_errno(r, "Failed to get credentials: %m"); - - bus_creds_dump(creds, NULL, false); - return 0; -} - -static int message_append_cmdline(sd_bus_message *m, const char *signature, char ***x) { - char **p; - int r; - - assert(m); - assert(signature); - assert(x); - - p = *x; - - for (;;) { - const char *v; - char t; - - t = *signature; - v = *p; - - if (t == 0) - break; - if (!v) { - log_error("Too few parameters for signature."); - return -EINVAL; - } - - signature++; - p++; - - switch (t) { - - case SD_BUS_TYPE_BOOLEAN: - - r = parse_boolean(v); - if (r < 0) { - log_error("Failed to parse as boolean: %s", v); - return r; - } - - r = sd_bus_message_append_basic(m, t, &r); - break; - - case SD_BUS_TYPE_BYTE: { - uint8_t z; - - r = safe_atou8(v, &z); - if (r < 0) { - log_error("Failed to parse as byte (unsigned 8bit integer): %s", v); - return r; - } - - r = sd_bus_message_append_basic(m, t, &z); - break; - } - - case SD_BUS_TYPE_INT16: { - int16_t z; - - r = safe_atoi16(v, &z); - if (r < 0) { - log_error("Failed to parse as signed 16bit integer: %s", v); - return r; - } - - r = sd_bus_message_append_basic(m, t, &z); - break; - } - - case SD_BUS_TYPE_UINT16: { - uint16_t z; - - r = safe_atou16(v, &z); - if (r < 0) { - log_error("Failed to parse as unsigned 16bit integer: %s", v); - return r; - } - - r = sd_bus_message_append_basic(m, t, &z); - break; - } - - case SD_BUS_TYPE_INT32: { - int32_t z; - - r = safe_atoi32(v, &z); - if (r < 0) { - log_error("Failed to parse as signed 32bit integer: %s", v); - return r; - } - - r = sd_bus_message_append_basic(m, t, &z); - break; - } - - case SD_BUS_TYPE_UINT32: { - uint32_t z; - - r = safe_atou32(v, &z); - if (r < 0) { - log_error("Failed to parse as unsigned 32bit integer: %s", v); - return r; - } - - r = sd_bus_message_append_basic(m, t, &z); - break; - } - - case SD_BUS_TYPE_INT64: { - int64_t z; - - r = safe_atoi64(v, &z); - if (r < 0) { - log_error("Failed to parse as signed 64bit integer: %s", v); - return r; - } - - r = sd_bus_message_append_basic(m, t, &z); - break; - } - - case SD_BUS_TYPE_UINT64: { - uint64_t z; - - r = safe_atou64(v, &z); - if (r < 0) { - log_error("Failed to parse as unsigned 64bit integer: %s", v); - return r; - } - - r = sd_bus_message_append_basic(m, t, &z); - break; - } - - - case SD_BUS_TYPE_DOUBLE: { - double z; - - r = safe_atod(v, &z); - if (r < 0) { - log_error("Failed to parse as double precision floating point: %s", v); - return r; - } - - r = sd_bus_message_append_basic(m, t, &z); - break; - } - - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_SIGNATURE: - - r = sd_bus_message_append_basic(m, t, v); - break; - - case SD_BUS_TYPE_ARRAY: { - uint32_t n; - size_t k; - - r = safe_atou32(v, &n); - if (r < 0) { - log_error("Failed to parse number of array entries: %s", v); - return r; - } - - r = signature_element_length(signature, &k); - if (r < 0) { - log_error("Invalid array signature."); - return r; - } - - { - unsigned i; - char s[k + 1]; - memcpy(s, signature, k); - s[k] = 0; - - r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, s); - if (r < 0) - return bus_log_create_error(r); - - for (i = 0; i < n; i++) { - r = message_append_cmdline(m, s, &p); - if (r < 0) - return r; - } - } - - signature += k; - - r = sd_bus_message_close_container(m); - break; - } - - case SD_BUS_TYPE_VARIANT: - r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT, v); - if (r < 0) - return bus_log_create_error(r); - - r = message_append_cmdline(m, v, &p); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - break; - - case SD_BUS_TYPE_STRUCT_BEGIN: - case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { - size_t k; - - signature--; - p--; - - r = signature_element_length(signature, &k); - if (r < 0) { - log_error("Invalid struct/dict entry signature."); - return r; - } - - { - char s[k-1]; - memcpy(s, signature + 1, k - 2); - s[k - 2] = 0; - - r = sd_bus_message_open_container(m, t == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s); - if (r < 0) - return bus_log_create_error(r); - - r = message_append_cmdline(m, s, &p); - if (r < 0) - return r; - } - - signature += k; - - r = sd_bus_message_close_container(m); - break; - } - - case SD_BUS_TYPE_UNIX_FD: - log_error("UNIX file descriptor not supported as type."); - return -EINVAL; - - default: - log_error("Unknown signature type %c.", t); - return -EINVAL; - } - - if (r < 0) - return bus_log_create_error(r); - } - - *x = p; - return 0; -} - -static int call(sd_bus *bus, char *argv[]) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - int r; - - assert(bus); - - if (strv_length(argv) < 5) { - log_error("Expects at least four arguments."); - return -EINVAL; - } - - r = sd_bus_message_new_method_call(bus, &m, argv[1], argv[2], argv[3], argv[4]); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_set_expect_reply(m, arg_expect_reply); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_set_auto_start(m, arg_auto_start); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_set_allow_interactive_authorization(m, arg_allow_interactive_authorization); - if (r < 0) - return bus_log_create_error(r); - - if (!isempty(argv[5])) { - char **p; - - p = argv+6; - - r = message_append_cmdline(m, argv[5], &p); - if (r < 0) - return r; - - if (*p) { - log_error("Too many parameters for signature."); - return -EINVAL; - } - } - - if (!arg_expect_reply) { - r = sd_bus_send(bus, m, NULL); - if (r < 0) { - log_error("Failed to send message."); - return r; - } - - return 0; - } - - r = sd_bus_call(bus, m, arg_timeout, &error, &reply); - if (r < 0) { - log_error("%s", bus_error_message(&error, r)); - return r; - } - - r = sd_bus_message_is_empty(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (r == 0 && !arg_quiet) { - - if (arg_verbose) { - pager_open(arg_no_pager, false); - - r = bus_message_dump(reply, stdout, 0); - if (r < 0) - return r; - } else { - - fputs(sd_bus_message_get_signature(reply, true), stdout); - fputc(' ', stdout); - - r = format_cmdline(reply, stdout, false); - if (r < 0) - return bus_log_parse_error(r); - - fputc('\n', stdout); - } - } - - return 0; -} - -static int get_property(sd_bus *bus, char *argv[]) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - unsigned n; - char **i; - int r; - - assert(bus); - - n = strv_length(argv); - if (n < 5) { - log_error("Expects at least four arguments."); - return -EINVAL; - } - - STRV_FOREACH(i, argv + 4) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *contents = NULL; - char type; - - r = sd_bus_call_method(bus, argv[1], argv[2], "org.freedesktop.DBus.Properties", "Get", &error, &reply, "ss", argv[3], *i); - if (r < 0) { - log_error("%s", bus_error_message(&error, r)); - return r; - } - - r = sd_bus_message_peek_type(reply, &type, &contents); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_enter_container(reply, 'v', contents); - if (r < 0) - return bus_log_parse_error(r); - - if (arg_verbose) { - pager_open(arg_no_pager, false); - - r = bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_SUBTREE_ONLY); - if (r < 0) - return r; - } else { - fputs(contents, stdout); - fputc(' ', stdout); - - r = format_cmdline(reply, stdout, false); - if (r < 0) - return bus_log_parse_error(r); - - fputc('\n', stdout); - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - } - - return 0; -} - -static int set_property(sd_bus *bus, char *argv[]) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - unsigned n; - char **p; - int r; - - assert(bus); - - n = strv_length(argv); - if (n < 6) { - log_error("Expects at least five arguments."); - return -EINVAL; - } - - r = sd_bus_message_new_method_call(bus, &m, argv[1], argv[2], "org.freedesktop.DBus.Properties", "Set"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "ss", argv[3], argv[4]); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'v', argv[5]); - if (r < 0) - return bus_log_create_error(r); - - p = argv+6; - r = message_append_cmdline(m, argv[5], &p); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - if (*p) { - log_error("Too many parameters for signature."); - return -EINVAL; - } - - r = sd_bus_call(bus, m, arg_timeout, &error, NULL); - if (r < 0) { - log_error("%s", bus_error_message(&error, r)); - return r; - } - - return 0; -} - -static int help(void) { - printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Introspect the bus.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --system Connect to system bus\n" - " --user Connect to user bus\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --address=ADDRESS Connect to bus specified by address\n" - " --show-machine Show machine ID column in list\n" - " --unique Only show unique names\n" - " --acquired Only show acquired names\n" - " --activatable Only show activatable names\n" - " --match=MATCH Only show matching messages\n" - " --size=SIZE Maximum length of captured packet\n" - " --list Don't show tree, but simple object path list\n" - " --quiet Don't show method call reply\n" - " --verbose Show result values in long format\n" - " --expect-reply=BOOL Expect a method call reply\n" - " --auto-start=BOOL Auto-start destination service\n" - " --allow-interactive-authorization=BOOL\n" - " Allow interactive authorization for operation\n" - " --timeout=SECS Maximum time to wait for method call completion\n" - " --augment-creds=BOOL Extend credential data with data read from /proc/$PID\n\n" - "Commands:\n" - " list List bus names\n" - " status [SERVICE] Show bus service, process or bus owner credentials\n" - " monitor [SERVICE...] Show bus traffic\n" - " capture [SERVICE...] Capture bus traffic as pcap\n" - " tree [SERVICE...] Show object tree of service\n" - " introspect SERVICE OBJECT [INTERFACE]\n" - " call SERVICE OBJECT INTERFACE METHOD [SIGNATURE [ARGUMENT...]]\n" - " Call a method\n" - " get-property SERVICE OBJECT INTERFACE PROPERTY...\n" - " Get property value\n" - " set-property SERVICE OBJECT INTERFACE PROPERTY SIGNATURE ARGUMENT...\n" - " Set property value\n" - " help Show this help\n" - , program_invocation_short_name); - - return 0; -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_SYSTEM, - ARG_USER, - ARG_ADDRESS, - ARG_MATCH, - ARG_SHOW_MACHINE, - ARG_UNIQUE, - ARG_ACQUIRED, - ARG_ACTIVATABLE, - ARG_SIZE, - ARG_LIST, - ARG_VERBOSE, - ARG_EXPECT_REPLY, - ARG_AUTO_START, - ARG_ALLOW_INTERACTIVE_AUTHORIZATION, - ARG_TIMEOUT, - ARG_AUGMENT_CREDS, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "address", required_argument, NULL, ARG_ADDRESS }, - { "show-machine", no_argument, NULL, ARG_SHOW_MACHINE }, - { "unique", no_argument, NULL, ARG_UNIQUE }, - { "acquired", no_argument, NULL, ARG_ACQUIRED }, - { "activatable", no_argument, NULL, ARG_ACTIVATABLE }, - { "match", required_argument, NULL, ARG_MATCH }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "size", required_argument, NULL, ARG_SIZE }, - { "list", no_argument, NULL, ARG_LIST }, - { "quiet", no_argument, NULL, 'q' }, - { "verbose", no_argument, NULL, ARG_VERBOSE }, - { "expect-reply", required_argument, NULL, ARG_EXPECT_REPLY }, - { "auto-start", required_argument, NULL, ARG_AUTO_START }, - { "allow-interactive-authorization", required_argument, NULL, ARG_ALLOW_INTERACTIVE_AUTHORIZATION }, - { "timeout", required_argument, NULL, ARG_TIMEOUT }, - { "augment-creds",required_argument, NULL, ARG_AUGMENT_CREDS}, - {}, - }; - - int c, r; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "hH:M:q", options, NULL)) >= 0) - - switch (c) { - - case 'h': - return help(); - - case ARG_VERSION: - return version(); - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case ARG_NO_LEGEND: - arg_legend = false; - break; - - case ARG_USER: - arg_user = true; - break; - - case ARG_SYSTEM: - arg_user = false; - break; - - case ARG_ADDRESS: - arg_address = optarg; - break; - - case ARG_SHOW_MACHINE: - arg_show_machine = true; - break; - - case ARG_UNIQUE: - arg_unique = true; - break; - - case ARG_ACQUIRED: - arg_acquired = true; - break; - - case ARG_ACTIVATABLE: - arg_activatable = true; - break; - - case ARG_MATCH: - if (strv_extend(&arg_matches, optarg) < 0) - return log_oom(); - break; - - case ARG_SIZE: { - uint64_t sz; - - r = parse_size(optarg, 1024, &sz); - if (r < 0) { - log_error("Failed to parse size: %s", optarg); - return r; - } - - if ((uint64_t) (size_t) sz != sz) { - log_error("Size out of range."); - return -E2BIG; - } - - arg_snaplen = (size_t) sz; - break; - } - - case ARG_LIST: - arg_list = true; - break; - - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; - break; - - case 'M': - arg_transport = BUS_TRANSPORT_MACHINE; - arg_host = optarg; - break; - - case 'q': - arg_quiet = true; - break; - - case ARG_VERBOSE: - arg_verbose = true; - break; - - case ARG_EXPECT_REPLY: - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --expect-reply= parameter."); - return r; - } - - arg_expect_reply = !!r; - break; - - - case ARG_AUTO_START: - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --auto-start= parameter."); - return r; - } - - arg_auto_start = !!r; - break; - - - case ARG_ALLOW_INTERACTIVE_AUTHORIZATION: - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --allow-interactive-authorization= parameter."); - return r; - } - - arg_allow_interactive_authorization = !!r; - break; - - case ARG_TIMEOUT: - r = parse_sec(optarg, &arg_timeout); - if (r < 0) { - log_error("Failed to parse --timeout= parameter."); - return r; - } - - break; - - case ARG_AUGMENT_CREDS: - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --augment-creds= parameter."); - return r; - } - - arg_augment_creds = !!r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -static int busctl_main(sd_bus *bus, int argc, char *argv[]) { - assert(bus); - - if (optind >= argc || - streq(argv[optind], "list")) - return list_bus_names(bus, argv + optind); - - if (streq(argv[optind], "monitor")) - return monitor(bus, argv + optind, message_dump); - - if (streq(argv[optind], "capture")) - return capture(bus, argv + optind); - - if (streq(argv[optind], "status")) - return status(bus, argv + optind); - - if (streq(argv[optind], "tree")) - return tree(bus, argv + optind); - - if (streq(argv[optind], "introspect")) - return introspect(bus, argv + optind); - - if (streq(argv[optind], "call")) - return call(bus, argv + optind); - - if (streq(argv[optind], "get-property")) - return get_property(bus, argv + optind); - - if (streq(argv[optind], "set-property")) - return set_property(bus, argv + optind); - - if (streq(argv[optind], "help")) - return help(); - - log_error("Unknown command '%s'", argv[optind]); - return -EINVAL; -} - -int main(int argc, char *argv[]) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = sd_bus_new(&bus); - if (r < 0) { - log_error_errno(r, "Failed to allocate bus: %m"); - goto finish; - } - - if (streq_ptr(argv[optind], "monitor") || - streq_ptr(argv[optind], "capture")) { - - r = sd_bus_set_monitor(bus, true); - if (r < 0) { - log_error_errno(r, "Failed to set monitor mode: %m"); - goto finish; - } - - r = sd_bus_negotiate_creds(bus, true, _SD_BUS_CREDS_ALL); - if (r < 0) { - log_error_errno(r, "Failed to enable credentials: %m"); - goto finish; - } - - r = sd_bus_negotiate_timestamp(bus, true); - if (r < 0) { - log_error_errno(r, "Failed to enable timestamps: %m"); - goto finish; - } - - r = sd_bus_negotiate_fds(bus, true); - if (r < 0) { - log_error_errno(r, "Failed to enable fds: %m"); - goto finish; - } - } - - r = sd_bus_set_bus_client(bus, true); - if (r < 0) { - log_error_errno(r, "Failed to set bus client: %m"); - goto finish; - } - - if (arg_address) - r = sd_bus_set_address(bus, arg_address); - else { - switch (arg_transport) { - - case BUS_TRANSPORT_LOCAL: - if (arg_user) { - bus->is_user = true; - r = bus_set_address_user(bus); - } else { - bus->is_system = true; - r = bus_set_address_system(bus); - } - break; - - case BUS_TRANSPORT_REMOTE: - r = bus_set_address_system_remote(bus, arg_host); - break; - - case BUS_TRANSPORT_MACHINE: - r = bus_set_address_system_machine(bus, arg_host); - break; - - default: - assert_not_reached("Hmm, unknown transport type."); - } - } - if (r < 0) { - log_error_errno(r, "Failed to set address: %m"); - goto finish; - } - - r = sd_bus_start(bus); - if (r < 0) { - log_error_errno(r, "Failed to connect to bus: %m"); - goto finish; - } - - r = busctl_main(bus, argc, argv); - -finish: - pager_close(); - - strv_free(arg_matches); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/libsystemd/sd-bus/kdbus.h b/src/libsystemd/sd-bus/kdbus.h deleted file mode 100644 index ecffc6b13c..0000000000 --- a/src/libsystemd/sd-bus/kdbus.h +++ /dev/null @@ -1,980 +0,0 @@ -/* - * kdbus 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. - */ - -#ifndef _UAPI_KDBUS_H_ -#define _UAPI_KDBUS_H_ - -#include -#include - -#define KDBUS_IOCTL_MAGIC 0x95 -#define KDBUS_SRC_ID_KERNEL (0) -#define KDBUS_DST_ID_NAME (0) -#define KDBUS_MATCH_ID_ANY (~0ULL) -#define KDBUS_DST_ID_BROADCAST (~0ULL) -#define KDBUS_FLAG_NEGOTIATE (1ULL << 63) - -/** - * struct kdbus_notify_id_change - name registry change message - * @id: New or former owner of the name - * @flags: flags field from KDBUS_HELLO_* - * - * Sent from kernel to userspace when the owner or activator of - * a well-known name changes. - * - * Attached to: - * KDBUS_ITEM_ID_ADD - * KDBUS_ITEM_ID_REMOVE - */ -struct kdbus_notify_id_change { - __u64 id; - __u64 flags; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_notify_name_change - name registry change message - * @old_id: ID and flags of former owner of a name - * @new_id: ID and flags of new owner of a name - * @name: Well-known name - * - * Sent from kernel to userspace when the owner or activator of - * a well-known name changes. - * - * Attached to: - * KDBUS_ITEM_NAME_ADD - * KDBUS_ITEM_NAME_REMOVE - * KDBUS_ITEM_NAME_CHANGE - */ -struct kdbus_notify_name_change { - struct kdbus_notify_id_change old_id; - struct kdbus_notify_id_change new_id; - char name[0]; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_creds - process credentials - * @uid: User ID - * @euid: Effective UID - * @suid: Saved UID - * @fsuid: Filesystem UID - * @gid: Group ID - * @egid: Effective GID - * @sgid: Saved GID - * @fsgid: Filesystem GID - * - * Attached to: - * KDBUS_ITEM_CREDS - */ -struct kdbus_creds { - __u64 uid; - __u64 euid; - __u64 suid; - __u64 fsuid; - __u64 gid; - __u64 egid; - __u64 sgid; - __u64 fsgid; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_pids - process identifiers - * @pid: Process ID - * @tid: Thread ID - * @ppid: Parent process ID - * - * The PID and TID of a process. - * - * Attached to: - * KDBUS_ITEM_PIDS - */ -struct kdbus_pids { - __u64 pid; - __u64 tid; - __u64 ppid; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_caps - process capabilities - * @last_cap: Highest currently known capability bit - * @caps: Variable number of 32-bit capabilities flags - * - * Contains a variable number of 32-bit capabilities flags. - * - * Attached to: - * KDBUS_ITEM_CAPS - */ -struct kdbus_caps { - __u32 last_cap; - __u32 caps[0]; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_audit - audit information - * @sessionid: The audit session ID - * @loginuid: The audit login uid - * - * Attached to: - * KDBUS_ITEM_AUDIT - */ -struct kdbus_audit { - __u32 sessionid; - __u32 loginuid; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_timestamp - * @seqnum: Global per-domain message sequence number - * @monotonic_ns: Monotonic timestamp, in nanoseconds - * @realtime_ns: Realtime timestamp, in nanoseconds - * - * Attached to: - * KDBUS_ITEM_TIMESTAMP - */ -struct kdbus_timestamp { - __u64 seqnum; - __u64 monotonic_ns; - __u64 realtime_ns; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_vec - I/O vector for kdbus payload items - * @size: The size of the vector - * @address: Memory address of data buffer - * @offset: Offset in the in-message payload memory, - * relative to the message head - * - * Attached to: - * KDBUS_ITEM_PAYLOAD_VEC, KDBUS_ITEM_PAYLOAD_OFF - */ -struct kdbus_vec { - __u64 size; - union { - __u64 address; - __u64 offset; - }; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_bloom_parameter - bus-wide bloom parameters - * @size: Size of the bit field in bytes (m / 8) - * @n_hash: Number of hash functions used (k) - */ -struct kdbus_bloom_parameter { - __u64 size; - __u64 n_hash; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_bloom_filter - bloom filter containing n elements - * @generation: Generation of the element set in the filter - * @data: Bit field, multiple of 8 bytes - */ -struct kdbus_bloom_filter { - __u64 generation; - __u64 data[0]; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_memfd - a kdbus memfd - * @start: The offset into the memfd where the segment starts - * @size: The size of the memfd segment - * @fd: The file descriptor number - * @__pad: Padding to ensure proper alignment and size - * - * Attached to: - * KDBUS_ITEM_PAYLOAD_MEMFD - */ -struct kdbus_memfd { - __u64 start; - __u64 size; - int fd; - __u32 __pad; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_name - a registered well-known name with its flags - * @flags: Flags from KDBUS_NAME_* - * @name: Well-known name - * - * Attached to: - * KDBUS_ITEM_OWNED_NAME - */ -struct kdbus_name { - __u64 flags; - char name[0]; -} __attribute__((__aligned__(8))); - -/** - * enum kdbus_policy_access_type - permissions of a policy record - * @_KDBUS_POLICY_ACCESS_NULL: Uninitialized/invalid - * @KDBUS_POLICY_ACCESS_USER: Grant access to a uid - * @KDBUS_POLICY_ACCESS_GROUP: Grant access to gid - * @KDBUS_POLICY_ACCESS_WORLD: World-accessible - */ -enum kdbus_policy_access_type { - _KDBUS_POLICY_ACCESS_NULL, - KDBUS_POLICY_ACCESS_USER, - KDBUS_POLICY_ACCESS_GROUP, - KDBUS_POLICY_ACCESS_WORLD, -}; - -/** - * enum kdbus_policy_access_flags - mode flags - * @KDBUS_POLICY_OWN: Allow to own a well-known name - * Implies KDBUS_POLICY_TALK and KDBUS_POLICY_SEE - * @KDBUS_POLICY_TALK: Allow communication to a well-known name - * Implies KDBUS_POLICY_SEE - * @KDBUS_POLICY_SEE: Allow to see a well-known name - */ -enum kdbus_policy_type { - KDBUS_POLICY_SEE = 0, - KDBUS_POLICY_TALK, - KDBUS_POLICY_OWN, -}; - -/** - * struct kdbus_policy_access - policy access item - * @type: One of KDBUS_POLICY_ACCESS_* types - * @access: Access to grant - * @id: For KDBUS_POLICY_ACCESS_USER, the uid - * For KDBUS_POLICY_ACCESS_GROUP, the gid - */ -struct kdbus_policy_access { - __u64 type; /* USER, GROUP, WORLD */ - __u64 access; /* OWN, TALK, SEE */ - __u64 id; /* uid, gid, 0 */ -} __attribute__((__aligned__(8))); - -/** - * enum kdbus_attach_flags - flags for metadata attachments - * @KDBUS_ATTACH_TIMESTAMP: Timestamp - * @KDBUS_ATTACH_CREDS: Credentials - * @KDBUS_ATTACH_PIDS: PIDs - * @KDBUS_ATTACH_AUXGROUPS: Auxiliary groups - * @KDBUS_ATTACH_NAMES: Well-known names - * @KDBUS_ATTACH_TID_COMM: The "comm" process identifier of the TID - * @KDBUS_ATTACH_PID_COMM: The "comm" process identifier of the PID - * @KDBUS_ATTACH_EXE: The path of the executable - * @KDBUS_ATTACH_CMDLINE: The process command line - * @KDBUS_ATTACH_CGROUP: The croup membership - * @KDBUS_ATTACH_CAPS: The process capabilities - * @KDBUS_ATTACH_SECLABEL: The security label - * @KDBUS_ATTACH_AUDIT: The audit IDs - * @KDBUS_ATTACH_CONN_DESCRIPTION: The human-readable connection name - * @_KDBUS_ATTACH_ALL: All of the above - * @_KDBUS_ATTACH_ANY: Wildcard match to enable any kind of - * metatdata. - */ -enum kdbus_attach_flags { - KDBUS_ATTACH_TIMESTAMP = 1ULL << 0, - KDBUS_ATTACH_CREDS = 1ULL << 1, - KDBUS_ATTACH_PIDS = 1ULL << 2, - KDBUS_ATTACH_AUXGROUPS = 1ULL << 3, - KDBUS_ATTACH_NAMES = 1ULL << 4, - KDBUS_ATTACH_TID_COMM = 1ULL << 5, - KDBUS_ATTACH_PID_COMM = 1ULL << 6, - KDBUS_ATTACH_EXE = 1ULL << 7, - KDBUS_ATTACH_CMDLINE = 1ULL << 8, - KDBUS_ATTACH_CGROUP = 1ULL << 9, - KDBUS_ATTACH_CAPS = 1ULL << 10, - KDBUS_ATTACH_SECLABEL = 1ULL << 11, - KDBUS_ATTACH_AUDIT = 1ULL << 12, - KDBUS_ATTACH_CONN_DESCRIPTION = 1ULL << 13, - _KDBUS_ATTACH_ALL = (1ULL << 14) - 1, - _KDBUS_ATTACH_ANY = ~0ULL -}; - -/** - * enum kdbus_item_type - item types to chain data in a list - * @_KDBUS_ITEM_NULL: Uninitialized/invalid - * @_KDBUS_ITEM_USER_BASE: Start of user items - * @KDBUS_ITEM_NEGOTIATE: Negotiate supported items - * @KDBUS_ITEM_PAYLOAD_VEC: Vector to data - * @KDBUS_ITEM_PAYLOAD_OFF: Data at returned offset to message head - * @KDBUS_ITEM_PAYLOAD_MEMFD: Data as sealed memfd - * @KDBUS_ITEM_FDS: Attached file descriptors - * @KDBUS_ITEM_CANCEL_FD: FD used to cancel a synchronous - * operation by writing to it from - * userspace - * @KDBUS_ITEM_BLOOM_PARAMETER: Bus-wide bloom parameters, used with - * KDBUS_CMD_BUS_MAKE, carries a - * struct kdbus_bloom_parameter - * @KDBUS_ITEM_BLOOM_FILTER: Bloom filter carried with a message, - * used to match against a bloom mask of a - * connection, carries a struct - * kdbus_bloom_filter - * @KDBUS_ITEM_BLOOM_MASK: Bloom mask used to match against a - * message'sbloom filter - * @KDBUS_ITEM_DST_NAME: Destination's well-known name - * @KDBUS_ITEM_MAKE_NAME: Name of domain, bus, endpoint - * @KDBUS_ITEM_ATTACH_FLAGS_SEND: Attach-flags, used for updating which - * metadata a connection opts in to send - * @KDBUS_ITEM_ATTACH_FLAGS_RECV: Attach-flags, used for updating which - * metadata a connection requests to - * receive for each reeceived message - * @KDBUS_ITEM_ID: Connection ID - * @KDBUS_ITEM_NAME: Well-know name with flags - * @_KDBUS_ITEM_ATTACH_BASE: Start of metadata attach items - * @KDBUS_ITEM_TIMESTAMP: Timestamp - * @KDBUS_ITEM_CREDS: Process credentials - * @KDBUS_ITEM_PIDS: Process identifiers - * @KDBUS_ITEM_AUXGROUPS: Auxiliary process groups - * @KDBUS_ITEM_OWNED_NAME: A name owned by the associated - * connection - * @KDBUS_ITEM_TID_COMM: Thread ID "comm" identifier - * (Don't trust this, see below.) - * @KDBUS_ITEM_PID_COMM: Process ID "comm" identifier - * (Don't trust this, see below.) - * @KDBUS_ITEM_EXE: The path of the executable - * (Don't trust this, see below.) - * @KDBUS_ITEM_CMDLINE: The process command line - * (Don't trust this, see below.) - * @KDBUS_ITEM_CGROUP: The croup membership - * @KDBUS_ITEM_CAPS: The process capabilities - * @KDBUS_ITEM_SECLABEL: The security label - * @KDBUS_ITEM_AUDIT: The audit IDs - * @KDBUS_ITEM_CONN_DESCRIPTION: The connection's human-readable name - * (debugging) - * @_KDBUS_ITEM_POLICY_BASE: Start of policy items - * @KDBUS_ITEM_POLICY_ACCESS: Policy access block - * @_KDBUS_ITEM_KERNEL_BASE: Start of kernel-generated message items - * @KDBUS_ITEM_NAME_ADD: Notification in kdbus_notify_name_change - * @KDBUS_ITEM_NAME_REMOVE: Notification in kdbus_notify_name_change - * @KDBUS_ITEM_NAME_CHANGE: Notification in kdbus_notify_name_change - * @KDBUS_ITEM_ID_ADD: Notification in kdbus_notify_id_change - * @KDBUS_ITEM_ID_REMOVE: Notification in kdbus_notify_id_change - * @KDBUS_ITEM_REPLY_TIMEOUT: Timeout has been reached - * @KDBUS_ITEM_REPLY_DEAD: Destination died - * - * N.B: The process and thread COMM fields, as well as the CMDLINE and - * EXE fields may be altered by unprivileged processes und should - * hence *not* used for security decisions. Peers should make use of - * these items only for informational purposes, such as generating log - * records. - */ -enum kdbus_item_type { - _KDBUS_ITEM_NULL, - _KDBUS_ITEM_USER_BASE, - KDBUS_ITEM_NEGOTIATE = _KDBUS_ITEM_USER_BASE, - KDBUS_ITEM_PAYLOAD_VEC, - KDBUS_ITEM_PAYLOAD_OFF, - KDBUS_ITEM_PAYLOAD_MEMFD, - KDBUS_ITEM_FDS, - KDBUS_ITEM_CANCEL_FD, - KDBUS_ITEM_BLOOM_PARAMETER, - KDBUS_ITEM_BLOOM_FILTER, - KDBUS_ITEM_BLOOM_MASK, - KDBUS_ITEM_DST_NAME, - KDBUS_ITEM_MAKE_NAME, - KDBUS_ITEM_ATTACH_FLAGS_SEND, - KDBUS_ITEM_ATTACH_FLAGS_RECV, - KDBUS_ITEM_ID, - KDBUS_ITEM_NAME, - KDBUS_ITEM_DST_ID, - - /* keep these item types in sync with KDBUS_ATTACH_* flags */ - _KDBUS_ITEM_ATTACH_BASE = 0x1000, - KDBUS_ITEM_TIMESTAMP = _KDBUS_ITEM_ATTACH_BASE, - KDBUS_ITEM_CREDS, - KDBUS_ITEM_PIDS, - KDBUS_ITEM_AUXGROUPS, - KDBUS_ITEM_OWNED_NAME, - KDBUS_ITEM_TID_COMM, - KDBUS_ITEM_PID_COMM, - KDBUS_ITEM_EXE, - KDBUS_ITEM_CMDLINE, - KDBUS_ITEM_CGROUP, - KDBUS_ITEM_CAPS, - KDBUS_ITEM_SECLABEL, - KDBUS_ITEM_AUDIT, - KDBUS_ITEM_CONN_DESCRIPTION, - - _KDBUS_ITEM_POLICY_BASE = 0x2000, - KDBUS_ITEM_POLICY_ACCESS = _KDBUS_ITEM_POLICY_BASE, - - _KDBUS_ITEM_KERNEL_BASE = 0x8000, - KDBUS_ITEM_NAME_ADD = _KDBUS_ITEM_KERNEL_BASE, - KDBUS_ITEM_NAME_REMOVE, - KDBUS_ITEM_NAME_CHANGE, - KDBUS_ITEM_ID_ADD, - KDBUS_ITEM_ID_REMOVE, - KDBUS_ITEM_REPLY_TIMEOUT, - KDBUS_ITEM_REPLY_DEAD, -}; - -/** - * struct kdbus_item - chain of data blocks - * @size: Overall data record size - * @type: Kdbus_item type of data - * @data: Generic bytes - * @data32: Generic 32 bit array - * @data64: Generic 64 bit array - * @str: Generic string - * @id: Connection ID - * @vec: KDBUS_ITEM_PAYLOAD_VEC - * @creds: KDBUS_ITEM_CREDS - * @audit: KDBUS_ITEM_AUDIT - * @timestamp: KDBUS_ITEM_TIMESTAMP - * @name: KDBUS_ITEM_NAME - * @bloom_parameter: KDBUS_ITEM_BLOOM_PARAMETER - * @bloom_filter: KDBUS_ITEM_BLOOM_FILTER - * @memfd: KDBUS_ITEM_PAYLOAD_MEMFD - * @name_change: KDBUS_ITEM_NAME_ADD - * KDBUS_ITEM_NAME_REMOVE - * KDBUS_ITEM_NAME_CHANGE - * @id_change: KDBUS_ITEM_ID_ADD - * KDBUS_ITEM_ID_REMOVE - * @policy: KDBUS_ITEM_POLICY_ACCESS - */ -struct kdbus_item { - __u64 size; - __u64 type; - union { - __u8 data[0]; - __u32 data32[0]; - __u64 data64[0]; - char str[0]; - - __u64 id; - struct kdbus_vec vec; - struct kdbus_creds creds; - struct kdbus_pids pids; - struct kdbus_audit audit; - struct kdbus_caps caps; - struct kdbus_timestamp timestamp; - struct kdbus_name name; - struct kdbus_bloom_parameter bloom_parameter; - struct kdbus_bloom_filter bloom_filter; - struct kdbus_memfd memfd; - int fds[0]; - struct kdbus_notify_name_change name_change; - struct kdbus_notify_id_change id_change; - struct kdbus_policy_access policy_access; - }; -} __attribute__((__aligned__(8))); - -/** - * enum kdbus_msg_flags - type of message - * @KDBUS_MSG_EXPECT_REPLY: Expect a reply message, used for - * method calls. The userspace-supplied - * cookie identifies the message and the - * respective reply carries the cookie - * in cookie_reply - * @KDBUS_MSG_NO_AUTO_START: Do not start a service if the addressed - * name is not currently active. This flag is - * not looked at by the kernel but only - * serves as hint for userspace implementations. - * @KDBUS_MSG_SIGNAL: Treat this message as signal - */ -enum kdbus_msg_flags { - KDBUS_MSG_EXPECT_REPLY = 1ULL << 0, - KDBUS_MSG_NO_AUTO_START = 1ULL << 1, - KDBUS_MSG_SIGNAL = 1ULL << 2, -}; - -/** - * enum kdbus_payload_type - type of payload carried by message - * @KDBUS_PAYLOAD_KERNEL: Kernel-generated simple message - * @KDBUS_PAYLOAD_DBUS: D-Bus marshalling "DBusDBus" - * - * Any payload-type is accepted. Common types will get added here once - * established. - */ -enum kdbus_payload_type { - KDBUS_PAYLOAD_KERNEL, - KDBUS_PAYLOAD_DBUS = 0x4442757344427573ULL, -}; - -/** - * struct kdbus_msg - the representation of a kdbus message - * @size: Total size of the message - * @flags: Message flags (KDBUS_MSG_*), userspace → kernel - * @priority: Message queue priority value - * @dst_id: 64-bit ID of the destination connection - * @src_id: 64-bit ID of the source connection - * @payload_type: Payload type (KDBUS_PAYLOAD_*) - * @cookie: Userspace-supplied cookie, for the connection - * to identify its messages - * @timeout_ns: The time to wait for a message reply from the peer. - * If there is no reply, and the send command is - * executed asynchronously, a kernel-generated message - * with an attached KDBUS_ITEM_REPLY_TIMEOUT item - * is sent to @src_id. For synchronously executed send - * command, the value denotes the maximum time the call - * blocks to wait for a reply. The timeout is expected in - * nanoseconds and as absolute CLOCK_MONOTONIC value. - * @cookie_reply: A reply to the requesting message with the same - * cookie. The requesting connection can match its - * request and the reply with this value - * @items: A list of kdbus_items containing the message payload - */ -struct kdbus_msg { - __u64 size; - __u64 flags; - __s64 priority; - __u64 dst_id; - __u64 src_id; - __u64 payload_type; - __u64 cookie; - union { - __u64 timeout_ns; - __u64 cookie_reply; - }; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_msg_info - returned message container - * @offset: Offset of kdbus_msg slice in pool - * @msg_size: Copy of the kdbus_msg.size field - * @return_flags: Command return flags, kernel → userspace - */ -struct kdbus_msg_info { - __u64 offset; - __u64 msg_size; - __u64 return_flags; -} __attribute__((__aligned__(8))); - -/** - * enum kdbus_send_flags - flags for sending messages - * @KDBUS_SEND_SYNC_REPLY: Wait for destination connection to - * reply to this message. The - * KDBUS_CMD_SEND ioctl() will block - * until the reply is received, and - * reply in struct kdbus_cmd_send will - * yield the offset in the sender's pool - * where the reply can be found. - * This flag is only valid if - * @KDBUS_MSG_EXPECT_REPLY is set as well. - */ -enum kdbus_send_flags { - KDBUS_SEND_SYNC_REPLY = 1ULL << 0, -}; - -/** - * struct kdbus_cmd_send - send message - * @size: Overall size of this structure - * @flags: Flags to change send behavior (KDBUS_SEND_*) - * @return_flags: Command return flags, kernel → userspace - * @msg_address: Storage address of the kdbus_msg to send - * @reply: Storage for message reply if KDBUS_SEND_SYNC_REPLY - * was given - * @items: Additional items for this command - */ -struct kdbus_cmd_send { - __u64 size; - __u64 flags; - __u64 return_flags; - __u64 msg_address; - struct kdbus_msg_info reply; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * enum kdbus_recv_flags - flags for de-queuing messages - * @KDBUS_RECV_PEEK: Return the next queued message without - * actually de-queuing it, and without installing - * any file descriptors or other resources. It is - * usually used to determine the activating - * connection of a bus name. - * @KDBUS_RECV_DROP: Drop and free the next queued message and all - * its resources without actually receiving it. - * @KDBUS_RECV_USE_PRIORITY: Only de-queue messages with the specified or - * higher priority (lowest values); if not set, - * the priority value is ignored. - */ -enum kdbus_recv_flags { - KDBUS_RECV_PEEK = 1ULL << 0, - KDBUS_RECV_DROP = 1ULL << 1, - KDBUS_RECV_USE_PRIORITY = 1ULL << 2, -}; - -/** - * enum kdbus_recv_return_flags - return flags for message receive commands - * @KDBUS_RECV_RETURN_INCOMPLETE_FDS: One or more file descriptors could not - * be installed. These descriptors in - * KDBUS_ITEM_FDS will carry the value -1. - * @KDBUS_RECV_RETURN_DROPPED_MSGS: There have been dropped messages since - * the last time a message was received. - * The 'dropped_msgs' counter contains the - * number of messages dropped pool - * overflows or other missed broadcasts. - */ -enum kdbus_recv_return_flags { - KDBUS_RECV_RETURN_INCOMPLETE_FDS = 1ULL << 0, - KDBUS_RECV_RETURN_DROPPED_MSGS = 1ULL << 1, -}; - -/** - * struct kdbus_cmd_recv - struct to de-queue a buffered message - * @size: Overall size of this object - * @flags: KDBUS_RECV_* flags, userspace → kernel - * @return_flags: Command return flags, kernel → userspace - * @priority: Minimum priority of the messages to de-queue. Lowest - * values have the highest priority. - * @dropped_msgs: In case there were any dropped messages since the last - * time a message was received, this will be set to the - * number of lost messages and - * KDBUS_RECV_RETURN_DROPPED_MSGS will be set in - * 'return_flags'. This can only happen if the ioctl - * returns 0 or EAGAIN. - * @msg: Return storage for received message. - * @items: Additional items for this command. - * - * This struct is used with the KDBUS_CMD_RECV ioctl. - */ -struct kdbus_cmd_recv { - __u64 size; - __u64 flags; - __u64 return_flags; - __s64 priority; - __u64 dropped_msgs; - struct kdbus_msg_info msg; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_cmd_free - struct to free a slice of memory in the pool - * @size: Overall size of this structure - * @flags: Flags for the free command, userspace → kernel - * @return_flags: Command return flags, kernel → userspace - * @offset: The offset of the memory slice, as returned by other - * ioctls - * @items: Additional items to modify the behavior - * - * This struct is used with the KDBUS_CMD_FREE ioctl. - */ -struct kdbus_cmd_free { - __u64 size; - __u64 flags; - __u64 return_flags; - __u64 offset; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * enum kdbus_hello_flags - flags for struct kdbus_cmd_hello - * @KDBUS_HELLO_ACCEPT_FD: The connection allows the reception of - * any passed file descriptors - * @KDBUS_HELLO_ACTIVATOR: Special-purpose connection which registers - * a well-know name for a process to be started - * when traffic arrives - * @KDBUS_HELLO_POLICY_HOLDER: Special-purpose connection which registers - * policy entries for a name. The provided name - * is not activated and not registered with the - * name database, it only allows unprivileged - * connections to acquire a name, talk or discover - * a service - * @KDBUS_HELLO_MONITOR: Special-purpose connection to monitor - * bus traffic - */ -enum kdbus_hello_flags { - KDBUS_HELLO_ACCEPT_FD = 1ULL << 0, - KDBUS_HELLO_ACTIVATOR = 1ULL << 1, - KDBUS_HELLO_POLICY_HOLDER = 1ULL << 2, - KDBUS_HELLO_MONITOR = 1ULL << 3, -}; - -/** - * struct kdbus_cmd_hello - struct to say hello to kdbus - * @size: The total size of the structure - * @flags: Connection flags (KDBUS_HELLO_*), userspace → kernel - * @return_flags: Command return flags, kernel → userspace - * @attach_flags_send: Mask of metadata to attach to each message sent - * off by this connection (KDBUS_ATTACH_*) - * @attach_flags_recv: Mask of metadata to attach to each message receieved - * by the new connection (KDBUS_ATTACH_*) - * @bus_flags: The flags field copied verbatim from the original - * KDBUS_CMD_BUS_MAKE ioctl. It's intended to be useful - * to do negotiation of features of the payload that is - * transferred (kernel → userspace) - * @id: The ID of this connection (kernel → userspace) - * @pool_size: Size of the connection's buffer where the received - * messages are placed - * @offset: Pool offset where items are returned to report - * additional information about the bus and the newly - * created connection. - * @items_size: Size of buffer returned in the pool slice at @offset. - * @id128: Unique 128-bit ID of the bus (kernel → userspace) - * @items: A list of items - * - * This struct is used with the KDBUS_CMD_HELLO ioctl. - */ -struct kdbus_cmd_hello { - __u64 size; - __u64 flags; - __u64 return_flags; - __u64 attach_flags_send; - __u64 attach_flags_recv; - __u64 bus_flags; - __u64 id; - __u64 pool_size; - __u64 offset; - __u64 items_size; - __u8 id128[16]; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_info - connection information - * @size: total size of the struct - * @id: 64bit object ID - * @flags: object creation flags - * @items: list of items - * - * Note that the user is responsible for freeing the allocated memory with - * the KDBUS_CMD_FREE ioctl. - */ -struct kdbus_info { - __u64 size; - __u64 id; - __u64 flags; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * enum kdbus_list_flags - what to include into the returned list - * @KDBUS_LIST_UNIQUE: active connections - * @KDBUS_LIST_ACTIVATORS: activator connections - * @KDBUS_LIST_NAMES: known well-known names - * @KDBUS_LIST_QUEUED: queued-up names - */ -enum kdbus_list_flags { - KDBUS_LIST_UNIQUE = 1ULL << 0, - KDBUS_LIST_NAMES = 1ULL << 1, - KDBUS_LIST_ACTIVATORS = 1ULL << 2, - KDBUS_LIST_QUEUED = 1ULL << 3, -}; - -/** - * struct kdbus_cmd_list - list connections - * @size: overall size of this object - * @flags: flags for the query (KDBUS_LIST_*), userspace → kernel - * @return_flags: command return flags, kernel → userspace - * @offset: Offset in the caller's pool buffer where an array of - * kdbus_info objects is stored. - * The user must use KDBUS_CMD_FREE to free the - * allocated memory. - * @list_size: size of returned list in bytes - * @items: Items for the command. Reserved for future use. - * - * This structure is used with the KDBUS_CMD_LIST ioctl. - */ -struct kdbus_cmd_list { - __u64 size; - __u64 flags; - __u64 return_flags; - __u64 offset; - __u64 list_size; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_cmd_info - struct used for KDBUS_CMD_CONN_INFO ioctl - * @size: The total size of the struct - * @flags: Flags for this ioctl, userspace → kernel - * @return_flags: Command return flags, kernel → userspace - * @id: The 64-bit ID of the connection. If set to zero, passing - * @name is required. kdbus will look up the name to - * determine the ID in this case. - * @attach_flags: Set of attach flags to specify the set of information - * to receive, userspace → kernel - * @offset: Returned offset in the caller's pool buffer where the - * kdbus_info struct result is stored. The user must - * use KDBUS_CMD_FREE to free the allocated memory. - * @info_size: Output buffer to report size of data at @offset. - * @items: The optional item list, containing the - * well-known name to look up as a KDBUS_ITEM_NAME. - * Only needed in case @id is zero. - * - * On success, the KDBUS_CMD_CONN_INFO ioctl will return 0 and @offset will - * tell the user the offset in the connection pool buffer at which to find the - * result in a struct kdbus_info. - */ -struct kdbus_cmd_info { - __u64 size; - __u64 flags; - __u64 return_flags; - __u64 id; - __u64 attach_flags; - __u64 offset; - __u64 info_size; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * enum kdbus_cmd_match_flags - flags to control the KDBUS_CMD_MATCH_ADD ioctl - * @KDBUS_MATCH_REPLACE: If entries with the supplied cookie already - * exists, remove them before installing the new - * matches. - */ -enum kdbus_cmd_match_flags { - KDBUS_MATCH_REPLACE = 1ULL << 0, -}; - -/** - * struct kdbus_cmd_match - struct to add or remove matches - * @size: The total size of the struct - * @flags: Flags for match command (KDBUS_MATCH_*), - * userspace → kernel - * @return_flags: Command return flags, kernel → userspace - * @cookie: Userspace supplied cookie. When removing, the cookie - * identifies the match to remove - * @items: A list of items for additional information - * - * This structure is used with the KDBUS_CMD_MATCH_ADD and - * KDBUS_CMD_MATCH_REMOVE ioctl. - */ -struct kdbus_cmd_match { - __u64 size; - __u64 flags; - __u64 return_flags; - __u64 cookie; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * enum kdbus_make_flags - Flags for KDBUS_CMD_{BUS,ENDPOINT}_MAKE - * @KDBUS_MAKE_ACCESS_GROUP: Make the bus or endpoint node group-accessible - * @KDBUS_MAKE_ACCESS_WORLD: Make the bus or endpoint node world-accessible - */ -enum kdbus_make_flags { - KDBUS_MAKE_ACCESS_GROUP = 1ULL << 0, - KDBUS_MAKE_ACCESS_WORLD = 1ULL << 1, -}; - -/** - * enum kdbus_name_flags - flags for KDBUS_CMD_NAME_ACQUIRE - * @KDBUS_NAME_REPLACE_EXISTING: Try to replace name of other connections - * @KDBUS_NAME_ALLOW_REPLACEMENT: Allow the replacement of the name - * @KDBUS_NAME_QUEUE: Name should be queued if busy - * @KDBUS_NAME_IN_QUEUE: Name is queued - * @KDBUS_NAME_ACTIVATOR: Name is owned by a activator connection - */ -enum kdbus_name_flags { - KDBUS_NAME_REPLACE_EXISTING = 1ULL << 0, - KDBUS_NAME_ALLOW_REPLACEMENT = 1ULL << 1, - KDBUS_NAME_QUEUE = 1ULL << 2, - KDBUS_NAME_IN_QUEUE = 1ULL << 3, - KDBUS_NAME_ACTIVATOR = 1ULL << 4, -}; - -/** - * struct kdbus_cmd - generic ioctl payload - * @size: Overall size of this structure - * @flags: Flags for this ioctl, userspace → kernel - * @return_flags: Ioctl return flags, kernel → userspace - * @items: Additional items to modify the behavior - * - * This is a generic ioctl payload object. It's used by all ioctls that only - * take flags and items as input. - */ -struct kdbus_cmd { - __u64 size; - __u64 flags; - __u64 return_flags; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * Ioctl API - * - * KDBUS_CMD_BUS_MAKE: After opening the "control" node, this command - * creates a new bus with the specified - * name. The bus is immediately shut down and - * cleaned up when the opened file descriptor is - * closed. - * - * KDBUS_CMD_ENDPOINT_MAKE: Creates a new named special endpoint to talk to - * the bus. Such endpoints usually carry a more - * restrictive policy and grant restricted access - * to specific applications. - * KDBUS_CMD_ENDPOINT_UPDATE: Update the properties of a custom enpoint. Used - * to update the policy. - * - * KDBUS_CMD_HELLO: By opening the bus node, a connection is - * created. After a HELLO the opened connection - * becomes an active peer on the bus. - * KDBUS_CMD_UPDATE: Update the properties of a connection. Used to - * update the metadata subscription mask and - * policy. - * KDBUS_CMD_BYEBYE: Disconnect a connection. If there are no - * messages queued up in the connection's pool, - * the call succeeds, and the handle is rendered - * unusable. Otherwise, -EBUSY is returned without - * any further side-effects. - * KDBUS_CMD_FREE: Release the allocated memory in the receiver's - * pool. - * KDBUS_CMD_CONN_INFO: Retrieve credentials and properties of the - * initial creator of the connection. The data was - * stored at registration time and does not - * necessarily represent the connected process or - * the actual state of the process. - * KDBUS_CMD_BUS_CREATOR_INFO: Retrieve information of the creator of the bus - * a connection is attached to. - * - * KDBUS_CMD_SEND: Send a message and pass data from userspace to - * the kernel. - * KDBUS_CMD_RECV: Receive a message from the kernel which is - * placed in the receiver's pool. - * - * KDBUS_CMD_NAME_ACQUIRE: Request a well-known bus name to associate with - * the connection. Well-known names are used to - * address a peer on the bus. - * KDBUS_CMD_NAME_RELEASE: Release a well-known name the connection - * currently owns. - * KDBUS_CMD_LIST: Retrieve the list of all currently registered - * well-known and unique names. - * - * KDBUS_CMD_MATCH_ADD: Install a match which broadcast messages should - * be delivered to the connection. - * KDBUS_CMD_MATCH_REMOVE: Remove a current match for broadcast messages. - */ -enum kdbus_ioctl_type { - /* bus owner (00-0f) */ - KDBUS_CMD_BUS_MAKE = _IOW(KDBUS_IOCTL_MAGIC, 0x00, - struct kdbus_cmd), - - /* endpoint owner (10-1f) */ - KDBUS_CMD_ENDPOINT_MAKE = _IOW(KDBUS_IOCTL_MAGIC, 0x10, - struct kdbus_cmd), - KDBUS_CMD_ENDPOINT_UPDATE = _IOW(KDBUS_IOCTL_MAGIC, 0x11, - struct kdbus_cmd), - - /* connection owner (80-ff) */ - KDBUS_CMD_HELLO = _IOWR(KDBUS_IOCTL_MAGIC, 0x80, - struct kdbus_cmd_hello), - KDBUS_CMD_UPDATE = _IOW(KDBUS_IOCTL_MAGIC, 0x81, - struct kdbus_cmd), - KDBUS_CMD_BYEBYE = _IOW(KDBUS_IOCTL_MAGIC, 0x82, - struct kdbus_cmd), - KDBUS_CMD_FREE = _IOW(KDBUS_IOCTL_MAGIC, 0x83, - struct kdbus_cmd_free), - KDBUS_CMD_CONN_INFO = _IOR(KDBUS_IOCTL_MAGIC, 0x84, - struct kdbus_cmd_info), - KDBUS_CMD_BUS_CREATOR_INFO = _IOR(KDBUS_IOCTL_MAGIC, 0x85, - struct kdbus_cmd_info), - KDBUS_CMD_LIST = _IOR(KDBUS_IOCTL_MAGIC, 0x86, - struct kdbus_cmd_list), - - KDBUS_CMD_SEND = _IOW(KDBUS_IOCTL_MAGIC, 0x90, - struct kdbus_cmd_send), - KDBUS_CMD_RECV = _IOR(KDBUS_IOCTL_MAGIC, 0x91, - struct kdbus_cmd_recv), - - KDBUS_CMD_NAME_ACQUIRE = _IOW(KDBUS_IOCTL_MAGIC, 0xa0, - struct kdbus_cmd), - KDBUS_CMD_NAME_RELEASE = _IOW(KDBUS_IOCTL_MAGIC, 0xa1, - struct kdbus_cmd), - - KDBUS_CMD_MATCH_ADD = _IOW(KDBUS_IOCTL_MAGIC, 0xb0, - struct kdbus_cmd_match), - KDBUS_CMD_MATCH_REMOVE = _IOW(KDBUS_IOCTL_MAGIC, 0xb1, - struct kdbus_cmd_match), -}; - -#endif /* _UAPI_KDBUS_H_ */ diff --git a/src/libsystemd/sd-bus/sd-bus.c b/src/libsystemd/sd-bus/sd-bus.c deleted file mode 100644 index ed5f94e136..0000000000 --- a/src/libsystemd/sd-bus/sd-bus.c +++ /dev/null @@ -1,3791 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 -#include -#include -#include -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-container.h" -#include "bus-control.h" -#include "bus-internal.h" -#include "bus-kernel.h" -#include "bus-label.h" -#include "bus-message.h" -#include "bus-objects.h" -#include "bus-protocol.h" -#include "bus-slot.h" -#include "bus-socket.h" -#include "bus-track.h" -#include "bus-type.h" -#include "bus-util.h" -#include "cgroup-util.h" -#include "def.h" -#include "fd-util.h" -#include "hexdecoct.h" -#include "hostname-util.h" -#include "macro.h" -#include "missing.h" -#include "parse-util.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -#define log_debug_bus_message(m) \ - do { \ - sd_bus_message *_mm = (m); \ - log_debug("Got message type=%s sender=%s destination=%s object=%s interface=%s member=%s cookie=%" PRIu64 " reply_cookie=%" PRIu64 " error=%s", \ - bus_message_type_to_string(_mm->header->type), \ - strna(sd_bus_message_get_sender(_mm)), \ - strna(sd_bus_message_get_destination(_mm)), \ - strna(sd_bus_message_get_path(_mm)), \ - strna(sd_bus_message_get_interface(_mm)), \ - strna(sd_bus_message_get_member(_mm)), \ - BUS_MESSAGE_COOKIE(_mm), \ - _mm->reply_cookie, \ - strna(_mm->error.message)); \ - } while (false) - -static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec); -static int attach_io_events(sd_bus *b); -static void detach_io_events(sd_bus *b); - -static thread_local sd_bus *default_system_bus = NULL; -static thread_local sd_bus *default_user_bus = NULL; -static thread_local sd_bus *default_starter_bus = NULL; - -static void bus_close_fds(sd_bus *b) { - assert(b); - - detach_io_events(b); - - if (b->input_fd != b->output_fd) - safe_close(b->output_fd); - b->output_fd = b->input_fd = safe_close(b->input_fd); -} - -static void bus_reset_queues(sd_bus *b) { - assert(b); - - while (b->rqueue_size > 0) - sd_bus_message_unref(b->rqueue[--b->rqueue_size]); - - b->rqueue = mfree(b->rqueue); - b->rqueue_allocated = 0; - - while (b->wqueue_size > 0) - sd_bus_message_unref(b->wqueue[--b->wqueue_size]); - - b->wqueue = mfree(b->wqueue); - b->wqueue_allocated = 0; -} - -static void bus_free(sd_bus *b) { - sd_bus_slot *s; - - assert(b); - assert(!b->track_queue); - - b->state = BUS_CLOSED; - - sd_bus_detach_event(b); - - while ((s = b->slots)) { - /* At this point only floating slots can still be - * around, because the non-floating ones keep a - * reference to the bus, and we thus couldn't be - * destructing right now... We forcibly disconnect the - * slots here, so that they still can be referenced by - * apps, but are dead. */ - - assert(s->floating); - bus_slot_disconnect(s); - sd_bus_slot_unref(s); - } - - if (b->default_bus_ptr) - *b->default_bus_ptr = NULL; - - bus_close_fds(b); - - if (b->kdbus_buffer) - munmap(b->kdbus_buffer, KDBUS_POOL_SIZE); - - free(b->label); - free(b->rbuffer); - free(b->unique_name); - free(b->auth_buffer); - free(b->address); - free(b->kernel); - free(b->machine); - free(b->fake_label); - free(b->cgroup_root); - free(b->description); - - free(b->exec_path); - strv_free(b->exec_argv); - - close_many(b->fds, b->n_fds); - free(b->fds); - - bus_reset_queues(b); - - ordered_hashmap_free_free(b->reply_callbacks); - prioq_free(b->reply_callbacks_prioq); - - assert(b->match_callbacks.type == BUS_MATCH_ROOT); - bus_match_free(&b->match_callbacks); - - hashmap_free_free(b->vtable_methods); - hashmap_free_free(b->vtable_properties); - - assert(hashmap_isempty(b->nodes)); - hashmap_free(b->nodes); - - bus_kernel_flush_memfd(b); - - assert_se(pthread_mutex_destroy(&b->memfd_cache_mutex) == 0); - - free(b); -} - -_public_ int sd_bus_new(sd_bus **ret) { - sd_bus *r; - - assert_return(ret, -EINVAL); - - r = new0(sd_bus, 1); - if (!r) - return -ENOMEM; - - r->n_ref = REFCNT_INIT; - r->input_fd = r->output_fd = -1; - r->message_version = 1; - r->creds_mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_UNIQUE_NAME; - r->hello_flags |= KDBUS_HELLO_ACCEPT_FD; - r->attach_flags |= KDBUS_ATTACH_NAMES; - r->original_pid = getpid(); - - assert_se(pthread_mutex_init(&r->memfd_cache_mutex, NULL) == 0); - - /* We guarantee that wqueue always has space for at least one - * entry */ - if (!GREEDY_REALLOC(r->wqueue, r->wqueue_allocated, 1)) { - free(r); - return -ENOMEM; - } - - *ret = r; - return 0; -} - -_public_ int sd_bus_set_address(sd_bus *bus, const char *address) { - char *a; - - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(address, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - a = strdup(address); - if (!a) - return -ENOMEM; - - free(bus->address); - bus->address = a; - - return 0; -} - -_public_ int sd_bus_set_fd(sd_bus *bus, int input_fd, int output_fd) { - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(input_fd >= 0, -EBADF); - assert_return(output_fd >= 0, -EBADF); - assert_return(!bus_pid_changed(bus), -ECHILD); - - bus->input_fd = input_fd; - bus->output_fd = output_fd; - return 0; -} - -_public_ int sd_bus_set_exec(sd_bus *bus, const char *path, char *const argv[]) { - char *p, **a; - - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(path, -EINVAL); - assert_return(!strv_isempty(argv), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - p = strdup(path); - if (!p) - return -ENOMEM; - - a = strv_copy(argv); - if (!a) { - free(p); - return -ENOMEM; - } - - free(bus->exec_path); - strv_free(bus->exec_argv); - - bus->exec_path = p; - bus->exec_argv = a; - - return 0; -} - -_public_ int sd_bus_set_bus_client(sd_bus *bus, int b) { - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - bus->bus_client = !!b; - return 0; -} - -_public_ int sd_bus_set_monitor(sd_bus *bus, int b) { - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - SET_FLAG(bus->hello_flags, KDBUS_HELLO_MONITOR, b); - return 0; -} - -_public_ int sd_bus_negotiate_fds(sd_bus *bus, int b) { - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - SET_FLAG(bus->hello_flags, KDBUS_HELLO_ACCEPT_FD, b); - return 0; -} - -_public_ int sd_bus_negotiate_timestamp(sd_bus *bus, int b) { - uint64_t new_flags; - assert_return(bus, -EINVAL); - assert_return(!IN_SET(bus->state, BUS_CLOSING, BUS_CLOSED), -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - new_flags = bus->attach_flags; - SET_FLAG(new_flags, KDBUS_ATTACH_TIMESTAMP, b); - - if (bus->attach_flags == new_flags) - return 0; - - bus->attach_flags = new_flags; - if (bus->state != BUS_UNSET && bus->is_kernel) - bus_kernel_realize_attach_flags(bus); - - return 0; -} - -_public_ int sd_bus_negotiate_creds(sd_bus *bus, int b, uint64_t mask) { - uint64_t new_flags; - - assert_return(bus, -EINVAL); - assert_return(mask <= _SD_BUS_CREDS_ALL, -EINVAL); - assert_return(!IN_SET(bus->state, BUS_CLOSING, BUS_CLOSED), -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - SET_FLAG(bus->creds_mask, mask, b); - - /* The well knowns we need unconditionally, so that matches can work */ - bus->creds_mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_UNIQUE_NAME; - - /* Make sure we don't lose the timestamp flag */ - new_flags = (bus->attach_flags & KDBUS_ATTACH_TIMESTAMP) | attach_flags_to_kdbus(bus->creds_mask); - if (bus->attach_flags == new_flags) - return 0; - - bus->attach_flags = new_flags; - if (bus->state != BUS_UNSET && bus->is_kernel) - bus_kernel_realize_attach_flags(bus); - - return 0; -} - -_public_ int sd_bus_set_server(sd_bus *bus, int b, sd_id128_t server_id) { - assert_return(bus, -EINVAL); - assert_return(b || sd_id128_equal(server_id, SD_ID128_NULL), -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - bus->is_server = !!b; - bus->server_id = server_id; - return 0; -} - -_public_ int sd_bus_set_anonymous(sd_bus *bus, int b) { - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - bus->anonymous_auth = !!b; - return 0; -} - -_public_ int sd_bus_set_trusted(sd_bus *bus, int b) { - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - bus->trusted = !!b; - return 0; -} - -_public_ int sd_bus_set_description(sd_bus *bus, const char *description) { - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - return free_and_strdup(&bus->description, description); -} - -_public_ int sd_bus_set_allow_interactive_authorization(sd_bus *bus, int b) { - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - bus->allow_interactive_authorization = !!b; - return 0; -} - -_public_ int sd_bus_get_allow_interactive_authorization(sd_bus *bus) { - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - return bus->allow_interactive_authorization; -} - -static int hello_callback(sd_bus_message *reply, void *userdata, sd_bus_error *error) { - const char *s; - sd_bus *bus; - int r; - - assert(reply); - bus = reply->bus; - assert(bus); - assert(bus->state == BUS_HELLO || bus->state == BUS_CLOSING); - - r = sd_bus_message_get_errno(reply); - if (r > 0) - return -r; - - r = sd_bus_message_read(reply, "s", &s); - if (r < 0) - return r; - - if (!service_name_is_valid(s) || s[0] != ':') - return -EBADMSG; - - bus->unique_name = strdup(s); - if (!bus->unique_name) - return -ENOMEM; - - if (bus->state == BUS_HELLO) - bus->state = BUS_RUNNING; - - return 1; -} - -static int bus_send_hello(sd_bus *bus) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - assert(bus); - - if (!bus->bus_client || bus->is_kernel) - return 0; - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "Hello"); - if (r < 0) - return r; - - return sd_bus_call_async(bus, NULL, m, hello_callback, NULL, 0); -} - -int bus_start_running(sd_bus *bus) { - assert(bus); - - if (bus->bus_client && !bus->is_kernel) { - bus->state = BUS_HELLO; - return 1; - } - - bus->state = BUS_RUNNING; - return 1; -} - -static int parse_address_key(const char **p, const char *key, char **value) { - size_t l, n = 0, allocated = 0; - const char *a; - char *r = NULL; - - assert(p); - assert(*p); - assert(value); - - if (key) { - l = strlen(key); - if (strncmp(*p, key, l) != 0) - return 0; - - if ((*p)[l] != '=') - return 0; - - if (*value) - return -EINVAL; - - a = *p + l + 1; - } else - a = *p; - - while (*a != ';' && *a != ',' && *a != 0) { - char c; - - if (*a == '%') { - int x, y; - - x = unhexchar(a[1]); - if (x < 0) { - free(r); - return x; - } - - y = unhexchar(a[2]); - if (y < 0) { - free(r); - return y; - } - - c = (char) ((x << 4) | y); - a += 3; - } else { - c = *a; - a++; - } - - if (!GREEDY_REALLOC(r, allocated, n + 2)) - return -ENOMEM; - - r[n++] = c; - } - - if (!r) { - r = strdup(""); - if (!r) - return -ENOMEM; - } else - r[n] = 0; - - if (*a == ',') - a++; - - *p = a; - - free(*value); - *value = r; - - return 1; -} - -static void skip_address_key(const char **p) { - assert(p); - assert(*p); - - *p += strcspn(*p, ","); - - if (**p == ',') - (*p)++; -} - -static int parse_unix_address(sd_bus *b, const char **p, char **guid) { - _cleanup_free_ char *path = NULL, *abstract = NULL; - size_t l; - int r; - - assert(b); - assert(p); - assert(*p); - assert(guid); - - while (**p != 0 && **p != ';') { - r = parse_address_key(p, "guid", guid); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "path", &path); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "abstract", &abstract); - if (r < 0) - return r; - else if (r > 0) - continue; - - skip_address_key(p); - } - - if (!path && !abstract) - return -EINVAL; - - if (path && abstract) - return -EINVAL; - - if (path) { - l = strlen(path); - if (l > sizeof(b->sockaddr.un.sun_path)) - return -E2BIG; - - b->sockaddr.un.sun_family = AF_UNIX; - strncpy(b->sockaddr.un.sun_path, path, sizeof(b->sockaddr.un.sun_path)); - b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + l; - } else if (abstract) { - l = strlen(abstract); - if (l > sizeof(b->sockaddr.un.sun_path) - 1) - return -E2BIG; - - b->sockaddr.un.sun_family = AF_UNIX; - b->sockaddr.un.sun_path[0] = 0; - strncpy(b->sockaddr.un.sun_path+1, abstract, sizeof(b->sockaddr.un.sun_path)-1); - b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + 1 + l; - } - - return 0; -} - -static int parse_tcp_address(sd_bus *b, const char **p, char **guid) { - _cleanup_free_ char *host = NULL, *port = NULL, *family = NULL; - int r; - struct addrinfo *result, hints = { - .ai_socktype = SOCK_STREAM, - .ai_flags = AI_ADDRCONFIG, - }; - - assert(b); - assert(p); - assert(*p); - assert(guid); - - while (**p != 0 && **p != ';') { - r = parse_address_key(p, "guid", guid); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "host", &host); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "port", &port); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "family", &family); - if (r < 0) - return r; - else if (r > 0) - continue; - - skip_address_key(p); - } - - if (!host || !port) - return -EINVAL; - - if (family) { - if (streq(family, "ipv4")) - hints.ai_family = AF_INET; - else if (streq(family, "ipv6")) - hints.ai_family = AF_INET6; - else - return -EINVAL; - } - - r = getaddrinfo(host, port, &hints, &result); - if (r == EAI_SYSTEM) - return -errno; - else if (r != 0) - return -EADDRNOTAVAIL; - - memcpy(&b->sockaddr, result->ai_addr, result->ai_addrlen); - b->sockaddr_size = result->ai_addrlen; - - freeaddrinfo(result); - - return 0; -} - -static int parse_exec_address(sd_bus *b, const char **p, char **guid) { - char *path = NULL; - unsigned n_argv = 0, j; - char **argv = NULL; - size_t allocated = 0; - int r; - - assert(b); - assert(p); - assert(*p); - assert(guid); - - while (**p != 0 && **p != ';') { - r = parse_address_key(p, "guid", guid); - if (r < 0) - goto fail; - else if (r > 0) - continue; - - r = parse_address_key(p, "path", &path); - if (r < 0) - goto fail; - else if (r > 0) - continue; - - if (startswith(*p, "argv")) { - unsigned ul; - - errno = 0; - ul = strtoul(*p + 4, (char**) p, 10); - if (errno > 0 || **p != '=' || ul > 256) { - r = -EINVAL; - goto fail; - } - - (*p)++; - - if (ul >= n_argv) { - if (!GREEDY_REALLOC0(argv, allocated, ul + 2)) { - r = -ENOMEM; - goto fail; - } - - n_argv = ul + 1; - } - - r = parse_address_key(p, NULL, argv + ul); - if (r < 0) - goto fail; - - continue; - } - - skip_address_key(p); - } - - if (!path) { - r = -EINVAL; - goto fail; - } - - /* Make sure there are no holes in the array, with the - * exception of argv[0] */ - for (j = 1; j < n_argv; j++) - if (!argv[j]) { - r = -EINVAL; - goto fail; - } - - if (argv && argv[0] == NULL) { - argv[0] = strdup(path); - if (!argv[0]) { - r = -ENOMEM; - goto fail; - } - } - - b->exec_path = path; - b->exec_argv = argv; - return 0; - -fail: - for (j = 0; j < n_argv; j++) - free(argv[j]); - - free(argv); - free(path); - return r; -} - -static int parse_kernel_address(sd_bus *b, const char **p, char **guid) { - _cleanup_free_ char *path = NULL; - int r; - - assert(b); - assert(p); - assert(*p); - assert(guid); - - while (**p != 0 && **p != ';') { - r = parse_address_key(p, "guid", guid); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "path", &path); - if (r < 0) - return r; - else if (r > 0) - continue; - - skip_address_key(p); - } - - if (!path) - return -EINVAL; - - free(b->kernel); - b->kernel = path; - path = NULL; - - return 0; -} - -static int parse_container_unix_address(sd_bus *b, const char **p, char **guid) { - _cleanup_free_ char *machine = NULL, *pid = NULL; - int r; - - assert(b); - assert(p); - assert(*p); - assert(guid); - - while (**p != 0 && **p != ';') { - r = parse_address_key(p, "guid", guid); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "machine", &machine); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "pid", &pid); - if (r < 0) - return r; - else if (r > 0) - continue; - - skip_address_key(p); - } - - if (!machine == !pid) - return -EINVAL; - - if (machine) { - if (!machine_name_is_valid(machine)) - return -EINVAL; - - free(b->machine); - b->machine = machine; - machine = NULL; - } else { - b->machine = mfree(b->machine); - } - - if (pid) { - r = parse_pid(pid, &b->nspid); - if (r < 0) - return r; - } else - b->nspid = 0; - - b->sockaddr.un.sun_family = AF_UNIX; - strncpy(b->sockaddr.un.sun_path, "/var/run/dbus/system_bus_socket", sizeof(b->sockaddr.un.sun_path)); - b->sockaddr_size = SOCKADDR_UN_LEN(b->sockaddr.un); - - return 0; -} - -static int parse_container_kernel_address(sd_bus *b, const char **p, char **guid) { - _cleanup_free_ char *machine = NULL, *pid = NULL; - int r; - - assert(b); - assert(p); - assert(*p); - assert(guid); - - while (**p != 0 && **p != ';') { - r = parse_address_key(p, "guid", guid); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "machine", &machine); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "pid", &pid); - if (r < 0) - return r; - else if (r > 0) - continue; - - skip_address_key(p); - } - - if (!machine == !pid) - return -EINVAL; - - if (machine) { - if (!machine_name_is_valid(machine)) - return -EINVAL; - - free(b->machine); - b->machine = machine; - machine = NULL; - } else { - b->machine = mfree(b->machine); - } - - if (pid) { - r = parse_pid(pid, &b->nspid); - if (r < 0) - return r; - } else - b->nspid = 0; - - r = free_and_strdup(&b->kernel, "/sys/fs/kdbus/0-system/bus"); - if (r < 0) - return r; - - return 0; -} - -static void bus_reset_parsed_address(sd_bus *b) { - assert(b); - - zero(b->sockaddr); - b->sockaddr_size = 0; - b->exec_argv = strv_free(b->exec_argv); - b->exec_path = mfree(b->exec_path); - b->server_id = SD_ID128_NULL; - b->kernel = mfree(b->kernel); - b->machine = mfree(b->machine); - b->nspid = 0; -} - -static int bus_parse_next_address(sd_bus *b) { - _cleanup_free_ char *guid = NULL; - const char *a; - int r; - - assert(b); - - if (!b->address) - return 0; - if (b->address[b->address_index] == 0) - return 0; - - bus_reset_parsed_address(b); - - a = b->address + b->address_index; - - while (*a != 0) { - - if (*a == ';') { - a++; - continue; - } - - if (startswith(a, "unix:")) { - a += 5; - - r = parse_unix_address(b, &a, &guid); - if (r < 0) - return r; - break; - - } else if (startswith(a, "tcp:")) { - - a += 4; - r = parse_tcp_address(b, &a, &guid); - if (r < 0) - return r; - - break; - - } else if (startswith(a, "unixexec:")) { - - a += 9; - r = parse_exec_address(b, &a, &guid); - if (r < 0) - return r; - - break; - - } else if (startswith(a, "kernel:")) { - - a += 7; - r = parse_kernel_address(b, &a, &guid); - if (r < 0) - return r; - - break; - } else if (startswith(a, "x-machine-unix:")) { - - a += 15; - r = parse_container_unix_address(b, &a, &guid); - if (r < 0) - return r; - - break; - } else if (startswith(a, "x-machine-kernel:")) { - - a += 17; - r = parse_container_kernel_address(b, &a, &guid); - if (r < 0) - return r; - - break; - } - - a = strchr(a, ';'); - if (!a) - return 0; - } - - if (guid) { - r = sd_id128_from_string(guid, &b->server_id); - if (r < 0) - return r; - } - - b->address_index = a - b->address; - return 1; -} - -static int bus_start_address(sd_bus *b) { - bool container_kdbus_available = false; - bool kdbus_available = false; - int r; - - assert(b); - - for (;;) { - bool skipped = false; - - bus_close_fds(b); - - /* - * Usually, if you provide multiple different bus-addresses, we - * try all of them in order. We use the first one that - * succeeds. However, if you mix kernel and unix addresses, we - * never try unix-addresses if a previous kernel address was - * tried and kdbus was available. This is required to prevent - * clients to fallback to the bus-proxy if kdbus is available - * but failed (eg., too many connections). - */ - - if (b->exec_path) - r = bus_socket_exec(b); - else if ((b->nspid > 0 || b->machine) && b->kernel) { - r = bus_container_connect_kernel(b); - if (r < 0 && !IN_SET(r, -ENOENT, -ESOCKTNOSUPPORT)) - container_kdbus_available = true; - - } else if ((b->nspid > 0 || b->machine) && b->sockaddr.sa.sa_family != AF_UNSPEC) { - if (!container_kdbus_available) - r = bus_container_connect_socket(b); - else - skipped = true; - - } else if (b->kernel) { - r = bus_kernel_connect(b); - if (r < 0 && !IN_SET(r, -ENOENT, -ESOCKTNOSUPPORT)) - kdbus_available = true; - - } else if (b->sockaddr.sa.sa_family != AF_UNSPEC) { - if (!kdbus_available) - r = bus_socket_connect(b); - else - skipped = true; - } else - skipped = true; - - if (!skipped) { - if (r >= 0) { - r = attach_io_events(b); - if (r >= 0) - return r; - } - - b->last_connect_error = -r; - } - - r = bus_parse_next_address(b); - if (r < 0) - return r; - if (r == 0) - return b->last_connect_error ? -b->last_connect_error : -ECONNREFUSED; - } -} - -int bus_next_address(sd_bus *b) { - assert(b); - - bus_reset_parsed_address(b); - return bus_start_address(b); -} - -static int bus_start_fd(sd_bus *b) { - struct stat st; - int r; - - assert(b); - assert(b->input_fd >= 0); - assert(b->output_fd >= 0); - - r = fd_nonblock(b->input_fd, true); - if (r < 0) - return r; - - r = fd_cloexec(b->input_fd, true); - if (r < 0) - return r; - - if (b->input_fd != b->output_fd) { - r = fd_nonblock(b->output_fd, true); - if (r < 0) - return r; - - r = fd_cloexec(b->output_fd, true); - if (r < 0) - return r; - } - - if (fstat(b->input_fd, &st) < 0) - return -errno; - - if (S_ISCHR(b->input_fd)) - return bus_kernel_take_fd(b); - else - return bus_socket_take_fd(b); -} - -_public_ int sd_bus_start(sd_bus *bus) { - int r; - - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - bus->state = BUS_OPENING; - - if (bus->is_server && bus->bus_client) - return -EINVAL; - - if (bus->input_fd >= 0) - r = bus_start_fd(bus); - else if (bus->address || bus->sockaddr.sa.sa_family != AF_UNSPEC || bus->exec_path || bus->kernel || bus->machine) - r = bus_start_address(bus); - else - return -EINVAL; - - if (r < 0) { - sd_bus_close(bus); - return r; - } - - return bus_send_hello(bus); -} - -_public_ int sd_bus_open(sd_bus **ret) { - const char *e; - sd_bus *b; - int r; - - assert_return(ret, -EINVAL); - - /* Let's connect to the starter bus if it is set, and - * otherwise to the bus that is appropropriate for the scope - * we are running in */ - - e = secure_getenv("DBUS_STARTER_BUS_TYPE"); - if (e) { - if (streq(e, "system")) - return sd_bus_open_system(ret); - else if (STR_IN_SET(e, "session", "user")) - return sd_bus_open_user(ret); - } - - e = secure_getenv("DBUS_STARTER_ADDRESS"); - if (!e) { - if (cg_pid_get_owner_uid(0, NULL) >= 0) - return sd_bus_open_user(ret); - else - return sd_bus_open_system(ret); - } - - r = sd_bus_new(&b); - if (r < 0) - return r; - - r = sd_bus_set_address(b, e); - if (r < 0) - goto fail; - - b->bus_client = true; - - /* We don't know whether the bus is trusted or not, so better - * be safe, and authenticate everything */ - b->trusted = false; - b->attach_flags |= KDBUS_ATTACH_CAPS | KDBUS_ATTACH_CREDS; - b->creds_mask |= SD_BUS_CREDS_UID | SD_BUS_CREDS_EUID | SD_BUS_CREDS_EFFECTIVE_CAPS; - - r = sd_bus_start(b); - if (r < 0) - goto fail; - - *ret = b; - return 0; - -fail: - bus_free(b); - return r; -} - -int bus_set_address_system(sd_bus *b) { - const char *e; - assert(b); - - e = secure_getenv("DBUS_SYSTEM_BUS_ADDRESS"); - if (e) - return sd_bus_set_address(b, e); - - return sd_bus_set_address(b, DEFAULT_SYSTEM_BUS_ADDRESS); -} - -_public_ int sd_bus_open_system(sd_bus **ret) { - sd_bus *b; - int r; - - assert_return(ret, -EINVAL); - - r = sd_bus_new(&b); - if (r < 0) - return r; - - r = bus_set_address_system(b); - if (r < 0) - goto fail; - - b->bus_client = true; - b->is_system = true; - - /* Let's do per-method access control on the system bus. We - * need the caller's UID and capability set for that. */ - b->trusted = false; - b->attach_flags |= KDBUS_ATTACH_CAPS | KDBUS_ATTACH_CREDS; - b->creds_mask |= SD_BUS_CREDS_UID | SD_BUS_CREDS_EUID | SD_BUS_CREDS_EFFECTIVE_CAPS; - - r = sd_bus_start(b); - if (r < 0) - goto fail; - - *ret = b; - return 0; - -fail: - bus_free(b); - return r; -} - -int bus_set_address_user(sd_bus *b) { - const char *e; - uid_t uid; - int r; - - assert(b); - - e = secure_getenv("DBUS_SESSION_BUS_ADDRESS"); - if (e) - return sd_bus_set_address(b, e); - - r = cg_pid_get_owner_uid(0, &uid); - if (r < 0) - uid = getuid(); - - e = secure_getenv("XDG_RUNTIME_DIR"); - if (e) { - _cleanup_free_ char *ee = NULL; - - ee = bus_address_escape(e); - if (!ee) - return -ENOMEM; - - (void) asprintf(&b->address, KERNEL_USER_BUS_ADDRESS_FMT ";" UNIX_USER_BUS_ADDRESS_FMT, uid, ee); - } else - (void) asprintf(&b->address, KERNEL_USER_BUS_ADDRESS_FMT, uid); - - if (!b->address) - return -ENOMEM; - - return 0; -} - -_public_ int sd_bus_open_user(sd_bus **ret) { - sd_bus *b; - int r; - - assert_return(ret, -EINVAL); - - r = sd_bus_new(&b); - if (r < 0) - return r; - - r = bus_set_address_user(b); - if (r < 0) - return r; - - b->bus_client = true; - b->is_user = true; - - /* We don't do any per-method access control on the user - * bus. */ - b->trusted = true; - - r = sd_bus_start(b); - if (r < 0) - goto fail; - - *ret = b; - return 0; - -fail: - bus_free(b); - return r; -} - -int bus_set_address_system_remote(sd_bus *b, const char *host) { - _cleanup_free_ char *e = NULL; - char *m = NULL, *c = NULL; - - assert(b); - assert(host); - - /* Let's see if we shall enter some container */ - m = strchr(host, ':'); - if (m) { - m++; - - /* Let's make sure this is not a port of some kind, - * and is a valid machine name. */ - if (!in_charset(m, "0123456789") && machine_name_is_valid(m)) { - char *t; - - /* Cut out the host part */ - t = strndupa(host, m - host - 1); - e = bus_address_escape(t); - if (!e) - return -ENOMEM; - - c = strjoina(",argv4=--machine=", m); - } - } - - if (!e) { - e = bus_address_escape(host); - if (!e) - return -ENOMEM; - } - - b->address = strjoin("unixexec:path=ssh,argv1=-xT,argv2=", e, ",argv3=systemd-stdio-bridge", c, NULL); - if (!b->address) - return -ENOMEM; - - return 0; - } - -_public_ int sd_bus_open_system_remote(sd_bus **ret, const char *host) { - sd_bus *bus; - int r; - - assert_return(host, -EINVAL); - assert_return(ret, -EINVAL); - - r = sd_bus_new(&bus); - if (r < 0) - return r; - - r = bus_set_address_system_remote(bus, host); - if (r < 0) - goto fail; - - bus->bus_client = true; - bus->trusted = false; - bus->is_system = true; - - r = sd_bus_start(bus); - if (r < 0) - goto fail; - - *ret = bus; - return 0; - -fail: - bus_free(bus); - return r; -} - -int bus_set_address_system_machine(sd_bus *b, const char *machine) { - _cleanup_free_ char *e = NULL; - - assert(b); - assert(machine); - - e = bus_address_escape(machine); - if (!e) - return -ENOMEM; - - b->address = strjoin("x-machine-kernel:machine=", e, ";x-machine-unix:machine=", e, NULL); - if (!b->address) - return -ENOMEM; - - return 0; -} - -_public_ int sd_bus_open_system_machine(sd_bus **ret, const char *machine) { - sd_bus *bus; - int r; - - assert_return(machine, -EINVAL); - assert_return(ret, -EINVAL); - assert_return(machine_name_is_valid(machine), -EINVAL); - - r = sd_bus_new(&bus); - if (r < 0) - return r; - - r = bus_set_address_system_machine(bus, machine); - if (r < 0) - goto fail; - - bus->bus_client = true; - bus->trusted = false; - bus->is_system = true; - - r = sd_bus_start(bus); - if (r < 0) - goto fail; - - *ret = bus; - return 0; - -fail: - bus_free(bus); - return r; -} - -_public_ void sd_bus_close(sd_bus *bus) { - - if (!bus) - return; - if (bus->state == BUS_CLOSED) - return; - if (bus_pid_changed(bus)) - return; - - bus->state = BUS_CLOSED; - - sd_bus_detach_event(bus); - - /* Drop all queued messages so that they drop references to - * the bus object and the bus may be freed */ - bus_reset_queues(bus); - - if (!bus->is_kernel) - bus_close_fds(bus); - - /* We'll leave the fd open in case this is a kernel bus, since - * there might still be memblocks around that reference this - * bus, and they might need to invoke the KDBUS_CMD_FREE - * ioctl on the fd when they are freed. */ -} - -_public_ sd_bus* sd_bus_flush_close_unref(sd_bus *bus) { - - if (!bus) - return NULL; - - sd_bus_flush(bus); - sd_bus_close(bus); - - return sd_bus_unref(bus); -} - -static void bus_enter_closing(sd_bus *bus) { - assert(bus); - - if (bus->state != BUS_OPENING && - bus->state != BUS_AUTHENTICATING && - bus->state != BUS_HELLO && - bus->state != BUS_RUNNING) - return; - - bus->state = BUS_CLOSING; -} - -_public_ sd_bus *sd_bus_ref(sd_bus *bus) { - - if (!bus) - return NULL; - - assert_se(REFCNT_INC(bus->n_ref) >= 2); - - return bus; -} - -_public_ sd_bus *sd_bus_unref(sd_bus *bus) { - unsigned i; - - if (!bus) - return NULL; - - i = REFCNT_DEC(bus->n_ref); - if (i > 0) - return NULL; - - bus_free(bus); - return NULL; -} - -_public_ int sd_bus_is_open(sd_bus *bus) { - - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - return BUS_IS_OPEN(bus->state); -} - -_public_ int sd_bus_can_send(sd_bus *bus, char type) { - int r; - - assert_return(bus, -EINVAL); - assert_return(bus->state != BUS_UNSET, -ENOTCONN); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (bus->hello_flags & KDBUS_HELLO_MONITOR) - return 0; - - if (type == SD_BUS_TYPE_UNIX_FD) { - if (!(bus->hello_flags & KDBUS_HELLO_ACCEPT_FD)) - return 0; - - r = bus_ensure_running(bus); - if (r < 0) - return r; - - return bus->can_fds; - } - - return bus_type_is_valid(type); -} - -_public_ int sd_bus_get_bus_id(sd_bus *bus, sd_id128_t *id) { - int r; - - assert_return(bus, -EINVAL); - assert_return(id, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - r = bus_ensure_running(bus); - if (r < 0) - return r; - - *id = bus->server_id; - return 0; -} - -static int bus_seal_message(sd_bus *b, sd_bus_message *m, usec_t timeout) { - assert(b); - assert(m); - - if (m->sealed) { - /* If we copy the same message to multiple - * destinations, avoid using the same cookie - * numbers. */ - b->cookie = MAX(b->cookie, BUS_MESSAGE_COOKIE(m)); - return 0; - } - - if (timeout == 0) - timeout = BUS_DEFAULT_TIMEOUT; - - return bus_message_seal(m, ++b->cookie, timeout); -} - -static int bus_remarshal_message(sd_bus *b, sd_bus_message **m) { - bool remarshal = false; - - assert(b); - - /* wrong packet version */ - if (b->message_version != 0 && b->message_version != (*m)->header->version) - remarshal = true; - - /* wrong packet endianness */ - if (b->message_endian != 0 && b->message_endian != (*m)->header->endian) - remarshal = true; - - /* TODO: kdbus-messages received from the kernel contain data which is - * not allowed to be passed to KDBUS_CMD_SEND. Therefore, we have to - * force remarshaling of the message. Technically, we could just - * recreate the kdbus message, but that is non-trivial as other parts of - * the message refer to m->kdbus already. This should be fixed! */ - if ((*m)->kdbus && (*m)->release_kdbus) - remarshal = true; - - return remarshal ? bus_message_remarshal(b, m) : 0; -} - -int bus_seal_synthetic_message(sd_bus *b, sd_bus_message *m) { - assert(b); - assert(m); - - /* Fake some timestamps, if they were requested, and not - * already initialized */ - if (b->attach_flags & KDBUS_ATTACH_TIMESTAMP) { - if (m->realtime <= 0) - m->realtime = now(CLOCK_REALTIME); - - if (m->monotonic <= 0) - m->monotonic = now(CLOCK_MONOTONIC); - } - - /* The bus specification says the serial number cannot be 0, - * hence let's fill something in for synthetic messages. Since - * synthetic messages might have a fake sender and we don't - * want to interfere with the real sender's serial numbers we - * pick a fixed, artificial one. We use (uint32_t) -1 rather - * than (uint64_t) -1 since dbus1 only had 32bit identifiers, - * even though kdbus can do 64bit. */ - return bus_message_seal(m, 0xFFFFFFFFULL, 0); -} - -static int bus_write_message(sd_bus *bus, sd_bus_message *m, bool hint_sync_call, size_t *idx) { - int r; - - assert(bus); - assert(m); - - if (bus->is_kernel) - r = bus_kernel_write_message(bus, m, hint_sync_call); - else - r = bus_socket_write_message(bus, m, idx); - - if (r <= 0) - return r; - - if (bus->is_kernel || *idx >= BUS_MESSAGE_SIZE(m)) - log_debug("Sent message type=%s sender=%s destination=%s object=%s interface=%s member=%s cookie=%" PRIu64 " reply_cookie=%" PRIu64 " error=%s", - bus_message_type_to_string(m->header->type), - strna(sd_bus_message_get_sender(m)), - strna(sd_bus_message_get_destination(m)), - strna(sd_bus_message_get_path(m)), - strna(sd_bus_message_get_interface(m)), - strna(sd_bus_message_get_member(m)), - BUS_MESSAGE_COOKIE(m), - m->reply_cookie, - strna(m->error.message)); - - return r; -} - -static int dispatch_wqueue(sd_bus *bus) { - int r, ret = 0; - - assert(bus); - assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); - - while (bus->wqueue_size > 0) { - - r = bus_write_message(bus, bus->wqueue[0], false, &bus->windex); - if (r < 0) - return r; - else if (r == 0) - /* Didn't do anything this time */ - return ret; - else if (bus->is_kernel || bus->windex >= BUS_MESSAGE_SIZE(bus->wqueue[0])) { - /* Fully written. Let's drop the entry from - * the queue. - * - * This isn't particularly optimized, but - * well, this is supposed to be our worst-case - * buffer only, and the socket buffer is - * supposed to be our primary buffer, and if - * it got full, then all bets are off - * anyway. */ - - bus->wqueue_size--; - sd_bus_message_unref(bus->wqueue[0]); - memmove(bus->wqueue, bus->wqueue + 1, sizeof(sd_bus_message*) * bus->wqueue_size); - bus->windex = 0; - - ret = 1; - } - } - - return ret; -} - -static int bus_read_message(sd_bus *bus, bool hint_priority, int64_t priority) { - assert(bus); - - if (bus->is_kernel) - return bus_kernel_read_message(bus, hint_priority, priority); - else - return bus_socket_read_message(bus); -} - -int bus_rqueue_make_room(sd_bus *bus) { - assert(bus); - - if (bus->rqueue_size >= BUS_RQUEUE_MAX) - return -ENOBUFS; - - if (!GREEDY_REALLOC(bus->rqueue, bus->rqueue_allocated, bus->rqueue_size + 1)) - return -ENOMEM; - - return 0; -} - -static int dispatch_rqueue(sd_bus *bus, bool hint_priority, int64_t priority, sd_bus_message **m) { - int r, ret = 0; - - assert(bus); - assert(m); - assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); - - /* Note that the priority logic is only available on kdbus, - * where the rqueue is unused. We check the rqueue here - * anyway, because it's simple... */ - - for (;;) { - if (bus->rqueue_size > 0) { - /* Dispatch a queued message */ - - *m = bus->rqueue[0]; - bus->rqueue_size--; - memmove(bus->rqueue, bus->rqueue + 1, sizeof(sd_bus_message*) * bus->rqueue_size); - return 1; - } - - /* Try to read a new message */ - r = bus_read_message(bus, hint_priority, priority); - if (r < 0) - return r; - if (r == 0) - return ret; - - ret = 1; - } -} - -static int bus_send_internal(sd_bus *bus, sd_bus_message *_m, uint64_t *cookie, bool hint_sync_call) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m); - int r; - - assert_return(m, -EINVAL); - - if (!bus) - bus = m->bus; - - assert_return(!bus_pid_changed(bus), -ECHILD); - assert_return(!bus->is_kernel || !(bus->hello_flags & KDBUS_HELLO_MONITOR), -EROFS); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (m->n_fds > 0) { - r = sd_bus_can_send(bus, SD_BUS_TYPE_UNIX_FD); - if (r < 0) - return r; - if (r == 0) - return -EOPNOTSUPP; - } - - /* If the cookie number isn't kept, then we know that no reply - * is expected */ - if (!cookie && !m->sealed) - m->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED; - - r = bus_seal_message(bus, m, 0); - if (r < 0) - return r; - - /* Remarshall if we have to. This will possibly unref the - * message and place a replacement in m */ - r = bus_remarshal_message(bus, &m); - if (r < 0) - return r; - - /* If this is a reply and no reply was requested, then let's - * suppress this, if we can */ - if (m->dont_send) - goto finish; - - if ((bus->state == BUS_RUNNING || bus->state == BUS_HELLO) && bus->wqueue_size <= 0) { - size_t idx = 0; - - r = bus_write_message(bus, m, hint_sync_call, &idx); - if (r < 0) { - if (IN_SET(r, -ENOTCONN, -ECONNRESET, -EPIPE, -ESHUTDOWN)) { - bus_enter_closing(bus); - return -ECONNRESET; - } - - return r; - } - - if (!bus->is_kernel && idx < BUS_MESSAGE_SIZE(m)) { - /* Wasn't fully written. So let's remember how - * much was written. Note that the first entry - * of the wqueue array is always allocated so - * that we always can remember how much was - * written. */ - bus->wqueue[0] = sd_bus_message_ref(m); - bus->wqueue_size = 1; - bus->windex = idx; - } - - } else { - /* Just append it to the queue. */ - - if (bus->wqueue_size >= BUS_WQUEUE_MAX) - return -ENOBUFS; - - if (!GREEDY_REALLOC(bus->wqueue, bus->wqueue_allocated, bus->wqueue_size + 1)) - return -ENOMEM; - - bus->wqueue[bus->wqueue_size++] = sd_bus_message_ref(m); - } - -finish: - if (cookie) - *cookie = BUS_MESSAGE_COOKIE(m); - - return 1; -} - -_public_ int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie) { - return bus_send_internal(bus, m, cookie, false); -} - -_public_ int sd_bus_send_to(sd_bus *bus, sd_bus_message *m, const char *destination, uint64_t *cookie) { - int r; - - assert_return(m, -EINVAL); - - if (!bus) - bus = m->bus; - - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (!streq_ptr(m->destination, destination)) { - - if (!destination) - return -EEXIST; - - r = sd_bus_message_set_destination(m, destination); - if (r < 0) - return r; - } - - return sd_bus_send(bus, m, cookie); -} - -static usec_t calc_elapse(uint64_t usec) { - if (usec == (uint64_t) -1) - return 0; - - return now(CLOCK_MONOTONIC) + usec; -} - -static int timeout_compare(const void *a, const void *b) { - const struct reply_callback *x = a, *y = b; - - if (x->timeout != 0 && y->timeout == 0) - return -1; - - if (x->timeout == 0 && y->timeout != 0) - return 1; - - if (x->timeout < y->timeout) - return -1; - - if (x->timeout > y->timeout) - return 1; - - return 0; -} - -_public_ int sd_bus_call_async( - sd_bus *bus, - sd_bus_slot **slot, - sd_bus_message *_m, - sd_bus_message_handler_t callback, - void *userdata, - uint64_t usec) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m); - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *s = NULL; - int r; - - assert_return(m, -EINVAL); - assert_return(m->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); - assert_return(!(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED), -EINVAL); - assert_return(callback, -EINVAL); - - if (!bus) - bus = m->bus; - - assert_return(!bus_pid_changed(bus), -ECHILD); - assert_return(!bus->is_kernel || !(bus->hello_flags & KDBUS_HELLO_MONITOR), -EROFS); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - r = ordered_hashmap_ensure_allocated(&bus->reply_callbacks, &uint64_hash_ops); - if (r < 0) - return r; - - r = prioq_ensure_allocated(&bus->reply_callbacks_prioq, timeout_compare); - if (r < 0) - return r; - - r = bus_seal_message(bus, m, usec); - if (r < 0) - return r; - - r = bus_remarshal_message(bus, &m); - if (r < 0) - return r; - - s = bus_slot_allocate(bus, !slot, BUS_REPLY_CALLBACK, sizeof(struct reply_callback), userdata); - if (!s) - return -ENOMEM; - - s->reply_callback.callback = callback; - - s->reply_callback.cookie = BUS_MESSAGE_COOKIE(m); - r = ordered_hashmap_put(bus->reply_callbacks, &s->reply_callback.cookie, &s->reply_callback); - if (r < 0) { - s->reply_callback.cookie = 0; - return r; - } - - s->reply_callback.timeout = calc_elapse(m->timeout); - if (s->reply_callback.timeout != 0) { - r = prioq_put(bus->reply_callbacks_prioq, &s->reply_callback, &s->reply_callback.prioq_idx); - if (r < 0) { - s->reply_callback.timeout = 0; - return r; - } - } - - r = sd_bus_send(bus, m, &s->reply_callback.cookie); - if (r < 0) - return r; - - if (slot) - *slot = s; - s = NULL; - - return r; -} - -int bus_ensure_running(sd_bus *bus) { - int r; - - assert(bus); - - if (bus->state == BUS_UNSET || bus->state == BUS_CLOSED || bus->state == BUS_CLOSING) - return -ENOTCONN; - if (bus->state == BUS_RUNNING) - return 1; - - for (;;) { - r = sd_bus_process(bus, NULL); - if (r < 0) - return r; - if (bus->state == BUS_RUNNING) - return 1; - if (r > 0) - continue; - - r = sd_bus_wait(bus, (uint64_t) -1); - if (r < 0) - return r; - } -} - -_public_ int sd_bus_call( - sd_bus *bus, - sd_bus_message *_m, - uint64_t usec, - sd_bus_error *error, - sd_bus_message **reply) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m); - usec_t timeout; - uint64_t cookie; - unsigned i; - int r; - - bus_assert_return(m, -EINVAL, error); - bus_assert_return(m->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL, error); - bus_assert_return(!(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED), -EINVAL, error); - bus_assert_return(!bus_error_is_dirty(error), -EINVAL, error); - - if (!bus) - bus = m->bus; - - bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); - bus_assert_return(!bus->is_kernel || !(bus->hello_flags & KDBUS_HELLO_MONITOR), -EROFS, error); - - if (!BUS_IS_OPEN(bus->state)) { - r = -ENOTCONN; - goto fail; - } - - r = bus_ensure_running(bus); - if (r < 0) - goto fail; - - i = bus->rqueue_size; - - r = bus_seal_message(bus, m, usec); - if (r < 0) - goto fail; - - r = bus_remarshal_message(bus, &m); - if (r < 0) - goto fail; - - r = bus_send_internal(bus, m, &cookie, true); - if (r < 0) - goto fail; - - timeout = calc_elapse(m->timeout); - - for (;;) { - usec_t left; - - while (i < bus->rqueue_size) { - sd_bus_message *incoming = NULL; - - incoming = bus->rqueue[i]; - - if (incoming->reply_cookie == cookie) { - /* Found a match! */ - - memmove(bus->rqueue + i, bus->rqueue + i + 1, sizeof(sd_bus_message*) * (bus->rqueue_size - i - 1)); - bus->rqueue_size--; - log_debug_bus_message(incoming); - - if (incoming->header->type == SD_BUS_MESSAGE_METHOD_RETURN) { - - if (incoming->n_fds <= 0 || (bus->hello_flags & KDBUS_HELLO_ACCEPT_FD)) { - if (reply) - *reply = incoming; - else - sd_bus_message_unref(incoming); - - return 1; - } - - r = sd_bus_error_setf(error, SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Reply message contained file descriptors which I couldn't accept. Sorry."); - sd_bus_message_unref(incoming); - return r; - - } else if (incoming->header->type == SD_BUS_MESSAGE_METHOD_ERROR) { - r = sd_bus_error_copy(error, &incoming->error); - sd_bus_message_unref(incoming); - return r; - } else { - r = -EIO; - goto fail; - } - - } else if (BUS_MESSAGE_COOKIE(incoming) == cookie && - bus->unique_name && - incoming->sender && - streq(bus->unique_name, incoming->sender)) { - - memmove(bus->rqueue + i, bus->rqueue + i + 1, sizeof(sd_bus_message*) * (bus->rqueue_size - i - 1)); - bus->rqueue_size--; - - /* Our own message? Somebody is trying - * to send its own client a message, - * let's not dead-lock, let's fail - * immediately. */ - - sd_bus_message_unref(incoming); - r = -ELOOP; - goto fail; - } - - /* Try to read more, right-away */ - i++; - } - - r = bus_read_message(bus, false, 0); - if (r < 0) { - if (IN_SET(r, -ENOTCONN, -ECONNRESET, -EPIPE, -ESHUTDOWN)) { - bus_enter_closing(bus); - r = -ECONNRESET; - } - - goto fail; - } - if (r > 0) - continue; - - if (timeout > 0) { - usec_t n; - - n = now(CLOCK_MONOTONIC); - if (n >= timeout) { - r = -ETIMEDOUT; - goto fail; - } - - left = timeout - n; - } else - left = (uint64_t) -1; - - r = bus_poll(bus, true, left); - if (r < 0) - goto fail; - if (r == 0) { - r = -ETIMEDOUT; - goto fail; - } - - r = dispatch_wqueue(bus); - if (r < 0) { - if (IN_SET(r, -ENOTCONN, -ECONNRESET, -EPIPE, -ESHUTDOWN)) { - bus_enter_closing(bus); - r = -ECONNRESET; - } - - goto fail; - } - } - -fail: - return sd_bus_error_set_errno(error, r); -} - -_public_ int sd_bus_get_fd(sd_bus *bus) { - - assert_return(bus, -EINVAL); - assert_return(bus->input_fd == bus->output_fd, -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - return bus->input_fd; -} - -_public_ int sd_bus_get_events(sd_bus *bus) { - int flags = 0; - - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state) && bus->state != BUS_CLOSING) - return -ENOTCONN; - - if (bus->state == BUS_OPENING) - flags |= POLLOUT; - else if (bus->state == BUS_AUTHENTICATING) { - - if (bus_socket_auth_needs_write(bus)) - flags |= POLLOUT; - - flags |= POLLIN; - - } else if (bus->state == BUS_RUNNING || bus->state == BUS_HELLO) { - if (bus->rqueue_size <= 0) - flags |= POLLIN; - if (bus->wqueue_size > 0) - flags |= POLLOUT; - } - - return flags; -} - -_public_ int sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec) { - struct reply_callback *c; - - assert_return(bus, -EINVAL); - assert_return(timeout_usec, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state) && bus->state != BUS_CLOSING) - return -ENOTCONN; - - if (bus->track_queue) { - *timeout_usec = 0; - return 1; - } - - if (bus->state == BUS_CLOSING) { - *timeout_usec = 0; - return 1; - } - - if (bus->state == BUS_AUTHENTICATING) { - *timeout_usec = bus->auth_timeout; - return 1; - } - - if (bus->state != BUS_RUNNING && bus->state != BUS_HELLO) { - *timeout_usec = (uint64_t) -1; - return 0; - } - - if (bus->rqueue_size > 0) { - *timeout_usec = 0; - return 1; - } - - c = prioq_peek(bus->reply_callbacks_prioq); - if (!c) { - *timeout_usec = (uint64_t) -1; - return 0; - } - - if (c->timeout == 0) { - *timeout_usec = (uint64_t) -1; - return 0; - } - - *timeout_usec = c->timeout; - return 1; -} - -static int process_timeout(sd_bus *bus) { - _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message* m = NULL; - struct reply_callback *c; - sd_bus_slot *slot; - usec_t n; - int r; - - assert(bus); - - c = prioq_peek(bus->reply_callbacks_prioq); - if (!c) - return 0; - - n = now(CLOCK_MONOTONIC); - if (c->timeout > n) - return 0; - - r = bus_message_new_synthetic_error( - bus, - c->cookie, - &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Method call timed out"), - &m); - if (r < 0) - return r; - - r = bus_seal_synthetic_message(bus, m); - if (r < 0) - return r; - - assert_se(prioq_pop(bus->reply_callbacks_prioq) == c); - c->timeout = 0; - - ordered_hashmap_remove(bus->reply_callbacks, &c->cookie); - c->cookie = 0; - - slot = container_of(c, sd_bus_slot, reply_callback); - - bus->iteration_counter++; - - bus->current_message = m; - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_handler = c->callback; - bus->current_userdata = slot->userdata; - r = c->callback(m, slot->userdata, &error_buffer); - bus->current_userdata = NULL; - bus->current_handler = NULL; - bus->current_slot = NULL; - bus->current_message = NULL; - - if (slot->floating) { - bus_slot_disconnect(slot); - sd_bus_slot_unref(slot); - } - - sd_bus_slot_unref(slot); - - return bus_maybe_reply_error(m, r, &error_buffer); -} - -static int process_hello(sd_bus *bus, sd_bus_message *m) { - assert(bus); - assert(m); - - if (bus->state != BUS_HELLO) - return 0; - - /* Let's make sure the first message on the bus is the HELLO - * reply. But note that we don't actually parse the message - * here (we leave that to the usual handling), we just verify - * we don't let any earlier msg through. */ - - if (m->header->type != SD_BUS_MESSAGE_METHOD_RETURN && - m->header->type != SD_BUS_MESSAGE_METHOD_ERROR) - return -EIO; - - if (m->reply_cookie != 1) - return -EIO; - - return 0; -} - -static int process_reply(sd_bus *bus, sd_bus_message *m) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *synthetic_reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; - struct reply_callback *c; - sd_bus_slot *slot; - int r; - - assert(bus); - assert(m); - - if (m->header->type != SD_BUS_MESSAGE_METHOD_RETURN && - m->header->type != SD_BUS_MESSAGE_METHOD_ERROR) - return 0; - - if (bus->is_kernel && (bus->hello_flags & KDBUS_HELLO_MONITOR)) - return 0; - - if (m->destination && bus->unique_name && !streq_ptr(m->destination, bus->unique_name)) - return 0; - - c = ordered_hashmap_remove(bus->reply_callbacks, &m->reply_cookie); - if (!c) - return 0; - - c->cookie = 0; - - slot = container_of(c, sd_bus_slot, reply_callback); - - if (m->n_fds > 0 && !(bus->hello_flags & KDBUS_HELLO_ACCEPT_FD)) { - - /* If the reply contained a file descriptor which we - * didn't want we pass an error instead. */ - - r = bus_message_new_synthetic_error( - bus, - m->reply_cookie, - &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Reply message contained file descriptor"), - &synthetic_reply); - if (r < 0) - return r; - - /* Copy over original timestamp */ - synthetic_reply->realtime = m->realtime; - synthetic_reply->monotonic = m->monotonic; - synthetic_reply->seqnum = m->seqnum; - - r = bus_seal_synthetic_message(bus, synthetic_reply); - if (r < 0) - return r; - - m = synthetic_reply; - } else { - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - } - - if (c->timeout != 0) { - prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx); - c->timeout = 0; - } - - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_handler = c->callback; - bus->current_userdata = slot->userdata; - r = c->callback(m, slot->userdata, &error_buffer); - bus->current_userdata = NULL; - bus->current_handler = NULL; - bus->current_slot = NULL; - - if (slot->floating) { - bus_slot_disconnect(slot); - sd_bus_slot_unref(slot); - } - - sd_bus_slot_unref(slot); - - return bus_maybe_reply_error(m, r, &error_buffer); -} - -static int process_filter(sd_bus *bus, sd_bus_message *m) { - _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; - struct filter_callback *l; - int r; - - assert(bus); - assert(m); - - do { - bus->filter_callbacks_modified = false; - - LIST_FOREACH(callbacks, l, bus->filter_callbacks) { - sd_bus_slot *slot; - - if (bus->filter_callbacks_modified) - break; - - /* Don't run this more than once per iteration */ - if (l->last_iteration == bus->iteration_counter) - continue; - - l->last_iteration = bus->iteration_counter; - - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - slot = container_of(l, sd_bus_slot, filter_callback); - - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_handler = l->callback; - bus->current_userdata = slot->userdata; - r = l->callback(m, slot->userdata, &error_buffer); - bus->current_userdata = NULL; - bus->current_handler = NULL; - bus->current_slot = sd_bus_slot_unref(slot); - - r = bus_maybe_reply_error(m, r, &error_buffer); - if (r != 0) - return r; - - } - - } while (bus->filter_callbacks_modified); - - return 0; -} - -static int process_match(sd_bus *bus, sd_bus_message *m) { - int r; - - assert(bus); - assert(m); - - do { - bus->match_callbacks_modified = false; - - r = bus_match_run(bus, &bus->match_callbacks, m); - if (r != 0) - return r; - - } while (bus->match_callbacks_modified); - - return 0; -} - -static int process_builtin(sd_bus *bus, sd_bus_message *m) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int r; - - assert(bus); - assert(m); - - if (bus->hello_flags & KDBUS_HELLO_MONITOR) - return 0; - - if (bus->manual_peer_interface) - return 0; - - if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL) - return 0; - - if (!streq_ptr(m->interface, "org.freedesktop.DBus.Peer")) - return 0; - - if (m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) - return 1; - - if (streq_ptr(m->member, "Ping")) - r = sd_bus_message_new_method_return(m, &reply); - else if (streq_ptr(m->member, "GetMachineId")) { - sd_id128_t id; - char sid[33]; - - r = sd_id128_get_machine(&id); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(m, &reply); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "s", sd_id128_to_string(id, sid)); - } else { - r = sd_bus_message_new_method_errorf( - m, &reply, - SD_BUS_ERROR_UNKNOWN_METHOD, - "Unknown method '%s' on interface '%s'.", m->member, m->interface); - } - - if (r < 0) - return r; - - r = sd_bus_send(bus, reply, NULL); - if (r < 0) - return r; - - return 1; -} - -static int process_fd_check(sd_bus *bus, sd_bus_message *m) { - assert(bus); - assert(m); - - /* If we got a message with a file descriptor which we didn't - * want to accept, then let's drop it. How can this even - * happen? For example, when the kernel queues a message into - * an activatable names's queue which allows fds, and then is - * delivered to us later even though we ourselves did not - * negotiate it. */ - - if (bus->hello_flags & KDBUS_HELLO_MONITOR) - return 0; - - if (m->n_fds <= 0) - return 0; - - if (bus->hello_flags & KDBUS_HELLO_ACCEPT_FD) - return 0; - - if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL) - return 1; /* just eat it up */ - - return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Message contains file descriptors, which I cannot accept. Sorry."); -} - -static int process_message(sd_bus *bus, sd_bus_message *m) { - int r; - - assert(bus); - assert(m); - - bus->current_message = m; - bus->iteration_counter++; - - log_debug_bus_message(m); - - r = process_hello(bus, m); - if (r != 0) - goto finish; - - r = process_reply(bus, m); - if (r != 0) - goto finish; - - r = process_fd_check(bus, m); - if (r != 0) - goto finish; - - r = process_filter(bus, m); - if (r != 0) - goto finish; - - r = process_match(bus, m); - if (r != 0) - goto finish; - - r = process_builtin(bus, m); - if (r != 0) - goto finish; - - r = bus_process_object(bus, m); - -finish: - bus->current_message = NULL; - return r; -} - -static int dispatch_track(sd_bus *bus) { - assert(bus); - - if (!bus->track_queue) - return 0; - - bus_track_dispatch(bus->track_queue); - return 1; -} - -static int process_running(sd_bus *bus, bool hint_priority, int64_t priority, sd_bus_message **ret) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - assert(bus); - assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); - - r = process_timeout(bus); - if (r != 0) - goto null_message; - - r = dispatch_wqueue(bus); - if (r != 0) - goto null_message; - - r = dispatch_track(bus); - if (r != 0) - goto null_message; - - r = dispatch_rqueue(bus, hint_priority, priority, &m); - if (r < 0) - return r; - if (!m) - goto null_message; - - r = process_message(bus, m); - if (r != 0) - goto null_message; - - if (ret) { - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - *ret = m; - m = NULL; - return 1; - } - - if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) { - - log_debug("Unprocessed message call sender=%s object=%s interface=%s member=%s", - strna(sd_bus_message_get_sender(m)), - strna(sd_bus_message_get_path(m)), - strna(sd_bus_message_get_interface(m)), - strna(sd_bus_message_get_member(m))); - - r = sd_bus_reply_method_errorf( - m, - SD_BUS_ERROR_UNKNOWN_OBJECT, - "Unknown object '%s'.", m->path); - if (r < 0) - return r; - } - - return 1; - -null_message: - if (r >= 0 && ret) - *ret = NULL; - - return r; -} - -static int process_closing(sd_bus *bus, sd_bus_message **ret) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - struct reply_callback *c; - int r; - - assert(bus); - assert(bus->state == BUS_CLOSING); - - c = ordered_hashmap_first(bus->reply_callbacks); - if (c) { - _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; - sd_bus_slot *slot; - - /* First, fail all outstanding method calls */ - r = bus_message_new_synthetic_error( - bus, - c->cookie, - &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Connection terminated"), - &m); - if (r < 0) - return r; - - r = bus_seal_synthetic_message(bus, m); - if (r < 0) - return r; - - if (c->timeout != 0) { - prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx); - c->timeout = 0; - } - - ordered_hashmap_remove(bus->reply_callbacks, &c->cookie); - c->cookie = 0; - - slot = container_of(c, sd_bus_slot, reply_callback); - - bus->iteration_counter++; - - bus->current_message = m; - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_handler = c->callback; - bus->current_userdata = slot->userdata; - r = c->callback(m, slot->userdata, &error_buffer); - bus->current_userdata = NULL; - bus->current_handler = NULL; - bus->current_slot = NULL; - bus->current_message = NULL; - - if (slot->floating) { - bus_slot_disconnect(slot); - sd_bus_slot_unref(slot); - } - - sd_bus_slot_unref(slot); - - return bus_maybe_reply_error(m, r, &error_buffer); - } - - /* Then, synthesize a Disconnected message */ - r = sd_bus_message_new_signal( - bus, - &m, - "/org/freedesktop/DBus/Local", - "org.freedesktop.DBus.Local", - "Disconnected"); - if (r < 0) - return r; - - bus_message_set_sender_local(bus, m); - - r = bus_seal_synthetic_message(bus, m); - if (r < 0) - return r; - - sd_bus_close(bus); - - bus->current_message = m; - bus->iteration_counter++; - - r = process_filter(bus, m); - if (r != 0) - goto finish; - - r = process_match(bus, m); - if (r != 0) - goto finish; - - if (ret) { - *ret = m; - m = NULL; - } - - r = 1; - -finish: - bus->current_message = NULL; - - return r; -} - -static int bus_process_internal(sd_bus *bus, bool hint_priority, int64_t priority, sd_bus_message **ret) { - BUS_DONT_DESTROY(bus); - int r; - - /* Returns 0 when we didn't do anything. This should cause the - * caller to invoke sd_bus_wait() before returning the next - * time. Returns > 0 when we did something, which possibly - * means *ret is filled in with an unprocessed message. */ - - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - /* We don't allow recursively invoking sd_bus_process(). */ - assert_return(!bus->current_message, -EBUSY); - assert(!bus->current_slot); - - switch (bus->state) { - - case BUS_UNSET: - return -ENOTCONN; - - case BUS_CLOSED: - return -ECONNRESET; - - case BUS_OPENING: - r = bus_socket_process_opening(bus); - if (IN_SET(r, -ENOTCONN, -ECONNRESET, -EPIPE, -ESHUTDOWN)) { - bus_enter_closing(bus); - r = 1; - } else if (r < 0) - return r; - if (ret) - *ret = NULL; - return r; - - case BUS_AUTHENTICATING: - r = bus_socket_process_authenticating(bus); - if (IN_SET(r, -ENOTCONN, -ECONNRESET, -EPIPE, -ESHUTDOWN)) { - bus_enter_closing(bus); - r = 1; - } else if (r < 0) - return r; - - if (ret) - *ret = NULL; - - return r; - - case BUS_RUNNING: - case BUS_HELLO: - r = process_running(bus, hint_priority, priority, ret); - if (IN_SET(r, -ENOTCONN, -ECONNRESET, -EPIPE, -ESHUTDOWN)) { - bus_enter_closing(bus); - r = 1; - - if (ret) - *ret = NULL; - } - - return r; - - case BUS_CLOSING: - return process_closing(bus, ret); - } - - assert_not_reached("Unknown state"); -} - -_public_ int sd_bus_process(sd_bus *bus, sd_bus_message **ret) { - return bus_process_internal(bus, false, 0, ret); -} - -_public_ int sd_bus_process_priority(sd_bus *bus, int64_t priority, sd_bus_message **ret) { - return bus_process_internal(bus, true, priority, ret); -} - -static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec) { - struct pollfd p[2] = {}; - int r, e, n; - struct timespec ts; - usec_t m = USEC_INFINITY; - - assert(bus); - - if (bus->state == BUS_CLOSING) - return 1; - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - e = sd_bus_get_events(bus); - if (e < 0) - return e; - - if (need_more) - /* The caller really needs some more data, he doesn't - * care about what's already read, or any timeouts - * except its own. */ - e |= POLLIN; - else { - usec_t until; - /* The caller wants to process if there's something to - * process, but doesn't care otherwise */ - - r = sd_bus_get_timeout(bus, &until); - if (r < 0) - return r; - if (r > 0) { - usec_t nw; - nw = now(CLOCK_MONOTONIC); - m = until > nw ? until - nw : 0; - } - } - - if (timeout_usec != (uint64_t) -1 && (m == (uint64_t) -1 || timeout_usec < m)) - m = timeout_usec; - - p[0].fd = bus->input_fd; - if (bus->output_fd == bus->input_fd) { - p[0].events = e; - n = 1; - } else { - p[0].events = e & POLLIN; - p[1].fd = bus->output_fd; - p[1].events = e & POLLOUT; - n = 2; - } - - r = ppoll(p, n, m == (uint64_t) -1 ? NULL : timespec_store(&ts, m), NULL); - if (r < 0) - return -errno; - - return r > 0 ? 1 : 0; -} - -_public_ int sd_bus_wait(sd_bus *bus, uint64_t timeout_usec) { - - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (bus->state == BUS_CLOSING) - return 0; - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (bus->rqueue_size > 0) - return 0; - - return bus_poll(bus, false, timeout_usec); -} - -_public_ int sd_bus_flush(sd_bus *bus) { - int r; - - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (bus->state == BUS_CLOSING) - return 0; - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - r = bus_ensure_running(bus); - if (r < 0) - return r; - - if (bus->wqueue_size <= 0) - return 0; - - for (;;) { - r = dispatch_wqueue(bus); - if (r < 0) { - if (IN_SET(r, -ENOTCONN, -ECONNRESET, -EPIPE, -ESHUTDOWN)) { - bus_enter_closing(bus); - return -ECONNRESET; - } - - return r; - } - - if (bus->wqueue_size <= 0) - return 0; - - r = bus_poll(bus, false, (uint64_t) -1); - if (r < 0) - return r; - } -} - -_public_ int sd_bus_add_filter( - sd_bus *bus, - sd_bus_slot **slot, - sd_bus_message_handler_t callback, - void *userdata) { - - sd_bus_slot *s; - - assert_return(bus, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - s = bus_slot_allocate(bus, !slot, BUS_FILTER_CALLBACK, sizeof(struct filter_callback), userdata); - if (!s) - return -ENOMEM; - - s->filter_callback.callback = callback; - - bus->filter_callbacks_modified = true; - LIST_PREPEND(callbacks, bus->filter_callbacks, &s->filter_callback); - - if (slot) - *slot = s; - - return 0; -} - -_public_ int sd_bus_add_match( - sd_bus *bus, - sd_bus_slot **slot, - const char *match, - sd_bus_message_handler_t callback, - void *userdata) { - - struct bus_match_component *components = NULL; - unsigned n_components = 0; - sd_bus_slot *s = NULL; - int r = 0; - - assert_return(bus, -EINVAL); - assert_return(match, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - r = bus_match_parse(match, &components, &n_components); - if (r < 0) - goto finish; - - s = bus_slot_allocate(bus, !slot, BUS_MATCH_CALLBACK, sizeof(struct match_callback), userdata); - if (!s) { - r = -ENOMEM; - goto finish; - } - - s->match_callback.callback = callback; - s->match_callback.cookie = ++bus->match_cookie; - - if (bus->bus_client) { - enum bus_match_scope scope; - - scope = bus_match_get_scope(components, n_components); - - /* Do not install server-side matches for matches - * against the local service, interface or bus - * path. */ - if (scope != BUS_MATCH_LOCAL) { - - if (!bus->is_kernel) { - /* When this is not a kernel transport, we - * store the original match string, so that we - * can use it to remove the match again */ - - s->match_callback.match_string = strdup(match); - if (!s->match_callback.match_string) { - r = -ENOMEM; - goto finish; - } - } - - r = bus_add_match_internal(bus, s->match_callback.match_string, components, n_components, s->match_callback.cookie); - if (r < 0) - goto finish; - - s->match_added = true; - } - } - - bus->match_callbacks_modified = true; - r = bus_match_add(&bus->match_callbacks, components, n_components, &s->match_callback); - if (r < 0) - goto finish; - - if (slot) - *slot = s; - s = NULL; - -finish: - bus_match_parse_free(components, n_components); - sd_bus_slot_unref(s); - - return r; -} - -int bus_remove_match_by_string( - sd_bus *bus, - const char *match, - sd_bus_message_handler_t callback, - void *userdata) { - - struct bus_match_component *components = NULL; - unsigned n_components = 0; - struct match_callback *c; - int r = 0; - - assert_return(bus, -EINVAL); - assert_return(match, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - r = bus_match_parse(match, &components, &n_components); - if (r < 0) - goto finish; - - r = bus_match_find(&bus->match_callbacks, components, n_components, NULL, NULL, &c); - if (r <= 0) - goto finish; - - sd_bus_slot_unref(container_of(c, sd_bus_slot, match_callback)); - -finish: - bus_match_parse_free(components, n_components); - - return r; -} - -bool bus_pid_changed(sd_bus *bus) { - assert(bus); - - /* We don't support people creating a bus connection and - * keeping it around over a fork(). Let's complain. */ - - return bus->original_pid != getpid(); -} - -static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - sd_bus *bus = userdata; - int r; - - assert(bus); - - r = sd_bus_process(bus, NULL); - if (r < 0) - return r; - - return 1; -} - -static int time_callback(sd_event_source *s, uint64_t usec, void *userdata) { - sd_bus *bus = userdata; - int r; - - assert(bus); - - r = sd_bus_process(bus, NULL); - if (r < 0) - return r; - - return 1; -} - -static int prepare_callback(sd_event_source *s, void *userdata) { - sd_bus *bus = userdata; - int r, e; - usec_t until; - - assert(s); - assert(bus); - - e = sd_bus_get_events(bus); - if (e < 0) - return e; - - if (bus->output_fd != bus->input_fd) { - - r = sd_event_source_set_io_events(bus->input_io_event_source, e & POLLIN); - if (r < 0) - return r; - - r = sd_event_source_set_io_events(bus->output_io_event_source, e & POLLOUT); - if (r < 0) - return r; - } else { - r = sd_event_source_set_io_events(bus->input_io_event_source, e); - if (r < 0) - return r; - } - - r = sd_bus_get_timeout(bus, &until); - if (r < 0) - return r; - if (r > 0) { - int j; - - j = sd_event_source_set_time(bus->time_event_source, until); - if (j < 0) - return j; - } - - r = sd_event_source_set_enabled(bus->time_event_source, r > 0); - if (r < 0) - return r; - - return 1; -} - -static int quit_callback(sd_event_source *event, void *userdata) { - sd_bus *bus = userdata; - - assert(event); - - sd_bus_flush(bus); - sd_bus_close(bus); - - return 1; -} - -static int attach_io_events(sd_bus *bus) { - int r; - - assert(bus); - - if (bus->input_fd < 0) - return 0; - - if (!bus->event) - return 0; - - if (!bus->input_io_event_source) { - r = sd_event_add_io(bus->event, &bus->input_io_event_source, bus->input_fd, 0, io_callback, bus); - if (r < 0) - return r; - - r = sd_event_source_set_prepare(bus->input_io_event_source, prepare_callback); - if (r < 0) - return r; - - r = sd_event_source_set_priority(bus->input_io_event_source, bus->event_priority); - if (r < 0) - return r; - - r = sd_event_source_set_description(bus->input_io_event_source, "bus-input"); - } else - r = sd_event_source_set_io_fd(bus->input_io_event_source, bus->input_fd); - - if (r < 0) - return r; - - if (bus->output_fd != bus->input_fd) { - assert(bus->output_fd >= 0); - - if (!bus->output_io_event_source) { - r = sd_event_add_io(bus->event, &bus->output_io_event_source, bus->output_fd, 0, io_callback, bus); - if (r < 0) - return r; - - r = sd_event_source_set_priority(bus->output_io_event_source, bus->event_priority); - if (r < 0) - return r; - - r = sd_event_source_set_description(bus->input_io_event_source, "bus-output"); - } else - r = sd_event_source_set_io_fd(bus->output_io_event_source, bus->output_fd); - - if (r < 0) - return r; - } - - return 0; -} - -static void detach_io_events(sd_bus *bus) { - assert(bus); - - if (bus->input_io_event_source) { - sd_event_source_set_enabled(bus->input_io_event_source, SD_EVENT_OFF); - bus->input_io_event_source = sd_event_source_unref(bus->input_io_event_source); - } - - if (bus->output_io_event_source) { - sd_event_source_set_enabled(bus->output_io_event_source, SD_EVENT_OFF); - bus->output_io_event_source = sd_event_source_unref(bus->output_io_event_source); - } -} - -_public_ int sd_bus_attach_event(sd_bus *bus, sd_event *event, int priority) { - int r; - - assert_return(bus, -EINVAL); - assert_return(!bus->event, -EBUSY); - - assert(!bus->input_io_event_source); - assert(!bus->output_io_event_source); - assert(!bus->time_event_source); - - if (event) - bus->event = sd_event_ref(event); - else { - r = sd_event_default(&bus->event); - if (r < 0) - return r; - } - - bus->event_priority = priority; - - r = sd_event_add_time(bus->event, &bus->time_event_source, CLOCK_MONOTONIC, 0, 0, time_callback, bus); - if (r < 0) - goto fail; - - r = sd_event_source_set_priority(bus->time_event_source, priority); - if (r < 0) - goto fail; - - r = sd_event_source_set_description(bus->time_event_source, "bus-time"); - if (r < 0) - goto fail; - - r = sd_event_add_exit(bus->event, &bus->quit_event_source, quit_callback, bus); - if (r < 0) - goto fail; - - r = sd_event_source_set_description(bus->quit_event_source, "bus-exit"); - if (r < 0) - goto fail; - - r = attach_io_events(bus); - if (r < 0) - goto fail; - - return 0; - -fail: - sd_bus_detach_event(bus); - return r; -} - -_public_ int sd_bus_detach_event(sd_bus *bus) { - assert_return(bus, -EINVAL); - - if (!bus->event) - return 0; - - detach_io_events(bus); - - if (bus->time_event_source) { - sd_event_source_set_enabled(bus->time_event_source, SD_EVENT_OFF); - bus->time_event_source = sd_event_source_unref(bus->time_event_source); - } - - if (bus->quit_event_source) { - sd_event_source_set_enabled(bus->quit_event_source, SD_EVENT_OFF); - bus->quit_event_source = sd_event_source_unref(bus->quit_event_source); - } - - bus->event = sd_event_unref(bus->event); - return 1; -} - -_public_ sd_event* sd_bus_get_event(sd_bus *bus) { - assert_return(bus, NULL); - - return bus->event; -} - -_public_ sd_bus_message* sd_bus_get_current_message(sd_bus *bus) { - assert_return(bus, NULL); - - return bus->current_message; -} - -_public_ sd_bus_slot* sd_bus_get_current_slot(sd_bus *bus) { - assert_return(bus, NULL); - - return bus->current_slot; -} - -_public_ sd_bus_message_handler_t sd_bus_get_current_handler(sd_bus *bus) { - assert_return(bus, NULL); - - return bus->current_handler; -} - -_public_ void* sd_bus_get_current_userdata(sd_bus *bus) { - assert_return(bus, NULL); - - return bus->current_userdata; -} - -static int bus_default(int (*bus_open)(sd_bus **), sd_bus **default_bus, sd_bus **ret) { - sd_bus *b = NULL; - int r; - - assert(bus_open); - assert(default_bus); - - if (!ret) - return !!*default_bus; - - if (*default_bus) { - *ret = sd_bus_ref(*default_bus); - return 0; - } - - r = bus_open(&b); - if (r < 0) - return r; - - b->default_bus_ptr = default_bus; - b->tid = gettid(); - *default_bus = b; - - *ret = b; - return 1; -} - -_public_ int sd_bus_default_system(sd_bus **ret) { - return bus_default(sd_bus_open_system, &default_system_bus, ret); -} - - -_public_ int sd_bus_default_user(sd_bus **ret) { - return bus_default(sd_bus_open_user, &default_user_bus, ret); -} - -_public_ int sd_bus_default(sd_bus **ret) { - - const char *e; - - /* Let's try our best to reuse another cached connection. If - * the starter bus type is set, connect via our normal - * connection logic, ignoring $DBUS_STARTER_ADDRESS, so that - * we can share the connection with the user/system default - * bus. */ - - e = secure_getenv("DBUS_STARTER_BUS_TYPE"); - if (e) { - if (streq(e, "system")) - return sd_bus_default_system(ret); - else if (STR_IN_SET(e, "user", "session")) - return sd_bus_default_user(ret); - } - - /* No type is specified, so we have not other option than to - * use the starter address if it is set. */ - - e = secure_getenv("DBUS_STARTER_ADDRESS"); - if (e) { - - return bus_default(sd_bus_open, &default_starter_bus, ret); - } - - /* Finally, if nothing is set use the cached connection for - * the right scope */ - - if (cg_pid_get_owner_uid(0, NULL) >= 0) - return sd_bus_default_user(ret); - else - return sd_bus_default_system(ret); -} - -_public_ int sd_bus_get_tid(sd_bus *b, pid_t *tid) { - assert_return(b, -EINVAL); - assert_return(tid, -EINVAL); - assert_return(!bus_pid_changed(b), -ECHILD); - - if (b->tid != 0) { - *tid = b->tid; - return 0; - } - - if (b->event) - return sd_event_get_tid(b->event, tid); - - return -ENXIO; -} - -_public_ int sd_bus_path_encode(const char *prefix, const char *external_id, char **ret_path) { - _cleanup_free_ char *e = NULL; - char *ret; - - assert_return(object_path_is_valid(prefix), -EINVAL); - assert_return(external_id, -EINVAL); - assert_return(ret_path, -EINVAL); - - e = bus_label_escape(external_id); - if (!e) - return -ENOMEM; - - ret = strjoin(prefix, "/", e, NULL); - if (!ret) - return -ENOMEM; - - *ret_path = ret; - return 0; -} - -_public_ int sd_bus_path_decode(const char *path, const char *prefix, char **external_id) { - const char *e; - char *ret; - - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(object_path_is_valid(prefix), -EINVAL); - assert_return(external_id, -EINVAL); - - e = object_path_startswith(path, prefix); - if (!e) { - *external_id = NULL; - return 0; - } - - ret = bus_label_unescape(e); - if (!ret) - return -ENOMEM; - - *external_id = ret; - return 1; -} - -_public_ int sd_bus_path_encode_many(char **out, const char *path_template, ...) { - _cleanup_strv_free_ char **labels = NULL; - char *path, *path_pos, **label_pos; - const char *sep, *template_pos; - size_t path_length; - va_list list; - int r; - - assert_return(out, -EINVAL); - assert_return(path_template, -EINVAL); - - path_length = strlen(path_template); - - va_start(list, path_template); - for (sep = strchr(path_template, '%'); sep; sep = strchr(sep + 1, '%')) { - const char *arg; - char *label; - - arg = va_arg(list, const char *); - if (!arg) { - va_end(list); - return -EINVAL; - } - - label = bus_label_escape(arg); - if (!label) { - va_end(list); - return -ENOMEM; - } - - r = strv_consume(&labels, label); - if (r < 0) { - va_end(list); - return r; - } - - /* add label length, but account for the format character */ - path_length += strlen(label) - 1; - } - va_end(list); - - path = malloc(path_length + 1); - if (!path) - return -ENOMEM; - - path_pos = path; - label_pos = labels; - - for (template_pos = path_template; *template_pos; ) { - sep = strchrnul(template_pos, '%'); - path_pos = mempcpy(path_pos, template_pos, sep - template_pos); - if (!*sep) - break; - - path_pos = stpcpy(path_pos, *label_pos++); - template_pos = sep + 1; - } - - *path_pos = 0; - *out = path; - return 0; -} - -_public_ int sd_bus_path_decode_many(const char *path, const char *path_template, ...) { - _cleanup_strv_free_ char **labels = NULL; - const char *template_pos, *path_pos; - char **label_pos; - va_list list; - int r; - - /* - * This decodes an object-path based on a template argument. The - * template consists of a verbatim path, optionally including special - * directives: - * - * - Each occurrence of '%' in the template matches an arbitrary - * substring of a label in the given path. At most one such - * directive is allowed per label. For each such directive, the - * caller must provide an output parameter (char **) via va_arg. If - * NULL is passed, the given label is verified, but not returned. - * For each matched label, the *decoded* label is stored in the - * passed output argument, and the caller is responsible to free - * it. Note that the output arguments are only modified if the - * actualy path matched the template. Otherwise, they're left - * untouched. - * - * This function returns <0 on error, 0 if the path does not match the - * template, 1 if it matched. - */ - - assert_return(path, -EINVAL); - assert_return(path_template, -EINVAL); - - path_pos = path; - - for (template_pos = path_template; *template_pos; ) { - const char *sep; - size_t length; - char *label; - - /* verify everything until the next '%' matches verbatim */ - sep = strchrnul(template_pos, '%'); - length = sep - template_pos; - if (strncmp(path_pos, template_pos, length)) - return 0; - - path_pos += length; - template_pos += length; - - if (!*template_pos) - break; - - /* We found the next '%' character. Everything up until here - * matched. We now skip ahead to the end of this label and make - * sure it matches the tail of the label in the path. Then we - * decode the string in-between and save it for later use. */ - - ++template_pos; /* skip over '%' */ - - sep = strchrnul(template_pos, '/'); - length = sep - template_pos; /* length of suffix to match verbatim */ - - /* verify the suffixes match */ - sep = strchrnul(path_pos, '/'); - if (sep - path_pos < (ssize_t)length || - strncmp(sep - length, template_pos, length)) - return 0; - - template_pos += length; /* skip over matched label */ - length = sep - path_pos - length; /* length of sub-label to decode */ - - /* store unescaped label for later use */ - label = bus_label_unescape_n(path_pos, length); - if (!label) - return -ENOMEM; - - r = strv_consume(&labels, label); - if (r < 0) - return r; - - path_pos = sep; /* skip decoded label and suffix */ - } - - /* end of template must match end of path */ - if (*path_pos) - return 0; - - /* copy the labels over to the caller */ - va_start(list, path_template); - for (label_pos = labels; label_pos && *label_pos; ++label_pos) { - char **arg; - - arg = va_arg(list, char **); - if (arg) - *arg = *label_pos; - else - free(*label_pos); - } - va_end(list); - - free(labels); - labels = NULL; - return 1; -} - -_public_ int sd_bus_try_close(sd_bus *bus) { - int r; - - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!bus->is_kernel) - return -EOPNOTSUPP; - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (bus->rqueue_size > 0) - return -EBUSY; - - if (bus->wqueue_size > 0) - return -EBUSY; - - r = bus_kernel_try_close(bus); - if (r < 0) - return r; - - sd_bus_close(bus); - return 0; -} - -_public_ int sd_bus_get_description(sd_bus *bus, const char **description) { - assert_return(bus, -EINVAL); - assert_return(description, -EINVAL); - assert_return(bus->description, -ENXIO); - assert_return(!bus_pid_changed(bus), -ECHILD); - - *description = bus->description; - return 0; -} - -int bus_get_root_path(sd_bus *bus) { - int r; - - if (bus->cgroup_root) - return 0; - - r = cg_get_root_path(&bus->cgroup_root); - if (r == -ENOENT) { - bus->cgroup_root = strdup("/"); - if (!bus->cgroup_root) - return -ENOMEM; - - r = 0; - } - - return r; -} - -_public_ int sd_bus_get_scope(sd_bus *bus, const char **scope) { - int r; - - assert_return(bus, -EINVAL); - assert_return(scope, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (bus->is_kernel) { - _cleanup_free_ char *n = NULL; - const char *dash; - - r = bus_kernel_get_bus_name(bus, &n); - if (r < 0) - return r; - - if (streq(n, "0-system")) { - *scope = "system"; - return 0; - } - - dash = strchr(n, '-'); - if (streq_ptr(dash, "-user")) { - *scope = "user"; - return 0; - } - } - - if (bus->is_user) { - *scope = "user"; - return 0; - } - - if (bus->is_system) { - *scope = "system"; - return 0; - } - - return -ENODATA; -} - -_public_ int sd_bus_get_address(sd_bus *bus, const char **address) { - - assert_return(bus, -EINVAL); - assert_return(address, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (bus->address) { - *address = bus->address; - return 0; - } - - return -ENODATA; -} - -_public_ int sd_bus_get_creds_mask(sd_bus *bus, uint64_t *mask) { - assert_return(bus, -EINVAL); - assert_return(mask, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - *mask = bus->creds_mask; - return 0; -} - -_public_ int sd_bus_is_bus_client(sd_bus *bus) { - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - return bus->bus_client; -} - -_public_ int sd_bus_is_server(sd_bus *bus) { - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - return bus->is_server; -} - -_public_ int sd_bus_is_anonymous(sd_bus *bus) { - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - return bus->anonymous_auth; -} - -_public_ int sd_bus_is_trusted(sd_bus *bus) { - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - return bus->trusted; -} - -_public_ int sd_bus_is_monitor(sd_bus *bus) { - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - return !!(bus->hello_flags & KDBUS_HELLO_MONITOR); -} - -static void flush_close(sd_bus *bus) { - if (!bus) - return; - - /* Flushes and closes the specified bus. We take a ref before, - * to ensure the flushing does not cause the bus to be - * unreferenced. */ - - sd_bus_flush_close_unref(sd_bus_ref(bus)); -} - -_public_ void sd_bus_default_flush_close(void) { - flush_close(default_starter_bus); - flush_close(default_user_bus); - flush_close(default_system_bus); -} diff --git a/src/libsystemd/sd-bus/test-bus-benchmark.c b/src/libsystemd/sd-bus/test-bus-benchmark.c deleted file mode 100644 index 56ac2ab3dd..0000000000 --- a/src/libsystemd/sd-bus/test-bus-benchmark.c +++ /dev/null @@ -1,371 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -#include "alloc-util.h" -#include "bus-internal.h" -#include "bus-kernel.h" -#include "bus-util.h" -#include "def.h" -#include "fd-util.h" -#include "time-util.h" -#include "util.h" - -#define MAX_SIZE (2*1024*1024) - -static usec_t arg_loop_usec = 100 * USEC_PER_MSEC; - -typedef enum Type { - TYPE_KDBUS, - TYPE_LEGACY, - TYPE_DIRECT, -} Type; - -static void server(sd_bus *b, size_t *result) { - int r; - - for (;;) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - r = sd_bus_process(b, &m); - assert_se(r >= 0); - - if (r == 0) - assert_se(sd_bus_wait(b, USEC_INFINITY) >= 0); - if (!m) - continue; - - if (sd_bus_message_is_method_call(m, "benchmark.server", "Ping")) - assert_se(sd_bus_reply_method_return(m, NULL) >= 0); - else if (sd_bus_message_is_method_call(m, "benchmark.server", "Work")) { - const void *p; - size_t sz; - - /* Make sure the mmap is mapped */ - assert_se(sd_bus_message_read_array(m, 'y', &p, &sz) > 0); - - r = sd_bus_reply_method_return(m, NULL); - assert_se(r >= 0); - } else if (sd_bus_message_is_method_call(m, "benchmark.server", "Exit")) { - uint64_t res; - assert_se(sd_bus_message_read(m, "t", &res) > 0); - - *result = res; - return; - - } else if (!sd_bus_message_is_signal(m, NULL, NULL)) - assert_not_reached("Unknown method"); - } -} - -static void transaction(sd_bus *b, size_t sz, const char *server_name) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - uint8_t *p; - - assert_se(sd_bus_message_new_method_call(b, &m, server_name, "/", "benchmark.server", "Work") >= 0); - assert_se(sd_bus_message_append_array_space(m, 'y', sz, (void**) &p) >= 0); - - memset(p, 0x80, sz); - - assert_se(sd_bus_call(b, m, 0, NULL, &reply) >= 0); -} - -static void client_bisect(const char *address, const char *server_name) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *x = NULL; - size_t lsize, rsize, csize; - sd_bus *b; - int r; - - r = sd_bus_new(&b); - assert_se(r >= 0); - - r = sd_bus_set_address(b, address); - assert_se(r >= 0); - - r = sd_bus_start(b); - assert_se(r >= 0); - - r = sd_bus_call_method(b, server_name, "/", "benchmark.server", "Ping", NULL, NULL, NULL); - assert_se(r >= 0); - - lsize = 1; - rsize = MAX_SIZE; - - printf("SIZE\tCOPY\tMEMFD\n"); - - for (;;) { - usec_t t; - unsigned n_copying, n_memfd; - - csize = (lsize + rsize) / 2; - - if (csize <= lsize) - break; - - if (csize <= 0) - break; - - printf("%zu\t", csize); - - b->use_memfd = 0; - - t = now(CLOCK_MONOTONIC); - for (n_copying = 0;; n_copying++) { - transaction(b, csize, server_name); - if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) - break; - } - printf("%u\t", (unsigned) ((n_copying * USEC_PER_SEC) / arg_loop_usec)); - - b->use_memfd = -1; - - t = now(CLOCK_MONOTONIC); - for (n_memfd = 0;; n_memfd++) { - transaction(b, csize, server_name); - if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) - break; - } - printf("%u\n", (unsigned) ((n_memfd * USEC_PER_SEC) / arg_loop_usec)); - - if (n_copying == n_memfd) - break; - - if (n_copying > n_memfd) - lsize = csize; - else - rsize = csize; - } - - b->use_memfd = 1; - assert_se(sd_bus_message_new_method_call(b, &x, server_name, "/", "benchmark.server", "Exit") >= 0); - assert_se(sd_bus_message_append(x, "t", csize) >= 0); - assert_se(sd_bus_send(b, x, NULL) >= 0); - - sd_bus_unref(b); -} - -static void client_chart(Type type, const char *address, const char *server_name, int fd) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *x = NULL; - size_t csize; - sd_bus *b; - int r; - - r = sd_bus_new(&b); - assert_se(r >= 0); - - if (type == TYPE_DIRECT) { - r = sd_bus_set_fd(b, fd, fd); - assert_se(r >= 0); - } else { - r = sd_bus_set_address(b, address); - assert_se(r >= 0); - - r = sd_bus_set_bus_client(b, true); - assert_se(r >= 0); - } - - r = sd_bus_start(b); - assert_se(r >= 0); - - r = sd_bus_call_method(b, server_name, "/", "benchmark.server", "Ping", NULL, NULL, NULL); - assert_se(r >= 0); - - switch (type) { - case TYPE_KDBUS: - printf("SIZE\tCOPY\tMEMFD\n"); - break; - case TYPE_LEGACY: - printf("SIZE\tLEGACY\n"); - break; - case TYPE_DIRECT: - printf("SIZE\tDIRECT\n"); - break; - } - - for (csize = 1; csize <= MAX_SIZE; csize *= 2) { - usec_t t; - unsigned n_copying, n_memfd; - - printf("%zu\t", csize); - - if (type == TYPE_KDBUS) { - b->use_memfd = 0; - - t = now(CLOCK_MONOTONIC); - for (n_copying = 0;; n_copying++) { - transaction(b, csize, server_name); - if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) - break; - } - - printf("%u\t", (unsigned) ((n_copying * USEC_PER_SEC) / arg_loop_usec)); - - b->use_memfd = -1; - } - - t = now(CLOCK_MONOTONIC); - for (n_memfd = 0;; n_memfd++) { - transaction(b, csize, server_name); - if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) - break; - } - - printf("%u\n", (unsigned) ((n_memfd * USEC_PER_SEC) / arg_loop_usec)); - } - - b->use_memfd = 1; - assert_se(sd_bus_message_new_method_call(b, &x, server_name, "/", "benchmark.server", "Exit") >= 0); - assert_se(sd_bus_message_append(x, "t", csize) >= 0); - assert_se(sd_bus_send(b, x, NULL) >= 0); - - sd_bus_unref(b); -} - -int main(int argc, char *argv[]) { - enum { - MODE_BISECT, - MODE_CHART, - } mode = MODE_BISECT; - Type type = TYPE_KDBUS; - int i, pair[2] = { -1, -1 }; - _cleanup_free_ char *name = NULL, *bus_name = NULL, *address = NULL, *server_name = NULL; - _cleanup_close_ int bus_ref = -1; - const char *unique; - cpu_set_t cpuset; - size_t result; - sd_bus *b; - pid_t pid; - int r; - - for (i = 1; i < argc; i++) { - if (streq(argv[i], "chart")) { - mode = MODE_CHART; - continue; - } else if (streq(argv[i], "legacy")) { - type = TYPE_LEGACY; - continue; - } else if (streq(argv[i], "direct")) { - type = TYPE_DIRECT; - continue; - } - - assert_se(parse_sec(argv[i], &arg_loop_usec) >= 0); - } - - assert_se(!MODE_BISECT || TYPE_KDBUS); - - assert_se(arg_loop_usec > 0); - - if (type == TYPE_KDBUS) { - assert_se(asprintf(&name, "deine-mutter-%u", (unsigned) getpid()) >= 0); - - bus_ref = bus_kernel_create_bus(name, false, &bus_name); - if (bus_ref == -ENOENT) - exit(EXIT_TEST_SKIP); - - assert_se(bus_ref >= 0); - - address = strappend("kernel:path=", bus_name); - assert_se(address); - } else if (type == TYPE_LEGACY) { - const char *e; - - e = secure_getenv("DBUS_SESSION_BUS_ADDRESS"); - assert_se(e); - - address = strdup(e); - assert_se(address); - } - - r = sd_bus_new(&b); - assert_se(r >= 0); - - if (type == TYPE_DIRECT) { - assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) >= 0); - - r = sd_bus_set_fd(b, pair[0], pair[0]); - assert_se(r >= 0); - - r = sd_bus_set_server(b, true, SD_ID128_NULL); - assert_se(r >= 0); - } else { - r = sd_bus_set_address(b, address); - assert_se(r >= 0); - - r = sd_bus_set_bus_client(b, true); - assert_se(r >= 0); - } - - r = sd_bus_start(b); - assert_se(r >= 0); - - if (type != TYPE_DIRECT) { - r = sd_bus_get_unique_name(b, &unique); - assert_se(r >= 0); - - server_name = strdup(unique); - assert_se(server_name); - } - - sync(); - setpriority(PRIO_PROCESS, 0, -19); - - pid = fork(); - assert_se(pid >= 0); - - if (pid == 0) { - CPU_ZERO(&cpuset); - CPU_SET(0, &cpuset); - pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); - - safe_close(bus_ref); - sd_bus_unref(b); - - switch (mode) { - case MODE_BISECT: - client_bisect(address, server_name); - break; - - case MODE_CHART: - client_chart(type, address, server_name, pair[1]); - break; - } - - _exit(0); - } - - CPU_ZERO(&cpuset); - CPU_SET(1, &cpuset); - pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); - - server(b, &result); - - if (mode == MODE_BISECT) - printf("Copying/memfd are equally fast at %zu bytes\n", result); - - assert_se(waitpid(pid, NULL, 0) == pid); - - safe_close(pair[1]); - sd_bus_unref(b); - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-chat.c b/src/libsystemd/sd-bus/test-bus-chat.c deleted file mode 100644 index 048c0d19e2..0000000000 --- a/src/libsystemd/sd-bus/test-bus-chat.c +++ /dev/null @@ -1,560 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-internal.h" -#include "bus-match.h" -#include "bus-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "log.h" -#include "macro.h" -#include "util.h" - -static int match_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - log_info("Match triggered! interface=%s member=%s", strna(sd_bus_message_get_interface(m)), strna(sd_bus_message_get_member(m))); - return 0; -} - -static int object_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - int r; - - if (sd_bus_message_is_method_error(m, NULL)) - return 0; - - if (sd_bus_message_is_method_call(m, "org.object.test", "Foobar")) { - log_info("Invoked Foobar() on %s", sd_bus_message_get_path(m)); - - r = sd_bus_reply_method_return(m, NULL); - if (r < 0) - return log_error_errno(r, "Failed to send reply: %m"); - - return 1; - } - - return 0; -} - -static int server_init(sd_bus **_bus) { - sd_bus *bus = NULL; - sd_id128_t id; - int r; - const char *unique; - - assert_se(_bus); - - r = sd_bus_open_user(&bus); - if (r < 0) { - log_error_errno(r, "Failed to connect to user bus: %m"); - goto fail; - } - - r = sd_bus_get_bus_id(bus, &id); - if (r < 0) { - log_error_errno(r, "Failed to get server ID: %m"); - goto fail; - } - - r = sd_bus_get_unique_name(bus, &unique); - if (r < 0) { - log_error_errno(r, "Failed to get unique name: %m"); - goto fail; - } - - log_info("Peer ID is " SD_ID128_FORMAT_STR ".", SD_ID128_FORMAT_VAL(id)); - log_info("Unique ID: %s", unique); - log_info("Can send file handles: %i", sd_bus_can_send(bus, 'h')); - - r = sd_bus_request_name(bus, "org.freedesktop.systemd.test", 0); - if (r < 0) { - log_error_errno(r, "Failed to acquire name: %m"); - goto fail; - } - - r = sd_bus_add_fallback(bus, NULL, "/foo/bar", object_callback, NULL); - if (r < 0) { - log_error_errno(r, "Failed to add object: %m"); - goto fail; - } - - r = sd_bus_add_match(bus, NULL, "type='signal',interface='foo.bar',member='Notify'", match_callback, NULL); - if (r < 0) { - log_error_errno(r, "Failed to add match: %m"); - goto fail; - } - - r = sd_bus_add_match(bus, NULL, "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged'", match_callback, NULL); - if (r < 0) { - log_error_errno(r, "Failed to add match: %m"); - goto fail; - } - - bus_match_dump(&bus->match_callbacks, 0); - - *_bus = bus; - return 0; - -fail: - sd_bus_unref(bus); - return r; -} - -static int server(sd_bus *bus) { - int r; - bool client1_gone = false, client2_gone = false; - - while (!client1_gone || !client2_gone) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - pid_t pid = 0; - const char *label = NULL; - - r = sd_bus_process(bus, &m); - if (r < 0) { - log_error_errno(r, "Failed to process requests: %m"); - goto fail; - } - - if (r == 0) { - r = sd_bus_wait(bus, (uint64_t) -1); - if (r < 0) { - log_error_errno(r, "Failed to wait: %m"); - goto fail; - } - - continue; - } - - if (!m) - continue; - - sd_bus_creds_get_pid(sd_bus_message_get_creds(m), &pid); - sd_bus_creds_get_selinux_context(sd_bus_message_get_creds(m), &label); - log_info("Got message! member=%s pid="PID_FMT" label=%s", - strna(sd_bus_message_get_member(m)), - pid, - strna(label)); - /* bus_message_dump(m); */ - /* sd_bus_message_rewind(m, true); */ - - if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "LowerCase")) { - const char *hello; - _cleanup_free_ char *lowercase = NULL; - - r = sd_bus_message_read(m, "s", &hello); - if (r < 0) { - log_error_errno(r, "Failed to get parameter: %m"); - goto fail; - } - - lowercase = strdup(hello); - if (!lowercase) { - r = log_oom(); - goto fail; - } - - ascii_strlower(lowercase); - - r = sd_bus_reply_method_return(m, "s", lowercase); - if (r < 0) { - log_error_errno(r, "Failed to send reply: %m"); - goto fail; - } - } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "ExitClient1")) { - - r = sd_bus_reply_method_return(m, NULL); - if (r < 0) { - log_error_errno(r, "Failed to send reply: %m"); - goto fail; - } - - client1_gone = true; - } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "ExitClient2")) { - - r = sd_bus_reply_method_return(m, NULL); - if (r < 0) { - log_error_errno(r, "Failed to send reply: %m"); - goto fail; - } - - client2_gone = true; - } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "Slow")) { - - sleep(1); - - r = sd_bus_reply_method_return(m, NULL); - if (r < 0) { - log_error_errno(r, "Failed to send reply: %m"); - goto fail; - } - - } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "FileDescriptor")) { - int fd; - static const char x = 'X'; - - r = sd_bus_message_read(m, "h", &fd); - if (r < 0) { - log_error_errno(r, "Failed to get parameter: %m"); - goto fail; - } - - log_info("Received fd=%d", fd); - - if (write(fd, &x, 1) < 0) { - log_error_errno(errno, "Failed to write to fd: %m"); - safe_close(fd); - goto fail; - } - - r = sd_bus_reply_method_return(m, NULL); - if (r < 0) { - log_error_errno(r, "Failed to send reply: %m"); - goto fail; - } - - } else if (sd_bus_message_is_method_call(m, NULL, NULL)) { - - r = sd_bus_reply_method_error( - m, - &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_UNKNOWN_METHOD, "Unknown method.")); - if (r < 0) { - log_error_errno(r, "Failed to send reply: %m"); - goto fail; - } - } - } - - r = 0; - -fail: - if (bus) { - sd_bus_flush(bus); - sd_bus_unref(bus); - } - - return r; -} - -static void* client1(void*p) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *hello; - int r; - _cleanup_close_pair_ int pp[2] = { -1, -1 }; - char x; - - r = sd_bus_open_user(&bus); - if (r < 0) { - log_error_errno(r, "Failed to connect to user bus: %m"); - goto finish; - } - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd.test", - "/", - "org.freedesktop.systemd.test", - "LowerCase", - &error, - &reply, - "s", - "HELLO"); - if (r < 0) { - log_error_errno(r, "Failed to issue method call: %m"); - goto finish; - } - - r = sd_bus_message_read(reply, "s", &hello); - if (r < 0) { - log_error_errno(r, "Failed to get string: %m"); - goto finish; - } - - assert_se(streq(hello, "hello")); - - if (pipe2(pp, O_CLOEXEC|O_NONBLOCK) < 0) { - log_error_errno(errno, "Failed to allocate pipe: %m"); - r = -errno; - goto finish; - } - - log_info("Sending fd=%d", pp[1]); - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd.test", - "/", - "org.freedesktop.systemd.test", - "FileDescriptor", - &error, - NULL, - "h", - pp[1]); - if (r < 0) { - log_error_errno(r, "Failed to issue method call: %m"); - goto finish; - } - - errno = 0; - if (read(pp[0], &x, 1) <= 0) { - log_error("Failed to read from pipe: %s", errno ? strerror(errno) : "early read"); - goto finish; - } - - r = 0; - -finish: - if (bus) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *q; - - r = sd_bus_message_new_method_call( - bus, - &q, - "org.freedesktop.systemd.test", - "/", - "org.freedesktop.systemd.test", - "ExitClient1"); - if (r < 0) - log_error_errno(r, "Failed to allocate method call: %m"); - else - sd_bus_send(bus, q, NULL); - - } - - return INT_TO_PTR(r); -} - -static int quit_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - bool *x = userdata; - - log_error("Quit callback: %s", strerror(sd_bus_message_get_errno(m))); - - *x = 1; - return 1; -} - -static void* client2(void*p) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - bool quit = false; - const char *mid; - int r; - - r = sd_bus_open_user(&bus); - if (r < 0) { - log_error_errno(r, "Failed to connect to user bus: %m"); - goto finish; - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd.test", - "/foo/bar/waldo/piep", - "org.object.test", - "Foobar"); - if (r < 0) { - log_error_errno(r, "Failed to allocate method call: %m"); - goto finish; - } - - r = sd_bus_send(bus, m, NULL); - if (r < 0) { - log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); - goto finish; - } - - m = sd_bus_message_unref(m); - - r = sd_bus_message_new_signal( - bus, - &m, - "/foobar", - "foo.bar", - "Notify"); - if (r < 0) { - log_error_errno(r, "Failed to allocate signal: %m"); - goto finish; - } - - r = sd_bus_send(bus, m, NULL); - if (r < 0) { - log_error("Failed to issue signal: %s", bus_error_message(&error, -r)); - goto finish; - } - - m = sd_bus_message_unref(m); - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd.test", - "/", - "org.freedesktop.DBus.Peer", - "GetMachineId"); - if (r < 0) { - log_error_errno(r, "Failed to allocate method call: %m"); - goto finish; - } - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); - goto finish; - } - - r = sd_bus_message_read(reply, "s", &mid); - if (r < 0) { - log_error_errno(r, "Failed to parse machine ID: %m"); - goto finish; - } - - log_info("Machine ID is %s.", mid); - - m = sd_bus_message_unref(m); - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd.test", - "/", - "org.freedesktop.systemd.test", - "Slow"); - if (r < 0) { - log_error_errno(r, "Failed to allocate method call: %m"); - goto finish; - } - - reply = sd_bus_message_unref(reply); - - r = sd_bus_call(bus, m, 200 * USEC_PER_MSEC, &error, &reply); - if (r < 0) - log_info("Failed to issue method call: %s", bus_error_message(&error, -r)); - else - log_info("Slow call succeed."); - - m = sd_bus_message_unref(m); - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd.test", - "/", - "org.freedesktop.systemd.test", - "Slow"); - if (r < 0) { - log_error_errno(r, "Failed to allocate method call: %m"); - goto finish; - } - - r = sd_bus_call_async(bus, NULL, m, quit_callback, &quit, 200 * USEC_PER_MSEC); - if (r < 0) { - log_info("Failed to issue method call: %s", bus_error_message(&error, -r)); - goto finish; - } - - while (!quit) { - r = sd_bus_process(bus, NULL); - if (r < 0) { - log_error_errno(r, "Failed to process requests: %m"); - goto finish; - } - if (r == 0) { - r = sd_bus_wait(bus, (uint64_t) -1); - if (r < 0) { - log_error_errno(r, "Failed to wait: %m"); - goto finish; - } - } - } - - r = 0; - -finish: - if (bus) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *q; - - r = sd_bus_message_new_method_call( - bus, - &q, - "org.freedesktop.systemd.test", - "/", - "org.freedesktop.systemd.test", - "ExitClient2"); - if (r < 0) { - log_error_errno(r, "Failed to allocate method call: %m"); - goto finish; - } - - (void) sd_bus_send(bus, q, NULL); - } - - return INT_TO_PTR(r); -} - -int main(int argc, char *argv[]) { - pthread_t c1, c2; - sd_bus *bus; - void *p; - int q, r; - - r = server_init(&bus); - if (r < 0) { - log_info("Failed to connect to bus, skipping tests."); - return EXIT_TEST_SKIP; - } - - log_info("Initialized..."); - - r = pthread_create(&c1, NULL, client1, bus); - if (r != 0) - return EXIT_FAILURE; - - r = pthread_create(&c2, NULL, client2, bus); - if (r != 0) - return EXIT_FAILURE; - - r = server(bus); - - q = pthread_join(c1, &p); - if (q != 0) - return EXIT_FAILURE; - if (PTR_TO_INT(p) < 0) - return EXIT_FAILURE; - - q = pthread_join(c2, &p); - if (q != 0) - return EXIT_FAILURE; - if (PTR_TO_INT(p) < 0) - return EXIT_FAILURE; - - if (r < 0) - return EXIT_FAILURE; - - return EXIT_SUCCESS; -} diff --git a/src/libsystemd/sd-bus/test-bus-cleanup.c b/src/libsystemd/sd-bus/test-bus-cleanup.c deleted file mode 100644 index 250a5b2908..0000000000 --- a/src/libsystemd/sd-bus/test-bus-cleanup.c +++ /dev/null @@ -1,95 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 Zbigniew Jędrzejewski-Szmek - - 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 "sd-bus.h" - -#include "bus-internal.h" -#include "bus-message.h" -#include "bus-util.h" -#include "refcnt.h" - -static void test_bus_new(void) { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - - assert_se(sd_bus_new(&bus) == 0); - printf("after new: refcount %u\n", REFCNT_GET(bus->n_ref)); -} - -static int test_bus_open(void) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - r = sd_bus_open_system(&bus); - if (r == -ECONNREFUSED || r == -ENOENT) - return r; - - assert_se(r >= 0); - printf("after open: refcount %u\n", REFCNT_GET(bus->n_ref)); - - return 0; -} - -static void test_bus_new_method_call(void) { - sd_bus *bus = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - assert_se(sd_bus_open_system(&bus) >= 0); - - assert_se(sd_bus_message_new_method_call(bus, &m, "a.service.name", "/an/object/path", "an.interface.name", "AMethodName") >= 0); - - printf("after message_new_method_call: refcount %u\n", REFCNT_GET(bus->n_ref)); - - sd_bus_flush_close_unref(bus); - printf("after bus_flush_close_unref: refcount %u\n", m->n_ref); -} - -static void test_bus_new_signal(void) { - sd_bus *bus = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - assert_se(sd_bus_open_system(&bus) >= 0); - - assert_se(sd_bus_message_new_signal(bus, &m, "/an/object/path", "an.interface.name", "Name") >= 0); - - printf("after message_new_signal: refcount %u\n", REFCNT_GET(bus->n_ref)); - - sd_bus_flush_close_unref(bus); - printf("after bus_flush_close_unref: refcount %u\n", m->n_ref); -} - -int main(int argc, char **argv) { - int r; - - log_parse_environment(); - log_open(); - - test_bus_new(); - r = test_bus_open(); - if (r < 0) { - log_info("Failed to connect to bus, skipping tests."); - return EXIT_TEST_SKIP; - } - - test_bus_new_method_call(); - test_bus_new_signal(); - - return EXIT_SUCCESS; -} diff --git a/src/libsystemd/sd-bus/test-bus-creds.c b/src/libsystemd/sd-bus/test-bus-creds.c deleted file mode 100644 index e9ef483bdd..0000000000 --- a/src/libsystemd/sd-bus/test-bus-creds.c +++ /dev/null @@ -1,50 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -#include "bus-dump.h" -#include "bus-util.h" -#include "cgroup-util.h" - -int main(int argc, char *argv[]) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - int r; - - if (cg_unified() == -ENOMEDIUM) { - puts("Skipping test: /sys/fs/cgroup/ not available"); - return EXIT_TEST_SKIP; - } - - r = sd_bus_creds_new_from_pid(&creds, 0, _SD_BUS_CREDS_ALL); - assert_se(r >= 0); - - bus_creds_dump(creds, NULL, true); - - creds = sd_bus_creds_unref(creds); - - r = sd_bus_creds_new_from_pid(&creds, 1, _SD_BUS_CREDS_ALL); - if (r != -EACCES) { - assert_se(r >= 0); - putchar('\n'); - bus_creds_dump(creds, NULL, true); - } - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-error.c b/src/libsystemd/sd-bus/test-bus-error.c deleted file mode 100644 index 66a3874f10..0000000000 --- a/src/libsystemd/sd-bus/test-bus-error.c +++ /dev/null @@ -1,232 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -#include "bus-common-errors.h" -#include "bus-error.h" -#include "bus-util.h" -#include "errno-list.h" - -static void test_error(void) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL, second = SD_BUS_ERROR_NULL; - const sd_bus_error const_error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FILE_EXISTS, "const error"); - const sd_bus_error temporarily_const_error = { - .name = SD_BUS_ERROR_ACCESS_DENIED, - .message = "oh! no", - ._need_free = -1 - }; - - assert_se(!sd_bus_error_is_set(&error)); - assert_se(sd_bus_error_set(&error, SD_BUS_ERROR_NOT_SUPPORTED, "xxx") == -EOPNOTSUPP); - assert_se(streq(error.name, SD_BUS_ERROR_NOT_SUPPORTED)); - assert_se(streq(error.message, "xxx")); - assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_NOT_SUPPORTED)); - assert_se(sd_bus_error_get_errno(&error) == EOPNOTSUPP); - assert_se(sd_bus_error_is_set(&error)); - sd_bus_error_free(&error); - - /* Check with no error */ - assert_se(!sd_bus_error_is_set(&error)); - assert_se(sd_bus_error_setf(&error, NULL, "yyy %i", -1) == 0); - assert_se(error.name == NULL); - assert_se(error.message == NULL); - assert_se(!sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND)); - assert_se(sd_bus_error_get_errno(&error) == 0); - assert_se(!sd_bus_error_is_set(&error)); - - assert_se(sd_bus_error_setf(&error, SD_BUS_ERROR_FILE_NOT_FOUND, "yyy %i", -1) == -ENOENT); - assert_se(streq(error.name, SD_BUS_ERROR_FILE_NOT_FOUND)); - assert_se(streq(error.message, "yyy -1")); - assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND)); - assert_se(sd_bus_error_get_errno(&error) == ENOENT); - assert_se(sd_bus_error_is_set(&error)); - - assert_se(!sd_bus_error_is_set(&second)); - assert_se(second._need_free == 0); - assert_se(error._need_free > 0); - assert_se(sd_bus_error_copy(&second, &error) == -ENOENT); - assert_se(second._need_free > 0); - assert_se(streq(error.name, second.name)); - assert_se(streq(error.message, second.message)); - assert_se(sd_bus_error_get_errno(&second) == ENOENT); - assert_se(sd_bus_error_has_name(&second, SD_BUS_ERROR_FILE_NOT_FOUND)); - assert_se(sd_bus_error_is_set(&second)); - - sd_bus_error_free(&error); - sd_bus_error_free(&second); - - assert_se(!sd_bus_error_is_set(&second)); - assert_se(const_error._need_free == 0); - assert_se(sd_bus_error_copy(&second, &const_error) == -EEXIST); - assert_se(second._need_free == 0); - assert_se(streq(const_error.name, second.name)); - assert_se(streq(const_error.message, second.message)); - assert_se(sd_bus_error_get_errno(&second) == EEXIST); - assert_se(sd_bus_error_has_name(&second, SD_BUS_ERROR_FILE_EXISTS)); - assert_se(sd_bus_error_is_set(&second)); - sd_bus_error_free(&second); - - assert_se(!sd_bus_error_is_set(&second)); - assert_se(temporarily_const_error._need_free < 0); - assert_se(sd_bus_error_copy(&second, &temporarily_const_error) == -EACCES); - assert_se(second._need_free > 0); - assert_se(streq(temporarily_const_error.name, second.name)); - assert_se(streq(temporarily_const_error.message, second.message)); - assert_se(sd_bus_error_get_errno(&second) == EACCES); - assert_se(sd_bus_error_has_name(&second, SD_BUS_ERROR_ACCESS_DENIED)); - assert_se(sd_bus_error_is_set(&second)); - - assert_se(!sd_bus_error_is_set(&error)); - assert_se(sd_bus_error_set_const(&error, "System.Error.EUCLEAN", "Hallo") == -EUCLEAN); - assert_se(streq(error.name, "System.Error.EUCLEAN")); - assert_se(streq(error.message, "Hallo")); - assert_se(sd_bus_error_has_name(&error, "System.Error.EUCLEAN")); - assert_se(sd_bus_error_get_errno(&error) == EUCLEAN); - assert_se(sd_bus_error_is_set(&error)); - sd_bus_error_free(&error); - - assert_se(!sd_bus_error_is_set(&error)); - assert_se(sd_bus_error_set_errno(&error, EBUSY) == -EBUSY); - assert_se(streq(error.name, "System.Error.EBUSY")); - assert_se(streq(error.message, strerror(EBUSY))); - assert_se(sd_bus_error_has_name(&error, "System.Error.EBUSY")); - assert_se(sd_bus_error_get_errno(&error) == EBUSY); - assert_se(sd_bus_error_is_set(&error)); - sd_bus_error_free(&error); - - assert_se(!sd_bus_error_is_set(&error)); - assert_se(sd_bus_error_set_errnof(&error, EIO, "Waldi %c", 'X') == -EIO); - assert_se(streq(error.name, SD_BUS_ERROR_IO_ERROR)); - assert_se(streq(error.message, "Waldi X")); - assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_IO_ERROR)); - assert_se(sd_bus_error_get_errno(&error) == EIO); - assert_se(sd_bus_error_is_set(&error)); - sd_bus_error_free(&error); - - /* Check with no error */ - assert_se(!sd_bus_error_is_set(&error)); - assert_se(sd_bus_error_set_errnof(&error, 0, "Waldi %c", 'X') == 0); - assert_se(error.name == NULL); - assert_se(error.message == NULL); - assert_se(!sd_bus_error_has_name(&error, SD_BUS_ERROR_IO_ERROR)); - assert_se(sd_bus_error_get_errno(&error) == 0); - assert_se(!sd_bus_error_is_set(&error)); -} - -extern const sd_bus_error_map __start_BUS_ERROR_MAP[]; -extern const sd_bus_error_map __stop_BUS_ERROR_MAP[]; - -static void dump_mapping_table(void) { - const sd_bus_error_map *m; - - printf("----- errno mappings ------\n"); - m = __start_BUS_ERROR_MAP; - while (m < __stop_BUS_ERROR_MAP) { - - if (m->code == BUS_ERROR_MAP_END_MARKER) { - m = ALIGN8_PTR(m+1); - continue; - } - - printf("%s -> %i/%s\n", strna(m->name), m->code, strna(errno_to_name(m->code))); - m++; - } - printf("---------------------------\n"); -} - -static void test_errno_mapping_standard(void) { - assert_se(sd_bus_error_set(NULL, "System.Error.EUCLEAN", NULL) == -EUCLEAN); - assert_se(sd_bus_error_set(NULL, "System.Error.EBUSY", NULL) == -EBUSY); - assert_se(sd_bus_error_set(NULL, "System.Error.EINVAL", NULL) == -EINVAL); - assert_se(sd_bus_error_set(NULL, "System.Error.WHATSIT", NULL) == -EIO); -} - -BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map test_errors[] = { - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error", 5), - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-2", 52), - SD_BUS_ERROR_MAP_END -}; - -BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map test_errors2[] = { - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-3", 33), - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-4", 44), - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-33", 333), - SD_BUS_ERROR_MAP_END -}; - -static const sd_bus_error_map test_errors3[] = { - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-88", 888), - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-99", 999), - SD_BUS_ERROR_MAP_END -}; - -static const sd_bus_error_map test_errors4[] = { - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-77", 777), - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-78", 778), - SD_BUS_ERROR_MAP_END -}; - -static const sd_bus_error_map test_errors_bad1[] = { - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-1", 0), - SD_BUS_ERROR_MAP_END -}; - -static const sd_bus_error_map test_errors_bad2[] = { - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-1", -1), - SD_BUS_ERROR_MAP_END -}; - -static void test_errno_mapping_custom(void) { - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error", NULL) == -5); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-2", NULL) == -52); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-x", NULL) == -EIO); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-33", NULL) == -333); - - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-88", NULL) == -EIO); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-99", NULL) == -EIO); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-77", NULL) == -EIO); - - assert_se(sd_bus_error_add_map(test_errors3) > 0); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-88", NULL) == -888); - assert_se(sd_bus_error_add_map(test_errors4) > 0); - assert_se(sd_bus_error_add_map(test_errors4) == 0); - assert_se(sd_bus_error_add_map(test_errors3) == 0); - - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-99", NULL) == -999); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-77", NULL) == -777); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-78", NULL) == -778); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-2", NULL) == -52); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-y", NULL) == -EIO); - - assert_se(sd_bus_error_set(NULL, BUS_ERROR_NO_SUCH_UNIT, NULL) == -ENOENT); - - assert_se(sd_bus_error_add_map(test_errors_bad1) == -EINVAL); - assert_se(sd_bus_error_add_map(test_errors_bad2) == -EINVAL); -} - -int main(int argc, char *argv[]) { - dump_mapping_table(); - - test_error(); - test_errno_mapping_standard(); - test_errno_mapping_custom(); - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-gvariant.c b/src/libsystemd/sd-bus/test-bus-gvariant.c deleted file mode 100644 index 83f114a0fe..0000000000 --- a/src/libsystemd/sd-bus/test-bus-gvariant.c +++ /dev/null @@ -1,224 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -#ifdef HAVE_GLIB -#include -#endif - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-dump.h" -#include "bus-gvariant.h" -#include "bus-internal.h" -#include "bus-message.h" -#include "bus-util.h" -#include "macro.h" -#include "util.h" - -static void test_bus_gvariant_is_fixed_size(void) { - assert_se(bus_gvariant_is_fixed_size("") > 0); - assert_se(bus_gvariant_is_fixed_size("()") > 0); - assert_se(bus_gvariant_is_fixed_size("y") > 0); - assert_se(bus_gvariant_is_fixed_size("u") > 0); - assert_se(bus_gvariant_is_fixed_size("b") > 0); - assert_se(bus_gvariant_is_fixed_size("n") > 0); - assert_se(bus_gvariant_is_fixed_size("q") > 0); - assert_se(bus_gvariant_is_fixed_size("i") > 0); - assert_se(bus_gvariant_is_fixed_size("t") > 0); - assert_se(bus_gvariant_is_fixed_size("d") > 0); - assert_se(bus_gvariant_is_fixed_size("s") == 0); - assert_se(bus_gvariant_is_fixed_size("o") == 0); - assert_se(bus_gvariant_is_fixed_size("g") == 0); - assert_se(bus_gvariant_is_fixed_size("h") > 0); - assert_se(bus_gvariant_is_fixed_size("ay") == 0); - assert_se(bus_gvariant_is_fixed_size("v") == 0); - assert_se(bus_gvariant_is_fixed_size("(u)") > 0); - assert_se(bus_gvariant_is_fixed_size("(uuuuy)") > 0); - assert_se(bus_gvariant_is_fixed_size("(uusuuy)") == 0); - assert_se(bus_gvariant_is_fixed_size("a{ss}") == 0); - assert_se(bus_gvariant_is_fixed_size("((u)yyy(b(iiii)))") > 0); - assert_se(bus_gvariant_is_fixed_size("((u)yyy(b(iiivi)))") == 0); -} - -static void test_bus_gvariant_get_size(void) { - assert_se(bus_gvariant_get_size("") == 0); - assert_se(bus_gvariant_get_size("()") == 1); - assert_se(bus_gvariant_get_size("y") == 1); - assert_se(bus_gvariant_get_size("u") == 4); - assert_se(bus_gvariant_get_size("b") == 1); - assert_se(bus_gvariant_get_size("n") == 2); - assert_se(bus_gvariant_get_size("q") == 2); - assert_se(bus_gvariant_get_size("i") == 4); - assert_se(bus_gvariant_get_size("t") == 8); - assert_se(bus_gvariant_get_size("d") == 8); - assert_se(bus_gvariant_get_size("s") < 0); - assert_se(bus_gvariant_get_size("o") < 0); - assert_se(bus_gvariant_get_size("g") < 0); - assert_se(bus_gvariant_get_size("h") == 4); - assert_se(bus_gvariant_get_size("ay") < 0); - assert_se(bus_gvariant_get_size("v") < 0); - assert_se(bus_gvariant_get_size("(u)") == 4); - assert_se(bus_gvariant_get_size("(uuuuy)") == 20); - assert_se(bus_gvariant_get_size("(uusuuy)") < 0); - assert_se(bus_gvariant_get_size("a{ss}") < 0); - assert_se(bus_gvariant_get_size("((u)yyy(b(iiii)))") == 28); - assert_se(bus_gvariant_get_size("((u)yyy(b(iiivi)))") < 0); - assert_se(bus_gvariant_get_size("((b)(t))") == 16); - assert_se(bus_gvariant_get_size("((b)(b)(t))") == 16); - assert_se(bus_gvariant_get_size("(bt)") == 16); - assert_se(bus_gvariant_get_size("((t)(b))") == 16); - assert_se(bus_gvariant_get_size("(tb)") == 16); - assert_se(bus_gvariant_get_size("((b)(b))") == 2); - assert_se(bus_gvariant_get_size("((t)(t))") == 16); -} - -static void test_bus_gvariant_get_alignment(void) { - assert_se(bus_gvariant_get_alignment("") == 1); - assert_se(bus_gvariant_get_alignment("()") == 1); - assert_se(bus_gvariant_get_alignment("y") == 1); - assert_se(bus_gvariant_get_alignment("b") == 1); - assert_se(bus_gvariant_get_alignment("u") == 4); - assert_se(bus_gvariant_get_alignment("s") == 1); - assert_se(bus_gvariant_get_alignment("o") == 1); - assert_se(bus_gvariant_get_alignment("g") == 1); - assert_se(bus_gvariant_get_alignment("v") == 8); - assert_se(bus_gvariant_get_alignment("h") == 4); - assert_se(bus_gvariant_get_alignment("i") == 4); - assert_se(bus_gvariant_get_alignment("t") == 8); - assert_se(bus_gvariant_get_alignment("x") == 8); - assert_se(bus_gvariant_get_alignment("q") == 2); - assert_se(bus_gvariant_get_alignment("n") == 2); - assert_se(bus_gvariant_get_alignment("d") == 8); - assert_se(bus_gvariant_get_alignment("ay") == 1); - assert_se(bus_gvariant_get_alignment("as") == 1); - assert_se(bus_gvariant_get_alignment("au") == 4); - assert_se(bus_gvariant_get_alignment("an") == 2); - assert_se(bus_gvariant_get_alignment("ans") == 2); - assert_se(bus_gvariant_get_alignment("ant") == 8); - assert_se(bus_gvariant_get_alignment("(ss)") == 1); - assert_se(bus_gvariant_get_alignment("(ssu)") == 4); - assert_se(bus_gvariant_get_alignment("a(ssu)") == 4); - assert_se(bus_gvariant_get_alignment("(u)") == 4); - assert_se(bus_gvariant_get_alignment("(uuuuy)") == 4); - assert_se(bus_gvariant_get_alignment("(uusuuy)") == 4); - assert_se(bus_gvariant_get_alignment("a{ss}") == 1); - assert_se(bus_gvariant_get_alignment("((u)yyy(b(iiii)))") == 4); - assert_se(bus_gvariant_get_alignment("((u)yyy(b(iiivi)))") == 8); - assert_se(bus_gvariant_get_alignment("((b)(t))") == 8); - assert_se(bus_gvariant_get_alignment("((b)(b)(t))") == 8); - assert_se(bus_gvariant_get_alignment("(bt)") == 8); - assert_se(bus_gvariant_get_alignment("((t)(b))") == 8); - assert_se(bus_gvariant_get_alignment("(tb)") == 8); - assert_se(bus_gvariant_get_alignment("((b)(b))") == 1); - assert_se(bus_gvariant_get_alignment("((t)(t))") == 8); -} - -static void test_marshal(void) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *n = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_free_ void *blob; - size_t sz; - int r; - - r = sd_bus_open_system(&bus); - if (r < 0) - exit(EXIT_TEST_SKIP); - - bus->message_version = 2; /* dirty hack to enable gvariant */ - - assert_se(sd_bus_message_new_method_call(bus, &m, "a.service.name", "/an/object/path/which/is/really/really/long/so/that/we/hit/the/eight/bit/boundary/by/quite/some/margin/to/test/this/stuff/that/it/really/works", "an.interface.name", "AMethodName") >= 0); - - assert_cc(sizeof(struct bus_header) == 16); - - assert_se(sd_bus_message_append(m, - "a(usv)", 3, - 4711, "first-string-parameter", "(st)", "X", (uint64_t) 1111, - 4712, "second-string-parameter", "(a(si))", 2, "Y", 5, "Z", 6, - 4713, "third-string-parameter", "(uu)", 1, 2) >= 0); - - assert_se(bus_message_seal(m, 4711, 0) >= 0); - -#ifdef HAVE_GLIB - { - GVariant *v; - char *t; - -#if !defined(GLIB_VERSION_2_36) - g_type_init(); -#endif - - v = g_variant_new_from_data(G_VARIANT_TYPE("(yyyyuta{tv})"), m->header, sizeof(struct bus_header) + m->fields_size, false, NULL, NULL); - assert_se(g_variant_is_normal_form(v)); - t = g_variant_print(v, TRUE); - printf("%s\n", t); - g_free(t); - g_variant_unref(v); - - v = g_variant_new_from_data(G_VARIANT_TYPE("(a(usv))"), m->body.data, m->user_body_size, false, NULL, NULL); - assert_se(g_variant_is_normal_form(v)); - t = g_variant_print(v, TRUE); - printf("%s\n", t); - g_free(t); - g_variant_unref(v); - } -#endif - - assert_se(bus_message_dump(m, NULL, BUS_MESSAGE_DUMP_WITH_HEADER) >= 0); - - assert_se(bus_message_get_blob(m, &blob, &sz) >= 0); - -#ifdef HAVE_GLIB - { - GVariant *v; - char *t; - - v = g_variant_new_from_data(G_VARIANT_TYPE("(yyyyuta{tv}v)"), blob, sz, false, NULL, NULL); - assert_se(g_variant_is_normal_form(v)); - t = g_variant_print(v, TRUE); - printf("%s\n", t); - g_free(t); - g_variant_unref(v); - } -#endif - - assert_se(bus_message_from_malloc(bus, blob, sz, NULL, 0, NULL, &n) >= 0); - blob = NULL; - - assert_se(bus_message_dump(n, NULL, BUS_MESSAGE_DUMP_WITH_HEADER) >= 0); - - m = sd_bus_message_unref(m); - - assert_se(sd_bus_message_new_method_call(bus, &m, "a.x", "/a/x", "a.x", "Ax") >= 0); - - assert_se(sd_bus_message_append(m, "as", 0) >= 0); - - assert_se(bus_message_seal(m, 4712, 0) >= 0); - assert_se(bus_message_dump(m, NULL, BUS_MESSAGE_DUMP_WITH_HEADER) >= 0); -} - -int main(int argc, char *argv[]) { - - test_bus_gvariant_is_fixed_size(); - test_bus_gvariant_get_size(); - test_bus_gvariant_get_alignment(); - test_marshal(); - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-introspect.c b/src/libsystemd/sd-bus/test-bus-introspect.c deleted file mode 100644 index 4425cfae26..0000000000 --- a/src/libsystemd/sd-bus/test-bus-introspect.c +++ /dev/null @@ -1,63 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "bus-introspect.h" -#include "log.h" - -static int prop_get(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { - return -EINVAL; -} - -static int prop_set(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { - return -EINVAL; -} - -static const sd_bus_vtable vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_METHOD("Hello", "ssas", "a(uu)", NULL, 0), - SD_BUS_METHOD("DeprecatedHello", "", "", NULL, SD_BUS_VTABLE_DEPRECATED), - SD_BUS_METHOD("DeprecatedHelloNoReply", "", "", NULL, SD_BUS_VTABLE_DEPRECATED|SD_BUS_VTABLE_METHOD_NO_REPLY), - SD_BUS_SIGNAL("Wowza", "sss", 0), - SD_BUS_SIGNAL("DeprecatedWowza", "ut", SD_BUS_VTABLE_DEPRECATED), - SD_BUS_WRITABLE_PROPERTY("AProperty", "s", prop_get, prop_set, 0, 0), - SD_BUS_PROPERTY("AReadOnlyDeprecatedProperty", "(ut)", prop_get, 0, SD_BUS_VTABLE_DEPRECATED), - SD_BUS_PROPERTY("ChangingProperty", "t", prop_get, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Invalidating", "t", prop_get, 0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - SD_BUS_PROPERTY("Constant", "t", prop_get, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EXPLICIT), - SD_BUS_VTABLE_END -}; - -int main(int argc, char *argv[]) { - struct introspect intro; - - log_set_max_level(LOG_DEBUG); - - assert_se(introspect_begin(&intro, false) >= 0); - - fprintf(intro.f, " \n"); - assert_se(introspect_write_interface(&intro, vtable) >= 0); - fputs(" \n", intro.f); - - fflush(intro.f); - fputs(intro.introspection, stdout); - - introspect_free(&intro); - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-kernel-bloom.c b/src/libsystemd/sd-bus/test-bus-kernel-bloom.c deleted file mode 100644 index eb6179d7d2..0000000000 --- a/src/libsystemd/sd-bus/test-bus-kernel-bloom.c +++ /dev/null @@ -1,141 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -#include "alloc-util.h" -#include "bus-kernel.h" -#include "bus-util.h" -#include "fd-util.h" -#include "log.h" -#include "util.h" - -static int test_match(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - int *found = userdata; - - *found = 1; - - return 0; -} - -static void test_one( - const char *path, - const char *interface, - const char *member, - bool as_list, - const char *arg0, - const char *match, - bool good) { - - _cleanup_close_ int bus_ref = -1; - _cleanup_free_ char *name = NULL, *bus_name = NULL, *address = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - sd_bus *a, *b; - int r, found = 0; - - assert_se(asprintf(&name, "deine-mutter-%u", (unsigned) getpid()) >= 0); - - bus_ref = bus_kernel_create_bus(name, false, &bus_name); - if (bus_ref == -ENOENT) - exit(EXIT_TEST_SKIP); - - assert_se(bus_ref >= 0); - - address = strappend("kernel:path=", bus_name); - assert_se(address); - - r = sd_bus_new(&a); - assert_se(r >= 0); - - r = sd_bus_new(&b); - assert_se(r >= 0); - - r = sd_bus_set_address(a, address); - assert_se(r >= 0); - - r = sd_bus_set_address(b, address); - assert_se(r >= 0); - - r = sd_bus_start(a); - assert_se(r >= 0); - - r = sd_bus_start(b); - assert_se(r >= 0); - - log_debug("match"); - r = sd_bus_add_match(b, NULL, match, test_match, &found); - assert_se(r >= 0); - - log_debug("signal"); - - if (as_list) - r = sd_bus_emit_signal(a, path, interface, member, "as", 1, arg0); - else - r = sd_bus_emit_signal(a, path, interface, member, "s", arg0); - assert_se(r >= 0); - - r = sd_bus_process(b, &m); - assert_se(r >= 0 && good == !!found); - - sd_bus_unref(a); - sd_bus_unref(b); -} - -int main(int argc, char *argv[]) { - log_set_max_level(LOG_DEBUG); - - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo/tuut'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "interface='waldo.com'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "member='Piep'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "member='Pi_ep'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "arg0='foobar'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "arg0='foo_bar'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", true, "foobar", "arg0='foobar'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", true, "foobar", "arg0='foo_bar'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", true, "foobar", "arg0has='foobar'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", true, "foobar", "arg0has='foo_bar'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo',interface='waldo.com',member='Piep',arg0='foobar'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo',interface='waldo.com',member='Piep',arg0='foobar2'", false); - - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo/quux'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo/bar/waldo'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo/bar'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/quux'", false); - test_one("/", "waldo.com", "Piep", false, "foobar", "path_namespace='/'", true); - - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo/'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo/bar/waldo/'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo/'", true); - - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "/foo/bar/waldo", "arg0path='/foo/'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "/foo", "arg0path='/foo'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "/foo", "arg0path='/foo/bar/waldo'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "/foo/", "arg0path='/foo/bar/waldo'", true); - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-kernel.c b/src/libsystemd/sd-bus/test-bus-kernel.c deleted file mode 100644 index 2214817312..0000000000 --- a/src/libsystemd/sd-bus/test-bus-kernel.c +++ /dev/null @@ -1,190 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -#include "alloc-util.h" -#include "bus-dump.h" -#include "bus-kernel.h" -#include "bus-util.h" -#include "fd-util.h" -#include "log.h" -#include "util.h" - -int main(int argc, char *argv[]) { - _cleanup_close_ int bus_ref = -1; - _cleanup_free_ char *name = NULL, *bus_name = NULL, *address = NULL, *bname = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *ua = NULL, *ub = NULL, *the_string = NULL; - sd_bus *a, *b; - int r, pipe_fds[2]; - const char *nn; - - log_set_max_level(LOG_DEBUG); - - assert_se(asprintf(&name, "deine-mutter-%u", (unsigned) getpid()) >= 0); - - bus_ref = bus_kernel_create_bus(name, false, &bus_name); - if (bus_ref == -ENOENT) - return EXIT_TEST_SKIP; - - assert_se(bus_ref >= 0); - - address = strappend("kernel:path=", bus_name); - assert_se(address); - - r = sd_bus_new(&a); - assert_se(r >= 0); - - r = sd_bus_new(&b); - assert_se(r >= 0); - - r = sd_bus_set_description(a, "a"); - assert_se(r >= 0); - - r = sd_bus_set_address(a, address); - assert_se(r >= 0); - - r = sd_bus_set_address(b, address); - assert_se(r >= 0); - - assert_se(sd_bus_negotiate_timestamp(a, 1) >= 0); - assert_se(sd_bus_negotiate_creds(a, true, _SD_BUS_CREDS_ALL) >= 0); - - assert_se(sd_bus_negotiate_timestamp(b, 0) >= 0); - assert_se(sd_bus_negotiate_creds(b, true, 0) >= 0); - - r = sd_bus_start(a); - assert_se(r >= 0); - - r = sd_bus_start(b); - assert_se(r >= 0); - - assert_se(sd_bus_negotiate_timestamp(b, 1) >= 0); - assert_se(sd_bus_negotiate_creds(b, true, _SD_BUS_CREDS_ALL) >= 0); - - r = sd_bus_get_unique_name(a, &ua); - assert_se(r >= 0); - printf("unique a: %s\n", ua); - - r = sd_bus_get_description(a, &nn); - assert_se(r >= 0); - printf("name of a: %s\n", nn); - - r = sd_bus_get_unique_name(b, &ub); - assert_se(r >= 0); - printf("unique b: %s\n", ub); - - r = sd_bus_get_description(b, &nn); - assert_se(r >= 0); - printf("name of b: %s\n", nn); - - assert_se(bus_kernel_get_bus_name(b, &bname) >= 0); - assert_se(endswith(bname, name)); - - r = sd_bus_call_method(a, "this.doesnt.exist", "/foo", "meh.mah", "muh", &error, NULL, "s", "yayayay"); - assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_SERVICE_UNKNOWN)); - assert_se(r == -EHOSTUNREACH); - - r = sd_bus_add_match(b, NULL, "interface='waldo.com',member='Piep'", NULL, NULL); - assert_se(r >= 0); - - r = sd_bus_emit_signal(a, "/foo/bar/waldo", "waldo.com", "Piep", "sss", "I am a string", "/this/is/a/path", "and.this.a.domain.name"); - assert_se(r >= 0); - - r = sd_bus_try_close(b); - assert_se(r == -EBUSY); - - r = sd_bus_process_priority(b, -10, &m); - assert_se(r == 0); - - r = sd_bus_process(b, &m); - assert_se(r > 0); - assert_se(m); - - bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - assert_se(sd_bus_message_rewind(m, true) >= 0); - - r = sd_bus_message_read(m, "s", &the_string); - assert_se(r >= 0); - assert_se(streq(the_string, "I am a string")); - - sd_bus_message_unref(m); - m = NULL; - - r = sd_bus_request_name(a, "net.x0pointer.foobar", 0); - assert_se(r >= 0); - - r = sd_bus_message_new_method_call(b, &m, "net.x0pointer.foobar", "/a/path", "an.inter.face", "AMethod"); - assert_se(r >= 0); - - assert_se(pipe2(pipe_fds, O_CLOEXEC) >= 0); - - assert_se(write(pipe_fds[1], "x", 1) == 1); - - pipe_fds[1] = safe_close(pipe_fds[1]); - - r = sd_bus_message_append(m, "h", pipe_fds[0]); - assert_se(r >= 0); - - pipe_fds[0] = safe_close(pipe_fds[0]); - - r = sd_bus_send(b, m, NULL); - assert_se(r >= 0); - - for (;;) { - sd_bus_message_unref(m); - m = NULL; - r = sd_bus_process(a, &m); - assert_se(r > 0); - assert_se(m); - - bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - assert_se(sd_bus_message_rewind(m, true) >= 0); - - if (sd_bus_message_is_method_call(m, "an.inter.face", "AMethod")) { - int fd; - char x; - - r = sd_bus_message_read(m, "h", &fd); - assert_se(r >= 0); - - assert_se(read(fd, &x, 1) == 1); - assert_se(x == 'x'); - break; - } - } - - r = sd_bus_release_name(a, "net.x0pointer.foobar"); - assert_se(r >= 0); - - r = sd_bus_release_name(a, "net.x0pointer.foobar"); - assert_se(r == -ESRCH); - - r = sd_bus_try_close(a); - assert_se(r >= 0); - - sd_bus_unref(a); - sd_bus_unref(b); - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-marshal.c b/src/libsystemd/sd-bus/test-bus-marshal.c deleted file mode 100644 index a28cc5b79e..0000000000 --- a/src/libsystemd/sd-bus/test-bus-marshal.c +++ /dev/null @@ -1,432 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 - -#ifdef HAVE_GLIB -#include -#endif - -#ifdef HAVE_DBUS -#include -#endif - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-dump.h" -#include "bus-label.h" -#include "bus-message.h" -#include "bus-util.h" -#include "fd-util.h" -#include "hexdecoct.h" -#include "log.h" -#include "util.h" - -static void test_bus_path_encode_unique(void) { - _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL; - - assert_se(bus_path_encode_unique(NULL, "/foo/bar", "some.sender", "a.suffix", &a) >= 0 && streq_ptr(a, "/foo/bar/some_2esender/a_2esuffix")); - assert_se(bus_path_decode_unique(a, "/foo/bar", &b, &c) > 0 && streq_ptr(b, "some.sender") && streq_ptr(c, "a.suffix")); - assert_se(bus_path_decode_unique(a, "/bar/foo", &d, &d) == 0 && !d); - assert_se(bus_path_decode_unique("/foo/bar/onlyOneSuffix", "/foo/bar", &d, &d) == 0 && !d); - assert_se(bus_path_decode_unique("/foo/bar/_/_", "/foo/bar", &d, &e) > 0 && streq_ptr(d, "") && streq_ptr(e, "")); -} - -static void test_bus_path_encode(void) { - _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *f = NULL; - - assert_se(sd_bus_path_encode("/foo/bar", "waldo", &a) >= 0 && streq(a, "/foo/bar/waldo")); - assert_se(sd_bus_path_decode(a, "/waldo", &b) == 0 && b == NULL); - assert_se(sd_bus_path_decode(a, "/foo/bar", &b) > 0 && streq(b, "waldo")); - - assert_se(sd_bus_path_encode("xxxx", "waldo", &c) < 0); - assert_se(sd_bus_path_encode("/foo/", "waldo", &c) < 0); - - assert_se(sd_bus_path_encode("/foo/bar", "", &c) >= 0 && streq(c, "/foo/bar/_")); - assert_se(sd_bus_path_decode(c, "/foo/bar", &d) > 0 && streq(d, "")); - - assert_se(sd_bus_path_encode("/foo/bar", "foo.bar", &e) >= 0 && streq(e, "/foo/bar/foo_2ebar")); - assert_se(sd_bus_path_decode(e, "/foo/bar", &f) > 0 && streq(f, "foo.bar")); -} - -static void test_bus_path_encode_many(void) { - _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *f = NULL; - - assert_se(sd_bus_path_decode_many("/foo/bar", "/prefix/%", NULL) == 0); - assert_se(sd_bus_path_decode_many("/prefix/bar", "/prefix/%bar", NULL) == 1); - assert_se(sd_bus_path_decode_many("/foo/bar", "/prefix/%/suffix", NULL) == 0); - assert_se(sd_bus_path_decode_many("/prefix/foobar/suffix", "/prefix/%/suffix", &a) == 1 && streq_ptr(a, "foobar")); - assert_se(sd_bus_path_decode_many("/prefix/one_foo_two/mid/three_bar_four/suffix", "/prefix/one_%_two/mid/three_%_four/suffix", &b, &c) == 1 && streq_ptr(b, "foo") && streq_ptr(c, "bar")); - assert_se(sd_bus_path_decode_many("/prefix/one_foo_two/mid/three_bar_four/suffix", "/prefix/one_%_two/mid/three_%_four/suffix", NULL, &d) == 1 && streq_ptr(d, "bar")); - - assert_se(sd_bus_path_decode_many("/foo/bar", "/foo/bar/%", NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/bar%", NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/bar", NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%bar", NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/bar/suffix") == 1); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%%/suffix", NULL, NULL) == 0); /* multiple '%' are treated verbatim */ - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/suffi", NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/suffix", &e) == 1 && streq_ptr(e, "bar")); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/%", NULL, NULL) == 1); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/%/%", NULL, NULL, NULL) == 1); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "%/%/%", NULL, NULL, NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/%", NULL, NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/%/", NULL, NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/", NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%", NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "%", NULL) == 0); - - assert_se(sd_bus_path_encode_many(&f, "/prefix/one_%_two/mid/three_%_four/suffix", "foo", "bar") >= 0 && streq_ptr(f, "/prefix/one_foo_two/mid/three_bar_four/suffix")); -} - -static void test_bus_label_escape_one(const char *a, const char *b) { - _cleanup_free_ char *t = NULL, *x = NULL, *y = NULL; - - assert_se(t = bus_label_escape(a)); - assert_se(streq(t, b)); - - assert_se(x = bus_label_unescape(t)); - assert_se(streq(a, x)); - - assert_se(y = bus_label_unescape(b)); - assert_se(streq(a, y)); -} - -static void test_bus_label_escape(void) { - test_bus_label_escape_one("foo123bar", "foo123bar"); - test_bus_label_escape_one("foo.bar", "foo_2ebar"); - test_bus_label_escape_one("foo_2ebar", "foo_5f2ebar"); - test_bus_label_escape_one("", "_"); - test_bus_label_escape_one("_", "_5f"); - test_bus_label_escape_one("1", "_31"); - test_bus_label_escape_one(":1", "_3a1"); -} - -int main(int argc, char *argv[]) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *copy = NULL; - int r, boolean; - const char *x, *x2, *y, *z, *a, *b, *c, *d, *a_signature; - uint8_t u, v; - void *buffer = NULL; - size_t sz; - char *h; - const int32_t integer_array[] = { -1, -2, 0, 1, 2 }, *return_array; - char *s; - _cleanup_free_ char *first = NULL, *second = NULL, *third = NULL; - _cleanup_fclose_ FILE *ms = NULL; - size_t first_size = 0, second_size = 0, third_size = 0; - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - double dbl; - uint64_t u64; - - r = sd_bus_default_system(&bus); - if (r < 0) - return EXIT_TEST_SKIP; - - r = sd_bus_message_new_method_call(bus, &m, "foobar.waldo", "/", "foobar.waldo", "Piep"); - assert_se(r >= 0); - - r = sd_bus_message_append(m, ""); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "s", "a string"); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "s", NULL); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "asg", 2, "string #1", "string #2", "sba(tt)ss"); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "sass", "foobar", 5, "foo", "bar", "waldo", "piep", "pap", "after"); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "a{yv}", 2, 3, "s", "foo", 5, "s", "waldo"); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "y(ty)y(yt)y", 8, 777ULL, 7, 9, 77, 7777ULL, 10); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "()"); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "ba(ss)", 255, 3, "aaa", "1", "bbb", "2", "ccc", "3"); - assert_se(r >= 0); - - r = sd_bus_message_open_container(m, 'a', "s"); - assert_se(r >= 0); - - r = sd_bus_message_append_basic(m, 's', "foobar"); - assert_se(r >= 0); - - r = sd_bus_message_append_basic(m, 's', "waldo"); - assert_se(r >= 0); - - r = sd_bus_message_close_container(m); - assert_se(r >= 0); - - r = sd_bus_message_append_string_space(m, 5, &s); - assert_se(r >= 0); - strcpy(s, "hallo"); - - r = sd_bus_message_append_array(m, 'i', integer_array, sizeof(integer_array)); - assert_se(r >= 0); - - r = sd_bus_message_append_array(m, 'u', NULL, 0); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "a(stdo)", 1, "foo", 815ULL, 47.0, "/"); - assert_se(r >= 0); - - r = bus_message_seal(m, 4711, 0); - assert_se(r >= 0); - - bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - ms = open_memstream(&first, &first_size); - bus_message_dump(m, ms, 0); - fflush(ms); - assert_se(!ferror(ms)); - - r = bus_message_get_blob(m, &buffer, &sz); - assert_se(r >= 0); - - h = hexmem(buffer, sz); - assert_se(h); - - log_info("message size = %zu, contents =\n%s", sz, h); - free(h); - -#ifdef HAVE_GLIB - { - GDBusMessage *g; - char *p; - -#if !defined(GLIB_VERSION_2_36) - g_type_init(); -#endif - - g = g_dbus_message_new_from_blob(buffer, sz, 0, NULL); - p = g_dbus_message_print(g, 0); - log_info("%s", p); - g_free(p); - g_object_unref(g); - } -#endif - -#ifdef HAVE_DBUS - { - DBusMessage *w; - DBusError error; - - dbus_error_init(&error); - - w = dbus_message_demarshal(buffer, sz, &error); - if (!w) - log_error("%s", error.message); - else - dbus_message_unref(w); - - dbus_error_free(&error); - } -#endif - - m = sd_bus_message_unref(m); - - r = bus_message_from_malloc(bus, buffer, sz, NULL, 0, NULL, &m); - assert_se(r >= 0); - - bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - fclose(ms); - ms = open_memstream(&second, &second_size); - bus_message_dump(m, ms, 0); - fflush(ms); - assert_se(!ferror(ms)); - assert_se(first_size == second_size); - assert_se(memcmp(first, second, first_size) == 0); - - assert_se(sd_bus_message_rewind(m, true) >= 0); - - r = sd_bus_message_read(m, "ssasg", &x, &x2, 2, &y, &z, &a_signature); - assert_se(r > 0); - assert_se(streq(x, "a string")); - assert_se(streq(x2, "")); - assert_se(streq(y, "string #1")); - assert_se(streq(z, "string #2")); - assert_se(streq(a_signature, "sba(tt)ss")); - - r = sd_bus_message_read(m, "sass", &x, 5, &y, &z, &a, &b, &c, &d); - assert_se(r > 0); - assert_se(streq(x, "foobar")); - assert_se(streq(y, "foo")); - assert_se(streq(z, "bar")); - assert_se(streq(a, "waldo")); - assert_se(streq(b, "piep")); - assert_se(streq(c, "pap")); - assert_se(streq(d, "after")); - - r = sd_bus_message_read(m, "a{yv}", 2, &u, "s", &x, &v, "s", &y); - assert_se(r > 0); - assert_se(u == 3); - assert_se(streq(x, "foo")); - assert_se(v == 5); - assert_se(streq(y, "waldo")); - - r = sd_bus_message_read(m, "y(ty)", &v, &u64, &u); - assert_se(r > 0); - assert_se(v == 8); - assert_se(u64 == 777); - assert_se(u == 7); - - r = sd_bus_message_read(m, "y(yt)", &v, &u, &u64); - assert_se(r > 0); - assert_se(v == 9); - assert_se(u == 77); - assert_se(u64 == 7777); - - r = sd_bus_message_read(m, "y", &v); - assert_se(r > 0); - assert_se(v == 10); - - r = sd_bus_message_read(m, "()"); - assert_se(r > 0); - - r = sd_bus_message_read(m, "ba(ss)", &boolean, 3, &x, &y, &a, &b, &c, &d); - assert_se(r > 0); - assert_se(boolean); - assert_se(streq(x, "aaa")); - assert_se(streq(y, "1")); - assert_se(streq(a, "bbb")); - assert_se(streq(b, "2")); - assert_se(streq(c, "ccc")); - assert_se(streq(d, "3")); - - assert_se(sd_bus_message_verify_type(m, 'a', "s") > 0); - - r = sd_bus_message_read(m, "as", 2, &x, &y); - assert_se(r > 0); - assert_se(streq(x, "foobar")); - assert_se(streq(y, "waldo")); - - r = sd_bus_message_read_basic(m, 's', &s); - assert_se(r > 0); - assert_se(streq(s, "hallo")); - - r = sd_bus_message_read_array(m, 'i', (const void**) &return_array, &sz); - assert_se(r > 0); - assert_se(sz == sizeof(integer_array)); - assert_se(memcmp(integer_array, return_array, sz) == 0); - - r = sd_bus_message_read_array(m, 'u', (const void**) &return_array, &sz); - assert_se(r > 0); - assert_se(sz == 0); - - r = sd_bus_message_read(m, "a(stdo)", 1, &x, &u64, &dbl, &y); - assert_se(r > 0); - assert_se(streq(x, "foo")); - assert_se(u64 == 815ULL); - assert_se(fabs(dbl - 47.0) < 0.1); - assert_se(streq(y, "/")); - - r = sd_bus_message_peek_type(m, NULL, NULL); - assert_se(r == 0); - - r = sd_bus_message_new_method_call(bus, ©, "foobar.waldo", "/", "foobar.waldo", "Piep"); - assert_se(r >= 0); - - r = sd_bus_message_rewind(m, true); - assert_se(r >= 0); - - r = sd_bus_message_copy(copy, m, true); - assert_se(r >= 0); - - r = bus_message_seal(copy, 4712, 0); - assert_se(r >= 0); - - fclose(ms); - ms = open_memstream(&third, &third_size); - bus_message_dump(copy, ms, 0); - fflush(ms); - assert_se(!ferror(ms)); - - printf("<%.*s>\n", (int) first_size, first); - printf("<%.*s>\n", (int) third_size, third); - - assert_se(first_size == third_size); - assert_se(memcmp(first, third, third_size) == 0); - - r = sd_bus_message_rewind(m, true); - assert_se(r >= 0); - - assert_se(sd_bus_message_verify_type(m, 's', NULL) > 0); - - r = sd_bus_message_skip(m, "ssasg"); - assert_se(r > 0); - - assert_se(sd_bus_message_verify_type(m, 's', NULL) > 0); - - r = sd_bus_message_skip(m, "sass"); - assert_se(r >= 0); - - assert_se(sd_bus_message_verify_type(m, 'a', "{yv}") > 0); - - r = sd_bus_message_skip(m, "a{yv}y(ty)y(yt)y()"); - assert_se(r >= 0); - - assert_se(sd_bus_message_verify_type(m, 'b', NULL) > 0); - - r = sd_bus_message_read(m, "b", &boolean); - assert_se(r > 0); - assert_se(boolean); - - r = sd_bus_message_enter_container(m, 0, NULL); - assert_se(r > 0); - - r = sd_bus_message_read(m, "(ss)", &x, &y); - assert_se(r > 0); - - r = sd_bus_message_read(m, "(ss)", &a, &b); - assert_se(r > 0); - - r = sd_bus_message_read(m, "(ss)", &c, &d); - assert_se(r > 0); - - r = sd_bus_message_read(m, "(ss)", &x, &y); - assert_se(r == 0); - - r = sd_bus_message_exit_container(m); - assert_se(r >= 0); - - assert_se(streq(x, "aaa")); - assert_se(streq(y, "1")); - assert_se(streq(a, "bbb")); - assert_se(streq(b, "2")); - assert_se(streq(c, "ccc")); - assert_se(streq(d, "3")); - - test_bus_label_escape(); - test_bus_path_encode(); - test_bus_path_encode_unique(); - test_bus_path_encode_many(); - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-match.c b/src/libsystemd/sd-bus/test-bus-match.c deleted file mode 100644 index 29c4529f95..0000000000 --- a/src/libsystemd/sd-bus/test-bus-match.c +++ /dev/null @@ -1,159 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "bus-match.h" -#include "bus-message.h" -#include "bus-slot.h" -#include "bus-util.h" -#include "log.h" -#include "macro.h" - -static bool mask[32]; - -static int filter(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - log_info("Ran %u", PTR_TO_UINT(userdata)); - assert_se(PTR_TO_UINT(userdata) < ELEMENTSOF(mask)); - mask[PTR_TO_UINT(userdata)] = true; - return 0; -} - -static bool mask_contains(unsigned a[], unsigned n) { - unsigned i, j; - - for (i = 0; i < ELEMENTSOF(mask); i++) { - bool found = false; - - for (j = 0; j < n; j++) - if (a[j] == i) { - found = true; - break; - } - - if (found != mask[i]) - return false; - } - - return true; -} - -static int match_add(sd_bus_slot *slots, struct bus_match_node *root, const char *match, int value) { - struct bus_match_component *components = NULL; - unsigned n_components = 0; - sd_bus_slot *s; - int r; - - s = slots + value; - zero(*s); - - r = bus_match_parse(match, &components, &n_components); - if (r < 0) - return r; - - s->userdata = INT_TO_PTR(value); - s->match_callback.callback = filter; - - r = bus_match_add(root, components, n_components, &s->match_callback); - bus_match_parse_free(components, n_components); - - return r; -} - -static void test_match_scope(const char *match, enum bus_match_scope scope) { - struct bus_match_component *components = NULL; - unsigned n_components = 0; - - assert_se(bus_match_parse(match, &components, &n_components) >= 0); - assert_se(bus_match_get_scope(components, n_components) == scope); - bus_match_parse_free(components, n_components); -} - -int main(int argc, char *argv[]) { - struct bus_match_node root = { - .type = BUS_MATCH_ROOT, - }; - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - enum bus_match_node_type i; - sd_bus_slot slots[19]; - int r; - - r = sd_bus_open_system(&bus); - if (r < 0) - return EXIT_TEST_SKIP; - - assert_se(match_add(slots, &root, "arg2='wal\\'do',sender='foo',type='signal',interface='bar.x',", 1) >= 0); - assert_se(match_add(slots, &root, "arg2='wal\\'do2',sender='foo',type='signal',interface='bar.x',", 2) >= 0); - assert_se(match_add(slots, &root, "arg3='test',sender='foo',type='signal',interface='bar.x',", 3) >= 0); - assert_se(match_add(slots, &root, "arg3='test',sender='foo',type='method_call',interface='bar.x',", 4) >= 0); - assert_se(match_add(slots, &root, "", 5) >= 0); - assert_se(match_add(slots, &root, "interface='quux.x'", 6) >= 0); - assert_se(match_add(slots, &root, "interface='bar.x'", 7) >= 0); - assert_se(match_add(slots, &root, "member='waldo',path='/foo/bar'", 8) >= 0); - assert_se(match_add(slots, &root, "path='/foo/bar'", 9) >= 0); - assert_se(match_add(slots, &root, "path_namespace='/foo'", 10) >= 0); - assert_se(match_add(slots, &root, "path_namespace='/foo/quux'", 11) >= 0); - assert_se(match_add(slots, &root, "arg1='two'", 12) >= 0); - assert_se(match_add(slots, &root, "member='waldo',arg2path='/prefix/'", 13) >= 0); - assert_se(match_add(slots, &root, "member=waldo,path='/foo/bar',arg3namespace='prefix'", 14) >= 0); - assert_se(match_add(slots, &root, "arg4has='pi'", 15) >= 0); - assert_se(match_add(slots, &root, "arg4has='pa'", 16) >= 0); - assert_se(match_add(slots, &root, "arg4has='po'", 17) >= 0); - assert_se(match_add(slots, &root, "arg4='pi'", 18) >= 0); - - bus_match_dump(&root, 0); - - assert_se(sd_bus_message_new_signal(bus, &m, "/foo/bar", "bar.x", "waldo") >= 0); - assert_se(sd_bus_message_append(m, "ssssas", "one", "two", "/prefix/three", "prefix.four", 3, "pi", "pa", "po") >= 0); - assert_se(bus_message_seal(m, 1, 0) >= 0); - - zero(mask); - assert_se(bus_match_run(NULL, &root, m) == 0); - assert_se(mask_contains((unsigned[]) { 9, 8, 7, 5, 10, 12, 13, 14, 15, 16, 17 }, 11)); - - assert_se(bus_match_remove(&root, &slots[8].match_callback) >= 0); - assert_se(bus_match_remove(&root, &slots[13].match_callback) >= 0); - - bus_match_dump(&root, 0); - - zero(mask); - assert_se(bus_match_run(NULL, &root, m) == 0); - assert_se(mask_contains((unsigned[]) { 9, 5, 10, 12, 14, 7, 15, 16, 17 }, 9)); - - for (i = 0; i < _BUS_MATCH_NODE_TYPE_MAX; i++) { - char buf[32]; - const char *x; - - assert_se(x = bus_match_node_type_to_string(i, buf, sizeof(buf))); - - if (i >= BUS_MATCH_MESSAGE_TYPE) - assert_se(bus_match_node_type_from_string(x, strlen(x)) == i); - } - - bus_match_free(&root); - - test_match_scope("interface='foobar'", BUS_MATCH_GENERIC); - test_match_scope("", BUS_MATCH_GENERIC); - test_match_scope("interface='org.freedesktop.DBus.Local'", BUS_MATCH_LOCAL); - test_match_scope("sender='org.freedesktop.DBus.Local'", BUS_MATCH_LOCAL); - test_match_scope("member='gurke',path='/org/freedesktop/DBus/Local'", BUS_MATCH_LOCAL); - test_match_scope("arg2='piep',sender='org.freedesktop.DBus',member='waldo'", BUS_MATCH_DRIVER); - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-objects.c b/src/libsystemd/sd-bus/test-bus-objects.c deleted file mode 100644 index f11cafd888..0000000000 --- a/src/libsystemd/sd-bus/test-bus-objects.c +++ /dev/null @@ -1,555 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-dump.h" -#include "bus-internal.h" -#include "bus-message.h" -#include "bus-util.h" -#include "log.h" -#include "macro.h" -#include "strv.h" -#include "util.h" - -struct context { - int fds[2]; - bool quit; - char *something; - char *automatic_string_property; - uint32_t automatic_integer_property; -}; - -static int something_handler(sd_bus_message *m, void *userdata, sd_bus_error *error) { - struct context *c = userdata; - const char *s; - char *n = NULL; - int r; - - r = sd_bus_message_read(m, "s", &s); - assert_se(r > 0); - - n = strjoin("<<<", s, ">>>", NULL); - assert_se(n); - - free(c->something); - c->something = n; - - log_info("AlterSomething() called, got %s, returning %s", s, n); - - /* This should fail, since the return type doesn't match */ - assert_se(sd_bus_reply_method_return(m, "u", 4711) == -ENOMSG); - - r = sd_bus_reply_method_return(m, "s", n); - assert_se(r >= 0); - - return 1; -} - -static int exit_handler(sd_bus_message *m, void *userdata, sd_bus_error *error) { - struct context *c = userdata; - int r; - - c->quit = true; - - log_info("Exit called"); - - r = sd_bus_reply_method_return(m, ""); - assert_se(r >= 0); - - return 1; -} - -static int get_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { - struct context *c = userdata; - int r; - - log_info("property get for %s called, returning \"%s\".", property, c->something); - - r = sd_bus_message_append(reply, "s", c->something); - assert_se(r >= 0); - - return 1; -} - -static int set_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *value, void *userdata, sd_bus_error *error) { - struct context *c = userdata; - const char *s; - char *n; - int r; - - log_info("property set for %s called", property); - - r = sd_bus_message_read(value, "s", &s); - assert_se(r >= 0); - - n = strdup(s); - assert_se(n); - - free(c->something); - c->something = n; - - return 1; -} - -static int value_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *s = NULL; - const char *x; - int r; - - assert_se(asprintf(&s, "object %p, path %s", userdata, path) >= 0); - r = sd_bus_message_append(reply, "s", s); - assert_se(r >= 0); - - assert_se(x = startswith(path, "/value/")); - - assert_se(PTR_TO_UINT(userdata) == 30); - - return 1; -} - -static int notify_test(sd_bus_message *m, void *userdata, sd_bus_error *error) { - int r; - - assert_se(sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), m->path, "org.freedesktop.systemd.ValueTest", "Value", NULL) >= 0); - - r = sd_bus_reply_method_return(m, NULL); - assert_se(r >= 0); - - return 1; -} - -static int notify_test2(sd_bus_message *m, void *userdata, sd_bus_error *error) { - int r; - - assert_se(sd_bus_emit_properties_changed_strv(sd_bus_message_get_bus(m), m->path, "org.freedesktop.systemd.ValueTest", NULL) >= 0); - - r = sd_bus_reply_method_return(m, NULL); - assert_se(r >= 0); - - return 1; -} - -static int emit_interfaces_added(sd_bus_message *m, void *userdata, sd_bus_error *error) { - int r; - - assert_se(sd_bus_emit_interfaces_added(sd_bus_message_get_bus(m), "/value/a/x", "org.freedesktop.systemd.ValueTest", NULL) >= 0); - - r = sd_bus_reply_method_return(m, NULL); - assert_se(r >= 0); - - return 1; -} - -static int emit_interfaces_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { - int r; - - assert_se(sd_bus_emit_interfaces_removed(sd_bus_message_get_bus(m), "/value/a/x", "org.freedesktop.systemd.ValueTest", NULL) >= 0); - - r = sd_bus_reply_method_return(m, NULL); - assert_se(r >= 0); - - return 1; -} - -static int emit_object_added(sd_bus_message *m, void *userdata, sd_bus_error *error) { - int r; - - assert_se(sd_bus_emit_object_added(sd_bus_message_get_bus(m), "/value/a/x") >= 0); - - r = sd_bus_reply_method_return(m, NULL); - assert_se(r >= 0); - - return 1; -} - -static int emit_object_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { - int r; - - assert_se(sd_bus_emit_object_removed(sd_bus_message_get_bus(m), "/value/a/x") >= 0); - - r = sd_bus_reply_method_return(m, NULL); - assert_se(r >= 0); - - return 1; -} - -static const sd_bus_vtable vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_METHOD("AlterSomething", "s", "s", something_handler, 0), - SD_BUS_METHOD("Exit", "", "", exit_handler, 0), - SD_BUS_WRITABLE_PROPERTY("Something", "s", get_handler, set_handler, 0, 0), - SD_BUS_WRITABLE_PROPERTY("AutomaticStringProperty", "s", NULL, NULL, offsetof(struct context, automatic_string_property), 0), - SD_BUS_WRITABLE_PROPERTY("AutomaticIntegerProperty", "u", NULL, NULL, offsetof(struct context, automatic_integer_property), 0), - SD_BUS_METHOD("NoOperation", NULL, NULL, NULL, 0), - SD_BUS_METHOD("EmitInterfacesAdded", NULL, NULL, emit_interfaces_added, 0), - SD_BUS_METHOD("EmitInterfacesRemoved", NULL, NULL, emit_interfaces_removed, 0), - SD_BUS_METHOD("EmitObjectAdded", NULL, NULL, emit_object_added, 0), - SD_BUS_METHOD("EmitObjectRemoved", NULL, NULL, emit_object_removed, 0), - SD_BUS_VTABLE_END -}; - -static const sd_bus_vtable vtable2[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_METHOD("NotifyTest", "", "", notify_test, 0), - SD_BUS_METHOD("NotifyTest2", "", "", notify_test2, 0), - SD_BUS_PROPERTY("Value", "s", value_handler, 10, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Value2", "s", value_handler, 10, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - SD_BUS_PROPERTY("Value3", "s", value_handler, 10, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Value4", "s", value_handler, 10, 0), - SD_BUS_PROPERTY("AnExplicitProperty", "s", NULL, offsetof(struct context, something), SD_BUS_VTABLE_PROPERTY_EXPLICIT|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - SD_BUS_VTABLE_END -}; - -static int enumerator_callback(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { - - if (object_path_startswith("/value", path)) - assert_se(*nodes = strv_new("/value/a", "/value/b", "/value/c", NULL)); - - return 1; -} - -static int enumerator2_callback(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { - - if (object_path_startswith("/value/a", path)) - assert_se(*nodes = strv_new("/value/a/x", "/value/a/y", "/value/a/z", NULL)); - - return 1; -} - -static void *server(void *p) { - struct context *c = p; - sd_bus *bus = NULL; - sd_id128_t id; - int r; - - c->quit = false; - - assert_se(sd_id128_randomize(&id) >= 0); - - assert_se(sd_bus_new(&bus) >= 0); - assert_se(sd_bus_set_fd(bus, c->fds[0], c->fds[0]) >= 0); - assert_se(sd_bus_set_server(bus, 1, id) >= 0); - - assert_se(sd_bus_add_object_vtable(bus, NULL, "/foo", "org.freedesktop.systemd.test", vtable, c) >= 0); - assert_se(sd_bus_add_object_vtable(bus, NULL, "/foo", "org.freedesktop.systemd.test2", vtable, c) >= 0); - assert_se(sd_bus_add_fallback_vtable(bus, NULL, "/value", "org.freedesktop.systemd.ValueTest", vtable2, NULL, UINT_TO_PTR(20)) >= 0); - assert_se(sd_bus_add_node_enumerator(bus, NULL, "/value", enumerator_callback, NULL) >= 0); - assert_se(sd_bus_add_node_enumerator(bus, NULL, "/value/a", enumerator2_callback, NULL) >= 0); - assert_se(sd_bus_add_object_manager(bus, NULL, "/value") >= 0); - assert_se(sd_bus_add_object_manager(bus, NULL, "/value/a") >= 0); - - assert_se(sd_bus_start(bus) >= 0); - - log_error("Entering event loop on server"); - - while (!c->quit) { - log_error("Loop!"); - - r = sd_bus_process(bus, NULL); - if (r < 0) { - log_error_errno(r, "Failed to process requests: %m"); - goto fail; - } - - if (r == 0) { - r = sd_bus_wait(bus, (uint64_t) -1); - if (r < 0) { - log_error_errno(r, "Failed to wait: %m"); - goto fail; - } - - continue; - } - } - - r = 0; - -fail: - if (bus) { - sd_bus_flush(bus); - sd_bus_unref(bus); - } - - return INT_TO_PTR(r); -} - -static int client(struct context *c) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *s; - int r; - - assert_se(sd_bus_new(&bus) >= 0); - assert_se(sd_bus_set_fd(bus, c->fds[1], c->fds[1]) >= 0); - assert_se(sd_bus_start(bus) >= 0); - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "NoOperation", &error, NULL, NULL); - assert_se(r >= 0); - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AlterSomething", &error, &reply, "s", "hallo"); - assert_se(r >= 0); - - r = sd_bus_message_read(reply, "s", &s); - assert_se(r >= 0); - assert_se(streq(s, "<<>>")); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Doesntexist", &error, &reply, ""); - assert_se(r < 0); - assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)); - - sd_bus_error_free(&error); - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AlterSomething", &error, &reply, "as", 1, "hallo"); - assert_se(r < 0); - assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_INVALID_ARGS)); - - sd_bus_error_free(&error); - - r = sd_bus_get_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Something", &error, &reply, "s"); - assert_se(r >= 0); - - r = sd_bus_message_read(reply, "s", &s); - assert_se(r >= 0); - assert_se(streq(s, "<<>>")); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_set_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Something", &error, "s", "test"); - assert_se(r >= 0); - - r = sd_bus_get_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Something", &error, &reply, "s"); - assert_se(r >= 0); - - r = sd_bus_message_read(reply, "s", &s); - assert_se(r >= 0); - assert_se(streq(s, "test")); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_set_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AutomaticIntegerProperty", &error, "u", 815); - assert_se(r >= 0); - - assert_se(c->automatic_integer_property == 815); - - r = sd_bus_set_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AutomaticStringProperty", &error, "s", "Du Dödel, Du!"); - assert_se(r >= 0); - - assert_se(streq(c->automatic_string_property, "Du Dödel, Du!")); - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); - assert_se(r >= 0); - - r = sd_bus_message_read(reply, "s", &s); - assert_se(r >= 0); - fputs(s, stdout); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_get_property(bus, "org.freedesktop.systemd.test", "/value/xuzz", "org.freedesktop.systemd.ValueTest", "Value", &error, &reply, "s"); - assert_se(r >= 0); - - r = sd_bus_message_read(reply, "s", &s); - assert_se(r >= 0); - log_info("read %s", s); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); - assert_se(r >= 0); - - r = sd_bus_message_read(reply, "s", &s); - assert_se(r >= 0); - fputs(s, stdout); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); - assert_se(r >= 0); - - r = sd_bus_message_read(reply, "s", &s); - assert_se(r >= 0); - fputs(s, stdout); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); - assert_se(r >= 0); - - r = sd_bus_message_read(reply, "s", &s); - assert_se(r >= 0); - fputs(s, stdout); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.DBus.Properties", "GetAll", &error, &reply, "s", ""); - assert_se(r >= 0); - - bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.DBus.Properties", "GetAll", &error, &reply, "s", "org.freedesktop.systemd.ValueTest2"); - assert_se(r < 0); - assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_INTERFACE)); - sd_bus_error_free(&error); - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects", &error, &reply, ""); - assert_se(r < 0); - assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)); - sd_bus_error_free(&error); - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects", &error, &reply, ""); - assert_se(r >= 0); - - bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.systemd.ValueTest", "NotifyTest", &error, NULL, ""); - assert_se(r >= 0); - - r = sd_bus_process(bus, &reply); - assert_se(r > 0); - - assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.Properties", "PropertiesChanged")); - bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.systemd.ValueTest", "NotifyTest2", &error, NULL, ""); - assert_se(r >= 0); - - r = sd_bus_process(bus, &reply); - assert_se(r > 0); - - assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.Properties", "PropertiesChanged")); - bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitInterfacesAdded", &error, NULL, ""); - assert_se(r >= 0); - - r = sd_bus_process(bus, &reply); - assert_se(r > 0); - - assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded")); - bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitInterfacesRemoved", &error, NULL, ""); - assert_se(r >= 0); - - r = sd_bus_process(bus, &reply); - assert_se(r > 0); - - assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved")); - bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitObjectAdded", &error, NULL, ""); - assert_se(r >= 0); - - r = sd_bus_process(bus, &reply); - assert_se(r > 0); - - assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded")); - bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitObjectRemoved", &error, NULL, ""); - assert_se(r >= 0); - - r = sd_bus_process(bus, &reply); - assert_se(r > 0); - - assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved")); - bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Exit", &error, NULL, ""); - assert_se(r >= 0); - - sd_bus_flush(bus); - - return 0; -} - -int main(int argc, char *argv[]) { - struct context c = {}; - pthread_t s; - void *p; - int r, q; - - zero(c); - - c.automatic_integer_property = 4711; - assert_se(c.automatic_string_property = strdup("dudeldu")); - - assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, c.fds) >= 0); - - r = pthread_create(&s, NULL, server, &c); - if (r != 0) - return -r; - - r = client(&c); - - q = pthread_join(s, &p); - if (q != 0) - return -q; - - if (r < 0) - return r; - - if (PTR_TO_INT(p) < 0) - return PTR_TO_INT(p); - - free(c.something); - free(c.automatic_string_property); - - return EXIT_SUCCESS; -} diff --git a/src/libsystemd/sd-bus/test-bus-server.c b/src/libsystemd/sd-bus/test-bus-server.c deleted file mode 100644 index b6272efc30..0000000000 --- a/src/libsystemd/sd-bus/test-bus-server.c +++ /dev/null @@ -1,216 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 - -#include "sd-bus.h" - -#include "bus-internal.h" -#include "bus-util.h" -#include "log.h" -#include "macro.h" -#include "util.h" - -struct context { - int fds[2]; - - bool client_negotiate_unix_fds; - bool server_negotiate_unix_fds; - - bool client_anonymous_auth; - bool server_anonymous_auth; -}; - -static void *server(void *p) { - struct context *c = p; - sd_bus *bus = NULL; - sd_id128_t id; - bool quit = false; - int r; - - assert_se(sd_id128_randomize(&id) >= 0); - - assert_se(sd_bus_new(&bus) >= 0); - assert_se(sd_bus_set_fd(bus, c->fds[0], c->fds[0]) >= 0); - assert_se(sd_bus_set_server(bus, 1, id) >= 0); - assert_se(sd_bus_set_anonymous(bus, c->server_anonymous_auth) >= 0); - assert_se(sd_bus_negotiate_fds(bus, c->server_negotiate_unix_fds) >= 0); - assert_se(sd_bus_start(bus) >= 0); - - while (!quit) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - - r = sd_bus_process(bus, &m); - if (r < 0) { - log_error_errno(r, "Failed to process requests: %m"); - goto fail; - } - - if (r == 0) { - r = sd_bus_wait(bus, (uint64_t) -1); - if (r < 0) { - log_error_errno(r, "Failed to wait: %m"); - goto fail; - } - - continue; - } - - if (!m) - continue; - - log_info("Got message! member=%s", strna(sd_bus_message_get_member(m))); - - if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "Exit")) { - - assert_se((sd_bus_can_send(bus, 'h') >= 1) == (c->server_negotiate_unix_fds && c->client_negotiate_unix_fds)); - - r = sd_bus_message_new_method_return(m, &reply); - if (r < 0) { - log_error_errno(r, "Failed to allocate return: %m"); - goto fail; - } - - quit = true; - - } else if (sd_bus_message_is_method_call(m, NULL, NULL)) { - r = sd_bus_message_new_method_error( - m, - &reply, - &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_UNKNOWN_METHOD, "Unknown method.")); - if (r < 0) { - log_error_errno(r, "Failed to allocate return: %m"); - goto fail; - } - } - - if (reply) { - r = sd_bus_send(bus, reply, NULL); - if (r < 0) { - log_error_errno(r, "Failed to send reply: %m"); - goto fail; - } - } - } - - r = 0; - -fail: - if (bus) { - sd_bus_flush(bus); - sd_bus_unref(bus); - } - - return INT_TO_PTR(r); -} - -static int client(struct context *c) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert_se(sd_bus_new(&bus) >= 0); - assert_se(sd_bus_set_fd(bus, c->fds[1], c->fds[1]) >= 0); - assert_se(sd_bus_negotiate_fds(bus, c->client_negotiate_unix_fds) >= 0); - assert_se(sd_bus_set_anonymous(bus, c->client_anonymous_auth) >= 0); - assert_se(sd_bus_start(bus) >= 0); - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd.test", - "/", - "org.freedesktop.systemd.test", - "Exit"); - if (r < 0) - return log_error_errno(r, "Failed to allocate method call: %m"); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int test_one(bool client_negotiate_unix_fds, bool server_negotiate_unix_fds, - bool client_anonymous_auth, bool server_anonymous_auth) { - - struct context c; - pthread_t s; - void *p; - int r, q; - - zero(c); - - assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, c.fds) >= 0); - - c.client_negotiate_unix_fds = client_negotiate_unix_fds; - c.server_negotiate_unix_fds = server_negotiate_unix_fds; - c.client_anonymous_auth = client_anonymous_auth; - c.server_anonymous_auth = server_anonymous_auth; - - r = pthread_create(&s, NULL, server, &c); - if (r != 0) - return -r; - - r = client(&c); - - q = pthread_join(s, &p); - if (q != 0) - return -q; - - if (r < 0) - return r; - - if (PTR_TO_INT(p) < 0) - return PTR_TO_INT(p); - - return 0; -} - -int main(int argc, char *argv[]) { - int r; - - r = test_one(true, true, false, false); - assert_se(r >= 0); - - r = test_one(true, false, false, false); - assert_se(r >= 0); - - r = test_one(false, true, false, false); - assert_se(r >= 0); - - r = test_one(false, false, false, false); - assert_se(r >= 0); - - r = test_one(true, true, true, true); - assert_se(r >= 0); - - r = test_one(true, true, false, true); - assert_se(r >= 0); - - r = test_one(true, true, true, false); - assert_se(r == -EPERM); - - return EXIT_SUCCESS; -} diff --git a/src/libsystemd/sd-bus/test-bus-signature.c b/src/libsystemd/sd-bus/test-bus-signature.c deleted file mode 100644 index 4f4fd093bf..0000000000 --- a/src/libsystemd/sd-bus/test-bus-signature.c +++ /dev/null @@ -1,164 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "bus-internal.h" -#include "bus-signature.h" -#include "log.h" -#include "string-util.h" - -int main(int argc, char *argv[]) { - char prefix[256]; - int r; - - assert_se(signature_is_single("y", false)); - assert_se(signature_is_single("u", false)); - assert_se(signature_is_single("v", false)); - assert_se(signature_is_single("as", false)); - assert_se(signature_is_single("(ss)", false)); - assert_se(signature_is_single("()", false)); - assert_se(signature_is_single("(()()()()())", false)); - assert_se(signature_is_single("(((())))", false)); - assert_se(signature_is_single("((((s))))", false)); - assert_se(signature_is_single("{ss}", true)); - assert_se(signature_is_single("a{ss}", false)); - assert_se(!signature_is_single("uu", false)); - assert_se(!signature_is_single("", false)); - assert_se(!signature_is_single("(", false)); - assert_se(!signature_is_single(")", false)); - assert_se(!signature_is_single("())", false)); - assert_se(!signature_is_single("((())", false)); - assert_se(!signature_is_single("{)", false)); - assert_se(!signature_is_single("{}", true)); - assert_se(!signature_is_single("{sss}", true)); - assert_se(!signature_is_single("{s}", true)); - assert_se(!signature_is_single("{ss}", false)); - assert_se(!signature_is_single("{ass}", true)); - assert_se(!signature_is_single("a}", true)); - - assert_se(signature_is_pair("yy")); - assert_se(signature_is_pair("ss")); - assert_se(signature_is_pair("sas")); - assert_se(signature_is_pair("sv")); - assert_se(signature_is_pair("sa(vs)")); - assert_se(!signature_is_pair("")); - assert_se(!signature_is_pair("va")); - assert_se(!signature_is_pair("sss")); - assert_se(!signature_is_pair("{s}ss")); - - assert_se(signature_is_valid("ssa{ss}sssub", true)); - assert_se(signature_is_valid("ssa{ss}sssub", false)); - assert_se(signature_is_valid("{ss}", true)); - assert_se(!signature_is_valid("{ss}", false)); - assert_se(signature_is_valid("", true)); - assert_se(signature_is_valid("", false)); - - assert_se(signature_is_valid("sssusa(uuubbba(uu)uuuu)a{u(uuuvas)}", false)); - - assert_se(!signature_is_valid("a", false)); - assert_se(signature_is_valid("as", false)); - assert_se(signature_is_valid("aas", false)); - assert_se(signature_is_valid("aaas", false)); - assert_se(signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad", false)); - assert_se(signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaas", false)); - assert_se(!signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaau", false)); - - assert_se(signature_is_valid("(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))", false)); - assert_se(!signature_is_valid("((((((((((((((((((((((((((((((((()))))))))))))))))))))))))))))))))", false)); - - assert_se(namespace_complex_pattern("", "")); - assert_se(namespace_complex_pattern("foobar", "foobar")); - assert_se(namespace_complex_pattern("foobar.waldo", "foobar.waldo")); - assert_se(namespace_complex_pattern("foobar.", "foobar.waldo")); - assert_se(namespace_complex_pattern("foobar.waldo", "foobar.")); - assert_se(!namespace_complex_pattern("foobar.waldo", "foobar")); - assert_se(!namespace_complex_pattern("foobar", "foobar.waldo")); - assert_se(!namespace_complex_pattern("", "foo")); - assert_se(!namespace_complex_pattern("foo", "")); - assert_se(!namespace_complex_pattern("foo.", "")); - - assert_se(path_complex_pattern("", "")); - assert_se(!path_complex_pattern("", "/")); - assert_se(!path_complex_pattern("/", "")); - assert_se(path_complex_pattern("/", "/")); - assert_se(path_complex_pattern("/foobar/", "/")); - assert_se(!path_complex_pattern("/foobar/", "/foobar")); - assert_se(path_complex_pattern("/foobar", "/foobar")); - assert_se(!path_complex_pattern("/foobar", "/foobar/")); - assert_se(!path_complex_pattern("/foobar", "/foobar/waldo")); - assert_se(path_complex_pattern("/foobar/", "/foobar/waldo")); - assert_se(path_complex_pattern("/foobar/waldo", "/foobar/")); - - assert_se(path_simple_pattern("/foo/", "/foo/bar/waldo")); - - assert_se(namespace_simple_pattern("", "")); - assert_se(namespace_simple_pattern("", ".foobar")); - assert_se(namespace_simple_pattern("foobar", "foobar")); - assert_se(namespace_simple_pattern("foobar.waldo", "foobar.waldo")); - assert_se(namespace_simple_pattern("foobar", "foobar.waldo")); - assert_se(!namespace_simple_pattern("foobar.waldo", "foobar")); - assert_se(!namespace_simple_pattern("", "foo")); - assert_se(!namespace_simple_pattern("foo", "")); - assert_se(namespace_simple_pattern("foo.", "foo.bar.waldo")); - - assert_se(streq(object_path_startswith("/foo/bar", "/foo"), "bar")); - assert_se(streq(object_path_startswith("/foo", "/foo"), "")); - assert_se(streq(object_path_startswith("/foo", "/"), "foo")); - assert_se(streq(object_path_startswith("/", "/"), "")); - assert_se(!object_path_startswith("/foo", "/bar")); - assert_se(!object_path_startswith("/", "/bar")); - assert_se(!object_path_startswith("/foo", "")); - - assert_se(object_path_is_valid("/foo/bar")); - assert_se(object_path_is_valid("/foo")); - assert_se(object_path_is_valid("/")); - assert_se(object_path_is_valid("/foo5")); - assert_se(object_path_is_valid("/foo_5")); - assert_se(!object_path_is_valid("")); - assert_se(!object_path_is_valid("/foo/")); - assert_se(!object_path_is_valid("//")); - assert_se(!object_path_is_valid("//foo")); - assert_se(!object_path_is_valid("/foo//bar")); - assert_se(!object_path_is_valid("/foo/aaaäöä")); - - OBJECT_PATH_FOREACH_PREFIX(prefix, "/") { - log_info("<%s>", prefix); - assert_not_reached("???"); - } - - r = 0; - OBJECT_PATH_FOREACH_PREFIX(prefix, "/xxx") { - log_info("<%s>", prefix); - assert_se(streq(prefix, "/")); - assert_se(r == 0); - r++; - } - assert_se(r == 1); - - r = 0; - OBJECT_PATH_FOREACH_PREFIX(prefix, "/xxx/yyy/zzz") { - log_info("<%s>", prefix); - assert_se(r != 0 || streq(prefix, "/xxx/yyy")); - assert_se(r != 1 || streq(prefix, "/xxx")); - assert_se(r != 2 || streq(prefix, "/")); - r++; - } - assert_se(r == 3); - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-zero-copy.c b/src/libsystemd/sd-bus/test-bus-zero-copy.c deleted file mode 100644 index 3380e8500a..0000000000 --- a/src/libsystemd/sd-bus/test-bus-zero-copy.c +++ /dev/null @@ -1,210 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -#include "alloc-util.h" -#include "bus-dump.h" -#include "bus-kernel.h" -#include "bus-message.h" -#include "fd-util.h" -#include "log.h" -#include "memfd-util.h" -#include "string-util.h" -#include "util.h" - -#define FIRST_ARRAY 17 -#define SECOND_ARRAY 33 - -#define STRING_SIZE 123 - -int main(int argc, char *argv[]) { - _cleanup_free_ char *name = NULL, *bus_name = NULL, *address = NULL; - const char *unique; - uint8_t *p; - sd_bus *a, *b; - int r, bus_ref; - sd_bus_message *m; - int f; - uint64_t sz; - uint32_t u32; - size_t i, l; - char *s; - _cleanup_close_ int sfd = -1; - - log_set_max_level(LOG_DEBUG); - - assert_se(asprintf(&name, "deine-mutter-%u", (unsigned) getpid()) >= 0); - - bus_ref = bus_kernel_create_bus(name, false, &bus_name); - if (bus_ref == -ENOENT) - return EXIT_TEST_SKIP; - - assert_se(bus_ref >= 0); - - address = strappend("kernel:path=", bus_name); - assert_se(address); - - r = sd_bus_new(&a); - assert_se(r >= 0); - - r = sd_bus_new(&b); - assert_se(r >= 0); - - r = sd_bus_set_address(a, address); - assert_se(r >= 0); - - r = sd_bus_set_address(b, address); - assert_se(r >= 0); - - r = sd_bus_start(a); - assert_se(r >= 0); - - r = sd_bus_start(b); - assert_se(r >= 0); - - r = sd_bus_get_unique_name(a, &unique); - assert_se(r >= 0); - - r = sd_bus_message_new_method_call(b, &m, unique, "/a/path", "an.inter.face", "AMethod"); - assert_se(r >= 0); - - r = sd_bus_message_open_container(m, 'r', "aysay"); - assert_se(r >= 0); - - r = sd_bus_message_append_array_space(m, 'y', FIRST_ARRAY, (void**) &p); - assert_se(r >= 0); - - p[0] = '<'; - memset(p+1, 'L', FIRST_ARRAY-2); - p[FIRST_ARRAY-1] = '>'; - - f = memfd_new_and_map(NULL, STRING_SIZE, (void**) &s); - assert_se(f >= 0); - - s[0] = '<'; - for (i = 1; i < STRING_SIZE-2; i++) - s[i] = '0' + (i % 10); - s[STRING_SIZE-2] = '>'; - s[STRING_SIZE-1] = 0; - munmap(s, STRING_SIZE); - - r = memfd_get_size(f, &sz); - assert_se(r >= 0); - assert_se(sz == STRING_SIZE); - - r = sd_bus_message_append_string_memfd(m, f, 0, (uint64_t) -1); - assert_se(r >= 0); - - close(f); - - f = memfd_new_and_map(NULL, SECOND_ARRAY, (void**) &p); - assert_se(f >= 0); - - p[0] = '<'; - memset(p+1, 'P', SECOND_ARRAY-2); - p[SECOND_ARRAY-1] = '>'; - munmap(p, SECOND_ARRAY); - - r = memfd_get_size(f, &sz); - assert_se(r >= 0); - assert_se(sz == SECOND_ARRAY); - - r = sd_bus_message_append_array_memfd(m, 'y', f, 0, (uint64_t) -1); - assert_se(r >= 0); - - close(f); - - r = sd_bus_message_close_container(m); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "u", 4711); - assert_se(r >= 0); - - assert_se((sfd = memfd_new_and_map(NULL, 6, (void**) &p)) >= 0); - memcpy(p, "abcd\0", 6); - munmap(p, 6); - assert_se(sd_bus_message_append_string_memfd(m, sfd, 1, 4) >= 0); - - r = bus_message_seal(m, 55, 99*USEC_PER_SEC); - assert_se(r >= 0); - - bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - r = sd_bus_send(b, m, NULL); - assert_se(r >= 0); - - sd_bus_message_unref(m); - - r = sd_bus_process(a, &m); - assert_se(r > 0); - - bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - sd_bus_message_rewind(m, true); - - r = sd_bus_message_enter_container(m, 'r', "aysay"); - assert_se(r > 0); - - r = sd_bus_message_read_array(m, 'y', (const void**) &p, &l); - assert_se(r > 0); - assert_se(l == FIRST_ARRAY); - - assert_se(p[0] == '<'); - for (i = 1; i < l-1; i++) - assert_se(p[i] == 'L'); - assert_se(p[l-1] == '>'); - - r = sd_bus_message_read(m, "s", &s); - assert_se(r > 0); - - assert_se(s[0] == '<'); - for (i = 1; i < STRING_SIZE-2; i++) - assert_se(s[i] == (char) ('0' + (i % 10))); - assert_se(s[STRING_SIZE-2] == '>'); - assert_se(s[STRING_SIZE-1] == 0); - - r = sd_bus_message_read_array(m, 'y', (const void**) &p, &l); - assert_se(r > 0); - assert_se(l == SECOND_ARRAY); - - assert_se(p[0] == '<'); - for (i = 1; i < l-1; i++) - assert_se(p[i] == 'P'); - assert_se(p[l-1] == '>'); - - r = sd_bus_message_exit_container(m); - assert_se(r > 0); - - r = sd_bus_message_read(m, "u", &u32); - assert_se(r > 0); - assert_se(u32 == 4711); - - r = sd_bus_message_read(m, "s", &s); - assert_se(r > 0); - assert_se(streq_ptr(s, "bcd")); - - sd_bus_message_unref(m); - - sd_bus_unref(a); - sd_bus_unref(b); - - return 0; -} diff --git a/src/libsystemd/sd-daemon/Makefile b/src/libsystemd/sd-daemon/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/libsystemd/sd-daemon/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-daemon/sd-daemon.c b/src/libsystemd/sd-daemon/sd-daemon.c deleted file mode 100644 index 4da9dbfd63..0000000000 --- a/src/libsystemd/sd-daemon/sd-daemon.c +++ /dev/null @@ -1,622 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "fs-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "socket-util.h" -#include "strv.h" -#include "util.h" - -#define SNDBUF_SIZE (8*1024*1024) - -static void unsetenv_all(bool unset_environment) { - - if (!unset_environment) - return; - - unsetenv("LISTEN_PID"); - unsetenv("LISTEN_FDS"); - unsetenv("LISTEN_FDNAMES"); -} - -_public_ int sd_listen_fds(int unset_environment) { - const char *e; - int n, r, fd; - pid_t pid; - - e = getenv("LISTEN_PID"); - if (!e) { - r = 0; - goto finish; - } - - r = parse_pid(e, &pid); - if (r < 0) - goto finish; - - /* Is this for us? */ - if (getpid() != pid) { - r = 0; - goto finish; - } - - e = getenv("LISTEN_FDS"); - if (!e) { - r = 0; - goto finish; - } - - r = safe_atoi(e, &n); - if (r < 0) - goto finish; - - assert_cc(SD_LISTEN_FDS_START < INT_MAX); - if (n <= 0 || n > INT_MAX - SD_LISTEN_FDS_START) { - r = -EINVAL; - goto finish; - } - - for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) { - r = fd_cloexec(fd, true); - if (r < 0) - goto finish; - } - - r = n; - -finish: - unsetenv_all(unset_environment); - return r; -} - -_public_ int sd_listen_fds_with_names(int unset_environment, char ***names) { - _cleanup_strv_free_ char **l = NULL; - bool have_names; - int n_names = 0, n_fds; - const char *e; - int r; - - if (!names) - return sd_listen_fds(unset_environment); - - e = getenv("LISTEN_FDNAMES"); - if (e) { - n_names = strv_split_extract(&l, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - if (n_names < 0) { - unsetenv_all(unset_environment); - return n_names; - } - - have_names = true; - } else - have_names = false; - - n_fds = sd_listen_fds(unset_environment); - if (n_fds <= 0) - return n_fds; - - if (have_names) { - if (n_names != n_fds) - return -EINVAL; - } else { - r = strv_extend_n(&l, "unknown", n_fds); - if (r < 0) - return r; - } - - *names = l; - l = NULL; - - return n_fds; -} - -_public_ int sd_is_fifo(int fd, const char *path) { - struct stat st_fd; - - assert_return(fd >= 0, -EBADF); - - if (fstat(fd, &st_fd) < 0) - return -errno; - - if (!S_ISFIFO(st_fd.st_mode)) - return 0; - - if (path) { - struct stat st_path; - - if (stat(path, &st_path) < 0) { - - if (errno == ENOENT || errno == ENOTDIR) - return 0; - - return -errno; - } - - return - st_path.st_dev == st_fd.st_dev && - st_path.st_ino == st_fd.st_ino; - } - - return 1; -} - -_public_ int sd_is_special(int fd, const char *path) { - struct stat st_fd; - - assert_return(fd >= 0, -EBADF); - - if (fstat(fd, &st_fd) < 0) - return -errno; - - if (!S_ISREG(st_fd.st_mode) && !S_ISCHR(st_fd.st_mode)) - return 0; - - if (path) { - struct stat st_path; - - if (stat(path, &st_path) < 0) { - - if (errno == ENOENT || errno == ENOTDIR) - return 0; - - return -errno; - } - - if (S_ISREG(st_fd.st_mode) && S_ISREG(st_path.st_mode)) - return - st_path.st_dev == st_fd.st_dev && - st_path.st_ino == st_fd.st_ino; - else if (S_ISCHR(st_fd.st_mode) && S_ISCHR(st_path.st_mode)) - return st_path.st_rdev == st_fd.st_rdev; - else - return 0; - } - - return 1; -} - -static int sd_is_socket_internal(int fd, int type, int listening) { - struct stat st_fd; - - assert_return(fd >= 0, -EBADF); - assert_return(type >= 0, -EINVAL); - - if (fstat(fd, &st_fd) < 0) - return -errno; - - if (!S_ISSOCK(st_fd.st_mode)) - return 0; - - if (type != 0) { - int other_type = 0; - socklen_t l = sizeof(other_type); - - if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0) - return -errno; - - if (l != sizeof(other_type)) - return -EINVAL; - - if (other_type != type) - return 0; - } - - if (listening >= 0) { - int accepting = 0; - socklen_t l = sizeof(accepting); - - if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0) - return -errno; - - if (l != sizeof(accepting)) - return -EINVAL; - - if (!accepting != !listening) - return 0; - } - - return 1; -} - -_public_ int sd_is_socket(int fd, int family, int type, int listening) { - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(family >= 0, -EINVAL); - - r = sd_is_socket_internal(fd, type, listening); - if (r <= 0) - return r; - - if (family > 0) { - union sockaddr_union sockaddr = {}; - socklen_t l = sizeof(sockaddr); - - if (getsockname(fd, &sockaddr.sa, &l) < 0) - return -errno; - - if (l < sizeof(sa_family_t)) - return -EINVAL; - - return sockaddr.sa.sa_family == family; - } - - return 1; -} - -_public_ int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) { - union sockaddr_union sockaddr = {}; - socklen_t l = sizeof(sockaddr); - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(IN_SET(family, 0, AF_INET, AF_INET6), -EINVAL); - - r = sd_is_socket_internal(fd, type, listening); - if (r <= 0) - return r; - - if (getsockname(fd, &sockaddr.sa, &l) < 0) - return -errno; - - if (l < sizeof(sa_family_t)) - return -EINVAL; - - if (sockaddr.sa.sa_family != AF_INET && - sockaddr.sa.sa_family != AF_INET6) - return 0; - - if (family != 0) - if (sockaddr.sa.sa_family != family) - return 0; - - if (port > 0) { - if (sockaddr.sa.sa_family == AF_INET) { - if (l < sizeof(struct sockaddr_in)) - return -EINVAL; - - return htons(port) == sockaddr.in.sin_port; - } else { - if (l < sizeof(struct sockaddr_in6)) - return -EINVAL; - - return htons(port) == sockaddr.in6.sin6_port; - } - } - - return 1; -} - -_public_ int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) { - union sockaddr_union sockaddr = {}; - socklen_t l = sizeof(sockaddr); - int r; - - assert_return(fd >= 0, -EBADF); - - r = sd_is_socket_internal(fd, type, listening); - if (r <= 0) - return r; - - if (getsockname(fd, &sockaddr.sa, &l) < 0) - return -errno; - - if (l < sizeof(sa_family_t)) - return -EINVAL; - - if (sockaddr.sa.sa_family != AF_UNIX) - return 0; - - if (path) { - if (length == 0) - length = strlen(path); - - if (length == 0) - /* Unnamed socket */ - return l == offsetof(struct sockaddr_un, sun_path); - - if (path[0]) - /* Normal path socket */ - return - (l >= offsetof(struct sockaddr_un, sun_path) + length + 1) && - memcmp(path, sockaddr.un.sun_path, length+1) == 0; - else - /* Abstract namespace socket */ - return - (l == offsetof(struct sockaddr_un, sun_path) + length) && - memcmp(path, sockaddr.un.sun_path, length) == 0; - } - - return 1; -} - -_public_ int sd_is_mq(int fd, const char *path) { - struct mq_attr attr; - - /* Check that the fd is valid */ - assert_return(fcntl(fd, F_GETFD) >= 0, -errno); - - if (mq_getattr(fd, &attr) < 0) { - if (errno == EBADF) - /* A non-mq fd (or an invalid one, but we ruled that out above) */ - return 0; - return -errno; - } - - if (path) { - char fpath[PATH_MAX]; - struct stat a, b; - - assert_return(path_is_absolute(path), -EINVAL); - - if (fstat(fd, &a) < 0) - return -errno; - - strncpy(stpcpy(fpath, "/dev/mqueue"), path, sizeof(fpath) - 12); - fpath[sizeof(fpath)-1] = 0; - - if (stat(fpath, &b) < 0) - return -errno; - - if (a.st_dev != b.st_dev || - a.st_ino != b.st_ino) - return 0; - } - - return 1; -} - -_public_ int sd_pid_notify_with_fds(pid_t pid, int unset_environment, const char *state, const int *fds, unsigned n_fds) { - union sockaddr_union sockaddr = { - .sa.sa_family = AF_UNIX, - }; - struct iovec iovec = { - .iov_base = (char*) state, - }; - struct msghdr msghdr = { - .msg_iov = &iovec, - .msg_iovlen = 1, - .msg_name = &sockaddr, - }; - _cleanup_close_ int fd = -1; - struct cmsghdr *cmsg = NULL; - const char *e; - bool have_pid; - int r; - - if (!state) { - r = -EINVAL; - goto finish; - } - - if (n_fds > 0 && !fds) { - r = -EINVAL; - goto finish; - } - - e = getenv("NOTIFY_SOCKET"); - if (!e) - return 0; - - /* Must be an abstract socket, or an absolute path */ - if ((e[0] != '@' && e[0] != '/') || e[1] == 0) { - r = -EINVAL; - goto finish; - } - - if (strlen(e) > sizeof(sockaddr.un.sun_path)) { - r = -EINVAL; - goto finish; - } - - fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); - if (fd < 0) { - r = -errno; - goto finish; - } - - fd_inc_sndbuf(fd, SNDBUF_SIZE); - - iovec.iov_len = strlen(state); - - strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path)); - if (sockaddr.un.sun_path[0] == '@') - sockaddr.un.sun_path[0] = 0; - - msghdr.msg_namelen = SOCKADDR_UN_LEN(sockaddr.un); - - have_pid = pid != 0 && pid != getpid(); - - if (n_fds > 0 || have_pid) { - /* CMSG_SPACE(0) may return value different than zero, which results in miscalculated controllen. */ - msghdr.msg_controllen = - (n_fds > 0 ? CMSG_SPACE(sizeof(int) * n_fds) : 0) + - (have_pid ? CMSG_SPACE(sizeof(struct ucred)) : 0); - - msghdr.msg_control = alloca0(msghdr.msg_controllen); - - cmsg = CMSG_FIRSTHDR(&msghdr); - if (n_fds > 0) { - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - cmsg->cmsg_len = CMSG_LEN(sizeof(int) * n_fds); - - memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * n_fds); - - if (have_pid) - assert_se(cmsg = CMSG_NXTHDR(&msghdr, cmsg)); - } - - if (have_pid) { - struct ucred *ucred; - - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_CREDENTIALS; - cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred)); - - ucred = (struct ucred*) CMSG_DATA(cmsg); - ucred->pid = pid; - ucred->uid = getuid(); - ucred->gid = getgid(); - } - } - - /* First try with fake ucred data, as requested */ - if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) >= 0) { - r = 1; - goto finish; - } - - /* If that failed, try with our own ucred instead */ - if (have_pid) { - msghdr.msg_controllen -= CMSG_SPACE(sizeof(struct ucred)); - if (msghdr.msg_controllen == 0) - msghdr.msg_control = NULL; - - if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) >= 0) { - r = 1; - goto finish; - } - } - - r = -errno; - -finish: - if (unset_environment) - unsetenv("NOTIFY_SOCKET"); - - return r; -} - -_public_ int sd_pid_notify(pid_t pid, int unset_environment, const char *state) { - return sd_pid_notify_with_fds(pid, unset_environment, state, NULL, 0); -} - -_public_ int sd_notify(int unset_environment, const char *state) { - return sd_pid_notify_with_fds(0, unset_environment, state, NULL, 0); -} - -_public_ int sd_pid_notifyf(pid_t pid, int unset_environment, const char *format, ...) { - _cleanup_free_ char *p = NULL; - int r; - - if (format) { - va_list ap; - - va_start(ap, format); - r = vasprintf(&p, format, ap); - va_end(ap); - - if (r < 0 || !p) - return -ENOMEM; - } - - return sd_pid_notify(pid, unset_environment, p); -} - -_public_ int sd_notifyf(int unset_environment, const char *format, ...) { - _cleanup_free_ char *p = NULL; - int r; - - if (format) { - va_list ap; - - va_start(ap, format); - r = vasprintf(&p, format, ap); - va_end(ap); - - if (r < 0 || !p) - return -ENOMEM; - } - - return sd_pid_notify(0, unset_environment, p); -} - -_public_ int sd_booted(void) { - /* We test whether the runtime unit file directory has been - * created. This takes place in mount-setup.c, so is - * guaranteed to happen very early during boot. */ - - return laccess("/run/systemd/system/", F_OK) >= 0; -} - -_public_ int sd_watchdog_enabled(int unset_environment, uint64_t *usec) { - const char *s, *p = ""; /* p is set to dummy value to do unsetting */ - uint64_t u; - int r = 0; - - s = getenv("WATCHDOG_USEC"); - if (!s) - goto finish; - - r = safe_atou64(s, &u); - if (r < 0) - goto finish; - if (u <= 0 || u >= USEC_INFINITY) { - r = -EINVAL; - goto finish; - } - - p = getenv("WATCHDOG_PID"); - if (p) { - pid_t pid; - - r = parse_pid(p, &pid); - if (r < 0) - goto finish; - - /* Is this for us? */ - if (getpid() != pid) { - r = 0; - goto finish; - } - } - - if (usec) - *usec = u; - - r = 1; - -finish: - if (unset_environment && s) - unsetenv("WATCHDOG_USEC"); - if (unset_environment && p) - unsetenv("WATCHDOG_PID"); - - return r; -} diff --git a/src/libsystemd/sd-device/Makefile b/src/libsystemd/sd-device/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/libsystemd/sd-device/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-device/device-enumerator-private.h b/src/libsystemd/sd-device/device-enumerator-private.h deleted file mode 100644 index eb06f9542d..0000000000 --- a/src/libsystemd/sd-device/device-enumerator-private.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 Tom Gundersen - - 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 "sd-device.h" - -int device_enumerator_scan_devices(sd_device_enumerator *enumeartor); -int device_enumerator_scan_subsystems(sd_device_enumerator *enumeartor); -int device_enumerator_add_device(sd_device_enumerator *enumerator, sd_device *device); -int device_enumerator_add_match_is_initialized(sd_device_enumerator *enumerator); -sd_device *device_enumerator_get_first(sd_device_enumerator *enumerator); -sd_device *device_enumerator_get_next(sd_device_enumerator *enumerator); - -#define FOREACH_DEVICE_AND_SUBSYSTEM(enumerator, device) \ - for (device = device_enumerator_get_first(enumerator); \ - device; \ - device = device_enumerator_get_next(enumerator)) diff --git a/src/libsystemd/sd-device/device-enumerator.c b/src/libsystemd/sd-device/device-enumerator.c deleted file mode 100644 index 4a7a8b1f9e..0000000000 --- a/src/libsystemd/sd-device/device-enumerator.c +++ /dev/null @@ -1,986 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2014-2015 Tom Gundersen - - 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 "sd-device.h" - -#include "alloc-util.h" -#include "device-enumerator-private.h" -#include "device-util.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "prioq.h" -#include "set.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -#define DEVICE_ENUMERATE_MAX_DEPTH 256 - -typedef enum DeviceEnumerationType { - DEVICE_ENUMERATION_TYPE_DEVICES, - DEVICE_ENUMERATION_TYPE_SUBSYSTEMS, - _DEVICE_ENUMERATION_TYPE_MAX, - _DEVICE_ENUMERATION_TYPE_INVALID = -1, -} DeviceEnumerationType; - -struct sd_device_enumerator { - unsigned n_ref; - - DeviceEnumerationType type; - Prioq *devices; - bool scan_uptodate; - - Set *match_subsystem; - Set *nomatch_subsystem; - Hashmap *match_sysattr; - Hashmap *nomatch_sysattr; - Hashmap *match_property; - Set *match_sysname; - Set *match_tag; - sd_device *match_parent; - bool match_allow_uninitialized; -}; - -_public_ int sd_device_enumerator_new(sd_device_enumerator **ret) { - _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *enumerator = NULL; - - assert(ret); - - enumerator = new0(sd_device_enumerator, 1); - if (!enumerator) - return -ENOMEM; - - enumerator->n_ref = 1; - enumerator->type = _DEVICE_ENUMERATION_TYPE_INVALID; - - *ret = enumerator; - enumerator = NULL; - - return 0; -} - -_public_ sd_device_enumerator *sd_device_enumerator_ref(sd_device_enumerator *enumerator) { - assert_return(enumerator, NULL); - - assert_se((++ enumerator->n_ref) >= 2); - - return enumerator; -} - -_public_ sd_device_enumerator *sd_device_enumerator_unref(sd_device_enumerator *enumerator) { - if (enumerator && (-- enumerator->n_ref) == 0) { - sd_device *device; - - while ((device = prioq_pop(enumerator->devices))) - sd_device_unref(device); - - prioq_free(enumerator->devices); - - set_free_free(enumerator->match_subsystem); - set_free_free(enumerator->nomatch_subsystem); - hashmap_free_free_free(enumerator->match_sysattr); - hashmap_free_free_free(enumerator->nomatch_sysattr); - hashmap_free_free_free(enumerator->match_property); - set_free_free(enumerator->match_sysname); - set_free_free(enumerator->match_tag); - sd_device_unref(enumerator->match_parent); - - free(enumerator); - } - - return NULL; -} - -_public_ int sd_device_enumerator_add_match_subsystem(sd_device_enumerator *enumerator, const char *subsystem, int match) { - Set **set; - int r; - - assert_return(enumerator, -EINVAL); - assert_return(subsystem, -EINVAL); - - if (match) - set = &enumerator->match_subsystem; - else - set = &enumerator->nomatch_subsystem; - - r = set_ensure_allocated(set, NULL); - if (r < 0) - return r; - - r = set_put_strdup(*set, subsystem); - if (r < 0) - return r; - - enumerator->scan_uptodate = false; - - return 0; -} - -_public_ int sd_device_enumerator_add_match_sysattr(sd_device_enumerator *enumerator, const char *_sysattr, const char *_value, int match) { - _cleanup_free_ char *sysattr = NULL, *value = NULL; - Hashmap **hashmap; - int r; - - assert_return(enumerator, -EINVAL); - assert_return(_sysattr, -EINVAL); - - if (match) - hashmap = &enumerator->match_sysattr; - else - hashmap = &enumerator->nomatch_sysattr; - - r = hashmap_ensure_allocated(hashmap, NULL); - if (r < 0) - return r; - - sysattr = strdup(_sysattr); - if (!sysattr) - return -ENOMEM; - - if (_value) { - value = strdup(_value); - if (!value) - return -ENOMEM; - } - - r = hashmap_put(*hashmap, sysattr, value); - if (r < 0) - return r; - - sysattr = NULL; - value = NULL; - - enumerator->scan_uptodate = false; - - return 0; -} - -_public_ int sd_device_enumerator_add_match_property(sd_device_enumerator *enumerator, const char *_property, const char *_value) { - _cleanup_free_ char *property = NULL, *value = NULL; - int r; - - assert_return(enumerator, -EINVAL); - assert_return(_property, -EINVAL); - - r = hashmap_ensure_allocated(&enumerator->match_property, NULL); - if (r < 0) - return r; - - property = strdup(_property); - if (!property) - return -ENOMEM; - - if (_value) { - value = strdup(_value); - if (!value) - return -ENOMEM; - } - - r = hashmap_put(enumerator->match_property, property, value); - if (r < 0) - return r; - - property = NULL; - value = NULL; - - enumerator->scan_uptodate = false; - - return 0; -} - -_public_ int sd_device_enumerator_add_match_sysname(sd_device_enumerator *enumerator, const char *sysname) { - int r; - - assert_return(enumerator, -EINVAL); - assert_return(sysname, -EINVAL); - - r = set_ensure_allocated(&enumerator->match_sysname, NULL); - if (r < 0) - return r; - - r = set_put_strdup(enumerator->match_sysname, sysname); - if (r < 0) - return r; - - enumerator->scan_uptodate = false; - - return 0; -} - -_public_ int sd_device_enumerator_add_match_tag(sd_device_enumerator *enumerator, const char *tag) { - int r; - - assert_return(enumerator, -EINVAL); - assert_return(tag, -EINVAL); - - r = set_ensure_allocated(&enumerator->match_tag, NULL); - if (r < 0) - return r; - - r = set_put_strdup(enumerator->match_tag, tag); - if (r < 0) - return r; - - enumerator->scan_uptodate = false; - - return 0; -} - -_public_ int sd_device_enumerator_add_match_parent(sd_device_enumerator *enumerator, sd_device *parent) { - assert_return(enumerator, -EINVAL); - assert_return(parent, -EINVAL); - - sd_device_unref(enumerator->match_parent); - enumerator->match_parent = sd_device_ref(parent); - - enumerator->scan_uptodate = false; - - return 0; -} - -_public_ int sd_device_enumerator_allow_uninitialized(sd_device_enumerator *enumerator) { - assert_return(enumerator, -EINVAL); - - enumerator->match_allow_uninitialized = true; - - enumerator->scan_uptodate = false; - - return 0; -} - -int device_enumerator_add_match_is_initialized(sd_device_enumerator *enumerator) { - assert_return(enumerator, -EINVAL); - - enumerator->match_allow_uninitialized = false; - - enumerator->scan_uptodate = false; - - return 0; -} - -static int device_compare(const void *_a, const void *_b) { - sd_device *a = (sd_device *)_a, *b = (sd_device *)_b; - const char *devpath_a, *devpath_b, *sound_a; - bool delay_a, delay_b; - - assert_se(sd_device_get_devpath(a, &devpath_a) >= 0); - assert_se(sd_device_get_devpath(b, &devpath_b) >= 0); - - sound_a = strstr(devpath_a, "/sound/card"); - if (sound_a) { - /* For sound cards the control device must be enumerated last to - * make sure it's the final device node that gets ACLs applied. - * Applications rely on this fact and use ACL changes on the - * control node as an indicator that the ACL change of the - * entire sound card completed. The kernel makes this guarantee - * when creating those devices, and hence we should too when - * enumerating them. */ - sound_a += strlen("/sound/card"); - sound_a = strchr(sound_a, '/'); - - if (sound_a) { - unsigned prefix_len; - - prefix_len = sound_a - devpath_a; - - if (strncmp(devpath_a, devpath_b, prefix_len) == 0) { - const char *sound_b; - - sound_b = devpath_b + prefix_len; - - if (startswith(sound_a, "/controlC") && - !startswith(sound_b, "/contolC")) - return 1; - - if (!startswith(sound_a, "/controlC") && - startswith(sound_b, "/controlC")) - return -1; - } - } - } - - /* md and dm devices are enumerated after all other devices */ - delay_a = strstr(devpath_a, "/block/md") || strstr(devpath_a, "/block/dm-"); - delay_b = strstr(devpath_b, "/block/md") || strstr(devpath_b, "/block/dm-"); - if (delay_a != delay_b) - return delay_a - delay_b; - - return strcmp(devpath_a, devpath_b); -} - -int device_enumerator_add_device(sd_device_enumerator *enumerator, sd_device *device) { - int r; - - assert_return(enumerator, -EINVAL); - assert_return(device, -EINVAL); - - r = prioq_ensure_allocated(&enumerator->devices, device_compare); - if (r < 0) - return r; - - r = prioq_put(enumerator->devices, device, NULL); - if (r < 0) - return r; - - sd_device_ref(device); - - return 0; -} - -static bool match_sysattr_value(sd_device *device, const char *sysattr, const char *match_value) { - const char *value; - int r; - - assert(device); - assert(sysattr); - - r = sd_device_get_sysattr_value(device, sysattr, &value); - if (r < 0) - return false; - - if (!match_value) - return true; - - if (fnmatch(match_value, value, 0) == 0) - return true; - - return false; -} - -static bool match_sysattr(sd_device_enumerator *enumerator, sd_device *device) { - const char *sysattr; - const char *value; - Iterator i; - - assert(enumerator); - assert(device); - - HASHMAP_FOREACH_KEY(value, sysattr, enumerator->nomatch_sysattr, i) - if (match_sysattr_value(device, sysattr, value)) - return false; - - HASHMAP_FOREACH_KEY(value, sysattr, enumerator->match_sysattr, i) - if (!match_sysattr_value(device, sysattr, value)) - return false; - - return true; -} - -static bool match_property(sd_device_enumerator *enumerator, sd_device *device) { - const char *property; - const char *value; - Iterator i; - - assert(enumerator); - assert(device); - - if (hashmap_isempty(enumerator->match_property)) - return true; - - HASHMAP_FOREACH_KEY(value, property, enumerator->match_property, i) { - const char *property_dev, *value_dev; - - FOREACH_DEVICE_PROPERTY(device, property_dev, value_dev) { - if (fnmatch(property, property_dev, 0) != 0) - continue; - - if (!value && !value_dev) - return true; - - if (!value || !value_dev) - continue; - - if (fnmatch(value, value_dev, 0) == 0) - return true; - } - } - - return false; -} - -static bool match_tag(sd_device_enumerator *enumerator, sd_device *device) { - const char *tag; - Iterator i; - - assert(enumerator); - assert(device); - - SET_FOREACH(tag, enumerator->match_tag, i) - if (!sd_device_has_tag(device, tag)) - return false; - - return true; -} - -static bool match_parent(sd_device_enumerator *enumerator, sd_device *device) { - const char *devpath, *devpath_dev; - int r; - - assert(enumerator); - assert(device); - - if (!enumerator->match_parent) - return true; - - r = sd_device_get_devpath(enumerator->match_parent, &devpath); - assert(r >= 0); - - r = sd_device_get_devpath(device, &devpath_dev); - assert(r >= 0); - - return startswith(devpath_dev, devpath); -} - -static bool match_sysname(sd_device_enumerator *enumerator, const char *sysname) { - const char *sysname_match; - Iterator i; - - assert(enumerator); - assert(sysname); - - if (set_isempty(enumerator->match_sysname)) - return true; - - SET_FOREACH(sysname_match, enumerator->match_sysname, i) - if (fnmatch(sysname_match, sysname, 0) == 0) - return true; - - return false; -} - -static int enumerator_scan_dir_and_add_devices(sd_device_enumerator *enumerator, const char *basedir, const char *subdir1, const char *subdir2) { - _cleanup_closedir_ DIR *dir = NULL; - char *path; - struct dirent *dent; - int r = 0; - - assert(enumerator); - assert(basedir); - - path = strjoina("/sys/", basedir, "/"); - - if (subdir1) - path = strjoina(path, subdir1, "/"); - - if (subdir2) - path = strjoina(path, subdir2, "/"); - - dir = opendir(path); - if (!dir) - return -errno; - - FOREACH_DIRENT_ALL(dent, dir, return -errno) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - char syspath[strlen(path) + 1 + strlen(dent->d_name) + 1]; - dev_t devnum; - int ifindex, initialized, k; - - if (dent->d_name[0] == '.') - continue; - - if (!match_sysname(enumerator, dent->d_name)) - continue; - - (void)sprintf(syspath, "%s%s", path, dent->d_name); - - k = sd_device_new_from_syspath(&device, syspath); - if (k < 0) { - if (k != -ENODEV) - /* this is necessarily racey, so ignore missing devices */ - r = k; - - continue; - } - - k = sd_device_get_devnum(device, &devnum); - if (k < 0) { - r = k; - continue; - } - - k = sd_device_get_ifindex(device, &ifindex); - if (k < 0) { - r = k; - continue; - } - - k = sd_device_get_is_initialized(device, &initialized); - if (k < 0) { - r = k; - continue; - } - - /* - * All devices with a device node or network interfaces - * possibly need udev to adjust the device node permission - * or context, or rename the interface before it can be - * reliably used from other processes. - * - * For now, we can only check these types of devices, we - * might not store a database, and have no way to find out - * for all other types of devices. - */ - if (!enumerator->match_allow_uninitialized && - !initialized && - (major(devnum) > 0 || ifindex > 0)) - continue; - - if (!match_parent(enumerator, device)) - continue; - - if (!match_tag(enumerator, device)) - continue; - - if (!match_property(enumerator, device)) - continue; - - if (!match_sysattr(enumerator, device)) - continue; - - k = device_enumerator_add_device(enumerator, device); - if (k < 0) - r = k; - } - - return r; -} - -static bool match_subsystem(sd_device_enumerator *enumerator, const char *subsystem) { - const char *subsystem_match; - Iterator i; - - assert(enumerator); - - if (!subsystem) - return false; - - SET_FOREACH(subsystem_match, enumerator->nomatch_subsystem, i) - if (fnmatch(subsystem_match, subsystem, 0) == 0) - return false; - - if (set_isempty(enumerator->match_subsystem)) - return true; - - SET_FOREACH(subsystem_match, enumerator->match_subsystem, i) - if (fnmatch(subsystem_match, subsystem, 0) == 0) - return true; - - return false; -} - -static int enumerator_scan_dir(sd_device_enumerator *enumerator, const char *basedir, const char *subdir, const char *subsystem) { - _cleanup_closedir_ DIR *dir = NULL; - char *path; - struct dirent *dent; - int r = 0; - - path = strjoina("/sys/", basedir); - - dir = opendir(path); - if (!dir) - return -errno; - - log_debug(" device-enumerator: scanning %s", path); - - FOREACH_DIRENT_ALL(dent, dir, return -errno) { - int k; - - if (dent->d_name[0] == '.') - continue; - - if (!match_subsystem(enumerator, subsystem ? : dent->d_name)) - continue; - - k = enumerator_scan_dir_and_add_devices(enumerator, basedir, dent->d_name, subdir); - if (k < 0) - r = k; - } - - return r; -} - -static int enumerator_scan_devices_tag(sd_device_enumerator *enumerator, const char *tag) { - _cleanup_closedir_ DIR *dir = NULL; - char *path; - struct dirent *dent; - int r = 0; - - assert(enumerator); - assert(tag); - - path = strjoina("/run/udev/tags/", tag); - - dir = opendir(path); - if (!dir) { - if (errno == ENOENT) - return 0; - else { - log_error("sd-device-enumerator: could not open tags directory %s: %m", path); - return -errno; - } - } - - /* TODO: filter away subsystems? */ - - FOREACH_DIRENT_ALL(dent, dir, return -errno) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - const char *subsystem, *sysname; - int k; - - if (dent->d_name[0] == '.') - continue; - - k = sd_device_new_from_device_id(&device, dent->d_name); - if (k < 0) { - if (k != -ENODEV) - /* this is necessarily racy, so ignore missing devices */ - r = k; - - continue; - } - - k = sd_device_get_subsystem(device, &subsystem); - if (k < 0) { - r = k; - continue; - } - - if (!match_subsystem(enumerator, subsystem)) - continue; - - k = sd_device_get_sysname(device, &sysname); - if (k < 0) { - r = k; - continue; - } - - if (!match_sysname(enumerator, sysname)) - continue; - - if (!match_parent(enumerator, device)) - continue; - - if (!match_property(enumerator, device)) - continue; - - if (!match_sysattr(enumerator, device)) - continue; - - k = device_enumerator_add_device(enumerator, device); - if (k < 0) { - r = k; - continue; - } - } - - return r; -} - -static int enumerator_scan_devices_tags(sd_device_enumerator *enumerator) { - const char *tag; - Iterator i; - int r; - - assert(enumerator); - - SET_FOREACH(tag, enumerator->match_tag, i) { - r = enumerator_scan_devices_tag(enumerator, tag); - if (r < 0) - return r; - } - - return 0; -} - -static int parent_add_child(sd_device_enumerator *enumerator, const char *path) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - const char *subsystem, *sysname; - int r; - - r = sd_device_new_from_syspath(&device, path); - if (r == -ENODEV) - /* this is necessarily racy, so ignore missing devices */ - return 0; - else if (r < 0) - return r; - - r = sd_device_get_subsystem(device, &subsystem); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - - if (!match_subsystem(enumerator, subsystem)) - return 0; - - r = sd_device_get_sysname(device, &sysname); - if (r < 0) - return r; - - if (!match_sysname(enumerator, sysname)) - return 0; - - if (!match_property(enumerator, device)) - return 0; - - if (!match_sysattr(enumerator, device)) - return 0; - - r = device_enumerator_add_device(enumerator, device); - if (r < 0) - return r; - - return 1; -} - -static int parent_crawl_children(sd_device_enumerator *enumerator, const char *path, unsigned maxdepth) { - _cleanup_closedir_ DIR *dir = NULL; - struct dirent *dent; - int r = 0; - - dir = opendir(path); - if (!dir) { - log_debug("sd-device-enumerate: could not open parent directory %s: %m", path); - return -errno; - } - - FOREACH_DIRENT_ALL(dent, dir, return -errno) { - _cleanup_free_ char *child = NULL; - int k; - - if (dent->d_name[0] == '.') - continue; - - if (dent->d_type != DT_DIR) - continue; - - child = strjoin(path, "/", dent->d_name, NULL); - if (!child) - return -ENOMEM; - - k = parent_add_child(enumerator, child); - if (k < 0) - r = k; - - if (maxdepth > 0) - parent_crawl_children(enumerator, child, maxdepth - 1); - else - log_debug("device-enumerate: max depth reached, %s: ignoring devices", child); - } - - return r; -} - -static int enumerator_scan_devices_children(sd_device_enumerator *enumerator) { - const char *path; - int r = 0, k; - - r = sd_device_get_syspath(enumerator->match_parent, &path); - if (r < 0) - return r; - - k = parent_add_child(enumerator, path); - if (k < 0) - r = k; - - k = parent_crawl_children(enumerator, path, DEVICE_ENUMERATE_MAX_DEPTH); - if (k < 0) - r = k; - - return r; -} - -static int enumerator_scan_devices_all(sd_device_enumerator *enumerator) { - int r = 0; - - log_debug("device-enumerator: scan all dirs"); - - if (access("/sys/subsystem", F_OK) >= 0) { - /* we have /subsystem/, forget all the old stuff */ - r = enumerator_scan_dir(enumerator, "subsystem", "devices", NULL); - if (r < 0) - return log_debug_errno(r, "device-enumerator: failed to scan /sys/subsystem: %m"); - } else { - int k; - - k = enumerator_scan_dir(enumerator, "bus", "devices", NULL); - if (k < 0) { - log_debug_errno(k, "device-enumerator: failed to scan /sys/bus: %m"); - r = k; - } - - k = enumerator_scan_dir(enumerator, "class", NULL, NULL); - if (k < 0) { - log_debug_errno(k, "device-enumerator: failed to scan /sys/class: %m"); - r = k; - } - } - - return r; -} - -int device_enumerator_scan_devices(sd_device_enumerator *enumerator) { - sd_device *device; - int r; - - assert(enumerator); - - if (enumerator->scan_uptodate && - enumerator->type == DEVICE_ENUMERATION_TYPE_DEVICES) - return 0; - - while ((device = prioq_pop(enumerator->devices))) - sd_device_unref(device); - - if (!set_isempty(enumerator->match_tag)) { - r = enumerator_scan_devices_tags(enumerator); - if (r < 0) - return r; - } else if (enumerator->match_parent) { - r = enumerator_scan_devices_children(enumerator); - if (r < 0) - return r; - } else { - r = enumerator_scan_devices_all(enumerator); - if (r < 0) - return r; - } - - enumerator->scan_uptodate = true; - - return 0; -} - -_public_ sd_device *sd_device_enumerator_get_device_first(sd_device_enumerator *enumerator) { - int r; - - assert_return(enumerator, NULL); - - r = device_enumerator_scan_devices(enumerator); - if (r < 0) - return NULL; - - enumerator->type = DEVICE_ENUMERATION_TYPE_DEVICES; - - return prioq_peek(enumerator->devices); -} - -_public_ sd_device *sd_device_enumerator_get_device_next(sd_device_enumerator *enumerator) { - assert_return(enumerator, NULL); - - if (!enumerator->scan_uptodate || - enumerator->type != DEVICE_ENUMERATION_TYPE_DEVICES) - return NULL; - - sd_device_unref(prioq_pop(enumerator->devices)); - - return prioq_peek(enumerator->devices); -} - -int device_enumerator_scan_subsystems(sd_device_enumerator *enumerator) { - sd_device *device; - const char *subsysdir; - int r = 0, k; - - assert(enumerator); - - if (enumerator->scan_uptodate && - enumerator->type == DEVICE_ENUMERATION_TYPE_SUBSYSTEMS) - return 0; - - while ((device = prioq_pop(enumerator->devices))) - sd_device_unref(device); - - /* modules */ - if (match_subsystem(enumerator, "module")) { - k = enumerator_scan_dir_and_add_devices(enumerator, "module", NULL, NULL); - if (k < 0) { - log_debug_errno(k, "device-enumerator: failed to scan modules: %m"); - r = k; - } - } - - if (access("/sys/subsystem", F_OK) >= 0) - subsysdir = "subsystem"; - else - subsysdir = "bus"; - - /* subsystems (only buses support coldplug) */ - if (match_subsystem(enumerator, "subsystem")) { - k = enumerator_scan_dir_and_add_devices(enumerator, subsysdir, NULL, NULL); - if (k < 0) { - log_debug_errno(k, "device-enumerator: failed to scan subsystems: %m"); - r = k; - } - } - - /* subsystem drivers */ - if (match_subsystem(enumerator, "drivers")) { - k = enumerator_scan_dir(enumerator, subsysdir, "drivers", "drivers"); - if (k < 0) { - log_debug_errno(k, "device-enumerator: failed to scan drivers: %m"); - r = k; - } - } - - enumerator->scan_uptodate = true; - - return r; -} - -_public_ sd_device *sd_device_enumerator_get_subsystem_first(sd_device_enumerator *enumerator) { - int r; - - assert_return(enumerator, NULL); - - r = device_enumerator_scan_subsystems(enumerator); - if (r < 0) - return NULL; - - enumerator->type = DEVICE_ENUMERATION_TYPE_SUBSYSTEMS; - - return prioq_peek(enumerator->devices); -} - -_public_ sd_device *sd_device_enumerator_get_subsystem_next(sd_device_enumerator *enumerator) { - assert_return(enumerator, NULL); - - if (enumerator->scan_uptodate || - enumerator->type != DEVICE_ENUMERATION_TYPE_SUBSYSTEMS) - return NULL; - - sd_device_unref(prioq_pop(enumerator->devices)); - - return prioq_peek(enumerator->devices); -} - -sd_device *device_enumerator_get_first(sd_device_enumerator *enumerator) { - assert_return(enumerator, NULL); - - return prioq_peek(enumerator->devices); -} - -sd_device *device_enumerator_get_next(sd_device_enumerator *enumerator) { - assert_return(enumerator, NULL); - - sd_device_unref(prioq_pop(enumerator->devices)); - - return prioq_peek(enumerator->devices); -} diff --git a/src/libsystemd/sd-device/device-internal.h b/src/libsystemd/sd-device/device-internal.h deleted file mode 100644 index ab222e27de..0000000000 --- a/src/libsystemd/sd-device/device-internal.h +++ /dev/null @@ -1,126 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2014 Tom Gundersen - - 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 "hashmap.h" -#include "set.h" - -struct sd_device { - uint64_t n_ref; - - sd_device *parent; - bool parent_set; /* no need to try to reload parent */ - - OrderedHashmap *properties; - Iterator properties_iterator; - uint64_t properties_generation; /* changes whenever the properties are changed */ - uint64_t properties_iterator_generation; /* generation when iteration was started */ - - /* the subset of the properties that should be written to the db*/ - OrderedHashmap *properties_db; - - Hashmap *sysattr_values; /* cached sysattr values */ - - Set *sysattrs; /* names of sysattrs */ - Iterator sysattrs_iterator; - bool sysattrs_read; /* don't try to re-read sysattrs once read */ - - Set *tags; - Iterator tags_iterator; - uint64_t tags_generation; /* changes whenever the tags are changed */ - uint64_t tags_iterator_generation; /* generation when iteration was started */ - bool property_tags_outdated; /* need to update TAGS= property */ - - Set *devlinks; - Iterator devlinks_iterator; - uint64_t devlinks_generation; /* changes whenever the devlinks are changed */ - uint64_t devlinks_iterator_generation; /* generation when iteration was started */ - bool property_devlinks_outdated; /* need to update DEVLINKS= property */ - int devlink_priority; - - char **properties_strv; /* the properties hashmap as a strv */ - uint8_t *properties_nulstr; /* the same as a nulstr */ - size_t properties_nulstr_len; - bool properties_buf_outdated; /* need to reread hashmap */ - - int watch_handle; - - char *syspath; - const char *devpath; - const char *sysnum; - char *sysname; - bool sysname_set; /* don't reread sysname */ - - char *devtype; - int ifindex; - char *devname; - dev_t devnum; - - char *subsystem; - bool subsystem_set; /* don't reread subsystem */ - char *driver; - bool driver_set; /* don't reread driver */ - - char *id_filename; - - bool is_initialized; - uint64_t usec_initialized; - - mode_t devmode; - uid_t devuid; - gid_t devgid; - - bool uevent_loaded; /* don't reread uevent */ - bool db_loaded; /* don't reread db */ - - bool sealed; /* don't read more information from uevent/db */ - bool db_persist; /* don't clean up the db when switching from initrd to real root */ -}; - -typedef enum DeviceAction { - DEVICE_ACTION_ADD, - DEVICE_ACTION_REMOVE, - DEVICE_ACTION_CHANGE, - DEVICE_ACTION_MOVE, - DEVICE_ACTION_ONLINE, - DEVICE_ACTION_OFFLINE, - _DEVICE_ACTION_MAX, - _DEVICE_ACTION_INVALID = -1, -} DeviceAction; - -int device_new_aux(sd_device **ret); -int device_add_property_aux(sd_device *device, const char *key, const char *value, bool db); -int device_add_property_internal(sd_device *device, const char *key, const char *value); -int device_read_uevent_file(sd_device *device); -int device_read_db_aux(sd_device *device, bool force); - -int device_set_syspath(sd_device *device, const char *_syspath, bool verify); -int device_set_ifindex(sd_device *device, const char *ifindex); -int device_set_devmode(sd_device *device, const char *devmode); -int device_set_devname(sd_device *device, const char *_devname); -int device_set_devtype(sd_device *device, const char *_devtype); -int device_set_devnum(sd_device *device, const char *major, const char *minor); -int device_set_subsystem(sd_device *device, const char *_subsystem); -int device_set_driver(sd_device *device, const char *_driver); -int device_set_usec_initialized(sd_device *device, const char *initialized); - -DeviceAction device_action_from_string(const char *s) _pure_; -const char *device_action_to_string(DeviceAction a) _const_; diff --git a/src/libsystemd/sd-device/device-private.c b/src/libsystemd/sd-device/device-private.c deleted file mode 100644 index 9082d377f4..0000000000 --- a/src/libsystemd/sd-device/device-private.c +++ /dev/null @@ -1,1119 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2014 Tom Gundersen - - 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 -#include - -#include "sd-device.h" - -#include "alloc-util.h" -#include "device-internal.h" -#include "device-private.h" -#include "device-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "hashmap.h" -#include "macro.h" -#include "mkdir.h" -#include "parse-util.h" -#include "path-util.h" -#include "refcnt.h" -#include "set.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "strxcpyx.h" -#include "user-util.h" -#include "util.h" - -int device_add_property(sd_device *device, const char *key, const char *value) { - int r; - - assert(device); - assert(key); - - r = device_add_property_aux(device, key, value, false); - if (r < 0) - return r; - - if (key[0] != '.') { - r = device_add_property_aux(device, key, value, true); - if (r < 0) - return r; - } - - return 0; -} - -static int device_add_property_internal_from_string(sd_device *device, const char *str) { - _cleanup_free_ char *key = NULL; - char *value; - - assert(device); - assert(str); - - key = strdup(str); - if (!key) - return -ENOMEM; - - value = strchr(key, '='); - if (!value) - return -EINVAL; - - *value = '\0'; - - if (isempty(++value)) - value = NULL; - - return device_add_property_internal(device, key, value); -} - -static int handle_db_line(sd_device *device, char key, const char *value) { - char *path; - int r; - - assert(device); - assert(value); - - switch (key) { - case 'S': - path = strjoina("/dev/", value); - r = device_add_devlink(device, path); - if (r < 0) - return r; - - break; - case 'L': - r = safe_atoi(value, &device->devlink_priority); - if (r < 0) - return r; - - break; - case 'E': - r = device_add_property_internal_from_string(device, value); - if (r < 0) - return r; - - break; - case 'G': - r = device_add_tag(device, value); - if (r < 0) - return r; - - break; - case 'W': - r = safe_atoi(value, &device->watch_handle); - if (r < 0) - return r; - - break; - case 'I': - r = device_set_usec_initialized(device, value); - if (r < 0) - return r; - - break; - default: - log_debug("device db: unknown key '%c'", key); - } - - return 0; -} - -void device_set_devlink_priority(sd_device *device, int priority) { - assert(device); - - device->devlink_priority = priority; -} - -void device_set_is_initialized(sd_device *device) { - assert(device); - - device->is_initialized = true; -} - -int device_ensure_usec_initialized(sd_device *device, sd_device *device_old) { - char num[DECIMAL_STR_MAX(usec_t)]; - usec_t usec_initialized; - int r; - - assert(device); - - if (device_old && device_old->usec_initialized > 0) - usec_initialized = device_old->usec_initialized; - else - usec_initialized = now(CLOCK_MONOTONIC); - - r = snprintf(num, sizeof(num), USEC_FMT, usec_initialized); - if (r < 0) - return -errno; - - r = device_set_usec_initialized(device, num); - if (r < 0) - return r; - - return 0; -} - -static int device_read_db(sd_device *device) { - _cleanup_free_ char *db = NULL; - char *path; - const char *id, *value; - char key; - size_t db_len; - unsigned i; - int r; - - enum { - PRE_KEY, - KEY, - PRE_VALUE, - VALUE, - INVALID_LINE, - } state = PRE_KEY; - - assert(device); - - if (device->db_loaded || device->sealed) - return 0; - - r = device_get_id_filename(device, &id); - if (r < 0) - return r; - - path = strjoina("/run/udev/data/", id); - - r = read_full_file(path, &db, &db_len); - if (r < 0) { - if (r == -ENOENT) - return 0; - else - return log_debug_errno(r, "sd-device: failed to read db '%s': %m", path); - } - - /* devices with a database entry are initialized */ - device_set_is_initialized(device); - - for (i = 0; i < db_len; i++) { - switch (state) { - case PRE_KEY: - if (!strchr(NEWLINE, db[i])) { - key = db[i]; - - state = KEY; - } - - break; - case KEY: - if (db[i] != ':') { - log_debug("sd-device: ignoring invalid db entry with key '%c'", key); - - state = INVALID_LINE; - } else { - db[i] = '\0'; - - state = PRE_VALUE; - } - - break; - case PRE_VALUE: - value = &db[i]; - - state = VALUE; - - break; - case INVALID_LINE: - if (strchr(NEWLINE, db[i])) - state = PRE_KEY; - - break; - case VALUE: - if (strchr(NEWLINE, db[i])) { - db[i] = '\0'; - r = handle_db_line(device, key, value); - if (r < 0) - log_debug_errno(r, "sd-device: failed to handle db entry '%c:%s': %m", key, value); - - state = PRE_KEY; - } - - break; - default: - assert_not_reached("invalid state when parsing db"); - } - } - - device->db_loaded = true; - - return 0; -} - -uint64_t device_get_properties_generation(sd_device *device) { - assert(device); - - return device->properties_generation; -} - -uint64_t device_get_tags_generation(sd_device *device) { - assert(device); - - return device->tags_generation; -} - -uint64_t device_get_devlinks_generation(sd_device *device) { - assert(device); - - return device->devlinks_generation; -} - -int device_get_devnode_mode(sd_device *device, mode_t *mode) { - int r; - - assert(device); - assert(mode); - - r = device_read_db(device); - if (r < 0) - return r; - - *mode = device->devmode; - - return 0; -} - -int device_get_devnode_uid(sd_device *device, uid_t *uid) { - int r; - - assert(device); - assert(uid); - - r = device_read_db(device); - if (r < 0) - return r; - - *uid = device->devuid; - - return 0; -} - -static int device_set_devuid(sd_device *device, const char *uid) { - unsigned u; - int r; - - assert(device); - assert(uid); - - r = safe_atou(uid, &u); - if (r < 0) - return r; - - r = device_add_property_internal(device, "DEVUID", uid); - if (r < 0) - return r; - - device->devuid = u; - - return 0; -} - -int device_get_devnode_gid(sd_device *device, gid_t *gid) { - int r; - - assert(device); - assert(gid); - - r = device_read_db(device); - if (r < 0) - return r; - - *gid = device->devgid; - - return 0; -} - -static int device_set_devgid(sd_device *device, const char *gid) { - unsigned g; - int r; - - assert(device); - assert(gid); - - r = safe_atou(gid, &g); - if (r < 0) - return r; - - r = device_add_property_internal(device, "DEVGID", gid); - if (r < 0) - return r; - - device->devgid = g; - - return 0; -} - -static int device_amend(sd_device *device, const char *key, const char *value) { - int r; - - assert(device); - assert(key); - assert(value); - - if (streq(key, "DEVPATH")) { - char *path; - - path = strjoina("/sys", value); - - /* the caller must verify or trust this data (e.g., if it comes from the kernel) */ - r = device_set_syspath(device, path, false); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set syspath to '%s': %m", path); - } else if (streq(key, "SUBSYSTEM")) { - r = device_set_subsystem(device, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set subsystem to '%s': %m", value); - } else if (streq(key, "DEVTYPE")) { - r = device_set_devtype(device, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set devtype to '%s': %m", value); - } else if (streq(key, "DEVNAME")) { - r = device_set_devname(device, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set devname to '%s': %m", value); - } else if (streq(key, "USEC_INITIALIZED")) { - r = device_set_usec_initialized(device, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set usec-initialized to '%s': %m", value); - } else if (streq(key, "DRIVER")) { - r = device_set_driver(device, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set driver to '%s': %m", value); - } else if (streq(key, "IFINDEX")) { - r = device_set_ifindex(device, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set ifindex to '%s': %m", value); - } else if (streq(key, "DEVMODE")) { - r = device_set_devmode(device, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set devmode to '%s': %m", value); - } else if (streq(key, "DEVUID")) { - r = device_set_devuid(device, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set devuid to '%s': %m", value); - } else if (streq(key, "DEVGID")) { - r = device_set_devgid(device, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set devgid to '%s': %m", value); - } else if (streq(key, "DEVLINKS")) { - const char *word, *state; - size_t l; - - FOREACH_WORD(word, l, value, state) { - char devlink[l + 1]; - - strncpy(devlink, word, l); - devlink[l] = '\0'; - - r = device_add_devlink(device, devlink); - if (r < 0) - return log_debug_errno(r, "sd-device: could not add devlink '%s': %m", devlink); - } - } else if (streq(key, "TAGS")) { - const char *word, *state; - size_t l; - - FOREACH_WORD_SEPARATOR(word, l, value, ":", state) { - char tag[l + 1]; - - (void)strncpy(tag, word, l); - tag[l] = '\0'; - - r = device_add_tag(device, tag); - if (r < 0) - return log_debug_errno(r, "sd-device: could not add tag '%s': %m", tag); - } - } else { - r = device_add_property_internal(device, key, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not add property '%s=%s': %m", key, value); - } - - return 0; -} - -static const char* const device_action_table[_DEVICE_ACTION_MAX] = { - [DEVICE_ACTION_ADD] = "add", - [DEVICE_ACTION_REMOVE] = "remove", - [DEVICE_ACTION_CHANGE] = "change", - [DEVICE_ACTION_MOVE] = "move", - [DEVICE_ACTION_ONLINE] = "online", - [DEVICE_ACTION_OFFLINE] = "offline", -}; - -DEFINE_STRING_TABLE_LOOKUP(device_action, DeviceAction); - -static int device_append(sd_device *device, char *key, const char **_major, const char **_minor, uint64_t *_seqnum, - DeviceAction *_action) { - DeviceAction action = _DEVICE_ACTION_INVALID; - uint64_t seqnum = 0; - const char *major = NULL, *minor = NULL; - char *value; - int r; - - assert(device); - assert(key); - assert(_major); - assert(_minor); - assert(_seqnum); - assert(_action); - - value = strchr(key, '='); - if (!value) { - log_debug("sd-device: not a key-value pair: '%s'", key); - return -EINVAL; - } - - *value = '\0'; - - value++; - - if (streq(key, "MAJOR")) - major = value; - else if (streq(key, "MINOR")) - minor = value; - else { - if (streq(key, "ACTION")) { - action = device_action_from_string(value); - if (action == _DEVICE_ACTION_INVALID) - return -EINVAL; - } else if (streq(key, "SEQNUM")) { - r = safe_atou64(value, &seqnum); - if (r < 0) - return r; - else if (seqnum == 0) - /* kernel only sends seqnum > 0 */ - return -EINVAL; - } - - r = device_amend(device, key, value); - if (r < 0) - return r; - } - - if (major != 0) - *_major = major; - - if (minor != 0) - *_minor = minor; - - if (action != _DEVICE_ACTION_INVALID) - *_action = action; - - if (seqnum > 0) - *_seqnum = seqnum; - - return 0; -} - -void device_seal(sd_device *device) { - assert(device); - - device->sealed = true; -} - -static int device_verify(sd_device *device, DeviceAction action, uint64_t seqnum) { - assert(device); - - if (!device->devpath || !device->subsystem || action == _DEVICE_ACTION_INVALID || seqnum == 0) { - log_debug("sd-device: device created from strv lacks devpath, subsystem, action or seqnum"); - return -EINVAL; - } - - device->sealed = true; - - return 0; -} - -int device_new_from_strv(sd_device **ret, char **strv) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - char **key; - const char *major = NULL, *minor = NULL; - DeviceAction action = _DEVICE_ACTION_INVALID; - uint64_t seqnum; - int r; - - assert(ret); - assert(strv); - - r = device_new_aux(&device); - if (r < 0) - return r; - - STRV_FOREACH(key, strv) { - r = device_append(device, *key, &major, &minor, &seqnum, &action); - if (r < 0) - return r; - } - - if (major) { - r = device_set_devnum(device, major, minor); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set devnum %s:%s: %m", major, minor); - } - - r = device_verify(device, action, seqnum); - if (r < 0) - return r; - - *ret = device; - device = NULL; - - return 0; -} - -int device_new_from_nulstr(sd_device **ret, uint8_t *nulstr, size_t len) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - const char *major = NULL, *minor = NULL; - DeviceAction action = _DEVICE_ACTION_INVALID; - uint64_t seqnum; - unsigned i = 0; - int r; - - assert(ret); - assert(nulstr); - assert(len); - - r = device_new_aux(&device); - if (r < 0) - return r; - - while (i < len) { - char *key; - const char *end; - - key = (char*)&nulstr[i]; - end = memchr(key, '\0', len - i); - if (!end) { - log_debug("sd-device: failed to parse nulstr"); - return -EINVAL; - } - i += end - key + 1; - - r = device_append(device, key, &major, &minor, &seqnum, &action); - if (r < 0) - return r; - } - - if (major) { - r = device_set_devnum(device, major, minor); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set devnum %s:%s: %m", major, minor); - } - - r = device_verify(device, action, seqnum); - if (r < 0) - return r; - - *ret = device; - device = NULL; - - return 0; -} - -static int device_update_properties_bufs(sd_device *device) { - const char *val, *prop; - _cleanup_free_ char **buf_strv = NULL; - _cleanup_free_ uint8_t *buf_nulstr = NULL; - size_t allocated_nulstr = 0; - size_t nulstr_len = 0, num = 0, i = 0; - - assert(device); - - if (!device->properties_buf_outdated) - return 0; - - FOREACH_DEVICE_PROPERTY(device, prop, val) { - size_t len = 0; - - len = strlen(prop) + 1 + strlen(val); - - buf_nulstr = GREEDY_REALLOC0(buf_nulstr, allocated_nulstr, nulstr_len + len + 2); - if (!buf_nulstr) - return -ENOMEM; - - strscpyl((char *)buf_nulstr + nulstr_len, len + 1, prop, "=", val, NULL); - nulstr_len += len + 1; - ++num; - } - - /* build buf_strv from buf_nulstr */ - buf_strv = new0(char *, num + 1); - if (!buf_strv) - return -ENOMEM; - - NULSTR_FOREACH(val, (char*) buf_nulstr) { - buf_strv[i] = (char *) val; - assert(i < num); - i++; - } - - free(device->properties_nulstr); - device->properties_nulstr = buf_nulstr; - buf_nulstr = NULL; - device->properties_nulstr_len = nulstr_len; - free(device->properties_strv); - device->properties_strv = buf_strv; - buf_strv = NULL; - - device->properties_buf_outdated = false; - - return 0; -} - -int device_get_properties_nulstr(sd_device *device, const uint8_t **nulstr, size_t *len) { - int r; - - assert(device); - assert(nulstr); - assert(len); - - r = device_update_properties_bufs(device); - if (r < 0) - return r; - - *nulstr = device->properties_nulstr; - *len = device->properties_nulstr_len; - - return 0; -} - -int device_get_properties_strv(sd_device *device, char ***strv) { - int r; - - assert(device); - assert(strv); - - r = device_update_properties_bufs(device); - if (r < 0) - return r; - - *strv = device->properties_strv; - - return 0; -} - -int device_get_devlink_priority(sd_device *device, int *priority) { - int r; - - assert(device); - assert(priority); - - r = device_read_db(device); - if (r < 0) - return r; - - *priority = device->devlink_priority; - - return 0; -} - -int device_get_watch_handle(sd_device *device, int *handle) { - int r; - - assert(device); - assert(handle); - - r = device_read_db(device); - if (r < 0) - return r; - - *handle = device->watch_handle; - - return 0; -} - -void device_set_watch_handle(sd_device *device, int handle) { - assert(device); - - device->watch_handle = handle; -} - -int device_rename(sd_device *device, const char *name) { - _cleanup_free_ char *dirname = NULL; - char *new_syspath; - const char *interface; - int r; - - assert(device); - assert(name); - - dirname = dirname_malloc(device->syspath); - if (!dirname) - return -ENOMEM; - - new_syspath = strjoina(dirname, "/", name); - - /* the user must trust that the new name is correct */ - r = device_set_syspath(device, new_syspath, false); - if (r < 0) - return r; - - r = sd_device_get_property_value(device, "INTERFACE", &interface); - if (r >= 0) { - r = device_add_property_internal(device, "INTERFACE", name); - if (r < 0) - return r; - - /* like DEVPATH_OLD, INTERFACE_OLD is not saved to the db, but only stays around for the current event */ - r = device_add_property_internal(device, "INTERFACE_OLD", interface); - if (r < 0) - return r; - } else if (r != -ENOENT) - return r; - - return 0; -} - -int device_shallow_clone(sd_device *old_device, sd_device **new_device) { - _cleanup_(sd_device_unrefp) sd_device *ret = NULL; - int r; - - assert(old_device); - assert(new_device); - - r = device_new_aux(&ret); - if (r < 0) - return r; - - r = device_set_syspath(ret, old_device->syspath, false); - if (r < 0) - return r; - - r = device_set_subsystem(ret, old_device->subsystem); - if (r < 0) - return r; - - ret->devnum = old_device->devnum; - - *new_device = ret; - ret = NULL; - - return 0; -} - -int device_clone_with_db(sd_device *old_device, sd_device **new_device) { - _cleanup_(sd_device_unrefp) sd_device *ret = NULL; - int r; - - assert(old_device); - assert(new_device); - - r = device_shallow_clone(old_device, &ret); - if (r < 0) - return r; - - r = device_read_db(ret); - if (r < 0) - return r; - - ret->sealed = true; - - *new_device = ret; - ret = NULL; - - return 0; -} - -int device_new_from_synthetic_event(sd_device **new_device, const char *syspath, const char *action) { - _cleanup_(sd_device_unrefp) sd_device *ret = NULL; - int r; - - assert(new_device); - assert(syspath); - assert(action); - - r = sd_device_new_from_syspath(&ret, syspath); - if (r < 0) - return r; - - r = device_read_uevent_file(ret); - if (r < 0) - return r; - - r = device_add_property_internal(ret, "ACTION", action); - if (r < 0) - return r; - - *new_device = ret; - ret = NULL; - - return 0; -} - -int device_copy_properties(sd_device *device_dst, sd_device *device_src) { - const char *property, *value; - int r; - - assert(device_dst); - assert(device_src); - - FOREACH_DEVICE_PROPERTY(device_src, property, value) { - r = device_add_property(device_dst, property, value); - if (r < 0) - return r; - } - - return 0; -} - -void device_cleanup_tags(sd_device *device) { - assert(device); - - set_free_free(device->tags); - device->tags = NULL; - device->property_tags_outdated = true; - device->tags_generation++; -} - -void device_cleanup_devlinks(sd_device *device) { - assert(device); - - set_free_free(device->devlinks); - device->devlinks = NULL; - device->property_devlinks_outdated = true; - device->devlinks_generation++; -} - -void device_remove_tag(sd_device *device, const char *tag) { - assert(device); - assert(tag); - - free(set_remove(device->tags, tag)); - device->property_tags_outdated = true; - device->tags_generation++; -} - -static int device_tag(sd_device *device, const char *tag, bool add) { - const char *id; - char *path; - int r; - - assert(device); - assert(tag); - - r = device_get_id_filename(device, &id); - if (r < 0) - return r; - - path = strjoina("/run/udev/tags/", tag, "/", id); - - if (add) { - r = touch_file(path, true, USEC_INFINITY, UID_INVALID, GID_INVALID, 0444); - if (r < 0) - return r; - } else { - r = unlink(path); - if (r < 0 && errno != ENOENT) - return -errno; - } - - return 0; -} - -int device_tag_index(sd_device *device, sd_device *device_old, bool add) { - const char *tag; - int r = 0, k; - - if (add && device_old) { - /* delete possible left-over tags */ - FOREACH_DEVICE_TAG(device_old, tag) { - if (!sd_device_has_tag(device, tag)) { - k = device_tag(device_old, tag, false); - if (r >= 0 && k < 0) - r = k; - } - } - } - - FOREACH_DEVICE_TAG(device, tag) { - k = device_tag(device, tag, add); - if (r >= 0 && k < 0) - r = k; - } - - return r; -} - -static bool device_has_info(sd_device *device) { - assert(device); - - if (!set_isempty(device->devlinks)) - return true; - - if (device->devlink_priority != 0) - return true; - - if (!ordered_hashmap_isempty(device->properties_db)) - return true; - - if (!set_isempty(device->tags)) - return true; - - if (device->watch_handle >= 0) - return true; - - return false; -} - -void device_set_db_persist(sd_device *device) { - assert(device); - - device->db_persist = true; -} - -int device_update_db(sd_device *device) { - const char *id; - char *path; - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *path_tmp = NULL; - bool has_info; - int r; - - assert(device); - - has_info = device_has_info(device); - - r = device_get_id_filename(device, &id); - if (r < 0) - return r; - - path = strjoina("/run/udev/data/", id); - - /* do not store anything for otherwise empty devices */ - if (!has_info && major(device->devnum) == 0 && device->ifindex == 0) { - r = unlink(path); - if (r < 0 && errno != ENOENT) - return -errno; - - return 0; - } - - /* write a database file */ - r = mkdir_parents(path, 0755); - if (r < 0) - return r; - - r = fopen_temporary(path, &f, &path_tmp); - if (r < 0) - return r; - - /* - * set 'sticky' bit to indicate that we should not clean the - * database when we transition from initramfs to the real root - */ - if (device->db_persist) { - r = fchmod(fileno(f), 01644); - if (r < 0) { - r = -errno; - goto fail; - } - } else { - r = fchmod(fileno(f), 0644); - if (r < 0) { - r = -errno; - goto fail; - } - } - - if (has_info) { - const char *property, *value, *tag; - Iterator i; - - if (major(device->devnum) > 0) { - const char *devlink; - - FOREACH_DEVICE_DEVLINK(device, devlink) - fprintf(f, "S:%s\n", devlink + strlen("/dev/")); - - if (device->devlink_priority != 0) - fprintf(f, "L:%i\n", device->devlink_priority); - - if (device->watch_handle >= 0) - fprintf(f, "W:%i\n", device->watch_handle); - } - - if (device->usec_initialized > 0) - fprintf(f, "I:"USEC_FMT"\n", device->usec_initialized); - - ORDERED_HASHMAP_FOREACH_KEY(value, property, device->properties_db, i) - fprintf(f, "E:%s=%s\n", property, value); - - FOREACH_DEVICE_TAG(device, tag) - fprintf(f, "G:%s\n", tag); - } - - r = fflush_and_check(f); - if (r < 0) - goto fail; - - r = rename(path_tmp, path); - if (r < 0) { - r = -errno; - goto fail; - } - - log_debug("created %s file '%s' for '%s'", has_info ? "db" : "empty", - path, device->devpath); - - return 0; - -fail: - (void) unlink(path); - (void) unlink(path_tmp); - - return log_error_errno(r, "failed to create %s file '%s' for '%s'", has_info ? "db" : "empty", path, device->devpath); -} - -int device_delete_db(sd_device *device) { - const char *id; - char *path; - int r; - - assert(device); - - r = device_get_id_filename(device, &id); - if (r < 0) - return r; - - path = strjoina("/run/udev/data/", id); - - r = unlink(path); - if (r < 0 && errno != ENOENT) - return -errno; - - return 0; -} - -int device_read_db_force(sd_device *device) { - assert(device); - - return device_read_db_aux(device, true); -} diff --git a/src/libsystemd/sd-device/device-private.h b/src/libsystemd/sd-device/device-private.h deleted file mode 100644 index 29b3e155fb..0000000000 --- a/src/libsystemd/sd-device/device-private.h +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 -#include - -#include "sd-device.h" - -int device_new_from_nulstr(sd_device **ret, uint8_t *nulstr, size_t len); -int device_new_from_strv(sd_device **ret, char **strv); - -int device_get_id_filename(sd_device *device, const char **ret); - -int device_get_devlink_priority(sd_device *device, int *priority); -int device_get_watch_handle(sd_device *device, int *handle); -int device_get_devnode_mode(sd_device *device, mode_t *mode); -int device_get_devnode_uid(sd_device *device, uid_t *uid); -int device_get_devnode_gid(sd_device *device, gid_t *gid); - -void device_seal(sd_device *device); -void device_set_is_initialized(sd_device *device); -void device_set_watch_handle(sd_device *device, int fd); -void device_set_db_persist(sd_device *device); -void device_set_devlink_priority(sd_device *device, int priority); -int device_ensure_usec_initialized(sd_device *device, sd_device *device_old); -int device_add_devlink(sd_device *device, const char *devlink); -int device_add_property(sd_device *device, const char *property, const char *value); -int device_add_tag(sd_device *device, const char *tag); -void device_remove_tag(sd_device *device, const char *tag); -void device_cleanup_tags(sd_device *device); -void device_cleanup_devlinks(sd_device *device); - -uint64_t device_get_properties_generation(sd_device *device); -uint64_t device_get_tags_generation(sd_device *device); -uint64_t device_get_devlinks_generation(sd_device *device); - -int device_get_properties_nulstr(sd_device *device, const uint8_t **nulstr, size_t *len); -int device_get_properties_strv(sd_device *device, char ***strv); - -int device_rename(sd_device *device, const char *name); -int device_shallow_clone(sd_device *old_device, sd_device **new_device); -int device_clone_with_db(sd_device *old_device, sd_device **new_device); -int device_copy_properties(sd_device *device_dst, sd_device *device_src); -int device_new_from_synthetic_event(sd_device **new_device, const char *syspath, const char *action); - -int device_tag_index(sd_device *dev, sd_device *dev_old, bool add); -int device_update_db(sd_device *device); -int device_delete_db(sd_device *device); -int device_read_db_force(sd_device *device); diff --git a/src/libsystemd/sd-device/device-util.h b/src/libsystemd/sd-device/device-util.h deleted file mode 100644 index 5b42e11de6..0000000000 --- a/src/libsystemd/sd-device/device-util.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014-2015 Tom Gundersen - - 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" - -#define FOREACH_DEVICE_PROPERTY(device, key, value) \ - for (key = sd_device_get_property_first(device, &(value)); \ - key; \ - key = sd_device_get_property_next(device, &(value))) - -#define FOREACH_DEVICE_TAG(device, tag) \ - for (tag = sd_device_get_tag_first(device); \ - tag; \ - tag = sd_device_get_tag_next(device)) - -#define FOREACH_DEVICE_SYSATTR(device, attr) \ - for (attr = sd_device_get_sysattr_first(device); \ - attr; \ - attr = sd_device_get_sysattr_next(device)) - -#define FOREACH_DEVICE_DEVLINK(device, devlink) \ - for (devlink = sd_device_get_devlink_first(device); \ - devlink; \ - devlink = sd_device_get_devlink_next(device)) - -#define FOREACH_DEVICE(enumerator, device) \ - for (device = sd_device_enumerator_get_device_first(enumerator); \ - device; \ - device = sd_device_enumerator_get_device_next(enumerator)) - -#define FOREACH_SUBSYSTEM(enumerator, device) \ - for (device = sd_device_enumerator_get_subsystem_first(enumerator); \ - device; \ - device = sd_device_enumerator_get_subsystem_next(enumerator)) diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c deleted file mode 100644 index b1c3d5f228..0000000000 --- a/src/libsystemd/sd-device/sd-device.c +++ /dev/null @@ -1,1884 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2014 Tom Gundersen - - 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 -#include - -#include "sd-device.h" - -#include "alloc-util.h" -#include "device-internal.h" -#include "device-private.h" -#include "device-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "hashmap.h" -#include "macro.h" -#include "parse-util.h" -#include "path-util.h" -#include "set.h" -#include "stat-util.h" -#include "string-util.h" -#include "strv.h" -#include "strxcpyx.h" -#include "util.h" - -int device_new_aux(sd_device **ret) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - - assert(ret); - - device = new0(sd_device, 1); - if (!device) - return -ENOMEM; - - device->n_ref = 1; - device->watch_handle = -1; - - *ret = device; - device = NULL; - - return 0; -} - -_public_ sd_device *sd_device_ref(sd_device *device) { - if (device) - assert_se(++ device->n_ref >= 2); - - return device; -} - -_public_ sd_device *sd_device_unref(sd_device *device) { - if (device && -- device->n_ref == 0) { - sd_device_unref(device->parent); - free(device->syspath); - free(device->sysname); - free(device->devtype); - free(device->devname); - free(device->subsystem); - free(device->driver); - free(device->id_filename); - free(device->properties_strv); - free(device->properties_nulstr); - - ordered_hashmap_free_free_free(device->properties); - ordered_hashmap_free_free_free(device->properties_db); - hashmap_free_free_free(device->sysattr_values); - set_free_free(device->sysattrs); - set_free_free(device->tags); - set_free_free(device->devlinks); - - free(device); - } - - return NULL; -} - -int device_add_property_aux(sd_device *device, const char *_key, const char *_value, bool db) { - OrderedHashmap **properties; - - assert(device); - assert(_key); - - if (db) - properties = &device->properties_db; - else - properties = &device->properties; - - if (_value) { - _cleanup_free_ char *key = NULL, *value = NULL, *old_key = NULL, *old_value = NULL; - int r; - - r = ordered_hashmap_ensure_allocated(properties, &string_hash_ops); - if (r < 0) - return r; - - key = strdup(_key); - if (!key) - return -ENOMEM; - - value = strdup(_value); - if (!value) - return -ENOMEM; - - old_value = ordered_hashmap_get2(*properties, key, (void**) &old_key); - - r = ordered_hashmap_replace(*properties, key, value); - if (r < 0) - return r; - - key = NULL; - value = NULL; - } else { - _cleanup_free_ char *key = NULL; - _cleanup_free_ char *value = NULL; - - value = ordered_hashmap_remove2(*properties, _key, (void**) &key); - } - - if (!db) { - device->properties_generation++; - device->properties_buf_outdated = true; - } - - return 0; -} - -int device_add_property_internal(sd_device *device, const char *key, const char *value) { - return device_add_property_aux(device, key, value, false); -} - -int device_set_syspath(sd_device *device, const char *_syspath, bool verify) { - _cleanup_free_ char *syspath = NULL; - const char *devpath; - int r; - - assert(device); - assert(_syspath); - - /* must be a subdirectory of /sys */ - if (!path_startswith(_syspath, "/sys/")) { - log_debug("sd-device: syspath '%s' is not a subdirectory of /sys", _syspath); - return -EINVAL; - } - - if (verify) { - r = readlink_and_canonicalize(_syspath, &syspath); - if (r == -ENOENT) - /* the device does not exist (any more?) */ - return -ENODEV; - else if (r == -EINVAL) { - /* not a symlink */ - syspath = canonicalize_file_name(_syspath); - if (!syspath) { - if (errno == ENOENT) - /* the device does not exist (any more?) */ - return -ENODEV; - - return log_debug_errno(errno, "sd-device: could not canonicalize '%s': %m", _syspath); - } - } else if (r < 0) { - log_debug_errno(r, "sd-device: could not get target of '%s': %m", _syspath); - return r; - } - - if (path_startswith(syspath, "/sys/devices/")) { - char *path; - - /* all 'devices' require an 'uevent' file */ - path = strjoina(syspath, "/uevent"); - r = access(path, F_OK); - if (r < 0) { - if (errno == ENOENT) - /* this is not a valid device */ - return -ENODEV; - - log_debug("sd-device: %s does not have an uevent file: %m", syspath); - return -errno; - } - } else { - /* everything else just just needs to be a directory */ - if (!is_dir(syspath, false)) - return -ENODEV; - } - } else { - syspath = strdup(_syspath); - if (!syspath) - return -ENOMEM; - } - - devpath = syspath + strlen("/sys"); - - r = device_add_property_internal(device, "DEVPATH", devpath); - if (r < 0) - return r; - - free(device->syspath); - device->syspath = syspath; - syspath = NULL; - - device->devpath = devpath; - - return 0; -} - -_public_ int sd_device_new_from_syspath(sd_device **ret, const char *syspath) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - int r; - - assert_return(ret, -EINVAL); - assert_return(syspath, -EINVAL); - - r = device_new_aux(&device); - if (r < 0) - return r; - - r = device_set_syspath(device, syspath, true); - if (r < 0) - return r; - - *ret = device; - device = NULL; - - return 0; -} - -_public_ int sd_device_new_from_devnum(sd_device **ret, char type, dev_t devnum) { - char *syspath; - char id[DECIMAL_STR_MAX(unsigned) * 2 + 1]; - - assert_return(ret, -EINVAL); - assert_return(type == 'b' || type == 'c', -EINVAL); - - /* use /sys/dev/{block,char}/: link */ - snprintf(id, sizeof(id), "%u:%u", major(devnum), minor(devnum)); - - syspath = strjoina("/sys/dev/", (type == 'b' ? "block" : "char"), "/", id); - - return sd_device_new_from_syspath(ret, syspath); -} - -_public_ int sd_device_new_from_subsystem_sysname(sd_device **ret, const char *subsystem, const char *sysname) { - char *syspath; - - assert_return(ret, -EINVAL); - assert_return(subsystem, -EINVAL); - assert_return(sysname, -EINVAL); - - if (streq(subsystem, "subsystem")) { - syspath = strjoina("/sys/subsystem/", sysname); - if (access(syspath, F_OK) >= 0) - return sd_device_new_from_syspath(ret, syspath); - - syspath = strjoina("/sys/bus/", sysname); - if (access(syspath, F_OK) >= 0) - return sd_device_new_from_syspath(ret, syspath); - - syspath = strjoina("/sys/class/", sysname); - if (access(syspath, F_OK) >= 0) - return sd_device_new_from_syspath(ret, syspath); - } else if (streq(subsystem, "module")) { - syspath = strjoina("/sys/module/", sysname); - if (access(syspath, F_OK) >= 0) - return sd_device_new_from_syspath(ret, syspath); - } else if (streq(subsystem, "drivers")) { - char subsys[PATH_MAX]; - char *driver; - - strscpy(subsys, sizeof(subsys), sysname); - driver = strchr(subsys, ':'); - if (driver) { - driver[0] = '\0'; - driver++; - - syspath = strjoina("/sys/subsystem/", subsys, "/drivers/", driver); - if (access(syspath, F_OK) >= 0) - return sd_device_new_from_syspath(ret, syspath); - - syspath = strjoina("/sys/bus/", subsys, "/drivers/", driver); - if (access(syspath, F_OK) >= 0) - return sd_device_new_from_syspath(ret, syspath); - } else - return -EINVAL; - } else { - char *name; - size_t len = 0; - - /* translate sysname back to sysfs filename */ - name = strdupa(sysname); - while (name[len] != '\0') { - if (name[len] == '/') - name[len] = '!'; - - len++; - } - - syspath = strjoina("/sys/subsystem/", subsystem, "/devices/", name); - if (access(syspath, F_OK) >= 0) - return sd_device_new_from_syspath(ret, syspath); - - syspath = strjoina("/sys/bus/", subsystem, "/devices/", name); - if (access(syspath, F_OK) >= 0) - return sd_device_new_from_syspath(ret, syspath); - - syspath = strjoina("/sys/class/", subsystem, "/", name); - if (access(syspath, F_OK) >= 0) - return sd_device_new_from_syspath(ret, syspath); - } - - return -ENODEV; -} - -int device_set_devtype(sd_device *device, const char *_devtype) { - _cleanup_free_ char *devtype = NULL; - int r; - - assert(device); - assert(_devtype); - - devtype = strdup(_devtype); - if (!devtype) - return -ENOMEM; - - r = device_add_property_internal(device, "DEVTYPE", devtype); - if (r < 0) - return r; - - free(device->devtype); - device->devtype = devtype; - devtype = NULL; - - return 0; -} - -int device_set_ifindex(sd_device *device, const char *_ifindex) { - int ifindex, r; - - assert(device); - assert(_ifindex); - - r = parse_ifindex(_ifindex, &ifindex); - if (r < 0) - return r; - - r = device_add_property_internal(device, "IFINDEX", _ifindex); - if (r < 0) - return r; - - device->ifindex = ifindex; - - return 0; -} - -int device_set_devname(sd_device *device, const char *_devname) { - _cleanup_free_ char *devname = NULL; - int r; - - assert(device); - assert(_devname); - - if (_devname[0] != '/') { - r = asprintf(&devname, "/dev/%s", _devname); - if (r < 0) - return -ENOMEM; - } else { - devname = strdup(_devname); - if (!devname) - return -ENOMEM; - } - - r = device_add_property_internal(device, "DEVNAME", devname); - if (r < 0) - return r; - - free(device->devname); - device->devname = devname; - devname = NULL; - - return 0; -} - -int device_set_devmode(sd_device *device, const char *_devmode) { - unsigned devmode; - int r; - - assert(device); - assert(_devmode); - - r = safe_atou(_devmode, &devmode); - if (r < 0) - return r; - - if (devmode > 07777) - return -EINVAL; - - r = device_add_property_internal(device, "DEVMODE", _devmode); - if (r < 0) - return r; - - device->devmode = devmode; - - return 0; -} - -int device_set_devnum(sd_device *device, const char *major, const char *minor) { - unsigned maj = 0, min = 0; - int r; - - assert(device); - assert(major); - - r = safe_atou(major, &maj); - if (r < 0) - return r; - if (!maj) - return 0; - - if (minor) { - r = safe_atou(minor, &min); - if (r < 0) - return r; - } - - r = device_add_property_internal(device, "MAJOR", major); - if (r < 0) - return r; - - if (minor) { - r = device_add_property_internal(device, "MINOR", minor); - if (r < 0) - return r; - } - - device->devnum = makedev(maj, min); - - return 0; -} - -static int handle_uevent_line(sd_device *device, const char *key, const char *value, const char **major, const char **minor) { - int r; - - assert(device); - assert(key); - assert(value); - assert(major); - assert(minor); - - if (streq(key, "DEVTYPE")) { - r = device_set_devtype(device, value); - if (r < 0) - return r; - } else if (streq(key, "IFINDEX")) { - r = device_set_ifindex(device, value); - if (r < 0) - return r; - } else if (streq(key, "DEVNAME")) { - r = device_set_devname(device, value); - if (r < 0) - return r; - } else if (streq(key, "DEVMODE")) { - r = device_set_devmode(device, value); - if (r < 0) - return r; - } else if (streq(key, "MAJOR")) - *major = value; - else if (streq(key, "MINOR")) - *minor = value; - else { - r = device_add_property_internal(device, key, value); - if (r < 0) - return r; - } - - return 0; -} - -int device_read_uevent_file(sd_device *device) { - _cleanup_free_ char *uevent = NULL; - const char *syspath, *key = NULL, *value = NULL, *major = NULL, *minor = NULL; - char *path; - size_t uevent_len; - unsigned i; - int r; - - enum { - PRE_KEY, - KEY, - PRE_VALUE, - VALUE, - INVALID_LINE, - } state = PRE_KEY; - - assert(device); - - if (device->uevent_loaded || device->sealed) - return 0; - - device->uevent_loaded = true; - - r = sd_device_get_syspath(device, &syspath); - if (r < 0) - return r; - - path = strjoina(syspath, "/uevent"); - - r = read_full_file(path, &uevent, &uevent_len); - if (r == -EACCES) - /* empty uevent files may be write-only */ - return 0; - else if (r == -ENOENT) - /* some devices may not have uevent files, see set_syspath() */ - return 0; - else if (r < 0) { - log_debug_errno(r, "sd-device: failed to read uevent file '%s': %m", path); - return r; - } - - for (i = 0; i < uevent_len; i++) { - switch (state) { - case PRE_KEY: - if (!strchr(NEWLINE, uevent[i])) { - key = &uevent[i]; - - state = KEY; - } - - break; - case KEY: - if (uevent[i] == '=') { - uevent[i] = '\0'; - - state = PRE_VALUE; - } else if (strchr(NEWLINE, uevent[i])) { - uevent[i] = '\0'; - log_debug("sd-device: ignoring invalid uevent line '%s'", key); - - state = PRE_KEY; - } - - break; - case PRE_VALUE: - value = &uevent[i]; - - state = VALUE; - - break; - case VALUE: - if (strchr(NEWLINE, uevent[i])) { - uevent[i] = '\0'; - - r = handle_uevent_line(device, key, value, &major, &minor); - if (r < 0) - log_debug_errno(r, "sd-device: failed to handle uevent entry '%s=%s': %m", key, value); - - state = PRE_KEY; - } - - break; - default: - assert_not_reached("invalid state when parsing uevent file"); - } - } - - if (major) { - r = device_set_devnum(device, major, minor); - if (r < 0) - log_debug_errno(r, "sd-device: could not set 'MAJOR=%s' or 'MINOR=%s' from '%s': %m", major, minor, path); - } - - return 0; -} - -_public_ int sd_device_get_ifindex(sd_device *device, int *ifindex) { - int r; - - assert_return(device, -EINVAL); - assert_return(ifindex, -EINVAL); - - r = device_read_uevent_file(device); - if (r < 0) - return r; - - *ifindex = device->ifindex; - - return 0; -} - -_public_ int sd_device_new_from_device_id(sd_device **ret, const char *id) { - int r; - - assert_return(ret, -EINVAL); - assert_return(id, -EINVAL); - - switch (id[0]) { - case 'b': - case 'c': - { - char type; - int maj, min; - - r = sscanf(id, "%c%i:%i", &type, &maj, &min); - if (r != 3) - return -EINVAL; - - return sd_device_new_from_devnum(ret, type, makedev(maj, min)); - } - case 'n': - { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - _cleanup_close_ int sk = -1; - struct ifreq ifr = {}; - int ifindex; - - r = parse_ifindex(&id[1], &ifr.ifr_ifindex); - if (r < 0) - return r; - - sk = socket(PF_INET, SOCK_DGRAM, 0); - if (sk < 0) - return -errno; - - r = ioctl(sk, SIOCGIFNAME, &ifr); - if (r < 0) - return -errno; - - r = sd_device_new_from_subsystem_sysname(&device, "net", ifr.ifr_name); - if (r < 0) - return r; - - r = sd_device_get_ifindex(device, &ifindex); - if (r < 0) - return r; - - /* this is racey, so we might end up with the wrong device */ - if (ifr.ifr_ifindex != ifindex) - return -ENODEV; - - *ret = device; - device = NULL; - - return 0; - } - case '+': - { - char subsys[PATH_MAX]; - char *sysname; - - (void)strscpy(subsys, sizeof(subsys), id + 1); - sysname = strchr(subsys, ':'); - if (!sysname) - return -EINVAL; - - sysname[0] = '\0'; - sysname++; - - return sd_device_new_from_subsystem_sysname(ret, subsys, sysname); - } - default: - return -EINVAL; - } -} - -_public_ int sd_device_get_syspath(sd_device *device, const char **ret) { - assert_return(device, -EINVAL); - assert_return(ret, -EINVAL); - - assert(path_startswith(device->syspath, "/sys/")); - - *ret = device->syspath; - - return 0; -} - -static int device_new_from_child(sd_device **ret, sd_device *child) { - _cleanup_free_ char *path = NULL; - const char *subdir, *syspath; - int r; - - assert(ret); - assert(child); - - r = sd_device_get_syspath(child, &syspath); - if (r < 0) - return r; - - path = strdup(syspath); - if (!path) - return -ENOMEM; - subdir = path + strlen("/sys"); - - for (;;) { - char *pos; - - pos = strrchr(subdir, '/'); - if (!pos || pos < subdir + 2) - break; - - *pos = '\0'; - - r = sd_device_new_from_syspath(ret, path); - if (r < 0) - continue; - - return 0; - } - - return -ENODEV; -} - -_public_ int sd_device_get_parent(sd_device *child, sd_device **ret) { - - assert_return(ret, -EINVAL); - assert_return(child, -EINVAL); - - if (!child->parent_set) { - child->parent_set = true; - - (void)device_new_from_child(&child->parent, child); - } - - if (!child->parent) - return -ENOENT; - - *ret = child->parent; - - return 0; -} - -int device_set_subsystem(sd_device *device, const char *_subsystem) { - _cleanup_free_ char *subsystem = NULL; - int r; - - assert(device); - assert(_subsystem); - - subsystem = strdup(_subsystem); - if (!subsystem) - return -ENOMEM; - - r = device_add_property_internal(device, "SUBSYSTEM", subsystem); - if (r < 0) - return r; - - free(device->subsystem); - device->subsystem = subsystem; - subsystem = NULL; - - device->subsystem_set = true; - - return 0; -} - -_public_ int sd_device_get_subsystem(sd_device *device, const char **ret) { - assert_return(ret, -EINVAL); - assert_return(device, -EINVAL); - - if (!device->subsystem_set) { - _cleanup_free_ char *subsystem = NULL; - const char *syspath; - char *path; - int r; - - /* read 'subsystem' link */ - r = sd_device_get_syspath(device, &syspath); - if (r < 0) - return r; - - path = strjoina(syspath, "/subsystem"); - r = readlink_value(path, &subsystem); - if (r >= 0) - r = device_set_subsystem(device, subsystem); - /* use implicit names */ - else if (path_startswith(device->devpath, "/module/")) - r = device_set_subsystem(device, "module"); - else if (strstr(device->devpath, "/drivers/")) - r = device_set_subsystem(device, "drivers"); - else if (path_startswith(device->devpath, "/subsystem/") || - path_startswith(device->devpath, "/class/") || - path_startswith(device->devpath, "/bus/")) - r = device_set_subsystem(device, "subsystem"); - if (r < 0 && r != -ENOENT) - return log_debug_errno(r, "sd-device: could not set subsystem for %s: %m", device->devpath); - - device->subsystem_set = true; - } - - if (!device->subsystem) - return -ENOENT; - - *ret = device->subsystem; - - return 0; -} - -_public_ int sd_device_get_devtype(sd_device *device, const char **devtype) { - int r; - - assert(devtype); - assert(device); - - r = device_read_uevent_file(device); - if (r < 0) - return r; - - *devtype = device->devtype; - - return 0; -} - -_public_ int sd_device_get_parent_with_subsystem_devtype(sd_device *child, const char *subsystem, const char *devtype, sd_device **ret) { - sd_device *parent = NULL; - int r; - - assert_return(child, -EINVAL); - assert_return(subsystem, -EINVAL); - - r = sd_device_get_parent(child, &parent); - while (r >= 0) { - const char *parent_subsystem = NULL; - const char *parent_devtype = NULL; - - (void)sd_device_get_subsystem(parent, &parent_subsystem); - if (streq_ptr(parent_subsystem, subsystem)) { - if (!devtype) - break; - - (void)sd_device_get_devtype(parent, &parent_devtype); - if (streq_ptr(parent_devtype, devtype)) - break; - } - r = sd_device_get_parent(parent, &parent); - } - - if (r < 0) - return r; - - *ret = parent; - - return 0; -} - -_public_ int sd_device_get_devnum(sd_device *device, dev_t *devnum) { - int r; - - assert_return(device, -EINVAL); - assert_return(devnum, -EINVAL); - - r = device_read_uevent_file(device); - if (r < 0) - return r; - - *devnum = device->devnum; - - return 0; -} - -int device_set_driver(sd_device *device, const char *_driver) { - _cleanup_free_ char *driver = NULL; - int r; - - assert(device); - assert(_driver); - - driver = strdup(_driver); - if (!driver) - return -ENOMEM; - - r = device_add_property_internal(device, "DRIVER", driver); - if (r < 0) - return r; - - free(device->driver); - device->driver = driver; - driver = NULL; - - device->driver_set = true; - - return 0; -} - -_public_ int sd_device_get_driver(sd_device *device, const char **ret) { - assert_return(device, -EINVAL); - assert_return(ret, -EINVAL); - - if (!device->driver_set) { - _cleanup_free_ char *driver = NULL; - const char *syspath; - char *path; - int r; - - r = sd_device_get_syspath(device, &syspath); - if (r < 0) - return r; - - path = strjoina(syspath, "/driver"); - r = readlink_value(path, &driver); - if (r >= 0) { - r = device_set_driver(device, driver); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set driver for %s: %m", device->devpath); - } else if (r == -ENOENT) - device->driver_set = true; - else - return log_debug_errno(r, "sd-device: could not set driver for %s: %m", device->devpath); - } - - if (!device->driver) - return -ENOENT; - - *ret = device->driver; - - return 0; -} - -_public_ int sd_device_get_devpath(sd_device *device, const char **devpath) { - assert_return(device, -EINVAL); - assert_return(devpath, -EINVAL); - - assert(device->devpath); - assert(device->devpath[0] == '/'); - - *devpath = device->devpath; - - return 0; -} - -_public_ int sd_device_get_devname(sd_device *device, const char **devname) { - int r; - - assert_return(device, -EINVAL); - assert_return(devname, -EINVAL); - - r = device_read_uevent_file(device); - if (r < 0) - return r; - - if (!device->devname) - return -ENOENT; - - assert(path_startswith(device->devname, "/dev/")); - - *devname = device->devname; - - return 0; -} - -static int device_set_sysname(sd_device *device) { - _cleanup_free_ char *sysname = NULL; - const char *sysnum = NULL; - const char *pos; - size_t len = 0; - - pos = strrchr(device->devpath, '/'); - if (!pos) - return -EINVAL; - pos++; - - /* devpath is not a root directory */ - if (*pos == '\0' || pos <= device->devpath) - return -EINVAL; - - sysname = strdup(pos); - if (!sysname) - return -ENOMEM; - - /* some devices have '!' in their name, change that to '/' */ - while (sysname[len] != '\0') { - if (sysname[len] == '!') - sysname[len] = '/'; - - len++; - } - - /* trailing number */ - while (len > 0 && isdigit(sysname[--len])) - sysnum = &sysname[len]; - - if (len == 0) - sysnum = NULL; - - free(device->sysname); - device->sysname = sysname; - sysname = NULL; - - device->sysnum = sysnum; - - device->sysname_set = true; - - return 0; -} - -_public_ int sd_device_get_sysname(sd_device *device, const char **ret) { - int r; - - assert_return(device, -EINVAL); - assert_return(ret, -EINVAL); - - if (!device->sysname_set) { - r = device_set_sysname(device); - if (r < 0) - return r; - } - - assert_return(device->sysname, -ENOENT); - - *ret = device->sysname; - - return 0; -} - -_public_ int sd_device_get_sysnum(sd_device *device, const char **ret) { - int r; - - assert_return(device, -EINVAL); - assert_return(ret, -EINVAL); - - if (!device->sysname_set) { - r = device_set_sysname(device); - if (r < 0) - return r; - } - - *ret = device->sysnum; - - return 0; -} - -static bool is_valid_tag(const char *tag) { - assert(tag); - - return !strchr(tag, ':') && !strchr(tag, ' '); -} - -int device_add_tag(sd_device *device, const char *tag) { - int r; - - assert(device); - assert(tag); - - if (!is_valid_tag(tag)) - return -EINVAL; - - r = set_ensure_allocated(&device->tags, &string_hash_ops); - if (r < 0) - return r; - - r = set_put_strdup(device->tags, tag); - if (r < 0) - return r; - - device->tags_generation++; - device->property_tags_outdated = true; - - return 0; -} - -int device_add_devlink(sd_device *device, const char *devlink) { - int r; - - assert(device); - assert(devlink); - - r = set_ensure_allocated(&device->devlinks, &string_hash_ops); - if (r < 0) - return r; - - r = set_put_strdup(device->devlinks, devlink); - if (r < 0) - return r; - - device->devlinks_generation++; - device->property_devlinks_outdated = true; - - return 0; -} - -static int device_add_property_internal_from_string(sd_device *device, const char *str) { - _cleanup_free_ char *key = NULL; - char *value; - - assert(device); - assert(str); - - key = strdup(str); - if (!key) - return -ENOMEM; - - value = strchr(key, '='); - if (!value) - return -EINVAL; - - *value = '\0'; - - if (isempty(++value)) - value = NULL; - - return device_add_property_internal(device, key, value); -} - -int device_set_usec_initialized(sd_device *device, const char *initialized) { - uint64_t usec_initialized; - int r; - - assert(device); - assert(initialized); - - r = safe_atou64(initialized, &usec_initialized); - if (r < 0) - return r; - - r = device_add_property_internal(device, "USEC_INITIALIZED", initialized); - if (r < 0) - return r; - - device->usec_initialized = usec_initialized; - - return 0; -} - -static int handle_db_line(sd_device *device, char key, const char *value) { - char *path; - int r; - - assert(device); - assert(value); - - switch (key) { - case 'G': - r = device_add_tag(device, value); - if (r < 0) - return r; - - break; - case 'S': - path = strjoina("/dev/", value); - r = device_add_devlink(device, path); - if (r < 0) - return r; - - break; - case 'E': - r = device_add_property_internal_from_string(device, value); - if (r < 0) - return r; - - break; - case 'I': - r = device_set_usec_initialized(device, value); - if (r < 0) - return r; - - break; - case 'L': - r = safe_atoi(value, &device->devlink_priority); - if (r < 0) - return r; - - break; - case 'W': - r = safe_atoi(value, &device->watch_handle); - if (r < 0) - return r; - - break; - default: - log_debug("device db: unknown key '%c'", key); - } - - return 0; -} - -int device_get_id_filename(sd_device *device, const char **ret) { - assert(device); - assert(ret); - - if (!device->id_filename) { - _cleanup_free_ char *id = NULL; - const char *subsystem; - dev_t devnum; - int ifindex, r; - - r = sd_device_get_subsystem(device, &subsystem); - if (r < 0) - return r; - - r = sd_device_get_devnum(device, &devnum); - if (r < 0) - return r; - - r = sd_device_get_ifindex(device, &ifindex); - if (r < 0) - return r; - - if (major(devnum) > 0) { - assert(subsystem); - - /* use dev_t — b259:131072, c254:0 */ - r = asprintf(&id, "%c%u:%u", - streq(subsystem, "block") ? 'b' : 'c', - major(devnum), minor(devnum)); - if (r < 0) - return -ENOMEM; - } else if (ifindex > 0) { - /* use netdev ifindex — n3 */ - r = asprintf(&id, "n%u", ifindex); - if (r < 0) - return -ENOMEM; - } else { - /* use $subsys:$sysname — pci:0000:00:1f.2 - * sysname() has '!' translated, get it from devpath - */ - const char *sysname; - - sysname = basename(device->devpath); - if (!sysname) - return -EINVAL; - - if (!subsystem) - return -EINVAL; - - r = asprintf(&id, "+%s:%s", subsystem, sysname); - if (r < 0) - return -ENOMEM; - } - - device->id_filename = id; - id = NULL; - } - - *ret = device->id_filename; - - return 0; -} - -int device_read_db_aux(sd_device *device, bool force) { - _cleanup_free_ char *db = NULL; - char *path; - const char *id, *value; - char key; - size_t db_len; - unsigned i; - int r; - - enum { - PRE_KEY, - KEY, - PRE_VALUE, - VALUE, - INVALID_LINE, - } state = PRE_KEY; - - if (device->db_loaded || (!force && device->sealed)) - return 0; - - device->db_loaded = true; - - r = device_get_id_filename(device, &id); - if (r < 0) - return r; - - path = strjoina("/run/udev/data/", id); - - r = read_full_file(path, &db, &db_len); - if (r < 0) { - if (r == -ENOENT) - return 0; - else - return log_debug_errno(r, "sd-device: failed to read db '%s': %m", path); - } - - /* devices with a database entry are initialized */ - device->is_initialized = true; - - for (i = 0; i < db_len; i++) { - switch (state) { - case PRE_KEY: - if (!strchr(NEWLINE, db[i])) { - key = db[i]; - - state = KEY; - } - - break; - case KEY: - if (db[i] != ':') { - log_debug("sd-device: ignoring invalid db entry with key '%c'", key); - - state = INVALID_LINE; - } else { - db[i] = '\0'; - - state = PRE_VALUE; - } - - break; - case PRE_VALUE: - value = &db[i]; - - state = VALUE; - - break; - case INVALID_LINE: - if (strchr(NEWLINE, db[i])) - state = PRE_KEY; - - break; - case VALUE: - if (strchr(NEWLINE, db[i])) { - db[i] = '\0'; - r = handle_db_line(device, key, value); - if (r < 0) - log_debug_errno(r, "sd-device: failed to handle db entry '%c:%s': %m", key, value); - - state = PRE_KEY; - } - - break; - default: - assert_not_reached("invalid state when parsing db"); - } - } - - return 0; -} - -static int device_read_db(sd_device *device) { - return device_read_db_aux(device, false); -} - -_public_ int sd_device_get_is_initialized(sd_device *device, int *initialized) { - int r; - - assert_return(device, -EINVAL); - assert_return(initialized, -EINVAL); - - r = device_read_db(device); - if (r < 0) - return r; - - *initialized = device->is_initialized; - - return 0; -} - -_public_ int sd_device_get_usec_since_initialized(sd_device *device, uint64_t *usec) { - usec_t now_ts; - int r; - - assert_return(device, -EINVAL); - assert_return(usec, -EINVAL); - - r = device_read_db(device); - if (r < 0) - return r; - - if (!device->is_initialized) - return -EBUSY; - - if (!device->usec_initialized) - return -ENODATA; - - now_ts = now(clock_boottime_or_monotonic()); - - if (now_ts < device->usec_initialized) - return -EIO; - - *usec = now_ts - device->usec_initialized; - - return 0; -} - -_public_ const char *sd_device_get_tag_first(sd_device *device) { - void *v; - - assert_return(device, NULL); - - (void) device_read_db(device); - - device->tags_iterator_generation = device->tags_generation; - device->tags_iterator = ITERATOR_FIRST; - - (void) set_iterate(device->tags, &device->tags_iterator, &v); - return v; -} - -_public_ const char *sd_device_get_tag_next(sd_device *device) { - void *v; - - assert_return(device, NULL); - - (void) device_read_db(device); - - if (device->tags_iterator_generation != device->tags_generation) - return NULL; - - (void) set_iterate(device->tags, &device->tags_iterator, &v); - return v; -} - -_public_ const char *sd_device_get_devlink_first(sd_device *device) { - void *v; - - assert_return(device, NULL); - - (void) device_read_db(device); - - device->devlinks_iterator_generation = device->devlinks_generation; - device->devlinks_iterator = ITERATOR_FIRST; - - (void) set_iterate(device->devlinks, &device->devlinks_iterator, &v); - return v; -} - -_public_ const char *sd_device_get_devlink_next(sd_device *device) { - void *v; - - assert_return(device, NULL); - - (void) device_read_db(device); - - if (device->devlinks_iterator_generation != device->devlinks_generation) - return NULL; - - (void) set_iterate(device->devlinks, &device->devlinks_iterator, &v); - return v; -} - -static int device_properties_prepare(sd_device *device) { - int r; - - assert(device); - - r = device_read_uevent_file(device); - if (r < 0) - return r; - - r = device_read_db(device); - if (r < 0) - return r; - - if (device->property_devlinks_outdated) { - _cleanup_free_ char *devlinks = NULL; - size_t devlinks_allocated = 0, devlinks_len = 0; - const char *devlink; - - for (devlink = sd_device_get_devlink_first(device); devlink; devlink = sd_device_get_devlink_next(device)) { - char *e; - - if (!GREEDY_REALLOC(devlinks, devlinks_allocated, devlinks_len + strlen(devlink) + 2)) - return -ENOMEM; - if (devlinks_len > 0) - stpcpy(devlinks + devlinks_len++, " "); - e = stpcpy(devlinks + devlinks_len, devlink); - devlinks_len = e - devlinks; - } - - r = device_add_property_internal(device, "DEVLINKS", devlinks); - if (r < 0) - return r; - - device->property_devlinks_outdated = false; - } - - if (device->property_tags_outdated) { - _cleanup_free_ char *tags = NULL; - size_t tags_allocated = 0, tags_len = 0; - const char *tag; - - if (!GREEDY_REALLOC(tags, tags_allocated, 2)) - return -ENOMEM; - stpcpy(tags, ":"); - tags_len++; - - for (tag = sd_device_get_tag_first(device); tag; tag = sd_device_get_tag_next(device)) { - char *e; - - if (!GREEDY_REALLOC(tags, tags_allocated, tags_len + strlen(tag) + 2)) - return -ENOMEM; - e = stpcpy(stpcpy(tags + tags_len, tag), ":"); - tags_len = e - tags; - } - - r = device_add_property_internal(device, "TAGS", tags); - if (r < 0) - return r; - - device->property_tags_outdated = false; - } - - return 0; -} - -_public_ const char *sd_device_get_property_first(sd_device *device, const char **_value) { - const char *key; - const char *value; - int r; - - assert_return(device, NULL); - - r = device_properties_prepare(device); - if (r < 0) - return NULL; - - device->properties_iterator_generation = device->properties_generation; - device->properties_iterator = ITERATOR_FIRST; - - ordered_hashmap_iterate(device->properties, &device->properties_iterator, (void**)&value, (const void**)&key); - - if (_value) - *_value = value; - - return key; -} - -_public_ const char *sd_device_get_property_next(sd_device *device, const char **_value) { - const char *key; - const char *value; - int r; - - assert_return(device, NULL); - - r = device_properties_prepare(device); - if (r < 0) - return NULL; - - if (device->properties_iterator_generation != device->properties_generation) - return NULL; - - ordered_hashmap_iterate(device->properties, &device->properties_iterator, (void**)&value, (const void**)&key); - - if (_value) - *_value = value; - - return key; -} - -static int device_sysattrs_read_all(sd_device *device) { - _cleanup_closedir_ DIR *dir = NULL; - const char *syspath; - struct dirent *dent; - int r; - - assert(device); - - if (device->sysattrs_read) - return 0; - - r = sd_device_get_syspath(device, &syspath); - if (r < 0) - return r; - - dir = opendir(syspath); - if (!dir) - return -errno; - - r = set_ensure_allocated(&device->sysattrs, &string_hash_ops); - if (r < 0) - return r; - - for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { - char *path; - struct stat statbuf; - - /* only handle symlinks and regular files */ - if (dent->d_type != DT_LNK && dent->d_type != DT_REG) - continue; - - path = strjoina(syspath, "/", dent->d_name); - - if (lstat(path, &statbuf) != 0) - continue; - - if (!(statbuf.st_mode & S_IRUSR)) - continue; - - r = set_put_strdup(device->sysattrs, dent->d_name); - if (r < 0) - return r; - } - - device->sysattrs_read = true; - - return 0; -} - -_public_ const char *sd_device_get_sysattr_first(sd_device *device) { - void *v; - int r; - - assert_return(device, NULL); - - if (!device->sysattrs_read) { - r = device_sysattrs_read_all(device); - if (r < 0) { - errno = -r; - return NULL; - } - } - - device->sysattrs_iterator = ITERATOR_FIRST; - - (void) set_iterate(device->sysattrs, &device->sysattrs_iterator, &v); - return v; -} - -_public_ const char *sd_device_get_sysattr_next(sd_device *device) { - void *v; - - assert_return(device, NULL); - - if (!device->sysattrs_read) - return NULL; - - (void) set_iterate(device->sysattrs, &device->sysattrs_iterator, &v); - return v; -} - -_public_ int sd_device_has_tag(sd_device *device, const char *tag) { - assert_return(device, -EINVAL); - assert_return(tag, -EINVAL); - - (void) device_read_db(device); - - return !!set_contains(device->tags, tag); -} - -_public_ int sd_device_get_property_value(sd_device *device, const char *key, const char **_value) { - char *value; - int r; - - assert_return(device, -EINVAL); - assert_return(key, -EINVAL); - assert_return(_value, -EINVAL); - - r = device_properties_prepare(device); - if (r < 0) - return r; - - value = ordered_hashmap_get(device->properties, key); - if (!value) - return -ENOENT; - - *_value = value; - - return 0; -} - -/* replaces the value if it already exists */ -static int device_add_sysattr_value(sd_device *device, const char *_key, char *value) { - _cleanup_free_ char *key = NULL; - _cleanup_free_ char *value_old = NULL; - int r; - - assert(device); - assert(_key); - - r = hashmap_ensure_allocated(&device->sysattr_values, &string_hash_ops); - if (r < 0) - return r; - - value_old = hashmap_remove2(device->sysattr_values, _key, (void **)&key); - if (!key) { - key = strdup(_key); - if (!key) - return -ENOMEM; - } - - r = hashmap_put(device->sysattr_values, key, value); - if (r < 0) - return r; - - key = NULL; - - return 0; -} - -static int device_get_sysattr_value(sd_device *device, const char *_key, const char **_value) { - const char *key = NULL, *value; - - assert(device); - assert(_key); - - value = hashmap_get2(device->sysattr_values, _key, (void **) &key); - if (!key) - return -ENOENT; - - if (_value) - *_value = value; - - return 0; -} - -/* We cache all sysattr lookups. If an attribute does not exist, it is stored - * with a NULL value in the cache, otherwise the returned string is stored */ -_public_ int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **_value) { - _cleanup_free_ char *value = NULL; - const char *syspath, *cached_value = NULL; - char *path; - struct stat statbuf; - int r; - - assert_return(device, -EINVAL); - assert_return(sysattr, -EINVAL); - - /* look for possibly already cached result */ - r = device_get_sysattr_value(device, sysattr, &cached_value); - if (r != -ENOENT) { - if (r < 0) - return r; - - if (!cached_value) - /* we looked up the sysattr before and it did not exist */ - return -ENOENT; - - if (_value) - *_value = cached_value; - - return 0; - } - - r = sd_device_get_syspath(device, &syspath); - if (r < 0) - return r; - - path = strjoina(syspath, "/", sysattr); - r = lstat(path, &statbuf); - if (r < 0) { - /* remember that we could not access the sysattr */ - r = device_add_sysattr_value(device, sysattr, NULL); - if (r < 0) - return r; - - return -ENOENT; - } else if (S_ISLNK(statbuf.st_mode)) { - /* Some core links return only the last element of the target path, - * these are just values, the paths should not be exposed. */ - if (STR_IN_SET(sysattr, "driver", "subsystem", "module")) { - r = readlink_value(path, &value); - if (r < 0) - return r; - } else - return -EINVAL; - } else if (S_ISDIR(statbuf.st_mode)) { - /* skip directories */ - return -EINVAL; - } else if (!(statbuf.st_mode & S_IRUSR)) { - /* skip non-readable files */ - return -EPERM; - } else { - size_t size; - - /* read attribute value */ - r = read_full_file(path, &value, &size); - if (r < 0) - return r; - - /* drop trailing newlines */ - while (size > 0 && value[--size] == '\n') - value[size] = '\0'; - } - - r = device_add_sysattr_value(device, sysattr, value); - if (r < 0) - return r; - - *_value = value; - value = NULL; - - return 0; -} - -static void device_remove_sysattr_value(sd_device *device, const char *_key) { - _cleanup_free_ char *key = NULL; - _cleanup_free_ char *value = NULL; - - assert(device); - assert(_key); - - value = hashmap_remove2(device->sysattr_values, _key, (void **) &key); - - return; -} - -/* set the attribute and save it in the cache. If a NULL value is passed the - * attribute is cleared from the cache */ -_public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, char *_value) { - _cleanup_close_ int fd = -1; - _cleanup_free_ char *value = NULL; - const char *syspath; - char *path; - struct stat statbuf; - size_t value_len = 0; - ssize_t size; - int r; - - assert_return(device, -EINVAL); - assert_return(sysattr, -EINVAL); - - if (!_value) { - device_remove_sysattr_value(device, sysattr); - - return 0; - } - - r = sd_device_get_syspath(device, &syspath); - if (r < 0) - return r; - - path = strjoina(syspath, "/", sysattr); - r = lstat(path, &statbuf); - if (r < 0) { - value = strdup(""); - if (!value) - return -ENOMEM; - - r = device_add_sysattr_value(device, sysattr, value); - if (r < 0) - return r; - - return -ENXIO; - } - - if (S_ISLNK(statbuf.st_mode)) - return -EINVAL; - - /* skip directories */ - if (S_ISDIR(statbuf.st_mode)) - return -EISDIR; - - /* skip non-readable files */ - if ((statbuf.st_mode & S_IRUSR) == 0) - return -EACCES; - - value_len = strlen(_value); - - /* drop trailing newlines */ - while (value_len > 0 && _value[value_len - 1] == '\n') - _value[--value_len] = '\0'; - - /* value length is limited to 4k */ - if (value_len > 4096) - return -EINVAL; - - fd = open(path, O_WRONLY | O_CLOEXEC); - if (fd < 0) - return -errno; - - value = strdup(_value); - if (!value) - return -ENOMEM; - - size = write(fd, value, value_len); - if (size < 0) - return -errno; - - if ((size_t)size != value_len) - return -EIO; - - r = device_add_sysattr_value(device, sysattr, value); - if (r < 0) - return r; - - value = NULL; - - return 0; -} diff --git a/src/libsystemd/sd-event/Makefile b/src/libsystemd/sd-event/Makefile deleted file mode 120000 index 94aaae2c4d..0000000000 --- a/src/libsystemd/sd-event/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c deleted file mode 100644 index 7ba6527f63..0000000000 --- a/src/libsystemd/sd-event/sd-event.c +++ /dev/null @@ -1,2898 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 -#include - -#include "sd-daemon.h" -#include "sd-event.h" -#include "sd-id128.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "hashmap.h" -#include "list.h" -#include "macro.h" -#include "missing.h" -#include "prioq.h" -#include "process-util.h" -#include "set.h" -#include "signal-util.h" -#include "string-table.h" -#include "string-util.h" -#include "time-util.h" -#include "util.h" - -#define DEFAULT_ACCURACY_USEC (250 * USEC_PER_MSEC) - -typedef enum EventSourceType { - SOURCE_IO, - SOURCE_TIME_REALTIME, - SOURCE_TIME_BOOTTIME, - SOURCE_TIME_MONOTONIC, - SOURCE_TIME_REALTIME_ALARM, - SOURCE_TIME_BOOTTIME_ALARM, - SOURCE_SIGNAL, - SOURCE_CHILD, - SOURCE_DEFER, - SOURCE_POST, - SOURCE_EXIT, - SOURCE_WATCHDOG, - _SOURCE_EVENT_SOURCE_TYPE_MAX, - _SOURCE_EVENT_SOURCE_TYPE_INVALID = -1 -} EventSourceType; - -static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX] = { - [SOURCE_IO] = "io", - [SOURCE_TIME_REALTIME] = "realtime", - [SOURCE_TIME_BOOTTIME] = "bootime", - [SOURCE_TIME_MONOTONIC] = "monotonic", - [SOURCE_TIME_REALTIME_ALARM] = "realtime-alarm", - [SOURCE_TIME_BOOTTIME_ALARM] = "boottime-alarm", - [SOURCE_SIGNAL] = "signal", - [SOURCE_CHILD] = "child", - [SOURCE_DEFER] = "defer", - [SOURCE_POST] = "post", - [SOURCE_EXIT] = "exit", - [SOURCE_WATCHDOG] = "watchdog", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int); - -/* All objects we use in epoll events start with this value, so that - * we know how to dispatch it */ -typedef enum WakeupType { - WAKEUP_NONE, - WAKEUP_EVENT_SOURCE, - WAKEUP_CLOCK_DATA, - WAKEUP_SIGNAL_DATA, - _WAKEUP_TYPE_MAX, - _WAKEUP_TYPE_INVALID = -1, -} WakeupType; - -#define EVENT_SOURCE_IS_TIME(t) IN_SET((t), SOURCE_TIME_REALTIME, SOURCE_TIME_BOOTTIME, SOURCE_TIME_MONOTONIC, SOURCE_TIME_REALTIME_ALARM, SOURCE_TIME_BOOTTIME_ALARM) - -struct sd_event_source { - WakeupType wakeup; - - unsigned n_ref; - - sd_event *event; - void *userdata; - sd_event_handler_t prepare; - - char *description; - - EventSourceType type:5; - int enabled:3; - bool pending:1; - bool dispatching:1; - bool floating:1; - - int64_t priority; - unsigned pending_index; - unsigned prepare_index; - unsigned pending_iteration; - unsigned prepare_iteration; - - LIST_FIELDS(sd_event_source, sources); - - union { - struct { - sd_event_io_handler_t callback; - int fd; - uint32_t events; - uint32_t revents; - bool registered:1; - } io; - struct { - sd_event_time_handler_t callback; - usec_t next, accuracy; - unsigned earliest_index; - unsigned latest_index; - } time; - struct { - sd_event_signal_handler_t callback; - struct signalfd_siginfo siginfo; - int sig; - } signal; - struct { - sd_event_child_handler_t callback; - siginfo_t siginfo; - pid_t pid; - int options; - } child; - struct { - sd_event_handler_t callback; - } defer; - struct { - sd_event_handler_t callback; - } post; - struct { - sd_event_handler_t callback; - unsigned prioq_index; - } exit; - }; -}; - -struct clock_data { - WakeupType wakeup; - int fd; - - /* For all clocks we maintain two priority queues each, one - * ordered for the earliest times the events may be - * dispatched, and one ordered by the latest times they must - * have been dispatched. The range between the top entries in - * the two prioqs is the time window we can freely schedule - * wakeups in */ - - Prioq *earliest; - Prioq *latest; - usec_t next; - - bool needs_rearm:1; -}; - -struct signal_data { - WakeupType wakeup; - - /* For each priority we maintain one signal fd, so that we - * only have to dequeue a single event per priority at a - * time. */ - - int fd; - int64_t priority; - sigset_t sigset; - sd_event_source *current; -}; - -struct sd_event { - unsigned n_ref; - - int epoll_fd; - int watchdog_fd; - - Prioq *pending; - Prioq *prepare; - - /* timerfd_create() only supports these five clocks so far. We - * can add support for more clocks when the kernel learns to - * deal with them, too. */ - struct clock_data realtime; - struct clock_data boottime; - struct clock_data monotonic; - struct clock_data realtime_alarm; - struct clock_data boottime_alarm; - - usec_t perturb; - - sd_event_source **signal_sources; /* indexed by signal number */ - Hashmap *signal_data; /* indexed by priority */ - - Hashmap *child_sources; - unsigned n_enabled_child_sources; - - Set *post_sources; - - Prioq *exit; - - pid_t original_pid; - - unsigned iteration; - dual_timestamp timestamp; - usec_t timestamp_boottime; - int state; - - bool exit_requested:1; - bool need_process_child:1; - bool watchdog:1; - bool profile_delays:1; - - int exit_code; - - pid_t tid; - sd_event **default_event_ptr; - - usec_t watchdog_last, watchdog_period; - - unsigned n_sources; - - LIST_HEAD(sd_event_source, sources); - - usec_t last_run, last_log; - unsigned delays[sizeof(usec_t) * 8]; -}; - -static void source_disconnect(sd_event_source *s); - -static int pending_prioq_compare(const void *a, const void *b) { - const sd_event_source *x = a, *y = b; - - assert(x->pending); - assert(y->pending); - - /* Enabled ones first */ - if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) - return -1; - if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) - return 1; - - /* Lower priority values first */ - if (x->priority < y->priority) - return -1; - if (x->priority > y->priority) - return 1; - - /* Older entries first */ - if (x->pending_iteration < y->pending_iteration) - return -1; - if (x->pending_iteration > y->pending_iteration) - return 1; - - return 0; -} - -static int prepare_prioq_compare(const void *a, const void *b) { - const sd_event_source *x = a, *y = b; - - assert(x->prepare); - assert(y->prepare); - - /* Enabled ones first */ - if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) - return -1; - if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) - return 1; - - /* Move most recently prepared ones last, so that we can stop - * preparing as soon as we hit one that has already been - * prepared in the current iteration */ - if (x->prepare_iteration < y->prepare_iteration) - return -1; - if (x->prepare_iteration > y->prepare_iteration) - return 1; - - /* Lower priority values first */ - if (x->priority < y->priority) - return -1; - if (x->priority > y->priority) - return 1; - - return 0; -} - -static int earliest_time_prioq_compare(const void *a, const void *b) { - const sd_event_source *x = a, *y = b; - - assert(EVENT_SOURCE_IS_TIME(x->type)); - assert(x->type == y->type); - - /* Enabled ones first */ - if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) - return -1; - if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) - return 1; - - /* Move the pending ones to the end */ - if (!x->pending && y->pending) - return -1; - if (x->pending && !y->pending) - return 1; - - /* Order by time */ - if (x->time.next < y->time.next) - return -1; - if (x->time.next > y->time.next) - return 1; - - return 0; -} - -static usec_t time_event_source_latest(const sd_event_source *s) { - return usec_add(s->time.next, s->time.accuracy); -} - -static int latest_time_prioq_compare(const void *a, const void *b) { - const sd_event_source *x = a, *y = b; - - assert(EVENT_SOURCE_IS_TIME(x->type)); - assert(x->type == y->type); - - /* Enabled ones first */ - if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) - return -1; - if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) - return 1; - - /* Move the pending ones to the end */ - if (!x->pending && y->pending) - return -1; - if (x->pending && !y->pending) - return 1; - - /* Order by time */ - if (time_event_source_latest(x) < time_event_source_latest(y)) - return -1; - if (time_event_source_latest(x) > time_event_source_latest(y)) - return 1; - - return 0; -} - -static int exit_prioq_compare(const void *a, const void *b) { - const sd_event_source *x = a, *y = b; - - assert(x->type == SOURCE_EXIT); - assert(y->type == SOURCE_EXIT); - - /* Enabled ones first */ - if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) - return -1; - if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) - return 1; - - /* Lower priority values first */ - if (x->priority < y->priority) - return -1; - if (x->priority > y->priority) - return 1; - - return 0; -} - -static void free_clock_data(struct clock_data *d) { - assert(d); - assert(d->wakeup == WAKEUP_CLOCK_DATA); - - safe_close(d->fd); - prioq_free(d->earliest); - prioq_free(d->latest); -} - -static void event_free(sd_event *e) { - sd_event_source *s; - - assert(e); - - while ((s = e->sources)) { - assert(s->floating); - source_disconnect(s); - sd_event_source_unref(s); - } - - assert(e->n_sources == 0); - - if (e->default_event_ptr) - *(e->default_event_ptr) = NULL; - - safe_close(e->epoll_fd); - safe_close(e->watchdog_fd); - - free_clock_data(&e->realtime); - free_clock_data(&e->boottime); - free_clock_data(&e->monotonic); - free_clock_data(&e->realtime_alarm); - free_clock_data(&e->boottime_alarm); - - prioq_free(e->pending); - prioq_free(e->prepare); - prioq_free(e->exit); - - free(e->signal_sources); - hashmap_free(e->signal_data); - - hashmap_free(e->child_sources); - set_free(e->post_sources); - free(e); -} - -_public_ int sd_event_new(sd_event** ret) { - sd_event *e; - int r; - - assert_return(ret, -EINVAL); - - e = new0(sd_event, 1); - if (!e) - return -ENOMEM; - - e->n_ref = 1; - e->watchdog_fd = e->epoll_fd = e->realtime.fd = e->boottime.fd = e->monotonic.fd = e->realtime_alarm.fd = e->boottime_alarm.fd = -1; - e->realtime.next = e->boottime.next = e->monotonic.next = e->realtime_alarm.next = e->boottime_alarm.next = USEC_INFINITY; - e->realtime.wakeup = e->boottime.wakeup = e->monotonic.wakeup = e->realtime_alarm.wakeup = e->boottime_alarm.wakeup = WAKEUP_CLOCK_DATA; - e->original_pid = getpid(); - e->perturb = USEC_INFINITY; - - r = prioq_ensure_allocated(&e->pending, pending_prioq_compare); - if (r < 0) - goto fail; - - e->epoll_fd = epoll_create1(EPOLL_CLOEXEC); - if (e->epoll_fd < 0) { - r = -errno; - goto fail; - } - - if (secure_getenv("SD_EVENT_PROFILE_DELAYS")) { - log_debug("Event loop profiling enabled. Logarithmic histogram of event loop iterations in the range 2^0 ... 2^63 us will be logged every 5s."); - e->profile_delays = true; - } - - *ret = e; - return 0; - -fail: - event_free(e); - return r; -} - -_public_ sd_event* sd_event_ref(sd_event *e) { - - if (!e) - return NULL; - - assert(e->n_ref >= 1); - e->n_ref++; - - return e; -} - -_public_ sd_event* sd_event_unref(sd_event *e) { - - if (!e) - return NULL; - - assert(e->n_ref >= 1); - e->n_ref--; - - if (e->n_ref <= 0) - event_free(e); - - return NULL; -} - -static bool event_pid_changed(sd_event *e) { - assert(e); - - /* We don't support people creating an event loop and keeping - * it around over a fork(). Let's complain. */ - - return e->original_pid != getpid(); -} - -static void source_io_unregister(sd_event_source *s) { - int r; - - assert(s); - assert(s->type == SOURCE_IO); - - if (event_pid_changed(s->event)) - return; - - if (!s->io.registered) - return; - - r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->io.fd, NULL); - if (r < 0) - log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll: %m", - strna(s->description), event_source_type_to_string(s->type)); - - s->io.registered = false; -} - -static int source_io_register( - sd_event_source *s, - int enabled, - uint32_t events) { - - struct epoll_event ev = {}; - int r; - - assert(s); - assert(s->type == SOURCE_IO); - assert(enabled != SD_EVENT_OFF); - - ev.events = events; - ev.data.ptr = s; - - if (enabled == SD_EVENT_ONESHOT) - ev.events |= EPOLLONESHOT; - - if (s->io.registered) - r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_MOD, s->io.fd, &ev); - else - r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_ADD, s->io.fd, &ev); - if (r < 0) - return -errno; - - s->io.registered = true; - - return 0; -} - -static clockid_t event_source_type_to_clock(EventSourceType t) { - - switch (t) { - - case SOURCE_TIME_REALTIME: - return CLOCK_REALTIME; - - case SOURCE_TIME_BOOTTIME: - return CLOCK_BOOTTIME; - - case SOURCE_TIME_MONOTONIC: - return CLOCK_MONOTONIC; - - case SOURCE_TIME_REALTIME_ALARM: - return CLOCK_REALTIME_ALARM; - - case SOURCE_TIME_BOOTTIME_ALARM: - return CLOCK_BOOTTIME_ALARM; - - default: - return (clockid_t) -1; - } -} - -static EventSourceType clock_to_event_source_type(clockid_t clock) { - - switch (clock) { - - case CLOCK_REALTIME: - return SOURCE_TIME_REALTIME; - - case CLOCK_BOOTTIME: - return SOURCE_TIME_BOOTTIME; - - case CLOCK_MONOTONIC: - return SOURCE_TIME_MONOTONIC; - - case CLOCK_REALTIME_ALARM: - return SOURCE_TIME_REALTIME_ALARM; - - case CLOCK_BOOTTIME_ALARM: - return SOURCE_TIME_BOOTTIME_ALARM; - - default: - return _SOURCE_EVENT_SOURCE_TYPE_INVALID; - } -} - -static struct clock_data* event_get_clock_data(sd_event *e, EventSourceType t) { - assert(e); - - switch (t) { - - case SOURCE_TIME_REALTIME: - return &e->realtime; - - case SOURCE_TIME_BOOTTIME: - return &e->boottime; - - case SOURCE_TIME_MONOTONIC: - return &e->monotonic; - - case SOURCE_TIME_REALTIME_ALARM: - return &e->realtime_alarm; - - case SOURCE_TIME_BOOTTIME_ALARM: - return &e->boottime_alarm; - - default: - return NULL; - } -} - -static int event_make_signal_data( - sd_event *e, - int sig, - struct signal_data **ret) { - - struct epoll_event ev = {}; - struct signal_data *d; - bool added = false; - sigset_t ss_copy; - int64_t priority; - int r; - - assert(e); - - if (event_pid_changed(e)) - return -ECHILD; - - if (e->signal_sources && e->signal_sources[sig]) - priority = e->signal_sources[sig]->priority; - else - priority = 0; - - d = hashmap_get(e->signal_data, &priority); - if (d) { - if (sigismember(&d->sigset, sig) > 0) { - if (ret) - *ret = d; - return 0; - } - } else { - r = hashmap_ensure_allocated(&e->signal_data, &uint64_hash_ops); - if (r < 0) - return r; - - d = new0(struct signal_data, 1); - if (!d) - return -ENOMEM; - - d->wakeup = WAKEUP_SIGNAL_DATA; - d->fd = -1; - d->priority = priority; - - r = hashmap_put(e->signal_data, &d->priority, d); - if (r < 0) { - free(d); - return r; - } - - added = true; - } - - ss_copy = d->sigset; - assert_se(sigaddset(&ss_copy, sig) >= 0); - - r = signalfd(d->fd, &ss_copy, SFD_NONBLOCK|SFD_CLOEXEC); - if (r < 0) { - r = -errno; - goto fail; - } - - d->sigset = ss_copy; - - if (d->fd >= 0) { - if (ret) - *ret = d; - return 0; - } - - d->fd = r; - - ev.events = EPOLLIN; - ev.data.ptr = d; - - r = epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, d->fd, &ev); - if (r < 0) { - r = -errno; - goto fail; - } - - if (ret) - *ret = d; - - return 0; - -fail: - if (added) { - d->fd = safe_close(d->fd); - hashmap_remove(e->signal_data, &d->priority); - free(d); - } - - return r; -} - -static void event_unmask_signal_data(sd_event *e, struct signal_data *d, int sig) { - assert(e); - assert(d); - - /* Turns off the specified signal in the signal data - * object. If the signal mask of the object becomes empty that - * way removes it. */ - - if (sigismember(&d->sigset, sig) == 0) - return; - - assert_se(sigdelset(&d->sigset, sig) >= 0); - - if (sigisemptyset(&d->sigset)) { - - /* If all the mask is all-zero we can get rid of the structure */ - hashmap_remove(e->signal_data, &d->priority); - assert(!d->current); - safe_close(d->fd); - free(d); - return; - } - - assert(d->fd >= 0); - - if (signalfd(d->fd, &d->sigset, SFD_NONBLOCK|SFD_CLOEXEC) < 0) - log_debug_errno(errno, "Failed to unset signal bit, ignoring: %m"); -} - -static void event_gc_signal_data(sd_event *e, const int64_t *priority, int sig) { - struct signal_data *d; - static const int64_t zero_priority = 0; - - assert(e); - - /* Rechecks if the specified signal is still something we are - * interested in. If not, we'll unmask it, and possibly drop - * the signalfd for it. */ - - if (sig == SIGCHLD && - e->n_enabled_child_sources > 0) - return; - - if (e->signal_sources && - e->signal_sources[sig] && - e->signal_sources[sig]->enabled != SD_EVENT_OFF) - return; - - /* - * The specified signal might be enabled in three different queues: - * - * 1) the one that belongs to the priority passed (if it is non-NULL) - * 2) the one that belongs to the priority of the event source of the signal (if there is one) - * 3) the 0 priority (to cover the SIGCHLD case) - * - * Hence, let's remove it from all three here. - */ - - if (priority) { - d = hashmap_get(e->signal_data, priority); - if (d) - event_unmask_signal_data(e, d, sig); - } - - if (e->signal_sources && e->signal_sources[sig]) { - d = hashmap_get(e->signal_data, &e->signal_sources[sig]->priority); - if (d) - event_unmask_signal_data(e, d, sig); - } - - d = hashmap_get(e->signal_data, &zero_priority); - if (d) - event_unmask_signal_data(e, d, sig); -} - -static void source_disconnect(sd_event_source *s) { - sd_event *event; - - assert(s); - - if (!s->event) - return; - - assert(s->event->n_sources > 0); - - switch (s->type) { - - case SOURCE_IO: - if (s->io.fd >= 0) - source_io_unregister(s); - - break; - - case SOURCE_TIME_REALTIME: - case SOURCE_TIME_BOOTTIME: - case SOURCE_TIME_MONOTONIC: - case SOURCE_TIME_REALTIME_ALARM: - case SOURCE_TIME_BOOTTIME_ALARM: { - struct clock_data *d; - - d = event_get_clock_data(s->event, s->type); - assert(d); - - prioq_remove(d->earliest, s, &s->time.earliest_index); - prioq_remove(d->latest, s, &s->time.latest_index); - d->needs_rearm = true; - break; - } - - case SOURCE_SIGNAL: - if (s->signal.sig > 0) { - - if (s->event->signal_sources) - s->event->signal_sources[s->signal.sig] = NULL; - - event_gc_signal_data(s->event, &s->priority, s->signal.sig); - } - - break; - - case SOURCE_CHILD: - if (s->child.pid > 0) { - if (s->enabled != SD_EVENT_OFF) { - assert(s->event->n_enabled_child_sources > 0); - s->event->n_enabled_child_sources--; - } - - (void) hashmap_remove(s->event->child_sources, PID_TO_PTR(s->child.pid)); - event_gc_signal_data(s->event, &s->priority, SIGCHLD); - } - - break; - - case SOURCE_DEFER: - /* nothing */ - break; - - case SOURCE_POST: - set_remove(s->event->post_sources, s); - break; - - case SOURCE_EXIT: - prioq_remove(s->event->exit, s, &s->exit.prioq_index); - break; - - default: - assert_not_reached("Wut? I shouldn't exist."); - } - - if (s->pending) - prioq_remove(s->event->pending, s, &s->pending_index); - - if (s->prepare) - prioq_remove(s->event->prepare, s, &s->prepare_index); - - event = s->event; - - s->type = _SOURCE_EVENT_SOURCE_TYPE_INVALID; - s->event = NULL; - LIST_REMOVE(sources, event->sources, s); - event->n_sources--; - - if (!s->floating) - sd_event_unref(event); -} - -static void source_free(sd_event_source *s) { - assert(s); - - source_disconnect(s); - free(s->description); - free(s); -} - -static int source_set_pending(sd_event_source *s, bool b) { - int r; - - assert(s); - assert(s->type != SOURCE_EXIT); - - if (s->pending == b) - return 0; - - s->pending = b; - - if (b) { - s->pending_iteration = s->event->iteration; - - r = prioq_put(s->event->pending, s, &s->pending_index); - if (r < 0) { - s->pending = false; - return r; - } - } else - assert_se(prioq_remove(s->event->pending, s, &s->pending_index)); - - if (EVENT_SOURCE_IS_TIME(s->type)) { - struct clock_data *d; - - d = event_get_clock_data(s->event, s->type); - assert(d); - - prioq_reshuffle(d->earliest, s, &s->time.earliest_index); - prioq_reshuffle(d->latest, s, &s->time.latest_index); - d->needs_rearm = true; - } - - if (s->type == SOURCE_SIGNAL && !b) { - struct signal_data *d; - - d = hashmap_get(s->event->signal_data, &s->priority); - if (d && d->current == s) - d->current = NULL; - } - - return 0; -} - -static sd_event_source *source_new(sd_event *e, bool floating, EventSourceType type) { - sd_event_source *s; - - assert(e); - - s = new0(sd_event_source, 1); - if (!s) - return NULL; - - s->n_ref = 1; - s->event = e; - s->floating = floating; - s->type = type; - s->pending_index = s->prepare_index = PRIOQ_IDX_NULL; - - if (!floating) - sd_event_ref(e); - - LIST_PREPEND(sources, e->sources, s); - e->n_sources++; - - return s; -} - -_public_ int sd_event_add_io( - sd_event *e, - sd_event_source **ret, - int fd, - uint32_t events, - sd_event_io_handler_t callback, - void *userdata) { - - sd_event_source *s; - int r; - - assert_return(e, -EINVAL); - assert_return(fd >= 0, -EBADF); - assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL); - assert_return(callback, -EINVAL); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(e), -ECHILD); - - s = source_new(e, !ret, SOURCE_IO); - if (!s) - return -ENOMEM; - - s->wakeup = WAKEUP_EVENT_SOURCE; - s->io.fd = fd; - s->io.events = events; - s->io.callback = callback; - s->userdata = userdata; - s->enabled = SD_EVENT_ON; - - r = source_io_register(s, s->enabled, events); - if (r < 0) { - source_free(s); - return r; - } - - if (ret) - *ret = s; - - return 0; -} - -static void initialize_perturb(sd_event *e) { - sd_id128_t bootid = {}; - - /* When we sleep for longer, we try to realign the wakeup to - the same time wihtin each minute/second/250ms, so that - events all across the system can be coalesced into a single - CPU wakeup. However, let's take some system-specific - randomness for this value, so that in a network of systems - with synced clocks timer events are distributed a - bit. Here, we calculate a perturbation usec offset from the - boot ID. */ - - if (_likely_(e->perturb != USEC_INFINITY)) - return; - - if (sd_id128_get_boot(&bootid) >= 0) - e->perturb = (bootid.qwords[0] ^ bootid.qwords[1]) % USEC_PER_MINUTE; -} - -static int event_setup_timer_fd( - sd_event *e, - struct clock_data *d, - clockid_t clock) { - - struct epoll_event ev = {}; - int r, fd; - - assert(e); - assert(d); - - if (_likely_(d->fd >= 0)) - return 0; - - fd = timerfd_create(clock, TFD_NONBLOCK|TFD_CLOEXEC); - if (fd < 0) - return -errno; - - ev.events = EPOLLIN; - ev.data.ptr = d; - - r = epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, fd, &ev); - if (r < 0) { - safe_close(fd); - return -errno; - } - - d->fd = fd; - return 0; -} - -static int time_exit_callback(sd_event_source *s, uint64_t usec, void *userdata) { - assert(s); - - return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); -} - -_public_ int sd_event_add_time( - sd_event *e, - sd_event_source **ret, - clockid_t clock, - uint64_t usec, - uint64_t accuracy, - sd_event_time_handler_t callback, - void *userdata) { - - EventSourceType type; - sd_event_source *s; - struct clock_data *d; - int r; - - assert_return(e, -EINVAL); - assert_return(accuracy != (uint64_t) -1, -EINVAL); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(e), -ECHILD); - - if (IN_SET(clock, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM) && - !clock_boottime_supported()) - return -EOPNOTSUPP; - - if (!callback) - callback = time_exit_callback; - - type = clock_to_event_source_type(clock); - assert_return(type >= 0, -EOPNOTSUPP); - - d = event_get_clock_data(e, type); - assert(d); - - r = prioq_ensure_allocated(&d->earliest, earliest_time_prioq_compare); - if (r < 0) - return r; - - r = prioq_ensure_allocated(&d->latest, latest_time_prioq_compare); - if (r < 0) - return r; - - if (d->fd < 0) { - r = event_setup_timer_fd(e, d, clock); - if (r < 0) - return r; - } - - s = source_new(e, !ret, type); - if (!s) - return -ENOMEM; - - s->time.next = usec; - s->time.accuracy = accuracy == 0 ? DEFAULT_ACCURACY_USEC : accuracy; - s->time.callback = callback; - s->time.earliest_index = s->time.latest_index = PRIOQ_IDX_NULL; - s->userdata = userdata; - s->enabled = SD_EVENT_ONESHOT; - - d->needs_rearm = true; - - r = prioq_put(d->earliest, s, &s->time.earliest_index); - if (r < 0) - goto fail; - - r = prioq_put(d->latest, s, &s->time.latest_index); - if (r < 0) - goto fail; - - if (ret) - *ret = s; - - return 0; - -fail: - source_free(s); - return r; -} - -static int signal_exit_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - assert(s); - - return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); -} - -_public_ int sd_event_add_signal( - sd_event *e, - sd_event_source **ret, - int sig, - sd_event_signal_handler_t callback, - void *userdata) { - - sd_event_source *s; - struct signal_data *d; - sigset_t ss; - int r; - - assert_return(e, -EINVAL); - assert_return(SIGNAL_VALID(sig), -EINVAL); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(e), -ECHILD); - - if (!callback) - callback = signal_exit_callback; - - r = pthread_sigmask(SIG_SETMASK, NULL, &ss); - if (r != 0) - return -r; - - if (!sigismember(&ss, sig)) - return -EBUSY; - - if (!e->signal_sources) { - e->signal_sources = new0(sd_event_source*, _NSIG); - if (!e->signal_sources) - return -ENOMEM; - } else if (e->signal_sources[sig]) - return -EBUSY; - - s = source_new(e, !ret, SOURCE_SIGNAL); - if (!s) - return -ENOMEM; - - s->signal.sig = sig; - s->signal.callback = callback; - s->userdata = userdata; - s->enabled = SD_EVENT_ON; - - e->signal_sources[sig] = s; - - r = event_make_signal_data(e, sig, &d); - if (r < 0) { - source_free(s); - return r; - } - - /* Use the signal name as description for the event source by default */ - (void) sd_event_source_set_description(s, signal_to_string(sig)); - - if (ret) - *ret = s; - - return 0; -} - -_public_ int sd_event_add_child( - sd_event *e, - sd_event_source **ret, - pid_t pid, - int options, - sd_event_child_handler_t callback, - void *userdata) { - - sd_event_source *s; - int r; - - assert_return(e, -EINVAL); - assert_return(pid > 1, -EINVAL); - assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL); - assert_return(options != 0, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(e), -ECHILD); - - r = hashmap_ensure_allocated(&e->child_sources, NULL); - if (r < 0) - return r; - - if (hashmap_contains(e->child_sources, PID_TO_PTR(pid))) - return -EBUSY; - - s = source_new(e, !ret, SOURCE_CHILD); - if (!s) - return -ENOMEM; - - s->child.pid = pid; - s->child.options = options; - s->child.callback = callback; - s->userdata = userdata; - s->enabled = SD_EVENT_ONESHOT; - - r = hashmap_put(e->child_sources, PID_TO_PTR(pid), s); - if (r < 0) { - source_free(s); - return r; - } - - e->n_enabled_child_sources++; - - r = event_make_signal_data(e, SIGCHLD, NULL); - if (r < 0) { - e->n_enabled_child_sources--; - source_free(s); - return r; - } - - e->need_process_child = true; - - if (ret) - *ret = s; - - return 0; -} - -_public_ int sd_event_add_defer( - sd_event *e, - sd_event_source **ret, - sd_event_handler_t callback, - void *userdata) { - - sd_event_source *s; - int r; - - assert_return(e, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(e), -ECHILD); - - s = source_new(e, !ret, SOURCE_DEFER); - if (!s) - return -ENOMEM; - - s->defer.callback = callback; - s->userdata = userdata; - s->enabled = SD_EVENT_ONESHOT; - - r = source_set_pending(s, true); - if (r < 0) { - source_free(s); - return r; - } - - if (ret) - *ret = s; - - return 0; -} - -_public_ int sd_event_add_post( - sd_event *e, - sd_event_source **ret, - sd_event_handler_t callback, - void *userdata) { - - sd_event_source *s; - int r; - - assert_return(e, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(e), -ECHILD); - - r = set_ensure_allocated(&e->post_sources, NULL); - if (r < 0) - return r; - - s = source_new(e, !ret, SOURCE_POST); - if (!s) - return -ENOMEM; - - s->post.callback = callback; - s->userdata = userdata; - s->enabled = SD_EVENT_ON; - - r = set_put(e->post_sources, s); - if (r < 0) { - source_free(s); - return r; - } - - if (ret) - *ret = s; - - return 0; -} - -_public_ int sd_event_add_exit( - sd_event *e, - sd_event_source **ret, - sd_event_handler_t callback, - void *userdata) { - - sd_event_source *s; - int r; - - assert_return(e, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(e), -ECHILD); - - r = prioq_ensure_allocated(&e->exit, exit_prioq_compare); - if (r < 0) - return r; - - s = source_new(e, !ret, SOURCE_EXIT); - if (!s) - return -ENOMEM; - - s->exit.callback = callback; - s->userdata = userdata; - s->exit.prioq_index = PRIOQ_IDX_NULL; - s->enabled = SD_EVENT_ONESHOT; - - r = prioq_put(s->event->exit, s, &s->exit.prioq_index); - if (r < 0) { - source_free(s); - return r; - } - - if (ret) - *ret = s; - - return 0; -} - -_public_ sd_event_source* sd_event_source_ref(sd_event_source *s) { - - if (!s) - return NULL; - - assert(s->n_ref >= 1); - s->n_ref++; - - return s; -} - -_public_ sd_event_source* sd_event_source_unref(sd_event_source *s) { - - if (!s) - return NULL; - - assert(s->n_ref >= 1); - s->n_ref--; - - if (s->n_ref <= 0) { - /* Here's a special hack: when we are called from a - * dispatch handler we won't free the event source - * immediately, but we will detach the fd from the - * epoll. This way it is safe for the caller to unref - * the event source and immediately close the fd, but - * we still retain a valid event source object after - * the callback. */ - - if (s->dispatching) { - if (s->type == SOURCE_IO) - source_io_unregister(s); - - source_disconnect(s); - } else - source_free(s); - } - - return NULL; -} - -_public_ int sd_event_source_set_description(sd_event_source *s, const char *description) { - assert_return(s, -EINVAL); - assert_return(!event_pid_changed(s->event), -ECHILD); - - return free_and_strdup(&s->description, description); -} - -_public_ int sd_event_source_get_description(sd_event_source *s, const char **description) { - assert_return(s, -EINVAL); - assert_return(description, -EINVAL); - assert_return(s->description, -ENXIO); - assert_return(!event_pid_changed(s->event), -ECHILD); - - *description = s->description; - return 0; -} - -_public_ sd_event *sd_event_source_get_event(sd_event_source *s) { - assert_return(s, NULL); - - return s->event; -} - -_public_ int sd_event_source_get_pending(sd_event_source *s) { - assert_return(s, -EINVAL); - assert_return(s->type != SOURCE_EXIT, -EDOM); - assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(s->event), -ECHILD); - - return s->pending; -} - -_public_ int sd_event_source_get_io_fd(sd_event_source *s) { - assert_return(s, -EINVAL); - assert_return(s->type == SOURCE_IO, -EDOM); - assert_return(!event_pid_changed(s->event), -ECHILD); - - return s->io.fd; -} - -_public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) { - int r; - - assert_return(s, -EINVAL); - assert_return(fd >= 0, -EBADF); - assert_return(s->type == SOURCE_IO, -EDOM); - assert_return(!event_pid_changed(s->event), -ECHILD); - - if (s->io.fd == fd) - return 0; - - if (s->enabled == SD_EVENT_OFF) { - s->io.fd = fd; - s->io.registered = false; - } else { - int saved_fd; - - saved_fd = s->io.fd; - assert(s->io.registered); - - s->io.fd = fd; - s->io.registered = false; - - r = source_io_register(s, s->enabled, s->io.events); - if (r < 0) { - s->io.fd = saved_fd; - s->io.registered = true; - return r; - } - - epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, saved_fd, NULL); - } - - return 0; -} - -_public_ int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events) { - assert_return(s, -EINVAL); - assert_return(events, -EINVAL); - assert_return(s->type == SOURCE_IO, -EDOM); - assert_return(!event_pid_changed(s->event), -ECHILD); - - *events = s->io.events; - return 0; -} - -_public_ int sd_event_source_set_io_events(sd_event_source *s, uint32_t events) { - int r; - - assert_return(s, -EINVAL); - assert_return(s->type == SOURCE_IO, -EDOM); - assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL); - assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(s->event), -ECHILD); - - /* edge-triggered updates are never skipped, so we can reset edges */ - if (s->io.events == events && !(events & EPOLLET)) - return 0; - - if (s->enabled != SD_EVENT_OFF) { - r = source_io_register(s, s->enabled, events); - if (r < 0) - return r; - } - - s->io.events = events; - source_set_pending(s, false); - - return 0; -} - -_public_ int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revents) { - assert_return(s, -EINVAL); - assert_return(revents, -EINVAL); - assert_return(s->type == SOURCE_IO, -EDOM); - assert_return(s->pending, -ENODATA); - assert_return(!event_pid_changed(s->event), -ECHILD); - - *revents = s->io.revents; - return 0; -} - -_public_ int sd_event_source_get_signal(sd_event_source *s) { - assert_return(s, -EINVAL); - assert_return(s->type == SOURCE_SIGNAL, -EDOM); - assert_return(!event_pid_changed(s->event), -ECHILD); - - return s->signal.sig; -} - -_public_ int sd_event_source_get_priority(sd_event_source *s, int64_t *priority) { - assert_return(s, -EINVAL); - assert_return(!event_pid_changed(s->event), -ECHILD); - - return s->priority; -} - -_public_ int sd_event_source_set_priority(sd_event_source *s, int64_t priority) { - int r; - - assert_return(s, -EINVAL); - assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(s->event), -ECHILD); - - if (s->priority == priority) - return 0; - - if (s->type == SOURCE_SIGNAL && s->enabled != SD_EVENT_OFF) { - struct signal_data *old, *d; - - /* Move us from the signalfd belonging to the old - * priority to the signalfd of the new priority */ - - assert_se(old = hashmap_get(s->event->signal_data, &s->priority)); - - s->priority = priority; - - r = event_make_signal_data(s->event, s->signal.sig, &d); - if (r < 0) { - s->priority = old->priority; - return r; - } - - event_unmask_signal_data(s->event, old, s->signal.sig); - } else - s->priority = priority; - - if (s->pending) - prioq_reshuffle(s->event->pending, s, &s->pending_index); - - if (s->prepare) - prioq_reshuffle(s->event->prepare, s, &s->prepare_index); - - if (s->type == SOURCE_EXIT) - prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index); - - return 0; -} - -_public_ int sd_event_source_get_enabled(sd_event_source *s, int *m) { - assert_return(s, -EINVAL); - assert_return(m, -EINVAL); - assert_return(!event_pid_changed(s->event), -ECHILD); - - *m = s->enabled; - return 0; -} - -_public_ int sd_event_source_set_enabled(sd_event_source *s, int m) { - int r; - - assert_return(s, -EINVAL); - assert_return(m == SD_EVENT_OFF || m == SD_EVENT_ON || m == SD_EVENT_ONESHOT, -EINVAL); - assert_return(!event_pid_changed(s->event), -ECHILD); - - /* If we are dead anyway, we are fine with turning off - * sources, but everything else needs to fail. */ - if (s->event->state == SD_EVENT_FINISHED) - return m == SD_EVENT_OFF ? 0 : -ESTALE; - - if (s->enabled == m) - return 0; - - if (m == SD_EVENT_OFF) { - - switch (s->type) { - - case SOURCE_IO: - source_io_unregister(s); - s->enabled = m; - break; - - case SOURCE_TIME_REALTIME: - case SOURCE_TIME_BOOTTIME: - case SOURCE_TIME_MONOTONIC: - case SOURCE_TIME_REALTIME_ALARM: - case SOURCE_TIME_BOOTTIME_ALARM: { - struct clock_data *d; - - s->enabled = m; - d = event_get_clock_data(s->event, s->type); - assert(d); - - prioq_reshuffle(d->earliest, s, &s->time.earliest_index); - prioq_reshuffle(d->latest, s, &s->time.latest_index); - d->needs_rearm = true; - break; - } - - case SOURCE_SIGNAL: - s->enabled = m; - - event_gc_signal_data(s->event, &s->priority, s->signal.sig); - break; - - case SOURCE_CHILD: - s->enabled = m; - - assert(s->event->n_enabled_child_sources > 0); - s->event->n_enabled_child_sources--; - - event_gc_signal_data(s->event, &s->priority, SIGCHLD); - break; - - case SOURCE_EXIT: - s->enabled = m; - prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index); - break; - - case SOURCE_DEFER: - case SOURCE_POST: - s->enabled = m; - break; - - default: - assert_not_reached("Wut? I shouldn't exist."); - } - - } else { - switch (s->type) { - - case SOURCE_IO: - r = source_io_register(s, m, s->io.events); - if (r < 0) - return r; - - s->enabled = m; - break; - - case SOURCE_TIME_REALTIME: - case SOURCE_TIME_BOOTTIME: - case SOURCE_TIME_MONOTONIC: - case SOURCE_TIME_REALTIME_ALARM: - case SOURCE_TIME_BOOTTIME_ALARM: { - struct clock_data *d; - - s->enabled = m; - d = event_get_clock_data(s->event, s->type); - assert(d); - - prioq_reshuffle(d->earliest, s, &s->time.earliest_index); - prioq_reshuffle(d->latest, s, &s->time.latest_index); - d->needs_rearm = true; - break; - } - - case SOURCE_SIGNAL: - - s->enabled = m; - - r = event_make_signal_data(s->event, s->signal.sig, NULL); - if (r < 0) { - s->enabled = SD_EVENT_OFF; - event_gc_signal_data(s->event, &s->priority, s->signal.sig); - return r; - } - - break; - - case SOURCE_CHILD: - - if (s->enabled == SD_EVENT_OFF) - s->event->n_enabled_child_sources++; - - s->enabled = m; - - r = event_make_signal_data(s->event, SIGCHLD, NULL); - if (r < 0) { - s->enabled = SD_EVENT_OFF; - s->event->n_enabled_child_sources--; - event_gc_signal_data(s->event, &s->priority, SIGCHLD); - return r; - } - - break; - - case SOURCE_EXIT: - s->enabled = m; - prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index); - break; - - case SOURCE_DEFER: - case SOURCE_POST: - s->enabled = m; - break; - - default: - assert_not_reached("Wut? I shouldn't exist."); - } - } - - if (s->pending) - prioq_reshuffle(s->event->pending, s, &s->pending_index); - - if (s->prepare) - prioq_reshuffle(s->event->prepare, s, &s->prepare_index); - - return 0; -} - -_public_ int sd_event_source_get_time(sd_event_source *s, uint64_t *usec) { - assert_return(s, -EINVAL); - assert_return(usec, -EINVAL); - assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); - assert_return(!event_pid_changed(s->event), -ECHILD); - - *usec = s->time.next; - return 0; -} - -_public_ int sd_event_source_set_time(sd_event_source *s, uint64_t usec) { - struct clock_data *d; - - assert_return(s, -EINVAL); - assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); - assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(s->event), -ECHILD); - - s->time.next = usec; - - source_set_pending(s, false); - - d = event_get_clock_data(s->event, s->type); - assert(d); - - prioq_reshuffle(d->earliest, s, &s->time.earliest_index); - prioq_reshuffle(d->latest, s, &s->time.latest_index); - d->needs_rearm = true; - - return 0; -} - -_public_ int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *usec) { - assert_return(s, -EINVAL); - assert_return(usec, -EINVAL); - assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); - assert_return(!event_pid_changed(s->event), -ECHILD); - - *usec = s->time.accuracy; - return 0; -} - -_public_ int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec) { - struct clock_data *d; - - assert_return(s, -EINVAL); - assert_return(usec != (uint64_t) -1, -EINVAL); - assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); - assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(s->event), -ECHILD); - - if (usec == 0) - usec = DEFAULT_ACCURACY_USEC; - - s->time.accuracy = usec; - - source_set_pending(s, false); - - d = event_get_clock_data(s->event, s->type); - assert(d); - - prioq_reshuffle(d->latest, s, &s->time.latest_index); - d->needs_rearm = true; - - return 0; -} - -_public_ int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock) { - assert_return(s, -EINVAL); - assert_return(clock, -EINVAL); - assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); - assert_return(!event_pid_changed(s->event), -ECHILD); - - *clock = event_source_type_to_clock(s->type); - return 0; -} - -_public_ int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid) { - assert_return(s, -EINVAL); - assert_return(pid, -EINVAL); - assert_return(s->type == SOURCE_CHILD, -EDOM); - assert_return(!event_pid_changed(s->event), -ECHILD); - - *pid = s->child.pid; - return 0; -} - -_public_ int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t callback) { - int r; - - assert_return(s, -EINVAL); - assert_return(s->type != SOURCE_EXIT, -EDOM); - assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(s->event), -ECHILD); - - if (s->prepare == callback) - return 0; - - if (callback && s->prepare) { - s->prepare = callback; - return 0; - } - - r = prioq_ensure_allocated(&s->event->prepare, prepare_prioq_compare); - if (r < 0) - return r; - - s->prepare = callback; - - if (callback) { - r = prioq_put(s->event->prepare, s, &s->prepare_index); - if (r < 0) - return r; - } else - prioq_remove(s->event->prepare, s, &s->prepare_index); - - return 0; -} - -_public_ void* sd_event_source_get_userdata(sd_event_source *s) { - assert_return(s, NULL); - - return s->userdata; -} - -_public_ void *sd_event_source_set_userdata(sd_event_source *s, void *userdata) { - void *ret; - - assert_return(s, NULL); - - ret = s->userdata; - s->userdata = userdata; - - return ret; -} - -static usec_t sleep_between(sd_event *e, usec_t a, usec_t b) { - usec_t c; - assert(e); - assert(a <= b); - - if (a <= 0) - return 0; - if (a >= USEC_INFINITY) - return USEC_INFINITY; - - if (b <= a + 1) - return a; - - initialize_perturb(e); - - /* - Find a good time to wake up again between times a and b. We - have two goals here: - - a) We want to wake up as seldom as possible, hence prefer - later times over earlier times. - - b) But if we have to wake up, then let's make sure to - dispatch as much as possible on the entire system. - - We implement this by waking up everywhere at the same time - within any given minute if we can, synchronised via the - perturbation value determined from the boot ID. If we can't, - then we try to find the same spot in every 10s, then 1s and - then 250ms step. Otherwise, we pick the last possible time - to wake up. - */ - - c = (b / USEC_PER_MINUTE) * USEC_PER_MINUTE + e->perturb; - if (c >= b) { - if (_unlikely_(c < USEC_PER_MINUTE)) - return b; - - c -= USEC_PER_MINUTE; - } - - if (c >= a) - return c; - - c = (b / (USEC_PER_SEC*10)) * (USEC_PER_SEC*10) + (e->perturb % (USEC_PER_SEC*10)); - if (c >= b) { - if (_unlikely_(c < USEC_PER_SEC*10)) - return b; - - c -= USEC_PER_SEC*10; - } - - if (c >= a) - return c; - - c = (b / USEC_PER_SEC) * USEC_PER_SEC + (e->perturb % USEC_PER_SEC); - if (c >= b) { - if (_unlikely_(c < USEC_PER_SEC)) - return b; - - c -= USEC_PER_SEC; - } - - if (c >= a) - return c; - - c = (b / (USEC_PER_MSEC*250)) * (USEC_PER_MSEC*250) + (e->perturb % (USEC_PER_MSEC*250)); - if (c >= b) { - if (_unlikely_(c < USEC_PER_MSEC*250)) - return b; - - c -= USEC_PER_MSEC*250; - } - - if (c >= a) - return c; - - return b; -} - -static int event_arm_timer( - sd_event *e, - struct clock_data *d) { - - struct itimerspec its = {}; - sd_event_source *a, *b; - usec_t t; - int r; - - assert(e); - assert(d); - - if (!d->needs_rearm) - return 0; - else - d->needs_rearm = false; - - a = prioq_peek(d->earliest); - if (!a || a->enabled == SD_EVENT_OFF || a->time.next == USEC_INFINITY) { - - if (d->fd < 0) - return 0; - - if (d->next == USEC_INFINITY) - return 0; - - /* disarm */ - r = timerfd_settime(d->fd, TFD_TIMER_ABSTIME, &its, NULL); - if (r < 0) - return r; - - d->next = USEC_INFINITY; - return 0; - } - - b = prioq_peek(d->latest); - assert_se(b && b->enabled != SD_EVENT_OFF); - - t = sleep_between(e, a->time.next, time_event_source_latest(b)); - if (d->next == t) - return 0; - - assert_se(d->fd >= 0); - - if (t == 0) { - /* We don' want to disarm here, just mean some time looooong ago. */ - its.it_value.tv_sec = 0; - its.it_value.tv_nsec = 1; - } else - timespec_store(&its.it_value, t); - - r = timerfd_settime(d->fd, TFD_TIMER_ABSTIME, &its, NULL); - if (r < 0) - return -errno; - - d->next = t; - return 0; -} - -static int process_io(sd_event *e, sd_event_source *s, uint32_t revents) { - assert(e); - assert(s); - assert(s->type == SOURCE_IO); - - /* If the event source was already pending, we just OR in the - * new revents, otherwise we reset the value. The ORing is - * necessary to handle EPOLLONESHOT events properly where - * readability might happen independently of writability, and - * we need to keep track of both */ - - if (s->pending) - s->io.revents |= revents; - else - s->io.revents = revents; - - return source_set_pending(s, true); -} - -static int flush_timer(sd_event *e, int fd, uint32_t events, usec_t *next) { - uint64_t x; - ssize_t ss; - - assert(e); - assert(fd >= 0); - - assert_return(events == EPOLLIN, -EIO); - - ss = read(fd, &x, sizeof(x)); - if (ss < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return -errno; - } - - if (_unlikely_(ss != sizeof(x))) - return -EIO; - - if (next) - *next = USEC_INFINITY; - - return 0; -} - -static int process_timer( - sd_event *e, - usec_t n, - struct clock_data *d) { - - sd_event_source *s; - int r; - - assert(e); - assert(d); - - for (;;) { - s = prioq_peek(d->earliest); - if (!s || - s->time.next > n || - s->enabled == SD_EVENT_OFF || - s->pending) - break; - - r = source_set_pending(s, true); - if (r < 0) - return r; - - prioq_reshuffle(d->earliest, s, &s->time.earliest_index); - prioq_reshuffle(d->latest, s, &s->time.latest_index); - d->needs_rearm = true; - } - - return 0; -} - -static int process_child(sd_event *e) { - sd_event_source *s; - Iterator i; - int r; - - assert(e); - - e->need_process_child = false; - - /* - So, this is ugly. We iteratively invoke waitid() with P_PID - + WNOHANG for each PID we wait for, instead of using - P_ALL. This is because we only want to get child - information of very specific child processes, and not all - of them. We might not have processed the SIGCHLD even of a - previous invocation and we don't want to maintain a - unbounded *per-child* event queue, hence we really don't - want anything flushed out of the kernel's queue that we - don't care about. Since this is O(n) this means that if you - have a lot of processes you probably want to handle SIGCHLD - yourself. - - We do not reap the children here (by using WNOWAIT), this - is only done after the event source is dispatched so that - the callback still sees the process as a zombie. - */ - - HASHMAP_FOREACH(s, e->child_sources, i) { - assert(s->type == SOURCE_CHILD); - - if (s->pending) - continue; - - if (s->enabled == SD_EVENT_OFF) - continue; - - zero(s->child.siginfo); - r = waitid(P_PID, s->child.pid, &s->child.siginfo, - WNOHANG | (s->child.options & WEXITED ? WNOWAIT : 0) | s->child.options); - if (r < 0) - return -errno; - - if (s->child.siginfo.si_pid != 0) { - bool zombie = - s->child.siginfo.si_code == CLD_EXITED || - s->child.siginfo.si_code == CLD_KILLED || - s->child.siginfo.si_code == CLD_DUMPED; - - if (!zombie && (s->child.options & WEXITED)) { - /* If the child isn't dead then let's - * immediately remove the state change - * from the queue, since there's no - * benefit in leaving it queued */ - - assert(s->child.options & (WSTOPPED|WCONTINUED)); - waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG|(s->child.options & (WSTOPPED|WCONTINUED))); - } - - r = source_set_pending(s, true); - if (r < 0) - return r; - } - } - - return 0; -} - -static int process_signal(sd_event *e, struct signal_data *d, uint32_t events) { - bool read_one = false; - int r; - - assert(e); - assert_return(events == EPOLLIN, -EIO); - - /* If there's a signal queued on this priority and SIGCHLD is - on this priority too, then make sure to recheck the - children we watch. This is because we only ever dequeue - the first signal per priority, and if we dequeue one, and - SIGCHLD might be enqueued later we wouldn't know, but we - might have higher priority children we care about hence we - need to check that explicitly. */ - - if (sigismember(&d->sigset, SIGCHLD)) - e->need_process_child = true; - - /* If there's already an event source pending for this - * priority we don't read another */ - if (d->current) - return 0; - - for (;;) { - struct signalfd_siginfo si; - ssize_t n; - sd_event_source *s = NULL; - - n = read(d->fd, &si, sizeof(si)); - if (n < 0) { - if (errno == EAGAIN || errno == EINTR) - return read_one; - - return -errno; - } - - if (_unlikely_(n != sizeof(si))) - return -EIO; - - assert(SIGNAL_VALID(si.ssi_signo)); - - read_one = true; - - if (e->signal_sources) - s = e->signal_sources[si.ssi_signo]; - if (!s) - continue; - if (s->pending) - continue; - - s->signal.siginfo = si; - d->current = s; - - r = source_set_pending(s, true); - if (r < 0) - return r; - - return 1; - } -} - -static int source_dispatch(sd_event_source *s) { - int r = 0; - - assert(s); - assert(s->pending || s->type == SOURCE_EXIT); - - if (s->type != SOURCE_DEFER && s->type != SOURCE_EXIT) { - r = source_set_pending(s, false); - if (r < 0) - return r; - } - - if (s->type != SOURCE_POST) { - sd_event_source *z; - Iterator i; - - /* If we execute a non-post source, let's mark all - * post sources as pending */ - - SET_FOREACH(z, s->event->post_sources, i) { - if (z->enabled == SD_EVENT_OFF) - continue; - - r = source_set_pending(z, true); - if (r < 0) - return r; - } - } - - if (s->enabled == SD_EVENT_ONESHOT) { - r = sd_event_source_set_enabled(s, SD_EVENT_OFF); - if (r < 0) - return r; - } - - s->dispatching = true; - - switch (s->type) { - - case SOURCE_IO: - r = s->io.callback(s, s->io.fd, s->io.revents, s->userdata); - break; - - case SOURCE_TIME_REALTIME: - case SOURCE_TIME_BOOTTIME: - case SOURCE_TIME_MONOTONIC: - case SOURCE_TIME_REALTIME_ALARM: - case SOURCE_TIME_BOOTTIME_ALARM: - r = s->time.callback(s, s->time.next, s->userdata); - break; - - case SOURCE_SIGNAL: - r = s->signal.callback(s, &s->signal.siginfo, s->userdata); - break; - - case SOURCE_CHILD: { - bool zombie; - - zombie = s->child.siginfo.si_code == CLD_EXITED || - s->child.siginfo.si_code == CLD_KILLED || - s->child.siginfo.si_code == CLD_DUMPED; - - r = s->child.callback(s, &s->child.siginfo, s->userdata); - - /* Now, reap the PID for good. */ - if (zombie) - waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG|WEXITED); - - break; - } - - case SOURCE_DEFER: - r = s->defer.callback(s, s->userdata); - break; - - case SOURCE_POST: - r = s->post.callback(s, s->userdata); - break; - - case SOURCE_EXIT: - r = s->exit.callback(s, s->userdata); - break; - - case SOURCE_WATCHDOG: - case _SOURCE_EVENT_SOURCE_TYPE_MAX: - case _SOURCE_EVENT_SOURCE_TYPE_INVALID: - assert_not_reached("Wut? I shouldn't exist."); - } - - s->dispatching = false; - - if (r < 0) - log_debug_errno(r, "Event source %s (type %s) returned error, disabling: %m", - strna(s->description), event_source_type_to_string(s->type)); - - if (s->n_ref == 0) - source_free(s); - else if (r < 0) - sd_event_source_set_enabled(s, SD_EVENT_OFF); - - return 1; -} - -static int event_prepare(sd_event *e) { - int r; - - assert(e); - - for (;;) { - sd_event_source *s; - - s = prioq_peek(e->prepare); - if (!s || s->prepare_iteration == e->iteration || s->enabled == SD_EVENT_OFF) - break; - - s->prepare_iteration = e->iteration; - r = prioq_reshuffle(e->prepare, s, &s->prepare_index); - if (r < 0) - return r; - - assert(s->prepare); - - s->dispatching = true; - r = s->prepare(s, s->userdata); - s->dispatching = false; - - if (r < 0) - log_debug_errno(r, "Prepare callback of event source %s (type %s) returned error, disabling: %m", - strna(s->description), event_source_type_to_string(s->type)); - - if (s->n_ref == 0) - source_free(s); - else if (r < 0) - sd_event_source_set_enabled(s, SD_EVENT_OFF); - } - - return 0; -} - -static int dispatch_exit(sd_event *e) { - sd_event_source *p; - int r; - - assert(e); - - p = prioq_peek(e->exit); - if (!p || p->enabled == SD_EVENT_OFF) { - e->state = SD_EVENT_FINISHED; - return 0; - } - - sd_event_ref(e); - e->iteration++; - e->state = SD_EVENT_EXITING; - - r = source_dispatch(p); - - e->state = SD_EVENT_INITIAL; - sd_event_unref(e); - - return r; -} - -static sd_event_source* event_next_pending(sd_event *e) { - sd_event_source *p; - - assert(e); - - p = prioq_peek(e->pending); - if (!p) - return NULL; - - if (p->enabled == SD_EVENT_OFF) - return NULL; - - return p; -} - -static int arm_watchdog(sd_event *e) { - struct itimerspec its = {}; - usec_t t; - int r; - - assert(e); - assert(e->watchdog_fd >= 0); - - t = sleep_between(e, - e->watchdog_last + (e->watchdog_period / 2), - e->watchdog_last + (e->watchdog_period * 3 / 4)); - - timespec_store(&its.it_value, t); - - /* Make sure we never set the watchdog to 0, which tells the - * kernel to disable it. */ - if (its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0) - its.it_value.tv_nsec = 1; - - r = timerfd_settime(e->watchdog_fd, TFD_TIMER_ABSTIME, &its, NULL); - if (r < 0) - return -errno; - - return 0; -} - -static int process_watchdog(sd_event *e) { - assert(e); - - if (!e->watchdog) - return 0; - - /* Don't notify watchdog too often */ - if (e->watchdog_last + e->watchdog_period / 4 > e->timestamp.monotonic) - return 0; - - sd_notify(false, "WATCHDOG=1"); - e->watchdog_last = e->timestamp.monotonic; - - return arm_watchdog(e); -} - -_public_ int sd_event_prepare(sd_event *e) { - int r; - - assert_return(e, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); - - if (e->exit_requested) - goto pending; - - e->iteration++; - - e->state = SD_EVENT_PREPARING; - r = event_prepare(e); - e->state = SD_EVENT_INITIAL; - if (r < 0) - return r; - - r = event_arm_timer(e, &e->realtime); - if (r < 0) - return r; - - r = event_arm_timer(e, &e->boottime); - if (r < 0) - return r; - - r = event_arm_timer(e, &e->monotonic); - if (r < 0) - return r; - - r = event_arm_timer(e, &e->realtime_alarm); - if (r < 0) - return r; - - r = event_arm_timer(e, &e->boottime_alarm); - if (r < 0) - return r; - - if (event_next_pending(e) || e->need_process_child) - goto pending; - - e->state = SD_EVENT_ARMED; - - return 0; - -pending: - e->state = SD_EVENT_ARMED; - r = sd_event_wait(e, 0); - if (r == 0) - e->state = SD_EVENT_ARMED; - - return r; -} - -_public_ int sd_event_wait(sd_event *e, uint64_t timeout) { - struct epoll_event *ev_queue; - unsigned ev_queue_max; - int r, m, i; - - assert_return(e, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(e->state == SD_EVENT_ARMED, -EBUSY); - - if (e->exit_requested) { - e->state = SD_EVENT_PENDING; - return 1; - } - - ev_queue_max = MAX(e->n_sources, 1u); - ev_queue = newa(struct epoll_event, ev_queue_max); - - m = epoll_wait(e->epoll_fd, ev_queue, ev_queue_max, - timeout == (uint64_t) -1 ? -1 : (int) ((timeout + USEC_PER_MSEC - 1) / USEC_PER_MSEC)); - if (m < 0) { - if (errno == EINTR) { - e->state = SD_EVENT_PENDING; - return 1; - } - - r = -errno; - goto finish; - } - - dual_timestamp_get(&e->timestamp); - if (clock_boottime_supported()) - e->timestamp_boottime = now(CLOCK_BOOTTIME); - - for (i = 0; i < m; i++) { - - if (ev_queue[i].data.ptr == INT_TO_PTR(SOURCE_WATCHDOG)) - r = flush_timer(e, e->watchdog_fd, ev_queue[i].events, NULL); - else { - WakeupType *t = ev_queue[i].data.ptr; - - switch (*t) { - - case WAKEUP_EVENT_SOURCE: - r = process_io(e, ev_queue[i].data.ptr, ev_queue[i].events); - break; - - case WAKEUP_CLOCK_DATA: { - struct clock_data *d = ev_queue[i].data.ptr; - r = flush_timer(e, d->fd, ev_queue[i].events, &d->next); - break; - } - - case WAKEUP_SIGNAL_DATA: - r = process_signal(e, ev_queue[i].data.ptr, ev_queue[i].events); - break; - - default: - assert_not_reached("Invalid wake-up pointer"); - } - } - if (r < 0) - goto finish; - } - - r = process_watchdog(e); - if (r < 0) - goto finish; - - r = process_timer(e, e->timestamp.realtime, &e->realtime); - if (r < 0) - goto finish; - - r = process_timer(e, e->timestamp_boottime, &e->boottime); - if (r < 0) - goto finish; - - r = process_timer(e, e->timestamp.monotonic, &e->monotonic); - if (r < 0) - goto finish; - - r = process_timer(e, e->timestamp.realtime, &e->realtime_alarm); - if (r < 0) - goto finish; - - r = process_timer(e, e->timestamp_boottime, &e->boottime_alarm); - if (r < 0) - goto finish; - - if (e->need_process_child) { - r = process_child(e); - if (r < 0) - goto finish; - } - - if (event_next_pending(e)) { - e->state = SD_EVENT_PENDING; - - return 1; - } - - r = 0; - -finish: - e->state = SD_EVENT_INITIAL; - - return r; -} - -_public_ int sd_event_dispatch(sd_event *e) { - sd_event_source *p; - int r; - - assert_return(e, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(e->state == SD_EVENT_PENDING, -EBUSY); - - if (e->exit_requested) - return dispatch_exit(e); - - p = event_next_pending(e); - if (p) { - sd_event_ref(e); - - e->state = SD_EVENT_RUNNING; - r = source_dispatch(p); - e->state = SD_EVENT_INITIAL; - - sd_event_unref(e); - - return r; - } - - e->state = SD_EVENT_INITIAL; - - return 1; -} - -static void event_log_delays(sd_event *e) { - char b[ELEMENTSOF(e->delays) * DECIMAL_STR_MAX(unsigned) + 1]; - unsigned i; - int o; - - for (i = o = 0; i < ELEMENTSOF(e->delays); i++) { - o += snprintf(&b[o], sizeof(b) - o, "%u ", e->delays[i]); - e->delays[i] = 0; - } - log_debug("Event loop iterations: %.*s", o, b); -} - -_public_ int sd_event_run(sd_event *e, uint64_t timeout) { - int r; - - assert_return(e, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); - - if (e->profile_delays && e->last_run) { - usec_t this_run; - unsigned l; - - this_run = now(CLOCK_MONOTONIC); - - l = u64log2(this_run - e->last_run); - assert(l < sizeof(e->delays)); - e->delays[l]++; - - if (this_run - e->last_log >= 5*USEC_PER_SEC) { - event_log_delays(e); - e->last_log = this_run; - } - } - - r = sd_event_prepare(e); - if (r == 0) - /* There was nothing? Then wait... */ - r = sd_event_wait(e, timeout); - - if (e->profile_delays) - e->last_run = now(CLOCK_MONOTONIC); - - if (r > 0) { - /* There's something now, then let's dispatch it */ - r = sd_event_dispatch(e); - if (r < 0) - return r; - - return 1; - } - - return r; -} - -_public_ int sd_event_loop(sd_event *e) { - int r; - - assert_return(e, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); - - sd_event_ref(e); - - while (e->state != SD_EVENT_FINISHED) { - r = sd_event_run(e, (uint64_t) -1); - if (r < 0) - goto finish; - } - - r = e->exit_code; - -finish: - sd_event_unref(e); - return r; -} - -_public_ int sd_event_get_fd(sd_event *e) { - - assert_return(e, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - - return e->epoll_fd; -} - -_public_ int sd_event_get_state(sd_event *e) { - assert_return(e, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - - return e->state; -} - -_public_ int sd_event_get_exit_code(sd_event *e, int *code) { - assert_return(e, -EINVAL); - assert_return(code, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - - if (!e->exit_requested) - return -ENODATA; - - *code = e->exit_code; - return 0; -} - -_public_ int sd_event_exit(sd_event *e, int code) { - assert_return(e, -EINVAL); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(e), -ECHILD); - - e->exit_requested = true; - e->exit_code = code; - - return 0; -} - -_public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) { - assert_return(e, -EINVAL); - assert_return(usec, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - assert_return(IN_SET(clock, - CLOCK_REALTIME, - CLOCK_REALTIME_ALARM, - CLOCK_MONOTONIC, - CLOCK_BOOTTIME, - CLOCK_BOOTTIME_ALARM), -EOPNOTSUPP); - - if (IN_SET(clock, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM) && !clock_boottime_supported()) - return -EOPNOTSUPP; - - if (!dual_timestamp_is_set(&e->timestamp)) { - /* Implicitly fall back to now() if we never ran - * before and thus have no cached time. */ - *usec = now(clock); - return 1; - } - - switch (clock) { - - case CLOCK_REALTIME: - case CLOCK_REALTIME_ALARM: - *usec = e->timestamp.realtime; - break; - - case CLOCK_MONOTONIC: - *usec = e->timestamp.monotonic; - break; - - case CLOCK_BOOTTIME: - case CLOCK_BOOTTIME_ALARM: - *usec = e->timestamp_boottime; - break; - - default: - assert_not_reached("Unknown clock?"); - } - - return 0; -} - -_public_ int sd_event_default(sd_event **ret) { - - static thread_local sd_event *default_event = NULL; - sd_event *e = NULL; - int r; - - if (!ret) - return !!default_event; - - if (default_event) { - *ret = sd_event_ref(default_event); - return 0; - } - - r = sd_event_new(&e); - if (r < 0) - return r; - - e->default_event_ptr = &default_event; - e->tid = gettid(); - default_event = e; - - *ret = e; - return 1; -} - -_public_ int sd_event_get_tid(sd_event *e, pid_t *tid) { - assert_return(e, -EINVAL); - assert_return(tid, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - - if (e->tid != 0) { - *tid = e->tid; - return 0; - } - - return -ENXIO; -} - -_public_ int sd_event_set_watchdog(sd_event *e, int b) { - int r; - - assert_return(e, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - - if (e->watchdog == !!b) - return e->watchdog; - - if (b) { - struct epoll_event ev = {}; - - r = sd_watchdog_enabled(false, &e->watchdog_period); - if (r <= 0) - return r; - - /* Issue first ping immediately */ - sd_notify(false, "WATCHDOG=1"); - e->watchdog_last = now(CLOCK_MONOTONIC); - - e->watchdog_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC); - if (e->watchdog_fd < 0) - return -errno; - - r = arm_watchdog(e); - if (r < 0) - goto fail; - - ev.events = EPOLLIN; - ev.data.ptr = INT_TO_PTR(SOURCE_WATCHDOG); - - r = epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, e->watchdog_fd, &ev); - if (r < 0) { - r = -errno; - goto fail; - } - - } else { - if (e->watchdog_fd >= 0) { - epoll_ctl(e->epoll_fd, EPOLL_CTL_DEL, e->watchdog_fd, NULL); - e->watchdog_fd = safe_close(e->watchdog_fd); - } - } - - e->watchdog = !!b; - return e->watchdog; - -fail: - e->watchdog_fd = safe_close(e->watchdog_fd); - return r; -} - -_public_ int sd_event_get_watchdog(sd_event *e) { - assert_return(e, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - - return e->watchdog; -} diff --git a/src/libsystemd/sd-event/test-event.c b/src/libsystemd/sd-event/test-event.c deleted file mode 100644 index 289114490c..0000000000 --- a/src/libsystemd/sd-event/test-event.c +++ /dev/null @@ -1,361 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-event.h" - -#include "fd-util.h" -#include "log.h" -#include "macro.h" -#include "signal-util.h" -#include "util.h" - -static int prepare_handler(sd_event_source *s, void *userdata) { - log_info("preparing %c", PTR_TO_INT(userdata)); - return 1; -} - -static bool got_a, got_b, got_c, got_unref; -static unsigned got_d; - -static int unref_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - sd_event_source_unref(s); - got_unref = true; - return 0; -} - -static int io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - - log_info("got IO on %c", PTR_TO_INT(userdata)); - - if (userdata == INT_TO_PTR('a')) { - assert_se(sd_event_source_set_enabled(s, SD_EVENT_OFF) >= 0); - assert_se(!got_a); - got_a = true; - } else if (userdata == INT_TO_PTR('b')) { - assert_se(!got_b); - got_b = true; - } else if (userdata == INT_TO_PTR('d')) { - got_d++; - if (got_d < 2) - assert_se(sd_event_source_set_enabled(s, SD_EVENT_ONESHOT) >= 0); - else - assert_se(sd_event_source_set_enabled(s, SD_EVENT_OFF) >= 0); - } else - assert_not_reached("Yuck!"); - - return 1; -} - -static int child_handler(sd_event_source *s, const siginfo_t *si, void *userdata) { - - assert_se(s); - assert_se(si); - - log_info("got child on %c", PTR_TO_INT(userdata)); - - assert_se(userdata == INT_TO_PTR('f')); - - assert_se(sd_event_exit(sd_event_source_get_event(s), 0) >= 0); - sd_event_source_unref(s); - - return 1; -} - -static int signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - sd_event_source *p = NULL; - pid_t pid; - - assert_se(s); - assert_se(si); - - log_info("got signal on %c", PTR_TO_INT(userdata)); - - assert_se(userdata == INT_TO_PTR('e')); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); - - pid = fork(); - assert_se(pid >= 0); - - if (pid == 0) - _exit(0); - - assert_se(sd_event_add_child(sd_event_source_get_event(s), &p, pid, WEXITED, child_handler, INT_TO_PTR('f')) >= 0); - assert_se(sd_event_source_set_enabled(p, SD_EVENT_ONESHOT) >= 0); - - sd_event_source_unref(s); - - return 1; -} - -static int defer_handler(sd_event_source *s, void *userdata) { - sd_event_source *p = NULL; - - assert_se(s); - - log_info("got defer on %c", PTR_TO_INT(userdata)); - - assert_se(userdata == INT_TO_PTR('d')); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGUSR1, -1) >= 0); - - assert_se(sd_event_add_signal(sd_event_source_get_event(s), &p, SIGUSR1, signal_handler, INT_TO_PTR('e')) >= 0); - assert_se(sd_event_source_set_enabled(p, SD_EVENT_ONESHOT) >= 0); - raise(SIGUSR1); - - sd_event_source_unref(s); - - return 1; -} - -static bool do_quit = false; - -static int time_handler(sd_event_source *s, uint64_t usec, void *userdata) { - log_info("got timer on %c", PTR_TO_INT(userdata)); - - if (userdata == INT_TO_PTR('c')) { - - if (do_quit) { - sd_event_source *p; - - assert_se(sd_event_add_defer(sd_event_source_get_event(s), &p, defer_handler, INT_TO_PTR('d')) >= 0); - assert_se(sd_event_source_set_enabled(p, SD_EVENT_ONESHOT) >= 0); - } else { - assert_se(!got_c); - got_c = true; - } - } else - assert_not_reached("Huh?"); - - return 2; -} - -static bool got_exit = false; - -static int exit_handler(sd_event_source *s, void *userdata) { - log_info("got quit handler on %c", PTR_TO_INT(userdata)); - - got_exit = true; - - return 3; -} - -static bool got_post = false; - -static int post_handler(sd_event_source *s, void *userdata) { - log_info("got post handler"); - - got_post = true; - - return 2; -} - -static void test_basic(void) { - sd_event *e = NULL; - sd_event_source *w = NULL, *x = NULL, *y = NULL, *z = NULL, *q = NULL, *t = NULL; - static const char ch = 'x'; - int a[2] = { -1, -1 }, b[2] = { -1, -1}, d[2] = { -1, -1}, k[2] = { -1, -1 }; - uint64_t event_now; - - assert_se(pipe(a) >= 0); - assert_se(pipe(b) >= 0); - assert_se(pipe(d) >= 0); - assert_se(pipe(k) >= 0); - - assert_se(sd_event_default(&e) >= 0); - assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) > 0); - - assert_se(sd_event_set_watchdog(e, true) >= 0); - - /* Test whether we cleanly can destroy an io event source from its own handler */ - got_unref = false; - assert_se(sd_event_add_io(e, &t, k[0], EPOLLIN, unref_handler, NULL) >= 0); - assert_se(write(k[1], &ch, 1) == 1); - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - assert_se(got_unref); - - got_a = false, got_b = false, got_c = false, got_d = 0; - - /* Add a oneshot handler, trigger it, re-enable it, and trigger - * it again. */ - assert_se(sd_event_add_io(e, &w, d[0], EPOLLIN, io_handler, INT_TO_PTR('d')) >= 0); - assert_se(sd_event_source_set_enabled(w, SD_EVENT_ONESHOT) >= 0); - assert_se(write(d[1], &ch, 1) >= 0); - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - assert_se(got_d == 1); - assert_se(write(d[1], &ch, 1) >= 0); - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - assert_se(got_d == 2); - - assert_se(sd_event_add_io(e, &x, a[0], EPOLLIN, io_handler, INT_TO_PTR('a')) >= 0); - assert_se(sd_event_add_io(e, &y, b[0], EPOLLIN, io_handler, INT_TO_PTR('b')) >= 0); - assert_se(sd_event_add_time(e, &z, CLOCK_MONOTONIC, 0, 0, time_handler, INT_TO_PTR('c')) >= 0); - assert_se(sd_event_add_exit(e, &q, exit_handler, INT_TO_PTR('g')) >= 0); - - assert_se(sd_event_source_set_priority(x, 99) >= 0); - assert_se(sd_event_source_set_enabled(y, SD_EVENT_ONESHOT) >= 0); - assert_se(sd_event_source_set_prepare(x, prepare_handler) >= 0); - assert_se(sd_event_source_set_priority(z, 50) >= 0); - assert_se(sd_event_source_set_enabled(z, SD_EVENT_ONESHOT) >= 0); - assert_se(sd_event_source_set_prepare(z, prepare_handler) >= 0); - - /* Test for floating event sources */ - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN+1, -1) >= 0); - assert_se(sd_event_add_signal(e, NULL, SIGRTMIN+1, NULL, NULL) >= 0); - - assert_se(write(a[1], &ch, 1) >= 0); - assert_se(write(b[1], &ch, 1) >= 0); - - assert_se(!got_a && !got_b && !got_c); - - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - - assert_se(!got_a && got_b && !got_c); - - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - - assert_se(!got_a && got_b && got_c); - - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - - assert_se(got_a && got_b && got_c); - - sd_event_source_unref(x); - sd_event_source_unref(y); - - do_quit = true; - assert_se(sd_event_add_post(e, NULL, post_handler, NULL) >= 0); - assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) == 0); - assert_se(sd_event_source_set_time(z, event_now + 200 * USEC_PER_MSEC) >= 0); - assert_se(sd_event_source_set_enabled(z, SD_EVENT_ONESHOT) >= 0); - - assert_se(sd_event_loop(e) >= 0); - assert_se(got_post); - assert_se(got_exit); - - sd_event_source_unref(z); - sd_event_source_unref(q); - - sd_event_source_unref(w); - - sd_event_unref(e); - - safe_close_pair(a); - safe_close_pair(b); - safe_close_pair(d); - safe_close_pair(k); -} - -static void test_sd_event_now(void) { - _cleanup_(sd_event_unrefp) sd_event *e = NULL; - uint64_t event_now; - - assert_se(sd_event_new(&e) >= 0); - assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) > 0); - assert_se(sd_event_now(e, CLOCK_REALTIME, &event_now) > 0); - assert_se(sd_event_now(e, CLOCK_REALTIME_ALARM, &event_now) > 0); - if (clock_boottime_supported()) { - assert_se(sd_event_now(e, CLOCK_BOOTTIME, &event_now) > 0); - assert_se(sd_event_now(e, CLOCK_BOOTTIME_ALARM, &event_now) > 0); - } - assert_se(sd_event_now(e, -1, &event_now) == -EOPNOTSUPP); - assert_se(sd_event_now(e, 900 /* arbitrary big number */, &event_now) == -EOPNOTSUPP); - - assert_se(sd_event_run(e, 0) == 0); - - assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) == 0); - assert_se(sd_event_now(e, CLOCK_REALTIME, &event_now) == 0); - assert_se(sd_event_now(e, CLOCK_REALTIME_ALARM, &event_now) == 0); - if (clock_boottime_supported()) { - assert_se(sd_event_now(e, CLOCK_BOOTTIME, &event_now) == 0); - assert_se(sd_event_now(e, CLOCK_BOOTTIME_ALARM, &event_now) == 0); - } - assert_se(sd_event_now(e, -1, &event_now) == -EOPNOTSUPP); - assert_se(sd_event_now(e, 900 /* arbitrary big number */, &event_now) == -EOPNOTSUPP); -} - -static int last_rtqueue_sigval = 0; -static int n_rtqueue = 0; - -static int rtqueue_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - last_rtqueue_sigval = si->ssi_int; - n_rtqueue++; - return 0; -} - -static void test_rtqueue(void) { - sd_event_source *u = NULL, *v = NULL, *s = NULL; - sd_event *e = NULL; - - assert_se(sd_event_default(&e) >= 0); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN+2, SIGRTMIN+3, SIGUSR2, -1) >= 0); - assert_se(sd_event_add_signal(e, &u, SIGRTMIN+2, rtqueue_handler, NULL) >= 0); - assert_se(sd_event_add_signal(e, &v, SIGRTMIN+3, rtqueue_handler, NULL) >= 0); - assert_se(sd_event_add_signal(e, &s, SIGUSR2, rtqueue_handler, NULL) >= 0); - - assert_se(sd_event_source_set_priority(v, -10) >= 0); - - assert(sigqueue(getpid(), SIGRTMIN+2, (union sigval) { .sival_int = 1 }) >= 0); - assert(sigqueue(getpid(), SIGRTMIN+3, (union sigval) { .sival_int = 2 }) >= 0); - assert(sigqueue(getpid(), SIGUSR2, (union sigval) { .sival_int = 3 }) >= 0); - assert(sigqueue(getpid(), SIGRTMIN+3, (union sigval) { .sival_int = 4 }) >= 0); - assert(sigqueue(getpid(), SIGUSR2, (union sigval) { .sival_int = 5 }) >= 0); - - assert_se(n_rtqueue == 0); - assert_se(last_rtqueue_sigval == 0); - - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - assert_se(n_rtqueue == 1); - assert_se(last_rtqueue_sigval == 2); /* first SIGRTMIN+3 */ - - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - assert_se(n_rtqueue == 2); - assert_se(last_rtqueue_sigval == 4); /* second SIGRTMIN+3 */ - - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - assert_se(n_rtqueue == 3); - assert_se(last_rtqueue_sigval == 3); /* first SIGUSR2 */ - - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - assert_se(n_rtqueue == 4); - assert_se(last_rtqueue_sigval == 1); /* SIGRTMIN+2 */ - - assert_se(sd_event_run(e, 0) == 0); /* the other SIGUSR2 is dropped, because the first one was still queued */ - assert_se(n_rtqueue == 4); - assert_se(last_rtqueue_sigval == 1); - - sd_event_source_unref(u); - sd_event_source_unref(v); - sd_event_source_unref(s); - - sd_event_unref(e); -} - -int main(int argc, char *argv[]) { - - log_set_max_level(LOG_DEBUG); - log_parse_environment(); - - test_basic(); - test_sd_event_now(); - test_rtqueue(); - - return 0; -} diff --git a/src/libsystemd/sd-hwdb/Makefile b/src/libsystemd/sd-hwdb/Makefile deleted file mode 120000 index 94aaae2c4d..0000000000 --- a/src/libsystemd/sd-hwdb/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-hwdb/hwdb-internal.h b/src/libsystemd/sd-hwdb/hwdb-internal.h deleted file mode 100644 index 8ffb5e5c74..0000000000 --- a/src/libsystemd/sd-hwdb/hwdb-internal.h +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 "sparse-endian.h" -#include "util.h" - -#define HWDB_SIG { 'K', 'S', 'L', 'P', 'H', 'H', 'R', 'H' } - -/* on-disk trie objects */ -struct trie_header_f { - uint8_t signature[8]; - - /* version of tool which created the file */ - le64_t tool_version; - le64_t file_size; - - /* size of structures to allow them to grow */ - le64_t header_size; - le64_t node_size; - le64_t child_entry_size; - le64_t value_entry_size; - - /* offset of the root trie node */ - le64_t nodes_root_off; - - /* size of the nodes and string section */ - le64_t nodes_len; - le64_t strings_len; -} _packed_; - -struct trie_node_f { - /* prefix of lookup string, shared by all children */ - le64_t prefix_off; - /* size of children entry array appended to the node */ - uint8_t children_count; - uint8_t padding[7]; - /* size of value entry array appended to the node */ - le64_t values_count; -} _packed_; - -/* array of child entries, follows directly the node record */ -struct trie_child_entry_f { - /* index of the child node */ - uint8_t c; - uint8_t padding[7]; - /* offset of the child node */ - le64_t child_off; -} _packed_; - -/* array of value entries, follows directly the node record/child array */ -struct trie_value_entry_f { - le64_t key_off; - le64_t value_off; -} _packed_; diff --git a/src/libsystemd/sd-hwdb/hwdb-util.h b/src/libsystemd/sd-hwdb/hwdb-util.h deleted file mode 100644 index 5e21e5008b..0000000000 --- a/src/libsystemd/sd-hwdb/hwdb-util.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 "sd-hwdb.h" - -#include "util.h" - -bool hwdb_validate(sd_hwdb *hwdb); diff --git a/src/libsystemd/sd-hwdb/sd-hwdb.c b/src/libsystemd/sd-hwdb/sd-hwdb.c deleted file mode 100644 index 062fa97b17..0000000000 --- a/src/libsystemd/sd-hwdb/sd-hwdb.c +++ /dev/null @@ -1,470 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 Kay Sievers - Copyright 2008 Alan Jenkins - Copyright 2014 Tom Gundersen - - 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 -#include -#include -#include -#include -#include - -#include "sd-hwdb.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "hashmap.h" -#include "hwdb-internal.h" -#include "hwdb-util.h" -#include "refcnt.h" -#include "string-util.h" - -struct sd_hwdb { - RefCount n_ref; - int refcount; - - FILE *f; - struct stat st; - union { - struct trie_header_f *head; - const char *map; - }; - - char *modalias; - - OrderedHashmap *properties; - Iterator properties_iterator; - bool properties_modified; -}; - -struct linebuf { - char bytes[LINE_MAX]; - size_t size; - size_t len; -}; - -static void linebuf_init(struct linebuf *buf) { - buf->size = 0; - buf->len = 0; -} - -static const char *linebuf_get(struct linebuf *buf) { - if (buf->len + 1 >= sizeof(buf->bytes)) - return NULL; - buf->bytes[buf->len] = '\0'; - return buf->bytes; -} - -static bool linebuf_add(struct linebuf *buf, const char *s, size_t len) { - if (buf->len + len >= sizeof(buf->bytes)) - return false; - memcpy(buf->bytes + buf->len, s, len); - buf->len += len; - return true; -} - -static bool linebuf_add_char(struct linebuf *buf, char c) { - if (buf->len + 1 >= sizeof(buf->bytes)) - return false; - buf->bytes[buf->len++] = c; - return true; -} - -static void linebuf_rem(struct linebuf *buf, size_t count) { - assert(buf->len >= count); - buf->len -= count; -} - -static void linebuf_rem_char(struct linebuf *buf) { - linebuf_rem(buf, 1); -} - -static const struct trie_child_entry_f *trie_node_children(sd_hwdb *hwdb, const struct trie_node_f *node) { - return (const struct trie_child_entry_f *)((const char *)node + le64toh(hwdb->head->node_size)); -} - -static const struct trie_value_entry_f *trie_node_values(sd_hwdb *hwdb, const struct trie_node_f *node) { - const char *base = (const char *)node; - - base += le64toh(hwdb->head->node_size); - base += node->children_count * le64toh(hwdb->head->child_entry_size); - return (const struct trie_value_entry_f *)base; -} - -static const struct trie_node_f *trie_node_from_off(sd_hwdb *hwdb, le64_t off) { - return (const struct trie_node_f *)(hwdb->map + le64toh(off)); -} - -static const char *trie_string(sd_hwdb *hwdb, le64_t off) { - return hwdb->map + le64toh(off); -} - -static int trie_children_cmp_f(const void *v1, const void *v2) { - const struct trie_child_entry_f *n1 = v1; - const struct trie_child_entry_f *n2 = v2; - - return n1->c - n2->c; -} - -static const struct trie_node_f *node_lookup_f(sd_hwdb *hwdb, const struct trie_node_f *node, uint8_t c) { - struct trie_child_entry_f *child; - struct trie_child_entry_f search; - - search.c = c; - child = bsearch(&search, trie_node_children(hwdb, node), node->children_count, - le64toh(hwdb->head->child_entry_size), trie_children_cmp_f); - if (child) - return trie_node_from_off(hwdb, child->child_off); - return NULL; -} - -static int hwdb_add_property(sd_hwdb *hwdb, const char *key, const char *value) { - int r; - - assert(hwdb); - assert(key); - assert(value); - - /* - * Silently ignore all properties which do not start with a - * space; future extensions might use additional prefixes. - */ - if (key[0] != ' ') - return 0; - - key++; - - r = ordered_hashmap_ensure_allocated(&hwdb->properties, &string_hash_ops); - if (r < 0) - return r; - - r = ordered_hashmap_replace(hwdb->properties, key, (char*)value); - if (r < 0) - return r; - - hwdb->properties_modified = true; - - return 0; -} - -static int trie_fnmatch_f(sd_hwdb *hwdb, const struct trie_node_f *node, size_t p, - struct linebuf *buf, const char *search) { - size_t len; - size_t i; - const char *prefix; - int err; - - prefix = trie_string(hwdb, node->prefix_off); - len = strlen(prefix + p); - linebuf_add(buf, prefix + p, len); - - for (i = 0; i < node->children_count; i++) { - const struct trie_child_entry_f *child = &trie_node_children(hwdb, node)[i]; - - linebuf_add_char(buf, child->c); - err = trie_fnmatch_f(hwdb, trie_node_from_off(hwdb, child->child_off), 0, buf, search); - if (err < 0) - return err; - linebuf_rem_char(buf); - } - - if (le64toh(node->values_count) && fnmatch(linebuf_get(buf), search, 0) == 0) - for (i = 0; i < le64toh(node->values_count); i++) { - err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[i].key_off), - trie_string(hwdb, trie_node_values(hwdb, node)[i].value_off)); - if (err < 0) - return err; - } - - linebuf_rem(buf, len); - return 0; -} - -static int trie_search_f(sd_hwdb *hwdb, const char *search) { - struct linebuf buf; - const struct trie_node_f *node; - size_t i = 0; - int err; - - linebuf_init(&buf); - - node = trie_node_from_off(hwdb, hwdb->head->nodes_root_off); - while (node) { - const struct trie_node_f *child; - size_t p = 0; - - if (node->prefix_off) { - uint8_t c; - - for (; (c = trie_string(hwdb, node->prefix_off)[p]); p++) { - if (c == '*' || c == '?' || c == '[') - return trie_fnmatch_f(hwdb, node, p, &buf, search + i + p); - if (c != search[i + p]) - return 0; - } - i += p; - } - - child = node_lookup_f(hwdb, node, '*'); - if (child) { - linebuf_add_char(&buf, '*'); - err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i); - if (err < 0) - return err; - linebuf_rem_char(&buf); - } - - child = node_lookup_f(hwdb, node, '?'); - if (child) { - linebuf_add_char(&buf, '?'); - err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i); - if (err < 0) - return err; - linebuf_rem_char(&buf); - } - - child = node_lookup_f(hwdb, node, '['); - if (child) { - linebuf_add_char(&buf, '['); - err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i); - if (err < 0) - return err; - linebuf_rem_char(&buf); - } - - if (search[i] == '\0') { - size_t n; - - for (n = 0; n < le64toh(node->values_count); n++) { - err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[n].key_off), - trie_string(hwdb, trie_node_values(hwdb, node)[n].value_off)); - if (err < 0) - return err; - } - return 0; - } - - child = node_lookup_f(hwdb, node, search[i]); - node = child; - i++; - } - return 0; -} - -static const char hwdb_bin_paths[] = - "/etc/systemd/hwdb/hwdb.bin\0" - "/etc/udev/hwdb.bin\0" - "/usr/lib/systemd/hwdb/hwdb.bin\0" -#ifdef HAVE_SPLIT_USR - "/lib/systemd/hwdb/hwdb.bin\0" -#endif - UDEVLIBEXECDIR "/hwdb.bin\0"; - -_public_ int sd_hwdb_new(sd_hwdb **ret) { - _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; - const char *hwdb_bin_path; - const char sig[] = HWDB_SIG; - - assert_return(ret, -EINVAL); - - hwdb = new0(sd_hwdb, 1); - if (!hwdb) - return -ENOMEM; - - hwdb->n_ref = REFCNT_INIT; - - /* find hwdb.bin in hwdb_bin_paths */ - NULSTR_FOREACH(hwdb_bin_path, hwdb_bin_paths) { - hwdb->f = fopen(hwdb_bin_path, "re"); - if (hwdb->f) - break; - else if (errno == ENOENT) - continue; - else - return log_debug_errno(errno, "error reading %s: %m", hwdb_bin_path); - } - - if (!hwdb->f) { - log_debug("hwdb.bin does not exist, please run udevadm hwdb --update"); - return -ENOENT; - } - - if (fstat(fileno(hwdb->f), &hwdb->st) < 0 || - (size_t)hwdb->st.st_size < offsetof(struct trie_header_f, strings_len) + 8) - return log_debug_errno(errno, "error reading %s: %m", hwdb_bin_path); - - hwdb->map = mmap(0, hwdb->st.st_size, PROT_READ, MAP_SHARED, fileno(hwdb->f), 0); - if (hwdb->map == MAP_FAILED) - return log_debug_errno(errno, "error mapping %s: %m", hwdb_bin_path); - - if (memcmp(hwdb->map, sig, sizeof(hwdb->head->signature)) != 0 || - (size_t)hwdb->st.st_size != le64toh(hwdb->head->file_size)) { - log_debug("error recognizing the format of %s", hwdb_bin_path); - return -EINVAL; - } - - log_debug("=== trie on-disk ==="); - log_debug("tool version: %"PRIu64, le64toh(hwdb->head->tool_version)); - log_debug("file size: %8"PRIi64" bytes", hwdb->st.st_size); - log_debug("header size %8"PRIu64" bytes", le64toh(hwdb->head->header_size)); - log_debug("strings %8"PRIu64" bytes", le64toh(hwdb->head->strings_len)); - log_debug("nodes %8"PRIu64" bytes", le64toh(hwdb->head->nodes_len)); - - *ret = hwdb; - hwdb = NULL; - - return 0; -} - -_public_ sd_hwdb *sd_hwdb_ref(sd_hwdb *hwdb) { - assert_return(hwdb, NULL); - - assert_se(REFCNT_INC(hwdb->n_ref) >= 2); - - return hwdb; -} - -_public_ sd_hwdb *sd_hwdb_unref(sd_hwdb *hwdb) { - if (hwdb && REFCNT_DEC(hwdb->n_ref) == 0) { - if (hwdb->map) - munmap((void *)hwdb->map, hwdb->st.st_size); - safe_fclose(hwdb->f); - free(hwdb->modalias); - ordered_hashmap_free(hwdb->properties); - free(hwdb); - } - - return NULL; -} - -bool hwdb_validate(sd_hwdb *hwdb) { - bool found = false; - const char* p; - struct stat st; - - if (!hwdb) - return false; - if (!hwdb->f) - return false; - - /* if hwdb.bin doesn't exist anywhere, we need to update */ - NULSTR_FOREACH(p, hwdb_bin_paths) { - if (stat(p, &st) >= 0) { - found = true; - break; - } - } - if (!found) - return true; - - if (timespec_load(&hwdb->st.st_mtim) != timespec_load(&st.st_mtim)) - return true; - return false; -} - -static int properties_prepare(sd_hwdb *hwdb, const char *modalias) { - _cleanup_free_ char *mod = NULL; - int r; - - assert(hwdb); - assert(modalias); - - if (streq_ptr(modalias, hwdb->modalias)) - return 0; - - mod = strdup(modalias); - if (!mod) - return -ENOMEM; - - ordered_hashmap_clear(hwdb->properties); - - hwdb->properties_modified = true; - - r = trie_search_f(hwdb, modalias); - if (r < 0) - return r; - - free(hwdb->modalias); - hwdb->modalias = mod; - mod = NULL; - - return 0; -} - -_public_ int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **_value) { - const char *value; - int r; - - assert_return(hwdb, -EINVAL); - assert_return(hwdb->f, -EINVAL); - assert_return(modalias, -EINVAL); - assert_return(_value, -EINVAL); - - r = properties_prepare(hwdb, modalias); - if (r < 0) - return r; - - value = ordered_hashmap_get(hwdb->properties, key); - if (!value) - return -ENOENT; - - *_value = value; - - return 0; -} - -_public_ int sd_hwdb_seek(sd_hwdb *hwdb, const char *modalias) { - int r; - - assert_return(hwdb, -EINVAL); - assert_return(hwdb->f, -EINVAL); - assert_return(modalias, -EINVAL); - - r = properties_prepare(hwdb, modalias); - if (r < 0) - return r; - - hwdb->properties_modified = false; - hwdb->properties_iterator = ITERATOR_FIRST; - - return 0; -} - -_public_ int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **key, const char **value) { - const void *k; - void *v; - - assert_return(hwdb, -EINVAL); - assert_return(key, -EINVAL); - assert_return(value, -EINVAL); - - if (hwdb->properties_modified) - return -EAGAIN; - - ordered_hashmap_iterate(hwdb->properties, &hwdb->properties_iterator, &v, &k); - if (!k) - return 0; - - *key = k; - *value = v; - - return 1; -} diff --git a/src/libsystemd/sd-id128/Makefile b/src/libsystemd/sd-id128/Makefile deleted file mode 120000 index 94aaae2c4d..0000000000 --- a/src/libsystemd/sd-id128/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-id128/sd-id128.c b/src/libsystemd/sd-id128/sd-id128.c deleted file mode 100644 index d9c0116f60..0000000000 --- a/src/libsystemd/sd-id128/sd-id128.c +++ /dev/null @@ -1,225 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include - -#include "sd-id128.h" - -#include "fd-util.h" -#include "hexdecoct.h" -#include "io-util.h" -#include "macro.h" -#include "random-util.h" -#include "util.h" - -_public_ char *sd_id128_to_string(sd_id128_t id, char s[SD_ID128_STRING_MAX]) { - unsigned n; - - assert_return(s, NULL); - - for (n = 0; n < 16; n++) { - s[n*2] = hexchar(id.bytes[n] >> 4); - s[n*2+1] = hexchar(id.bytes[n] & 0xF); - } - - s[32] = 0; - - return s; -} - -_public_ int sd_id128_from_string(const char s[], sd_id128_t *ret) { - unsigned n, i; - sd_id128_t t; - bool is_guid = false; - - assert_return(s, -EINVAL); - assert_return(ret, -EINVAL); - - for (n = 0, i = 0; n < 16;) { - int a, b; - - if (s[i] == '-') { - /* Is this a GUID? Then be nice, and skip over - * the dashes */ - - if (i == 8) - is_guid = true; - else if (i == 13 || i == 18 || i == 23) { - if (!is_guid) - return -EINVAL; - } else - return -EINVAL; - - i++; - continue; - } - - a = unhexchar(s[i++]); - if (a < 0) - return -EINVAL; - - b = unhexchar(s[i++]); - if (b < 0) - return -EINVAL; - - t.bytes[n++] = (a << 4) | b; - } - - if (i != (is_guid ? 36 : 32)) - return -EINVAL; - - if (s[i] != 0) - return -EINVAL; - - *ret = t; - return 0; -} - -static sd_id128_t make_v4_uuid(sd_id128_t id) { - /* Stolen from generate_random_uuid() of drivers/char/random.c - * in the kernel sources */ - - /* Set UUID version to 4 --- truly random generation */ - id.bytes[6] = (id.bytes[6] & 0x0F) | 0x40; - - /* Set the UUID variant to DCE */ - id.bytes[8] = (id.bytes[8] & 0x3F) | 0x80; - - return id; -} - -_public_ int sd_id128_get_machine(sd_id128_t *ret) { - static thread_local sd_id128_t saved_machine_id; - static thread_local bool saved_machine_id_valid = false; - _cleanup_close_ int fd = -1; - char buf[33]; - unsigned j; - sd_id128_t t; - int r; - - assert_return(ret, -EINVAL); - - if (saved_machine_id_valid) { - *ret = saved_machine_id; - return 0; - } - - fd = open("/etc/machine-id", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return -errno; - - r = loop_read_exact(fd, buf, 33, false); - if (r < 0) - return r; - if (buf[32] !='\n') - return -EIO; - - for (j = 0; j < 16; j++) { - int a, b; - - a = unhexchar(buf[j*2]); - b = unhexchar(buf[j*2+1]); - - if (a < 0 || b < 0) - return -EIO; - - t.bytes[j] = a << 4 | b; - } - - saved_machine_id = t; - saved_machine_id_valid = true; - - *ret = t; - return 0; -} - -_public_ int sd_id128_get_boot(sd_id128_t *ret) { - static thread_local sd_id128_t saved_boot_id; - static thread_local bool saved_boot_id_valid = false; - _cleanup_close_ int fd = -1; - char buf[36]; - unsigned j; - sd_id128_t t; - char *p; - int r; - - assert_return(ret, -EINVAL); - - if (saved_boot_id_valid) { - *ret = saved_boot_id; - return 0; - } - - fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return -errno; - - r = loop_read_exact(fd, buf, 36, false); - if (r < 0) - return r; - - for (j = 0, p = buf; j < 16; j++) { - int a, b; - - if (p >= buf + 35) - return -EIO; - - if (*p == '-') { - p++; - if (p >= buf + 35) - return -EIO; - } - - a = unhexchar(p[0]); - b = unhexchar(p[1]); - - if (a < 0 || b < 0) - return -EIO; - - t.bytes[j] = a << 4 | b; - - p += 2; - } - - saved_boot_id = t; - saved_boot_id_valid = true; - - *ret = t; - return 0; -} - -_public_ int sd_id128_randomize(sd_id128_t *ret) { - sd_id128_t t; - int r; - - assert_return(ret, -EINVAL); - - r = dev_urandom(&t, sizeof(t)); - if (r < 0) - return r; - - /* Turn this into a valid v4 UUID, to be nice. Note that we - * only guarantee this for newly generated UUIDs, not for - * pre-existing ones. */ - - *ret = make_v4_uuid(t); - return 0; -} diff --git a/src/libsystemd/sd-login/Makefile b/src/libsystemd/sd-login/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/libsystemd/sd-login/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-login/sd-login.c b/src/libsystemd/sd-login/sd-login.c deleted file mode 100644 index 9d4f187502..0000000000 --- a/src/libsystemd/sd-login/sd-login.c +++ /dev/null @@ -1,1062 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include -#include -#include - -#include "sd-login.h" - -#include "alloc-util.h" -#include "cgroup-util.h" -#include "dirent-util.h" -#include "escape.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "hostname-util.h" -#include "io-util.h" -#include "login-util.h" -#include "macro.h" -#include "parse-util.h" -#include "path-util.h" -#include "socket-util.h" -#include "string-util.h" -#include "strv.h" -#include "user-util.h" -#include "util.h" - -/* Error codes: - * - * invalid input parameters → -EINVAL - * invalid fd → -EBADF - * process does not exist → -ESRCH - * cgroup does not exist → -ENOENT - * machine, session does not exist → -ENXIO - * requested metadata on object is missing → -ENODATA - */ - -_public_ int sd_pid_get_session(pid_t pid, char **session) { - - assert_return(pid >= 0, -EINVAL); - assert_return(session, -EINVAL); - - return cg_pid_get_session(pid, session); -} - -_public_ int sd_pid_get_unit(pid_t pid, char **unit) { - - assert_return(pid >= 0, -EINVAL); - assert_return(unit, -EINVAL); - - return cg_pid_get_unit(pid, unit); -} - -_public_ int sd_pid_get_user_unit(pid_t pid, char **unit) { - - assert_return(pid >= 0, -EINVAL); - assert_return(unit, -EINVAL); - - return cg_pid_get_user_unit(pid, unit); -} - -_public_ int sd_pid_get_machine_name(pid_t pid, char **name) { - - assert_return(pid >= 0, -EINVAL); - assert_return(name, -EINVAL); - - return cg_pid_get_machine_name(pid, name); -} - -_public_ int sd_pid_get_slice(pid_t pid, char **slice) { - - assert_return(pid >= 0, -EINVAL); - assert_return(slice, -EINVAL); - - return cg_pid_get_slice(pid, slice); -} - -_public_ int sd_pid_get_user_slice(pid_t pid, char **slice) { - - assert_return(pid >= 0, -EINVAL); - assert_return(slice, -EINVAL); - - return cg_pid_get_user_slice(pid, slice); -} - -_public_ int sd_pid_get_owner_uid(pid_t pid, uid_t *uid) { - - assert_return(pid >= 0, -EINVAL); - assert_return(uid, -EINVAL); - - return cg_pid_get_owner_uid(pid, uid); -} - -_public_ int sd_pid_get_cgroup(pid_t pid, char **cgroup) { - char *c; - int r; - - assert_return(pid >= 0, -EINVAL); - assert_return(cgroup, -EINVAL); - - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &c); - if (r < 0) - return r; - - /* The internal APIs return the empty string for the root - * cgroup, let's return the "/" in the public APIs instead, as - * that's easier and less ambigious for people to grok. */ - if (isempty(c)) { - free(c); - c = strdup("/"); - if (!c) - return -ENOMEM; - - } - - *cgroup = c; - return 0; -} - -_public_ int sd_peer_get_session(int fd, char **session) { - struct ucred ucred = {}; - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(session, -EINVAL); - - r = getpeercred(fd, &ucred); - if (r < 0) - return r; - - return cg_pid_get_session(ucred.pid, session); -} - -_public_ int sd_peer_get_owner_uid(int fd, uid_t *uid) { - struct ucred ucred; - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(uid, -EINVAL); - - r = getpeercred(fd, &ucred); - if (r < 0) - return r; - - return cg_pid_get_owner_uid(ucred.pid, uid); -} - -_public_ int sd_peer_get_unit(int fd, char **unit) { - struct ucred ucred; - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(unit, -EINVAL); - - r = getpeercred(fd, &ucred); - if (r < 0) - return r; - - return cg_pid_get_unit(ucred.pid, unit); -} - -_public_ int sd_peer_get_user_unit(int fd, char **unit) { - struct ucred ucred; - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(unit, -EINVAL); - - r = getpeercred(fd, &ucred); - if (r < 0) - return r; - - return cg_pid_get_user_unit(ucred.pid, unit); -} - -_public_ int sd_peer_get_machine_name(int fd, char **machine) { - struct ucred ucred; - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(machine, -EINVAL); - - r = getpeercred(fd, &ucred); - if (r < 0) - return r; - - return cg_pid_get_machine_name(ucred.pid, machine); -} - -_public_ int sd_peer_get_slice(int fd, char **slice) { - struct ucred ucred; - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(slice, -EINVAL); - - r = getpeercred(fd, &ucred); - if (r < 0) - return r; - - return cg_pid_get_slice(ucred.pid, slice); -} - -_public_ int sd_peer_get_user_slice(int fd, char **slice) { - struct ucred ucred; - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(slice, -EINVAL); - - r = getpeercred(fd, &ucred); - if (r < 0) - return r; - - return cg_pid_get_user_slice(ucred.pid, slice); -} - -_public_ int sd_peer_get_cgroup(int fd, char **cgroup) { - struct ucred ucred; - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(cgroup, -EINVAL); - - r = getpeercred(fd, &ucred); - if (r < 0) - return r; - - return sd_pid_get_cgroup(ucred.pid, cgroup); -} - -static int file_of_uid(uid_t uid, char **p) { - - assert_return(uid_is_valid(uid), -EINVAL); - assert(p); - - if (asprintf(p, "/run/systemd/users/" UID_FMT, uid) < 0) - return -ENOMEM; - - return 0; -} - -_public_ int sd_uid_get_state(uid_t uid, char**state) { - _cleanup_free_ char *p = NULL; - char *s = NULL; - int r; - - assert_return(state, -EINVAL); - - r = file_of_uid(uid, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, "STATE", &s, NULL); - if (r == -ENOENT) { - free(s); - s = strdup("offline"); - if (!s) - return -ENOMEM; - - } - if (r < 0) { - free(s); - return r; - } - if (isempty(s)) { - free(s); - return -EIO; - } - - *state = s; - return 0; -} - -_public_ int sd_uid_get_display(uid_t uid, char **session) { - _cleanup_free_ char *p = NULL, *s = NULL; - int r; - - assert_return(session, -EINVAL); - - r = file_of_uid(uid, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, "DISPLAY", &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; - - *session = s; - s = NULL; - - return 0; -} - -static int file_of_seat(const char *seat, char **_p) { - char *p; - int r; - - assert(_p); - - if (seat) { - if (!filename_is_valid(seat)) - return -EINVAL; - - p = strappend("/run/systemd/seats/", seat); - } else { - _cleanup_free_ char *buf = NULL; - - r = sd_session_get_seat(NULL, &buf); - if (r < 0) - return r; - - p = strappend("/run/systemd/seats/", buf); - } - - if (!p) - return -ENOMEM; - - *_p = p; - p = NULL; - return 0; -} - -_public_ int sd_uid_is_on_seat(uid_t uid, int require_active, const char *seat) { - _cleanup_free_ char *t = NULL, *s = NULL, *p = NULL; - size_t l; - int r; - const char *word, *variable, *state; - - assert_return(uid_is_valid(uid), -EINVAL); - - r = file_of_seat(seat, &p); - if (r < 0) - return r; - - variable = require_active ? "ACTIVE_UID" : "UIDS"; - - r = parse_env_file(p, NEWLINE, variable, &s, NULL); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - if (isempty(s)) - return 0; - - if (asprintf(&t, UID_FMT, uid) < 0) - return -ENOMEM; - - FOREACH_WORD(word, l, s, state) - if (strneq(t, word, l)) - return 1; - - return 0; -} - -static int uid_get_array(uid_t uid, const char *variable, char ***array) { - _cleanup_free_ char *p = NULL, *s = NULL; - char **a; - int r; - - assert(variable); - - r = file_of_uid(uid, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, variable, &s, NULL); - if (r == -ENOENT || (r >= 0 && isempty(s))) { - if (array) - *array = NULL; - return 0; - } - if (r < 0) - return r; - - a = strv_split(s, " "); - if (!a) - return -ENOMEM; - - strv_uniq(a); - r = strv_length(a); - - if (array) - *array = a; - else - strv_free(a); - - return r; -} - -_public_ int sd_uid_get_sessions(uid_t uid, int require_active, char ***sessions) { - return uid_get_array( - uid, - require_active == 0 ? "ONLINE_SESSIONS" : - require_active > 0 ? "ACTIVE_SESSIONS" : - "SESSIONS", - sessions); -} - -_public_ int sd_uid_get_seats(uid_t uid, int require_active, char ***seats) { - return uid_get_array( - uid, - require_active == 0 ? "ONLINE_SEATS" : - require_active > 0 ? "ACTIVE_SEATS" : - "SEATS", - seats); -} - -static int file_of_session(const char *session, char **_p) { - char *p; - int r; - - assert(_p); - - if (session) { - if (!session_id_valid(session)) - return -EINVAL; - - p = strappend("/run/systemd/sessions/", session); - } else { - _cleanup_free_ char *buf = NULL; - - r = sd_pid_get_session(0, &buf); - if (r < 0) - return r; - - p = strappend("/run/systemd/sessions/", buf); - } - - if (!p) - return -ENOMEM; - - *_p = p; - return 0; -} - -_public_ int sd_session_is_active(const char *session) { - _cleanup_free_ char *p = NULL, *s = NULL; - int r; - - r = file_of_session(session, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, "ACTIVE", &s, NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - if (isempty(s)) - return -EIO; - - return parse_boolean(s); -} - -_public_ int sd_session_is_remote(const char *session) { - _cleanup_free_ char *p = NULL, *s = NULL; - int r; - - r = file_of_session(session, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, "REMOTE", &s, NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; - - return parse_boolean(s); -} - -_public_ int sd_session_get_state(const char *session, char **state) { - _cleanup_free_ char *p = NULL, *s = NULL; - int r; - - assert_return(state, -EINVAL); - - r = file_of_session(session, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, "STATE", &s, NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - if (isempty(s)) - return -EIO; - - *state = s; - s = NULL; - - return 0; -} - -_public_ int sd_session_get_uid(const char *session, uid_t *uid) { - int r; - _cleanup_free_ char *p = NULL, *s = NULL; - - assert_return(uid, -EINVAL); - - r = file_of_session(session, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, "UID", &s, NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - if (isempty(s)) - return -EIO; - - return parse_uid(s, uid); -} - -static int session_get_string(const char *session, const char *field, char **value) { - _cleanup_free_ char *p = NULL, *s = NULL; - int r; - - assert_return(value, -EINVAL); - assert(field); - - r = file_of_session(session, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, field, &s, NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; - - *value = s; - s = NULL; - return 0; -} - -_public_ int sd_session_get_seat(const char *session, char **seat) { - return session_get_string(session, "SEAT", seat); -} - -_public_ int sd_session_get_tty(const char *session, char **tty) { - return session_get_string(session, "TTY", tty); -} - -_public_ int sd_session_get_vt(const char *session, unsigned *vtnr) { - _cleanup_free_ char *vtnr_string = NULL; - unsigned u; - int r; - - assert_return(vtnr, -EINVAL); - - r = session_get_string(session, "VTNR", &vtnr_string); - if (r < 0) - return r; - - r = safe_atou(vtnr_string, &u); - if (r < 0) - return r; - - *vtnr = u; - return 0; -} - -_public_ int sd_session_get_service(const char *session, char **service) { - return session_get_string(session, "SERVICE", service); -} - -_public_ int sd_session_get_type(const char *session, char **type) { - return session_get_string(session, "TYPE", type); -} - -_public_ int sd_session_get_class(const char *session, char **class) { - return session_get_string(session, "CLASS", class); -} - -_public_ int sd_session_get_desktop(const char *session, char **desktop) { - _cleanup_free_ char *escaped = NULL; - char *t; - int r; - - assert_return(desktop, -EINVAL); - - r = session_get_string(session, "DESKTOP", &escaped); - if (r < 0) - return r; - - r = cunescape(escaped, 0, &t); - if (r < 0) - return r; - - *desktop = t; - return 0; -} - -_public_ int sd_session_get_display(const char *session, char **display) { - return session_get_string(session, "DISPLAY", display); -} - -_public_ int sd_session_get_remote_user(const char *session, char **remote_user) { - return session_get_string(session, "REMOTE_USER", remote_user); -} - -_public_ int sd_session_get_remote_host(const char *session, char **remote_host) { - return session_get_string(session, "REMOTE_HOST", remote_host); -} - -_public_ int sd_seat_get_active(const char *seat, char **session, uid_t *uid) { - _cleanup_free_ char *p = NULL, *s = NULL, *t = NULL; - int r; - - assert_return(session || uid, -EINVAL); - - r = file_of_seat(seat, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, - "ACTIVE", &s, - "ACTIVE_UID", &t, - NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - - if (session && !s) - return -ENODATA; - - if (uid && !t) - return -ENODATA; - - if (uid && t) { - r = parse_uid(t, uid); - if (r < 0) - return r; - } - - if (session && s) { - *session = s; - s = NULL; - } - - return 0; -} - -_public_ int sd_seat_get_sessions(const char *seat, char ***sessions, uid_t **uids, unsigned *n_uids) { - _cleanup_free_ char *p = NULL, *s = NULL, *t = NULL; - _cleanup_strv_free_ char **a = NULL; - _cleanup_free_ uid_t *b = NULL; - unsigned n = 0; - int r; - - r = file_of_seat(seat, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, - "SESSIONS", &s, - "ACTIVE_SESSIONS", &t, - NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - - if (s) { - a = strv_split(s, " "); - if (!a) - return -ENOMEM; - } - - if (uids && t) { - const char *word, *state; - size_t l; - - FOREACH_WORD(word, l, t, state) - n++; - - if (n > 0) { - unsigned i = 0; - - b = new(uid_t, n); - if (!b) - return -ENOMEM; - - FOREACH_WORD(word, l, t, state) { - _cleanup_free_ char *k = NULL; - - k = strndup(word, l); - if (!k) - return -ENOMEM; - - r = parse_uid(k, b + i); - if (r < 0) - continue; - - i++; - } - } - } - - r = strv_length(a); - - if (sessions) { - *sessions = a; - a = NULL; - } - - if (uids) { - *uids = b; - b = NULL; - } - - if (n_uids) - *n_uids = n; - - return r; -} - -static int seat_get_can(const char *seat, const char *variable) { - _cleanup_free_ char *p = NULL, *s = NULL; - int r; - - assert(variable); - - r = file_of_seat(seat, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, - variable, &s, - NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; - - return parse_boolean(s); -} - -_public_ int sd_seat_can_multi_session(const char *seat) { - return seat_get_can(seat, "CAN_MULTI_SESSION"); -} - -_public_ int sd_seat_can_tty(const char *seat) { - return seat_get_can(seat, "CAN_TTY"); -} - -_public_ int sd_seat_can_graphical(const char *seat) { - return seat_get_can(seat, "CAN_GRAPHICAL"); -} - -_public_ int sd_get_seats(char ***seats) { - return get_files_in_directory("/run/systemd/seats/", seats); -} - -_public_ int sd_get_sessions(char ***sessions) { - return get_files_in_directory("/run/systemd/sessions/", sessions); -} - -_public_ int sd_get_uids(uid_t **users) { - _cleanup_closedir_ DIR *d; - int r = 0; - unsigned n = 0; - _cleanup_free_ uid_t *l = NULL; - - d = opendir("/run/systemd/users/"); - if (!d) - return -errno; - - for (;;) { - struct dirent *de; - int k; - uid_t uid; - - errno = 0; - de = readdir(d); - if (!de && errno > 0) - return -errno; - - if (!de) - break; - - dirent_ensure_type(d, de); - - if (!dirent_is_file(de)) - continue; - - k = parse_uid(de->d_name, &uid); - if (k < 0) - continue; - - if (users) { - if ((unsigned) r >= n) { - uid_t *t; - - n = MAX(16, 2*r); - t = realloc(l, sizeof(uid_t) * n); - if (!t) - return -ENOMEM; - - l = t; - } - - assert((unsigned) r < n); - l[r++] = uid; - } else - r++; - } - - if (users) { - *users = l; - l = NULL; - } - - return r; -} - -_public_ int sd_get_machine_names(char ***machines) { - char **l = NULL, **a, **b; - int r; - - assert_return(machines, -EINVAL); - - r = get_files_in_directory("/run/systemd/machines/", &l); - if (r < 0) - return r; - - if (l) { - r = 0; - - /* Filter out the unit: symlinks */ - for (a = l, b = l; *a; a++) { - if (startswith(*a, "unit:") || !machine_name_is_valid(*a)) - free(*a); - else { - *b = *a; - b++; - r++; - } - } - - *b = NULL; - } - - *machines = l; - return r; -} - -_public_ int sd_machine_get_class(const char *machine, char **class) { - _cleanup_free_ char *c = NULL; - const char *p; - int r; - - assert_return(machine_name_is_valid(machine), -EINVAL); - assert_return(class, -EINVAL); - - p = strjoina("/run/systemd/machines/", machine); - r = parse_env_file(p, NEWLINE, "CLASS", &c, NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - if (!c) - return -EIO; - - *class = c; - c = NULL; - - return 0; -} - -_public_ int sd_machine_get_ifindices(const char *machine, int **ifindices) { - _cleanup_free_ char *netif = NULL; - size_t l, allocated = 0, nr = 0; - int *ni = NULL; - const char *p, *word, *state; - int r; - - assert_return(machine_name_is_valid(machine), -EINVAL); - assert_return(ifindices, -EINVAL); - - p = strjoina("/run/systemd/machines/", machine); - r = parse_env_file(p, NEWLINE, "NETIF", &netif, NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - if (!netif) { - *ifindices = NULL; - return 0; - } - - FOREACH_WORD(word, l, netif, state) { - char buf[l+1]; - int ifi; - - *(char*) (mempcpy(buf, word, l)) = 0; - - if (parse_ifindex(buf, &ifi) < 0) - continue; - - if (!GREEDY_REALLOC(ni, allocated, nr+1)) { - free(ni); - return -ENOMEM; - } - - ni[nr++] = ifi; - } - - *ifindices = ni; - return nr; -} - -static inline int MONITOR_TO_FD(sd_login_monitor *m) { - return (int) (unsigned long) m - 1; -} - -static inline sd_login_monitor* FD_TO_MONITOR(int fd) { - return (sd_login_monitor*) (unsigned long) (fd + 1); -} - -_public_ int sd_login_monitor_new(const char *category, sd_login_monitor **m) { - int fd, k; - bool good = false; - - assert_return(m, -EINVAL); - - fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); - if (fd < 0) - return -errno; - - if (!category || streq(category, "seat")) { - k = inotify_add_watch(fd, "/run/systemd/seats/", IN_MOVED_TO|IN_DELETE); - if (k < 0) { - safe_close(fd); - return -errno; - } - - good = true; - } - - if (!category || streq(category, "session")) { - k = inotify_add_watch(fd, "/run/systemd/sessions/", IN_MOVED_TO|IN_DELETE); - if (k < 0) { - safe_close(fd); - return -errno; - } - - good = true; - } - - if (!category || streq(category, "uid")) { - k = inotify_add_watch(fd, "/run/systemd/users/", IN_MOVED_TO|IN_DELETE); - if (k < 0) { - safe_close(fd); - return -errno; - } - - good = true; - } - - if (!category || streq(category, "machine")) { - k = inotify_add_watch(fd, "/run/systemd/machines/", IN_MOVED_TO|IN_DELETE); - if (k < 0) { - safe_close(fd); - return -errno; - } - - good = true; - } - - if (!good) { - close_nointr(fd); - return -EINVAL; - } - - *m = FD_TO_MONITOR(fd); - return 0; -} - -_public_ sd_login_monitor* sd_login_monitor_unref(sd_login_monitor *m) { - int fd; - - if (!m) - return NULL; - - fd = MONITOR_TO_FD(m); - close_nointr(fd); - - return NULL; -} - -_public_ int sd_login_monitor_flush(sd_login_monitor *m) { - - assert_return(m, -EINVAL); - - return flush_fd(MONITOR_TO_FD(m)); -} - -_public_ int sd_login_monitor_get_fd(sd_login_monitor *m) { - - assert_return(m, -EINVAL); - - return MONITOR_TO_FD(m); -} - -_public_ int sd_login_monitor_get_events(sd_login_monitor *m) { - - assert_return(m, -EINVAL); - - /* For now we will only return POLLIN here, since we don't - * need anything else ever for inotify. However, let's have - * this API to keep our options open should we later on need - * it. */ - return POLLIN; -} - -_public_ int sd_login_monitor_get_timeout(sd_login_monitor *m, uint64_t *timeout_usec) { - - assert_return(m, -EINVAL); - assert_return(timeout_usec, -EINVAL); - - /* For now we will only return (uint64_t) -1, since we don't - * need any timeout. However, let's have this API to keep our - * options open should we later on need it. */ - *timeout_usec = (uint64_t) -1; - return 0; -} diff --git a/src/libsystemd/sd-login/test-login.c b/src/libsystemd/sd-login/test-login.c deleted file mode 100644 index c1fd7dd33e..0000000000 --- a/src/libsystemd/sd-login/test-login.c +++ /dev/null @@ -1,264 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 - -#include "sd-login.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -static void test_login(void) { - _cleanup_close_pair_ int pair[2] = { -1, -1 }; - _cleanup_free_ char *pp = NULL, *qq = NULL; - int r, k; - uid_t u, u2; - char *seat, *type, *class, *display, *remote_user, *remote_host, *display_session, *cgroup; - char *session; - char *state; - char *session2; - char *t; - char **seats, **sessions, **machines; - uid_t *uids; - unsigned n; - struct pollfd pollfd; - sd_login_monitor *m = NULL; - - assert_se(sd_pid_get_session(0, &session) == 0); - printf("session = %s\n", session); - - assert_se(sd_pid_get_owner_uid(0, &u2) == 0); - printf("user = "UID_FMT"\n", u2); - - assert_se(sd_pid_get_cgroup(0, &cgroup) == 0); - printf("cgroup = %s\n", cgroup); - free(cgroup); - - display_session = NULL; - r = sd_uid_get_display(u2, &display_session); - assert_se(r >= 0 || r == -ENODATA); - printf("user's display session = %s\n", strna(display_session)); - free(display_session); - - assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == 0); - sd_peer_get_session(pair[0], &pp); - sd_peer_get_session(pair[1], &qq); - assert_se(streq_ptr(pp, qq)); - - r = sd_uid_get_sessions(u2, false, &sessions); - assert_se(r >= 0); - assert_se(r == (int) strv_length(sessions)); - assert_se(t = strv_join(sessions, ", ")); - strv_free(sessions); - printf("sessions = %s\n", t); - free(t); - - assert_se(r == sd_uid_get_sessions(u2, false, NULL)); - - r = sd_uid_get_seats(u2, false, &seats); - assert_se(r >= 0); - assert_se(r == (int) strv_length(seats)); - assert_se(t = strv_join(seats, ", ")); - strv_free(seats); - printf("seats = %s\n", t); - free(t); - - assert_se(r == sd_uid_get_seats(u2, false, NULL)); - - r = sd_session_is_active(session); - assert_se(r >= 0); - printf("active = %s\n", yes_no(r)); - - r = sd_session_is_remote(session); - assert_se(r >= 0); - printf("remote = %s\n", yes_no(r)); - - r = sd_session_get_state(session, &state); - assert_se(r >= 0); - printf("state = %s\n", state); - free(state); - - assert_se(sd_session_get_uid(session, &u) >= 0); - printf("uid = "UID_FMT"\n", u); - assert_se(u == u2); - - assert_se(sd_session_get_type(session, &type) >= 0); - printf("type = %s\n", type); - free(type); - - assert_se(sd_session_get_class(session, &class) >= 0); - printf("class = %s\n", class); - free(class); - - display = NULL; - r = sd_session_get_display(session, &display); - assert_se(r >= 0 || r == -ENODATA); - printf("display = %s\n", strna(display)); - free(display); - - remote_user = NULL; - r = sd_session_get_remote_user(session, &remote_user); - assert_se(r >= 0 || r == -ENODATA); - printf("remote_user = %s\n", strna(remote_user)); - free(remote_user); - - remote_host = NULL; - r = sd_session_get_remote_host(session, &remote_host); - assert_se(r >= 0 || r == -ENODATA); - printf("remote_host = %s\n", strna(remote_host)); - free(remote_host); - - assert_se(sd_session_get_seat(session, &seat) >= 0); - printf("seat = %s\n", seat); - - r = sd_seat_can_multi_session(seat); - assert_se(r >= 0); - printf("can do multi session = %s\n", yes_no(r)); - - r = sd_seat_can_tty(seat); - assert_se(r >= 0); - printf("can do tty = %s\n", yes_no(r)); - - r = sd_seat_can_graphical(seat); - assert_se(r >= 0); - printf("can do graphical = %s\n", yes_no(r)); - - assert_se(sd_uid_get_state(u, &state) >= 0); - printf("state = %s\n", state); - - assert_se(sd_uid_is_on_seat(u, 0, seat) > 0); - - k = sd_uid_is_on_seat(u, 1, seat); - assert_se(k >= 0); - assert_se(!!r == !!r); - - assert_se(sd_seat_get_active(seat, &session2, &u2) >= 0); - printf("session2 = %s\n", session2); - printf("uid2 = "UID_FMT"\n", u2); - - r = sd_seat_get_sessions(seat, &sessions, &uids, &n); - assert_se(r >= 0); - printf("n_sessions = %i\n", r); - assert_se(r == (int) strv_length(sessions)); - assert_se(t = strv_join(sessions, ", ")); - strv_free(sessions); - printf("sessions = %s\n", t); - free(t); - printf("uids ="); - for (k = 0; k < (int) n; k++) - printf(" "UID_FMT, uids[k]); - printf("\n"); - free(uids); - - assert_se(sd_seat_get_sessions(seat, NULL, NULL, NULL) == r); - - free(session); - free(state); - free(session2); - free(seat); - - r = sd_get_seats(&seats); - assert_se(r >= 0); - assert_se(r == (int) strv_length(seats)); - assert_se(t = strv_join(seats, ", ")); - strv_free(seats); - printf("n_seats = %i\n", r); - printf("seats = %s\n", t); - free(t); - - assert_se(sd_get_seats(NULL) == r); - - r = sd_seat_get_active(NULL, &t, NULL); - assert_se(r >= 0); - printf("active session on current seat = %s\n", t); - free(t); - - r = sd_get_sessions(&sessions); - assert_se(r >= 0); - assert_se(r == (int) strv_length(sessions)); - assert_se(t = strv_join(sessions, ", ")); - strv_free(sessions); - printf("n_sessions = %i\n", r); - printf("sessions = %s\n", t); - free(t); - - assert_se(sd_get_sessions(NULL) == r); - - r = sd_get_uids(&uids); - assert_se(r >= 0); - - printf("uids ="); - for (k = 0; k < r; k++) - printf(" "UID_FMT, uids[k]); - printf("\n"); - free(uids); - - printf("n_uids = %i\n", r); - assert_se(sd_get_uids(NULL) == r); - - r = sd_get_machine_names(&machines); - assert_se(r >= 0); - assert_se(r == (int) strv_length(machines)); - assert_se(t = strv_join(machines, ", ")); - strv_free(machines); - printf("n_machines = %i\n", r); - printf("machines = %s\n", t); - free(t); - - r = sd_login_monitor_new("session", &m); - assert_se(r >= 0); - - for (n = 0; n < 5; n++) { - usec_t timeout, nw; - - zero(pollfd); - assert_se((pollfd.fd = sd_login_monitor_get_fd(m)) >= 0); - assert_se((pollfd.events = sd_login_monitor_get_events(m)) >= 0); - - assert_se(sd_login_monitor_get_timeout(m, &timeout) >= 0); - - nw = now(CLOCK_MONOTONIC); - - r = poll(&pollfd, 1, - timeout == (uint64_t) -1 ? -1 : - timeout > nw ? (int) ((timeout - nw) / 1000) : - 0); - - assert_se(r >= 0); - - sd_login_monitor_flush(m); - printf("Wake!\n"); - } - - sd_login_monitor_unref(m); -} - -int main(int argc, char* argv[]) { - log_parse_environment(); - log_open(); - - test_login(); - - return 0; -} diff --git a/src/libsystemd/sd-netlink/Makefile b/src/libsystemd/sd-netlink/Makefile deleted file mode 120000 index 94aaae2c4d..0000000000 --- a/src/libsystemd/sd-netlink/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-netlink/local-addresses.c b/src/libsystemd/sd-netlink/local-addresses.c deleted file mode 100644 index ed9ee041ab..0000000000 --- a/src/libsystemd/sd-netlink/local-addresses.c +++ /dev/null @@ -1,275 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2011 Lennart Poettering - Copyright 2014 Tom Gundersen - - 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 "sd-netlink.h" - -#include "alloc-util.h" -#include "local-addresses.h" -#include "macro.h" -#include "netlink-util.h" - -static int address_compare(const void *_a, const void *_b) { - const struct local_address *a = _a, *b = _b; - - /* Order lowest scope first, IPv4 before IPv6, lowest interface index first */ - - if (a->family == AF_INET && b->family == AF_INET6) - return -1; - if (a->family == AF_INET6 && b->family == AF_INET) - return 1; - - if (a->scope < b->scope) - return -1; - if (a->scope > b->scope) - return 1; - - if (a->metric < b->metric) - return -1; - if (a->metric > b->metric) - return 1; - - if (a->ifindex < b->ifindex) - return -1; - if (a->ifindex > b->ifindex) - return 1; - - return memcmp(&a->address, &b->address, FAMILY_ADDRESS_SIZE(a->family)); -} - -int local_addresses(sd_netlink *context, int ifindex, int af, struct local_address **ret) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - _cleanup_free_ struct local_address *list = NULL; - size_t n_list = 0, n_allocated = 0; - sd_netlink_message *m; - int r; - - assert(ret); - - if (context) - rtnl = sd_netlink_ref(context); - else { - r = sd_netlink_open(&rtnl); - if (r < 0) - return r; - } - - r = sd_rtnl_message_new_addr(rtnl, &req, RTM_GETADDR, 0, af); - if (r < 0) - return r; - - r = sd_netlink_call(rtnl, req, 0, &reply); - if (r < 0) - return r; - - for (m = reply; m; m = sd_netlink_message_next(m)) { - struct local_address *a; - unsigned char flags; - uint16_t type; - int ifi, family; - - r = sd_netlink_message_get_errno(m); - if (r < 0) - return r; - - r = sd_netlink_message_get_type(m, &type); - if (r < 0) - return r; - if (type != RTM_NEWADDR) - continue; - - r = sd_rtnl_message_addr_get_ifindex(m, &ifi); - if (r < 0) - return r; - if (ifindex > 0 && ifi != ifindex) - continue; - - r = sd_rtnl_message_addr_get_family(m, &family); - if (r < 0) - return r; - if (af != AF_UNSPEC && af != family) - continue; - - r = sd_rtnl_message_addr_get_flags(m, &flags); - if (r < 0) - return r; - if (flags & IFA_F_DEPRECATED) - continue; - - if (!GREEDY_REALLOC0(list, n_allocated, n_list+1)) - return -ENOMEM; - - a = list + n_list; - - r = sd_rtnl_message_addr_get_scope(m, &a->scope); - if (r < 0) - return r; - - if (ifindex == 0 && (a->scope == RT_SCOPE_HOST || a->scope == RT_SCOPE_NOWHERE)) - continue; - - switch (family) { - - case AF_INET: - r = sd_netlink_message_read_in_addr(m, IFA_LOCAL, &a->address.in); - if (r < 0) { - r = sd_netlink_message_read_in_addr(m, IFA_ADDRESS, &a->address.in); - if (r < 0) - continue; - } - break; - - case AF_INET6: - r = sd_netlink_message_read_in6_addr(m, IFA_LOCAL, &a->address.in6); - if (r < 0) { - r = sd_netlink_message_read_in6_addr(m, IFA_ADDRESS, &a->address.in6); - if (r < 0) - continue; - } - break; - - default: - continue; - } - - a->ifindex = ifi; - a->family = family; - - n_list++; - }; - - qsort_safe(list, n_list, sizeof(struct local_address), address_compare); - - *ret = list; - list = NULL; - - return (int) n_list; -} - -int local_gateways(sd_netlink *context, int ifindex, int af, struct local_address **ret) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - _cleanup_free_ struct local_address *list = NULL; - sd_netlink_message *m = NULL; - size_t n_list = 0, n_allocated = 0; - int r; - - assert(ret); - - if (context) - rtnl = sd_netlink_ref(context); - else { - r = sd_netlink_open(&rtnl); - if (r < 0) - return r; - } - - r = sd_rtnl_message_new_route(rtnl, &req, RTM_GETROUTE, af, RTPROT_UNSPEC); - if (r < 0) - return r; - - r = sd_netlink_message_request_dump(req, true); - if (r < 0) - return r; - - r = sd_netlink_call(rtnl, req, 0, &reply); - if (r < 0) - return r; - - for (m = reply; m; m = sd_netlink_message_next(m)) { - struct local_address *a; - uint16_t type; - unsigned char dst_len, src_len; - uint32_t ifi; - int family; - - r = sd_netlink_message_get_errno(m); - if (r < 0) - return r; - - r = sd_netlink_message_get_type(m, &type); - if (r < 0) - return r; - if (type != RTM_NEWROUTE) - continue; - - /* We only care for default routes */ - r = sd_rtnl_message_route_get_dst_prefixlen(m, &dst_len); - if (r < 0) - return r; - if (dst_len != 0) - continue; - - r = sd_rtnl_message_route_get_src_prefixlen(m, &src_len); - if (r < 0) - return r; - if (src_len != 0) - continue; - - r = sd_netlink_message_read_u32(m, RTA_OIF, &ifi); - if (r < 0) - return r; - if (ifindex > 0 && (int) ifi != ifindex) - continue; - - r = sd_rtnl_message_route_get_family(m, &family); - if (r < 0) - return r; - if (af != AF_UNSPEC && af != family) - continue; - - if (!GREEDY_REALLOC0(list, n_allocated, n_list + 1)) - return -ENOMEM; - - a = list + n_list; - - switch (family) { - case AF_INET: - r = sd_netlink_message_read_in_addr(m, RTA_GATEWAY, &a->address.in); - if (r < 0) - continue; - - break; - case AF_INET6: - r = sd_netlink_message_read_in6_addr(m, RTA_GATEWAY, &a->address.in6); - if (r < 0) - continue; - - break; - default: - continue; - } - - sd_netlink_message_read_u32(m, RTA_PRIORITY, &a->metric); - - a->ifindex = ifi; - a->family = family; - - n_list++; - } - - if (n_list > 0) - qsort(list, n_list, sizeof(struct local_address), address_compare); - - *ret = list; - list = NULL; - - return (int) n_list; -} diff --git a/src/libsystemd/sd-netlink/local-addresses.h b/src/libsystemd/sd-netlink/local-addresses.h deleted file mode 100644 index 18d71e797e..0000000000 --- a/src/libsystemd/sd-netlink/local-addresses.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2008-2011 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 "sd-netlink.h" - -#include "in-addr-util.h" - -struct local_address { - int family, ifindex; - unsigned char scope; - uint32_t metric; - union in_addr_union address; -}; - -int local_addresses(sd_netlink *rtnl, int ifindex, int af, struct local_address **ret); - -int local_gateways(sd_netlink *rtnl, int ifindex, int af, struct local_address **ret); diff --git a/src/libsystemd/sd-netlink/netlink-internal.h b/src/libsystemd/sd-netlink/netlink-internal.h deleted file mode 100644 index dcfb080ad3..0000000000 --- a/src/libsystemd/sd-netlink/netlink-internal.h +++ /dev/null @@ -1,137 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 Tom Gundersen - - 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 "sd-netlink.h" - -#include "list.h" -#include "netlink-types.h" -#include "prioq.h" -#include "refcnt.h" - -#define RTNL_DEFAULT_TIMEOUT ((usec_t) (25 * USEC_PER_SEC)) - -#define RTNL_WQUEUE_MAX 1024 -#define RTNL_RQUEUE_MAX 64*1024 - -#define RTNL_CONTAINER_DEPTH 32 - -struct reply_callback { - sd_netlink_message_handler_t callback; - void *userdata; - usec_t timeout; - uint64_t serial; - unsigned prioq_idx; -}; - -struct match_callback { - sd_netlink_message_handler_t callback; - uint16_t type; - void *userdata; - - LIST_FIELDS(struct match_callback, match_callbacks); -}; - -struct sd_netlink { - RefCount n_ref; - - int fd; - - union { - struct sockaddr sa; - struct sockaddr_nl nl; - } sockaddr; - - Hashmap *broadcast_group_refs; - bool broadcast_group_dont_leave:1; /* until we can rely on 4.2 */ - - sd_netlink_message **rqueue; - unsigned rqueue_size; - size_t rqueue_allocated; - - sd_netlink_message **rqueue_partial; - unsigned rqueue_partial_size; - size_t rqueue_partial_allocated; - - struct nlmsghdr *rbuffer; - size_t rbuffer_allocated; - - bool processing:1; - - uint32_t serial; - - struct Prioq *reply_callbacks_prioq; - Hashmap *reply_callbacks; - - LIST_HEAD(struct match_callback, match_callbacks); - - pid_t original_pid; - - sd_event_source *io_event_source; - sd_event_source *time_event_source; - sd_event_source *exit_event_source; - sd_event *event; -}; - -struct netlink_attribute { - size_t offset; /* offset from hdr to attribute */ - bool nested:1; - bool net_byteorder:1; -}; - -struct netlink_container { - const struct NLTypeSystem *type_system; /* the type system of the container */ - size_t offset; /* offset from hdr to the start of the container */ - struct netlink_attribute *attributes; - unsigned short n_attributes; /* number of attributes in container */ -}; - -struct sd_netlink_message { - RefCount n_ref; - - sd_netlink *rtnl; - - struct nlmsghdr *hdr; - struct netlink_container containers[RTNL_CONTAINER_DEPTH]; - unsigned n_containers; /* number of containers */ - bool sealed:1; - bool broadcast:1; - - sd_netlink_message *next; /* next in a chain of multi-part messages */ -}; - -int message_new(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t type); -int message_new_empty(sd_netlink *rtnl, sd_netlink_message **ret); - -int socket_open(int family); -int socket_bind(sd_netlink *nl); -int socket_broadcast_group_ref(sd_netlink *nl, unsigned group); -int socket_broadcast_group_unref(sd_netlink *nl, unsigned group); -int socket_write_message(sd_netlink *nl, sd_netlink_message *m); -int socket_read_message(sd_netlink *nl); - -int rtnl_rqueue_make_room(sd_netlink *rtnl); -int rtnl_rqueue_partial_make_room(sd_netlink *rtnl); - -/* Make sure callbacks don't destroy the rtnl connection */ -#define NETLINK_DONT_DESTROY(rtnl) \ - _cleanup_(sd_netlink_unrefp) _unused_ sd_netlink *_dont_destroy_##rtnl = sd_netlink_ref(rtnl) diff --git a/src/libsystemd/sd-netlink/netlink-message.c b/src/libsystemd/sd-netlink/netlink-message.c deleted file mode 100644 index 86d8dee867..0000000000 --- a/src/libsystemd/sd-netlink/netlink-message.c +++ /dev/null @@ -1,961 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 Tom Gundersen - - 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 -#include - -#include "sd-netlink.h" - -#include "alloc-util.h" -#include "formats-util.h" -#include "missing.h" -#include "netlink-internal.h" -#include "netlink-types.h" -#include "netlink-util.h" -#include "refcnt.h" -#include "socket-util.h" -#include "util.h" - -#define GET_CONTAINER(m, i) ((i) < (m)->n_containers ? (struct rtattr*)((uint8_t*)(m)->hdr + (m)->containers[i].offset) : NULL) -#define PUSH_CONTAINER(m, new) (m)->container_offsets[(m)->n_containers++] = (uint8_t*)(new) - (uint8_t*)(m)->hdr; - -#define RTA_TYPE(rta) ((rta)->rta_type & NLA_TYPE_MASK) -#define RTA_FLAGS(rta) ((rta)->rta_type & ~NLA_TYPE_MASK) - -int message_new_empty(sd_netlink *rtnl, sd_netlink_message **ret) { - sd_netlink_message *m; - - assert_return(ret, -EINVAL); - - /* Note that 'rtnl' is currently unused, if we start using it internally - we must take care to avoid problems due to mutual references between - buses and their queued messages. See sd-bus. - */ - - m = new0(sd_netlink_message, 1); - if (!m) - return -ENOMEM; - - m->n_ref = REFCNT_INIT; - - m->sealed = false; - - *ret = m; - - return 0; -} - -int message_new(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t type) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - const NLType *nl_type; - size_t size; - int r; - - r = type_system_get_type(&type_system_root, &nl_type, type); - if (r < 0) - return r; - - if (type_get_type(nl_type) != NETLINK_TYPE_NESTED) - return -EINVAL; - - r = message_new_empty(rtnl, &m); - if (r < 0) - return r; - - size = NLMSG_SPACE(type_get_size(nl_type)); - - assert(size >= sizeof(struct nlmsghdr)); - m->hdr = malloc0(size); - if (!m->hdr) - return -ENOMEM; - - m->hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; - - type_get_type_system(nl_type, &m->containers[0].type_system); - m->hdr->nlmsg_len = size; - m->hdr->nlmsg_type = type; - - *ret = m; - m = NULL; - - return 0; -} - -int sd_netlink_message_request_dump(sd_netlink_message *m, int dump) { - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(m->hdr->nlmsg_type == RTM_GETLINK || - m->hdr->nlmsg_type == RTM_GETADDR || - m->hdr->nlmsg_type == RTM_GETROUTE || - m->hdr->nlmsg_type == RTM_GETNEIGH, - -EINVAL); - - SET_FLAG(m->hdr->nlmsg_flags, NLM_F_DUMP, dump); - - return 0; -} - -sd_netlink_message *sd_netlink_message_ref(sd_netlink_message *m) { - if (m) - assert_se(REFCNT_INC(m->n_ref) >= 2); - - return m; -} - -sd_netlink_message *sd_netlink_message_unref(sd_netlink_message *m) { - if (m && REFCNT_DEC(m->n_ref) == 0) { - unsigned i; - - free(m->hdr); - - for (i = 0; i <= m->n_containers; i++) - free(m->containers[i].attributes); - - sd_netlink_message_unref(m->next); - - free(m); - } - - return NULL; -} - -int sd_netlink_message_get_type(sd_netlink_message *m, uint16_t *type) { - assert_return(m, -EINVAL); - assert_return(type, -EINVAL); - - *type = m->hdr->nlmsg_type; - - return 0; -} - -int sd_netlink_message_set_flags(sd_netlink_message *m, uint16_t flags) { - assert_return(m, -EINVAL); - assert_return(flags, -EINVAL); - - m->hdr->nlmsg_flags = flags; - - return 0; -} - -int sd_netlink_message_is_broadcast(sd_netlink_message *m) { - assert_return(m, -EINVAL); - - return m->broadcast; -} - -/* If successful the updated message will be correctly aligned, if - unsuccessful the old message is untouched. */ -static int add_rtattr(sd_netlink_message *m, unsigned short type, const void *data, size_t data_length) { - uint32_t rta_length; - size_t message_length, padding_length; - struct nlmsghdr *new_hdr; - struct rtattr *rta; - char *padding; - unsigned i; - int offset; - - assert(m); - assert(m->hdr); - assert(!m->sealed); - assert(NLMSG_ALIGN(m->hdr->nlmsg_len) == m->hdr->nlmsg_len); - assert(!data || data_length); - - /* get offset of the new attribute */ - offset = m->hdr->nlmsg_len; - - /* get the size of the new rta attribute (with padding at the end) */ - rta_length = RTA_LENGTH(data_length); - - /* get the new message size (with padding at the end) */ - message_length = offset + RTA_ALIGN(rta_length); - - /* realloc to fit the new attribute */ - new_hdr = realloc(m->hdr, message_length); - if (!new_hdr) - return -ENOMEM; - m->hdr = new_hdr; - - /* get pointer to the attribute we are about to add */ - rta = (struct rtattr *) ((uint8_t *) m->hdr + offset); - - /* if we are inside containers, extend them */ - for (i = 0; i < m->n_containers; i++) - GET_CONTAINER(m, i)->rta_len += message_length - offset; - - /* fill in the attribute */ - rta->rta_type = type; - rta->rta_len = rta_length; - if (data) - /* we don't deal with the case where the user lies about the type - * and gives us too little data (so don't do that) - */ - padding = mempcpy(RTA_DATA(rta), data, data_length); - - else - /* if no data was passed, make sure we still initialize the padding - note that we can have data_length > 0 (used by some containers) */ - padding = RTA_DATA(rta); - - /* make sure also the padding at the end of the message is initialized */ - padding_length = (uint8_t*)m->hdr + message_length - (uint8_t*)padding; - memzero(padding, padding_length); - - /* update message size */ - m->hdr->nlmsg_len = message_length; - - return offset; -} - -static int message_attribute_has_type(sd_netlink_message *m, size_t *out_size, uint16_t attribute_type, uint16_t data_type) { - const NLType *type; - int r; - - assert(m); - - r = type_system_get_type(m->containers[m->n_containers].type_system, &type, attribute_type); - if (r < 0) - return r; - - if (type_get_type(type) != data_type) - return -EINVAL; - - if (out_size) - *out_size = type_get_size(type); - return 0; -} - -int sd_netlink_message_append_string(sd_netlink_message *m, unsigned short type, const char *data) { - size_t length, size; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(data, -EINVAL); - - r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_STRING); - if (r < 0) - return r; - - if (size) { - length = strnlen(data, size+1); - if (length > size) - return -EINVAL; - } else - length = strlen(data); - - r = add_rtattr(m, type, data, length + 1); - if (r < 0) - return r; - - return 0; -} - -int sd_netlink_message_append_flag(sd_netlink_message *m, unsigned short type) { - size_t size; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - - r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_FLAG); - if (r < 0) - return r; - - r = add_rtattr(m, type, NULL, 0); - if (r < 0) - return r; - - return 0; -} - -int sd_netlink_message_append_u8(sd_netlink_message *m, unsigned short type, uint8_t data) { - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U8); - if (r < 0) - return r; - - r = add_rtattr(m, type, &data, sizeof(uint8_t)); - if (r < 0) - return r; - - return 0; -} - - -int sd_netlink_message_append_u16(sd_netlink_message *m, unsigned short type, uint16_t data) { - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U16); - if (r < 0) - return r; - - r = add_rtattr(m, type, &data, sizeof(uint16_t)); - if (r < 0) - return r; - - return 0; -} - -int sd_netlink_message_append_u32(sd_netlink_message *m, unsigned short type, uint32_t data) { - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U32); - if (r < 0) - return r; - - r = add_rtattr(m, type, &data, sizeof(uint32_t)); - if (r < 0) - return r; - - return 0; -} - -int sd_netlink_message_append_data(sd_netlink_message *m, unsigned short type, const void *data, size_t len) { - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - - r = add_rtattr(m, type, data, len); - if (r < 0) - return r; - - return 0; -} - -int sd_netlink_message_append_in_addr(sd_netlink_message *m, unsigned short type, const struct in_addr *data) { - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(data, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); - if (r < 0) - return r; - - r = add_rtattr(m, type, data, sizeof(struct in_addr)); - if (r < 0) - return r; - - return 0; -} - -int sd_netlink_message_append_in6_addr(sd_netlink_message *m, unsigned short type, const struct in6_addr *data) { - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(data, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); - if (r < 0) - return r; - - r = add_rtattr(m, type, data, sizeof(struct in6_addr)); - if (r < 0) - return r; - - return 0; -} - -int sd_netlink_message_append_ether_addr(sd_netlink_message *m, unsigned short type, const struct ether_addr *data) { - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(data, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_ETHER_ADDR); - if (r < 0) - return r; - - r = add_rtattr(m, type, data, ETH_ALEN); - if (r < 0) - return r; - - return 0; -} - -int sd_netlink_message_append_cache_info(sd_netlink_message *m, unsigned short type, const struct ifa_cacheinfo *info) { - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(info, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_CACHE_INFO); - if (r < 0) - return r; - - r = add_rtattr(m, type, info, sizeof(struct ifa_cacheinfo)); - if (r < 0) - return r; - - return 0; -} - -int sd_netlink_message_open_container(sd_netlink_message *m, unsigned short type) { - size_t size; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(m->n_containers < RTNL_CONTAINER_DEPTH, -ERANGE); - - r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_NESTED); - if (r < 0) { - const NLTypeSystemUnion *type_system_union; - int family; - - r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_UNION); - if (r < 0) - return r; - - r = sd_rtnl_message_get_family(m, &family); - if (r < 0) - return r; - - r = type_system_get_type_system_union(m->containers[m->n_containers].type_system, &type_system_union, type); - if (r < 0) - return r; - - r = type_system_union_protocol_get_type_system(type_system_union, - &m->containers[m->n_containers + 1].type_system, - family); - if (r < 0) - return r; - } else { - r = type_system_get_type_system(m->containers[m->n_containers].type_system, - &m->containers[m->n_containers + 1].type_system, - type); - if (r < 0) - return r; - } - - r = add_rtattr(m, type | NLA_F_NESTED, NULL, size); - if (r < 0) - return r; - - m->containers[m->n_containers++].offset = r; - - return 0; -} - -int sd_netlink_message_open_container_union(sd_netlink_message *m, unsigned short type, const char *key) { - const NLTypeSystemUnion *type_system_union; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - - r = type_system_get_type_system_union(m->containers[m->n_containers].type_system, &type_system_union, type); - if (r < 0) - return r; - - r = type_system_union_get_type_system(type_system_union, - &m->containers[m->n_containers + 1].type_system, - key); - if (r < 0) - return r; - - r = sd_netlink_message_append_string(m, type_system_union->match, key); - if (r < 0) - return r; - - /* do we evere need non-null size */ - r = add_rtattr(m, type | NLA_F_NESTED, NULL, 0); - if (r < 0) - return r; - - m->containers[m->n_containers++].offset = r; - - return 0; -} - - -int sd_netlink_message_close_container(sd_netlink_message *m) { - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(m->n_containers > 0, -EINVAL); - - m->containers[m->n_containers].type_system = NULL; - m->n_containers--; - - return 0; -} - -static int netlink_message_read_internal(sd_netlink_message *m, unsigned short type, void **data, bool *net_byteorder) { - struct netlink_attribute *attribute; - struct rtattr *rta; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - assert_return(data, -EINVAL); - assert(m->n_containers < RTNL_CONTAINER_DEPTH); - assert(m->containers[m->n_containers].attributes); - assert(type < m->containers[m->n_containers].n_attributes); - - attribute = &m->containers[m->n_containers].attributes[type]; - - if (!attribute->offset) - return -ENODATA; - - rta = (struct rtattr*)((uint8_t *) m->hdr + attribute->offset); - - *data = RTA_DATA(rta); - - if (net_byteorder) - *net_byteorder = attribute->net_byteorder; - - return RTA_PAYLOAD(rta); -} - -int sd_netlink_message_read_string(sd_netlink_message *m, unsigned short type, const char **data) { - int r; - void *attr_data; - - assert_return(m, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_STRING); - if (r < 0) - return r; - - r = netlink_message_read_internal(m, type, &attr_data, NULL); - if (r < 0) - return r; - else if (strnlen(attr_data, r) >= (size_t) r) - return -EIO; - - if (data) - *data = (const char *) attr_data; - - return 0; -} - -int sd_netlink_message_read_u8(sd_netlink_message *m, unsigned short type, uint8_t *data) { - int r; - void *attr_data; - - assert_return(m, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U8); - if (r < 0) - return r; - - r = netlink_message_read_internal(m, type, &attr_data, NULL); - if (r < 0) - return r; - else if ((size_t) r < sizeof(uint8_t)) - return -EIO; - - if (data) - *data = *(uint8_t *) attr_data; - - return 0; -} - -int sd_netlink_message_read_u16(sd_netlink_message *m, unsigned short type, uint16_t *data) { - void *attr_data; - bool net_byteorder; - int r; - - assert_return(m, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U16); - if (r < 0) - return r; - - r = netlink_message_read_internal(m, type, &attr_data, &net_byteorder); - if (r < 0) - return r; - else if ((size_t) r < sizeof(uint16_t)) - return -EIO; - - if (data) { - if (net_byteorder) - *data = be16toh(*(uint16_t *) attr_data); - else - *data = *(uint16_t *) attr_data; - } - - return 0; -} - -int sd_netlink_message_read_u32(sd_netlink_message *m, unsigned short type, uint32_t *data) { - void *attr_data; - bool net_byteorder; - int r; - - assert_return(m, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U32); - if (r < 0) - return r; - - r = netlink_message_read_internal(m, type, &attr_data, &net_byteorder); - if (r < 0) - return r; - else if ((size_t)r < sizeof(uint32_t)) - return -EIO; - - if (data) { - if (net_byteorder) - *data = be32toh(*(uint32_t *) attr_data); - else - *data = *(uint32_t *) attr_data; - } - - return 0; -} - -int sd_netlink_message_read_ether_addr(sd_netlink_message *m, unsigned short type, struct ether_addr *data) { - int r; - void *attr_data; - - assert_return(m, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_ETHER_ADDR); - if (r < 0) - return r; - - r = netlink_message_read_internal(m, type, &attr_data, NULL); - if (r < 0) - return r; - else if ((size_t)r < sizeof(struct ether_addr)) - return -EIO; - - if (data) - memcpy(data, attr_data, sizeof(struct ether_addr)); - - return 0; -} - -int sd_netlink_message_read_cache_info(sd_netlink_message *m, unsigned short type, struct ifa_cacheinfo *info) { - int r; - void *attr_data; - - assert_return(m, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_CACHE_INFO); - if (r < 0) - return r; - - r = netlink_message_read_internal(m, type, &attr_data, NULL); - if (r < 0) - return r; - else if ((size_t)r < sizeof(struct ifa_cacheinfo)) - return -EIO; - - if (info) - memcpy(info, attr_data, sizeof(struct ifa_cacheinfo)); - - return 0; -} - -int sd_netlink_message_read_in_addr(sd_netlink_message *m, unsigned short type, struct in_addr *data) { - int r; - void *attr_data; - - assert_return(m, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); - if (r < 0) - return r; - - r = netlink_message_read_internal(m, type, &attr_data, NULL); - if (r < 0) - return r; - else if ((size_t)r < sizeof(struct in_addr)) - return -EIO; - - if (data) - memcpy(data, attr_data, sizeof(struct in_addr)); - - return 0; -} - -int sd_netlink_message_read_in6_addr(sd_netlink_message *m, unsigned short type, struct in6_addr *data) { - int r; - void *attr_data; - - assert_return(m, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); - if (r < 0) - return r; - - r = netlink_message_read_internal(m, type, &attr_data, NULL); - if (r < 0) - return r; - else if ((size_t)r < sizeof(struct in6_addr)) - return -EIO; - - if (data) - memcpy(data, attr_data, sizeof(struct in6_addr)); - - return 0; -} - -static int netlink_container_parse(sd_netlink_message *m, - struct netlink_container *container, - int count, - struct rtattr *rta, - unsigned int rt_len) { - _cleanup_free_ struct netlink_attribute *attributes = NULL; - - attributes = new0(struct netlink_attribute, count); - if (!attributes) - return -ENOMEM; - - for (; RTA_OK(rta, rt_len); rta = RTA_NEXT(rta, rt_len)) { - unsigned short type; - - type = RTA_TYPE(rta); - - /* if the kernel is newer than the headers we used - when building, we ignore out-of-range attributes */ - if (type >= count) - continue; - - if (attributes[type].offset) - log_debug("rtnl: message parse - overwriting repeated attribute"); - - attributes[type].offset = (uint8_t *) rta - (uint8_t *) m->hdr; - attributes[type].nested = RTA_FLAGS(rta) & NLA_F_NESTED; - attributes[type].net_byteorder = RTA_FLAGS(rta) & NLA_F_NET_BYTEORDER; - } - - container->attributes = attributes; - attributes = NULL; - container->n_attributes = count; - - return 0; -} - -int sd_netlink_message_enter_container(sd_netlink_message *m, unsigned short type_id) { - const NLType *nl_type; - const NLTypeSystem *type_system; - void *container; - uint16_t type; - size_t size; - int r; - - assert_return(m, -EINVAL); - assert_return(m->n_containers < RTNL_CONTAINER_DEPTH, -EINVAL); - - r = type_system_get_type(m->containers[m->n_containers].type_system, - &nl_type, - type_id); - if (r < 0) - return r; - - type = type_get_type(nl_type); - - if (type == NETLINK_TYPE_NESTED) { - r = type_system_get_type_system(m->containers[m->n_containers].type_system, - &type_system, - type_id); - if (r < 0) - return r; - } else if (type == NETLINK_TYPE_UNION) { - const NLTypeSystemUnion *type_system_union; - - r = type_system_get_type_system_union(m->containers[m->n_containers].type_system, - &type_system_union, - type_id); - if (r < 0) - return r; - - switch (type_system_union->match_type) { - case NL_MATCH_SIBLING: - { - const char *key; - - r = sd_netlink_message_read_string(m, type_system_union->match, &key); - if (r < 0) - return r; - - r = type_system_union_get_type_system(type_system_union, - &type_system, - key); - if (r < 0) - return r; - - break; - } - case NL_MATCH_PROTOCOL: - { - int family; - - r = sd_rtnl_message_get_family(m, &family); - if (r < 0) - return r; - - r = type_system_union_protocol_get_type_system(type_system_union, - &type_system, - family); - if (r < 0) - return r; - - break; - } - default: - assert_not_reached("sd-netlink: invalid type system union type"); - } - } else - return -EINVAL; - - r = netlink_message_read_internal(m, type_id, &container, NULL); - if (r < 0) - return r; - else - size = (size_t)r; - - m->n_containers++; - - r = netlink_container_parse(m, - &m->containers[m->n_containers], - type_system_get_count(type_system), - container, - size); - if (r < 0) { - m->n_containers--; - return r; - } - - m->containers[m->n_containers].type_system = type_system; - - return 0; -} - -int sd_netlink_message_exit_container(sd_netlink_message *m) { - assert_return(m, -EINVAL); - assert_return(m->sealed, -EINVAL); - assert_return(m->n_containers > 0, -EINVAL); - - m->containers[m->n_containers].attributes = mfree(m->containers[m->n_containers].attributes); - m->containers[m->n_containers].type_system = NULL; - - m->n_containers--; - - return 0; -} - -uint32_t rtnl_message_get_serial(sd_netlink_message *m) { - assert(m); - assert(m->hdr); - - return m->hdr->nlmsg_seq; -} - -int sd_netlink_message_is_error(sd_netlink_message *m) { - assert_return(m, 0); - assert_return(m->hdr, 0); - - return m->hdr->nlmsg_type == NLMSG_ERROR; -} - -int sd_netlink_message_get_errno(sd_netlink_message *m) { - struct nlmsgerr *err; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - - if (!sd_netlink_message_is_error(m)) - return 0; - - err = NLMSG_DATA(m->hdr); - - return err->error; -} - -int sd_netlink_message_rewind(sd_netlink_message *m) { - const NLType *nl_type; - uint16_t type; - size_t size; - unsigned i; - int r; - - assert_return(m, -EINVAL); - - /* don't allow appending to message once parsed */ - if (!m->sealed) - rtnl_message_seal(m); - - for (i = 1; i <= m->n_containers; i++) - m->containers[i].attributes = mfree(m->containers[i].attributes); - - m->n_containers = 0; - - if (m->containers[0].attributes) - /* top-level attributes have already been parsed */ - return 0; - - assert(m->hdr); - - r = type_system_get_type(&type_system_root, &nl_type, m->hdr->nlmsg_type); - if (r < 0) - return r; - - type = type_get_type(nl_type); - size = type_get_size(nl_type); - - if (type == NETLINK_TYPE_NESTED) { - const NLTypeSystem *type_system; - - type_get_type_system(nl_type, &type_system); - - m->containers[0].type_system = type_system; - - r = netlink_container_parse(m, - &m->containers[m->n_containers], - type_system_get_count(type_system), - (struct rtattr*)((uint8_t*)NLMSG_DATA(m->hdr) + NLMSG_ALIGN(size)), - NLMSG_PAYLOAD(m->hdr, size)); - if (r < 0) - return r; - } - - return 0; -} - -void rtnl_message_seal(sd_netlink_message *m) { - assert(m); - assert(!m->sealed); - - m->sealed = true; -} - -sd_netlink_message *sd_netlink_message_next(sd_netlink_message *m) { - assert_return(m, NULL); - - return m->next; -} diff --git a/src/libsystemd/sd-netlink/netlink-socket.c b/src/libsystemd/sd-netlink/netlink-socket.c deleted file mode 100644 index c165fa3359..0000000000 --- a/src/libsystemd/sd-netlink/netlink-socket.c +++ /dev/null @@ -1,474 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 Tom Gundersen - - 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 -#include - -#include "sd-netlink.h" - -#include "alloc-util.h" -#include "formats-util.h" -#include "missing.h" -#include "netlink-internal.h" -#include "netlink-types.h" -#include "netlink-util.h" -#include "refcnt.h" -#include "socket-util.h" -#include "util.h" - -int socket_open(int family) { - int fd; - - fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, family); - if (fd < 0) - return -errno; - - return fd; -} - -static int broadcast_groups_get(sd_netlink *nl) { - _cleanup_free_ uint32_t *groups = NULL; - socklen_t len = 0, old_len; - unsigned i, j; - int r; - - assert(nl); - assert(nl->fd >= 0); - - r = getsockopt(nl->fd, SOL_NETLINK, NETLINK_LIST_MEMBERSHIPS, NULL, &len); - if (r < 0) { - if (errno == ENOPROTOOPT) { - nl->broadcast_group_dont_leave = true; - return 0; - } else - return -errno; - } - - if (len == 0) - return 0; - - groups = new0(uint32_t, len); - if (!groups) - return -ENOMEM; - - old_len = len; - - r = getsockopt(nl->fd, SOL_NETLINK, NETLINK_LIST_MEMBERSHIPS, groups, &len); - if (r < 0) - return -errno; - - if (old_len != len) - return -EIO; - - r = hashmap_ensure_allocated(&nl->broadcast_group_refs, NULL); - if (r < 0) - return r; - - for (i = 0; i < len; i++) { - for (j = 0; j < sizeof(uint32_t) * 8; j++) { - uint32_t offset; - unsigned group; - - offset = 1U << j; - - if (!(groups[i] & offset)) - continue; - - group = i * sizeof(uint32_t) * 8 + j + 1; - - r = hashmap_put(nl->broadcast_group_refs, UINT_TO_PTR(group), UINT_TO_PTR(1)); - if (r < 0) - return r; - } - } - - return 0; -} - -int socket_bind(sd_netlink *nl) { - socklen_t addrlen; - int r, one = 1; - - r = setsockopt(nl->fd, SOL_NETLINK, NETLINK_PKTINFO, &one, sizeof(one)); - if (r < 0) - return -errno; - - addrlen = sizeof(nl->sockaddr); - - r = bind(nl->fd, &nl->sockaddr.sa, addrlen); - /* ignore EINVAL to allow opening an already bound socket */ - if (r < 0 && errno != EINVAL) - return -errno; - - r = getsockname(nl->fd, &nl->sockaddr.sa, &addrlen); - if (r < 0) - return -errno; - - r = broadcast_groups_get(nl); - if (r < 0) - return r; - - return 0; -} - -static unsigned broadcast_group_get_ref(sd_netlink *nl, unsigned group) { - assert(nl); - - return PTR_TO_UINT(hashmap_get(nl->broadcast_group_refs, UINT_TO_PTR(group))); -} - -static int broadcast_group_set_ref(sd_netlink *nl, unsigned group, unsigned n_ref) { - int r; - - assert(nl); - - r = hashmap_replace(nl->broadcast_group_refs, UINT_TO_PTR(group), UINT_TO_PTR(n_ref)); - if (r < 0) - return r; - - return 0; -} - -static int broadcast_group_join(sd_netlink *nl, unsigned group) { - int r; - - assert(nl); - assert(nl->fd >= 0); - assert(group > 0); - - r = setsockopt(nl->fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group)); - if (r < 0) - return -errno; - - return 0; -} - -int socket_broadcast_group_ref(sd_netlink *nl, unsigned group) { - unsigned n_ref; - int r; - - assert(nl); - - n_ref = broadcast_group_get_ref(nl, group); - - n_ref++; - - r = hashmap_ensure_allocated(&nl->broadcast_group_refs, NULL); - if (r < 0) - return r; - - r = broadcast_group_set_ref(nl, group, n_ref); - if (r < 0) - return r; - - if (n_ref > 1) - /* not yet in the group */ - return 0; - - r = broadcast_group_join(nl, group); - if (r < 0) - return r; - - return 0; -} - -static int broadcast_group_leave(sd_netlink *nl, unsigned group) { - int r; - - assert(nl); - assert(nl->fd >= 0); - assert(group > 0); - - if (nl->broadcast_group_dont_leave) - return 0; - - r = setsockopt(nl->fd, SOL_NETLINK, NETLINK_DROP_MEMBERSHIP, &group, sizeof(group)); - if (r < 0) - return -errno; - - return 0; -} - -int socket_broadcast_group_unref(sd_netlink *nl, unsigned group) { - unsigned n_ref; - int r; - - assert(nl); - - n_ref = broadcast_group_get_ref(nl, group); - - assert(n_ref > 0); - - n_ref--; - - r = broadcast_group_set_ref(nl, group, n_ref); - if (r < 0) - return r; - - if (n_ref > 0) - /* still refs left */ - return 0; - - r = broadcast_group_leave(nl, group); - if (r < 0) - return r; - - return 0; -} - -/* returns the number of bytes sent, or a negative error code */ -int socket_write_message(sd_netlink *nl, sd_netlink_message *m) { - union { - struct sockaddr sa; - struct sockaddr_nl nl; - } addr = { - .nl.nl_family = AF_NETLINK, - }; - ssize_t k; - - assert(nl); - assert(m); - assert(m->hdr); - - k = sendto(nl->fd, m->hdr, m->hdr->nlmsg_len, - 0, &addr.sa, sizeof(addr)); - if (k < 0) - return -errno; - - return k; -} - -static int socket_recv_message(int fd, struct iovec *iov, uint32_t *_group, bool peek) { - union sockaddr_union sender; - uint8_t cmsg_buffer[CMSG_SPACE(sizeof(struct nl_pktinfo))]; - struct msghdr msg = { - .msg_iov = iov, - .msg_iovlen = 1, - .msg_name = &sender, - .msg_namelen = sizeof(sender), - .msg_control = cmsg_buffer, - .msg_controllen = sizeof(cmsg_buffer), - }; - struct cmsghdr *cmsg; - uint32_t group = 0; - int r; - - assert(fd >= 0); - assert(iov); - - r = recvmsg(fd, &msg, MSG_TRUNC | (peek ? MSG_PEEK : 0)); - if (r < 0) { - /* no data */ - if (errno == ENOBUFS) - log_debug("rtnl: kernel receive buffer overrun"); - else if (errno == EAGAIN) - log_debug("rtnl: no data in socket"); - - return (errno == EAGAIN || errno == EINTR) ? 0 : -errno; - } - - if (sender.nl.nl_pid != 0) { - /* not from the kernel, ignore */ - log_debug("rtnl: ignoring message from portid %"PRIu32, sender.nl.nl_pid); - - if (peek) { - /* drop the message */ - r = recvmsg(fd, &msg, 0); - if (r < 0) - return (errno == EAGAIN || errno == EINTR) ? 0 : -errno; - } - - return 0; - } - - CMSG_FOREACH(cmsg, &msg) { - if (cmsg->cmsg_level == SOL_NETLINK && - cmsg->cmsg_type == NETLINK_PKTINFO && - cmsg->cmsg_len == CMSG_LEN(sizeof(struct nl_pktinfo))) { - struct nl_pktinfo *pktinfo = (void *)CMSG_DATA(cmsg); - - /* multi-cast group */ - group = pktinfo->group; - } - } - - if (_group) - *_group = group; - - return r; -} - -/* On success, the number of bytes received is returned and *ret points to the received message - * which has a valid header and the correct size. - * If nothing useful was received 0 is returned. - * On failure, a negative error code is returned. - */ -int socket_read_message(sd_netlink *rtnl) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *first = NULL; - struct iovec iov = {}; - uint32_t group = 0; - bool multi_part = false, done = false; - struct nlmsghdr *new_msg; - size_t len; - int r; - unsigned i = 0; - - assert(rtnl); - assert(rtnl->rbuffer); - assert(rtnl->rbuffer_allocated >= sizeof(struct nlmsghdr)); - - /* read nothing, just get the pending message size */ - r = socket_recv_message(rtnl->fd, &iov, NULL, true); - if (r <= 0) - return r; - else - len = (size_t)r; - - /* make room for the pending message */ - if (!greedy_realloc((void **)&rtnl->rbuffer, - &rtnl->rbuffer_allocated, - len, sizeof(uint8_t))) - return -ENOMEM; - - iov.iov_base = rtnl->rbuffer; - iov.iov_len = rtnl->rbuffer_allocated; - - /* read the pending message */ - r = socket_recv_message(rtnl->fd, &iov, &group, false); - if (r <= 0) - return r; - else - len = (size_t)r; - - if (len > rtnl->rbuffer_allocated) - /* message did not fit in read buffer */ - return -EIO; - - if (NLMSG_OK(rtnl->rbuffer, len) && rtnl->rbuffer->nlmsg_flags & NLM_F_MULTI) { - multi_part = true; - - for (i = 0; i < rtnl->rqueue_partial_size; i++) { - if (rtnl_message_get_serial(rtnl->rqueue_partial[i]) == - rtnl->rbuffer->nlmsg_seq) { - first = rtnl->rqueue_partial[i]; - break; - } - } - } - - for (new_msg = rtnl->rbuffer; NLMSG_OK(new_msg, len) && !done; new_msg = NLMSG_NEXT(new_msg, len)) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - const NLType *nl_type; - - if (!group && new_msg->nlmsg_pid != rtnl->sockaddr.nl.nl_pid) - /* not broadcast and not for us */ - continue; - - if (new_msg->nlmsg_type == NLMSG_NOOP) - /* silently drop noop messages */ - continue; - - if (new_msg->nlmsg_type == NLMSG_DONE) { - /* finished reading multi-part message */ - done = true; - - /* if first is not defined, put NLMSG_DONE into the receive queue. */ - if (first) - continue; - } - - /* check that we support this message type */ - r = type_system_get_type(&type_system_root, &nl_type, new_msg->nlmsg_type); - if (r < 0) { - if (r == -EOPNOTSUPP) - log_debug("sd-netlink: ignored message with unknown type: %i", - new_msg->nlmsg_type); - - continue; - } - - /* check that the size matches the message type */ - if (new_msg->nlmsg_len < NLMSG_LENGTH(type_get_size(nl_type))) { - log_debug("sd-netlink: message larger than expected, dropping"); - continue; - } - - r = message_new_empty(rtnl, &m); - if (r < 0) - return r; - - m->broadcast = !!group; - - m->hdr = memdup(new_msg, new_msg->nlmsg_len); - if (!m->hdr) - return -ENOMEM; - - /* seal and parse the top-level message */ - r = sd_netlink_message_rewind(m); - if (r < 0) - return r; - - /* push the message onto the multi-part message stack */ - if (first) - m->next = first; - first = m; - m = NULL; - } - - if (len) - log_debug("sd-netlink: discarding %zu bytes of incoming message", len); - - if (!first) - return 0; - - if (!multi_part || done) { - /* we got a complete message, push it on the read queue */ - r = rtnl_rqueue_make_room(rtnl); - if (r < 0) - return r; - - rtnl->rqueue[rtnl->rqueue_size++] = first; - first = NULL; - - if (multi_part && (i < rtnl->rqueue_partial_size)) { - /* remove the message form the partial read queue */ - memmove(rtnl->rqueue_partial + i,rtnl->rqueue_partial + i + 1, - sizeof(sd_netlink_message*) * (rtnl->rqueue_partial_size - i - 1)); - rtnl->rqueue_partial_size--; - } - - return 1; - } else { - /* we only got a partial multi-part message, push it on the - partial read queue */ - if (i < rtnl->rqueue_partial_size) { - rtnl->rqueue_partial[i] = first; - } else { - r = rtnl_rqueue_partial_make_room(rtnl); - if (r < 0) - return r; - - rtnl->rqueue_partial[rtnl->rqueue_partial_size++] = first; - } - first = NULL; - - return 0; - } -} diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c deleted file mode 100644 index 3a4bac2ced..0000000000 --- a/src/libsystemd/sd-netlink/netlink-types.c +++ /dev/null @@ -1,684 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "macro.h" -#include "missing.h" -#include "netlink-types.h" -#include "string-table.h" -#include "util.h" - -/* Maximum ARP IP target defined in kernel */ -#define BOND_MAX_ARP_TARGETS 16 - -typedef enum { - BOND_ARP_TARGETS_0, - BOND_ARP_TARGETS_1, - BOND_ARP_TARGETS_2, - BOND_ARP_TARGETS_3, - BOND_ARP_TARGETS_4, - BOND_ARP_TARGETS_5, - BOND_ARP_TARGETS_6, - BOND_ARP_TARGETS_7, - BOND_ARP_TARGETS_8, - BOND_ARP_TARGETS_9, - BOND_ARP_TARGETS_10, - BOND_ARP_TARGETS_11, - BOND_ARP_TARGETS_12, - BOND_ARP_TARGETS_13, - BOND_ARP_TARGETS_14, - BOND_ARP_TARGETS_MAX = BOND_MAX_ARP_TARGETS, -} BondArpTargets; - -struct NLType { - uint16_t type; - size_t size; - const NLTypeSystem *type_system; - const NLTypeSystemUnion *type_system_union; -}; - -struct NLTypeSystem { - uint16_t count; - const NLType *types; -}; - -static const NLTypeSystem rtnl_link_type_system; - -static const NLType empty_types[1] = { - /* fake array to avoid .types==NULL, which denotes invalid type-systems */ -}; - -static const NLTypeSystem empty_type_system = { - .count = 0, - .types = empty_types, -}; - -static const NLType rtnl_link_info_data_veth_types[] = { - [VETH_INFO_PEER] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, -}; - -static const NLType rtnl_link_info_data_ipvlan_types[] = { - [IFLA_IPVLAN_MODE] = { .type = NETLINK_TYPE_U16 }, -}; - -static const NLType rtnl_link_info_data_macvlan_types[] = { - [IFLA_MACVLAN_MODE] = { .type = NETLINK_TYPE_U32 }, - [IFLA_MACVLAN_FLAGS] = { .type = NETLINK_TYPE_U16 }, -}; - -static const NLType rtnl_link_info_data_bridge_types[] = { - [IFLA_BR_FORWARD_DELAY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BR_HELLO_TIME] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BR_MAX_AGE] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BR_AGEING_TIME] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BR_STP_STATE] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BR_PRIORITY] = { .type = NETLINK_TYPE_U16 }, - [IFLA_BR_VLAN_FILTERING] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BR_VLAN_PROTOCOL] = { .type = NETLINK_TYPE_U16 }, - [IFLA_BR_GROUP_FWD_MASK] = { .type = NETLINK_TYPE_U16 }, - [IFLA_BR_ROOT_PORT] = { .type = NETLINK_TYPE_U16 }, - [IFLA_BR_ROOT_PATH_COST] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BR_TOPOLOGY_CHANGE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BR_TOPOLOGY_CHANGE_DETECTED] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BR_HELLO_TIMER] = { .type = NETLINK_TYPE_U16 }, - [IFLA_BR_TCN_TIMER] = { .type = NETLINK_TYPE_U16 }, - [IFLA_BR_TOPOLOGY_CHANGE_TIMER] = { .type = NETLINK_TYPE_U16 }, - [IFLA_BR_GC_TIMER] = { .type = NETLINK_TYPE_U64 }, - [IFLA_BR_GROUP_ADDR] = { .type = NETLINK_TYPE_U16 }, - [IFLA_BR_FDB_FLUSH] = { .type = NETLINK_TYPE_U16 }, - [IFLA_BR_MCAST_ROUTER] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BR_MCAST_SNOOPING] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BR_MCAST_QUERY_USE_IFADDR] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BR_MCAST_QUERIER] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BR_MCAST_HASH_ELASTICITY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BR_MCAST_HASH_MAX] = { .type = NETLINK_TYPE_U16 }, - [IFLA_BR_MCAST_LAST_MEMBER_CNT] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BR_MCAST_STARTUP_QUERY_CNT] = { .type = NETLINK_TYPE_U16 }, - [IFLA_BR_MCAST_LAST_MEMBER_INTVL] = { .type = NETLINK_TYPE_U64 }, - [IFLA_BR_MCAST_MEMBERSHIP_INTVL] = { .type = NETLINK_TYPE_U64 }, - [IFLA_BR_MCAST_QUERIER_INTVL] = { .type = NETLINK_TYPE_U64 }, - [IFLA_BR_MCAST_QUERY_INTVL] = { .type = NETLINK_TYPE_U64 }, - [IFLA_BR_MCAST_QUERY_RESPONSE_INTVL] = { .type = NETLINK_TYPE_U64 }, - [IFLA_BR_MCAST_STARTUP_QUERY_INTVL] = { .type = NETLINK_TYPE_U64 }, - [IFLA_BR_NF_CALL_IPTABLES] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BR_NF_CALL_IP6TABLES] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BR_NF_CALL_ARPTABLES] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BR_VLAN_DEFAULT_PVID] = { .type = NETLINK_TYPE_U16 }, -}; - -static const NLType rtnl_link_info_data_vlan_types[] = { - [IFLA_VLAN_ID] = { .type = NETLINK_TYPE_U16 }, -/* - [IFLA_VLAN_FLAGS] = { .len = sizeof(struct ifla_vlan_flags) }, - [IFLA_VLAN_EGRESS_QOS] = { .type = NETLINK_TYPE_NESTED }, - [IFLA_VLAN_INGRESS_QOS] = { .type = NETLINK_TYPE_NESTED }, -*/ - [IFLA_VLAN_PROTOCOL] = { .type = NETLINK_TYPE_U16 }, -}; - -static const NLType rtnl_link_info_data_vxlan_types[] = { - [IFLA_VXLAN_ID] = { .type = NETLINK_TYPE_U32 }, - [IFLA_VXLAN_GROUP] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_VXLAN_LINK] = { .type = NETLINK_TYPE_U32 }, - [IFLA_VXLAN_LOCAL] = { .type = NETLINK_TYPE_U32}, - [IFLA_VXLAN_TTL] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_TOS] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_LEARNING] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_AGEING] = { .type = NETLINK_TYPE_U32 }, - [IFLA_VXLAN_LIMIT] = { .type = NETLINK_TYPE_U32 }, - [IFLA_VXLAN_PORT_RANGE] = { .type = NETLINK_TYPE_U32}, - [IFLA_VXLAN_PROXY] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_RSC] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_L2MISS] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_L3MISS] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_PORT] = { .type = NETLINK_TYPE_U16 }, - [IFLA_VXLAN_GROUP6] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_VXLAN_LOCAL6] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_VXLAN_UDP_CSUM] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_UDP_ZERO_CSUM6_TX] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_UDP_ZERO_CSUM6_RX] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_REMCSUM_TX] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_REMCSUM_RX] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_GBP] = { .type = NETLINK_TYPE_FLAG }, - [IFLA_VXLAN_REMCSUM_NOPARTIAL] = { .type = NETLINK_TYPE_FLAG }, -}; - -static const NLType rtnl_bond_arp_target_types[] = { - [BOND_ARP_TARGETS_0] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_1] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_2] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_3] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_4] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_5] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_6] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_7] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_8] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_9] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_10] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_11] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_12] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_13] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_14] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_MAX] = { .type = NETLINK_TYPE_U32 }, -}; - -static const NLTypeSystem rtnl_bond_arp_type_system = { - .count = ELEMENTSOF(rtnl_bond_arp_target_types), - .types = rtnl_bond_arp_target_types, -}; - -static const NLType rtnl_link_info_data_bond_types[] = { - [IFLA_BOND_MODE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BOND_ACTIVE_SLAVE] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_MIIMON] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_UPDELAY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_DOWNDELAY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_USE_CARRIER] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BOND_ARP_INTERVAL] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_ARP_IP_TARGET] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_bond_arp_type_system }, - [IFLA_BOND_ARP_VALIDATE] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_ARP_ALL_TARGETS] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_PRIMARY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_PRIMARY_RESELECT] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BOND_FAIL_OVER_MAC] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BOND_XMIT_HASH_POLICY] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BOND_RESEND_IGMP] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_NUM_PEER_NOTIF] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BOND_ALL_SLAVES_ACTIVE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BOND_MIN_LINKS] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_LP_INTERVAL] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_PACKETS_PER_SLAVE] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_AD_LACP_RATE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BOND_AD_SELECT] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BOND_AD_INFO] = { .type = NETLINK_TYPE_NESTED }, -}; - -static const NLType rtnl_link_info_data_iptun_types[] = { - [IFLA_IPTUN_LINK] = { .type = NETLINK_TYPE_U32 }, - [IFLA_IPTUN_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_IPTUN_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_IPTUN_TTL] = { .type = NETLINK_TYPE_U8 }, - [IFLA_IPTUN_TOS] = { .type = NETLINK_TYPE_U8 }, - [IFLA_IPTUN_PMTUDISC] = { .type = NETLINK_TYPE_U8 }, - [IFLA_IPTUN_FLAGS] = { .type = NETLINK_TYPE_U16 }, - [IFLA_IPTUN_PROTO] = { .type = NETLINK_TYPE_U8 }, - [IFLA_IPTUN_6RD_PREFIX] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_IPTUN_6RD_RELAY_PREFIX] = { .type = NETLINK_TYPE_U32 }, - [IFLA_IPTUN_6RD_PREFIXLEN] = { .type = NETLINK_TYPE_U16 }, - [IFLA_IPTUN_6RD_RELAY_PREFIXLEN] = { .type = NETLINK_TYPE_U16 }, - [IFLA_IPTUN_ENCAP_TYPE] = { .type = NETLINK_TYPE_U16 }, - [IFLA_IPTUN_ENCAP_FLAGS] = { .type = NETLINK_TYPE_U16 }, - [IFLA_IPTUN_ENCAP_SPORT] = { .type = NETLINK_TYPE_U16 }, - [IFLA_IPTUN_ENCAP_DPORT] = { .type = NETLINK_TYPE_U16 }, -}; - -static const NLType rtnl_link_info_data_ipgre_types[] = { - [IFLA_GRE_LINK] = { .type = NETLINK_TYPE_U32 }, - [IFLA_GRE_IFLAGS] = { .type = NETLINK_TYPE_U16 }, - [IFLA_GRE_OFLAGS] = { .type = NETLINK_TYPE_U16 }, - [IFLA_GRE_IKEY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_GRE_OKEY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_GRE_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_GRE_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_GRE_TTL] = { .type = NETLINK_TYPE_U8 }, - [IFLA_GRE_TOS] = { .type = NETLINK_TYPE_U8 }, - [IFLA_GRE_PMTUDISC] = { .type = NETLINK_TYPE_U8 }, - [IFLA_GRE_FLOWINFO] = { .type = NETLINK_TYPE_U32 }, - [IFLA_GRE_FLAGS] = { .type = NETLINK_TYPE_U32 }, - [IFLA_GRE_ENCAP_TYPE] = { .type = NETLINK_TYPE_U16 }, - [IFLA_GRE_ENCAP_FLAGS] = { .type = NETLINK_TYPE_U16 }, - [IFLA_GRE_ENCAP_SPORT] = { .type = NETLINK_TYPE_U16 }, - [IFLA_GRE_ENCAP_DPORT] = { .type = NETLINK_TYPE_U16 }, -}; - -static const NLType rtnl_link_info_data_ipvti_types[] = { - [IFLA_VTI_LINK] = { .type = NETLINK_TYPE_U32 }, - [IFLA_VTI_IKEY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_VTI_OKEY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_VTI_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_VTI_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, -}; - -static const NLType rtnl_link_info_data_ip6tnl_types[] = { - [IFLA_IPTUN_LINK] = { .type = NETLINK_TYPE_U32 }, - [IFLA_IPTUN_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_IPTUN_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_IPTUN_TTL] = { .type = NETLINK_TYPE_U8 }, - [IFLA_IPTUN_FLAGS] = { .type = NETLINK_TYPE_U32 }, - [IFLA_IPTUN_PROTO] = { .type = NETLINK_TYPE_U8 }, - [IFLA_IPTUN_ENCAP_LIMIT] = { .type = NETLINK_TYPE_U8 }, - [IFLA_IPTUN_FLOWINFO] = { .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_BRIDGE] = "bridge", - [NL_UNION_LINK_INFO_DATA_VLAN] = "vlan", - [NL_UNION_LINK_INFO_DATA_VETH] = "veth", - [NL_UNION_LINK_INFO_DATA_DUMMY] = "dummy", - [NL_UNION_LINK_INFO_DATA_MACVLAN] = "macvlan", - [NL_UNION_LINK_INFO_DATA_MACVTAP] = "macvtap", - [NL_UNION_LINK_INFO_DATA_IPVLAN] = "ipvlan", - [NL_UNION_LINK_INFO_DATA_VXLAN] = "vxlan", - [NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL] = "ipip", - [NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL] = "gre", - [NL_UNION_LINK_INFO_DATA_IPGRETAP_TUNNEL] = "gretap", - [NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL] = "ip6gre", - [NL_UNION_LINK_INFO_DATA_IP6GRETAP_TUNNEL] = "ip6gretap", - [NL_UNION_LINK_INFO_DATA_SIT_TUNNEL] = "sit", - [NL_UNION_LINK_INFO_DATA_VTI_TUNNEL] = "vti", - [NL_UNION_LINK_INFO_DATA_VTI6_TUNNEL] = "vti6", - [NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL] = "ip6tnl", -}; - -DEFINE_STRING_TABLE_LOOKUP(nl_union_link_info_data, NLUnionLinkInfoData); - -static const NLTypeSystem rtnl_link_info_data_type_systems[] = { - [NL_UNION_LINK_INFO_DATA_BOND] = { .count = ELEMENTSOF(rtnl_link_info_data_bond_types), - .types = rtnl_link_info_data_bond_types }, - [NL_UNION_LINK_INFO_DATA_BRIDGE] = { .count = ELEMENTSOF(rtnl_link_info_data_bridge_types), - .types = rtnl_link_info_data_bridge_types }, - [NL_UNION_LINK_INFO_DATA_VLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_vlan_types), - .types = rtnl_link_info_data_vlan_types }, - [NL_UNION_LINK_INFO_DATA_VETH] = { .count = ELEMENTSOF(rtnl_link_info_data_veth_types), - .types = rtnl_link_info_data_veth_types }, - [NL_UNION_LINK_INFO_DATA_MACVLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_macvlan_types), - .types = rtnl_link_info_data_macvlan_types }, - [NL_UNION_LINK_INFO_DATA_MACVTAP] = { .count = ELEMENTSOF(rtnl_link_info_data_macvlan_types), - .types = rtnl_link_info_data_macvlan_types }, - [NL_UNION_LINK_INFO_DATA_IPVLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvlan_types), - .types = rtnl_link_info_data_ipvlan_types }, - [NL_UNION_LINK_INFO_DATA_VXLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_vxlan_types), - .types = rtnl_link_info_data_vxlan_types }, - [NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_iptun_types), - .types = rtnl_link_info_data_iptun_types }, - [NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), - .types = rtnl_link_info_data_ipgre_types }, - [NL_UNION_LINK_INFO_DATA_IPGRETAP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), - .types = rtnl_link_info_data_ipgre_types }, - [NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), - .types = rtnl_link_info_data_ipgre_types }, - [NL_UNION_LINK_INFO_DATA_IP6GRETAP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), - .types = rtnl_link_info_data_ipgre_types }, - [NL_UNION_LINK_INFO_DATA_SIT_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_iptun_types), - .types = rtnl_link_info_data_iptun_types }, - [NL_UNION_LINK_INFO_DATA_VTI_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvti_types), - .types = rtnl_link_info_data_ipvti_types }, - [NL_UNION_LINK_INFO_DATA_VTI6_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvti_types), - .types = rtnl_link_info_data_ipvti_types }, - [NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ip6tnl_types), - .types = rtnl_link_info_data_ip6tnl_types }, - -}; - -static const NLTypeSystemUnion rtnl_link_info_data_type_system_union = { - .num = _NL_UNION_LINK_INFO_DATA_MAX, - .lookup = nl_union_link_info_data_from_string, - .type_systems = rtnl_link_info_data_type_systems, - .match_type = NL_MATCH_SIBLING, - .match = IFLA_INFO_KIND, -}; - -static const NLType rtnl_link_info_types[] = { - [IFLA_INFO_KIND] = { .type = NETLINK_TYPE_STRING }, - [IFLA_INFO_DATA] = { .type = NETLINK_TYPE_UNION, .type_system_union = &rtnl_link_info_data_type_system_union}, -/* - [IFLA_INFO_XSTATS], - [IFLA_INFO_SLAVE_KIND] = { .type = NETLINK_TYPE_STRING }, - [IFLA_INFO_SLAVE_DATA] = { .type = NETLINK_TYPE_NESTED }, -*/ -}; - -static const NLTypeSystem rtnl_link_info_type_system = { - .count = ELEMENTSOF(rtnl_link_info_types), - .types = rtnl_link_info_types, -}; - -static const struct NLType rtnl_prot_info_bridge_port_types[] = { - [IFLA_BRPORT_STATE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_COST] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BRPORT_PRIORITY] = { .type = NETLINK_TYPE_U16 }, - [IFLA_BRPORT_MODE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_GUARD] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_PROTECT] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_FAST_LEAVE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_LEARNING] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_UNICAST_FLOOD] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_PROXYARP] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_LEARNING_SYNC] = { .type = NETLINK_TYPE_U8 }, -}; - -static const NLTypeSystem rtnl_prot_info_type_systems[] = { - [AF_BRIDGE] = { .count = ELEMENTSOF(rtnl_prot_info_bridge_port_types), - .types = rtnl_prot_info_bridge_port_types }, -}; - -static const NLTypeSystemUnion rtnl_prot_info_type_system_union = { - .num = AF_MAX, - .type_systems = rtnl_prot_info_type_systems, - .match_type = NL_MATCH_PROTOCOL, -}; - -static const struct NLType rtnl_af_spec_inet6_types[] = { - [IFLA_INET6_FLAGS] = { .type = NETLINK_TYPE_U32 }, -/* - IFLA_INET6_CONF, - IFLA_INET6_STATS, - IFLA_INET6_MCAST, - IFLA_INET6_CACHEINFO, - IFLA_INET6_ICMP6STATS, -*/ - [IFLA_INET6_TOKEN] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_INET6_ADDR_GEN_MODE] = { .type = NETLINK_TYPE_U8 }, -}; - -static const NLTypeSystem rtnl_af_spec_inet6_type_system = { - .count = ELEMENTSOF(rtnl_af_spec_inet6_types), - .types = rtnl_af_spec_inet6_types, -}; - -static const NLType rtnl_af_spec_types[] = { - [AF_INET6] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_af_spec_inet6_type_system }, -}; - -static const NLTypeSystem rtnl_af_spec_type_system = { - .count = ELEMENTSOF(rtnl_af_spec_types), - .types = rtnl_af_spec_types, -}; - -static const NLType rtnl_link_types[] = { - [IFLA_ADDRESS] = { .type = NETLINK_TYPE_ETHER_ADDR }, - [IFLA_BROADCAST] = { .type = NETLINK_TYPE_ETHER_ADDR }, - [IFLA_IFNAME] = { .type = NETLINK_TYPE_STRING, .size = IFNAMSIZ - 1 }, - [IFLA_MTU] = { .type = NETLINK_TYPE_U32 }, - [IFLA_LINK] = { .type = NETLINK_TYPE_U32 }, -/* - [IFLA_QDISC], - [IFLA_STATS], - [IFLA_COST], - [IFLA_PRIORITY], -*/ - [IFLA_MASTER] = { .type = NETLINK_TYPE_U32 }, -/* - [IFLA_WIRELESS], -*/ - [IFLA_PROTINFO] = { .type = NETLINK_TYPE_UNION, .type_system_union = &rtnl_prot_info_type_system_union }, - [IFLA_TXQLEN] = { .type = NETLINK_TYPE_U32 }, -/* - [IFLA_MAP] = { .len = sizeof(struct rtnl_link_ifmap) }, -*/ - [IFLA_WEIGHT] = { .type = NETLINK_TYPE_U32 }, - [IFLA_OPERSTATE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_LINKMODE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_LINKINFO] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_info_type_system }, - [IFLA_NET_NS_PID] = { .type = NETLINK_TYPE_U32 }, - [IFLA_IFALIAS] = { .type = NETLINK_TYPE_STRING, .size = IFALIASZ - 1 }, -/* - [IFLA_NUM_VF], - [IFLA_VFINFO_LIST] = {. type = NETLINK_TYPE_NESTED, }, - [IFLA_STATS64], - [IFLA_VF_PORTS] = { .type = NETLINK_TYPE_NESTED }, - [IFLA_PORT_SELF] = { .type = NETLINK_TYPE_NESTED }, -*/ - [IFLA_AF_SPEC] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_af_spec_type_system }, -/* - [IFLA_VF_PORTS], - [IFLA_PORT_SELF], - [IFLA_AF_SPEC], -*/ - [IFLA_GROUP] = { .type = NETLINK_TYPE_U32 }, - [IFLA_NET_NS_FD] = { .type = NETLINK_TYPE_U32 }, - [IFLA_EXT_MASK] = { .type = NETLINK_TYPE_U32 }, - [IFLA_PROMISCUITY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_NUM_TX_QUEUES] = { .type = NETLINK_TYPE_U32 }, - [IFLA_NUM_RX_QUEUES] = { .type = NETLINK_TYPE_U32 }, - [IFLA_CARRIER] = { .type = NETLINK_TYPE_U8 }, -/* - [IFLA_PHYS_PORT_ID] = { .type = NETLINK_TYPE_BINARY, .len = MAX_PHYS_PORT_ID_LEN }, -*/ -}; - -static const NLTypeSystem rtnl_link_type_system = { - .count = ELEMENTSOF(rtnl_link_types), - .types = rtnl_link_types, -}; - -/* IFA_FLAGS was defined in kernel 3.14, but we still support older - * kernels where IFA_MAX is lower. */ -static const NLType rtnl_address_types[] = { - [IFA_ADDRESS] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFA_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFA_LABEL] = { .type = NETLINK_TYPE_STRING, .size = IFNAMSIZ - 1 }, - [IFA_BROADCAST] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ - [IFA_CACHEINFO] = { .type = NETLINK_TYPE_CACHE_INFO, .size = sizeof(struct ifa_cacheinfo) }, -/* - [IFA_ANYCAST], - [IFA_MULTICAST], -*/ - [IFA_FLAGS] = { .type = NETLINK_TYPE_U32 }, -}; - -static const NLTypeSystem rtnl_address_type_system = { - .count = ELEMENTSOF(rtnl_address_types), - .types = rtnl_address_types, -}; - -static const NLType rtnl_route_types[] = { - [RTA_DST] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ - [RTA_SRC] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ - [RTA_IIF] = { .type = NETLINK_TYPE_U32 }, - [RTA_OIF] = { .type = NETLINK_TYPE_U32 }, - [RTA_GATEWAY] = { .type = NETLINK_TYPE_IN_ADDR }, - [RTA_PRIORITY] = { .type = NETLINK_TYPE_U32 }, - [RTA_PREFSRC] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ -/* - [RTA_METRICS] = { .type = NETLINK_TYPE_NESTED }, - [RTA_MULTIPATH] = { .len = sizeof(struct rtnexthop) }, -*/ - [RTA_FLOW] = { .type = NETLINK_TYPE_U32 }, /* 6? */ -/* - RTA_CACHEINFO, - RTA_TABLE, - RTA_MARK, - RTA_MFC_STATS, - RTA_VIA, - RTA_NEWDST, -*/ - [RTA_PREF] = { .type = NETLINK_TYPE_U8 }, - -}; - -static const NLTypeSystem rtnl_route_type_system = { - .count = ELEMENTSOF(rtnl_route_types), - .types = rtnl_route_types, -}; - -static const NLType rtnl_neigh_types[] = { - [NDA_DST] = { .type = NETLINK_TYPE_IN_ADDR }, - [NDA_LLADDR] = { .type = NETLINK_TYPE_ETHER_ADDR }, - [NDA_CACHEINFO] = { .type = NETLINK_TYPE_CACHE_INFO, .size = sizeof(struct nda_cacheinfo) }, - [NDA_PROBES] = { .type = NETLINK_TYPE_U32 }, - [NDA_VLAN] = { .type = NETLINK_TYPE_U16 }, - [NDA_PORT] = { .type = NETLINK_TYPE_U16 }, - [NDA_VNI] = { .type = NETLINK_TYPE_U32 }, - [NDA_IFINDEX] = { .type = NETLINK_TYPE_U32 }, -}; - -static const NLTypeSystem rtnl_neigh_type_system = { - .count = ELEMENTSOF(rtnl_neigh_types), - .types = rtnl_neigh_types, -}; - -static const NLType rtnl_types[] = { - [NLMSG_DONE] = { .type = NETLINK_TYPE_NESTED, .type_system = &empty_type_system, .size = 0 }, - [NLMSG_ERROR] = { .type = NETLINK_TYPE_NESTED, .type_system = &empty_type_system, .size = sizeof(struct nlmsgerr) }, - [RTM_NEWLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, - [RTM_DELLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, - [RTM_GETLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, - [RTM_SETLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, - [RTM_NEWADDR] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_address_type_system, .size = sizeof(struct ifaddrmsg) }, - [RTM_DELADDR] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_address_type_system, .size = sizeof(struct ifaddrmsg) }, - [RTM_GETADDR] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_address_type_system, .size = sizeof(struct ifaddrmsg) }, - [RTM_NEWROUTE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_route_type_system, .size = sizeof(struct rtmsg) }, - [RTM_DELROUTE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_route_type_system, .size = sizeof(struct rtmsg) }, - [RTM_GETROUTE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_route_type_system, .size = sizeof(struct rtmsg) }, - [RTM_NEWNEIGH] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_neigh_type_system, .size = sizeof(struct ndmsg) }, - [RTM_DELNEIGH] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_neigh_type_system, .size = sizeof(struct ndmsg) }, - [RTM_GETNEIGH] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_neigh_type_system, .size = sizeof(struct ndmsg) }, -}; - -const NLTypeSystem type_system_root = { - .count = ELEMENTSOF(rtnl_types), - .types = rtnl_types, -}; - -uint16_t type_get_type(const NLType *type) { - assert(type); - return type->type; -} - -size_t type_get_size(const NLType *type) { - assert(type); - return type->size; -} - -void type_get_type_system(const NLType *nl_type, const NLTypeSystem **ret) { - assert(nl_type); - assert(ret); - assert(nl_type->type == NETLINK_TYPE_NESTED); - assert(nl_type->type_system); - - *ret = nl_type->type_system; -} - -void type_get_type_system_union(const NLType *nl_type, const NLTypeSystemUnion **ret) { - assert(nl_type); - assert(ret); - assert(nl_type->type == NETLINK_TYPE_UNION); - assert(nl_type->type_system_union); - - *ret = nl_type->type_system_union; -} - -uint16_t type_system_get_count(const NLTypeSystem *type_system) { - assert(type_system); - return type_system->count; -} - -int type_system_get_type(const NLTypeSystem *type_system, const NLType **ret, uint16_t type) { - const NLType *nl_type; - - assert(ret); - assert(type_system); - assert(type_system->types); - - if (type >= type_system->count) - return -EOPNOTSUPP; - - nl_type = &type_system->types[type]; - - if (nl_type->type == NETLINK_TYPE_UNSPEC) - return -EOPNOTSUPP; - - *ret = nl_type; - - return 0; -} - -int type_system_get_type_system(const NLTypeSystem *type_system, const NLTypeSystem **ret, uint16_t type) { - const NLType *nl_type; - int r; - - assert(ret); - - r = type_system_get_type(type_system, &nl_type, type); - if (r < 0) - return r; - - type_get_type_system(nl_type, ret); - return 0; -} - -int type_system_get_type_system_union(const NLTypeSystem *type_system, const NLTypeSystemUnion **ret, uint16_t type) { - const NLType *nl_type; - int r; - - assert(ret); - - r = type_system_get_type(type_system, &nl_type, type); - if (r < 0) - return r; - - type_get_type_system_union(nl_type, ret); - return 0; -} - -int type_system_union_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, const char *key) { - int type; - - assert(type_system_union); - assert(type_system_union->match_type == NL_MATCH_SIBLING); - assert(type_system_union->lookup); - assert(type_system_union->type_systems); - assert(ret); - assert(key); - - type = type_system_union->lookup(key); - if (type < 0) - return -EOPNOTSUPP; - - assert(type < type_system_union->num); - - *ret = &type_system_union->type_systems[type]; - - return 0; -} - -int type_system_union_protocol_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, uint16_t protocol) { - const NLTypeSystem *type_system; - - assert(type_system_union); - assert(type_system_union->type_systems); - assert(type_system_union->match_type == NL_MATCH_PROTOCOL); - assert(ret); - - if (protocol >= type_system_union->num) - return -EOPNOTSUPP; - - type_system = &type_system_union->type_systems[protocol]; - if (!type_system->types) - return -EOPNOTSUPP; - - *ret = type_system; - - return 0; -} diff --git a/src/libsystemd/sd-netlink/netlink-types.h b/src/libsystemd/sd-netlink/netlink-types.h deleted file mode 100644 index ecb20bfcdc..0000000000 --- a/src/libsystemd/sd-netlink/netlink-types.h +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 "macro.h" - -enum { - NETLINK_TYPE_UNSPEC, - NETLINK_TYPE_U8, /* NLA_U8 */ - NETLINK_TYPE_U16, /* NLA_U16 */ - NETLINK_TYPE_U32, /* NLA_U32 */ - NETLINK_TYPE_U64, /* NLA_U64 */ - NETLINK_TYPE_STRING, /* NLA_STRING */ - NETLINK_TYPE_FLAG, /* NLA_FLAG */ - NETLINK_TYPE_IN_ADDR, - NETLINK_TYPE_ETHER_ADDR, - NETLINK_TYPE_CACHE_INFO, - NETLINK_TYPE_NESTED, /* NLA_NESTED */ - NETLINK_TYPE_UNION, -}; - -typedef enum NLMatchType { - NL_MATCH_SIBLING, - NL_MATCH_PROTOCOL, -} NLMatchType; - -typedef struct NLTypeSystemUnion NLTypeSystemUnion; -typedef struct NLTypeSystem NLTypeSystem; -typedef struct NLType NLType; - -struct NLTypeSystemUnion { - int num; - NLMatchType match_type; - uint16_t match; - int (*lookup)(const char *); - const NLTypeSystem *type_systems; -}; - -extern const NLTypeSystem type_system_root; - -uint16_t type_get_type(const NLType *type); -size_t type_get_size(const NLType *type); -void type_get_type_system(const NLType *type, const NLTypeSystem **ret); -void type_get_type_system_union(const NLType *type, const NLTypeSystemUnion **ret); - -uint16_t type_system_get_count(const NLTypeSystem *type_system); -int type_system_get_type(const NLTypeSystem *type_system, const NLType **ret, uint16_t type); -int type_system_get_type_system(const NLTypeSystem *type_system, const NLTypeSystem **ret, uint16_t type); -int type_system_get_type_system_union(const NLTypeSystem *type_system, const NLTypeSystemUnion **ret, uint16_t type); -int type_system_union_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, const char *key); -int type_system_union_protocol_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, uint16_t protocol); - -typedef enum NLUnionLinkInfoData { - NL_UNION_LINK_INFO_DATA_BOND, - NL_UNION_LINK_INFO_DATA_BRIDGE, - NL_UNION_LINK_INFO_DATA_VLAN, - NL_UNION_LINK_INFO_DATA_VETH, - NL_UNION_LINK_INFO_DATA_DUMMY, - NL_UNION_LINK_INFO_DATA_MACVLAN, - NL_UNION_LINK_INFO_DATA_MACVTAP, - NL_UNION_LINK_INFO_DATA_IPVLAN, - NL_UNION_LINK_INFO_DATA_VXLAN, - NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL, - NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL, - NL_UNION_LINK_INFO_DATA_IPGRETAP_TUNNEL, - NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL, - NL_UNION_LINK_INFO_DATA_IP6GRETAP_TUNNEL, - NL_UNION_LINK_INFO_DATA_SIT_TUNNEL, - NL_UNION_LINK_INFO_DATA_VTI_TUNNEL, - NL_UNION_LINK_INFO_DATA_VTI6_TUNNEL, - NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL, - _NL_UNION_LINK_INFO_DATA_MAX, - _NL_UNION_LINK_INFO_DATA_INVALID = -1 -} NLUnionLinkInfoData; - -const char *nl_union_link_info_data_to_string(NLUnionLinkInfoData p) _const_; -NLUnionLinkInfoData nl_union_link_info_data_from_string(const char *p) _pure_; diff --git a/src/libsystemd/sd-netlink/netlink-util.c b/src/libsystemd/sd-netlink/netlink-util.c deleted file mode 100644 index 73b9ac0258..0000000000 --- a/src/libsystemd/sd-netlink/netlink-util.c +++ /dev/null @@ -1,170 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2013 Tom Gundersen - - 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 "sd-netlink.h" - -#include "netlink-internal.h" -#include "netlink-util.h" - -int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; - int r; - - assert(rtnl); - assert(ifindex > 0); - assert(name); - - if (!*rtnl) { - r = sd_netlink_open(rtnl); - if (r < 0) - return r; - } - - r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); - if (r < 0) - return r; - - r = sd_netlink_message_append_string(message, IFLA_IFNAME, name); - if (r < 0) - return r; - - r = sd_netlink_call(*rtnl, message, 0, NULL); - if (r < 0) - return r; - - return 0; -} - -int rtnl_set_link_properties(sd_netlink **rtnl, int ifindex, const char *alias, - const struct ether_addr *mac, unsigned mtu) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; - int r; - - assert(rtnl); - assert(ifindex > 0); - - if (!alias && !mac && mtu == 0) - return 0; - - if (!*rtnl) { - r = sd_netlink_open(rtnl); - if (r < 0) - return r; - } - - r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); - if (r < 0) - return r; - - if (alias) { - r = sd_netlink_message_append_string(message, IFLA_IFALIAS, alias); - if (r < 0) - return r; - } - - if (mac) { - r = sd_netlink_message_append_ether_addr(message, IFLA_ADDRESS, mac); - if (r < 0) - return r; - } - - if (mtu > 0) { - r = sd_netlink_message_append_u32(message, IFLA_MTU, mtu); - if (r < 0) - return r; - } - - r = sd_netlink_call(*rtnl, message, 0, NULL); - if (r < 0) - return r; - - return 0; -} - -int rtnl_message_new_synthetic_error(int error, uint32_t serial, sd_netlink_message **ret) { - struct nlmsgerr *err; - int r; - - assert(error <= 0); - - r = message_new(NULL, ret, NLMSG_ERROR); - if (r < 0) - return r; - - (*ret)->hdr->nlmsg_seq = serial; - - err = NLMSG_DATA((*ret)->hdr); - - err->error = error; - - return 0; -} - -bool rtnl_message_type_is_neigh(uint16_t type) { - switch (type) { - case RTM_NEWNEIGH: - case RTM_GETNEIGH: - case RTM_DELNEIGH: - return true; - default: - return false; - } -} - -bool rtnl_message_type_is_route(uint16_t type) { - switch (type) { - case RTM_NEWROUTE: - case RTM_GETROUTE: - case RTM_DELROUTE: - return true; - default: - return false; - } -} - -bool rtnl_message_type_is_link(uint16_t type) { - switch (type) { - case RTM_NEWLINK: - case RTM_SETLINK: - case RTM_GETLINK: - case RTM_DELLINK: - return true; - default: - return false; - } -} - -bool rtnl_message_type_is_addr(uint16_t type) { - switch (type) { - case RTM_NEWADDR: - case RTM_GETADDR: - case RTM_DELADDR: - return true; - default: - return false; - } -} - -int rtnl_log_parse_error(int r) { - return log_error_errno(r, "Failed to parse netlink message: %m"); -} - -int rtnl_log_create_error(int r) { - return log_error_errno(r, "Failed to create netlink message: %m"); -} diff --git a/src/libsystemd/sd-netlink/netlink-util.h b/src/libsystemd/sd-netlink/netlink-util.h deleted file mode 100644 index f49bf4eaa6..0000000000 --- a/src/libsystemd/sd-netlink/netlink-util.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright (C) 2013 Tom Gundersen - - 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 "sd-netlink.h" - -#include "util.h" - -int rtnl_message_new_synthetic_error(int error, uint32_t serial, sd_netlink_message **ret); -uint32_t rtnl_message_get_serial(sd_netlink_message *m); -void rtnl_message_seal(sd_netlink_message *m); - -bool rtnl_message_type_is_link(uint16_t type); -bool rtnl_message_type_is_addr(uint16_t type); -bool rtnl_message_type_is_route(uint16_t type); -bool rtnl_message_type_is_neigh(uint16_t type); - -int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name); -int rtnl_set_link_properties(sd_netlink **rtnl, int ifindex, const char *alias, const struct ether_addr *mac, unsigned mtu); - -int rtnl_log_parse_error(int r); -int rtnl_log_create_error(int r); diff --git a/src/libsystemd/sd-netlink/rtnl-message.c b/src/libsystemd/sd-netlink/rtnl-message.c deleted file mode 100644 index 09240c7b2a..0000000000 --- a/src/libsystemd/sd-netlink/rtnl-message.c +++ /dev/null @@ -1,702 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 Tom Gundersen - - 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 -#include - -#include "sd-netlink.h" - -#include "formats-util.h" -#include "missing.h" -#include "netlink-internal.h" -#include "netlink-types.h" -#include "netlink-util.h" -#include "refcnt.h" -#include "socket-util.h" -#include "util.h" - -int sd_rtnl_message_route_set_dst_prefixlen(sd_netlink_message *m, unsigned char prefixlen) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - if ((rtm->rtm_family == AF_INET && prefixlen > 32) || - (rtm->rtm_family == AF_INET6 && prefixlen > 128)) - return -ERANGE; - - rtm->rtm_dst_len = prefixlen; - - return 0; -} - -int sd_rtnl_message_route_set_src_prefixlen(sd_netlink_message *m, unsigned char prefixlen) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - if ((rtm->rtm_family == AF_INET && prefixlen > 32) || - (rtm->rtm_family == AF_INET6 && prefixlen > 128)) - return -ERANGE; - - rtm->rtm_src_len = prefixlen; - - return 0; -} - -int sd_rtnl_message_route_set_scope(sd_netlink_message *m, unsigned char scope) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - rtm->rtm_scope = scope; - - return 0; -} - -int sd_rtnl_message_route_set_flags(sd_netlink_message *m, unsigned flags) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - rtm->rtm_flags = flags; - - return 0; -} - -int sd_rtnl_message_route_get_flags(sd_netlink_message *m, unsigned *flags) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - assert_return(flags, -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - *flags = rtm->rtm_flags; - - return 0; -} - -int sd_rtnl_message_route_set_table(sd_netlink_message *m, unsigned char table) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - rtm->rtm_table = table; - - return 0; -} - -int sd_rtnl_message_route_get_family(sd_netlink_message *m, int *family) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - assert_return(family, -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - *family = rtm->rtm_family; - - return 0; -} - -int sd_rtnl_message_route_set_family(sd_netlink_message *m, int family) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - rtm->rtm_family = family; - - return 0; -} - -int sd_rtnl_message_route_get_protocol(sd_netlink_message *m, unsigned char *protocol) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - assert_return(protocol, -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - *protocol = rtm->rtm_protocol; - - return 0; -} - -int sd_rtnl_message_route_get_scope(sd_netlink_message *m, unsigned char *scope) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - assert_return(scope, -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - *scope = rtm->rtm_scope; - - return 0; -} - -int sd_rtnl_message_route_get_tos(sd_netlink_message *m, unsigned char *tos) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - assert_return(tos, -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - *tos = rtm->rtm_tos; - - return 0; -} - -int sd_rtnl_message_route_get_table(sd_netlink_message *m, unsigned char *table) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - assert_return(table, -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - *table = rtm->rtm_table; - - return 0; -} - -int sd_rtnl_message_route_get_dst_prefixlen(sd_netlink_message *m, unsigned char *dst_len) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - assert_return(dst_len, -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - *dst_len = rtm->rtm_dst_len; - - return 0; -} - -int sd_rtnl_message_route_get_src_prefixlen(sd_netlink_message *m, unsigned char *src_len) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - assert_return(src_len, -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - *src_len = rtm->rtm_src_len; - - return 0; -} - -int sd_rtnl_message_new_route(sd_netlink *rtnl, sd_netlink_message **ret, - uint16_t nlmsg_type, int rtm_family, - unsigned char rtm_protocol) { - struct rtmsg *rtm; - int r; - - assert_return(rtnl_message_type_is_route(nlmsg_type), -EINVAL); - assert_return((nlmsg_type == RTM_GETROUTE && rtm_family == AF_UNSPEC) || - rtm_family == AF_INET || rtm_family == AF_INET6, -EINVAL); - assert_return(ret, -EINVAL); - - r = message_new(rtnl, ret, nlmsg_type); - if (r < 0) - return r; - - if (nlmsg_type == RTM_NEWROUTE) - (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_APPEND; - - rtm = NLMSG_DATA((*ret)->hdr); - - rtm->rtm_family = rtm_family; - rtm->rtm_scope = RT_SCOPE_UNIVERSE; - rtm->rtm_type = RTN_UNICAST; - rtm->rtm_table = RT_TABLE_MAIN; - rtm->rtm_protocol = rtm_protocol; - - return 0; -} - -int sd_rtnl_message_neigh_set_flags(sd_netlink_message *m, uint8_t flags) { - struct ndmsg *ndm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); - - ndm = NLMSG_DATA(m->hdr); - ndm->ndm_flags |= flags; - - return 0; -} - -int sd_rtnl_message_neigh_set_state(sd_netlink_message *m, uint16_t state) { - struct ndmsg *ndm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); - - ndm = NLMSG_DATA(m->hdr); - ndm->ndm_state |= state; - - return 0; -} - -int sd_rtnl_message_neigh_get_flags(sd_netlink_message *m, uint8_t *flags) { - struct ndmsg *ndm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); - - ndm = NLMSG_DATA(m->hdr); - *flags = ndm->ndm_flags; - - return 0; -} - -int sd_rtnl_message_neigh_get_state(sd_netlink_message *m, uint16_t *state) { - struct ndmsg *ndm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); - - ndm = NLMSG_DATA(m->hdr); - *state = ndm->ndm_state; - - return 0; -} - -int sd_rtnl_message_neigh_get_family(sd_netlink_message *m, int *family) { - struct ndmsg *ndm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); - assert_return(family, -EINVAL); - - ndm = NLMSG_DATA(m->hdr); - - *family = ndm->ndm_family; - - return 0; -} - -int sd_rtnl_message_neigh_get_ifindex(sd_netlink_message *m, int *index) { - struct ndmsg *ndm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); - assert_return(index, -EINVAL); - - ndm = NLMSG_DATA(m->hdr); - - *index = ndm->ndm_ifindex; - - return 0; -} - -int sd_rtnl_message_new_neigh(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nlmsg_type, int index, int ndm_family) { - struct ndmsg *ndm; - int r; - - assert_return(rtnl_message_type_is_neigh(nlmsg_type), -EINVAL); - assert_return(ndm_family == AF_INET || - ndm_family == AF_INET6 || - ndm_family == PF_BRIDGE, -EINVAL); - assert_return(ret, -EINVAL); - - r = message_new(rtnl, ret, nlmsg_type); - if (r < 0) - return r; - - if (nlmsg_type == RTM_NEWNEIGH) - (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_APPEND; - - ndm = NLMSG_DATA((*ret)->hdr); - - ndm->ndm_family = ndm_family; - ndm->ndm_ifindex = index; - - return 0; -} - -int sd_rtnl_message_link_set_flags(sd_netlink_message *m, unsigned flags, unsigned change) { - struct ifinfomsg *ifi; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); - assert_return(change, -EINVAL); - - ifi = NLMSG_DATA(m->hdr); - - ifi->ifi_flags = flags; - ifi->ifi_change = change; - - return 0; -} - -int sd_rtnl_message_link_set_type(sd_netlink_message *m, unsigned type) { - struct ifinfomsg *ifi; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); - - ifi = NLMSG_DATA(m->hdr); - - ifi->ifi_type = type; - - return 0; -} - -int sd_rtnl_message_link_set_family(sd_netlink_message *m, unsigned family) { - struct ifinfomsg *ifi; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); - - ifi = NLMSG_DATA(m->hdr); - - ifi->ifi_family = family; - - return 0; -} - -int sd_rtnl_message_new_link(sd_netlink *rtnl, sd_netlink_message **ret, - uint16_t nlmsg_type, int index) { - struct ifinfomsg *ifi; - int r; - - assert_return(rtnl_message_type_is_link(nlmsg_type), -EINVAL); - assert_return(ret, -EINVAL); - - r = message_new(rtnl, ret, nlmsg_type); - if (r < 0) - return r; - - if (nlmsg_type == RTM_NEWLINK) - (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; - - ifi = NLMSG_DATA((*ret)->hdr); - - ifi->ifi_family = AF_UNSPEC; - ifi->ifi_index = index; - - return 0; -} - -int sd_rtnl_message_addr_set_prefixlen(sd_netlink_message *m, unsigned char prefixlen) { - struct ifaddrmsg *ifa; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); - - ifa = NLMSG_DATA(m->hdr); - - if ((ifa->ifa_family == AF_INET && prefixlen > 32) || - (ifa->ifa_family == AF_INET6 && prefixlen > 128)) - return -ERANGE; - - ifa->ifa_prefixlen = prefixlen; - - return 0; -} - -int sd_rtnl_message_addr_set_flags(sd_netlink_message *m, unsigned char flags) { - struct ifaddrmsg *ifa; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); - - ifa = NLMSG_DATA(m->hdr); - - ifa->ifa_flags = flags; - - return 0; -} - -int sd_rtnl_message_addr_set_scope(sd_netlink_message *m, unsigned char scope) { - struct ifaddrmsg *ifa; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); - - ifa = NLMSG_DATA(m->hdr); - - ifa->ifa_scope = scope; - - return 0; -} - -int sd_rtnl_message_addr_get_family(sd_netlink_message *m, int *family) { - struct ifaddrmsg *ifa; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); - assert_return(family, -EINVAL); - - ifa = NLMSG_DATA(m->hdr); - - *family = ifa->ifa_family; - - return 0; -} - -int sd_rtnl_message_addr_get_prefixlen(sd_netlink_message *m, unsigned char *prefixlen) { - struct ifaddrmsg *ifa; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); - assert_return(prefixlen, -EINVAL); - - ifa = NLMSG_DATA(m->hdr); - - *prefixlen = ifa->ifa_prefixlen; - - return 0; -} - -int sd_rtnl_message_addr_get_scope(sd_netlink_message *m, unsigned char *scope) { - struct ifaddrmsg *ifa; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); - assert_return(scope, -EINVAL); - - ifa = NLMSG_DATA(m->hdr); - - *scope = ifa->ifa_scope; - - return 0; -} - -int sd_rtnl_message_addr_get_flags(sd_netlink_message *m, unsigned char *flags) { - struct ifaddrmsg *ifa; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); - assert_return(flags, -EINVAL); - - ifa = NLMSG_DATA(m->hdr); - - *flags = ifa->ifa_flags; - - return 0; -} - -int sd_rtnl_message_addr_get_ifindex(sd_netlink_message *m, int *ifindex) { - struct ifaddrmsg *ifa; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); - assert_return(ifindex, -EINVAL); - - ifa = NLMSG_DATA(m->hdr); - - *ifindex = ifa->ifa_index; - - return 0; -} - -int sd_rtnl_message_new_addr(sd_netlink *rtnl, sd_netlink_message **ret, - uint16_t nlmsg_type, int index, - int family) { - struct ifaddrmsg *ifa; - int r; - - assert_return(rtnl_message_type_is_addr(nlmsg_type), -EINVAL); - assert_return((nlmsg_type == RTM_GETADDR && index == 0) || - index > 0, -EINVAL); - assert_return((nlmsg_type == RTM_GETADDR && family == AF_UNSPEC) || - family == AF_INET || family == AF_INET6, -EINVAL); - assert_return(ret, -EINVAL); - - r = message_new(rtnl, ret, nlmsg_type); - if (r < 0) - return r; - - if (nlmsg_type == RTM_GETADDR) - (*ret)->hdr->nlmsg_flags |= NLM_F_DUMP; - - ifa = NLMSG_DATA((*ret)->hdr); - - ifa->ifa_index = index; - ifa->ifa_family = family; - if (family == AF_INET) - ifa->ifa_prefixlen = 32; - else if (family == AF_INET6) - ifa->ifa_prefixlen = 128; - - return 0; -} - -int sd_rtnl_message_new_addr_update(sd_netlink *rtnl, sd_netlink_message **ret, - int index, int family) { - int r; - - r = sd_rtnl_message_new_addr(rtnl, ret, RTM_NEWADDR, index, family); - if (r < 0) - return r; - - (*ret)->hdr->nlmsg_flags |= NLM_F_REPLACE; - - return 0; -} - -int sd_rtnl_message_link_get_ifindex(sd_netlink_message *m, int *ifindex) { - struct ifinfomsg *ifi; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); - assert_return(ifindex, -EINVAL); - - ifi = NLMSG_DATA(m->hdr); - - *ifindex = ifi->ifi_index; - - return 0; -} - -int sd_rtnl_message_link_get_flags(sd_netlink_message *m, unsigned *flags) { - struct ifinfomsg *ifi; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); - assert_return(flags, -EINVAL); - - ifi = NLMSG_DATA(m->hdr); - - *flags = ifi->ifi_flags; - - return 0; -} - -int sd_rtnl_message_link_get_type(sd_netlink_message *m, unsigned short *type) { - struct ifinfomsg *ifi; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); - assert_return(type, -EINVAL); - - ifi = NLMSG_DATA(m->hdr); - - *type = ifi->ifi_type; - - return 0; -} - -int sd_rtnl_message_get_family(sd_netlink_message *m, int *family) { - assert_return(m, -EINVAL); - assert_return(family, -EINVAL); - - assert(m->hdr); - - if (rtnl_message_type_is_link(m->hdr->nlmsg_type)) { - struct ifinfomsg *ifi; - - ifi = NLMSG_DATA(m->hdr); - - *family = ifi->ifi_family; - - return 0; - } else if (rtnl_message_type_is_route(m->hdr->nlmsg_type)) { - struct rtmsg *rtm; - - rtm = NLMSG_DATA(m->hdr); - - *family = rtm->rtm_family; - - return 0; - } else if (rtnl_message_type_is_neigh(m->hdr->nlmsg_type)) { - struct ndmsg *ndm; - - ndm = NLMSG_DATA(m->hdr); - - *family = ndm->ndm_family; - - return 0; - } else if (rtnl_message_type_is_addr(m->hdr->nlmsg_type)) { - struct ifaddrmsg *ifa; - - ifa = NLMSG_DATA(m->hdr); - - *family = ifa->ifa_family; - - return 0; - } - - return -EOPNOTSUPP; -} diff --git a/src/libsystemd/sd-netlink/sd-netlink.c b/src/libsystemd/sd-netlink/sd-netlink.c deleted file mode 100644 index 91701405a5..0000000000 --- a/src/libsystemd/sd-netlink/sd-netlink.c +++ /dev/null @@ -1,954 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 Tom Gundersen - - 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 - -#include "sd-netlink.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "hashmap.h" -#include "macro.h" -#include "missing.h" -#include "netlink-internal.h" -#include "netlink-util.h" -#include "socket-util.h" -#include "util.h" - -static int sd_netlink_new(sd_netlink **ret) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - - assert_return(ret, -EINVAL); - - rtnl = new0(sd_netlink, 1); - if (!rtnl) - return -ENOMEM; - - rtnl->n_ref = REFCNT_INIT; - rtnl->fd = -1; - rtnl->sockaddr.nl.nl_family = AF_NETLINK; - rtnl->original_pid = getpid(); - - LIST_HEAD_INIT(rtnl->match_callbacks); - - /* We guarantee that the read buffer has at least space for - * a message header */ - if (!greedy_realloc((void**)&rtnl->rbuffer, &rtnl->rbuffer_allocated, - sizeof(struct nlmsghdr), sizeof(uint8_t))) - return -ENOMEM; - - /* Change notification responses have sequence 0, so we must - * start our request sequence numbers at 1, or we may confuse our - * responses with notifications from the kernel */ - rtnl->serial = 1; - - *ret = rtnl; - rtnl = NULL; - - return 0; -} - -int sd_netlink_new_from_netlink(sd_netlink **ret, int fd) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - socklen_t addrlen; - int r; - - assert_return(ret, -EINVAL); - - r = sd_netlink_new(&rtnl); - if (r < 0) - return r; - - addrlen = sizeof(rtnl->sockaddr); - - r = getsockname(fd, &rtnl->sockaddr.sa, &addrlen); - if (r < 0) - return -errno; - - if (rtnl->sockaddr.nl.nl_family != AF_NETLINK) - return -EINVAL; - - rtnl->fd = fd; - - *ret = rtnl; - rtnl = NULL; - - return 0; -} - -static bool rtnl_pid_changed(sd_netlink *rtnl) { - assert(rtnl); - - /* We don't support people creating an rtnl connection and - * keeping it around over a fork(). Let's complain. */ - - return rtnl->original_pid != getpid(); -} - -int sd_netlink_open_fd(sd_netlink **ret, int fd) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - int r; - - assert_return(ret, -EINVAL); - assert_return(fd >= 0, -EBADF); - - r = sd_netlink_new(&rtnl); - if (r < 0) - return r; - - rtnl->fd = fd; - - r = socket_bind(rtnl); - if (r < 0) { - rtnl->fd = -1; /* on failure, the caller remains owner of the fd, hence don't close it here */ - return r; - } - - *ret = rtnl; - rtnl = NULL; - - return 0; -} - -int sd_netlink_open(sd_netlink **ret) { - _cleanup_close_ int fd = -1; - int r; - - fd = socket_open(NETLINK_ROUTE); - if (fd < 0) - return fd; - - r = sd_netlink_open_fd(ret, fd); - if (r < 0) - return r; - - fd = -1; - - return 0; -} - -int sd_netlink_inc_rcvbuf(const sd_netlink *const rtnl, const int size) { - return fd_inc_rcvbuf(rtnl->fd, size); -} - -sd_netlink *sd_netlink_ref(sd_netlink *rtnl) { - assert_return(rtnl, NULL); - assert_return(!rtnl_pid_changed(rtnl), NULL); - - if (rtnl) - assert_se(REFCNT_INC(rtnl->n_ref) >= 2); - - return rtnl; -} - -sd_netlink *sd_netlink_unref(sd_netlink *rtnl) { - if (!rtnl) - return NULL; - - assert_return(!rtnl_pid_changed(rtnl), NULL); - - if (REFCNT_DEC(rtnl->n_ref) == 0) { - struct match_callback *f; - unsigned i; - - for (i = 0; i < rtnl->rqueue_size; i++) - sd_netlink_message_unref(rtnl->rqueue[i]); - free(rtnl->rqueue); - - for (i = 0; i < rtnl->rqueue_partial_size; i++) - sd_netlink_message_unref(rtnl->rqueue_partial[i]); - free(rtnl->rqueue_partial); - - free(rtnl->rbuffer); - - hashmap_free_free(rtnl->reply_callbacks); - prioq_free(rtnl->reply_callbacks_prioq); - - sd_event_source_unref(rtnl->io_event_source); - sd_event_source_unref(rtnl->time_event_source); - sd_event_unref(rtnl->event); - - while ((f = rtnl->match_callbacks)) { - sd_netlink_remove_match(rtnl, f->type, f->callback, f->userdata); - } - - hashmap_free(rtnl->broadcast_group_refs); - - safe_close(rtnl->fd); - free(rtnl); - } - - return NULL; -} - -static void rtnl_seal_message(sd_netlink *rtnl, sd_netlink_message *m) { - assert(rtnl); - assert(!rtnl_pid_changed(rtnl)); - assert(m); - assert(m->hdr); - - /* don't use seq == 0, as that is used for broadcasts, so we - would get confused by replies to such messages */ - m->hdr->nlmsg_seq = rtnl->serial++ ? : rtnl->serial++; - - rtnl_message_seal(m); - - return; -} - -int sd_netlink_send(sd_netlink *nl, - sd_netlink_message *message, - uint32_t *serial) { - int r; - - assert_return(nl, -EINVAL); - assert_return(!rtnl_pid_changed(nl), -ECHILD); - assert_return(message, -EINVAL); - assert_return(!message->sealed, -EPERM); - - rtnl_seal_message(nl, message); - - r = socket_write_message(nl, message); - if (r < 0) - return r; - - if (serial) - *serial = rtnl_message_get_serial(message); - - return 1; -} - -int rtnl_rqueue_make_room(sd_netlink *rtnl) { - assert(rtnl); - - if (rtnl->rqueue_size >= RTNL_RQUEUE_MAX) { - log_debug("rtnl: exhausted the read queue size (%d)", RTNL_RQUEUE_MAX); - return -ENOBUFS; - } - - if (!GREEDY_REALLOC(rtnl->rqueue, rtnl->rqueue_allocated, rtnl->rqueue_size + 1)) - return -ENOMEM; - - return 0; -} - -int rtnl_rqueue_partial_make_room(sd_netlink *rtnl) { - assert(rtnl); - - if (rtnl->rqueue_partial_size >= RTNL_RQUEUE_MAX) { - log_debug("rtnl: exhausted the partial read queue size (%d)", RTNL_RQUEUE_MAX); - return -ENOBUFS; - } - - if (!GREEDY_REALLOC(rtnl->rqueue_partial, rtnl->rqueue_partial_allocated, - rtnl->rqueue_partial_size + 1)) - return -ENOMEM; - - return 0; -} - -static int dispatch_rqueue(sd_netlink *rtnl, sd_netlink_message **message) { - int r; - - assert(rtnl); - assert(message); - - if (rtnl->rqueue_size <= 0) { - /* Try to read a new message */ - r = socket_read_message(rtnl); - if (r <= 0) - return r; - } - - /* Dispatch a queued message */ - *message = rtnl->rqueue[0]; - rtnl->rqueue_size--; - memmove(rtnl->rqueue, rtnl->rqueue + 1, sizeof(sd_netlink_message*) * rtnl->rqueue_size); - - return 1; -} - -static int process_timeout(sd_netlink *rtnl) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - struct reply_callback *c; - usec_t n; - int r; - - assert(rtnl); - - c = prioq_peek(rtnl->reply_callbacks_prioq); - if (!c) - return 0; - - n = now(CLOCK_MONOTONIC); - if (c->timeout > n) - return 0; - - r = rtnl_message_new_synthetic_error(-ETIMEDOUT, c->serial, &m); - if (r < 0) - return r; - - assert_se(prioq_pop(rtnl->reply_callbacks_prioq) == c); - hashmap_remove(rtnl->reply_callbacks, &c->serial); - - r = c->callback(rtnl, m, c->userdata); - if (r < 0) - log_debug_errno(r, "sd-netlink: timedout callback failed: %m"); - - free(c); - - return 1; -} - -static int process_reply(sd_netlink *rtnl, sd_netlink_message *m) { - _cleanup_free_ struct reply_callback *c = NULL; - uint64_t serial; - uint16_t type; - int r; - - assert(rtnl); - assert(m); - - serial = rtnl_message_get_serial(m); - c = hashmap_remove(rtnl->reply_callbacks, &serial); - if (!c) - return 0; - - if (c->timeout != 0) - prioq_remove(rtnl->reply_callbacks_prioq, c, &c->prioq_idx); - - r = sd_netlink_message_get_type(m, &type); - if (r < 0) - return 0; - - if (type == NLMSG_DONE) - m = NULL; - - r = c->callback(rtnl, m, c->userdata); - if (r < 0) - log_debug_errno(r, "sd-netlink: callback failed: %m"); - - return 1; -} - -static int process_match(sd_netlink *rtnl, sd_netlink_message *m) { - struct match_callback *c; - uint16_t type; - int r; - - assert(rtnl); - assert(m); - - r = sd_netlink_message_get_type(m, &type); - if (r < 0) - return r; - - LIST_FOREACH(match_callbacks, c, rtnl->match_callbacks) { - if (type == c->type) { - r = c->callback(rtnl, m, c->userdata); - if (r != 0) { - if (r < 0) - log_debug_errno(r, "sd-netlink: match callback failed: %m"); - - break; - } - } - } - - return 1; -} - -static int process_running(sd_netlink *rtnl, sd_netlink_message **ret) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - int r; - - assert(rtnl); - - r = process_timeout(rtnl); - if (r != 0) - goto null_message; - - r = dispatch_rqueue(rtnl, &m); - if (r < 0) - return r; - if (!m) - goto null_message; - - if (sd_netlink_message_is_broadcast(m)) { - r = process_match(rtnl, m); - if (r != 0) - goto null_message; - } else { - r = process_reply(rtnl, m); - if (r != 0) - goto null_message; - } - - if (ret) { - *ret = m; - m = NULL; - - return 1; - } - - return 1; - -null_message: - if (r >= 0 && ret) - *ret = NULL; - - return r; -} - -int sd_netlink_process(sd_netlink *rtnl, sd_netlink_message **ret) { - NETLINK_DONT_DESTROY(rtnl); - int r; - - assert_return(rtnl, -EINVAL); - assert_return(!rtnl_pid_changed(rtnl), -ECHILD); - assert_return(!rtnl->processing, -EBUSY); - - rtnl->processing = true; - r = process_running(rtnl, ret); - rtnl->processing = false; - - return r; -} - -static usec_t calc_elapse(uint64_t usec) { - if (usec == (uint64_t) -1) - return 0; - - if (usec == 0) - usec = RTNL_DEFAULT_TIMEOUT; - - return now(CLOCK_MONOTONIC) + usec; -} - -static int rtnl_poll(sd_netlink *rtnl, bool need_more, uint64_t timeout_usec) { - struct pollfd p[1] = {}; - struct timespec ts; - usec_t m = USEC_INFINITY; - int r, e; - - assert(rtnl); - - e = sd_netlink_get_events(rtnl); - if (e < 0) - return e; - - if (need_more) - /* Caller wants more data, and doesn't care about - * what's been read or any other timeouts. */ - e |= POLLIN; - else { - usec_t until; - /* Caller wants to process if there is something to - * process, but doesn't care otherwise */ - - r = sd_netlink_get_timeout(rtnl, &until); - if (r < 0) - return r; - if (r > 0) { - usec_t nw; - nw = now(CLOCK_MONOTONIC); - m = until > nw ? until - nw : 0; - } - } - - if (timeout_usec != (uint64_t) -1 && (m == (uint64_t) -1 || timeout_usec < m)) - m = timeout_usec; - - p[0].fd = rtnl->fd; - p[0].events = e; - - r = ppoll(p, 1, m == (uint64_t) -1 ? NULL : timespec_store(&ts, m), NULL); - if (r < 0) - return -errno; - - return r > 0 ? 1 : 0; -} - -int sd_netlink_wait(sd_netlink *nl, uint64_t timeout_usec) { - assert_return(nl, -EINVAL); - assert_return(!rtnl_pid_changed(nl), -ECHILD); - - if (nl->rqueue_size > 0) - return 0; - - return rtnl_poll(nl, false, timeout_usec); -} - -static int timeout_compare(const void *a, const void *b) { - const struct reply_callback *x = a, *y = b; - - if (x->timeout != 0 && y->timeout == 0) - return -1; - - if (x->timeout == 0 && y->timeout != 0) - return 1; - - if (x->timeout < y->timeout) - return -1; - - if (x->timeout > y->timeout) - return 1; - - return 0; -} - -int sd_netlink_call_async(sd_netlink *nl, - sd_netlink_message *m, - sd_netlink_message_handler_t callback, - void *userdata, - uint64_t usec, - uint32_t *serial) { - struct reply_callback *c; - uint32_t s; - int r, k; - - assert_return(nl, -EINVAL); - assert_return(m, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(!rtnl_pid_changed(nl), -ECHILD); - - r = hashmap_ensure_allocated(&nl->reply_callbacks, &uint64_hash_ops); - if (r < 0) - return r; - - if (usec != (uint64_t) -1) { - r = prioq_ensure_allocated(&nl->reply_callbacks_prioq, timeout_compare); - if (r < 0) - return r; - } - - c = new0(struct reply_callback, 1); - if (!c) - return -ENOMEM; - - c->callback = callback; - c->userdata = userdata; - c->timeout = calc_elapse(usec); - - k = sd_netlink_send(nl, m, &s); - if (k < 0) { - free(c); - return k; - } - - c->serial = s; - - r = hashmap_put(nl->reply_callbacks, &c->serial, c); - if (r < 0) { - free(c); - return r; - } - - if (c->timeout != 0) { - r = prioq_put(nl->reply_callbacks_prioq, c, &c->prioq_idx); - if (r > 0) { - c->timeout = 0; - sd_netlink_call_async_cancel(nl, c->serial); - return r; - } - } - - if (serial) - *serial = s; - - return k; -} - -int sd_netlink_call_async_cancel(sd_netlink *nl, uint32_t serial) { - struct reply_callback *c; - uint64_t s = serial; - - assert_return(nl, -EINVAL); - assert_return(serial != 0, -EINVAL); - assert_return(!rtnl_pid_changed(nl), -ECHILD); - - c = hashmap_remove(nl->reply_callbacks, &s); - if (!c) - return 0; - - if (c->timeout != 0) - prioq_remove(nl->reply_callbacks_prioq, c, &c->prioq_idx); - - free(c); - return 1; -} - -int sd_netlink_call(sd_netlink *rtnl, - sd_netlink_message *message, - uint64_t usec, - sd_netlink_message **ret) { - usec_t timeout; - uint32_t serial; - int r; - - assert_return(rtnl, -EINVAL); - assert_return(!rtnl_pid_changed(rtnl), -ECHILD); - assert_return(message, -EINVAL); - - r = sd_netlink_send(rtnl, message, &serial); - if (r < 0) - return r; - - timeout = calc_elapse(usec); - - for (;;) { - usec_t left; - unsigned i; - - for (i = 0; i < rtnl->rqueue_size; i++) { - uint32_t received_serial; - - received_serial = rtnl_message_get_serial(rtnl->rqueue[i]); - - if (received_serial == serial) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *incoming = NULL; - uint16_t type; - - incoming = rtnl->rqueue[i]; - - /* found a match, remove from rqueue and return it */ - memmove(rtnl->rqueue + i,rtnl->rqueue + i + 1, - sizeof(sd_netlink_message*) * (rtnl->rqueue_size - i - 1)); - rtnl->rqueue_size--; - - r = sd_netlink_message_get_errno(incoming); - if (r < 0) - return r; - - r = sd_netlink_message_get_type(incoming, &type); - if (r < 0) - return r; - - if (type == NLMSG_DONE) { - *ret = NULL; - return 0; - } - - if (ret) { - *ret = incoming; - incoming = NULL; - } - - return 1; - } - } - - r = socket_read_message(rtnl); - if (r < 0) - return r; - if (r > 0) - /* received message, so try to process straight away */ - continue; - - if (timeout > 0) { - usec_t n; - - n = now(CLOCK_MONOTONIC); - if (n >= timeout) - return -ETIMEDOUT; - - left = timeout - n; - } else - left = (uint64_t) -1; - - r = rtnl_poll(rtnl, true, left); - if (r < 0) - return r; - else if (r == 0) - return -ETIMEDOUT; - } -} - -int sd_netlink_get_events(sd_netlink *rtnl) { - assert_return(rtnl, -EINVAL); - assert_return(!rtnl_pid_changed(rtnl), -ECHILD); - - if (rtnl->rqueue_size == 0) - return POLLIN; - else - return 0; -} - -int sd_netlink_get_timeout(sd_netlink *rtnl, uint64_t *timeout_usec) { - struct reply_callback *c; - - assert_return(rtnl, -EINVAL); - assert_return(timeout_usec, -EINVAL); - assert_return(!rtnl_pid_changed(rtnl), -ECHILD); - - if (rtnl->rqueue_size > 0) { - *timeout_usec = 0; - return 1; - } - - c = prioq_peek(rtnl->reply_callbacks_prioq); - if (!c) { - *timeout_usec = (uint64_t) -1; - return 0; - } - - *timeout_usec = c->timeout; - - return 1; -} - -static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - sd_netlink *rtnl = userdata; - int r; - - assert(rtnl); - - r = sd_netlink_process(rtnl, NULL); - if (r < 0) - return r; - - return 1; -} - -static int time_callback(sd_event_source *s, uint64_t usec, void *userdata) { - sd_netlink *rtnl = userdata; - int r; - - assert(rtnl); - - r = sd_netlink_process(rtnl, NULL); - if (r < 0) - return r; - - return 1; -} - -static int prepare_callback(sd_event_source *s, void *userdata) { - sd_netlink *rtnl = userdata; - int r, e; - usec_t until; - - assert(s); - assert(rtnl); - - e = sd_netlink_get_events(rtnl); - if (e < 0) - return e; - - r = sd_event_source_set_io_events(rtnl->io_event_source, e); - if (r < 0) - return r; - - r = sd_netlink_get_timeout(rtnl, &until); - if (r < 0) - return r; - if (r > 0) { - int j; - - j = sd_event_source_set_time(rtnl->time_event_source, until); - if (j < 0) - return j; - } - - r = sd_event_source_set_enabled(rtnl->time_event_source, r > 0); - if (r < 0) - return r; - - return 1; -} - -int sd_netlink_attach_event(sd_netlink *rtnl, sd_event *event, int64_t priority) { - int r; - - assert_return(rtnl, -EINVAL); - assert_return(!rtnl->event, -EBUSY); - - assert(!rtnl->io_event_source); - assert(!rtnl->time_event_source); - - if (event) - rtnl->event = sd_event_ref(event); - else { - r = sd_event_default(&rtnl->event); - if (r < 0) - return r; - } - - r = sd_event_add_io(rtnl->event, &rtnl->io_event_source, rtnl->fd, 0, io_callback, rtnl); - if (r < 0) - goto fail; - - r = sd_event_source_set_priority(rtnl->io_event_source, priority); - if (r < 0) - goto fail; - - r = sd_event_source_set_description(rtnl->io_event_source, "rtnl-receive-message"); - if (r < 0) - goto fail; - - r = sd_event_source_set_prepare(rtnl->io_event_source, prepare_callback); - if (r < 0) - goto fail; - - r = sd_event_add_time(rtnl->event, &rtnl->time_event_source, CLOCK_MONOTONIC, 0, 0, time_callback, rtnl); - if (r < 0) - goto fail; - - r = sd_event_source_set_priority(rtnl->time_event_source, priority); - if (r < 0) - goto fail; - - r = sd_event_source_set_description(rtnl->time_event_source, "rtnl-timer"); - if (r < 0) - goto fail; - - return 0; - -fail: - sd_netlink_detach_event(rtnl); - return r; -} - -int sd_netlink_detach_event(sd_netlink *rtnl) { - assert_return(rtnl, -EINVAL); - assert_return(rtnl->event, -ENXIO); - - rtnl->io_event_source = sd_event_source_unref(rtnl->io_event_source); - - rtnl->time_event_source = sd_event_source_unref(rtnl->time_event_source); - - rtnl->event = sd_event_unref(rtnl->event); - - return 0; -} - -int sd_netlink_add_match(sd_netlink *rtnl, - uint16_t type, - sd_netlink_message_handler_t callback, - void *userdata) { - _cleanup_free_ struct match_callback *c = NULL; - int r; - - assert_return(rtnl, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(!rtnl_pid_changed(rtnl), -ECHILD); - - c = new0(struct match_callback, 1); - if (!c) - return -ENOMEM; - - c->callback = callback; - c->type = type; - c->userdata = userdata; - - switch (type) { - case RTM_NEWLINK: - case RTM_DELLINK: - r = socket_broadcast_group_ref(rtnl, RTNLGRP_LINK); - if (r < 0) - return r; - - break; - case RTM_NEWADDR: - case RTM_DELADDR: - r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV4_IFADDR); - if (r < 0) - return r; - - r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV6_IFADDR); - if (r < 0) - return r; - - break; - case RTM_NEWROUTE: - case RTM_DELROUTE: - r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV4_ROUTE); - if (r < 0) - return r; - - r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV6_ROUTE); - if (r < 0) - return r; - break; - default: - return -EOPNOTSUPP; - } - - LIST_PREPEND(match_callbacks, rtnl->match_callbacks, c); - - c = NULL; - - return 0; -} - -int sd_netlink_remove_match(sd_netlink *rtnl, - uint16_t type, - sd_netlink_message_handler_t callback, - void *userdata) { - struct match_callback *c; - int r; - - assert_return(rtnl, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(!rtnl_pid_changed(rtnl), -ECHILD); - - LIST_FOREACH(match_callbacks, c, rtnl->match_callbacks) - if (c->callback == callback && c->type == type && c->userdata == userdata) { - LIST_REMOVE(match_callbacks, rtnl->match_callbacks, c); - free(c); - - switch (type) { - case RTM_NEWLINK: - case RTM_DELLINK: - r = socket_broadcast_group_unref(rtnl, RTNLGRP_LINK); - if (r < 0) - return r; - - break; - case RTM_NEWADDR: - case RTM_DELADDR: - r = socket_broadcast_group_unref(rtnl, RTNLGRP_IPV4_IFADDR); - if (r < 0) - return r; - - r = socket_broadcast_group_unref(rtnl, RTNLGRP_IPV6_IFADDR); - if (r < 0) - return r; - - break; - case RTM_NEWROUTE: - case RTM_DELROUTE: - r = socket_broadcast_group_unref(rtnl, RTNLGRP_IPV4_ROUTE); - if (r < 0) - return r; - - r = socket_broadcast_group_unref(rtnl, RTNLGRP_IPV6_ROUTE); - if (r < 0) - return r; - break; - default: - return -EOPNOTSUPP; - } - - return 1; - } - - return 0; -} diff --git a/src/libsystemd/sd-netlink/test-local-addresses.c b/src/libsystemd/sd-netlink/test-local-addresses.c deleted file mode 100644 index e0e28cc0cc..0000000000 --- a/src/libsystemd/sd-netlink/test-local-addresses.c +++ /dev/null @@ -1,56 +0,0 @@ -/*** - 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 "af-list.h" -#include "alloc-util.h" -#include "in-addr-util.h" -#include "local-addresses.h" - -static void print_local_addresses(struct local_address *a, unsigned n) { - unsigned i; - - for (i = 0; i < n; i++) { - _cleanup_free_ char *b = NULL; - - assert_se(in_addr_to_string(a[i].family, &a[i].address, &b) >= 0); - printf("%s if%i scope=%i metric=%u address=%s\n", af_to_name(a[i].family), a[i].ifindex, a[i].scope, a[i].metric, b); - } -} - -int main(int argc, char *argv[]) { - struct local_address *a; - int n; - - a = NULL; - n = local_addresses(NULL, 0, AF_UNSPEC, &a); - assert_se(n >= 0); - - printf("Local Addresses:\n"); - print_local_addresses(a, (unsigned) n); - a = mfree(a); - - n = local_gateways(NULL, 0, AF_UNSPEC, &a); - assert_se(n >= 0); - - printf("Local Gateways:\n"); - print_local_addresses(a, (unsigned) n); - free(a); - - return 0; -} diff --git a/src/libsystemd/sd-netlink/test-netlink.c b/src/libsystemd/sd-netlink/test-netlink.c deleted file mode 100644 index 58c2e892f5..0000000000 --- a/src/libsystemd/sd-netlink/test-netlink.c +++ /dev/null @@ -1,440 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 Tom Gundersen - - 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 - -#include "sd-netlink.h" - -#include "ether-addr-util.h" -#include "macro.h" -#include "missing.h" -#include "netlink-util.h" -#include "socket-util.h" -#include "string-util.h" -#include "util.h" - -static void test_message_link_bridge(sd_netlink *rtnl) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; - uint32_t cost; - - assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_NEWLINK, 1) >= 0); - assert_se(sd_rtnl_message_link_set_family(message, PF_BRIDGE) >= 0); - assert_se(sd_netlink_message_open_container(message, IFLA_PROTINFO) >= 0); - assert_se(sd_netlink_message_append_u32(message, IFLA_BRPORT_COST, 10) >= 0); - assert_se(sd_netlink_message_close_container(message) >= 0); - - assert_se(sd_netlink_message_rewind(message) >= 0); - - assert_se(sd_netlink_message_enter_container(message, IFLA_PROTINFO) >= 0); - assert_se(sd_netlink_message_read_u32(message, IFLA_BRPORT_COST, &cost) >= 0); - assert_se(cost == 10); - assert_se(sd_netlink_message_exit_container(message) >= 0); -} - -static void test_link_configure(sd_netlink *rtnl, int ifindex) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; - const char *mac = "98:fe:94:3f:c6:18", *name = "test"; - char buffer[ETHER_ADDR_TO_STRING_MAX]; - unsigned int mtu = 1450, mtu_out; - const char *name_out; - struct ether_addr mac_out; - - /* we'd really like to test NEWLINK, but let's not mess with the running kernel */ - assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_GETLINK, ifindex) >= 0); - assert_se(sd_netlink_message_append_string(message, IFLA_IFNAME, name) >= 0); - assert_se(sd_netlink_message_append_ether_addr(message, IFLA_ADDRESS, ether_aton(mac)) >= 0); - assert_se(sd_netlink_message_append_u32(message, IFLA_MTU, mtu) >= 0); - - assert_se(sd_netlink_call(rtnl, message, 0, NULL) == 1); - assert_se(sd_netlink_message_rewind(message) >= 0); - - assert_se(sd_netlink_message_read_string(message, IFLA_IFNAME, &name_out) >= 0); - assert_se(streq(name, name_out)); - - assert_se(sd_netlink_message_read_ether_addr(message, IFLA_ADDRESS, &mac_out) >= 0); - assert_se(streq(mac, ether_addr_to_string(&mac_out, buffer))); - - assert_se(sd_netlink_message_read_u32(message, IFLA_MTU, &mtu_out) >= 0); - assert_se(mtu == mtu_out); -} - -static void test_link_get(sd_netlink *rtnl, int ifindex) { - sd_netlink_message *m; - sd_netlink_message *r; - unsigned int mtu = 1500; - const char *str_data; - uint8_t u8_data; - uint32_t u32_data; - struct ether_addr eth_data; - - assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); - assert_se(m); - - /* u8 test cases */ - assert_se(sd_netlink_message_append_u8(m, IFLA_CARRIER, 0) >= 0); - assert_se(sd_netlink_message_append_u8(m, IFLA_OPERSTATE, 0) >= 0); - assert_se(sd_netlink_message_append_u8(m, IFLA_LINKMODE, 0) >= 0); - - /* u32 test cases */ - assert_se(sd_netlink_message_append_u32(m, IFLA_MTU, mtu) >= 0); - assert_se(sd_netlink_message_append_u32(m, IFLA_GROUP, 0) >= 0); - assert_se(sd_netlink_message_append_u32(m, IFLA_TXQLEN, 0) >= 0); - assert_se(sd_netlink_message_append_u32(m, IFLA_NUM_TX_QUEUES, 0) >= 0); - assert_se(sd_netlink_message_append_u32(m, IFLA_NUM_RX_QUEUES, 0) >= 0); - - assert_se(sd_netlink_call(rtnl, m, -1, &r) == 1); - - assert_se(sd_netlink_message_read_string(r, IFLA_IFNAME, &str_data) == 0); - - assert_se(sd_netlink_message_read_u8(r, IFLA_CARRIER, &u8_data) == 0); - assert_se(sd_netlink_message_read_u8(r, IFLA_OPERSTATE, &u8_data) == 0); - assert_se(sd_netlink_message_read_u8(r, IFLA_LINKMODE, &u8_data) == 0); - - assert_se(sd_netlink_message_read_u32(r, IFLA_MTU, &u32_data) == 0); - assert_se(sd_netlink_message_read_u32(r, IFLA_GROUP, &u32_data) == 0); - assert_se(sd_netlink_message_read_u32(r, IFLA_TXQLEN, &u32_data) == 0); - assert_se(sd_netlink_message_read_u32(r, IFLA_NUM_TX_QUEUES, &u32_data) == 0); - assert_se(sd_netlink_message_read_u32(r, IFLA_NUM_RX_QUEUES, &u32_data) == 0); - - assert_se(sd_netlink_message_read_ether_addr(r, IFLA_ADDRESS, ð_data) == 0); - - assert_se((m = sd_netlink_message_unref(m)) == NULL); - assert_se((r = sd_netlink_message_unref(r)) == NULL); -} - - -static void test_address_get(sd_netlink *rtnl, int ifindex) { - sd_netlink_message *m; - sd_netlink_message *r; - struct in_addr in_data; - struct ifa_cacheinfo cache; - const char *label; - - assert_se(sd_rtnl_message_new_addr(rtnl, &m, RTM_GETADDR, ifindex, AF_INET) >= 0); - assert_se(m); - - assert_se(sd_netlink_call(rtnl, m, -1, &r) == 1); - - assert_se(sd_netlink_message_read_in_addr(r, IFA_LOCAL, &in_data) == 0); - assert_se(sd_netlink_message_read_in_addr(r, IFA_ADDRESS, &in_data) == 0); - assert_se(sd_netlink_message_read_string(r, IFA_LABEL, &label) == 0); - assert_se(sd_netlink_message_read_cache_info(r, IFA_CACHEINFO, &cache) == 0); - - assert_se((m = sd_netlink_message_unref(m)) == NULL); - assert_se((r = sd_netlink_message_unref(r)) == NULL); - -} - -static void test_route(void) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req; - struct in_addr addr, addr_data; - uint32_t index = 2, u32_data; - int r; - - r = sd_rtnl_message_new_route(NULL, &req, RTM_NEWROUTE, AF_INET, RTPROT_STATIC); - if (r < 0) { - log_error_errno(r, "Could not create RTM_NEWROUTE message: %m"); - return; - } - - addr.s_addr = htonl(INADDR_LOOPBACK); - - r = sd_netlink_message_append_in_addr(req, RTA_GATEWAY, &addr); - if (r < 0) { - log_error_errno(r, "Could not append RTA_GATEWAY attribute: %m"); - return; - } - - r = sd_netlink_message_append_u32(req, RTA_OIF, index); - if (r < 0) { - log_error_errno(r, "Could not append RTA_OIF attribute: %m"); - return; - } - - assert_se(sd_netlink_message_rewind(req) >= 0); - - assert_se(sd_netlink_message_read_in_addr(req, RTA_GATEWAY, &addr_data) >= 0); - assert_se(addr_data.s_addr == addr.s_addr); - - assert_se(sd_netlink_message_read_u32(req, RTA_OIF, &u32_data) >= 0); - assert_se(u32_data == index); - - assert_se((req = sd_netlink_message_unref(req)) == NULL); -} - -static void test_multiple(void) { - sd_netlink *rtnl1, *rtnl2; - - assert_se(sd_netlink_open(&rtnl1) >= 0); - assert_se(sd_netlink_open(&rtnl2) >= 0); - - rtnl1 = sd_netlink_unref(rtnl1); - rtnl2 = sd_netlink_unref(rtnl2); -} - -static int link_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { - char *ifname = userdata; - const char *data; - - assert_se(rtnl); - assert_se(m); - - log_info("got link info about %s", ifname); - free(ifname); - - assert_se(sd_netlink_message_read_string(m, IFLA_IFNAME, &data) >= 0); - assert_se(streq(data, "lo")); - - return 1; -} - -static void test_event_loop(int ifindex) { - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - char *ifname; - - ifname = strdup("lo2"); - assert_se(ifname); - - assert_se(sd_netlink_open(&rtnl) >= 0); - assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); - - assert_se(sd_netlink_call_async(rtnl, m, link_handler, ifname, 0, NULL) >= 0); - - assert_se(sd_event_default(&event) >= 0); - - assert_se(sd_netlink_attach_event(rtnl, event, 0) >= 0); - - assert_se(sd_event_run(event, 0) >= 0); - - assert_se(sd_netlink_detach_event(rtnl) >= 0); - - assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); -} - -static int pipe_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { - int *counter = userdata; - int r; - - (*counter)--; - - r = sd_netlink_message_get_errno(m); - - log_info_errno(r, "%d left in pipe. got reply: %m", *counter); - - assert_se(r >= 0); - - return 1; -} - -static void test_async(int ifindex) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *r = NULL; - uint32_t serial; - char *ifname; - - ifname = strdup("lo"); - assert_se(ifname); - - assert_se(sd_netlink_open(&rtnl) >= 0); - - assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); - - assert_se(sd_netlink_call_async(rtnl, m, link_handler, ifname, 0, &serial) >= 0); - - assert_se(sd_netlink_wait(rtnl, 0) >= 0); - assert_se(sd_netlink_process(rtnl, &r) >= 0); - - assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); -} - -static void test_pipe(int ifindex) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m1 = NULL, *m2 = NULL; - int counter = 0; - - assert_se(sd_netlink_open(&rtnl) >= 0); - - assert_se(sd_rtnl_message_new_link(rtnl, &m1, RTM_GETLINK, ifindex) >= 0); - assert_se(sd_rtnl_message_new_link(rtnl, &m2, RTM_GETLINK, ifindex) >= 0); - - counter++; - assert_se(sd_netlink_call_async(rtnl, m1, pipe_handler, &counter, 0, NULL) >= 0); - - counter++; - assert_se(sd_netlink_call_async(rtnl, m2, pipe_handler, &counter, 0, NULL) >= 0); - - while (counter > 0) { - assert_se(sd_netlink_wait(rtnl, 0) >= 0); - assert_se(sd_netlink_process(rtnl, NULL) >= 0); - } - - assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); -} - -static void test_container(void) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - uint16_t u16_data; - uint32_t u32_data; - const char *string_data; - - assert_se(sd_rtnl_message_new_link(NULL, &m, RTM_NEWLINK, 0) >= 0); - - assert_se(sd_netlink_message_open_container(m, IFLA_LINKINFO) >= 0); - assert_se(sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "vlan") >= 0); - assert_se(sd_netlink_message_append_u16(m, IFLA_VLAN_ID, 100) >= 0); - assert_se(sd_netlink_message_close_container(m) >= 0); - assert_se(sd_netlink_message_append_string(m, IFLA_INFO_KIND, "vlan") >= 0); - assert_se(sd_netlink_message_close_container(m) >= 0); - assert_se(sd_netlink_message_close_container(m) == -EINVAL); - - assert_se(sd_netlink_message_rewind(m) >= 0); - - assert_se(sd_netlink_message_enter_container(m, IFLA_LINKINFO) >= 0); - assert_se(sd_netlink_message_read_string(m, IFLA_INFO_KIND, &string_data) >= 0); - assert_se(streq("vlan", string_data)); - - assert_se(sd_netlink_message_enter_container(m, IFLA_INFO_DATA) >= 0); - assert_se(sd_netlink_message_read_u16(m, IFLA_VLAN_ID, &u16_data) >= 0); - assert_se(sd_netlink_message_exit_container(m) >= 0); - - assert_se(sd_netlink_message_read_string(m, IFLA_INFO_KIND, &string_data) >= 0); - assert_se(streq("vlan", string_data)); - assert_se(sd_netlink_message_exit_container(m) >= 0); - - assert_se(sd_netlink_message_read_u32(m, IFLA_LINKINFO, &u32_data) < 0); - - assert_se(sd_netlink_message_exit_container(m) == -EINVAL); -} - -static void test_match(void) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - - assert_se(sd_netlink_open(&rtnl) >= 0); - - assert_se(sd_netlink_add_match(rtnl, RTM_NEWLINK, link_handler, NULL) >= 0); - assert_se(sd_netlink_add_match(rtnl, RTM_NEWLINK, link_handler, NULL) >= 0); - - assert_se(sd_netlink_remove_match(rtnl, RTM_NEWLINK, link_handler, NULL) == 1); - assert_se(sd_netlink_remove_match(rtnl, RTM_NEWLINK, link_handler, NULL) == 1); - assert_se(sd_netlink_remove_match(rtnl, RTM_NEWLINK, link_handler, NULL) == 0); - - assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); -} - -static void test_get_addresses(sd_netlink *rtnl) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; - sd_netlink_message *m; - - assert_se(sd_rtnl_message_new_addr(rtnl, &req, RTM_GETADDR, 0, AF_UNSPEC) >= 0); - - assert_se(sd_netlink_call(rtnl, req, 0, &reply) >= 0); - - for (m = reply; m; m = sd_netlink_message_next(m)) { - uint16_t type; - unsigned char scope, flags; - int family, ifindex; - - assert_se(sd_netlink_message_get_type(m, &type) >= 0); - assert_se(type == RTM_NEWADDR); - - assert_se(sd_rtnl_message_addr_get_ifindex(m, &ifindex) >= 0); - assert_se(sd_rtnl_message_addr_get_family(m, &family) >= 0); - assert_se(sd_rtnl_message_addr_get_scope(m, &scope) >= 0); - assert_se(sd_rtnl_message_addr_get_flags(m, &flags) >= 0); - - assert_se(ifindex > 0); - assert_se(family == AF_INET || family == AF_INET6); - - log_info("got IPv%u address on ifindex %i", family == AF_INET ? 4: 6, ifindex); - } -} - -static void test_message(void) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - - assert_se(rtnl_message_new_synthetic_error(-ETIMEDOUT, 1, &m) >= 0); - assert_se(sd_netlink_message_get_errno(m) == -ETIMEDOUT); -} - -int main(void) { - sd_netlink *rtnl; - sd_netlink_message *m; - sd_netlink_message *r; - const char *string_data; - int if_loopback; - uint16_t type; - - test_message(); - - test_match(); - - test_multiple(); - - test_route(); - - test_container(); - - assert_se(sd_netlink_open(&rtnl) >= 0); - assert_se(rtnl); - - if_loopback = (int) if_nametoindex("lo"); - assert_se(if_loopback > 0); - - test_async(if_loopback); - - test_pipe(if_loopback); - - test_event_loop(if_loopback); - - test_link_configure(rtnl, if_loopback); - - test_get_addresses(rtnl); - - test_message_link_bridge(rtnl); - - assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, if_loopback) >= 0); - assert_se(m); - - assert_se(sd_netlink_message_get_type(m, &type) >= 0); - assert_se(type == RTM_GETLINK); - - assert_se(sd_netlink_message_read_string(m, IFLA_IFNAME, &string_data) == -EPERM); - - assert_se(sd_netlink_call(rtnl, m, 0, &r) == 1); - assert_se(sd_netlink_message_get_type(r, &type) >= 0); - assert_se(type == RTM_NEWLINK); - - assert_se((r = sd_netlink_message_unref(r)) == NULL); - - assert_se(sd_netlink_call(rtnl, m, -1, &r) == -EPERM); - assert_se((m = sd_netlink_message_unref(m)) == NULL); - assert_se((r = sd_netlink_message_unref(r)) == NULL); - - test_link_get(rtnl, if_loopback); - test_address_get(rtnl, if_loopback); - - assert_se((m = sd_netlink_message_unref(m)) == NULL); - assert_se((r = sd_netlink_message_unref(r)) == NULL); - assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); - - return EXIT_SUCCESS; -} diff --git a/src/libsystemd/sd-network/Makefile b/src/libsystemd/sd-network/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/libsystemd/sd-network/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-network/network-util.c b/src/libsystemd/sd-network/network-util.c deleted file mode 100644 index a0d9b5f1a4..0000000000 --- a/src/libsystemd/sd-network/network-util.c +++ /dev/null @@ -1,37 +0,0 @@ -/*** - 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 "alloc-util.h" -#include "fd-util.h" -#include "network-util.h" -#include "strv.h" - -bool network_is_online(void) { - _cleanup_free_ char *state = NULL; - int r; - - r = sd_network_get_operational_state(&state); - if (r < 0) /* if we don't know anything, we consider the system online */ - return true; - - if (STR_IN_SET(state, "routable", "degraded")) - return true; - - return false; -} diff --git a/src/libsystemd/sd-network/network-util.h b/src/libsystemd/sd-network/network-util.h deleted file mode 100644 index 26780dce28..0000000000 --- a/src/libsystemd/sd-network/network-util.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Thomas Hindø Paabøl Andersen - - 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 "sd-network.h" - -bool network_is_online(void); diff --git a/src/libsystemd/sd-network/sd-network.c b/src/libsystemd/sd-network/sd-network.c deleted file mode 100644 index f8e18f23fd..0000000000 --- a/src/libsystemd/sd-network/sd-network.c +++ /dev/null @@ -1,400 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 Lennart Poettering - Copyright 2014 Tom Gundersen - - 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 -#include -#include - -#include "sd-network.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "macro.h" -#include "parse-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -_public_ int sd_network_get_operational_state(char **state) { - _cleanup_free_ char *s = NULL; - int r; - - assert_return(state, -EINVAL); - - r = parse_env_file("/run/systemd/netif/state", NEWLINE, "OPER_STATE", &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; - - *state = s; - s = NULL; - - return 0; -} - -static int network_get_strv(const char *key, char ***ret) { - _cleanup_strv_free_ char **a = NULL; - _cleanup_free_ char *s = NULL; - int r; - - assert_return(ret, -EINVAL); - - r = parse_env_file("/run/systemd/netif/state", NEWLINE, key, &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) { - *ret = NULL; - return 0; - } - - a = strv_split(s, " "); - if (!a) - return -ENOMEM; - - strv_uniq(a); - r = strv_length(a); - - *ret = a; - a = NULL; - - return r; -} - -_public_ int sd_network_get_dns(char ***ret) { - return network_get_strv("DNS", ret); -} - -_public_ int sd_network_get_ntp(char ***ret) { - return network_get_strv("NTP", ret); -} - -_public_ int sd_network_get_search_domains(char ***ret) { - return network_get_strv("DOMAINS", ret); -} - -_public_ int sd_network_get_route_domains(char ***ret) { - return network_get_strv("ROUTE_DOMAINS", ret); -} - -static int network_link_get_string(int ifindex, const char *field, char **ret) { - char path[strlen("/run/systemd/netif/links/") + DECIMAL_STR_MAX(ifindex) + 1]; - _cleanup_free_ char *s = NULL; - int r; - - assert_return(ifindex > 0, -EINVAL); - assert_return(ret, -EINVAL); - - xsprintf(path, "/run/systemd/netif/links/%i", ifindex); - - r = parse_env_file(path, NEWLINE, field, &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; - - *ret = s; - s = NULL; - - return 0; -} - -static int network_link_get_strv(int ifindex, const char *key, char ***ret) { - char path[strlen("/run/systemd/netif/links/") + DECIMAL_STR_MAX(ifindex) + 1]; - _cleanup_strv_free_ char **a = NULL; - _cleanup_free_ char *s = NULL; - int r; - - assert_return(ifindex > 0, -EINVAL); - assert_return(ret, -EINVAL); - - xsprintf(path, "/run/systemd/netif/links/%i", ifindex); - r = parse_env_file(path, NEWLINE, key, &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) { - *ret = NULL; - return 0; - } - - a = strv_split(s, " "); - if (!a) - return -ENOMEM; - - strv_uniq(a); - r = strv_length(a); - - *ret = a; - a = NULL; - - return r; -} - -_public_ int sd_network_link_get_setup_state(int ifindex, char **state) { - return network_link_get_string(ifindex, "ADMIN_STATE", state); -} - -_public_ int sd_network_link_get_network_file(int ifindex, char **filename) { - return network_link_get_string(ifindex, "NETWORK_FILE", filename); -} - -_public_ int sd_network_link_get_operational_state(int ifindex, char **state) { - return network_link_get_string(ifindex, "OPER_STATE", state); -} - -_public_ int sd_network_link_get_llmnr(int ifindex, char **llmnr) { - return network_link_get_string(ifindex, "LLMNR", llmnr); -} - -_public_ int sd_network_link_get_mdns(int ifindex, char **mdns) { - return network_link_get_string(ifindex, "MDNS", mdns); -} - -_public_ int sd_network_link_get_dnssec(int ifindex, char **dnssec) { - return network_link_get_string(ifindex, "DNSSEC", dnssec); -} - -_public_ int sd_network_link_get_dnssec_negative_trust_anchors(int ifindex, char ***nta) { - return network_link_get_strv(ifindex, "DNSSEC_NTA", nta); -} - -_public_ int sd_network_link_get_timezone(int ifindex, char **ret) { - return network_link_get_string(ifindex, "TIMEZONE", ret); -} - -_public_ int sd_network_link_get_dns(int ifindex, char ***ret) { - return network_link_get_strv(ifindex, "DNS", ret); -} - -_public_ int sd_network_link_get_ntp(int ifindex, char ***ret) { - return network_link_get_strv(ifindex, "NTP", ret); -} - -_public_ int sd_network_link_get_search_domains(int ifindex, char ***ret) { - return network_link_get_strv(ifindex, "DOMAINS", ret); -} - -_public_ int sd_network_link_get_route_domains(int ifindex, char ***ret) { - return network_link_get_strv(ifindex, "ROUTE_DOMAINS", ret); -} - -static int network_link_get_ifindexes(int ifindex, const char *key, int **ret) { - char path[strlen("/run/systemd/netif/links/") + DECIMAL_STR_MAX(ifindex) + 1]; - _cleanup_free_ int *ifis = NULL; - _cleanup_free_ char *s = NULL; - size_t allocated = 0, c = 0; - const char *x; - int r; - - assert_return(ifindex > 0, -EINVAL); - assert_return(ret, -EINVAL); - - xsprintf(path, "/run/systemd/netif/links/%i", ifindex); - r = parse_env_file(path, NEWLINE, key, &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) { - *ret = NULL; - return 0; - } - - x = s; - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&x, &word, NULL, 0); - if (r < 0) - return r; - if (r == 0) - break; - - r = parse_ifindex(word, &ifindex); - if (r < 0) - return r; - - if (!GREEDY_REALLOC(ifis, allocated, c + 1)) - return -ENOMEM; - - ifis[c++] = ifindex; - } - - if (!GREEDY_REALLOC(ifis, allocated, c + 1)) - return -ENOMEM; - ifis[c] = 0; /* Let's add a 0 ifindex to the end, to be nice*/ - - *ret = ifis; - ifis = NULL; - - return c; -} - -_public_ int sd_network_link_get_carrier_bound_to(int ifindex, int **ret) { - return network_link_get_ifindexes(ifindex, "CARRIER_BOUND_TO", ret); -} - -_public_ int sd_network_link_get_carrier_bound_by(int ifindex, int **ret) { - return network_link_get_ifindexes(ifindex, "CARRIER_BOUND_BY", ret); -} - -static inline int MONITOR_TO_FD(sd_network_monitor *m) { - return (int) (unsigned long) m - 1; -} - -static inline sd_network_monitor* FD_TO_MONITOR(int fd) { - return (sd_network_monitor*) (unsigned long) (fd + 1); -} - -static int monitor_add_inotify_watch(int fd) { - int k; - - k = inotify_add_watch(fd, "/run/systemd/netif/links/", IN_MOVED_TO|IN_DELETE); - if (k >= 0) - return 0; - else if (errno != ENOENT) - return -errno; - - k = inotify_add_watch(fd, "/run/systemd/netif/", IN_CREATE|IN_ISDIR); - if (k >= 0) - return 0; - else if (errno != ENOENT) - return -errno; - - k = inotify_add_watch(fd, "/run/systemd/", IN_CREATE|IN_ISDIR); - if (k < 0) - return -errno; - - return 0; -} - -_public_ int sd_network_monitor_new(sd_network_monitor **m, const char *category) { - _cleanup_close_ int fd = -1; - int k; - bool good = false; - - assert_return(m, -EINVAL); - - fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); - if (fd < 0) - return -errno; - - if (!category || streq(category, "links")) { - k = monitor_add_inotify_watch(fd); - if (k < 0) - return k; - - good = true; - } - - if (!good) - return -EINVAL; - - *m = FD_TO_MONITOR(fd); - fd = -1; - - return 0; -} - -_public_ sd_network_monitor* sd_network_monitor_unref(sd_network_monitor *m) { - int fd; - - if (m) { - fd = MONITOR_TO_FD(m); - close_nointr(fd); - } - - return NULL; -} - -_public_ int sd_network_monitor_flush(sd_network_monitor *m) { - union inotify_event_buffer buffer; - struct inotify_event *e; - ssize_t l; - int fd, k; - - assert_return(m, -EINVAL); - - fd = MONITOR_TO_FD(m); - - l = read(fd, &buffer, sizeof(buffer)); - if (l < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return -errno; - } - - FOREACH_INOTIFY_EVENT(e, buffer, l) { - if (e->mask & IN_ISDIR) { - k = monitor_add_inotify_watch(fd); - if (k < 0) - return k; - - k = inotify_rm_watch(fd, e->wd); - if (k < 0) - return -errno; - } - } - - return 0; -} - -_public_ int sd_network_monitor_get_fd(sd_network_monitor *m) { - - assert_return(m, -EINVAL); - - return MONITOR_TO_FD(m); -} - -_public_ int sd_network_monitor_get_events(sd_network_monitor *m) { - - assert_return(m, -EINVAL); - - /* For now we will only return POLLIN here, since we don't - * need anything else ever for inotify. However, let's have - * this API to keep our options open should we later on need - * it. */ - return POLLIN; -} - -_public_ int sd_network_monitor_get_timeout(sd_network_monitor *m, uint64_t *timeout_usec) { - - assert_return(m, -EINVAL); - assert_return(timeout_usec, -EINVAL); - - /* For now we will only return (uint64_t) -1, since we don't - * need any timeout. However, let's have this API to keep our - * options open should we later on need it. */ - *timeout_usec = (uint64_t) -1; - return 0; -} diff --git a/src/libsystemd/sd-path/Makefile b/src/libsystemd/sd-path/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/libsystemd/sd-path/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-path/sd-path.c b/src/libsystemd/sd-path/sd-path.c deleted file mode 100644 index b7aec1f20a..0000000000 --- a/src/libsystemd/sd-path/sd-path.c +++ /dev/null @@ -1,638 +0,0 @@ -/*** - 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 "sd-path.h" - -#include "alloc-util.h" -#include "architecture.h" -#include "fd-util.h" -#include "fileio.h" -#include "missing.h" -#include "path-util.h" -#include "string-util.h" -#include "strv.h" -#include "user-util.h" -#include "util.h" - -static int from_environment(const char *envname, const char *fallback, const char **ret) { - assert(ret); - - if (envname) { - const char *e; - - e = secure_getenv(envname); - if (e && path_is_absolute(e)) { - *ret = e; - return 0; - } - } - - if (fallback) { - *ret = fallback; - return 0; - } - - return -ENXIO; -} - -static int from_home_dir(const char *envname, const char *suffix, char **buffer, const char **ret) { - _cleanup_free_ char *h = NULL; - char *cc = NULL; - int r; - - assert(suffix); - assert(buffer); - assert(ret); - - if (envname) { - const char *e = NULL; - - e = secure_getenv(envname); - if (e && path_is_absolute(e)) { - *ret = e; - return 0; - } - } - - r = get_home_dir(&h); - if (r < 0) - return r; - - if (endswith(h, "/")) - cc = strappend(h, suffix); - else - cc = strjoin(h, "/", suffix, NULL); - if (!cc) - return -ENOMEM; - - *buffer = cc; - *ret = cc; - return 0; -} - -static int from_user_dir(const char *field, char **buffer, const char **ret) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *b = NULL; - _cleanup_free_ const char *fn = NULL; - const char *c = NULL; - char line[LINE_MAX]; - size_t n; - int r; - - assert(field); - assert(buffer); - assert(ret); - - r = from_home_dir("XDG_CONFIG_HOME", ".config", &b, &c); - if (r < 0) - return r; - - fn = strappend(c, "/user-dirs.dirs"); - if (!fn) - return -ENOMEM; - - f = fopen(fn, "re"); - if (!f) { - if (errno == ENOENT) - goto fallback; - - return -errno; - } - - /* This is an awful parse, but it follows closely what - * xdg-user-dirs does upstream */ - - n = strlen(field); - FOREACH_LINE(line, f, return -errno) { - char *l, *p, *e; - - l = strstrip(line); - - if (!strneq(l, field, n)) - continue; - - p = l + n; - p += strspn(p, WHITESPACE); - - if (*p != '=') - continue; - p++; - - p += strspn(p, WHITESPACE); - - if (*p != '"') - continue; - p++; - - e = strrchr(p, '"'); - if (!e) - continue; - *e = 0; - - /* Three syntaxes permitted: relative to $HOME, $HOME itself, and absolute path */ - if (startswith(p, "$HOME/")) { - _cleanup_free_ char *h = NULL; - char *cc; - - r = get_home_dir(&h); - if (r < 0) - return r; - - cc = strappend(h, p+5); - if (!cc) - return -ENOMEM; - - *buffer = cc; - *ret = cc; - return 0; - } else if (streq(p, "$HOME")) { - - r = get_home_dir(buffer); - if (r < 0) - return r; - - *ret = *buffer; - return 0; - } else if (path_is_absolute(p)) { - char *copy; - - copy = strdup(p); - if (!copy) - return -ENOMEM; - - *buffer = copy; - *ret = copy; - return 0; - } - } - -fallback: - /* The desktop directory defaults to $HOME/Desktop, the others to $HOME */ - if (streq(field, "XDG_DESKTOP_DIR")) { - _cleanup_free_ char *h = NULL; - char *cc; - - r = get_home_dir(&h); - if (r < 0) - return r; - - cc = strappend(h, "/Desktop"); - if (!cc) - return -ENOMEM; - - *buffer = cc; - *ret = cc; - } else { - - r = get_home_dir(buffer); - if (r < 0) - return r; - - *ret = *buffer; - } - - return 0; -} - -static int get_path(uint64_t type, char **buffer, const char **ret) { - int r; - - assert(buffer); - assert(ret); - - switch (type) { - - case SD_PATH_TEMPORARY: - return from_environment("TMPDIR", "/tmp", ret); - - case SD_PATH_TEMPORARY_LARGE: - return from_environment("TMPDIR", "/var/tmp", ret); - - case SD_PATH_SYSTEM_BINARIES: - *ret = "/usr/bin"; - return 0; - - case SD_PATH_SYSTEM_INCLUDE: - *ret = "/usr/include"; - return 0; - - case SD_PATH_SYSTEM_LIBRARY_PRIVATE: - *ret = "/usr/lib"; - return 0; - - case SD_PATH_SYSTEM_LIBRARY_ARCH: - *ret = LIBDIR; - return 0; - - case SD_PATH_SYSTEM_SHARED: - *ret = "/usr/share"; - return 0; - - case SD_PATH_SYSTEM_CONFIGURATION_FACTORY: - *ret = "/usr/share/factory/etc"; - return 0; - - case SD_PATH_SYSTEM_STATE_FACTORY: - *ret = "/usr/share/factory/var"; - return 0; - - case SD_PATH_SYSTEM_CONFIGURATION: - *ret = "/etc"; - return 0; - - case SD_PATH_SYSTEM_RUNTIME: - *ret = "/run"; - return 0; - - case SD_PATH_SYSTEM_RUNTIME_LOGS: - *ret = "/run/log"; - return 0; - - case SD_PATH_SYSTEM_STATE_PRIVATE: - *ret = "/var/lib"; - return 0; - - case SD_PATH_SYSTEM_STATE_LOGS: - *ret = "/var/log"; - return 0; - - case SD_PATH_SYSTEM_STATE_CACHE: - *ret = "/var/cache"; - return 0; - - case SD_PATH_SYSTEM_STATE_SPOOL: - *ret = "/var/spool"; - return 0; - - case SD_PATH_USER_BINARIES: - return from_home_dir(NULL, ".local/bin", buffer, ret); - - case SD_PATH_USER_LIBRARY_PRIVATE: - return from_home_dir(NULL, ".local/lib", buffer, ret); - - case SD_PATH_USER_LIBRARY_ARCH: - return from_home_dir(NULL, ".local/lib/" LIB_ARCH_TUPLE, buffer, ret); - - case SD_PATH_USER_SHARED: - return from_home_dir("XDG_DATA_HOME", ".local/share", buffer, ret); - - case SD_PATH_USER_CONFIGURATION: - return from_home_dir("XDG_CONFIG_HOME", ".config", buffer, ret); - - case SD_PATH_USER_RUNTIME: - return from_environment("XDG_RUNTIME_DIR", NULL, ret); - - case SD_PATH_USER_STATE_CACHE: - return from_home_dir("XDG_CACHE_HOME", ".cache", buffer, ret); - - case SD_PATH_USER: - r = get_home_dir(buffer); - if (r < 0) - return r; - - *ret = *buffer; - return 0; - - case SD_PATH_USER_DOCUMENTS: - return from_user_dir("XDG_DOCUMENTS_DIR", buffer, ret); - - case SD_PATH_USER_MUSIC: - return from_user_dir("XDG_MUSIC_DIR", buffer, ret); - - case SD_PATH_USER_PICTURES: - return from_user_dir("XDG_PICTURES_DIR", buffer, ret); - - case SD_PATH_USER_VIDEOS: - return from_user_dir("XDG_VIDEOS_DIR", buffer, ret); - - case SD_PATH_USER_DOWNLOAD: - return from_user_dir("XDG_DOWNLOAD_DIR", buffer, ret); - - case SD_PATH_USER_PUBLIC: - return from_user_dir("XDG_PUBLICSHARE_DIR", buffer, ret); - - case SD_PATH_USER_TEMPLATES: - return from_user_dir("XDG_TEMPLATES_DIR", buffer, ret); - - case SD_PATH_USER_DESKTOP: - return from_user_dir("XDG_DESKTOP_DIR", buffer, ret); - } - - return -EOPNOTSUPP; -} - -_public_ int sd_path_home(uint64_t type, const char *suffix, char **path) { - char *buffer = NULL, *cc; - const char *ret; - int r; - - assert_return(path, -EINVAL); - - if (IN_SET(type, - SD_PATH_SEARCH_BINARIES, - SD_PATH_SEARCH_LIBRARY_PRIVATE, - SD_PATH_SEARCH_LIBRARY_ARCH, - SD_PATH_SEARCH_SHARED, - SD_PATH_SEARCH_CONFIGURATION_FACTORY, - SD_PATH_SEARCH_STATE_FACTORY, - SD_PATH_SEARCH_CONFIGURATION)) { - - _cleanup_strv_free_ char **l = NULL; - - r = sd_path_search(type, suffix, &l); - if (r < 0) - return r; - - buffer = strv_join(l, ":"); - if (!buffer) - return -ENOMEM; - - *path = buffer; - return 0; - } - - r = get_path(type, &buffer, &ret); - if (r < 0) - return r; - - if (!suffix) { - if (!buffer) { - buffer = strdup(ret); - if (!buffer) - return -ENOMEM; - } - - *path = buffer; - return 0; - } - - suffix += strspn(suffix, "/"); - - if (endswith(ret, "/")) - cc = strappend(ret, suffix); - else - cc = strjoin(ret, "/", suffix, NULL); - - free(buffer); - - if (!cc) - return -ENOMEM; - - *path = cc; - return 0; -} - -static int search_from_environment( - char ***list, - const char *env_home, - const char *home_suffix, - const char *env_search, - bool env_search_sufficient, - const char *first, ...) { - - const char *e; - char *h = NULL; - char **l = NULL; - int r; - - assert(list); - - if (env_search) { - e = secure_getenv(env_search); - if (e) { - l = strv_split(e, ":"); - if (!l) - return -ENOMEM; - - if (env_search_sufficient) { - *list = l; - return 0; - } - } - } - - if (!l && first) { - va_list ap; - - va_start(ap, first); - l = strv_new_ap(first, ap); - va_end(ap); - - if (!l) - return -ENOMEM; - } - - if (env_home) { - e = secure_getenv(env_home); - if (e && path_is_absolute(e)) { - h = strdup(e); - if (!h) { - strv_free(l); - return -ENOMEM; - } - } - } - - if (!h && home_suffix) { - e = secure_getenv("HOME"); - if (e && path_is_absolute(e)) { - if (endswith(e, "/")) - h = strappend(e, home_suffix); - else - h = strjoin(e, "/", home_suffix, NULL); - - if (!h) { - strv_free(l); - return -ENOMEM; - } - } - } - - if (h) { - r = strv_consume_prepend(&l, h); - if (r < 0) { - strv_free(l); - return -ENOMEM; - } - } - - *list = l; - return 0; -} - -static int get_search(uint64_t type, char ***list) { - - assert(list); - - switch(type) { - - case SD_PATH_SEARCH_BINARIES: - return search_from_environment(list, - NULL, - ".local/bin", - "PATH", - true, - "/usr/local/sbin", - "/usr/local/bin", - "/usr/sbin", - "/usr/bin", -#ifdef HAVE_SPLIT_USR - "/sbin", - "/bin", -#endif - NULL); - - case SD_PATH_SEARCH_LIBRARY_PRIVATE: - return search_from_environment(list, - NULL, - ".local/lib", - NULL, - false, - "/usr/local/lib", - "/usr/lib", -#ifdef HAVE_SPLIT_USR - "/lib", -#endif - NULL); - - case SD_PATH_SEARCH_LIBRARY_ARCH: - return search_from_environment(list, - NULL, - ".local/lib/" LIB_ARCH_TUPLE, - "LD_LIBRARY_PATH", - true, - LIBDIR, -#ifdef HAVE_SPLIT_USR - ROOTLIBDIR, -#endif - NULL); - - case SD_PATH_SEARCH_SHARED: - return search_from_environment(list, - "XDG_DATA_HOME", - ".local/share", - "XDG_DATA_DIRS", - false, - "/usr/local/share", - "/usr/share", - NULL); - - case SD_PATH_SEARCH_CONFIGURATION_FACTORY: - return search_from_environment(list, - NULL, - NULL, - NULL, - false, - "/usr/local/share/factory/etc", - "/usr/share/factory/etc", - NULL); - - case SD_PATH_SEARCH_STATE_FACTORY: - return search_from_environment(list, - NULL, - NULL, - NULL, - false, - "/usr/local/share/factory/var", - "/usr/share/factory/var", - NULL); - - case SD_PATH_SEARCH_CONFIGURATION: - return search_from_environment(list, - "XDG_CONFIG_HOME", - ".config", - "XDG_CONFIG_DIRS", - false, - "/etc", - NULL); - } - - return -EOPNOTSUPP; -} - -_public_ int sd_path_search(uint64_t type, const char *suffix, char ***paths) { - char **l, **i, **j, **n; - int r; - - assert_return(paths, -EINVAL); - - if (!IN_SET(type, - SD_PATH_SEARCH_BINARIES, - SD_PATH_SEARCH_LIBRARY_PRIVATE, - SD_PATH_SEARCH_LIBRARY_ARCH, - SD_PATH_SEARCH_SHARED, - SD_PATH_SEARCH_CONFIGURATION_FACTORY, - SD_PATH_SEARCH_STATE_FACTORY, - SD_PATH_SEARCH_CONFIGURATION)) { - - char *p; - - r = sd_path_home(type, suffix, &p); - if (r < 0) - return r; - - l = new(char*, 2); - if (!l) { - free(p); - return -ENOMEM; - } - - l[0] = p; - l[1] = NULL; - - *paths = l; - return 0; - } - - r = get_search(type, &l); - if (r < 0) - return r; - - if (!suffix) { - *paths = l; - return 0; - } - - n = new(char*, strv_length(l)+1); - if (!n) { - strv_free(l); - return -ENOMEM; - } - - j = n; - STRV_FOREACH(i, l) { - - if (endswith(*i, "/")) - *j = strappend(*i, suffix); - else - *j = strjoin(*i, "/", suffix, NULL); - - if (!*j) { - strv_free(l); - strv_free(n); - return -ENOMEM; - } - - j++; - } - - *j = NULL; - *paths = n; - return 0; -} diff --git a/src/libsystemd/sd-resolve/Makefile b/src/libsystemd/sd-resolve/Makefile deleted file mode 120000 index 94aaae2c4d..0000000000 --- a/src/libsystemd/sd-resolve/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-resolve/sd-resolve.c b/src/libsystemd/sd-resolve/sd-resolve.c deleted file mode 100644 index d8303e2e69..0000000000 --- a/src/libsystemd/sd-resolve/sd-resolve.c +++ /dev/null @@ -1,1245 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2005-2008 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-resolve.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "io-util.h" -#include "list.h" -#include "missing.h" -#include "socket-util.h" -#include "util.h" - -#define WORKERS_MIN 1U -#define WORKERS_MAX 16U -#define QUERIES_MAX 256U -#define BUFSIZE 10240U - -typedef enum { - REQUEST_ADDRINFO, - RESPONSE_ADDRINFO, - REQUEST_NAMEINFO, - RESPONSE_NAMEINFO, - REQUEST_TERMINATE, - RESPONSE_DIED -} QueryType; - -enum { - REQUEST_RECV_FD, - REQUEST_SEND_FD, - RESPONSE_RECV_FD, - RESPONSE_SEND_FD, - _FD_MAX -}; - -struct sd_resolve { - unsigned n_ref; - - bool dead:1; - pid_t original_pid; - - int fds[_FD_MAX]; - - pthread_t workers[WORKERS_MAX]; - unsigned n_valid_workers; - - unsigned current_id; - sd_resolve_query* query_array[QUERIES_MAX]; - unsigned n_queries, n_done, n_outstanding; - - sd_event_source *event_source; - sd_event *event; - - sd_resolve_query *current; - - sd_resolve **default_resolve_ptr; - pid_t tid; - - LIST_HEAD(sd_resolve_query, queries); -}; - -struct sd_resolve_query { - unsigned n_ref; - - sd_resolve *resolve; - - QueryType type:4; - bool done:1; - bool floating:1; - unsigned id; - - int ret; - int _errno; - int _h_errno; - struct addrinfo *addrinfo; - char *serv, *host; - - union { - sd_resolve_getaddrinfo_handler_t getaddrinfo_handler; - sd_resolve_getnameinfo_handler_t getnameinfo_handler; - }; - - void *userdata; - - LIST_FIELDS(sd_resolve_query, queries); -}; - -typedef struct RHeader { - QueryType type; - unsigned id; - size_t length; -} RHeader; - -typedef struct AddrInfoRequest { - struct RHeader header; - bool hints_valid; - int ai_flags; - int ai_family; - int ai_socktype; - int ai_protocol; - size_t node_len, service_len; -} AddrInfoRequest; - -typedef struct AddrInfoResponse { - struct RHeader header; - int ret; - int _errno; - int _h_errno; - /* followed by addrinfo_serialization[] */ -} AddrInfoResponse; - -typedef struct AddrInfoSerialization { - int ai_flags; - int ai_family; - int ai_socktype; - int ai_protocol; - size_t ai_addrlen; - size_t canonname_len; - /* Followed by ai_addr amd ai_canonname with variable lengths */ -} AddrInfoSerialization; - -typedef struct NameInfoRequest { - struct RHeader header; - int flags; - socklen_t sockaddr_len; - bool gethost:1, getserv:1; -} NameInfoRequest; - -typedef struct NameInfoResponse { - struct RHeader header; - size_t hostlen, servlen; - int ret; - int _errno; - int _h_errno; -} NameInfoResponse; - -typedef union Packet { - RHeader rheader; - AddrInfoRequest addrinfo_request; - AddrInfoResponse addrinfo_response; - NameInfoRequest nameinfo_request; - NameInfoResponse nameinfo_response; -} Packet; - -static int getaddrinfo_done(sd_resolve_query* q); -static int getnameinfo_done(sd_resolve_query *q); - -static void resolve_query_disconnect(sd_resolve_query *q); - -#define RESOLVE_DONT_DESTROY(resolve) \ - _cleanup_(sd_resolve_unrefp) _unused_ sd_resolve *_dont_destroy_##resolve = sd_resolve_ref(resolve) - -static int send_died(int out_fd) { - - RHeader rh = { - .type = RESPONSE_DIED, - .length = sizeof(RHeader), - }; - - assert(out_fd >= 0); - - if (send(out_fd, &rh, rh.length, MSG_NOSIGNAL) < 0) - return -errno; - - return 0; -} - -static void *serialize_addrinfo(void *p, const struct addrinfo *ai, size_t *length, size_t maxlength) { - AddrInfoSerialization s; - size_t cnl, l; - - assert(p); - assert(ai); - assert(length); - assert(*length <= maxlength); - - cnl = ai->ai_canonname ? strlen(ai->ai_canonname)+1 : 0; - l = sizeof(AddrInfoSerialization) + ai->ai_addrlen + cnl; - - if (*length + l > maxlength) - return NULL; - - s.ai_flags = ai->ai_flags; - s.ai_family = ai->ai_family; - s.ai_socktype = ai->ai_socktype; - s.ai_protocol = ai->ai_protocol; - s.ai_addrlen = ai->ai_addrlen; - s.canonname_len = cnl; - - memcpy((uint8_t*) p, &s, sizeof(AddrInfoSerialization)); - memcpy((uint8_t*) p + sizeof(AddrInfoSerialization), ai->ai_addr, ai->ai_addrlen); - memcpy_safe((char*) p + sizeof(AddrInfoSerialization) + ai->ai_addrlen, - ai->ai_canonname, cnl); - - *length += l; - return (uint8_t*) p + l; -} - -static int send_addrinfo_reply( - int out_fd, - unsigned id, - int ret, - struct addrinfo *ai, - int _errno, - int _h_errno) { - - AddrInfoResponse resp = { - .header.type = RESPONSE_ADDRINFO, - .header.id = id, - .header.length = sizeof(AddrInfoResponse), - .ret = ret, - ._errno = _errno, - ._h_errno = _h_errno, - }; - - struct msghdr mh = {}; - struct iovec iov[2]; - union { - AddrInfoSerialization ais; - uint8_t space[BUFSIZE]; - } buffer; - - assert(out_fd >= 0); - - if (ret == 0 && ai) { - void *p = &buffer; - struct addrinfo *k; - - for (k = ai; k; k = k->ai_next) { - p = serialize_addrinfo(p, k, &resp.header.length, (uint8_t*) &buffer + BUFSIZE - (uint8_t*) p); - if (!p) { - freeaddrinfo(ai); - return -ENOBUFS; - } - } - } - - if (ai) - freeaddrinfo(ai); - - iov[0] = (struct iovec) { .iov_base = &resp, .iov_len = sizeof(AddrInfoResponse) }; - iov[1] = (struct iovec) { .iov_base = &buffer, .iov_len = resp.header.length - sizeof(AddrInfoResponse) }; - - mh.msg_iov = iov; - mh.msg_iovlen = ELEMENTSOF(iov); - - if (sendmsg(out_fd, &mh, MSG_NOSIGNAL) < 0) - return -errno; - - return 0; -} - -static int send_nameinfo_reply( - int out_fd, - unsigned id, - int ret, - const char *host, - const char *serv, - int _errno, - int _h_errno) { - - NameInfoResponse resp = { - .header.type = RESPONSE_NAMEINFO, - .header.id = id, - .ret = ret, - ._errno = _errno, - ._h_errno = _h_errno, - }; - - struct msghdr mh = {}; - struct iovec iov[3]; - size_t hl, sl; - - assert(out_fd >= 0); - - sl = serv ? strlen(serv)+1 : 0; - hl = host ? strlen(host)+1 : 0; - - resp.header.length = sizeof(NameInfoResponse) + hl + sl; - resp.hostlen = hl; - resp.servlen = sl; - - iov[0] = (struct iovec) { .iov_base = &resp, .iov_len = sizeof(NameInfoResponse) }; - iov[1] = (struct iovec) { .iov_base = (void*) host, .iov_len = hl }; - iov[2] = (struct iovec) { .iov_base = (void*) serv, .iov_len = sl }; - - mh.msg_iov = iov; - mh.msg_iovlen = ELEMENTSOF(iov); - - if (sendmsg(out_fd, &mh, MSG_NOSIGNAL) < 0) - return -errno; - - return 0; -} - -static int handle_request(int out_fd, const Packet *packet, size_t length) { - const RHeader *req; - - assert(out_fd >= 0); - assert(packet); - - req = &packet->rheader; - - assert(length >= sizeof(RHeader)); - assert(length == req->length); - - switch (req->type) { - - case REQUEST_ADDRINFO: { - const AddrInfoRequest *ai_req = &packet->addrinfo_request; - struct addrinfo hints = {}, *result = NULL; - const char *node, *service; - int ret; - - assert(length >= sizeof(AddrInfoRequest)); - assert(length == sizeof(AddrInfoRequest) + ai_req->node_len + ai_req->service_len); - - hints.ai_flags = ai_req->ai_flags; - hints.ai_family = ai_req->ai_family; - hints.ai_socktype = ai_req->ai_socktype; - hints.ai_protocol = ai_req->ai_protocol; - - node = ai_req->node_len ? (const char*) ai_req + sizeof(AddrInfoRequest) : NULL; - service = ai_req->service_len ? (const char*) ai_req + sizeof(AddrInfoRequest) + ai_req->node_len : NULL; - - ret = getaddrinfo( - node, service, - ai_req->hints_valid ? &hints : NULL, - &result); - - /* send_addrinfo_reply() frees result */ - return send_addrinfo_reply(out_fd, req->id, ret, result, errno, h_errno); - } - - case REQUEST_NAMEINFO: { - const NameInfoRequest *ni_req = &packet->nameinfo_request; - char hostbuf[NI_MAXHOST], servbuf[NI_MAXSERV]; - union sockaddr_union sa; - int ret; - - assert(length >= sizeof(NameInfoRequest)); - assert(length == sizeof(NameInfoRequest) + ni_req->sockaddr_len); - assert(sizeof(sa) >= ni_req->sockaddr_len); - - memcpy(&sa, (const uint8_t *) ni_req + sizeof(NameInfoRequest), ni_req->sockaddr_len); - - ret = getnameinfo(&sa.sa, ni_req->sockaddr_len, - ni_req->gethost ? hostbuf : NULL, ni_req->gethost ? sizeof(hostbuf) : 0, - ni_req->getserv ? servbuf : NULL, ni_req->getserv ? sizeof(servbuf) : 0, - ni_req->flags); - - return send_nameinfo_reply(out_fd, req->id, ret, - ret == 0 && ni_req->gethost ? hostbuf : NULL, - ret == 0 && ni_req->getserv ? servbuf : NULL, - errno, h_errno); - } - - case REQUEST_TERMINATE: - /* Quit */ - return -ECONNRESET; - - default: - assert_not_reached("Unknown request"); - } - - return 0; -} - -static void* thread_worker(void *p) { - sd_resolve *resolve = p; - sigset_t fullset; - - /* No signals in this thread please */ - assert_se(sigfillset(&fullset) == 0); - assert_se(pthread_sigmask(SIG_BLOCK, &fullset, NULL) == 0); - - /* Assign a pretty name to this thread */ - (void) prctl(PR_SET_NAME, (unsigned long) "sd-resolve"); - - while (!resolve->dead) { - union { - Packet packet; - uint8_t space[BUFSIZE]; - } buf; - ssize_t length; - - length = recv(resolve->fds[REQUEST_RECV_FD], &buf, sizeof(buf), 0); - if (length < 0) { - if (errno == EINTR) - continue; - - break; - } - if (length == 0) - break; - - if (resolve->dead) - break; - - if (handle_request(resolve->fds[RESPONSE_SEND_FD], &buf.packet, (size_t) length) < 0) - break; - } - - send_died(resolve->fds[RESPONSE_SEND_FD]); - - return NULL; -} - -static int start_threads(sd_resolve *resolve, unsigned extra) { - unsigned n; - int r; - - n = resolve->n_outstanding + extra; - n = CLAMP(n, WORKERS_MIN, WORKERS_MAX); - - while (resolve->n_valid_workers < n) { - - r = pthread_create(&resolve->workers[resolve->n_valid_workers], NULL, thread_worker, resolve); - if (r != 0) - return -r; - - resolve->n_valid_workers++; - } - - return 0; -} - -static bool resolve_pid_changed(sd_resolve *r) { - assert(r); - - /* We don't support people creating a resolver and keeping it - * around after fork(). Let's complain. */ - - return r->original_pid != getpid(); -} - -_public_ int sd_resolve_new(sd_resolve **ret) { - sd_resolve *resolve = NULL; - int i, r; - - assert_return(ret, -EINVAL); - - resolve = new0(sd_resolve, 1); - if (!resolve) - return -ENOMEM; - - resolve->n_ref = 1; - resolve->original_pid = getpid(); - - for (i = 0; i < _FD_MAX; i++) - resolve->fds[i] = -1; - - r = socketpair(PF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, resolve->fds + REQUEST_RECV_FD); - if (r < 0) { - r = -errno; - goto fail; - } - - r = socketpair(PF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, resolve->fds + RESPONSE_RECV_FD); - if (r < 0) { - r = -errno; - goto fail; - } - - fd_inc_sndbuf(resolve->fds[REQUEST_SEND_FD], QUERIES_MAX * BUFSIZE); - fd_inc_rcvbuf(resolve->fds[REQUEST_RECV_FD], QUERIES_MAX * BUFSIZE); - fd_inc_sndbuf(resolve->fds[RESPONSE_SEND_FD], QUERIES_MAX * BUFSIZE); - fd_inc_rcvbuf(resolve->fds[RESPONSE_RECV_FD], QUERIES_MAX * BUFSIZE); - - fd_nonblock(resolve->fds[RESPONSE_RECV_FD], true); - - *ret = resolve; - return 0; - -fail: - sd_resolve_unref(resolve); - return r; -} - -_public_ int sd_resolve_default(sd_resolve **ret) { - - static thread_local sd_resolve *default_resolve = NULL; - sd_resolve *e = NULL; - int r; - - if (!ret) - return !!default_resolve; - - if (default_resolve) { - *ret = sd_resolve_ref(default_resolve); - return 0; - } - - r = sd_resolve_new(&e); - if (r < 0) - return r; - - e->default_resolve_ptr = &default_resolve; - e->tid = gettid(); - default_resolve = e; - - *ret = e; - return 1; -} - -_public_ int sd_resolve_get_tid(sd_resolve *resolve, pid_t *tid) { - assert_return(resolve, -EINVAL); - assert_return(tid, -EINVAL); - assert_return(!resolve_pid_changed(resolve), -ECHILD); - - if (resolve->tid != 0) { - *tid = resolve->tid; - return 0; - } - - if (resolve->event) - return sd_event_get_tid(resolve->event, tid); - - return -ENXIO; -} - -static void resolve_free(sd_resolve *resolve) { - PROTECT_ERRNO; - sd_resolve_query *q; - unsigned i; - - assert(resolve); - - while ((q = resolve->queries)) { - assert(q->floating); - resolve_query_disconnect(q); - sd_resolve_query_unref(q); - } - - if (resolve->default_resolve_ptr) - *(resolve->default_resolve_ptr) = NULL; - - resolve->dead = true; - - sd_resolve_detach_event(resolve); - - if (resolve->fds[REQUEST_SEND_FD] >= 0) { - - RHeader req = { - .type = REQUEST_TERMINATE, - .length = sizeof(req) - }; - - /* Send one termination packet for each worker */ - for (i = 0; i < resolve->n_valid_workers; i++) - (void) send(resolve->fds[REQUEST_SEND_FD], &req, req.length, MSG_NOSIGNAL); - } - - /* Now terminate them and wait until they are gone. - If we get an error than most likely the thread already exited. */ - for (i = 0; i < resolve->n_valid_workers; i++) - (void) pthread_join(resolve->workers[i], NULL); - - /* Close all communication channels */ - for (i = 0; i < _FD_MAX; i++) - safe_close(resolve->fds[i]); - - free(resolve); -} - -_public_ sd_resolve* sd_resolve_ref(sd_resolve *resolve) { - assert_return(resolve, NULL); - - assert(resolve->n_ref >= 1); - resolve->n_ref++; - - return resolve; -} - -_public_ sd_resolve* sd_resolve_unref(sd_resolve *resolve) { - - if (!resolve) - return NULL; - - assert(resolve->n_ref >= 1); - resolve->n_ref--; - - if (resolve->n_ref <= 0) - resolve_free(resolve); - - return NULL; -} - -_public_ int sd_resolve_get_fd(sd_resolve *resolve) { - assert_return(resolve, -EINVAL); - assert_return(!resolve_pid_changed(resolve), -ECHILD); - - return resolve->fds[RESPONSE_RECV_FD]; -} - -_public_ int sd_resolve_get_events(sd_resolve *resolve) { - assert_return(resolve, -EINVAL); - assert_return(!resolve_pid_changed(resolve), -ECHILD); - - return resolve->n_queries > resolve->n_done ? POLLIN : 0; -} - -_public_ int sd_resolve_get_timeout(sd_resolve *resolve, uint64_t *usec) { - assert_return(resolve, -EINVAL); - assert_return(usec, -EINVAL); - assert_return(!resolve_pid_changed(resolve), -ECHILD); - - *usec = (uint64_t) -1; - return 0; -} - -static sd_resolve_query *lookup_query(sd_resolve *resolve, unsigned id) { - sd_resolve_query *q; - - assert(resolve); - - q = resolve->query_array[id % QUERIES_MAX]; - if (q) - if (q->id == id) - return q; - - return NULL; -} - -static int complete_query(sd_resolve *resolve, sd_resolve_query *q) { - int r; - - assert(q); - assert(!q->done); - assert(q->resolve == resolve); - - q->done = true; - resolve->n_done++; - - resolve->current = sd_resolve_query_ref(q); - - switch (q->type) { - - case REQUEST_ADDRINFO: - r = getaddrinfo_done(q); - break; - - case REQUEST_NAMEINFO: - r = getnameinfo_done(q); - break; - - default: - assert_not_reached("Cannot complete unknown query type"); - } - - resolve->current = NULL; - - if (q->floating) { - resolve_query_disconnect(q); - sd_resolve_query_unref(q); - } - - sd_resolve_query_unref(q); - - return r; -} - -static int unserialize_addrinfo(const void **p, size_t *length, struct addrinfo **ret_ai) { - AddrInfoSerialization s; - size_t l; - struct addrinfo *ai; - - assert(p); - assert(*p); - assert(ret_ai); - assert(length); - - if (*length < sizeof(AddrInfoSerialization)) - return -EBADMSG; - - memcpy(&s, *p, sizeof(s)); - - l = sizeof(AddrInfoSerialization) + s.ai_addrlen + s.canonname_len; - if (*length < l) - return -EBADMSG; - - ai = new0(struct addrinfo, 1); - if (!ai) - return -ENOMEM; - - ai->ai_flags = s.ai_flags; - ai->ai_family = s.ai_family; - ai->ai_socktype = s.ai_socktype; - ai->ai_protocol = s.ai_protocol; - ai->ai_addrlen = s.ai_addrlen; - - if (s.ai_addrlen > 0) { - ai->ai_addr = memdup((const uint8_t*) *p + sizeof(AddrInfoSerialization), s.ai_addrlen); - if (!ai->ai_addr) { - free(ai); - return -ENOMEM; - } - } - - if (s.canonname_len > 0) { - ai->ai_canonname = memdup((const uint8_t*) *p + sizeof(AddrInfoSerialization) + s.ai_addrlen, s.canonname_len); - if (!ai->ai_canonname) { - free(ai->ai_addr); - free(ai); - return -ENOMEM; - } - } - - *length -= l; - *ret_ai = ai; - *p = ((const uint8_t*) *p) + l; - - return 0; -} - -static int handle_response(sd_resolve *resolve, const Packet *packet, size_t length) { - const RHeader *resp; - sd_resolve_query *q; - int r; - - assert(resolve); - - resp = &packet->rheader; - assert(resp); - assert(length >= sizeof(RHeader)); - assert(length == resp->length); - - if (resp->type == RESPONSE_DIED) { - resolve->dead = true; - return 0; - } - - assert(resolve->n_outstanding > 0); - resolve->n_outstanding--; - - q = lookup_query(resolve, resp->id); - if (!q) - return 0; - - switch (resp->type) { - - case RESPONSE_ADDRINFO: { - const AddrInfoResponse *ai_resp = &packet->addrinfo_response; - const void *p; - size_t l; - struct addrinfo *prev = NULL; - - assert(length >= sizeof(AddrInfoResponse)); - assert(q->type == REQUEST_ADDRINFO); - - q->ret = ai_resp->ret; - q->_errno = ai_resp->_errno; - q->_h_errno = ai_resp->_h_errno; - - l = length - sizeof(AddrInfoResponse); - p = (const uint8_t*) resp + sizeof(AddrInfoResponse); - - while (l > 0 && p) { - struct addrinfo *ai = NULL; - - r = unserialize_addrinfo(&p, &l, &ai); - if (r < 0) { - q->ret = EAI_SYSTEM; - q->_errno = -r; - q->_h_errno = 0; - freeaddrinfo(q->addrinfo); - q->addrinfo = NULL; - break; - } - - if (prev) - prev->ai_next = ai; - else - q->addrinfo = ai; - - prev = ai; - } - - return complete_query(resolve, q); - } - - case RESPONSE_NAMEINFO: { - const NameInfoResponse *ni_resp = &packet->nameinfo_response; - - assert(length >= sizeof(NameInfoResponse)); - assert(q->type == REQUEST_NAMEINFO); - - q->ret = ni_resp->ret; - q->_errno = ni_resp->_errno; - q->_h_errno = ni_resp->_h_errno; - - if (ni_resp->hostlen > 0) { - q->host = strndup((const char*) ni_resp + sizeof(NameInfoResponse), ni_resp->hostlen-1); - if (!q->host) { - q->ret = EAI_MEMORY; - q->_errno = ENOMEM; - q->_h_errno = 0; - } - } - - if (ni_resp->servlen > 0) { - q->serv = strndup((const char*) ni_resp + sizeof(NameInfoResponse) + ni_resp->hostlen, ni_resp->servlen-1); - if (!q->serv) { - q->ret = EAI_MEMORY; - q->_errno = ENOMEM; - q->_h_errno = 0; - } - } - - return complete_query(resolve, q); - } - - default: - return 0; - } -} - -_public_ int sd_resolve_process(sd_resolve *resolve) { - RESOLVE_DONT_DESTROY(resolve); - - union { - Packet packet; - uint8_t space[BUFSIZE]; - } buf; - ssize_t l; - int r; - - assert_return(resolve, -EINVAL); - assert_return(!resolve_pid_changed(resolve), -ECHILD); - - /* We don't allow recursively invoking sd_resolve_process(). */ - assert_return(!resolve->current, -EBUSY); - - l = recv(resolve->fds[RESPONSE_RECV_FD], &buf, sizeof(buf), 0); - if (l < 0) { - if (errno == EAGAIN) - return 0; - - return -errno; - } - if (l == 0) - return -ECONNREFUSED; - - r = handle_response(resolve, &buf.packet, (size_t) l); - if (r < 0) - return r; - - return 1; -} - -_public_ int sd_resolve_wait(sd_resolve *resolve, uint64_t timeout_usec) { - int r; - - assert_return(resolve, -EINVAL); - assert_return(!resolve_pid_changed(resolve), -ECHILD); - - if (resolve->n_done >= resolve->n_queries) - return 0; - - do { - r = fd_wait_for_event(resolve->fds[RESPONSE_RECV_FD], POLLIN, timeout_usec); - } while (r == -EINTR); - - if (r < 0) - return r; - - return sd_resolve_process(resolve); -} - -static int alloc_query(sd_resolve *resolve, bool floating, sd_resolve_query **_q) { - sd_resolve_query *q; - int r; - - assert(resolve); - assert(_q); - - if (resolve->n_queries >= QUERIES_MAX) - return -ENOBUFS; - - r = start_threads(resolve, 1); - if (r < 0) - return r; - - while (resolve->query_array[resolve->current_id % QUERIES_MAX]) - resolve->current_id++; - - q = resolve->query_array[resolve->current_id % QUERIES_MAX] = new0(sd_resolve_query, 1); - if (!q) - return -ENOMEM; - - q->n_ref = 1; - q->resolve = resolve; - q->floating = floating; - q->id = resolve->current_id++; - - if (!floating) - sd_resolve_ref(resolve); - - LIST_PREPEND(queries, resolve->queries, q); - resolve->n_queries++; - - *_q = q; - return 0; -} - -_public_ int sd_resolve_getaddrinfo( - sd_resolve *resolve, - sd_resolve_query **_q, - const char *node, const char *service, - const struct addrinfo *hints, - sd_resolve_getaddrinfo_handler_t callback, void *userdata) { - - AddrInfoRequest req = {}; - struct msghdr mh = {}; - struct iovec iov[3]; - sd_resolve_query *q; - int r; - - assert_return(resolve, -EINVAL); - assert_return(node || service, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(!resolve_pid_changed(resolve), -ECHILD); - - r = alloc_query(resolve, !_q, &q); - if (r < 0) - return r; - - q->type = REQUEST_ADDRINFO; - q->getaddrinfo_handler = callback; - q->userdata = userdata; - - req.node_len = node ? strlen(node)+1 : 0; - req.service_len = service ? strlen(service)+1 : 0; - - req.header.id = q->id; - req.header.type = REQUEST_ADDRINFO; - req.header.length = sizeof(AddrInfoRequest) + req.node_len + req.service_len; - - if (hints) { - req.hints_valid = true; - req.ai_flags = hints->ai_flags; - req.ai_family = hints->ai_family; - req.ai_socktype = hints->ai_socktype; - req.ai_protocol = hints->ai_protocol; - } - - iov[mh.msg_iovlen++] = (struct iovec) { .iov_base = &req, .iov_len = sizeof(AddrInfoRequest) }; - if (node) - iov[mh.msg_iovlen++] = (struct iovec) { .iov_base = (void*) node, .iov_len = req.node_len }; - if (service) - iov[mh.msg_iovlen++] = (struct iovec) { .iov_base = (void*) service, .iov_len = req.service_len }; - mh.msg_iov = iov; - - if (sendmsg(resolve->fds[REQUEST_SEND_FD], &mh, MSG_NOSIGNAL) < 0) { - sd_resolve_query_unref(q); - return -errno; - } - - resolve->n_outstanding++; - - if (_q) - *_q = q; - - return 0; -} - -static int getaddrinfo_done(sd_resolve_query* q) { - assert(q); - assert(q->done); - assert(q->getaddrinfo_handler); - - errno = q->_errno; - h_errno = q->_h_errno; - - return q->getaddrinfo_handler(q, q->ret, q->addrinfo, q->userdata); -} - -_public_ int sd_resolve_getnameinfo( - sd_resolve *resolve, - sd_resolve_query**_q, - const struct sockaddr *sa, socklen_t salen, - int flags, - uint64_t get, - sd_resolve_getnameinfo_handler_t callback, - void *userdata) { - - NameInfoRequest req = {}; - struct msghdr mh = {}; - struct iovec iov[2]; - sd_resolve_query *q; - int r; - - assert_return(resolve, -EINVAL); - assert_return(sa, -EINVAL); - assert_return(salen >= sizeof(struct sockaddr), -EINVAL); - assert_return(salen <= sizeof(union sockaddr_union), -EINVAL); - assert_return((get & ~SD_RESOLVE_GET_BOTH) == 0, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(!resolve_pid_changed(resolve), -ECHILD); - - r = alloc_query(resolve, !_q, &q); - if (r < 0) - return r; - - q->type = REQUEST_NAMEINFO; - q->getnameinfo_handler = callback; - q->userdata = userdata; - - req.header.id = q->id; - req.header.type = REQUEST_NAMEINFO; - req.header.length = sizeof(NameInfoRequest) + salen; - - req.flags = flags; - req.sockaddr_len = salen; - req.gethost = !!(get & SD_RESOLVE_GET_HOST); - req.getserv = !!(get & SD_RESOLVE_GET_SERVICE); - - iov[0] = (struct iovec) { .iov_base = &req, .iov_len = sizeof(NameInfoRequest) }; - iov[1] = (struct iovec) { .iov_base = (void*) sa, .iov_len = salen }; - - mh.msg_iov = iov; - mh.msg_iovlen = 2; - - if (sendmsg(resolve->fds[REQUEST_SEND_FD], &mh, MSG_NOSIGNAL) < 0) { - sd_resolve_query_unref(q); - return -errno; - } - - resolve->n_outstanding++; - - if (_q) - *_q = q; - - return 0; -} - -static int getnameinfo_done(sd_resolve_query *q) { - - assert(q); - assert(q->done); - assert(q->getnameinfo_handler); - - errno = q->_errno; - h_errno= q->_h_errno; - - return q->getnameinfo_handler(q, q->ret, q->host, q->serv, q->userdata); -} - -_public_ sd_resolve_query* sd_resolve_query_ref(sd_resolve_query *q) { - assert_return(q, NULL); - - assert(q->n_ref >= 1); - q->n_ref++; - - return q; -} - -static void resolve_freeaddrinfo(struct addrinfo *ai) { - while (ai) { - struct addrinfo *next = ai->ai_next; - - free(ai->ai_addr); - free(ai->ai_canonname); - free(ai); - ai = next; - } -} - -static void resolve_query_disconnect(sd_resolve_query *q) { - sd_resolve *resolve; - unsigned i; - - assert(q); - - if (!q->resolve) - return; - - resolve = q->resolve; - assert(resolve->n_queries > 0); - - if (q->done) { - assert(resolve->n_done > 0); - resolve->n_done--; - } - - i = q->id % QUERIES_MAX; - assert(resolve->query_array[i] == q); - resolve->query_array[i] = NULL; - LIST_REMOVE(queries, resolve->queries, q); - resolve->n_queries--; - - q->resolve = NULL; - if (!q->floating) - sd_resolve_unref(resolve); -} - -static void resolve_query_free(sd_resolve_query *q) { - assert(q); - - resolve_query_disconnect(q); - - resolve_freeaddrinfo(q->addrinfo); - free(q->host); - free(q->serv); - free(q); -} - -_public_ sd_resolve_query* sd_resolve_query_unref(sd_resolve_query* q) { - if (!q) - return NULL; - - assert(q->n_ref >= 1); - q->n_ref--; - - if (q->n_ref <= 0) - resolve_query_free(q); - - return NULL; -} - -_public_ int sd_resolve_query_is_done(sd_resolve_query *q) { - assert_return(q, -EINVAL); - assert_return(!resolve_pid_changed(q->resolve), -ECHILD); - - return q->done; -} - -_public_ void* sd_resolve_query_set_userdata(sd_resolve_query *q, void *userdata) { - void *ret; - - assert_return(q, NULL); - assert_return(!resolve_pid_changed(q->resolve), NULL); - - ret = q->userdata; - q->userdata = userdata; - - return ret; -} - -_public_ void* sd_resolve_query_get_userdata(sd_resolve_query *q) { - assert_return(q, NULL); - assert_return(!resolve_pid_changed(q->resolve), NULL); - - return q->userdata; -} - -_public_ sd_resolve *sd_resolve_query_get_resolve(sd_resolve_query *q) { - assert_return(q, NULL); - assert_return(!resolve_pid_changed(q->resolve), NULL); - - return q->resolve; -} - -static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - sd_resolve *resolve = userdata; - int r; - - assert(resolve); - - r = sd_resolve_process(resolve); - if (r < 0) - return r; - - return 1; -} - -_public_ int sd_resolve_attach_event(sd_resolve *resolve, sd_event *event, int64_t priority) { - int r; - - assert_return(resolve, -EINVAL); - assert_return(!resolve->event, -EBUSY); - - assert(!resolve->event_source); - - if (event) - resolve->event = sd_event_ref(event); - else { - r = sd_event_default(&resolve->event); - if (r < 0) - return r; - } - - r = sd_event_add_io(resolve->event, &resolve->event_source, resolve->fds[RESPONSE_RECV_FD], POLLIN, io_callback, resolve); - if (r < 0) - goto fail; - - r = sd_event_source_set_priority(resolve->event_source, priority); - if (r < 0) - goto fail; - - return 0; - -fail: - sd_resolve_detach_event(resolve); - return r; -} - -_public_ int sd_resolve_detach_event(sd_resolve *resolve) { - assert_return(resolve, -EINVAL); - - if (!resolve->event) - return 0; - - if (resolve->event_source) { - sd_event_source_set_enabled(resolve->event_source, SD_EVENT_OFF); - resolve->event_source = sd_event_source_unref(resolve->event_source); - } - - resolve->event = sd_event_unref(resolve->event); - return 1; -} - -_public_ sd_event *sd_resolve_get_event(sd_resolve *resolve) { - assert_return(resolve, NULL); - - return resolve->event; -} diff --git a/src/libsystemd/sd-resolve/test-resolve.c b/src/libsystemd/sd-resolve/test-resolve.c deleted file mode 100644 index 1be1a7f8a7..0000000000 --- a/src/libsystemd/sd-resolve/test-resolve.c +++ /dev/null @@ -1,114 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2005-2008 Lennart Poettering - Copyright 2014 Daniel Buch - - 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 -#include -#include -#include -#include -#include - -#include "sd-resolve.h" - -#include "alloc-util.h" -#include "macro.h" -#include "socket-util.h" -#include "string-util.h" - -static int getaddrinfo_handler(sd_resolve_query *q, int ret, const struct addrinfo *ai, void *userdata) { - const struct addrinfo *i; - - assert_se(q); - - if (ret != 0) { - log_error("getaddrinfo error: %s %i", gai_strerror(ret), ret); - return 0; - } - - for (i = ai; i; i = i->ai_next) { - _cleanup_free_ char *addr = NULL; - - assert_se(sockaddr_pretty(i->ai_addr, i->ai_addrlen, false, true, &addr) == 0); - puts(addr); - } - - printf("canonical name: %s\n", strna(ai->ai_canonname)); - - return 0; -} - -static int getnameinfo_handler(sd_resolve_query *q, int ret, const char *host, const char *serv, void *userdata) { - assert_se(q); - - if (ret != 0) { - log_error("getnameinfo error: %s %i", gai_strerror(ret), ret); - return 0; - } - - printf("Host: %s — Serv: %s\n", strna(host), strna(serv)); - return 0; -} - -int main(int argc, char *argv[]) { - _cleanup_(sd_resolve_query_unrefp) sd_resolve_query *q1 = NULL, *q2 = NULL; - _cleanup_(sd_resolve_unrefp) sd_resolve *resolve = NULL; - int r = 0; - - struct addrinfo hints = { - .ai_family = PF_UNSPEC, - .ai_socktype = SOCK_STREAM, - .ai_flags = AI_CANONNAME - }; - - struct sockaddr_in sa = { - .sin_family = AF_INET, - .sin_port = htons(80) - }; - - assert_se(sd_resolve_default(&resolve) >= 0); - - /* Test a floating resolver query */ - sd_resolve_getaddrinfo(resolve, NULL, "redhat.com", "http", NULL, getaddrinfo_handler, NULL); - - /* Make a name -> address query */ - r = sd_resolve_getaddrinfo(resolve, &q1, argc >= 2 ? argv[1] : "www.heise.de", NULL, &hints, getaddrinfo_handler, NULL); - if (r < 0) - log_error_errno(r, "sd_resolve_getaddrinfo(): %m"); - - /* Make an address -> name query */ - sa.sin_addr.s_addr = inet_addr(argc >= 3 ? argv[2] : "193.99.144.71"); - r = sd_resolve_getnameinfo(resolve, &q2, (struct sockaddr*) &sa, sizeof(sa), 0, SD_RESOLVE_GET_BOTH, getnameinfo_handler, NULL); - if (r < 0) - log_error_errno(r, "sd_resolve_getnameinfo(): %m"); - - /* Wait until all queries are completed */ - for (;;) { - r = sd_resolve_wait(resolve, (uint64_t) -1); - if (r == 0) - break; - if (r < 0) { - log_error_errno(r, "sd_resolve_wait(): %m"); - assert_not_reached("sd_resolve_wait() failed"); - } - } - - return 0; -} diff --git a/src/libsystemd/sd-utf8/Makefile b/src/libsystemd/sd-utf8/Makefile deleted file mode 120000 index 94aaae2c4d..0000000000 --- a/src/libsystemd/sd-utf8/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-utf8/sd-utf8.c b/src/libsystemd/sd-utf8/sd-utf8.c deleted file mode 100644 index 33a5a04ea1..0000000000 --- a/src/libsystemd/sd-utf8/sd-utf8.c +++ /dev/null @@ -1,35 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-utf8.h" - -#include "utf8.h" -#include "util.h" - -_public_ const char *sd_utf8_is_valid(const char *s) { - assert_return(s, NULL); - - return utf8_is_valid(s); -} - -_public_ const char *sd_ascii_is_valid(const char *s) { - assert_return(s, NULL); - - return ascii_is_valid(s); -} diff --git a/src/libudev/.gitignore b/src/libudev/.gitignore deleted file mode 100644 index 0c8a5d5231..0000000000 --- a/src/libudev/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/libudev.pc diff --git a/src/libudev/Makefile b/src/libudev/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/libudev/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/libudev/Makefile b/src/libudev/Makefile new file mode 100644 index 0000000000..8d9fecb1fb --- /dev/null +++ b/src/libudev/Makefile @@ -0,0 +1,28 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +at.subdirs += src + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libudev/include/libudev.h b/src/libudev/include/libudev.h new file mode 100644 index 0000000000..3f6d0ed16c --- /dev/null +++ b/src/libudev/include/libudev.h @@ -0,0 +1,207 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 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 . +***/ + +#ifndef _LIBUDEV_H_ +#define _LIBUDEV_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * udev - library context + * + * reads the udev config and system environment + * allows custom logging + */ +struct udev; +struct udev *udev_ref(struct udev *udev); +struct udev *udev_unref(struct udev *udev); +struct udev *udev_new(void); +void udev_set_log_fn(struct udev *udev, + void (*log_fn)(struct udev *udev, + int priority, const char *file, int line, const char *fn, + const char *format, va_list args)) __attribute__ ((deprecated)); +int udev_get_log_priority(struct udev *udev) __attribute__ ((deprecated)); +void udev_set_log_priority(struct udev *udev, int priority) __attribute__ ((deprecated)); +void *udev_get_userdata(struct udev *udev); +void udev_set_userdata(struct udev *udev, void *userdata); + +/* + * udev_list + * + * access to libudev generated lists + */ +struct udev_list_entry; +struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry); +struct udev_list_entry *udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name); +const char *udev_list_entry_get_name(struct udev_list_entry *list_entry); +const char *udev_list_entry_get_value(struct udev_list_entry *list_entry); +/** + * udev_list_entry_foreach: + * @list_entry: entry to store the current position + * @first_entry: first entry to start with + * + * Helper to iterate over all entries of a list. + */ +#define udev_list_entry_foreach(list_entry, first_entry) \ + for (list_entry = first_entry; \ + list_entry != NULL; \ + list_entry = udev_list_entry_get_next(list_entry)) + +/* + * udev_device + * + * access to sysfs/kernel devices + */ +struct udev_device; +struct udev_device *udev_device_ref(struct udev_device *udev_device); +struct udev_device *udev_device_unref(struct udev_device *udev_device); +struct udev *udev_device_get_udev(struct udev_device *udev_device); +struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath); +struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum); +struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname); +struct udev_device *udev_device_new_from_device_id(struct udev *udev, const char *id); +struct udev_device *udev_device_new_from_environment(struct udev *udev); +/* udev_device_get_parent_*() does not take a reference on the returned device, it is automatically unref'd with the parent */ +struct udev_device *udev_device_get_parent(struct udev_device *udev_device); +struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device, + const char *subsystem, const char *devtype); +/* retrieve device properties */ +const char *udev_device_get_devpath(struct udev_device *udev_device); +const char *udev_device_get_subsystem(struct udev_device *udev_device); +const char *udev_device_get_devtype(struct udev_device *udev_device); +const char *udev_device_get_syspath(struct udev_device *udev_device); +const char *udev_device_get_sysname(struct udev_device *udev_device); +const char *udev_device_get_sysnum(struct udev_device *udev_device); +const char *udev_device_get_devnode(struct udev_device *udev_device); +int udev_device_get_is_initialized(struct udev_device *udev_device); +struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device); +struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device); +struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device); +struct udev_list_entry *udev_device_get_sysattr_list_entry(struct udev_device *udev_device); +const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key); +const char *udev_device_get_driver(struct udev_device *udev_device); +dev_t udev_device_get_devnum(struct udev_device *udev_device); +const char *udev_device_get_action(struct udev_device *udev_device); +unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device); +unsigned long long int udev_device_get_usec_since_initialized(struct udev_device *udev_device); +const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr); +int udev_device_set_sysattr_value(struct udev_device *udev_device, const char *sysattr, char *value); +int udev_device_has_tag(struct udev_device *udev_device, const char *tag); + +/* + * udev_monitor + * + * access to kernel uevents and udev events + */ +struct udev_monitor; +struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor); +struct udev_monitor *udev_monitor_unref(struct udev_monitor *udev_monitor); +struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor); +/* kernel and udev generated events over netlink */ +struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name); +/* bind socket */ +int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor); +int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size); +int udev_monitor_get_fd(struct udev_monitor *udev_monitor); +struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor); +/* in-kernel socket filters to select messages that get delivered to a listener */ +int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, + const char *subsystem, const char *devtype); +int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag); +int udev_monitor_filter_update(struct udev_monitor *udev_monitor); +int udev_monitor_filter_remove(struct udev_monitor *udev_monitor); + +/* + * udev_enumerate + * + * search sysfs for specific devices and provide a sorted list + */ +struct udev_enumerate; +struct udev_enumerate *udev_enumerate_ref(struct udev_enumerate *udev_enumerate); +struct udev_enumerate *udev_enumerate_unref(struct udev_enumerate *udev_enumerate); +struct udev *udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate); +struct udev_enumerate *udev_enumerate_new(struct udev *udev); +/* device properties filter */ +int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem); +int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem); +int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value); +int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value); +int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value); +int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname); +int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag); +int udev_enumerate_add_match_parent(struct udev_enumerate *udev_enumerate, struct udev_device *parent); +int udev_enumerate_add_match_is_initialized(struct udev_enumerate *udev_enumerate); +int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath); +/* run enumeration with active filters */ +int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate); +int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate); +/* return device list */ +struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate); + +/* + * udev_queue + * + * access to the currently running udev events + */ +struct udev_queue; +struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue); +struct udev_queue *udev_queue_unref(struct udev_queue *udev_queue); +struct udev *udev_queue_get_udev(struct udev_queue *udev_queue); +struct udev_queue *udev_queue_new(struct udev *udev); +unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue) __attribute__ ((deprecated)); +unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue) __attribute__ ((deprecated)); +int udev_queue_get_udev_is_active(struct udev_queue *udev_queue); +int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue); +int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum) __attribute__ ((deprecated)); +int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue, + unsigned long long int start, unsigned long long int end) __attribute__ ((deprecated)); +int udev_queue_get_fd(struct udev_queue *udev_queue); +int udev_queue_flush(struct udev_queue *udev_queue); +struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue) __attribute__ ((deprecated)); + +/* + * udev_hwdb + * + * access to the static hardware properties database + */ +struct udev_hwdb; +struct udev_hwdb *udev_hwdb_new(struct udev *udev); +struct udev_hwdb *udev_hwdb_ref(struct udev_hwdb *hwdb); +struct udev_hwdb *udev_hwdb_unref(struct udev_hwdb *hwdb); +struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned int flags); + +/* + * udev_util + * + * udev specific utilities + */ +int udev_util_encode_string(const char *str, char *str_enc, size_t len); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/libudev/libudev-device-internal.h b/src/libudev/libudev-device-internal.h deleted file mode 100644 index 0e9af8ec09..0000000000 --- a/src/libudev/libudev-device-internal.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2015 Tom Gundersen - - 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 "libudev.h" -#include "sd-device.h" - -#include "libudev-private.h" - -/** - * udev_device: - * - * Opaque object representing one kernel sys device. - */ -struct udev_device { - struct udev *udev; - - /* real device object */ - sd_device *device; - - /* legacy */ - int refcount; - - struct udev_device *parent; - bool parent_set; - - struct udev_list properties; - uint64_t properties_generation; - struct udev_list tags; - uint64_t tags_generation; - struct udev_list devlinks; - uint64_t devlinks_generation; - bool properties_read:1; - bool tags_read:1; - bool devlinks_read:1; - struct udev_list sysattrs; - bool sysattrs_read; -}; - -struct udev_device *udev_device_new(struct udev *udev); diff --git a/src/libudev/libudev-device-private.c b/src/libudev/libudev-device-private.c deleted file mode 100644 index 2aae0726c1..0000000000 --- a/src/libudev/libudev-device-private.c +++ /dev/null @@ -1,411 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2015 Tom Gundersen - - 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 "libudev.h" - -#include "device-private.h" -#include "libudev-device-internal.h" -#include "libudev-private.h" - -int udev_device_tag_index(struct udev_device *udev_device, struct udev_device *udev_device_old, bool add) { - sd_device *device_old = NULL; - int r; - - assert(udev_device); - - if (udev_device_old) - device_old = udev_device_old->device; - - r = device_tag_index(udev_device->device, device_old, add); - if (r < 0) - return r; - - return 0; -} - -int udev_device_update_db(struct udev_device *udev_device) { - int r; - - assert(udev_device); - - r = device_update_db(udev_device->device); - if (r < 0) - return r; - - return 0; -} - -int udev_device_delete_db(struct udev_device *udev_device) { - int r; - - assert(udev_device); - - r = device_delete_db(udev_device->device); - if (r < 0) - return r; - - return 0; -} - -int udev_device_get_ifindex(struct udev_device *udev_device) { - int r, ifindex; - - assert(udev_device); - - r = sd_device_get_ifindex(udev_device->device, &ifindex); - if (r < 0) - return r; - - return ifindex; -} - -const char *udev_device_get_devpath_old(struct udev_device *udev_device) { - const char *devpath_old = NULL; - int r; - - assert(udev_device); - - r = sd_device_get_property_value(udev_device->device, "DEVPATH_OLD", &devpath_old); - if (r < 0 && r != -ENOENT) { - errno = -r; - return NULL; - } - - return devpath_old; -} - -mode_t udev_device_get_devnode_mode(struct udev_device *udev_device) { - mode_t mode; - int r; - - assert(udev_device); - - r = device_get_devnode_mode(udev_device->device, &mode); - if (r < 0) { - errno = -r; - return 0; - } - - return mode; -} - -uid_t udev_device_get_devnode_uid(struct udev_device *udev_device) { - uid_t uid; - int r; - - assert(udev_device); - - r = device_get_devnode_uid(udev_device->device, &uid); - if (r < 0) { - errno = -r; - return 0; - } - - return uid; -} - -gid_t udev_device_get_devnode_gid(struct udev_device *udev_device) { - gid_t gid; - int r; - - assert(udev_device); - - r = device_get_devnode_gid(udev_device->device, &gid); - if (r < 0) { - errno = -r; - return 0; - } - - return gid; -} - -void udev_device_ensure_usec_initialized(struct udev_device *udev_device, struct udev_device *udev_device_old) { - assert(udev_device); - - device_ensure_usec_initialized(udev_device->device, - udev_device_old ? udev_device_old->device : NULL); -} - -char **udev_device_get_properties_envp(struct udev_device *udev_device) { - char **envp; - int r; - - assert(udev_device); - - r = device_get_properties_strv(udev_device->device, &envp); - if (r < 0) { - errno = -r; - return NULL; - } - - return envp; -} - -ssize_t udev_device_get_properties_monitor_buf(struct udev_device *udev_device, const char **buf) { - const char *nulstr; - size_t len; - int r; - - assert(udev_device); - assert(buf); - - r = device_get_properties_nulstr(udev_device->device, (const uint8_t **)&nulstr, &len); - if (r < 0) - return r; - - *buf = nulstr; - - return len; -} - -int udev_device_get_devlink_priority(struct udev_device *udev_device) { - int priority, r; - - assert(udev_device); - - r = device_get_devlink_priority(udev_device->device, &priority); - if (r < 0) - return r; - - return priority; -} - -int udev_device_get_watch_handle(struct udev_device *udev_device) { - int handle, r; - - assert(udev_device); - - r = device_get_watch_handle(udev_device->device, &handle); - if (r < 0) - return r; - - return handle; -} - -void udev_device_set_is_initialized(struct udev_device *udev_device) { - assert(udev_device); - - device_set_is_initialized(udev_device->device); -} - -int udev_device_rename(struct udev_device *udev_device, const char *name) { - int r; - - assert(udev_device); - - r = device_rename(udev_device->device, name); - if (r < 0) - return r; - - return 0; -} - -struct udev_device *udev_device_shallow_clone(struct udev_device *old_device) { - struct udev_device *device; - int r; - - assert(old_device); - - device = udev_device_new(old_device->udev); - if (!device) - return NULL; - - r = device_shallow_clone(old_device->device, &device->device); - if (r < 0) { - udev_device_unref(device); - errno = -r; - return NULL; - } - - return device; -} - -struct udev_device *udev_device_clone_with_db(struct udev_device *udev_device_old) { - struct udev_device *udev_device; - int r; - - assert(udev_device_old); - - udev_device = udev_device_new(udev_device_old->udev); - if (!udev_device) - return NULL; - - r = device_clone_with_db(udev_device_old->device, &udev_device->device); - if (r < 0) { - udev_device_unref(udev_device); - errno = -r; - return NULL; - } - - return udev_device; -} - -struct udev_device *udev_device_new_from_nulstr(struct udev *udev, char *nulstr, ssize_t buflen) { - struct udev_device *device; - int r; - - device = udev_device_new(udev); - if (!device) - return NULL; - - r = device_new_from_nulstr(&device->device, (uint8_t*)nulstr, buflen); - if (r < 0) { - udev_device_unref(device); - errno = -r; - return NULL; - } - - return device; -} - -struct udev_device *udev_device_new_from_synthetic_event(struct udev *udev, const char *syspath, const char *action) { - struct udev_device *device; - int r; - - device = udev_device_new(udev); - if (!device) - return NULL; - - r = device_new_from_synthetic_event(&device->device, syspath, action); - if (r < 0) { - udev_device_unref(device); - errno = -r; - return NULL; - } - - return device; -} - -int udev_device_copy_properties(struct udev_device *udev_device_dst, struct udev_device *udev_device_src) { - int r; - - assert(udev_device_dst); - assert(udev_device_src); - - r = device_copy_properties(udev_device_dst->device, udev_device_src->device); - if (r < 0) - return r; - - return 0; -} - -const char *udev_device_get_id_filename(struct udev_device *udev_device) { - const char *filename; - int r; - - assert(udev_device); - - r = device_get_id_filename(udev_device->device, &filename); - if (r < 0) { - errno = -r; - return NULL; - } - - return filename; -} - -int udev_device_set_watch_handle(struct udev_device *udev_device, int handle) { - - assert(udev_device); - - device_set_watch_handle(udev_device->device, handle); - - return 0; -} - -void udev_device_set_db_persist(struct udev_device *udev_device) { - assert(udev_device); - - device_set_db_persist(udev_device->device); -} - -int udev_device_set_devlink_priority(struct udev_device *udev_device, int priority) { - assert(udev_device); - - device_set_devlink_priority(udev_device->device, priority); - - return 0; -} - -int udev_device_add_devlink(struct udev_device *udev_device, const char *devlink) { - int r; - - assert(udev_device); - - r = device_add_devlink(udev_device->device, devlink); - if (r < 0) - return r; - - return 0; -} - -int udev_device_add_property(struct udev_device *udev_device, const char *property, const char *value) { - int r; - - assert(udev_device); - - r = device_add_property(udev_device->device, property, value); - if (r < 0) - return r; - - return 0; -} - -int udev_device_add_tag(struct udev_device *udev_device, const char *tag) { - int r; - - assert(udev_device); - - r = device_add_tag(udev_device->device, tag); - if (r < 0) - return r; - - return 0; -} - -void udev_device_remove_tag(struct udev_device *udev_device, const char *tag) { - assert(udev_device); - - device_remove_tag(udev_device->device, tag); -} - -void udev_device_cleanup_tags_list(struct udev_device *udev_device) { - assert(udev_device); - - device_cleanup_tags(udev_device->device); -} - -void udev_device_cleanup_devlinks_list(struct udev_device *udev_device) { - assert(udev_device); - - device_cleanup_devlinks(udev_device->device); -} - -void udev_device_set_info_loaded(struct udev_device *udev_device) { - assert(udev_device); - - device_seal(udev_device->device); -} - -void udev_device_read_db(struct udev_device *udev_device) { - assert(udev_device); - - device_read_db_force(udev_device->device); -} diff --git a/src/libudev/libudev-device.c b/src/libudev/libudev-device.c deleted file mode 100644 index 814e016800..0000000000 --- a/src/libudev/libudev-device.c +++ /dev/null @@ -1,958 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2015 Tom Gundersen - - 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "libudev.h" -#include "sd-device.h" - -#include "alloc-util.h" -#include "device-private.h" -#include "device-util.h" -#include "libudev-device-internal.h" -#include "libudev-private.h" -#include "parse-util.h" - -/** - * SECTION:libudev-device - * @short_description: kernel sys devices - * - * Representation of kernel sys devices. Devices are uniquely identified - * by their syspath, every device has exactly one path in the kernel sys - * filesystem. Devices usually belong to a kernel subsystem, and have - * a unique name inside that subsystem. - */ - -/** - * udev_device_get_seqnum: - * @udev_device: udev device - * - * This is only valid if the device was received through a monitor. Devices read from - * sys do not have a sequence number. - * - * Returns: the kernel event sequence number, or 0 if there is no sequence number available. - **/ -_public_ unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device) -{ - const char *seqnum; - unsigned long long ret; - int r; - - assert_return_errno(udev_device, 0, EINVAL); - - r = sd_device_get_property_value(udev_device->device, "SEQNUM", &seqnum); - if (r == -ENOENT) - return 0; - else if (r < 0) { - errno = -r; - return 0; - } - - r = safe_atollu(seqnum, &ret); - if (r < 0) { - errno = -r; - return 0; - } - - return ret; -} - -/** - * udev_device_get_devnum: - * @udev_device: udev device - * - * Get the device major/minor number. - * - * Returns: the dev_t number. - **/ -_public_ dev_t udev_device_get_devnum(struct udev_device *udev_device) -{ - dev_t devnum; - int r; - - assert_return_errno(udev_device, makedev(0, 0), EINVAL); - - r = sd_device_get_devnum(udev_device->device, &devnum); - if (r < 0) { - errno = -r; - return makedev(0, 0); - } - - return devnum; -} - -/** - * udev_device_get_driver: - * @udev_device: udev device - * - * Get the kernel driver name. - * - * Returns: the driver name string, or #NULL if there is no driver attached. - **/ -_public_ const char *udev_device_get_driver(struct udev_device *udev_device) -{ - const char *driver; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_driver(udev_device->device, &driver); - if (r < 0) { - errno = -r; - return NULL; - } - - return driver; -} - -/** - * udev_device_get_devtype: - * @udev_device: udev device - * - * Retrieve the devtype string of the udev device. - * - * Returns: the devtype name of the udev device, or #NULL if it can not be determined - **/ -_public_ const char *udev_device_get_devtype(struct udev_device *udev_device) -{ - const char *devtype; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_devtype(udev_device->device, &devtype); - if (r < 0) { - errno = -r; - return NULL; - } - - return devtype; -} - -/** - * udev_device_get_subsystem: - * @udev_device: udev device - * - * Retrieve the subsystem string of the udev device. The string does not - * contain any "/". - * - * Returns: the subsystem name of the udev device, or #NULL if it can not be determined - **/ -_public_ const char *udev_device_get_subsystem(struct udev_device *udev_device) -{ - const char *subsystem; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_subsystem(udev_device->device, &subsystem); - if (r < 0) { - errno = -r; - return NULL; - } else if (!subsystem) - errno = ENODATA; - - return subsystem; -} - -/** - * udev_device_get_property_value: - * @udev_device: udev device - * @key: property name - * - * Get the value of a given property. - * - * Returns: the property string, or #NULL if there is no such property. - **/ -_public_ const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key) -{ - const char *value = NULL; - int r; - - assert_return_errno(udev_device && key, NULL, EINVAL); - - r = sd_device_get_property_value(udev_device->device, key, &value); - if (r < 0) { - errno = -r; - return NULL; - } - - return value; -} - -struct udev_device *udev_device_new(struct udev *udev) { - struct udev_device *udev_device; - - assert_return_errno(udev, NULL, EINVAL); - - udev_device = new0(struct udev_device, 1); - if (!udev_device) { - errno = ENOMEM; - return NULL; - } - udev_device->refcount = 1; - udev_device->udev = udev; - udev_list_init(udev, &udev_device->properties, true); - udev_list_init(udev, &udev_device->tags, true); - udev_list_init(udev, &udev_device->sysattrs, true); - udev_list_init(udev, &udev_device->devlinks, true); - - return udev_device; -} - -/** - * udev_device_new_from_syspath: - * @udev: udev library context - * @syspath: sys device path including sys directory - * - * Create new udev device, and fill in information from the sys - * device and the udev database entry. The syspath is the absolute - * path to the device, including the sys mount point. - * - * The initial refcount is 1, and needs to be decremented to - * release the resources of the udev device. - * - * Returns: a new udev device, or #NULL, if it does not exist - **/ -_public_ struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath) { - struct udev_device *udev_device; - int r; - - udev_device = udev_device_new(udev); - if (!udev_device) - return NULL; - - r = sd_device_new_from_syspath(&udev_device->device, syspath); - if (r < 0) { - errno = -r; - udev_device_unref(udev_device); - return NULL; - } - - return udev_device; -} - -/** - * udev_device_new_from_devnum: - * @udev: udev library context - * @type: char or block device - * @devnum: device major/minor number - * - * Create new udev device, and fill in information from the sys - * device and the udev database entry. The device is looked-up - * by its major/minor number and type. Character and block device - * numbers are not unique across the two types. - * - * The initial refcount is 1, and needs to be decremented to - * release the resources of the udev device. - * - * Returns: a new udev device, or #NULL, if it does not exist - **/ -_public_ struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum) -{ - struct udev_device *udev_device; - int r; - - udev_device = udev_device_new(udev); - if (!udev_device) - return NULL; - - r = sd_device_new_from_devnum(&udev_device->device, type, devnum); - if (r < 0) { - errno = -r; - udev_device_unref(udev_device); - return NULL; - } - - return udev_device; -} - -/** - * udev_device_new_from_device_id: - * @udev: udev library context - * @id: text string identifying a kernel device - * - * Create new udev device, and fill in information from the sys - * device and the udev database entry. The device is looked-up - * by a special string: - * b8:2 - block device major:minor - * c128:1 - char device major:minor - * n3 - network device ifindex - * +sound:card29 - kernel driver core subsystem:device name - * - * The initial refcount is 1, and needs to be decremented to - * release the resources of the udev device. - * - * Returns: a new udev device, or #NULL, if it does not exist - **/ -_public_ struct udev_device *udev_device_new_from_device_id(struct udev *udev, const char *id) -{ - struct udev_device *udev_device; - int r; - - udev_device = udev_device_new(udev); - if (!udev_device) - return NULL; - - r = sd_device_new_from_device_id(&udev_device->device, id); - if (r < 0) { - errno = -r; - udev_device_unref(udev_device); - return NULL; - } - - return udev_device; -} - -/** - * udev_device_new_from_subsystem_sysname: - * @udev: udev library context - * @subsystem: the subsystem of the device - * @sysname: the name of the device - * - * Create new udev device, and fill in information from the sys device - * and the udev database entry. The device is looked up by the subsystem - * and name string of the device, like "mem" / "zero", or "block" / "sda". - * - * The initial refcount is 1, and needs to be decremented to - * release the resources of the udev device. - * - * Returns: a new udev device, or #NULL, if it does not exist - **/ -_public_ struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname) -{ - struct udev_device *udev_device; - int r; - - udev_device = udev_device_new(udev); - if (!udev_device) - return NULL; - - r = sd_device_new_from_subsystem_sysname(&udev_device->device, subsystem, sysname); - if (r < 0) { - errno = -r; - udev_device_unref(udev_device); - return NULL; - } - - return udev_device; -} - -/** - * udev_device_new_from_environment - * @udev: udev library context - * - * Create new udev device, and fill in information from the - * current process environment. This only works reliable if - * the process is called from a udev rule. It is usually used - * for tools executed from IMPORT= rules. - * - * The initial refcount is 1, and needs to be decremented to - * release the resources of the udev device. - * - * Returns: a new udev device, or #NULL, if it does not exist - **/ -_public_ struct udev_device *udev_device_new_from_environment(struct udev *udev) -{ - struct udev_device *udev_device; - int r; - - udev_device = udev_device_new(udev); - if (!udev_device) - return NULL; - - r = device_new_from_strv(&udev_device->device, environ); - if (r < 0) { - errno = -r; - udev_device_unref(udev_device); - return NULL; - } - - return udev_device; -} - -static struct udev_device *device_new_from_parent(struct udev_device *child) -{ - struct udev_device *parent; - int r; - - assert_return_errno(child, NULL, EINVAL); - - parent = udev_device_new(child->udev); - if (!parent) - return NULL; - - r = sd_device_get_parent(child->device, &parent->device); - if (r < 0) { - errno = -r; - udev_device_unref(parent); - return NULL; - } - - /* the parent is unref'ed with the child, so take a ref from libudev as well */ - sd_device_ref(parent->device); - - return parent; -} - -/** - * udev_device_get_parent: - * @udev_device: the device to start searching from - * - * Find the next parent device, and fill in information from the sys - * device and the udev database entry. - * - * Returned device is not referenced. It is attached to the child - * device, and will be cleaned up when the child device is cleaned up. - * - * It is not necessarily just the upper level directory, empty or not - * recognized sys directories are ignored. - * - * It can be called as many times as needed, without caring about - * references. - * - * Returns: a new udev device, or #NULL, if it no parent exist. - **/ -_public_ struct udev_device *udev_device_get_parent(struct udev_device *udev_device) -{ - assert_return_errno(udev_device, NULL, EINVAL); - - if (!udev_device->parent_set) { - udev_device->parent_set = true; - udev_device->parent = device_new_from_parent(udev_device); - } - - /* TODO: errno will differ here in case parent == NULL */ - return udev_device->parent; -} - -/** - * udev_device_get_parent_with_subsystem_devtype: - * @udev_device: udev device to start searching from - * @subsystem: the subsystem of the device - * @devtype: the type (DEVTYPE) of the device - * - * Find the next parent device, with a matching subsystem and devtype - * value, and fill in information from the sys device and the udev - * database entry. - * - * If devtype is #NULL, only subsystem is checked, and any devtype will - * match. - * - * Returned device is not referenced. It is attached to the child - * device, and will be cleaned up when the child device is cleaned up. - * - * It can be called as many times as needed, without caring about - * references. - * - * Returns: a new udev device, or #NULL if no matching parent exists. - **/ -_public_ struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device, const char *subsystem, const char *devtype) -{ - sd_device *parent; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - /* this relies on the fact that finding the subdevice of a parent or the - parent of a subdevice commute */ - - /* first find the correct sd_device */ - r = sd_device_get_parent_with_subsystem_devtype(udev_device->device, subsystem, devtype, &parent); - if (r < 0) { - errno = -r; - return NULL; - } - - /* then walk the chain of udev_device parents until the correspanding - one is found */ - while ((udev_device = udev_device_get_parent(udev_device))) { - if (udev_device->device == parent) - return udev_device; - } - - errno = ENOENT; - return NULL; -} - -/** - * udev_device_get_udev: - * @udev_device: udev device - * - * Retrieve the udev library context the device was created with. - * - * Returns: the udev library context - **/ -_public_ struct udev *udev_device_get_udev(struct udev_device *udev_device) -{ - assert_return_errno(udev_device, NULL, EINVAL); - - return udev_device->udev; -} - -/** - * udev_device_ref: - * @udev_device: udev device - * - * Take a reference of a udev device. - * - * Returns: the passed udev device - **/ -_public_ struct udev_device *udev_device_ref(struct udev_device *udev_device) -{ - if (udev_device) - udev_device->refcount++; - - return udev_device; -} - -/** - * udev_device_unref: - * @udev_device: udev device - * - * Drop a reference of a udev device. If the refcount reaches zero, - * the resources of the device will be released. - * - * Returns: #NULL - **/ -_public_ struct udev_device *udev_device_unref(struct udev_device *udev_device) -{ - if (udev_device && (-- udev_device->refcount) == 0) { - sd_device_unref(udev_device->device); - udev_device_unref(udev_device->parent); - - udev_list_cleanup(&udev_device->properties); - udev_list_cleanup(&udev_device->sysattrs); - udev_list_cleanup(&udev_device->tags); - udev_list_cleanup(&udev_device->devlinks); - - free(udev_device); - } - - return NULL; -} - -/** - * udev_device_get_devpath: - * @udev_device: udev device - * - * Retrieve the kernel devpath value of the udev device. The path - * does not contain the sys mount point, and starts with a '/'. - * - * Returns: the devpath of the udev device - **/ -_public_ const char *udev_device_get_devpath(struct udev_device *udev_device) -{ - const char *devpath; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_devpath(udev_device->device, &devpath); - if (r < 0) { - errno = -r; - return NULL; - } - - return devpath; -} - -/** - * udev_device_get_syspath: - * @udev_device: udev device - * - * Retrieve the sys path of the udev device. The path is an - * absolute path and starts with the sys mount point. - * - * Returns: the sys path of the udev device - **/ -_public_ const char *udev_device_get_syspath(struct udev_device *udev_device) -{ - const char *syspath; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_syspath(udev_device->device, &syspath); - if (r < 0) { - errno = -r; - return NULL; - } - - return syspath; -} - -/** - * udev_device_get_sysname: - * @udev_device: udev device - * - * Get the kernel device name in /sys. - * - * Returns: the name string of the device device - **/ -_public_ const char *udev_device_get_sysname(struct udev_device *udev_device) -{ - const char *sysname; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_sysname(udev_device->device, &sysname); - if (r < 0) { - errno = -r; - return NULL; - } - - return sysname; -} - -/** - * udev_device_get_sysnum: - * @udev_device: udev device - * - * Get the instance number of the device. - * - * Returns: the trailing number string of the device name - **/ -_public_ const char *udev_device_get_sysnum(struct udev_device *udev_device) -{ - const char *sysnum; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_sysnum(udev_device->device, &sysnum); - if (r < 0) { - errno = -r; - return NULL; - } - - return sysnum; -} - -/** - * udev_device_get_devnode: - * @udev_device: udev device - * - * Retrieve the device node file name belonging to the udev device. - * The path is an absolute path, and starts with the device directory. - * - * Returns: the device node file name of the udev device, or #NULL if no device node exists - **/ -_public_ const char *udev_device_get_devnode(struct udev_device *udev_device) -{ - const char *devnode; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_devname(udev_device->device, &devnode); - if (r < 0) { - errno = -r; - return NULL; - } - - return devnode; -} - -/** - * udev_device_get_devlinks_list_entry: - * @udev_device: udev device - * - * Retrieve the list of device links pointing to the device file of - * the udev device. The next list entry can be retrieved with - * udev_list_entry_get_next(), which returns #NULL if no more entries exist. - * The devlink path can be retrieved from the list entry by - * udev_list_entry_get_name(). The path is an absolute path, and starts with - * the device directory. - * - * Returns: the first entry of the device node link list - **/ -_public_ struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device) -{ - assert_return_errno(udev_device, NULL, EINVAL); - - if (device_get_devlinks_generation(udev_device->device) != udev_device->devlinks_generation || - !udev_device->devlinks_read) { - const char *devlink; - - udev_list_cleanup(&udev_device->devlinks); - - FOREACH_DEVICE_DEVLINK(udev_device->device, devlink) - udev_list_entry_add(&udev_device->devlinks, devlink, NULL); - - udev_device->devlinks_read = true; - udev_device->devlinks_generation = device_get_devlinks_generation(udev_device->device); - } - - return udev_list_get_entry(&udev_device->devlinks); -} - -/** - * udev_device_get_event_properties_entry: - * @udev_device: udev device - * - * Retrieve the list of key/value device properties of the udev - * device. The next list entry can be retrieved with udev_list_entry_get_next(), - * which returns #NULL if no more entries exist. The property name - * can be retrieved from the list entry by udev_list_entry_get_name(), - * the property value by udev_list_entry_get_value(). - * - * Returns: the first entry of the property list - **/ -_public_ struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device) -{ - assert_return_errno(udev_device, NULL, EINVAL); - - if (device_get_properties_generation(udev_device->device) != udev_device->properties_generation || - !udev_device->properties_read) { - const char *key, *value; - - udev_list_cleanup(&udev_device->properties); - - FOREACH_DEVICE_PROPERTY(udev_device->device, key, value) - udev_list_entry_add(&udev_device->properties, key, value); - - udev_device->properties_read = true; - udev_device->properties_generation = device_get_properties_generation(udev_device->device); - } - - return udev_list_get_entry(&udev_device->properties); -} - -/** - * udev_device_get_action: - * @udev_device: udev device - * - * This is only valid if the device was received through a monitor. Devices read from - * sys do not have an action string. Usual actions are: add, remove, change, online, - * offline. - * - * Returns: the kernel action value, or #NULL if there is no action value available. - **/ -_public_ const char *udev_device_get_action(struct udev_device *udev_device) { - const char *action = NULL; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_property_value(udev_device->device, "ACTION", &action); - if (r < 0 && r != -ENOENT) { - errno = -r; - return NULL; - } - - return action; -} - -/** - * udev_device_get_usec_since_initialized: - * @udev_device: udev device - * - * Return the number of microseconds passed since udev set up the - * device for the first time. - * - * This is only implemented for devices with need to store properties - * in the udev database. All other devices return 0 here. - * - * Returns: the number of microseconds since the device was first seen. - **/ -_public_ unsigned long long int udev_device_get_usec_since_initialized(struct udev_device *udev_device) -{ - usec_t ts; - int r; - - assert_return(udev_device, -EINVAL); - - r = sd_device_get_usec_since_initialized(udev_device->device, &ts); - if (r < 0) { - errno = EINVAL; - return 0; - } - - return ts; -} - -/** - * udev_device_get_sysattr_value: - * @udev_device: udev device - * @sysattr: attribute name - * - * The retrieved value is cached in the device. Repeated calls will return the same - * value and not open the attribute again. - * - * Returns: the content of a sys attribute file, or #NULL if there is no sys attribute value. - **/ -_public_ const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr) -{ - const char *value; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_sysattr_value(udev_device->device, sysattr, &value); - if (r < 0) { - errno = -r; - return NULL; - } - - return value; -} - -/** - * udev_device_set_sysattr_value: - * @udev_device: udev device - * @sysattr: attribute name - * @value: new value to be set - * - * Update the contents of the sys attribute and the cached value of the device. - * - * Returns: Negative error code on failure or 0 on success. - **/ -_public_ int udev_device_set_sysattr_value(struct udev_device *udev_device, const char *sysattr, char *value) -{ - int r; - - assert_return(udev_device, -EINVAL); - - r = sd_device_set_sysattr_value(udev_device->device, sysattr, value); - if (r < 0) - return r; - - return 0; -} - -/** - * udev_device_get_sysattr_list_entry: - * @udev_device: udev device - * - * Retrieve the list of available sysattrs, with value being empty; - * This just return all available sysfs attributes for a particular - * device without reading their values. - * - * Returns: the first entry of the property list - **/ -_public_ struct udev_list_entry *udev_device_get_sysattr_list_entry(struct udev_device *udev_device) -{ - assert_return_errno(udev_device, NULL, EINVAL); - - if (!udev_device->sysattrs_read) { - const char *sysattr; - - udev_list_cleanup(&udev_device->sysattrs); - - FOREACH_DEVICE_SYSATTR(udev_device->device, sysattr) - udev_list_entry_add(&udev_device->sysattrs, sysattr, NULL); - - udev_device->sysattrs_read = true; - } - - return udev_list_get_entry(&udev_device->sysattrs); -} - -/** - * udev_device_get_is_initialized: - * @udev_device: udev device - * - * Check if udev has already handled the device and has set up - * device node permissions and context, or has renamed a network - * device. - * - * This is only implemented for devices with a device node - * or network interfaces. All other devices return 1 here. - * - * Returns: 1 if the device is set up. 0 otherwise. - **/ -_public_ int udev_device_get_is_initialized(struct udev_device *udev_device) -{ - int r, initialized; - - assert_return(udev_device, -EINVAL); - - r = sd_device_get_is_initialized(udev_device->device, &initialized); - if (r < 0) { - errno = -r; - - return 0; - } - - return initialized; -} - -/** - * udev_device_get_tags_list_entry: - * @udev_device: udev device - * - * Retrieve the list of tags attached to the udev device. The next - * list entry can be retrieved with udev_list_entry_get_next(), - * which returns #NULL if no more entries exist. The tag string - * can be retrieved from the list entry by udev_list_entry_get_name(). - * - * Returns: the first entry of the tag list - **/ -_public_ struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device) -{ - assert_return_errno(udev_device, NULL, EINVAL); - - if (device_get_tags_generation(udev_device->device) != udev_device->tags_generation || - !udev_device->tags_read) { - const char *tag; - - udev_list_cleanup(&udev_device->tags); - - FOREACH_DEVICE_TAG(udev_device->device, tag) - udev_list_entry_add(&udev_device->tags, tag, NULL); - - udev_device->tags_read = true; - udev_device->tags_generation = device_get_tags_generation(udev_device->device); - } - - return udev_list_get_entry(&udev_device->tags); -} - -/** - * udev_device_has_tag: - * @udev_device: udev device - * @tag: tag name - * - * Check if a given device has a certain tag associated. - * - * Returns: 1 if the tag is found. 0 otherwise. - **/ -_public_ int udev_device_has_tag(struct udev_device *udev_device, const char *tag) -{ - assert_return(udev_device, 0); - - return sd_device_has_tag(udev_device->device, tag); -} diff --git a/src/libudev/libudev-enumerate.c b/src/libudev/libudev-enumerate.c deleted file mode 100644 index 3b8abfb260..0000000000 --- a/src/libudev/libudev-enumerate.c +++ /dev/null @@ -1,419 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2015 Tom Gundersen - - 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 -#include -#include -#include -#include -#include -#include -#include - -#include "libudev.h" -#include "sd-device.h" - -#include "alloc-util.h" -#include "device-enumerator-private.h" -#include "device-util.h" -#include "libudev-device-internal.h" - -/** - * SECTION:libudev-enumerate - * @short_description: lookup and sort sys devices - * - * Lookup devices in the sys filesystem, filter devices by properties, - * and return a sorted list of devices. - */ - -/** - * udev_enumerate: - * - * Opaque object representing one device lookup/sort context. - */ -struct udev_enumerate { - struct udev *udev; - int refcount; - struct udev_list devices_list; - bool devices_uptodate:1; - - sd_device_enumerator *enumerator; -}; - -/** - * udev_enumerate_new: - * @udev: udev library context - * - * Create an enumeration context to scan /sys. - * - * Returns: an enumeration context. - **/ -_public_ struct udev_enumerate *udev_enumerate_new(struct udev *udev) { - _cleanup_free_ struct udev_enumerate *udev_enumerate = NULL; - struct udev_enumerate *ret; - int r; - - assert_return_errno(udev, NULL, EINVAL); - - udev_enumerate = new0(struct udev_enumerate, 1); - if (!udev_enumerate) { - errno = ENOMEM; - return NULL; - } - - r = sd_device_enumerator_new(&udev_enumerate->enumerator); - if (r < 0) { - errno = -r; - return NULL; - } - - r = sd_device_enumerator_allow_uninitialized(udev_enumerate->enumerator); - if (r < 0) { - errno = -r; - return NULL; - } - - udev_enumerate->refcount = 1; - udev_enumerate->udev = udev; - - udev_list_init(udev, &udev_enumerate->devices_list, false); - - ret = udev_enumerate; - udev_enumerate = NULL; - - return ret; -} - -/** - * udev_enumerate_ref: - * @udev_enumerate: context - * - * Take a reference of a enumeration context. - * - * Returns: the passed enumeration context - **/ -_public_ struct udev_enumerate *udev_enumerate_ref(struct udev_enumerate *udev_enumerate) { - if (udev_enumerate) - udev_enumerate->refcount++; - - return udev_enumerate; -} - -/** - * udev_enumerate_unref: - * @udev_enumerate: context - * - * Drop a reference of an enumeration context. If the refcount reaches zero, - * all resources of the enumeration context will be released. - * - * Returns: #NULL - **/ -_public_ struct udev_enumerate *udev_enumerate_unref(struct udev_enumerate *udev_enumerate) { - if (udev_enumerate && (-- udev_enumerate->refcount) == 0) { - udev_list_cleanup(&udev_enumerate->devices_list); - sd_device_enumerator_unref(udev_enumerate->enumerator); - free(udev_enumerate); - } - - return NULL; -} - -/** - * udev_enumerate_get_udev: - * @udev_enumerate: context - * - * Get the udev library context. - * - * Returns: a pointer to the context. - */ -_public_ struct udev *udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate) { - assert_return_errno(udev_enumerate, NULL, EINVAL); - - return udev_enumerate->udev; -} - -/** - * udev_enumerate_get_list_entry: - * @udev_enumerate: context - * - * Get the first entry of the sorted list of device paths. - * - * Returns: a udev_list_entry. - */ -_public_ struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate) { - assert_return_errno(udev_enumerate, NULL, EINVAL); - - if (!udev_enumerate->devices_uptodate) { - sd_device *device; - - udev_list_cleanup(&udev_enumerate->devices_list); - - FOREACH_DEVICE_AND_SUBSYSTEM(udev_enumerate->enumerator, device) { - const char *syspath; - int r; - - r = sd_device_get_syspath(device, &syspath); - if (r < 0) { - errno = -r; - return NULL; - } - - udev_list_entry_add(&udev_enumerate->devices_list, syspath, NULL); - } - - udev_enumerate->devices_uptodate = true; - } - - return udev_list_get_entry(&udev_enumerate->devices_list); -} - -/** - * udev_enumerate_add_match_subsystem: - * @udev_enumerate: context - * @subsystem: filter for a subsystem of the device to include in the list - * - * Match only devices belonging to a certain kernel subsystem. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem) { - assert_return(udev_enumerate, -EINVAL); - - if (!subsystem) - return 0; - - return sd_device_enumerator_add_match_subsystem(udev_enumerate->enumerator, subsystem, true); -} - -/** - * udev_enumerate_add_nomatch_subsystem: - * @udev_enumerate: context - * @subsystem: filter for a subsystem of the device to exclude from the list - * - * Match only devices not belonging to a certain kernel subsystem. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem) { - assert_return(udev_enumerate, -EINVAL); - - if (!subsystem) - return 0; - - return sd_device_enumerator_add_match_subsystem(udev_enumerate->enumerator, subsystem, false); -} - -/** - * udev_enumerate_add_match_sysattr: - * @udev_enumerate: context - * @sysattr: filter for a sys attribute at the device to include in the list - * @value: optional value of the sys attribute - * - * Match only devices with a certain /sys device attribute. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value) { - assert_return(udev_enumerate, -EINVAL); - - if (!sysattr) - return 0; - - return sd_device_enumerator_add_match_sysattr(udev_enumerate->enumerator, sysattr, value, true); -} - -/** - * udev_enumerate_add_nomatch_sysattr: - * @udev_enumerate: context - * @sysattr: filter for a sys attribute at the device to exclude from the list - * @value: optional value of the sys attribute - * - * Match only devices not having a certain /sys device attribute. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value) { - assert_return(udev_enumerate, -EINVAL); - - if (!sysattr) - return 0; - - return sd_device_enumerator_add_match_sysattr(udev_enumerate->enumerator, sysattr, value, false); -} - -/** - * udev_enumerate_add_match_property: - * @udev_enumerate: context - * @property: filter for a property of the device to include in the list - * @value: value of the property - * - * Match only devices with a certain property. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value) { - assert_return(udev_enumerate, -EINVAL); - - if (!property) - return 0; - - return sd_device_enumerator_add_match_property(udev_enumerate->enumerator, property, value); -} - -/** - * udev_enumerate_add_match_tag: - * @udev_enumerate: context - * @tag: filter for a tag of the device to include in the list - * - * Match only devices with a certain tag. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag) { - assert_return(udev_enumerate, -EINVAL); - - if (!tag) - return 0; - - return sd_device_enumerator_add_match_tag(udev_enumerate->enumerator, tag); -} - -/** - * udev_enumerate_add_match_parent: - * @udev_enumerate: context - * @parent: parent device where to start searching - * - * Return the devices on the subtree of one given device. The parent - * itself is included in the list. - * - * A reference for the device is held until the udev_enumerate context - * is cleaned up. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_match_parent(struct udev_enumerate *udev_enumerate, struct udev_device *parent) { - assert_return(udev_enumerate, -EINVAL); - - if (!parent) - return 0; - - return sd_device_enumerator_add_match_parent(udev_enumerate->enumerator, parent->device); -} - -/** - * udev_enumerate_add_match_is_initialized: - * @udev_enumerate: context - * - * Match only devices which udev has set up already. This makes - * sure, that the device node permissions and context are properly set - * and that network devices are fully renamed. - * - * Usually, devices which are found in the kernel but not already - * handled by udev, have still pending events. Services should subscribe - * to monitor events and wait for these devices to become ready, instead - * of using uninitialized devices. - * - * For now, this will not affect devices which do not have a device node - * and are not network interfaces. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_match_is_initialized(struct udev_enumerate *udev_enumerate) { - assert_return(udev_enumerate, -EINVAL); - - return device_enumerator_add_match_is_initialized(udev_enumerate->enumerator); -} - -/** - * udev_enumerate_add_match_sysname: - * @udev_enumerate: context - * @sysname: filter for the name of the device to include in the list - * - * Match only devices with a given /sys device name. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname) { - assert_return(udev_enumerate, -EINVAL); - - if (!sysname) - return 0; - - return sd_device_enumerator_add_match_sysname(udev_enumerate->enumerator, sysname); -} - -/** - * udev_enumerate_add_syspath: - * @udev_enumerate: context - * @syspath: path of a device - * - * Add a device to the list of devices, to retrieve it back sorted in dependency order. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - int r; - - assert_return(udev_enumerate, -EINVAL); - - if (!syspath) - return 0; - - r = sd_device_new_from_syspath(&device, syspath); - if (r < 0) - return r; - - r = device_enumerator_add_device(udev_enumerate->enumerator, device); - if (r < 0) - return r; - - return 0; -} - -/** - * udev_enumerate_scan_devices: - * @udev_enumerate: udev enumeration context - * - * Scan /sys for all devices which match the given filters. No matches - * will return all currently available devices. - * - * Returns: 0 on success, otherwise a negative error value. - **/ -_public_ int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate) { - assert_return(udev_enumerate, -EINVAL); - - return device_enumerator_scan_devices(udev_enumerate->enumerator); -} - -/** - * udev_enumerate_scan_subsystems: - * @udev_enumerate: udev enumeration context - * - * Scan /sys for all kernel subsystems, including buses, classes, drivers. - * - * Returns: 0 on success, otherwise a negative error value. - **/ -_public_ int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate) { - assert_return(udev_enumerate, -EINVAL); - - return device_enumerator_scan_subsystems(udev_enumerate->enumerator); -} diff --git a/src/libudev/libudev-hwdb.c b/src/libudev/libudev-hwdb.c deleted file mode 100644 index a53f000015..0000000000 --- a/src/libudev/libudev-hwdb.c +++ /dev/null @@ -1,146 +0,0 @@ -/*** - This file is part of systemd. - - Copyright Tom Gundersen - - 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 "sd-hwdb.h" - -#include "alloc-util.h" -#include "hwdb-util.h" -#include "libudev-private.h" - -/** - * SECTION:libudev-hwdb - * @short_description: retrieve properties from the hardware database - * - * Libudev hardware database interface. - */ - -/** - * udev_hwdb: - * - * Opaque object representing the hardware database. - */ -struct udev_hwdb { - struct udev *udev; - int refcount; - - sd_hwdb *hwdb; - - struct udev_list properties_list; -}; - -/** - * udev_hwdb_new: - * @udev: udev library context - * - * Create a hardware database context to query properties for devices. - * - * Returns: a hwdb context. - **/ -_public_ struct udev_hwdb *udev_hwdb_new(struct udev *udev) { - _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb_internal = NULL; - struct udev_hwdb *hwdb; - int r; - - assert_return(udev, NULL); - - r = sd_hwdb_new(&hwdb_internal); - if (r < 0) - return NULL; - - hwdb = new0(struct udev_hwdb, 1); - if (!hwdb) - return NULL; - - hwdb->refcount = 1; - hwdb->hwdb = hwdb_internal; - hwdb_internal = NULL; - - udev_list_init(udev, &hwdb->properties_list, true); - - return hwdb; -} - -/** - * udev_hwdb_ref: - * @hwdb: context - * - * Take a reference of a hwdb context. - * - * Returns: the passed enumeration context - **/ -_public_ struct udev_hwdb *udev_hwdb_ref(struct udev_hwdb *hwdb) { - if (!hwdb) - return NULL; - hwdb->refcount++; - return hwdb; -} - -/** - * udev_hwdb_unref: - * @hwdb: context - * - * Drop a reference of a hwdb context. If the refcount reaches zero, - * all resources of the hwdb context will be released. - * - * Returns: #NULL - **/ -_public_ struct udev_hwdb *udev_hwdb_unref(struct udev_hwdb *hwdb) { - if (!hwdb) - return NULL; - hwdb->refcount--; - if (hwdb->refcount > 0) - return NULL; - sd_hwdb_unref(hwdb->hwdb); - udev_list_cleanup(&hwdb->properties_list); - free(hwdb); - return NULL; -} - -/** - * udev_hwdb_get_properties_list_entry: - * @hwdb: context - * @modalias: modalias string - * @flags: (unused) - * - * Lookup a matching device in the hardware database. The lookup key is a - * modalias string, whose formats are defined for the Linux kernel modules. - * Examples are: pci:v00008086d00001C2D*, usb:v04F2pB221*. The first entry - * of a list of retrieved properties is returned. - * - * Returns: a udev_list_entry. - */ -_public_ struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned int flags) { - const char *key, *value; - - if (!hwdb || !modalias) { - errno = EINVAL; - return NULL; - } - - udev_list_cleanup(&hwdb->properties_list); - - SD_HWDB_FOREACH_PROPERTY(hwdb->hwdb, modalias, key, value) { - if (udev_list_entry_add(&hwdb->properties_list, key, value) == NULL) { - errno = ENOMEM; - return NULL; - } - } - - return udev_list_get_entry(&hwdb->properties_list); -} diff --git a/src/libudev/libudev-list.c b/src/libudev/libudev-list.c deleted file mode 100644 index da496ed456..0000000000 --- a/src/libudev/libudev-list.c +++ /dev/null @@ -1,352 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 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 -#include -#include -#include - -#include "alloc-util.h" -#include "libudev-private.h" - -/** - * SECTION:libudev-list - * @short_description: list operation - * - * Libudev list operations. - */ - -/** - * udev_list_entry: - * - * Opaque object representing one entry in a list. An entry contains - * contains a name, and optionally a value. - */ -struct udev_list_entry { - struct udev_list_node node; - struct udev_list *list; - char *name; - char *value; - int num; -}; - -/* the list's head points to itself if empty */ -void udev_list_node_init(struct udev_list_node *list) -{ - list->next = list; - list->prev = list; -} - -int udev_list_node_is_empty(struct udev_list_node *list) -{ - return list->next == list; -} - -static void udev_list_node_insert_between(struct udev_list_node *new, - struct udev_list_node *prev, - struct udev_list_node *next) -{ - next->prev = new; - new->next = next; - new->prev = prev; - prev->next = new; -} - -void udev_list_node_append(struct udev_list_node *new, struct udev_list_node *list) -{ - udev_list_node_insert_between(new, list->prev, list); -} - -void udev_list_node_remove(struct udev_list_node *entry) -{ - struct udev_list_node *prev = entry->prev; - struct udev_list_node *next = entry->next; - - next->prev = prev; - prev->next = next; - - entry->prev = NULL; - entry->next = NULL; -} - -/* return list entry which embeds this node */ -static inline struct udev_list_entry *list_node_to_entry(struct udev_list_node *node) -{ - return container_of(node, struct udev_list_entry, node); -} - -void udev_list_init(struct udev *udev, struct udev_list *list, bool unique) -{ - memzero(list, sizeof(struct udev_list)); - list->udev = udev; - list->unique = unique; - udev_list_node_init(&list->node); -} - -/* insert entry into a list as the last element */ -static void udev_list_entry_append(struct udev_list_entry *new, struct udev_list *list) -{ - /* inserting before the list head make the node the last node in the list */ - udev_list_node_insert_between(&new->node, list->node.prev, &list->node); - new->list = list; -} - -/* insert entry into a list, before a given existing entry */ -static void udev_list_entry_insert_before(struct udev_list_entry *new, struct udev_list_entry *entry) -{ - udev_list_node_insert_between(&new->node, entry->node.prev, &entry->node); - new->list = entry->list; -} - -/* binary search in sorted array */ -static int list_search(struct udev_list *list, const char *name) -{ - unsigned int first, last; - - first = 0; - last = list->entries_cur; - while (first < last) { - unsigned int i; - int cmp; - - i = (first + last)/2; - cmp = strcmp(name, list->entries[i]->name); - if (cmp < 0) - last = i; - else if (cmp > 0) - first = i+1; - else - return i; - } - - /* not found, return negative insertion-index+1 */ - return -(first+1); -} - -struct udev_list_entry *udev_list_entry_add(struct udev_list *list, const char *name, const char *value) -{ - struct udev_list_entry *entry; - int i = 0; - - if (list->unique) { - /* lookup existing name or insertion-index */ - i = list_search(list, name); - if (i >= 0) { - entry = list->entries[i]; - - free(entry->value); - if (value == NULL) { - entry->value = NULL; - return entry; - } - entry->value = strdup(value); - if (entry->value == NULL) - return NULL; - return entry; - } - } - - /* add new name */ - entry = new0(struct udev_list_entry, 1); - if (entry == NULL) - return NULL; - entry->name = strdup(name); - if (entry->name == NULL) { - free(entry); - return NULL; - } - if (value != NULL) { - entry->value = strdup(value); - if (entry->value == NULL) { - free(entry->name); - free(entry); - return NULL; - } - } - - if (list->unique) { - /* allocate or enlarge sorted array if needed */ - if (list->entries_cur >= list->entries_max) { - struct udev_list_entry **entries; - unsigned int add; - - add = list->entries_max; - if (add < 1) - add = 64; - entries = realloc(list->entries, (list->entries_max + add) * sizeof(struct udev_list_entry *)); - if (entries == NULL) { - free(entry->name); - free(entry->value); - free(entry); - return NULL; - } - list->entries = entries; - list->entries_max += add; - } - - /* the negative i returned the insertion index */ - i = (-i)-1; - - /* insert into sorted list */ - if ((unsigned int)i < list->entries_cur) - udev_list_entry_insert_before(entry, list->entries[i]); - else - udev_list_entry_append(entry, list); - - /* insert into sorted array */ - memmove(&list->entries[i+1], &list->entries[i], - (list->entries_cur - i) * sizeof(struct udev_list_entry *)); - list->entries[i] = entry; - list->entries_cur++; - } else { - udev_list_entry_append(entry, list); - } - - return entry; -} - -void udev_list_entry_delete(struct udev_list_entry *entry) -{ - if (entry->list->entries != NULL) { - int i; - struct udev_list *list = entry->list; - - /* remove entry from sorted array */ - i = list_search(list, entry->name); - if (i >= 0) { - memmove(&list->entries[i], &list->entries[i+1], - ((list->entries_cur-1) - i) * sizeof(struct udev_list_entry *)); - list->entries_cur--; - } - } - - udev_list_node_remove(&entry->node); - free(entry->name); - free(entry->value); - free(entry); -} - -void udev_list_cleanup(struct udev_list *list) -{ - struct udev_list_entry *entry_loop; - struct udev_list_entry *entry_tmp; - - list->entries = mfree(list->entries); - list->entries_cur = 0; - list->entries_max = 0; - udev_list_entry_foreach_safe(entry_loop, entry_tmp, udev_list_get_entry(list)) - udev_list_entry_delete(entry_loop); -} - -struct udev_list_entry *udev_list_get_entry(struct udev_list *list) -{ - if (udev_list_node_is_empty(&list->node)) - return NULL; - return list_node_to_entry(list->node.next); -} - -/** - * udev_list_entry_get_next: - * @list_entry: current entry - * - * Get the next entry from the list. - * - * Returns: udev_list_entry, #NULL if no more entries are available. - */ -_public_ struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry) -{ - struct udev_list_node *next; - - if (list_entry == NULL) - return NULL; - next = list_entry->node.next; - /* empty list or no more entries */ - if (next == &list_entry->list->node) - return NULL; - return list_node_to_entry(next); -} - -/** - * udev_list_entry_get_by_name: - * @list_entry: current entry - * @name: name string to match - * - * Lookup an entry in the list with a certain name. - * - * Returns: udev_list_entry, #NULL if no matching entry is found. - */ -_public_ struct udev_list_entry *udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name) -{ - int i; - - if (list_entry == NULL) - return NULL; - - if (!list_entry->list->unique) - return NULL; - - i = list_search(list_entry->list, name); - if (i < 0) - return NULL; - return list_entry->list->entries[i]; -} - -/** - * udev_list_entry_get_name: - * @list_entry: current entry - * - * Get the name of a list entry. - * - * Returns: the name string of this entry. - */ -_public_ const char *udev_list_entry_get_name(struct udev_list_entry *list_entry) -{ - if (list_entry == NULL) - return NULL; - return list_entry->name; -} - -/** - * udev_list_entry_get_value: - * @list_entry: current entry - * - * Get the value of list entry. - * - * Returns: the value string of this entry. - */ -_public_ const char *udev_list_entry_get_value(struct udev_list_entry *list_entry) -{ - if (list_entry == NULL) - return NULL; - return list_entry->value; -} - -int udev_list_entry_get_num(struct udev_list_entry *list_entry) -{ - if (list_entry == NULL) - return -EINVAL; - return list_entry->num; -} - -void udev_list_entry_set_num(struct udev_list_entry *list_entry, int num) -{ - if (list_entry == NULL) - return; - list_entry->num = num; -} diff --git a/src/libudev/libudev-monitor.c b/src/libudev/libudev-monitor.c deleted file mode 100644 index f870eba9eb..0000000000 --- a/src/libudev/libudev-monitor.c +++ /dev/null @@ -1,845 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "libudev.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "libudev-private.h" -#include "missing.h" -#include "mount-util.h" -#include "socket-util.h" -#include "string-util.h" - -/** - * SECTION:libudev-monitor - * @short_description: device event source - * - * Connects to a device event source. - */ - -/** - * udev_monitor: - * - * Opaque object handling an event source. - */ -struct udev_monitor { - struct udev *udev; - int refcount; - int sock; - union sockaddr_union snl; - union sockaddr_union snl_trusted_sender; - union sockaddr_union snl_destination; - socklen_t addrlen; - struct udev_list filter_subsystem_list; - struct udev_list filter_tag_list; - bool bound; -}; - -enum udev_monitor_netlink_group { - UDEV_MONITOR_NONE, - UDEV_MONITOR_KERNEL, - UDEV_MONITOR_UDEV, -}; - -#define UDEV_MONITOR_MAGIC 0xfeedcafe -struct udev_monitor_netlink_header { - /* "libudev" prefix to distinguish libudev and kernel messages */ - char prefix[8]; - /* - * magic to protect against daemon <-> library message format mismatch - * used in the kernel from socket filter rules; needs to be stored in network order - */ - unsigned int magic; - /* total length of header structure known to the sender */ - unsigned int header_size; - /* properties string buffer */ - unsigned int properties_off; - unsigned int properties_len; - /* - * hashes of primary device properties strings, to let libudev subscribers - * use in-kernel socket filters; values need to be stored in network order - */ - unsigned int filter_subsystem_hash; - unsigned int filter_devtype_hash; - unsigned int filter_tag_bloom_hi; - unsigned int filter_tag_bloom_lo; -}; - -static struct udev_monitor *udev_monitor_new(struct udev *udev) -{ - struct udev_monitor *udev_monitor; - - udev_monitor = new0(struct udev_monitor, 1); - if (udev_monitor == NULL) - return NULL; - udev_monitor->refcount = 1; - udev_monitor->udev = udev; - udev_list_init(udev, &udev_monitor->filter_subsystem_list, false); - udev_list_init(udev, &udev_monitor->filter_tag_list, true); - return udev_monitor; -} - -/* we consider udev running when /dev is on devtmpfs */ -static bool udev_has_devtmpfs(struct udev *udev) { - - union file_handle_union h = FILE_HANDLE_INIT; - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX], *e; - int mount_id; - int r; - - r = name_to_handle_at(AT_FDCWD, "/dev", &h.handle, &mount_id, 0); - if (r < 0) { - if (errno != EOPNOTSUPP) - log_debug_errno(errno, "name_to_handle_at on /dev: %m"); - return false; - } - - f = fopen("/proc/self/mountinfo", "re"); - if (!f) - return false; - - FOREACH_LINE(line, f, return false) { - int mid; - - if (sscanf(line, "%i", &mid) != 1) - continue; - - if (mid != mount_id) - continue; - - e = strstr(line, " - "); - if (!e) - continue; - - /* accept any name that starts with the currently expected type */ - if (startswith(e + 3, "devtmpfs")) - return true; - } - - return false; -} - -static void monitor_set_nl_address(struct udev_monitor *udev_monitor) { - union sockaddr_union snl; - socklen_t addrlen; - int r; - - assert(udev_monitor); - - /* get the address the kernel has assigned us - * it is usually, but not necessarily the pid - */ - addrlen = sizeof(struct sockaddr_nl); - r = getsockname(udev_monitor->sock, &snl.sa, &addrlen); - if (r >= 0) - udev_monitor->snl.nl.nl_pid = snl.nl.nl_pid; -} - -struct udev_monitor *udev_monitor_new_from_netlink_fd(struct udev *udev, const char *name, int fd) -{ - struct udev_monitor *udev_monitor; - unsigned int group; - - if (udev == NULL) - return NULL; - - if (name == NULL) - group = UDEV_MONITOR_NONE; - else if (streq(name, "udev")) { - /* - * We do not support subscribing to uevents if no instance of - * udev is running. Uevents would otherwise broadcast the - * processing data of the host into containers, which is not - * desired. - * - * Containers will currently not get any udev uevents, until - * a supporting infrastructure is available. - * - * We do not set a netlink multicast group here, so the socket - * will not receive any messages. - */ - if (access("/run/udev/control", F_OK) < 0 && !udev_has_devtmpfs(udev)) { - log_debug("the udev service seems not to be active, disable the monitor"); - group = UDEV_MONITOR_NONE; - } else - group = UDEV_MONITOR_UDEV; - } else if (streq(name, "kernel")) - group = UDEV_MONITOR_KERNEL; - else - return NULL; - - udev_monitor = udev_monitor_new(udev); - if (udev_monitor == NULL) - return NULL; - - if (fd < 0) { - udev_monitor->sock = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_KOBJECT_UEVENT); - if (udev_monitor->sock < 0) { - log_debug_errno(errno, "error getting socket: %m"); - free(udev_monitor); - return NULL; - } - } else { - udev_monitor->bound = true; - udev_monitor->sock = fd; - monitor_set_nl_address(udev_monitor); - } - - udev_monitor->snl.nl.nl_family = AF_NETLINK; - udev_monitor->snl.nl.nl_groups = group; - - /* default destination for sending */ - udev_monitor->snl_destination.nl.nl_family = AF_NETLINK; - udev_monitor->snl_destination.nl.nl_groups = UDEV_MONITOR_UDEV; - - return udev_monitor; -} - -/** - * udev_monitor_new_from_netlink: - * @udev: udev library context - * @name: name of event source - * - * Create new udev monitor and connect to a specified event - * source. Valid sources identifiers are "udev" and "kernel". - * - * Applications should usually not connect directly to the - * "kernel" events, because the devices might not be useable - * at that time, before udev has configured them, and created - * device nodes. Accessing devices at the same time as udev, - * might result in unpredictable behavior. The "udev" events - * are sent out after udev has finished its event processing, - * all rules have been processed, and needed device nodes are - * created. - * - * The initial refcount is 1, and needs to be decremented to - * release the resources of the udev monitor. - * - * Returns: a new udev monitor, or #NULL, in case of an error - **/ -_public_ struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name) -{ - return udev_monitor_new_from_netlink_fd(udev, name, -1); -} - -static inline void bpf_stmt(struct sock_filter *inss, unsigned int *i, - unsigned short code, unsigned int data) -{ - struct sock_filter *ins = &inss[*i]; - - ins->code = code; - ins->k = data; - (*i)++; -} - -static inline void bpf_jmp(struct sock_filter *inss, unsigned int *i, - unsigned short code, unsigned int data, - unsigned short jt, unsigned short jf) -{ - struct sock_filter *ins = &inss[*i]; - - ins->code = code; - ins->jt = jt; - ins->jf = jf; - ins->k = data; - (*i)++; -} - -/** - * udev_monitor_filter_update: - * @udev_monitor: monitor - * - * Update the installed socket filter. This is only needed, - * if the filter was removed or changed. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_monitor_filter_update(struct udev_monitor *udev_monitor) -{ - struct sock_filter ins[512]; - struct sock_fprog filter; - unsigned int i; - struct udev_list_entry *list_entry; - int err; - - if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) == NULL && - udev_list_get_entry(&udev_monitor->filter_tag_list) == NULL) - return 0; - - memzero(ins, sizeof(ins)); - i = 0; - - /* load magic in A */ - bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, magic)); - /* jump if magic matches */ - bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, UDEV_MONITOR_MAGIC, 1, 0); - /* wrong magic, pass packet */ - bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); - - if (udev_list_get_entry(&udev_monitor->filter_tag_list) != NULL) { - int tag_matches; - - /* count tag matches, to calculate end of tag match block */ - tag_matches = 0; - udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_tag_list)) - tag_matches++; - - /* add all tags matches */ - udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_tag_list)) { - uint64_t tag_bloom_bits = util_string_bloom64(udev_list_entry_get_name(list_entry)); - uint32_t tag_bloom_hi = tag_bloom_bits >> 32; - uint32_t tag_bloom_lo = tag_bloom_bits & 0xffffffff; - - /* load device bloom bits in A */ - bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_tag_bloom_hi)); - /* clear bits (tag bits & bloom bits) */ - bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_hi); - /* jump to next tag if it does not match */ - bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_hi, 0, 3); - - /* load device bloom bits in A */ - bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_tag_bloom_lo)); - /* clear bits (tag bits & bloom bits) */ - bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_lo); - /* jump behind end of tag match block if tag matches */ - tag_matches--; - bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_lo, 1 + (tag_matches * 6), 0); - } - - /* nothing matched, drop packet */ - bpf_stmt(ins, &i, BPF_RET|BPF_K, 0); - } - - /* add all subsystem matches */ - if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) != NULL) { - udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_subsystem_list)) { - unsigned int hash = util_string_hash32(udev_list_entry_get_name(list_entry)); - - /* load device subsystem value in A */ - bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_subsystem_hash)); - if (udev_list_entry_get_value(list_entry) == NULL) { - /* jump if subsystem does not match */ - bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1); - } else { - /* jump if subsystem does not match */ - bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 3); - - /* load device devtype value in A */ - bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_devtype_hash)); - /* jump if value does not match */ - hash = util_string_hash32(udev_list_entry_get_value(list_entry)); - bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1); - } - - /* matched, pass packet */ - bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); - - if (i+1 >= ELEMENTSOF(ins)) - return -E2BIG; - } - - /* nothing matched, drop packet */ - bpf_stmt(ins, &i, BPF_RET|BPF_K, 0); - } - - /* matched, pass packet */ - bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); - - /* install filter */ - memzero(&filter, sizeof(filter)); - filter.len = i; - filter.filter = ins; - err = setsockopt(udev_monitor->sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)); - return err < 0 ? -errno : 0; -} - -int udev_monitor_allow_unicast_sender(struct udev_monitor *udev_monitor, struct udev_monitor *sender) -{ - udev_monitor->snl_trusted_sender.nl.nl_pid = sender->snl.nl.nl_pid; - return 0; -} - -/** - * udev_monitor_enable_receiving: - * @udev_monitor: the monitor which should receive events - * - * Binds the @udev_monitor socket to the event source. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor) -{ - int err = 0; - const int on = 1; - - udev_monitor_filter_update(udev_monitor); - - if (!udev_monitor->bound) { - err = bind(udev_monitor->sock, - &udev_monitor->snl.sa, sizeof(struct sockaddr_nl)); - if (err == 0) - udev_monitor->bound = true; - } - - if (err >= 0) - monitor_set_nl_address(udev_monitor); - else - return log_debug_errno(errno, "bind failed: %m"); - - /* enable receiving of sender credentials */ - err = setsockopt(udev_monitor->sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); - if (err < 0) - log_debug_errno(errno, "setting SO_PASSCRED failed: %m"); - - return 0; -} - -/** - * udev_monitor_set_receive_buffer_size: - * @udev_monitor: the monitor which should receive events - * @size: the size in bytes - * - * Set the size of the kernel socket buffer. This call needs the - * appropriate privileges to succeed. - * - * Returns: 0 on success, otherwise -1 on error. - */ -_public_ int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size) -{ - if (udev_monitor == NULL) - return -EINVAL; - return setsockopt(udev_monitor->sock, SOL_SOCKET, SO_RCVBUFFORCE, &size, sizeof(size)); -} - -int udev_monitor_disconnect(struct udev_monitor *udev_monitor) -{ - int err; - - err = close(udev_monitor->sock); - udev_monitor->sock = -1; - return err < 0 ? -errno : 0; -} - -/** - * udev_monitor_ref: - * @udev_monitor: udev monitor - * - * Take a reference of a udev monitor. - * - * Returns: the passed udev monitor - **/ -_public_ struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor) -{ - if (udev_monitor == NULL) - return NULL; - udev_monitor->refcount++; - return udev_monitor; -} - -/** - * udev_monitor_unref: - * @udev_monitor: udev monitor - * - * Drop a reference of a udev monitor. If the refcount reaches zero, - * the bound socket will be closed, and the resources of the monitor - * will be released. - * - * Returns: #NULL - **/ -_public_ struct udev_monitor *udev_monitor_unref(struct udev_monitor *udev_monitor) -{ - if (udev_monitor == NULL) - return NULL; - udev_monitor->refcount--; - if (udev_monitor->refcount > 0) - return NULL; - if (udev_monitor->sock >= 0) - close(udev_monitor->sock); - udev_list_cleanup(&udev_monitor->filter_subsystem_list); - udev_list_cleanup(&udev_monitor->filter_tag_list); - free(udev_monitor); - return NULL; -} - -/** - * udev_monitor_get_udev: - * @udev_monitor: udev monitor - * - * Retrieve the udev library context the monitor was created with. - * - * Returns: the udev library context - **/ -_public_ struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor) -{ - if (udev_monitor == NULL) - return NULL; - return udev_monitor->udev; -} - -/** - * udev_monitor_get_fd: - * @udev_monitor: udev monitor - * - * Retrieve the socket file descriptor associated with the monitor. - * - * Returns: the socket file descriptor - **/ -_public_ int udev_monitor_get_fd(struct udev_monitor *udev_monitor) -{ - if (udev_monitor == NULL) - return -EINVAL; - return udev_monitor->sock; -} - -static int passes_filter(struct udev_monitor *udev_monitor, struct udev_device *udev_device) -{ - struct udev_list_entry *list_entry; - - if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) == NULL) - goto tag; - udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_subsystem_list)) { - const char *subsys = udev_list_entry_get_name(list_entry); - const char *dsubsys = udev_device_get_subsystem(udev_device); - const char *devtype; - const char *ddevtype; - - if (!streq(dsubsys, subsys)) - continue; - - devtype = udev_list_entry_get_value(list_entry); - if (devtype == NULL) - goto tag; - ddevtype = udev_device_get_devtype(udev_device); - if (ddevtype == NULL) - continue; - if (streq(ddevtype, devtype)) - goto tag; - } - return 0; - -tag: - if (udev_list_get_entry(&udev_monitor->filter_tag_list) == NULL) - return 1; - udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_tag_list)) { - const char *tag = udev_list_entry_get_name(list_entry); - - if (udev_device_has_tag(udev_device, tag)) - return 1; - } - return 0; -} - -/** - * udev_monitor_receive_device: - * @udev_monitor: udev monitor - * - * Receive data from the udev monitor socket, allocate a new udev - * device, fill in the received data, and return the device. - * - * Only socket connections with uid=0 are accepted. - * - * The monitor socket is by default set to NONBLOCK. A variant of poll() on - * the file descriptor returned by udev_monitor_get_fd() should to be used to - * wake up when new devices arrive, or alternatively the file descriptor - * switched into blocking mode. - * - * The initial refcount is 1, and needs to be decremented to - * release the resources of the udev device. - * - * Returns: a new udev device, or #NULL, in case of an error - **/ -_public_ struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor) -{ - struct udev_device *udev_device; - struct msghdr smsg; - struct iovec iov; - char cred_msg[CMSG_SPACE(sizeof(struct ucred))]; - struct cmsghdr *cmsg; - union sockaddr_union snl; - struct ucred *cred; - union { - struct udev_monitor_netlink_header nlh; - char raw[8192]; - } buf; - ssize_t buflen; - ssize_t bufpos; - bool is_initialized = false; - -retry: - if (udev_monitor == NULL) - return NULL; - iov.iov_base = &buf; - iov.iov_len = sizeof(buf); - memzero(&smsg, sizeof(struct msghdr)); - smsg.msg_iov = &iov; - smsg.msg_iovlen = 1; - smsg.msg_control = cred_msg; - smsg.msg_controllen = sizeof(cred_msg); - smsg.msg_name = &snl; - smsg.msg_namelen = sizeof(snl); - - buflen = recvmsg(udev_monitor->sock, &smsg, 0); - if (buflen < 0) { - if (errno != EINTR) - log_debug("unable to receive message"); - return NULL; - } - - if (buflen < 32 || (smsg.msg_flags & MSG_TRUNC)) { - log_debug("invalid message length"); - return NULL; - } - - if (snl.nl.nl_groups == 0) { - /* unicast message, check if we trust the sender */ - if (udev_monitor->snl_trusted_sender.nl.nl_pid == 0 || - snl.nl.nl_pid != udev_monitor->snl_trusted_sender.nl.nl_pid) { - log_debug("unicast netlink message ignored"); - return NULL; - } - } else if (snl.nl.nl_groups == UDEV_MONITOR_KERNEL) { - if (snl.nl.nl_pid > 0) { - log_debug("multicast kernel netlink message from PID %"PRIu32" ignored", - snl.nl.nl_pid); - return NULL; - } - } - - cmsg = CMSG_FIRSTHDR(&smsg); - if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) { - log_debug("no sender credentials received, message ignored"); - return NULL; - } - - cred = (struct ucred *)CMSG_DATA(cmsg); - if (cred->uid != 0) { - log_debug("sender uid="UID_FMT", message ignored", cred->uid); - return NULL; - } - - if (memcmp(buf.raw, "libudev", 8) == 0) { - /* udev message needs proper version magic */ - if (buf.nlh.magic != htonl(UDEV_MONITOR_MAGIC)) { - log_debug("unrecognized message signature (%x != %x)", - buf.nlh.magic, htonl(UDEV_MONITOR_MAGIC)); - return NULL; - } - if (buf.nlh.properties_off+32 > (size_t)buflen) { - log_debug("message smaller than expected (%u > %zd)", - buf.nlh.properties_off+32, buflen); - return NULL; - } - - bufpos = buf.nlh.properties_off; - - /* devices received from udev are always initialized */ - is_initialized = true; - } else { - /* kernel message with header */ - bufpos = strlen(buf.raw) + 1; - if ((size_t)bufpos < sizeof("a@/d") || bufpos >= buflen) { - log_debug("invalid message length"); - return NULL; - } - - /* check message header */ - if (strstr(buf.raw, "@/") == NULL) { - log_debug("unrecognized message header"); - return NULL; - } - } - - udev_device = udev_device_new_from_nulstr(udev_monitor->udev, &buf.raw[bufpos], buflen - bufpos); - if (!udev_device) { - log_debug("could not create device: %m"); - return NULL; - } - - if (is_initialized) - udev_device_set_is_initialized(udev_device); - - /* skip device, if it does not pass the current filter */ - if (!passes_filter(udev_monitor, udev_device)) { - struct pollfd pfd[1]; - int rc; - - udev_device_unref(udev_device); - - /* if something is queued, get next device */ - pfd[0].fd = udev_monitor->sock; - pfd[0].events = POLLIN; - rc = poll(pfd, 1, 0); - if (rc > 0) - goto retry; - return NULL; - } - - return udev_device; -} - -int udev_monitor_send_device(struct udev_monitor *udev_monitor, - struct udev_monitor *destination, struct udev_device *udev_device) -{ - const char *buf, *val; - ssize_t blen, count; - struct udev_monitor_netlink_header nlh = { - .prefix = "libudev", - .magic = htonl(UDEV_MONITOR_MAGIC), - .header_size = sizeof nlh, - }; - struct iovec iov[2] = { - { .iov_base = &nlh, .iov_len = sizeof nlh }, - }; - struct msghdr smsg = { - .msg_iov = iov, - .msg_iovlen = 2, - }; - struct udev_list_entry *list_entry; - uint64_t tag_bloom_bits; - - blen = udev_device_get_properties_monitor_buf(udev_device, &buf); - if (blen < 32) { - log_debug("device buffer is too small to contain a valid device"); - return -EINVAL; - } - - /* fill in versioned header */ - val = udev_device_get_subsystem(udev_device); - nlh.filter_subsystem_hash = htonl(util_string_hash32(val)); - - val = udev_device_get_devtype(udev_device); - if (val != NULL) - nlh.filter_devtype_hash = htonl(util_string_hash32(val)); - - /* add tag bloom filter */ - tag_bloom_bits = 0; - udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(udev_device)) - tag_bloom_bits |= util_string_bloom64(udev_list_entry_get_name(list_entry)); - if (tag_bloom_bits > 0) { - nlh.filter_tag_bloom_hi = htonl(tag_bloom_bits >> 32); - nlh.filter_tag_bloom_lo = htonl(tag_bloom_bits & 0xffffffff); - } - - /* add properties list */ - nlh.properties_off = iov[0].iov_len; - nlh.properties_len = blen; - iov[1].iov_base = (char *)buf; - iov[1].iov_len = blen; - - /* - * Use custom address for target, or the default one. - * - * If we send to a multicast group, we will get - * ECONNREFUSED, which is expected. - */ - if (destination) - smsg.msg_name = &destination->snl; - else - smsg.msg_name = &udev_monitor->snl_destination; - smsg.msg_namelen = sizeof(struct sockaddr_nl); - count = sendmsg(udev_monitor->sock, &smsg, 0); - if (count < 0) { - if (!destination && errno == ECONNREFUSED) { - log_debug("passed device to netlink monitor %p", udev_monitor); - return 0; - } else - return -errno; - } - - log_debug("passed %zi byte device to netlink monitor %p", count, udev_monitor); - return count; -} - -/** - * udev_monitor_filter_add_match_subsystem_devtype: - * @udev_monitor: the monitor - * @subsystem: the subsystem value to match the incoming devices against - * @devtype: the devtype value to match the incoming devices against - * - * This filter is efficiently executed inside the kernel, and libudev subscribers - * will usually not be woken up for devices which do not match. - * - * The filter must be installed before the monitor is switched to listening mode. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, const char *subsystem, const char *devtype) -{ - if (udev_monitor == NULL) - return -EINVAL; - if (subsystem == NULL) - return -EINVAL; - if (udev_list_entry_add(&udev_monitor->filter_subsystem_list, subsystem, devtype) == NULL) - return -ENOMEM; - return 0; -} - -/** - * udev_monitor_filter_add_match_tag: - * @udev_monitor: the monitor - * @tag: the name of a tag - * - * This filter is efficiently executed inside the kernel, and libudev subscribers - * will usually not be woken up for devices which do not match. - * - * The filter must be installed before the monitor is switched to listening mode. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag) -{ - if (udev_monitor == NULL) - return -EINVAL; - if (tag == NULL) - return -EINVAL; - if (udev_list_entry_add(&udev_monitor->filter_tag_list, tag, NULL) == NULL) - return -ENOMEM; - return 0; -} - -/** - * udev_monitor_filter_remove: - * @udev_monitor: monitor - * - * Remove all filters from monitor. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_monitor_filter_remove(struct udev_monitor *udev_monitor) -{ - static struct sock_fprog filter = { 0, NULL }; - - udev_list_cleanup(&udev_monitor->filter_subsystem_list); - return setsockopt(udev_monitor->sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)); -} diff --git a/src/libudev/libudev-private.h b/src/libudev/libudev-private.h deleted file mode 100644 index 52c5075110..0000000000 --- a/src/libudev/libudev-private.h +++ /dev/null @@ -1,150 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 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 . -***/ - -#ifndef _LIBUDEV_PRIVATE_H_ -#define _LIBUDEV_PRIVATE_H_ - -#include -#include -#include - -#include "libudev.h" - -#include "macro.h" -#include "mkdir.h" -#include "strxcpyx.h" -#include "util.h" - -#define READ_END 0 -#define WRITE_END 1 - -/* libudev.c */ -int udev_get_rules_path(struct udev *udev, char **path[], usec_t *ts_usec[]); - -/* libudev-device.c */ -struct udev_device *udev_device_new_from_nulstr(struct udev *udev, char *nulstr, ssize_t buflen); -struct udev_device *udev_device_new_from_synthetic_event(struct udev *udev, const char *syspath, const char *action); -struct udev_device *udev_device_shallow_clone(struct udev_device *old_device); -struct udev_device *udev_device_clone_with_db(struct udev_device *old_device); -int udev_device_copy_properties(struct udev_device *dst, struct udev_device *src); -mode_t udev_device_get_devnode_mode(struct udev_device *udev_device); -uid_t udev_device_get_devnode_uid(struct udev_device *udev_device); -gid_t udev_device_get_devnode_gid(struct udev_device *udev_device); -int udev_device_rename(struct udev_device *udev_device, const char *new_name); -int udev_device_add_devlink(struct udev_device *udev_device, const char *devlink); -void udev_device_cleanup_devlinks_list(struct udev_device *udev_device); -int udev_device_add_property(struct udev_device *udev_device, const char *key, const char *value); -char **udev_device_get_properties_envp(struct udev_device *udev_device); -ssize_t udev_device_get_properties_monitor_buf(struct udev_device *udev_device, const char **buf); -const char *udev_device_get_devpath_old(struct udev_device *udev_device); -const char *udev_device_get_id_filename(struct udev_device *udev_device); -void udev_device_set_is_initialized(struct udev_device *udev_device); -int udev_device_add_tag(struct udev_device *udev_device, const char *tag); -void udev_device_remove_tag(struct udev_device *udev_device, const char *tag); -void udev_device_cleanup_tags_list(struct udev_device *udev_device); -usec_t udev_device_get_usec_initialized(struct udev_device *udev_device); -void udev_device_ensure_usec_initialized(struct udev_device *udev_device, struct udev_device *old_device); -int udev_device_get_devlink_priority(struct udev_device *udev_device); -int udev_device_set_devlink_priority(struct udev_device *udev_device, int prio); -int udev_device_get_watch_handle(struct udev_device *udev_device); -int udev_device_set_watch_handle(struct udev_device *udev_device, int handle); -int udev_device_get_ifindex(struct udev_device *udev_device); -void udev_device_set_info_loaded(struct udev_device *device); -bool udev_device_get_db_persist(struct udev_device *udev_device); -void udev_device_set_db_persist(struct udev_device *udev_device); -void udev_device_read_db(struct udev_device *udev_device); - -/* libudev-device-private.c */ -int udev_device_update_db(struct udev_device *udev_device); -int udev_device_delete_db(struct udev_device *udev_device); -int udev_device_tag_index(struct udev_device *dev, struct udev_device *dev_old, bool add); - -/* libudev-monitor.c - netlink/unix socket communication */ -int udev_monitor_disconnect(struct udev_monitor *udev_monitor); -int udev_monitor_allow_unicast_sender(struct udev_monitor *udev_monitor, struct udev_monitor *sender); -int udev_monitor_send_device(struct udev_monitor *udev_monitor, - struct udev_monitor *destination, struct udev_device *udev_device); -struct udev_monitor *udev_monitor_new_from_netlink_fd(struct udev *udev, const char *name, int fd); - -/* libudev-list.c */ -struct udev_list_node { - struct udev_list_node *next, *prev; -}; -struct udev_list { - struct udev *udev; - struct udev_list_node node; - struct udev_list_entry **entries; - unsigned int entries_cur; - unsigned int entries_max; - bool unique; -}; -void udev_list_node_init(struct udev_list_node *list); -int udev_list_node_is_empty(struct udev_list_node *list); -void udev_list_node_append(struct udev_list_node *new, struct udev_list_node *list); -void udev_list_node_remove(struct udev_list_node *entry); -#define udev_list_node_foreach(node, list) \ - for (node = (list)->next; \ - node != list; \ - node = (node)->next) -#define udev_list_node_foreach_safe(node, tmp, list) \ - for (node = (list)->next, tmp = (node)->next; \ - node != list; \ - node = tmp, tmp = (tmp)->next) -void udev_list_init(struct udev *udev, struct udev_list *list, bool unique); -void udev_list_cleanup(struct udev_list *list); -struct udev_list_entry *udev_list_get_entry(struct udev_list *list); -struct udev_list_entry *udev_list_entry_add(struct udev_list *list, const char *name, const char *value); -void udev_list_entry_delete(struct udev_list_entry *entry); -int udev_list_entry_get_num(struct udev_list_entry *list_entry); -void udev_list_entry_set_num(struct udev_list_entry *list_entry, int num); -#define udev_list_entry_foreach_safe(entry, tmp, first) \ - for (entry = first, tmp = udev_list_entry_get_next(entry); \ - entry != NULL; \ - entry = tmp, tmp = udev_list_entry_get_next(tmp)) - -/* libudev-queue.c */ -unsigned long long int udev_get_kernel_seqnum(struct udev *udev); -int udev_queue_read_seqnum(FILE *queue_file, unsigned long long int *seqnum); -ssize_t udev_queue_read_devpath(FILE *queue_file, char *devpath, size_t size); -ssize_t udev_queue_skip_devpath(FILE *queue_file); - -/* libudev-queue-private.c */ -struct udev_queue_export *udev_queue_export_new(struct udev *udev); -struct udev_queue_export *udev_queue_export_unref(struct udev_queue_export *udev_queue_export); -void udev_queue_export_cleanup(struct udev_queue_export *udev_queue_export); -int udev_queue_export_device_queued(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device); -int udev_queue_export_device_finished(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device); - -/* libudev-util.c */ -#define UTIL_PATH_SIZE 1024 -#define UTIL_NAME_SIZE 512 -#define UTIL_LINE_SIZE 16384 -#define UDEV_ALLOWED_CHARS_INPUT "/ $%?," -int util_log_priority(const char *priority); -size_t util_path_encode(const char *src, char *dest, size_t size); -void util_remove_trailing_chars(char *path, char c); -int util_replace_whitespace(const char *str, char *to, size_t len); -int util_replace_chars(char *str, const char *white); -unsigned int util_string_hash32(const char *key); -uint64_t util_string_bloom64(const char *str); - -/* libudev-util-private.c */ -int util_resolve_subsys_kernel(struct udev *udev, const char *string, char *result, size_t maxsize, int read_value); - -#endif diff --git a/src/libudev/libudev-queue.c b/src/libudev/libudev-queue.c deleted file mode 100644 index e3dffa6925..0000000000 --- a/src/libudev/libudev-queue.c +++ /dev/null @@ -1,268 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2009 Alan Jenkins - - 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 -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "io-util.h" -#include "libudev-private.h" - -/** - * SECTION:libudev-queue - * @short_description: access to currently active events - * - * This exports the current state of the udev processing queue. - */ - -/** - * udev_queue: - * - * Opaque object representing the current event queue in the udev daemon. - */ -struct udev_queue { - struct udev *udev; - int refcount; - int fd; -}; - -/** - * udev_queue_new: - * @udev: udev library context - * - * The initial refcount is 1, and needs to be decremented to - * release the resources of the udev queue context. - * - * Returns: the udev queue context, or #NULL on error. - **/ -_public_ struct udev_queue *udev_queue_new(struct udev *udev) -{ - struct udev_queue *udev_queue; - - if (udev == NULL) - return NULL; - - udev_queue = new0(struct udev_queue, 1); - if (udev_queue == NULL) - return NULL; - - udev_queue->refcount = 1; - udev_queue->udev = udev; - udev_queue->fd = -1; - return udev_queue; -} - -/** - * udev_queue_ref: - * @udev_queue: udev queue context - * - * Take a reference of a udev queue context. - * - * Returns: the same udev queue context. - **/ -_public_ struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue) -{ - if (udev_queue == NULL) - return NULL; - - udev_queue->refcount++; - return udev_queue; -} - -/** - * udev_queue_unref: - * @udev_queue: udev queue context - * - * Drop a reference of a udev queue context. If the refcount reaches zero, - * the resources of the queue context will be released. - * - * Returns: #NULL - **/ -_public_ struct udev_queue *udev_queue_unref(struct udev_queue *udev_queue) -{ - if (udev_queue == NULL) - return NULL; - - udev_queue->refcount--; - if (udev_queue->refcount > 0) - return NULL; - - safe_close(udev_queue->fd); - - free(udev_queue); - return NULL; -} - -/** - * udev_queue_get_udev: - * @udev_queue: udev queue context - * - * Retrieve the udev library context the queue context was created with. - * - * Returns: the udev library context. - **/ -_public_ struct udev *udev_queue_get_udev(struct udev_queue *udev_queue) -{ - if (udev_queue == NULL) - return NULL; - return udev_queue->udev; -} - -/** - * udev_queue_get_kernel_seqnum: - * @udev_queue: udev queue context - * - * This function is deprecated. - * - * Returns: 0. - **/ -_public_ unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue) -{ - return 0; -} - -/** - * udev_queue_get_udev_seqnum: - * @udev_queue: udev queue context - * - * This function is deprecated. - * - * Returns: 0. - **/ -_public_ unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue) -{ - return 0; -} - -/** - * udev_queue_get_udev_is_active: - * @udev_queue: udev queue context - * - * Check if udev is active on the system. - * - * Returns: a flag indicating if udev is active. - **/ -_public_ int udev_queue_get_udev_is_active(struct udev_queue *udev_queue) -{ - return access("/run/udev/control", F_OK) >= 0; -} - -/** - * udev_queue_get_queue_is_empty: - * @udev_queue: udev queue context - * - * Check if udev is currently processing any events. - * - * Returns: a flag indicating if udev is currently handling events. - **/ -_public_ int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue) -{ - return access("/run/udev/queue", F_OK) < 0; -} - -/** - * udev_queue_get_seqnum_sequence_is_finished: - * @udev_queue: udev queue context - * @start: first event sequence number - * @end: last event sequence number - * - * This function is deprecated, it just returns the result of - * udev_queue_get_queue_is_empty(). - * - * Returns: a flag indicating if udev is currently handling events. - **/ -_public_ int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue, - unsigned long long int start, unsigned long long int end) -{ - return udev_queue_get_queue_is_empty(udev_queue); -} - -/** - * udev_queue_get_seqnum_is_finished: - * @udev_queue: udev queue context - * @seqnum: sequence number - * - * This function is deprecated, it just returns the result of - * udev_queue_get_queue_is_empty(). - * - * Returns: a flag indicating if udev is currently handling events. - **/ -_public_ int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum) -{ - return udev_queue_get_queue_is_empty(udev_queue); -} - -/** - * udev_queue_get_queued_list_entry: - * @udev_queue: udev queue context - * - * This function is deprecated. - * - * Returns: NULL. - **/ -_public_ struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue) -{ - return NULL; -} - -/** - * udev_queue_get_fd: - * @udev_queue: udev queue context - * - * Returns: a file descriptor to watch for a queue to become empty. - */ -_public_ int udev_queue_get_fd(struct udev_queue *udev_queue) { - int fd; - int r; - - if (udev_queue->fd >= 0) - return udev_queue->fd; - - fd = inotify_init1(IN_CLOEXEC); - if (fd < 0) - return -errno; - - r = inotify_add_watch(fd, "/run/udev" , IN_DELETE); - if (r < 0) { - r = -errno; - close(fd); - return r; - } - - udev_queue->fd = fd; - return fd; -} - -/** - * udev_queue_flush: - * @udev_queue: udev queue context - * - * Returns: the result of clearing the watch for queue changes. - */ -_public_ int udev_queue_flush(struct udev_queue *udev_queue) { - if (udev_queue->fd < 0) - return -EINVAL; - - return flush_fd(udev_queue->fd); -} diff --git a/src/libudev/libudev-util.c b/src/libudev/libudev-util.c deleted file mode 100644 index 574cfeac85..0000000000 --- a/src/libudev/libudev-util.c +++ /dev/null @@ -1,268 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 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 -#include -#include -#include -#include -#include - -#include "libudev.h" - -#include "MurmurHash2.h" -#include "device-nodes.h" -#include "libudev-private.h" -#include "syslog-util.h" -#include "utf8.h" - -/** - * SECTION:libudev-util - * @short_description: utils - * - * Utilities useful when dealing with devices and device node names. - */ - -/* handle "[/]" format */ -int util_resolve_subsys_kernel(struct udev *udev, const char *string, - char *result, size_t maxsize, int read_value) -{ - char temp[UTIL_PATH_SIZE]; - char *subsys; - char *sysname; - struct udev_device *dev; - char *attr; - - if (string[0] != '[') - return -1; - - strscpy(temp, sizeof(temp), string); - - subsys = &temp[1]; - - sysname = strchr(subsys, '/'); - if (sysname == NULL) - return -1; - sysname[0] = '\0'; - sysname = &sysname[1]; - - attr = strchr(sysname, ']'); - if (attr == NULL) - return -1; - attr[0] = '\0'; - attr = &attr[1]; - if (attr[0] == '/') - attr = &attr[1]; - if (attr[0] == '\0') - attr = NULL; - - if (read_value && attr == NULL) - return -1; - - dev = udev_device_new_from_subsystem_sysname(udev, subsys, sysname); - if (dev == NULL) - return -1; - - if (read_value) { - const char *val; - - val = udev_device_get_sysattr_value(dev, attr); - if (val != NULL) - strscpy(result, maxsize, val); - else - result[0] = '\0'; - log_debug("value '[%s/%s]%s' is '%s'", subsys, sysname, attr, result); - } else { - size_t l; - char *s; - - s = result; - l = strpcpyl(&s, maxsize, udev_device_get_syspath(dev), NULL); - if (attr != NULL) - strpcpyl(&s, l, "/", attr, NULL); - log_debug("path '[%s/%s]%s' is '%s'", subsys, sysname, attr, result); - } - udev_device_unref(dev); - return 0; -} - -int util_log_priority(const char *priority) -{ - char *endptr; - int prio; - - prio = strtoul(priority, &endptr, 10); - if (endptr[0] == '\0' || isspace(endptr[0])) { - if (prio >= 0 && prio <= 7) - return prio; - else - return -ERANGE; - } - - return log_level_from_string(priority); -} - -size_t util_path_encode(const char *src, char *dest, size_t size) -{ - size_t i, j; - - for (i = 0, j = 0; src[i] != '\0'; i++) { - if (src[i] == '/') { - if (j+4 >= size) { - j = 0; - break; - } - memcpy(&dest[j], "\\x2f", 4); - j += 4; - } else if (src[i] == '\\') { - if (j+4 >= size) { - j = 0; - break; - } - memcpy(&dest[j], "\\x5c", 4); - j += 4; - } else { - if (j+1 >= size) { - j = 0; - break; - } - dest[j] = src[i]; - j++; - } - } - dest[j] = '\0'; - return j; -} - -void util_remove_trailing_chars(char *path, char c) -{ - size_t len; - - if (path == NULL) - return; - len = strlen(path); - while (len > 0 && path[len-1] == c) - path[--len] = '\0'; -} - -int util_replace_whitespace(const char *str, char *to, size_t len) -{ - size_t i, j; - - /* strip trailing whitespace */ - len = strnlen(str, len); - while (len && isspace(str[len-1])) - len--; - - /* strip leading whitespace */ - i = 0; - while ((i < len) && isspace(str[i])) - i++; - - j = 0; - while (i < len) { - /* substitute multiple whitespace with a single '_' */ - if (isspace(str[i])) { - while (isspace(str[i])) - i++; - to[j++] = '_'; - } - to[j++] = str[i++]; - } - to[j] = '\0'; - return 0; -} - -/* allow chars in whitelist, plain ascii, hex-escaping and valid utf8 */ -int util_replace_chars(char *str, const char *white) -{ - size_t i = 0; - int replaced = 0; - - while (str[i] != '\0') { - int len; - - if (whitelisted_char_for_devnode(str[i], white)) { - i++; - continue; - } - - /* accept hex encoding */ - if (str[i] == '\\' && str[i+1] == 'x') { - i += 2; - continue; - } - - /* accept valid utf8 */ - len = utf8_encoded_valid_unichar(&str[i]); - if (len > 1) { - i += len; - continue; - } - - /* if space is allowed, replace whitespace with ordinary space */ - if (isspace(str[i]) && white != NULL && strchr(white, ' ') != NULL) { - str[i] = ' '; - i++; - replaced++; - continue; - } - - /* everything else is replaced with '_' */ - str[i] = '_'; - i++; - replaced++; - } - return replaced; -} - -/** - * udev_util_encode_string: - * @str: input string to be encoded - * @str_enc: output string to store the encoded input string - * @len: maximum size of the output string, which may be - * four times as long as the input string - * - * Encode all potentially unsafe characters of a string to the - * corresponding 2 char hex value prefixed by '\x'. - * - * Returns: 0 if the entire string was copied, non-zero otherwise. - **/ -_public_ int udev_util_encode_string(const char *str, char *str_enc, size_t len) -{ - return encode_devnode_name(str, str_enc, len); -} - -unsigned int util_string_hash32(const char *str) -{ - return MurmurHash2(str, strlen(str), 0); -} - -/* get a bunch of bit numbers out of the hash, and set the bits in our bit field */ -uint64_t util_string_bloom64(const char *str) -{ - uint64_t bits = 0; - unsigned int hash = util_string_hash32(str); - - bits |= 1LLU << (hash & 63); - bits |= 1LLU << ((hash >> 6) & 63); - bits |= 1LLU << ((hash >> 12) & 63); - bits |= 1LLU << ((hash >> 18) & 63); - return bits; -} diff --git a/src/libudev/libudev.c b/src/libudev/libudev.c deleted file mode 100644 index 63fb05547d..0000000000 --- a/src/libudev/libudev.c +++ /dev/null @@ -1,253 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-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 -#include -#include -#include -#include -#include - -#include "libudev.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "libudev-private.h" -#include "missing.h" -#include "string-util.h" - -/** - * SECTION:libudev - * @short_description: libudev context - * - * The context contains the default values read from the udev config file, - * and is passed to all library operations. - */ - -/** - * udev: - * - * Opaque object representing the library context. - */ -struct udev { - int refcount; - void (*log_fn)(struct udev *udev, - int priority, const char *file, int line, const char *fn, - const char *format, va_list args); - void *userdata; -}; - -/** - * udev_get_userdata: - * @udev: udev library context - * - * Retrieve stored data pointer from library context. This might be useful - * to access from callbacks. - * - * Returns: stored userdata - **/ -_public_ void *udev_get_userdata(struct udev *udev) { - if (udev == NULL) - return NULL; - return udev->userdata; -} - -/** - * udev_set_userdata: - * @udev: udev library context - * @userdata: data pointer - * - * Store custom @userdata in the library context. - **/ -_public_ void udev_set_userdata(struct udev *udev, void *userdata) { - if (udev == NULL) - return; - udev->userdata = userdata; -} - -/** - * udev_new: - * - * Create udev library context. This reads the udev configuration - * file, and fills in the default values. - * - * The initial refcount is 1, and needs to be decremented to - * release the resources of the udev library context. - * - * Returns: a new udev library context - **/ -_public_ struct udev *udev_new(void) { - struct udev *udev; - _cleanup_fclose_ FILE *f = NULL; - - udev = new0(struct udev, 1); - if (udev == NULL) - return NULL; - udev->refcount = 1; - - f = fopen("/etc/udev/udev.conf", "re"); - if (f != NULL) { - char line[UTIL_LINE_SIZE]; - unsigned line_nr = 0; - - while (fgets(line, sizeof(line), f)) { - size_t len; - char *key; - char *val; - - line_nr++; - - /* find key */ - key = line; - while (isspace(key[0])) - key++; - - /* comment or empty line */ - if (key[0] == '#' || key[0] == '\0') - continue; - - /* split key/value */ - val = strchr(key, '='); - if (val == NULL) { - log_debug("/etc/udev/udev.conf:%u: missing assignment, skipping line.", line_nr); - continue; - } - val[0] = '\0'; - val++; - - /* find value */ - while (isspace(val[0])) - val++; - - /* terminate key */ - len = strlen(key); - if (len == 0) - continue; - while (isspace(key[len-1])) - len--; - key[len] = '\0'; - - /* terminate value */ - len = strlen(val); - if (len == 0) - continue; - while (isspace(val[len-1])) - len--; - val[len] = '\0'; - - if (len == 0) - continue; - - /* unquote */ - if (val[0] == '"' || val[0] == '\'') { - if (val[len-1] != val[0]) { - log_debug("/etc/udev/udev.conf:%u: inconsistent quoting, skipping line.", line_nr); - continue; - } - val[len-1] = '\0'; - val++; - } - - if (streq(key, "udev_log")) { - int prio; - - prio = util_log_priority(val); - if (prio < 0) - log_debug("/etc/udev/udev.conf:%u: invalid log level '%s', ignoring.", line_nr, val); - else - log_set_max_level(prio); - continue; - } - } - } - - return udev; -} - -/** - * udev_ref: - * @udev: udev library context - * - * Take a reference of the udev library context. - * - * Returns: the passed udev library context - **/ -_public_ struct udev *udev_ref(struct udev *udev) { - if (udev == NULL) - return NULL; - udev->refcount++; - return udev; -} - -/** - * udev_unref: - * @udev: udev library context - * - * Drop a reference of the udev library context. If the refcount - * reaches zero, the resources of the context will be released. - * - * Returns: the passed udev library context if it has still an active reference, or #NULL otherwise. - **/ -_public_ struct udev *udev_unref(struct udev *udev) { - if (udev == NULL) - return NULL; - udev->refcount--; - if (udev->refcount > 0) - return udev; - free(udev); - return NULL; -} - -/** - * udev_set_log_fn: - * @udev: udev library context - * @log_fn: function to be called for log messages - * - * This function is deprecated. - * - **/ -_public_ void udev_set_log_fn(struct udev *udev, - void (*log_fn)(struct udev *udev, - int priority, const char *file, int line, const char *fn, - const char *format, va_list args)) { - return; -} - -/** - * udev_get_log_priority: - * @udev: udev library context - * - * This function is deprecated. - * - **/ -_public_ int udev_get_log_priority(struct udev *udev) { - return log_get_max_level(); -} - -/** - * udev_set_log_priority: - * @udev: udev library context - * @priority: the new log priority - * - * This function is deprecated. - * - **/ -_public_ void udev_set_log_priority(struct udev *udev, int priority) { - log_set_max_level(priority); -} diff --git a/src/libudev/libudev.h b/src/libudev/libudev.h deleted file mode 100644 index 3f6d0ed16c..0000000000 --- a/src/libudev/libudev.h +++ /dev/null @@ -1,207 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 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 . -***/ - -#ifndef _LIBUDEV_H_ -#define _LIBUDEV_H_ - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * udev - library context - * - * reads the udev config and system environment - * allows custom logging - */ -struct udev; -struct udev *udev_ref(struct udev *udev); -struct udev *udev_unref(struct udev *udev); -struct udev *udev_new(void); -void udev_set_log_fn(struct udev *udev, - void (*log_fn)(struct udev *udev, - int priority, const char *file, int line, const char *fn, - const char *format, va_list args)) __attribute__ ((deprecated)); -int udev_get_log_priority(struct udev *udev) __attribute__ ((deprecated)); -void udev_set_log_priority(struct udev *udev, int priority) __attribute__ ((deprecated)); -void *udev_get_userdata(struct udev *udev); -void udev_set_userdata(struct udev *udev, void *userdata); - -/* - * udev_list - * - * access to libudev generated lists - */ -struct udev_list_entry; -struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry); -struct udev_list_entry *udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name); -const char *udev_list_entry_get_name(struct udev_list_entry *list_entry); -const char *udev_list_entry_get_value(struct udev_list_entry *list_entry); -/** - * udev_list_entry_foreach: - * @list_entry: entry to store the current position - * @first_entry: first entry to start with - * - * Helper to iterate over all entries of a list. - */ -#define udev_list_entry_foreach(list_entry, first_entry) \ - for (list_entry = first_entry; \ - list_entry != NULL; \ - list_entry = udev_list_entry_get_next(list_entry)) - -/* - * udev_device - * - * access to sysfs/kernel devices - */ -struct udev_device; -struct udev_device *udev_device_ref(struct udev_device *udev_device); -struct udev_device *udev_device_unref(struct udev_device *udev_device); -struct udev *udev_device_get_udev(struct udev_device *udev_device); -struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath); -struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum); -struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname); -struct udev_device *udev_device_new_from_device_id(struct udev *udev, const char *id); -struct udev_device *udev_device_new_from_environment(struct udev *udev); -/* udev_device_get_parent_*() does not take a reference on the returned device, it is automatically unref'd with the parent */ -struct udev_device *udev_device_get_parent(struct udev_device *udev_device); -struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device, - const char *subsystem, const char *devtype); -/* retrieve device properties */ -const char *udev_device_get_devpath(struct udev_device *udev_device); -const char *udev_device_get_subsystem(struct udev_device *udev_device); -const char *udev_device_get_devtype(struct udev_device *udev_device); -const char *udev_device_get_syspath(struct udev_device *udev_device); -const char *udev_device_get_sysname(struct udev_device *udev_device); -const char *udev_device_get_sysnum(struct udev_device *udev_device); -const char *udev_device_get_devnode(struct udev_device *udev_device); -int udev_device_get_is_initialized(struct udev_device *udev_device); -struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device); -struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device); -struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device); -struct udev_list_entry *udev_device_get_sysattr_list_entry(struct udev_device *udev_device); -const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key); -const char *udev_device_get_driver(struct udev_device *udev_device); -dev_t udev_device_get_devnum(struct udev_device *udev_device); -const char *udev_device_get_action(struct udev_device *udev_device); -unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device); -unsigned long long int udev_device_get_usec_since_initialized(struct udev_device *udev_device); -const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr); -int udev_device_set_sysattr_value(struct udev_device *udev_device, const char *sysattr, char *value); -int udev_device_has_tag(struct udev_device *udev_device, const char *tag); - -/* - * udev_monitor - * - * access to kernel uevents and udev events - */ -struct udev_monitor; -struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor); -struct udev_monitor *udev_monitor_unref(struct udev_monitor *udev_monitor); -struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor); -/* kernel and udev generated events over netlink */ -struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name); -/* bind socket */ -int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor); -int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size); -int udev_monitor_get_fd(struct udev_monitor *udev_monitor); -struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor); -/* in-kernel socket filters to select messages that get delivered to a listener */ -int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, - const char *subsystem, const char *devtype); -int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag); -int udev_monitor_filter_update(struct udev_monitor *udev_monitor); -int udev_monitor_filter_remove(struct udev_monitor *udev_monitor); - -/* - * udev_enumerate - * - * search sysfs for specific devices and provide a sorted list - */ -struct udev_enumerate; -struct udev_enumerate *udev_enumerate_ref(struct udev_enumerate *udev_enumerate); -struct udev_enumerate *udev_enumerate_unref(struct udev_enumerate *udev_enumerate); -struct udev *udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate); -struct udev_enumerate *udev_enumerate_new(struct udev *udev); -/* device properties filter */ -int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem); -int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem); -int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value); -int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value); -int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value); -int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname); -int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag); -int udev_enumerate_add_match_parent(struct udev_enumerate *udev_enumerate, struct udev_device *parent); -int udev_enumerate_add_match_is_initialized(struct udev_enumerate *udev_enumerate); -int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath); -/* run enumeration with active filters */ -int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate); -int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate); -/* return device list */ -struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate); - -/* - * udev_queue - * - * access to the currently running udev events - */ -struct udev_queue; -struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue); -struct udev_queue *udev_queue_unref(struct udev_queue *udev_queue); -struct udev *udev_queue_get_udev(struct udev_queue *udev_queue); -struct udev_queue *udev_queue_new(struct udev *udev); -unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue) __attribute__ ((deprecated)); -unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue) __attribute__ ((deprecated)); -int udev_queue_get_udev_is_active(struct udev_queue *udev_queue); -int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue); -int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum) __attribute__ ((deprecated)); -int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue, - unsigned long long int start, unsigned long long int end) __attribute__ ((deprecated)); -int udev_queue_get_fd(struct udev_queue *udev_queue); -int udev_queue_flush(struct udev_queue *udev_queue); -struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue) __attribute__ ((deprecated)); - -/* - * udev_hwdb - * - * access to the static hardware properties database - */ -struct udev_hwdb; -struct udev_hwdb *udev_hwdb_new(struct udev *udev); -struct udev_hwdb *udev_hwdb_ref(struct udev_hwdb *hwdb); -struct udev_hwdb *udev_hwdb_unref(struct udev_hwdb *hwdb); -struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned int flags); - -/* - * udev_util - * - * udev specific utilities - */ -int udev_util_encode_string(const char *str, char *str_enc, size_t len); - - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif diff --git a/src/libudev/libudev.pc.in b/src/libudev/libudev.pc.in deleted file mode 100644 index a0f3f524e0..0000000000 --- a/src/libudev/libudev.pc.in +++ /dev/null @@ -1,17 +0,0 @@ -# This file is part of systemd. -# -# 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. - -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ - -Name: libudev -Description: Library to access udev device information -Version: @VERSION@ -Libs: -L${libdir} -ludev -Cflags: -I${includedir} diff --git a/src/libudev/libudev.sym b/src/libudev/libudev.sym deleted file mode 100644 index 76726fca77..0000000000 --- a/src/libudev/libudev.sym +++ /dev/null @@ -1,120 +0,0 @@ -/*** - This file is part of systemd. - - 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. -***/ - -LIBUDEV_183 { -global: - udev_device_get_action; - udev_device_get_devlinks_list_entry; - udev_device_get_devnode; - udev_device_get_devnum; - udev_device_get_devpath; - udev_device_get_devtype; - udev_device_get_driver; - udev_device_get_is_initialized; - udev_device_get_parent; - udev_device_get_parent_with_subsystem_devtype; - udev_device_get_properties_list_entry; - udev_device_get_property_value; - udev_device_get_seqnum; - udev_device_get_subsystem; - udev_device_get_sysattr_list_entry; - udev_device_get_sysattr_value; - udev_device_get_sysname; - udev_device_get_sysnum; - udev_device_get_syspath; - udev_device_get_tags_list_entry; - udev_device_get_udev; - udev_device_get_usec_since_initialized; - udev_device_has_tag; - udev_device_new_from_devnum; - udev_device_new_from_environment; - udev_device_new_from_subsystem_sysname; - udev_device_new_from_syspath; - udev_device_ref; - udev_device_unref; - udev_enumerate_add_match_is_initialized; - udev_enumerate_add_match_parent; - udev_enumerate_add_match_property; - udev_enumerate_add_match_subsystem; - udev_enumerate_add_match_sysattr; - udev_enumerate_add_match_sysname; - udev_enumerate_add_match_tag; - udev_enumerate_add_nomatch_subsystem; - udev_enumerate_add_nomatch_sysattr; - udev_enumerate_add_syspath; - udev_enumerate_get_list_entry; - udev_enumerate_get_udev; - udev_enumerate_new; - udev_enumerate_ref; - udev_enumerate_scan_devices; - udev_enumerate_scan_subsystems; - udev_enumerate_unref; - udev_get_log_priority; - udev_get_userdata; - udev_list_entry_get_by_name; - udev_list_entry_get_name; - udev_list_entry_get_next; - udev_list_entry_get_value; - udev_monitor_enable_receiving; - udev_monitor_filter_add_match_subsystem_devtype; - udev_monitor_filter_add_match_tag; - udev_monitor_filter_remove; - udev_monitor_filter_update; - udev_monitor_get_fd; - udev_monitor_get_udev; - udev_monitor_new_from_netlink; - udev_monitor_receive_device; - udev_monitor_ref; - udev_monitor_set_receive_buffer_size; - udev_monitor_unref; - udev_new; - udev_queue_get_kernel_seqnum; - udev_queue_get_queue_is_empty; - udev_queue_get_queued_list_entry; - udev_queue_get_seqnum_is_finished; - udev_queue_get_seqnum_sequence_is_finished; - udev_queue_get_udev; - udev_queue_get_udev_is_active; - udev_queue_get_udev_seqnum; - udev_queue_new; - udev_queue_ref; - udev_queue_unref; - udev_ref; - udev_set_log_fn; - udev_set_log_priority; - udev_set_userdata; - udev_unref; - udev_util_encode_string; -local: - *; -}; - -LIBUDEV_189 { -global: - udev_device_new_from_device_id; -} LIBUDEV_183; - -LIBUDEV_196 { -global: - udev_hwdb_new; - udev_hwdb_ref; - udev_hwdb_unref; - udev_hwdb_get_properties_list_entry; -} LIBUDEV_189; - -LIBUDEV_199 { -global: - udev_device_set_sysattr_value; -} LIBUDEV_196; - -LIBUDEV_215 { -global: - udev_queue_flush; - udev_queue_get_fd; -} LIBUDEV_199; diff --git a/src/libudev/src/.gitignore b/src/libudev/src/.gitignore new file mode 100644 index 0000000000..0c8a5d5231 --- /dev/null +++ b/src/libudev/src/.gitignore @@ -0,0 +1 @@ +/libudev.pc diff --git a/src/libudev/src/Makefile b/src/libudev/src/Makefile new file mode 100644 index 0000000000..8690587b3f --- /dev/null +++ b/src/libudev/src/Makefile @@ -0,0 +1,77 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +LIBUDEV_CURRENT=7 +LIBUDEV_REVISION=4 +LIBUDEV_AGE=6 + +include_HEADERS += \ + src/libudev/libudev.h + +lib_LTLIBRARIES += \ + libudev.la + +libudev_la_SOURCES =\ + src/libudev/libudev.sym \ + src/libudev/libudev-private.h \ + src/libudev/libudev-device-internal.h \ + src/libudev/libudev.c \ + src/libudev/libudev-list.c \ + src/libudev/libudev-util.c \ + src/libudev/libudev-device.c \ + src/libudev/libudev-device-private.c \ + src/libudev/libudev-enumerate.c \ + src/libudev/libudev-monitor.c \ + src/libudev/libudev-queue.c \ + src/libudev/libudev-hwdb.c + +libudev_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -version-info $(LIBUDEV_CURRENT):$(LIBUDEV_REVISION):$(LIBUDEV_AGE) \ + -Wl,--version-script=$(srcdir)/libudev.sym + +libudev_la_LIBADD = \ + libsystemd-internal.la + +pkgconfiglib_DATA += \ + src/libudev/libudev.pc + +EXTRA_DIST += \ + src/libudev/libudev.pc.in + +test-libudev-sym.c: \ + src/libudev/libudev.sym \ + src/udev/udev.h + $(generate-sym-test) + +nodist_test_libudev_sym_SOURCES = \ + test-libudev-sym.c +test_libudev_sym_CFLAGS = \ + $(AM_CFLAGS) \ + -Wno-deprecated-declarations +test_libudev_sym_LDADD = \ + libudev.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libudev/src/libudev-device-internal.h b/src/libudev/src/libudev-device-internal.h new file mode 100644 index 0000000000..f76da09407 --- /dev/null +++ b/src/libudev/src/libudev-device-internal.h @@ -0,0 +1,58 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2015 Tom Gundersen + + 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 "libudev.h" +#include + +#include "libudev-private.h" + +/** + * udev_device: + * + * Opaque object representing one kernel sys device. + */ +struct udev_device { + struct udev *udev; + + /* real device object */ + sd_device *device; + + /* legacy */ + int refcount; + + struct udev_device *parent; + bool parent_set; + + struct udev_list properties; + uint64_t properties_generation; + struct udev_list tags; + uint64_t tags_generation; + struct udev_list devlinks; + uint64_t devlinks_generation; + bool properties_read:1; + bool tags_read:1; + bool devlinks_read:1; + struct udev_list sysattrs; + bool sysattrs_read; +}; + +struct udev_device *udev_device_new(struct udev *udev); diff --git a/src/libudev/src/libudev-device-private.c b/src/libudev/src/libudev-device-private.c new file mode 100644 index 0000000000..2aae0726c1 --- /dev/null +++ b/src/libudev/src/libudev-device-private.c @@ -0,0 +1,411 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2015 Tom Gundersen + + 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 "libudev.h" + +#include "device-private.h" +#include "libudev-device-internal.h" +#include "libudev-private.h" + +int udev_device_tag_index(struct udev_device *udev_device, struct udev_device *udev_device_old, bool add) { + sd_device *device_old = NULL; + int r; + + assert(udev_device); + + if (udev_device_old) + device_old = udev_device_old->device; + + r = device_tag_index(udev_device->device, device_old, add); + if (r < 0) + return r; + + return 0; +} + +int udev_device_update_db(struct udev_device *udev_device) { + int r; + + assert(udev_device); + + r = device_update_db(udev_device->device); + if (r < 0) + return r; + + return 0; +} + +int udev_device_delete_db(struct udev_device *udev_device) { + int r; + + assert(udev_device); + + r = device_delete_db(udev_device->device); + if (r < 0) + return r; + + return 0; +} + +int udev_device_get_ifindex(struct udev_device *udev_device) { + int r, ifindex; + + assert(udev_device); + + r = sd_device_get_ifindex(udev_device->device, &ifindex); + if (r < 0) + return r; + + return ifindex; +} + +const char *udev_device_get_devpath_old(struct udev_device *udev_device) { + const char *devpath_old = NULL; + int r; + + assert(udev_device); + + r = sd_device_get_property_value(udev_device->device, "DEVPATH_OLD", &devpath_old); + if (r < 0 && r != -ENOENT) { + errno = -r; + return NULL; + } + + return devpath_old; +} + +mode_t udev_device_get_devnode_mode(struct udev_device *udev_device) { + mode_t mode; + int r; + + assert(udev_device); + + r = device_get_devnode_mode(udev_device->device, &mode); + if (r < 0) { + errno = -r; + return 0; + } + + return mode; +} + +uid_t udev_device_get_devnode_uid(struct udev_device *udev_device) { + uid_t uid; + int r; + + assert(udev_device); + + r = device_get_devnode_uid(udev_device->device, &uid); + if (r < 0) { + errno = -r; + return 0; + } + + return uid; +} + +gid_t udev_device_get_devnode_gid(struct udev_device *udev_device) { + gid_t gid; + int r; + + assert(udev_device); + + r = device_get_devnode_gid(udev_device->device, &gid); + if (r < 0) { + errno = -r; + return 0; + } + + return gid; +} + +void udev_device_ensure_usec_initialized(struct udev_device *udev_device, struct udev_device *udev_device_old) { + assert(udev_device); + + device_ensure_usec_initialized(udev_device->device, + udev_device_old ? udev_device_old->device : NULL); +} + +char **udev_device_get_properties_envp(struct udev_device *udev_device) { + char **envp; + int r; + + assert(udev_device); + + r = device_get_properties_strv(udev_device->device, &envp); + if (r < 0) { + errno = -r; + return NULL; + } + + return envp; +} + +ssize_t udev_device_get_properties_monitor_buf(struct udev_device *udev_device, const char **buf) { + const char *nulstr; + size_t len; + int r; + + assert(udev_device); + assert(buf); + + r = device_get_properties_nulstr(udev_device->device, (const uint8_t **)&nulstr, &len); + if (r < 0) + return r; + + *buf = nulstr; + + return len; +} + +int udev_device_get_devlink_priority(struct udev_device *udev_device) { + int priority, r; + + assert(udev_device); + + r = device_get_devlink_priority(udev_device->device, &priority); + if (r < 0) + return r; + + return priority; +} + +int udev_device_get_watch_handle(struct udev_device *udev_device) { + int handle, r; + + assert(udev_device); + + r = device_get_watch_handle(udev_device->device, &handle); + if (r < 0) + return r; + + return handle; +} + +void udev_device_set_is_initialized(struct udev_device *udev_device) { + assert(udev_device); + + device_set_is_initialized(udev_device->device); +} + +int udev_device_rename(struct udev_device *udev_device, const char *name) { + int r; + + assert(udev_device); + + r = device_rename(udev_device->device, name); + if (r < 0) + return r; + + return 0; +} + +struct udev_device *udev_device_shallow_clone(struct udev_device *old_device) { + struct udev_device *device; + int r; + + assert(old_device); + + device = udev_device_new(old_device->udev); + if (!device) + return NULL; + + r = device_shallow_clone(old_device->device, &device->device); + if (r < 0) { + udev_device_unref(device); + errno = -r; + return NULL; + } + + return device; +} + +struct udev_device *udev_device_clone_with_db(struct udev_device *udev_device_old) { + struct udev_device *udev_device; + int r; + + assert(udev_device_old); + + udev_device = udev_device_new(udev_device_old->udev); + if (!udev_device) + return NULL; + + r = device_clone_with_db(udev_device_old->device, &udev_device->device); + if (r < 0) { + udev_device_unref(udev_device); + errno = -r; + return NULL; + } + + return udev_device; +} + +struct udev_device *udev_device_new_from_nulstr(struct udev *udev, char *nulstr, ssize_t buflen) { + struct udev_device *device; + int r; + + device = udev_device_new(udev); + if (!device) + return NULL; + + r = device_new_from_nulstr(&device->device, (uint8_t*)nulstr, buflen); + if (r < 0) { + udev_device_unref(device); + errno = -r; + return NULL; + } + + return device; +} + +struct udev_device *udev_device_new_from_synthetic_event(struct udev *udev, const char *syspath, const char *action) { + struct udev_device *device; + int r; + + device = udev_device_new(udev); + if (!device) + return NULL; + + r = device_new_from_synthetic_event(&device->device, syspath, action); + if (r < 0) { + udev_device_unref(device); + errno = -r; + return NULL; + } + + return device; +} + +int udev_device_copy_properties(struct udev_device *udev_device_dst, struct udev_device *udev_device_src) { + int r; + + assert(udev_device_dst); + assert(udev_device_src); + + r = device_copy_properties(udev_device_dst->device, udev_device_src->device); + if (r < 0) + return r; + + return 0; +} + +const char *udev_device_get_id_filename(struct udev_device *udev_device) { + const char *filename; + int r; + + assert(udev_device); + + r = device_get_id_filename(udev_device->device, &filename); + if (r < 0) { + errno = -r; + return NULL; + } + + return filename; +} + +int udev_device_set_watch_handle(struct udev_device *udev_device, int handle) { + + assert(udev_device); + + device_set_watch_handle(udev_device->device, handle); + + return 0; +} + +void udev_device_set_db_persist(struct udev_device *udev_device) { + assert(udev_device); + + device_set_db_persist(udev_device->device); +} + +int udev_device_set_devlink_priority(struct udev_device *udev_device, int priority) { + assert(udev_device); + + device_set_devlink_priority(udev_device->device, priority); + + return 0; +} + +int udev_device_add_devlink(struct udev_device *udev_device, const char *devlink) { + int r; + + assert(udev_device); + + r = device_add_devlink(udev_device->device, devlink); + if (r < 0) + return r; + + return 0; +} + +int udev_device_add_property(struct udev_device *udev_device, const char *property, const char *value) { + int r; + + assert(udev_device); + + r = device_add_property(udev_device->device, property, value); + if (r < 0) + return r; + + return 0; +} + +int udev_device_add_tag(struct udev_device *udev_device, const char *tag) { + int r; + + assert(udev_device); + + r = device_add_tag(udev_device->device, tag); + if (r < 0) + return r; + + return 0; +} + +void udev_device_remove_tag(struct udev_device *udev_device, const char *tag) { + assert(udev_device); + + device_remove_tag(udev_device->device, tag); +} + +void udev_device_cleanup_tags_list(struct udev_device *udev_device) { + assert(udev_device); + + device_cleanup_tags(udev_device->device); +} + +void udev_device_cleanup_devlinks_list(struct udev_device *udev_device) { + assert(udev_device); + + device_cleanup_devlinks(udev_device->device); +} + +void udev_device_set_info_loaded(struct udev_device *udev_device) { + assert(udev_device); + + device_seal(udev_device->device); +} + +void udev_device_read_db(struct udev_device *udev_device) { + assert(udev_device); + + device_read_db_force(udev_device->device); +} diff --git a/src/libudev/src/libudev-device.c b/src/libudev/src/libudev-device.c new file mode 100644 index 0000000000..6536f4cfbb --- /dev/null +++ b/src/libudev/src/libudev-device.c @@ -0,0 +1,958 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2015 Tom Gundersen + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libudev.h" +#include + +#include "alloc-util.h" +#include "device-private.h" +#include "device-util.h" +#include "libudev-device-internal.h" +#include "libudev-private.h" +#include "parse-util.h" + +/** + * SECTION:libudev-device + * @short_description: kernel sys devices + * + * Representation of kernel sys devices. Devices are uniquely identified + * by their syspath, every device has exactly one path in the kernel sys + * filesystem. Devices usually belong to a kernel subsystem, and have + * a unique name inside that subsystem. + */ + +/** + * udev_device_get_seqnum: + * @udev_device: udev device + * + * This is only valid if the device was received through a monitor. Devices read from + * sys do not have a sequence number. + * + * Returns: the kernel event sequence number, or 0 if there is no sequence number available. + **/ +_public_ unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device) +{ + const char *seqnum; + unsigned long long ret; + int r; + + assert_return_errno(udev_device, 0, EINVAL); + + r = sd_device_get_property_value(udev_device->device, "SEQNUM", &seqnum); + if (r == -ENOENT) + return 0; + else if (r < 0) { + errno = -r; + return 0; + } + + r = safe_atollu(seqnum, &ret); + if (r < 0) { + errno = -r; + return 0; + } + + return ret; +} + +/** + * udev_device_get_devnum: + * @udev_device: udev device + * + * Get the device major/minor number. + * + * Returns: the dev_t number. + **/ +_public_ dev_t udev_device_get_devnum(struct udev_device *udev_device) +{ + dev_t devnum; + int r; + + assert_return_errno(udev_device, makedev(0, 0), EINVAL); + + r = sd_device_get_devnum(udev_device->device, &devnum); + if (r < 0) { + errno = -r; + return makedev(0, 0); + } + + return devnum; +} + +/** + * udev_device_get_driver: + * @udev_device: udev device + * + * Get the kernel driver name. + * + * Returns: the driver name string, or #NULL if there is no driver attached. + **/ +_public_ const char *udev_device_get_driver(struct udev_device *udev_device) +{ + const char *driver; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_driver(udev_device->device, &driver); + if (r < 0) { + errno = -r; + return NULL; + } + + return driver; +} + +/** + * udev_device_get_devtype: + * @udev_device: udev device + * + * Retrieve the devtype string of the udev device. + * + * Returns: the devtype name of the udev device, or #NULL if it can not be determined + **/ +_public_ const char *udev_device_get_devtype(struct udev_device *udev_device) +{ + const char *devtype; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_devtype(udev_device->device, &devtype); + if (r < 0) { + errno = -r; + return NULL; + } + + return devtype; +} + +/** + * udev_device_get_subsystem: + * @udev_device: udev device + * + * Retrieve the subsystem string of the udev device. The string does not + * contain any "/". + * + * Returns: the subsystem name of the udev device, or #NULL if it can not be determined + **/ +_public_ const char *udev_device_get_subsystem(struct udev_device *udev_device) +{ + const char *subsystem; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_subsystem(udev_device->device, &subsystem); + if (r < 0) { + errno = -r; + return NULL; + } else if (!subsystem) + errno = ENODATA; + + return subsystem; +} + +/** + * udev_device_get_property_value: + * @udev_device: udev device + * @key: property name + * + * Get the value of a given property. + * + * Returns: the property string, or #NULL if there is no such property. + **/ +_public_ const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key) +{ + const char *value = NULL; + int r; + + assert_return_errno(udev_device && key, NULL, EINVAL); + + r = sd_device_get_property_value(udev_device->device, key, &value); + if (r < 0) { + errno = -r; + return NULL; + } + + return value; +} + +struct udev_device *udev_device_new(struct udev *udev) { + struct udev_device *udev_device; + + assert_return_errno(udev, NULL, EINVAL); + + udev_device = new0(struct udev_device, 1); + if (!udev_device) { + errno = ENOMEM; + return NULL; + } + udev_device->refcount = 1; + udev_device->udev = udev; + udev_list_init(udev, &udev_device->properties, true); + udev_list_init(udev, &udev_device->tags, true); + udev_list_init(udev, &udev_device->sysattrs, true); + udev_list_init(udev, &udev_device->devlinks, true); + + return udev_device; +} + +/** + * udev_device_new_from_syspath: + * @udev: udev library context + * @syspath: sys device path including sys directory + * + * Create new udev device, and fill in information from the sys + * device and the udev database entry. The syspath is the absolute + * path to the device, including the sys mount point. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, if it does not exist + **/ +_public_ struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath) { + struct udev_device *udev_device; + int r; + + udev_device = udev_device_new(udev); + if (!udev_device) + return NULL; + + r = sd_device_new_from_syspath(&udev_device->device, syspath); + if (r < 0) { + errno = -r; + udev_device_unref(udev_device); + return NULL; + } + + return udev_device; +} + +/** + * udev_device_new_from_devnum: + * @udev: udev library context + * @type: char or block device + * @devnum: device major/minor number + * + * Create new udev device, and fill in information from the sys + * device and the udev database entry. The device is looked-up + * by its major/minor number and type. Character and block device + * numbers are not unique across the two types. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, if it does not exist + **/ +_public_ struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum) +{ + struct udev_device *udev_device; + int r; + + udev_device = udev_device_new(udev); + if (!udev_device) + return NULL; + + r = sd_device_new_from_devnum(&udev_device->device, type, devnum); + if (r < 0) { + errno = -r; + udev_device_unref(udev_device); + return NULL; + } + + return udev_device; +} + +/** + * udev_device_new_from_device_id: + * @udev: udev library context + * @id: text string identifying a kernel device + * + * Create new udev device, and fill in information from the sys + * device and the udev database entry. The device is looked-up + * by a special string: + * b8:2 - block device major:minor + * c128:1 - char device major:minor + * n3 - network device ifindex + * +sound:card29 - kernel driver core subsystem:device name + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, if it does not exist + **/ +_public_ struct udev_device *udev_device_new_from_device_id(struct udev *udev, const char *id) +{ + struct udev_device *udev_device; + int r; + + udev_device = udev_device_new(udev); + if (!udev_device) + return NULL; + + r = sd_device_new_from_device_id(&udev_device->device, id); + if (r < 0) { + errno = -r; + udev_device_unref(udev_device); + return NULL; + } + + return udev_device; +} + +/** + * udev_device_new_from_subsystem_sysname: + * @udev: udev library context + * @subsystem: the subsystem of the device + * @sysname: the name of the device + * + * Create new udev device, and fill in information from the sys device + * and the udev database entry. The device is looked up by the subsystem + * and name string of the device, like "mem" / "zero", or "block" / "sda". + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, if it does not exist + **/ +_public_ struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname) +{ + struct udev_device *udev_device; + int r; + + udev_device = udev_device_new(udev); + if (!udev_device) + return NULL; + + r = sd_device_new_from_subsystem_sysname(&udev_device->device, subsystem, sysname); + if (r < 0) { + errno = -r; + udev_device_unref(udev_device); + return NULL; + } + + return udev_device; +} + +/** + * udev_device_new_from_environment + * @udev: udev library context + * + * Create new udev device, and fill in information from the + * current process environment. This only works reliable if + * the process is called from a udev rule. It is usually used + * for tools executed from IMPORT= rules. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, if it does not exist + **/ +_public_ struct udev_device *udev_device_new_from_environment(struct udev *udev) +{ + struct udev_device *udev_device; + int r; + + udev_device = udev_device_new(udev); + if (!udev_device) + return NULL; + + r = device_new_from_strv(&udev_device->device, environ); + if (r < 0) { + errno = -r; + udev_device_unref(udev_device); + return NULL; + } + + return udev_device; +} + +static struct udev_device *device_new_from_parent(struct udev_device *child) +{ + struct udev_device *parent; + int r; + + assert_return_errno(child, NULL, EINVAL); + + parent = udev_device_new(child->udev); + if (!parent) + return NULL; + + r = sd_device_get_parent(child->device, &parent->device); + if (r < 0) { + errno = -r; + udev_device_unref(parent); + return NULL; + } + + /* the parent is unref'ed with the child, so take a ref from libudev as well */ + sd_device_ref(parent->device); + + return parent; +} + +/** + * udev_device_get_parent: + * @udev_device: the device to start searching from + * + * Find the next parent device, and fill in information from the sys + * device and the udev database entry. + * + * Returned device is not referenced. It is attached to the child + * device, and will be cleaned up when the child device is cleaned up. + * + * It is not necessarily just the upper level directory, empty or not + * recognized sys directories are ignored. + * + * It can be called as many times as needed, without caring about + * references. + * + * Returns: a new udev device, or #NULL, if it no parent exist. + **/ +_public_ struct udev_device *udev_device_get_parent(struct udev_device *udev_device) +{ + assert_return_errno(udev_device, NULL, EINVAL); + + if (!udev_device->parent_set) { + udev_device->parent_set = true; + udev_device->parent = device_new_from_parent(udev_device); + } + + /* TODO: errno will differ here in case parent == NULL */ + return udev_device->parent; +} + +/** + * udev_device_get_parent_with_subsystem_devtype: + * @udev_device: udev device to start searching from + * @subsystem: the subsystem of the device + * @devtype: the type (DEVTYPE) of the device + * + * Find the next parent device, with a matching subsystem and devtype + * value, and fill in information from the sys device and the udev + * database entry. + * + * If devtype is #NULL, only subsystem is checked, and any devtype will + * match. + * + * Returned device is not referenced. It is attached to the child + * device, and will be cleaned up when the child device is cleaned up. + * + * It can be called as many times as needed, without caring about + * references. + * + * Returns: a new udev device, or #NULL if no matching parent exists. + **/ +_public_ struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device, const char *subsystem, const char *devtype) +{ + sd_device *parent; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + /* this relies on the fact that finding the subdevice of a parent or the + parent of a subdevice commute */ + + /* first find the correct sd_device */ + r = sd_device_get_parent_with_subsystem_devtype(udev_device->device, subsystem, devtype, &parent); + if (r < 0) { + errno = -r; + return NULL; + } + + /* then walk the chain of udev_device parents until the correspanding + one is found */ + while ((udev_device = udev_device_get_parent(udev_device))) { + if (udev_device->device == parent) + return udev_device; + } + + errno = ENOENT; + return NULL; +} + +/** + * udev_device_get_udev: + * @udev_device: udev device + * + * Retrieve the udev library context the device was created with. + * + * Returns: the udev library context + **/ +_public_ struct udev *udev_device_get_udev(struct udev_device *udev_device) +{ + assert_return_errno(udev_device, NULL, EINVAL); + + return udev_device->udev; +} + +/** + * udev_device_ref: + * @udev_device: udev device + * + * Take a reference of a udev device. + * + * Returns: the passed udev device + **/ +_public_ struct udev_device *udev_device_ref(struct udev_device *udev_device) +{ + if (udev_device) + udev_device->refcount++; + + return udev_device; +} + +/** + * udev_device_unref: + * @udev_device: udev device + * + * Drop a reference of a udev device. If the refcount reaches zero, + * the resources of the device will be released. + * + * Returns: #NULL + **/ +_public_ struct udev_device *udev_device_unref(struct udev_device *udev_device) +{ + if (udev_device && (-- udev_device->refcount) == 0) { + sd_device_unref(udev_device->device); + udev_device_unref(udev_device->parent); + + udev_list_cleanup(&udev_device->properties); + udev_list_cleanup(&udev_device->sysattrs); + udev_list_cleanup(&udev_device->tags); + udev_list_cleanup(&udev_device->devlinks); + + free(udev_device); + } + + return NULL; +} + +/** + * udev_device_get_devpath: + * @udev_device: udev device + * + * Retrieve the kernel devpath value of the udev device. The path + * does not contain the sys mount point, and starts with a '/'. + * + * Returns: the devpath of the udev device + **/ +_public_ const char *udev_device_get_devpath(struct udev_device *udev_device) +{ + const char *devpath; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_devpath(udev_device->device, &devpath); + if (r < 0) { + errno = -r; + return NULL; + } + + return devpath; +} + +/** + * udev_device_get_syspath: + * @udev_device: udev device + * + * Retrieve the sys path of the udev device. The path is an + * absolute path and starts with the sys mount point. + * + * Returns: the sys path of the udev device + **/ +_public_ const char *udev_device_get_syspath(struct udev_device *udev_device) +{ + const char *syspath; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_syspath(udev_device->device, &syspath); + if (r < 0) { + errno = -r; + return NULL; + } + + return syspath; +} + +/** + * udev_device_get_sysname: + * @udev_device: udev device + * + * Get the kernel device name in /sys. + * + * Returns: the name string of the device device + **/ +_public_ const char *udev_device_get_sysname(struct udev_device *udev_device) +{ + const char *sysname; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_sysname(udev_device->device, &sysname); + if (r < 0) { + errno = -r; + return NULL; + } + + return sysname; +} + +/** + * udev_device_get_sysnum: + * @udev_device: udev device + * + * Get the instance number of the device. + * + * Returns: the trailing number string of the device name + **/ +_public_ const char *udev_device_get_sysnum(struct udev_device *udev_device) +{ + const char *sysnum; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_sysnum(udev_device->device, &sysnum); + if (r < 0) { + errno = -r; + return NULL; + } + + return sysnum; +} + +/** + * udev_device_get_devnode: + * @udev_device: udev device + * + * Retrieve the device node file name belonging to the udev device. + * The path is an absolute path, and starts with the device directory. + * + * Returns: the device node file name of the udev device, or #NULL if no device node exists + **/ +_public_ const char *udev_device_get_devnode(struct udev_device *udev_device) +{ + const char *devnode; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_devname(udev_device->device, &devnode); + if (r < 0) { + errno = -r; + return NULL; + } + + return devnode; +} + +/** + * udev_device_get_devlinks_list_entry: + * @udev_device: udev device + * + * Retrieve the list of device links pointing to the device file of + * the udev device. The next list entry can be retrieved with + * udev_list_entry_get_next(), which returns #NULL if no more entries exist. + * The devlink path can be retrieved from the list entry by + * udev_list_entry_get_name(). The path is an absolute path, and starts with + * the device directory. + * + * Returns: the first entry of the device node link list + **/ +_public_ struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device) +{ + assert_return_errno(udev_device, NULL, EINVAL); + + if (device_get_devlinks_generation(udev_device->device) != udev_device->devlinks_generation || + !udev_device->devlinks_read) { + const char *devlink; + + udev_list_cleanup(&udev_device->devlinks); + + FOREACH_DEVICE_DEVLINK(udev_device->device, devlink) + udev_list_entry_add(&udev_device->devlinks, devlink, NULL); + + udev_device->devlinks_read = true; + udev_device->devlinks_generation = device_get_devlinks_generation(udev_device->device); + } + + return udev_list_get_entry(&udev_device->devlinks); +} + +/** + * udev_device_get_event_properties_entry: + * @udev_device: udev device + * + * Retrieve the list of key/value device properties of the udev + * device. The next list entry can be retrieved with udev_list_entry_get_next(), + * which returns #NULL if no more entries exist. The property name + * can be retrieved from the list entry by udev_list_entry_get_name(), + * the property value by udev_list_entry_get_value(). + * + * Returns: the first entry of the property list + **/ +_public_ struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device) +{ + assert_return_errno(udev_device, NULL, EINVAL); + + if (device_get_properties_generation(udev_device->device) != udev_device->properties_generation || + !udev_device->properties_read) { + const char *key, *value; + + udev_list_cleanup(&udev_device->properties); + + FOREACH_DEVICE_PROPERTY(udev_device->device, key, value) + udev_list_entry_add(&udev_device->properties, key, value); + + udev_device->properties_read = true; + udev_device->properties_generation = device_get_properties_generation(udev_device->device); + } + + return udev_list_get_entry(&udev_device->properties); +} + +/** + * udev_device_get_action: + * @udev_device: udev device + * + * This is only valid if the device was received through a monitor. Devices read from + * sys do not have an action string. Usual actions are: add, remove, change, online, + * offline. + * + * Returns: the kernel action value, or #NULL if there is no action value available. + **/ +_public_ const char *udev_device_get_action(struct udev_device *udev_device) { + const char *action = NULL; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_property_value(udev_device->device, "ACTION", &action); + if (r < 0 && r != -ENOENT) { + errno = -r; + return NULL; + } + + return action; +} + +/** + * udev_device_get_usec_since_initialized: + * @udev_device: udev device + * + * Return the number of microseconds passed since udev set up the + * device for the first time. + * + * This is only implemented for devices with need to store properties + * in the udev database. All other devices return 0 here. + * + * Returns: the number of microseconds since the device was first seen. + **/ +_public_ unsigned long long int udev_device_get_usec_since_initialized(struct udev_device *udev_device) +{ + usec_t ts; + int r; + + assert_return(udev_device, -EINVAL); + + r = sd_device_get_usec_since_initialized(udev_device->device, &ts); + if (r < 0) { + errno = EINVAL; + return 0; + } + + return ts; +} + +/** + * udev_device_get_sysattr_value: + * @udev_device: udev device + * @sysattr: attribute name + * + * The retrieved value is cached in the device. Repeated calls will return the same + * value and not open the attribute again. + * + * Returns: the content of a sys attribute file, or #NULL if there is no sys attribute value. + **/ +_public_ const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr) +{ + const char *value; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_sysattr_value(udev_device->device, sysattr, &value); + if (r < 0) { + errno = -r; + return NULL; + } + + return value; +} + +/** + * udev_device_set_sysattr_value: + * @udev_device: udev device + * @sysattr: attribute name + * @value: new value to be set + * + * Update the contents of the sys attribute and the cached value of the device. + * + * Returns: Negative error code on failure or 0 on success. + **/ +_public_ int udev_device_set_sysattr_value(struct udev_device *udev_device, const char *sysattr, char *value) +{ + int r; + + assert_return(udev_device, -EINVAL); + + r = sd_device_set_sysattr_value(udev_device->device, sysattr, value); + if (r < 0) + return r; + + return 0; +} + +/** + * udev_device_get_sysattr_list_entry: + * @udev_device: udev device + * + * Retrieve the list of available sysattrs, with value being empty; + * This just return all available sysfs attributes for a particular + * device without reading their values. + * + * Returns: the first entry of the property list + **/ +_public_ struct udev_list_entry *udev_device_get_sysattr_list_entry(struct udev_device *udev_device) +{ + assert_return_errno(udev_device, NULL, EINVAL); + + if (!udev_device->sysattrs_read) { + const char *sysattr; + + udev_list_cleanup(&udev_device->sysattrs); + + FOREACH_DEVICE_SYSATTR(udev_device->device, sysattr) + udev_list_entry_add(&udev_device->sysattrs, sysattr, NULL); + + udev_device->sysattrs_read = true; + } + + return udev_list_get_entry(&udev_device->sysattrs); +} + +/** + * udev_device_get_is_initialized: + * @udev_device: udev device + * + * Check if udev has already handled the device and has set up + * device node permissions and context, or has renamed a network + * device. + * + * This is only implemented for devices with a device node + * or network interfaces. All other devices return 1 here. + * + * Returns: 1 if the device is set up. 0 otherwise. + **/ +_public_ int udev_device_get_is_initialized(struct udev_device *udev_device) +{ + int r, initialized; + + assert_return(udev_device, -EINVAL); + + r = sd_device_get_is_initialized(udev_device->device, &initialized); + if (r < 0) { + errno = -r; + + return 0; + } + + return initialized; +} + +/** + * udev_device_get_tags_list_entry: + * @udev_device: udev device + * + * Retrieve the list of tags attached to the udev device. The next + * list entry can be retrieved with udev_list_entry_get_next(), + * which returns #NULL if no more entries exist. The tag string + * can be retrieved from the list entry by udev_list_entry_get_name(). + * + * Returns: the first entry of the tag list + **/ +_public_ struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device) +{ + assert_return_errno(udev_device, NULL, EINVAL); + + if (device_get_tags_generation(udev_device->device) != udev_device->tags_generation || + !udev_device->tags_read) { + const char *tag; + + udev_list_cleanup(&udev_device->tags); + + FOREACH_DEVICE_TAG(udev_device->device, tag) + udev_list_entry_add(&udev_device->tags, tag, NULL); + + udev_device->tags_read = true; + udev_device->tags_generation = device_get_tags_generation(udev_device->device); + } + + return udev_list_get_entry(&udev_device->tags); +} + +/** + * udev_device_has_tag: + * @udev_device: udev device + * @tag: tag name + * + * Check if a given device has a certain tag associated. + * + * Returns: 1 if the tag is found. 0 otherwise. + **/ +_public_ int udev_device_has_tag(struct udev_device *udev_device, const char *tag) +{ + assert_return(udev_device, 0); + + return sd_device_has_tag(udev_device->device, tag); +} diff --git a/src/libudev/src/libudev-enumerate.c b/src/libudev/src/libudev-enumerate.c new file mode 100644 index 0000000000..9910cea957 --- /dev/null +++ b/src/libudev/src/libudev-enumerate.c @@ -0,0 +1,419 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2015 Tom Gundersen + + 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 +#include +#include +#include +#include +#include +#include +#include + +#include "libudev.h" +#include + +#include "alloc-util.h" +#include "device-enumerator-private.h" +#include "device-util.h" +#include "libudev-device-internal.h" + +/** + * SECTION:libudev-enumerate + * @short_description: lookup and sort sys devices + * + * Lookup devices in the sys filesystem, filter devices by properties, + * and return a sorted list of devices. + */ + +/** + * udev_enumerate: + * + * Opaque object representing one device lookup/sort context. + */ +struct udev_enumerate { + struct udev *udev; + int refcount; + struct udev_list devices_list; + bool devices_uptodate:1; + + sd_device_enumerator *enumerator; +}; + +/** + * udev_enumerate_new: + * @udev: udev library context + * + * Create an enumeration context to scan /sys. + * + * Returns: an enumeration context. + **/ +_public_ struct udev_enumerate *udev_enumerate_new(struct udev *udev) { + _cleanup_free_ struct udev_enumerate *udev_enumerate = NULL; + struct udev_enumerate *ret; + int r; + + assert_return_errno(udev, NULL, EINVAL); + + udev_enumerate = new0(struct udev_enumerate, 1); + if (!udev_enumerate) { + errno = ENOMEM; + return NULL; + } + + r = sd_device_enumerator_new(&udev_enumerate->enumerator); + if (r < 0) { + errno = -r; + return NULL; + } + + r = sd_device_enumerator_allow_uninitialized(udev_enumerate->enumerator); + if (r < 0) { + errno = -r; + return NULL; + } + + udev_enumerate->refcount = 1; + udev_enumerate->udev = udev; + + udev_list_init(udev, &udev_enumerate->devices_list, false); + + ret = udev_enumerate; + udev_enumerate = NULL; + + return ret; +} + +/** + * udev_enumerate_ref: + * @udev_enumerate: context + * + * Take a reference of a enumeration context. + * + * Returns: the passed enumeration context + **/ +_public_ struct udev_enumerate *udev_enumerate_ref(struct udev_enumerate *udev_enumerate) { + if (udev_enumerate) + udev_enumerate->refcount++; + + return udev_enumerate; +} + +/** + * udev_enumerate_unref: + * @udev_enumerate: context + * + * Drop a reference of an enumeration context. If the refcount reaches zero, + * all resources of the enumeration context will be released. + * + * Returns: #NULL + **/ +_public_ struct udev_enumerate *udev_enumerate_unref(struct udev_enumerate *udev_enumerate) { + if (udev_enumerate && (-- udev_enumerate->refcount) == 0) { + udev_list_cleanup(&udev_enumerate->devices_list); + sd_device_enumerator_unref(udev_enumerate->enumerator); + free(udev_enumerate); + } + + return NULL; +} + +/** + * udev_enumerate_get_udev: + * @udev_enumerate: context + * + * Get the udev library context. + * + * Returns: a pointer to the context. + */ +_public_ struct udev *udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate) { + assert_return_errno(udev_enumerate, NULL, EINVAL); + + return udev_enumerate->udev; +} + +/** + * udev_enumerate_get_list_entry: + * @udev_enumerate: context + * + * Get the first entry of the sorted list of device paths. + * + * Returns: a udev_list_entry. + */ +_public_ struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate) { + assert_return_errno(udev_enumerate, NULL, EINVAL); + + if (!udev_enumerate->devices_uptodate) { + sd_device *device; + + udev_list_cleanup(&udev_enumerate->devices_list); + + FOREACH_DEVICE_AND_SUBSYSTEM(udev_enumerate->enumerator, device) { + const char *syspath; + int r; + + r = sd_device_get_syspath(device, &syspath); + if (r < 0) { + errno = -r; + return NULL; + } + + udev_list_entry_add(&udev_enumerate->devices_list, syspath, NULL); + } + + udev_enumerate->devices_uptodate = true; + } + + return udev_list_get_entry(&udev_enumerate->devices_list); +} + +/** + * udev_enumerate_add_match_subsystem: + * @udev_enumerate: context + * @subsystem: filter for a subsystem of the device to include in the list + * + * Match only devices belonging to a certain kernel subsystem. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem) { + assert_return(udev_enumerate, -EINVAL); + + if (!subsystem) + return 0; + + return sd_device_enumerator_add_match_subsystem(udev_enumerate->enumerator, subsystem, true); +} + +/** + * udev_enumerate_add_nomatch_subsystem: + * @udev_enumerate: context + * @subsystem: filter for a subsystem of the device to exclude from the list + * + * Match only devices not belonging to a certain kernel subsystem. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem) { + assert_return(udev_enumerate, -EINVAL); + + if (!subsystem) + return 0; + + return sd_device_enumerator_add_match_subsystem(udev_enumerate->enumerator, subsystem, false); +} + +/** + * udev_enumerate_add_match_sysattr: + * @udev_enumerate: context + * @sysattr: filter for a sys attribute at the device to include in the list + * @value: optional value of the sys attribute + * + * Match only devices with a certain /sys device attribute. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value) { + assert_return(udev_enumerate, -EINVAL); + + if (!sysattr) + return 0; + + return sd_device_enumerator_add_match_sysattr(udev_enumerate->enumerator, sysattr, value, true); +} + +/** + * udev_enumerate_add_nomatch_sysattr: + * @udev_enumerate: context + * @sysattr: filter for a sys attribute at the device to exclude from the list + * @value: optional value of the sys attribute + * + * Match only devices not having a certain /sys device attribute. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value) { + assert_return(udev_enumerate, -EINVAL); + + if (!sysattr) + return 0; + + return sd_device_enumerator_add_match_sysattr(udev_enumerate->enumerator, sysattr, value, false); +} + +/** + * udev_enumerate_add_match_property: + * @udev_enumerate: context + * @property: filter for a property of the device to include in the list + * @value: value of the property + * + * Match only devices with a certain property. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value) { + assert_return(udev_enumerate, -EINVAL); + + if (!property) + return 0; + + return sd_device_enumerator_add_match_property(udev_enumerate->enumerator, property, value); +} + +/** + * udev_enumerate_add_match_tag: + * @udev_enumerate: context + * @tag: filter for a tag of the device to include in the list + * + * Match only devices with a certain tag. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag) { + assert_return(udev_enumerate, -EINVAL); + + if (!tag) + return 0; + + return sd_device_enumerator_add_match_tag(udev_enumerate->enumerator, tag); +} + +/** + * udev_enumerate_add_match_parent: + * @udev_enumerate: context + * @parent: parent device where to start searching + * + * Return the devices on the subtree of one given device. The parent + * itself is included in the list. + * + * A reference for the device is held until the udev_enumerate context + * is cleaned up. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_parent(struct udev_enumerate *udev_enumerate, struct udev_device *parent) { + assert_return(udev_enumerate, -EINVAL); + + if (!parent) + return 0; + + return sd_device_enumerator_add_match_parent(udev_enumerate->enumerator, parent->device); +} + +/** + * udev_enumerate_add_match_is_initialized: + * @udev_enumerate: context + * + * Match only devices which udev has set up already. This makes + * sure, that the device node permissions and context are properly set + * and that network devices are fully renamed. + * + * Usually, devices which are found in the kernel but not already + * handled by udev, have still pending events. Services should subscribe + * to monitor events and wait for these devices to become ready, instead + * of using uninitialized devices. + * + * For now, this will not affect devices which do not have a device node + * and are not network interfaces. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_is_initialized(struct udev_enumerate *udev_enumerate) { + assert_return(udev_enumerate, -EINVAL); + + return device_enumerator_add_match_is_initialized(udev_enumerate->enumerator); +} + +/** + * udev_enumerate_add_match_sysname: + * @udev_enumerate: context + * @sysname: filter for the name of the device to include in the list + * + * Match only devices with a given /sys device name. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname) { + assert_return(udev_enumerate, -EINVAL); + + if (!sysname) + return 0; + + return sd_device_enumerator_add_match_sysname(udev_enumerate->enumerator, sysname); +} + +/** + * udev_enumerate_add_syspath: + * @udev_enumerate: context + * @syspath: path of a device + * + * Add a device to the list of devices, to retrieve it back sorted in dependency order. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + int r; + + assert_return(udev_enumerate, -EINVAL); + + if (!syspath) + return 0; + + r = sd_device_new_from_syspath(&device, syspath); + if (r < 0) + return r; + + r = device_enumerator_add_device(udev_enumerate->enumerator, device); + if (r < 0) + return r; + + return 0; +} + +/** + * udev_enumerate_scan_devices: + * @udev_enumerate: udev enumeration context + * + * Scan /sys for all devices which match the given filters. No matches + * will return all currently available devices. + * + * Returns: 0 on success, otherwise a negative error value. + **/ +_public_ int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate) { + assert_return(udev_enumerate, -EINVAL); + + return device_enumerator_scan_devices(udev_enumerate->enumerator); +} + +/** + * udev_enumerate_scan_subsystems: + * @udev_enumerate: udev enumeration context + * + * Scan /sys for all kernel subsystems, including buses, classes, drivers. + * + * Returns: 0 on success, otherwise a negative error value. + **/ +_public_ int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate) { + assert_return(udev_enumerate, -EINVAL); + + return device_enumerator_scan_subsystems(udev_enumerate->enumerator); +} diff --git a/src/libudev/src/libudev-hwdb.c b/src/libudev/src/libudev-hwdb.c new file mode 100644 index 0000000000..8c4b488086 --- /dev/null +++ b/src/libudev/src/libudev-hwdb.c @@ -0,0 +1,146 @@ +/*** + This file is part of systemd. + + Copyright Tom Gundersen + + 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 "alloc-util.h" +#include "hwdb-util.h" +#include "libudev-private.h" + +/** + * SECTION:libudev-hwdb + * @short_description: retrieve properties from the hardware database + * + * Libudev hardware database interface. + */ + +/** + * udev_hwdb: + * + * Opaque object representing the hardware database. + */ +struct udev_hwdb { + struct udev *udev; + int refcount; + + sd_hwdb *hwdb; + + struct udev_list properties_list; +}; + +/** + * udev_hwdb_new: + * @udev: udev library context + * + * Create a hardware database context to query properties for devices. + * + * Returns: a hwdb context. + **/ +_public_ struct udev_hwdb *udev_hwdb_new(struct udev *udev) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb_internal = NULL; + struct udev_hwdb *hwdb; + int r; + + assert_return(udev, NULL); + + r = sd_hwdb_new(&hwdb_internal); + if (r < 0) + return NULL; + + hwdb = new0(struct udev_hwdb, 1); + if (!hwdb) + return NULL; + + hwdb->refcount = 1; + hwdb->hwdb = hwdb_internal; + hwdb_internal = NULL; + + udev_list_init(udev, &hwdb->properties_list, true); + + return hwdb; +} + +/** + * udev_hwdb_ref: + * @hwdb: context + * + * Take a reference of a hwdb context. + * + * Returns: the passed enumeration context + **/ +_public_ struct udev_hwdb *udev_hwdb_ref(struct udev_hwdb *hwdb) { + if (!hwdb) + return NULL; + hwdb->refcount++; + return hwdb; +} + +/** + * udev_hwdb_unref: + * @hwdb: context + * + * Drop a reference of a hwdb context. If the refcount reaches zero, + * all resources of the hwdb context will be released. + * + * Returns: #NULL + **/ +_public_ struct udev_hwdb *udev_hwdb_unref(struct udev_hwdb *hwdb) { + if (!hwdb) + return NULL; + hwdb->refcount--; + if (hwdb->refcount > 0) + return NULL; + sd_hwdb_unref(hwdb->hwdb); + udev_list_cleanup(&hwdb->properties_list); + free(hwdb); + return NULL; +} + +/** + * udev_hwdb_get_properties_list_entry: + * @hwdb: context + * @modalias: modalias string + * @flags: (unused) + * + * Lookup a matching device in the hardware database. The lookup key is a + * modalias string, whose formats are defined for the Linux kernel modules. + * Examples are: pci:v00008086d00001C2D*, usb:v04F2pB221*. The first entry + * of a list of retrieved properties is returned. + * + * Returns: a udev_list_entry. + */ +_public_ struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned int flags) { + const char *key, *value; + + if (!hwdb || !modalias) { + errno = EINVAL; + return NULL; + } + + udev_list_cleanup(&hwdb->properties_list); + + SD_HWDB_FOREACH_PROPERTY(hwdb->hwdb, modalias, key, value) { + if (udev_list_entry_add(&hwdb->properties_list, key, value) == NULL) { + errno = ENOMEM; + return NULL; + } + } + + return udev_list_get_entry(&hwdb->properties_list); +} diff --git a/src/libudev/src/libudev-list.c b/src/libudev/src/libudev-list.c new file mode 100644 index 0000000000..da496ed456 --- /dev/null +++ b/src/libudev/src/libudev-list.c @@ -0,0 +1,352 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 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 +#include +#include +#include + +#include "alloc-util.h" +#include "libudev-private.h" + +/** + * SECTION:libudev-list + * @short_description: list operation + * + * Libudev list operations. + */ + +/** + * udev_list_entry: + * + * Opaque object representing one entry in a list. An entry contains + * contains a name, and optionally a value. + */ +struct udev_list_entry { + struct udev_list_node node; + struct udev_list *list; + char *name; + char *value; + int num; +}; + +/* the list's head points to itself if empty */ +void udev_list_node_init(struct udev_list_node *list) +{ + list->next = list; + list->prev = list; +} + +int udev_list_node_is_empty(struct udev_list_node *list) +{ + return list->next == list; +} + +static void udev_list_node_insert_between(struct udev_list_node *new, + struct udev_list_node *prev, + struct udev_list_node *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +void udev_list_node_append(struct udev_list_node *new, struct udev_list_node *list) +{ + udev_list_node_insert_between(new, list->prev, list); +} + +void udev_list_node_remove(struct udev_list_node *entry) +{ + struct udev_list_node *prev = entry->prev; + struct udev_list_node *next = entry->next; + + next->prev = prev; + prev->next = next; + + entry->prev = NULL; + entry->next = NULL; +} + +/* return list entry which embeds this node */ +static inline struct udev_list_entry *list_node_to_entry(struct udev_list_node *node) +{ + return container_of(node, struct udev_list_entry, node); +} + +void udev_list_init(struct udev *udev, struct udev_list *list, bool unique) +{ + memzero(list, sizeof(struct udev_list)); + list->udev = udev; + list->unique = unique; + udev_list_node_init(&list->node); +} + +/* insert entry into a list as the last element */ +static void udev_list_entry_append(struct udev_list_entry *new, struct udev_list *list) +{ + /* inserting before the list head make the node the last node in the list */ + udev_list_node_insert_between(&new->node, list->node.prev, &list->node); + new->list = list; +} + +/* insert entry into a list, before a given existing entry */ +static void udev_list_entry_insert_before(struct udev_list_entry *new, struct udev_list_entry *entry) +{ + udev_list_node_insert_between(&new->node, entry->node.prev, &entry->node); + new->list = entry->list; +} + +/* binary search in sorted array */ +static int list_search(struct udev_list *list, const char *name) +{ + unsigned int first, last; + + first = 0; + last = list->entries_cur; + while (first < last) { + unsigned int i; + int cmp; + + i = (first + last)/2; + cmp = strcmp(name, list->entries[i]->name); + if (cmp < 0) + last = i; + else if (cmp > 0) + first = i+1; + else + return i; + } + + /* not found, return negative insertion-index+1 */ + return -(first+1); +} + +struct udev_list_entry *udev_list_entry_add(struct udev_list *list, const char *name, const char *value) +{ + struct udev_list_entry *entry; + int i = 0; + + if (list->unique) { + /* lookup existing name or insertion-index */ + i = list_search(list, name); + if (i >= 0) { + entry = list->entries[i]; + + free(entry->value); + if (value == NULL) { + entry->value = NULL; + return entry; + } + entry->value = strdup(value); + if (entry->value == NULL) + return NULL; + return entry; + } + } + + /* add new name */ + entry = new0(struct udev_list_entry, 1); + if (entry == NULL) + return NULL; + entry->name = strdup(name); + if (entry->name == NULL) { + free(entry); + return NULL; + } + if (value != NULL) { + entry->value = strdup(value); + if (entry->value == NULL) { + free(entry->name); + free(entry); + return NULL; + } + } + + if (list->unique) { + /* allocate or enlarge sorted array if needed */ + if (list->entries_cur >= list->entries_max) { + struct udev_list_entry **entries; + unsigned int add; + + add = list->entries_max; + if (add < 1) + add = 64; + entries = realloc(list->entries, (list->entries_max + add) * sizeof(struct udev_list_entry *)); + if (entries == NULL) { + free(entry->name); + free(entry->value); + free(entry); + return NULL; + } + list->entries = entries; + list->entries_max += add; + } + + /* the negative i returned the insertion index */ + i = (-i)-1; + + /* insert into sorted list */ + if ((unsigned int)i < list->entries_cur) + udev_list_entry_insert_before(entry, list->entries[i]); + else + udev_list_entry_append(entry, list); + + /* insert into sorted array */ + memmove(&list->entries[i+1], &list->entries[i], + (list->entries_cur - i) * sizeof(struct udev_list_entry *)); + list->entries[i] = entry; + list->entries_cur++; + } else { + udev_list_entry_append(entry, list); + } + + return entry; +} + +void udev_list_entry_delete(struct udev_list_entry *entry) +{ + if (entry->list->entries != NULL) { + int i; + struct udev_list *list = entry->list; + + /* remove entry from sorted array */ + i = list_search(list, entry->name); + if (i >= 0) { + memmove(&list->entries[i], &list->entries[i+1], + ((list->entries_cur-1) - i) * sizeof(struct udev_list_entry *)); + list->entries_cur--; + } + } + + udev_list_node_remove(&entry->node); + free(entry->name); + free(entry->value); + free(entry); +} + +void udev_list_cleanup(struct udev_list *list) +{ + struct udev_list_entry *entry_loop; + struct udev_list_entry *entry_tmp; + + list->entries = mfree(list->entries); + list->entries_cur = 0; + list->entries_max = 0; + udev_list_entry_foreach_safe(entry_loop, entry_tmp, udev_list_get_entry(list)) + udev_list_entry_delete(entry_loop); +} + +struct udev_list_entry *udev_list_get_entry(struct udev_list *list) +{ + if (udev_list_node_is_empty(&list->node)) + return NULL; + return list_node_to_entry(list->node.next); +} + +/** + * udev_list_entry_get_next: + * @list_entry: current entry + * + * Get the next entry from the list. + * + * Returns: udev_list_entry, #NULL if no more entries are available. + */ +_public_ struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry) +{ + struct udev_list_node *next; + + if (list_entry == NULL) + return NULL; + next = list_entry->node.next; + /* empty list or no more entries */ + if (next == &list_entry->list->node) + return NULL; + return list_node_to_entry(next); +} + +/** + * udev_list_entry_get_by_name: + * @list_entry: current entry + * @name: name string to match + * + * Lookup an entry in the list with a certain name. + * + * Returns: udev_list_entry, #NULL if no matching entry is found. + */ +_public_ struct udev_list_entry *udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name) +{ + int i; + + if (list_entry == NULL) + return NULL; + + if (!list_entry->list->unique) + return NULL; + + i = list_search(list_entry->list, name); + if (i < 0) + return NULL; + return list_entry->list->entries[i]; +} + +/** + * udev_list_entry_get_name: + * @list_entry: current entry + * + * Get the name of a list entry. + * + * Returns: the name string of this entry. + */ +_public_ const char *udev_list_entry_get_name(struct udev_list_entry *list_entry) +{ + if (list_entry == NULL) + return NULL; + return list_entry->name; +} + +/** + * udev_list_entry_get_value: + * @list_entry: current entry + * + * Get the value of list entry. + * + * Returns: the value string of this entry. + */ +_public_ const char *udev_list_entry_get_value(struct udev_list_entry *list_entry) +{ + if (list_entry == NULL) + return NULL; + return list_entry->value; +} + +int udev_list_entry_get_num(struct udev_list_entry *list_entry) +{ + if (list_entry == NULL) + return -EINVAL; + return list_entry->num; +} + +void udev_list_entry_set_num(struct udev_list_entry *list_entry, int num) +{ + if (list_entry == NULL) + return; + list_entry->num = num; +} diff --git a/src/libudev/src/libudev-monitor.c b/src/libudev/src/libudev-monitor.c new file mode 100644 index 0000000000..f870eba9eb --- /dev/null +++ b/src/libudev/src/libudev-monitor.c @@ -0,0 +1,845 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libudev.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "libudev-private.h" +#include "missing.h" +#include "mount-util.h" +#include "socket-util.h" +#include "string-util.h" + +/** + * SECTION:libudev-monitor + * @short_description: device event source + * + * Connects to a device event source. + */ + +/** + * udev_monitor: + * + * Opaque object handling an event source. + */ +struct udev_monitor { + struct udev *udev; + int refcount; + int sock; + union sockaddr_union snl; + union sockaddr_union snl_trusted_sender; + union sockaddr_union snl_destination; + socklen_t addrlen; + struct udev_list filter_subsystem_list; + struct udev_list filter_tag_list; + bool bound; +}; + +enum udev_monitor_netlink_group { + UDEV_MONITOR_NONE, + UDEV_MONITOR_KERNEL, + UDEV_MONITOR_UDEV, +}; + +#define UDEV_MONITOR_MAGIC 0xfeedcafe +struct udev_monitor_netlink_header { + /* "libudev" prefix to distinguish libudev and kernel messages */ + char prefix[8]; + /* + * magic to protect against daemon <-> library message format mismatch + * used in the kernel from socket filter rules; needs to be stored in network order + */ + unsigned int magic; + /* total length of header structure known to the sender */ + unsigned int header_size; + /* properties string buffer */ + unsigned int properties_off; + unsigned int properties_len; + /* + * hashes of primary device properties strings, to let libudev subscribers + * use in-kernel socket filters; values need to be stored in network order + */ + unsigned int filter_subsystem_hash; + unsigned int filter_devtype_hash; + unsigned int filter_tag_bloom_hi; + unsigned int filter_tag_bloom_lo; +}; + +static struct udev_monitor *udev_monitor_new(struct udev *udev) +{ + struct udev_monitor *udev_monitor; + + udev_monitor = new0(struct udev_monitor, 1); + if (udev_monitor == NULL) + return NULL; + udev_monitor->refcount = 1; + udev_monitor->udev = udev; + udev_list_init(udev, &udev_monitor->filter_subsystem_list, false); + udev_list_init(udev, &udev_monitor->filter_tag_list, true); + return udev_monitor; +} + +/* we consider udev running when /dev is on devtmpfs */ +static bool udev_has_devtmpfs(struct udev *udev) { + + union file_handle_union h = FILE_HANDLE_INIT; + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX], *e; + int mount_id; + int r; + + r = name_to_handle_at(AT_FDCWD, "/dev", &h.handle, &mount_id, 0); + if (r < 0) { + if (errno != EOPNOTSUPP) + log_debug_errno(errno, "name_to_handle_at on /dev: %m"); + return false; + } + + f = fopen("/proc/self/mountinfo", "re"); + if (!f) + return false; + + FOREACH_LINE(line, f, return false) { + int mid; + + if (sscanf(line, "%i", &mid) != 1) + continue; + + if (mid != mount_id) + continue; + + e = strstr(line, " - "); + if (!e) + continue; + + /* accept any name that starts with the currently expected type */ + if (startswith(e + 3, "devtmpfs")) + return true; + } + + return false; +} + +static void monitor_set_nl_address(struct udev_monitor *udev_monitor) { + union sockaddr_union snl; + socklen_t addrlen; + int r; + + assert(udev_monitor); + + /* get the address the kernel has assigned us + * it is usually, but not necessarily the pid + */ + addrlen = sizeof(struct sockaddr_nl); + r = getsockname(udev_monitor->sock, &snl.sa, &addrlen); + if (r >= 0) + udev_monitor->snl.nl.nl_pid = snl.nl.nl_pid; +} + +struct udev_monitor *udev_monitor_new_from_netlink_fd(struct udev *udev, const char *name, int fd) +{ + struct udev_monitor *udev_monitor; + unsigned int group; + + if (udev == NULL) + return NULL; + + if (name == NULL) + group = UDEV_MONITOR_NONE; + else if (streq(name, "udev")) { + /* + * We do not support subscribing to uevents if no instance of + * udev is running. Uevents would otherwise broadcast the + * processing data of the host into containers, which is not + * desired. + * + * Containers will currently not get any udev uevents, until + * a supporting infrastructure is available. + * + * We do not set a netlink multicast group here, so the socket + * will not receive any messages. + */ + if (access("/run/udev/control", F_OK) < 0 && !udev_has_devtmpfs(udev)) { + log_debug("the udev service seems not to be active, disable the monitor"); + group = UDEV_MONITOR_NONE; + } else + group = UDEV_MONITOR_UDEV; + } else if (streq(name, "kernel")) + group = UDEV_MONITOR_KERNEL; + else + return NULL; + + udev_monitor = udev_monitor_new(udev); + if (udev_monitor == NULL) + return NULL; + + if (fd < 0) { + udev_monitor->sock = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_KOBJECT_UEVENT); + if (udev_monitor->sock < 0) { + log_debug_errno(errno, "error getting socket: %m"); + free(udev_monitor); + return NULL; + } + } else { + udev_monitor->bound = true; + udev_monitor->sock = fd; + monitor_set_nl_address(udev_monitor); + } + + udev_monitor->snl.nl.nl_family = AF_NETLINK; + udev_monitor->snl.nl.nl_groups = group; + + /* default destination for sending */ + udev_monitor->snl_destination.nl.nl_family = AF_NETLINK; + udev_monitor->snl_destination.nl.nl_groups = UDEV_MONITOR_UDEV; + + return udev_monitor; +} + +/** + * udev_monitor_new_from_netlink: + * @udev: udev library context + * @name: name of event source + * + * Create new udev monitor and connect to a specified event + * source. Valid sources identifiers are "udev" and "kernel". + * + * Applications should usually not connect directly to the + * "kernel" events, because the devices might not be useable + * at that time, before udev has configured them, and created + * device nodes. Accessing devices at the same time as udev, + * might result in unpredictable behavior. The "udev" events + * are sent out after udev has finished its event processing, + * all rules have been processed, and needed device nodes are + * created. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev monitor. + * + * Returns: a new udev monitor, or #NULL, in case of an error + **/ +_public_ struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name) +{ + return udev_monitor_new_from_netlink_fd(udev, name, -1); +} + +static inline void bpf_stmt(struct sock_filter *inss, unsigned int *i, + unsigned short code, unsigned int data) +{ + struct sock_filter *ins = &inss[*i]; + + ins->code = code; + ins->k = data; + (*i)++; +} + +static inline void bpf_jmp(struct sock_filter *inss, unsigned int *i, + unsigned short code, unsigned int data, + unsigned short jt, unsigned short jf) +{ + struct sock_filter *ins = &inss[*i]; + + ins->code = code; + ins->jt = jt; + ins->jf = jf; + ins->k = data; + (*i)++; +} + +/** + * udev_monitor_filter_update: + * @udev_monitor: monitor + * + * Update the installed socket filter. This is only needed, + * if the filter was removed or changed. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_monitor_filter_update(struct udev_monitor *udev_monitor) +{ + struct sock_filter ins[512]; + struct sock_fprog filter; + unsigned int i; + struct udev_list_entry *list_entry; + int err; + + if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) == NULL && + udev_list_get_entry(&udev_monitor->filter_tag_list) == NULL) + return 0; + + memzero(ins, sizeof(ins)); + i = 0; + + /* load magic in A */ + bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, magic)); + /* jump if magic matches */ + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, UDEV_MONITOR_MAGIC, 1, 0); + /* wrong magic, pass packet */ + bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); + + if (udev_list_get_entry(&udev_monitor->filter_tag_list) != NULL) { + int tag_matches; + + /* count tag matches, to calculate end of tag match block */ + tag_matches = 0; + udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_tag_list)) + tag_matches++; + + /* add all tags matches */ + udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_tag_list)) { + uint64_t tag_bloom_bits = util_string_bloom64(udev_list_entry_get_name(list_entry)); + uint32_t tag_bloom_hi = tag_bloom_bits >> 32; + uint32_t tag_bloom_lo = tag_bloom_bits & 0xffffffff; + + /* load device bloom bits in A */ + bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_tag_bloom_hi)); + /* clear bits (tag bits & bloom bits) */ + bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_hi); + /* jump to next tag if it does not match */ + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_hi, 0, 3); + + /* load device bloom bits in A */ + bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_tag_bloom_lo)); + /* clear bits (tag bits & bloom bits) */ + bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_lo); + /* jump behind end of tag match block if tag matches */ + tag_matches--; + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_lo, 1 + (tag_matches * 6), 0); + } + + /* nothing matched, drop packet */ + bpf_stmt(ins, &i, BPF_RET|BPF_K, 0); + } + + /* add all subsystem matches */ + if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) != NULL) { + udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_subsystem_list)) { + unsigned int hash = util_string_hash32(udev_list_entry_get_name(list_entry)); + + /* load device subsystem value in A */ + bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_subsystem_hash)); + if (udev_list_entry_get_value(list_entry) == NULL) { + /* jump if subsystem does not match */ + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1); + } else { + /* jump if subsystem does not match */ + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 3); + + /* load device devtype value in A */ + bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_devtype_hash)); + /* jump if value does not match */ + hash = util_string_hash32(udev_list_entry_get_value(list_entry)); + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1); + } + + /* matched, pass packet */ + bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); + + if (i+1 >= ELEMENTSOF(ins)) + return -E2BIG; + } + + /* nothing matched, drop packet */ + bpf_stmt(ins, &i, BPF_RET|BPF_K, 0); + } + + /* matched, pass packet */ + bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); + + /* install filter */ + memzero(&filter, sizeof(filter)); + filter.len = i; + filter.filter = ins; + err = setsockopt(udev_monitor->sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)); + return err < 0 ? -errno : 0; +} + +int udev_monitor_allow_unicast_sender(struct udev_monitor *udev_monitor, struct udev_monitor *sender) +{ + udev_monitor->snl_trusted_sender.nl.nl_pid = sender->snl.nl.nl_pid; + return 0; +} + +/** + * udev_monitor_enable_receiving: + * @udev_monitor: the monitor which should receive events + * + * Binds the @udev_monitor socket to the event source. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor) +{ + int err = 0; + const int on = 1; + + udev_monitor_filter_update(udev_monitor); + + if (!udev_monitor->bound) { + err = bind(udev_monitor->sock, + &udev_monitor->snl.sa, sizeof(struct sockaddr_nl)); + if (err == 0) + udev_monitor->bound = true; + } + + if (err >= 0) + monitor_set_nl_address(udev_monitor); + else + return log_debug_errno(errno, "bind failed: %m"); + + /* enable receiving of sender credentials */ + err = setsockopt(udev_monitor->sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); + if (err < 0) + log_debug_errno(errno, "setting SO_PASSCRED failed: %m"); + + return 0; +} + +/** + * udev_monitor_set_receive_buffer_size: + * @udev_monitor: the monitor which should receive events + * @size: the size in bytes + * + * Set the size of the kernel socket buffer. This call needs the + * appropriate privileges to succeed. + * + * Returns: 0 on success, otherwise -1 on error. + */ +_public_ int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size) +{ + if (udev_monitor == NULL) + return -EINVAL; + return setsockopt(udev_monitor->sock, SOL_SOCKET, SO_RCVBUFFORCE, &size, sizeof(size)); +} + +int udev_monitor_disconnect(struct udev_monitor *udev_monitor) +{ + int err; + + err = close(udev_monitor->sock); + udev_monitor->sock = -1; + return err < 0 ? -errno : 0; +} + +/** + * udev_monitor_ref: + * @udev_monitor: udev monitor + * + * Take a reference of a udev monitor. + * + * Returns: the passed udev monitor + **/ +_public_ struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor) +{ + if (udev_monitor == NULL) + return NULL; + udev_monitor->refcount++; + return udev_monitor; +} + +/** + * udev_monitor_unref: + * @udev_monitor: udev monitor + * + * Drop a reference of a udev monitor. If the refcount reaches zero, + * the bound socket will be closed, and the resources of the monitor + * will be released. + * + * Returns: #NULL + **/ +_public_ struct udev_monitor *udev_monitor_unref(struct udev_monitor *udev_monitor) +{ + if (udev_monitor == NULL) + return NULL; + udev_monitor->refcount--; + if (udev_monitor->refcount > 0) + return NULL; + if (udev_monitor->sock >= 0) + close(udev_monitor->sock); + udev_list_cleanup(&udev_monitor->filter_subsystem_list); + udev_list_cleanup(&udev_monitor->filter_tag_list); + free(udev_monitor); + return NULL; +} + +/** + * udev_monitor_get_udev: + * @udev_monitor: udev monitor + * + * Retrieve the udev library context the monitor was created with. + * + * Returns: the udev library context + **/ +_public_ struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor) +{ + if (udev_monitor == NULL) + return NULL; + return udev_monitor->udev; +} + +/** + * udev_monitor_get_fd: + * @udev_monitor: udev monitor + * + * Retrieve the socket file descriptor associated with the monitor. + * + * Returns: the socket file descriptor + **/ +_public_ int udev_monitor_get_fd(struct udev_monitor *udev_monitor) +{ + if (udev_monitor == NULL) + return -EINVAL; + return udev_monitor->sock; +} + +static int passes_filter(struct udev_monitor *udev_monitor, struct udev_device *udev_device) +{ + struct udev_list_entry *list_entry; + + if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) == NULL) + goto tag; + udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_subsystem_list)) { + const char *subsys = udev_list_entry_get_name(list_entry); + const char *dsubsys = udev_device_get_subsystem(udev_device); + const char *devtype; + const char *ddevtype; + + if (!streq(dsubsys, subsys)) + continue; + + devtype = udev_list_entry_get_value(list_entry); + if (devtype == NULL) + goto tag; + ddevtype = udev_device_get_devtype(udev_device); + if (ddevtype == NULL) + continue; + if (streq(ddevtype, devtype)) + goto tag; + } + return 0; + +tag: + if (udev_list_get_entry(&udev_monitor->filter_tag_list) == NULL) + return 1; + udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_tag_list)) { + const char *tag = udev_list_entry_get_name(list_entry); + + if (udev_device_has_tag(udev_device, tag)) + return 1; + } + return 0; +} + +/** + * udev_monitor_receive_device: + * @udev_monitor: udev monitor + * + * Receive data from the udev monitor socket, allocate a new udev + * device, fill in the received data, and return the device. + * + * Only socket connections with uid=0 are accepted. + * + * The monitor socket is by default set to NONBLOCK. A variant of poll() on + * the file descriptor returned by udev_monitor_get_fd() should to be used to + * wake up when new devices arrive, or alternatively the file descriptor + * switched into blocking mode. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, in case of an error + **/ +_public_ struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor) +{ + struct udev_device *udev_device; + struct msghdr smsg; + struct iovec iov; + char cred_msg[CMSG_SPACE(sizeof(struct ucred))]; + struct cmsghdr *cmsg; + union sockaddr_union snl; + struct ucred *cred; + union { + struct udev_monitor_netlink_header nlh; + char raw[8192]; + } buf; + ssize_t buflen; + ssize_t bufpos; + bool is_initialized = false; + +retry: + if (udev_monitor == NULL) + return NULL; + iov.iov_base = &buf; + iov.iov_len = sizeof(buf); + memzero(&smsg, sizeof(struct msghdr)); + smsg.msg_iov = &iov; + smsg.msg_iovlen = 1; + smsg.msg_control = cred_msg; + smsg.msg_controllen = sizeof(cred_msg); + smsg.msg_name = &snl; + smsg.msg_namelen = sizeof(snl); + + buflen = recvmsg(udev_monitor->sock, &smsg, 0); + if (buflen < 0) { + if (errno != EINTR) + log_debug("unable to receive message"); + return NULL; + } + + if (buflen < 32 || (smsg.msg_flags & MSG_TRUNC)) { + log_debug("invalid message length"); + return NULL; + } + + if (snl.nl.nl_groups == 0) { + /* unicast message, check if we trust the sender */ + if (udev_monitor->snl_trusted_sender.nl.nl_pid == 0 || + snl.nl.nl_pid != udev_monitor->snl_trusted_sender.nl.nl_pid) { + log_debug("unicast netlink message ignored"); + return NULL; + } + } else if (snl.nl.nl_groups == UDEV_MONITOR_KERNEL) { + if (snl.nl.nl_pid > 0) { + log_debug("multicast kernel netlink message from PID %"PRIu32" ignored", + snl.nl.nl_pid); + return NULL; + } + } + + cmsg = CMSG_FIRSTHDR(&smsg); + if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) { + log_debug("no sender credentials received, message ignored"); + return NULL; + } + + cred = (struct ucred *)CMSG_DATA(cmsg); + if (cred->uid != 0) { + log_debug("sender uid="UID_FMT", message ignored", cred->uid); + return NULL; + } + + if (memcmp(buf.raw, "libudev", 8) == 0) { + /* udev message needs proper version magic */ + if (buf.nlh.magic != htonl(UDEV_MONITOR_MAGIC)) { + log_debug("unrecognized message signature (%x != %x)", + buf.nlh.magic, htonl(UDEV_MONITOR_MAGIC)); + return NULL; + } + if (buf.nlh.properties_off+32 > (size_t)buflen) { + log_debug("message smaller than expected (%u > %zd)", + buf.nlh.properties_off+32, buflen); + return NULL; + } + + bufpos = buf.nlh.properties_off; + + /* devices received from udev are always initialized */ + is_initialized = true; + } else { + /* kernel message with header */ + bufpos = strlen(buf.raw) + 1; + if ((size_t)bufpos < sizeof("a@/d") || bufpos >= buflen) { + log_debug("invalid message length"); + return NULL; + } + + /* check message header */ + if (strstr(buf.raw, "@/") == NULL) { + log_debug("unrecognized message header"); + return NULL; + } + } + + udev_device = udev_device_new_from_nulstr(udev_monitor->udev, &buf.raw[bufpos], buflen - bufpos); + if (!udev_device) { + log_debug("could not create device: %m"); + return NULL; + } + + if (is_initialized) + udev_device_set_is_initialized(udev_device); + + /* skip device, if it does not pass the current filter */ + if (!passes_filter(udev_monitor, udev_device)) { + struct pollfd pfd[1]; + int rc; + + udev_device_unref(udev_device); + + /* if something is queued, get next device */ + pfd[0].fd = udev_monitor->sock; + pfd[0].events = POLLIN; + rc = poll(pfd, 1, 0); + if (rc > 0) + goto retry; + return NULL; + } + + return udev_device; +} + +int udev_monitor_send_device(struct udev_monitor *udev_monitor, + struct udev_monitor *destination, struct udev_device *udev_device) +{ + const char *buf, *val; + ssize_t blen, count; + struct udev_monitor_netlink_header nlh = { + .prefix = "libudev", + .magic = htonl(UDEV_MONITOR_MAGIC), + .header_size = sizeof nlh, + }; + struct iovec iov[2] = { + { .iov_base = &nlh, .iov_len = sizeof nlh }, + }; + struct msghdr smsg = { + .msg_iov = iov, + .msg_iovlen = 2, + }; + struct udev_list_entry *list_entry; + uint64_t tag_bloom_bits; + + blen = udev_device_get_properties_monitor_buf(udev_device, &buf); + if (blen < 32) { + log_debug("device buffer is too small to contain a valid device"); + return -EINVAL; + } + + /* fill in versioned header */ + val = udev_device_get_subsystem(udev_device); + nlh.filter_subsystem_hash = htonl(util_string_hash32(val)); + + val = udev_device_get_devtype(udev_device); + if (val != NULL) + nlh.filter_devtype_hash = htonl(util_string_hash32(val)); + + /* add tag bloom filter */ + tag_bloom_bits = 0; + udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(udev_device)) + tag_bloom_bits |= util_string_bloom64(udev_list_entry_get_name(list_entry)); + if (tag_bloom_bits > 0) { + nlh.filter_tag_bloom_hi = htonl(tag_bloom_bits >> 32); + nlh.filter_tag_bloom_lo = htonl(tag_bloom_bits & 0xffffffff); + } + + /* add properties list */ + nlh.properties_off = iov[0].iov_len; + nlh.properties_len = blen; + iov[1].iov_base = (char *)buf; + iov[1].iov_len = blen; + + /* + * Use custom address for target, or the default one. + * + * If we send to a multicast group, we will get + * ECONNREFUSED, which is expected. + */ + if (destination) + smsg.msg_name = &destination->snl; + else + smsg.msg_name = &udev_monitor->snl_destination; + smsg.msg_namelen = sizeof(struct sockaddr_nl); + count = sendmsg(udev_monitor->sock, &smsg, 0); + if (count < 0) { + if (!destination && errno == ECONNREFUSED) { + log_debug("passed device to netlink monitor %p", udev_monitor); + return 0; + } else + return -errno; + } + + log_debug("passed %zi byte device to netlink monitor %p", count, udev_monitor); + return count; +} + +/** + * udev_monitor_filter_add_match_subsystem_devtype: + * @udev_monitor: the monitor + * @subsystem: the subsystem value to match the incoming devices against + * @devtype: the devtype value to match the incoming devices against + * + * This filter is efficiently executed inside the kernel, and libudev subscribers + * will usually not be woken up for devices which do not match. + * + * The filter must be installed before the monitor is switched to listening mode. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, const char *subsystem, const char *devtype) +{ + if (udev_monitor == NULL) + return -EINVAL; + if (subsystem == NULL) + return -EINVAL; + if (udev_list_entry_add(&udev_monitor->filter_subsystem_list, subsystem, devtype) == NULL) + return -ENOMEM; + return 0; +} + +/** + * udev_monitor_filter_add_match_tag: + * @udev_monitor: the monitor + * @tag: the name of a tag + * + * This filter is efficiently executed inside the kernel, and libudev subscribers + * will usually not be woken up for devices which do not match. + * + * The filter must be installed before the monitor is switched to listening mode. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag) +{ + if (udev_monitor == NULL) + return -EINVAL; + if (tag == NULL) + return -EINVAL; + if (udev_list_entry_add(&udev_monitor->filter_tag_list, tag, NULL) == NULL) + return -ENOMEM; + return 0; +} + +/** + * udev_monitor_filter_remove: + * @udev_monitor: monitor + * + * Remove all filters from monitor. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_monitor_filter_remove(struct udev_monitor *udev_monitor) +{ + static struct sock_fprog filter = { 0, NULL }; + + udev_list_cleanup(&udev_monitor->filter_subsystem_list); + return setsockopt(udev_monitor->sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)); +} diff --git a/src/libudev/src/libudev-private.h b/src/libudev/src/libudev-private.h new file mode 100644 index 0000000000..52c5075110 --- /dev/null +++ b/src/libudev/src/libudev-private.h @@ -0,0 +1,150 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 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 . +***/ + +#ifndef _LIBUDEV_PRIVATE_H_ +#define _LIBUDEV_PRIVATE_H_ + +#include +#include +#include + +#include "libudev.h" + +#include "macro.h" +#include "mkdir.h" +#include "strxcpyx.h" +#include "util.h" + +#define READ_END 0 +#define WRITE_END 1 + +/* libudev.c */ +int udev_get_rules_path(struct udev *udev, char **path[], usec_t *ts_usec[]); + +/* libudev-device.c */ +struct udev_device *udev_device_new_from_nulstr(struct udev *udev, char *nulstr, ssize_t buflen); +struct udev_device *udev_device_new_from_synthetic_event(struct udev *udev, const char *syspath, const char *action); +struct udev_device *udev_device_shallow_clone(struct udev_device *old_device); +struct udev_device *udev_device_clone_with_db(struct udev_device *old_device); +int udev_device_copy_properties(struct udev_device *dst, struct udev_device *src); +mode_t udev_device_get_devnode_mode(struct udev_device *udev_device); +uid_t udev_device_get_devnode_uid(struct udev_device *udev_device); +gid_t udev_device_get_devnode_gid(struct udev_device *udev_device); +int udev_device_rename(struct udev_device *udev_device, const char *new_name); +int udev_device_add_devlink(struct udev_device *udev_device, const char *devlink); +void udev_device_cleanup_devlinks_list(struct udev_device *udev_device); +int udev_device_add_property(struct udev_device *udev_device, const char *key, const char *value); +char **udev_device_get_properties_envp(struct udev_device *udev_device); +ssize_t udev_device_get_properties_monitor_buf(struct udev_device *udev_device, const char **buf); +const char *udev_device_get_devpath_old(struct udev_device *udev_device); +const char *udev_device_get_id_filename(struct udev_device *udev_device); +void udev_device_set_is_initialized(struct udev_device *udev_device); +int udev_device_add_tag(struct udev_device *udev_device, const char *tag); +void udev_device_remove_tag(struct udev_device *udev_device, const char *tag); +void udev_device_cleanup_tags_list(struct udev_device *udev_device); +usec_t udev_device_get_usec_initialized(struct udev_device *udev_device); +void udev_device_ensure_usec_initialized(struct udev_device *udev_device, struct udev_device *old_device); +int udev_device_get_devlink_priority(struct udev_device *udev_device); +int udev_device_set_devlink_priority(struct udev_device *udev_device, int prio); +int udev_device_get_watch_handle(struct udev_device *udev_device); +int udev_device_set_watch_handle(struct udev_device *udev_device, int handle); +int udev_device_get_ifindex(struct udev_device *udev_device); +void udev_device_set_info_loaded(struct udev_device *device); +bool udev_device_get_db_persist(struct udev_device *udev_device); +void udev_device_set_db_persist(struct udev_device *udev_device); +void udev_device_read_db(struct udev_device *udev_device); + +/* libudev-device-private.c */ +int udev_device_update_db(struct udev_device *udev_device); +int udev_device_delete_db(struct udev_device *udev_device); +int udev_device_tag_index(struct udev_device *dev, struct udev_device *dev_old, bool add); + +/* libudev-monitor.c - netlink/unix socket communication */ +int udev_monitor_disconnect(struct udev_monitor *udev_monitor); +int udev_monitor_allow_unicast_sender(struct udev_monitor *udev_monitor, struct udev_monitor *sender); +int udev_monitor_send_device(struct udev_monitor *udev_monitor, + struct udev_monitor *destination, struct udev_device *udev_device); +struct udev_monitor *udev_monitor_new_from_netlink_fd(struct udev *udev, const char *name, int fd); + +/* libudev-list.c */ +struct udev_list_node { + struct udev_list_node *next, *prev; +}; +struct udev_list { + struct udev *udev; + struct udev_list_node node; + struct udev_list_entry **entries; + unsigned int entries_cur; + unsigned int entries_max; + bool unique; +}; +void udev_list_node_init(struct udev_list_node *list); +int udev_list_node_is_empty(struct udev_list_node *list); +void udev_list_node_append(struct udev_list_node *new, struct udev_list_node *list); +void udev_list_node_remove(struct udev_list_node *entry); +#define udev_list_node_foreach(node, list) \ + for (node = (list)->next; \ + node != list; \ + node = (node)->next) +#define udev_list_node_foreach_safe(node, tmp, list) \ + for (node = (list)->next, tmp = (node)->next; \ + node != list; \ + node = tmp, tmp = (tmp)->next) +void udev_list_init(struct udev *udev, struct udev_list *list, bool unique); +void udev_list_cleanup(struct udev_list *list); +struct udev_list_entry *udev_list_get_entry(struct udev_list *list); +struct udev_list_entry *udev_list_entry_add(struct udev_list *list, const char *name, const char *value); +void udev_list_entry_delete(struct udev_list_entry *entry); +int udev_list_entry_get_num(struct udev_list_entry *list_entry); +void udev_list_entry_set_num(struct udev_list_entry *list_entry, int num); +#define udev_list_entry_foreach_safe(entry, tmp, first) \ + for (entry = first, tmp = udev_list_entry_get_next(entry); \ + entry != NULL; \ + entry = tmp, tmp = udev_list_entry_get_next(tmp)) + +/* libudev-queue.c */ +unsigned long long int udev_get_kernel_seqnum(struct udev *udev); +int udev_queue_read_seqnum(FILE *queue_file, unsigned long long int *seqnum); +ssize_t udev_queue_read_devpath(FILE *queue_file, char *devpath, size_t size); +ssize_t udev_queue_skip_devpath(FILE *queue_file); + +/* libudev-queue-private.c */ +struct udev_queue_export *udev_queue_export_new(struct udev *udev); +struct udev_queue_export *udev_queue_export_unref(struct udev_queue_export *udev_queue_export); +void udev_queue_export_cleanup(struct udev_queue_export *udev_queue_export); +int udev_queue_export_device_queued(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device); +int udev_queue_export_device_finished(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device); + +/* libudev-util.c */ +#define UTIL_PATH_SIZE 1024 +#define UTIL_NAME_SIZE 512 +#define UTIL_LINE_SIZE 16384 +#define UDEV_ALLOWED_CHARS_INPUT "/ $%?," +int util_log_priority(const char *priority); +size_t util_path_encode(const char *src, char *dest, size_t size); +void util_remove_trailing_chars(char *path, char c); +int util_replace_whitespace(const char *str, char *to, size_t len); +int util_replace_chars(char *str, const char *white); +unsigned int util_string_hash32(const char *key); +uint64_t util_string_bloom64(const char *str); + +/* libudev-util-private.c */ +int util_resolve_subsys_kernel(struct udev *udev, const char *string, char *result, size_t maxsize, int read_value); + +#endif diff --git a/src/libudev/src/libudev-queue.c b/src/libudev/src/libudev-queue.c new file mode 100644 index 0000000000..e3dffa6925 --- /dev/null +++ b/src/libudev/src/libudev-queue.c @@ -0,0 +1,268 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2009 Alan Jenkins + + 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 +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "libudev-private.h" + +/** + * SECTION:libudev-queue + * @short_description: access to currently active events + * + * This exports the current state of the udev processing queue. + */ + +/** + * udev_queue: + * + * Opaque object representing the current event queue in the udev daemon. + */ +struct udev_queue { + struct udev *udev; + int refcount; + int fd; +}; + +/** + * udev_queue_new: + * @udev: udev library context + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev queue context. + * + * Returns: the udev queue context, or #NULL on error. + **/ +_public_ struct udev_queue *udev_queue_new(struct udev *udev) +{ + struct udev_queue *udev_queue; + + if (udev == NULL) + return NULL; + + udev_queue = new0(struct udev_queue, 1); + if (udev_queue == NULL) + return NULL; + + udev_queue->refcount = 1; + udev_queue->udev = udev; + udev_queue->fd = -1; + return udev_queue; +} + +/** + * udev_queue_ref: + * @udev_queue: udev queue context + * + * Take a reference of a udev queue context. + * + * Returns: the same udev queue context. + **/ +_public_ struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue) +{ + if (udev_queue == NULL) + return NULL; + + udev_queue->refcount++; + return udev_queue; +} + +/** + * udev_queue_unref: + * @udev_queue: udev queue context + * + * Drop a reference of a udev queue context. If the refcount reaches zero, + * the resources of the queue context will be released. + * + * Returns: #NULL + **/ +_public_ struct udev_queue *udev_queue_unref(struct udev_queue *udev_queue) +{ + if (udev_queue == NULL) + return NULL; + + udev_queue->refcount--; + if (udev_queue->refcount > 0) + return NULL; + + safe_close(udev_queue->fd); + + free(udev_queue); + return NULL; +} + +/** + * udev_queue_get_udev: + * @udev_queue: udev queue context + * + * Retrieve the udev library context the queue context was created with. + * + * Returns: the udev library context. + **/ +_public_ struct udev *udev_queue_get_udev(struct udev_queue *udev_queue) +{ + if (udev_queue == NULL) + return NULL; + return udev_queue->udev; +} + +/** + * udev_queue_get_kernel_seqnum: + * @udev_queue: udev queue context + * + * This function is deprecated. + * + * Returns: 0. + **/ +_public_ unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue) +{ + return 0; +} + +/** + * udev_queue_get_udev_seqnum: + * @udev_queue: udev queue context + * + * This function is deprecated. + * + * Returns: 0. + **/ +_public_ unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue) +{ + return 0; +} + +/** + * udev_queue_get_udev_is_active: + * @udev_queue: udev queue context + * + * Check if udev is active on the system. + * + * Returns: a flag indicating if udev is active. + **/ +_public_ int udev_queue_get_udev_is_active(struct udev_queue *udev_queue) +{ + return access("/run/udev/control", F_OK) >= 0; +} + +/** + * udev_queue_get_queue_is_empty: + * @udev_queue: udev queue context + * + * Check if udev is currently processing any events. + * + * Returns: a flag indicating if udev is currently handling events. + **/ +_public_ int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue) +{ + return access("/run/udev/queue", F_OK) < 0; +} + +/** + * udev_queue_get_seqnum_sequence_is_finished: + * @udev_queue: udev queue context + * @start: first event sequence number + * @end: last event sequence number + * + * This function is deprecated, it just returns the result of + * udev_queue_get_queue_is_empty(). + * + * Returns: a flag indicating if udev is currently handling events. + **/ +_public_ int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue, + unsigned long long int start, unsigned long long int end) +{ + return udev_queue_get_queue_is_empty(udev_queue); +} + +/** + * udev_queue_get_seqnum_is_finished: + * @udev_queue: udev queue context + * @seqnum: sequence number + * + * This function is deprecated, it just returns the result of + * udev_queue_get_queue_is_empty(). + * + * Returns: a flag indicating if udev is currently handling events. + **/ +_public_ int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum) +{ + return udev_queue_get_queue_is_empty(udev_queue); +} + +/** + * udev_queue_get_queued_list_entry: + * @udev_queue: udev queue context + * + * This function is deprecated. + * + * Returns: NULL. + **/ +_public_ struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue) +{ + return NULL; +} + +/** + * udev_queue_get_fd: + * @udev_queue: udev queue context + * + * Returns: a file descriptor to watch for a queue to become empty. + */ +_public_ int udev_queue_get_fd(struct udev_queue *udev_queue) { + int fd; + int r; + + if (udev_queue->fd >= 0) + return udev_queue->fd; + + fd = inotify_init1(IN_CLOEXEC); + if (fd < 0) + return -errno; + + r = inotify_add_watch(fd, "/run/udev" , IN_DELETE); + if (r < 0) { + r = -errno; + close(fd); + return r; + } + + udev_queue->fd = fd; + return fd; +} + +/** + * udev_queue_flush: + * @udev_queue: udev queue context + * + * Returns: the result of clearing the watch for queue changes. + */ +_public_ int udev_queue_flush(struct udev_queue *udev_queue) { + if (udev_queue->fd < 0) + return -EINVAL; + + return flush_fd(udev_queue->fd); +} diff --git a/src/libudev/src/libudev-util.c b/src/libudev/src/libudev-util.c new file mode 100644 index 0000000000..574cfeac85 --- /dev/null +++ b/src/libudev/src/libudev-util.c @@ -0,0 +1,268 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 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 +#include +#include +#include +#include +#include + +#include "libudev.h" + +#include "MurmurHash2.h" +#include "device-nodes.h" +#include "libudev-private.h" +#include "syslog-util.h" +#include "utf8.h" + +/** + * SECTION:libudev-util + * @short_description: utils + * + * Utilities useful when dealing with devices and device node names. + */ + +/* handle "[/]" format */ +int util_resolve_subsys_kernel(struct udev *udev, const char *string, + char *result, size_t maxsize, int read_value) +{ + char temp[UTIL_PATH_SIZE]; + char *subsys; + char *sysname; + struct udev_device *dev; + char *attr; + + if (string[0] != '[') + return -1; + + strscpy(temp, sizeof(temp), string); + + subsys = &temp[1]; + + sysname = strchr(subsys, '/'); + if (sysname == NULL) + return -1; + sysname[0] = '\0'; + sysname = &sysname[1]; + + attr = strchr(sysname, ']'); + if (attr == NULL) + return -1; + attr[0] = '\0'; + attr = &attr[1]; + if (attr[0] == '/') + attr = &attr[1]; + if (attr[0] == '\0') + attr = NULL; + + if (read_value && attr == NULL) + return -1; + + dev = udev_device_new_from_subsystem_sysname(udev, subsys, sysname); + if (dev == NULL) + return -1; + + if (read_value) { + const char *val; + + val = udev_device_get_sysattr_value(dev, attr); + if (val != NULL) + strscpy(result, maxsize, val); + else + result[0] = '\0'; + log_debug("value '[%s/%s]%s' is '%s'", subsys, sysname, attr, result); + } else { + size_t l; + char *s; + + s = result; + l = strpcpyl(&s, maxsize, udev_device_get_syspath(dev), NULL); + if (attr != NULL) + strpcpyl(&s, l, "/", attr, NULL); + log_debug("path '[%s/%s]%s' is '%s'", subsys, sysname, attr, result); + } + udev_device_unref(dev); + return 0; +} + +int util_log_priority(const char *priority) +{ + char *endptr; + int prio; + + prio = strtoul(priority, &endptr, 10); + if (endptr[0] == '\0' || isspace(endptr[0])) { + if (prio >= 0 && prio <= 7) + return prio; + else + return -ERANGE; + } + + return log_level_from_string(priority); +} + +size_t util_path_encode(const char *src, char *dest, size_t size) +{ + size_t i, j; + + for (i = 0, j = 0; src[i] != '\0'; i++) { + if (src[i] == '/') { + if (j+4 >= size) { + j = 0; + break; + } + memcpy(&dest[j], "\\x2f", 4); + j += 4; + } else if (src[i] == '\\') { + if (j+4 >= size) { + j = 0; + break; + } + memcpy(&dest[j], "\\x5c", 4); + j += 4; + } else { + if (j+1 >= size) { + j = 0; + break; + } + dest[j] = src[i]; + j++; + } + } + dest[j] = '\0'; + return j; +} + +void util_remove_trailing_chars(char *path, char c) +{ + size_t len; + + if (path == NULL) + return; + len = strlen(path); + while (len > 0 && path[len-1] == c) + path[--len] = '\0'; +} + +int util_replace_whitespace(const char *str, char *to, size_t len) +{ + size_t i, j; + + /* strip trailing whitespace */ + len = strnlen(str, len); + while (len && isspace(str[len-1])) + len--; + + /* strip leading whitespace */ + i = 0; + while ((i < len) && isspace(str[i])) + i++; + + j = 0; + while (i < len) { + /* substitute multiple whitespace with a single '_' */ + if (isspace(str[i])) { + while (isspace(str[i])) + i++; + to[j++] = '_'; + } + to[j++] = str[i++]; + } + to[j] = '\0'; + return 0; +} + +/* allow chars in whitelist, plain ascii, hex-escaping and valid utf8 */ +int util_replace_chars(char *str, const char *white) +{ + size_t i = 0; + int replaced = 0; + + while (str[i] != '\0') { + int len; + + if (whitelisted_char_for_devnode(str[i], white)) { + i++; + continue; + } + + /* accept hex encoding */ + if (str[i] == '\\' && str[i+1] == 'x') { + i += 2; + continue; + } + + /* accept valid utf8 */ + len = utf8_encoded_valid_unichar(&str[i]); + if (len > 1) { + i += len; + continue; + } + + /* if space is allowed, replace whitespace with ordinary space */ + if (isspace(str[i]) && white != NULL && strchr(white, ' ') != NULL) { + str[i] = ' '; + i++; + replaced++; + continue; + } + + /* everything else is replaced with '_' */ + str[i] = '_'; + i++; + replaced++; + } + return replaced; +} + +/** + * udev_util_encode_string: + * @str: input string to be encoded + * @str_enc: output string to store the encoded input string + * @len: maximum size of the output string, which may be + * four times as long as the input string + * + * Encode all potentially unsafe characters of a string to the + * corresponding 2 char hex value prefixed by '\x'. + * + * Returns: 0 if the entire string was copied, non-zero otherwise. + **/ +_public_ int udev_util_encode_string(const char *str, char *str_enc, size_t len) +{ + return encode_devnode_name(str, str_enc, len); +} + +unsigned int util_string_hash32(const char *str) +{ + return MurmurHash2(str, strlen(str), 0); +} + +/* get a bunch of bit numbers out of the hash, and set the bits in our bit field */ +uint64_t util_string_bloom64(const char *str) +{ + uint64_t bits = 0; + unsigned int hash = util_string_hash32(str); + + bits |= 1LLU << (hash & 63); + bits |= 1LLU << ((hash >> 6) & 63); + bits |= 1LLU << ((hash >> 12) & 63); + bits |= 1LLU << ((hash >> 18) & 63); + return bits; +} diff --git a/src/libudev/src/libudev.c b/src/libudev/src/libudev.c new file mode 100644 index 0000000000..63fb05547d --- /dev/null +++ b/src/libudev/src/libudev.c @@ -0,0 +1,253 @@ +/*** + This file is part of systemd. + + Copyright 2008-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 +#include +#include +#include +#include +#include + +#include "libudev.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "libudev-private.h" +#include "missing.h" +#include "string-util.h" + +/** + * SECTION:libudev + * @short_description: libudev context + * + * The context contains the default values read from the udev config file, + * and is passed to all library operations. + */ + +/** + * udev: + * + * Opaque object representing the library context. + */ +struct udev { + int refcount; + void (*log_fn)(struct udev *udev, + int priority, const char *file, int line, const char *fn, + const char *format, va_list args); + void *userdata; +}; + +/** + * udev_get_userdata: + * @udev: udev library context + * + * Retrieve stored data pointer from library context. This might be useful + * to access from callbacks. + * + * Returns: stored userdata + **/ +_public_ void *udev_get_userdata(struct udev *udev) { + if (udev == NULL) + return NULL; + return udev->userdata; +} + +/** + * udev_set_userdata: + * @udev: udev library context + * @userdata: data pointer + * + * Store custom @userdata in the library context. + **/ +_public_ void udev_set_userdata(struct udev *udev, void *userdata) { + if (udev == NULL) + return; + udev->userdata = userdata; +} + +/** + * udev_new: + * + * Create udev library context. This reads the udev configuration + * file, and fills in the default values. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev library context. + * + * Returns: a new udev library context + **/ +_public_ struct udev *udev_new(void) { + struct udev *udev; + _cleanup_fclose_ FILE *f = NULL; + + udev = new0(struct udev, 1); + if (udev == NULL) + return NULL; + udev->refcount = 1; + + f = fopen("/etc/udev/udev.conf", "re"); + if (f != NULL) { + char line[UTIL_LINE_SIZE]; + unsigned line_nr = 0; + + while (fgets(line, sizeof(line), f)) { + size_t len; + char *key; + char *val; + + line_nr++; + + /* find key */ + key = line; + while (isspace(key[0])) + key++; + + /* comment or empty line */ + if (key[0] == '#' || key[0] == '\0') + continue; + + /* split key/value */ + val = strchr(key, '='); + if (val == NULL) { + log_debug("/etc/udev/udev.conf:%u: missing assignment, skipping line.", line_nr); + continue; + } + val[0] = '\0'; + val++; + + /* find value */ + while (isspace(val[0])) + val++; + + /* terminate key */ + len = strlen(key); + if (len == 0) + continue; + while (isspace(key[len-1])) + len--; + key[len] = '\0'; + + /* terminate value */ + len = strlen(val); + if (len == 0) + continue; + while (isspace(val[len-1])) + len--; + val[len] = '\0'; + + if (len == 0) + continue; + + /* unquote */ + if (val[0] == '"' || val[0] == '\'') { + if (val[len-1] != val[0]) { + log_debug("/etc/udev/udev.conf:%u: inconsistent quoting, skipping line.", line_nr); + continue; + } + val[len-1] = '\0'; + val++; + } + + if (streq(key, "udev_log")) { + int prio; + + prio = util_log_priority(val); + if (prio < 0) + log_debug("/etc/udev/udev.conf:%u: invalid log level '%s', ignoring.", line_nr, val); + else + log_set_max_level(prio); + continue; + } + } + } + + return udev; +} + +/** + * udev_ref: + * @udev: udev library context + * + * Take a reference of the udev library context. + * + * Returns: the passed udev library context + **/ +_public_ struct udev *udev_ref(struct udev *udev) { + if (udev == NULL) + return NULL; + udev->refcount++; + return udev; +} + +/** + * udev_unref: + * @udev: udev library context + * + * Drop a reference of the udev library context. If the refcount + * reaches zero, the resources of the context will be released. + * + * Returns: the passed udev library context if it has still an active reference, or #NULL otherwise. + **/ +_public_ struct udev *udev_unref(struct udev *udev) { + if (udev == NULL) + return NULL; + udev->refcount--; + if (udev->refcount > 0) + return udev; + free(udev); + return NULL; +} + +/** + * udev_set_log_fn: + * @udev: udev library context + * @log_fn: function to be called for log messages + * + * This function is deprecated. + * + **/ +_public_ void udev_set_log_fn(struct udev *udev, + void (*log_fn)(struct udev *udev, + int priority, const char *file, int line, const char *fn, + const char *format, va_list args)) { + return; +} + +/** + * udev_get_log_priority: + * @udev: udev library context + * + * This function is deprecated. + * + **/ +_public_ int udev_get_log_priority(struct udev *udev) { + return log_get_max_level(); +} + +/** + * udev_set_log_priority: + * @udev: udev library context + * @priority: the new log priority + * + * This function is deprecated. + * + **/ +_public_ void udev_set_log_priority(struct udev *udev, int priority) { + log_set_max_level(priority); +} diff --git a/src/libudev/src/libudev.pc.in b/src/libudev/src/libudev.pc.in new file mode 100644 index 0000000000..a0f3f524e0 --- /dev/null +++ b/src/libudev/src/libudev.pc.in @@ -0,0 +1,17 @@ +# This file is part of systemd. +# +# 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. + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libudev +Description: Library to access udev device information +Version: @VERSION@ +Libs: -L${libdir} -ludev +Cflags: -I${includedir} diff --git a/src/libudev/src/libudev.sym b/src/libudev/src/libudev.sym new file mode 100644 index 0000000000..76726fca77 --- /dev/null +++ b/src/libudev/src/libudev.sym @@ -0,0 +1,120 @@ +/*** + This file is part of systemd. + + 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. +***/ + +LIBUDEV_183 { +global: + udev_device_get_action; + udev_device_get_devlinks_list_entry; + udev_device_get_devnode; + udev_device_get_devnum; + udev_device_get_devpath; + udev_device_get_devtype; + udev_device_get_driver; + udev_device_get_is_initialized; + udev_device_get_parent; + udev_device_get_parent_with_subsystem_devtype; + udev_device_get_properties_list_entry; + udev_device_get_property_value; + udev_device_get_seqnum; + udev_device_get_subsystem; + udev_device_get_sysattr_list_entry; + udev_device_get_sysattr_value; + udev_device_get_sysname; + udev_device_get_sysnum; + udev_device_get_syspath; + udev_device_get_tags_list_entry; + udev_device_get_udev; + udev_device_get_usec_since_initialized; + udev_device_has_tag; + udev_device_new_from_devnum; + udev_device_new_from_environment; + udev_device_new_from_subsystem_sysname; + udev_device_new_from_syspath; + udev_device_ref; + udev_device_unref; + udev_enumerate_add_match_is_initialized; + udev_enumerate_add_match_parent; + udev_enumerate_add_match_property; + udev_enumerate_add_match_subsystem; + udev_enumerate_add_match_sysattr; + udev_enumerate_add_match_sysname; + udev_enumerate_add_match_tag; + udev_enumerate_add_nomatch_subsystem; + udev_enumerate_add_nomatch_sysattr; + udev_enumerate_add_syspath; + udev_enumerate_get_list_entry; + udev_enumerate_get_udev; + udev_enumerate_new; + udev_enumerate_ref; + udev_enumerate_scan_devices; + udev_enumerate_scan_subsystems; + udev_enumerate_unref; + udev_get_log_priority; + udev_get_userdata; + udev_list_entry_get_by_name; + udev_list_entry_get_name; + udev_list_entry_get_next; + udev_list_entry_get_value; + udev_monitor_enable_receiving; + udev_monitor_filter_add_match_subsystem_devtype; + udev_monitor_filter_add_match_tag; + udev_monitor_filter_remove; + udev_monitor_filter_update; + udev_monitor_get_fd; + udev_monitor_get_udev; + udev_monitor_new_from_netlink; + udev_monitor_receive_device; + udev_monitor_ref; + udev_monitor_set_receive_buffer_size; + udev_monitor_unref; + udev_new; + udev_queue_get_kernel_seqnum; + udev_queue_get_queue_is_empty; + udev_queue_get_queued_list_entry; + udev_queue_get_seqnum_is_finished; + udev_queue_get_seqnum_sequence_is_finished; + udev_queue_get_udev; + udev_queue_get_udev_is_active; + udev_queue_get_udev_seqnum; + udev_queue_new; + udev_queue_ref; + udev_queue_unref; + udev_ref; + udev_set_log_fn; + udev_set_log_priority; + udev_set_userdata; + udev_unref; + udev_util_encode_string; +local: + *; +}; + +LIBUDEV_189 { +global: + udev_device_new_from_device_id; +} LIBUDEV_183; + +LIBUDEV_196 { +global: + udev_hwdb_new; + udev_hwdb_ref; + udev_hwdb_unref; + udev_hwdb_get_properties_list_entry; +} LIBUDEV_189; + +LIBUDEV_199 { +global: + udev_device_set_sysattr_value; +} LIBUDEV_196; + +LIBUDEV_215 { +global: + udev_queue_flush; + udev_queue_get_fd; +} LIBUDEV_199; diff --git a/src/libudev/src/udev.h b/src/libudev/src/udev.h new file mode 100644 index 0000000000..00de88972a --- /dev/null +++ b/src/libudev/src/udev.h @@ -0,0 +1,216 @@ +#pragma once + +/* + * Copyright (C) 2003 Greg Kroah-Hartman + * Copyright (C) 2003-2010 Kay Sievers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "libudev.h" +#include + +#include "label.h" +#include "libudev-private.h" +#include "macro.h" +#include "strv.h" +#include "util.h" + +struct udev_event { + struct udev *udev; + struct udev_device *dev; + struct udev_device *dev_parent; + struct udev_device *dev_db; + char *name; + char *program_result; + mode_t mode; + uid_t uid; + gid_t gid; + struct udev_list seclabel_list; + struct udev_list run_list; + int exec_delay; + usec_t birth_usec; + sd_netlink *rtnl; + unsigned int builtin_run; + unsigned int builtin_ret; + bool inotify_watch; + bool inotify_watch_final; + bool group_set; + bool group_final; + bool owner_set; + bool owner_final; + bool mode_set; + bool mode_final; + bool name_final; + bool devlink_final; + bool run_final; +}; + +struct udev_watch { + struct udev_list_node node; + int handle; + char *name; +}; + +/* udev-rules.c */ +struct udev_rules; +struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names); +struct udev_rules *udev_rules_unref(struct udev_rules *rules); +bool udev_rules_check_timestamp(struct udev_rules *rules); +void udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event, + usec_t timeout_usec, usec_t timeout_warn_usec, + struct udev_list *properties_list); +int udev_rules_apply_static_dev_perms(struct udev_rules *rules); + +/* udev-event.c */ +struct udev_event *udev_event_new(struct udev_device *dev); +void udev_event_unref(struct udev_event *event); +size_t udev_event_apply_format(struct udev_event *event, const char *src, char *dest, size_t size); +int udev_event_apply_subsys_kernel(struct udev_event *event, const char *string, + char *result, size_t maxsize, int read_value); +int udev_event_spawn(struct udev_event *event, + usec_t timeout_usec, + usec_t timeout_warn_usec, + bool accept_failure, + const char *cmd, char *result, size_t ressize); +void udev_event_execute_rules(struct udev_event *event, + usec_t timeout_usec, usec_t timeout_warn_usec, + struct udev_list *properties_list, + struct udev_rules *rules); +void udev_event_execute_run(struct udev_event *event, usec_t timeout_usec, usec_t timeout_warn_usec); +int udev_build_argv(struct udev *udev, char *cmd, int *argc, char *argv[]); + +/* udev-watch.c */ +int udev_watch_init(struct udev *udev); +void udev_watch_restore(struct udev *udev); +void udev_watch_begin(struct udev *udev, struct udev_device *dev); +void udev_watch_end(struct udev *udev, struct udev_device *dev); +struct udev_device *udev_watch_lookup(struct udev *udev, int wd); + +/* udev-node.c */ +void udev_node_add(struct udev_device *dev, bool apply, + mode_t mode, uid_t uid, gid_t gid, + struct udev_list *seclabel_list); +void udev_node_remove(struct udev_device *dev); +void udev_node_update_old_links(struct udev_device *dev, struct udev_device *dev_old); + +/* udev-ctrl.c */ +struct udev_ctrl; +struct udev_ctrl *udev_ctrl_new(struct udev *udev); +struct udev_ctrl *udev_ctrl_new_from_fd(struct udev *udev, int fd); +int udev_ctrl_enable_receiving(struct udev_ctrl *uctrl); +struct udev_ctrl *udev_ctrl_unref(struct udev_ctrl *uctrl); +int udev_ctrl_cleanup(struct udev_ctrl *uctrl); +struct udev *udev_ctrl_get_udev(struct udev_ctrl *uctrl); +int udev_ctrl_get_fd(struct udev_ctrl *uctrl); +int udev_ctrl_send_set_log_level(struct udev_ctrl *uctrl, int priority, int timeout); +int udev_ctrl_send_stop_exec_queue(struct udev_ctrl *uctrl, int timeout); +int udev_ctrl_send_start_exec_queue(struct udev_ctrl *uctrl, int timeout); +int udev_ctrl_send_reload(struct udev_ctrl *uctrl, int timeout); +int udev_ctrl_send_ping(struct udev_ctrl *uctrl, int timeout); +int udev_ctrl_send_exit(struct udev_ctrl *uctrl, int timeout); +int udev_ctrl_send_set_env(struct udev_ctrl *uctrl, const char *key, int timeout); +int udev_ctrl_send_set_children_max(struct udev_ctrl *uctrl, int count, int timeout); +struct udev_ctrl_connection; +struct udev_ctrl_connection *udev_ctrl_get_connection(struct udev_ctrl *uctrl); +struct udev_ctrl_connection *udev_ctrl_connection_ref(struct udev_ctrl_connection *conn); +struct udev_ctrl_connection *udev_ctrl_connection_unref(struct udev_ctrl_connection *conn); +struct udev_ctrl_msg; +struct udev_ctrl_msg *udev_ctrl_receive_msg(struct udev_ctrl_connection *conn); +struct udev_ctrl_msg *udev_ctrl_msg_unref(struct udev_ctrl_msg *ctrl_msg); +int udev_ctrl_get_set_log_level(struct udev_ctrl_msg *ctrl_msg); +int udev_ctrl_get_stop_exec_queue(struct udev_ctrl_msg *ctrl_msg); +int udev_ctrl_get_start_exec_queue(struct udev_ctrl_msg *ctrl_msg); +int udev_ctrl_get_reload(struct udev_ctrl_msg *ctrl_msg); +int udev_ctrl_get_ping(struct udev_ctrl_msg *ctrl_msg); +int udev_ctrl_get_exit(struct udev_ctrl_msg *ctrl_msg); +const char *udev_ctrl_get_set_env(struct udev_ctrl_msg *ctrl_msg); +int udev_ctrl_get_set_children_max(struct udev_ctrl_msg *ctrl_msg); + +/* built-in commands */ +enum udev_builtin_cmd { +#ifdef HAVE_BLKID + UDEV_BUILTIN_BLKID, +#endif + UDEV_BUILTIN_BTRFS, + UDEV_BUILTIN_HWDB, + UDEV_BUILTIN_INPUT_ID, + UDEV_BUILTIN_KEYBOARD, +#ifdef HAVE_KMOD + UDEV_BUILTIN_KMOD, +#endif + UDEV_BUILTIN_NET_ID, + UDEV_BUILTIN_NET_LINK, + UDEV_BUILTIN_PATH_ID, + UDEV_BUILTIN_USB_ID, +#ifdef HAVE_ACL + UDEV_BUILTIN_UACCESS, +#endif + UDEV_BUILTIN_MAX +}; +struct udev_builtin { + const char *name; + int (*cmd)(struct udev_device *dev, int argc, char *argv[], bool test); + const char *help; + int (*init)(struct udev *udev); + void (*exit)(struct udev *udev); + bool (*validate)(struct udev *udev); + bool run_once; +}; +#ifdef HAVE_BLKID +extern const struct udev_builtin udev_builtin_blkid; +#endif +extern const struct udev_builtin udev_builtin_btrfs; +extern const struct udev_builtin udev_builtin_hwdb; +extern const struct udev_builtin udev_builtin_input_id; +extern const struct udev_builtin udev_builtin_keyboard; +#ifdef HAVE_KMOD +extern const struct udev_builtin udev_builtin_kmod; +#endif +extern const struct udev_builtin udev_builtin_net_id; +extern const struct udev_builtin udev_builtin_net_setup_link; +extern const struct udev_builtin udev_builtin_path_id; +extern const struct udev_builtin udev_builtin_usb_id; +extern const struct udev_builtin udev_builtin_uaccess; +void udev_builtin_init(struct udev *udev); +void udev_builtin_exit(struct udev *udev); +enum udev_builtin_cmd udev_builtin_lookup(const char *command); +const char *udev_builtin_name(enum udev_builtin_cmd cmd); +bool udev_builtin_run_once(enum udev_builtin_cmd cmd); +int udev_builtin_run(struct udev_device *dev, enum udev_builtin_cmd cmd, const char *command, bool test); +void udev_builtin_list(struct udev *udev); +bool udev_builtin_validate(struct udev *udev); +int udev_builtin_add_property(struct udev_device *dev, bool test, const char *key, const char *val); +int udev_builtin_hwdb_lookup(struct udev_device *dev, const char *prefix, const char *modalias, + const char *filter, bool test); + +/* udevadm commands */ +struct udevadm_cmd { + const char *name; + int (*cmd)(struct udev *udev, int argc, char *argv[]); + const char *help; + int debug; +}; +extern const struct udevadm_cmd udevadm_info; +extern const struct udevadm_cmd udevadm_trigger; +extern const struct udevadm_cmd udevadm_settle; +extern const struct udevadm_cmd udevadm_control; +extern const struct udevadm_cmd udevadm_monitor; +extern const struct udevadm_cmd udevadm_hwdb; +extern const struct udevadm_cmd udevadm_test; +extern const struct udevadm_cmd udevadm_test_builtin; diff --git a/src/locale/Makefile b/src/locale/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/locale/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/locale/Makefile b/src/locale/Makefile new file mode 100644 index 0000000000..ad60553af9 --- /dev/null +++ b/src/locale/Makefile @@ -0,0 +1,90 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_LOCALED),) +systemd_localed_SOURCES = \ + src/locale/localed.c + +systemd_localed_LDADD = \ + libshared.la \ + -ldl + +systemd_localed_CFLAGS = \ + $(AM_CFLAGS) \ + $(XKBCOMMON_CFLAGS) + +nodist_systemunit_DATA += \ + units/systemd-localed.service + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.locale1.busname + +libexec_PROGRAMS += \ + systemd-localed + +dist_dbuspolicy_DATA += \ + src/locale/org.freedesktop.locale1.conf + +dist_dbussystemservice_DATA += \ + src/locale/org.freedesktop.locale1.service + +polkitpolicy_files += \ + src/locale/org.freedesktop.locale1.policy + +SYSTEM_UNIT_ALIASES += \ + systemd-localed.service dbus-org.freedesktop.locale1.service + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.locale1.busname + +dist_pkgdata_DATA = \ + src/locale/kbd-model-map \ + src/locale/language-fallback-map + +localectl_SOURCES = \ + src/locale/localectl.c + +localectl_LDADD = \ + libshared.la + +bin_PROGRAMS += \ + localectl + +dist_bashcompletion_data += \ + shell-completion/bash/localectl + +dist_zshcompletion_data += \ + shell-completion/zsh/_localectl +endif # ENABLE_LOCALED + +.PHONY: update-kbd-model-map + +polkitpolicy_in_files += \ + src/locale/org.freedesktop.locale1.policy.in + +EXTRA_DIST += \ + units/systemd-localed.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/locale/localectl.c b/src/locale/localectl.c index 4865335349..c1b0a56346 100644 --- a/src/locale/localectl.c +++ b/src/locale/localectl.c @@ -25,7 +25,7 @@ #include #include -#include "sd-bus.h" +#include #include "bus-error.h" #include "bus-util.h" diff --git a/src/locale/localed.c b/src/locale/localed.c index 3b22a582ac..7b4cbadfd0 100644 --- a/src/locale/localed.c +++ b/src/locale/localed.c @@ -27,7 +27,7 @@ #include #endif -#include "sd-bus.h" +#include #include "alloc-util.h" #include "bus-error.h" diff --git a/src/login/Makefile b/src/login/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/login/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/login/Makefile b/src/login/Makefile new file mode 100644 index 0000000000..9879cecbd3 --- /dev/null +++ b/src/login/Makefile @@ -0,0 +1,244 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_LOGIND),) +systemd_logind_SOURCES = \ + src/login/logind.c \ + src/login/logind.h + +nodist_systemd_logind_SOURCES = \ + src/login/logind-gperf.c + +systemd_logind_LDADD = \ + liblogind-core.la + +liblogind_core_la_SOURCES = \ + src/login/logind-core.c \ + src/login/logind-device.c \ + src/login/logind-device.h \ + src/login/logind-button.c \ + src/login/logind-button.h \ + src/login/logind-action.c \ + src/login/logind-action.h \ + src/login/logind-seat.c \ + src/login/logind-seat.h \ + src/login/logind-session.c \ + src/login/logind-session.h \ + src/login/logind-session-device.c \ + src/login/logind-session-device.h \ + src/login/logind-user.c \ + src/login/logind-user.h \ + src/login/logind-inhibit.c \ + src/login/logind-inhibit.h \ + src/login/logind-dbus.c \ + src/login/logind-session-dbus.c \ + src/login/logind-seat-dbus.c \ + src/login/logind-user-dbus.c \ + src/login/logind-utmp.c \ + src/login/logind-acl.h + +liblogind_core_la_LIBADD = \ + libshared.la + +ifneq ($(HAVE_ACL),) +liblogind_core_la_SOURCES += \ + src/login/logind-acl.c +endif # HAVE_ACL + +noinst_LTLIBRARIES += \ + liblogind-core.la + +libexec_PROGRAMS += \ + systemd-logind + +loginctl_SOURCES = \ + src/login/loginctl.c \ + src/login/sysfs-show.h \ + src/login/sysfs-show.c + +loginctl_LDADD = \ + libshared.la + +bin_PROGRAMS += \ + loginctl + +dist_bashcompletion_data += \ + shell-completion/bash/loginctl + +dist_zshcompletion_data += \ + shell-completion/zsh/_loginctl \ + shell-completion/zsh/_systemd-inhibit + +systemd_inhibit_SOURCES = \ + src/login/inhibit.c + +systemd_inhibit_LDADD = \ + libshared.la + +bin_PROGRAMS += \ + systemd-inhibit + +test_login_SOURCES = \ + src/libsystemd/sd-login/test-login.c + +test_login_LDADD = \ + libshared.la + +test_login_shared_SOURCES = \ + src/login/test-login-shared.c + +test_login_shared_LDADD = \ + libshared.la + +test_inhibit_SOURCES = \ + src/login/test-inhibit.c + +test_inhibit_LDADD = \ + libshared.la + +test_login_tables_SOURCES = \ + src/login/test-login-tables.c + +test_login_tables_LDADD = \ + liblogind-core.la + +manual_tests += \ + test-login \ + test-inhibit + +tests += \ + test-login-tables \ + test-login-shared + +ifneq ($(HAVE_PAM),) +pam_systemd_la_SOURCES = \ + src/login/pam_systemd.sym \ + src/login/pam_systemd.c + +pam_systemd_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(PAM_CFLAGS) + +pam_systemd_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -module \ + -export-dynamic \ + -avoid-version \ + -shared \ + -Wl,--version-script=$(srcdir)/pam_systemd.sym + +pam_systemd_la_LIBADD = \ + libshared.la \ + $(PAM_LIBS) + +pamlib_LTLIBRARIES = \ + pam_systemd.la + +dist_pamconf_DATA = \ + src/login/systemd-user + +EXTRA_DIST += \ + src/login/systemd-user.m4 +endif # HAVE_PAM + +nodist_systemunit_DATA += \ + units/systemd-logind.service + +dist_systemunit_DATA += \ + units/user.slice + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.login1.busname + +dist_dbussystemservice_DATA += \ + src/login/org.freedesktop.login1.service + +dist_dbuspolicy_DATA += \ + src/login/org.freedesktop.login1.conf + +nodist_pkgsysconf_DATA += \ + src/login/logind.conf + +polkitpolicy_files += \ + src/login/org.freedesktop.login1.policy + +INSTALL_DIRS += \ + $(systemdstatedir) + +MULTI_USER_TARGET_WANTS += \ + systemd-logind.service + +SYSTEM_UNIT_ALIASES += \ + systemd-logind.service dbus-org.freedesktop.login1.service + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.login1.busname + +dist_udevrules_DATA += \ + src/login/70-uaccess.rules \ + src/login/70-power-switch.rules + +nodist_udevrules_DATA += \ + src/login/71-seat.rules \ + src/login/73-seat-late.rules + +endif # ENABLE_LOGIND + +polkitpolicy_in_files += \ + src/login/org.freedesktop.login1.policy.in + +gperf_gperf_sources += \ + src/login/logind-gperf.gperf + +EXTRA_DIST += \ + src/login/71-seat.rules.in \ + src/login/73-seat-late.rules.in \ + units/systemd-logind.service.in \ + src/login/logind.conf.in + +# ------------------------------------------------------------------------------ +ifneq ($(HAVE_PAM),) + +systemd_user_sessions_SOURCES = \ + src/user-sessions/user-sessions.c + +systemd_user_sessions_LDADD = \ + libshared.la + +libexec_PROGRAMS += \ + systemd-user-sessions + +nodist_systemunit_DATA += \ + units/systemd-user-sessions.service + +MULTI_USER_TARGET_WANTS += \ + systemd-user-sessions.service + +endif # HAVE_PAM + +EXTRA_DIST += \ + units/systemd-user-sessions.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/login/inhibit.c b/src/login/inhibit.c index f2c37a8623..905e757a32 100644 --- a/src/login/inhibit.c +++ b/src/login/inhibit.c @@ -23,7 +23,7 @@ #include #include -#include "sd-bus.h" +#include #include "alloc-util.h" #include "bus-error.h" diff --git a/src/login/loginctl.c b/src/login/loginctl.c index 1c75565636..f3f57b4b13 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -23,7 +23,7 @@ #include #include -#include "sd-bus.h" +#include #include "alloc-util.h" #include "bus-error.h" diff --git a/src/login/logind-button.c b/src/login/logind-button.c index baa6b7113c..acf7504a2e 100644 --- a/src/login/logind-button.c +++ b/src/login/logind-button.c @@ -24,7 +24,7 @@ #include #include -#include "sd-messages.h" +#include #include "alloc-util.h" #include "fd-util.h" diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 0a84d75e24..90dcd94710 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -22,7 +22,7 @@ #include #include -#include "sd-messages.h" +#include #include "alloc-util.h" #include "audit-util.h" diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c index b5192320e4..aec67718a1 100644 --- a/src/login/logind-seat.c +++ b/src/login/logind-seat.c @@ -22,7 +22,7 @@ #include #include -#include "sd-messages.h" +#include #include "alloc-util.h" #include "fd-util.h" diff --git a/src/login/logind-session.c b/src/login/logind-session.c index 1e0666884a..11a83106b1 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -26,7 +26,7 @@ #include #include -#include "sd-messages.h" +#include #include "alloc-util.h" #include "audit-util.h" diff --git a/src/login/logind-utmp.c b/src/login/logind-utmp.c index 29ab00eb1f..47599fd466 100644 --- a/src/login/logind-utmp.c +++ b/src/login/logind-utmp.c @@ -22,7 +22,7 @@ #include #include -#include "sd-messages.h" +#include #include "alloc-util.h" #include "audit-util.h" diff --git a/src/login/logind.c b/src/login/logind.c index caf149cfb7..925c04a344 100644 --- a/src/login/logind.c +++ b/src/login/logind.c @@ -23,7 +23,7 @@ #include #include "libudev.h" -#include "sd-daemon.h" +#include #include "alloc-util.h" #include "bus-error.h" diff --git a/src/login/logind.h b/src/login/logind.h index 90431eb4b0..9d43c2e7ee 100644 --- a/src/login/logind.h +++ b/src/login/logind.h @@ -22,8 +22,8 @@ #include #include "libudev.h" -#include "sd-bus.h" -#include "sd-event.h" +#include +#include #include "hashmap.h" #include "list.h" diff --git a/src/login/test-inhibit.c b/src/login/test-inhibit.c index a3cf9d293b..be7145b4bc 100644 --- a/src/login/test-inhibit.c +++ b/src/login/test-inhibit.c @@ -19,7 +19,7 @@ #include -#include "sd-bus.h" +#include #include "bus-util.h" #include "fd-util.h" diff --git a/src/machine-id-setup/Makefile b/src/machine-id-setup/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/machine-id-setup/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/machine-id-setup/Makefile b/src/machine-id-setup/Makefile new file mode 100644 index 0000000000..125471d17a --- /dev/null +++ b/src/machine-id-setup/Makefile @@ -0,0 +1,37 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_machine_id_setup_SOURCES = \ + src/machine-id-setup/machine-id-setup-main.c \ + src/core/machine-id-setup.c \ + src/core/machine-id-setup.h + +systemd_machine_id_setup_LDADD = \ + libshared.la + +SYSINIT_TARGET_WANTS += \ + systemd-machine-id-commit.service + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/machine/.gitignore b/src/machine/.gitignore deleted file mode 100644 index e1065b5894..0000000000 --- a/src/machine/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/org.freedesktop.machine1.policy diff --git a/src/machine/Makefile b/src/machine/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/machine/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c deleted file mode 100644 index 0eed9b81bb..0000000000 --- a/src/machine/image-dbus.c +++ /dev/null @@ -1,422 +0,0 @@ -/*** - 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 "alloc-util.h" -#include "bus-label.h" -#include "bus-util.h" -#include "fd-util.h" -#include "image-dbus.h" -#include "io-util.h" -#include "machine-image.h" -#include "process-util.h" -#include "strv.h" -#include "user-util.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, image_type, ImageType); - -int bus_image_method_remove( - sd_bus_message *message, - void *userdata, - sd_bus_error *error) { - - _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 }; - Image *image = userdata; - Manager *m = image->userdata; - pid_t child; - int r; - - assert(message); - assert(image); - - if (m->n_operations >= OPERATIONS_MAX) - return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing operations."); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-images", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m"); - - child = fork(); - if (child < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); - if (child == 0) { - errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); - - r = image_remove(image); - if (r < 0) { - (void) write(errno_pipe_fd[1], &r, sizeof(r)); - _exit(EXIT_FAILURE); - } - - _exit(EXIT_SUCCESS); - } - - errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); - - r = operation_new(m, NULL, child, message, errno_pipe_fd[0]); - if (r < 0) { - (void) sigkill_wait(child); - return r; - } - - errno_pipe_fd[0] = -1; - - return 1; -} - -int bus_image_method_rename( - sd_bus_message *message, - void *userdata, - sd_bus_error *error) { - - Image *image = userdata; - Manager *m = image->userdata; - const char *new_name; - int r; - - assert(message); - assert(image); - - r = sd_bus_message_read(message, "s", &new_name); - if (r < 0) - return r; - - if (!image_name_is_valid(new_name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-images", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = image_rename(image, new_name); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_image_method_clone( - sd_bus_message *message, - void *userdata, - sd_bus_error *error) { - - _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 }; - Image *image = userdata; - Manager *m = image->userdata; - const char *new_name; - int r, read_only; - pid_t child; - - assert(message); - assert(image); - assert(m); - - if (m->n_operations >= OPERATIONS_MAX) - return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing operations."); - - r = sd_bus_message_read(message, "sb", &new_name, &read_only); - if (r < 0) - return r; - - if (!image_name_is_valid(new_name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-images", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m"); - - child = fork(); - if (child < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); - if (child == 0) { - errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); - - r = image_clone(image, new_name, read_only); - if (r < 0) { - (void) write(errno_pipe_fd[1], &r, sizeof(r)); - _exit(EXIT_FAILURE); - } - - _exit(EXIT_SUCCESS); - } - - errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); - - r = operation_new(m, NULL, child, message, errno_pipe_fd[0]); - if (r < 0) { - (void) sigkill_wait(child); - return r; - } - - errno_pipe_fd[0] = -1; - - return 1; -} - -int bus_image_method_mark_read_only( - sd_bus_message *message, - void *userdata, - sd_bus_error *error) { - - Image *image = userdata; - Manager *m = image->userdata; - int r, read_only; - - assert(message); - - r = sd_bus_message_read(message, "b", &read_only); - if (r < 0) - return r; - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-images", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = image_read_only(image, read_only); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_image_method_set_limit( - sd_bus_message *message, - void *userdata, - sd_bus_error *error) { - - Image *image = userdata; - Manager *m = image->userdata; - uint64_t limit; - int r; - - assert(message); - - r = sd_bus_message_read(message, "t", &limit); - if (r < 0) - return r; - if (!FILE_SIZE_VALID_OR_INFINITY(limit)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range"); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-images", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = image_set_limit(image, limit); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -const sd_bus_vtable image_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Image, name), 0), - SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Image, path), 0), - SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Image, type), 0), - SD_BUS_PROPERTY("ReadOnly", "b", bus_property_get_bool, offsetof(Image, read_only), 0), - SD_BUS_PROPERTY("CreationTimestamp", "t", NULL, offsetof(Image, crtime), 0), - SD_BUS_PROPERTY("ModificationTimestamp", "t", NULL, offsetof(Image, mtime), 0), - SD_BUS_PROPERTY("Usage", "t", NULL, offsetof(Image, usage), 0), - SD_BUS_PROPERTY("Limit", "t", NULL, offsetof(Image, limit), 0), - SD_BUS_PROPERTY("UsageExclusive", "t", NULL, offsetof(Image, usage_exclusive), 0), - SD_BUS_PROPERTY("LimitExclusive", "t", NULL, offsetof(Image, limit_exclusive), 0), - SD_BUS_METHOD("Remove", NULL, NULL, bus_image_method_remove, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Rename", "s", NULL, bus_image_method_rename, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Clone", "sb", NULL, bus_image_method_clone, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MarkReadOnly", "b", NULL, bus_image_method_mark_read_only, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetLimit", "t", NULL, bus_image_method_set_limit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_VTABLE_END -}; - -static int image_flush_cache(sd_event_source *s, void *userdata) { - Manager *m = userdata; - Image *i; - - assert(s); - assert(m); - - while ((i = hashmap_steal_first(m->image_cache))) - image_unref(i); - - return 0; -} - -int image_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - _cleanup_free_ char *e = NULL; - Manager *m = userdata; - Image *image = NULL; - const char *p; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - - p = startswith(path, "/org/freedesktop/machine1/image/"); - if (!p) - return 0; - - e = bus_label_unescape(p); - if (!e) - return -ENOMEM; - - image = hashmap_get(m->image_cache, e); - if (image) { - *found = image; - return 1; - } - - r = hashmap_ensure_allocated(&m->image_cache, &string_hash_ops); - if (r < 0) - return r; - - if (!m->image_cache_defer_event) { - r = sd_event_add_defer(m->event, &m->image_cache_defer_event, image_flush_cache, m); - if (r < 0) - return r; - - r = sd_event_source_set_priority(m->image_cache_defer_event, SD_EVENT_PRIORITY_IDLE); - if (r < 0) - return r; - } - - r = sd_event_source_set_enabled(m->image_cache_defer_event, SD_EVENT_ONESHOT); - if (r < 0) - return r; - - r = image_find(e, &image); - if (r <= 0) - return r; - - image->userdata = m; - - r = hashmap_put(m->image_cache, image->name, image); - if (r < 0) { - image_unref(image); - return r; - } - - *found = image; - return 1; -} - -char *image_bus_path(const char *name) { - _cleanup_free_ char *e = NULL; - - assert(name); - - e = bus_label_escape(name); - if (!e) - return NULL; - - return strappend("/org/freedesktop/machine1/image/", e); -} - -int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { - _cleanup_(image_hashmap_freep) Hashmap *images = NULL; - _cleanup_strv_free_ char **l = NULL; - Image *image; - Iterator i; - int r; - - assert(bus); - assert(path); - assert(nodes); - - images = hashmap_new(&string_hash_ops); - if (!images) - return -ENOMEM; - - r = image_discover(images); - if (r < 0) - return r; - - HASHMAP_FOREACH(image, images, i) { - char *p; - - p = image_bus_path(image->name); - if (!p) - return -ENOMEM; - - r = strv_consume(&l, p); - if (r < 0) - return r; - } - - *nodes = l; - l = NULL; - - return 1; -} diff --git a/src/machine/image-dbus.h b/src/machine/image-dbus.h deleted file mode 100644 index b62da996c6..0000000000 --- a/src/machine/image-dbus.h +++ /dev/null @@ -1,35 +0,0 @@ -#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 "machined.h" - -extern const sd_bus_vtable image_vtable[]; - -char *image_bus_path(const char *name); - -int image_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); -int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); - -int bus_image_method_remove(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_image_method_rename(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_image_method_clone(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_image_method_mark_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_image_method_set_limit(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c deleted file mode 100644 index 7b9aa66d63..0000000000 --- a/src/machine/machine-dbus.c +++ /dev/null @@ -1,1475 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include -#include - -/* When we include libgen.h because we need dirname() we immediately - * undefine basename() since libgen.h defines it as a macro to the POSIX - * version which is really broken. We prefer GNU basename(). */ -#include -#undef basename - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-internal.h" -#include "bus-label.h" -#include "bus-util.h" -#include "copy.h" -#include "env-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "in-addr-util.h" -#include "local-addresses.h" -#include "machine-dbus.h" -#include "machine.h" -#include "mkdir.h" -#include "path-util.h" -#include "process-util.h" -#include "signal-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "user-util.h" - -static int property_get_id( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Machine *m = userdata; - - assert(bus); - assert(reply); - assert(m); - - return sd_bus_message_append_array(reply, 'y', &m->id, 16); -} - -static int property_get_state( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Machine *m = userdata; - const char *state; - int r; - - assert(bus); - assert(reply); - assert(m); - - state = machine_state_to_string(machine_get_state(m)); - - r = sd_bus_message_append_basic(reply, 's', state); - if (r < 0) - return r; - - return 1; -} - -static int property_get_netif( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Machine *m = userdata; - - assert(bus); - assert(reply); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - - return sd_bus_message_append_array(reply, 'i', m->netif, m->n_netif * sizeof(int)); -} - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_class, machine_class, MachineClass); - -int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Machine *m = userdata; - int r; - - assert(message); - assert(m); - - r = bus_verify_polkit_async( - message, - CAP_KILL, - "org.freedesktop.machine1.manage-machines", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = machine_stop(m); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Machine *m = userdata; - const char *swho; - int32_t signo; - KillWho who; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "si", &swho, &signo); - if (r < 0) - return r; - - if (isempty(swho)) - who = KILL_ALL; - else { - who = kill_who_from_string(swho); - if (who < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid kill parameter '%s'", swho); - } - - if (!SIGNAL_VALID(signo)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid signal %i", signo); - - r = bus_verify_polkit_async( - message, - CAP_KILL, - "org.freedesktop.machine1.manage-machines", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = machine_kill(m, who, signo); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - Machine *m = userdata; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(iay)"); - if (r < 0) - return r; - - switch (m->class) { - - case MACHINE_HOST: { - _cleanup_free_ struct local_address *addresses = NULL; - struct local_address *a; - int n, i; - - n = local_addresses(NULL, 0, AF_UNSPEC, &addresses); - if (n < 0) - return n; - - for (a = addresses, i = 0; i < n; a++, i++) { - - r = sd_bus_message_open_container(reply, 'r', "iay"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "i", addresses[i].family); - if (r < 0) - return r; - - r = sd_bus_message_append_array(reply, 'y', &addresses[i].address, FAMILY_ADDRESS_SIZE(addresses[i].family)); - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - } - - break; - } - - case MACHINE_CONTAINER: { - _cleanup_close_pair_ int pair[2] = { -1, -1 }; - _cleanup_free_ char *us = NULL, *them = NULL; - _cleanup_close_ int netns_fd = -1; - const char *p; - siginfo_t si; - pid_t child; - - r = readlink_malloc("/proc/self/ns/net", &us); - if (r < 0) - return r; - - p = procfs_file_alloca(m->leader, "ns/net"); - r = readlink_malloc(p, &them); - if (r < 0) - return r; - - if (streq(us, them)) - return sd_bus_error_setf(error, BUS_ERROR_NO_PRIVATE_NETWORKING, "Machine %s does not use private networking", m->name); - - r = namespace_open(m->leader, NULL, NULL, &netns_fd, NULL, NULL); - if (r < 0) - return r; - - if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) - return -errno; - - child = fork(); - if (child < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); - - if (child == 0) { - _cleanup_free_ struct local_address *addresses = NULL; - struct local_address *a; - int i, n; - - pair[0] = safe_close(pair[0]); - - r = namespace_enter(-1, -1, netns_fd, -1, -1); - if (r < 0) - _exit(EXIT_FAILURE); - - n = local_addresses(NULL, 0, AF_UNSPEC, &addresses); - if (n < 0) - _exit(EXIT_FAILURE); - - for (a = addresses, i = 0; i < n; a++, i++) { - struct iovec iov[2] = { - { .iov_base = &a->family, .iov_len = sizeof(a->family) }, - { .iov_base = &a->address, .iov_len = FAMILY_ADDRESS_SIZE(a->family) }, - }; - - r = writev(pair[1], iov, 2); - if (r < 0) - _exit(EXIT_FAILURE); - } - - pair[1] = safe_close(pair[1]); - - _exit(EXIT_SUCCESS); - } - - pair[1] = safe_close(pair[1]); - - for (;;) { - int family; - ssize_t n; - union in_addr_union in_addr; - struct iovec iov[2]; - struct msghdr mh = { - .msg_iov = iov, - .msg_iovlen = 2, - }; - - iov[0] = (struct iovec) { .iov_base = &family, .iov_len = sizeof(family) }; - iov[1] = (struct iovec) { .iov_base = &in_addr, .iov_len = sizeof(in_addr) }; - - n = recvmsg(pair[0], &mh, 0); - if (n < 0) - return -errno; - if ((size_t) n < sizeof(family)) - break; - - r = sd_bus_message_open_container(reply, 'r', "iay"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "i", family); - if (r < 0) - return r; - - switch (family) { - - case AF_INET: - if (n != sizeof(struct in_addr) + sizeof(family)) - return -EIO; - - r = sd_bus_message_append_array(reply, 'y', &in_addr.in, sizeof(in_addr.in)); - break; - - case AF_INET6: - if (n != sizeof(struct in6_addr) + sizeof(family)) - return -EIO; - - r = sd_bus_message_append_array(reply, 'y', &in_addr.in6, sizeof(in_addr.in6)); - break; - } - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - } - - r = wait_for_terminate(child, &si); - if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); - if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) - return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); - break; - } - - default: - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting IP address data is only supported on container machines."); - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_strv_free_ char **l = NULL; - Machine *m = userdata; - char **k, **v; - int r; - - assert(message); - assert(m); - - switch (m->class) { - - case MACHINE_HOST: - r = load_env_file_pairs(NULL, "/etc/os-release", NULL, &l); - if (r < 0) - return r; - - break; - - case MACHINE_CONTAINER: { - _cleanup_close_ int mntns_fd = -1, root_fd = -1; - _cleanup_close_pair_ int pair[2] = { -1, -1 }; - _cleanup_fclose_ FILE *f = NULL; - siginfo_t si; - pid_t child; - - r = namespace_open(m->leader, NULL, &mntns_fd, NULL, NULL, &root_fd); - if (r < 0) - return r; - - if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) - return -errno; - - child = fork(); - if (child < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); - - if (child == 0) { - _cleanup_close_ int fd = -1; - - pair[0] = safe_close(pair[0]); - - r = namespace_enter(-1, mntns_fd, -1, -1, root_fd); - if (r < 0) - _exit(EXIT_FAILURE); - - fd = open("/etc/os-release", O_RDONLY|O_CLOEXEC); - if (fd < 0) { - fd = open("/usr/lib/os-release", O_RDONLY|O_CLOEXEC); - if (fd < 0) - _exit(EXIT_FAILURE); - } - - r = copy_bytes(fd, pair[1], (uint64_t) -1, false); - if (r < 0) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); - } - - pair[1] = safe_close(pair[1]); - - f = fdopen(pair[0], "re"); - if (!f) - return -errno; - - pair[0] = -1; - - r = load_env_file_pairs(f, "/etc/os-release", NULL, &l); - if (r < 0) - return r; - - r = wait_for_terminate(child, &si); - if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); - if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) - return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); - - break; - } - - default: - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting OS release data is only supported on container machines."); - } - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "{ss}"); - if (r < 0) - return r; - - STRV_FOREACH_PAIR(k, v, l) { - r = sd_bus_message_append(reply, "{ss}", *k, *v); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *pty_name = NULL; - _cleanup_close_ int master = -1; - Machine *m = userdata; - int r; - - assert(message); - assert(m); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-open-pty" : "org.freedesktop.machine1.open-pty", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (master < 0) - return master; - - r = ptsname_namespace(master, &pty_name); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "hs", master, pty_name); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -static int container_bus_new(Machine *m, sd_bus_error *error, sd_bus **ret) { - int r; - - assert(m); - assert(ret); - - switch (m->class) { - - case MACHINE_HOST: - *ret = NULL; - break; - - case MACHINE_CONTAINER: { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - char *address; - - r = sd_bus_new(&bus); - if (r < 0) - return r; - - if (asprintf(&address, "x-machine-kernel:pid=%1$" PID_PRI ";x-machine-unix:pid=%1$" PID_PRI, m->leader) < 0) - return -ENOMEM; - - bus->address = address; - bus->bus_client = true; - bus->trusted = false; - bus->is_system = true; - - r = sd_bus_start(bus); - if (r == -ENOENT) - return sd_bus_error_set_errnof(error, r, "There is no system bus in container %s.", m->name); - if (r < 0) - return r; - - *ret = bus; - bus = NULL; - break; - } - - default: - return -EOPNOTSUPP; - } - - return 0; -} - -int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *pty_name = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL; - _cleanup_close_ int master = -1; - sd_bus *container_bus = NULL; - Machine *m = userdata; - const char *p, *getty; - int r; - - assert(message); - assert(m); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-login" : "org.freedesktop.machine1.login", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (master < 0) - return master; - - r = ptsname_namespace(master, &pty_name); - if (r < 0) - return r; - - p = path_startswith(pty_name, "/dev/pts/"); - if (!p) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "PTS name %s is invalid", pty_name); - - r = container_bus_new(m, error, &allocated_bus); - if (r < 0) - return r; - - container_bus = allocated_bus ?: m->manager->bus; - - getty = strjoina("container-getty@", p, ".service"); - - r = sd_bus_call_method( - container_bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartUnit", - error, NULL, - "ss", getty, "replace"); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "hs", master, pty_name); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *tm = NULL; - _cleanup_free_ char *pty_name = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL; - sd_bus *container_bus = NULL; - _cleanup_close_ int master = -1, slave = -1; - _cleanup_strv_free_ char **env = NULL, **args = NULL; - Machine *m = userdata; - const char *p, *unit, *user, *path, *description, *utmp_id; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "ss", &user, &path); - if (r < 0) - return r; - if (isempty(user)) - user = NULL; - if (isempty(path)) - path = "/bin/sh"; - if (!path_is_absolute(path)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified path '%s' is not absolute", path); - - r = sd_bus_message_read_strv(message, &args); - if (r < 0) - return r; - if (strv_isempty(args)) { - args = strv_free(args); - - args = strv_new(path, NULL); - if (!args) - return -ENOMEM; - - args[0][0] = '-'; /* Tell /bin/sh that this shall be a login shell */ - } - - r = sd_bus_message_read_strv(message, &env); - if (r < 0) - return r; - if (!strv_env_is_valid(env)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments"); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-shell" : "org.freedesktop.machine1.shell", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (master < 0) - return master; - - r = ptsname_namespace(master, &pty_name); - if (r < 0) - return r; - - p = path_startswith(pty_name, "/dev/pts/"); - assert(p); - - slave = machine_open_terminal(m, pty_name, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (slave < 0) - return slave; - - utmp_id = path_startswith(pty_name, "/dev/"); - assert(utmp_id); - - r = container_bus_new(m, error, &allocated_bus); - if (r < 0) - return r; - - container_bus = allocated_bus ?: m->manager->bus; - - r = sd_bus_message_new_method_call( - container_bus, - &tm, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartTransientUnit"); - if (r < 0) - return r; - - /* Name and mode */ - unit = strjoina("container-shell@", p, ".service"); - r = sd_bus_message_append(tm, "ss", unit, "fail"); - if (r < 0) - return r; - - /* Properties */ - r = sd_bus_message_open_container(tm, 'a', "(sv)"); - if (r < 0) - return r; - - description = strjoina("Shell for User ", isempty(user) ? "root" : user); - r = sd_bus_message_append(tm, - "(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)", - "Description", "s", description, - "StandardInputFileDescriptor", "h", slave, - "StandardOutputFileDescriptor", "h", slave, - "StandardErrorFileDescriptor", "h", slave, - "SendSIGHUP", "b", true, - "IgnoreSIGPIPE", "b", false, - "KillMode", "s", "mixed", - "TTYReset", "b", true, - "UtmpIdentifier", "s", utmp_id, - "UtmpMode", "s", "user", - "PAMName", "s", "login", - "WorkingDirectory", "s", "-~"); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "(sv)", "User", "s", isempty(user) ? "root" : user); - if (r < 0) - return r; - - if (!strv_isempty(env)) { - r = sd_bus_message_open_container(tm, 'r', "sv"); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "s", "Environment"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(tm, 'v', "as"); - if (r < 0) - return r; - - r = sd_bus_message_append_strv(tm, env); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - } - - /* Exec container */ - r = sd_bus_message_open_container(tm, 'r', "sv"); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "s", "ExecStart"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(tm, 'v', "a(sasb)"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(tm, 'a', "(sasb)"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(tm, 'r', "sasb"); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "s", path); - if (r < 0) - return r; - - r = sd_bus_message_append_strv(tm, args); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "b", true); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - /* Auxiliary units */ - r = sd_bus_message_append(tm, "a(sa(sv))", 0); - if (r < 0) - return r; - - r = sd_bus_call(container_bus, tm, 0, error, NULL); - if (r < 0) - return r; - - slave = safe_close(slave); - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "hs", master, pty_name); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 }; - char mount_slave[] = "/tmp/propagate.XXXXXX", *mount_tmp, *mount_outside, *p; - bool mount_slave_created = false, mount_slave_mounted = false, - mount_tmp_created = false, mount_tmp_mounted = false, - mount_outside_created = false, mount_outside_mounted = false; - const char *dest, *src; - Machine *m = userdata; - int read_only, make_directory; - pid_t child; - siginfo_t si; - int r; - - assert(message); - assert(m); - - if (m->class != MACHINE_CONTAINER) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Bind mounting is only supported on container machines."); - - r = sd_bus_message_read(message, "ssbb", &src, &dest, &read_only, &make_directory); - if (r < 0) - return r; - - if (!path_is_absolute(src) || !path_is_safe(src)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute and not contain ../."); - - if (isempty(dest)) - dest = src; - else if (!path_is_absolute(dest) || !path_is_safe(dest)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute and not contain ../."); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-machines", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - /* One day, when bind mounting /proc/self/fd/n works across - * namespace boundaries we should rework this logic to make - * use of it... */ - - p = strjoina("/run/systemd/nspawn/propagate/", m->name, "/"); - if (laccess(p, F_OK) < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Container does not allow propagation of mount points."); - - /* Our goal is to install a new bind mount into the container, - possibly read-only. This is irritatingly complex - unfortunately, currently. - - First, we start by creating a private playground in /tmp, - that we can mount MS_SLAVE. (Which is necessary, since - MS_MOVE cannot be applied to mounts with MS_SHARED parent - mounts.) */ - - if (!mkdtemp(mount_slave)) - return sd_bus_error_set_errnof(error, errno, "Failed to create playground %s: %m", mount_slave); - - mount_slave_created = true; - - if (mount(mount_slave, mount_slave, NULL, MS_BIND, NULL) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to make bind mount %s: %m", mount_slave); - goto finish; - } - - mount_slave_mounted = true; - - if (mount(NULL, mount_slave, NULL, MS_SLAVE, NULL) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to remount slave %s: %m", mount_slave); - goto finish; - } - - /* Second, we mount the source directory to a directory inside - of our MS_SLAVE playground. */ - mount_tmp = strjoina(mount_slave, "/mount"); - if (mkdir(mount_tmp, 0700) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount point %s: %m", mount_tmp); - goto finish; - } - - mount_tmp_created = true; - - if (mount(src, mount_tmp, NULL, MS_BIND, NULL) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to overmount %s: %m", mount_tmp); - goto finish; - } - - mount_tmp_mounted = true; - - /* Third, we remount the new bind mount read-only if requested. */ - if (read_only) - if (mount(NULL, mount_tmp, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to remount read-only %s: %m", mount_tmp); - goto finish; - } - - /* Fourth, we move the new bind mount into the propagation - * directory. This way it will appear there read-only - * right-away. */ - - mount_outside = strjoina("/run/systemd/nspawn/propagate/", m->name, "/XXXXXX"); - if (!mkdtemp(mount_outside)) { - r = sd_bus_error_set_errnof(error, errno, "Cannot create propagation directory %s: %m", mount_outside); - goto finish; - } - - mount_outside_created = true; - - if (mount(mount_tmp, mount_outside, NULL, MS_MOVE, NULL) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to move %s to %s: %m", mount_tmp, mount_outside); - goto finish; - } - - mount_outside_mounted = true; - mount_tmp_mounted = false; - - (void) rmdir(mount_tmp); - mount_tmp_created = false; - - (void) umount(mount_slave); - mount_slave_mounted = false; - - (void) rmdir(mount_slave); - mount_slave_created = false; - - if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m"); - goto finish; - } - - child = fork(); - if (child < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); - goto finish; - } - - if (child == 0) { - const char *mount_inside; - int mntfd; - const char *q; - - errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); - - q = procfs_file_alloca(m->leader, "ns/mnt"); - mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (mntfd < 0) { - r = log_error_errno(errno, "Failed to open mount namespace of leader: %m"); - goto child_fail; - } - - if (setns(mntfd, CLONE_NEWNS) < 0) { - r = log_error_errno(errno, "Failed to join namespace of leader: %m"); - goto child_fail; - } - - if (make_directory) - (void) mkdir_p(dest, 0755); - - /* Fifth, move the mount to the right place inside */ - mount_inside = strjoina("/run/systemd/nspawn/incoming/", basename(mount_outside)); - if (mount(mount_inside, dest, NULL, MS_MOVE, NULL) < 0) { - r = log_error_errno(errno, "Failed to mount: %m"); - goto child_fail; - } - - _exit(EXIT_SUCCESS); - - child_fail: - (void) write(errno_pipe_fd[1], &r, sizeof(r)); - errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); - - _exit(EXIT_FAILURE); - } - - errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); - - r = wait_for_terminate(child, &si); - if (r < 0) { - r = sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); - goto finish; - } - if (si.si_code != CLD_EXITED) { - r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); - goto finish; - } - if (si.si_status != EXIT_SUCCESS) { - - if (read(errno_pipe_fd[0], &r, sizeof(r)) == sizeof(r)) - r = sd_bus_error_set_errnof(error, r, "Failed to mount: %m"); - else - r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child failed."); - goto finish; - } - - r = sd_bus_reply_method_return(message, NULL); - -finish: - if (mount_outside_mounted) - umount(mount_outside); - if (mount_outside_created) - rmdir(mount_outside); - - if (mount_tmp_mounted) - umount(mount_tmp); - if (mount_tmp_created) - rmdir(mount_tmp); - - if (mount_slave_mounted) - umount(mount_slave); - if (mount_slave_created) - rmdir(mount_slave); - - return r; -} - -int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error) { - const char *src, *dest, *host_path, *container_path, *host_basename, *host_dirname, *container_basename, *container_dirname; - _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 }; - _cleanup_close_ int hostfd = -1; - Machine *m = userdata; - bool copy_from; - pid_t child; - char *t; - int r; - - assert(message); - assert(m); - - if (m->manager->n_operations >= OPERATIONS_MAX) - return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing copies."); - - if (m->class != MACHINE_CONTAINER) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Copying files is only supported on container machines."); - - r = sd_bus_message_read(message, "ss", &src, &dest); - if (r < 0) - return r; - - if (!path_is_absolute(src)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute."); - - if (isempty(dest)) - dest = src; - else if (!path_is_absolute(dest)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute."); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-machines", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - copy_from = strstr(sd_bus_message_get_member(message), "CopyFrom"); - - if (copy_from) { - container_path = src; - host_path = dest; - } else { - host_path = src; - container_path = dest; - } - - host_basename = basename(host_path); - t = strdupa(host_path); - host_dirname = dirname(t); - - container_basename = basename(container_path); - t = strdupa(container_path); - container_dirname = dirname(t); - - hostfd = open(host_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY); - if (hostfd < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to open host directory %s: %m", host_dirname); - - if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m"); - - child = fork(); - if (child < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); - - if (child == 0) { - int containerfd; - const char *q; - int mntfd; - - errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); - - q = procfs_file_alloca(m->leader, "ns/mnt"); - mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (mntfd < 0) { - r = log_error_errno(errno, "Failed to open mount namespace of leader: %m"); - goto child_fail; - } - - if (setns(mntfd, CLONE_NEWNS) < 0) { - r = log_error_errno(errno, "Failed to join namespace of leader: %m"); - goto child_fail; - } - - containerfd = open(container_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY); - if (containerfd < 0) { - r = log_error_errno(errno, "Failed top open destination directory: %m"); - goto child_fail; - } - - if (copy_from) - r = copy_tree_at(containerfd, container_basename, hostfd, host_basename, true); - else - r = copy_tree_at(hostfd, host_basename, containerfd, container_basename, true); - - hostfd = safe_close(hostfd); - containerfd = safe_close(containerfd); - - if (r < 0) { - r = log_error_errno(r, "Failed to copy tree: %m"); - goto child_fail; - } - - _exit(EXIT_SUCCESS); - - child_fail: - (void) write(errno_pipe_fd[1], &r, sizeof(r)); - errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); - - _exit(EXIT_FAILURE); - } - - errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); - - /* Copying might take a while, hence install a watch on the child, and return */ - - r = operation_new(m->manager, m, child, message, errno_pipe_fd[0]); - if (r < 0) { - (void) sigkill_wait(child); - return r; - } - errno_pipe_fd[0] = -1; - - return 1; -} - -int bus_machine_method_open_root_directory(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_close_ int fd = -1; - Machine *m = userdata; - int r; - - assert(message); - assert(m); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-machines", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - switch (m->class) { - - case MACHINE_HOST: - fd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - return -errno; - - break; - - case MACHINE_CONTAINER: { - _cleanup_close_ int mntns_fd = -1, root_fd = -1; - _cleanup_close_pair_ int pair[2] = { -1, -1 }; - siginfo_t si; - pid_t child; - - r = namespace_open(m->leader, NULL, &mntns_fd, NULL, NULL, &root_fd); - if (r < 0) - return r; - - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) - return -errno; - - child = fork(); - if (child < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); - - if (child == 0) { - _cleanup_close_ int dfd = -1; - - pair[0] = safe_close(pair[0]); - - r = namespace_enter(-1, mntns_fd, -1, -1, root_fd); - if (r < 0) - _exit(EXIT_FAILURE); - - dfd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY); - if (dfd < 0) - _exit(EXIT_FAILURE); - - r = send_one_fd(pair[1], dfd, 0); - dfd = safe_close(dfd); - if (r < 0) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); - } - - pair[1] = safe_close(pair[1]); - - r = wait_for_terminate(child, &si); - if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); - if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) - return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); - - fd = receive_one_fd(pair[0], MSG_DONTWAIT); - if (fd < 0) - return fd; - - break; - } - - default: - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Opening the root directory is only supported on container machines."); - } - - return sd_bus_reply_method_return(message, "h", fd); -} - -const sd_bus_vtable machine_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Machine, name), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Id", "ay", property_get_id, 0, SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("Timestamp", offsetof(Machine, timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Service", "s", NULL, offsetof(Machine, service), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Unit", "s", NULL, offsetof(Machine, unit), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Scope", "s", NULL, offsetof(Machine, unit), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("Leader", "u", NULL, offsetof(Machine, leader), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Class", "s", property_get_class, offsetof(Machine, class), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RootDirectory", "s", NULL, offsetof(Machine, root_directory), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("NetworkInterfaces", "ai", property_get_netif, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("State", "s", property_get_state, 0, 0), - SD_BUS_METHOD("Terminate", NULL, NULL, bus_machine_method_terminate, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Kill", "si", NULL, bus_machine_method_kill, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetAddresses", NULL, "a(iay)", bus_machine_method_get_addresses, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetOSRelease", NULL, "a{ss}", bus_machine_method_get_os_release, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("OpenPTY", NULL, "hs", bus_machine_method_open_pty, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("OpenLogin", NULL, "hs", bus_machine_method_open_login, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("OpenShell", "ssasas", "hs", bus_machine_method_open_shell, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("BindMount", "ssbb", NULL, bus_machine_method_bind_mount, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CopyFrom", "ss", NULL, bus_machine_method_copy, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CopyTo", "ss", NULL, bus_machine_method_copy, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("OpenRootDirectory", NULL, "h", bus_machine_method_open_root_directory, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_VTABLE_END -}; - -int machine_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - if (streq(path, "/org/freedesktop/machine1/machine/self")) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - sd_bus_message *message; - pid_t pid; - - message = sd_bus_get_current_message(bus); - if (!message) - return 0; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_pid(creds, &pid); - if (r < 0) - return r; - - r = manager_get_machine_by_pid(m, pid, &machine); - if (r <= 0) - return 0; - } else { - _cleanup_free_ char *e = NULL; - const char *p; - - p = startswith(path, "/org/freedesktop/machine1/machine/"); - if (!p) - return 0; - - e = bus_label_unescape(p); - if (!e) - return -ENOMEM; - - machine = hashmap_get(m->machines, e); - if (!machine) - return 0; - } - - *found = machine; - return 1; -} - -char *machine_bus_path(Machine *m) { - _cleanup_free_ char *e = NULL; - - assert(m); - - e = bus_label_escape(m->name); - if (!e) - return NULL; - - return strappend("/org/freedesktop/machine1/machine/", e); -} - -int machine_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { - _cleanup_strv_free_ char **l = NULL; - Machine *machine = NULL; - Manager *m = userdata; - Iterator i; - int r; - - assert(bus); - assert(path); - assert(nodes); - - HASHMAP_FOREACH(machine, m->machines, i) { - char *p; - - p = machine_bus_path(machine); - if (!p) - return -ENOMEM; - - r = strv_consume(&l, p); - if (r < 0) - return r; - } - - *nodes = l; - l = NULL; - - return 1; -} - -int machine_send_signal(Machine *m, bool new_machine) { - _cleanup_free_ char *p = NULL; - - assert(m); - - p = machine_bus_path(m); - if (!p) - return -ENOMEM; - - return sd_bus_emit_signal( - m->manager->bus, - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - new_machine ? "MachineNew" : "MachineRemoved", - "so", m->name, p); -} - -int machine_send_create_reply(Machine *m, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL; - _cleanup_free_ char *p = NULL; - - assert(m); - - if (!m->create_message) - return 0; - - c = m->create_message; - m->create_message = NULL; - - if (error) - return sd_bus_reply_method_error(c, error); - - /* Update the machine state file before we notify the client - * about the result. */ - machine_save(m); - - p = machine_bus_path(m); - if (!p) - return -ENOMEM; - - return sd_bus_reply_method_return(c, "o", p); -} diff --git a/src/machine/machine-dbus.h b/src/machine/machine-dbus.h deleted file mode 100644 index 241b23c7ec..0000000000 --- a/src/machine/machine-dbus.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" - -#include "machine.h" - -extern const sd_bus_vtable machine_vtable[]; - -char *machine_bus_path(Machine *s); -int machine_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); -int machine_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); - -int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_open_root_directory(sd_bus_message *message, void *userdata, sd_bus_error *error); - -int machine_send_signal(Machine *m, bool new_machine); -int machine_send_create_reply(Machine *m, sd_bus_error *error); diff --git a/src/machine/machine.c b/src/machine/machine.c deleted file mode 100644 index c1fae57084..0000000000 --- a/src/machine/machine.c +++ /dev/null @@ -1,630 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include - -#include "sd-messages.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "escape.h" -#include "extract-word.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "hashmap.h" -#include "machine-dbus.h" -#include "machine.h" -#include "mkdir.h" -#include "parse-util.h" -#include "process-util.h" -#include "special.h" -#include "string-table.h" -#include "terminal-util.h" -#include "unit-name.h" -#include "util.h" - -Machine* machine_new(Manager *manager, MachineClass class, const char *name) { - Machine *m; - - assert(manager); - assert(class < _MACHINE_CLASS_MAX); - assert(name); - - /* Passing class == _MACHINE_CLASS_INVALID here is fine. It - * means as much as "we don't know yet", and that we'll figure - * it out later when loading the state file. */ - - m = new0(Machine, 1); - if (!m) - return NULL; - - m->name = strdup(name); - if (!m->name) - goto fail; - - if (class != MACHINE_HOST) { - m->state_file = strappend("/run/systemd/machines/", m->name); - if (!m->state_file) - goto fail; - } - - m->class = class; - - if (hashmap_put(manager->machines, m->name, m) < 0) - goto fail; - - m->manager = manager; - - return m; - -fail: - free(m->state_file); - free(m->name); - free(m); - - return NULL; -} - -void machine_free(Machine *m) { - assert(m); - - while (m->operations) - operation_free(m->operations); - - if (m->in_gc_queue) - LIST_REMOVE(gc_queue, m->manager->machine_gc_queue, m); - - machine_release_unit(m); - - free(m->scope_job); - - (void) hashmap_remove(m->manager->machines, m->name); - - if (m->manager->host_machine == m) - m->manager->host_machine = NULL; - - if (m->leader > 0) - (void) hashmap_remove_value(m->manager->machine_leaders, PID_TO_PTR(m->leader), m); - - sd_bus_message_unref(m->create_message); - - free(m->name); - free(m->state_file); - free(m->service); - free(m->root_directory); - free(m->netif); - free(m); -} - -int machine_save(Machine *m) { - _cleanup_free_ char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(m); - - if (!m->state_file) - return 0; - - if (!m->started) - return 0; - - r = mkdir_safe_label("/run/systemd/machines", 0755, 0, 0); - if (r < 0) - goto fail; - - r = fopen_temporary(m->state_file, &f, &temp_path); - if (r < 0) - goto fail; - - (void) fchmod(fileno(f), 0644); - - fprintf(f, - "# This is private data. Do not parse.\n" - "NAME=%s\n", - m->name); - - if (m->unit) { - _cleanup_free_ char *escaped; - - escaped = cescape(m->unit); - if (!escaped) { - r = -ENOMEM; - goto fail; - } - - fprintf(f, "SCOPE=%s\n", escaped); /* We continue to call this "SCOPE=" because it is internal only, and we want to stay compatible with old files */ - } - - if (m->scope_job) - fprintf(f, "SCOPE_JOB=%s\n", m->scope_job); - - if (m->service) { - _cleanup_free_ char *escaped; - - escaped = cescape(m->service); - if (!escaped) { - r = -ENOMEM; - goto fail; - } - fprintf(f, "SERVICE=%s\n", escaped); - } - - if (m->root_directory) { - _cleanup_free_ char *escaped; - - escaped = cescape(m->root_directory); - if (!escaped) { - r = -ENOMEM; - goto fail; - } - fprintf(f, "ROOT=%s\n", escaped); - } - - if (!sd_id128_equal(m->id, SD_ID128_NULL)) - fprintf(f, "ID=" SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->id)); - - if (m->leader != 0) - fprintf(f, "LEADER="PID_FMT"\n", m->leader); - - if (m->class != _MACHINE_CLASS_INVALID) - fprintf(f, "CLASS=%s\n", machine_class_to_string(m->class)); - - if (dual_timestamp_is_set(&m->timestamp)) - fprintf(f, - "REALTIME="USEC_FMT"\n" - "MONOTONIC="USEC_FMT"\n", - m->timestamp.realtime, - m->timestamp.monotonic); - - if (m->n_netif > 0) { - unsigned i; - - fputs("NETIF=", f); - - for (i = 0; i < m->n_netif; i++) { - if (i != 0) - fputc(' ', f); - - fprintf(f, "%i", m->netif[i]); - } - - fputc('\n', f); - } - - r = fflush_and_check(f); - if (r < 0) - goto fail; - - if (rename(temp_path, m->state_file) < 0) { - r = -errno; - goto fail; - } - - if (m->unit) { - char *sl; - - /* Create a symlink from the unit name to the machine - * name, so that we can quickly find the machine for - * each given unit. Ignore error. */ - sl = strjoina("/run/systemd/machines/unit:", m->unit); - (void) symlink(m->name, sl); - } - - return 0; - -fail: - (void) unlink(m->state_file); - - if (temp_path) - (void) unlink(temp_path); - - return log_error_errno(r, "Failed to save machine data %s: %m", m->state_file); -} - -static void machine_unlink(Machine *m) { - assert(m); - - if (m->unit) { - - char *sl; - - sl = strjoina("/run/systemd/machines/unit:", m->unit); - (void) unlink(sl); - } - - if (m->state_file) - (void) unlink(m->state_file); -} - -int machine_load(Machine *m) { - _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *id = NULL, *leader = NULL, *class = NULL, *netif = NULL; - int r; - - assert(m); - - if (!m->state_file) - return 0; - - r = parse_env_file(m->state_file, NEWLINE, - "SCOPE", &m->unit, - "SCOPE_JOB", &m->scope_job, - "SERVICE", &m->service, - "ROOT", &m->root_directory, - "ID", &id, - "LEADER", &leader, - "CLASS", &class, - "REALTIME", &realtime, - "MONOTONIC", &monotonic, - "NETIF", &netif, - NULL); - if (r < 0) { - if (r == -ENOENT) - return 0; - - return log_error_errno(r, "Failed to read %s: %m", m->state_file); - } - - if (id) - sd_id128_from_string(id, &m->id); - - if (leader) - parse_pid(leader, &m->leader); - - if (class) { - MachineClass c; - - c = machine_class_from_string(class); - if (c >= 0) - m->class = c; - } - - if (realtime) - timestamp_deserialize(realtime, &m->timestamp.realtime); - if (monotonic) - timestamp_deserialize(monotonic, &m->timestamp.monotonic); - - if (netif) { - size_t allocated = 0, nr = 0; - const char *p; - int *ni = NULL; - - p = netif; - for (;;) { - _cleanup_free_ char *word = NULL; - int ifi; - - r = extract_first_word(&p, &word, NULL, 0); - if (r == 0) - break; - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_warning_errno(r, "Failed to parse NETIF: %s", netif); - break; - } - - if (parse_ifindex(word, &ifi) < 0) - continue; - - if (!GREEDY_REALLOC(ni, allocated, nr+1)) { - free(ni); - return log_oom(); - } - - ni[nr++] = ifi; - } - - free(m->netif); - m->netif = ni; - m->n_netif = nr; - } - - return r; -} - -static int machine_start_scope(Machine *m, sd_bus_message *properties, sd_bus_error *error) { - int r = 0; - - assert(m); - assert(m->class != MACHINE_HOST); - - if (!m->unit) { - _cleanup_free_ char *escaped = NULL; - char *scope, *description, *job = NULL; - - escaped = unit_name_escape(m->name); - if (!escaped) - return log_oom(); - - scope = strjoin("machine-", escaped, ".scope", NULL); - if (!scope) - return log_oom(); - - description = strjoina(m->class == MACHINE_VM ? "Virtual Machine " : "Container ", m->name); - - r = manager_start_scope(m->manager, scope, m->leader, SPECIAL_MACHINE_SLICE, description, properties, error, &job); - if (r < 0) { - log_error("Failed to start machine scope: %s", bus_error_message(error, r)); - free(scope); - return r; - } else { - m->unit = scope; - - free(m->scope_job); - m->scope_job = job; - } - } - - if (m->unit) - hashmap_put(m->manager->machine_units, m->unit, m); - - return r; -} - -int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error) { - int r; - - assert(m); - - if (!IN_SET(m->class, MACHINE_CONTAINER, MACHINE_VM)) - return -EOPNOTSUPP; - - if (m->started) - return 0; - - r = hashmap_put(m->manager->machine_leaders, PID_TO_PTR(m->leader), m); - if (r < 0) - return r; - - /* Create cgroup */ - r = machine_start_scope(m, properties, error); - if (r < 0) - return r; - - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_MACHINE_START), - "NAME=%s", m->name, - "LEADER="PID_FMT, m->leader, - LOG_MESSAGE("New machine %s.", m->name), - NULL); - - if (!dual_timestamp_is_set(&m->timestamp)) - dual_timestamp_get(&m->timestamp); - - m->started = true; - - /* Save new machine data */ - machine_save(m); - - machine_send_signal(m, true); - - return 0; -} - -static int machine_stop_scope(Machine *m) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char *job = NULL; - int r; - - assert(m); - assert(m->class != MACHINE_HOST); - - if (!m->unit) - return 0; - - r = manager_stop_unit(m->manager, m->unit, &error, &job); - if (r < 0) { - log_error("Failed to stop machine scope: %s", bus_error_message(&error, r)); - return r; - } - - free(m->scope_job); - m->scope_job = job; - - return 0; -} - -int machine_stop(Machine *m) { - int r; - assert(m); - - if (!IN_SET(m->class, MACHINE_CONTAINER, MACHINE_VM)) - return -EOPNOTSUPP; - - r = machine_stop_scope(m); - - m->stopping = true; - - machine_save(m); - - return r; -} - -int machine_finalize(Machine *m) { - assert(m); - - if (m->started) - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_MACHINE_STOP), - "NAME=%s", m->name, - "LEADER="PID_FMT, m->leader, - LOG_MESSAGE("Machine %s terminated.", m->name), - NULL); - - machine_unlink(m); - machine_add_to_gc_queue(m); - - if (m->started) { - machine_send_signal(m, false); - m->started = false; - } - - return 0; -} - -bool machine_check_gc(Machine *m, bool drop_not_started) { - assert(m); - - if (m->class == MACHINE_HOST) - return true; - - if (drop_not_started && !m->started) - return false; - - if (m->scope_job && manager_job_is_active(m->manager, m->scope_job)) - return true; - - if (m->unit && manager_unit_is_active(m->manager, m->unit)) - return true; - - return false; -} - -void machine_add_to_gc_queue(Machine *m) { - assert(m); - - if (m->in_gc_queue) - return; - - LIST_PREPEND(gc_queue, m->manager->machine_gc_queue, m); - m->in_gc_queue = true; -} - -MachineState machine_get_state(Machine *s) { - assert(s); - - if (s->class == MACHINE_HOST) - return MACHINE_RUNNING; - - if (s->stopping) - return MACHINE_CLOSING; - - if (s->scope_job) - return MACHINE_OPENING; - - return MACHINE_RUNNING; -} - -int machine_kill(Machine *m, KillWho who, int signo) { - assert(m); - - if (!IN_SET(m->class, MACHINE_VM, MACHINE_CONTAINER)) - return -EOPNOTSUPP; - - if (!m->unit) - return -ESRCH; - - if (who == KILL_LEADER) { - /* If we shall simply kill the leader, do so directly */ - - if (kill(m->leader, signo) < 0) - return -errno; - - return 0; - } - - /* Otherwise, make PID 1 do it for us, for the entire cgroup */ - return manager_kill_unit(m->manager, m->unit, signo, NULL); -} - -int machine_openpt(Machine *m, int flags) { - assert(m); - - switch (m->class) { - - case MACHINE_HOST: { - int fd; - - fd = posix_openpt(flags); - if (fd < 0) - return -errno; - - if (unlockpt(fd) < 0) - return -errno; - - return fd; - } - - case MACHINE_CONTAINER: - if (m->leader <= 0) - return -EINVAL; - - return openpt_in_namespace(m->leader, flags); - - default: - return -EOPNOTSUPP; - } -} - -int machine_open_terminal(Machine *m, const char *path, int mode) { - assert(m); - - switch (m->class) { - - case MACHINE_HOST: - return open_terminal(path, mode); - - case MACHINE_CONTAINER: - if (m->leader <= 0) - return -EINVAL; - - return open_terminal_in_namespace(m->leader, path, mode); - - default: - return -EOPNOTSUPP; - } -} - -void machine_release_unit(Machine *m) { - assert(m); - - if (!m->unit) - return; - - (void) hashmap_remove(m->manager->machine_units, m->unit); - m->unit = mfree(m->unit); -} - -static const char* const machine_class_table[_MACHINE_CLASS_MAX] = { - [MACHINE_CONTAINER] = "container", - [MACHINE_VM] = "vm", - [MACHINE_HOST] = "host", -}; - -DEFINE_STRING_TABLE_LOOKUP(machine_class, MachineClass); - -static const char* const machine_state_table[_MACHINE_STATE_MAX] = { - [MACHINE_OPENING] = "opening", - [MACHINE_RUNNING] = "running", - [MACHINE_CLOSING] = "closing" -}; - -DEFINE_STRING_TABLE_LOOKUP(machine_state, MachineState); - -static const char* const kill_who_table[_KILL_WHO_MAX] = { - [KILL_LEADER] = "leader", - [KILL_ALL] = "all" -}; - -DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho); diff --git a/src/machine/machine.h b/src/machine/machine.h deleted file mode 100644 index e5d75361a9..0000000000 --- a/src/machine/machine.h +++ /dev/null @@ -1,110 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -typedef struct Machine Machine; -typedef enum KillWho KillWho; - -#include "list.h" -#include "machined.h" -#include "operation.h" - -typedef enum MachineState { - MACHINE_OPENING, /* Machine is being registered */ - MACHINE_RUNNING, /* Machine is running */ - MACHINE_CLOSING, /* Machine is terminating */ - _MACHINE_STATE_MAX, - _MACHINE_STATE_INVALID = -1 -} MachineState; - -typedef enum MachineClass { - MACHINE_CONTAINER, - MACHINE_VM, - MACHINE_HOST, - _MACHINE_CLASS_MAX, - _MACHINE_CLASS_INVALID = -1 -} MachineClass; - -enum KillWho { - KILL_LEADER, - KILL_ALL, - _KILL_WHO_MAX, - _KILL_WHO_INVALID = -1 -}; - -struct Machine { - Manager *manager; - - char *name; - sd_id128_t id; - - MachineClass class; - - char *state_file; - char *service; - char *root_directory; - - char *unit; - char *scope_job; - - pid_t leader; - - dual_timestamp timestamp; - - bool in_gc_queue:1; - bool started:1; - bool stopping:1; - - sd_bus_message *create_message; - - int *netif; - unsigned n_netif; - - LIST_HEAD(Operation, operations); - - LIST_FIELDS(Machine, gc_queue); -}; - -Machine* machine_new(Manager *manager, MachineClass class, const char *name); -void machine_free(Machine *m); -bool machine_check_gc(Machine *m, bool drop_not_started); -void machine_add_to_gc_queue(Machine *m); -int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error); -int machine_stop(Machine *m); -int machine_finalize(Machine *m); -int machine_save(Machine *m); -int machine_load(Machine *m); -int machine_kill(Machine *m, KillWho who, int signo); - -void machine_release_unit(Machine *m); - -MachineState machine_get_state(Machine *u); - -const char* machine_class_to_string(MachineClass t) _const_; -MachineClass machine_class_from_string(const char *s) _pure_; - -const char* machine_state_to_string(MachineState t) _const_; -MachineState machine_state_from_string(const char *s) _pure_; - -const char *kill_who_to_string(KillWho k) _const_; -KillWho kill_who_from_string(const char *s) _pure_; - -int machine_openpt(Machine *m, int flags); -int machine_open_terminal(Machine *m, const char *path, int mode); diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c deleted file mode 100644 index 8e4ffa9a39..0000000000 --- a/src/machine/machinectl.c +++ /dev/null @@ -1,2782 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-unit-util.h" -#include "bus-util.h" -#include "cgroup-show.h" -#include "cgroup-util.h" -#include "copy.h" -#include "env-util.h" -#include "fd-util.h" -#include "hostname-util.h" -#include "import-util.h" -#include "log.h" -#include "logs-show.h" -#include "macro.h" -#include "mkdir.h" -#include "pager.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "ptyfwd.h" -#include "signal-util.h" -#include "spawn-polkit-agent.h" -#include "strv.h" -#include "terminal-util.h" -#include "unit-name.h" -#include "util.h" -#include "verbs.h" -#include "web-util.h" - -static char **arg_property = NULL; -static bool arg_all = false; -static bool arg_value = false; -static bool arg_full = false; -static bool arg_no_pager = false; -static bool arg_legend = true; -static const char *arg_kill_who = NULL; -static int arg_signal = SIGTERM; -static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; -static char *arg_host = NULL; -static bool arg_read_only = false; -static bool arg_mkdir = false; -static bool arg_quiet = false; -static bool arg_ask_password = true; -static unsigned arg_lines = 10; -static OutputMode arg_output = OUTPUT_SHORT; -static bool arg_force = false; -static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; -static const char* arg_format = NULL; -static const char *arg_uid = NULL; -static char **arg_setenv = NULL; - -static void polkit_agent_open_if_enabled(void) { - - /* Open the polkit agent as a child process if necessary */ - - if (!arg_ask_password) - return; - - if (arg_transport != BUS_TRANSPORT_LOCAL) - return; - - polkit_agent_open(); -} - -static OutputFlags get_output_flags(void) { - return - arg_all * OUTPUT_SHOW_ALL | - arg_full * OUTPUT_FULL_WIDTH | - (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH | - colors_enabled() * OUTPUT_COLOR | - !arg_quiet * OUTPUT_WARN_CUTOFF; -} - -typedef struct MachineInfo { - const char *name; - const char *class; - const char *service; -} MachineInfo; - -static int compare_machine_info(const void *a, const void *b) { - const MachineInfo *x = a, *y = b; - - return strcmp(x->name, y->name); -} - -static int list_machines(int argc, char *argv[], void *userdata) { - - size_t max_name = strlen("MACHINE"), max_class = strlen("CLASS"), max_service = strlen("SERVICE"); - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ MachineInfo *machines = NULL; - const char *name, *class, *service, *object; - size_t n_machines = 0, n_allocated = 0, j; - sd_bus *bus = userdata; - int r; - - assert(bus); - - pager_open(arg_no_pager, false); - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "ListMachines", - &error, - &reply, - NULL); - if (r < 0) { - log_error("Could not get machines: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_enter_container(reply, 'a', "(ssso)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) { - size_t l; - - if (name[0] == '.' && !arg_all) - continue; - - if (!GREEDY_REALLOC(machines, n_allocated, n_machines + 1)) - return log_oom(); - - machines[n_machines].name = name; - machines[n_machines].class = class; - machines[n_machines].service = service; - - l = strlen(name); - if (l > max_name) - max_name = l; - - l = strlen(class); - if (l > max_class) - max_class = l; - - l = strlen(service); - if (l > max_service) - max_service = l; - - n_machines++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - qsort_safe(machines, n_machines, sizeof(MachineInfo), compare_machine_info); - - if (arg_legend) - printf("%-*s %-*s %-*s\n", - (int) max_name, "MACHINE", - (int) max_class, "CLASS", - (int) max_service, "SERVICE"); - - for (j = 0; j < n_machines; j++) - printf("%-*s %-*s %-*s\n", - (int) max_name, machines[j].name, - (int) max_class, machines[j].class, - (int) max_service, machines[j].service); - - if (arg_legend) - printf("\n%zu machines listed.\n", n_machines); - - return 0; -} - -typedef struct ImageInfo { - const char *name; - const char *type; - bool read_only; - usec_t crtime; - usec_t mtime; - uint64_t size; -} ImageInfo; - -static int compare_image_info(const void *a, const void *b) { - const ImageInfo *x = a, *y = b; - - return strcmp(x->name, y->name); -} - -static int list_images(int argc, char *argv[], void *userdata) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - size_t max_name = strlen("NAME"), max_type = strlen("TYPE"), max_size = strlen("USAGE"), max_crtime = strlen("CREATED"), max_mtime = strlen("MODIFIED"); - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ ImageInfo *images = NULL; - size_t n_images = 0, n_allocated = 0, j; - const char *name, *type, *object; - sd_bus *bus = userdata; - uint64_t crtime, mtime, size; - int read_only, r; - - assert(bus); - - pager_open(arg_no_pager, false); - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "ListImages", - &error, - &reply, - ""); - if (r < 0) { - log_error("Could not get images: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbttto)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "(ssbttto)", &name, &type, &read_only, &crtime, &mtime, &size, &object)) > 0) { - char buf[MAX(FORMAT_TIMESTAMP_MAX, FORMAT_BYTES_MAX)]; - size_t l; - - if (name[0] == '.' && !arg_all) - continue; - - if (!GREEDY_REALLOC(images, n_allocated, n_images + 1)) - return log_oom(); - - images[n_images].name = name; - images[n_images].type = type; - images[n_images].read_only = read_only; - images[n_images].crtime = crtime; - images[n_images].mtime = mtime; - images[n_images].size = size; - - l = strlen(name); - if (l > max_name) - max_name = l; - - l = strlen(type); - if (l > max_type) - max_type = l; - - if (crtime != 0) { - l = strlen(strna(format_timestamp(buf, sizeof(buf), crtime))); - if (l > max_crtime) - max_crtime = l; - } - - if (mtime != 0) { - l = strlen(strna(format_timestamp(buf, sizeof(buf), mtime))); - if (l > max_mtime) - max_mtime = l; - } - - if (size != (uint64_t) -1) { - l = strlen(strna(format_bytes(buf, sizeof(buf), size))); - if (l > max_size) - max_size = l; - } - - n_images++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - qsort_safe(images, n_images, sizeof(ImageInfo), compare_image_info); - - if (arg_legend) - printf("%-*s %-*s %-3s %-*s %-*s %-*s\n", - (int) max_name, "NAME", - (int) max_type, "TYPE", - "RO", - (int) max_size, "USAGE", - (int) max_crtime, "CREATED", - (int) max_mtime, "MODIFIED"); - - for (j = 0; j < n_images; j++) { - char crtime_buf[FORMAT_TIMESTAMP_MAX], mtime_buf[FORMAT_TIMESTAMP_MAX], size_buf[FORMAT_BYTES_MAX]; - - printf("%-*s %-*s %s%-3s%s %-*s %-*s %-*s\n", - (int) max_name, images[j].name, - (int) max_type, images[j].type, - images[j].read_only ? ansi_highlight_red() : "", yes_no(images[j].read_only), images[j].read_only ? ansi_normal() : "", - (int) max_size, strna(format_bytes(size_buf, sizeof(size_buf), images[j].size)), - (int) max_crtime, strna(format_timestamp(crtime_buf, sizeof(crtime_buf), images[j].crtime)), - (int) max_mtime, strna(format_timestamp(mtime_buf, sizeof(mtime_buf), images[j].mtime))); - } - - if (arg_legend) - printf("\n%zu images listed.\n", n_images); - - return 0; -} - -static int show_unit_cgroup(sd_bus *bus, const char *unit, pid_t leader) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *path = NULL; - const char *cgroup; - int r; - unsigned c; - - assert(bus); - assert(unit); - - path = unit_dbus_path_from_name(unit); - if (!path) - return log_oom(); - - r = sd_bus_get_property( - bus, - "org.freedesktop.systemd1", - path, - unit_dbus_interface_from_name(unit), - "ControlGroup", - &error, - &reply, - "s"); - if (r < 0) - return log_error_errno(r, "Failed to query ControlGroup: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "s", &cgroup); - if (r < 0) - return bus_log_parse_error(r); - - if (isempty(cgroup)) - return 0; - - c = columns(); - if (c > 18) - c -= 18; - else - c = 0; - - r = unit_show_processes(bus, unit, cgroup, "\t\t ", c, get_output_flags(), &error); - if (r == -EBADR) { - - if (arg_transport == BUS_TRANSPORT_REMOTE) - return 0; - - /* Fallback for older systemd versions where the GetUnitProcesses() call is not yet available */ - - if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup) != 0 && leader <= 0) - return 0; - - show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, &leader, leader > 0, get_output_flags()); - } else if (r < 0) - return log_error_errno(r, "Failed to dump process list: %s", bus_error_message(&error, r)); - - return 0; -} - -static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *prefix, const char *prefix2) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int r; - - assert(bus); - assert(name); - assert(prefix); - assert(prefix2); - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetMachineAddresses", - NULL, - &reply, - "s", name); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(reply, 'a', "(iay)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) { - int family; - const void *a; - size_t sz; - char buffer[MAX(INET6_ADDRSTRLEN, INET_ADDRSTRLEN)]; - - r = sd_bus_message_read(reply, "i", &family); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - return bus_log_parse_error(r); - - fputs(prefix, stdout); - fputs(inet_ntop(family, a, buffer, sizeof(buffer)), stdout); - if (family == AF_INET6 && ifi > 0) - printf("%%%i", ifi); - fputc('\n', stdout); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (prefix != prefix2) - prefix = prefix2; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - return 0; -} - -static int print_os_release(sd_bus *bus, const char *name, const char *prefix) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *k, *v, *pretty = NULL; - int r; - - assert(bus); - assert(name); - assert(prefix); - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetMachineOSRelease", - NULL, - &reply, - "s", name); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(reply, 'a', "{ss}"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "{ss}", &k, &v)) > 0) { - if (streq(k, "PRETTY_NAME")) - pretty = v; - - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (pretty) - printf("%s%s\n", prefix, pretty); - - return 0; -} - -typedef struct MachineStatusInfo { - char *name; - sd_id128_t id; - char *class; - char *service; - char *unit; - char *root_directory; - pid_t leader; - struct dual_timestamp timestamp; - int *netif; - unsigned n_netif; -} MachineStatusInfo; - -static void machine_status_info_clear(MachineStatusInfo *info) { - if (info) { - free(info->name); - free(info->class); - free(info->service); - free(info->unit); - free(info->root_directory); - free(info->netif); - zero(*info); - } -} - -static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) { - char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; - char since2[FORMAT_TIMESTAMP_MAX], *s2; - int ifi = -1; - - assert(bus); - assert(i); - - fputs(strna(i->name), stdout); - - if (!sd_id128_equal(i->id, SD_ID128_NULL)) - printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id)); - else - putchar('\n'); - - s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp.realtime); - s2 = format_timestamp(since2, sizeof(since2), i->timestamp.realtime); - - if (s1) - printf("\t Since: %s; %s\n", s2, s1); - else if (s2) - printf("\t Since: %s\n", s2); - - if (i->leader > 0) { - _cleanup_free_ char *t = NULL; - - printf("\t Leader: %u", (unsigned) i->leader); - - get_process_comm(i->leader, &t); - if (t) - printf(" (%s)", t); - - putchar('\n'); - } - - if (i->service) { - printf("\t Service: %s", i->service); - - if (i->class) - printf("; class %s", i->class); - - putchar('\n'); - } else if (i->class) - printf("\t Class: %s\n", i->class); - - if (i->root_directory) - printf("\t Root: %s\n", i->root_directory); - - if (i->n_netif > 0) { - unsigned c; - - fputs("\t Iface:", stdout); - - for (c = 0; c < i->n_netif; c++) { - char name[IF_NAMESIZE+1] = ""; - - if (if_indextoname(i->netif[c], name)) { - fputc(' ', stdout); - fputs(name, stdout); - - if (ifi < 0) - ifi = i->netif[c]; - else - ifi = 0; - } else - printf(" %i", i->netif[c]); - } - - fputc('\n', stdout); - } - - print_addresses(bus, i->name, ifi, - "\t Address: ", - "\t "); - - print_os_release(bus, i->name, "\t OS: "); - - if (i->unit) { - printf("\t Unit: %s\n", i->unit); - show_unit_cgroup(bus, i->unit, i->leader); - - if (arg_transport == BUS_TRANSPORT_LOCAL) - - show_journal_by_unit( - stdout, - i->unit, - arg_output, - 0, - i->timestamp.monotonic, - arg_lines, - 0, - get_output_flags() | OUTPUT_BEGIN_NEWLINE, - SD_JOURNAL_LOCAL_ONLY, - true, - NULL); - } -} - -static int map_netif(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { - MachineStatusInfo *i = userdata; - size_t l; - const void *v; - int r; - - assert_cc(sizeof(int32_t) == sizeof(int)); - r = sd_bus_message_read_array(m, SD_BUS_TYPE_INT32, &v, &l); - if (r < 0) - return r; - if (r == 0) - return -EBADMSG; - - i->n_netif = l / sizeof(int32_t); - i->netif = memdup(v, l); - if (!i->netif) - return -ENOMEM; - - return 0; -} - -static int show_machine_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) { - - static const struct bus_properties_map map[] = { - { "Name", "s", NULL, offsetof(MachineStatusInfo, name) }, - { "Class", "s", NULL, offsetof(MachineStatusInfo, class) }, - { "Service", "s", NULL, offsetof(MachineStatusInfo, service) }, - { "Unit", "s", NULL, offsetof(MachineStatusInfo, unit) }, - { "RootDirectory", "s", NULL, offsetof(MachineStatusInfo, root_directory) }, - { "Leader", "u", NULL, offsetof(MachineStatusInfo, leader) }, - { "Timestamp", "t", NULL, offsetof(MachineStatusInfo, timestamp.realtime) }, - { "TimestampMonotonic", "t", NULL, offsetof(MachineStatusInfo, timestamp.monotonic) }, - { "Id", "ay", bus_map_id128, offsetof(MachineStatusInfo, id) }, - { "NetworkInterfaces", "ai", map_netif, 0 }, - {} - }; - - _cleanup_(machine_status_info_clear) MachineStatusInfo info = {}; - int r; - - assert(verb); - assert(bus); - assert(path); - assert(new_line); - - r = bus_map_all_properties(bus, - "org.freedesktop.machine1", - path, - map, - &info); - if (r < 0) - return log_error_errno(r, "Could not get properties: %m"); - - if (*new_line) - printf("\n"); - *new_line = true; - - print_machine_status_info(bus, &info); - - return r; -} - -static int show_machine_properties(sd_bus *bus, const char *path, bool *new_line) { - int r; - - assert(bus); - assert(path); - assert(new_line); - - if (*new_line) - printf("\n"); - - *new_line = true; - - r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_value, arg_all); - if (r < 0) - log_error_errno(r, "Could not get properties: %m"); - - return r; -} - -static int show_machine(int argc, char *argv[], void *userdata) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - bool properties, new_line = false; - sd_bus *bus = userdata; - int r = 0, i; - - assert(bus); - - properties = !strstr(argv[0], "status"); - - pager_open(arg_no_pager, false); - - if (properties && argc <= 1) { - - /* If no argument is specified, inspect the manager - * itself */ - r = show_machine_properties(bus, "/org/freedesktop/machine1", &new_line); - if (r < 0) - return r; - } - - for (i = 1; i < argc; i++) { - const char *path = NULL; - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetMachine", - &error, - &reply, - "s", argv[i]); - if (r < 0) { - log_error("Could not get path to machine: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); - - if (properties) - r = show_machine_properties(bus, path, &new_line); - else - r = show_machine_info(argv[0], bus, path, &new_line); - } - - return r; -} - -typedef struct ImageStatusInfo { - char *name; - char *path; - char *type; - int read_only; - usec_t crtime; - usec_t mtime; - uint64_t usage; - uint64_t limit; - uint64_t usage_exclusive; - uint64_t limit_exclusive; -} ImageStatusInfo; - -static void image_status_info_clear(ImageStatusInfo *info) { - if (info) { - free(info->name); - free(info->path); - free(info->type); - zero(*info); - } -} - -static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) { - char ts_relative[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; - char ts_absolute[FORMAT_TIMESTAMP_MAX], *s2; - char bs[FORMAT_BYTES_MAX], *s3; - char bs_exclusive[FORMAT_BYTES_MAX], *s4; - - assert(bus); - assert(i); - - if (i->name) { - fputs(i->name, stdout); - putchar('\n'); - } - - if (i->type) - printf("\t Type: %s\n", i->type); - - if (i->path) - printf("\t Path: %s\n", i->path); - - printf("\t RO: %s%s%s\n", - i->read_only ? ansi_highlight_red() : "", - i->read_only ? "read-only" : "writable", - i->read_only ? ansi_normal() : ""); - - s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->crtime); - s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->crtime); - if (s1 && s2) - printf("\t Created: %s; %s\n", s2, s1); - else if (s2) - printf("\t Created: %s\n", s2); - - s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->mtime); - s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->mtime); - if (s1 && s2) - printf("\tModified: %s; %s\n", s2, s1); - else if (s2) - printf("\tModified: %s\n", s2); - - s3 = format_bytes(bs, sizeof(bs), i->usage); - s4 = i->usage_exclusive != i->usage ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->usage_exclusive) : NULL; - if (s3 && s4) - printf("\t Usage: %s (exclusive: %s)\n", s3, s4); - else if (s3) - printf("\t Usage: %s\n", s3); - - s3 = format_bytes(bs, sizeof(bs), i->limit); - s4 = i->limit_exclusive != i->limit ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->limit_exclusive) : NULL; - if (s3 && s4) - printf("\t Limit: %s (exclusive: %s)\n", s3, s4); - else if (s3) - printf("\t Limit: %s\n", s3); -} - -static int show_image_info(sd_bus *bus, const char *path, bool *new_line) { - - static const struct bus_properties_map map[] = { - { "Name", "s", NULL, offsetof(ImageStatusInfo, name) }, - { "Path", "s", NULL, offsetof(ImageStatusInfo, path) }, - { "Type", "s", NULL, offsetof(ImageStatusInfo, type) }, - { "ReadOnly", "b", NULL, offsetof(ImageStatusInfo, read_only) }, - { "CreationTimestamp", "t", NULL, offsetof(ImageStatusInfo, crtime) }, - { "ModificationTimestamp", "t", NULL, offsetof(ImageStatusInfo, mtime) }, - { "Usage", "t", NULL, offsetof(ImageStatusInfo, usage) }, - { "Limit", "t", NULL, offsetof(ImageStatusInfo, limit) }, - { "UsageExclusive", "t", NULL, offsetof(ImageStatusInfo, usage_exclusive) }, - { "LimitExclusive", "t", NULL, offsetof(ImageStatusInfo, limit_exclusive) }, - {} - }; - - _cleanup_(image_status_info_clear) ImageStatusInfo info = {}; - int r; - - assert(bus); - assert(path); - assert(new_line); - - r = bus_map_all_properties(bus, - "org.freedesktop.machine1", - path, - map, - &info); - if (r < 0) - return log_error_errno(r, "Could not get properties: %m"); - - if (*new_line) - printf("\n"); - *new_line = true; - - print_image_status_info(bus, &info); - - return r; -} - -typedef struct PoolStatusInfo { - char *path; - uint64_t usage; - uint64_t limit; -} PoolStatusInfo; - -static void pool_status_info_clear(PoolStatusInfo *info) { - if (info) { - free(info->path); - zero(*info); - info->usage = -1; - info->limit = -1; - } -} - -static void print_pool_status_info(sd_bus *bus, PoolStatusInfo *i) { - char bs[FORMAT_BYTES_MAX], *s; - - if (i->path) - printf("\t Path: %s\n", i->path); - - s = format_bytes(bs, sizeof(bs), i->usage); - if (s) - printf("\t Usage: %s\n", s); - - s = format_bytes(bs, sizeof(bs), i->limit); - if (s) - printf("\t Limit: %s\n", s); -} - -static int show_pool_info(sd_bus *bus) { - - static const struct bus_properties_map map[] = { - { "PoolPath", "s", NULL, offsetof(PoolStatusInfo, path) }, - { "PoolUsage", "t", NULL, offsetof(PoolStatusInfo, usage) }, - { "PoolLimit", "t", NULL, offsetof(PoolStatusInfo, limit) }, - {} - }; - - _cleanup_(pool_status_info_clear) PoolStatusInfo info = { - .usage = (uint64_t) -1, - .limit = (uint64_t) -1, - }; - int r; - - assert(bus); - - r = bus_map_all_properties(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - map, - &info); - if (r < 0) - return log_error_errno(r, "Could not get properties: %m"); - - print_pool_status_info(bus, &info); - - return 0; -} - - -static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) { - int r; - - assert(bus); - assert(path); - assert(new_line); - - if (*new_line) - printf("\n"); - - *new_line = true; - - r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_value, arg_all); - if (r < 0) - log_error_errno(r, "Could not get properties: %m"); - - return r; -} - -static int show_image(int argc, char *argv[], void *userdata) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - bool properties, new_line = false; - sd_bus *bus = userdata; - int r = 0, i; - - assert(bus); - - properties = !strstr(argv[0], "status"); - - pager_open(arg_no_pager, false); - - if (argc <= 1) { - - /* If no argument is specified, inspect the manager - * itself */ - - if (properties) - r = show_image_properties(bus, "/org/freedesktop/machine1", &new_line); - else - r = show_pool_info(bus); - if (r < 0) - return r; - } - - for (i = 1; i < argc; i++) { - const char *path = NULL; - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetImage", - &error, - &reply, - "s", argv[i]); - if (r < 0) { - log_error("Could not get path to image: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); - - if (properties) - r = show_image_properties(bus, path, &new_line); - else - r = show_image_info(bus, path, &new_line); - } - - return r; -} - -static int kill_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int r, i; - - assert(bus); - - polkit_agent_open_if_enabled(); - - if (!arg_kill_who) - arg_kill_who = "all"; - - for (i = 1; i < argc; i++) { - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "KillMachine", - &error, - NULL, - "ssi", argv[i], arg_kill_who, arg_signal); - if (r < 0) { - log_error("Could not kill machine: %s", bus_error_message(&error, -r)); - return r; - } - } - - return 0; -} - -static int reboot_machine(int argc, char *argv[], void *userdata) { - arg_kill_who = "leader"; - arg_signal = SIGINT; /* sysvinit + systemd */ - - return kill_machine(argc, argv, userdata); -} - -static int poweroff_machine(int argc, char *argv[], void *userdata) { - arg_kill_who = "leader"; - arg_signal = SIGRTMIN+4; /* only systemd */ - - return kill_machine(argc, argv, userdata); -} - -static int terminate_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int r, i; - - assert(bus); - - polkit_agent_open_if_enabled(); - - for (i = 1; i < argc; i++) { - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "TerminateMachine", - &error, - NULL, - "s", argv[i]); - if (r < 0) { - log_error("Could not terminate machine: %s", bus_error_message(&error, -r)); - return r; - } - } - - return 0; -} - -static int copy_files(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *abs_host_path = NULL; - char *dest, *host_path, *container_path; - sd_bus *bus = userdata; - bool copy_from; - int r; - - assert(bus); - - polkit_agent_open_if_enabled(); - - copy_from = streq(argv[0], "copy-from"); - dest = argv[3] ?: argv[2]; - host_path = copy_from ? dest : argv[2]; - container_path = copy_from ? argv[2] : dest; - - if (!path_is_absolute(host_path)) { - r = path_make_absolute_cwd(host_path, &abs_host_path); - if (r < 0) - return log_error_errno(r, "Failed to make path absolute: %m"); - - host_path = abs_host_path; - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - copy_from ? "CopyFromMachine" : "CopyToMachine"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sss", - argv[1], - copy_from ? container_path : host_path, - copy_from ? host_path : container_path); - if (r < 0) - return bus_log_create_error(r); - - /* This is a slow operation, hence turn off any method call timeouts */ - r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL); - if (r < 0) - return log_error_errno(r, "Failed to copy: %s", bus_error_message(&error, r)); - - return 0; -} - -static int bind_mount(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int r; - - assert(bus); - - polkit_agent_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "BindMountMachine", - &error, - NULL, - "sssbb", - argv[1], - argv[2], - argv[3], - arg_read_only, - arg_mkdir); - if (r < 0) { - log_error("Failed to bind mount: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int on_machine_removed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - PTYForward ** forward = (PTYForward**) userdata; - int r; - - assert(m); - assert(forward); - - if (*forward) { - /* If the forwarder is already initialized, tell it to - * exit on the next vhangup(), so that we still flush - * out what might be queued and exit then. */ - - r = pty_forward_set_ignore_vhangup(*forward, false); - if (r >= 0) - return 0; - - log_error_errno(r, "Failed to set ignore_vhangup flag: %m"); - } - - /* On error, or when the forwarder is not initialized yet, quit immediately */ - sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), EXIT_FAILURE); - return 0; -} - -static int process_forward(sd_event *event, PTYForward **forward, int master, PTYForwardFlags flags, const char *name) { - char last_char = 0; - bool machine_died; - int ret = 0, r; - - assert(event); - assert(master >= 0); - assert(name); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); - - if (streq(name, ".host")) - log_info("Connected to the local host. Press ^] three times within 1s to exit session."); - else - log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name); - - sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); - sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); - - r = pty_forward_new(event, master, flags, forward); - if (r < 0) - return log_error_errno(r, "Failed to create PTY forwarder: %m"); - - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); - - pty_forward_get_last_char(*forward, &last_char); - - machine_died = - (flags & PTY_FORWARD_IGNORE_VHANGUP) && - pty_forward_get_ignore_vhangup(*forward) == 0; - - *forward = pty_forward_free(*forward); - - if (last_char != '\n') - fputc('\n', stdout); - - if (machine_died) - log_info("Machine %s terminated.", name); - else if (streq(name, ".host")) - log_info("Connection to the local host terminated."); - else - log_info("Connection to machine %s terminated.", name); - - sd_event_get_exit_code(event, &ret); - return ret; -} - -static int login_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(pty_forward_freep) PTYForward *forward = NULL; - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - int master = -1, r; - sd_bus *bus = userdata; - const char *pty, *match, *machine; - - assert(bus); - - if (!strv_isempty(arg_setenv) || arg_uid) { - log_error("--setenv= and --uid= are not supported for 'login'. Use 'shell' instead."); - return -EINVAL; - } - - if (arg_transport != BUS_TRANSPORT_LOCAL && - arg_transport != BUS_TRANSPORT_MACHINE) { - log_error("Login only supported on local machines."); - return -EOPNOTSUPP; - } - - polkit_agent_open_if_enabled(); - - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); - - r = sd_bus_attach_event(bus, event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - - machine = argc < 2 || isempty(argv[1]) ? ".host" : argv[1]; - - match = strjoina("type='signal'," - "sender='org.freedesktop.machine1'," - "path='/org/freedesktop/machine1',", - "interface='org.freedesktop.machine1.Manager'," - "member='MachineRemoved'," - "arg0='", machine, "'"); - - r = sd_bus_add_match(bus, &slot, match, on_machine_removed, &forward); - if (r < 0) - return log_error_errno(r, "Failed to add machine removal match: %m"); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "OpenMachineLogin", - &error, - &reply, - "s", machine); - if (r < 0) { - log_error("Failed to get login PTY: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "hs", &master, &pty); - if (r < 0) - return bus_log_parse_error(r); - - return process_forward(event, &forward, master, PTY_FORWARD_IGNORE_VHANGUP, machine); -} - -static int shell_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(pty_forward_freep) PTYForward *forward = NULL; - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - int master = -1, r; - sd_bus *bus = userdata; - const char *pty, *match, *machine, *path, *uid = NULL; - - assert(bus); - - if (arg_transport != BUS_TRANSPORT_LOCAL && - arg_transport != BUS_TRANSPORT_MACHINE) { - log_error("Shell only supported on local machines."); - return -EOPNOTSUPP; - } - - /* Pass $TERM to shell session, if not explicitly specified. */ - if (!strv_find_prefix(arg_setenv, "TERM=")) { - const char *t; - - t = strv_find_prefix(environ, "TERM="); - if (t) { - if (strv_extend(&arg_setenv, t) < 0) - return log_oom(); - } - } - - polkit_agent_open_if_enabled(); - - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); - - r = sd_bus_attach_event(bus, event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - - machine = argc < 2 || isempty(argv[1]) ? NULL : argv[1]; - - if (arg_uid) - uid = arg_uid; - else if (machine) { - const char *at; - - at = strchr(machine, '@'); - if (at) { - uid = strndupa(machine, at - machine); - machine = at + 1; - } - } - - if (isempty(machine)) - machine = ".host"; - - match = strjoina("type='signal'," - "sender='org.freedesktop.machine1'," - "path='/org/freedesktop/machine1',", - "interface='org.freedesktop.machine1.Manager'," - "member='MachineRemoved'," - "arg0='", machine, "'"); - - r = sd_bus_add_match(bus, &slot, match, on_machine_removed, &forward); - if (r < 0) - return log_error_errno(r, "Failed to add machine removal match: %m"); - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "OpenMachineShell"); - if (r < 0) - return bus_log_create_error(r); - - path = argc < 3 || isempty(argv[2]) ? NULL : argv[2]; - - r = sd_bus_message_append(m, "sss", machine, uid, path); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_strv(m, strv_length(argv) <= 3 ? NULL : argv + 2); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_strv(m, arg_setenv); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed to get shell PTY: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "hs", &master, &pty); - if (r < 0) - return bus_log_parse_error(r); - - return process_forward(event, &forward, master, 0, machine); -} - -static int remove_image(int argc, char *argv[], void *userdata) { - sd_bus *bus = userdata; - int r, i; - - assert(bus); - - polkit_agent_open_if_enabled(); - - for (i = 1; i < argc; i++) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "RemoveImage"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", argv[i]); - if (r < 0) - return bus_log_create_error(r); - - /* This is a slow operation, hence turn off any method call timeouts */ - r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL); - if (r < 0) - return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r)); - } - - return 0; -} - -static int rename_image(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int r; - - polkit_agent_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "RenameImage", - &error, - NULL, - "ss", argv[1], argv[2]); - if (r < 0) { - log_error("Could not rename image: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int clone_image(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - sd_bus *bus = userdata; - int r; - - polkit_agent_open_if_enabled(); - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "CloneImage"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "ssb", argv[1], argv[2], arg_read_only); - if (r < 0) - return bus_log_create_error(r); - - /* This is a slow operation, hence turn off any method call timeouts */ - r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL); - if (r < 0) - return log_error_errno(r, "Could not clone image: %s", bus_error_message(&error, r)); - - return 0; -} - -static int read_only_image(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int b = true, r; - - if (argc > 2) { - b = parse_boolean(argv[2]); - if (b < 0) { - log_error("Failed to parse boolean argument: %s", argv[2]); - return -EINVAL; - } - } - - polkit_agent_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "MarkImageReadOnly", - &error, - NULL, - "sb", argv[1], b); - if (r < 0) { - log_error("Could not mark image read-only: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int make_service_name(const char *name, char **ret) { - _cleanup_free_ char *e = NULL; - int r; - - assert(name); - assert(ret); - - if (!machine_name_is_valid(name)) { - log_error("Invalid machine name %s.", name); - return -EINVAL; - } - - e = unit_name_escape(name); - if (!e) - return log_oom(); - - r = unit_name_build("systemd-nspawn", e, ".service", ret); - if (r < 0) - return log_error_errno(r, "Failed to build unit name: %m"); - - return 0; -} - -static int start_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; - sd_bus *bus = userdata; - int r, i; - - assert(bus); - - polkit_agent_open_if_enabled(); - - r = bus_wait_for_jobs_new(bus, &w); - if (r < 0) - return log_oom(); - - for (i = 1; i < argc; i++) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *unit = NULL; - const char *object; - - r = make_service_name(argv[i], &unit); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartUnit", - &error, - &reply, - "ss", unit, "fail"); - if (r < 0) { - log_error("Failed to start unit: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "o", &object); - if (r < 0) - return bus_log_parse_error(r); - - r = bus_wait_for_jobs_add(w, object); - if (r < 0) - return log_oom(); - } - - r = bus_wait_for_jobs(w, arg_quiet, NULL); - if (r < 0) - return r; - - return 0; -} - -static int enable_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - int carries_install_info = 0; - const char *method = NULL; - sd_bus *bus = userdata; - int r, i; - - assert(bus); - - polkit_agent_open_if_enabled(); - - method = streq(argv[0], "enable") ? "EnableUnitFiles" : "DisableUnitFiles"; - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - method); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'a', "s"); - if (r < 0) - return bus_log_create_error(r); - - for (i = 1; i < argc; i++) { - _cleanup_free_ char *unit = NULL; - - r = make_service_name(argv[i], &unit); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "s", unit); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - if (streq(argv[0], "enable")) - r = sd_bus_message_append(m, "bb", false, false); - else - r = sd_bus_message_append(m, "b", false); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed to enable or disable unit: %s", bus_error_message(&error, -r)); - return r; - } - - if (streq(argv[0], "enable")) { - r = sd_bus_message_read(reply, "b", carries_install_info); - if (r < 0) - return bus_log_parse_error(r); - } - - r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes); - if (r < 0) - goto finish; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "Reload", - &error, - NULL, - NULL); - if (r < 0) { - log_error("Failed to reload daemon: %s", bus_error_message(&error, -r)); - goto finish; - } - - r = 0; - -finish: - unit_file_changes_free(changes, n_changes); - - return r; -} - -static int match_log_message(sd_bus_message *m, void *userdata, sd_bus_error *error) { - const char **our_path = userdata, *line; - unsigned priority; - int r; - - assert(m); - assert(our_path); - - r = sd_bus_message_read(m, "us", &priority, &line); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - if (!streq_ptr(*our_path, sd_bus_message_get_path(m))) - return 0; - - if (arg_quiet && LOG_PRI(priority) >= LOG_INFO) - return 0; - - log_full(priority, "%s", line); - return 0; -} - -static int match_transfer_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { - const char **our_path = userdata, *path, *result; - uint32_t id; - int r; - - assert(m); - assert(our_path); - - r = sd_bus_message_read(m, "uos", &id, &path, &result); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - if (!streq_ptr(*our_path, path)) - return 0; - - sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), !streq_ptr(result, "done")); - return 0; -} - -static int transfer_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - assert(s); - assert(si); - - if (!arg_quiet) - log_info("Continuing download in the background. Use \"machinectl cancel-transfer %" PRIu32 "\" to abort transfer.", PTR_TO_UINT32(userdata)); - - sd_event_exit(sd_event_source_get_event(s), EINTR); - return 0; -} - -static int transfer_image_common(sd_bus *bus, sd_bus_message *m) { - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot_job_removed = NULL, *slot_log_message = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_event_unrefp) sd_event* event = NULL; - const char *path = NULL; - uint32_t id; - int r; - - assert(bus); - assert(m); - - polkit_agent_open_if_enabled(); - - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); - - r = sd_bus_attach_event(bus, event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - - r = sd_bus_add_match( - bus, - &slot_job_removed, - "type='signal'," - "sender='org.freedesktop.import1'," - "interface='org.freedesktop.import1.Manager'," - "member='TransferRemoved'," - "path='/org/freedesktop/import1'", - match_transfer_removed, &path); - if (r < 0) - return log_error_errno(r, "Failed to install match: %m"); - - r = sd_bus_add_match( - bus, - &slot_log_message, - "type='signal'," - "sender='org.freedesktop.import1'," - "interface='org.freedesktop.import1.Transfer'," - "member='LogMessage'", - match_log_message, &path); - if (r < 0) - return log_error_errno(r, "Failed to install match: %m"); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed to transfer image: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "uo", &id, &path); - if (r < 0) - return bus_log_parse_error(r); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - - if (!arg_quiet) - log_info("Enqueued transfer job %u. Press C-c to continue download in background.", id); - - sd_event_add_signal(event, NULL, SIGINT, transfer_signal_handler, UINT32_TO_PTR(id)); - sd_event_add_signal(event, NULL, SIGTERM, transfer_signal_handler, UINT32_TO_PTR(id)); - - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); - - return -r; -} - -static int import_tar(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *ll = NULL; - _cleanup_close_ int fd = -1; - const char *local = NULL, *path = NULL; - sd_bus *bus = userdata; - int r; - - assert(bus); - - if (argc >= 2) - path = argv[1]; - if (isempty(path) || streq(path, "-")) - path = NULL; - - if (argc >= 3) - local = argv[2]; - else if (path) - local = basename(path); - if (isempty(local) || streq(local, "-")) - local = NULL; - - if (!local) { - log_error("Need either path or local name."); - return -EINVAL; - } - - r = tar_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!machine_name_is_valid(local)) { - log_error("Local name %s is not a suitable machine name.", local); - return -EINVAL; - } - - if (path) { - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "ImportTar"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "hsbb", - fd >= 0 ? fd : STDIN_FILENO, - local, - arg_force, - arg_read_only); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int import_raw(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *ll = NULL; - _cleanup_close_ int fd = -1; - const char *local = NULL, *path = NULL; - sd_bus *bus = userdata; - int r; - - assert(bus); - - if (argc >= 2) - path = argv[1]; - if (isempty(path) || streq(path, "-")) - path = NULL; - - if (argc >= 3) - local = argv[2]; - else if (path) - local = basename(path); - if (isempty(local) || streq(local, "-")) - local = NULL; - - if (!local) { - log_error("Need either path or local name."); - return -EINVAL; - } - - r = raw_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!machine_name_is_valid(local)) { - log_error("Local name %s is not a suitable machine name.", local); - return -EINVAL; - } - - if (path) { - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "ImportRaw"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "hsbb", - fd >= 0 ? fd : STDIN_FILENO, - local, - arg_force, - arg_read_only); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static void determine_compression_from_filename(const char *p) { - if (arg_format) - return; - - if (!p) - return; - - if (endswith(p, ".xz")) - arg_format = "xz"; - else if (endswith(p, ".gz")) - arg_format = "gzip"; - else if (endswith(p, ".bz2")) - arg_format = "bzip2"; -} - -static int export_tar(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_close_ int fd = -1; - const char *local = NULL, *path = NULL; - sd_bus *bus = userdata; - int r; - - assert(bus); - - local = argv[1]; - if (!machine_name_is_valid(local)) { - log_error("Machine name %s is not valid.", local); - return -EINVAL; - } - - if (argc >= 3) - path = argv[2]; - if (isempty(path) || streq(path, "-")) - path = NULL; - - if (path) { - determine_compression_from_filename(path); - - fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "ExportTar"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "shs", - local, - fd >= 0 ? fd : STDOUT_FILENO, - arg_format); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int export_raw(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_close_ int fd = -1; - const char *local = NULL, *path = NULL; - sd_bus *bus = userdata; - int r; - - assert(bus); - - local = argv[1]; - if (!machine_name_is_valid(local)) { - log_error("Machine name %s is not valid.", local); - return -EINVAL; - } - - if (argc >= 3) - path = argv[2]; - if (isempty(path) || streq(path, "-")) - path = NULL; - - if (path) { - determine_compression_from_filename(path); - - fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "ExportRaw"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "shs", - local, - fd >= 0 ? fd : STDOUT_FILENO, - arg_format); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int pull_tar(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *l = NULL, *ll = NULL; - const char *local, *remote; - sd_bus *bus = userdata; - int r; - - assert(bus); - - remote = argv[1]; - if (!http_url_is_valid(remote)) { - log_error("URL '%s' is not valid.", remote); - return -EINVAL; - } - - if (argc >= 3) - local = argv[2]; - else { - r = import_url_last_component(remote, &l); - if (r < 0) - return log_error_errno(r, "Failed to get final component of URL: %m"); - - local = l; - } - - if (isempty(local) || streq(local, "-")) - local = NULL; - - if (local) { - r = tar_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!machine_name_is_valid(local)) { - log_error("Local name %s is not a suitable machine name.", local); - return -EINVAL; - } - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "PullTar"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sssb", - remote, - local, - import_verify_to_string(arg_verify), - arg_force); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int pull_raw(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *l = NULL, *ll = NULL; - const char *local, *remote; - sd_bus *bus = userdata; - int r; - - assert(bus); - - remote = argv[1]; - if (!http_url_is_valid(remote)) { - log_error("URL '%s' is not valid.", remote); - return -EINVAL; - } - - if (argc >= 3) - local = argv[2]; - else { - r = import_url_last_component(remote, &l); - if (r < 0) - return log_error_errno(r, "Failed to get final component of URL: %m"); - - local = l; - } - - if (isempty(local) || streq(local, "-")) - local = NULL; - - if (local) { - r = raw_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!machine_name_is_valid(local)) { - log_error("Local name %s is not a suitable machine name.", local); - return -EINVAL; - } - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "PullRaw"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sssb", - remote, - local, - import_verify_to_string(arg_verify), - arg_force); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -typedef struct TransferInfo { - uint32_t id; - const char *type; - const char *remote; - const char *local; - double progress; -} TransferInfo; - -static int compare_transfer_info(const void *a, const void *b) { - const TransferInfo *x = a, *y = b; - - return strcmp(x->local, y->local); -} - -static int list_transfers(int argc, char *argv[], void *userdata) { - size_t max_type = strlen("TYPE"), max_local = strlen("LOCAL"), max_remote = strlen("REMOTE"); - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ TransferInfo *transfers = NULL; - size_t n_transfers = 0, n_allocated = 0, j; - const char *type, *remote, *local, *object; - sd_bus *bus = userdata; - uint32_t id, max_id = 0; - double progress; - int r; - - pager_open(arg_no_pager, false); - - r = sd_bus_call_method(bus, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "ListTransfers", - &error, - &reply, - NULL); - if (r < 0) { - log_error("Could not get transfers: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_enter_container(reply, 'a', "(usssdo)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "(usssdo)", &id, &type, &remote, &local, &progress, &object)) > 0) { - size_t l; - - if (!GREEDY_REALLOC(transfers, n_allocated, n_transfers + 1)) - return log_oom(); - - transfers[n_transfers].id = id; - transfers[n_transfers].type = type; - transfers[n_transfers].remote = remote; - transfers[n_transfers].local = local; - transfers[n_transfers].progress = progress; - - l = strlen(type); - if (l > max_type) - max_type = l; - - l = strlen(remote); - if (l > max_remote) - max_remote = l; - - l = strlen(local); - if (l > max_local) - max_local = l; - - if (id > max_id) - max_id = id; - - n_transfers++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - qsort_safe(transfers, n_transfers, sizeof(TransferInfo), compare_transfer_info); - - if (arg_legend) - printf("%-*s %-*s %-*s %-*s %-*s\n", - (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), "ID", - (int) 7, "PERCENT", - (int) max_type, "TYPE", - (int) max_local, "LOCAL", - (int) max_remote, "REMOTE"); - - for (j = 0; j < n_transfers; j++) - printf("%*" PRIu32 " %*u%% %-*s %-*s %-*s\n", - (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), transfers[j].id, - (int) 6, (unsigned) (transfers[j].progress * 100), - (int) max_type, transfers[j].type, - (int) max_local, transfers[j].local, - (int) max_remote, transfers[j].remote); - - if (arg_legend) - printf("\n%zu transfers listed.\n", n_transfers); - - return 0; -} - -static int cancel_transfer(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int r, i; - - assert(bus); - - polkit_agent_open_if_enabled(); - - for (i = 1; i < argc; i++) { - uint32_t id; - - r = safe_atou32(argv[i], &id); - if (r < 0) - return log_error_errno(r, "Failed to parse transfer id: %s", argv[i]); - - r = sd_bus_call_method( - bus, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "CancelTransfer", - &error, - NULL, - "u", id); - if (r < 0) { - log_error("Could not cancel transfer: %s", bus_error_message(&error, -r)); - return r; - } - } - - return 0; -} - -static int set_limit(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - uint64_t limit; - int r; - - if (STR_IN_SET(argv[argc-1], "-", "none", "infinity")) - limit = (uint64_t) -1; - else { - r = parse_size(argv[argc-1], 1024, &limit); - if (r < 0) - return log_error("Failed to parse size: %s", argv[argc-1]); - } - - if (argc > 2) - /* With two arguments changes the quota limit of the - * specified image */ - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "SetImageLimit", - &error, - NULL, - "st", argv[1], limit); - else - /* With one argument changes the pool quota limit */ - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "SetPoolLimit", - &error, - NULL, - "t", limit); - - if (r < 0) { - log_error("Could not set limit: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int clean_images(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - uint64_t usage, total = 0; - char fb[FORMAT_BYTES_MAX]; - sd_bus *bus = userdata; - const char *name; - unsigned c = 0; - int r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "CleanPool", - &error, - &reply, - "s", arg_all ? "all" : "hidden"); - if (r < 0) - return log_error_errno(r, "Could not clean pool: %s", bus_error_message(&error, r)); - - r = sd_bus_message_enter_container(reply, 'a', "(st)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "(st)", &name, &usage)) > 0) { - log_info("Removed image '%s'. Freed exclusive disk space: %s", - name, format_bytes(fb, sizeof(fb), usage)); - - total += usage; - c++; - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - log_info("Removed %u images in total. Total freed exclusive disk space %s.", - c, format_bytes(fb, sizeof(fb), total)); - - return 0; -} - -static int help(int argc, char *argv[], void *userdata) { - - printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Send control commands to or query the virtual machine and container\n" - "registration manager.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --no-ask-password Do not ask for system passwords\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " -p --property=NAME Show only properties by this name\n" - " -q --quiet Suppress output\n" - " -a --all Show all properties, including empty ones\n" - " --value When showing properties, only print the value\n" - " -l --full Do not ellipsize output\n" - " --kill-who=WHO Who to send signal to\n" - " -s --signal=SIGNAL Which signal to send\n" - " --uid=USER Specify user ID to invoke shell as\n" - " -E --setenv=VAR=VALUE Add an environment variable for shell\n" - " --read-only Create read-only bind mount\n" - " --mkdir Create directory before bind mounting, if missing\n" - " -n --lines=INTEGER Number of journal entries to show\n" - " -o --output=STRING Change journal output mode (short,\n" - " short-monotonic, verbose, export, json,\n" - " json-pretty, json-sse, cat)\n" - " --verify=MODE Verification mode for downloaded images (no,\n" - " checksum, signature)\n" - " --force Download image even if already exists\n\n" - "Machine Commands:\n" - " list List running VMs and containers\n" - " status NAME... Show VM/container details\n" - " show [NAME...] Show properties of one or more VMs/containers\n" - " start NAME... Start container as a service\n" - " login [NAME] Get a login prompt in a container or on the\n" - " local host\n" - " shell [[USER@]NAME [COMMAND...]]\n" - " Invoke a shell (or other command) in a container\n" - " or on the local host\n" - " enable NAME... Enable automatic container start at boot\n" - " disable NAME... Disable automatic container start at boot\n" - " poweroff NAME... Power off one or more containers\n" - " reboot NAME... Reboot one or more containers\n" - " terminate NAME... Terminate one or more VMs/containers\n" - " kill NAME... Send signal to processes of a VM/container\n" - " copy-to NAME PATH [PATH] Copy files from the host to a container\n" - " copy-from NAME PATH [PATH] Copy files from a container to the host\n" - " bind NAME PATH [PATH] Bind mount a path from the host into a container\n\n" - "Image Commands:\n" - " list-images Show available container and VM images\n" - " image-status [NAME...] Show image details\n" - " show-image [NAME...] Show properties of image\n" - " clone NAME NAME Clone an image\n" - " rename NAME NAME Rename an image\n" - " read-only NAME [BOOL] Mark or unmark image read-only\n" - " remove NAME... Remove an image\n" - " set-limit [NAME] BYTES Set image or pool size limit (disk quota)\n" - " clean Remove hidden (or all) images\n\n" - "Image Transfer Commands:\n" - " pull-tar URL [NAME] Download a TAR container image\n" - " pull-raw URL [NAME] Download a RAW container or VM image\n" - " import-tar FILE [NAME] Import a local TAR container image\n" - " import-raw FILE [NAME] Import a local RAW container or VM image\n" - " export-tar NAME [FILE] Export a TAR container image locally\n" - " export-raw NAME [FILE] Export a RAW container or VM image locally\n" - " list-transfers Show list of downloads in progress\n" - " cancel-transfer Cancel a download\n" - , program_invocation_short_name); - - return 0; -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_VALUE, - ARG_KILL_WHO, - ARG_READ_ONLY, - ARG_MKDIR, - ARG_NO_ASK_PASSWORD, - ARG_VERIFY, - ARG_FORCE, - ARG_FORMAT, - ARG_UID, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "property", required_argument, NULL, 'p' }, - { "all", no_argument, NULL, 'a' }, - { "value", no_argument, NULL, ARG_VALUE }, - { "full", no_argument, NULL, 'l' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "kill-who", required_argument, NULL, ARG_KILL_WHO }, - { "signal", required_argument, NULL, 's' }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "mkdir", no_argument, NULL, ARG_MKDIR }, - { "quiet", no_argument, NULL, 'q' }, - { "lines", required_argument, NULL, 'n' }, - { "output", required_argument, NULL, 'o' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "verify", required_argument, NULL, ARG_VERIFY }, - { "force", no_argument, NULL, ARG_FORCE }, - { "format", required_argument, NULL, ARG_FORMAT }, - { "uid", required_argument, NULL, ARG_UID }, - { "setenv", required_argument, NULL, 'E' }, - {} - }; - - bool reorder = false; - int c, r; - - assert(argc >= 0); - assert(argv); - - for (;;) { - const char * const option_string = "+hp:als:H:M:qn:o:"; - - c = getopt_long(argc, argv, option_string + reorder, options, NULL); - if (c < 0) { - /* We generally are fine with the fact that getopt_long() reorders the command line, and looks - * for switches after the main verb. However, for "shell" we really don't want that, since we - * want that switches passed after that are passed to the program to execute, and not processed - * by us. To make this possible, we'll first invoke getopt_long() with reordering disabled - * (i.e. with the "+" prefix in the option string), and as soon as we hit the end (i.e. the - * verb) we check if that's "shell". If it is, we exit the loop, since we don't want any - * further options processed. However, if it is anything else, we process the same argument - * again, but this time allow reordering. */ - - if (!reorder && optind < argc && !streq(argv[optind], "shell")) { - reorder = true; - optind--; - continue; - } - - break; - } - - switch (c) { - - case 'h': - return help(0, NULL, NULL); - - case ARG_VERSION: - return version(); - - case 'p': - r = strv_extend(&arg_property, optarg); - if (r < 0) - return log_oom(); - - /* If the user asked for a particular - * property, show it to him, even if it is - * empty. */ - arg_all = true; - break; - - case 'a': - arg_all = true; - break; - - case ARG_VALUE: - arg_value = true; - break; - - case 'l': - arg_full = true; - break; - - case 'n': - if (safe_atou(optarg, &arg_lines) < 0) { - log_error("Failed to parse lines '%s'", optarg); - return -EINVAL; - } - break; - - case 'o': - arg_output = output_mode_from_string(optarg); - if (arg_output < 0) { - log_error("Unknown output '%s'.", optarg); - return -EINVAL; - } - break; - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case ARG_NO_LEGEND: - arg_legend = false; - break; - - case ARG_KILL_WHO: - arg_kill_who = optarg; - break; - - case 's': - arg_signal = signal_from_string_try_harder(optarg); - if (arg_signal < 0) { - log_error("Failed to parse signal string %s.", optarg); - return -EINVAL; - } - break; - - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; - - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; - break; - - case 'M': - arg_transport = BUS_TRANSPORT_MACHINE; - arg_host = optarg; - break; - - case ARG_READ_ONLY: - arg_read_only = true; - break; - - case ARG_MKDIR: - arg_mkdir = true; - break; - - case 'q': - arg_quiet = true; - break; - - case ARG_VERIFY: - arg_verify = import_verify_from_string(optarg); - if (arg_verify < 0) { - log_error("Failed to parse --verify= setting: %s", optarg); - return -EINVAL; - } - break; - - case ARG_FORCE: - arg_force = true; - break; - - case ARG_FORMAT: - if (!STR_IN_SET(optarg, "uncompressed", "xz", "gzip", "bzip2")) { - log_error("Unknown format: %s", optarg); - return -EINVAL; - } - - arg_format = optarg; - break; - - case ARG_UID: - arg_uid = optarg; - break; - - case 'E': - if (!env_assignment_is_valid(optarg)) { - log_error("Environment assignment invalid: %s", optarg); - return -EINVAL; - } - - r = strv_extend(&arg_setenv, optarg); - if (r < 0) - return log_oom(); - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - } - - return 1; -} - -static int machinectl_main(int argc, char *argv[], sd_bus *bus) { - - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "list", VERB_ANY, 1, VERB_DEFAULT, list_machines }, - { "list-images", VERB_ANY, 1, 0, list_images }, - { "status", 2, VERB_ANY, 0, show_machine }, - { "image-status", VERB_ANY, VERB_ANY, 0, show_image }, - { "show", VERB_ANY, VERB_ANY, 0, show_machine }, - { "show-image", VERB_ANY, VERB_ANY, 0, show_image }, - { "terminate", 2, VERB_ANY, 0, terminate_machine }, - { "reboot", 2, VERB_ANY, 0, reboot_machine }, - { "poweroff", 2, VERB_ANY, 0, poweroff_machine }, - { "kill", 2, VERB_ANY, 0, kill_machine }, - { "login", VERB_ANY, 2, 0, login_machine }, - { "shell", VERB_ANY, VERB_ANY, 0, shell_machine }, - { "bind", 3, 4, 0, bind_mount }, - { "copy-to", 3, 4, 0, copy_files }, - { "copy-from", 3, 4, 0, copy_files }, - { "remove", 2, VERB_ANY, 0, remove_image }, - { "rename", 3, 3, 0, rename_image }, - { "clone", 3, 3, 0, clone_image }, - { "read-only", 2, 3, 0, read_only_image }, - { "start", 2, VERB_ANY, 0, start_machine }, - { "enable", 2, VERB_ANY, 0, enable_machine }, - { "disable", 2, VERB_ANY, 0, enable_machine }, - { "import-tar", 2, 3, 0, import_tar }, - { "import-raw", 2, 3, 0, import_raw }, - { "export-tar", 2, 3, 0, export_tar }, - { "export-raw", 2, 3, 0, export_raw }, - { "pull-tar", 2, 3, 0, pull_tar }, - { "pull-raw", 2, 3, 0, pull_raw }, - { "list-transfers", VERB_ANY, 1, 0, list_transfers }, - { "cancel-transfer", 2, VERB_ANY, 0, cancel_transfer }, - { "set-limit", 2, 3, 0, set_limit }, - { "clean", VERB_ANY, 1, 0, clean_images }, - {} - }; - - return dispatch_verb(argc, argv, verbs, bus); -} - -int main(int argc, char*argv[]) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = bus_connect_transport(arg_transport, arg_host, false, &bus); - if (r < 0) { - log_error_errno(r, "Failed to create bus connection: %m"); - goto finish; - } - - sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); - - r = machinectl_main(argc, argv, bus); - -finish: - pager_close(); - polkit_agent_close(); - - strv_free(arg_property); - strv_free(arg_setenv); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c deleted file mode 100644 index 31efa3695b..0000000000 --- a/src/machine/machined-dbus.c +++ /dev/null @@ -1,1660 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include - -#include "sd-id128.h" - -#include "alloc-util.h" -#include "btrfs-util.h" -#include "bus-common-errors.h" -#include "bus-util.h" -#include "cgroup-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "hostname-util.h" -#include "image-dbus.h" -#include "io-util.h" -#include "machine-dbus.h" -#include "machine-image.h" -#include "machine-pool.h" -#include "machined.h" -#include "path-util.h" -#include "process-util.h" -#include "stdio-util.h" -#include "strv.h" -#include "unit-name.h" -#include "user-util.h" - -static int property_get_pool_path( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - assert(bus); - assert(reply); - - return sd_bus_message_append(reply, "s", "/var/lib/machines"); -} - -static int property_get_pool_usage( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - _cleanup_close_ int fd = -1; - uint64_t usage = (uint64_t) -1; - struct stat st; - - assert(bus); - assert(reply); - - /* We try to read the quota info from /var/lib/machines, as - * well as the usage of the loopback file - * /var/lib/machines.raw, and pick the larger value. */ - - fd = open("/var/lib/machines", O_RDONLY|O_CLOEXEC|O_DIRECTORY); - if (fd >= 0) { - BtrfsQuotaInfo q; - - if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0) - usage = q.referenced; - } - - if (stat("/var/lib/machines.raw", &st) >= 0) { - if (usage == (uint64_t) -1 || st.st_blocks * 512ULL > usage) - usage = st.st_blocks * 512ULL; - } - - return sd_bus_message_append(reply, "t", usage); -} - -static int property_get_pool_limit( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - _cleanup_close_ int fd = -1; - uint64_t size = (uint64_t) -1; - struct stat st; - - assert(bus); - assert(reply); - - /* We try to read the quota limit from /var/lib/machines, as - * well as the size of the loopback file - * /var/lib/machines.raw, and pick the smaller value. */ - - fd = open("/var/lib/machines", O_RDONLY|O_CLOEXEC|O_DIRECTORY); - if (fd >= 0) { - BtrfsQuotaInfo q; - - if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0) - size = q.referenced_max; - } - - if (stat("/var/lib/machines.raw", &st) >= 0) { - if (size == (uint64_t) -1 || (uint64_t) st.st_size < size) - size = st.st_size; - } - - return sd_bus_message_append(reply, "t", size); -} - -static int method_get_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *p = NULL; - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - p = machine_bus_path(machine); - if (!p) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", p); -} - -static int method_get_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *p = NULL; - Manager *m = userdata; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - r = image_find(name, NULL); - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); - if (r < 0) - return r; - - p = image_bus_path(name); - if (!p) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", p); -} - -static int method_get_machine_by_pid(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *p = NULL; - Manager *m = userdata; - Machine *machine = NULL; - pid_t pid; - int r; - - assert(message); - assert(m); - - assert_cc(sizeof(pid_t) == sizeof(uint32_t)); - - r = sd_bus_message_read(message, "u", &pid); - if (r < 0) - return r; - - if (pid < 0) - return -EINVAL; - - if (pid == 0) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_pid(creds, &pid); - if (r < 0) - return r; - } - - r = manager_get_machine_by_pid(m, pid, &machine); - if (r < 0) - return r; - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_MACHINE_FOR_PID, "PID "PID_FMT" does not belong to any known machine", pid); - - p = machine_bus_path(machine); - if (!p) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", p); -} - -static int method_list_machines(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - Manager *m = userdata; - Machine *machine; - Iterator i; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - r = sd_bus_message_open_container(reply, 'a', "(ssso)"); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - HASHMAP_FOREACH(machine, m->machines, i) { - _cleanup_free_ char *p = NULL; - - p = machine_bus_path(machine); - if (!p) - return -ENOMEM; - - r = sd_bus_message_append(reply, "(ssso)", - machine->name, - strempty(machine_class_to_string(machine->class)), - machine->service, - p); - if (r < 0) - return sd_bus_error_set_errno(error, r); - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - return sd_bus_send(NULL, reply, NULL); -} - -static int method_create_or_register_machine(Manager *manager, sd_bus_message *message, bool read_network, Machine **_m, sd_bus_error *error) { - const char *name, *service, *class, *root_directory; - const int32_t *netif = NULL; - MachineClass c; - uint32_t leader; - sd_id128_t id; - const void *v; - Machine *m; - size_t n, n_netif = 0; - int r; - - assert(manager); - assert(message); - assert(_m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - if (!machine_name_is_valid(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine name"); - - r = sd_bus_message_read_array(message, 'y', &v, &n); - if (r < 0) - return r; - if (n == 0) - id = SD_ID128_NULL; - else if (n == 16) - memcpy(&id, v, n); - else - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine ID parameter"); - - r = sd_bus_message_read(message, "ssus", &service, &class, &leader, &root_directory); - if (r < 0) - return r; - - if (read_network) { - size_t i; - - r = sd_bus_message_read_array(message, 'i', (const void**) &netif, &n_netif); - if (r < 0) - return r; - - n_netif /= sizeof(int32_t); - - for (i = 0; i < n_netif; i++) { - if (netif[i] <= 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid network interface index %i", netif[i]); - } - } - - if (isempty(class)) - c = _MACHINE_CLASS_INVALID; - else { - c = machine_class_from_string(class); - if (c < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine class parameter"); - } - - if (leader == 1) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid leader PID"); - - if (!isempty(root_directory) && !path_is_absolute(root_directory)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Root directory must be empty or an absolute path"); - - if (leader == 0) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - assert_cc(sizeof(uint32_t) == sizeof(pid_t)); - - r = sd_bus_creds_get_pid(creds, (pid_t*) &leader); - if (r < 0) - return r; - } - - if (hashmap_get(manager->machines, name)) - return sd_bus_error_setf(error, BUS_ERROR_MACHINE_EXISTS, "Machine '%s' already exists", name); - - r = manager_add_machine(manager, name, &m); - if (r < 0) - return r; - - m->leader = leader; - m->class = c; - m->id = id; - - if (!isempty(service)) { - m->service = strdup(service); - if (!m->service) { - r = -ENOMEM; - goto fail; - } - } - - if (!isempty(root_directory)) { - m->root_directory = strdup(root_directory); - if (!m->root_directory) { - r = -ENOMEM; - goto fail; - } - } - - if (n_netif > 0) { - assert_cc(sizeof(int32_t) == sizeof(int)); - m->netif = memdup(netif, sizeof(int32_t) * n_netif); - if (!m->netif) { - r = -ENOMEM; - goto fail; - } - - m->n_netif = n_netif; - } - - *_m = m; - - return 1; - -fail: - machine_add_to_gc_queue(m); - return r; -} - -static int method_create_machine_internal(sd_bus_message *message, bool read_network, void *userdata, sd_bus_error *error) { - Manager *manager = userdata; - Machine *m = NULL; - int r; - - assert(message); - assert(manager); - - r = method_create_or_register_machine(manager, message, read_network, &m, error); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(message, 'a', "(sv)"); - if (r < 0) - goto fail; - - r = machine_start(m, message, error); - if (r < 0) - goto fail; - - m->create_message = sd_bus_message_ref(message); - return 1; - -fail: - machine_add_to_gc_queue(m); - return r; -} - -static int method_create_machine_with_network(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_create_machine_internal(message, true, userdata, error); -} - -static int method_create_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_create_machine_internal(message, false, userdata, error); -} - -static int method_register_machine_internal(sd_bus_message *message, bool read_network, void *userdata, sd_bus_error *error) { - Manager *manager = userdata; - _cleanup_free_ char *p = NULL; - Machine *m = NULL; - int r; - - assert(message); - assert(manager); - - r = method_create_or_register_machine(manager, message, read_network, &m, error); - if (r < 0) - return r; - - r = cg_pid_get_unit(m->leader, &m->unit); - if (r < 0) { - r = sd_bus_error_set_errnof(error, r, "Failed to determine unit of process "PID_FMT" : %s", m->leader, strerror(-r)); - goto fail; - } - - r = machine_start(m, NULL, error); - if (r < 0) - goto fail; - - p = machine_bus_path(m); - if (!p) { - r = -ENOMEM; - goto fail; - } - - return sd_bus_reply_method_return(message, "o", p); - -fail: - machine_add_to_gc_queue(m); - return r; -} - -static int method_register_machine_with_network(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_register_machine_internal(message, true, userdata, error); -} - -static int method_register_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_register_machine_internal(message, false, userdata, error); -} - -static int method_terminate_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_terminate(message, machine, error); -} - -static int method_kill_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_kill(message, machine, error); -} - -static int method_get_machine_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_get_addresses(message, machine, error); -} - -static int method_get_machine_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_get_os_release(message, machine, error); -} - -static int method_list_images(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(image_hashmap_freep) Hashmap *images = NULL; - Manager *m = userdata; - Image *image; - Iterator i; - int r; - - assert(message); - assert(m); - - images = hashmap_new(&string_hash_ops); - if (!images) - return -ENOMEM; - - r = image_discover(images); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(ssbttto)"); - if (r < 0) - return r; - - HASHMAP_FOREACH(image, images, i) { - _cleanup_free_ char *p = NULL; - - p = image_bus_path(image->name); - if (!p) - return -ENOMEM; - - r = sd_bus_message_append(reply, "(ssbttto)", - image->name, - image_type_to_string(image->type), - image->read_only, - image->crtime, - image->mtime, - image->usage, - p); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -static int method_open_machine_pty(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_open_pty(message, machine, error); -} - -static int method_open_machine_login(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_open_login(message, machine, error); -} - -static int method_open_machine_shell(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_open_shell(message, machine, error); -} - -static int method_bind_mount_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_bind_mount(message, machine, error); -} - -static int method_copy_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_copy(message, machine, error); -} - -static int method_open_machine_root_directory(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_open_root_directory(message, machine, error); -} - -static int method_remove_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(image_unrefp) Image* i = NULL; - const char *name; - int r; - - assert(message); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - if (!image_name_is_valid(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); - - r = image_find(name, &i); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); - - i->userdata = userdata; - return bus_image_method_remove(message, i, error); -} - -static int method_rename_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(image_unrefp) Image* i = NULL; - const char *old_name; - int r; - - assert(message); - - r = sd_bus_message_read(message, "s", &old_name); - if (r < 0) - return r; - - if (!image_name_is_valid(old_name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", old_name); - - r = image_find(old_name, &i); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", old_name); - - i->userdata = userdata; - return bus_image_method_rename(message, i, error); -} - -static int method_clone_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(image_unrefp) Image *i = NULL; - const char *old_name; - int r; - - assert(message); - - r = sd_bus_message_read(message, "s", &old_name); - if (r < 0) - return r; - - if (!image_name_is_valid(old_name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", old_name); - - r = image_find(old_name, &i); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", old_name); - - i->userdata = userdata; - return bus_image_method_clone(message, i, error); -} - -static int method_mark_image_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(image_unrefp) Image *i = NULL; - const char *name; - int r; - - assert(message); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - if (!image_name_is_valid(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); - - r = image_find(name, &i); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); - - i->userdata = userdata; - return bus_image_method_mark_read_only(message, i, error); -} - -static int method_clean_pool(sd_bus_message *message, void *userdata, sd_bus_error *error) { - enum { - REMOVE_ALL, - REMOVE_HIDDEN, - } mode; - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(image_hashmap_freep) Hashmap *images = NULL; - Manager *m = userdata; - Image *image; - const char *mm; - Iterator i; - int r; - - assert(message); - - r = sd_bus_message_read(message, "s", &mm); - if (r < 0) - return r; - - if (streq(mm, "all")) - mode = REMOVE_ALL; - else if (streq(mm, "hidden")) - mode = REMOVE_HIDDEN; - else - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown mode '%s'.", mm); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-machines", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - images = hashmap_new(&string_hash_ops); - if (!images) - return -ENOMEM; - - r = image_discover(images); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(st)"); - if (r < 0) - return r; - - HASHMAP_FOREACH(image, images, i) { - - /* We can't remove vendor images (i.e. those in /usr) */ - if (IMAGE_IS_VENDOR(image)) - continue; - - if (IMAGE_IS_HOST(image)) - continue; - - if (mode == REMOVE_HIDDEN && !IMAGE_IS_HIDDEN(image)) - continue; - - r = image_remove(image); - if (r == -EBUSY) /* keep images that are currently being used. */ - continue; - if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to remove image %s: %m", image->name); - - r = sd_bus_message_append(reply, "(st)", image->name, image->usage_exclusive); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - uint64_t limit; - int r; - - assert(message); - - r = sd_bus_message_read(message, "t", &limit); - if (r < 0) - return r; - if (!FILE_SIZE_VALID_OR_INFINITY(limit)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range"); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-machines", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - /* Set up the machine directory if necessary */ - r = setup_machine_directory(limit, error); - if (r < 0) - return r; - - /* Resize the backing loopback device, if there is one, except if we asked to drop any limit */ - if (limit != (uint64_t) -1) { - r = btrfs_resize_loopback("/var/lib/machines", limit, false); - if (r == -ENOTTY) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Quota is only supported on btrfs."); - if (r < 0 && r != -ENODEV) /* ignore ENODEV, as that's what is returned if the file system is not on loopback */ - return sd_bus_error_set_errnof(error, r, "Failed to adjust loopback limit: %m"); - } - - (void) btrfs_qgroup_set_limit("/var/lib/machines", 0, limit); - - r = btrfs_subvol_set_subtree_quota_limit("/var/lib/machines", 0, limit); - if (r == -ENOTTY) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Quota is only supported on btrfs."); - if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to adjust quota limit: %m"); - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_set_image_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(image_unrefp) Image *i = NULL; - const char *name; - int r; - - assert(message); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - if (!image_name_is_valid(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); - - r = image_find(name, &i); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); - - i->userdata = userdata; - return bus_image_method_set_limit(message, i, error); -} - -static int method_map_from_machine_user(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_fclose_ FILE *f = NULL; - Manager *m = userdata; - const char *name, *p; - Machine *machine; - uint32_t uid; - int r; - - r = sd_bus_message_read(message, "su", &name, &uid); - if (r < 0) - return r; - - if (!uid_is_valid(uid)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - if (machine->class != MACHINE_CONTAINER) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Not supported for non-container machines."); - - p = procfs_file_alloca(machine->leader, "uid_map"); - f = fopen(p, "re"); - if (!f) - return -errno; - - for (;;) { - uid_t uid_base, uid_shift, uid_range, converted; - int k; - - errno = 0; - k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT, &uid_base, &uid_shift, &uid_range); - if (k < 0 && feof(f)) - break; - if (k != 3) { - if (ferror(f) && errno > 0) - return -errno; - - return -EIO; - } - - if (uid < uid_base || uid >= uid_base + uid_range) - continue; - - converted = uid - uid_base + uid_shift; - if (!uid_is_valid(converted)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); - - return sd_bus_reply_method_return(message, "u", (uint32_t) converted); - } - - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "Machine '%s' has no matching user mappings.", name); -} - -static int method_map_to_machine_user(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - uid_t uid; - Iterator i; - int r; - - r = sd_bus_message_read(message, "u", &uid); - if (r < 0) - return r; - if (!uid_is_valid(uid)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); - if (uid < 0x10000) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "User " UID_FMT " belongs to host UID range", uid); - - HASHMAP_FOREACH(machine, m->machines, i) { - _cleanup_fclose_ FILE *f = NULL; - char p[strlen("/proc//uid_map") + DECIMAL_STR_MAX(pid_t) + 1]; - - if (machine->class != MACHINE_CONTAINER) - continue; - - xsprintf(p, "/proc/" UID_FMT "/uid_map", machine->leader); - f = fopen(p, "re"); - if (!f) { - log_warning_errno(errno, "Failed top open %s, ignoring,", p); - continue; - } - - for (;;) { - _cleanup_free_ char *o = NULL; - uid_t uid_base, uid_shift, uid_range, converted; - int k; - - errno = 0; - k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT, &uid_base, &uid_shift, &uid_range); - if (k < 0 && feof(f)) - break; - if (k != 3) { - if (ferror(f) && errno > 0) - return -errno; - - return -EIO; - } - - if (uid < uid_shift || uid >= uid_shift + uid_range) - continue; - - converted = (uid - uid_shift + uid_base); - if (!uid_is_valid(converted)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); - - o = machine_bus_path(machine); - if (!o) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "sou", machine->name, o, (uint32_t) converted); - } - } - - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "No matching user mapping for " UID_FMT ".", uid); -} - -static int method_map_from_machine_group(sd_bus_message *message, void *groupdata, sd_bus_error *error) { - _cleanup_fclose_ FILE *f = NULL; - Manager *m = groupdata; - const char *name, *p; - Machine *machine; - uint32_t gid; - int r; - - r = sd_bus_message_read(message, "su", &name, &gid); - if (r < 0) - return r; - - if (!gid_is_valid(gid)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - if (machine->class != MACHINE_CONTAINER) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Not supported for non-container machines."); - - p = procfs_file_alloca(machine->leader, "gid_map"); - f = fopen(p, "re"); - if (!f) - return -errno; - - for (;;) { - gid_t gid_base, gid_shift, gid_range, converted; - int k; - - errno = 0; - k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT, &gid_base, &gid_shift, &gid_range); - if (k < 0 && feof(f)) - break; - if (k != 3) { - if (ferror(f) && errno > 0) - return -errno; - - return -EIO; - } - - if (gid < gid_base || gid >= gid_base + gid_range) - continue; - - converted = gid - gid_base + gid_shift; - if (!gid_is_valid(converted)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); - - return sd_bus_reply_method_return(message, "u", (uint32_t) converted); - } - - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "Machine '%s' has no matching group mappings.", name); -} - -static int method_map_to_machine_group(sd_bus_message *message, void *groupdata, sd_bus_error *error) { - Manager *m = groupdata; - Machine *machine; - gid_t gid; - Iterator i; - int r; - - r = sd_bus_message_read(message, "u", &gid); - if (r < 0) - return r; - if (!gid_is_valid(gid)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); - if (gid < 0x10000) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "Group " GID_FMT " belongs to host GID range", gid); - - HASHMAP_FOREACH(machine, m->machines, i) { - _cleanup_fclose_ FILE *f = NULL; - char p[strlen("/proc//gid_map") + DECIMAL_STR_MAX(pid_t) + 1]; - - if (machine->class != MACHINE_CONTAINER) - continue; - - xsprintf(p, "/proc/" GID_FMT "/gid_map", machine->leader); - f = fopen(p, "re"); - if (!f) { - log_warning_errno(errno, "Failed top open %s, ignoring,", p); - continue; - } - - for (;;) { - _cleanup_free_ char *o = NULL; - gid_t gid_base, gid_shift, gid_range, converted; - int k; - - errno = 0; - k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT, &gid_base, &gid_shift, &gid_range); - if (k < 0 && feof(f)) - break; - if (k != 3) { - if (ferror(f) && errno > 0) - return -errno; - - return -EIO; - } - - if (gid < gid_shift || gid >= gid_shift + gid_range) - continue; - - converted = (gid - gid_shift + gid_base); - if (!gid_is_valid(converted)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); - - o = machine_bus_path(machine); - if (!o) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "sou", machine->name, o, (uint32_t) converted); - } - } - - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "No matching group mapping for " GID_FMT ".", gid); -} - -const sd_bus_vtable manager_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("PoolPath", "s", property_get_pool_path, 0, 0), - SD_BUS_PROPERTY("PoolUsage", "t", property_get_pool_usage, 0, 0), - SD_BUS_PROPERTY("PoolLimit", "t", property_get_pool_limit, 0, 0), - SD_BUS_METHOD("GetMachine", "s", "o", method_get_machine, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetImage", "s", "o", method_get_image, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetMachineByPID", "u", "o", method_get_machine_by_pid, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListMachines", NULL, "a(ssso)", method_list_machines, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListImages", NULL, "a(ssbttto)", method_list_images, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CreateMachine", "sayssusa(sv)", "o", method_create_machine, 0), - SD_BUS_METHOD("CreateMachineWithNetwork", "sayssusaia(sv)", "o", method_create_machine_with_network, 0), - SD_BUS_METHOD("RegisterMachine", "sayssus", "o", method_register_machine, 0), - SD_BUS_METHOD("RegisterMachineWithNetwork", "sayssusai", "o", method_register_machine_with_network, 0), - SD_BUS_METHOD("TerminateMachine", "s", NULL, method_terminate_machine, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("KillMachine", "ssi", NULL, method_kill_machine, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetMachineAddresses", "s", "a(iay)", method_get_machine_addresses, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetMachineOSRelease", "s", "a{ss}", method_get_machine_os_release, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("OpenMachinePTY", "s", "hs", method_open_machine_pty, 0), - SD_BUS_METHOD("OpenMachineLogin", "s", "hs", method_open_machine_login, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("OpenMachineShell", "sssasas", "hs", method_open_machine_shell, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("BindMountMachine", "sssbb", NULL, method_bind_mount_machine, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CopyFromMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CopyToMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("OpenMachineRootDirectory", "s", "h", method_open_machine_root_directory, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("RemoveImage", "s", NULL, method_remove_image, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("RenameImage", "ss", NULL, method_rename_image, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CloneImage", "ssb", NULL, method_clone_image, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MarkImageReadOnly", "sb", NULL, method_mark_image_read_only, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetPoolLimit", "t", NULL, method_set_pool_limit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetImageLimit", "st", NULL, method_set_image_limit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CleanPool", "s", "a(st)", method_clean_pool, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MapFromMachineUser", "su", "u", method_map_from_machine_user, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MapToMachineUser", "u", "sou", method_map_to_machine_user, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MapFromMachineGroup", "su", "u", method_map_from_machine_group, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MapToMachineGroup", "u", "sou", method_map_to_machine_group, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_SIGNAL("MachineNew", "so", 0), - SD_BUS_SIGNAL("MachineRemoved", "so", 0), - SD_BUS_VTABLE_END -}; - -int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) { - const char *path, *result, *unit; - Manager *m = userdata; - Machine *machine; - uint32_t id; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "uoss", &id, &path, &unit, &result); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - machine = hashmap_get(m->machine_units, unit); - if (!machine) - return 0; - - if (streq_ptr(path, machine->scope_job)) { - machine->scope_job = mfree(machine->scope_job); - - if (machine->started) { - if (streq(result, "done")) - machine_send_create_reply(machine, NULL); - else { - _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL; - - sd_bus_error_setf(&e, BUS_ERROR_JOB_FAILED, "Start job for unit %s failed with '%s'", unit, result); - - machine_send_create_reply(machine, &e); - } - } - - machine_save(machine); - } - - machine_add_to_gc_queue(machine); - return 0; -} - -int match_properties_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *unit = NULL; - const char *path; - Manager *m = userdata; - Machine *machine; - int r; - - assert(message); - assert(m); - - path = sd_bus_message_get_path(message); - if (!path) - return 0; - - r = unit_name_from_dbus_path(path, &unit); - if (r == -EINVAL) /* not for a unit */ - return 0; - if (r < 0) { - log_oom(); - return 0; - } - - machine = hashmap_get(m->machine_units, unit); - if (!machine) - return 0; - - machine_add_to_gc_queue(machine); - return 0; -} - -int match_unit_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) { - const char *path, *unit; - Manager *m = userdata; - Machine *machine; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "so", &unit, &path); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - machine = hashmap_get(m->machine_units, unit); - if (!machine) - return 0; - - machine_add_to_gc_queue(machine); - return 0; -} - -int match_reloading(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - Iterator i; - int b, r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - if (b) - return 0; - - /* systemd finished reloading, let's recheck all our machines */ - log_debug("System manager has been reloaded, rechecking machines..."); - - HASHMAP_FOREACH(machine, m->machines, i) - machine_add_to_gc_queue(machine); - - return 0; -} - -int manager_start_scope( - Manager *manager, - const char *scope, - pid_t pid, - const char *slice, - const char *description, - sd_bus_message *more_properties, - sd_bus_error *error, - char **job) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - int r; - - assert(manager); - assert(scope); - assert(pid > 1); - - r = sd_bus_message_new_method_call( - manager->bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartTransientUnit"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "ss", strempty(scope), "fail"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'a', "(sv)"); - if (r < 0) - return r; - - if (!isempty(slice)) { - r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); - if (r < 0) - return r; - } - - if (!isempty(description)) { - r = sd_bus_message_append(m, "(sv)", "Description", "s", description); - if (r < 0) - return r; - } - - r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, pid); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "(sv)", "Delegate", "b", 1); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", UINT64_C(16384)); - if (r < 0) - return bus_log_create_error(r); - - if (more_properties) { - r = sd_bus_message_copy(m, more_properties, true); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "a(sa(sv))", 0); - if (r < 0) - return r; - - r = sd_bus_call(manager->bus, m, 0, error, &reply); - if (r < 0) - return r; - - if (job) { - const char *j; - char *copy; - - r = sd_bus_message_read(reply, "o", &j); - if (r < 0) - return r; - - copy = strdup(j); - if (!copy) - return -ENOMEM; - - *job = copy; - } - - return 1; -} - -int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int r; - - assert(manager); - assert(unit); - - r = sd_bus_call_method( - manager->bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StopUnit", - error, - &reply, - "ss", unit, "fail"); - if (r < 0) { - if (sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) || - sd_bus_error_has_name(error, BUS_ERROR_LOAD_FAILED)) { - - if (job) - *job = NULL; - - sd_bus_error_free(error); - return 0; - } - - return r; - } - - if (job) { - const char *j; - char *copy; - - r = sd_bus_message_read(reply, "o", &j); - if (r < 0) - return r; - - copy = strdup(j); - if (!copy) - return -ENOMEM; - - *job = copy; - } - - return 1; -} - -int manager_kill_unit(Manager *manager, const char *unit, int signo, sd_bus_error *error) { - assert(manager); - assert(unit); - - return sd_bus_call_method( - manager->bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "KillUnit", - error, - NULL, - "ssi", unit, "all", signo); -} - -int manager_unit_is_active(Manager *manager, const char *unit) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *path = NULL; - const char *state; - int r; - - assert(manager); - assert(unit); - - path = unit_dbus_path_from_name(unit); - if (!path) - return -ENOMEM; - - r = sd_bus_get_property( - manager->bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "ActiveState", - &error, - &reply, - "s"); - if (r < 0) { - if (sd_bus_error_has_name(&error, SD_BUS_ERROR_NO_REPLY) || - sd_bus_error_has_name(&error, SD_BUS_ERROR_DISCONNECTED)) - return true; - - if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT) || - sd_bus_error_has_name(&error, BUS_ERROR_LOAD_FAILED)) - return false; - - return r; - } - - r = sd_bus_message_read(reply, "s", &state); - if (r < 0) - return -EINVAL; - - return !STR_IN_SET(state, "inactive", "failed"); -} - -int manager_job_is_active(Manager *manager, const char *path) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int r; - - assert(manager); - assert(path); - - r = sd_bus_get_property( - manager->bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Job", - "State", - &error, - &reply, - "s"); - if (r < 0) { - if (sd_bus_error_has_name(&error, SD_BUS_ERROR_NO_REPLY) || - sd_bus_error_has_name(&error, SD_BUS_ERROR_DISCONNECTED)) - return true; - - if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_OBJECT)) - return false; - - return r; - } - - /* We don't actually care about the state really. The fact - * that we could read the job state is enough for us */ - - return true; -} - -int manager_get_machine_by_pid(Manager *m, pid_t pid, Machine **machine) { - Machine *mm; - int r; - - assert(m); - assert(pid >= 1); - assert(machine); - - mm = hashmap_get(m->machine_leaders, PID_TO_PTR(pid)); - if (!mm) { - _cleanup_free_ char *unit = NULL; - - r = cg_pid_get_unit(pid, &unit); - if (r >= 0) - mm = hashmap_get(m->machine_units, unit); - } - if (!mm) - return 0; - - *machine = mm; - return 1; -} - -int manager_add_machine(Manager *m, const char *name, Machine **_machine) { - Machine *machine; - - assert(m); - assert(name); - - machine = hashmap_get(m->machines, name); - if (!machine) { - machine = machine_new(m, _MACHINE_CLASS_INVALID, name); - if (!machine) - return -ENOMEM; - } - - if (_machine) - *_machine = machine; - - return 0; -} diff --git a/src/machine/machined.c b/src/machine/machined.c deleted file mode 100644 index f7ceb5e603..0000000000 --- a/src/machine/machined.c +++ /dev/null @@ -1,415 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 -#include - -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "cgroup-util.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "hostname-util.h" -#include "label.h" -#include "machine-image.h" -#include "machined.h" -#include "signal-util.h" - -Manager *manager_new(void) { - Manager *m; - int r; - - m = new0(Manager, 1); - if (!m) - return NULL; - - m->machines = hashmap_new(&string_hash_ops); - m->machine_units = hashmap_new(&string_hash_ops); - m->machine_leaders = hashmap_new(NULL); - - if (!m->machines || !m->machine_units || !m->machine_leaders) { - manager_free(m); - return NULL; - } - - r = sd_event_default(&m->event); - if (r < 0) { - manager_free(m); - return NULL; - } - - sd_event_set_watchdog(m->event, true); - - return m; -} - -void manager_free(Manager *m) { - Machine *machine; - Image *i; - - assert(m); - - while (m->operations) - operation_free(m->operations); - - assert(m->n_operations == 0); - - while ((machine = hashmap_first(m->machines))) - machine_free(machine); - - hashmap_free(m->machines); - hashmap_free(m->machine_units); - hashmap_free(m->machine_leaders); - - while ((i = hashmap_steal_first(m->image_cache))) - image_unref(i); - - hashmap_free(m->image_cache); - - sd_event_source_unref(m->image_cache_defer_event); - - bus_verify_polkit_async_registry_free(m->polkit_registry); - - sd_bus_unref(m->bus); - sd_event_unref(m->event); - - free(m); -} - -static int manager_add_host_machine(Manager *m) { - _cleanup_free_ char *rd = NULL, *unit = NULL; - sd_id128_t mid; - Machine *t; - int r; - - if (m->host_machine) - return 0; - - r = sd_id128_get_machine(&mid); - if (r < 0) - return log_error_errno(r, "Failed to get machine ID: %m"); - - rd = strdup("/"); - if (!rd) - return log_oom(); - - unit = strdup("-.slice"); - if (!unit) - return log_oom(); - - t = machine_new(m, MACHINE_HOST, ".host"); - if (!t) - return log_oom(); - - t->leader = 1; - t->id = mid; - - t->root_directory = rd; - t->unit = unit; - rd = unit = NULL; - - dual_timestamp_from_boottime_or_monotonic(&t->timestamp, 0); - - m->host_machine = t; - - return 0; -} - -int manager_enumerate_machines(Manager *m) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - int r = 0; - - assert(m); - - r = manager_add_host_machine(m); - if (r < 0) - return r; - - /* Read in machine data stored on disk */ - d = opendir("/run/systemd/machines"); - if (!d) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open /run/systemd/machines: %m"); - } - - FOREACH_DIRENT(de, d, return -errno) { - struct Machine *machine; - int k; - - if (!dirent_is_file(de)) - continue; - - /* Ignore symlinks that map the unit name to the machine */ - if (startswith(de->d_name, "unit:")) - continue; - - if (!machine_name_is_valid(de->d_name)) - continue; - - k = manager_add_machine(m, de->d_name, &machine); - if (k < 0) { - r = log_error_errno(k, "Failed to add machine by file name %s: %m", de->d_name); - continue; - } - - machine_add_to_gc_queue(machine); - - k = machine_load(machine); - if (k < 0) - r = k; - } - - return r; -} - -static int manager_connect_bus(Manager *m) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(m); - assert(!m->bus); - - r = sd_bus_default_system(&m->bus); - if (r < 0) - return log_error_errno(r, "Failed to connect to system bus: %m"); - - r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/machine1", "org.freedesktop.machine1.Manager", manager_vtable, m); - if (r < 0) - return log_error_errno(r, "Failed to add manager object vtable: %m"); - - r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/machine1/machine", "org.freedesktop.machine1.Machine", machine_vtable, machine_object_find, m); - if (r < 0) - return log_error_errno(r, "Failed to add machine object vtable: %m"); - - r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/machine1/machine", machine_node_enumerator, m); - if (r < 0) - return log_error_errno(r, "Failed to add machine enumerator: %m"); - - r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/machine1/image", "org.freedesktop.machine1.Image", image_vtable, image_object_find, m); - if (r < 0) - return log_error_errno(r, "Failed to add image object vtable: %m"); - - r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/machine1/image", image_node_enumerator, m); - if (r < 0) - return log_error_errno(r, "Failed to add image enumerator: %m"); - - r = sd_bus_add_match(m->bus, - NULL, - "type='signal'," - "sender='org.freedesktop.systemd1'," - "interface='org.freedesktop.systemd1.Manager'," - "member='JobRemoved'," - "path='/org/freedesktop/systemd1'", - match_job_removed, - m); - if (r < 0) - return log_error_errno(r, "Failed to add match for JobRemoved: %m"); - - r = sd_bus_add_match(m->bus, - NULL, - "type='signal'," - "sender='org.freedesktop.systemd1'," - "interface='org.freedesktop.systemd1.Manager'," - "member='UnitRemoved'," - "path='/org/freedesktop/systemd1'", - match_unit_removed, - m); - if (r < 0) - return log_error_errno(r, "Failed to add match for UnitRemoved: %m"); - - r = sd_bus_add_match(m->bus, - NULL, - "type='signal'," - "sender='org.freedesktop.systemd1'," - "interface='org.freedesktop.DBus.Properties'," - "member='PropertiesChanged'," - "arg0='org.freedesktop.systemd1.Unit'", - match_properties_changed, - m); - if (r < 0) - return log_error_errno(r, "Failed to add match for PropertiesChanged: %m"); - - r = sd_bus_add_match(m->bus, - NULL, - "type='signal'," - "sender='org.freedesktop.systemd1'," - "interface='org.freedesktop.systemd1.Manager'," - "member='Reloading'," - "path='/org/freedesktop/systemd1'", - match_reloading, - m); - if (r < 0) - return log_error_errno(r, "Failed to add match for Reloading: %m"); - - r = sd_bus_call_method( - m->bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "Subscribe", - &error, - NULL, NULL); - if (r < 0) { - log_error("Failed to enable subscription: %s", bus_error_message(&error, r)); - return r; - } - - r = sd_bus_request_name(m->bus, "org.freedesktop.machine1", 0); - if (r < 0) - return log_error_errno(r, "Failed to register name: %m"); - - r = sd_bus_attach_event(m->bus, m->event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - - return 0; -} - -void manager_gc(Manager *m, bool drop_not_started) { - Machine *machine; - - assert(m); - - while ((machine = m->machine_gc_queue)) { - LIST_REMOVE(gc_queue, m->machine_gc_queue, machine); - machine->in_gc_queue = false; - - /* First, if we are not closing yet, initiate stopping */ - if (!machine_check_gc(machine, drop_not_started) && - machine_get_state(machine) != MACHINE_CLOSING) - machine_stop(machine); - - /* Now, the stop stop probably made this referenced - * again, but if it didn't, then it's time to let it - * go entirely. */ - if (!machine_check_gc(machine, drop_not_started)) { - machine_finalize(machine); - machine_free(machine); - } - } -} - -int manager_startup(Manager *m) { - Machine *machine; - Iterator i; - int r; - - assert(m); - - /* Connect to the bus */ - r = manager_connect_bus(m); - if (r < 0) - return r; - - /* Deserialize state */ - manager_enumerate_machines(m); - - /* Remove stale objects before we start them */ - manager_gc(m, false); - - /* And start everything */ - HASHMAP_FOREACH(machine, m->machines, i) - machine_start(machine, NULL, NULL); - - return 0; -} - -static bool check_idle(void *userdata) { - Manager *m = userdata; - - if (m->operations) - return false; - - manager_gc(m, true); - - return hashmap_isempty(m->machines); -} - -int manager_run(Manager *m) { - assert(m); - - return bus_event_loop_with_idle( - m->event, - m->bus, - "org.freedesktop.machine1", - DEFAULT_EXIT_USEC, - check_idle, m); -} - -int main(int argc, char *argv[]) { - Manager *m = NULL; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_set_facility(LOG_AUTH); - log_parse_environment(); - log_open(); - - umask(0022); - - if (argc != 1) { - log_error("This program takes no arguments."); - r = -EINVAL; - goto finish; - } - - /* Always create the directories people can create inotify - * watches in. Note that some applications might check for the - * existence of /run/systemd/machines/ to determine whether - * machined is available, so please always make sure this - * check stays in. */ - mkdir_label("/run/systemd/machines", 0755); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); - - m = manager_new(); - if (!m) { - r = log_oom(); - goto finish; - } - - r = manager_startup(m); - if (r < 0) { - log_error_errno(r, "Failed to fully start up daemon: %m"); - goto finish; - } - - log_debug("systemd-machined running as pid "PID_FMT, getpid()); - - sd_notify(false, - "READY=1\n" - "STATUS=Processing requests..."); - - r = manager_run(m); - - log_debug("systemd-machined stopped as pid "PID_FMT, getpid()); - -finish: - manager_free(m); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/machine/machined.h b/src/machine/machined.h deleted file mode 100644 index 7b9b148044..0000000000 --- a/src/machine/machined.h +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "sd-bus.h" -#include "sd-event.h" - -#include "hashmap.h" -#include "list.h" - -typedef struct Manager Manager; - -#include "image-dbus.h" -#include "machine-dbus.h" -#include "machine.h" -#include "operation.h" - -struct Manager { - sd_event *event; - sd_bus *bus; - - Hashmap *machines; - Hashmap *machine_units; - Hashmap *machine_leaders; - - Hashmap *polkit_registry; - - Hashmap *image_cache; - sd_event_source *image_cache_defer_event; - - LIST_HEAD(Machine, machine_gc_queue); - - Machine *host_machine; - - LIST_HEAD(Operation, operations); - unsigned n_operations; -}; - -Manager *manager_new(void); -void manager_free(Manager *m); - -int manager_add_machine(Manager *m, const char *name, Machine **_machine); -int manager_enumerate_machines(Manager *m); - -int manager_startup(Manager *m); -int manager_run(Manager *m); - -void manager_gc(Manager *m, bool drop_not_started); - -int manager_get_machine_by_pid(Manager *m, pid_t pid, Machine **machine); - -extern const sd_bus_vtable manager_vtable[]; - -int match_reloading(sd_bus_message *message, void *userdata, sd_bus_error *error); -int match_unit_removed(sd_bus_message *message, void *userdata, sd_bus_error *error); -int match_properties_changed(sd_bus_message *message, void *userdata, sd_bus_error *error); -int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error); - -int manager_start_scope(Manager *manager, const char *scope, pid_t pid, const char *slice, const char *description, sd_bus_message *more_properties, sd_bus_error *error, char **job); -int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job); -int manager_kill_unit(Manager *manager, const char *unit, int signo, sd_bus_error *error); -int manager_unit_is_active(Manager *manager, const char *unit); -int manager_job_is_active(Manager *manager, const char *path); diff --git a/src/machine/operation.c b/src/machine/operation.c deleted file mode 100644 index e6ddc41a55..0000000000 --- a/src/machine/operation.c +++ /dev/null @@ -1,131 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2016 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 "alloc-util.h" -#include "fd-util.h" -#include "operation.h" -#include "process-util.h" - -static int operation_done(sd_event_source *s, const siginfo_t *si, void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - Operation *o = userdata; - int r; - - assert(o); - assert(si); - - log_debug("Operating " PID_FMT " is now complete with with code=%s status=%i", - o->pid, - sigchld_code_to_string(si->si_code), si->si_status); - - o->pid = 0; - - if (si->si_code != CLD_EXITED) { - r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child died abnormally."); - goto fail; - } - - if (si->si_status != EXIT_SUCCESS) { - if (read(o->errno_fd, &r, sizeof(r)) == sizeof(r)) - r = sd_bus_error_set_errnof(&error, r, "%m"); - else - r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child failed."); - - goto fail; - } - - r = sd_bus_reply_method_return(o->message, NULL); - if (r < 0) - log_error_errno(r, "Failed to reply to message: %m"); - - operation_free(o); - return 0; - -fail: - r = sd_bus_reply_method_error(o->message, &error); - if (r < 0) - log_error_errno(r, "Failed to reply to message: %m"); - - operation_free(o); - return 0; -} - -int operation_new(Manager *manager, Machine *machine, pid_t child, sd_bus_message *message, int errno_fd) { - Operation *o; - int r; - - assert(manager); - assert(child > 1); - assert(message); - assert(errno_fd >= 0); - - o = new0(Operation, 1); - if (!o) - return -ENOMEM; - - r = sd_event_add_child(manager->event, &o->event_source, child, WEXITED, operation_done, o); - if (r < 0) { - free(o); - return r; - } - - o->pid = child; - o->message = sd_bus_message_ref(message); - o->errno_fd = errno_fd; - - LIST_PREPEND(operations, manager->operations, o); - manager->n_operations++; - o->manager = manager; - - if (machine) { - LIST_PREPEND(operations_by_machine, machine->operations, o); - o->machine = machine; - } - - log_debug("Started new operation " PID_FMT ".", child); - - /* At this point we took ownership of both the child and the errno file descriptor! */ - - return 0; -} - -Operation *operation_free(Operation *o) { - if (!o) - return NULL; - - sd_event_source_unref(o->event_source); - - safe_close(o->errno_fd); - - if (o->pid > 1) - (void) sigkill_wait(o->pid); - - sd_bus_message_unref(o->message); - - if (o->manager) { - LIST_REMOVE(operations, o->manager->operations, o); - o->manager->n_operations--; - } - - if (o->machine) - LIST_REMOVE(operations_by_machine, o->machine->operations, o); - - free(o); - return NULL; -} diff --git a/src/machine/operation.h b/src/machine/operation.h deleted file mode 100644 index 7ca47bc3af..0000000000 --- a/src/machine/operation.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2016 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 "sd-bus.h" -#include "sd-event.h" - -#include "list.h" - -typedef struct Operation Operation; - -#include "machined.h" - -#define OPERATIONS_MAX 64 - -struct Operation { - Manager *manager; - Machine *machine; - pid_t pid; - sd_bus_message *message; - int errno_fd; - sd_event_source *event_source; - LIST_FIELDS(Operation, operations); - LIST_FIELDS(Operation, operations_by_machine); -}; - -int operation_new(Manager *manager, Machine *machine, pid_t child, sd_bus_message *message, int errno_fd); -Operation *operation_free(Operation *o); diff --git a/src/machine/org.freedesktop.machine1.conf b/src/machine/org.freedesktop.machine1.conf deleted file mode 100644 index 9d40b90151..0000000000 --- a/src/machine/org.freedesktop.machine1.conf +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/machine/org.freedesktop.machine1.policy.in b/src/machine/org.freedesktop.machine1.policy.in deleted file mode 100644 index 69f78a5c25..0000000000 --- a/src/machine/org.freedesktop.machine1.policy.in +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - The systemd Project - http://www.freedesktop.org/wiki/Software/systemd - - - <_description>Log into a local container - <_message>Authentication is required to log into a local container. - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Log into the local host - <_message>Authentication is required to log into the local host. - - auth_admin - auth_admin - yes - - - - - <_description>Acquire a shell in a local container - <_message>Authentication is required to acquire a shell in a local container. - - auth_admin - auth_admin - auth_admin_keep - - org.freedesktop.login1.login - - - - <_description>Acquire a shell on the local host - <_message>Authentication is required to acquire a shell on the local host. - - auth_admin - auth_admin - auth_admin_keep - - org.freedesktop.login1.host-login - - - - <_description>Acquire a pseudo TTY in a local container - <_message>Authentication is required to acquire a pseudo TTY in a local container. - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Acquire a pseudo TTY on the local host - <_message>Authentication is required to acquire a pseudo TTY on the local host. - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Manage local virtual machines and containers - <_message>Authentication is required to manage local virtual machines and containers. - - auth_admin - auth_admin - auth_admin_keep - - org.freedesktop.login1.shell org.freedesktop.login1.login - - - - <_description>Manage local virtual machine and container images - <_message>Authentication is required to manage local virtual machine and container images. - - auth_admin - auth_admin - auth_admin_keep - - - - diff --git a/src/machine/org.freedesktop.machine1.service b/src/machine/org.freedesktop.machine1.service deleted file mode 100644 index d3dc99852b..0000000000 --- a/src/machine/org.freedesktop.machine1.service +++ /dev/null @@ -1,12 +0,0 @@ -# This file is part of systemd. -# -# 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. - -[D-BUS Service] -Name=org.freedesktop.machine1 -Exec=/bin/false -User=root -SystemdService=dbus-org.freedesktop.machine1.service diff --git a/src/machine/test-machine-tables.c b/src/machine/test-machine-tables.c deleted file mode 100644 index f851a4d37d..0000000000 --- a/src/machine/test-machine-tables.c +++ /dev/null @@ -1,29 +0,0 @@ -/*** - This file is part of systemd - - Copyright 2013 Zbigniew Jędrzejewski-Szmek - - 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 "machine.h" -#include "test-tables.h" - -int main(int argc, char **argv) { - test_table(machine_class, MACHINE_CLASS); - test_table(machine_state, MACHINE_STATE); - test_table(kill_who, KILL_WHO); - - return EXIT_SUCCESS; -} diff --git a/src/modules-load/Makefile b/src/modules-load/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/modules-load/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/modules-load/Makefile b/src/modules-load/Makefile new file mode 100644 index 0000000000..ab334e16e8 --- /dev/null +++ b/src/modules-load/Makefile @@ -0,0 +1,60 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(HAVE_KMOD),) +systemd_modules_load_SOURCES = \ + src/modules-load/modules-load.c + +systemd_modules_load_CFLAGS = \ + $(AM_CFLAGS) \ + $(KMOD_CFLAGS) + +systemd_modules_load_LDADD = \ + libshared.la \ + $(KMOD_LIBS) + +libexec_PROGRAMS += \ + systemd-modules-load + +nodist_systemunit_DATA += \ + units/systemd-modules-load.service + +SYSINIT_TARGET_WANTS += \ + systemd-modules-load.service + +ifneq ($(ENABLE_TMPFILES),) +nodist_systemunit_DATA += \ + units/kmod-static-nodes.service + +SYSINIT_TARGET_WANTS += \ + kmod-static-nodes.service +endif # ENABLE_TMPFILES +endif # HAVE_KMOD + +EXTRA_DIST += \ + units/systemd-modules-load.service.in \ + units/kmod-static-nodes.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/network/Makefile b/src/network/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/network/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/network/Makefile b/src/network/Makefile new file mode 100644 index 0000000000..d43dfdfca8 --- /dev/null +++ b/src/network/Makefile @@ -0,0 +1,213 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_NETWORKD),) +libexec_PROGRAMS += \ + systemd-networkd + +systemd_networkd_SOURCES = \ + src/network/networkd.c + +systemd_networkd_LDADD = \ + libnetworkd-core.la + +ifneq ($(HAVE_LIBIPTC),) +systemd_networkd_LDADD += \ + libfirewall.la +endif # HAVE_LIBIPTC + +noinst_LTLIBRARIES += \ + libnetworkd-core.la + +libnetworkd_core_la_CFLAGS = \ + $(AM_CFLAGS) + +libnetworkd_core_la_SOURCES = \ + src/libsystemd-network/network-internal.h \ + src/network/networkd.h \ + src/network/networkd-conf.h \ + src/network/networkd-conf.c \ + src/network/networkd-link.h \ + src/network/networkd-link.c \ + src/network/networkd-netdev.h \ + src/network/networkd-netdev.c \ + src/network/networkd-netdev-tunnel.h \ + src/network/networkd-netdev-tunnel.c \ + src/network/networkd-netdev-veth.h \ + src/network/networkd-netdev-veth.c \ + src/network/networkd-netdev-vxlan.h \ + src/network/networkd-netdev-vxlan.c \ + src/network/networkd-netdev-vlan.h \ + src/network/networkd-netdev-vlan.c \ + src/network/networkd-netdev-macvlan.h \ + src/network/networkd-netdev-macvlan.c \ + src/network/networkd-netdev-ipvlan.h \ + src/network/networkd-netdev-ipvlan.c \ + src/network/networkd-netdev-dummy.h \ + src/network/networkd-netdev-dummy.c \ + src/network/networkd-netdev-tuntap.h \ + src/network/networkd-netdev-tuntap.c \ + src/network/networkd-netdev-bond.h \ + src/network/networkd-netdev-bond.c \ + src/network/networkd-netdev-bridge.h \ + src/network/networkd-netdev-bridge.c \ + src/network/networkd-link-bus.c \ + src/network/networkd-ipv4ll.c \ + src/network/networkd-dhcp4.c \ + src/network/networkd-dhcp6.c \ + src/network/networkd-ndisc.c \ + src/network/networkd-network.h \ + src/network/networkd-network.c \ + src/network/networkd-network-bus.c \ + src/network/networkd-address.h \ + src/network/networkd-address.c \ + src/network/networkd-route.h \ + src/network/networkd-route.c \ + src/network/networkd-manager.c \ + src/network/networkd-manager-bus.c \ + src/network/networkd-fdb.h \ + src/network/networkd-fdb.c \ + src/network/networkd-address-pool.h \ + src/network/networkd-address-pool.c \ + src/network/networkd-util.h \ + src/network/networkd-util.c \ + src/network/networkd-lldp-tx.h \ + src/network/networkd-lldp-tx.c + +nodist_libnetworkd_core_la_SOURCES = \ + src/network/networkd-gperf.c \ + src/network/networkd-network-gperf.c \ + src/network/networkd-netdev-gperf.c + +libnetworkd_core_la_LIBADD = \ + libsystemd-network.la \ + libshared.la + +libexec_PROGRAMS += \ + systemd-networkd-wait-online + +systemd_networkd_wait_online_CFLAGS = \ + $(AM_CFLAGS) + +systemd_networkd_wait_online_SOURCES = \ + src/libsystemd-network/network-internal.h \ + src/network/networkd-wait-online.h \ + src/network/networkd-wait-online-link.h \ + src/network/networkd-wait-online.c \ + src/network/networkd-wait-online-manager.c \ + src/network/networkd-wait-online-link.c + +systemd_networkd_wait_online_LDADD = \ + libsystemd-network.la \ + libshared.la + +bin_PROGRAMS += \ + networkctl + +networkctl_SOURCES = \ + src/network/networkctl.c + +networkctl_LDADD = \ + libshared.la \ + libsystemd-network.la + +dist_bashcompletion_data += \ + shell-completion/bash/networkctl + +test_networkd_conf_SOURCES = \ + src/network/test-networkd-conf.c + +test_networkd_conf_LDADD = \ + libnetworkd-core.la + +test_network_SOURCES = \ + src/network/test-network.c + +test_network_LDADD = \ + libnetworkd-core.la + +ifneq ($(HAVE_LIBIPTC),) +test_network_LDADD += \ + libfirewall.la +endif # HAVE_LIBIPTC + +test_network_tables_SOURCES = \ + src/network/test-network-tables.c \ + src/shared/test-tables.h + +test_network_tables_LDADD = \ + libnetworkd-core.la \ + libudev-core.la + +ifneq ($(HAVE_LIBIPTC),) +test_network_tables_LDADD += \ + libfirewall.la +endif # HAVE_LIBIPTC + +tests += \ + test-networkd-conf \ + test-network \ + test-network-tables + +dist_systemunit_DATA += \ + units/systemd-networkd.socket + +nodist_systemunit_DATA += \ + units/systemd-networkd.service \ + units/systemd-networkd-wait-online.service + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.network1.busname + +dist_dbussystemservice_DATA += \ + src/network/org.freedesktop.network1.service + +dist_dbuspolicy_DATA += \ + src/network/org.freedesktop.network1.conf + +GENERAL_ALIASES += \ + $(systemunitdir)/systemd-networkd.socket $(pkgsysconfdir)/system/sockets.target.wants/systemd-networkd.socket \ + $(systemunitdir)/systemd-networkd.service $(pkgsysconfdir)/system/multi-user.target.wants/systemd-networkd.service \ + $(systemunitdir)/systemd-networkd-wait-online.service $(pkgsysconfdir)/system/network-online.target.wants/systemd-networkd-wait-online.service + +SYSTEM_UNIT_ALIASES += \ + systemd-networkd.service dbus-org.freedesktop.network1.service + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.network1.busname + +endif # ENABLE_NETWORKD + +gperf_gperf_sources += \ + src/network/networkd-gperf.gperf \ + src/network/networkd-network-gperf.gperf \ + src/network/networkd-netdev-gperf.gperf + +EXTRA_DIST += \ + units/systemd-networkd.service.m4.in \ + units/systemd-networkd-wait-online.service.in \ + test/networkd-test.py + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/network/networkctl.c b/src/network/networkctl.c index d2df9b7560..85635b59bc 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -21,11 +21,11 @@ #include #include -#include "sd-device.h" -#include "sd-hwdb.h" -#include "sd-lldp.h" -#include "sd-netlink.h" -#include "sd-network.h" +#include +#include +#include +#include +#include #include "alloc-util.h" #include "arphrd-list.h" diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 37e13e639e..c5a3c52e94 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -20,7 +20,7 @@ #include #include -#include "sd-dhcp6-client.h" +#include #include "network-internal.h" #include "networkd.h" diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 14c4a02c7e..90cb9b93f6 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -21,14 +21,14 @@ #include -#include "sd-bus.h" -#include "sd-dhcp-client.h" -#include "sd-dhcp-server.h" -#include "sd-dhcp6-client.h" -#include "sd-ipv4ll.h" -#include "sd-lldp.h" -#include "sd-ndisc.h" -#include "sd-netlink.h" +#include +#include +#include +#include +#include +#include +#include +#include #include "list.h" #include "set.h" diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 9174dcc7f4..7af7abaa81 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -20,8 +20,8 @@ #include #include -#include "sd-daemon.h" -#include "sd-netlink.h" +#include +#include #include "alloc-util.h" #include "bus-util.h" diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 1a380bd214..f3a4fc0fa5 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -22,7 +22,7 @@ #include #include -#include "sd-ndisc.h" +#include #include "networkd.h" diff --git a/src/network/networkd-netdev-bond.c b/src/network/networkd-netdev-bond.c index 7913b0088e..7005b165d9 100644 --- a/src/network/networkd-netdev-bond.c +++ b/src/network/networkd-netdev-bond.c @@ -21,7 +21,7 @@ #include #include -#include "sd-netlink.h" +#include #include "alloc-util.h" #include "conf-parser.h" diff --git a/src/network/networkd-netdev-tunnel.c b/src/network/networkd-netdev-tunnel.c index 7aaa041ba3..26a9a972f1 100644 --- a/src/network/networkd-netdev-tunnel.c +++ b/src/network/networkd-netdev-tunnel.c @@ -23,7 +23,7 @@ #include #include -#include "sd-netlink.h" +#include #include "conf-parser.h" #include "missing.h" diff --git a/src/network/networkd-netdev-veth.c b/src/network/networkd-netdev-veth.c index b122a06c25..185b441c5a 100644 --- a/src/network/networkd-netdev-veth.c +++ b/src/network/networkd-netdev-veth.c @@ -20,7 +20,7 @@ #include #include -#include "sd-netlink.h" +#include #include "networkd-netdev-veth.h" diff --git a/src/network/networkd-netdev-vxlan.c b/src/network/networkd-netdev-vxlan.c index 724f9861be..363a6bdde6 100644 --- a/src/network/networkd-netdev-vxlan.c +++ b/src/network/networkd-netdev-vxlan.c @@ -19,7 +19,7 @@ #include -#include "sd-netlink.h" +#include #include "conf-parser.h" #include "alloc-util.h" diff --git a/src/network/networkd-netdev.h b/src/network/networkd-netdev.h index 20244c0309..ab3f068167 100644 --- a/src/network/networkd-netdev.h +++ b/src/network/networkd-netdev.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-netlink.h" +#include #include "list.h" #include "time-util.h" diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 91099161ce..177bc11ec4 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-bus.h" +#include #include "udev.h" #include "condition.h" diff --git a/src/network/networkd-wait-online-link.c b/src/network/networkd-wait-online-link.c index 5727422e3d..971545296f 100644 --- a/src/network/networkd-wait-online-link.c +++ b/src/network/networkd-wait-online-link.c @@ -18,7 +18,7 @@ along with systemd; If not, see . ***/ -#include "sd-network.h" +#include #include "alloc-util.h" #include "networkd-wait-online-link.h" diff --git a/src/network/networkd-wait-online.c b/src/network/networkd-wait-online.c index 3220c4b7ef..a08272463e 100644 --- a/src/network/networkd-wait-online.c +++ b/src/network/networkd-wait-online.c @@ -20,7 +20,7 @@ #include -#include "sd-daemon.h" +#include #include "networkd-wait-online.h" #include "signal-util.h" diff --git a/src/network/networkd-wait-online.h b/src/network/networkd-wait-online.h index f91995c306..7ac7f4018a 100644 --- a/src/network/networkd-wait-online.h +++ b/src/network/networkd-wait-online.h @@ -19,9 +19,9 @@ along with systemd; If not, see . ***/ -#include "sd-event.h" -#include "sd-netlink.h" -#include "sd-network.h" +#include +#include +#include #include "hashmap.h" diff --git a/src/network/networkd.c b/src/network/networkd.c index c8f81a2ca6..9f5c75ac3d 100644 --- a/src/network/networkd.c +++ b/src/network/networkd.c @@ -17,7 +17,7 @@ along with systemd; If not, see . ***/ -#include "sd-daemon.h" +#include #include "capability-util.h" #include "networkd.h" diff --git a/src/network/networkd.h b/src/network/networkd.h index ab512f0d08..b61e03920e 100644 --- a/src/network/networkd.h +++ b/src/network/networkd.h @@ -21,9 +21,9 @@ #include -#include "sd-bus.h" -#include "sd-event.h" -#include "sd-netlink.h" +#include +#include +#include #include "udev.h" #include "dhcp-identifier.h" diff --git a/src/notify/Makefile b/src/notify/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/notify/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/notify/notify.c b/src/notify/notify.c deleted file mode 100644 index 49f97c61d9..0000000000 --- a/src/notify/notify.c +++ /dev/null @@ -1,203 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include - -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "env-util.h" -#include "formats-util.h" -#include "log.h" -#include "parse-util.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -static bool arg_ready = false; -static pid_t arg_pid = 0; -static const char *arg_status = NULL; -static bool arg_booted = false; - -static void help(void) { - printf("%s [OPTIONS...] [VARIABLE=VALUE...]\n\n" - "Notify the init system about service status updates.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --ready Inform the init system about service start-up completion\n" - " --pid[=PID] Set main pid of daemon\n" - " --status=TEXT Set status text\n" - " --booted Check if the system was booted up with systemd\n", - program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_READY = 0x100, - ARG_VERSION, - ARG_PID, - ARG_STATUS, - ARG_BOOTED, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "ready", no_argument, NULL, ARG_READY }, - { "pid", optional_argument, NULL, ARG_PID }, - { "status", required_argument, NULL, ARG_STATUS }, - { "booted", no_argument, NULL, ARG_BOOTED }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_READY: - arg_ready = true; - break; - - case ARG_PID: - - if (optarg) { - if (parse_pid(optarg, &arg_pid) < 0) { - log_error("Failed to parse PID %s.", optarg); - return -EINVAL; - } - } else - arg_pid = getppid(); - - break; - - case ARG_STATUS: - arg_status = optarg; - break; - - case ARG_BOOTED: - arg_booted = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - } - - if (optind >= argc && - !arg_ready && - !arg_status && - !arg_pid && - !arg_booted) { - help(); - return -EINVAL; - } - - return 1; -} - -int main(int argc, char* argv[]) { - _cleanup_free_ char *status = NULL, *cpid = NULL, *n = NULL; - _cleanup_strv_free_ char **final_env = NULL; - char* our_env[4]; - unsigned i = 0; - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (arg_booted) - return sd_booted() <= 0; - - if (arg_ready) - our_env[i++] = (char*) "READY=1"; - - if (arg_status) { - status = strappend("STATUS=", arg_status); - if (!status) { - r = log_oom(); - goto finish; - } - - our_env[i++] = status; - } - - if (arg_pid > 0) { - if (asprintf(&cpid, "MAINPID="PID_FMT, arg_pid) < 0) { - r = log_oom(); - goto finish; - } - - our_env[i++] = cpid; - } - - our_env[i++] = NULL; - - final_env = strv_env_merge(2, our_env, argv + optind); - if (!final_env) { - r = log_oom(); - goto finish; - } - - if (strv_length(final_env) <= 0) { - r = 0; - goto finish; - } - - n = strv_join(final_env, "\n"); - if (!n) { - r = log_oom(); - goto finish; - } - - r = sd_pid_notify(arg_pid ? arg_pid : getppid(), false, n); - if (r < 0) { - log_error_errno(r, "Failed to notify init system: %m"); - goto finish; - } else if (r == 0) { - log_error("No status data could be sent: $NOTIFY_SOCKET was not set"); - r = -EOPNOTSUPP; - } - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/nspawn/.gitignore b/src/nspawn/.gitignore deleted file mode 100644 index 85c81fff24..0000000000 --- a/src/nspawn/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/nspawn-gperf.c diff --git a/src/nspawn/Makefile b/src/nspawn/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/nspawn/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/nspawn/nspawn-cgroup.c b/src/nspawn/nspawn-cgroup.c deleted file mode 100644 index f50f1ad6c2..0000000000 --- a/src/nspawn/nspawn-cgroup.c +++ /dev/null @@ -1,162 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 "alloc-util.h" -#include "cgroup-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "mkdir.h" -#include "nspawn-cgroup.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -int chown_cgroup(pid_t pid, uid_t uid_shift) { - _cleanup_free_ char *path = NULL, *fs = NULL; - _cleanup_close_ int fd = -1; - const char *fn; - int r; - - r = cg_pid_get_path(NULL, pid, &path); - if (r < 0) - return log_error_errno(r, "Failed to get container cgroup path: %m"); - - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs); - if (r < 0) - return log_error_errno(r, "Failed to get file system path for container cgroup: %m"); - - fd = open(fs, O_RDONLY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", fs); - - FOREACH_STRING(fn, - ".", - "tasks", - "notify_on_release", - "cgroup.procs", - "cgroup.events", - "cgroup.clone_children", - "cgroup.controllers", - "cgroup.subtree_control") - if (fchownat(fd, fn, uid_shift, uid_shift, 0) < 0) - log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno, - "Failed to chown() cgroup file %s, ignoring: %m", fn); - - return 0; -} - -int sync_cgroup(pid_t pid, bool unified_requested) { - _cleanup_free_ char *cgroup = NULL; - char tree[] = "/tmp/unifiedXXXXXX", pid_string[DECIMAL_STR_MAX(pid) + 1]; - bool undo_mount = false; - const char *fn; - int unified, r; - - unified = cg_unified(); - if (unified < 0) - return log_error_errno(unified, "Failed to determine whether the unified hierarchy is used: %m"); - - if ((unified > 0) == unified_requested) - return 0; - - /* When the host uses the legacy cgroup setup, but the - * container shall use the unified hierarchy, let's make sure - * we copy the path from the name=systemd hierarchy into the - * unified hierarchy. Similar for the reverse situation. */ - - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cgroup); - if (r < 0) - return log_error_errno(r, "Failed to get control group of " PID_FMT ": %m", pid); - - /* In order to access the unified hierarchy we need to mount it */ - if (!mkdtemp(tree)) - return log_error_errno(errno, "Failed to generate temporary mount point for unified hierarchy: %m"); - - if (unified) - r = mount("cgroup", tree, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, "none,name=systemd,xattr"); - else - r = mount("cgroup", tree, "cgroup2", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL); - if (r < 0) { - r = log_error_errno(errno, "Failed to mount unified hierarchy: %m"); - goto finish; - } - - undo_mount = true; - - fn = strjoina(tree, cgroup, "/cgroup.procs"); - (void) mkdir_parents(fn, 0755); - - sprintf(pid_string, PID_FMT, pid); - r = write_string_file(fn, pid_string, 0); - if (r < 0) - log_error_errno(r, "Failed to move process: %m"); - -finish: - if (undo_mount) - (void) umount(tree); - - (void) rmdir(tree); - return r; -} - -int create_subcgroup(pid_t pid, bool unified_requested) { - _cleanup_free_ char *cgroup = NULL; - const char *child; - int unified, r; - CGroupMask supported; - - /* In the unified hierarchy inner nodes may only only contain - * subgroups, but not processes. Hence, if we running in the - * unified hierarchy and the container does the same, and we - * did not create a scope unit for the container move us and - * the container into two separate subcgroups. */ - - if (!unified_requested) - return 0; - - unified = cg_unified(); - if (unified < 0) - return log_error_errno(unified, "Failed to determine whether the unified hierarchy is used: %m"); - if (unified == 0) - return 0; - - r = cg_mask_supported(&supported); - if (r < 0) - return log_error_errno(r, "Failed to determine supported controllers: %m"); - - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &cgroup); - if (r < 0) - return log_error_errno(r, "Failed to get our control group: %m"); - - child = strjoina(cgroup, "/payload"); - r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, child, pid); - if (r < 0) - return log_error_errno(r, "Failed to create %s subcgroup: %m", child); - - child = strjoina(cgroup, "/supervisor"); - r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, child, 0); - if (r < 0) - return log_error_errno(r, "Failed to create %s subcgroup: %m", child); - - /* Try to enable as many controllers as possible for the new payload. */ - (void) cg_enable_everywhere(supported, supported, cgroup); - return 0; -} diff --git a/src/nspawn/nspawn-cgroup.h b/src/nspawn/nspawn-cgroup.h deleted file mode 100644 index 1ff35a299a..0000000000 --- a/src/nspawn/nspawn-cgroup.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 - -int chown_cgroup(pid_t pid, uid_t uid_shift); -int sync_cgroup(pid_t pid, bool unified_requested); -int create_subcgroup(pid_t pid, bool unified_requested); diff --git a/src/nspawn/nspawn-expose-ports.c b/src/nspawn/nspawn-expose-ports.c deleted file mode 100644 index 86124b8779..0000000000 --- a/src/nspawn/nspawn-expose-ports.c +++ /dev/null @@ -1,245 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 "sd-netlink.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "firewall-util.h" -#include "in-addr-util.h" -#include "local-addresses.h" -#include "netlink-util.h" -#include "nspawn-expose-ports.h" -#include "parse-util.h" -#include "socket-util.h" -#include "string-util.h" -#include "util.h" - -int expose_port_parse(ExposePort **l, const char *s) { - - const char *split, *e; - uint16_t container_port, host_port; - int protocol; - ExposePort *p; - int r; - - assert(l); - assert(s); - - if ((e = startswith(s, "tcp:"))) - protocol = IPPROTO_TCP; - else if ((e = startswith(s, "udp:"))) - protocol = IPPROTO_UDP; - else { - e = s; - protocol = IPPROTO_TCP; - } - - split = strchr(e, ':'); - if (split) { - char v[split - e + 1]; - - memcpy(v, e, split - e); - v[split - e] = 0; - - r = safe_atou16(v, &host_port); - if (r < 0 || host_port <= 0) - return -EINVAL; - - r = safe_atou16(split + 1, &container_port); - } else { - r = safe_atou16(e, &container_port); - host_port = container_port; - } - - if (r < 0 || container_port <= 0) - return -EINVAL; - - LIST_FOREACH(ports, p, *l) - if (p->protocol == protocol && p->host_port == host_port) - return -EEXIST; - - p = new(ExposePort, 1); - if (!p) - return -ENOMEM; - - p->protocol = protocol; - p->host_port = host_port; - p->container_port = container_port; - - LIST_PREPEND(ports, *l, p); - - return 0; -} - -void expose_port_free_all(ExposePort *p) { - - while (p) { - ExposePort *q = p; - LIST_REMOVE(ports, p, q); - free(q); - } -} - -int expose_port_flush(ExposePort* l, union in_addr_union *exposed) { - ExposePort *p; - int r, af = AF_INET; - - assert(exposed); - - if (!l) - return 0; - - if (in_addr_is_null(af, exposed)) - return 0; - - log_debug("Lost IP address."); - - LIST_FOREACH(ports, p, l) { - r = fw_add_local_dnat(false, - af, - p->protocol, - NULL, - NULL, 0, - NULL, 0, - p->host_port, - exposed, - p->container_port, - NULL); - if (r < 0) - log_warning_errno(r, "Failed to modify firewall: %m"); - } - - *exposed = IN_ADDR_NULL; - return 0; -} - -int expose_port_execute(sd_netlink *rtnl, ExposePort *l, union in_addr_union *exposed) { - _cleanup_free_ struct local_address *addresses = NULL; - _cleanup_free_ char *pretty = NULL; - union in_addr_union new_exposed; - ExposePort *p; - bool add; - int af = AF_INET, r; - - assert(exposed); - - /* Invoked each time an address is added or removed inside the - * container */ - - if (!l) - return 0; - - r = local_addresses(rtnl, 0, af, &addresses); - if (r < 0) - return log_error_errno(r, "Failed to enumerate local addresses: %m"); - - add = r > 0 && - addresses[0].family == af && - addresses[0].scope < RT_SCOPE_LINK; - - if (!add) - return expose_port_flush(l, exposed); - - new_exposed = addresses[0].address; - if (in_addr_equal(af, exposed, &new_exposed)) - return 0; - - in_addr_to_string(af, &new_exposed, &pretty); - log_debug("New container IP is %s.", strna(pretty)); - - LIST_FOREACH(ports, p, l) { - - r = fw_add_local_dnat(true, - af, - p->protocol, - NULL, - NULL, 0, - NULL, 0, - p->host_port, - &new_exposed, - p->container_port, - in_addr_is_null(af, exposed) ? NULL : exposed); - if (r < 0) - log_warning_errno(r, "Failed to modify firewall: %m"); - } - - *exposed = new_exposed; - return 0; -} - -int expose_port_send_rtnl(int send_fd) { - _cleanup_close_ int fd = -1; - int r; - - assert(send_fd >= 0); - - fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_ROUTE); - if (fd < 0) - return log_error_errno(errno, "Failed to allocate container netlink: %m"); - - /* Store away the fd in the socket, so that it stays open as - * long as we run the child */ - r = send_one_fd(send_fd, fd, 0); - if (r < 0) - return log_error_errno(r, "Failed to send netlink fd: %m"); - - return 0; -} - -int expose_port_watch_rtnl( - sd_event *event, - int recv_fd, - sd_netlink_message_handler_t handler, - union in_addr_union *exposed, - sd_netlink **ret) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - int fd, r; - - assert(event); - assert(recv_fd >= 0); - assert(ret); - - fd = receive_one_fd(recv_fd, 0); - if (fd < 0) - return log_error_errno(fd, "Failed to recv netlink fd: %m"); - - r = sd_netlink_open_fd(&rtnl, fd); - if (r < 0) { - safe_close(fd); - return log_error_errno(r, "Failed to create rtnl object: %m"); - } - - r = sd_netlink_add_match(rtnl, RTM_NEWADDR, handler, exposed); - if (r < 0) - return log_error_errno(r, "Failed to subscribe to RTM_NEWADDR messages: %m"); - - r = sd_netlink_add_match(rtnl, RTM_DELADDR, handler, exposed); - if (r < 0) - return log_error_errno(r, "Failed to subscribe to RTM_DELADDR messages: %m"); - - r = sd_netlink_attach_event(rtnl, event, 0); - if (r < 0) - return log_error_errno(r, "Failed to add to even loop: %m"); - - *ret = rtnl; - rtnl = NULL; - - return 0; -} diff --git a/src/nspawn/nspawn-expose-ports.h b/src/nspawn/nspawn-expose-ports.h deleted file mode 100644 index 741ad9765c..0000000000 --- a/src/nspawn/nspawn-expose-ports.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 "sd-event.h" -#include "sd-netlink.h" - -#include "in-addr-util.h" -#include "list.h" - -typedef struct ExposePort { - int protocol; - uint16_t host_port; - uint16_t container_port; - LIST_FIELDS(struct ExposePort, ports); -} ExposePort; - -void expose_port_free_all(ExposePort *p); -int expose_port_parse(ExposePort **l, const char *s); - -int expose_port_watch_rtnl(sd_event *event, int recv_fd, sd_netlink_message_handler_t handler, union in_addr_union *exposed, sd_netlink **ret); -int expose_port_send_rtnl(int send_fd); - -int expose_port_execute(sd_netlink *rtnl, ExposePort *l, union in_addr_union *exposed); -int expose_port_flush(ExposePort* l, union in_addr_union *exposed); diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf deleted file mode 100644 index 2b5d452662..0000000000 --- a/src/nspawn/nspawn-gperf.gperf +++ /dev/null @@ -1,44 +0,0 @@ -%{ -#include -#include "conf-parser.h" -#include "nspawn-settings.h" -#include "nspawn-expose-ports.h" -%} -struct ConfigPerfItem; -%null_strings -%language=ANSI-C -%define slot-name section_and_lvalue -%define hash-function-name nspawn_gperf_hash -%define lookup-function-name nspawn_gperf_lookup -%readonly-tables -%omit-struct-type -%struct-type -%includes -%% -Exec.Boot, config_parse_boot, 0, 0 -Exec.ProcessTwo, config_parse_pid2, 0, 0 -Exec.Parameters, config_parse_strv, 0, offsetof(Settings, parameters) -Exec.Environment, config_parse_strv, 0, offsetof(Settings, environment) -Exec.User, config_parse_string, 0, offsetof(Settings, user) -Exec.Capability, config_parse_capability, 0, offsetof(Settings, capability) -Exec.DropCapability, config_parse_capability, 0, offsetof(Settings, drop_capability) -Exec.KillSignal, config_parse_signal, 0, offsetof(Settings, kill_signal) -Exec.Personality, config_parse_personality, 0, offsetof(Settings, personality) -Exec.MachineID, config_parse_id128, 0, offsetof(Settings, machine_id) -Exec.WorkingDirectory, config_parse_path, 0, offsetof(Settings, working_directory) -Exec.PrivateUsers, config_parse_private_users, 0, 0 -Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only) -Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode) -Files.Bind, config_parse_bind, 0, 0 -Files.BindReadOnly, config_parse_bind, 1, 0 -Files.TemporaryFileSystem, config_parse_tmpfs, 0, 0 -Files.PrivateUsersChown, config_parse_tristate, 0, offsetof(Settings, userns_chown) -Network.Private, config_parse_tristate, 0, offsetof(Settings, private_network) -Network.Interface, config_parse_strv, 0, offsetof(Settings, network_interfaces) -Network.MACVLAN, config_parse_strv, 0, offsetof(Settings, network_macvlan) -Network.IPVLAN, config_parse_strv, 0, offsetof(Settings, network_ipvlan) -Network.VirtualEthernet, config_parse_tristate, 0, offsetof(Settings, network_veth) -Network.VirtualEthernetExtra, config_parse_veth_extra, 0, 0 -Network.Bridge, config_parse_ifname, 0, offsetof(Settings, network_bridge) -Network.Zone, config_parse_network_zone, 0, 0 -Network.Port, config_parse_expose_port, 0, 0 diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c deleted file mode 100644 index 8e2d2d543c..0000000000 --- a/src/nspawn/nspawn-mount.c +++ /dev/null @@ -1,943 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 - -#include "alloc-util.h" -#include "cgroup-util.h" -#include "escape.h" -#include "fs-util.h" -#include "label.h" -#include "mkdir.h" -#include "mount-util.h" -#include "nspawn-mount.h" -#include "parse-util.h" -#include "path-util.h" -#include "rm-rf.h" -#include "set.h" -#include "stat-util.h" -#include "string-util.h" -#include "strv.h" -#include "user-util.h" -#include "util.h" - -CustomMount* custom_mount_add(CustomMount **l, unsigned *n, CustomMountType t) { - CustomMount *c, *ret; - - assert(l); - assert(n); - assert(t >= 0); - assert(t < _CUSTOM_MOUNT_TYPE_MAX); - - c = realloc(*l, (*n + 1) * sizeof(CustomMount)); - if (!c) - return NULL; - - *l = c; - ret = *l + *n; - (*n)++; - - *ret = (CustomMount) { .type = t }; - - return ret; -} - -void custom_mount_free_all(CustomMount *l, unsigned n) { - unsigned i; - - for (i = 0; i < n; i++) { - CustomMount *m = l + i; - - free(m->source); - free(m->destination); - free(m->options); - - if (m->work_dir) { - (void) rm_rf(m->work_dir, REMOVE_ROOT|REMOVE_PHYSICAL); - free(m->work_dir); - } - - strv_free(m->lower); - } - - free(l); -} - -int custom_mount_compare(const void *a, const void *b) { - const CustomMount *x = a, *y = b; - int r; - - r = path_compare(x->destination, y->destination); - if (r != 0) - return r; - - if (x->type < y->type) - return -1; - if (x->type > y->type) - return 1; - - return 0; -} - -int bind_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only) { - _cleanup_free_ char *source = NULL, *destination = NULL, *opts = NULL; - const char *p = s; - CustomMount *m; - int r; - - assert(l); - assert(n); - - r = extract_many_words(&p, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination, NULL); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - if (r == 1) { - destination = strdup(source); - if (!destination) - return -ENOMEM; - } - - if (r == 2 && !isempty(p)) { - opts = strdup(p); - if (!opts) - return -ENOMEM; - } - - if (!path_is_absolute(source)) - return -EINVAL; - - if (!path_is_absolute(destination)) - return -EINVAL; - - m = custom_mount_add(l, n, CUSTOM_MOUNT_BIND); - if (!m) - return log_oom(); - - m->source = source; - m->destination = destination; - m->read_only = read_only; - m->options = opts; - - source = destination = opts = NULL; - return 0; -} - -int tmpfs_mount_parse(CustomMount **l, unsigned *n, const char *s) { - _cleanup_free_ char *path = NULL, *opts = NULL; - const char *p = s; - CustomMount *m; - int r; - - assert(l); - assert(n); - assert(s); - - r = extract_first_word(&p, &path, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - if (isempty(p)) - opts = strdup("mode=0755"); - else - opts = strdup(p); - if (!opts) - return -ENOMEM; - - if (!path_is_absolute(path)) - return -EINVAL; - - m = custom_mount_add(l, n, CUSTOM_MOUNT_TMPFS); - if (!m) - return -ENOMEM; - - m->destination = path; - m->options = opts; - - path = opts = NULL; - return 0; -} - -static int tmpfs_patch_options( - const char *options, - bool userns, uid_t uid_shift, uid_t uid_range, - const char *selinux_apifs_context, - char **ret) { - - char *buf = NULL; - - if (userns && uid_shift != 0) { - assert(uid_shift != UID_INVALID); - - if (options) - (void) asprintf(&buf, "%s,uid=" UID_FMT ",gid=" UID_FMT, options, uid_shift, uid_shift); - else - (void) asprintf(&buf, "uid=" UID_FMT ",gid=" UID_FMT, uid_shift, uid_shift); - if (!buf) - return -ENOMEM; - - options = buf; - } - -#ifdef HAVE_SELINUX - if (selinux_apifs_context) { - char *t; - - if (options) - t = strjoin(options, ",context=\"", selinux_apifs_context, "\"", NULL); - else - t = strjoin("context=\"", selinux_apifs_context, "\"", NULL); - if (!t) { - free(buf); - return -ENOMEM; - } - - free(buf); - buf = t; - } -#endif - - *ret = buf; - return !!buf; -} - -int mount_sysfs(const char *dest) { - const char *full, *top, *x; - int r; - - top = prefix_roota(dest, "/sys"); - r = path_check_fstype(top, SYSFS_MAGIC); - if (r < 0) - return log_error_errno(r, "Failed to determine filesystem type of %s: %m", top); - /* /sys might already be mounted as sysfs by the outer child in the - * !netns case. In this case, it's all good. Don't touch it because we - * don't have the right to do so, see https://github.com/systemd/systemd/issues/1555. - */ - if (r > 0) - return 0; - - full = prefix_roota(top, "/full"); - - (void) mkdir(full, 0755); - - if (mount("sysfs", full, "sysfs", MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) < 0) - return log_error_errno(errno, "Failed to mount sysfs to %s: %m", full); - - FOREACH_STRING(x, "block", "bus", "class", "dev", "devices", "kernel") { - _cleanup_free_ char *from = NULL, *to = NULL; - - from = prefix_root(full, x); - if (!from) - return log_oom(); - - to = prefix_root(top, x); - if (!to) - return log_oom(); - - (void) mkdir(to, 0755); - - if (mount(from, to, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Failed to mount /sys/%s into place: %m", x); - - if (mount(NULL, to, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, NULL) < 0) - return log_error_errno(errno, "Failed to mount /sys/%s read-only: %m", x); - } - - if (umount(full) < 0) - return log_error_errno(errno, "Failed to unmount %s: %m", full); - - if (rmdir(full) < 0) - return log_error_errno(errno, "Failed to remove %s: %m", full); - - x = prefix_roota(top, "/fs/kdbus"); - (void) mkdir(x, 0755); - - if (mount(NULL, top, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, NULL) < 0) - return log_error_errno(errno, "Failed to make %s read-only: %m", top); - - return 0; -} - -int mount_all(const char *dest, - bool use_userns, bool in_userns, - bool use_netns, - uid_t uid_shift, uid_t uid_range, - const char *selinux_apifs_context) { - - typedef struct MountPoint { - const char *what; - const char *where; - const char *type; - const char *options; - unsigned long flags; - bool fatal; - bool in_userns; - bool use_netns; - } MountPoint; - - static const MountPoint mount_table[] = { - { "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, true, true, false }, - { "/proc/sys", "/proc/sys", NULL, NULL, MS_BIND, true, true, false }, /* Bind mount first */ - { NULL, "/proc/sys", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, true, true, false }, /* Then, make it r/o */ - { "tmpfs", "/sys", "tmpfs", "mode=755", MS_NOSUID|MS_NOEXEC|MS_NODEV, true, false, true }, - { "sysfs", "/sys", "sysfs", NULL, MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, true, false, false }, - { "tmpfs", "/dev", "tmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME, true, false, false }, - { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true, false, false }, - { "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true, false, false }, - { "tmpfs", "/tmp", "tmpfs", "mode=1777", MS_STRICTATIME, true, false, false }, -#ifdef HAVE_SELINUX - { "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND, false, false, false }, /* Bind mount first */ - { NULL, "/sys/fs/selinux", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, false, false, false }, /* Then, make it r/o */ -#endif - }; - - unsigned k; - int r; - - for (k = 0; k < ELEMENTSOF(mount_table); k++) { - _cleanup_free_ char *where = NULL, *options = NULL; - const char *o; - - if (in_userns != mount_table[k].in_userns) - continue; - - if (!use_netns && mount_table[k].use_netns) - continue; - - where = prefix_root(dest, mount_table[k].where); - if (!where) - return log_oom(); - - r = path_is_mount_point(where, AT_SYMLINK_FOLLOW); - if (r < 0 && r != -ENOENT) - return log_error_errno(r, "Failed to detect whether %s is a mount point: %m", where); - - /* Skip this entry if it is not a remount. */ - if (mount_table[k].what && r > 0) - continue; - - r = mkdir_p(where, 0755); - if (r < 0) { - if (mount_table[k].fatal) - return log_error_errno(r, "Failed to create directory %s: %m", where); - - log_warning_errno(r, "Failed to create directory %s: %m", where); - continue; - } - - o = mount_table[k].options; - if (streq_ptr(mount_table[k].type, "tmpfs")) { - r = tmpfs_patch_options(o, use_userns, uid_shift, uid_range, selinux_apifs_context, &options); - if (r < 0) - return log_oom(); - if (r > 0) - o = options; - } - - if (mount(mount_table[k].what, - where, - mount_table[k].type, - mount_table[k].flags, - o) < 0) { - - if (mount_table[k].fatal) - return log_error_errno(errno, "mount(%s) failed: %m", where); - - log_warning_errno(errno, "mount(%s) failed, ignoring: %m", where); - } - } - - return 0; -} - -static int parse_mount_bind_options(const char *options, unsigned long *mount_flags, char **mount_opts) { - const char *p = options; - unsigned long flags = *mount_flags; - char *opts = NULL; - - assert(options); - - for (;;) { - _cleanup_free_ char *word = NULL; - int r = extract_first_word(&p, &word, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to extract mount option: %m"); - if (r == 0) - break; - - if (streq(word, "rbind")) - flags |= MS_REC; - else if (streq(word, "norbind")) - flags &= ~MS_REC; - else { - log_error("Invalid bind mount option: %s", word); - return -EINVAL; - } - } - - *mount_flags = flags; - /* in the future mount_opts will hold string options for mount(2) */ - *mount_opts = opts; - - return 0; -} - -static int mount_bind(const char *dest, CustomMount *m) { - struct stat source_st, dest_st; - const char *where; - unsigned long mount_flags = MS_BIND | MS_REC; - _cleanup_free_ char *mount_opts = NULL; - int r; - - assert(m); - - if (m->options) { - r = parse_mount_bind_options(m->options, &mount_flags, &mount_opts); - if (r < 0) - return r; - } - - if (stat(m->source, &source_st) < 0) - return log_error_errno(errno, "Failed to stat %s: %m", m->source); - - where = prefix_roota(dest, m->destination); - - if (stat(where, &dest_st) >= 0) { - if (S_ISDIR(source_st.st_mode) && !S_ISDIR(dest_st.st_mode)) { - log_error("Cannot bind mount directory %s on file %s.", m->source, where); - return -EINVAL; - } - - if (!S_ISDIR(source_st.st_mode) && S_ISDIR(dest_st.st_mode)) { - log_error("Cannot bind mount file %s on directory %s.", m->source, where); - return -EINVAL; - } - - } else if (errno == ENOENT) { - r = mkdir_parents_label(where, 0755); - if (r < 0) - return log_error_errno(r, "Failed to make parents of %s: %m", where); - - /* Create the mount point. Any non-directory file can be - * mounted on any non-directory file (regular, fifo, socket, - * char, block). - */ - if (S_ISDIR(source_st.st_mode)) - r = mkdir_label(where, 0755); - else - r = touch(where); - if (r < 0) - return log_error_errno(r, "Failed to create mount point %s: %m", where); - - } else { - return log_error_errno(errno, "Failed to stat %s: %m", where); - } - - if (mount(m->source, where, NULL, mount_flags, mount_opts) < 0) - return log_error_errno(errno, "mount(%s) failed: %m", where); - - if (m->read_only) { - r = bind_remount_recursive(where, true); - if (r < 0) - return log_error_errno(r, "Read-only bind mount failed: %m"); - } - - return 0; -} - -static int mount_tmpfs( - const char *dest, - CustomMount *m, - bool userns, uid_t uid_shift, uid_t uid_range, - const char *selinux_apifs_context) { - - const char *where, *options; - _cleanup_free_ char *buf = NULL; - int r; - - assert(dest); - assert(m); - - where = prefix_roota(dest, m->destination); - - r = mkdir_p_label(where, 0755); - if (r < 0 && r != -EEXIST) - return log_error_errno(r, "Creating mount point for tmpfs %s failed: %m", where); - - r = tmpfs_patch_options(m->options, userns, uid_shift, uid_range, selinux_apifs_context, &buf); - if (r < 0) - return log_oom(); - options = r > 0 ? buf : m->options; - - if (mount("tmpfs", where, "tmpfs", MS_NODEV|MS_STRICTATIME, options) < 0) - return log_error_errno(errno, "tmpfs mount to %s failed: %m", where); - - return 0; -} - -static char *joined_and_escaped_lower_dirs(char * const *lower) { - _cleanup_strv_free_ char **sv = NULL; - - sv = strv_copy(lower); - if (!sv) - return NULL; - - strv_reverse(sv); - - if (!strv_shell_escape(sv, ",:")) - return NULL; - - return strv_join(sv, ":"); -} - -static int mount_overlay(const char *dest, CustomMount *m) { - _cleanup_free_ char *lower = NULL; - const char *where, *options; - int r; - - assert(dest); - assert(m); - - where = prefix_roota(dest, m->destination); - - r = mkdir_label(where, 0755); - if (r < 0 && r != -EEXIST) - return log_error_errno(r, "Creating mount point for overlay %s failed: %m", where); - - (void) mkdir_p_label(m->source, 0755); - - lower = joined_and_escaped_lower_dirs(m->lower); - if (!lower) - return log_oom(); - - if (m->read_only) { - _cleanup_free_ char *escaped_source = NULL; - - escaped_source = shell_escape(m->source, ",:"); - if (!escaped_source) - return log_oom(); - - options = strjoina("lowerdir=", escaped_source, ":", lower); - } else { - _cleanup_free_ char *escaped_source = NULL, *escaped_work_dir = NULL; - - assert(m->work_dir); - (void) mkdir_label(m->work_dir, 0700); - - escaped_source = shell_escape(m->source, ",:"); - if (!escaped_source) - return log_oom(); - escaped_work_dir = shell_escape(m->work_dir, ",:"); - if (!escaped_work_dir) - return log_oom(); - - options = strjoina("lowerdir=", lower, ",upperdir=", escaped_source, ",workdir=", escaped_work_dir); - } - - if (mount("overlay", where, "overlay", m->read_only ? MS_RDONLY : 0, options) < 0) - return log_error_errno(errno, "overlay mount to %s failed: %m", where); - - return 0; -} - -int mount_custom( - const char *dest, - CustomMount *mounts, unsigned n, - bool userns, uid_t uid_shift, uid_t uid_range, - const char *selinux_apifs_context) { - - unsigned i; - int r; - - assert(dest); - - for (i = 0; i < n; i++) { - CustomMount *m = mounts + i; - - switch (m->type) { - - case CUSTOM_MOUNT_BIND: - r = mount_bind(dest, m); - break; - - case CUSTOM_MOUNT_TMPFS: - r = mount_tmpfs(dest, m, userns, uid_shift, uid_range, selinux_apifs_context); - break; - - case CUSTOM_MOUNT_OVERLAY: - r = mount_overlay(dest, m); - break; - - default: - assert_not_reached("Unknown custom mount type"); - } - - if (r < 0) - return r; - } - - return 0; -} - -static int mount_legacy_cgroup_hierarchy(const char *dest, const char *controller, const char *hierarchy, bool read_only) { - char *to; - int r; - - to = strjoina(strempty(dest), "/sys/fs/cgroup/", hierarchy); - - r = path_is_mount_point(to, 0); - if (r < 0 && r != -ENOENT) - return log_error_errno(r, "Failed to determine if %s is mounted already: %m", to); - if (r > 0) - return 0; - - mkdir_p(to, 0755); - - /* The superblock mount options of the mount point need to be - * identical to the hosts', and hence writable... */ - if (mount("cgroup", to, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, controller) < 0) - return log_error_errno(errno, "Failed to mount to %s: %m", to); - - /* ... hence let's only make the bind mount read-only, not the - * superblock. */ - if (read_only) { - if (mount(NULL, to, NULL, MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, NULL) < 0) - return log_error_errno(errno, "Failed to remount %s read-only: %m", to); - } - return 1; -} - -static int mount_legacy_cgroups( - const char *dest, - bool userns, uid_t uid_shift, uid_t uid_range, - const char *selinux_apifs_context) { - - _cleanup_set_free_free_ Set *controllers = NULL; - const char *cgroup_root; - int r; - - cgroup_root = prefix_roota(dest, "/sys/fs/cgroup"); - - (void) mkdir_p(cgroup_root, 0755); - - /* Mount a tmpfs to /sys/fs/cgroup if it's not mounted there yet. */ - r = path_is_mount_point(cgroup_root, AT_SYMLINK_FOLLOW); - if (r < 0) - return log_error_errno(r, "Failed to determine if /sys/fs/cgroup is already mounted: %m"); - if (r == 0) { - _cleanup_free_ char *options = NULL; - - r = tmpfs_patch_options("mode=755", userns, uid_shift, uid_range, selinux_apifs_context, &options); - if (r < 0) - return log_oom(); - - if (mount("tmpfs", cgroup_root, "tmpfs", MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, options) < 0) - return log_error_errno(errno, "Failed to mount /sys/fs/cgroup: %m"); - } - - if (cg_unified() > 0) - goto skip_controllers; - - controllers = set_new(&string_hash_ops); - if (!controllers) - return log_oom(); - - r = cg_kernel_controllers(controllers); - if (r < 0) - return log_error_errno(r, "Failed to determine cgroup controllers: %m"); - - for (;;) { - _cleanup_free_ char *controller = NULL, *origin = NULL, *combined = NULL; - - controller = set_steal_first(controllers); - if (!controller) - break; - - origin = prefix_root("/sys/fs/cgroup/", controller); - if (!origin) - return log_oom(); - - r = readlink_malloc(origin, &combined); - if (r == -EINVAL) { - /* Not a symbolic link, but directly a single cgroup hierarchy */ - - r = mount_legacy_cgroup_hierarchy(dest, controller, controller, true); - if (r < 0) - return r; - - } else if (r < 0) - return log_error_errno(r, "Failed to read link %s: %m", origin); - else { - _cleanup_free_ char *target = NULL; - - target = prefix_root(dest, origin); - if (!target) - return log_oom(); - - /* A symbolic link, a combination of controllers in one hierarchy */ - - if (!filename_is_valid(combined)) { - log_warning("Ignoring invalid combined hierarchy %s.", combined); - continue; - } - - r = mount_legacy_cgroup_hierarchy(dest, combined, combined, true); - if (r < 0) - return r; - - r = symlink_idempotent(combined, target); - if (r == -EINVAL) { - log_error("Invalid existing symlink for combined hierarchy"); - return r; - } - if (r < 0) - return log_error_errno(r, "Failed to create symlink for combined hierarchy: %m"); - } - } - -skip_controllers: - r = mount_legacy_cgroup_hierarchy(dest, "none,name=systemd,xattr", "systemd", false); - if (r < 0) - return r; - - if (mount(NULL, cgroup_root, NULL, MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, "mode=755") < 0) - return log_error_errno(errno, "Failed to remount %s read-only: %m", cgroup_root); - - return 0; -} - -static int mount_unified_cgroups(const char *dest) { - const char *p; - int r; - - assert(dest); - - p = prefix_roota(dest, "/sys/fs/cgroup"); - - (void) mkdir_p(p, 0755); - - r = path_is_mount_point(p, AT_SYMLINK_FOLLOW); - if (r < 0) - return log_error_errno(r, "Failed to determine if %s is mounted already: %m", p); - if (r > 0) { - p = prefix_roota(dest, "/sys/fs/cgroup/cgroup.procs"); - if (access(p, F_OK) >= 0) - return 0; - if (errno != ENOENT) - return log_error_errno(errno, "Failed to determine if mount point %s contains the unified cgroup hierarchy: %m", p); - - log_error("%s is already mounted but not a unified cgroup hierarchy. Refusing.", p); - return -EINVAL; - } - - if (mount("cgroup", p, "cgroup2", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) < 0) - return log_error_errno(errno, "Failed to mount unified cgroup hierarchy to %s: %m", p); - - return 0; -} - -int mount_cgroups( - const char *dest, - bool unified_requested, - bool userns, uid_t uid_shift, uid_t uid_range, - const char *selinux_apifs_context) { - - if (unified_requested) - return mount_unified_cgroups(dest); - else - return mount_legacy_cgroups(dest, userns, uid_shift, uid_range, selinux_apifs_context); -} - -int mount_systemd_cgroup_writable( - const char *dest, - bool unified_requested) { - - _cleanup_free_ char *own_cgroup_path = NULL; - const char *systemd_root, *systemd_own; - int r; - - assert(dest); - - r = cg_pid_get_path(NULL, 0, &own_cgroup_path); - if (r < 0) - return log_error_errno(r, "Failed to determine our own cgroup path: %m"); - - /* If we are living in the top-level, then there's nothing to do... */ - if (path_equal(own_cgroup_path, "/")) - return 0; - - if (unified_requested) { - systemd_own = strjoina(dest, "/sys/fs/cgroup", own_cgroup_path); - systemd_root = prefix_roota(dest, "/sys/fs/cgroup"); - } else { - systemd_own = strjoina(dest, "/sys/fs/cgroup/systemd", own_cgroup_path); - systemd_root = prefix_roota(dest, "/sys/fs/cgroup/systemd"); - } - - /* Make our own cgroup a (writable) bind mount */ - if (mount(systemd_own, systemd_own, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Failed to turn %s into a bind mount: %m", own_cgroup_path); - - /* And then remount the systemd cgroup root read-only */ - if (mount(NULL, systemd_root, NULL, MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, NULL) < 0) - return log_error_errno(errno, "Failed to mount cgroup root read-only: %m"); - - return 0; -} - -int setup_volatile_state( - const char *directory, - VolatileMode mode, - bool userns, uid_t uid_shift, uid_t uid_range, - const char *selinux_apifs_context) { - - _cleanup_free_ char *buf = NULL; - const char *p, *options; - int r; - - assert(directory); - - if (mode != VOLATILE_STATE) - return 0; - - /* --volatile=state means we simply overmount /var - with a tmpfs, and the rest read-only. */ - - r = bind_remount_recursive(directory, true); - if (r < 0) - return log_error_errno(r, "Failed to remount %s read-only: %m", directory); - - p = prefix_roota(directory, "/var"); - r = mkdir(p, 0755); - if (r < 0 && errno != EEXIST) - return log_error_errno(errno, "Failed to create %s: %m", directory); - - options = "mode=755"; - r = tmpfs_patch_options(options, userns, uid_shift, uid_range, selinux_apifs_context, &buf); - if (r < 0) - return log_oom(); - if (r > 0) - options = buf; - - if (mount("tmpfs", p, "tmpfs", MS_STRICTATIME, options) < 0) - return log_error_errno(errno, "Failed to mount tmpfs to /var: %m"); - - return 0; -} - -int setup_volatile( - const char *directory, - VolatileMode mode, - bool userns, uid_t uid_shift, uid_t uid_range, - const char *selinux_apifs_context) { - - bool tmpfs_mounted = false, bind_mounted = false; - char template[] = "/tmp/nspawn-volatile-XXXXXX"; - _cleanup_free_ char *buf = NULL; - const char *f, *t, *options; - int r; - - assert(directory); - - if (mode != VOLATILE_YES) - return 0; - - /* --volatile=yes means we mount a tmpfs to the root dir, and - the original /usr to use inside it, and that read-only. */ - - if (!mkdtemp(template)) - return log_error_errno(errno, "Failed to create temporary directory: %m"); - - options = "mode=755"; - r = tmpfs_patch_options(options, userns, uid_shift, uid_range, selinux_apifs_context, &buf); - if (r < 0) - return log_oom(); - if (r > 0) - options = buf; - - if (mount("tmpfs", template, "tmpfs", MS_STRICTATIME, options) < 0) { - r = log_error_errno(errno, "Failed to mount tmpfs for root directory: %m"); - goto fail; - } - - tmpfs_mounted = true; - - f = prefix_roota(directory, "/usr"); - t = prefix_roota(template, "/usr"); - - r = mkdir(t, 0755); - if (r < 0 && errno != EEXIST) { - r = log_error_errno(errno, "Failed to create %s: %m", t); - goto fail; - } - - if (mount(f, t, NULL, MS_BIND|MS_REC, NULL) < 0) { - r = log_error_errno(errno, "Failed to create /usr bind mount: %m"); - goto fail; - } - - bind_mounted = true; - - r = bind_remount_recursive(t, true); - if (r < 0) { - log_error_errno(r, "Failed to remount %s read-only: %m", t); - goto fail; - } - - if (mount(template, directory, NULL, MS_MOVE, NULL) < 0) { - r = log_error_errno(errno, "Failed to move root mount: %m"); - goto fail; - } - - (void) rmdir(template); - - return 0; - -fail: - if (bind_mounted) - (void) umount(t); - - if (tmpfs_mounted) - (void) umount(template); - (void) rmdir(template); - return r; -} - -VolatileMode volatile_mode_from_string(const char *s) { - int b; - - if (isempty(s)) - return _VOLATILE_MODE_INVALID; - - b = parse_boolean(s); - if (b > 0) - return VOLATILE_YES; - if (b == 0) - return VOLATILE_NO; - - if (streq(s, "state")) - return VOLATILE_STATE; - - return _VOLATILE_MODE_INVALID; -} diff --git a/src/nspawn/nspawn-mount.h b/src/nspawn/nspawn-mount.h deleted file mode 100644 index 0daf145412..0000000000 --- a/src/nspawn/nspawn-mount.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 - -typedef enum VolatileMode { - VOLATILE_NO, - VOLATILE_YES, - VOLATILE_STATE, - _VOLATILE_MODE_MAX, - _VOLATILE_MODE_INVALID = -1 -} VolatileMode; - -typedef enum CustomMountType { - CUSTOM_MOUNT_BIND, - CUSTOM_MOUNT_TMPFS, - CUSTOM_MOUNT_OVERLAY, - _CUSTOM_MOUNT_TYPE_MAX, - _CUSTOM_MOUNT_TYPE_INVALID = -1 -} CustomMountType; - -typedef struct CustomMount { - CustomMountType type; - bool read_only; - char *source; /* for overlayfs this is the upper directory */ - char *destination; - char *options; - char *work_dir; - char **lower; -} CustomMount; - -CustomMount* custom_mount_add(CustomMount **l, unsigned *n, CustomMountType t); - -void custom_mount_free_all(CustomMount *l, unsigned n); -int bind_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only); -int tmpfs_mount_parse(CustomMount **l, unsigned *n, const char *s); - -int custom_mount_compare(const void *a, const void *b); - -int mount_all(const char *dest, bool use_userns, bool in_userns, bool use_netns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); -int mount_sysfs(const char *dest); - -int mount_cgroups(const char *dest, bool unified_requested, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); -int mount_systemd_cgroup_writable(const char *dest, bool unified_requested); - -int mount_custom(const char *dest, CustomMount *mounts, unsigned n, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); - -int setup_volatile(const char *directory, VolatileMode mode, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); -int setup_volatile_state(const char *directory, VolatileMode mode, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); - -VolatileMode volatile_mode_from_string(const char *s); diff --git a/src/nspawn/nspawn-network.c b/src/nspawn/nspawn-network.c deleted file mode 100644 index 8da47a2ca6..0000000000 --- a/src/nspawn/nspawn-network.c +++ /dev/null @@ -1,694 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 - -#include "libudev.h" -#include "sd-id128.h" -#include "sd-netlink.h" - -#include "alloc-util.h" -#include "ether-addr-util.h" -#include "lockfile-util.h" -#include "netlink-util.h" -#include "nspawn-network.h" -#include "siphash24.h" -#include "socket-util.h" -#include "stat-util.h" -#include "string-util.h" -#include "udev-util.h" -#include "util.h" - -#define HOST_HASH_KEY SD_ID128_MAKE(1a,37,6f,c7,46,ec,45,0b,ad,a3,d5,31,06,60,5d,b1) -#define CONTAINER_HASH_KEY SD_ID128_MAKE(c3,c4,f9,19,b5,57,b2,1c,e6,cf,14,27,03,9c,ee,a2) -#define VETH_EXTRA_HOST_HASH_KEY SD_ID128_MAKE(48,c7,f6,b7,ea,9d,4c,9e,b7,28,d4,de,91,d5,bf,66) -#define VETH_EXTRA_CONTAINER_HASH_KEY SD_ID128_MAKE(af,50,17,61,ce,f9,4d,35,84,0d,2b,20,54,be,ce,59) -#define MACVLAN_HASH_KEY SD_ID128_MAKE(00,13,6d,bc,66,83,44,81,bb,0c,f9,51,1f,24,a6,6f) - -static int remove_one_link(sd_netlink *rtnl, const char *name) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - int r; - - if (isempty(name)) - return 0; - - r = sd_rtnl_message_new_link(rtnl, &m, RTM_DELLINK, 0); - if (r < 0) - return log_error_errno(r, "Failed to allocate netlink message: %m"); - - r = sd_netlink_message_append_string(m, IFLA_IFNAME, name); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface name: %m"); - - r = sd_netlink_call(rtnl, m, 0, NULL); - if (r == -ENODEV) /* Already gone */ - return 0; - if (r < 0) - return log_error_errno(r, "Failed to remove interface %s: %m", name); - - return 1; -} - -static int generate_mac( - const char *machine_name, - struct ether_addr *mac, - sd_id128_t hash_key, - uint64_t idx) { - - uint64_t result; - size_t l, sz; - uint8_t *v, *i; - int r; - - l = strlen(machine_name); - sz = sizeof(sd_id128_t) + l; - if (idx > 0) - sz += sizeof(idx); - - v = alloca(sz); - - /* fetch some persistent data unique to the host */ - r = sd_id128_get_machine((sd_id128_t*) v); - if (r < 0) - return r; - - /* combine with some data unique (on this host) to this - * container instance */ - i = mempcpy(v + sizeof(sd_id128_t), machine_name, l); - if (idx > 0) { - idx = htole64(idx); - memcpy(i, &idx, sizeof(idx)); - } - - /* Let's hash the host machine ID plus the container name. We - * use a fixed, but originally randomly created hash key here. */ - result = htole64(siphash24(v, sz, hash_key.bytes)); - - assert_cc(ETH_ALEN <= sizeof(result)); - memcpy(mac->ether_addr_octet, &result, ETH_ALEN); - - /* see eth_random_addr in the kernel */ - mac->ether_addr_octet[0] &= 0xfe; /* clear multicast bit */ - mac->ether_addr_octet[0] |= 0x02; /* set local assignment bit (IEEE802) */ - - return 0; -} - -static int add_veth( - sd_netlink *rtnl, - pid_t pid, - const char *ifname_host, - const struct ether_addr *mac_host, - const char *ifname_container, - const struct ether_addr *mac_container) { - - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - int r; - - assert(rtnl); - assert(ifname_host); - assert(mac_host); - assert(ifname_container); - assert(mac_container); - - r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); - if (r < 0) - return log_error_errno(r, "Failed to allocate netlink message: %m"); - - r = sd_netlink_message_append_string(m, IFLA_IFNAME, ifname_host); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface name: %m"); - - r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, mac_host); - if (r < 0) - return log_error_errno(r, "Failed to add netlink MAC address: %m"); - - r = sd_netlink_message_open_container(m, IFLA_LINKINFO); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "veth"); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_open_container(m, VETH_INFO_PEER); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_append_string(m, IFLA_IFNAME, ifname_container); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface name: %m"); - - r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, mac_container); - if (r < 0) - return log_error_errno(r, "Failed to add netlink MAC address: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); - if (r < 0) - return log_error_errno(r, "Failed to add netlink namespace field: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_call(rtnl, m, 0, NULL); - if (r < 0) - return log_error_errno(r, "Failed to add new veth interfaces (%s:%s): %m", ifname_host, ifname_container); - - return 0; -} - -int setup_veth(const char *machine_name, - pid_t pid, - char iface_name[IFNAMSIZ], - bool bridge) { - - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - struct ether_addr mac_host, mac_container; - int r, i; - - assert(machine_name); - assert(pid > 0); - assert(iface_name); - - /* Use two different interface name prefixes depending whether - * we are in bridge mode or not. */ - snprintf(iface_name, IFNAMSIZ - 1, "%s-%s", - bridge ? "vb" : "ve", machine_name); - - r = generate_mac(machine_name, &mac_container, CONTAINER_HASH_KEY, 0); - if (r < 0) - return log_error_errno(r, "Failed to generate predictable MAC address for container side: %m"); - - r = generate_mac(machine_name, &mac_host, HOST_HASH_KEY, 0); - if (r < 0) - return log_error_errno(r, "Failed to generate predictable MAC address for host side: %m"); - - r = sd_netlink_open(&rtnl); - if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); - - r = add_veth(rtnl, pid, iface_name, &mac_host, "host0", &mac_container); - if (r < 0) - return r; - - i = (int) if_nametoindex(iface_name); - if (i <= 0) - return log_error_errno(errno, "Failed to resolve interface %s: %m", iface_name); - - return i; -} - -int setup_veth_extra( - const char *machine_name, - pid_t pid, - char **pairs) { - - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - uint64_t idx = 0; - char **a, **b; - int r; - - assert(machine_name); - assert(pid > 0); - - if (strv_isempty(pairs)) - return 0; - - r = sd_netlink_open(&rtnl); - if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); - - STRV_FOREACH_PAIR(a, b, pairs) { - struct ether_addr mac_host, mac_container; - - r = generate_mac(machine_name, &mac_container, VETH_EXTRA_CONTAINER_HASH_KEY, idx); - if (r < 0) - return log_error_errno(r, "Failed to generate predictable MAC address for container side of extra veth link: %m"); - - r = generate_mac(machine_name, &mac_host, VETH_EXTRA_HOST_HASH_KEY, idx); - if (r < 0) - return log_error_errno(r, "Failed to generate predictable MAC address for container side of extra veth link: %m"); - - r = add_veth(rtnl, pid, *a, &mac_host, *b, &mac_container); - if (r < 0) - return r; - - idx++; - } - - return 0; -} - -static int join_bridge(sd_netlink *rtnl, const char *veth_name, const char *bridge_name) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - int r, bridge_ifi; - - assert(rtnl); - assert(veth_name); - assert(bridge_name); - - bridge_ifi = (int) if_nametoindex(bridge_name); - if (bridge_ifi <= 0) - return -errno; - - r = sd_rtnl_message_new_link(rtnl, &m, RTM_SETLINK, 0); - if (r < 0) - return r; - - r = sd_rtnl_message_link_set_flags(m, IFF_UP, IFF_UP); - if (r < 0) - return r; - - r = sd_netlink_message_append_string(m, IFLA_IFNAME, veth_name); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, IFLA_MASTER, bridge_ifi); - if (r < 0) - return r; - - r = sd_netlink_call(rtnl, m, 0, NULL); - if (r < 0) - return r; - - return bridge_ifi; -} - -static int create_bridge(sd_netlink *rtnl, const char *bridge_name) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - int r; - - r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); - if (r < 0) - return r; - - r = sd_netlink_message_append_string(m, IFLA_IFNAME, bridge_name); - if (r < 0) - return r; - - r = sd_netlink_message_open_container(m, IFLA_LINKINFO); - if (r < 0) - return r; - - r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "bridge"); - if (r < 0) - return r; - - r = sd_netlink_message_close_container(m); - if (r < 0) - return r; - - r = sd_netlink_message_close_container(m); - if (r < 0) - return r; - - r = sd_netlink_call(rtnl, m, 0, NULL); - if (r < 0) - return r; - - return 0; -} - -int setup_bridge(const char *veth_name, const char *bridge_name, bool create) { - _cleanup_release_lock_file_ LockFile bridge_lock = LOCK_FILE_INIT; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - int r, bridge_ifi; - unsigned n = 0; - - assert(veth_name); - assert(bridge_name); - - r = sd_netlink_open(&rtnl); - if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); - - if (create) { - /* We take a system-wide lock here, so that we can safely check whether there's still a member in the - * bridge before removing it, without risking interferance from other nspawn instances. */ - - r = make_lock_file("/run/systemd/nspawn-network-zone", LOCK_EX, &bridge_lock); - if (r < 0) - return log_error_errno(r, "Failed to take network zone lock: %m"); - } - - for (;;) { - bridge_ifi = join_bridge(rtnl, veth_name, bridge_name); - if (bridge_ifi >= 0) - return bridge_ifi; - if (bridge_ifi != -ENODEV || !create || n > 10) - return log_error_errno(bridge_ifi, "Failed to add interface %s to bridge %s: %m", veth_name, bridge_name); - - /* Count attempts, so that we don't enter an endless loop here. */ - n++; - - /* The bridge doesn't exist yet. Let's create it */ - r = create_bridge(rtnl, bridge_name); - if (r < 0) - return log_error_errno(r, "Failed to create bridge interface %s: %m", bridge_name); - - /* Try again, now that the bridge exists */ - } -} - -int remove_bridge(const char *bridge_name) { - _cleanup_release_lock_file_ LockFile bridge_lock = LOCK_FILE_INIT; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - const char *path; - int r; - - /* Removes the specified bridge, but only if it is currently empty */ - - if (isempty(bridge_name)) - return 0; - - r = make_lock_file("/run/systemd/nspawn-network-zone", LOCK_EX, &bridge_lock); - if (r < 0) - return log_error_errno(r, "Failed to take network zone lock: %m"); - - path = strjoina("/sys/class/net/", bridge_name, "/brif"); - - r = dir_is_empty(path); - if (r == -ENOENT) /* Already gone? */ - return 0; - if (r < 0) - return log_error_errno(r, "Can't detect if bridge %s is empty: %m", bridge_name); - if (r == 0) /* Still populated, leave it around */ - return 0; - - r = sd_netlink_open(&rtnl); - if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); - - return remove_one_link(rtnl, bridge_name); -} - -static int parse_interface(struct udev *udev, const char *name) { - _cleanup_udev_device_unref_ struct udev_device *d = NULL; - char ifi_str[2 + DECIMAL_STR_MAX(int)]; - int ifi; - - ifi = (int) if_nametoindex(name); - if (ifi <= 0) - return log_error_errno(errno, "Failed to resolve interface %s: %m", name); - - sprintf(ifi_str, "n%i", ifi); - d = udev_device_new_from_device_id(udev, ifi_str); - if (!d) - return log_error_errno(errno, "Failed to get udev device for interface %s: %m", name); - - if (udev_device_get_is_initialized(d) <= 0) { - log_error("Network interface %s is not initialized yet.", name); - return -EBUSY; - } - - return ifi; -} - -int move_network_interfaces(pid_t pid, char **ifaces) { - _cleanup_udev_unref_ struct udev *udev = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - char **i; - int r; - - if (strv_isempty(ifaces)) - return 0; - - r = sd_netlink_open(&rtnl); - if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); - - udev = udev_new(); - if (!udev) { - log_error("Failed to connect to udev."); - return -ENOMEM; - } - - STRV_FOREACH(i, ifaces) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - int ifi; - - ifi = parse_interface(udev, *i); - if (ifi < 0) - return ifi; - - r = sd_rtnl_message_new_link(rtnl, &m, RTM_SETLINK, ifi); - if (r < 0) - return log_error_errno(r, "Failed to allocate netlink message: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); - if (r < 0) - return log_error_errno(r, "Failed to append namespace PID to netlink message: %m"); - - r = sd_netlink_call(rtnl, m, 0, NULL); - if (r < 0) - return log_error_errno(r, "Failed to move interface %s to namespace: %m", *i); - } - - return 0; -} - -int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces) { - _cleanup_udev_unref_ struct udev *udev = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - unsigned idx = 0; - char **i; - int r; - - if (strv_isempty(ifaces)) - return 0; - - r = sd_netlink_open(&rtnl); - if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); - - udev = udev_new(); - if (!udev) { - log_error("Failed to connect to udev."); - return -ENOMEM; - } - - STRV_FOREACH(i, ifaces) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - _cleanup_free_ char *n = NULL; - struct ether_addr mac; - int ifi; - - ifi = parse_interface(udev, *i); - if (ifi < 0) - return ifi; - - r = generate_mac(machine_name, &mac, MACVLAN_HASH_KEY, idx++); - if (r < 0) - return log_error_errno(r, "Failed to create MACVLAN MAC address: %m"); - - r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); - if (r < 0) - return log_error_errno(r, "Failed to allocate netlink message: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_LINK, ifi); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface index: %m"); - - n = strappend("mv-", *i); - if (!n) - return log_oom(); - - strshorten(n, IFNAMSIZ-1); - - r = sd_netlink_message_append_string(m, IFLA_IFNAME, n); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface name: %m"); - - r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, &mac); - if (r < 0) - return log_error_errno(r, "Failed to add netlink MAC address: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); - if (r < 0) - return log_error_errno(r, "Failed to add netlink namespace field: %m"); - - r = sd_netlink_message_open_container(m, IFLA_LINKINFO); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "macvlan"); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_MACVLAN_MODE, MACVLAN_MODE_BRIDGE); - if (r < 0) - return log_error_errno(r, "Failed to append macvlan mode: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_call(rtnl, m, 0, NULL); - if (r < 0) - return log_error_errno(r, "Failed to add new macvlan interfaces: %m"); - } - - return 0; -} - -int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces) { - _cleanup_udev_unref_ struct udev *udev = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - char **i; - int r; - - if (strv_isempty(ifaces)) - return 0; - - r = sd_netlink_open(&rtnl); - if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); - - udev = udev_new(); - if (!udev) { - log_error("Failed to connect to udev."); - return -ENOMEM; - } - - STRV_FOREACH(i, ifaces) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - _cleanup_free_ char *n = NULL; - int ifi; - - ifi = parse_interface(udev, *i); - if (ifi < 0) - return ifi; - - r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); - if (r < 0) - return log_error_errno(r, "Failed to allocate netlink message: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_LINK, ifi); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface index: %m"); - - n = strappend("iv-", *i); - if (!n) - return log_oom(); - - strshorten(n, IFNAMSIZ-1); - - r = sd_netlink_message_append_string(m, IFLA_IFNAME, n); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface name: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); - if (r < 0) - return log_error_errno(r, "Failed to add netlink namespace field: %m"); - - r = sd_netlink_message_open_container(m, IFLA_LINKINFO); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "ipvlan"); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_append_u16(m, IFLA_IPVLAN_MODE, IPVLAN_MODE_L2); - if (r < 0) - return log_error_errno(r, "Failed to add ipvlan mode: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_call(rtnl, m, 0, NULL); - if (r < 0) - return log_error_errno(r, "Failed to add new ipvlan interfaces: %m"); - } - - return 0; -} - -int veth_extra_parse(char ***l, const char *p) { - _cleanup_free_ char *a = NULL, *b = NULL; - int r; - - r = extract_first_word(&p, &a, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r < 0) - return r; - if (r == 0 || !ifname_valid(a)) - return -EINVAL; - - r = extract_first_word(&p, &b, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r < 0) - return r; - if (r == 0 || !ifname_valid(b)) { - free(b); - b = strdup(a); - if (!b) - return -ENOMEM; - } - - if (p) - return -EINVAL; - - r = strv_push_pair(l, a, b); - if (r < 0) - return -ENOMEM; - - a = b = NULL; - return 0; -} - -int remove_veth_links(const char *primary, char **pairs) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - char **a, **b; - int r; - - /* In some cases the kernel might pin the veth links between host and container even after the namespace - * died. Hence, let's better remove them explicitly too. */ - - if (isempty(primary) && strv_isempty(pairs)) - return 0; - - r = sd_netlink_open(&rtnl); - if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); - - remove_one_link(rtnl, primary); - - STRV_FOREACH_PAIR(a, b, pairs) - remove_one_link(rtnl, *a); - - return 0; -} diff --git a/src/nspawn/nspawn-network.h b/src/nspawn/nspawn-network.h deleted file mode 100644 index 3d8861e1e5..0000000000 --- a/src/nspawn/nspawn-network.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 -#include - -int setup_veth(const char *machine_name, pid_t pid, char iface_name[IFNAMSIZ], bool bridge); -int setup_veth_extra(const char *machine_name, pid_t pid, char **pairs); - -int setup_bridge(const char *veth_name, const char *bridge_name, bool create); -int remove_bridge(const char *bridge_name); - -int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces); -int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces); - -int move_network_interfaces(pid_t pid, char **ifaces); - -int veth_extra_parse(char ***l, const char *p); - -int remove_veth_links(const char *primary, char **pairs); diff --git a/src/nspawn/nspawn-patch-uid.c b/src/nspawn/nspawn-patch-uid.c deleted file mode 100644 index c7382d412d..0000000000 --- a/src/nspawn/nspawn-patch-uid.c +++ /dev/null @@ -1,469 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2016 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 -#ifdef HAVE_ACL -#include -#endif -#include -#include -#include - -#include "acl-util.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "missing.h" -#include "nspawn-patch-uid.h" -#include "stat-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "strv.h" -#include "user-util.h" - -#ifdef HAVE_ACL - -static int get_acl(int fd, const char *name, acl_type_t type, acl_t *ret) { - char procfs_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; - acl_t acl; - - assert(fd >= 0); - assert(ret); - - if (name) { - _cleanup_close_ int child_fd = -1; - - child_fd = openat(fd, name, O_PATH|O_CLOEXEC|O_NOFOLLOW); - if (child_fd < 0) - return -errno; - - xsprintf(procfs_path, "/proc/self/fd/%i", child_fd); - acl = acl_get_file(procfs_path, type); - } else if (type == ACL_TYPE_ACCESS) - acl = acl_get_fd(fd); - else { - xsprintf(procfs_path, "/proc/self/fd/%i", fd); - acl = acl_get_file(procfs_path, type); - } - if (!acl) - return -errno; - - *ret = acl; - return 0; -} - -static int set_acl(int fd, const char *name, acl_type_t type, acl_t acl) { - char procfs_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; - int r; - - assert(fd >= 0); - assert(acl); - - if (name) { - _cleanup_close_ int child_fd = -1; - - child_fd = openat(fd, name, O_PATH|O_CLOEXEC|O_NOFOLLOW); - if (child_fd < 0) - return -errno; - - xsprintf(procfs_path, "/proc/self/fd/%i", child_fd); - r = acl_set_file(procfs_path, type, acl); - } else if (type == ACL_TYPE_ACCESS) - r = acl_set_fd(fd, acl); - else { - xsprintf(procfs_path, "/proc/self/fd/%i", fd); - r = acl_set_file(procfs_path, type, acl); - } - if (r < 0) - return -errno; - - return 0; -} - -static int shift_acl(acl_t acl, uid_t shift, acl_t *ret) { - _cleanup_(acl_freep) acl_t copy = NULL; - acl_entry_t i; - int r; - - assert(acl); - assert(ret); - - r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i); - if (r < 0) - return -errno; - while (r > 0) { - uid_t *old_uid, new_uid; - bool modify = false; - acl_tag_t tag; - - if (acl_get_tag_type(i, &tag) < 0) - return -errno; - - if (IN_SET(tag, ACL_USER, ACL_GROUP)) { - - /* We don't distuingish here between uid_t and gid_t, let's make sure the compiler checks that - * this is actually OK */ - assert_cc(sizeof(uid_t) == sizeof(gid_t)); - - old_uid = acl_get_qualifier(i); - if (!old_uid) - return -errno; - - new_uid = shift | (*old_uid & UINT32_C(0xFFFF)); - if (!uid_is_valid(new_uid)) - return -EINVAL; - - modify = new_uid != *old_uid; - if (modify && !copy) { - int n; - - /* There's no copy of the ACL yet? if so, let's create one, and start the loop from the - * beginning, so that we copy all entries, starting from the first, this time. */ - - n = acl_entries(acl); - if (n < 0) - return -errno; - - copy = acl_init(n); - if (!copy) - return -errno; - - /* Seek back to the beginning */ - r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i); - if (r < 0) - return -errno; - continue; - } - } - - if (copy) { - acl_entry_t new_entry; - - if (acl_create_entry(©, &new_entry) < 0) - return -errno; - - if (acl_copy_entry(new_entry, i) < 0) - return -errno; - - if (modify) - if (acl_set_qualifier(new_entry, &new_uid) < 0) - return -errno; - } - - r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i); - if (r < 0) - return -errno; - } - - *ret = copy; - copy = NULL; - - return !!*ret; -} - -static int patch_acls(int fd, const char *name, const struct stat *st, uid_t shift) { - _cleanup_(acl_freep) acl_t acl = NULL, shifted = NULL; - bool changed = false; - int r; - - assert(fd >= 0); - assert(st); - - /* ACLs are not supported on symlinks, there's no point in trying */ - if (S_ISLNK(st->st_mode)) - return 0; - - r = get_acl(fd, name, ACL_TYPE_ACCESS, &acl); - if (r == -EOPNOTSUPP) - return 0; - if (r < 0) - return r; - - r = shift_acl(acl, shift, &shifted); - if (r < 0) - return r; - if (r > 0) { - r = set_acl(fd, name, ACL_TYPE_ACCESS, shifted); - if (r < 0) - return r; - - changed = true; - } - - if (S_ISDIR(st->st_mode)) { - acl_free(acl); - acl_free(shifted); - - acl = shifted = NULL; - - r = get_acl(fd, name, ACL_TYPE_DEFAULT, &acl); - if (r < 0) - return r; - - r = shift_acl(acl, shift, &shifted); - if (r < 0) - return r; - if (r > 0) { - r = set_acl(fd, name, ACL_TYPE_DEFAULT, shifted); - if (r < 0) - return r; - - changed = true; - } - } - - return changed; -} - -#else - -static int patch_acls(int fd, const char *name, const struct stat *st, uid_t shift) { - return 0; -} - -#endif - -static int patch_fd(int fd, const char *name, const struct stat *st, uid_t shift) { - uid_t new_uid; - gid_t new_gid; - bool changed = false; - int r; - - assert(fd >= 0); - assert(st); - - new_uid = shift | (st->st_uid & UINT32_C(0xFFFF)); - new_gid = (gid_t) shift | (st->st_gid & UINT32_C(0xFFFF)); - - if (!uid_is_valid(new_uid) || !gid_is_valid(new_gid)) - return -EINVAL; - - if (st->st_uid != new_uid || st->st_gid != new_gid) { - if (name) - r = fchownat(fd, name, new_uid, new_gid, AT_SYMLINK_NOFOLLOW); - else - r = fchown(fd, new_uid, new_gid); - if (r < 0) - return -errno; - - /* The Linux kernel alters the mode in some cases of chown(). Let's undo this. */ - if (name && !S_ISLNK(st->st_mode)) - r = fchmodat(fd, name, st->st_mode, 0); - else - r = fchmod(fd, st->st_mode); - if (r < 0) - return -errno; - - changed = true; - } - - r = patch_acls(fd, name, st, shift); - if (r < 0) - return r; - - return r > 0 || changed; -} - -static int is_procfs_sysfs_or_suchlike(int fd) { - struct statfs sfs; - - assert(fd >= 0); - - if (fstatfs(fd, &sfs) < 0) - return -errno; - - return F_TYPE_EQUAL(sfs.f_type, BINFMTFS_MAGIC) || - F_TYPE_EQUAL(sfs.f_type, CGROUP_SUPER_MAGIC) || - F_TYPE_EQUAL(sfs.f_type, CGROUP2_SUPER_MAGIC) || - F_TYPE_EQUAL(sfs.f_type, DEBUGFS_MAGIC) || - F_TYPE_EQUAL(sfs.f_type, DEVPTS_SUPER_MAGIC) || - F_TYPE_EQUAL(sfs.f_type, EFIVARFS_MAGIC) || - F_TYPE_EQUAL(sfs.f_type, HUGETLBFS_MAGIC) || - F_TYPE_EQUAL(sfs.f_type, MQUEUE_MAGIC) || - F_TYPE_EQUAL(sfs.f_type, PROC_SUPER_MAGIC) || - F_TYPE_EQUAL(sfs.f_type, PSTOREFS_MAGIC) || - F_TYPE_EQUAL(sfs.f_type, SELINUX_MAGIC) || - F_TYPE_EQUAL(sfs.f_type, SMACK_MAGIC) || - F_TYPE_EQUAL(sfs.f_type, SYSFS_MAGIC); -} - -static int recurse_fd(int fd, bool donate_fd, const struct stat *st, uid_t shift, bool is_toplevel) { - bool changed = false; - int r; - - assert(fd >= 0); - - /* We generally want to permit crossing of mount boundaries when patching the UIDs/GIDs. However, we - * probably shouldn't do this for /proc and /sys if that is already mounted into place. Hence, let's - * stop the recursion when we hit a procfs or sysfs file system. */ - r = is_procfs_sysfs_or_suchlike(fd); - if (r < 0) - goto finish; - if (r > 0) { - r = 0; /* don't recurse */ - goto finish; - } - - r = patch_fd(fd, NULL, st, shift); - if (r == -EROFS) { - _cleanup_free_ char *name = NULL; - - if (!is_toplevel) { - /* When we hit a ready-only subtree we simply skip it, but log about it. */ - (void) fd_get_path(fd, &name); - log_debug("Skippping read-only file or directory %s.", strna(name)); - r = 0; - } - - goto finish; - } - if (r < 0) - goto finish; - - if (S_ISDIR(st->st_mode)) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - - if (!donate_fd) { - int copy; - - copy = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (copy < 0) { - r = -errno; - goto finish; - } - - fd = copy; - donate_fd = true; - } - - d = fdopendir(fd); - if (!d) { - r = -errno; - goto finish; - } - fd = -1; - - FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) { - struct stat fst; - - if (STR_IN_SET(de->d_name, ".", "..")) - continue; - - if (fstatat(dirfd(d), de->d_name, &fst, AT_SYMLINK_NOFOLLOW) < 0) { - r = -errno; - goto finish; - } - - if (S_ISDIR(fst.st_mode)) { - int subdir_fd; - - subdir_fd = openat(dirfd(d), de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); - if (subdir_fd < 0) { - r = -errno; - goto finish; - - } - - r = recurse_fd(subdir_fd, true, &fst, shift, false); - if (r < 0) - goto finish; - if (r > 0) - changed = true; - - } else { - r = patch_fd(dirfd(d), de->d_name, &fst, shift); - if (r < 0) - goto finish; - if (r > 0) - changed = true; - } - } - } - - r = changed; - -finish: - if (donate_fd) - safe_close(fd); - - return r; -} - -static int fd_patch_uid_internal(int fd, bool donate_fd, uid_t shift, uid_t range) { - struct stat st; - int r; - - assert(fd >= 0); - - /* Recursively adjusts the UID/GIDs of all files of a directory tree. This is used to automatically fix up an - * OS tree to the used user namespace UID range. Note that this automatic adjustment only works for UID ranges - * following the concept that the upper 16bit of a UID identify the container, and the lower 16bit are the actual - * UID within the container. */ - - if ((shift & 0xFFFF) != 0) { - /* We only support containers where the shift starts at a 2^16 boundary */ - r = -EOPNOTSUPP; - goto finish; - } - - if (range != 0x10000) { - /* We only support containers with 16bit UID ranges for the patching logic */ - r = -EOPNOTSUPP; - goto finish; - } - - if (fstat(fd, &st) < 0) { - r = -errno; - goto finish; - } - - if ((uint32_t) st.st_uid >> 16 != (uint32_t) st.st_gid >> 16) { - /* We only support containers where the uid/gid container ID match */ - r = -EBADE; - goto finish; - } - - /* Try to detect if the range is already right. Of course, this a pretty drastic optimization, as we assume - * that if the top-level dir has the right upper 16bit assigned, then everything below will have too... */ - if (((uint32_t) (st.st_uid ^ shift) >> 16) == 0) - return 0; - - return recurse_fd(fd, donate_fd, &st, shift, true); - -finish: - if (donate_fd) - safe_close(fd); - - return r; -} - -int fd_patch_uid(int fd, uid_t shift, uid_t range) { - return fd_patch_uid_internal(fd, false, shift, range); -} - -int path_patch_uid(const char *path, uid_t shift, uid_t range) { - int fd; - - fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); - if (fd < 0) - return -errno; - - return fd_patch_uid_internal(fd, true, shift, range); -} diff --git a/src/nspawn/nspawn-patch-uid.h b/src/nspawn/nspawn-patch-uid.h deleted file mode 100644 index 55d0990016..0000000000 --- a/src/nspawn/nspawn-patch-uid.h +++ /dev/null @@ -1,23 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2016 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 - -int fd_patch_uid(int fd, uid_t shift, uid_t range); -int path_patch_uid(const char *path, uid_t shift, uid_t range); diff --git a/src/nspawn/nspawn-register.c b/src/nspawn/nspawn-register.c deleted file mode 100644 index 20103c5e88..0000000000 --- a/src/nspawn/nspawn-register.c +++ /dev/null @@ -1,236 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 "sd-bus.h" - -#include "bus-error.h" -#include "bus-unit-util.h" -#include "bus-util.h" -#include "nspawn-register.h" -#include "stat-util.h" -#include "strv.h" -#include "util.h" - -int register_machine( - const char *machine_name, - pid_t pid, - const char *directory, - sd_id128_t uuid, - int local_ifindex, - const char *slice, - CustomMount *mounts, - unsigned n_mounts, - int kill_signal, - char **properties, - bool keep_unit, - const char *service) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - r = sd_bus_default_system(&bus); - if (r < 0) - return log_error_errno(r, "Failed to open system bus: %m"); - - if (keep_unit) { - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "RegisterMachineWithNetwork", - &error, - NULL, - "sayssusai", - machine_name, - SD_BUS_MESSAGE_APPEND_ID128(uuid), - service, - "container", - (uint32_t) pid, - strempty(directory), - local_ifindex > 0 ? 1 : 0, local_ifindex); - } else { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - char **i; - unsigned j; - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "CreateMachineWithNetwork"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sayssusai", - machine_name, - SD_BUS_MESSAGE_APPEND_ID128(uuid), - service, - "container", - (uint32_t) pid, - strempty(directory), - local_ifindex > 0 ? 1 : 0, local_ifindex); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'a', "(sv)"); - if (r < 0) - return bus_log_create_error(r); - - if (!isempty(slice)) { - r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_message_append(m, "(sv)", "DevicePolicy", "s", "strict"); - if (r < 0) - return bus_log_create_error(r); - - /* If you make changes here, also make sure to update - * systemd-nspawn@.service, to keep the device - * policies in sync regardless if we are run with or - * without the --keep-unit switch. */ - r = sd_bus_message_append(m, "(sv)", "DeviceAllow", "a(ss)", 9, - /* Allow the container to - * access and create the API - * device nodes, so that - * PrivateDevices= in the - * container can work - * fine */ - "/dev/null", "rwm", - "/dev/zero", "rwm", - "/dev/full", "rwm", - "/dev/random", "rwm", - "/dev/urandom", "rwm", - "/dev/tty", "rwm", - "/dev/net/tun", "rwm", - /* Allow the container - * access to ptys. However, - * do not permit the - * container to ever create - * these device nodes. */ - "/dev/pts/ptmx", "rw", - "char-pts", "rw"); - if (r < 0) - return bus_log_create_error(r); - - for (j = 0; j < n_mounts; j++) { - CustomMount *cm = mounts + j; - - if (cm->type != CUSTOM_MOUNT_BIND) - continue; - - r = is_device_node(cm->source); - if (r < 0) - return log_error_errno(r, "Failed to stat %s: %m", cm->source); - - if (r) { - r = sd_bus_message_append(m, "(sv)", "DeviceAllow", "a(ss)", 1, - cm->source, cm->read_only ? "r" : "rw"); - if (r < 0) - return log_error_errno(r, "Failed to append message arguments: %m"); - } - } - - if (kill_signal != 0) { - r = sd_bus_message_append(m, "(sv)", "KillSignal", "i", kill_signal); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "(sv)", "KillMode", "s", "mixed"); - if (r < 0) - return bus_log_create_error(r); - } - - STRV_FOREACH(i, properties) { - r = bus_append_unit_property_assignment(m, *i); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, NULL); - } - - if (r < 0) { - log_error("Failed to register machine: %s", bus_error_message(&error, r)); - return r; - } - - return 0; -} - -int terminate_machine(pid_t pid) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - const char *path; - int r; - - r = sd_bus_default_system(&bus); - if (r < 0) - return log_error_errno(r, "Failed to open system bus: %m"); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetMachineByPID", - &error, - &reply, - "u", - (uint32_t) pid); - if (r < 0) { - /* Note that the machine might already have been - * cleaned up automatically, hence don't consider it a - * failure if we cannot get the machine object. */ - log_debug("Failed to get machine: %s", bus_error_message(&error, r)); - return 0; - } - - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - path, - "org.freedesktop.machine1.Machine", - "Terminate", - &error, - NULL, - NULL); - if (r < 0) { - log_debug("Failed to terminate machine: %s", bus_error_message(&error, r)); - return 0; - } - - return 0; -} diff --git a/src/nspawn/nspawn-register.h b/src/nspawn/nspawn-register.h deleted file mode 100644 index 304c5a485b..0000000000 --- a/src/nspawn/nspawn-register.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 "sd-id128.h" - -#include "nspawn-mount.h" - -int register_machine(const char *machine_name, pid_t pid, const char *directory, sd_id128_t uuid, int local_ifindex, const char *slice, CustomMount *mounts, unsigned n_mounts, int kill_signal, char **properties, bool keep_unit, const char *service); -int terminate_machine(pid_t pid); diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c deleted file mode 100644 index 5f1522cfb6..0000000000 --- a/src/nspawn/nspawn-settings.c +++ /dev/null @@ -1,516 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 "alloc-util.h" -#include "cap-list.h" -#include "conf-parser.h" -#include "nspawn-network.h" -#include "nspawn-settings.h" -#include "parse-util.h" -#include "process-util.h" -#include "socket-util.h" -#include "string-util.h" -#include "strv.h" -#include "user-util.h" -#include "util.h" - -int settings_load(FILE *f, const char *path, Settings **ret) { - _cleanup_(settings_freep) Settings *s = NULL; - int r; - - assert(path); - assert(ret); - - s = new0(Settings, 1); - if (!s) - return -ENOMEM; - - s->start_mode = _START_MODE_INVALID; - s->personality = PERSONALITY_INVALID; - s->userns_mode = _USER_NAMESPACE_MODE_INVALID; - s->uid_shift = UID_INVALID; - s->uid_range = UID_INVALID; - - s->read_only = -1; - s->volatile_mode = _VOLATILE_MODE_INVALID; - s->userns_chown = -1; - - s->private_network = -1; - s->network_veth = -1; - - r = config_parse(NULL, path, f, - "Exec\0" - "Network\0" - "Files\0", - config_item_perf_lookup, nspawn_gperf_lookup, - false, - false, - true, - s); - if (r < 0) - return r; - - /* Make sure that if userns_mode is set, userns_chown is set to something appropriate, and vice versa. Either - * both fields shall be initialized or neither. */ - if (s->userns_mode == USER_NAMESPACE_PICK) - s->userns_chown = true; - else if (s->userns_mode != _USER_NAMESPACE_MODE_INVALID && s->userns_chown < 0) - s->userns_chown = false; - - if (s->userns_chown >= 0 && s->userns_mode == _USER_NAMESPACE_MODE_INVALID) - s->userns_mode = USER_NAMESPACE_NO; - - *ret = s; - s = NULL; - - return 0; -} - -Settings* settings_free(Settings *s) { - - if (!s) - return NULL; - - strv_free(s->parameters); - strv_free(s->environment); - free(s->user); - free(s->working_directory); - - strv_free(s->network_interfaces); - strv_free(s->network_macvlan); - strv_free(s->network_ipvlan); - strv_free(s->network_veth_extra); - free(s->network_bridge); - free(s->network_zone); - expose_port_free_all(s->expose_ports); - - custom_mount_free_all(s->custom_mounts, s->n_custom_mounts); - free(s); - - return NULL; -} - -bool settings_private_network(Settings *s) { - assert(s); - - return - s->private_network > 0 || - s->network_veth > 0 || - s->network_bridge || - s->network_zone || - s->network_interfaces || - s->network_macvlan || - s->network_ipvlan || - s->network_veth_extra; -} - -bool settings_network_veth(Settings *s) { - assert(s); - - return - s->network_veth > 0 || - s->network_bridge || - s->network_zone; -} - -DEFINE_CONFIG_PARSE_ENUM(config_parse_volatile_mode, volatile_mode, VolatileMode, "Failed to parse volatile mode"); - -int config_parse_expose_port( - 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) { - - Settings *s = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = expose_port_parse(&s->expose_ports, rvalue); - if (r == -EEXIST) { - log_syntax(unit, LOG_ERR, filename, line, r, "Duplicate port specification, ignoring: %s", rvalue); - return 0; - } - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse host port %s: %m", rvalue); - return 0; - } - - return 0; -} - -int config_parse_capability( - 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) { - - uint64_t u = 0, *result = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - for (;;) { - _cleanup_free_ char *word = NULL; - int cap; - - r = extract_first_word(&rvalue, &word, NULL, 0); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract capability string, ignoring: %s", rvalue); - return 0; - } - if (r == 0) - break; - - cap = capability_from_name(word); - if (cap < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse capability, ignoring: %s", word); - continue; - } - - u |= 1 << ((uint64_t) cap); - } - - if (u == 0) - return 0; - - *result |= u; - return 0; -} - -int config_parse_id128( - 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) { - - sd_id128_t t, *result = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = sd_id128_from_string(rvalue, &t); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse 128bit ID/UUID, ignoring: %s", rvalue); - return 0; - } - - *result = t; - return 0; -} - -int config_parse_bind( - 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) { - - Settings *settings = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = bind_mount_parse(&settings->custom_mounts, &settings->n_custom_mounts, rvalue, ltype); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Invalid bind mount specification %s: %m", rvalue); - return 0; - } - - return 0; -} - -int config_parse_tmpfs( - 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) { - - Settings *settings = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = tmpfs_mount_parse(&settings->custom_mounts, &settings->n_custom_mounts, rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Invalid temporary file system specification %s: %m", rvalue); - return 0; - } - - return 0; -} - -int config_parse_veth_extra( - 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) { - - Settings *settings = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = veth_extra_parse(&settings->network_veth_extra, rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Invalid extra virtual Ethernet link specification %s: %m", rvalue); - return 0; - } - - return 0; -} - -int config_parse_network_zone( - 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) { - - Settings *settings = data; - _cleanup_free_ char *j = NULL; - - assert(filename); - assert(lvalue); - assert(rvalue); - - j = strappend("vz-", rvalue); - if (!ifname_valid(j)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid network zone name %s, ignoring: %m", rvalue); - return 0; - } - - free(settings->network_zone); - settings->network_zone = j; - j = NULL; - - return 0; -} - -int config_parse_boot( - 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) { - - Settings *settings = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse Boot= parameter %s, ignoring: %m", rvalue); - return 0; - } - - if (r > 0) { - if (settings->start_mode == START_PID2) - goto conflict; - - settings->start_mode = START_BOOT; - } else { - if (settings->start_mode == START_BOOT) - goto conflict; - - if (settings->start_mode < 0) - settings->start_mode = START_PID1; - } - - return 0; - -conflict: - log_syntax(unit, LOG_ERR, filename, line, r, "Conflicting Boot= or ProcessTwo= setting found. Ignoring."); - return 0; -} - -int config_parse_pid2( - 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) { - - Settings *settings = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse ProcessTwo= parameter %s, ignoring: %m", rvalue); - return 0; - } - - if (r > 0) { - if (settings->start_mode == START_BOOT) - goto conflict; - - settings->start_mode = START_PID2; - } else { - if (settings->start_mode == START_PID2) - goto conflict; - - if (settings->start_mode < 0) - settings->start_mode = START_PID1; - } - - return 0; - -conflict: - log_syntax(unit, LOG_ERR, filename, line, r, "Conflicting Boot= or ProcessTwo= setting found. Ignoring."); - return 0; -} - -int config_parse_private_users( - 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) { - - Settings *settings = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = parse_boolean(rvalue); - if (r == 0) { - /* no: User namespacing off */ - settings->userns_mode = USER_NAMESPACE_NO; - settings->uid_shift = UID_INVALID; - settings->uid_range = UINT32_C(0x10000); - } else if (r > 0) { - /* yes: User namespacing on, UID range is read from root dir */ - settings->userns_mode = USER_NAMESPACE_FIXED; - settings->uid_shift = UID_INVALID; - settings->uid_range = UINT32_C(0x10000); - } else if (streq(rvalue, "pick")) { - /* pick: User namespacing on, UID range is picked randomly */ - settings->userns_mode = USER_NAMESPACE_PICK; - settings->uid_shift = UID_INVALID; - settings->uid_range = UINT32_C(0x10000); - } else { - const char *range, *shift; - uid_t sh, rn; - - /* anything else: User namespacing on, UID range is explicitly configured */ - - range = strchr(rvalue, ':'); - if (range) { - shift = strndupa(rvalue, range - rvalue); - range++; - - r = safe_atou32(range, &rn); - if (r < 0 || rn <= 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "UID/GID range invalid, ignoring: %s", range); - return 0; - } - } else { - shift = rvalue; - rn = UINT32_C(0x10000); - } - - r = parse_uid(shift, &sh); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "UID/GID shift invalid, ignoring: %s", range); - return 0; - } - - settings->userns_mode = USER_NAMESPACE_FIXED; - settings->uid_shift = sh; - settings->uid_range = rn; - } - - return 0; -} diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h deleted file mode 100644 index 1c47e37912..0000000000 --- a/src/nspawn/nspawn-settings.h +++ /dev/null @@ -1,116 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 "macro.h" -#include "nspawn-expose-ports.h" -#include "nspawn-mount.h" - -typedef enum StartMode { - START_PID1, /* Run parameters as command line as process 1 */ - START_PID2, /* Use stub init process as PID 1, run parameters as command line as process 2 */ - START_BOOT, /* Search for init system, pass arguments as parameters */ - _START_MODE_MAX, - _START_MODE_INVALID = -1 -} StartMode; - -typedef enum UserNamespaceMode { - USER_NAMESPACE_NO, - USER_NAMESPACE_FIXED, - USER_NAMESPACE_PICK, - _USER_NAMESPACE_MODE_MAX, - _USER_NAMESPACE_MODE_INVALID = -1, -} UserNamespaceMode; - -typedef enum SettingsMask { - SETTING_START_MODE = 1 << 0, - SETTING_ENVIRONMENT = 1 << 1, - SETTING_USER = 1 << 2, - SETTING_CAPABILITY = 1 << 3, - SETTING_KILL_SIGNAL = 1 << 4, - SETTING_PERSONALITY = 1 << 5, - SETTING_MACHINE_ID = 1 << 6, - SETTING_NETWORK = 1 << 7, - SETTING_EXPOSE_PORTS = 1 << 8, - SETTING_READ_ONLY = 1 << 9, - SETTING_VOLATILE_MODE = 1 << 10, - SETTING_CUSTOM_MOUNTS = 1 << 11, - SETTING_WORKING_DIRECTORY = 1 << 12, - SETTING_USERNS = 1 << 13, - _SETTINGS_MASK_ALL = (1 << 14) -1 -} SettingsMask; - -typedef struct Settings { - /* [Run] */ - StartMode start_mode; - char **parameters; - char **environment; - char *user; - uint64_t capability; - uint64_t drop_capability; - int kill_signal; - unsigned long personality; - sd_id128_t machine_id; - char *working_directory; - UserNamespaceMode userns_mode; - uid_t uid_shift, uid_range; - - /* [Image] */ - int read_only; - VolatileMode volatile_mode; - CustomMount *custom_mounts; - unsigned n_custom_mounts; - int userns_chown; - - /* [Network] */ - int private_network; - int network_veth; - char *network_bridge; - char *network_zone; - char **network_interfaces; - char **network_macvlan; - char **network_ipvlan; - char **network_veth_extra; - ExposePort *expose_ports; -} Settings; - -int settings_load(FILE *f, const char *path, Settings **ret); -Settings* settings_free(Settings *s); - -bool settings_network_veth(Settings *s); -bool settings_private_network(Settings *s); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Settings*, settings_free); - -const struct ConfigPerfItem* nspawn_gperf_lookup(const char *key, unsigned length); - -int config_parse_capability(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); -int config_parse_id128(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); -int config_parse_expose_port(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); -int config_parse_volatile_mode(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); -int config_parse_bind(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); -int config_parse_tmpfs(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); -int config_parse_veth_extra(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); -int config_parse_network_zone(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); -int config_parse_boot(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); -int config_parse_pid2(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); -int config_parse_private_users(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); diff --git a/src/nspawn/nspawn-setuid.c b/src/nspawn/nspawn-setuid.c deleted file mode 100644 index ee15a47e93..0000000000 --- a/src/nspawn/nspawn-setuid.c +++ /dev/null @@ -1,273 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "mkdir.h" -#include "nspawn-setuid.h" -#include "process-util.h" -#include "signal-util.h" -#include "string-util.h" -#include "user-util.h" -#include "util.h" - -static int spawn_getent(const char *database, const char *key, pid_t *rpid) { - int pipe_fds[2]; - pid_t pid; - - assert(database); - assert(key); - assert(rpid); - - if (pipe2(pipe_fds, O_CLOEXEC) < 0) - return log_error_errno(errno, "Failed to allocate pipe: %m"); - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork getent child: %m"); - else if (pid == 0) { - int nullfd; - char *empty_env = NULL; - - if (dup3(pipe_fds[1], STDOUT_FILENO, 0) < 0) - _exit(EXIT_FAILURE); - - if (pipe_fds[0] > 2) - safe_close(pipe_fds[0]); - if (pipe_fds[1] > 2) - safe_close(pipe_fds[1]); - - nullfd = open("/dev/null", O_RDWR); - if (nullfd < 0) - _exit(EXIT_FAILURE); - - if (dup3(nullfd, STDIN_FILENO, 0) < 0) - _exit(EXIT_FAILURE); - - if (dup3(nullfd, STDERR_FILENO, 0) < 0) - _exit(EXIT_FAILURE); - - if (nullfd > 2) - safe_close(nullfd); - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - close_all_fds(NULL, 0); - - execle("/usr/bin/getent", "getent", database, key, NULL, &empty_env); - execle("/bin/getent", "getent", database, key, NULL, &empty_env); - _exit(EXIT_FAILURE); - } - - pipe_fds[1] = safe_close(pipe_fds[1]); - - *rpid = pid; - - return pipe_fds[0]; -} - -int change_uid_gid(const char *user, char **_home) { - char line[LINE_MAX], *x, *u, *g, *h; - const char *word, *state; - _cleanup_free_ uid_t *uids = NULL; - _cleanup_free_ char *home = NULL; - _cleanup_fclose_ FILE *f = NULL; - _cleanup_close_ int fd = -1; - unsigned n_uids = 0; - size_t sz = 0, l; - uid_t uid; - gid_t gid; - pid_t pid; - int r; - - assert(_home); - - if (!user || streq(user, "root") || streq(user, "0")) { - /* Reset everything fully to 0, just in case */ - - r = reset_uid_gid(); - if (r < 0) - return log_error_errno(r, "Failed to become root: %m"); - - *_home = NULL; - return 0; - } - - /* First, get user credentials */ - fd = spawn_getent("passwd", user, &pid); - if (fd < 0) - return fd; - - f = fdopen(fd, "r"); - if (!f) - return log_oom(); - fd = -1; - - if (!fgets(line, sizeof(line), f)) { - - if (!ferror(f)) { - log_error("Failed to resolve user %s.", user); - return -ESRCH; - } - - log_error_errno(errno, "Failed to read from getent: %m"); - return -errno; - } - - truncate_nl(line); - - wait_for_terminate_and_warn("getent passwd", pid, true); - - x = strchr(line, ':'); - if (!x) { - log_error("/etc/passwd entry has invalid user field."); - return -EIO; - } - - u = strchr(x+1, ':'); - if (!u) { - log_error("/etc/passwd entry has invalid password field."); - return -EIO; - } - - u++; - g = strchr(u, ':'); - if (!g) { - log_error("/etc/passwd entry has invalid UID field."); - return -EIO; - } - - *g = 0; - g++; - x = strchr(g, ':'); - if (!x) { - log_error("/etc/passwd entry has invalid GID field."); - return -EIO; - } - - *x = 0; - h = strchr(x+1, ':'); - if (!h) { - log_error("/etc/passwd entry has invalid GECOS field."); - return -EIO; - } - - h++; - x = strchr(h, ':'); - if (!x) { - log_error("/etc/passwd entry has invalid home directory field."); - return -EIO; - } - - *x = 0; - - r = parse_uid(u, &uid); - if (r < 0) { - log_error("Failed to parse UID of user."); - return -EIO; - } - - r = parse_gid(g, &gid); - if (r < 0) { - log_error("Failed to parse GID of user."); - return -EIO; - } - - home = strdup(h); - if (!home) - return log_oom(); - - /* Second, get group memberships */ - fd = spawn_getent("initgroups", user, &pid); - if (fd < 0) - return fd; - - fclose(f); - f = fdopen(fd, "r"); - if (!f) - return log_oom(); - fd = -1; - - if (!fgets(line, sizeof(line), f)) { - if (!ferror(f)) { - log_error("Failed to resolve user %s.", user); - return -ESRCH; - } - - log_error_errno(errno, "Failed to read from getent: %m"); - return -errno; - } - - truncate_nl(line); - - wait_for_terminate_and_warn("getent initgroups", pid, true); - - /* Skip over the username and subsequent separator whitespace */ - x = line; - x += strcspn(x, WHITESPACE); - x += strspn(x, WHITESPACE); - - FOREACH_WORD(word, l, x, state) { - char c[l+1]; - - memcpy(c, word, l); - c[l] = 0; - - if (!GREEDY_REALLOC(uids, sz, n_uids+1)) - return log_oom(); - - r = parse_uid(c, &uids[n_uids++]); - if (r < 0) { - log_error("Failed to parse group data from getent."); - return -EIO; - } - } - - r = mkdir_parents(home, 0775); - if (r < 0) - return log_error_errno(r, "Failed to make home root directory: %m"); - - r = mkdir_safe(home, 0755, uid, gid); - if (r < 0 && r != -EEXIST) - return log_error_errno(r, "Failed to make home directory: %m"); - - (void) fchown(STDIN_FILENO, uid, gid); - (void) fchown(STDOUT_FILENO, uid, gid); - (void) fchown(STDERR_FILENO, uid, gid); - - if (setgroups(n_uids, uids) < 0) - return log_error_errno(errno, "Failed to set auxiliary groups: %m"); - - if (setresgid(gid, gid, gid) < 0) - return log_error_errno(errno, "setresgid() failed: %m"); - - if (setresuid(uid, uid, uid) < 0) - return log_error_errno(errno, "setresuid() failed: %m"); - - if (_home) { - *_home = home; - home = NULL; - } - - return 0; -} diff --git a/src/nspawn/nspawn-setuid.h b/src/nspawn/nspawn-setuid.h deleted file mode 100644 index b4968ba1fc..0000000000 --- a/src/nspawn/nspawn-setuid.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -int change_uid_gid(const char *user, char **ret); diff --git a/src/nspawn/nspawn-stub-pid1.c b/src/nspawn/nspawn-stub-pid1.c deleted file mode 100644 index 2de87e3c63..0000000000 --- a/src/nspawn/nspawn-stub-pid1.c +++ /dev/null @@ -1,170 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2016 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 -#include - -#include "fd-util.h" -#include "log.h" -#include "nspawn-stub-pid1.h" -#include "process-util.h" -#include "signal-util.h" -#include "time-util.h" -#include "def.h" - -int stub_pid1(void) { - enum { - STATE_RUNNING, - STATE_REBOOT, - STATE_POWEROFF, - } state = STATE_RUNNING; - - sigset_t fullmask, oldmask, waitmask; - usec_t quit_usec = USEC_INFINITY; - pid_t pid; - int r; - - /* Implements a stub PID 1, that reaps all processes and processes a couple of standard signals. This is useful - * for allowing arbitrary processes run in a container, and still have all zombies reaped. */ - - assert_se(sigfillset(&fullmask) >= 0); - assert_se(sigprocmask(SIG_BLOCK, &fullmask, &oldmask) >= 0); - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork child pid: %m"); - - if (pid == 0) { - /* Return in the child */ - assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) >= 0); - setsid(); - return 0; - } - - reset_all_signal_handlers(); - - log_close(); - close_all_fds(NULL, 0); - log_open(); - - rename_process("STUBINIT"); - - assert_se(sigemptyset(&waitmask) >= 0); - assert_se(sigset_add_many(&waitmask, - SIGCHLD, /* posix: process died */ - SIGINT, /* sysv: ctrl-alt-del */ - SIGRTMIN+3, /* systemd: halt */ - SIGRTMIN+4, /* systemd: poweroff */ - SIGRTMIN+5, /* systemd: reboot */ - SIGRTMIN+6, /* systemd: kexec */ - SIGRTMIN+13, /* systemd: halt */ - SIGRTMIN+14, /* systemd: poweroff */ - SIGRTMIN+15, /* systemd: reboot */ - SIGRTMIN+16, /* systemd: kexec */ - -1) >= 0); - - /* Note that we ignore SIGTERM (sysv's reexec), SIGHUP (reload), and all other signals here, since we don't - * support reexec/reloading in this stub process. */ - - for (;;) { - siginfo_t si; - usec_t current_usec; - - si.si_pid = 0; - r = waitid(P_ALL, 0, &si, WEXITED|WNOHANG); - if (r < 0) { - r = log_error_errno(errno, "Failed to reap children: %m"); - goto finish; - } - - current_usec = now(CLOCK_MONOTONIC); - - if (si.si_pid == pid || current_usec >= quit_usec) { - - /* The child we started ourselves died or we reached a timeout. */ - - if (state == STATE_REBOOT) { /* dispatch a queued reboot */ - (void) reboot(RB_AUTOBOOT); - r = log_error_errno(errno, "Failed to reboot: %m"); - goto finish; - - } else if (state == STATE_POWEROFF) - (void) reboot(RB_POWER_OFF); /* if this fails, fall back to normal exit. */ - - if (si.si_pid == pid && si.si_code == CLD_EXITED) - r = si.si_status; /* pass on exit code */ - else - r = 255; /* signal, coredump, timeout, … */ - - goto finish; - } - if (si.si_pid != 0) - /* We reaped something. Retry until there's nothing more to reap. */ - continue; - - if (quit_usec == USEC_INFINITY) - r = sigwaitinfo(&waitmask, &si); - else { - struct timespec ts; - r = sigtimedwait(&waitmask, &si, timespec_store(&ts, quit_usec - current_usec)); - } - if (r < 0) { - if (errno == EINTR) /* strace -p attach can result in EINTR, let's handle this nicely. */ - continue; - if (errno == EAGAIN) /* timeout reached */ - continue; - - r = log_error_errno(errno, "Failed to wait for signal: %m"); - goto finish; - } - - if (si.si_signo == SIGCHLD) - continue; /* Let's reap this */ - - if (state != STATE_RUNNING) - continue; - - /* Would love to use a switch() statement here, but SIGRTMIN is actually a function call, not a - * constant… */ - - if (si.si_signo == SIGRTMIN+3 || - si.si_signo == SIGRTMIN+4 || - si.si_signo == SIGRTMIN+13 || - si.si_signo == SIGRTMIN+14) - - state = STATE_POWEROFF; - - else if (si.si_signo == SIGINT || - si.si_signo == SIGRTMIN+5 || - si.si_signo == SIGRTMIN+6 || - si.si_signo == SIGRTMIN+15 || - si.si_signo == SIGRTMIN+16) - - state = STATE_REBOOT; - else - assert_not_reached("Got unexpected signal"); - - /* (void) kill_and_sigcont(pid, SIGTERM); */ - quit_usec = now(CLOCK_MONOTONIC) + DEFAULT_TIMEOUT_USEC; - } - -finish: - _exit(r < 0 ? EXIT_FAILURE : r); -} diff --git a/src/nspawn/nspawn-stub-pid1.h b/src/nspawn/nspawn-stub-pid1.h deleted file mode 100644 index 36c1aaf5dd..0000000000 --- a/src/nspawn/nspawn-stub-pid1.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2016 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 . -***/ - -int stub_pid1(void); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c deleted file mode 100644 index 8ec058431b..0000000000 --- a/src/nspawn/nspawn.c +++ /dev/null @@ -1,4116 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#ifdef HAVE_BLKID -#include -#endif -#include -#include -#include -#include -#include -#include -#ifdef HAVE_SECCOMP -#include -#endif -#ifdef HAVE_SELINUX -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-daemon.h" -#include "sd-id128.h" - -#include "alloc-util.h" -#include "barrier.h" -#include "base-filesystem.h" -#include "blkid-util.h" -#include "btrfs-util.h" -#include "cap-list.h" -#include "capability-util.h" -#include "cgroup-util.h" -#include "copy.h" -#include "dev-setup.h" -#include "env-util.h" -#include "fd-util.h" -#include "fdset.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "gpt.h" -#include "hostname-util.h" -#include "log.h" -#include "loopback-setup.h" -#include "machine-id-setup.h" -#include "machine-image.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "mount-util.h" -#include "netlink-util.h" -#include "nspawn-cgroup.h" -#include "nspawn-expose-ports.h" -#include "nspawn-mount.h" -#include "nspawn-network.h" -#include "nspawn-patch-uid.h" -#include "nspawn-register.h" -#include "nspawn-settings.h" -#include "nspawn-setuid.h" -#include "nspawn-stub-pid1.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "ptyfwd.h" -#include "random-util.h" -#include "rm-rf.h" -#ifdef HAVE_SECCOMP -#include "seccomp-util.h" -#endif -#include "selinux-util.h" -#include "signal-util.h" -#include "socket-util.h" -#include "stat-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "udev-util.h" -#include "umask-util.h" -#include "user-util.h" -#include "util.h" - -/* Note that devpts's gid= parameter parses GIDs as signed values, hence we stay away from the upper half of the 32bit - * UID range here */ -#define UID_SHIFT_PICK_MIN ((uid_t) UINT32_C(0x00080000)) -#define UID_SHIFT_PICK_MAX ((uid_t) UINT32_C(0x6FFF0000)) - -typedef enum ContainerStatus { - CONTAINER_TERMINATED, - CONTAINER_REBOOTED -} ContainerStatus; - -typedef enum LinkJournal { - LINK_NO, - LINK_AUTO, - LINK_HOST, - LINK_GUEST -} LinkJournal; - -static char *arg_directory = NULL; -static char *arg_template = NULL; -static char *arg_chdir = NULL; -static char *arg_user = NULL; -static sd_id128_t arg_uuid = {}; -static char *arg_machine = NULL; -static const char *arg_selinux_context = NULL; -static const char *arg_selinux_apifs_context = NULL; -static const char *arg_slice = NULL; -static bool arg_private_network = false; -static bool arg_read_only = false; -static StartMode arg_start_mode = START_PID1; -static bool arg_ephemeral = false; -static LinkJournal arg_link_journal = LINK_AUTO; -static bool arg_link_journal_try = false; -static uint64_t arg_retain = - (1ULL << CAP_CHOWN) | - (1ULL << CAP_DAC_OVERRIDE) | - (1ULL << CAP_DAC_READ_SEARCH) | - (1ULL << CAP_FOWNER) | - (1ULL << CAP_FSETID) | - (1ULL << CAP_IPC_OWNER) | - (1ULL << CAP_KILL) | - (1ULL << CAP_LEASE) | - (1ULL << CAP_LINUX_IMMUTABLE) | - (1ULL << CAP_NET_BIND_SERVICE) | - (1ULL << CAP_NET_BROADCAST) | - (1ULL << CAP_NET_RAW) | - (1ULL << CAP_SETGID) | - (1ULL << CAP_SETFCAP) | - (1ULL << CAP_SETPCAP) | - (1ULL << CAP_SETUID) | - (1ULL << CAP_SYS_ADMIN) | - (1ULL << CAP_SYS_CHROOT) | - (1ULL << CAP_SYS_NICE) | - (1ULL << CAP_SYS_PTRACE) | - (1ULL << CAP_SYS_TTY_CONFIG) | - (1ULL << CAP_SYS_RESOURCE) | - (1ULL << CAP_SYS_BOOT) | - (1ULL << CAP_AUDIT_WRITE) | - (1ULL << CAP_AUDIT_CONTROL) | - (1ULL << CAP_MKNOD); -static CustomMount *arg_custom_mounts = NULL; -static unsigned arg_n_custom_mounts = 0; -static char **arg_setenv = NULL; -static bool arg_quiet = false; -static bool arg_share_system = false; -static bool arg_register = true; -static bool arg_keep_unit = false; -static char **arg_network_interfaces = NULL; -static char **arg_network_macvlan = NULL; -static char **arg_network_ipvlan = NULL; -static bool arg_network_veth = false; -static char **arg_network_veth_extra = NULL; -static char *arg_network_bridge = NULL; -static char *arg_network_zone = NULL; -static unsigned long arg_personality = PERSONALITY_INVALID; -static char *arg_image = NULL; -static VolatileMode arg_volatile_mode = VOLATILE_NO; -static ExposePort *arg_expose_ports = NULL; -static char **arg_property = NULL; -static UserNamespaceMode arg_userns_mode = USER_NAMESPACE_NO; -static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U; -static bool arg_userns_chown = false; -static int arg_kill_signal = 0; -static bool arg_unified_cgroup_hierarchy = false; -static SettingsMask arg_settings_mask = 0; -static int arg_settings_trusted = -1; -static char **arg_parameters = NULL; -static const char *arg_container_service_name = "systemd-nspawn"; - -static void help(void) { - printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" - "Spawn a minimal namespace container for debugging, testing and building.\n\n" - " -h --help Show this help\n" - " --version Print version string\n" - " -q --quiet Do not show status information\n" - " -D --directory=PATH Root directory for the container\n" - " --template=PATH Initialize root directory from template directory,\n" - " if missing\n" - " -x --ephemeral Run container with snapshot of root directory, and\n" - " remove it after exit\n" - " -i --image=PATH File system device or disk image for the container\n" - " -a --as-pid2 Maintain a stub init as PID1, invoke binary as PID2\n" - " -b --boot Boot up full system (i.e. invoke init)\n" - " --chdir=PATH Set working directory in the container\n" - " -u --user=USER Run the command under specified user or uid\n" - " -M --machine=NAME Set the machine name for the container\n" - " --uuid=UUID Set a specific machine UUID for the container\n" - " -S --slice=SLICE Place the container in the specified slice\n" - " --property=NAME=VALUE Set scope unit property\n" - " -U --private-users=pick Run within user namespace, pick UID/GID range automatically\n" - " --private-users[=UIDBASE[:NUIDS]]\n" - " Run within user namespace, user configured UID/GID range\n" - " --private-user-chown Adjust OS tree file ownership for private UID/GID range\n" - " --private-network Disable network in container\n" - " --network-interface=INTERFACE\n" - " Assign an existing network interface to the\n" - " container\n" - " --network-macvlan=INTERFACE\n" - " Create a macvlan network interface based on an\n" - " existing network interface to the container\n" - " --network-ipvlan=INTERFACE\n" - " Create a ipvlan network interface based on an\n" - " existing network interface to the container\n" - " -n --network-veth Add a virtual Ethernet connection between host\n" - " and container\n" - " --network-veth-extra=HOSTIF[:CONTAINERIF]\n" - " Add an additional virtual Ethernet link between\n" - " host and container\n" - " --network-bridge=INTERFACE\n" - " Add a virtual Ethernet connection between host\n" - " and container and add it to an existing bridge on\n" - " the host\n" - " --network-zone=NAME Add a virtual Ethernet connection to the container,\n" - " and add it to an automatically managed bridge interface\n" - " -p --port=[PROTOCOL:]HOSTPORT[:CONTAINERPORT]\n" - " Expose a container IP port on the host\n" - " -Z --selinux-context=SECLABEL\n" - " Set the SELinux security context to be used by\n" - " processes in the container\n" - " -L --selinux-apifs-context=SECLABEL\n" - " Set the SELinux security context to be used by\n" - " API/tmpfs file systems in the container\n" - " --capability=CAP In addition to the default, retain specified\n" - " capability\n" - " --drop-capability=CAP Drop the specified capability from the default set\n" - " --kill-signal=SIGNAL Select signal to use for shutting down PID 1\n" - " --link-journal=MODE Link up guest journal, one of no, auto, guest, \n" - " host, try-guest, try-host\n" - " -j Equivalent to --link-journal=try-guest\n" - " --read-only Mount the root directory read-only\n" - " --bind=PATH[:PATH[:OPTIONS]]\n" - " Bind mount a file or directory from the host into\n" - " the container\n" - " --bind-ro=PATH[:PATH[:OPTIONS]\n" - " Similar, but creates a read-only bind mount\n" - " --tmpfs=PATH:[OPTIONS] Mount an empty tmpfs to the specified directory\n" - " --overlay=PATH[:PATH...]:PATH\n" - " Create an overlay mount from the host to \n" - " the container\n" - " --overlay-ro=PATH[:PATH...]:PATH\n" - " Similar, but creates a read-only overlay mount\n" - " -E --setenv=NAME=VALUE Pass an environment variable to PID 1\n" - " --share-system Share system namespaces with host\n" - " --register=BOOLEAN Register container as machine\n" - " --keep-unit Do not register a scope for the machine, reuse\n" - " the service unit nspawn is running in\n" - " --volatile[=MODE] Run the system in volatile mode\n" - " --settings=BOOLEAN Load additional settings from .nspawn file\n" - , program_invocation_short_name); -} - - -static int custom_mounts_prepare(void) { - unsigned i; - int r; - - /* Ensure the mounts are applied prefix first. */ - qsort_safe(arg_custom_mounts, arg_n_custom_mounts, sizeof(CustomMount), custom_mount_compare); - - /* Allocate working directories for the overlay file systems that need it */ - for (i = 0; i < arg_n_custom_mounts; i++) { - CustomMount *m = &arg_custom_mounts[i]; - - if (path_equal(m->destination, "/") && arg_userns_mode != USER_NAMESPACE_NO) { - - if (arg_userns_chown) { - log_error("--private-users-chown may not be combined with custom root mounts."); - return -EINVAL; - } else if (arg_uid_shift == UID_INVALID) { - log_error("--private-users with automatic UID shift may not be combined with custom root mounts."); - return -EINVAL; - } - } - - if (m->type != CUSTOM_MOUNT_OVERLAY) - continue; - - if (m->work_dir) - continue; - - if (m->read_only) - continue; - - r = tempfn_random(m->source, NULL, &m->work_dir); - if (r < 0) - return log_error_errno(r, "Failed to generate work directory from %s: %m", m->source); - } - - return 0; -} - -static int detect_unified_cgroup_hierarchy(void) { - const char *e; - int r; - - /* Allow the user to control whether the unified hierarchy is used */ - e = getenv("UNIFIED_CGROUP_HIERARCHY"); - if (e) { - r = parse_boolean(e); - if (r < 0) - return log_error_errno(r, "Failed to parse $UNIFIED_CGROUP_HIERARCHY."); - - arg_unified_cgroup_hierarchy = r; - return 0; - } - - /* Otherwise inherit the default from the host system */ - r = cg_unified(); - if (r < 0) - return log_error_errno(r, "Failed to determine whether the unified cgroups hierarchy is used: %m"); - - arg_unified_cgroup_hierarchy = r; - return 0; -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_PRIVATE_NETWORK, - ARG_UUID, - ARG_READ_ONLY, - ARG_CAPABILITY, - ARG_DROP_CAPABILITY, - ARG_LINK_JOURNAL, - ARG_BIND, - ARG_BIND_RO, - ARG_TMPFS, - ARG_OVERLAY, - ARG_OVERLAY_RO, - ARG_SHARE_SYSTEM, - ARG_REGISTER, - ARG_KEEP_UNIT, - ARG_NETWORK_INTERFACE, - ARG_NETWORK_MACVLAN, - ARG_NETWORK_IPVLAN, - ARG_NETWORK_BRIDGE, - ARG_NETWORK_ZONE, - ARG_NETWORK_VETH_EXTRA, - ARG_PERSONALITY, - ARG_VOLATILE, - ARG_TEMPLATE, - ARG_PROPERTY, - ARG_PRIVATE_USERS, - ARG_KILL_SIGNAL, - ARG_SETTINGS, - ARG_CHDIR, - ARG_PRIVATE_USERS_CHOWN, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "directory", required_argument, NULL, 'D' }, - { "template", required_argument, NULL, ARG_TEMPLATE }, - { "ephemeral", no_argument, NULL, 'x' }, - { "user", required_argument, NULL, 'u' }, - { "private-network", no_argument, NULL, ARG_PRIVATE_NETWORK }, - { "as-pid2", no_argument, NULL, 'a' }, - { "boot", no_argument, NULL, 'b' }, - { "uuid", required_argument, NULL, ARG_UUID }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "capability", required_argument, NULL, ARG_CAPABILITY }, - { "drop-capability", required_argument, NULL, ARG_DROP_CAPABILITY }, - { "link-journal", required_argument, NULL, ARG_LINK_JOURNAL }, - { "bind", required_argument, NULL, ARG_BIND }, - { "bind-ro", required_argument, NULL, ARG_BIND_RO }, - { "tmpfs", required_argument, NULL, ARG_TMPFS }, - { "overlay", required_argument, NULL, ARG_OVERLAY }, - { "overlay-ro", required_argument, NULL, ARG_OVERLAY_RO }, - { "machine", required_argument, NULL, 'M' }, - { "slice", required_argument, NULL, 'S' }, - { "setenv", required_argument, NULL, 'E' }, - { "selinux-context", required_argument, NULL, 'Z' }, - { "selinux-apifs-context", required_argument, NULL, 'L' }, - { "quiet", no_argument, NULL, 'q' }, - { "share-system", no_argument, NULL, ARG_SHARE_SYSTEM }, - { "register", required_argument, NULL, ARG_REGISTER }, - { "keep-unit", no_argument, NULL, ARG_KEEP_UNIT }, - { "network-interface", required_argument, NULL, ARG_NETWORK_INTERFACE }, - { "network-macvlan", required_argument, NULL, ARG_NETWORK_MACVLAN }, - { "network-ipvlan", required_argument, NULL, ARG_NETWORK_IPVLAN }, - { "network-veth", no_argument, NULL, 'n' }, - { "network-veth-extra", required_argument, NULL, ARG_NETWORK_VETH_EXTRA}, - { "network-bridge", required_argument, NULL, ARG_NETWORK_BRIDGE }, - { "network-zone", required_argument, NULL, ARG_NETWORK_ZONE }, - { "personality", required_argument, NULL, ARG_PERSONALITY }, - { "image", required_argument, NULL, 'i' }, - { "volatile", optional_argument, NULL, ARG_VOLATILE }, - { "port", required_argument, NULL, 'p' }, - { "property", required_argument, NULL, ARG_PROPERTY }, - { "private-users", optional_argument, NULL, ARG_PRIVATE_USERS }, - { "private-users-chown", optional_argument, NULL, ARG_PRIVATE_USERS_CHOWN}, - { "kill-signal", required_argument, NULL, ARG_KILL_SIGNAL }, - { "settings", required_argument, NULL, ARG_SETTINGS }, - { "chdir", required_argument, NULL, ARG_CHDIR }, - {} - }; - - int c, r; - const char *p, *e; - uint64_t plus = 0, minus = 0; - bool mask_all_settings = false, mask_no_settings = false; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "+hD:u:abL:M:jS:Z:qi:xp:nU", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case 'D': - r = parse_path_argument_and_warn(optarg, false, &arg_directory); - if (r < 0) - return r; - break; - - case ARG_TEMPLATE: - r = parse_path_argument_and_warn(optarg, false, &arg_template); - if (r < 0) - return r; - break; - - case 'i': - r = parse_path_argument_and_warn(optarg, false, &arg_image); - if (r < 0) - return r; - break; - - case 'x': - arg_ephemeral = true; - break; - - case 'u': - r = free_and_strdup(&arg_user, optarg); - if (r < 0) - return log_oom(); - - arg_settings_mask |= SETTING_USER; - break; - - case ARG_NETWORK_ZONE: { - char *j; - - j = strappend("vz-", optarg); - if (!j) - return log_oom(); - - if (!ifname_valid(j)) { - log_error("Network zone name not valid: %s", j); - free(j); - return -EINVAL; - } - - free(arg_network_zone); - arg_network_zone = j; - - arg_network_veth = true; - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; - break; - } - - case ARG_NETWORK_BRIDGE: - - if (!ifname_valid(optarg)) { - log_error("Bridge interface name not valid: %s", optarg); - return -EINVAL; - } - - r = free_and_strdup(&arg_network_bridge, optarg); - if (r < 0) - return log_oom(); - - /* fall through */ - - case 'n': - arg_network_veth = true; - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; - break; - - case ARG_NETWORK_VETH_EXTRA: - r = veth_extra_parse(&arg_network_veth_extra, optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --network-veth-extra= parameter: %s", optarg); - - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; - break; - - case ARG_NETWORK_INTERFACE: - - if (!ifname_valid(optarg)) { - log_error("Network interface name not valid: %s", optarg); - return -EINVAL; - } - - if (strv_extend(&arg_network_interfaces, optarg) < 0) - return log_oom(); - - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; - break; - - case ARG_NETWORK_MACVLAN: - - if (!ifname_valid(optarg)) { - log_error("MACVLAN network interface name not valid: %s", optarg); - return -EINVAL; - } - - if (strv_extend(&arg_network_macvlan, optarg) < 0) - return log_oom(); - - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; - break; - - case ARG_NETWORK_IPVLAN: - - if (!ifname_valid(optarg)) { - log_error("IPVLAN network interface name not valid: %s", optarg); - return -EINVAL; - } - - if (strv_extend(&arg_network_ipvlan, optarg) < 0) - return log_oom(); - - /* fall through */ - - case ARG_PRIVATE_NETWORK: - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; - break; - - case 'b': - if (arg_start_mode == START_PID2) { - log_error("--boot and --as-pid2 may not be combined."); - return -EINVAL; - } - - arg_start_mode = START_BOOT; - arg_settings_mask |= SETTING_START_MODE; - break; - - case 'a': - if (arg_start_mode == START_BOOT) { - log_error("--boot and --as-pid2 may not be combined."); - return -EINVAL; - } - - arg_start_mode = START_PID2; - arg_settings_mask |= SETTING_START_MODE; - break; - - case ARG_UUID: - r = sd_id128_from_string(optarg, &arg_uuid); - if (r < 0) { - log_error("Invalid UUID: %s", optarg); - return r; - } - - arg_settings_mask |= SETTING_MACHINE_ID; - break; - - case 'S': - arg_slice = optarg; - break; - - case 'M': - if (isempty(optarg)) - arg_machine = mfree(arg_machine); - else { - if (!machine_name_is_valid(optarg)) { - log_error("Invalid machine name: %s", optarg); - return -EINVAL; - } - - r = free_and_strdup(&arg_machine, optarg); - if (r < 0) - return log_oom(); - - break; - } - - case 'Z': - arg_selinux_context = optarg; - break; - - case 'L': - arg_selinux_apifs_context = optarg; - break; - - case ARG_READ_ONLY: - arg_read_only = true; - arg_settings_mask |= SETTING_READ_ONLY; - break; - - case ARG_CAPABILITY: - case ARG_DROP_CAPABILITY: { - p = optarg; - for (;;) { - _cleanup_free_ char *t = NULL; - - r = extract_first_word(&p, &t, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse capability %s.", t); - - if (r == 0) - break; - - if (streq(t, "all")) { - if (c == ARG_CAPABILITY) - plus = (uint64_t) -1; - else - minus = (uint64_t) -1; - } else { - int cap; - - cap = capability_from_name(t); - if (cap < 0) { - log_error("Failed to parse capability %s.", t); - return -EINVAL; - } - - if (c == ARG_CAPABILITY) - plus |= 1ULL << (uint64_t) cap; - else - minus |= 1ULL << (uint64_t) cap; - } - } - - arg_settings_mask |= SETTING_CAPABILITY; - break; - } - - case 'j': - arg_link_journal = LINK_GUEST; - arg_link_journal_try = true; - break; - - case ARG_LINK_JOURNAL: - if (streq(optarg, "auto")) { - arg_link_journal = LINK_AUTO; - arg_link_journal_try = false; - } else if (streq(optarg, "no")) { - arg_link_journal = LINK_NO; - arg_link_journal_try = false; - } else if (streq(optarg, "guest")) { - arg_link_journal = LINK_GUEST; - arg_link_journal_try = false; - } else if (streq(optarg, "host")) { - arg_link_journal = LINK_HOST; - arg_link_journal_try = false; - } else if (streq(optarg, "try-guest")) { - arg_link_journal = LINK_GUEST; - arg_link_journal_try = true; - } else if (streq(optarg, "try-host")) { - arg_link_journal = LINK_HOST; - arg_link_journal_try = true; - } else { - log_error("Failed to parse link journal mode %s", optarg); - return -EINVAL; - } - - break; - - case ARG_BIND: - case ARG_BIND_RO: - r = bind_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, optarg, c == ARG_BIND_RO); - if (r < 0) - return log_error_errno(r, "Failed to parse --bind(-ro)= argument %s: %m", optarg); - - arg_settings_mask |= SETTING_CUSTOM_MOUNTS; - break; - - case ARG_TMPFS: - r = tmpfs_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --tmpfs= argument %s: %m", optarg); - - arg_settings_mask |= SETTING_CUSTOM_MOUNTS; - break; - - case ARG_OVERLAY: - case ARG_OVERLAY_RO: { - _cleanup_free_ char *upper = NULL, *destination = NULL; - _cleanup_strv_free_ char **lower = NULL; - CustomMount *m; - unsigned n = 0; - char **i; - - r = strv_split_extract(&lower, optarg, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r == -ENOMEM) - return log_oom(); - else if (r < 0) { - log_error("Invalid overlay specification: %s", optarg); - return r; - } - - STRV_FOREACH(i, lower) { - if (!path_is_absolute(*i)) { - log_error("Overlay path %s is not absolute.", *i); - return -EINVAL; - } - - n++; - } - - if (n < 2) { - log_error("--overlay= needs at least two colon-separated directories specified."); - return -EINVAL; - } - - if (n == 2) { - /* If two parameters are specified, - * the first one is the lower, the - * second one the upper directory. And - * we'll also define the destination - * mount point the same as the upper. */ - upper = lower[1]; - lower[1] = NULL; - - destination = strdup(upper); - if (!destination) - return log_oom(); - - } else { - upper = lower[n - 2]; - destination = lower[n - 1]; - lower[n - 2] = NULL; - } - - m = custom_mount_add(&arg_custom_mounts, &arg_n_custom_mounts, CUSTOM_MOUNT_OVERLAY); - if (!m) - return log_oom(); - - m->destination = destination; - m->source = upper; - m->lower = lower; - m->read_only = c == ARG_OVERLAY_RO; - - upper = destination = NULL; - lower = NULL; - - arg_settings_mask |= SETTING_CUSTOM_MOUNTS; - break; - } - - case 'E': { - char **n; - - if (!env_assignment_is_valid(optarg)) { - log_error("Environment variable assignment '%s' is not valid.", optarg); - return -EINVAL; - } - - n = strv_env_set(arg_setenv, optarg); - if (!n) - return log_oom(); - - strv_free(arg_setenv); - arg_setenv = n; - - arg_settings_mask |= SETTING_ENVIRONMENT; - break; - } - - case 'q': - arg_quiet = true; - break; - - case ARG_SHARE_SYSTEM: - arg_share_system = true; - break; - - case ARG_REGISTER: - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --register= argument: %s", optarg); - return r; - } - - arg_register = r; - break; - - case ARG_KEEP_UNIT: - arg_keep_unit = true; - break; - - case ARG_PERSONALITY: - - arg_personality = personality_from_string(optarg); - if (arg_personality == PERSONALITY_INVALID) { - log_error("Unknown or unsupported personality '%s'.", optarg); - return -EINVAL; - } - - arg_settings_mask |= SETTING_PERSONALITY; - break; - - case ARG_VOLATILE: - - if (!optarg) - arg_volatile_mode = VOLATILE_YES; - else { - VolatileMode m; - - m = volatile_mode_from_string(optarg); - if (m < 0) { - log_error("Failed to parse --volatile= argument: %s", optarg); - return -EINVAL; - } else - arg_volatile_mode = m; - } - - arg_settings_mask |= SETTING_VOLATILE_MODE; - break; - - case 'p': - r = expose_port_parse(&arg_expose_ports, optarg); - if (r == -EEXIST) - return log_error_errno(r, "Duplicate port specification: %s", optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse host port %s: %m", optarg); - - arg_settings_mask |= SETTING_EXPOSE_PORTS; - break; - - case ARG_PROPERTY: - if (strv_extend(&arg_property, optarg) < 0) - return log_oom(); - - break; - - case ARG_PRIVATE_USERS: - - r = optarg ? parse_boolean(optarg) : 1; - if (r == 0) { - /* no: User namespacing off */ - arg_userns_mode = USER_NAMESPACE_NO; - arg_uid_shift = UID_INVALID; - arg_uid_range = UINT32_C(0x10000); - } else if (r > 0) { - /* yes: User namespacing on, UID range is read from root dir */ - arg_userns_mode = USER_NAMESPACE_FIXED; - arg_uid_shift = UID_INVALID; - arg_uid_range = UINT32_C(0x10000); - } else if (streq(optarg, "pick")) { - /* pick: User namespacing on, UID range is picked randomly */ - arg_userns_mode = USER_NAMESPACE_PICK; - arg_uid_shift = UID_INVALID; - arg_uid_range = UINT32_C(0x10000); - } else { - _cleanup_free_ char *buffer = NULL; - const char *range, *shift; - - /* anything else: User namespacing on, UID range is explicitly configured */ - - range = strchr(optarg, ':'); - if (range) { - buffer = strndup(optarg, range - optarg); - if (!buffer) - return log_oom(); - shift = buffer; - - range++; - if (safe_atou32(range, &arg_uid_range) < 0 || arg_uid_range <= 0) { - log_error("Failed to parse UID range: %s", range); - return -EINVAL; - } - } else - shift = optarg; - - if (parse_uid(shift, &arg_uid_shift) < 0) { - log_error("Failed to parse UID: %s", optarg); - return -EINVAL; - } - - arg_userns_mode = USER_NAMESPACE_FIXED; - } - - arg_settings_mask |= SETTING_USERNS; - break; - - case 'U': - if (userns_supported()) { - arg_userns_mode = USER_NAMESPACE_PICK; - arg_uid_shift = UID_INVALID; - arg_uid_range = UINT32_C(0x10000); - - arg_settings_mask |= SETTING_USERNS; - } - - break; - - case ARG_PRIVATE_USERS_CHOWN: - arg_userns_chown = true; - - arg_settings_mask |= SETTING_USERNS; - break; - - case ARG_KILL_SIGNAL: - arg_kill_signal = signal_from_string_try_harder(optarg); - if (arg_kill_signal < 0) { - log_error("Cannot parse signal: %s", optarg); - return -EINVAL; - } - - arg_settings_mask |= SETTING_KILL_SIGNAL; - break; - - case ARG_SETTINGS: - - /* no → do not read files - * yes → read files, do not override cmdline, trust only subset - * override → read files, override cmdline, trust only subset - * trusted → read files, do not override cmdline, trust all - */ - - r = parse_boolean(optarg); - if (r < 0) { - if (streq(optarg, "trusted")) { - mask_all_settings = false; - mask_no_settings = false; - arg_settings_trusted = true; - - } else if (streq(optarg, "override")) { - mask_all_settings = false; - mask_no_settings = true; - arg_settings_trusted = -1; - } else - return log_error_errno(r, "Failed to parse --settings= argument: %s", optarg); - } else if (r > 0) { - /* yes */ - mask_all_settings = false; - mask_no_settings = false; - arg_settings_trusted = -1; - } else { - /* no */ - mask_all_settings = true; - mask_no_settings = false; - arg_settings_trusted = false; - } - - break; - - case ARG_CHDIR: - if (!path_is_absolute(optarg)) { - log_error("Working directory %s is not an absolute path.", optarg); - return -EINVAL; - } - - r = free_and_strdup(&arg_chdir, optarg); - if (r < 0) - return log_oom(); - - arg_settings_mask |= SETTING_WORKING_DIRECTORY; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (arg_share_system) - arg_register = false; - - if (arg_userns_mode == USER_NAMESPACE_PICK) - arg_userns_chown = true; - - if (arg_start_mode != START_PID1 && arg_share_system) { - log_error("--boot and --share-system may not be combined."); - return -EINVAL; - } - - if (arg_keep_unit && cg_pid_get_owner_uid(0, NULL) >= 0) { - log_error("--keep-unit may not be used when invoked from a user session."); - return -EINVAL; - } - - if (arg_directory && arg_image) { - log_error("--directory= and --image= may not be combined."); - return -EINVAL; - } - - if (arg_template && arg_image) { - log_error("--template= and --image= may not be combined."); - return -EINVAL; - } - - if (arg_template && !(arg_directory || arg_machine)) { - log_error("--template= needs --directory= or --machine=."); - return -EINVAL; - } - - if (arg_ephemeral && arg_template) { - log_error("--ephemeral and --template= may not be combined."); - return -EINVAL; - } - - if (arg_ephemeral && arg_image) { - log_error("--ephemeral and --image= may not be combined."); - return -EINVAL; - } - - if (arg_ephemeral && !IN_SET(arg_link_journal, LINK_NO, LINK_AUTO)) { - log_error("--ephemeral and --link-journal= may not be combined."); - return -EINVAL; - } - - if (arg_userns_mode != USER_NAMESPACE_NO && !userns_supported()) { - log_error("--private-users= is not supported, kernel compiled without user namespace support."); - return -EOPNOTSUPP; - } - - if (arg_userns_chown && arg_read_only) { - log_error("--read-only and --private-users-chown may not be combined."); - return -EINVAL; - } - - if (arg_network_bridge && arg_network_zone) { - log_error("--network-bridge= and --network-zone= may not be combined."); - return -EINVAL; - } - - if (argc > optind) { - arg_parameters = strv_copy(argv + optind); - if (!arg_parameters) - return log_oom(); - - arg_settings_mask |= SETTING_START_MODE; - } - - /* Load all settings from .nspawn files */ - if (mask_no_settings) - arg_settings_mask = 0; - - /* Don't load any settings from .nspawn files */ - if (mask_all_settings) - arg_settings_mask = _SETTINGS_MASK_ALL; - - arg_retain = (arg_retain | plus | (arg_private_network ? 1ULL << CAP_NET_ADMIN : 0)) & ~minus; - - r = detect_unified_cgroup_hierarchy(); - if (r < 0) - return r; - - e = getenv("SYSTEMD_NSPAWN_CONTAINER_SERVICE"); - if (e) - arg_container_service_name = e; - - return 1; -} - -static int verify_arguments(void) { - - if (arg_volatile_mode != VOLATILE_NO && arg_read_only) { - log_error("Cannot combine --read-only with --volatile. Note that --volatile already implies a read-only base hierarchy."); - return -EINVAL; - } - - if (arg_expose_ports && !arg_private_network) { - log_error("Cannot use --port= without private networking."); - return -EINVAL; - } - -#ifndef HAVE_LIBIPTC - if (arg_expose_ports) { - log_error("--port= is not supported, compiled without libiptc support."); - return -EOPNOTSUPP; - } -#endif - - if (arg_start_mode == START_BOOT && arg_kill_signal <= 0) - arg_kill_signal = SIGRTMIN+3; - - return 0; -} - -static int userns_lchown(const char *p, uid_t uid, gid_t gid) { - assert(p); - - if (arg_userns_mode == USER_NAMESPACE_NO) - return 0; - - if (uid == UID_INVALID && gid == GID_INVALID) - return 0; - - if (uid != UID_INVALID) { - uid += arg_uid_shift; - - if (uid < arg_uid_shift || uid >= arg_uid_shift + arg_uid_range) - return -EOVERFLOW; - } - - if (gid != GID_INVALID) { - gid += (gid_t) arg_uid_shift; - - if (gid < (gid_t) arg_uid_shift || gid >= (gid_t) (arg_uid_shift + arg_uid_range)) - return -EOVERFLOW; - } - - if (lchown(p, uid, gid) < 0) - return -errno; - - return 0; -} - -static int userns_mkdir(const char *root, const char *path, mode_t mode, uid_t uid, gid_t gid) { - const char *q; - - q = prefix_roota(root, path); - if (mkdir(q, mode) < 0) { - if (errno == EEXIST) - return 0; - return -errno; - } - - return userns_lchown(q, uid, gid); -} - -static int setup_timezone(const char *dest) { - _cleanup_free_ char *p = NULL, *q = NULL; - const char *where, *check, *what; - char *z, *y; - int r; - - assert(dest); - - /* Fix the timezone, if possible */ - r = readlink_malloc("/etc/localtime", &p); - if (r < 0) { - log_warning("/etc/localtime is not a symlink, not updating container timezone."); - return 0; - } - - z = path_startswith(p, "../usr/share/zoneinfo/"); - if (!z) - z = path_startswith(p, "/usr/share/zoneinfo/"); - if (!z) { - log_warning("/etc/localtime does not point into /usr/share/zoneinfo/, not updating container timezone."); - return 0; - } - - where = prefix_roota(dest, "/etc/localtime"); - r = readlink_malloc(where, &q); - if (r >= 0) { - y = path_startswith(q, "../usr/share/zoneinfo/"); - if (!y) - y = path_startswith(q, "/usr/share/zoneinfo/"); - - /* Already pointing to the right place? Then do nothing .. */ - if (y && streq(y, z)) - return 0; - } - - check = strjoina("/usr/share/zoneinfo/", z); - check = prefix_roota(dest, check); - if (laccess(check, F_OK) < 0) { - log_warning("Timezone %s does not exist in container, not updating container timezone.", z); - return 0; - } - - r = unlink(where); - if (r < 0 && errno != ENOENT) { - log_error_errno(errno, "Failed to remove existing timezone info %s in container: %m", where); - return 0; - } - - what = strjoina("../usr/share/zoneinfo/", z); - if (symlink(what, where) < 0) { - log_error_errno(errno, "Failed to correct timezone of container: %m"); - return 0; - } - - r = userns_lchown(where, 0, 0); - if (r < 0) - return log_warning_errno(r, "Failed to chown /etc/localtime: %m"); - - return 0; -} - -static int setup_resolv_conf(const char *dest) { - const char *where = NULL; - int r; - - assert(dest); - - if (arg_private_network) - return 0; - - /* Fix resolv.conf, if possible */ - where = prefix_roota(dest, "/etc/resolv.conf"); - - r = copy_file("/etc/resolv.conf", where, O_TRUNC|O_NOFOLLOW, 0644, 0); - if (r < 0) { - /* If the file already exists as symlink, let's - * suppress the warning, under the assumption that - * resolved or something similar runs inside and the - * symlink points there. - * - * If the disk image is read-only, there's also no - * point in complaining. - */ - log_full_errno(IN_SET(r, -ELOOP, -EROFS) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to copy /etc/resolv.conf to %s: %m", where); - return 0; - } - - r = userns_lchown(where, 0, 0); - if (r < 0) - log_warning_errno(r, "Failed to chown /etc/resolv.conf: %m"); - - return 0; -} - -static char* id128_format_as_uuid(sd_id128_t id, char s[37]) { - assert(s); - - snprintf(s, 37, - "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", - SD_ID128_FORMAT_VAL(id)); - - return s; -} - -static int setup_boot_id(const char *dest) { - const char *from, *to; - sd_id128_t rnd = {}; - char as_uuid[37]; - int r; - - if (arg_share_system) - return 0; - - /* Generate a new randomized boot ID, so that each boot-up of - * the container gets a new one */ - - from = prefix_roota(dest, "/run/proc-sys-kernel-random-boot-id"); - to = prefix_roota(dest, "/proc/sys/kernel/random/boot_id"); - - r = sd_id128_randomize(&rnd); - if (r < 0) - return log_error_errno(r, "Failed to generate random boot id: %m"); - - id128_format_as_uuid(rnd, as_uuid); - - r = write_string_file(from, as_uuid, WRITE_STRING_FILE_CREATE); - if (r < 0) - return log_error_errno(r, "Failed to write boot id: %m"); - - if (mount(from, to, NULL, MS_BIND, NULL) < 0) - r = log_error_errno(errno, "Failed to bind mount boot id: %m"); - else if (mount(NULL, to, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NODEV, NULL) < 0) - log_warning_errno(errno, "Failed to make boot id read-only: %m"); - - unlink(from); - return r; -} - -static int copy_devnodes(const char *dest) { - - static const char devnodes[] = - "null\0" - "zero\0" - "full\0" - "random\0" - "urandom\0" - "tty\0" - "net/tun\0"; - - const char *d; - int r = 0; - _cleanup_umask_ mode_t u; - - assert(dest); - - u = umask(0000); - - /* Create /dev/net, so that we can create /dev/net/tun in it */ - if (userns_mkdir(dest, "/dev/net", 0755, 0, 0) < 0) - return log_error_errno(r, "Failed to create /dev/net directory: %m"); - - NULSTR_FOREACH(d, devnodes) { - _cleanup_free_ char *from = NULL, *to = NULL; - struct stat st; - - from = strappend("/dev/", d); - to = prefix_root(dest, from); - - if (stat(from, &st) < 0) { - - if (errno != ENOENT) - return log_error_errno(errno, "Failed to stat %s: %m", from); - - } else if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) { - - log_error("%s is not a char or block device, cannot copy.", from); - return -EIO; - - } else { - if (mknod(to, st.st_mode, st.st_rdev) < 0) { - if (errno != EPERM) - return log_error_errno(errno, "mknod(%s) failed: %m", to); - - /* Some systems abusively restrict mknod but - * allow bind mounts. */ - r = touch(to); - if (r < 0) - return log_error_errno(r, "touch (%s) failed: %m", to); - if (mount(from, to, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Both mknod and bind mount (%s) failed: %m", to); - } - - r = userns_lchown(to, 0, 0); - if (r < 0) - return log_error_errno(r, "chown() of device node %s failed: %m", to); - } - } - - return r; -} - -static int setup_pts(const char *dest) { - _cleanup_free_ char *options = NULL; - const char *p; - int r; - -#ifdef HAVE_SELINUX - if (arg_selinux_apifs_context) - (void) asprintf(&options, - "newinstance,ptmxmode=0666,mode=620,gid=" GID_FMT ",context=\"%s\"", - arg_uid_shift + TTY_GID, - arg_selinux_apifs_context); - else -#endif - (void) asprintf(&options, - "newinstance,ptmxmode=0666,mode=620,gid=" GID_FMT, - arg_uid_shift + TTY_GID); - - if (!options) - return log_oom(); - - /* Mount /dev/pts itself */ - p = prefix_roota(dest, "/dev/pts"); - if (mkdir(p, 0755) < 0) - return log_error_errno(errno, "Failed to create /dev/pts: %m"); - if (mount("devpts", p, "devpts", MS_NOSUID|MS_NOEXEC, options) < 0) - return log_error_errno(errno, "Failed to mount /dev/pts: %m"); - r = userns_lchown(p, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to chown /dev/pts: %m"); - - /* Create /dev/ptmx symlink */ - p = prefix_roota(dest, "/dev/ptmx"); - if (symlink("pts/ptmx", p) < 0) - return log_error_errno(errno, "Failed to create /dev/ptmx symlink: %m"); - r = userns_lchown(p, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to chown /dev/ptmx: %m"); - - /* And fix /dev/pts/ptmx ownership */ - p = prefix_roota(dest, "/dev/pts/ptmx"); - r = userns_lchown(p, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to chown /dev/pts/ptmx: %m"); - - return 0; -} - -static int setup_dev_console(const char *dest, const char *console) { - _cleanup_umask_ mode_t u; - const char *to; - int r; - - assert(dest); - assert(console); - - u = umask(0000); - - r = chmod_and_chown(console, 0600, arg_uid_shift, arg_uid_shift); - if (r < 0) - return log_error_errno(r, "Failed to correct access mode for TTY: %m"); - - /* We need to bind mount the right tty to /dev/console since - * ptys can only exist on pts file systems. To have something - * to bind mount things on we create a empty regular file. */ - - to = prefix_roota(dest, "/dev/console"); - r = touch(to); - if (r < 0) - return log_error_errno(r, "touch() for /dev/console failed: %m"); - - if (mount(console, to, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Bind mount for /dev/console failed: %m"); - - return 0; -} - -static int setup_kmsg(const char *dest, int kmsg_socket) { - const char *from, *to; - _cleanup_umask_ mode_t u; - int fd, r; - - assert(kmsg_socket >= 0); - - u = umask(0000); - - /* We create the kmsg FIFO as /run/kmsg, but immediately - * delete it after bind mounting it to /proc/kmsg. While FIFOs - * on the reading side behave very similar to /proc/kmsg, - * their writing side behaves differently from /dev/kmsg in - * that writing blocks when nothing is reading. In order to - * avoid any problems with containers deadlocking due to this - * we simply make /dev/kmsg unavailable to the container. */ - from = prefix_roota(dest, "/run/kmsg"); - to = prefix_roota(dest, "/proc/kmsg"); - - if (mkfifo(from, 0600) < 0) - return log_error_errno(errno, "mkfifo() for /run/kmsg failed: %m"); - if (mount(from, to, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Bind mount for /proc/kmsg failed: %m"); - - fd = open(from, O_RDWR|O_NDELAY|O_CLOEXEC); - if (fd < 0) - return log_error_errno(errno, "Failed to open fifo: %m"); - - /* Store away the fd in the socket, so that it stays open as - * long as we run the child */ - r = send_one_fd(kmsg_socket, fd, 0); - safe_close(fd); - - if (r < 0) - return log_error_errno(r, "Failed to send FIFO fd: %m"); - - /* And now make the FIFO unavailable as /run/kmsg... */ - (void) unlink(from); - - return 0; -} - -static int on_address_change(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { - union in_addr_union *exposed = userdata; - - assert(rtnl); - assert(m); - assert(exposed); - - expose_port_execute(rtnl, arg_expose_ports, exposed); - return 0; -} - -static int setup_hostname(void) { - - if (arg_share_system) - return 0; - - if (sethostname_idempotent(arg_machine) < 0) - return -errno; - - return 0; -} - -static int setup_journal(const char *directory) { - sd_id128_t this_id; - _cleanup_free_ char *d = NULL; - const char *p, *q; - bool try; - char id[33]; - int r; - - /* Don't link journals in ephemeral mode */ - if (arg_ephemeral) - return 0; - - if (arg_link_journal == LINK_NO) - return 0; - - try = arg_link_journal_try || arg_link_journal == LINK_AUTO; - - r = sd_id128_get_machine(&this_id); - if (r < 0) - return log_error_errno(r, "Failed to retrieve machine ID: %m"); - - if (sd_id128_equal(arg_uuid, this_id)) { - log_full(try ? LOG_WARNING : LOG_ERR, - "Host and machine ids are equal (%s): refusing to link journals", sd_id128_to_string(arg_uuid, id)); - if (try) - return 0; - return -EEXIST; - } - - r = userns_mkdir(directory, "/var", 0755, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to create /var: %m"); - - r = userns_mkdir(directory, "/var/log", 0755, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to create /var/log: %m"); - - r = userns_mkdir(directory, "/var/log/journal", 0755, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to create /var/log/journal: %m"); - - (void) sd_id128_to_string(arg_uuid, id); - - p = strjoina("/var/log/journal/", id); - q = prefix_roota(directory, p); - - if (path_is_mount_point(p, 0) > 0) { - if (try) - return 0; - - log_error("%s: already a mount point, refusing to use for journal", p); - return -EEXIST; - } - - if (path_is_mount_point(q, 0) > 0) { - if (try) - return 0; - - log_error("%s: already a mount point, refusing to use for journal", q); - return -EEXIST; - } - - r = readlink_and_make_absolute(p, &d); - if (r >= 0) { - if ((arg_link_journal == LINK_GUEST || - arg_link_journal == LINK_AUTO) && - path_equal(d, q)) { - - r = userns_mkdir(directory, p, 0755, 0, 0); - if (r < 0) - log_warning_errno(r, "Failed to create directory %s: %m", q); - return 0; - } - - if (unlink(p) < 0) - return log_error_errno(errno, "Failed to remove symlink %s: %m", p); - } else if (r == -EINVAL) { - - if (arg_link_journal == LINK_GUEST && - rmdir(p) < 0) { - - if (errno == ENOTDIR) { - log_error("%s already exists and is neither a symlink nor a directory", p); - return r; - } else - return log_error_errno(errno, "Failed to remove %s: %m", p); - } - } else if (r != -ENOENT) - return log_error_errno(r, "readlink(%s) failed: %m", p); - - if (arg_link_journal == LINK_GUEST) { - - if (symlink(q, p) < 0) { - if (try) { - log_debug_errno(errno, "Failed to symlink %s to %s, skipping journal setup: %m", q, p); - return 0; - } else - return log_error_errno(errno, "Failed to symlink %s to %s: %m", q, p); - } - - r = userns_mkdir(directory, p, 0755, 0, 0); - if (r < 0) - log_warning_errno(r, "Failed to create directory %s: %m", q); - return 0; - } - - if (arg_link_journal == LINK_HOST) { - /* don't create parents here — if the host doesn't have - * permanent journal set up, don't force it here */ - - if (mkdir(p, 0755) < 0 && errno != EEXIST) { - if (try) { - log_debug_errno(errno, "Failed to create %s, skipping journal setup: %m", p); - return 0; - } else - return log_error_errno(errno, "Failed to create %s: %m", p); - } - - } else if (access(p, F_OK) < 0) - return 0; - - if (dir_is_empty(q) == 0) - log_warning("%s is not empty, proceeding anyway.", q); - - r = userns_mkdir(directory, p, 0755, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to create %s: %m", q); - - if (mount(p, q, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Failed to bind mount journal from host into guest: %m"); - - return 0; -} - -static int drop_capabilities(void) { - return capability_bounding_set_drop(arg_retain, false); -} - -static int reset_audit_loginuid(void) { - _cleanup_free_ char *p = NULL; - int r; - - if (arg_share_system) - return 0; - - r = read_one_line_file("/proc/self/loginuid", &p); - if (r == -ENOENT) - return 0; - if (r < 0) - return log_error_errno(r, "Failed to read /proc/self/loginuid: %m"); - - /* Already reset? */ - if (streq(p, "4294967295")) - return 0; - - r = write_string_file("/proc/self/loginuid", "4294967295", 0); - if (r < 0) { - log_error_errno(r, - "Failed to reset audit login UID. This probably means that your kernel is too\n" - "old and you have audit enabled. Note that the auditing subsystem is known to\n" - "be incompatible with containers on old kernels. Please make sure to upgrade\n" - "your kernel or to off auditing with 'audit=0' on the kernel command line before\n" - "using systemd-nspawn. Sleeping for 5s... (%m)"); - - sleep(5); - } - - return 0; -} - -static int setup_seccomp(void) { - -#ifdef HAVE_SECCOMP - static const struct { - uint64_t capability; - int syscall_num; - } blacklist[] = { - { CAP_SYS_RAWIO, SCMP_SYS(iopl) }, - { CAP_SYS_RAWIO, SCMP_SYS(ioperm) }, - { CAP_SYS_BOOT, SCMP_SYS(kexec_load) }, - { CAP_SYS_ADMIN, SCMP_SYS(swapon) }, - { CAP_SYS_ADMIN, SCMP_SYS(swapoff) }, - { CAP_SYS_ADMIN, SCMP_SYS(open_by_handle_at) }, - { CAP_SYS_MODULE, SCMP_SYS(init_module) }, - { CAP_SYS_MODULE, SCMP_SYS(finit_module) }, - { CAP_SYS_MODULE, SCMP_SYS(delete_module) }, - { CAP_SYSLOG, SCMP_SYS(syslog) }, - }; - - scmp_filter_ctx seccomp; - unsigned i; - int r; - - seccomp = seccomp_init(SCMP_ACT_ALLOW); - if (!seccomp) - return log_oom(); - - r = seccomp_add_secondary_archs(seccomp); - if (r < 0) { - log_error_errno(r, "Failed to add secondary archs to seccomp filter: %m"); - goto finish; - } - - for (i = 0; i < ELEMENTSOF(blacklist); i++) { - if (arg_retain & (1ULL << blacklist[i].capability)) - continue; - - r = seccomp_rule_add(seccomp, SCMP_ACT_ERRNO(EPERM), blacklist[i].syscall_num, 0); - if (r == -EFAULT) - continue; /* unknown syscall */ - if (r < 0) { - log_error_errno(r, "Failed to block syscall: %m"); - goto finish; - } - } - - /* - Audit is broken in containers, much of the userspace audit - hookup will fail if running inside a container. We don't - care and just turn off creation of audit sockets. - - This will make socket(AF_NETLINK, *, NETLINK_AUDIT) fail - with EAFNOSUPPORT which audit userspace uses as indication - that audit is disabled in the kernel. - */ - - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EAFNOSUPPORT), - SCMP_SYS(socket), - 2, - SCMP_A0(SCMP_CMP_EQ, AF_NETLINK), - SCMP_A2(SCMP_CMP_EQ, NETLINK_AUDIT)); - if (r < 0) { - log_error_errno(r, "Failed to add audit seccomp rule: %m"); - goto finish; - } - - r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); - if (r < 0) { - log_error_errno(r, "Failed to unset NO_NEW_PRIVS: %m"); - goto finish; - } - - r = seccomp_load(seccomp); - if (r == -EINVAL) { - log_debug_errno(r, "Kernel is probably not configured with CONFIG_SECCOMP. Disabling seccomp audit filter: %m"); - r = 0; - goto finish; - } - if (r < 0) { - log_error_errno(r, "Failed to install seccomp audit filter: %m"); - goto finish; - } - -finish: - seccomp_release(seccomp); - return r; -#else - return 0; -#endif - -} - -static int setup_propagate(const char *root) { - const char *p, *q; - int r; - - (void) mkdir_p("/run/systemd/nspawn/", 0755); - (void) mkdir_p("/run/systemd/nspawn/propagate", 0600); - p = strjoina("/run/systemd/nspawn/propagate/", arg_machine); - (void) mkdir_p(p, 0600); - - r = userns_mkdir(root, "/run/systemd", 0755, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to create /run/systemd: %m"); - - r = userns_mkdir(root, "/run/systemd/nspawn", 0755, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to create /run/systemd/nspawn: %m"); - - r = userns_mkdir(root, "/run/systemd/nspawn/incoming", 0600, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to create /run/systemd/nspawn/incoming: %m"); - - q = prefix_roota(root, "/run/systemd/nspawn/incoming"); - if (mount(p, q, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Failed to install propagation bind mount."); - - if (mount(NULL, q, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL) < 0) - return log_error_errno(errno, "Failed to make propagation mount read-only"); - - return 0; -} - -static int setup_image(char **device_path, int *loop_nr) { - struct loop_info64 info = { - .lo_flags = LO_FLAGS_AUTOCLEAR|LO_FLAGS_PARTSCAN - }; - _cleanup_close_ int fd = -1, control = -1, loop = -1; - _cleanup_free_ char* loopdev = NULL; - struct stat st; - int r, nr; - - assert(device_path); - assert(loop_nr); - assert(arg_image); - - fd = open(arg_image, O_CLOEXEC|(arg_read_only ? O_RDONLY : O_RDWR)|O_NONBLOCK|O_NOCTTY); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", arg_image); - - if (fstat(fd, &st) < 0) - return log_error_errno(errno, "Failed to stat %s: %m", arg_image); - - if (S_ISBLK(st.st_mode)) { - char *p; - - p = strdup(arg_image); - if (!p) - return log_oom(); - - *device_path = p; - - *loop_nr = -1; - - r = fd; - fd = -1; - - return r; - } - - if (!S_ISREG(st.st_mode)) { - log_error("%s is not a regular file or block device.", arg_image); - return -EINVAL; - } - - control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); - if (control < 0) - return log_error_errno(errno, "Failed to open /dev/loop-control: %m"); - - nr = ioctl(control, LOOP_CTL_GET_FREE); - if (nr < 0) - return log_error_errno(errno, "Failed to allocate loop device: %m"); - - if (asprintf(&loopdev, "/dev/loop%i", nr) < 0) - return log_oom(); - - loop = open(loopdev, O_CLOEXEC|(arg_read_only ? O_RDONLY : O_RDWR)|O_NONBLOCK|O_NOCTTY); - if (loop < 0) - return log_error_errno(errno, "Failed to open loop device %s: %m", loopdev); - - if (ioctl(loop, LOOP_SET_FD, fd) < 0) - return log_error_errno(errno, "Failed to set loopback file descriptor on %s: %m", loopdev); - - if (arg_read_only) - info.lo_flags |= LO_FLAGS_READ_ONLY; - - if (ioctl(loop, LOOP_SET_STATUS64, &info) < 0) - return log_error_errno(errno, "Failed to set loopback settings on %s: %m", loopdev); - - *device_path = loopdev; - loopdev = NULL; - - *loop_nr = nr; - - r = loop; - loop = -1; - - return r; -} - -#define PARTITION_TABLE_BLURB \ - "Note that the disk image needs to either contain only a single MBR partition of\n" \ - "type 0x83 that is marked bootable, or a single GPT partition of type " \ - "0FC63DAF-8483-4772-8E79-3D69D8477DE4 or follow\n" \ - " http://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/\n" \ - "to be bootable with systemd-nspawn." - -static int dissect_image( - int fd, - char **root_device, bool *root_device_rw, - char **home_device, bool *home_device_rw, - char **srv_device, bool *srv_device_rw, - bool *secondary) { - -#ifdef HAVE_BLKID - int home_nr = -1, srv_nr = -1; -#ifdef GPT_ROOT_NATIVE - int root_nr = -1; -#endif -#ifdef GPT_ROOT_SECONDARY - int secondary_root_nr = -1; -#endif - _cleanup_free_ char *home = NULL, *root = NULL, *secondary_root = NULL, *srv = NULL, *generic = NULL; - _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; - _cleanup_udev_device_unref_ struct udev_device *d = NULL; - _cleanup_blkid_free_probe_ blkid_probe b = NULL; - _cleanup_udev_unref_ struct udev *udev = NULL; - struct udev_list_entry *first, *item; - bool home_rw = true, root_rw = true, secondary_root_rw = true, srv_rw = true, generic_rw = true; - bool is_gpt, is_mbr, multiple_generic = false; - const char *pttype = NULL; - blkid_partlist pl; - struct stat st; - unsigned i; - int r; - - assert(fd >= 0); - assert(root_device); - assert(home_device); - assert(srv_device); - assert(secondary); - assert(arg_image); - - b = blkid_new_probe(); - if (!b) - return log_oom(); - - errno = 0; - r = blkid_probe_set_device(b, fd, 0, 0); - if (r != 0) { - if (errno == 0) - return log_oom(); - - return log_error_errno(errno, "Failed to set device on blkid probe: %m"); - } - - blkid_probe_enable_partitions(b, 1); - blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); - - errno = 0; - r = blkid_do_safeprobe(b); - if (r == -2 || r == 1) { - log_error("Failed to identify any partition table on\n" - " %s\n" - PARTITION_TABLE_BLURB, arg_image); - return -EINVAL; - } else if (r != 0) { - if (errno == 0) - errno = EIO; - return log_error_errno(errno, "Failed to probe: %m"); - } - - (void) blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL); - - is_gpt = streq_ptr(pttype, "gpt"); - is_mbr = streq_ptr(pttype, "dos"); - - if (!is_gpt && !is_mbr) { - log_error("No GPT or MBR partition table discovered on\n" - " %s\n" - PARTITION_TABLE_BLURB, arg_image); - return -EINVAL; - } - - errno = 0; - pl = blkid_probe_get_partitions(b); - if (!pl) { - if (errno == 0) - return log_oom(); - - log_error("Failed to list partitions of %s", arg_image); - return -errno; - } - - udev = udev_new(); - if (!udev) - return log_oom(); - - if (fstat(fd, &st) < 0) - return log_error_errno(errno, "Failed to stat block device: %m"); - - d = udev_device_new_from_devnum(udev, 'b', st.st_rdev); - if (!d) - return log_oom(); - - for (i = 0;; i++) { - int n, m; - - if (i >= 10) { - log_error("Kernel partitions never appeared."); - return -ENXIO; - } - - e = udev_enumerate_new(udev); - if (!e) - return log_oom(); - - r = udev_enumerate_add_match_parent(e, d); - if (r < 0) - return log_oom(); - - r = udev_enumerate_scan_devices(e); - if (r < 0) - return log_error_errno(r, "Failed to scan for partition devices of %s: %m", arg_image); - - /* Count the partitions enumerated by the kernel */ - n = 0; - first = udev_enumerate_get_list_entry(e); - udev_list_entry_foreach(item, first) - n++; - - /* Count the partitions enumerated by blkid */ - m = blkid_partlist_numof_partitions(pl); - if (n == m + 1) - break; - if (n > m + 1) { - log_error("blkid and kernel partition list do not match."); - return -EIO; - } - if (n < m + 1) { - unsigned j; - - /* The kernel has probed fewer partitions than - * blkid? Maybe the kernel prober is still - * running or it got EBUSY because udev - * already opened the device. Let's reprobe - * the device, which is a synchronous call - * that waits until probing is complete. */ - - for (j = 0; j < 20; j++) { - - r = ioctl(fd, BLKRRPART, 0); - if (r < 0) - r = -errno; - if (r >= 0 || r != -EBUSY) - break; - - /* If something else has the device - * open, such as an udev rule, the - * ioctl will return EBUSY. Since - * there's no way to wait until it - * isn't busy anymore, let's just wait - * a bit, and try again. - * - * This is really something they - * should fix in the kernel! */ - - usleep(50 * USEC_PER_MSEC); - } - - if (r < 0) - return log_error_errno(r, "Failed to reread partition table: %m"); - } - - e = udev_enumerate_unref(e); - } - - first = udev_enumerate_get_list_entry(e); - udev_list_entry_foreach(item, first) { - _cleanup_udev_device_unref_ struct udev_device *q; - const char *node; - unsigned long long flags; - blkid_partition pp; - dev_t qn; - int nr; - - errno = 0; - q = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); - if (!q) { - if (!errno) - errno = ENOMEM; - - return log_error_errno(errno, "Failed to get partition device of %s: %m", arg_image); - } - - qn = udev_device_get_devnum(q); - if (major(qn) == 0) - continue; - - if (st.st_rdev == qn) - continue; - - node = udev_device_get_devnode(q); - if (!node) - continue; - - pp = blkid_partlist_devno_to_partition(pl, qn); - if (!pp) - continue; - - flags = blkid_partition_get_flags(pp); - - nr = blkid_partition_get_partno(pp); - if (nr < 0) - continue; - - if (is_gpt) { - sd_id128_t type_id; - const char *stype; - - if (flags & GPT_FLAG_NO_AUTO) - continue; - - stype = blkid_partition_get_type_string(pp); - if (!stype) - continue; - - if (sd_id128_from_string(stype, &type_id) < 0) - continue; - - if (sd_id128_equal(type_id, GPT_HOME)) { - - if (home && nr >= home_nr) - continue; - - home_nr = nr; - home_rw = !(flags & GPT_FLAG_READ_ONLY); - - r = free_and_strdup(&home, node); - if (r < 0) - return log_oom(); - - } else if (sd_id128_equal(type_id, GPT_SRV)) { - - if (srv && nr >= srv_nr) - continue; - - srv_nr = nr; - srv_rw = !(flags & GPT_FLAG_READ_ONLY); - - r = free_and_strdup(&srv, node); - if (r < 0) - return log_oom(); - } -#ifdef GPT_ROOT_NATIVE - else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE)) { - - if (root && nr >= root_nr) - continue; - - root_nr = nr; - root_rw = !(flags & GPT_FLAG_READ_ONLY); - - r = free_and_strdup(&root, node); - if (r < 0) - return log_oom(); - } -#endif -#ifdef GPT_ROOT_SECONDARY - else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY)) { - - if (secondary_root && nr >= secondary_root_nr) - continue; - - secondary_root_nr = nr; - secondary_root_rw = !(flags & GPT_FLAG_READ_ONLY); - - r = free_and_strdup(&secondary_root, node); - if (r < 0) - return log_oom(); - } -#endif - else if (sd_id128_equal(type_id, GPT_LINUX_GENERIC)) { - - if (generic) - multiple_generic = true; - else { - generic_rw = !(flags & GPT_FLAG_READ_ONLY); - - r = free_and_strdup(&generic, node); - if (r < 0) - return log_oom(); - } - } - - } else if (is_mbr) { - int type; - - if (flags != 0x80) /* Bootable flag */ - continue; - - type = blkid_partition_get_type(pp); - if (type != 0x83) /* Linux partition */ - continue; - - if (generic) - multiple_generic = true; - else { - generic_rw = true; - - r = free_and_strdup(&root, node); - if (r < 0) - return log_oom(); - } - } - } - - if (root) { - *root_device = root; - root = NULL; - - *root_device_rw = root_rw; - *secondary = false; - } else if (secondary_root) { - *root_device = secondary_root; - secondary_root = NULL; - - *root_device_rw = secondary_root_rw; - *secondary = true; - } else if (generic) { - - /* There were no partitions with precise meanings - * around, but we found generic partitions. In this - * case, if there's only one, we can go ahead and boot - * it, otherwise we bail out, because we really cannot - * make any sense of it. */ - - if (multiple_generic) { - log_error("Identified multiple bootable Linux partitions on\n" - " %s\n" - PARTITION_TABLE_BLURB, arg_image); - return -EINVAL; - } - - *root_device = generic; - generic = NULL; - - *root_device_rw = generic_rw; - *secondary = false; - } else { - log_error("Failed to identify root partition in disk image\n" - " %s\n" - PARTITION_TABLE_BLURB, arg_image); - return -EINVAL; - } - - if (home) { - *home_device = home; - home = NULL; - - *home_device_rw = home_rw; - } - - if (srv) { - *srv_device = srv; - srv = NULL; - - *srv_device_rw = srv_rw; - } - - return 0; -#else - log_error("--image= is not supported, compiled without blkid support."); - return -EOPNOTSUPP; -#endif -} - -static int mount_device(const char *what, const char *where, const char *directory, bool rw) { -#ifdef HAVE_BLKID - _cleanup_blkid_free_probe_ blkid_probe b = NULL; - const char *fstype, *p; - int r; - - assert(what); - assert(where); - - if (arg_read_only) - rw = false; - - if (directory) - p = strjoina(where, directory); - else - p = where; - - errno = 0; - b = blkid_new_probe_from_filename(what); - if (!b) { - if (errno == 0) - return log_oom(); - return log_error_errno(errno, "Failed to allocate prober for %s: %m", what); - } - - blkid_probe_enable_superblocks(b, 1); - blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE); - - errno = 0; - r = blkid_do_safeprobe(b); - if (r == -1 || r == 1) { - log_error("Cannot determine file system type of %s", what); - return -EINVAL; - } else if (r != 0) { - if (errno == 0) - errno = EIO; - return log_error_errno(errno, "Failed to probe %s: %m", what); - } - - errno = 0; - if (blkid_probe_lookup_value(b, "TYPE", &fstype, NULL) < 0) { - if (errno == 0) - errno = EINVAL; - log_error("Failed to determine file system type of %s", what); - return -errno; - } - - if (streq(fstype, "crypto_LUKS")) { - log_error("nspawn currently does not support LUKS disk images."); - return -EOPNOTSUPP; - } - - if (mount(what, p, fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), NULL) < 0) - return log_error_errno(errno, "Failed to mount %s: %m", what); - - return 0; -#else - log_error("--image= is not supported, compiled without blkid support."); - return -EOPNOTSUPP; -#endif -} - -static int setup_machine_id(const char *directory) { - int r; - const char *etc_machine_id, *t; - _cleanup_free_ char *s = NULL; - - etc_machine_id = prefix_roota(directory, "/etc/machine-id"); - - r = read_one_line_file(etc_machine_id, &s); - if (r < 0) - return log_error_errno(r, "Failed to read machine ID from %s: %m", etc_machine_id); - - t = strstrip(s); - - if (!isempty(t)) { - r = sd_id128_from_string(t, &arg_uuid); - if (r < 0) - return log_error_errno(r, "Failed to parse machine ID from %s: %m", etc_machine_id); - } else { - if (sd_id128_is_null(arg_uuid)) { - r = sd_id128_randomize(&arg_uuid); - if (r < 0) - return log_error_errno(r, "Failed to generate random machine ID: %m"); - } - } - - r = machine_id_setup(directory, arg_uuid); - if (r < 0) - return log_error_errno(r, "Failed to setup machine ID: %m"); - - return 0; -} - -static int recursive_chown(const char *directory, uid_t shift, uid_t range) { - int r; - - assert(directory); - - if (arg_userns_mode == USER_NAMESPACE_NO || !arg_userns_chown) - return 0; - - r = path_patch_uid(directory, arg_uid_shift, arg_uid_range); - if (r == -EOPNOTSUPP) - return log_error_errno(r, "Automatic UID/GID adjusting is only supported for UID/GID ranges starting at multiples of 2^16 with a range of 2^16."); - if (r == -EBADE) - return log_error_errno(r, "Upper 16 bits of root directory UID and GID do not match."); - if (r < 0) - return log_error_errno(r, "Failed to adjust UID/GID shift of OS tree: %m"); - if (r == 0) - log_debug("Root directory of image is already owned by the right UID/GID range, skipping recursive chown operation."); - else - log_debug("Patched directory tree to match UID/GID range."); - - return r; -} - -static int mount_devices( - const char *where, - const char *root_device, bool root_device_rw, - const char *home_device, bool home_device_rw, - const char *srv_device, bool srv_device_rw) { - int r; - - assert(where); - - if (root_device) { - r = mount_device(root_device, arg_directory, NULL, root_device_rw); - if (r < 0) - return log_error_errno(r, "Failed to mount root directory: %m"); - } - - if (home_device) { - r = mount_device(home_device, arg_directory, "/home", home_device_rw); - if (r < 0) - return log_error_errno(r, "Failed to mount home directory: %m"); - } - - if (srv_device) { - r = mount_device(srv_device, arg_directory, "/srv", srv_device_rw); - if (r < 0) - return log_error_errno(r, "Failed to mount server data directory: %m"); - } - - return 0; -} - -static void loop_remove(int nr, int *image_fd) { - _cleanup_close_ int control = -1; - int r; - - if (nr < 0) - return; - - if (image_fd && *image_fd >= 0) { - r = ioctl(*image_fd, LOOP_CLR_FD); - if (r < 0) - log_debug_errno(errno, "Failed to close loop image: %m"); - *image_fd = safe_close(*image_fd); - } - - control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); - if (control < 0) { - log_warning_errno(errno, "Failed to open /dev/loop-control: %m"); - return; - } - - r = ioctl(control, LOOP_CTL_REMOVE, nr); - if (r < 0) - log_debug_errno(errno, "Failed to remove loop %d: %m", nr); -} - -/* - * Return values: - * < 0 : wait_for_terminate() failed to get the state of the - * container, the container was terminated by a signal, or - * failed for an unknown reason. No change is made to the - * container argument. - * > 0 : The program executed in the container terminated with an - * error. The exit code of the program executed in the - * container is returned. The container argument has been set - * to CONTAINER_TERMINATED. - * 0 : The container is being rebooted, has been shut down or exited - * successfully. The container argument has been set to either - * CONTAINER_TERMINATED or CONTAINER_REBOOTED. - * - * That is, success is indicated by a return value of zero, and an - * error is indicated by a non-zero value. - */ -static int wait_for_container(pid_t pid, ContainerStatus *container) { - siginfo_t status; - int r; - - r = wait_for_terminate(pid, &status); - if (r < 0) - return log_warning_errno(r, "Failed to wait for container: %m"); - - switch (status.si_code) { - - case CLD_EXITED: - if (status.si_status == 0) { - log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "Container %s exited successfully.", arg_machine); - - } else - log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "Container %s failed with error code %i.", arg_machine, status.si_status); - - *container = CONTAINER_TERMINATED; - return status.si_status; - - case CLD_KILLED: - if (status.si_status == SIGINT) { - - log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "Container %s has been shut down.", arg_machine); - *container = CONTAINER_TERMINATED; - return 0; - - } else if (status.si_status == SIGHUP) { - - log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "Container %s is being rebooted.", arg_machine); - *container = CONTAINER_REBOOTED; - return 0; - } - - /* CLD_KILLED fallthrough */ - - case CLD_DUMPED: - log_error("Container %s terminated by signal %s.", arg_machine, signal_to_string(status.si_status)); - return -EIO; - - default: - log_error("Container %s failed due to unknown reason.", arg_machine); - return -EIO; - } - - return r; -} - -static int on_orderly_shutdown(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - pid_t pid; - - pid = PTR_TO_PID(userdata); - if (pid > 0) { - if (kill(pid, arg_kill_signal) >= 0) { - log_info("Trying to halt container. Send SIGTERM again to trigger immediate termination."); - sd_event_source_set_userdata(s, NULL); - return 0; - } - } - - sd_event_exit(sd_event_source_get_event(s), 0); - return 0; -} - -static int determine_names(void) { - int r; - - if (arg_template && !arg_directory && arg_machine) { - - /* If --template= was specified then we should not - * search for a machine, but instead create a new one - * in /var/lib/machine. */ - - arg_directory = strjoin("/var/lib/machines/", arg_machine, NULL); - if (!arg_directory) - return log_oom(); - } - - if (!arg_image && !arg_directory) { - if (arg_machine) { - _cleanup_(image_unrefp) Image *i = NULL; - - r = image_find(arg_machine, &i); - if (r < 0) - return log_error_errno(r, "Failed to find image for machine '%s': %m", arg_machine); - else if (r == 0) { - log_error("No image for machine '%s': %m", arg_machine); - return -ENOENT; - } - - if (i->type == IMAGE_RAW) - r = free_and_strdup(&arg_image, i->path); - else - r = free_and_strdup(&arg_directory, i->path); - if (r < 0) - return log_error_errno(r, "Invalid image directory: %m"); - - if (!arg_ephemeral) - arg_read_only = arg_read_only || i->read_only; - } else - arg_directory = get_current_dir_name(); - - if (!arg_directory && !arg_machine) { - log_error("Failed to determine path, please use -D or -i."); - return -EINVAL; - } - } - - if (!arg_machine) { - if (arg_directory && path_equal(arg_directory, "/")) - arg_machine = gethostname_malloc(); - else - arg_machine = strdup(basename(arg_image ?: arg_directory)); - - if (!arg_machine) - return log_oom(); - - hostname_cleanup(arg_machine); - if (!machine_name_is_valid(arg_machine)) { - log_error("Failed to determine machine name automatically, please use -M."); - return -EINVAL; - } - - if (arg_ephemeral) { - char *b; - - /* Add a random suffix when this is an - * ephemeral machine, so that we can run many - * instances at once without manually having - * to specify -M each time. */ - - if (asprintf(&b, "%s-%016" PRIx64, arg_machine, random_u64()) < 0) - return log_oom(); - - free(arg_machine); - arg_machine = b; - } - } - - return 0; -} - -static int determine_uid_shift(const char *directory) { - int r; - - if (arg_userns_mode == USER_NAMESPACE_NO) { - arg_uid_shift = 0; - return 0; - } - - if (arg_uid_shift == UID_INVALID) { - struct stat st; - - r = stat(directory, &st); - if (r < 0) - return log_error_errno(errno, "Failed to determine UID base of %s: %m", directory); - - arg_uid_shift = st.st_uid & UINT32_C(0xffff0000); - - if (arg_uid_shift != (st.st_gid & UINT32_C(0xffff0000))) { - log_error("UID and GID base of %s don't match.", directory); - return -EINVAL; - } - - arg_uid_range = UINT32_C(0x10000); - } - - if (arg_uid_shift > (uid_t) -1 - arg_uid_range) { - log_error("UID base too high for UID range."); - return -EINVAL; - } - - return 0; -} - -static int inner_child( - Barrier *barrier, - const char *directory, - bool secondary, - int kmsg_socket, - int rtnl_socket, - FDSet *fds) { - - _cleanup_free_ char *home = NULL; - char as_uuid[37]; - unsigned n_env = 1; - const char *envp[] = { - "PATH=" DEFAULT_PATH_SPLIT_USR, - NULL, /* container */ - NULL, /* TERM */ - NULL, /* HOME */ - NULL, /* USER */ - NULL, /* LOGNAME */ - NULL, /* container_uuid */ - NULL, /* LISTEN_FDS */ - NULL, /* LISTEN_PID */ - NULL - }; - - _cleanup_strv_free_ char **env_use = NULL; - int r; - - assert(barrier); - assert(directory); - assert(kmsg_socket >= 0); - - cg_unified_flush(); - - if (arg_userns_mode != USER_NAMESPACE_NO) { - /* Tell the parent, that it now can write the UID map. */ - (void) barrier_place(barrier); /* #1 */ - - /* Wait until the parent wrote the UID map */ - if (!barrier_place_and_sync(barrier)) { /* #2 */ - log_error("Parent died too early"); - return -ESRCH; - } - } - - r = mount_all(NULL, - arg_userns_mode != USER_NAMESPACE_NO, - true, - arg_private_network, - arg_uid_shift, - arg_uid_range, - arg_selinux_apifs_context); - - if (r < 0) - return r; - - r = mount_sysfs(NULL); - if (r < 0) - return r; - - /* Wait until we are cgroup-ified, so that we - * can mount the right cgroup path writable */ - if (!barrier_place_and_sync(barrier)) { /* #3 */ - log_error("Parent died too early"); - return -ESRCH; - } - - r = mount_systemd_cgroup_writable("", arg_unified_cgroup_hierarchy); - if (r < 0) - return r; - - r = reset_uid_gid(); - if (r < 0) - return log_error_errno(r, "Couldn't become new root: %m"); - - r = setup_boot_id(NULL); - if (r < 0) - return r; - - r = setup_kmsg(NULL, kmsg_socket); - if (r < 0) - return r; - kmsg_socket = safe_close(kmsg_socket); - - umask(0022); - - if (setsid() < 0) - return log_error_errno(errno, "setsid() failed: %m"); - - if (arg_private_network) - loopback_setup(); - - if (arg_expose_ports) { - r = expose_port_send_rtnl(rtnl_socket); - if (r < 0) - return r; - rtnl_socket = safe_close(rtnl_socket); - } - - r = drop_capabilities(); - if (r < 0) - return log_error_errno(r, "drop_capabilities() failed: %m"); - - setup_hostname(); - - if (arg_personality != PERSONALITY_INVALID) { - if (personality(arg_personality) < 0) - return log_error_errno(errno, "personality() failed: %m"); - } else if (secondary) { - if (personality(PER_LINUX32) < 0) - return log_error_errno(errno, "personality() failed: %m"); - } - -#ifdef HAVE_SELINUX - if (arg_selinux_context) - if (setexeccon((security_context_t) arg_selinux_context) < 0) - return log_error_errno(errno, "setexeccon(\"%s\") failed: %m", arg_selinux_context); -#endif - - r = change_uid_gid(arg_user, &home); - if (r < 0) - return r; - - /* LXC sets container=lxc, so follow the scheme here */ - envp[n_env++] = strjoina("container=", arg_container_service_name); - - envp[n_env] = strv_find_prefix(environ, "TERM="); - if (envp[n_env]) - n_env++; - - if ((asprintf((char**)(envp + n_env++), "HOME=%s", home ? home: "/root") < 0) || - (asprintf((char**)(envp + n_env++), "USER=%s", arg_user ? arg_user : "root") < 0) || - (asprintf((char**)(envp + n_env++), "LOGNAME=%s", arg_user ? arg_user : "root") < 0)) - return log_oom(); - - assert(!sd_id128_equal(arg_uuid, SD_ID128_NULL)); - - if (asprintf((char**)(envp + n_env++), "container_uuid=%s", id128_format_as_uuid(arg_uuid, as_uuid)) < 0) - return log_oom(); - - if (fdset_size(fds) > 0) { - r = fdset_cloexec(fds, false); - if (r < 0) - return log_error_errno(r, "Failed to unset O_CLOEXEC for file descriptors."); - - if ((asprintf((char **)(envp + n_env++), "LISTEN_FDS=%u", fdset_size(fds)) < 0) || - (asprintf((char **)(envp + n_env++), "LISTEN_PID=1") < 0)) - return log_oom(); - } - - env_use = strv_env_merge(2, envp, arg_setenv); - if (!env_use) - return log_oom(); - - /* Let the parent know that we are ready and - * wait until the parent is ready with the - * setup, too... */ - if (!barrier_place_and_sync(barrier)) { /* #4 */ - log_error("Parent died too early"); - return -ESRCH; - } - - if (arg_chdir) - if (chdir(arg_chdir) < 0) - return log_error_errno(errno, "Failed to change to specified working directory %s: %m", arg_chdir); - - if (arg_start_mode == START_PID2) { - r = stub_pid1(); - if (r < 0) - return r; - } - - /* Now, explicitly close the log, so that we - * then can close all remaining fds. Closing - * the log explicitly first has the benefit - * that the logging subsystem knows about it, - * and is thus ready to be reopened should we - * need it again. Note that the other fds - * closed here are at least the locking and - * barrier fds. */ - log_close(); - (void) fdset_close_others(fds); - - if (arg_start_mode == START_BOOT) { - char **a; - size_t m; - - /* Automatically search for the init system */ - - m = strv_length(arg_parameters); - a = newa(char*, m + 2); - memcpy_safe(a + 1, arg_parameters, m * sizeof(char*)); - a[1 + m] = NULL; - - a[0] = (char*) "/usr/lib/systemd/systemd"; - execve(a[0], a, env_use); - - a[0] = (char*) "/lib/systemd/systemd"; - execve(a[0], a, env_use); - - a[0] = (char*) "/sbin/init"; - execve(a[0], a, env_use); - } else if (!strv_isempty(arg_parameters)) - execvpe(arg_parameters[0], arg_parameters, env_use); - else { - if (!arg_chdir) - /* If we cannot change the directory, we'll end up in /, that is expected. */ - (void) chdir(home ?: "/root"); - - execle("/bin/bash", "-bash", NULL, env_use); - execle("/bin/sh", "-sh", NULL, env_use); - } - - r = -errno; - (void) log_open(); - return log_error_errno(r, "execv() failed: %m"); -} - -static int outer_child( - Barrier *barrier, - const char *directory, - const char *console, - const char *root_device, bool root_device_rw, - const char *home_device, bool home_device_rw, - const char *srv_device, bool srv_device_rw, - bool interactive, - bool secondary, - int pid_socket, - int uuid_socket, - int kmsg_socket, - int rtnl_socket, - int uid_shift_socket, - FDSet *fds) { - - pid_t pid; - ssize_t l; - int r; - - assert(barrier); - assert(directory); - assert(console); - assert(pid_socket >= 0); - assert(uuid_socket >= 0); - assert(kmsg_socket >= 0); - - cg_unified_flush(); - - if (prctl(PR_SET_PDEATHSIG, SIGKILL) < 0) - return log_error_errno(errno, "PR_SET_PDEATHSIG failed: %m"); - - if (interactive) { - close_nointr(STDIN_FILENO); - close_nointr(STDOUT_FILENO); - close_nointr(STDERR_FILENO); - - r = open_terminal(console, O_RDWR); - if (r != STDIN_FILENO) { - if (r >= 0) { - safe_close(r); - r = -EINVAL; - } - - return log_error_errno(r, "Failed to open console: %m"); - } - - if (dup2(STDIN_FILENO, STDOUT_FILENO) != STDOUT_FILENO || - dup2(STDIN_FILENO, STDERR_FILENO) != STDERR_FILENO) - return log_error_errno(errno, "Failed to duplicate console: %m"); - } - - r = reset_audit_loginuid(); - if (r < 0) - return r; - - /* Mark everything as slave, so that we still - * receive mounts from the real root, but don't - * propagate mounts to the real root. */ - if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) - return log_error_errno(errno, "MS_SLAVE|MS_REC failed: %m"); - - r = mount_devices(directory, - root_device, root_device_rw, - home_device, home_device_rw, - srv_device, srv_device_rw); - if (r < 0) - return r; - - r = determine_uid_shift(directory); - if (r < 0) - return r; - - if (arg_userns_mode != USER_NAMESPACE_NO) { - /* Let the parent know which UID shift we read from the image */ - l = send(uid_shift_socket, &arg_uid_shift, sizeof(arg_uid_shift), MSG_NOSIGNAL); - if (l < 0) - return log_error_errno(errno, "Failed to send UID shift: %m"); - if (l != sizeof(arg_uid_shift)) { - log_error("Short write while sending UID shift."); - return -EIO; - } - - if (arg_userns_mode == USER_NAMESPACE_PICK) { - /* When we are supposed to pick the UID shift, the parent will check now whether the UID shift - * we just read from the image is available. If yes, it will send the UID shift back to us, if - * not it will pick a different one, and send it back to us. */ - - l = recv(uid_shift_socket, &arg_uid_shift, sizeof(arg_uid_shift), 0); - if (l < 0) - return log_error_errno(errno, "Failed to recv UID shift: %m"); - if (l != sizeof(arg_uid_shift)) { - log_error("Short read while recieving UID shift."); - return -EIO; - } - } - - log_info("Selected user namespace base " UID_FMT " and range " UID_FMT ".", arg_uid_shift, arg_uid_range); - } - - /* Turn directory into bind mount */ - if (mount(directory, directory, NULL, MS_BIND|MS_REC, NULL) < 0) - return log_error_errno(errno, "Failed to make bind mount: %m"); - - r = recursive_chown(directory, arg_uid_shift, arg_uid_range); - if (r < 0) - return r; - - r = setup_volatile( - directory, - arg_volatile_mode, - arg_userns_mode != USER_NAMESPACE_NO, - arg_uid_shift, - arg_uid_range, - arg_selinux_context); - if (r < 0) - return r; - - r = setup_volatile_state( - directory, - arg_volatile_mode, - arg_userns_mode != USER_NAMESPACE_NO, - arg_uid_shift, - arg_uid_range, - arg_selinux_context); - if (r < 0) - return r; - - r = base_filesystem_create(directory, arg_uid_shift, (gid_t) arg_uid_shift); - if (r < 0) - return r; - - if (arg_read_only) { - r = bind_remount_recursive(directory, true); - if (r < 0) - return log_error_errno(r, "Failed to make tree read-only: %m"); - } - - r = mount_all(directory, - arg_userns_mode != USER_NAMESPACE_NO, - false, - arg_private_network, - arg_uid_shift, - arg_uid_range, - arg_selinux_apifs_context); - if (r < 0) - return r; - - r = copy_devnodes(directory); - if (r < 0) - return r; - - dev_setup(directory, arg_uid_shift, arg_uid_shift); - - r = setup_pts(directory); - if (r < 0) - return r; - - r = setup_propagate(directory); - if (r < 0) - return r; - - r = setup_dev_console(directory, console); - if (r < 0) - return r; - - r = setup_seccomp(); - if (r < 0) - return r; - - r = setup_timezone(directory); - if (r < 0) - return r; - - r = setup_resolv_conf(directory); - if (r < 0) - return r; - - r = setup_machine_id(directory); - if (r < 0) - return r; - - r = setup_journal(directory); - if (r < 0) - return r; - - r = mount_custom( - directory, - arg_custom_mounts, - arg_n_custom_mounts, - arg_userns_mode != USER_NAMESPACE_NO, - arg_uid_shift, - arg_uid_range, - arg_selinux_apifs_context); - if (r < 0) - return r; - - r = mount_cgroups( - directory, - arg_unified_cgroup_hierarchy, - arg_userns_mode != USER_NAMESPACE_NO, - arg_uid_shift, - arg_uid_range, - arg_selinux_apifs_context); - if (r < 0) - return r; - - r = mount_move_root(directory); - if (r < 0) - return log_error_errno(r, "Failed to move root directory: %m"); - - pid = raw_clone(SIGCHLD|CLONE_NEWNS| - (arg_share_system ? 0 : CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS) | - (arg_private_network ? CLONE_NEWNET : 0) | - (arg_userns_mode != USER_NAMESPACE_NO ? CLONE_NEWUSER : 0), - NULL); - if (pid < 0) - return log_error_errno(errno, "Failed to fork inner child: %m"); - if (pid == 0) { - pid_socket = safe_close(pid_socket); - uuid_socket = safe_close(uuid_socket); - uid_shift_socket = safe_close(uid_shift_socket); - - /* The inner child has all namespaces that are - * requested, so that we all are owned by the user if - * user namespaces are turned on. */ - - r = inner_child(barrier, directory, secondary, kmsg_socket, rtnl_socket, fds); - if (r < 0) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); - } - - l = send(pid_socket, &pid, sizeof(pid), MSG_NOSIGNAL); - if (l < 0) - return log_error_errno(errno, "Failed to send PID: %m"); - if (l != sizeof(pid)) { - log_error("Short write while sending PID."); - return -EIO; - } - - l = send(uuid_socket, &arg_uuid, sizeof(arg_uuid), MSG_NOSIGNAL); - if (l < 0) - return log_error_errno(errno, "Failed to send machine ID: %m"); - if (l != sizeof(arg_uuid)) { - log_error("Short write while sending machine ID."); - return -EIO; - } - - pid_socket = safe_close(pid_socket); - uuid_socket = safe_close(uuid_socket); - kmsg_socket = safe_close(kmsg_socket); - rtnl_socket = safe_close(rtnl_socket); - - return 0; -} - -static int uid_shift_pick(uid_t *shift, LockFile *ret_lock_file) { - unsigned n_tries = 100; - uid_t candidate; - int r; - - assert(shift); - assert(ret_lock_file); - assert(arg_userns_mode == USER_NAMESPACE_PICK); - assert(arg_uid_range == 0x10000U); - - candidate = *shift; - - (void) mkdir("/run/systemd/nspawn-uid", 0755); - - for (;;) { - char lock_path[strlen("/run/systemd/nspawn-uid/") + DECIMAL_STR_MAX(uid_t) + 1]; - _cleanup_release_lock_file_ LockFile lf = LOCK_FILE_INIT; - - if (--n_tries <= 0) - return -EBUSY; - - if (candidate < UID_SHIFT_PICK_MIN || candidate > UID_SHIFT_PICK_MAX) - goto next; - if ((candidate & UINT32_C(0xFFFF)) != 0) - goto next; - - xsprintf(lock_path, "/run/systemd/nspawn-uid/" UID_FMT, candidate); - r = make_lock_file(lock_path, LOCK_EX|LOCK_NB, &lf); - if (r == -EBUSY) /* Range already taken by another nspawn instance */ - goto next; - if (r < 0) - return r; - - /* Make some superficial checks whether the range is currently known in the user database */ - if (getpwuid(candidate)) - goto next; - if (getpwuid(candidate + UINT32_C(0xFFFE))) - goto next; - if (getgrgid(candidate)) - goto next; - if (getgrgid(candidate + UINT32_C(0xFFFE))) - goto next; - - *ret_lock_file = lf; - lf = (struct LockFile) LOCK_FILE_INIT; - *shift = candidate; - return 0; - - next: - random_bytes(&candidate, sizeof(candidate)); - candidate = (candidate % (UID_SHIFT_PICK_MAX - UID_SHIFT_PICK_MIN)) + UID_SHIFT_PICK_MIN; - candidate &= (uid_t) UINT32_C(0xFFFF0000); - } -} - -static int setup_uid_map(pid_t pid) { - char uid_map[strlen("/proc//uid_map") + DECIMAL_STR_MAX(uid_t) + 1], line[DECIMAL_STR_MAX(uid_t)*3+3+1]; - int r; - - assert(pid > 1); - - xsprintf(uid_map, "/proc/" PID_FMT "/uid_map", pid); - xsprintf(line, UID_FMT " " UID_FMT " " UID_FMT "\n", 0, arg_uid_shift, arg_uid_range); - r = write_string_file(uid_map, line, 0); - if (r < 0) - return log_error_errno(r, "Failed to write UID map: %m"); - - /* We always assign the same UID and GID ranges */ - xsprintf(uid_map, "/proc/" PID_FMT "/gid_map", pid); - r = write_string_file(uid_map, line, 0); - if (r < 0) - return log_error_errno(r, "Failed to write GID map: %m"); - - return 0; -} - -static int load_settings(void) { - _cleanup_(settings_freep) Settings *settings = NULL; - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *p = NULL; - const char *fn, *i; - int r; - - /* If all settings are masked, there's no point in looking for - * the settings file */ - if ((arg_settings_mask & _SETTINGS_MASK_ALL) == _SETTINGS_MASK_ALL) - return 0; - - fn = strjoina(arg_machine, ".nspawn"); - - /* We first look in the admin's directories in /etc and /run */ - FOREACH_STRING(i, "/etc/systemd/nspawn", "/run/systemd/nspawn") { - _cleanup_free_ char *j = NULL; - - j = strjoin(i, "/", fn, NULL); - if (!j) - return log_oom(); - - f = fopen(j, "re"); - if (f) { - p = j; - j = NULL; - - /* By default, we trust configuration from /etc and /run */ - if (arg_settings_trusted < 0) - arg_settings_trusted = true; - - break; - } - - if (errno != ENOENT) - return log_error_errno(errno, "Failed to open %s: %m", j); - } - - if (!f) { - /* After that, let's look for a file next to the - * actual image we shall boot. */ - - if (arg_image) { - p = file_in_same_dir(arg_image, fn); - if (!p) - return log_oom(); - } else if (arg_directory) { - p = file_in_same_dir(arg_directory, fn); - if (!p) - return log_oom(); - } - - if (p) { - f = fopen(p, "re"); - if (!f && errno != ENOENT) - return log_error_errno(errno, "Failed to open %s: %m", p); - - /* By default, we do not trust configuration from /var/lib/machines */ - if (arg_settings_trusted < 0) - arg_settings_trusted = false; - } - } - - if (!f) - return 0; - - log_debug("Settings are trusted: %s", yes_no(arg_settings_trusted)); - - r = settings_load(f, p, &settings); - if (r < 0) - return r; - - /* Copy over bits from the settings, unless they have been - * explicitly masked by command line switches. */ - - if ((arg_settings_mask & SETTING_START_MODE) == 0 && - settings->start_mode >= 0) { - arg_start_mode = settings->start_mode; - - strv_free(arg_parameters); - arg_parameters = settings->parameters; - settings->parameters = NULL; - } - - if ((arg_settings_mask & SETTING_WORKING_DIRECTORY) == 0 && - settings->working_directory) { - free(arg_chdir); - arg_chdir = settings->working_directory; - settings->working_directory = NULL; - } - - if ((arg_settings_mask & SETTING_ENVIRONMENT) == 0 && - settings->environment) { - strv_free(arg_setenv); - arg_setenv = settings->environment; - settings->environment = NULL; - } - - if ((arg_settings_mask & SETTING_USER) == 0 && - settings->user) { - free(arg_user); - arg_user = settings->user; - settings->user = NULL; - } - - if ((arg_settings_mask & SETTING_CAPABILITY) == 0) { - uint64_t plus; - - plus = settings->capability; - if (settings_private_network(settings)) - plus |= (1ULL << CAP_NET_ADMIN); - - if (!arg_settings_trusted && plus != 0) { - if (settings->capability != 0) - log_warning("Ignoring Capability= setting, file %s is not trusted.", p); - } else - arg_retain |= plus; - - arg_retain &= ~settings->drop_capability; - } - - if ((arg_settings_mask & SETTING_KILL_SIGNAL) == 0 && - settings->kill_signal > 0) - arg_kill_signal = settings->kill_signal; - - if ((arg_settings_mask & SETTING_PERSONALITY) == 0 && - settings->personality != PERSONALITY_INVALID) - arg_personality = settings->personality; - - if ((arg_settings_mask & SETTING_MACHINE_ID) == 0 && - !sd_id128_is_null(settings->machine_id)) { - - if (!arg_settings_trusted) - log_warning("Ignoring MachineID= setting, file %s is not trusted.", p); - else - arg_uuid = settings->machine_id; - } - - if ((arg_settings_mask & SETTING_READ_ONLY) == 0 && - settings->read_only >= 0) - arg_read_only = settings->read_only; - - if ((arg_settings_mask & SETTING_VOLATILE_MODE) == 0 && - settings->volatile_mode != _VOLATILE_MODE_INVALID) - arg_volatile_mode = settings->volatile_mode; - - if ((arg_settings_mask & SETTING_CUSTOM_MOUNTS) == 0 && - settings->n_custom_mounts > 0) { - - if (!arg_settings_trusted) - log_warning("Ignoring TemporaryFileSystem=, Bind= and BindReadOnly= settings, file %s is not trusted.", p); - else { - custom_mount_free_all(arg_custom_mounts, arg_n_custom_mounts); - arg_custom_mounts = settings->custom_mounts; - arg_n_custom_mounts = settings->n_custom_mounts; - - settings->custom_mounts = NULL; - settings->n_custom_mounts = 0; - } - } - - if ((arg_settings_mask & SETTING_NETWORK) == 0 && - (settings->private_network >= 0 || - settings->network_veth >= 0 || - settings->network_bridge || - settings->network_zone || - settings->network_interfaces || - settings->network_macvlan || - settings->network_ipvlan || - settings->network_veth_extra)) { - - if (!arg_settings_trusted) - log_warning("Ignoring network settings, file %s is not trusted.", p); - else { - arg_network_veth = settings_network_veth(settings); - arg_private_network = settings_private_network(settings); - - strv_free(arg_network_interfaces); - arg_network_interfaces = settings->network_interfaces; - settings->network_interfaces = NULL; - - strv_free(arg_network_macvlan); - arg_network_macvlan = settings->network_macvlan; - settings->network_macvlan = NULL; - - strv_free(arg_network_ipvlan); - arg_network_ipvlan = settings->network_ipvlan; - settings->network_ipvlan = NULL; - - strv_free(arg_network_veth_extra); - arg_network_veth_extra = settings->network_veth_extra; - settings->network_veth_extra = NULL; - - free(arg_network_bridge); - arg_network_bridge = settings->network_bridge; - settings->network_bridge = NULL; - - free(arg_network_zone); - arg_network_zone = settings->network_zone; - settings->network_zone = NULL; - } - } - - if ((arg_settings_mask & SETTING_EXPOSE_PORTS) == 0 && - settings->expose_ports) { - - if (!arg_settings_trusted) - log_warning("Ignoring Port= setting, file %s is not trusted.", p); - else { - expose_port_free_all(arg_expose_ports); - arg_expose_ports = settings->expose_ports; - settings->expose_ports = NULL; - } - } - - if ((arg_settings_mask & SETTING_USERNS) == 0 && - settings->userns_mode != _USER_NAMESPACE_MODE_INVALID) { - - if (!arg_settings_trusted) - log_warning("Ignoring PrivateUsers= and PrivateUsersChown= settings, file %s is not trusted.", p); - else { - arg_userns_mode = settings->userns_mode; - arg_uid_shift = settings->uid_shift; - arg_uid_range = settings->uid_range; - arg_userns_chown = settings->userns_chown; - } - } - - return 0; -} - -int main(int argc, char *argv[]) { - - _cleanup_free_ char *device_path = NULL, *root_device = NULL, *home_device = NULL, *srv_device = NULL, *console = NULL; - bool root_device_rw = true, home_device_rw = true, srv_device_rw = true; - _cleanup_close_ int master = -1, image_fd = -1; - _cleanup_fdset_free_ FDSet *fds = NULL; - int r, n_fd_passed, loop_nr = -1; - char veth_name[IFNAMSIZ] = ""; - bool secondary = false, remove_subvol = false; - sigset_t mask_chld; - pid_t pid = 0; - int ret = EXIT_SUCCESS; - union in_addr_union exposed = {}; - _cleanup_release_lock_file_ LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT; - bool interactive, veth_created = false; - - log_parse_environment(); - log_open(); - - /* Make sure rename_process() in the stub init process can work */ - saved_argv = argv; - saved_argc = argc; - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (geteuid() != 0) { - log_error("Need to be root."); - r = -EPERM; - goto finish; - } - r = determine_names(); - if (r < 0) - goto finish; - - r = load_settings(); - if (r < 0) - goto finish; - - r = verify_arguments(); - if (r < 0) - goto finish; - - n_fd_passed = sd_listen_fds(false); - if (n_fd_passed > 0) { - r = fdset_new_listen_fds(&fds, false); - if (r < 0) { - log_error_errno(r, "Failed to collect file descriptors: %m"); - goto finish; - } - } - - if (arg_directory) { - assert(!arg_image); - - if (path_equal(arg_directory, "/") && !arg_ephemeral) { - log_error("Spawning container on root directory is not supported. Consider using --ephemeral."); - r = -EINVAL; - goto finish; - } - - if (arg_ephemeral) { - _cleanup_free_ char *np = NULL; - - /* If the specified path is a mount point we - * generate the new snapshot immediately - * inside it under a random name. However if - * the specified is not a mount point we - * create the new snapshot in the parent - * directory, just next to it. */ - r = path_is_mount_point(arg_directory, 0); - if (r < 0) { - log_error_errno(r, "Failed to determine whether directory %s is mount point: %m", arg_directory); - goto finish; - } - if (r > 0) - r = tempfn_random_child(arg_directory, "machine.", &np); - else - r = tempfn_random(arg_directory, "machine.", &np); - if (r < 0) { - log_error_errno(r, "Failed to generate name for snapshot: %m"); - goto finish; - } - - r = image_path_lock(np, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock); - if (r < 0) { - log_error_errno(r, "Failed to lock %s: %m", np); - goto finish; - } - - r = btrfs_subvol_snapshot(arg_directory, np, (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA); - if (r < 0) { - log_error_errno(r, "Failed to create snapshot %s from %s: %m", np, arg_directory); - goto finish; - } - - free(arg_directory); - arg_directory = np; - np = NULL; - - remove_subvol = true; - - } else { - r = image_path_lock(arg_directory, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock); - if (r == -EBUSY) { - log_error_errno(r, "Directory tree %s is currently busy.", arg_directory); - goto finish; - } - if (r < 0) { - log_error_errno(r, "Failed to lock %s: %m", arg_directory); - return r; - } - - if (arg_template) { - r = btrfs_subvol_snapshot(arg_template, arg_directory, (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA); - if (r == -EEXIST) { - if (!arg_quiet) - log_info("Directory %s already exists, not populating from template %s.", arg_directory, arg_template); - } else if (r < 0) { - log_error_errno(r, "Couldn't create snapshot %s from %s: %m", arg_directory, arg_template); - goto finish; - } else { - if (!arg_quiet) - log_info("Populated %s from template %s.", arg_directory, arg_template); - } - } - } - - if (arg_start_mode == START_BOOT) { - if (path_is_os_tree(arg_directory) <= 0) { - log_error("Directory %s doesn't look like an OS root directory (os-release file is missing). Refusing.", arg_directory); - r = -EINVAL; - goto finish; - } - } else { - const char *p; - - p = strjoina(arg_directory, "/usr/"); - if (laccess(p, F_OK) < 0) { - log_error("Directory %s doesn't look like it has an OS tree. Refusing.", arg_directory); - r = -EINVAL; - goto finish; - } - } - - } else { - char template[] = "/tmp/nspawn-root-XXXXXX"; - - assert(arg_image); - assert(!arg_template); - - r = image_path_lock(arg_image, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock); - if (r == -EBUSY) { - r = log_error_errno(r, "Disk image %s is currently busy.", arg_image); - goto finish; - } - if (r < 0) { - r = log_error_errno(r, "Failed to create image lock: %m"); - goto finish; - } - - if (!mkdtemp(template)) { - log_error_errno(errno, "Failed to create temporary directory: %m"); - r = -errno; - goto finish; - } - - arg_directory = strdup(template); - if (!arg_directory) { - r = log_oom(); - goto finish; - } - - image_fd = setup_image(&device_path, &loop_nr); - if (image_fd < 0) { - r = image_fd; - goto finish; - } - - r = dissect_image(image_fd, - &root_device, &root_device_rw, - &home_device, &home_device_rw, - &srv_device, &srv_device_rw, - &secondary); - if (r < 0) - goto finish; - } - - r = custom_mounts_prepare(); - if (r < 0) - goto finish; - - interactive = - isatty(STDIN_FILENO) > 0 && - isatty(STDOUT_FILENO) > 0; - - master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY); - if (master < 0) { - r = log_error_errno(errno, "Failed to acquire pseudo tty: %m"); - goto finish; - } - - r = ptsname_malloc(master, &console); - if (r < 0) { - r = log_error_errno(r, "Failed to determine tty name: %m"); - goto finish; - } - - if (arg_selinux_apifs_context) { - r = mac_selinux_apply(console, arg_selinux_apifs_context); - if (r < 0) - goto finish; - } - - if (unlockpt(master) < 0) { - r = log_error_errno(errno, "Failed to unlock tty: %m"); - goto finish; - } - - if (!arg_quiet) - log_info("Spawning container %s on %s.\nPress ^] three times within 1s to kill container.", - arg_machine, arg_image ?: arg_directory); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); - - assert_se(sigemptyset(&mask_chld) == 0); - assert_se(sigaddset(&mask_chld, SIGCHLD) == 0); - - if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) { - r = log_error_errno(errno, "Failed to become subreaper: %m"); - goto finish; - } - - for (;;) { - static const struct sigaction sa = { - .sa_handler = nop_signal_handler, - .sa_flags = SA_NOCLDSTOP, - }; - - _cleanup_release_lock_file_ LockFile uid_shift_lock = LOCK_FILE_INIT; - _cleanup_close_ int etc_passwd_lock = -1; - _cleanup_close_pair_ int - kmsg_socket_pair[2] = { -1, -1 }, - rtnl_socket_pair[2] = { -1, -1 }, - pid_socket_pair[2] = { -1, -1 }, - uuid_socket_pair[2] = { -1, -1 }, - uid_shift_socket_pair[2] = { -1, -1 }; - _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - _cleanup_(pty_forward_freep) PTYForward *forward = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - ContainerStatus container_status; - char last_char = 0; - int ifi = 0; - ssize_t l; - - if (arg_userns_mode == USER_NAMESPACE_PICK) { - /* When we shall pick the UID/GID range, let's first lock /etc/passwd, so that we can safely - * check with getpwuid() if the specific user already exists. Note that /etc might be - * read-only, in which case this will fail with EROFS. But that's really OK, as in that case we - * can be reasonably sure that no users are going to be added. Note that getpwuid() checks are - * really just an extra safety net. We kinda assume that the UID range we allocate from is - * really ours. */ - - etc_passwd_lock = take_etc_passwd_lock(NULL); - if (etc_passwd_lock < 0 && etc_passwd_lock != -EROFS) { - log_error_errno(r, "Failed to take /etc/passwd lock: %m"); - goto finish; - } - } - - r = barrier_create(&barrier); - if (r < 0) { - log_error_errno(r, "Cannot initialize IPC barrier: %m"); - goto finish; - } - - if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, kmsg_socket_pair) < 0) { - r = log_error_errno(errno, "Failed to create kmsg socket pair: %m"); - goto finish; - } - - if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, rtnl_socket_pair) < 0) { - r = log_error_errno(errno, "Failed to create rtnl socket pair: %m"); - goto finish; - } - - if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pid_socket_pair) < 0) { - r = log_error_errno(errno, "Failed to create pid socket pair: %m"); - goto finish; - } - - if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, uuid_socket_pair) < 0) { - r = log_error_errno(errno, "Failed to create id socket pair: %m"); - goto finish; - } - - if (arg_userns_mode != USER_NAMESPACE_NO) - if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, uid_shift_socket_pair) < 0) { - r = log_error_errno(errno, "Failed to create uid shift socket pair: %m"); - goto finish; - } - - /* Child can be killed before execv(), so handle SIGCHLD - * in order to interrupt parent's blocking calls and - * give it a chance to call wait() and terminate. */ - r = sigprocmask(SIG_UNBLOCK, &mask_chld, NULL); - if (r < 0) { - r = log_error_errno(errno, "Failed to change the signal mask: %m"); - goto finish; - } - - r = sigaction(SIGCHLD, &sa, NULL); - if (r < 0) { - r = log_error_errno(errno, "Failed to install SIGCHLD handler: %m"); - goto finish; - } - - pid = raw_clone(SIGCHLD|CLONE_NEWNS, NULL); - if (pid < 0) { - if (errno == EINVAL) - r = log_error_errno(errno, "clone() failed, do you have namespace support enabled in your kernel? (You need UTS, IPC, PID and NET namespacing built in): %m"); - else - r = log_error_errno(errno, "clone() failed: %m"); - - goto finish; - } - - if (pid == 0) { - /* The outer child only has a file system namespace. */ - barrier_set_role(&barrier, BARRIER_CHILD); - - master = safe_close(master); - - kmsg_socket_pair[0] = safe_close(kmsg_socket_pair[0]); - rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]); - pid_socket_pair[0] = safe_close(pid_socket_pair[0]); - uuid_socket_pair[0] = safe_close(uuid_socket_pair[0]); - uid_shift_socket_pair[0] = safe_close(uid_shift_socket_pair[0]); - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - r = outer_child(&barrier, - arg_directory, - console, - root_device, root_device_rw, - home_device, home_device_rw, - srv_device, srv_device_rw, - interactive, - secondary, - pid_socket_pair[1], - uuid_socket_pair[1], - kmsg_socket_pair[1], - rtnl_socket_pair[1], - uid_shift_socket_pair[1], - fds); - if (r < 0) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); - } - - barrier_set_role(&barrier, BARRIER_PARENT); - - fds = fdset_free(fds); - - kmsg_socket_pair[1] = safe_close(kmsg_socket_pair[1]); - rtnl_socket_pair[1] = safe_close(rtnl_socket_pair[1]); - pid_socket_pair[1] = safe_close(pid_socket_pair[1]); - uuid_socket_pair[1] = safe_close(uuid_socket_pair[1]); - uid_shift_socket_pair[1] = safe_close(uid_shift_socket_pair[1]); - - if (arg_userns_mode != USER_NAMESPACE_NO) { - /* The child just let us know the UID shift it might have read from the image. */ - l = recv(uid_shift_socket_pair[0], &arg_uid_shift, sizeof(arg_uid_shift), 0); - if (l < 0) { - r = log_error_errno(errno, "Failed to read UID shift: %m"); - goto finish; - } - if (l != sizeof(arg_uid_shift)) { - log_error("Short read while reading UID shift."); - r = EIO; - goto finish; - } - - if (arg_userns_mode == USER_NAMESPACE_PICK) { - /* If we are supposed to pick the UID shift, let's try to use the shift read from the - * image, but if that's already in use, pick a new one, and report back to the child, - * which one we now picked. */ - - r = uid_shift_pick(&arg_uid_shift, &uid_shift_lock); - if (r < 0) { - log_error_errno(r, "Failed to pick suitable UID/GID range: %m"); - goto finish; - } - - l = send(uid_shift_socket_pair[0], &arg_uid_shift, sizeof(arg_uid_shift), MSG_NOSIGNAL); - if (l < 0) { - r = log_error_errno(errno, "Failed to send UID shift: %m"); - goto finish; - } - if (l != sizeof(arg_uid_shift)) { - log_error("Short write while writing UID shift."); - r = -EIO; - goto finish; - } - } - } - - /* Wait for the outer child. */ - r = wait_for_terminate_and_warn("namespace helper", pid, NULL); - if (r < 0) - goto finish; - if (r != 0) { - r = -EIO; - goto finish; - } - pid = 0; - - /* And now retrieve the PID of the inner child. */ - l = recv(pid_socket_pair[0], &pid, sizeof(pid), 0); - if (l < 0) { - r = log_error_errno(errno, "Failed to read inner child PID: %m"); - goto finish; - } - if (l != sizeof(pid)) { - log_error("Short read while reading inner child PID."); - r = EIO; - goto finish; - } - - /* We also retrieve container UUID in case it was generated by outer child */ - l = recv(uuid_socket_pair[0], &arg_uuid, sizeof(arg_uuid), 0); - if (l < 0) { - r = log_error_errno(errno, "Failed to read container machine ID: %m"); - goto finish; - } - if (l != sizeof(arg_uuid)) { - log_error("Short read while reading container machined ID."); - r = EIO; - goto finish; - } - - log_debug("Init process invoked as PID " PID_FMT, pid); - - if (arg_userns_mode != USER_NAMESPACE_NO) { - if (!barrier_place_and_sync(&barrier)) { /* #1 */ - log_error("Child died too early."); - r = -ESRCH; - goto finish; - } - - r = setup_uid_map(pid); - if (r < 0) - goto finish; - - (void) barrier_place(&barrier); /* #2 */ - } - - if (arg_private_network) { - - r = move_network_interfaces(pid, arg_network_interfaces); - if (r < 0) - goto finish; - - if (arg_network_veth) { - r = setup_veth(arg_machine, pid, veth_name, - arg_network_bridge || arg_network_zone); - if (r < 0) - goto finish; - else if (r > 0) - ifi = r; - - if (arg_network_bridge) { - /* Add the interface to a bridge */ - r = setup_bridge(veth_name, arg_network_bridge, false); - if (r < 0) - goto finish; - if (r > 0) - ifi = r; - } else if (arg_network_zone) { - /* Add the interface to a bridge, possibly creating it */ - r = setup_bridge(veth_name, arg_network_zone, true); - if (r < 0) - goto finish; - if (r > 0) - ifi = r; - } - } - - r = setup_veth_extra(arg_machine, pid, arg_network_veth_extra); - if (r < 0) - goto finish; - - /* We created the primary and extra veth links now; let's remember this, so that we know to - remove them later on. Note that we don't bother with removing veth links that were created - here when their setup failed half-way, because in that case the kernel should be able to - remove them on its own, since they cannot be referenced by anything yet. */ - veth_created = true; - - r = setup_macvlan(arg_machine, pid, arg_network_macvlan); - if (r < 0) - goto finish; - - r = setup_ipvlan(arg_machine, pid, arg_network_ipvlan); - if (r < 0) - goto finish; - } - - if (arg_register) { - r = register_machine( - arg_machine, - pid, - arg_directory, - arg_uuid, - ifi, - arg_slice, - arg_custom_mounts, arg_n_custom_mounts, - arg_kill_signal, - arg_property, - arg_keep_unit, - arg_container_service_name); - if (r < 0) - goto finish; - } - - r = sync_cgroup(pid, arg_unified_cgroup_hierarchy); - if (r < 0) - goto finish; - - if (arg_keep_unit) { - r = create_subcgroup(pid, arg_unified_cgroup_hierarchy); - if (r < 0) - goto finish; - } - - r = chown_cgroup(pid, arg_uid_shift); - if (r < 0) - goto finish; - - /* Notify the child that the parent is ready with all - * its setup (including cgroup-ification), and that - * the child can now hand over control to the code to - * run inside the container. */ - (void) barrier_place(&barrier); /* #3 */ - - /* Block SIGCHLD here, before notifying child. - * process_pty() will handle it with the other signals. */ - assert_se(sigprocmask(SIG_BLOCK, &mask_chld, NULL) >= 0); - - /* Reset signal to default */ - r = default_signals(SIGCHLD, -1); - if (r < 0) { - log_error_errno(r, "Failed to reset SIGCHLD: %m"); - goto finish; - } - - /* Let the child know that we are ready and wait that the child is completely ready now. */ - if (!barrier_place_and_sync(&barrier)) { /* #4 */ - log_error("Child died too early."); - r = -ESRCH; - goto finish; - } - - /* At this point we have made use of the UID we picked, and thus nss-mymachines will make them appear - * in getpwuid(), thus we can release the /etc/passwd lock. */ - etc_passwd_lock = safe_close(etc_passwd_lock); - - sd_notifyf(false, - "READY=1\n" - "STATUS=Container running.\n" - "X_NSPAWN_LEADER_PID=" PID_FMT, pid); - - r = sd_event_new(&event); - if (r < 0) { - log_error_errno(r, "Failed to get default event source: %m"); - goto finish; - } - - if (arg_kill_signal > 0) { - /* Try to kill the init system on SIGINT or SIGTERM */ - sd_event_add_signal(event, NULL, SIGINT, on_orderly_shutdown, PID_TO_PTR(pid)); - sd_event_add_signal(event, NULL, SIGTERM, on_orderly_shutdown, PID_TO_PTR(pid)); - } else { - /* Immediately exit */ - sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); - sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); - } - - /* simply exit on sigchld */ - sd_event_add_signal(event, NULL, SIGCHLD, NULL, NULL); - - if (arg_expose_ports) { - r = expose_port_watch_rtnl(event, rtnl_socket_pair[0], on_address_change, &exposed, &rtnl); - if (r < 0) - goto finish; - - (void) expose_port_execute(rtnl, arg_expose_ports, &exposed); - } - - rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]); - - r = pty_forward_new(event, master, PTY_FORWARD_IGNORE_VHANGUP | (interactive ? 0 : PTY_FORWARD_READ_ONLY), &forward); - if (r < 0) { - log_error_errno(r, "Failed to create PTY forwarder: %m"); - goto finish; - } - - r = sd_event_loop(event); - if (r < 0) { - log_error_errno(r, "Failed to run event loop: %m"); - goto finish; - } - - pty_forward_get_last_char(forward, &last_char); - - forward = pty_forward_free(forward); - - if (!arg_quiet && last_char != '\n') - putc('\n', stdout); - - /* Kill if it is not dead yet anyway */ - if (arg_register && !arg_keep_unit) - terminate_machine(pid); - - /* Normally redundant, but better safe than sorry */ - kill(pid, SIGKILL); - - r = wait_for_container(pid, &container_status); - pid = 0; - - if (r < 0) - /* We failed to wait for the container, or the - * container exited abnormally */ - goto finish; - else if (r > 0 || container_status == CONTAINER_TERMINATED) { - /* The container exited with a non-zero - * status, or with zero status and no reboot - * was requested. */ - ret = r; - break; - } - - /* CONTAINER_REBOOTED, loop again */ - - if (arg_keep_unit) { - /* Special handling if we are running as a - * service: instead of simply restarting the - * machine we want to restart the entire - * service, so let's inform systemd about this - * with the special exit code 133. The service - * file uses RestartForceExitStatus=133 so - * that this results in a full nspawn - * restart. This is necessary since we might - * have cgroup parameters set we want to have - * flushed out. */ - ret = 133; - r = 0; - break; - } - - expose_port_flush(arg_expose_ports, &exposed); - - (void) remove_veth_links(veth_name, arg_network_veth_extra); - veth_created = false; - } - -finish: - sd_notify(false, - "STOPPING=1\n" - "STATUS=Terminating..."); - - if (pid > 0) - kill(pid, SIGKILL); - - /* Try to flush whatever is still queued in the pty */ - if (master >= 0) - (void) copy_bytes(master, STDOUT_FILENO, (uint64_t) -1, false); - - loop_remove(loop_nr, &image_fd); - - if (remove_subvol && arg_directory) { - int k; - - k = btrfs_subvol_remove(arg_directory, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); - if (k < 0) - log_warning_errno(k, "Cannot remove subvolume '%s', ignoring: %m", arg_directory); - } - - if (arg_machine) { - const char *p; - - p = strjoina("/run/systemd/nspawn/propagate/", arg_machine); - (void) rm_rf(p, REMOVE_ROOT); - } - - expose_port_flush(arg_expose_ports, &exposed); - - if (veth_created) - (void) remove_veth_links(veth_name, arg_network_veth_extra); - (void) remove_bridge(arg_network_zone); - - free(arg_directory); - free(arg_template); - free(arg_image); - free(arg_machine); - free(arg_user); - free(arg_chdir); - strv_free(arg_setenv); - free(arg_network_bridge); - strv_free(arg_network_interfaces); - strv_free(arg_network_macvlan); - strv_free(arg_network_ipvlan); - strv_free(arg_network_veth_extra); - strv_free(arg_parameters); - custom_mount_free_all(arg_custom_mounts, arg_n_custom_mounts); - expose_port_free_all(arg_expose_ports); - - return r < 0 ? EXIT_FAILURE : ret; -} diff --git a/src/nspawn/test-patch-uid.c b/src/nspawn/test-patch-uid.c deleted file mode 100644 index 11c5321788..0000000000 --- a/src/nspawn/test-patch-uid.c +++ /dev/null @@ -1,61 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2016 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 "log.h" -#include "nspawn-patch-uid.h" -#include "user-util.h" -#include "util.h" - -int main(int argc, char *argv[]) { - uid_t shift, range; - int r; - - log_set_max_level(LOG_DEBUG); - log_parse_environment(); - log_open(); - - if (argc != 4) { - log_error("Expected PATH SHIFT RANGE parameters."); - return EXIT_FAILURE; - } - - r = parse_uid(argv[2], &shift); - if (r < 0) { - log_error_errno(r, "Failed to parse UID shift %s.", argv[2]); - return EXIT_FAILURE; - } - - r = parse_gid(argv[3], &range); - if (r < 0) { - log_error_errno(r, "Failed to parse UID range %s.", argv[3]); - return EXIT_FAILURE; - } - - r = path_patch_uid(argv[1], shift, range); - if (r < 0) { - log_error_errno(r, "Failed to patch directory tree: %m"); - return EXIT_FAILURE; - } - - log_info("Changed: %s", yes_no(r)); - - return EXIT_SUCCESS; -} diff --git a/src/nss-myhostname/Makefile b/src/nss-myhostname/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/nss-myhostname/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/nss-myhostname/Makefile b/src/nss-myhostname/Makefile new file mode 100644 index 0000000000..b5f6ff62aa --- /dev/null +++ b/src/nss-myhostname/Makefile @@ -0,0 +1,47 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(HAVE_MYHOSTNAME),) +libnss_myhostname_la_SOURCES = \ + src/nss-myhostname/nss-myhostname.sym \ + src/nss-myhostname/nss-myhostname.c + +libnss_myhostname_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -module \ + -export-dynamic \ + -avoid-version \ + -shared \ + -shrext .so.2 \ + -Wl,--version-script=$(srcdir)/nss-myhostname.sym + +libnss_myhostname_la_LIBADD = \ + libsystemd-internal.la + +lib_LTLIBRARIES += \ + libnss_myhostname.la +endif # HAVE_MYHOSTNAME + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/nss-mymachines/Makefile b/src/nss-mymachines/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/nss-mymachines/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/nss-mymachines/nss-mymachines.c b/src/nss-mymachines/nss-mymachines.c deleted file mode 100644 index 8d57b26cbc..0000000000 --- a/src/nss-mymachines/nss-mymachines.c +++ /dev/null @@ -1,738 +0,0 @@ -/*** - 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 - -#include "sd-bus.h" -#include "sd-login.h" - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "hostname-util.h" -#include "in-addr-util.h" -#include "macro.h" -#include "nss-util.h" -#include "signal-util.h" -#include "string-util.h" -#include "user-util.h" -#include "util.h" - -NSS_GETHOSTBYNAME_PROTOTYPES(mymachines); -NSS_GETPW_PROTOTYPES(mymachines); -NSS_GETGR_PROTOTYPES(mymachines); - -#define HOST_UID_LIMIT ((uid_t) UINT32_C(0x10000)) -#define HOST_GID_LIMIT ((gid_t) UINT32_C(0x10000)) - -static int count_addresses(sd_bus_message *m, int af, unsigned *ret) { - unsigned c = 0; - int r; - - assert(m); - assert(ret); - - while ((r = sd_bus_message_enter_container(m, 'r', "iay")) > 0) { - int family; - - r = sd_bus_message_read(m, "i", &family); - if (r < 0) - return r; - - r = sd_bus_message_skip(m, "ay"); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - if (af != AF_UNSPEC && family != af) - continue; - - c++; - } - if (r < 0) - return r; - - r = sd_bus_message_rewind(m, false); - if (r < 0) - return r; - - *ret = c; - return 0; -} - -enum nss_status _nss_mymachines_gethostbyname4_r( - const char *name, - struct gaih_addrtuple **pat, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp) { - - struct gaih_addrtuple *r_tuple, *r_tuple_first = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_free_ int *ifindices = NULL; - _cleanup_free_ char *class = NULL; - size_t l, ms, idx; - unsigned i = 0, c = 0; - char *r_name; - int n_ifindices, r; - - BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); - - assert(name); - assert(pat); - assert(buffer); - assert(errnop); - assert(h_errnop); - - r = sd_machine_get_class(name, &class); - if (r < 0) - goto fail; - if (!streq(class, "container")) { - r = -ENOTTY; - goto fail; - } - - n_ifindices = sd_machine_get_ifindices(name, &ifindices); - if (n_ifindices < 0) { - r = n_ifindices; - goto fail; - } - - r = sd_bus_open_system(&bus); - if (r < 0) - goto fail; - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetMachineAddresses", - NULL, - &reply, - "s", name); - if (r < 0) - goto fail; - - r = sd_bus_message_enter_container(reply, 'a', "(iay)"); - if (r < 0) - goto fail; - - r = count_addresses(reply, AF_UNSPEC, &c); - if (r < 0) - goto fail; - - if (c <= 0) { - *errnop = ESRCH; - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } - - l = strlen(name); - ms = ALIGN(l+1) + ALIGN(sizeof(struct gaih_addrtuple)) * c; - if (buflen < ms) { - *errnop = ENOMEM; - *h_errnop = TRY_AGAIN; - return NSS_STATUS_TRYAGAIN; - } - - /* First, append name */ - r_name = buffer; - memcpy(r_name, name, l+1); - idx = ALIGN(l+1); - - /* Second, append addresses */ - r_tuple_first = (struct gaih_addrtuple*) (buffer + idx); - while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) { - int family; - const void *a; - size_t sz; - - r = sd_bus_message_read(reply, "i", &family); - if (r < 0) - goto fail; - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - goto fail; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto fail; - - if (!IN_SET(family, AF_INET, AF_INET6)) { - r = -EAFNOSUPPORT; - goto fail; - } - - if (sz != FAMILY_ADDRESS_SIZE(family)) { - r = -EINVAL; - goto fail; - } - - r_tuple = (struct gaih_addrtuple*) (buffer + idx); - r_tuple->next = i == c-1 ? NULL : (struct gaih_addrtuple*) ((char*) r_tuple + ALIGN(sizeof(struct gaih_addrtuple))); - r_tuple->name = r_name; - r_tuple->family = family; - r_tuple->scopeid = n_ifindices == 1 ? ifindices[0] : 0; - memcpy(r_tuple->addr, a, sz); - - idx += ALIGN(sizeof(struct gaih_addrtuple)); - i++; - } - - assert(i == c); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto fail; - - assert(idx == ms); - - if (*pat) - **pat = *r_tuple_first; - else - *pat = r_tuple_first; - - if (ttlp) - *ttlp = 0; - - /* Explicitly reset all error variables */ - *errnop = 0; - *h_errnop = NETDB_SUCCESS; - h_errno = 0; - - return NSS_STATUS_SUCCESS; - -fail: - *errnop = -r; - *h_errnop = NO_DATA; - return NSS_STATUS_UNAVAIL; -} - -enum nss_status _nss_mymachines_gethostbyname3_r( - const char *name, - int af, - struct hostent *result, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp, - char **canonp) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_free_ char *class = NULL; - unsigned c = 0, i = 0; - char *r_name, *r_aliases, *r_addr, *r_addr_list; - size_t l, idx, ms, alen; - int r; - - BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); - - assert(name); - assert(result); - assert(buffer); - assert(errnop); - assert(h_errnop); - - if (af == AF_UNSPEC) - af = AF_INET; - - if (af != AF_INET && af != AF_INET6) { - r = -EAFNOSUPPORT; - goto fail; - } - - r = sd_machine_get_class(name, &class); - if (r < 0) - goto fail; - if (!streq(class, "container")) { - r = -ENOTTY; - goto fail; - } - - r = sd_bus_open_system(&bus); - if (r < 0) - goto fail; - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetMachineAddresses", - NULL, - &reply, - "s", name); - if (r < 0) - goto fail; - - r = sd_bus_message_enter_container(reply, 'a', "(iay)"); - if (r < 0) - goto fail; - - r = count_addresses(reply, af, &c); - if (r < 0) - goto fail; - - if (c <= 0) { - *errnop = ENOENT; - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } - - alen = FAMILY_ADDRESS_SIZE(af); - l = strlen(name); - - ms = ALIGN(l+1) + c * ALIGN(alen) + (c+2) * sizeof(char*); - - if (buflen < ms) { - *errnop = ENOMEM; - *h_errnop = NO_RECOVERY; - return NSS_STATUS_TRYAGAIN; - } - - /* First, append name */ - r_name = buffer; - memcpy(r_name, name, l+1); - idx = ALIGN(l+1); - - /* Second, create aliases array */ - r_aliases = buffer + idx; - ((char**) r_aliases)[0] = NULL; - idx += sizeof(char*); - - /* Third, append addresses */ - r_addr = buffer + idx; - while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) { - int family; - const void *a; - size_t sz; - - r = sd_bus_message_read(reply, "i", &family); - if (r < 0) - goto fail; - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - goto fail; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto fail; - - if (family != af) - continue; - - if (sz != alen) { - r = -EINVAL; - goto fail; - } - - memcpy(r_addr + i*ALIGN(alen), a, alen); - i++; - } - - assert(i == c); - idx += c * ALIGN(alen); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto fail; - - /* Third, append address pointer array */ - r_addr_list = buffer + idx; - for (i = 0; i < c; i++) - ((char**) r_addr_list)[i] = r_addr + i*ALIGN(alen); - - ((char**) r_addr_list)[i] = NULL; - idx += (c+1) * sizeof(char*); - - assert(idx == ms); - - result->h_name = r_name; - result->h_aliases = (char**) r_aliases; - result->h_addrtype = af; - result->h_length = alen; - result->h_addr_list = (char**) r_addr_list; - - if (ttlp) - *ttlp = 0; - - if (canonp) - *canonp = r_name; - - /* Explicitly reset all error variables */ - *errnop = 0; - *h_errnop = NETDB_SUCCESS; - h_errno = 0; - - return NSS_STATUS_SUCCESS; - -fail: - *errnop = -r; - *h_errnop = NO_DATA; - return NSS_STATUS_UNAVAIL; -} - -NSS_GETHOSTBYNAME_FALLBACKS(mymachines); - -enum nss_status _nss_mymachines_getpwnam_r( - const char *name, - struct passwd *pwd, - char *buffer, size_t buflen, - int *errnop) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - const char *p, *e, *machine; - uint32_t mapped; - uid_t uid; - size_t l; - int r; - - BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); - - assert(name); - assert(pwd); - - p = startswith(name, "vu-"); - if (!p) - goto not_found; - - e = strrchr(p, '-'); - if (!e || e == p) - goto not_found; - - if (e - p > HOST_NAME_MAX - 1) /* -1 for the last dash */ - goto not_found; - - r = parse_uid(e + 1, &uid); - if (r < 0) - goto not_found; - - machine = strndupa(p, e - p); - if (!machine_name_is_valid(machine)) - goto not_found; - - r = sd_bus_open_system(&bus); - if (r < 0) - goto fail; - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "MapFromMachineUser", - &error, - &reply, - "su", - machine, (uint32_t) uid); - if (r < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_USER_MAPPING)) - goto not_found; - - goto fail; - } - - r = sd_bus_message_read(reply, "u", &mapped); - if (r < 0) - goto fail; - - /* Refuse to work if the mapped address is in the host UID range, or if there was no mapping at all. */ - if (mapped < HOST_UID_LIMIT || mapped == uid) - goto not_found; - - l = strlen(name); - if (buflen < l+1) { - *errnop = ENOMEM; - return NSS_STATUS_TRYAGAIN; - } - - memcpy(buffer, name, l+1); - - pwd->pw_name = buffer; - pwd->pw_uid = mapped; - pwd->pw_gid = 65534; /* nobody */ - pwd->pw_gecos = buffer; - pwd->pw_passwd = (char*) "*"; /* locked */ - pwd->pw_dir = (char*) "/"; - pwd->pw_shell = (char*) "/sbin/nologin"; - - *errnop = 0; - return NSS_STATUS_SUCCESS; - -not_found: - *errnop = 0; - return NSS_STATUS_NOTFOUND; - -fail: - *errnop = -r; - return NSS_STATUS_UNAVAIL; -} - -enum nss_status _nss_mymachines_getpwuid_r( - uid_t uid, - struct passwd *pwd, - char *buffer, size_t buflen, - int *errnop) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - const char *machine, *object; - uint32_t mapped; - int r; - - BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); - - if (!uid_is_valid(uid)) { - r = -EINVAL; - goto fail; - } - - /* We consider all uids < 65536 host uids */ - if (uid < HOST_UID_LIMIT) - goto not_found; - - r = sd_bus_open_system(&bus); - if (r < 0) - goto fail; - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "MapToMachineUser", - &error, - &reply, - "u", - (uint32_t) uid); - if (r < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_USER_MAPPING)) - goto not_found; - - goto fail; - } - - r = sd_bus_message_read(reply, "sou", &machine, &object, &mapped); - if (r < 0) - goto fail; - - if (mapped == uid) - goto not_found; - - if (snprintf(buffer, buflen, "vu-%s-" UID_FMT, machine, (uid_t) mapped) >= (int) buflen) { - *errnop = ENOMEM; - return NSS_STATUS_TRYAGAIN; - } - - pwd->pw_name = buffer; - pwd->pw_uid = uid; - pwd->pw_gid = 65534; /* nobody */ - pwd->pw_gecos = buffer; - pwd->pw_passwd = (char*) "*"; /* locked */ - pwd->pw_dir = (char*) "/"; - pwd->pw_shell = (char*) "/sbin/nologin"; - - *errnop = 0; - return NSS_STATUS_SUCCESS; - -not_found: - *errnop = 0; - return NSS_STATUS_NOTFOUND; - -fail: - *errnop = -r; - return NSS_STATUS_UNAVAIL; -} - -enum nss_status _nss_mymachines_getgrnam_r( - const char *name, - struct group *gr, - char *buffer, size_t buflen, - int *errnop) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - const char *p, *e, *machine; - uint32_t mapped; - uid_t gid; - size_t l; - int r; - - BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); - - assert(name); - assert(gr); - - p = startswith(name, "vg-"); - if (!p) - goto not_found; - - e = strrchr(p, '-'); - if (!e || e == p) - goto not_found; - - if (e - p > HOST_NAME_MAX - 1) /* -1 for the last dash */ - goto not_found; - - r = parse_gid(e + 1, &gid); - if (r < 0) - goto not_found; - - machine = strndupa(p, e - p); - if (!machine_name_is_valid(machine)) - goto not_found; - - r = sd_bus_open_system(&bus); - if (r < 0) - goto fail; - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "MapFromMachineGroup", - &error, - &reply, - "su", - machine, (uint32_t) gid); - if (r < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_GROUP_MAPPING)) - goto not_found; - - goto fail; - } - - r = sd_bus_message_read(reply, "u", &mapped); - if (r < 0) - goto fail; - - if (mapped < HOST_GID_LIMIT || mapped == gid) - goto not_found; - - l = sizeof(char*) + strlen(name) + 1; - if (buflen < l) { - *errnop = ENOMEM; - return NSS_STATUS_TRYAGAIN; - } - - memzero(buffer, sizeof(char*)); - strcpy(buffer + sizeof(char*), name); - - gr->gr_name = buffer + sizeof(char*); - gr->gr_gid = gid; - gr->gr_passwd = (char*) "*"; /* locked */ - gr->gr_mem = (char**) buffer; - - *errnop = 0; - return NSS_STATUS_SUCCESS; - -not_found: - *errnop = 0; - return NSS_STATUS_NOTFOUND; - -fail: - *errnop = -r; - return NSS_STATUS_UNAVAIL; -} - -enum nss_status _nss_mymachines_getgrgid_r( - gid_t gid, - struct group *gr, - char *buffer, size_t buflen, - int *errnop) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - const char *machine, *object; - uint32_t mapped; - int r; - - BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); - - if (!gid_is_valid(gid)) { - r = -EINVAL; - goto fail; - } - - /* We consider all gids < 65536 host gids */ - if (gid < HOST_GID_LIMIT) - goto not_found; - - r = sd_bus_open_system(&bus); - if (r < 0) - goto fail; - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "MapToMachineGroup", - &error, - &reply, - "u", - (uint32_t) gid); - if (r < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_GROUP_MAPPING)) - goto not_found; - - goto fail; - } - - r = sd_bus_message_read(reply, "sou", &machine, &object, &mapped); - if (r < 0) - goto fail; - - if (mapped == gid) - goto not_found; - - if (buflen < sizeof(char*) + 1) { - *errnop = ENOMEM; - return NSS_STATUS_TRYAGAIN; - } - - memzero(buffer, sizeof(char*)); - if (snprintf(buffer + sizeof(char*), buflen - sizeof(char*), "vg-%s-" GID_FMT, machine, (gid_t) mapped) >= (int) buflen) { - *errnop = ENOMEM; - return NSS_STATUS_TRYAGAIN; - } - - gr->gr_name = buffer + sizeof(char*); - gr->gr_gid = gid; - gr->gr_passwd = (char*) "*"; /* locked */ - gr->gr_mem = (char**) buffer; - - *errnop = 0; - return NSS_STATUS_SUCCESS; - -not_found: - *errnop = 0; - return NSS_STATUS_NOTFOUND; - -fail: - *errnop = -r; - return NSS_STATUS_UNAVAIL; -} diff --git a/src/nss-mymachines/nss-mymachines.sym b/src/nss-mymachines/nss-mymachines.sym deleted file mode 100644 index 0728ac3ba7..0000000000 --- a/src/nss-mymachines/nss-mymachines.sym +++ /dev/null @@ -1,21 +0,0 @@ -/*** - This file is part of systemd. - - 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. -***/ - -{ -global: - _nss_mymachines_gethostbyname_r; - _nss_mymachines_gethostbyname2_r; - _nss_mymachines_gethostbyname3_r; - _nss_mymachines_gethostbyname4_r; - _nss_mymachines_getpwnam_r; - _nss_mymachines_getpwuid_r; - _nss_mymachines_getgrnam_r; - _nss_mymachines_getgrgid_r; -local: *; -}; diff --git a/src/nss-resolve/Makefile b/src/nss-resolve/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/nss-resolve/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/nss-resolve/nss-resolve.c b/src/nss-resolve/nss-resolve.c deleted file mode 100644 index 5ce10f1cbd..0000000000 --- a/src/nss-resolve/nss-resolve.c +++ /dev/null @@ -1,675 +0,0 @@ -/*** - 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 -#include -#include -#include -#include - -#include "sd-bus.h" - -#include "bus-common-errors.h" -#include "in-addr-util.h" -#include "macro.h" -#include "nss-util.h" -#include "string-util.h" -#include "util.h" -#include "signal-util.h" - -NSS_GETHOSTBYNAME_PROTOTYPES(resolve); -NSS_GETHOSTBYADDR_PROTOTYPES(resolve); - -#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) - -typedef void (*voidfunc_t)(void); - -static voidfunc_t find_fallback(const char *module, const char *symbol) { - void *dl; - - /* Try to find a fallback NSS module symbol */ - - dl = dlopen(module, RTLD_LAZY|RTLD_NODELETE); - if (!dl) - return NULL; - - return dlsym(dl, symbol); -} - -static bool bus_error_shall_fallback(sd_bus_error *e) { - return sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN) || - sd_bus_error_has_name(e, SD_BUS_ERROR_NAME_HAS_NO_OWNER) || - sd_bus_error_has_name(e, SD_BUS_ERROR_NO_REPLY) || - sd_bus_error_has_name(e, SD_BUS_ERROR_ACCESS_DENIED); -} - -static int count_addresses(sd_bus_message *m, int af, const char **canonical) { - int c = 0, r; - - assert(m); - assert(canonical); - - r = sd_bus_message_enter_container(m, 'a', "(iiay)"); - if (r < 0) - return r; - - while ((r = sd_bus_message_enter_container(m, 'r', "iiay")) > 0) { - int family, ifindex; - - assert_cc(sizeof(int32_t) == sizeof(int)); - - r = sd_bus_message_read(m, "ii", &ifindex, &family); - if (r < 0) - return r; - - r = sd_bus_message_skip(m, "ay"); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - if (af != AF_UNSPEC && family != af) - continue; - - c++; - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - r = sd_bus_message_read(m, "s", canonical); - if (r < 0) - return r; - - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - return c; -} - -enum nss_status _nss_resolve_gethostbyname4_r( - const char *name, - struct gaih_addrtuple **pat, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - struct gaih_addrtuple *r_tuple, *r_tuple_first = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - const char *canonical = NULL; - size_t l, ms, idx; - char *r_name; - int c, r, i = 0; - - BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); - - assert(name); - assert(pat); - assert(buffer); - assert(errnop); - assert(h_errnop); - - r = sd_bus_open_system(&bus); - if (r < 0) - goto fallback; - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveHostname"); - if (r < 0) - goto fail; - - r = sd_bus_message_set_auto_start(req, false); - if (r < 0) - goto fail; - - r = sd_bus_message_append(req, "isit", 0, name, AF_UNSPEC, (uint64_t) 0); - if (r < 0) - goto fail; - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) { - if (sd_bus_error_has_name(&error, _BUS_ERROR_DNS "NXDOMAIN")) { - *errnop = ESRCH; - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } - - if (bus_error_shall_fallback(&error)) - goto fallback; - - goto fail; - } - - c = count_addresses(reply, AF_UNSPEC, &canonical); - if (c < 0) { - r = c; - goto fail; - } - if (c == 0) { - *errnop = ESRCH; - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } - - if (isempty(canonical)) - canonical = name; - - l = strlen(canonical); - ms = ALIGN(l+1) + ALIGN(sizeof(struct gaih_addrtuple)) * c; - if (buflen < ms) { - *errnop = ENOMEM; - *h_errnop = TRY_AGAIN; - return NSS_STATUS_TRYAGAIN; - } - - /* First, append name */ - r_name = buffer; - memcpy(r_name, canonical, l+1); - idx = ALIGN(l+1); - - /* Second, append addresses */ - r_tuple_first = (struct gaih_addrtuple*) (buffer + idx); - - r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); - if (r < 0) - goto fail; - - while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { - int family, ifindex; - const void *a; - size_t sz; - - assert_cc(sizeof(int32_t) == sizeof(int)); - - r = sd_bus_message_read(reply, "ii", &ifindex, &family); - if (r < 0) - goto fail; - - if (ifindex < 0) { - r = -EINVAL; - goto fail; - } - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - goto fail; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto fail; - - if (!IN_SET(family, AF_INET, AF_INET6)) - continue; - - if (sz != FAMILY_ADDRESS_SIZE(family)) { - r = -EINVAL; - goto fail; - } - - r_tuple = (struct gaih_addrtuple*) (buffer + idx); - r_tuple->next = i == c-1 ? NULL : (struct gaih_addrtuple*) ((char*) r_tuple + ALIGN(sizeof(struct gaih_addrtuple))); - r_tuple->name = r_name; - r_tuple->family = family; - r_tuple->scopeid = ifindex; - memcpy(r_tuple->addr, a, sz); - - idx += ALIGN(sizeof(struct gaih_addrtuple)); - i++; - } - if (r < 0) - goto fail; - - assert(i == c); - assert(idx == ms); - - if (*pat) - **pat = *r_tuple_first; - else - *pat = r_tuple_first; - - if (ttlp) - *ttlp = 0; - - /* Explicitly reset all error variables */ - *errnop = 0; - *h_errnop = NETDB_SUCCESS; - h_errno = 0; - - return NSS_STATUS_SUCCESS; - -fallback: - { - _nss_gethostbyname4_r_t fallback; - - fallback = (_nss_gethostbyname4_r_t) - find_fallback("libnss_dns.so.2", "_nss_dns_gethostbyname4_r"); - - if (fallback) - return fallback(name, pat, buffer, buflen, errnop, h_errnop, ttlp); - } - -fail: - *errnop = -r; - *h_errnop = NO_RECOVERY; - return NSS_STATUS_UNAVAIL; -} - -enum nss_status _nss_resolve_gethostbyname3_r( - const char *name, - int af, - struct hostent *result, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp, - char **canonp) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char *r_name, *r_aliases, *r_addr, *r_addr_list; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - size_t l, idx, ms, alen; - const char *canonical; - int c, r, i = 0; - - BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); - - assert(name); - assert(result); - assert(buffer); - assert(errnop); - assert(h_errnop); - - if (af == AF_UNSPEC) - af = AF_INET; - - if (af != AF_INET && af != AF_INET6) { - r = -EAFNOSUPPORT; - goto fail; - } - - r = sd_bus_open_system(&bus); - if (r < 0) - goto fallback; - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveHostname"); - if (r < 0) - goto fail; - - r = sd_bus_message_set_auto_start(req, false); - if (r < 0) - goto fail; - - r = sd_bus_message_append(req, "isit", 0, name, af, (uint64_t) 0); - if (r < 0) - goto fail; - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) { - if (sd_bus_error_has_name(&error, _BUS_ERROR_DNS "NXDOMAIN")) { - *errnop = ESRCH; - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } - - if (bus_error_shall_fallback(&error)) - goto fallback; - - goto fail; - } - - c = count_addresses(reply, af, &canonical); - if (c < 0) { - r = c; - goto fail; - } - if (c == 0) { - *errnop = ESRCH; - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } - - if (isempty(canonical)) - canonical = name; - - alen = FAMILY_ADDRESS_SIZE(af); - l = strlen(canonical); - - ms = ALIGN(l+1) + c * ALIGN(alen) + (c+2) * sizeof(char*); - - if (buflen < ms) { - *errnop = ENOMEM; - *h_errnop = TRY_AGAIN; - return NSS_STATUS_TRYAGAIN; - } - - /* First, append name */ - r_name = buffer; - memcpy(r_name, canonical, l+1); - idx = ALIGN(l+1); - - /* Second, create empty aliases array */ - r_aliases = buffer + idx; - ((char**) r_aliases)[0] = NULL; - idx += sizeof(char*); - - /* Third, append addresses */ - r_addr = buffer + idx; - - r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); - if (r < 0) - goto fail; - - while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { - int ifindex, family; - const void *a; - size_t sz; - - r = sd_bus_message_read(reply, "ii", &ifindex, &family); - if (r < 0) - goto fail; - - if (ifindex < 0) { - r = -EINVAL; - goto fail; - } - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - goto fail; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto fail; - - if (family != af) - continue; - - if (sz != alen) { - r = -EINVAL; - goto fail; - } - - memcpy(r_addr + i*ALIGN(alen), a, alen); - i++; - } - if (r < 0) - goto fail; - - assert(i == c); - idx += c * ALIGN(alen); - - /* Fourth, append address pointer array */ - r_addr_list = buffer + idx; - for (i = 0; i < c; i++) - ((char**) r_addr_list)[i] = r_addr + i*ALIGN(alen); - - ((char**) r_addr_list)[i] = NULL; - idx += (c+1) * sizeof(char*); - - assert(idx == ms); - - result->h_name = r_name; - result->h_aliases = (char**) r_aliases; - result->h_addrtype = af; - result->h_length = alen; - result->h_addr_list = (char**) r_addr_list; - - /* Explicitly reset all error variables */ - *errnop = 0; - *h_errnop = NETDB_SUCCESS; - h_errno = 0; - - if (ttlp) - *ttlp = 0; - - if (canonp) - *canonp = r_name; - - return NSS_STATUS_SUCCESS; - -fallback: - { - _nss_gethostbyname3_r_t fallback; - - fallback = (_nss_gethostbyname3_r_t) - find_fallback("libnss_dns.so.2", "_nss_dns_gethostbyname3_r"); - if (fallback) - return fallback(name, af, result, buffer, buflen, errnop, h_errnop, ttlp, canonp); - } - -fail: - *errnop = -r; - *h_errnop = NO_RECOVERY; - return NSS_STATUS_UNAVAIL; -} - -enum nss_status _nss_resolve_gethostbyaddr2_r( - const void* addr, socklen_t len, - int af, - struct hostent *result, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char *r_name, *r_aliases, *r_addr, *r_addr_list; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - unsigned c = 0, i = 0; - size_t ms = 0, idx; - const char *n; - int r, ifindex; - - BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); - - assert(addr); - assert(result); - assert(buffer); - assert(errnop); - assert(h_errnop); - - if (!IN_SET(af, AF_INET, AF_INET6)) { - *errnop = EAFNOSUPPORT; - *h_errnop = NO_DATA; - return NSS_STATUS_UNAVAIL; - } - - if (len != FAMILY_ADDRESS_SIZE(af)) { - *errnop = EINVAL; - *h_errnop = NO_RECOVERY; - return NSS_STATUS_UNAVAIL; - } - - r = sd_bus_open_system(&bus); - if (r < 0) - goto fallback; - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveAddress"); - if (r < 0) - goto fail; - - r = sd_bus_message_set_auto_start(req, false); - if (r < 0) - goto fail; - - r = sd_bus_message_append(req, "ii", 0, af); - if (r < 0) - goto fail; - - r = sd_bus_message_append_array(req, 'y', addr, len); - if (r < 0) - goto fail; - - r = sd_bus_message_append(req, "t", (uint64_t) 0); - if (r < 0) - goto fail; - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) { - if (sd_bus_error_has_name(&error, _BUS_ERROR_DNS "NXDOMAIN")) { - *errnop = ESRCH; - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } - - if (bus_error_shall_fallback(&error)) - goto fallback; - - - *errnop = -r; - *h_errnop = NO_RECOVERY; - return NSS_STATUS_UNAVAIL; - } - - r = sd_bus_message_enter_container(reply, 'a', "(is)"); - if (r < 0) - goto fail; - - while ((r = sd_bus_message_read(reply, "(is)", &ifindex, &n)) > 0) { - - if (ifindex < 0) { - r = -EINVAL; - goto fail; - } - - c++; - ms += ALIGN(strlen(n) + 1); - } - if (r < 0) - goto fail; - - r = sd_bus_message_rewind(reply, false); - if (r < 0) - return r; - - if (c <= 0) { - *errnop = ESRCH; - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } - - ms += ALIGN(len) + /* the address */ - 2 * sizeof(char*) + /* pointers to the address, plus trailing NULL */ - c * sizeof(char*); /* pointers to aliases, plus trailing NULL */ - - if (buflen < ms) { - *errnop = ENOMEM; - *h_errnop = TRY_AGAIN; - return NSS_STATUS_TRYAGAIN; - } - - /* First, place address */ - r_addr = buffer; - memcpy(r_addr, addr, len); - idx = ALIGN(len); - - /* Second, place address list */ - r_addr_list = buffer + idx; - ((char**) r_addr_list)[0] = r_addr; - ((char**) r_addr_list)[1] = NULL; - idx += sizeof(char*) * 2; - - /* Third, reserve space for the aliases array */ - r_aliases = buffer + idx; - idx += sizeof(char*) * c; - - /* Fourth, place aliases */ - i = 0; - r_name = buffer + idx; - while ((r = sd_bus_message_read(reply, "(is)", &ifindex, &n)) > 0) { - char *p; - size_t l; - - l = strlen(n); - p = buffer + idx; - memcpy(p, n, l+1); - - if (i > 0) - ((char**) r_aliases)[i-1] = p; - i++; - - idx += ALIGN(l+1); - } - if (r < 0) - goto fail; - - ((char**) r_aliases)[c-1] = NULL; - assert(idx == ms); - - result->h_name = r_name; - result->h_aliases = (char**) r_aliases; - result->h_addrtype = af; - result->h_length = len; - result->h_addr_list = (char**) r_addr_list; - - if (ttlp) - *ttlp = 0; - - /* Explicitly reset all error variables */ - *errnop = 0; - *h_errnop = NETDB_SUCCESS; - h_errno = 0; - - return NSS_STATUS_SUCCESS; - -fallback: - { - _nss_gethostbyaddr2_r_t fallback; - - fallback = (_nss_gethostbyaddr2_r_t) - find_fallback("libnss_dns.so.2", "_nss_dns_gethostbyaddr2_r"); - - if (fallback) - return fallback(addr, len, af, result, buffer, buflen, errnop, h_errnop, ttlp); - } - -fail: - *errnop = -r; - *h_errnop = NO_RECOVERY; - return NSS_STATUS_UNAVAIL; -} - -NSS_GETHOSTBYNAME_FALLBACKS(resolve); -NSS_GETHOSTBYADDR_FALLBACKS(resolve); diff --git a/src/nss-resolve/nss-resolve.sym b/src/nss-resolve/nss-resolve.sym deleted file mode 100644 index df8dff2a20..0000000000 --- a/src/nss-resolve/nss-resolve.sym +++ /dev/null @@ -1,19 +0,0 @@ -/*** - This file is part of systemd. - - 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. -***/ - -{ -global: - _nss_resolve_gethostbyname_r; - _nss_resolve_gethostbyname2_r; - _nss_resolve_gethostbyname3_r; - _nss_resolve_gethostbyname4_r; - _nss_resolve_gethostbyaddr_r; - _nss_resolve_gethostbyaddr2_r; -local: *; -}; diff --git a/src/path/Makefile b/src/path/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/path/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/path/path.c b/src/path/path.c deleted file mode 100644 index 61d877fcf8..0000000000 --- a/src/path/path.c +++ /dev/null @@ -1,198 +0,0 @@ -/*** - 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 -#include -#include - -#include "sd-path.h" - -#include "alloc-util.h" -#include "log.h" -#include "macro.h" -#include "string-util.h" -#include "util.h" - -static const char *arg_suffix = NULL; - -static const char* const path_table[_SD_PATH_MAX] = { - [SD_PATH_TEMPORARY] = "temporary", - [SD_PATH_TEMPORARY_LARGE] = "temporary-large", - [SD_PATH_SYSTEM_BINARIES] = "system-binaries", - [SD_PATH_SYSTEM_INCLUDE] = "system-include", - [SD_PATH_SYSTEM_LIBRARY_PRIVATE] = "system-library-private", - [SD_PATH_SYSTEM_LIBRARY_ARCH] = "system-library-arch", - [SD_PATH_SYSTEM_SHARED] = "system-shared", - [SD_PATH_SYSTEM_CONFIGURATION_FACTORY] = "system-configuration-factory", - [SD_PATH_SYSTEM_STATE_FACTORY] = "system-state-factory", - [SD_PATH_SYSTEM_CONFIGURATION] = "system-configuration", - [SD_PATH_SYSTEM_RUNTIME] = "system-runtime", - [SD_PATH_SYSTEM_RUNTIME_LOGS] = "system-runtime-logs", - [SD_PATH_SYSTEM_STATE_PRIVATE] = "system-state-private", - [SD_PATH_SYSTEM_STATE_LOGS] = "system-state-logs", - [SD_PATH_SYSTEM_STATE_CACHE] = "system-state-cache", - [SD_PATH_SYSTEM_STATE_SPOOL] = "system-state-spool", - [SD_PATH_USER_BINARIES] = "user-binaries", - [SD_PATH_USER_LIBRARY_PRIVATE] = "user-library-private", - [SD_PATH_USER_LIBRARY_ARCH] = "user-library-arch", - [SD_PATH_USER_SHARED] = "user-shared", - [SD_PATH_USER_CONFIGURATION] = "user-configuration", - [SD_PATH_USER_RUNTIME] = "user-runtime", - [SD_PATH_USER_STATE_CACHE] = "user-state-cache", - [SD_PATH_USER] = "user", - [SD_PATH_USER_DOCUMENTS] = "user-documents", - [SD_PATH_USER_MUSIC] = "user-music", - [SD_PATH_USER_PICTURES] = "user-pictures", - [SD_PATH_USER_VIDEOS] = "user-videos", - [SD_PATH_USER_DOWNLOAD] = "user-download", - [SD_PATH_USER_PUBLIC] = "user-public", - [SD_PATH_USER_TEMPLATES] = "user-templates", - [SD_PATH_USER_DESKTOP] = "user-desktop", - [SD_PATH_SEARCH_BINARIES] = "search-binaries", - [SD_PATH_SEARCH_LIBRARY_PRIVATE] = "search-library-private", - [SD_PATH_SEARCH_LIBRARY_ARCH] = "search-library-arch", - [SD_PATH_SEARCH_SHARED] = "search-shared", - [SD_PATH_SEARCH_CONFIGURATION_FACTORY] = "search-configuration-factory", - [SD_PATH_SEARCH_STATE_FACTORY] = "search-state-factory", - [SD_PATH_SEARCH_CONFIGURATION] = "search-configuration", -}; - -static int list_homes(void) { - uint64_t i = 0; - int r = 0; - - for (i = 0; i < ELEMENTSOF(path_table); i++) { - _cleanup_free_ char *p = NULL; - int q; - - q = sd_path_home(i, arg_suffix, &p); - if (q == -ENXIO) - continue; - if (q < 0) { - log_error_errno(r, "Failed to query %s: %m", path_table[i]); - r = q; - continue; - } - - printf("%s: %s\n", path_table[i], p); - } - - return r; -} - -static int print_home(const char *n) { - uint64_t i = 0; - int r; - - for (i = 0; i < ELEMENTSOF(path_table); i++) { - if (streq(path_table[i], n)) { - _cleanup_free_ char *p = NULL; - - r = sd_path_home(i, arg_suffix, &p); - if (r < 0) - return log_error_errno(r, "Failed to query %s: %m", n); - - printf("%s\n", p); - return 0; - } - } - - log_error("Path %s not known.", n); - return -EOPNOTSUPP; -} - -static void help(void) { - printf("%s [OPTIONS...] [NAME...]\n\n" - "Show system and user paths.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --suffix=SUFFIX Suffix to append to paths\n", - program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_SUFFIX, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "suffix", required_argument, NULL, ARG_SUFFIX }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_SUFFIX: - arg_suffix = optarg; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -int main(int argc, char* argv[]) { - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (argc > optind) { - int i, q; - - for (i = optind; i < argc; i++) { - q = print_home(argv[i]); - if (q < 0) - r = q; - } - } else - r = list_homes(); - - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/quotacheck/Makefile b/src/quotacheck/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/quotacheck/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/quotacheck/quotacheck.c b/src/quotacheck/quotacheck.c deleted file mode 100644 index 6d8c05f046..0000000000 --- a/src/quotacheck/quotacheck.c +++ /dev/null @@ -1,124 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include - -#include "proc-cmdline.h" -#include "process-util.h" -#include "signal-util.h" -#include "string-util.h" -#include "util.h" - -static bool arg_skip = false; -static bool arg_force = false; - -static int parse_proc_cmdline_item(const char *key, const char *value) { - - if (streq(key, "quotacheck.mode") && value) { - - if (streq(value, "auto")) - arg_force = arg_skip = false; - else if (streq(value, "force")) - arg_force = true; - else if (streq(value, "skip")) - arg_skip = true; - else - log_warning("Invalid quotacheck.mode= parameter '%s'. Ignoring.", value); - } - -#ifdef HAVE_SYSV_COMPAT - else if (streq(key, "forcequotacheck") && !value) { - log_warning("Please use 'quotacheck.mode=force' rather than 'forcequotacheck' on the kernel command line."); - arg_force = true; - } -#endif - - return 0; -} - -static void test_files(void) { - -#ifdef HAVE_SYSV_COMPAT - if (access("/forcequotacheck", F_OK) >= 0) { - log_error("Please pass 'quotacheck.mode=force' on the kernel command line rather than creating /forcequotacheck on the root file system."); - arg_force = true; - } -#endif -} - -int main(int argc, char *argv[]) { - - static const char * const cmdline[] = { - QUOTACHECK, - "-anug", - NULL - }; - - pid_t pid; - int r; - - if (argc > 1) { - log_error("This program takes no arguments."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - r = parse_proc_cmdline(parse_proc_cmdline_item); - if (r < 0) - log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - - test_files(); - - if (!arg_force) { - if (arg_skip) - return EXIT_SUCCESS; - - if (access("/run/systemd/quotacheck", F_OK) < 0) - return EXIT_SUCCESS; - } - - pid = fork(); - if (pid < 0) { - log_error_errno(errno, "fork(): %m"); - return EXIT_FAILURE; - } else if (pid == 0) { - - /* Child */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - execv(cmdline[0], (char**) cmdline); - _exit(1); /* Operational error */ - } - - r = wait_for_terminate_and_warn("quotacheck", pid, true); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/random-seed/Makefile b/src/random-seed/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/random-seed/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/random-seed/random-seed.c b/src/random-seed/random-seed.c deleted file mode 100644 index 6748bb9dd3..0000000000 --- a/src/random-seed/random-seed.c +++ /dev/null @@ -1,176 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "io-util.h" -#include "log.h" -#include "mkdir.h" -#include "string-util.h" -#include "util.h" - -#define POOL_SIZE_MIN 512 - -int main(int argc, char *argv[]) { - _cleanup_close_ int seed_fd = -1, random_fd = -1; - _cleanup_free_ void* buf = NULL; - size_t buf_size = 0; - ssize_t k; - int r, open_rw_error; - FILE *f; - bool refresh_seed_file = true; - - if (argc != 2) { - log_error("This program requires one argument."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - /* Read pool size, if possible */ - f = fopen("/proc/sys/kernel/random/poolsize", "re"); - if (f) { - if (fscanf(f, "%zu", &buf_size) > 0) - /* poolsize is in bits on 2.6, but we want bytes */ - buf_size /= 8; - - fclose(f); - } - - if (buf_size <= POOL_SIZE_MIN) - buf_size = POOL_SIZE_MIN; - - buf = malloc(buf_size); - if (!buf) { - r = log_oom(); - goto finish; - } - - r = mkdir_parents_label(RANDOM_SEED, 0755); - if (r < 0) { - log_error_errno(r, "Failed to create directory " RANDOM_SEED_DIR ": %m"); - goto finish; - } - - /* When we load the seed we read it and write it to the device - * and then immediately update the saved seed with new data, - * to make sure the next boot gets seeded differently. */ - - if (streq(argv[1], "load")) { - - seed_fd = open(RANDOM_SEED, O_RDWR|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600); - open_rw_error = -errno; - if (seed_fd < 0) { - refresh_seed_file = false; - - seed_fd = open(RANDOM_SEED, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (seed_fd < 0) { - bool missing = errno == ENOENT; - - log_full_errno(missing ? LOG_DEBUG : LOG_ERR, - open_rw_error, "Failed to open " RANDOM_SEED " for writing: %m"); - r = log_full_errno(missing ? LOG_DEBUG : LOG_ERR, - errno, "Failed to open " RANDOM_SEED " for reading: %m"); - if (missing) - r = 0; - - goto finish; - } - } - - random_fd = open("/dev/urandom", O_RDWR|O_CLOEXEC|O_NOCTTY, 0600); - if (random_fd < 0) { - random_fd = open("/dev/urandom", O_WRONLY|O_CLOEXEC|O_NOCTTY, 0600); - if (random_fd < 0) { - r = log_error_errno(errno, "Failed to open /dev/urandom: %m"); - goto finish; - } - } - - k = loop_read(seed_fd, buf, buf_size, false); - if (k < 0) - r = log_error_errno(k, "Failed to read seed from " RANDOM_SEED ": %m"); - else if (k == 0) { - r = 0; - log_debug("Seed file " RANDOM_SEED " not yet initialized, proceeding."); - } else { - (void) lseek(seed_fd, 0, SEEK_SET); - - r = loop_write(random_fd, buf, (size_t) k, false); - if (r < 0) - log_error_errno(r, "Failed to write seed to /dev/urandom: %m"); - } - - } else if (streq(argv[1], "save")) { - - seed_fd = open(RANDOM_SEED, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600); - if (seed_fd < 0) { - r = log_error_errno(errno, "Failed to open " RANDOM_SEED ": %m"); - goto finish; - } - - random_fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (random_fd < 0) { - r = log_error_errno(errno, "Failed to open /dev/urandom: %m"); - goto finish; - } - - } else { - log_error("Unknown verb '%s'.", argv[1]); - r = -EINVAL; - goto finish; - } - - if (refresh_seed_file) { - - /* This is just a safety measure. Given that we are root and - * most likely created the file ourselves the mode and owner - * should be correct anyway. */ - (void) fchmod(seed_fd, 0600); - (void) fchown(seed_fd, 0, 0); - - k = loop_read(random_fd, buf, buf_size, false); - if (k < 0) { - r = log_error_errno(k, "Failed to read new seed from /dev/urandom: %m"); - goto finish; - } - if (k == 0) { - log_error("Got EOF while reading from /dev/urandom."); - r = -EIO; - goto finish; - } - - r = loop_write(seed_fd, buf, (size_t) k, false); - if (r < 0) - log_error_errno(r, "Failed to write new random seed file: %m"); - } - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/rc-local-generator/Makefile b/src/rc-local-generator/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/rc-local-generator/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/rc-local-generator/rc-local-generator.c b/src/rc-local-generator/rc-local-generator.c deleted file mode 100644 index 618bbe428d..0000000000 --- a/src/rc-local-generator/rc-local-generator.c +++ /dev/null @@ -1,101 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2011 Michal Schmidt - - 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 -#include - -#include "alloc-util.h" -#include "log.h" -#include "mkdir.h" -#include "string-util.h" -#include "util.h" - -#ifndef RC_LOCAL_SCRIPT_PATH_START -#define RC_LOCAL_SCRIPT_PATH_START "/etc/rc.d/rc.local" -#endif - -#ifndef RC_LOCAL_SCRIPT_PATH_STOP -#define RC_LOCAL_SCRIPT_PATH_STOP "/sbin/halt.local" -#endif - -static const char *arg_dest = "/tmp"; - -static int add_symlink(const char *service, const char *where) { - _cleanup_free_ char *from = NULL, *to = NULL; - int r; - - assert(service); - assert(where); - - from = strjoin(SYSTEM_DATA_UNIT_PATH, "/", service, NULL); - if (!from) - return log_oom(); - - to = strjoin(arg_dest, "/", where, ".wants/", service, NULL); - if (!to) - return log_oom(); - - mkdir_parents_label(to, 0755); - - r = symlink(from, to); - if (r < 0) { - if (errno == EEXIST) - return 0; - - return log_error_errno(errno, "Failed to create symlink %s: %m", to); - } - - return 1; -} - -int main(int argc, char *argv[]) { - int r = EXIT_SUCCESS; - - if (argc > 1 && argc != 4) { - log_error("This program takes three or no arguments."); - return EXIT_FAILURE; - } - - if (argc > 1) - arg_dest = argv[1]; - - log_set_target(LOG_TARGET_SAFE); - log_parse_environment(); - log_open(); - - umask(0022); - - if (access(RC_LOCAL_SCRIPT_PATH_START, X_OK) >= 0) { - log_debug("Automatically adding rc-local.service."); - - if (add_symlink("rc-local.service", "multi-user.target") < 0) - r = EXIT_FAILURE; - } - - if (access(RC_LOCAL_SCRIPT_PATH_STOP, X_OK) >= 0) { - log_debug("Automatically adding halt-local.service."); - - if (add_symlink("halt-local.service", "final.target") < 0) - r = EXIT_FAILURE; - } - - return r; -} diff --git a/src/remount-fs/Makefile b/src/remount-fs/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/remount-fs/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/remount-fs/remount-fs.c b/src/remount-fs/remount-fs.c deleted file mode 100644 index 6468d1eecd..0000000000 --- a/src/remount-fs/remount-fs.c +++ /dev/null @@ -1,155 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include - -#include "exit-status.h" -#include "log.h" -#include "mount-setup.h" -#include "mount-util.h" -#include "path-util.h" -#include "process-util.h" -#include "signal-util.h" -#include "strv.h" -#include "util.h" - -/* Goes through /etc/fstab and remounts all API file systems, applying - * options that are in /etc/fstab that systemd might not have - * respected */ - -int main(int argc, char *argv[]) { - _cleanup_hashmap_free_free_ Hashmap *pids = NULL; - _cleanup_endmntent_ FILE *f = NULL; - struct mntent* me; - int r; - - if (argc > 1) { - log_error("This program takes no argument."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - f = setmntent("/etc/fstab", "r"); - if (!f) { - if (errno == ENOENT) { - r = 0; - goto finish; - } - - r = log_error_errno(errno, "Failed to open /etc/fstab: %m"); - goto finish; - } - - pids = hashmap_new(NULL); - if (!pids) { - r = log_oom(); - goto finish; - } - - while ((me = getmntent(f))) { - pid_t pid; - int k; - char *s; - - /* Remount the root fs, /usr and all API VFS */ - if (!mount_point_is_api(me->mnt_dir) && - !path_equal(me->mnt_dir, "/") && - !path_equal(me->mnt_dir, "/usr")) - continue; - - log_debug("Remounting %s", me->mnt_dir); - - pid = fork(); - if (pid < 0) { - r = log_error_errno(errno, "Failed to fork: %m"); - goto finish; - } - - if (pid == 0) { - /* Child */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - (void) prctl(PR_SET_PDEATHSIG, SIGTERM); - - execv(MOUNT_PATH, STRV_MAKE(MOUNT_PATH, me->mnt_dir, "-o", "remount")); - - log_error_errno(errno, "Failed to execute " MOUNT_PATH ": %m"); - _exit(EXIT_FAILURE); - } - - /* Parent */ - - s = strdup(me->mnt_dir); - if (!s) { - r = log_oom(); - goto finish; - } - - k = hashmap_put(pids, PID_TO_PTR(pid), s); - if (k < 0) { - free(s); - r = log_oom(); - goto finish; - } - } - - r = 0; - while (!hashmap_isempty(pids)) { - siginfo_t si = {}; - char *s; - - if (waitid(P_ALL, 0, &si, WEXITED) < 0) { - - if (errno == EINTR) - continue; - - r = log_error_errno(errno, "waitid() failed: %m"); - goto finish; - } - - s = hashmap_remove(pids, PID_TO_PTR(si.si_pid)); - if (s) { - if (!is_clean_exit(si.si_code, si.si_status, NULL)) { - if (si.si_code == CLD_EXITED) - log_error(MOUNT_PATH " for %s exited with exit status %i.", s, si.si_status); - else - log_error(MOUNT_PATH " for %s terminated by signal %s.", s, signal_to_string(si.si_status)); - - r = -ENOEXEC; - } - - free(s); - } - } - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/reply-password/Makefile b/src/reply-password/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/reply-password/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/reply-password/reply-password.c b/src/reply-password/reply-password.c deleted file mode 100644 index 17eab9772e..0000000000 --- a/src/reply-password/reply-password.c +++ /dev/null @@ -1,96 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include - -#include "fd-util.h" -#include "log.h" -#include "macro.h" -#include "socket-util.h" -#include "string-util.h" -#include "util.h" - -static int send_on_socket(int fd, const char *socket_name, const void *packet, size_t size) { - union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - }; - - assert(fd >= 0); - assert(socket_name); - assert(packet); - - strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path)); - - if (sendto(fd, packet, size, MSG_NOSIGNAL, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) - return log_error_errno(errno, "Failed to send: %m"); - - return 0; -} - -int main(int argc, char *argv[]) { - _cleanup_close_ int fd = -1; - char packet[LINE_MAX]; - size_t length; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - if (argc != 3) { - log_error("Wrong number of arguments."); - return EXIT_FAILURE; - } - - if (streq(argv[1], "1")) { - - packet[0] = '+'; - if (!fgets(packet+1, sizeof(packet)-1, stdin)) { - r = log_error_errno(errno, "Failed to read password: %m"); - goto finish; - } - - truncate_nl(packet+1); - length = 1 + strlen(packet+1) + 1; - } else if (streq(argv[1], "0")) { - packet[0] = '-'; - length = 1; - } else { - log_error("Invalid first argument %s", argv[1]); - r = -EINVAL; - goto finish; - } - - fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (fd < 0) { - r = log_error_errno(errno, "socket() failed: %m"); - goto finish; - } - - r = send_on_socket(fd, argv[2], packet, length); - -finish: - memory_erase(packet, sizeof(packet)); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/resolve/.gitignore b/src/resolve/.gitignore deleted file mode 100644 index f0835923b7..0000000000 --- a/src/resolve/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -/resolved-gperf.c -/resolved.conf -/dns_type-from-name.gperf -/dns_type-from-name.h -/dns_type-list.txt -/dns_type-to-name.h diff --git a/src/resolve/Makefile b/src/resolve/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/resolve/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/resolve/RFCs b/src/resolve/RFCs deleted file mode 100644 index 09c85f9518..0000000000 --- a/src/resolve/RFCs +++ /dev/null @@ -1,59 +0,0 @@ -Y = Comprehensively Implemented, to the point appropriate for resolved -D = Comprehensively Implemented, by a dependency of resolved -! = Missing and something we might want to implement -~ = Needs no explicit support or doesn't apply -? = Is this relevant today? - = We are working on this - -Y https://tools.ietf.org/html/rfc1034 → DOMAIN NAMES - CONCEPTS AND FACILITIES -Y https://tools.ietf.org/html/rfc1035 → DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION -? https://tools.ietf.org/html/rfc1101 → DNS Encoding of Network Names and Other Types -Y https://tools.ietf.org/html/rfc1123 → Requirements for Internet Hosts — Application and Support -~ https://tools.ietf.org/html/rfc1464 → Using the Domain Name System To Store Arbitrary String Attributes -Y https://tools.ietf.org/html/rfc1536 → Common DNS Implementation Errors and Suggested Fixes -Y https://tools.ietf.org/html/rfc1876 → A Means for Expressing Location Information in the Domain Name System -Y https://tools.ietf.org/html/rfc2181 → Clarifications to the DNS Specification -Y https://tools.ietf.org/html/rfc2308 → Negative Caching of DNS Queries (DNS NCACHE) -Y https://tools.ietf.org/html/rfc2782 → A DNS RR for specifying the location of services (DNS SRV) -D https://tools.ietf.org/html/rfc3492 → Punycode: A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA) -Y https://tools.ietf.org/html/rfc3596 → DNS Extensions to Support IP Version 6 -Y https://tools.ietf.org/html/rfc3597 → Handling of Unknown DNS Resource Record (RR) Types -Y https://tools.ietf.org/html/rfc4033 → DNS Security Introduction and Requirements -Y https://tools.ietf.org/html/rfc4034 → Resource Records for the DNS Security Extensions -Y https://tools.ietf.org/html/rfc4035 → Protocol Modifications for the DNS Security Extensions -! https://tools.ietf.org/html/rfc4183 → A Suggested Scheme for DNS Resolution of Networks and Gateways -Y https://tools.ietf.org/html/rfc4255 → Using DNS to Securely Publish Secure Shell (SSH) Key Fingerprints -Y https://tools.ietf.org/html/rfc4343 → Domain Name System (DNS) Case Insensitivity Clarification -~ https://tools.ietf.org/html/rfc4470 → Minimally Covering NSEC Records and DNSSEC On-line Signing -Y https://tools.ietf.org/html/rfc4501 → Domain Name System Uniform Resource Identifiers -Y https://tools.ietf.org/html/rfc4509 → Use of SHA-256 in DNSSEC Delegation Signer (DS) Resource Records (RRs) -~ https://tools.ietf.org/html/rfc4592 → The Role of Wildcards in the Domain Name System -~ https://tools.ietf.org/html/rfc4697 → Observed DNS Resolution Misbehavior -Y https://tools.ietf.org/html/rfc4795 → Link-Local Multicast Name Resolution (LLMNR) -Y https://tools.ietf.org/html/rfc5011 → Automated Updates of DNS Security (DNSSEC) Trust Anchors -Y https://tools.ietf.org/html/rfc5155 → DNS Security (DNSSEC) Hashed Authenticated Denial of Existence -Y https://tools.ietf.org/html/rfc5452 → Measures for Making DNS More Resilient against Forged Answers -Y https://tools.ietf.org/html/rfc5702 → Use of SHA-2 Algorithms with RSA in DNSKEY and RRSIG Resource Records for DNSSEC -Y https://tools.ietf.org/html/rfc5890 → Internationalized Domain Names for Applications (IDNA): Definitions and Document Framework -Y https://tools.ietf.org/html/rfc5891 → Internationalized Domain Names in Applications (IDNA): Protocol -Y https://tools.ietf.org/html/rfc5966 → DNS Transport over TCP - Implementation Requirements -Y https://tools.ietf.org/html/rfc6303 → Locally Served DNS Zones -Y https://tools.ietf.org/html/rfc6604 → xNAME RCODE and Status Bits Clarification -Y https://tools.ietf.org/html/rfc6605 → Elliptic Curve Digital Signature Algorithm (DSA) for DNSSEC - https://tools.ietf.org/html/rfc6672 → DNAME Redirection in the DNS -! https://tools.ietf.org/html/rfc6731 → Improved Recursive DNS Server Selection for Multi-Interfaced Nodes -Y https://tools.ietf.org/html/rfc6761 → Special-Use Domain Names - https://tools.ietf.org/html/rfc6762 → Multicast DNS - https://tools.ietf.org/html/rfc6763 → DNS-Based Service Discovery -~ https://tools.ietf.org/html/rfc6781 → DNSSEC Operational Practices, Version 2 -Y https://tools.ietf.org/html/rfc6840 → Clarifications and Implementation Notes for DNS Security (DNSSEC) -Y https://tools.ietf.org/html/rfc6891 → Extension Mechanisms for DNS (EDNS(0)) -Y https://tools.ietf.org/html/rfc6944 → Applicability Statement: DNS Security (DNSSEC) DNSKEY Algorithm Implementation Status -Y https://tools.ietf.org/html/rfc6975 → Signaling Cryptographic Algorithm Understanding in DNS Security Extensions (DNSSEC) -Y https://tools.ietf.org/html/rfc7129 → Authenticated Denial of Existence in the DNS -Y https://tools.ietf.org/html/rfc7646 → Definition and Use of DNSSEC Negative Trust Anchors -~ https://tools.ietf.org/html/rfc7719 → DNS Terminology - -Also relevant: - - https://www.iab.org/documents/correspondence-reports-documents/2013-2/iab-statement-dotless-domains-considered-harmful/ diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c deleted file mode 100644 index 78d9d5733f..0000000000 --- a/src/resolve/dns-type.c +++ /dev/null @@ -1,323 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Zbigniew Jędrzejewski-Szmek - - 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 "dns-type.h" -#include "parse-util.h" -#include "string-util.h" - -typedef const struct { - uint16_t type; - const char *name; -} dns_type; - -static const struct dns_type_name * -lookup_dns_type (register const char *str, register unsigned int len); - -#include "dns_type-from-name.h" -#include "dns_type-to-name.h" - -int dns_type_from_string(const char *s) { - const struct dns_type_name *sc; - - assert(s); - - sc = lookup_dns_type(s, strlen(s)); - if (sc) - return sc->id; - - s = startswith_no_case(s, "TYPE"); - if (s) { - unsigned x; - - if (safe_atou(s, &x) >= 0 && - x <= UINT16_MAX) - return (int) x; - } - - return _DNS_TYPE_INVALID; -} - -bool dns_type_is_pseudo(uint16_t type) { - - /* Checks whether the specified type is a "pseudo-type". What - * a "pseudo-type" precisely is, is defined only very weakly, - * but apparently entails all RR types that are not actually - * stored as RRs on the server and should hence also not be - * cached. We use this list primarily to validate NSEC type - * bitfields, and to verify what to cache. */ - - return IN_SET(type, - 0, /* A Pseudo RR type, according to RFC 2931 */ - DNS_TYPE_ANY, - DNS_TYPE_AXFR, - DNS_TYPE_IXFR, - DNS_TYPE_OPT, - DNS_TYPE_TSIG, - DNS_TYPE_TKEY - ); -} - -bool dns_class_is_pseudo(uint16_t class) { - return class == DNS_TYPE_ANY; -} - -bool dns_type_is_valid_query(uint16_t type) { - - /* The types valid as questions in packets */ - - return !IN_SET(type, - 0, - DNS_TYPE_OPT, - DNS_TYPE_TSIG, - DNS_TYPE_TKEY, - - /* RRSIG are technically valid as questions, but we refuse doing explicit queries for them, as - * they aren't really payload, but signatures for payload, and cannot be validated on their - * own. After all they are the signatures, and have no signatures of their own validating - * them. */ - DNS_TYPE_RRSIG); -} - -bool dns_type_is_valid_rr(uint16_t type) { - - /* The types valid as RR in packets (but not necessarily - * stored on servers). */ - - return !IN_SET(type, - DNS_TYPE_ANY, - DNS_TYPE_AXFR, - DNS_TYPE_IXFR); -} - -bool dns_class_is_valid_rr(uint16_t class) { - return class != DNS_CLASS_ANY; -} - -bool dns_type_may_redirect(uint16_t type) { - /* The following record types should never be redirected using - * CNAME/DNAME RRs. See - * . */ - - if (dns_type_is_pseudo(type)) - return false; - - return !IN_SET(type, - DNS_TYPE_CNAME, - DNS_TYPE_DNAME, - DNS_TYPE_NSEC3, - DNS_TYPE_NSEC, - DNS_TYPE_RRSIG, - DNS_TYPE_NXT, - DNS_TYPE_SIG, - DNS_TYPE_KEY); -} - -bool dns_type_may_wildcard(uint16_t type) { - - /* The following records may not be expanded from wildcard RRsets */ - - if (dns_type_is_pseudo(type)) - return false; - - return !IN_SET(type, - DNS_TYPE_NSEC3, - DNS_TYPE_SOA, - - /* Prohibited by https://tools.ietf.org/html/rfc4592#section-4.4 */ - DNS_TYPE_DNAME); -} - -bool dns_type_apex_only(uint16_t type) { - - /* Returns true for all RR types that may only appear signed in a zone apex */ - - return IN_SET(type, - DNS_TYPE_SOA, - DNS_TYPE_NS, /* this one can appear elsewhere, too, but not signed */ - DNS_TYPE_DNSKEY, - DNS_TYPE_NSEC3PARAM); -} - -bool dns_type_is_dnssec(uint16_t type) { - return IN_SET(type, - DNS_TYPE_DS, - DNS_TYPE_DNSKEY, - DNS_TYPE_RRSIG, - DNS_TYPE_NSEC, - DNS_TYPE_NSEC3, - DNS_TYPE_NSEC3PARAM); -} - -bool dns_type_is_obsolete(uint16_t type) { - return IN_SET(type, - /* Obsoleted by RFC 973 */ - DNS_TYPE_MD, - DNS_TYPE_MF, - DNS_TYPE_MAILA, - - /* Kinda obsoleted by RFC 2505 */ - DNS_TYPE_MB, - DNS_TYPE_MG, - DNS_TYPE_MR, - DNS_TYPE_MINFO, - DNS_TYPE_MAILB, - - /* RFC1127 kinda obsoleted this by recommending against its use */ - DNS_TYPE_WKS, - - /* Declared historical by RFC 6563 */ - DNS_TYPE_A6, - - /* Obsoleted by DNSSEC-bis */ - DNS_TYPE_NXT, - - /* RFC 1035 removed support for concepts that needed this from RFC 883 */ - DNS_TYPE_NULL); -} - -bool dns_type_needs_authentication(uint16_t type) { - - /* Returns true for all (non-obsolete) RR types where records are not useful if they aren't - * authenticated. I.e. everything that contains crypto keys. */ - - return IN_SET(type, - DNS_TYPE_CERT, - DNS_TYPE_SSHFP, - DNS_TYPE_IPSECKEY, - DNS_TYPE_DS, - DNS_TYPE_DNSKEY, - DNS_TYPE_TLSA, - DNS_TYPE_CDNSKEY, - DNS_TYPE_OPENPGPKEY, - DNS_TYPE_CAA); -} - -int dns_type_to_af(uint16_t t) { - switch (t) { - - case DNS_TYPE_A: - return AF_INET; - - case DNS_TYPE_AAAA: - return AF_INET6; - - case DNS_TYPE_ANY: - return AF_UNSPEC; - - default: - return -EINVAL; - } -} - -const char *dns_class_to_string(uint16_t class) { - - switch (class) { - - case DNS_CLASS_IN: - return "IN"; - - case DNS_CLASS_ANY: - return "ANY"; - } - - return NULL; -} - -int dns_class_from_string(const char *s) { - - if (!s) - return _DNS_CLASS_INVALID; - - if (strcaseeq(s, "IN")) - return DNS_CLASS_IN; - else if (strcaseeq(s, "ANY")) - return DNS_CLASS_ANY; - - return _DNS_CLASS_INVALID; -} - -const char* tlsa_cert_usage_to_string(uint8_t cert_usage) { - - switch (cert_usage) { - - case 0: - return "CA constraint"; - - case 1: - return "Service certificate constraint"; - - case 2: - return "Trust anchor assertion"; - - case 3: - return "Domain-issued certificate"; - - case 4 ... 254: - return "Unassigned"; - - case 255: - return "Private use"; - } - - return NULL; /* clang cannot count that we covered everything */ -} - -const char* tlsa_selector_to_string(uint8_t selector) { - switch (selector) { - - case 0: - return "Full Certificate"; - - case 1: - return "SubjectPublicKeyInfo"; - - case 2 ... 254: - return "Unassigned"; - - case 255: - return "Private use"; - } - - return NULL; -} - -const char* tlsa_matching_type_to_string(uint8_t selector) { - - switch (selector) { - - case 0: - return "No hash used"; - - case 1: - return "SHA-256"; - - case 2: - return "SHA-512"; - - case 3 ... 254: - return "Unassigned"; - - case 255: - return "Private use"; - } - - return NULL; -} diff --git a/src/resolve/dns-type.h b/src/resolve/dns-type.h deleted file mode 100644 index 7b79d29d7e..0000000000 --- a/src/resolve/dns-type.h +++ /dev/null @@ -1,161 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Zbigniew Jędrzejewski-Szmek - - 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 "macro.h" - -/* DNS record types, taken from - * http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml. - */ -enum { - /* Normal records */ - DNS_TYPE_A = 0x01, - DNS_TYPE_NS, - DNS_TYPE_MD, - DNS_TYPE_MF, - DNS_TYPE_CNAME, - DNS_TYPE_SOA, - DNS_TYPE_MB, - DNS_TYPE_MG, - DNS_TYPE_MR, - DNS_TYPE_NULL, - DNS_TYPE_WKS, - DNS_TYPE_PTR, - DNS_TYPE_HINFO, - DNS_TYPE_MINFO, - DNS_TYPE_MX, - DNS_TYPE_TXT, - DNS_TYPE_RP, - DNS_TYPE_AFSDB, - DNS_TYPE_X25, - DNS_TYPE_ISDN, - DNS_TYPE_RT, - DNS_TYPE_NSAP, - DNS_TYPE_NSAP_PTR, - DNS_TYPE_SIG, - DNS_TYPE_KEY, - DNS_TYPE_PX, - DNS_TYPE_GPOS, - DNS_TYPE_AAAA, - DNS_TYPE_LOC, - DNS_TYPE_NXT, - DNS_TYPE_EID, - DNS_TYPE_NIMLOC, - DNS_TYPE_SRV, - DNS_TYPE_ATMA, - DNS_TYPE_NAPTR, - DNS_TYPE_KX, - DNS_TYPE_CERT, - DNS_TYPE_A6, - DNS_TYPE_DNAME, - DNS_TYPE_SINK, - DNS_TYPE_OPT, /* EDNS0 option */ - DNS_TYPE_APL, - DNS_TYPE_DS, - DNS_TYPE_SSHFP, - DNS_TYPE_IPSECKEY, - DNS_TYPE_RRSIG, - DNS_TYPE_NSEC, - DNS_TYPE_DNSKEY, - DNS_TYPE_DHCID, - DNS_TYPE_NSEC3, - DNS_TYPE_NSEC3PARAM, - DNS_TYPE_TLSA, - - DNS_TYPE_HIP = 0x37, - DNS_TYPE_NINFO, - DNS_TYPE_RKEY, - DNS_TYPE_TALINK, - DNS_TYPE_CDS, - DNS_TYPE_CDNSKEY, - DNS_TYPE_OPENPGPKEY, - - DNS_TYPE_SPF = 0x63, - DNS_TYPE_NID, - DNS_TYPE_L32, - DNS_TYPE_L64, - DNS_TYPE_LP, - DNS_TYPE_EUI48, - DNS_TYPE_EUI64, - - DNS_TYPE_TKEY = 0xF9, - DNS_TYPE_TSIG, - DNS_TYPE_IXFR, - DNS_TYPE_AXFR, - DNS_TYPE_MAILB, - DNS_TYPE_MAILA, - DNS_TYPE_ANY, - DNS_TYPE_URI, - DNS_TYPE_CAA, - DNS_TYPE_TA = 0x8000, - DNS_TYPE_DLV, - - _DNS_TYPE_MAX, - _DNS_TYPE_INVALID = -1 -}; - -assert_cc(DNS_TYPE_SSHFP == 44); -assert_cc(DNS_TYPE_TLSA == 52); -assert_cc(DNS_TYPE_ANY == 255); - -/* DNS record classes, see RFC 1035 */ -enum { - DNS_CLASS_IN = 0x01, - DNS_CLASS_ANY = 0xFF, - - _DNS_CLASS_MAX, - _DNS_CLASS_INVALID = -1 -}; - -#define _DNS_CLASS_STRING_MAX (sizeof "CLASS" + DECIMAL_STR_MAX(uint16_t)) -#define _DNS_TYPE_STRING_MAX (sizeof "CLASS" + DECIMAL_STR_MAX(uint16_t)) - -bool dns_type_is_pseudo(uint16_t type); -bool dns_type_is_valid_query(uint16_t type); -bool dns_type_is_valid_rr(uint16_t type); -bool dns_type_may_redirect(uint16_t type); -bool dns_type_is_dnssec(uint16_t type); -bool dns_type_is_obsolete(uint16_t type); -bool dns_type_may_wildcard(uint16_t type); -bool dns_type_apex_only(uint16_t type); -bool dns_type_needs_authentication(uint16_t type); -int dns_type_to_af(uint16_t type); - -bool dns_class_is_pseudo(uint16_t class); -bool dns_class_is_valid_rr(uint16_t class); - -/* TYPE?? follows http://tools.ietf.org/html/rfc3597#section-5 */ -const char *dns_type_to_string(int type); -int dns_type_from_string(const char *s); - -const char *dns_class_to_string(uint16_t class); -int dns_class_from_string(const char *name); - -/* https://tools.ietf.org/html/draft-ietf-dane-protocol-23#section-7.2 */ -const char *tlsa_cert_usage_to_string(uint8_t cert_usage); - -/* https://tools.ietf.org/html/draft-ietf-dane-protocol-23#section-7.3 */ -const char *tlsa_selector_to_string(uint8_t selector); - -/* https://tools.ietf.org/html/draft-ietf-dane-protocol-23#section-7.4 */ -const char *tlsa_matching_type_to_string(uint8_t selector); - -/* https://tools.ietf.org/html/rfc6844#section-5.1 */ -#define CAA_FLAG_CRITICAL (1u << 7) diff --git a/src/resolve/org.freedesktop.resolve1.conf b/src/resolve/org.freedesktop.resolve1.conf deleted file mode 100644 index 25b09774e5..0000000000 --- a/src/resolve/org.freedesktop.resolve1.conf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/resolve/org.freedesktop.resolve1.service b/src/resolve/org.freedesktop.resolve1.service deleted file mode 100644 index 7ac5c323f0..0000000000 --- a/src/resolve/org.freedesktop.resolve1.service +++ /dev/null @@ -1,12 +0,0 @@ -# This file is part of systemd. -# -# 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. - -[D-BUS Service] -Name=org.freedesktop.resolve1 -Exec=/bin/false -User=root -SystemdService=dbus-org.freedesktop.resolve1.service diff --git a/src/resolve/resolve-tool.c b/src/resolve/resolve-tool.c deleted file mode 100644 index 14ee01c49d..0000000000 --- a/src/resolve/resolve-tool.c +++ /dev/null @@ -1,1484 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Zbigniew Jędrzejewski-Szmek - - 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 - -#include "sd-bus.h" - -#include "af-list.h" -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "escape.h" -#include "in-addr-util.h" -#include "gcrypt-util.h" -#include "parse-util.h" -#include "resolved-def.h" -#include "resolved-dns-packet.h" -#include "terminal-util.h" - -#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) - -static int arg_family = AF_UNSPEC; -static int arg_ifindex = 0; -static uint16_t arg_type = 0; -static uint16_t arg_class = 0; -static bool arg_legend = true; -static uint64_t arg_flags = 0; - -typedef enum ServiceFamily { - SERVICE_FAMILY_TCP, - SERVICE_FAMILY_UDP, - SERVICE_FAMILY_SCTP, - _SERVICE_FAMILY_INVALID = -1, -} ServiceFamily; -static ServiceFamily arg_service_family = SERVICE_FAMILY_TCP; - -typedef enum RawType { - RAW_NONE, - RAW_PAYLOAD, - RAW_PACKET, -} RawType; -static RawType arg_raw = RAW_NONE; - -static enum { - MODE_RESOLVE_HOST, - MODE_RESOLVE_RECORD, - MODE_RESOLVE_SERVICE, - MODE_RESOLVE_OPENPGP, - MODE_RESOLVE_TLSA, - MODE_STATISTICS, - MODE_RESET_STATISTICS, -} arg_mode = MODE_RESOLVE_HOST; - -static ServiceFamily service_family_from_string(const char *s) { - if (s == NULL || streq(s, "tcp")) - return SERVICE_FAMILY_TCP; - if (streq(s, "udp")) - return SERVICE_FAMILY_UDP; - if (streq(s, "sctp")) - return SERVICE_FAMILY_SCTP; - return _SERVICE_FAMILY_INVALID; -} - -static const char* service_family_to_string(ServiceFamily service) { - switch(service) { - case SERVICE_FAMILY_TCP: - return "_tcp"; - case SERVICE_FAMILY_UDP: - return "_udp"; - case SERVICE_FAMILY_SCTP: - return "_sctp"; - default: - assert_not_reached("invalid service"); - } -} - -static void print_source(uint64_t flags, usec_t rtt) { - char rtt_str[FORMAT_TIMESTAMP_MAX]; - - if (!arg_legend) - return; - - if (flags == 0) - return; - - fputs("\n-- Information acquired via", stdout); - - if (flags != 0) - printf(" protocol%s%s%s%s%s", - flags & SD_RESOLVED_DNS ? " DNS" :"", - flags & SD_RESOLVED_LLMNR_IPV4 ? " LLMNR/IPv4" : "", - flags & SD_RESOLVED_LLMNR_IPV6 ? " LLMNR/IPv6" : "", - flags & SD_RESOLVED_MDNS_IPV4 ? "mDNS/IPv4" : "", - flags & SD_RESOLVED_MDNS_IPV6 ? "mDNS/IPv6" : ""); - - assert_se(format_timespan(rtt_str, sizeof(rtt_str), rtt, 100)); - - printf(" in %s", rtt_str); - - fputc('.', stdout); - fputc('\n', stdout); - - printf("-- Data is authenticated: %s\n", yes_no(flags & SD_RESOLVED_AUTHENTICATED)); -} - -static int resolve_host(sd_bus *bus, const char *name) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *canonical = NULL; - char ifname[IF_NAMESIZE] = ""; - unsigned c = 0; - int r; - uint64_t flags; - usec_t ts; - - assert(name); - - if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) - return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); - - log_debug("Resolving %s (family %s, interface %s).", name, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveHostname"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "isit", arg_ifindex, name, arg_family, arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) - return log_error_errno(r, "%s: resolve call failed: %s", name, bus_error_message(&error, r)); - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { - _cleanup_free_ char *pretty = NULL; - int ifindex, family; - const void *a; - size_t sz; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "ii", &ifindex, &family); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (!IN_SET(family, AF_INET, AF_INET6)) { - log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown"); - continue; - } - - if (sz != FAMILY_ADDRESS_SIZE(family)) { - log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown"); - return -EINVAL; - } - - ifname[0] = 0; - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - r = in_addr_to_string(family, a, &pretty); - if (r < 0) - return log_error_errno(r, "Failed to print address for %s: %m", name); - - printf("%*s%s %s%s%s\n", - (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", - pretty, - isempty(ifname) ? "" : "%", ifname); - - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "st", &canonical, &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (!streq(name, canonical)) - printf("%*s%s (%s)\n", - (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", - canonical); - - if (c == 0) { - log_error("%s: no addresses found", name); - return -ESRCH; - } - - print_source(flags, ts); - - return 0; -} - -static int resolve_address(sd_bus *bus, int family, const union in_addr_union *address, int ifindex) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *pretty = NULL; - char ifname[IF_NAMESIZE] = ""; - uint64_t flags; - unsigned c = 0; - usec_t ts; - int r; - - assert(bus); - assert(IN_SET(family, AF_INET, AF_INET6)); - assert(address); - - if (ifindex <= 0) - ifindex = arg_ifindex; - - r = in_addr_to_string(family, address, &pretty); - if (r < 0) - return log_oom(); - - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - log_debug("Resolving %s%s%s.", pretty, isempty(ifname) ? "" : "%", ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveAddress"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "ii", ifindex, family); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_array(req, 'y', address, FAMILY_ADDRESS_SIZE(family)); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "t", arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) { - log_error("%s: resolve call failed: %s", pretty, bus_error_message(&error, r)); - return r; - } - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(is)"); - if (r < 0) - return bus_log_create_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "is")) > 0) { - const char *n; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "is", &ifindex, &n); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return r; - - ifname[0] = 0; - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - printf("%*s%*s%*s%s %s\n", - (int) strlen(pretty), c == 0 ? pretty : "", - isempty(ifname) ? 0 : 1, c > 0 || isempty(ifname) ? "" : "%", - (int) strlen(ifname), c == 0 ? ifname : "", - c == 0 ? ":" : " ", - n); - - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "t", &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (c == 0) { - log_error("%s: no names found", pretty); - return -ESRCH; - } - - print_source(flags, ts); - - return 0; -} - -static int parse_address(const char *s, int *family, union in_addr_union *address, int *ifindex) { - const char *percent, *a; - int ifi = 0; - int r; - - percent = strchr(s, '%'); - if (percent) { - if (parse_ifindex(percent+1, &ifi) < 0) { - ifi = if_nametoindex(percent+1); - if (ifi <= 0) - return -EINVAL; - } - - a = strndupa(s, percent - s); - } else - a = s; - - r = in_addr_from_string_auto(a, family, address); - if (r < 0) - return r; - - *ifindex = ifi; - return 0; -} - -static int output_rr_packet(const void *d, size_t l, int ifindex) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - int r; - char ifname[IF_NAMESIZE] = ""; - - r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0); - if (r < 0) - return log_oom(); - - p->refuse_compression = true; - - r = dns_packet_append_blob(p, d, l, NULL); - if (r < 0) - return log_oom(); - - r = dns_packet_read_rr(p, &rr, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to parse RR: %m"); - - if (arg_raw == RAW_PAYLOAD) { - void *data; - ssize_t k; - - k = dns_resource_record_payload(rr, &data); - if (k < 0) - return log_error_errno(k, "Cannot dump RR: %m"); - fwrite(data, 1, k, stdout); - } else { - const char *s; - - s = dns_resource_record_to_string(rr); - if (!s) - return log_oom(); - - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - printf("%s%s%s\n", s, isempty(ifname) ? "" : " # interface ", ifname); - } - - return 0; -} - -static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_t type) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char ifname[IF_NAMESIZE] = ""; - unsigned n = 0; - uint64_t flags; - int r; - usec_t ts; - bool needs_authentication = false; - - assert(name); - - if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) - return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); - - log_debug("Resolving %s %s %s (interface %s).", name, dns_class_to_string(class), dns_type_to_string(type), isempty(ifname) ? "*" : ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveRecord"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, class, type, arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) { - log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r)); - return r; - } - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(iqqay)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "iqqay")) > 0) { - uint16_t c, t; - int ifindex; - const void *d; - size_t l; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "iqq", &ifindex, &c, &t); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &d, &l); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (arg_raw == RAW_PACKET) { - uint64_t u64 = htole64(l); - - fwrite(&u64, sizeof(u64), 1, stdout); - fwrite(d, 1, l, stdout); - } else { - r = output_rr_packet(d, l, ifindex); - if (r < 0) - return r; - } - - if (dns_type_needs_authentication(t)) - needs_authentication = true; - - n++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "t", &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (n == 0) { - log_error("%s: no records found", name); - return -ESRCH; - } - - print_source(flags, ts); - - if ((flags & SD_RESOLVED_AUTHENTICATED) == 0 && needs_authentication) { - fflush(stdout); - - fprintf(stderr, "\n%s" - "WARNING: The resources shown contain cryptographic key data which could not be\n" - " authenticated. It is not suitable to authenticate any communication.\n" - " This is usually indication that DNSSEC authentication was not enabled\n" - " or is not available for the selected protocol or DNS servers.%s\n", - ansi_highlight_red(), - ansi_normal()); - } - - return 0; -} - -static int resolve_rfc4501(sd_bus *bus, const char *name) { - uint16_t type = 0, class = 0; - const char *p, *q, *n; - int r; - - assert(bus); - assert(name); - assert(startswith(name, "dns:")); - - /* Parse RFC 4501 dns: URIs */ - - p = name + 4; - - if (p[0] == '/') { - const char *e; - - if (p[1] != '/') - goto invalid; - - e = strchr(p + 2, '/'); - if (!e) - goto invalid; - - if (e != p + 2) - log_warning("DNS authority specification not supported; ignoring specified authority."); - - p = e + 1; - } - - q = strchr(p, '?'); - if (q) { - n = strndupa(p, q - p); - q++; - - for (;;) { - const char *f; - - f = startswith_no_case(q, "class="); - if (f) { - _cleanup_free_ char *t = NULL; - const char *e; - - if (class != 0) { - log_error("DNS class specified twice."); - return -EINVAL; - } - - e = strchrnul(f, ';'); - t = strndup(f, e - f); - if (!t) - return log_oom(); - - r = dns_class_from_string(t); - if (r < 0) { - log_error("Unknown DNS class %s.", t); - return -EINVAL; - } - - class = r; - - if (*e == ';') { - q = e + 1; - continue; - } - - break; - } - - f = startswith_no_case(q, "type="); - if (f) { - _cleanup_free_ char *t = NULL; - const char *e; - - if (type != 0) { - log_error("DNS type specified twice."); - return -EINVAL; - } - - e = strchrnul(f, ';'); - t = strndup(f, e - f); - if (!t) - return log_oom(); - - r = dns_type_from_string(t); - if (r < 0) { - log_error("Unknown DNS type %s.", t); - return -EINVAL; - } - - type = r; - - if (*e == ';') { - q = e + 1; - continue; - } - - break; - } - - goto invalid; - } - } else - n = p; - - if (class == 0) - class = arg_class ?: DNS_CLASS_IN; - if (type == 0) - type = arg_type ?: DNS_TYPE_A; - - return resolve_record(bus, n, class, type); - -invalid: - log_error("Invalid DNS URI: %s", name); - return -EINVAL; -} - -static int resolve_service(sd_bus *bus, const char *name, const char *type, const char *domain) { - const char *canonical_name, *canonical_type, *canonical_domain; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char ifname[IF_NAMESIZE] = ""; - size_t indent, sz; - uint64_t flags; - const char *p; - unsigned c; - usec_t ts; - int r; - - assert(bus); - assert(domain); - - if (isempty(name)) - name = NULL; - if (isempty(type)) - type = NULL; - - if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) - return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); - - if (name) - log_debug("Resolving service \"%s\" of type %s in %s (family %s, interface %s).", name, type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - else if (type) - log_debug("Resolving service type %s of %s (family %s, interface %s).", type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - else - log_debug("Resolving service type %s (family %s, interface %s).", domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveService"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "isssit", arg_ifindex, name, type, domain, arg_family, arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) - return log_error_errno(r, "Resolve call failed: %s", bus_error_message(&error, r)); - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(qqqsa(iiay)s)"); - if (r < 0) - return bus_log_parse_error(r); - - indent = - (name ? strlen(name) + 1 : 0) + - (type ? strlen(type) + 1 : 0) + - strlen(domain) + 2; - - c = 0; - while ((r = sd_bus_message_enter_container(reply, 'r', "qqqsa(iiay)s")) > 0) { - uint16_t priority, weight, port; - const char *hostname, *canonical; - - r = sd_bus_message_read(reply, "qqqs", &priority, &weight, &port, &hostname); - if (r < 0) - return bus_log_parse_error(r); - - if (name) - printf("%*s%s", (int) strlen(name), c == 0 ? name : "", c == 0 ? "/" : " "); - if (type) - printf("%*s%s", (int) strlen(type), c == 0 ? type : "", c == 0 ? "/" : " "); - - printf("%*s%s %s:%u [priority=%u, weight=%u]\n", - (int) strlen(domain), c == 0 ? domain : "", - c == 0 ? ":" : " ", - hostname, port, - priority, weight); - - r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { - _cleanup_free_ char *pretty = NULL; - int ifindex, family; - const void *a; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "ii", &ifindex, &family); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (!IN_SET(family, AF_INET, AF_INET6)) { - log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown"); - continue; - } - - if (sz != FAMILY_ADDRESS_SIZE(family)) { - log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown"); - return -EINVAL; - } - - ifname[0] = 0; - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - r = in_addr_to_string(family, a, &pretty); - if (r < 0) - return log_error_errno(r, "Failed to print address for %s: %m", name); - - printf("%*s%s%s%s\n", (int) indent, "", pretty, isempty(ifname) ? "" : "%s", ifname); - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "s", &canonical); - if (r < 0) - return bus_log_parse_error(r); - - if (!streq(hostname, canonical)) - printf("%*s(%s)\n", (int) indent, "", canonical); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_enter_container(reply, 'a', "ay"); - if (r < 0) - return bus_log_parse_error(r); - - c = 0; - while ((r = sd_bus_message_read_array(reply, 'y', (const void**) &p, &sz)) > 0) { - _cleanup_free_ char *escaped = NULL; - - escaped = cescape_length(p, sz); - if (!escaped) - return log_oom(); - - printf("%*s%s\n", (int) indent, "", escaped); - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "ssst", &canonical_name, &canonical_type, &canonical_domain, &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (isempty(canonical_name)) - canonical_name = NULL; - if (isempty(canonical_type)) - canonical_type = NULL; - - if (!streq_ptr(name, canonical_name) || - !streq_ptr(type, canonical_type) || - !streq_ptr(domain, canonical_domain)) { - - printf("%*s(", (int) indent, ""); - - if (canonical_name) - printf("%s/", canonical_name); - if (canonical_type) - printf("%s/", canonical_type); - - printf("%s)\n", canonical_domain); - } - - print_source(flags, ts); - - return 0; -} - -static int resolve_openpgp(sd_bus *bus, const char *address) { - const char *domain, *full; - int r; - _cleanup_free_ char *hashed = NULL; - - assert(bus); - assert(address); - - domain = strrchr(address, '@'); - if (!domain) { - log_error("Address does not contain '@': \"%s\"", address); - return -EINVAL; - } else if (domain == address || domain[1] == '\0') { - log_error("Address starts or ends with '@': \"%s\"", address); - return -EINVAL; - } - domain++; - - r = string_hashsum_sha224(address, domain - 1 - address, &hashed); - if (r < 0) - return log_error_errno(r, "Hashing failed: %m"); - - full = strjoina(hashed, "._openpgpkey.", domain); - log_debug("Looking up \"%s\".", full); - - return resolve_record(bus, full, - arg_class ?: DNS_CLASS_IN, - arg_type ?: DNS_TYPE_OPENPGPKEY); -} - -static int resolve_tlsa(sd_bus *bus, const char *address) { - const char *port; - uint16_t port_num = 443; - _cleanup_free_ char *full = NULL; - int r; - - assert(bus); - assert(address); - - port = strrchr(address, ':'); - if (port) { - r = safe_atou16(port + 1, &port_num); - if (r < 0 || port_num == 0) - return log_error_errno(r, "Invalid port \"%s\".", port + 1); - - address = strndupa(address, port - address); - } - - r = asprintf(&full, "_%u.%s.%s", - port_num, - service_family_to_string(arg_service_family), - address); - if (r < 0) - return log_oom(); - - log_debug("Looking up \"%s\".", full); - - return resolve_record(bus, full, - arg_class ?: DNS_CLASS_IN, - arg_type ?: DNS_TYPE_TLSA); -} - -static int show_statistics(sd_bus *bus) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - uint64_t n_current_transactions, n_total_transactions, - cache_size, n_cache_hit, n_cache_miss, - n_dnssec_secure, n_dnssec_insecure, n_dnssec_bogus, n_dnssec_indeterminate; - int r, dnssec_supported; - - assert(bus); - - r = sd_bus_get_property_trivial(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "DNSSECSupported", - &error, - 'b', - &dnssec_supported); - if (r < 0) - return log_error_errno(r, "Failed to get DNSSEC supported state: %s", bus_error_message(&error, r)); - - printf("DNSSEC supported by current servers: %s%s%s\n\n", - ansi_highlight(), - yes_no(dnssec_supported), - ansi_normal()); - - r = sd_bus_get_property(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "TransactionStatistics", - &error, - &reply, - "(tt)"); - if (r < 0) - return log_error_errno(r, "Failed to get transaction statistics: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "(tt)", - &n_current_transactions, - &n_total_transactions); - if (r < 0) - return bus_log_parse_error(r); - - printf("%sTransactions%s\n" - "Current Transactions: %" PRIu64 "\n" - " Total Transactions: %" PRIu64 "\n", - ansi_highlight(), - ansi_normal(), - n_current_transactions, - n_total_transactions); - - reply = sd_bus_message_unref(reply); - - r = sd_bus_get_property(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "CacheStatistics", - &error, - &reply, - "(ttt)"); - if (r < 0) - return log_error_errno(r, "Failed to get cache statistics: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "(ttt)", - &cache_size, - &n_cache_hit, - &n_cache_miss); - if (r < 0) - return bus_log_parse_error(r); - - printf("\n%sCache%s\n" - " Current Cache Size: %" PRIu64 "\n" - " Cache Hits: %" PRIu64 "\n" - " Cache Misses: %" PRIu64 "\n", - ansi_highlight(), - ansi_normal(), - cache_size, - n_cache_hit, - n_cache_miss); - - reply = sd_bus_message_unref(reply); - - r = sd_bus_get_property(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "DNSSECStatistics", - &error, - &reply, - "(tttt)"); - if (r < 0) - return log_error_errno(r, "Failed to get DNSSEC statistics: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "(tttt)", - &n_dnssec_secure, - &n_dnssec_insecure, - &n_dnssec_bogus, - &n_dnssec_indeterminate); - if (r < 0) - return bus_log_parse_error(r); - - printf("\n%sDNSSEC Verdicts%s\n" - " Secure: %" PRIu64 "\n" - " Insecure: %" PRIu64 "\n" - " Bogus: %" PRIu64 "\n" - " Indeterminate: %" PRIu64 "\n", - ansi_highlight(), - ansi_normal(), - n_dnssec_secure, - n_dnssec_insecure, - n_dnssec_bogus, - n_dnssec_indeterminate); - - return 0; -} - -static int reset_statistics(sd_bus *bus) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - r = sd_bus_call_method(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResetStatistics", - &error, - NULL, - NULL); - if (r < 0) - return log_error_errno(r, "Failed to reset statistics: %s", bus_error_message(&error, r)); - - return 0; -} - -static void help_protocol_types(void) { - if (arg_legend) - puts("Known protocol types:"); - puts("dns\nllmnr\nllmnr-ipv4\nllmnr-ipv6"); -} - -static void help_dns_types(void) { - int i; - const char *t; - - if (arg_legend) - puts("Known DNS RR types:"); - for (i = 0; i < _DNS_TYPE_MAX; i++) { - t = dns_type_to_string(i); - if (t) - puts(t); - } -} - -static void help_dns_classes(void) { - int i; - const char *t; - - if (arg_legend) - puts("Known DNS RR classes:"); - for (i = 0; i < _DNS_CLASS_MAX; i++) { - t = dns_class_to_string(i); - if (t) - puts(t); - } -} - -static void help(void) { - printf("%1$s [OPTIONS...] HOSTNAME|ADDRESS...\n" - "%1$s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n" - "%1$s [OPTIONS...] --openpgp EMAIL@DOMAIN...\n" - "%1$s [OPTIONS...] --statistics\n" - "%1$s [OPTIONS...] --reset-statistics\n" - "\n" - "Resolve domain names, IPv4 and IPv6 addresses, DNS resource records, and services.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -4 Resolve IPv4 addresses\n" - " -6 Resolve IPv6 addresses\n" - " -i --interface=INTERFACE Look on interface\n" - " -p --protocol=PROTO|help Look via protocol\n" - " -t --type=TYPE|help Query RR with DNS type\n" - " -c --class=CLASS|help Query RR with DNS class\n" - " --service Resolve service (SRV)\n" - " --service-address=BOOL Resolve address for services (default: yes)\n" - " --service-txt=BOOL Resolve TXT records for services (default: yes)\n" - " --openpgp Query OpenPGP public key\n" - " --tlsa Query TLS public key\n" - " --cname=BOOL Follow CNAME redirects (default: yes)\n" - " --search=BOOL Use search domains for single-label names\n" - " (default: yes)\n" - " --raw[=payload|packet] Dump the answer as binary data\n" - " --legend=BOOL Print headers and additional info (default: yes)\n" - " --statistics Show resolver statistics\n" - " --reset-statistics Reset resolver statistics\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_LEGEND, - ARG_SERVICE, - ARG_CNAME, - ARG_SERVICE_ADDRESS, - ARG_SERVICE_TXT, - ARG_OPENPGP, - ARG_TLSA, - ARG_RAW, - ARG_SEARCH, - ARG_STATISTICS, - ARG_RESET_STATISTICS, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "type", required_argument, NULL, 't' }, - { "class", required_argument, NULL, 'c' }, - { "legend", required_argument, NULL, ARG_LEGEND }, - { "interface", required_argument, NULL, 'i' }, - { "protocol", required_argument, NULL, 'p' }, - { "cname", required_argument, NULL, ARG_CNAME }, - { "service", no_argument, NULL, ARG_SERVICE }, - { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS }, - { "service-txt", required_argument, NULL, ARG_SERVICE_TXT }, - { "openpgp", no_argument, NULL, ARG_OPENPGP }, - { "tlsa", optional_argument, NULL, ARG_TLSA }, - { "raw", optional_argument, NULL, ARG_RAW }, - { "search", required_argument, NULL, ARG_SEARCH }, - { "statistics", no_argument, NULL, ARG_STATISTICS, }, - { "reset-statistics", no_argument, NULL, ARG_RESET_STATISTICS }, - {} - }; - - int c, r; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h46i:t:c:p:", options, NULL)) >= 0) - switch(c) { - - case 'h': - help(); - return 0; /* done */; - - case ARG_VERSION: - return version(); - - case '4': - arg_family = AF_INET; - break; - - case '6': - arg_family = AF_INET6; - break; - - case 'i': { - int ifi; - - if (parse_ifindex(optarg, &ifi) >= 0) - arg_ifindex = ifi; - else { - ifi = if_nametoindex(optarg); - if (ifi <= 0) - return log_error_errno(errno, "Unknown interface %s: %m", optarg); - - arg_ifindex = ifi; - } - - break; - } - - case 't': - if (streq(optarg, "help")) { - help_dns_types(); - return 0; - } - - r = dns_type_from_string(optarg); - if (r < 0) { - log_error("Failed to parse RR record type %s", optarg); - return r; - } - arg_type = (uint16_t) r; - assert((int) arg_type == r); - - arg_mode = MODE_RESOLVE_RECORD; - break; - - case 'c': - if (streq(optarg, "help")) { - help_dns_classes(); - return 0; - } - - r = dns_class_from_string(optarg); - if (r < 0) { - log_error("Failed to parse RR record class %s", optarg); - return r; - } - arg_class = (uint16_t) r; - assert((int) arg_class == r); - - break; - - case ARG_LEGEND: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --legend= argument"); - - arg_legend = r; - break; - - case 'p': - if (streq(optarg, "help")) { - help_protocol_types(); - return 0; - } else if (streq(optarg, "dns")) - arg_flags |= SD_RESOLVED_DNS; - else if (streq(optarg, "llmnr")) - arg_flags |= SD_RESOLVED_LLMNR; - else if (streq(optarg, "llmnr-ipv4")) - arg_flags |= SD_RESOLVED_LLMNR_IPV4; - else if (streq(optarg, "llmnr-ipv6")) - arg_flags |= SD_RESOLVED_LLMNR_IPV6; - else { - log_error("Unknown protocol specifier: %s", optarg); - return -EINVAL; - } - - break; - - case ARG_SERVICE: - arg_mode = MODE_RESOLVE_SERVICE; - break; - - case ARG_OPENPGP: - arg_mode = MODE_RESOLVE_OPENPGP; - break; - - case ARG_TLSA: - arg_mode = MODE_RESOLVE_TLSA; - arg_service_family = service_family_from_string(optarg); - if (arg_service_family < 0) { - log_error("Unknown service family \"%s\".", optarg); - return -EINVAL; - } - break; - - case ARG_RAW: - if (on_tty()) { - log_error("Refusing to write binary data to tty."); - return -ENOTTY; - } - - if (optarg == NULL || streq(optarg, "payload")) - arg_raw = RAW_PAYLOAD; - else if (streq(optarg, "packet")) - arg_raw = RAW_PACKET; - else { - log_error("Unknown --raw specifier \"%s\".", optarg); - return -EINVAL; - } - - arg_legend = false; - break; - - case ARG_CNAME: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --cname= argument."); - SET_FLAG(arg_flags, SD_RESOLVED_NO_CNAME, r == 0); - break; - - case ARG_SERVICE_ADDRESS: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --service-address= argument."); - SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0); - break; - - case ARG_SERVICE_TXT: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --service-txt= argument."); - SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); - break; - - case ARG_SEARCH: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --search argument."); - SET_FLAG(arg_flags, SD_RESOLVED_NO_SEARCH, r == 0); - break; - - case ARG_STATISTICS: - arg_mode = MODE_STATISTICS; - break; - - case ARG_RESET_STATISTICS: - arg_mode = MODE_RESET_STATISTICS; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (arg_type == 0 && arg_class != 0) { - log_error("--class= may only be used in conjunction with --type=."); - return -EINVAL; - } - - if (arg_type != 0 && arg_mode == MODE_RESOLVE_SERVICE) { - log_error("--service and --type= may not be combined."); - return -EINVAL; - } - - if (arg_type != 0 && arg_class == 0) - arg_class = DNS_CLASS_IN; - - if (arg_class != 0 && arg_type == 0) - arg_type = DNS_TYPE_A; - - return 1 /* work to do */; -} - -int main(int argc, char **argv) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = sd_bus_open_system(&bus); - if (r < 0) { - log_error_errno(r, "sd_bus_open_system: %m"); - goto finish; - } - - switch (arg_mode) { - - case MODE_RESOLVE_HOST: - if (optind >= argc) { - log_error("No arguments passed."); - r = -EINVAL; - goto finish; - } - - while (argv[optind]) { - int family, ifindex, k; - union in_addr_union a; - - if (startswith(argv[optind], "dns:")) - k = resolve_rfc4501(bus, argv[optind]); - else { - k = parse_address(argv[optind], &family, &a, &ifindex); - if (k >= 0) - k = resolve_address(bus, family, &a, ifindex); - else - k = resolve_host(bus, argv[optind]); - } - - if (r == 0) - r = k; - - optind++; - } - break; - - case MODE_RESOLVE_RECORD: - if (optind >= argc) { - log_error("No arguments passed."); - r = -EINVAL; - goto finish; - } - - while (argv[optind]) { - int k; - - k = resolve_record(bus, argv[optind], arg_class, arg_type); - if (r == 0) - r = k; - - optind++; - } - break; - - case MODE_RESOLVE_SERVICE: - if (argc < optind + 1) { - log_error("Domain specification required."); - r = -EINVAL; - goto finish; - - } else if (argc == optind + 1) - r = resolve_service(bus, NULL, NULL, argv[optind]); - else if (argc == optind + 2) - r = resolve_service(bus, NULL, argv[optind], argv[optind+1]); - else if (argc == optind + 3) - r = resolve_service(bus, argv[optind], argv[optind+1], argv[optind+2]); - else { - log_error("Too many arguments."); - r = -EINVAL; - goto finish; - } - - break; - - case MODE_RESOLVE_OPENPGP: - if (argc < optind + 1) { - log_error("E-mail address required."); - r = -EINVAL; - goto finish; - - } - - r = 0; - while (optind < argc) { - int k; - - k = resolve_openpgp(bus, argv[optind++]); - if (k < 0) - r = k; - } - break; - - case MODE_RESOLVE_TLSA: - if (argc < optind + 1) { - log_error("Domain name required."); - r = -EINVAL; - goto finish; - - } - - r = 0; - while (optind < argc) { - int k; - - k = resolve_tlsa(bus, argv[optind++]); - if (k < 0) - r = k; - } - break; - - case MODE_STATISTICS: - if (argc > optind) { - log_error("Too many arguments."); - r = -EINVAL; - goto finish; - } - - r = show_statistics(bus); - break; - - case MODE_RESET_STATISTICS: - if (argc > optind) { - log_error("Too many arguments."); - r = -EINVAL; - goto finish; - } - - r = reset_statistics(bus); - break; - } - -finish: - return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c deleted file mode 100644 index 33f7c61557..0000000000 --- a/src/resolve/resolved-bus.c +++ /dev/null @@ -1,1655 +0,0 @@ -/*** - 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 "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-util.h" -#include "dns-domain.h" -#include "resolved-bus.h" -#include "resolved-def.h" -#include "resolved-dns-synthesize.h" -#include "resolved-link-bus.h" - -static int reply_query_state(DnsQuery *q) { - - switch (q->state) { - - case DNS_TRANSACTION_NO_SERVERS: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_NAME_SERVERS, "No appropriate name servers or networks for name found"); - - case DNS_TRANSACTION_TIMEOUT: - return sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "Query timed out"); - - case DNS_TRANSACTION_ATTEMPTS_MAX_REACHED: - return sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "All attempts to contact name servers or networks failed"); - - case DNS_TRANSACTION_INVALID_REPLY: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply"); - - case DNS_TRANSACTION_ERRNO: - return sd_bus_reply_method_errnof(q->request, q->answer_errno, "Lookup failed due to system error: %m"); - - case DNS_TRANSACTION_ABORTED: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "Query aborted"); - - case DNS_TRANSACTION_DNSSEC_FAILED: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s", - dnssec_result_to_string(q->answer_dnssec_result)); - - case DNS_TRANSACTION_NO_TRUST_ANCHOR: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_TRUST_ANCHOR, "No suitable trust anchor known"); - - case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_RR_TYPE_UNSUPPORTED, "Server does not support requested resource record type"); - - case DNS_TRANSACTION_NETWORK_DOWN: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NETWORK_DOWN, "Network is down"); - - case DNS_TRANSACTION_NOT_FOUND: - /* We return this as NXDOMAIN. This is only generated when a host doesn't implement LLMNR/TCP, and we - * thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */ - return sd_bus_reply_method_errorf(q->request, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q)); - - case DNS_TRANSACTION_RCODE_FAILURE: { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - - if (q->answer_rcode == DNS_RCODE_NXDOMAIN) - sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q)); - else { - const char *rc, *n; - char p[DECIMAL_STR_MAX(q->answer_rcode)]; - - rc = dns_rcode_to_string(q->answer_rcode); - if (!rc) { - sprintf(p, "%i", q->answer_rcode); - rc = p; - } - - n = strjoina(_BUS_ERROR_DNS, rc); - sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error %s", dns_query_string(q), rc); - } - - return sd_bus_reply_method_error(q->request, &error); - } - - case DNS_TRANSACTION_NULL: - case DNS_TRANSACTION_PENDING: - case DNS_TRANSACTION_VALIDATING: - case DNS_TRANSACTION_SUCCESS: - default: - assert_not_reached("Impossible state"); - } -} - -static int append_address(sd_bus_message *reply, DnsResourceRecord *rr, int ifindex) { - int r; - - assert(reply); - assert(rr); - - r = sd_bus_message_open_container(reply, 'r', "iiay"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "i", ifindex); - if (r < 0) - return r; - - if (rr->key->type == DNS_TYPE_A) { - r = sd_bus_message_append(reply, "i", AF_INET); - if (r < 0) - return r; - - r = sd_bus_message_append_array(reply, 'y', &rr->a.in_addr, sizeof(struct in_addr)); - - } else if (rr->key->type == DNS_TYPE_AAAA) { - r = sd_bus_message_append(reply, "i", AF_INET6); - if (r < 0) - return r; - - r = sd_bus_message_append_array(reply, 'y', &rr->aaaa.in6_addr, sizeof(struct in6_addr)); - } else - return -EAFNOSUPPORT; - - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return 0; -} - -static void bus_method_resolve_hostname_complete(DnsQuery *q) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *normalized = NULL; - DnsResourceRecord *rr; - unsigned added = 0; - int ifindex, r; - - assert(q); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } - - r = dns_query_process_cname(q); - if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); - goto finish; - } - if (r < 0) - goto finish; - if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ - return; - - r = sd_bus_message_new_method_return(q->request, &reply); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "(iiay)"); - if (r < 0) - goto finish; - - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - DnsQuestion *question; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - - r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); - if (r < 0) - goto finish; - if (r == 0) - continue; - - r = append_address(reply, rr, ifindex); - if (r < 0) - goto finish; - - if (!canonical) - canonical = dns_resource_record_ref(rr); - - added++; - } - - if (added <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q)); - goto finish; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - /* The key names are not necessarily normalized, make sure that they are when we return them to our bus - * clients. */ - r = dns_name_normalize(dns_resource_key_name(canonical->key), &normalized); - if (r < 0) - goto finish; - - /* Return the precise spelling and uppercasing and CNAME target reported by the server */ - assert(canonical); - r = sd_bus_message_append( - reply, "st", - normalized, - SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); - if (r < 0) - goto finish; - - r = sd_bus_send(q->manager->bus, reply, NULL); - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send hostname reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static int check_ifindex_flags(int ifindex, uint64_t *flags, uint64_t ok, sd_bus_error *error) { - assert(flags); - - if (ifindex < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); - - if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_CNAME|ok)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter"); - - if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */ - *flags |= SD_RESOLVED_PROTOCOLS_ALL; - - return 0; -} - -static int parse_as_address(sd_bus_message *m, int ifindex, const char *hostname, int family, uint64_t flags) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *canonical = NULL; - union in_addr_union parsed; - int r, ff; - - /* Check if the hostname is actually already an IP address formatted as string. In that case just parse it, - * let's not attempt to look it up. */ - - r = in_addr_from_string_auto(hostname, &ff, &parsed); - if (r < 0) /* not an address */ - return 0; - - if (family != AF_UNSPEC && ff != family) - return sd_bus_reply_method_errorf(m, BUS_ERROR_NO_SUCH_RR, "The specified address is not of the requested family."); - - r = sd_bus_message_new_method_return(m, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(iiay)"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'r', "iiay"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "ii", ifindex, ff); - if (r < 0) - return r; - - r = sd_bus_message_append_array(reply, 'y', &parsed, FAMILY_ADDRESS_SIZE(ff)); - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - /* When an IP address is specified we just return it as canonical name, in order to avoid a DNS - * look-up. However, we reformat it to make sure it's in a truly canonical form (i.e. on IPv6 the inner - * omissions are always done the same way). */ - r = in_addr_to_string(ff, &parsed, &canonical); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "st", canonical, - SD_RESOLVED_FLAGS_MAKE(dns_synthesize_protocol(flags), ff, true)); - if (r < 0) - return r; - - return sd_bus_send(sd_bus_message_get_bus(m), reply, NULL); -} - -static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL; - Manager *m = userdata; - const char *hostname; - int family, ifindex; - uint64_t flags; - DnsQuery *q; - int r; - - assert(message); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(message, "isit", &ifindex, &hostname, &family, &flags); - if (r < 0) - return r; - - if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); - - r = check_ifindex_flags(ifindex, &flags, SD_RESOLVED_NO_SEARCH, error); - if (r < 0) - return r; - - r = parse_as_address(message, ifindex, hostname, family, flags); - if (r != 0) - return r; - - r = dns_name_is_valid(hostname); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", hostname); - - r = dns_question_new_address(&question_utf8, family, hostname, false); - if (r < 0) - return r; - - r = dns_question_new_address(&question_idna, family, hostname, true); - if (r < 0) - return r; - - r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags); - if (r < 0) - return r; - - q->request = sd_bus_message_ref(message); - q->request_family = family; - q->complete = bus_method_resolve_hostname_complete; - q->suppress_unroutable_family = family == AF_UNSPEC; - - r = dns_query_bus_track(q, message); - if (r < 0) - goto fail; - - r = dns_query_go(q); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(q); - return r; -} - -static void bus_method_resolve_address_complete(DnsQuery *q) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - DnsQuestion *question; - DnsResourceRecord *rr; - unsigned added = 0; - int ifindex, r; - - assert(q); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } - - r = dns_query_process_cname(q); - if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); - goto finish; - } - if (r < 0) - goto finish; - if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ - return; - - r = sd_bus_message_new_method_return(q->request, &reply); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "(is)"); - if (r < 0) - goto finish; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - _cleanup_free_ char *normalized = NULL; - - r = dns_question_matches_rr(question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; - - r = dns_name_normalize(rr->ptr.name, &normalized); - if (r < 0) - goto finish; - - r = sd_bus_message_append(reply, "(is)", ifindex, normalized); - if (r < 0) - goto finish; - - added++; - } - - if (added <= 0) { - _cleanup_free_ char *ip = NULL; - - (void) in_addr_to_string(q->request_family, &q->request_address, &ip); - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, - "Address '%s' does not have any RR of requested type", strnull(ip)); - goto finish; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); - if (r < 0) - goto finish; - - r = sd_bus_send(q->manager->bus, reply, NULL); - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send address reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static int bus_method_resolve_address(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; - Manager *m = userdata; - int family, ifindex; - uint64_t flags; - const void *d; - DnsQuery *q; - size_t sz; - int r; - - assert(message); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(message, "ii", &ifindex, &family); - if (r < 0) - return r; - - if (!IN_SET(family, AF_INET, AF_INET6)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); - - r = sd_bus_message_read_array(message, 'y', &d, &sz); - if (r < 0) - return r; - - if (sz != FAMILY_ADDRESS_SIZE(family)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid address size"); - - r = sd_bus_message_read(message, "t", &flags); - if (r < 0) - return r; - - r = check_ifindex_flags(ifindex, &flags, 0, error); - if (r < 0) - return r; - - r = dns_question_new_reverse(&question, family, d); - if (r < 0) - return r; - - r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); - if (r < 0) - return r; - - q->request = sd_bus_message_ref(message); - q->request_family = family; - memcpy(&q->request_address, d, sz); - q->complete = bus_method_resolve_address_complete; - - r = dns_query_bus_track(q, message); - if (r < 0) - goto fail; - - r = dns_query_go(q); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(q); - return r; -} - -static int bus_message_append_rr(sd_bus_message *m, DnsResourceRecord *rr, int ifindex) { - int r; - - assert(m); - assert(rr); - - r = sd_bus_message_open_container(m, 'r', "iqqay"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "iqq", - ifindex, - rr->key->class, - rr->key->type); - if (r < 0) - return r; - - r = dns_resource_record_to_wire_format(rr, false); - if (r < 0) - return r; - - r = sd_bus_message_append_array(m, 'y', rr->wire_format, rr->wire_format_size); - if (r < 0) - return r; - - return sd_bus_message_close_container(m); -} - -static void bus_method_resolve_record_complete(DnsQuery *q) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - DnsResourceRecord *rr; - DnsQuestion *question; - unsigned added = 0; - int ifindex; - int r; - - assert(q); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } - - r = dns_query_process_cname(q); - if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); - goto finish; - } - if (r < 0) - goto finish; - if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ - return; - - r = sd_bus_message_new_method_return(q->request, &reply); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "(iqqay)"); - if (r < 0) - goto finish; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - r = dns_question_matches_rr(question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; - - r = bus_message_append_rr(reply, rr, ifindex); - if (r < 0) - goto finish; - - added++; - } - - if (added <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Name '%s' does not have any RR of the requested type", dns_query_string(q)); - goto finish; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); - if (r < 0) - goto finish; - - r = sd_bus_send(q->manager->bus, reply, NULL); - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send record reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; - Manager *m = userdata; - uint16_t class, type; - const char *name; - int r, ifindex; - uint64_t flags; - DnsQuery *q; - - assert(message); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(message, "isqqt", &ifindex, &name, &class, &type, &flags); - if (r < 0) - return r; - - r = dns_name_is_valid(name); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid name '%s'", name); - - if (!dns_type_is_valid_query(type)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified resource record type %" PRIu16 " may not be used in a query.", type); - if (dns_type_is_obsolete(type)) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Specified DNS resource record type %" PRIu16 " is obsolete.", type); - - r = check_ifindex_flags(ifindex, &flags, 0, error); - if (r < 0) - return r; - - question = dns_question_new(1); - if (!question) - return -ENOMEM; - - key = dns_resource_key_new(class, type, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(question, key); - if (r < 0) - return r; - - r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); - if (r < 0) - return r; - - q->request = sd_bus_message_ref(message); - q->complete = bus_method_resolve_record_complete; - - r = dns_query_bus_track(q, message); - if (r < 0) - goto fail; - - r = dns_query_go(q); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(q); - return r; -} - -static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; - _cleanup_free_ char *normalized = NULL; - DnsQuery *aux; - int r; - - assert(q); - assert(reply); - assert(rr); - assert(rr->key); - - if (rr->key->type != DNS_TYPE_SRV) - return 0; - - if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { - /* First, let's see if we could find an appropriate A or AAAA - * record for the SRV record */ - LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { - DnsResourceRecord *zz; - DnsQuestion *question; - - if (aux->state != DNS_TRANSACTION_SUCCESS) - continue; - if (aux->auxiliary_result != 0) - continue; - - question = dns_query_question_for_protocol(aux, aux->answer_protocol); - - r = dns_name_equal(dns_question_first_name(question), rr->srv.name); - if (r < 0) - return r; - if (r == 0) - continue; - - DNS_ANSWER_FOREACH(zz, aux->answer) { - - r = dns_question_matches_rr(question, zz, NULL); - if (r < 0) - return r; - if (r == 0) - continue; - - canonical = dns_resource_record_ref(zz); - break; - } - - if (canonical) - break; - } - - /* Is there are successful A/AAAA lookup for this SRV RR? If not, don't add it */ - if (!canonical) - return 0; - } - - r = sd_bus_message_open_container(reply, 'r', "qqqsa(iiay)s"); - if (r < 0) - return r; - - r = dns_name_normalize(rr->srv.name, &normalized); - if (r < 0) - return r; - - r = sd_bus_message_append( - reply, - "qqqs", - rr->srv.priority, rr->srv.weight, rr->srv.port, normalized); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(iiay)"); - if (r < 0) - return r; - - if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { - LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { - DnsResourceRecord *zz; - DnsQuestion *question; - int ifindex; - - if (aux->state != DNS_TRANSACTION_SUCCESS) - continue; - if (aux->auxiliary_result != 0) - continue; - - question = dns_query_question_for_protocol(aux, aux->answer_protocol); - - r = dns_name_equal(dns_question_first_name(question), rr->srv.name); - if (r < 0) - return r; - if (r == 0) - continue; - - DNS_ANSWER_FOREACH_IFINDEX(zz, ifindex, aux->answer) { - - r = dns_question_matches_rr(question, zz, NULL); - if (r < 0) - return r; - if (r == 0) - continue; - - r = append_address(reply, zz, ifindex); - if (r < 0) - return r; - } - } - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - if (canonical) { - normalized = mfree(normalized); - - r = dns_name_normalize(dns_resource_key_name(canonical->key), &normalized); - if (r < 0) - return r; - } - - /* Note that above we appended the hostname as encoded in the - * SRV, and here the canonical hostname this maps to. */ - r = sd_bus_message_append(reply, "s", normalized); - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return 1; -} - -static int append_txt(sd_bus_message *reply, DnsResourceRecord *rr) { - DnsTxtItem *i; - int r; - - assert(reply); - assert(rr); - assert(rr->key); - - if (rr->key->type != DNS_TYPE_TXT) - return 0; - - LIST_FOREACH(items, i, rr->txt.items) { - - if (i->length <= 0) - continue; - - r = sd_bus_message_append_array(reply, 'y', i->data, i->length); - if (r < 0) - return r; - } - - return 1; -} - -static void resolve_service_all_complete(DnsQuery *q) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL; - DnsQuestion *question; - DnsResourceRecord *rr; - unsigned added = 0; - DnsQuery *aux; - int r; - - assert(q); - - if (q->block_all_complete > 0) - return; - - if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { - DnsQuery *bad = NULL; - bool have_success = false; - - LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { - - switch (aux->state) { - - case DNS_TRANSACTION_PENDING: - /* If an auxiliary query is still pending, let's wait */ - return; - - case DNS_TRANSACTION_SUCCESS: - if (aux->auxiliary_result == 0) - have_success = true; - else - bad = aux; - break; - - default: - bad = aux; - break; - } - } - - if (!have_success) { - /* We can only return one error, hence pick the last error we encountered */ - - assert(bad); - - if (bad->state == DNS_TRANSACTION_SUCCESS) { - assert(bad->auxiliary_result != 0); - - if (bad->auxiliary_result == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(bad)); - goto finish; - } - - r = bad->auxiliary_result; - goto finish; - } - - r = reply_query_state(bad); - goto finish; - } - } - - r = sd_bus_message_new_method_return(q->request, &reply); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "(qqqsa(iiay)s)"); - if (r < 0) - goto finish; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - DNS_ANSWER_FOREACH(rr, q->answer) { - r = dns_question_matches_rr(question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; - - r = append_srv(q, reply, rr); - if (r < 0) - goto finish; - if (r == 0) /* not an SRV record */ - continue; - - if (!canonical) - canonical = dns_resource_record_ref(rr); - - added++; - } - - if (added <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q)); - goto finish; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "ay"); - if (r < 0) - goto finish; - - DNS_ANSWER_FOREACH(rr, q->answer) { - r = dns_question_matches_rr(question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; - - r = append_txt(reply, rr); - if (r < 0) - goto finish; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - assert(canonical); - r = dns_service_split(dns_resource_key_name(canonical->key), &name, &type, &domain); - if (r < 0) - goto finish; - - r = sd_bus_message_append( - reply, - "ssst", - name, type, domain, - SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); - if (r < 0) - goto finish; - - r = sd_bus_send(q->manager->bus, reply, NULL); - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send service reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static void resolve_service_hostname_complete(DnsQuery *q) { - int r; - - assert(q); - assert(q->auxiliary_for); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - resolve_service_all_complete(q->auxiliary_for); - return; - } - - r = dns_query_process_cname(q); - if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ - return; - - /* This auxiliary lookup is finished or failed, let's see if all are finished now. */ - q->auxiliary_result = r; - resolve_service_all_complete(q->auxiliary_for); -} - -static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifindex) { - _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; - DnsQuery *aux; - int r; - - assert(q); - assert(rr); - assert(rr->key); - assert(rr->key->type == DNS_TYPE_SRV); - - /* OK, we found an SRV record for the service. Let's resolve - * the hostname included in it */ - - r = dns_question_new_address(&question, q->request_family, rr->srv.name, false); - if (r < 0) - return r; - - r = dns_query_new(q->manager, &aux, question, question, ifindex, q->flags|SD_RESOLVED_NO_SEARCH); - if (r < 0) - return r; - - aux->request_family = q->request_family; - aux->complete = resolve_service_hostname_complete; - - r = dns_query_make_auxiliary(aux, q); - if (r == -EAGAIN) { - /* Too many auxiliary lookups? If so, don't complain, - * let's just not add this one, we already have more - * than enough */ - - dns_query_free(aux); - return 0; - } - if (r < 0) - goto fail; - - /* Note that auxiliary queries do not track the original bus - * client, only the primary request does that. */ - - r = dns_query_go(aux); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(aux); - return r; -} - -static void bus_method_resolve_service_complete(DnsQuery *q) { - bool has_root_domain = false; - DnsResourceRecord *rr; - DnsQuestion *question; - unsigned found = 0; - int ifindex, r; - - assert(q); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } - - r = dns_query_process_cname(q); - if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); - goto finish; - } - if (r < 0) - goto finish; - if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ - return; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - r = dns_question_matches_rr(question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; - - if (rr->key->type != DNS_TYPE_SRV) - continue; - - if (dns_name_is_root(rr->srv.name)) { - has_root_domain = true; - continue; - } - - if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { - q->block_all_complete++; - r = resolve_service_hostname(q, rr, ifindex); - q->block_all_complete--; - - if (r < 0) - goto finish; - } - - found++; - } - - if (has_root_domain && found <= 0) { - /* If there's exactly one SRV RR and it uses - * the root domain as host name, then the - * service is explicitly not offered on the - * domain. Report this as a recognizable - * error. See RFC 2782, Section "Usage - * Rules". */ - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_SERVICE, "'%s' does not provide the requested service", dns_query_string(q)); - goto finish; - } - - if (found <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q)); - goto finish; - } - - /* Maybe we are already finished? check now... */ - resolve_service_all_complete(q); - return; - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send service reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static int bus_method_resolve_service(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL; - const char *name, *type, *domain; - Manager *m = userdata; - int family, ifindex; - uint64_t flags; - DnsQuery *q; - int r; - - assert(message); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(message, "isssit", &ifindex, &name, &type, &domain, &family, &flags); - if (r < 0) - return r; - - if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); - - if (isempty(name)) - name = NULL; - else if (!dns_service_name_is_valid(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid service name '%s'", name); - - if (isempty(type)) - type = NULL; - else if (!dns_srv_type_is_valid(type)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid SRV service type '%s'", type); - - r = dns_name_is_valid(domain); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid domain '%s'", domain); - - if (name && !type) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Service name cannot be specified without service type."); - - r = check_ifindex_flags(ifindex, &flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS, error); - if (r < 0) - return r; - - r = dns_question_new_service(&question_utf8, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), false); - if (r < 0) - return r; - - r = dns_question_new_service(&question_idna, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), true); - if (r < 0) - return r; - - r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags|SD_RESOLVED_NO_SEARCH); - if (r < 0) - return r; - - q->request = sd_bus_message_ref(message); - q->request_family = family; - q->complete = bus_method_resolve_service_complete; - - r = dns_query_bus_track(q, message); - if (r < 0) - goto fail; - - r = dns_query_go(q); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(q); - return r; -} - -int bus_dns_server_append(sd_bus_message *reply, DnsServer *s, bool with_ifindex) { - int r; - - assert(reply); - assert(s); - - r = sd_bus_message_open_container(reply, 'r', with_ifindex ? "iiay" : "iay"); - if (r < 0) - return r; - - if (with_ifindex) { - r = sd_bus_message_append(reply, "i", s->link ? s->link->ifindex : 0); - if (r < 0) - return r; - } - - r = sd_bus_message_append(reply, "i", s->family); - if (r < 0) - return r; - - r = sd_bus_message_append_array(reply, 'y', &s->address, FAMILY_ADDRESS_SIZE(s->family)); - if (r < 0) - return r; - - return sd_bus_message_close_container(reply); -} - -static int bus_property_get_dns_servers( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Manager *m = userdata; - unsigned c = 0; - DnsServer *s; - Iterator i; - Link *l; - int r; - - assert(reply); - assert(m); - - r = sd_bus_message_open_container(reply, 'a', "(iiay)"); - if (r < 0) - return r; - - LIST_FOREACH(servers, s, m->dns_servers) { - r = bus_dns_server_append(reply, s, true); - if (r < 0) - return r; - - c++; - } - - HASHMAP_FOREACH(l, m->links, i) { - LIST_FOREACH(servers, s, l->dns_servers) { - r = bus_dns_server_append(reply, s, true); - if (r < 0) - return r; - c++; - } - } - - if (c == 0) { - LIST_FOREACH(servers, s, m->fallback_dns_servers) { - r = bus_dns_server_append(reply, s, true); - if (r < 0) - return r; - } - } - - return sd_bus_message_close_container(reply); -} - -static int bus_property_get_domains( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Manager *m = userdata; - DnsSearchDomain *d; - Iterator i; - Link *l; - int r; - - assert(reply); - assert(m); - - r = sd_bus_message_open_container(reply, 'a', "(isb)"); - if (r < 0) - return r; - - LIST_FOREACH(domains, d, m->search_domains) { - r = sd_bus_message_append(reply, "(isb)", 0, d->name, d->route_only); - if (r < 0) - return r; - } - - HASHMAP_FOREACH(l, m->links, i) { - LIST_FOREACH(domains, d, l->search_domains) { - r = sd_bus_message_append(reply, "(isb)", l->ifindex, d->name, d->route_only); - if (r < 0) - return r; - } - } - - return sd_bus_message_close_container(reply); -} - -static int bus_property_get_transaction_statistics( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Manager *m = userdata; - - assert(reply); - assert(m); - - return sd_bus_message_append(reply, "(tt)", - (uint64_t) hashmap_size(m->dns_transactions), - (uint64_t) m->n_transactions_total); -} - -static int bus_property_get_cache_statistics( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - uint64_t size = 0, hit = 0, miss = 0; - Manager *m = userdata; - DnsScope *s; - - assert(reply); - assert(m); - - LIST_FOREACH(scopes, s, m->dns_scopes) { - size += dns_cache_size(&s->cache); - hit += s->cache.n_hit; - miss += s->cache.n_miss; - } - - return sd_bus_message_append(reply, "(ttt)", size, hit, miss); -} - -static int bus_property_get_dnssec_statistics( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Manager *m = userdata; - - assert(reply); - assert(m); - - return sd_bus_message_append(reply, "(tttt)", - (uint64_t) m->n_dnssec_verdict[DNSSEC_SECURE], - (uint64_t) m->n_dnssec_verdict[DNSSEC_INSECURE], - (uint64_t) m->n_dnssec_verdict[DNSSEC_BOGUS], - (uint64_t) m->n_dnssec_verdict[DNSSEC_INDETERMINATE]); -} - -static int bus_property_get_dnssec_supported( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Manager *m = userdata; - - assert(reply); - assert(m); - - return sd_bus_message_append(reply, "b", manager_dnssec_supported(m)); -} - -static int bus_method_reset_statistics(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - DnsScope *s; - - assert(message); - assert(m); - - LIST_FOREACH(scopes, s, m->dns_scopes) - s->cache.n_hit = s->cache.n_miss = 0; - - m->n_transactions_total = 0; - zero(m->n_dnssec_verdict); - - return sd_bus_reply_method_return(message, NULL); -} - -static int get_any_link(Manager *m, int ifindex, Link **ret, sd_bus_error *error) { - Link *l; - - assert(m); - assert(ret); - - if (ifindex <= 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); - - l = hashmap_get(m->links, INT_TO_PTR(ifindex)); - if (!l) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_LINK, "Link %i not known", ifindex); - - *ret = l; - return 0; -} - -static int get_unmanaged_link(Manager *m, int ifindex, Link **ret, sd_bus_error *error) { - Link *l; - int r; - - assert(m); - assert(ret); - - r = get_any_link(m, ifindex, &l, error); - if (r < 0) - return r; - - if (l->flags & IFF_LOOPBACK) - return sd_bus_error_setf(error, BUS_ERROR_LINK_BUSY, "Link %s is loopback device.", l->name); - if (l->is_managed) - return sd_bus_error_setf(error, BUS_ERROR_LINK_BUSY, "Link %s is managed.", l->name); - - *ret = l; - return 0; -} - -static int call_link_method(Manager *m, sd_bus_message *message, sd_bus_message_handler_t handler, sd_bus_error *error) { - int ifindex, r; - Link *l; - - assert(m); - assert(message); - assert(handler); - - assert_cc(sizeof(int) == sizeof(int32_t)); - r = sd_bus_message_read(message, "i", &ifindex); - if (r < 0) - return r; - - r = get_unmanaged_link(m, ifindex, &l, error); - if (r < 0) - return r; - - return handler(message, l, error); -} - -static int bus_method_set_link_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_set_dns_servers, error); -} - -static int bus_method_set_link_domains(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_set_domains, error); -} - -static int bus_method_set_link_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_set_llmnr, error); -} - -static int bus_method_set_link_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_set_mdns, error); -} - -static int bus_method_set_link_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_set_dnssec, error); -} - -static int bus_method_set_link_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_set_dnssec_negative_trust_anchors, error); -} - -static int bus_method_revert_link(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_revert, error); -} - -static int bus_method_get_link(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *p = NULL; - Manager *m = userdata; - int r, ifindex; - Link *l; - - assert(message); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - r = sd_bus_message_read(message, "i", &ifindex); - if (r < 0) - return r; - - r = get_any_link(m, ifindex, &l, error); - if (r < 0) - return r; - - p = link_bus_path(l); - if (!p) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", p); -} - -static const sd_bus_vtable resolve_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("LLMNRHostname", "s", NULL, offsetof(Manager, llmnr_hostname), 0), - SD_BUS_PROPERTY("DNS", "a(iiay)", bus_property_get_dns_servers, 0, 0), - SD_BUS_PROPERTY("Domains", "a(isb)", bus_property_get_domains, 0, 0), - SD_BUS_PROPERTY("TransactionStatistics", "(tt)", bus_property_get_transaction_statistics, 0, 0), - SD_BUS_PROPERTY("CacheStatistics", "(ttt)", bus_property_get_cache_statistics, 0, 0), - SD_BUS_PROPERTY("DNSSECStatistics", "(tttt)", bus_property_get_dnssec_statistics, 0, 0), - SD_BUS_PROPERTY("DNSSECSupported", "b", bus_property_get_dnssec_supported, 0, 0), - - SD_BUS_METHOD("ResolveHostname", "isit", "a(iiay)st", bus_method_resolve_hostname, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ResolveAddress", "iiayt", "a(is)t", bus_method_resolve_address, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ResolveRecord", "isqqt", "a(iqqay)t", bus_method_resolve_record, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ResolveService", "isssit", "a(qqqsa(iiay)s)aayssst", bus_method_resolve_service, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ResetStatistics", NULL, NULL, bus_method_reset_statistics, 0), - SD_BUS_METHOD("GetLink", "i", "o", bus_method_get_link, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetLinkDNS", "ia(iay)", NULL, bus_method_set_link_dns_servers, 0), - SD_BUS_METHOD("SetLinkDomains", "ia(sb)", NULL, bus_method_set_link_domains, 0), - SD_BUS_METHOD("SetLinkLLMNR", "is", NULL, bus_method_set_link_llmnr, 0), - SD_BUS_METHOD("SetLinkMulticastDNS", "is", NULL, bus_method_set_link_mdns, 0), - SD_BUS_METHOD("SetLinkDNSSEC", "is", NULL, bus_method_set_link_dnssec, 0), - SD_BUS_METHOD("SetLinkDNSSECNegativeTrustAnchors", "ias", NULL, bus_method_set_link_dnssec_negative_trust_anchors, 0), - SD_BUS_METHOD("RevertLink", "i", NULL, bus_method_revert_link, 0), - - SD_BUS_VTABLE_END, -}; - -static int on_bus_retry(sd_event_source *s, usec_t usec, void *userdata) { - Manager *m = userdata; - - assert(s); - assert(m); - - m->bus_retry_event_source = sd_event_source_unref(m->bus_retry_event_source); - - manager_connect_bus(m); - return 0; -} - -static int match_prepare_for_sleep(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) { - Manager *m = userdata; - int b, r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) { - log_debug_errno(r, "Failed to parse PrepareForSleep signal: %m"); - return 0; - } - - if (b) - return 0; - - log_debug("Coming back from suspend, verifying all RRs..."); - - manager_verify_all(m); - return 0; -} - -int manager_connect_bus(Manager *m) { - int r; - - assert(m); - - if (m->bus) - return 0; - - r = sd_bus_default_system(&m->bus); - if (r < 0) { - /* We failed to connect? Yuck, we must be in early - * boot. Let's try in 5s again. As soon as we have - * kdbus we can stop doing this... */ - - log_debug_errno(r, "Failed to connect to bus, trying again in 5s: %m"); - - r = sd_event_add_time(m->event, &m->bus_retry_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 5*USEC_PER_SEC, 0, on_bus_retry, m); - if (r < 0) - return log_error_errno(r, "Failed to install bus reconnect time event: %m"); - - (void) sd_event_source_set_description(m->bus_retry_event_source, "bus-retry"); - return 0; - } - - r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/resolve1", "org.freedesktop.resolve1.Manager", resolve_vtable, m); - if (r < 0) - return log_error_errno(r, "Failed to register object: %m"); - - r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/resolve1/link", "org.freedesktop.resolve1.Link", link_vtable, link_object_find, m); - if (r < 0) - return log_error_errno(r, "Failed to register link objects: %m"); - - r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/resolve1/link", link_node_enumerator, m); - if (r < 0) - return log_error_errno(r, "Failed to register link enumerator: %m"); - - r = sd_bus_request_name(m->bus, "org.freedesktop.resolve1", 0); - if (r < 0) - return log_error_errno(r, "Failed to register name: %m"); - - r = sd_bus_attach_event(m->bus, m->event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - - r = sd_bus_add_match(m->bus, &m->prepare_for_sleep_slot, - "type='signal'," - "sender='org.freedesktop.login1'," - "interface='org.freedesktop.login1.Manager'," - "member='PrepareForSleep'," - "path='/org/freedesktop/login1'", - match_prepare_for_sleep, - m); - if (r < 0) - log_error_errno(r, "Failed to add match for PrepareForSleep: %m"); - - return 0; -} diff --git a/src/resolve/resolved-bus.h b/src/resolve/resolved-bus.h deleted file mode 100644 index f49e1337d2..0000000000 --- a/src/resolve/resolved-bus.h +++ /dev/null @@ -1,25 +0,0 @@ -#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 "resolved-manager.h" - -int manager_connect_bus(Manager *m); -int bus_dns_server_append(sd_bus_message *reply, DnsServer *s, bool with_ifindex); diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c deleted file mode 100644 index 990dc03b60..0000000000 --- a/src/resolve/resolved-conf.c +++ /dev/null @@ -1,236 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 "alloc-util.h" -#include "conf-parser.h" -#include "def.h" -#include "extract-word.h" -#include "parse-util.h" -#include "resolved-conf.h" -#include "string-util.h" - -int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word) { - union in_addr_union address; - int family, r; - DnsServer *s; - - assert(m); - assert(word); - - r = in_addr_from_string_auto(word, &family, &address); - if (r < 0) - return r; - - /* Filter out duplicates */ - s = dns_server_find(manager_get_first_dns_server(m, type), family, &address); - if (s) { - /* - * Drop the marker. This is used to find the servers - * that ceased to exist, see - * manager_mark_dns_servers() and - * manager_flush_marked_dns_servers(). - */ - dns_server_move_back_and_unmark(s); - return 0; - } - - return dns_server_new(m, NULL, type, NULL, family, &address); -} - -int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string) { - int r; - - assert(m); - assert(string); - - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&string, &word, NULL, 0); - if (r < 0) - return r; - if (r == 0) - break; - - r = manager_add_dns_server_by_string(m, type, word); - if (r < 0) - log_warning_errno(r, "Failed to add DNS server address '%s', ignoring.", word); - } - - return 0; -} - -int manager_add_search_domain_by_string(Manager *m, const char *domain) { - DnsSearchDomain *d; - bool route_only; - int r; - - assert(m); - assert(domain); - - route_only = *domain == '~'; - if (route_only) - domain++; - - if (dns_name_is_root(domain) || streq(domain, "*")) { - route_only = true; - domain = "."; - } - - r = dns_search_domain_find(m->search_domains, domain, &d); - if (r < 0) - return r; - if (r > 0) - dns_search_domain_move_back_and_unmark(d); - else { - r = dns_search_domain_new(m, &d, DNS_SEARCH_DOMAIN_SYSTEM, NULL, domain); - if (r < 0) - return r; - } - - d->route_only = route_only; - return 0; -} - -int manager_parse_search_domains_and_warn(Manager *m, const char *string) { - int r; - - assert(m); - assert(string); - - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&string, &word, NULL, EXTRACT_QUOTES); - if (r < 0) - return r; - if (r == 0) - break; - - r = manager_add_search_domain_by_string(m, word); - if (r < 0) - log_warning_errno(r, "Failed to add search domain '%s', ignoring.", word); - } - - return 0; -} - -int config_parse_dns_servers( - 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) { - - Manager *m = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(m); - - if (isempty(rvalue)) - /* Empty assignment means clear the list */ - dns_server_unlink_all(manager_get_first_dns_server(m, ltype)); - else { - /* Otherwise, add to the list */ - r = manager_parse_dns_server_string_and_warn(m, ltype, rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse DNS server string '%s'. Ignoring.", rvalue); - return 0; - } - } - - /* If we have a manual setting, then we stop reading - * /etc/resolv.conf */ - if (ltype == DNS_SERVER_SYSTEM) - m->read_resolv_conf = false; - if (ltype == DNS_SERVER_FALLBACK) - m->need_builtin_fallbacks = false; - - return 0; -} - -int config_parse_search_domains( - 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) { - - Manager *m = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(m); - - if (isempty(rvalue)) - /* Empty assignment means clear the list */ - dns_search_domain_unlink_all(m->search_domains); - else { - /* Otherwise, add to the list */ - r = manager_parse_search_domains_and_warn(m, rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse search domains string '%s'. Ignoring.", rvalue); - return 0; - } - } - - /* If we have a manual setting, then we stop reading - * /etc/resolv.conf */ - m->read_resolv_conf = false; - - return 0; -} - -int manager_parse_config_file(Manager *m) { - int r; - - assert(m); - - r = config_parse_many(PKGSYSCONFDIR "/resolved.conf", - CONF_PATHS_NULSTR("systemd/resolved.conf.d"), - "Resolve\0", - config_item_perf_lookup, resolved_gperf_lookup, - false, m); - if (r < 0) - return r; - - if (m->need_builtin_fallbacks) { - r = manager_parse_dns_server_string_and_warn(m, DNS_SERVER_FALLBACK, DNS_SERVERS); - if (r < 0) - return r; - } - - return 0; - -} diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h deleted file mode 100644 index e1fd2cceec..0000000000 --- a/src/resolve/resolved-conf.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 "resolved-manager.h" - -int manager_parse_config_file(Manager *m); - -int manager_add_search_domain_by_string(Manager *m, const char *domain); -int manager_parse_search_domains_and_warn(Manager *m, const char *string); - -int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word); -int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string); - -const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, unsigned length); - -int config_parse_dns_servers(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); -int config_parse_search_domains(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); -int config_parse_dnssec(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); diff --git a/src/resolve/resolved-def.h b/src/resolve/resolved-def.h deleted file mode 100644 index c4c1915b18..0000000000 --- a/src/resolve/resolved-def.h +++ /dev/null @@ -1,38 +0,0 @@ -#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 - -#define SD_RESOLVED_DNS (UINT64_C(1) << 0) -#define SD_RESOLVED_LLMNR_IPV4 (UINT64_C(1) << 1) -#define SD_RESOLVED_LLMNR_IPV6 (UINT64_C(1) << 2) -#define SD_RESOLVED_MDNS_IPV4 (UINT64_C(1) << 3) -#define SD_RESOLVED_MDNS_IPV6 (UINT64_C(1) << 4) -#define SD_RESOLVED_NO_CNAME (UINT64_C(1) << 5) -#define SD_RESOLVED_NO_TXT (UINT64_C(1) << 6) -#define SD_RESOLVED_NO_ADDRESS (UINT64_C(1) << 7) -#define SD_RESOLVED_NO_SEARCH (UINT64_C(1) << 8) -#define SD_RESOLVED_AUTHENTICATED (UINT64_C(1) << 9) - -#define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6) -#define SD_RESOLVED_MDNS (SD_RESOLVED_MDNS_IPV4|SD_RESOLVED_MDNS_IPV6) - -#define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS) diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c deleted file mode 100644 index 0dadf8b1dd..0000000000 --- a/src/resolve/resolved-dns-answer.c +++ /dev/null @@ -1,858 +0,0 @@ -/*** - 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 "alloc-util.h" -#include "dns-domain.h" -#include "resolved-dns-answer.h" -#include "resolved-dns-dnssec.h" -#include "string-util.h" - -DnsAnswer *dns_answer_new(unsigned n) { - DnsAnswer *a; - - a = malloc0(offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * n); - if (!a) - return NULL; - - a->n_ref = 1; - a->n_allocated = n; - - return a; -} - -DnsAnswer *dns_answer_ref(DnsAnswer *a) { - if (!a) - return NULL; - - assert(a->n_ref > 0); - a->n_ref++; - return a; -} - -static void dns_answer_flush(DnsAnswer *a) { - DnsResourceRecord *rr; - - if (!a) - return; - - DNS_ANSWER_FOREACH(rr, a) - dns_resource_record_unref(rr); - - a->n_rrs = 0; -} - -DnsAnswer *dns_answer_unref(DnsAnswer *a) { - if (!a) - return NULL; - - assert(a->n_ref > 0); - - if (a->n_ref == 1) { - dns_answer_flush(a); - free(a); - } else - a->n_ref--; - - return NULL; -} - -static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { - assert(rr); - - if (!a) - return -ENOSPC; - - if (a->n_rrs >= a->n_allocated) - return -ENOSPC; - - a->items[a->n_rrs++] = (DnsAnswerItem) { - .rr = dns_resource_record_ref(rr), - .ifindex = ifindex, - .flags = flags, - }; - - return 1; -} - -static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) { - DnsResourceRecord *rr; - DnsAnswerFlags flags; - int ifindex, r; - - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, source) { - r = dns_answer_add_raw(a, rr, ifindex, flags); - if (r < 0) - return r; - } - - return 0; -} - -int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { - unsigned i; - int r; - - assert(rr); - - if (!a) - return -ENOSPC; - if (a->n_ref > 1) - return -EBUSY; - - for (i = 0; i < a->n_rrs; i++) { - if (a->items[i].ifindex != ifindex) - continue; - - r = dns_resource_record_equal(a->items[i].rr, rr); - if (r < 0) - return r; - if (r > 0) { - /* Don't mix contradicting TTLs (see below) */ - if ((rr->ttl == 0) != (a->items[i].rr->ttl == 0)) - return -EINVAL; - - /* Entry already exists, keep the entry with - * the higher RR. */ - if (rr->ttl > a->items[i].rr->ttl) { - dns_resource_record_ref(rr); - dns_resource_record_unref(a->items[i].rr); - a->items[i].rr = rr; - } - - a->items[i].flags |= flags; - return 0; - } - - r = dns_resource_key_equal(a->items[i].rr->key, rr->key); - if (r < 0) - return r; - if (r > 0) { - /* There's already an RR of the same RRset in - * place! Let's see if the TTLs more or less - * match. We don't really care if they match - * precisely, but we do care whether one is 0 - * and the other is not. See RFC 2181, Section - * 5.2.*/ - - if ((rr->ttl == 0) != (a->items[i].rr->ttl == 0)) - return -EINVAL; - } - } - - return dns_answer_add_raw(a, rr, ifindex, flags); -} - -static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) { - DnsResourceRecord *rr; - DnsAnswerFlags flags; - int ifindex, r; - - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, b) { - r = dns_answer_add(a, rr, ifindex, flags); - if (r < 0) - return r; - } - - return 0; -} - -int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { - int r; - - assert(a); - assert(rr); - - r = dns_answer_reserve_or_clone(a, 1); - if (r < 0) - return r; - - return dns_answer_add(*a, rr, ifindex, flags); -} - -int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *soa = NULL; - - soa = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_SOA, name); - if (!soa) - return -ENOMEM; - - soa->ttl = ttl; - - soa->soa.mname = strdup(name); - if (!soa->soa.mname) - return -ENOMEM; - - soa->soa.rname = strappend("root.", name); - if (!soa->soa.rname) - return -ENOMEM; - - soa->soa.serial = 1; - soa->soa.refresh = 1; - soa->soa.retry = 1; - soa->soa.expire = 1; - soa->soa.minimum = ttl; - - return dns_answer_add(a, soa, 0, DNS_ANSWER_AUTHENTICATED); -} - -int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) { - DnsAnswerFlags flags = 0, i_flags; - DnsResourceRecord *i; - bool found = false; - int r; - - assert(key); - - DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { - r = dns_resource_key_match_rr(key, i, NULL); - if (r < 0) - return r; - if (r == 0) - continue; - - if (!ret_flags) - return 1; - - if (found) - flags &= i_flags; - else { - flags = i_flags; - found = true; - } - } - - if (ret_flags) - *ret_flags = flags; - - return found; -} - -int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr, DnsAnswerFlags *ret_flags) { - DnsAnswerFlags flags = 0, i_flags; - DnsResourceRecord *i; - bool found = false; - int r; - - assert(rr); - - DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { - r = dns_resource_record_equal(i, rr); - if (r < 0) - return r; - if (r == 0) - continue; - - if (!ret_flags) - return 1; - - if (found) - flags &= i_flags; - else { - flags = i_flags; - found = true; - } - } - - if (ret_flags) - *ret_flags = flags; - - return found; -} - -int dns_answer_contains_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) { - DnsAnswerFlags flags = 0, i_flags; - DnsResourceRecord *i; - bool found = false; - int r; - - assert(key); - - DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { - r = dns_resource_key_equal(i->key, key); - if (r < 0) - return r; - if (r == 0) - continue; - - if (!ret_flags) - return true; - - if (found) - flags &= i_flags; - else { - flags = i_flags; - found = true; - } - } - - if (ret_flags) - *ret_flags = flags; - - return found; -} - -int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a) { - DnsResourceRecord *i; - - DNS_ANSWER_FOREACH(i, a) { - if (IN_SET(i->key->type, DNS_TYPE_NSEC, DNS_TYPE_NSEC3)) - return true; - } - - return false; -} - -int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone) { - DnsResourceRecord *rr; - int r; - - /* Checks whether the specified answer contains at least one NSEC3 RR in the specified zone */ - - DNS_ANSWER_FOREACH(rr, answer) { - const char *p; - - if (rr->key->type != DNS_TYPE_NSEC3) - continue; - - p = dns_resource_key_name(rr->key); - r = dns_name_parent(&p); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dns_name_equal(p, zone); - if (r != 0) - return r; - } - - return false; -} - -int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) { - DnsResourceRecord *rr, *soa = NULL; - DnsAnswerFlags rr_flags, soa_flags = 0; - int r; - - assert(key); - - /* For a SOA record we can never find a matching SOA record */ - if (key->type == DNS_TYPE_SOA) - return 0; - - DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) { - r = dns_resource_key_match_soa(key, rr->key); - if (r < 0) - return r; - if (r > 0) { - - if (soa) { - r = dns_name_endswith(dns_resource_key_name(rr->key), dns_resource_key_name(soa->key)); - if (r < 0) - return r; - if (r > 0) - continue; - } - - soa = rr; - soa_flags = rr_flags; - } - } - - if (!soa) - return 0; - - if (ret) - *ret = soa; - if (flags) - *flags = soa_flags; - - return 1; -} - -int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) { - DnsResourceRecord *rr; - DnsAnswerFlags rr_flags; - int r; - - assert(key); - - /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */ - if (!dns_type_may_redirect(key->type)) - return 0; - - DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) { - r = dns_resource_key_match_cname_or_dname(key, rr->key, NULL); - if (r < 0) - return r; - if (r > 0) { - if (ret) - *ret = rr; - if (flags) - *flags = rr_flags; - return 1; - } - } - - return 0; -} - -int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret) { - _cleanup_(dns_answer_unrefp) DnsAnswer *k = NULL; - int r; - - assert(ret); - - if (dns_answer_size(a) <= 0) { - *ret = dns_answer_ref(b); - return 0; - } - - if (dns_answer_size(b) <= 0) { - *ret = dns_answer_ref(a); - return 0; - } - - k = dns_answer_new(a->n_rrs + b->n_rrs); - if (!k) - return -ENOMEM; - - r = dns_answer_add_raw_all(k, a); - if (r < 0) - return r; - - r = dns_answer_add_all(k, b); - if (r < 0) - return r; - - *ret = k; - k = NULL; - - return 0; -} - -int dns_answer_extend(DnsAnswer **a, DnsAnswer *b) { - DnsAnswer *merged; - int r; - - assert(a); - - r = dns_answer_merge(*a, b, &merged); - if (r < 0) - return r; - - dns_answer_unref(*a); - *a = merged; - - return 0; -} - -int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) { - bool found = false, other = false; - DnsResourceRecord *rr; - unsigned i; - int r; - - assert(a); - assert(key); - - /* Remove all entries matching the specified key from *a */ - - DNS_ANSWER_FOREACH(rr, *a) { - r = dns_resource_key_equal(rr->key, key); - if (r < 0) - return r; - if (r > 0) - found = true; - else - other = true; - - if (found && other) - break; - } - - if (!found) - return 0; - - if (!other) { - *a = dns_answer_unref(*a); /* Return NULL for the empty answer */ - return 1; - } - - if ((*a)->n_ref > 1) { - _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; - DnsAnswerFlags flags; - int ifindex; - - copy = dns_answer_new((*a)->n_rrs); - if (!copy) - return -ENOMEM; - - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) { - r = dns_resource_key_equal(rr->key, key); - if (r < 0) - return r; - if (r > 0) - continue; - - r = dns_answer_add_raw(copy, rr, ifindex, flags); - if (r < 0) - return r; - } - - dns_answer_unref(*a); - *a = copy; - copy = NULL; - - return 1; - } - - /* Only a single reference, edit in-place */ - - i = 0; - for (;;) { - if (i >= (*a)->n_rrs) - break; - - r = dns_resource_key_equal((*a)->items[i].rr->key, key); - if (r < 0) - return r; - if (r > 0) { - /* Kill this entry */ - - dns_resource_record_unref((*a)->items[i].rr); - memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1)); - (*a)->n_rrs--; - continue; - - } else - /* Keep this entry */ - i++; - } - - return 1; -} - -int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rm) { - bool found = false, other = false; - DnsResourceRecord *rr; - unsigned i; - int r; - - assert(a); - assert(rm); - - /* Remove all entries matching the specified RR from *a */ - - DNS_ANSWER_FOREACH(rr, *a) { - r = dns_resource_record_equal(rr, rm); - if (r < 0) - return r; - if (r > 0) - found = true; - else - other = true; - - if (found && other) - break; - } - - if (!found) - return 0; - - if (!other) { - *a = dns_answer_unref(*a); /* Return NULL for the empty answer */ - return 1; - } - - if ((*a)->n_ref > 1) { - _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; - DnsAnswerFlags flags; - int ifindex; - - copy = dns_answer_new((*a)->n_rrs); - if (!copy) - return -ENOMEM; - - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) { - r = dns_resource_record_equal(rr, rm); - if (r < 0) - return r; - if (r > 0) - continue; - - r = dns_answer_add_raw(copy, rr, ifindex, flags); - if (r < 0) - return r; - } - - dns_answer_unref(*a); - *a = copy; - copy = NULL; - - return 1; - } - - /* Only a single reference, edit in-place */ - - i = 0; - for (;;) { - if (i >= (*a)->n_rrs) - break; - - r = dns_resource_record_equal((*a)->items[i].rr, rm); - if (r < 0) - return r; - if (r > 0) { - /* Kill this entry */ - - dns_resource_record_unref((*a)->items[i].rr); - memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1)); - (*a)->n_rrs--; - continue; - - } else - /* Keep this entry */ - i++; - } - - return 1; -} - -int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags) { - DnsResourceRecord *rr_source; - int ifindex_source, r; - DnsAnswerFlags flags_source; - - assert(a); - assert(key); - - /* Copy all RRs matching the specified key from source into *a */ - - DNS_ANSWER_FOREACH_FULL(rr_source, ifindex_source, flags_source, source) { - - r = dns_resource_key_equal(rr_source->key, key); - if (r < 0) - return r; - if (r == 0) - continue; - - /* Make space for at least one entry */ - r = dns_answer_reserve_or_clone(a, 1); - if (r < 0) - return r; - - r = dns_answer_add(*a, rr_source, ifindex_source, flags_source|or_flags); - if (r < 0) - return r; - } - - return 0; -} - -int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags) { - int r; - - assert(to); - assert(from); - assert(key); - - r = dns_answer_copy_by_key(to, *from, key, or_flags); - if (r < 0) - return r; - - return dns_answer_remove_by_key(from, key); -} - -void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local) { - DnsAnswerItem *items; - unsigned i, start, end; - - if (!a) - return; - - if (a->n_rrs <= 1) - return; - - start = 0; - end = a->n_rrs-1; - - /* RFC 4795, Section 2.6 suggests we should order entries - * depending on whether the sender is a link-local address. */ - - items = newa(DnsAnswerItem, a->n_rrs); - for (i = 0; i < a->n_rrs; i++) { - - if (a->items[i].rr->key->class == DNS_CLASS_IN && - ((a->items[i].rr->key->type == DNS_TYPE_A && in_addr_is_link_local(AF_INET, (union in_addr_union*) &a->items[i].rr->a.in_addr) != prefer_link_local) || - (a->items[i].rr->key->type == DNS_TYPE_AAAA && in_addr_is_link_local(AF_INET6, (union in_addr_union*) &a->items[i].rr->aaaa.in6_addr) != prefer_link_local))) - /* Order address records that are are not preferred to the end of the array */ - items[end--] = a->items[i]; - else - /* Order all other records to the beginning of the array */ - items[start++] = a->items[i]; - } - - assert(start == end+1); - memcpy(a->items, items, sizeof(DnsAnswerItem) * a->n_rrs); -} - -int dns_answer_reserve(DnsAnswer **a, unsigned n_free) { - DnsAnswer *n; - - assert(a); - - if (n_free <= 0) - return 0; - - if (*a) { - unsigned ns; - - if ((*a)->n_ref > 1) - return -EBUSY; - - ns = (*a)->n_rrs + n_free; - - if ((*a)->n_allocated >= ns) - return 0; - - /* Allocate more than we need */ - ns *= 2; - - n = realloc(*a, offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * ns); - if (!n) - return -ENOMEM; - - n->n_allocated = ns; - } else { - n = dns_answer_new(n_free); - if (!n) - return -ENOMEM; - } - - *a = n; - return 0; -} - -int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free) { - _cleanup_(dns_answer_unrefp) DnsAnswer *n = NULL; - int r; - - assert(a); - - /* Tries to extend the DnsAnswer object. And if that's not - * possible, since we are not the sole owner, then allocate a - * new, appropriately sized one. Either way, after this call - * the object will only have a single reference, and has room - * for at least the specified number of RRs. */ - - r = dns_answer_reserve(a, n_free); - if (r != -EBUSY) - return r; - - assert(*a); - - n = dns_answer_new(((*a)->n_rrs + n_free) * 2); - if (!n) - return -ENOMEM; - - r = dns_answer_add_raw_all(n, *a); - if (r < 0) - return r; - - dns_answer_unref(*a); - *a = n; - n = NULL; - - return 0; -} - -void dns_answer_dump(DnsAnswer *answer, FILE *f) { - DnsResourceRecord *rr; - DnsAnswerFlags flags; - int ifindex; - - if (!f) - f = stdout; - - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) { - const char *t; - - fputc('\t', f); - - t = dns_resource_record_to_string(rr); - if (!t) { - log_oom(); - continue; - } - - fputs(t, f); - - if (ifindex != 0 || flags & (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE|DNS_ANSWER_SHARED_OWNER)) - fputs("\t;", f); - - if (ifindex != 0) - printf(" ifindex=%i", ifindex); - if (flags & DNS_ANSWER_AUTHENTICATED) - fputs(" authenticated", f); - if (flags & DNS_ANSWER_CACHEABLE) - fputs(" cachable", f); - if (flags & DNS_ANSWER_SHARED_OWNER) - fputs(" shared-owner", f); - - fputc('\n', f); - } -} - -bool dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname) { - DnsResourceRecord *rr; - int r; - - assert(cname); - - /* Checks whether the answer contains a DNAME record that indicates that the specified CNAME record is - * synthesized from it */ - - if (cname->key->type != DNS_TYPE_CNAME) - return 0; - - DNS_ANSWER_FOREACH(rr, a) { - _cleanup_free_ char *n = NULL; - - if (rr->key->type != DNS_TYPE_DNAME) - continue; - if (rr->key->class != cname->key->class) - continue; - - r = dns_name_change_suffix(cname->cname.name, rr->dname.name, dns_resource_key_name(rr->key), &n); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dns_name_equal(n, dns_resource_key_name(cname->key)); - if (r < 0) - return r; - if (r > 0) - return 1; - - } - - return 0; -} diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h deleted file mode 100644 index 0679c610f5..0000000000 --- a/src/resolve/resolved-dns-answer.h +++ /dev/null @@ -1,143 +0,0 @@ -#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 . -***/ - -typedef struct DnsAnswer DnsAnswer; -typedef struct DnsAnswerItem DnsAnswerItem; - -#include "macro.h" -#include "resolved-dns-rr.h" - -/* A simple array of resource records. We keep track of the - * originating ifindex for each RR where that makes sense, so that we - * can qualify A and AAAA RRs referring to a local link with the - * right ifindex. - * - * Note that we usually encode the empty DnsAnswer object as a simple NULL. */ - -typedef enum DnsAnswerFlags { - DNS_ANSWER_AUTHENTICATED = 1, /* Item has been authenticated */ - DNS_ANSWER_CACHEABLE = 2, /* Item is subject to caching */ - DNS_ANSWER_SHARED_OWNER = 4, /* For mDNS: RRset may be owner by multiple peers */ -} DnsAnswerFlags; - -struct DnsAnswerItem { - DnsResourceRecord *rr; - int ifindex; - DnsAnswerFlags flags; -}; - -struct DnsAnswer { - unsigned n_ref; - unsigned n_rrs, n_allocated; - DnsAnswerItem items[0]; -}; - -DnsAnswer *dns_answer_new(unsigned n); -DnsAnswer *dns_answer_ref(DnsAnswer *a); -DnsAnswer *dns_answer_unref(DnsAnswer *a); - -int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags); -int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags); -int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl); - -int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags); -int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr, DnsAnswerFlags *combined_flags); -int dns_answer_contains_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags); -int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a); -int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone); - -int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); -int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); - -int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret); -int dns_answer_extend(DnsAnswer **a, DnsAnswer *b); - -void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local); - -int dns_answer_reserve(DnsAnswer **a, unsigned n_free); -int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free); - -int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key); -int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rr); - -int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags); -int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags); - -bool dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname); - -static inline unsigned dns_answer_size(DnsAnswer *a) { - return a ? a->n_rrs : 0; -} - -void dns_answer_dump(DnsAnswer *answer, FILE *f); - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); - -#define _DNS_ANSWER_FOREACH(q, kk, a) \ - for (unsigned UNIQ_T(i, q) = ({ \ - (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ - 0; \ - }); \ - (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ - UNIQ_T(i, q)++, (kk) = (UNIQ_T(i, q) < (a)->n_rrs ? (a)->items[UNIQ_T(i, q)].rr : NULL)) - -#define DNS_ANSWER_FOREACH(kk, a) _DNS_ANSWER_FOREACH(UNIQ, kk, a) - -#define _DNS_ANSWER_FOREACH_IFINDEX(q, kk, ifi, a) \ - for (unsigned UNIQ_T(i, q) = ({ \ - (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ - (ifi) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \ - 0; \ - }); \ - (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ - UNIQ_T(i, q)++, \ - (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ - (ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0)) - -#define DNS_ANSWER_FOREACH_IFINDEX(kk, ifindex, a) _DNS_ANSWER_FOREACH_IFINDEX(UNIQ, kk, ifindex, a) - -#define _DNS_ANSWER_FOREACH_FLAGS(q, kk, fl, a) \ - for (unsigned UNIQ_T(i, q) = ({ \ - (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ - (fl) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].flags : 0; \ - 0; \ - }); \ - (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ - UNIQ_T(i, q)++, \ - (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ - (fl) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].flags : 0)) - -#define DNS_ANSWER_FOREACH_FLAGS(kk, flags, a) _DNS_ANSWER_FOREACH_FLAGS(UNIQ, kk, flags, a) - -#define _DNS_ANSWER_FOREACH_FULL(q, kk, ifi, fl, a) \ - for (unsigned UNIQ_T(i, q) = ({ \ - (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ - (ifi) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \ - (fl) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].flags : 0; \ - 0; \ - }); \ - (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ - UNIQ_T(i, q)++, \ - (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ - (ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0), \ - (fl) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].flags : 0)) - -#define DNS_ANSWER_FOREACH_FULL(kk, ifindex, flags, a) _DNS_ANSWER_FOREACH_FULL(UNIQ, kk, ifindex, flags, a) diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c deleted file mode 100644 index 77c42d7aad..0000000000 --- a/src/resolve/resolved-dns-cache.c +++ /dev/null @@ -1,1050 +0,0 @@ -/*** - 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 "af-list.h" -#include "alloc-util.h" -#include "dns-domain.h" -#include "resolved-dns-answer.h" -#include "resolved-dns-cache.h" -#include "resolved-dns-packet.h" -#include "string-util.h" - -/* Never cache more than 4K entries. RFC 1536, Section 5 suggests to - * leave DNS caches unbounded, but that's crazy. */ -#define CACHE_MAX 4096 - -/* We never keep any item longer than 2h in our cache */ -#define CACHE_TTL_MAX_USEC (2 * USEC_PER_HOUR) - -typedef enum DnsCacheItemType DnsCacheItemType; -typedef struct DnsCacheItem DnsCacheItem; - -enum DnsCacheItemType { - DNS_CACHE_POSITIVE, - DNS_CACHE_NODATA, - DNS_CACHE_NXDOMAIN, -}; - -struct DnsCacheItem { - DnsCacheItemType type; - DnsResourceKey *key; - DnsResourceRecord *rr; - - usec_t until; - bool authenticated:1; - bool shared_owner:1; - - int ifindex; - int owner_family; - union in_addr_union owner_address; - - unsigned prioq_idx; - LIST_FIELDS(DnsCacheItem, by_key); -}; - -static void dns_cache_item_free(DnsCacheItem *i) { - if (!i) - return; - - dns_resource_record_unref(i->rr); - dns_resource_key_unref(i->key); - free(i); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem*, dns_cache_item_free); - -static void dns_cache_item_unlink_and_free(DnsCache *c, DnsCacheItem *i) { - DnsCacheItem *first; - - assert(c); - - if (!i) - return; - - first = hashmap_get(c->by_key, i->key); - LIST_REMOVE(by_key, first, i); - - if (first) - assert_se(hashmap_replace(c->by_key, first->key, first) >= 0); - else - hashmap_remove(c->by_key, i->key); - - prioq_remove(c->by_expiry, i, &i->prioq_idx); - - dns_cache_item_free(i); -} - -static bool dns_cache_remove_by_rr(DnsCache *c, DnsResourceRecord *rr) { - DnsCacheItem *first, *i; - int r; - - first = hashmap_get(c->by_key, rr->key); - LIST_FOREACH(by_key, i, first) { - r = dns_resource_record_equal(i->rr, rr); - if (r < 0) - return r; - if (r > 0) { - dns_cache_item_unlink_and_free(c, i); - return true; - } - } - - return false; -} - -static bool dns_cache_remove_by_key(DnsCache *c, DnsResourceKey *key) { - DnsCacheItem *first, *i, *n; - - assert(c); - assert(key); - - first = hashmap_remove(c->by_key, key); - if (!first) - return false; - - LIST_FOREACH_SAFE(by_key, i, n, first) { - prioq_remove(c->by_expiry, i, &i->prioq_idx); - dns_cache_item_free(i); - } - - return true; -} - -void dns_cache_flush(DnsCache *c) { - DnsResourceKey *key; - - assert(c); - - while ((key = hashmap_first_key(c->by_key))) - dns_cache_remove_by_key(c, key); - - assert(hashmap_size(c->by_key) == 0); - assert(prioq_size(c->by_expiry) == 0); - - c->by_key = hashmap_free(c->by_key); - c->by_expiry = prioq_free(c->by_expiry); -} - -static void dns_cache_make_space(DnsCache *c, unsigned add) { - assert(c); - - if (add <= 0) - return; - - /* Makes space for n new entries. Note that we actually allow - * the cache to grow beyond CACHE_MAX, but only when we shall - * add more RRs to the cache than CACHE_MAX at once. In that - * case the cache will be emptied completely otherwise. */ - - for (;;) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - DnsCacheItem *i; - - if (prioq_size(c->by_expiry) <= 0) - break; - - if (prioq_size(c->by_expiry) + add < CACHE_MAX) - break; - - i = prioq_peek(c->by_expiry); - assert(i); - - /* Take an extra reference to the key so that it - * doesn't go away in the middle of the remove call */ - key = dns_resource_key_ref(i->key); - dns_cache_remove_by_key(c, key); - } -} - -void dns_cache_prune(DnsCache *c) { - usec_t t = 0; - - assert(c); - - /* Remove all entries that are past their TTL */ - - for (;;) { - DnsCacheItem *i; - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - - i = prioq_peek(c->by_expiry); - if (!i) - break; - - if (t <= 0) - t = now(clock_boottime_or_monotonic()); - - if (i->until > t) - break; - - /* Depending whether this is an mDNS shared entry - * either remove only this one RR or the whole RRset */ - log_debug("Removing %scache entry for %s (expired "USEC_FMT"s ago)", - i->shared_owner ? "shared " : "", - dns_resource_key_to_string(i->key, key_str, sizeof key_str), - (t - i->until) / USEC_PER_SEC); - - if (i->shared_owner) - dns_cache_item_unlink_and_free(c, i); - else { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - /* Take an extra reference to the key so that it - * doesn't go away in the middle of the remove call */ - key = dns_resource_key_ref(i->key); - dns_cache_remove_by_key(c, key); - } - } -} - -static int dns_cache_item_prioq_compare_func(const void *a, const void *b) { - const DnsCacheItem *x = a, *y = b; - - if (x->until < y->until) - return -1; - if (x->until > y->until) - return 1; - return 0; -} - -static int dns_cache_init(DnsCache *c) { - int r; - - assert(c); - - r = prioq_ensure_allocated(&c->by_expiry, dns_cache_item_prioq_compare_func); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&c->by_key, &dns_resource_key_hash_ops); - if (r < 0) - return r; - - return r; -} - -static int dns_cache_link_item(DnsCache *c, DnsCacheItem *i) { - DnsCacheItem *first; - int r; - - assert(c); - assert(i); - - r = prioq_put(c->by_expiry, i, &i->prioq_idx); - if (r < 0) - return r; - - first = hashmap_get(c->by_key, i->key); - if (first) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; - - /* Keep a reference to the original key, while we manipulate the list. */ - k = dns_resource_key_ref(first->key); - - /* Now, try to reduce the number of keys we keep */ - dns_resource_key_reduce(&first->key, &i->key); - - if (first->rr) - dns_resource_key_reduce(&first->rr->key, &i->key); - if (i->rr) - dns_resource_key_reduce(&i->rr->key, &i->key); - - LIST_PREPEND(by_key, first, i); - assert_se(hashmap_replace(c->by_key, first->key, first) >= 0); - } else { - r = hashmap_put(c->by_key, i->key, i); - if (r < 0) { - prioq_remove(c->by_expiry, i, &i->prioq_idx); - return r; - } - } - - return 0; -} - -static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) { - DnsCacheItem *i; - - assert(c); - assert(rr); - - LIST_FOREACH(by_key, i, hashmap_get(c->by_key, rr->key)) - if (i->rr && dns_resource_record_equal(i->rr, rr) > 0) - return i; - - return NULL; -} - -static usec_t calculate_until(DnsResourceRecord *rr, uint32_t nsec_ttl, usec_t timestamp, bool use_soa_minimum) { - uint32_t ttl; - usec_t u; - - assert(rr); - - ttl = MIN(rr->ttl, nsec_ttl); - if (rr->key->type == DNS_TYPE_SOA && use_soa_minimum) { - /* If this is a SOA RR, and it is requested, clamp to - * the SOA's minimum field. This is used when we do - * negative caching, to determine the TTL for the - * negative caching entry. See RFC 2308, Section - * 5. */ - - if (ttl > rr->soa.minimum) - ttl = rr->soa.minimum; - } - - u = ttl * USEC_PER_SEC; - if (u > CACHE_TTL_MAX_USEC) - u = CACHE_TTL_MAX_USEC; - - if (rr->expiry != USEC_INFINITY) { - usec_t left; - - /* Make use of the DNSSEC RRSIG expiry info, if we - * have it */ - - left = LESS_BY(rr->expiry, now(CLOCK_REALTIME)); - if (u > left) - u = left; - } - - return timestamp + u; -} - -static void dns_cache_item_update_positive( - DnsCache *c, - DnsCacheItem *i, - DnsResourceRecord *rr, - bool authenticated, - bool shared_owner, - usec_t timestamp, - int ifindex, - int owner_family, - const union in_addr_union *owner_address) { - - assert(c); - assert(i); - assert(rr); - assert(owner_address); - - i->type = DNS_CACHE_POSITIVE; - - if (!i->by_key_prev) - /* We are the first item in the list, we need to - * update the key used in the hashmap */ - - assert_se(hashmap_replace(c->by_key, rr->key, i) >= 0); - - dns_resource_record_ref(rr); - dns_resource_record_unref(i->rr); - i->rr = rr; - - dns_resource_key_unref(i->key); - i->key = dns_resource_key_ref(rr->key); - - i->until = calculate_until(rr, (uint32_t) -1, timestamp, false); - i->authenticated = authenticated; - i->shared_owner = shared_owner; - - i->ifindex = ifindex; - - i->owner_family = owner_family; - i->owner_address = *owner_address; - - prioq_reshuffle(c->by_expiry, i, &i->prioq_idx); -} - -static int dns_cache_put_positive( - DnsCache *c, - DnsResourceRecord *rr, - bool authenticated, - bool shared_owner, - usec_t timestamp, - int ifindex, - int owner_family, - const union in_addr_union *owner_address) { - - _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; - DnsCacheItem *existing; - char key_str[DNS_RESOURCE_KEY_STRING_MAX], ifname[IF_NAMESIZE]; - int r, k; - - assert(c); - assert(rr); - assert(owner_address); - - /* Never cache pseudo RRs */ - if (dns_class_is_pseudo(rr->key->class)) - return 0; - if (dns_type_is_pseudo(rr->key->type)) - return 0; - - /* New TTL is 0? Delete this specific entry... */ - if (rr->ttl <= 0) { - k = dns_cache_remove_by_rr(c, rr); - log_debug("%s: %s", - k > 0 ? "Removed zero TTL entry from cache" : "Not caching zero TTL cache entry", - dns_resource_key_to_string(rr->key, key_str, sizeof key_str)); - return 0; - } - - /* Entry exists already? Update TTL, timestamp and owner*/ - existing = dns_cache_get(c, rr); - if (existing) { - dns_cache_item_update_positive( - c, - existing, - rr, - authenticated, - shared_owner, - timestamp, - ifindex, - owner_family, - owner_address); - return 0; - } - - /* Otherwise, add the new RR */ - r = dns_cache_init(c); - if (r < 0) - return r; - - dns_cache_make_space(c, 1); - - i = new0(DnsCacheItem, 1); - if (!i) - return -ENOMEM; - - i->type = DNS_CACHE_POSITIVE; - i->key = dns_resource_key_ref(rr->key); - i->rr = dns_resource_record_ref(rr); - i->until = calculate_until(rr, (uint32_t) -1, timestamp, false); - i->authenticated = authenticated; - i->shared_owner = shared_owner; - i->ifindex = ifindex; - i->owner_family = owner_family; - i->owner_address = *owner_address; - i->prioq_idx = PRIOQ_IDX_NULL; - - r = dns_cache_link_item(c, i); - if (r < 0) - return r; - - if (log_get_max_level() >= LOG_DEBUG) { - _cleanup_free_ char *t = NULL; - - (void) in_addr_to_string(i->owner_family, &i->owner_address, &t); - - log_debug("Added positive %s%s cache entry for %s "USEC_FMT"s on %s/%s/%s", - i->authenticated ? "authenticated" : "unauthenticated", - i->shared_owner ? " shared" : "", - dns_resource_key_to_string(i->key, key_str, sizeof key_str), - (i->until - timestamp) / USEC_PER_SEC, - i->ifindex == 0 ? "*" : strna(if_indextoname(i->ifindex, ifname)), - af_to_name_short(i->owner_family), - strna(t)); - } - - i = NULL; - return 0; -} - -static int dns_cache_put_negative( - DnsCache *c, - DnsResourceKey *key, - int rcode, - bool authenticated, - uint32_t nsec_ttl, - usec_t timestamp, - DnsResourceRecord *soa, - int owner_family, - const union in_addr_union *owner_address) { - - _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - int r; - - assert(c); - assert(key); - assert(soa); - assert(owner_address); - - /* Never cache pseudo RR keys. DNS_TYPE_ANY is particularly - * important to filter out as we use this as a pseudo-type for - * NXDOMAIN entries */ - if (dns_class_is_pseudo(key->class)) - return 0; - if (dns_type_is_pseudo(key->type)) - return 0; - - if (nsec_ttl <= 0 || soa->soa.minimum <= 0 || soa->ttl <= 0) { - log_debug("Not caching negative entry with zero SOA/NSEC/NSEC3 TTL: %s", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - return 0; - } - - if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) - return 0; - - r = dns_cache_init(c); - if (r < 0) - return r; - - dns_cache_make_space(c, 1); - - i = new0(DnsCacheItem, 1); - if (!i) - return -ENOMEM; - - i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN; - i->until = calculate_until(soa, nsec_ttl, timestamp, true); - i->authenticated = authenticated; - i->owner_family = owner_family; - i->owner_address = *owner_address; - i->prioq_idx = PRIOQ_IDX_NULL; - - if (i->type == DNS_CACHE_NXDOMAIN) { - /* NXDOMAIN entries should apply equally to all types, so we use ANY as - * a pseudo type for this purpose here. */ - i->key = dns_resource_key_new(key->class, DNS_TYPE_ANY, dns_resource_key_name(key)); - if (!i->key) - return -ENOMEM; - - /* Make sure to remove any previous entry for this - * specific ANY key. (For non-ANY keys the cache data - * is already cleared by the caller.) Note that we - * don't bother removing positive or NODATA cache - * items in this case, because it would either be slow - * or require explicit indexing by name */ - dns_cache_remove_by_key(c, key); - } else - i->key = dns_resource_key_ref(key); - - r = dns_cache_link_item(c, i); - if (r < 0) - return r; - - log_debug("Added %s cache entry for %s "USEC_FMT"s", - i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", - dns_resource_key_to_string(i->key, key_str, sizeof key_str), - (i->until - timestamp) / USEC_PER_SEC); - - i = NULL; - return 0; -} - -static void dns_cache_remove_previous( - DnsCache *c, - DnsResourceKey *key, - DnsAnswer *answer) { - - DnsResourceRecord *rr; - DnsAnswerFlags flags; - - assert(c); - - /* First, if we were passed a key (i.e. on LLMNR/DNS, but - * not on mDNS), delete all matching old RRs, so that we only - * keep complete by_key in place. */ - if (key) - dns_cache_remove_by_key(c, key); - - /* Second, flush all entries matching the answer, unless this - * is an RR that is explicitly marked to be "shared" between - * peers (i.e. mDNS RRs without the flush-cache bit set). */ - DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { - if ((flags & DNS_ANSWER_CACHEABLE) == 0) - continue; - - if (flags & DNS_ANSWER_SHARED_OWNER) - continue; - - dns_cache_remove_by_key(c, rr->key); - } -} - -static bool rr_eligible(DnsResourceRecord *rr) { - assert(rr); - - /* When we see an NSEC/NSEC3 RR, we'll only cache it if it is from the lower zone, not the upper zone, since - * that's where the interesting bits are (with exception of DS RRs). Of course, this way we cannot derive DS - * existence from any cached NSEC/NSEC3, but that should be fine. */ - - switch (rr->key->type) { - - case DNS_TYPE_NSEC: - return !bitmap_isset(rr->nsec.types, DNS_TYPE_NS) || - bitmap_isset(rr->nsec.types, DNS_TYPE_SOA); - - case DNS_TYPE_NSEC3: - return !bitmap_isset(rr->nsec3.types, DNS_TYPE_NS) || - bitmap_isset(rr->nsec3.types, DNS_TYPE_SOA); - - default: - return true; - } -} - -int dns_cache_put( - DnsCache *c, - DnsResourceKey *key, - int rcode, - DnsAnswer *answer, - bool authenticated, - uint32_t nsec_ttl, - usec_t timestamp, - int owner_family, - const union in_addr_union *owner_address) { - - DnsResourceRecord *soa = NULL, *rr; - DnsAnswerFlags flags; - unsigned cache_keys; - int r, ifindex; - - assert(c); - assert(owner_address); - - dns_cache_remove_previous(c, key, answer); - - if (dns_answer_size(answer) <= 0) { - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - - log_debug("Not caching negative entry without a SOA record: %s", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - return 0; - } - - /* We only care for positive replies and NXDOMAINs, on all - * other replies we will simply flush the respective entries, - * and that's it */ - if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) - return 0; - - cache_keys = dns_answer_size(answer); - if (key) - cache_keys++; - - /* Make some space for our new entries */ - dns_cache_make_space(c, cache_keys); - - if (timestamp <= 0) - timestamp = now(clock_boottime_or_monotonic()); - - /* Second, add in positive entries for all contained RRs */ - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) { - if ((flags & DNS_ANSWER_CACHEABLE) == 0) - continue; - - r = rr_eligible(rr); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dns_cache_put_positive( - c, - rr, - flags & DNS_ANSWER_AUTHENTICATED, - flags & DNS_ANSWER_SHARED_OWNER, - timestamp, - ifindex, - owner_family, owner_address); - if (r < 0) - goto fail; - } - - if (!key) /* mDNS doesn't know negative caching, really */ - return 0; - - /* Third, add in negative entries if the key has no RR */ - r = dns_answer_match_key(answer, key, NULL); - if (r < 0) - goto fail; - if (r > 0) - return 0; - - /* But not if it has a matching CNAME/DNAME (the negative - * caching will be done on the canonical name, not on the - * alias) */ - r = dns_answer_find_cname_or_dname(answer, key, NULL, NULL); - if (r < 0) - goto fail; - if (r > 0) - return 0; - - /* See https://tools.ietf.org/html/rfc2308, which say that a - * matching SOA record in the packet is used to to enable - * negative caching. */ - r = dns_answer_find_soa(answer, key, &soa, &flags); - if (r < 0) - goto fail; - if (r == 0) - return 0; - - /* Refuse using the SOA data if it is unsigned, but the key is - * signed */ - if (authenticated && (flags & DNS_ANSWER_AUTHENTICATED) == 0) - return 0; - - r = dns_cache_put_negative( - c, - key, - rcode, - authenticated, - nsec_ttl, - timestamp, - soa, - owner_family, owner_address); - if (r < 0) - goto fail; - - return 0; - -fail: - /* Adding all RRs failed. Let's clean up what we already - * added, just in case */ - - if (key) - dns_cache_remove_by_key(c, key); - - DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { - if ((flags & DNS_ANSWER_CACHEABLE) == 0) - continue; - - dns_cache_remove_by_key(c, rr->key); - } - - return r; -} - -static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, DnsResourceKey *k) { - DnsCacheItem *i; - const char *n; - int r; - - assert(c); - assert(k); - - /* If we hit some OOM error, or suchlike, we don't care too - * much, after all this is just a cache */ - - i = hashmap_get(c->by_key, k); - if (i) - return i; - - n = dns_resource_key_name(k); - - /* Check if we have an NXDOMAIN cache item for the name, notice that we use - * the pseudo-type ANY for NXDOMAIN cache items. */ - i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_ANY, n)); - if (i && i->type == DNS_CACHE_NXDOMAIN) - return i; - - if (dns_type_may_redirect(k->type)) { - /* Check if we have a CNAME record instead */ - i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_CNAME, n)); - if (i) - return i; - - /* OK, let's look for cached DNAME records. */ - for (;;) { - if (isempty(n)) - return NULL; - - i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_DNAME, n)); - if (i) - return i; - - /* Jump one label ahead */ - r = dns_name_parent(&n); - if (r <= 0) - return NULL; - } - } - - if (k->type != DNS_TYPE_NSEC) { - /* Check if we have an NSEC record instead for the name. */ - i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_NSEC, n)); - if (i) - return i; - } - - return NULL; -} - -int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **ret, bool *authenticated) { - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - unsigned n = 0; - int r; - bool nxdomain = false; - DnsCacheItem *j, *first, *nsec = NULL; - bool have_authenticated = false, have_non_authenticated = false; - - assert(c); - assert(key); - assert(rcode); - assert(ret); - assert(authenticated); - - if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { - /* If we have ANY lookups we don't use the cache, so - * that the caller refreshes via the network. */ - - log_debug("Ignoring cache for ANY lookup: %s", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - - c->n_miss++; - - *ret = NULL; - *rcode = DNS_RCODE_SUCCESS; - return 0; - } - - first = dns_cache_get_by_key_follow_cname_dname_nsec(c, key); - if (!first) { - /* If one question cannot be answered we need to refresh */ - - log_debug("Cache miss for %s", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - - c->n_miss++; - - *ret = NULL; - *rcode = DNS_RCODE_SUCCESS; - return 0; - } - - LIST_FOREACH(by_key, j, first) { - if (j->rr) { - if (j->rr->key->type == DNS_TYPE_NSEC) - nsec = j; - - n++; - } else if (j->type == DNS_CACHE_NXDOMAIN) - nxdomain = true; - - if (j->authenticated) - have_authenticated = true; - else - have_non_authenticated = true; - } - - if (nsec && !IN_SET(key->type, DNS_TYPE_NSEC, DNS_TYPE_DS)) { - /* Note that we won't derive information for DS RRs from an NSEC, because we only cache NSEC RRs from - * the lower-zone of a zone cut, but the DS RRs are on the upper zone. */ - - log_debug("NSEC NODATA cache hit for %s", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - - /* We only found an NSEC record that matches our name. - * If it says the type doesn't exist report - * NODATA. Otherwise report a cache miss. */ - - *ret = NULL; - *rcode = DNS_RCODE_SUCCESS; - *authenticated = nsec->authenticated; - - if (!bitmap_isset(nsec->rr->nsec.types, key->type) && - !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) && - !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_DNAME)) { - c->n_hit++; - return 1; - } - - c->n_miss++; - return 0; - } - - log_debug("%s cache hit for %s", - n > 0 ? "Positive" : - nxdomain ? "NXDOMAIN" : "NODATA", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - - if (n <= 0) { - c->n_hit++; - - *ret = NULL; - *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS; - *authenticated = have_authenticated && !have_non_authenticated; - return 1; - } - - answer = dns_answer_new(n); - if (!answer) - return -ENOMEM; - - LIST_FOREACH(by_key, j, first) { - if (!j->rr) - continue; - - r = dns_answer_add(answer, j->rr, j->ifindex, j->authenticated ? DNS_ANSWER_AUTHENTICATED : 0); - if (r < 0) - return r; - } - - c->n_hit++; - - *ret = answer; - *rcode = DNS_RCODE_SUCCESS; - *authenticated = have_authenticated && !have_non_authenticated; - answer = NULL; - - return n; -} - -int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) { - DnsCacheItem *i, *first; - bool same_owner = true; - - assert(cache); - assert(rr); - - dns_cache_prune(cache); - - /* See if there's a cache entry for the same key. If there - * isn't there's no conflict */ - first = hashmap_get(cache->by_key, rr->key); - if (!first) - return 0; - - /* See if the RR key is owned by the same owner, if so, there - * isn't a conflict either */ - LIST_FOREACH(by_key, i, first) { - if (i->owner_family != owner_family || - !in_addr_equal(owner_family, &i->owner_address, owner_address)) { - same_owner = false; - break; - } - } - if (same_owner) - return 0; - - /* See if there's the exact same RR in the cache. If yes, then - * there's no conflict. */ - if (dns_cache_get(cache, rr)) - return 0; - - /* There's a conflict */ - return 1; -} - -int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p) { - unsigned ancount = 0; - Iterator iterator; - DnsCacheItem *i; - int r; - - assert(cache); - assert(p); - - HASHMAP_FOREACH(i, cache->by_key, iterator) { - DnsCacheItem *j; - - LIST_FOREACH(by_key, j, i) { - if (!j->rr) - continue; - - if (!j->shared_owner) - continue; - - r = dns_packet_append_rr(p, j->rr, NULL, NULL); - if (r == -EMSGSIZE && p->protocol == DNS_PROTOCOL_MDNS) { - /* For mDNS, if we're unable to stuff all known answers into the given packet, - * allocate a new one, push the RR into that one and link it to the current one. - */ - - DNS_PACKET_HEADER(p)->ancount = htobe16(ancount); - ancount = 0; - - r = dns_packet_new_query(&p->more, p->protocol, 0, true); - if (r < 0) - return r; - - /* continue with new packet */ - p = p->more; - r = dns_packet_append_rr(p, j->rr, NULL, NULL); - } - - if (r < 0) - return r; - - ancount++; - } - } - - DNS_PACKET_HEADER(p)->ancount = htobe16(ancount); - - return 0; -} - -void dns_cache_dump(DnsCache *cache, FILE *f) { - Iterator iterator; - DnsCacheItem *i; - - if (!cache) - return; - - if (!f) - f = stdout; - - HASHMAP_FOREACH(i, cache->by_key, iterator) { - DnsCacheItem *j; - - LIST_FOREACH(by_key, j, i) { - - fputc('\t', f); - - if (j->rr) { - const char *t; - t = dns_resource_record_to_string(j->rr); - if (!t) { - log_oom(); - continue; - } - - fputs(t, f); - fputc('\n', f); - } else { - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - - fputs(dns_resource_key_to_string(j->key, key_str, sizeof key_str), f); - fputs(" -- ", f); - fputs(j->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", f); - fputc('\n', f); - } - } - } -} - -bool dns_cache_is_empty(DnsCache *cache) { - if (!cache) - return true; - - return hashmap_isempty(cache->by_key); -} - -unsigned dns_cache_size(DnsCache *cache) { - if (!cache) - return 0; - - return hashmap_size(cache->by_key); -} diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h deleted file mode 100644 index 2293718e86..0000000000 --- a/src/resolve/resolved-dns-cache.h +++ /dev/null @@ -1,52 +0,0 @@ -#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 "hashmap.h" -#include "list.h" -#include "prioq.h" -#include "time-util.h" - -typedef struct DnsCache { - Hashmap *by_key; - Prioq *by_expiry; - unsigned n_hit; - unsigned n_miss; -} DnsCache; - -#include "resolved-dns-answer.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-question.h" -#include "resolved-dns-rr.h" - -void dns_cache_flush(DnsCache *c); -void dns_cache_prune(DnsCache *c); - -int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, bool authenticated, uint32_t nsec_ttl, usec_t timestamp, int owner_family, const union in_addr_union *owner_address); -int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **answer, bool *authenticated); - -int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address); - -void dns_cache_dump(DnsCache *cache, FILE *f); -bool dns_cache_is_empty(DnsCache *cache); - -unsigned dns_cache_size(DnsCache *cache); - -int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p); diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c deleted file mode 100644 index a54aed3a63..0000000000 --- a/src/resolve/resolved-dns-dnssec.c +++ /dev/null @@ -1,2199 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#ifdef HAVE_GCRYPT -#include -#endif - -#include "alloc-util.h" -#include "dns-domain.h" -#include "gcrypt-util.h" -#include "hexdecoct.h" -#include "resolved-dns-dnssec.h" -#include "resolved-dns-packet.h" -#include "string-table.h" - -#define VERIFY_RRS_MAX 256 -#define MAX_KEY_SIZE (32*1024) - -/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */ -#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE) - -/* Maximum number of NSEC3 iterations we'll do. RFC5155 says 2500 shall be the maximum useful value */ -#define NSEC3_ITERATIONS_MAX 2500 - -/* - * The DNSSEC Chain of trust: - * - * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone - * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree - * DS RRs are protected like normal RRs - * - * Example chain: - * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS - */ - -uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) { - const uint8_t *p; - uint32_t sum, f; - size_t i; - - /* The algorithm from RFC 4034, Appendix B. */ - - assert(dnskey); - assert(dnskey->key->type == DNS_TYPE_DNSKEY); - - f = (uint32_t) dnskey->dnskey.flags; - - if (mask_revoke) - f &= ~DNSKEY_FLAG_REVOKE; - - sum = f + ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm); - - p = dnskey->dnskey.key; - - for (i = 0; i < dnskey->dnskey.key_size; i++) - sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i]; - - sum += (sum >> 16) & UINT32_C(0xFFFF); - - return sum & UINT32_C(0xFFFF); -} - -int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) { - size_t c = 0; - int r; - - /* Converts the specified hostname into DNSSEC canonicalized - * form. */ - - if (buffer_max < 2) - return -ENOBUFS; - - for (;;) { - r = dns_label_unescape(&n, buffer, buffer_max); - if (r < 0) - return r; - if (r == 0) - break; - - if (buffer_max < (size_t) r + 2) - return -ENOBUFS; - - /* The DNSSEC canonical form is not clear on what to - * do with dots appearing in labels, the way DNS-SD - * does it. Refuse it for now. */ - - if (memchr(buffer, '.', r)) - return -EINVAL; - - ascii_strlower_n(buffer, (size_t) r); - buffer[r] = '.'; - - buffer += r + 1; - c += r + 1; - - buffer_max -= r + 1; - } - - if (c <= 0) { - /* Not even a single label: this is the root domain name */ - - assert(buffer_max > 2); - buffer[0] = '.'; - buffer[1] = 0; - - return 1; - } - - return (int) c; -} - -#ifdef HAVE_GCRYPT - -static int rr_compare(const void *a, const void *b) { - DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b; - size_t m; - int r; - - /* Let's order the RRs according to RFC 4034, Section 6.3 */ - - assert(x); - assert(*x); - assert((*x)->wire_format); - assert(y); - assert(*y); - assert((*y)->wire_format); - - m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y)); - - r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m); - if (r != 0) - return r; - - if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) - return -1; - else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) - return 1; - - return 0; -} - -static int dnssec_rsa_verify_raw( - const char *hash_algorithm, - const void *signature, size_t signature_size, - const void *data, size_t data_size, - const void *exponent, size_t exponent_size, - const void *modulus, size_t modulus_size) { - - gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL; - gcry_mpi_t n = NULL, e = NULL, s = NULL; - gcry_error_t ge; - int r; - - assert(hash_algorithm); - - ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&signature_sexp, - NULL, - "(sig-val (rsa (s %m)))", - s); - - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&data_sexp, - NULL, - "(data (flags pkcs1) (hash %s %b))", - hash_algorithm, - (int) data_size, - data); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&public_key_sexp, - NULL, - "(public-key (rsa (n %m) (e %m)))", - n, - e); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp); - if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE) - r = 0; - else if (ge != 0) { - log_debug("RSA signature check failed: %s", gpg_strerror(ge)); - r = -EIO; - } else - r = 1; - -finish: - if (e) - gcry_mpi_release(e); - if (n) - gcry_mpi_release(n); - if (s) - gcry_mpi_release(s); - - if (public_key_sexp) - gcry_sexp_release(public_key_sexp); - if (signature_sexp) - gcry_sexp_release(signature_sexp); - if (data_sexp) - gcry_sexp_release(data_sexp); - - return r; -} - -static int dnssec_rsa_verify( - const char *hash_algorithm, - const void *hash, size_t hash_size, - DnsResourceRecord *rrsig, - DnsResourceRecord *dnskey) { - - size_t exponent_size, modulus_size; - void *exponent, *modulus; - - assert(hash_algorithm); - assert(hash); - assert(hash_size > 0); - assert(rrsig); - assert(dnskey); - - if (*(uint8_t*) dnskey->dnskey.key == 0) { - /* exponent is > 255 bytes long */ - - exponent = (uint8_t*) dnskey->dnskey.key + 3; - exponent_size = - ((size_t) (((uint8_t*) dnskey->dnskey.key)[1]) << 8) | - ((size_t) ((uint8_t*) dnskey->dnskey.key)[2]); - - if (exponent_size < 256) - return -EINVAL; - - if (3 + exponent_size >= dnskey->dnskey.key_size) - return -EINVAL; - - modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size; - modulus_size = dnskey->dnskey.key_size - 3 - exponent_size; - - } else { - /* exponent is <= 255 bytes long */ - - exponent = (uint8_t*) dnskey->dnskey.key + 1; - exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0]; - - if (exponent_size <= 0) - return -EINVAL; - - if (1 + exponent_size >= dnskey->dnskey.key_size) - return -EINVAL; - - modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size; - modulus_size = dnskey->dnskey.key_size - 1 - exponent_size; - } - - return dnssec_rsa_verify_raw( - hash_algorithm, - rrsig->rrsig.signature, rrsig->rrsig.signature_size, - hash, hash_size, - exponent, exponent_size, - modulus, modulus_size); -} - -static int dnssec_ecdsa_verify_raw( - const char *hash_algorithm, - const char *curve, - const void *signature_r, size_t signature_r_size, - const void *signature_s, size_t signature_s_size, - const void *data, size_t data_size, - const void *key, size_t key_size) { - - gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL; - gcry_mpi_t q = NULL, r = NULL, s = NULL; - gcry_error_t ge; - int k; - - assert(hash_algorithm); - - ge = gcry_mpi_scan(&r, GCRYMPI_FMT_USG, signature_r, signature_r_size, NULL); - if (ge != 0) { - k = -EIO; - goto finish; - } - - ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature_s, signature_s_size, NULL); - if (ge != 0) { - k = -EIO; - goto finish; - } - - ge = gcry_mpi_scan(&q, GCRYMPI_FMT_USG, key, key_size, NULL); - if (ge != 0) { - k = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&signature_sexp, - NULL, - "(sig-val (ecdsa (r %m) (s %m)))", - r, - s); - if (ge != 0) { - k = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&data_sexp, - NULL, - "(data (flags rfc6979) (hash %s %b))", - hash_algorithm, - (int) data_size, - data); - if (ge != 0) { - k = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&public_key_sexp, - NULL, - "(public-key (ecc (curve %s) (q %m)))", - curve, - q); - if (ge != 0) { - k = -EIO; - goto finish; - } - - ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp); - if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE) - k = 0; - else if (ge != 0) { - log_debug("ECDSA signature check failed: %s", gpg_strerror(ge)); - k = -EIO; - } else - k = 1; -finish: - if (r) - gcry_mpi_release(r); - if (s) - gcry_mpi_release(s); - if (q) - gcry_mpi_release(q); - - if (public_key_sexp) - gcry_sexp_release(public_key_sexp); - if (signature_sexp) - gcry_sexp_release(signature_sexp); - if (data_sexp) - gcry_sexp_release(data_sexp); - - return k; -} - -static int dnssec_ecdsa_verify( - const char *hash_algorithm, - int algorithm, - const void *hash, size_t hash_size, - DnsResourceRecord *rrsig, - DnsResourceRecord *dnskey) { - - const char *curve; - size_t key_size; - uint8_t *q; - - assert(hash); - assert(hash_size); - assert(rrsig); - assert(dnskey); - - if (algorithm == DNSSEC_ALGORITHM_ECDSAP256SHA256) { - key_size = 32; - curve = "NIST P-256"; - } else if (algorithm == DNSSEC_ALGORITHM_ECDSAP384SHA384) { - key_size = 48; - curve = "NIST P-384"; - } else - return -EOPNOTSUPP; - - if (dnskey->dnskey.key_size != key_size * 2) - return -EINVAL; - - if (rrsig->rrsig.signature_size != key_size * 2) - return -EINVAL; - - q = alloca(key_size*2 + 1); - q[0] = 0x04; /* Prepend 0x04 to indicate an uncompressed key */ - memcpy(q+1, dnskey->dnskey.key, key_size*2); - - return dnssec_ecdsa_verify_raw( - hash_algorithm, - curve, - rrsig->rrsig.signature, key_size, - (uint8_t*) rrsig->rrsig.signature + key_size, key_size, - hash, hash_size, - q, key_size*2+1); -} - -static void md_add_uint8(gcry_md_hd_t md, uint8_t v) { - gcry_md_write(md, &v, sizeof(v)); -} - -static void md_add_uint16(gcry_md_hd_t md, uint16_t v) { - v = htobe16(v); - gcry_md_write(md, &v, sizeof(v)); -} - -static void md_add_uint32(gcry_md_hd_t md, uint32_t v) { - v = htobe32(v); - gcry_md_write(md, &v, sizeof(v)); -} - -static int dnssec_rrsig_prepare(DnsResourceRecord *rrsig) { - int n_key_labels, n_signer_labels; - const char *name; - int r; - - /* Checks whether the specified RRSIG RR is somewhat valid, and initializes the .n_skip_labels_source and - * .n_skip_labels_signer fields so that we can use them later on. */ - - assert(rrsig); - assert(rrsig->key->type == DNS_TYPE_RRSIG); - - /* Check if this RRSIG RR is already prepared */ - if (rrsig->n_skip_labels_source != (unsigned) -1) - return 0; - - if (rrsig->rrsig.inception > rrsig->rrsig.expiration) - return -EINVAL; - - name = dns_resource_key_name(rrsig->key); - - n_key_labels = dns_name_count_labels(name); - if (n_key_labels < 0) - return n_key_labels; - if (rrsig->rrsig.labels > n_key_labels) - return -EINVAL; - - n_signer_labels = dns_name_count_labels(rrsig->rrsig.signer); - if (n_signer_labels < 0) - return n_signer_labels; - if (n_signer_labels > rrsig->rrsig.labels) - return -EINVAL; - - r = dns_name_skip(name, n_key_labels - n_signer_labels, &name); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - /* Check if the signer is really a suffix of us */ - r = dns_name_equal(name, rrsig->rrsig.signer); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - rrsig->n_skip_labels_source = n_key_labels - rrsig->rrsig.labels; - rrsig->n_skip_labels_signer = n_key_labels - n_signer_labels; - - return 0; -} - -static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) { - usec_t expiration, inception, skew; - - assert(rrsig); - assert(rrsig->key->type == DNS_TYPE_RRSIG); - - if (realtime == USEC_INFINITY) - realtime = now(CLOCK_REALTIME); - - expiration = rrsig->rrsig.expiration * USEC_PER_SEC; - inception = rrsig->rrsig.inception * USEC_PER_SEC; - - /* Consider inverted validity intervals as expired */ - if (inception > expiration) - return true; - - /* Permit a certain amount of clock skew of 10% of the valid - * time range. This takes inspiration from unbound's - * resolver. */ - skew = (expiration - inception) / 10; - if (skew > SKEW_MAX) - skew = SKEW_MAX; - - if (inception < skew) - inception = 0; - else - inception -= skew; - - if (expiration + skew < expiration) - expiration = USEC_INFINITY; - else - expiration += skew; - - return realtime < inception || realtime > expiration; -} - -static int algorithm_to_gcrypt_md(uint8_t algorithm) { - - /* Translates a DNSSEC signature algorithm into a gcrypt - * digest identifier. - * - * Note that we implement all algorithms listed as "Must - * implement" and "Recommended to Implement" in RFC6944. We - * don't implement any algorithms that are listed as - * "Optional" or "Must Not Implement". Specifically, we do not - * implement RSAMD5, DSASHA1, DH, DSA-NSEC3-SHA1, and - * GOST-ECC. */ - - switch (algorithm) { - - case DNSSEC_ALGORITHM_RSASHA1: - case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: - return GCRY_MD_SHA1; - - case DNSSEC_ALGORITHM_RSASHA256: - case DNSSEC_ALGORITHM_ECDSAP256SHA256: - return GCRY_MD_SHA256; - - case DNSSEC_ALGORITHM_ECDSAP384SHA384: - return GCRY_MD_SHA384; - - case DNSSEC_ALGORITHM_RSASHA512: - return GCRY_MD_SHA512; - - default: - return -EOPNOTSUPP; - } -} - -static void dnssec_fix_rrset_ttl( - DnsResourceRecord *list[], - unsigned n, - DnsResourceRecord *rrsig, - usec_t realtime) { - - unsigned k; - - assert(list); - assert(n > 0); - assert(rrsig); - - for (k = 0; k < n; k++) { - DnsResourceRecord *rr = list[k]; - - /* Pick the TTL as the minimum of the RR's TTL, the - * RR's original TTL according to the RRSIG and the - * RRSIG's own TTL, see RFC 4035, Section 5.3.3 */ - rr->ttl = MIN3(rr->ttl, rrsig->rrsig.original_ttl, rrsig->ttl); - rr->expiry = rrsig->rrsig.expiration * USEC_PER_SEC; - - /* Copy over information about the signer and wildcard source of synthesis */ - rr->n_skip_labels_source = rrsig->n_skip_labels_source; - rr->n_skip_labels_signer = rrsig->n_skip_labels_signer; - } - - rrsig->expiry = rrsig->rrsig.expiration * USEC_PER_SEC; -} - -int dnssec_verify_rrset( - DnsAnswer *a, - const DnsResourceKey *key, - DnsResourceRecord *rrsig, - DnsResourceRecord *dnskey, - usec_t realtime, - DnssecResult *result) { - - uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX]; - DnsResourceRecord **list, *rr; - const char *source, *name; - gcry_md_hd_t md = NULL; - int r, md_algorithm; - size_t k, n = 0; - size_t hash_size; - void *hash; - bool wildcard; - - assert(key); - assert(rrsig); - assert(dnskey); - assert(result); - assert(rrsig->key->type == DNS_TYPE_RRSIG); - assert(dnskey->key->type == DNS_TYPE_DNSKEY); - - /* Verifies that the RRSet matches the specified "key" in "a", - * using the signature "rrsig" and the key "dnskey". It's - * assumed that RRSIG and DNSKEY match. */ - - md_algorithm = algorithm_to_gcrypt_md(rrsig->rrsig.algorithm); - if (md_algorithm == -EOPNOTSUPP) { - *result = DNSSEC_UNSUPPORTED_ALGORITHM; - return 0; - } - if (md_algorithm < 0) - return md_algorithm; - - r = dnssec_rrsig_prepare(rrsig); - if (r == -EINVAL) { - *result = DNSSEC_INVALID; - return r; - } - if (r < 0) - return r; - - r = dnssec_rrsig_expired(rrsig, realtime); - if (r < 0) - return r; - if (r > 0) { - *result = DNSSEC_SIGNATURE_EXPIRED; - return 0; - } - - name = dns_resource_key_name(key); - - /* Some keys may only appear signed in the zone apex, and are invalid anywhere else. (SOA, NS...) */ - if (dns_type_apex_only(rrsig->rrsig.type_covered)) { - r = dns_name_equal(rrsig->rrsig.signer, name); - if (r < 0) - return r; - if (r == 0) { - *result = DNSSEC_INVALID; - return 0; - } - } - - /* OTOH DS RRs may not appear in the zone apex, but are valid everywhere else. */ - if (rrsig->rrsig.type_covered == DNS_TYPE_DS) { - r = dns_name_equal(rrsig->rrsig.signer, name); - if (r < 0) - return r; - if (r > 0) { - *result = DNSSEC_INVALID; - return 0; - } - } - - /* Determine the "Source of Synthesis" and whether this is a wildcard RRSIG */ - r = dns_name_suffix(name, rrsig->rrsig.labels, &source); - if (r < 0) - return r; - if (r > 0 && !dns_type_may_wildcard(rrsig->rrsig.type_covered)) { - /* We refuse to validate NSEC3 or SOA RRs that are synthesized from wildcards */ - *result = DNSSEC_INVALID; - return 0; - } - if (r == 1) { - /* If we stripped a single label, then let's see if that maybe was "*". If so, we are not really - * synthesized from a wildcard, we are the wildcard itself. Treat that like a normal name. */ - r = dns_name_startswith(name, "*"); - if (r < 0) - return r; - if (r > 0) - source = name; - - wildcard = r == 0; - } else - wildcard = r > 0; - - /* Collect all relevant RRs in a single array, so that we can look at the RRset */ - list = newa(DnsResourceRecord *, dns_answer_size(a)); - - DNS_ANSWER_FOREACH(rr, a) { - r = dns_resource_key_equal(key, rr->key); - if (r < 0) - return r; - if (r == 0) - continue; - - /* We need the wire format for ordering, and digest calculation */ - r = dns_resource_record_to_wire_format(rr, true); - if (r < 0) - return r; - - list[n++] = rr; - - if (n > VERIFY_RRS_MAX) - return -E2BIG; - } - - if (n <= 0) - return -ENODATA; - - /* Bring the RRs into canonical order */ - qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare); - - /* OK, the RRs are now in canonical order. Let's calculate the digest */ - initialize_libgcrypt(false); - - hash_size = gcry_md_get_algo_dlen(md_algorithm); - assert(hash_size > 0); - - gcry_md_open(&md, md_algorithm, 0); - if (!md) - return -EIO; - - md_add_uint16(md, rrsig->rrsig.type_covered); - md_add_uint8(md, rrsig->rrsig.algorithm); - md_add_uint8(md, rrsig->rrsig.labels); - md_add_uint32(md, rrsig->rrsig.original_ttl); - md_add_uint32(md, rrsig->rrsig.expiration); - md_add_uint32(md, rrsig->rrsig.inception); - md_add_uint16(md, rrsig->rrsig.key_tag); - - r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true); - if (r < 0) - goto finish; - gcry_md_write(md, wire_format_name, r); - - /* Convert the source of synthesis into wire format */ - r = dns_name_to_wire_format(source, wire_format_name, sizeof(wire_format_name), true); - if (r < 0) - goto finish; - - for (k = 0; k < n; k++) { - size_t l; - - rr = list[k]; - - /* Hash the source of synthesis. If this is a wildcard, then prefix it with the *. label */ - if (wildcard) - gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2); - gcry_md_write(md, wire_format_name, r); - - md_add_uint16(md, rr->key->type); - md_add_uint16(md, rr->key->class); - md_add_uint32(md, rrsig->rrsig.original_ttl); - - l = DNS_RESOURCE_RECORD_RDATA_SIZE(rr); - assert(l <= 0xFFFF); - - md_add_uint16(md, (uint16_t) l); - gcry_md_write(md, DNS_RESOURCE_RECORD_RDATA(rr), l); - } - - hash = gcry_md_read(md, 0); - if (!hash) { - r = -EIO; - goto finish; - } - - switch (rrsig->rrsig.algorithm) { - - case DNSSEC_ALGORITHM_RSASHA1: - case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: - case DNSSEC_ALGORITHM_RSASHA256: - case DNSSEC_ALGORITHM_RSASHA512: - r = dnssec_rsa_verify( - gcry_md_algo_name(md_algorithm), - hash, hash_size, - rrsig, - dnskey); - break; - - case DNSSEC_ALGORITHM_ECDSAP256SHA256: - case DNSSEC_ALGORITHM_ECDSAP384SHA384: - r = dnssec_ecdsa_verify( - gcry_md_algo_name(md_algorithm), - rrsig->rrsig.algorithm, - hash, hash_size, - rrsig, - dnskey); - break; - } - - if (r < 0) - goto finish; - - /* Now, fix the ttl, expiry, and remember the synthesizing source and the signer */ - if (r > 0) - dnssec_fix_rrset_ttl(list, n, rrsig, realtime); - - if (r == 0) - *result = DNSSEC_INVALID; - else if (wildcard) - *result = DNSSEC_VALIDATED_WILDCARD; - else - *result = DNSSEC_VALIDATED; - - r = 0; - -finish: - gcry_md_close(md); - return r; -} - -int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) { - - assert(rrsig); - assert(dnskey); - - /* Checks if the specified DNSKEY RR matches the key used for - * the signature in the specified RRSIG RR */ - - if (rrsig->key->type != DNS_TYPE_RRSIG) - return -EINVAL; - - if (dnskey->key->type != DNS_TYPE_DNSKEY) - return 0; - if (dnskey->key->class != rrsig->key->class) - return 0; - if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0) - return 0; - if (!revoked_ok && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE)) - return 0; - if (dnskey->dnskey.protocol != 3) - return 0; - if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm) - return 0; - - if (dnssec_keytag(dnskey, false) != rrsig->rrsig.key_tag) - return 0; - - return dns_name_equal(dns_resource_key_name(dnskey->key), rrsig->rrsig.signer); -} - -int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) { - assert(key); - assert(rrsig); - - /* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */ - - if (rrsig->key->type != DNS_TYPE_RRSIG) - return 0; - if (rrsig->key->class != key->class) - return 0; - if (rrsig->rrsig.type_covered != key->type) - return 0; - - return dns_name_equal(dns_resource_key_name(rrsig->key), dns_resource_key_name(key)); -} - -int dnssec_verify_rrset_search( - DnsAnswer *a, - const DnsResourceKey *key, - DnsAnswer *validated_dnskeys, - usec_t realtime, - DnssecResult *result, - DnsResourceRecord **ret_rrsig) { - - bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false; - DnsResourceRecord *rrsig; - int r; - - assert(key); - assert(result); - - /* Verifies all RRs from "a" that match the key "key" against DNSKEYs in "validated_dnskeys" */ - - if (!a || a->n_rrs <= 0) - return -ENODATA; - - /* Iterate through each RRSIG RR. */ - DNS_ANSWER_FOREACH(rrsig, a) { - DnsResourceRecord *dnskey; - DnsAnswerFlags flags; - - /* Is this an RRSIG RR that applies to RRs matching our key? */ - r = dnssec_key_match_rrsig(key, rrsig); - if (r < 0) - return r; - if (r == 0) - continue; - - found_rrsig = true; - - /* Look for a matching key */ - DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) { - DnssecResult one_result; - - if ((flags & DNS_ANSWER_AUTHENTICATED) == 0) - continue; - - /* Is this a DNSKEY RR that matches they key of our RRSIG? */ - r = dnssec_rrsig_match_dnskey(rrsig, dnskey, false); - if (r < 0) - return r; - if (r == 0) - continue; - - /* Take the time here, if it isn't set yet, so - * that we do all validations with the same - * time. */ - if (realtime == USEC_INFINITY) - realtime = now(CLOCK_REALTIME); - - /* Yay, we found a matching RRSIG with a matching - * DNSKEY, awesome. Now let's verify all entries of - * the RRSet against the RRSIG and DNSKEY - * combination. */ - - r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime, &one_result); - if (r < 0) - return r; - - switch (one_result) { - - case DNSSEC_VALIDATED: - case DNSSEC_VALIDATED_WILDCARD: - /* Yay, the RR has been validated, - * return immediately, but fix up the expiry */ - if (ret_rrsig) - *ret_rrsig = rrsig; - - *result = one_result; - return 0; - - case DNSSEC_INVALID: - /* If the signature is invalid, let's try another - key and/or signature. After all they - key_tags and stuff are not unique, and - might be shared by multiple keys. */ - found_invalid = true; - continue; - - case DNSSEC_UNSUPPORTED_ALGORITHM: - /* If the key algorithm is - unsupported, try another - RRSIG/DNSKEY pair, but remember we - encountered this, so that we can - return a proper error when we - encounter nothing better. */ - found_unsupported_algorithm = true; - continue; - - case DNSSEC_SIGNATURE_EXPIRED: - /* If the signature is expired, try - another one, but remember it, so - that we can return this */ - found_expired_rrsig = true; - continue; - - default: - assert_not_reached("Unexpected DNSSEC validation result"); - } - } - } - - if (found_expired_rrsig) - *result = DNSSEC_SIGNATURE_EXPIRED; - else if (found_unsupported_algorithm) - *result = DNSSEC_UNSUPPORTED_ALGORITHM; - else if (found_invalid) - *result = DNSSEC_INVALID; - else if (found_rrsig) - *result = DNSSEC_MISSING_KEY; - else - *result = DNSSEC_NO_SIGNATURE; - - if (ret_rrsig) - *ret_rrsig = NULL; - - return 0; -} - -int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) { - DnsResourceRecord *rr; - int r; - - /* Checks whether there's at least one RRSIG in 'a' that proctects RRs of the specified key */ - - DNS_ANSWER_FOREACH(rr, a) { - r = dnssec_key_match_rrsig(key, rr); - if (r < 0) - return r; - if (r > 0) - return 1; - } - - return 0; -} - -static int digest_to_gcrypt_md(uint8_t algorithm) { - - /* Translates a DNSSEC digest algorithm into a gcrypt digest identifier */ - - switch (algorithm) { - - case DNSSEC_DIGEST_SHA1: - return GCRY_MD_SHA1; - - case DNSSEC_DIGEST_SHA256: - return GCRY_MD_SHA256; - - case DNSSEC_DIGEST_SHA384: - return GCRY_MD_SHA384; - - default: - return -EOPNOTSUPP; - } -} - -int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) { - char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX]; - gcry_md_hd_t md = NULL; - size_t hash_size; - int md_algorithm, r; - void *result; - - assert(dnskey); - assert(ds); - - /* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */ - - if (dnskey->key->type != DNS_TYPE_DNSKEY) - return -EINVAL; - if (ds->key->type != DNS_TYPE_DS) - return -EINVAL; - if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0) - return -EKEYREJECTED; - if (!mask_revoke && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE)) - return -EKEYREJECTED; - if (dnskey->dnskey.protocol != 3) - return -EKEYREJECTED; - - if (dnskey->dnskey.algorithm != ds->ds.algorithm) - return 0; - if (dnssec_keytag(dnskey, mask_revoke) != ds->ds.key_tag) - return 0; - - initialize_libgcrypt(false); - - md_algorithm = digest_to_gcrypt_md(ds->ds.digest_type); - if (md_algorithm < 0) - return md_algorithm; - - hash_size = gcry_md_get_algo_dlen(md_algorithm); - assert(hash_size > 0); - - if (ds->ds.digest_size != hash_size) - return 0; - - r = dnssec_canonicalize(dns_resource_key_name(dnskey->key), owner_name, sizeof(owner_name)); - if (r < 0) - return r; - - gcry_md_open(&md, md_algorithm, 0); - if (!md) - return -EIO; - - gcry_md_write(md, owner_name, r); - if (mask_revoke) - md_add_uint16(md, dnskey->dnskey.flags & ~DNSKEY_FLAG_REVOKE); - else - md_add_uint16(md, dnskey->dnskey.flags); - md_add_uint8(md, dnskey->dnskey.protocol); - md_add_uint8(md, dnskey->dnskey.algorithm); - gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size); - - result = gcry_md_read(md, 0); - if (!result) { - r = -EIO; - goto finish; - } - - r = memcmp(result, ds->ds.digest, ds->ds.digest_size) != 0; - -finish: - gcry_md_close(md); - return r; -} - -int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) { - DnsResourceRecord *ds; - DnsAnswerFlags flags; - int r; - - assert(dnskey); - - if (dnskey->key->type != DNS_TYPE_DNSKEY) - return 0; - - DNS_ANSWER_FOREACH_FLAGS(ds, flags, validated_ds) { - - if ((flags & DNS_ANSWER_AUTHENTICATED) == 0) - continue; - - if (ds->key->type != DNS_TYPE_DS) - continue; - if (ds->key->class != dnskey->key->class) - continue; - - r = dns_name_equal(dns_resource_key_name(dnskey->key), dns_resource_key_name(ds->key)); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dnssec_verify_dnskey_by_ds(dnskey, ds, false); - if (IN_SET(r, -EKEYREJECTED, -EOPNOTSUPP)) - return 0; /* The DNSKEY is revoked or otherwise invalid, or we don't support the digest algorithm */ - if (r < 0) - return r; - if (r > 0) - return 1; - } - - return 0; -} - -static int nsec3_hash_to_gcrypt_md(uint8_t algorithm) { - - /* Translates a DNSSEC NSEC3 hash algorithm into a gcrypt digest identifier */ - - switch (algorithm) { - - case NSEC3_ALGORITHM_SHA1: - return GCRY_MD_SHA1; - - default: - return -EOPNOTSUPP; - } -} - -int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { - uint8_t wire_format[DNS_WIRE_FOMAT_HOSTNAME_MAX]; - gcry_md_hd_t md = NULL; - size_t hash_size; - int algorithm; - void *result; - unsigned k; - int r; - - assert(nsec3); - assert(name); - assert(ret); - - if (nsec3->key->type != DNS_TYPE_NSEC3) - return -EINVAL; - - if (nsec3->nsec3.iterations > NSEC3_ITERATIONS_MAX) { - log_debug("Ignoring NSEC3 RR %s with excessive number of iterations.", dns_resource_record_to_string(nsec3)); - return -EOPNOTSUPP; - } - - algorithm = nsec3_hash_to_gcrypt_md(nsec3->nsec3.algorithm); - if (algorithm < 0) - return algorithm; - - initialize_libgcrypt(false); - - hash_size = gcry_md_get_algo_dlen(algorithm); - assert(hash_size > 0); - - if (nsec3->nsec3.next_hashed_name_size != hash_size) - return -EINVAL; - - r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true); - if (r < 0) - return r; - - gcry_md_open(&md, algorithm, 0); - if (!md) - return -EIO; - - gcry_md_write(md, wire_format, r); - gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size); - - result = gcry_md_read(md, 0); - if (!result) { - r = -EIO; - goto finish; - } - - for (k = 0; k < nsec3->nsec3.iterations; k++) { - uint8_t tmp[hash_size]; - memcpy(tmp, result, hash_size); - - gcry_md_reset(md); - gcry_md_write(md, tmp, hash_size); - gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size); - - result = gcry_md_read(md, 0); - if (!result) { - r = -EIO; - goto finish; - } - } - - memcpy(ret, result, hash_size); - r = (int) hash_size; - -finish: - gcry_md_close(md); - return r; -} - -static int nsec3_is_good(DnsResourceRecord *rr, DnsResourceRecord *nsec3) { - const char *a, *b; - int r; - - assert(rr); - - if (rr->key->type != DNS_TYPE_NSEC3) - return 0; - - /* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */ - if (!IN_SET(rr->nsec3.flags, 0, 1)) - return 0; - - /* Ignore NSEC3 RRs whose algorithm we don't know */ - if (nsec3_hash_to_gcrypt_md(rr->nsec3.algorithm) < 0) - return 0; - /* Ignore NSEC3 RRs with an excessive number of required iterations */ - if (rr->nsec3.iterations > NSEC3_ITERATIONS_MAX) - return 0; - - /* Ignore NSEC3 RRs generated from wildcards. If these NSEC3 RRs weren't correctly signed we can't make this - * check (since rr->n_skip_labels_source is -1), but that's OK, as we won't trust them anyway in that case. */ - if (rr->n_skip_labels_source != 0 && rr->n_skip_labels_source != (unsigned) -1) - return 0; - /* Ignore NSEC3 RRs that are located anywhere else than one label below the zone */ - if (rr->n_skip_labels_signer != 1 && rr->n_skip_labels_signer != (unsigned) -1) - return 0; - - if (!nsec3) - return 1; - - /* If a second NSEC3 RR is specified, also check if they are from the same zone. */ - - if (nsec3 == rr) /* Shortcut */ - return 1; - - if (rr->key->class != nsec3->key->class) - return 0; - if (rr->nsec3.algorithm != nsec3->nsec3.algorithm) - return 0; - if (rr->nsec3.iterations != nsec3->nsec3.iterations) - return 0; - if (rr->nsec3.salt_size != nsec3->nsec3.salt_size) - return 0; - if (memcmp(rr->nsec3.salt, nsec3->nsec3.salt, rr->nsec3.salt_size) != 0) - return 0; - - a = dns_resource_key_name(rr->key); - r = dns_name_parent(&a); /* strip off hash */ - if (r < 0) - return r; - if (r == 0) - return 0; - - b = dns_resource_key_name(nsec3->key); - r = dns_name_parent(&b); /* strip off hash */ - if (r < 0) - return r; - if (r == 0) - return 0; - - /* Make sure both have the same parent */ - return dns_name_equal(a, b); -} - -static int nsec3_hashed_domain_format(const uint8_t *hashed, size_t hashed_size, const char *zone, char **ret) { - _cleanup_free_ char *l = NULL; - char *j; - - assert(hashed); - assert(hashed_size > 0); - assert(zone); - assert(ret); - - l = base32hexmem(hashed, hashed_size, false); - if (!l) - return -ENOMEM; - - j = strjoin(l, ".", zone, NULL); - if (!j) - return -ENOMEM; - - *ret = j; - return (int) hashed_size; -} - -static int nsec3_hashed_domain_make(DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) { - uint8_t hashed[DNSSEC_HASH_SIZE_MAX]; - int hashed_size; - - assert(nsec3); - assert(domain); - assert(zone); - assert(ret); - - hashed_size = dnssec_nsec3_hash(nsec3, domain, hashed); - if (hashed_size < 0) - return hashed_size; - - return nsec3_hashed_domain_format(hashed, (size_t) hashed_size, zone, ret); -} - -/* See RFC 5155, Section 8 - * First try to find a NSEC3 record that matches our query precisely, if that fails, find the closest - * enclosure. Secondly, find a proof that there is no closer enclosure and either a proof that there - * is no wildcard domain as a direct descendant of the closest enclosure, or find an NSEC3 record that - * matches the wildcard domain. - * - * Based on this we can prove either the existence of the record in @key, or NXDOMAIN or NODATA, or - * that there is no proof either way. The latter is the case if a the proof of non-existence of a given - * name uses an NSEC3 record with the opt-out bit set. Lastly, if we are given insufficient NSEC3 records - * to conclude anything we indicate this by returning NO_RR. */ -static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { - _cleanup_free_ char *next_closer_domain = NULL, *wildcard_domain = NULL; - const char *zone, *p, *pp = NULL, *wildcard; - DnsResourceRecord *rr, *enclosure_rr, *zone_rr, *wildcard_rr = NULL; - DnsAnswerFlags flags; - int hashed_size, r; - bool a, no_closer = false, no_wildcard = false, optout = false; - - assert(key); - assert(result); - - /* First step, find the zone name and the NSEC3 parameters of the zone. - * it is sufficient to look for the longest common suffix we find with - * any NSEC3 RR in the response. Any NSEC3 record will do as all NSEC3 - * records from a given zone in a response must use the same - * parameters. */ - zone = dns_resource_key_name(key); - for (;;) { - DNS_ANSWER_FOREACH_FLAGS(zone_rr, flags, answer) { - r = nsec3_is_good(zone_rr, NULL); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dns_name_equal_skip(dns_resource_key_name(zone_rr->key), 1, zone); - if (r < 0) - return r; - if (r > 0) - goto found_zone; - } - - /* Strip one label from the front */ - r = dns_name_parent(&zone); - if (r < 0) - return r; - if (r == 0) - break; - } - - *result = DNSSEC_NSEC_NO_RR; - return 0; - -found_zone: - /* Second step, find the closest encloser NSEC3 RR in 'answer' that matches 'key' */ - p = dns_resource_key_name(key); - for (;;) { - _cleanup_free_ char *hashed_domain = NULL; - - hashed_size = nsec3_hashed_domain_make(zone_rr, p, zone, &hashed_domain); - if (hashed_size == -EOPNOTSUPP) { - *result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM; - return 0; - } - if (hashed_size < 0) - return hashed_size; - - DNS_ANSWER_FOREACH_FLAGS(enclosure_rr, flags, answer) { - - r = nsec3_is_good(enclosure_rr, zone_rr); - if (r < 0) - return r; - if (r == 0) - continue; - - if (enclosure_rr->nsec3.next_hashed_name_size != (size_t) hashed_size) - continue; - - r = dns_name_equal(dns_resource_key_name(enclosure_rr->key), hashed_domain); - if (r < 0) - return r; - if (r > 0) { - a = flags & DNS_ANSWER_AUTHENTICATED; - goto found_closest_encloser; - } - } - - /* We didn't find the closest encloser with this name, - * but let's remember this domain name, it might be - * the next closer name */ - - pp = p; - - /* Strip one label from the front */ - r = dns_name_parent(&p); - if (r < 0) - return r; - if (r == 0) - break; - } - - *result = DNSSEC_NSEC_NO_RR; - return 0; - -found_closest_encloser: - /* We found a closest encloser in 'p'; next closer is 'pp' */ - - if (!pp) { - /* We have an exact match! If we area looking for a DS RR, then we must insist that we got the NSEC3 RR - * from the parent. Otherwise the one from the child. Do so, by checking whether SOA and NS are - * appropriately set. */ - - if (key->type == DNS_TYPE_DS) { - if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) - return -EBADMSG; - } else { - if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_NS) && - !bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) - return -EBADMSG; - } - - /* No next closer NSEC3 RR. That means there's a direct NSEC3 RR for our key. */ - if (bitmap_isset(enclosure_rr->nsec3.types, key->type)) - *result = DNSSEC_NSEC_FOUND; - else if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_CNAME)) - *result = DNSSEC_NSEC_CNAME; - else - *result = DNSSEC_NSEC_NODATA; - - if (authenticated) - *authenticated = a; - if (ttl) - *ttl = enclosure_rr->ttl; - - return 0; - } - - /* Ensure this is not a DNAME domain, see RFC5155, section 8.3. */ - if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_DNAME)) - return -EBADMSG; - - /* Ensure that this data is from the delegated domain - * (i.e. originates from the "lower" DNS server), and isn't - * just glue records (i.e. doesn't originate from the "upper" - * DNS server). */ - if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_NS) && - !bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) - return -EBADMSG; - - /* Prove that there is no next closer and whether or not there is a wildcard domain. */ - - wildcard = strjoina("*.", p); - r = nsec3_hashed_domain_make(enclosure_rr, wildcard, zone, &wildcard_domain); - if (r < 0) - return r; - if (r != hashed_size) - return -EBADMSG; - - r = nsec3_hashed_domain_make(enclosure_rr, pp, zone, &next_closer_domain); - if (r < 0) - return r; - if (r != hashed_size) - return -EBADMSG; - - DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { - _cleanup_free_ char *next_hashed_domain = NULL; - - r = nsec3_is_good(rr, zone_rr); - if (r < 0) - return r; - if (r == 0) - continue; - - r = nsec3_hashed_domain_format(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, zone, &next_hashed_domain); - if (r < 0) - return r; - - r = dns_name_between(dns_resource_key_name(rr->key), next_closer_domain, next_hashed_domain); - if (r < 0) - return r; - if (r > 0) { - if (rr->nsec3.flags & 1) - optout = true; - - a = a && (flags & DNS_ANSWER_AUTHENTICATED); - - no_closer = true; - } - - r = dns_name_equal(dns_resource_key_name(rr->key), wildcard_domain); - if (r < 0) - return r; - if (r > 0) { - a = a && (flags & DNS_ANSWER_AUTHENTICATED); - - wildcard_rr = rr; - } - - r = dns_name_between(dns_resource_key_name(rr->key), wildcard_domain, next_hashed_domain); - if (r < 0) - return r; - if (r > 0) { - if (rr->nsec3.flags & 1) - /* This only makes sense if we have a wildcard delegation, which is - * very unlikely, see RFC 4592, Section 4.2, but we cannot rely on - * this not happening, so hence cannot simply conclude NXDOMAIN as - * we would wish */ - optout = true; - - a = a && (flags & DNS_ANSWER_AUTHENTICATED); - - no_wildcard = true; - } - } - - if (wildcard_rr && no_wildcard) - return -EBADMSG; - - if (!no_closer) { - *result = DNSSEC_NSEC_NO_RR; - return 0; - } - - if (wildcard_rr) { - /* A wildcard exists that matches our query. */ - if (optout) - /* This is not specified in any RFC to the best of my knowledge, but - * if the next closer enclosure is covered by an opt-out NSEC3 RR - * it means that we cannot prove that the source of synthesis is - * correct, as there may be a closer match. */ - *result = DNSSEC_NSEC_OPTOUT; - else if (bitmap_isset(wildcard_rr->nsec3.types, key->type)) - *result = DNSSEC_NSEC_FOUND; - else if (bitmap_isset(wildcard_rr->nsec3.types, DNS_TYPE_CNAME)) - *result = DNSSEC_NSEC_CNAME; - else - *result = DNSSEC_NSEC_NODATA; - } else { - if (optout) - /* The RFC only specifies that we have to care for optout for NODATA for - * DS records. However, children of an insecure opt-out delegation should - * also be considered opt-out, rather than verified NXDOMAIN. - * Note that we do not require a proof of wildcard non-existence if the - * next closer domain is covered by an opt-out, as that would not provide - * any additional information. */ - *result = DNSSEC_NSEC_OPTOUT; - else if (no_wildcard) - *result = DNSSEC_NSEC_NXDOMAIN; - else { - *result = DNSSEC_NSEC_NO_RR; - - return 0; - } - } - - if (authenticated) - *authenticated = a; - - if (ttl) - *ttl = enclosure_rr->ttl; - - return 0; -} - -static int dnssec_nsec_wildcard_equal(DnsResourceRecord *rr, const char *name) { - char label[DNS_LABEL_MAX]; - const char *n; - int r; - - assert(rr); - assert(rr->key->type == DNS_TYPE_NSEC); - - /* Checks whether the specified RR has a name beginning in "*.", and if the rest is a suffix of our name */ - - if (rr->n_skip_labels_source != 1) - return 0; - - n = dns_resource_key_name(rr->key); - r = dns_label_unescape(&n, label, sizeof(label)); - if (r <= 0) - return r; - if (r != 1 || label[0] != '*') - return 0; - - return dns_name_endswith(name, n); -} - -static int dnssec_nsec_in_path(DnsResourceRecord *rr, const char *name) { - const char *nn, *common_suffix; - int r; - - assert(rr); - assert(rr->key->type == DNS_TYPE_NSEC); - - /* Checks whether the specified nsec RR indicates that name is an empty non-terminal (ENT) - * - * A couple of examples: - * - * NSEC bar → waldo.foo.bar: indicates that foo.bar exists and is an ENT - * NSEC waldo.foo.bar → yyy.zzz.xoo.bar: indicates that xoo.bar and zzz.xoo.bar exist and are ENTs - * NSEC yyy.zzz.xoo.bar → bar: indicates pretty much nothing about ENTs - */ - - /* First, determine parent of next domain. */ - nn = rr->nsec.next_domain_name; - r = dns_name_parent(&nn); - if (r <= 0) - return r; - - /* If the name we just determined is not equal or child of the name we are interested in, then we can't say - * anything at all. */ - r = dns_name_endswith(nn, name); - if (r <= 0) - return r; - - /* If the name we we are interested in is not a prefix of the common suffix of the NSEC RR's owner and next domain names, then we can't say anything either. */ - r = dns_name_common_suffix(dns_resource_key_name(rr->key), rr->nsec.next_domain_name, &common_suffix); - if (r < 0) - return r; - - return dns_name_endswith(name, common_suffix); -} - -static int dnssec_nsec_from_parent_zone(DnsResourceRecord *rr, const char *name) { - int r; - - assert(rr); - assert(rr->key->type == DNS_TYPE_NSEC); - - /* Checks whether this NSEC originates to the parent zone or the child zone. */ - - r = dns_name_parent(&name); - if (r <= 0) - return r; - - r = dns_name_equal(name, dns_resource_key_name(rr->key)); - if (r <= 0) - return r; - - /* DNAME, and NS without SOA is an indication for a delegation. */ - if (bitmap_isset(rr->nsec.types, DNS_TYPE_DNAME)) - return 1; - - if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) && !bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) - return 1; - - return 0; -} - -static int dnssec_nsec_covers(DnsResourceRecord *rr, const char *name) { - const char *common_suffix, *p; - int r; - - assert(rr); - assert(rr->key->type == DNS_TYPE_NSEC); - - /* Checks whether the "Next Closer" is witin the space covered by the specified RR. */ - - r = dns_name_common_suffix(dns_resource_key_name(rr->key), rr->nsec.next_domain_name, &common_suffix); - if (r < 0) - return r; - - for (;;) { - p = name; - r = dns_name_parent(&name); - if (r < 0) - return r; - if (r == 0) - return 0; - - r = dns_name_equal(name, common_suffix); - if (r < 0) - return r; - if (r > 0) - break; - } - - /* p is now the "Next Closer". */ - - return dns_name_between(dns_resource_key_name(rr->key), p, rr->nsec.next_domain_name); -} - -static int dnssec_nsec_covers_wildcard(DnsResourceRecord *rr, const char *name) { - const char *common_suffix, *wc; - int r; - - assert(rr); - assert(rr->key->type == DNS_TYPE_NSEC); - - /* Checks whether the "Wildcard at the Closest Encloser" is within the space covered by the specified - * RR. Specifically, checks whether 'name' has the common suffix of the NSEC RR's owner and next names as - * suffix, and whether the NSEC covers the name generated by that suffix prepended with an asterisk label. - * - * NSEC bar → waldo.foo.bar: indicates that *.bar and *.foo.bar do not exist - * NSEC waldo.foo.bar → yyy.zzz.xoo.bar: indicates that *.xoo.bar and *.zzz.xoo.bar do not exist (and more ...) - * NSEC yyy.zzz.xoo.bar → bar: indicates that a number of wildcards don#t exist either... - */ - - r = dns_name_common_suffix(dns_resource_key_name(rr->key), rr->nsec.next_domain_name, &common_suffix); - if (r < 0) - return r; - - /* If the common suffix is not shared by the name we are interested in, it has nothing to say for us. */ - r = dns_name_endswith(name, common_suffix); - if (r <= 0) - return r; - - wc = strjoina("*.", common_suffix); - return dns_name_between(dns_resource_key_name(rr->key), wc, rr->nsec.next_domain_name); -} - -int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { - bool have_nsec3 = false, covering_rr_authenticated = false, wildcard_rr_authenticated = false; - DnsResourceRecord *rr, *covering_rr = NULL, *wildcard_rr = NULL; - DnsAnswerFlags flags; - const char *name; - int r; - - assert(key); - assert(result); - - /* Look for any NSEC/NSEC3 RRs that say something about the specified key. */ - - name = dns_resource_key_name(key); - - DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { - - if (rr->key->class != key->class) - continue; - - have_nsec3 = have_nsec3 || (rr->key->type == DNS_TYPE_NSEC3); - - if (rr->key->type != DNS_TYPE_NSEC) - continue; - - /* The following checks only make sense for NSEC RRs that are not expanded from a wildcard */ - r = dns_resource_record_is_synthetic(rr); - if (r < 0) - return r; - if (r > 0) - continue; - - /* Check if this is a direct match. If so, we have encountered a NODATA case */ - r = dns_name_equal(dns_resource_key_name(rr->key), name); - if (r < 0) - return r; - if (r == 0) { - /* If it's not a direct match, maybe it's a wild card match? */ - r = dnssec_nsec_wildcard_equal(rr, name); - if (r < 0) - return r; - } - if (r > 0) { - if (key->type == DNS_TYPE_DS) { - /* If we look for a DS RR and the server sent us the NSEC RR of the child zone - * we have a problem. For DS RRs we want the NSEC RR from the parent */ - if (bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) - continue; - } else { - /* For all RR types, ensure that if NS is set SOA is set too, so that we know - * we got the child's NSEC. */ - if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) && - !bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) - continue; - } - - if (bitmap_isset(rr->nsec.types, key->type)) - *result = DNSSEC_NSEC_FOUND; - else if (bitmap_isset(rr->nsec.types, DNS_TYPE_CNAME)) - *result = DNSSEC_NSEC_CNAME; - else - *result = DNSSEC_NSEC_NODATA; - - if (authenticated) - *authenticated = flags & DNS_ANSWER_AUTHENTICATED; - if (ttl) - *ttl = rr->ttl; - - return 0; - } - - /* Check if the name we are looking for is an empty non-terminal within the owner or next name - * of the NSEC RR. */ - r = dnssec_nsec_in_path(rr, name); - if (r < 0) - return r; - if (r > 0) { - *result = DNSSEC_NSEC_NODATA; - - if (authenticated) - *authenticated = flags & DNS_ANSWER_AUTHENTICATED; - if (ttl) - *ttl = rr->ttl; - - return 0; - } - - /* The following two "covering" checks, are not useful if the NSEC is from the parent */ - r = dnssec_nsec_from_parent_zone(rr, name); - if (r < 0) - return r; - if (r > 0) - continue; - - /* Check if this NSEC RR proves the absence of an explicit RR under this name */ - r = dnssec_nsec_covers(rr, name); - if (r < 0) - return r; - if (r > 0 && (!covering_rr || !covering_rr_authenticated)) { - covering_rr = rr; - covering_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED; - } - - /* Check if this NSEC RR proves the absence of a wildcard RR under this name */ - r = dnssec_nsec_covers_wildcard(rr, name); - if (r < 0) - return r; - if (r > 0 && (!wildcard_rr || !wildcard_rr_authenticated)) { - wildcard_rr = rr; - wildcard_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED; - } - } - - if (covering_rr && wildcard_rr) { - /* If we could prove that neither the name itself, nor the wildcard at the closest encloser exists, we - * proved the NXDOMAIN case. */ - *result = DNSSEC_NSEC_NXDOMAIN; - - if (authenticated) - *authenticated = covering_rr_authenticated && wildcard_rr_authenticated; - if (ttl) - *ttl = MIN(covering_rr->ttl, wildcard_rr->ttl); - - return 0; - } - - /* OK, this was not sufficient. Let's see if NSEC3 can help. */ - if (have_nsec3) - return dnssec_test_nsec3(answer, key, result, authenticated, ttl); - - /* No approproate NSEC RR found, report this. */ - *result = DNSSEC_NSEC_NO_RR; - return 0; -} - -static int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, const char *name, const char *zone, bool *authenticated) { - DnsResourceRecord *rr; - DnsAnswerFlags flags; - int r; - - assert(name); - assert(zone); - - /* Checks whether there's an NSEC/NSEC3 that proves that the specified 'name' is non-existing in the specified - * 'zone'. The 'zone' must be a suffix of the 'name'. */ - - DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { - bool found = false; - - if (rr->key->type != type && type != DNS_TYPE_ANY) - continue; - - switch (rr->key->type) { - - case DNS_TYPE_NSEC: - - /* We only care for NSEC RRs from the indicated zone */ - r = dns_resource_record_is_signer(rr, zone); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dns_name_between(dns_resource_key_name(rr->key), name, rr->nsec.next_domain_name); - if (r < 0) - return r; - - found = r > 0; - break; - - case DNS_TYPE_NSEC3: { - _cleanup_free_ char *hashed_domain = NULL, *next_hashed_domain = NULL; - - /* We only care for NSEC3 RRs from the indicated zone */ - r = dns_resource_record_is_signer(rr, zone); - if (r < 0) - return r; - if (r == 0) - continue; - - r = nsec3_is_good(rr, NULL); - if (r < 0) - return r; - if (r == 0) - break; - - /* Format the domain we are testing with the NSEC3 RR's hash function */ - r = nsec3_hashed_domain_make( - rr, - name, - zone, - &hashed_domain); - if (r < 0) - return r; - if ((size_t) r != rr->nsec3.next_hashed_name_size) - break; - - /* Format the NSEC3's next hashed name as proper domain name */ - r = nsec3_hashed_domain_format( - rr->nsec3.next_hashed_name, - rr->nsec3.next_hashed_name_size, - zone, - &next_hashed_domain); - if (r < 0) - return r; - - r = dns_name_between(dns_resource_key_name(rr->key), hashed_domain, next_hashed_domain); - if (r < 0) - return r; - - found = r > 0; - break; - } - - default: - continue; - } - - if (found) { - if (authenticated) - *authenticated = flags & DNS_ANSWER_AUTHENTICATED; - return 1; - } - } - - return 0; -} - -static int dnssec_test_positive_wildcard_nsec3( - DnsAnswer *answer, - const char *name, - const char *source, - const char *zone, - bool *authenticated) { - - const char *next_closer = NULL; - int r; - - /* Run a positive NSEC3 wildcard proof. Specifically: - * - * A proof that the "next closer" of the generating wildcard does not exist. - * - * Note a key difference between the NSEC3 and NSEC versions of the proof. NSEC RRs don't have to exist for - * empty non-transients. NSEC3 RRs however have to. This means it's sufficient to check if the next closer name - * exists for the NSEC3 RR and we are done. - * - * To prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f all we have to check is that - * c.d.e.f does not exist. */ - - for (;;) { - next_closer = name; - r = dns_name_parent(&name); - if (r < 0) - return r; - if (r == 0) - return 0; - - r = dns_name_equal(name, source); - if (r < 0) - return r; - if (r > 0) - break; - } - - return dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC3, next_closer, zone, authenticated); -} - -static int dnssec_test_positive_wildcard_nsec( - DnsAnswer *answer, - const char *name, - const char *source, - const char *zone, - bool *_authenticated) { - - bool authenticated = true; - int r; - - /* Run a positive NSEC wildcard proof. Specifically: - * - * A proof that there's neither a wildcard name nor a non-wildcard name that is a suffix of the name "name" and - * a prefix of the synthesizing source "source" in the zone "zone". - * - * See RFC 5155, Section 8.8 and RFC 4035, Section 5.3.4 - * - * Note that if we want to prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f, then we - * have to prove that none of the following exist: - * - * 1) a.b.c.d.e.f - * 2) *.b.c.d.e.f - * 3) b.c.d.e.f - * 4) *.c.d.e.f - * 5) c.d.e.f - * - */ - - for (;;) { - _cleanup_free_ char *wc = NULL; - bool a = false; - - /* Check if there's an NSEC or NSEC3 RR that proves that the mame we determined is really non-existing, - * i.e between the owner name and the next name of an NSEC RR. */ - r = dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC, name, zone, &a); - if (r <= 0) - return r; - - authenticated = authenticated && a; - - /* Strip one label off */ - r = dns_name_parent(&name); - if (r <= 0) - return r; - - /* Did we reach the source of synthesis? */ - r = dns_name_equal(name, source); - if (r < 0) - return r; - if (r > 0) { - /* Successful exit */ - *_authenticated = authenticated; - return 1; - } - - /* Safety check, that the source of synthesis is still our suffix */ - r = dns_name_endswith(name, source); - if (r < 0) - return r; - if (r == 0) - return -EBADMSG; - - /* Replace the label we stripped off with an asterisk */ - wc = strappend("*.", name); - if (!wc) - return -ENOMEM; - - /* And check if the proof holds for the asterisk name, too */ - r = dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC, wc, zone, &a); - if (r <= 0) - return r; - - authenticated = authenticated && a; - /* In the next iteration we'll check the non-asterisk-prefixed version */ - } -} - -int dnssec_test_positive_wildcard( - DnsAnswer *answer, - const char *name, - const char *source, - const char *zone, - bool *authenticated) { - - int r; - - assert(name); - assert(source); - assert(zone); - assert(authenticated); - - r = dns_answer_contains_zone_nsec3(answer, zone); - if (r < 0) - return r; - if (r > 0) - return dnssec_test_positive_wildcard_nsec3(answer, name, source, zone, authenticated); - else - return dnssec_test_positive_wildcard_nsec(answer, name, source, zone, authenticated); -} - -#else - -int dnssec_verify_rrset( - DnsAnswer *a, - const DnsResourceKey *key, - DnsResourceRecord *rrsig, - DnsResourceRecord *dnskey, - usec_t realtime, - DnssecResult *result) { - - return -EOPNOTSUPP; -} - -int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) { - - return -EOPNOTSUPP; -} - -int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) { - - return -EOPNOTSUPP; -} - -int dnssec_verify_rrset_search( - DnsAnswer *a, - const DnsResourceKey *key, - DnsAnswer *validated_dnskeys, - usec_t realtime, - DnssecResult *result, - DnsResourceRecord **ret_rrsig) { - - return -EOPNOTSUPP; -} - -int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) { - - return -EOPNOTSUPP; -} - -int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) { - - return -EOPNOTSUPP; -} - -int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) { - - return -EOPNOTSUPP; -} - -int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { - - return -EOPNOTSUPP; -} - -int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { - - return -EOPNOTSUPP; -} - -int dnssec_test_positive_wildcard( - DnsAnswer *answer, - const char *name, - const char *source, - const char *zone, - bool *authenticated) { - - return -EOPNOTSUPP; -} - -#endif - -static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = { - [DNSSEC_VALIDATED] = "validated", - [DNSSEC_VALIDATED_WILDCARD] = "validated-wildcard", - [DNSSEC_INVALID] = "invalid", - [DNSSEC_SIGNATURE_EXPIRED] = "signature-expired", - [DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm", - [DNSSEC_NO_SIGNATURE] = "no-signature", - [DNSSEC_MISSING_KEY] = "missing-key", - [DNSSEC_UNSIGNED] = "unsigned", - [DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary", - [DNSSEC_NSEC_MISMATCH] = "nsec-mismatch", - [DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server", -}; -DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult); - -static const char* const dnssec_verdict_table[_DNSSEC_VERDICT_MAX] = { - [DNSSEC_SECURE] = "secure", - [DNSSEC_INSECURE] = "insecure", - [DNSSEC_BOGUS] = "bogus", - [DNSSEC_INDETERMINATE] = "indeterminate", -}; -DEFINE_STRING_TABLE_LOOKUP(dnssec_verdict, DnssecVerdict); diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h deleted file mode 100644 index 77bd4d71bf..0000000000 --- a/src/resolve/resolved-dns-dnssec.h +++ /dev/null @@ -1,102 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -typedef enum DnssecResult DnssecResult; -typedef enum DnssecVerdict DnssecVerdict; - -#include "dns-domain.h" -#include "resolved-dns-answer.h" -#include "resolved-dns-rr.h" - -enum DnssecResult { - /* These five are returned by dnssec_verify_rrset() */ - DNSSEC_VALIDATED, - DNSSEC_VALIDATED_WILDCARD, /* Validated via a wildcard RRSIG, further NSEC/NSEC3 checks necessary */ - DNSSEC_INVALID, - DNSSEC_SIGNATURE_EXPIRED, - DNSSEC_UNSUPPORTED_ALGORITHM, - - /* These two are added by dnssec_verify_rrset_search() */ - DNSSEC_NO_SIGNATURE, - DNSSEC_MISSING_KEY, - - /* These two are added by the DnsTransaction logic */ - DNSSEC_UNSIGNED, - DNSSEC_FAILED_AUXILIARY, - DNSSEC_NSEC_MISMATCH, - DNSSEC_INCOMPATIBLE_SERVER, - - _DNSSEC_RESULT_MAX, - _DNSSEC_RESULT_INVALID = -1 -}; - -enum DnssecVerdict { - DNSSEC_SECURE, - DNSSEC_INSECURE, - DNSSEC_BOGUS, - DNSSEC_INDETERMINATE, - - _DNSSEC_VERDICT_MAX, - _DNSSEC_VERDICT_INVALID = -1 -}; - -#define DNSSEC_CANONICAL_HOSTNAME_MAX (DNS_HOSTNAME_MAX + 2) - -/* The longest digest we'll ever generate, of all digest algorithms we support */ -#define DNSSEC_HASH_SIZE_MAX (MAX(20, 32)) - -int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok); -int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig); - -int dnssec_verify_rrset(DnsAnswer *answer, const DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime, DnssecResult *result); -int dnssec_verify_rrset_search(DnsAnswer *answer, const DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime, DnssecResult *result, DnsResourceRecord **rrsig); - -int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke); -int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds); - -int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key); - -uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke); - -int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max); - -int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret); - -typedef enum DnssecNsecResult { - DNSSEC_NSEC_NO_RR, /* No suitable NSEC/NSEC3 RR found */ - DNSSEC_NSEC_CNAME, /* Didn't find what was asked for, but did find CNAME */ - DNSSEC_NSEC_UNSUPPORTED_ALGORITHM, - DNSSEC_NSEC_NXDOMAIN, - DNSSEC_NSEC_NODATA, - DNSSEC_NSEC_FOUND, - DNSSEC_NSEC_OPTOUT, -} DnssecNsecResult; - -int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl); - - -int dnssec_test_positive_wildcard(DnsAnswer *a, const char *name, const char *source, const char *zone, bool *authenticated); - -const char* dnssec_result_to_string(DnssecResult m) _const_; -DnssecResult dnssec_result_from_string(const char *s) _pure_; - -const char* dnssec_verdict_to_string(DnssecVerdict m) _const_; -DnssecVerdict dnssec_verdict_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c deleted file mode 100644 index b7907bb511..0000000000 --- a/src/resolve/resolved-dns-packet.c +++ /dev/null @@ -1,2255 +0,0 @@ -/*** - 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 "alloc-util.h" -#include "dns-domain.h" -#include "resolved-dns-packet.h" -#include "string-table.h" -#include "strv.h" -#include "unaligned.h" -#include "utf8.h" -#include "util.h" - -#define EDNS0_OPT_DO (1<<15) - -typedef struct DnsPacketRewinder { - DnsPacket *packet; - size_t saved_rindex; -} DnsPacketRewinder; - -static void rewind_dns_packet(DnsPacketRewinder *rewinder) { - if (rewinder->packet) - dns_packet_rewind(rewinder->packet, rewinder->saved_rindex); -} - -#define INIT_REWINDER(rewinder, p) do { rewinder.packet = p; rewinder.saved_rindex = p->rindex; } while (0) -#define CANCEL_REWINDER(rewinder) do { rewinder.packet = NULL; } while (0) - -int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { - DnsPacket *p; - size_t a; - - assert(ret); - - if (mtu <= UDP_PACKET_HEADER_SIZE) - a = DNS_PACKET_SIZE_START; - else - a = mtu - UDP_PACKET_HEADER_SIZE; - - if (a < DNS_PACKET_HEADER_SIZE) - a = DNS_PACKET_HEADER_SIZE; - - /* round up to next page size */ - a = PAGE_ALIGN(ALIGN(sizeof(DnsPacket)) + a) - ALIGN(sizeof(DnsPacket)); - - /* make sure we never allocate more than useful */ - if (a > DNS_PACKET_SIZE_MAX) - a = DNS_PACKET_SIZE_MAX; - - p = malloc0(ALIGN(sizeof(DnsPacket)) + a); - if (!p) - return -ENOMEM; - - p->size = p->rindex = DNS_PACKET_HEADER_SIZE; - p->allocated = a; - p->protocol = protocol; - p->opt_start = p->opt_size = (size_t) -1; - p->n_ref = 1; - - *ret = p; - - return 0; -} - -void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated) { - - DnsPacketHeader *h; - - assert(p); - - h = DNS_PACKET_HEADER(p); - - switch(p->protocol) { - case DNS_PROTOCOL_LLMNR: - assert(!truncated); - - h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, - 0 /* opcode */, - 0 /* c */, - 0 /* tc */, - 0 /* t */, - 0 /* ra */, - 0 /* ad */, - 0 /* cd */, - 0 /* rcode */)); - break; - - case DNS_PROTOCOL_MDNS: - h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, - 0 /* opcode */, - 0 /* aa */, - truncated /* tc */, - 0 /* rd (ask for recursion) */, - 0 /* ra */, - 0 /* ad */, - 0 /* cd */, - 0 /* rcode */)); - break; - - default: - assert(!truncated); - - h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, - 0 /* opcode */, - 0 /* aa */, - 0 /* tc */, - 1 /* rd (ask for recursion) */, - 0 /* ra */, - 0 /* ad */, - dnssec_checking_disabled /* cd */, - 0 /* rcode */)); - } -} - -int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled) { - DnsPacket *p; - int r; - - assert(ret); - - r = dns_packet_new(&p, protocol, mtu); - if (r < 0) - return r; - - /* Always set the TC bit to 0 initially. - * If there are multiple packets later, we'll update the bit shortly before sending. - */ - dns_packet_set_flags(p, dnssec_checking_disabled, false); - - *ret = p; - return 0; -} - -DnsPacket *dns_packet_ref(DnsPacket *p) { - - if (!p) - return NULL; - - assert(!p->on_stack); - - assert(p->n_ref > 0); - p->n_ref++; - return p; -} - -static void dns_packet_free(DnsPacket *p) { - char *s; - - assert(p); - - dns_question_unref(p->question); - dns_answer_unref(p->answer); - dns_resource_record_unref(p->opt); - - while ((s = hashmap_steal_first_key(p->names))) - free(s); - hashmap_free(p->names); - - free(p->_data); - - if (!p->on_stack) - free(p); -} - -DnsPacket *dns_packet_unref(DnsPacket *p) { - if (!p) - return NULL; - - assert(p->n_ref > 0); - - dns_packet_unref(p->more); - - if (p->n_ref == 1) - dns_packet_free(p); - else - p->n_ref--; - - return NULL; -} - -int dns_packet_validate(DnsPacket *p) { - assert(p); - - if (p->size < DNS_PACKET_HEADER_SIZE) - return -EBADMSG; - - if (p->size > DNS_PACKET_SIZE_MAX) - return -EBADMSG; - - return 1; -} - -int dns_packet_validate_reply(DnsPacket *p) { - int r; - - assert(p); - - r = dns_packet_validate(p); - if (r < 0) - return r; - - if (DNS_PACKET_QR(p) != 1) - return 0; - - if (DNS_PACKET_OPCODE(p) != 0) - return -EBADMSG; - - switch (p->protocol) { - - case DNS_PROTOCOL_LLMNR: - /* RFC 4795, Section 2.1.1. says to discard all replies with QDCOUNT != 1 */ - if (DNS_PACKET_QDCOUNT(p) != 1) - return -EBADMSG; - - break; - - case DNS_PROTOCOL_MDNS: - /* RFC 6762, Section 18 */ - if (DNS_PACKET_RCODE(p) != 0) - return -EBADMSG; - - break; - - default: - break; - } - - return 1; -} - -int dns_packet_validate_query(DnsPacket *p) { - int r; - - assert(p); - - r = dns_packet_validate(p); - if (r < 0) - return r; - - if (DNS_PACKET_QR(p) != 0) - return 0; - - if (DNS_PACKET_OPCODE(p) != 0) - return -EBADMSG; - - if (DNS_PACKET_TC(p)) - return -EBADMSG; - - switch (p->protocol) { - - case DNS_PROTOCOL_LLMNR: - /* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */ - if (DNS_PACKET_QDCOUNT(p) != 1) - return -EBADMSG; - - /* RFC 4795, Section 2.1.1. says to discard all queries with ANCOUNT != 0 */ - if (DNS_PACKET_ANCOUNT(p) > 0) - return -EBADMSG; - - /* RFC 4795, Section 2.1.1. says to discard all queries with NSCOUNT != 0 */ - if (DNS_PACKET_NSCOUNT(p) > 0) - return -EBADMSG; - - break; - - case DNS_PROTOCOL_MDNS: - /* RFC 6762, Section 18 */ - if (DNS_PACKET_AA(p) != 0 || - DNS_PACKET_RD(p) != 0 || - DNS_PACKET_RA(p) != 0 || - DNS_PACKET_AD(p) != 0 || - DNS_PACKET_CD(p) != 0 || - DNS_PACKET_RCODE(p) != 0) - return -EBADMSG; - - break; - - default: - break; - } - - return 1; -} - -static int dns_packet_extend(DnsPacket *p, size_t add, void **ret, size_t *start) { - assert(p); - - if (p->size + add > p->allocated) { - size_t a; - - a = PAGE_ALIGN((p->size + add) * 2); - if (a > DNS_PACKET_SIZE_MAX) - a = DNS_PACKET_SIZE_MAX; - - if (p->size + add > a) - return -EMSGSIZE; - - if (p->_data) { - void *d; - - d = realloc(p->_data, a); - if (!d) - return -ENOMEM; - - p->_data = d; - } else { - p->_data = malloc(a); - if (!p->_data) - return -ENOMEM; - - memcpy(p->_data, (uint8_t*) p + ALIGN(sizeof(DnsPacket)), p->size); - memzero((uint8_t*) p->_data + p->size, a - p->size); - } - - p->allocated = a; - } - - if (start) - *start = p->size; - - if (ret) - *ret = (uint8_t*) DNS_PACKET_DATA(p) + p->size; - - p->size += add; - return 0; -} - -void dns_packet_truncate(DnsPacket *p, size_t sz) { - Iterator i; - char *s; - void *n; - - assert(p); - - if (p->size <= sz) - return; - - HASHMAP_FOREACH_KEY(n, s, p->names, i) { - - if (PTR_TO_SIZE(n) < sz) - continue; - - hashmap_remove(p->names, s); - free(s); - } - - p->size = sz; -} - -int dns_packet_append_blob(DnsPacket *p, const void *d, size_t l, size_t *start) { - void *q; - int r; - - assert(p); - - r = dns_packet_extend(p, l, &q, start); - if (r < 0) - return r; - - memcpy(q, d, l); - return 0; -} - -int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start) { - void *d; - int r; - - assert(p); - - r = dns_packet_extend(p, sizeof(uint8_t), &d, start); - if (r < 0) - return r; - - ((uint8_t*) d)[0] = v; - - return 0; -} - -int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start) { - void *d; - int r; - - assert(p); - - r = dns_packet_extend(p, sizeof(uint16_t), &d, start); - if (r < 0) - return r; - - unaligned_write_be16(d, v); - - return 0; -} - -int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start) { - void *d; - int r; - - assert(p); - - r = dns_packet_extend(p, sizeof(uint32_t), &d, start); - if (r < 0) - return r; - - unaligned_write_be32(d, v); - - return 0; -} - -int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start) { - assert(p); - assert(s); - - return dns_packet_append_raw_string(p, s, strlen(s), start); -} - -int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start) { - void *d; - int r; - - assert(p); - assert(s || size == 0); - - if (size > 255) - return -E2BIG; - - r = dns_packet_extend(p, 1 + size, &d, start); - if (r < 0) - return r; - - ((uint8_t*) d)[0] = (uint8_t) size; - - memcpy_safe(((uint8_t*) d) + 1, s, size); - - return 0; -} - -int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, bool canonical_candidate, size_t *start) { - uint8_t *w; - int r; - - /* Append a label to a packet. Optionally, does this in DNSSEC - * canonical form, if this label is marked as a candidate for - * it, and the canonical form logic is enabled for the - * packet */ - - assert(p); - assert(d); - - if (l > DNS_LABEL_MAX) - return -E2BIG; - - r = dns_packet_extend(p, 1 + l, (void**) &w, start); - if (r < 0) - return r; - - *(w++) = (uint8_t) l; - - if (p->canonical_form && canonical_candidate) { - size_t i; - - /* Generate in canonical form, as defined by DNSSEC - * RFC 4034, Section 6.2, i.e. all lower-case. */ - - for (i = 0; i < l; i++) - w[i] = (uint8_t) ascii_tolower(d[i]); - } else - /* Otherwise, just copy the string unaltered. This is - * essential for DNS-SD, where the casing of labels - * matters and needs to be retained. */ - memcpy(w, d, l); - - return 0; -} - -int dns_packet_append_name( - DnsPacket *p, - const char *name, - bool allow_compression, - bool canonical_candidate, - size_t *start) { - - size_t saved_size; - int r; - - assert(p); - assert(name); - - if (p->refuse_compression) - allow_compression = false; - - saved_size = p->size; - - while (!dns_name_is_root(name)) { - const char *z = name; - char label[DNS_LABEL_MAX]; - size_t n = 0; - - if (allow_compression) - n = PTR_TO_SIZE(hashmap_get(p->names, name)); - if (n > 0) { - assert(n < p->size); - - if (n < 0x4000) { - r = dns_packet_append_uint16(p, 0xC000 | n, NULL); - if (r < 0) - goto fail; - - goto done; - } - } - - r = dns_label_unescape(&name, label, sizeof(label)); - if (r < 0) - goto fail; - - r = dns_packet_append_label(p, label, r, canonical_candidate, &n); - if (r < 0) - goto fail; - - if (allow_compression) { - _cleanup_free_ char *s = NULL; - - s = strdup(z); - if (!s) { - r = -ENOMEM; - goto fail; - } - - r = hashmap_ensure_allocated(&p->names, &dns_name_hash_ops); - if (r < 0) - goto fail; - - r = hashmap_put(p->names, s, SIZE_TO_PTR(n)); - if (r < 0) - goto fail; - - s = NULL; - } - } - - r = dns_packet_append_uint8(p, 0, NULL); - if (r < 0) - return r; - -done: - if (start) - *start = saved_size; - - return 0; - -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start) { - size_t saved_size; - int r; - - assert(p); - assert(k); - - saved_size = p->size; - - r = dns_packet_append_name(p, dns_resource_key_name(k), true, true, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, k->type, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, k->class, NULL); - if (r < 0) - goto fail; - - if (start) - *start = saved_size; - - return 0; - -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, const uint8_t *types, size_t *start) { - size_t saved_size; - int r; - - assert(p); - assert(types); - assert(length > 0); - - saved_size = p->size; - - r = dns_packet_append_uint8(p, window, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, length, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, types, length, NULL); - if (r < 0) - goto fail; - - if (start) - *start = saved_size; - - return 0; -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) { - Iterator i; - uint8_t window = 0; - uint8_t entry = 0; - uint8_t bitmaps[32] = {}; - unsigned n; - size_t saved_size; - int r; - - assert(p); - - saved_size = p->size; - - BITMAP_FOREACH(n, types, i) { - assert(n <= 0xffff); - - if ((n >> 8) != window && bitmaps[entry / 8] != 0) { - r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); - if (r < 0) - goto fail; - - zero(bitmaps); - } - - window = n >> 8; - entry = n & 255; - - bitmaps[entry / 8] |= 1 << (7 - (entry % 8)); - } - - if (bitmaps[entry / 8] != 0) { - r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); - if (r < 0) - goto fail; - } - - if (start) - *start = saved_size; - - return 0; -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -/* Append the OPT pseudo-RR described in RFC6891 */ -int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start) { - size_t saved_size; - int r; - - assert(p); - /* we must never advertise supported packet size smaller than the legacy max */ - assert(max_udp_size >= DNS_PACKET_UNICAST_SIZE_MAX); - - if (p->opt_start != (size_t) -1) - return -EBUSY; - - assert(p->opt_size == (size_t) -1); - - saved_size = p->size; - - /* empty name */ - r = dns_packet_append_uint8(p, 0, NULL); - if (r < 0) - return r; - - /* type */ - r = dns_packet_append_uint16(p, DNS_TYPE_OPT, NULL); - if (r < 0) - goto fail; - - /* maximum udp packet that can be received */ - r = dns_packet_append_uint16(p, max_udp_size, NULL); - if (r < 0) - goto fail; - - /* extended RCODE and VERSION */ - r = dns_packet_append_uint16(p, 0, NULL); - if (r < 0) - goto fail; - - /* flags: DNSSEC OK (DO), see RFC3225 */ - r = dns_packet_append_uint16(p, edns0_do ? EDNS0_OPT_DO : 0, NULL); - if (r < 0) - goto fail; - - /* RDLENGTH */ - - if (edns0_do) { - /* If DO is on, also append RFC6975 Algorithm data */ - - static const uint8_t rfc6975[] = { - - 0, 5, /* OPTION_CODE: DAU */ - 0, 6, /* LIST_LENGTH */ - DNSSEC_ALGORITHM_RSASHA1, - DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, - DNSSEC_ALGORITHM_RSASHA256, - DNSSEC_ALGORITHM_RSASHA512, - DNSSEC_ALGORITHM_ECDSAP256SHA256, - DNSSEC_ALGORITHM_ECDSAP384SHA384, - - 0, 6, /* OPTION_CODE: DHU */ - 0, 3, /* LIST_LENGTH */ - DNSSEC_DIGEST_SHA1, - DNSSEC_DIGEST_SHA256, - DNSSEC_DIGEST_SHA384, - - 0, 7, /* OPTION_CODE: N3U */ - 0, 1, /* LIST_LENGTH */ - NSEC3_ALGORITHM_SHA1, - }; - - r = dns_packet_append_uint16(p, sizeof(rfc6975), NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rfc6975, sizeof(rfc6975), NULL); - } else - r = dns_packet_append_uint16(p, 0, NULL); - - if (r < 0) - goto fail; - - DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) + 1); - - p->opt_start = saved_size; - p->opt_size = p->size - saved_size; - - if (start) - *start = saved_size; - - return 0; - -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -int dns_packet_truncate_opt(DnsPacket *p) { - assert(p); - - if (p->opt_start == (size_t) -1) { - assert(p->opt_size == (size_t) -1); - return 0; - } - - assert(p->opt_size != (size_t) -1); - assert(DNS_PACKET_ARCOUNT(p) > 0); - - if (p->opt_start + p->opt_size != p->size) - return -EBUSY; - - dns_packet_truncate(p, p->opt_start); - DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) - 1); - p->opt_start = p->opt_size = (size_t) -1; - - return 1; -} - -int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start) { - size_t saved_size, rdlength_offset, end, rdlength, rds; - int r; - - assert(p); - assert(rr); - - saved_size = p->size; - - r = dns_packet_append_key(p, rr->key, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->ttl, NULL); - if (r < 0) - goto fail; - - /* Initially we write 0 here */ - r = dns_packet_append_uint16(p, 0, &rdlength_offset); - if (r < 0) - goto fail; - - rds = p->size - saved_size; - - switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { - - case DNS_TYPE_SRV: - r = dns_packet_append_uint16(p, rr->srv.priority, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, rr->srv.weight, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, rr->srv.port, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_name(p, rr->srv.name, true, false, NULL); - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - r = dns_packet_append_name(p, rr->ptr.name, true, false, NULL); - break; - - case DNS_TYPE_HINFO: - r = dns_packet_append_string(p, rr->hinfo.cpu, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_string(p, rr->hinfo.os, NULL); - break; - - case DNS_TYPE_SPF: /* exactly the same as TXT */ - case DNS_TYPE_TXT: - - if (!rr->txt.items) { - /* RFC 6763, section 6.1 suggests to generate - * single empty string for an empty array. */ - - r = dns_packet_append_raw_string(p, NULL, 0, NULL); - if (r < 0) - goto fail; - } else { - DnsTxtItem *i; - - LIST_FOREACH(items, i, rr->txt.items) { - r = dns_packet_append_raw_string(p, i->data, i->length, NULL); - if (r < 0) - goto fail; - } - } - - r = 0; - break; - - case DNS_TYPE_A: - r = dns_packet_append_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL); - break; - - case DNS_TYPE_AAAA: - r = dns_packet_append_blob(p, &rr->aaaa.in6_addr, sizeof(struct in6_addr), NULL); - break; - - case DNS_TYPE_SOA: - r = dns_packet_append_name(p, rr->soa.mname, true, false, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_name(p, rr->soa.rname, true, false, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.serial, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.refresh, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.retry, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.expire, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.minimum, NULL); - break; - - case DNS_TYPE_MX: - r = dns_packet_append_uint16(p, rr->mx.priority, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_name(p, rr->mx.exchange, true, false, NULL); - break; - - case DNS_TYPE_LOC: - r = dns_packet_append_uint8(p, rr->loc.version, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->loc.size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->loc.horiz_pre, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->loc.vert_pre, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->loc.latitude, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->loc.longitude, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->loc.altitude, NULL); - break; - - case DNS_TYPE_DS: - r = dns_packet_append_uint16(p, rr->ds.key_tag, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->ds.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->ds.digest_type, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->ds.digest, rr->ds.digest_size, NULL); - break; - - case DNS_TYPE_SSHFP: - r = dns_packet_append_uint8(p, rr->sshfp.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->sshfp.fptype, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, NULL); - break; - - case DNS_TYPE_DNSKEY: - r = dns_packet_append_uint16(p, rr->dnskey.flags, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->dnskey.protocol, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->dnskey.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->dnskey.key, rr->dnskey.key_size, NULL); - break; - - case DNS_TYPE_RRSIG: - r = dns_packet_append_uint16(p, rr->rrsig.type_covered, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->rrsig.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->rrsig.labels, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->rrsig.original_ttl, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->rrsig.expiration, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->rrsig.inception, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, rr->rrsig.key_tag, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_name(p, rr->rrsig.signer, false, true, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->rrsig.signature, rr->rrsig.signature_size, NULL); - break; - - case DNS_TYPE_NSEC: - r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, false, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_types(p, rr->nsec.types, NULL); - if (r < 0) - goto fail; - - break; - - case DNS_TYPE_NSEC3: - r = dns_packet_append_uint8(p, rr->nsec3.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->nsec3.flags, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, rr->nsec3.iterations, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->nsec3.salt_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->nsec3.salt, rr->nsec3.salt_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->nsec3.next_hashed_name_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_types(p, rr->nsec3.types, NULL); - if (r < 0) - goto fail; - - break; - - case DNS_TYPE_TLSA: - r = dns_packet_append_uint8(p, rr->tlsa.cert_usage, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->tlsa.selector, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->tlsa.matching_type, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->tlsa.data, rr->tlsa.data_size, NULL); - break; - - case DNS_TYPE_CAA: - r = dns_packet_append_uint8(p, rr->caa.flags, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_string(p, rr->caa.tag, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->caa.value, rr->caa.value_size, NULL); - break; - - case DNS_TYPE_OPT: - case DNS_TYPE_OPENPGPKEY: - case _DNS_TYPE_INVALID: /* unparseable */ - default: - - r = dns_packet_append_blob(p, rr->generic.data, rr->generic.data_size, NULL); - break; - } - if (r < 0) - goto fail; - - /* Let's calculate the actual data size and update the field */ - rdlength = p->size - rdlength_offset - sizeof(uint16_t); - if (rdlength > 0xFFFF) { - r = -ENOSPC; - goto fail; - } - - end = p->size; - p->size = rdlength_offset; - r = dns_packet_append_uint16(p, rdlength, NULL); - if (r < 0) - goto fail; - p->size = end; - - if (start) - *start = saved_size; - - if (rdata_start) - *rdata_start = rds; - - return 0; - -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) { - assert(p); - - if (p->rindex + sz > p->size) - return -EMSGSIZE; - - if (ret) - *ret = (uint8_t*) DNS_PACKET_DATA(p) + p->rindex; - - if (start) - *start = p->rindex; - - p->rindex += sz; - return 0; -} - -void dns_packet_rewind(DnsPacket *p, size_t idx) { - assert(p); - assert(idx <= p->size); - assert(idx >= DNS_PACKET_HEADER_SIZE); - - p->rindex = idx; -} - -int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start) { - const void *q; - int r; - - assert(p); - assert(d); - - r = dns_packet_read(p, sz, &q, start); - if (r < 0) - return r; - - memcpy(d, q, sz); - return 0; -} - -static int dns_packet_read_memdup( - DnsPacket *p, size_t size, - void **ret, size_t *ret_size, - size_t *ret_start) { - - const void *src; - size_t start; - int r; - - assert(p); - assert(ret); - - r = dns_packet_read(p, size, &src, &start); - if (r < 0) - return r; - - if (size <= 0) - *ret = NULL; - else { - void *copy; - - copy = memdup(src, size); - if (!copy) - return -ENOMEM; - - *ret = copy; - } - - if (ret_size) - *ret_size = size; - if (ret_start) - *ret_start = start; - - return 0; -} - -int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start) { - const void *d; - int r; - - assert(p); - - r = dns_packet_read(p, sizeof(uint8_t), &d, start); - if (r < 0) - return r; - - *ret = ((uint8_t*) d)[0]; - return 0; -} - -int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start) { - const void *d; - int r; - - assert(p); - - r = dns_packet_read(p, sizeof(uint16_t), &d, start); - if (r < 0) - return r; - - *ret = unaligned_read_be16(d); - - return 0; -} - -int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start) { - const void *d; - int r; - - assert(p); - - r = dns_packet_read(p, sizeof(uint32_t), &d, start); - if (r < 0) - return r; - - *ret = unaligned_read_be32(d); - - return 0; -} - -int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start) { - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - const void *d; - char *t; - uint8_t c; - int r; - - assert(p); - INIT_REWINDER(rewinder, p); - - r = dns_packet_read_uint8(p, &c, NULL); - if (r < 0) - return r; - - r = dns_packet_read(p, c, &d, NULL); - if (r < 0) - return r; - - if (memchr(d, 0, c)) - return -EBADMSG; - - t = strndup(d, c); - if (!t) - return -ENOMEM; - - if (!utf8_is_valid(t)) { - free(t); - return -EBADMSG; - } - - *ret = t; - - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start) { - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - uint8_t c; - int r; - - assert(p); - INIT_REWINDER(rewinder, p); - - r = dns_packet_read_uint8(p, &c, NULL); - if (r < 0) - return r; - - r = dns_packet_read(p, c, ret, NULL); - if (r < 0) - return r; - - if (size) - *size = c; - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -int dns_packet_read_name( - DnsPacket *p, - char **_ret, - bool allow_compression, - size_t *start) { - - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - size_t after_rindex = 0, jump_barrier; - _cleanup_free_ char *ret = NULL; - size_t n = 0, allocated = 0; - bool first = true; - int r; - - assert(p); - assert(_ret); - INIT_REWINDER(rewinder, p); - jump_barrier = p->rindex; - - if (p->refuse_compression) - allow_compression = false; - - for (;;) { - uint8_t c, d; - - r = dns_packet_read_uint8(p, &c, NULL); - if (r < 0) - return r; - - if (c == 0) - /* End of name */ - break; - else if (c <= 63) { - const char *label; - - /* Literal label */ - r = dns_packet_read(p, c, (const void**) &label, NULL); - if (r < 0) - return r; - - if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) - return -ENOMEM; - - if (first) - first = false; - else - ret[n++] = '.'; - - r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); - if (r < 0) - return r; - - n += r; - continue; - } else if (allow_compression && (c & 0xc0) == 0xc0) { - uint16_t ptr; - - /* Pointer */ - r = dns_packet_read_uint8(p, &d, NULL); - if (r < 0) - return r; - - ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d; - if (ptr < DNS_PACKET_HEADER_SIZE || ptr >= jump_barrier) - return -EBADMSG; - - if (after_rindex == 0) - after_rindex = p->rindex; - - /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */ - jump_barrier = ptr; - p->rindex = ptr; - } else - return -EBADMSG; - } - - if (!GREEDY_REALLOC(ret, allocated, n + 1)) - return -ENOMEM; - - ret[n] = 0; - - if (after_rindex != 0) - p->rindex= after_rindex; - - *_ret = ret; - ret = NULL; - - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *start) { - uint8_t window; - uint8_t length; - const uint8_t *bitmap; - uint8_t bit = 0; - unsigned i; - bool found = false; - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - int r; - - assert(p); - assert(types); - INIT_REWINDER(rewinder, p); - - r = bitmap_ensure_allocated(types); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &window, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &length, NULL); - if (r < 0) - return r; - - if (length == 0 || length > 32) - return -EBADMSG; - - r = dns_packet_read(p, length, (const void **)&bitmap, NULL); - if (r < 0) - return r; - - for (i = 0; i < length; i++) { - uint8_t bitmask = 1 << 7; - - if (!bitmap[i]) { - found = false; - bit += 8; - continue; - } - - found = true; - - while (bitmask) { - if (bitmap[i] & bitmask) { - uint16_t n; - - n = (uint16_t) window << 8 | (uint16_t) bit; - - /* Ignore pseudo-types. see RFC4034 section 4.1.2 */ - if (dns_type_is_pseudo(n)) - continue; - - r = bitmap_set(*types, n); - if (r < 0) - return r; - } - - bit++; - bitmask >>= 1; - } - } - - if (!found) - return -EBADMSG; - - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -static int dns_packet_read_type_windows(DnsPacket *p, Bitmap **types, size_t size, size_t *start) { - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - int r; - - INIT_REWINDER(rewinder, p); - - while (p->rindex < rewinder.saved_rindex + size) { - r = dns_packet_read_type_window(p, types, NULL); - if (r < 0) - return r; - - /* don't read past end of current RR */ - if (p->rindex > rewinder.saved_rindex + size) - return -EBADMSG; - } - - if (p->rindex != rewinder.saved_rindex + size) - return -EBADMSG; - - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start) { - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - _cleanup_free_ char *name = NULL; - bool cache_flush = false; - uint16_t class, type; - DnsResourceKey *key; - int r; - - assert(p); - assert(ret); - INIT_REWINDER(rewinder, p); - - r = dns_packet_read_name(p, &name, true, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint16(p, &type, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint16(p, &class, NULL); - if (r < 0) - return r; - - if (p->protocol == DNS_PROTOCOL_MDNS) { - /* See RFC6762, Section 10.2 */ - - if (type != DNS_TYPE_OPT && (class & MDNS_RR_CACHE_FLUSH)) { - class &= ~MDNS_RR_CACHE_FLUSH; - cache_flush = true; - } - } - - key = dns_resource_key_new_consume(class, type, name); - if (!key) - return -ENOMEM; - - name = NULL; - *ret = key; - - if (ret_cache_flush) - *ret_cache_flush = cache_flush; - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -static bool loc_size_ok(uint8_t size) { - uint8_t m = size >> 4, e = size & 0xF; - - return m <= 9 && e <= 9 && (m > 0 || e == 0); -} - -int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - size_t offset; - uint16_t rdlength; - bool cache_flush; - int r; - - assert(p); - assert(ret); - - INIT_REWINDER(rewinder, p); - - r = dns_packet_read_key(p, &key, &cache_flush, NULL); - if (r < 0) - return r; - - if (!dns_class_is_valid_rr(key->class) || !dns_type_is_valid_rr(key->type)) - return -EBADMSG; - - rr = dns_resource_record_new(key); - if (!rr) - return -ENOMEM; - - r = dns_packet_read_uint32(p, &rr->ttl, NULL); - if (r < 0) - return r; - - /* RFC 2181, Section 8, suggests to - * treat a TTL with the MSB set as a zero TTL. */ - if (rr->ttl & UINT32_C(0x80000000)) - rr->ttl = 0; - - r = dns_packet_read_uint16(p, &rdlength, NULL); - if (r < 0) - return r; - - if (p->rindex + rdlength > p->size) - return -EBADMSG; - - offset = p->rindex; - - switch (rr->key->type) { - - case DNS_TYPE_SRV: - r = dns_packet_read_uint16(p, &rr->srv.priority, NULL); - if (r < 0) - return r; - r = dns_packet_read_uint16(p, &rr->srv.weight, NULL); - if (r < 0) - return r; - r = dns_packet_read_uint16(p, &rr->srv.port, NULL); - if (r < 0) - return r; - r = dns_packet_read_name(p, &rr->srv.name, true, NULL); - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - r = dns_packet_read_name(p, &rr->ptr.name, true, NULL); - break; - - case DNS_TYPE_HINFO: - r = dns_packet_read_string(p, &rr->hinfo.cpu, NULL); - if (r < 0) - return r; - - r = dns_packet_read_string(p, &rr->hinfo.os, NULL); - break; - - case DNS_TYPE_SPF: /* exactly the same as TXT */ - case DNS_TYPE_TXT: - if (rdlength <= 0) { - DnsTxtItem *i; - /* RFC 6763, section 6.1 suggests to treat - * empty TXT RRs as equivalent to a TXT record - * with a single empty string. */ - - i = malloc0(offsetof(DnsTxtItem, data) + 1); /* for safety reasons we add an extra NUL byte */ - if (!i) - return -ENOMEM; - - rr->txt.items = i; - } else { - DnsTxtItem *last = NULL; - - while (p->rindex < offset + rdlength) { - DnsTxtItem *i; - const void *data; - size_t sz; - - r = dns_packet_read_raw_string(p, &data, &sz, NULL); - if (r < 0) - return r; - - i = malloc0(offsetof(DnsTxtItem, data) + sz + 1); /* extra NUL byte at the end */ - if (!i) - return -ENOMEM; - - memcpy(i->data, data, sz); - i->length = sz; - - LIST_INSERT_AFTER(items, rr->txt.items, last, i); - last = i; - } - } - - r = 0; - break; - - case DNS_TYPE_A: - r = dns_packet_read_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL); - break; - - case DNS_TYPE_AAAA: - r = dns_packet_read_blob(p, &rr->aaaa.in6_addr, sizeof(struct in6_addr), NULL); - break; - - case DNS_TYPE_SOA: - r = dns_packet_read_name(p, &rr->soa.mname, true, NULL); - if (r < 0) - return r; - - r = dns_packet_read_name(p, &rr->soa.rname, true, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->soa.serial, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->soa.refresh, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->soa.retry, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->soa.expire, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->soa.minimum, NULL); - break; - - case DNS_TYPE_MX: - r = dns_packet_read_uint16(p, &rr->mx.priority, NULL); - if (r < 0) - return r; - - r = dns_packet_read_name(p, &rr->mx.exchange, true, NULL); - break; - - case DNS_TYPE_LOC: { - uint8_t t; - size_t pos; - - r = dns_packet_read_uint8(p, &t, &pos); - if (r < 0) - return r; - - if (t == 0) { - rr->loc.version = t; - - r = dns_packet_read_uint8(p, &rr->loc.size, NULL); - if (r < 0) - return r; - - if (!loc_size_ok(rr->loc.size)) - return -EBADMSG; - - r = dns_packet_read_uint8(p, &rr->loc.horiz_pre, NULL); - if (r < 0) - return r; - - if (!loc_size_ok(rr->loc.horiz_pre)) - return -EBADMSG; - - r = dns_packet_read_uint8(p, &rr->loc.vert_pre, NULL); - if (r < 0) - return r; - - if (!loc_size_ok(rr->loc.vert_pre)) - return -EBADMSG; - - r = dns_packet_read_uint32(p, &rr->loc.latitude, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->loc.longitude, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->loc.altitude, NULL); - if (r < 0) - return r; - - break; - } else { - dns_packet_rewind(p, pos); - rr->unparseable = true; - goto unparseable; - } - } - - case DNS_TYPE_DS: - r = dns_packet_read_uint16(p, &rr->ds.key_tag, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->ds.algorithm, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->ds.digest_type, NULL); - if (r < 0) - return r; - - r = dns_packet_read_memdup(p, rdlength - 4, - &rr->ds.digest, &rr->ds.digest_size, - NULL); - if (r < 0) - return r; - - if (rr->ds.digest_size <= 0) - /* the accepted size depends on the algorithm, but for now - just ensure that the value is greater than zero */ - return -EBADMSG; - - break; - - case DNS_TYPE_SSHFP: - r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->sshfp.fptype, NULL); - if (r < 0) - return r; - - r = dns_packet_read_memdup(p, rdlength - 2, - &rr->sshfp.fingerprint, &rr->sshfp.fingerprint_size, - NULL); - - if (rr->sshfp.fingerprint_size <= 0) - /* the accepted size depends on the algorithm, but for now - just ensure that the value is greater than zero */ - return -EBADMSG; - - break; - - case DNS_TYPE_DNSKEY: - r = dns_packet_read_uint16(p, &rr->dnskey.flags, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->dnskey.protocol, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->dnskey.algorithm, NULL); - if (r < 0) - return r; - - r = dns_packet_read_memdup(p, rdlength - 4, - &rr->dnskey.key, &rr->dnskey.key_size, - NULL); - - if (rr->dnskey.key_size <= 0) - /* the accepted size depends on the algorithm, but for now - just ensure that the value is greater than zero */ - return -EBADMSG; - - break; - - case DNS_TYPE_RRSIG: - r = dns_packet_read_uint16(p, &rr->rrsig.type_covered, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->rrsig.algorithm, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->rrsig.labels, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->rrsig.original_ttl, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->rrsig.expiration, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->rrsig.inception, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint16(p, &rr->rrsig.key_tag, NULL); - if (r < 0) - return r; - - r = dns_packet_read_name(p, &rr->rrsig.signer, false, NULL); - if (r < 0) - return r; - - r = dns_packet_read_memdup(p, offset + rdlength - p->rindex, - &rr->rrsig.signature, &rr->rrsig.signature_size, - NULL); - - if (rr->rrsig.signature_size <= 0) - /* the accepted size depends on the algorithm, but for now - just ensure that the value is greater than zero */ - return -EBADMSG; - - break; - - case DNS_TYPE_NSEC: { - - /* - * RFC6762, section 18.14 explictly states mDNS should use name compression. - * This contradicts RFC3845, section 2.1.1 - */ - - bool allow_compressed = p->protocol == DNS_PROTOCOL_MDNS; - - r = dns_packet_read_name(p, &rr->nsec.next_domain_name, allow_compressed, NULL); - if (r < 0) - return r; - - r = dns_packet_read_type_windows(p, &rr->nsec.types, offset + rdlength - p->rindex, NULL); - - /* We accept empty NSEC bitmaps. The bit indicating the presence of the NSEC record itself - * is redundant and in e.g., RFC4956 this fact is used to define a use for NSEC records - * without the NSEC bit set. */ - - break; - } - case DNS_TYPE_NSEC3: { - uint8_t size; - - r = dns_packet_read_uint8(p, &rr->nsec3.algorithm, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->nsec3.flags, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint16(p, &rr->nsec3.iterations, NULL); - if (r < 0) - return r; - - /* this may be zero */ - r = dns_packet_read_uint8(p, &size, NULL); - if (r < 0) - return r; - - r = dns_packet_read_memdup(p, size, &rr->nsec3.salt, &rr->nsec3.salt_size, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &size, NULL); - if (r < 0) - return r; - - if (size <= 0) - return -EBADMSG; - - r = dns_packet_read_memdup(p, size, - &rr->nsec3.next_hashed_name, &rr->nsec3.next_hashed_name_size, - NULL); - if (r < 0) - return r; - - r = dns_packet_read_type_windows(p, &rr->nsec3.types, offset + rdlength - p->rindex, NULL); - - /* empty non-terminals can have NSEC3 records, so empty bitmaps are allowed */ - - break; - } - - case DNS_TYPE_TLSA: - r = dns_packet_read_uint8(p, &rr->tlsa.cert_usage, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->tlsa.selector, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->tlsa.matching_type, NULL); - if (r < 0) - return r; - - r = dns_packet_read_memdup(p, rdlength - 3, - &rr->tlsa.data, &rr->tlsa.data_size, - NULL); - - if (rr->tlsa.data_size <= 0) - /* the accepted size depends on the algorithm, but for now - just ensure that the value is greater than zero */ - return -EBADMSG; - - break; - - case DNS_TYPE_CAA: - r = dns_packet_read_uint8(p, &rr->caa.flags, NULL); - if (r < 0) - return r; - - r = dns_packet_read_string(p, &rr->caa.tag, NULL); - if (r < 0) - return r; - - r = dns_packet_read_memdup(p, - rdlength + offset - p->rindex, - &rr->caa.value, &rr->caa.value_size, NULL); - - break; - - case DNS_TYPE_OPT: /* we only care about the header of OPT for now. */ - case DNS_TYPE_OPENPGPKEY: - default: - unparseable: - r = dns_packet_read_memdup(p, rdlength, &rr->generic.data, &rr->generic.data_size, NULL); - - break; - } - if (r < 0) - return r; - if (p->rindex != offset + rdlength) - return -EBADMSG; - - *ret = rr; - rr = NULL; - - if (ret_cache_flush) - *ret_cache_flush = cache_flush; - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) { - const uint8_t* p; - bool found_dau_dhu_n3u = false; - size_t l; - - /* Checks whether the specified OPT RR is well-formed and whether it contains RFC6975 data (which is not OK in - * a reply). */ - - assert(rr); - assert(rr->key->type == DNS_TYPE_OPT); - - /* Check that the version is 0 */ - if (((rr->ttl >> 16) & UINT32_C(0xFF)) != 0) - return false; - - p = rr->opt.data; - l = rr->opt.data_size; - while (l > 0) { - uint16_t option_code, option_length; - - /* At least four bytes for OPTION-CODE and OPTION-LENGTH are required */ - if (l < 4U) - return false; - - option_code = unaligned_read_be16(p); - option_length = unaligned_read_be16(p + 2); - - if (l < option_length + 4U) - return false; - - /* RFC 6975 DAU, DHU or N3U fields found. */ - if (IN_SET(option_code, 5, 6, 7)) - found_dau_dhu_n3u = true; - - p += option_length + 4U; - l -= option_length + 4U; - } - - *rfc6975 = found_dau_dhu_n3u; - return true; -} - -int dns_packet_extract(DnsPacket *p) { - _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = {}; - unsigned n, i; - int r; - - if (p->extracted) - return 0; - - INIT_REWINDER(rewinder, p); - dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE); - - n = DNS_PACKET_QDCOUNT(p); - if (n > 0) { - question = dns_question_new(n); - if (!question) - return -ENOMEM; - - for (i = 0; i < n; i++) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - bool cache_flush; - - r = dns_packet_read_key(p, &key, &cache_flush, NULL); - if (r < 0) - return r; - - if (cache_flush) - return -EBADMSG; - - if (!dns_type_is_valid_query(key->type)) - return -EBADMSG; - - r = dns_question_add(question, key); - if (r < 0) - return r; - } - } - - n = DNS_PACKET_RRCOUNT(p); - if (n > 0) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *previous = NULL; - bool bad_opt = false; - - answer = dns_answer_new(n); - if (!answer) - return -ENOMEM; - - for (i = 0; i < n; i++) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - bool cache_flush; - - r = dns_packet_read_rr(p, &rr, &cache_flush, NULL); - if (r < 0) - return r; - - /* Try to reduce memory usage a bit */ - if (previous) - dns_resource_key_reduce(&rr->key, &previous->key); - - if (rr->key->type == DNS_TYPE_OPT) { - bool has_rfc6975; - - if (p->opt || bad_opt) { - /* Multiple OPT RRs? if so, let's ignore all, because there's something wrong - * with the server, and if one is valid we wouldn't know which one. */ - log_debug("Multiple OPT RRs detected, ignoring all."); - bad_opt = true; - continue; - } - - if (!dns_name_is_root(dns_resource_key_name(rr->key))) { - /* If the OPT RR is not owned by the root domain, then it is bad, let's ignore - * it. */ - log_debug("OPT RR is not owned by root domain, ignoring."); - bad_opt = true; - continue; - } - - if (i < DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)) { - /* OPT RR is in the wrong section? Some Belkin routers do this. This is a hint - * the EDNS implementation is borked, like the Belkin one is, hence ignore - * it. */ - log_debug("OPT RR in wrong section, ignoring."); - bad_opt = true; - continue; - } - - if (!opt_is_good(rr, &has_rfc6975)) { - log_debug("Malformed OPT RR, ignoring."); - bad_opt = true; - continue; - } - - if (has_rfc6975) { - /* If the OPT RR contains RFC6975 algorithm data, then this is indication that - * the server just copied the OPT it got from us (which contained that data) - * back into the reply. If so, then it doesn't properly support EDNS, as - * RFC6975 makes it very clear that the algorithm data should only be contained - * in questions, never in replies. Crappy Belkin routers copy the OPT data for - * example, hence let's detect this so that we downgrade early. */ - log_debug("OPT RR contained RFC6975 data, ignoring."); - bad_opt = true; - continue; - } - - p->opt = dns_resource_record_ref(rr); - } else { - - /* According to RFC 4795, section 2.9. only the RRs from the Answer section shall be - * cached. Hence mark only those RRs as cacheable by default, but not the ones from the - * Additional or Authority sections. */ - - r = dns_answer_add(answer, rr, p->ifindex, - (i < DNS_PACKET_ANCOUNT(p) ? DNS_ANSWER_CACHEABLE : 0) | - (p->protocol == DNS_PROTOCOL_MDNS && !cache_flush ? DNS_ANSWER_SHARED_OWNER : 0)); - if (r < 0) - return r; - } - - /* Remember this RR, so that we potentically can merge it's ->key object with the next RR. Note - * that we only do this if we actually decided to keep the RR around. */ - dns_resource_record_unref(previous); - previous = dns_resource_record_ref(rr); - } - - if (bad_opt) - p->opt = dns_resource_record_unref(p->opt); - } - - p->question = question; - question = NULL; - - p->answer = answer; - answer = NULL; - - p->extracted = true; - - /* no CANCEL, always rewind */ - return 0; -} - -int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key) { - int r; - - assert(p); - assert(key); - - /* Checks if the specified packet is a reply for the specified - * key and the specified key is the only one in the question - * section. */ - - if (DNS_PACKET_QR(p) != 1) - return 0; - - /* Let's unpack the packet, if that hasn't happened yet. */ - r = dns_packet_extract(p); - if (r < 0) - return r; - - if (p->question->n_keys != 1) - return 0; - - return dns_resource_key_equal(p->question->keys[0], key); -} - -static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = { - [DNS_RCODE_SUCCESS] = "SUCCESS", - [DNS_RCODE_FORMERR] = "FORMERR", - [DNS_RCODE_SERVFAIL] = "SERVFAIL", - [DNS_RCODE_NXDOMAIN] = "NXDOMAIN", - [DNS_RCODE_NOTIMP] = "NOTIMP", - [DNS_RCODE_REFUSED] = "REFUSED", - [DNS_RCODE_YXDOMAIN] = "YXDOMAIN", - [DNS_RCODE_YXRRSET] = "YRRSET", - [DNS_RCODE_NXRRSET] = "NXRRSET", - [DNS_RCODE_NOTAUTH] = "NOTAUTH", - [DNS_RCODE_NOTZONE] = "NOTZONE", - [DNS_RCODE_BADVERS] = "BADVERS", - [DNS_RCODE_BADKEY] = "BADKEY", - [DNS_RCODE_BADTIME] = "BADTIME", - [DNS_RCODE_BADMODE] = "BADMODE", - [DNS_RCODE_BADNAME] = "BADNAME", - [DNS_RCODE_BADALG] = "BADALG", - [DNS_RCODE_BADTRUNC] = "BADTRUNC", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_rcode, int); - -static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = { - [DNS_PROTOCOL_DNS] = "dns", - [DNS_PROTOCOL_MDNS] = "mdns", - [DNS_PROTOCOL_LLMNR] = "llmnr", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_protocol, DnsProtocol); diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h deleted file mode 100644 index 416335d0a2..0000000000 --- a/src/resolve/resolved-dns-packet.h +++ /dev/null @@ -1,270 +0,0 @@ -#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 -#include - -#include "hashmap.h" -#include "in-addr-util.h" -#include "macro.h" -#include "sparse-endian.h" - -typedef struct DnsPacketHeader DnsPacketHeader; -typedef struct DnsPacket DnsPacket; - -#include "resolved-def.h" -#include "resolved-dns-answer.h" -#include "resolved-dns-question.h" -#include "resolved-dns-rr.h" - -typedef enum DnsProtocol { - DNS_PROTOCOL_DNS, - DNS_PROTOCOL_MDNS, - DNS_PROTOCOL_LLMNR, - _DNS_PROTOCOL_MAX, - _DNS_PROTOCOL_INVALID = -1 -} DnsProtocol; - -struct DnsPacketHeader { - uint16_t id; - be16_t flags; - be16_t qdcount; - be16_t ancount; - be16_t nscount; - be16_t arcount; -}; - -#define DNS_PACKET_HEADER_SIZE sizeof(DnsPacketHeader) -#define UDP_PACKET_HEADER_SIZE (sizeof(struct iphdr) + sizeof(struct udphdr)) - -/* The various DNS protocols deviate in how large a packet can grow, - but the TCP transport has a 16bit size field, hence that appears to - be the absolute maximum. */ -#define DNS_PACKET_SIZE_MAX 0xFFFF - -/* RFC 1035 say 512 is the maximum, for classic unicast DNS */ -#define DNS_PACKET_UNICAST_SIZE_MAX 512 - -/* With EDNS0 we can use larger packets, default to 4096, which is what is commonly used */ -#define DNS_PACKET_UNICAST_SIZE_LARGE_MAX 4096 - -#define DNS_PACKET_SIZE_START 512 - -struct DnsPacket { - int n_ref; - DnsProtocol protocol; - size_t size, allocated, rindex; - void *_data; /* don't access directly, use DNS_PACKET_DATA()! */ - Hashmap *names; /* For name compression */ - size_t opt_start, opt_size; - - /* Parsed data */ - DnsQuestion *question; - DnsAnswer *answer; - DnsResourceRecord *opt; - - /* Packet reception metadata */ - int ifindex; - int family, ipproto; - union in_addr_union sender, destination; - uint16_t sender_port, destination_port; - uint32_t ttl; - - /* For support of truncated packets */ - DnsPacket *more; - - bool on_stack:1; - bool extracted:1; - bool refuse_compression:1; - bool canonical_form:1; -}; - -static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) { - if (_unlikely_(!p)) - return NULL; - - if (p->_data) - return p->_data; - - return ((uint8_t*) p) + ALIGN(sizeof(DnsPacket)); -} - -#define DNS_PACKET_HEADER(p) ((DnsPacketHeader*) DNS_PACKET_DATA(p)) -#define DNS_PACKET_ID(p) DNS_PACKET_HEADER(p)->id -#define DNS_PACKET_QR(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 15) & 1) -#define DNS_PACKET_OPCODE(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 11) & 15) -#define DNS_PACKET_AA(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 10) & 1) -#define DNS_PACKET_TC(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 9) & 1) -#define DNS_PACKET_RD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 8) & 1) -#define DNS_PACKET_RA(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 7) & 1) -#define DNS_PACKET_AD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 5) & 1) -#define DNS_PACKET_CD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 4) & 1) - -static inline uint16_t DNS_PACKET_RCODE(DnsPacket *p) { - uint16_t rcode; - - if (p->opt) - rcode = (uint16_t) (p->opt->ttl >> 24); - else - rcode = 0; - - return rcode | (be16toh(DNS_PACKET_HEADER(p)->flags) & 15); -} - -/* LLMNR defines some bits differently */ -#define DNS_PACKET_LLMNR_C(p) DNS_PACKET_AA(p) -#define DNS_PACKET_LLMNR_T(p) DNS_PACKET_RD(p) - -#define DNS_PACKET_QDCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->qdcount) -#define DNS_PACKET_ANCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->ancount) -#define DNS_PACKET_NSCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->nscount) -#define DNS_PACKET_ARCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->arcount) - -#define DNS_PACKET_MAKE_FLAGS(qr, opcode, aa, tc, rd, ra, ad, cd, rcode) \ - (((uint16_t) !!(qr) << 15) | \ - ((uint16_t) ((opcode) & 15) << 11) | \ - ((uint16_t) !!(aa) << 10) | /* on LLMNR: c */ \ - ((uint16_t) !!(tc) << 9) | \ - ((uint16_t) !!(rd) << 8) | /* on LLMNR: t */ \ - ((uint16_t) !!(ra) << 7) | \ - ((uint16_t) !!(ad) << 5) | \ - ((uint16_t) !!(cd) << 4) | \ - ((uint16_t) ((rcode) & 15))) - -static inline unsigned DNS_PACKET_RRCOUNT(DnsPacket *p) { - return - (unsigned) DNS_PACKET_ANCOUNT(p) + - (unsigned) DNS_PACKET_NSCOUNT(p) + - (unsigned) DNS_PACKET_ARCOUNT(p); -} - -int dns_packet_new(DnsPacket **p, DnsProtocol protocol, size_t mtu); -int dns_packet_new_query(DnsPacket **p, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled); - -void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated); - -DnsPacket *dns_packet_ref(DnsPacket *p); -DnsPacket *dns_packet_unref(DnsPacket *p); - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsPacket*, dns_packet_unref); - -int dns_packet_validate(DnsPacket *p); -int dns_packet_validate_reply(DnsPacket *p); -int dns_packet_validate_query(DnsPacket *p); - -int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key); - -int dns_packet_append_blob(DnsPacket *p, const void *d, size_t sz, size_t *start); -int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start); -int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start); -int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start); -int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start); -int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start); -int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, bool canonical_candidate, size_t *start); -int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, bool canonical_candidate, size_t *start); -int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, size_t *start); -int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start); -int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start); - -void dns_packet_truncate(DnsPacket *p, size_t sz); -int dns_packet_truncate_opt(DnsPacket *p); - -int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start); -int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start); -int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start); -int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start); -int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start); -int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start); -int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start); -int dns_packet_read_name(DnsPacket *p, char **ret, bool allow_compression, size_t *start); -int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start); -int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start); - -void dns_packet_rewind(DnsPacket *p, size_t idx); - -int dns_packet_skip_question(DnsPacket *p); -int dns_packet_extract(DnsPacket *p); - -static inline bool DNS_PACKET_SHALL_CACHE(DnsPacket *p) { - /* Never cache data originating from localhost, under the - * assumption, that it's coming from a locally DNS forwarder - * or server, that is caching on its own. */ - - return in_addr_is_localhost(p->family, &p->sender) == 0; -} - -/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 */ -enum { - DNS_RCODE_SUCCESS = 0, - DNS_RCODE_FORMERR = 1, - DNS_RCODE_SERVFAIL = 2, - DNS_RCODE_NXDOMAIN = 3, - DNS_RCODE_NOTIMP = 4, - DNS_RCODE_REFUSED = 5, - DNS_RCODE_YXDOMAIN = 6, - DNS_RCODE_YXRRSET = 7, - DNS_RCODE_NXRRSET = 8, - DNS_RCODE_NOTAUTH = 9, - DNS_RCODE_NOTZONE = 10, - DNS_RCODE_BADVERS = 16, - DNS_RCODE_BADSIG = 16, /* duplicate value! */ - DNS_RCODE_BADKEY = 17, - DNS_RCODE_BADTIME = 18, - DNS_RCODE_BADMODE = 19, - DNS_RCODE_BADNAME = 20, - DNS_RCODE_BADALG = 21, - DNS_RCODE_BADTRUNC = 22, - _DNS_RCODE_MAX_DEFINED -}; - -const char* dns_rcode_to_string(int i) _const_; -int dns_rcode_from_string(const char *s) _pure_; - -const char* dns_protocol_to_string(DnsProtocol p) _const_; -DnsProtocol dns_protocol_from_string(const char *s) _pure_; - -#define LLMNR_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 252U) }) -#define LLMNR_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03 } }) - -#define MDNS_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 251U) }) -#define MDNS_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb } }) - -static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family, bool authenticated) { - uint64_t f; - - /* Converts a protocol + family into a flags field as used in queries and responses */ - - f = authenticated ? SD_RESOLVED_AUTHENTICATED : 0; - - switch (protocol) { - case DNS_PROTOCOL_DNS: - return f|SD_RESOLVED_DNS; - - case DNS_PROTOCOL_LLMNR: - return f|(family == AF_INET6 ? SD_RESOLVED_LLMNR_IPV6 : SD_RESOLVED_LLMNR_IPV4); - - case DNS_PROTOCOL_MDNS: - return f|(family == AF_INET6 ? SD_RESOLVED_MDNS_IPV6 : SD_RESOLVED_MDNS_IPV4); - - default: - return f; - } -} diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c deleted file mode 100644 index ea04e58d61..0000000000 --- a/src/resolve/resolved-dns-query.c +++ /dev/null @@ -1,1109 +0,0 @@ -/*** - 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 "alloc-util.h" -#include "dns-domain.h" -#include "dns-type.h" -#include "hostname-util.h" -#include "local-addresses.h" -#include "resolved-dns-query.h" -#include "resolved-dns-synthesize.h" -#include "resolved-etc-hosts.h" -#include "string-util.h" - -/* How long to wait for the query in total */ -#define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC) - -#define CNAME_MAX 8 -#define QUERIES_MAX 2048 -#define AUXILIARY_QUERIES_MAX 64 - -static int dns_query_candidate_new(DnsQueryCandidate **ret, DnsQuery *q, DnsScope *s) { - DnsQueryCandidate *c; - - assert(ret); - assert(q); - assert(s); - - c = new0(DnsQueryCandidate, 1); - if (!c) - return -ENOMEM; - - c->query = q; - c->scope = s; - - LIST_PREPEND(candidates_by_query, q->candidates, c); - LIST_PREPEND(candidates_by_scope, s->query_candidates, c); - - *ret = c; - return 0; -} - -static void dns_query_candidate_stop(DnsQueryCandidate *c) { - DnsTransaction *t; - - assert(c); - - while ((t = set_steal_first(c->transactions))) { - set_remove(t->notify_query_candidates, c); - set_remove(t->notify_query_candidates_done, c); - dns_transaction_gc(t); - } -} - -DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c) { - - if (!c) - return NULL; - - dns_query_candidate_stop(c); - - set_free(c->transactions); - dns_search_domain_unref(c->search_domain); - - if (c->query) - LIST_REMOVE(candidates_by_query, c->query->candidates, c); - - if (c->scope) - LIST_REMOVE(candidates_by_scope, c->scope->query_candidates, c); - - free(c); - - return NULL; -} - -static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) { - DnsSearchDomain *next = NULL; - - assert(c); - - if (c->search_domain && c->search_domain->linked) - next = c->search_domain->domains_next; - else - next = dns_scope_get_search_domains(c->scope); - - for (;;) { - if (!next) /* We hit the end of the list */ - return 0; - - if (!next->route_only) - break; - - /* Skip over route-only domains */ - next = next->domains_next; - } - - dns_search_domain_unref(c->search_domain); - c->search_domain = dns_search_domain_ref(next); - - return 1; -} - -static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResourceKey *key) { - DnsTransaction *t; - int r; - - assert(c); - assert(key); - - t = dns_scope_find_transaction(c->scope, key, true); - if (!t) { - r = dns_transaction_new(&t, c->scope, key); - if (r < 0) - return r; - } else { - if (set_contains(c->transactions, t)) - return 0; - } - - r = set_ensure_allocated(&c->transactions, NULL); - if (r < 0) - goto gc; - - r = set_ensure_allocated(&t->notify_query_candidates, NULL); - if (r < 0) - goto gc; - - r = set_ensure_allocated(&t->notify_query_candidates_done, NULL); - if (r < 0) - goto gc; - - r = set_put(t->notify_query_candidates, c); - if (r < 0) - goto gc; - - r = set_put(c->transactions, t); - if (r < 0) { - (void) set_remove(t->notify_query_candidates, c); - goto gc; - } - - return 1; - -gc: - dns_transaction_gc(t); - return r; -} - -static int dns_query_candidate_go(DnsQueryCandidate *c) { - DnsTransaction *t; - Iterator i; - int r; - unsigned n = 0; - - assert(c); - - /* Start the transactions that are not started yet */ - SET_FOREACH(t, c->transactions, i) { - if (t->state != DNS_TRANSACTION_NULL) - continue; - - r = dns_transaction_go(t); - if (r < 0) - return r; - - n++; - } - - /* If there was nothing to start, then let's proceed immediately */ - if (n == 0) - dns_query_candidate_notify(c); - - return 0; -} - -static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { - DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; - DnsTransaction *t; - Iterator i; - - assert(c); - - if (c->error_code != 0) - return DNS_TRANSACTION_ERRNO; - - SET_FOREACH(t, c->transactions, i) { - - switch (t->state) { - - case DNS_TRANSACTION_NULL: - /* If there's a NULL transaction pending, then - * this means not all transactions where - * started yet, and we were called from within - * the stackframe that is supposed to start - * remaining transactions. In this case, - * simply claim the candidate is pending. */ - - case DNS_TRANSACTION_PENDING: - case DNS_TRANSACTION_VALIDATING: - /* If there's one transaction currently in - * VALIDATING state, then this means there's - * also one in PENDING state, hence we can - * return PENDING immediately. */ - return DNS_TRANSACTION_PENDING; - - case DNS_TRANSACTION_SUCCESS: - state = t->state; - break; - - default: - if (state != DNS_TRANSACTION_SUCCESS) - state = t->state; - - break; - } - } - - return state; -} - -static bool dns_query_candidate_is_routable(DnsQueryCandidate *c, uint16_t type) { - int family; - - assert(c); - - /* Checks whether the specified RR type matches an address family that is routable on the link(s) the scope of - * this candidate belongs to. Specifically, whether there's a routable IPv4 address on it if we query an A RR, - * or a routable IPv6 address if we query an AAAA RR. */ - - if (!c->query->suppress_unroutable_family) - return true; - - if (c->scope->protocol != DNS_PROTOCOL_DNS) - return true; - - family = dns_type_to_af(type); - if (family < 0) - return true; - - if (c->scope->link) - return link_relevant(c->scope->link, family, false); - else - return manager_routable(c->scope->manager, family); -} - -static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) { - DnsQuestion *question; - DnsResourceKey *key; - int n = 0, r; - - assert(c); - - dns_query_candidate_stop(c); - - question = dns_query_question_for_protocol(c->query, c->scope->protocol); - - /* Create one transaction per question key */ - DNS_QUESTION_FOREACH(key, question) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *new_key = NULL; - DnsResourceKey *qkey; - - if (!dns_query_candidate_is_routable(c, key->type)) - continue; - - if (c->search_domain) { - r = dns_resource_key_new_append_suffix(&new_key, key, c->search_domain->name); - if (r < 0) - goto fail; - - qkey = new_key; - } else - qkey = key; - - if (!dns_scope_good_key(c->scope, qkey)) - continue; - - r = dns_query_candidate_add_transaction(c, qkey); - if (r < 0) - goto fail; - - n++; - } - - return n; - -fail: - dns_query_candidate_stop(c); - return r; -} - -void dns_query_candidate_notify(DnsQueryCandidate *c) { - DnsTransactionState state; - int r; - - assert(c); - - state = dns_query_candidate_state(c); - - if (DNS_TRANSACTION_IS_LIVE(state)) - return; - - if (state != DNS_TRANSACTION_SUCCESS && c->search_domain) { - - r = dns_query_candidate_next_search_domain(c); - if (r < 0) - goto fail; - - if (r > 0) { - /* OK, there's another search domain to try, let's do so. */ - - r = dns_query_candidate_setup_transactions(c); - if (r < 0) - goto fail; - - if (r > 0) { - /* New transactions where queued. Start them and wait */ - - r = dns_query_candidate_go(c); - if (r < 0) - goto fail; - - return; - } - } - - } - - dns_query_ready(c->query); - return; - -fail: - log_warning_errno(r, "Failed to follow search domains: %m"); - c->error_code = r; - dns_query_ready(c->query); -} - -static void dns_query_stop(DnsQuery *q) { - DnsQueryCandidate *c; - - assert(q); - - q->timeout_event_source = sd_event_source_unref(q->timeout_event_source); - - LIST_FOREACH(candidates_by_query, c, q->candidates) - dns_query_candidate_stop(c); -} - -static void dns_query_free_candidates(DnsQuery *q) { - assert(q); - - while (q->candidates) - dns_query_candidate_free(q->candidates); -} - -static void dns_query_reset_answer(DnsQuery *q) { - assert(q); - - q->answer = dns_answer_unref(q->answer); - q->answer_rcode = 0; - q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; - q->answer_errno = 0; - q->answer_authenticated = false; - q->answer_protocol = _DNS_PROTOCOL_INVALID; - q->answer_family = AF_UNSPEC; - q->answer_search_domain = dns_search_domain_unref(q->answer_search_domain); -} - -DnsQuery *dns_query_free(DnsQuery *q) { - if (!q) - return NULL; - - while (q->auxiliary_queries) - dns_query_free(q->auxiliary_queries); - - if (q->auxiliary_for) { - assert(q->auxiliary_for->n_auxiliary_queries > 0); - q->auxiliary_for->n_auxiliary_queries--; - LIST_REMOVE(auxiliary_queries, q->auxiliary_for->auxiliary_queries, q); - } - - dns_query_free_candidates(q); - - dns_question_unref(q->question_idna); - dns_question_unref(q->question_utf8); - - dns_query_reset_answer(q); - - sd_bus_message_unref(q->request); - sd_bus_track_unref(q->bus_track); - - free(q->request_address_string); - - if (q->manager) { - LIST_REMOVE(queries, q->manager->dns_queries, q); - q->manager->n_dns_queries--; - } - - free(q); - - return NULL; -} - -int dns_query_new( - Manager *m, - DnsQuery **ret, - DnsQuestion *question_utf8, - DnsQuestion *question_idna, - int ifindex, uint64_t flags) { - - _cleanup_(dns_query_freep) DnsQuery *q = NULL; - DnsResourceKey *key; - bool good = false; - int r; - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - - assert(m); - - if (dns_question_size(question_utf8) > 0) { - r = dns_question_is_valid_for_query(question_utf8); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - good = true; - } - - /* If the IDNA and UTF8 questions are the same, merge their references */ - r = dns_question_is_equal(question_idna, question_utf8); - if (r < 0) - return r; - if (r > 0) - question_idna = question_utf8; - else { - if (dns_question_size(question_idna) > 0) { - r = dns_question_is_valid_for_query(question_idna); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - good = true; - } - } - - if (!good) /* don't allow empty queries */ - return -EINVAL; - - if (m->n_dns_queries >= QUERIES_MAX) - return -EBUSY; - - q = new0(DnsQuery, 1); - if (!q) - return -ENOMEM; - - q->question_utf8 = dns_question_ref(question_utf8); - q->question_idna = dns_question_ref(question_idna); - q->ifindex = ifindex; - q->flags = flags; - q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; - q->answer_protocol = _DNS_PROTOCOL_INVALID; - q->answer_family = AF_UNSPEC; - - /* First dump UTF8 question */ - DNS_QUESTION_FOREACH(key, question_utf8) - log_debug("Looking up RR for %s.", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - - /* And then dump the IDNA question, but only what hasn't been dumped already through the UTF8 question. */ - DNS_QUESTION_FOREACH(key, question_idna) { - r = dns_question_contains(question_utf8, key); - if (r < 0) - return r; - if (r > 0) - continue; - - log_debug("Looking up IDNA RR for %s.", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - } - - LIST_PREPEND(queries, m->dns_queries, q); - m->n_dns_queries++; - q->manager = m; - - if (ret) - *ret = q; - q = NULL; - - return 0; -} - -int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for) { - assert(q); - assert(auxiliary_for); - - /* Ensure that that the query is not auxiliary yet, and - * nothing else is auxiliary to it either */ - assert(!q->auxiliary_for); - assert(!q->auxiliary_queries); - - /* Ensure that the unit we shall be made auxiliary for isn't - * auxiliary itself */ - assert(!auxiliary_for->auxiliary_for); - - if (auxiliary_for->n_auxiliary_queries >= AUXILIARY_QUERIES_MAX) - return -EAGAIN; - - LIST_PREPEND(auxiliary_queries, auxiliary_for->auxiliary_queries, q); - q->auxiliary_for = auxiliary_for; - - auxiliary_for->n_auxiliary_queries++; - return 0; -} - -static void dns_query_complete(DnsQuery *q, DnsTransactionState state) { - assert(q); - assert(!DNS_TRANSACTION_IS_LIVE(state)); - assert(DNS_TRANSACTION_IS_LIVE(q->state)); - - /* Note that this call might invalidate the query. Callers - * should hence not attempt to access the query or transaction - * after calling this function. */ - - q->state = state; - - dns_query_stop(q); - if (q->complete) - q->complete(q); -} - -static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) { - DnsQuery *q = userdata; - - assert(s); - assert(q); - - dns_query_complete(q, DNS_TRANSACTION_TIMEOUT); - return 0; -} - -static int dns_query_add_candidate(DnsQuery *q, DnsScope *s) { - DnsQueryCandidate *c; - int r; - - assert(q); - assert(s); - - r = dns_query_candidate_new(&c, q, s); - if (r < 0) - return r; - - /* If this a single-label domain on DNS, we might append a suitable search domain first. */ - if ((q->flags & SD_RESOLVED_NO_SEARCH) == 0) { - r = dns_scope_name_needs_search_domain(s, dns_question_first_name(q->question_idna)); - if (r < 0) - goto fail; - if (r > 0) { - /* OK, we need a search domain now. Let's find one for this scope */ - - r = dns_query_candidate_next_search_domain(c); - if (r <= 0) /* if there's no search domain, then we won't add any transaction. */ - goto fail; - } - } - - r = dns_query_candidate_setup_transactions(c); - if (r < 0) - goto fail; - - return 0; - -fail: - dns_query_candidate_free(c); - return r; -} - -static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - int r; - - assert(q); - assert(state); - - /* Tries to synthesize localhost RR replies (and others) where appropriate. Note that this is done *after* the - * the normal lookup finished. The data from the network hence takes precedence over the data we - * synthesize. (But note that many scopes refuse to resolve certain domain names) */ - - if (!IN_SET(*state, - DNS_TRANSACTION_RCODE_FAILURE, - DNS_TRANSACTION_NO_SERVERS, - DNS_TRANSACTION_TIMEOUT, - DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, - DNS_TRANSACTION_NETWORK_DOWN, - DNS_TRANSACTION_NOT_FOUND)) - return 0; - - r = dns_synthesize_answer( - q->manager, - q->question_utf8, - q->ifindex, - &answer); - - if (r <= 0) - return r; - - dns_query_reset_answer(q); - - q->answer = answer; - answer = NULL; - q->answer_rcode = DNS_RCODE_SUCCESS; - q->answer_protocol = dns_synthesize_protocol(q->flags); - q->answer_family = dns_synthesize_family(q->flags); - q->answer_authenticated = true; - - *state = DNS_TRANSACTION_SUCCESS; - - return 1; -} - -static int dns_query_try_etc_hosts(DnsQuery *q) { - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - int r; - - assert(q); - - /* Looks in /etc/hosts for matching entries. Note that this is done *before* the normal lookup is done. The - * data from /etc/hosts hence takes precedence over the network. */ - - r = manager_etc_hosts_lookup( - q->manager, - q->question_utf8, - &answer); - if (r <= 0) - return r; - - dns_query_reset_answer(q); - - q->answer = answer; - answer = NULL; - q->answer_rcode = DNS_RCODE_SUCCESS; - q->answer_protocol = dns_synthesize_protocol(q->flags); - q->answer_family = dns_synthesize_family(q->flags); - q->answer_authenticated = true; - - return 1; -} - -int dns_query_go(DnsQuery *q) { - DnsScopeMatch found = DNS_SCOPE_NO; - DnsScope *s, *first = NULL; - DnsQueryCandidate *c; - int r; - - assert(q); - - if (q->state != DNS_TRANSACTION_NULL) - return 0; - - r = dns_query_try_etc_hosts(q); - if (r < 0) - return r; - if (r > 0) { - dns_query_complete(q, DNS_TRANSACTION_SUCCESS); - return 1; - } - - LIST_FOREACH(scopes, s, q->manager->dns_scopes) { - DnsScopeMatch match; - const char *name; - - name = dns_question_first_name(dns_query_question_for_protocol(q, s->protocol)); - if (!name) - continue; - - match = dns_scope_good_domain(s, q->ifindex, q->flags, name); - if (match < 0) - return match; - - if (match == DNS_SCOPE_NO) - continue; - - found = match; - - if (match == DNS_SCOPE_YES) { - first = s; - break; - } else { - assert(match == DNS_SCOPE_MAYBE); - - if (!first) - first = s; - } - } - - if (found == DNS_SCOPE_NO) { - DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; - - r = dns_query_synthesize_reply(q, &state); - if (r < 0) - return r; - - dns_query_complete(q, state); - return 1; - } - - r = dns_query_add_candidate(q, first); - if (r < 0) - goto fail; - - LIST_FOREACH(scopes, s, first->scopes_next) { - DnsScopeMatch match; - const char *name; - - name = dns_question_first_name(dns_query_question_for_protocol(q, s->protocol)); - if (!name) - continue; - - match = dns_scope_good_domain(s, q->ifindex, q->flags, name); - if (match < 0) - goto fail; - - if (match != found) - continue; - - r = dns_query_add_candidate(q, s); - if (r < 0) - goto fail; - } - - dns_query_reset_answer(q); - - r = sd_event_add_time( - q->manager->event, - &q->timeout_event_source, - clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()) + QUERY_TIMEOUT_USEC, 0, - on_query_timeout, q); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(q->timeout_event_source, "query-timeout"); - - q->state = DNS_TRANSACTION_PENDING; - q->block_ready++; - - /* Start the transactions */ - LIST_FOREACH(candidates_by_query, c, q->candidates) { - r = dns_query_candidate_go(c); - if (r < 0) { - q->block_ready--; - goto fail; - } - } - - q->block_ready--; - dns_query_ready(q); - - return 1; - -fail: - dns_query_stop(q); - return r; -} - -static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { - DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; - bool has_authenticated = false, has_non_authenticated = false; - DnssecResult dnssec_result_authenticated = _DNSSEC_RESULT_INVALID, dnssec_result_non_authenticated = _DNSSEC_RESULT_INVALID; - DnsTransaction *t; - Iterator i; - int r; - - assert(q); - - if (!c) { - r = dns_query_synthesize_reply(q, &state); - if (r < 0) - goto fail; - - dns_query_complete(q, state); - return; - } - - if (c->error_code != 0) { - /* If the candidate had an error condition of its own, start with that. */ - state = DNS_TRANSACTION_ERRNO; - q->answer = dns_answer_unref(q->answer); - q->answer_rcode = 0; - q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; - q->answer_errno = c->error_code; - } - - SET_FOREACH(t, c->transactions, i) { - - switch (t->state) { - - case DNS_TRANSACTION_SUCCESS: { - /* We found a successfully reply, merge it into the answer */ - r = dns_answer_extend(&q->answer, t->answer); - if (r < 0) - goto fail; - - q->answer_rcode = t->answer_rcode; - q->answer_errno = 0; - - if (t->answer_authenticated) { - has_authenticated = true; - dnssec_result_authenticated = t->answer_dnssec_result; - } else { - has_non_authenticated = true; - dnssec_result_non_authenticated = t->answer_dnssec_result; - } - - state = DNS_TRANSACTION_SUCCESS; - break; - } - - case DNS_TRANSACTION_NULL: - case DNS_TRANSACTION_PENDING: - case DNS_TRANSACTION_VALIDATING: - case DNS_TRANSACTION_ABORTED: - /* Ignore transactions that didn't complete */ - continue; - - default: - /* Any kind of failure? Store the data away, - * if there's nothing stored yet. */ - - if (state == DNS_TRANSACTION_SUCCESS) - continue; - - q->answer = dns_answer_unref(q->answer); - q->answer_rcode = t->answer_rcode; - q->answer_dnssec_result = t->answer_dnssec_result; - q->answer_errno = t->answer_errno; - - state = t->state; - break; - } - } - - if (state == DNS_TRANSACTION_SUCCESS) { - q->answer_authenticated = has_authenticated && !has_non_authenticated; - q->answer_dnssec_result = q->answer_authenticated ? dnssec_result_authenticated : dnssec_result_non_authenticated; - } - - q->answer_protocol = c->scope->protocol; - q->answer_family = c->scope->family; - - dns_search_domain_unref(q->answer_search_domain); - q->answer_search_domain = dns_search_domain_ref(c->search_domain); - - r = dns_query_synthesize_reply(q, &state); - if (r < 0) - goto fail; - - dns_query_complete(q, state); - return; - -fail: - q->answer_errno = -r; - dns_query_complete(q, DNS_TRANSACTION_ERRNO); -} - -void dns_query_ready(DnsQuery *q) { - - DnsQueryCandidate *bad = NULL, *c; - bool pending = false; - - assert(q); - assert(DNS_TRANSACTION_IS_LIVE(q->state)); - - /* Note that this call might invalidate the query. Callers - * should hence not attempt to access the query or transaction - * after calling this function, unless the block_ready - * counter was explicitly bumped before doing so. */ - - if (q->block_ready > 0) - return; - - LIST_FOREACH(candidates_by_query, c, q->candidates) { - DnsTransactionState state; - - state = dns_query_candidate_state(c); - switch (state) { - - case DNS_TRANSACTION_SUCCESS: - /* One of the candidates is successful, - * let's use it, and copy its data out */ - dns_query_accept(q, c); - return; - - case DNS_TRANSACTION_NULL: - case DNS_TRANSACTION_PENDING: - case DNS_TRANSACTION_VALIDATING: - /* One of the candidates is still going on, - * let's maybe wait for it */ - pending = true; - break; - - default: - /* Any kind of failure */ - bad = c; - break; - } - } - - if (pending) - return; - - dns_query_accept(q, bad); -} - -static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) { - _cleanup_(dns_question_unrefp) DnsQuestion *nq_idna = NULL, *nq_utf8 = NULL; - int r, k; - - assert(q); - - q->n_cname_redirects++; - if (q->n_cname_redirects > CNAME_MAX) - return -ELOOP; - - r = dns_question_cname_redirect(q->question_idna, cname, &nq_idna); - if (r < 0) - return r; - else if (r > 0) - log_debug("Following CNAME/DNAME %s → %s.", dns_question_first_name(q->question_idna), dns_question_first_name(nq_idna)); - - k = dns_question_is_equal(q->question_idna, q->question_utf8); - if (k < 0) - return r; - if (k > 0) { - /* Same question? Shortcut new question generation */ - nq_utf8 = dns_question_ref(nq_idna); - k = r; - } else { - k = dns_question_cname_redirect(q->question_utf8, cname, &nq_utf8); - if (k < 0) - return k; - else if (k > 0) - log_debug("Following UTF8 CNAME/DNAME %s → %s.", dns_question_first_name(q->question_utf8), dns_question_first_name(nq_utf8)); - } - - if (r == 0 && k == 0) /* No actual cname happened? */ - return -ELOOP; - - if (q->answer_protocol == DNS_PROTOCOL_DNS) { - /* Don't permit CNAME redirects from unicast DNS to LLMNR or MulticastDNS, so that global resources - * cannot invade the local namespace. The opposite way we permit: local names may redirect to global - * ones. */ - - q->flags &= ~(SD_RESOLVED_LLMNR|SD_RESOLVED_MDNS); /* mask away the local protocols */ - } - - /* Turn off searching for the new name */ - q->flags |= SD_RESOLVED_NO_SEARCH; - - dns_question_unref(q->question_idna); - q->question_idna = nq_idna; - nq_idna = NULL; - - dns_question_unref(q->question_utf8); - q->question_utf8 = nq_utf8; - nq_utf8 = NULL; - - dns_query_free_candidates(q); - dns_query_reset_answer(q); - - q->state = DNS_TRANSACTION_NULL; - - return 0; -} - -int dns_query_process_cname(DnsQuery *q) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *cname = NULL; - DnsQuestion *question; - DnsResourceRecord *rr; - int r; - - assert(q); - - if (!IN_SET(q->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_NULL)) - return DNS_QUERY_NOMATCH; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - - DNS_ANSWER_FOREACH(rr, q->answer) { - r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); - if (r < 0) - return r; - if (r > 0) - return DNS_QUERY_MATCH; /* The answer matches directly, no need to follow cnames */ - - r = dns_question_matches_cname_or_dname(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); - if (r < 0) - return r; - if (r > 0 && !cname) - cname = dns_resource_record_ref(rr); - } - - if (!cname) - return DNS_QUERY_NOMATCH; /* No match and no cname to follow */ - - if (q->flags & SD_RESOLVED_NO_CNAME) - return -ELOOP; - - /* OK, let's actually follow the CNAME */ - r = dns_query_cname_redirect(q, cname); - if (r < 0) - return r; - - /* Let's see if the answer can already answer the new - * redirected question */ - r = dns_query_process_cname(q); - if (r != DNS_QUERY_NOMATCH) - return r; - - /* OK, it cannot, let's begin with the new query */ - r = dns_query_go(q); - if (r < 0) - return r; - - return DNS_QUERY_RESTARTED; /* We restarted the query for a new cname */ -} - -static int on_bus_track(sd_bus_track *t, void *userdata) { - DnsQuery *q = userdata; - - assert(t); - assert(q); - - log_debug("Client of active query vanished, aborting query."); - dns_query_complete(q, DNS_TRANSACTION_ABORTED); - return 0; -} - -int dns_query_bus_track(DnsQuery *q, sd_bus_message *m) { - int r; - - assert(q); - assert(m); - - if (!q->bus_track) { - r = sd_bus_track_new(sd_bus_message_get_bus(m), &q->bus_track, on_bus_track, q); - if (r < 0) - return r; - } - - r = sd_bus_track_add_sender(q->bus_track, m); - if (r < 0) - return r; - - return 0; -} - -DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol) { - assert(q); - - switch (protocol) { - - case DNS_PROTOCOL_DNS: - return q->question_idna; - - case DNS_PROTOCOL_MDNS: - case DNS_PROTOCOL_LLMNR: - return q->question_utf8; - - default: - return NULL; - } -} - -const char *dns_query_string(DnsQuery *q) { - const char *name; - int r; - - /* Returns a somewhat useful human-readable lookup key string for this query */ - - if (q->request_address_string) - return q->request_address_string; - - if (q->request_address_valid) { - r = in_addr_to_string(q->request_family, &q->request_address, &q->request_address_string); - if (r >= 0) - return q->request_address_string; - } - - name = dns_question_first_name(q->question_utf8); - if (name) - return name; - - return dns_question_first_name(q->question_idna); -} diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h deleted file mode 100644 index c2ac02f68b..0000000000 --- a/src/resolve/resolved-dns-query.h +++ /dev/null @@ -1,133 +0,0 @@ -#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 "sd-bus.h" - -#include "set.h" - -typedef struct DnsQueryCandidate DnsQueryCandidate; -typedef struct DnsQuery DnsQuery; - -#include "resolved-dns-answer.h" -#include "resolved-dns-question.h" -#include "resolved-dns-stream.h" -#include "resolved-dns-search-domain.h" - -struct DnsQueryCandidate { - DnsQuery *query; - DnsScope *scope; - - DnsSearchDomain *search_domain; - - int error_code; - Set *transactions; - - LIST_FIELDS(DnsQueryCandidate, candidates_by_query); - LIST_FIELDS(DnsQueryCandidate, candidates_by_scope); -}; - -struct DnsQuery { - Manager *manager; - - /* When resolving a service, we first create a TXT+SRV query, - * and then for the hostnames we discover auxiliary A+AAAA - * queries. This pointer always points from the auxiliary - * queries back to the TXT+SRV query. */ - DnsQuery *auxiliary_for; - LIST_HEAD(DnsQuery, auxiliary_queries); - unsigned n_auxiliary_queries; - int auxiliary_result; - - /* The question, formatted in IDNA for use on classic DNS, and as UTF8 for use in LLMNR or mDNS. Note that even - * on classic DNS some labels might use UTF8 encoding. Specifically, DNS-SD service names (in contrast to their - * domain suffixes) use UTF-8 encoding even on DNS. Thus, the difference between these two fields is mostly - * relevant only for explicit *hostname* lookups as well as the domain suffixes of service lookups. */ - DnsQuestion *question_idna; - DnsQuestion *question_utf8; - - uint64_t flags; - int ifindex; - - /* If true, A or AAAA RR lookups will be suppressed on links with no routable address of the matching address - * family */ - bool suppress_unroutable_family; - - DnsTransactionState state; - unsigned n_cname_redirects; - - LIST_HEAD(DnsQueryCandidate, candidates); - sd_event_source *timeout_event_source; - - /* Discovered data */ - DnsAnswer *answer; - int answer_rcode; - DnssecResult answer_dnssec_result; - bool answer_authenticated; - DnsProtocol answer_protocol; - int answer_family; - DnsSearchDomain *answer_search_domain; - int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */ - - /* Bus client information */ - sd_bus_message *request; - int request_family; - bool request_address_valid; - union in_addr_union request_address; - unsigned block_all_complete; - char *request_address_string; - - /* Completion callback */ - void (*complete)(DnsQuery* q); - unsigned block_ready; - - sd_bus_track *bus_track; - - LIST_FIELDS(DnsQuery, queries); - LIST_FIELDS(DnsQuery, auxiliary_queries); -}; - -enum { - DNS_QUERY_MATCH, - DNS_QUERY_NOMATCH, - DNS_QUERY_RESTARTED, -}; - -DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c); -void dns_query_candidate_notify(DnsQueryCandidate *c); - -int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question_utf8, DnsQuestion *question_idna, int family, uint64_t flags); -DnsQuery *dns_query_free(DnsQuery *q); - -int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for); - -int dns_query_go(DnsQuery *q); -void dns_query_ready(DnsQuery *q); - -int dns_query_process_cname(DnsQuery *q); - -int dns_query_bus_track(DnsQuery *q, sd_bus_message *m); - -DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol); - -const char *dns_query_string(DnsQuery *q); - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuery*, dns_query_free); diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c deleted file mode 100644 index c8b502d1cd..0000000000 --- a/src/resolve/resolved-dns-question.c +++ /dev/null @@ -1,468 +0,0 @@ -/*** - 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 "alloc-util.h" -#include "dns-domain.h" -#include "dns-type.h" -#include "resolved-dns-question.h" - -DnsQuestion *dns_question_new(unsigned n) { - DnsQuestion *q; - - assert(n > 0); - - q = malloc0(offsetof(DnsQuestion, keys) + sizeof(DnsResourceKey*) * n); - if (!q) - return NULL; - - q->n_ref = 1; - q->n_allocated = n; - - return q; -} - -DnsQuestion *dns_question_ref(DnsQuestion *q) { - if (!q) - return NULL; - - assert(q->n_ref > 0); - q->n_ref++; - return q; -} - -DnsQuestion *dns_question_unref(DnsQuestion *q) { - if (!q) - return NULL; - - assert(q->n_ref > 0); - - if (q->n_ref == 1) { - unsigned i; - - for (i = 0; i < q->n_keys; i++) - dns_resource_key_unref(q->keys[i]); - free(q); - } else - q->n_ref--; - - return NULL; -} - -int dns_question_add(DnsQuestion *q, DnsResourceKey *key) { - unsigned i; - int r; - - assert(key); - - if (!q) - return -ENOSPC; - - for (i = 0; i < q->n_keys; i++) { - r = dns_resource_key_equal(q->keys[i], key); - if (r < 0) - return r; - if (r > 0) - return 0; - } - - if (q->n_keys >= q->n_allocated) - return -ENOSPC; - - q->keys[q->n_keys++] = dns_resource_key_ref(key); - return 0; -} - -int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { - unsigned i; - int r; - - assert(rr); - - if (!q) - return 0; - - for (i = 0; i < q->n_keys; i++) { - r = dns_resource_key_match_rr(q->keys[i], rr, search_domain); - if (r != 0) - return r; - } - - return 0; -} - -int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { - unsigned i; - int r; - - assert(rr); - - if (!q) - return 0; - - if (!IN_SET(rr->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)) - return 0; - - for (i = 0; i < q->n_keys; i++) { - /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */ - if (!dns_type_may_redirect(q->keys[i]->type)) - return 0; - - r = dns_resource_key_match_cname_or_dname(q->keys[i], rr->key, search_domain); - if (r != 0) - return r; - } - - return 0; -} - -int dns_question_is_valid_for_query(DnsQuestion *q) { - const char *name; - unsigned i; - int r; - - if (!q) - return 0; - - if (q->n_keys <= 0) - return 0; - - if (q->n_keys > 65535) - return 0; - - name = dns_resource_key_name(q->keys[0]); - if (!name) - return 0; - - /* Check that all keys in this question bear the same name */ - for (i = 0; i < q->n_keys; i++) { - assert(q->keys[i]); - - if (i > 0) { - r = dns_name_equal(dns_resource_key_name(q->keys[i]), name); - if (r <= 0) - return r; - } - - if (!dns_type_is_valid_query(q->keys[i]->type)) - return 0; - } - - return 1; -} - -int dns_question_contains(DnsQuestion *a, const DnsResourceKey *k) { - unsigned j; - int r; - - assert(k); - - if (!a) - return 0; - - for (j = 0; j < a->n_keys; j++) { - r = dns_resource_key_equal(a->keys[j], k); - if (r != 0) - return r; - } - - return 0; -} - -int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b) { - unsigned j; - int r; - - if (a == b) - return 1; - - if (!a) - return !b || b->n_keys == 0; - if (!b) - return a->n_keys == 0; - - /* Checks if all keys in a are also contained b, and vice versa */ - - for (j = 0; j < a->n_keys; j++) { - r = dns_question_contains(b, a->keys[j]); - if (r <= 0) - return r; - } - - for (j = 0; j < b->n_keys; j++) { - r = dns_question_contains(a, b->keys[j]); - if (r <= 0) - return r; - } - - return 1; -} - -int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret) { - _cleanup_(dns_question_unrefp) DnsQuestion *n = NULL; - DnsResourceKey *key; - bool same = true; - int r; - - assert(cname); - assert(ret); - assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)); - - if (dns_question_size(q) <= 0) { - *ret = NULL; - return 0; - } - - DNS_QUESTION_FOREACH(key, q) { - _cleanup_free_ char *destination = NULL; - const char *d; - - if (cname->key->type == DNS_TYPE_CNAME) - d = cname->cname.name; - else { - r = dns_name_change_suffix(dns_resource_key_name(key), dns_resource_key_name(cname->key), cname->dname.name, &destination); - if (r < 0) - return r; - if (r == 0) - continue; - - d = destination; - } - - r = dns_name_equal(dns_resource_key_name(key), d); - if (r < 0) - return r; - - if (r == 0) { - same = false; - break; - } - } - - /* Fully the same, indicate we didn't do a thing */ - if (same) { - *ret = NULL; - return 0; - } - - n = dns_question_new(q->n_keys); - if (!n) - return -ENOMEM; - - /* Create a new question, and patch in the new name */ - DNS_QUESTION_FOREACH(key, q) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; - - k = dns_resource_key_new_redirect(key, cname); - if (!k) - return -ENOMEM; - - r = dns_question_add(n, k); - if (r < 0) - return r; - } - - *ret = n; - n = NULL; - - return 1; -} - -const char *dns_question_first_name(DnsQuestion *q) { - - if (!q) - return NULL; - - if (q->n_keys < 1) - return NULL; - - return dns_resource_key_name(q->keys[0]); -} - -int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna) { - _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; - _cleanup_free_ char *buf = NULL; - int r; - - assert(ret); - assert(name); - - if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) - return -EAFNOSUPPORT; - - if (convert_idna) { - r = dns_name_apply_idna(name, &buf); - if (r < 0) - return r; - - name = buf; - } - - q = dns_question_new(family == AF_UNSPEC ? 2 : 1); - if (!q) - return -ENOMEM; - - if (family != AF_INET6) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(q, key); - if (r < 0) - return r; - } - - if (family != AF_INET) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(q, key); - if (r < 0) - return r; - } - - *ret = q; - q = NULL; - - return 0; -} - -int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; - _cleanup_free_ char *reverse = NULL; - int r; - - assert(ret); - assert(a); - - if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) - return -EAFNOSUPPORT; - - r = dns_name_reverse(family, a, &reverse); - if (r < 0) - return r; - - q = dns_question_new(1); - if (!q) - return -ENOMEM; - - key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, reverse); - if (!key) - return -ENOMEM; - - reverse = NULL; - - r = dns_question_add(q, key); - if (r < 0) - return r; - - *ret = q; - q = NULL; - - return 0; -} - -int dns_question_new_service( - DnsQuestion **ret, - const char *service, - const char *type, - const char *domain, - bool with_txt, - bool convert_idna) { - - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; - _cleanup_free_ char *buf = NULL, *joined = NULL; - const char *name; - int r; - - assert(ret); - - /* We support three modes of invocation: - * - * 1. Only a domain is specified, in which case we assume a properly encoded SRV RR name, including service - * type and possibly a service name. If specified in this way we assume it's already IDNA converted if - * that's necessary. - * - * 2. Both service type and a domain specified, in which case a normal SRV RR is assumed, without a DNS-SD - * style prefix. In this case we'll IDNA convert the domain, if that's requested. - * - * 3. All three of service name, type and domain are specified, in which case a DNS-SD service is put - * together. The service name is never IDNA converted, and the domain is if requested. - * - * It's not supported to specify a service name without a type, or no domain name. - */ - - if (!domain) - return -EINVAL; - - if (type) { - if (convert_idna) { - r = dns_name_apply_idna(domain, &buf); - if (r < 0) - return r; - - domain = buf; - } - - r = dns_service_join(service, type, domain, &joined); - if (r < 0) - return r; - - name = joined; - } else { - if (service) - return -EINVAL; - - name = domain; - } - - q = dns_question_new(1 + with_txt); - if (!q) - return -ENOMEM; - - key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_SRV, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(q, key); - if (r < 0) - return r; - - if (with_txt) { - dns_resource_key_unref(key); - key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_TXT, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(q, key); - if (r < 0) - return r; - } - - *ret = q; - q = NULL; - - return 0; -} diff --git a/src/resolve/resolved-dns-question.h b/src/resolve/resolved-dns-question.h deleted file mode 100644 index ea41478975..0000000000 --- a/src/resolve/resolved-dns-question.h +++ /dev/null @@ -1,69 +0,0 @@ -#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 . -***/ - -typedef struct DnsQuestion DnsQuestion; - -#include "macro.h" -#include "resolved-dns-rr.h" - -/* A simple array of resource keys */ - -struct DnsQuestion { - unsigned n_ref; - unsigned n_keys, n_allocated; - DnsResourceKey* keys[0]; -}; - -DnsQuestion *dns_question_new(unsigned n); -DnsQuestion *dns_question_ref(DnsQuestion *q); -DnsQuestion *dns_question_unref(DnsQuestion *q); - -int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna); -int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a); -int dns_question_new_service(DnsQuestion **ret, const char *service, const char *type, const char *domain, bool with_txt, bool convert_idna); - -int dns_question_add(DnsQuestion *q, DnsResourceKey *key); - -int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain); -int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char* search_domain); -int dns_question_is_valid_for_query(DnsQuestion *q); -int dns_question_contains(DnsQuestion *a, const DnsResourceKey *k); -int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b); - -int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret); - -const char *dns_question_first_name(DnsQuestion *q); - -static inline unsigned dns_question_size(DnsQuestion *q) { - return q ? q->n_keys : 0; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuestion*, dns_question_unref); - -#define _DNS_QUESTION_FOREACH(u, key, q) \ - for (unsigned UNIQ_T(i, u) = ({ \ - (key) = ((q) && (q)->n_keys > 0) ? (q)->keys[0] : NULL; \ - 0; \ - }); \ - (q) && (UNIQ_T(i, u) < (q)->n_keys); \ - UNIQ_T(i, u)++, (key) = (UNIQ_T(i, u) < (q)->n_keys ? (q)->keys[UNIQ_T(i, u)] : NULL)) - -#define DNS_QUESTION_FOREACH(key, q) _DNS_QUESTION_FOREACH(UNIQ, key, q) diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c deleted file mode 100644 index 6a29a93a26..0000000000 --- a/src/resolve/resolved-dns-rr.c +++ /dev/null @@ -1,1594 +0,0 @@ -/*** - 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 "alloc-util.h" -#include "dns-domain.h" -#include "dns-type.h" -#include "escape.h" -#include "hexdecoct.h" -#include "resolved-dns-dnssec.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-rr.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" - -DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name) { - DnsResourceKey *k; - size_t l; - - assert(name); - - l = strlen(name); - k = malloc0(sizeof(DnsResourceKey) + l + 1); - if (!k) - return NULL; - - k->n_ref = 1; - k->class = class; - k->type = type; - - strcpy((char*) k + sizeof(DnsResourceKey), name); - - return k; -} - -DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname) { - int r; - - assert(key); - assert(cname); - - assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)); - - if (cname->key->type == DNS_TYPE_CNAME) - return dns_resource_key_new(key->class, key->type, cname->cname.name); - else { - DnsResourceKey *k; - char *destination = NULL; - - r = dns_name_change_suffix(dns_resource_key_name(key), dns_resource_key_name(cname->key), cname->dname.name, &destination); - if (r < 0) - return NULL; - if (r == 0) - return dns_resource_key_ref((DnsResourceKey*) key); - - k = dns_resource_key_new_consume(key->class, key->type, destination); - if (!k) { - free(destination); - return NULL; - } - - return k; - } -} - -int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name) { - DnsResourceKey *new_key; - char *joined; - int r; - - assert(ret); - assert(key); - assert(name); - - if (dns_name_is_root(name)) { - *ret = dns_resource_key_ref(key); - return 0; - } - - r = dns_name_concat(dns_resource_key_name(key), name, &joined); - if (r < 0) - return r; - - new_key = dns_resource_key_new_consume(key->class, key->type, joined); - if (!new_key) { - free(joined); - return -ENOMEM; - } - - *ret = new_key; - return 0; -} - -DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name) { - DnsResourceKey *k; - - assert(name); - - k = new0(DnsResourceKey, 1); - if (!k) - return NULL; - - k->n_ref = 1; - k->class = class; - k->type = type; - k->_name = name; - - return k; -} - -DnsResourceKey* dns_resource_key_ref(DnsResourceKey *k) { - - if (!k) - return NULL; - - /* Static/const keys created with DNS_RESOURCE_KEY_CONST will - * set this to -1, they should not be reffed/unreffed */ - assert(k->n_ref != (unsigned) -1); - - assert(k->n_ref > 0); - k->n_ref++; - - return k; -} - -DnsResourceKey* dns_resource_key_unref(DnsResourceKey *k) { - if (!k) - return NULL; - - assert(k->n_ref != (unsigned) -1); - assert(k->n_ref > 0); - - if (k->n_ref == 1) { - free(k->_name); - free(k); - } else - k->n_ref--; - - return NULL; -} - -const char* dns_resource_key_name(const DnsResourceKey *key) { - const char *name; - - if (!key) - return NULL; - - if (key->_name) - name = key->_name; - else - name = (char*) key + sizeof(DnsResourceKey); - - if (dns_name_is_root(name)) - return "."; - else - return name; -} - -bool dns_resource_key_is_address(const DnsResourceKey *key) { - assert(key); - - /* Check if this is an A or AAAA resource key */ - - return key->class == DNS_CLASS_IN && IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_AAAA); -} - -int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) { - int r; - - if (a == b) - return 1; - - r = dns_name_equal(dns_resource_key_name(a), dns_resource_key_name(b)); - if (r <= 0) - return r; - - if (a->class != b->class) - return 0; - - if (a->type != b->type) - return 0; - - return 1; -} - -int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain) { - int r; - - assert(key); - assert(rr); - - if (key == rr->key) - return 1; - - /* Checks if an rr matches the specified key. If a search - * domain is specified, it will also be checked if the key - * with the search domain suffixed might match the RR. */ - - if (rr->key->class != key->class && key->class != DNS_CLASS_ANY) - return 0; - - if (rr->key->type != key->type && key->type != DNS_TYPE_ANY) - return 0; - - r = dns_name_equal(dns_resource_key_name(rr->key), dns_resource_key_name(key)); - if (r != 0) - return r; - - if (search_domain) { - _cleanup_free_ char *joined = NULL; - - r = dns_name_concat(dns_resource_key_name(key), search_domain, &joined); - if (r < 0) - return r; - - return dns_name_equal(dns_resource_key_name(rr->key), joined); - } - - return 0; -} - -int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain) { - int r; - - assert(key); - assert(cname); - - if (cname->class != key->class && key->class != DNS_CLASS_ANY) - return 0; - - if (cname->type == DNS_TYPE_CNAME) - r = dns_name_equal(dns_resource_key_name(key), dns_resource_key_name(cname)); - else if (cname->type == DNS_TYPE_DNAME) - r = dns_name_endswith(dns_resource_key_name(key), dns_resource_key_name(cname)); - else - return 0; - - if (r != 0) - return r; - - if (search_domain) { - _cleanup_free_ char *joined = NULL; - - r = dns_name_concat(dns_resource_key_name(key), search_domain, &joined); - if (r < 0) - return r; - - if (cname->type == DNS_TYPE_CNAME) - return dns_name_equal(joined, dns_resource_key_name(cname)); - else if (cname->type == DNS_TYPE_DNAME) - return dns_name_endswith(joined, dns_resource_key_name(cname)); - } - - return 0; -} - -int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa) { - assert(soa); - assert(key); - - /* Checks whether 'soa' is a SOA record for the specified key. */ - - if (soa->class != key->class) - return 0; - - if (soa->type != DNS_TYPE_SOA) - return 0; - - return dns_name_endswith(dns_resource_key_name(key), dns_resource_key_name(soa)); -} - -static void dns_resource_key_hash_func(const void *i, struct siphash *state) { - const DnsResourceKey *k = i; - - assert(k); - - dns_name_hash_func(dns_resource_key_name(k), state); - siphash24_compress(&k->class, sizeof(k->class), state); - siphash24_compress(&k->type, sizeof(k->type), state); -} - -static int dns_resource_key_compare_func(const void *a, const void *b) { - const DnsResourceKey *x = a, *y = b; - int ret; - - ret = dns_name_compare_func(dns_resource_key_name(x), dns_resource_key_name(y)); - if (ret != 0) - return ret; - - if (x->type < y->type) - return -1; - if (x->type > y->type) - return 1; - - if (x->class < y->class) - return -1; - if (x->class > y->class) - return 1; - - return 0; -} - -const struct hash_ops dns_resource_key_hash_ops = { - .hash = dns_resource_key_hash_func, - .compare = dns_resource_key_compare_func -}; - -char* dns_resource_key_to_string(const DnsResourceKey *key, char *buf, size_t buf_size) { - const char *c, *t; - char *ans = buf; - - /* If we cannot convert the CLASS/TYPE into a known string, - use the format recommended by RFC 3597, Section 5. */ - - c = dns_class_to_string(key->class); - t = dns_type_to_string(key->type); - - snprintf(buf, buf_size, "%s %s%s%.0u %s%s%.0u", - dns_resource_key_name(key), - c ?: "", c ? "" : "CLASS", c ? 0 : key->class, - t ?: "", t ? "" : "TYPE", t ? 0 : key->class); - - return ans; -} - -bool dns_resource_key_reduce(DnsResourceKey **a, DnsResourceKey **b) { - assert(a); - assert(b); - - /* Try to replace one RR key by another if they are identical, thus saving a bit of memory. Note that we do - * this only for RR keys, not for RRs themselves, as they carry a lot of additional metadata (where they come - * from, validity data, and suchlike), and cannot be replaced so easily by other RRs that have the same - * superficial data. */ - - if (!*a) - return false; - if (!*b) - return false; - - /* We refuse merging const keys */ - if ((*a)->n_ref == (unsigned) -1) - return false; - if ((*b)->n_ref == (unsigned) -1) - return false; - - /* Already the same? */ - if (*a == *b) - return true; - - /* Are they really identical? */ - if (dns_resource_key_equal(*a, *b) <= 0) - return false; - - /* Keep the one which already has more references. */ - if ((*a)->n_ref > (*b)->n_ref) { - dns_resource_key_unref(*b); - *b = dns_resource_key_ref(*a); - } else { - dns_resource_key_unref(*a); - *a = dns_resource_key_ref(*b); - } - - return true; -} - -DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key) { - DnsResourceRecord *rr; - - rr = new0(DnsResourceRecord, 1); - if (!rr) - return NULL; - - rr->n_ref = 1; - rr->key = dns_resource_key_ref(key); - rr->expiry = USEC_INFINITY; - rr->n_skip_labels_signer = rr->n_skip_labels_source = (unsigned) -1; - - return rr; -} - -DnsResourceRecord* dns_resource_record_new_full(uint16_t class, uint16_t type, const char *name) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - key = dns_resource_key_new(class, type, name); - if (!key) - return NULL; - - return dns_resource_record_new(key); -} - -DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr) { - if (!rr) - return NULL; - - assert(rr->n_ref > 0); - rr->n_ref++; - - return rr; -} - -DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) { - if (!rr) - return NULL; - - assert(rr->n_ref > 0); - - if (rr->n_ref > 1) { - rr->n_ref--; - return NULL; - } - - if (rr->key) { - switch(rr->key->type) { - - case DNS_TYPE_SRV: - free(rr->srv.name); - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - free(rr->ptr.name); - break; - - case DNS_TYPE_HINFO: - free(rr->hinfo.cpu); - free(rr->hinfo.os); - break; - - case DNS_TYPE_TXT: - case DNS_TYPE_SPF: - dns_txt_item_free_all(rr->txt.items); - break; - - case DNS_TYPE_SOA: - free(rr->soa.mname); - free(rr->soa.rname); - break; - - case DNS_TYPE_MX: - free(rr->mx.exchange); - break; - - case DNS_TYPE_DS: - free(rr->ds.digest); - break; - - case DNS_TYPE_SSHFP: - free(rr->sshfp.fingerprint); - break; - - case DNS_TYPE_DNSKEY: - free(rr->dnskey.key); - break; - - case DNS_TYPE_RRSIG: - free(rr->rrsig.signer); - free(rr->rrsig.signature); - break; - - case DNS_TYPE_NSEC: - free(rr->nsec.next_domain_name); - bitmap_free(rr->nsec.types); - break; - - case DNS_TYPE_NSEC3: - free(rr->nsec3.next_hashed_name); - free(rr->nsec3.salt); - bitmap_free(rr->nsec3.types); - break; - - case DNS_TYPE_LOC: - case DNS_TYPE_A: - case DNS_TYPE_AAAA: - break; - - case DNS_TYPE_TLSA: - free(rr->tlsa.data); - break; - - case DNS_TYPE_CAA: - free(rr->caa.tag); - free(rr->caa.value); - break; - - case DNS_TYPE_OPENPGPKEY: - default: - free(rr->generic.data); - } - - free(rr->wire_format); - dns_resource_key_unref(rr->key); - } - - free(rr->to_string); - free(rr); - - return NULL; -} - -int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *hostname) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_free_ char *ptr = NULL; - int r; - - assert(ret); - assert(address); - assert(hostname); - - r = dns_name_reverse(family, address, &ptr); - if (r < 0) - return r; - - key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, ptr); - if (!key) - return -ENOMEM; - - ptr = NULL; - - rr = dns_resource_record_new(key); - if (!rr) - return -ENOMEM; - - rr->ptr.name = strdup(hostname); - if (!rr->ptr.name) - return -ENOMEM; - - *ret = rr; - rr = NULL; - - return 0; -} - -int dns_resource_record_new_address(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name) { - DnsResourceRecord *rr; - - assert(ret); - assert(address); - assert(family); - - if (family == AF_INET) { - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, name); - if (!rr) - return -ENOMEM; - - rr->a.in_addr = address->in; - - } else if (family == AF_INET6) { - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, name); - if (!rr) - return -ENOMEM; - - rr->aaaa.in6_addr = address->in6; - } else - return -EAFNOSUPPORT; - - *ret = rr; - - return 0; -} - -#define FIELD_EQUAL(a, b, field) \ - ((a).field ## _size == (b).field ## _size && \ - memcmp((a).field, (b).field, (a).field ## _size) == 0) - -int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b) { - int r; - - assert(a); - assert(b); - - if (a == b) - return 1; - - r = dns_resource_key_equal(a->key, b->key); - if (r <= 0) - return r; - - if (a->unparseable != b->unparseable) - return 0; - - switch (a->unparseable ? _DNS_TYPE_INVALID : a->key->type) { - - case DNS_TYPE_SRV: - r = dns_name_equal(a->srv.name, b->srv.name); - if (r <= 0) - return r; - - return a->srv.priority == b->srv.priority && - a->srv.weight == b->srv.weight && - a->srv.port == b->srv.port; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - return dns_name_equal(a->ptr.name, b->ptr.name); - - case DNS_TYPE_HINFO: - return strcaseeq(a->hinfo.cpu, b->hinfo.cpu) && - strcaseeq(a->hinfo.os, b->hinfo.os); - - case DNS_TYPE_SPF: /* exactly the same as TXT */ - case DNS_TYPE_TXT: - return dns_txt_item_equal(a->txt.items, b->txt.items); - - case DNS_TYPE_A: - return memcmp(&a->a.in_addr, &b->a.in_addr, sizeof(struct in_addr)) == 0; - - case DNS_TYPE_AAAA: - return memcmp(&a->aaaa.in6_addr, &b->aaaa.in6_addr, sizeof(struct in6_addr)) == 0; - - case DNS_TYPE_SOA: - r = dns_name_equal(a->soa.mname, b->soa.mname); - if (r <= 0) - return r; - r = dns_name_equal(a->soa.rname, b->soa.rname); - if (r <= 0) - return r; - - return a->soa.serial == b->soa.serial && - a->soa.refresh == b->soa.refresh && - a->soa.retry == b->soa.retry && - a->soa.expire == b->soa.expire && - a->soa.minimum == b->soa.minimum; - - case DNS_TYPE_MX: - if (a->mx.priority != b->mx.priority) - return 0; - - return dns_name_equal(a->mx.exchange, b->mx.exchange); - - case DNS_TYPE_LOC: - assert(a->loc.version == b->loc.version); - - return a->loc.size == b->loc.size && - a->loc.horiz_pre == b->loc.horiz_pre && - a->loc.vert_pre == b->loc.vert_pre && - a->loc.latitude == b->loc.latitude && - a->loc.longitude == b->loc.longitude && - a->loc.altitude == b->loc.altitude; - - case DNS_TYPE_DS: - return a->ds.key_tag == b->ds.key_tag && - a->ds.algorithm == b->ds.algorithm && - a->ds.digest_type == b->ds.digest_type && - FIELD_EQUAL(a->ds, b->ds, digest); - - case DNS_TYPE_SSHFP: - return a->sshfp.algorithm == b->sshfp.algorithm && - a->sshfp.fptype == b->sshfp.fptype && - FIELD_EQUAL(a->sshfp, b->sshfp, fingerprint); - - case DNS_TYPE_DNSKEY: - return a->dnskey.flags == b->dnskey.flags && - a->dnskey.protocol == b->dnskey.protocol && - a->dnskey.algorithm == b->dnskey.algorithm && - FIELD_EQUAL(a->dnskey, b->dnskey, key); - - case DNS_TYPE_RRSIG: - /* do the fast comparisons first */ - return a->rrsig.type_covered == b->rrsig.type_covered && - a->rrsig.algorithm == b->rrsig.algorithm && - a->rrsig.labels == b->rrsig.labels && - a->rrsig.original_ttl == b->rrsig.original_ttl && - a->rrsig.expiration == b->rrsig.expiration && - a->rrsig.inception == b->rrsig.inception && - a->rrsig.key_tag == b->rrsig.key_tag && - FIELD_EQUAL(a->rrsig, b->rrsig, signature) && - dns_name_equal(a->rrsig.signer, b->rrsig.signer); - - case DNS_TYPE_NSEC: - return dns_name_equal(a->nsec.next_domain_name, b->nsec.next_domain_name) && - bitmap_equal(a->nsec.types, b->nsec.types); - - case DNS_TYPE_NSEC3: - return a->nsec3.algorithm == b->nsec3.algorithm && - a->nsec3.flags == b->nsec3.flags && - a->nsec3.iterations == b->nsec3.iterations && - FIELD_EQUAL(a->nsec3, b->nsec3, salt) && - FIELD_EQUAL(a->nsec3, b->nsec3, next_hashed_name) && - bitmap_equal(a->nsec3.types, b->nsec3.types); - - case DNS_TYPE_TLSA: - return a->tlsa.cert_usage == b->tlsa.cert_usage && - a->tlsa.selector == b->tlsa.selector && - a->tlsa.matching_type == b->tlsa.matching_type && - FIELD_EQUAL(a->tlsa, b->tlsa, data); - - case DNS_TYPE_CAA: - return a->caa.flags == b->caa.flags && - streq(a->caa.tag, b->caa.tag) && - FIELD_EQUAL(a->caa, b->caa, value); - - case DNS_TYPE_OPENPGPKEY: - default: - return FIELD_EQUAL(a->generic, b->generic, data); - } -} - -static char* format_location(uint32_t latitude, uint32_t longitude, uint32_t altitude, - uint8_t size, uint8_t horiz_pre, uint8_t vert_pre) { - char *s; - char NS = latitude >= 1U<<31 ? 'N' : 'S'; - char EW = longitude >= 1U<<31 ? 'E' : 'W'; - - int lat = latitude >= 1U<<31 ? (int) (latitude - (1U<<31)) : (int) ((1U<<31) - latitude); - int lon = longitude >= 1U<<31 ? (int) (longitude - (1U<<31)) : (int) ((1U<<31) - longitude); - double alt = altitude >= 10000000u ? altitude - 10000000u : -(double)(10000000u - altitude); - double siz = (size >> 4) * exp10((double) (size & 0xF)); - double hor = (horiz_pre >> 4) * exp10((double) (horiz_pre & 0xF)); - double ver = (vert_pre >> 4) * exp10((double) (vert_pre & 0xF)); - - if (asprintf(&s, "%d %d %.3f %c %d %d %.3f %c %.2fm %.2fm %.2fm %.2fm", - (lat / 60000 / 60), - (lat / 60000) % 60, - (lat % 60000) / 1000., - NS, - (lon / 60000 / 60), - (lon / 60000) % 60, - (lon % 60000) / 1000., - EW, - alt / 100., - siz / 100., - hor / 100., - ver / 100.) < 0) - return NULL; - - return s; -} - -static int format_timestamp_dns(char *buf, size_t l, time_t sec) { - struct tm tm; - - assert(buf); - assert(l > strlen("YYYYMMDDHHmmSS")); - - if (!gmtime_r(&sec, &tm)) - return -EINVAL; - - if (strftime(buf, l, "%Y%m%d%H%M%S", &tm) <= 0) - return -EINVAL; - - return 0; -} - -static char *format_types(Bitmap *types) { - _cleanup_strv_free_ char **strv = NULL; - _cleanup_free_ char *str = NULL; - Iterator i; - unsigned type; - int r; - - BITMAP_FOREACH(type, types, i) { - if (dns_type_to_string(type)) { - r = strv_extend(&strv, dns_type_to_string(type)); - if (r < 0) - return NULL; - } else { - char *t; - - r = asprintf(&t, "TYPE%u", type); - if (r < 0) - return NULL; - - r = strv_consume(&strv, t); - if (r < 0) - return NULL; - } - } - - str = strv_join(strv, " "); - if (!str) - return NULL; - - return strjoin("( ", str, " )", NULL); -} - -static char *format_txt(DnsTxtItem *first) { - DnsTxtItem *i; - size_t c = 1; - char *p, *s; - - LIST_FOREACH(items, i, first) - c += i->length * 4 + 3; - - p = s = new(char, c); - if (!s) - return NULL; - - LIST_FOREACH(items, i, first) { - size_t j; - - if (i != first) - *(p++) = ' '; - - *(p++) = '"'; - - for (j = 0; j < i->length; j++) { - if (i->data[j] < ' ' || i->data[j] == '"' || i->data[j] >= 127) { - *(p++) = '\\'; - *(p++) = '0' + (i->data[j] / 100); - *(p++) = '0' + ((i->data[j] / 10) % 10); - *(p++) = '0' + (i->data[j] % 10); - } else - *(p++) = i->data[j]; - } - - *(p++) = '"'; - } - - *p = 0; - return s; -} - -const char *dns_resource_record_to_string(DnsResourceRecord *rr) { - _cleanup_free_ char *t = NULL; - char *s, k[DNS_RESOURCE_KEY_STRING_MAX]; - int r; - - assert(rr); - - if (rr->to_string) - return rr->to_string; - - dns_resource_key_to_string(rr->key, k, sizeof(k)); - - switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { - - case DNS_TYPE_SRV: - r = asprintf(&s, "%s %u %u %u %s", - k, - rr->srv.priority, - rr->srv.weight, - rr->srv.port, - strna(rr->srv.name)); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - s = strjoin(k, " ", rr->ptr.name, NULL); - if (!s) - return NULL; - - break; - - case DNS_TYPE_HINFO: - s = strjoin(k, " ", rr->hinfo.cpu, " ", rr->hinfo.os, NULL); - if (!s) - return NULL; - break; - - case DNS_TYPE_SPF: /* exactly the same as TXT */ - case DNS_TYPE_TXT: - t = format_txt(rr->txt.items); - if (!t) - return NULL; - - s = strjoin(k, " ", t, NULL); - if (!s) - return NULL; - break; - - case DNS_TYPE_A: { - _cleanup_free_ char *x = NULL; - - r = in_addr_to_string(AF_INET, (const union in_addr_union*) &rr->a.in_addr, &x); - if (r < 0) - return NULL; - - s = strjoin(k, " ", x, NULL); - if (!s) - return NULL; - break; - } - - case DNS_TYPE_AAAA: - r = in_addr_to_string(AF_INET6, (const union in_addr_union*) &rr->aaaa.in6_addr, &t); - if (r < 0) - return NULL; - - s = strjoin(k, " ", t, NULL); - if (!s) - return NULL; - break; - - case DNS_TYPE_SOA: - r = asprintf(&s, "%s %s %s %u %u %u %u %u", - k, - strna(rr->soa.mname), - strna(rr->soa.rname), - rr->soa.serial, - rr->soa.refresh, - rr->soa.retry, - rr->soa.expire, - rr->soa.minimum); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_MX: - r = asprintf(&s, "%s %u %s", - k, - rr->mx.priority, - rr->mx.exchange); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_LOC: - assert(rr->loc.version == 0); - - t = format_location(rr->loc.latitude, - rr->loc.longitude, - rr->loc.altitude, - rr->loc.size, - rr->loc.horiz_pre, - rr->loc.vert_pre); - if (!t) - return NULL; - - s = strjoin(k, " ", t, NULL); - if (!s) - return NULL; - break; - - case DNS_TYPE_DS: - t = hexmem(rr->ds.digest, rr->ds.digest_size); - if (!t) - return NULL; - - r = asprintf(&s, "%s %u %u %u %s", - k, - rr->ds.key_tag, - rr->ds.algorithm, - rr->ds.digest_type, - t); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_SSHFP: - t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); - if (!t) - return NULL; - - r = asprintf(&s, "%s %u %u %s", - k, - rr->sshfp.algorithm, - rr->sshfp.fptype, - t); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_DNSKEY: { - _cleanup_free_ char *alg = NULL; - char *ss; - int n; - uint16_t key_tag; - - key_tag = dnssec_keytag(rr, true); - - r = dnssec_algorithm_to_string_alloc(rr->dnskey.algorithm, &alg); - if (r < 0) - return NULL; - - r = asprintf(&s, "%s %u %u %s %n", - k, - rr->dnskey.flags, - rr->dnskey.protocol, - alg, - &n); - if (r < 0) - return NULL; - - r = base64_append(&s, n, - rr->dnskey.key, rr->dnskey.key_size, - 8, columns()); - if (r < 0) - return NULL; - - r = asprintf(&ss, "%s\n" - " -- Flags:%s%s%s\n" - " -- Key tag: %u", - s, - rr->dnskey.flags & DNSKEY_FLAG_SEP ? " SEP" : "", - rr->dnskey.flags & DNSKEY_FLAG_REVOKE ? " REVOKE" : "", - rr->dnskey.flags & DNSKEY_FLAG_ZONE_KEY ? " ZONE_KEY" : "", - key_tag); - if (r < 0) - return NULL; - free(s); - s = ss; - - break; - } - - case DNS_TYPE_RRSIG: { - _cleanup_free_ char *alg = NULL; - char expiration[strlen("YYYYMMDDHHmmSS") + 1], inception[strlen("YYYYMMDDHHmmSS") + 1]; - const char *type; - int n; - - type = dns_type_to_string(rr->rrsig.type_covered); - - r = dnssec_algorithm_to_string_alloc(rr->rrsig.algorithm, &alg); - if (r < 0) - return NULL; - - r = format_timestamp_dns(expiration, sizeof(expiration), rr->rrsig.expiration); - if (r < 0) - return NULL; - - r = format_timestamp_dns(inception, sizeof(inception), rr->rrsig.inception); - if (r < 0) - return NULL; - - /* TYPE?? follows - * http://tools.ietf.org/html/rfc3597#section-5 */ - - r = asprintf(&s, "%s %s%.*u %s %u %u %s %s %u %s %n", - k, - type ?: "TYPE", - type ? 0 : 1, type ? 0u : (unsigned) rr->rrsig.type_covered, - alg, - rr->rrsig.labels, - rr->rrsig.original_ttl, - expiration, - inception, - rr->rrsig.key_tag, - rr->rrsig.signer, - &n); - if (r < 0) - return NULL; - - r = base64_append(&s, n, - rr->rrsig.signature, rr->rrsig.signature_size, - 8, columns()); - if (r < 0) - return NULL; - - break; - } - - case DNS_TYPE_NSEC: - t = format_types(rr->nsec.types); - if (!t) - return NULL; - - r = asprintf(&s, "%s %s %s", - k, - rr->nsec.next_domain_name, - t); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_NSEC3: { - _cleanup_free_ char *salt = NULL, *hash = NULL; - - if (rr->nsec3.salt_size > 0) { - salt = hexmem(rr->nsec3.salt, rr->nsec3.salt_size); - if (!salt) - return NULL; - } - - hash = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false); - if (!hash) - return NULL; - - t = format_types(rr->nsec3.types); - if (!t) - return NULL; - - r = asprintf(&s, "%s %"PRIu8" %"PRIu8" %"PRIu16" %s %s %s", - k, - rr->nsec3.algorithm, - rr->nsec3.flags, - rr->nsec3.iterations, - rr->nsec3.salt_size > 0 ? salt : "-", - hash, - t); - if (r < 0) - return NULL; - - break; - } - - case DNS_TYPE_TLSA: { - const char *cert_usage, *selector, *matching_type; - - cert_usage = tlsa_cert_usage_to_string(rr->tlsa.cert_usage); - selector = tlsa_selector_to_string(rr->tlsa.selector); - matching_type = tlsa_matching_type_to_string(rr->tlsa.matching_type); - - t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); - if (!t) - return NULL; - - r = asprintf(&s, - "%s %u %u %u %s\n" - " -- Cert. usage: %s\n" - " -- Selector: %s\n" - " -- Matching type: %s", - k, - rr->tlsa.cert_usage, - rr->tlsa.selector, - rr->tlsa.matching_type, - t, - cert_usage, - selector, - matching_type); - if (r < 0) - return NULL; - - break; - } - - case DNS_TYPE_CAA: { - _cleanup_free_ char *value; - - value = octescape(rr->caa.value, rr->caa.value_size); - if (!value) - return NULL; - - r = asprintf(&s, "%s %u %s \"%s\"%s%s%s%.0u", - k, - rr->caa.flags, - rr->caa.tag, - value, - rr->caa.flags ? "\n -- Flags:" : "", - rr->caa.flags & CAA_FLAG_CRITICAL ? " critical" : "", - rr->caa.flags & ~CAA_FLAG_CRITICAL ? " " : "", - rr->caa.flags & ~CAA_FLAG_CRITICAL); - if (r < 0) - return NULL; - - break; - } - - case DNS_TYPE_OPENPGPKEY: { - int n; - - r = asprintf(&s, "%s %n", - k, - &n); - if (r < 0) - return NULL; - - r = base64_append(&s, n, - rr->generic.data, rr->generic.data_size, - 8, columns()); - if (r < 0) - return NULL; - break; - } - - default: - t = hexmem(rr->generic.data, rr->generic.data_size); - if (!t) - return NULL; - - /* Format as documented in RFC 3597, Section 5 */ - r = asprintf(&s, "%s \\# %zu %s", k, rr->generic.data_size, t); - if (r < 0) - return NULL; - break; - } - - rr->to_string = s; - return s; -} - -ssize_t dns_resource_record_payload(DnsResourceRecord *rr, void **out) { - assert(rr); - assert(out); - - switch(rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { - case DNS_TYPE_SRV: - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - case DNS_TYPE_HINFO: - case DNS_TYPE_SPF: - case DNS_TYPE_TXT: - case DNS_TYPE_A: - case DNS_TYPE_AAAA: - case DNS_TYPE_SOA: - case DNS_TYPE_MX: - case DNS_TYPE_LOC: - case DNS_TYPE_DS: - case DNS_TYPE_DNSKEY: - case DNS_TYPE_RRSIG: - case DNS_TYPE_NSEC: - case DNS_TYPE_NSEC3: - return -EINVAL; - - case DNS_TYPE_SSHFP: - *out = rr->sshfp.fingerprint; - return rr->sshfp.fingerprint_size; - - case DNS_TYPE_TLSA: - *out = rr->tlsa.data; - return rr->tlsa.data_size; - - - case DNS_TYPE_OPENPGPKEY: - default: - *out = rr->generic.data; - return rr->generic.data_size; - } -} - -int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) { - - DnsPacket packet = { - .n_ref = 1, - .protocol = DNS_PROTOCOL_DNS, - .on_stack = true, - .refuse_compression = true, - .canonical_form = canonical, - }; - - size_t start, rds; - int r; - - assert(rr); - - /* Generates the RR in wire-format, optionally in the - * canonical form as discussed in the DNSSEC RFC 4034, Section - * 6.2. We allocate a throw-away DnsPacket object on the stack - * here, because we need some book-keeping for memory - * management, and can reuse the DnsPacket serializer, that - * can generate the canonical form, too, but also knows label - * compression and suchlike. */ - - if (rr->wire_format && rr->wire_format_canonical == canonical) - return 0; - - r = dns_packet_append_rr(&packet, rr, &start, &rds); - if (r < 0) - return r; - - assert(start == 0); - assert(packet._data); - - free(rr->wire_format); - rr->wire_format = packet._data; - rr->wire_format_size = packet.size; - rr->wire_format_rdata_offset = rds; - rr->wire_format_canonical = canonical; - - packet._data = NULL; - dns_packet_unref(&packet); - - return 0; -} - -int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret) { - const char *n; - int r; - - assert(rr); - assert(ret); - - /* Returns the RRset's signer, if it is known. */ - - if (rr->n_skip_labels_signer == (unsigned) -1) - return -ENODATA; - - n = dns_resource_key_name(rr->key); - r = dns_name_skip(n, rr->n_skip_labels_signer, &n); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - *ret = n; - return 0; -} - -int dns_resource_record_source(DnsResourceRecord *rr, const char **ret) { - const char *n; - int r; - - assert(rr); - assert(ret); - - /* Returns the RRset's synthesizing source, if it is known. */ - - if (rr->n_skip_labels_source == (unsigned) -1) - return -ENODATA; - - n = dns_resource_key_name(rr->key); - r = dns_name_skip(n, rr->n_skip_labels_source, &n); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - *ret = n; - return 0; -} - -int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone) { - const char *signer; - int r; - - assert(rr); - - r = dns_resource_record_signer(rr, &signer); - if (r < 0) - return r; - - return dns_name_equal(zone, signer); -} - -int dns_resource_record_is_synthetic(DnsResourceRecord *rr) { - int r; - - assert(rr); - - /* Returns > 0 if the RR is generated from a wildcard, and is not the asterisk name itself */ - - if (rr->n_skip_labels_source == (unsigned) -1) - return -ENODATA; - - if (rr->n_skip_labels_source == 0) - return 0; - - if (rr->n_skip_labels_source > 1) - return 1; - - r = dns_name_startswith(dns_resource_key_name(rr->key), "*"); - if (r < 0) - return r; - - return !r; -} - -void dns_resource_record_hash_func(const void *i, struct siphash *state) { - const DnsResourceRecord *rr = i; - - assert(rr); - - dns_resource_key_hash_func(rr->key, state); - - switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { - - case DNS_TYPE_SRV: - siphash24_compress(&rr->srv.priority, sizeof(rr->srv.priority), state); - siphash24_compress(&rr->srv.weight, sizeof(rr->srv.weight), state); - siphash24_compress(&rr->srv.port, sizeof(rr->srv.port), state); - dns_name_hash_func(rr->srv.name, state); - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - dns_name_hash_func(rr->ptr.name, state); - break; - - case DNS_TYPE_HINFO: - string_hash_func(rr->hinfo.cpu, state); - string_hash_func(rr->hinfo.os, state); - break; - - case DNS_TYPE_TXT: - case DNS_TYPE_SPF: { - DnsTxtItem *j; - - LIST_FOREACH(items, j, rr->txt.items) { - siphash24_compress(j->data, j->length, state); - - /* Add an extra NUL byte, so that "a" followed by "b" doesn't result in the same hash as "ab" - * followed by "". */ - siphash24_compress_byte(0, state); - } - break; - } - - case DNS_TYPE_A: - siphash24_compress(&rr->a.in_addr, sizeof(rr->a.in_addr), state); - break; - - case DNS_TYPE_AAAA: - siphash24_compress(&rr->aaaa.in6_addr, sizeof(rr->aaaa.in6_addr), state); - break; - - case DNS_TYPE_SOA: - dns_name_hash_func(rr->soa.mname, state); - dns_name_hash_func(rr->soa.rname, state); - siphash24_compress(&rr->soa.serial, sizeof(rr->soa.serial), state); - siphash24_compress(&rr->soa.refresh, sizeof(rr->soa.refresh), state); - siphash24_compress(&rr->soa.retry, sizeof(rr->soa.retry), state); - siphash24_compress(&rr->soa.expire, sizeof(rr->soa.expire), state); - siphash24_compress(&rr->soa.minimum, sizeof(rr->soa.minimum), state); - break; - - case DNS_TYPE_MX: - siphash24_compress(&rr->mx.priority, sizeof(rr->mx.priority), state); - dns_name_hash_func(rr->mx.exchange, state); - break; - - case DNS_TYPE_LOC: - siphash24_compress(&rr->loc.version, sizeof(rr->loc.version), state); - siphash24_compress(&rr->loc.size, sizeof(rr->loc.size), state); - siphash24_compress(&rr->loc.horiz_pre, sizeof(rr->loc.horiz_pre), state); - siphash24_compress(&rr->loc.vert_pre, sizeof(rr->loc.vert_pre), state); - siphash24_compress(&rr->loc.latitude, sizeof(rr->loc.latitude), state); - siphash24_compress(&rr->loc.longitude, sizeof(rr->loc.longitude), state); - siphash24_compress(&rr->loc.altitude, sizeof(rr->loc.altitude), state); - break; - - case DNS_TYPE_SSHFP: - siphash24_compress(&rr->sshfp.algorithm, sizeof(rr->sshfp.algorithm), state); - siphash24_compress(&rr->sshfp.fptype, sizeof(rr->sshfp.fptype), state); - siphash24_compress(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, state); - break; - - case DNS_TYPE_DNSKEY: - siphash24_compress(&rr->dnskey.flags, sizeof(rr->dnskey.flags), state); - siphash24_compress(&rr->dnskey.protocol, sizeof(rr->dnskey.protocol), state); - siphash24_compress(&rr->dnskey.algorithm, sizeof(rr->dnskey.algorithm), state); - siphash24_compress(rr->dnskey.key, rr->dnskey.key_size, state); - break; - - case DNS_TYPE_RRSIG: - siphash24_compress(&rr->rrsig.type_covered, sizeof(rr->rrsig.type_covered), state); - siphash24_compress(&rr->rrsig.algorithm, sizeof(rr->rrsig.algorithm), state); - siphash24_compress(&rr->rrsig.labels, sizeof(rr->rrsig.labels), state); - siphash24_compress(&rr->rrsig.original_ttl, sizeof(rr->rrsig.original_ttl), state); - siphash24_compress(&rr->rrsig.expiration, sizeof(rr->rrsig.expiration), state); - siphash24_compress(&rr->rrsig.inception, sizeof(rr->rrsig.inception), state); - siphash24_compress(&rr->rrsig.key_tag, sizeof(rr->rrsig.key_tag), state); - dns_name_hash_func(rr->rrsig.signer, state); - siphash24_compress(rr->rrsig.signature, rr->rrsig.signature_size, state); - break; - - case DNS_TYPE_NSEC: - dns_name_hash_func(rr->nsec.next_domain_name, state); - /* FIXME: we leave out the type bitmap here. Hash - * would be better if we'd take it into account - * too. */ - break; - - case DNS_TYPE_DS: - siphash24_compress(&rr->ds.key_tag, sizeof(rr->ds.key_tag), state); - siphash24_compress(&rr->ds.algorithm, sizeof(rr->ds.algorithm), state); - siphash24_compress(&rr->ds.digest_type, sizeof(rr->ds.digest_type), state); - siphash24_compress(rr->ds.digest, rr->ds.digest_size, state); - break; - - case DNS_TYPE_NSEC3: - siphash24_compress(&rr->nsec3.algorithm, sizeof(rr->nsec3.algorithm), state); - siphash24_compress(&rr->nsec3.flags, sizeof(rr->nsec3.flags), state); - siphash24_compress(&rr->nsec3.iterations, sizeof(rr->nsec3.iterations), state); - siphash24_compress(rr->nsec3.salt, rr->nsec3.salt_size, state); - siphash24_compress(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, state); - /* FIXME: We leave the bitmaps out */ - break; - - case DNS_TYPE_TLSA: - siphash24_compress(&rr->tlsa.cert_usage, sizeof(rr->tlsa.cert_usage), state); - siphash24_compress(&rr->tlsa.selector, sizeof(rr->tlsa.selector), state); - siphash24_compress(&rr->tlsa.matching_type, sizeof(rr->tlsa.matching_type), state); - siphash24_compress(rr->tlsa.data, rr->tlsa.data_size, state); - break; - - case DNS_TYPE_CAA: - siphash24_compress(&rr->caa.flags, sizeof(rr->caa.flags), state); - string_hash_func(rr->caa.tag, state); - siphash24_compress(rr->caa.value, rr->caa.value_size, state); - break; - - case DNS_TYPE_OPENPGPKEY: - default: - siphash24_compress(rr->generic.data, rr->generic.data_size, state); - break; - } -} - -static int dns_resource_record_compare_func(const void *a, const void *b) { - const DnsResourceRecord *x = a, *y = b; - int ret; - - ret = dns_resource_key_compare_func(x->key, y->key); - if (ret != 0) - return ret; - - if (dns_resource_record_equal(x, y)) - return 0; - - /* This is a bit dirty, we don't implement proper ordering, but - * the hashtable doesn't need ordering anyway, hence we don't - * care. */ - return x < y ? -1 : 1; -} - -const struct hash_ops dns_resource_record_hash_ops = { - .hash = dns_resource_record_hash_func, - .compare = dns_resource_record_compare_func, -}; - -DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i) { - DnsTxtItem *n; - - if (!i) - return NULL; - - n = i->items_next; - - free(i); - return dns_txt_item_free_all(n); -} - -bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) { - - if (a == b) - return true; - - if (!a != !b) - return false; - - if (!a) - return true; - - if (a->length != b->length) - return false; - - if (memcmp(a->data, b->data, a->length) != 0) - return false; - - return dns_txt_item_equal(a->items_next, b->items_next); -} - -static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = { - /* Mnemonics as listed on https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ - [DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5", - [DNSSEC_ALGORITHM_DH] = "DH", - [DNSSEC_ALGORITHM_DSA] = "DSA", - [DNSSEC_ALGORITHM_ECC] = "ECC", - [DNSSEC_ALGORITHM_RSASHA1] = "RSASHA1", - [DNSSEC_ALGORITHM_DSA_NSEC3_SHA1] = "DSA-NSEC3-SHA1", - [DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1] = "RSASHA1-NSEC3-SHA1", - [DNSSEC_ALGORITHM_RSASHA256] = "RSASHA256", - [DNSSEC_ALGORITHM_RSASHA512] = "RSASHA512", - [DNSSEC_ALGORITHM_ECC_GOST] = "ECC-GOST", - [DNSSEC_ALGORITHM_ECDSAP256SHA256] = "ECDSAP256SHA256", - [DNSSEC_ALGORITHM_ECDSAP384SHA384] = "ECDSAP384SHA384", - [DNSSEC_ALGORITHM_INDIRECT] = "INDIRECT", - [DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS", - [DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID", -}; -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(dnssec_algorithm, int, 255); - -static const char* const dnssec_digest_table[_DNSSEC_DIGEST_MAX_DEFINED] = { - /* Names as listed on https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ - [DNSSEC_DIGEST_SHA1] = "SHA-1", - [DNSSEC_DIGEST_SHA256] = "SHA-256", - [DNSSEC_DIGEST_GOST_R_34_11_94] = "GOST_R_34.11-94", - [DNSSEC_DIGEST_SHA384] = "SHA-384", -}; -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(dnssec_digest, int, 255); diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h deleted file mode 100644 index 020a2abd77..0000000000 --- a/src/resolve/resolved-dns-rr.h +++ /dev/null @@ -1,342 +0,0 @@ -#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 - -#include "bitmap.h" -#include "dns-type.h" -#include "hashmap.h" -#include "in-addr-util.h" -#include "list.h" -#include "string-util.h" - -typedef struct DnsResourceKey DnsResourceKey; -typedef struct DnsResourceRecord DnsResourceRecord; -typedef struct DnsTxtItem DnsTxtItem; - -/* DNSKEY RR flags */ -#define DNSKEY_FLAG_SEP (UINT16_C(1) << 0) -#define DNSKEY_FLAG_REVOKE (UINT16_C(1) << 7) -#define DNSKEY_FLAG_ZONE_KEY (UINT16_C(1) << 8) - -/* mDNS RR flags */ -#define MDNS_RR_CACHE_FLUSH (UINT16_C(1) << 15) - -/* DNSSEC algorithm identifiers, see - * http://tools.ietf.org/html/rfc4034#appendix-A.1 and - * https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ -enum { - DNSSEC_ALGORITHM_RSAMD5 = 1, - DNSSEC_ALGORITHM_DH, - DNSSEC_ALGORITHM_DSA, - DNSSEC_ALGORITHM_ECC, - DNSSEC_ALGORITHM_RSASHA1, - DNSSEC_ALGORITHM_DSA_NSEC3_SHA1, - DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, - DNSSEC_ALGORITHM_RSASHA256 = 8, /* RFC 5702 */ - DNSSEC_ALGORITHM_RSASHA512 = 10, /* RFC 5702 */ - DNSSEC_ALGORITHM_ECC_GOST = 12, /* RFC 5933 */ - DNSSEC_ALGORITHM_ECDSAP256SHA256 = 13, /* RFC 6605 */ - DNSSEC_ALGORITHM_ECDSAP384SHA384 = 14, /* RFC 6605 */ - DNSSEC_ALGORITHM_INDIRECT = 252, - DNSSEC_ALGORITHM_PRIVATEDNS, - DNSSEC_ALGORITHM_PRIVATEOID, - _DNSSEC_ALGORITHM_MAX_DEFINED -}; - -/* DNSSEC digest identifiers, see - * https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ -enum { - DNSSEC_DIGEST_SHA1 = 1, - DNSSEC_DIGEST_SHA256 = 2, /* RFC 4509 */ - DNSSEC_DIGEST_GOST_R_34_11_94 = 3, /* RFC 5933 */ - DNSSEC_DIGEST_SHA384 = 4, /* RFC 6605 */ - _DNSSEC_DIGEST_MAX_DEFINED -}; - -/* DNSSEC NSEC3 hash algorithms, see - * https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml */ -enum { - NSEC3_ALGORITHM_SHA1 = 1, - _NSEC3_ALGORITHM_MAX_DEFINED -}; - -struct DnsResourceKey { - unsigned n_ref; /* (unsigned -1) for const keys, see below */ - uint16_t class, type; - char *_name; /* don't access directly, use dns_resource_key_name()! */ -}; - -/* Creates a temporary resource key. This is only useful to quickly - * look up something, without allocating a full DnsResourceKey object - * for it. Note that it is not OK to take references to this kind of - * resource key object. */ -#define DNS_RESOURCE_KEY_CONST(c, t, n) \ - ((DnsResourceKey) { \ - .n_ref = (unsigned) -1, \ - .class = c, \ - .type = t, \ - ._name = (char*) n, \ - }) - - -struct DnsTxtItem { - size_t length; - LIST_FIELDS(DnsTxtItem, items); - uint8_t data[]; -}; - -struct DnsResourceRecord { - unsigned n_ref; - DnsResourceKey *key; - - char *to_string; - - uint32_t ttl; - usec_t expiry; /* RRSIG signature expiry */ - - /* How many labels to strip to determine "signer" of the RRSIG (aka, the zone). -1 if not signed. */ - unsigned n_skip_labels_signer; - /* How many labels to strip to determine "synthesizing source" of this RR, i.e. the wildcard's immediate parent. -1 if not signed. */ - unsigned n_skip_labels_source; - - bool unparseable:1; - - bool wire_format_canonical:1; - void *wire_format; - size_t wire_format_size; - size_t wire_format_rdata_offset; - - union { - struct { - void *data; - size_t data_size; - } generic, opt; - - struct { - uint16_t priority; - uint16_t weight; - uint16_t port; - char *name; - } srv; - - struct { - char *name; - } ptr, ns, cname, dname; - - struct { - char *cpu; - char *os; - } hinfo; - - struct { - DnsTxtItem *items; - } txt, spf; - - struct { - struct in_addr in_addr; - } a; - - struct { - struct in6_addr in6_addr; - } aaaa; - - struct { - char *mname; - char *rname; - uint32_t serial; - uint32_t refresh; - uint32_t retry; - uint32_t expire; - uint32_t minimum; - } soa; - - struct { - uint16_t priority; - char *exchange; - } mx; - - /* https://tools.ietf.org/html/rfc1876 */ - struct { - uint8_t version; - uint8_t size; - uint8_t horiz_pre; - uint8_t vert_pre; - uint32_t latitude; - uint32_t longitude; - uint32_t altitude; - } loc; - - /* https://tools.ietf.org/html/rfc4255#section-3.1 */ - struct { - uint8_t algorithm; - uint8_t fptype; - void *fingerprint; - size_t fingerprint_size; - } sshfp; - - /* http://tools.ietf.org/html/rfc4034#section-2.1 */ - struct { - uint16_t flags; - uint8_t protocol; - uint8_t algorithm; - void* key; - size_t key_size; - } dnskey; - - /* http://tools.ietf.org/html/rfc4034#section-3.1 */ - struct { - uint16_t type_covered; - uint8_t algorithm; - uint8_t labels; - uint32_t original_ttl; - uint32_t expiration; - uint32_t inception; - uint16_t key_tag; - char *signer; - void *signature; - size_t signature_size; - } rrsig; - - /* https://tools.ietf.org/html/rfc4034#section-4.1 */ - struct { - char *next_domain_name; - Bitmap *types; - } nsec; - - /* https://tools.ietf.org/html/rfc4034#section-5.1 */ - struct { - uint16_t key_tag; - uint8_t algorithm; - uint8_t digest_type; - void *digest; - size_t digest_size; - } ds; - - struct { - uint8_t algorithm; - uint8_t flags; - uint16_t iterations; - void *salt; - size_t salt_size; - void *next_hashed_name; - size_t next_hashed_name_size; - Bitmap *types; - } nsec3; - - /* https://tools.ietf.org/html/draft-ietf-dane-protocol-23 */ - struct { - uint8_t cert_usage; - uint8_t selector; - uint8_t matching_type; - void *data; - size_t data_size; - } tlsa; - - /* https://tools.ietf.org/html/rfc6844 */ - struct { - uint8_t flags; - char *tag; - void *value; - size_t value_size; - } caa; - }; -}; - -static inline const void* DNS_RESOURCE_RECORD_RDATA(DnsResourceRecord *rr) { - if (!rr) - return NULL; - - if (!rr->wire_format) - return NULL; - - assert(rr->wire_format_rdata_offset <= rr->wire_format_size); - return (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset; -} - -static inline size_t DNS_RESOURCE_RECORD_RDATA_SIZE(DnsResourceRecord *rr) { - if (!rr) - return 0; - if (!rr->wire_format) - return 0; - - assert(rr->wire_format_rdata_offset <= rr->wire_format_size); - return rr->wire_format_size - rr->wire_format_rdata_offset; -} - -DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name); -DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname); -int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name); -DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name); -DnsResourceKey* dns_resource_key_ref(DnsResourceKey *key); -DnsResourceKey* dns_resource_key_unref(DnsResourceKey *key); -const char* dns_resource_key_name(const DnsResourceKey *key); -bool dns_resource_key_is_address(const DnsResourceKey *key); -int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b); -int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain); -int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain); -int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa); - -/* _DNS_{CLASS,TYPE}_STRING_MAX include one byte for NUL, which we use for space instead below. - * DNS_HOSTNAME_MAX does not include the NUL byte, so we need to add 1. */ -#define DNS_RESOURCE_KEY_STRING_MAX (_DNS_CLASS_STRING_MAX + _DNS_TYPE_STRING_MAX + DNS_HOSTNAME_MAX + 1) - -char* dns_resource_key_to_string(const DnsResourceKey *key, char *buf, size_t buf_size); -ssize_t dns_resource_record_payload(DnsResourceRecord *rr, void **out); - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceKey*, dns_resource_key_unref); - -static inline bool dns_key_is_shared(const DnsResourceKey *key) { - return IN_SET(key->type, DNS_TYPE_PTR); -} - -bool dns_resource_key_reduce(DnsResourceKey **a, DnsResourceKey **b); - -DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key); -DnsResourceRecord* dns_resource_record_new_full(uint16_t class, uint16_t type, const char *name); -DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr); -DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr); -int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name); -int dns_resource_record_new_address(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name); -int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b); -const char* dns_resource_record_to_string(DnsResourceRecord *rr); -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref); - -int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical); - -int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret); -int dns_resource_record_source(DnsResourceRecord *rr, const char **ret); -int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone); -int dns_resource_record_is_synthetic(DnsResourceRecord *rr); - -DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i); -bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b); - -void dns_resource_record_hash_func(const void *i, struct siphash *state); - -extern const struct hash_ops dns_resource_key_hash_ops; -extern const struct hash_ops dns_resource_record_hash_ops; - -int dnssec_algorithm_to_string_alloc(int i, char **ret); -int dnssec_algorithm_from_string(const char *s) _pure_; - -int dnssec_digest_to_string_alloc(int i, char **ret); -int dnssec_digest_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c deleted file mode 100644 index 66e4585c18..0000000000 --- a/src/resolve/resolved-dns-scope.c +++ /dev/null @@ -1,1028 +0,0 @@ -/*** - 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 "af-list.h" -#include "alloc-util.h" -#include "dns-domain.h" -#include "fd-util.h" -#include "hostname-util.h" -#include "missing.h" -#include "random-util.h" -#include "resolved-dns-scope.h" -#include "resolved-llmnr.h" -#include "resolved-mdns.h" -#include "socket-util.h" -#include "strv.h" - -#define MULTICAST_RATELIMIT_INTERVAL_USEC (1*USEC_PER_SEC) -#define MULTICAST_RATELIMIT_BURST 1000 - -/* After how much time to repeat LLMNR requests, see RFC 4795 Section 7 */ -#define MULTICAST_RESEND_TIMEOUT_MIN_USEC (100 * USEC_PER_MSEC) -#define MULTICAST_RESEND_TIMEOUT_MAX_USEC (1 * USEC_PER_SEC) - -int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int family) { - DnsScope *s; - - assert(m); - assert(ret); - - s = new0(DnsScope, 1); - if (!s) - return -ENOMEM; - - s->manager = m; - s->link = l; - s->protocol = protocol; - s->family = family; - s->resend_timeout = MULTICAST_RESEND_TIMEOUT_MIN_USEC; - - if (protocol == DNS_PROTOCOL_DNS) { - /* Copy DNSSEC mode from the link if it is set there, - * otherwise take the manager's DNSSEC mode. Note that - * we copy this only at scope creation time, and do - * not update it from the on, even if the setting - * changes. */ - - if (l) - s->dnssec_mode = link_get_dnssec_mode(l); - else - s->dnssec_mode = manager_get_dnssec_mode(m); - } else - s->dnssec_mode = DNSSEC_NO; - - LIST_PREPEND(scopes, m->dns_scopes, s); - - dns_scope_llmnr_membership(s, true); - dns_scope_mdns_membership(s, true); - - log_debug("New scope on link %s, protocol %s, family %s", l ? l->name : "*", dns_protocol_to_string(protocol), family == AF_UNSPEC ? "*" : af_to_name(family)); - - /* Enforce ratelimiting for the multicast protocols */ - RATELIMIT_INIT(s->ratelimit, MULTICAST_RATELIMIT_INTERVAL_USEC, MULTICAST_RATELIMIT_BURST); - - *ret = s; - return 0; -} - -static void dns_scope_abort_transactions(DnsScope *s) { - assert(s); - - while (s->transactions) { - DnsTransaction *t = s->transactions; - - /* Abort the transaction, but make sure it is not - * freed while we still look at it */ - - t->block_gc++; - if (DNS_TRANSACTION_IS_LIVE(t->state)) - dns_transaction_complete(t, DNS_TRANSACTION_ABORTED); - t->block_gc--; - - dns_transaction_free(t); - } -} - -DnsScope* dns_scope_free(DnsScope *s) { - DnsResourceRecord *rr; - - if (!s) - return NULL; - - log_debug("Removing scope on link %s, protocol %s, family %s", s->link ? s->link->name : "*", dns_protocol_to_string(s->protocol), s->family == AF_UNSPEC ? "*" : af_to_name(s->family)); - - dns_scope_llmnr_membership(s, false); - dns_scope_mdns_membership(s, false); - dns_scope_abort_transactions(s); - - while (s->query_candidates) - dns_query_candidate_free(s->query_candidates); - - hashmap_free(s->transactions_by_key); - - while ((rr = ordered_hashmap_steal_first(s->conflict_queue))) - dns_resource_record_unref(rr); - - ordered_hashmap_free(s->conflict_queue); - sd_event_source_unref(s->conflict_event_source); - - dns_cache_flush(&s->cache); - dns_zone_flush(&s->zone); - - LIST_REMOVE(scopes, s->manager->dns_scopes, s); - free(s); - - return NULL; -} - -DnsServer *dns_scope_get_dns_server(DnsScope *s) { - assert(s); - - if (s->protocol != DNS_PROTOCOL_DNS) - return NULL; - - if (s->link) - return link_get_dns_server(s->link); - else - return manager_get_dns_server(s->manager); -} - -void dns_scope_next_dns_server(DnsScope *s) { - assert(s); - - if (s->protocol != DNS_PROTOCOL_DNS) - return; - - if (s->link) - link_next_dns_server(s->link); - else - manager_next_dns_server(s->manager); -} - -void dns_scope_packet_received(DnsScope *s, usec_t rtt) { - assert(s); - - if (rtt <= s->max_rtt) - return; - - s->max_rtt = rtt; - s->resend_timeout = MIN(MAX(MULTICAST_RESEND_TIMEOUT_MIN_USEC, s->max_rtt * 2), MULTICAST_RESEND_TIMEOUT_MAX_USEC); -} - -void dns_scope_packet_lost(DnsScope *s, usec_t usec) { - assert(s); - - if (s->resend_timeout <= usec) - s->resend_timeout = MIN(s->resend_timeout * 2, MULTICAST_RESEND_TIMEOUT_MAX_USEC); -} - -static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) { - union in_addr_union addr; - int ifindex = 0, r; - int family; - uint32_t mtu; - - assert(s); - assert(p); - assert(p->protocol == s->protocol); - - if (s->link) { - mtu = s->link->mtu; - ifindex = s->link->ifindex; - } else - mtu = manager_find_mtu(s->manager); - - switch (s->protocol) { - - case DNS_PROTOCOL_DNS: - assert(fd >= 0); - - if (DNS_PACKET_QDCOUNT(p) > 1) - return -EOPNOTSUPP; - - if (p->size > DNS_PACKET_UNICAST_SIZE_MAX) - return -EMSGSIZE; - - if (p->size + UDP_PACKET_HEADER_SIZE > mtu) - return -EMSGSIZE; - - r = manager_write(s->manager, fd, p); - if (r < 0) - return r; - - break; - - case DNS_PROTOCOL_LLMNR: - assert(fd < 0); - - if (DNS_PACKET_QDCOUNT(p) > 1) - return -EOPNOTSUPP; - - if (!ratelimit_test(&s->ratelimit)) - return -EBUSY; - - family = s->family; - - if (family == AF_INET) { - addr.in = LLMNR_MULTICAST_IPV4_ADDRESS; - fd = manager_llmnr_ipv4_udp_fd(s->manager); - } else if (family == AF_INET6) { - addr.in6 = LLMNR_MULTICAST_IPV6_ADDRESS; - fd = manager_llmnr_ipv6_udp_fd(s->manager); - } else - return -EAFNOSUPPORT; - if (fd < 0) - return fd; - - r = manager_send(s->manager, fd, ifindex, family, &addr, LLMNR_PORT, p); - if (r < 0) - return r; - - break; - - case DNS_PROTOCOL_MDNS: - assert(fd < 0); - - if (!ratelimit_test(&s->ratelimit)) - return -EBUSY; - - family = s->family; - - if (family == AF_INET) { - addr.in = MDNS_MULTICAST_IPV4_ADDRESS; - fd = manager_mdns_ipv4_fd(s->manager); - } else if (family == AF_INET6) { - addr.in6 = MDNS_MULTICAST_IPV6_ADDRESS; - fd = manager_mdns_ipv6_fd(s->manager); - } else - return -EAFNOSUPPORT; - if (fd < 0) - return fd; - - r = manager_send(s->manager, fd, ifindex, family, &addr, MDNS_PORT, p); - if (r < 0) - return r; - - break; - - default: - return -EAFNOSUPPORT; - } - - return 1; -} - -int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p) { - int r; - - assert(s); - assert(p); - assert(p->protocol == s->protocol); - assert((s->protocol == DNS_PROTOCOL_DNS) == (fd >= 0)); - - do { - /* If there are multiple linked packets, set the TC bit in all but the last of them */ - if (p->more) { - assert(p->protocol == DNS_PROTOCOL_MDNS); - dns_packet_set_flags(p, true, true); - } - - r = dns_scope_emit_one(s, fd, p); - if (r < 0) - return r; - - p = p->more; - } while (p); - - return 0; -} - -static int dns_scope_socket( - DnsScope *s, - int type, - int family, - const union in_addr_union *address, - DnsServer *server, - uint16_t port) { - - _cleanup_close_ int fd = -1; - union sockaddr_union sa = {}; - socklen_t salen; - static const int one = 1; - int ret, r; - - assert(s); - - if (server) { - assert(family == AF_UNSPEC); - assert(!address); - - sa.sa.sa_family = server->family; - if (server->family == AF_INET) { - sa.in.sin_port = htobe16(port); - sa.in.sin_addr = server->address.in; - salen = sizeof(sa.in); - } else if (server->family == AF_INET6) { - sa.in6.sin6_port = htobe16(port); - sa.in6.sin6_addr = server->address.in6; - sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0; - salen = sizeof(sa.in6); - } else - return -EAFNOSUPPORT; - } else { - assert(family != AF_UNSPEC); - assert(address); - - sa.sa.sa_family = family; - - if (family == AF_INET) { - sa.in.sin_port = htobe16(port); - sa.in.sin_addr = address->in; - salen = sizeof(sa.in); - } else if (family == AF_INET6) { - sa.in6.sin6_port = htobe16(port); - sa.in6.sin6_addr = address->in6; - sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0; - salen = sizeof(sa.in6); - } else - return -EAFNOSUPPORT; - } - - fd = socket(sa.sa.sa_family, type|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (fd < 0) - return -errno; - - if (type == SOCK_STREAM) { - r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); - if (r < 0) - return -errno; - } - - if (s->link) { - uint32_t ifindex = htobe32(s->link->ifindex); - - if (sa.sa.sa_family == AF_INET) { - r = setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex)); - if (r < 0) - return -errno; - } else if (sa.sa.sa_family == AF_INET6) { - r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex)); - if (r < 0) - return -errno; - } - } - - if (s->protocol == DNS_PROTOCOL_LLMNR) { - /* RFC 4795, section 2.5 requires the TTL to be set to 1 */ - - if (sa.sa.sa_family == AF_INET) { - r = setsockopt(fd, IPPROTO_IP, IP_TTL, &one, sizeof(one)); - if (r < 0) - return -errno; - } else if (sa.sa.sa_family == AF_INET6) { - r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one)); - if (r < 0) - return -errno; - } - } - - r = connect(fd, &sa.sa, salen); - if (r < 0 && errno != EINPROGRESS) - return -errno; - - ret = fd; - fd = -1; - - return ret; -} - -int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port) { - return dns_scope_socket(s, SOCK_DGRAM, AF_UNSPEC, NULL, server, port); -} - -int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port) { - return dns_scope_socket(s, SOCK_STREAM, family, address, server, port); -} - -DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain) { - DnsSearchDomain *d; - - assert(s); - assert(domain); - - /* Checks if the specified domain is something to look up on - * this scope. Note that this accepts non-qualified hostnames, - * i.e. those without any search path prefixed yet. */ - - if (ifindex != 0 && (!s->link || s->link->ifindex != ifindex)) - return DNS_SCOPE_NO; - - if ((SD_RESOLVED_FLAGS_MAKE(s->protocol, s->family, 0) & flags) == 0) - return DNS_SCOPE_NO; - - /* Never resolve any loopback hostname or IP address via DNS, - * LLMNR or mDNS. Instead, always rely on synthesized RRs for - * these. */ - if (is_localhost(domain) || - dns_name_endswith(domain, "127.in-addr.arpa") > 0 || - dns_name_equal(domain, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) - return DNS_SCOPE_NO; - - /* Never respond to some of the domains listed in RFC6303 */ - if (dns_name_endswith(domain, "0.in-addr.arpa") > 0 || - dns_name_equal(domain, "255.255.255.255.in-addr.arpa") > 0 || - dns_name_equal(domain, "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) - return DNS_SCOPE_NO; - - /* Never respond to some of the domains listed in RFC6761 */ - if (dns_name_endswith(domain, "invalid") > 0) - return DNS_SCOPE_NO; - - /* Always honour search domains for routing queries. Note that - * we return DNS_SCOPE_YES here, rather than just - * DNS_SCOPE_MAYBE, which means wildcard scopes won't be - * considered anymore. */ - LIST_FOREACH(domains, d, dns_scope_get_search_domains(s)) - if (dns_name_endswith(domain, d->name) > 0) - return DNS_SCOPE_YES; - - switch (s->protocol) { - - case DNS_PROTOCOL_DNS: - - /* Exclude link-local IP ranges */ - if (dns_name_endswith(domain, "254.169.in-addr.arpa") == 0 && - dns_name_endswith(domain, "8.e.f.ip6.arpa") == 0 && - dns_name_endswith(domain, "9.e.f.ip6.arpa") == 0 && - dns_name_endswith(domain, "a.e.f.ip6.arpa") == 0 && - dns_name_endswith(domain, "b.e.f.ip6.arpa") == 0 && - /* If networks use .local in their private setups, they are supposed to also add .local to their search - * domains, which we already checked above. Otherwise, we consider .local specific to mDNS and won't - * send such queries ordinary DNS servers. */ - dns_name_endswith(domain, "local") == 0) - return DNS_SCOPE_MAYBE; - - return DNS_SCOPE_NO; - - case DNS_PROTOCOL_MDNS: - if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || - (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) || - (dns_name_endswith(domain, "local") > 0 && /* only resolve names ending in .local via mDNS */ - dns_name_equal(domain, "local") == 0 && /* but not the single-label "local" name itself */ - manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via mDNS */ - return DNS_SCOPE_MAYBE; - - return DNS_SCOPE_NO; - - case DNS_PROTOCOL_LLMNR: - if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || - (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) || - (dns_name_is_single_label(domain) && /* only resolve single label names via LLMNR */ - !is_gateway_hostname(domain) && /* don't resolve "gateway" with LLMNR, let nss-myhostname handle this */ - manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via LLMNR */ - return DNS_SCOPE_MAYBE; - - return DNS_SCOPE_NO; - - default: - assert_not_reached("Unknown scope protocol"); - } -} - -bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key) { - int key_family; - - assert(s); - assert(key); - - /* Check if it makes sense to resolve the specified key on - * this scope. Note that this call assumes as fully qualified - * name, i.e. the search suffixes already appended. */ - - if (key->class != DNS_CLASS_IN) - return false; - - if (s->protocol == DNS_PROTOCOL_DNS) { - - /* On classic DNS, looking up non-address RRs is always - * fine. (Specifically, we want to permit looking up - * DNSKEY and DS records on the root and top-level - * domains.) */ - if (!dns_resource_key_is_address(key)) - return true; - - /* However, we refuse to look up A and AAAA RRs on the - * root and single-label domains, under the assumption - * that those should be resolved via LLMNR or search - * path only, and should not be leaked onto the - * internet. */ - return !(dns_name_is_single_label(dns_resource_key_name(key)) || - dns_name_is_root(dns_resource_key_name(key))); - } - - /* On mDNS and LLMNR, send A and AAAA queries only on the - * respective scopes */ - - key_family = dns_type_to_af(key->type); - if (key_family < 0) - return true; - - return key_family == s->family; -} - -static int dns_scope_multicast_membership(DnsScope *s, bool b, struct in_addr in, struct in6_addr in6) { - int fd; - - assert(s); - assert(s->link); - - if (s->family == AF_INET) { - struct ip_mreqn mreqn = { - .imr_multiaddr = in, - .imr_ifindex = s->link->ifindex, - }; - - fd = manager_llmnr_ipv4_udp_fd(s->manager); - if (fd < 0) - return fd; - - /* Always first try to drop membership before we add - * one. This is necessary on some devices, such as - * veth. */ - if (b) - (void) setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn)); - - if (setsockopt(fd, IPPROTO_IP, b ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn)) < 0) - return -errno; - - } else if (s->family == AF_INET6) { - struct ipv6_mreq mreq = { - .ipv6mr_multiaddr = in6, - .ipv6mr_interface = s->link->ifindex, - }; - - fd = manager_llmnr_ipv6_udp_fd(s->manager); - if (fd < 0) - return fd; - - if (b) - (void) setsockopt(fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); - - if (setsockopt(fd, IPPROTO_IPV6, b ? IPV6_ADD_MEMBERSHIP : IPV6_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) - return -errno; - } else - return -EAFNOSUPPORT; - - return 0; -} - -int dns_scope_llmnr_membership(DnsScope *s, bool b) { - - if (s->protocol != DNS_PROTOCOL_LLMNR) - return 0; - - return dns_scope_multicast_membership(s, b, LLMNR_MULTICAST_IPV4_ADDRESS, LLMNR_MULTICAST_IPV6_ADDRESS); -} - -int dns_scope_mdns_membership(DnsScope *s, bool b) { - - if (s->protocol != DNS_PROTOCOL_MDNS) - return 0; - - return dns_scope_multicast_membership(s, b, MDNS_MULTICAST_IPV4_ADDRESS, MDNS_MULTICAST_IPV6_ADDRESS); -} - -static int dns_scope_make_reply_packet( - DnsScope *s, - uint16_t id, - int rcode, - DnsQuestion *q, - DnsAnswer *answer, - DnsAnswer *soa, - bool tentative, - DnsPacket **ret) { - - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - unsigned i; - int r; - - assert(s); - assert(ret); - - if ((!q || q->n_keys <= 0) - && (!answer || answer->n_rrs <= 0) - && (!soa || soa->n_rrs <= 0)) - return -EINVAL; - - r = dns_packet_new(&p, s->protocol, 0); - if (r < 0) - return r; - - DNS_PACKET_HEADER(p)->id = id; - DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( - 1 /* qr */, - 0 /* opcode */, - 0 /* c */, - 0 /* tc */, - tentative, - 0 /* (ra) */, - 0 /* (ad) */, - 0 /* (cd) */, - rcode)); - - if (q) { - for (i = 0; i < q->n_keys; i++) { - r = dns_packet_append_key(p, q->keys[i], NULL); - if (r < 0) - return r; - } - - DNS_PACKET_HEADER(p)->qdcount = htobe16(q->n_keys); - } - - if (answer) { - for (i = 0; i < answer->n_rrs; i++) { - r = dns_packet_append_rr(p, answer->items[i].rr, NULL, NULL); - if (r < 0) - return r; - } - - DNS_PACKET_HEADER(p)->ancount = htobe16(answer->n_rrs); - } - - if (soa) { - for (i = 0; i < soa->n_rrs; i++) { - r = dns_packet_append_rr(p, soa->items[i].rr, NULL, NULL); - if (r < 0) - return r; - } - - DNS_PACKET_HEADER(p)->arcount = htobe16(soa->n_rrs); - } - - *ret = p; - p = NULL; - - return 0; -} - -static void dns_scope_verify_conflicts(DnsScope *s, DnsPacket *p) { - unsigned n; - - assert(s); - assert(p); - - if (p->question) - for (n = 0; n < p->question->n_keys; n++) - dns_zone_verify_conflicts(&s->zone, p->question->keys[n]); - if (p->answer) - for (n = 0; n < p->answer->n_rrs; n++) - dns_zone_verify_conflicts(&s->zone, p->answer->items[n].rr->key); -} - -void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) { - _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL; - DnsResourceKey *key = NULL; - bool tentative = false; - int r, fd; - - assert(s); - assert(p); - - if (p->protocol != DNS_PROTOCOL_LLMNR) - return; - - if (p->ipproto == IPPROTO_UDP) { - /* Don't accept UDP queries directed to anything but - * the LLMNR multicast addresses. See RFC 4795, - * section 2.5. */ - - if (p->family == AF_INET && !in_addr_equal(AF_INET, &p->destination, (union in_addr_union*) &LLMNR_MULTICAST_IPV4_ADDRESS)) - return; - - if (p->family == AF_INET6 && !in_addr_equal(AF_INET6, &p->destination, (union in_addr_union*) &LLMNR_MULTICAST_IPV6_ADDRESS)) - return; - } - - r = dns_packet_extract(p); - if (r < 0) { - log_debug_errno(r, "Failed to extract resources from incoming packet: %m"); - return; - } - - if (DNS_PACKET_LLMNR_C(p)) { - /* Somebody notified us about a possible conflict */ - dns_scope_verify_conflicts(s, p); - return; - } - - assert(p->question->n_keys == 1); - key = p->question->keys[0]; - - r = dns_zone_lookup(&s->zone, key, &answer, &soa, &tentative); - if (r < 0) { - log_debug_errno(r, "Failed to lookup key: %m"); - return; - } - if (r == 0) - return; - - if (answer) - dns_answer_order_by_scope(answer, in_addr_is_link_local(p->family, &p->sender) > 0); - - r = dns_scope_make_reply_packet(s, DNS_PACKET_ID(p), DNS_RCODE_SUCCESS, p->question, answer, soa, tentative, &reply); - if (r < 0) { - log_debug_errno(r, "Failed to build reply packet: %m"); - return; - } - - if (stream) - r = dns_stream_write_packet(stream, reply); - else { - if (!ratelimit_test(&s->ratelimit)) - return; - - if (p->family == AF_INET) - fd = manager_llmnr_ipv4_udp_fd(s->manager); - else if (p->family == AF_INET6) - fd = manager_llmnr_ipv6_udp_fd(s->manager); - else { - log_debug("Unknown protocol"); - return; - } - if (fd < 0) { - log_debug_errno(fd, "Failed to get reply socket: %m"); - return; - } - - /* Note that we always immediately reply to all LLMNR - * requests, and do not wait any time, since we - * verified uniqueness for all records. Also see RFC - * 4795, Section 2.7 */ - - r = manager_send(s->manager, fd, p->ifindex, p->family, &p->sender, p->sender_port, reply); - } - - if (r < 0) { - log_debug_errno(r, "Failed to send reply packet: %m"); - return; - } -} - -DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok) { - DnsTransaction *t; - - assert(scope); - assert(key); - - /* Try to find an ongoing transaction that is a equal to the - * specified question */ - t = hashmap_get(scope->transactions_by_key, key); - if (!t) - return NULL; - - /* Refuse reusing transactions that completed based on cached - * data instead of a real packet, if that's requested. */ - if (!cache_ok && - IN_SET(t->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_RCODE_FAILURE) && - t->answer_source != DNS_TRANSACTION_NETWORK) - return NULL; - - return t; -} - -static int dns_scope_make_conflict_packet( - DnsScope *s, - DnsResourceRecord *rr, - DnsPacket **ret) { - - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - int r; - - assert(s); - assert(rr); - assert(ret); - - r = dns_packet_new(&p, s->protocol, 0); - if (r < 0) - return r; - - DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( - 0 /* qr */, - 0 /* opcode */, - 1 /* conflict */, - 0 /* tc */, - 0 /* t */, - 0 /* (ra) */, - 0 /* (ad) */, - 0 /* (cd) */, - 0)); - - /* For mDNS, the transaction ID should always be 0 */ - if (s->protocol != DNS_PROTOCOL_MDNS) - random_bytes(&DNS_PACKET_HEADER(p)->id, sizeof(uint16_t)); - - DNS_PACKET_HEADER(p)->qdcount = htobe16(1); - DNS_PACKET_HEADER(p)->arcount = htobe16(1); - - r = dns_packet_append_key(p, rr->key, NULL); - if (r < 0) - return r; - - r = dns_packet_append_rr(p, rr, NULL, NULL); - if (r < 0) - return r; - - *ret = p; - p = NULL; - - return 0; -} - -static int on_conflict_dispatch(sd_event_source *es, usec_t usec, void *userdata) { - DnsScope *scope = userdata; - int r; - - assert(es); - assert(scope); - - scope->conflict_event_source = sd_event_source_unref(scope->conflict_event_source); - - for (;;) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - - rr = ordered_hashmap_steal_first(scope->conflict_queue); - if (!rr) - break; - - r = dns_scope_make_conflict_packet(scope, rr, &p); - if (r < 0) { - log_error_errno(r, "Failed to make conflict packet: %m"); - return 0; - } - - r = dns_scope_emit_udp(scope, -1, p); - if (r < 0) - log_debug_errno(r, "Failed to send conflict packet: %m"); - } - - return 0; -} - -int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr) { - usec_t jitter; - int r; - - assert(scope); - assert(rr); - - /* We don't send these queries immediately. Instead, we queue - * them, and send them after some jitter delay. */ - r = ordered_hashmap_ensure_allocated(&scope->conflict_queue, &dns_resource_key_hash_ops); - if (r < 0) { - log_oom(); - return r; - } - - /* We only place one RR per key in the conflict - * messages, not all of them. That should be enough to - * indicate where there might be a conflict */ - r = ordered_hashmap_put(scope->conflict_queue, rr->key, rr); - if (r == -EEXIST || r == 0) - return 0; - if (r < 0) - return log_debug_errno(r, "Failed to queue conflicting RR: %m"); - - dns_resource_record_ref(rr); - - if (scope->conflict_event_source) - return 0; - - random_bytes(&jitter, sizeof(jitter)); - jitter %= LLMNR_JITTER_INTERVAL_USEC; - - r = sd_event_add_time(scope->manager->event, - &scope->conflict_event_source, - clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()) + jitter, - LLMNR_JITTER_INTERVAL_USEC, - on_conflict_dispatch, scope); - if (r < 0) - return log_debug_errno(r, "Failed to add conflict dispatch event: %m"); - - (void) sd_event_source_set_description(scope->conflict_event_source, "scope-conflict"); - - return 0; -} - -void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p) { - unsigned i; - int r; - - assert(scope); - assert(p); - - if (p->protocol != DNS_PROTOCOL_LLMNR) - return; - - if (DNS_PACKET_RRCOUNT(p) <= 0) - return; - - if (DNS_PACKET_LLMNR_C(p) != 0) - return; - - if (DNS_PACKET_LLMNR_T(p) != 0) - return; - - if (manager_our_packet(scope->manager, p)) - return; - - r = dns_packet_extract(p); - if (r < 0) { - log_debug_errno(r, "Failed to extract packet: %m"); - return; - } - - log_debug("Checking for conflicts..."); - - for (i = 0; i < p->answer->n_rrs; i++) { - - /* Check for conflicts against the local zone. If we - * found one, we won't check any further */ - r = dns_zone_check_conflicts(&scope->zone, p->answer->items[i].rr); - if (r != 0) - continue; - - /* Check for conflicts against the local cache. If so, - * send out an advisory query, to inform everybody */ - r = dns_cache_check_conflicts(&scope->cache, p->answer->items[i].rr, p->family, &p->sender); - if (r <= 0) - continue; - - dns_scope_notify_conflict(scope, p->answer->items[i].rr); - } -} - -void dns_scope_dump(DnsScope *s, FILE *f) { - assert(s); - - if (!f) - f = stdout; - - fputs("[Scope protocol=", f); - fputs(dns_protocol_to_string(s->protocol), f); - - if (s->link) { - fputs(" interface=", f); - fputs(s->link->name, f); - } - - if (s->family != AF_UNSPEC) { - fputs(" family=", f); - fputs(af_to_name(s->family), f); - } - - fputs("]\n", f); - - if (!dns_zone_is_empty(&s->zone)) { - fputs("ZONE:\n", f); - dns_zone_dump(&s->zone, f); - } - - if (!dns_cache_is_empty(&s->cache)) { - fputs("CACHE:\n", f); - dns_cache_dump(&s->cache, f); - } -} - -DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s) { - assert(s); - - if (s->protocol != DNS_PROTOCOL_DNS) - return NULL; - - if (s->link) - return s->link->search_domains; - - return s->manager->search_domains; -} - -bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name) { - assert(s); - - if (s->protocol != DNS_PROTOCOL_DNS) - return false; - - return dns_name_is_single_label(name); -} - -bool dns_scope_network_good(DnsScope *s) { - /* Checks whether the network is in good state for lookups on this scope. For mDNS/LLMNR/Classic DNS scopes - * bound to links this is easy, as they don't even exist if the link isn't in a suitable state. For the global - * DNS scope we check whether there are any links that are up and have an address. */ - - if (s->link) - return true; - - return manager_routable(s->manager, AF_UNSPEC); -} diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h deleted file mode 100644 index 291e5817d0..0000000000 --- a/src/resolve/resolved-dns-scope.h +++ /dev/null @@ -1,109 +0,0 @@ -#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 "list.h" - -typedef struct DnsScope DnsScope; - -#include "resolved-dns-cache.h" -#include "resolved-dns-dnssec.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-server.h" -#include "resolved-dns-zone.h" -#include "resolved-link.h" - -typedef enum DnsScopeMatch { - DNS_SCOPE_NO, - DNS_SCOPE_MAYBE, - DNS_SCOPE_YES, - _DNS_SCOPE_MATCH_MAX, - _DNS_SCOPE_INVALID = -1 -} DnsScopeMatch; - -struct DnsScope { - Manager *manager; - - DnsProtocol protocol; - int family; - DnssecMode dnssec_mode; - - Link *link; - - DnsCache cache; - DnsZone zone; - - OrderedHashmap *conflict_queue; - sd_event_source *conflict_event_source; - - RateLimit ratelimit; - - usec_t resend_timeout; - usec_t max_rtt; - - LIST_HEAD(DnsQueryCandidate, query_candidates); - - /* Note that we keep track of ongoing transactions in two - * ways: once in a hashmap, indexed by the rr key, and once in - * a linked list. We use the hashmap to quickly find - * transactions we can reuse for a key. But note that there - * might be multiple transactions for the same key (because - * the zone probing can't reuse a transaction answered from - * the zone or the cache), and the hashmap only tracks the - * most recent entry. */ - Hashmap *transactions_by_key; - LIST_HEAD(DnsTransaction, transactions); - - LIST_FIELDS(DnsScope, scopes); -}; - -int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol p, int family); -DnsScope* dns_scope_free(DnsScope *s); - -void dns_scope_packet_received(DnsScope *s, usec_t rtt); -void dns_scope_packet_lost(DnsScope *s, usec_t usec); - -int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p); -int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port); -int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port); - -DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain); -bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key); - -DnsServer *dns_scope_get_dns_server(DnsScope *s); -void dns_scope_next_dns_server(DnsScope *s); - -int dns_scope_llmnr_membership(DnsScope *s, bool b); -int dns_scope_mdns_membership(DnsScope *s, bool b); - -void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p); - -DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok); - -int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr); -void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p); - -void dns_scope_dump(DnsScope *s, FILE *f); - -DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s); - -bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name); - -bool dns_scope_network_good(DnsScope *s); diff --git a/src/resolve/resolved-dns-search-domain.c b/src/resolve/resolved-dns-search-domain.c deleted file mode 100644 index 732471027b..0000000000 --- a/src/resolve/resolved-dns-search-domain.c +++ /dev/null @@ -1,227 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 "alloc-util.h" -#include "dns-domain.h" -#include "resolved-dns-search-domain.h" - -int dns_search_domain_new( - Manager *m, - DnsSearchDomain **ret, - DnsSearchDomainType type, - Link *l, - const char *name) { - - _cleanup_free_ char *normalized = NULL; - DnsSearchDomain *d; - int r; - - assert(m); - assert((type == DNS_SEARCH_DOMAIN_LINK) == !!l); - assert(name); - - r = dns_name_normalize(name, &normalized); - if (r < 0) - return r; - - if (l) { - if (l->n_search_domains >= LINK_SEARCH_DOMAINS_MAX) - return -E2BIG; - } else { - if (m->n_search_domains >= MANAGER_SEARCH_DOMAINS_MAX) - return -E2BIG; - } - - d = new0(DnsSearchDomain, 1); - if (!d) - return -ENOMEM; - - d->n_ref = 1; - d->manager = m; - d->type = type; - d->name = normalized; - normalized = NULL; - - switch (type) { - - case DNS_SEARCH_DOMAIN_LINK: - d->link = l; - LIST_APPEND(domains, l->search_domains, d); - l->n_search_domains++; - break; - - case DNS_SERVER_SYSTEM: - LIST_APPEND(domains, m->search_domains, d); - m->n_search_domains++; - break; - - default: - assert_not_reached("Unknown search domain type"); - } - - d->linked = true; - - if (ret) - *ret = d; - - return 0; -} - -DnsSearchDomain* dns_search_domain_ref(DnsSearchDomain *d) { - if (!d) - return NULL; - - assert(d->n_ref > 0); - d->n_ref++; - - return d; -} - -DnsSearchDomain* dns_search_domain_unref(DnsSearchDomain *d) { - if (!d) - return NULL; - - assert(d->n_ref > 0); - d->n_ref--; - - if (d->n_ref > 0) - return NULL; - - free(d->name); - free(d); - - return NULL; -} - -void dns_search_domain_unlink(DnsSearchDomain *d) { - assert(d); - assert(d->manager); - - if (!d->linked) - return; - - switch (d->type) { - - case DNS_SEARCH_DOMAIN_LINK: - assert(d->link); - assert(d->link->n_search_domains > 0); - LIST_REMOVE(domains, d->link->search_domains, d); - d->link->n_search_domains--; - break; - - case DNS_SEARCH_DOMAIN_SYSTEM: - assert(d->manager->n_search_domains > 0); - LIST_REMOVE(domains, d->manager->search_domains, d); - d->manager->n_search_domains--; - break; - } - - d->linked = false; - - dns_search_domain_unref(d); -} - -void dns_search_domain_move_back_and_unmark(DnsSearchDomain *d) { - DnsSearchDomain *tail; - - assert(d); - - if (!d->marked) - return; - - d->marked = false; - - if (!d->linked || !d->domains_next) - return; - - switch (d->type) { - - case DNS_SEARCH_DOMAIN_LINK: - assert(d->link); - LIST_FIND_TAIL(domains, d, tail); - LIST_REMOVE(domains, d->link->search_domains, d); - LIST_INSERT_AFTER(domains, d->link->search_domains, tail, d); - break; - - case DNS_SEARCH_DOMAIN_SYSTEM: - LIST_FIND_TAIL(domains, d, tail); - LIST_REMOVE(domains, d->manager->search_domains, d); - LIST_INSERT_AFTER(domains, d->manager->search_domains, tail, d); - break; - - default: - assert_not_reached("Unknown search domain type"); - } -} - -void dns_search_domain_unlink_all(DnsSearchDomain *first) { - DnsSearchDomain *next; - - if (!first) - return; - - next = first->domains_next; - dns_search_domain_unlink(first); - - dns_search_domain_unlink_all(next); -} - -void dns_search_domain_unlink_marked(DnsSearchDomain *first) { - DnsSearchDomain *next; - - if (!first) - return; - - next = first->domains_next; - - if (first->marked) - dns_search_domain_unlink(first); - - dns_search_domain_unlink_marked(next); -} - -void dns_search_domain_mark_all(DnsSearchDomain *first) { - if (!first) - return; - - first->marked = true; - dns_search_domain_mark_all(first->domains_next); -} - -int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDomain **ret) { - DnsSearchDomain *d; - int r; - - assert(name); - assert(ret); - - LIST_FOREACH(domains, d, first) { - - r = dns_name_equal(name, d->name); - if (r < 0) - return r; - if (r > 0) { - *ret = d; - return 1; - } - } - - *ret = NULL; - return 0; -} diff --git a/src/resolve/resolved-dns-search-domain.h b/src/resolve/resolved-dns-search-domain.h deleted file mode 100644 index eaacef4edc..0000000000 --- a/src/resolve/resolved-dns-search-domain.h +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 "macro.h" - -typedef struct DnsSearchDomain DnsSearchDomain; - -typedef enum DnsSearchDomainType { - DNS_SEARCH_DOMAIN_SYSTEM, - DNS_SEARCH_DOMAIN_LINK, -} DnsSearchDomainType; - -#include "resolved-link.h" -#include "resolved-manager.h" - -struct DnsSearchDomain { - Manager *manager; - - unsigned n_ref; - - DnsSearchDomainType type; - Link *link; - - char *name; - - bool marked:1; - bool route_only:1; - - bool linked:1; - LIST_FIELDS(DnsSearchDomain, domains); -}; - -int dns_search_domain_new( - Manager *m, - DnsSearchDomain **ret, - DnsSearchDomainType type, - Link *link, - const char *name); - -DnsSearchDomain* dns_search_domain_ref(DnsSearchDomain *d); -DnsSearchDomain* dns_search_domain_unref(DnsSearchDomain *d); - -void dns_search_domain_unlink(DnsSearchDomain *d); -void dns_search_domain_move_back_and_unmark(DnsSearchDomain *d); - -void dns_search_domain_unlink_all(DnsSearchDomain *first); -void dns_search_domain_unlink_marked(DnsSearchDomain *first); -void dns_search_domain_mark_all(DnsSearchDomain *first); - -int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDomain **ret); - -static inline const char* DNS_SEARCH_DOMAIN_NAME(DnsSearchDomain *d) { - return d ? d->name : NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsSearchDomain*, dns_search_domain_unref); diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c deleted file mode 100644 index 3095c042db..0000000000 --- a/src/resolve/resolved-dns-server.c +++ /dev/null @@ -1,741 +0,0 @@ -/*** - 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 "alloc-util.h" -#include "resolved-dns-server.h" -#include "resolved-resolv-conf.h" -#include "siphash24.h" -#include "string-table.h" -#include "string-util.h" - -/* After how much time to repeat classic DNS requests */ -#define DNS_TIMEOUT_MIN_USEC (500 * USEC_PER_MSEC) -#define DNS_TIMEOUT_MAX_USEC (5 * USEC_PER_SEC) - -/* The amount of time to wait before retrying with a full feature set */ -#define DNS_SERVER_FEATURE_GRACE_PERIOD_MAX_USEC (6 * USEC_PER_HOUR) -#define DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC (5 * USEC_PER_MINUTE) - -/* The number of times we will attempt a certain feature set before degrading */ -#define DNS_SERVER_FEATURE_RETRY_ATTEMPTS 3 - -int dns_server_new( - Manager *m, - DnsServer **ret, - DnsServerType type, - Link *l, - int family, - const union in_addr_union *in_addr) { - - DnsServer *s; - - assert(m); - assert((type == DNS_SERVER_LINK) == !!l); - assert(in_addr); - - if (!IN_SET(family, AF_INET, AF_INET6)) - return -EAFNOSUPPORT; - - if (l) { - if (l->n_dns_servers >= LINK_DNS_SERVERS_MAX) - return -E2BIG; - } else { - if (m->n_dns_servers >= MANAGER_DNS_SERVERS_MAX) - return -E2BIG; - } - - s = new0(DnsServer, 1); - if (!s) - return -ENOMEM; - - s->n_ref = 1; - s->manager = m; - s->verified_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID; - s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST; - s->features_grace_period_usec = DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC; - s->received_udp_packet_max = DNS_PACKET_UNICAST_SIZE_MAX; - s->type = type; - s->family = family; - s->address = *in_addr; - s->resend_timeout = DNS_TIMEOUT_MIN_USEC; - - switch (type) { - - case DNS_SERVER_LINK: - s->link = l; - LIST_APPEND(servers, l->dns_servers, s); - l->n_dns_servers++; - break; - - case DNS_SERVER_SYSTEM: - LIST_APPEND(servers, m->dns_servers, s); - m->n_dns_servers++; - break; - - case DNS_SERVER_FALLBACK: - LIST_APPEND(servers, m->fallback_dns_servers, s); - m->n_dns_servers++; - break; - - default: - assert_not_reached("Unknown server type"); - } - - s->linked = true; - - /* A new DNS server that isn't fallback is added and the one - * we used so far was a fallback one? Then let's try to pick - * the new one */ - if (type != DNS_SERVER_FALLBACK && - m->current_dns_server && - m->current_dns_server->type == DNS_SERVER_FALLBACK) - manager_set_dns_server(m, NULL); - - if (ret) - *ret = s; - - return 0; -} - -DnsServer* dns_server_ref(DnsServer *s) { - if (!s) - return NULL; - - assert(s->n_ref > 0); - s->n_ref++; - - return s; -} - -DnsServer* dns_server_unref(DnsServer *s) { - if (!s) - return NULL; - - assert(s->n_ref > 0); - s->n_ref--; - - if (s->n_ref > 0) - return NULL; - - free(s->server_string); - free(s); - return NULL; -} - -void dns_server_unlink(DnsServer *s) { - assert(s); - assert(s->manager); - - /* This removes the specified server from the linked list of - * servers, but any server might still stay around if it has - * refs, for example from an ongoing transaction. */ - - if (!s->linked) - return; - - switch (s->type) { - - case DNS_SERVER_LINK: - assert(s->link); - assert(s->link->n_dns_servers > 0); - LIST_REMOVE(servers, s->link->dns_servers, s); - s->link->n_dns_servers--; - break; - - case DNS_SERVER_SYSTEM: - assert(s->manager->n_dns_servers > 0); - LIST_REMOVE(servers, s->manager->dns_servers, s); - s->manager->n_dns_servers--; - break; - - case DNS_SERVER_FALLBACK: - assert(s->manager->n_dns_servers > 0); - LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); - s->manager->n_dns_servers--; - break; - } - - s->linked = false; - - if (s->link && s->link->current_dns_server == s) - link_set_dns_server(s->link, NULL); - - if (s->manager->current_dns_server == s) - manager_set_dns_server(s->manager, NULL); - - dns_server_unref(s); -} - -void dns_server_move_back_and_unmark(DnsServer *s) { - DnsServer *tail; - - assert(s); - - if (!s->marked) - return; - - s->marked = false; - - if (!s->linked || !s->servers_next) - return; - - /* Move us to the end of the list, so that the order is - * strictly kept, if we are not at the end anyway. */ - - switch (s->type) { - - case DNS_SERVER_LINK: - assert(s->link); - LIST_FIND_TAIL(servers, s, tail); - LIST_REMOVE(servers, s->link->dns_servers, s); - LIST_INSERT_AFTER(servers, s->link->dns_servers, tail, s); - break; - - case DNS_SERVER_SYSTEM: - LIST_FIND_TAIL(servers, s, tail); - LIST_REMOVE(servers, s->manager->dns_servers, s); - LIST_INSERT_AFTER(servers, s->manager->dns_servers, tail, s); - break; - - case DNS_SERVER_FALLBACK: - LIST_FIND_TAIL(servers, s, tail); - LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); - LIST_INSERT_AFTER(servers, s->manager->fallback_dns_servers, tail, s); - break; - - default: - assert_not_reached("Unknown server type"); - } -} - -static void dns_server_verified(DnsServer *s, DnsServerFeatureLevel level) { - assert(s); - - if (s->verified_feature_level > level) - return; - - if (s->verified_feature_level != level) { - log_debug("Verified we get a response at feature level %s from DNS server %s.", - dns_server_feature_level_to_string(level), - dns_server_string(s)); - s->verified_feature_level = level; - } - - assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0); -} - -void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t rtt, size_t size) { - assert(s); - - if (protocol == IPPROTO_UDP) { - if (s->possible_feature_level == level) - s->n_failed_udp = 0; - - /* If the RRSIG data is missing, then we can only validate EDNS0 at max */ - if (s->packet_rrsig_missing && level >= DNS_SERVER_FEATURE_LEVEL_DO) - level = DNS_SERVER_FEATURE_LEVEL_DO - 1; - - /* If the OPT RR got lost, then we can only validate UDP at max */ - if (s->packet_bad_opt && level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) - level = DNS_SERVER_FEATURE_LEVEL_EDNS0 - 1; - - /* Even if we successfully receive a reply to a request announcing support for large packets, - that does not mean we can necessarily receive large packets. */ - if (level == DNS_SERVER_FEATURE_LEVEL_LARGE) - level = DNS_SERVER_FEATURE_LEVEL_LARGE - 1; - - } else if (protocol == IPPROTO_TCP) { - - if (s->possible_feature_level == level) - s->n_failed_tcp = 0; - - /* Successful TCP connections are only useful to verify the TCP feature level. */ - level = DNS_SERVER_FEATURE_LEVEL_TCP; - } - - dns_server_verified(s, level); - - /* Remember the size of the largest UDP packet we received from a server, - we know that we can always announce support for packets with at least - this size. */ - if (protocol == IPPROTO_UDP && s->received_udp_packet_max < size) - s->received_udp_packet_max = size; - - if (s->max_rtt < rtt) { - s->max_rtt = rtt; - s->resend_timeout = CLAMP(s->max_rtt * 2, DNS_TIMEOUT_MIN_USEC, DNS_TIMEOUT_MAX_USEC); - } -} - -void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t usec) { - assert(s); - assert(s->manager); - - if (s->possible_feature_level == level) { - if (protocol == IPPROTO_UDP) - s->n_failed_udp++; - else if (protocol == IPPROTO_TCP) - s->n_failed_tcp++; - } - - if (s->resend_timeout > usec) - return; - - s->resend_timeout = MIN(s->resend_timeout * 2, DNS_TIMEOUT_MAX_USEC); -} - -void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level) { - assert(s); - - /* Invoked whenever we get a FORMERR, SERVFAIL or NOTIMP rcode from a server. */ - - if (s->possible_feature_level != level) - return; - - s->packet_failed = true; -} - -void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level) { - assert(s); - - /* Invoked whenever we get a packet with TC bit set. */ - - if (s->possible_feature_level != level) - return; - - s->packet_truncated = true; -} - -void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level) { - assert(s); - - if (level < DNS_SERVER_FEATURE_LEVEL_DO) - return; - - /* If the RRSIG RRs are missing, we have to downgrade what we previously verified */ - if (s->verified_feature_level >= DNS_SERVER_FEATURE_LEVEL_DO) - s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_DO-1; - - s->packet_rrsig_missing = true; -} - -void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level) { - assert(s); - - if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0) - return; - - /* If the OPT RR got lost, we have to downgrade what we previously verified */ - if (s->verified_feature_level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) - s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_EDNS0-1; - - s->packet_bad_opt = true; -} - -static bool dns_server_grace_period_expired(DnsServer *s) { - usec_t ts; - - assert(s); - assert(s->manager); - - if (s->verified_usec == 0) - return false; - - assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); - - if (s->verified_usec + s->features_grace_period_usec > ts) - return false; - - s->features_grace_period_usec = MIN(s->features_grace_period_usec * 2, DNS_SERVER_FEATURE_GRACE_PERIOD_MAX_USEC); - - return true; -} - -static void dns_server_reset_counters(DnsServer *s) { - assert(s); - - s->n_failed_udp = 0; - s->n_failed_tcp = 0; - s->packet_failed = false; - s->packet_truncated = false; - s->verified_usec = 0; - - /* Note that we do not reset s->packet_bad_opt and s->packet_rrsig_missing here. We reset them only when the - * grace period ends, but not when lowering the possible feature level, as a lower level feature level should - * not make RRSIGs appear or OPT appear, but rather make them disappear. If the reappear anyway, then that's - * indication for a differently broken OPT/RRSIG implementation, and we really don't want to support that - * either. - * - * This is particularly important to deal with certain Belkin routers which break OPT for certain lookups (A), - * but pass traffic through for others (AAAA). If we detect the broken behaviour on one lookup we should not - * reenable it for another, because we cannot validate things anyway, given that the RRSIG/OPT data will be - * incomplete. */ -} - -DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) { - assert(s); - - if (s->possible_feature_level != DNS_SERVER_FEATURE_LEVEL_BEST && - dns_server_grace_period_expired(s)) { - - s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST; - - dns_server_reset_counters(s); - - s->packet_bad_opt = false; - s->packet_rrsig_missing = false; - - log_info("Grace period over, resuming full feature set (%s) for DNS server %s.", - dns_server_feature_level_to_string(s->possible_feature_level), - dns_server_string(s)); - - } else if (s->possible_feature_level <= s->verified_feature_level) - s->possible_feature_level = s->verified_feature_level; - else { - DnsServerFeatureLevel p = s->possible_feature_level; - - if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && - s->possible_feature_level == DNS_SERVER_FEATURE_LEVEL_TCP) { - - /* We are at the TCP (lowest) level, and we tried a couple of TCP connections, and it didn't - * work. Upgrade back to UDP again. */ - log_debug("Reached maximum number of failed TCP connection attempts, trying UDP again..."); - s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP; - - } else if (s->packet_bad_opt && - s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) { - - /* A reply to one of our EDNS0 queries didn't carry a valid OPT RR, then downgrade to below - * EDNS0 levels. After all, some records generate different responses with and without OPT RR - * in the request. Example: - * https://open.nlnetlabs.nl/pipermail/dnssec-trigger/2014-November/000376.html */ - - log_debug("Server doesn't support EDNS(0) properly, downgrading feature level..."); - s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP; - - } else if (s->packet_rrsig_missing && - s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_DO) { - - /* RRSIG data was missing on a EDNS0 packet with DO bit set. This means the server doesn't - * augment responses with DNSSEC RRs. If so, let's better not ask the server for it anymore, - * after all some servers generate different replies depending if an OPT RR is in the query or - * not. */ - - log_debug("Detected server responses lack RRSIG records, downgrading feature level..."); - s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_EDNS0; - - } else if (s->n_failed_udp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && - s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_UDP) { - - /* We lost too many UDP packets in a row, and are on a feature level of UDP or higher. If the - * packets are lost, maybe the server cannot parse them, hence downgrading sounds like a good - * idea. We might downgrade all the way down to TCP this way. */ - - log_debug("Lost too many UDP packets, downgrading feature level..."); - s->possible_feature_level--; - - } else if (s->packet_failed && - s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) { - - /* We got a failure packet, and are at a feature level above UDP. Note that in this case we - * downgrade no further than UDP, under the assumption that a failure packet indicates an - * incompatible packet contents, but not a problem with the transport. */ - - log_debug("Got server failure, downgrading feature level..."); - s->possible_feature_level--; - - } else if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && - s->packet_truncated && - s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) { - - /* We got too many TCP connection failures in a row, we had at least one truncated packet, and - * are on a feature level above UDP. By downgrading things and getting rid of DNSSEC or EDNS0 - * data we hope to make the packet smaller, so that it still works via UDP given that TCP - * appears not to be a fallback. Note that if we are already at the lowest UDP level, we don't - * go further down, since that's TCP, and TCP failed too often after all. */ - - log_debug("Got too many failed TCP connection failures and truncated UDP packets, downgrading feature level..."); - s->possible_feature_level--; - } - - if (p != s->possible_feature_level) { - - /* We changed the feature level, reset the counting */ - dns_server_reset_counters(s); - - log_warning("Using degraded feature set (%s) for DNS server %s.", - dns_server_feature_level_to_string(s->possible_feature_level), - dns_server_string(s)); - } - } - - return s->possible_feature_level; -} - -int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level) { - size_t packet_size; - bool edns_do; - int r; - - assert(server); - assert(packet); - assert(packet->protocol == DNS_PROTOCOL_DNS); - - /* Fix the OPT field in the packet to match our current feature level. */ - - r = dns_packet_truncate_opt(packet); - if (r < 0) - return r; - - if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0) - return 0; - - edns_do = level >= DNS_SERVER_FEATURE_LEVEL_DO; - - if (level >= DNS_SERVER_FEATURE_LEVEL_LARGE) - packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX; - else - packet_size = server->received_udp_packet_max; - - return dns_packet_append_opt(packet, packet_size, edns_do, NULL); -} - -const char *dns_server_string(DnsServer *server) { - assert(server); - - if (!server->server_string) - (void) in_addr_to_string(server->family, &server->address, &server->server_string); - - return strna(server->server_string); -} - -bool dns_server_dnssec_supported(DnsServer *server) { - assert(server); - - /* Returns whether the server supports DNSSEC according to what we know about it */ - - if (server->possible_feature_level < DNS_SERVER_FEATURE_LEVEL_DO) - return false; - - if (server->packet_bad_opt) - return false; - - if (server->packet_rrsig_missing) - return false; - - /* DNSSEC servers need to support TCP properly (see RFC5966), if they don't, we assume DNSSEC is borked too */ - if (server->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS) - return false; - - return true; -} - -void dns_server_warn_downgrade(DnsServer *server) { - assert(server); - - if (server->warned_downgrade) - return; - - log_struct(LOG_NOTICE, - LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_DOWNGRADE), - LOG_MESSAGE("Server %s does not support DNSSEC, downgrading to non-DNSSEC mode.", dns_server_string(server)), - "DNS_SERVER=%s", dns_server_string(server), - "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(server->possible_feature_level), - NULL); - - server->warned_downgrade = true; -} - -static void dns_server_hash_func(const void *p, struct siphash *state) { - const DnsServer *s = p; - - assert(s); - - siphash24_compress(&s->family, sizeof(s->family), state); - siphash24_compress(&s->address, FAMILY_ADDRESS_SIZE(s->family), state); -} - -static int dns_server_compare_func(const void *a, const void *b) { - const DnsServer *x = a, *y = b; - - if (x->family < y->family) - return -1; - if (x->family > y->family) - return 1; - - return memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family)); -} - -const struct hash_ops dns_server_hash_ops = { - .hash = dns_server_hash_func, - .compare = dns_server_compare_func -}; - -void dns_server_unlink_all(DnsServer *first) { - DnsServer *next; - - if (!first) - return; - - next = first->servers_next; - dns_server_unlink(first); - - dns_server_unlink_all(next); -} - -void dns_server_unlink_marked(DnsServer *first) { - DnsServer *next; - - if (!first) - return; - - next = first->servers_next; - - if (first->marked) - dns_server_unlink(first); - - dns_server_unlink_marked(next); -} - -void dns_server_mark_all(DnsServer *first) { - if (!first) - return; - - first->marked = true; - dns_server_mark_all(first->servers_next); -} - -DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr) { - DnsServer *s; - - LIST_FOREACH(servers, s, first) - if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0) - return s; - - return NULL; -} - -DnsServer *manager_get_first_dns_server(Manager *m, DnsServerType t) { - assert(m); - - switch (t) { - - case DNS_SERVER_SYSTEM: - return m->dns_servers; - - case DNS_SERVER_FALLBACK: - return m->fallback_dns_servers; - - default: - return NULL; - } -} - -DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) { - assert(m); - - if (m->current_dns_server == s) - return s; - - if (s) - log_info("Switching to %s DNS server %s.", - dns_server_type_to_string(s->type), - dns_server_string(s)); - - dns_server_unref(m->current_dns_server); - m->current_dns_server = dns_server_ref(s); - - if (m->unicast_scope) - dns_cache_flush(&m->unicast_scope->cache); - - return s; -} - -DnsServer *manager_get_dns_server(Manager *m) { - Link *l; - assert(m); - - /* Try to read updates resolv.conf */ - manager_read_resolv_conf(m); - - /* If no DNS server was chosen so far, pick the first one */ - if (!m->current_dns_server) - manager_set_dns_server(m, m->dns_servers); - - if (!m->current_dns_server) { - bool found = false; - Iterator i; - - /* No DNS servers configured, let's see if there are - * any on any links. If not, we use the fallback - * servers */ - - HASHMAP_FOREACH(l, m->links, i) - if (l->dns_servers) { - found = true; - break; - } - - if (!found) - manager_set_dns_server(m, m->fallback_dns_servers); - } - - return m->current_dns_server; -} - -void manager_next_dns_server(Manager *m) { - assert(m); - - /* If there's currently no DNS server set, then the next - * manager_get_dns_server() will find one */ - if (!m->current_dns_server) - return; - - /* Change to the next one, but make sure to follow the linked - * list only if the server is still linked. */ - if (m->current_dns_server->linked && m->current_dns_server->servers_next) { - manager_set_dns_server(m, m->current_dns_server->servers_next); - return; - } - - /* If there was no next one, then start from the beginning of - * the list */ - if (m->current_dns_server->type == DNS_SERVER_FALLBACK) - manager_set_dns_server(m, m->fallback_dns_servers); - else - manager_set_dns_server(m, m->dns_servers); -} - -static const char* const dns_server_type_table[_DNS_SERVER_TYPE_MAX] = { - [DNS_SERVER_SYSTEM] = "system", - [DNS_SERVER_FALLBACK] = "fallback", - [DNS_SERVER_LINK] = "link", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_server_type, DnsServerType); - -static const char* const dns_server_feature_level_table[_DNS_SERVER_FEATURE_LEVEL_MAX] = { - [DNS_SERVER_FEATURE_LEVEL_TCP] = "TCP", - [DNS_SERVER_FEATURE_LEVEL_UDP] = "UDP", - [DNS_SERVER_FEATURE_LEVEL_EDNS0] = "UDP+EDNS0", - [DNS_SERVER_FEATURE_LEVEL_DO] = "UDP+EDNS0+DO", - [DNS_SERVER_FEATURE_LEVEL_LARGE] = "UDP+EDNS0+DO+LARGE", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_server_feature_level, DnsServerFeatureLevel); diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h deleted file mode 100644 index 9f4a69c37a..0000000000 --- a/src/resolve/resolved-dns-server.h +++ /dev/null @@ -1,143 +0,0 @@ -#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 "in-addr-util.h" - -typedef struct DnsServer DnsServer; - -typedef enum DnsServerType { - DNS_SERVER_SYSTEM, - DNS_SERVER_FALLBACK, - DNS_SERVER_LINK, -} DnsServerType; -#define _DNS_SERVER_TYPE_MAX (DNS_SERVER_LINK + 1) - -const char* dns_server_type_to_string(DnsServerType i) _const_; -DnsServerType dns_server_type_from_string(const char *s) _pure_; - -typedef enum DnsServerFeatureLevel { - DNS_SERVER_FEATURE_LEVEL_TCP, - DNS_SERVER_FEATURE_LEVEL_UDP, - DNS_SERVER_FEATURE_LEVEL_EDNS0, - DNS_SERVER_FEATURE_LEVEL_DO, - DNS_SERVER_FEATURE_LEVEL_LARGE, - _DNS_SERVER_FEATURE_LEVEL_MAX, - _DNS_SERVER_FEATURE_LEVEL_INVALID = -1 -} DnsServerFeatureLevel; - -#define DNS_SERVER_FEATURE_LEVEL_WORST 0 -#define DNS_SERVER_FEATURE_LEVEL_BEST (_DNS_SERVER_FEATURE_LEVEL_MAX - 1) - -const char* dns_server_feature_level_to_string(int i) _const_; -int dns_server_feature_level_from_string(const char *s) _pure_; - -#include "resolved-link.h" -#include "resolved-manager.h" - -struct DnsServer { - Manager *manager; - - unsigned n_ref; - - DnsServerType type; - Link *link; - - int family; - union in_addr_union address; - - char *server_string; - - usec_t resend_timeout; - usec_t max_rtt; - - DnsServerFeatureLevel verified_feature_level; - DnsServerFeatureLevel possible_feature_level; - - size_t received_udp_packet_max; - - unsigned n_failed_udp; - unsigned n_failed_tcp; - - bool packet_failed:1; - bool packet_truncated:1; - bool packet_bad_opt:1; - bool packet_rrsig_missing:1; - - usec_t verified_usec; - usec_t features_grace_period_usec; - - /* Whether we already warned about downgrading to non-DNSSEC mode for this server */ - bool warned_downgrade:1; - - /* Used when GC'ing old DNS servers when configuration changes. */ - bool marked:1; - - /* If linked is set, then this server appears in the servers linked list */ - bool linked:1; - LIST_FIELDS(DnsServer, servers); -}; - -int dns_server_new( - Manager *m, - DnsServer **ret, - DnsServerType type, - Link *link, - int family, - const union in_addr_union *address); - -DnsServer* dns_server_ref(DnsServer *s); -DnsServer* dns_server_unref(DnsServer *s); - -void dns_server_unlink(DnsServer *s); -void dns_server_move_back_and_unmark(DnsServer *s); - -void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t rtt, size_t size); -void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t usec); -void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level); -void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level); -void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level); -void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level); - -DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s); - -int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level); - -const char *dns_server_string(DnsServer *server); - -bool dns_server_dnssec_supported(DnsServer *server); - -void dns_server_warn_downgrade(DnsServer *server); - -DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr); - -void dns_server_unlink_all(DnsServer *first); -void dns_server_unlink_marked(DnsServer *first); -void dns_server_mark_all(DnsServer *first); - -DnsServer *manager_get_first_dns_server(Manager *m, DnsServerType t); - -DnsServer *manager_set_dns_server(Manager *m, DnsServer *s); -DnsServer *manager_get_dns_server(Manager *m); -void manager_next_dns_server(Manager *m); - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref); - -extern const struct hash_ops dns_server_hash_ops; diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c deleted file mode 100644 index a1040aeff4..0000000000 --- a/src/resolve/resolved-dns-stream.c +++ /dev/null @@ -1,403 +0,0 @@ -/*** - 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 "alloc-util.h" -#include "fd-util.h" -#include "io-util.h" -#include "missing.h" -#include "resolved-dns-stream.h" - -#define DNS_STREAM_TIMEOUT_USEC (10 * USEC_PER_SEC) -#define DNS_STREAMS_MAX 128 - -static void dns_stream_stop(DnsStream *s) { - assert(s); - - s->io_event_source = sd_event_source_unref(s->io_event_source); - s->timeout_event_source = sd_event_source_unref(s->timeout_event_source); - s->fd = safe_close(s->fd); -} - -static int dns_stream_update_io(DnsStream *s) { - int f = 0; - - assert(s); - - if (s->write_packet && s->n_written < sizeof(s->write_size) + s->write_packet->size) - f |= EPOLLOUT; - if (!s->read_packet || s->n_read < sizeof(s->read_size) + s->read_packet->size) - f |= EPOLLIN; - - return sd_event_source_set_io_events(s->io_event_source, f); -} - -static int dns_stream_complete(DnsStream *s, int error) { - assert(s); - - dns_stream_stop(s); - - if (s->complete) - s->complete(s, error); - else - dns_stream_free(s); - - return 0; -} - -static int dns_stream_identify(DnsStream *s) { - union { - struct cmsghdr header; /* For alignment */ - uint8_t buffer[CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo)) - + EXTRA_CMSG_SPACE /* kernel appears to require extra space */]; - } control; - struct msghdr mh = {}; - struct cmsghdr *cmsg; - socklen_t sl; - int r; - - assert(s); - - if (s->identified) - return 0; - - /* Query the local side */ - s->local_salen = sizeof(s->local); - r = getsockname(s->fd, &s->local.sa, &s->local_salen); - if (r < 0) - return -errno; - if (s->local.sa.sa_family == AF_INET6 && s->ifindex <= 0) - s->ifindex = s->local.in6.sin6_scope_id; - - /* Query the remote side */ - s->peer_salen = sizeof(s->peer); - r = getpeername(s->fd, &s->peer.sa, &s->peer_salen); - if (r < 0) - return -errno; - if (s->peer.sa.sa_family == AF_INET6 && s->ifindex <= 0) - s->ifindex = s->peer.in6.sin6_scope_id; - - /* Check consistency */ - assert(s->peer.sa.sa_family == s->local.sa.sa_family); - assert(IN_SET(s->peer.sa.sa_family, AF_INET, AF_INET6)); - - /* Query connection meta information */ - sl = sizeof(control); - if (s->peer.sa.sa_family == AF_INET) { - r = getsockopt(s->fd, IPPROTO_IP, IP_PKTOPTIONS, &control, &sl); - if (r < 0) - return -errno; - } else if (s->peer.sa.sa_family == AF_INET6) { - - r = getsockopt(s->fd, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, &control, &sl); - if (r < 0) - return -errno; - } else - return -EAFNOSUPPORT; - - mh.msg_control = &control; - mh.msg_controllen = sl; - - CMSG_FOREACH(cmsg, &mh) { - - if (cmsg->cmsg_level == IPPROTO_IPV6) { - assert(s->peer.sa.sa_family == AF_INET6); - - switch (cmsg->cmsg_type) { - - case IPV6_PKTINFO: { - struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg); - - if (s->ifindex <= 0) - s->ifindex = i->ipi6_ifindex; - break; - } - - case IPV6_HOPLIMIT: - s->ttl = *(int *) CMSG_DATA(cmsg); - break; - } - - } else if (cmsg->cmsg_level == IPPROTO_IP) { - assert(s->peer.sa.sa_family == AF_INET); - - switch (cmsg->cmsg_type) { - - case IP_PKTINFO: { - struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg); - - if (s->ifindex <= 0) - s->ifindex = i->ipi_ifindex; - break; - } - - case IP_TTL: - s->ttl = *(int *) CMSG_DATA(cmsg); - break; - } - } - } - - /* The Linux kernel sets the interface index to the loopback - * device if the connection came from the local host since it - * avoids the routing table in such a case. Let's unset the - * interface index in such a case. */ - if (s->ifindex == LOOPBACK_IFINDEX) - s->ifindex = 0; - - /* If we don't know the interface index still, we look for the - * first local interface with a matching address. Yuck! */ - if (s->ifindex <= 0) - s->ifindex = manager_find_ifindex(s->manager, s->local.sa.sa_family, s->local.sa.sa_family == AF_INET ? (union in_addr_union*) &s->local.in.sin_addr : (union in_addr_union*) &s->local.in6.sin6_addr); - - if (s->protocol == DNS_PROTOCOL_LLMNR && s->ifindex > 0) { - uint32_t ifindex = htobe32(s->ifindex); - - /* Make sure all packets for this connection are sent on the same interface */ - if (s->local.sa.sa_family == AF_INET) { - r = setsockopt(s->fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex)); - if (r < 0) - log_debug_errno(errno, "Failed to invoke IP_UNICAST_IF: %m"); - } else if (s->local.sa.sa_family == AF_INET6) { - r = setsockopt(s->fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex)); - if (r < 0) - log_debug_errno(errno, "Failed to invoke IPV6_UNICAST_IF: %m"); - } - } - - s->identified = true; - - return 0; -} - -static int on_stream_timeout(sd_event_source *es, usec_t usec, void *userdata) { - DnsStream *s = userdata; - - assert(s); - - return dns_stream_complete(s, ETIMEDOUT); -} - -static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *userdata) { - DnsStream *s = userdata; - int r; - - assert(s); - - r = dns_stream_identify(s); - if (r < 0) - return dns_stream_complete(s, -r); - - if ((revents & EPOLLOUT) && - s->write_packet && - s->n_written < sizeof(s->write_size) + s->write_packet->size) { - - struct iovec iov[2]; - ssize_t ss; - - iov[0].iov_base = &s->write_size; - iov[0].iov_len = sizeof(s->write_size); - iov[1].iov_base = DNS_PACKET_DATA(s->write_packet); - iov[1].iov_len = s->write_packet->size; - - IOVEC_INCREMENT(iov, 2, s->n_written); - - ss = writev(fd, iov, 2); - if (ss < 0) { - if (errno != EINTR && errno != EAGAIN) - return dns_stream_complete(s, errno); - } else - s->n_written += ss; - - /* Are we done? If so, disable the event source for EPOLLOUT */ - if (s->n_written >= sizeof(s->write_size) + s->write_packet->size) { - r = dns_stream_update_io(s); - if (r < 0) - return dns_stream_complete(s, -r); - } - } - - if ((revents & (EPOLLIN|EPOLLHUP|EPOLLRDHUP)) && - (!s->read_packet || - s->n_read < sizeof(s->read_size) + s->read_packet->size)) { - - if (s->n_read < sizeof(s->read_size)) { - ssize_t ss; - - ss = read(fd, (uint8_t*) &s->read_size + s->n_read, sizeof(s->read_size) - s->n_read); - if (ss < 0) { - if (errno != EINTR && errno != EAGAIN) - return dns_stream_complete(s, errno); - } else if (ss == 0) - return dns_stream_complete(s, ECONNRESET); - else - s->n_read += ss; - } - - if (s->n_read >= sizeof(s->read_size)) { - - if (be16toh(s->read_size) < DNS_PACKET_HEADER_SIZE) - return dns_stream_complete(s, EBADMSG); - - if (s->n_read < sizeof(s->read_size) + be16toh(s->read_size)) { - ssize_t ss; - - if (!s->read_packet) { - r = dns_packet_new(&s->read_packet, s->protocol, be16toh(s->read_size)); - if (r < 0) - return dns_stream_complete(s, -r); - - s->read_packet->size = be16toh(s->read_size); - s->read_packet->ipproto = IPPROTO_TCP; - s->read_packet->family = s->peer.sa.sa_family; - s->read_packet->ttl = s->ttl; - s->read_packet->ifindex = s->ifindex; - - if (s->read_packet->family == AF_INET) { - s->read_packet->sender.in = s->peer.in.sin_addr; - s->read_packet->sender_port = be16toh(s->peer.in.sin_port); - s->read_packet->destination.in = s->local.in.sin_addr; - s->read_packet->destination_port = be16toh(s->local.in.sin_port); - } else { - assert(s->read_packet->family == AF_INET6); - s->read_packet->sender.in6 = s->peer.in6.sin6_addr; - s->read_packet->sender_port = be16toh(s->peer.in6.sin6_port); - s->read_packet->destination.in6 = s->local.in6.sin6_addr; - s->read_packet->destination_port = be16toh(s->local.in6.sin6_port); - - if (s->read_packet->ifindex == 0) - s->read_packet->ifindex = s->peer.in6.sin6_scope_id; - if (s->read_packet->ifindex == 0) - s->read_packet->ifindex = s->local.in6.sin6_scope_id; - } - } - - ss = read(fd, - (uint8_t*) DNS_PACKET_DATA(s->read_packet) + s->n_read - sizeof(s->read_size), - sizeof(s->read_size) + be16toh(s->read_size) - s->n_read); - if (ss < 0) { - if (errno != EINTR && errno != EAGAIN) - return dns_stream_complete(s, errno); - } else if (ss == 0) - return dns_stream_complete(s, ECONNRESET); - else - s->n_read += ss; - } - - /* Are we done? If so, disable the event source for EPOLLIN */ - if (s->n_read >= sizeof(s->read_size) + be16toh(s->read_size)) { - r = dns_stream_update_io(s); - if (r < 0) - return dns_stream_complete(s, -r); - - /* If there's a packet handler - * installed, call that. Note that - * this is optional... */ - if (s->on_packet) - return s->on_packet(s); - } - } - } - - if ((s->write_packet && s->n_written >= sizeof(s->write_size) + s->write_packet->size) && - (s->read_packet && s->n_read >= sizeof(s->read_size) + s->read_packet->size)) - return dns_stream_complete(s, 0); - - return 0; -} - -DnsStream *dns_stream_free(DnsStream *s) { - if (!s) - return NULL; - - dns_stream_stop(s); - - if (s->manager) { - LIST_REMOVE(streams, s->manager->dns_streams, s); - s->manager->n_dns_streams--; - } - - dns_packet_unref(s->write_packet); - dns_packet_unref(s->read_packet); - - free(s); - - return 0; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_free); - -int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { - _cleanup_(dns_stream_freep) DnsStream *s = NULL; - int r; - - assert(m); - assert(fd >= 0); - - if (m->n_dns_streams > DNS_STREAMS_MAX) - return -EBUSY; - - s = new0(DnsStream, 1); - if (!s) - return -ENOMEM; - - s->fd = -1; - s->protocol = protocol; - - r = sd_event_add_io(m->event, &s->io_event_source, fd, EPOLLIN, on_stream_io, s); - if (r < 0) - return r; - - (void) sd_event_source_set_description(s->io_event_source, "dns-stream-io"); - - r = sd_event_add_time( - m->event, - &s->timeout_event_source, - clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()) + DNS_STREAM_TIMEOUT_USEC, 0, - on_stream_timeout, s); - if (r < 0) - return r; - - (void) sd_event_source_set_description(s->timeout_event_source, "dns-stream-timeout"); - - LIST_PREPEND(streams, m->dns_streams, s); - s->manager = m; - s->fd = fd; - m->n_dns_streams++; - - *ret = s; - s = NULL; - - return 0; -} - -int dns_stream_write_packet(DnsStream *s, DnsPacket *p) { - assert(s); - - if (s->write_packet) - return -EBUSY; - - s->write_packet = dns_packet_ref(p); - s->write_size = htobe16(p->size); - s->n_written = 0; - - return dns_stream_update_io(s); -} diff --git a/src/resolve/resolved-dns-stream.h b/src/resolve/resolved-dns-stream.h deleted file mode 100644 index 5ccc842249..0000000000 --- a/src/resolve/resolved-dns-stream.h +++ /dev/null @@ -1,61 +0,0 @@ -#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 "socket-util.h" - -typedef struct DnsStream DnsStream; - -#include "resolved-dns-packet.h" -#include "resolved-dns-transaction.h" - -struct DnsStream { - Manager *manager; - - DnsProtocol protocol; - - int fd; - union sockaddr_union peer; - socklen_t peer_salen; - union sockaddr_union local; - socklen_t local_salen; - int ifindex; - uint32_t ttl; - bool identified; - - sd_event_source *io_event_source; - sd_event_source *timeout_event_source; - - be16_t write_size, read_size; - DnsPacket *write_packet, *read_packet; - size_t n_written, n_read; - - int (*on_packet)(DnsStream *s); - int (*complete)(DnsStream *s, int error); - - DnsTransaction *transaction; - - LIST_FIELDS(DnsStream, streams); -}; - -int dns_stream_new(Manager *m, DnsStream **s, DnsProtocol protocol, int fd); -DnsStream *dns_stream_free(DnsStream *s); - -int dns_stream_write_packet(DnsStream *s, DnsPacket *p); diff --git a/src/resolve/resolved-dns-synthesize.c b/src/resolve/resolved-dns-synthesize.c deleted file mode 100644 index e3003411f7..0000000000 --- a/src/resolve/resolved-dns-synthesize.c +++ /dev/null @@ -1,413 +0,0 @@ -/*** - 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 "alloc-util.h" -#include "hostname-util.h" -#include "local-addresses.h" -#include "resolved-dns-synthesize.h" - -int dns_synthesize_ifindex(int ifindex) { - - /* When the caller asked for resolving on a specific - * interface, we synthesize the answer for that - * interface. However, if nothing specific was claimed and we - * only return localhost RRs, we synthesize the answer for - * localhost. */ - - if (ifindex > 0) - return ifindex; - - return LOOPBACK_IFINDEX; -} - -int dns_synthesize_family(uint64_t flags) { - - /* Picks an address family depending on set flags. This is - * purely for synthesized answers, where the family we return - * for the reply should match what was requested in the - * question, even though we are synthesizing the answer - * here. */ - - if (!(flags & SD_RESOLVED_DNS)) { - if (flags & (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_MDNS_IPV4)) - return AF_INET; - if (flags & (SD_RESOLVED_LLMNR_IPV6|SD_RESOLVED_MDNS_IPV6)) - return AF_INET6; - } - - return AF_UNSPEC; -} - -DnsProtocol dns_synthesize_protocol(uint64_t flags) { - - /* Similar as dns_synthesize_family() but does this for the - * protocol. If resolving via DNS was requested, we claim it - * was DNS. Similar, if nothing specific was - * requested. However, if only resolving via LLMNR was - * requested we return that. */ - - if (flags & SD_RESOLVED_DNS) - return DNS_PROTOCOL_DNS; - if (flags & SD_RESOLVED_LLMNR) - return DNS_PROTOCOL_LLMNR; - if (flags & SD_RESOLVED_MDNS) - return DNS_PROTOCOL_MDNS; - - return DNS_PROTOCOL_DNS; -} - -static int synthesize_localhost_rr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { - int r; - - assert(m); - assert(key); - assert(answer); - - r = dns_answer_reserve(answer, 2); - if (r < 0) - return r; - - if (IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_ANY)) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, dns_resource_key_name(key)); - if (!rr) - return -ENOMEM; - - rr->a.in_addr.s_addr = htobe32(INADDR_LOOPBACK); - - r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - if (IN_SET(key->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, dns_resource_key_name(key)); - if (!rr) - return -ENOMEM; - - rr->aaaa.in6_addr = in6addr_loopback; - - r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - return 0; -} - -static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to, int ifindex, DnsAnswerFlags flags) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, from); - if (!rr) - return -ENOMEM; - - rr->ptr.name = strdup(to); - if (!rr->ptr.name) - return -ENOMEM; - - return dns_answer_add(*answer, rr, ifindex, flags); -} - -static int synthesize_localhost_ptr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { - int r; - - assert(m); - assert(key); - assert(answer); - - if (IN_SET(key->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) { - r = dns_answer_reserve(answer, 1); - if (r < 0) - return r; - - r = answer_add_ptr(answer, dns_resource_key_name(key), "localhost", dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - return 0; -} - -static int answer_add_addresses_rr( - DnsAnswer **answer, - const char *name, - struct local_address *addresses, - unsigned n_addresses) { - - unsigned j; - int r; - - assert(answer); - assert(name); - - r = dns_answer_reserve(answer, n_addresses); - if (r < 0) - return r; - - for (j = 0; j < n_addresses; j++) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - r = dns_resource_record_new_address(&rr, addresses[j].family, &addresses[j].address, name); - if (r < 0) - return r; - - r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - return 0; -} - -static int answer_add_addresses_ptr( - DnsAnswer **answer, - const char *name, - struct local_address *addresses, - unsigned n_addresses, - int af, const union in_addr_union *match) { - - unsigned j; - int r; - - assert(answer); - assert(name); - - for (j = 0; j < n_addresses; j++) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - if (af != AF_UNSPEC) { - - if (addresses[j].family != af) - continue; - - if (match && !in_addr_equal(af, match, &addresses[j].address)) - continue; - } - - r = dns_answer_reserve(answer, 1); - if (r < 0) - return r; - - r = dns_resource_record_new_reverse(&rr, addresses[j].family, &addresses[j].address, name); - if (r < 0) - return r; - - r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - return 0; -} - -static int synthesize_system_hostname_rr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { - _cleanup_free_ struct local_address *addresses = NULL; - int n = 0, af; - - assert(m); - assert(key); - assert(answer); - - af = dns_type_to_af(key->type); - if (af >= 0) { - n = local_addresses(m->rtnl, ifindex, af, &addresses); - if (n < 0) - return n; - - if (n == 0) { - struct local_address buffer[2]; - - /* If we have no local addresses then use ::1 - * and 127.0.0.2 as local ones. */ - - if (af == AF_INET || af == AF_UNSPEC) - buffer[n++] = (struct local_address) { - .family = AF_INET, - .ifindex = dns_synthesize_ifindex(ifindex), - .address.in.s_addr = htobe32(0x7F000002), - }; - - if (af == AF_INET6 || af == AF_UNSPEC) - buffer[n++] = (struct local_address) { - .family = AF_INET6, - .ifindex = dns_synthesize_ifindex(ifindex), - .address.in6 = in6addr_loopback, - }; - - return answer_add_addresses_rr(answer, dns_resource_key_name(key), buffer, n); - } - } - - return answer_add_addresses_rr(answer, dns_resource_key_name(key), addresses, n); -} - -static int synthesize_system_hostname_ptr(Manager *m, int af, const union in_addr_union *address, int ifindex, DnsAnswer **answer) { - _cleanup_free_ struct local_address *addresses = NULL; - int n, r; - - assert(m); - assert(address); - assert(answer); - - if (af == AF_INET && address->in.s_addr == htobe32(0x7F000002)) { - - /* Always map the IPv4 address 127.0.0.2 to the local - * hostname, in addition to "localhost": */ - - r = dns_answer_reserve(answer, 3); - if (r < 0) - return r; - - r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->llmnr_hostname, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->mdns_hostname, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", "localhost", dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - return 0; - } - - n = local_addresses(m->rtnl, ifindex, af, &addresses); - if (n < 0) - return n; - - r = answer_add_addresses_ptr(answer, m->llmnr_hostname, addresses, n, af, address); - if (r < 0) - return r; - - return answer_add_addresses_ptr(answer, m->mdns_hostname, addresses, n, af, address); -} - -static int synthesize_gateway_rr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { - _cleanup_free_ struct local_address *addresses = NULL; - int n = 0, af; - - assert(m); - assert(key); - assert(answer); - - af = dns_type_to_af(key->type); - if (af >= 0) { - n = local_gateways(m->rtnl, ifindex, af, &addresses); - if (n < 0) - return n; - } - - return answer_add_addresses_rr(answer, dns_resource_key_name(key), addresses, n); -} - -static int synthesize_gateway_ptr(Manager *m, int af, const union in_addr_union *address, int ifindex, DnsAnswer **answer) { - _cleanup_free_ struct local_address *addresses = NULL; - int n; - - assert(m); - assert(address); - assert(answer); - - n = local_gateways(m->rtnl, ifindex, af, &addresses); - if (n < 0) - return n; - - return answer_add_addresses_ptr(answer, "gateway", addresses, n, af, address); -} - -int dns_synthesize_answer( - Manager *m, - DnsQuestion *q, - int ifindex, - DnsAnswer **ret) { - - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - DnsResourceKey *key; - bool found = false; - int r; - - assert(m); - assert(q); - - DNS_QUESTION_FOREACH(key, q) { - union in_addr_union address; - const char *name; - int af; - - if (key->class != DNS_CLASS_IN && - key->class != DNS_CLASS_ANY) - continue; - - name = dns_resource_key_name(key); - - if (is_localhost(name)) { - - r = synthesize_localhost_rr(m, key, ifindex, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize localhost RRs: %m"); - - } else if (manager_is_own_hostname(m, name)) { - - r = synthesize_system_hostname_rr(m, key, ifindex, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize system hostname RRs: %m"); - - } else if (is_gateway_hostname(name)) { - - r = synthesize_gateway_rr(m, key, ifindex, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize gateway RRs: %m"); - - } else if ((dns_name_endswith(name, "127.in-addr.arpa") > 0 && dns_name_equal(name, "2.0.0.127.in-addr.arpa") == 0) || - dns_name_equal(name, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) { - - r = synthesize_localhost_ptr(m, key, ifindex, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize localhost PTR RRs: %m"); - - } else if (dns_name_address(name, &af, &address) > 0) { - - r = synthesize_system_hostname_ptr(m, af, &address, ifindex, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize system hostname PTR RR: %m"); - - r = synthesize_gateway_ptr(m, af, &address, ifindex, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize gateway hostname PTR RR: %m"); - } else - continue; - - found = true; - } - - r = found; - - if (ret) { - *ret = answer; - answer = NULL; - } - - return r; -} diff --git a/src/resolve/resolved-dns-synthesize.h b/src/resolve/resolved-dns-synthesize.h deleted file mode 100644 index 5d829bb2e7..0000000000 --- a/src/resolve/resolved-dns-synthesize.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2016 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 "resolved-dns-answer.h" -#include "resolved-dns-question.h" -#include "resolved-manager.h" - -int dns_synthesize_ifindex(int ifindex); -int dns_synthesize_family(uint64_t flags); -DnsProtocol dns_synthesize_protocol(uint64_t flags); - -int dns_synthesize_answer(Manager *m, DnsQuestion *q, int ifindex, DnsAnswer **ret); diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c deleted file mode 100644 index a4a67623e7..0000000000 --- a/src/resolve/resolved-dns-transaction.c +++ /dev/null @@ -1,3048 +0,0 @@ -/*** - 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 "af-list.h" -#include "alloc-util.h" -#include "dns-domain.h" -#include "errno-list.h" -#include "fd-util.h" -#include "random-util.h" -#include "resolved-dns-cache.h" -#include "resolved-dns-transaction.h" -#include "resolved-llmnr.h" -#include "string-table.h" - -#define TRANSACTIONS_MAX 4096 - -static void dns_transaction_reset_answer(DnsTransaction *t) { - assert(t); - - t->received = dns_packet_unref(t->received); - t->answer = dns_answer_unref(t->answer); - t->answer_rcode = 0; - t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; - t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; - t->answer_authenticated = false; - t->answer_nsec_ttl = (uint32_t) -1; - t->answer_errno = 0; -} - -static void dns_transaction_flush_dnssec_transactions(DnsTransaction *t) { - DnsTransaction *z; - - assert(t); - - while ((z = set_steal_first(t->dnssec_transactions))) { - set_remove(z->notify_transactions, t); - set_remove(z->notify_transactions_done, t); - dns_transaction_gc(z); - } -} - -static void dns_transaction_close_connection(DnsTransaction *t) { - assert(t); - - t->stream = dns_stream_free(t->stream); - t->dns_udp_event_source = sd_event_source_unref(t->dns_udp_event_source); - t->dns_udp_fd = safe_close(t->dns_udp_fd); -} - -static void dns_transaction_stop_timeout(DnsTransaction *t) { - assert(t); - - t->timeout_event_source = sd_event_source_unref(t->timeout_event_source); -} - -DnsTransaction* dns_transaction_free(DnsTransaction *t) { - DnsQueryCandidate *c; - DnsZoneItem *i; - DnsTransaction *z; - - if (!t) - return NULL; - - log_debug("Freeing transaction %" PRIu16 ".", t->id); - - dns_transaction_close_connection(t); - dns_transaction_stop_timeout(t); - - dns_packet_unref(t->sent); - dns_transaction_reset_answer(t); - - dns_server_unref(t->server); - - if (t->scope) { - hashmap_remove_value(t->scope->transactions_by_key, t->key, t); - LIST_REMOVE(transactions_by_scope, t->scope->transactions, t); - - if (t->id != 0) - hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id)); - } - - while ((c = set_steal_first(t->notify_query_candidates))) - set_remove(c->transactions, t); - set_free(t->notify_query_candidates); - - while ((c = set_steal_first(t->notify_query_candidates_done))) - set_remove(c->transactions, t); - set_free(t->notify_query_candidates_done); - - while ((i = set_steal_first(t->notify_zone_items))) - i->probe_transaction = NULL; - set_free(t->notify_zone_items); - - while ((i = set_steal_first(t->notify_zone_items_done))) - i->probe_transaction = NULL; - set_free(t->notify_zone_items_done); - - while ((z = set_steal_first(t->notify_transactions))) - set_remove(z->dnssec_transactions, t); - set_free(t->notify_transactions); - - while ((z = set_steal_first(t->notify_transactions_done))) - set_remove(z->dnssec_transactions, t); - set_free(t->notify_transactions_done); - - dns_transaction_flush_dnssec_transactions(t); - set_free(t->dnssec_transactions); - - dns_answer_unref(t->validated_keys); - dns_resource_key_unref(t->key); - - free(t); - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsTransaction*, dns_transaction_free); - -bool dns_transaction_gc(DnsTransaction *t) { - assert(t); - - if (t->block_gc > 0) - return true; - - if (set_isempty(t->notify_query_candidates) && - set_isempty(t->notify_query_candidates_done) && - set_isempty(t->notify_zone_items) && - set_isempty(t->notify_zone_items_done) && - set_isempty(t->notify_transactions) && - set_isempty(t->notify_transactions_done)) { - dns_transaction_free(t); - return false; - } - - return true; -} - -static uint16_t pick_new_id(Manager *m) { - uint16_t new_id; - - /* Find a fresh, unused transaction id. Note that this loop is bounded because there's a limit on the number of - * transactions, and it's much lower than the space of IDs. */ - - assert_cc(TRANSACTIONS_MAX < 0xFFFF); - - do - random_bytes(&new_id, sizeof(new_id)); - while (new_id == 0 || - hashmap_get(m->dns_transactions, UINT_TO_PTR(new_id))); - - return new_id; -} - -int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) { - _cleanup_(dns_transaction_freep) DnsTransaction *t = NULL; - int r; - - assert(ret); - assert(s); - assert(key); - - /* Don't allow looking up invalid or pseudo RRs */ - if (!dns_type_is_valid_query(key->type)) - return -EINVAL; - if (dns_type_is_obsolete(key->type)) - return -EOPNOTSUPP; - - /* We only support the IN class */ - if (key->class != DNS_CLASS_IN && key->class != DNS_CLASS_ANY) - return -EOPNOTSUPP; - - if (hashmap_size(s->manager->dns_transactions) >= TRANSACTIONS_MAX) - return -EBUSY; - - r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&s->transactions_by_key, &dns_resource_key_hash_ops); - if (r < 0) - return r; - - t = new0(DnsTransaction, 1); - if (!t) - return -ENOMEM; - - t->dns_udp_fd = -1; - t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; - t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; - t->answer_nsec_ttl = (uint32_t) -1; - t->key = dns_resource_key_ref(key); - t->current_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID; - - t->id = pick_new_id(s->manager); - - r = hashmap_put(s->manager->dns_transactions, UINT_TO_PTR(t->id), t); - if (r < 0) { - t->id = 0; - return r; - } - - r = hashmap_replace(s->transactions_by_key, t->key, t); - if (r < 0) { - hashmap_remove(s->manager->dns_transactions, UINT_TO_PTR(t->id)); - return r; - } - - LIST_PREPEND(transactions_by_scope, s->transactions, t); - t->scope = s; - - s->manager->n_transactions_total++; - - if (ret) - *ret = t; - - t = NULL; - - return 0; -} - -static void dns_transaction_shuffle_id(DnsTransaction *t) { - uint16_t new_id; - assert(t); - - /* Pick a new ID for this transaction. */ - - new_id = pick_new_id(t->scope->manager); - assert_se(hashmap_remove_and_put(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id), UINT_TO_PTR(new_id), t) >= 0); - - log_debug("Transaction %" PRIu16 " is now %" PRIu16 ".", t->id, new_id); - t->id = new_id; - - /* Make sure we generate a new packet with the new ID */ - t->sent = dns_packet_unref(t->sent); -} - -static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) { - _cleanup_free_ char *pretty = NULL; - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - DnsZoneItem *z; - - assert(t); - assert(p); - - if (manager_our_packet(t->scope->manager, p) != 0) - return; - - (void) in_addr_to_string(p->family, &p->sender, &pretty); - - log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s got tentative packet from %s.", - t->id, - dns_resource_key_to_string(t->key, key_str, sizeof key_str), - dns_protocol_to_string(t->scope->protocol), - t->scope->link ? t->scope->link->name : "*", - af_to_name_short(t->scope->family), - strnull(pretty)); - - /* RFC 4795, Section 4.1 says that the peer with the - * lexicographically smaller IP address loses */ - if (memcmp(&p->sender, &p->destination, FAMILY_ADDRESS_SIZE(p->family)) >= 0) { - log_debug("Peer has lexicographically larger IP address and thus lost in the conflict."); - return; - } - - log_debug("We have the lexicographically larger IP address and thus lost in the conflict."); - - t->block_gc++; - - while ((z = set_first(t->notify_zone_items))) { - /* First, make sure the zone item drops the reference - * to us */ - dns_zone_item_probe_stop(z); - - /* Secondly, report this as conflict, so that we might - * look for a different hostname */ - dns_zone_item_conflict(z); - } - t->block_gc--; - - dns_transaction_gc(t); -} - -void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { - DnsQueryCandidate *c; - DnsZoneItem *z; - DnsTransaction *d; - const char *st; - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - - assert(t); - assert(!DNS_TRANSACTION_IS_LIVE(state)); - - if (state == DNS_TRANSACTION_DNSSEC_FAILED) { - dns_resource_key_to_string(t->key, key_str, sizeof key_str); - - log_struct(LOG_NOTICE, - LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_FAILURE), - LOG_MESSAGE("DNSSEC validation failed for question %s: %s", key_str, dnssec_result_to_string(t->answer_dnssec_result)), - "DNS_TRANSACTION=%" PRIu16, t->id, - "DNS_QUESTION=%s", key_str, - "DNSSEC_RESULT=%s", dnssec_result_to_string(t->answer_dnssec_result), - "DNS_SERVER=%s", dns_server_string(t->server), - "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(t->server->possible_feature_level), - NULL); - } - - /* Note that this call might invalidate the query. Callers - * should hence not attempt to access the query or transaction - * after calling this function. */ - - if (state == DNS_TRANSACTION_ERRNO) - st = errno_to_name(t->answer_errno); - else - st = dns_transaction_state_to_string(state); - - log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s now complete with <%s> from %s (%s).", - t->id, - dns_resource_key_to_string(t->key, key_str, sizeof key_str), - dns_protocol_to_string(t->scope->protocol), - t->scope->link ? t->scope->link->name : "*", - af_to_name_short(t->scope->family), - st, - t->answer_source < 0 ? "none" : dns_transaction_source_to_string(t->answer_source), - t->answer_authenticated ? "authenticated" : "unsigned"); - - t->state = state; - - dns_transaction_close_connection(t); - dns_transaction_stop_timeout(t); - - /* Notify all queries that are interested, but make sure the - * transaction isn't freed while we are still looking at it */ - t->block_gc++; - - SET_FOREACH_MOVE(c, t->notify_query_candidates_done, t->notify_query_candidates) - dns_query_candidate_notify(c); - SWAP_TWO(t->notify_query_candidates, t->notify_query_candidates_done); - - SET_FOREACH_MOVE(z, t->notify_zone_items_done, t->notify_zone_items) - dns_zone_item_notify(z); - SWAP_TWO(t->notify_zone_items, t->notify_zone_items_done); - - SET_FOREACH_MOVE(d, t->notify_transactions_done, t->notify_transactions) - dns_transaction_notify(d, t); - SWAP_TWO(t->notify_transactions, t->notify_transactions_done); - - t->block_gc--; - dns_transaction_gc(t); -} - -static int dns_transaction_pick_server(DnsTransaction *t) { - DnsServer *server; - - assert(t); - assert(t->scope->protocol == DNS_PROTOCOL_DNS); - - server = dns_scope_get_dns_server(t->scope); - if (!server) - return -ESRCH; - - t->current_feature_level = dns_server_possible_feature_level(server); - - if (server == t->server) - return 0; - - dns_server_unref(t->server); - t->server = dns_server_ref(server); - - return 1; -} - -static void dns_transaction_retry(DnsTransaction *t) { - int r; - - assert(t); - - log_debug("Retrying transaction %" PRIu16 ".", t->id); - - /* Before we try again, switch to a new server. */ - dns_scope_next_dns_server(t->scope); - - r = dns_transaction_go(t); - if (r < 0) { - t->answer_errno = -r; - dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); - } -} - -static int dns_transaction_maybe_restart(DnsTransaction *t) { - assert(t); - - if (!t->server) - return 0; - - if (t->current_feature_level <= dns_server_possible_feature_level(t->server)) - return 0; - - /* The server's current feature level is lower than when we sent the original query. We learnt something from - the response or possibly an auxiliary DNSSEC response that we didn't know before. We take that as reason to - restart the whole transaction. This is a good idea to deal with servers that respond rubbish if we include - OPT RR or DO bit. One of these cases is documented here, for example: - https://open.nlnetlabs.nl/pipermail/dnssec-trigger/2014-November/000376.html */ - - log_debug("Server feature level is now lower than when we began our transaction. Restarting with new ID."); - dns_transaction_shuffle_id(t); - return dns_transaction_go(t); -} - -static int on_stream_complete(DnsStream *s, int error) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - DnsTransaction *t; - - assert(s); - assert(s->transaction); - - /* Copy the data we care about out of the stream before we - * destroy it. */ - t = s->transaction; - p = dns_packet_ref(s->read_packet); - - t->stream = dns_stream_free(t->stream); - - if (ERRNO_IS_DISCONNECT(error)) { - usec_t usec; - - if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { - /* If the LLMNR/TCP connection failed, the host doesn't support LLMNR, and we cannot answer the - * question on this scope. */ - dns_transaction_complete(t, DNS_TRANSACTION_NOT_FOUND); - return 0; - } - - log_debug_errno(error, "Connection failure for DNS TCP stream: %m"); - assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &usec) >= 0); - dns_server_packet_lost(t->server, IPPROTO_TCP, t->current_feature_level, usec - t->start_usec); - - dns_transaction_retry(t); - return 0; - } - if (error != 0) { - t->answer_errno = error; - dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); - return 0; - } - - if (dns_packet_validate_reply(p) <= 0) { - log_debug("Invalid TCP reply packet."); - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return 0; - } - - dns_scope_check_conflicts(t->scope, p); - - t->block_gc++; - dns_transaction_process_reply(t, p); - t->block_gc--; - - /* If the response wasn't useful, then complete the transition - * now. After all, we are the worst feature set now with TCP - * sockets, and there's really no point in retrying. */ - if (t->state == DNS_TRANSACTION_PENDING) - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - else - dns_transaction_gc(t); - - return 0; -} - -static int dns_transaction_open_tcp(DnsTransaction *t) { - _cleanup_close_ int fd = -1; - int r; - - assert(t); - - dns_transaction_close_connection(t); - - switch (t->scope->protocol) { - - case DNS_PROTOCOL_DNS: - r = dns_transaction_pick_server(t); - if (r < 0) - return r; - - if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) - return -EOPNOTSUPP; - - r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level); - if (r < 0) - return r; - - fd = dns_scope_socket_tcp(t->scope, AF_UNSPEC, NULL, t->server, 53); - break; - - case DNS_PROTOCOL_LLMNR: - /* When we already received a reply to this (but it was truncated), send to its sender address */ - if (t->received) - fd = dns_scope_socket_tcp(t->scope, t->received->family, &t->received->sender, NULL, t->received->sender_port); - else { - union in_addr_union address; - int family = AF_UNSPEC; - - /* Otherwise, try to talk to the owner of a - * the IP address, in case this is a reverse - * PTR lookup */ - - r = dns_name_address(dns_resource_key_name(t->key), &family, &address); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - if (family != t->scope->family) - return -ESRCH; - - fd = dns_scope_socket_tcp(t->scope, family, &address, NULL, LLMNR_PORT); - } - - break; - - default: - return -EAFNOSUPPORT; - } - - if (fd < 0) - return fd; - - r = dns_stream_new(t->scope->manager, &t->stream, t->scope->protocol, fd); - if (r < 0) - return r; - fd = -1; - - r = dns_stream_write_packet(t->stream, t->sent); - if (r < 0) { - t->stream = dns_stream_free(t->stream); - return r; - } - - t->stream->complete = on_stream_complete; - t->stream->transaction = t; - - /* The interface index is difficult to determine if we are - * connecting to the local host, hence fill this in right away - * instead of determining it from the socket */ - if (t->scope->link) - t->stream->ifindex = t->scope->link->ifindex; - - dns_transaction_reset_answer(t); - - t->tried_stream = true; - - return 0; -} - -static void dns_transaction_cache_answer(DnsTransaction *t) { - assert(t); - - /* For mDNS we cache whenever we get the packet, rather than - * in each transaction. */ - if (!IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) - return; - - /* We never cache if this packet is from the local host, under - * the assumption that a locally running DNS server would - * cache this anyway, and probably knows better when to flush - * the cache then we could. */ - if (!DNS_PACKET_SHALL_CACHE(t->received)) - return; - - dns_cache_put(&t->scope->cache, - t->key, - t->answer_rcode, - t->answer, - t->answer_authenticated, - t->answer_nsec_ttl, - 0, - t->received->family, - &t->received->sender); -} - -static bool dns_transaction_dnssec_is_live(DnsTransaction *t) { - DnsTransaction *dt; - Iterator i; - - assert(t); - - SET_FOREACH(dt, t->dnssec_transactions, i) - if (DNS_TRANSACTION_IS_LIVE(dt->state)) - return true; - - return false; -} - -static int dns_transaction_dnssec_ready(DnsTransaction *t) { - DnsTransaction *dt; - Iterator i; - - assert(t); - - /* Checks whether the auxiliary DNSSEC transactions of our transaction have completed, or are still - * ongoing. Returns 0, if we aren't ready for the DNSSEC validation, positive if we are. */ - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - switch (dt->state) { - - case DNS_TRANSACTION_NULL: - case DNS_TRANSACTION_PENDING: - case DNS_TRANSACTION_VALIDATING: - /* Still ongoing */ - return 0; - - case DNS_TRANSACTION_RCODE_FAILURE: - if (dt->answer_rcode != DNS_RCODE_NXDOMAIN) { - log_debug("Auxiliary DNSSEC RR query failed with rcode=%s.", dns_rcode_to_string(dt->answer_rcode)); - goto fail; - } - - /* Fall-through: NXDOMAIN is good enough for us. This is because some DNS servers erronously - * return NXDOMAIN for empty non-terminals (Akamai...), and we need to handle that nicely, when - * asking for parent SOA or similar RRs to make unsigned proofs. */ - - case DNS_TRANSACTION_SUCCESS: - /* All good. */ - break; - - case DNS_TRANSACTION_DNSSEC_FAILED: - /* We handle DNSSEC failures different from other errors, as we care about the DNSSEC - * validationr result */ - - log_debug("Auxiliary DNSSEC RR query failed validation: %s", dnssec_result_to_string(dt->answer_dnssec_result)); - t->answer_dnssec_result = dt->answer_dnssec_result; /* Copy error code over */ - dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); - return 0; - - - default: - log_debug("Auxiliary DNSSEC RR query failed with %s", dns_transaction_state_to_string(dt->state)); - goto fail; - } - } - - /* All is ready, we can go and validate */ - return 1; - -fail: - t->answer_dnssec_result = DNSSEC_FAILED_AUXILIARY; - dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); - return 0; -} - -static void dns_transaction_process_dnssec(DnsTransaction *t) { - int r; - - assert(t); - - /* Are there ongoing DNSSEC transactions? If so, let's wait for them. */ - r = dns_transaction_dnssec_ready(t); - if (r < 0) - goto fail; - if (r == 0) /* We aren't ready yet (or one of our auxiliary transactions failed, and we shouldn't validate now */ - return; - - /* See if we learnt things from the additional DNSSEC transactions, that we didn't know before, and better - * restart the lookup immediately. */ - r = dns_transaction_maybe_restart(t); - if (r < 0) - goto fail; - if (r > 0) /* Transaction got restarted... */ - return; - - /* All our auxiliary DNSSEC transactions are complete now. Try - * to validate our RRset now. */ - r = dns_transaction_validate_dnssec(t); - if (r == -EBADMSG) { - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - if (r < 0) - goto fail; - - if (t->answer_dnssec_result == DNSSEC_INCOMPATIBLE_SERVER && - t->scope->dnssec_mode == DNSSEC_YES) { - /* We are not in automatic downgrade mode, and the - * server is bad, refuse operation. */ - dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); - return; - } - - if (!IN_SET(t->answer_dnssec_result, - _DNSSEC_RESULT_INVALID, /* No DNSSEC validation enabled */ - DNSSEC_VALIDATED, /* Answer is signed and validated successfully */ - DNSSEC_UNSIGNED, /* Answer is right-fully unsigned */ - DNSSEC_INCOMPATIBLE_SERVER)) { /* Server does not do DNSSEC (Yay, we are downgrade attack vulnerable!) */ - dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); - return; - } - - if (t->answer_dnssec_result == DNSSEC_INCOMPATIBLE_SERVER) - dns_server_warn_downgrade(t->server); - - dns_transaction_cache_answer(t); - - if (t->answer_rcode == DNS_RCODE_SUCCESS) - dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - else - dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); - - return; - -fail: - t->answer_errno = -r; - dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); -} - -static int dns_transaction_has_positive_answer(DnsTransaction *t, DnsAnswerFlags *flags) { - int r; - - assert(t); - - /* Checks whether the answer is positive, i.e. either a direct - * answer to the question, or a CNAME/DNAME for it */ - - r = dns_answer_match_key(t->answer, t->key, flags); - if (r != 0) - return r; - - r = dns_answer_find_cname_or_dname(t->answer, t->key, NULL, flags); - if (r != 0) - return r; - - return false; -} - -static int dns_transaction_fix_rcode(DnsTransaction *t) { - int r; - - assert(t); - - /* Fix up the RCODE to SUCCESS if we get at least one matching RR in a response. Note that this contradicts the - * DNS RFCs a bit. Specifically, RFC 6604 Section 3 clarifies that the RCODE shall say something about a - * CNAME/DNAME chain element coming after the last chain element contained in the message, and not the first - * one included. However, it also indicates that not all DNS servers implement this correctly. Moreover, when - * using DNSSEC we usually only can prove the first element of a CNAME/DNAME chain anyway, hence let's settle - * on always processing the RCODE as referring to the immediate look-up we do, i.e. the first element of a - * CNAME/DNAME chain. This way, we uniformly handle CNAME/DNAME chains, regardless if the DNS server - * incorrectly implements RCODE, whether DNSSEC is in use, or whether the DNS server only supplied us with an - * incomplete CNAME/DNAME chain. - * - * Or in other words: if we get at least one positive reply in a message we patch NXDOMAIN to become SUCCESS, - * and then rely on the CNAME chasing logic to figure out that there's actually a CNAME error with a new - * lookup. */ - - if (t->answer_rcode != DNS_RCODE_NXDOMAIN) - return 0; - - r = dns_transaction_has_positive_answer(t, NULL); - if (r <= 0) - return r; - - t->answer_rcode = DNS_RCODE_SUCCESS; - return 0; -} - -void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { - usec_t ts; - int r; - - assert(t); - assert(p); - assert(t->scope); - assert(t->scope->manager); - - if (t->state != DNS_TRANSACTION_PENDING) - return; - - /* Note that this call might invalidate the query. Callers - * should hence not attempt to access the query or transaction - * after calling this function. */ - - log_debug("Processing incoming packet on transaction %" PRIu16".", t->id); - - switch (t->scope->protocol) { - - case DNS_PROTOCOL_LLMNR: - assert(t->scope->link); - - /* For LLMNR we will not accept any packets from other - * interfaces */ - - if (p->ifindex != t->scope->link->ifindex) - return; - - if (p->family != t->scope->family) - return; - - /* Tentative packets are not full responses but still - * useful for identifying uniqueness conflicts during - * probing. */ - if (DNS_PACKET_LLMNR_T(p)) { - dns_transaction_tentative(t, p); - return; - } - - break; - - case DNS_PROTOCOL_MDNS: - assert(t->scope->link); - - /* For mDNS we will not accept any packets from other interfaces */ - if (p->ifindex != t->scope->link->ifindex) - return; - - if (p->family != t->scope->family) - return; - - break; - - case DNS_PROTOCOL_DNS: - /* Note that we do not need to verify the - * addresses/port numbers of incoming traffic, as we - * invoked connect() on our UDP socket in which case - * the kernel already does the needed verification for - * us. */ - break; - - default: - assert_not_reached("Invalid DNS protocol."); - } - - if (t->received != p) { - dns_packet_unref(t->received); - t->received = dns_packet_ref(p); - } - - t->answer_source = DNS_TRANSACTION_NETWORK; - - if (p->ipproto == IPPROTO_TCP) { - if (DNS_PACKET_TC(p)) { - /* Truncated via TCP? Somebody must be fucking with us */ - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - - if (DNS_PACKET_ID(p) != t->id) { - /* Not the reply to our query? Somebody must be fucking with us */ - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - } - - assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); - - switch (t->scope->protocol) { - - case DNS_PROTOCOL_DNS: - assert(t->server); - - if (IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) { - - /* Request failed, immediately try again with reduced features */ - log_debug("Server returned error: %s", dns_rcode_to_string(DNS_PACKET_RCODE(p))); - - dns_server_packet_failed(t->server, t->current_feature_level); - dns_transaction_retry(t); - return; - } else if (DNS_PACKET_TC(p)) - dns_server_packet_truncated(t->server, t->current_feature_level); - - break; - - case DNS_PROTOCOL_LLMNR: - case DNS_PROTOCOL_MDNS: - dns_scope_packet_received(t->scope, ts - t->start_usec); - break; - - default: - assert_not_reached("Invalid DNS protocol."); - } - - if (DNS_PACKET_TC(p)) { - - /* Truncated packets for mDNS are not allowed. Give up immediately. */ - if (t->scope->protocol == DNS_PROTOCOL_MDNS) { - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - - log_debug("Reply truncated, retrying via TCP."); - - /* Response was truncated, let's try again with good old TCP */ - r = dns_transaction_open_tcp(t); - if (r == -ESRCH) { - /* No servers found? Damn! */ - dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); - return; - } - if (r == -EOPNOTSUPP) { - /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */ - dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); - return; - } - if (r < 0) { - /* On LLMNR, if we cannot connect to the host, - * we immediately give up */ - if (t->scope->protocol != DNS_PROTOCOL_DNS) - goto fail; - - /* On DNS, couldn't send? Try immediately again, with a new server */ - dns_transaction_retry(t); - } - - return; - } - - /* After the superficial checks, actually parse the message. */ - r = dns_packet_extract(p); - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - - /* Report that the OPT RR was missing */ - if (t->server) { - if (!p->opt) - dns_server_packet_bad_opt(t->server, t->current_feature_level); - - dns_server_packet_received(t->server, p->ipproto, t->current_feature_level, ts - t->start_usec, p->size); - } - - /* See if we know things we didn't know before that indicate we better restart the lookup immediately. */ - r = dns_transaction_maybe_restart(t); - if (r < 0) - goto fail; - if (r > 0) /* Transaction got restarted... */ - return; - - if (IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) { - - /* Only consider responses with equivalent query section to the request */ - r = dns_packet_is_reply_for(p, t->key); - if (r < 0) - goto fail; - if (r == 0) { - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - - /* Install the answer as answer to the transaction */ - dns_answer_unref(t->answer); - t->answer = dns_answer_ref(p->answer); - t->answer_rcode = DNS_PACKET_RCODE(p); - t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; - t->answer_authenticated = false; - - r = dns_transaction_fix_rcode(t); - if (r < 0) - goto fail; - - /* Block GC while starting requests for additional DNSSEC RRs */ - t->block_gc++; - r = dns_transaction_request_dnssec_keys(t); - t->block_gc--; - - /* Maybe the transaction is ready for GC'ing now? If so, free it and return. */ - if (!dns_transaction_gc(t)) - return; - - /* Requesting additional keys might have resulted in - * this transaction to fail, since the auxiliary - * request failed for some reason. If so, we are not - * in pending state anymore, and we should exit - * quickly. */ - if (t->state != DNS_TRANSACTION_PENDING) - return; - if (r < 0) - goto fail; - if (r > 0) { - /* There are DNSSEC transactions pending now. Update the state accordingly. */ - t->state = DNS_TRANSACTION_VALIDATING; - dns_transaction_close_connection(t); - dns_transaction_stop_timeout(t); - return; - } - } - - dns_transaction_process_dnssec(t); - return; - -fail: - t->answer_errno = -r; - dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); -} - -static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - DnsTransaction *t = userdata; - int r; - - assert(t); - assert(t->scope); - - r = manager_recv(t->scope->manager, fd, DNS_PROTOCOL_DNS, &p); - if (ERRNO_IS_DISCONNECT(-r)) { - usec_t usec; - - /* UDP connection failure get reported via ICMP and then are possible delivered to us on the next - * recvmsg(). Treat this like a lost packet. */ - - log_debug_errno(r, "Connection failure for DNS UDP packet: %m"); - assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &usec) >= 0); - dns_server_packet_lost(t->server, IPPROTO_UDP, t->current_feature_level, usec - t->start_usec); - - dns_transaction_retry(t); - return 0; - } - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); - t->answer_errno = -r; - return 0; - } - - r = dns_packet_validate_reply(p); - if (r < 0) { - log_debug_errno(r, "Received invalid DNS packet as response, ignoring: %m"); - return 0; - } - if (r == 0) { - log_debug("Received inappropriate DNS packet as response, ignoring."); - return 0; - } - - if (DNS_PACKET_ID(p) != t->id) { - log_debug("Received packet with incorrect transaction ID, ignoring."); - return 0; - } - - dns_transaction_process_reply(t, p); - return 0; -} - -static int dns_transaction_emit_udp(DnsTransaction *t) { - int r; - - assert(t); - - if (t->scope->protocol == DNS_PROTOCOL_DNS) { - - r = dns_transaction_pick_server(t); - if (r < 0) - return r; - - if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_UDP) - return -EAGAIN; - - if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) - return -EOPNOTSUPP; - - if (r > 0 || t->dns_udp_fd < 0) { /* Server changed, or no connection yet. */ - int fd; - - dns_transaction_close_connection(t); - - fd = dns_scope_socket_udp(t->scope, t->server, 53); - if (fd < 0) - return fd; - - r = sd_event_add_io(t->scope->manager->event, &t->dns_udp_event_source, fd, EPOLLIN, on_dns_packet, t); - if (r < 0) { - safe_close(fd); - return r; - } - - (void) sd_event_source_set_description(t->dns_udp_event_source, "dns-transaction-udp"); - t->dns_udp_fd = fd; - } - - r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level); - if (r < 0) - return r; - } else - dns_transaction_close_connection(t); - - r = dns_scope_emit_udp(t->scope, t->dns_udp_fd, t->sent); - if (r < 0) - return r; - - dns_transaction_reset_answer(t); - - return 0; -} - -static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) { - DnsTransaction *t = userdata; - - assert(s); - assert(t); - - if (!t->initial_jitter_scheduled || t->initial_jitter_elapsed) { - /* Timeout reached? Increase the timeout for the server used */ - switch (t->scope->protocol) { - - case DNS_PROTOCOL_DNS: - assert(t->server); - dns_server_packet_lost(t->server, t->stream ? IPPROTO_TCP : IPPROTO_UDP, t->current_feature_level, usec - t->start_usec); - break; - - case DNS_PROTOCOL_LLMNR: - case DNS_PROTOCOL_MDNS: - dns_scope_packet_lost(t->scope, usec - t->start_usec); - break; - - default: - assert_not_reached("Invalid DNS protocol."); - } - - if (t->initial_jitter_scheduled) - t->initial_jitter_elapsed = true; - } - - log_debug("Timeout reached on transaction %" PRIu16 ".", t->id); - - dns_transaction_retry(t); - return 0; -} - -static usec_t transaction_get_resend_timeout(DnsTransaction *t) { - assert(t); - assert(t->scope); - - switch (t->scope->protocol) { - - case DNS_PROTOCOL_DNS: - assert(t->server); - return t->server->resend_timeout; - - case DNS_PROTOCOL_MDNS: - assert(t->n_attempts > 0); - return (1 << (t->n_attempts - 1)) * USEC_PER_SEC; - - case DNS_PROTOCOL_LLMNR: - return t->scope->resend_timeout; - - default: - assert_not_reached("Invalid DNS protocol."); - } -} - -static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { - int r; - - assert(t); - - dns_transaction_stop_timeout(t); - - r = dns_scope_network_good(t->scope); - if (r < 0) - return r; - if (r == 0) { - dns_transaction_complete(t, DNS_TRANSACTION_NETWORK_DOWN); - return 0; - } - - if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t->scope->protocol)) { - dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); - return 0; - } - - if (t->scope->protocol == DNS_PROTOCOL_LLMNR && t->tried_stream) { - /* If we already tried via a stream, then we don't - * retry on LLMNR. See RFC 4795, Section 2.7. */ - dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); - return 0; - } - - t->n_attempts++; - t->start_usec = ts; - - dns_transaction_reset_answer(t); - dns_transaction_flush_dnssec_transactions(t); - - /* Check the trust anchor. Do so only on classic DNS, since DNSSEC does not apply otherwise. */ - if (t->scope->protocol == DNS_PROTOCOL_DNS) { - r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, t->key, &t->answer); - if (r < 0) - return r; - if (r > 0) { - t->answer_rcode = DNS_RCODE_SUCCESS; - t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR; - t->answer_authenticated = true; - dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - return 0; - } - - if (dns_name_is_root(dns_resource_key_name(t->key)) && - t->key->type == DNS_TYPE_DS) { - - /* Hmm, this is a request for the root DS? A - * DS RR doesn't exist in the root zone, and - * if our trust anchor didn't know it either, - * this means we cannot do any DNSSEC logic - * anymore. */ - - if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) { - /* We are in downgrade mode. In this - * case, synthesize an unsigned empty - * response, so that the any lookup - * depending on this one can continue - * assuming there was no DS, and hence - * the root zone was unsigned. */ - - t->answer_rcode = DNS_RCODE_SUCCESS; - t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR; - t->answer_authenticated = false; - dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - } else - /* If we are not in downgrade mode, - * then fail the lookup, because we - * cannot reasonably answer it. There - * might be DS RRs, but we don't know - * them, and the DNS server won't tell - * them to us (and even if it would, - * we couldn't validate and trust them. */ - dns_transaction_complete(t, DNS_TRANSACTION_NO_TRUST_ANCHOR); - - return 0; - } - } - - /* Check the zone, but only if this transaction is not used - * for probing or verifying a zone item. */ - if (set_isempty(t->notify_zone_items)) { - - r = dns_zone_lookup(&t->scope->zone, t->key, &t->answer, NULL, NULL); - if (r < 0) - return r; - if (r > 0) { - t->answer_rcode = DNS_RCODE_SUCCESS; - t->answer_source = DNS_TRANSACTION_ZONE; - t->answer_authenticated = true; - dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - return 0; - } - } - - /* Check the cache, but only if this transaction is not used - * for probing or verifying a zone item. */ - if (set_isempty(t->notify_zone_items)) { - - /* Before trying the cache, let's make sure we figured out a - * server to use. Should this cause a change of server this - * might flush the cache. */ - dns_scope_get_dns_server(t->scope); - - /* Let's then prune all outdated entries */ - dns_cache_prune(&t->scope->cache); - - r = dns_cache_lookup(&t->scope->cache, t->key, &t->answer_rcode, &t->answer, &t->answer_authenticated); - if (r < 0) - return r; - if (r > 0) { - t->answer_source = DNS_TRANSACTION_CACHE; - if (t->answer_rcode == DNS_RCODE_SUCCESS) - dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - else - dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); - return 0; - } - } - - return 1; -} - -static int dns_transaction_make_packet_mdns(DnsTransaction *t) { - - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - bool add_known_answers = false; - DnsTransaction *other; - unsigned qdcount; - usec_t ts; - int r; - - assert(t); - assert(t->scope->protocol == DNS_PROTOCOL_MDNS); - - /* Discard any previously prepared packet, so we can start over and coalesce again */ - t->sent = dns_packet_unref(t->sent); - - r = dns_packet_new_query(&p, t->scope->protocol, 0, false); - if (r < 0) - return r; - - r = dns_packet_append_key(p, t->key, NULL); - if (r < 0) - return r; - - qdcount = 1; - - if (dns_key_is_shared(t->key)) - add_known_answers = true; - - /* - * For mDNS, we want to coalesce as many open queries in pending transactions into one single - * query packet on the wire as possible. To achieve that, we iterate through all pending transactions - * in our current scope, and see whether their timing contraints allow them to be sent. - */ - - assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); - - LIST_FOREACH(transactions_by_scope, other, t->scope->transactions) { - - /* Skip ourselves */ - if (other == t) - continue; - - if (other->state != DNS_TRANSACTION_PENDING) - continue; - - if (other->next_attempt_after > ts) - continue; - - if (qdcount >= UINT16_MAX) - break; - - r = dns_packet_append_key(p, other->key, NULL); - - /* - * If we can't stuff more questions into the packet, just give up. - * One of the 'other' transactions will fire later and take care of the rest. - */ - if (r == -EMSGSIZE) - break; - - if (r < 0) - return r; - - r = dns_transaction_prepare(other, ts); - if (r <= 0) - continue; - - ts += transaction_get_resend_timeout(other); - - r = sd_event_add_time( - other->scope->manager->event, - &other->timeout_event_source, - clock_boottime_or_monotonic(), - ts, 0, - on_transaction_timeout, other); - if (r < 0) - return r; - - (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); - - other->state = DNS_TRANSACTION_PENDING; - other->next_attempt_after = ts; - - qdcount++; - - if (dns_key_is_shared(other->key)) - add_known_answers = true; - } - - DNS_PACKET_HEADER(p)->qdcount = htobe16(qdcount); - - /* Append known answer section if we're asking for any shared record */ - if (add_known_answers) { - r = dns_cache_export_shared_to_packet(&t->scope->cache, p); - if (r < 0) - return r; - } - - t->sent = p; - p = NULL; - - return 0; -} - -static int dns_transaction_make_packet(DnsTransaction *t) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - int r; - - assert(t); - - if (t->scope->protocol == DNS_PROTOCOL_MDNS) - return dns_transaction_make_packet_mdns(t); - - if (t->sent) - return 0; - - r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode != DNSSEC_NO); - if (r < 0) - return r; - - r = dns_packet_append_key(p, t->key, NULL); - if (r < 0) - return r; - - DNS_PACKET_HEADER(p)->qdcount = htobe16(1); - DNS_PACKET_HEADER(p)->id = t->id; - - t->sent = p; - p = NULL; - - return 0; -} - -int dns_transaction_go(DnsTransaction *t) { - usec_t ts; - int r; - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - - assert(t); - - assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); - - r = dns_transaction_prepare(t, ts); - if (r <= 0) - return r; - - log_debug("Transaction %" PRIu16 " for <%s> scope %s on %s/%s.", - t->id, - dns_resource_key_to_string(t->key, key_str, sizeof key_str), - dns_protocol_to_string(t->scope->protocol), - t->scope->link ? t->scope->link->name : "*", - af_to_name_short(t->scope->family)); - - if (!t->initial_jitter_scheduled && - (t->scope->protocol == DNS_PROTOCOL_LLMNR || - t->scope->protocol == DNS_PROTOCOL_MDNS)) { - usec_t jitter, accuracy; - - /* RFC 4795 Section 2.7 suggests all queries should be - * delayed by a random time from 0 to JITTER_INTERVAL. */ - - t->initial_jitter_scheduled = true; - - random_bytes(&jitter, sizeof(jitter)); - - switch (t->scope->protocol) { - - case DNS_PROTOCOL_LLMNR: - jitter %= LLMNR_JITTER_INTERVAL_USEC; - accuracy = LLMNR_JITTER_INTERVAL_USEC; - break; - - case DNS_PROTOCOL_MDNS: - jitter %= MDNS_JITTER_RANGE_USEC; - jitter += MDNS_JITTER_MIN_USEC; - accuracy = MDNS_JITTER_RANGE_USEC; - break; - default: - assert_not_reached("bad protocol"); - } - - r = sd_event_add_time( - t->scope->manager->event, - &t->timeout_event_source, - clock_boottime_or_monotonic(), - ts + jitter, accuracy, - on_transaction_timeout, t); - if (r < 0) - return r; - - (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); - - t->n_attempts = 0; - t->next_attempt_after = ts; - t->state = DNS_TRANSACTION_PENDING; - - log_debug("Delaying %s transaction for " USEC_FMT "us.", dns_protocol_to_string(t->scope->protocol), jitter); - return 0; - } - - /* Otherwise, we need to ask the network */ - r = dns_transaction_make_packet(t); - if (r < 0) - return r; - - if (t->scope->protocol == DNS_PROTOCOL_LLMNR && - (dns_name_endswith(dns_resource_key_name(t->key), "in-addr.arpa") > 0 || - dns_name_endswith(dns_resource_key_name(t->key), "ip6.arpa") > 0)) { - - /* RFC 4795, Section 2.4. says reverse lookups shall - * always be made via TCP on LLMNR */ - r = dns_transaction_open_tcp(t); - } else { - /* Try via UDP, and if that fails due to large size or lack of - * support try via TCP */ - r = dns_transaction_emit_udp(t); - if (r == -EMSGSIZE) - log_debug("Sending query via TCP since it is too large."); - if (r == -EAGAIN) - log_debug("Sending query via TCP since server doesn't support UDP."); - if (r == -EMSGSIZE || r == -EAGAIN) - r = dns_transaction_open_tcp(t); - } - - if (r == -ESRCH) { - /* No servers to send this to? */ - dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); - return 0; - } - if (r == -EOPNOTSUPP) { - /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */ - dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); - return 0; - } - if (t->scope->protocol == DNS_PROTOCOL_LLMNR && ERRNO_IS_DISCONNECT(-r)) { - /* On LLMNR, if we cannot connect to a host via TCP when doing reverse lookups. This means we cannot - * answer this request with this protocol. */ - dns_transaction_complete(t, DNS_TRANSACTION_NOT_FOUND); - return 0; - } - if (r < 0) { - if (t->scope->protocol != DNS_PROTOCOL_DNS) - return r; - - /* Couldn't send? Try immediately again, with a new server */ - dns_scope_next_dns_server(t->scope); - - return dns_transaction_go(t); - } - - ts += transaction_get_resend_timeout(t); - - r = sd_event_add_time( - t->scope->manager->event, - &t->timeout_event_source, - clock_boottime_or_monotonic(), - ts, 0, - on_transaction_timeout, t); - if (r < 0) - return r; - - (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); - - t->state = DNS_TRANSACTION_PENDING; - t->next_attempt_after = ts; - - return 1; -} - -static int dns_transaction_find_cyclic(DnsTransaction *t, DnsTransaction *aux) { - DnsTransaction *n; - Iterator i; - int r; - - assert(t); - assert(aux); - - /* Try to find cyclic dependencies between transaction objects */ - - if (t == aux) - return 1; - - SET_FOREACH(n, aux->dnssec_transactions, i) { - r = dns_transaction_find_cyclic(t, n); - if (r != 0) - return r; - } - - return 0; -} - -static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResourceKey *key, DnsTransaction **ret) { - DnsTransaction *aux; - int r; - - assert(t); - assert(ret); - assert(key); - - aux = dns_scope_find_transaction(t->scope, key, true); - if (!aux) { - r = dns_transaction_new(&aux, t->scope, key); - if (r < 0) - return r; - } else { - if (set_contains(t->dnssec_transactions, aux)) { - *ret = aux; - return 0; - } - - r = dns_transaction_find_cyclic(t, aux); - if (r < 0) - return r; - if (r > 0) { - char s[DNS_RESOURCE_KEY_STRING_MAX], saux[DNS_RESOURCE_KEY_STRING_MAX]; - - log_debug("Potential cyclic dependency, refusing to add transaction %" PRIu16 " (%s) as dependency for %" PRIu16 " (%s).", - aux->id, - dns_resource_key_to_string(t->key, s, sizeof s), - t->id, - dns_resource_key_to_string(aux->key, saux, sizeof saux)); - - return -ELOOP; - } - } - - r = set_ensure_allocated(&t->dnssec_transactions, NULL); - if (r < 0) - goto gc; - - r = set_ensure_allocated(&aux->notify_transactions, NULL); - if (r < 0) - goto gc; - - r = set_ensure_allocated(&aux->notify_transactions_done, NULL); - if (r < 0) - goto gc; - - r = set_put(t->dnssec_transactions, aux); - if (r < 0) - goto gc; - - r = set_put(aux->notify_transactions, t); - if (r < 0) { - (void) set_remove(t->dnssec_transactions, aux); - goto gc; - } - - *ret = aux; - return 1; - -gc: - dns_transaction_gc(aux); - return r; -} - -static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey *key) { - _cleanup_(dns_answer_unrefp) DnsAnswer *a = NULL; - DnsTransaction *aux; - int r; - - assert(t); - assert(key); - - /* Try to get the data from the trust anchor */ - r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, key, &a); - if (r < 0) - return r; - if (r > 0) { - r = dns_answer_extend(&t->validated_keys, a); - if (r < 0) - return r; - - return 0; - } - - /* This didn't work, ask for it via the network/cache then. */ - r = dns_transaction_add_dnssec_transaction(t, key, &aux); - if (r == -ELOOP) /* This would result in a cyclic dependency */ - return 0; - if (r < 0) - return r; - - if (aux->state == DNS_TRANSACTION_NULL) { - r = dns_transaction_go(aux); - if (r < 0) - return r; - } - - return 1; -} - -static int dns_transaction_negative_trust_anchor_lookup(DnsTransaction *t, const char *name) { - int r; - - assert(t); - - /* Check whether the specified name is in the NTA - * database, either in the global one, or the link-local - * one. */ - - r = dns_trust_anchor_lookup_negative(&t->scope->manager->trust_anchor, name); - if (r != 0) - return r; - - if (!t->scope->link) - return 0; - - return set_contains(t->scope->link->dnssec_negative_trust_anchors, name); -} - -static int dns_transaction_has_unsigned_negative_answer(DnsTransaction *t) { - int r; - - assert(t); - - /* Checks whether the answer is negative, and lacks NSEC/NSEC3 - * RRs to prove it */ - - r = dns_transaction_has_positive_answer(t, NULL); - if (r < 0) - return r; - if (r > 0) - return false; - - /* Is this key explicitly listed as a negative trust anchor? - * If so, it's nothing we need to care about */ - r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(t->key)); - if (r < 0) - return r; - if (r > 0) - return false; - - /* The answer does not contain any RRs that match to the - * question. If so, let's see if there are any NSEC/NSEC3 RRs - * included. If not, the answer is unsigned. */ - - r = dns_answer_contains_nsec_or_nsec3(t->answer); - if (r < 0) - return r; - if (r > 0) - return false; - - return true; -} - -static int dns_transaction_is_primary_response(DnsTransaction *t, DnsResourceRecord *rr) { - int r; - - assert(t); - assert(rr); - - /* Check if the specified RR is the "primary" response, - * i.e. either matches the question precisely or is a - * CNAME/DNAME for it. */ - - r = dns_resource_key_match_rr(t->key, rr, NULL); - if (r != 0) - return r; - - return dns_resource_key_match_cname_or_dname(t->key, rr->key, NULL); -} - -static bool dns_transaction_dnssec_supported(DnsTransaction *t) { - assert(t); - - /* Checks whether our transaction's DNS server is assumed to be compatible with DNSSEC. Returns false as soon - * as we changed our mind about a server, and now believe it is incompatible with DNSSEC. */ - - if (t->scope->protocol != DNS_PROTOCOL_DNS) - return false; - - /* If we have picked no server, then we are working from the cache or some other source, and DNSSEC might well - * be supported, hence return true. */ - if (!t->server) - return true; - - if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_DO) - return false; - - return dns_server_dnssec_supported(t->server); -} - -static bool dns_transaction_dnssec_supported_full(DnsTransaction *t) { - DnsTransaction *dt; - Iterator i; - - assert(t); - - /* Checks whether our transaction our any of the auxiliary transactions couldn't do DNSSEC. */ - - if (!dns_transaction_dnssec_supported(t)) - return false; - - SET_FOREACH(dt, t->dnssec_transactions, i) - if (!dns_transaction_dnssec_supported(dt)) - return false; - - return true; -} - -int dns_transaction_request_dnssec_keys(DnsTransaction *t) { - DnsResourceRecord *rr; - - int r; - - assert(t); - - /* - * Retrieve all auxiliary RRs for the answer we got, so that - * we can verify signatures or prove that RRs are rightfully - * unsigned. Specifically: - * - * - For RRSIG we get the matching DNSKEY - * - For DNSKEY we get the matching DS - * - For unsigned SOA/NS we get the matching DS - * - For unsigned CNAME/DNAME/DS we get the parent SOA RR - * - For other unsigned RRs we get the matching SOA RR - * - For SOA/NS queries with no matching response RR, and no NSEC/NSEC3, the DS RR - * - For DS queries with no matching response RRs, and no NSEC/NSEC3, the parent's SOA RR - * - For other queries with no matching response RRs, and no NSEC/NSEC3, the SOA RR - */ - - if (t->scope->dnssec_mode == DNSSEC_NO) - return 0; - if (t->answer_source != DNS_TRANSACTION_NETWORK) - return 0; /* We only need to validate stuff from the network */ - if (!dns_transaction_dnssec_supported(t)) - return 0; /* If we can't do DNSSEC anyway there's no point in geting the auxiliary RRs */ - - DNS_ANSWER_FOREACH(rr, t->answer) { - - if (dns_type_is_pseudo(rr->key->type)) - continue; - - /* If this RR is in the negative trust anchor, we don't need to validate it. */ - r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(rr->key)); - if (r < 0) - return r; - if (r > 0) - continue; - - switch (rr->key->type) { - - case DNS_TYPE_RRSIG: { - /* For each RRSIG we request the matching DNSKEY */ - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *dnskey = NULL; - - /* If this RRSIG is about a DNSKEY RR and the - * signer is the same as the owner, then we - * already have the DNSKEY, and we don't have - * to look for more. */ - if (rr->rrsig.type_covered == DNS_TYPE_DNSKEY) { - r = dns_name_equal(rr->rrsig.signer, dns_resource_key_name(rr->key)); - if (r < 0) - return r; - if (r > 0) - continue; - } - - /* If the signer is not a parent of our - * original query, then this is about an - * auxiliary RRset, but not anything we asked - * for. In this case we aren't interested, - * because we don't want to request additional - * RRs for stuff we didn't really ask for, and - * also to avoid request loops, where - * additional RRs from one transaction result - * in another transaction whose additonal RRs - * point back to the original transaction, and - * we deadlock. */ - r = dns_name_endswith(dns_resource_key_name(t->key), rr->rrsig.signer); - if (r < 0) - return r; - if (r == 0) - continue; - - dnskey = dns_resource_key_new(rr->key->class, DNS_TYPE_DNSKEY, rr->rrsig.signer); - if (!dnskey) - return -ENOMEM; - - log_debug("Requesting DNSKEY to validate transaction %" PRIu16" (%s, RRSIG with key tag: %" PRIu16 ").", - t->id, dns_resource_key_name(rr->key), rr->rrsig.key_tag); - r = dns_transaction_request_dnssec_rr(t, dnskey); - if (r < 0) - return r; - break; - } - - case DNS_TYPE_DNSKEY: { - /* For each DNSKEY we request the matching DS */ - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; - - /* If the DNSKEY we are looking at is not for - * zone we are interested in, nor any of its - * parents, we aren't interested, and don't - * request it. After all, we don't want to end - * up in request loops, and want to keep - * additional traffic down. */ - - r = dns_name_endswith(dns_resource_key_name(t->key), dns_resource_key_name(rr->key)); - if (r < 0) - return r; - if (r == 0) - continue; - - ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, dns_resource_key_name(rr->key)); - if (!ds) - return -ENOMEM; - - log_debug("Requesting DS to validate transaction %" PRIu16" (%s, DNSKEY with key tag: %" PRIu16 ").", - t->id, dns_resource_key_name(rr->key), dnssec_keytag(rr, false)); - r = dns_transaction_request_dnssec_rr(t, ds); - if (r < 0) - return r; - - break; - } - - case DNS_TYPE_SOA: - case DNS_TYPE_NS: { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; - - /* For an unsigned SOA or NS, try to acquire - * the matching DS RR, as we are at a zone cut - * then, and whether a DS exists tells us - * whether the zone is signed. Do so only if - * this RR matches our original question, - * however. */ - - r = dns_resource_key_match_rr(t->key, rr, NULL); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dnssec_has_rrsig(t->answer, rr->key); - if (r < 0) - return r; - if (r > 0) - continue; - - ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, dns_resource_key_name(rr->key)); - if (!ds) - return -ENOMEM; - - log_debug("Requesting DS to validate transaction %" PRIu16 " (%s, unsigned SOA/NS RRset).", - t->id, dns_resource_key_name(rr->key)); - r = dns_transaction_request_dnssec_rr(t, ds); - if (r < 0) - return r; - - break; - } - - case DNS_TYPE_DS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; - const char *name; - - /* CNAMEs and DNAMEs cannot be located at a - * zone apex, hence ask for the parent SOA for - * unsigned CNAME/DNAME RRs, maybe that's the - * apex. But do all that only if this is - * actually a response to our original - * question. - * - * Similar for DS RRs, which are signed when - * the parent SOA is signed. */ - - r = dns_transaction_is_primary_response(t, rr); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dnssec_has_rrsig(t->answer, rr->key); - if (r < 0) - return r; - if (r > 0) - continue; - - r = dns_answer_has_dname_for_cname(t->answer, rr); - if (r < 0) - return r; - if (r > 0) - continue; - - name = dns_resource_key_name(rr->key); - r = dns_name_parent(&name); - if (r < 0) - return r; - if (r == 0) - continue; - - soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, name); - if (!soa) - return -ENOMEM; - - log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned CNAME/DNAME/DS RRset).", - t->id, dns_resource_key_name(rr->key)); - r = dns_transaction_request_dnssec_rr(t, soa); - if (r < 0) - return r; - - break; - } - - default: { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; - - /* For other unsigned RRsets (including - * NSEC/NSEC3!), look for proof the zone is - * unsigned, by requesting the SOA RR of the - * zone. However, do so only if they are - * directly relevant to our original - * question. */ - - r = dns_transaction_is_primary_response(t, rr); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dnssec_has_rrsig(t->answer, rr->key); - if (r < 0) - return r; - if (r > 0) - continue; - - soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, dns_resource_key_name(rr->key)); - if (!soa) - return -ENOMEM; - - log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned non-SOA/NS RRset <%s>).", - t->id, dns_resource_key_name(rr->key), dns_resource_record_to_string(rr)); - r = dns_transaction_request_dnssec_rr(t, soa); - if (r < 0) - return r; - break; - }} - } - - /* Above, we requested everything necessary to validate what - * we got. Now, let's request what we need to validate what we - * didn't get... */ - - r = dns_transaction_has_unsigned_negative_answer(t); - if (r < 0) - return r; - if (r > 0) { - const char *name; - uint16_t type = 0; - - name = dns_resource_key_name(t->key); - - /* If this was a SOA or NS request, then check if there's a DS RR for the same domain. Note that this - * could also be used as indication that we are not at a zone apex, but in real world setups there are - * too many broken DNS servers (Hello, incapdns.net!) where non-terminal zones return NXDOMAIN even - * though they have further children. If this was a DS request, then it's signed when the parent zone - * is signed, hence ask the parent SOA in that case. If this was any other RR then ask for the SOA RR, - * to see if that is signed. */ - - if (t->key->type == DNS_TYPE_DS) { - r = dns_name_parent(&name); - if (r > 0) { - type = DNS_TYPE_SOA; - log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned empty DS response).", - t->id, dns_resource_key_name(t->key)); - } else - name = NULL; - - } else if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS)) { - - type = DNS_TYPE_DS; - log_debug("Requesting DS to validate transaction %" PRIu16 " (%s, unsigned empty SOA/NS response).", - t->id, dns_resource_key_name(t->key)); - - } else { - type = DNS_TYPE_SOA; - log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned empty non-SOA/NS/DS response).", - t->id, dns_resource_key_name(t->key)); - } - - if (name) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; - - soa = dns_resource_key_new(t->key->class, type, name); - if (!soa) - return -ENOMEM; - - r = dns_transaction_request_dnssec_rr(t, soa); - if (r < 0) - return r; - } - } - - return dns_transaction_dnssec_is_live(t); -} - -void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source) { - assert(t); - assert(source); - - /* Invoked whenever any of our auxiliary DNSSEC transactions completed its work. If the state is still PENDING, - we are still in the loop that adds further DNSSEC transactions, hence don't check if we are ready yet. If - the state is VALIDATING however, we should check if we are complete now. */ - - if (t->state == DNS_TRANSACTION_VALIDATING) - dns_transaction_process_dnssec(t); -} - -static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) { - DnsResourceRecord *rr; - int ifindex, r; - - assert(t); - - /* Add all DNSKEY RRs from the answer that are validated by DS - * RRs from the list of validated keys to the list of - * validated keys. */ - - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, t->answer) { - - r = dnssec_verify_dnskey_by_ds_search(rr, t->validated_keys); - if (r < 0) - return r; - if (r == 0) - continue; - - /* If so, the DNSKEY is validated too. */ - r = dns_answer_add_extend(&t->validated_keys, rr, ifindex, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - return 0; -} - -static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *rr) { - int r; - - assert(t); - assert(rr); - - /* Checks if the RR we are looking for must be signed with an - * RRSIG. This is used for positive responses. */ - - if (t->scope->dnssec_mode == DNSSEC_NO) - return false; - - if (dns_type_is_pseudo(rr->key->type)) - return -EINVAL; - - r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(rr->key)); - if (r < 0) - return r; - if (r > 0) - return false; - - switch (rr->key->type) { - - case DNS_TYPE_RRSIG: - /* RRSIGs are the signatures themselves, they need no signing. */ - return false; - - case DNS_TYPE_SOA: - case DNS_TYPE_NS: { - DnsTransaction *dt; - Iterator i; - - /* For SOA or NS RRs we look for a matching DS transaction */ - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (dt->key->class != rr->key->class) - continue; - if (dt->key->type != DNS_TYPE_DS) - continue; - - r = dns_name_equal(dns_resource_key_name(dt->key), dns_resource_key_name(rr->key)); - if (r < 0) - return r; - if (r == 0) - continue; - - /* We found a DS transactions for the SOA/NS - * RRs we are looking at. If it discovered signed DS - * RRs, then we need to be signed, too. */ - - if (!dt->answer_authenticated) - return false; - - return dns_answer_match_key(dt->answer, dt->key, NULL); - } - - /* We found nothing that proves this is safe to leave - * this unauthenticated, hence ask inist on - * authentication. */ - return true; - } - - case DNS_TYPE_DS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: { - const char *parent = NULL; - DnsTransaction *dt; - Iterator i; - - /* - * CNAME/DNAME RRs cannot be located at a zone apex, hence look directly for the parent SOA. - * - * DS RRs are signed if the parent is signed, hence also look at the parent SOA - */ - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (dt->key->class != rr->key->class) - continue; - if (dt->key->type != DNS_TYPE_SOA) - continue; - - if (!parent) { - parent = dns_resource_key_name(rr->key); - r = dns_name_parent(&parent); - if (r < 0) - return r; - if (r == 0) { - if (rr->key->type == DNS_TYPE_DS) - return true; - - /* A CNAME/DNAME without a parent? That's sooo weird. */ - log_debug("Transaction %" PRIu16 " claims CNAME/DNAME at root. Refusing.", t->id); - return -EBADMSG; - } - } - - r = dns_name_equal(dns_resource_key_name(dt->key), parent); - if (r < 0) - return r; - if (r == 0) - continue; - - return t->answer_authenticated; - } - - return true; - } - - default: { - DnsTransaction *dt; - Iterator i; - - /* Any other kind of RR (including DNSKEY/NSEC/NSEC3). Let's see if our SOA lookup was authenticated */ - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (dt->key->class != rr->key->class) - continue; - if (dt->key->type != DNS_TYPE_SOA) - continue; - - r = dns_name_equal(dns_resource_key_name(dt->key), dns_resource_key_name(rr->key)); - if (r < 0) - return r; - if (r == 0) - continue; - - /* We found the transaction that was supposed to find - * the SOA RR for us. It was successful, but found no - * RR for us. This means we are not at a zone cut. In - * this case, we require authentication if the SOA - * lookup was authenticated too. */ - return t->answer_authenticated; - } - - return true; - }} -} - -static int dns_transaction_in_private_tld(DnsTransaction *t, const DnsResourceKey *key) { - DnsTransaction *dt; - const char *tld; - Iterator i; - int r; - - /* If DNSSEC downgrade mode is on, checks whether the - * specified RR is one level below a TLD we have proven not to - * exist. In such a case we assume that this is a private - * domain, and permit it. - * - * This detects cases like the Fritz!Box router networks. Each - * Fritz!Box router serves a private "fritz.box" zone, in the - * non-existing TLD "box". Requests for the "fritz.box" domain - * are served by the router itself, while requests for the - * "box" domain will result in NXDOMAIN. - * - * Note that this logic is unable to detect cases where a - * router serves a private DNS zone directly under - * non-existing TLD. In such a case we cannot detect whether - * the TLD is supposed to exist or not, as all requests we - * make for it will be answered by the router's zone, and not - * by the root zone. */ - - assert(t); - - if (t->scope->dnssec_mode != DNSSEC_ALLOW_DOWNGRADE) - return false; /* In strict DNSSEC mode what doesn't exist, doesn't exist */ - - tld = dns_resource_key_name(key); - r = dns_name_parent(&tld); - if (r < 0) - return r; - if (r == 0) - return false; /* Already the root domain */ - - if (!dns_name_is_single_label(tld)) - return false; - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (dt->key->class != key->class) - continue; - - r = dns_name_equal(dns_resource_key_name(dt->key), tld); - if (r < 0) - return r; - if (r == 0) - continue; - - /* We found an auxiliary lookup we did for the TLD. If - * that returned with NXDOMAIN, we know the TLD didn't - * exist, and hence this might be a private zone. */ - - return dt->answer_rcode == DNS_RCODE_NXDOMAIN; - } - - return false; -} - -static int dns_transaction_requires_nsec(DnsTransaction *t) { - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - DnsTransaction *dt; - const char *name; - uint16_t type = 0; - Iterator i; - int r; - - assert(t); - - /* Checks if we need to insist on NSEC/NSEC3 RRs for proving - * this negative reply */ - - if (t->scope->dnssec_mode == DNSSEC_NO) - return false; - - if (dns_type_is_pseudo(t->key->type)) - return -EINVAL; - - r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(t->key)); - if (r < 0) - return r; - if (r > 0) - return false; - - r = dns_transaction_in_private_tld(t, t->key); - if (r < 0) - return r; - if (r > 0) { - /* The lookup is from a TLD that is proven not to - * exist, and we are in downgrade mode, hence ignore - * that fact that we didn't get any NSEC RRs.*/ - - log_info("Detected a negative query %s in a private DNS zone, permitting unsigned response.", - dns_resource_key_to_string(t->key, key_str, sizeof key_str)); - return false; - } - - name = dns_resource_key_name(t->key); - - if (t->key->type == DNS_TYPE_DS) { - - /* We got a negative reply for this DS lookup? DS RRs are signed when their parent zone is signed, - * hence check the parent SOA in this case. */ - - r = dns_name_parent(&name); - if (r < 0) - return r; - if (r == 0) - return true; - - type = DNS_TYPE_SOA; - - } else if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS)) - /* We got a negative reply for this SOA/NS lookup? If so, check if there's a DS RR for this */ - type = DNS_TYPE_DS; - else - /* For all other negative replies, check for the SOA lookup */ - type = DNS_TYPE_SOA; - - /* For all other RRs we check the SOA on the same level to see - * if it's signed. */ - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (dt->key->class != t->key->class) - continue; - if (dt->key->type != type) - continue; - - r = dns_name_equal(dns_resource_key_name(dt->key), name); - if (r < 0) - return r; - if (r == 0) - continue; - - return dt->answer_authenticated; - } - - /* If in doubt, require NSEC/NSEC3 */ - return true; -} - -static int dns_transaction_dnskey_authenticated(DnsTransaction *t, DnsResourceRecord *rr) { - DnsResourceRecord *rrsig; - bool found = false; - int r; - - /* Checks whether any of the DNSKEYs used for the RRSIGs for - * the specified RRset is authenticated (i.e. has a matching - * DS RR). */ - - r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(rr->key)); - if (r < 0) - return r; - if (r > 0) - return false; - - DNS_ANSWER_FOREACH(rrsig, t->answer) { - DnsTransaction *dt; - Iterator i; - - r = dnssec_key_match_rrsig(rr->key, rrsig); - if (r < 0) - return r; - if (r == 0) - continue; - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (dt->key->class != rr->key->class) - continue; - - if (dt->key->type == DNS_TYPE_DNSKEY) { - - r = dns_name_equal(dns_resource_key_name(dt->key), rrsig->rrsig.signer); - if (r < 0) - return r; - if (r == 0) - continue; - - /* OK, we found an auxiliary DNSKEY - * lookup. If that lookup is - * authenticated, report this. */ - - if (dt->answer_authenticated) - return true; - - found = true; - - } else if (dt->key->type == DNS_TYPE_DS) { - - r = dns_name_equal(dns_resource_key_name(dt->key), rrsig->rrsig.signer); - if (r < 0) - return r; - if (r == 0) - continue; - - /* OK, we found an auxiliary DS - * lookup. If that lookup is - * authenticated and non-zero, we - * won! */ - - if (!dt->answer_authenticated) - return false; - - return dns_answer_match_key(dt->answer, dt->key, NULL); - } - } - } - - return found ? false : -ENXIO; -} - -static int dns_transaction_known_signed(DnsTransaction *t, DnsResourceRecord *rr) { - assert(t); - assert(rr); - - /* We know that the root domain is signed, hence if it appears - * not to be signed, there's a problem with the DNS server */ - - return rr->key->class == DNS_CLASS_IN && - dns_name_is_root(dns_resource_key_name(rr->key)); -} - -static int dns_transaction_check_revoked_trust_anchors(DnsTransaction *t) { - DnsResourceRecord *rr; - int r; - - assert(t); - - /* Maybe warn the user that we encountered a revoked DNSKEY - * for a key from our trust anchor. Note that we don't care - * whether the DNSKEY can be authenticated or not. It's - * sufficient if it is self-signed. */ - - DNS_ANSWER_FOREACH(rr, t->answer) { - r = dns_trust_anchor_check_revoked(&t->scope->manager->trust_anchor, rr, t->answer); - if (r < 0) - return r; - } - - return 0; -} - -static int dns_transaction_invalidate_revoked_keys(DnsTransaction *t) { - bool changed; - int r; - - assert(t); - - /* Removes all DNSKEY/DS objects from t->validated_keys that - * our trust anchors database considers revoked. */ - - do { - DnsResourceRecord *rr; - - changed = false; - - DNS_ANSWER_FOREACH(rr, t->validated_keys) { - r = dns_trust_anchor_is_revoked(&t->scope->manager->trust_anchor, rr); - if (r < 0) - return r; - if (r > 0) { - r = dns_answer_remove_by_rr(&t->validated_keys, rr); - if (r < 0) - return r; - - assert(r > 0); - changed = true; - break; - } - } - } while (changed); - - return 0; -} - -static int dns_transaction_copy_validated(DnsTransaction *t) { - DnsTransaction *dt; - Iterator i; - int r; - - assert(t); - - /* Copy all validated RRs from the auxiliary DNSSEC transactions into our set of validated RRs */ - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (DNS_TRANSACTION_IS_LIVE(dt->state)) - continue; - - if (!dt->answer_authenticated) - continue; - - r = dns_answer_extend(&t->validated_keys, dt->answer); - if (r < 0) - return r; - } - - return 0; -} - -typedef enum { - DNSSEC_PHASE_DNSKEY, /* Phase #1, only validate DNSKEYs */ - DNSSEC_PHASE_NSEC, /* Phase #2, only validate NSEC+NSEC3 */ - DNSSEC_PHASE_ALL, /* Phase #3, validate everything else */ -} Phase; - -static int dnssec_validate_records( - DnsTransaction *t, - Phase phase, - bool *have_nsec, - DnsAnswer **validated) { - - DnsResourceRecord *rr; - int r; - - /* Returns negative on error, 0 if validation failed, 1 to restart validation, 2 when finished. */ - - DNS_ANSWER_FOREACH(rr, t->answer) { - DnsResourceRecord *rrsig = NULL; - DnssecResult result; - - switch (rr->key->type) { - case DNS_TYPE_RRSIG: - continue; - - case DNS_TYPE_DNSKEY: - /* We validate DNSKEYs only in the DNSKEY and ALL phases */ - if (phase == DNSSEC_PHASE_NSEC) - continue; - break; - - case DNS_TYPE_NSEC: - case DNS_TYPE_NSEC3: - *have_nsec = true; - - /* We validate NSEC/NSEC3 only in the NSEC and ALL phases */ - if (phase == DNSSEC_PHASE_DNSKEY) - continue; - break; - - default: - /* We validate all other RRs only in the ALL phases */ - if (phase != DNSSEC_PHASE_ALL) - continue; - } - - r = dnssec_verify_rrset_search(t->answer, rr->key, t->validated_keys, USEC_INFINITY, &result, &rrsig); - if (r < 0) - return r; - - log_debug("Looking at %s: %s", strna(dns_resource_record_to_string(rr)), dnssec_result_to_string(result)); - - if (result == DNSSEC_VALIDATED) { - - if (rr->key->type == DNS_TYPE_DNSKEY) { - /* If we just validated a DNSKEY RRset, then let's add these keys to - * the set of validated keys for this transaction. */ - - r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - /* Some of the DNSKEYs we just added might already have been revoked, - * remove them again in that case. */ - r = dns_transaction_invalidate_revoked_keys(t); - if (r < 0) - return r; - } - - /* Add the validated RRset to the new list of validated - * RRsets, and remove it from the unvalidated RRsets. - * We mark the RRset as authenticated and cacheable. */ - r = dns_answer_move_by_key(validated, &t->answer, rr->key, DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE); - if (r < 0) - return r; - - manager_dnssec_verdict(t->scope->manager, DNSSEC_SECURE, rr->key); - - /* Exit the loop, we dropped something from the answer, start from the beginning */ - return 1; - } - - /* If we haven't read all DNSKEYs yet a negative result of the validation is irrelevant, as - * there might be more DNSKEYs coming. Similar, if we haven't read all NSEC/NSEC3 RRs yet, - * we cannot do positive wildcard proofs yet, as those require the NSEC/NSEC3 RRs. */ - if (phase != DNSSEC_PHASE_ALL) - continue; - - if (result == DNSSEC_VALIDATED_WILDCARD) { - bool authenticated = false; - const char *source; - - /* This RRset validated, but as a wildcard. This means we need - * to prove via NSEC/NSEC3 that no matching non-wildcard RR exists.*/ - - /* First step, determine the source of synthesis */ - r = dns_resource_record_source(rrsig, &source); - if (r < 0) - return r; - - r = dnssec_test_positive_wildcard(*validated, - dns_resource_key_name(rr->key), - source, - rrsig->rrsig.signer, - &authenticated); - - /* Unless the NSEC proof showed that the key really doesn't exist something is off. */ - if (r == 0) - result = DNSSEC_INVALID; - else { - r = dns_answer_move_by_key(validated, &t->answer, rr->key, - authenticated ? (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE) : 0); - if (r < 0) - return r; - - manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, rr->key); - - /* Exit the loop, we dropped something from the answer, start from the beginning */ - return 1; - } - } - - if (result == DNSSEC_NO_SIGNATURE) { - r = dns_transaction_requires_rrsig(t, rr); - if (r < 0) - return r; - if (r == 0) { - /* Data does not require signing. In that case, just copy it over, - * but remember that this is by no means authenticated.*/ - r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0); - if (r < 0) - return r; - - manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); - return 1; - } - - r = dns_transaction_known_signed(t, rr); - if (r < 0) - return r; - if (r > 0) { - /* This is an RR we know has to be signed. If it isn't this means - * the server is not attaching RRSIGs, hence complain. */ - - dns_server_packet_rrsig_missing(t->server, t->current_feature_level); - - if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) { - - /* Downgrading is OK? If so, just consider the information unsigned */ - - r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0); - if (r < 0) - return r; - - manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); - return 1; - } - - /* Otherwise, fail */ - t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER; - return 0; - } - - r = dns_transaction_in_private_tld(t, rr->key); - if (r < 0) - return r; - if (r > 0) { - char s[DNS_RESOURCE_KEY_STRING_MAX]; - - /* The data is from a TLD that is proven not to exist, and we are in downgrade - * mode, hence ignore the fact that this was not signed. */ - - log_info("Detected RRset %s is in a private DNS zone, permitting unsigned RRs.", - dns_resource_key_to_string(rr->key, s, sizeof s)); - - r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0); - if (r < 0) - return r; - - manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); - return 1; - } - } - - if (IN_SET(result, - DNSSEC_MISSING_KEY, - DNSSEC_SIGNATURE_EXPIRED, - DNSSEC_UNSUPPORTED_ALGORITHM)) { - - r = dns_transaction_dnskey_authenticated(t, rr); - if (r < 0 && r != -ENXIO) - return r; - if (r == 0) { - /* The DNSKEY transaction was not authenticated, this means there's - * no DS for this, which means it's OK if no keys are found for this signature. */ - - r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0); - if (r < 0) - return r; - - manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); - return 1; - } - } - - r = dns_transaction_is_primary_response(t, rr); - if (r < 0) - return r; - if (r > 0) { - /* Look for a matching DNAME for this CNAME */ - r = dns_answer_has_dname_for_cname(t->answer, rr); - if (r < 0) - return r; - if (r == 0) { - /* Also look among the stuff we already validated */ - r = dns_answer_has_dname_for_cname(*validated, rr); - if (r < 0) - return r; - } - - if (r == 0) { - if (IN_SET(result, - DNSSEC_INVALID, - DNSSEC_SIGNATURE_EXPIRED, - DNSSEC_NO_SIGNATURE)) - manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, rr->key); - else /* DNSSEC_MISSING_KEY or DNSSEC_UNSUPPORTED_ALGORITHM */ - manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, rr->key); - - /* This is a primary response to our question, and it failed validation. - * That's fatal. */ - t->answer_dnssec_result = result; - return 0; - } - - /* This is a primary response, but we do have a DNAME RR - * in the RR that can replay this CNAME, hence rely on - * that, and we can remove the CNAME in favour of it. */ - } - - /* This is just some auxiliary data. Just remove the RRset and continue. */ - r = dns_answer_remove_by_key(&t->answer, rr->key); - if (r < 0) - return r; - - /* We dropped something from the answer, start from the beginning. */ - return 1; - } - - return 2; /* Finito. */ -} - -int dns_transaction_validate_dnssec(DnsTransaction *t) { - _cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL; - Phase phase; - DnsAnswerFlags flags; - int r; - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - - assert(t); - - /* We have now collected all DS and DNSKEY RRs in - * t->validated_keys, let's see which RRs we can now - * authenticate with that. */ - - if (t->scope->dnssec_mode == DNSSEC_NO) - return 0; - - /* Already validated */ - if (t->answer_dnssec_result != _DNSSEC_RESULT_INVALID) - return 0; - - /* Our own stuff needs no validation */ - if (IN_SET(t->answer_source, DNS_TRANSACTION_ZONE, DNS_TRANSACTION_TRUST_ANCHOR)) { - t->answer_dnssec_result = DNSSEC_VALIDATED; - t->answer_authenticated = true; - return 0; - } - - /* Cached stuff is not affected by validation. */ - if (t->answer_source != DNS_TRANSACTION_NETWORK) - return 0; - - if (!dns_transaction_dnssec_supported_full(t)) { - /* The server does not support DNSSEC, or doesn't augment responses with RRSIGs. */ - t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER; - log_debug("Not validating response for %" PRIu16 ", server lacks DNSSEC support.", t->id); - return 0; - } - - log_debug("Validating response from transaction %" PRIu16 " (%s).", - t->id, - dns_resource_key_to_string(t->key, key_str, sizeof key_str)); - - /* First, see if this response contains any revoked trust - * anchors we care about */ - r = dns_transaction_check_revoked_trust_anchors(t); - if (r < 0) - return r; - - /* Third, copy all RRs we acquired successfully from auxiliary RRs over. */ - r = dns_transaction_copy_validated(t); - if (r < 0) - return r; - - /* Second, see if there are DNSKEYs we already know a - * validated DS for. */ - r = dns_transaction_validate_dnskey_by_ds(t); - if (r < 0) - return r; - - /* Fourth, remove all DNSKEY and DS RRs again that our trust - * anchor says are revoked. After all we might have marked - * some keys revoked above, but they might still be lingering - * in our validated_keys list. */ - r = dns_transaction_invalidate_revoked_keys(t); - if (r < 0) - return r; - - phase = DNSSEC_PHASE_DNSKEY; - for (;;) { - bool have_nsec = false; - - r = dnssec_validate_records(t, phase, &have_nsec, &validated); - if (r <= 0) - return r; - - /* Try again as long as we managed to achieve something */ - if (r == 1) - continue; - - if (phase == DNSSEC_PHASE_DNSKEY && have_nsec) { - /* OK, we processed all DNSKEYs, and there are NSEC/NSEC3 RRs, look at those now. */ - phase = DNSSEC_PHASE_NSEC; - continue; - } - - if (phase != DNSSEC_PHASE_ALL) { - /* OK, we processed all DNSKEYs and NSEC/NSEC3 RRs, look at all the rest now. - * Note that in this third phase we start to remove RRs we couldn't validate. */ - phase = DNSSEC_PHASE_ALL; - continue; - } - - /* We're done */ - break; - } - - dns_answer_unref(t->answer); - t->answer = validated; - validated = NULL; - - /* At this point the answer only contains validated - * RRsets. Now, let's see if it actually answers the question - * we asked. If so, great! If it doesn't, then see if - * NSEC/NSEC3 can prove this. */ - r = dns_transaction_has_positive_answer(t, &flags); - if (r > 0) { - /* Yes, it answers the question! */ - - if (flags & DNS_ANSWER_AUTHENTICATED) { - /* The answer is fully authenticated, yay. */ - t->answer_dnssec_result = DNSSEC_VALIDATED; - t->answer_rcode = DNS_RCODE_SUCCESS; - t->answer_authenticated = true; - } else { - /* The answer is not fully authenticated. */ - t->answer_dnssec_result = DNSSEC_UNSIGNED; - t->answer_authenticated = false; - } - - } else if (r == 0) { - DnssecNsecResult nr; - bool authenticated = false; - - /* Bummer! Let's check NSEC/NSEC3 */ - r = dnssec_nsec_test(t->answer, t->key, &nr, &authenticated, &t->answer_nsec_ttl); - if (r < 0) - return r; - - switch (nr) { - - case DNSSEC_NSEC_NXDOMAIN: - /* NSEC proves the domain doesn't exist. Very good. */ - log_debug("Proved NXDOMAIN via NSEC/NSEC3 for transaction %u (%s)", t->id, key_str); - t->answer_dnssec_result = DNSSEC_VALIDATED; - t->answer_rcode = DNS_RCODE_NXDOMAIN; - t->answer_authenticated = authenticated; - - manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, t->key); - break; - - case DNSSEC_NSEC_NODATA: - /* NSEC proves that there's no data here, very good. */ - log_debug("Proved NODATA via NSEC/NSEC3 for transaction %u (%s)", t->id, key_str); - t->answer_dnssec_result = DNSSEC_VALIDATED; - t->answer_rcode = DNS_RCODE_SUCCESS; - t->answer_authenticated = authenticated; - - manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, t->key); - break; - - case DNSSEC_NSEC_OPTOUT: - /* NSEC3 says the data might not be signed */ - log_debug("Data is NSEC3 opt-out via NSEC/NSEC3 for transaction %u (%s)", t->id, key_str); - t->answer_dnssec_result = DNSSEC_UNSIGNED; - t->answer_authenticated = false; - - manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, t->key); - break; - - case DNSSEC_NSEC_NO_RR: - /* No NSEC data? Bummer! */ - - r = dns_transaction_requires_nsec(t); - if (r < 0) - return r; - if (r > 0) { - t->answer_dnssec_result = DNSSEC_NO_SIGNATURE; - manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, t->key); - } else { - t->answer_dnssec_result = DNSSEC_UNSIGNED; - t->answer_authenticated = false; - manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, t->key); - } - - break; - - case DNSSEC_NSEC_UNSUPPORTED_ALGORITHM: - /* We don't know the NSEC3 algorithm used? */ - t->answer_dnssec_result = DNSSEC_UNSUPPORTED_ALGORITHM; - manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, t->key); - break; - - case DNSSEC_NSEC_FOUND: - case DNSSEC_NSEC_CNAME: - /* NSEC says it needs to be there, but we couldn't find it? Bummer! */ - t->answer_dnssec_result = DNSSEC_NSEC_MISMATCH; - manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, t->key); - break; - - default: - assert_not_reached("Unexpected NSEC result."); - } - } - - return 1; -} - -static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] = { - [DNS_TRANSACTION_NULL] = "null", - [DNS_TRANSACTION_PENDING] = "pending", - [DNS_TRANSACTION_VALIDATING] = "validating", - [DNS_TRANSACTION_RCODE_FAILURE] = "rcode-failure", - [DNS_TRANSACTION_SUCCESS] = "success", - [DNS_TRANSACTION_NO_SERVERS] = "no-servers", - [DNS_TRANSACTION_TIMEOUT] = "timeout", - [DNS_TRANSACTION_ATTEMPTS_MAX_REACHED] = "attempts-max-reached", - [DNS_TRANSACTION_INVALID_REPLY] = "invalid-reply", - [DNS_TRANSACTION_ERRNO] = "errno", - [DNS_TRANSACTION_ABORTED] = "aborted", - [DNS_TRANSACTION_DNSSEC_FAILED] = "dnssec-failed", - [DNS_TRANSACTION_NO_TRUST_ANCHOR] = "no-trust-anchor", - [DNS_TRANSACTION_RR_TYPE_UNSUPPORTED] = "rr-type-unsupported", - [DNS_TRANSACTION_NETWORK_DOWN] = "network-down", - [DNS_TRANSACTION_NOT_FOUND] = "not-found", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_transaction_state, DnsTransactionState); - -static const char* const dns_transaction_source_table[_DNS_TRANSACTION_SOURCE_MAX] = { - [DNS_TRANSACTION_NETWORK] = "network", - [DNS_TRANSACTION_CACHE] = "cache", - [DNS_TRANSACTION_ZONE] = "zone", - [DNS_TRANSACTION_TRUST_ANCHOR] = "trust-anchor", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_transaction_source, DnsTransactionSource); diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h deleted file mode 100644 index eaece91533..0000000000 --- a/src/resolve/resolved-dns-transaction.h +++ /dev/null @@ -1,174 +0,0 @@ -#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 . -***/ - -typedef struct DnsTransaction DnsTransaction; -typedef enum DnsTransactionState DnsTransactionState; -typedef enum DnsTransactionSource DnsTransactionSource; - -enum DnsTransactionState { - DNS_TRANSACTION_NULL, - DNS_TRANSACTION_PENDING, - DNS_TRANSACTION_VALIDATING, - DNS_TRANSACTION_RCODE_FAILURE, - DNS_TRANSACTION_SUCCESS, - DNS_TRANSACTION_NO_SERVERS, - DNS_TRANSACTION_TIMEOUT, - DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, - DNS_TRANSACTION_INVALID_REPLY, - DNS_TRANSACTION_ERRNO, - DNS_TRANSACTION_ABORTED, - DNS_TRANSACTION_DNSSEC_FAILED, - DNS_TRANSACTION_NO_TRUST_ANCHOR, - DNS_TRANSACTION_RR_TYPE_UNSUPPORTED, - DNS_TRANSACTION_NETWORK_DOWN, - DNS_TRANSACTION_NOT_FOUND, /* like NXDOMAIN, but when LLMNR/TCP connections fail */ - _DNS_TRANSACTION_STATE_MAX, - _DNS_TRANSACTION_STATE_INVALID = -1 -}; - -#define DNS_TRANSACTION_IS_LIVE(state) IN_SET((state), DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING) - -enum DnsTransactionSource { - DNS_TRANSACTION_NETWORK, - DNS_TRANSACTION_CACHE, - DNS_TRANSACTION_ZONE, - DNS_TRANSACTION_TRUST_ANCHOR, - _DNS_TRANSACTION_SOURCE_MAX, - _DNS_TRANSACTION_SOURCE_INVALID = -1 -}; - -#include "resolved-dns-answer.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-question.h" -#include "resolved-dns-scope.h" - -struct DnsTransaction { - DnsScope *scope; - - DnsResourceKey *key; - - DnsTransactionState state; - - uint16_t id; - - bool tried_stream:1; - - bool initial_jitter_scheduled:1; - bool initial_jitter_elapsed:1; - - DnsPacket *sent, *received; - - DnsAnswer *answer; - int answer_rcode; - DnssecResult answer_dnssec_result; - DnsTransactionSource answer_source; - uint32_t answer_nsec_ttl; - int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */ - - /* Indicates whether the primary answer is authenticated, - * i.e. whether the RRs from answer which directly match the - * question are authenticated, or, if there are none, whether - * the NODATA or NXDOMAIN case is. It says nothing about - * additional RRs listed in the answer, however they have - * their own DNS_ANSWER_AUTHORIZED FLAGS. Note that this bit - * is defined different than the AD bit in DNS packets, as - * that covers more than just the actual primary answer. */ - bool answer_authenticated; - - /* Contains DNSKEY, DS, SOA RRs we already verified and need - * to authenticate this reply */ - DnsAnswer *validated_keys; - - usec_t start_usec; - usec_t next_attempt_after; - sd_event_source *timeout_event_source; - unsigned n_attempts; - - /* UDP connection logic, if we need it */ - int dns_udp_fd; - sd_event_source *dns_udp_event_source; - - /* TCP connection logic, if we need it */ - DnsStream *stream; - - /* The active server */ - DnsServer *server; - - /* The features of the DNS server at time of transaction start */ - DnsServerFeatureLevel current_feature_level; - - /* Query candidates this transaction is referenced by and that - * shall be notified about this specific transaction - * completing. */ - Set *notify_query_candidates, *notify_query_candidates_done; - - /* Zone items this transaction is referenced by and that shall - * be notified about completion. */ - Set *notify_zone_items, *notify_zone_items_done; - - /* Other transactions that this transactions is referenced by - * and that shall be notified about completion. This is used - * when transactions want to validate their RRsets, but need - * another DNSKEY or DS RR to do so. */ - Set *notify_transactions, *notify_transactions_done; - - /* The opposite direction: the transactions this transaction - * created in order to request DNSKEY or DS RRs. */ - Set *dnssec_transactions; - - unsigned block_gc; - - LIST_FIELDS(DnsTransaction, transactions_by_scope); -}; - -int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key); -DnsTransaction* dns_transaction_free(DnsTransaction *t); - -bool dns_transaction_gc(DnsTransaction *t); -int dns_transaction_go(DnsTransaction *t); - -void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p); -void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state); - -void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source); -int dns_transaction_validate_dnssec(DnsTransaction *t); -int dns_transaction_request_dnssec_keys(DnsTransaction *t); - -const char* dns_transaction_state_to_string(DnsTransactionState p) _const_; -DnsTransactionState dns_transaction_state_from_string(const char *s) _pure_; - -const char* dns_transaction_source_to_string(DnsTransactionSource p) _const_; -DnsTransactionSource dns_transaction_source_from_string(const char *s) _pure_; - -/* LLMNR Jitter interval, see RFC 4795 Section 7 */ -#define LLMNR_JITTER_INTERVAL_USEC (100 * USEC_PER_MSEC) - -/* mDNS Jitter interval, see RFC 6762 Section 5.2 */ -#define MDNS_JITTER_MIN_USEC (20 * USEC_PER_MSEC) -#define MDNS_JITTER_RANGE_USEC (100 * USEC_PER_MSEC) - -/* Maximum attempts to send DNS requests, across all DNS servers */ -#define DNS_TRANSACTION_ATTEMPTS_MAX 16 - -/* Maximum attempts to send LLMNR requests, see RFC 4795 Section 2.7 */ -#define LLMNR_TRANSACTION_ATTEMPTS_MAX 3 - -#define TRANSACTION_ATTEMPTS_MAX(p) ((p) == DNS_PROTOCOL_LLMNR ? LLMNR_TRANSACTION_ATTEMPTS_MAX : DNS_TRANSACTION_ATTEMPTS_MAX) diff --git a/src/resolve/resolved-dns-trust-anchor.c b/src/resolve/resolved-dns-trust-anchor.c deleted file mode 100644 index 77370e7dd5..0000000000 --- a/src/resolve/resolved-dns-trust-anchor.c +++ /dev/null @@ -1,743 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 "alloc-util.h" -#include "conf-files.h" -#include "def.h" -#include "dns-domain.h" -#include "fd-util.h" -#include "fileio.h" -#include "hexdecoct.h" -#include "parse-util.h" -#include "resolved-dns-trust-anchor.h" -#include "resolved-dns-dnssec.h" -#include "set.h" -#include "string-util.h" -#include "strv.h" - -static const char trust_anchor_dirs[] = CONF_PATHS_NULSTR("dnssec-trust-anchors.d"); - -/* The DS RR from https://data.iana.org/root-anchors/root-anchors.xml, retrieved December 2015 */ -static const uint8_t root_digest[] = - { 0x49, 0xAA, 0xC1, 0x1D, 0x7B, 0x6F, 0x64, 0x46, 0x70, 0x2E, 0x54, 0xA1, 0x60, 0x73, 0x71, 0x60, - 0x7A, 0x1A, 0x41, 0x85, 0x52, 0x00, 0xFD, 0x2C, 0xE1, 0xCD, 0xDE, 0x32, 0xF2, 0x4E, 0x8F, 0xB5 }; - -static bool dns_trust_anchor_knows_domain_positive(DnsTrustAnchor *d, const char *name) { - assert(d); - - /* Returns true if there's an entry for the specified domain - * name in our trust anchor */ - - return - hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DNSKEY, name)) || - hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, name)); -} - -static int dns_trust_anchor_add_builtin_positive(DnsTrustAnchor *d) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - int r; - - assert(d); - - r = hashmap_ensure_allocated(&d->positive_by_key, &dns_resource_key_hash_ops); - if (r < 0) - return r; - - /* Only add the built-in trust anchor if there's neither a DS - * nor a DNSKEY defined for the root domain. That way users - * have an easy way to override the root domain DS/DNSKEY - * data. */ - if (dns_trust_anchor_knows_domain_positive(d, ".")) - return 0; - - /* Add the RR from https://data.iana.org/root-anchors/root-anchors.xml */ - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, ""); - if (!rr) - return -ENOMEM; - - rr->ds.key_tag = 19036; - rr->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; - rr->ds.digest_type = DNSSEC_DIGEST_SHA256; - rr->ds.digest_size = sizeof(root_digest); - rr->ds.digest = memdup(root_digest, rr->ds.digest_size); - if (!rr->ds.digest) - return -ENOMEM; - - answer = dns_answer_new(1); - if (!answer) - return -ENOMEM; - - r = dns_answer_add(answer, rr, 0, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - r = hashmap_put(d->positive_by_key, rr->key, answer); - if (r < 0) - return r; - - answer = NULL; - return 0; -} - -static int dns_trust_anchor_add_builtin_negative(DnsTrustAnchor *d) { - - static const char private_domains[] = - /* RFC 6761 says that .test is a special domain for - * testing and not to be installed in the root zone */ - "test\0" - - /* RFC 6761 says that these reverse IP lookup ranges - * are for private addresses, and hence should not - * show up in the root zone */ - "10.in-addr.arpa\0" - "16.172.in-addr.arpa\0" - "17.172.in-addr.arpa\0" - "18.172.in-addr.arpa\0" - "19.172.in-addr.arpa\0" - "20.172.in-addr.arpa\0" - "21.172.in-addr.arpa\0" - "22.172.in-addr.arpa\0" - "23.172.in-addr.arpa\0" - "24.172.in-addr.arpa\0" - "25.172.in-addr.arpa\0" - "26.172.in-addr.arpa\0" - "27.172.in-addr.arpa\0" - "28.172.in-addr.arpa\0" - "29.172.in-addr.arpa\0" - "30.172.in-addr.arpa\0" - "31.172.in-addr.arpa\0" - "168.192.in-addr.arpa\0" - - /* RFC 6762 reserves the .local domain for Multicast - * DNS, it hence cannot appear in the root zone. (Note - * that we by default do not route .local traffic to - * DNS anyway, except when a configured search domain - * suggests so.) */ - "local\0" - - /* These two are well known, popular private zone - * TLDs, that are blocked from delegation, according - * to: - * http://icannwiki.com/Name_Collision#NGPC_Resolution - * - * There's also ongoing work on making this official - * in an RRC: - * https://www.ietf.org/archive/id/draft-chapin-additional-reserved-tlds-02.txt */ - "home\0" - "corp\0" - - /* The following four TLDs are suggested for private - * zones in RFC 6762, Appendix G, and are hence very - * unlikely to be made official TLDs any day soon */ - "lan\0" - "intranet\0" - "internal\0" - "private\0"; - - const char *name; - int r; - - assert(d); - - /* Only add the built-in trust anchor if there's no negative - * trust anchor defined at all. This enables easy overriding - * of negative trust anchors. */ - - if (set_size(d->negative_by_name) > 0) - return 0; - - r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops); - if (r < 0) - return r; - - /* We add a couple of domains as default negative trust - * anchors, where it's very unlikely they will be installed in - * the root zone. If they exist they must be private, and thus - * unsigned. */ - - NULSTR_FOREACH(name, private_domains) { - - if (dns_trust_anchor_knows_domain_positive(d, name)) - continue; - - r = set_put_strdup(d->negative_by_name, name); - if (r < 0) - return r; - } - - return 0; -} - -static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, unsigned line, const char *s) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_free_ char *domain = NULL, *class = NULL, *type = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - DnsAnswer *old_answer = NULL; - const char *p = s; - int r; - - assert(d); - assert(line); - - r = extract_first_word(&p, &domain, NULL, EXTRACT_QUOTES); - if (r < 0) - return log_warning_errno(r, "Unable to parse domain in line %s:%u: %m", path, line); - - if (!dns_name_is_valid(domain)) { - log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain, path, line); - return -EINVAL; - } - - r = extract_many_words(&p, NULL, 0, &class, &type, NULL); - if (r < 0) - return log_warning_errno(r, "Unable to parse class and type in line %s:%u: %m", path, line); - if (r != 2) { - log_warning("Missing class or type in line %s:%u", path, line); - return -EINVAL; - } - - if (!strcaseeq(class, "IN")) { - log_warning("RR class %s is not supported, ignoring line %s:%u.", class, path, line); - return -EINVAL; - } - - if (strcaseeq(type, "DS")) { - _cleanup_free_ char *key_tag = NULL, *algorithm = NULL, *digest_type = NULL, *digest = NULL; - _cleanup_free_ void *dd = NULL; - uint16_t kt; - int a, dt; - size_t l; - - r = extract_many_words(&p, NULL, 0, &key_tag, &algorithm, &digest_type, &digest, NULL); - if (r < 0) { - log_warning_errno(r, "Failed to parse DS parameters on line %s:%u: %m", path, line); - return -EINVAL; - } - if (r != 4) { - log_warning("Missing DS parameters on line %s:%u", path, line); - return -EINVAL; - } - - r = safe_atou16(key_tag, &kt); - if (r < 0) - return log_warning_errno(r, "Failed to parse DS key tag %s on line %s:%u: %m", key_tag, path, line); - - a = dnssec_algorithm_from_string(algorithm); - if (a < 0) { - log_warning("Failed to parse DS algorithm %s on line %s:%u", algorithm, path, line); - return -EINVAL; - } - - dt = dnssec_digest_from_string(digest_type); - if (dt < 0) { - log_warning("Failed to parse DS digest type %s on line %s:%u", digest_type, path, line); - return -EINVAL; - } - - r = unhexmem(digest, strlen(digest), &dd, &l); - if (r < 0) { - log_warning("Failed to parse DS digest %s on line %s:%u", digest, path, line); - return -EINVAL; - } - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, domain); - if (!rr) - return log_oom(); - - rr->ds.key_tag = kt; - rr->ds.algorithm = a; - rr->ds.digest_type = dt; - rr->ds.digest_size = l; - rr->ds.digest = dd; - dd = NULL; - - } else if (strcaseeq(type, "DNSKEY")) { - _cleanup_free_ char *flags = NULL, *protocol = NULL, *algorithm = NULL, *key = NULL; - _cleanup_free_ void *k = NULL; - uint16_t f; - size_t l; - int a; - - r = extract_many_words(&p, NULL, 0, &flags, &protocol, &algorithm, &key, NULL); - if (r < 0) - return log_warning_errno(r, "Failed to parse DNSKEY parameters on line %s:%u: %m", path, line); - if (r != 4) { - log_warning("Missing DNSKEY parameters on line %s:%u", path, line); - return -EINVAL; - } - - if (!streq(protocol, "3")) { - log_warning("DNSKEY Protocol is not 3 on line %s:%u", path, line); - return -EINVAL; - } - - r = safe_atou16(flags, &f); - if (r < 0) - return log_warning_errno(r, "Failed to parse DNSKEY flags field %s on line %s:%u", flags, path, line); - if ((f & DNSKEY_FLAG_ZONE_KEY) == 0) { - log_warning("DNSKEY lacks zone key bit set on line %s:%u", path, line); - return -EINVAL; - } - if ((f & DNSKEY_FLAG_REVOKE)) { - log_warning("DNSKEY is already revoked on line %s:%u", path, line); - return -EINVAL; - } - - a = dnssec_algorithm_from_string(algorithm); - if (a < 0) { - log_warning("Failed to parse DNSKEY algorithm %s on line %s:%u", algorithm, path, line); - return -EINVAL; - } - - r = unbase64mem(key, strlen(key), &k, &l); - if (r < 0) - return log_warning_errno(r, "Failed to parse DNSKEY key data %s on line %s:%u", key, path, line); - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, domain); - if (!rr) - return log_oom(); - - rr->dnskey.flags = f; - rr->dnskey.protocol = 3; - rr->dnskey.algorithm = a; - rr->dnskey.key_size = l; - rr->dnskey.key = k; - k = NULL; - - } else { - log_warning("RR type %s is not supported, ignoring line %s:%u.", type, path, line); - return -EINVAL; - } - - if (!isempty(p)) { - log_warning("Trailing garbage on line %s:%u, ignoring line.", path, line); - return -EINVAL; - } - - r = hashmap_ensure_allocated(&d->positive_by_key, &dns_resource_key_hash_ops); - if (r < 0) - return log_oom(); - - old_answer = hashmap_get(d->positive_by_key, rr->key); - answer = dns_answer_ref(old_answer); - - r = dns_answer_add_extend(&answer, rr, 0, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return log_error_errno(r, "Failed to add trust anchor RR: %m"); - - r = hashmap_replace(d->positive_by_key, rr->key, answer); - if (r < 0) - return log_error_errno(r, "Failed to add answer to trust anchor: %m"); - - old_answer = dns_answer_unref(old_answer); - answer = NULL; - - return 0; -} - -static int dns_trust_anchor_load_negative(DnsTrustAnchor *d, const char *path, unsigned line, const char *s) { - _cleanup_free_ char *domain = NULL; - const char *p = s; - int r; - - assert(d); - assert(line); - - r = extract_first_word(&p, &domain, NULL, EXTRACT_QUOTES); - if (r < 0) - return log_warning_errno(r, "Unable to parse line %s:%u: %m", path, line); - - if (!dns_name_is_valid(domain)) { - log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain, path, line); - return -EINVAL; - } - - if (!isempty(p)) { - log_warning("Trailing garbage at line %s:%u, ignoring line.", path, line); - return -EINVAL; - } - - r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops); - if (r < 0) - return log_oom(); - - r = set_put(d->negative_by_name, domain); - if (r < 0) - return log_oom(); - if (r > 0) - domain = NULL; - - return 0; -} - -static int dns_trust_anchor_load_files( - DnsTrustAnchor *d, - const char *suffix, - int (*loader)(DnsTrustAnchor *d, const char *path, unsigned n, const char *line)) { - - _cleanup_strv_free_ char **files = NULL; - char **f; - int r; - - assert(d); - assert(suffix); - assert(loader); - - r = conf_files_list_nulstr(&files, suffix, NULL, trust_anchor_dirs); - if (r < 0) - return log_error_errno(r, "Failed to enumerate %s trust anchor files: %m", suffix); - - STRV_FOREACH(f, files) { - _cleanup_fclose_ FILE *g = NULL; - char line[LINE_MAX]; - unsigned n = 0; - - g = fopen(*f, "r"); - if (!g) { - if (errno == ENOENT) - continue; - - log_warning_errno(errno, "Failed to open %s: %m", *f); - continue; - } - - FOREACH_LINE(line, g, log_warning_errno(errno, "Failed to read %s, ignoring: %m", *f)) { - char *l; - - n++; - - l = strstrip(line); - if (isempty(l)) - continue; - - if (*l == ';') - continue; - - (void) loader(d, *f, n, l); - } - } - - return 0; -} - -static int domain_name_cmp(const void *a, const void *b) { - char **x = (char**) a, **y = (char**) b; - - return dns_name_compare_func(*x, *y); -} - -static int dns_trust_anchor_dump(DnsTrustAnchor *d) { - DnsAnswer *a; - Iterator i; - - assert(d); - - if (hashmap_isempty(d->positive_by_key)) - log_info("No positive trust anchors defined."); - else { - log_info("Positive Trust Anchors:"); - HASHMAP_FOREACH(a, d->positive_by_key, i) { - DnsResourceRecord *rr; - - DNS_ANSWER_FOREACH(rr, a) - log_info("%s", dns_resource_record_to_string(rr)); - } - } - - if (set_isempty(d->negative_by_name)) - log_info("No negative trust anchors defined."); - else { - _cleanup_free_ char **l = NULL, *j = NULL; - - l = set_get_strv(d->negative_by_name); - if (!l) - return log_oom(); - - qsort_safe(l, set_size(d->negative_by_name), sizeof(char*), domain_name_cmp); - - j = strv_join(l, " "); - if (!j) - return log_oom(); - - log_info("Negative trust anchors: %s", j); - } - - return 0; -} - -int dns_trust_anchor_load(DnsTrustAnchor *d) { - int r; - - assert(d); - - /* If loading things from disk fails, we don't consider this fatal */ - (void) dns_trust_anchor_load_files(d, ".positive", dns_trust_anchor_load_positive); - (void) dns_trust_anchor_load_files(d, ".negative", dns_trust_anchor_load_negative); - - /* However, if the built-in DS fails, then we have a problem. */ - r = dns_trust_anchor_add_builtin_positive(d); - if (r < 0) - return log_error_errno(r, "Failed to add built-in positive trust anchor: %m"); - - r = dns_trust_anchor_add_builtin_negative(d); - if (r < 0) - return log_error_errno(r, "Failed to add built-in negative trust anchor: %m"); - - dns_trust_anchor_dump(d); - - return 0; -} - -void dns_trust_anchor_flush(DnsTrustAnchor *d) { - DnsAnswer *a; - DnsResourceRecord *rr; - - assert(d); - - while ((a = hashmap_steal_first(d->positive_by_key))) - dns_answer_unref(a); - d->positive_by_key = hashmap_free(d->positive_by_key); - - while ((rr = set_steal_first(d->revoked_by_rr))) - dns_resource_record_unref(rr); - d->revoked_by_rr = set_free(d->revoked_by_rr); - - d->negative_by_name = set_free_free(d->negative_by_name); -} - -int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey *key, DnsAnswer **ret) { - DnsAnswer *a; - - assert(d); - assert(key); - assert(ret); - - /* We only serve DS and DNSKEY RRs. */ - if (!IN_SET(key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY)) - return 0; - - a = hashmap_get(d->positive_by_key, key); - if (!a) - return 0; - - *ret = dns_answer_ref(a); - return 1; -} - -int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name) { - assert(d); - assert(name); - - return set_contains(d->negative_by_name, name); -} - -static int dns_trust_anchor_revoked_put(DnsTrustAnchor *d, DnsResourceRecord *rr) { - int r; - - assert(d); - - r = set_ensure_allocated(&d->revoked_by_rr, &dns_resource_record_hash_ops); - if (r < 0) - return r; - - r = set_put(d->revoked_by_rr, rr); - if (r < 0) - return r; - if (r > 0) - dns_resource_record_ref(rr); - - return r; -} - -static int dns_trust_anchor_remove_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr) { - _cleanup_(dns_answer_unrefp) DnsAnswer *new_answer = NULL; - DnsAnswer *old_answer; - int r; - - /* Remember that this is a revoked trust anchor RR */ - r = dns_trust_anchor_revoked_put(d, rr); - if (r < 0) - return r; - - /* Remove this from the positive trust anchor */ - old_answer = hashmap_get(d->positive_by_key, rr->key); - if (!old_answer) - return 0; - - new_answer = dns_answer_ref(old_answer); - - r = dns_answer_remove_by_rr(&new_answer, rr); - if (r <= 0) - return r; - - /* We found the key! Warn the user */ - log_struct(LOG_WARNING, - LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED), - LOG_MESSAGE("DNSSEC Trust anchor %s has been revoked. Please update the trust anchor, or upgrade your operating system."), strna(dns_resource_record_to_string(rr)), - "TRUST_ANCHOR=%s", dns_resource_record_to_string(rr), - NULL); - - if (dns_answer_size(new_answer) <= 0) { - assert_se(hashmap_remove(d->positive_by_key, rr->key) == old_answer); - dns_answer_unref(old_answer); - return 1; - } - - r = hashmap_replace(d->positive_by_key, new_answer->items[0].rr->key, new_answer); - if (r < 0) - return r; - - new_answer = NULL; - dns_answer_unref(old_answer); - return 1; -} - -static int dns_trust_anchor_check_revoked_one(DnsTrustAnchor *d, DnsResourceRecord *revoked_dnskey) { - DnsAnswer *a; - int r; - - assert(d); - assert(revoked_dnskey); - assert(revoked_dnskey->key->type == DNS_TYPE_DNSKEY); - assert(revoked_dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE); - - a = hashmap_get(d->positive_by_key, revoked_dnskey->key); - if (a) { - DnsResourceRecord *anchor; - - /* First, look for the precise DNSKEY in our trust anchor database */ - - DNS_ANSWER_FOREACH(anchor, a) { - - if (anchor->dnskey.protocol != revoked_dnskey->dnskey.protocol) - continue; - - if (anchor->dnskey.algorithm != revoked_dnskey->dnskey.algorithm) - continue; - - if (anchor->dnskey.key_size != revoked_dnskey->dnskey.key_size) - continue; - - /* Note that we allow the REVOKE bit to be - * different! It will be set in the revoked - * key, but unset in our version of it */ - if (((anchor->dnskey.flags ^ revoked_dnskey->dnskey.flags) | DNSKEY_FLAG_REVOKE) != DNSKEY_FLAG_REVOKE) - continue; - - if (memcmp(anchor->dnskey.key, revoked_dnskey->dnskey.key, anchor->dnskey.key_size) != 0) - continue; - - dns_trust_anchor_remove_revoked(d, anchor); - break; - } - } - - a = hashmap_get(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(revoked_dnskey->key->class, DNS_TYPE_DS, dns_resource_key_name(revoked_dnskey->key))); - if (a) { - DnsResourceRecord *anchor; - - /* Second, look for DS RRs matching this DNSKEY in our trust anchor database */ - - DNS_ANSWER_FOREACH(anchor, a) { - - /* We set mask_revoke to true here, since our - * DS fingerprint will be the one of the - * unrevoked DNSKEY, but the one we got passed - * here has the bit set. */ - r = dnssec_verify_dnskey_by_ds(revoked_dnskey, anchor, true); - if (r < 0) - return r; - if (r == 0) - continue; - - dns_trust_anchor_remove_revoked(d, anchor); - break; - } - } - - return 0; -} - -int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsResourceRecord *dnskey, DnsAnswer *rrs) { - DnsResourceRecord *rrsig; - int r; - - assert(d); - assert(dnskey); - - /* Looks if "dnskey" is a self-signed RR that has been revoked - * and matches one of our trust anchor entries. If so, removes - * it from the trust anchor and returns > 0. */ - - if (dnskey->key->type != DNS_TYPE_DNSKEY) - return 0; - - /* Is this DNSKEY revoked? */ - if ((dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE) == 0) - return 0; - - /* Could this be interesting to us at all? If not, - * there's no point in looking for and verifying a - * self-signed RRSIG. */ - if (!dns_trust_anchor_knows_domain_positive(d, dns_resource_key_name(dnskey->key))) - return 0; - - /* Look for a self-signed RRSIG in the other rrs belonging to this DNSKEY */ - DNS_ANSWER_FOREACH(rrsig, rrs) { - DnssecResult result; - - if (rrsig->key->type != DNS_TYPE_RRSIG) - continue; - - r = dnssec_rrsig_match_dnskey(rrsig, dnskey, true); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dnssec_verify_rrset(rrs, dnskey->key, rrsig, dnskey, USEC_INFINITY, &result); - if (r < 0) - return r; - if (result != DNSSEC_VALIDATED) - continue; - - /* Bingo! This is a revoked self-signed DNSKEY. Let's - * see if this precise one exists in our trust anchor - * database, too. */ - r = dns_trust_anchor_check_revoked_one(d, dnskey); - if (r < 0) - return r; - - return 1; - } - - return 0; -} - -int dns_trust_anchor_is_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr) { - assert(d); - - if (!IN_SET(rr->key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY)) - return 0; - - return set_contains(d->revoked_by_rr, rr); -} diff --git a/src/resolve/resolved-dns-trust-anchor.h b/src/resolve/resolved-dns-trust-anchor.h deleted file mode 100644 index 635c75fde5..0000000000 --- a/src/resolve/resolved-dns-trust-anchor.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -typedef struct DnsTrustAnchor DnsTrustAnchor; - -#include "hashmap.h" -#include "resolved-dns-answer.h" -#include "resolved-dns-rr.h" - -/* This contains a fixed database mapping domain names to DS or DNSKEY records. */ - -struct DnsTrustAnchor { - Hashmap *positive_by_key; - Set *negative_by_name; - Set *revoked_by_rr; -}; - -int dns_trust_anchor_load(DnsTrustAnchor *d); -void dns_trust_anchor_flush(DnsTrustAnchor *d); - -int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey* key, DnsAnswer **answer); -int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name); - -int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsResourceRecord *dnskey, DnsAnswer *rrs); -int dns_trust_anchor_is_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr); diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c deleted file mode 100644 index 850eed8cb8..0000000000 --- a/src/resolve/resolved-dns-zone.c +++ /dev/null @@ -1,661 +0,0 @@ -/*** - 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 "alloc-util.h" -#include "dns-domain.h" -#include "list.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-zone.h" -#include "string-util.h" - -/* Never allow more than 1K entries */ -#define ZONE_MAX 1024 - -void dns_zone_item_probe_stop(DnsZoneItem *i) { - DnsTransaction *t; - assert(i); - - if (!i->probe_transaction) - return; - - t = i->probe_transaction; - i->probe_transaction = NULL; - - set_remove(t->notify_zone_items, i); - set_remove(t->notify_zone_items_done, i); - dns_transaction_gc(t); -} - -static void dns_zone_item_free(DnsZoneItem *i) { - if (!i) - return; - - dns_zone_item_probe_stop(i); - dns_resource_record_unref(i->rr); - - free(i); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free); - -static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) { - DnsZoneItem *first; - - assert(z); - - if (!i) - return; - - first = hashmap_get(z->by_key, i->rr->key); - LIST_REMOVE(by_key, first, i); - if (first) - assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0); - else - hashmap_remove(z->by_key, i->rr->key); - - first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key)); - LIST_REMOVE(by_name, first, i); - if (first) - assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0); - else - hashmap_remove(z->by_name, dns_resource_key_name(i->rr->key)); - - dns_zone_item_free(i); -} - -void dns_zone_flush(DnsZone *z) { - DnsZoneItem *i; - - assert(z); - - while ((i = hashmap_first(z->by_key))) - dns_zone_item_remove_and_free(z, i); - - assert(hashmap_size(z->by_key) == 0); - assert(hashmap_size(z->by_name) == 0); - - z->by_key = hashmap_free(z->by_key); - z->by_name = hashmap_free(z->by_name); -} - -static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) { - DnsZoneItem *i; - - assert(z); - assert(rr); - - LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key)) - if (dns_resource_record_equal(i->rr, rr) > 0) - return i; - - return NULL; -} - -void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) { - DnsZoneItem *i; - - assert(z); - assert(rr); - - i = dns_zone_get(z, rr); - if (i) - dns_zone_item_remove_and_free(z, i); -} - -static int dns_zone_init(DnsZone *z) { - int r; - - assert(z); - - r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops); - if (r < 0) - return r; - - return 0; -} - -static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) { - DnsZoneItem *first; - int r; - - first = hashmap_get(z->by_key, i->rr->key); - if (first) { - LIST_PREPEND(by_key, first, i); - assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0); - } else { - r = hashmap_put(z->by_key, i->rr->key, i); - if (r < 0) - return r; - } - - first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key)); - if (first) { - LIST_PREPEND(by_name, first, i); - assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0); - } else { - r = hashmap_put(z->by_name, dns_resource_key_name(i->rr->key), i); - if (r < 0) - return r; - } - - return 0; -} - -static int dns_zone_item_probe_start(DnsZoneItem *i) { - DnsTransaction *t; - int r; - - assert(i); - - if (i->probe_transaction) - return 0; - - t = dns_scope_find_transaction(i->scope, &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key)), false); - if (!t) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key)); - if (!key) - return -ENOMEM; - - r = dns_transaction_new(&t, i->scope, key); - if (r < 0) - return r; - } - - r = set_ensure_allocated(&t->notify_zone_items, NULL); - if (r < 0) - goto gc; - - r = set_ensure_allocated(&t->notify_zone_items_done, NULL); - if (r < 0) - goto gc; - - r = set_put(t->notify_zone_items, i); - if (r < 0) - goto gc; - - i->probe_transaction = t; - - if (t->state == DNS_TRANSACTION_NULL) { - - i->block_ready++; - r = dns_transaction_go(t); - i->block_ready--; - - if (r < 0) { - dns_zone_item_probe_stop(i); - return r; - } - } - - dns_zone_item_notify(i); - return 0; - -gc: - dns_transaction_gc(t); - return r; -} - -int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) { - _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL; - DnsZoneItem *existing; - int r; - - assert(z); - assert(s); - assert(rr); - - if (dns_class_is_pseudo(rr->key->class)) - return -EINVAL; - if (dns_type_is_pseudo(rr->key->type)) - return -EINVAL; - - existing = dns_zone_get(z, rr); - if (existing) - return 0; - - r = dns_zone_init(z); - if (r < 0) - return r; - - i = new0(DnsZoneItem, 1); - if (!i) - return -ENOMEM; - - i->scope = s; - i->rr = dns_resource_record_ref(rr); - i->probing_enabled = probe; - - r = dns_zone_link_item(z, i); - if (r < 0) - return r; - - if (probe) { - DnsZoneItem *first, *j; - bool established = false; - - /* Check if there's already an RR with the same name - * established. If so, it has been probed already, and - * we don't ned to probe again. */ - - LIST_FIND_HEAD(by_name, i, first); - LIST_FOREACH(by_name, j, first) { - if (i == j) - continue; - - if (j->state == DNS_ZONE_ITEM_ESTABLISHED) - established = true; - } - - if (established) - i->state = DNS_ZONE_ITEM_ESTABLISHED; - else { - i->state = DNS_ZONE_ITEM_PROBING; - - r = dns_zone_item_probe_start(i); - if (r < 0) { - dns_zone_item_remove_and_free(z, i); - i = NULL; - return r; - } - } - } else - i->state = DNS_ZONE_ITEM_ESTABLISHED; - - i = NULL; - return 0; -} - -int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) { - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL; - unsigned n_answer = 0; - DnsZoneItem *j, *first; - bool tentative = true, need_soa = false; - int r; - - assert(z); - assert(key); - assert(ret_answer); - - /* First iteration, count what we have */ - - if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { - bool found = false, added = false; - int k; - - /* If this is a generic match, then we have to - * go through the list by the name and look - * for everything manually */ - - first = hashmap_get(z->by_name, dns_resource_key_name(key)); - LIST_FOREACH(by_name, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - found = true; - - k = dns_resource_key_match_rr(key, j->rr, NULL); - if (k < 0) - return k; - if (k > 0) { - n_answer++; - added = true; - } - - } - - if (found && !added) - need_soa = true; - - } else { - bool found = false; - - /* If this is a specific match, then look for - * the right key immediately */ - - first = hashmap_get(z->by_key, key); - LIST_FOREACH(by_key, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - found = true; - n_answer++; - } - - if (!found) { - first = hashmap_get(z->by_name, dns_resource_key_name(key)); - LIST_FOREACH(by_name, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - need_soa = true; - break; - } - } - } - - if (n_answer <= 0 && !need_soa) - goto return_empty; - - if (n_answer > 0) { - answer = dns_answer_new(n_answer); - if (!answer) - return -ENOMEM; - } - - if (need_soa) { - soa = dns_answer_new(1); - if (!soa) - return -ENOMEM; - } - - /* Second iteration, actually add the RRs to the answers */ - if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { - bool found = false, added = false; - int k; - - first = hashmap_get(z->by_name, dns_resource_key_name(key)); - LIST_FOREACH(by_name, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - found = true; - - if (j->state != DNS_ZONE_ITEM_PROBING) - tentative = false; - - k = dns_resource_key_match_rr(key, j->rr, NULL); - if (k < 0) - return k; - if (k > 0) { - r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - added = true; - } - } - - if (found && !added) { - r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL); - if (r < 0) - return r; - } - } else { - bool found = false; - - first = hashmap_get(z->by_key, key); - LIST_FOREACH(by_key, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - found = true; - - if (j->state != DNS_ZONE_ITEM_PROBING) - tentative = false; - - r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - if (!found) { - bool add_soa = false; - - first = hashmap_get(z->by_name, dns_resource_key_name(key)); - LIST_FOREACH(by_name, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - if (j->state != DNS_ZONE_ITEM_PROBING) - tentative = false; - - add_soa = true; - } - - if (add_soa) { - r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL); - if (r < 0) - return r; - } - } - } - - /* If the caller sets ret_tentative to NULL, then use this as - * indication to not return tentative entries */ - - if (!ret_tentative && tentative) - goto return_empty; - - *ret_answer = answer; - answer = NULL; - - if (ret_soa) { - *ret_soa = soa; - soa = NULL; - } - - if (ret_tentative) - *ret_tentative = tentative; - - return 1; - -return_empty: - *ret_answer = NULL; - - if (ret_soa) - *ret_soa = NULL; - - if (ret_tentative) - *ret_tentative = false; - - return 0; -} - -void dns_zone_item_conflict(DnsZoneItem *i) { - assert(i); - - if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED)) - return; - - log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr))); - - dns_zone_item_probe_stop(i); - - /* Withdraw the conflict item */ - i->state = DNS_ZONE_ITEM_WITHDRAWN; - - /* Maybe change the hostname */ - if (manager_is_own_hostname(i->scope->manager, dns_resource_key_name(i->rr->key)) > 0) - manager_next_hostname(i->scope->manager); -} - -void dns_zone_item_notify(DnsZoneItem *i) { - assert(i); - assert(i->probe_transaction); - - if (i->block_ready > 0) - return; - - if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING)) - return; - - if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) { - bool we_lost = false; - - /* The probe got a successful reply. If we so far - * weren't established we just give up. If we already - * were established, and the peer has the - * lexicographically larger IP address we continue - * and defend it. */ - - if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) { - log_debug("Got a successful probe for not yet established RR, we lost."); - we_lost = true; - } else { - assert(i->probe_transaction->received); - we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0; - if (we_lost) - log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost."); - } - - if (we_lost) { - dns_zone_item_conflict(i); - return; - } - - log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost."); - } - - log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr))); - - dns_zone_item_probe_stop(i); - i->state = DNS_ZONE_ITEM_ESTABLISHED; -} - -static int dns_zone_item_verify(DnsZoneItem *i) { - int r; - - assert(i); - - if (i->state != DNS_ZONE_ITEM_ESTABLISHED) - return 0; - - log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr))); - - i->state = DNS_ZONE_ITEM_VERIFYING; - r = dns_zone_item_probe_start(i); - if (r < 0) { - log_error_errno(r, "Failed to start probing for verifying RR: %m"); - i->state = DNS_ZONE_ITEM_ESTABLISHED; - return r; - } - - return 0; -} - -int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) { - DnsZoneItem *i, *first; - int c = 0; - - assert(zone); - assert(rr); - - /* This checks whether a response RR we received from somebody - * else is one that we actually thought was uniquely ours. If - * so, we'll verify our RRs. */ - - /* No conflict if we don't have the name at all. */ - first = hashmap_get(zone->by_name, dns_resource_key_name(rr->key)); - if (!first) - return 0; - - /* No conflict if we have the exact same RR */ - if (dns_zone_get(zone, rr)) - return 0; - - /* OK, somebody else has RRs for the same name. Yuck! Let's - * start probing again */ - - LIST_FOREACH(by_name, i, first) { - if (dns_resource_record_equal(i->rr, rr)) - continue; - - dns_zone_item_verify(i); - c++; - } - - return c; -} - -int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) { - DnsZoneItem *i, *first; - int c = 0; - - assert(zone); - - /* Somebody else notified us about a possible conflict. Let's - * verify if that's true. */ - - first = hashmap_get(zone->by_name, dns_resource_key_name(key)); - if (!first) - return 0; - - LIST_FOREACH(by_name, i, first) { - dns_zone_item_verify(i); - c++; - } - - return c; -} - -void dns_zone_verify_all(DnsZone *zone) { - DnsZoneItem *i; - Iterator iterator; - - assert(zone); - - HASHMAP_FOREACH(i, zone->by_key, iterator) { - DnsZoneItem *j; - - LIST_FOREACH(by_key, j, i) - dns_zone_item_verify(j); - } -} - -void dns_zone_dump(DnsZone *zone, FILE *f) { - Iterator iterator; - DnsZoneItem *i; - - if (!zone) - return; - - if (!f) - f = stdout; - - HASHMAP_FOREACH(i, zone->by_key, iterator) { - DnsZoneItem *j; - - LIST_FOREACH(by_key, j, i) { - const char *t; - - t = dns_resource_record_to_string(j->rr); - if (!t) { - log_oom(); - continue; - } - - fputc('\t', f); - fputs(t, f); - fputc('\n', f); - } - } -} - -bool dns_zone_is_empty(DnsZone *zone) { - if (!zone) - return true; - - return hashmap_isempty(zone->by_key); -} diff --git a/src/resolve/resolved-dns-zone.h b/src/resolve/resolved-dns-zone.h deleted file mode 100644 index 408833c359..0000000000 --- a/src/resolve/resolved-dns-zone.h +++ /dev/null @@ -1,81 +0,0 @@ -#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 "hashmap.h" - -typedef struct DnsZone { - Hashmap *by_key; - Hashmap *by_name; -} DnsZone; - -typedef struct DnsZoneItem DnsZoneItem; -typedef enum DnsZoneItemState DnsZoneItemState; - -#include "resolved-dns-answer.h" -#include "resolved-dns-question.h" -#include "resolved-dns-rr.h" -#include "resolved-dns-transaction.h" - -/* RFC 4795 Section 2.8. suggests a TTL of 30s by default */ -#define LLMNR_DEFAULT_TTL (30) - -enum DnsZoneItemState { - DNS_ZONE_ITEM_PROBING, - DNS_ZONE_ITEM_ESTABLISHED, - DNS_ZONE_ITEM_VERIFYING, - DNS_ZONE_ITEM_WITHDRAWN, -}; - -struct DnsZoneItem { - DnsScope *scope; - DnsResourceRecord *rr; - - DnsZoneItemState state; - - unsigned block_ready; - - bool probing_enabled; - - LIST_FIELDS(DnsZoneItem, by_key); - LIST_FIELDS(DnsZoneItem, by_name); - - DnsTransaction *probe_transaction; -}; - -void dns_zone_flush(DnsZone *z); - -int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe); -void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr); - -int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **answer, DnsAnswer **soa, bool *tentative); - -void dns_zone_item_conflict(DnsZoneItem *i); -void dns_zone_item_notify(DnsZoneItem *i); - -int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr); -int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key); - -void dns_zone_verify_all(DnsZone *zone); - -void dns_zone_item_probe_stop(DnsZoneItem *i); - -void dns_zone_dump(DnsZone *zone, FILE *f); -bool dns_zone_is_empty(DnsZone *zone); diff --git a/src/resolve/resolved-etc-hosts.c b/src/resolve/resolved-etc-hosts.c deleted file mode 100644 index 40d650949d..0000000000 --- a/src/resolve/resolved-etc-hosts.c +++ /dev/null @@ -1,448 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2016 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 "fd-util.h" -#include "fileio.h" -#include "hostname-util.h" -#include "resolved-etc-hosts.h" -#include "resolved-dns-synthesize.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" - -/* Recheck /etc/hosts at most once every 2s */ -#define ETC_HOSTS_RECHECK_USEC (2*USEC_PER_SEC) - -typedef struct EtcHostsItem { - int family; - union in_addr_union address; - - char **names; -} EtcHostsItem; - -typedef struct EtcHostsItemByName { - char *name; - - EtcHostsItem **items; - size_t n_items, n_allocated; -} EtcHostsItemByName; - -void manager_etc_hosts_flush(Manager *m) { - EtcHostsItem *item; - EtcHostsItemByName *bn; - - while ((item = set_steal_first(m->etc_hosts_by_address))) { - strv_free(item->names); - free(item); - } - - while ((bn = hashmap_steal_first(m->etc_hosts_by_name))) { - free(bn->name); - free(bn->items); - free(bn); - } - - m->etc_hosts_by_address = set_free(m->etc_hosts_by_address); - m->etc_hosts_by_name = hashmap_free(m->etc_hosts_by_name); - - m->etc_hosts_mtime = USEC_INFINITY; -} - -static void etc_hosts_item_hash_func(const void *p, struct siphash *state) { - const EtcHostsItem *item = p; - - siphash24_compress(&item->family, sizeof(item->family), state); - - if (item->family == AF_INET) - siphash24_compress(&item->address.in, sizeof(item->address.in), state); - else if (item->family == AF_INET6) - siphash24_compress(&item->address.in6, sizeof(item->address.in6), state); -} - -static int etc_hosts_item_compare_func(const void *a, const void *b) { - const EtcHostsItem *x = a, *y = b; - - if (x->family != y->family) - return x->family - y->family; - - if (x->family == AF_INET) - return memcmp(&x->address.in.s_addr, &y->address.in.s_addr, sizeof(struct in_addr)); - - if (x->family == AF_INET6) - return memcmp(&x->address.in6.s6_addr, &y->address.in6.s6_addr, sizeof(struct in6_addr)); - - return trivial_compare_func(a, b); -} - -static const struct hash_ops etc_hosts_item_ops = { - .hash = etc_hosts_item_hash_func, - .compare = etc_hosts_item_compare_func, -}; - -static int add_item(Manager *m, int family, const union in_addr_union *address, char **names) { - - EtcHostsItem key = { - .family = family, - .address = *address, - }; - EtcHostsItem *item; - char **n; - int r; - - assert(m); - assert(address); - - r = in_addr_is_null(family, address); - if (r < 0) - return r; - if (r > 0) - /* This is an 0.0.0.0 or :: item, which we assume means that we shall map the specified hostname to - * nothing. */ - item = NULL; - else { - /* If this is a normal address, then, simply add entry mapping it to the specified names */ - - item = set_get(m->etc_hosts_by_address, &key); - if (item) { - r = strv_extend_strv(&item->names, names, true); - if (r < 0) - return log_oom(); - } else { - - r = set_ensure_allocated(&m->etc_hosts_by_address, &etc_hosts_item_ops); - if (r < 0) - return log_oom(); - - item = new0(EtcHostsItem, 1); - if (!item) - return log_oom(); - - item->family = family; - item->address = *address; - item->names = names; - - r = set_put(m->etc_hosts_by_address, item); - if (r < 0) { - free(item); - return log_oom(); - } - } - } - - STRV_FOREACH(n, names) { - EtcHostsItemByName *bn; - - bn = hashmap_get(m->etc_hosts_by_name, *n); - if (!bn) { - r = hashmap_ensure_allocated(&m->etc_hosts_by_name, &dns_name_hash_ops); - if (r < 0) - return log_oom(); - - bn = new0(EtcHostsItemByName, 1); - if (!bn) - return log_oom(); - - bn->name = strdup(*n); - if (!bn->name) { - free(bn); - return log_oom(); - } - - r = hashmap_put(m->etc_hosts_by_name, bn->name, bn); - if (r < 0) { - free(bn->name); - free(bn); - return log_oom(); - } - } - - if (item) { - if (!GREEDY_REALLOC(bn->items, bn->n_allocated, bn->n_items+1)) - return log_oom(); - - bn->items[bn->n_items++] = item; - } - } - - return 0; -} - -static int parse_line(Manager *m, unsigned nr, const char *line) { - _cleanup_free_ char *address = NULL; - _cleanup_strv_free_ char **names = NULL; - union in_addr_union in; - bool suppressed = false; - int family, r; - - assert(m); - assert(line); - - r = extract_first_word(&line, &address, NULL, EXTRACT_RELAX); - if (r < 0) - return log_error_errno(r, "Couldn't extract address, in line /etc/hosts:%u.", nr); - if (r == 0) { - log_error("Premature end of line, in line /etc/hosts:%u.", nr); - return -EINVAL; - } - - r = in_addr_from_string_auto(address, &family, &in); - if (r < 0) - return log_error_errno(r, "Address '%s' is invalid, in line /etc/hosts:%u.", address, nr); - - for (;;) { - _cleanup_free_ char *name = NULL; - - r = extract_first_word(&line, &name, NULL, EXTRACT_RELAX); - if (r < 0) - return log_error_errno(r, "Couldn't extract host name, in line /etc/hosts:%u.", nr); - if (r == 0) - break; - - r = dns_name_is_valid(name); - if (r <= 0) - return log_error_errno(r, "Hostname %s is not valid, ignoring, in line /etc/hosts:%u.", name, nr); - - if (is_localhost(name)) { - /* Suppress the "localhost" line that is often seen */ - suppressed = true; - continue; - } - - r = strv_push(&names, name); - if (r < 0) - return log_oom(); - - name = NULL; - } - - if (strv_isempty(names)) { - - if (suppressed) - return 0; - - log_error("Line is missing any host names, in line /etc/hosts:%u.", nr); - return -EINVAL; - } - - /* Takes possession of the names strv */ - r = add_item(m, family, &in, names); - if (r < 0) - return r; - - names = NULL; - return r; -} - -int manager_etc_hosts_read(Manager *m) { - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - struct stat st; - usec_t ts; - unsigned nr = 0; - int r; - - assert_se(sd_event_now(m->event, clock_boottime_or_monotonic(), &ts) >= 0); - - /* See if we checked /etc/hosts recently already */ - if (m->etc_hosts_last != USEC_INFINITY && m->etc_hosts_last + ETC_HOSTS_RECHECK_USEC > ts) - return 0; - - m->etc_hosts_last = ts; - - if (m->etc_hosts_mtime != USEC_INFINITY) { - if (stat("/etc/hosts", &st) < 0) { - if (errno == ENOENT) { - r = 0; - goto clear; - } - - return log_error_errno(errno, "Failed to stat /etc/hosts: %m"); - } - - /* Did the mtime change? If not, there's no point in re-reading the file. */ - if (timespec_load(&st.st_mtim) == m->etc_hosts_mtime) - return 0; - } - - f = fopen("/etc/hosts", "re"); - if (!f) { - if (errno == ENOENT) { - r = 0; - goto clear; - } - - return log_error_errno(errno, "Failed to open /etc/hosts: %m"); - } - - /* Take the timestamp at the beginning of processing, so that any changes made later are read on the next - * invocation */ - r = fstat(fileno(f), &st); - if (r < 0) - return log_error_errno(errno, "Failed to fstat() /etc/hosts: %m"); - - manager_etc_hosts_flush(m); - - FOREACH_LINE(line, f, return log_error_errno(errno, "Failed to read /etc/hosts: %m")) { - char *l; - - nr++; - - l = strstrip(line); - if (isempty(l)) - continue; - if (l[0] == '#') - continue; - - r = parse_line(m, nr, l); - if (r == -ENOMEM) /* On OOM we abandon the half-built-up structure. All other errors we ignore and proceed */ - goto clear; - } - - m->etc_hosts_mtime = timespec_load(&st.st_mtim); - m->etc_hosts_last = ts; - - return 1; - -clear: - manager_etc_hosts_flush(m); - return r; -} - -int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer) { - bool found_a = false, found_aaaa = false; - EtcHostsItemByName *bn; - EtcHostsItem k = {}; - DnsResourceKey *t; - const char *name; - unsigned i; - int r; - - assert(m); - assert(q); - assert(answer); - - r = manager_etc_hosts_read(m); - if (r < 0) - return r; - - name = dns_question_first_name(q); - if (!name) - return 0; - - r = dns_name_address(name, &k.family, &k.address); - if (r > 0) { - EtcHostsItem *item; - DnsResourceKey *found_ptr = NULL; - - item = set_get(m->etc_hosts_by_address, &k); - if (!item) - return 0; - - /* We have an address in /etc/hosts that matches the queried name. Let's return successful. Actual data - * we'll only return if the request was for PTR. */ - - DNS_QUESTION_FOREACH(t, q) { - if (!IN_SET(t->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) - continue; - if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY)) - continue; - - r = dns_name_equal(dns_resource_key_name(t), name); - if (r < 0) - return r; - if (r > 0) { - found_ptr = t; - break; - } - } - - if (found_ptr) { - char **n; - - r = dns_answer_reserve(answer, strv_length(item->names)); - if (r < 0) - return r; - - STRV_FOREACH(n, item->names) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - rr = dns_resource_record_new(found_ptr); - if (!rr) - return -ENOMEM; - - rr->ptr.name = strdup(*n); - if (!rr->ptr.name) - return -ENOMEM; - - r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - } - - return 1; - } - - bn = hashmap_get(m->etc_hosts_by_name, name); - if (!bn) - return 0; - - r = dns_answer_reserve(answer, bn->n_items); - if (r < 0) - return r; - - DNS_QUESTION_FOREACH(t, q) { - if (!IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_TYPE_ANY)) - continue; - if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY)) - continue; - - r = dns_name_equal(dns_resource_key_name(t), name); - if (r < 0) - return r; - if (r == 0) - continue; - - if (IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_ANY)) - found_a = true; - if (IN_SET(t->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) - found_aaaa = true; - - if (found_a && found_aaaa) - break; - } - - for (i = 0; i < bn->n_items; i++) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - if ((found_a && bn->items[i]->family != AF_INET) && - (found_aaaa && bn->items[i]->family != AF_INET6)) - continue; - - r = dns_resource_record_new_address(&rr, bn->items[i]->family, &bn->items[i]->address, bn->name); - if (r < 0) - return r; - - r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - return 1; -} diff --git a/src/resolve/resolved-etc-hosts.h b/src/resolve/resolved-etc-hosts.h deleted file mode 100644 index 9d5a175f18..0000000000 --- a/src/resolve/resolved-etc-hosts.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2016 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 "resolved-manager.h" -#include "resolved-dns-question.h" -#include "resolved-dns-answer.h" - -void manager_etc_hosts_flush(Manager *m); -int manager_etc_hosts_read(Manager *m); -int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer); diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf deleted file mode 100644 index 82f26215df..0000000000 --- a/src/resolve/resolved-gperf.gperf +++ /dev/null @@ -1,21 +0,0 @@ -%{ -#include -#include "conf-parser.h" -#include "resolved-conf.h" -%} -struct ConfigPerfItem; -%null_strings -%language=ANSI-C -%define slot-name section_and_lvalue -%define hash-function-name resolved_gperf_hash -%define lookup-function-name resolved_gperf_lookup -%readonly-tables -%omit-struct-type -%struct-type -%includes -%% -Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0 -Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0 -Resolve.Domains, config_parse_search_domains, 0, 0 -Resolve.LLMNR, config_parse_resolve_support, 0, offsetof(Manager, llmnr_support) -Resolve.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode) diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c deleted file mode 100644 index 7f21891819..0000000000 --- a/src/resolve/resolved-link-bus.c +++ /dev/null @@ -1,550 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2016 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 "alloc-util.h" -#include "bus-util.h" -#include "parse-util.h" -#include "resolve-util.h" -#include "resolved-bus.h" -#include "resolved-link-bus.h" -#include "strv.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_resolve_support, resolve_support, ResolveSupport); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_dnssec_mode, dnssec_mode, DnssecMode); - -static int property_get_dns( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Link *l = userdata; - DnsServer *s; - int r; - - assert(reply); - assert(l); - - r = sd_bus_message_open_container(reply, 'a', "(iay)"); - if (r < 0) - return r; - - LIST_FOREACH(servers, s, l->dns_servers) { - r = bus_dns_server_append(reply, s, false); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_domains( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Link *l = userdata; - DnsSearchDomain *d; - int r; - - assert(reply); - assert(l); - - r = sd_bus_message_open_container(reply, 'a', "(sb)"); - if (r < 0) - return r; - - LIST_FOREACH(domains, d, l->search_domains) { - r = sd_bus_message_append(reply, "(sb)", d->name, d->route_only); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_scopes_mask( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Link *l = userdata; - uint64_t mask; - - assert(reply); - assert(l); - - mask = (l->unicast_scope ? SD_RESOLVED_DNS : 0) | - (l->llmnr_ipv4_scope ? SD_RESOLVED_LLMNR_IPV4 : 0) | - (l->llmnr_ipv6_scope ? SD_RESOLVED_LLMNR_IPV6 : 0) | - (l->mdns_ipv4_scope ? SD_RESOLVED_MDNS_IPV4 : 0) | - (l->mdns_ipv6_scope ? SD_RESOLVED_MDNS_IPV6 : 0); - - return sd_bus_message_append(reply, "t", mask); -} - -static int property_get_ntas( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Link *l = userdata; - const char *name; - Iterator i; - int r; - - assert(reply); - assert(l); - - r = sd_bus_message_open_container(reply, 'a', "s"); - if (r < 0) - return r; - - SET_FOREACH(name, l->dnssec_negative_trust_anchors, i) { - r = sd_bus_message_append(reply, "s", name); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_dnssec_supported( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Link *l = userdata; - - assert(reply); - assert(l); - - return sd_bus_message_append(reply, "b", link_dnssec_supported(l)); -} - -int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ struct in_addr_data *dns = NULL; - size_t allocated = 0, n = 0; - Link *l = userdata; - unsigned i; - int r; - - assert(message); - assert(l); - - r = sd_bus_message_enter_container(message, 'a', "(iay)"); - if (r < 0) - return r; - - for (;;) { - int family; - size_t sz; - const void *d; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_enter_container(message, 'r', "iay"); - if (r < 0) - return r; - if (r == 0) - break; - - r = sd_bus_message_read(message, "i", &family); - if (r < 0) - return r; - - if (!IN_SET(family, AF_INET, AF_INET6)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); - - r = sd_bus_message_read_array(message, 'y', &d, &sz); - if (r < 0) - return r; - if (sz != FAMILY_ADDRESS_SIZE(family)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid address size"); - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (!GREEDY_REALLOC(dns, allocated, n+1)) - return -ENOMEM; - - dns[n].family = family; - memcpy(&dns[n].address, d, sz); - n++; - } - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - dns_server_mark_all(l->dns_servers); - - for (i = 0; i < n; i++) { - DnsServer *s; - - s = dns_server_find(l->dns_servers, dns[i].family, &dns[i].address); - if (s) - dns_server_move_back_and_unmark(s); - else { - r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, dns[i].family, &dns[i].address); - if (r < 0) - goto clear; - } - - } - - dns_server_unlink_marked(l->dns_servers); - link_allocate_scopes(l); - - return sd_bus_reply_method_return(message, NULL); - -clear: - dns_server_unlink_all(l->dns_servers); - return r; -} - -int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Link *l = userdata; - int r; - - assert(message); - assert(l); - - r = sd_bus_message_enter_container(message, 'a', "(sb)"); - if (r < 0) - return r; - - for (;;) { - const char *name; - int route_only; - - r = sd_bus_message_read(message, "(sb)", &name, &route_only); - if (r < 0) - return r; - if (r == 0) - break; - - r = dns_name_is_valid(name); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid search domain %s", name); - if (!route_only && dns_name_is_root(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Root domain is not suitable as search domain"); - } - - dns_search_domain_mark_all(l->search_domains); - - r = sd_bus_message_rewind(message, false); - if (r < 0) - return r; - - for (;;) { - DnsSearchDomain *d; - const char *name; - int route_only; - - r = sd_bus_message_read(message, "(sb)", &name, &route_only); - if (r < 0) - goto clear; - if (r == 0) - break; - - r = dns_search_domain_find(l->search_domains, name, &d); - if (r < 0) - goto clear; - - if (r > 0) - dns_search_domain_move_back_and_unmark(d); - else { - r = dns_search_domain_new(l->manager, &d, DNS_SEARCH_DOMAIN_LINK, l, name); - if (r < 0) - goto clear; - } - - d->route_only = route_only; - } - - r = sd_bus_message_exit_container(message); - if (r < 0) - goto clear; - - dns_search_domain_unlink_marked(l->search_domains); - return sd_bus_reply_method_return(message, NULL); - -clear: - dns_search_domain_unlink_all(l->search_domains); - return r; -} - -int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Link *l = userdata; - ResolveSupport mode; - const char *llmnr; - int r; - - assert(message); - assert(l); - - r = sd_bus_message_read(message, "s", &llmnr); - if (r < 0) - return r; - - if (isempty(llmnr)) - mode = RESOLVE_SUPPORT_YES; - else { - mode = resolve_support_from_string(llmnr); - if (mode < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid LLMNR setting: %s", llmnr); - } - - l->llmnr_support = mode; - link_allocate_scopes(l); - link_add_rrs(l, false); - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Link *l = userdata; - ResolveSupport mode; - const char *mdns; - int r; - - assert(message); - assert(l); - - r = sd_bus_message_read(message, "s", &mdns); - if (r < 0) - return r; - - if (isempty(mdns)) - mode = RESOLVE_SUPPORT_NO; - else { - mode = resolve_support_from_string(mdns); - if (mode < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid MulticastDNS setting: %s", mdns); - } - - l->mdns_support = mode; - link_allocate_scopes(l); - link_add_rrs(l, false); - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Link *l = userdata; - const char *dnssec; - DnssecMode mode; - int r; - - assert(message); - assert(l); - - r = sd_bus_message_read(message, "s", &dnssec); - if (r < 0) - return r; - - if (isempty(dnssec)) - mode = _DNSSEC_MODE_INVALID; - else { - mode = dnssec_mode_from_string(dnssec); - if (mode < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNSSEC setting: %s", dnssec); - } - - link_set_dnssec_mode(l, mode); - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_set_free_free_ Set *ns = NULL; - _cleanup_free_ char **ntas = NULL; - Link *l = userdata; - int r; - char **i; - - assert(message); - assert(l); - - r = sd_bus_message_read_strv(message, &ntas); - if (r < 0) - return r; - - STRV_FOREACH(i, ntas) { - r = dns_name_is_valid(*i); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid negative trust anchor domain: %s", *i); - } - - ns = set_new(&dns_name_hash_ops); - if (!ns) - return -ENOMEM; - - STRV_FOREACH(i, ntas) { - r = set_put_strdup(ns, *i); - if (r < 0) - return r; - } - - set_free_free(l->dnssec_negative_trust_anchors); - l->dnssec_negative_trust_anchors = ns; - ns = NULL; - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_link_method_revert(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Link *l = userdata; - - assert(message); - assert(l); - - link_flush_settings(l); - link_allocate_scopes(l); - link_add_rrs(l, false); - - return sd_bus_reply_method_return(message, NULL); -} - -const sd_bus_vtable link_vtable[] = { - SD_BUS_VTABLE_START(0), - - SD_BUS_PROPERTY("ScopesMask", "t", property_get_scopes_mask, 0, 0), - SD_BUS_PROPERTY("DNS", "a(iay)", property_get_dns, 0, 0), - SD_BUS_PROPERTY("Domains", "a(sb)", property_get_domains, 0, 0), - SD_BUS_PROPERTY("LLMNR", "s", property_get_resolve_support, offsetof(Link, llmnr_support), 0), - SD_BUS_PROPERTY("MulticastDNS", "s", property_get_resolve_support, offsetof(Link, mdns_support), 0), - SD_BUS_PROPERTY("DNSSEC", "s", property_get_dnssec_mode, offsetof(Link, dnssec_mode), 0), - SD_BUS_PROPERTY("DNSSECNegativeTrustAnchors", "as", property_get_ntas, 0, 0), - SD_BUS_PROPERTY("DNSSECSupported", "b", property_get_dnssec_supported, 0, 0), - - SD_BUS_METHOD("SetDNS", "a(iay)", NULL, bus_link_method_set_dns_servers, 0), - SD_BUS_METHOD("SetDomains", "a(sb)", NULL, bus_link_method_set_domains, 0), - SD_BUS_METHOD("SetLLMNR", "s", NULL, bus_link_method_set_llmnr, 0), - SD_BUS_METHOD("SetMulticastDNS", "s", NULL, bus_link_method_set_mdns, 0), - SD_BUS_METHOD("SetDNSSEC", "s", NULL, bus_link_method_set_dnssec, 0), - SD_BUS_METHOD("SetDNSSECNegativeTrustAnchors", "as", NULL, bus_link_method_set_dnssec_negative_trust_anchors, 0), - SD_BUS_METHOD("Revert", NULL, NULL, bus_link_method_revert, 0), - - SD_BUS_VTABLE_END -}; - -int link_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - _cleanup_free_ char *e = NULL; - Manager *m = userdata; - int ifindex; - Link *link; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - r = sd_bus_path_decode(path, "/org/freedesktop/resolve1/link", &e); - if (r <= 0) - return 0; - - r = parse_ifindex(e, &ifindex); - if (r < 0) - return 0; - - link = hashmap_get(m->links, INT_TO_PTR(ifindex)); - if (!link) - return 0; - - *found = link; - return 1; -} - -char *link_bus_path(Link *link) { - _cleanup_free_ char *ifindex = NULL; - char *p; - int r; - - assert(link); - - if (asprintf(&ifindex, "%i", link->ifindex) < 0) - return NULL; - - r = sd_bus_path_encode("/org/freedesktop/resolve1/link", ifindex, &p); - if (r < 0) - return NULL; - - return p; -} - -int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { - _cleanup_strv_free_ char **l = NULL; - Manager *m = userdata; - Link *link; - Iterator i; - unsigned c = 0; - - assert(bus); - assert(path); - assert(m); - assert(nodes); - - l = new0(char*, hashmap_size(m->links) + 1); - if (!l) - return -ENOMEM; - - HASHMAP_FOREACH(link, m->links, i) { - char *p; - - p = link_bus_path(link); - if (!p) - return -ENOMEM; - - l[c++] = p; - } - - l[c] = NULL; - *nodes = l; - l = NULL; - - return 1; -} diff --git a/src/resolve/resolved-link-bus.h b/src/resolve/resolved-link-bus.h deleted file mode 100644 index 646031b631..0000000000 --- a/src/resolve/resolved-link-bus.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2016 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 "sd-bus.h" - -#include "resolved-link.h" - -extern const sd_bus_vtable link_vtable[]; - -int link_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); -char *link_bus_path(Link *link); -int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); - -int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_link_method_revert(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c deleted file mode 100644 index b0dc65036d..0000000000 --- a/src/resolve/resolved-link.c +++ /dev/null @@ -1,840 +0,0 @@ -/*** - 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 "sd-network.h" - -#include "alloc-util.h" -#include "missing.h" -#include "parse-util.h" -#include "resolved-link.h" -#include "string-util.h" -#include "strv.h" - -int link_new(Manager *m, Link **ret, int ifindex) { - _cleanup_(link_freep) Link *l = NULL; - int r; - - assert(m); - assert(ifindex > 0); - - r = hashmap_ensure_allocated(&m->links, NULL); - if (r < 0) - return r; - - l = new0(Link, 1); - if (!l) - return -ENOMEM; - - l->ifindex = ifindex; - l->llmnr_support = RESOLVE_SUPPORT_YES; - l->mdns_support = RESOLVE_SUPPORT_NO; - l->dnssec_mode = _DNSSEC_MODE_INVALID; - l->operstate = IF_OPER_UNKNOWN; - - r = hashmap_put(m->links, INT_TO_PTR(ifindex), l); - if (r < 0) - return r; - - l->manager = m; - - if (ret) - *ret = l; - l = NULL; - - return 0; -} - -void link_flush_settings(Link *l) { - assert(l); - - l->llmnr_support = RESOLVE_SUPPORT_YES; - l->mdns_support = RESOLVE_SUPPORT_NO; - l->dnssec_mode = _DNSSEC_MODE_INVALID; - - dns_server_unlink_all(l->dns_servers); - dns_search_domain_unlink_all(l->search_domains); - - l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors); -} - -Link *link_free(Link *l) { - if (!l) - return NULL; - - link_flush_settings(l); - - while (l->addresses) - (void) link_address_free(l->addresses); - - if (l->manager) - hashmap_remove(l->manager->links, INT_TO_PTR(l->ifindex)); - - dns_scope_free(l->unicast_scope); - dns_scope_free(l->llmnr_ipv4_scope); - dns_scope_free(l->llmnr_ipv6_scope); - dns_scope_free(l->mdns_ipv4_scope); - dns_scope_free(l->mdns_ipv6_scope); - - free(l); - return NULL; -} - -void link_allocate_scopes(Link *l) { - int r; - - assert(l); - - if (link_relevant(l, AF_UNSPEC, false) && - l->dns_servers) { - if (!l->unicast_scope) { - r = dns_scope_new(l->manager, &l->unicast_scope, l, DNS_PROTOCOL_DNS, AF_UNSPEC); - if (r < 0) - log_warning_errno(r, "Failed to allocate DNS scope: %m"); - } - } else - l->unicast_scope = dns_scope_free(l->unicast_scope); - - if (link_relevant(l, AF_INET, true) && - l->llmnr_support != RESOLVE_SUPPORT_NO && - l->manager->llmnr_support != RESOLVE_SUPPORT_NO) { - if (!l->llmnr_ipv4_scope) { - r = dns_scope_new(l->manager, &l->llmnr_ipv4_scope, l, DNS_PROTOCOL_LLMNR, AF_INET); - if (r < 0) - log_warning_errno(r, "Failed to allocate LLMNR IPv4 scope: %m"); - } - } else - l->llmnr_ipv4_scope = dns_scope_free(l->llmnr_ipv4_scope); - - if (link_relevant(l, AF_INET6, true) && - l->llmnr_support != RESOLVE_SUPPORT_NO && - l->manager->llmnr_support != RESOLVE_SUPPORT_NO && - socket_ipv6_is_supported()) { - if (!l->llmnr_ipv6_scope) { - r = dns_scope_new(l->manager, &l->llmnr_ipv6_scope, l, DNS_PROTOCOL_LLMNR, AF_INET6); - if (r < 0) - log_warning_errno(r, "Failed to allocate LLMNR IPv6 scope: %m"); - } - } else - l->llmnr_ipv6_scope = dns_scope_free(l->llmnr_ipv6_scope); - - if (link_relevant(l, AF_INET, true) && - l->mdns_support != RESOLVE_SUPPORT_NO && - l->manager->mdns_support != RESOLVE_SUPPORT_NO) { - if (!l->mdns_ipv4_scope) { - r = dns_scope_new(l->manager, &l->mdns_ipv4_scope, l, DNS_PROTOCOL_MDNS, AF_INET); - if (r < 0) - log_warning_errno(r, "Failed to allocate mDNS IPv4 scope: %m"); - } - } else - l->mdns_ipv4_scope = dns_scope_free(l->mdns_ipv4_scope); - - if (link_relevant(l, AF_INET6, true) && - l->mdns_support != RESOLVE_SUPPORT_NO && - l->manager->mdns_support != RESOLVE_SUPPORT_NO) { - if (!l->mdns_ipv6_scope) { - r = dns_scope_new(l->manager, &l->mdns_ipv6_scope, l, DNS_PROTOCOL_MDNS, AF_INET6); - if (r < 0) - log_warning_errno(r, "Failed to allocate mDNS IPv6 scope: %m"); - } - } else - l->mdns_ipv6_scope = dns_scope_free(l->mdns_ipv6_scope); -} - -void link_add_rrs(Link *l, bool force_remove) { - LinkAddress *a; - - LIST_FOREACH(addresses, a, l->addresses) - link_address_add_rrs(a, force_remove); -} - -int link_update_rtnl(Link *l, sd_netlink_message *m) { - const char *n = NULL; - int r; - - assert(l); - assert(m); - - r = sd_rtnl_message_link_get_flags(m, &l->flags); - if (r < 0) - return r; - - (void) sd_netlink_message_read_u32(m, IFLA_MTU, &l->mtu); - (void) sd_netlink_message_read_u8(m, IFLA_OPERSTATE, &l->operstate); - - if (sd_netlink_message_read_string(m, IFLA_IFNAME, &n) >= 0) { - strncpy(l->name, n, sizeof(l->name)-1); - char_array_0(l->name); - } - - link_allocate_scopes(l); - link_add_rrs(l, false); - - return 0; -} - -static int link_update_dns_servers(Link *l) { - _cleanup_strv_free_ char **nameservers = NULL; - char **nameserver; - int r; - - assert(l); - - r = sd_network_link_get_dns(l->ifindex, &nameservers); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - dns_server_mark_all(l->dns_servers); - - STRV_FOREACH(nameserver, nameservers) { - union in_addr_union a; - DnsServer *s; - int family; - - r = in_addr_from_string_auto(*nameserver, &family, &a); - if (r < 0) - goto clear; - - s = dns_server_find(l->dns_servers, family, &a); - if (s) - dns_server_move_back_and_unmark(s); - else { - r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, family, &a); - if (r < 0) - goto clear; - } - } - - dns_server_unlink_marked(l->dns_servers); - return 0; - -clear: - dns_server_unlink_all(l->dns_servers); - return r; -} - -static int link_update_llmnr_support(Link *l) { - _cleanup_free_ char *b = NULL; - int r; - - assert(l); - - r = sd_network_link_get_llmnr(l->ifindex, &b); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - l->llmnr_support = resolve_support_from_string(b); - if (l->llmnr_support < 0) { - r = -EINVAL; - goto clear; - } - - return 0; - -clear: - l->llmnr_support = RESOLVE_SUPPORT_YES; - return r; -} - -static int link_update_mdns_support(Link *l) { - _cleanup_free_ char *b = NULL; - int r; - - assert(l); - - r = sd_network_link_get_mdns(l->ifindex, &b); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - l->mdns_support = resolve_support_from_string(b); - if (l->mdns_support < 0) { - r = -EINVAL; - goto clear; - } - - return 0; - -clear: - l->mdns_support = RESOLVE_SUPPORT_NO; - return r; -} - -void link_set_dnssec_mode(Link *l, DnssecMode mode) { - - assert(l); - - if (l->dnssec_mode == mode) - return; - - if ((l->dnssec_mode == _DNSSEC_MODE_INVALID) || - (l->dnssec_mode == DNSSEC_NO && mode != DNSSEC_NO) || - (l->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE && mode == DNSSEC_YES)) { - - /* When switching from non-DNSSEC mode to DNSSEC mode, flush the cache. Also when switching from the - * allow-downgrade mode to full DNSSEC mode, flush it too. */ - if (l->unicast_scope) - dns_cache_flush(&l->unicast_scope->cache); - } - - l->dnssec_mode = mode; -} - -static int link_update_dnssec_mode(Link *l) { - _cleanup_free_ char *m = NULL; - DnssecMode mode; - int r; - - assert(l); - - r = sd_network_link_get_dnssec(l->ifindex, &m); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - mode = dnssec_mode_from_string(m); - if (mode < 0) { - r = -EINVAL; - goto clear; - } - - link_set_dnssec_mode(l, mode); - - return 0; - -clear: - l->dnssec_mode = _DNSSEC_MODE_INVALID; - return r; -} - -static int link_update_dnssec_negative_trust_anchors(Link *l) { - _cleanup_strv_free_ char **ntas = NULL; - _cleanup_set_free_free_ Set *ns = NULL; - char **i; - int r; - - assert(l); - - r = sd_network_link_get_dnssec_negative_trust_anchors(l->ifindex, &ntas); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - ns = set_new(&dns_name_hash_ops); - if (!ns) - return -ENOMEM; - - STRV_FOREACH(i, ntas) { - r = set_put_strdup(ns, *i); - if (r < 0) - return r; - } - - set_free_free(l->dnssec_negative_trust_anchors); - l->dnssec_negative_trust_anchors = ns; - ns = NULL; - - return 0; - -clear: - l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors); - return r; -} - -static int link_update_search_domain_one(Link *l, const char *name, bool route_only) { - DnsSearchDomain *d; - int r; - - r = dns_search_domain_find(l->search_domains, name, &d); - if (r < 0) - return r; - if (r > 0) - dns_search_domain_move_back_and_unmark(d); - else { - r = dns_search_domain_new(l->manager, &d, DNS_SEARCH_DOMAIN_LINK, l, name); - if (r < 0) - return r; - } - - d->route_only = route_only; - return 0; -} - -static int link_update_search_domains(Link *l) { - _cleanup_strv_free_ char **sdomains = NULL, **rdomains = NULL; - char **i; - int r, q; - - assert(l); - - r = sd_network_link_get_search_domains(l->ifindex, &sdomains); - if (r < 0 && r != -ENODATA) - goto clear; - - q = sd_network_link_get_route_domains(l->ifindex, &rdomains); - if (q < 0 && q != -ENODATA) { - r = q; - goto clear; - } - - if (r == -ENODATA && q == -ENODATA) { - /* networkd knows nothing about this interface, and that's fine. */ - r = 0; - goto clear; - } - - dns_search_domain_mark_all(l->search_domains); - - STRV_FOREACH(i, sdomains) { - r = link_update_search_domain_one(l, *i, false); - if (r < 0) - goto clear; - } - - STRV_FOREACH(i, rdomains) { - r = link_update_search_domain_one(l, *i, true); - if (r < 0) - goto clear; - } - - dns_search_domain_unlink_marked(l->search_domains); - return 0; - -clear: - dns_search_domain_unlink_all(l->search_domains); - return r; -} - -static int link_is_unmanaged(Link *l) { - _cleanup_free_ char *state = NULL; - int r; - - assert(l); - - r = sd_network_link_get_setup_state(l->ifindex, &state); - if (r == -ENODATA) - return 1; - if (r < 0) - return r; - - return STR_IN_SET(state, "pending", "unmanaged"); -} - -static void link_read_settings(Link *l) { - int r; - - assert(l); - - /* Read settings from networkd, except when networkd is not managing this interface. */ - - r = link_is_unmanaged(l); - if (r < 0) { - log_warning_errno(r, "Failed to determine whether interface %s is managed: %m", l->name); - return; - } - if (r > 0) { - - /* If this link used to be managed, but is now unmanaged, flush all our settings — but only once. */ - if (l->is_managed) - link_flush_settings(l); - - l->is_managed = false; - return; - } - - l->is_managed = true; - - r = link_update_dns_servers(l); - if (r < 0) - log_warning_errno(r, "Failed to read DNS servers for interface %s, ignoring: %m", l->name); - - r = link_update_llmnr_support(l); - if (r < 0) - log_warning_errno(r, "Failed to read LLMNR support for interface %s, ignoring: %m", l->name); - - r = link_update_mdns_support(l); - if (r < 0) - log_warning_errno(r, "Failed to read mDNS support for interface %s, ignoring: %m", l->name); - - r = link_update_dnssec_mode(l); - if (r < 0) - log_warning_errno(r, "Failed to read DNSSEC mode for interface %s, ignoring: %m", l->name); - - r = link_update_dnssec_negative_trust_anchors(l); - if (r < 0) - log_warning_errno(r, "Failed to read DNSSEC negative trust anchors for interface %s, ignoring: %m", l->name); - - r = link_update_search_domains(l); - if (r < 0) - log_warning_errno(r, "Failed to read search domains for interface %s, ignoring: %m", l->name); -} - -int link_update_monitor(Link *l) { - assert(l); - - link_read_settings(l); - link_allocate_scopes(l); - link_add_rrs(l, false); - - return 0; -} - -bool link_relevant(Link *l, int family, bool local_multicast) { - _cleanup_free_ char *state = NULL; - LinkAddress *a; - - assert(l); - - /* A link is relevant for local multicast traffic if it isn't a loopback or pointopoint device, has a link - * beat, can do multicast and has at least one link-local (or better) IP address. - * - * A link is relevant for non-multicast traffic if it isn't a loopback device, has a link beat, and has at - * least one routable address.*/ - - if (l->flags & (IFF_LOOPBACK|IFF_DORMANT)) - return false; - - if ((l->flags & (IFF_UP|IFF_LOWER_UP)) != (IFF_UP|IFF_LOWER_UP)) - return false; - - if (local_multicast) { - if (l->flags & IFF_POINTOPOINT) - return false; - - if ((l->flags & IFF_MULTICAST) != IFF_MULTICAST) - return false; - } - - /* Check kernel operstate - * https://www.kernel.org/doc/Documentation/networking/operstates.txt */ - if (!IN_SET(l->operstate, IF_OPER_UNKNOWN, IF_OPER_UP)) - return false; - - (void) sd_network_link_get_operational_state(l->ifindex, &state); - if (state && !STR_IN_SET(state, "unknown", "degraded", "routable")) - return false; - - LIST_FOREACH(addresses, a, l->addresses) - if ((family == AF_UNSPEC || a->family == family) && link_address_relevant(a, local_multicast)) - return true; - - return false; -} - -LinkAddress *link_find_address(Link *l, int family, const union in_addr_union *in_addr) { - LinkAddress *a; - - assert(l); - - LIST_FOREACH(addresses, a, l->addresses) - if (a->family == family && in_addr_equal(family, &a->in_addr, in_addr)) - return a; - - return NULL; -} - -DnsServer* link_set_dns_server(Link *l, DnsServer *s) { - assert(l); - - if (l->current_dns_server == s) - return s; - - if (s) - log_info("Switching to DNS server %s for interface %s.", dns_server_string(s), l->name); - - dns_server_unref(l->current_dns_server); - l->current_dns_server = dns_server_ref(s); - - if (l->unicast_scope) - dns_cache_flush(&l->unicast_scope->cache); - - return s; -} - -DnsServer *link_get_dns_server(Link *l) { - assert(l); - - if (!l->current_dns_server) - link_set_dns_server(l, l->dns_servers); - - return l->current_dns_server; -} - -void link_next_dns_server(Link *l) { - assert(l); - - if (!l->current_dns_server) - return; - - /* Change to the next one, but make sure to follow the linked - * list only if this server is actually still linked. */ - if (l->current_dns_server->linked && l->current_dns_server->servers_next) { - link_set_dns_server(l, l->current_dns_server->servers_next); - return; - } - - link_set_dns_server(l, l->dns_servers); -} - -DnssecMode link_get_dnssec_mode(Link *l) { - assert(l); - - if (l->dnssec_mode != _DNSSEC_MODE_INVALID) - return l->dnssec_mode; - - return manager_get_dnssec_mode(l->manager); -} - -bool link_dnssec_supported(Link *l) { - DnsServer *server; - - assert(l); - - if (link_get_dnssec_mode(l) == DNSSEC_NO) - return false; - - server = link_get_dns_server(l); - if (server) - return dns_server_dnssec_supported(server); - - return true; -} - -int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr) { - LinkAddress *a; - - assert(l); - assert(in_addr); - - a = new0(LinkAddress, 1); - if (!a) - return -ENOMEM; - - a->family = family; - a->in_addr = *in_addr; - - a->link = l; - LIST_PREPEND(addresses, l->addresses, a); - - if (ret) - *ret = a; - - return 0; -} - -LinkAddress *link_address_free(LinkAddress *a) { - if (!a) - return NULL; - - if (a->link) { - LIST_REMOVE(addresses, a->link->addresses, a); - - if (a->llmnr_address_rr) { - if (a->family == AF_INET && a->link->llmnr_ipv4_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_address_rr); - else if (a->family == AF_INET6 && a->link->llmnr_ipv6_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_address_rr); - } - - if (a->llmnr_ptr_rr) { - if (a->family == AF_INET && a->link->llmnr_ipv4_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_ptr_rr); - else if (a->family == AF_INET6 && a->link->llmnr_ipv6_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_ptr_rr); - } - } - - dns_resource_record_unref(a->llmnr_address_rr); - dns_resource_record_unref(a->llmnr_ptr_rr); - - free(a); - return NULL; -} - -void link_address_add_rrs(LinkAddress *a, bool force_remove) { - int r; - - assert(a); - - if (a->family == AF_INET) { - - if (!force_remove && - link_address_relevant(a, true) && - a->link->llmnr_ipv4_scope && - a->link->llmnr_support == RESOLVE_SUPPORT_YES && - a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) { - - if (!a->link->manager->llmnr_host_ipv4_key) { - a->link->manager->llmnr_host_ipv4_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, a->link->manager->llmnr_hostname); - if (!a->link->manager->llmnr_host_ipv4_key) { - r = -ENOMEM; - goto fail; - } - } - - if (!a->llmnr_address_rr) { - a->llmnr_address_rr = dns_resource_record_new(a->link->manager->llmnr_host_ipv4_key); - if (!a->llmnr_address_rr) { - r = -ENOMEM; - goto fail; - } - - a->llmnr_address_rr->a.in_addr = a->in_addr.in; - a->llmnr_address_rr->ttl = LLMNR_DEFAULT_TTL; - } - - if (!a->llmnr_ptr_rr) { - r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->llmnr_hostname); - if (r < 0) - goto fail; - - a->llmnr_ptr_rr->ttl = LLMNR_DEFAULT_TTL; - } - - r = dns_zone_put(&a->link->llmnr_ipv4_scope->zone, a->link->llmnr_ipv4_scope, a->llmnr_address_rr, true); - if (r < 0) - log_warning_errno(r, "Failed to add A record to LLMNR zone: %m"); - - r = dns_zone_put(&a->link->llmnr_ipv4_scope->zone, a->link->llmnr_ipv4_scope, a->llmnr_ptr_rr, false); - if (r < 0) - log_warning_errno(r, "Failed to add IPv6 PTR record to LLMNR zone: %m"); - } else { - if (a->llmnr_address_rr) { - if (a->link->llmnr_ipv4_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_address_rr); - a->llmnr_address_rr = dns_resource_record_unref(a->llmnr_address_rr); - } - - if (a->llmnr_ptr_rr) { - if (a->link->llmnr_ipv4_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_ptr_rr); - a->llmnr_ptr_rr = dns_resource_record_unref(a->llmnr_ptr_rr); - } - } - } - - if (a->family == AF_INET6) { - - if (!force_remove && - link_address_relevant(a, true) && - a->link->llmnr_ipv6_scope && - a->link->llmnr_support == RESOLVE_SUPPORT_YES && - a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) { - - if (!a->link->manager->llmnr_host_ipv6_key) { - a->link->manager->llmnr_host_ipv6_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, a->link->manager->llmnr_hostname); - if (!a->link->manager->llmnr_host_ipv6_key) { - r = -ENOMEM; - goto fail; - } - } - - if (!a->llmnr_address_rr) { - a->llmnr_address_rr = dns_resource_record_new(a->link->manager->llmnr_host_ipv6_key); - if (!a->llmnr_address_rr) { - r = -ENOMEM; - goto fail; - } - - a->llmnr_address_rr->aaaa.in6_addr = a->in_addr.in6; - a->llmnr_address_rr->ttl = LLMNR_DEFAULT_TTL; - } - - if (!a->llmnr_ptr_rr) { - r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->llmnr_hostname); - if (r < 0) - goto fail; - - a->llmnr_ptr_rr->ttl = LLMNR_DEFAULT_TTL; - } - - r = dns_zone_put(&a->link->llmnr_ipv6_scope->zone, a->link->llmnr_ipv6_scope, a->llmnr_address_rr, true); - if (r < 0) - log_warning_errno(r, "Failed to add AAAA record to LLMNR zone: %m"); - - r = dns_zone_put(&a->link->llmnr_ipv6_scope->zone, a->link->llmnr_ipv6_scope, a->llmnr_ptr_rr, false); - if (r < 0) - log_warning_errno(r, "Failed to add IPv6 PTR record to LLMNR zone: %m"); - } else { - if (a->llmnr_address_rr) { - if (a->link->llmnr_ipv6_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_address_rr); - a->llmnr_address_rr = dns_resource_record_unref(a->llmnr_address_rr); - } - - if (a->llmnr_ptr_rr) { - if (a->link->llmnr_ipv6_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_ptr_rr); - a->llmnr_ptr_rr = dns_resource_record_unref(a->llmnr_ptr_rr); - } - } - } - - return; - -fail: - log_debug_errno(r, "Failed to update address RRs: %m"); -} - -int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m) { - int r; - assert(a); - assert(m); - - r = sd_rtnl_message_addr_get_flags(m, &a->flags); - if (r < 0) - return r; - - sd_rtnl_message_addr_get_scope(m, &a->scope); - - link_allocate_scopes(a->link); - link_add_rrs(a->link, false); - - return 0; -} - -bool link_address_relevant(LinkAddress *a, bool local_multicast) { - assert(a); - - if (a->flags & (IFA_F_DEPRECATED|IFA_F_TENTATIVE)) - return false; - - if (a->scope >= (local_multicast ? RT_SCOPE_HOST : RT_SCOPE_LINK)) - return false; - - return true; -} diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h deleted file mode 100644 index f534c12824..0000000000 --- a/src/resolve/resolved-link.h +++ /dev/null @@ -1,111 +0,0 @@ -#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 - -#include "in-addr-util.h" -#include "ratelimit.h" -#include "resolve-util.h" - -typedef struct Link Link; -typedef struct LinkAddress LinkAddress; - -#include "resolved-dns-rr.h" -#include "resolved-dns-search-domain.h" -#include "resolved-dns-server.h" -#include "resolved-manager.h" - -#define LINK_SEARCH_DOMAINS_MAX 32 -#define LINK_DNS_SERVERS_MAX 32 - -struct LinkAddress { - Link *link; - - int family; - union in_addr_union in_addr; - - unsigned char flags, scope; - - DnsResourceRecord *llmnr_address_rr; - DnsResourceRecord *llmnr_ptr_rr; - - LIST_FIELDS(LinkAddress, addresses); -}; - -struct Link { - Manager *manager; - - int ifindex; - unsigned flags; - - LIST_HEAD(LinkAddress, addresses); - - LIST_HEAD(DnsServer, dns_servers); - DnsServer *current_dns_server; - unsigned n_dns_servers; - - LIST_HEAD(DnsSearchDomain, search_domains); - unsigned n_search_domains; - - ResolveSupport llmnr_support; - ResolveSupport mdns_support; - DnssecMode dnssec_mode; - Set *dnssec_negative_trust_anchors; - - DnsScope *unicast_scope; - DnsScope *llmnr_ipv4_scope; - DnsScope *llmnr_ipv6_scope; - DnsScope *mdns_ipv4_scope; - DnsScope *mdns_ipv6_scope; - - bool is_managed; - - char name[IF_NAMESIZE]; - uint32_t mtu; - uint8_t operstate; -}; - -int link_new(Manager *m, Link **ret, int ifindex); -Link *link_free(Link *l); -int link_update_rtnl(Link *l, sd_netlink_message *m); -int link_update_monitor(Link *l); -bool link_relevant(Link *l, int family, bool local_multicast); -LinkAddress* link_find_address(Link *l, int family, const union in_addr_union *in_addr); -void link_add_rrs(Link *l, bool force_remove); - -void link_flush_settings(Link *l); -void link_set_dnssec_mode(Link *l, DnssecMode mode); -void link_allocate_scopes(Link *l); - -DnsServer* link_set_dns_server(Link *l, DnsServer *s); -DnsServer* link_get_dns_server(Link *l); -void link_next_dns_server(Link *l); - -DnssecMode link_get_dnssec_mode(Link *l); -bool link_dnssec_supported(Link *l); - -int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr); -LinkAddress *link_address_free(LinkAddress *a); -int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m); -bool link_address_relevant(LinkAddress *l, bool local_multicast); -void link_address_add_rrs(LinkAddress *a, bool force_remove); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_free); diff --git a/src/resolve/resolved-llmnr.c b/src/resolve/resolved-llmnr.c deleted file mode 100644 index 8b1d71a3eb..0000000000 --- a/src/resolve/resolved-llmnr.c +++ /dev/null @@ -1,476 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 - -#include "fd-util.h" -#include "resolved-llmnr.h" -#include "resolved-manager.h" - -void manager_llmnr_stop(Manager *m) { - assert(m); - - m->llmnr_ipv4_udp_event_source = sd_event_source_unref(m->llmnr_ipv4_udp_event_source); - m->llmnr_ipv4_udp_fd = safe_close(m->llmnr_ipv4_udp_fd); - - m->llmnr_ipv6_udp_event_source = sd_event_source_unref(m->llmnr_ipv6_udp_event_source); - m->llmnr_ipv6_udp_fd = safe_close(m->llmnr_ipv6_udp_fd); - - m->llmnr_ipv4_tcp_event_source = sd_event_source_unref(m->llmnr_ipv4_tcp_event_source); - m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd); - - m->llmnr_ipv6_tcp_event_source = sd_event_source_unref(m->llmnr_ipv6_tcp_event_source); - m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); -} - -int manager_llmnr_start(Manager *m) { - int r; - - assert(m); - - if (m->llmnr_support == RESOLVE_SUPPORT_NO) - return 0; - - r = manager_llmnr_ipv4_udp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - r = manager_llmnr_ipv4_tcp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - if (socket_ipv6_is_supported()) { - r = manager_llmnr_ipv6_udp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - r = manager_llmnr_ipv6_tcp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - } - - return 0; - -eaddrinuse: - log_warning("There appears to be another LLMNR responder running. Turning off LLMNR support."); - m->llmnr_support = RESOLVE_SUPPORT_NO; - manager_llmnr_stop(m); - - return 0; -} - -static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - DnsTransaction *t = NULL; - Manager *m = userdata; - DnsScope *scope; - int r; - - r = manager_recv(m, fd, DNS_PROTOCOL_LLMNR, &p); - if (r <= 0) - return r; - - scope = manager_find_scope(m, p); - if (!scope) { - log_warning("Got LLMNR UDP packet on unknown scope. Ignoring."); - return 0; - } - - if (dns_packet_validate_reply(p) > 0) { - log_debug("Got LLMNR reply packet for id %u", DNS_PACKET_ID(p)); - - dns_scope_check_conflicts(scope, p); - - t = hashmap_get(m->dns_transactions, UINT_TO_PTR(DNS_PACKET_ID(p))); - if (t) - dns_transaction_process_reply(t, p); - - } else if (dns_packet_validate_query(p) > 0) { - log_debug("Got LLMNR query packet for id %u", DNS_PACKET_ID(p)); - - dns_scope_process_query(scope, NULL, p); - } else - log_debug("Invalid LLMNR UDP packet, ignoring."); - - return 0; -} - -int manager_llmnr_ipv4_udp_fd(Manager *m) { - union sockaddr_union sa = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(LLMNR_PORT), - }; - static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; - int r; - - assert(m); - - if (m->llmnr_ipv4_udp_fd >= 0) - return m->llmnr_ipv4_udp_fd; - - m->llmnr_ipv4_udp_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv4_udp_fd < 0) - return -errno; - - /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* Disable Don't-Fragment bit in the IP header */ - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv4_udp_fd, &sa.sa, sizeof(sa.in)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->llmnr_ipv4_udp_event_source, m->llmnr_ipv4_udp_fd, EPOLLIN, on_llmnr_packet, m); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(m->llmnr_ipv4_udp_event_source, "llmnr-ipv4-udp"); - - return m->llmnr_ipv4_udp_fd; - -fail: - m->llmnr_ipv4_udp_fd = safe_close(m->llmnr_ipv4_udp_fd); - return r; -} - -int manager_llmnr_ipv6_udp_fd(Manager *m) { - union sockaddr_union sa = { - .in6.sin6_family = AF_INET6, - .in6.sin6_port = htobe16(LLMNR_PORT), - }; - static const int one = 1, ttl = 255; - int r; - - assert(m); - - if (m->llmnr_ipv6_udp_fd >= 0) - return m->llmnr_ipv6_udp_fd; - - m->llmnr_ipv6_udp_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv6_udp_fd < 0) - return -errno; - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv6_udp_fd, &sa.sa, sizeof(sa.in6)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->llmnr_ipv6_udp_event_source, m->llmnr_ipv6_udp_fd, EPOLLIN, on_llmnr_packet, m); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(m->llmnr_ipv6_udp_event_source, "llmnr-ipv6-udp"); - - return m->llmnr_ipv6_udp_fd; - -fail: - m->llmnr_ipv6_udp_fd = safe_close(m->llmnr_ipv6_udp_fd); - return r; -} - -static int on_llmnr_stream_packet(DnsStream *s) { - DnsScope *scope; - - assert(s); - - scope = manager_find_scope(s->manager, s->read_packet); - if (!scope) { - log_warning("Got LLMNR TCP packet on unknown scope. Ignoring."); - return 0; - } - - if (dns_packet_validate_query(s->read_packet) > 0) { - log_debug("Got query packet for id %u", DNS_PACKET_ID(s->read_packet)); - - dns_scope_process_query(scope, s, s->read_packet); - - /* If no reply packet was set, we free the stream */ - if (s->write_packet) - return 0; - } else - log_debug("Invalid LLMNR TCP packet."); - - dns_stream_free(s); - return 0; -} - -static int on_llmnr_stream(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - DnsStream *stream; - Manager *m = userdata; - int cfd, r; - - cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); - if (cfd < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return -errno; - } - - r = dns_stream_new(m, &stream, DNS_PROTOCOL_LLMNR, cfd); - if (r < 0) { - safe_close(cfd); - return r; - } - - stream->on_packet = on_llmnr_stream_packet; - return 0; -} - -int manager_llmnr_ipv4_tcp_fd(Manager *m) { - union sockaddr_union sa = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(LLMNR_PORT), - }; - static const int one = 1, pmtu = IP_PMTUDISC_DONT; - int r; - - assert(m); - - if (m->llmnr_ipv4_tcp_fd >= 0) - return m->llmnr_ipv4_tcp_fd; - - m->llmnr_ipv4_tcp_fd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv4_tcp_fd < 0) - return -errno; - - /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */ - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_TTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* Disable Don't-Fragment bit in the IP header */ - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv4_tcp_fd, &sa.sa, sizeof(sa.in)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = listen(m->llmnr_ipv4_tcp_fd, SOMAXCONN); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->llmnr_ipv4_tcp_event_source, m->llmnr_ipv4_tcp_fd, EPOLLIN, on_llmnr_stream, m); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(m->llmnr_ipv4_tcp_event_source, "llmnr-ipv4-tcp"); - - return m->llmnr_ipv4_tcp_fd; - -fail: - m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd); - return r; -} - -int manager_llmnr_ipv6_tcp_fd(Manager *m) { - union sockaddr_union sa = { - .in6.sin6_family = AF_INET6, - .in6.sin6_port = htobe16(LLMNR_PORT), - }; - static const int one = 1; - int r; - - assert(m); - - if (m->llmnr_ipv6_tcp_fd >= 0) - return m->llmnr_ipv6_tcp_fd; - - m->llmnr_ipv6_tcp_fd = socket(AF_INET6, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv6_tcp_fd < 0) - return -errno; - - /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */ - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv6_tcp_fd, &sa.sa, sizeof(sa.in6)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = listen(m->llmnr_ipv6_tcp_fd, SOMAXCONN); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->llmnr_ipv6_tcp_event_source, m->llmnr_ipv6_tcp_fd, EPOLLIN, on_llmnr_stream, m); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(m->llmnr_ipv6_tcp_event_source, "llmnr-ipv6-tcp"); - - return m->llmnr_ipv6_tcp_fd; - -fail: - m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); - return r; -} diff --git a/src/resolve/resolved-llmnr.h b/src/resolve/resolved-llmnr.h deleted file mode 100644 index 8133582fa7..0000000000 --- a/src/resolve/resolved-llmnr.h +++ /dev/null @@ -1,32 +0,0 @@ -#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 "resolved-manager.h" - -#define LLMNR_PORT 5355 - -int manager_llmnr_ipv4_udp_fd(Manager *m); -int manager_llmnr_ipv6_udp_fd(Manager *m); -int manager_llmnr_ipv4_tcp_fd(Manager *m); -int manager_llmnr_ipv6_tcp_fd(Manager *m); - -void manager_llmnr_stop(Manager *m); -int manager_llmnr_start(Manager *m); diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c deleted file mode 100644 index 7166b94d71..0000000000 --- a/src/resolve/resolved-manager.c +++ /dev/null @@ -1,1239 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 -#include - -#include "af-list.h" -#include "alloc-util.h" -#include "dns-domain.h" -#include "fd-util.h" -#include "fileio-label.h" -#include "hostname-util.h" -#include "io-util.h" -#include "netlink-util.h" -#include "network-internal.h" -#include "ordered-set.h" -#include "parse-util.h" -#include "random-util.h" -#include "resolved-bus.h" -#include "resolved-conf.h" -#include "resolved-etc-hosts.h" -#include "resolved-llmnr.h" -#include "resolved-manager.h" -#include "resolved-mdns.h" -#include "resolved-resolv-conf.h" -#include "socket-util.h" -#include "string-table.h" -#include "string-util.h" -#include "utf8.h" - -#define SEND_TIMEOUT_USEC (200 * USEC_PER_MSEC) - -static int manager_process_link(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) { - Manager *m = userdata; - uint16_t type; - Link *l; - int ifindex, r; - - assert(rtnl); - assert(m); - assert(mm); - - r = sd_netlink_message_get_type(mm, &type); - if (r < 0) - goto fail; - - r = sd_rtnl_message_link_get_ifindex(mm, &ifindex); - if (r < 0) - goto fail; - - l = hashmap_get(m->links, INT_TO_PTR(ifindex)); - - switch (type) { - - case RTM_NEWLINK:{ - bool is_new = !l; - - if (!l) { - r = link_new(m, &l, ifindex); - if (r < 0) - goto fail; - } - - r = link_update_rtnl(l, mm); - if (r < 0) - goto fail; - - r = link_update_monitor(l); - if (r < 0) - goto fail; - - if (is_new) - log_debug("Found new link %i/%s", ifindex, l->name); - - break; - } - - case RTM_DELLINK: - if (l) { - log_debug("Removing link %i/%s", l->ifindex, l->name); - link_free(l); - } - - break; - } - - return 0; - -fail: - log_warning_errno(r, "Failed to process RTNL link message: %m"); - return 0; -} - -static int manager_process_address(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) { - Manager *m = userdata; - union in_addr_union address; - uint16_t type; - int r, ifindex, family; - LinkAddress *a; - Link *l; - - assert(rtnl); - assert(mm); - assert(m); - - r = sd_netlink_message_get_type(mm, &type); - if (r < 0) - goto fail; - - r = sd_rtnl_message_addr_get_ifindex(mm, &ifindex); - if (r < 0) - goto fail; - - l = hashmap_get(m->links, INT_TO_PTR(ifindex)); - if (!l) - return 0; - - r = sd_rtnl_message_addr_get_family(mm, &family); - if (r < 0) - goto fail; - - switch (family) { - - case AF_INET: - r = sd_netlink_message_read_in_addr(mm, IFA_LOCAL, &address.in); - if (r < 0) { - r = sd_netlink_message_read_in_addr(mm, IFA_ADDRESS, &address.in); - if (r < 0) - goto fail; - } - - break; - - case AF_INET6: - r = sd_netlink_message_read_in6_addr(mm, IFA_LOCAL, &address.in6); - if (r < 0) { - r = sd_netlink_message_read_in6_addr(mm, IFA_ADDRESS, &address.in6); - if (r < 0) - goto fail; - } - - break; - - default: - return 0; - } - - a = link_find_address(l, family, &address); - - switch (type) { - - case RTM_NEWADDR: - - if (!a) { - r = link_address_new(l, &a, family, &address); - if (r < 0) - return r; - } - - r = link_address_update_rtnl(a, mm); - if (r < 0) - return r; - - break; - - case RTM_DELADDR: - link_address_free(a); - break; - } - - return 0; - -fail: - log_warning_errno(r, "Failed to process RTNL address message: %m"); - return 0; -} - -static int manager_rtnl_listen(Manager *m) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; - sd_netlink_message *i; - int r; - - assert(m); - - /* First, subscribe to interfaces coming and going */ - r = sd_netlink_open(&m->rtnl); - if (r < 0) - return r; - - r = sd_netlink_attach_event(m->rtnl, m->event, SD_EVENT_PRIORITY_IMPORTANT); - if (r < 0) - return r; - - r = sd_netlink_add_match(m->rtnl, RTM_NEWLINK, manager_process_link, m); - if (r < 0) - return r; - - r = sd_netlink_add_match(m->rtnl, RTM_DELLINK, manager_process_link, m); - if (r < 0) - return r; - - r = sd_netlink_add_match(m->rtnl, RTM_NEWADDR, manager_process_address, m); - if (r < 0) - return r; - - r = sd_netlink_add_match(m->rtnl, RTM_DELADDR, manager_process_address, m); - if (r < 0) - return r; - - /* Then, enumerate all links */ - r = sd_rtnl_message_new_link(m->rtnl, &req, RTM_GETLINK, 0); - if (r < 0) - return r; - - r = sd_netlink_message_request_dump(req, true); - if (r < 0) - return r; - - r = sd_netlink_call(m->rtnl, req, 0, &reply); - if (r < 0) - return r; - - for (i = reply; i; i = sd_netlink_message_next(i)) { - r = manager_process_link(m->rtnl, i, m); - if (r < 0) - return r; - } - - req = sd_netlink_message_unref(req); - reply = sd_netlink_message_unref(reply); - - /* Finally, enumerate all addresses, too */ - r = sd_rtnl_message_new_addr(m->rtnl, &req, RTM_GETADDR, 0, AF_UNSPEC); - if (r < 0) - return r; - - r = sd_netlink_message_request_dump(req, true); - if (r < 0) - return r; - - r = sd_netlink_call(m->rtnl, req, 0, &reply); - if (r < 0) - return r; - - for (i = reply; i; i = sd_netlink_message_next(i)) { - r = manager_process_address(m->rtnl, i, m); - if (r < 0) - return r; - } - - return r; -} - -static int on_network_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - Iterator i; - Link *l; - int r; - - assert(m); - - sd_network_monitor_flush(m->network_monitor); - - HASHMAP_FOREACH(l, m->links, i) { - r = link_update_monitor(l); - if (r < 0) - log_warning_errno(r, "Failed to update monitor information for %i: %m", l->ifindex); - } - - r = manager_write_resolv_conf(m); - if (r < 0) - log_warning_errno(r, "Could not update "PRIVATE_RESOLV_CONF": %m"); - - return 0; -} - -static int manager_network_monitor_listen(Manager *m) { - int r, fd, events; - - assert(m); - - r = sd_network_monitor_new(&m->network_monitor, NULL); - if (r < 0) - return r; - - fd = sd_network_monitor_get_fd(m->network_monitor); - if (fd < 0) - return fd; - - events = sd_network_monitor_get_events(m->network_monitor); - if (events < 0) - return events; - - r = sd_event_add_io(m->event, &m->network_event_source, fd, events, &on_network_event, m); - if (r < 0) - return r; - - r = sd_event_source_set_priority(m->network_event_source, SD_EVENT_PRIORITY_IMPORTANT+5); - if (r < 0) - return r; - - (void) sd_event_source_set_description(m->network_event_source, "network-monitor"); - - return 0; -} - -static int determine_hostname(char **llmnr_hostname, char **mdns_hostname) { - _cleanup_free_ char *h = NULL, *n = NULL; - char label[DNS_LABEL_MAX]; - const char *p; - int r, k; - - assert(llmnr_hostname); - assert(mdns_hostname); - - /* Extract and normalize the first label of the locally - * configured hostname, and check it's not "localhost". */ - - h = gethostname_malloc(); - if (!h) - return log_oom(); - - p = h; - r = dns_label_unescape(&p, label, sizeof(label)); - if (r < 0) - return log_error_errno(r, "Failed to unescape host name: %m"); - if (r == 0) { - log_error("Couldn't find a single label in hosntame."); - return -EINVAL; - } - - k = dns_label_undo_idna(label, r, label, sizeof(label)); - if (k < 0) - return log_error_errno(k, "Failed to undo IDNA: %m"); - if (k > 0) - r = k; - - if (!utf8_is_valid(label)) { - log_error("System hostname is not UTF-8 clean."); - return -EINVAL; - } - - r = dns_label_escape_new(label, r, &n); - if (r < 0) - return log_error_errno(r, "Failed to escape host name: %m"); - - if (is_localhost(n)) { - log_debug("System hostname is 'localhost', ignoring."); - return -EINVAL; - } - - r = dns_name_concat(n, "local", mdns_hostname); - if (r < 0) - return log_error_errno(r, "Failed to determine mDNS hostname: %m"); - - *llmnr_hostname = n; - n = NULL; - - return 0; -} - -static int on_hostname_change(sd_event_source *es, int fd, uint32_t revents, void *userdata) { - _cleanup_free_ char *llmnr_hostname = NULL, *mdns_hostname = NULL; - Manager *m = userdata; - int r; - - assert(m); - - r = determine_hostname(&llmnr_hostname, &mdns_hostname); - if (r < 0) - return 0; /* ignore invalid hostnames */ - - if (streq(llmnr_hostname, m->llmnr_hostname) && streq(mdns_hostname, m->mdns_hostname)) - return 0; - - log_info("System hostname changed to '%s'.", llmnr_hostname); - - free(m->llmnr_hostname); - free(m->mdns_hostname); - - m->llmnr_hostname = llmnr_hostname; - m->mdns_hostname = mdns_hostname; - - llmnr_hostname = mdns_hostname = NULL; - - manager_refresh_rrs(m); - - return 0; -} - -static int manager_watch_hostname(Manager *m) { - int r; - - assert(m); - - m->hostname_fd = open("/proc/sys/kernel/hostname", O_RDONLY|O_CLOEXEC|O_NDELAY|O_NOCTTY); - if (m->hostname_fd < 0) { - log_warning_errno(errno, "Failed to watch hostname: %m"); - return 0; - } - - r = sd_event_add_io(m->event, &m->hostname_event_source, m->hostname_fd, 0, on_hostname_change, m); - if (r < 0) { - if (r == -EPERM) - /* kernels prior to 3.2 don't support polling this file. Ignore the failure. */ - m->hostname_fd = safe_close(m->hostname_fd); - else - return log_error_errno(r, "Failed to add hostname event source: %m"); - } - - (void) sd_event_source_set_description(m->hostname_event_source, "hostname"); - - r = determine_hostname(&m->llmnr_hostname, &m->mdns_hostname); - if (r < 0) { - log_info("Defaulting to hostname 'gnu-linux'."); - m->llmnr_hostname = strdup("gnu-linux"); - if (!m->llmnr_hostname) - return log_oom(); - - m->mdns_hostname = strdup("gnu-linux.local"); - if (!m->mdns_hostname) - return log_oom(); - } else - log_info("Using system hostname '%s'.", m->llmnr_hostname); - - return 0; -} - -static int manager_sigusr1(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - _cleanup_free_ char *buffer = NULL; - _cleanup_fclose_ FILE *f = NULL; - Manager *m = userdata; - size_t size = 0; - DnsScope *scope; - - assert(s); - assert(si); - assert(m); - - f = open_memstream(&buffer, &size); - if (!f) - return log_oom(); - - LIST_FOREACH(scopes, scope, m->dns_scopes) - dns_scope_dump(scope, f); - - if (fflush_and_check(f) < 0) - return log_oom(); - - log_dump(LOG_INFO, buffer); - return 0; -} - -int manager_new(Manager **ret) { - _cleanup_(manager_freep) Manager *m = NULL; - int r; - - assert(ret); - - m = new0(Manager, 1); - if (!m) - return -ENOMEM; - - m->llmnr_ipv4_udp_fd = m->llmnr_ipv6_udp_fd = -1; - m->llmnr_ipv4_tcp_fd = m->llmnr_ipv6_tcp_fd = -1; - m->mdns_ipv4_fd = m->mdns_ipv6_fd = -1; - m->hostname_fd = -1; - - m->llmnr_support = RESOLVE_SUPPORT_YES; - m->mdns_support = RESOLVE_SUPPORT_NO; - m->dnssec_mode = DEFAULT_DNSSEC_MODE; - m->read_resolv_conf = true; - m->need_builtin_fallbacks = true; - m->etc_hosts_last = m->etc_hosts_mtime = USEC_INFINITY; - - r = dns_trust_anchor_load(&m->trust_anchor); - if (r < 0) - return r; - - r = manager_parse_config_file(m); - if (r < 0) - return r; - - r = sd_event_default(&m->event); - if (r < 0) - return r; - - sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); - sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); - - sd_event_set_watchdog(m->event, true); - - r = manager_watch_hostname(m); - if (r < 0) - return r; - - r = dns_scope_new(m, &m->unicast_scope, NULL, DNS_PROTOCOL_DNS, AF_UNSPEC); - if (r < 0) - return r; - - r = manager_network_monitor_listen(m); - if (r < 0) - return r; - - r = manager_rtnl_listen(m); - if (r < 0) - return r; - - r = manager_connect_bus(m); - if (r < 0) - return r; - - (void) sd_event_add_signal(m->event, &m->sigusr1_event_source, SIGUSR1, manager_sigusr1, m); - - *ret = m; - m = NULL; - - return 0; -} - -int manager_start(Manager *m) { - int r; - - assert(m); - - r = manager_llmnr_start(m); - if (r < 0) - return r; - - r = manager_mdns_start(m); - if (r < 0) - return r; - - return 0; -} - -Manager *manager_free(Manager *m) { - Link *l; - - if (!m) - return NULL; - - dns_server_unlink_all(m->dns_servers); - dns_server_unlink_all(m->fallback_dns_servers); - dns_search_domain_unlink_all(m->search_domains); - - while ((l = hashmap_first(m->links))) - link_free(l); - - while (m->dns_queries) - dns_query_free(m->dns_queries); - - dns_scope_free(m->unicast_scope); - - hashmap_free(m->links); - hashmap_free(m->dns_transactions); - - sd_event_source_unref(m->network_event_source); - sd_network_monitor_unref(m->network_monitor); - - sd_netlink_unref(m->rtnl); - sd_event_source_unref(m->rtnl_event_source); - - manager_llmnr_stop(m); - manager_mdns_stop(m); - - sd_bus_slot_unref(m->prepare_for_sleep_slot); - sd_event_source_unref(m->bus_retry_event_source); - sd_bus_unref(m->bus); - - sd_event_source_unref(m->sigusr1_event_source); - - sd_event_unref(m->event); - - dns_resource_key_unref(m->llmnr_host_ipv4_key); - dns_resource_key_unref(m->llmnr_host_ipv6_key); - - sd_event_source_unref(m->hostname_event_source); - safe_close(m->hostname_fd); - free(m->llmnr_hostname); - free(m->mdns_hostname); - - dns_trust_anchor_flush(&m->trust_anchor); - manager_etc_hosts_flush(m); - - free(m); - - return NULL; -} - -int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - union { - struct cmsghdr header; /* For alignment */ - uint8_t buffer[CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo)) - + CMSG_SPACE(int) /* ttl/hoplimit */ - + EXTRA_CMSG_SPACE /* kernel appears to require extra buffer space */]; - } control; - union sockaddr_union sa; - struct msghdr mh = {}; - struct cmsghdr *cmsg; - struct iovec iov; - ssize_t ms, l; - int r; - - assert(m); - assert(fd >= 0); - assert(ret); - - ms = next_datagram_size_fd(fd); - if (ms < 0) - return ms; - - r = dns_packet_new(&p, protocol, ms); - if (r < 0) - return r; - - iov.iov_base = DNS_PACKET_DATA(p); - iov.iov_len = p->allocated; - - mh.msg_name = &sa.sa; - mh.msg_namelen = sizeof(sa); - mh.msg_iov = &iov; - mh.msg_iovlen = 1; - mh.msg_control = &control; - mh.msg_controllen = sizeof(control); - - l = recvmsg(fd, &mh, 0); - if (l < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return -errno; - } - - if (l <= 0) - return -EIO; - - assert(!(mh.msg_flags & MSG_CTRUNC)); - assert(!(mh.msg_flags & MSG_TRUNC)); - - p->size = (size_t) l; - - p->family = sa.sa.sa_family; - p->ipproto = IPPROTO_UDP; - if (p->family == AF_INET) { - p->sender.in = sa.in.sin_addr; - p->sender_port = be16toh(sa.in.sin_port); - } else if (p->family == AF_INET6) { - p->sender.in6 = sa.in6.sin6_addr; - p->sender_port = be16toh(sa.in6.sin6_port); - p->ifindex = sa.in6.sin6_scope_id; - } else - return -EAFNOSUPPORT; - - CMSG_FOREACH(cmsg, &mh) { - - if (cmsg->cmsg_level == IPPROTO_IPV6) { - assert(p->family == AF_INET6); - - switch (cmsg->cmsg_type) { - - case IPV6_PKTINFO: { - struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg); - - if (p->ifindex <= 0) - p->ifindex = i->ipi6_ifindex; - - p->destination.in6 = i->ipi6_addr; - break; - } - - case IPV6_HOPLIMIT: - p->ttl = *(int *) CMSG_DATA(cmsg); - break; - - } - } else if (cmsg->cmsg_level == IPPROTO_IP) { - assert(p->family == AF_INET); - - switch (cmsg->cmsg_type) { - - case IP_PKTINFO: { - struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg); - - if (p->ifindex <= 0) - p->ifindex = i->ipi_ifindex; - - p->destination.in = i->ipi_addr; - break; - } - - case IP_TTL: - p->ttl = *(int *) CMSG_DATA(cmsg); - break; - } - } - } - - /* The Linux kernel sets the interface index to the loopback - * device if the packet came from the local host since it - * avoids the routing table in such a case. Let's unset the - * interface index in such a case. */ - if (p->ifindex == LOOPBACK_IFINDEX) - p->ifindex = 0; - - if (protocol != DNS_PROTOCOL_DNS) { - /* If we don't know the interface index still, we look for the - * first local interface with a matching address. Yuck! */ - if (p->ifindex <= 0) - p->ifindex = manager_find_ifindex(m, p->family, &p->destination); - } - - *ret = p; - p = NULL; - - return 1; -} - -static int sendmsg_loop(int fd, struct msghdr *mh, int flags) { - int r; - - assert(fd >= 0); - assert(mh); - - for (;;) { - if (sendmsg(fd, mh, flags) >= 0) - return 0; - - if (errno == EINTR) - continue; - - if (errno != EAGAIN) - return -errno; - - r = fd_wait_for_event(fd, POLLOUT, SEND_TIMEOUT_USEC); - if (r < 0) - return r; - if (r == 0) - return -ETIMEDOUT; - } -} - -static int write_loop(int fd, void *message, size_t length) { - int r; - - assert(fd >= 0); - assert(message); - - for (;;) { - if (write(fd, message, length) >= 0) - return 0; - - if (errno == EINTR) - continue; - - if (errno != EAGAIN) - return -errno; - - r = fd_wait_for_event(fd, POLLOUT, SEND_TIMEOUT_USEC); - if (r < 0) - return r; - if (r == 0) - return -ETIMEDOUT; - } -} - -int manager_write(Manager *m, int fd, DnsPacket *p) { - int r; - - log_debug("Sending %s packet with id %" PRIu16 ".", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p)); - - r = write_loop(fd, DNS_PACKET_DATA(p), p->size); - if (r < 0) - return r; - - return 0; -} - -static int manager_ipv4_send(Manager *m, int fd, int ifindex, const struct in_addr *addr, uint16_t port, DnsPacket *p) { - union sockaddr_union sa = { - .in.sin_family = AF_INET, - }; - union { - struct cmsghdr header; /* For alignment */ - uint8_t buffer[CMSG_SPACE(sizeof(struct in_pktinfo))]; - } control; - struct msghdr mh = {}; - struct iovec iov; - - assert(m); - assert(fd >= 0); - assert(addr); - assert(port > 0); - assert(p); - - iov.iov_base = DNS_PACKET_DATA(p); - iov.iov_len = p->size; - - sa.in.sin_addr = *addr; - sa.in.sin_port = htobe16(port), - - mh.msg_iov = &iov; - mh.msg_iovlen = 1; - mh.msg_name = &sa.sa; - mh.msg_namelen = sizeof(sa.in); - - if (ifindex > 0) { - struct cmsghdr *cmsg; - struct in_pktinfo *pi; - - zero(control); - - mh.msg_control = &control; - mh.msg_controllen = CMSG_LEN(sizeof(struct in_pktinfo)); - - cmsg = CMSG_FIRSTHDR(&mh); - cmsg->cmsg_len = mh.msg_controllen; - cmsg->cmsg_level = IPPROTO_IP; - cmsg->cmsg_type = IP_PKTINFO; - - pi = (struct in_pktinfo*) CMSG_DATA(cmsg); - pi->ipi_ifindex = ifindex; - } - - return sendmsg_loop(fd, &mh, 0); -} - -static int manager_ipv6_send(Manager *m, int fd, int ifindex, const struct in6_addr *addr, uint16_t port, DnsPacket *p) { - union sockaddr_union sa = { - .in6.sin6_family = AF_INET6, - }; - union { - struct cmsghdr header; /* For alignment */ - uint8_t buffer[CMSG_SPACE(sizeof(struct in6_pktinfo))]; - } control; - struct msghdr mh = {}; - struct iovec iov; - - assert(m); - assert(fd >= 0); - assert(addr); - assert(port > 0); - assert(p); - - iov.iov_base = DNS_PACKET_DATA(p); - iov.iov_len = p->size; - - sa.in6.sin6_addr = *addr; - sa.in6.sin6_port = htobe16(port), - sa.in6.sin6_scope_id = ifindex; - - mh.msg_iov = &iov; - mh.msg_iovlen = 1; - mh.msg_name = &sa.sa; - mh.msg_namelen = sizeof(sa.in6); - - if (ifindex > 0) { - struct cmsghdr *cmsg; - struct in6_pktinfo *pi; - - zero(control); - - mh.msg_control = &control; - mh.msg_controllen = CMSG_LEN(sizeof(struct in6_pktinfo)); - - cmsg = CMSG_FIRSTHDR(&mh); - cmsg->cmsg_len = mh.msg_controllen; - cmsg->cmsg_level = IPPROTO_IPV6; - cmsg->cmsg_type = IPV6_PKTINFO; - - pi = (struct in6_pktinfo*) CMSG_DATA(cmsg); - pi->ipi6_ifindex = ifindex; - } - - return sendmsg_loop(fd, &mh, 0); -} - -int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *addr, uint16_t port, DnsPacket *p) { - assert(m); - assert(fd >= 0); - assert(addr); - assert(port > 0); - assert(p); - - log_debug("Sending %s packet with id %" PRIu16 " on interface %i/%s.", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p), ifindex, af_to_name(family)); - - if (family == AF_INET) - return manager_ipv4_send(m, fd, ifindex, &addr->in, port, p); - else if (family == AF_INET6) - return manager_ipv6_send(m, fd, ifindex, &addr->in6, port, p); - - return -EAFNOSUPPORT; -} - -uint32_t manager_find_mtu(Manager *m) { - uint32_t mtu = 0; - Link *l; - Iterator i; - - /* If we don't know on which link a DNS packet would be - * delivered, let's find the largest MTU that works on all - * interfaces we know of */ - - HASHMAP_FOREACH(l, m->links, i) { - if (l->mtu <= 0) - continue; - - if (mtu <= 0 || l->mtu < mtu) - mtu = l->mtu; - } - - return mtu; -} - -int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr) { - LinkAddress *a; - - assert(m); - - a = manager_find_link_address(m, family, in_addr); - if (a) - return a->link->ifindex; - - return 0; -} - -void manager_refresh_rrs(Manager *m) { - Iterator i; - Link *l; - - assert(m); - - m->llmnr_host_ipv4_key = dns_resource_key_unref(m->llmnr_host_ipv4_key); - m->llmnr_host_ipv6_key = dns_resource_key_unref(m->llmnr_host_ipv6_key); - - HASHMAP_FOREACH(l, m->links, i) { - link_add_rrs(l, true); - link_add_rrs(l, false); - } -} - -int manager_next_hostname(Manager *m) { - const char *p; - uint64_t u, a; - char *h, *k; - int r; - - assert(m); - - p = strchr(m->llmnr_hostname, 0); - assert(p); - - while (p > m->llmnr_hostname) { - if (!strchr("0123456789", p[-1])) - break; - - p--; - } - - if (*p == 0 || safe_atou64(p, &u) < 0 || u <= 0) - u = 1; - - /* Add a random number to the old value. This way we can avoid - * that two hosts pick the same hostname, win on IPv4 and lose - * on IPv6 (or vice versa), and pick the same hostname - * replacement hostname, ad infinitum. We still want the - * numbers to go up monotonically, hence we just add a random - * value 1..10 */ - - random_bytes(&a, sizeof(a)); - u += 1 + a % 10; - - if (asprintf(&h, "%.*s%" PRIu64, (int) (p - m->llmnr_hostname), m->llmnr_hostname, u) < 0) - return -ENOMEM; - - r = dns_name_concat(h, "local", &k); - if (r < 0) { - free(h); - return r; - } - - log_info("Hostname conflict, changing published hostname from '%s' to '%s'.", m->llmnr_hostname, h); - - free(m->llmnr_hostname); - m->llmnr_hostname = h; - - free(m->mdns_hostname); - m->mdns_hostname = k; - - manager_refresh_rrs(m); - - return 0; -} - -LinkAddress* manager_find_link_address(Manager *m, int family, const union in_addr_union *in_addr) { - Iterator i; - Link *l; - - assert(m); - - HASHMAP_FOREACH(l, m->links, i) { - LinkAddress *a; - - a = link_find_address(l, family, in_addr); - if (a) - return a; - } - - return NULL; -} - -bool manager_our_packet(Manager *m, DnsPacket *p) { - assert(m); - assert(p); - - return !!manager_find_link_address(m, p->family, &p->sender); -} - -DnsScope* manager_find_scope(Manager *m, DnsPacket *p) { - Link *l; - - assert(m); - assert(p); - - l = hashmap_get(m->links, INT_TO_PTR(p->ifindex)); - if (!l) - return NULL; - - switch (p->protocol) { - case DNS_PROTOCOL_LLMNR: - if (p->family == AF_INET) - return l->llmnr_ipv4_scope; - else if (p->family == AF_INET6) - return l->llmnr_ipv6_scope; - - break; - - case DNS_PROTOCOL_MDNS: - if (p->family == AF_INET) - return l->mdns_ipv4_scope; - else if (p->family == AF_INET6) - return l->mdns_ipv6_scope; - - break; - - default: - break; - } - - return NULL; -} - -void manager_verify_all(Manager *m) { - DnsScope *s; - - assert(m); - - LIST_FOREACH(scopes, s, m->dns_scopes) - dns_zone_verify_all(&s->zone); -} - -int manager_is_own_hostname(Manager *m, const char *name) { - int r; - - assert(m); - assert(name); - - if (m->llmnr_hostname) { - r = dns_name_equal(name, m->llmnr_hostname); - if (r != 0) - return r; - } - - if (m->mdns_hostname) - return dns_name_equal(name, m->mdns_hostname); - - return 0; -} - -int manager_compile_dns_servers(Manager *m, OrderedSet **dns) { - DnsServer *s; - Iterator i; - Link *l; - int r; - - assert(m); - assert(dns); - - r = ordered_set_ensure_allocated(dns, &dns_server_hash_ops); - if (r < 0) - return r; - - /* First add the system-wide servers and domains */ - LIST_FOREACH(servers, s, m->dns_servers) { - r = ordered_set_put(*dns, s); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - - /* Then, add the per-link servers */ - HASHMAP_FOREACH(l, m->links, i) { - LIST_FOREACH(servers, s, l->dns_servers) { - r = ordered_set_put(*dns, s); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - } - - /* If we found nothing, add the fallback servers */ - if (ordered_set_isempty(*dns)) { - LIST_FOREACH(servers, s, m->fallback_dns_servers) { - r = ordered_set_put(*dns, s); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - } - - return 0; -} - -int manager_compile_search_domains(Manager *m, OrderedSet **domains) { - DnsSearchDomain *d; - Iterator i; - Link *l; - int r; - - assert(m); - assert(domains); - - r = ordered_set_ensure_allocated(domains, &dns_name_hash_ops); - if (r < 0) - return r; - - LIST_FOREACH(domains, d, m->search_domains) { - r = ordered_set_put(*domains, d->name); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - - HASHMAP_FOREACH(l, m->links, i) { - - LIST_FOREACH(domains, d, l->search_domains) { - r = ordered_set_put(*domains, d->name); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - } - - return 0; -} - -DnssecMode manager_get_dnssec_mode(Manager *m) { - assert(m); - - if (m->dnssec_mode != _DNSSEC_MODE_INVALID) - return m->dnssec_mode; - - return DNSSEC_NO; -} - -bool manager_dnssec_supported(Manager *m) { - DnsServer *server; - Iterator i; - Link *l; - - assert(m); - - if (manager_get_dnssec_mode(m) == DNSSEC_NO) - return false; - - server = manager_get_dns_server(m); - if (server && !dns_server_dnssec_supported(server)) - return false; - - HASHMAP_FOREACH(l, m->links, i) - if (!link_dnssec_supported(l)) - return false; - - return true; -} - -void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key) { - - assert(verdict >= 0); - assert(verdict < _DNSSEC_VERDICT_MAX); - - if (log_get_max_level() >= LOG_DEBUG) { - char s[DNS_RESOURCE_KEY_STRING_MAX]; - - log_debug("Found verdict for lookup %s: %s", - dns_resource_key_to_string(key, s, sizeof s), - dnssec_verdict_to_string(verdict)); - } - - m->n_dnssec_verdict[verdict]++; -} - -bool manager_routable(Manager *m, int family) { - Iterator i; - Link *l; - - assert(m); - - /* Returns true if the host has at least one interface with a routable address of the specified type */ - - HASHMAP_FOREACH(l, m->links, i) - if (link_relevant(l, family, false)) - return true; - - return false; -} diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h deleted file mode 100644 index e82a824f29..0000000000 --- a/src/resolve/resolved-manager.h +++ /dev/null @@ -1,171 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 "sd-event.h" -#include "sd-netlink.h" -#include "sd-network.h" - -#include "hashmap.h" -#include "list.h" -#include "ordered-set.h" -#include "resolve-util.h" - -typedef struct Manager Manager; - -#include "resolved-dns-query.h" -#include "resolved-dns-search-domain.h" -#include "resolved-dns-server.h" -#include "resolved-dns-stream.h" -#include "resolved-dns-trust-anchor.h" -#include "resolved-link.h" - -#define MANAGER_SEARCH_DOMAINS_MAX 32 -#define MANAGER_DNS_SERVERS_MAX 32 - -struct Manager { - sd_event *event; - - ResolveSupport llmnr_support; - ResolveSupport mdns_support; - DnssecMode dnssec_mode; - - /* Network */ - Hashmap *links; - - sd_netlink *rtnl; - sd_event_source *rtnl_event_source; - - sd_network_monitor *network_monitor; - sd_event_source *network_event_source; - - /* DNS query management */ - Hashmap *dns_transactions; - LIST_HEAD(DnsQuery, dns_queries); - unsigned n_dns_queries; - - LIST_HEAD(DnsStream, dns_streams); - unsigned n_dns_streams; - - /* Unicast dns */ - LIST_HEAD(DnsServer, dns_servers); - LIST_HEAD(DnsServer, fallback_dns_servers); - unsigned n_dns_servers; /* counts both main and fallback */ - DnsServer *current_dns_server; - - LIST_HEAD(DnsSearchDomain, search_domains); - unsigned n_search_domains; - bool permit_domain_search; - - bool need_builtin_fallbacks:1; - - bool read_resolv_conf:1; - usec_t resolv_conf_mtime; - - DnsTrustAnchor trust_anchor; - - LIST_HEAD(DnsScope, dns_scopes); - DnsScope *unicast_scope; - - /* LLMNR */ - int llmnr_ipv4_udp_fd; - int llmnr_ipv6_udp_fd; - int llmnr_ipv4_tcp_fd; - int llmnr_ipv6_tcp_fd; - - sd_event_source *llmnr_ipv4_udp_event_source; - sd_event_source *llmnr_ipv6_udp_event_source; - sd_event_source *llmnr_ipv4_tcp_event_source; - sd_event_source *llmnr_ipv6_tcp_event_source; - - /* mDNS */ - int mdns_ipv4_fd; - int mdns_ipv6_fd; - - sd_event_source *mdns_ipv4_event_source; - sd_event_source *mdns_ipv6_event_source; - - /* dbus */ - sd_bus *bus; - sd_event_source *bus_retry_event_source; - - /* The hostname we publish on LLMNR and mDNS */ - char *llmnr_hostname; - char *mdns_hostname; - DnsResourceKey *llmnr_host_ipv4_key; - DnsResourceKey *llmnr_host_ipv6_key; - - /* Watch the system hostname */ - int hostname_fd; - sd_event_source *hostname_event_source; - - /* Watch for system suspends */ - sd_bus_slot *prepare_for_sleep_slot; - - sd_event_source *sigusr1_event_source; - - unsigned n_transactions_total; - unsigned n_dnssec_verdict[_DNSSEC_VERDICT_MAX]; - - /* Data from /etc/hosts */ - Set* etc_hosts_by_address; - Hashmap* etc_hosts_by_name; - usec_t etc_hosts_last, etc_hosts_mtime; -}; - -/* Manager */ - -int manager_new(Manager **ret); -Manager* manager_free(Manager *m); - -int manager_start(Manager *m); - -uint32_t manager_find_mtu(Manager *m); - -int manager_write(Manager *m, int fd, DnsPacket *p); -int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *addr, uint16_t port, DnsPacket *p); -int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret); - -int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr); -LinkAddress* manager_find_link_address(Manager *m, int family, const union in_addr_union *in_addr); - -void manager_refresh_rrs(Manager *m); -int manager_next_hostname(Manager *m); - -bool manager_our_packet(Manager *m, DnsPacket *p); -DnsScope* manager_find_scope(Manager *m, DnsPacket *p); - -void manager_verify_all(Manager *m); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); - -#define EXTRA_CMSG_SPACE 1024 - -int manager_is_own_hostname(Manager *m, const char *name); - -int manager_compile_dns_servers(Manager *m, OrderedSet **servers); -int manager_compile_search_domains(Manager *m, OrderedSet **domains); - -DnssecMode manager_get_dnssec_mode(Manager *m); -bool manager_dnssec_supported(Manager *m); - -void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key); - -bool manager_routable(Manager *m, int family); diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c deleted file mode 100644 index b13b1d0144..0000000000 --- a/src/resolve/resolved-mdns.c +++ /dev/null @@ -1,287 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 Daniel Mack - - 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 -#include - -#include "fd-util.h" -#include "resolved-manager.h" -#include "resolved-mdns.h" - -void manager_mdns_stop(Manager *m) { - assert(m); - - m->mdns_ipv4_event_source = sd_event_source_unref(m->mdns_ipv4_event_source); - m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); - - m->mdns_ipv6_event_source = sd_event_source_unref(m->mdns_ipv6_event_source); - m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); -} - -int manager_mdns_start(Manager *m) { - int r; - - assert(m); - - if (m->mdns_support == RESOLVE_SUPPORT_NO) - return 0; - - r = manager_mdns_ipv4_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - if (socket_ipv6_is_supported()) { - r = manager_mdns_ipv6_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - } - - return 0; - -eaddrinuse: - log_warning("There appears to be another mDNS responder running. Turning off mDNS support."); - m->mdns_support = RESOLVE_SUPPORT_NO; - manager_mdns_stop(m); - - return 0; -} - -static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - Manager *m = userdata; - DnsScope *scope; - int r; - - r = manager_recv(m, fd, DNS_PROTOCOL_MDNS, &p); - if (r <= 0) - return r; - - scope = manager_find_scope(m, p); - if (!scope) { - log_warning("Got mDNS UDP packet on unknown scope. Ignoring."); - return 0; - } - - if (dns_packet_validate_reply(p) > 0) { - DnsResourceRecord *rr; - - log_debug("Got mDNS reply packet"); - - /* - * mDNS is different from regular DNS and LLMNR with regard to handling responses. - * While on other protocols, we can ignore every answer that doesn't match a question - * we broadcast earlier, RFC6762, section 18.1 recommends looking at and caching all - * incoming information, regardless of the DNS packet ID. - * - * Hence, extract the packet here, and try to find a transaction for answer the we got - * and complete it. Also store the new information in scope's cache. - */ - r = dns_packet_extract(p); - if (r < 0) { - log_debug("mDNS packet extraction failed."); - return 0; - } - - dns_scope_check_conflicts(scope, p); - - DNS_ANSWER_FOREACH(rr, p->answer) { - const char *name = dns_resource_key_name(rr->key); - DnsTransaction *t; - - /* If the received reply packet contains ANY record that is not .local or .in-addr.arpa, - * we assume someone's playing tricks on us and discard the packet completely. */ - if (!(dns_name_endswith(name, "in-addr.arpa") > 0 || - dns_name_endswith(name, "local") > 0)) - return 0; - - t = dns_scope_find_transaction(scope, rr->key, false); - if (t) - dns_transaction_process_reply(t, p); - } - - dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, (uint32_t) -1, 0, p->family, &p->sender); - - } else if (dns_packet_validate_query(p) > 0) { - log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p)); - - dns_scope_process_query(scope, NULL, p); - } else - log_debug("Invalid mDNS UDP packet."); - - return 0; -} - -int manager_mdns_ipv4_fd(Manager *m) { - union sockaddr_union sa = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(MDNS_PORT), - }; - static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; - int r; - - assert(m); - - if (m->mdns_ipv4_fd >= 0) - return m->mdns_ipv4_fd; - - m->mdns_ipv4_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->mdns_ipv4_fd < 0) - return -errno; - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* Disable Don't-Fragment bit in the IP header */ - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->mdns_ipv4_fd, &sa.sa, sizeof(sa.in)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->mdns_ipv4_event_source, m->mdns_ipv4_fd, EPOLLIN, on_mdns_packet, m); - if (r < 0) - goto fail; - - return m->mdns_ipv4_fd; - -fail: - m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); - return r; -} - -int manager_mdns_ipv6_fd(Manager *m) { - union sockaddr_union sa = { - .in6.sin6_family = AF_INET6, - .in6.sin6_port = htobe16(MDNS_PORT), - }; - static const int one = 1, ttl = 255; - int r; - - assert(m); - - if (m->mdns_ipv6_fd >= 0) - return m->mdns_ipv6_fd; - - m->mdns_ipv6_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->mdns_ipv6_fd < 0) - return -errno; - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->mdns_ipv6_fd, &sa.sa, sizeof(sa.in6)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->mdns_ipv6_event_source, m->mdns_ipv6_fd, EPOLLIN, on_mdns_packet, m); - if (r < 0) - goto fail; - - return m->mdns_ipv6_fd; - -fail: - m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); - return r; -} diff --git a/src/resolve/resolved-mdns.h b/src/resolve/resolved-mdns.h deleted file mode 100644 index 5d274648f4..0000000000 --- a/src/resolve/resolved-mdns.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 Daniel Mack - - 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 "resolved-manager.h" - -#define MDNS_PORT 5353 - -int manager_mdns_ipv4_fd(Manager *m); -int manager_mdns_ipv6_fd(Manager *m); - -void manager_mdns_stop(Manager *m); -int manager_mdns_start(Manager *m); diff --git a/src/resolve/resolved-resolv-conf.c b/src/resolve/resolved-resolv-conf.c deleted file mode 100644 index ff03acc772..0000000000 --- a/src/resolve/resolved-resolv-conf.c +++ /dev/null @@ -1,267 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 "alloc-util.h" -#include "dns-domain.h" -#include "fd-util.h" -#include "fileio-label.h" -#include "fileio.h" -#include "ordered-set.h" -#include "resolved-conf.h" -#include "resolved-resolv-conf.h" -#include "string-util.h" -#include "strv.h" - -int manager_read_resolv_conf(Manager *m) { - _cleanup_fclose_ FILE *f = NULL; - struct stat st, own; - char line[LINE_MAX]; - usec_t t; - int r; - - assert(m); - - /* Reads the system /etc/resolv.conf, if it exists and is not - * symlinked to our own resolv.conf instance */ - - if (!m->read_resolv_conf) - return 0; - - r = stat("/etc/resolv.conf", &st); - if (r < 0) { - if (errno == ENOENT) - return 0; - - r = log_warning_errno(errno, "Failed to stat /etc/resolv.conf: %m"); - goto clear; - } - - /* Have we already seen the file? */ - t = timespec_load(&st.st_mtim); - if (t == m->resolv_conf_mtime) - return 0; - - /* Is it symlinked to our own file? */ - if (stat("/run/systemd/resolve/resolv.conf", &own) >= 0 && - st.st_dev == own.st_dev && - st.st_ino == own.st_ino) - return 0; - - f = fopen("/etc/resolv.conf", "re"); - if (!f) { - if (errno == ENOENT) - return 0; - - r = log_warning_errno(errno, "Failed to open /etc/resolv.conf: %m"); - goto clear; - } - - if (fstat(fileno(f), &st) < 0) { - r = log_error_errno(errno, "Failed to stat open file: %m"); - goto clear; - } - - dns_server_mark_all(m->dns_servers); - dns_search_domain_mark_all(m->search_domains); - - FOREACH_LINE(line, f, r = -errno; goto clear) { - const char *a; - char *l; - - l = strstrip(line); - if (*l == '#' || *l == ';') - continue; - - a = first_word(l, "nameserver"); - if (a) { - r = manager_add_dns_server_by_string(m, DNS_SERVER_SYSTEM, a); - if (r < 0) - log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring.", a); - - continue; - } - - a = first_word(l, "domain"); - if (!a) /* We treat "domain" lines, and "search" lines as equivalent, and add both to our list. */ - a = first_word(l, "search"); - if (a) { - r = manager_parse_search_domains_and_warn(m, a); - if (r < 0) - log_warning_errno(r, "Failed to parse search domain string '%s', ignoring.", a); - } - } - - m->resolv_conf_mtime = t; - - /* Flush out all servers and search domains that are still - * marked. Those are then ones that didn't appear in the new - * /etc/resolv.conf */ - dns_server_unlink_marked(m->dns_servers); - dns_search_domain_unlink_marked(m->search_domains); - - /* Whenever /etc/resolv.conf changes, start using the first - * DNS server of it. This is useful to deal with broken - * network managing implementations (like NetworkManager), - * that when connecting to a VPN place both the VPN DNS - * servers and the local ones in /etc/resolv.conf. Without - * resetting the DNS server to use back to the first entry we - * will continue to use the local one thus being unable to - * resolve VPN domains. */ - manager_set_dns_server(m, m->dns_servers); - - /* Unconditionally flush the cache when /etc/resolv.conf is - * modified, even if the data it contained was completely - * identical to the previous version we used. We do this - * because altering /etc/resolv.conf is typically done when - * the network configuration changes, and that should be - * enough to flush the global unicast DNS cache. */ - if (m->unicast_scope) - dns_cache_flush(&m->unicast_scope->cache); - - return 0; - -clear: - dns_server_unlink_all(m->dns_servers); - dns_search_domain_unlink_all(m->search_domains); - return r; -} - -static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) { - assert(s); - assert(f); - assert(count); - - (void) dns_server_string(s); - - if (!s->server_string) { - log_warning("Our of memory, or invalid DNS address. Ignoring server."); - return; - } - - if (*count == MAXNS) - fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f); - (*count)++; - - fprintf(f, "nameserver %s\n", s->server_string); -} - -static void write_resolv_conf_search( - const char *domain, - FILE *f, - unsigned *count, - unsigned *length) { - - assert(domain); - assert(f); - assert(length); - - if (*count >= MAXDNSRCH || - *length + strlen(domain) > 256) { - if (*count == MAXDNSRCH) - fputs(" # Too many search domains configured, remaining ones ignored.", f); - if (*length <= 256) - fputs(" # Total length of all search domains is too long, remaining ones ignored.", f); - - return; - } - - (*length) += strlen(domain); - (*count)++; - - fputc(' ', f); - fputs(domain, f); -} - -static int write_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) { - Iterator i; - - fputs("# This file is managed by systemd-resolved(8). Do not edit.\n#\n" - "# Third party programs must not access this file directly, but\n" - "# only through the symlink at /etc/resolv.conf. To manage\n" - "# resolv.conf(5) in a different way, replace the symlink by a\n" - "# static file or a different symlink.\n\n", f); - - if (ordered_set_isempty(dns)) - fputs("# No DNS servers known.\n", f); - else { - unsigned count = 0; - DnsServer *s; - - ORDERED_SET_FOREACH(s, dns, i) - write_resolv_conf_server(s, f, &count); - } - - if (!ordered_set_isempty(domains)) { - unsigned length = 0, count = 0; - char *domain; - - fputs("search", f); - ORDERED_SET_FOREACH(domain, domains, i) - write_resolv_conf_search(domain, f, &count, &length); - fputs("\n", f); - } - - return fflush_and_check(f); -} - -int manager_write_resolv_conf(Manager *m) { - - _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *domains = NULL; - _cleanup_free_ char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(m); - - /* Read the system /etc/resolv.conf first */ - manager_read_resolv_conf(m); - - /* Add the full list to a set, to filter out duplicates */ - r = manager_compile_dns_servers(m, &dns); - if (r < 0) - return r; - - r = manager_compile_search_domains(m, &domains); - if (r < 0) - return r; - - r = fopen_temporary_label(PRIVATE_RESOLV_CONF, PRIVATE_RESOLV_CONF, &f, &temp_path); - if (r < 0) - return r; - - fchmod(fileno(f), 0644); - - r = write_resolv_conf_contents(f, dns, domains); - if (r < 0) - goto fail; - - if (rename(temp_path, PRIVATE_RESOLV_CONF) < 0) { - r = -errno; - goto fail; - } - - return 0; - -fail: - (void) unlink(PRIVATE_RESOLV_CONF); - (void) unlink(temp_path); - return r; -} diff --git a/src/resolve/resolved-resolv-conf.h b/src/resolve/resolved-resolv-conf.h deleted file mode 100644 index 75fa080e4c..0000000000 --- a/src/resolve/resolved-resolv-conf.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 "resolved-manager.h" - -#define PRIVATE_RESOLV_CONF "/run/systemd/resolve/resolv.conf" - -int manager_read_resolv_conf(Manager *m); -int manager_write_resolv_conf(Manager *m); diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c deleted file mode 100644 index 161ea03412..0000000000 --- a/src/resolve/resolved.c +++ /dev/null @@ -1,112 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 "sd-daemon.h" -#include "sd-event.h" - -#include "capability-util.h" -#include "mkdir.h" -#include "resolved-conf.h" -#include "resolved-manager.h" -#include "resolved-resolv-conf.h" -#include "selinux-util.h" -#include "signal-util.h" -#include "user-util.h" - -int main(int argc, char *argv[]) { - _cleanup_(manager_freep) Manager *m = NULL; - const char *user = "systemd-resolve"; - uid_t uid; - gid_t gid; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - if (argc != 1) { - log_error("This program takes no arguments."); - r = -EINVAL; - goto finish; - } - - umask(0022); - - r = mac_selinux_init(); - if (r < 0) { - log_error_errno(r, "SELinux setup failed: %m"); - goto finish; - } - - r = get_user_creds(&user, &uid, &gid, NULL, NULL); - if (r < 0) { - log_error_errno(r, "Cannot resolve user name %s: %m", user); - goto finish; - } - - /* Always create the directory where resolv.conf will live */ - r = mkdir_safe_label("/run/systemd/resolve", 0755, uid, gid); - if (r < 0) { - log_error_errno(r, "Could not create runtime directory: %m"); - goto finish; - } - - r = drop_privileges(uid, gid, 0); - if (r < 0) - goto finish; - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGUSR1, -1) >= 0); - - r = manager_new(&m); - if (r < 0) { - log_error_errno(r, "Could not create manager: %m"); - goto finish; - } - - r = manager_start(m); - if (r < 0) { - log_error_errno(r, "Failed to start manager: %m"); - goto finish; - } - - /* Write finish default resolv.conf to avoid a dangling - * symlink */ - r = manager_write_resolv_conf(m); - if (r < 0) - log_warning_errno(r, "Could not create "PRIVATE_RESOLV_CONF": %m"); - - sd_notify(false, - "READY=1\n" - "STATUS=Processing requests..."); - - r = sd_event_loop(m->event); - if (r < 0) { - log_error_errno(r, "Event loop failed: %m"); - goto finish; - } - - sd_event_get_exit_code(m->event, &r); - -finish: - sd_notify(false, - "STOPPING=1\n" - "STATUS=Shutting down..."); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in deleted file mode 100644 index a288588924..0000000000 --- a/src/resolve/resolved.conf.in +++ /dev/null @@ -1,19 +0,0 @@ -# This file is part of systemd. -# -# 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. -# -# Entries in this file show the compile time defaults. -# You can change settings by editing this file. -# Defaults can be restored by simply deleting this file. -# -# See resolved.conf(5) for details - -[Resolve] -#DNS= -#FallbackDNS=@DNS_SERVERS@ -#Domains= -#LLMNR=yes -#DNSSEC=@DEFAULT_DNSSEC_MODE@ diff --git a/src/resolve/test-data/_443._tcp.fedoraproject.org.pkts b/src/resolve/test-data/_443._tcp.fedoraproject.org.pkts deleted file mode 100644 index a383c6286d..0000000000 Binary files a/src/resolve/test-data/_443._tcp.fedoraproject.org.pkts and /dev/null differ diff --git a/src/resolve/test-data/_openpgpkey.fedoraproject.org.pkts b/src/resolve/test-data/_openpgpkey.fedoraproject.org.pkts deleted file mode 100644 index 15de02e997..0000000000 Binary files a/src/resolve/test-data/_openpgpkey.fedoraproject.org.pkts and /dev/null differ diff --git a/src/resolve/test-data/fake-caa.pkts b/src/resolve/test-data/fake-caa.pkts deleted file mode 100644 index 1c3ecc5491..0000000000 Binary files a/src/resolve/test-data/fake-caa.pkts and /dev/null differ diff --git a/src/resolve/test-data/fedoraproject.org.pkts b/src/resolve/test-data/fedoraproject.org.pkts deleted file mode 100644 index 17874844d9..0000000000 Binary files a/src/resolve/test-data/fedoraproject.org.pkts and /dev/null differ diff --git a/src/resolve/test-data/gandi.net.pkts b/src/resolve/test-data/gandi.net.pkts deleted file mode 100644 index 5ef51e0c8e..0000000000 Binary files a/src/resolve/test-data/gandi.net.pkts and /dev/null differ diff --git a/src/resolve/test-data/google.com.pkts b/src/resolve/test-data/google.com.pkts deleted file mode 100644 index f98c4cd855..0000000000 Binary files a/src/resolve/test-data/google.com.pkts and /dev/null differ diff --git a/src/resolve/test-data/kyhwana.org.pkts b/src/resolve/test-data/kyhwana.org.pkts deleted file mode 100644 index e28a725c9a..0000000000 Binary files a/src/resolve/test-data/kyhwana.org.pkts and /dev/null differ diff --git a/src/resolve/test-data/root.pkts b/src/resolve/test-data/root.pkts deleted file mode 100644 index 54ba668c75..0000000000 Binary files a/src/resolve/test-data/root.pkts and /dev/null differ diff --git a/src/resolve/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts b/src/resolve/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts deleted file mode 100644 index a854249532..0000000000 Binary files a/src/resolve/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts and /dev/null differ diff --git a/src/resolve/test-data/teamits.com.pkts b/src/resolve/test-data/teamits.com.pkts deleted file mode 100644 index 11deb39677..0000000000 Binary files a/src/resolve/test-data/teamits.com.pkts and /dev/null differ diff --git a/src/resolve/test-data/zbyszek@fedoraproject.org.pkts b/src/resolve/test-data/zbyszek@fedoraproject.org.pkts deleted file mode 100644 index f0a6f982df..0000000000 Binary files a/src/resolve/test-data/zbyszek@fedoraproject.org.pkts and /dev/null differ diff --git a/src/resolve/test-dns-packet.c b/src/resolve/test-dns-packet.c deleted file mode 100644 index c232a69ce1..0000000000 --- a/src/resolve/test-dns-packet.c +++ /dev/null @@ -1,114 +0,0 @@ -/*** - This file is part of systemd - - Copyright 2016 Zbigniew Jędrzejewski-Szmek - - 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 - -#include "alloc-util.h" -#include "fileio.h" -#include "glob-util.h" -#include "log.h" -#include "macro.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-rr.h" -#include "string-util.h" -#include "strv.h" - -#define HASH_KEY SD_ID128_MAKE(d3,1e,48,90,4b,fa,4c,fe,af,9d,d5,a1,d7,2e,8a,b1) - -static uint64_t hash(DnsResourceRecord *rr) { - struct siphash state; - - siphash24_init(&state, HASH_KEY.bytes); - dns_resource_record_hash_func(rr, &state); - return siphash24_finalize(&state); -} - -static void test_packet_from_file(const char* filename, bool canonical) { - _cleanup_free_ char *data = NULL; - size_t data_size, packet_size, offset; - - assert_se(read_full_file(filename, &data, &data_size) >= 0); - assert_se(data); - assert_se(data_size > 8); - - log_info("============== %s %s==============", filename, canonical ? "canonical " : ""); - - for (offset = 0; offset < data_size; offset += 8 + packet_size) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL, *p2 = NULL; - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL, *rr2 = NULL; - const char *s, *s2; - uint64_t hash1, hash2; - - packet_size = le64toh( *(uint64_t*)(data + offset) ); - assert_se(packet_size > 0); - assert_se(offset + 8 + packet_size <= data_size); - - assert_se(dns_packet_new(&p, DNS_PROTOCOL_DNS, 0) >= 0); - - assert_se(dns_packet_append_blob(p, data + offset + 8, packet_size, NULL) >= 0); - assert_se(dns_packet_read_rr(p, &rr, NULL, NULL) >= 0); - - s = dns_resource_record_to_string(rr); - assert_se(s); - puts(s); - - hash1 = hash(rr); - - assert_se(dns_resource_record_to_wire_format(rr, canonical) >= 0); - - assert_se(dns_packet_new(&p2, DNS_PROTOCOL_DNS, 0) >= 0); - assert_se(dns_packet_append_blob(p2, rr->wire_format, rr->wire_format_size, NULL) >= 0); - assert_se(dns_packet_read_rr(p2, &rr2, NULL, NULL) >= 0); - - s2 = dns_resource_record_to_string(rr); - assert_se(s2); - assert_se(streq(s, s2)); - - hash2 = hash(rr); - assert_se(hash1 == hash2); - } -} - -int main(int argc, char **argv) { - int i, N; - _cleanup_globfree_ glob_t g = {}; - char **fnames; - - log_parse_environment(); - - if (argc >= 2) { - N = argc - 1; - fnames = argv + 1; - } else { - assert_se(glob(RESOLVE_TEST_DIR "/*.pkts", GLOB_NOSORT, NULL, &g) == 0); - N = g.gl_pathc; - fnames = g.gl_pathv; - } - - for (i = 0; i < N; i++) { - test_packet_from_file(fnames[i], false); - puts(""); - test_packet_from_file(fnames[i], true); - if (i + 1 < N) - puts(""); - } - - return EXIT_SUCCESS; -} diff --git a/src/resolve/test-dnssec-complex.c b/src/resolve/test-dnssec-complex.c deleted file mode 100644 index 58c089eb40..0000000000 --- a/src/resolve/test-dnssec-complex.c +++ /dev/null @@ -1,236 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2016 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 "sd-bus.h" - -#include "af-list.h" -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "dns-type.h" -#include "random-util.h" -#include "string-util.h" -#include "time-util.h" - -#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) - -static void prefix_random(const char *name, char **ret) { - uint64_t i, u; - char *m = NULL; - - u = 1 + (random_u64() & 3); - - for (i = 0; i < u; i++) { - _cleanup_free_ char *b = NULL; - char *x; - - assert_se(asprintf(&b, "x%" PRIu64 "x", random_u64())); - x = strjoin(b, ".", name, NULL); - assert_se(x); - - free(m); - m = x; - } - - *ret = m; - } - -static void test_rr_lookup(sd_bus *bus, const char *name, uint16_t type, const char *result) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *m = NULL; - int r; - - /* If the name starts with a dot, we prefix one to three random labels */ - if (startswith(name, ".")) { - prefix_random(name + 1, &m); - name = m; - } - - assert_se(sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveRecord") >= 0); - - assert_se(sd_bus_message_append(req, "isqqt", 0, name, DNS_CLASS_IN, type, UINT64_C(0)) >= 0); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - - if (r < 0) { - assert_se(result); - assert_se(sd_bus_error_has_name(&error, result)); - log_info("[OK] %s/%s resulted in <%s>.", name, dns_type_to_string(type), error.name); - } else { - assert_se(!result); - log_info("[OK] %s/%s succeeded.", name, dns_type_to_string(type)); - } -} - -static void test_hostname_lookup(sd_bus *bus, const char *name, int family, const char *result) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *m = NULL; - const char *af; - int r; - - af = family == AF_UNSPEC ? "AF_UNSPEC" : af_to_name(family); - - /* If the name starts with a dot, we prefix one to three random labels */ - if (startswith(name, ".")) { - prefix_random(name + 1, &m); - name = m; - } - - assert_se(sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveHostname") >= 0); - - assert_se(sd_bus_message_append(req, "isit", 0, name, family, UINT64_C(0)) >= 0); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - - if (r < 0) { - assert_se(result); - assert_se(sd_bus_error_has_name(&error, result)); - log_info("[OK] %s/%s resulted in <%s>.", name, af, error.name); - } else { - assert_se(!result); - log_info("[OK] %s/%s succeeded.", name, af); - } - -} - -int main(int argc, char* argv[]) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - - /* Note that this is a manual test as it requires: - * - * Full network access - * A DNSSEC capable DNS server - * That zones contacted are still set up as they were when I wrote this. - */ - - assert_se(sd_bus_open_system(&bus) >= 0); - - /* Normally signed */ - test_rr_lookup(bus, "www.eurid.eu", DNS_TYPE_A, NULL); - test_hostname_lookup(bus, "www.eurid.eu", AF_UNSPEC, NULL); - - test_rr_lookup(bus, "sigok.verteiltesysteme.net", DNS_TYPE_A, NULL); - test_hostname_lookup(bus, "sigok.verteiltesysteme.net", AF_UNSPEC, NULL); - - /* Normally signed, NODATA */ - test_rr_lookup(bus, "www.eurid.eu", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); - test_rr_lookup(bus, "sigok.verteiltesysteme.net", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); - - /* Invalid signature */ - test_rr_lookup(bus, "sigfail.verteiltesysteme.net", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED); - test_hostname_lookup(bus, "sigfail.verteiltesysteme.net", AF_INET, BUS_ERROR_DNSSEC_FAILED); - - /* Invalid signature, RSA, wildcard */ - test_rr_lookup(bus, ".wilda.rhybar.0skar.cz", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED); - test_hostname_lookup(bus, ".wilda.rhybar.0skar.cz", AF_INET, BUS_ERROR_DNSSEC_FAILED); - - /* Invalid signature, ECDSA, wildcard */ - test_rr_lookup(bus, ".wilda.rhybar.ecdsa.0skar.cz", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED); - test_hostname_lookup(bus, ".wilda.rhybar.ecdsa.0skar.cz", AF_INET, BUS_ERROR_DNSSEC_FAILED); - - /* NXDOMAIN in NSEC domain */ - test_rr_lookup(bus, "hhh.nasa.gov", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "hhh.nasa.gov", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); - - /* wildcard, NSEC zone */ - test_rr_lookup(bus, ".wilda.nsec.0skar.cz", DNS_TYPE_A, NULL); - test_hostname_lookup(bus, ".wilda.nsec.0skar.cz", AF_INET, NULL); - - /* wildcard, NSEC zone, NODATA */ - test_rr_lookup(bus, ".wilda.nsec.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); - - /* wildcard, NSEC3 zone */ - test_rr_lookup(bus, ".wilda.0skar.cz", DNS_TYPE_A, NULL); - test_hostname_lookup(bus, ".wilda.0skar.cz", AF_INET, NULL); - - /* wildcard, NSEC3 zone, NODATA */ - test_rr_lookup(bus, ".wilda.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); - - /* wildcard, NSEC zone, CNAME */ - test_rr_lookup(bus, ".wild.nsec.0skar.cz", DNS_TYPE_A, NULL); - test_hostname_lookup(bus, ".wild.nsec.0skar.cz", AF_UNSPEC, NULL); - test_hostname_lookup(bus, ".wild.nsec.0skar.cz", AF_INET, NULL); - - /* wildcard, NSEC zone, NODATA, CNAME */ - test_rr_lookup(bus, ".wild.nsec.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); - - /* wildcard, NSEC3 zone, CNAME */ - test_rr_lookup(bus, ".wild.0skar.cz", DNS_TYPE_A, NULL); - test_hostname_lookup(bus, ".wild.0skar.cz", AF_UNSPEC, NULL); - test_hostname_lookup(bus, ".wild.0skar.cz", AF_INET, NULL); - - /* wildcard, NSEC3 zone, NODATA, CNAME */ - test_rr_lookup(bus, ".wild.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); - - /* NODATA due to empty non-terminal in NSEC domain */ - test_rr_lookup(bus, "herndon.nasa.gov", DNS_TYPE_A, BUS_ERROR_NO_SUCH_RR); - test_hostname_lookup(bus, "herndon.nasa.gov", AF_UNSPEC, BUS_ERROR_NO_SUCH_RR); - test_hostname_lookup(bus, "herndon.nasa.gov", AF_INET, BUS_ERROR_NO_SUCH_RR); - test_hostname_lookup(bus, "herndon.nasa.gov", AF_INET6, BUS_ERROR_NO_SUCH_RR); - - /* NXDOMAIN in NSEC root zone: */ - test_rr_lookup(bus, "jasdhjas.kjkfgjhfjg", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_INET, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN"); - - /* NXDOMAIN in NSEC3 .com zone: */ - test_rr_lookup(bus, "kjkfgjhfjgsdfdsfd.com", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_INET, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); - - /* Unsigned A */ - test_rr_lookup(bus, "poettering.de", DNS_TYPE_A, NULL); - test_rr_lookup(bus, "poettering.de", DNS_TYPE_AAAA, NULL); - test_hostname_lookup(bus, "poettering.de", AF_UNSPEC, NULL); - test_hostname_lookup(bus, "poettering.de", AF_INET, NULL); - test_hostname_lookup(bus, "poettering.de", AF_INET6, NULL); - -#ifdef HAVE_LIBIDN - /* Unsigned A with IDNA conversion necessary */ - test_hostname_lookup(bus, "pöttering.de", AF_UNSPEC, NULL); - test_hostname_lookup(bus, "pöttering.de", AF_INET, NULL); - test_hostname_lookup(bus, "pöttering.de", AF_INET6, NULL); -#endif - - /* DNAME, pointing to NXDOMAIN */ - test_rr_lookup(bus, ".ireallyhpoethisdoesnexist.xn--kprw13d.", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); - test_rr_lookup(bus, ".ireallyhpoethisdoesnexist.xn--kprw13d.", DNS_TYPE_RP, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_INET, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN"); - - return 0; -} diff --git a/src/resolve/test-dnssec.c b/src/resolve/test-dnssec.c deleted file mode 100644 index b3018e8239..0000000000 --- a/src/resolve/test-dnssec.c +++ /dev/null @@ -1,343 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 -#include - -#include "alloc-util.h" -#include "resolved-dns-dnssec.h" -#include "resolved-dns-rr.h" -#include "string-util.h" -#include "hexdecoct.h" - -static void test_dnssec_canonicalize_one(const char *original, const char *canonical, int r) { - char canonicalized[DNSSEC_CANONICAL_HOSTNAME_MAX]; - - assert_se(dnssec_canonicalize(original, canonicalized, sizeof(canonicalized)) == r); - if (r < 0) - return; - - assert_se(streq(canonicalized, canonical)); -} - -static void test_dnssec_canonicalize(void) { - test_dnssec_canonicalize_one("", ".", 1); - test_dnssec_canonicalize_one(".", ".", 1); - test_dnssec_canonicalize_one("foo", "foo.", 4); - test_dnssec_canonicalize_one("foo.", "foo.", 4); - test_dnssec_canonicalize_one("FOO.", "foo.", 4); - test_dnssec_canonicalize_one("FOO.bar.", "foo.bar.", 8); - test_dnssec_canonicalize_one("FOO..bar.", NULL, -EINVAL); -} - -#ifdef HAVE_GCRYPT - -static void test_dnssec_verify_dns_key(void) { - - static const uint8_t ds1_fprint[] = { - 0x46, 0x8B, 0xC8, 0xDD, 0xC7, 0xE8, 0x27, 0x03, 0x40, 0xBB, 0x8A, 0x1F, 0x3B, 0x2E, 0x45, 0x9D, - 0x80, 0x67, 0x14, 0x01, - }; - static const uint8_t ds2_fprint[] = { - 0x8A, 0xEE, 0x80, 0x47, 0x05, 0x5F, 0x83, 0xD1, 0x48, 0xBA, 0x8F, 0xF6, 0xDD, 0xA7, 0x60, 0xCE, - 0x94, 0xF7, 0xC7, 0x5E, 0x52, 0x4C, 0xF2, 0xE9, 0x50, 0xB9, 0x2E, 0xCB, 0xEF, 0x96, 0xB9, 0x98, - }; - static const uint8_t dnskey_blob[] = { - 0x03, 0x01, 0x00, 0x01, 0xa8, 0x12, 0xda, 0x4f, 0xd2, 0x7d, 0x54, 0x14, 0x0e, 0xcc, 0x5b, 0x5e, - 0x45, 0x9c, 0x96, 0x98, 0xc0, 0xc0, 0x85, 0x81, 0xb1, 0x47, 0x8c, 0x7d, 0xe8, 0x39, 0x50, 0xcc, - 0xc5, 0xd0, 0xf2, 0x00, 0x81, 0x67, 0x79, 0xf6, 0xcc, 0x9d, 0xad, 0x6c, 0xbb, 0x7b, 0x6f, 0x48, - 0x97, 0x15, 0x1c, 0xfd, 0x0b, 0xfe, 0xd3, 0xd7, 0x7d, 0x9f, 0x81, 0x26, 0xd3, 0xc5, 0x65, 0x49, - 0xcf, 0x46, 0x62, 0xb0, 0x55, 0x6e, 0x47, 0xc7, 0x30, 0xef, 0x51, 0xfb, 0x3e, 0xc6, 0xef, 0xde, - 0x27, 0x3f, 0xfa, 0x57, 0x2d, 0xa7, 0x1d, 0x80, 0x46, 0x9a, 0x5f, 0x14, 0xb3, 0xb0, 0x2c, 0xbe, - 0x72, 0xca, 0xdf, 0xb2, 0xff, 0x36, 0x5b, 0x4f, 0xec, 0x58, 0x8e, 0x8d, 0x01, 0xe9, 0xa9, 0xdf, - 0xb5, 0x60, 0xad, 0x52, 0x4d, 0xfc, 0xa9, 0x3e, 0x8d, 0x35, 0x95, 0xb3, 0x4e, 0x0f, 0xca, 0x45, - 0x1b, 0xf7, 0xef, 0x3a, 0x88, 0x25, 0x08, 0xc7, 0x4e, 0x06, 0xc1, 0x62, 0x1a, 0xce, 0xd8, 0x77, - 0xbd, 0x02, 0x65, 0xf8, 0x49, 0xfb, 0xce, 0xf6, 0xa8, 0x09, 0xfc, 0xde, 0xb2, 0x09, 0x9d, 0x39, - 0xf8, 0x63, 0x9c, 0x32, 0x42, 0x7c, 0xa0, 0x30, 0x86, 0x72, 0x7a, 0x4a, 0xc6, 0xd4, 0xb3, 0x2d, - 0x24, 0xef, 0x96, 0x3f, 0xc2, 0xda, 0xd3, 0xf2, 0x15, 0x6f, 0xda, 0x65, 0x4b, 0x81, 0x28, 0x68, - 0xf4, 0xfe, 0x3e, 0x71, 0x4f, 0x50, 0x96, 0x72, 0x58, 0xa1, 0x89, 0xdd, 0x01, 0x61, 0x39, 0x39, - 0xc6, 0x76, 0xa4, 0xda, 0x02, 0x70, 0x3d, 0xc0, 0xdc, 0x8d, 0x70, 0x72, 0x04, 0x90, 0x79, 0xd4, - 0xec, 0x65, 0xcf, 0x49, 0x35, 0x25, 0x3a, 0x14, 0x1a, 0x45, 0x20, 0xeb, 0x31, 0xaf, 0x92, 0xba, - 0x20, 0xd3, 0xcd, 0xa7, 0x13, 0x44, 0xdc, 0xcf, 0xf0, 0x27, 0x34, 0xb9, 0xe7, 0x24, 0x6f, 0x73, - 0xe7, 0xea, 0x77, 0x03, - }; - - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *dnskey = NULL, *ds1 = NULL, *ds2 = NULL; - - /* The two DS RRs in effect for nasa.gov on 2015-12-01. */ - ds1 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "nasa.gov"); - assert_se(ds1); - - ds1->ds.key_tag = 47857; - ds1->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; - ds1->ds.digest_type = DNSSEC_DIGEST_SHA1; - ds1->ds.digest_size = sizeof(ds1_fprint); - ds1->ds.digest = memdup(ds1_fprint, ds1->ds.digest_size); - assert_se(ds1->ds.digest); - - log_info("DS1: %s", strna(dns_resource_record_to_string(ds1))); - - ds2 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "NASA.GOV"); - assert_se(ds2); - - ds2->ds.key_tag = 47857; - ds2->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; - ds2->ds.digest_type = DNSSEC_DIGEST_SHA256; - ds2->ds.digest_size = sizeof(ds2_fprint); - ds2->ds.digest = memdup(ds2_fprint, ds2->ds.digest_size); - assert_se(ds2->ds.digest); - - log_info("DS2: %s", strna(dns_resource_record_to_string(ds2))); - - dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nasa.GOV"); - assert_se(dnskey); - - dnskey->dnskey.flags = 257; - dnskey->dnskey.protocol = 3; - dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; - dnskey->dnskey.key_size = sizeof(dnskey_blob); - dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); - assert_se(dnskey->dnskey.key); - - log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); - log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); - - assert_se(dnssec_verify_dnskey_by_ds(dnskey, ds1, false) > 0); - assert_se(dnssec_verify_dnskey_by_ds(dnskey, ds2, false) > 0); -} - -static void test_dnssec_verify_rrset(void) { - - static const uint8_t signature_blob[] = { - 0x7f, 0x79, 0xdd, 0x5e, 0x89, 0x79, 0x18, 0xd0, 0x34, 0x86, 0x8c, 0x72, 0x77, 0x75, 0x48, 0x4d, - 0xc3, 0x7d, 0x38, 0x04, 0xab, 0xcd, 0x9e, 0x4c, 0x82, 0xb0, 0x92, 0xca, 0xe9, 0x66, 0xe9, 0x6e, - 0x47, 0xc7, 0x68, 0x8c, 0x94, 0xf6, 0x69, 0xcb, 0x75, 0x94, 0xe6, 0x30, 0xa6, 0xfb, 0x68, 0x64, - 0x96, 0x1a, 0x84, 0xe1, 0xdc, 0x16, 0x4c, 0x83, 0x6c, 0x44, 0xf2, 0x74, 0x4d, 0x74, 0x79, 0x8f, - 0xf3, 0xf4, 0x63, 0x0d, 0xef, 0x5a, 0xe7, 0xe2, 0xfd, 0xf2, 0x2b, 0x38, 0x7c, 0x28, 0x96, 0x9d, - 0xb6, 0xcd, 0x5c, 0x3b, 0x57, 0xe2, 0x24, 0x78, 0x65, 0xd0, 0x9e, 0x77, 0x83, 0x09, 0x6c, 0xff, - 0x3d, 0x52, 0x3f, 0x6e, 0xd1, 0xed, 0x2e, 0xf9, 0xee, 0x8e, 0xa6, 0xbe, 0x9a, 0xa8, 0x87, 0x76, - 0xd8, 0x77, 0xcc, 0x96, 0xa0, 0x98, 0xa1, 0xd1, 0x68, 0x09, 0x43, 0xcf, 0x56, 0xd9, 0xd1, 0x66, - }; - - static const uint8_t dnskey_blob[] = { - 0x03, 0x01, 0x00, 0x01, 0x9b, 0x49, 0x9b, 0xc1, 0xf9, 0x9a, 0xe0, 0x4e, 0xcf, 0xcb, 0x14, 0x45, - 0x2e, 0xc9, 0xf9, 0x74, 0xa7, 0x18, 0xb5, 0xf3, 0xde, 0x39, 0x49, 0xdf, 0x63, 0x33, 0x97, 0x52, - 0xe0, 0x8e, 0xac, 0x50, 0x30, 0x8e, 0x09, 0xd5, 0x24, 0x3d, 0x26, 0xa4, 0x49, 0x37, 0x2b, 0xb0, - 0x6b, 0x1b, 0xdf, 0xde, 0x85, 0x83, 0xcb, 0x22, 0x4e, 0x60, 0x0a, 0x91, 0x1a, 0x1f, 0xc5, 0x40, - 0xb1, 0xc3, 0x15, 0xc1, 0x54, 0x77, 0x86, 0x65, 0x53, 0xec, 0x10, 0x90, 0x0c, 0x91, 0x00, 0x5e, - 0x15, 0xdc, 0x08, 0x02, 0x4c, 0x8c, 0x0d, 0xc0, 0xac, 0x6e, 0xc4, 0x3e, 0x1b, 0x80, 0x19, 0xe4, - 0xf7, 0x5f, 0x77, 0x51, 0x06, 0x87, 0x61, 0xde, 0xa2, 0x18, 0x0f, 0x40, 0x8b, 0x79, 0x72, 0xfa, - 0x8d, 0x1a, 0x44, 0x47, 0x0d, 0x8e, 0x3a, 0x2d, 0xc7, 0x39, 0xbf, 0x56, 0x28, 0x97, 0xd9, 0x20, - 0x4f, 0x00, 0x51, 0x3b, - }; - - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *a = NULL, *rrsig = NULL, *dnskey = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - DnssecResult result; - - a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "nAsA.gov"); - assert_se(a); - - a->a.in_addr.s_addr = inet_addr("52.0.14.116"); - - log_info("A: %s", strna(dns_resource_record_to_string(a))); - - rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV."); - assert_se(rrsig); - - rrsig->rrsig.type_covered = DNS_TYPE_A; - rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256; - rrsig->rrsig.labels = 2; - rrsig->rrsig.original_ttl = 600; - rrsig->rrsig.expiration = 0x5683135c; - rrsig->rrsig.inception = 0x565b7da8; - rrsig->rrsig.key_tag = 63876; - rrsig->rrsig.signer = strdup("Nasa.Gov."); - assert_se(rrsig->rrsig.signer); - rrsig->rrsig.signature_size = sizeof(signature_blob); - rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size); - assert_se(rrsig->rrsig.signature); - - log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig))); - - dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV"); - assert_se(dnskey); - - dnskey->dnskey.flags = 256; - dnskey->dnskey.protocol = 3; - dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; - dnskey->dnskey.key_size = sizeof(dnskey_blob); - dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); - assert_se(dnskey->dnskey.key); - - log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); - log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); - - assert_se(dnssec_key_match_rrsig(a->key, rrsig) > 0); - assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0); - - answer = dns_answer_new(1); - assert_se(answer); - assert_se(dns_answer_add(answer, a, 0, DNS_ANSWER_AUTHENTICATED) >= 0); - - /* Validate the RR as it if was 2015-12-2 today */ - assert_se(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, 1449092754*USEC_PER_SEC, &result) >= 0); - assert_se(result == DNSSEC_VALIDATED); -} - -static void test_dnssec_verify_rrset2(void) { - - static const uint8_t signature_blob[] = { - 0x48, 0x45, 0xc8, 0x8b, 0xc0, 0x14, 0x92, 0xf5, 0x15, 0xc6, 0x84, 0x9d, 0x2f, 0xe3, 0x32, 0x11, - 0x7d, 0xf1, 0xe6, 0x87, 0xb9, 0x42, 0xd3, 0x8b, 0x9e, 0xaf, 0x92, 0x31, 0x0a, 0x53, 0xad, 0x8b, - 0xa7, 0x5c, 0x83, 0x39, 0x8c, 0x28, 0xac, 0xce, 0x6e, 0x9c, 0x18, 0xe3, 0x31, 0x16, 0x6e, 0xca, - 0x38, 0x31, 0xaf, 0xd9, 0x94, 0xf1, 0x84, 0xb1, 0xdf, 0x5a, 0xc2, 0x73, 0x22, 0xf6, 0xcb, 0xa2, - 0xe7, 0x8c, 0x77, 0x0c, 0x74, 0x2f, 0xc2, 0x13, 0xb0, 0x93, 0x51, 0xa9, 0x4f, 0xae, 0x0a, 0xda, - 0x45, 0xcc, 0xfd, 0x43, 0x99, 0x36, 0x9a, 0x0d, 0x21, 0xe0, 0xeb, 0x30, 0x65, 0xd4, 0xa0, 0x27, - 0x37, 0x3b, 0xe4, 0xc1, 0xc5, 0xa1, 0x2a, 0xd1, 0x76, 0xc4, 0x7e, 0x64, 0x0e, 0x5a, 0xa6, 0x50, - 0x24, 0xd5, 0x2c, 0xcc, 0x6d, 0xe5, 0x37, 0xea, 0xbd, 0x09, 0x34, 0xed, 0x24, 0x06, 0xa1, 0x22, - }; - - static const uint8_t dnskey_blob[] = { - 0x03, 0x01, 0x00, 0x01, 0xc3, 0x7f, 0x1d, 0xd1, 0x1c, 0x97, 0xb1, 0x13, 0x34, 0x3a, 0x9a, 0xea, - 0xee, 0xd9, 0x5a, 0x11, 0x1b, 0x17, 0xc7, 0xe3, 0xd4, 0xda, 0x20, 0xbc, 0x5d, 0xba, 0x74, 0xe3, - 0x37, 0x99, 0xec, 0x25, 0xce, 0x93, 0x7f, 0xbd, 0x22, 0x73, 0x7e, 0x14, 0x71, 0xe0, 0x60, 0x07, - 0xd4, 0x39, 0x8b, 0x5e, 0xe9, 0xba, 0x25, 0xe8, 0x49, 0xe9, 0x34, 0xef, 0xfe, 0x04, 0x5c, 0xa5, - 0x27, 0xcd, 0xa9, 0xda, 0x70, 0x05, 0x21, 0xab, 0x15, 0x82, 0x24, 0xc3, 0x94, 0xf5, 0xd7, 0xb7, - 0xc4, 0x66, 0xcb, 0x32, 0x6e, 0x60, 0x2b, 0x55, 0x59, 0x28, 0x89, 0x8a, 0x72, 0xde, 0x88, 0x56, - 0x27, 0x95, 0xd9, 0xac, 0x88, 0x4f, 0x65, 0x2b, 0x68, 0xfc, 0xe6, 0x41, 0xc1, 0x1b, 0xef, 0x4e, - 0xd6, 0xc2, 0x0f, 0x64, 0x88, 0x95, 0x5e, 0xdd, 0x3a, 0x02, 0x07, 0x50, 0xa9, 0xda, 0xa4, 0x49, - 0x74, 0x62, 0xfe, 0xd7, - }; - - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *nsec = NULL, *rrsig = NULL, *dnskey = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - DnssecResult result; - - nsec = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC, "nasa.gov"); - assert_se(nsec); - - nsec->nsec.next_domain_name = strdup("3D-Printing.nasa.gov"); - assert_se(nsec->nsec.next_domain_name); - - nsec->nsec.types = bitmap_new(); - assert_se(nsec->nsec.types); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_A) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NS) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_SOA) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_MX) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_TXT) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_RRSIG) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NSEC) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_DNSKEY) >= 0); - assert_se(bitmap_set(nsec->nsec.types, 65534) >= 0); - - log_info("NSEC: %s", strna(dns_resource_record_to_string(nsec))); - - rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV."); - assert_se(rrsig); - - rrsig->rrsig.type_covered = DNS_TYPE_NSEC; - rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256; - rrsig->rrsig.labels = 2; - rrsig->rrsig.original_ttl = 300; - rrsig->rrsig.expiration = 0x5689002f; - rrsig->rrsig.inception = 0x56617230; - rrsig->rrsig.key_tag = 30390; - rrsig->rrsig.signer = strdup("Nasa.Gov."); - assert_se(rrsig->rrsig.signer); - rrsig->rrsig.signature_size = sizeof(signature_blob); - rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size); - assert_se(rrsig->rrsig.signature); - - log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig))); - - dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV"); - assert_se(dnskey); - - dnskey->dnskey.flags = 256; - dnskey->dnskey.protocol = 3; - dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; - dnskey->dnskey.key_size = sizeof(dnskey_blob); - dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); - assert_se(dnskey->dnskey.key); - - log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); - log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); - - assert_se(dnssec_key_match_rrsig(nsec->key, rrsig) > 0); - assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0); - - answer = dns_answer_new(1); - assert_se(answer); - assert_se(dns_answer_add(answer, nsec, 0, DNS_ANSWER_AUTHENTICATED) >= 0); - - /* Validate the RR as it if was 2015-12-11 today */ - assert_se(dnssec_verify_rrset(answer, nsec->key, rrsig, dnskey, 1449849318*USEC_PER_SEC, &result) >= 0); - assert_se(result == DNSSEC_VALIDATED); -} - -static void test_dnssec_nsec3_hash(void) { - static const uint8_t salt[] = { 0xB0, 0x1D, 0xFA, 0xCE }; - static const uint8_t next_hashed_name[] = { 0x84, 0x10, 0x26, 0x53, 0xc9, 0xfa, 0x4d, 0x85, 0x6c, 0x97, 0x82, 0xe2, 0x8f, 0xdf, 0x2d, 0x5e, 0x87, 0x69, 0xc4, 0x52 }; - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - uint8_t h[DNSSEC_HASH_SIZE_MAX]; - _cleanup_free_ char *b = NULL; - int k; - - /* The NSEC3 RR for eurid.eu on 2015-12-14. */ - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC3, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM.eurid.eu."); - assert_se(rr); - - rr->nsec3.algorithm = DNSSEC_DIGEST_SHA1; - rr->nsec3.flags = 1; - rr->nsec3.iterations = 1; - rr->nsec3.salt = memdup(salt, sizeof(salt)); - assert_se(rr->nsec3.salt); - rr->nsec3.salt_size = sizeof(salt); - rr->nsec3.next_hashed_name = memdup(next_hashed_name, sizeof(next_hashed_name)); - assert_se(rr->nsec3.next_hashed_name); - rr->nsec3.next_hashed_name_size = sizeof(next_hashed_name); - - log_info("NSEC3: %s", strna(dns_resource_record_to_string(rr))); - - k = dnssec_nsec3_hash(rr, "eurid.eu", &h); - assert_se(k >= 0); - - b = base32hexmem(h, k, false); - assert_se(b); - assert_se(strcasecmp(b, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM") == 0); -} - -#endif - -int main(int argc, char*argv[]) { - - test_dnssec_canonicalize(); - -#ifdef HAVE_GCRYPT - test_dnssec_verify_dns_key(); - test_dnssec_verify_rrset(); - test_dnssec_verify_rrset2(); - test_dnssec_nsec3_hash(); -#endif - - return 0; -} diff --git a/src/resolve/test-resolve-tables.c b/src/resolve/test-resolve-tables.c deleted file mode 100644 index 2d615130e1..0000000000 --- a/src/resolve/test-resolve-tables.c +++ /dev/null @@ -1,64 +0,0 @@ -/*** - This file is part of systemd - - Copyright 2013 Zbigniew Jędrzejewski-Szmek - - 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 "dns-type.h" -#include "test-tables.h" - -int main(int argc, char **argv) { - uint16_t i; - - test_table_sparse(dns_type, DNS_TYPE); - - log_info("/* DNS_TYPE */"); - for (i = 0; i < _DNS_TYPE_MAX; i++) { - const char *s; - - s = dns_type_to_string(i); - assert_se(s == NULL || strlen(s) < _DNS_TYPE_STRING_MAX); - - if (s) - log_info("%-*s %s%s%s%s%s%s%s%s%s", - (int) _DNS_TYPE_STRING_MAX - 1, s, - dns_type_is_pseudo(i) ? "pseudo " : "", - dns_type_is_valid_query(i) ? "valid_query " : "", - dns_type_is_valid_rr(i) ? "is_valid_rr " : "", - dns_type_may_redirect(i) ? "may_redirect " : "", - dns_type_is_dnssec(i) ? "dnssec " : "", - dns_type_is_obsolete(i) ? "obsolete " : "", - dns_type_may_wildcard(i) ? "wildcard " : "", - dns_type_apex_only(i) ? "apex_only " : "", - dns_type_needs_authentication(i) ? "needs_authentication" : ""); - } - - log_info("/* DNS_CLASS */"); - for (i = 0; i < _DNS_CLASS_MAX; i++) { - const char *s; - - s = dns_class_to_string(i); - assert_se(s == NULL || strlen(s) < _DNS_CLASS_STRING_MAX); - - if (s) - log_info("%-*s %s%s", - (int) _DNS_CLASS_STRING_MAX - 1, s, - dns_class_is_pseudo(i) ? "is_pseudo " : "", - dns_class_is_valid_rr(i) ? "is_valid_rr " : ""); - } - - return EXIT_SUCCESS; -} diff --git a/src/rfkill/Makefile b/src/rfkill/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/rfkill/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/rfkill/rfkill.c b/src/rfkill/rfkill.c deleted file mode 100644 index 0acdf229ed..0000000000 --- a/src/rfkill/rfkill.c +++ /dev/null @@ -1,426 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 - -#include "libudev.h" -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "escape.h" -#include "fd-util.h" -#include "fileio.h" -#include "io-util.h" -#include "mkdir.h" -#include "parse-util.h" -#include "proc-cmdline.h" -#include "string-table.h" -#include "string-util.h" -#include "udev-util.h" -#include "util.h" - -#define EXIT_USEC (5 * USEC_PER_SEC) - -static const char* const rfkill_type_table[NUM_RFKILL_TYPES] = { - [RFKILL_TYPE_ALL] = "all", - [RFKILL_TYPE_WLAN] = "wlan", - [RFKILL_TYPE_BLUETOOTH] = "bluetooth", - [RFKILL_TYPE_UWB] = "uwb", - [RFKILL_TYPE_WIMAX] = "wimax", - [RFKILL_TYPE_WWAN] = "wwan", - [RFKILL_TYPE_GPS] = "gps", - [RFKILL_TYPE_FM] = "fm", - [RFKILL_TYPE_NFC] = "nfc", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(rfkill_type, int); - -static int find_device( - struct udev *udev, - const struct rfkill_event *event, - struct udev_device **ret) { - - _cleanup_free_ char *sysname = NULL; - struct udev_device *device; - const char *name; - - assert(udev); - assert(event); - assert(ret); - - if (asprintf(&sysname, "rfkill%i", event->idx) < 0) - return log_oom(); - - device = udev_device_new_from_subsystem_sysname(udev, "rfkill", sysname); - if (!device) - return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open device: %m"); - - name = udev_device_get_sysattr_value(device, "name"); - if (!name) { - log_debug("Device has no name, ignoring."); - udev_device_unref(device); - return -ENOENT; - } - - log_debug("Operating on rfkill device '%s'.", name); - - *ret = device; - return 0; -} - -static int wait_for_initialized( - struct udev *udev, - struct udev_device *device, - struct udev_device **ret) { - - _cleanup_udev_monitor_unref_ struct udev_monitor *monitor = NULL; - struct udev_device *d; - const char *sysname; - int watch_fd, r; - - assert(udev); - assert(device); - assert(ret); - - if (udev_device_get_is_initialized(device) != 0) { - *ret = udev_device_ref(device); - return 0; - } - - assert_se(sysname = udev_device_get_sysname(device)); - - /* Wait until the device is initialized, so that we can get - * access to the ID_PATH property */ - - monitor = udev_monitor_new_from_netlink(udev, "udev"); - if (!monitor) - return log_error_errno(errno, "Failed to acquire monitor: %m"); - - r = udev_monitor_filter_add_match_subsystem_devtype(monitor, "rfkill", NULL); - if (r < 0) - return log_error_errno(r, "Failed to add rfkill udev match to monitor: %m"); - - r = udev_monitor_enable_receiving(monitor); - if (r < 0) - return log_error_errno(r, "Failed to enable udev receiving: %m"); - - watch_fd = udev_monitor_get_fd(monitor); - if (watch_fd < 0) - return log_error_errno(watch_fd, "Failed to get watch fd: %m"); - - /* Check again, maybe things changed */ - d = udev_device_new_from_subsystem_sysname(udev, "rfkill", sysname); - if (!d) - return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open device: %m"); - - if (udev_device_get_is_initialized(d) != 0) { - *ret = d; - return 0; - } - - for (;;) { - _cleanup_udev_device_unref_ struct udev_device *t = NULL; - - r = fd_wait_for_event(watch_fd, POLLIN, USEC_INFINITY); - if (r == -EINTR) - continue; - if (r < 0) - return log_error_errno(r, "Failed to watch udev monitor: %m"); - - t = udev_monitor_receive_device(monitor); - if (!t) - continue; - - if (streq_ptr(udev_device_get_sysname(device), sysname)) { - *ret = udev_device_ref(t); - return 0; - } - } -} - -static int determine_state_file( - struct udev *udev, - const struct rfkill_event *event, - struct udev_device *d, - char **ret) { - - _cleanup_udev_device_unref_ struct udev_device *device = NULL; - const char *path_id, *type; - char *state_file; - int r; - - assert(event); - assert(d); - assert(ret); - - r = wait_for_initialized(udev, d, &device); - if (r < 0) - return r; - - assert_se(type = rfkill_type_to_string(event->type)); - - path_id = udev_device_get_property_value(device, "ID_PATH"); - if (path_id) { - _cleanup_free_ char *escaped_path_id = NULL; - - escaped_path_id = cescape(path_id); - if (!escaped_path_id) - return log_oom(); - - state_file = strjoin("/var/lib/systemd/rfkill/", escaped_path_id, ":", type, NULL); - } else - state_file = strjoin("/var/lib/systemd/rfkill/", type, NULL); - - if (!state_file) - return log_oom(); - - *ret = state_file; - return 0; -} - -static int load_state( - int rfkill_fd, - struct udev *udev, - const struct rfkill_event *event) { - - _cleanup_udev_device_unref_ struct udev_device *device = NULL; - _cleanup_free_ char *state_file = NULL, *value = NULL; - struct rfkill_event we; - ssize_t l; - int b, r; - - assert(rfkill_fd >= 0); - assert(udev); - assert(event); - - if (shall_restore_state() == 0) - return 0; - - r = find_device(udev, event, &device); - if (r < 0) - return r; - - r = determine_state_file(udev, event, device, &state_file); - if (r < 0) - return r; - - r = read_one_line_file(state_file, &value); - if (r == -ENOENT) { - /* No state file? Then save the current state */ - - r = write_string_file(state_file, one_zero(event->soft), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); - if (r < 0) - return log_error_errno(r, "Failed to write state file %s: %m", state_file); - - log_debug("Saved state '%s' to %s.", one_zero(event->soft), state_file); - return 0; - } - if (r < 0) - return log_error_errno(r, "Failed to read state file %s: %m", state_file); - - b = parse_boolean(value); - if (b < 0) - return log_error_errno(b, "Failed to parse state file %s: %m", state_file); - - we = (struct rfkill_event) { - .op = RFKILL_OP_CHANGE, - .idx = event->idx, - .soft = b, - }; - - l = write(rfkill_fd, &we, sizeof(we)); - if (l < 0) - return log_error_errno(errno, "Failed to restore rfkill state for %i: %m", event->idx); - if (l != sizeof(we)) { - log_error("Couldn't write rfkill event structure, too short."); - return -EIO; - } - - log_debug("Loaded state '%s' from %s.", one_zero(b), state_file); - return 0; -} - -static int save_state( - int rfkill_fd, - struct udev *udev, - const struct rfkill_event *event) { - - _cleanup_udev_device_unref_ struct udev_device *device = NULL; - _cleanup_free_ char *state_file = NULL; - int r; - - assert(rfkill_fd >= 0); - assert(udev); - assert(event); - - r = find_device(udev, event, &device); - if (r < 0) - return r; - - r = determine_state_file(udev, event, device, &state_file); - if (r < 0) - return r; - - r = write_string_file(state_file, one_zero(event->soft), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); - if (r < 0) - return log_error_errno(r, "Failed to write state file %s: %m", state_file); - - log_debug("Saved state '%s' to %s.", one_zero(event->soft), state_file); - return 0; -} - -int main(int argc, char *argv[]) { - _cleanup_udev_unref_ struct udev *udev = NULL; - _cleanup_close_ int rfkill_fd = -1; - bool ready = false; - int r, n; - - if (argc > 1) { - log_error("This program requires no arguments."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - udev = udev_new(); - if (!udev) { - r = log_oom(); - goto finish; - } - - r = mkdir_p("/var/lib/systemd/rfkill", 0755); - if (r < 0) { - log_error_errno(r, "Failed to create rfkill directory: %m"); - goto finish; - } - - n = sd_listen_fds(false); - if (n < 0) { - r = log_error_errno(n, "Failed to determine whether we got any file descriptors passed: %m"); - goto finish; - } - if (n > 1) { - log_error("Got too many file descriptors."); - r = -EINVAL; - goto finish; - } - - if (n == 0) { - rfkill_fd = open("/dev/rfkill", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); - if (rfkill_fd < 0) { - if (errno == ENOENT) { - log_debug_errno(errno, "Missing rfkill subsystem, or no device present, exiting."); - r = 0; - goto finish; - } - - r = log_error_errno(errno, "Failed to open /dev/rfkill: %m"); - goto finish; - } - } else { - rfkill_fd = SD_LISTEN_FDS_START; - - r = fd_nonblock(rfkill_fd, 1); - if (r < 0) { - log_error_errno(r, "Failed to make /dev/rfkill socket non-blocking: %m"); - goto finish; - } - } - - for (;;) { - struct rfkill_event event; - const char *type; - ssize_t l; - - l = read(rfkill_fd, &event, sizeof(event)); - if (l < 0) { - if (errno == EAGAIN) { - - if (!ready) { - /* Notify manager that we are - * now finished with - * processing whatever was - * queued */ - (void) sd_notify(false, "READY=1"); - ready = true; - } - - /* Hang around for a bit, maybe there's more coming */ - - r = fd_wait_for_event(rfkill_fd, POLLIN, EXIT_USEC); - if (r == -EINTR) - continue; - if (r < 0) { - log_error_errno(r, "Failed to poll() on device: %m"); - goto finish; - } - if (r > 0) - continue; - - log_debug("All events read and idle, exiting."); - break; - } - - log_error_errno(errno, "Failed to read from /dev/rfkill: %m"); - } - - if (l != RFKILL_EVENT_SIZE_V1) { - log_error("Read event structure of invalid size."); - r = -EIO; - goto finish; - } - - type = rfkill_type_to_string(event.type); - if (!type) { - log_debug("An rfkill device of unknown type %i discovered, ignoring.", event.type); - continue; - } - - switch (event.op) { - - case RFKILL_OP_ADD: - log_debug("A new rfkill device has been added with index %i and type %s.", event.idx, type); - (void) load_state(rfkill_fd, udev, &event); - break; - - case RFKILL_OP_DEL: - log_debug("An rfkill device has been removed with index %i and type %s", event.idx, type); - break; - - case RFKILL_OP_CHANGE: - log_debug("An rfkill device has changed state with index %i and type %s", event.idx, type); - (void) save_state(rfkill_fd, udev, &event); - break; - - default: - log_debug("Unknown event %i from /dev/rfkill for index %i and type %s, ignoring.", event.op, event.idx, type); - break; - } - } - - r = 0; - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/run/Makefile b/src/run/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/run/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/run/run.c b/src/run/run.c deleted file mode 100644 index d6c9b6d37a..0000000000 --- a/src/run/run.c +++ /dev/null @@ -1,1261 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 - -#include "sd-bus.h" -#include "sd-event.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-unit-util.h" -#include "bus-util.h" -#include "calendarspec.h" -#include "env-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "ptyfwd.h" -#include "signal-util.h" -#include "spawn-polkit-agent.h" -#include "strv.h" -#include "terminal-util.h" -#include "unit-name.h" -#include "user-util.h" - -static bool arg_ask_password = true; -static bool arg_scope = false; -static bool arg_remain_after_exit = false; -static bool arg_no_block = false; -static const char *arg_unit = NULL; -static const char *arg_description = NULL; -static const char *arg_slice = NULL; -static bool arg_send_sighup = false; -static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; -static const char *arg_host = NULL; -static bool arg_user = false; -static const char *arg_service_type = NULL; -static const char *arg_exec_user = NULL; -static const char *arg_exec_group = NULL; -static int arg_nice = 0; -static bool arg_nice_set = false; -static char **arg_environment = NULL; -static char **arg_property = NULL; -static bool arg_pty = false; -static usec_t arg_on_active = 0; -static usec_t arg_on_boot = 0; -static usec_t arg_on_startup = 0; -static usec_t arg_on_unit_active = 0; -static usec_t arg_on_unit_inactive = 0; -static const char *arg_on_calendar = NULL; -static char **arg_timer_property = NULL; -static bool arg_quiet = false; - -static void polkit_agent_open_if_enabled(void) { - - /* Open the polkit agent as a child process if necessary */ - if (!arg_ask_password) - return; - - if (arg_transport != BUS_TRANSPORT_LOCAL) - return; - - polkit_agent_open(); -} - -static void help(void) { - printf("%s [OPTIONS...] {COMMAND} [ARGS...]\n\n" - "Run the specified command in a transient scope or service or timer\n" - "unit. If a timer option is specified and the unit specified with\n" - "the --unit option exists, the command can be omitted.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-ask-password Do not prompt for password\n" - " --user Run as user unit\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --scope Run this as scope rather than service\n" - " --unit=UNIT Run under the specified unit name\n" - " -p --property=NAME=VALUE Set unit property\n" - " --description=TEXT Description for unit\n" - " --slice=SLICE Run in the specified slice\n" - " --no-block Do not wait until operation finished\n" - " -r --remain-after-exit Leave service around until explicitly stopped\n" - " --send-sighup Send SIGHUP when terminating\n" - " --service-type=TYPE Service type\n" - " --uid=USER Run as system user\n" - " --gid=GROUP Run as system group\n" - " --nice=NICE Nice level\n" - " -E --setenv=NAME=VALUE Set environment\n" - " -t --pty Run service on pseudo tty\n" - " -q --quiet Suppress information messages during runtime\n\n" - "Timer options:\n\n" - " --on-active=SECONDS Run after SECONDS delay\n" - " --on-boot=SECONDS Run SECONDS after machine was booted up\n" - " --on-startup=SECONDS Run SECONDS after systemd activation\n" - " --on-unit-active=SECONDS Run SECONDS after the last activation\n" - " --on-unit-inactive=SECONDS Run SECONDS after the last deactivation\n" - " --on-calendar=SPEC Realtime timer\n" - " --timer-property=NAME=VALUE Set timer unit property\n", - program_invocation_short_name); -} - -static bool with_timer(void) { - return arg_on_active || arg_on_boot || arg_on_startup || arg_on_unit_active || arg_on_unit_inactive || arg_on_calendar; -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_USER, - ARG_SYSTEM, - ARG_SCOPE, - ARG_UNIT, - ARG_DESCRIPTION, - ARG_SLICE, - ARG_SEND_SIGHUP, - ARG_SERVICE_TYPE, - ARG_EXEC_USER, - ARG_EXEC_GROUP, - ARG_NICE, - ARG_ON_ACTIVE, - ARG_ON_BOOT, - ARG_ON_STARTUP, - ARG_ON_UNIT_ACTIVE, - ARG_ON_UNIT_INACTIVE, - ARG_ON_CALENDAR, - ARG_TIMER_PROPERTY, - ARG_NO_BLOCK, - ARG_NO_ASK_PASSWORD, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "scope", no_argument, NULL, ARG_SCOPE }, - { "unit", required_argument, NULL, ARG_UNIT }, - { "description", required_argument, NULL, ARG_DESCRIPTION }, - { "slice", required_argument, NULL, ARG_SLICE }, - { "remain-after-exit", no_argument, NULL, 'r' }, - { "send-sighup", no_argument, NULL, ARG_SEND_SIGHUP }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "service-type", required_argument, NULL, ARG_SERVICE_TYPE }, - { "uid", required_argument, NULL, ARG_EXEC_USER }, - { "gid", required_argument, NULL, ARG_EXEC_GROUP }, - { "nice", required_argument, NULL, ARG_NICE }, - { "setenv", required_argument, NULL, 'E' }, - { "property", required_argument, NULL, 'p' }, - { "tty", no_argument, NULL, 't' }, /* deprecated */ - { "pty", no_argument, NULL, 't' }, - { "quiet", no_argument, NULL, 'q' }, - { "on-active", required_argument, NULL, ARG_ON_ACTIVE }, - { "on-boot", required_argument, NULL, ARG_ON_BOOT }, - { "on-startup", required_argument, NULL, ARG_ON_STARTUP }, - { "on-unit-active", required_argument, NULL, ARG_ON_UNIT_ACTIVE }, - { "on-unit-inactive", required_argument, NULL, ARG_ON_UNIT_INACTIVE }, - { "on-calendar", required_argument, NULL, ARG_ON_CALENDAR }, - { "timer-property", required_argument, NULL, ARG_TIMER_PROPERTY }, - { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - {}, - }; - - int r, c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "+hrH:M:p:tq", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; - - case ARG_VERSION: - return version(); - - case ARG_USER: - arg_user = true; - break; - - case ARG_SYSTEM: - arg_user = false; - break; - - case ARG_SCOPE: - arg_scope = true; - break; - - case ARG_UNIT: - arg_unit = optarg; - break; - - case ARG_DESCRIPTION: - arg_description = optarg; - break; - - case ARG_SLICE: - arg_slice = optarg; - break; - - case ARG_SEND_SIGHUP: - arg_send_sighup = true; - break; - - case 'r': - arg_remain_after_exit = true; - break; - - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; - break; - - case 'M': - arg_transport = BUS_TRANSPORT_MACHINE; - arg_host = optarg; - break; - - case ARG_SERVICE_TYPE: - arg_service_type = optarg; - break; - - case ARG_EXEC_USER: - arg_exec_user = optarg; - break; - - case ARG_EXEC_GROUP: - arg_exec_group = optarg; - break; - - case ARG_NICE: - r = safe_atoi(optarg, &arg_nice); - if (r < 0 || arg_nice < PRIO_MIN || arg_nice >= PRIO_MAX) { - log_error("Failed to parse nice value"); - return -EINVAL; - } - - arg_nice_set = true; - break; - - case 'E': - if (strv_extend(&arg_environment, optarg) < 0) - return log_oom(); - - break; - - case 'p': - if (strv_extend(&arg_property, optarg) < 0) - return log_oom(); - - break; - - case 't': - arg_pty = true; - break; - - case 'q': - arg_quiet = true; - break; - - case ARG_ON_ACTIVE: - - r = parse_sec(optarg, &arg_on_active); - if (r < 0) { - log_error("Failed to parse timer value: %s", optarg); - return r; - } - - break; - - case ARG_ON_BOOT: - - r = parse_sec(optarg, &arg_on_boot); - if (r < 0) { - log_error("Failed to parse timer value: %s", optarg); - return r; - } - - break; - - case ARG_ON_STARTUP: - - r = parse_sec(optarg, &arg_on_startup); - if (r < 0) { - log_error("Failed to parse timer value: %s", optarg); - return r; - } - - break; - - case ARG_ON_UNIT_ACTIVE: - - r = parse_sec(optarg, &arg_on_unit_active); - if (r < 0) { - log_error("Failed to parse timer value: %s", optarg); - return r; - } - - break; - - case ARG_ON_UNIT_INACTIVE: - - r = parse_sec(optarg, &arg_on_unit_inactive); - if (r < 0) { - log_error("Failed to parse timer value: %s", optarg); - return r; - } - - break; - - case ARG_ON_CALENDAR: { - CalendarSpec *spec = NULL; - - r = calendar_spec_from_string(optarg, &spec); - if (r < 0) { - log_error("Invalid calendar spec: %s", optarg); - return r; - } - - calendar_spec_free(spec); - arg_on_calendar = optarg; - break; - } - - case ARG_TIMER_PROPERTY: - - if (strv_extend(&arg_timer_property, optarg) < 0) - return log_oom(); - - break; - - case ARG_NO_BLOCK: - arg_no_block = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if ((optind >= argc) && (!arg_unit || !with_timer())) { - log_error("Command line to execute required."); - return -EINVAL; - } - - if (arg_user && arg_transport != BUS_TRANSPORT_LOCAL) { - log_error("Execution in user context is not supported on non-local systems."); - return -EINVAL; - } - - if (arg_scope && arg_transport != BUS_TRANSPORT_LOCAL) { - log_error("Scope execution is not supported on non-local systems."); - return -EINVAL; - } - - if (arg_scope && (arg_remain_after_exit || arg_service_type)) { - log_error("--remain-after-exit and --service-type= are not supported in --scope mode."); - return -EINVAL; - } - - if (arg_pty && (with_timer() || arg_scope)) { - log_error("--pty is not compatible in timer or --scope mode."); - return -EINVAL; - } - - if (arg_pty && arg_transport == BUS_TRANSPORT_REMOTE) { - log_error("--pty is only supported when connecting to the local system or containers."); - return -EINVAL; - } - - if (arg_scope && with_timer()) { - log_error("Timer options are not supported in --scope mode."); - return -EINVAL; - } - - if (arg_timer_property && !with_timer()) { - log_error("--timer-property= has no effect without any other timer options."); - return -EINVAL; - } - - return 1; -} - -static int transient_unit_set_properties(sd_bus_message *m, char **properties) { - char **i; - int r; - - r = sd_bus_message_append(m, "(sv)", "Description", "s", arg_description); - if (r < 0) - return r; - - STRV_FOREACH(i, properties) { - r = bus_append_unit_property_assignment(m, *i); - if (r < 0) - return r; - } - - return 0; -} - -static int transient_cgroup_set_properties(sd_bus_message *m) { - int r; - assert(m); - - if (!isempty(arg_slice)) { - _cleanup_free_ char *slice; - - r = unit_name_mangle_with_suffix(arg_slice, UNIT_NAME_NOGLOB, ".slice", &slice); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); - if (r < 0) - return r; - } - - return 0; -} - -static int transient_kill_set_properties(sd_bus_message *m) { - assert(m); - - if (arg_send_sighup) - return sd_bus_message_append(m, "(sv)", "SendSIGHUP", "b", arg_send_sighup); - else - return 0; -} - -static int transient_service_set_properties(sd_bus_message *m, char **argv, const char *pty_path) { - int r; - - assert(m); - - r = transient_unit_set_properties(m, arg_property); - if (r < 0) - return r; - - r = transient_kill_set_properties(m); - if (r < 0) - return r; - - r = transient_cgroup_set_properties(m); - if (r < 0) - return r; - - if (arg_remain_after_exit) { - r = sd_bus_message_append(m, "(sv)", "RemainAfterExit", "b", arg_remain_after_exit); - if (r < 0) - return r; - } - - if (arg_service_type) { - r = sd_bus_message_append(m, "(sv)", "Type", "s", arg_service_type); - if (r < 0) - return r; - } - - if (arg_exec_user) { - r = sd_bus_message_append(m, "(sv)", "User", "s", arg_exec_user); - if (r < 0) - return r; - } - - if (arg_exec_group) { - r = sd_bus_message_append(m, "(sv)", "Group", "s", arg_exec_group); - if (r < 0) - return r; - } - - if (arg_nice_set) { - r = sd_bus_message_append(m, "(sv)", "Nice", "i", arg_nice); - if (r < 0) - return r; - } - - if (pty_path) { - const char *e; - - r = sd_bus_message_append(m, - "(sv)(sv)(sv)(sv)", - "StandardInput", "s", "tty", - "StandardOutput", "s", "tty", - "StandardError", "s", "tty", - "TTYPath", "s", pty_path); - if (r < 0) - return r; - - e = getenv("TERM"); - if (e) { - char *n; - - n = strjoina("TERM=", e); - r = sd_bus_message_append(m, - "(sv)", - "Environment", "as", 1, n); - if (r < 0) - return r; - } - } - - if (!strv_isempty(arg_environment)) { - r = sd_bus_message_open_container(m, 'r', "sv"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "s", "Environment"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'v', "as"); - if (r < 0) - return r; - - r = sd_bus_message_append_strv(m, arg_environment); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - } - - /* Exec container */ - { - r = sd_bus_message_open_container(m, 'r', "sv"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "s", "ExecStart"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'v', "a(sasb)"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'a', "(sasb)"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'r', "sasb"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "s", argv[0]); - if (r < 0) - return r; - - r = sd_bus_message_append_strv(m, argv); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "b", false); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - } - - return 0; -} - -static int transient_scope_set_properties(sd_bus_message *m) { - int r; - - assert(m); - - r = transient_unit_set_properties(m, arg_property); - if (r < 0) - return r; - - r = transient_kill_set_properties(m); - if (r < 0) - return r; - - r = transient_cgroup_set_properties(m); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, (uint32_t) getpid()); - if (r < 0) - return r; - - return 0; -} - -static int transient_timer_set_properties(sd_bus_message *m) { - int r; - - assert(m); - - r = transient_unit_set_properties(m, arg_timer_property); - if (r < 0) - return r; - - /* Automatically clean up our transient timers */ - r = sd_bus_message_append(m, "(sv)", "RemainAfterElapse", "b", false); - if (r < 0) - return r; - - if (arg_on_active) { - r = sd_bus_message_append(m, "(sv)", "OnActiveSec", "t", arg_on_active); - if (r < 0) - return r; - } - - if (arg_on_boot) { - r = sd_bus_message_append(m, "(sv)", "OnBootSec", "t", arg_on_boot); - if (r < 0) - return r; - } - - if (arg_on_startup) { - r = sd_bus_message_append(m, "(sv)", "OnStartupSec", "t", arg_on_startup); - if (r < 0) - return r; - } - - if (arg_on_unit_active) { - r = sd_bus_message_append(m, "(sv)", "OnUnitActiveSec", "t", arg_on_unit_active); - if (r < 0) - return r; - } - - if (arg_on_unit_inactive) { - r = sd_bus_message_append(m, "(sv)", "OnUnitInactiveSec", "t", arg_on_unit_inactive); - if (r < 0) - return r; - } - - if (arg_on_calendar) { - r = sd_bus_message_append(m, "(sv)", "OnCalendar", "s", arg_on_calendar); - if (r < 0) - return r; - } - - return 0; -} - -static int make_unit_name(sd_bus *bus, UnitType t, char **ret) { - const char *unique, *id; - char *p; - int r; - - assert(bus); - assert(t >= 0); - assert(t < _UNIT_TYPE_MAX); - - r = sd_bus_get_unique_name(bus, &unique); - if (r < 0) { - sd_id128_t rnd; - - /* We couldn't get the unique name, which is a pretty - * common case if we are connected to systemd - * directly. In that case, just pick a random uuid as - * name */ - - r = sd_id128_randomize(&rnd); - if (r < 0) - return log_error_errno(r, "Failed to generate random run unit name: %m"); - - if (asprintf(ret, "run-r" SD_ID128_FORMAT_STR ".%s", SD_ID128_FORMAT_VAL(rnd), unit_type_to_string(t)) < 0) - return log_oom(); - - return 0; - } - - /* We managed to get the unique name, then let's use that to - * name our transient units. */ - - id = startswith(unique, ":1."); - if (!id) { - log_error("Unique name %s has unexpected format.", unique); - return -EINVAL; - } - - p = strjoin("run-u", id, ".", unit_type_to_string(t), NULL); - if (!p) - return log_oom(); - - *ret = p; - return 0; -} - -static int start_transient_service( - sd_bus *bus, - char **argv) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; - _cleanup_free_ char *service = NULL, *pty_path = NULL; - _cleanup_close_ int master = -1; - int r; - - assert(bus); - assert(argv); - - if (arg_pty) { - - if (arg_transport == BUS_TRANSPORT_LOCAL) { - master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY); - if (master < 0) - return log_error_errno(errno, "Failed to acquire pseudo tty: %m"); - - r = ptsname_malloc(master, &pty_path); - if (r < 0) - return log_error_errno(r, "Failed to determine tty name: %m"); - - if (unlockpt(master) < 0) - return log_error_errno(errno, "Failed to unlock tty: %m"); - - } else if (arg_transport == BUS_TRANSPORT_MACHINE) { - _cleanup_(sd_bus_unrefp) sd_bus *system_bus = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *pty_reply = NULL; - const char *s; - - r = sd_bus_default_system(&system_bus); - if (r < 0) - return log_error_errno(r, "Failed to connect to system bus: %m"); - - r = sd_bus_call_method(system_bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "OpenMachinePTY", - &error, - &pty_reply, - "s", arg_host); - if (r < 0) { - log_error("Failed to get machine PTY: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(pty_reply, "hs", &master, &s); - if (r < 0) - return bus_log_parse_error(r); - - master = fcntl(master, F_DUPFD_CLOEXEC, 3); - if (master < 0) - return log_error_errno(errno, "Failed to duplicate master fd: %m"); - - pty_path = strdup(s); - if (!pty_path) - return log_oom(); - } else - assert_not_reached("Can't allocate tty via ssh"); - } - - if (!arg_no_block) { - r = bus_wait_for_jobs_new(bus, &w); - if (r < 0) - return log_error_errno(r, "Could not watch jobs: %m"); - } - - if (arg_unit) { - r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".service", &service); - if (r < 0) - return log_error_errno(r, "Failed to mangle unit name: %m"); - } else { - r = make_unit_name(bus, UNIT_SERVICE, &service); - if (r < 0) - return r; - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartTransientUnit"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); - if (r < 0) - return bus_log_create_error(r); - - /* Name and mode */ - r = sd_bus_message_append(m, "ss", service, "fail"); - if (r < 0) - return bus_log_create_error(r); - - /* Properties */ - r = sd_bus_message_open_container(m, 'a', "(sv)"); - if (r < 0) - return bus_log_create_error(r); - - r = transient_service_set_properties(m, argv, pty_path); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - /* Auxiliary units */ - r = sd_bus_message_append(m, "a(sa(sv))", 0); - if (r < 0) - return bus_log_create_error(r); - - polkit_agent_open_if_enabled(); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) - return log_error_errno(r, "Failed to start transient service unit: %s", bus_error_message(&error, r)); - - if (w) { - const char *object; - - r = sd_bus_message_read(reply, "o", &object); - if (r < 0) - return bus_log_parse_error(r); - - r = bus_wait_for_jobs_one(w, object, arg_quiet); - if (r < 0) - return r; - } - - if (master >= 0) { - _cleanup_(pty_forward_freep) PTYForward *forward = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - char last_char = 0; - - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); - - (void) sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); - (void) sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); - - if (!arg_quiet) - log_info("Running as unit: %s\nPress ^] three times within 1s to disconnect TTY.", service); - - r = pty_forward_new(event, master, PTY_FORWARD_IGNORE_INITIAL_VHANGUP, &forward); - if (r < 0) - return log_error_errno(r, "Failed to create PTY forwarder: %m"); - - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); - - pty_forward_get_last_char(forward, &last_char); - - forward = pty_forward_free(forward); - - if (!arg_quiet && last_char != '\n') - fputc('\n', stdout); - - } else if (!arg_quiet) - log_info("Running as unit: %s", service); - - return 0; -} - -static int start_transient_scope( - sd_bus *bus, - char **argv) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; - _cleanup_strv_free_ char **env = NULL, **user_env = NULL; - _cleanup_free_ char *scope = NULL; - const char *object = NULL; - int r; - - assert(bus); - assert(argv); - - r = bus_wait_for_jobs_new(bus, &w); - if (r < 0) - return log_oom(); - - if (arg_unit) { - r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".scope", &scope); - if (r < 0) - return log_error_errno(r, "Failed to mangle scope name: %m"); - } else { - r = make_unit_name(bus, UNIT_SCOPE, &scope); - if (r < 0) - return r; - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartTransientUnit"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); - if (r < 0) - return bus_log_create_error(r); - - /* Name and Mode */ - r = sd_bus_message_append(m, "ss", scope, "fail"); - if (r < 0) - return bus_log_create_error(r); - - /* Properties */ - r = sd_bus_message_open_container(m, 'a', "(sv)"); - if (r < 0) - return bus_log_create_error(r); - - r = transient_scope_set_properties(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - /* Auxiliary units */ - r = sd_bus_message_append(m, "a(sa(sv))", 0); - if (r < 0) - return bus_log_create_error(r); - - polkit_agent_open_if_enabled(); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed to start transient scope unit: %s", bus_error_message(&error, -r)); - return r; - } - - if (arg_nice_set) { - if (setpriority(PRIO_PROCESS, 0, arg_nice) < 0) - return log_error_errno(errno, "Failed to set nice level: %m"); - } - - if (arg_exec_group) { - gid_t gid; - - r = get_group_creds(&arg_exec_group, &gid); - if (r < 0) - return log_error_errno(r, "Failed to resolve group %s: %m", arg_exec_group); - - if (setresgid(gid, gid, gid) < 0) - return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid); - } - - if (arg_exec_user) { - const char *home, *shell; - uid_t uid; - gid_t gid; - - r = get_user_creds(&arg_exec_user, &uid, &gid, &home, &shell); - if (r < 0) - return log_error_errno(r, "Failed to resolve user %s: %m", arg_exec_user); - - r = strv_extendf(&user_env, "HOME=%s", home); - if (r < 0) - return log_oom(); - - r = strv_extendf(&user_env, "SHELL=%s", shell); - if (r < 0) - return log_oom(); - - r = strv_extendf(&user_env, "USER=%s", arg_exec_user); - if (r < 0) - return log_oom(); - - r = strv_extendf(&user_env, "LOGNAME=%s", arg_exec_user); - if (r < 0) - return log_oom(); - - if (!arg_exec_group) { - if (setresgid(gid, gid, gid) < 0) - return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid); - } - - if (setresuid(uid, uid, uid) < 0) - return log_error_errno(errno, "Failed to change UID to " UID_FMT ": %m", uid); - } - - env = strv_env_merge(3, environ, user_env, arg_environment); - if (!env) - return log_oom(); - - r = sd_bus_message_read(reply, "o", &object); - if (r < 0) - return bus_log_parse_error(r); - - r = bus_wait_for_jobs_one(w, object, arg_quiet); - if (r < 0) - return r; - - if (!arg_quiet) - log_info("Running scope as unit: %s", scope); - - execvpe(argv[0], argv, env); - - return log_error_errno(errno, "Failed to execute: %m"); -} - -static int start_transient_timer( - sd_bus *bus, - char **argv) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; - _cleanup_free_ char *timer = NULL, *service = NULL; - const char *object = NULL; - int r; - - assert(bus); - assert(argv); - - r = bus_wait_for_jobs_new(bus, &w); - if (r < 0) - return log_oom(); - - if (arg_unit) { - switch (unit_name_to_type(arg_unit)) { - - case UNIT_SERVICE: - service = strdup(arg_unit); - if (!service) - return log_oom(); - - r = unit_name_change_suffix(service, ".timer", &timer); - if (r < 0) - return log_error_errno(r, "Failed to change unit suffix: %m"); - break; - - case UNIT_TIMER: - timer = strdup(arg_unit); - if (!timer) - return log_oom(); - - r = unit_name_change_suffix(timer, ".service", &service); - if (r < 0) - return log_error_errno(r, "Failed to change unit suffix: %m"); - break; - - default: - r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".service", &service); - if (r < 0) - return log_error_errno(r, "Failed to mangle unit name: %m"); - - r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".timer", &timer); - if (r < 0) - return log_error_errno(r, "Failed to mangle unit name: %m"); - - break; - } - } else { - r = make_unit_name(bus, UNIT_SERVICE, &service); - if (r < 0) - return r; - - r = unit_name_change_suffix(service, ".timer", &timer); - if (r < 0) - return log_error_errno(r, "Failed to change unit suffix: %m"); - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartTransientUnit"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); - if (r < 0) - return bus_log_create_error(r); - - /* Name and Mode */ - r = sd_bus_message_append(m, "ss", timer, "fail"); - if (r < 0) - return bus_log_create_error(r); - - /* Properties */ - r = sd_bus_message_open_container(m, 'a', "(sv)"); - if (r < 0) - return bus_log_create_error(r); - - r = transient_timer_set_properties(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'a', "(sa(sv))"); - if (r < 0) - return bus_log_create_error(r); - - if (argv[0]) { - r = sd_bus_message_open_container(m, 'r', "sa(sv)"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", service); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'a', "(sv)"); - if (r < 0) - return bus_log_create_error(r); - - r = transient_service_set_properties(m, argv, NULL); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - polkit_agent_open_if_enabled(); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed to start transient timer unit: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "o", &object); - if (r < 0) - return bus_log_parse_error(r); - - r = bus_wait_for_jobs_one(w, object, arg_quiet); - if (r < 0) - return r; - - log_info("Running timer as unit: %s", timer); - if (argv[0]) - log_info("Will run service as unit: %s", service); - - return 0; -} - -int main(int argc, char* argv[]) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_free_ char *description = NULL, *command = NULL; - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (argc > optind && arg_transport == BUS_TRANSPORT_LOCAL) { - /* Patch in an absolute path */ - - r = find_binary(argv[optind], &command); - if (r < 0) { - log_error_errno(r, "Failed to find executable %s: %m", argv[optind]); - goto finish; - } - - argv[optind] = command; - } - - if (!arg_description) { - description = strv_join(argv + optind, " "); - if (!description) { - r = log_oom(); - goto finish; - } - - if (arg_unit && isempty(description)) { - r = free_and_strdup(&description, arg_unit); - if (r < 0) - goto finish; - } - - arg_description = description; - } - - r = bus_connect_transport_systemd(arg_transport, arg_host, arg_user, &bus); - if (r < 0) { - log_error_errno(r, "Failed to create bus connection: %m"); - goto finish; - } - - if (arg_scope) - r = start_transient_scope(bus, argv + optind); - else if (with_timer()) - r = start_transient_timer(bus, argv + optind); - else - r = start_transient_service(bus, argv + optind); - -finish: - strv_free(arg_environment); - strv_free(arg_property); - strv_free(arg_timer_property); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/shared/Makefile b/src/shared/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/shared/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/shared/acl-util.c b/src/shared/acl-util.c deleted file mode 100644 index 2aa951fce9..0000000000 --- a/src/shared/acl-util.c +++ /dev/null @@ -1,429 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011,2013 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 - -#include "acl-util.h" -#include "alloc-util.h" -#include "string-util.h" -#include "strv.h" -#include "user-util.h" -#include "util.h" - -int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry) { - acl_entry_t i; - int r; - - assert(acl); - assert(entry); - - for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i); - r > 0; - r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) { - - acl_tag_t tag; - uid_t *u; - bool b; - - if (acl_get_tag_type(i, &tag) < 0) - return -errno; - - if (tag != ACL_USER) - continue; - - u = acl_get_qualifier(i); - if (!u) - return -errno; - - b = *u == uid; - acl_free(u); - - if (b) { - *entry = i; - return 1; - } - } - if (r < 0) - return -errno; - - return 0; -} - -int calc_acl_mask_if_needed(acl_t *acl_p) { - acl_entry_t i; - int r; - bool need = false; - - assert(acl_p); - - for (r = acl_get_entry(*acl_p, ACL_FIRST_ENTRY, &i); - r > 0; - r = acl_get_entry(*acl_p, ACL_NEXT_ENTRY, &i)) { - acl_tag_t tag; - - if (acl_get_tag_type(i, &tag) < 0) - return -errno; - - if (tag == ACL_MASK) - return 0; - - if (IN_SET(tag, ACL_USER, ACL_GROUP)) - need = true; - } - if (r < 0) - return -errno; - - if (need && acl_calc_mask(acl_p) < 0) - return -errno; - - return need; -} - -int add_base_acls_if_needed(acl_t *acl_p, const char *path) { - acl_entry_t i; - int r; - bool have_user_obj = false, have_group_obj = false, have_other = false; - struct stat st; - _cleanup_(acl_freep) acl_t basic = NULL; - - assert(acl_p); - - for (r = acl_get_entry(*acl_p, ACL_FIRST_ENTRY, &i); - r > 0; - r = acl_get_entry(*acl_p, ACL_NEXT_ENTRY, &i)) { - acl_tag_t tag; - - if (acl_get_tag_type(i, &tag) < 0) - return -errno; - - if (tag == ACL_USER_OBJ) - have_user_obj = true; - else if (tag == ACL_GROUP_OBJ) - have_group_obj = true; - else if (tag == ACL_OTHER) - have_other = true; - if (have_user_obj && have_group_obj && have_other) - return 0; - } - if (r < 0) - return -errno; - - r = stat(path, &st); - if (r < 0) - return -errno; - - basic = acl_from_mode(st.st_mode); - if (!basic) - return -errno; - - for (r = acl_get_entry(basic, ACL_FIRST_ENTRY, &i); - r > 0; - r = acl_get_entry(basic, ACL_NEXT_ENTRY, &i)) { - acl_tag_t tag; - acl_entry_t dst; - - if (acl_get_tag_type(i, &tag) < 0) - return -errno; - - if ((tag == ACL_USER_OBJ && have_user_obj) || - (tag == ACL_GROUP_OBJ && have_group_obj) || - (tag == ACL_OTHER && have_other)) - continue; - - r = acl_create_entry(acl_p, &dst); - if (r < 0) - return -errno; - - r = acl_copy_entry(dst, i); - if (r < 0) - return -errno; - } - if (r < 0) - return -errno; - return 0; -} - -int acl_search_groups(const char *path, char ***ret_groups) { - _cleanup_strv_free_ char **g = NULL; - _cleanup_(acl_free) acl_t acl = NULL; - bool ret = false; - acl_entry_t entry; - int r; - - assert(path); - - acl = acl_get_file(path, ACL_TYPE_DEFAULT); - if (!acl) - return -errno; - - r = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); - for (;;) { - _cleanup_(acl_free_gid_tpp) gid_t *gid = NULL; - acl_tag_t tag; - - if (r < 0) - return -errno; - if (r == 0) - break; - - if (acl_get_tag_type(entry, &tag) < 0) - return -errno; - - if (tag != ACL_GROUP) - goto next; - - gid = acl_get_qualifier(entry); - if (!gid) - return -errno; - - if (in_gid(*gid) > 0) { - if (!ret_groups) - return true; - - ret = true; - } - - if (ret_groups) { - char *name; - - name = gid_to_name(*gid); - if (!name) - return -ENOMEM; - - r = strv_consume(&g, name); - if (r < 0) - return r; - } - - next: - r = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry); - } - - if (ret_groups) { - *ret_groups = g; - g = NULL; - } - - return ret; -} - -int parse_acl(const char *text, acl_t *acl_access, acl_t *acl_default, bool want_mask) { - _cleanup_free_ char **a = NULL, **d = NULL; /* strings are not be freed */ - _cleanup_strv_free_ char **split; - char **entry; - int r = -EINVAL; - _cleanup_(acl_freep) acl_t a_acl = NULL, d_acl = NULL; - - split = strv_split(text, ","); - if (!split) - return -ENOMEM; - - STRV_FOREACH(entry, split) { - char *p; - - p = startswith(*entry, "default:"); - if (!p) - p = startswith(*entry, "d:"); - - if (p) - r = strv_push(&d, p); - else - r = strv_push(&a, *entry); - if (r < 0) - return r; - } - - if (!strv_isempty(a)) { - _cleanup_free_ char *join; - - join = strv_join(a, ","); - if (!join) - return -ENOMEM; - - a_acl = acl_from_text(join); - if (!a_acl) - return -errno; - - if (want_mask) { - r = calc_acl_mask_if_needed(&a_acl); - if (r < 0) - return r; - } - } - - if (!strv_isempty(d)) { - _cleanup_free_ char *join; - - join = strv_join(d, ","); - if (!join) - return -ENOMEM; - - d_acl = acl_from_text(join); - if (!d_acl) - return -errno; - - if (want_mask) { - r = calc_acl_mask_if_needed(&d_acl); - if (r < 0) - return r; - } - } - - *acl_access = a_acl; - *acl_default = d_acl; - a_acl = d_acl = NULL; - - return 0; -} - -static int acl_entry_equal(acl_entry_t a, acl_entry_t b) { - acl_tag_t tag_a, tag_b; - - if (acl_get_tag_type(a, &tag_a) < 0) - return -errno; - - if (acl_get_tag_type(b, &tag_b) < 0) - return -errno; - - if (tag_a != tag_b) - return false; - - switch (tag_a) { - case ACL_USER_OBJ: - case ACL_GROUP_OBJ: - case ACL_MASK: - case ACL_OTHER: - /* can have only one of those */ - return true; - case ACL_USER: { - _cleanup_(acl_free_uid_tpp) uid_t *uid_a = NULL, *uid_b = NULL; - - uid_a = acl_get_qualifier(a); - if (!uid_a) - return -errno; - - uid_b = acl_get_qualifier(b); - if (!uid_b) - return -errno; - - return *uid_a == *uid_b; - } - case ACL_GROUP: { - _cleanup_(acl_free_gid_tpp) gid_t *gid_a = NULL, *gid_b = NULL; - - gid_a = acl_get_qualifier(a); - if (!gid_a) - return -errno; - - gid_b = acl_get_qualifier(b); - if (!gid_b) - return -errno; - - return *gid_a == *gid_b; - } - default: - assert_not_reached("Unknown acl tag type"); - } -} - -static int find_acl_entry(acl_t acl, acl_entry_t entry, acl_entry_t *out) { - acl_entry_t i; - int r; - - for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i); - r > 0; - r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) { - - r = acl_entry_equal(i, entry); - if (r < 0) - return r; - if (r > 0) { - *out = i; - return 1; - } - } - if (r < 0) - return -errno; - return 0; -} - -int acls_for_file(const char *path, acl_type_t type, acl_t new, acl_t *acl) { - _cleanup_(acl_freep) acl_t old; - acl_entry_t i; - int r; - - old = acl_get_file(path, type); - if (!old) - return -errno; - - for (r = acl_get_entry(new, ACL_FIRST_ENTRY, &i); - r > 0; - r = acl_get_entry(new, ACL_NEXT_ENTRY, &i)) { - - acl_entry_t j; - - r = find_acl_entry(old, i, &j); - if (r < 0) - return r; - if (r == 0) - if (acl_create_entry(&old, &j) < 0) - return -errno; - - if (acl_copy_entry(j, i) < 0) - return -errno; - } - if (r < 0) - return -errno; - - *acl = old; - old = NULL; - return 0; -} - -int add_acls_for_user(int fd, uid_t uid) { - _cleanup_(acl_freep) acl_t acl = NULL; - acl_entry_t entry; - acl_permset_t permset; - int r; - - acl = acl_get_fd(fd); - if (!acl) - return -errno; - - r = acl_find_uid(acl, uid, &entry); - if (r <= 0) { - if (acl_create_entry(&acl, &entry) < 0 || - acl_set_tag_type(entry, ACL_USER) < 0 || - acl_set_qualifier(entry, &uid) < 0) - return -errno; - } - - /* We do not recalculate the mask unconditionally here, - * so that the fchmod() mask above stays intact. */ - if (acl_get_permset(entry, &permset) < 0 || - acl_add_perm(permset, ACL_READ) < 0) - return -errno; - - r = calc_acl_mask_if_needed(&acl); - if (r < 0) - return r; - - return acl_set_fd(fd, acl); -} diff --git a/src/shared/acl-util.h b/src/shared/acl-util.h deleted file mode 100644 index 396e9e067e..0000000000 --- a/src/shared/acl-util.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#ifdef HAVE_ACL - -#include -#include -#include - -#include "macro.h" - -int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry); -int calc_acl_mask_if_needed(acl_t *acl_p); -int add_base_acls_if_needed(acl_t *acl_p, const char *path); -int acl_search_groups(const char* path, char ***ret_groups); -int parse_acl(const char *text, acl_t *acl_access, acl_t *acl_default, bool want_mask); -int acls_for_file(const char *path, acl_type_t type, acl_t new, acl_t *acl); -int add_acls_for_user(int fd, uid_t uid); - -/* acl_free takes multiple argument types. - * Multiple cleanup functions are necessary. */ -DEFINE_TRIVIAL_CLEANUP_FUNC(acl_t, acl_free); -#define acl_free_charp acl_free -DEFINE_TRIVIAL_CLEANUP_FUNC(char*, acl_free_charp); -#define acl_free_uid_tp acl_free -DEFINE_TRIVIAL_CLEANUP_FUNC(uid_t*, acl_free_uid_tp); -#define acl_free_gid_tp acl_free -DEFINE_TRIVIAL_CLEANUP_FUNC(gid_t*, acl_free_gid_tp); - -#endif diff --git a/src/shared/acpi-fpdt.c b/src/shared/acpi-fpdt.c deleted file mode 100644 index 6779691c28..0000000000 --- a/src/shared/acpi-fpdt.c +++ /dev/null @@ -1,164 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 -#include -#include -#include -#include -#include - -#include "acpi-fpdt.h" -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "time-util.h" - -struct acpi_table_header { - char signature[4]; - uint32_t length; - uint8_t revision; - uint8_t checksum; - char oem_id[6]; - char oem_table_id[8]; - uint32_t oem_revision; - char asl_compiler_id[4]; - uint32_t asl_compiler_revision; -}; - -enum { - ACPI_FPDT_TYPE_BOOT = 0, - ACPI_FPDT_TYPE_S3PERF = 1, -}; - -struct acpi_fpdt_header { - uint16_t type; - uint8_t length; - uint8_t revision; - uint8_t reserved[4]; - uint64_t ptr; -}; - -struct acpi_fpdt_boot_header { - char signature[4]; - uint32_t length; -}; - -enum { - ACPI_FPDT_S3PERF_RESUME_REC = 0, - ACPI_FPDT_S3PERF_SUSPEND_REC = 1, - ACPI_FPDT_BOOT_REC = 2, -}; - -struct acpi_fpdt_boot { - uint16_t type; - uint8_t length; - uint8_t revision; - uint8_t reserved[4]; - uint64_t reset_end; - uint64_t load_start; - uint64_t startup_start; - uint64_t exit_services_entry; - uint64_t exit_services_exit; -}; - -int acpi_get_boot_usec(usec_t *loader_start, usec_t *loader_exit) { - _cleanup_free_ char *buf = NULL; - struct acpi_table_header *tbl; - size_t l = 0; - struct acpi_fpdt_header *rec; - int r; - uint64_t ptr = 0; - _cleanup_close_ int fd = -1; - struct acpi_fpdt_boot_header hbrec; - struct acpi_fpdt_boot brec; - - r = read_full_file("/sys/firmware/acpi/tables/FPDT", &buf, &l); - if (r < 0) - return r; - - if (l < sizeof(struct acpi_table_header) + sizeof(struct acpi_fpdt_header)) - return -EINVAL; - - tbl = (struct acpi_table_header *)buf; - if (l != tbl->length) - return -EINVAL; - - if (memcmp(tbl->signature, "FPDT", 4) != 0) - return -EINVAL; - - /* find Firmware Basic Boot Performance Pointer Record */ - for (rec = (struct acpi_fpdt_header *)(buf + sizeof(struct acpi_table_header)); - (char *)rec < buf + l; - rec = (struct acpi_fpdt_header *)((char *)rec + rec->length)) { - if (rec->length <= 0) - break; - if (rec->type != ACPI_FPDT_TYPE_BOOT) - continue; - if (rec->length != sizeof(struct acpi_fpdt_header)) - continue; - - ptr = rec->ptr; - break; - } - - if (ptr == 0) - return -ENODATA; - - /* read Firmware Basic Boot Performance Data Record */ - fd = open("/dev/mem", O_CLOEXEC|O_RDONLY); - if (fd < 0) - return -errno; - - l = pread(fd, &hbrec, sizeof(struct acpi_fpdt_boot_header), ptr); - if (l != sizeof(struct acpi_fpdt_boot_header)) - return -EINVAL; - - if (memcmp(hbrec.signature, "FBPT", 4) != 0) - return -EINVAL; - - if (hbrec.length < sizeof(struct acpi_fpdt_boot_header) + sizeof(struct acpi_fpdt_boot)) - return -EINVAL; - - l = pread(fd, &brec, sizeof(struct acpi_fpdt_boot), ptr + sizeof(struct acpi_fpdt_boot_header)); - if (l != sizeof(struct acpi_fpdt_boot)) - return -EINVAL; - - if (brec.length != sizeof(struct acpi_fpdt_boot)) - return -EINVAL; - - if (brec.type != ACPI_FPDT_BOOT_REC) - return -EINVAL; - - if (brec.exit_services_exit == 0) - /* Non-UEFI compatible boot. */ - return -ENODATA; - - if (brec.startup_start == 0 || brec.exit_services_exit < brec.startup_start) - return -EINVAL; - if (brec.exit_services_exit > NSEC_PER_HOUR) - return -EINVAL; - - if (loader_start) - *loader_start = brec.startup_start / 1000; - if (loader_exit) - *loader_exit = brec.exit_services_exit / 1000; - - return 0; -} diff --git a/src/shared/acpi-fpdt.h b/src/shared/acpi-fpdt.h deleted file mode 100644 index fc28175d0a..0000000000 --- a/src/shared/acpi-fpdt.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 - -int acpi_get_boot_usec(usec_t *loader_start, usec_t *loader_exit); diff --git a/src/shared/apparmor-util.c b/src/shared/apparmor-util.c deleted file mode 100644 index edd695fd23..0000000000 --- a/src/shared/apparmor-util.c +++ /dev/null @@ -1,39 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "alloc-util.h" -#include "apparmor-util.h" -#include "fileio.h" -#include "parse-util.h" - -bool mac_apparmor_use(void) { - static int cached_use = -1; - - if (cached_use < 0) { - _cleanup_free_ char *p = NULL; - - cached_use = - read_one_line_file("/sys/module/apparmor/parameters/enabled", &p) >= 0 && - parse_boolean(p) > 0; - } - - return cached_use; -} diff --git a/src/shared/apparmor-util.h b/src/shared/apparmor-util.h deleted file mode 100644 index 524f740152..0000000000 --- a/src/shared/apparmor-util.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 - -bool mac_apparmor_use(void); diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c deleted file mode 100644 index 4a4bd8d3b8..0000000000 --- a/src/shared/ask-password-api.c +++ /dev/null @@ -1,736 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "ask-password-api.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "io-util.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "random-util.h" -#include "signal-util.h" -#include "socket-util.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "time-util.h" -#include "umask-util.h" -#include "utf8.h" -#include "util.h" - -#define KEYRING_TIMEOUT_USEC ((5 * USEC_PER_MINUTE) / 2) - -static int lookup_key(const char *keyname, key_serial_t *ret) { - key_serial_t serial; - - assert(keyname); - assert(ret); - - serial = request_key("user", keyname, NULL, 0); - if (serial == -1) - return negative_errno(); - - *ret = serial; - return 0; -} - -static int retrieve_key(key_serial_t serial, char ***ret) { - _cleanup_free_ char *p = NULL; - long m = 100, n; - char **l; - - assert(ret); - - for (;;) { - p = new(char, m); - if (!p) - return -ENOMEM; - - n = keyctl(KEYCTL_READ, (unsigned long) serial, (unsigned long) p, (unsigned long) m, 0); - if (n < 0) - return -errno; - - if (n < m) - break; - - memory_erase(p, n); - free(p); - m *= 2; - } - - l = strv_parse_nulstr(p, n); - if (!l) - return -ENOMEM; - - memory_erase(p, n); - - *ret = l; - return 0; -} - -static int add_to_keyring(const char *keyname, AskPasswordFlags flags, char **passwords) { - _cleanup_strv_free_erase_ char **l = NULL; - _cleanup_free_ char *p = NULL; - key_serial_t serial; - size_t n; - int r; - - assert(keyname); - assert(passwords); - - if (!(flags & ASK_PASSWORD_PUSH_CACHE)) - return 0; - - r = lookup_key(keyname, &serial); - if (r >= 0) { - r = retrieve_key(serial, &l); - if (r < 0) - return r; - } else if (r != -ENOKEY) - return r; - - r = strv_extend_strv(&l, passwords, true); - if (r <= 0) - return r; - - r = strv_make_nulstr(l, &p, &n); - if (r < 0) - return r; - - /* Truncate trailing NUL */ - assert(n > 0); - assert(p[n-1] == 0); - - serial = add_key("user", keyname, p, n-1, KEY_SPEC_USER_KEYRING); - memory_erase(p, n); - if (serial == -1) - return -errno; - - if (keyctl(KEYCTL_SET_TIMEOUT, - (unsigned long) serial, - (unsigned long) DIV_ROUND_UP(KEYRING_TIMEOUT_USEC, USEC_PER_SEC), 0, 0) < 0) - log_debug_errno(errno, "Failed to adjust timeout: %m"); - - log_debug("Added key to keyring as %" PRIi32 ".", serial); - - return 1; -} - -static int add_to_keyring_and_log(const char *keyname, AskPasswordFlags flags, char **passwords) { - int r; - - assert(keyname); - assert(passwords); - - r = add_to_keyring(keyname, flags, passwords); - if (r < 0) - return log_debug_errno(r, "Failed to add password to keyring: %m"); - - return 0; -} - -int ask_password_keyring(const char *keyname, AskPasswordFlags flags, char ***ret) { - - key_serial_t serial; - int r; - - assert(keyname); - assert(ret); - - if (!(flags & ASK_PASSWORD_ACCEPT_CACHED)) - return -EUNATCH; - - r = lookup_key(keyname, &serial); - if (r == -ENOSYS) /* when retrieving the distinction doesn't matter */ - return -ENOKEY; - if (r < 0) - return r; - - return retrieve_key(serial, ret); -} - -static void backspace_chars(int ttyfd, size_t p) { - - if (ttyfd < 0) - return; - - while (p > 0) { - p--; - - loop_write(ttyfd, "\b \b", 3, false); - } -} - -int ask_password_tty( - const char *message, - const char *keyname, - usec_t until, - AskPasswordFlags flags, - const char *flag_file, - char **ret) { - - struct termios old_termios, new_termios; - char passphrase[LINE_MAX + 1] = {}, *x; - size_t p = 0, codepoint = 0; - int r; - _cleanup_close_ int ttyfd = -1, notify = -1; - struct pollfd pollfd[2]; - bool reset_tty = false; - bool dirty = false; - enum { - POLL_TTY, - POLL_INOTIFY - }; - - assert(ret); - - if (flags & ASK_PASSWORD_NO_TTY) - return -EUNATCH; - - if (!message) - message = "Password:"; - - if (flag_file) { - notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK); - if (notify < 0) { - r = -errno; - goto finish; - } - - if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) { - r = -errno; - goto finish; - } - } - - ttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC); - if (ttyfd >= 0) { - - if (tcgetattr(ttyfd, &old_termios) < 0) { - r = -errno; - goto finish; - } - - loop_write(ttyfd, ANSI_HIGHLIGHT, strlen(ANSI_HIGHLIGHT), false); - loop_write(ttyfd, message, strlen(message), false); - loop_write(ttyfd, " ", 1, false); - loop_write(ttyfd, ANSI_NORMAL, strlen(ANSI_NORMAL), false); - - new_termios = old_termios; - new_termios.c_lflag &= ~(ICANON|ECHO); - new_termios.c_cc[VMIN] = 1; - new_termios.c_cc[VTIME] = 0; - - if (tcsetattr(ttyfd, TCSADRAIN, &new_termios) < 0) { - r = -errno; - goto finish; - } - - reset_tty = true; - } - - zero(pollfd); - pollfd[POLL_TTY].fd = ttyfd >= 0 ? ttyfd : STDIN_FILENO; - pollfd[POLL_TTY].events = POLLIN; - pollfd[POLL_INOTIFY].fd = notify; - pollfd[POLL_INOTIFY].events = POLLIN; - - for (;;) { - char c; - int sleep_for = -1, k; - ssize_t n; - - if (until > 0) { - usec_t y; - - y = now(CLOCK_MONOTONIC); - - if (y > until) { - r = -ETIME; - goto finish; - } - - sleep_for = (int) ((until - y) / USEC_PER_MSEC); - } - - if (flag_file) - if (access(flag_file, F_OK) < 0) { - r = -errno; - goto finish; - } - - k = poll(pollfd, notify >= 0 ? 2 : 1, sleep_for); - if (k < 0) { - if (errno == EINTR) - continue; - - r = -errno; - goto finish; - } else if (k == 0) { - r = -ETIME; - goto finish; - } - - if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0) - flush_fd(notify); - - if (pollfd[POLL_TTY].revents == 0) - continue; - - n = read(ttyfd >= 0 ? ttyfd : STDIN_FILENO, &c, 1); - if (n < 0) { - if (errno == EINTR || errno == EAGAIN) - continue; - - r = -errno; - goto finish; - - } else if (n == 0) - break; - - if (c == '\n') - break; - else if (c == 21) { /* C-u */ - - if (!(flags & ASK_PASSWORD_SILENT)) - backspace_chars(ttyfd, p); - p = 0; - - } else if (c == '\b' || c == 127) { - - if (p > 0) { - - if (!(flags & ASK_PASSWORD_SILENT)) - backspace_chars(ttyfd, 1); - - p--; - } else if (!dirty && !(flags & ASK_PASSWORD_SILENT)) { - - flags |= ASK_PASSWORD_SILENT; - - /* There are two ways to enter silent - * mode. Either by pressing backspace - * as first key (and only as first - * key), or ... */ - if (ttyfd >= 0) - loop_write(ttyfd, "(no echo) ", 10, false); - - } else if (ttyfd >= 0) - loop_write(ttyfd, "\a", 1, false); - - } else if (c == '\t' && !(flags & ASK_PASSWORD_SILENT)) { - - backspace_chars(ttyfd, p); - flags |= ASK_PASSWORD_SILENT; - - /* ... or by pressing TAB at any time. */ - - if (ttyfd >= 0) - loop_write(ttyfd, "(no echo) ", 10, false); - } else { - if (p >= sizeof(passphrase)-1) { - loop_write(ttyfd, "\a", 1, false); - continue; - } - - passphrase[p++] = c; - - if (!(flags & ASK_PASSWORD_SILENT) && ttyfd >= 0) { - n = utf8_encoded_valid_unichar(passphrase + codepoint); - if (n >= 0) { - codepoint = p; - loop_write(ttyfd, (flags & ASK_PASSWORD_ECHO) ? &c : "*", 1, false); - } - } - - dirty = true; - } - - c = 'x'; - } - - x = strndup(passphrase, p); - memory_erase(passphrase, p); - if (!x) { - r = -ENOMEM; - goto finish; - } - - if (keyname) - (void) add_to_keyring_and_log(keyname, flags, STRV_MAKE(x)); - - *ret = x; - r = 0; - -finish: - if (ttyfd >= 0 && reset_tty) { - loop_write(ttyfd, "\n", 1, false); - tcsetattr(ttyfd, TCSADRAIN, &old_termios); - } - - return r; -} - -static int create_socket(char **name) { - union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - }; - _cleanup_close_ int fd = -1; - static const int one = 1; - char *c; - int r; - - assert(name); - - fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (fd < 0) - return -errno; - - snprintf(sa.un.sun_path, sizeof(sa.un.sun_path)-1, "/run/systemd/ask-password/sck.%" PRIx64, random_u64()); - - RUN_WITH_UMASK(0177) { - if (bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) - return -errno; - } - - if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) - return -errno; - - c = strdup(sa.un.sun_path); - if (!c) - return -ENOMEM; - - *name = c; - - r = fd; - fd = -1; - - return r; -} - -int ask_password_agent( - const char *message, - const char *icon, - const char *id, - const char *keyname, - usec_t until, - AskPasswordFlags flags, - char ***ret) { - - enum { - FD_SOCKET, - FD_SIGNAL, - _FD_MAX - }; - - _cleanup_close_ int socket_fd = -1, signal_fd = -1, fd = -1; - char temp[] = "/run/systemd/ask-password/tmp.XXXXXX"; - char final[sizeof(temp)] = ""; - _cleanup_free_ char *socket_name = NULL; - _cleanup_strv_free_ char **l = NULL; - _cleanup_fclose_ FILE *f = NULL; - struct pollfd pollfd[_FD_MAX]; - sigset_t mask, oldmask; - int r; - - assert(ret); - - if (flags & ASK_PASSWORD_NO_AGENT) - return -EUNATCH; - - assert_se(sigemptyset(&mask) >= 0); - assert_se(sigset_add_many(&mask, SIGINT, SIGTERM, -1) >= 0); - assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) >= 0); - - (void) mkdir_p_label("/run/systemd/ask-password", 0755); - - fd = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC); - if (fd < 0) { - r = fd; - goto finish; - } - - (void) fchmod(fd, 0644); - - f = fdopen(fd, "w"); - if (!f) { - r = -errno; - goto finish; - } - - fd = -1; - - signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); - if (signal_fd < 0) { - r = -errno; - goto finish; - } - - socket_fd = create_socket(&socket_name); - if (socket_fd < 0) { - r = socket_fd; - goto finish; - } - - fprintf(f, - "[Ask]\n" - "PID="PID_FMT"\n" - "Socket=%s\n" - "AcceptCached=%i\n" - "Echo=%i\n" - "NotAfter="USEC_FMT"\n", - getpid(), - socket_name, - (flags & ASK_PASSWORD_ACCEPT_CACHED) ? 1 : 0, - (flags & ASK_PASSWORD_ECHO) ? 1 : 0, - until); - - if (message) - fprintf(f, "Message=%s\n", message); - - if (icon) - fprintf(f, "Icon=%s\n", icon); - - if (id) - fprintf(f, "Id=%s\n", id); - - r = fflush_and_check(f); - if (r < 0) - goto finish; - - memcpy(final, temp, sizeof(temp)); - - final[sizeof(final)-11] = 'a'; - final[sizeof(final)-10] = 's'; - final[sizeof(final)-9] = 'k'; - - if (rename(temp, final) < 0) { - r = -errno; - goto finish; - } - - zero(pollfd); - pollfd[FD_SOCKET].fd = socket_fd; - pollfd[FD_SOCKET].events = POLLIN; - pollfd[FD_SIGNAL].fd = signal_fd; - pollfd[FD_SIGNAL].events = POLLIN; - - for (;;) { - char passphrase[LINE_MAX+1]; - struct msghdr msghdr; - struct iovec iovec; - struct ucred *ucred; - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; - } control; - ssize_t n; - int k; - usec_t t; - - t = now(CLOCK_MONOTONIC); - - if (until > 0 && until <= t) { - r = -ETIME; - goto finish; - } - - k = poll(pollfd, _FD_MAX, until > 0 ? (int) ((until-t)/USEC_PER_MSEC) : -1); - if (k < 0) { - if (errno == EINTR) - continue; - - r = -errno; - goto finish; - } - - if (k <= 0) { - r = -ETIME; - goto finish; - } - - if (pollfd[FD_SIGNAL].revents & POLLIN) { - r = -EINTR; - goto finish; - } - - if (pollfd[FD_SOCKET].revents != POLLIN) { - r = -EIO; - goto finish; - } - - zero(iovec); - iovec.iov_base = passphrase; - iovec.iov_len = sizeof(passphrase); - - zero(control); - zero(msghdr); - msghdr.msg_iov = &iovec; - msghdr.msg_iovlen = 1; - msghdr.msg_control = &control; - msghdr.msg_controllen = sizeof(control); - - n = recvmsg(socket_fd, &msghdr, 0); - if (n < 0) { - if (errno == EAGAIN || - errno == EINTR) - continue; - - r = -errno; - goto finish; - } - - cmsg_close_all(&msghdr); - - if (n <= 0) { - log_debug("Message too short"); - continue; - } - - if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) || - control.cmsghdr.cmsg_level != SOL_SOCKET || - control.cmsghdr.cmsg_type != SCM_CREDENTIALS || - control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) { - log_debug("Received message without credentials. Ignoring."); - continue; - } - - ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr); - if (ucred->uid != 0) { - log_debug("Got request from unprivileged user. Ignoring."); - continue; - } - - if (passphrase[0] == '+') { - /* An empty message refers to the empty password */ - if (n == 1) - l = strv_new("", NULL); - else - l = strv_parse_nulstr(passphrase+1, n-1); - memory_erase(passphrase, n); - if (!l) { - r = -ENOMEM; - goto finish; - } - - if (strv_length(l) <= 0) { - l = strv_free(l); - log_debug("Invalid packet"); - continue; - } - - break; - } - - if (passphrase[0] == '-') { - r = -ECANCELED; - goto finish; - } - - log_debug("Invalid packet"); - } - - if (keyname) - (void) add_to_keyring_and_log(keyname, flags, l); - - *ret = l; - l = NULL; - r = 0; - -finish: - if (socket_name) - (void) unlink(socket_name); - - (void) unlink(temp); - - if (final[0]) - (void) unlink(final); - - assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0); - return r; -} - -int ask_password_auto( - const char *message, - const char *icon, - const char *id, - const char *keyname, - usec_t until, - AskPasswordFlags flags, - char ***ret) { - - int r; - - assert(ret); - - if ((flags & ASK_PASSWORD_ACCEPT_CACHED) && keyname) { - r = ask_password_keyring(keyname, flags, ret); - if (r != -ENOKEY) - return r; - } - - if (!(flags & ASK_PASSWORD_NO_TTY) && isatty(STDIN_FILENO)) { - char *s = NULL, **l = NULL; - - r = ask_password_tty(message, keyname, until, flags, NULL, &s); - if (r < 0) - return r; - - r = strv_push(&l, s); - if (r < 0) { - string_erase(s); - free(s); - return -ENOMEM; - } - - *ret = l; - return 0; - } - - if (!(flags & ASK_PASSWORD_NO_AGENT)) - return ask_password_agent(message, icon, id, keyname, until, flags, ret); - - return -EUNATCH; -} diff --git a/src/shared/ask-password-api.h b/src/shared/ask-password-api.h deleted file mode 100644 index 9d7f65130c..0000000000 --- a/src/shared/ask-password-api.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "time-util.h" - -typedef enum AskPasswordFlags { - ASK_PASSWORD_ACCEPT_CACHED = 1, - ASK_PASSWORD_PUSH_CACHE = 2, - ASK_PASSWORD_ECHO = 4, /* show the password literally while reading, instead of "*" */ - ASK_PASSWORD_SILENT = 8, /* do no show any password at all while reading */ - ASK_PASSWORD_NO_TTY = 16, - ASK_PASSWORD_NO_AGENT = 32, -} AskPasswordFlags; - -int ask_password_tty(const char *message, const char *keyname, usec_t until, AskPasswordFlags flags, const char *flag_file, char **ret); -int ask_password_agent(const char *message, const char *icon, const char *id, const char *keyname, usec_t until, AskPasswordFlags flag, char ***ret); -int ask_password_keyring(const char *keyname, AskPasswordFlags flags, char ***ret); -int ask_password_auto(const char *message, const char *icon, const char *id, const char *keyname, usec_t until, AskPasswordFlags flag, char ***ret); diff --git a/src/shared/base-filesystem.c b/src/shared/base-filesystem.c deleted file mode 100644 index 59a34a9d11..0000000000 --- a/src/shared/base-filesystem.c +++ /dev/null @@ -1,129 +0,0 @@ -/*** - 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 -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "base-filesystem.h" -#include "fd-util.h" -#include "log.h" -#include "macro.h" -#include "string-util.h" -#include "umask-util.h" -#include "user-util.h" -#include "util.h" - -typedef struct BaseFilesystem { - const char *dir; - mode_t mode; - const char *target; - const char *exists; - bool ignore_failure; -} BaseFilesystem; - -static const BaseFilesystem table[] = { - { "bin", 0, "usr/bin\0", NULL }, - { "lib", 0, "usr/lib\0", NULL }, - { "root", 0755, NULL, NULL, true }, - { "sbin", 0, "usr/sbin\0", NULL }, - { "usr", 0755, NULL, NULL }, - { "var", 0755, NULL, NULL }, - { "etc", 0755, NULL, NULL }, -#if defined(__i386__) || defined(__x86_64__) - { "lib64", 0, "usr/lib/x86_64-linux-gnu\0" - "usr/lib64\0", "ld-linux-x86-64.so.2" }, -#endif -}; - -int base_filesystem_create(const char *root, uid_t uid, gid_t gid) { - _cleanup_close_ int fd = -1; - unsigned i; - int r = 0; - - fd = open(root, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); - if (fd < 0) - return log_error_errno(errno, "Failed to open root file system: %m"); - - for (i = 0; i < ELEMENTSOF(table); i ++) { - if (faccessat(fd, table[i].dir, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) - continue; - - if (table[i].target) { - const char *target = NULL, *s; - - /* check if one of the targets exists */ - NULSTR_FOREACH(s, table[i].target) { - if (faccessat(fd, s, F_OK, AT_SYMLINK_NOFOLLOW) < 0) - continue; - - /* check if a specific file exists at the target path */ - if (table[i].exists) { - _cleanup_free_ char *p = NULL; - - p = strjoin(s, "/", table[i].exists, NULL); - if (!p) - return log_oom(); - - if (faccessat(fd, p, F_OK, AT_SYMLINK_NOFOLLOW) < 0) - continue; - } - - target = s; - break; - } - - if (!target) - continue; - - r = symlinkat(target, fd, table[i].dir); - if (r < 0 && errno != EEXIST) - return log_error_errno(errno, "Failed to create symlink at %s/%s: %m", root, table[i].dir); - - if (uid != UID_INVALID || gid != UID_INVALID) { - if (fchownat(fd, table[i].dir, uid, gid, AT_SYMLINK_NOFOLLOW) < 0) - return log_error_errno(errno, "Failed to chown symlink at %s/%s: %m", root, table[i].dir); - } - - continue; - } - - RUN_WITH_UMASK(0000) - r = mkdirat(fd, table[i].dir, table[i].mode); - if (r < 0 && errno != EEXIST) { - log_full_errno(table[i].ignore_failure ? LOG_DEBUG : LOG_ERR, errno, - "Failed to create directory at %s/%s: %m", root, table[i].dir); - - if (!table[i].ignore_failure) - return -errno; - } - - if (uid != UID_INVALID || gid != UID_INVALID) { - if (fchownat(fd, table[i].dir, uid, gid, AT_SYMLINK_NOFOLLOW) < 0) - return log_error_errno(errno, "Failed to chown directory at %s/%s: %m", root, table[i].dir); - } - } - - return 0; -} diff --git a/src/shared/base-filesystem.h b/src/shared/base-filesystem.h deleted file mode 100644 index 49599f0a60..0000000000 --- a/src/shared/base-filesystem.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - 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 - -int base_filesystem_create(const char *root, uid_t uid, gid_t gid); diff --git a/src/shared/boot-timestamps.c b/src/shared/boot-timestamps.c deleted file mode 100644 index 7e0152761c..0000000000 --- a/src/shared/boot-timestamps.c +++ /dev/null @@ -1,64 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 Lennart Poettering - Copyright 2013 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 "acpi-fpdt.h" -#include "boot-timestamps.h" -#include "efivars.h" -#include "macro.h" -#include "time-util.h" - -int boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader) { - usec_t x = 0, y = 0, a; - int r; - dual_timestamp _n; - - assert(firmware); - assert(loader); - - if (!n) { - dual_timestamp_get(&_n); - n = &_n; - } - - r = acpi_get_boot_usec(&x, &y); - if (r < 0) { - r = efi_loader_get_boot_usec(&x, &y); - if (r < 0) - return r; - } - - /* Let's convert this to timestamps where the firmware - * began/loader began working. To make this more confusing: - * since usec_t is unsigned and the kernel's monotonic clock - * begins at kernel initialization we'll actually initialize - * the monotonic timestamps here as negative of the actual - * value. */ - - firmware->monotonic = y; - loader->monotonic = y - x; - - a = n->monotonic + firmware->monotonic; - firmware->realtime = n->realtime > a ? n->realtime - a : 0; - - a = n->monotonic + loader->monotonic; - loader->realtime = n->realtime > a ? n->realtime - a : 0; - - return 0; -} diff --git a/src/shared/boot-timestamps.h b/src/shared/boot-timestamps.h deleted file mode 100644 index 6f691026be..0000000000 --- a/src/shared/boot-timestamps.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 Lennart Poettering - Copyright 2013 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 - -int boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader); diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c deleted file mode 100644 index f68c4a41ac..0000000000 --- a/src/shared/bus-unit-util.c +++ /dev/null @@ -1,1307 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2016 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 "alloc-util.h" -#include "bus-internal.h" -#include "bus-unit-util.h" -#include "bus-util.h" -#include "cgroup-util.h" -#include "env-util.h" -#include "escape.h" -#include "hashmap.h" -#include "list.h" -#include "locale-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "rlimit-util.h" -#include "signal-util.h" -#include "string-util.h" -#include "syslog-util.h" -#include "terminal-util.h" -#include "utf8.h" -#include "util.h" - -int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u) { - assert(message); - assert(u); - - u->machine = NULL; - - return sd_bus_message_read( - message, - "(ssssssouso)", - &u->id, - &u->description, - &u->load_state, - &u->active_state, - &u->sub_state, - &u->following, - &u->unit_path, - &u->job_id, - &u->job_type, - &u->job_path); -} - -int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignment) { - const char *eq, *field; - int r, rl; - - assert(m); - assert(assignment); - - eq = strchr(assignment, '='); - if (!eq) { - log_error("Not an assignment: %s", assignment); - return -EINVAL; - } - - r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv"); - if (r < 0) - return bus_log_create_error(r); - - field = strndupa(assignment, eq - assignment); - eq++; - - if (streq(field, "CPUQuota")) { - - if (isempty(eq)) - r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", USEC_INFINITY); - else if (endswith(eq, "%")) { - double percent; - - if (sscanf(eq, "%lf%%", &percent) != 1 || percent <= 0) { - log_error("CPU quota '%s' invalid.", eq); - return -EINVAL; - } - - r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", (usec_t) percent * USEC_PER_SEC / 100); - } else { - log_error("CPU quota needs to be in percent."); - return -EINVAL; - } - - goto finish; - - } else if (streq(field, "EnvironmentFile")) { - - r = sd_bus_message_append(m, "sv", "EnvironmentFiles", "a(sb)", 1, - eq[0] == '-' ? eq + 1 : eq, - eq[0] == '-'); - goto finish; - - } else if (STR_IN_SET(field, "AccuracySec", "RandomizedDelaySec", "RuntimeMaxSec")) { - char *n; - usec_t t; - size_t l; - r = parse_sec(eq, &t); - if (r < 0) - return log_error_errno(r, "Failed to parse %s= parameter: %s", field, eq); - - l = strlen(field); - n = newa(char, l + 2); - if (!n) - return log_oom(); - - /* Change suffix Sec → USec */ - strcpy(mempcpy(n, field, l - 3), "USec"); - r = sd_bus_message_append(m, "sv", n, "t", t); - goto finish; - } - - r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field); - if (r < 0) - return bus_log_create_error(r); - - rl = rlimit_from_string(field); - if (rl >= 0) { - const char *sn; - struct rlimit l; - - r = rlimit_parse(rl, eq, &l); - if (r < 0) - return log_error_errno(r, "Failed to parse resource limit: %s", eq); - - r = sd_bus_message_append(m, "v", "t", l.rlim_max); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv"); - if (r < 0) - return bus_log_create_error(r); - - sn = strjoina(field, "Soft"); - r = sd_bus_message_append(m, "sv", sn, "t", l.rlim_cur); - - } else if (STR_IN_SET(field, - "CPUAccounting", "MemoryAccounting", "IOAccounting", "BlockIOAccounting", "TasksAccounting", - "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies", - "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit", - "PrivateTmp", "PrivateDevices", "PrivateNetwork", "NoNewPrivileges", - "SyslogLevelPrefix", "Delegate", "RemainAfterElapse")) { - - r = parse_boolean(eq); - if (r < 0) - return log_error_errno(r, "Failed to parse boolean assignment %s.", assignment); - - r = sd_bus_message_append(m, "v", "b", r); - - } else if (streq(field, "MemoryLimit")) { - uint64_t bytes; - - if (isempty(eq) || streq(eq, "infinity")) - bytes = (uint64_t) -1; - else { - r = parse_size(eq, 1024, &bytes); - if (r < 0) { - log_error("Failed to parse bytes specification %s", assignment); - return -EINVAL; - } - } - - r = sd_bus_message_append(m, "v", "t", bytes); - - } else if (streq(field, "TasksMax")) { - uint64_t n; - - if (isempty(eq) || streq(eq, "infinity")) - n = (uint64_t) -1; - else { - r = safe_atou64(eq, &n); - if (r < 0) { - log_error("Failed to parse maximum tasks specification %s", assignment); - return -EINVAL; - } - } - - r = sd_bus_message_append(m, "v", "t", n); - - } else if (STR_IN_SET(field, "CPUShares", "StartupCPUShares")) { - uint64_t u; - - r = cg_cpu_shares_parse(eq, &u); - if (r < 0) { - log_error("Failed to parse %s value %s.", field, eq); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "t", u); - - } else if (STR_IN_SET(field, "IOWeight", "StartupIOWeight")) { - uint64_t u; - - r = cg_weight_parse(eq, &u); - if (r < 0) { - log_error("Failed to parse %s value %s.", field, eq); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "t", u); - - } else if (STR_IN_SET(field, "BlockIOWeight", "StartupBlockIOWeight")) { - uint64_t u; - - r = cg_blkio_weight_parse(eq, &u); - if (r < 0) { - log_error("Failed to parse %s value %s.", field, eq); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "t", u); - - } else if (STR_IN_SET(field, - "User", "Group", "DevicePolicy", "KillMode", - "UtmpIdentifier", "UtmpMode", "PAMName", "TTYPath", - "StandardInput", "StandardOutput", "StandardError", - "Description", "Slice", "Type", "WorkingDirectory", - "RootDirectory", "SyslogIdentifier", "ProtectSystem", - "ProtectHome", "SELinuxContext")) - r = sd_bus_message_append(m, "v", "s", eq); - - else if (streq(field, "SyslogLevel")) { - int level; - - level = log_level_from_string(eq); - if (level < 0) { - log_error("Failed to parse %s value %s.", field, eq); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "i", level); - - } else if (streq(field, "SyslogFacility")) { - int facility; - - facility = log_facility_unshifted_from_string(eq); - if (facility < 0) { - log_error("Failed to parse %s value %s.", field, eq); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "i", facility); - - } else if (streq(field, "DeviceAllow")) { - - if (isempty(eq)) - r = sd_bus_message_append(m, "v", "a(ss)", 0); - else { - const char *path, *rwm, *e; - - e = strchr(eq, ' '); - if (e) { - path = strndupa(eq, e - eq); - rwm = e+1; - } else { - path = eq; - rwm = ""; - } - - if (!path_startswith(path, "/dev")) { - log_error("%s is not a device file in /dev.", path); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "a(ss)", 1, path, rwm); - } - - } else if (cgroup_io_limit_type_from_string(field) >= 0 || STR_IN_SET(field, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) { - - if (isempty(eq)) - r = sd_bus_message_append(m, "v", "a(st)", 0); - else { - const char *path, *bandwidth, *e; - uint64_t bytes; - - e = strchr(eq, ' '); - if (e) { - path = strndupa(eq, e - eq); - bandwidth = e+1; - } else { - log_error("Failed to parse %s value %s.", field, eq); - return -EINVAL; - } - - if (!path_startswith(path, "/dev")) { - log_error("%s is not a device file in /dev.", path); - return -EINVAL; - } - - if (streq(bandwidth, "max")) { - bytes = CGROUP_LIMIT_MAX; - } else { - r = parse_size(bandwidth, 1000, &bytes); - if (r < 0) { - log_error("Failed to parse byte value %s.", bandwidth); - return -EINVAL; - } - } - - r = sd_bus_message_append(m, "v", "a(st)", 1, path, bytes); - } - - } else if (STR_IN_SET(field, "IODeviceWeight", "BlockIODeviceWeight")) { - - if (isempty(eq)) - r = sd_bus_message_append(m, "v", "a(st)", 0); - else { - const char *path, *weight, *e; - uint64_t u; - - e = strchr(eq, ' '); - if (e) { - path = strndupa(eq, e - eq); - weight = e+1; - } else { - log_error("Failed to parse %s value %s.", field, eq); - return -EINVAL; - } - - if (!path_startswith(path, "/dev")) { - log_error("%s is not a device file in /dev.", path); - return -EINVAL; - } - - r = safe_atou64(weight, &u); - if (r < 0) { - log_error("Failed to parse %s value %s.", field, weight); - return -EINVAL; - } - r = sd_bus_message_append(m, "v", "a(st)", 1, path, u); - } - - } else if (streq(field, "Nice")) { - int32_t i; - - r = safe_atoi32(eq, &i); - if (r < 0) { - log_error("Failed to parse %s value %s.", field, eq); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "i", i); - - } else if (STR_IN_SET(field, "Environment", "PassEnvironment")) { - const char *p; - - r = sd_bus_message_open_container(m, 'v', "as"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'a', "s"); - if (r < 0) - return bus_log_create_error(r); - - p = eq; - - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE); - if (r < 0) { - log_error("Failed to parse Environment value %s", eq); - return -EINVAL; - } - if (r == 0) - break; - - if (streq(field, "Environment")) { - if (!env_assignment_is_valid(word)) { - log_error("Invalid environment assignment: %s", word); - return -EINVAL; - } - } else { /* PassEnvironment */ - if (!env_name_is_valid(word)) { - log_error("Invalid environment variable name: %s", word); - return -EINVAL; - } - } - - r = sd_bus_message_append_basic(m, 's', word); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - - } else if (streq(field, "KillSignal")) { - int sig; - - sig = signal_from_string_try_harder(eq); - if (sig < 0) { - log_error("Failed to parse %s value %s.", field, eq); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "i", sig); - - } else if (streq(field, "TimerSlackNSec")) { - nsec_t n; - - r = parse_nsec(eq, &n); - if (r < 0) { - log_error("Failed to parse %s value %s", field, eq); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "t", n); - } else if (streq(field, "OOMScoreAdjust")) { - int oa; - - r = safe_atoi(eq, &oa); - if (r < 0) { - log_error("Failed to parse %s value %s", field, eq); - return -EINVAL; - } - - if (!oom_score_adjust_is_valid(oa)) { - log_error("OOM score adjust value out of range"); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "i", oa); - } else if (STR_IN_SET(field, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories")) { - const char *p; - - r = sd_bus_message_open_container(m, 'v', "as"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'a', "s"); - if (r < 0) - return bus_log_create_error(r); - - p = eq; - - for (;;) { - _cleanup_free_ char *word = NULL; - int offset; - - r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES); - if (r < 0) { - log_error("Failed to parse %s value %s", field, eq); - return -EINVAL; - } - if (r == 0) - break; - - if (!utf8_is_valid(word)) { - log_error("Failed to parse %s value %s", field, eq); - return -EINVAL; - } - - offset = word[0] == '-'; - if (!path_is_absolute(word + offset)) { - log_error("Failed to parse %s value %s", field, eq); - return -EINVAL; - } - - path_kill_slashes(word + offset); - - r = sd_bus_message_append_basic(m, 's', word); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - - } else if (streq(field, "RuntimeDirectory")) { - const char *p; - - r = sd_bus_message_open_container(m, 'v', "as"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'a', "s"); - if (r < 0) - return bus_log_create_error(r); - - p = eq; - - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES); - if (r < 0) - return log_error_errno(r, "Failed to parse %s value %s", field, eq); - - if (r == 0) - break; - - r = sd_bus_message_append_basic(m, 's', word); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - - } else { - log_error("Unknown assignment %s.", assignment); - return -EINVAL; - } - -finish: - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - return 0; -} - -typedef struct BusWaitForJobs { - sd_bus *bus; - Set *jobs; - - char *name; - char *result; - - sd_bus_slot *slot_job_removed; - sd_bus_slot *slot_disconnected; -} BusWaitForJobs; - -static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) { - assert(m); - - log_error("Warning! D-Bus connection terminated."); - sd_bus_close(sd_bus_message_get_bus(m)); - - return 0; -} - -static int match_job_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { - const char *path, *unit, *result; - BusWaitForJobs *d = userdata; - uint32_t id; - char *found; - int r; - - assert(m); - assert(d); - - r = sd_bus_message_read(m, "uoss", &id, &path, &unit, &result); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - found = set_remove(d->jobs, (char*) path); - if (!found) - return 0; - - free(found); - - if (!isempty(result)) - d->result = strdup(result); - - if (!isempty(unit)) - d->name = strdup(unit); - - return 0; -} - -void bus_wait_for_jobs_free(BusWaitForJobs *d) { - if (!d) - return; - - set_free_free(d->jobs); - - sd_bus_slot_unref(d->slot_disconnected); - sd_bus_slot_unref(d->slot_job_removed); - - sd_bus_unref(d->bus); - - free(d->name); - free(d->result); - - free(d); -} - -int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret) { - _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *d = NULL; - int r; - - assert(bus); - assert(ret); - - d = new0(BusWaitForJobs, 1); - if (!d) - return -ENOMEM; - - d->bus = sd_bus_ref(bus); - - /* When we are a bus client we match by sender. Direct - * connections OTOH have no initialized sender field, and - * hence we ignore the sender then */ - r = sd_bus_add_match( - bus, - &d->slot_job_removed, - bus->bus_client ? - "type='signal'," - "sender='org.freedesktop.systemd1'," - "interface='org.freedesktop.systemd1.Manager'," - "member='JobRemoved'," - "path='/org/freedesktop/systemd1'" : - "type='signal'," - "interface='org.freedesktop.systemd1.Manager'," - "member='JobRemoved'," - "path='/org/freedesktop/systemd1'", - match_job_removed, d); - if (r < 0) - return r; - - r = sd_bus_add_match( - bus, - &d->slot_disconnected, - "type='signal'," - "sender='org.freedesktop.DBus.Local'," - "interface='org.freedesktop.DBus.Local'," - "member='Disconnected'", - match_disconnected, d); - if (r < 0) - return r; - - *ret = d; - d = NULL; - - return 0; -} - -static int bus_process_wait(sd_bus *bus) { - int r; - - for (;;) { - r = sd_bus_process(bus, NULL); - if (r < 0) - return r; - if (r > 0) - return 0; - - r = sd_bus_wait(bus, (uint64_t) -1); - if (r < 0) - return r; - } -} - -static int bus_job_get_service_result(BusWaitForJobs *d, char **result) { - _cleanup_free_ char *dbus_path = NULL; - - assert(d); - assert(d->name); - assert(result); - - dbus_path = unit_dbus_path_from_name(d->name); - if (!dbus_path) - return -ENOMEM; - - return sd_bus_get_property_string(d->bus, - "org.freedesktop.systemd1", - dbus_path, - "org.freedesktop.systemd1.Service", - "Result", - NULL, - result); -} - -static const struct { - const char *result, *explanation; -} explanations [] = { - { "resources", "of unavailable resources or another system error" }, - { "timeout", "a timeout was exceeded" }, - { "exit-code", "the control process exited with error code" }, - { "signal", "a fatal signal was delivered to the control process" }, - { "core-dump", "a fatal signal was delivered causing the control process to dump core" }, - { "watchdog", "the service failed to send watchdog ping" }, - { "start-limit", "start of the service was attempted too often" } -}; - -static void log_job_error_with_service_result(const char* service, const char *result, const char* const* extra_args) { - _cleanup_free_ char *service_shell_quoted = NULL; - const char *systemctl = "systemctl", *journalctl = "journalctl"; - - assert(service); - - service_shell_quoted = shell_maybe_quote(service); - - if (extra_args && extra_args[1]) { - _cleanup_free_ char *t; - - t = strv_join((char**) extra_args, " "); - systemctl = strjoina("systemctl ", t ? : ""); - journalctl = strjoina("journalctl ", t ? : ""); - } - - if (!isempty(result)) { - unsigned i; - - for (i = 0; i < ELEMENTSOF(explanations); ++i) - if (streq(result, explanations[i].result)) - break; - - if (i < ELEMENTSOF(explanations)) { - log_error("Job for %s failed because %s.\n" - "See \"%s status %s\" and \"%s -xe\" for details.\n", - service, - explanations[i].explanation, - systemctl, - service_shell_quoted ?: "", - journalctl); - goto finish; - } - } - - log_error("Job for %s failed.\n" - "See \"%s status %s\" and \"%s -xe\" for details.\n", - service, - systemctl, - service_shell_quoted ?: "", - journalctl); - -finish: - /* For some results maybe additional explanation is required */ - if (streq_ptr(result, "start-limit")) - log_info("To force a start use \"%1$s reset-failed %2$s\"\n" - "followed by \"%1$s start %2$s\" again.", - systemctl, - service_shell_quoted ?: ""); -} - -static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const* extra_args) { - int r = 0; - - assert(d->result); - - if (!quiet) { - if (streq(d->result, "canceled")) - log_error("Job for %s canceled.", strna(d->name)); - else if (streq(d->result, "timeout")) - log_error("Job for %s timed out.", strna(d->name)); - else if (streq(d->result, "dependency")) - log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d->name)); - else if (streq(d->result, "invalid")) - log_error("%s is not active, cannot reload.", strna(d->name)); - else if (streq(d->result, "assert")) - log_error("Assertion failed on job for %s.", strna(d->name)); - else if (streq(d->result, "unsupported")) - log_error("Operation on or unit type of %s not supported on this system.", strna(d->name)); - else if (!streq(d->result, "done") && !streq(d->result, "skipped")) { - if (d->name) { - int q; - _cleanup_free_ char *result = NULL; - - q = bus_job_get_service_result(d, &result); - if (q < 0) - log_debug_errno(q, "Failed to get Result property of service %s: %m", d->name); - - log_job_error_with_service_result(d->name, result, extra_args); - } else - log_error("Job failed. See \"journalctl -xe\" for details."); - } - } - - if (streq(d->result, "canceled")) - r = -ECANCELED; - else if (streq(d->result, "timeout")) - r = -ETIME; - else if (streq(d->result, "dependency")) - r = -EIO; - else if (streq(d->result, "invalid")) - r = -ENOEXEC; - else if (streq(d->result, "assert")) - r = -EPROTO; - else if (streq(d->result, "unsupported")) - r = -EOPNOTSUPP; - else if (!streq(d->result, "done") && !streq(d->result, "skipped")) - r = -EIO; - - return r; -} - -int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char* const* extra_args) { - int r = 0; - - assert(d); - - while (!set_isempty(d->jobs)) { - int q; - - q = bus_process_wait(d->bus); - if (q < 0) - return log_error_errno(q, "Failed to wait for response: %m"); - - if (d->result) { - q = check_wait_response(d, quiet, extra_args); - /* Return the first error as it is most likely to be - * meaningful. */ - if (q < 0 && r == 0) - r = q; - - log_debug_errno(q, "Got result %s/%m for job %s", strna(d->result), strna(d->name)); - } - - d->name = mfree(d->name); - d->result = mfree(d->result); - } - - return r; -} - -int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path) { - int r; - - assert(d); - - r = set_ensure_allocated(&d->jobs, &string_hash_ops); - if (r < 0) - return r; - - return set_put_strdup(d->jobs, path); -} - -int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet) { - int r; - - r = bus_wait_for_jobs_add(d, path); - if (r < 0) - return log_oom(); - - return bus_wait_for_jobs(d, quiet, NULL); -} - -int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, UnitFileChange **changes, unsigned *n_changes) { - const char *type, *path, *source; - int r; - - /* changes is dereferenced when calling unit_file_dump_changes() later, - * so we have to make sure this is not NULL. */ - assert(changes); - assert(n_changes); - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sss)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(sss)", &type, &path, &source)) > 0) { - /* We expect only "success" changes to be sent over the bus. - Hence, reject anything negative. */ - UnitFileChangeType ch = unit_file_change_type_from_string(type); - - if (ch < 0) { - log_notice("Manager reported unknown change type \"%s\" for path \"%s\", ignoring.", type, path); - continue; - } - - r = unit_file_changes_add(changes, n_changes, ch, path, source); - if (r < 0) - return r; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - unit_file_dump_changes(0, NULL, *changes, *n_changes, false); - return 0; -} - -struct CGroupInfo { - char *cgroup_path; - bool is_const; /* If false, cgroup_path should be free()'d */ - - Hashmap *pids; /* PID → process name */ - bool done; - - struct CGroupInfo *parent; - LIST_FIELDS(struct CGroupInfo, siblings); - LIST_HEAD(struct CGroupInfo, children); - size_t n_children; -}; - -static bool IS_ROOT(const char *p) { - return isempty(p) || streq(p, "/"); -} - -static int add_cgroup(Hashmap *cgroups, const char *path, bool is_const, struct CGroupInfo **ret) { - struct CGroupInfo *parent = NULL, *cg; - int r; - - assert(cgroups); - assert(ret); - - if (IS_ROOT(path)) - path = "/"; - - cg = hashmap_get(cgroups, path); - if (cg) { - *ret = cg; - return 0; - } - - if (!IS_ROOT(path)) { - const char *e, *pp; - - e = strrchr(path, '/'); - if (!e) - return -EINVAL; - - pp = strndupa(path, e - path); - if (!pp) - return -ENOMEM; - - r = add_cgroup(cgroups, pp, false, &parent); - if (r < 0) - return r; - } - - cg = new0(struct CGroupInfo, 1); - if (!cg) - return -ENOMEM; - - if (is_const) - cg->cgroup_path = (char*) path; - else { - cg->cgroup_path = strdup(path); - if (!cg->cgroup_path) { - free(cg); - return -ENOMEM; - } - } - - cg->is_const = is_const; - cg->parent = parent; - - r = hashmap_put(cgroups, cg->cgroup_path, cg); - if (r < 0) { - if (!is_const) - free(cg->cgroup_path); - free(cg); - return r; - } - - if (parent) { - LIST_PREPEND(siblings, parent->children, cg); - parent->n_children++; - } - - *ret = cg; - return 1; -} - -static int add_process( - Hashmap *cgroups, - const char *path, - pid_t pid, - const char *name) { - - struct CGroupInfo *cg; - int r; - - assert(cgroups); - assert(name); - assert(pid > 0); - - r = add_cgroup(cgroups, path, true, &cg); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&cg->pids, &trivial_hash_ops); - if (r < 0) - return r; - - return hashmap_put(cg->pids, PID_TO_PTR(pid), (void*) name); -} - -static void remove_cgroup(Hashmap *cgroups, struct CGroupInfo *cg) { - assert(cgroups); - assert(cg); - - while (cg->children) - remove_cgroup(cgroups, cg->children); - - hashmap_remove(cgroups, cg->cgroup_path); - - if (!cg->is_const) - free(cg->cgroup_path); - - hashmap_free(cg->pids); - - if (cg->parent) - LIST_REMOVE(siblings, cg->parent->children, cg); - - free(cg); -} - -static int cgroup_info_compare_func(const void *a, const void *b) { - const struct CGroupInfo *x = *(const struct CGroupInfo* const*) a, *y = *(const struct CGroupInfo* const*) b; - - assert(x); - assert(y); - - return strcmp(x->cgroup_path, y->cgroup_path); -} - -static int dump_processes( - Hashmap *cgroups, - const char *cgroup_path, - const char *prefix, - unsigned n_columns, - OutputFlags flags) { - - struct CGroupInfo *cg; - int r; - - assert(prefix); - - if (IS_ROOT(cgroup_path)) - cgroup_path = "/"; - - cg = hashmap_get(cgroups, cgroup_path); - if (!cg) - return 0; - - if (!hashmap_isempty(cg->pids)) { - const char *name; - size_t n = 0, i; - pid_t *pids; - void *pidp; - Iterator j; - int width; - - /* Order processes by their PID */ - pids = newa(pid_t, hashmap_size(cg->pids)); - - HASHMAP_FOREACH_KEY(name, pidp, cg->pids, j) - pids[n++] = PTR_TO_PID(pidp); - - assert(n == hashmap_size(cg->pids)); - qsort_safe(pids, n, sizeof(pid_t), pid_compare_func); - - width = DECIMAL_STR_WIDTH(pids[n-1]); - - for (i = 0; i < n; i++) { - _cleanup_free_ char *e = NULL; - const char *special; - bool more; - - name = hashmap_get(cg->pids, PID_TO_PTR(pids[i])); - assert(name); - - if (n_columns != 0) { - unsigned k; - - k = MAX(LESS_BY(n_columns, 2U + width + 1U), 20U); - - e = ellipsize(name, k, 100); - if (e) - name = e; - } - - more = i+1 < n || cg->children; - special = special_glyph(more ? TREE_BRANCH : TREE_RIGHT); - - fprintf(stdout, "%s%s%*"PID_PRI" %s\n", - prefix, - special, - width, pids[i], - name); - } - } - - if (cg->children) { - struct CGroupInfo **children, *child; - size_t n = 0, i; - - /* Order subcgroups by their name */ - children = newa(struct CGroupInfo*, cg->n_children); - LIST_FOREACH(siblings, child, cg->children) - children[n++] = child; - assert(n == cg->n_children); - qsort_safe(children, n, sizeof(struct CGroupInfo*), cgroup_info_compare_func); - - n_columns = MAX(LESS_BY(n_columns, 2U), 20U); - - for (i = 0; i < n; i++) { - _cleanup_free_ char *pp = NULL; - const char *name, *special; - bool more; - - child = children[i]; - - name = strrchr(child->cgroup_path, '/'); - if (!name) - return -EINVAL; - name++; - - more = i+1 < n; - special = special_glyph(more ? TREE_BRANCH : TREE_RIGHT); - - fputs(prefix, stdout); - fputs(special, stdout); - fputs(name, stdout); - fputc('\n', stdout); - - special = special_glyph(more ? TREE_VERTICAL : TREE_SPACE); - - pp = strappend(prefix, special); - if (!pp) - return -ENOMEM; - - r = dump_processes(cgroups, child->cgroup_path, pp, n_columns, flags); - if (r < 0) - return r; - } - } - - cg->done = true; - return 0; -} - -static int dump_extra_processes( - Hashmap *cgroups, - const char *prefix, - unsigned n_columns, - OutputFlags flags) { - - _cleanup_free_ pid_t *pids = NULL; - _cleanup_hashmap_free_ Hashmap *names = NULL; - struct CGroupInfo *cg; - size_t n_allocated = 0, n = 0, k; - Iterator i; - int width, r; - - /* Prints the extra processes, i.e. those that are in cgroups we haven't displayed yet. We show them as - * combined, sorted, linear list. */ - - HASHMAP_FOREACH(cg, cgroups, i) { - const char *name; - void *pidp; - Iterator j; - - if (cg->done) - continue; - - if (hashmap_isempty(cg->pids)) - continue; - - r = hashmap_ensure_allocated(&names, &trivial_hash_ops); - if (r < 0) - return r; - - if (!GREEDY_REALLOC(pids, n_allocated, n + hashmap_size(cg->pids))) - return -ENOMEM; - - HASHMAP_FOREACH_KEY(name, pidp, cg->pids, j) { - pids[n++] = PTR_TO_PID(pidp); - - r = hashmap_put(names, pidp, (void*) name); - if (r < 0) - return r; - } - } - - if (n == 0) - return 0; - - qsort_safe(pids, n, sizeof(pid_t), pid_compare_func); - width = DECIMAL_STR_WIDTH(pids[n-1]); - - for (k = 0; k < n; k++) { - _cleanup_free_ char *e = NULL; - const char *name; - - name = hashmap_get(names, PID_TO_PTR(pids[k])); - assert(name); - - if (n_columns != 0) { - unsigned z; - - z = MAX(LESS_BY(n_columns, 2U + width + 1U), 20U); - - e = ellipsize(name, z, 100); - if (e) - name = e; - } - - fprintf(stdout, "%s%s %*" PID_PRI " %s\n", - prefix, - special_glyph(TRIANGULAR_BULLET), - width, pids[k], - name); - } - - return 0; -} - -int unit_show_processes( - sd_bus *bus, - const char *unit, - const char *cgroup_path, - const char *prefix, - unsigned n_columns, - OutputFlags flags, - sd_bus_error *error) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - Hashmap *cgroups = NULL; - struct CGroupInfo *cg; - int r; - - assert(bus); - assert(unit); - - if (flags & OUTPUT_FULL_WIDTH) - n_columns = 0; - else if (n_columns <= 0) - n_columns = columns(); - - prefix = strempty(prefix); - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GetUnitProcesses", - error, - &reply, - "s", - unit); - if (r < 0) - return r; - - cgroups = hashmap_new(&string_hash_ops); - if (!cgroups) - return -ENOMEM; - - r = sd_bus_message_enter_container(reply, 'a', "(sus)"); - if (r < 0) - goto finish; - - for (;;) { - const char *path = NULL, *name = NULL; - uint32_t pid; - - r = sd_bus_message_read(reply, "(sus)", &path, &pid, &name); - if (r < 0) - goto finish; - if (r == 0) - break; - - r = add_process(cgroups, path, pid, name); - if (r < 0) - goto finish; - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto finish; - - r = dump_processes(cgroups, cgroup_path, prefix, n_columns, flags); - if (r < 0) - goto finish; - - r = dump_extra_processes(cgroups, prefix, n_columns, flags); - -finish: - while ((cg = hashmap_first(cgroups))) - remove_cgroup(cgroups, cg); - - hashmap_free(cgroups); - - return r; -} diff --git a/src/shared/bus-unit-util.h b/src/shared/bus-unit-util.h deleted file mode 100644 index c0c172f336..0000000000 --- a/src/shared/bus-unit-util.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2016 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 "sd-bus.h" - -#include "output-mode.h" -#include "install.h" - -typedef struct UnitInfo { - const char *machine; - const char *id; - const char *description; - const char *load_state; - const char *active_state; - const char *sub_state; - const char *following; - const char *unit_path; - uint32_t job_id; - const char *job_type; - const char *job_path; -} UnitInfo; - -int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u); - -int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignment); - -typedef struct BusWaitForJobs BusWaitForJobs; - -int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret); -void bus_wait_for_jobs_free(BusWaitForJobs *d); -int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path); -int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char* const* extra_args); -int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet); - -DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForJobs*, bus_wait_for_jobs_free); - -int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, UnitFileChange **changes, unsigned *n_changes); - -int unit_show_processes(sd_bus *bus, const char *unit, const char *cgroup_path, const char *prefix, unsigned n_columns, OutputFlags flags, sd_bus_error *error); diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c deleted file mode 100644 index 4efbf3710f..0000000000 --- a/src/shared/bus-util.c +++ /dev/null @@ -1,1571 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-bus-protocol.h" -#include "sd-bus.h" -#include "sd-daemon.h" -#include "sd-event.h" -#include "sd-id128.h" - -#include "alloc-util.h" -#include "bus-internal.h" -#include "bus-label.h" -#include "bus-message.h" -#include "bus-util.h" -#include "def.h" -#include "escape.h" -#include "fd-util.h" -#include "missing.h" -#include "parse-util.h" -#include "proc-cmdline.h" -#include "rlimit-util.h" -#include "stdio-util.h" -#include "strv.h" -#include "user-util.h" - -static int name_owner_change_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - sd_event *e = userdata; - - assert(m); - assert(e); - - sd_bus_close(sd_bus_message_get_bus(m)); - sd_event_exit(e, 0); - - return 1; -} - -int bus_async_unregister_and_exit(sd_event *e, sd_bus *bus, const char *name) { - _cleanup_free_ char *match = NULL; - const char *unique; - int r; - - assert(e); - assert(bus); - assert(name); - - /* We unregister the name here and then wait for the - * NameOwnerChanged signal for this event to arrive before we - * quit. We do this in order to make sure that any queued - * requests are still processed before we really exit. */ - - r = sd_bus_get_unique_name(bus, &unique); - if (r < 0) - return r; - - r = asprintf(&match, - "sender='org.freedesktop.DBus'," - "type='signal'," - "interface='org.freedesktop.DBus'," - "member='NameOwnerChanged'," - "path='/org/freedesktop/DBus'," - "arg0='%s'," - "arg1='%s'," - "arg2=''", name, unique); - if (r < 0) - return -ENOMEM; - - r = sd_bus_add_match(bus, NULL, match, name_owner_change_callback, e); - if (r < 0) - return r; - - r = sd_bus_release_name(bus, name); - if (r < 0) - return r; - - return 0; -} - -int bus_event_loop_with_idle( - sd_event *e, - sd_bus *bus, - const char *name, - usec_t timeout, - check_idle_t check_idle, - void *userdata) { - bool exiting = false; - int r, code; - - assert(e); - assert(bus); - assert(name); - - for (;;) { - bool idle; - - r = sd_event_get_state(e); - if (r < 0) - return r; - if (r == SD_EVENT_FINISHED) - break; - - if (check_idle) - idle = check_idle(userdata); - else - idle = true; - - r = sd_event_run(e, exiting || !idle ? (uint64_t) -1 : timeout); - if (r < 0) - return r; - - if (r == 0 && !exiting && idle) { - - r = sd_bus_try_close(bus); - if (r == -EBUSY) - continue; - - /* Fallback for dbus1 connections: we - * unregister the name and wait for the - * response to come through for it */ - if (r == -EOPNOTSUPP) { - - /* Inform the service manager that we - * are going down, so that it will - * queue all further start requests, - * instead of assuming we are already - * running. */ - sd_notify(false, "STOPPING=1"); - - r = bus_async_unregister_and_exit(e, bus, name); - if (r < 0) - return r; - - exiting = true; - continue; - } - - if (r < 0) - return r; - - sd_event_exit(e, 0); - break; - } - } - - r = sd_event_get_exit_code(e, &code); - if (r < 0) - return r; - - return code; -} - -int bus_name_has_owner(sd_bus *c, const char *name, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *rep = NULL; - int r, has_owner = 0; - - assert(c); - assert(name); - - r = sd_bus_call_method(c, - "org.freedesktop.DBus", - "/org/freedesktop/dbus", - "org.freedesktop.DBus", - "NameHasOwner", - error, - &rep, - "s", - name); - if (r < 0) - return r; - - r = sd_bus_message_read_basic(rep, 'b', &has_owner); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - return has_owner; -} - -static int check_good_user(sd_bus_message *m, uid_t good_user) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - uid_t sender_uid; - int r; - - assert(m); - - if (good_user == UID_INVALID) - return 0; - - r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &creds); - if (r < 0) - return r; - - /* Don't trust augmented credentials for authorization */ - assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_EUID) == 0, -EPERM); - - r = sd_bus_creds_get_euid(creds, &sender_uid); - if (r < 0) - return r; - - return sender_uid == good_user; -} - -int bus_test_polkit( - sd_bus_message *call, - int capability, - const char *action, - const char **details, - uid_t good_user, - bool *_challenge, - sd_bus_error *e) { - - int r; - - assert(call); - assert(action); - - /* Tests non-interactively! */ - - r = check_good_user(call, good_user); - if (r != 0) - return r; - - r = sd_bus_query_sender_privilege(call, capability); - if (r < 0) - return r; - else if (r > 0) - return 1; -#ifdef ENABLE_POLKIT - else { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *request = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int authorized = false, challenge = false; - const char *sender, **k, **v; - - sender = sd_bus_message_get_sender(call); - if (!sender) - return -EBADMSG; - - r = sd_bus_message_new_method_call( - call->bus, - &request, - "org.freedesktop.PolicyKit1", - "/org/freedesktop/PolicyKit1/Authority", - "org.freedesktop.PolicyKit1.Authority", - "CheckAuthorization"); - if (r < 0) - return r; - - r = sd_bus_message_append( - request, - "(sa{sv})s", - "system-bus-name", 1, "name", "s", sender, - action); - if (r < 0) - return r; - - r = sd_bus_message_open_container(request, 'a', "{ss}"); - if (r < 0) - return r; - - STRV_FOREACH_PAIR(k, v, details) { - r = sd_bus_message_append(request, "{ss}", *k, *v); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(request); - if (r < 0) - return r; - - r = sd_bus_message_append(request, "us", 0, NULL); - if (r < 0) - return r; - - r = sd_bus_call(call->bus, request, 0, e, &reply); - if (r < 0) { - /* Treat no PK available as access denied */ - if (sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN)) { - sd_bus_error_free(e); - return -EACCES; - } - - return r; - } - - r = sd_bus_message_enter_container(reply, 'r', "bba{ss}"); - if (r < 0) - return r; - - r = sd_bus_message_read(reply, "bb", &authorized, &challenge); - if (r < 0) - return r; - - if (authorized) - return 1; - - if (_challenge) { - *_challenge = challenge; - return 0; - } - } -#endif - - return -EACCES; -} - -#ifdef ENABLE_POLKIT - -typedef struct AsyncPolkitQuery { - sd_bus_message *request, *reply; - sd_bus_message_handler_t callback; - void *userdata; - sd_bus_slot *slot; - Hashmap *registry; -} AsyncPolkitQuery; - -static void async_polkit_query_free(AsyncPolkitQuery *q) { - - if (!q) - return; - - sd_bus_slot_unref(q->slot); - - if (q->registry && q->request) - hashmap_remove(q->registry, q->request); - - sd_bus_message_unref(q->request); - sd_bus_message_unref(q->reply); - - free(q); -} - -static int async_polkit_callback(sd_bus_message *reply, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; - AsyncPolkitQuery *q = userdata; - int r; - - assert(reply); - assert(q); - - q->slot = sd_bus_slot_unref(q->slot); - q->reply = sd_bus_message_ref(reply); - - r = sd_bus_message_rewind(q->request, true); - if (r < 0) { - r = sd_bus_reply_method_errno(q->request, r, NULL); - goto finish; - } - - r = q->callback(q->request, q->userdata, &error_buffer); - r = bus_maybe_reply_error(q->request, r, &error_buffer); - -finish: - async_polkit_query_free(q); - - return r; -} - -#endif - -int bus_verify_polkit_async( - sd_bus_message *call, - int capability, - const char *action, - const char **details, - bool interactive, - uid_t good_user, - Hashmap **registry, - sd_bus_error *error) { - -#ifdef ENABLE_POLKIT - _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL; - AsyncPolkitQuery *q; - const char *sender, **k, **v; - sd_bus_message_handler_t callback; - void *userdata; - int c; -#endif - int r; - - assert(call); - assert(action); - assert(registry); - - r = check_good_user(call, good_user); - if (r != 0) - return r; - -#ifdef ENABLE_POLKIT - q = hashmap_get(*registry, call); - if (q) { - int authorized, challenge; - - /* This is the second invocation of this function, and - * there's already a response from polkit, let's - * process it */ - assert(q->reply); - - if (sd_bus_message_is_method_error(q->reply, NULL)) { - const sd_bus_error *e; - - /* Copy error from polkit reply */ - e = sd_bus_message_get_error(q->reply); - sd_bus_error_copy(error, e); - - /* Treat no PK available as access denied */ - if (sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN)) - return -EACCES; - - return -sd_bus_error_get_errno(e); - } - - r = sd_bus_message_enter_container(q->reply, 'r', "bba{ss}"); - if (r >= 0) - r = sd_bus_message_read(q->reply, "bb", &authorized, &challenge); - - if (r < 0) - return r; - - if (authorized) - return 1; - - if (challenge) - return sd_bus_error_set(error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED, "Interactive authentication required."); - - return -EACCES; - } -#endif - - r = sd_bus_query_sender_privilege(call, capability); - if (r < 0) - return r; - else if (r > 0) - return 1; - -#ifdef ENABLE_POLKIT - if (sd_bus_get_current_message(call->bus) != call) - return -EINVAL; - - callback = sd_bus_get_current_handler(call->bus); - if (!callback) - return -EINVAL; - - userdata = sd_bus_get_current_userdata(call->bus); - - sender = sd_bus_message_get_sender(call); - if (!sender) - return -EBADMSG; - - c = sd_bus_message_get_allow_interactive_authorization(call); - if (c < 0) - return c; - if (c > 0) - interactive = true; - - r = hashmap_ensure_allocated(registry, NULL); - if (r < 0) - return r; - - r = sd_bus_message_new_method_call( - call->bus, - &pk, - "org.freedesktop.PolicyKit1", - "/org/freedesktop/PolicyKit1/Authority", - "org.freedesktop.PolicyKit1.Authority", - "CheckAuthorization"); - if (r < 0) - return r; - - r = sd_bus_message_append( - pk, - "(sa{sv})s", - "system-bus-name", 1, "name", "s", sender, - action); - if (r < 0) - return r; - - r = sd_bus_message_open_container(pk, 'a', "{ss}"); - if (r < 0) - return r; - - STRV_FOREACH_PAIR(k, v, details) { - r = sd_bus_message_append(pk, "{ss}", *k, *v); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(pk); - if (r < 0) - return r; - - r = sd_bus_message_append(pk, "us", !!interactive, NULL); - if (r < 0) - return r; - - q = new0(AsyncPolkitQuery, 1); - if (!q) - return -ENOMEM; - - q->request = sd_bus_message_ref(call); - q->callback = callback; - q->userdata = userdata; - - r = hashmap_put(*registry, call, q); - if (r < 0) { - async_polkit_query_free(q); - return r; - } - - q->registry = *registry; - - r = sd_bus_call_async(call->bus, &q->slot, pk, async_polkit_callback, q, 0); - if (r < 0) { - async_polkit_query_free(q); - return r; - } - - return 0; -#endif - - return -EACCES; -} - -void bus_verify_polkit_async_registry_free(Hashmap *registry) { -#ifdef ENABLE_POLKIT - AsyncPolkitQuery *q; - - while ((q = hashmap_steal_first(registry))) - async_polkit_query_free(q); - - hashmap_free(registry); -#endif -} - -int bus_check_peercred(sd_bus *c) { - struct ucred ucred; - socklen_t l; - int fd; - - assert(c); - - fd = sd_bus_get_fd(c); - if (fd < 0) - return fd; - - l = sizeof(struct ucred); - if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l) < 0) - return -errno; - - if (l != sizeof(struct ucred)) - return -E2BIG; - - if (ucred.uid != 0 && ucred.uid != geteuid()) - return -EPERM; - - return 1; -} - -int bus_connect_system_systemd(sd_bus **_bus) { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - int r; - - assert(_bus); - - if (geteuid() != 0) - return sd_bus_default_system(_bus); - - /* If we are root and kdbus is not available, then let's talk - * directly to the system instance, instead of going via the - * bus */ - - r = sd_bus_new(&bus); - if (r < 0) - return r; - - r = sd_bus_set_address(bus, KERNEL_SYSTEM_BUS_ADDRESS); - if (r < 0) - return r; - - bus->bus_client = true; - - r = sd_bus_start(bus); - if (r >= 0) { - *_bus = bus; - bus = NULL; - return 0; - } - - bus = sd_bus_unref(bus); - - r = sd_bus_new(&bus); - if (r < 0) - return r; - - r = sd_bus_set_address(bus, "unix:path=/run/systemd/private"); - if (r < 0) - return r; - - r = sd_bus_start(bus); - if (r < 0) - return sd_bus_default_system(_bus); - - r = bus_check_peercred(bus); - if (r < 0) - return r; - - *_bus = bus; - bus = NULL; - - return 0; -} - -int bus_connect_user_systemd(sd_bus **_bus) { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - _cleanup_free_ char *ee = NULL; - const char *e; - int r; - - /* Try via kdbus first, and then directly */ - - assert(_bus); - - r = sd_bus_new(&bus); - if (r < 0) - return r; - - if (asprintf(&bus->address, KERNEL_USER_BUS_ADDRESS_FMT, getuid()) < 0) - return -ENOMEM; - - bus->bus_client = true; - - r = sd_bus_start(bus); - if (r >= 0) { - *_bus = bus; - bus = NULL; - return 0; - } - - bus = sd_bus_unref(bus); - - e = secure_getenv("XDG_RUNTIME_DIR"); - if (!e) - return sd_bus_default_user(_bus); - - ee = bus_address_escape(e); - if (!ee) - return -ENOMEM; - - r = sd_bus_new(&bus); - if (r < 0) - return r; - - bus->address = strjoin("unix:path=", ee, "/systemd/private", NULL); - if (!bus->address) - return -ENOMEM; - - r = sd_bus_start(bus); - if (r < 0) - return sd_bus_default_user(_bus); - - r = bus_check_peercred(bus); - if (r < 0) - return r; - - *_bus = bus; - bus = NULL; - - return 0; -} - -#define print_property(name, fmt, ...) \ - do { \ - if (value) \ - printf(fmt "\n", __VA_ARGS__); \ - else \ - printf("%s=" fmt "\n", name, __VA_ARGS__); \ - } while(0) - -int bus_print_property(const char *name, sd_bus_message *property, bool value, bool all) { - char type; - const char *contents; - int r; - - assert(name); - assert(property); - - r = sd_bus_message_peek_type(property, &type, &contents); - if (r < 0) - return r; - - switch (type) { - - case SD_BUS_TYPE_STRING: { - const char *s; - - r = sd_bus_message_read_basic(property, type, &s); - if (r < 0) - return r; - - if (all || !isempty(s)) { - _cleanup_free_ char *escaped = NULL; - - escaped = xescape(s, "\n"); - if (!escaped) - return -ENOMEM; - - print_property(name, "%s", escaped); - } - - return 1; - } - - case SD_BUS_TYPE_BOOLEAN: { - int b; - - r = sd_bus_message_read_basic(property, type, &b); - if (r < 0) - return r; - - print_property(name, "%s", yes_no(b)); - - return 1; - } - - case SD_BUS_TYPE_UINT64: { - uint64_t u; - - r = sd_bus_message_read_basic(property, type, &u); - if (r < 0) - return r; - - /* Yes, heuristics! But we can change this check - * should it turn out to not be sufficient */ - - if (endswith(name, "Timestamp")) { - char timestamp[FORMAT_TIMESTAMP_MAX], *t; - - t = format_timestamp(timestamp, sizeof(timestamp), u); - if (t || all) - print_property(name, "%s", strempty(t)); - - } else if (strstr(name, "USec")) { - char timespan[FORMAT_TIMESPAN_MAX]; - - print_property(name, "%s", format_timespan(timespan, sizeof(timespan), u, 0)); - } else - print_property(name, "%"PRIu64, u); - - return 1; - } - - case SD_BUS_TYPE_INT64: { - int64_t i; - - r = sd_bus_message_read_basic(property, type, &i); - if (r < 0) - return r; - - print_property(name, "%"PRIi64, i); - - return 1; - } - - case SD_BUS_TYPE_UINT32: { - uint32_t u; - - r = sd_bus_message_read_basic(property, type, &u); - if (r < 0) - return r; - - if (strstr(name, "UMask") || strstr(name, "Mode")) - print_property(name, "%04o", u); - else - print_property(name, "%"PRIu32, u); - - return 1; - } - - case SD_BUS_TYPE_INT32: { - int32_t i; - - r = sd_bus_message_read_basic(property, type, &i); - if (r < 0) - return r; - - print_property(name, "%"PRIi32, i); - return 1; - } - - case SD_BUS_TYPE_DOUBLE: { - double d; - - r = sd_bus_message_read_basic(property, type, &d); - if (r < 0) - return r; - - print_property(name, "%g", d); - return 1; - } - - case SD_BUS_TYPE_ARRAY: - if (streq(contents, "s")) { - bool first = true; - const char *str; - - r = sd_bus_message_enter_container(property, SD_BUS_TYPE_ARRAY, contents); - if (r < 0) - return r; - - while ((r = sd_bus_message_read_basic(property, SD_BUS_TYPE_STRING, &str)) > 0) { - _cleanup_free_ char *escaped = NULL; - - if (first && !value) - printf("%s=", name); - - escaped = xescape(str, "\n "); - if (!escaped) - return -ENOMEM; - - printf("%s%s", first ? "" : " ", escaped); - - first = false; - } - if (r < 0) - return r; - - if (first && all && !value) - printf("%s=", name); - if (!first || all) - puts(""); - - r = sd_bus_message_exit_container(property); - if (r < 0) - return r; - - return 1; - - } else if (streq(contents, "y")) { - const uint8_t *u; - size_t n; - - r = sd_bus_message_read_array(property, SD_BUS_TYPE_BYTE, (const void**) &u, &n); - if (r < 0) - return r; - - if (all || n > 0) { - unsigned int i; - - if (!value) - printf("%s=", name); - - for (i = 0; i < n; i++) - printf("%02x", u[i]); - - puts(""); - } - - return 1; - - } else if (streq(contents, "u")) { - uint32_t *u; - size_t n; - - r = sd_bus_message_read_array(property, SD_BUS_TYPE_UINT32, (const void**) &u, &n); - if (r < 0) - return r; - - if (all || n > 0) { - unsigned int i; - - if (!value) - printf("%s=", name); - - for (i = 0; i < n; i++) - printf("%08x", u[i]); - - puts(""); - } - - return 1; - } - - break; - } - - return 0; -} - -int bus_print_all_properties(sd_bus *bus, const char *dest, const char *path, char **filter, bool value, bool all) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(path); - - r = sd_bus_call_method(bus, - dest, - path, - "org.freedesktop.DBus.Properties", - "GetAll", - &error, - &reply, - "s", ""); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sv}"); - if (r < 0) - return r; - - while ((r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { - const char *name; - const char *contents; - - r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_STRING, &name); - if (r < 0) - return r; - - if (!filter || strv_find(filter, name)) { - r = sd_bus_message_peek_type(reply, NULL, &contents); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_VARIANT, contents); - if (r < 0) - return r; - - r = bus_print_property(name, reply, value, all); - if (r < 0) - return r; - if (r == 0) { - if (all) - printf("%s=[unprintable]\n", name); - /* skip what we didn't read */ - r = sd_bus_message_skip(reply, contents); - if (r < 0) - return r; - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return r; - } else { - r = sd_bus_message_skip(reply, "v"); - if (r < 0) - return r; - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return r; - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return r; - - return 0; -} - -int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { - sd_id128_t *p = userdata; - const void *v; - size_t n; - int r; - - r = sd_bus_message_read_array(m, SD_BUS_TYPE_BYTE, &v, &n); - if (r < 0) - return r; - - if (n == 0) - *p = SD_ID128_NULL; - else if (n == 16) - memcpy((*p).bytes, v, n); - else - return -EINVAL; - - return 0; -} - -static int map_basic(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { - char type; - int r; - - r = sd_bus_message_peek_type(m, &type, NULL); - if (r < 0) - return r; - - switch (type) { - case SD_BUS_TYPE_STRING: { - const char *s; - char **p = userdata; - - r = sd_bus_message_read_basic(m, type, &s); - if (r < 0) - break; - - if (isempty(s)) - break; - - r = free_and_strdup(p, s); - break; - } - - case SD_BUS_TYPE_ARRAY: { - _cleanup_strv_free_ char **l = NULL; - char ***p = userdata; - - r = bus_message_read_strv_extend(m, &l); - if (r < 0) - break; - - strv_free(*p); - *p = l; - l = NULL; - - break; - } - - case SD_BUS_TYPE_BOOLEAN: { - unsigned b; - bool *p = userdata; - - r = sd_bus_message_read_basic(m, type, &b); - if (r < 0) - break; - - *p = b; - - break; - } - - case SD_BUS_TYPE_UINT32: { - uint32_t u; - uint32_t *p = userdata; - - r = sd_bus_message_read_basic(m, type, &u); - if (r < 0) - break; - - *p = u; - - break; - } - - case SD_BUS_TYPE_UINT64: { - uint64_t t; - uint64_t *p = userdata; - - r = sd_bus_message_read_basic(m, type, &t); - if (r < 0) - break; - - *p = t; - - break; - } - - default: - break; - } - - return r; -} - -int bus_message_map_all_properties( - sd_bus_message *m, - const struct bus_properties_map *map, - void *userdata) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(m); - assert(map); - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}"); - if (r < 0) - return r; - - while ((r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { - const struct bus_properties_map *prop; - const char *member; - const char *contents; - void *v; - unsigned i; - - r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &member); - if (r < 0) - return r; - - for (i = 0, prop = NULL; map[i].member; i++) - if (streq(map[i].member, member)) { - prop = &map[i]; - break; - } - - if (prop) { - r = sd_bus_message_peek_type(m, NULL, &contents); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, contents); - if (r < 0) - return r; - - v = (uint8_t *)userdata + prop->offset; - if (map[i].set) - r = prop->set(sd_bus_message_get_bus(m), member, m, &error, v); - else - r = map_basic(sd_bus_message_get_bus(m), member, m, &error, v); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - } else { - r = sd_bus_message_skip(m, "v"); - if (r < 0) - return r; - } - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - } - if (r < 0) - return r; - - return sd_bus_message_exit_container(m); -} - -int bus_message_map_properties_changed( - sd_bus_message *m, - const struct bus_properties_map *map, - void *userdata) { - - const char *member; - int r, invalidated, i; - - assert(m); - assert(map); - - r = bus_message_map_all_properties(m, map, userdata); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "s"); - if (r < 0) - return r; - - invalidated = 0; - while ((r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &member)) > 0) - for (i = 0; map[i].member; i++) - if (streq(map[i].member, member)) { - ++invalidated; - break; - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - return invalidated; -} - -int bus_map_all_properties( - sd_bus *bus, - const char *destination, - const char *path, - const struct bus_properties_map *map, - void *userdata) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(destination); - assert(path); - assert(map); - - r = sd_bus_call_method( - bus, - destination, - path, - "org.freedesktop.DBus.Properties", - "GetAll", - &error, - &m, - "s", ""); - if (r < 0) - return r; - - return bus_message_map_all_properties(m, map, userdata); -} - -int bus_connect_transport(BusTransport transport, const char *host, bool user, sd_bus **bus) { - int r; - - assert(transport >= 0); - assert(transport < _BUS_TRANSPORT_MAX); - assert(bus); - - assert_return((transport == BUS_TRANSPORT_LOCAL) == !host, -EINVAL); - assert_return(transport == BUS_TRANSPORT_LOCAL || !user, -EOPNOTSUPP); - - switch (transport) { - - case BUS_TRANSPORT_LOCAL: - if (user) - r = sd_bus_default_user(bus); - else - r = sd_bus_default_system(bus); - - break; - - case BUS_TRANSPORT_REMOTE: - r = sd_bus_open_system_remote(bus, host); - break; - - case BUS_TRANSPORT_MACHINE: - r = sd_bus_open_system_machine(bus, host); - break; - - default: - assert_not_reached("Hmm, unknown transport type."); - } - - return r; -} - -int bus_connect_transport_systemd(BusTransport transport, const char *host, bool user, sd_bus **bus) { - int r; - - assert(transport >= 0); - assert(transport < _BUS_TRANSPORT_MAX); - assert(bus); - - assert_return((transport == BUS_TRANSPORT_LOCAL) == !host, -EINVAL); - assert_return(transport == BUS_TRANSPORT_LOCAL || !user, -EOPNOTSUPP); - - switch (transport) { - - case BUS_TRANSPORT_LOCAL: - if (user) - r = bus_connect_user_systemd(bus); - else - r = bus_connect_system_systemd(bus); - - break; - - case BUS_TRANSPORT_REMOTE: - r = sd_bus_open_system_remote(bus, host); - break; - - case BUS_TRANSPORT_MACHINE: - r = sd_bus_open_system_machine(bus, host); - break; - - default: - assert_not_reached("Hmm, unknown transport type."); - } - - return r; -} - -int bus_property_get_bool( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - int b = *(bool*) userdata; - - return sd_bus_message_append_basic(reply, 'b', &b); -} - -#if __SIZEOF_SIZE_T__ != 8 -int bus_property_get_size( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - uint64_t sz = *(size_t*) userdata; - - return sd_bus_message_append_basic(reply, 't', &sz); -} -#endif - -#if __SIZEOF_LONG__ != 8 -int bus_property_get_long( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - int64_t l = *(long*) userdata; - - return sd_bus_message_append_basic(reply, 'x', &l); -} - -int bus_property_get_ulong( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - uint64_t ul = *(unsigned long*) userdata; - - return sd_bus_message_append_basic(reply, 't', &ul); -} -#endif - -int bus_log_parse_error(int r) { - return log_error_errno(r, "Failed to parse bus message: %m"); -} - -int bus_log_create_error(int r) { - return log_error_errno(r, "Failed to create bus message: %m"); -} - -/** - * bus_path_encode_unique() - encode unique object path - * @b: bus connection or NULL - * @prefix: object path prefix - * @sender_id: unique-name of client, or NULL - * @external_id: external ID to be chosen by client, or NULL - * @ret_path: storage for encoded object path pointer - * - * Whenever we provide a bus API that allows clients to create and manage - * server-side objects, we need to provide a unique name for these objects. If - * we let the server choose the name, we suffer from a race condition: If a - * client creates an object asynchronously, it cannot destroy that object until - * it received the method reply. It cannot know the name of the new object, - * thus, it cannot destroy it. Furthermore, it enforces a round-trip. - * - * Therefore, many APIs allow the client to choose the unique name for newly - * created objects. There're two problems to solve, though: - * 1) Object names are usually defined via dbus object paths, which are - * usually globally namespaced. Therefore, multiple clients must be able - * to choose unique object names without interference. - * 2) If multiple libraries share the same bus connection, they must be - * able to choose unique object names without interference. - * The first problem is solved easily by prefixing a name with the - * unique-bus-name of a connection. The server side must enforce this and - * reject any other name. The second problem is solved by providing unique - * suffixes from within sd-bus. - * - * This helper allows clients to create unique object-paths. It uses the - * template '/prefix/sender_id/external_id' and returns the new path in - * @ret_path (must be freed by the caller). - * If @sender_id is NULL, the unique-name of @b is used. If @external_id is - * NULL, this function allocates a unique suffix via @b (by requesting a new - * cookie). If both @sender_id and @external_id are given, @b can be passed as - * NULL. - * - * Returns: 0 on success, negative error code on failure. - */ -int bus_path_encode_unique(sd_bus *b, const char *prefix, const char *sender_id, const char *external_id, char **ret_path) { - _cleanup_free_ char *sender_label = NULL, *external_label = NULL; - char external_buf[DECIMAL_STR_MAX(uint64_t)], *p; - int r; - - assert_return(b || (sender_id && external_id), -EINVAL); - assert_return(object_path_is_valid(prefix), -EINVAL); - assert_return(ret_path, -EINVAL); - - if (!sender_id) { - r = sd_bus_get_unique_name(b, &sender_id); - if (r < 0) - return r; - } - - if (!external_id) { - xsprintf(external_buf, "%"PRIu64, ++b->cookie); - external_id = external_buf; - } - - sender_label = bus_label_escape(sender_id); - if (!sender_label) - return -ENOMEM; - - external_label = bus_label_escape(external_id); - if (!external_label) - return -ENOMEM; - - p = strjoin(prefix, "/", sender_label, "/", external_label, NULL); - if (!p) - return -ENOMEM; - - *ret_path = p; - return 0; -} - -/** - * bus_path_decode_unique() - decode unique object path - * @path: object path to decode - * @prefix: object path prefix - * @ret_sender: output parameter for sender-id label - * @ret_external: output parameter for external-id label - * - * This does the reverse of bus_path_encode_unique() (see its description for - * details). Both trailing labels, sender-id and external-id, are unescaped and - * returned in the given output parameters (the caller must free them). - * - * Note that this function returns 0 if the path does not match the template - * (see bus_path_encode_unique()), 1 if it matched. - * - * Returns: Negative error code on failure, 0 if the given object path does not - * match the template (return parameters are set to NULL), 1 if it was - * parsed successfully (return parameters contain allocated labels). - */ -int bus_path_decode_unique(const char *path, const char *prefix, char **ret_sender, char **ret_external) { - const char *p, *q; - char *sender, *external; - - assert(object_path_is_valid(path)); - assert(object_path_is_valid(prefix)); - assert(ret_sender); - assert(ret_external); - - p = object_path_startswith(path, prefix); - if (!p) { - *ret_sender = NULL; - *ret_external = NULL; - return 0; - } - - q = strchr(p, '/'); - if (!q) { - *ret_sender = NULL; - *ret_external = NULL; - return 0; - } - - sender = bus_label_unescape_n(p, q - p); - external = bus_label_unescape(q + 1); - if (!sender || !external) { - free(sender); - free(external); - return -ENOMEM; - } - - *ret_sender = sender; - *ret_external = external; - return 1; -} - -bool is_kdbus_wanted(void) { - _cleanup_free_ char *value = NULL; -#ifdef ENABLE_KDBUS - const bool configured = true; -#else - const bool configured = false; -#endif - - int r; - - if (get_proc_cmdline_key("kdbus", NULL) > 0) - return true; - - r = get_proc_cmdline_key("kdbus=", &value); - if (r <= 0) - return configured; - - return parse_boolean(value) == 1; -} - -bool is_kdbus_available(void) { - _cleanup_close_ int fd = -1; - struct kdbus_cmd cmd = { .size = sizeof(cmd), .flags = KDBUS_FLAG_NEGOTIATE }; - - if (!is_kdbus_wanted()) - return false; - - fd = open("/sys/fs/kdbus/control", O_RDWR | O_CLOEXEC | O_NONBLOCK | O_NOCTTY); - if (fd < 0) - return false; - - return ioctl(fd, KDBUS_CMD_BUS_MAKE, &cmd) >= 0; -} - -int bus_property_get_rlimit( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - struct rlimit *rl; - uint64_t u; - rlim_t x; - const char *is_soft; - - assert(bus); - assert(reply); - assert(userdata); - - is_soft = endswith(property, "Soft"); - rl = *(struct rlimit**) userdata; - if (rl) - x = is_soft ? rl->rlim_cur : rl->rlim_max; - else { - struct rlimit buf = {}; - int z; - const char *s; - - s = is_soft ? strndupa(property, is_soft - property) : property; - - z = rlimit_from_string(strstr(s, "Limit")); - assert(z >= 0); - - getrlimit(z, &buf); - x = is_soft ? buf.rlim_cur : buf.rlim_max; - } - - /* rlim_t might have different sizes, let's map - * RLIMIT_INFINITY to (uint64_t) -1, so that it is the same on - * all archs */ - u = x == RLIM_INFINITY ? (uint64_t) -1 : (uint64_t) x; - - return sd_bus_message_append(reply, "t", u); -} diff --git a/src/shared/bus-util.h b/src/shared/bus-util.h deleted file mode 100644 index d792258ecd..0000000000 --- a/src/shared/bus-util.h +++ /dev/null @@ -1,163 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 -#include -#include - -#include "sd-bus.h" -#include "sd-event.h" - -#include "hashmap.h" -#include "macro.h" -#include "string-util.h" - -typedef enum BusTransport { - BUS_TRANSPORT_LOCAL, - BUS_TRANSPORT_REMOTE, - BUS_TRANSPORT_MACHINE, - _BUS_TRANSPORT_MAX, - _BUS_TRANSPORT_INVALID = -1 -} BusTransport; - -typedef int (*bus_property_set_t) (sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata); - -struct bus_properties_map { - const char *member; - const char *signature; - bus_property_set_t set; - size_t offset; -}; - -int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata); - -int bus_message_map_all_properties(sd_bus_message *m, const struct bus_properties_map *map, void *userdata); -int bus_message_map_properties_changed(sd_bus_message *m, const struct bus_properties_map *map, void *userdata); -int bus_map_all_properties(sd_bus *bus, const char *destination, const char *path, const struct bus_properties_map *map, void *userdata); - -int bus_async_unregister_and_exit(sd_event *e, sd_bus *bus, const char *name); - -typedef bool (*check_idle_t)(void *userdata); - -int bus_event_loop_with_idle(sd_event *e, sd_bus *bus, const char *name, usec_t timeout, check_idle_t check_idle, void *userdata); - -int bus_name_has_owner(sd_bus *c, const char *name, sd_bus_error *error); - -int bus_check_peercred(sd_bus *c); - -int bus_test_polkit(sd_bus_message *call, int capability, const char *action, const char **details, uid_t good_user, bool *_challenge, sd_bus_error *e); - -int bus_verify_polkit_async(sd_bus_message *call, int capability, const char *action, const char **details, bool interactive, uid_t good_user, Hashmap **registry, sd_bus_error *error); -void bus_verify_polkit_async_registry_free(Hashmap *registry); - -int bus_connect_system_systemd(sd_bus **_bus); -int bus_connect_user_systemd(sd_bus **_bus); - -int bus_connect_transport(BusTransport transport, const char *host, bool user, sd_bus **bus); -int bus_connect_transport_systemd(BusTransport transport, const char *host, bool user, sd_bus **bus); - -int bus_print_property(const char *name, sd_bus_message *property, bool value, bool all); -int bus_print_all_properties(sd_bus *bus, const char *dest, const char *path, char **filter, bool value, bool all); - -int bus_property_get_bool(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); - -#define bus_property_get_usec ((sd_bus_property_get_t) NULL) -#define bus_property_set_usec ((sd_bus_property_set_t) NULL) - -assert_cc(sizeof(int) == sizeof(int32_t)); -#define bus_property_get_int ((sd_bus_property_get_t) NULL) - -assert_cc(sizeof(unsigned) == sizeof(unsigned)); -#define bus_property_get_unsigned ((sd_bus_property_get_t) NULL) - -/* On 64bit machines we can use the default serializer for size_t and - * friends, otherwise we need to cast this manually */ -#if __SIZEOF_SIZE_T__ == 8 -#define bus_property_get_size ((sd_bus_property_get_t) NULL) -#else -int bus_property_get_size(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); -#endif - -#if __SIZEOF_LONG__ == 8 -#define bus_property_get_long ((sd_bus_property_get_t) NULL) -#define bus_property_get_ulong ((sd_bus_property_get_t) NULL) -#else -int bus_property_get_long(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); -int bus_property_get_ulong(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); -#endif - -/* uid_t and friends on Linux 32 bit. This means we can just use the - * default serializer for 32bit unsigned, for serializing it, and map - * it to NULL here */ -assert_cc(sizeof(uid_t) == sizeof(uint32_t)); -#define bus_property_get_uid ((sd_bus_property_get_t) NULL) - -assert_cc(sizeof(gid_t) == sizeof(uint32_t)); -#define bus_property_get_gid ((sd_bus_property_get_t) NULL) - -assert_cc(sizeof(pid_t) == sizeof(uint32_t)); -#define bus_property_get_pid ((sd_bus_property_get_t) NULL) - -assert_cc(sizeof(mode_t) == sizeof(uint32_t)); -#define bus_property_get_mode ((sd_bus_property_get_t) NULL) - -int bus_log_parse_error(int r); -int bus_log_create_error(int r); - -#define BUS_DEFINE_PROPERTY_GET_ENUM(function, name, type) \ - int function(sd_bus *bus, \ - const char *path, \ - const char *interface, \ - const char *property, \ - sd_bus_message *reply, \ - void *userdata, \ - sd_bus_error *error) { \ - \ - const char *value; \ - type *field = userdata; \ - int r; \ - \ - assert(bus); \ - assert(reply); \ - assert(field); \ - \ - value = strempty(name##_to_string(*field)); \ - \ - r = sd_bus_message_append_basic(reply, 's', value); \ - if (r < 0) \ - return r; \ - \ - return 1; \ - } \ - struct __useless_struct_to_allow_trailing_semicolon__ - -#define BUS_PROPERTY_DUAL_TIMESTAMP(name, offset, flags) \ - SD_BUS_PROPERTY(name, "t", bus_property_get_usec, (offset) + offsetof(struct dual_timestamp, realtime), (flags)), \ - SD_BUS_PROPERTY(name "Monotonic", "t", bus_property_get_usec, (offset) + offsetof(struct dual_timestamp, monotonic), (flags)) - -int bus_path_encode_unique(sd_bus *b, const char *prefix, const char *sender_id, const char *external_id, char **ret_path); -int bus_path_decode_unique(const char *path, const char *prefix, char **ret_sender, char **ret_external); - -bool is_kdbus_wanted(void); -bool is_kdbus_available(void); - -int bus_property_get_rlimit(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); diff --git a/src/shared/cgroup-show.c b/src/shared/cgroup-show.c deleted file mode 100644 index 3e451db715..0000000000 --- a/src/shared/cgroup-show.c +++ /dev/null @@ -1,312 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include - -#include "alloc-util.h" -#include "cgroup-show.h" -#include "cgroup-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "locale-util.h" -#include "macro.h" -#include "output-mode.h" -#include "path-util.h" -#include "process-util.h" -#include "string-util.h" -#include "terminal-util.h" - -static void show_pid_array( - pid_t pids[], - unsigned n_pids, - const char *prefix, - unsigned n_columns, - bool extra, - bool more, - OutputFlags flags) { - - unsigned i, j, pid_width; - - if (n_pids == 0) - return; - - qsort(pids, n_pids, sizeof(pid_t), pid_compare_func); - - /* Filter duplicates */ - for (j = 0, i = 1; i < n_pids; i++) { - if (pids[i] == pids[j]) - continue; - pids[++j] = pids[i]; - } - n_pids = j + 1; - pid_width = DECIMAL_STR_WIDTH(pids[j]); - - if (flags & OUTPUT_FULL_WIDTH) - n_columns = 0; - else { - if (n_columns > pid_width+2) - n_columns -= pid_width+2; - else - n_columns = 20; - } - for (i = 0; i < n_pids; i++) { - _cleanup_free_ char *t = NULL; - - get_process_cmdline(pids[i], n_columns, true, &t); - - if (extra) - printf("%s%s ", prefix, special_glyph(TRIANGULAR_BULLET)); - else - printf("%s%s", prefix, special_glyph(((more || i < n_pids-1) ? TREE_BRANCH : TREE_RIGHT))); - - printf("%*"PID_PRI" %s\n", pid_width, pids[i], strna(t)); - } -} - -static int show_cgroup_one_by_path( - const char *path, - const char *prefix, - unsigned n_columns, - bool more, - OutputFlags flags) { - - char *fn; - _cleanup_fclose_ FILE *f = NULL; - size_t n = 0, n_allocated = 0; - _cleanup_free_ pid_t *pids = NULL; - _cleanup_free_ char *p = NULL; - pid_t pid; - int r; - - r = cg_mangle_path(path, &p); - if (r < 0) - return r; - - fn = strjoina(p, "/cgroup.procs"); - f = fopen(fn, "re"); - if (!f) - return -errno; - - while ((r = cg_read_pid(f, &pid)) > 0) { - - if (!(flags & OUTPUT_KERNEL_THREADS) && is_kernel_thread(pid) > 0) - continue; - - if (!GREEDY_REALLOC(pids, n_allocated, n + 1)) - return -ENOMEM; - - assert(n < n_allocated); - pids[n++] = pid; - } - - if (r < 0) - return r; - - show_pid_array(pids, n, prefix, n_columns, false, more, flags); - - return 0; -} - -int show_cgroup_by_path( - const char *path, - const char *prefix, - unsigned n_columns, - OutputFlags flags) { - - _cleanup_free_ char *fn = NULL, *p1 = NULL, *last = NULL, *p2 = NULL; - _cleanup_closedir_ DIR *d = NULL; - char *gn = NULL; - bool shown_pids = false; - int r; - - assert(path); - - if (n_columns <= 0) - n_columns = columns(); - - prefix = strempty(prefix); - - r = cg_mangle_path(path, &fn); - if (r < 0) - return r; - - d = opendir(fn); - if (!d) - return -errno; - - while ((r = cg_read_subgroup(d, &gn)) > 0) { - _cleanup_free_ char *k = NULL; - - k = strjoin(fn, "/", gn, NULL); - free(gn); - if (!k) - return -ENOMEM; - - if (!(flags & OUTPUT_SHOW_ALL) && cg_is_empty_recursive(NULL, k) > 0) - continue; - - if (!shown_pids) { - show_cgroup_one_by_path(path, prefix, n_columns, true, flags); - shown_pids = true; - } - - if (last) { - printf("%s%s%s\n", prefix, special_glyph(TREE_BRANCH), cg_unescape(basename(last))); - - if (!p1) { - p1 = strappend(prefix, special_glyph(TREE_VERTICAL)); - if (!p1) - return -ENOMEM; - } - - show_cgroup_by_path(last, p1, n_columns-2, flags); - free(last); - } - - last = k; - k = NULL; - } - - if (r < 0) - return r; - - if (!shown_pids) - show_cgroup_one_by_path(path, prefix, n_columns, !!last, flags); - - if (last) { - printf("%s%s%s\n", prefix, special_glyph(TREE_RIGHT), cg_unescape(basename(last))); - - if (!p2) { - p2 = strappend(prefix, " "); - if (!p2) - return -ENOMEM; - } - - show_cgroup_by_path(last, p2, n_columns-2, flags); - } - - return 0; -} - -int show_cgroup(const char *controller, - const char *path, - const char *prefix, - unsigned n_columns, - OutputFlags flags) { - _cleanup_free_ char *p = NULL; - int r; - - assert(path); - - r = cg_get_path(controller, path, NULL, &p); - if (r < 0) - return r; - - return show_cgroup_by_path(p, prefix, n_columns, flags); -} - -static int show_extra_pids( - const char *controller, - const char *path, - const char *prefix, - unsigned n_columns, - const pid_t pids[], - unsigned n_pids, - OutputFlags flags) { - - _cleanup_free_ pid_t *copy = NULL; - unsigned i, j; - int r; - - assert(path); - - if (n_pids <= 0) - return 0; - - if (n_columns <= 0) - n_columns = columns(); - - prefix = strempty(prefix); - - copy = new(pid_t, n_pids); - if (!copy) - return -ENOMEM; - - for (i = 0, j = 0; i < n_pids; i++) { - _cleanup_free_ char *k = NULL; - - r = cg_pid_get_path(controller, pids[i], &k); - if (r < 0) - return r; - - if (path_startswith(k, path)) - continue; - - copy[j++] = pids[i]; - } - - show_pid_array(copy, j, prefix, n_columns, true, false, flags); - - return 0; -} - -int show_cgroup_and_extra( - const char *controller, - const char *path, - const char *prefix, - unsigned n_columns, - const pid_t extra_pids[], - unsigned n_extra_pids, - OutputFlags flags) { - - int r; - - assert(path); - - r = show_cgroup(controller, path, prefix, n_columns, flags); - if (r < 0) - return r; - - return show_extra_pids(controller, path, prefix, n_columns, extra_pids, n_extra_pids, flags); -} - -int show_cgroup_and_extra_by_spec( - const char *spec, - const char *prefix, - unsigned n_columns, - const pid_t extra_pids[], - unsigned n_extra_pids, - OutputFlags flags) { - - _cleanup_free_ char *controller = NULL, *path = NULL; - int r; - - assert(spec); - - r = cg_split_spec(spec, &controller, &path); - if (r < 0) - return r; - - return show_cgroup_and_extra(controller, path, prefix, n_columns, extra_pids, n_extra_pids, flags); -} diff --git a/src/shared/cgroup-show.h b/src/shared/cgroup-show.h deleted file mode 100644 index 5c1d6e6d98..0000000000 --- a/src/shared/cgroup-show.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 - -#include "logs-show.h" -#include "output-mode.h" - -int show_cgroup_by_path(const char *path, const char *prefix, unsigned columns, OutputFlags flags); -int show_cgroup(const char *controller, const char *path, const char *prefix, unsigned columns, OutputFlags flags); - -int show_cgroup_and_extra_by_spec(const char *spec, const char *prefix, unsigned n_columns, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags); -int show_cgroup_and_extra(const char *controller, const char *path, const char *prefix, unsigned n_columns, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags); diff --git a/src/shared/clean-ipc.c b/src/shared/clean-ipc.c deleted file mode 100644 index a3ac7aeb82..0000000000 --- a/src/shared/clean-ipc.c +++ /dev/null @@ -1,365 +0,0 @@ -/*** - 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "clean-ipc.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "log.h" -#include "macro.h" -#include "string-util.h" -#include "strv.h" - -static int clean_sysvipc_shm(uid_t delete_uid) { - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - bool first = true; - int ret = 0; - - f = fopen("/proc/sysvipc/shm", "re"); - if (!f) { - if (errno == ENOENT) - return 0; - - return log_warning_errno(errno, "Failed to open /proc/sysvipc/shm: %m"); - } - - FOREACH_LINE(line, f, goto fail) { - unsigned n_attached; - pid_t cpid, lpid; - uid_t uid, cuid; - gid_t gid, cgid; - int shmid; - - if (first) { - first = false; - continue; - } - - truncate_nl(line); - - if (sscanf(line, "%*i %i %*o %*u " PID_FMT " " PID_FMT " %u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT, - &shmid, &cpid, &lpid, &n_attached, &uid, &gid, &cuid, &cgid) != 8) - continue; - - if (n_attached > 0) - continue; - - if (uid != delete_uid) - continue; - - if (shmctl(shmid, IPC_RMID, NULL) < 0) { - - /* Ignore entries that are already deleted */ - if (errno == EIDRM || errno == EINVAL) - continue; - - ret = log_warning_errno(errno, - "Failed to remove SysV shared memory segment %i: %m", - shmid); - } - } - - return ret; - -fail: - return log_warning_errno(errno, "Failed to read /proc/sysvipc/shm: %m"); -} - -static int clean_sysvipc_sem(uid_t delete_uid) { - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - bool first = true; - int ret = 0; - - f = fopen("/proc/sysvipc/sem", "re"); - if (!f) { - if (errno == ENOENT) - return 0; - - return log_warning_errno(errno, "Failed to open /proc/sysvipc/sem: %m"); - } - - FOREACH_LINE(line, f, goto fail) { - uid_t uid, cuid; - gid_t gid, cgid; - int semid; - - if (first) { - first = false; - continue; - } - - truncate_nl(line); - - if (sscanf(line, "%*i %i %*o %*u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT, - &semid, &uid, &gid, &cuid, &cgid) != 5) - continue; - - if (uid != delete_uid) - continue; - - if (semctl(semid, 0, IPC_RMID) < 0) { - - /* Ignore entries that are already deleted */ - if (errno == EIDRM || errno == EINVAL) - continue; - - ret = log_warning_errno(errno, - "Failed to remove SysV semaphores object %i: %m", - semid); - } - } - - return ret; - -fail: - return log_warning_errno(errno, "Failed to read /proc/sysvipc/sem: %m"); -} - -static int clean_sysvipc_msg(uid_t delete_uid) { - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - bool first = true; - int ret = 0; - - f = fopen("/proc/sysvipc/msg", "re"); - if (!f) { - if (errno == ENOENT) - return 0; - - return log_warning_errno(errno, "Failed to open /proc/sysvipc/msg: %m"); - } - - FOREACH_LINE(line, f, goto fail) { - uid_t uid, cuid; - gid_t gid, cgid; - pid_t cpid, lpid; - int msgid; - - if (first) { - first = false; - continue; - } - - truncate_nl(line); - - if (sscanf(line, "%*i %i %*o %*u %*u " PID_FMT " " PID_FMT " " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT, - &msgid, &cpid, &lpid, &uid, &gid, &cuid, &cgid) != 7) - continue; - - if (uid != delete_uid) - continue; - - if (msgctl(msgid, IPC_RMID, NULL) < 0) { - - /* Ignore entries that are already deleted */ - if (errno == EIDRM || errno == EINVAL) - continue; - - ret = log_warning_errno(errno, - "Failed to remove SysV message queue %i: %m", - msgid); - } - } - - return ret; - -fail: - return log_warning_errno(errno, "Failed to read /proc/sysvipc/msg: %m"); -} - -static int clean_posix_shm_internal(DIR *dir, uid_t uid) { - struct dirent *de; - int ret = 0, r; - - assert(dir); - - FOREACH_DIRENT(de, dir, goto fail) { - struct stat st; - - if (STR_IN_SET(de->d_name, "..", ".")) - continue; - - if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { - if (errno == ENOENT) - continue; - - log_warning_errno(errno, "Failed to stat() POSIX shared memory segment %s: %m", de->d_name); - ret = -errno; - continue; - } - - if (st.st_uid != uid) - continue; - - if (S_ISDIR(st.st_mode)) { - _cleanup_closedir_ DIR *kid; - - kid = xopendirat(dirfd(dir), de->d_name, O_NOFOLLOW|O_NOATIME); - if (!kid) { - if (errno != ENOENT) { - log_warning_errno(errno, "Failed to enter shared memory directory %s: %m", de->d_name); - ret = -errno; - } - } else { - r = clean_posix_shm_internal(kid, uid); - if (r < 0) - ret = r; - } - - if (unlinkat(dirfd(dir), de->d_name, AT_REMOVEDIR) < 0) { - - if (errno == ENOENT) - continue; - - log_warning_errno(errno, "Failed to remove POSIX shared memory directory %s: %m", de->d_name); - ret = -errno; - } - } else { - - if (unlinkat(dirfd(dir), de->d_name, 0) < 0) { - - if (errno == ENOENT) - continue; - - log_warning_errno(errno, "Failed to remove POSIX shared memory segment %s: %m", de->d_name); - ret = -errno; - } - } - } - - return ret; - -fail: - log_warning_errno(errno, "Failed to read /dev/shm: %m"); - return -errno; -} - -static int clean_posix_shm(uid_t uid) { - _cleanup_closedir_ DIR *dir = NULL; - - dir = opendir("/dev/shm"); - if (!dir) { - if (errno == ENOENT) - return 0; - - return log_warning_errno(errno, "Failed to open /dev/shm: %m"); - } - - return clean_posix_shm_internal(dir, uid); -} - -static int clean_posix_mq(uid_t uid) { - _cleanup_closedir_ DIR *dir = NULL; - struct dirent *de; - int ret = 0; - - dir = opendir("/dev/mqueue"); - if (!dir) { - if (errno == ENOENT) - return 0; - - return log_warning_errno(errno, "Failed to open /dev/mqueue: %m"); - } - - FOREACH_DIRENT(de, dir, goto fail) { - struct stat st; - char fn[1+strlen(de->d_name)+1]; - - if (STR_IN_SET(de->d_name, "..", ".")) - continue; - - if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { - if (errno == ENOENT) - continue; - - ret = log_warning_errno(errno, - "Failed to stat() MQ segment %s: %m", - de->d_name); - continue; - } - - if (st.st_uid != uid) - continue; - - fn[0] = '/'; - strcpy(fn+1, de->d_name); - - if (mq_unlink(fn) < 0) { - if (errno == ENOENT) - continue; - - ret = log_warning_errno(errno, - "Failed to unlink POSIX message queue %s: %m", - fn); - } - } - - return ret; - -fail: - return log_warning_errno(errno, "Failed to read /dev/mqueue: %m"); -} - -int clean_ipc(uid_t uid) { - int ret = 0, r; - - /* Refuse to clean IPC of the root and system users */ - if (uid <= SYSTEM_UID_MAX) - return 0; - - r = clean_sysvipc_shm(uid); - if (r < 0) - ret = r; - - r = clean_sysvipc_sem(uid); - if (r < 0) - ret = r; - - r = clean_sysvipc_msg(uid); - if (r < 0) - ret = r; - - r = clean_posix_shm(uid); - if (r < 0) - ret = r; - - r = clean_posix_mq(uid); - if (r < 0) - ret = r; - - return ret; -} diff --git a/src/shared/clean-ipc.h b/src/shared/clean-ipc.h deleted file mode 100644 index 44a83afcf7..0000000000 --- a/src/shared/clean-ipc.h +++ /dev/null @@ -1,24 +0,0 @@ -#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 - -int clean_ipc(uid_t uid); diff --git a/src/shared/condition.c b/src/shared/condition.c deleted file mode 100644 index 3a45ed265c..0000000000 --- a/src/shared/condition.c +++ /dev/null @@ -1,541 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include - -#include "sd-id128.h" - -#include "alloc-util.h" -#include "apparmor-util.h" -#include "architecture.h" -#include "audit-util.h" -#include "cap-list.h" -#include "condition.h" -#include "extract-word.h" -#include "fd-util.h" -#include "glob-util.h" -#include "hostname-util.h" -#include "ima-util.h" -#include "list.h" -#include "macro.h" -#include "mount-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "proc-cmdline.h" -#include "selinux-util.h" -#include "smack-util.h" -#include "stat-util.h" -#include "string-table.h" -#include "string-util.h" -#include "util.h" -#include "virt.h" - -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); - if (!c) - return NULL; - - c->type = type; - c->trigger = trigger; - c->negate = negate; - - r = free_and_strdup(&c->parameter, parameter); - if (r < 0) { - free(c); - return NULL; - } - - return c; -} - -void condition_free(Condition *c) { - assert(c); - - free(c->parameter); - free(c); -} - -Condition* condition_free_list(Condition *first) { - Condition *c, *n; - - LIST_FOREACH_SAFE(conditions, c, n, first) - condition_free(c); - - return NULL; -} - -static int condition_test_kernel_command_line(Condition *c) { - _cleanup_free_ char *line = NULL; - const char *p; - bool equal; - int r; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_KERNEL_COMMAND_LINE); - - r = proc_cmdline(&line); - if (r < 0) - return r; - - equal = !!strchr(c->parameter, '='); - p = line; - - for (;;) { - _cleanup_free_ char *word = NULL; - bool found; - - r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); - if (r < 0) - return r; - if (r == 0) - break; - - if (equal) - found = streq(word, c->parameter); - else { - const char *f; - - f = startswith(word, c->parameter); - found = f && (*f == '=' || *f == 0); - } - - if (found) - return true; - } - - return false; -} - -static int condition_test_virtualization(Condition *c) { - int b, v; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_VIRTUALIZATION); - - v = detect_virtualization(); - if (v < 0) - return v; - - /* First, compare with yes/no */ - b = parse_boolean(c->parameter); - - if (v > 0 && b > 0) - return true; - - if (v == 0 && b == 0) - return true; - - /* Then, compare categorization */ - if (VIRTUALIZATION_IS_VM(v) && streq(c->parameter, "vm")) - return true; - - if (VIRTUALIZATION_IS_CONTAINER(v) && streq(c->parameter, "container")) - return true; - - /* Finally compare id */ - return v != VIRTUALIZATION_NONE && streq(c->parameter, virtualization_to_string(v)); -} - -static int condition_test_architecture(Condition *c) { - int a, b; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_ARCHITECTURE); - - a = uname_architecture(); - if (a < 0) - return a; - - if (streq(c->parameter, "native")) - b = native_architecture(); - else - b = architecture_from_string(c->parameter); - if (b < 0) - return b; - - return a == b; -} - -static int condition_test_host(Condition *c) { - _cleanup_free_ char *h = NULL; - sd_id128_t x, y; - int r; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_HOST); - - if (sd_id128_from_string(c->parameter, &x) >= 0) { - - r = sd_id128_get_machine(&y); - if (r < 0) - return r; - - return sd_id128_equal(x, y); - } - - h = gethostname_malloc(); - if (!h) - return -ENOMEM; - - return fnmatch(c->parameter, h, FNM_CASEFOLD) == 0; -} - -static int condition_test_ac_power(Condition *c) { - int r; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_AC_POWER); - - r = parse_boolean(c->parameter); - if (r < 0) - return r; - - return (on_ac_power() != 0) == !!r; -} - -static int condition_test_security(Condition *c) { - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_SECURITY); - - if (streq(c->parameter, "selinux")) - return mac_selinux_have(); - if (streq(c->parameter, "smack")) - return mac_smack_use(); - if (streq(c->parameter, "apparmor")) - return mac_apparmor_use(); - if (streq(c->parameter, "audit")) - return use_audit(); - if (streq(c->parameter, "ima")) - return use_ima(); - - return false; -} - -static int condition_test_capability(Condition *c) { - _cleanup_fclose_ FILE *f = NULL; - int value; - char line[LINE_MAX]; - unsigned long long capabilities = -1; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_CAPABILITY); - - /* If it's an invalid capability, we don't have it */ - value = capability_from_name(c->parameter); - if (value < 0) - return -EINVAL; - - /* If it's a valid capability we default to assume - * that we have it */ - - f = fopen("/proc/self/status", "re"); - if (!f) - return -errno; - - while (fgets(line, sizeof(line), f)) { - truncate_nl(line); - - if (startswith(line, "CapBnd:")) { - (void) sscanf(line+7, "%llx", &capabilities); - break; - } - } - - return !!(capabilities & (1ULL << value)); -} - -static int condition_test_needs_update(Condition *c) { - const char *p; - struct stat usr, other; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_NEEDS_UPDATE); - - /* If the file system is read-only we shouldn't suggest an update */ - if (path_is_read_only_fs(c->parameter) > 0) - return false; - - /* Any other failure means we should allow the condition to be true, - * so that we rather invoke too many update tools than too - * few. */ - - if (!path_is_absolute(c->parameter)) - return true; - - p = strjoina(c->parameter, "/.updated"); - if (lstat(p, &other) < 0) - return true; - - if (lstat("/usr/", &usr) < 0) - return true; - - return usr.st_mtim.tv_sec > other.st_mtim.tv_sec || - (usr.st_mtim.tv_sec == other.st_mtim.tv_sec && usr.st_mtim.tv_nsec > other.st_mtim.tv_nsec); -} - -static int condition_test_first_boot(Condition *c) { - int r; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_FIRST_BOOT); - - r = parse_boolean(c->parameter); - if (r < 0) - return r; - - return (access("/run/systemd/first-boot", F_OK) >= 0) == !!r; -} - -static int condition_test_path_exists(Condition *c) { - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_PATH_EXISTS); - - return access(c->parameter, F_OK) >= 0; -} - -static int condition_test_path_exists_glob(Condition *c) { - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_PATH_EXISTS_GLOB); - - return glob_exists(c->parameter) > 0; -} - -static int condition_test_path_is_directory(Condition *c) { - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_PATH_IS_DIRECTORY); - - return is_dir(c->parameter, true) > 0; -} - -static int condition_test_path_is_symbolic_link(Condition *c) { - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_PATH_IS_SYMBOLIC_LINK); - - return is_symlink(c->parameter) > 0; -} - -static int condition_test_path_is_mount_point(Condition *c) { - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_PATH_IS_MOUNT_POINT); - - return path_is_mount_point(c->parameter, AT_SYMLINK_FOLLOW) > 0; -} - -static int condition_test_path_is_read_write(Condition *c) { - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_PATH_IS_READ_WRITE); - - return path_is_read_only_fs(c->parameter) <= 0; -} - -static int condition_test_directory_not_empty(Condition *c) { - int r; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_DIRECTORY_NOT_EMPTY); - - r = dir_is_empty(c->parameter); - return r <= 0 && r != -ENOENT; -} - -static int condition_test_file_not_empty(Condition *c) { - struct stat st; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_FILE_NOT_EMPTY); - - return (stat(c->parameter, &st) >= 0 && - S_ISREG(st.st_mode) && - st.st_size > 0); -} - -static int condition_test_file_is_executable(Condition *c) { - struct stat st; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_FILE_IS_EXECUTABLE); - - return (stat(c->parameter, &st) >= 0 && - S_ISREG(st.st_mode) && - (st.st_mode & 0111)); -} - -static int condition_test_null(Condition *c) { - assert(c); - assert(c->type == CONDITION_NULL); - - /* Note that during parsing we already evaluate the string and - * store it in c->negate */ - return true; -} - -int condition_test(Condition *c) { - - static int (*const condition_tests[_CONDITION_TYPE_MAX])(Condition *c) = { - [CONDITION_PATH_EXISTS] = condition_test_path_exists, - [CONDITION_PATH_EXISTS_GLOB] = condition_test_path_exists_glob, - [CONDITION_PATH_IS_DIRECTORY] = condition_test_path_is_directory, - [CONDITION_PATH_IS_SYMBOLIC_LINK] = condition_test_path_is_symbolic_link, - [CONDITION_PATH_IS_MOUNT_POINT] = condition_test_path_is_mount_point, - [CONDITION_PATH_IS_READ_WRITE] = condition_test_path_is_read_write, - [CONDITION_DIRECTORY_NOT_EMPTY] = condition_test_directory_not_empty, - [CONDITION_FILE_NOT_EMPTY] = condition_test_file_not_empty, - [CONDITION_FILE_IS_EXECUTABLE] = condition_test_file_is_executable, - [CONDITION_KERNEL_COMMAND_LINE] = condition_test_kernel_command_line, - [CONDITION_VIRTUALIZATION] = condition_test_virtualization, - [CONDITION_SECURITY] = condition_test_security, - [CONDITION_CAPABILITY] = condition_test_capability, - [CONDITION_HOST] = condition_test_host, - [CONDITION_AC_POWER] = condition_test_ac_power, - [CONDITION_ARCHITECTURE] = condition_test_architecture, - [CONDITION_NEEDS_UPDATE] = condition_test_needs_update, - [CONDITION_FIRST_BOOT] = condition_test_first_boot, - [CONDITION_NULL] = condition_test_null, - }; - - int r, b; - - assert(c); - assert(c->type >= 0); - assert(c->type < _CONDITION_TYPE_MAX); - - r = condition_tests[c->type](c); - if (r < 0) { - c->result = CONDITION_ERROR; - return r; - } - - b = (r > 0) == !c->negate; - c->result = b ? CONDITION_SUCCEEDED : CONDITION_FAILED; - return b; -} - -void condition_dump(Condition *c, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)) { - assert(c); - assert(f); - - if (!prefix) - prefix = ""; - - fprintf(f, - "%s\t%s: %s%s%s %s\n", - prefix, - to_string(c->type), - c->trigger ? "|" : "", - c->negate ? "!" : "", - c->parameter, - condition_result_to_string(c->result)); -} - -void condition_dump_list(Condition *first, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)) { - Condition *c; - - LIST_FOREACH(conditions, c, first) - condition_dump(c, f, prefix, to_string); -} - -static const char* const condition_type_table[_CONDITION_TYPE_MAX] = { - [CONDITION_ARCHITECTURE] = "ConditionArchitecture", - [CONDITION_VIRTUALIZATION] = "ConditionVirtualization", - [CONDITION_HOST] = "ConditionHost", - [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine", - [CONDITION_SECURITY] = "ConditionSecurity", - [CONDITION_CAPABILITY] = "ConditionCapability", - [CONDITION_AC_POWER] = "ConditionACPower", - [CONDITION_NEEDS_UPDATE] = "ConditionNeedsUpdate", - [CONDITION_FIRST_BOOT] = "ConditionFirstBoot", - [CONDITION_PATH_EXISTS] = "ConditionPathExists", - [CONDITION_PATH_EXISTS_GLOB] = "ConditionPathExistsGlob", - [CONDITION_PATH_IS_DIRECTORY] = "ConditionPathIsDirectory", - [CONDITION_PATH_IS_SYMBOLIC_LINK] = "ConditionPathIsSymbolicLink", - [CONDITION_PATH_IS_MOUNT_POINT] = "ConditionPathIsMountPoint", - [CONDITION_PATH_IS_READ_WRITE] = "ConditionPathIsReadWrite", - [CONDITION_DIRECTORY_NOT_EMPTY] = "ConditionDirectoryNotEmpty", - [CONDITION_FILE_NOT_EMPTY] = "ConditionFileNotEmpty", - [CONDITION_FILE_IS_EXECUTABLE] = "ConditionFileIsExecutable", - [CONDITION_NULL] = "ConditionNull" -}; - -DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType); - -static const char* const assert_type_table[_CONDITION_TYPE_MAX] = { - [CONDITION_ARCHITECTURE] = "AssertArchitecture", - [CONDITION_VIRTUALIZATION] = "AssertVirtualization", - [CONDITION_HOST] = "AssertHost", - [CONDITION_KERNEL_COMMAND_LINE] = "AssertKernelCommandLine", - [CONDITION_SECURITY] = "AssertSecurity", - [CONDITION_CAPABILITY] = "AssertCapability", - [CONDITION_AC_POWER] = "AssertACPower", - [CONDITION_NEEDS_UPDATE] = "AssertNeedsUpdate", - [CONDITION_FIRST_BOOT] = "AssertFirstBoot", - [CONDITION_PATH_EXISTS] = "AssertPathExists", - [CONDITION_PATH_EXISTS_GLOB] = "AssertPathExistsGlob", - [CONDITION_PATH_IS_DIRECTORY] = "AssertPathIsDirectory", - [CONDITION_PATH_IS_SYMBOLIC_LINK] = "AssertPathIsSymbolicLink", - [CONDITION_PATH_IS_MOUNT_POINT] = "AssertPathIsMountPoint", - [CONDITION_PATH_IS_READ_WRITE] = "AssertPathIsReadWrite", - [CONDITION_DIRECTORY_NOT_EMPTY] = "AssertDirectoryNotEmpty", - [CONDITION_FILE_NOT_EMPTY] = "AssertFileNotEmpty", - [CONDITION_FILE_IS_EXECUTABLE] = "AssertFileIsExecutable", - [CONDITION_NULL] = "AssertNull" -}; - -DEFINE_STRING_TABLE_LOOKUP(assert_type, ConditionType); - -static const char* const condition_result_table[_CONDITION_RESULT_MAX] = { - [CONDITION_UNTESTED] = "untested", - [CONDITION_SUCCEEDED] = "succeeded", - [CONDITION_FAILED] = "failed", - [CONDITION_ERROR] = "error", -}; - -DEFINE_STRING_TABLE_LOOKUP(condition_result, ConditionResult); diff --git a/src/shared/condition.h b/src/shared/condition.h deleted file mode 100644 index bdda04b770..0000000000 --- a/src/shared/condition.h +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 - -#include "list.h" -#include "macro.h" - -typedef enum ConditionType { - CONDITION_ARCHITECTURE, - CONDITION_VIRTUALIZATION, - CONDITION_HOST, - CONDITION_KERNEL_COMMAND_LINE, - CONDITION_SECURITY, - CONDITION_CAPABILITY, - CONDITION_AC_POWER, - - CONDITION_NEEDS_UPDATE, - CONDITION_FIRST_BOOT, - - CONDITION_PATH_EXISTS, - CONDITION_PATH_EXISTS_GLOB, - CONDITION_PATH_IS_DIRECTORY, - CONDITION_PATH_IS_SYMBOLIC_LINK, - CONDITION_PATH_IS_MOUNT_POINT, - CONDITION_PATH_IS_READ_WRITE, - CONDITION_DIRECTORY_NOT_EMPTY, - CONDITION_FILE_NOT_EMPTY, - CONDITION_FILE_IS_EXECUTABLE, - - CONDITION_NULL, - - _CONDITION_TYPE_MAX, - _CONDITION_TYPE_INVALID = -1 -} ConditionType; - -typedef enum ConditionResult { - CONDITION_UNTESTED, - CONDITION_SUCCEEDED, - CONDITION_FAILED, - CONDITION_ERROR, - _CONDITION_RESULT_MAX, - _CONDITION_RESULT_INVALID = -1 -} ConditionResult; - -typedef struct Condition { - ConditionType type:8; - - bool trigger:1; - bool negate:1; - - ConditionResult result:6; - - char *parameter; - - LIST_FIELDS(struct Condition, conditions); -} Condition; - -Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate); -void condition_free(Condition *c); -Condition* condition_free_list(Condition *c); - -int condition_test(Condition *c); - -void condition_dump(Condition *c, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)); -void condition_dump_list(Condition *c, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)); - -const char* condition_type_to_string(ConditionType t) _const_; -ConditionType condition_type_from_string(const char *s) _pure_; - -const char* assert_type_to_string(ConditionType t) _const_; -ConditionType assert_type_from_string(const char *s) _pure_; - -const char* condition_result_to_string(ConditionResult r) _const_; -ConditionResult condition_result_from_string(const char *s) _pure_; diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c deleted file mode 100644 index 83be79a4f5..0000000000 --- a/src/shared/conf-parser.c +++ /dev/null @@ -1,913 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "conf-files.h" -#include "conf-parser.h" -#include "extract-word.h" -#include "fd-util.h" -#include "fs-util.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "signal-util.h" -#include "socket-util.h" -#include "string-util.h" -#include "strv.h" -#include "syslog-util.h" -#include "time-util.h" -#include "utf8.h" - -int config_item_table_lookup( - const void *table, - const char *section, - const char *lvalue, - ConfigParserCallback *func, - int *ltype, - void **data, - void *userdata) { - - const ConfigTableItem *t; - - assert(table); - assert(lvalue); - assert(func); - assert(ltype); - assert(data); - - for (t = table; t->lvalue; t++) { - - if (!streq(lvalue, t->lvalue)) - continue; - - if (!streq_ptr(section, t->section)) - continue; - - *func = t->parse; - *ltype = t->ltype; - *data = t->data; - return 1; - } - - return 0; -} - -int config_item_perf_lookup( - const void *table, - const char *section, - const char *lvalue, - ConfigParserCallback *func, - int *ltype, - void **data, - void *userdata) { - - ConfigPerfItemLookup lookup = (ConfigPerfItemLookup) table; - const ConfigPerfItem *p; - - assert(table); - assert(lvalue); - assert(func); - assert(ltype); - assert(data); - - if (!section) - p = lookup(lvalue, strlen(lvalue)); - else { - char *key; - - key = strjoin(section, ".", lvalue, NULL); - if (!key) - return -ENOMEM; - - p = lookup(key, strlen(key)); - free(key); - } - - if (!p) - return 0; - - *func = p->parse; - *ltype = p->ltype; - *data = (uint8_t*) userdata + p->offset; - return 1; -} - -/* Run the user supplied parser for an assignment */ -static int next_assignment(const char *unit, - const char *filename, - unsigned line, - ConfigItemLookup lookup, - const void *table, - const char *section, - unsigned section_line, - const char *lvalue, - const char *rvalue, - bool relaxed, - void *userdata) { - - ConfigParserCallback func = NULL; - int ltype = 0; - void *data = NULL; - int r; - - assert(filename); - assert(line > 0); - assert(lookup); - assert(lvalue); - assert(rvalue); - - r = lookup(table, section, lvalue, &func, <ype, &data, userdata); - if (r < 0) - return r; - - if (r > 0) { - if (func) - return func(unit, filename, line, section, section_line, - lvalue, ltype, rvalue, data, userdata); - - return 0; - } - - /* Warn about unknown non-extension fields. */ - if (!relaxed && !startswith(lvalue, "X-")) - log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown lvalue '%s' in section '%s'", lvalue, section); - - return 0; -} - -/* Parse a variable assignment line */ -static int parse_line(const char* unit, - const char *filename, - unsigned line, - const char *sections, - ConfigItemLookup lookup, - const void *table, - bool relaxed, - bool allow_include, - char **section, - unsigned *section_line, - bool *section_ignored, - char *l, - void *userdata) { - - char *e; - - assert(filename); - assert(line > 0); - assert(lookup); - assert(l); - - l = strstrip(l); - - if (!*l) - return 0; - - if (strchr(COMMENTS "\n", *l)) - return 0; - - if (startswith(l, ".include ")) { - _cleanup_free_ char *fn = NULL; - - /* .includes are a bad idea, we only support them here - * for historical reasons. They create cyclic include - * problems and make it difficult to detect - * configuration file changes with an easy - * stat(). Better approaches, such as .d/ drop-in - * snippets exist. - * - * Support for them should be eventually removed. */ - - if (!allow_include) { - log_syntax(unit, LOG_ERR, filename, line, 0, ".include not allowed here. Ignoring."); - return 0; - } - - fn = file_in_same_dir(filename, strstrip(l+9)); - if (!fn) - return -ENOMEM; - - return config_parse(unit, fn, NULL, sections, lookup, table, relaxed, false, false, userdata); - } - - if (*l == '[') { - size_t k; - char *n; - - k = strlen(l); - assert(k > 0); - - if (l[k-1] != ']') { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid section header '%s'", l); - return -EBADMSG; - } - - n = strndup(l+1, k-2); - if (!n) - return -ENOMEM; - - if (sections && !nulstr_contains(sections, n)) { - - if (!relaxed && !startswith(n, "X-")) - log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown section '%s'. Ignoring.", n); - - free(n); - *section = mfree(*section); - *section_line = 0; - *section_ignored = true; - } else { - free(*section); - *section = n; - *section_line = line; - *section_ignored = false; - } - - return 0; - } - - if (sections && !*section) { - - if (!relaxed && !*section_ignored) - log_syntax(unit, LOG_WARNING, filename, line, 0, "Assignment outside of section. Ignoring."); - - return 0; - } - - e = strchr(l, '='); - if (!e) { - log_syntax(unit, LOG_WARNING, filename, line, 0, "Missing '='."); - return -EINVAL; - } - - *e = 0; - e++; - - return next_assignment(unit, - filename, - line, - lookup, - table, - *section, - *section_line, - strstrip(l), - strstrip(e), - relaxed, - userdata); -} - -/* Go through the file and parse each line */ -int config_parse(const char *unit, - const char *filename, - FILE *f, - const char *sections, - ConfigItemLookup lookup, - const void *table, - bool relaxed, - bool allow_include, - bool warn, - void *userdata) { - - _cleanup_free_ char *section = NULL, *continuation = NULL; - _cleanup_fclose_ FILE *ours = NULL; - unsigned line = 0, section_line = 0; - bool section_ignored = false, allow_bom = true; - int r; - - assert(filename); - assert(lookup); - - if (!f) { - f = ours = fopen(filename, "re"); - if (!f) { - /* Only log on request, except for ENOENT, - * since we return 0 to the caller. */ - if (warn || errno == ENOENT) - log_full(errno == ENOENT ? LOG_DEBUG : LOG_ERR, - "Failed to open configuration file '%s': %m", filename); - return errno == ENOENT ? 0 : -errno; - } - } - - fd_warn_permissions(filename, fileno(f)); - - for (;;) { - char buf[LINE_MAX], *l, *p, *c = NULL, *e; - bool escaped = false; - - if (!fgets(buf, sizeof buf, f)) { - if (feof(f)) - break; - - log_error_errno(errno, "Failed to read configuration file '%s': %m", filename); - return -errno; - } - - l = buf; - if (allow_bom && startswith(l, UTF8_BYTE_ORDER_MARK)) - l += strlen(UTF8_BYTE_ORDER_MARK); - allow_bom = false; - - truncate_nl(l); - - if (continuation) { - c = strappend(continuation, l); - if (!c) { - if (warn) - log_oom(); - return -ENOMEM; - } - - continuation = mfree(continuation); - p = c; - } else - p = l; - - for (e = p; *e; e++) { - if (escaped) - escaped = false; - else if (*e == '\\') - escaped = true; - } - - if (escaped) { - *(e-1) = ' '; - - if (c) - continuation = c; - else { - continuation = strdup(l); - if (!continuation) { - if (warn) - log_oom(); - return -ENOMEM; - } - } - - continue; - } - - r = parse_line(unit, - filename, - ++line, - sections, - lookup, - table, - relaxed, - allow_include, - §ion, - §ion_line, - §ion_ignored, - p, - userdata); - free(c); - - if (r < 0) { - if (warn) - log_warning_errno(r, "Failed to parse file '%s': %m", - filename); - return r; - } - } - - return 0; -} - -/* Parse each config file in the specified directories. */ -int config_parse_many(const char *conf_file, - const char *conf_file_dirs, - const char *sections, - ConfigItemLookup lookup, - const void *table, - bool relaxed, - void *userdata) { - _cleanup_strv_free_ char **files = NULL; - char **fn; - int r; - - r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs); - if (r < 0) - return r; - - if (conf_file) { - r = config_parse(NULL, conf_file, NULL, sections, lookup, table, relaxed, false, true, userdata); - if (r < 0) - return r; - } - - STRV_FOREACH(fn, files) { - r = config_parse(NULL, *fn, NULL, sections, lookup, table, relaxed, false, true, userdata); - if (r < 0) - return r; - } - - return 0; -} - -#define DEFINE_PARSER(type, vartype, conv_func) \ - int config_parse_##type( \ - 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) { \ - \ - vartype *i = data; \ - int r; \ - \ - assert(filename); \ - assert(lvalue); \ - assert(rvalue); \ - assert(data); \ - \ - r = conv_func(rvalue, i); \ - if (r < 0) \ - log_syntax(unit, LOG_ERR, filename, line, r, \ - "Failed to parse %s value, ignoring: %s", \ - #type, rvalue); \ - \ - return 0; \ - } \ - struct __useless_struct_to_allow_trailing_semicolon__ - -DEFINE_PARSER(int, int, safe_atoi); -DEFINE_PARSER(long, long, safe_atoli); -DEFINE_PARSER(uint32, uint32_t, safe_atou32); -DEFINE_PARSER(uint64, uint64_t, safe_atou64); -DEFINE_PARSER(unsigned, unsigned, safe_atou); -DEFINE_PARSER(double, double, safe_atod); -DEFINE_PARSER(nsec, nsec_t, parse_nsec); -DEFINE_PARSER(sec, usec_t, parse_sec); -DEFINE_PARSER(mode, mode_t, parse_mode); - -int config_parse_iec_size(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) { - - size_t *sz = data; - uint64_t v; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = parse_size(rvalue, 1024, &v); - if (r < 0 || (uint64_t) (size_t) v != v) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue); - return 0; - } - - *sz = (size_t) v; - return 0; -} - -int config_parse_si_size(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) { - - size_t *sz = data; - uint64_t v; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = parse_size(rvalue, 1000, &v); - if (r < 0 || (uint64_t) (size_t) v != v) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue); - return 0; - } - - *sz = (size_t) v; - return 0; -} - -int config_parse_iec_uint64(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) { - - uint64_t *bytes = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = parse_size(rvalue, 1024, bytes); - if (r < 0) - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue); - - return 0; -} - -int config_parse_bool(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) { - - int k; - bool *b = data; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - k = parse_boolean(rvalue); - if (k < 0) { - log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse boolean value, ignoring: %s", rvalue); - return 0; - } - - *b = !!k; - return 0; -} - -int config_parse_tristate( - 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) { - - int k, *t = data; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - /* A tristate is pretty much a boolean, except that it can - * also take the special value -1, indicating "uninitialized", - * much like NULL is for a pointer type. */ - - k = parse_boolean(rvalue); - if (k < 0) { - log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse boolean value, ignoring: %s", rvalue); - return 0; - } - - *t = !!k; - return 0; -} - -int config_parse_string( - 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) { - - char **s = data, *n; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (!utf8_is_valid(rvalue)) { - log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); - return 0; - } - - if (isempty(rvalue)) - n = NULL; - else { - n = strdup(rvalue); - if (!n) - return log_oom(); - } - - free(*s); - *s = n; - - return 0; -} - -int config_parse_path( - 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) { - - char **s = data, *n; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (!utf8_is_valid(rvalue)) { - log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); - return 0; - } - - if (!path_is_absolute(rvalue)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute path, ignoring: %s", rvalue); - return 0; - } - - n = strdup(rvalue); - if (!n) - return log_oom(); - - path_kill_slashes(n); - - free(*s); - *s = n; - - return 0; -} - -int config_parse_strv(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) { - - char ***sv = data; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - char **empty; - - /* Empty assignment resets the list. As a special rule - * we actually fill in a real empty array here rather - * than NULL, since some code wants to know if - * something was set at all... */ - empty = strv_new(NULL, NULL); - if (!empty) - return log_oom(); - - strv_free(*sv); - *sv = empty; - return 0; - } - - for (;;) { - char *word = NULL; - int r; - r = extract_first_word(&rvalue, &word, WHITESPACE, EXTRACT_QUOTES|EXTRACT_RETAIN_ESCAPE); - if (r == 0) - break; - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue); - break; - } - - if (!utf8_is_valid(word)) { - log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); - free(word); - continue; - } - r = strv_consume(sv, word); - if (r < 0) - return log_oom(); - } - - return 0; -} - -int config_parse_log_facility( - 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) { - - - int *o = data, x; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - x = log_facility_unshifted_from_string(rvalue); - if (x < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse log facility, ignoring: %s", rvalue); - return 0; - } - - *o = (x << 3) | LOG_PRI(*o); - - return 0; -} - -int config_parse_log_level( - 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) { - - - int *o = data, x; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - x = log_level_from_string(rvalue); - if (x < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse log level, ignoring: %s", rvalue); - return 0; - } - - *o = (*o & LOG_FACMASK) | x; - return 0; -} - -int config_parse_signal( - 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) { - - int *sig = data, r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(sig); - - r = signal_from_string_try_harder(rvalue); - if (r <= 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse signal name, ignoring: %s", rvalue); - return 0; - } - - *sig = r; - return 0; -} - -int config_parse_personality( - 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 long *personality = data, p; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(personality); - - p = personality_from_string(rvalue); - if (p == PERSONALITY_INVALID) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse personality, ignoring: %s", rvalue); - return 0; - } - - *personality = p; - return 0; -} - -int config_parse_ifname( - 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) { - - char **s = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - *s = mfree(*s); - return 0; - } - - if (!ifname_valid(rvalue)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is not valid or too long, ignoring assignment: %s", rvalue); - return 0; - } - - r = free_and_strdup(s, rvalue); - if (r < 0) - return log_oom(); - - return 0; -} diff --git a/src/shared/conf-parser.h b/src/shared/conf-parser.h deleted file mode 100644 index f6964e3fd4..0000000000 --- a/src/shared/conf-parser.h +++ /dev/null @@ -1,229 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include - -#include "alloc-util.h" -#include "log.h" -#include "macro.h" - -/* An abstract parser for simple, line based, shallow configuration - * files consisting of variable assignments only. */ - -/* Prototype for a parser for a specific configuration setting */ -typedef int (*ConfigParserCallback)(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); - -/* Wraps information for parsing a specific configuration variable, to - * be stored in a simple array */ -typedef struct ConfigTableItem { - const char *section; /* Section */ - const char *lvalue; /* Name of the variable */ - ConfigParserCallback parse; /* Function that is called to parse the variable's value */ - int ltype; /* Distinguish different variables passed to the same callback */ - void *data; /* Where to store the variable's data */ -} ConfigTableItem; - -/* Wraps information for parsing a specific configuration variable, to - * be stored in a gperf perfect hashtable */ -typedef struct ConfigPerfItem { - const char *section_and_lvalue; /* Section + "." + name of the variable */ - ConfigParserCallback parse; /* Function that is called to parse the variable's value */ - int ltype; /* Distinguish different variables passed to the same callback */ - size_t offset; /* Offset where to store data, from the beginning of userdata */ -} ConfigPerfItem; - -/* Prototype for a low-level gperf lookup function */ -typedef const ConfigPerfItem* (*ConfigPerfItemLookup)(const char *section_and_lvalue, unsigned length); - -/* Prototype for a generic high-level lookup function */ -typedef int (*ConfigItemLookup)( - const void *table, - const char *section, - const char *lvalue, - ConfigParserCallback *func, - int *ltype, - void **data, - void *userdata); - -/* Linear table search implementation of ConfigItemLookup, based on - * ConfigTableItem arrays */ -int config_item_table_lookup(const void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata); - -/* gperf implementation of ConfigItemLookup, based on gperf - * ConfigPerfItem tables */ -int config_item_perf_lookup(const void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata); - -int config_parse(const char *unit, - const char *filename, - FILE *f, - const char *sections, /* nulstr */ - ConfigItemLookup lookup, - const void *table, - bool relaxed, - bool allow_include, - bool warn, - void *userdata); - -int config_parse_many(const char *conf_file, /* possibly NULL */ - const char *conf_file_dirs, /* nulstr */ - const char *sections, /* nulstr */ - ConfigItemLookup lookup, - const void *table, - bool relaxed, - void *userdata); - -/* Generic parsers */ -int config_parse_int(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); -int config_parse_unsigned(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); -int config_parse_long(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); -int config_parse_uint32(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); -int config_parse_uint64(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); -int config_parse_double(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); -int config_parse_iec_size(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); -int config_parse_si_size(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); -int config_parse_iec_uint64(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); -int config_parse_bool(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); -int config_parse_tristate(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); -int config_parse_string(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); -int config_parse_path(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); -int config_parse_strv(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); -int config_parse_sec(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); -int config_parse_nsec(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); -int config_parse_mode(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); -int config_parse_log_facility(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); -int config_parse_log_level(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); -int config_parse_signal(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); -int config_parse_personality(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); -int config_parse_ifname(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); - -#define DEFINE_CONFIG_PARSE_ENUM(function,name,type,msg) \ - int function(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) { \ - \ - type *i = data, x; \ - \ - assert(filename); \ - assert(lvalue); \ - assert(rvalue); \ - assert(data); \ - \ - if ((x = name##_from_string(rvalue)) < 0) { \ - log_syntax(unit, LOG_ERR, filename, line, -x, \ - msg ", ignoring: %s", rvalue); \ - return 0; \ - } \ - \ - *i = x; \ - return 0; \ - } - -#define DEFINE_CONFIG_PARSE_ENUMV(function,name,type,invalid,msg) \ - int function(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) { \ - \ - type **enums = data, x, *ys; \ - _cleanup_free_ type *xs = NULL; \ - const char *word, *state; \ - size_t l, i = 0; \ - \ - assert(filename); \ - assert(lvalue); \ - assert(rvalue); \ - assert(data); \ - \ - xs = new0(type, 1); \ - if (!xs) \ - return -ENOMEM; \ - \ - *xs = invalid; \ - \ - FOREACH_WORD(word, l, rvalue, state) { \ - _cleanup_free_ char *en = NULL; \ - type *new_xs; \ - \ - en = strndup(word, l); \ - if (!en) \ - return -ENOMEM; \ - \ - if ((x = name##_from_string(en)) < 0) { \ - log_syntax(unit, LOG_ERR, filename, line, \ - -x, msg ", ignoring: %s", en); \ - continue; \ - } \ - \ - for (ys = xs; x != invalid && *ys != invalid; ys++) { \ - if (*ys == x) { \ - log_syntax(unit, LOG_ERR, filename, \ - line, -x, \ - "Duplicate entry, ignoring: %s", \ - en); \ - x = invalid; \ - } \ - } \ - \ - if (x == invalid) \ - continue; \ - \ - *(xs + i) = x; \ - new_xs = realloc(xs, (++i + 1) * sizeof(type)); \ - if (new_xs) \ - xs = new_xs; \ - else \ - return -ENOMEM; \ - \ - *(xs + i) = invalid; \ - } \ - \ - free(*enums); \ - *enums = xs; \ - xs = NULL; \ - \ - return 0; \ - } diff --git a/src/shared/dev-setup.c b/src/shared/dev-setup.c deleted file mode 100644 index b2d464c117..0000000000 --- a/src/shared/dev-setup.c +++ /dev/null @@ -1,73 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010-2012 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 -#include - -#include "alloc-util.h" -#include "dev-setup.h" -#include "label.h" -#include "log.h" -#include "path-util.h" -#include "user-util.h" -#include "util.h" - -int dev_setup(const char *prefix, uid_t uid, gid_t gid) { - static const char symlinks[] = - "-/proc/kcore\0" "/dev/core\0" - "/proc/self/fd\0" "/dev/fd\0" - "/proc/self/fd/0\0" "/dev/stdin\0" - "/proc/self/fd/1\0" "/dev/stdout\0" - "/proc/self/fd/2\0" "/dev/stderr\0"; - - const char *j, *k; - int r; - - NULSTR_FOREACH_PAIR(j, k, symlinks) { - _cleanup_free_ char *link_name = NULL; - const char *n; - - if (j[0] == '-') { - j++; - - if (access(j, F_OK) < 0) - continue; - } - - if (prefix) { - link_name = prefix_root(prefix, k); - if (!link_name) - return -ENOMEM; - - n = link_name; - } else - n = k; - - r = symlink_label(j, n); - if (r < 0) - log_debug_errno(r, "Failed to symlink %s to %s: %m", j, n); - - if (uid != UID_INVALID || gid != GID_INVALID) - if (lchown(n, uid, gid) < 0) - log_debug_errno(errno, "Failed to chown %s: %m", n); - } - - return 0; -} diff --git a/src/shared/dev-setup.h b/src/shared/dev-setup.h deleted file mode 100644 index 5766a62060..0000000000 --- a/src/shared/dev-setup.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010-2012 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 - -int dev_setup(const char *prefix, uid_t uid, gid_t gid); diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c deleted file mode 100644 index 835557c6b2..0000000000 --- a/src/shared/dns-domain.c +++ /dev/null @@ -1,1322 +0,0 @@ -/*** - 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 . - ***/ - -#ifdef HAVE_LIBIDN -#include -#include -#endif - -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "dns-domain.h" -#include "hashmap.h" -#include "hexdecoct.h" -#include "in-addr-util.h" -#include "macro.h" -#include "parse-util.h" -#include "string-util.h" -#include "strv.h" -#include "utf8.h" - -int dns_label_unescape(const char **name, char *dest, size_t sz) { - const char *n; - char *d; - int r = 0; - - assert(name); - assert(*name); - - n = *name; - d = dest; - - for (;;) { - if (*n == '.') { - n++; - break; - } - - if (*n == 0) - break; - - if (r >= DNS_LABEL_MAX) - return -EINVAL; - - if (sz <= 0) - return -ENOBUFS; - - if (*n == '\\') { - /* Escaped character */ - - n++; - - if (*n == 0) - /* Ending NUL */ - return -EINVAL; - - else if (*n == '\\' || *n == '.') { - /* Escaped backslash or dot */ - - if (d) - *(d++) = *n; - sz--; - r++; - n++; - - } else if (n[0] >= '0' && n[0] <= '9') { - unsigned k; - - /* Escaped literal ASCII character */ - - if (!(n[1] >= '0' && n[1] <= '9') || - !(n[2] >= '0' && n[2] <= '9')) - return -EINVAL; - - k = ((unsigned) (n[0] - '0') * 100) + - ((unsigned) (n[1] - '0') * 10) + - ((unsigned) (n[2] - '0')); - - /* Don't allow anything that doesn't - * fit in 8bit. Note that we do allow - * control characters, as some servers - * (e.g. cloudflare) are happy to - * generate labels with them - * inside. */ - if (k > 255) - return -EINVAL; - - if (d) - *(d++) = (char) k; - sz--; - r++; - - n += 3; - } else - return -EINVAL; - - } else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) { - - /* Normal character */ - - if (d) - *(d++) = *n; - sz--; - r++; - n++; - } else - return -EINVAL; - } - - /* Empty label that is not at the end? */ - if (r == 0 && *n) - return -EINVAL; - - if (sz >= 1 && d) - *d = 0; - - *name = n; - return r; -} - -/* @label_terminal: terminal character of a label, updated to point to the terminal character of - * the previous label (always skipping one dot) or to NULL if there are no more - * labels. */ -int dns_label_unescape_suffix(const char *name, const char **label_terminal, char *dest, size_t sz) { - const char *terminal; - int r; - - assert(name); - assert(label_terminal); - assert(dest); - - /* no more labels */ - if (!*label_terminal) { - if (sz >= 1) - *dest = 0; - - return 0; - } - - terminal = *label_terminal; - assert(*terminal == '.' || *terminal == 0); - - /* Skip current terminal character (and accept domain names ending it ".") */ - if (*terminal == 0) - terminal--; - if (terminal >= name && *terminal == '.') - terminal--; - - /* Point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */ - for (;;) { - if (terminal < name) { - /* Reached the first label, so indicate that there are no more */ - terminal = NULL; - break; - } - - /* Find the start of the last label */ - if (*terminal == '.') { - const char *y; - unsigned slashes = 0; - - for (y = terminal - 1; y >= name && *y == '\\'; y--) - slashes++; - - if (slashes % 2 == 0) { - /* The '.' was not escaped */ - name = terminal + 1; - break; - } else { - terminal = y; - continue; - } - } - - terminal--; - } - - r = dns_label_unescape(&name, dest, sz); - if (r < 0) - return r; - - *label_terminal = terminal; - - return r; -} - -int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) { - char *q; - - /* DNS labels must be between 1 and 63 characters long. A - * zero-length label does not exist. See RFC 2182, Section - * 11. */ - - if (l <= 0 || l > DNS_LABEL_MAX) - return -EINVAL; - if (sz < 1) - return -ENOBUFS; - - assert(p); - assert(dest); - - q = dest; - while (l > 0) { - - if (*p == '.' || *p == '\\') { - - /* Dot or backslash */ - - if (sz < 3) - return -ENOBUFS; - - *(q++) = '\\'; - *(q++) = *p; - - sz -= 2; - - } else if (*p == '_' || - *p == '-' || - (*p >= '0' && *p <= '9') || - (*p >= 'a' && *p <= 'z') || - (*p >= 'A' && *p <= 'Z')) { - - /* Proper character */ - - if (sz < 2) - return -ENOBUFS; - - *(q++) = *p; - sz -= 1; - - } else { - - /* Everything else */ - - if (sz < 5) - return -ENOBUFS; - - *(q++) = '\\'; - *(q++) = '0' + (char) ((uint8_t) *p / 100); - *(q++) = '0' + (char) (((uint8_t) *p / 10) % 10); - *(q++) = '0' + (char) ((uint8_t) *p % 10); - - sz -= 4; - } - - p++; - l--; - } - - *q = 0; - return (int) (q - dest); -} - -int dns_label_escape_new(const char *p, size_t l, char **ret) { - _cleanup_free_ char *s = NULL; - int r; - - assert(p); - assert(ret); - - if (l <= 0 || l > DNS_LABEL_MAX) - return -EINVAL; - - s = new(char, DNS_LABEL_ESCAPED_MAX); - if (!s) - return -ENOMEM; - - r = dns_label_escape(p, l, s, DNS_LABEL_ESCAPED_MAX); - if (r < 0) - return r; - - *ret = s; - s = NULL; - - return r; -} - -int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) { -#ifdef HAVE_LIBIDN - _cleanup_free_ uint32_t *input = NULL; - size_t input_size, l; - const char *p; - bool contains_8bit = false; - char buffer[DNS_LABEL_MAX+1]; - - assert(encoded); - assert(decoded); - - /* Converts an U-label into an A-label */ - - if (encoded_size <= 0) - return -EINVAL; - - for (p = encoded; p < encoded + encoded_size; p++) - if ((uint8_t) *p > 127) - contains_8bit = true; - - if (!contains_8bit) { - if (encoded_size > DNS_LABEL_MAX) - return -EINVAL; - - return 0; - } - - input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size); - if (!input) - return -ENOMEM; - - if (idna_to_ascii_4i(input, input_size, buffer, 0) != 0) - return -EINVAL; - - l = strlen(buffer); - - /* Verify that the result is not longer than one DNS label. */ - if (l <= 0 || l > DNS_LABEL_MAX) - return -EINVAL; - if (l > decoded_max) - return -ENOBUFS; - - memcpy(decoded, buffer, l); - - /* If there's room, append a trailing NUL byte, but only then */ - if (decoded_max > l) - decoded[l] = 0; - - return (int) l; -#else - return 0; -#endif -} - -int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) { -#ifdef HAVE_LIBIDN - size_t input_size, output_size; - _cleanup_free_ uint32_t *input = NULL; - _cleanup_free_ char *result = NULL; - uint32_t *output = NULL; - size_t w; - - /* To be invoked after unescaping. Converts an A-label into an U-label. */ - - assert(encoded); - assert(decoded); - - if (encoded_size <= 0 || encoded_size > DNS_LABEL_MAX) - return -EINVAL; - - if (encoded_size < sizeof(IDNA_ACE_PREFIX)-1) - return 0; - - if (memcmp(encoded, IDNA_ACE_PREFIX, sizeof(IDNA_ACE_PREFIX) -1) != 0) - return 0; - - input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size); - if (!input) - return -ENOMEM; - - output_size = input_size; - output = newa(uint32_t, output_size); - - idna_to_unicode_44i(input, input_size, output, &output_size, 0); - - result = stringprep_ucs4_to_utf8(output, output_size, NULL, &w); - if (!result) - return -ENOMEM; - if (w <= 0) - return -EINVAL; - if (w > decoded_max) - return -ENOBUFS; - - memcpy(decoded, result, w); - - /* Append trailing NUL byte if there's space, but only then. */ - if (decoded_max > w) - decoded[w] = 0; - - return w; -#else - return 0; -#endif -} - -int dns_name_concat(const char *a, const char *b, char **_ret) { - _cleanup_free_ char *ret = NULL; - size_t n = 0, allocated = 0; - const char *p; - bool first = true; - int r; - - if (a) - p = a; - else if (b) { - p = b; - b = NULL; - } else - goto finish; - - for (;;) { - char label[DNS_LABEL_MAX]; - - r = dns_label_unescape(&p, label, sizeof(label)); - if (r < 0) - return r; - if (r == 0) { - if (*p != 0) - return -EINVAL; - - if (b) { - /* Now continue with the second string, if there is one */ - p = b; - b = NULL; - continue; - } - - break; - } - - if (_ret) { - if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) - return -ENOMEM; - - r = dns_label_escape(label, r, ret + n + !first, DNS_LABEL_ESCAPED_MAX); - if (r < 0) - return r; - - if (!first) - ret[n] = '.'; - } else { - char escaped[DNS_LABEL_ESCAPED_MAX]; - - r = dns_label_escape(label, r, escaped, sizeof(escaped)); - if (r < 0) - return r; - } - - if (!first) - n++; - else - first = false; - - n += r; - } - -finish: - if (n > DNS_HOSTNAME_MAX) - return -EINVAL; - - if (_ret) { - if (n == 0) { - /* Nothing appended? If so, generate at least a single dot, to indicate the DNS root domain */ - if (!GREEDY_REALLOC(ret, allocated, 2)) - return -ENOMEM; - - ret[n++] = '.'; - } else { - if (!GREEDY_REALLOC(ret, allocated, n + 1)) - return -ENOMEM; - } - - ret[n] = 0; - *_ret = ret; - ret = NULL; - } - - return 0; -} - -void dns_name_hash_func(const void *s, struct siphash *state) { - const char *p = s; - int r; - - assert(p); - - for (;;) { - char label[DNS_LABEL_MAX+1]; - - r = dns_label_unescape(&p, label, sizeof(label)); - if (r < 0) - break; - if (r == 0) - break; - - ascii_strlower_n(label, r); - siphash24_compress(label, r, state); - siphash24_compress_byte(0, state); /* make sure foobar and foo.bar result in different hashes */ - } - - /* enforce that all names are terminated by the empty label */ - string_hash_func("", state); -} - -int dns_name_compare_func(const void *a, const void *b) { - const char *x, *y; - int r, q; - - assert(a); - assert(b); - - x = (const char *) a + strlen(a); - y = (const char *) b + strlen(b); - - for (;;) { - char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; - - if (x == NULL && y == NULL) - return 0; - - r = dns_label_unescape_suffix(a, &x, la, sizeof(la)); - q = dns_label_unescape_suffix(b, &y, lb, sizeof(lb)); - if (r < 0 || q < 0) - return r - q; - - r = ascii_strcasecmp_nn(la, r, lb, q); - if (r != 0) - return r; - } -} - -const struct hash_ops dns_name_hash_ops = { - .hash = dns_name_hash_func, - .compare = dns_name_compare_func -}; - -int dns_name_equal(const char *x, const char *y) { - int r, q; - - assert(x); - assert(y); - - for (;;) { - char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; - - r = dns_label_unescape(&x, la, sizeof(la)); - if (r < 0) - return r; - - q = dns_label_unescape(&y, lb, sizeof(lb)); - if (q < 0) - return q; - - if (r != q) - return false; - if (r == 0) - return true; - - if (ascii_strcasecmp_n(la, lb, r) != 0) - return false; - } -} - -int dns_name_endswith(const char *name, const char *suffix) { - const char *n, *s, *saved_n = NULL; - int r, q; - - assert(name); - assert(suffix); - - n = name; - s = suffix; - - for (;;) { - char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; - - r = dns_label_unescape(&n, ln, sizeof(ln)); - if (r < 0) - return r; - - if (!saved_n) - saved_n = n; - - q = dns_label_unescape(&s, ls, sizeof(ls)); - if (q < 0) - return q; - - if (r == 0 && q == 0) - return true; - if (r == 0 && saved_n == n) - return false; - - if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) { - - /* Not the same, let's jump back, and try with the next label again */ - s = suffix; - n = saved_n; - saved_n = NULL; - } - } -} - -int dns_name_startswith(const char *name, const char *prefix) { - const char *n, *p; - int r, q; - - assert(name); - assert(prefix); - - n = name; - p = prefix; - - for (;;) { - char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX]; - - r = dns_label_unescape(&p, lp, sizeof(lp)); - if (r < 0) - return r; - if (r == 0) - return true; - - q = dns_label_unescape(&n, ln, sizeof(ln)); - if (q < 0) - return q; - - if (r != q) - return false; - if (ascii_strcasecmp_n(ln, lp, r) != 0) - return false; - } -} - -int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret) { - const char *n, *s, *saved_before = NULL, *saved_after = NULL, *prefix; - int r, q; - - assert(name); - assert(old_suffix); - assert(new_suffix); - assert(ret); - - n = name; - s = old_suffix; - - for (;;) { - char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; - - if (!saved_before) - saved_before = n; - - r = dns_label_unescape(&n, ln, sizeof(ln)); - if (r < 0) - return r; - - if (!saved_after) - saved_after = n; - - q = dns_label_unescape(&s, ls, sizeof(ls)); - if (q < 0) - return q; - - if (r == 0 && q == 0) - break; - if (r == 0 && saved_after == n) { - *ret = NULL; /* doesn't match */ - return 0; - } - - if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) { - - /* Not the same, let's jump back, and try with the next label again */ - s = old_suffix; - n = saved_after; - saved_after = saved_before = NULL; - } - } - - /* Found it! Now generate the new name */ - prefix = strndupa(name, saved_before - name); - - r = dns_name_concat(prefix, new_suffix, ret); - if (r < 0) - return r; - - return 1; -} - -int dns_name_between(const char *a, const char *b, const char *c) { - int n; - - /* Determine if b is strictly greater than a and strictly smaller than c. - We consider the order of names to be circular, so that if a is - strictly greater than c, we consider b to be between them if it is - either greater than a or smaller than c. This is how the canonical - DNS name order used in NSEC records work. */ - - n = dns_name_compare_func(a, c); - if (n == 0) - return -EINVAL; - else if (n < 0) - /* a<---b--->c */ - return dns_name_compare_func(a, b) < 0 && - dns_name_compare_func(b, c) < 0; - else - /* <--b--c a--b--> */ - return dns_name_compare_func(b, c) < 0 || - dns_name_compare_func(a, b) < 0; -} - -int dns_name_reverse(int family, const union in_addr_union *a, char **ret) { - const uint8_t *p; - int r; - - assert(a); - assert(ret); - - p = (const uint8_t*) a; - - if (family == AF_INET) - r = asprintf(ret, "%u.%u.%u.%u.in-addr.arpa", p[3], p[2], p[1], p[0]); - else if (family == AF_INET6) - r = asprintf(ret, "%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.ip6.arpa", - hexchar(p[15] & 0xF), hexchar(p[15] >> 4), hexchar(p[14] & 0xF), hexchar(p[14] >> 4), - hexchar(p[13] & 0xF), hexchar(p[13] >> 4), hexchar(p[12] & 0xF), hexchar(p[12] >> 4), - hexchar(p[11] & 0xF), hexchar(p[11] >> 4), hexchar(p[10] & 0xF), hexchar(p[10] >> 4), - hexchar(p[ 9] & 0xF), hexchar(p[ 9] >> 4), hexchar(p[ 8] & 0xF), hexchar(p[ 8] >> 4), - hexchar(p[ 7] & 0xF), hexchar(p[ 7] >> 4), hexchar(p[ 6] & 0xF), hexchar(p[ 6] >> 4), - hexchar(p[ 5] & 0xF), hexchar(p[ 5] >> 4), hexchar(p[ 4] & 0xF), hexchar(p[ 4] >> 4), - hexchar(p[ 3] & 0xF), hexchar(p[ 3] >> 4), hexchar(p[ 2] & 0xF), hexchar(p[ 2] >> 4), - hexchar(p[ 1] & 0xF), hexchar(p[ 1] >> 4), hexchar(p[ 0] & 0xF), hexchar(p[ 0] >> 4)); - else - return -EAFNOSUPPORT; - if (r < 0) - return -ENOMEM; - - return 0; -} - -int dns_name_address(const char *p, int *family, union in_addr_union *address) { - int r; - - assert(p); - assert(family); - assert(address); - - r = dns_name_endswith(p, "in-addr.arpa"); - if (r < 0) - return r; - if (r > 0) { - uint8_t a[4]; - unsigned i; - - for (i = 0; i < ELEMENTSOF(a); i++) { - char label[DNS_LABEL_MAX+1]; - - r = dns_label_unescape(&p, label, sizeof(label)); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - if (r > 3) - return -EINVAL; - - r = safe_atou8(label, &a[i]); - if (r < 0) - return r; - } - - r = dns_name_equal(p, "in-addr.arpa"); - if (r <= 0) - return r; - - *family = AF_INET; - address->in.s_addr = htobe32(((uint32_t) a[3] << 24) | - ((uint32_t) a[2] << 16) | - ((uint32_t) a[1] << 8) | - (uint32_t) a[0]); - - return 1; - } - - r = dns_name_endswith(p, "ip6.arpa"); - if (r < 0) - return r; - if (r > 0) { - struct in6_addr a; - unsigned i; - - for (i = 0; i < ELEMENTSOF(a.s6_addr); i++) { - char label[DNS_LABEL_MAX+1]; - int x, y; - - r = dns_label_unescape(&p, label, sizeof(label)); - if (r <= 0) - return r; - if (r != 1) - return -EINVAL; - x = unhexchar(label[0]); - if (x < 0) - return -EINVAL; - - r = dns_label_unescape(&p, label, sizeof(label)); - if (r <= 0) - return r; - if (r != 1) - return -EINVAL; - y = unhexchar(label[0]); - if (y < 0) - return -EINVAL; - - a.s6_addr[ELEMENTSOF(a.s6_addr) - i - 1] = (uint8_t) y << 4 | (uint8_t) x; - } - - r = dns_name_equal(p, "ip6.arpa"); - if (r <= 0) - return r; - - *family = AF_INET6; - address->in6 = a; - return 1; - } - - return 0; -} - -bool dns_name_is_root(const char *name) { - - assert(name); - - /* There are exactly two ways to encode the root domain name: - * as empty string, or with a single dot. */ - - return STR_IN_SET(name, "", "."); -} - -bool dns_name_is_single_label(const char *name) { - int r; - - assert(name); - - r = dns_name_parent(&name); - if (r <= 0) - return false; - - return dns_name_is_root(name); -} - -/* Encode a domain name according to RFC 1035 Section 3.1, without compression */ -int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical) { - uint8_t *label_length, *out; - int r; - - assert(domain); - assert(buffer); - - out = buffer; - - do { - /* Reserve a byte for label length */ - if (len <= 0) - return -ENOBUFS; - len--; - label_length = out; - out++; - - /* Convert and copy a single label. Note that - * dns_label_unescape() returns 0 when it hits the end - * of the domain name, which we rely on here to encode - * the trailing NUL byte. */ - r = dns_label_unescape(&domain, (char *) out, len); - if (r < 0) - return r; - - /* Optionally, output the name in DNSSEC canonical - * format, as described in RFC 4034, section 6.2. Or - * in other words: in lower-case. */ - if (canonical) - ascii_strlower_n((char*) out, (size_t) r); - - /* Fill label length, move forward */ - *label_length = r; - out += r; - len -= r; - - } while (r != 0); - - /* Verify the maximum size of the encoded name. The trailing - * dot + NUL byte account are included this time, hence - * compare against DNS_HOSTNAME_MAX + 2 (which is 255) this - * time. */ - if (out - buffer > DNS_HOSTNAME_MAX + 2) - return -EINVAL; - - return out - buffer; -} - -static bool srv_type_label_is_valid(const char *label, size_t n) { - size_t k; - - assert(label); - - if (n < 2) /* Label needs to be at least 2 chars long */ - return false; - - if (label[0] != '_') /* First label char needs to be underscore */ - return false; - - /* Second char must be a letter */ - if (!(label[1] >= 'A' && label[1] <= 'Z') && - !(label[1] >= 'a' && label[1] <= 'z')) - return false; - - /* Third and further chars must be alphanumeric or a hyphen */ - for (k = 2; k < n; k++) { - if (!(label[k] >= 'A' && label[k] <= 'Z') && - !(label[k] >= 'a' && label[k] <= 'z') && - !(label[k] >= '0' && label[k] <= '9') && - label[k] != '-') - return false; - } - - return true; -} - -bool dns_srv_type_is_valid(const char *name) { - unsigned c = 0; - int r; - - if (!name) - return false; - - for (;;) { - char label[DNS_LABEL_MAX]; - - /* This more or less implements RFC 6335, Section 5.1 */ - - r = dns_label_unescape(&name, label, sizeof(label)); - if (r < 0) - return false; - if (r == 0) - break; - - if (c >= 2) - return false; - - if (!srv_type_label_is_valid(label, r)) - return false; - - c++; - } - - return c == 2; /* exactly two labels */ -} - -bool dns_service_name_is_valid(const char *name) { - size_t l; - - /* This more or less implements RFC 6763, Section 4.1.1 */ - - if (!name) - return false; - - if (!utf8_is_valid(name)) - return false; - - if (string_has_cc(name, NULL)) - return false; - - l = strlen(name); - if (l <= 0) - return false; - if (l > 63) - return false; - - return true; -} - -int dns_service_join(const char *name, const char *type, const char *domain, char **ret) { - char escaped[DNS_LABEL_ESCAPED_MAX]; - _cleanup_free_ char *n = NULL; - int r; - - assert(type); - assert(domain); - assert(ret); - - if (!dns_srv_type_is_valid(type)) - return -EINVAL; - - if (!name) - return dns_name_concat(type, domain, ret); - - if (!dns_service_name_is_valid(name)) - return -EINVAL; - - r = dns_label_escape(name, strlen(name), escaped, sizeof(escaped)); - if (r < 0) - return r; - - r = dns_name_concat(type, domain, &n); - if (r < 0) - return r; - - return dns_name_concat(escaped, n, ret); -} - -static bool dns_service_name_label_is_valid(const char *label, size_t n) { - char *s; - - assert(label); - - if (memchr(label, 0, n)) - return false; - - s = strndupa(label, n); - return dns_service_name_is_valid(s); -} - -int dns_service_split(const char *joined, char **_name, char **_type, char **_domain) { - _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL; - const char *p = joined, *q = NULL, *d = NULL; - char a[DNS_LABEL_MAX], b[DNS_LABEL_MAX], c[DNS_LABEL_MAX]; - int an, bn, cn, r; - unsigned x = 0; - - assert(joined); - - /* Get first label from the full name */ - an = dns_label_unescape(&p, a, sizeof(a)); - if (an < 0) - return an; - - if (an > 0) { - x++; - - /* If there was a first label, try to get the second one */ - bn = dns_label_unescape(&p, b, sizeof(b)); - if (bn < 0) - return bn; - - if (bn > 0) { - x++; - - /* If there was a second label, try to get the third one */ - q = p; - cn = dns_label_unescape(&p, c, sizeof(c)); - if (cn < 0) - return cn; - - if (cn > 0) - x++; - } else - cn = 0; - } else - an = 0; - - if (x >= 2 && srv_type_label_is_valid(b, bn)) { - - if (x >= 3 && srv_type_label_is_valid(c, cn)) { - - if (dns_service_name_label_is_valid(a, an)) { - /* OK, got . . . */ - - name = strndup(a, an); - if (!name) - return -ENOMEM; - - type = strjoin(b, ".", c, NULL); - if (!type) - return -ENOMEM; - - d = p; - goto finish; - } - - } else if (srv_type_label_is_valid(a, an)) { - - /* OK, got . . */ - - name = NULL; - - type = strjoin(a, ".", b, NULL); - if (!type) - return -ENOMEM; - - d = q; - goto finish; - } - } - - name = NULL; - type = NULL; - d = joined; - -finish: - r = dns_name_normalize(d, &domain); - if (r < 0) - return r; - - if (_domain) { - *_domain = domain; - domain = NULL; - } - - if (_type) { - *_type = type; - type = NULL; - } - - if (_name) { - *_name = name; - name = NULL; - } - - return 0; -} - -static int dns_name_build_suffix_table(const char *name, const char*table[]) { - const char *p; - unsigned n = 0; - int r; - - assert(name); - assert(table); - - p = name; - for (;;) { - if (n > DNS_N_LABELS_MAX) - return -EINVAL; - - table[n] = p; - r = dns_name_parent(&p); - if (r < 0) - return r; - if (r == 0) - break; - - n++; - } - - return (int) n; -} - -int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) { - const char* labels[DNS_N_LABELS_MAX+1]; - int n; - - assert(name); - assert(ret); - - n = dns_name_build_suffix_table(name, labels); - if (n < 0) - return n; - - if ((unsigned) n < n_labels) - return -EINVAL; - - *ret = labels[n - n_labels]; - return (int) (n - n_labels); -} - -int dns_name_skip(const char *a, unsigned n_labels, const char **ret) { - int r; - - assert(a); - assert(ret); - - for (; n_labels > 0; n_labels--) { - r = dns_name_parent(&a); - if (r < 0) - return r; - if (r == 0) { - *ret = ""; - return 0; - } - } - - *ret = a; - return 1; -} - -int dns_name_count_labels(const char *name) { - unsigned n = 0; - const char *p; - int r; - - assert(name); - - p = name; - for (;;) { - r = dns_name_parent(&p); - if (r < 0) - return r; - if (r == 0) - break; - - if (n >= DNS_N_LABELS_MAX) - return -EINVAL; - - n++; - } - - return (int) n; -} - -int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) { - int r; - - assert(a); - assert(b); - - r = dns_name_skip(a, n_labels, &a); - if (r <= 0) - return r; - - return dns_name_equal(a, b); -} - -int dns_name_common_suffix(const char *a, const char *b, const char **ret) { - const char *a_labels[DNS_N_LABELS_MAX+1], *b_labels[DNS_N_LABELS_MAX+1]; - int n = 0, m = 0, k = 0, r, q; - - assert(a); - assert(b); - assert(ret); - - /* Determines the common suffix of domain names a and b */ - - n = dns_name_build_suffix_table(a, a_labels); - if (n < 0) - return n; - - m = dns_name_build_suffix_table(b, b_labels); - if (m < 0) - return m; - - for (;;) { - char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; - const char *x, *y; - - if (k >= n || k >= m) { - *ret = a_labels[n - k]; - return 0; - } - - x = a_labels[n - 1 - k]; - r = dns_label_unescape(&x, la, sizeof(la)); - if (r < 0) - return r; - - y = b_labels[m - 1 - k]; - q = dns_label_unescape(&y, lb, sizeof(lb)); - if (q < 0) - return q; - - if (r != q || ascii_strcasecmp_n(la, lb, r) != 0) { - *ret = a_labels[n - k]; - return 0; - } - - k++; - } -} - -int dns_name_apply_idna(const char *name, char **ret) { - _cleanup_free_ char *buf = NULL; - size_t n = 0, allocated = 0; - bool first = true; - int r, q; - - assert(name); - assert(ret); - - for (;;) { - char label[DNS_LABEL_MAX]; - - r = dns_label_unescape(&name, label, sizeof(label)); - if (r < 0) - return r; - if (r == 0) - break; - - q = dns_label_apply_idna(label, r, label, sizeof(label)); - if (q < 0) - return q; - if (q > 0) - r = q; - - if (!GREEDY_REALLOC(buf, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) - return -ENOMEM; - - r = dns_label_escape(label, r, buf + n + !first, DNS_LABEL_ESCAPED_MAX); - if (r < 0) - return r; - - if (first) - first = false; - else - buf[n++] = '.'; - - n +=r; - } - - if (n > DNS_HOSTNAME_MAX) - return -EINVAL; - - if (!GREEDY_REALLOC(buf, allocated, n + 1)) - return -ENOMEM; - - buf[n] = 0; - *ret = buf; - buf = NULL; - - return (int) n; -} diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h deleted file mode 100644 index af780f0b8b..0000000000 --- a/src/shared/dns-domain.h +++ /dev/null @@ -1,109 +0,0 @@ -#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 -#include -#include -#include - -#include "hashmap.h" -#include "in-addr-util.h" - -/* Length of a single label, with all escaping removed, excluding any trailing dot or NUL byte */ -#define DNS_LABEL_MAX 63 - -/* Worst case length of a single label, with all escaping applied and room for a trailing NUL byte. */ -#define DNS_LABEL_ESCAPED_MAX (DNS_LABEL_MAX*4+1) - -/* Maximum length of a full hostname, consisting of a series of unescaped labels, and no trailing dot or NUL byte */ -#define DNS_HOSTNAME_MAX 253 - -/* Maximum length of a full hostname, on the wire, including the final NUL byte */ -#define DNS_WIRE_FOMAT_HOSTNAME_MAX 255 - -/* Maximum number of labels per valid hostname */ -#define DNS_N_LABELS_MAX 127 - -int dns_label_unescape(const char **name, char *dest, size_t sz); -int dns_label_unescape_suffix(const char *name, const char **label_end, char *dest, size_t sz); -int dns_label_escape(const char *p, size_t l, char *dest, size_t sz); -int dns_label_escape_new(const char *p, size_t l, char **ret); - -static inline int dns_name_parent(const char **name) { - return dns_label_unescape(name, NULL, DNS_LABEL_MAX); -} - -int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); -int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); - -int dns_name_concat(const char *a, const char *b, char **ret); - -static inline int dns_name_normalize(const char *s, char **ret) { - /* dns_name_concat() normalizes as a side-effect */ - return dns_name_concat(s, NULL, ret); -} - -static inline int dns_name_is_valid(const char *s) { - int r; - - /* dns_name_normalize() verifies as a side effect */ - r = dns_name_normalize(s, NULL); - if (r == -EINVAL) - return 0; - if (r < 0) - return r; - return 1; -} - -void dns_name_hash_func(const void *s, struct siphash *state); -int dns_name_compare_func(const void *a, const void *b); -extern const struct hash_ops dns_name_hash_ops; - -int dns_name_between(const char *a, const char *b, const char *c); -int dns_name_equal(const char *x, const char *y); -int dns_name_endswith(const char *name, const char *suffix); -int dns_name_startswith(const char *name, const char *prefix); - -int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret); - -int dns_name_reverse(int family, const union in_addr_union *a, char **ret); -int dns_name_address(const char *p, int *family, union in_addr_union *a); - -bool dns_name_is_root(const char *name); -bool dns_name_is_single_label(const char *name); - -int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical); - -bool dns_srv_type_is_valid(const char *name); -bool dns_service_name_is_valid(const char *name); - -int dns_service_join(const char *name, const char *type, const char *domain, char **ret); -int dns_service_split(const char *joined, char **name, char **type, char **domain); - -int dns_name_suffix(const char *name, unsigned n_labels, const char **ret); -int dns_name_count_labels(const char *name); - -int dns_name_skip(const char *a, unsigned n_labels, const char **ret); -int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b); - -int dns_name_common_suffix(const char *a, const char *b, const char **ret); - -int dns_name_apply_idna(const char *name, char **ret); diff --git a/src/shared/dropin.c b/src/shared/dropin.c deleted file mode 100644 index b9cd952ac8..0000000000 --- a/src/shared/dropin.c +++ /dev/null @@ -1,251 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Zbigniew Jędrzejewski-Szmek - - 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 -#include -#include -#include - -#include "alloc-util.h" -#include "conf-files.h" -#include "dropin.h" -#include "escape.h" -#include "fd-util.h" -#include "fileio-label.h" -#include "hashmap.h" -#include "log.h" -#include "macro.h" -#include "mkdir.h" -#include "path-util.h" -#include "set.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" - -int drop_in_file(const char *dir, const char *unit, unsigned level, - const char *name, char **_p, char **_q) { - - _cleanup_free_ char *b = NULL; - char *p, *q; - - char prefix[DECIMAL_STR_MAX(unsigned)]; - - assert(unit); - assert(name); - assert(_p); - assert(_q); - - sprintf(prefix, "%u", level); - - b = xescape(name, "/."); - if (!b) - return -ENOMEM; - - if (!filename_is_valid(b)) - return -EINVAL; - - p = strjoin(dir, "/", unit, ".d", NULL); - if (!p) - return -ENOMEM; - - q = strjoin(p, "/", prefix, "-", b, ".conf", NULL); - if (!q) { - free(p); - return -ENOMEM; - } - - *_p = p; - *_q = q; - return 0; -} - -int write_drop_in(const char *dir, const char *unit, unsigned level, - const char *name, const char *data) { - - _cleanup_free_ char *p = NULL, *q = NULL; - int r; - - assert(dir); - assert(unit); - assert(name); - assert(data); - - r = drop_in_file(dir, unit, level, name, &p, &q); - if (r < 0) - return r; - - (void) mkdir_p(p, 0755); - return write_string_file_atomic_label(q, data); -} - -int write_drop_in_format(const char *dir, const char *unit, unsigned level, - const char *name, const char *format, ...) { - _cleanup_free_ char *p = NULL; - va_list ap; - int r; - - assert(dir); - assert(unit); - assert(name); - assert(format); - - va_start(ap, format); - r = vasprintf(&p, format, ap); - va_end(ap); - - if (r < 0) - return -ENOMEM; - - return write_drop_in(dir, unit, level, name, p); -} - -static int iterate_dir( - const char *path, - UnitDependency dependency, - dependency_consumer_t consumer, - void *arg, - char ***strv) { - - _cleanup_closedir_ DIR *d = NULL; - int r; - - assert(path); - - /* The config directories are special, since the order of the - * drop-ins matters */ - if (dependency < 0) { - r = strv_extend(strv, path); - if (r < 0) - return log_oom(); - - return 0; - } - - assert(consumer); - - d = opendir(path); - if (!d) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open directory %s: %m", path); - } - - for (;;) { - struct dirent *de; - _cleanup_free_ char *f = NULL; - - errno = 0; - de = readdir(d); - if (!de && errno > 0) - return log_error_errno(errno, "Failed to read directory %s: %m", path); - - if (!de) - break; - - if (hidden_or_backup_file(de->d_name)) - continue; - - f = strjoin(path, "/", de->d_name, NULL); - if (!f) - return log_oom(); - - r = consumer(dependency, de->d_name, f, arg); - if (r < 0) - return r; - } - - return 0; -} - -int unit_file_process_dir( - Set *unit_path_cache, - const char *unit_path, - const char *name, - const char *suffix, - UnitDependency dependency, - dependency_consumer_t consumer, - void *arg, - char ***strv) { - - _cleanup_free_ char *path = NULL; - int r; - - assert(unit_path); - assert(name); - assert(suffix); - - path = strjoin(unit_path, "/", name, suffix, NULL); - if (!path) - return log_oom(); - - if (!unit_path_cache || set_get(unit_path_cache, path)) - (void) iterate_dir(path, dependency, consumer, arg, strv); - - if (unit_name_is_valid(name, UNIT_NAME_INSTANCE)) { - _cleanup_free_ char *template = NULL, *p = NULL; - /* Also try the template dir */ - - r = unit_name_template(name, &template); - if (r < 0) - return log_error_errno(r, "Failed to generate template from unit name: %m"); - - p = strjoin(unit_path, "/", template, suffix, NULL); - if (!p) - return log_oom(); - - if (!unit_path_cache || set_get(unit_path_cache, p)) - (void) iterate_dir(p, dependency, consumer, arg, strv); - } - - return 0; -} - -int unit_file_find_dropin_paths( - char **lookup_path, - Set *unit_path_cache, - Set *names, - char ***paths) { - - _cleanup_strv_free_ char **strv = NULL, **ans = NULL; - Iterator i; - char *t; - int r; - - assert(paths); - - SET_FOREACH(t, names, i) { - char **p; - - STRV_FOREACH(p, lookup_path) - unit_file_process_dir(unit_path_cache, *p, t, ".d", _UNIT_DEPENDENCY_INVALID, NULL, NULL, &strv); - } - - if (strv_isempty(strv)) - return 0; - - r = conf_files_list_strv(&ans, ".conf", NULL, (const char**) strv); - if (r < 0) - return log_warning_errno(r, "Failed to get list of configuration files: %m"); - - *paths = ans; - ans = NULL; - return 1; -} diff --git a/src/shared/dropin.h b/src/shared/dropin.h deleted file mode 100644 index c1936f397b..0000000000 --- a/src/shared/dropin.h +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Zbigniew Jędrzejewski-Szmek - - 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 "hashmap.h" -#include "macro.h" -#include "set.h" -#include "unit-name.h" - -int drop_in_file(const char *dir, const char *unit, unsigned level, - const char *name, char **_p, char **_q); - -int write_drop_in(const char *dir, const char *unit, unsigned level, - const char *name, const char *data); - -int write_drop_in_format(const char *dir, const char *unit, unsigned level, - const char *name, const char *format, ...) _printf_(5, 6); - -/** - * This callback will be called for each directory entry @entry, - * with @filepath being the full path to the entry. - * - * If return value is negative, loop will be aborted. - */ -typedef int (*dependency_consumer_t)(UnitDependency dependency, - const char *entry, - const char* filepath, - void *arg); - -int unit_file_process_dir( - Set * unit_path_cache, - const char *unit_path, - const char *name, - const char *suffix, - UnitDependency dependency, - dependency_consumer_t consumer, - void *arg, - char ***strv); - -int unit_file_find_dropin_paths( - char **lookup_path, - Set *unit_path_cache, - Set *names, - char ***paths); diff --git a/src/shared/efivars.c b/src/shared/efivars.c deleted file mode 100644 index 8631a5a5d9..0000000000 --- a/src/shared/efivars.c +++ /dev/null @@ -1,715 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 -#include -#include -#include -#include -#include -#include -#include - -#include "sd-id128.h" - -#include "alloc-util.h" -#include "dirent-util.h" -#include "efivars.h" -#include "fd-util.h" -#include "io-util.h" -#include "macro.h" -#include "parse-util.h" -#include "stdio-util.h" -#include "time-util.h" -#include "utf8.h" -#include "util.h" -#include "virt.h" - -#ifdef ENABLE_EFI - -#define LOAD_OPTION_ACTIVE 0x00000001 -#define MEDIA_DEVICE_PATH 0x04 -#define MEDIA_HARDDRIVE_DP 0x01 -#define MEDIA_FILEPATH_DP 0x04 -#define SIGNATURE_TYPE_GUID 0x02 -#define MBR_TYPE_EFI_PARTITION_TABLE_HEADER 0x02 -#define END_DEVICE_PATH_TYPE 0x7f -#define END_ENTIRE_DEVICE_PATH_SUBTYPE 0xff -#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001 - -struct boot_option { - uint32_t attr; - uint16_t path_len; - uint16_t title[]; -} _packed_; - -struct drive_path { - uint32_t part_nr; - uint64_t part_start; - uint64_t part_size; - char signature[16]; - uint8_t mbr_type; - uint8_t signature_type; -} _packed_; - -struct device_path { - uint8_t type; - uint8_t sub_type; - uint16_t length; - union { - uint16_t path[0]; - struct drive_path drive; - }; -} _packed_; - -bool is_efi_boot(void) { - return access("/sys/firmware/efi", F_OK) >= 0; -} - -static int read_flag(const char *varname) { - int r; - _cleanup_free_ void *v = NULL; - size_t s; - uint8_t b; - - r = efi_get_variable(EFI_VENDOR_GLOBAL, varname, NULL, &v, &s); - if (r < 0) - return r; - - if (s != 1) - return -EINVAL; - - b = *(uint8_t *)v; - r = b > 0; - return r; -} - -bool is_efi_secure_boot(void) { - return read_flag("SecureBoot") > 0; -} - -bool is_efi_secure_boot_setup_mode(void) { - return read_flag("SetupMode") > 0; -} - -int efi_reboot_to_firmware_supported(void) { - int r; - size_t s; - uint64_t b; - _cleanup_free_ void *v = NULL; - - if (!is_efi_boot() || detect_container() > 0) - return -EOPNOTSUPP; - - r = efi_get_variable(EFI_VENDOR_GLOBAL, "OsIndicationsSupported", NULL, &v, &s); - if (r < 0) - return r; - else if (s != sizeof(uint64_t)) - return -EINVAL; - - b = *(uint64_t *)v; - b &= EFI_OS_INDICATIONS_BOOT_TO_FW_UI; - return b > 0 ? 0 : -EOPNOTSUPP; -} - -static int get_os_indications(uint64_t *os_indication) { - int r; - size_t s; - _cleanup_free_ void *v = NULL; - - r = efi_reboot_to_firmware_supported(); - if (r < 0) - return r; - - r = efi_get_variable(EFI_VENDOR_GLOBAL, "OsIndications", NULL, &v, &s); - if (r == -ENOENT) { - /* Some firmware implementations that do support - * OsIndications and report that with - * OsIndicationsSupported will remove the - * OsIndications variable when it is unset. Let's - * pretend it's 0 then, to hide this implementation - * detail. Note that this call will return -ENOENT - * then only if the support for OsIndications is - * missing entirely, as determined by - * efi_reboot_to_firmware_supported() above. */ - *os_indication = 0; - return 0; - } else if (r < 0) - return r; - else if (s != sizeof(uint64_t)) - return -EINVAL; - - *os_indication = *(uint64_t *)v; - return 0; -} - -int efi_get_reboot_to_firmware(void) { - int r; - uint64_t b; - - r = get_os_indications(&b); - if (r < 0) - return r; - - return !!(b & EFI_OS_INDICATIONS_BOOT_TO_FW_UI); -} - -int efi_set_reboot_to_firmware(bool value) { - int r; - uint64_t b, b_new; - - r = get_os_indications(&b); - if (r < 0) - return r; - - if (value) - b_new = b | EFI_OS_INDICATIONS_BOOT_TO_FW_UI; - else - b_new = b & ~EFI_OS_INDICATIONS_BOOT_TO_FW_UI; - - /* Avoid writing to efi vars store if we can due to firmware bugs. */ - if (b != b_new) - return efi_set_variable(EFI_VENDOR_GLOBAL, "OsIndications", &b_new, sizeof(uint64_t)); - - return 0; -} - -int efi_get_variable( - sd_id128_t vendor, - const char *name, - uint32_t *attribute, - void **value, - size_t *size) { - - _cleanup_close_ int fd = -1; - _cleanup_free_ char *p = NULL; - uint32_t a; - ssize_t n; - struct stat st; - _cleanup_free_ void *buf = NULL; - - assert(name); - assert(value); - assert(size); - - if (asprintf(&p, - "/sys/firmware/efi/efivars/%s-%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", - name, SD_ID128_FORMAT_VAL(vendor)) < 0) - return -ENOMEM; - - fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return -errno; - - if (fstat(fd, &st) < 0) - return -errno; - if (st.st_size < 4) - return -EIO; - if (st.st_size > 4*1024*1024 + 4) - return -E2BIG; - - n = read(fd, &a, sizeof(a)); - if (n < 0) - return -errno; - if (n != sizeof(a)) - return -EIO; - - buf = malloc(st.st_size - 4 + 2); - if (!buf) - return -ENOMEM; - - n = read(fd, buf, (size_t) st.st_size - 4); - if (n < 0) - return -errno; - if (n != (ssize_t) st.st_size - 4) - return -EIO; - - /* Always NUL terminate (2 bytes, to protect UTF-16) */ - ((char*) buf)[st.st_size - 4] = 0; - ((char*) buf)[st.st_size - 4 + 1] = 0; - - *value = buf; - buf = NULL; - *size = (size_t) st.st_size - 4; - - if (attribute) - *attribute = a; - - return 0; -} - -int efi_set_variable( - sd_id128_t vendor, - const char *name, - const void *value, - size_t size) { - - struct var { - uint32_t attr; - char buf[]; - } _packed_ * _cleanup_free_ buf = NULL; - _cleanup_free_ char *p = NULL; - _cleanup_close_ int fd = -1; - - assert(name); - - if (asprintf(&p, - "/sys/firmware/efi/efivars/%s-%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", - name, SD_ID128_FORMAT_VAL(vendor)) < 0) - return -ENOMEM; - - if (size == 0) { - if (unlink(p) < 0) - return -errno; - return 0; - } - - fd = open(p, O_WRONLY|O_CREAT|O_NOCTTY|O_CLOEXEC, 0644); - if (fd < 0) - return -errno; - - buf = malloc(sizeof(uint32_t) + size); - if (!buf) - return -ENOMEM; - - buf->attr = EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS; - memcpy(buf->buf, value, size); - - return loop_write(fd, buf, sizeof(uint32_t) + size, false); -} - -int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p) { - _cleanup_free_ void *s = NULL; - size_t ss = 0; - int r; - char *x; - - r = efi_get_variable(vendor, name, NULL, &s, &ss); - if (r < 0) - return r; - - x = utf16_to_utf8(s, ss); - if (!x) - return -ENOMEM; - - *p = x; - return 0; -} - -static size_t utf16_size(const uint16_t *s) { - size_t l = 0; - - while (s[l] > 0) - l++; - - return (l+1) * sizeof(uint16_t); -} - -static void efi_guid_to_id128(const void *guid, sd_id128_t *id128) { - struct uuid { - uint32_t u1; - uint16_t u2; - uint16_t u3; - uint8_t u4[8]; - } _packed_; - const struct uuid *uuid = guid; - - id128->bytes[0] = (uuid->u1 >> 24) & 0xff; - id128->bytes[1] = (uuid->u1 >> 16) & 0xff; - id128->bytes[2] = (uuid->u1 >> 8) & 0xff; - id128->bytes[3] = (uuid->u1) & 0xff; - id128->bytes[4] = (uuid->u2 >> 8) & 0xff; - id128->bytes[5] = (uuid->u2) & 0xff; - id128->bytes[6] = (uuid->u3 >> 8) & 0xff; - id128->bytes[7] = (uuid->u3) & 0xff; - memcpy(&id128->bytes[8], uuid->u4, sizeof(uuid->u4)); -} - -int efi_get_boot_option( - uint16_t id, - char **title, - sd_id128_t *part_uuid, - char **path, - bool *active) { - - char boot_id[9]; - _cleanup_free_ uint8_t *buf = NULL; - size_t l; - struct boot_option *header; - size_t title_size; - _cleanup_free_ char *s = NULL, *p = NULL; - sd_id128_t p_uuid = SD_ID128_NULL; - int r; - - xsprintf(boot_id, "Boot%04X", id); - r = efi_get_variable(EFI_VENDOR_GLOBAL, boot_id, NULL, (void **)&buf, &l); - if (r < 0) - return r; - if (l < sizeof(struct boot_option)) - return -ENOENT; - - header = (struct boot_option *)buf; - title_size = utf16_size(header->title); - if (title_size > l - offsetof(struct boot_option, title)) - return -EINVAL; - - if (title) { - s = utf16_to_utf8(header->title, title_size); - if (!s) - return -ENOMEM; - } - - if (header->path_len > 0) { - uint8_t *dbuf; - size_t dnext; - - dbuf = buf + offsetof(struct boot_option, title) + title_size; - dnext = 0; - while (dnext < header->path_len) { - struct device_path *dpath; - - dpath = (struct device_path *)(dbuf + dnext); - if (dpath->length < 4) - break; - - /* Type 0x7F – End of Hardware Device Path, Sub-Type 0xFF – End Entire Device Path */ - if (dpath->type == END_DEVICE_PATH_TYPE && dpath->sub_type == END_ENTIRE_DEVICE_PATH_SUBTYPE) - break; - - dnext += dpath->length; - - /* Type 0x04 – Media Device Path */ - if (dpath->type != MEDIA_DEVICE_PATH) - continue; - - /* Sub-Type 1 – Hard Drive */ - if (dpath->sub_type == MEDIA_HARDDRIVE_DP) { - /* 0x02 – GUID Partition Table */ - if (dpath->drive.mbr_type != MBR_TYPE_EFI_PARTITION_TABLE_HEADER) - continue; - - /* 0x02 – GUID signature */ - if (dpath->drive.signature_type != SIGNATURE_TYPE_GUID) - continue; - - if (part_uuid) - efi_guid_to_id128(dpath->drive.signature, &p_uuid); - continue; - } - - /* Sub-Type 4 – File Path */ - if (dpath->sub_type == MEDIA_FILEPATH_DP && !p && path) { - p = utf16_to_utf8(dpath->path, dpath->length-4); - efi_tilt_backslashes(p); - continue; - } - } - } - - if (title) { - *title = s; - s = NULL; - } - if (part_uuid) - *part_uuid = p_uuid; - if (path) { - *path = p; - p = NULL; - } - if (active) - *active = !!(header->attr & LOAD_OPTION_ACTIVE); - - return 0; -} - -static void to_utf16(uint16_t *dest, const char *src) { - int i; - - for (i = 0; src[i] != '\0'; i++) - dest[i] = src[i]; - dest[i] = '\0'; -} - -struct guid { - uint32_t u1; - uint16_t u2; - uint16_t u3; - uint8_t u4[8]; -} _packed_; - -static void id128_to_efi_guid(sd_id128_t id, void *guid) { - struct guid *uuid = guid; - - uuid->u1 = id.bytes[0] << 24 | id.bytes[1] << 16 | id.bytes[2] << 8 | id.bytes[3]; - uuid->u2 = id.bytes[4] << 8 | id.bytes[5]; - uuid->u3 = id.bytes[6] << 8 | id.bytes[7]; - memcpy(uuid->u4, id.bytes+8, sizeof(uuid->u4)); -} - -static uint16_t *tilt_slashes(uint16_t *s) { - uint16_t *p; - - for (p = s; *p; p++) - if (*p == '/') - *p = '\\'; - - return s; -} - -int efi_add_boot_option(uint16_t id, const char *title, - uint32_t part, uint64_t pstart, uint64_t psize, - sd_id128_t part_uuid, const char *path) { - char boot_id[9]; - size_t size; - size_t title_len; - size_t path_len; - struct boot_option *option; - struct device_path *devicep; - _cleanup_free_ char *buf = NULL; - - title_len = (strlen(title)+1) * 2; - path_len = (strlen(path)+1) * 2; - - buf = calloc(sizeof(struct boot_option) + title_len + - sizeof(struct drive_path) + - sizeof(struct device_path) + path_len, 1); - if (!buf) - return -ENOMEM; - - /* header */ - option = (struct boot_option *)buf; - option->attr = LOAD_OPTION_ACTIVE; - option->path_len = offsetof(struct device_path, drive) + sizeof(struct drive_path) + - offsetof(struct device_path, path) + path_len + - offsetof(struct device_path, path); - to_utf16(option->title, title); - size = offsetof(struct boot_option, title) + title_len; - - /* partition info */ - devicep = (struct device_path *)(buf + size); - devicep->type = MEDIA_DEVICE_PATH; - devicep->sub_type = MEDIA_HARDDRIVE_DP; - devicep->length = offsetof(struct device_path, drive) + sizeof(struct drive_path); - devicep->drive.part_nr = part; - devicep->drive.part_start = pstart; - devicep->drive.part_size = psize; - devicep->drive.signature_type = SIGNATURE_TYPE_GUID; - devicep->drive.mbr_type = MBR_TYPE_EFI_PARTITION_TABLE_HEADER; - id128_to_efi_guid(part_uuid, devicep->drive.signature); - size += devicep->length; - - /* path to loader */ - devicep = (struct device_path *)(buf + size); - devicep->type = MEDIA_DEVICE_PATH; - devicep->sub_type = MEDIA_FILEPATH_DP; - devicep->length = offsetof(struct device_path, path) + path_len; - to_utf16(devicep->path, path); - tilt_slashes(devicep->path); - size += devicep->length; - - /* end of path */ - devicep = (struct device_path *)(buf + size); - devicep->type = END_DEVICE_PATH_TYPE; - devicep->sub_type = END_ENTIRE_DEVICE_PATH_SUBTYPE; - devicep->length = offsetof(struct device_path, path); - size += devicep->length; - - xsprintf(boot_id, "Boot%04X", id); - return efi_set_variable(EFI_VENDOR_GLOBAL, boot_id, buf, size); -} - -int efi_remove_boot_option(uint16_t id) { - char boot_id[9]; - - xsprintf(boot_id, "Boot%04X", id); - return efi_set_variable(EFI_VENDOR_GLOBAL, boot_id, NULL, 0); -} - -int efi_get_boot_order(uint16_t **order) { - _cleanup_free_ void *buf = NULL; - size_t l; - int r; - - r = efi_get_variable(EFI_VENDOR_GLOBAL, "BootOrder", NULL, &buf, &l); - if (r < 0) - return r; - - if (l <= 0) - return -ENOENT; - - if (l % sizeof(uint16_t) > 0 || - l / sizeof(uint16_t) > INT_MAX) - return -EINVAL; - - *order = buf; - buf = NULL; - return (int) (l / sizeof(uint16_t)); -} - -int efi_set_boot_order(uint16_t *order, size_t n) { - return efi_set_variable(EFI_VENDOR_GLOBAL, "BootOrder", order, n * sizeof(uint16_t)); -} - -static int boot_id_hex(const char s[4]) { - int i; - int id = 0; - - for (i = 0; i < 4; i++) - if (s[i] >= '0' && s[i] <= '9') - id |= (s[i] - '0') << (3 - i) * 4; - else if (s[i] >= 'A' && s[i] <= 'F') - id |= (s[i] - 'A' + 10) << (3 - i) * 4; - else - return -EINVAL; - - return id; -} - -static int cmp_uint16(const void *_a, const void *_b) { - const uint16_t *a = _a, *b = _b; - - return (int)*a - (int)*b; -} - -int efi_get_boot_options(uint16_t **options) { - _cleanup_closedir_ DIR *dir = NULL; - struct dirent *de; - _cleanup_free_ uint16_t *list = NULL; - size_t alloc = 0; - int count = 0; - - assert(options); - - dir = opendir("/sys/firmware/efi/efivars/"); - if (!dir) - return -errno; - - FOREACH_DIRENT(de, dir, return -errno) { - int id; - - if (strncmp(de->d_name, "Boot", 4) != 0) - continue; - - if (strlen(de->d_name) != 45) - continue; - - if (strcmp(de->d_name + 8, "-8be4df61-93ca-11d2-aa0d-00e098032b8c") != 0) - continue; - - id = boot_id_hex(de->d_name + 4); - if (id < 0) - continue; - - if (!GREEDY_REALLOC(list, alloc, count + 1)) - return -ENOMEM; - - list[count++] = id; - } - - qsort_safe(list, count, sizeof(uint16_t), cmp_uint16); - - *options = list; - list = NULL; - return count; -} - -static int read_usec(sd_id128_t vendor, const char *name, usec_t *u) { - _cleanup_free_ char *j = NULL; - int r; - uint64_t x = 0; - - assert(name); - assert(u); - - r = efi_get_variable_string(EFI_VENDOR_LOADER, name, &j); - if (r < 0) - return r; - - r = safe_atou64(j, &x); - if (r < 0) - return r; - - *u = x; - return 0; -} - -int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader) { - uint64_t x, y; - int r; - - assert(firmware); - assert(loader); - - r = read_usec(EFI_VENDOR_LOADER, "LoaderTimeInitUSec", &x); - if (r < 0) - return r; - - r = read_usec(EFI_VENDOR_LOADER, "LoaderTimeExecUSec", &y); - if (r < 0) - return r; - - if (y == 0 || y < x) - return -EIO; - - if (y > USEC_PER_HOUR) - return -EIO; - - *firmware = x; - *loader = y; - - return 0; -} - -int efi_loader_get_device_part_uuid(sd_id128_t *u) { - _cleanup_free_ char *p = NULL; - int r, parsed[16]; - - r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderDevicePartUUID", &p); - if (r < 0) - return r; - - if (sscanf(p, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", - &parsed[0], &parsed[1], &parsed[2], &parsed[3], - &parsed[4], &parsed[5], &parsed[6], &parsed[7], - &parsed[8], &parsed[9], &parsed[10], &parsed[11], - &parsed[12], &parsed[13], &parsed[14], &parsed[15]) != 16) - return -EIO; - - if (u) { - unsigned i; - - for (i = 0; i < ELEMENTSOF(parsed); i++) - u->bytes[i] = parsed[i]; - } - - return 0; -} - -#endif - -char *efi_tilt_backslashes(char *s) { - char *p; - - for (p = s; *p; p++) - if (*p == '\\') - *p = '/'; - - return s; -} diff --git a/src/shared/efivars.h b/src/shared/efivars.h deleted file mode 100644 index b61d14c4ec..0000000000 --- a/src/shared/efivars.h +++ /dev/null @@ -1,131 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 -#include - -#include "sd-id128.h" - -#include "time-util.h" - -#define EFI_VENDOR_LOADER SD_ID128_MAKE(4a,67,b0,82,0a,4c,41,cf,b6,c7,44,0b,29,bb,8c,4f) -#define EFI_VENDOR_GLOBAL SD_ID128_MAKE(8b,e4,df,61,93,ca,11,d2,aa,0d,00,e0,98,03,2b,8c) -#define EFI_VARIABLE_NON_VOLATILE 0x0000000000000001 -#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x0000000000000002 -#define EFI_VARIABLE_RUNTIME_ACCESS 0x0000000000000004 - -#ifdef ENABLE_EFI - -bool is_efi_boot(void); -bool is_efi_secure_boot(void); -bool is_efi_secure_boot_setup_mode(void); -int efi_reboot_to_firmware_supported(void); -int efi_get_reboot_to_firmware(void); -int efi_set_reboot_to_firmware(bool value); - -int efi_get_variable(sd_id128_t vendor, const char *name, uint32_t *attribute, void **value, size_t *size); -int efi_set_variable(sd_id128_t vendor, const char *name, const void *value, size_t size); -int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p); - -int efi_get_boot_option(uint16_t nr, char **title, sd_id128_t *part_uuid, char **path, bool *active); -int efi_add_boot_option(uint16_t id, const char *title, uint32_t part, uint64_t pstart, uint64_t psize, sd_id128_t part_uuid, const char *path); -int efi_remove_boot_option(uint16_t id); -int efi_get_boot_order(uint16_t **order); -int efi_set_boot_order(uint16_t *order, size_t n); -int efi_get_boot_options(uint16_t **options); - -int efi_loader_get_device_part_uuid(sd_id128_t *u); -int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader); - -#else - -static inline bool is_efi_boot(void) { - return false; -} - -static inline bool is_efi_secure_boot(void) { - return false; -} - -static inline bool is_efi_secure_boot_setup_mode(void) { - return false; -} - -static inline int efi_reboot_to_firmware_supported(void) { - return -EOPNOTSUPP; -} - -static inline int efi_get_reboot_to_firmware(void) { - return -EOPNOTSUPP; -} - -static inline int efi_set_reboot_to_firmware(bool value) { - return -EOPNOTSUPP; -} - -static inline int efi_get_variable(sd_id128_t vendor, const char *name, uint32_t *attribute, void **value, size_t *size) { - return -EOPNOTSUPP; -} - -static inline int efi_set_variable(sd_id128_t vendor, const char *name, const void *value, size_t size) { - return -EOPNOTSUPP; -} - -static inline int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p) { - return -EOPNOTSUPP; -} - -static inline int efi_get_boot_option(uint16_t nr, char **title, sd_id128_t *part_uuid, char **path, bool *active) { - return -EOPNOTSUPP; -} - -static inline int efi_add_boot_option(uint16_t id, const char *title, uint32_t part, uint64_t pstart, uint64_t psize, sd_id128_t part_uuid, const char *path) { - return -EOPNOTSUPP; -} - -static inline int efi_remove_boot_option(uint16_t id) { - return -EOPNOTSUPP; -} - -static inline int efi_get_boot_order(uint16_t **order) { - return -EOPNOTSUPP; -} - -static inline int efi_set_boot_order(uint16_t *order, size_t n) { - return -EOPNOTSUPP; -} - -static inline int efi_get_boot_options(uint16_t **options) { - return -EOPNOTSUPP; -} - -static inline int efi_loader_get_device_part_uuid(sd_id128_t *u) { - return -EOPNOTSUPP; -} - -static inline int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader) { - return -EOPNOTSUPP; -} - -#endif - -char *efi_tilt_backslashes(char *s); diff --git a/src/shared/firewall-util.c b/src/shared/firewall-util.c deleted file mode 100644 index f73108eaa3..0000000000 --- a/src/shared/firewall-util.c +++ /dev/null @@ -1,357 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#warning "Temporary work-around for broken glibc vs. linux kernel header definitions" -#warning "This really should be removed sooner rather than later, when this is fixed upstream" -#define _NET_IF_H 1 - -#include -#include -#include -#include -#include -#include -#include -#include -#ifndef IFNAMSIZ -#define IFNAMSIZ 16 -#endif -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "firewall-util.h" -#include "in-addr-util.h" -#include "macro.h" -#include "socket-util.h" - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct xtc_handle*, iptc_free); - -static int entry_fill_basics( - struct ipt_entry *entry, - int protocol, - const char *in_interface, - const union in_addr_union *source, - unsigned source_prefixlen, - const char *out_interface, - const union in_addr_union *destination, - unsigned destination_prefixlen) { - - assert(entry); - - if (out_interface && !ifname_valid(out_interface)) - return -EINVAL; - if (in_interface && !ifname_valid(in_interface)) - return -EINVAL; - - entry->ip.proto = protocol; - - if (in_interface) { - strcpy(entry->ip.iniface, in_interface); - memset(entry->ip.iniface_mask, 0xFF, strlen(in_interface)+1); - } - if (source) { - entry->ip.src = source->in; - in_addr_prefixlen_to_netmask(&entry->ip.smsk, source_prefixlen); - } - - if (out_interface) { - strcpy(entry->ip.outiface, out_interface); - memset(entry->ip.outiface_mask, 0xFF, strlen(out_interface)+1); - } - if (destination) { - entry->ip.dst = destination->in; - in_addr_prefixlen_to_netmask(&entry->ip.dmsk, destination_prefixlen); - } - - return 0; -} - -int fw_add_masquerade( - bool add, - int af, - int protocol, - const union in_addr_union *source, - unsigned source_prefixlen, - const char *out_interface, - const union in_addr_union *destination, - unsigned destination_prefixlen) { - - _cleanup_(iptc_freep) struct xtc_handle *h = NULL; - struct ipt_entry *entry, *mask; - struct ipt_entry_target *t; - size_t sz; - struct nf_nat_ipv4_multi_range_compat *mr; - int r; - - if (af != AF_INET) - return -EOPNOTSUPP; - - if (protocol != 0 && protocol != IPPROTO_TCP && protocol != IPPROTO_UDP) - return -EOPNOTSUPP; - - h = iptc_init("nat"); - if (!h) - return -errno; - - sz = XT_ALIGN(sizeof(struct ipt_entry)) + - XT_ALIGN(sizeof(struct ipt_entry_target)) + - XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); - - /* Put together the entry we want to add or remove */ - entry = alloca0(sz); - entry->next_offset = sz; - entry->target_offset = XT_ALIGN(sizeof(struct ipt_entry)); - r = entry_fill_basics(entry, protocol, NULL, source, source_prefixlen, out_interface, destination, destination_prefixlen); - if (r < 0) - return r; - - /* Fill in target part */ - t = ipt_get_target(entry); - t->u.target_size = - XT_ALIGN(sizeof(struct ipt_entry_target)) + - XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); - strncpy(t->u.user.name, "MASQUERADE", sizeof(t->u.user.name)); - mr = (struct nf_nat_ipv4_multi_range_compat*) t->data; - mr->rangesize = 1; - - /* Create a search mask entry */ - mask = alloca(sz); - memset(mask, 0xFF, sz); - - if (add) { - if (iptc_check_entry("POSTROUTING", entry, (unsigned char*) mask, h)) - return 0; - if (errno != ENOENT) /* if other error than not existing yet, fail */ - return -errno; - - if (!iptc_insert_entry("POSTROUTING", entry, 0, h)) - return -errno; - } else { - if (!iptc_delete_entry("POSTROUTING", entry, (unsigned char*) mask, h)) { - if (errno == ENOENT) /* if it's already gone, all is good! */ - return 0; - - return -errno; - } - } - - if (!iptc_commit(h)) - return -errno; - - return 0; -} - -int fw_add_local_dnat( - bool add, - int af, - int protocol, - const char *in_interface, - const union in_addr_union *source, - unsigned source_prefixlen, - const union in_addr_union *destination, - unsigned destination_prefixlen, - uint16_t local_port, - const union in_addr_union *remote, - uint16_t remote_port, - const union in_addr_union *previous_remote) { - - - _cleanup_(iptc_freep) struct xtc_handle *h = NULL; - struct ipt_entry *entry, *mask; - struct ipt_entry_target *t; - struct ipt_entry_match *m; - struct xt_addrtype_info_v1 *at; - struct nf_nat_ipv4_multi_range_compat *mr; - size_t sz, msz; - int r; - - assert(add || !previous_remote); - - if (af != AF_INET) - return -EOPNOTSUPP; - - if (protocol != IPPROTO_TCP && protocol != IPPROTO_UDP) - return -EOPNOTSUPP; - - if (local_port <= 0) - return -EINVAL; - - if (remote_port <= 0) - return -EINVAL; - - h = iptc_init("nat"); - if (!h) - return -errno; - - sz = XT_ALIGN(sizeof(struct ipt_entry)) + - XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) + - XT_ALIGN(sizeof(struct ipt_entry_target)) + - XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); - - if (protocol == IPPROTO_TCP) - msz = XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_tcp)); - else - msz = XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_udp)); - - sz += msz; - - /* Fill in basic part */ - entry = alloca0(sz); - entry->next_offset = sz; - entry->target_offset = - XT_ALIGN(sizeof(struct ipt_entry)) + - XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) + - msz; - r = entry_fill_basics(entry, protocol, in_interface, source, source_prefixlen, NULL, destination, destination_prefixlen); - if (r < 0) - return r; - - /* Fill in first match */ - m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry))); - m->u.match_size = msz; - if (protocol == IPPROTO_TCP) { - struct xt_tcp *tcp; - - strncpy(m->u.user.name, "tcp", sizeof(m->u.user.name)); - tcp = (struct xt_tcp*) m->data; - tcp->dpts[0] = tcp->dpts[1] = local_port; - tcp->spts[0] = 0; - tcp->spts[1] = 0xFFFF; - - } else { - struct xt_udp *udp; - - strncpy(m->u.user.name, "udp", sizeof(m->u.user.name)); - udp = (struct xt_udp*) m->data; - udp->dpts[0] = udp->dpts[1] = local_port; - udp->spts[0] = 0; - udp->spts[1] = 0xFFFF; - } - - /* Fill in second match */ - m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry)) + msz); - m->u.match_size = - XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_addrtype_info_v1)); - strncpy(m->u.user.name, "addrtype", sizeof(m->u.user.name)); - m->u.user.revision = 1; - at = (struct xt_addrtype_info_v1*) m->data; - at->dest = XT_ADDRTYPE_LOCAL; - - /* Fill in target part */ - t = ipt_get_target(entry); - t->u.target_size = - XT_ALIGN(sizeof(struct ipt_entry_target)) + - XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); - strncpy(t->u.user.name, "DNAT", sizeof(t->u.user.name)); - mr = (struct nf_nat_ipv4_multi_range_compat*) t->data; - mr->rangesize = 1; - mr->range[0].flags = NF_NAT_RANGE_PROTO_SPECIFIED|NF_NAT_RANGE_MAP_IPS; - mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr; - if (protocol == IPPROTO_TCP) - mr->range[0].min.tcp.port = mr->range[0].max.tcp.port = htons(remote_port); - else - mr->range[0].min.udp.port = mr->range[0].max.udp.port = htons(remote_port); - - mask = alloca0(sz); - memset(mask, 0xFF, sz); - - if (add) { - /* Add the PREROUTING rule, if it is missing so far */ - if (!iptc_check_entry("PREROUTING", entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -EINVAL; - - if (!iptc_insert_entry("PREROUTING", entry, 0, h)) - return -errno; - } - - /* If a previous remote is set, remove its entry */ - if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) { - mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr; - - if (!iptc_delete_entry("PREROUTING", entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - } - - mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr; - } - - /* Add the OUTPUT rule, if it is missing so far */ - if (!in_interface) { - - /* Don't apply onto loopback addresses */ - if (!destination) { - entry->ip.dst.s_addr = htobe32(0x7F000000); - entry->ip.dmsk.s_addr = htobe32(0xFF000000); - entry->ip.invflags = IPT_INV_DSTIP; - } - - if (!iptc_check_entry("OUTPUT", entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - - if (!iptc_insert_entry("OUTPUT", entry, 0, h)) - return -errno; - } - - /* If a previous remote is set, remove its entry */ - if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) { - mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr; - - if (!iptc_delete_entry("OUTPUT", entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - } - } - } - } else { - if (!iptc_delete_entry("PREROUTING", entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - } - - if (!in_interface) { - if (!destination) { - entry->ip.dst.s_addr = htobe32(0x7F000000); - entry->ip.dmsk.s_addr = htobe32(0xFF000000); - entry->ip.invflags = IPT_INV_DSTIP; - } - - if (!iptc_delete_entry("OUTPUT", entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - } - } - } - - if (!iptc_commit(h)) - return -errno; - - return 0; -} diff --git a/src/shared/firewall-util.h b/src/shared/firewall-util.h deleted file mode 100644 index c39b34cf8f..0000000000 --- a/src/shared/firewall-util.h +++ /dev/null @@ -1,83 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 - -#include "in-addr-util.h" - -#ifdef HAVE_LIBIPTC - -int fw_add_masquerade( - bool add, - int af, - int protocol, - const union in_addr_union *source, - unsigned source_prefixlen, - const char *out_interface, - const union in_addr_union *destination, - unsigned destination_prefixlen); - -int fw_add_local_dnat( - bool add, - int af, - int protocol, - const char *in_interface, - const union in_addr_union *source, - unsigned source_prefixlen, - const union in_addr_union *destination, - unsigned destination_prefixlen, - uint16_t local_port, - const union in_addr_union *remote, - uint16_t remote_port, - const union in_addr_union *previous_remote); - -#else - -static inline int fw_add_masquerade( - bool add, - int af, - int protocol, - const union in_addr_union *source, - unsigned source_prefixlen, - const char *out_interface, - const union in_addr_union *destination, - unsigned destination_prefixlen) { - return -EOPNOTSUPP; -} - -static inline int fw_add_local_dnat( - bool add, - int af, - int protocol, - const char *in_interface, - const union in_addr_union *source, - unsigned source_prefixlen, - const union in_addr_union *destination, - unsigned destination_prefixlen, - uint16_t local_port, - const union in_addr_union *remote, - uint16_t remote_port, - const union in_addr_union *previous_remote) { - return -EOPNOTSUPP; -} - -#endif diff --git a/src/shared/fstab-util.c b/src/shared/fstab-util.c deleted file mode 100644 index a4e0cd3267..0000000000 --- a/src/shared/fstab-util.c +++ /dev/null @@ -1,263 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 Zbigniew Jędrzejewski-Szmek - - 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 -#include -#include -#include - -#include "alloc-util.h" -#include "device-nodes.h" -#include "fstab-util.h" -#include "macro.h" -#include "mount-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -bool fstab_is_mount_point(const char *mount) { - _cleanup_endmntent_ FILE *f = NULL; - struct mntent *m; - - f = setmntent("/etc/fstab", "r"); - if (!f) - return false; - - while ((m = getmntent(f))) - if (path_equal(m->mnt_dir, mount)) - return true; - - return false; -} - -int fstab_filter_options(const char *opts, const char *names, - const char **namefound, char **value, char **filtered) { - const char *name, *n = NULL, *x; - _cleanup_strv_free_ char **stor = NULL; - _cleanup_free_ char *v = NULL, **strv = NULL; - - assert(names && *names); - - if (!opts) - goto answer; - - /* If !value and !filtered, this function is not allowed to fail. */ - - if (!filtered) { - const char *word, *state; - size_t l; - - FOREACH_WORD_SEPARATOR(word, l, opts, ",", state) - NULSTR_FOREACH(name, names) { - if (l < strlen(name)) - continue; - if (!strneq(word, name, strlen(name))) - continue; - - /* we know that the string is NUL - * terminated, so *x is valid */ - x = word + strlen(name); - if (IN_SET(*x, '\0', '=', ',')) { - n = name; - if (value) { - free(v); - if (IN_SET(*x, '\0', ',')) - v = NULL; - else { - assert(*x == '='); - x++; - v = strndup(x, l - strlen(name) - 1); - if (!v) - return -ENOMEM; - } - } - } - } - } else { - char **t, **s; - - stor = strv_split(opts, ","); - if (!stor) - return -ENOMEM; - strv = memdup(stor, sizeof(char*) * (strv_length(stor) + 1)); - if (!strv) - return -ENOMEM; - - for (s = t = strv; *s; s++) { - NULSTR_FOREACH(name, names) { - x = startswith(*s, name); - if (x && IN_SET(*x, '\0', '=')) - goto found; - } - - *t = *s; - t++; - continue; - found: - /* Keep the last occurence found */ - n = name; - if (value) { - free(v); - if (*x == '\0') - v = NULL; - else { - assert(*x == '='); - x++; - v = strdup(x); - if (!v) - return -ENOMEM; - } - } - } - *t = NULL; - } - -answer: - if (namefound) - *namefound = n; - if (filtered) { - char *f; - - f = strv_join(strv, ","); - if (!f) - return -ENOMEM; - - *filtered = f; - } - if (value) { - *value = v; - v = NULL; - } - - return !!n; -} - -int fstab_extract_values(const char *opts, const char *name, char ***values) { - _cleanup_strv_free_ char **optsv = NULL, **res = NULL; - char **s; - - assert(opts); - assert(name); - assert(values); - - optsv = strv_split(opts, ","); - if (!optsv) - return -ENOMEM; - - STRV_FOREACH(s, optsv) { - char *arg; - int r; - - arg = startswith(*s, name); - if (!arg || *arg != '=') - continue; - r = strv_extend(&res, arg + 1); - if (r < 0) - return r; - } - - *values = res; - res = NULL; - - return !!*values; -} - -int fstab_find_pri(const char *options, int *ret) { - _cleanup_free_ char *opt = NULL; - int r; - unsigned pri; - - assert(ret); - - r = fstab_filter_options(options, "pri\0", NULL, &opt, NULL); - if (r < 0) - return r; - if (r == 0 || !opt) - return 0; - - r = safe_atou(opt, &pri); - if (r < 0) - return r; - - if ((int) pri < 0) - return -ERANGE; - - *ret = (int) pri; - return 1; -} - -static char *unquote(const char *s, const char* quotes) { - size_t l; - assert(s); - - /* This is rather stupid, simply removes the heading and - * trailing quotes if there is one. Doesn't care about - * escaping or anything. - * - * DON'T USE THIS FOR NEW CODE ANYMORE!*/ - - l = strlen(s); - if (l < 2) - return strdup(s); - - if (strchr(quotes, s[0]) && s[l-1] == s[0]) - return strndup(s+1, l-2); - - return strdup(s); -} - -static char *tag_to_udev_node(const char *tagvalue, const char *by) { - _cleanup_free_ char *t = NULL, *u = NULL; - size_t enc_len; - - u = unquote(tagvalue, QUOTES); - if (!u) - return NULL; - - enc_len = strlen(u) * 4 + 1; - t = new(char, enc_len); - if (!t) - return NULL; - - if (encode_devnode_name(u, t, enc_len) < 0) - return NULL; - - return strjoin("/dev/disk/by-", by, "/", t, NULL); -} - -char *fstab_node_to_udev_node(const char *p) { - assert(p); - - if (startswith(p, "LABEL=")) - return tag_to_udev_node(p+6, "label"); - - if (startswith(p, "UUID=")) - return tag_to_udev_node(p+5, "uuid"); - - if (startswith(p, "PARTUUID=")) - return tag_to_udev_node(p+9, "partuuid"); - - if (startswith(p, "PARTLABEL=")) - return tag_to_udev_node(p+10, "partlabel"); - - return strdup(p); -} diff --git a/src/shared/fstab-util.h b/src/shared/fstab-util.h deleted file mode 100644 index 679f6902f7..0000000000 --- a/src/shared/fstab-util.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 Zbigniew Jędrzejewski-Szmek - - 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 - -#include "macro.h" - -bool fstab_is_mount_point(const char *mount); - -int fstab_filter_options(const char *opts, const char *names, const char **namefound, char **value, char **filtered); - -int fstab_extract_values(const char *opts, const char *name, char ***values); - -static inline bool fstab_test_option(const char *opts, const char *names) { - return !!fstab_filter_options(opts, names, NULL, NULL, NULL); -} - -int fstab_find_pri(const char *options, int *ret); - -static inline bool fstab_test_yes_no_option(const char *opts, const char *yes_no) { - int r; - const char *opt; - - /* If first name given is last, return 1. - * If second name given is last or neither is found, return 0. */ - - r = fstab_filter_options(opts, yes_no, &opt, NULL, NULL); - assert(r >= 0); - - return opt == yes_no; -} - -char *fstab_node_to_udev_node(const char *p); diff --git a/src/shared/gcrypt-util.c b/src/shared/gcrypt-util.c deleted file mode 100644 index 39b544b6f0..0000000000 --- a/src/shared/gcrypt-util.c +++ /dev/null @@ -1,71 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#ifdef HAVE_GCRYPT -#include - -#include "gcrypt-util.h" -#include "hexdecoct.h" - -void initialize_libgcrypt(bool secmem) { - const char *p; - if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) - return; - - p = gcry_check_version("1.4.5"); - assert(p); - - /* Turn off "secmem". Clients which wish to make use of this - * feature should initialize the library manually */ - if (!secmem) - gcry_control(GCRYCTL_DISABLE_SECMEM); - gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); -} - -int string_hashsum(const char *s, size_t len, int md_algorithm, char **out) { - gcry_md_hd_t md = NULL; - size_t hash_size; - void *hash; - char *enc; - - initialize_libgcrypt(false); - - hash_size = gcry_md_get_algo_dlen(md_algorithm); - assert(hash_size > 0); - - gcry_md_open(&md, md_algorithm, 0); - if (!md) - return -EIO; - - gcry_md_write(md, s, len); - - hash = gcry_md_read(md, 0); - if (!hash) - return -EIO; - - enc = hexmem(hash, hash_size); - if (!enc) - return -ENOMEM; - - *out = enc; - return 0; -} -#endif diff --git a/src/shared/gcrypt-util.h b/src/shared/gcrypt-util.h deleted file mode 100644 index cf33b3c59c..0000000000 --- a/src/shared/gcrypt-util.h +++ /dev/null @@ -1,39 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2016 Zbigniew Jędrzejewski-Szmek - - 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 -#include - -#ifdef HAVE_GCRYPT -#include - -void initialize_libgcrypt(bool secmem); -int string_hashsum(const char *s, size_t len, int md_algorithm, char **out); -#endif - -static inline int string_hashsum_sha224(const char *s, size_t len, char **out) { -#ifdef HAVE_GCRYPT - return string_hashsum(s, len, GCRY_MD_SHA224, out); -#else - return -EOPNOTSUPP; -#endif -} diff --git a/src/shared/generator.c b/src/shared/generator.c deleted file mode 100644 index 70afc6a285..0000000000 --- a/src/shared/generator.c +++ /dev/null @@ -1,207 +0,0 @@ -/*** - 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 - -#include "alloc-util.h" -#include "dropin.h" -#include "escape.h" -#include "fd-util.h" -#include "fileio.h" -#include "fstab-util.h" -#include "generator.h" -#include "log.h" -#include "macro.h" -#include "mkdir.h" -#include "path-util.h" -#include "special.h" -#include "string-util.h" -#include "time-util.h" -#include "unit-name.h" -#include "util.h" - -static int write_fsck_sysroot_service(const char *dir, const char *what) { - _cleanup_free_ char *device = NULL, *escaped = NULL; - _cleanup_fclose_ FILE *f = NULL; - const char *unit; - int r; - - escaped = cescape(what); - if (!escaped) - return log_oom(); - - unit = strjoina(dir, "/systemd-fsck-root.service"); - log_debug("Creating %s", unit); - - r = unit_name_from_path(what, ".device", &device); - if (r < 0) - return log_error_errno(r, "Failed to convert device \"%s\" to unit name: %m", what); - - f = fopen(unit, "wxe"); - if (!f) - return log_error_errno(errno, "Failed to create unit file %s: %m", unit); - - fprintf(f, - "# Automatically generated by %1$s\n\n" - "[Unit]\n" - "Documentation=man:systemd-fsck-root.service(8)\n" - "Description=File System Check on %2$s\n" - "DefaultDependencies=no\n" - "BindsTo=%3$s\n" - "After=initrd-root-device.target local-fs-pre.target\n" - "Before=shutdown.target\n" - "\n" - "[Service]\n" - "Type=oneshot\n" - "RemainAfterExit=yes\n" - "ExecStart=" SYSTEMD_FSCK_PATH " %4$s\n" - "TimeoutSec=0\n", - program_invocation_short_name, - what, - device, - escaped); - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write unit file %s: %m", unit); - - return 0; -} - -int generator_write_fsck_deps( - FILE *f, - const char *dir, - const char *what, - const char *where, - const char *fstype) { - - int r; - - assert(f); - assert(dir); - assert(what); - assert(where); - - if (!is_device_path(what)) { - log_warning("Checking was requested for \"%s\", but it is not a device.", what); - return 0; - } - - if (!isempty(fstype) && !streq(fstype, "auto")) { - r = fsck_exists(fstype); - if (r < 0) - log_warning_errno(r, "Checking was requested for %s, but couldn't detect if fsck.%s may be used, proceeding: %m", what, fstype); - else if (r == 0) { - /* treat missing check as essentially OK */ - log_debug("Checking was requested for %s, but fsck.%s does not exist.", what, fstype); - return 0; - } - } - - if (path_equal(where, "/")) { - const char *lnk; - - lnk = strjoina(dir, "/" SPECIAL_LOCAL_FS_TARGET ".wants/systemd-fsck-root.service"); - - mkdir_parents(lnk, 0755); - if (symlink(SYSTEM_DATA_UNIT_PATH "/systemd-fsck-root.service", lnk) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); - - } else { - _cleanup_free_ char *_fsck = NULL; - const char *fsck; - - if (in_initrd() && path_equal(where, "/sysroot")) { - r = write_fsck_sysroot_service(dir, what); - if (r < 0) - return r; - - fsck = "systemd-fsck-root.service"; - } else { - r = unit_name_from_path_instance("systemd-fsck", what, ".service", &_fsck); - if (r < 0) - return log_error_errno(r, "Failed to create fsck service name: %m"); - - fsck = _fsck; - } - - fprintf(f, - "Requires=%1$s\n" - "After=%1$s\n", - fsck); - } - - return 0; -} - -int generator_write_timeouts( - const char *dir, - const char *what, - const char *where, - const char *opts, - char **filtered) { - - /* Allow configuration how long we wait for a device that - * backs a mount point to show up. This is useful to support - * endless device timeouts for devices that show up only after - * user input, like crypto devices. */ - - _cleanup_free_ char *node = NULL, *unit = NULL, *timeout = NULL; - usec_t u; - int r; - - r = fstab_filter_options(opts, "comment=systemd.device-timeout\0" "x-systemd.device-timeout\0", - NULL, &timeout, filtered); - if (r <= 0) - return r; - - r = parse_sec(timeout, &u); - if (r < 0) { - log_warning("Failed to parse timeout for %s, ignoring: %s", where, timeout); - return 0; - } - - node = fstab_node_to_udev_node(what); - if (!node) - return log_oom(); - - r = unit_name_from_path(node, ".device", &unit); - if (r < 0) - return log_error_errno(r, "Failed to make unit name from path: %m"); - - return write_drop_in_format(dir, unit, 50, "device-timeout", - "# Automatically generated by %s\n\n" - "[Unit]\nJobTimeoutSec=%s", - program_invocation_short_name, timeout); -} - -int generator_write_initrd_root_device_deps(const char *dir, const char *what) { - _cleanup_free_ char *unit = NULL; - int r; - - r = unit_name_from_path(what, ".device", &unit); - if (r < 0) - return log_error_errno(r, "Failed to make unit name from path: %m"); - - return write_drop_in_format(dir, SPECIAL_INITRD_ROOT_DEVICE_TARGET, 50, "root-device", - "# Automatically generated by %s\n\n" - "[Unit]\nRequires=%s\nAfter=%s", - program_invocation_short_name, unit, unit); -} diff --git a/src/shared/generator.h b/src/shared/generator.h deleted file mode 100644 index a6017c1b76..0000000000 --- a/src/shared/generator.h +++ /dev/null @@ -1,40 +0,0 @@ -#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 - -int generator_write_fsck_deps( - FILE *f, - const char *dir, - const char *what, - const char *where, - const char *type); - -int generator_write_timeouts( - const char *dir, - const char *what, - const char *where, - const char *opts, - char **filtered); - -int generator_write_initrd_root_device_deps( - const char *dir, - const char *what); diff --git a/src/shared/gpt.h b/src/shared/gpt.h deleted file mode 100644 index 55b41bbcd8..0000000000 --- a/src/shared/gpt.h +++ /dev/null @@ -1,66 +0,0 @@ -#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 - -#include "sd-id128.h" - -/* We only support root disk discovery for x86, x86-64, Itanium and ARM for - * now, since EFI for anything else doesn't really exist, and we only - * care for root partitions on the same disk as the EFI ESP. */ - -#define GPT_ROOT_X86 SD_ID128_MAKE(44,47,95,40,f2,97,41,b2,9a,f7,d1,31,d5,f0,45,8a) -#define GPT_ROOT_X86_64 SD_ID128_MAKE(4f,68,bc,e3,e8,cd,4d,b1,96,e7,fb,ca,f9,84,b7,09) -#define GPT_ROOT_ARM SD_ID128_MAKE(69,da,d7,10,2c,e4,4e,3c,b1,6c,21,a1,d4,9a,be,d3) -#define GPT_ROOT_ARM_64 SD_ID128_MAKE(b9,21,b0,45,1d,f0,41,c3,af,44,4c,6f,28,0d,3f,ae) -#define GPT_ROOT_IA64 SD_ID128_MAKE(99,3d,8d,3d,f8,0e,42,25,85,5a,9d,af,8e,d7,ea,97) - -#define GPT_ESP SD_ID128_MAKE(c1,2a,73,28,f8,1f,11,d2,ba,4b,00,a0,c9,3e,c9,3b) -#define GPT_SWAP SD_ID128_MAKE(06,57,fd,6d,a4,ab,43,c4,84,e5,09,33,c8,4b,4f,4f) -#define GPT_HOME SD_ID128_MAKE(93,3a,c7,e1,2e,b4,4f,13,b8,44,0e,14,e2,ae,f9,15) -#define GPT_SRV SD_ID128_MAKE(3b,8f,84,25,20,e0,4f,3b,90,7f,1a,25,a7,6f,98,e8) - -#if defined(__x86_64__) -# define GPT_ROOT_NATIVE GPT_ROOT_X86_64 -# define GPT_ROOT_SECONDARY GPT_ROOT_X86 -#elif defined(__i386__) -# define GPT_ROOT_NATIVE GPT_ROOT_X86 -#endif - -#if defined(__ia64__) -# define GPT_ROOT_NATIVE GPT_ROOT_IA64 -#endif - -#if defined(__aarch64__) && (__BYTE_ORDER != __BIG_ENDIAN) -# define GPT_ROOT_NATIVE GPT_ROOT_ARM_64 -# define GPT_ROOT_SECONDARY GPT_ROOT_ARM -#elif defined(__arm__) && (__BYTE_ORDER != __BIG_ENDIAN) -# define GPT_ROOT_NATIVE GPT_ROOT_ARM -#endif - -/* Flags we recognize on the root, swap, home and srv partitions when - * doing auto-discovery. These happen to be identical to what - * Microsoft defines for its own Basic Data Partitions, but that's - * just because we saw no point in defining any other values here. */ -#define GPT_FLAG_READ_ONLY (1ULL << 60) -#define GPT_FLAG_NO_AUTO (1ULL << 63) - -#define GPT_LINUX_GENERIC SD_ID128_MAKE(0f,c6,3d,af,84,83,47,72,8e,79,3d,69,d8,47,7d,e4) diff --git a/src/shared/ima-util.c b/src/shared/ima-util.c deleted file mode 100644 index 789064d653..0000000000 --- a/src/shared/ima-util.c +++ /dev/null @@ -1,32 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 "ima-util.h" - -static int use_ima_cached = -1; - -bool use_ima(void) { - - if (use_ima_cached < 0) - use_ima_cached = access("/sys/kernel/security/ima/", F_OK) >= 0; - - return use_ima_cached; -} diff --git a/src/shared/ima-util.h b/src/shared/ima-util.h deleted file mode 100644 index 5be94761fd..0000000000 --- a/src/shared/ima-util.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 - -bool use_ima(void); diff --git a/src/shared/import-util.c b/src/shared/import-util.c deleted file mode 100644 index ab701ad8b2..0000000000 --- a/src/shared/import-util.c +++ /dev/null @@ -1,185 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 - -#include "alloc-util.h" -#include "btrfs-util.h" -#include "import-util.h" -#include "log.h" -#include "macro.h" -#include "path-util.h" -#include "string-table.h" -#include "string-util.h" -#include "util.h" - -int import_url_last_component(const char *url, char **ret) { - const char *e, *p; - char *s; - - e = strchrnul(url, '?'); - - while (e > url && e[-1] == '/') - e--; - - p = e; - while (p > url && p[-1] != '/') - p--; - - if (e <= p) - return -EINVAL; - - s = strndup(p, e - p); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - - -int import_url_change_last_component(const char *url, const char *suffix, char **ret) { - const char *e; - char *s; - - assert(url); - assert(ret); - - e = strchrnul(url, '?'); - - while (e > url && e[-1] == '/') - e--; - - while (e > url && e[-1] != '/') - e--; - - if (e <= url) - return -EINVAL; - - s = new(char, (e - url) + strlen(suffix) + 1); - if (!s) - return -ENOMEM; - - strcpy(mempcpy(s, url, e - url), suffix); - *ret = s; - return 0; -} - -static const char* const import_verify_table[_IMPORT_VERIFY_MAX] = { - [IMPORT_VERIFY_NO] = "no", - [IMPORT_VERIFY_CHECKSUM] = "checksum", - [IMPORT_VERIFY_SIGNATURE] = "signature", -}; - -DEFINE_STRING_TABLE_LOOKUP(import_verify, ImportVerify); - -int tar_strip_suffixes(const char *name, char **ret) { - const char *e; - char *s; - - e = endswith(name, ".tar"); - if (!e) - e = endswith(name, ".tar.xz"); - if (!e) - e = endswith(name, ".tar.gz"); - if (!e) - e = endswith(name, ".tar.bz2"); - if (!e) - e = endswith(name, ".tgz"); - if (!e) - e = strchr(name, 0); - - if (e <= name) - return -EINVAL; - - s = strndup(name, e - name); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - -int raw_strip_suffixes(const char *p, char **ret) { - - static const char suffixes[] = - ".xz\0" - ".gz\0" - ".bz2\0" - ".raw\0" - ".qcow2\0" - ".img\0" - ".bin\0"; - - _cleanup_free_ char *q = NULL; - - q = strdup(p); - if (!q) - return -ENOMEM; - - for (;;) { - const char *sfx; - bool changed = false; - - NULSTR_FOREACH(sfx, suffixes) { - char *e; - - e = endswith(q, sfx); - if (e) { - *e = 0; - changed = true; - } - } - - if (!changed) - break; - } - - *ret = q; - q = NULL; - - return 0; -} - -int import_assign_pool_quota_and_warn(const char *path) { - int r; - - r = btrfs_subvol_auto_qgroup("/var/lib/machines", 0, true); - if (r == -ENOTTY) { - log_debug_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines, as directory is not on btrfs or not a subvolume. Ignoring."); - return 0; - } - if (r < 0) - return log_error_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines: %m"); - if (r > 0) - log_info("Set up default quota hierarchy for /var/lib/machines."); - - r = btrfs_subvol_auto_qgroup(path, 0, true); - if (r == -ENOTTY) { - log_debug_errno(r, "Failed to set up quota hierarchy for %s, as directory is not on btrfs or not a subvolume. Ignoring.", path); - return 0; - } - if (r < 0) - return log_error_errno(r, "Failed to set up default quota hierarchy for %s: %m", path); - if (r > 0) - log_info("Set up default quota hierarchy for %s.", path); - - return 0; -} diff --git a/src/shared/import-util.h b/src/shared/import-util.h deleted file mode 100644 index 77b17d91f3..0000000000 --- a/src/shared/import-util.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 "macro.h" - -typedef enum ImportVerify { - IMPORT_VERIFY_NO, - IMPORT_VERIFY_CHECKSUM, - IMPORT_VERIFY_SIGNATURE, - _IMPORT_VERIFY_MAX, - _IMPORT_VERIFY_INVALID = -1, -} ImportVerify; - -int import_url_last_component(const char *url, char **ret); -int import_url_change_last_component(const char *url, const char *suffix, char **ret); - -const char* import_verify_to_string(ImportVerify v) _const_; -ImportVerify import_verify_from_string(const char *s) _pure_; - -int tar_strip_suffixes(const char *name, char **ret); -int raw_strip_suffixes(const char *name, char **ret); - -int import_assign_pool_quota_and_warn(const char *path); diff --git a/src/shared/initreq.h b/src/shared/initreq.h deleted file mode 100644 index 710037d84b..0000000000 --- a/src/shared/initreq.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * initreq.h Interface to talk to init through /dev/initctl. - * - * Copyright (C) 1995-2004 Miquel van Smoorenburg - * - * This library 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 of the License, or (at your option) any later version. - * - * Version: @(#)initreq.h 1.28 31-Mar-2004 MvS - * - */ -#ifndef _INITREQ_H -#define _INITREQ_H - -#include - -#if defined(__FreeBSD_kernel__) -# define INIT_FIFO "/etc/.initctl" -#else -# define INIT_FIFO "/dev/initctl" -#endif - -#define INIT_MAGIC 0x03091969 -#define INIT_CMD_START 0 -#define INIT_CMD_RUNLVL 1 -#define INIT_CMD_POWERFAIL 2 -#define INIT_CMD_POWERFAILNOW 3 -#define INIT_CMD_POWEROK 4 -#define INIT_CMD_BSD 5 -#define INIT_CMD_SETENV 6 -#define INIT_CMD_UNSETENV 7 - -#define INIT_CMD_CHANGECONS 12345 - -#ifdef MAXHOSTNAMELEN -# define INITRQ_HLEN MAXHOSTNAMELEN -#else -# define INITRQ_HLEN 64 -#endif - -/* - * This is what BSD 4.4 uses when talking to init. - * Linux doesn't use this right now. - */ -struct init_request_bsd { - char gen_id[8]; /* Beats me.. telnetd uses "fe" */ - char tty_id[16]; /* Tty name minus /dev/tty */ - char host[INITRQ_HLEN]; /* Hostname */ - char term_type[16]; /* Terminal type */ - int signal; /* Signal to send */ - int pid; /* Process to send to */ - char exec_name[128]; /* Program to execute */ - char reserved[128]; /* For future expansion. */ -}; - - -/* - * Because of legacy interfaces, "runlevel" and "sleeptime" - * aren't in a separate struct in the union. - * - * The weird sizes are because init expects the whole - * struct to be 384 bytes. - */ -struct init_request { - int magic; /* Magic number */ - int cmd; /* What kind of request */ - int runlevel; /* Runlevel to change to */ - int sleeptime; /* Time between TERM and KILL */ - union { - struct init_request_bsd bsd; - char data[368]; - } i; -}; - -#endif diff --git a/src/shared/install-printf.c b/src/shared/install-printf.c deleted file mode 100644 index 88143361da..0000000000 --- a/src/shared/install-printf.c +++ /dev/null @@ -1,133 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 Zbigniew Jędrzejewski-Szmek - - 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 -#include -#include - -#include "formats-util.h" -#include "install-printf.h" -#include "install.h" -#include "macro.h" -#include "specifier.h" -#include "unit-name.h" -#include "user-util.h" - -static int specifier_prefix_and_instance(char specifier, void *data, void *userdata, char **ret) { - UnitFileInstallInfo *i = userdata; - - assert(i); - - return unit_name_to_prefix_and_instance(i->name, ret); -} - -static int specifier_prefix(char specifier, void *data, void *userdata, char **ret) { - UnitFileInstallInfo *i = userdata; - - assert(i); - - return unit_name_to_prefix(i->name, ret); -} - -static int specifier_instance(char specifier, void *data, void *userdata, char **ret) { - UnitFileInstallInfo *i = userdata; - char *instance; - int r; - - assert(i); - - r = unit_name_to_instance(i->name, &instance); - if (r < 0) - return r; - - if (!instance) { - instance = strdup(""); - if (!instance) - return -ENOMEM; - } - - *ret = instance; - return 0; -} - -static int specifier_user_name(char specifier, void *data, void *userdata, char **ret) { - char *t; - - /* If we are UID 0 (root), this will not result in NSS, - * otherwise it might. This is good, as we want to be able to - * run this in PID 1, where our user ID is 0, but where NSS - * lookups are not allowed. */ - - t = getusername_malloc(); - if (!t) - return -ENOMEM; - - *ret = t; - return 0; -} - -static int specifier_user_id(char specifier, void *data, void *userdata, char **ret) { - - if (asprintf(ret, UID_FMT, getuid()) < 0) - return -ENOMEM; - - return 0; -} - -int install_full_printf(UnitFileInstallInfo *i, const char *format, char **ret) { - - /* This is similar to unit_full_printf() but does not support - * anything path-related. - * - * %n: the full id of the unit (foo@bar.waldo) - * %N: the id of the unit without the suffix (foo@bar) - * %p: the prefix (foo) - * %i: the instance (bar) - - * %U the UID of the running user - * %u the username of running user - * %m the machine ID of the running system - * %H the host name of the running system - * %b the boot ID of the running system - * %v `uname -r` of the running system - */ - - const Specifier table[] = { - { 'n', specifier_string, i->name }, - { 'N', specifier_prefix_and_instance, NULL }, - { 'p', specifier_prefix, NULL }, - { 'i', specifier_instance, NULL }, - - { 'U', specifier_user_id, NULL }, - { 'u', specifier_user_name, NULL }, - - { 'm', specifier_machine_id, NULL }, - { 'H', specifier_host_name, NULL }, - { 'b', specifier_boot_id, NULL }, - { 'v', specifier_kernel_release, NULL }, - {} - }; - - assert(i); - assert(format); - assert(ret); - - return specifier_printf(format, table, i, ret); -} diff --git a/src/shared/install-printf.h b/src/shared/install-printf.h deleted file mode 100644 index 8a570fc265..0000000000 --- a/src/shared/install-printf.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 Zbigniew Jędrzejewski-Szmek - - 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 "install.h" - -int install_full_printf(UnitFileInstallInfo *i, const char *format, char **ret); diff --git a/src/shared/install.c b/src/shared/install.c deleted file mode 100644 index 64d66a45d3..0000000000 --- a/src/shared/install.c +++ /dev/null @@ -1,2953 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "conf-files.h" -#include "conf-parser.h" -#include "dirent-util.h" -#include "extract-word.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "hashmap.h" -#include "install-printf.h" -#include "install.h" -#include "locale-util.h" -#include "log.h" -#include "macro.h" -#include "mkdir.h" -#include "path-lookup.h" -#include "path-util.h" -#include "rm-rf.h" -#include "set.h" -#include "special.h" -#include "stat-util.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" - -#define UNIT_FILE_FOLLOW_SYMLINK_MAX 64 - -typedef enum SearchFlags { - SEARCH_LOAD = 1, - SEARCH_FOLLOW_CONFIG_SYMLINKS = 2, -} SearchFlags; - -typedef struct { - OrderedHashmap *will_process; - OrderedHashmap *have_processed; -} InstallContext; - -typedef enum { - PRESET_UNKNOWN, - PRESET_ENABLE, - PRESET_DISABLE, -} PresetAction; - -typedef struct { - char *pattern; - PresetAction action; -} PresetRule; - -typedef struct { - PresetRule *rules; - size_t n_rules; -} Presets; - -static inline void presets_freep(Presets *p) { - size_t i; - - if (!p) - return; - - for (i = 0; i < p->n_rules; i++) - free(p->rules[i].pattern); - - free(p->rules); - p->n_rules = 0; -} - -static int unit_file_lookup_state(UnitFileScope scope, const LookupPaths *paths, const char *name, UnitFileState *ret); - -bool unit_type_may_alias(UnitType type) { - return IN_SET(type, - UNIT_SERVICE, - UNIT_SOCKET, - UNIT_TARGET, - UNIT_DEVICE, - UNIT_TIMER, - UNIT_PATH); -} - -bool unit_type_may_template(UnitType type) { - return IN_SET(type, - UNIT_SERVICE, - UNIT_SOCKET, - UNIT_TARGET, - UNIT_TIMER, - UNIT_PATH); -} - -static const char *unit_file_type_table[_UNIT_FILE_TYPE_MAX] = { - [UNIT_FILE_TYPE_REGULAR] = "regular", - [UNIT_FILE_TYPE_SYMLINK] = "symlink", - [UNIT_FILE_TYPE_MASKED] = "masked", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(unit_file_type, UnitFileType); - -static int in_search_path(const LookupPaths *p, const char *path) { - _cleanup_free_ char *parent = NULL; - char **i; - - assert(path); - - parent = dirname_malloc(path); - if (!parent) - return -ENOMEM; - - STRV_FOREACH(i, p->search_path) - if (path_equal(parent, *i)) - return true; - - return false; -} - -static const char* skip_root(const LookupPaths *p, const char *path) { - char *e; - - assert(p); - assert(path); - - if (!p->root_dir) - return path; - - e = path_startswith(path, p->root_dir); - if (!e) - return NULL; - - /* Make sure the returned path starts with a slash */ - if (e[0] != '/') { - if (e == path || e[-1] != '/') - return NULL; - - e--; - } - - return e; -} - -static int path_is_generator(const LookupPaths *p, const char *path) { - _cleanup_free_ char *parent = NULL; - - assert(p); - assert(path); - - parent = dirname_malloc(path); - if (!parent) - return -ENOMEM; - - return path_equal_ptr(parent, p->generator) || - path_equal_ptr(parent, p->generator_early) || - path_equal_ptr(parent, p->generator_late); -} - -static int path_is_transient(const LookupPaths *p, const char *path) { - _cleanup_free_ char *parent = NULL; - - assert(p); - assert(path); - - parent = dirname_malloc(path); - if (!parent) - return -ENOMEM; - - return path_equal_ptr(parent, p->transient); -} - -static int path_is_control(const LookupPaths *p, const char *path) { - _cleanup_free_ char *parent = NULL; - - assert(p); - assert(path); - - parent = dirname_malloc(path); - if (!parent) - return -ENOMEM; - - return path_equal_ptr(parent, p->persistent_control) || - path_equal_ptr(parent, p->runtime_control); -} - -static int path_is_config(const LookupPaths *p, const char *path) { - _cleanup_free_ char *parent = NULL; - - assert(p); - assert(path); - - /* Note that we do *not* have generic checks for /etc or /run in place, since with them we couldn't discern - * configuration from transient or generated units */ - - parent = dirname_malloc(path); - if (!parent) - return -ENOMEM; - - return path_equal_ptr(parent, p->persistent_config) || - path_equal_ptr(parent, p->runtime_config); -} - -static int path_is_runtime(const LookupPaths *p, const char *path) { - _cleanup_free_ char *parent = NULL; - const char *rpath; - - assert(p); - assert(path); - - /* Everything in /run is considered runtime. On top of that we also add explicit checks for the various runtime - * directories, as safety net. */ - - rpath = skip_root(p, path); - if (rpath && path_startswith(rpath, "/run")) - return true; - - parent = dirname_malloc(path); - if (!parent) - return -ENOMEM; - - return path_equal_ptr(parent, p->runtime_config) || - path_equal_ptr(parent, p->generator) || - path_equal_ptr(parent, p->generator_early) || - path_equal_ptr(parent, p->generator_late) || - path_equal_ptr(parent, p->transient) || - path_equal_ptr(parent, p->runtime_control); -} - -static int path_is_vendor(const LookupPaths *p, const char *path) { - const char *rpath; - - assert(p); - assert(path); - - rpath = skip_root(p, path); - if (!rpath) - return 0; - - if (path_startswith(rpath, "/usr")) - return true; - -#ifdef HAVE_SPLIT_USR - if (path_startswith(rpath, "/lib")) - return true; -#endif - - return path_equal(rpath, SYSTEM_DATA_UNIT_PATH); -} - -int unit_file_changes_add( - UnitFileChange **changes, - unsigned *n_changes, - UnitFileChangeType type, - const char *path, - const char *source) { - - _cleanup_free_ char *p = NULL, *s = NULL; - UnitFileChange *c; - - assert(path); - assert(!changes == !n_changes); - - if (!changes) - return 0; - - c = realloc(*changes, (*n_changes + 1) * sizeof(UnitFileChange)); - if (!c) - return -ENOMEM; - *changes = c; - - p = strdup(path); - if (source) - s = strdup(source); - - if (!p || (source && !s)) - return -ENOMEM; - - path_kill_slashes(p); - if (s) - path_kill_slashes(s); - - c[*n_changes] = (UnitFileChange) { type, p, s }; - p = s = NULL; - (*n_changes) ++; - return 0; -} - -void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes) { - unsigned i; - - assert(changes || n_changes == 0); - - for (i = 0; i < n_changes; i++) { - free(changes[i].path); - free(changes[i].source); - } - - free(changes); -} - -void unit_file_dump_changes(int r, const char *verb, const UnitFileChange *changes, unsigned n_changes, bool quiet) { - unsigned i; - bool logged = false; - - assert(changes || n_changes == 0); - /* If verb is not specified, errors are not allowed! */ - assert(verb || r >= 0); - - for (i = 0; i < n_changes; i++) { - assert(verb || changes[i].type >= 0); - - switch(changes[i].type) { - case UNIT_FILE_SYMLINK: - if (!quiet) - log_info("Created symlink %s %s %s.", - changes[i].path, - special_glyph(ARROW), - changes[i].source); - break; - case UNIT_FILE_UNLINK: - if (!quiet) - log_info("Removed %s.", changes[i].path); - break; - case UNIT_FILE_IS_MASKED: - if (!quiet) - log_info("Unit %s is masked, ignoring.", changes[i].path); - break; - case UNIT_FILE_IS_DANGLING: - if (!quiet) - log_info("Unit %s is an alias to a unit that is not present, ignoring.", - changes[i].path); - break; - case -EEXIST: - if (changes[i].source) - log_error_errno(changes[i].type, - "Failed to %s unit, file %s already exists and is a symlink to %s.", - verb, changes[i].path, changes[i].source); - else - log_error_errno(changes[i].type, - "Failed to %s unit, file %s already exists.", - verb, changes[i].path); - logged = true; - break; - case -ERFKILL: - log_error_errno(changes[i].type, "Failed to %s unit, unit %s is masked.", - verb, changes[i].path); - logged = true; - break; - case -EADDRNOTAVAIL: - log_error_errno(changes[i].type, "Failed to %s unit, unit %s is transient or generated.", - verb, changes[i].path); - logged = true; - break; - case -ELOOP: - log_error_errno(changes[i].type, "Failed to %s unit, refusing to operate on linked unit file %s", - verb, changes[i].path); - logged = true; - break; - default: - assert(changes[i].type < 0); - log_error_errno(changes[i].type, "Failed to %s unit, file %s: %m.", - verb, changes[i].path); - logged = true; - } - } - - if (r < 0 && !logged) - log_error_errno(r, "Failed to %s: %m.", verb); -} - -static int create_symlink( - const char *old_path, - const char *new_path, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_free_ char *dest = NULL; - int r; - - assert(old_path); - assert(new_path); - - /* Actually create a symlink, and remember that we did. Is - * smart enough to check if there's already a valid symlink in - * place. - * - * Returns 1 if a symlink was created or already exists and points to - * the right place, or negative on error. - */ - - mkdir_parents_label(new_path, 0755); - - if (symlink(old_path, new_path) >= 0) { - unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path); - return 1; - } - - if (errno != EEXIST) { - unit_file_changes_add(changes, n_changes, -errno, new_path, NULL); - return -errno; - } - - r = readlink_malloc(new_path, &dest); - if (r < 0) { - /* translate EINVAL (non-symlink exists) to EEXIST */ - if (r == -EINVAL) - r = -EEXIST; - - unit_file_changes_add(changes, n_changes, r, new_path, NULL); - return r; - } - - if (path_equal(dest, old_path)) - return 1; - - if (!force) { - unit_file_changes_add(changes, n_changes, -EEXIST, new_path, dest); - return -EEXIST; - } - - r = symlink_atomic(old_path, new_path); - if (r < 0) { - unit_file_changes_add(changes, n_changes, r, new_path, NULL); - return r; - } - - unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, new_path, NULL); - unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path); - - return 1; -} - -static int mark_symlink_for_removal( - Set **remove_symlinks_to, - const char *p) { - - char *n; - int r; - - assert(p); - - r = set_ensure_allocated(remove_symlinks_to, &string_hash_ops); - if (r < 0) - return r; - - n = strdup(p); - if (!n) - return -ENOMEM; - - path_kill_slashes(n); - - r = set_consume(*remove_symlinks_to, n); - if (r == -EEXIST) - return 0; - if (r < 0) - return r; - - return 1; -} - -static int remove_marked_symlinks_fd( - Set *remove_symlinks_to, - int fd, - const char *path, - const char *config_path, - const LookupPaths *lp, - bool *restart, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - int r = 0; - - assert(remove_symlinks_to); - assert(fd >= 0); - assert(path); - assert(config_path); - assert(lp); - assert(restart); - - d = fdopendir(fd); - if (!d) { - safe_close(fd); - return -errno; - } - - rewinddir(d); - - FOREACH_DIRENT(de, d, return -errno) { - - dirent_ensure_type(d, de); - - if (de->d_type == DT_DIR) { - _cleanup_free_ char *p = NULL; - int nfd, q; - - nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); - if (nfd < 0) { - if (errno == ENOENT) - continue; - - if (r == 0) - r = -errno; - continue; - } - - p = path_make_absolute(de->d_name, path); - if (!p) { - safe_close(nfd); - return -ENOMEM; - } - - /* This will close nfd, regardless whether it succeeds or not */ - q = remove_marked_symlinks_fd(remove_symlinks_to, nfd, p, config_path, lp, restart, changes, n_changes); - if (q < 0 && r == 0) - r = q; - - } else if (de->d_type == DT_LNK) { - _cleanup_free_ char *p = NULL, *dest = NULL; - const char *rp; - bool found; - int q; - - if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) - continue; - - p = path_make_absolute(de->d_name, path); - if (!p) - return -ENOMEM; - path_kill_slashes(p); - - q = readlink_malloc(p, &dest); - if (q == -ENOENT) - continue; - if (q < 0) { - if (r == 0) - r = q; - continue; - } - - /* We remove all links pointing to a file or path that is marked, as well as all files sharing - * the same name as a file that is marked. */ - - found = set_contains(remove_symlinks_to, dest) || - set_contains(remove_symlinks_to, basename(dest)) || - set_contains(remove_symlinks_to, de->d_name); - - if (!found) - continue; - - if (unlinkat(fd, de->d_name, 0) < 0 && errno != ENOENT) { - if (r == 0) - r = -errno; - unit_file_changes_add(changes, n_changes, -errno, p, NULL); - continue; - } - - (void) rmdir_parents(p, config_path); - - unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, p, NULL); - - /* Now, remember the full path (but with the root prefix removed) of - * the symlink we just removed, and remove any symlinks to it, too. */ - - rp = skip_root(lp, p); - q = mark_symlink_for_removal(&remove_symlinks_to, rp ?: p); - if (q < 0) - return q; - if (q > 0) - *restart = true; - } - } - - return r; -} - -static int remove_marked_symlinks( - Set *remove_symlinks_to, - const char *config_path, - const LookupPaths *lp, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_close_ int fd = -1; - bool restart; - int r = 0; - - assert(config_path); - assert(lp); - - if (set_size(remove_symlinks_to) <= 0) - return 0; - - fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); - if (fd < 0) - return -errno; - - do { - int q, cfd; - restart = false; - - cfd = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (cfd < 0) - return -errno; - - /* This takes possession of cfd and closes it */ - q = remove_marked_symlinks_fd(remove_symlinks_to, cfd, config_path, config_path, lp, &restart, changes, n_changes); - if (r == 0) - r = q; - } while (restart); - - return r; -} - -static int find_symlinks_fd( - const char *root_dir, - const char *name, - int fd, - const char *path, - const char *config_path, - const LookupPaths *lp, - bool *same_name_link) { - - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - int r = 0; - - assert(name); - assert(fd >= 0); - assert(path); - assert(config_path); - assert(lp); - assert(same_name_link); - - d = fdopendir(fd); - if (!d) { - safe_close(fd); - return -errno; - } - - FOREACH_DIRENT(de, d, return -errno) { - - dirent_ensure_type(d, de); - - if (de->d_type == DT_DIR) { - _cleanup_free_ char *p = NULL; - int nfd, q; - - nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); - if (nfd < 0) { - if (errno == ENOENT) - continue; - - if (r == 0) - r = -errno; - continue; - } - - p = path_make_absolute(de->d_name, path); - if (!p) { - safe_close(nfd); - return -ENOMEM; - } - - /* This will close nfd, regardless whether it succeeds or not */ - q = find_symlinks_fd(root_dir, name, nfd, p, config_path, lp, same_name_link); - if (q > 0) - return 1; - if (r == 0) - r = q; - - } else if (de->d_type == DT_LNK) { - _cleanup_free_ char *p = NULL, *dest = NULL; - bool found_path, found_dest, b = false; - int q; - - /* Acquire symlink name */ - p = path_make_absolute(de->d_name, path); - if (!p) - return -ENOMEM; - - /* Acquire symlink destination */ - q = readlink_malloc(p, &dest); - if (q == -ENOENT) - continue; - if (q < 0) { - if (r == 0) - r = q; - continue; - } - - /* Make absolute */ - if (!path_is_absolute(dest)) { - char *x; - - x = prefix_root(root_dir, dest); - if (!x) - return -ENOMEM; - - free(dest); - dest = x; - } - - /* Check if the symlink itself matches what we - * are looking for */ - if (path_is_absolute(name)) - found_path = path_equal(p, name); - else - found_path = streq(de->d_name, name); - - /* Check if what the symlink points to - * matches what we are looking for */ - if (path_is_absolute(name)) - found_dest = path_equal(dest, name); - else - found_dest = streq(basename(dest), name); - - if (found_path && found_dest) { - _cleanup_free_ char *t = NULL; - - /* Filter out same name links in the main - * config path */ - t = path_make_absolute(name, config_path); - if (!t) - return -ENOMEM; - - b = path_equal(t, p); - } - - if (b) - *same_name_link = true; - else if (found_path || found_dest) - return 1; - } - } - - return r; -} - -static int find_symlinks( - const char *root_dir, - const char *name, - const char *config_path, - const LookupPaths *lp, - bool *same_name_link) { - - int fd; - - assert(name); - assert(config_path); - assert(same_name_link); - - fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); - if (fd < 0) { - if (errno == ENOENT) - return 0; - return -errno; - } - - /* This takes possession of fd and closes it */ - return find_symlinks_fd(root_dir, name, fd, config_path, config_path, lp, same_name_link); -} - -static int find_symlinks_in_scope( - UnitFileScope scope, - const LookupPaths *paths, - const char *name, - UnitFileState *state) { - - bool same_name_link_runtime = false, same_name_link = false; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(paths); - assert(name); - - /* First look in the persistent config path */ - r = find_symlinks(paths->root_dir, name, paths->persistent_config, paths, &same_name_link); - if (r < 0) - return r; - if (r > 0) { - *state = UNIT_FILE_ENABLED; - return r; - } - - /* Then look in runtime config path */ - r = find_symlinks(paths->root_dir, name, paths->runtime_config, paths, &same_name_link_runtime); - if (r < 0) - return r; - if (r > 0) { - *state = UNIT_FILE_ENABLED_RUNTIME; - return r; - } - - /* Hmm, we didn't find it, but maybe we found the same name - * link? */ - if (same_name_link) { - *state = UNIT_FILE_LINKED; - return 1; - } - if (same_name_link_runtime) { - *state = UNIT_FILE_LINKED_RUNTIME; - return 1; - } - - return 0; -} - -static void install_info_free(UnitFileInstallInfo *i) { - - if (!i) - return; - - free(i->name); - free(i->path); - strv_free(i->aliases); - strv_free(i->wanted_by); - strv_free(i->required_by); - strv_free(i->also); - free(i->default_instance); - free(i->symlink_target); - free(i); -} - -static OrderedHashmap* install_info_hashmap_free(OrderedHashmap *m) { - UnitFileInstallInfo *i; - - if (!m) - return NULL; - - while ((i = ordered_hashmap_steal_first(m))) - install_info_free(i); - - return ordered_hashmap_free(m); -} - -static void install_context_done(InstallContext *c) { - assert(c); - - c->will_process = install_info_hashmap_free(c->will_process); - c->have_processed = install_info_hashmap_free(c->have_processed); -} - -static UnitFileInstallInfo *install_info_find(InstallContext *c, const char *name) { - UnitFileInstallInfo *i; - - i = ordered_hashmap_get(c->have_processed, name); - if (i) - return i; - - return ordered_hashmap_get(c->will_process, name); -} - -static int install_info_may_process( - UnitFileInstallInfo *i, - const LookupPaths *paths, - UnitFileChange **changes, - unsigned *n_changes) { - assert(i); - assert(paths); - - /* Checks whether the loaded unit file is one we should process, or is masked, transient or generated and thus - * not subject to enable/disable operations. */ - - if (i->type == UNIT_FILE_TYPE_MASKED) { - unit_file_changes_add(changes, n_changes, -ERFKILL, i->path, NULL); - return -ERFKILL; - } - if (path_is_generator(paths, i->path) || - path_is_transient(paths, i->path)) { - unit_file_changes_add(changes, n_changes, -EADDRNOTAVAIL, i->path, NULL); - return -EADDRNOTAVAIL; - } - - return 0; -} - -static int install_info_add( - InstallContext *c, - const char *name, - const char *path, - UnitFileInstallInfo **ret) { - - UnitFileInstallInfo *i = NULL; - int r; - - assert(c); - assert(name || path); - - if (!name) - name = basename(path); - - if (!unit_name_is_valid(name, UNIT_NAME_ANY)) - return -EINVAL; - - i = install_info_find(c, name); - if (i) { - if (ret) - *ret = i; - return 0; - } - - r = ordered_hashmap_ensure_allocated(&c->will_process, &string_hash_ops); - if (r < 0) - return r; - - i = new0(UnitFileInstallInfo, 1); - if (!i) - return -ENOMEM; - i->type = _UNIT_FILE_TYPE_INVALID; - - i->name = strdup(name); - if (!i->name) { - r = -ENOMEM; - goto fail; - } - - if (path) { - i->path = strdup(path); - if (!i->path) { - r = -ENOMEM; - goto fail; - } - } - - r = ordered_hashmap_put(c->will_process, i->name, i); - if (r < 0) - goto fail; - - if (ret) - *ret = i; - - return 0; - -fail: - install_info_free(i); - return r; -} - -static int config_parse_alias( - 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) { - - const char *name; - UnitType type; - - assert(filename); - assert(lvalue); - assert(rvalue); - - name = basename(filename); - type = unit_name_to_type(name); - if (!unit_type_may_alias(type)) - return log_syntax(unit, LOG_WARNING, filename, line, 0, - "Aliases are not allowed for %s units, ignoring.", - unit_type_to_string(type)); - - return config_parse_strv(unit, filename, line, section, section_line, - lvalue, ltype, rvalue, data, userdata); -} - -static int config_parse_also( - 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) { - - UnitFileInstallInfo *i = userdata; - InstallContext *c = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&rvalue, &word, NULL, 0); - if (r < 0) - return r; - if (r == 0) - break; - - r = install_info_add(c, word, NULL, NULL); - if (r < 0) - return r; - - r = strv_push(&i->also, word); - if (r < 0) - return r; - - word = NULL; - } - - return 0; -} - -static int config_parse_default_instance( - 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) { - - UnitFileInstallInfo *i = data; - const char *name; - char *printed; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - name = basename(filename); - if (unit_name_is_valid(name, UNIT_NAME_INSTANCE)) - /* When enabling an instance, we might be using a template unit file, - * but we should ignore DefaultInstance silently. */ - return 0; - if (!unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) - return log_syntax(unit, LOG_WARNING, filename, line, 0, - "DefaultInstance only makes sense for template units, ignoring."); - - r = install_full_printf(i, rvalue, &printed); - if (r < 0) - return r; - - if (!unit_instance_is_valid(printed)) { - free(printed); - return -EINVAL; - } - - free(i->default_instance); - i->default_instance = printed; - - return 0; -} - -static int unit_file_load( - InstallContext *c, - UnitFileInstallInfo *info, - const char *path, - SearchFlags flags) { - - const ConfigTableItem items[] = { - { "Install", "Alias", config_parse_alias, 0, &info->aliases }, - { "Install", "WantedBy", config_parse_strv, 0, &info->wanted_by }, - { "Install", "RequiredBy", config_parse_strv, 0, &info->required_by }, - { "Install", "DefaultInstance", config_parse_default_instance, 0, info }, - { "Install", "Also", config_parse_also, 0, c }, - {} - }; - - const char *name; - UnitType type; - _cleanup_fclose_ FILE *f = NULL; - _cleanup_close_ int fd = -1; - struct stat st; - int r; - - assert(c); - assert(info); - assert(path); - - name = basename(path); - type = unit_name_to_type(name); - if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE|UNIT_NAME_INSTANCE) && - !unit_type_may_template(type)) - return log_error_errno(EINVAL, "Unit type %s cannot be templated.", unit_type_to_string(type)); - - if (!(flags & SEARCH_LOAD)) { - r = lstat(path, &st); - if (r < 0) - return -errno; - - if (null_or_empty(&st)) - info->type = UNIT_FILE_TYPE_MASKED; - else if (S_ISREG(st.st_mode)) - info->type = UNIT_FILE_TYPE_REGULAR; - else if (S_ISLNK(st.st_mode)) - return -ELOOP; - else if (S_ISDIR(st.st_mode)) - return -EISDIR; - else - return -ENOTTY; - - return 0; - } - - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; - if (fstat(fd, &st) < 0) - return -errno; - if (null_or_empty(&st)) { - info->type = UNIT_FILE_TYPE_MASKED; - return 0; - } - if (S_ISDIR(st.st_mode)) - return -EISDIR; - if (!S_ISREG(st.st_mode)) - return -ENOTTY; - - f = fdopen(fd, "re"); - if (!f) - return -errno; - fd = -1; - - r = config_parse(NULL, path, f, - NULL, - config_item_table_lookup, items, - true, true, false, info); - if (r < 0) - return r; - - info->type = UNIT_FILE_TYPE_REGULAR; - - return - (int) strv_length(info->aliases) + - (int) strv_length(info->wanted_by) + - (int) strv_length(info->required_by); -} - -static int unit_file_load_or_readlink( - InstallContext *c, - UnitFileInstallInfo *info, - const char *path, - const char *root_dir, - SearchFlags flags) { - - _cleanup_free_ char *target = NULL; - int r; - - r = unit_file_load(c, info, path, flags); - if (r != -ELOOP) - return r; - - /* This is a symlink, let's read it. */ - - r = readlink_malloc(path, &target); - if (r < 0) - return r; - - if (path_equal(target, "/dev/null")) - info->type = UNIT_FILE_TYPE_MASKED; - else { - const char *bn; - UnitType a, b; - - bn = basename(target); - - if (unit_name_is_valid(info->name, UNIT_NAME_PLAIN)) { - - if (!unit_name_is_valid(bn, UNIT_NAME_PLAIN)) - return -EINVAL; - - } else if (unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) { - - if (!unit_name_is_valid(bn, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) - return -EINVAL; - - } else if (unit_name_is_valid(info->name, UNIT_NAME_TEMPLATE)) { - - if (!unit_name_is_valid(bn, UNIT_NAME_TEMPLATE)) - return -EINVAL; - } else - return -EINVAL; - - /* Enforce that the symlink destination does not - * change the unit file type. */ - - a = unit_name_to_type(info->name); - b = unit_name_to_type(bn); - if (a < 0 || b < 0 || a != b) - return -EINVAL; - - if (path_is_absolute(target)) - /* This is an absolute path, prefix the root so that we always deal with fully qualified paths */ - info->symlink_target = prefix_root(root_dir, target); - else - /* This is a relative path, take it relative to the dir the symlink is located in. */ - info->symlink_target = file_in_same_dir(path, target); - if (!info->symlink_target) - return -ENOMEM; - - info->type = UNIT_FILE_TYPE_SYMLINK; - } - - return 0; -} - -static int unit_file_search( - InstallContext *c, - UnitFileInstallInfo *info, - const LookupPaths *paths, - SearchFlags flags) { - - _cleanup_free_ char *template = NULL; - char **p; - int r; - - assert(c); - assert(info); - assert(paths); - - /* Was this unit already loaded? */ - if (info->type != _UNIT_FILE_TYPE_INVALID) - return 0; - - if (info->path) - return unit_file_load_or_readlink(c, info, info->path, paths->root_dir, flags); - - assert(info->name); - - STRV_FOREACH(p, paths->search_path) { - _cleanup_free_ char *path = NULL; - - path = strjoin(*p, "/", info->name, NULL); - if (!path) - return -ENOMEM; - - r = unit_file_load_or_readlink(c, info, path, paths->root_dir, flags); - if (r >= 0) { - info->path = path; - path = NULL; - return r; - } else if (r != -ENOENT) - return r; - } - - if (unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) { - /* Unit file doesn't exist, however instance - * enablement was requested. We will check if it is - * possible to load template unit file. */ - - r = unit_name_template(info->name, &template); - if (r < 0) - return r; - - STRV_FOREACH(p, paths->search_path) { - _cleanup_free_ char *path = NULL; - - path = strjoin(*p, "/", template, NULL); - if (!path) - return -ENOMEM; - - r = unit_file_load_or_readlink(c, info, path, paths->root_dir, flags); - if (r >= 0) { - info->path = path; - path = NULL; - return r; - } else if (r != -ENOENT) - return r; - } - } - - log_debug("Cannot find unit %s%s%s.", info->name, template ? " or " : "", strempty(template)); - return -ENOENT; -} - -static int install_info_follow( - InstallContext *c, - UnitFileInstallInfo *i, - const char *root_dir, - SearchFlags flags) { - - assert(c); - assert(i); - - if (i->type != UNIT_FILE_TYPE_SYMLINK) - return -EINVAL; - if (!i->symlink_target) - return -EINVAL; - - /* If the basename doesn't match, the caller should add a - * complete new entry for this. */ - - if (!streq(basename(i->symlink_target), i->name)) - return -EXDEV; - - free(i->path); - i->path = i->symlink_target; - i->symlink_target = NULL; - i->type = _UNIT_FILE_TYPE_INVALID; - - return unit_file_load_or_readlink(c, i, i->path, root_dir, flags); -} - -/** - * Search for the unit file. If the unit name is a symlink, - * follow the symlink to the target, maybe more than once. - * Propagate the instance name if present. - */ -static int install_info_traverse( - UnitFileScope scope, - InstallContext *c, - const LookupPaths *paths, - UnitFileInstallInfo *start, - SearchFlags flags, - UnitFileInstallInfo **ret) { - - UnitFileInstallInfo *i; - unsigned k = 0; - int r; - - assert(paths); - assert(start); - assert(c); - - r = unit_file_search(c, start, paths, flags); - if (r < 0) - return r; - - i = start; - while (i->type == UNIT_FILE_TYPE_SYMLINK) { - /* Follow the symlink */ - - if (++k > UNIT_FILE_FOLLOW_SYMLINK_MAX) - return -ELOOP; - - if (!(flags & SEARCH_FOLLOW_CONFIG_SYMLINKS)) { - r = path_is_config(paths, i->path); - if (r < 0) - return r; - if (r > 0) - return -ELOOP; - } - - r = install_info_follow(c, i, paths->root_dir, flags); - if (r == -EXDEV) { - _cleanup_free_ char *buffer = NULL; - const char *bn; - - /* Target has a different name, create a new - * install info object for that, and continue - * with that. */ - - bn = basename(i->symlink_target); - - if (unit_name_is_valid(i->name, UNIT_NAME_INSTANCE) && - unit_name_is_valid(bn, UNIT_NAME_TEMPLATE)) { - - _cleanup_free_ char *instance = NULL; - - r = unit_name_to_instance(i->name, &instance); - if (r < 0) - return r; - - r = unit_name_replace_instance(bn, instance, &buffer); - if (r < 0) - return r; - - bn = buffer; - } - - r = install_info_add(c, bn, NULL, &i); - if (r < 0) - return r; - - /* Try again, with the new target we found. */ - r = unit_file_search(c, i, paths, flags); - if (r == -ENOENT) - /* Translate error code to highlight this specific case */ - return -ENOLINK; - } - - if (r < 0) - return r; - } - - if (ret) - *ret = i; - - return 0; -} - -static int install_info_add_auto( - InstallContext *c, - const LookupPaths *paths, - const char *name_or_path, - UnitFileInstallInfo **ret) { - - assert(c); - assert(name_or_path); - - if (path_is_absolute(name_or_path)) { - const char *pp; - - pp = prefix_roota(paths->root_dir, name_or_path); - - return install_info_add(c, NULL, pp, ret); - } else - return install_info_add(c, name_or_path, NULL, ret); -} - -static int install_info_discover( - UnitFileScope scope, - InstallContext *c, - const LookupPaths *paths, - const char *name, - SearchFlags flags, - UnitFileInstallInfo **ret) { - - UnitFileInstallInfo *i; - int r; - - assert(c); - assert(paths); - assert(name); - - r = install_info_add_auto(c, paths, name, &i); - if (r < 0) - return r; - - return install_info_traverse(scope, c, paths, i, flags, ret); -} - -static int install_info_symlink_alias( - UnitFileInstallInfo *i, - const LookupPaths *paths, - const char *config_path, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - char **s; - int r = 0, q; - - assert(i); - assert(paths); - assert(config_path); - - STRV_FOREACH(s, i->aliases) { - _cleanup_free_ char *alias_path = NULL, *dst = NULL; - const char *rp; - - q = install_full_printf(i, *s, &dst); - if (q < 0) - return q; - - alias_path = path_make_absolute(dst, config_path); - if (!alias_path) - return -ENOMEM; - - rp = skip_root(paths, i->path); - - q = create_symlink(rp ?: i->path, alias_path, force, changes, n_changes); - if (r == 0) - r = q; - } - - return r; -} - -static int install_info_symlink_wants( - UnitFileInstallInfo *i, - const LookupPaths *paths, - const char *config_path, - char **list, - const char *suffix, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_free_ char *buf = NULL; - const char *n; - char **s; - int r = 0, q; - - assert(i); - assert(paths); - assert(config_path); - - if (unit_name_is_valid(i->name, UNIT_NAME_TEMPLATE)) { - - /* Don't install any symlink if there's no default - * instance configured */ - - if (!i->default_instance) - return 0; - - r = unit_name_replace_instance(i->name, i->default_instance, &buf); - if (r < 0) - return r; - - n = buf; - } else - n = i->name; - - STRV_FOREACH(s, list) { - _cleanup_free_ char *path = NULL, *dst = NULL; - const char *rp; - - q = install_full_printf(i, *s, &dst); - if (q < 0) - return q; - - if (!unit_name_is_valid(dst, UNIT_NAME_ANY)) { - r = -EINVAL; - continue; - } - - path = strjoin(config_path, "/", dst, suffix, n, NULL); - if (!path) - return -ENOMEM; - - rp = skip_root(paths, i->path); - - q = create_symlink(rp ?: i->path, path, true, changes, n_changes); - if (r == 0) - r = q; - } - - return r; -} - -static int install_info_symlink_link( - UnitFileInstallInfo *i, - const LookupPaths *paths, - const char *config_path, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_free_ char *path = NULL; - const char *rp; - int r; - - assert(i); - assert(paths); - assert(config_path); - assert(i->path); - - r = in_search_path(paths, i->path); - if (r < 0) - return r; - if (r > 0) - return 0; - - path = strjoin(config_path, "/", i->name, NULL); - if (!path) - return -ENOMEM; - - rp = skip_root(paths, i->path); - - return create_symlink(rp ?: i->path, path, force, changes, n_changes); -} - -static int install_info_apply( - UnitFileInstallInfo *i, - const LookupPaths *paths, - const char *config_path, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - int r, q; - - assert(i); - assert(paths); - assert(config_path); - - if (i->type != UNIT_FILE_TYPE_REGULAR) - return 0; - - r = install_info_symlink_alias(i, paths, config_path, force, changes, n_changes); - - q = install_info_symlink_wants(i, paths, config_path, i->wanted_by, ".wants/", changes, n_changes); - if (r == 0) - r = q; - - q = install_info_symlink_wants(i, paths, config_path, i->required_by, ".requires/", changes, n_changes); - if (r == 0) - r = q; - - q = install_info_symlink_link(i, paths, config_path, force, changes, n_changes); - /* Do not count links to the unit file towards the "carries_install_info" count */ - if (r == 0 && q < 0) - r = q; - - return r; -} - -static int install_context_apply( - UnitFileScope scope, - InstallContext *c, - const LookupPaths *paths, - const char *config_path, - bool force, - SearchFlags flags, - UnitFileChange **changes, - unsigned *n_changes) { - - UnitFileInstallInfo *i; - int r; - - assert(c); - assert(paths); - assert(config_path); - - if (ordered_hashmap_isempty(c->will_process)) - return 0; - - r = ordered_hashmap_ensure_allocated(&c->have_processed, &string_hash_ops); - if (r < 0) - return r; - - r = 0; - while ((i = ordered_hashmap_first(c->will_process))) { - int q; - - q = ordered_hashmap_move_one(c->have_processed, c->will_process, i->name); - if (q < 0) - return q; - - r = install_info_traverse(scope, c, paths, i, flags, NULL); - if (r < 0) - return r; - - if (i->type != UNIT_FILE_TYPE_REGULAR) - continue; - - q = install_info_apply(i, paths, config_path, force, changes, n_changes); - if (r >= 0) { - if (q < 0) - r = q; - else - r += q; - } - } - - return r; -} - -static int install_context_mark_for_removal( - UnitFileScope scope, - InstallContext *c, - const LookupPaths *paths, - Set **remove_symlinks_to, - const char *config_path) { - - UnitFileInstallInfo *i; - int r; - - assert(c); - assert(paths); - assert(config_path); - - /* Marks all items for removal */ - - if (ordered_hashmap_isempty(c->will_process)) - return 0; - - r = ordered_hashmap_ensure_allocated(&c->have_processed, &string_hash_ops); - if (r < 0) - return r; - - while ((i = ordered_hashmap_first(c->will_process))) { - - r = ordered_hashmap_move_one(c->have_processed, c->will_process, i->name); - if (r < 0) - return r; - - r = install_info_traverse(scope, c, paths, i, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, NULL); - if (r == -ENOLINK) - return 0; - else if (r < 0) - return r; - - if (i->type != UNIT_FILE_TYPE_REGULAR) { - log_debug("Unit %s has type %s, ignoring.", - i->name, - unit_file_type_to_string(i->type) ?: "invalid"); - continue; - } - - r = mark_symlink_for_removal(remove_symlinks_to, i->name); - if (r < 0) - return r; - } - - return 0; -} - -int unit_file_mask( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - const char *config_path; - char **i; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - - r = lookup_paths_init(&paths, scope, 0, root_dir); - if (r < 0) - return r; - - config_path = runtime ? paths.runtime_config : paths.persistent_config; - - STRV_FOREACH(i, files) { - _cleanup_free_ char *path = NULL; - int q; - - if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) { - if (r == 0) - r = -EINVAL; - continue; - } - - path = path_make_absolute(*i, config_path); - if (!path) - return -ENOMEM; - - q = create_symlink("/dev/null", path, force, changes, n_changes); - if (q < 0 && r >= 0) - r = q; - } - - return r; -} - -int unit_file_unmask( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; - _cleanup_free_ char **todo = NULL; - size_t n_todo = 0, n_allocated = 0; - const char *config_path; - char **i; - int r, q; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - - r = lookup_paths_init(&paths, scope, 0, root_dir); - if (r < 0) - return r; - - config_path = runtime ? paths.runtime_config : paths.persistent_config; - - STRV_FOREACH(i, files) { - _cleanup_free_ char *path = NULL; - - if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) - return -EINVAL; - - path = path_make_absolute(*i, config_path); - if (!path) - return -ENOMEM; - - r = null_or_empty_path(path); - if (r == -ENOENT) - continue; - if (r < 0) - return r; - if (r == 0) - continue; - - if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2)) - return -ENOMEM; - - todo[n_todo++] = *i; - } - - strv_uniq(todo); - - r = 0; - STRV_FOREACH(i, todo) { - _cleanup_free_ char *path = NULL; - const char *rp; - - path = path_make_absolute(*i, config_path); - if (!path) - return -ENOMEM; - - if (unlink(path) < 0) { - if (errno != ENOENT) { - if (r >= 0) - r = -errno; - unit_file_changes_add(changes, n_changes, -errno, path, NULL); - } - - continue; - } - - unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, path, NULL); - - rp = skip_root(&paths, path); - q = mark_symlink_for_removal(&remove_symlinks_to, rp ?: path); - if (q < 0) - return q; - } - - q = remove_marked_symlinks(remove_symlinks_to, config_path, &paths, changes, n_changes); - if (r >= 0) - r = q; - - return r; -} - -int unit_file_link( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_free_ char **todo = NULL; - size_t n_todo = 0, n_allocated = 0; - const char *config_path; - char **i; - int r, q; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - - r = lookup_paths_init(&paths, scope, 0, root_dir); - if (r < 0) - return r; - - config_path = runtime ? paths.runtime_config : paths.persistent_config; - - STRV_FOREACH(i, files) { - _cleanup_free_ char *full = NULL; - struct stat st; - char *fn; - - if (!path_is_absolute(*i)) - return -EINVAL; - - fn = basename(*i); - if (!unit_name_is_valid(fn, UNIT_NAME_ANY)) - return -EINVAL; - - full = prefix_root(paths.root_dir, *i); - if (!full) - return -ENOMEM; - - if (lstat(full, &st) < 0) - return -errno; - if (S_ISLNK(st.st_mode)) - return -ELOOP; - if (S_ISDIR(st.st_mode)) - return -EISDIR; - if (!S_ISREG(st.st_mode)) - return -ENOTTY; - - q = in_search_path(&paths, *i); - if (q < 0) - return q; - if (q > 0) - continue; - - if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2)) - return -ENOMEM; - - todo[n_todo++] = *i; - } - - strv_uniq(todo); - - r = 0; - STRV_FOREACH(i, todo) { - _cleanup_free_ char *new_path = NULL; - const char *old_path; - - old_path = skip_root(&paths, *i); - new_path = path_make_absolute(basename(*i), config_path); - if (!new_path) - return -ENOMEM; - - q = create_symlink(old_path ?: *i, new_path, force, changes, n_changes); - if (q < 0 && r >= 0) - r = q; - } - - return r; -} - -static int path_shall_revert(const LookupPaths *paths, const char *path) { - int r; - - assert(paths); - assert(path); - - /* Checks whether the path is one where the drop-in directories shall be removed. */ - - r = path_is_config(paths, path); - if (r != 0) - return r; - - r = path_is_control(paths, path); - if (r != 0) - return r; - - return path_is_transient(paths, path); -} - -int unit_file_revert( - UnitFileScope scope, - const char *root_dir, - char **files, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; - /* _cleanup_(install_context_done) InstallContext c = {}; */ - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_strv_free_ char **todo = NULL; - size_t n_todo = 0, n_allocated = 0; - char **i; - int r, q; - - /* Puts a unit file back into vendor state. This means: - * - * a) we remove all drop-in snippets added by the user ("config"), add to transient units ("transient"), and - * added via "systemctl set-property" ("control"), but not if the drop-in is generated ("generated"). - * - * c) if there's a vendor unit file (i.e. one in /usr) we remove any configured overriding unit files (i.e. in - * "config", but not in "transient" or "control" or even "generated"). - * - * We remove all that in both the runtime and the persistent directories, if that applies. - */ - - r = lookup_paths_init(&paths, scope, 0, root_dir); - if (r < 0) - return r; - - STRV_FOREACH(i, files) { - bool has_vendor = false; - char **p; - - if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) - return -EINVAL; - - STRV_FOREACH(p, paths.search_path) { - _cleanup_free_ char *path = NULL, *dropin = NULL; - struct stat st; - - path = path_make_absolute(*i, *p); - if (!path) - return -ENOMEM; - - r = lstat(path, &st); - if (r < 0) { - if (errno != ENOENT) - return -errno; - } else if (S_ISREG(st.st_mode)) { - /* Check if there's a vendor version */ - r = path_is_vendor(&paths, path); - if (r < 0) - return r; - if (r > 0) - has_vendor = true; - } - - dropin = strappend(path, ".d"); - if (!dropin) - return -ENOMEM; - - r = lstat(dropin, &st); - if (r < 0) { - if (errno != ENOENT) - return -errno; - } else if (S_ISDIR(st.st_mode)) { - /* Remove the drop-ins */ - r = path_shall_revert(&paths, dropin); - if (r < 0) - return r; - if (r > 0) { - if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2)) - return -ENOMEM; - - todo[n_todo++] = dropin; - dropin = NULL; - } - } - } - - if (!has_vendor) - continue; - - /* OK, there's a vendor version, hence drop all configuration versions */ - STRV_FOREACH(p, paths.search_path) { - _cleanup_free_ char *path = NULL; - struct stat st; - - path = path_make_absolute(*i, *p); - if (!path) - return -ENOMEM; - - r = lstat(path, &st); - if (r < 0) { - if (errno != ENOENT) - return -errno; - } else if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { - r = path_is_config(&paths, path); - if (r < 0) - return r; - if (r > 0) { - if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2)) - return -ENOMEM; - - todo[n_todo++] = path; - path = NULL; - } - } - } - } - - strv_uniq(todo); - - r = 0; - STRV_FOREACH(i, todo) { - _cleanup_strv_free_ char **fs = NULL; - const char *rp; - char **j; - - (void) get_files_in_directory(*i, &fs); - - q = rm_rf(*i, REMOVE_ROOT|REMOVE_PHYSICAL); - if (q < 0 && q != -ENOENT && r >= 0) { - r = q; - continue; - } - - STRV_FOREACH(j, fs) { - _cleanup_free_ char *t = NULL; - - t = strjoin(*i, "/", *j, NULL); - if (!t) - return -ENOMEM; - - unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, t, NULL); - } - - unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, *i, NULL); - - rp = skip_root(&paths, *i); - q = mark_symlink_for_removal(&remove_symlinks_to, rp ?: *i); - if (q < 0) - return q; - } - - q = remove_marked_symlinks(remove_symlinks_to, paths.runtime_config, &paths, changes, n_changes); - if (r >= 0) - r = q; - - q = remove_marked_symlinks(remove_symlinks_to, paths.persistent_config, &paths, changes, n_changes); - if (r >= 0) - r = q; - - return r; -} - -int unit_file_add_dependency( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - const char *target, - UnitDependency dep, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_(install_context_done) InstallContext c = {}; - UnitFileInstallInfo *i, *target_info; - const char *config_path; - char **f; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(target); - - if (!IN_SET(dep, UNIT_WANTS, UNIT_REQUIRES)) - return -EINVAL; - - if (!unit_name_is_valid(target, UNIT_NAME_ANY)) - return -EINVAL; - - r = lookup_paths_init(&paths, scope, 0, root_dir); - if (r < 0) - return r; - - config_path = runtime ? paths.runtime_config : paths.persistent_config; - - r = install_info_discover(scope, &c, &paths, target, SEARCH_FOLLOW_CONFIG_SYMLINKS, &target_info); - if (r < 0) - return r; - r = install_info_may_process(target_info, &paths, changes, n_changes); - if (r < 0) - return r; - - assert(target_info->type == UNIT_FILE_TYPE_REGULAR); - - STRV_FOREACH(f, files) { - char ***l; - - r = install_info_discover(scope, &c, &paths, *f, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); - if (r < 0) - return r; - r = install_info_may_process(i, &paths, changes, n_changes); - if (r < 0) - return r; - - assert(i->type == UNIT_FILE_TYPE_REGULAR); - - /* We didn't actually load anything from the unit - * file, but instead just add in our new symlink to - * create. */ - - if (dep == UNIT_WANTS) - l = &i->wanted_by; - else - l = &i->required_by; - - strv_free(*l); - *l = strv_new(target_info->name, NULL); - if (!*l) - return -ENOMEM; - } - - return install_context_apply(scope, &c, &paths, config_path, force, SEARCH_FOLLOW_CONFIG_SYMLINKS, changes, n_changes); -} - -int unit_file_enable( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_(install_context_done) InstallContext c = {}; - const char *config_path; - UnitFileInstallInfo *i; - char **f; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - - r = lookup_paths_init(&paths, scope, 0, root_dir); - if (r < 0) - return r; - - config_path = runtime ? paths.runtime_config : paths.persistent_config; - - STRV_FOREACH(f, files) { - r = install_info_discover(scope, &c, &paths, *f, SEARCH_LOAD, &i); - if (r < 0) - return r; - r = install_info_may_process(i, &paths, changes, n_changes); - if (r < 0) - return r; - - assert(i->type == UNIT_FILE_TYPE_REGULAR); - } - - /* This will return the number of symlink rules that were - supposed to be created, not the ones actually created. This - is useful to determine whether the passed files had any - installation data at all. */ - - return install_context_apply(scope, &c, &paths, config_path, force, SEARCH_LOAD, changes, n_changes); -} - -int unit_file_disable( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_(install_context_done) InstallContext c = {}; - _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; - const char *config_path; - char **i; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - - r = lookup_paths_init(&paths, scope, 0, root_dir); - if (r < 0) - return r; - - config_path = runtime ? paths.runtime_config : paths.persistent_config; - - STRV_FOREACH(i, files) { - if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) - return -EINVAL; - - r = install_info_add(&c, *i, NULL, NULL); - if (r < 0) - return r; - } - - r = install_context_mark_for_removal(scope, &c, &paths, &remove_symlinks_to, config_path); - if (r < 0) - return r; - - return remove_marked_symlinks(remove_symlinks_to, config_path, &paths, changes, n_changes); -} - -int unit_file_reenable( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - char **n; - int r; - size_t l, i; - - /* First, we invoke the disable command with only the basename... */ - l = strv_length(files); - n = newa(char*, l+1); - for (i = 0; i < l; i++) - n[i] = basename(files[i]); - n[i] = NULL; - - r = unit_file_disable(scope, runtime, root_dir, n, changes, n_changes); - if (r < 0) - return r; - - /* But the enable command with the full name */ - return unit_file_enable(scope, runtime, root_dir, files, force, changes, n_changes); -} - -int unit_file_set_default( - UnitFileScope scope, - const char *root_dir, - const char *name, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_(install_context_done) InstallContext c = {}; - UnitFileInstallInfo *i; - const char *new_path, *old_path; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(name); - - if (unit_name_to_type(name) != UNIT_TARGET) /* this also validates the name */ - return -EINVAL; - if (streq(name, SPECIAL_DEFAULT_TARGET)) - return -EINVAL; - - r = lookup_paths_init(&paths, scope, 0, root_dir); - if (r < 0) - return r; - - r = install_info_discover(scope, &c, &paths, name, 0, &i); - if (r < 0) - return r; - r = install_info_may_process(i, &paths, changes, n_changes); - if (r < 0) - return r; - - old_path = skip_root(&paths, i->path); - new_path = strjoina(paths.persistent_config, "/" SPECIAL_DEFAULT_TARGET); - - return create_symlink(old_path ?: i->path, new_path, force, changes, n_changes); -} - -int unit_file_get_default( - UnitFileScope scope, - const char *root_dir, - char **name) { - - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_(install_context_done) InstallContext c = {}; - UnitFileInstallInfo *i; - char *n; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(name); - - r = lookup_paths_init(&paths, scope, 0, root_dir); - if (r < 0) - return r; - - r = install_info_discover(scope, &c, &paths, SPECIAL_DEFAULT_TARGET, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); - if (r < 0) - return r; - r = install_info_may_process(i, &paths, NULL, 0); - if (r < 0) - return r; - - n = strdup(i->name); - if (!n) - return -ENOMEM; - - *name = n; - return 0; -} - -static int unit_file_lookup_state( - UnitFileScope scope, - const LookupPaths *paths, - const char *name, - UnitFileState *ret) { - - _cleanup_(install_context_done) InstallContext c = {}; - UnitFileInstallInfo *i; - UnitFileState state; - int r; - - assert(paths); - assert(name); - - if (!unit_name_is_valid(name, UNIT_NAME_ANY)) - return -EINVAL; - - r = install_info_discover(scope, &c, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); - if (r < 0) - return r; - - /* Shortcut things, if the caller just wants to know if this unit exists. */ - if (!ret) - return 0; - - switch (i->type) { - - case UNIT_FILE_TYPE_MASKED: - r = path_is_runtime(paths, i->path); - if (r < 0) - return r; - - state = r > 0 ? UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED; - break; - - case UNIT_FILE_TYPE_REGULAR: - r = path_is_generator(paths, i->path); - if (r < 0) - return r; - if (r > 0) { - state = UNIT_FILE_GENERATED; - break; - } - - r = path_is_transient(paths, i->path); - if (r < 0) - return r; - if (r > 0) { - state = UNIT_FILE_TRANSIENT; - break; - } - - r = find_symlinks_in_scope(scope, paths, i->name, &state); - if (r < 0) - return r; - if (r == 0) { - if (UNIT_FILE_INSTALL_INFO_HAS_RULES(i)) - state = UNIT_FILE_DISABLED; - else if (UNIT_FILE_INSTALL_INFO_HAS_ALSO(i)) - state = UNIT_FILE_INDIRECT; - else - state = UNIT_FILE_STATIC; - } - - break; - - default: - assert_not_reached("Unexpect unit file type."); - } - - *ret = state; - return 0; -} - -int unit_file_get_state( - UnitFileScope scope, - const char *root_dir, - const char *name, - UnitFileState *ret) { - - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(name); - - r = lookup_paths_init(&paths, scope, 0, root_dir); - if (r < 0) - return r; - - return unit_file_lookup_state(scope, &paths, name, ret); -} - -int unit_file_exists(UnitFileScope scope, const LookupPaths *paths, const char *name) { - _cleanup_(install_context_done) InstallContext c = {}; - int r; - - assert(paths); - assert(name); - - if (!unit_name_is_valid(name, UNIT_NAME_ANY)) - return -EINVAL; - - r = install_info_discover(scope, &c, paths, name, 0, NULL); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - - return 1; -} - -static int read_presets(UnitFileScope scope, const char *root_dir, Presets *presets) { - _cleanup_(presets_freep) Presets ps = {}; - size_t n_allocated = 0; - _cleanup_strv_free_ char **files = NULL; - char **p; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(presets); - - if (scope == UNIT_FILE_SYSTEM) - r = conf_files_list(&files, ".preset", root_dir, - "/etc/systemd/system-preset", - "/usr/local/lib/systemd/system-preset", - "/usr/lib/systemd/system-preset", -#ifdef HAVE_SPLIT_USR - "/lib/systemd/system-preset", -#endif - NULL); - else if (scope == UNIT_FILE_GLOBAL) - r = conf_files_list(&files, ".preset", root_dir, - "/etc/systemd/user-preset", - "/usr/local/lib/systemd/user-preset", - "/usr/lib/systemd/user-preset", - NULL); - else { - *presets = (Presets){}; - - return 0; - } - - if (r < 0) - return r; - - STRV_FOREACH(p, files) { - _cleanup_fclose_ FILE *f; - char line[LINE_MAX]; - int n = 0; - - f = fopen(*p, "re"); - if (!f) { - if (errno == ENOENT) - continue; - - return -errno; - } - - FOREACH_LINE(line, f, return -errno) { - PresetRule rule = {}; - const char *parameter; - char *l; - - l = strstrip(line); - n++; - - if (isempty(l)) - continue; - if (strchr(COMMENTS, *l)) - continue; - - parameter = first_word(l, "enable"); - if (parameter) { - char *pattern; - - pattern = strdup(parameter); - if (!pattern) - return -ENOMEM; - - rule = (PresetRule) { - .pattern = pattern, - .action = PRESET_ENABLE, - }; - } - - parameter = first_word(l, "disable"); - if (parameter) { - char *pattern; - - pattern = strdup(parameter); - if (!pattern) - return -ENOMEM; - - rule = (PresetRule) { - .pattern = pattern, - .action = PRESET_DISABLE, - }; - } - - if (rule.action) { - if (!GREEDY_REALLOC(ps.rules, n_allocated, ps.n_rules + 1)) - return -ENOMEM; - - ps.rules[ps.n_rules++] = rule; - continue; - } - - log_syntax(NULL, LOG_WARNING, *p, n, 0, "Couldn't parse line '%s'. Ignoring.", line); - } - } - - *presets = ps; - ps = (Presets){}; - - return 0; -} - -static int query_presets(const char *name, const Presets presets) { - PresetAction action = PRESET_UNKNOWN; - size_t i; - - if (!unit_name_is_valid(name, UNIT_NAME_ANY)) - return -EINVAL; - - for (i = 0; i < presets.n_rules; i++) - if (fnmatch(presets.rules[i].pattern, name, FNM_NOESCAPE) == 0) { - action = presets.rules[i].action; - break; - } - - switch (action) { - case PRESET_UNKNOWN: - log_debug("Preset files don't specify rule for %s. Enabling.", name); - return 1; - case PRESET_ENABLE: - log_debug("Preset files say enable %s.", name); - return 1; - case PRESET_DISABLE: - log_debug("Preset files say disable %s.", name); - return 0; - default: - assert_not_reached("invalid preset action"); - } -} - -int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char *name) { - _cleanup_(presets_freep) Presets presets = {}; - int r; - - r = read_presets(scope, root_dir, &presets); - if (r < 0) - return r; - - return query_presets(name, presets); -} - -static int execute_preset( - UnitFileScope scope, - InstallContext *plus, - InstallContext *minus, - const LookupPaths *paths, - const char *config_path, - char **files, - UnitFilePresetMode mode, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - int r; - - assert(plus); - assert(minus); - assert(paths); - assert(config_path); - - if (mode != UNIT_FILE_PRESET_ENABLE_ONLY) { - _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; - - r = install_context_mark_for_removal(scope, minus, paths, &remove_symlinks_to, config_path); - if (r < 0) - return r; - - r = remove_marked_symlinks(remove_symlinks_to, config_path, paths, changes, n_changes); - } else - r = 0; - - if (mode != UNIT_FILE_PRESET_DISABLE_ONLY) { - int q; - - /* Returns number of symlinks that where supposed to be installed. */ - q = install_context_apply(scope, plus, paths, config_path, force, SEARCH_LOAD, changes, n_changes); - if (r >= 0) { - if (q < 0) - r = q; - else - r += q; - } - } - - return r; -} - -static int preset_prepare_one( - UnitFileScope scope, - InstallContext *plus, - InstallContext *minus, - LookupPaths *paths, - UnitFilePresetMode mode, - const char *name, - Presets presets, - UnitFileChange **changes, - unsigned *n_changes) { - - UnitFileInstallInfo *i; - int r; - - if (install_info_find(plus, name) || - install_info_find(minus, name)) - return 0; - - r = query_presets(name, presets); - if (r < 0) - return r; - - if (r > 0) { - r = install_info_discover(scope, plus, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); - if (r < 0) - return r; - - r = install_info_may_process(i, paths, changes, n_changes); - if (r < 0) - return r; - } else - r = install_info_discover(scope, minus, paths, name, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); - - return r; -} - -int unit_file_preset( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - UnitFilePresetMode mode, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_(install_context_done) InstallContext plus = {}, minus = {}; - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_(presets_freep) Presets presets = {}; - const char *config_path; - char **i; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(mode < _UNIT_FILE_PRESET_MAX); - - r = lookup_paths_init(&paths, scope, 0, root_dir); - if (r < 0) - return r; - - config_path = runtime ? paths.runtime_config : paths.persistent_config; - - r = read_presets(scope, root_dir, &presets); - if (r < 0) - return r; - - STRV_FOREACH(i, files) { - r = preset_prepare_one(scope, &plus, &minus, &paths, mode, *i, presets, changes, n_changes); - if (r < 0) - return r; - } - - return execute_preset(scope, &plus, &minus, &paths, config_path, files, mode, force, changes, n_changes); -} - -int unit_file_preset_all( - UnitFileScope scope, - bool runtime, - const char *root_dir, - UnitFilePresetMode mode, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_(install_context_done) InstallContext plus = {}, minus = {}; - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_(presets_freep) Presets presets = {}; - const char *config_path = NULL; - char **i; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(mode < _UNIT_FILE_PRESET_MAX); - - r = lookup_paths_init(&paths, scope, 0, root_dir); - if (r < 0) - return r; - - config_path = runtime ? paths.runtime_config : paths.persistent_config; - - r = read_presets(scope, root_dir, &presets); - if (r < 0) - return r; - - STRV_FOREACH(i, paths.search_path) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - - d = opendir(*i); - if (!d) { - if (errno == ENOENT) - continue; - - return -errno; - } - - FOREACH_DIRENT(de, d, return -errno) { - - if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) - continue; - - dirent_ensure_type(d, de); - - if (!IN_SET(de->d_type, DT_LNK, DT_REG)) - continue; - - /* we don't pass changes[] in, because we want to handle errors on our own */ - r = preset_prepare_one(scope, &plus, &minus, &paths, mode, de->d_name, presets, NULL, 0); - if (r == -ERFKILL) - r = unit_file_changes_add(changes, n_changes, - UNIT_FILE_IS_MASKED, de->d_name, NULL); - else if (r == -ENOLINK) - r = unit_file_changes_add(changes, n_changes, - UNIT_FILE_IS_DANGLING, de->d_name, NULL); - if (r < 0) - return r; - } - } - - return execute_preset(scope, &plus, &minus, &paths, config_path, NULL, mode, force, changes, n_changes); -} - -static void unit_file_list_free_one(UnitFileList *f) { - if (!f) - return; - - free(f->path); - free(f); -} - -Hashmap* unit_file_list_free(Hashmap *h) { - UnitFileList *i; - - while ((i = hashmap_steal_first(h))) - unit_file_list_free_one(i); - - return hashmap_free(h); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(UnitFileList*, unit_file_list_free_one); - -int unit_file_get_list( - UnitFileScope scope, - const char *root_dir, - Hashmap *h, - char **states, - char **patterns) { - - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - char **i; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(h); - - r = lookup_paths_init(&paths, scope, 0, root_dir); - if (r < 0) - return r; - - STRV_FOREACH(i, paths.search_path) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - - d = opendir(*i); - if (!d) { - if (errno == ENOENT) - continue; - - return -errno; - } - - FOREACH_DIRENT(de, d, return -errno) { - _cleanup_(unit_file_list_free_onep) UnitFileList *f = NULL; - - if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) - continue; - - if (!strv_fnmatch_or_empty(patterns, de->d_name, FNM_NOESCAPE)) - continue; - - if (hashmap_get(h, de->d_name)) - continue; - - dirent_ensure_type(d, de); - - if (!IN_SET(de->d_type, DT_LNK, DT_REG)) - continue; - - f = new0(UnitFileList, 1); - if (!f) - return -ENOMEM; - - f->path = path_make_absolute(de->d_name, *i); - if (!f->path) - return -ENOMEM; - - r = unit_file_lookup_state(scope, &paths, de->d_name, &f->state); - if (r < 0) - f->state = UNIT_FILE_BAD; - - if (!strv_isempty(states) && - !strv_contains(states, unit_file_state_to_string(f->state))) - continue; - - r = hashmap_put(h, basename(f->path), f); - if (r < 0) - return r; - - f = NULL; /* prevent cleanup */ - } - } - - return 0; -} - -static const char* const unit_file_state_table[_UNIT_FILE_STATE_MAX] = { - [UNIT_FILE_ENABLED] = "enabled", - [UNIT_FILE_ENABLED_RUNTIME] = "enabled-runtime", - [UNIT_FILE_LINKED] = "linked", - [UNIT_FILE_LINKED_RUNTIME] = "linked-runtime", - [UNIT_FILE_MASKED] = "masked", - [UNIT_FILE_MASKED_RUNTIME] = "masked-runtime", - [UNIT_FILE_STATIC] = "static", - [UNIT_FILE_DISABLED] = "disabled", - [UNIT_FILE_INDIRECT] = "indirect", - [UNIT_FILE_GENERATED] = "generated", - [UNIT_FILE_TRANSIENT] = "transient", - [UNIT_FILE_BAD] = "bad", -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_file_state, UnitFileState); - -static const char* const unit_file_change_type_table[_UNIT_FILE_CHANGE_TYPE_MAX] = { - [UNIT_FILE_SYMLINK] = "symlink", - [UNIT_FILE_UNLINK] = "unlink", - [UNIT_FILE_IS_MASKED] = "masked", - [UNIT_FILE_IS_DANGLING] = "dangling", -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_file_change_type, UnitFileChangeType); - -static const char* const unit_file_preset_mode_table[_UNIT_FILE_PRESET_MAX] = { - [UNIT_FILE_PRESET_FULL] = "full", - [UNIT_FILE_PRESET_ENABLE_ONLY] = "enable-only", - [UNIT_FILE_PRESET_DISABLE_ONLY] = "disable-only", -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_file_preset_mode, UnitFilePresetMode); diff --git a/src/shared/install.h b/src/shared/install.h deleted file mode 100644 index c6aa4f6ef1..0000000000 --- a/src/shared/install.h +++ /dev/null @@ -1,256 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -typedef enum UnitFileScope UnitFileScope; -typedef enum UnitFileState UnitFileState; -typedef enum UnitFilePresetMode UnitFilePresetMode; -typedef enum UnitFileChangeType UnitFileChangeType; -typedef enum UnitFileType UnitFileType; -typedef struct UnitFileChange UnitFileChange; -typedef struct UnitFileList UnitFileList; -typedef struct UnitFileInstallInfo UnitFileInstallInfo; - -#include - -#include "hashmap.h" -#include "macro.h" -#include "path-lookup.h" -#include "strv.h" -#include "unit-name.h" - -enum UnitFileScope { - UNIT_FILE_SYSTEM, - UNIT_FILE_GLOBAL, - UNIT_FILE_USER, - _UNIT_FILE_SCOPE_MAX, - _UNIT_FILE_SCOPE_INVALID = -1 -}; - -enum UnitFileState { - UNIT_FILE_ENABLED, - UNIT_FILE_ENABLED_RUNTIME, - UNIT_FILE_LINKED, - UNIT_FILE_LINKED_RUNTIME, - UNIT_FILE_MASKED, - UNIT_FILE_MASKED_RUNTIME, - UNIT_FILE_STATIC, - UNIT_FILE_DISABLED, - UNIT_FILE_INDIRECT, - UNIT_FILE_GENERATED, - UNIT_FILE_TRANSIENT, - UNIT_FILE_BAD, - _UNIT_FILE_STATE_MAX, - _UNIT_FILE_STATE_INVALID = -1 -}; - -enum UnitFilePresetMode { - UNIT_FILE_PRESET_FULL, - UNIT_FILE_PRESET_ENABLE_ONLY, - UNIT_FILE_PRESET_DISABLE_ONLY, - _UNIT_FILE_PRESET_MAX, - _UNIT_FILE_PRESET_INVALID = -1 -}; - -enum UnitFileChangeType { - UNIT_FILE_SYMLINK, - UNIT_FILE_UNLINK, - UNIT_FILE_IS_MASKED, - UNIT_FILE_IS_DANGLING, - _UNIT_FILE_CHANGE_TYPE_MAX, - _UNIT_FILE_CHANGE_INVALID = INT_MIN -}; - -/* type can either one of the UnitFileChangeTypes listed above, or a negative error. - * If source is specified, it should be the contents of the path symlink. - * In case of an error, source should be the existing symlink contents or NULL - */ -struct UnitFileChange { - int type; /* UnitFileChangeType or bust */ - char *path; - char *source; -}; - -static inline bool unit_file_changes_have_modification(const UnitFileChange* changes, unsigned n_changes) { - unsigned i; - for (i = 0; i < n_changes; i++) - if (IN_SET(changes[i].type, UNIT_FILE_SYMLINK, UNIT_FILE_UNLINK)) - return true; - return false; -} - -struct UnitFileList { - char *path; - UnitFileState state; -}; - -enum UnitFileType { - UNIT_FILE_TYPE_REGULAR, - UNIT_FILE_TYPE_SYMLINK, - UNIT_FILE_TYPE_MASKED, - _UNIT_FILE_TYPE_MAX, - _UNIT_FILE_TYPE_INVALID = -1, -}; - -struct UnitFileInstallInfo { - char *name; - char *path; - - char **aliases; - char **wanted_by; - char **required_by; - char **also; - - char *default_instance; - - UnitFileType type; - - char *symlink_target; -}; - -static inline bool UNIT_FILE_INSTALL_INFO_HAS_RULES(UnitFileInstallInfo *i) { - assert(i); - - return !strv_isempty(i->aliases) || - !strv_isempty(i->wanted_by) || - !strv_isempty(i->required_by); -} - -static inline bool UNIT_FILE_INSTALL_INFO_HAS_ALSO(UnitFileInstallInfo *i) { - assert(i); - - return !strv_isempty(i->also); -} - -bool unit_type_may_alias(UnitType type) _const_; -bool unit_type_may_template(UnitType type) _const_; - -int unit_file_enable( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - bool force, - UnitFileChange **changes, - unsigned *n_changes); -int unit_file_disable( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - UnitFileChange **changes, - unsigned *n_changes); -int unit_file_reenable( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - bool force, - UnitFileChange **changes, - unsigned *n_changes); -int unit_file_preset( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - UnitFilePresetMode mode, - bool force, - UnitFileChange **changes, - unsigned *n_changes); -int unit_file_preset_all( - UnitFileScope scope, - bool runtime, - const char *root_dir, - UnitFilePresetMode mode, - bool force, - UnitFileChange **changes, - unsigned *n_changes); -int unit_file_mask( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - bool force, - UnitFileChange **changes, - unsigned *n_changes); -int unit_file_unmask( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - UnitFileChange **changes, - unsigned *n_changes); -int unit_file_link( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - bool force, - UnitFileChange **changes, - unsigned *n_changes); -int unit_file_revert( - UnitFileScope scope, - const char *root_dir, - char **files, - UnitFileChange **changes, - unsigned *n_changes); -int unit_file_set_default( - UnitFileScope scope, - const char *root_dir, - const char *file, - bool force, - UnitFileChange **changes, - unsigned *n_changes); -int unit_file_get_default( - UnitFileScope scope, - const char *root_dir, - char **name); -int unit_file_add_dependency( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - const char *target, - UnitDependency dep, - bool force, - UnitFileChange **changes, - unsigned *n_changes); - -int unit_file_get_state(UnitFileScope scope, const char *root_dir, const char *filename, UnitFileState *ret); -int unit_file_exists(UnitFileScope scope, const LookupPaths *paths, const char *name); - -int unit_file_get_list(UnitFileScope scope, const char *root_dir, Hashmap *h, char **states, char **patterns); -Hashmap* unit_file_list_free(Hashmap *h); - -int unit_file_changes_add(UnitFileChange **changes, unsigned *n_changes, UnitFileChangeType type, const char *path, const char *source); -void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes); -void unit_file_dump_changes(int r, const char *verb, const UnitFileChange *changes, unsigned n_changes, bool quiet); - -int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char *name); - -const char *unit_file_state_to_string(UnitFileState s) _const_; -UnitFileState unit_file_state_from_string(const char *s) _pure_; -/* from_string conversion is unreliable because of the overlap between -EPERM and -1 for error. */ - -const char *unit_file_change_type_to_string(UnitFileChangeType s) _const_; -UnitFileChangeType unit_file_change_type_from_string(const char *s) _pure_; - -const char *unit_file_preset_mode_to_string(UnitFilePresetMode m) _const_; -UnitFilePresetMode unit_file_preset_mode_from_string(const char *s) _pure_; diff --git a/src/shared/linux/auto_dev-ioctl.h b/src/shared/linux/auto_dev-ioctl.h deleted file mode 100644 index aeaeb3ea7a..0000000000 --- a/src/shared/linux/auto_dev-ioctl.h +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright 2008 Red Hat, Inc. All rights reserved. - * Copyright 2008 Ian Kent - * - * This file is part of the Linux kernel and is made available under - * the terms of the GNU General Public License, version 2, or at your - * option, any later version, incorporated herein by reference. - */ - -#ifndef _LINUX_AUTO_DEV_IOCTL_H -#define _LINUX_AUTO_DEV_IOCTL_H - -#include - -#ifdef __KERNEL__ -#include -#else -#include -#endif /* __KERNEL__ */ - -#define AUTOFS_DEVICE_NAME "autofs" - -#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 -#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 - -#define AUTOFS_DEVID_LEN 16 - -#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) - -/* - * An ioctl interface for autofs mount point control. - */ - -struct args_protover { - __u32 version; -}; - -struct args_protosubver { - __u32 sub_version; -}; - -struct args_openmount { - __u32 devid; -}; - -struct args_ready { - __u32 token; -}; - -struct args_fail { - __u32 token; - __s32 status; -}; - -struct args_setpipefd { - __s32 pipefd; -}; - -struct args_timeout { - __u64 timeout; -}; - -struct args_requester { - __u32 uid; - __u32 gid; -}; - -struct args_expire { - __u32 how; -}; - -struct args_askumount { - __u32 may_umount; -}; - -struct args_ismountpoint { - union { - struct args_in { - __u32 type; - } in; - struct args_out { - __u32 devid; - __u32 magic; - } out; - }; -}; - -/* - * All the ioctls use this structure. - * When sending a path size must account for the total length - * of the chunk of memory otherwise is is the size of the - * structure. - */ - -struct autofs_dev_ioctl { - __u32 ver_major; - __u32 ver_minor; - __u32 size; /* total size of data passed in - * including this struct */ - __s32 ioctlfd; /* automount command fd */ - - /* Command parameters */ - - union { - struct args_protover protover; - struct args_protosubver protosubver; - struct args_openmount openmount; - struct args_ready ready; - struct args_fail fail; - struct args_setpipefd setpipefd; - struct args_timeout timeout; - struct args_requester requester; - struct args_expire expire; - struct args_askumount askumount; - struct args_ismountpoint ismountpoint; - }; - - char path[0]; -}; - -static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) { - memset(in, 0, sizeof(struct autofs_dev_ioctl)); - in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; - in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; - in->size = sizeof(struct autofs_dev_ioctl); - in->ioctlfd = -1; - return; -} - -/* - * If you change this make sure you make the corresponding change - * to autofs-dev-ioctl.c:lookup_ioctl() - */ -enum { - /* Get various version info */ - AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, - AUTOFS_DEV_IOCTL_PROTOVER_CMD, - AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, - - /* Open mount ioctl fd */ - AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, - - /* Close mount ioctl fd */ - AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, - - /* Mount/expire status returns */ - AUTOFS_DEV_IOCTL_READY_CMD, - AUTOFS_DEV_IOCTL_FAIL_CMD, - - /* Activate/deactivate autofs mount */ - AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, - AUTOFS_DEV_IOCTL_CATATONIC_CMD, - - /* Expiry timeout */ - AUTOFS_DEV_IOCTL_TIMEOUT_CMD, - - /* Get mount last requesting uid and gid */ - AUTOFS_DEV_IOCTL_REQUESTER_CMD, - - /* Check for eligible expire candidates */ - AUTOFS_DEV_IOCTL_EXPIRE_CMD, - - /* Request busy status */ - AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, - - /* Check if path is a mountpoint */ - AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, -}; - -#define AUTOFS_IOCTL 0x93 - -#define AUTOFS_DEV_IOCTL_VERSION \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_PROTOVER \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_OPENMOUNT \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_READY \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_FAIL \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_SETPIPEFD \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_CATATONIC \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_TIMEOUT \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_REQUESTER \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_EXPIRE \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) - -#endif /* _LINUX_AUTO_DEV_IOCTL_H */ diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c deleted file mode 100644 index 9351b85eed..0000000000 --- a/src/shared/logs-show.c +++ /dev/null @@ -1,1310 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-id128.h" -#include "sd-journal.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "hashmap.h" -#include "hostname-util.h" -#include "io-util.h" -#include "journal-internal.h" -#include "log.h" -#include "logs-show.h" -#include "macro.h" -#include "output-mode.h" -#include "parse-util.h" -#include "process-util.h" -#include "sparse-endian.h" -#include "string-table.h" -#include "string-util.h" -#include "terminal-util.h" -#include "time-util.h" -#include "utf8.h" -#include "util.h" - -/* up to three lines (each up to 100 characters) or 300 characters, whichever is less */ -#define PRINT_LINE_THRESHOLD 3 -#define PRINT_CHAR_THRESHOLD 300 - -#define JSON_THRESHOLD 4096 - -static int print_catalog(FILE *f, sd_journal *j) { - int r; - _cleanup_free_ char *t = NULL, *z = NULL; - - - r = sd_journal_get_catalog(j, &t); - if (r < 0) - return r; - - z = strreplace(strstrip(t), "\n", "\n-- "); - if (!z) - return log_oom(); - - fputs("-- ", f); - fputs(z, f); - fputc('\n', f); - - return 0; -} - -static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) { - size_t fl, nl; - char *buf; - - assert(data); - assert(field); - assert(target); - - fl = strlen(field); - if (length < fl) - return 0; - - if (memcmp(data, field, fl)) - return 0; - - nl = length - fl; - buf = new(char, nl+1); - if (!buf) - return log_oom(); - - memcpy(buf, (const char*) data + fl, nl); - buf[nl] = 0; - - free(*target); - *target = buf; - - if (target_size) - *target_size = nl; - - return 1; -} - -static bool shall_print(const char *p, size_t l, OutputFlags flags) { - assert(p); - - if (flags & OUTPUT_SHOW_ALL) - return true; - - if (l >= PRINT_CHAR_THRESHOLD) - return false; - - if (!utf8_is_printable(p, l)) - return false; - - return true; -} - -static bool print_multiline(FILE *f, unsigned prefix, unsigned n_columns, OutputFlags flags, int priority, const char* message, size_t message_len) { - const char *color_on = "", *color_off = ""; - const char *pos, *end; - bool ellipsized = false; - int line = 0; - - if (flags & OUTPUT_COLOR) { - if (priority <= LOG_ERR) { - color_on = ANSI_HIGHLIGHT_RED; - color_off = ANSI_NORMAL; - } else if (priority <= LOG_NOTICE) { - color_on = ANSI_HIGHLIGHT; - color_off = ANSI_NORMAL; - } - } - - /* A special case: make sure that we print a newline when - the message is empty. */ - if (message_len == 0) - fputs("\n", f); - - for (pos = message; - pos < message + message_len; - pos = end + 1, line++) { - bool continuation = line > 0; - bool tail_line; - int len; - for (end = pos; end < message + message_len && *end != '\n'; end++) - ; - len = end - pos; - assert(len >= 0); - - /* We need to figure out when we are showing not-last line, *and* - * will skip subsequent lines. In that case, we will put the dots - * at the end of the line, instead of putting dots in the middle - * or not at all. - */ - tail_line = - line + 1 == PRINT_LINE_THRESHOLD || - end + 1 >= message + PRINT_CHAR_THRESHOLD; - - if (flags & (OUTPUT_FULL_WIDTH | OUTPUT_SHOW_ALL) || - (prefix + len + 1 < n_columns && !tail_line)) { - fprintf(f, "%*s%s%.*s%s\n", - continuation * prefix, "", - color_on, len, pos, color_off); - continue; - } - - /* Beyond this point, ellipsization will happen. */ - ellipsized = true; - - if (prefix < n_columns && n_columns - prefix >= 3) { - if (n_columns - prefix > (unsigned) len + 3) - fprintf(f, "%*s%s%.*s...%s\n", - continuation * prefix, "", - color_on, len, pos, color_off); - else { - _cleanup_free_ char *e; - - e = ellipsize_mem(pos, len, n_columns - prefix, - tail_line ? 100 : 90); - if (!e) - fprintf(f, "%*s%s%.*s%s\n", - continuation * prefix, "", - color_on, len, pos, color_off); - else - fprintf(f, "%*s%s%s%s\n", - continuation * prefix, "", - color_on, e, color_off); - } - } else - fputs("...\n", f); - - if (tail_line) - break; - } - - return ellipsized; -} - -static int output_short( - FILE *f, - sd_journal *j, - OutputMode mode, - unsigned n_columns, - OutputFlags flags) { - - int r; - const void *data; - size_t length; - size_t n = 0; - _cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, *message = NULL, *realtime = NULL, *monotonic = NULL, *priority = NULL; - size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0, realtime_len = 0, monotonic_len = 0, priority_len = 0; - int p = LOG_INFO; - bool ellipsized = false; - - assert(f); - assert(j); - - /* Set the threshold to one bigger than the actual print - * threshold, so that if the line is actually longer than what - * we're willing to print, ellipsization will occur. This way - * we won't output a misleading line without any indication of - * truncation. - */ - sd_journal_set_data_threshold(j, flags & (OUTPUT_SHOW_ALL|OUTPUT_FULL_WIDTH) ? 0 : PRINT_CHAR_THRESHOLD + 1); - - JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { - - r = parse_field(data, length, "PRIORITY=", &priority, &priority_len); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_field(data, length, "_HOSTNAME=", &hostname, &hostname_len); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_field(data, length, "SYSLOG_IDENTIFIER=", &identifier, &identifier_len); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_field(data, length, "_COMM=", &comm, &comm_len); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_field(data, length, "_PID=", &pid, &pid_len); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_field(data, length, "SYSLOG_PID=", &fake_pid, &fake_pid_len); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_field(data, length, "_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_field(data, length, "MESSAGE=", &message, &message_len); - if (r < 0) - return r; - } - if (r == -EBADMSG) { - log_debug_errno(r, "Skipping message we can't read: %m"); - return 0; - } - if (r < 0) - return log_error_errno(r, "Failed to get journal fields: %m"); - - if (!message) { - log_debug("Skipping message without MESSAGE= field."); - return 0; - } - - if (!(flags & OUTPUT_SHOW_ALL)) - strip_tab_ansi(&message, &message_len); - - if (priority_len == 1 && *priority >= '0' && *priority <= '7') - p = *priority - '0'; - - if (mode == OUTPUT_SHORT_MONOTONIC) { - uint64_t t; - sd_id128_t boot_id; - - r = -ENOENT; - - if (monotonic) - r = safe_atou64(monotonic, &t); - - if (r < 0) - r = sd_journal_get_monotonic_usec(j, &t, &boot_id); - - if (r < 0) - return log_error_errno(r, "Failed to get monotonic timestamp: %m"); - - fprintf(f, "[%5llu.%06llu]", - (unsigned long long) (t / USEC_PER_SEC), - (unsigned long long) (t % USEC_PER_SEC)); - - n += 1 + 5 + 1 + 6 + 1; - - } else { - char buf[64]; - uint64_t x; - time_t t; - struct tm tm; - struct tm *(*gettime_r)(const time_t *, struct tm *); - - r = -ENOENT; - gettime_r = (flags & OUTPUT_UTC) ? gmtime_r : localtime_r; - - if (realtime) - r = safe_atou64(realtime, &x); - - if (r < 0) - r = sd_journal_get_realtime_usec(j, &x); - - if (r < 0) - return log_error_errno(r, "Failed to get realtime timestamp: %m"); - - t = (time_t) (x / USEC_PER_SEC); - - switch (mode) { - - case OUTPUT_SHORT_UNIX: - r = snprintf(buf, sizeof(buf), "%10llu.%06llu", (unsigned long long) t, (unsigned long long) (x % USEC_PER_SEC)); - break; - - case OUTPUT_SHORT_ISO: - r = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", gettime_r(&t, &tm)); - break; - - case OUTPUT_SHORT_PRECISE: - r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm)); - if (r > 0) - snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ".%06llu", (unsigned long long) (x % USEC_PER_SEC)); - break; - - default: - r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm)); - } - - if (r <= 0) { - log_error("Failed to format time."); - return -EINVAL; - } - - fputs(buf, f); - n += strlen(buf); - } - - if (hostname && (flags & OUTPUT_NO_HOSTNAME)) { - /* Suppress display of the hostname if this is requested. */ - hostname = NULL; - hostname_len = 0; - } - - if (hostname && shall_print(hostname, hostname_len, flags)) { - fprintf(f, " %.*s", (int) hostname_len, hostname); - n += hostname_len + 1; - } - - if (identifier && shall_print(identifier, identifier_len, flags)) { - fprintf(f, " %.*s", (int) identifier_len, identifier); - n += identifier_len + 1; - } else if (comm && shall_print(comm, comm_len, flags)) { - fprintf(f, " %.*s", (int) comm_len, comm); - n += comm_len + 1; - } else - fputs(" unknown", f); - - if (pid && shall_print(pid, pid_len, flags)) { - fprintf(f, "[%.*s]", (int) pid_len, pid); - n += pid_len + 2; - } else if (fake_pid && shall_print(fake_pid, fake_pid_len, flags)) { - fprintf(f, "[%.*s]", (int) fake_pid_len, fake_pid); - n += fake_pid_len + 2; - } - - if (!(flags & OUTPUT_SHOW_ALL) && !utf8_is_printable(message, message_len)) { - char bytes[FORMAT_BYTES_MAX]; - fprintf(f, ": [%s blob data]\n", format_bytes(bytes, sizeof(bytes), message_len)); - } else { - fputs(": ", f); - ellipsized |= - print_multiline(f, n + 2, n_columns, flags, p, message, message_len); - } - - if (flags & OUTPUT_CATALOG) - print_catalog(f, j); - - return ellipsized; -} - -static int output_verbose( - FILE *f, - sd_journal *j, - OutputMode mode, - unsigned n_columns, - OutputFlags flags) { - - const void *data; - size_t length; - _cleanup_free_ char *cursor = NULL; - uint64_t realtime = 0; - char ts[FORMAT_TIMESTAMP_MAX + 7]; - int r; - - assert(f); - assert(j); - - sd_journal_set_data_threshold(j, 0); - - r = sd_journal_get_data(j, "_SOURCE_REALTIME_TIMESTAMP", &data, &length); - if (r == -ENOENT) - log_debug("Source realtime timestamp not found"); - else if (r < 0) - return log_full_errno(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_ERR, r, "Failed to get source realtime timestamp: %m"); - else { - _cleanup_free_ char *value = NULL; - - r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &value, NULL); - if (r < 0) - return r; - assert(r > 0); - - r = safe_atou64(value, &realtime); - if (r < 0) - log_debug_errno(r, "Failed to parse realtime timestamp: %m"); - } - - if (r < 0) { - r = sd_journal_get_realtime_usec(j, &realtime); - if (r < 0) - return log_full_errno(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_ERR, r, "Failed to get realtime timestamp: %m"); - } - - r = sd_journal_get_cursor(j, &cursor); - if (r < 0) - return log_error_errno(r, "Failed to get cursor: %m"); - - fprintf(f, "%s [%s]\n", - flags & OUTPUT_UTC ? - format_timestamp_us_utc(ts, sizeof(ts), realtime) : - format_timestamp_us(ts, sizeof(ts), realtime), - cursor); - - JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { - const char *c; - int fieldlen; - const char *on = "", *off = ""; - - c = memchr(data, '=', length); - if (!c) { - log_error("Invalid field."); - return -EINVAL; - } - fieldlen = c - (const char*) data; - - if (flags & OUTPUT_COLOR && startswith(data, "MESSAGE=")) { - on = ANSI_HIGHLIGHT; - off = ANSI_NORMAL; - } - - if (flags & OUTPUT_SHOW_ALL || - (((length < PRINT_CHAR_THRESHOLD) || flags & OUTPUT_FULL_WIDTH) - && utf8_is_printable(data, length))) { - fprintf(f, " %s%.*s=", on, fieldlen, (const char*)data); - print_multiline(f, 4 + fieldlen + 1, 0, OUTPUT_FULL_WIDTH, 0, c + 1, length - fieldlen - 1); - fputs(off, f); - } else { - char bytes[FORMAT_BYTES_MAX]; - - fprintf(f, " %s%.*s=[%s blob data]%s\n", - on, - (int) (c - (const char*) data), - (const char*) data, - format_bytes(bytes, sizeof(bytes), length - (c - (const char *) data) - 1), - off); - } - } - - if (r < 0) - return r; - - if (flags & OUTPUT_CATALOG) - print_catalog(f, j); - - return 0; -} - -static int output_export( - FILE *f, - sd_journal *j, - OutputMode mode, - unsigned n_columns, - OutputFlags flags) { - - sd_id128_t boot_id; - char sid[33]; - int r; - usec_t realtime, monotonic; - _cleanup_free_ char *cursor = NULL; - const void *data; - size_t length; - - assert(j); - - sd_journal_set_data_threshold(j, 0); - - r = sd_journal_get_realtime_usec(j, &realtime); - if (r < 0) - return log_error_errno(r, "Failed to get realtime timestamp: %m"); - - r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get monotonic timestamp: %m"); - - r = sd_journal_get_cursor(j, &cursor); - if (r < 0) - return log_error_errno(r, "Failed to get cursor: %m"); - - fprintf(f, - "__CURSOR=%s\n" - "__REALTIME_TIMESTAMP="USEC_FMT"\n" - "__MONOTONIC_TIMESTAMP="USEC_FMT"\n" - "_BOOT_ID=%s\n", - cursor, - realtime, - monotonic, - sd_id128_to_string(boot_id, sid)); - - JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { - - /* We already printed the boot id, from the data in - * the header, hence let's suppress it here */ - if (length >= 9 && - startswith(data, "_BOOT_ID=")) - continue; - - if (utf8_is_printable_newline(data, length, false)) - fwrite(data, length, 1, f); - else { - const char *c; - uint64_t le64; - - c = memchr(data, '=', length); - if (!c) { - log_error("Invalid field."); - return -EINVAL; - } - - fwrite(data, c - (const char*) data, 1, f); - fputc('\n', f); - le64 = htole64(length - (c - (const char*) data) - 1); - fwrite(&le64, sizeof(le64), 1, f); - fwrite(c + 1, length - (c - (const char*) data) - 1, 1, f); - } - - fputc('\n', f); - } - - if (r < 0) - return r; - - fputc('\n', f); - - return 0; -} - -void json_escape( - FILE *f, - const char* p, - size_t l, - OutputFlags flags) { - - assert(f); - assert(p); - - if (!(flags & OUTPUT_SHOW_ALL) && l >= JSON_THRESHOLD) - fputs("null", f); - - else if (!utf8_is_printable(p, l)) { - bool not_first = false; - - fputs("[ ", f); - - while (l > 0) { - if (not_first) - fprintf(f, ", %u", (uint8_t) *p); - else { - not_first = true; - fprintf(f, "%u", (uint8_t) *p); - } - - p++; - l--; - } - - fputs(" ]", f); - } else { - fputc('\"', f); - - while (l > 0) { - if (*p == '"' || *p == '\\') { - fputc('\\', f); - fputc(*p, f); - } else if (*p == '\n') - fputs("\\n", f); - else if ((uint8_t) *p < ' ') - fprintf(f, "\\u%04x", (uint8_t) *p); - else - fputc(*p, f); - - p++; - l--; - } - - fputc('\"', f); - } -} - -static int output_json( - FILE *f, - sd_journal *j, - OutputMode mode, - unsigned n_columns, - OutputFlags flags) { - - uint64_t realtime, monotonic; - _cleanup_free_ char *cursor = NULL; - const void *data; - size_t length; - sd_id128_t boot_id; - char sid[33], *k; - int r; - Hashmap *h = NULL; - bool done, separator; - - assert(j); - - sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD); - - r = sd_journal_get_realtime_usec(j, &realtime); - if (r < 0) - return log_error_errno(r, "Failed to get realtime timestamp: %m"); - - r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get monotonic timestamp: %m"); - - r = sd_journal_get_cursor(j, &cursor); - if (r < 0) - return log_error_errno(r, "Failed to get cursor: %m"); - - if (mode == OUTPUT_JSON_PRETTY) - fprintf(f, - "{\n" - "\t\"__CURSOR\" : \"%s\",\n" - "\t\"__REALTIME_TIMESTAMP\" : \""USEC_FMT"\",\n" - "\t\"__MONOTONIC_TIMESTAMP\" : \""USEC_FMT"\",\n" - "\t\"_BOOT_ID\" : \"%s\"", - cursor, - realtime, - monotonic, - sd_id128_to_string(boot_id, sid)); - else { - if (mode == OUTPUT_JSON_SSE) - fputs("data: ", f); - - fprintf(f, - "{ \"__CURSOR\" : \"%s\", " - "\"__REALTIME_TIMESTAMP\" : \""USEC_FMT"\", " - "\"__MONOTONIC_TIMESTAMP\" : \""USEC_FMT"\", " - "\"_BOOT_ID\" : \"%s\"", - cursor, - realtime, - monotonic, - sd_id128_to_string(boot_id, sid)); - } - - h = hashmap_new(&string_hash_ops); - if (!h) - return log_oom(); - - /* First round, iterate through the entry and count how often each field appears */ - JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { - const char *eq; - char *n; - unsigned u; - - if (length >= 9 && - memcmp(data, "_BOOT_ID=", 9) == 0) - continue; - - eq = memchr(data, '=', length); - if (!eq) - continue; - - n = strndup(data, eq - (const char*) data); - if (!n) { - r = log_oom(); - goto finish; - } - - u = PTR_TO_UINT(hashmap_get(h, n)); - if (u == 0) { - r = hashmap_put(h, n, UINT_TO_PTR(1)); - if (r < 0) { - free(n); - log_oom(); - goto finish; - } - } else { - r = hashmap_update(h, n, UINT_TO_PTR(u + 1)); - free(n); - if (r < 0) { - log_oom(); - goto finish; - } - } - } - - if (r < 0) - return r; - - separator = true; - do { - done = true; - - SD_JOURNAL_FOREACH_DATA(j, data, length) { - const char *eq; - char *kk, *n; - size_t m; - unsigned u; - - /* We already printed the boot id, from the data in - * the header, hence let's suppress it here */ - if (length >= 9 && - memcmp(data, "_BOOT_ID=", 9) == 0) - continue; - - eq = memchr(data, '=', length); - if (!eq) - continue; - - if (separator) { - if (mode == OUTPUT_JSON_PRETTY) - fputs(",\n\t", f); - else - fputs(", ", f); - } - - m = eq - (const char*) data; - - n = strndup(data, m); - if (!n) { - r = log_oom(); - goto finish; - } - - u = PTR_TO_UINT(hashmap_get2(h, n, (void**) &kk)); - if (u == 0) { - /* We already printed this, let's jump to the next */ - free(n); - separator = false; - - continue; - } else if (u == 1) { - /* Field only appears once, output it directly */ - - json_escape(f, data, m, flags); - fputs(" : ", f); - - json_escape(f, eq + 1, length - m - 1, flags); - - hashmap_remove(h, n); - free(kk); - free(n); - - separator = true; - - continue; - - } else { - /* Field appears multiple times, output it as array */ - json_escape(f, data, m, flags); - fputs(" : [ ", f); - json_escape(f, eq + 1, length - m - 1, flags); - - /* Iterate through the end of the list */ - - while (sd_journal_enumerate_data(j, &data, &length) > 0) { - if (length < m + 1) - continue; - - if (memcmp(data, n, m) != 0) - continue; - - if (((const char*) data)[m] != '=') - continue; - - fputs(", ", f); - json_escape(f, (const char*) data + m + 1, length - m - 1, flags); - } - - fputs(" ]", f); - - hashmap_remove(h, n); - free(kk); - free(n); - - /* Iterate data fields form the beginning */ - done = false; - separator = true; - - break; - } - } - - } while (!done); - - if (mode == OUTPUT_JSON_PRETTY) - fputs("\n}\n", f); - else if (mode == OUTPUT_JSON_SSE) - fputs("}\n\n", f); - else - fputs(" }\n", f); - - r = 0; - -finish: - while ((k = hashmap_steal_first_key(h))) - free(k); - - hashmap_free(h); - - return r; -} - -static int output_cat( - FILE *f, - sd_journal *j, - OutputMode mode, - unsigned n_columns, - OutputFlags flags) { - - const void *data; - size_t l; - int r; - - assert(j); - assert(f); - - sd_journal_set_data_threshold(j, 0); - - r = sd_journal_get_data(j, "MESSAGE", &data, &l); - if (r < 0) { - /* An entry without MESSAGE=? */ - if (r == -ENOENT) - return 0; - - return log_error_errno(r, "Failed to get data: %m"); - } - - assert(l >= 8); - - fwrite((const char*) data + 8, 1, l - 8, f); - fputc('\n', f); - - return 0; -} - -static int (*output_funcs[_OUTPUT_MODE_MAX])( - FILE *f, - sd_journal*j, - OutputMode mode, - unsigned n_columns, - OutputFlags flags) = { - - [OUTPUT_SHORT] = output_short, - [OUTPUT_SHORT_ISO] = output_short, - [OUTPUT_SHORT_PRECISE] = output_short, - [OUTPUT_SHORT_MONOTONIC] = output_short, - [OUTPUT_SHORT_UNIX] = output_short, - [OUTPUT_VERBOSE] = output_verbose, - [OUTPUT_EXPORT] = output_export, - [OUTPUT_JSON] = output_json, - [OUTPUT_JSON_PRETTY] = output_json, - [OUTPUT_JSON_SSE] = output_json, - [OUTPUT_CAT] = output_cat -}; - -int output_journal( - FILE *f, - sd_journal *j, - OutputMode mode, - unsigned n_columns, - OutputFlags flags, - bool *ellipsized) { - - int ret; - assert(mode >= 0); - assert(mode < _OUTPUT_MODE_MAX); - - if (n_columns <= 0) - n_columns = columns(); - - ret = output_funcs[mode](f, j, mode, n_columns, flags); - fflush(stdout); - - if (ellipsized && ret > 0) - *ellipsized = true; - - return ret; -} - -static int maybe_print_begin_newline(FILE *f, OutputFlags *flags) { - assert(f); - assert(flags); - - if (!(*flags & OUTPUT_BEGIN_NEWLINE)) - return 0; - - /* Print a beginning new line if that's request, but only once - * on the first line we print. */ - - fputc('\n', f); - *flags &= ~OUTPUT_BEGIN_NEWLINE; - return 0; -} - -static int show_journal(FILE *f, - sd_journal *j, - OutputMode mode, - unsigned n_columns, - usec_t not_before, - unsigned how_many, - OutputFlags flags, - bool *ellipsized) { - - int r; - unsigned line = 0; - bool need_seek = false; - int warn_cutoff = flags & OUTPUT_WARN_CUTOFF; - - assert(j); - assert(mode >= 0); - assert(mode < _OUTPUT_MODE_MAX); - - /* Seek to end */ - r = sd_journal_seek_tail(j); - if (r < 0) - return log_error_errno(r, "Failed to seek to tail: %m"); - - r = sd_journal_previous_skip(j, how_many); - if (r < 0) - return log_error_errno(r, "Failed to skip previous: %m"); - - for (;;) { - for (;;) { - usec_t usec; - - if (need_seek) { - r = sd_journal_next(j); - if (r < 0) - return log_error_errno(r, "Failed to iterate through journal: %m"); - } - - if (r == 0) - break; - - need_seek = true; - - if (not_before > 0) { - r = sd_journal_get_monotonic_usec(j, &usec, NULL); - - /* -ESTALE is returned if the - timestamp is not from this boot */ - if (r == -ESTALE) - continue; - else if (r < 0) - return log_error_errno(r, "Failed to get journal time: %m"); - - if (usec < not_before) - continue; - } - - line++; - maybe_print_begin_newline(f, &flags); - - r = output_journal(f, j, mode, n_columns, flags, ellipsized); - if (r < 0) - return r; - } - - if (warn_cutoff && line < how_many && not_before > 0) { - sd_id128_t boot_id; - usec_t cutoff = 0; - - /* Check whether the cutoff line is too early */ - - r = sd_id128_get_boot(&boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get boot id: %m"); - - r = sd_journal_get_cutoff_monotonic_usec(j, boot_id, &cutoff, NULL); - if (r < 0) - return log_error_errno(r, "Failed to get journal cutoff time: %m"); - - if (r > 0 && not_before < cutoff) { - maybe_print_begin_newline(f, &flags); - fprintf(f, "Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable.\n"); - } - - warn_cutoff = false; - } - - if (!(flags & OUTPUT_FOLLOW)) - break; - - r = sd_journal_wait(j, USEC_INFINITY); - if (r < 0) - return log_error_errno(r, "Failed to wait for journal: %m"); - - } - - return 0; -} - -int add_matches_for_unit(sd_journal *j, const char *unit) { - const char *m1, *m2, *m3, *m4; - int r; - - assert(j); - assert(unit); - - m1 = strjoina("_SYSTEMD_UNIT=", unit); - m2 = strjoina("COREDUMP_UNIT=", unit); - m3 = strjoina("UNIT=", unit); - m4 = strjoina("OBJECT_SYSTEMD_UNIT=", unit); - - (void)( - /* Look for messages from the service itself */ - (r = sd_journal_add_match(j, m1, 0)) || - - /* Look for coredumps of the service */ - (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", 0)) || - (r = sd_journal_add_match(j, "_UID=0", 0)) || - (r = sd_journal_add_match(j, m2, 0)) || - - /* Look for messages from PID 1 about this service */ - (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, "_PID=1", 0)) || - (r = sd_journal_add_match(j, m3, 0)) || - - /* Look for messages from authorized daemons about this service */ - (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, "_UID=0", 0)) || - (r = sd_journal_add_match(j, m4, 0)) - ); - - if (r == 0 && endswith(unit, ".slice")) { - const char *m5; - - m5 = strjoina("_SYSTEMD_SLICE=", unit); - - /* Show all messages belonging to a slice */ - (void)( - (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m5, 0)) - ); - } - - return r; -} - -int add_matches_for_user_unit(sd_journal *j, const char *unit, uid_t uid) { - int r; - char *m1, *m2, *m3, *m4; - char muid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)]; - - assert(j); - assert(unit); - - m1 = strjoina("_SYSTEMD_USER_UNIT=", unit); - m2 = strjoina("USER_UNIT=", unit); - m3 = strjoina("COREDUMP_USER_UNIT=", unit); - m4 = strjoina("OBJECT_SYSTEMD_USER_UNIT=", unit); - sprintf(muid, "_UID="UID_FMT, uid); - - (void) ( - /* Look for messages from the user service itself */ - (r = sd_journal_add_match(j, m1, 0)) || - (r = sd_journal_add_match(j, muid, 0)) || - - /* Look for messages from systemd about this service */ - (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m2, 0)) || - (r = sd_journal_add_match(j, muid, 0)) || - - /* Look for coredumps of the service */ - (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m3, 0)) || - (r = sd_journal_add_match(j, muid, 0)) || - (r = sd_journal_add_match(j, "_UID=0", 0)) || - - /* Look for messages from authorized daemons about this service */ - (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m4, 0)) || - (r = sd_journal_add_match(j, muid, 0)) || - (r = sd_journal_add_match(j, "_UID=0", 0)) - ); - - if (r == 0 && endswith(unit, ".slice")) { - const char *m5; - - m5 = strjoina("_SYSTEMD_SLICE=", unit); - - /* Show all messages belonging to a slice */ - (void)( - (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m5, 0)) || - (r = sd_journal_add_match(j, muid, 0)) - ); - } - - return r; -} - -static int get_boot_id_for_machine(const char *machine, sd_id128_t *boot_id) { - _cleanup_close_pair_ int pair[2] = { -1, -1 }; - _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, rootfd = -1; - pid_t pid, child; - siginfo_t si; - char buf[37]; - ssize_t k; - int r; - - assert(machine); - assert(boot_id); - - if (!machine_name_is_valid(machine)) - return -EINVAL; - - r = container_get_leader(machine, &pid); - if (r < 0) - return r; - - r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, NULL, &rootfd); - if (r < 0) - return r; - - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) - return -errno; - - child = fork(); - if (child < 0) - return -errno; - - if (child == 0) { - int fd; - - pair[0] = safe_close(pair[0]); - - r = namespace_enter(pidnsfd, mntnsfd, -1, -1, rootfd); - if (r < 0) - _exit(EXIT_FAILURE); - - fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - _exit(EXIT_FAILURE); - - r = loop_read_exact(fd, buf, 36, false); - safe_close(fd); - if (r < 0) - _exit(EXIT_FAILURE); - - k = send(pair[1], buf, 36, MSG_NOSIGNAL); - if (k != 36) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); - } - - pair[1] = safe_close(pair[1]); - - r = wait_for_terminate(child, &si); - if (r < 0 || si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) - return r < 0 ? r : -EIO; - - k = recv(pair[0], buf, 36, 0); - if (k != 36) - return -EIO; - - buf[36] = 0; - r = sd_id128_from_string(buf, boot_id); - if (r < 0) - return r; - - return 0; -} - -int add_match_this_boot(sd_journal *j, const char *machine) { - char match[9+32+1] = "_BOOT_ID="; - sd_id128_t boot_id; - int r; - - assert(j); - - if (machine) { - r = get_boot_id_for_machine(machine, &boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get boot id of container %s: %m", machine); - } else { - r = sd_id128_get_boot(&boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get boot id: %m"); - } - - sd_id128_to_string(boot_id, match + 9); - r = sd_journal_add_match(j, match, strlen(match)); - if (r < 0) - return log_error_errno(r, "Failed to add match: %m"); - - r = sd_journal_add_conjunction(j); - if (r < 0) - return log_error_errno(r, "Failed to add conjunction: %m"); - - return 0; -} - -int show_journal_by_unit( - FILE *f, - const char *unit, - OutputMode mode, - unsigned n_columns, - usec_t not_before, - unsigned how_many, - uid_t uid, - OutputFlags flags, - int journal_open_flags, - bool system_unit, - bool *ellipsized) { - - _cleanup_(sd_journal_closep) sd_journal *j = NULL; - int r; - - assert(mode >= 0); - assert(mode < _OUTPUT_MODE_MAX); - assert(unit); - - if (how_many <= 0) - return 0; - - r = sd_journal_open(&j, journal_open_flags); - if (r < 0) - return log_error_errno(r, "Failed to open journal: %m"); - - r = add_match_this_boot(j, NULL); - if (r < 0) - return r; - - if (system_unit) - r = add_matches_for_unit(j, unit); - else - r = add_matches_for_user_unit(j, unit, uid); - if (r < 0) - return log_error_errno(r, "Failed to add unit matches: %m"); - - if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) { - _cleanup_free_ char *filter; - - filter = journal_make_match_string(j); - if (!filter) - return log_oom(); - - log_debug("Journal filter: %s", filter); - } - - return show_journal(f, j, mode, n_columns, not_before, how_many, flags, ellipsized); -} diff --git a/src/shared/logs-show.h b/src/shared/logs-show.h deleted file mode 100644 index 6643440881..0000000000 --- a/src/shared/logs-show.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 -#include -#include - -#include "sd-journal.h" - -#include "macro.h" -#include "output-mode.h" -#include "time-util.h" -#include "util.h" - -int output_journal( - FILE *f, - sd_journal *j, - OutputMode mode, - unsigned n_columns, - OutputFlags flags, - bool *ellipsized); - -int add_match_this_boot(sd_journal *j, const char *machine); - -int add_matches_for_unit( - sd_journal *j, - const char *unit); - -int add_matches_for_user_unit( - sd_journal *j, - const char *unit, - uid_t uid); - -int show_journal_by_unit( - FILE *f, - const char *unit, - OutputMode mode, - unsigned n_columns, - usec_t not_before, - unsigned how_many, - uid_t uid, - OutputFlags flags, - int journal_open_flags, - bool system_unit, - bool *ellipsized); - -void json_escape( - FILE *f, - const char* p, - size_t l, - OutputFlags flags); diff --git a/src/shared/machine-image.c b/src/shared/machine-image.c deleted file mode 100644 index 529d89ee2a..0000000000 --- a/src/shared/machine-image.c +++ /dev/null @@ -1,818 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include "alloc-util.h" -#include "btrfs-util.h" -#include "chattr-util.h" -#include "copy.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fs-util.h" -#include "hashmap.h" -#include "lockfile-util.h" -#include "log.h" -#include "macro.h" -#include "machine-image.h" -#include "mkdir.h" -#include "path-util.h" -#include "rm-rf.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" -#include "utf8.h" -#include "util.h" -#include "xattr-util.h" - -static const char image_search_path[] = - "/var/lib/machines\0" - "/var/lib/container\0" /* legacy */ - "/usr/local/lib/machines\0" - "/usr/lib/machines\0"; - -Image *image_unref(Image *i) { - if (!i) - return NULL; - - free(i->name); - free(i->path); - free(i); - return NULL; -} - -static char **image_settings_path(Image *image) { - _cleanup_strv_free_ char **l = NULL; - char **ret; - const char *fn, *s; - unsigned i = 0; - - assert(image); - - l = new0(char*, 4); - if (!l) - return NULL; - - fn = strjoina(image->name, ".nspawn"); - - FOREACH_STRING(s, "/etc/systemd/nspawn/", "/run/systemd/nspawn/") { - l[i] = strappend(s, fn); - if (!l[i]) - return NULL; - - i++; - } - - l[i] = file_in_same_dir(image->path, fn); - if (!l[i]) - return NULL; - - ret = l; - l = NULL; - - return ret; -} - -static int image_new( - ImageType t, - const char *pretty, - const char *path, - const char *filename, - bool read_only, - usec_t crtime, - usec_t mtime, - Image **ret) { - - _cleanup_(image_unrefp) Image *i = NULL; - - assert(t >= 0); - assert(t < _IMAGE_TYPE_MAX); - assert(pretty); - assert(filename); - assert(ret); - - i = new0(Image, 1); - if (!i) - return -ENOMEM; - - i->type = t; - i->read_only = read_only; - i->crtime = crtime; - i->mtime = mtime; - i->usage = i->usage_exclusive = (uint64_t) -1; - i->limit = i->limit_exclusive = (uint64_t) -1; - - i->name = strdup(pretty); - if (!i->name) - return -ENOMEM; - - if (path) - i->path = strjoin(path, "/", filename, NULL); - else - i->path = strdup(filename); - - if (!i->path) - return -ENOMEM; - - path_kill_slashes(i->path); - - *ret = i; - i = NULL; - - return 0; -} - -static int image_make( - const char *pretty, - int dfd, - const char *path, - const char *filename, - Image **ret) { - - struct stat st; - bool read_only; - int r; - - assert(filename); - - /* We explicitly *do* follow symlinks here, since we want to - * allow symlinking trees into /var/lib/machines/, and treat - * them normally. */ - - if (fstatat(dfd, filename, &st, 0) < 0) - return -errno; - - read_only = - (path && path_startswith(path, "/usr")) || - (faccessat(dfd, filename, W_OK, AT_EACCESS) < 0 && errno == EROFS); - - if (S_ISDIR(st.st_mode)) { - _cleanup_close_ int fd = -1; - unsigned file_attr = 0; - - if (!ret) - return 1; - - if (!pretty) - pretty = filename; - - fd = openat(dfd, filename, O_CLOEXEC|O_NOCTTY|O_DIRECTORY); - if (fd < 0) - return -errno; - - /* btrfs subvolumes have inode 256 */ - if (st.st_ino == 256) { - - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (r) { - BtrfsSubvolInfo info; - - /* It's a btrfs subvolume */ - - r = btrfs_subvol_get_info_fd(fd, 0, &info); - if (r < 0) - return r; - - r = image_new(IMAGE_SUBVOLUME, - pretty, - path, - filename, - info.read_only || read_only, - info.otime, - 0, - ret); - if (r < 0) - return r; - - if (btrfs_quota_scan_ongoing(fd) == 0) { - BtrfsQuotaInfo quota; - - r = btrfs_subvol_get_subtree_quota_fd(fd, 0, "a); - if (r >= 0) { - (*ret)->usage = quota.referenced; - (*ret)->usage_exclusive = quota.exclusive; - - (*ret)->limit = quota.referenced_max; - (*ret)->limit_exclusive = quota.exclusive_max; - } - } - - return 1; - } - } - - /* If the IMMUTABLE bit is set, we consider the - * directory read-only. Since the ioctl is not - * supported everywhere we ignore failures. */ - (void) read_attr_fd(fd, &file_attr); - - /* It's just a normal directory. */ - r = image_new(IMAGE_DIRECTORY, - pretty, - path, - filename, - read_only || (file_attr & FS_IMMUTABLE_FL), - 0, - 0, - ret); - if (r < 0) - return r; - - return 1; - - } else if (S_ISREG(st.st_mode) && endswith(filename, ".raw")) { - usec_t crtime = 0; - - /* It's a RAW disk image */ - - if (!ret) - return 1; - - fd_getcrtime_at(dfd, filename, &crtime, 0); - - if (!pretty) - pretty = strndupa(filename, strlen(filename) - 4); - - r = image_new(IMAGE_RAW, - pretty, - path, - filename, - !(st.st_mode & 0222) || read_only, - crtime, - timespec_load(&st.st_mtim), - ret); - if (r < 0) - return r; - - (*ret)->usage = (*ret)->usage_exclusive = st.st_blocks * 512; - (*ret)->limit = (*ret)->limit_exclusive = st.st_size; - - return 1; - } - - return 0; -} - -int image_find(const char *name, Image **ret) { - const char *path; - int r; - - assert(name); - - /* There are no images with invalid names */ - if (!image_name_is_valid(name)) - return 0; - - NULSTR_FOREACH(path, image_search_path) { - _cleanup_closedir_ DIR *d = NULL; - - d = opendir(path); - if (!d) { - if (errno == ENOENT) - continue; - - return -errno; - } - - r = image_make(NULL, dirfd(d), path, name, ret); - if (r == 0 || r == -ENOENT) { - _cleanup_free_ char *raw = NULL; - - raw = strappend(name, ".raw"); - if (!raw) - return -ENOMEM; - - r = image_make(NULL, dirfd(d), path, raw, ret); - if (r == 0 || r == -ENOENT) - continue; - } - if (r < 0) - return r; - - return 1; - } - - if (streq(name, ".host")) - return image_make(".host", AT_FDCWD, NULL, "/", ret); - - return 0; -}; - -int image_discover(Hashmap *h) { - const char *path; - int r; - - assert(h); - - NULSTR_FOREACH(path, image_search_path) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - - d = opendir(path); - if (!d) { - if (errno == ENOENT) - continue; - - return -errno; - } - - FOREACH_DIRENT_ALL(de, d, return -errno) { - _cleanup_(image_unrefp) Image *image = NULL; - - if (!image_name_is_valid(de->d_name)) - continue; - - if (hashmap_contains(h, de->d_name)) - continue; - - r = image_make(NULL, dirfd(d), path, de->d_name, &image); - if (r == 0 || r == -ENOENT) - continue; - if (r < 0) - return r; - - r = hashmap_put(h, image->name, image); - if (r < 0) - return r; - - image = NULL; - } - } - - if (!hashmap_contains(h, ".host")) { - _cleanup_(image_unrefp) Image *image = NULL; - - r = image_make(".host", AT_FDCWD, NULL, "/", &image); - if (r < 0) - return r; - - r = hashmap_put(h, image->name, image); - if (r < 0) - return r; - - image = NULL; - - } - - return 0; -} - -void image_hashmap_free(Hashmap *map) { - Image *i; - - while ((i = hashmap_steal_first(map))) - image_unref(i); - - hashmap_free(map); -} - -int image_remove(Image *i) { - _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT; - _cleanup_strv_free_ char **settings = NULL; - char **j; - int r; - - assert(i); - - if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i)) - return -EROFS; - - settings = image_settings_path(i); - if (!settings) - return -ENOMEM; - - /* Make sure we don't interfere with a running nspawn */ - r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock); - if (r < 0) - return r; - - switch (i->type) { - - case IMAGE_SUBVOLUME: - r = btrfs_subvol_remove(i->path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); - if (r < 0) - return r; - break; - - case IMAGE_DIRECTORY: - /* Allow deletion of read-only directories */ - (void) chattr_path(i->path, 0, FS_IMMUTABLE_FL); - r = rm_rf(i->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); - if (r < 0) - return r; - - break; - - case IMAGE_RAW: - if (unlink(i->path) < 0) - return -errno; - break; - - default: - return -EOPNOTSUPP; - } - - STRV_FOREACH(j, settings) { - if (unlink(*j) < 0 && errno != ENOENT) - log_debug_errno(errno, "Failed to unlink %s, ignoring: %m", *j); - } - - return 0; -} - -static int rename_settings_file(const char *path, const char *new_name) { - _cleanup_free_ char *rs = NULL; - const char *fn; - - fn = strjoina(new_name, ".nspawn"); - - rs = file_in_same_dir(path, fn); - if (!rs) - return -ENOMEM; - - return rename_noreplace(AT_FDCWD, path, AT_FDCWD, rs); -} - -int image_rename(Image *i, const char *new_name) { - _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT, name_lock = LOCK_FILE_INIT; - _cleanup_free_ char *new_path = NULL, *nn = NULL; - _cleanup_strv_free_ char **settings = NULL; - unsigned file_attr = 0; - char **j; - int r; - - assert(i); - - if (!image_name_is_valid(new_name)) - return -EINVAL; - - if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i)) - return -EROFS; - - settings = image_settings_path(i); - if (!settings) - return -ENOMEM; - - /* Make sure we don't interfere with a running nspawn */ - r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock); - if (r < 0) - return r; - - /* Make sure nobody takes the new name, between the time we - * checked it is currently unused in all search paths, and the - * time we take possession of it */ - r = image_name_lock(new_name, LOCK_EX|LOCK_NB, &name_lock); - if (r < 0) - return r; - - r = image_find(new_name, NULL); - if (r < 0) - return r; - if (r > 0) - return -EEXIST; - - switch (i->type) { - - case IMAGE_DIRECTORY: - /* Turn of the immutable bit while we rename the image, so that we can rename it */ - (void) read_attr_path(i->path, &file_attr); - - if (file_attr & FS_IMMUTABLE_FL) - (void) chattr_path(i->path, 0, FS_IMMUTABLE_FL); - - /* fall through */ - - case IMAGE_SUBVOLUME: - new_path = file_in_same_dir(i->path, new_name); - break; - - case IMAGE_RAW: { - const char *fn; - - fn = strjoina(new_name, ".raw"); - new_path = file_in_same_dir(i->path, fn); - break; - } - - default: - return -EOPNOTSUPP; - } - - if (!new_path) - return -ENOMEM; - - nn = strdup(new_name); - if (!nn) - return -ENOMEM; - - r = rename_noreplace(AT_FDCWD, i->path, AT_FDCWD, new_path); - if (r < 0) - return r; - - /* Restore the immutable bit, if it was set before */ - if (file_attr & FS_IMMUTABLE_FL) - (void) chattr_path(new_path, FS_IMMUTABLE_FL, FS_IMMUTABLE_FL); - - free(i->path); - i->path = new_path; - new_path = NULL; - - free(i->name); - i->name = nn; - nn = NULL; - - STRV_FOREACH(j, settings) { - r = rename_settings_file(*j, new_name); - if (r < 0 && r != -ENOENT) - log_debug_errno(r, "Failed to rename settings file %s, ignoring: %m", *j); - } - - return 0; -} - -static int clone_settings_file(const char *path, const char *new_name) { - _cleanup_free_ char *rs = NULL; - const char *fn; - - fn = strjoina(new_name, ".nspawn"); - - rs = file_in_same_dir(path, fn); - if (!rs) - return -ENOMEM; - - return copy_file_atomic(path, rs, 0664, false, 0); -} - -int image_clone(Image *i, const char *new_name, bool read_only) { - _cleanup_release_lock_file_ LockFile name_lock = LOCK_FILE_INIT; - _cleanup_strv_free_ char **settings = NULL; - const char *new_path; - char **j; - int r; - - assert(i); - - if (!image_name_is_valid(new_name)) - return -EINVAL; - - settings = image_settings_path(i); - if (!settings) - return -ENOMEM; - - /* Make sure nobody takes the new name, between the time we - * checked it is currently unused in all search paths, and the - * time we take possession of it */ - r = image_name_lock(new_name, LOCK_EX|LOCK_NB, &name_lock); - if (r < 0) - return r; - - r = image_find(new_name, NULL); - if (r < 0) - return r; - if (r > 0) - return -EEXIST; - - switch (i->type) { - - case IMAGE_SUBVOLUME: - case IMAGE_DIRECTORY: - /* If we can we'll always try to create a new btrfs subvolume here, even if the source is a plain - * directory.*/ - - new_path = strjoina("/var/lib/machines/", new_name); - - r = btrfs_subvol_snapshot(i->path, new_path, (read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA); - if (r == -EOPNOTSUPP) { - /* No btrfs snapshots supported, create a normal directory then. */ - - r = copy_directory(i->path, new_path, false); - if (r >= 0) - (void) chattr_path(new_path, read_only ? FS_IMMUTABLE_FL : 0, FS_IMMUTABLE_FL); - } else if (r >= 0) - /* Enable "subtree" quotas for the copy, if we didn't copy any quota from the source. */ - (void) btrfs_subvol_auto_qgroup(new_path, 0, true); - - break; - - case IMAGE_RAW: - new_path = strjoina("/var/lib/machines/", new_name, ".raw"); - - r = copy_file_atomic(i->path, new_path, read_only ? 0444 : 0644, false, FS_NOCOW_FL); - break; - - default: - return -EOPNOTSUPP; - } - - if (r < 0) - return r; - - STRV_FOREACH(j, settings) { - r = clone_settings_file(*j, new_name); - if (r < 0 && r != -ENOENT) - log_debug_errno(r, "Failed to clone settings %s, ignoring: %m", *j); - } - - return 0; -} - -int image_read_only(Image *i, bool b) { - _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT; - int r; - assert(i); - - if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i)) - return -EROFS; - - /* Make sure we don't interfere with a running nspawn */ - r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock); - if (r < 0) - return r; - - switch (i->type) { - - case IMAGE_SUBVOLUME: - - /* Note that we set the flag only on the top-level - * subvolume of the image. */ - - r = btrfs_subvol_set_read_only(i->path, b); - if (r < 0) - return r; - - break; - - case IMAGE_DIRECTORY: - /* For simple directory trees we cannot use the access - mode of the top-level directory, since it has an - effect on the container itself. However, we can - use the "immutable" flag, to at least make the - top-level directory read-only. It's not as good as - a read-only subvolume, but at least something, and - we can read the value back.*/ - - r = chattr_path(i->path, b ? FS_IMMUTABLE_FL : 0, FS_IMMUTABLE_FL); - if (r < 0) - return r; - - break; - - case IMAGE_RAW: { - struct stat st; - - if (stat(i->path, &st) < 0) - return -errno; - - if (chmod(i->path, (st.st_mode & 0444) | (b ? 0000 : 0200)) < 0) - return -errno; - - /* If the images is now read-only, it's a good time to - * defrag it, given that no write patterns will - * fragment it again. */ - if (b) - (void) btrfs_defrag(i->path); - break; - } - - default: - return -EOPNOTSUPP; - } - - return 0; -} - -int image_path_lock(const char *path, int operation, LockFile *global, LockFile *local) { - _cleanup_free_ char *p = NULL; - LockFile t = LOCK_FILE_INIT; - struct stat st; - int r; - - assert(path); - assert(global); - assert(local); - - /* Locks an image path. This actually creates two locks: one - * "local" one, next to the image path itself, which might be - * shared via NFS. And another "global" one, in /run, that - * uses the device/inode number. This has the benefit that we - * can even lock a tree that is a mount point, correctly. */ - - if (path_equal(path, "/")) - return -EBUSY; - - if (!path_is_absolute(path)) - return -EINVAL; - - if (stat(path, &st) >= 0) { - if (asprintf(&p, "/run/systemd/nspawn/locks/inode-%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino) < 0) - return -ENOMEM; - } - - r = make_lock_file_for(path, operation, &t); - if (r < 0) - return r; - - if (p) { - mkdir_p("/run/systemd/nspawn/locks", 0700); - - r = make_lock_file(p, operation, global); - if (r < 0) { - release_lock_file(&t); - return r; - } - } - - *local = t; - return 0; -} - -int image_set_limit(Image *i, uint64_t referenced_max) { - assert(i); - - if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i)) - return -EROFS; - - if (i->type != IMAGE_SUBVOLUME) - return -EOPNOTSUPP; - - /* We set the quota both for the subvolume as well as for the - * subtree. The latter is mostly for historical reasons, since - * we didn't use to have a concept of subtree quota, and hence - * only modified the subvolume quota. */ - - (void) btrfs_qgroup_set_limit(i->path, 0, referenced_max); - (void) btrfs_subvol_auto_qgroup(i->path, 0, true); - return btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max); -} - -int image_name_lock(const char *name, int operation, LockFile *ret) { - const char *p; - - assert(name); - assert(ret); - - /* Locks an image name, regardless of the precise path used. */ - - if (!image_name_is_valid(name)) - return -EINVAL; - - if (streq(name, ".host")) - return -EBUSY; - - mkdir_p("/run/systemd/nspawn/locks", 0700); - p = strjoina("/run/systemd/nspawn/locks/name-", name); - - return make_lock_file(p, operation, ret); -} - -bool image_name_is_valid(const char *s) { - if (!filename_is_valid(s)) - return false; - - if (string_has_cc(s, NULL)) - return false; - - if (!utf8_is_valid(s)) - return false; - - /* Temporary files for atomically creating new files */ - if (startswith(s, ".#")) - return false; - - return true; -} - -static const char* const image_type_table[_IMAGE_TYPE_MAX] = { - [IMAGE_DIRECTORY] = "directory", - [IMAGE_SUBVOLUME] = "subvolume", - [IMAGE_RAW] = "raw", -}; - -DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType); diff --git a/src/shared/machine-image.h b/src/shared/machine-image.h deleted file mode 100644 index 7410168c4f..0000000000 --- a/src/shared/machine-image.h +++ /dev/null @@ -1,103 +0,0 @@ -#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 -#include - -#include "hashmap.h" -#include "lockfile-util.h" -#include "macro.h" -#include "path-util.h" -#include "string-util.h" -#include "time-util.h" - -typedef enum ImageType { - IMAGE_DIRECTORY, - IMAGE_SUBVOLUME, - IMAGE_RAW, - _IMAGE_TYPE_MAX, - _IMAGE_TYPE_INVALID = -1 -} ImageType; - -typedef struct Image { - ImageType type; - char *name; - char *path; - bool read_only; - - usec_t crtime; - usec_t mtime; - - uint64_t usage; - uint64_t usage_exclusive; - uint64_t limit; - uint64_t limit_exclusive; - - void *userdata; -} Image; - -Image *image_unref(Image *i); -void image_hashmap_free(Hashmap *map); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Image*, image_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, image_hashmap_free); - -int image_find(const char *name, Image **ret); -int image_discover(Hashmap *map); - -int image_remove(Image *i); -int image_rename(Image *i, const char *new_name); -int image_clone(Image *i, const char *new_name, bool read_only); -int image_read_only(Image *i, bool b); - -const char* image_type_to_string(ImageType t) _const_; -ImageType image_type_from_string(const char *s) _pure_; - -bool image_name_is_valid(const char *s) _pure_; - -int image_path_lock(const char *path, int operation, LockFile *global, LockFile *local); -int image_name_lock(const char *name, int operation, LockFile *ret); - -int image_set_limit(Image *i, uint64_t referenced_max); - -static inline bool IMAGE_IS_HIDDEN(const struct Image *i) { - assert(i); - - return i->name && i->name[0] == '.'; -} - -static inline bool IMAGE_IS_VENDOR(const struct Image *i) { - assert(i); - - return i->path && path_startswith(i->path, "/usr"); -} - -static inline bool IMAGE_IS_HOST(const struct Image *i) { - assert(i); - - if (i->name && streq(i->name, ".host")) - return true; - - if (i->path && path_equal(i->path, "/")) - return true; - - return false; -} diff --git a/src/shared/machine-pool.c b/src/shared/machine-pool.c deleted file mode 100644 index 23890c63a0..0000000000 --- a/src/shared/machine-pool.c +++ /dev/null @@ -1,426 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-bus-protocol.h" -#include "sd-bus.h" - -#include "alloc-util.h" -#include "btrfs-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "lockfile-util.h" -#include "log.h" -#include "machine-pool.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "mount-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "signal-util.h" -#include "stat-util.h" -#include "string-util.h" - -#define VAR_LIB_MACHINES_SIZE_START (1024UL*1024UL*500UL) -#define VAR_LIB_MACHINES_FREE_MIN (1024UL*1024UL*750UL) - -static int check_btrfs(void) { - struct statfs sfs; - - if (statfs("/var/lib/machines", &sfs) < 0) { - if (errno != ENOENT) - return -errno; - - if (statfs("/var/lib", &sfs) < 0) - return -errno; - } - - return F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC); -} - -static int setup_machine_raw(uint64_t size, sd_bus_error *error) { - _cleanup_free_ char *tmp = NULL; - _cleanup_close_ int fd = -1; - struct statvfs ss; - pid_t pid = 0; - siginfo_t si; - int r; - - /* We want to be able to make use of btrfs-specific file - * system features, in particular subvolumes, reflinks and - * quota. Hence, if we detect that /var/lib/machines.raw is - * not located on btrfs, let's create a loopback file, place a - * btrfs file system into it, and mount it to - * /var/lib/machines. */ - - fd = open("/var/lib/machines.raw", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); - if (fd >= 0) { - r = fd; - fd = -1; - return r; - } - - if (errno != ENOENT) - return sd_bus_error_set_errnof(error, errno, "Failed to open /var/lib/machines.raw: %m"); - - r = tempfn_xxxxxx("/var/lib/machines.raw", NULL, &tmp); - if (r < 0) - return r; - - (void) mkdir_p_label("/var/lib", 0755); - fd = open(tmp, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0600); - if (fd < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to create /var/lib/machines.raw: %m"); - - if (fstatvfs(fd, &ss) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to determine free space on /var/lib/machines.raw: %m"); - goto fail; - } - - if (ss.f_bsize * ss.f_bavail < VAR_LIB_MACHINES_FREE_MIN) { - r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Not enough free disk space to set up /var/lib/machines."); - goto fail; - } - - if (ftruncate(fd, size) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to enlarge /var/lib/machines.raw: %m"); - goto fail; - } - - pid = fork(); - if (pid < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to fork mkfs.btrfs: %m"); - goto fail; - } - - if (pid == 0) { - - /* Child */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - fd = safe_close(fd); - - execlp("mkfs.btrfs", "-Lvar-lib-machines", tmp, NULL); - if (errno == ENOENT) - _exit(99); - - _exit(EXIT_FAILURE); - } - - r = wait_for_terminate(pid, &si); - if (r < 0) { - sd_bus_error_set_errnof(error, r, "Failed to wait for mkfs.btrfs: %m"); - goto fail; - } - - pid = 0; - - if (si.si_code != CLD_EXITED) { - r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "mkfs.btrfs died abnormally."); - goto fail; - } - if (si.si_status == 99) { - r = sd_bus_error_set_errnof(error, ENOENT, "Cannot set up /var/lib/machines, mkfs.btrfs is missing"); - goto fail; - } - if (si.si_status != 0) { - r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "mkfs.btrfs failed with error code %i", si.si_status); - goto fail; - } - - r = rename_noreplace(AT_FDCWD, tmp, AT_FDCWD, "/var/lib/machines.raw"); - if (r < 0) { - sd_bus_error_set_errnof(error, r, "Failed to move /var/lib/machines.raw into place: %m"); - goto fail; - } - - r = fd; - fd = -1; - - return r; - -fail: - unlink_noerrno(tmp); - - if (pid > 1) - kill_and_sigcont(pid, SIGKILL); - - return r; -} - -int setup_machine_directory(uint64_t size, sd_bus_error *error) { - _cleanup_release_lock_file_ LockFile lock_file = LOCK_FILE_INIT; - struct loop_info64 info = { - .lo_flags = LO_FLAGS_AUTOCLEAR, - }; - _cleanup_close_ int fd = -1, control = -1, loop = -1; - _cleanup_free_ char* loopdev = NULL; - char tmpdir[] = "/tmp/machine-pool.XXXXXX", *mntdir = NULL; - bool tmpdir_made = false, mntdir_made = false, mntdir_mounted = false; - char buf[FORMAT_BYTES_MAX]; - int r, nr = -1; - - /* btrfs cannot handle file systems < 16M, hence use this as minimum */ - if (size == (uint64_t) -1) - size = VAR_LIB_MACHINES_SIZE_START; - else if (size < 16*1024*1024) - size = 16*1024*1024; - - /* Make sure we only set the directory up once at a time */ - r = make_lock_file("/run/systemd/machines.lock", LOCK_EX, &lock_file); - if (r < 0) - return r; - - r = check_btrfs(); - if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to determine whether /var/lib/machines is located on btrfs: %m"); - if (r > 0) { - (void) btrfs_subvol_make_label("/var/lib/machines"); - - r = btrfs_quota_enable("/var/lib/machines", true); - if (r < 0) - log_warning_errno(r, "Failed to enable quota for /var/lib/machines, ignoring: %m"); - - r = btrfs_subvol_auto_qgroup("/var/lib/machines", 0, true); - if (r < 0) - log_warning_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines, ignoring: %m"); - - return 1; - } - - if (path_is_mount_point("/var/lib/machines", AT_SYMLINK_FOLLOW) > 0) { - log_debug("/var/lib/machines is already a mount point, not creating loopback file for it."); - return 0; - } - - r = dir_is_populated("/var/lib/machines"); - if (r < 0 && r != -ENOENT) - return r; - if (r > 0) { - log_debug("/var/log/machines is already populated, not creating loopback file for it."); - return 0; - } - - r = mkfs_exists("btrfs"); - if (r == 0) - return sd_bus_error_set_errnof(error, ENOENT, "Cannot set up /var/lib/machines, mkfs.btrfs is missing"); - if (r < 0) - return r; - - fd = setup_machine_raw(size, error); - if (fd < 0) - return fd; - - control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); - if (control < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to open /dev/loop-control: %m"); - - nr = ioctl(control, LOOP_CTL_GET_FREE); - if (nr < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to allocate loop device: %m"); - - if (asprintf(&loopdev, "/dev/loop%i", nr) < 0) { - r = -ENOMEM; - goto fail; - } - - loop = open(loopdev, O_CLOEXEC|O_RDWR|O_NOCTTY|O_NONBLOCK); - if (loop < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to open loopback device: %m"); - goto fail; - } - - if (ioctl(loop, LOOP_SET_FD, fd) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to bind loopback device: %m"); - goto fail; - } - - if (ioctl(loop, LOOP_SET_STATUS64, &info) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to enable auto-clear for loopback device: %m"); - goto fail; - } - - /* We need to make sure the new /var/lib/machines directory - * has an access mode of 0700 at the time it is first made - * available. mkfs will create it with 0755 however. Hence, - * let's mount the directory into an inaccessible directory - * below /tmp first, fix the access mode, and move it to the - * public place then. */ - - if (!mkdtemp(tmpdir)) { - r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount parent directory: %m"); - goto fail; - } - tmpdir_made = true; - - mntdir = strjoina(tmpdir, "/mnt"); - if (mkdir(mntdir, 0700) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount directory: %m"); - goto fail; - } - mntdir_made = true; - - if (mount(loopdev, mntdir, "btrfs", 0, NULL) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to mount loopback device: %m"); - goto fail; - } - mntdir_mounted = true; - - r = btrfs_quota_enable(mntdir, true); - if (r < 0) - log_warning_errno(r, "Failed to enable quota, ignoring: %m"); - - r = btrfs_subvol_auto_qgroup(mntdir, 0, true); - if (r < 0) - log_warning_errno(r, "Failed to set up default quota hierarchy, ignoring: %m"); - - if (chmod(mntdir, 0700) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to fix owner: %m"); - goto fail; - } - - (void) mkdir_p_label("/var/lib/machines", 0700); - - if (mount(mntdir, "/var/lib/machines", NULL, MS_BIND, NULL) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to mount directory into right place: %m"); - goto fail; - } - - (void) syncfs(fd); - - log_info("Set up /var/lib/machines as btrfs loopback file system of size %s mounted on /var/lib/machines.raw.", format_bytes(buf, sizeof(buf), size)); - - (void) umount2(mntdir, MNT_DETACH); - (void) rmdir(mntdir); - (void) rmdir(tmpdir); - - return 1; - -fail: - if (mntdir_mounted) - (void) umount2(mntdir, MNT_DETACH); - - if (mntdir_made) - (void) rmdir(mntdir); - if (tmpdir_made) - (void) rmdir(tmpdir); - - if (loop >= 0) { - (void) ioctl(loop, LOOP_CLR_FD); - loop = safe_close(loop); - } - - if (control >= 0 && nr >= 0) - (void) ioctl(control, LOOP_CTL_REMOVE, nr); - - return r; -} - -static int sync_path(const char *p) { - _cleanup_close_ int fd = -1; - - fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return -errno; - - if (syncfs(fd) < 0) - return -errno; - - return 0; -} - -int grow_machine_directory(void) { - char buf[FORMAT_BYTES_MAX]; - struct statvfs a, b; - uint64_t old_size, new_size, max_add; - int r; - - /* Ensure the disk space data is accurate */ - sync_path("/var/lib/machines"); - sync_path("/var/lib/machines.raw"); - - if (statvfs("/var/lib/machines.raw", &a) < 0) - return -errno; - - if (statvfs("/var/lib/machines", &b) < 0) - return -errno; - - /* Don't grow if not enough disk space is available on the host */ - if (((uint64_t) a.f_bavail * (uint64_t) a.f_bsize) <= VAR_LIB_MACHINES_FREE_MIN) - return 0; - - /* Don't grow if at least 1/3th of the fs is still free */ - if (b.f_bavail > b.f_blocks / 3) - return 0; - - /* Calculate how much we are willing to add at most */ - max_add = ((uint64_t) a.f_bavail * (uint64_t) a.f_bsize) - VAR_LIB_MACHINES_FREE_MIN; - - /* Calculate the old size */ - old_size = (uint64_t) b.f_blocks * (uint64_t) b.f_bsize; - - /* Calculate the new size as three times the size of what is used right now */ - new_size = ((uint64_t) b.f_blocks - (uint64_t) b.f_bavail) * (uint64_t) b.f_bsize * 3; - - /* Always, grow at least to the start size */ - if (new_size < VAR_LIB_MACHINES_SIZE_START) - new_size = VAR_LIB_MACHINES_SIZE_START; - - /* If the new size is smaller than the old size, don't grow */ - if (new_size < old_size) - return 0; - - /* Ensure we never add more than the maximum */ - if (new_size > old_size + max_add) - new_size = old_size + max_add; - - r = btrfs_resize_loopback("/var/lib/machines", new_size, true); - if (r <= 0) - return r; - - /* Also bump the quota, of both the subvolume leaf qgroup, as - * well as of any subtree quota group by the same id but a - * higher level, if it exists. */ - (void) btrfs_qgroup_set_limit("/var/lib/machines", 0, new_size); - (void) btrfs_subvol_set_subtree_quota_limit("/var/lib/machines", 0, new_size); - - log_info("Grew /var/lib/machines btrfs loopback file system to %s.", format_bytes(buf, sizeof(buf), new_size)); - return 1; -} diff --git a/src/shared/machine-pool.h b/src/shared/machine-pool.h deleted file mode 100644 index 40fe5ecb3a..0000000000 --- a/src/shared/machine-pool.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 "sd-bus.h" - -/* Grow the /var/lib/machines directory after each 10MiB written */ -#define GROW_INTERVAL_BYTES (UINT64_C(10) * UINT64_C(1024) * UINT64_C(1024)) - -int setup_machine_directory(uint64_t size, sd_bus_error *error); -int grow_machine_directory(void); diff --git a/src/shared/output-mode.c b/src/shared/output-mode.c deleted file mode 100644 index bec53ee0ae..0000000000 --- a/src/shared/output-mode.c +++ /dev/null @@ -1,37 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 "output-mode.h" -#include "string-table.h" - -static const char *const output_mode_table[_OUTPUT_MODE_MAX] = { - [OUTPUT_SHORT] = "short", - [OUTPUT_SHORT_ISO] = "short-iso", - [OUTPUT_SHORT_PRECISE] = "short-precise", - [OUTPUT_SHORT_MONOTONIC] = "short-monotonic", - [OUTPUT_SHORT_UNIX] = "short-unix", - [OUTPUT_VERBOSE] = "verbose", - [OUTPUT_EXPORT] = "export", - [OUTPUT_JSON] = "json", - [OUTPUT_JSON_PRETTY] = "json-pretty", - [OUTPUT_JSON_SSE] = "json-sse", - [OUTPUT_CAT] = "cat" -}; - -DEFINE_STRING_TABLE_LOOKUP(output_mode, OutputMode); diff --git a/src/shared/output-mode.h b/src/shared/output-mode.h deleted file mode 100644 index f37189e57f..0000000000 --- a/src/shared/output-mode.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 "macro.h" - -typedef enum OutputMode { - OUTPUT_SHORT, - OUTPUT_SHORT_ISO, - OUTPUT_SHORT_PRECISE, - OUTPUT_SHORT_MONOTONIC, - OUTPUT_SHORT_UNIX, - OUTPUT_VERBOSE, - OUTPUT_EXPORT, - OUTPUT_JSON, - OUTPUT_JSON_PRETTY, - OUTPUT_JSON_SSE, - OUTPUT_CAT, - _OUTPUT_MODE_MAX, - _OUTPUT_MODE_INVALID = -1 -} OutputMode; - -/* The output flags definitions are shared by the logs and process tree output. Some apply to both, some only to the - * logs output, others only to the process tree output. */ - -typedef enum OutputFlags { - OUTPUT_SHOW_ALL = 1 << 0, - OUTPUT_FOLLOW = 1 << 1, - OUTPUT_WARN_CUTOFF = 1 << 2, - OUTPUT_FULL_WIDTH = 1 << 3, - OUTPUT_COLOR = 1 << 4, - OUTPUT_CATALOG = 1 << 5, - OUTPUT_BEGIN_NEWLINE = 1 << 6, - OUTPUT_UTC = 1 << 7, - OUTPUT_KERNEL_THREADS = 1 << 8, - OUTPUT_NO_HOSTNAME = 1 << 9, -} OutputFlags; - -const char* output_mode_to_string(OutputMode m) _const_; -OutputMode output_mode_from_string(const char *s) _pure_; diff --git a/src/shared/pager.c b/src/shared/pager.c deleted file mode 100644 index c16bc027be..0000000000 --- a/src/shared/pager.c +++ /dev/null @@ -1,226 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include - -#include "copy.h" -#include "fd-util.h" -#include "locale-util.h" -#include "log.h" -#include "macro.h" -#include "pager.h" -#include "process-util.h" -#include "signal-util.h" -#include "string-util.h" -#include "terminal-util.h" - -static pid_t pager_pid = 0; - -noreturn static void pager_fallback(void) { - int r; - - r = copy_bytes(STDIN_FILENO, STDOUT_FILENO, (uint64_t) -1, false); - if (r < 0) { - log_error_errno(r, "Internal pager failed: %m"); - _exit(EXIT_FAILURE); - } - - _exit(EXIT_SUCCESS); -} - -int pager_open(bool no_pager, bool jump_to_end) { - _cleanup_close_pair_ int fd[2] = { -1, -1 }; - const char *pager; - pid_t parent_pid; - - if (no_pager) - return 0; - - if (pager_pid > 0) - return 1; - - if (!on_tty()) - return 0; - - pager = getenv("SYSTEMD_PAGER"); - if (!pager) - pager = getenv("PAGER"); - - /* If the pager is explicitly turned off, honour it */ - if (pager && (pager[0] == 0 || streq(pager, "cat"))) - return 0; - - /* Determine and cache number of columns before we spawn the - * pager so that we get the value from the actual tty */ - (void) columns(); - - if (pipe(fd) < 0) - return log_error_errno(errno, "Failed to create pager pipe: %m"); - - parent_pid = getpid(); - - pager_pid = fork(); - if (pager_pid < 0) - return log_error_errno(errno, "Failed to fork pager: %m"); - - /* In the child start the pager */ - if (pager_pid == 0) { - const char* less_opts, *less_charset; - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - (void) dup2(fd[0], STDIN_FILENO); - safe_close_pair(fd); - - /* Initialize a good set of less options */ - less_opts = getenv("SYSTEMD_LESS"); - if (!less_opts) - less_opts = "FRSXMK"; - if (jump_to_end) - less_opts = strjoina(less_opts, " +G"); - setenv("LESS", less_opts, 1); - - /* Initialize a good charset for less. This is - * particularly important if we output UTF-8 - * characters. */ - less_charset = getenv("SYSTEMD_LESSCHARSET"); - if (!less_charset && is_locale_utf8()) - less_charset = "utf-8"; - if (less_charset) - setenv("LESSCHARSET", less_charset, 1); - - /* Make sure the pager goes away when the parent dies */ - if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) - _exit(EXIT_FAILURE); - - /* Check whether our parent died before we were able - * to set the death signal */ - if (getppid() != parent_pid) - _exit(EXIT_SUCCESS); - - if (pager) { - execlp(pager, pager, NULL); - execl("/bin/sh", "sh", "-c", pager, NULL); - } - - /* Debian's alternatives command for pagers is - * called 'pager'. Note that we do not call - * sensible-pagers here, since that is just a - * shell script that implements a logic that - * is similar to this one anyway, but is - * Debian-specific. */ - execlp("pager", "pager", NULL); - - execlp("less", "less", NULL); - execlp("more", "more", NULL); - - pager_fallback(); - /* not reached */ - } - - /* Return in the parent */ - if (dup2(fd[1], STDOUT_FILENO) < 0) - return log_error_errno(errno, "Failed to duplicate pager pipe: %m"); - if (dup2(fd[1], STDERR_FILENO) < 0) - return log_error_errno(errno, "Failed to duplicate pager pipe: %m"); - - return 1; -} - -void pager_close(void) { - - if (pager_pid <= 0) - return; - - /* Inform pager that we are done */ - stdout = safe_fclose(stdout); - stderr = safe_fclose(stderr); - - (void) kill(pager_pid, SIGCONT); - (void) wait_for_terminate(pager_pid, NULL); - pager_pid = 0; -} - -bool pager_have(void) { - return pager_pid > 0; -} - -int show_man_page(const char *desc, bool null_stdio) { - const char *args[4] = { "man", NULL, NULL, NULL }; - char *e = NULL; - pid_t pid; - size_t k; - int r; - siginfo_t status; - - k = strlen(desc); - - if (desc[k-1] == ')') - e = strrchr(desc, '('); - - if (e) { - char *page = NULL, *section = NULL; - - page = strndupa(desc, e - desc); - section = strndupa(e + 1, desc + k - e - 2); - - args[1] = section; - args[2] = page; - } else - args[1] = desc; - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork: %m"); - - if (pid == 0) { - /* Child */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - if (null_stdio) { - r = make_null_stdio(); - if (r < 0) { - log_error_errno(r, "Failed to kill stdio: %m"); - _exit(EXIT_FAILURE); - } - } - - execvp(args[0], (char**) args); - log_error_errno(errno, "Failed to execute man: %m"); - _exit(EXIT_FAILURE); - } - - r = wait_for_terminate(pid, &status); - if (r < 0) - return r; - - log_debug("Exit code %i status %i", status.si_code, status.si_status); - return status.si_status; -} diff --git a/src/shared/pager.h b/src/shared/pager.h deleted file mode 100644 index 893e1d2bb6..0000000000 --- a/src/shared/pager.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 "macro.h" - -int pager_open(bool no_pager, bool jump_to_end); -void pager_close(void); -bool pager_have(void) _pure_; - -int show_man_page(const char *page, bool null_stdio); diff --git a/src/shared/path-lookup.c b/src/shared/path-lookup.c deleted file mode 100644 index ca593b6963..0000000000 --- a/src/shared/path-lookup.c +++ /dev/null @@ -1,822 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include - -#include "alloc-util.h" -#include "install.h" -#include "log.h" -#include "macro.h" -#include "mkdir.h" -#include "path-lookup.h" -#include "path-util.h" -#include "rm-rf.h" -#include "stat-util.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -static int user_runtime_dir(char **ret, const char *suffix) { - const char *e; - char *j; - - assert(ret); - assert(suffix); - - e = getenv("XDG_RUNTIME_DIR"); - if (!e) - return -ENXIO; - - j = strappend(e, suffix); - if (!j) - return -ENOMEM; - - *ret = j; - return 0; -} - -static int user_config_dir(char **ret, const char *suffix) { - const char *e; - char *j; - - assert(ret); - - e = getenv("XDG_CONFIG_HOME"); - if (e) - j = strappend(e, suffix); - else { - const char *home; - - home = getenv("HOME"); - if (!home) - return -ENXIO; - - j = strjoin(home, "/.config", suffix, NULL); - } - - if (!j) - return -ENOMEM; - - *ret = j; - return 0; -} - -static int user_data_dir(char **ret, const char *suffix) { - const char *e; - char *j; - - assert(ret); - assert(suffix); - - /* We don't treat /etc/xdg/systemd here as the spec - * suggests because we assume that that is a link to - * /etc/systemd/ anyway. */ - - e = getenv("XDG_DATA_HOME"); - if (e) - j = strappend(e, suffix); - else { - const char *home; - - home = getenv("HOME"); - if (!home) - return -ENXIO; - - - j = strjoin(home, "/.local/share", suffix, NULL); - } - if (!j) - return -ENOMEM; - - *ret = j; - return 1; -} - -static char** user_dirs( - const char *persistent_config, - const char *runtime_config, - const char *generator, - const char *generator_early, - const char *generator_late, - const char *transient, - const char *persistent_control, - const char *runtime_control) { - - const char * const config_unit_paths[] = { - USER_CONFIG_UNIT_PATH, - "/etc/systemd/user", - NULL - }; - - const char * const data_unit_paths[] = { - "/usr/local/lib/systemd/user", - "/usr/local/share/systemd/user", - USER_DATA_UNIT_PATH, - "/usr/lib/systemd/user", - "/usr/share/systemd/user", - NULL - }; - - const char *e; - _cleanup_strv_free_ char **config_dirs = NULL, **data_dirs = NULL; - _cleanup_free_ char *data_home = NULL; - _cleanup_free_ char **res = NULL; - char **tmp; - int r; - - /* Implement the mechanisms defined in - * - * http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html - * - * We look in both the config and the data dirs because we - * want to encourage that distributors ship their unit files - * as data, and allow overriding as configuration. - */ - - e = getenv("XDG_CONFIG_DIRS"); - if (e) { - config_dirs = strv_split(e, ":"); - if (!config_dirs) - return NULL; - } - - r = user_data_dir(&data_home, "/systemd/user"); - if (r < 0 && r != -ENXIO) - return NULL; - - e = getenv("XDG_DATA_DIRS"); - if (e) - data_dirs = strv_split(e, ":"); - else - data_dirs = strv_new("/usr/local/share", - "/usr/share", - NULL); - if (!data_dirs) - return NULL; - - /* Now merge everything we found. */ - if (strv_extend(&res, persistent_control) < 0) - return NULL; - - if (strv_extend(&res, runtime_control) < 0) - return NULL; - - if (strv_extend(&res, transient) < 0) - return NULL; - - if (strv_extend(&res, generator_early) < 0) - return NULL; - - if (!strv_isempty(config_dirs)) - if (strv_extend_strv_concat(&res, config_dirs, "/systemd/user") < 0) - return NULL; - - if (strv_extend(&res, persistent_config) < 0) - return NULL; - - if (strv_extend_strv(&res, (char**) config_unit_paths, false) < 0) - return NULL; - - if (strv_extend(&res, runtime_config) < 0) - return NULL; - - if (strv_extend(&res, generator) < 0) - return NULL; - - if (strv_extend(&res, data_home) < 0) - return NULL; - - if (!strv_isempty(data_dirs)) - if (strv_extend_strv_concat(&res, data_dirs, "/systemd/user") < 0) - return NULL; - - if (strv_extend_strv(&res, (char**) data_unit_paths, false) < 0) - return NULL; - - if (strv_extend(&res, generator_late) < 0) - return NULL; - - if (path_strv_make_absolute_cwd(res) < 0) - return NULL; - - tmp = res; - res = NULL; - return tmp; -} - -static int acquire_generator_dirs( - UnitFileScope scope, - char **generator, - char **generator_early, - char **generator_late) { - - _cleanup_free_ char *x = NULL, *y = NULL, *z = NULL; - const char *prefix; - - assert(generator); - assert(generator_early); - assert(generator_late); - - switch (scope) { - - case UNIT_FILE_SYSTEM: - prefix = "/run/systemd/"; - break; - - case UNIT_FILE_USER: { - const char *e; - - e = getenv("XDG_RUNTIME_DIR"); - if (!e) - return -ENXIO; - - prefix = strjoina(e, "/systemd/"); - break; - } - - case UNIT_FILE_GLOBAL: - return -EOPNOTSUPP; - - default: - assert_not_reached("Hmm, unexpected scope value."); - } - - x = strappend(prefix, "generator"); - if (!x) - return -ENOMEM; - - y = strappend(prefix, "generator.early"); - if (!y) - return -ENOMEM; - - z = strappend(prefix, "generator.late"); - if (!z) - return -ENOMEM; - - *generator = x; - *generator_early = y; - *generator_late = z; - - x = y = z = NULL; - return 0; -} - -static int acquire_transient_dir(UnitFileScope scope, char **ret) { - assert(ret); - - switch (scope) { - - case UNIT_FILE_SYSTEM: { - char *transient; - - transient = strdup("/run/systemd/transient"); - if (!transient) - return -ENOMEM; - - *ret = transient; - return 0; - } - - case UNIT_FILE_USER: - return user_runtime_dir(ret, "/systemd/transient"); - - case UNIT_FILE_GLOBAL: - return -EOPNOTSUPP; - - default: - assert_not_reached("Hmm, unexpected scope value."); - } -} - -static int acquire_config_dirs(UnitFileScope scope, char **persistent, char **runtime) { - _cleanup_free_ char *a = NULL, *b = NULL; - int r; - - assert(persistent); - assert(runtime); - - switch (scope) { - - case UNIT_FILE_SYSTEM: - a = strdup(SYSTEM_CONFIG_UNIT_PATH); - b = strdup("/run/systemd/system"); - break; - - case UNIT_FILE_GLOBAL: - a = strdup(USER_CONFIG_UNIT_PATH); - b = strdup("/run/systemd/user"); - break; - - case UNIT_FILE_USER: - r = user_config_dir(&a, "/systemd/user"); - if (r < 0) - return r; - - r = user_runtime_dir(runtime, "/systemd/user"); - if (r < 0) - return r; - - *persistent = a; - a = NULL; - - return 0; - - default: - assert_not_reached("Hmm, unexpected scope value."); - } - - if (!a || !b) - return -ENOMEM; - - *persistent = a; - *runtime = b; - a = b = NULL; - - return 0; -} - -static int acquire_control_dirs(UnitFileScope scope, char **persistent, char **runtime) { - _cleanup_free_ char *a = NULL; - int r; - - assert(persistent); - assert(runtime); - - switch (scope) { - - case UNIT_FILE_SYSTEM: { - _cleanup_free_ char *b = NULL; - - a = strdup("/etc/systemd/system.control"); - if (!a) - return -ENOMEM; - - b = strdup("/run/systemd/system.control"); - if (!b) - return -ENOMEM; - - *runtime = b; - b = NULL; - - break; - } - - case UNIT_FILE_USER: - r = user_config_dir(&a, "/systemd/system.control"); - if (r < 0) - return r; - - r = user_runtime_dir(runtime, "/systemd/system.control"); - if (r < 0) - return r; - - break; - - case UNIT_FILE_GLOBAL: - return -EOPNOTSUPP; - - default: - assert_not_reached("Hmm, unexpected scope value."); - } - - *persistent = a; - a = NULL; - - return 0; -} - -static int patch_root_prefix(char **p, const char *root_dir) { - char *c; - - assert(p); - - if (!*p) - return 0; - - c = prefix_root(root_dir, *p); - if (!c) - return -ENOMEM; - - free(*p); - *p = c; - - return 0; -} - -static int patch_root_prefix_strv(char **l, const char *root_dir) { - char **i; - int r; - - if (!root_dir) - return 0; - - STRV_FOREACH(i, l) { - r = patch_root_prefix(i, root_dir); - if (r < 0) - return r; - } - - return 0; -} - -int lookup_paths_init( - LookupPaths *p, - UnitFileScope scope, - LookupPathsFlags flags, - const char *root_dir) { - - _cleanup_free_ char - *root = NULL, - *persistent_config = NULL, *runtime_config = NULL, - *generator = NULL, *generator_early = NULL, *generator_late = NULL, - *transient = NULL, - *persistent_control = NULL, *runtime_control = NULL; - bool append = false; /* Add items from SYSTEMD_UNIT_PATH before normal directories */ - _cleanup_strv_free_ char **paths = NULL; - const char *e; - int r; - - assert(p); - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - - if (!isempty(root_dir) && !path_equal(root_dir, "/")) { - if (scope == UNIT_FILE_USER) - return -EINVAL; - - r = is_dir(root_dir, true); - if (r < 0) - return r; - if (r == 0) - return -ENOTDIR; - - root = strdup(root_dir); - if (!root) - return -ENOMEM; - } - - r = acquire_config_dirs(scope, &persistent_config, &runtime_config); - if (r < 0 && r != -ENXIO) - return r; - - if ((flags & LOOKUP_PATHS_EXCLUDE_GENERATED) == 0) { - r = acquire_generator_dirs(scope, &generator, &generator_early, &generator_late); - if (r < 0 && r != -EOPNOTSUPP && r != -ENXIO) - return r; - } - - r = acquire_transient_dir(scope, &transient); - if (r < 0 && r != -EOPNOTSUPP && r != -ENXIO) - return r; - - r = acquire_control_dirs(scope, &persistent_control, &runtime_control); - if (r < 0 && r != -EOPNOTSUPP && r != -ENXIO) - return r; - - /* First priority is whatever has been passed to us via env vars */ - e = getenv("SYSTEMD_UNIT_PATH"); - if (e) { - const char *k; - - k = endswith(e, ":"); - if (k) { - e = strndupa(e, k - e); - append = true; - } - - /* FIXME: empty components in other places should be - * rejected. */ - - r = path_split_and_make_absolute(e, &paths); - if (r < 0) - return r; - } - - if (!paths || append) { - /* Let's figure something out. */ - - _cleanup_strv_free_ char **add = NULL; - - /* For the user units we include share/ in the search - * path in order to comply with the XDG basedir spec. - * For the system stuff we avoid such nonsense. OTOH - * we include /lib in the search path for the system - * stuff but avoid it for user stuff. */ - - switch (scope) { - - case UNIT_FILE_SYSTEM: - add = strv_new( - /* If you modify this you also want to modify - * systemdsystemunitpath= in systemd.pc.in! */ - STRV_IFNOTNULL(persistent_control), - STRV_IFNOTNULL(runtime_control), - STRV_IFNOTNULL(transient), - STRV_IFNOTNULL(generator_early), - persistent_config, - SYSTEM_CONFIG_UNIT_PATH, - "/etc/systemd/system", - runtime_config, - "/run/systemd/system", - STRV_IFNOTNULL(generator), - "/usr/local/lib/systemd/system", - SYSTEM_DATA_UNIT_PATH, - "/usr/lib/systemd/system", -#ifdef HAVE_SPLIT_USR - "/lib/systemd/system", -#endif - STRV_IFNOTNULL(generator_late), - NULL); - break; - - case UNIT_FILE_GLOBAL: - add = strv_new( - /* If you modify this you also want to modify - * systemduserunitpath= in systemd.pc.in, and - * the arrays in user_dirs() above! */ - STRV_IFNOTNULL(persistent_control), - STRV_IFNOTNULL(runtime_control), - STRV_IFNOTNULL(transient), - STRV_IFNOTNULL(generator_early), - persistent_config, - USER_CONFIG_UNIT_PATH, - "/etc/systemd/user", - runtime_config, - "/run/systemd/user", - STRV_IFNOTNULL(generator), - "/usr/local/lib/systemd/user", - "/usr/local/share/systemd/user", - USER_DATA_UNIT_PATH, - "/usr/lib/systemd/user", - "/usr/share/systemd/user", - STRV_IFNOTNULL(generator_late), - NULL); - break; - - case UNIT_FILE_USER: - add = user_dirs(persistent_config, runtime_config, - generator, generator_early, generator_late, - transient, - persistent_config, runtime_control); - break; - - default: - assert_not_reached("Hmm, unexpected scope?"); - } - - if (!add) - return -ENOMEM; - - if (paths) { - r = strv_extend_strv(&paths, add, true); - if (r < 0) - return r; - } else { - /* Small optimization: if paths is NULL (and it usually is), we can simply assign 'add' to it, - * and don't have to copy anything */ - paths = add; - add = NULL; - } - } - - r = patch_root_prefix(&persistent_config, root); - if (r < 0) - return r; - r = patch_root_prefix(&runtime_config, root); - if (r < 0) - return r; - - r = patch_root_prefix(&generator, root); - if (r < 0) - return r; - r = patch_root_prefix(&generator_early, root); - if (r < 0) - return r; - r = patch_root_prefix(&generator_late, root); - if (r < 0) - return r; - - r = patch_root_prefix(&transient, root); - if (r < 0) - return r; - - r = patch_root_prefix(&persistent_control, root); - if (r < 0) - return r; - - r = patch_root_prefix(&runtime_control, root); - if (r < 0) - return r; - - r = patch_root_prefix_strv(paths, root); - if (r < 0) - return -ENOMEM; - - p->search_path = strv_uniq(paths); - paths = NULL; - - p->persistent_config = persistent_config; - p->runtime_config = runtime_config; - persistent_config = runtime_config = NULL; - - p->generator = generator; - p->generator_early = generator_early; - p->generator_late = generator_late; - generator = generator_early = generator_late = NULL; - - p->transient = transient; - transient = NULL; - - p->persistent_control = persistent_control; - p->runtime_control = runtime_control; - persistent_control = runtime_control = NULL; - - p->root_dir = root; - root = NULL; - - return 0; -} - -void lookup_paths_free(LookupPaths *p) { - if (!p) - return; - - p->search_path = strv_free(p->search_path); - - p->persistent_config = mfree(p->persistent_config); - p->runtime_config = mfree(p->runtime_config); - - p->generator = mfree(p->generator); - p->generator_early = mfree(p->generator_early); - p->generator_late = mfree(p->generator_late); - - p->transient = mfree(p->transient); - - p->persistent_control = mfree(p->persistent_control); - p->runtime_control = mfree(p->runtime_control); - - p->root_dir = mfree(p->root_dir); -} - -int lookup_paths_reduce(LookupPaths *p) { - _cleanup_free_ struct stat *stats = NULL; - size_t n_stats = 0, allocated = 0; - unsigned c = 0; - int r; - - assert(p); - - /* Drop duplicates and non-existing directories from the search path. We figure out whether two directories are - * the same by comparing their device and inode numbers. Note one special tweak: when we have a root path set, - * we do not follow symlinks when retrieving them, because the kernel wouldn't take the root prefix into - * account when following symlinks. When we have no root path set this restriction does not apply however. */ - - if (!p->search_path) - return 0; - - while (p->search_path[c]) { - struct stat st; - unsigned k; - - if (p->root_dir) - r = lstat(p->search_path[c], &st); - else - r = stat(p->search_path[c], &st); - if (r < 0) { - if (errno == ENOENT) - goto remove_item; - - /* If something we don't grok happened, let's better leave it in. */ - log_debug_errno(errno, "Failed to stat %s: %m", p->search_path[c]); - c++; - continue; - } - - for (k = 0; k < n_stats; k++) { - if (stats[k].st_dev == st.st_dev && - stats[k].st_ino == st.st_ino) - break; - } - - if (k < n_stats) /* Is there already an entry with the same device/inode? */ - goto remove_item; - - if (!GREEDY_REALLOC(stats, allocated, n_stats+1)) - return -ENOMEM; - - stats[n_stats++] = st; - c++; - continue; - - remove_item: - free(p->search_path[c]); - memmove(p->search_path + c, - p->search_path + c + 1, - (strv_length(p->search_path + c + 1) + 1) * sizeof(char*)); - } - - if (strv_isempty(p->search_path)) { - log_debug("Ignoring unit files."); - p->search_path = strv_free(p->search_path); - } else { - _cleanup_free_ char *t; - - t = strv_join(p->search_path, "\n\t"); - if (!t) - return -ENOMEM; - - log_debug("Looking for unit files in (higher priority first):\n\t%s", t); - } - - return 0; -} - -int lookup_paths_mkdir_generator(LookupPaths *p) { - int r, q; - - assert(p); - - if (!p->generator || !p->generator_early || !p->generator_late) - return -EINVAL; - - r = mkdir_p_label(p->generator, 0755); - - q = mkdir_p_label(p->generator_early, 0755); - if (q < 0 && r >= 0) - r = q; - - q = mkdir_p_label(p->generator_late, 0755); - if (q < 0 && r >= 0) - r = q; - - return r; -} - -void lookup_paths_trim_generator(LookupPaths *p) { - assert(p); - - /* Trim empty dirs */ - - if (p->generator) - (void) rmdir(p->generator); - if (p->generator_early) - (void) rmdir(p->generator_early); - if (p->generator_late) - (void) rmdir(p->generator_late); -} - -void lookup_paths_flush_generator(LookupPaths *p) { - assert(p); - - /* Flush the generated unit files in full */ - - if (p->generator) - (void) rm_rf(p->generator, REMOVE_ROOT); - if (p->generator_early) - (void) rm_rf(p->generator_early, REMOVE_ROOT); - if (p->generator_late) - (void) rm_rf(p->generator_late, REMOVE_ROOT); -} - -char **generator_binary_paths(UnitFileScope scope) { - - switch (scope) { - - case UNIT_FILE_SYSTEM: - return strv_new("/run/systemd/system-generators", - "/etc/systemd/system-generators", - "/usr/local/lib/systemd/system-generators", - SYSTEM_GENERATOR_PATH, - NULL); - - case UNIT_FILE_GLOBAL: - case UNIT_FILE_USER: - return strv_new("/run/systemd/user-generators", - "/etc/systemd/user-generators", - "/usr/local/lib/systemd/user-generators", - USER_GENERATOR_PATH, - NULL); - - default: - assert_not_reached("Hmm, unexpected scope."); - } -} diff --git a/src/shared/path-lookup.h b/src/shared/path-lookup.h deleted file mode 100644 index f9bb2fe237..0000000000 --- a/src/shared/path-lookup.h +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 - -typedef struct LookupPaths LookupPaths; - -#include "install.h" -#include "macro.h" - -typedef enum LookupPathsFlags { - LOOKUP_PATHS_EXCLUDE_GENERATED = 1, -} LookupPathsFlags; - -struct LookupPaths { - /* Where we look for unit files. This includes the individual special paths below, but also any vendor - * supplied, static unit file paths. */ - char **search_path; - - /* Where we shall create or remove our installation symlinks, aka "configuration", and where the user/admin - * shall place his own unit files. */ - char *persistent_config; - char *runtime_config; - - /* Where to place generated unit files (i.e. those a "generator" tool generated). Note the special semantics of - * this directory: the generators are flushed each time a "systemctl daemon-reload" is issued. The user should - * not alter these directories directly. */ - char *generator; - char *generator_early; - char *generator_late; - - /* Where to place transient unit files (i.e. those created dynamically via the bus API). Note the special - * semantics of this directory: all units created transiently have their unit files removed as the transient - * unit is unloaded. The user should not alter this directory directly. */ - char *transient; - - /* Where the snippets created by "systemctl set-property" are placed. Note that for transient units, the - * snippets are placed in the transient directory though (see above). The user should not alter this directory - * directly. */ - char *persistent_control; - char *runtime_control; - - /* The root directory prepended to all items above, or NULL */ - char *root_dir; -}; - -int lookup_paths_init(LookupPaths *p, UnitFileScope scope, LookupPathsFlags flags, const char *root_dir); - -int lookup_paths_reduce(LookupPaths *p); - -int lookup_paths_mkdir_generator(LookupPaths *p); -void lookup_paths_trim_generator(LookupPaths *p); -void lookup_paths_flush_generator(LookupPaths *p); - -void lookup_paths_free(LookupPaths *p); -#define _cleanup_lookup_paths_free_ _cleanup_(lookup_paths_free) - -char **generator_binary_paths(UnitFileScope scope); diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c deleted file mode 100644 index 02c03b98d8..0000000000 --- a/src/shared/ptyfwd.c +++ /dev/null @@ -1,484 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010-2013 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-event.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "log.h" -#include "macro.h" -#include "ptyfwd.h" -#include "time-util.h" - -struct PTYForward { - sd_event *event; - - int master; - - PTYForwardFlags flags; - - sd_event_source *stdin_event_source; - sd_event_source *stdout_event_source; - sd_event_source *master_event_source; - - sd_event_source *sigwinch_event_source; - - struct termios saved_stdin_attr; - struct termios saved_stdout_attr; - - bool saved_stdin:1; - bool saved_stdout:1; - - bool stdin_readable:1; - bool stdin_hangup:1; - bool stdout_writable:1; - bool stdout_hangup:1; - bool master_readable:1; - bool master_writable:1; - bool master_hangup:1; - - bool read_from_master:1; - - bool last_char_set:1; - char last_char; - - char in_buffer[LINE_MAX], out_buffer[LINE_MAX]; - size_t in_buffer_full, out_buffer_full; - - usec_t escape_timestamp; - unsigned escape_counter; -}; - -#define ESCAPE_USEC (1*USEC_PER_SEC) - -static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) { - const char *p; - - assert(f); - assert(buffer); - assert(n > 0); - - for (p = buffer; p < buffer + n; p++) { - - /* Check for ^] */ - if (*p == 0x1D) { - usec_t nw = now(CLOCK_MONOTONIC); - - if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) { - f->escape_timestamp = nw; - f->escape_counter = 1; - } else { - (f->escape_counter)++; - - if (f->escape_counter >= 3) - return true; - } - } else { - f->escape_timestamp = 0; - f->escape_counter = 0; - } - } - - return false; -} - -static bool ignore_vhangup(PTYForward *f) { - assert(f); - - if (f->flags & PTY_FORWARD_IGNORE_VHANGUP) - return true; - - if ((f->flags & PTY_FORWARD_IGNORE_INITIAL_VHANGUP) && !f->read_from_master) - return true; - - return false; -} - -static int shovel(PTYForward *f) { - ssize_t k; - - assert(f); - - while ((f->stdin_readable && f->in_buffer_full <= 0) || - (f->master_writable && f->in_buffer_full > 0) || - (f->master_readable && f->out_buffer_full <= 0) || - (f->stdout_writable && f->out_buffer_full > 0)) { - - if (f->stdin_readable && f->in_buffer_full < LINE_MAX) { - - k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full); - if (k < 0) { - - if (errno == EAGAIN) - f->stdin_readable = false; - else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) { - f->stdin_readable = false; - f->stdin_hangup = true; - - f->stdin_event_source = sd_event_source_unref(f->stdin_event_source); - } else { - log_error_errno(errno, "read(): %m"); - return sd_event_exit(f->event, EXIT_FAILURE); - } - } else if (k == 0) { - /* EOF on stdin */ - f->stdin_readable = false; - f->stdin_hangup = true; - - f->stdin_event_source = sd_event_source_unref(f->stdin_event_source); - } else { - /* Check if ^] has been - * pressed three times within - * one second. If we get this - * we quite immediately. */ - if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k)) - return sd_event_exit(f->event, EXIT_FAILURE); - - f->in_buffer_full += (size_t) k; - } - } - - if (f->master_writable && f->in_buffer_full > 0) { - - k = write(f->master, f->in_buffer, f->in_buffer_full); - if (k < 0) { - - if (errno == EAGAIN || errno == EIO) - f->master_writable = false; - else if (errno == EPIPE || errno == ECONNRESET) { - f->master_writable = f->master_readable = false; - f->master_hangup = true; - - f->master_event_source = sd_event_source_unref(f->master_event_source); - } else { - log_error_errno(errno, "write(): %m"); - return sd_event_exit(f->event, EXIT_FAILURE); - } - } else { - assert(f->in_buffer_full >= (size_t) k); - memmove(f->in_buffer, f->in_buffer + k, f->in_buffer_full - k); - f->in_buffer_full -= k; - } - } - - if (f->master_readable && f->out_buffer_full < LINE_MAX) { - - k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full); - if (k < 0) { - - /* Note that EIO on the master device - * might be caused by vhangup() or - * temporary closing of everything on - * the other side, we treat it like - * EAGAIN here and try again, unless - * ignore_vhangup is off. */ - - if (errno == EAGAIN || (errno == EIO && ignore_vhangup(f))) - f->master_readable = false; - else if (errno == EPIPE || errno == ECONNRESET || errno == EIO) { - f->master_readable = f->master_writable = false; - f->master_hangup = true; - - f->master_event_source = sd_event_source_unref(f->master_event_source); - } else { - log_error_errno(errno, "read(): %m"); - return sd_event_exit(f->event, EXIT_FAILURE); - } - } else { - f->read_from_master = true; - f->out_buffer_full += (size_t) k; - } - } - - if (f->stdout_writable && f->out_buffer_full > 0) { - - k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full); - if (k < 0) { - - if (errno == EAGAIN) - f->stdout_writable = false; - else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) { - f->stdout_writable = false; - f->stdout_hangup = true; - f->stdout_event_source = sd_event_source_unref(f->stdout_event_source); - } else { - log_error_errno(errno, "write(): %m"); - return sd_event_exit(f->event, EXIT_FAILURE); - } - - } else { - - if (k > 0) { - f->last_char = f->out_buffer[k-1]; - f->last_char_set = true; - } - - assert(f->out_buffer_full >= (size_t) k); - memmove(f->out_buffer, f->out_buffer + k, f->out_buffer_full - k); - f->out_buffer_full -= k; - } - } - } - - if (f->stdin_hangup || f->stdout_hangup || f->master_hangup) { - /* Exit the loop if any side hung up and if there's - * nothing more to write or nothing we could write. */ - - if ((f->out_buffer_full <= 0 || f->stdout_hangup) && - (f->in_buffer_full <= 0 || f->master_hangup)) - return sd_event_exit(f->event, EXIT_SUCCESS); - } - - return 0; -} - -static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) { - PTYForward *f = userdata; - - assert(f); - assert(e); - assert(e == f->master_event_source); - assert(fd >= 0); - assert(fd == f->master); - - if (revents & (EPOLLIN|EPOLLHUP)) - f->master_readable = true; - - if (revents & (EPOLLOUT|EPOLLHUP)) - f->master_writable = true; - - return shovel(f); -} - -static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) { - PTYForward *f = userdata; - - assert(f); - assert(e); - assert(e == f->stdin_event_source); - assert(fd >= 0); - assert(fd == STDIN_FILENO); - - if (revents & (EPOLLIN|EPOLLHUP)) - f->stdin_readable = true; - - return shovel(f); -} - -static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) { - PTYForward *f = userdata; - - assert(f); - assert(e); - assert(e == f->stdout_event_source); - assert(fd >= 0); - assert(fd == STDOUT_FILENO); - - if (revents & (EPOLLOUT|EPOLLHUP)) - f->stdout_writable = true; - - return shovel(f); -} - -static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) { - PTYForward *f = userdata; - struct winsize ws; - - assert(f); - assert(e); - assert(e == f->sigwinch_event_source); - - /* The window size changed, let's forward that. */ - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0) - (void) ioctl(f->master, TIOCSWINSZ, &ws); - - return 0; -} - -int pty_forward_new( - sd_event *event, - int master, - PTYForwardFlags flags, - PTYForward **ret) { - - _cleanup_(pty_forward_freep) PTYForward *f = NULL; - struct winsize ws; - int r; - - f = new0(PTYForward, 1); - if (!f) - return -ENOMEM; - - f->flags = flags; - - if (event) - f->event = sd_event_ref(event); - else { - r = sd_event_default(&f->event); - if (r < 0) - return r; - } - - if (!(flags & PTY_FORWARD_READ_ONLY)) { - r = fd_nonblock(STDIN_FILENO, true); - if (r < 0) - return r; - - r = fd_nonblock(STDOUT_FILENO, true); - if (r < 0) - return r; - } - - r = fd_nonblock(master, true); - if (r < 0) - return r; - - f->master = master; - - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0) - (void) ioctl(master, TIOCSWINSZ, &ws); - - if (!(flags & PTY_FORWARD_READ_ONLY)) { - if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) { - struct termios raw_stdin_attr; - - f->saved_stdin = true; - - raw_stdin_attr = f->saved_stdin_attr; - cfmakeraw(&raw_stdin_attr); - raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag; - tcsetattr(STDIN_FILENO, TCSANOW, &raw_stdin_attr); - } - - if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) { - struct termios raw_stdout_attr; - - f->saved_stdout = true; - - raw_stdout_attr = f->saved_stdout_attr; - cfmakeraw(&raw_stdout_attr); - raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag; - raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag; - tcsetattr(STDOUT_FILENO, TCSANOW, &raw_stdout_attr); - } - - r = sd_event_add_io(f->event, &f->stdin_event_source, STDIN_FILENO, EPOLLIN|EPOLLET, on_stdin_event, f); - if (r < 0 && r != -EPERM) - return r; - } - - r = sd_event_add_io(f->event, &f->stdout_event_source, STDOUT_FILENO, EPOLLOUT|EPOLLET, on_stdout_event, f); - if (r == -EPERM) - /* stdout without epoll support. Likely redirected to regular file. */ - f->stdout_writable = true; - else if (r < 0) - return r; - - r = sd_event_add_io(f->event, &f->master_event_source, master, EPOLLIN|EPOLLOUT|EPOLLET, on_master_event, f); - if (r < 0) - return r; - - r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f); - if (r < 0) - return r; - - *ret = f; - f = NULL; - - return 0; -} - -PTYForward *pty_forward_free(PTYForward *f) { - - if (f) { - sd_event_source_unref(f->stdin_event_source); - sd_event_source_unref(f->stdout_event_source); - sd_event_source_unref(f->master_event_source); - sd_event_source_unref(f->sigwinch_event_source); - sd_event_unref(f->event); - - if (f->saved_stdout) - tcsetattr(STDOUT_FILENO, TCSANOW, &f->saved_stdout_attr); - if (f->saved_stdin) - tcsetattr(STDIN_FILENO, TCSANOW, &f->saved_stdin_attr); - - free(f); - } - - /* STDIN/STDOUT should not be nonblocking normally, so let's - * unconditionally reset it */ - fd_nonblock(STDIN_FILENO, false); - fd_nonblock(STDOUT_FILENO, false); - - return NULL; -} - -int pty_forward_get_last_char(PTYForward *f, char *ch) { - assert(f); - assert(ch); - - if (!f->last_char_set) - return -ENXIO; - - *ch = f->last_char; - return 0; -} - -int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) { - int r; - - assert(f); - - if (!!(f->flags & PTY_FORWARD_IGNORE_VHANGUP) == b) - return 0; - - SET_FLAG(f->flags, PTY_FORWARD_IGNORE_VHANGUP, b); - - if (!ignore_vhangup(f)) { - - /* We shall now react to vhangup()s? Let's check - * immediately if we might be in one */ - - f->master_readable = true; - r = shovel(f); - if (r < 0) - return r; - } - - return 0; -} - -int pty_forward_get_ignore_vhangup(PTYForward *f) { - assert(f); - - return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP); -} diff --git a/src/shared/ptyfwd.h b/src/shared/ptyfwd.h deleted file mode 100644 index a046eb4e5e..0000000000 --- a/src/shared/ptyfwd.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010-2013 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 "sd-event.h" - -#include "macro.h" - -typedef struct PTYForward PTYForward; - -typedef enum PTYForwardFlags { - PTY_FORWARD_READ_ONLY = 1, - - /* Continue reading after hangup? */ - PTY_FORWARD_IGNORE_VHANGUP = 2, - - /* Continue reading after hangup but only if we never read anything else? */ - PTY_FORWARD_IGNORE_INITIAL_VHANGUP = 4, -} PTYForwardFlags; - -int pty_forward_new(sd_event *event, int master, PTYForwardFlags flags, PTYForward **f); -PTYForward *pty_forward_free(PTYForward *f); - -int pty_forward_get_last_char(PTYForward *f, char *ch); - -int pty_forward_set_ignore_vhangup(PTYForward *f, bool ignore_vhangup); -int pty_forward_get_ignore_vhangup(PTYForward *f); - -DEFINE_TRIVIAL_CLEANUP_FUNC(PTYForward*, pty_forward_free); diff --git a/src/shared/resolve-util.c b/src/shared/resolve-util.c deleted file mode 100644 index e2da81bab7..0000000000 --- a/src/shared/resolve-util.c +++ /dev/null @@ -1,39 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2016 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 "conf-parser.h" -#include "resolve-util.h" -#include "string-table.h" - -DEFINE_CONFIG_PARSE_ENUM(config_parse_resolve_support, resolve_support, ResolveSupport, "Failed to parse resolve support setting"); -DEFINE_CONFIG_PARSE_ENUM(config_parse_dnssec_mode, dnssec_mode, DnssecMode, "Failed to parse DNSSEC mode setting"); - -static const char* const resolve_support_table[_RESOLVE_SUPPORT_MAX] = { - [RESOLVE_SUPPORT_NO] = "no", - [RESOLVE_SUPPORT_YES] = "yes", - [RESOLVE_SUPPORT_RESOLVE] = "resolve", -}; -DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(resolve_support, ResolveSupport, RESOLVE_SUPPORT_YES); - -static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = { - [DNSSEC_NO] = "no", - [DNSSEC_ALLOW_DOWNGRADE] = "allow-downgrade", - [DNSSEC_YES] = "yes", -}; -DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dnssec_mode, DnssecMode, DNSSEC_YES); diff --git a/src/shared/resolve-util.h b/src/shared/resolve-util.h deleted file mode 100644 index 8636a6c134..0000000000 --- a/src/shared/resolve-util.h +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2016 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 "macro.h" - -typedef enum ResolveSupport ResolveSupport; -typedef enum DnssecMode DnssecMode; - -enum ResolveSupport { - RESOLVE_SUPPORT_NO, - RESOLVE_SUPPORT_YES, - RESOLVE_SUPPORT_RESOLVE, - _RESOLVE_SUPPORT_MAX, - _RESOLVE_SUPPORT_INVALID = -1 -}; - -enum DnssecMode { - /* No DNSSEC validation is done */ - DNSSEC_NO, - - /* Validate locally, if the server knows DO, but if not, - * don't. Don't trust the AD bit. If the server doesn't do - * DNSSEC properly, downgrade to non-DNSSEC operation. Of - * course, we then are vulnerable to a downgrade attack, but - * that's life and what is configured. */ - DNSSEC_ALLOW_DOWNGRADE, - - /* Insist on DNSSEC server support, and rather fail than downgrading. */ - DNSSEC_YES, - - _DNSSEC_MODE_MAX, - _DNSSEC_MODE_INVALID = -1 -}; - -int config_parse_resolve_support(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); -int config_parse_dnssec_mode(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); - -const char* resolve_support_to_string(ResolveSupport p) _const_; -ResolveSupport resolve_support_from_string(const char *s) _pure_; - -const char* dnssec_mode_to_string(DnssecMode p) _const_; -DnssecMode dnssec_mode_from_string(const char *s) _pure_; diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c deleted file mode 100644 index cebe0fce2a..0000000000 --- a/src/shared/seccomp-util.c +++ /dev/null @@ -1,90 +0,0 @@ -/*** - 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 -#include - -#include "macro.h" -#include "seccomp-util.h" -#include "string-util.h" - -const char* seccomp_arch_to_string(uint32_t c) { - - if (c == SCMP_ARCH_NATIVE) - return "native"; - if (c == SCMP_ARCH_X86) - return "x86"; - if (c == SCMP_ARCH_X86_64) - return "x86-64"; - if (c == SCMP_ARCH_X32) - return "x32"; - if (c == SCMP_ARCH_ARM) - return "arm"; - - return NULL; -} - -int seccomp_arch_from_string(const char *n, uint32_t *ret) { - if (!n) - return -EINVAL; - - assert(ret); - - if (streq(n, "native")) - *ret = SCMP_ARCH_NATIVE; - else if (streq(n, "x86")) - *ret = SCMP_ARCH_X86; - else if (streq(n, "x86-64")) - *ret = SCMP_ARCH_X86_64; - else if (streq(n, "x32")) - *ret = SCMP_ARCH_X32; - else if (streq(n, "arm")) - *ret = SCMP_ARCH_ARM; - else - return -EINVAL; - - return 0; -} - -int seccomp_add_secondary_archs(scmp_filter_ctx *c) { - -#if defined(__i386__) || defined(__x86_64__) - int r; - - /* Add in all possible secondary archs we are aware of that - * this kernel might support. */ - - r = seccomp_arch_add(c, SCMP_ARCH_X86); - if (r < 0 && r != -EEXIST) - return r; - - r = seccomp_arch_add(c, SCMP_ARCH_X86_64); - if (r < 0 && r != -EEXIST) - return r; - - r = seccomp_arch_add(c, SCMP_ARCH_X32); - if (r < 0 && r != -EEXIST) - return r; - -#endif - - return 0; - -} diff --git a/src/shared/seccomp-util.h b/src/shared/seccomp-util.h deleted file mode 100644 index 4ed2afc1b2..0000000000 --- a/src/shared/seccomp-util.h +++ /dev/null @@ -1,28 +0,0 @@ -#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 -#include - -const char* seccomp_arch_to_string(uint32_t c); -int seccomp_arch_from_string(const char *n, uint32_t *ret); - -int seccomp_add_secondary_archs(scmp_filter_ctx *c); diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c deleted file mode 100644 index f00624d0f2..0000000000 --- a/src/shared/sleep-config.c +++ /dev/null @@ -1,278 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 Zbigniew Jędrzejewski-Szmek - - 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 -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "conf-parser.h" -#include "def.h" -#include "env-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "sleep-config.h" -#include "string-util.h" -#include "strv.h" - -#define USE(x, y) do { (x) = (y); (y) = NULL; } while (0) - -int parse_sleep_config(const char *verb, char ***_modes, char ***_states) { - - _cleanup_strv_free_ char - **suspend_mode = NULL, **suspend_state = NULL, - **hibernate_mode = NULL, **hibernate_state = NULL, - **hybrid_mode = NULL, **hybrid_state = NULL; - char **modes, **states; - - const ConfigTableItem items[] = { - { "Sleep", "SuspendMode", config_parse_strv, 0, &suspend_mode }, - { "Sleep", "SuspendState", config_parse_strv, 0, &suspend_state }, - { "Sleep", "HibernateMode", config_parse_strv, 0, &hibernate_mode }, - { "Sleep", "HibernateState", config_parse_strv, 0, &hibernate_state }, - { "Sleep", "HybridSleepMode", config_parse_strv, 0, &hybrid_mode }, - { "Sleep", "HybridSleepState", config_parse_strv, 0, &hybrid_state }, - {} - }; - - config_parse_many(PKGSYSCONFDIR "/sleep.conf", - CONF_PATHS_NULSTR("systemd/sleep.conf.d"), - "Sleep\0", config_item_table_lookup, items, - false, NULL); - - if (streq(verb, "suspend")) { - /* empty by default */ - USE(modes, suspend_mode); - - if (suspend_state) - USE(states, suspend_state); - else - states = strv_new("mem", "standby", "freeze", NULL); - - } else if (streq(verb, "hibernate")) { - if (hibernate_mode) - USE(modes, hibernate_mode); - else - modes = strv_new("platform", "shutdown", NULL); - - if (hibernate_state) - USE(states, hibernate_state); - else - states = strv_new("disk", NULL); - - } else if (streq(verb, "hybrid-sleep")) { - if (hybrid_mode) - USE(modes, hybrid_mode); - else - modes = strv_new("suspend", "platform", "shutdown", NULL); - - if (hybrid_state) - USE(states, hybrid_state); - else - states = strv_new("disk", NULL); - - } else - assert_not_reached("what verb"); - - if ((!modes && !streq(verb, "suspend")) || !states) { - strv_free(modes); - strv_free(states); - return log_oom(); - } - - *_modes = modes; - *_states = states; - return 0; -} - -int can_sleep_state(char **types) { - char **type; - int r; - _cleanup_free_ char *p = NULL; - - if (strv_isempty(types)) - return true; - - /* If /sys is read-only we cannot sleep */ - if (access("/sys/power/state", W_OK) < 0) - return false; - - r = read_one_line_file("/sys/power/state", &p); - if (r < 0) - return false; - - STRV_FOREACH(type, types) { - const char *word, *state; - size_t l, k; - - k = strlen(*type); - FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) - if (l == k && memcmp(word, *type, l) == 0) - return true; - } - - return false; -} - -int can_sleep_disk(char **types) { - char **type; - int r; - _cleanup_free_ char *p = NULL; - - if (strv_isempty(types)) - return true; - - /* If /sys is read-only we cannot sleep */ - if (access("/sys/power/disk", W_OK) < 0) - return false; - - r = read_one_line_file("/sys/power/disk", &p); - if (r < 0) - return false; - - STRV_FOREACH(type, types) { - const char *word, *state; - size_t l, k; - - k = strlen(*type); - FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) { - if (l == k && memcmp(word, *type, l) == 0) - return true; - - if (l == k + 2 && - word[0] == '[' && - memcmp(word + 1, *type, l - 2) == 0 && - word[l-1] == ']') - return true; - } - } - - return false; -} - -#define HIBERNATION_SWAP_THRESHOLD 0.98 - -static int hibernation_partition_size(size_t *size, size_t *used) { - _cleanup_fclose_ FILE *f; - unsigned i; - - assert(size); - assert(used); - - f = fopen("/proc/swaps", "re"); - if (!f) { - log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, - "Failed to retrieve open /proc/swaps: %m"); - assert(errno > 0); - return -errno; - } - - (void) fscanf(f, "%*s %*s %*s %*s %*s\n"); - - for (i = 1;; i++) { - _cleanup_free_ char *dev = NULL, *type = NULL; - size_t size_field, used_field; - int k; - - k = fscanf(f, - "%ms " /* device/file */ - "%ms " /* type of swap */ - "%zu " /* swap size */ - "%zu " /* used */ - "%*i\n", /* priority */ - &dev, &type, &size_field, &used_field); - if (k != 4) { - if (k == EOF) - break; - - log_warning("Failed to parse /proc/swaps:%u", i); - continue; - } - - if (streq(type, "partition") && endswith(dev, "\\040(deleted)")) { - log_warning("Ignoring deleted swapfile '%s'.", dev); - continue; - } - - *size = size_field; - *used = used_field; - return 0; - } - - log_debug("No swap partitions were found."); - return -ENOSYS; -} - -static bool enough_memory_for_hibernation(void) { - _cleanup_free_ char *active = NULL; - unsigned long long act = 0; - size_t size = 0, used = 0; - int r; - - if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0) - return true; - - r = hibernation_partition_size(&size, &used); - if (r < 0) - return false; - - r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active); - if (r < 0) { - log_error_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m"); - return false; - } - - r = safe_atollu(active, &act); - if (r < 0) { - log_error_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m", - active); - return false; - } - - r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD; - log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%", - r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD); - - return r; -} - -int can_sleep(const char *verb) { - _cleanup_strv_free_ char **modes = NULL, **states = NULL; - int r; - - assert(streq(verb, "suspend") || - streq(verb, "hibernate") || - streq(verb, "hybrid-sleep")); - - r = parse_sleep_config(verb, &modes, &states); - if (r < 0) - return false; - - if (!can_sleep_state(states) || !can_sleep_disk(modes)) - return false; - - return streq(verb, "suspend") || enough_memory_for_hibernation(); -} diff --git a/src/shared/sleep-config.h b/src/shared/sleep-config.h deleted file mode 100644 index ad10039ff4..0000000000 --- a/src/shared/sleep-config.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 Zbigniew Jędrzejewski-Szmek - - 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 . -***/ - -int parse_sleep_config(const char *verb, char ***modes, char ***states); - -int can_sleep(const char *verb); -int can_sleep_disk(char **types); -int can_sleep_state(char **types); diff --git a/src/shared/spawn-ask-password-agent.c b/src/shared/spawn-ask-password-agent.c deleted file mode 100644 index a46b7525f0..0000000000 --- a/src/shared/spawn-ask-password-agent.c +++ /dev/null @@ -1,62 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include - -#include "log.h" -#include "process-util.h" -#include "spawn-ask-password-agent.h" -#include "util.h" - -static pid_t agent_pid = 0; - -int ask_password_agent_open(void) { - int r; - - if (agent_pid > 0) - return 0; - - /* We check STDIN here, not STDOUT, since this is about input, - * not output */ - if (!isatty(STDIN_FILENO)) - return 0; - - r = fork_agent(&agent_pid, - NULL, 0, - SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, - SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, "--watch", NULL); - if (r < 0) - return log_error_errno(r, "Failed to fork TTY ask password agent: %m"); - - return 1; -} - -void ask_password_agent_close(void) { - - if (agent_pid <= 0) - return; - - /* Inform agent that we are done */ - (void) kill(agent_pid, SIGTERM); - (void) kill(agent_pid, SIGCONT); - (void) wait_for_terminate(agent_pid, NULL); - agent_pid = 0; -} diff --git a/src/shared/spawn-ask-password-agent.h b/src/shared/spawn-ask-password-agent.h deleted file mode 100644 index fb0749b13f..0000000000 --- a/src/shared/spawn-ask-password-agent.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -int ask_password_agent_open(void); -void ask_password_agent_close(void); diff --git a/src/shared/spawn-polkit-agent.c b/src/shared/spawn-polkit-agent.c deleted file mode 100644 index 7dae4d14fe..0000000000 --- a/src/shared/spawn-polkit-agent.c +++ /dev/null @@ -1,102 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include -#include -#include - -#include "fd-util.h" -#include "io-util.h" -#include "log.h" -#include "macro.h" -#include "process-util.h" -#include "spawn-polkit-agent.h" -#include "stdio-util.h" -#include "time-util.h" -#include "util.h" - -#ifdef ENABLE_POLKIT -static pid_t agent_pid = 0; - -int polkit_agent_open(void) { - int r; - int pipe_fd[2]; - char notify_fd[DECIMAL_STR_MAX(int) + 1]; - - if (agent_pid > 0) - return 0; - - /* Clients that run as root don't need to activate/query polkit */ - if (geteuid() == 0) - return 0; - - /* We check STDIN here, not STDOUT, since this is about input, - * not output */ - if (!isatty(STDIN_FILENO)) - return 0; - - if (pipe2(pipe_fd, 0) < 0) - return -errno; - - xsprintf(notify_fd, "%i", pipe_fd[1]); - - r = fork_agent(&agent_pid, - &pipe_fd[1], 1, - POLKIT_AGENT_BINARY_PATH, - POLKIT_AGENT_BINARY_PATH, "--notify-fd", notify_fd, "--fallback", NULL); - - /* Close the writing side, because that's the one for the agent */ - safe_close(pipe_fd[1]); - - if (r < 0) - log_error_errno(r, "Failed to fork TTY ask password agent: %m"); - else - /* Wait until the agent closes the fd */ - fd_wait_for_event(pipe_fd[0], POLLHUP, USEC_INFINITY); - - safe_close(pipe_fd[0]); - - return r; -} - -void polkit_agent_close(void) { - - if (agent_pid <= 0) - return; - - /* Inform agent that we are done */ - (void) kill(agent_pid, SIGTERM); - (void) kill(agent_pid, SIGCONT); - - (void) wait_for_terminate(agent_pid, NULL); - agent_pid = 0; -} - -#else - -int polkit_agent_open(void) { - return 0; -} - -void polkit_agent_close(void) { -} - -#endif diff --git a/src/shared/spawn-polkit-agent.h b/src/shared/spawn-polkit-agent.h deleted file mode 100644 index 42b2989ded..0000000000 --- a/src/shared/spawn-polkit-agent.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -int polkit_agent_open(void); -void polkit_agent_close(void); diff --git a/src/shared/specifier.c b/src/shared/specifier.c deleted file mode 100644 index 1c17eb5251..0000000000 --- a/src/shared/specifier.c +++ /dev/null @@ -1,188 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include - -#include "sd-id128.h" - -#include "alloc-util.h" -#include "hostname-util.h" -#include "macro.h" -#include "specifier.h" -#include "string-util.h" - -/* - * Generic infrastructure for replacing %x style specifiers in - * strings. Will call a callback for each replacement. - * - */ - -int specifier_printf(const char *text, const Specifier table[], void *userdata, char **_ret) { - char *ret, *t; - const char *f; - bool percent = false; - size_t l; - int r; - - assert(text); - assert(table); - - l = strlen(text); - ret = new(char, l+1); - if (!ret) - return -ENOMEM; - - t = ret; - - for (f = text; *f; f++, l--) { - - if (percent) { - if (*f == '%') - *(t++) = '%'; - else { - const Specifier *i; - - for (i = table; i->specifier; i++) - if (i->specifier == *f) - break; - - if (i->lookup) { - _cleanup_free_ char *w = NULL; - char *n; - size_t k, j; - - r = i->lookup(i->specifier, i->data, userdata, &w); - if (r < 0) { - free(ret); - return r; - } - - j = t - ret; - k = strlen(w); - - n = new(char, j + k + l + 1); - if (!n) { - free(ret); - return -ENOMEM; - } - - memcpy(n, ret, j); - memcpy(n + j, w, k); - - free(ret); - - ret = n; - t = n + j + k; - } else { - *(t++) = '%'; - *(t++) = *f; - } - } - - percent = false; - } else if (*f == '%') - percent = true; - else - *(t++) = *f; - } - - *t = 0; - *_ret = ret; - return 0; -} - -/* Generic handler for simple string replacements */ - -int specifier_string(char specifier, void *data, void *userdata, char **ret) { - char *n; - - n = strdup(strempty(data)); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; -} - -int specifier_machine_id(char specifier, void *data, void *userdata, char **ret) { - sd_id128_t id; - char *n; - int r; - - r = sd_id128_get_machine(&id); - if (r < 0) - return r; - - n = new(char, 33); - if (!n) - return -ENOMEM; - - *ret = sd_id128_to_string(id, n); - return 0; -} - -int specifier_boot_id(char specifier, void *data, void *userdata, char **ret) { - sd_id128_t id; - char *n; - int r; - - r = sd_id128_get_boot(&id); - if (r < 0) - return r; - - n = new(char, 33); - if (!n) - return -ENOMEM; - - *ret = sd_id128_to_string(id, n); - return 0; -} - -int specifier_host_name(char specifier, void *data, void *userdata, char **ret) { - char *n; - - n = gethostname_malloc(); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; -} - -int specifier_kernel_release(char specifier, void *data, void *userdata, char **ret) { - struct utsname uts; - char *n; - int r; - - r = uname(&uts); - if (r < 0) - return -errno; - - n = strdup(uts.release); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; -} diff --git a/src/shared/specifier.h b/src/shared/specifier.h deleted file mode 100644 index 6b1623ee61..0000000000 --- a/src/shared/specifier.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef int (*SpecifierCallback)(char specifier, void *data, void *userdata, char **ret); - -typedef struct Specifier { - const char specifier; - const SpecifierCallback lookup; - void *data; -} Specifier; - -int specifier_printf(const char *text, const Specifier table[], void *userdata, char **ret); - -int specifier_string(char specifier, void *data, void *userdata, char **ret); - -int specifier_machine_id(char specifier, void *data, void *userdata, char **ret); -int specifier_boot_id(char specifier, void *data, void *userdata, char **ret); -int specifier_host_name(char specifier, void *data, void *userdata, char **ret); -int specifier_kernel_release(char specifier, void *data, void *userdata, char **ret); diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c deleted file mode 100644 index 47d3a5a1fa..0000000000 --- a/src/shared/switch-root.c +++ /dev/null @@ -1,156 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 Harald Hoyer, 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 -#include -#include -#include -#include -#include -#include - -#include "base-filesystem.h" -#include "fd-util.h" -#include "log.h" -#include "missing.h" -#include "mkdir.h" -#include "path-util.h" -#include "rm-rf.h" -#include "stdio-util.h" -#include "string-util.h" -#include "switch-root.h" -#include "user-util.h" -#include "util.h" - -int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, unsigned long mountflags) { - - /* Don't try to unmount/move the old "/", there's no way to do it. */ - static const char move_mounts[] = - "/dev\0" - "/proc\0" - "/sys\0" - "/run\0"; - - _cleanup_close_ int old_root_fd = -1; - struct stat new_root_stat; - bool old_root_remove; - const char *i, *temporary_old_root; - - if (path_equal(new_root, "/")) - return 0; - - temporary_old_root = strjoina(new_root, oldroot); - mkdir_p_label(temporary_old_root, 0755); - - old_root_remove = in_initrd(); - - if (stat(new_root, &new_root_stat) < 0) - return log_error_errno(errno, "Failed to stat directory %s: %m", new_root); - - /* Work-around for kernel design: the kernel refuses switching - * root if any file systems are mounted MS_SHARED. Hence - * remount them MS_PRIVATE here as a work-around. - * - * https://bugzilla.redhat.com/show_bug.cgi?id=847418 */ - if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0) - log_warning_errno(errno, "Failed to make \"/\" private mount: %m"); - - NULSTR_FOREACH(i, move_mounts) { - char new_mount[PATH_MAX]; - struct stat sb; - - xsprintf(new_mount, "%s%s", new_root, i); - - mkdir_p_label(new_mount, 0755); - - if ((stat(new_mount, &sb) < 0) || - sb.st_dev != new_root_stat.st_dev) { - - /* Mount point seems to be mounted already or - * stat failed. Unmount the old mount - * point. */ - if (umount2(i, MNT_DETACH) < 0) - log_warning_errno(errno, "Failed to unmount %s: %m", i); - continue; - } - - if (mount(i, new_mount, NULL, mountflags, NULL) < 0) { - if (mountflags & MS_MOVE) { - log_error_errno(errno, "Failed to move mount %s to %s, forcing unmount: %m", i, new_mount); - - if (umount2(i, MNT_FORCE) < 0) - log_warning_errno(errno, "Failed to unmount %s: %m", i); - } - if (mountflags & MS_BIND) - log_error_errno(errno, "Failed to bind mount %s to %s: %m", i, new_mount); - - } - } - - /* Do not fail, if base_filesystem_create() fails. Not all - * switch roots are like base_filesystem_create() wants them - * to look like. They might even boot, if they are RO and - * don't have the FS layout. Just ignore the error and - * switch_root() nevertheless. */ - (void) base_filesystem_create(new_root, UID_INVALID, GID_INVALID); - - if (chdir(new_root) < 0) - return log_error_errno(errno, "Failed to change directory to %s: %m", new_root); - - if (old_root_remove) { - old_root_fd = open("/", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY|O_DIRECTORY); - if (old_root_fd < 0) - log_warning_errno(errno, "Failed to open root directory: %m"); - } - - /* We first try a pivot_root() so that we can umount the old - * root dir. In many cases (i.e. where rootfs is /), that's - * not possible however, and hence we simply overmount root */ - if (pivot_root(new_root, temporary_old_root) >= 0) { - - /* Immediately get rid of the old root, if detach_oldroot is set. - * Since we are running off it we need to do this lazily. */ - if (detach_oldroot && umount2(oldroot, MNT_DETACH) < 0) - log_error_errno(errno, "Failed to lazily umount old root dir %s, %s: %m", - oldroot, - errno == ENOENT ? "ignoring" : "leaving it around"); - - } else if (mount(new_root, "/", NULL, MS_MOVE, NULL) < 0) - return log_error_errno(errno, "Failed to mount moving %s to /: %m", new_root); - - if (chroot(".") < 0) - return log_error_errno(errno, "Failed to change root: %m"); - - if (chdir("/") < 0) - return log_error_errno(errno, "Failed to change directory: %m"); - - if (old_root_fd >= 0) { - struct stat rb; - - if (fstat(old_root_fd, &rb) < 0) - log_warning_errno(errno, "Failed to stat old root directory, leaving: %m"); - else { - (void) rm_rf_children(old_root_fd, 0, &rb); - old_root_fd = -1; - } - } - - return 0; -} diff --git a/src/shared/switch-root.h b/src/shared/switch-root.h deleted file mode 100644 index a7a080b3e8..0000000000 --- a/src/shared/switch-root.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -/*** - This file is part of systemd. - - Copyright 2012 Harald Hoyer, 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 . -***/ - -int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, unsigned long mountflags); diff --git a/src/shared/sysctl-util.c b/src/shared/sysctl-util.c deleted file mode 100644 index e1ccb3294c..0000000000 --- a/src/shared/sysctl-util.c +++ /dev/null @@ -1,73 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 - -#include "fileio.h" -#include "log.h" -#include "macro.h" -#include "string-util.h" -#include "sysctl-util.h" - -char *sysctl_normalize(char *s) { - char *n; - - n = strpbrk(s, "/."); - /* If the first separator is a slash, the path is - * assumed to be normalized and slashes remain slashes - * and dots remains dots. */ - if (!n || *n == '/') - return s; - - /* Otherwise, dots become slashes and slashes become - * dots. Fun. */ - while (n) { - if (*n == '.') - *n = '/'; - else - *n = '.'; - - n = strpbrk(n + 1, "/."); - } - - return s; -} - -int sysctl_write(const char *property, const char *value) { - char *p; - - assert(property); - assert(value); - - log_debug("Setting '%s' to '%s'", property, value); - - p = strjoina("/proc/sys/", property); - return write_string_file(p, value, 0); -} - -int sysctl_read(const char *property, char **content) { - char *p; - - assert(property); - assert(content); - - p = strjoina("/proc/sys/", property); - return read_full_file(p, content, NULL); -} diff --git a/src/shared/sysctl-util.h b/src/shared/sysctl-util.h deleted file mode 100644 index 2decb39f58..0000000000 --- a/src/shared/sysctl-util.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -char *sysctl_normalize(char *s); -int sysctl_read(const char *property, char **value); -int sysctl_write(const char *property, const char *value); - diff --git a/src/shared/test-tables.h b/src/shared/test-tables.h deleted file mode 100644 index 228e510104..0000000000 --- a/src/shared/test-tables.h +++ /dev/null @@ -1,60 +0,0 @@ -/*** - This file is part of systemd - - Copyright 2013 Zbigniew Jędrzejewski-Szmek - - 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 - -typedef const char* (*lookup_t)(int); -typedef int (*reverse_t)(const char*); - -static inline void _test_table(const char *name, - lookup_t lookup, - reverse_t reverse, - int size, - bool sparse) { - int i, boring = 0; - - for (i = -1; i < size + 1; i++) { - const char* val = lookup(i); - int rev; - - if (val) { - rev = reverse(val); - boring = 0; - } else { - rev = reverse("--no-such--value----"); - boring += i >= 0; - } - - if (boring < 1 || i == size) - printf("%s: %d → %s → %d\n", name, i, val, rev); - else if (boring == 1) - printf("%*s ...\n", (int) strlen(name), ""); - - assert_se(!(i >= 0 && i < size ? - sparse ? rev != i && rev != -1 : val == NULL || rev != i : - val != NULL || rev != -1)); - } -} - -#define test_table(lower, upper) \ - _test_table(STRINGIFY(lower), lower##_to_string, lower##_from_string, _##upper##_MAX, false) - -#define test_table_sparse(lower, upper) \ - _test_table(STRINGIFY(lower), lower##_to_string, lower##_from_string, _##upper##_MAX, true) diff --git a/src/shared/tests.c b/src/shared/tests.c deleted file mode 100644 index 409116290d..0000000000 --- a/src/shared/tests.c +++ /dev/null @@ -1,33 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2016 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 - -#include "tests.h" - -char* setup_fake_runtime_dir(void) { - char t[] = "/tmp/fake-xdg-runtime-XXXXXX", *p; - - assert_se(mkdtemp(t)); - assert_se(setenv("XDG_RUNTIME_DIR", t, 1) >= 0); - assert_se(p = strdup(t)); - - return p; -} diff --git a/src/shared/tests.h b/src/shared/tests.h deleted file mode 100644 index 93f09013a1..0000000000 --- a/src/shared/tests.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2016 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 . -***/ - -char* setup_fake_runtime_dir(void); diff --git a/src/shared/udev-util.h b/src/shared/udev-util.h deleted file mode 100644 index ca0889f8a6..0000000000 --- a/src/shared/udev-util.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 Zbigniew Jędrzejewski-Szmek - - 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 "udev.h" -#include "util.h" - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev*, udev_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_device*, udev_device_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_enumerate*, udev_enumerate_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_event*, udev_event_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_rules*, udev_rules_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_ctrl*, udev_ctrl_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_ctrl_connection*, udev_ctrl_connection_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_ctrl_msg*, udev_ctrl_msg_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_monitor*, udev_monitor_unref); - -#define _cleanup_udev_unref_ _cleanup_(udev_unrefp) -#define _cleanup_udev_device_unref_ _cleanup_(udev_device_unrefp) -#define _cleanup_udev_enumerate_unref_ _cleanup_(udev_enumerate_unrefp) -#define _cleanup_udev_event_unref_ _cleanup_(udev_event_unrefp) -#define _cleanup_udev_rules_unref_ _cleanup_(udev_rules_unrefp) -#define _cleanup_udev_ctrl_unref_ _cleanup_(udev_ctrl_unrefp) -#define _cleanup_udev_ctrl_connection_unref_ _cleanup_(udev_ctrl_connection_unrefp) -#define _cleanup_udev_ctrl_msg_unref_ _cleanup_(udev_ctrl_msg_unrefp) -#define _cleanup_udev_monitor_unref_ _cleanup_(udev_monitor_unrefp) -#define _cleanup_udev_list_cleanup_ _cleanup_(udev_list_cleanup) diff --git a/src/shared/uid-range.c b/src/shared/uid-range.c deleted file mode 100644 index b6ec474390..0000000000 --- a/src/shared/uid-range.c +++ /dev/null @@ -1,208 +0,0 @@ -/*** - 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 -#include - -#include "macro.h" -#include "uid-range.h" -#include "user-util.h" - -static bool uid_range_intersect(UidRange *range, uid_t start, uid_t nr) { - assert(range); - - return range->start <= start + nr && - range->start + range->nr >= start; -} - -static void uid_range_coalesce(UidRange **p, unsigned *n) { - unsigned i, j; - - assert(p); - assert(n); - - for (i = 0; i < *n; i++) { - for (j = i + 1; j < *n; j++) { - UidRange *x = (*p)+i, *y = (*p)+j; - - if (uid_range_intersect(x, y->start, y->nr)) { - uid_t begin, end; - - begin = MIN(x->start, y->start); - end = MAX(x->start + x->nr, y->start + y->nr); - - x->start = begin; - x->nr = end - begin; - - if (*n > j+1) - memmove(y, y+1, sizeof(UidRange) * (*n - j -1)); - - (*n)--; - j--; - } - } - } - -} - -static int uid_range_compare(const void *a, const void *b) { - const UidRange *x = a, *y = b; - - if (x->start < y->start) - return -1; - if (x->start > y->start) - return 1; - - if (x->nr < y->nr) - return -1; - if (x->nr > y->nr) - return 1; - - return 0; -} - -int uid_range_add(UidRange **p, unsigned *n, uid_t start, uid_t nr) { - bool found = false; - UidRange *x; - unsigned i; - - assert(p); - assert(n); - - if (nr <= 0) - return 0; - - for (i = 0; i < *n; i++) { - x = (*p) + i; - if (uid_range_intersect(x, start, nr)) { - found = true; - break; - } - } - - if (found) { - uid_t begin, end; - - begin = MIN(x->start, start); - end = MAX(x->start + x->nr, start + nr); - - x->start = begin; - x->nr = end - begin; - } else { - UidRange *t; - - t = realloc(*p, sizeof(UidRange) * (*n + 1)); - if (!t) - return -ENOMEM; - - *p = t; - x = t + ((*n) ++); - - x->start = start; - x->nr = nr; - } - - qsort(*p, *n, sizeof(UidRange), uid_range_compare); - uid_range_coalesce(p, n); - - return *n; -} - -int uid_range_add_str(UidRange **p, unsigned *n, const char *s) { - uid_t start, nr; - const char *t; - int r; - - assert(p); - assert(n); - assert(s); - - t = strchr(s, '-'); - if (t) { - char *b; - uid_t end; - - b = strndupa(s, t - s); - r = parse_uid(b, &start); - if (r < 0) - return r; - - r = parse_uid(t+1, &end); - if (r < 0) - return r; - - if (end < start) - return -EINVAL; - - nr = end - start + 1; - } else { - r = parse_uid(s, &start); - if (r < 0) - return r; - - nr = 1; - } - - return uid_range_add(p, n, start, nr); -} - -int uid_range_next_lower(const UidRange *p, unsigned n, uid_t *uid) { - uid_t closest = UID_INVALID, candidate; - unsigned i; - - assert(p); - assert(uid); - - candidate = *uid - 1; - - for (i = 0; i < n; i++) { - uid_t begin, end; - - begin = p[i].start; - end = p[i].start + p[i].nr - 1; - - if (candidate >= begin && candidate <= end) { - *uid = candidate; - return 1; - } - - if (end < candidate) - closest = end; - } - - if (closest == UID_INVALID) - return -EBUSY; - - *uid = closest; - return 1; -} - -bool uid_range_contains(const UidRange *p, unsigned n, uid_t uid) { - unsigned i; - - assert(p); - assert(uid); - - for (i = 0; i < n; i++) - if (uid >= p[i].start && uid < p[i].start + p[i].nr) - return true; - - return false; -} diff --git a/src/shared/uid-range.h b/src/shared/uid-range.h deleted file mode 100644 index 4044eb4c9c..0000000000 --- a/src/shared/uid-range.h +++ /dev/null @@ -1,33 +0,0 @@ -#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 -#include - -typedef struct UidRange { - uid_t start, nr; -} UidRange; - -int uid_range_add(UidRange **p, unsigned *n, uid_t start, uid_t nr); -int uid_range_add_str(UidRange **p, unsigned *n, const char *s); - -int uid_range_next_lower(const UidRange *p, unsigned n, uid_t *uid); -bool uid_range_contains(const UidRange *p, unsigned n, uid_t uid); diff --git a/src/shared/utmp-wtmp.c b/src/shared/utmp-wtmp.c deleted file mode 100644 index 9750dcd817..0000000000 --- a/src/shared/utmp-wtmp.c +++ /dev/null @@ -1,445 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "hostname-util.h" -#include "macro.h" -#include "path-util.h" -#include "string-util.h" -#include "terminal-util.h" -#include "time-util.h" -#include "user-util.h" -#include "util.h" -#include "utmp-wtmp.h" - -int utmp_get_runlevel(int *runlevel, int *previous) { - struct utmpx *found, lookup = { .ut_type = RUN_LVL }; - int r; - const char *e; - - assert(runlevel); - - /* If these values are set in the environment this takes - * precedence. Presumably, sysvinit does this to work around a - * race condition that would otherwise exist where we'd always - * go to disk and hence might read runlevel data that might be - * very new and does not apply to the current script being - * executed. */ - - e = getenv("RUNLEVEL"); - if (e && e[0] > 0) { - *runlevel = e[0]; - - if (previous) { - /* $PREVLEVEL seems to be an Upstart thing */ - - e = getenv("PREVLEVEL"); - if (e && e[0] > 0) - *previous = e[0]; - else - *previous = 0; - } - - return 0; - } - - if (utmpxname(_PATH_UTMPX) < 0) - return -errno; - - setutxent(); - - found = getutxid(&lookup); - if (!found) - r = -errno; - else { - int a, b; - - a = found->ut_pid & 0xFF; - b = (found->ut_pid >> 8) & 0xFF; - - *runlevel = a; - if (previous) - *previous = b; - - r = 0; - } - - endutxent(); - - return r; -} - -static void init_timestamp(struct utmpx *store, usec_t t) { - assert(store); - - if (t <= 0) - t = now(CLOCK_REALTIME); - - store->ut_tv.tv_sec = t / USEC_PER_SEC; - store->ut_tv.tv_usec = t % USEC_PER_SEC; -} - -static void init_entry(struct utmpx *store, usec_t t) { - struct utsname uts = {}; - - assert(store); - - init_timestamp(store, t); - - if (uname(&uts) >= 0) - strncpy(store->ut_host, uts.release, sizeof(store->ut_host)); - - strncpy(store->ut_line, "~", sizeof(store->ut_line)); /* or ~~ ? */ - strncpy(store->ut_id, "~~", sizeof(store->ut_id)); -} - -static int write_entry_utmp(const struct utmpx *store) { - int r; - - assert(store); - - /* utmp is similar to wtmp, but there is only one entry for - * each entry type resp. user; i.e. basically a key/value - * table. */ - - if (utmpxname(_PATH_UTMPX) < 0) - return -errno; - - setutxent(); - - if (!pututxline(store)) - r = -errno; - else - r = 0; - - endutxent(); - - return r; -} - -static int write_entry_wtmp(const struct utmpx *store) { - assert(store); - - /* wtmp is a simple append-only file where each entry is - simply appended to the end; i.e. basically a log. */ - - errno = 0; - updwtmpx(_PATH_WTMPX, store); - return -errno; -} - -static int write_utmp_wtmp(const struct utmpx *store_utmp, const struct utmpx *store_wtmp) { - int r, s; - - r = write_entry_utmp(store_utmp); - s = write_entry_wtmp(store_wtmp); - - if (r >= 0) - r = s; - - /* If utmp/wtmp have been disabled, that's a good thing, hence - * ignore the errors */ - if (r == -ENOENT) - r = 0; - - return r; -} - -static int write_entry_both(const struct utmpx *store) { - return write_utmp_wtmp(store, store); -} - -int utmp_put_shutdown(void) { - struct utmpx store = {}; - - init_entry(&store, 0); - - store.ut_type = RUN_LVL; - strncpy(store.ut_user, "shutdown", sizeof(store.ut_user)); - - return write_entry_both(&store); -} - -int utmp_put_reboot(usec_t t) { - struct utmpx store = {}; - - init_entry(&store, t); - - store.ut_type = BOOT_TIME; - strncpy(store.ut_user, "reboot", sizeof(store.ut_user)); - - return write_entry_both(&store); -} - -_pure_ static const char *sanitize_id(const char *id) { - size_t l; - - assert(id); - l = strlen(id); - - if (l <= sizeof(((struct utmpx*) NULL)->ut_id)) - return id; - - return id + l - sizeof(((struct utmpx*) NULL)->ut_id); -} - -int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user) { - struct utmpx store = { - .ut_type = INIT_PROCESS, - .ut_pid = pid, - .ut_session = sid, - }; - int r; - - assert(id); - - init_timestamp(&store, 0); - - /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */ - strncpy(store.ut_id, sanitize_id(id), sizeof(store.ut_id)); - - if (line) - strncpy(store.ut_line, basename(line), sizeof(store.ut_line)); - - r = write_entry_both(&store); - if (r < 0) - return r; - - if (ut_type == LOGIN_PROCESS || ut_type == USER_PROCESS) { - store.ut_type = LOGIN_PROCESS; - r = write_entry_both(&store); - if (r < 0) - return r; - } - - if (ut_type == USER_PROCESS) { - store.ut_type = USER_PROCESS; - strncpy(store.ut_user, user, sizeof(store.ut_user)-1); - r = write_entry_both(&store); - if (r < 0) - return r; - } - - return 0; -} - -int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) { - struct utmpx lookup = { - .ut_type = INIT_PROCESS /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */ - }, store, store_wtmp, *found; - - assert(id); - - setutxent(); - - /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */ - strncpy(lookup.ut_id, sanitize_id(id), sizeof(lookup.ut_id)); - - found = getutxid(&lookup); - if (!found) - return 0; - - if (found->ut_pid != pid) - return 0; - - memcpy(&store, found, sizeof(store)); - store.ut_type = DEAD_PROCESS; - store.ut_exit.e_termination = code; - store.ut_exit.e_exit = status; - - zero(store.ut_user); - zero(store.ut_host); - zero(store.ut_tv); - - memcpy(&store_wtmp, &store, sizeof(store_wtmp)); - /* wtmp wants the current time */ - init_timestamp(&store_wtmp, 0); - - return write_utmp_wtmp(&store, &store_wtmp); -} - - -int utmp_put_runlevel(int runlevel, int previous) { - struct utmpx store = {}; - int r; - - assert(runlevel > 0); - - if (previous <= 0) { - /* Find the old runlevel automatically */ - - r = utmp_get_runlevel(&previous, NULL); - if (r < 0) { - if (r != -ESRCH) - return r; - - previous = 0; - } - } - - if (previous == runlevel) - return 0; - - init_entry(&store, 0); - - store.ut_type = RUN_LVL; - store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8); - strncpy(store.ut_user, "runlevel", sizeof(store.ut_user)); - - return write_entry_both(&store); -} - -#define TIMEOUT_MSEC 50 - -static int write_to_terminal(const char *tty, const char *message) { - _cleanup_close_ int fd = -1; - const char *p; - size_t left; - usec_t end; - - assert(tty); - assert(message); - - fd = open(tty, O_WRONLY|O_NDELAY|O_NOCTTY|O_CLOEXEC); - if (fd < 0 || !isatty(fd)) - return -errno; - - p = message; - left = strlen(message); - - end = now(CLOCK_MONOTONIC) + TIMEOUT_MSEC*USEC_PER_MSEC; - - while (left > 0) { - ssize_t n; - struct pollfd pollfd = { - .fd = fd, - .events = POLLOUT, - }; - usec_t t; - int k; - - t = now(CLOCK_MONOTONIC); - - if (t >= end) - return -ETIME; - - k = poll(&pollfd, 1, (end - t) / USEC_PER_MSEC); - if (k < 0) - return -errno; - - if (k == 0) - return -ETIME; - - n = write(fd, p, left); - if (n < 0) { - if (errno == EAGAIN) - continue; - - return -errno; - } - - assert((size_t) n <= left); - - p += n; - left -= n; - } - - return 0; -} - -int utmp_wall( - const char *message, - const char *username, - const char *origin_tty, - bool (*match_tty)(const char *tty, void *userdata), - void *userdata) { - - _cleanup_free_ char *text = NULL, *hn = NULL, *un = NULL, *stdin_tty = NULL; - char date[FORMAT_TIMESTAMP_MAX]; - struct utmpx *u; - int r; - - hn = gethostname_malloc(); - if (!hn) - return -ENOMEM; - if (!username) { - un = getlogname_malloc(); - if (!un) - return -ENOMEM; - } - - if (!origin_tty) { - getttyname_harder(STDIN_FILENO, &stdin_tty); - origin_tty = stdin_tty; - } - - if (asprintf(&text, - "\a\r\n" - "Broadcast message from %s@%s%s%s (%s):\r\n\r\n" - "%s\r\n\r\n", - un ?: username, hn, - origin_tty ? " on " : "", strempty(origin_tty), - format_timestamp(date, sizeof(date), now(CLOCK_REALTIME)), - message) < 0) - return -ENOMEM; - - setutxent(); - - r = 0; - - while ((u = getutxent())) { - _cleanup_free_ char *buf = NULL; - const char *path; - int q; - - if (u->ut_type != USER_PROCESS || u->ut_user[0] == 0) - continue; - - /* this access is fine, because strlen("/dev/") << 32 (UT_LINESIZE) */ - if (path_startswith(u->ut_line, "/dev/")) - path = u->ut_line; - else { - if (asprintf(&buf, "/dev/%.*s", (int) sizeof(u->ut_line), u->ut_line) < 0) - return -ENOMEM; - - path = buf; - } - - if (!match_tty || match_tty(path, userdata)) { - q = write_to_terminal(path, text); - if (q < 0) - r = q; - } - } - - return r; -} diff --git a/src/shared/utmp-wtmp.h b/src/shared/utmp-wtmp.h deleted file mode 100644 index 438e270a26..0000000000 --- a/src/shared/utmp-wtmp.h +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 - -#include "time-util.h" -#include "util.h" - -#ifdef HAVE_UTMP -int utmp_get_runlevel(int *runlevel, int *previous); - -int utmp_put_shutdown(void); -int utmp_put_reboot(usec_t timestamp); -int utmp_put_runlevel(int runlevel, int previous); - -int utmp_put_dead_process(const char *id, pid_t pid, int code, int status); -int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user); - -int utmp_wall( - const char *message, - const char *username, - const char *origin_tty, - bool (*match_tty)(const char *tty, void *userdata), - void *userdata); - -#else /* HAVE_UTMP */ - -static inline int utmp_get_runlevel(int *runlevel, int *previous) { - return -ESRCH; -} -static inline int utmp_put_shutdown(void) { - return 0; -} -static inline int utmp_put_reboot(usec_t timestamp) { - return 0; -} -static inline int utmp_put_runlevel(int runlevel, int previous) { - return 0; -} -static inline int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) { - return 0; -} -static inline int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user) { - return 0; -} -static inline int utmp_wall( - const char *message, - const char *username, - const char *origin_tty, - bool (*match_tty)(const char *tty, void *userdata), - void *userdata) { - return 0; -} - -#endif /* HAVE_UTMP */ diff --git a/src/shared/watchdog.c b/src/shared/watchdog.c deleted file mode 100644 index 4f3e0125f3..0000000000 --- a/src/shared/watchdog.c +++ /dev/null @@ -1,164 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 -#include -#include -#include -#include - -#include "fd-util.h" -#include "log.h" -#include "time-util.h" -#include "watchdog.h" - -static int watchdog_fd = -1; -static usec_t watchdog_timeout = USEC_INFINITY; - -static int update_timeout(void) { - int r; - - if (watchdog_fd < 0) - return 0; - - if (watchdog_timeout == USEC_INFINITY) - return 0; - else if (watchdog_timeout == 0) { - int flags; - - flags = WDIOS_DISABLECARD; - r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); - if (r < 0) - return log_warning_errno(errno, "Failed to disable hardware watchdog: %m"); - } else { - int sec, flags; - char buf[FORMAT_TIMESPAN_MAX]; - - sec = (int) ((watchdog_timeout + USEC_PER_SEC - 1) / USEC_PER_SEC); - r = ioctl(watchdog_fd, WDIOC_SETTIMEOUT, &sec); - if (r < 0) - return log_warning_errno(errno, "Failed to set timeout to %is: %m", sec); - - watchdog_timeout = (usec_t) sec * USEC_PER_SEC; - log_info("Set hardware watchdog to %s.", format_timespan(buf, sizeof(buf), watchdog_timeout, 0)); - - flags = WDIOS_ENABLECARD; - r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); - if (r < 0) { - /* ENOTTY means the watchdog is always enabled so we're fine */ - log_full(errno == ENOTTY ? LOG_DEBUG : LOG_WARNING, - "Failed to enable hardware watchdog: %m"); - if (errno != ENOTTY) - return -errno; - } - - r = ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0); - if (r < 0) - return log_warning_errno(errno, "Failed to ping hardware watchdog: %m"); - } - - return 0; -} - -static int open_watchdog(void) { - struct watchdog_info ident; - - if (watchdog_fd >= 0) - return 0; - - watchdog_fd = open("/dev/watchdog", O_WRONLY|O_CLOEXEC); - if (watchdog_fd < 0) - return -errno; - - if (ioctl(watchdog_fd, WDIOC_GETSUPPORT, &ident) >= 0) - log_info("Hardware watchdog '%s', version %x", - ident.identity, - ident.firmware_version); - - return update_timeout(); -} - -int watchdog_set_timeout(usec_t *usec) { - int r; - - watchdog_timeout = *usec; - - /* If we didn't open the watchdog yet and didn't get any - * explicit timeout value set, don't do anything */ - if (watchdog_fd < 0 && watchdog_timeout == USEC_INFINITY) - return 0; - - if (watchdog_fd < 0) - r = open_watchdog(); - else - r = update_timeout(); - - *usec = watchdog_timeout; - - return r; -} - -int watchdog_ping(void) { - int r; - - if (watchdog_fd < 0) { - r = open_watchdog(); - if (r < 0) - return r; - } - - r = ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0); - if (r < 0) - return log_warning_errno(errno, "Failed to ping hardware watchdog: %m"); - - return 0; -} - -void watchdog_close(bool disarm) { - int r; - - if (watchdog_fd < 0) - return; - - if (disarm) { - int flags; - - /* Explicitly disarm it */ - flags = WDIOS_DISABLECARD; - r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); - if (r < 0) - log_warning_errno(errno, "Failed to disable hardware watchdog: %m"); - - /* To be sure, use magic close logic, too */ - for (;;) { - static const char v = 'V'; - - if (write(watchdog_fd, &v, 1) > 0) - break; - - if (errno != EINTR) { - log_error_errno(errno, "Failed to disarm watchdog timer: %m"); - break; - } - } - } - - watchdog_fd = safe_close(watchdog_fd); -} diff --git a/src/shared/watchdog.h b/src/shared/watchdog.h deleted file mode 100644 index f6ec178ea1..0000000000 --- a/src/shared/watchdog.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 "time-util.h" -#include "util.h" - -int watchdog_set_timeout(usec_t *usec); -int watchdog_ping(void); -void watchdog_close(bool disarm); diff --git a/src/sleep/Makefile b/src/sleep/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/sleep/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/sleep/Makefile b/src/sleep/Makefile new file mode 100644 index 0000000000..2d8854bbf9 --- /dev/null +++ b/src/sleep/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_sleep_SOURCES = \ + src/sleep/sleep.c + +systemd_sleep_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index c8f0742183..7f8a95728d 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -22,7 +22,7 @@ #include #include -#include "sd-messages.h" +#include #include "def.h" #include "fd-util.h" diff --git a/src/socket-proxy/Makefile b/src/socket-proxy/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/socket-proxy/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/socket-proxy/Makefile b/src/socket-proxy/Makefile new file mode 100644 index 0000000000..cead4f37a9 --- /dev/null +++ b/src/socket-proxy/Makefile @@ -0,0 +1,33 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + + +systemd_socket_proxyd_SOURCES = \ + src/socket-proxy/socket-proxyd.c + +systemd_socket_proxyd_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/socket-proxy/socket-proxyd.c b/src/socket-proxy/socket-proxyd.c index 52b4db8875..ce226c4d66 100644 --- a/src/socket-proxy/socket-proxyd.c +++ b/src/socket-proxy/socket-proxyd.c @@ -28,9 +28,9 @@ #include #include -#include "sd-daemon.h" -#include "sd-event.h" -#include "sd-resolve.h" +#include +#include +#include #include "alloc-util.h" #include "fd-util.h" diff --git a/src/stdio-bridge/stdio-bridge.c b/src/stdio-bridge/stdio-bridge.c deleted file mode 100644 index ce8efce3d5..0000000000 --- a/src/stdio-bridge/stdio-bridge.c +++ /dev/null @@ -1,302 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include - -#include "sd-bus.h" -#include "sd-daemon.h" - -#include "bus-internal.h" -#include "bus-util.h" -#include "build.h" -#include "log.h" -#include "util.h" - -#define DEFAULT_BUS_PATH "unix:path=/run/dbus/system_bus_socket" - -const char *arg_bus_path = DEFAULT_BUS_PATH; - -static int help(void) { - - printf("%s [OPTIONS...]\n\n" - "STDIO or socket-activatable proxy to a given DBus endpoint.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --bus-path=PATH Path to the kernel bus (default: %s)\n", - program_invocation_short_name, DEFAULT_BUS_PATH); - - return 0; -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "bus-path", required_argument, NULL, 'p' }, - { NULL, 0, NULL, 0 } - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "hsup:", options, NULL)) >= 0) { - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case '?': - return -EINVAL; - - case 'p': - arg_bus_path = optarg; - break; - - default: - log_error("Unknown option code %c", c); - return -EINVAL; - } - } - - return 1; -} - -int main(int argc, char *argv[]) { - _cleanup_(sd_bus_unrefp) sd_bus *a = NULL, *b = NULL; - sd_id128_t server_id; - bool is_unix; - int r, in_fd, out_fd; - - log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = sd_listen_fds(0); - if (r == 0) { - in_fd = STDIN_FILENO; - out_fd = STDOUT_FILENO; - } else if (r == 1) { - in_fd = SD_LISTEN_FDS_START; - out_fd = SD_LISTEN_FDS_START; - } else { - log_error("Illegal number of file descriptors passed\n"); - goto finish; - } - - is_unix = - sd_is_socket(in_fd, AF_UNIX, 0, 0) > 0 && - sd_is_socket(out_fd, AF_UNIX, 0, 0) > 0; - - r = sd_bus_new(&a); - if (r < 0) { - log_error_errno(r, "Failed to allocate bus: %m"); - goto finish; - } - - r = sd_bus_set_address(a, arg_bus_path); - if (r < 0) { - log_error_errno(r, "Failed to set address to connect to: %m"); - goto finish; - } - - r = sd_bus_negotiate_fds(a, is_unix); - if (r < 0) { - log_error_errno(r, "Failed to set FD negotiation: %m"); - goto finish; - } - - r = sd_bus_start(a); - if (r < 0) { - log_error_errno(r, "Failed to start bus client: %m"); - goto finish; - } - - r = sd_bus_get_bus_id(a, &server_id); - if (r < 0) { - log_error_errno(r, "Failed to get server ID: %m"); - goto finish; - } - - r = sd_bus_new(&b); - if (r < 0) { - log_error_errno(r, "Failed to allocate bus: %m"); - goto finish; - } - - r = sd_bus_set_fd(b, in_fd, out_fd); - if (r < 0) { - log_error_errno(r, "Failed to set fds: %m"); - goto finish; - } - - r = sd_bus_set_server(b, 1, server_id); - if (r < 0) { - log_error_errno(r, "Failed to set server mode: %m"); - goto finish; - } - - r = sd_bus_negotiate_fds(b, is_unix); - if (r < 0) { - log_error_errno(r, "Failed to set FD negotiation: %m"); - goto finish; - } - - r = sd_bus_set_anonymous(b, true); - if (r < 0) { - log_error_errno(r, "Failed to set anonymous authentication: %m"); - goto finish; - } - - r = sd_bus_start(b); - if (r < 0) { - log_error_errno(r, "Failed to start bus client: %m"); - goto finish; - } - - for (;;) { - _cleanup_(sd_bus_message_unrefp)sd_bus_message *m = NULL; - int events_a, events_b, fd; - uint64_t timeout_a, timeout_b, t; - struct timespec _ts, *ts; - - r = sd_bus_process(a, &m); - if (r < 0) { - log_error_errno(r, "Failed to process bus a: %m"); - goto finish; - } - - if (m) { - r = sd_bus_send(b, m, NULL); - if (r < 0) { - log_error_errno(r, "Failed to send message: %m"); - goto finish; - } - } - - if (r > 0) - continue; - - r = sd_bus_process(b, &m); - if (r < 0) { - /* treat 'connection reset by peer' as clean exit condition */ - if (r == -ECONNRESET) - r = 0; - - goto finish; - } - - if (m) { - r = sd_bus_send(a, m, NULL); - if (r < 0) { - log_error_errno(r, "Failed to send message: %m"); - goto finish; - } - } - - if (r > 0) - continue; - - fd = sd_bus_get_fd(a); - if (fd < 0) { - r = fd; - log_error_errno(r, "Failed to get fd: %m"); - goto finish; - } - - events_a = sd_bus_get_events(a); - if (events_a < 0) { - r = events_a; - log_error_errno(r, "Failed to get events mask: %m"); - goto finish; - } - - r = sd_bus_get_timeout(a, &timeout_a); - if (r < 0) { - log_error_errno(r, "Failed to get timeout: %m"); - goto finish; - } - - events_b = sd_bus_get_events(b); - if (events_b < 0) { - r = events_b; - log_error_errno(r, "Failed to get events mask: %m"); - goto finish; - } - - r = sd_bus_get_timeout(b, &timeout_b); - if (r < 0) { - log_error_errno(r, "Failed to get timeout: %m"); - goto finish; - } - - t = timeout_a; - if (t == (uint64_t) -1 || (timeout_b != (uint64_t) -1 && timeout_b < timeout_a)) - t = timeout_b; - - if (t == (uint64_t) -1) - ts = NULL; - else { - usec_t nw; - - nw = now(CLOCK_MONOTONIC); - if (t > nw) - t -= nw; - else - t = 0; - - ts = timespec_store(&_ts, t); - } - - { - struct pollfd p[3] = { - {.fd = fd, .events = events_a, }, - {.fd = STDIN_FILENO, .events = events_b & POLLIN, }, - {.fd = STDOUT_FILENO, .events = events_b & POLLOUT, }}; - - r = ppoll(p, ELEMENTSOF(p), ts, NULL); - } - if (r < 0) { - log_error("ppoll() failed: %m"); - goto finish; - } - } - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/sysctl/Makefile b/src/sysctl/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/sysctl/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/sysctl/Makefile b/src/sysctl/Makefile new file mode 100644 index 0000000000..31d38d9104 --- /dev/null +++ b/src/sysctl/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_sysctl_SOURCES = \ + src/sysctl/sysctl.c + +systemd_sysctl_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/system-update-generator/Makefile b/src/system-update-generator/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/system-update-generator/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/system-update-generator/system-update-generator.c b/src/system-update-generator/system-update-generator.c deleted file mode 100644 index a3d677f068..0000000000 --- a/src/system-update-generator/system-update-generator.c +++ /dev/null @@ -1,73 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 - -#include "fs-util.h" -#include "log.h" -#include "string-util.h" -#include "util.h" - -/* - * Implements the logic described in - * http://freedesktop.org/wiki/Software/systemd/SystemUpdates - */ - -static const char *arg_dest = "/tmp"; - -static int generate_symlink(void) { - const char *p = NULL; - - if (laccess("/system-update", F_OK) < 0) { - if (errno == ENOENT) - return 0; - - log_error_errno(errno, "Failed to check for system update: %m"); - return -EINVAL; - } - - p = strjoina(arg_dest, "/default.target"); - if (symlink(SYSTEM_DATA_UNIT_PATH "/system-update.target", p) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", p); - - return 0; -} - -int main(int argc, char *argv[]) { - int r; - - if (argc > 1 && argc != 4) { - log_error("This program takes three or no arguments."); - return EXIT_FAILURE; - } - - if (argc > 1) - arg_dest = argv[2]; - - log_set_target(LOG_TARGET_SAFE); - log_parse_environment(); - log_open(); - - umask(0022); - - r = generate_symlink(); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/systemctl/Makefile b/src/systemctl/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/systemctl/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c deleted file mode 100644 index 2480f69a75..0000000000 --- a/src/systemctl/systemctl.c +++ /dev/null @@ -1,7815 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2013 Marc-Antoine Perennou - - 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-bus.h" -#include "sd-daemon.h" -#include "sd-login.h" - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-error.h" -#include "bus-message.h" -#include "bus-unit-util.h" -#include "bus-util.h" -#include "cgroup-show.h" -#include "cgroup-util.h" -#include "copy.h" -#include "dropin.h" -#include "efivars.h" -#include "env-util.h" -#include "exit-status.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "glob-util.h" -#include "hostname-util.h" -#include "initreq.h" -#include "install.h" -#include "io-util.h" -#include "list.h" -#include "locale-util.h" -#include "log.h" -#include "logs-show.h" -#include "macro.h" -#include "mkdir.h" -#include "pager.h" -#include "parse-util.h" -#include "path-lookup.h" -#include "path-util.h" -#include "process-util.h" -#include "rlimit-util.h" -#include "set.h" -#include "signal-util.h" -#include "socket-util.h" -#include "spawn-ask-password-agent.h" -#include "spawn-polkit-agent.h" -#include "special.h" -#include "stat-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "unit-name.h" -#include "user-util.h" -#include "util.h" -#include "utmp-wtmp.h" -#include "verbs.h" -#include "virt.h" - -static char **arg_types = NULL; -static char **arg_states = NULL; -static char **arg_properties = NULL; -static bool arg_all = false; -static enum dependency { - DEPENDENCY_FORWARD, - DEPENDENCY_REVERSE, - DEPENDENCY_AFTER, - DEPENDENCY_BEFORE, - _DEPENDENCY_MAX -} arg_dependency = DEPENDENCY_FORWARD; -static const char *arg_job_mode = "replace"; -static UnitFileScope arg_scope = UNIT_FILE_SYSTEM; -static bool arg_no_block = false; -static bool arg_no_legend = false; -static bool arg_no_pager = false; -static bool arg_no_wtmp = false; -static bool arg_no_sync = false; -static bool arg_no_wall = false; -static bool arg_no_reload = false; -static bool arg_value = false; -static bool arg_show_types = false; -static bool arg_ignore_inhibitors = false; -static bool arg_dry = false; -static bool arg_quiet = false; -static bool arg_full = false; -static bool arg_recursive = false; -static int arg_force = 0; -static bool arg_ask_password = false; -static bool arg_runtime = false; -static UnitFilePresetMode arg_preset_mode = UNIT_FILE_PRESET_FULL; -static char **arg_wall = NULL; -static const char *arg_kill_who = NULL; -static int arg_signal = SIGTERM; -static char *arg_root = NULL; -static usec_t arg_when = 0; -static enum action { - _ACTION_INVALID, - ACTION_SYSTEMCTL, - ACTION_HALT, - ACTION_POWEROFF, - ACTION_REBOOT, - ACTION_KEXEC, - ACTION_EXIT, - ACTION_SUSPEND, - ACTION_HIBERNATE, - ACTION_HYBRID_SLEEP, - ACTION_RUNLEVEL2, - ACTION_RUNLEVEL3, - ACTION_RUNLEVEL4, - ACTION_RUNLEVEL5, - ACTION_RESCUE, - ACTION_EMERGENCY, - ACTION_DEFAULT, - ACTION_RELOAD, - ACTION_REEXEC, - ACTION_RUNLEVEL, - ACTION_CANCEL_SHUTDOWN, - _ACTION_MAX -} arg_action = ACTION_SYSTEMCTL; -static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; -static const char *arg_host = NULL; -static unsigned arg_lines = 10; -static OutputMode arg_output = OUTPUT_SHORT; -static bool arg_plain = false; -static bool arg_firmware_setup = false; -static bool arg_now = false; - -static int daemon_reload(int argc, char *argv[], void* userdata); -static int halt_now(enum action a); -static int get_state_one_unit(sd_bus *bus, const char *name, UnitActiveState *active_state); - -static bool original_stdout_is_tty; - -typedef enum BusFocus { - BUS_FULL, /* The full bus indicated via --system or --user */ - BUS_MANAGER, /* The manager itself, possibly directly, possibly via the bus */ - _BUS_FOCUS_MAX -} BusFocus; - -static sd_bus *busses[_BUS_FOCUS_MAX] = {}; - -static int acquire_bus(BusFocus focus, sd_bus **ret) { - int r; - - assert(focus < _BUS_FOCUS_MAX); - assert(ret); - - /* We only go directly to the manager, if we are using a local transport */ - if (arg_transport != BUS_TRANSPORT_LOCAL) - focus = BUS_FULL; - - if (!busses[focus]) { - bool user; - - user = arg_scope != UNIT_FILE_SYSTEM; - - if (focus == BUS_MANAGER) - r = bus_connect_transport_systemd(arg_transport, arg_host, user, &busses[focus]); - else - r = bus_connect_transport(arg_transport, arg_host, user, &busses[focus]); - if (r < 0) - return log_error_errno(r, "Failed to connect to bus: %m"); - - (void) sd_bus_set_allow_interactive_authorization(busses[focus], arg_ask_password); - } - - *ret = busses[focus]; - return 0; -} - -static void release_busses(void) { - BusFocus w; - - for (w = 0; w < _BUS_FOCUS_MAX; w++) - busses[w] = sd_bus_flush_close_unref(busses[w]); -} - -static void ask_password_agent_open_if_enabled(void) { - - /* Open the password agent as a child process if necessary */ - - if (!arg_ask_password) - return; - - if (arg_scope != UNIT_FILE_SYSTEM) - return; - - if (arg_transport != BUS_TRANSPORT_LOCAL) - return; - - ask_password_agent_open(); -} - -static void polkit_agent_open_if_enabled(void) { - - /* Open the polkit agent as a child process if necessary */ - - if (!arg_ask_password) - return; - - if (arg_scope != UNIT_FILE_SYSTEM) - return; - - if (arg_transport != BUS_TRANSPORT_LOCAL) - return; - - polkit_agent_open(); -} - -static OutputFlags get_output_flags(void) { - return - arg_all * OUTPUT_SHOW_ALL | - arg_full * OUTPUT_FULL_WIDTH | - (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH | - colors_enabled() * OUTPUT_COLOR | - !arg_quiet * OUTPUT_WARN_CUTOFF; -} - -static int translate_bus_error_to_exit_status(int r, const sd_bus_error *error) { - assert(error); - - if (!sd_bus_error_is_set(error)) - return r; - - if (sd_bus_error_has_name(error, SD_BUS_ERROR_ACCESS_DENIED) || - sd_bus_error_has_name(error, BUS_ERROR_ONLY_BY_DEPENDENCY) || - sd_bus_error_has_name(error, BUS_ERROR_NO_ISOLATION) || - sd_bus_error_has_name(error, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE)) - return EXIT_NOPERMISSION; - - if (sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT)) - return EXIT_NOTINSTALLED; - - if (sd_bus_error_has_name(error, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE) || - sd_bus_error_has_name(error, SD_BUS_ERROR_NOT_SUPPORTED)) - return EXIT_NOTIMPLEMENTED; - - if (sd_bus_error_has_name(error, BUS_ERROR_LOAD_FAILED)) - return EXIT_NOTCONFIGURED; - - if (r != 0) - return r; - - return EXIT_FAILURE; -} - -static bool install_client_side(void) { - - /* Decides when to execute enable/disable/... operations - * client-side rather than server-side. */ - - if (running_in_chroot() > 0) - return true; - - if (sd_booted() <= 0) - return true; - - if (!isempty(arg_root)) - return true; - - if (arg_scope == UNIT_FILE_GLOBAL) - return true; - - /* Unsupported environment variable, mostly for debugging purposes */ - if (getenv_bool("SYSTEMCTL_INSTALL_CLIENT_SIDE") > 0) - return true; - - return false; -} - -static int compare_unit_info(const void *a, const void *b) { - const UnitInfo *u = a, *v = b; - const char *d1, *d2; - int r; - - /* First, order by machine */ - if (!u->machine && v->machine) - return -1; - if (u->machine && !v->machine) - return 1; - if (u->machine && v->machine) { - r = strcasecmp(u->machine, v->machine); - if (r != 0) - return r; - } - - /* Second, order by unit type */ - d1 = strrchr(u->id, '.'); - d2 = strrchr(v->id, '.'); - if (d1 && d2) { - r = strcasecmp(d1, d2); - if (r != 0) - return r; - } - - /* Third, order by name */ - return strcasecmp(u->id, v->id); -} - -static bool output_show_unit(const UnitInfo *u, char **patterns) { - assert(u); - - if (!strv_fnmatch_or_empty(patterns, u->id, FNM_NOESCAPE)) - return false; - - if (arg_types) { - const char *dot; - - dot = strrchr(u->id, '.'); - if (!dot) - return false; - - if (!strv_find(arg_types, dot+1)) - return false; - } - - if (arg_all) - return true; - - /* Note that '--all' is not purely a state filter, but also a - * filter that hides units that "follow" other units (which is - * used for device units that appear under different names). */ - if (!isempty(u->following)) - return false; - - if (!strv_isempty(arg_states)) - return true; - - /* By default show all units except the ones in inactive - * state and with no pending job */ - if (u->job_id > 0) - return true; - - if (streq(u->active_state, "inactive")) - return false; - - return true; -} - -static int output_units_list(const UnitInfo *unit_infos, unsigned c) { - unsigned circle_len = 0, id_len, max_id_len, load_len, active_len, sub_len, job_len, desc_len; - const UnitInfo *u; - unsigned n_shown = 0; - int job_count = 0; - - max_id_len = strlen("UNIT"); - load_len = strlen("LOAD"); - active_len = strlen("ACTIVE"); - sub_len = strlen("SUB"); - job_len = strlen("JOB"); - desc_len = 0; - - for (u = unit_infos; u < unit_infos + c; u++) { - max_id_len = MAX(max_id_len, strlen(u->id) + (u->machine ? strlen(u->machine)+1 : 0)); - load_len = MAX(load_len, strlen(u->load_state)); - active_len = MAX(active_len, strlen(u->active_state)); - sub_len = MAX(sub_len, strlen(u->sub_state)); - - if (u->job_id != 0) { - job_len = MAX(job_len, strlen(u->job_type)); - job_count++; - } - - if (!arg_no_legend && - (streq(u->active_state, "failed") || - STR_IN_SET(u->load_state, "error", "not-found", "masked"))) - circle_len = 2; - } - - if (!arg_full && original_stdout_is_tty) { - unsigned basic_len; - - id_len = MIN(max_id_len, 25u); - basic_len = circle_len + 5 + id_len + 5 + active_len + sub_len; - - if (job_count) - basic_len += job_len + 1; - - if (basic_len < (unsigned) columns()) { - unsigned extra_len, incr; - extra_len = columns() - basic_len; - - /* Either UNIT already got 25, or is fully satisfied. - * Grant up to 25 to DESC now. */ - incr = MIN(extra_len, 25u); - desc_len += incr; - extra_len -= incr; - - /* split the remaining space between UNIT and DESC, - * but do not give UNIT more than it needs. */ - if (extra_len > 0) { - incr = MIN(extra_len / 2, max_id_len - id_len); - id_len += incr; - desc_len += extra_len - incr; - } - } - } else - id_len = max_id_len; - - for (u = unit_infos; u < unit_infos + c; u++) { - _cleanup_free_ char *e = NULL, *j = NULL; - const char *on_loaded = "", *off_loaded = ""; - const char *on_active = "", *off_active = ""; - const char *on_circle = "", *off_circle = ""; - const char *id; - bool circle = false; - - if (!n_shown && !arg_no_legend) { - - if (circle_len > 0) - fputs(" ", stdout); - - printf("%-*s %-*s %-*s %-*s ", - id_len, "UNIT", - load_len, "LOAD", - active_len, "ACTIVE", - sub_len, "SUB"); - - if (job_count) - printf("%-*s ", job_len, "JOB"); - - if (!arg_full && arg_no_pager) - printf("%.*s\n", desc_len, "DESCRIPTION"); - else - printf("%s\n", "DESCRIPTION"); - } - - n_shown++; - - if (STR_IN_SET(u->load_state, "error", "not-found", "masked") && !arg_plain) { - on_loaded = ansi_highlight_red(); - on_circle = ansi_highlight_yellow(); - off_loaded = off_circle = ansi_normal(); - circle = true; - } else if (streq(u->active_state, "failed") && !arg_plain) { - on_circle = on_active = ansi_highlight_red(); - off_circle = off_active = ansi_normal(); - circle = true; - } - - if (u->machine) { - j = strjoin(u->machine, ":", u->id, NULL); - if (!j) - return log_oom(); - - id = j; - } else - id = u->id; - - if (arg_full) { - e = ellipsize(id, id_len, 33); - if (!e) - return log_oom(); - - id = e; - } - - if (circle_len > 0) - printf("%s%s%s ", on_circle, circle ? special_glyph(BLACK_CIRCLE) : " ", off_circle); - - printf("%s%-*s%s %s%-*s%s %s%-*s %-*s%s %-*s", - on_active, id_len, id, off_active, - on_loaded, load_len, u->load_state, off_loaded, - on_active, active_len, u->active_state, - sub_len, u->sub_state, off_active, - job_count ? job_len + 1 : 0, u->job_id ? u->job_type : ""); - - if (desc_len > 0) - printf("%.*s\n", desc_len, u->description); - else - printf("%s\n", u->description); - } - - if (!arg_no_legend) { - const char *on, *off; - - if (n_shown) { - puts("\n" - "LOAD = Reflects whether the unit definition was properly loaded.\n" - "ACTIVE = The high-level unit activation state, i.e. generalization of SUB.\n" - "SUB = The low-level unit activation state, values depend on unit type."); - puts(job_count ? "JOB = Pending job for the unit.\n" : ""); - on = ansi_highlight(); - off = ansi_normal(); - } else { - on = ansi_highlight_red(); - off = ansi_normal(); - } - - if (arg_all) - printf("%s%u loaded units listed.%s\n" - "To show all installed unit files use 'systemctl list-unit-files'.\n", - on, n_shown, off); - else - printf("%s%u loaded units listed.%s Pass --all to see loaded but inactive units, too.\n" - "To show all installed unit files use 'systemctl list-unit-files'.\n", - on, n_shown, off); - } - - return 0; -} - -static int get_unit_list( - sd_bus *bus, - const char *machine, - char **patterns, - UnitInfo **unit_infos, - int c, - sd_bus_message **_reply) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - size_t size = c; - int r; - UnitInfo u; - bool fallback = false; - - assert(bus); - assert(unit_infos); - assert(_reply); - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "ListUnitsByPatterns"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_strv(m, arg_states); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_strv(m, patterns); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) { - /* Fallback to legacy ListUnitsFiltered method */ - fallback = true; - log_debug_errno(r, "Failed to list units: %s Falling back to ListUnitsFiltered method.", bus_error_message(&error, r)); - m = sd_bus_message_unref(m); - sd_bus_error_free(&error); - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "ListUnitsFiltered"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_strv(m, arg_states); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, &reply); - } - if (r < 0) - return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r)); - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = bus_parse_unit_info(reply, &u)) > 0) { - u.machine = machine; - - if (!output_show_unit(&u, fallback ? patterns : NULL)) - continue; - - if (!GREEDY_REALLOC(*unit_infos, size, c+1)) - return log_oom(); - - (*unit_infos)[c++] = u; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - *_reply = reply; - reply = NULL; - - return c; -} - -static void message_set_freep(Set **set) { - sd_bus_message *m; - - while ((m = set_steal_first(*set))) - sd_bus_message_unref(m); - - set_free(*set); -} - -static int get_unit_list_recursive( - sd_bus *bus, - char **patterns, - UnitInfo **_unit_infos, - Set **_replies, - char ***_machines) { - - _cleanup_free_ UnitInfo *unit_infos = NULL; - _cleanup_(message_set_freep) Set *replies; - sd_bus_message *reply; - int c, r; - - assert(bus); - assert(_replies); - assert(_unit_infos); - assert(_machines); - - replies = set_new(NULL); - if (!replies) - return log_oom(); - - c = get_unit_list(bus, NULL, patterns, &unit_infos, 0, &reply); - if (c < 0) - return c; - - r = set_put(replies, reply); - if (r < 0) { - sd_bus_message_unref(reply); - return log_oom(); - } - - if (arg_recursive) { - _cleanup_strv_free_ char **machines = NULL; - char **i; - - r = sd_get_machine_names(&machines); - if (r < 0) - return log_error_errno(r, "Failed to get machine names: %m"); - - STRV_FOREACH(i, machines) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *container = NULL; - int k; - - r = sd_bus_open_system_machine(&container, *i); - if (r < 0) { - log_warning_errno(r, "Failed to connect to container %s, ignoring: %m", *i); - continue; - } - - k = get_unit_list(container, *i, patterns, &unit_infos, c, &reply); - if (k < 0) - return k; - - c = k; - - r = set_put(replies, reply); - if (r < 0) { - sd_bus_message_unref(reply); - return log_oom(); - } - } - - *_machines = machines; - machines = NULL; - } else - *_machines = NULL; - - *_unit_infos = unit_infos; - unit_infos = NULL; - - *_replies = replies; - replies = NULL; - - return c; -} - -static int list_units(int argc, char *argv[], void *userdata) { - _cleanup_free_ UnitInfo *unit_infos = NULL; - _cleanup_(message_set_freep) Set *replies = NULL; - _cleanup_strv_free_ char **machines = NULL; - sd_bus *bus; - int r; - - pager_open(arg_no_pager, false); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines); - if (r < 0) - return r; - - qsort_safe(unit_infos, r, sizeof(UnitInfo), compare_unit_info); - return output_units_list(unit_infos, r); -} - -static int get_triggered_units( - sd_bus *bus, - const char* path, - char*** ret) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(path); - assert(ret); - - r = sd_bus_get_property_strv( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "Triggers", - &error, - ret); - if (r < 0) - return log_error_errno(r, "Failed to determine triggers: %s", bus_error_message(&error, r)); - - return 0; -} - -static int get_listening( - sd_bus *bus, - const char* unit_path, - char*** listening) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *type, *path; - int r, n = 0; - - r = sd_bus_get_property( - bus, - "org.freedesktop.systemd1", - unit_path, - "org.freedesktop.systemd1.Socket", - "Listen", - &error, - &reply, - "a(ss)"); - if (r < 0) - return log_error_errno(r, "Failed to get list of listening sockets: %s", bus_error_message(&error, r)); - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "(ss)", &type, &path)) > 0) { - - r = strv_extend(listening, type); - if (r < 0) - return log_oom(); - - r = strv_extend(listening, path); - if (r < 0) - return log_oom(); - - n++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - return n; -} - -struct socket_info { - const char *machine; - const char* id; - - char* type; - char* path; - - /* Note: triggered is a list here, although it almost certainly - * will always be one unit. Nevertheless, dbus API allows for multiple - * values, so let's follow that. */ - char** triggered; - - /* The strv above is shared. free is set only in the first one. */ - bool own_triggered; -}; - -static int socket_info_compare(const struct socket_info *a, const struct socket_info *b) { - int o; - - assert(a); - assert(b); - - if (!a->machine && b->machine) - return -1; - if (a->machine && !b->machine) - return 1; - if (a->machine && b->machine) { - o = strcasecmp(a->machine, b->machine); - if (o != 0) - return o; - } - - o = strcmp(a->path, b->path); - if (o == 0) - o = strcmp(a->type, b->type); - - return o; -} - -static int output_sockets_list(struct socket_info *socket_infos, unsigned cs) { - struct socket_info *s; - unsigned pathlen = strlen("LISTEN"), - typelen = strlen("TYPE") * arg_show_types, - socklen = strlen("UNIT"), - servlen = strlen("ACTIVATES"); - const char *on, *off; - - for (s = socket_infos; s < socket_infos + cs; s++) { - unsigned tmp = 0; - char **a; - - socklen = MAX(socklen, strlen(s->id)); - if (arg_show_types) - typelen = MAX(typelen, strlen(s->type)); - pathlen = MAX(pathlen, strlen(s->path) + (s->machine ? strlen(s->machine)+1 : 0)); - - STRV_FOREACH(a, s->triggered) - tmp += strlen(*a) + 2*(a != s->triggered); - servlen = MAX(servlen, tmp); - } - - if (cs) { - if (!arg_no_legend) - printf("%-*s %-*.*s%-*s %s\n", - pathlen, "LISTEN", - typelen + arg_show_types, typelen + arg_show_types, "TYPE ", - socklen, "UNIT", - "ACTIVATES"); - - for (s = socket_infos; s < socket_infos + cs; s++) { - _cleanup_free_ char *j = NULL; - const char *path; - char **a; - - if (s->machine) { - j = strjoin(s->machine, ":", s->path, NULL); - if (!j) - return log_oom(); - path = j; - } else - path = s->path; - - if (arg_show_types) - printf("%-*s %-*s %-*s", - pathlen, path, typelen, s->type, socklen, s->id); - else - printf("%-*s %-*s", - pathlen, path, socklen, s->id); - STRV_FOREACH(a, s->triggered) - printf("%s %s", - a == s->triggered ? "" : ",", *a); - printf("\n"); - } - - on = ansi_highlight(); - off = ansi_normal(); - if (!arg_no_legend) - printf("\n"); - } else { - on = ansi_highlight_red(); - off = ansi_normal(); - } - - if (!arg_no_legend) { - printf("%s%u sockets listed.%s\n", on, cs, off); - if (!arg_all) - printf("Pass --all to see loaded but inactive sockets, too.\n"); - } - - return 0; -} - -static int list_sockets(int argc, char *argv[], void *userdata) { - _cleanup_(message_set_freep) Set *replies = NULL; - _cleanup_strv_free_ char **machines = NULL; - _cleanup_free_ UnitInfo *unit_infos = NULL; - _cleanup_free_ struct socket_info *socket_infos = NULL; - const UnitInfo *u; - struct socket_info *s; - unsigned cs = 0; - size_t size = 0; - int r = 0, n; - sd_bus *bus; - - pager_open(arg_no_pager, false); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - n = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines); - if (n < 0) - return n; - - for (u = unit_infos; u < unit_infos + n; u++) { - _cleanup_strv_free_ char **listening = NULL, **triggered = NULL; - int i, c; - - if (!endswith(u->id, ".socket")) - continue; - - r = get_triggered_units(bus, u->unit_path, &triggered); - if (r < 0) - goto cleanup; - - c = get_listening(bus, u->unit_path, &listening); - if (c < 0) { - r = c; - goto cleanup; - } - - if (!GREEDY_REALLOC(socket_infos, size, cs + c)) { - r = log_oom(); - goto cleanup; - } - - for (i = 0; i < c; i++) - socket_infos[cs + i] = (struct socket_info) { - .machine = u->machine, - .id = u->id, - .type = listening[i*2], - .path = listening[i*2 + 1], - .triggered = triggered, - .own_triggered = i==0, - }; - - /* from this point on we will cleanup those socket_infos */ - cs += c; - free(listening); - listening = triggered = NULL; /* avoid cleanup */ - } - - qsort_safe(socket_infos, cs, sizeof(struct socket_info), - (__compar_fn_t) socket_info_compare); - - output_sockets_list(socket_infos, cs); - - cleanup: - assert(cs == 0 || socket_infos); - for (s = socket_infos; s < socket_infos + cs; s++) { - free(s->type); - free(s->path); - if (s->own_triggered) - strv_free(s->triggered); - } - - return r; -} - -static int get_next_elapse( - sd_bus *bus, - const char *path, - dual_timestamp *next) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - dual_timestamp t; - int r; - - assert(bus); - assert(path); - assert(next); - - r = sd_bus_get_property_trivial( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Timer", - "NextElapseUSecMonotonic", - &error, - 't', - &t.monotonic); - if (r < 0) - return log_error_errno(r, "Failed to get next elapsation time: %s", bus_error_message(&error, r)); - - r = sd_bus_get_property_trivial( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Timer", - "NextElapseUSecRealtime", - &error, - 't', - &t.realtime); - if (r < 0) - return log_error_errno(r, "Failed to get next elapsation time: %s", bus_error_message(&error, r)); - - *next = t; - return 0; -} - -static int get_last_trigger( - sd_bus *bus, - const char *path, - usec_t *last) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(path); - assert(last); - - r = sd_bus_get_property_trivial( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Timer", - "LastTriggerUSec", - &error, - 't', - last); - if (r < 0) - return log_error_errno(r, "Failed to get last trigger time: %s", bus_error_message(&error, r)); - - return 0; -} - -struct timer_info { - const char* machine; - const char* id; - usec_t next_elapse; - usec_t last_trigger; - char** triggered; -}; - -static int timer_info_compare(const struct timer_info *a, const struct timer_info *b) { - int o; - - assert(a); - assert(b); - - if (!a->machine && b->machine) - return -1; - if (a->machine && !b->machine) - return 1; - if (a->machine && b->machine) { - o = strcasecmp(a->machine, b->machine); - if (o != 0) - return o; - } - - if (a->next_elapse < b->next_elapse) - return -1; - if (a->next_elapse > b->next_elapse) - return 1; - - return strcmp(a->id, b->id); -} - -static int output_timers_list(struct timer_info *timer_infos, unsigned n) { - struct timer_info *t; - unsigned - nextlen = strlen("NEXT"), - leftlen = strlen("LEFT"), - lastlen = strlen("LAST"), - passedlen = strlen("PASSED"), - unitlen = strlen("UNIT"), - activatelen = strlen("ACTIVATES"); - - const char *on, *off; - - assert(timer_infos || n == 0); - - for (t = timer_infos; t < timer_infos + n; t++) { - unsigned ul = 0; - char **a; - - if (t->next_elapse > 0) { - char tstamp[FORMAT_TIMESTAMP_MAX] = "", trel[FORMAT_TIMESTAMP_RELATIVE_MAX] = ""; - - format_timestamp(tstamp, sizeof(tstamp), t->next_elapse); - nextlen = MAX(nextlen, strlen(tstamp) + 1); - - format_timestamp_relative(trel, sizeof(trel), t->next_elapse); - leftlen = MAX(leftlen, strlen(trel)); - } - - if (t->last_trigger > 0) { - char tstamp[FORMAT_TIMESTAMP_MAX] = "", trel[FORMAT_TIMESTAMP_RELATIVE_MAX] = ""; - - format_timestamp(tstamp, sizeof(tstamp), t->last_trigger); - lastlen = MAX(lastlen, strlen(tstamp) + 1); - - format_timestamp_relative(trel, sizeof(trel), t->last_trigger); - passedlen = MAX(passedlen, strlen(trel)); - } - - unitlen = MAX(unitlen, strlen(t->id) + (t->machine ? strlen(t->machine)+1 : 0)); - - STRV_FOREACH(a, t->triggered) - ul += strlen(*a) + 2*(a != t->triggered); - - activatelen = MAX(activatelen, ul); - } - - if (n > 0) { - if (!arg_no_legend) - printf("%-*s %-*s %-*s %-*s %-*s %s\n", - nextlen, "NEXT", - leftlen, "LEFT", - lastlen, "LAST", - passedlen, "PASSED", - unitlen, "UNIT", - "ACTIVATES"); - - for (t = timer_infos; t < timer_infos + n; t++) { - _cleanup_free_ char *j = NULL; - const char *unit; - char tstamp1[FORMAT_TIMESTAMP_MAX] = "n/a", trel1[FORMAT_TIMESTAMP_RELATIVE_MAX] = "n/a"; - char tstamp2[FORMAT_TIMESTAMP_MAX] = "n/a", trel2[FORMAT_TIMESTAMP_RELATIVE_MAX] = "n/a"; - char **a; - - format_timestamp(tstamp1, sizeof(tstamp1), t->next_elapse); - format_timestamp_relative(trel1, sizeof(trel1), t->next_elapse); - - format_timestamp(tstamp2, sizeof(tstamp2), t->last_trigger); - format_timestamp_relative(trel2, sizeof(trel2), t->last_trigger); - - if (t->machine) { - j = strjoin(t->machine, ":", t->id, NULL); - if (!j) - return log_oom(); - unit = j; - } else - unit = t->id; - - printf("%-*s %-*s %-*s %-*s %-*s", - nextlen, tstamp1, leftlen, trel1, lastlen, tstamp2, passedlen, trel2, unitlen, unit); - - STRV_FOREACH(a, t->triggered) - printf("%s %s", - a == t->triggered ? "" : ",", *a); - printf("\n"); - } - - on = ansi_highlight(); - off = ansi_normal(); - if (!arg_no_legend) - printf("\n"); - } else { - on = ansi_highlight_red(); - off = ansi_normal(); - } - - if (!arg_no_legend) { - printf("%s%u timers listed.%s\n", on, n, off); - if (!arg_all) - printf("Pass --all to see loaded but inactive timers, too.\n"); - } - - return 0; -} - -static usec_t calc_next_elapse(dual_timestamp *nw, dual_timestamp *next) { - usec_t next_elapse; - - assert(nw); - assert(next); - - if (next->monotonic != USEC_INFINITY && next->monotonic > 0) { - usec_t converted; - - if (next->monotonic > nw->monotonic) - converted = nw->realtime + (next->monotonic - nw->monotonic); - else - converted = nw->realtime - (nw->monotonic - next->monotonic); - - if (next->realtime != USEC_INFINITY && next->realtime > 0) - next_elapse = MIN(converted, next->realtime); - else - next_elapse = converted; - - } else - next_elapse = next->realtime; - - return next_elapse; -} - -static int list_timers(int argc, char *argv[], void *userdata) { - _cleanup_(message_set_freep) Set *replies = NULL; - _cleanup_strv_free_ char **machines = NULL; - _cleanup_free_ struct timer_info *timer_infos = NULL; - _cleanup_free_ UnitInfo *unit_infos = NULL; - struct timer_info *t; - const UnitInfo *u; - size_t size = 0; - int n, c = 0; - dual_timestamp nw; - sd_bus *bus; - int r = 0; - - pager_open(arg_no_pager, false); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - n = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines); - if (n < 0) - return n; - - dual_timestamp_get(&nw); - - for (u = unit_infos; u < unit_infos + n; u++) { - _cleanup_strv_free_ char **triggered = NULL; - dual_timestamp next = DUAL_TIMESTAMP_NULL; - usec_t m, last = 0; - - if (!endswith(u->id, ".timer")) - continue; - - r = get_triggered_units(bus, u->unit_path, &triggered); - if (r < 0) - goto cleanup; - - r = get_next_elapse(bus, u->unit_path, &next); - if (r < 0) - goto cleanup; - - get_last_trigger(bus, u->unit_path, &last); - - if (!GREEDY_REALLOC(timer_infos, size, c+1)) { - r = log_oom(); - goto cleanup; - } - - m = calc_next_elapse(&nw, &next); - - timer_infos[c++] = (struct timer_info) { - .machine = u->machine, - .id = u->id, - .next_elapse = m, - .last_trigger = last, - .triggered = triggered, - }; - - triggered = NULL; /* avoid cleanup */ - } - - qsort_safe(timer_infos, c, sizeof(struct timer_info), - (__compar_fn_t) timer_info_compare); - - output_timers_list(timer_infos, c); - - cleanup: - for (t = timer_infos; t < timer_infos + c; t++) - strv_free(t->triggered); - - return r; -} - -static int compare_unit_file_list(const void *a, const void *b) { - const char *d1, *d2; - const UnitFileList *u = a, *v = b; - - d1 = strrchr(u->path, '.'); - d2 = strrchr(v->path, '.'); - - if (d1 && d2) { - int r; - - r = strcasecmp(d1, d2); - if (r != 0) - return r; - } - - return strcasecmp(basename(u->path), basename(v->path)); -} - -static bool output_show_unit_file(const UnitFileList *u, char **states, char **patterns) { - assert(u); - - if (!strv_fnmatch_or_empty(patterns, basename(u->path), FNM_NOESCAPE)) - return false; - - if (!strv_isempty(arg_types)) { - const char *dot; - - dot = strrchr(u->path, '.'); - if (!dot) - return false; - - if (!strv_find(arg_types, dot+1)) - return false; - } - - if (!strv_isempty(states) && - !strv_find(states, unit_file_state_to_string(u->state))) - return false; - - return true; -} - -static void output_unit_file_list(const UnitFileList *units, unsigned c) { - unsigned max_id_len, id_cols, state_cols; - const UnitFileList *u; - - max_id_len = strlen("UNIT FILE"); - state_cols = strlen("STATE"); - - for (u = units; u < units + c; u++) { - max_id_len = MAX(max_id_len, strlen(basename(u->path))); - state_cols = MAX(state_cols, strlen(unit_file_state_to_string(u->state))); - } - - if (!arg_full) { - unsigned basic_cols; - - id_cols = MIN(max_id_len, 25u); - basic_cols = 1 + id_cols + state_cols; - if (basic_cols < (unsigned) columns()) - id_cols += MIN(columns() - basic_cols, max_id_len - id_cols); - } else - id_cols = max_id_len; - - if (!arg_no_legend && c > 0) - printf("%-*s %-*s\n", - id_cols, "UNIT FILE", - state_cols, "STATE"); - - for (u = units; u < units + c; u++) { - _cleanup_free_ char *e = NULL; - const char *on, *off; - const char *id; - - if (IN_SET(u->state, - UNIT_FILE_MASKED, - UNIT_FILE_MASKED_RUNTIME, - UNIT_FILE_DISABLED, - UNIT_FILE_BAD)) { - on = ansi_highlight_red(); - off = ansi_normal(); - } else if (u->state == UNIT_FILE_ENABLED) { - on = ansi_highlight_green(); - off = ansi_normal(); - } else - on = off = ""; - - id = basename(u->path); - - e = arg_full ? NULL : ellipsize(id, id_cols, 33); - - printf("%-*s %s%-*s%s\n", - id_cols, e ? e : id, - on, state_cols, unit_file_state_to_string(u->state), off); - } - - if (!arg_no_legend) - printf("\n%u unit files listed.\n", c); -} - -static int list_unit_files(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ UnitFileList *units = NULL; - UnitFileList *unit; - size_t size = 0; - unsigned c = 0; - const char *state; - char *path; - int r; - bool fallback = false; - - pager_open(arg_no_pager, false); - - if (install_client_side()) { - Hashmap *h; - UnitFileList *u; - Iterator i; - unsigned n_units; - - h = hashmap_new(&string_hash_ops); - if (!h) - return log_oom(); - - r = unit_file_get_list(arg_scope, arg_root, h, arg_states, strv_skip(argv, 1)); - if (r < 0) { - unit_file_list_free(h); - return log_error_errno(r, "Failed to get unit file list: %m"); - } - - n_units = hashmap_size(h); - - units = new(UnitFileList, n_units ?: 1); /* avoid malloc(0) */ - if (!units) { - unit_file_list_free(h); - return log_oom(); - } - - HASHMAP_FOREACH(u, h, i) { - if (!output_show_unit_file(u, NULL, NULL)) - continue; - - units[c++] = *u; - free(u); - } - - assert(c <= n_units); - hashmap_free(h); - - r = 0; - } else { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus; - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "ListUnitFilesByPatterns"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_strv(m, arg_states); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_strv(m, strv_skip(argv, 1)); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) { - /* Fallback to legacy ListUnitFiles method */ - fallback = true; - log_debug_errno(r, "Failed to list unit files: %s Falling back to ListUnitsFiles method.", bus_error_message(&error, r)); - m = sd_bus_message_unref(m); - sd_bus_error_free(&error); - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "ListUnitFiles"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, &reply); - } - if (r < 0) - return log_error_errno(r, "Failed to list unit files: %s", bus_error_message(&error, r)); - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "(ss)", &path, &state)) > 0) { - - if (!GREEDY_REALLOC(units, size, c + 1)) - return log_oom(); - - units[c] = (struct UnitFileList) { - path, - unit_file_state_from_string(state) - }; - - if (output_show_unit_file(&units[c], - fallback ? arg_states : NULL, - fallback ? strv_skip(argv, 1) : NULL)) - c++; - - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - } - - qsort_safe(units, c, sizeof(UnitFileList), compare_unit_file_list); - output_unit_file_list(units, c); - - if (install_client_side()) - for (unit = units; unit < units + c; unit++) - free(unit->path); - - return 0; -} - -static int list_dependencies_print(const char *name, int level, unsigned int branches, bool last) { - _cleanup_free_ char *n = NULL; - size_t max_len = MAX(columns(),20u); - size_t len = 0; - int i; - - if (!arg_plain) { - - for (i = level - 1; i >= 0; i--) { - len += 2; - if (len > max_len - 3 && !arg_full) { - printf("%s...\n",max_len % 2 ? "" : " "); - return 0; - } - printf("%s", special_glyph(branches & (1 << i) ? TREE_VERTICAL : TREE_SPACE)); - } - len += 2; - - if (len > max_len - 3 && !arg_full) { - printf("%s...\n",max_len % 2 ? "" : " "); - return 0; - } - - printf("%s", special_glyph(last ? TREE_RIGHT : TREE_BRANCH)); - } - - if (arg_full) { - printf("%s\n", name); - return 0; - } - - n = ellipsize(name, max_len-len, 100); - if (!n) - return log_oom(); - - printf("%s\n", n); - return 0; -} - -static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) { - - static const char *dependencies[_DEPENDENCY_MAX] = { - [DEPENDENCY_FORWARD] = "Requires\0" - "Requisite\0" - "Wants\0" - "ConsistsOf\0" - "BindsTo\0", - [DEPENDENCY_REVERSE] = "RequiredBy\0" - "RequisiteOf\0" - "WantedBy\0" - "PartOf\0" - "BoundBy\0", - [DEPENDENCY_AFTER] = "After\0", - [DEPENDENCY_BEFORE] = "Before\0", - }; - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_strv_free_ char **ret = NULL; - _cleanup_free_ char *path = NULL; - int r; - - assert(bus); - assert(name); - assert(deps); - assert_cc(ELEMENTSOF(dependencies) == _DEPENDENCY_MAX); - - path = unit_dbus_path_from_name(name); - if (!path) - return log_oom(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.DBus.Properties", - "GetAll", - &error, - &reply, - "s", "org.freedesktop.systemd1.Unit"); - if (r < 0) - return log_error_errno(r, "Failed to get properties of %s: %s", name, bus_error_message(&error, r)); - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sv}"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { - const char *prop; - - r = sd_bus_message_read(reply, "s", &prop); - if (r < 0) - return bus_log_parse_error(r); - - if (!nulstr_contains(dependencies[arg_dependency], prop)) { - r = sd_bus_message_skip(reply, "v"); - if (r < 0) - return bus_log_parse_error(r); - } else { - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_VARIANT, "as"); - if (r < 0) - return bus_log_parse_error(r); - - r = bus_message_read_strv_extend(reply, &ret); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - *deps = ret; - ret = NULL; - - return 0; -} - -static int list_dependencies_compare(const void *_a, const void *_b) { - const char **a = (const char**) _a, **b = (const char**) _b; - - if (unit_name_to_type(*a) == UNIT_TARGET && unit_name_to_type(*b) != UNIT_TARGET) - return 1; - if (unit_name_to_type(*a) != UNIT_TARGET && unit_name_to_type(*b) == UNIT_TARGET) - return -1; - - return strcasecmp(*a, *b); -} - -static int list_dependencies_one( - sd_bus *bus, - const char *name, - int level, - char ***units, - unsigned int branches) { - - _cleanup_strv_free_ char **deps = NULL; - char **c; - int r = 0; - - assert(bus); - assert(name); - assert(units); - - r = strv_extend(units, name); - if (r < 0) - return log_oom(); - - r = list_dependencies_get_dependencies(bus, name, &deps); - if (r < 0) - return r; - - qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare); - - STRV_FOREACH(c, deps) { - if (strv_contains(*units, *c)) { - if (!arg_plain) { - r = list_dependencies_print("...", level + 1, (branches << 1) | (c[1] == NULL ? 0 : 1), 1); - if (r < 0) - return r; - } - continue; - } - - if (arg_plain) - printf(" "); - else { - UnitActiveState active_state = _UNIT_ACTIVE_STATE_INVALID; - const char *on; - - (void) get_state_one_unit(bus, *c, &active_state); - - switch (active_state) { - case UNIT_ACTIVE: - case UNIT_RELOADING: - case UNIT_ACTIVATING: - on = ansi_highlight_green(); - break; - - case UNIT_INACTIVE: - case UNIT_DEACTIVATING: - on = ansi_normal(); - break; - - default: - on = ansi_highlight_red(); - break; - } - - printf("%s%s%s ", on, special_glyph(BLACK_CIRCLE), ansi_normal()); - } - - r = list_dependencies_print(*c, level, branches, c[1] == NULL); - if (r < 0) - return r; - - if (arg_all || unit_name_to_type(*c) == UNIT_TARGET) { - r = list_dependencies_one(bus, *c, level + 1, units, (branches << 1) | (c[1] == NULL ? 0 : 1)); - if (r < 0) - return r; - } - } - - if (!arg_plain) - strv_remove(*units, name); - - return 0; -} - -static int list_dependencies(int argc, char *argv[], void *userdata) { - _cleanup_strv_free_ char **units = NULL; - _cleanup_free_ char *unit = NULL; - const char *u; - sd_bus *bus; - int r; - - if (argv[1]) { - r = unit_name_mangle(argv[1], UNIT_NAME_NOGLOB, &unit); - if (r < 0) - return log_error_errno(r, "Failed to mangle unit name: %m"); - - u = unit; - } else - u = SPECIAL_DEFAULT_TARGET; - - pager_open(arg_no_pager, false); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - puts(u); - - return list_dependencies_one(bus, u, 0, &units, 0); -} - -struct machine_info { - bool is_host; - char *name; - char *state; - char *control_group; - uint32_t n_failed_units; - uint32_t n_jobs; - usec_t timestamp; -}; - -static const struct bus_properties_map machine_info_property_map[] = { - { "SystemState", "s", NULL, offsetof(struct machine_info, state) }, - { "NJobs", "u", NULL, offsetof(struct machine_info, n_jobs) }, - { "NFailedUnits", "u", NULL, offsetof(struct machine_info, n_failed_units) }, - { "ControlGroup", "s", NULL, offsetof(struct machine_info, control_group) }, - { "UserspaceTimestamp", "t", NULL, offsetof(struct machine_info, timestamp) }, - {} -}; - -static void machine_info_clear(struct machine_info *info) { - if (info) { - free(info->name); - free(info->state); - free(info->control_group); - zero(*info); - } -} - -static void free_machines_list(struct machine_info *machine_infos, int n) { - int i; - - if (!machine_infos) - return; - - for (i = 0; i < n; i++) - machine_info_clear(&machine_infos[i]); - - free(machine_infos); -} - -static int compare_machine_info(const void *a, const void *b) { - const struct machine_info *u = a, *v = b; - - if (u->is_host != v->is_host) - return u->is_host > v->is_host ? -1 : 1; - - return strcasecmp(u->name, v->name); -} - -static int get_machine_properties(sd_bus *bus, struct machine_info *mi) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *container = NULL; - int r; - - assert(mi); - - if (!bus) { - r = sd_bus_open_system_machine(&container, mi->name); - if (r < 0) - return r; - - bus = container; - } - - r = bus_map_all_properties(bus, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", machine_info_property_map, mi); - if (r < 0) - return r; - - return 0; -} - -static bool output_show_machine(const char *name, char **patterns) { - return strv_fnmatch_or_empty(patterns, name, FNM_NOESCAPE); -} - -static int get_machine_list( - sd_bus *bus, - struct machine_info **_machine_infos, - char **patterns) { - - struct machine_info *machine_infos = NULL; - _cleanup_strv_free_ char **m = NULL; - _cleanup_free_ char *hn = NULL; - size_t sz = 0; - char **i; - int c = 0, r; - - hn = gethostname_malloc(); - if (!hn) - return log_oom(); - - if (output_show_machine(hn, patterns)) { - if (!GREEDY_REALLOC0(machine_infos, sz, c+1)) - return log_oom(); - - machine_infos[c].is_host = true; - machine_infos[c].name = hn; - hn = NULL; - - get_machine_properties(bus, &machine_infos[c]); - c++; - } - - r = sd_get_machine_names(&m); - if (r < 0) - return log_error_errno(r, "Failed to get machine list: %m"); - - STRV_FOREACH(i, m) { - _cleanup_free_ char *class = NULL; - - if (!output_show_machine(*i, patterns)) - continue; - - sd_machine_get_class(*i, &class); - if (!streq_ptr(class, "container")) - continue; - - if (!GREEDY_REALLOC0(machine_infos, sz, c+1)) { - free_machines_list(machine_infos, c); - return log_oom(); - } - - machine_infos[c].is_host = false; - machine_infos[c].name = strdup(*i); - if (!machine_infos[c].name) { - free_machines_list(machine_infos, c); - return log_oom(); - } - - get_machine_properties(NULL, &machine_infos[c]); - c++; - } - - *_machine_infos = machine_infos; - return c; -} - -static void output_machines_list(struct machine_info *machine_infos, unsigned n) { - struct machine_info *m; - unsigned - circle_len = 0, - namelen = sizeof("NAME") - 1, - statelen = sizeof("STATE") - 1, - failedlen = sizeof("FAILED") - 1, - jobslen = sizeof("JOBS") - 1; - - assert(machine_infos || n == 0); - - for (m = machine_infos; m < machine_infos + n; m++) { - namelen = MAX(namelen, strlen(m->name) + (m->is_host ? sizeof(" (host)") - 1 : 0)); - statelen = MAX(statelen, m->state ? strlen(m->state) : 0); - failedlen = MAX(failedlen, DECIMAL_STR_WIDTH(m->n_failed_units)); - jobslen = MAX(jobslen, DECIMAL_STR_WIDTH(m->n_jobs)); - - if (!arg_plain && !streq_ptr(m->state, "running")) - circle_len = 2; - } - - if (!arg_no_legend) { - if (circle_len > 0) - fputs(" ", stdout); - - printf("%-*s %-*s %-*s %-*s\n", - namelen, "NAME", - statelen, "STATE", - failedlen, "FAILED", - jobslen, "JOBS"); - } - - for (m = machine_infos; m < machine_infos + n; m++) { - const char *on_state = "", *off_state = ""; - const char *on_failed = "", *off_failed = ""; - bool circle = false; - - if (streq_ptr(m->state, "degraded")) { - on_state = ansi_highlight_red(); - off_state = ansi_normal(); - circle = true; - } else if (!streq_ptr(m->state, "running")) { - on_state = ansi_highlight_yellow(); - off_state = ansi_normal(); - circle = true; - } - - if (m->n_failed_units > 0) { - on_failed = ansi_highlight_red(); - off_failed = ansi_normal(); - } else - on_failed = off_failed = ""; - - if (circle_len > 0) - printf("%s%s%s ", on_state, circle ? special_glyph(BLACK_CIRCLE) : " ", off_state); - - if (m->is_host) - printf("%-*s (host) %s%-*s%s %s%*" PRIu32 "%s %*" PRIu32 "\n", - (int) (namelen - (sizeof(" (host)")-1)), strna(m->name), - on_state, statelen, strna(m->state), off_state, - on_failed, failedlen, m->n_failed_units, off_failed, - jobslen, m->n_jobs); - else - printf("%-*s %s%-*s%s %s%*" PRIu32 "%s %*" PRIu32 "\n", - namelen, strna(m->name), - on_state, statelen, strna(m->state), off_state, - on_failed, failedlen, m->n_failed_units, off_failed, - jobslen, m->n_jobs); - } - - if (!arg_no_legend) - printf("\n%u machines listed.\n", n); -} - -static int list_machines(int argc, char *argv[], void *userdata) { - struct machine_info *machine_infos = NULL; - sd_bus *bus; - int r; - - if (geteuid() != 0) { - log_error("Must be root."); - return -EPERM; - } - - pager_open(arg_no_pager, false); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = get_machine_list(bus, &machine_infos, strv_skip(argv, 1)); - if (r < 0) - return r; - - qsort_safe(machine_infos, r, sizeof(struct machine_info), compare_machine_info); - output_machines_list(machine_infos, r); - free_machines_list(machine_infos, r); - - return 0; -} - -static int get_default(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *_path = NULL; - const char *path; - int r; - - if (install_client_side()) { - r = unit_file_get_default(arg_scope, arg_root, &_path); - if (r < 0) - return log_error_errno(r, "Failed to get default target: %m"); - path = _path; - - r = 0; - } else { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus; - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GetDefaultTarget", - &error, - &reply, - NULL); - if (r < 0) - return log_error_errno(r, "Failed to get default target: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "s", &path); - if (r < 0) - return bus_log_parse_error(r); - } - - if (path) - printf("%s\n", path); - - return 0; -} - -static int set_default(int argc, char *argv[], void *userdata) { - _cleanup_free_ char *unit = NULL; - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - int r; - - assert(argc >= 2); - assert(argv); - - r = unit_name_mangle_with_suffix(argv[1], UNIT_NAME_NOGLOB, ".target", &unit); - if (r < 0) - return log_error_errno(r, "Failed to mangle unit name: %m"); - - if (install_client_side()) { - r = unit_file_set_default(arg_scope, arg_root, unit, true, &changes, &n_changes); - unit_file_dump_changes(r, "set default", changes, n_changes, arg_quiet); - - if (r > 0) - r = 0; - } else { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - sd_bus *bus; - - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SetDefaultTarget", - &error, - &reply, - "sb", unit, 1); - if (r < 0) - return log_error_errno(r, "Failed to set default target: %s", bus_error_message(&error, r)); - - r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes); - if (r < 0) - goto finish; - - /* Try to reload if enabled */ - if (!arg_no_reload) - r = daemon_reload(argc, argv, userdata); - else - r = 0; - } - -finish: - unit_file_changes_free(changes, n_changes); - - return r; -} - -struct job_info { - uint32_t id; - const char *name, *type, *state; -}; - -static void output_jobs_list(const struct job_info* jobs, unsigned n, bool skipped) { - unsigned id_len, unit_len, type_len, state_len; - const struct job_info *j; - const char *on, *off; - bool shorten = false; - - assert(n == 0 || jobs); - - if (n == 0) { - if (!arg_no_legend) { - on = ansi_highlight_green(); - off = ansi_normal(); - - printf("%sNo jobs %s.%s\n", on, skipped ? "listed" : "running", off); - } - return; - } - - pager_open(arg_no_pager, false); - - id_len = strlen("JOB"); - unit_len = strlen("UNIT"); - type_len = strlen("TYPE"); - state_len = strlen("STATE"); - - for (j = jobs; j < jobs + n; j++) { - uint32_t id = j->id; - assert(j->name && j->type && j->state); - - id_len = MAX(id_len, DECIMAL_STR_WIDTH(id)); - unit_len = MAX(unit_len, strlen(j->name)); - type_len = MAX(type_len, strlen(j->type)); - state_len = MAX(state_len, strlen(j->state)); - } - - if (!arg_full && id_len + 1 + unit_len + type_len + 1 + state_len > columns()) { - unit_len = MAX(33u, columns() - id_len - type_len - state_len - 3); - shorten = true; - } - - if (!arg_no_legend) - printf("%*s %-*s %-*s %-*s\n", - id_len, "JOB", - unit_len, "UNIT", - type_len, "TYPE", - state_len, "STATE"); - - for (j = jobs; j < jobs + n; j++) { - _cleanup_free_ char *e = NULL; - - if (streq(j->state, "running")) { - on = ansi_highlight(); - off = ansi_normal(); - } else - on = off = ""; - - e = shorten ? ellipsize(j->name, unit_len, 33) : NULL; - printf("%*u %s%-*s%s %-*s %s%-*s%s\n", - id_len, j->id, - on, unit_len, e ? e : j->name, off, - type_len, j->type, - on, state_len, j->state, off); - } - - if (!arg_no_legend) { - on = ansi_highlight(); - off = ansi_normal(); - - printf("\n%s%u jobs listed%s.\n", on, n, off); - } -} - -static bool output_show_job(struct job_info *job, char **patterns) { - return strv_fnmatch_or_empty(patterns, job->name, FNM_NOESCAPE); -} - -static int list_jobs(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *name, *type, *state, *job_path, *unit_path; - _cleanup_free_ struct job_info *jobs = NULL; - size_t size = 0; - unsigned c = 0; - sd_bus *bus; - uint32_t id; - int r; - bool skipped = false; - - pager_open(arg_no_pager, false); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "ListJobs", - &error, - &reply, - NULL); - if (r < 0) - return log_error_errno(r, "Failed to list jobs: %s", bus_error_message(&error, r)); - - r = sd_bus_message_enter_container(reply, 'a', "(usssoo)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "(usssoo)", &id, &name, &type, &state, &job_path, &unit_path)) > 0) { - struct job_info job = { id, name, type, state }; - - if (!output_show_job(&job, strv_skip(argv, 1))) { - skipped = true; - continue; - } - - if (!GREEDY_REALLOC(jobs, size, c + 1)) - return log_oom(); - - jobs[c++] = job; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - output_jobs_list(jobs, c, skipped); - return 0; -} - -static int cancel_job(int argc, char *argv[], void *userdata) { - sd_bus *bus; - char **name; - int r = 0; - - if (argc <= 1) - return daemon_reload(argc, argv, userdata); - - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - STRV_FOREACH(name, strv_skip(argv, 1)) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - uint32_t id; - int q; - - q = safe_atou32(*name, &id); - if (q < 0) - return log_error_errno(q, "Failed to parse job id \"%s\": %m", *name); - - q = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "CancelJob", - &error, - NULL, - "u", id); - if (q < 0) { - log_error_errno(q, "Failed to cancel job %"PRIu32": %s", id, bus_error_message(&error, q)); - if (r == 0) - r = q; - } - } - - return r; -} - -static int need_daemon_reload(sd_bus *bus, const char *unit) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *path; - int b, r; - - /* We ignore all errors here, since this is used to show a - * warning only */ - - /* We don't use unit_dbus_path_from_name() directly since we - * don't want to load the unit if it isn't loaded. */ - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GetUnit", - NULL, - &reply, - "s", unit); - if (r < 0) - return r; - - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return r; - - r = sd_bus_get_property_trivial( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "NeedDaemonReload", - NULL, - 'b', &b); - if (r < 0) - return r; - - return b; -} - -static void warn_unit_file_changed(const char *name) { - assert(name); - - log_warning("%sWarning:%s %s changed on disk. Run 'systemctl%s daemon-reload' to reload units.", - ansi_highlight_red(), - ansi_normal(), - name, - arg_scope == UNIT_FILE_SYSTEM ? "" : " --user"); -} - -static int unit_file_find_path(LookupPaths *lp, const char *unit_name, char **unit_path) { - char **p; - - assert(lp); - assert(unit_name); - assert(unit_path); - - STRV_FOREACH(p, lp->search_path) { - _cleanup_free_ char *path; - - path = path_join(arg_root, *p, unit_name); - if (!path) - return log_oom(); - - if (access(path, F_OK) == 0) { - *unit_path = path; - path = NULL; - return 1; - } - } - - return 0; -} - -static int unit_find_paths( - sd_bus *bus, - const char *unit_name, - LookupPaths *lp, - char **fragment_path, - char ***dropin_paths) { - - _cleanup_free_ char *path = NULL; - _cleanup_strv_free_ char **dropins = NULL; - int r; - - /** - * Finds where the unit is defined on disk. Returns 0 if the unit - * is not found. Returns 1 if it is found, and sets - * - the path to the unit in *path, if it exists on disk, - * - and a strv of existing drop-ins in *dropins, - * if the arg is not NULL and any dropins were found. - */ - - assert(unit_name); - assert(fragment_path); - assert(lp); - - if (!install_client_side() && !unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *unit = NULL; - - unit = unit_dbus_path_from_name(unit_name); - if (!unit) - return log_oom(); - - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - unit, - "org.freedesktop.systemd1.Unit", - "FragmentPath", - &error, - &path); - if (r < 0) - return log_error_errno(r, "Failed to get FragmentPath: %s", bus_error_message(&error, r)); - - if (dropin_paths) { - r = sd_bus_get_property_strv( - bus, - "org.freedesktop.systemd1", - unit, - "org.freedesktop.systemd1.Unit", - "DropInPaths", - &error, - &dropins); - if (r < 0) - return log_error_errno(r, "Failed to get DropInPaths: %s", bus_error_message(&error, r)); - } - } else { - _cleanup_set_free_ Set *names; - - names = set_new(NULL); - if (!names) - return log_oom(); - - r = set_put(names, unit_name); - if (r < 0) - return log_error_errno(r, "Failed to add unit name: %m"); - - r = unit_file_find_path(lp, unit_name, &path); - if (r < 0) - return r; - - if (r == 0) { - _cleanup_free_ char *template = NULL; - - r = unit_name_template(unit_name, &template); - if (r < 0 && r != -EINVAL) - return log_error_errno(r, "Failed to determine template name: %m"); - if (r >= 0) { - r = unit_file_find_path(lp, template, &path); - if (r < 0) - return r; - } - } - - if (dropin_paths) { - r = unit_file_find_dropin_paths(lp->search_path, NULL, names, &dropins); - if (r < 0) - return r; - } - } - - r = 0; - - if (!isempty(path)) { - *fragment_path = path; - path = NULL; - r = 1; - } - - if (dropin_paths && !strv_isempty(dropins)) { - *dropin_paths = dropins; - dropins = NULL; - r = 1; - } - - if (r == 0) - log_error("No files found for %s.", unit_name); - - return r; -} - -static int get_state_one_unit(sd_bus *bus, const char *name, UnitActiveState *active_state) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *buf = NULL; - UnitActiveState state; - const char *path; - int r; - - assert(name); - assert(active_state); - - /* We don't use unit_dbus_path_from_name() directly since we don't want to load the unit unnecessarily, if it - * isn't loaded. */ - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GetUnit", - &error, - &reply, - "s", name); - if (r < 0) { - if (!sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT)) - return log_error_errno(r, "Failed to retrieve unit: %s", bus_error_message(&error, r)); - - /* The unit is currently not loaded, hence say it's "inactive", since all units that aren't loaded are - * considered inactive. */ - state = UNIT_INACTIVE; - - } else { - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "ActiveState", - &error, - &buf); - if (r < 0) - return log_error_errno(r, "Failed to retrieve unit state: %s", bus_error_message(&error, r)); - - state = unit_active_state_from_string(buf); - if (state == _UNIT_ACTIVE_STATE_INVALID) { - log_error("Invalid unit state '%s' for: %s", buf, name); - return -EINVAL; - } - } - - *active_state = state; - return 0; -} - -static int check_triggering_units( - sd_bus *bus, - const char *name) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *path = NULL, *n = NULL, *load_state = NULL; - _cleanup_strv_free_ char **triggered_by = NULL; - bool print_warning_label = true; - UnitActiveState active_state; - char **i; - int r; - - r = unit_name_mangle(name, UNIT_NAME_NOGLOB, &n); - if (r < 0) - return log_error_errno(r, "Failed to mangle unit name: %m"); - - path = unit_dbus_path_from_name(n); - if (!path) - return log_oom(); - - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "LoadState", - &error, - &load_state); - if (r < 0) - return log_error_errno(r, "Failed to get load state of %s: %s", n, bus_error_message(&error, r)); - - if (streq(load_state, "masked")) - return 0; - - r = sd_bus_get_property_strv( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "TriggeredBy", - &error, - &triggered_by); - if (r < 0) - return log_error_errno(r, "Failed to get triggered by array of %s: %s", n, bus_error_message(&error, r)); - - STRV_FOREACH(i, triggered_by) { - r = get_state_one_unit(bus, *i, &active_state); - if (r < 0) - return r; - - if (!IN_SET(active_state, UNIT_ACTIVE, UNIT_RELOADING)) - continue; - - if (print_warning_label) { - log_warning("Warning: Stopping %s, but it can still be activated by:", n); - print_warning_label = false; - } - - log_warning(" %s", *i); - } - - return 0; -} - -static const struct { - const char *verb; - const char *method; -} unit_actions[] = { - { "start", "StartUnit" }, - { "stop", "StopUnit" }, - { "condstop", "StopUnit" }, - { "reload", "ReloadUnit" }, - { "restart", "RestartUnit" }, - { "try-restart", "TryRestartUnit" }, - { "condrestart", "TryRestartUnit" }, - { "reload-or-restart", "ReloadOrRestartUnit" }, - { "try-reload-or-restart", "ReloadOrTryRestartUnit" }, - { "reload-or-try-restart", "ReloadOrTryRestartUnit" }, - { "condreload", "ReloadOrTryRestartUnit" }, - { "force-reload", "ReloadOrTryRestartUnit" } -}; - -static const char *verb_to_method(const char *verb) { - uint i; - - for (i = 0; i < ELEMENTSOF(unit_actions); i++) - if (streq_ptr(unit_actions[i].verb, verb)) - return unit_actions[i].method; - - return "StartUnit"; -} - -static const char *method_to_verb(const char *method) { - uint i; - - for (i = 0; i < ELEMENTSOF(unit_actions); i++) - if (streq_ptr(unit_actions[i].method, method)) - return unit_actions[i].verb; - - return "n/a"; -} - -static int start_unit_one( - sd_bus *bus, - const char *method, - const char *name, - const char *mode, - sd_bus_error *error, - BusWaitForJobs *w) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *path; - int r; - - assert(method); - assert(name); - assert(mode); - assert(error); - - log_debug("Calling manager for %s on %s, %s", method, name, mode); - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - method, - error, - &reply, - "ss", name, mode); - if (r < 0) { - const char *verb; - - if (r == -ENOENT && arg_action != ACTION_SYSTEMCTL) - /* There's always a fallback possible for - * legacy actions. */ - return -EADDRNOTAVAIL; - - verb = method_to_verb(method); - - log_error("Failed to %s %s: %s", verb, name, bus_error_message(error, r)); - - if (!sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) && - !sd_bus_error_has_name(error, BUS_ERROR_UNIT_MASKED)) - log_error("See %s logs and 'systemctl%s status %s' for details.", - arg_scope == UNIT_FILE_SYSTEM ? "system" : "user", - arg_scope == UNIT_FILE_SYSTEM ? "" : " --user", - name); - - return r; - } - - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); - - if (need_daemon_reload(bus, name) > 0) - warn_unit_file_changed(name); - - if (w) { - log_debug("Adding %s to the set", path); - r = bus_wait_for_jobs_add(w, path); - if (r < 0) - return log_oom(); - } - - return 0; -} - -static int expand_names(sd_bus *bus, char **names, const char* suffix, char ***ret) { - _cleanup_strv_free_ char **mangled = NULL, **globs = NULL; - char **name; - int r, i; - - assert(bus); - assert(ret); - - STRV_FOREACH(name, names) { - char *t; - - if (suffix) - r = unit_name_mangle_with_suffix(*name, UNIT_NAME_GLOB, suffix, &t); - else - r = unit_name_mangle(*name, UNIT_NAME_GLOB, &t); - if (r < 0) - return log_error_errno(r, "Failed to mangle name: %m"); - - if (string_is_glob(t)) - r = strv_consume(&globs, t); - else - r = strv_consume(&mangled, t); - if (r < 0) - return log_oom(); - } - - /* Query the manager only if any of the names are a glob, since - * this is fairly expensive */ - if (!strv_isempty(globs)) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ UnitInfo *unit_infos = NULL; - size_t allocated, n; - - r = get_unit_list(bus, NULL, globs, &unit_infos, 0, &reply); - if (r < 0) - return r; - - n = strv_length(mangled); - allocated = n + 1; - - for (i = 0; i < r; i++) { - if (!GREEDY_REALLOC(mangled, allocated, n+2)) - return log_oom(); - - mangled[n] = strdup(unit_infos[i].id); - if (!mangled[n]) - return log_oom(); - - mangled[++n] = NULL; - } - } - - *ret = mangled; - mangled = NULL; /* do not free */ - - return 0; -} - -static const struct { - const char *target; - const char *verb; - const char *mode; -} action_table[_ACTION_MAX] = { - [ACTION_HALT] = { SPECIAL_HALT_TARGET, "halt", "replace-irreversibly" }, - [ACTION_POWEROFF] = { SPECIAL_POWEROFF_TARGET, "poweroff", "replace-irreversibly" }, - [ACTION_REBOOT] = { SPECIAL_REBOOT_TARGET, "reboot", "replace-irreversibly" }, - [ACTION_KEXEC] = { SPECIAL_KEXEC_TARGET, "kexec", "replace-irreversibly" }, - [ACTION_RUNLEVEL2] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, - [ACTION_RUNLEVEL3] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, - [ACTION_RUNLEVEL4] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, - [ACTION_RUNLEVEL5] = { SPECIAL_GRAPHICAL_TARGET, NULL, "isolate" }, - [ACTION_RESCUE] = { SPECIAL_RESCUE_TARGET, "rescue", "isolate" }, - [ACTION_EMERGENCY] = { SPECIAL_EMERGENCY_TARGET, "emergency", "isolate" }, - [ACTION_DEFAULT] = { SPECIAL_DEFAULT_TARGET, "default", "isolate" }, - [ACTION_EXIT] = { SPECIAL_EXIT_TARGET, "exit", "replace-irreversibly" }, - [ACTION_SUSPEND] = { SPECIAL_SUSPEND_TARGET, "suspend", "replace-irreversibly" }, - [ACTION_HIBERNATE] = { SPECIAL_HIBERNATE_TARGET, "hibernate", "replace-irreversibly" }, - [ACTION_HYBRID_SLEEP] = { SPECIAL_HYBRID_SLEEP_TARGET, "hybrid-sleep", "replace-irreversibly" }, -}; - -static enum action verb_to_action(const char *verb) { - enum action i; - - for (i = _ACTION_INVALID; i < _ACTION_MAX; i++) - if (streq_ptr(action_table[i].verb, verb)) - return i; - - return _ACTION_INVALID; -} - -static int start_unit(int argc, char *argv[], void *userdata) { - _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; - const char *method, *mode, *one_name, *suffix = NULL; - _cleanup_strv_free_ char **names = NULL; - sd_bus *bus; - char **name; - int r = 0; - - ask_password_agent_open_if_enabled(); - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - if (arg_action == ACTION_SYSTEMCTL) { - enum action action; - - method = verb_to_method(argv[0]); - action = verb_to_action(argv[0]); - - if (streq(argv[0], "isolate")) { - mode = "isolate"; - suffix = ".target"; - } else - mode = action_table[action].mode ?: arg_job_mode; - - one_name = action_table[action].target; - } else { - assert(arg_action < ELEMENTSOF(action_table)); - assert(action_table[arg_action].target); - - method = "StartUnit"; - - mode = action_table[arg_action].mode; - one_name = action_table[arg_action].target; - } - - if (one_name) - names = strv_new(one_name, NULL); - else { - r = expand_names(bus, strv_skip(argv, 1), suffix, &names); - if (r < 0) - return log_error_errno(r, "Failed to expand names: %m"); - } - - if (!arg_no_block) { - r = bus_wait_for_jobs_new(bus, &w); - if (r < 0) - return log_error_errno(r, "Could not watch jobs: %m"); - } - - STRV_FOREACH(name, names) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int q; - - q = start_unit_one(bus, method, *name, mode, &error, w); - if (r >= 0 && q < 0) - r = translate_bus_error_to_exit_status(q, &error); - } - - if (!arg_no_block) { - int q, arg_count = 0; - const char* extra_args[4] = {}; - - if (arg_scope != UNIT_FILE_SYSTEM) - extra_args[arg_count++] = "--user"; - - assert(IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_REMOTE, BUS_TRANSPORT_MACHINE)); - if (arg_transport == BUS_TRANSPORT_REMOTE) { - extra_args[arg_count++] = "-H"; - extra_args[arg_count++] = arg_host; - } else if (arg_transport == BUS_TRANSPORT_MACHINE) { - extra_args[arg_count++] = "-M"; - extra_args[arg_count++] = arg_host; - } - - q = bus_wait_for_jobs(w, arg_quiet, extra_args); - if (q < 0) - return q; - - /* When stopping units, warn if they can still be triggered by - * another active unit (socket, path, timer) */ - if (!arg_quiet && streq(method, "StopUnit")) - STRV_FOREACH(name, names) - check_triggering_units(bus, *name); - } - - return r; -} - -static int logind_set_wall_message(void) { -#ifdef HAVE_LOGIND - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus; - _cleanup_free_ char *m = NULL; - int r; - - r = acquire_bus(BUS_FULL, &bus); - if (r < 0) - return r; - - m = strv_join(arg_wall, " "); - if (!m) - return log_oom(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "SetWallMessage", - &error, - NULL, - "sb", - m, - !arg_no_wall); - - if (r < 0) - return log_warning_errno(r, "Failed to set wall message, ignoring: %s", bus_error_message(&error, r)); - -#endif - return 0; -} - -/* Ask systemd-logind, which might grant access to unprivileged users - * through PolicyKit */ -static int logind_reboot(enum action a) { -#ifdef HAVE_LOGIND - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *method, *description; - sd_bus *bus; - int r; - - polkit_agent_open_if_enabled(); - (void) logind_set_wall_message(); - - r = acquire_bus(BUS_FULL, &bus); - if (r < 0) - return r; - - switch (a) { - - case ACTION_REBOOT: - method = "Reboot"; - description = "reboot system"; - break; - - case ACTION_POWEROFF: - method = "PowerOff"; - description = "power off system"; - break; - - case ACTION_SUSPEND: - method = "Suspend"; - description = "suspend system"; - break; - - case ACTION_HIBERNATE: - method = "Hibernate"; - description = "hibernate system"; - break; - - case ACTION_HYBRID_SLEEP: - method = "HybridSleep"; - description = "put system into hybrid sleep"; - break; - - default: - return -EINVAL; - } - - r = sd_bus_call_method( - bus, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - method, - &error, - NULL, - "b", arg_ask_password); - if (r < 0) - return log_error_errno(r, "Failed to %s via logind: %s", description, bus_error_message(&error, r)); - - return 0; -#else - return -ENOSYS; -#endif -} - -static int logind_check_inhibitors(enum action a) { -#ifdef HAVE_LOGIND - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_strv_free_ char **sessions = NULL; - const char *what, *who, *why, *mode; - uint32_t uid, pid; - sd_bus *bus; - unsigned c = 0; - char **s; - int r; - - if (arg_ignore_inhibitors || arg_force > 0) - return 0; - - if (arg_when > 0) - return 0; - - if (geteuid() == 0) - return 0; - - if (!on_tty()) - return 0; - - r = acquire_bus(BUS_FULL, &bus); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "ListInhibitors", - NULL, - &reply, - NULL); - if (r < 0) - /* If logind is not around, then there are no inhibitors... */ - return 0; - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssuu)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "(ssssuu)", &what, &who, &why, &mode, &uid, &pid)) > 0) { - _cleanup_free_ char *comm = NULL, *user = NULL; - _cleanup_strv_free_ char **sv = NULL; - - if (!streq(mode, "block")) - continue; - - sv = strv_split(what, ":"); - if (!sv) - return log_oom(); - - if ((pid_t) pid < 0) - return log_error_errno(ERANGE, "Bad PID %"PRIu32": %m", pid); - - if (!strv_contains(sv, - IN_SET(a, - ACTION_HALT, - ACTION_POWEROFF, - ACTION_REBOOT, - ACTION_KEXEC) ? "shutdown" : "sleep")) - continue; - - get_process_comm(pid, &comm); - user = uid_to_name(uid); - - log_warning("Operation inhibited by \"%s\" (PID "PID_FMT" \"%s\", user %s), reason is \"%s\".", - who, (pid_t) pid, strna(comm), strna(user), why); - - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - /* Check for current sessions */ - sd_get_sessions(&sessions); - STRV_FOREACH(s, sessions) { - _cleanup_free_ char *type = NULL, *tty = NULL, *seat = NULL, *user = NULL, *service = NULL, *class = NULL; - - if (sd_session_get_uid(*s, &uid) < 0 || uid == getuid()) - continue; - - if (sd_session_get_class(*s, &class) < 0 || !streq(class, "user")) - continue; - - if (sd_session_get_type(*s, &type) < 0 || (!streq(type, "x11") && !streq(type, "tty"))) - continue; - - sd_session_get_tty(*s, &tty); - sd_session_get_seat(*s, &seat); - sd_session_get_service(*s, &service); - user = uid_to_name(uid); - - log_warning("User %s is logged in on %s.", strna(user), isempty(tty) ? (isempty(seat) ? strna(service) : seat) : tty); - c++; - } - - if (c <= 0) - return 0; - - log_error("Please retry operation after closing inhibitors and logging out other users.\nAlternatively, ignore inhibitors and users with 'systemctl %s -i'.", - action_table[a].verb); - - return -EPERM; -#else - return 0; -#endif -} - -static int logind_prepare_firmware_setup(void) { -#ifdef HAVE_LOGIND - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus; - int r; - - r = acquire_bus(BUS_FULL, &bus); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "SetRebootToFirmwareSetup", - &error, - NULL, - "b", true); - if (r < 0) - return log_error_errno(r, "Cannot indicate to EFI to boot into setup mode: %s", bus_error_message(&error, r)); - - return 0; -#else - log_error("Cannot remotely indicate to EFI to boot into setup mode."); - return -ENOSYS; -#endif -} - -static int prepare_firmware_setup(void) { - int r; - - if (!arg_firmware_setup) - return 0; - - if (arg_transport == BUS_TRANSPORT_LOCAL) { - - r = efi_set_reboot_to_firmware(true); - if (r < 0) - log_debug_errno(r, "Cannot indicate to EFI to boot into setup mode, will retry via logind: %m"); - else - return r; - } - - return logind_prepare_firmware_setup(); -} - -static int set_exit_code(uint8_t code) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus; - int r; - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SetExitCode", - &error, - NULL, - "y", code); - if (r < 0) - return log_error_errno(r, "Failed to set exit code: %s", bus_error_message(&error, r)); - - return 0; -} - -static int start_special(int argc, char *argv[], void *userdata) { - enum action a; - int r; - - assert(argv); - - a = verb_to_action(argv[0]); - - r = logind_check_inhibitors(a); - if (r < 0) - return r; - - if (arg_force >= 2 && geteuid() != 0) { - log_error("Must be root."); - return -EPERM; - } - - r = prepare_firmware_setup(); - if (r < 0) - return r; - - if (a == ACTION_REBOOT && argc > 1) { - r = update_reboot_parameter_and_warn(argv[1]); - if (r < 0) - return r; - - } else if (a == ACTION_EXIT && argc > 1) { - uint8_t code; - - /* If the exit code is not given on the command line, - * don't reset it to zero: just keep it as it might - * have been set previously. */ - - r = safe_atou8(argv[1], &code); - if (r < 0) - return log_error_errno(r, "Invalid exit code."); - - r = set_exit_code(code); - if (r < 0) - return r; - } - - if (arg_force >= 2 && - IN_SET(a, - ACTION_HALT, - ACTION_POWEROFF, - ACTION_REBOOT)) - return halt_now(a); - - if (arg_force >= 1 && - IN_SET(a, - ACTION_HALT, - ACTION_POWEROFF, - ACTION_REBOOT, - ACTION_KEXEC, - ACTION_EXIT)) - return daemon_reload(argc, argv, userdata); - - /* First try logind, to allow authentication with polkit */ - if (IN_SET(a, - ACTION_POWEROFF, - ACTION_REBOOT, - ACTION_SUSPEND, - ACTION_HIBERNATE, - ACTION_HYBRID_SLEEP)) { - r = logind_reboot(a); - if (r >= 0) - return r; - if (IN_SET(r, -EOPNOTSUPP, -EINPROGRESS)) - /* requested operation is not supported or already in progress */ - return r; - - /* On all other errors, try low-level operation */ - } - - return start_unit(argc, argv, userdata); -} - -static int check_unit_generic(int code, const UnitActiveState good_states[], int nb_states, char **args) { - _cleanup_strv_free_ char **names = NULL; - UnitActiveState active_state; - sd_bus *bus; - char **name; - int r, i; - bool found = false; - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = expand_names(bus, args, NULL, &names); - if (r < 0) - return log_error_errno(r, "Failed to expand names: %m"); - - STRV_FOREACH(name, names) { - r = get_state_one_unit(bus, *name, &active_state); - if (r < 0) - return r; - - if (!arg_quiet) - puts(unit_active_state_to_string(active_state)); - - for (i = 0; i < nb_states; ++i) - if (good_states[i] == active_state) - found = true; - } - - /* use the given return code for the case that we won't find - * any unit which matches the list */ - return found ? 0 : code; -} - -static int check_unit_active(int argc, char *argv[], void *userdata) { - const UnitActiveState states[] = { UNIT_ACTIVE, UNIT_RELOADING }; - /* According to LSB: 3, "program is not running" */ - return check_unit_generic(3, states, ELEMENTSOF(states), strv_skip(argv, 1)); -} - -static int check_unit_failed(int argc, char *argv[], void *userdata) { - const UnitActiveState states[] = { UNIT_FAILED }; - return check_unit_generic(1, states, ELEMENTSOF(states), strv_skip(argv, 1)); -} - -static int kill_unit(int argc, char *argv[], void *userdata) { - _cleanup_strv_free_ char **names = NULL; - char *kill_who = NULL, **name; - sd_bus *bus; - int r, q; - - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - if (!arg_kill_who) - arg_kill_who = "all"; - - /* --fail was specified */ - if (streq(arg_job_mode, "fail")) - kill_who = strjoina(arg_kill_who, "-fail"); - - r = expand_names(bus, strv_skip(argv, 1), NULL, &names); - if (r < 0) - return log_error_errno(r, "Failed to expand names: %m"); - - STRV_FOREACH(name, names) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - - q = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "KillUnit", - &error, - NULL, - "ssi", *names, kill_who ? kill_who : arg_kill_who, arg_signal); - if (q < 0) { - log_error_errno(q, "Failed to kill unit %s: %s", *names, bus_error_message(&error, q)); - if (r == 0) - r = q; - } - } - - return r; -} - -typedef struct ExecStatusInfo { - char *name; - - char *path; - char **argv; - - bool ignore; - - usec_t start_timestamp; - usec_t exit_timestamp; - pid_t pid; - int code; - int status; - - LIST_FIELDS(struct ExecStatusInfo, exec); -} ExecStatusInfo; - -static void exec_status_info_free(ExecStatusInfo *i) { - assert(i); - - free(i->name); - free(i->path); - strv_free(i->argv); - free(i); -} - -static int exec_status_info_deserialize(sd_bus_message *m, ExecStatusInfo *i) { - uint64_t start_timestamp, exit_timestamp, start_timestamp_monotonic, exit_timestamp_monotonic; - const char *path; - uint32_t pid; - int32_t code, status; - int ignore, r; - - assert(m); - assert(i); - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_STRUCT, "sasbttttuii"); - if (r < 0) - return bus_log_parse_error(r); - else if (r == 0) - return 0; - - r = sd_bus_message_read(m, "s", &path); - if (r < 0) - return bus_log_parse_error(r); - - i->path = strdup(path); - if (!i->path) - return log_oom(); - - r = sd_bus_message_read_strv(m, &i->argv); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(m, - "bttttuii", - &ignore, - &start_timestamp, &start_timestamp_monotonic, - &exit_timestamp, &exit_timestamp_monotonic, - &pid, - &code, &status); - if (r < 0) - return bus_log_parse_error(r); - - i->ignore = ignore; - i->start_timestamp = (usec_t) start_timestamp; - i->exit_timestamp = (usec_t) exit_timestamp; - i->pid = (pid_t) pid; - i->code = code; - i->status = status; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 1; -} - -typedef struct UnitStatusInfo { - const char *id; - const char *load_state; - const char *active_state; - const char *sub_state; - const char *unit_file_state; - const char *unit_file_preset; - - const char *description; - const char *following; - - char **documentation; - - const char *fragment_path; - const char *source_path; - const char *control_group; - - char **dropin_paths; - - const char *load_error; - const char *result; - - usec_t inactive_exit_timestamp; - usec_t inactive_exit_timestamp_monotonic; - usec_t active_enter_timestamp; - usec_t active_exit_timestamp; - usec_t inactive_enter_timestamp; - - bool need_daemon_reload; - bool transient; - - /* Service */ - pid_t main_pid; - pid_t control_pid; - const char *status_text; - const char *pid_file; - bool running:1; - int status_errno; - - usec_t start_timestamp; - usec_t exit_timestamp; - - int exit_code, exit_status; - - usec_t condition_timestamp; - bool condition_result; - bool failed_condition_trigger; - bool failed_condition_negate; - const char *failed_condition; - const char *failed_condition_parameter; - - usec_t assert_timestamp; - bool assert_result; - bool failed_assert_trigger; - bool failed_assert_negate; - const char *failed_assert; - const char *failed_assert_parameter; - - /* Socket */ - unsigned n_accepted; - unsigned n_connections; - bool accept; - - /* Pairs of type, path */ - char **listen; - - /* Device */ - const char *sysfs_path; - - /* Mount, Automount */ - const char *where; - - /* Swap */ - const char *what; - - /* CGroup */ - uint64_t memory_current; - uint64_t memory_limit; - uint64_t cpu_usage_nsec; - uint64_t tasks_current; - uint64_t tasks_max; - - LIST_HEAD(ExecStatusInfo, exec); -} UnitStatusInfo; - -static void print_status_info( - sd_bus *bus, - UnitStatusInfo *i, - bool *ellipsized) { - - ExecStatusInfo *p; - const char *active_on, *active_off, *on, *off, *ss; - usec_t timestamp; - char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; - char since2[FORMAT_TIMESTAMP_MAX], *s2; - const char *path; - char **t, **t2; - int r; - - assert(i); - - /* This shows pretty information about a unit. See - * print_property() for a low-level property printer */ - - if (streq_ptr(i->active_state, "failed")) { - active_on = ansi_highlight_red(); - active_off = ansi_normal(); - } else if (streq_ptr(i->active_state, "active") || streq_ptr(i->active_state, "reloading")) { - active_on = ansi_highlight_green(); - active_off = ansi_normal(); - } else - active_on = active_off = ""; - - printf("%s%s%s %s", active_on, special_glyph(BLACK_CIRCLE), active_off, strna(i->id)); - - if (i->description && !streq_ptr(i->id, i->description)) - printf(" - %s", i->description); - - printf("\n"); - - if (i->following) - printf(" Follow: unit currently follows state of %s\n", i->following); - - if (streq_ptr(i->load_state, "error")) { - on = ansi_highlight_red(); - off = ansi_normal(); - } else - on = off = ""; - - path = i->source_path ? i->source_path : i->fragment_path; - - if (i->load_error != 0) - printf(" Loaded: %s%s%s (Reason: %s)\n", - on, strna(i->load_state), off, i->load_error); - else if (path && !isempty(i->unit_file_state) && !isempty(i->unit_file_preset)) - printf(" Loaded: %s%s%s (%s; %s; vendor preset: %s)\n", - on, strna(i->load_state), off, path, i->unit_file_state, i->unit_file_preset); - else if (path && !isempty(i->unit_file_state)) - printf(" Loaded: %s%s%s (%s; %s)\n", - on, strna(i->load_state), off, path, i->unit_file_state); - else if (path) - printf(" Loaded: %s%s%s (%s)\n", - on, strna(i->load_state), off, path); - else - printf(" Loaded: %s%s%s\n", - on, strna(i->load_state), off); - - if (i->transient) - printf("Transient: yes\n"); - - if (!strv_isempty(i->dropin_paths)) { - _cleanup_free_ char *dir = NULL; - bool last = false; - char ** dropin; - - STRV_FOREACH(dropin, i->dropin_paths) { - if (! dir || last) { - printf(dir ? " " : " Drop-In: "); - - dir = mfree(dir); - - dir = dirname_malloc(*dropin); - if (!dir) { - log_oom(); - return; - } - - printf("%s\n %s", dir, - special_glyph(TREE_RIGHT)); - } - - last = ! (*(dropin + 1) && startswith(*(dropin + 1), dir)); - - printf("%s%s", basename(*dropin), last ? "\n" : ", "); - } - } - - ss = streq_ptr(i->active_state, i->sub_state) ? NULL : i->sub_state; - if (ss) - printf(" Active: %s%s (%s)%s", - active_on, strna(i->active_state), ss, active_off); - else - printf(" Active: %s%s%s", - active_on, strna(i->active_state), active_off); - - if (!isempty(i->result) && !streq(i->result, "success")) - printf(" (Result: %s)", i->result); - - timestamp = (streq_ptr(i->active_state, "active") || - streq_ptr(i->active_state, "reloading")) ? i->active_enter_timestamp : - (streq_ptr(i->active_state, "inactive") || - streq_ptr(i->active_state, "failed")) ? i->inactive_enter_timestamp : - streq_ptr(i->active_state, "activating") ? i->inactive_exit_timestamp : - i->active_exit_timestamp; - - s1 = format_timestamp_relative(since1, sizeof(since1), timestamp); - s2 = format_timestamp(since2, sizeof(since2), timestamp); - - if (s1) - printf(" since %s; %s\n", s2, s1); - else if (s2) - printf(" since %s\n", s2); - else - printf("\n"); - - if (!i->condition_result && i->condition_timestamp > 0) { - s1 = format_timestamp_relative(since1, sizeof(since1), i->condition_timestamp); - s2 = format_timestamp(since2, sizeof(since2), i->condition_timestamp); - - printf("Condition: start %scondition failed%s at %s%s%s\n", - ansi_highlight_yellow(), ansi_normal(), - s2, s1 ? "; " : "", strempty(s1)); - if (i->failed_condition_trigger) - printf(" none of the trigger conditions were met\n"); - else if (i->failed_condition) - printf(" %s=%s%s was not met\n", - i->failed_condition, - i->failed_condition_negate ? "!" : "", - i->failed_condition_parameter); - } - - if (!i->assert_result && i->assert_timestamp > 0) { - s1 = format_timestamp_relative(since1, sizeof(since1), i->assert_timestamp); - s2 = format_timestamp(since2, sizeof(since2), i->assert_timestamp); - - printf(" Assert: start %sassertion failed%s at %s%s%s\n", - ansi_highlight_red(), ansi_normal(), - s2, s1 ? "; " : "", strempty(s1)); - if (i->failed_assert_trigger) - printf(" none of the trigger assertions were met\n"); - else if (i->failed_assert) - printf(" %s=%s%s was not met\n", - i->failed_assert, - i->failed_assert_negate ? "!" : "", - i->failed_assert_parameter); - } - - if (i->sysfs_path) - printf(" Device: %s\n", i->sysfs_path); - if (i->where) - printf(" Where: %s\n", i->where); - if (i->what) - printf(" What: %s\n", i->what); - - STRV_FOREACH(t, i->documentation) - printf(" %*s %s\n", 9, t == i->documentation ? "Docs:" : "", *t); - - 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); - - LIST_FOREACH(exec, p, i->exec) { - _cleanup_free_ char *argv = NULL; - bool good; - - /* Only show exited processes here */ - if (p->code == 0) - continue; - - argv = strv_join(p->argv, " "); - printf(" Process: "PID_FMT" %s=%s ", p->pid, p->name, strna(argv)); - - good = is_clean_exit_lsb(p->code, p->status, NULL); - if (!good) { - on = ansi_highlight_red(); - off = ansi_normal(); - } else - on = off = ""; - - printf("%s(code=%s, ", on, sigchld_code_to_string(p->code)); - - if (p->code == CLD_EXITED) { - const char *c; - - printf("status=%i", p->status); - - c = exit_status_to_string(p->status, EXIT_STATUS_SYSTEMD); - if (c) - printf("/%s", c); - - } else - printf("signal=%s", signal_to_string(p->status)); - - printf(")%s\n", off); - - if (i->main_pid == p->pid && - i->start_timestamp == p->start_timestamp && - i->exit_timestamp == p->start_timestamp) - /* Let's not show this twice */ - i->main_pid = 0; - - if (p->pid == i->control_pid) - i->control_pid = 0; - } - - if (i->main_pid > 0 || i->control_pid > 0) { - if (i->main_pid > 0) { - printf(" Main PID: "PID_FMT, i->main_pid); - - if (i->running) { - _cleanup_free_ char *comm = NULL; - get_process_comm(i->main_pid, &comm); - if (comm) - printf(" (%s)", comm); - } else if (i->exit_code > 0) { - printf(" (code=%s, ", sigchld_code_to_string(i->exit_code)); - - if (i->exit_code == CLD_EXITED) { - const char *c; - - printf("status=%i", i->exit_status); - - c = exit_status_to_string(i->exit_status, EXIT_STATUS_SYSTEMD); - if (c) - printf("/%s", c); - - } else - printf("signal=%s", signal_to_string(i->exit_status)); - printf(")"); - } - - if (i->control_pid > 0) - printf(";"); - } - - if (i->control_pid > 0) { - _cleanup_free_ char *c = NULL; - - printf(" %8s: "PID_FMT, i->main_pid ? "" : " Control", i->control_pid); - - get_process_comm(i->control_pid, &c); - if (c) - printf(" (%s)", c); - } - - printf("\n"); - } - - if (i->status_text) - printf(" Status: \"%s\"\n", i->status_text); - if (i->status_errno > 0) - printf(" Error: %i (%s)\n", i->status_errno, strerror(i->status_errno)); - - if (i->tasks_current != (uint64_t) -1) { - printf(" Tasks: %" PRIu64, i->tasks_current); - - if (i->tasks_max != (uint64_t) -1) - printf(" (limit: %" PRIi64 ")\n", i->tasks_max); - else - printf("\n"); - } - - if (i->memory_current != (uint64_t) -1) { - char buf[FORMAT_BYTES_MAX]; - - printf(" Memory: %s", format_bytes(buf, sizeof(buf), i->memory_current)); - - if (i->memory_limit != (uint64_t) -1) - printf(" (limit: %s)\n", format_bytes(buf, sizeof(buf), i->memory_limit)); - else - printf("\n"); - } - - if (i->cpu_usage_nsec != (uint64_t) -1) { - char buf[FORMAT_TIMESPAN_MAX]; - printf(" CPU: %s\n", format_timespan(buf, sizeof(buf), i->cpu_usage_nsec / NSEC_PER_USEC, USEC_PER_MSEC)); - } - - if (i->control_group) - printf(" CGroup: %s\n", i->control_group); - - { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - static const char prefix[] = " "; - unsigned c; - - c = columns(); - if (c > sizeof(prefix) - 1) - c -= sizeof(prefix) - 1; - else - c = 0; - - r = unit_show_processes(bus, i->id, i->control_group, prefix, c, get_output_flags(), &error); - if (r == -EBADR) { - unsigned k = 0; - pid_t extra[2]; - - /* Fallback for older systemd versions where the GetUnitProcesses() call is not yet available */ - - if (i->main_pid > 0) - extra[k++] = i->main_pid; - - if (i->control_pid > 0) - extra[k++] = i->control_pid; - - show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, i->control_group, prefix, c, extra, k, get_output_flags()); - } else if (r < 0) - log_warning_errno(r, "Failed to dump process list, ignoring: %s", bus_error_message(&error, r)); - } - - if (i->id && arg_transport == BUS_TRANSPORT_LOCAL) - show_journal_by_unit( - stdout, - i->id, - arg_output, - 0, - i->inactive_exit_timestamp_monotonic, - arg_lines, - getuid(), - get_output_flags() | OUTPUT_BEGIN_NEWLINE, - SD_JOURNAL_LOCAL_ONLY, - arg_scope == UNIT_FILE_SYSTEM, - ellipsized); - - if (i->need_daemon_reload) - warn_unit_file_changed(i->id); -} - -static void show_unit_help(UnitStatusInfo *i) { - char **p; - - assert(i); - - if (!i->documentation) { - log_info("Documentation for %s not known.", i->id); - return; - } - - STRV_FOREACH(p, i->documentation) - if (startswith(*p, "man:")) - show_man_page(*p + 4, false); - else - log_info("Can't show: %s", *p); -} - -static int status_property(const char *name, sd_bus_message *m, UnitStatusInfo *i, const char *contents) { - int r; - - assert(name); - assert(m); - assert(i); - - switch (contents[0]) { - - case SD_BUS_TYPE_STRING: { - const char *s; - - r = sd_bus_message_read(m, "s", &s); - if (r < 0) - return bus_log_parse_error(r); - - if (!isempty(s)) { - if (streq(name, "Id")) - i->id = s; - else if (streq(name, "LoadState")) - i->load_state = s; - else if (streq(name, "ActiveState")) - i->active_state = s; - else if (streq(name, "SubState")) - i->sub_state = s; - else if (streq(name, "Description")) - i->description = s; - else if (streq(name, "FragmentPath")) - i->fragment_path = s; - else if (streq(name, "SourcePath")) - i->source_path = s; -#ifndef NOLEGACY - else if (streq(name, "DefaultControlGroup")) { - const char *e; - e = startswith(s, SYSTEMD_CGROUP_CONTROLLER ":"); - if (e) - i->control_group = e; - } -#endif - else if (streq(name, "ControlGroup")) - i->control_group = s; - else if (streq(name, "StatusText")) - i->status_text = s; - else if (streq(name, "PIDFile")) - i->pid_file = s; - else if (streq(name, "SysFSPath")) - i->sysfs_path = s; - else if (streq(name, "Where")) - i->where = s; - else if (streq(name, "What")) - i->what = s; - else if (streq(name, "Following")) - i->following = s; - else if (streq(name, "UnitFileState")) - i->unit_file_state = s; - else if (streq(name, "UnitFilePreset")) - i->unit_file_preset = s; - else if (streq(name, "Result")) - i->result = s; - } - - break; - } - - case SD_BUS_TYPE_BOOLEAN: { - int b; - - r = sd_bus_message_read(m, "b", &b); - if (r < 0) - return bus_log_parse_error(r); - - if (streq(name, "Accept")) - i->accept = b; - else if (streq(name, "NeedDaemonReload")) - i->need_daemon_reload = b; - else if (streq(name, "ConditionResult")) - i->condition_result = b; - else if (streq(name, "AssertResult")) - i->assert_result = b; - else if (streq(name, "Transient")) - i->transient = b; - - break; - } - - case SD_BUS_TYPE_UINT32: { - uint32_t u; - - r = sd_bus_message_read(m, "u", &u); - if (r < 0) - return bus_log_parse_error(r); - - if (streq(name, "MainPID")) { - if (u > 0) { - i->main_pid = (pid_t) u; - i->running = true; - } - } else if (streq(name, "ControlPID")) - i->control_pid = (pid_t) u; - else if (streq(name, "ExecMainPID")) { - if (u > 0) - i->main_pid = (pid_t) u; - } else if (streq(name, "NAccepted")) - i->n_accepted = u; - else if (streq(name, "NConnections")) - i->n_connections = u; - - break; - } - - case SD_BUS_TYPE_INT32: { - int32_t j; - - r = sd_bus_message_read(m, "i", &j); - if (r < 0) - return bus_log_parse_error(r); - - if (streq(name, "ExecMainCode")) - i->exit_code = (int) j; - else if (streq(name, "ExecMainStatus")) - i->exit_status = (int) j; - else if (streq(name, "StatusErrno")) - i->status_errno = (int) j; - - break; - } - - case SD_BUS_TYPE_UINT64: { - uint64_t u; - - r = sd_bus_message_read(m, "t", &u); - if (r < 0) - return bus_log_parse_error(r); - - if (streq(name, "ExecMainStartTimestamp")) - i->start_timestamp = (usec_t) u; - else if (streq(name, "ExecMainExitTimestamp")) - i->exit_timestamp = (usec_t) u; - else if (streq(name, "ActiveEnterTimestamp")) - i->active_enter_timestamp = (usec_t) u; - else if (streq(name, "InactiveEnterTimestamp")) - i->inactive_enter_timestamp = (usec_t) u; - else if (streq(name, "InactiveExitTimestamp")) - i->inactive_exit_timestamp = (usec_t) u; - else if (streq(name, "InactiveExitTimestampMonotonic")) - i->inactive_exit_timestamp_monotonic = (usec_t) u; - else if (streq(name, "ActiveExitTimestamp")) - i->active_exit_timestamp = (usec_t) u; - else if (streq(name, "ConditionTimestamp")) - i->condition_timestamp = (usec_t) u; - else if (streq(name, "AssertTimestamp")) - i->assert_timestamp = (usec_t) u; - else if (streq(name, "MemoryCurrent")) - i->memory_current = u; - else if (streq(name, "MemoryLimit")) - i->memory_limit = u; - else if (streq(name, "TasksCurrent")) - i->tasks_current = u; - else if (streq(name, "TasksMax")) - i->tasks_max = u; - else if (streq(name, "CPUUsageNSec")) - i->cpu_usage_nsec = u; - - break; - } - - case SD_BUS_TYPE_ARRAY: - - if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && startswith(name, "Exec")) { - _cleanup_free_ ExecStatusInfo *info = NULL; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sasbttttuii)"); - if (r < 0) - return bus_log_parse_error(r); - - info = new0(ExecStatusInfo, 1); - if (!info) - return log_oom(); - - while ((r = exec_status_info_deserialize(m, info)) > 0) { - - info->name = strdup(name); - if (!info->name) - return log_oom(); - - LIST_PREPEND(exec, i->exec, info); - - info = new0(ExecStatusInfo, 1); - if (!info) - return log_oom(); - } - - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Listen")) { - const char *type, *path; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(ss)", &type, &path)) > 0) { - - r = strv_extend(&i->listen, type); - if (r < 0) - return r; - - r = strv_extend(&i->listen, path); - if (r < 0) - return r; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRING && streq(name, "DropInPaths")) { - - r = sd_bus_message_read_strv(m, &i->dropin_paths); - if (r < 0) - return bus_log_parse_error(r); - - } else if (contents[1] == SD_BUS_TYPE_STRING && streq(name, "Documentation")) { - - r = sd_bus_message_read_strv(m, &i->documentation); - if (r < 0) - return bus_log_parse_error(r); - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Conditions")) { - const char *cond, *param; - int trigger, negate; - int32_t state; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sbbsi)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(sbbsi)", &cond, &trigger, &negate, ¶m, &state)) > 0) { - log_debug("%s %d %d %s %d", cond, trigger, negate, param, state); - if (state < 0 && (!trigger || !i->failed_condition)) { - i->failed_condition = cond; - i->failed_condition_trigger = trigger; - i->failed_condition_negate = negate; - i->failed_condition_parameter = param; - } - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Asserts")) { - const char *cond, *param; - int trigger, negate; - int32_t state; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sbbsi)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(sbbsi)", &cond, &trigger, &negate, ¶m, &state)) > 0) { - log_debug("%s %d %d %s %d", cond, trigger, negate, param, state); - if (state < 0 && (!trigger || !i->failed_assert)) { - i->failed_assert = cond; - i->failed_assert_trigger = trigger; - i->failed_assert_negate = negate; - i->failed_assert_parameter = param; - } - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - } else - goto skip; - - break; - - case SD_BUS_TYPE_STRUCT_BEGIN: - - if (streq(name, "LoadError")) { - const char *n, *message; - - r = sd_bus_message_read(m, "(ss)", &n, &message); - if (r < 0) - return bus_log_parse_error(r); - - if (!isempty(message)) - i->load_error = message; - } else - goto skip; - - break; - - default: - goto skip; - } - - return 0; - -skip: - r = sd_bus_message_skip(m, contents); - if (r < 0) - return bus_log_parse_error(r); - - return 0; -} - -#define print_prop(name, fmt, ...) \ - do { \ - if (arg_value) \ - printf(fmt "\n", __VA_ARGS__); \ - else \ - printf("%s=" fmt "\n", name, __VA_ARGS__); \ - } while(0) - -static int print_property(const char *name, sd_bus_message *m, const char *contents) { - int r; - - assert(name); - assert(m); - - /* This is a low-level property printer, see - * print_status_info() for the nicer output */ - - if (arg_properties && !strv_find(arg_properties, name)) { - /* skip what we didn't read */ - r = sd_bus_message_skip(m, contents); - return r; - } - - switch (contents[0]) { - - case SD_BUS_TYPE_STRUCT_BEGIN: - - if (contents[1] == SD_BUS_TYPE_UINT32 && streq(name, "Job")) { - uint32_t u; - - r = sd_bus_message_read(m, "(uo)", &u, NULL); - if (r < 0) - return bus_log_parse_error(r); - - if (u > 0) - print_prop(name, "%"PRIu32, u); - else if (arg_all) - print_prop(name, "%s", ""); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRING && streq(name, "Unit")) { - const char *s; - - r = sd_bus_message_read(m, "(so)", &s, NULL); - if (r < 0) - return bus_log_parse_error(r); - - if (arg_all || !isempty(s)) - print_prop(name, "%s", s); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRING && streq(name, "LoadError")) { - const char *a = NULL, *b = NULL; - - r = sd_bus_message_read(m, "(ss)", &a, &b); - if (r < 0) - return bus_log_parse_error(r); - - if (arg_all || !isempty(a) || !isempty(b)) - print_prop(name, "%s \"%s\"", strempty(a), strempty(b)); - - return 0; - } else if (streq_ptr(name, "SystemCallFilter")) { - _cleanup_strv_free_ char **l = NULL; - int whitelist; - - r = sd_bus_message_enter_container(m, 'r', "bas"); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(m, "b", &whitelist); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_strv(m, &l); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - if (arg_all || whitelist || !strv_isempty(l)) { - bool first = true; - char **i; - - if (!arg_value) { - fputs(name, stdout); - fputc('=', stdout); - } - - if (!whitelist) - fputc('~', stdout); - - STRV_FOREACH(i, l) { - if (first) - first = false; - else - fputc(' ', stdout); - - fputs(*i, stdout); - } - fputc('\n', stdout); - } - - return 0; - } - - break; - - case SD_BUS_TYPE_ARRAY: - - if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "EnvironmentFiles")) { - const char *path; - int ignore; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sb)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(sb)", &path, &ignore)) > 0) - print_prop("EnvironmentFile", "%s (ignore_errors=%s)\n", path, yes_no(ignore)); - - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Paths")) { - const char *type, *path; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(ss)", &type, &path)) > 0) - print_prop(type, "%s", path); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Listen")) { - const char *type, *path; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(ss)", &type, &path)) > 0) - if (arg_value) - puts(path); - else - printf("Listen%s=%s\n", type, path); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Timers")) { - const char *base; - uint64_t value, next_elapse; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(stt)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(stt)", &base, &value, &next_elapse)) > 0) { - char timespan1[FORMAT_TIMESPAN_MAX], timespan2[FORMAT_TIMESPAN_MAX]; - - print_prop(base, "{ value=%s ; next_elapse=%s }", - format_timespan(timespan1, sizeof(timespan1), value, 0), - format_timespan(timespan2, sizeof(timespan2), next_elapse, 0)); - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && startswith(name, "Exec")) { - ExecStatusInfo info = {}; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sasbttttuii)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = exec_status_info_deserialize(m, &info)) > 0) { - char timestamp1[FORMAT_TIMESTAMP_MAX], timestamp2[FORMAT_TIMESTAMP_MAX]; - _cleanup_free_ char *tt; - - tt = strv_join(info.argv, " "); - - print_prop(name, - "{ path=%s ; argv[]=%s ; ignore_errors=%s ; start_time=[%s] ; stop_time=[%s] ; pid="PID_FMT" ; code=%s ; status=%i%s%s }", - strna(info.path), - strna(tt), - yes_no(info.ignore), - strna(format_timestamp(timestamp1, sizeof(timestamp1), info.start_timestamp)), - strna(format_timestamp(timestamp2, sizeof(timestamp2), info.exit_timestamp)), - info.pid, - sigchld_code_to_string(info.code), - info.status, - info.code == CLD_EXITED ? "" : "/", - strempty(info.code == CLD_EXITED ? NULL : signal_to_string(info.status))); - - free(info.path); - strv_free(info.argv); - zero(info); - } - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "DeviceAllow")) { - const char *path, *rwm; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(ss)", &path, &rwm)) > 0) - print_prop(name, "%s %s", strna(path), strna(rwm)); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && (streq(name, "IODeviceWeight") || streq(name, "BlockIODeviceWeight"))) { - const char *path; - uint64_t weight; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(st)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(st)", &path, &weight)) > 0) - print_prop(name, "%s %"PRIu64, strna(path), weight); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && (cgroup_io_limit_type_from_string(name) >= 0 || - streq(name, "BlockIOReadBandwidth") || streq(name, "BlockIOWriteBandwidth"))) { - const char *path; - uint64_t bandwidth; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(st)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(st)", &path, &bandwidth)) > 0) - print_prop(name, "%s %"PRIu64, strna(path), bandwidth); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - } - - break; - } - - r = bus_print_property(name, m, arg_value, arg_all); - if (r < 0) - return bus_log_parse_error(r); - - if (r == 0) { - r = sd_bus_message_skip(m, contents); - if (r < 0) - return bus_log_parse_error(r); - - if (arg_all) - printf("%s=[unprintable]\n", name); - } - - return 0; -} - -static int show_one( - const char *verb, - sd_bus *bus, - const char *path, - bool show_properties, - bool *new_line, - bool *ellipsized) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - UnitStatusInfo info = { - .memory_current = (uint64_t) -1, - .memory_limit = (uint64_t) -1, - .cpu_usage_nsec = (uint64_t) -1, - .tasks_current = (uint64_t) -1, - .tasks_max = (uint64_t) -1, - }; - ExecStatusInfo *p; - int r; - - assert(path); - assert(new_line); - - log_debug("Showing one %s", path); - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.DBus.Properties", - "GetAll", - &error, - &reply, - "s", ""); - if (r < 0) - return log_error_errno(r, "Failed to get properties: %s", bus_error_message(&error, r)); - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sv}"); - if (r < 0) - return bus_log_parse_error(r); - - if (*new_line) - printf("\n"); - - *new_line = true; - - while ((r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { - const char *name, *contents; - - r = sd_bus_message_read(reply, "s", &name); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_peek_type(reply, NULL, &contents); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_VARIANT, contents); - if (r < 0) - return bus_log_parse_error(r); - - if (show_properties) - r = print_property(name, reply, contents); - else - r = status_property(name, reply, &info, contents); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = 0; - - if (!show_properties) { - if (streq(verb, "help")) - show_unit_help(&info); - else - print_status_info(bus, &info, ellipsized); - } - - strv_free(info.documentation); - strv_free(info.dropin_paths); - strv_free(info.listen); - - if (!streq_ptr(info.active_state, "active") && - !streq_ptr(info.active_state, "reloading") && - streq(verb, "status")) { - /* According to LSB: "program not running" */ - /* 0: program is running or service is OK - * 1: program is dead and /run PID file exists - * 2: program is dead and /run/lock lock file exists - * 3: program is not running - * 4: program or service status is unknown - */ - if (info.pid_file && access(info.pid_file, F_OK) == 0) - r = 1; - else - r = 3; - } - - while ((p = info.exec)) { - LIST_REMOVE(exec, info.exec, p); - exec_status_info_free(p); - } - - return r; -} - -static int get_unit_dbus_path_by_pid( - sd_bus *bus, - uint32_t pid, - char **unit) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - char *u; - int r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GetUnitByPID", - &error, - &reply, - "u", pid); - if (r < 0) - return log_error_errno(r, "Failed to get unit for PID %"PRIu32": %s", pid, bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "o", &u); - if (r < 0) - return bus_log_parse_error(r); - - u = strdup(u); - if (!u) - return log_oom(); - - *unit = u; - return 0; -} - -static int show_all( - const char* verb, - sd_bus *bus, - bool show_properties, - bool *new_line, - bool *ellipsized) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ UnitInfo *unit_infos = NULL; - const UnitInfo *u; - unsigned c; - int r, ret = 0; - - r = get_unit_list(bus, NULL, NULL, &unit_infos, 0, &reply); - if (r < 0) - return r; - - pager_open(arg_no_pager, false); - - c = (unsigned) r; - - qsort_safe(unit_infos, c, sizeof(UnitInfo), compare_unit_info); - - for (u = unit_infos; u < unit_infos + c; u++) { - _cleanup_free_ char *p = NULL; - - p = unit_dbus_path_from_name(u->id); - if (!p) - return log_oom(); - - r = show_one(verb, bus, p, show_properties, new_line, ellipsized); - if (r < 0) - return r; - else if (r > 0 && ret == 0) - ret = r; - } - - return ret; -} - -static int show_system_status(sd_bus *bus) { - char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], since2[FORMAT_TIMESTAMP_MAX]; - _cleanup_free_ char *hn = NULL; - _cleanup_(machine_info_clear) struct machine_info mi = {}; - const char *on, *off; - int r; - - hn = gethostname_malloc(); - if (!hn) - return log_oom(); - - r = bus_map_all_properties(bus, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", machine_info_property_map, &mi); - if (r < 0) - return log_error_errno(r, "Failed to read server status: %m"); - - if (streq_ptr(mi.state, "degraded")) { - on = ansi_highlight_red(); - off = ansi_normal(); - } else if (!streq_ptr(mi.state, "running")) { - on = ansi_highlight_yellow(); - off = ansi_normal(); - } else - on = off = ""; - - printf("%s%s%s %s\n", on, special_glyph(BLACK_CIRCLE), off, arg_host ? arg_host : hn); - - printf(" State: %s%s%s\n", - on, strna(mi.state), off); - - printf(" Jobs: %" PRIu32 " queued\n", mi.n_jobs); - printf(" Failed: %" PRIu32 " units\n", mi.n_failed_units); - - printf(" Since: %s; %s\n", - format_timestamp(since2, sizeof(since2), mi.timestamp), - format_timestamp_relative(since1, sizeof(since1), mi.timestamp)); - - printf(" CGroup: %s\n", mi.control_group ?: "/"); - if (IN_SET(arg_transport, - BUS_TRANSPORT_LOCAL, - BUS_TRANSPORT_MACHINE)) { - static const char prefix[] = " "; - unsigned c; - - c = columns(); - if (c > sizeof(prefix) - 1) - c -= sizeof(prefix) - 1; - else - c = 0; - - show_cgroup(SYSTEMD_CGROUP_CONTROLLER, strempty(mi.control_group), prefix, c, get_output_flags()); - } - - return 0; -} - -static int show(int argc, char *argv[], void *userdata) { - bool show_properties, show_status, show_help, new_line = false; - bool ellipsized = false; - int r, ret = 0; - sd_bus *bus; - - assert(argv); - - show_properties = streq(argv[0], "show"); - show_status = streq(argv[0], "status"); - show_help = streq(argv[0], "help"); - - if (show_help && argc <= 1) { - log_error("This command expects one or more unit names. Did you mean --help?"); - return -EINVAL; - } - - pager_open(arg_no_pager, false); - - if (show_status) - /* Increase max number of open files to 16K if we can, we - * might needs this when browsing journal files, which might - * be split up into many files. */ - setrlimit_closest(RLIMIT_NOFILE, &RLIMIT_MAKE_CONST(16384)); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - /* If no argument is specified inspect the manager itself */ - if (show_properties && argc <= 1) - return show_one(argv[0], bus, "/org/freedesktop/systemd1", show_properties, &new_line, &ellipsized); - - if (show_status && argc <= 1) { - - pager_open(arg_no_pager, false); - show_system_status(bus); - new_line = true; - - if (arg_all) - ret = show_all(argv[0], bus, false, &new_line, &ellipsized); - } else { - _cleanup_free_ char **patterns = NULL; - char **name; - - STRV_FOREACH(name, strv_skip(argv, 1)) { - _cleanup_free_ char *unit = NULL; - uint32_t id; - - if (safe_atou32(*name, &id) < 0) { - if (strv_push(&patterns, *name) < 0) - return log_oom(); - - continue; - } else if (show_properties) { - /* Interpret as job id */ - if (asprintf(&unit, "/org/freedesktop/systemd1/job/%u", id) < 0) - return log_oom(); - - } else { - /* Interpret as PID */ - r = get_unit_dbus_path_by_pid(bus, id, &unit); - if (r < 0) { - ret = r; - continue; - } - } - - r = show_one(argv[0], bus, unit, show_properties, &new_line, &ellipsized); - if (r < 0) - return r; - else if (r > 0 && ret == 0) - ret = r; - } - - if (!strv_isempty(patterns)) { - _cleanup_strv_free_ char **names = NULL; - - r = expand_names(bus, patterns, NULL, &names); - if (r < 0) - return log_error_errno(r, "Failed to expand names: %m"); - - STRV_FOREACH(name, names) { - _cleanup_free_ char *unit; - - unit = unit_dbus_path_from_name(*name); - if (!unit) - return log_oom(); - - r = show_one(argv[0], bus, unit, show_properties, &new_line, &ellipsized); - if (r < 0) - return r; - else if (r > 0 && ret == 0) - ret = r; - } - } - } - - if (ellipsized && !arg_quiet) - printf("Hint: Some lines were ellipsized, use -l to show in full.\n"); - - return ret; -} - -static int cat_file(const char *filename, bool newline) { - _cleanup_close_ int fd; - - fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return -errno; - - printf("%s%s# %s%s\n", - newline ? "\n" : "", - ansi_highlight_blue(), - filename, - ansi_normal()); - fflush(stdout); - - return copy_bytes(fd, STDOUT_FILENO, (uint64_t) -1, false); -} - -static int cat(int argc, char *argv[], void *userdata) { - _cleanup_lookup_paths_free_ LookupPaths lp = {}; - _cleanup_strv_free_ char **names = NULL; - char **name; - sd_bus *bus; - bool first = true; - int r; - - if (arg_transport != BUS_TRANSPORT_LOCAL) { - log_error("Cannot remotely cat units."); - return -EINVAL; - } - - r = lookup_paths_init(&lp, arg_scope, 0, arg_root); - if (r < 0) - return log_error_errno(r, "Failed to determine unit paths: %m"); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = expand_names(bus, strv_skip(argv, 1), NULL, &names); - if (r < 0) - return log_error_errno(r, "Failed to expand names: %m"); - - pager_open(arg_no_pager, false); - - STRV_FOREACH(name, names) { - _cleanup_free_ char *fragment_path = NULL; - _cleanup_strv_free_ char **dropin_paths = NULL; - char **path; - - r = unit_find_paths(bus, *name, &lp, &fragment_path, &dropin_paths); - if (r < 0) - return r; - else if (r == 0) - return -ENOENT; - - if (first) - first = false; - else - puts(""); - - if (fragment_path) { - r = cat_file(fragment_path, false); - if (r < 0) - return log_warning_errno(r, "Failed to cat %s: %m", fragment_path); - } - - STRV_FOREACH(path, dropin_paths) { - r = cat_file(*path, path == dropin_paths); - if (r < 0) - return log_warning_errno(r, "Failed to cat %s: %m", *path); - } - } - - return 0; -} - -static int set_property(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *n = NULL; - sd_bus *bus; - char **i; - int r; - - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SetUnitProperties"); - if (r < 0) - return bus_log_create_error(r); - - r = unit_name_mangle(argv[1], UNIT_NAME_NOGLOB, &n); - if (r < 0) - return log_error_errno(r, "Failed to mangle unit name: %m"); - - r = sd_bus_message_append(m, "sb", n, arg_runtime); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, "(sv)"); - if (r < 0) - return bus_log_create_error(r); - - STRV_FOREACH(i, strv_skip(argv, 2)) { - r = bus_append_unit_property_assignment(m, *i); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, NULL); - if (r < 0) - return log_error_errno(r, "Failed to set unit properties on %s: %s", n, bus_error_message(&error, r)); - - return 0; -} - -static int daemon_reload(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *method; - sd_bus *bus; - int r; - - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - if (arg_action == ACTION_RELOAD) - method = "Reload"; - else if (arg_action == ACTION_REEXEC) - method = "Reexecute"; - else { - assert(arg_action == ACTION_SYSTEMCTL); - - method = - streq(argv[0], "clear-jobs") || - streq(argv[0], "cancel") ? "ClearJobs" : - streq(argv[0], "daemon-reexec") ? "Reexecute" : - streq(argv[0], "reset-failed") ? "ResetFailed" : - streq(argv[0], "halt") ? "Halt" : - streq(argv[0], "poweroff") ? "PowerOff" : - streq(argv[0], "reboot") ? "Reboot" : - streq(argv[0], "kexec") ? "KExec" : - streq(argv[0], "exit") ? "Exit" : - /* "daemon-reload" */ "Reload"; - } - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - method, - &error, - NULL, - NULL); - if (r == -ENOENT && arg_action != ACTION_SYSTEMCTL) - /* There's always a fallback possible for - * legacy actions. */ - r = -EADDRNOTAVAIL; - else if ((r == -ETIMEDOUT || r == -ECONNRESET) && streq(method, "Reexecute")) - /* On reexecution, we expect a disconnect, not a - * reply */ - r = 0; - else if (r < 0) - return log_error_errno(r, "Failed to reload daemon: %s", bus_error_message(&error, r)); - - return r < 0 ? r : 0; -} - -static int reset_failed(int argc, char *argv[], void *userdata) { - _cleanup_strv_free_ char **names = NULL; - sd_bus *bus; - char **name; - int r, q; - - if (argc <= 1) - return daemon_reload(argc, argv, userdata); - - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = expand_names(bus, strv_skip(argv, 1), NULL, &names); - if (r < 0) - return log_error_errno(r, "Failed to expand names: %m"); - - STRV_FOREACH(name, names) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - - q = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "ResetFailedUnit", - &error, - NULL, - "s", *name); - if (q < 0) { - log_error_errno(q, "Failed to reset failed state of unit %s: %s", *name, bus_error_message(&error, q)); - if (r == 0) - r = q; - } - } - - return r; -} - -static int show_environment(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *text; - sd_bus *bus; - int r; - - pager_open(arg_no_pager, false); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = sd_bus_get_property( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "Environment", - &error, - &reply, - "as"); - if (r < 0) - return log_error_errno(r, "Failed to get environment: %s", bus_error_message(&error, r)); - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "s"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_STRING, &text)) > 0) - puts(text); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - return 0; -} - -static int switch_root(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *cmdline_init = NULL; - const char *root, *init; - sd_bus *bus; - int r; - - if (arg_transport != BUS_TRANSPORT_LOCAL) { - log_error("Cannot switch root remotely."); - return -EINVAL; - } - - if (argc < 2 || argc > 3) { - log_error("Wrong number of arguments."); - return -EINVAL; - } - - root = argv[1]; - - if (argc >= 3) - init = argv[2]; - else { - r = parse_env_file("/proc/cmdline", WHITESPACE, - "init", &cmdline_init, - NULL); - if (r < 0) - log_debug_errno(r, "Failed to parse /proc/cmdline: %m"); - - init = cmdline_init; - } - - if (isempty(init)) - init = NULL; - - if (init) { - const char *root_systemd_path = NULL, *root_init_path = NULL; - - root_systemd_path = strjoina(root, "/" SYSTEMD_BINARY_PATH); - root_init_path = strjoina(root, "/", init); - - /* If the passed init is actually the same as the - * systemd binary, then let's suppress it. */ - if (files_same(root_init_path, root_systemd_path) > 0) - init = NULL; - } - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - log_debug("Switching root - root: %s; init: %s", root, strna(init)); - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SwitchRoot", - &error, - NULL, - "ss", root, init); - if (r < 0) - return log_error_errno(r, "Failed to switch root: %s", bus_error_message(&error, r)); - - return 0; -} - -static int set_environment(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - const char *method; - sd_bus *bus; - int r; - - assert(argc > 1); - assert(argv); - - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - method = streq(argv[0], "set-environment") - ? "SetEnvironment" - : "UnsetEnvironment"; - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - method); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_strv(m, strv_skip(argv, 1)); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, NULL); - if (r < 0) - return log_error_errno(r, "Failed to set environment: %s", bus_error_message(&error, r)); - - return 0; -} - -static int import_environment(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - sd_bus *bus; - int r; - - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SetEnvironment"); - if (r < 0) - return bus_log_create_error(r); - - if (argc < 2) - r = sd_bus_message_append_strv(m, environ); - else { - char **a, **b; - - r = sd_bus_message_open_container(m, 'a', "s"); - if (r < 0) - return bus_log_create_error(r); - - STRV_FOREACH(a, strv_skip(argv, 1)) { - - if (!env_name_is_valid(*a)) { - log_error("Not a valid environment variable name: %s", *a); - return -EINVAL; - } - - STRV_FOREACH(b, environ) { - const char *eq; - - eq = startswith(*b, *a); - if (eq && *eq == '=') { - - r = sd_bus_message_append(m, "s", *b); - if (r < 0) - return bus_log_create_error(r); - - break; - } - } - } - - r = sd_bus_message_close_container(m); - } - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, NULL); - if (r < 0) - return log_error_errno(r, "Failed to import environment: %s", bus_error_message(&error, r)); - - return 0; -} - -static int enable_sysv_units(const char *verb, char **args) { - int r = 0; - -#if defined(HAVE_SYSV_COMPAT) - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - unsigned f = 0; - - /* Processes all SysV units, and reshuffles the array so that afterwards only the native units remain */ - - if (arg_scope != UNIT_FILE_SYSTEM) - return 0; - - if (getenv_bool("SYSTEMCTL_SKIP_SYSV") > 0) - return 0; - - if (!STR_IN_SET(verb, - "enable", - "disable", - "is-enabled")) - return 0; - - r = lookup_paths_init(&paths, arg_scope, LOOKUP_PATHS_EXCLUDE_GENERATED, arg_root); - if (r < 0) - return r; - - r = 0; - while (args[f]) { - - const char *argv[] = { - ROOTLIBEXECDIR "/systemd-sysv-install", - NULL, - NULL, - NULL, - NULL, - }; - - _cleanup_free_ char *p = NULL, *q = NULL, *l = NULL; - bool found_native = false, found_sysv; - siginfo_t status; - const char *name; - unsigned c = 1; - pid_t pid; - int j; - - name = args[f++]; - - if (!endswith(name, ".service")) - continue; - - if (path_is_absolute(name)) - continue; - - j = unit_file_exists(arg_scope, &paths, name); - if (j < 0 && !IN_SET(j, -ELOOP, -ERFKILL, -EADDRNOTAVAIL)) - return log_error_errno(j, "Failed to lookup unit file state: %m"); - found_native = j != 0; - - /* If we have both a native unit and a SysV script, enable/disable them both (below); for is-enabled, - * prefer the native unit */ - if (found_native && streq(verb, "is-enabled")) - continue; - - p = path_join(arg_root, SYSTEM_SYSVINIT_PATH, name); - if (!p) - return log_oom(); - - p[strlen(p) - strlen(".service")] = 0; - found_sysv = access(p, F_OK) >= 0; - if (!found_sysv) - continue; - - if (found_native) - log_info("Synchronizing state of %s with SysV service script with %s.", name, argv[0]); - else - log_info("%s is not a native service, redirecting to systemd-sysv-install.", name); - - if (!isempty(arg_root)) - argv[c++] = q = strappend("--root=", arg_root); - - argv[c++] = verb; - argv[c++] = basename(p); - argv[c] = NULL; - - l = strv_join((char**)argv, " "); - if (!l) - return log_oom(); - - log_info("Executing: %s", l); - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork: %m"); - else if (pid == 0) { - /* Child */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - execv(argv[0], (char**) argv); - log_error_errno(errno, "Failed to execute %s: %m", argv[0]); - _exit(EXIT_FAILURE); - } - - j = wait_for_terminate(pid, &status); - if (j < 0) { - log_error_errno(j, "Failed to wait for child: %m"); - return j; - } - - if (status.si_code == CLD_EXITED) { - if (streq(verb, "is-enabled")) { - if (status.si_status == 0) { - if (!arg_quiet) - puts("enabled"); - r = 1; - } else { - if (!arg_quiet) - puts("disabled"); - } - - } else if (status.si_status != 0) - return -EBADE; /* We don't warn here, under the assumption the script already showed an explanation */ - } else { - log_error("Unexpected waitid() result."); - return -EPROTO; - } - - if (found_native) - continue; - - /* Remove this entry, so that we don't try enabling it as native unit */ - assert(f > 0); - f--; - assert(args[f] == name); - strv_remove(args, name); - } - -#endif - return r; -} - -static int mangle_names(char **original_names, char ***mangled_names) { - char **i, **l, **name; - int r; - - l = i = new(char*, strv_length(original_names) + 1); - if (!l) - return log_oom(); - - STRV_FOREACH(name, original_names) { - - /* When enabling units qualified path names are OK, - * too, hence allow them explicitly. */ - - if (is_path(*name)) { - *i = strdup(*name); - if (!*i) { - strv_free(l); - return log_oom(); - } - } else { - r = unit_name_mangle(*name, UNIT_NAME_NOGLOB, i); - if (r < 0) { - strv_free(l); - return log_error_errno(r, "Failed to mangle unit name: %m"); - } - } - - i++; - } - - *i = NULL; - *mangled_names = l; - - return 0; -} - -static int enable_unit(int argc, char *argv[], void *userdata) { - _cleanup_strv_free_ char **names = NULL; - const char *verb = argv[0]; - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - int carries_install_info = -1; - bool ignore_carries_install_info = arg_quiet; - int r; - - if (!argv[1]) - return 0; - - r = mangle_names(strv_skip(argv, 1), &names); - if (r < 0) - return r; - - r = enable_sysv_units(verb, names); - if (r < 0) - return r; - - /* If the operation was fully executed by the SysV compat, let's finish early */ - if (strv_isempty(names)) { - if (arg_no_reload || install_client_side()) - return 0; - return daemon_reload(argc, argv, userdata); - } - - if (install_client_side()) { - if (streq(verb, "enable")) { - r = unit_file_enable(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); - carries_install_info = r; - } else if (streq(verb, "disable")) - r = unit_file_disable(arg_scope, arg_runtime, arg_root, names, &changes, &n_changes); - else if (streq(verb, "reenable")) { - r = unit_file_reenable(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); - carries_install_info = r; - } else if (streq(verb, "link")) - r = unit_file_link(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); - else if (streq(verb, "preset")) { - r = unit_file_preset(arg_scope, arg_runtime, arg_root, names, arg_preset_mode, arg_force, &changes, &n_changes); - } else if (streq(verb, "mask")) - r = unit_file_mask(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); - else if (streq(verb, "unmask")) - r = unit_file_unmask(arg_scope, arg_runtime, arg_root, names, &changes, &n_changes); - else if (streq(verb, "revert")) - r = unit_file_revert(arg_scope, arg_root, names, &changes, &n_changes); - else - assert_not_reached("Unknown verb"); - - unit_file_dump_changes(r, verb, changes, n_changes, arg_quiet); - if (r < 0) - return r; - r = 0; - } else { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - bool expect_carries_install_info = false; - bool send_runtime = true, send_force = true, send_preset_mode = false; - const char *method; - sd_bus *bus; - - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - if (streq(verb, "enable")) { - method = "EnableUnitFiles"; - expect_carries_install_info = true; - } else if (streq(verb, "disable")) { - method = "DisableUnitFiles"; - send_force = false; - } else if (streq(verb, "reenable")) { - method = "ReenableUnitFiles"; - expect_carries_install_info = true; - } else if (streq(verb, "link")) - method = "LinkUnitFiles"; - else if (streq(verb, "preset")) { - - if (arg_preset_mode != UNIT_FILE_PRESET_FULL) { - method = "PresetUnitFilesWithMode"; - send_preset_mode = true; - } else - method = "PresetUnitFiles"; - - expect_carries_install_info = true; - ignore_carries_install_info = true; - } else if (streq(verb, "mask")) - method = "MaskUnitFiles"; - else if (streq(verb, "unmask")) { - method = "UnmaskUnitFiles"; - send_force = false; - } else if (streq(verb, "revert")) { - method = "RevertUnitFiles"; - send_runtime = send_force = false; - } else - assert_not_reached("Unknown verb"); - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - method); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_strv(m, names); - if (r < 0) - return bus_log_create_error(r); - - if (send_preset_mode) { - r = sd_bus_message_append(m, "s", unit_file_preset_mode_to_string(arg_preset_mode)); - if (r < 0) - return bus_log_create_error(r); - } - - if (send_runtime) { - r = sd_bus_message_append(m, "b", arg_runtime); - if (r < 0) - return bus_log_create_error(r); - } - - if (send_force) { - r = sd_bus_message_append(m, "b", arg_force); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) - return log_error_errno(r, "Failed to %s unit: %s", verb, bus_error_message(&error, r)); - - if (expect_carries_install_info) { - r = sd_bus_message_read(reply, "b", &carries_install_info); - if (r < 0) - return bus_log_parse_error(r); - } - - r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes); - if (r < 0) - return r; - - /* Try to reload if enabled */ - if (!arg_no_reload) - r = daemon_reload(argc, argv, userdata); - else - r = 0; - } - - if (carries_install_info == 0 && !ignore_carries_install_info) - log_warning("The unit files have no installation config (WantedBy, RequiredBy, Also, Alias\n" - "settings in the [Install] section, and DefaultInstance for template units).\n" - "This means they are not meant to be enabled using systemctl.\n" - "Possible reasons for having this kind of units are:\n" - "1) A unit may be statically enabled by being symlinked from another unit's\n" - " .wants/ or .requires/ directory.\n" - "2) A unit's purpose may be to act as a helper for some other unit which has\n" - " a requirement dependency on it.\n" - "3) A unit may be started when needed via activation (socket, path, timer,\n" - " D-Bus, udev, scripted systemctl call, ...).\n" - "4) In case of template units, the unit is meant to be enabled with some\n" - " instance name specified."); - - if (arg_now && n_changes > 0 && STR_IN_SET(argv[0], "enable", "disable", "mask")) { - char *new_args[n_changes + 2]; - sd_bus *bus; - unsigned i; - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - goto finish; - - new_args[0] = (char*) (streq(argv[0], "enable") ? "start" : "stop"); - for (i = 0; i < n_changes; i++) - new_args[i + 1] = basename(changes[i].path); - new_args[i + 1] = NULL; - - r = start_unit(strv_length(new_args), new_args, userdata); - } - -finish: - unit_file_changes_free(changes, n_changes); - - return r; -} - -static int add_dependency(int argc, char *argv[], void *userdata) { - _cleanup_strv_free_ char **names = NULL; - _cleanup_free_ char *target = NULL; - const char *verb = argv[0]; - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - UnitDependency dep; - int r = 0; - - if (!argv[1]) - return 0; - - r = unit_name_mangle_with_suffix(argv[1], UNIT_NAME_NOGLOB, ".target", &target); - if (r < 0) - return log_error_errno(r, "Failed to mangle unit name: %m"); - - r = mangle_names(strv_skip(argv, 2), &names); - if (r < 0) - return r; - - if (streq(verb, "add-wants")) - dep = UNIT_WANTS; - else if (streq(verb, "add-requires")) - dep = UNIT_REQUIRES; - else - assert_not_reached("Unknown verb"); - - if (install_client_side()) { - r = unit_file_add_dependency(arg_scope, arg_runtime, arg_root, names, target, dep, arg_force, &changes, &n_changes); - unit_file_dump_changes(r, "add dependency on", changes, n_changes, arg_quiet); - - if (r > 0) - r = 0; - } else { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus; - - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "AddDependencyUnitFiles"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_strv(m, names); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "ssbb", target, unit_dependency_to_string(dep), arg_runtime, arg_force); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) - return log_error_errno(r, "Failed to add dependency: %s", bus_error_message(&error, r)); - - r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes); - if (r < 0) - goto finish; - - if (arg_no_reload) { - r = 0; - goto finish; - } - - r = daemon_reload(argc, argv, userdata); - } - -finish: - unit_file_changes_free(changes, n_changes); - - return r; -} - -static int preset_all(int argc, char *argv[], void *userdata) { - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - int r; - - if (install_client_side()) { - r = unit_file_preset_all(arg_scope, arg_runtime, arg_root, arg_preset_mode, arg_force, &changes, &n_changes); - unit_file_dump_changes(r, "preset", changes, n_changes, arg_quiet); - - if (r > 0) - r = 0; - } else { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - sd_bus *bus; - - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "PresetAllUnitFiles", - &error, - &reply, - "sbb", - unit_file_preset_mode_to_string(arg_preset_mode), - arg_runtime, - arg_force); - if (r < 0) - return log_error_errno(r, "Failed to preset all units: %s", bus_error_message(&error, r)); - - r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes); - if (r < 0) - goto finish; - - if (arg_no_reload) { - r = 0; - goto finish; - } - - r = daemon_reload(argc, argv, userdata); - } - -finish: - unit_file_changes_free(changes, n_changes); - - return r; -} - -static int unit_is_enabled(int argc, char *argv[], void *userdata) { - - _cleanup_strv_free_ char **names = NULL; - bool enabled; - char **name; - int r; - - r = mangle_names(strv_skip(argv, 1), &names); - if (r < 0) - return r; - - r = enable_sysv_units(argv[0], names); - if (r < 0) - return r; - - enabled = r > 0; - - if (install_client_side()) { - - STRV_FOREACH(name, names) { - UnitFileState state; - - r = unit_file_get_state(arg_scope, arg_root, *name, &state); - if (r < 0) - return log_error_errno(state, "Failed to get unit file state for %s: %m", *name); - - if (IN_SET(state, - UNIT_FILE_ENABLED, - UNIT_FILE_ENABLED_RUNTIME, - UNIT_FILE_STATIC, - UNIT_FILE_INDIRECT, - UNIT_FILE_GENERATED)) - enabled = true; - - if (!arg_quiet) - puts(unit_file_state_to_string(state)); - } - - r = 0; - } else { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus; - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - STRV_FOREACH(name, names) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *s; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GetUnitFileState", - &error, - &reply, - "s", *name); - if (r < 0) - return log_error_errno(r, "Failed to get unit file state for %s: %s", *name, bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "s", &s); - if (r < 0) - return bus_log_parse_error(r); - - if (STR_IN_SET(s, "enabled", "enabled-runtime", "static", "indirect", "generated")) - enabled = true; - - if (!arg_quiet) - puts(s); - } - } - - return enabled ? EXIT_SUCCESS : EXIT_FAILURE; -} - -static int is_system_running(int argc, char *argv[], void *userdata) { - _cleanup_free_ char *state = NULL; - sd_bus *bus; - int r; - - if (running_in_chroot() > 0 || (arg_transport == BUS_TRANSPORT_LOCAL && !sd_booted())) { - if (!arg_quiet) - puts("offline"); - return EXIT_FAILURE; - } - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SystemState", - NULL, - &state); - if (r < 0) { - if (!arg_quiet) - puts("unknown"); - return 0; - } - - if (!arg_quiet) - puts(state); - - return streq(state, "running") ? EXIT_SUCCESS : EXIT_FAILURE; -} - -static int create_edit_temp_file(const char *new_path, const char *original_path, char **ret_tmp_fn) { - _cleanup_free_ char *t = NULL; - int r; - - assert(new_path); - assert(original_path); - assert(ret_tmp_fn); - - r = tempfn_random(new_path, NULL, &t); - if (r < 0) - return log_error_errno(r, "Failed to determine temporary filename for \"%s\": %m", new_path); - - r = mkdir_parents(new_path, 0755); - if (r < 0) - return log_error_errno(r, "Failed to create directories for \"%s\": %m", new_path); - - r = copy_file(original_path, t, 0, 0644, 0); - if (r == -ENOENT) { - - r = touch(t); - if (r < 0) - return log_error_errno(r, "Failed to create temporary file \"%s\": %m", t); - - } else if (r < 0) - return log_error_errno(r, "Failed to copy \"%s\" to \"%s\": %m", original_path, t); - - *ret_tmp_fn = t; - t = NULL; - - return 0; -} - -static int get_file_to_edit( - const LookupPaths *paths, - const char *name, - char **ret_path) { - - _cleanup_free_ char *path = NULL, *run = NULL; - - assert(name); - assert(ret_path); - - path = strjoin(paths->persistent_config, "/", name, NULL); - if (!path) - return log_oom(); - - if (arg_runtime) { - run = strjoin(paths->runtime_config, name, NULL); - if (!run) - return log_oom(); - } - - if (arg_runtime) { - if (access(path, F_OK) >= 0) { - log_error("Refusing to create \"%s\" because it would be overridden by \"%s\" anyway.", run, path); - return -EEXIST; - } - - *ret_path = run; - run = NULL; - } else { - *ret_path = path; - path = NULL; - } - - return 0; -} - -static int unit_file_create_dropin( - const LookupPaths *paths, - const char *unit_name, - char **ret_new_path, - char **ret_tmp_path) { - - char *tmp_new_path, *tmp_tmp_path, *ending; - int r; - - assert(unit_name); - assert(ret_new_path); - assert(ret_tmp_path); - - ending = strjoina(unit_name, ".d/override.conf"); - r = get_file_to_edit(paths, ending, &tmp_new_path); - if (r < 0) - return r; - - r = create_edit_temp_file(tmp_new_path, tmp_new_path, &tmp_tmp_path); - if (r < 0) { - free(tmp_new_path); - return r; - } - - *ret_new_path = tmp_new_path; - *ret_tmp_path = tmp_tmp_path; - - return 0; -} - -static int unit_file_create_copy( - const LookupPaths *paths, - const char *unit_name, - const char *fragment_path, - char **ret_new_path, - char **ret_tmp_path) { - - char *tmp_new_path, *tmp_tmp_path; - int r; - - assert(fragment_path); - assert(unit_name); - assert(ret_new_path); - assert(ret_tmp_path); - - r = get_file_to_edit(paths, unit_name, &tmp_new_path); - if (r < 0) - return r; - - if (!path_equal(fragment_path, tmp_new_path) && access(tmp_new_path, F_OK) == 0) { - char response; - - r = ask_char(&response, "yn", "\"%s\" already exists. Overwrite with \"%s\"? [(y)es, (n)o] ", tmp_new_path, fragment_path); - if (r < 0) { - free(tmp_new_path); - return r; - } - if (response != 'y') { - log_warning("%s ignored", unit_name); - free(tmp_new_path); - return -1; - } - } - - r = create_edit_temp_file(tmp_new_path, fragment_path, &tmp_tmp_path); - if (r < 0) { - log_error_errno(r, "Failed to create temporary file for \"%s\": %m", tmp_new_path); - free(tmp_new_path); - return r; - } - - *ret_new_path = tmp_new_path; - *ret_tmp_path = tmp_tmp_path; - - return 0; -} - -static int run_editor(char **paths) { - pid_t pid; - int r; - - assert(paths); - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork: %m"); - - if (pid == 0) { - const char **args; - char *editor, **editor_args = NULL; - char **tmp_path, **original_path, *p; - unsigned n_editor_args = 0, i = 1; - size_t argc; - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - argc = strv_length(paths)/2 + 1; - - /* SYSTEMD_EDITOR takes precedence over EDITOR which takes precedence over VISUAL - * If neither SYSTEMD_EDITOR nor EDITOR nor VISUAL are present, - * we try to execute well known editors - */ - editor = getenv("SYSTEMD_EDITOR"); - if (!editor) - editor = getenv("EDITOR"); - if (!editor) - editor = getenv("VISUAL"); - - if (!isempty(editor)) { - editor_args = strv_split(editor, WHITESPACE); - if (!editor_args) { - (void) log_oom(); - _exit(EXIT_FAILURE); - } - n_editor_args = strv_length(editor_args); - argc += n_editor_args - 1; - } - args = newa(const char*, argc + 1); - - if (n_editor_args > 0) { - args[0] = editor_args[0]; - for (; i < n_editor_args; i++) - args[i] = editor_args[i]; - } - - STRV_FOREACH_PAIR(original_path, tmp_path, paths) { - args[i] = *tmp_path; - i++; - } - args[i] = NULL; - - if (n_editor_args > 0) - execvp(args[0], (char* const*) args); - - FOREACH_STRING(p, "editor", "nano", "vim", "vi") { - args[0] = p; - execvp(p, (char* const*) args); - /* We do not fail if the editor doesn't exist - * because we want to try each one of them before - * failing. - */ - if (errno != ENOENT) { - log_error_errno(errno, "Failed to execute %s: %m", editor); - _exit(EXIT_FAILURE); - } - } - - log_error("Cannot edit unit(s), no editor available. Please set either $SYSTEMD_EDITOR, $EDITOR or $VISUAL."); - _exit(EXIT_FAILURE); - } - - r = wait_for_terminate_and_warn("editor", pid, true); - if (r < 0) - return log_error_errno(r, "Failed to wait for child: %m"); - - return 0; -} - -static int find_paths_to_edit(sd_bus *bus, char **names, char ***paths) { - _cleanup_lookup_paths_free_ LookupPaths lp = {}; - char **name; - int r; - - assert(names); - assert(paths); - - r = lookup_paths_init(&lp, arg_scope, 0, arg_root); - if (r < 0) - return r; - - STRV_FOREACH(name, names) { - _cleanup_free_ char *path = NULL, *new_path = NULL, *tmp_path = NULL; - - r = unit_find_paths(bus, *name, &lp, &path, NULL); - if (r < 0) - return r; - else if (r == 0) - return -ENOENT; - else if (!path) { - // FIXME: support units with path==NULL (no FragmentPath) - log_error("No fragment exists for %s.", *name); - return -ENOENT; - } - - if (arg_full) - r = unit_file_create_copy(&lp, *name, path, &new_path, &tmp_path); - else - r = unit_file_create_dropin(&lp, *name, &new_path, &tmp_path); - if (r < 0) - return r; - - r = strv_push_pair(paths, new_path, tmp_path); - if (r < 0) - return log_oom(); - new_path = tmp_path = NULL; - } - - return 0; -} - -static int edit(int argc, char *argv[], void *userdata) { - _cleanup_strv_free_ char **names = NULL; - _cleanup_strv_free_ char **paths = NULL; - char **original, **tmp; - sd_bus *bus; - int r; - - if (!on_tty()) { - log_error("Cannot edit units if not on a tty."); - return -EINVAL; - } - - if (arg_transport != BUS_TRANSPORT_LOCAL) { - log_error("Cannot edit units remotely."); - return -EINVAL; - } - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = expand_names(bus, strv_skip(argv, 1), NULL, &names); - if (r < 0) - return log_error_errno(r, "Failed to expand names: %m"); - - r = find_paths_to_edit(bus, names, &paths); - if (r < 0) - return r; - - if (strv_isempty(paths)) - return -ENOENT; - - r = run_editor(paths); - if (r < 0) - goto end; - - STRV_FOREACH_PAIR(original, tmp, paths) { - /* If the temporary file is empty we ignore it. It's - * useful if the user wants to cancel its modification - */ - if (null_or_empty_path(*tmp)) { - log_warning("Editing \"%s\" canceled: temporary file is empty.", *original); - continue; - } - - r = rename(*tmp, *original); - if (r < 0) { - r = log_error_errno(errno, "Failed to rename \"%s\" to \"%s\": %m", *tmp, *original); - goto end; - } - } - - r = 0; - - if (!arg_no_reload && !install_client_side()) - r = daemon_reload(argc, argv, userdata); - -end: - STRV_FOREACH_PAIR(original, tmp, paths) { - (void) unlink(*tmp); - - /* Removing empty dropin dirs */ - if (!arg_full) { - _cleanup_free_ char *dir; - - dir = dirname_malloc(*original); - if (!dir) - return log_oom(); - - /* no need to check if the dir is empty, rmdir - * does nothing if it is not the case. - */ - (void) rmdir(dir); - } - } - - return r; -} - -static void systemctl_help(void) { - - pager_open(arg_no_pager, false); - - printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Query or send control commands to the systemd manager.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --system Connect to system manager\n" - " --user Connect to user service manager\n" - " -H --host=[USER@]HOST\n" - " Operate on remote host\n" - " -M --machine=CONTAINER\n" - " Operate on local container\n" - " -t --type=TYPE List units of a particular type\n" - " --state=STATE List units with particular LOAD or SUB or ACTIVE state\n" - " -p --property=NAME Show only properties by this name\n" - " -a --all Show all loaded units/properties, including dead/empty\n" - " ones. To list all units installed on the system, use\n" - " the 'list-unit-files' command instead.\n" - " -l --full Don't ellipsize unit names on output\n" - " -r --recursive Show unit list of host and local containers\n" - " --reverse Show reverse dependencies with 'list-dependencies'\n" - " --job-mode=MODE Specify how to deal with already queued jobs, when\n" - " queueing a new job\n" - " --show-types When showing sockets, explicitly show their type\n" - " --value When showing properties, only print the value\n" - " -i --ignore-inhibitors\n" - " When shutting down or sleeping, ignore inhibitors\n" - " --kill-who=WHO Who to send signal to\n" - " -s --signal=SIGNAL Which signal to send\n" - " --now Start or stop unit in addition to enabling or disabling it\n" - " -q --quiet Suppress output\n" - " --no-block Do not wait until operation finished\n" - " --no-wall Don't send wall message before halt/power-off/reboot\n" - " --no-reload Don't reload daemon after en-/dis-abling unit files\n" - " --no-legend Do not print a legend (column headers and hints)\n" - " --no-pager Do not pipe output into a pager\n" - " --no-ask-password\n" - " Do not ask for system passwords\n" - " --global Enable/disable unit files globally\n" - " --runtime Enable unit files only temporarily until next reboot\n" - " -f --force When enabling unit files, override existing symlinks\n" - " When shutting down, execute action immediately\n" - " --preset-mode= Apply only enable, only disable, or all presets\n" - " --root=PATH Enable unit files in the specified root directory\n" - " -n --lines=INTEGER Number of journal entries to show\n" - " -o --output=STRING Change journal output mode (short, short-iso,\n" - " short-precise, short-monotonic, verbose,\n" - " export, json, json-pretty, json-sse, cat)\n" - " --firmware-setup Tell the firmware to show the setup menu on next boot\n" - " --plain Print unit dependencies as a list instead of a tree\n\n" - "Unit Commands:\n" - " list-units [PATTERN...] List loaded units\n" - " list-sockets [PATTERN...] List loaded sockets ordered by address\n" - " list-timers [PATTERN...] List loaded timers ordered by next elapse\n" - " start NAME... Start (activate) one or more units\n" - " stop NAME... Stop (deactivate) one or more units\n" - " reload NAME... Reload one or more units\n" - " restart NAME... Start or restart one or more units\n" - " try-restart NAME... Restart one or more units if active\n" - " reload-or-restart NAME... Reload one or more units if possible,\n" - " otherwise start or restart\n" - " try-reload-or-restart NAME... If active, reload one or more units,\n" - " if supported, otherwise restart\n" - " isolate NAME Start one unit and stop all others\n" - " kill NAME... Send signal to processes of a unit\n" - " is-active PATTERN... Check whether units are active\n" - " is-failed PATTERN... Check whether units are failed\n" - " status [PATTERN...|PID...] Show runtime status of one or more units\n" - " show [PATTERN...|JOB...] Show properties of one or more\n" - " units/jobs or the manager\n" - " cat PATTERN... Show files and drop-ins of one or more units\n" - " set-property NAME ASSIGNMENT... Sets one or more properties of a unit\n" - " help PATTERN...|PID... Show manual for one or more units\n" - " reset-failed [PATTERN...] Reset failed state for all, one, or more\n" - " units\n" - " list-dependencies [NAME] Recursively show units which are required\n" - " or wanted by this unit or by which this\n" - " unit is required or wanted\n\n" - "Unit File Commands:\n" - " list-unit-files [PATTERN...] List installed unit files\n" - " enable NAME... Enable one or more unit files\n" - " disable NAME... Disable one or more unit files\n" - " reenable NAME... Reenable one or more unit files\n" - " preset NAME... Enable/disable one or more unit files\n" - " based on preset configuration\n" - " preset-all Enable/disable all unit files based on\n" - " preset configuration\n" - " is-enabled NAME... Check whether unit files are enabled\n" - " mask NAME... Mask one or more units\n" - " unmask NAME... Unmask one or more units\n" - " link PATH... Link one or more units files into\n" - " the search path\n" - " revert NAME... Revert one or more unit files to vendor\n" - " version\n" - " add-wants TARGET NAME... Add 'Wants' dependency for the target\n" - " on specified one or more units\n" - " add-requires TARGET NAME... Add 'Requires' dependency for the target\n" - " on specified one or more units\n" - " edit NAME... Edit one or more unit files\n" - " get-default Get the name of the default target\n" - " set-default NAME Set the default target\n\n" - "Machine Commands:\n" - " list-machines [PATTERN...] List local containers and host\n\n" - "Job Commands:\n" - " list-jobs [PATTERN...] List jobs\n" - " cancel [JOB...] Cancel all, one, or more jobs\n\n" - "Environment Commands:\n" - " show-environment Dump environment\n" - " set-environment NAME=VALUE... Set one or more environment variables\n" - " unset-environment NAME... Unset one or more environment variables\n" - " import-environment [NAME...] Import all or some environment variables\n\n" - "Manager Lifecycle Commands:\n" - " daemon-reload Reload systemd manager configuration\n" - " daemon-reexec Reexecute systemd manager\n\n" - "System Commands:\n" - " is-system-running Check whether system is fully running\n" - " default Enter system default mode\n" - " rescue Enter system rescue mode\n" - " emergency Enter system emergency mode\n" - " halt Shut down and halt the system\n" - " poweroff Shut down and power-off the system\n" - " reboot [ARG] Shut down and reboot the system\n" - " kexec Shut down and reboot the system with kexec\n" - " exit [EXIT_CODE] Request user instance or container exit\n" - " switch-root ROOT [INIT] Change to a different root file system\n" - " suspend Suspend the system\n" - " hibernate Hibernate the system\n" - " hybrid-sleep Hibernate and suspend the system\n", - program_invocation_short_name); -} - -static void halt_help(void) { - printf("%s [OPTIONS...]%s\n\n" - "%s the system.\n\n" - " --help Show this help\n" - " --halt Halt the machine\n" - " -p --poweroff Switch off the machine\n" - " --reboot Reboot the machine\n" - " -f --force Force immediate halt/power-off/reboot\n" - " -w --wtmp-only Don't halt/power-off/reboot, just write wtmp record\n" - " -d --no-wtmp Don't write wtmp record\n" - " --no-wall Don't send wall message before halt/power-off/reboot\n", - program_invocation_short_name, - arg_action == ACTION_REBOOT ? " [ARG]" : "", - arg_action == ACTION_REBOOT ? "Reboot" : - arg_action == ACTION_POWEROFF ? "Power off" : - "Halt"); -} - -static void shutdown_help(void) { - printf("%s [OPTIONS...] [TIME] [WALL...]\n\n" - "Shut down the system.\n\n" - " --help Show this help\n" - " -H --halt Halt the machine\n" - " -P --poweroff Power-off the machine\n" - " -r --reboot Reboot the machine\n" - " -h Equivalent to --poweroff, overridden by --halt\n" - " -k Don't halt/power-off/reboot, just send warnings\n" - " --no-wall Don't send wall message before halt/power-off/reboot\n" - " -c Cancel a pending shutdown\n", - program_invocation_short_name); -} - -static void telinit_help(void) { - printf("%s [OPTIONS...] {COMMAND}\n\n" - "Send control commands to the init daemon.\n\n" - " --help Show this help\n" - " --no-wall Don't send wall message before halt/power-off/reboot\n\n" - "Commands:\n" - " 0 Power-off the machine\n" - " 6 Reboot the machine\n" - " 2, 3, 4, 5 Start runlevelX.target unit\n" - " 1, s, S Enter rescue mode\n" - " q, Q Reload init daemon configuration\n" - " u, U Reexecute init daemon\n", - program_invocation_short_name); -} - -static void runlevel_help(void) { - printf("%s [OPTIONS...]\n\n" - "Prints the previous and current runlevel of the init system.\n\n" - " --help Show this help\n", - program_invocation_short_name); -} - -static void help_types(void) { - int i; - - if (!arg_no_legend) - puts("Available unit types:"); - for (i = 0; i < _UNIT_TYPE_MAX; i++) - puts(unit_type_to_string(i)); -} - -static void help_states(void) { - int i; - - if (!arg_no_legend) - puts("Available unit load states:"); - for (i = 0; i < _UNIT_LOAD_STATE_MAX; i++) - puts(unit_load_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable unit active states:"); - for (i = 0; i < _UNIT_ACTIVE_STATE_MAX; i++) - puts(unit_active_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable automount unit substates:"); - for (i = 0; i < _AUTOMOUNT_STATE_MAX; i++) - puts(automount_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable busname unit substates:"); - for (i = 0; i < _BUSNAME_STATE_MAX; i++) - puts(busname_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable device unit substates:"); - for (i = 0; i < _DEVICE_STATE_MAX; i++) - puts(device_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable mount unit substates:"); - for (i = 0; i < _MOUNT_STATE_MAX; i++) - puts(mount_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable path unit substates:"); - for (i = 0; i < _PATH_STATE_MAX; i++) - puts(path_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable scope unit substates:"); - for (i = 0; i < _SCOPE_STATE_MAX; i++) - puts(scope_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable service unit substates:"); - for (i = 0; i < _SERVICE_STATE_MAX; i++) - puts(service_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable slice unit substates:"); - for (i = 0; i < _SLICE_STATE_MAX; i++) - puts(slice_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable socket unit substates:"); - for (i = 0; i < _SOCKET_STATE_MAX; i++) - puts(socket_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable swap unit substates:"); - for (i = 0; i < _SWAP_STATE_MAX; i++) - puts(swap_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable target unit substates:"); - for (i = 0; i < _TARGET_STATE_MAX; i++) - puts(target_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable timer unit substates:"); - for (i = 0; i < _TIMER_STATE_MAX; i++) - puts(timer_state_to_string(i)); -} - -static int systemctl_parse_argv(int argc, char *argv[]) { - - enum { - ARG_FAIL = 0x100, - ARG_REVERSE, - ARG_AFTER, - ARG_BEFORE, - ARG_SHOW_TYPES, - ARG_IRREVERSIBLE, - ARG_IGNORE_DEPENDENCIES, - ARG_VALUE, - ARG_VERSION, - ARG_USER, - ARG_SYSTEM, - ARG_GLOBAL, - ARG_NO_BLOCK, - ARG_NO_LEGEND, - ARG_NO_PAGER, - ARG_NO_WALL, - ARG_ROOT, - ARG_NO_RELOAD, - ARG_KILL_WHO, - ARG_NO_ASK_PASSWORD, - ARG_FAILED, - ARG_RUNTIME, - ARG_FORCE, - ARG_PLAIN, - ARG_STATE, - ARG_JOB_MODE, - ARG_PRESET_MODE, - ARG_FIRMWARE_SETUP, - ARG_NOW, - ARG_MESSAGE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "type", required_argument, NULL, 't' }, - { "property", required_argument, NULL, 'p' }, - { "all", no_argument, NULL, 'a' }, - { "reverse", no_argument, NULL, ARG_REVERSE }, - { "after", no_argument, NULL, ARG_AFTER }, - { "before", no_argument, NULL, ARG_BEFORE }, - { "show-types", no_argument, NULL, ARG_SHOW_TYPES }, - { "failed", no_argument, NULL, ARG_FAILED }, /* compatibility only */ - { "full", no_argument, NULL, 'l' }, - { "job-mode", required_argument, NULL, ARG_JOB_MODE }, - { "fail", no_argument, NULL, ARG_FAIL }, /* compatibility only */ - { "irreversible", no_argument, NULL, ARG_IRREVERSIBLE }, /* compatibility only */ - { "ignore-dependencies", no_argument, NULL, ARG_IGNORE_DEPENDENCIES }, /* compatibility only */ - { "ignore-inhibitors", no_argument, NULL, 'i' }, - { "value", no_argument, NULL, ARG_VALUE }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "global", no_argument, NULL, ARG_GLOBAL }, - { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-wall", no_argument, NULL, ARG_NO_WALL }, - { "quiet", no_argument, NULL, 'q' }, - { "root", required_argument, NULL, ARG_ROOT }, - { "force", no_argument, NULL, ARG_FORCE }, - { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, - { "kill-who", required_argument, NULL, ARG_KILL_WHO }, - { "signal", required_argument, NULL, 's' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "runtime", no_argument, NULL, ARG_RUNTIME }, - { "lines", required_argument, NULL, 'n' }, - { "output", required_argument, NULL, 'o' }, - { "plain", no_argument, NULL, ARG_PLAIN }, - { "state", required_argument, NULL, ARG_STATE }, - { "recursive", no_argument, NULL, 'r' }, - { "preset-mode", required_argument, NULL, ARG_PRESET_MODE }, - { "firmware-setup", no_argument, NULL, ARG_FIRMWARE_SETUP }, - { "now", no_argument, NULL, ARG_NOW }, - { "message", required_argument, NULL, ARG_MESSAGE }, - {} - }; - - const char *p; - int c, r; - - assert(argc >= 0); - assert(argv); - - /* we default to allowing interactive authorization only in systemctl (not in the legacy commands) */ - arg_ask_password = true; - - while ((c = getopt_long(argc, argv, "ht:p:alqfs:H:M:n:o:ir", options, NULL)) >= 0) - - switch (c) { - - case 'h': - systemctl_help(); - return 0; - - case ARG_VERSION: - return version(); - - case 't': { - if (isempty(optarg)) { - log_error("--type requires arguments."); - return -EINVAL; - } - - p = optarg; - for (;;) { - _cleanup_free_ char *type = NULL; - - r = extract_first_word(&p, &type, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse type: %s", optarg); - - if (r == 0) - break; - - if (streq(type, "help")) { - help_types(); - return 0; - } - - if (unit_type_from_string(type) >= 0) { - if (strv_push(&arg_types, type) < 0) - return log_oom(); - type = NULL; - continue; - } - - /* It's much nicer to use --state= for - * load states, but let's support this - * in --types= too for compatibility - * with old versions */ - if (unit_load_state_from_string(type) >= 0) { - if (strv_push(&arg_states, type) < 0) - return log_oom(); - type = NULL; - continue; - } - - log_error("Unknown unit type or load state '%s'.", type); - log_info("Use -t help to see a list of allowed values."); - return -EINVAL; - } - - break; - } - - case 'p': { - /* Make sure that if the empty property list - was specified, we won't show any properties. */ - if (isempty(optarg) && !arg_properties) { - arg_properties = new0(char*, 1); - if (!arg_properties) - return log_oom(); - } else { - p = optarg; - for (;;) { - _cleanup_free_ char *prop = NULL; - - r = extract_first_word(&p, &prop, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse property: %s", optarg); - - if (r == 0) - break; - - if (strv_push(&arg_properties, prop) < 0) - return log_oom(); - - prop = NULL; - } - } - - /* If the user asked for a particular - * property, show it to him, even if it is - * empty. */ - arg_all = true; - - break; - } - - case 'a': - arg_all = true; - break; - - case ARG_REVERSE: - arg_dependency = DEPENDENCY_REVERSE; - break; - - case ARG_AFTER: - arg_dependency = DEPENDENCY_AFTER; - break; - - case ARG_BEFORE: - arg_dependency = DEPENDENCY_BEFORE; - break; - - case ARG_SHOW_TYPES: - arg_show_types = true; - break; - - case ARG_VALUE: - arg_value = true; - break; - - case ARG_JOB_MODE: - arg_job_mode = optarg; - break; - - case ARG_FAIL: - arg_job_mode = "fail"; - break; - - case ARG_IRREVERSIBLE: - arg_job_mode = "replace-irreversibly"; - break; - - case ARG_IGNORE_DEPENDENCIES: - arg_job_mode = "ignore-dependencies"; - break; - - case ARG_USER: - arg_scope = UNIT_FILE_USER; - break; - - case ARG_SYSTEM: - arg_scope = UNIT_FILE_SYSTEM; - break; - - case ARG_GLOBAL: - arg_scope = UNIT_FILE_GLOBAL; - break; - - case ARG_NO_BLOCK: - arg_no_block = true; - break; - - case ARG_NO_LEGEND: - arg_no_legend = true; - break; - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case ARG_NO_WALL: - arg_no_wall = true; - break; - - case ARG_ROOT: - r = parse_path_argument_and_warn(optarg, false, &arg_root); - if (r < 0) - return r; - break; - - case 'l': - arg_full = true; - break; - - case ARG_FAILED: - if (strv_extend(&arg_states, "failed") < 0) - return log_oom(); - - break; - - case 'q': - arg_quiet = true; - break; - - case ARG_FORCE: - arg_force++; - break; - - case 'f': - arg_force++; - break; - - case ARG_NO_RELOAD: - arg_no_reload = true; - break; - - case ARG_KILL_WHO: - arg_kill_who = optarg; - break; - - case 's': - arg_signal = signal_from_string_try_harder(optarg); - if (arg_signal < 0) { - log_error("Failed to parse signal string %s.", optarg); - return -EINVAL; - } - break; - - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; - - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; - break; - - case 'M': - arg_transport = BUS_TRANSPORT_MACHINE; - arg_host = optarg; - break; - - case ARG_RUNTIME: - arg_runtime = true; - break; - - case 'n': - if (safe_atou(optarg, &arg_lines) < 0) { - log_error("Failed to parse lines '%s'", optarg); - return -EINVAL; - } - break; - - case 'o': - arg_output = output_mode_from_string(optarg); - if (arg_output < 0) { - log_error("Unknown output '%s'.", optarg); - return -EINVAL; - } - break; - - case 'i': - arg_ignore_inhibitors = true; - break; - - case ARG_PLAIN: - arg_plain = true; - break; - - case ARG_FIRMWARE_SETUP: - arg_firmware_setup = true; - break; - - case ARG_STATE: { - if (isempty(optarg)) { - log_error("--signal requires arguments."); - return -EINVAL; - } - - p = optarg; - for (;;) { - _cleanup_free_ char *s = NULL; - - r = extract_first_word(&p, &s, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse signal: %s", optarg); - - if (r == 0) - break; - - if (streq(s, "help")) { - help_states(); - return 0; - } - - if (strv_push(&arg_states, s) < 0) - return log_oom(); - - s = NULL; - } - break; - } - - case 'r': - if (geteuid() != 0) { - log_error("--recursive requires root privileges."); - return -EPERM; - } - - arg_recursive = true; - break; - - case ARG_PRESET_MODE: - - arg_preset_mode = unit_file_preset_mode_from_string(optarg); - if (arg_preset_mode < 0) { - log_error("Failed to parse preset mode: %s.", optarg); - return -EINVAL; - } - - break; - - case ARG_NOW: - arg_now = true; - break; - - case ARG_MESSAGE: - if (strv_extend(&arg_wall, optarg) < 0) - return log_oom(); - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (arg_transport != BUS_TRANSPORT_LOCAL && arg_scope != UNIT_FILE_SYSTEM) { - log_error("Cannot access user instance remotely."); - return -EINVAL; - } - - return 1; -} - -static int halt_parse_argv(int argc, char *argv[]) { - - enum { - ARG_HELP = 0x100, - ARG_HALT, - ARG_REBOOT, - ARG_NO_WALL - }; - - static const struct option options[] = { - { "help", no_argument, NULL, ARG_HELP }, - { "halt", no_argument, NULL, ARG_HALT }, - { "poweroff", no_argument, NULL, 'p' }, - { "reboot", no_argument, NULL, ARG_REBOOT }, - { "force", no_argument, NULL, 'f' }, - { "wtmp-only", no_argument, NULL, 'w' }, - { "no-wtmp", no_argument, NULL, 'd' }, - { "no-sync", no_argument, NULL, 'n' }, - { "no-wall", no_argument, NULL, ARG_NO_WALL }, - {} - }; - - int c, r, runlevel; - - assert(argc >= 0); - assert(argv); - - if (utmp_get_runlevel(&runlevel, NULL) >= 0) - if (runlevel == '0' || runlevel == '6') - arg_force = 2; - - while ((c = getopt_long(argc, argv, "pfwdnih", options, NULL)) >= 0) - switch (c) { - - case ARG_HELP: - halt_help(); - return 0; - - case ARG_HALT: - arg_action = ACTION_HALT; - break; - - case 'p': - if (arg_action != ACTION_REBOOT) - arg_action = ACTION_POWEROFF; - break; - - case ARG_REBOOT: - arg_action = ACTION_REBOOT; - break; - - case 'f': - arg_force = 2; - break; - - case 'w': - arg_dry = true; - break; - - case 'd': - arg_no_wtmp = true; - break; - - case 'n': - arg_no_sync = true; - break; - - case ARG_NO_WALL: - arg_no_wall = true; - break; - - case 'i': - case 'h': - /* Compatibility nops */ - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (arg_action == ACTION_REBOOT && (argc == optind || argc == optind + 1)) { - r = update_reboot_parameter_and_warn(argc == optind + 1 ? argv[optind] : NULL); - if (r < 0) - return r; - } else if (optind < argc) { - log_error("Too many arguments."); - return -EINVAL; - } - - return 1; -} - -static int parse_shutdown_time_spec(const char *t, usec_t *_u) { - assert(t); - assert(_u); - - if (streq(t, "now")) - *_u = 0; - else if (!strchr(t, ':')) { - uint64_t u; - - if (safe_atou64(t, &u) < 0) - return -EINVAL; - - *_u = now(CLOCK_REALTIME) + USEC_PER_MINUTE * u; - } else { - char *e = NULL; - long hour, minute; - struct tm tm = {}; - time_t s; - usec_t n; - - errno = 0; - hour = strtol(t, &e, 10); - if (errno > 0 || *e != ':' || hour < 0 || hour > 23) - return -EINVAL; - - minute = strtol(e+1, &e, 10); - if (errno > 0 || *e != 0 || minute < 0 || minute > 59) - return -EINVAL; - - n = now(CLOCK_REALTIME); - s = (time_t) (n / USEC_PER_SEC); - - assert_se(localtime_r(&s, &tm)); - - tm.tm_hour = (int) hour; - tm.tm_min = (int) minute; - tm.tm_sec = 0; - - assert_se(s = mktime(&tm)); - - *_u = (usec_t) s * USEC_PER_SEC; - - while (*_u <= n) - *_u += USEC_PER_DAY; - } - - return 0; -} - -static int shutdown_parse_argv(int argc, char *argv[]) { - - enum { - ARG_HELP = 0x100, - ARG_NO_WALL - }; - - static const struct option options[] = { - { "help", no_argument, NULL, ARG_HELP }, - { "halt", no_argument, NULL, 'H' }, - { "poweroff", no_argument, NULL, 'P' }, - { "reboot", no_argument, NULL, 'r' }, - { "kexec", no_argument, NULL, 'K' }, /* not documented extension */ - { "no-wall", no_argument, NULL, ARG_NO_WALL }, - {} - }; - - char **wall = NULL; - int c, r; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "HPrhkKtafFc", options, NULL)) >= 0) - switch (c) { - - case ARG_HELP: - shutdown_help(); - return 0; - - case 'H': - arg_action = ACTION_HALT; - break; - - case 'P': - arg_action = ACTION_POWEROFF; - break; - - case 'r': - if (kexec_loaded()) - arg_action = ACTION_KEXEC; - else - arg_action = ACTION_REBOOT; - break; - - case 'K': - arg_action = ACTION_KEXEC; - break; - - case 'h': - if (arg_action != ACTION_HALT) - arg_action = ACTION_POWEROFF; - break; - - case 'k': - arg_dry = true; - break; - - case ARG_NO_WALL: - arg_no_wall = true; - break; - - case 't': - case 'a': - case 'f': - case 'F': - /* Compatibility nops */ - break; - - case 'c': - arg_action = ACTION_CANCEL_SHUTDOWN; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (argc > optind && arg_action != ACTION_CANCEL_SHUTDOWN) { - r = parse_shutdown_time_spec(argv[optind], &arg_when); - if (r < 0) { - log_error("Failed to parse time specification: %s", argv[optind]); - return r; - } - } else - arg_when = now(CLOCK_REALTIME) + USEC_PER_MINUTE; - - if (argc > optind && arg_action == ACTION_CANCEL_SHUTDOWN) - /* No time argument for shutdown cancel */ - wall = argv + optind; - else if (argc > optind + 1) - /* We skip the time argument */ - wall = argv + optind + 1; - - if (wall) { - arg_wall = strv_copy(wall); - if (!arg_wall) - return log_oom(); - } - - optind = argc; - - return 1; -} - -static int telinit_parse_argv(int argc, char *argv[]) { - - enum { - ARG_HELP = 0x100, - ARG_NO_WALL - }; - - static const struct option options[] = { - { "help", no_argument, NULL, ARG_HELP }, - { "no-wall", no_argument, NULL, ARG_NO_WALL }, - {} - }; - - static const struct { - char from; - enum action to; - } table[] = { - { '0', ACTION_POWEROFF }, - { '6', ACTION_REBOOT }, - { '1', ACTION_RESCUE }, - { '2', ACTION_RUNLEVEL2 }, - { '3', ACTION_RUNLEVEL3 }, - { '4', ACTION_RUNLEVEL4 }, - { '5', ACTION_RUNLEVEL5 }, - { 's', ACTION_RESCUE }, - { 'S', ACTION_RESCUE }, - { 'q', ACTION_RELOAD }, - { 'Q', ACTION_RELOAD }, - { 'u', ACTION_REEXEC }, - { 'U', ACTION_REEXEC } - }; - - unsigned i; - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0) - switch (c) { - - case ARG_HELP: - telinit_help(); - return 0; - - case ARG_NO_WALL: - arg_no_wall = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (optind >= argc) { - log_error("%s: required argument missing.", program_invocation_short_name); - return -EINVAL; - } - - if (optind + 1 < argc) { - log_error("Too many arguments."); - return -EINVAL; - } - - if (strlen(argv[optind]) != 1) { - log_error("Expected single character argument."); - return -EINVAL; - } - - for (i = 0; i < ELEMENTSOF(table); i++) - if (table[i].from == argv[optind][0]) - break; - - if (i >= ELEMENTSOF(table)) { - log_error("Unknown command '%s'.", argv[optind]); - return -EINVAL; - } - - arg_action = table[i].to; - - optind++; - - return 1; -} - -static int runlevel_parse_argv(int argc, char *argv[]) { - - enum { - ARG_HELP = 0x100, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, ARG_HELP }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0) - switch (c) { - - case ARG_HELP: - runlevel_help(); - return 0; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (optind < argc) { - log_error("Too many arguments."); - return -EINVAL; - } - - return 1; -} - -static int parse_argv(int argc, char *argv[]) { - assert(argc >= 0); - assert(argv); - - if (program_invocation_short_name) { - - if (strstr(program_invocation_short_name, "halt")) { - arg_action = ACTION_HALT; - return halt_parse_argv(argc, argv); - } else if (strstr(program_invocation_short_name, "poweroff")) { - arg_action = ACTION_POWEROFF; - return halt_parse_argv(argc, argv); - } else if (strstr(program_invocation_short_name, "reboot")) { - if (kexec_loaded()) - arg_action = ACTION_KEXEC; - else - arg_action = ACTION_REBOOT; - return halt_parse_argv(argc, argv); - } else if (strstr(program_invocation_short_name, "shutdown")) { - arg_action = ACTION_POWEROFF; - return shutdown_parse_argv(argc, argv); - } else if (strstr(program_invocation_short_name, "init")) { - - if (sd_booted() > 0) { - arg_action = _ACTION_INVALID; - return telinit_parse_argv(argc, argv); - } else { - /* Hmm, so some other init system is - * running, we need to forward this - * request to it. For now we simply - * guess that it is Upstart. */ - - execv(TELINIT, argv); - - log_error("Couldn't find an alternative telinit implementation to spawn."); - return -EIO; - } - - } else if (strstr(program_invocation_short_name, "runlevel")) { - arg_action = ACTION_RUNLEVEL; - return runlevel_parse_argv(argc, argv); - } - } - - arg_action = ACTION_SYSTEMCTL; - return systemctl_parse_argv(argc, argv); -} - -#ifdef HAVE_SYSV_COMPAT -_pure_ static int action_to_runlevel(void) { - - static const char table[_ACTION_MAX] = { - [ACTION_HALT] = '0', - [ACTION_POWEROFF] = '0', - [ACTION_REBOOT] = '6', - [ACTION_RUNLEVEL2] = '2', - [ACTION_RUNLEVEL3] = '3', - [ACTION_RUNLEVEL4] = '4', - [ACTION_RUNLEVEL5] = '5', - [ACTION_RESCUE] = '1' - }; - - assert(arg_action < _ACTION_MAX); - - return table[arg_action]; -} -#endif - -static int talk_initctl(void) { -#ifdef HAVE_SYSV_COMPAT - struct init_request request = { - .magic = INIT_MAGIC, - .sleeptime = 0, - .cmd = INIT_CMD_RUNLVL - }; - - _cleanup_close_ int fd = -1; - char rl; - int r; - - rl = action_to_runlevel(); - if (!rl) - return 0; - - request.runlevel = rl; - - fd = open(INIT_FIFO, O_WRONLY|O_NDELAY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open "INIT_FIFO": %m"); - } - - r = loop_write(fd, &request, sizeof(request), false); - if (r < 0) - return log_error_errno(r, "Failed to write to "INIT_FIFO": %m"); - - return 1; -#else - return 0; -#endif -} - -static int systemctl_main(int argc, char *argv[]) { - - static const Verb verbs[] = { - { "list-units", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_NOCHROOT, list_units }, - { "list-unit-files", VERB_ANY, VERB_ANY, 0, list_unit_files }, - { "list-sockets", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_sockets }, - { "list-timers", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_timers }, - { "list-jobs", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_jobs }, - { "list-machines", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_machines }, - { "clear-jobs", VERB_ANY, 1, VERB_NOCHROOT, daemon_reload }, - { "cancel", VERB_ANY, VERB_ANY, VERB_NOCHROOT, cancel_job }, - { "start", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, - { "stop", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, - { "condstop", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with ALTLinux */ - { "reload", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, - { "restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, - { "try-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, - { "reload-or-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, - { "reload-or-try-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatbility with old systemctl <= 228 */ - { "try-reload-or-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, - { "force-reload", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with SysV */ - { "condreload", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with ALTLinux */ - { "condrestart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with RH */ - { "isolate", 2, 2, VERB_NOCHROOT, start_unit }, - { "kill", 2, VERB_ANY, VERB_NOCHROOT, kill_unit }, - { "is-active", 2, VERB_ANY, VERB_NOCHROOT, check_unit_active }, - { "check", 2, VERB_ANY, VERB_NOCHROOT, check_unit_active }, - { "is-failed", 2, VERB_ANY, VERB_NOCHROOT, check_unit_failed }, - { "show", VERB_ANY, VERB_ANY, VERB_NOCHROOT, show }, - { "cat", 2, VERB_ANY, VERB_NOCHROOT, cat }, - { "status", VERB_ANY, VERB_ANY, VERB_NOCHROOT, show }, - { "help", VERB_ANY, VERB_ANY, VERB_NOCHROOT, show }, - { "daemon-reload", VERB_ANY, 1, VERB_NOCHROOT, daemon_reload }, - { "daemon-reexec", VERB_ANY, 1, VERB_NOCHROOT, daemon_reload }, - { "show-environment", VERB_ANY, 1, VERB_NOCHROOT, show_environment }, - { "set-environment", 2, VERB_ANY, VERB_NOCHROOT, set_environment }, - { "unset-environment", 2, VERB_ANY, VERB_NOCHROOT, set_environment }, - { "import-environment", VERB_ANY, VERB_ANY, VERB_NOCHROOT, import_environment}, - { "halt", VERB_ANY, 1, VERB_NOCHROOT, start_special }, - { "poweroff", VERB_ANY, 1, VERB_NOCHROOT, start_special }, - { "reboot", VERB_ANY, 2, VERB_NOCHROOT, start_special }, - { "kexec", VERB_ANY, 1, VERB_NOCHROOT, start_special }, - { "suspend", VERB_ANY, 1, VERB_NOCHROOT, start_special }, - { "hibernate", VERB_ANY, 1, VERB_NOCHROOT, start_special }, - { "hybrid-sleep", VERB_ANY, 1, VERB_NOCHROOT, start_special }, - { "default", VERB_ANY, 1, VERB_NOCHROOT, start_special }, - { "rescue", VERB_ANY, 1, VERB_NOCHROOT, start_special }, - { "emergency", VERB_ANY, 1, VERB_NOCHROOT, start_special }, - { "exit", VERB_ANY, 2, VERB_NOCHROOT, start_special }, - { "reset-failed", VERB_ANY, VERB_ANY, VERB_NOCHROOT, reset_failed }, - { "enable", 2, VERB_ANY, 0, enable_unit }, - { "disable", 2, VERB_ANY, 0, enable_unit }, - { "is-enabled", 2, VERB_ANY, 0, unit_is_enabled }, - { "reenable", 2, VERB_ANY, 0, enable_unit }, - { "preset", 2, VERB_ANY, 0, enable_unit }, - { "preset-all", VERB_ANY, 1, 0, preset_all }, - { "mask", 2, VERB_ANY, 0, enable_unit }, - { "unmask", 2, VERB_ANY, 0, enable_unit }, - { "link", 2, VERB_ANY, 0, enable_unit }, - { "revert", 2, VERB_ANY, 0, enable_unit }, - { "switch-root", 2, VERB_ANY, VERB_NOCHROOT, switch_root }, - { "list-dependencies", VERB_ANY, 2, VERB_NOCHROOT, list_dependencies }, - { "set-default", 2, 2, 0, set_default }, - { "get-default", VERB_ANY, 1, 0, get_default, }, - { "set-property", 3, VERB_ANY, VERB_NOCHROOT, set_property }, - { "is-system-running", VERB_ANY, 1, 0, is_system_running }, - { "add-wants", 3, VERB_ANY, 0, add_dependency }, - { "add-requires", 3, VERB_ANY, 0, add_dependency }, - { "edit", 2, VERB_ANY, VERB_NOCHROOT, edit }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - -static int reload_with_fallback(void) { - - /* First, try systemd via D-Bus. */ - if (daemon_reload(0, NULL, NULL) >= 0) - return 0; - - /* Nothing else worked, so let's try signals */ - assert(arg_action == ACTION_RELOAD || arg_action == ACTION_REEXEC); - - if (kill(1, arg_action == ACTION_RELOAD ? SIGHUP : SIGTERM) < 0) - return log_error_errno(errno, "kill() failed: %m"); - - return 0; -} - -static int start_with_fallback(void) { - - /* First, try systemd via D-Bus. */ - if (start_unit(0, NULL, NULL) >= 0) - return 0; - - /* Nothing else worked, so let's try - * /dev/initctl */ - if (talk_initctl() > 0) - return 0; - - log_error("Failed to talk to init daemon."); - return -EIO; -} - -static int halt_now(enum action a) { - int r; - - /* The kernel will automaticall flush ATA disks and suchlike - * on reboot(), but the file systems need to be synce'd - * explicitly in advance. */ - if (!arg_no_sync) - (void) sync(); - - /* Make sure C-A-D is handled by the kernel from this point - * on... */ - (void) reboot(RB_ENABLE_CAD); - - switch (a) { - - case ACTION_HALT: - log_info("Halting."); - (void) reboot(RB_HALT_SYSTEM); - return -errno; - - case ACTION_POWEROFF: - log_info("Powering off."); - (void) reboot(RB_POWER_OFF); - return -errno; - - case ACTION_KEXEC: - case ACTION_REBOOT: { - _cleanup_free_ char *param = NULL; - - r = read_one_line_file("/run/systemd/reboot-param", ¶m); - if (r < 0) - log_warning_errno(r, "Failed to read reboot parameter file: %m"); - - if (!isempty(param)) { - log_info("Rebooting with argument '%s'.", param); - (void) syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, param); - log_warning_errno(errno, "Failed to reboot with parameter, retrying without: %m"); - } - - log_info("Rebooting."); - (void) reboot(RB_AUTOBOOT); - return -errno; - } - - default: - assert_not_reached("Unknown action."); - } -} - -static int logind_schedule_shutdown(void) { - -#ifdef HAVE_LOGIND - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char date[FORMAT_TIMESTAMP_MAX]; - const char *action; - sd_bus *bus; - int r; - - (void) logind_set_wall_message(); - - r = acquire_bus(BUS_FULL, &bus); - if (r < 0) - return r; - - switch (arg_action) { - case ACTION_HALT: - action = "halt"; - break; - case ACTION_POWEROFF: - action = "poweroff"; - break; - case ACTION_KEXEC: - action = "kexec"; - break; - case ACTION_EXIT: - action = "exit"; - break; - case ACTION_REBOOT: - default: - action = "reboot"; - break; - } - - if (arg_dry) - action = strjoina("dry-", action); - - r = sd_bus_call_method( - bus, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "ScheduleShutdown", - &error, - NULL, - "st", - action, - arg_when); - if (r < 0) - return log_warning_errno(r, "Failed to call ScheduleShutdown in logind, proceeding with immediate shutdown: %s", bus_error_message(&error, r)); - - log_info("Shutdown scheduled for %s, use 'shutdown -c' to cancel.", format_timestamp(date, sizeof(date), arg_when)); - return 0; -#else - log_error("Cannot schedule shutdown without logind support, proceeding with immediate shutdown."); - return -ENOSYS; -#endif -} - -static int halt_main(void) { - int r; - - r = logind_check_inhibitors(arg_action); - if (r < 0) - return r; - - if (arg_when > 0) - return logind_schedule_shutdown(); - - if (geteuid() != 0) { - if (arg_dry || arg_force > 0) { - log_error("Must be root."); - return -EPERM; - } - - /* Try logind if we are a normal user and no special - * mode applies. Maybe PolicyKit allows us to shutdown - * the machine. */ - if (IN_SET(arg_action, ACTION_POWEROFF, ACTION_REBOOT)) { - r = logind_reboot(arg_action); - if (r >= 0) - return r; - if (IN_SET(r, -EOPNOTSUPP, -EINPROGRESS)) - /* requested operation is not - * supported on the local system or - * already in progress */ - return r; - /* on all other errors, try low-level operation */ - } - } - - if (!arg_dry && !arg_force) - return start_with_fallback(); - - assert(geteuid() == 0); - - if (!arg_no_wtmp) { - if (sd_booted() > 0) - log_debug("Not writing utmp record, assuming that systemd-update-utmp is used."); - else { - r = utmp_put_shutdown(); - if (r < 0) - log_warning_errno(r, "Failed to write utmp record: %m"); - } - } - - if (arg_dry) - return 0; - - r = halt_now(arg_action); - return log_error_errno(r, "Failed to reboot: %m"); -} - -static int runlevel_main(void) { - int r, runlevel, previous; - - r = utmp_get_runlevel(&runlevel, &previous); - if (r < 0) { - puts("unknown"); - return r; - } - - printf("%c %c\n", - previous <= 0 ? 'N' : previous, - runlevel <= 0 ? 'N' : runlevel); - - return 0; -} - -static int logind_cancel_shutdown(void) { -#ifdef HAVE_LOGIND - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus; - int r; - - r = acquire_bus(BUS_FULL, &bus); - if (r < 0) - return r; - - (void) logind_set_wall_message(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "CancelScheduledShutdown", - &error, - NULL, NULL); - if (r < 0) - return log_warning_errno(r, "Failed to talk to logind, shutdown hasn't been cancelled: %s", bus_error_message(&error, r)); - - return 0; -#else - log_error("Not compiled with logind support, cannot cancel scheduled shutdowns."); - return -ENOSYS; -#endif -} - -int main(int argc, char*argv[]) { - int r; - - setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); - - /* Explicitly not on_tty() to avoid setting cached value. - * This becomes relevant for piping output which might be - * ellipsized. */ - original_stdout_is_tty = isatty(STDOUT_FILENO); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (arg_action != ACTION_SYSTEMCTL && running_in_chroot() > 0) { - log_info("Running in chroot, ignoring request."); - r = 0; - goto finish; - } - - /* systemctl_main() will print an error message for the bus - * connection, but only if it needs to */ - - switch (arg_action) { - - case ACTION_SYSTEMCTL: - r = systemctl_main(argc, argv); - break; - - case ACTION_HALT: - case ACTION_POWEROFF: - case ACTION_REBOOT: - case ACTION_KEXEC: - r = halt_main(); - break; - - case ACTION_RUNLEVEL2: - case ACTION_RUNLEVEL3: - case ACTION_RUNLEVEL4: - case ACTION_RUNLEVEL5: - case ACTION_RESCUE: - case ACTION_EMERGENCY: - case ACTION_DEFAULT: - r = start_with_fallback(); - break; - - case ACTION_RELOAD: - case ACTION_REEXEC: - r = reload_with_fallback(); - break; - - case ACTION_CANCEL_SHUTDOWN: - r = logind_cancel_shutdown(); - break; - - case ACTION_RUNLEVEL: - r = runlevel_main(); - break; - - case _ACTION_INVALID: - default: - assert_not_reached("Unknown action"); - } - -finish: - pager_close(); - ask_password_agent_close(); - polkit_agent_close(); - - strv_free(arg_types); - strv_free(arg_states); - strv_free(arg_properties); - - strv_free(arg_wall); - free(arg_root); - - release_busses(); - - /* Note that we return r here, not EXIT_SUCCESS, so that we can implement the LSB-like return codes */ - - return r < 0 ? EXIT_FAILURE : r; -} diff --git a/src/systemctl/systemd-sysv-install.SKELETON b/src/systemctl/systemd-sysv-install.SKELETON deleted file mode 100755 index a53a3e6221..0000000000 --- a/src/systemctl/systemd-sysv-install.SKELETON +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/sh -# This script is called by "systemctl enable/disable" when the given unit is a -# SysV init.d script. It needs to call the distribution's mechanism for -# enabling/disabling those, such as chkconfig, update-rc.d, or similar. This -# can optionally take a --root argument for enabling a SysV init script -# in a chroot or similar. -set -e - -usage() { - echo "Usage: $0 [--root=path] enable|disable|is-enabled " >&2 - exit 1 -} - -# parse options -eval set -- "$(getopt -o r: --long root: -- "$@")" -while true; do - case "$1" in - -r|--root) - ROOT="$2" - shift 2 ;; - --) shift ; break ;; - *) usage ;; - esac -done - -NAME="$2" -[ -n "$NAME" ] || usage - -case "$1" in - enable) - # call the command to enable SysV init script $NAME here - # (consider optional $ROOT) - echo "IMPLEMENT ME: enabling SysV init.d script $NAME" - ;; - disable) - # call the command to disable SysV init script $NAME here - # (consider optional $ROOT) - echo "IMPLEMENT ME: disabling SysV init.d script $NAME" - ;; - is-enabled) - # exit with 0 if $NAME is enabled, non-zero if it is disabled - # (consider optional $ROOT) - echo "IMPLEMENT ME: checking SysV init.d script $NAME" - ;; - *) - usage ;; -esac diff --git a/src/systemd-ac-power/Makefile b/src/systemd-ac-power/Makefile new file mode 100644 index 0000000000..b06af09332 --- /dev/null +++ b/src/systemd-ac-power/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_ac_power_SOURCES = \ + src/ac-power/ac-power.c + +systemd_ac_power_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-ac-power/ac-power.c b/src/systemd-ac-power/ac-power.c new file mode 100644 index 0000000000..c5277884a8 --- /dev/null +++ b/src/systemd-ac-power/ac-power.c @@ -0,0 +1,35 @@ +/*** + This file is part of systemd. + + Copyright 2010 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" + +int main(int argc, char *argv[]) { + int r; + + /* This is mostly intended to be used for scripts which want + * to detect whether AC power is plugged in or not. */ + + r = on_ac_power(); + if (r < 0) { + log_error_errno(r, "Failed to read AC status: %m"); + return EXIT_FAILURE; + } + + return r != 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/systemd-activate/Makefile b/src/systemd-activate/Makefile new file mode 100644 index 0000000000..5e2299f00e --- /dev/null +++ b/src/systemd-activate/Makefile @@ -0,0 +1,36 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + + +bin_PROGRAMS += \ + systemd-socket-activate + +systemd_socket_activate_SOURCES = \ + src/activate/activate.c + +systemd_socket_activate_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-activate/activate.c b/src/systemd-activate/activate.c new file mode 100644 index 0000000000..89cc1ee813 --- /dev/null +++ b/src/systemd-activate/activate.c @@ -0,0 +1,545 @@ +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + 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 +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "escape.h" +#include "fd-util.h" +#include "log.h" +#include "macro.h" +#include "signal-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "strv.h" + +static char** arg_listen = NULL; +static bool arg_accept = false; +static int arg_socket_type = SOCK_STREAM; +static char** arg_args = NULL; +static char** arg_setenv = NULL; +static char **arg_fdnames = NULL; +static bool arg_inetd = false; + +static int add_epoll(int epoll_fd, int fd) { + struct epoll_event ev = { + .events = EPOLLIN + }; + int r; + + assert(epoll_fd >= 0); + assert(fd >= 0); + + ev.data.fd = fd; + r = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev); + if (r < 0) + return log_error_errno(errno, "Failed to add event on epoll fd:%d for fd:%d: %m", epoll_fd, fd); + + return 0; +} + +static int open_sockets(int *epoll_fd, bool accept) { + char **address; + int n, fd, r; + int count = 0; + + n = sd_listen_fds(true); + if (n < 0) + return log_error_errno(n, "Failed to read listening file descriptors from environment: %m"); + if (n > 0) { + log_info("Received %i descriptors via the environment.", n); + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { + r = fd_cloexec(fd, arg_accept); + if (r < 0) + return r; + + count++; + } + } + + /* Close logging and all other descriptors */ + if (arg_listen) { + int except[3 + n]; + + for (fd = 0; fd < SD_LISTEN_FDS_START + n; fd++) + except[fd] = fd; + + log_close(); + close_all_fds(except, 3 + n); + } + + /** Note: we leak some fd's on error here. I doesn't matter + * much, since the program will exit immediately anyway, but + * would be a pain to fix. + */ + + STRV_FOREACH(address, arg_listen) { + fd = make_socket_fd(LOG_DEBUG, *address, arg_socket_type, (arg_accept*SOCK_CLOEXEC)); + if (fd < 0) { + log_open(); + return log_error_errno(fd, "Failed to open '%s': %m", *address); + } + + assert(fd == SD_LISTEN_FDS_START + count); + count++; + } + + if (arg_listen) + log_open(); + + *epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (*epoll_fd < 0) + return log_error_errno(errno, "Failed to create epoll object: %m"); + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + count; fd++) { + _cleanup_free_ char *name = NULL; + + getsockname_pretty(fd, &name); + log_info("Listening on %s as %i.", strna(name), fd); + + r = add_epoll(*epoll_fd, fd); + if (r < 0) + return r; + } + + return count; +} + +static int exec_process(const char* name, char **argv, char **env, int start_fd, int n_fds) { + + _cleanup_strv_free_ char **envp = NULL; + _cleanup_free_ char *joined = NULL; + unsigned n_env = 0, length; + const char *tocopy; + char **s; + int r; + + if (arg_inetd && n_fds != 1) { + log_error("--inetd only supported for single file descriptors."); + return -EINVAL; + } + + length = strv_length(arg_setenv); + + /* PATH, TERM, HOME, USER, LISTEN_FDS, LISTEN_PID, LISTEN_FDNAMES, NULL */ + envp = new0(char *, length + 8); + if (!envp) + return log_oom(); + + STRV_FOREACH(s, arg_setenv) { + + if (strchr(*s, '=')) { + char *k; + + k = strdup(*s); + if (!k) + return log_oom(); + + envp[n_env++] = k; + } else { + _cleanup_free_ char *p; + const char *n; + + p = strappend(*s, "="); + if (!p) + return log_oom(); + + n = strv_find_prefix(env, p); + if (!n) + continue; + + envp[n_env] = strdup(n); + if (!envp[n_env]) + return log_oom(); + + n_env++; + } + } + + FOREACH_STRING(tocopy, "TERM=", "PATH=", "USER=", "HOME=") { + const char *n; + + n = strv_find_prefix(env, tocopy); + if (!n) + continue; + + envp[n_env] = strdup(n); + if (!envp[n_env]) + return log_oom(); + + n_env++; + } + + if (arg_inetd) { + assert(n_fds == 1); + + r = dup2(start_fd, STDIN_FILENO); + if (r < 0) + return log_error_errno(errno, "Failed to dup connection to stdin: %m"); + + r = dup2(start_fd, STDOUT_FILENO); + if (r < 0) + return log_error_errno(errno, "Failed to dup connection to stdout: %m"); + + start_fd = safe_close(start_fd); + } else { + if (start_fd != SD_LISTEN_FDS_START) { + assert(n_fds == 1); + + r = dup2(start_fd, SD_LISTEN_FDS_START); + if (r < 0) + return log_error_errno(errno, "Failed to dup connection: %m"); + + safe_close(start_fd); + start_fd = SD_LISTEN_FDS_START; + } + + if (asprintf((char**)(envp + n_env++), "LISTEN_FDS=%i", n_fds) < 0) + return log_oom(); + + if (asprintf((char**)(envp + n_env++), "LISTEN_PID=" PID_FMT, getpid()) < 0) + return log_oom(); + + if (arg_fdnames) { + _cleanup_free_ char *names = NULL; + size_t len; + char *e; + int i; + + len = strv_length(arg_fdnames); + if (len == 1) + for (i = 1; i < n_fds; i++) { + r = strv_extend(&arg_fdnames, arg_fdnames[0]); + if (r < 0) + return log_error_errno(r, "Failed to extend strv: %m"); + } + else if (len != (unsigned) n_fds) + log_warning("The number of fd names is different than number of fds: %zu vs %d", + len, n_fds); + + names = strv_join(arg_fdnames, ":"); + if (!names) + return log_oom(); + + e = strappend("LISTEN_FDNAMES=", names); + if (!e) + return log_oom(); + + envp[n_env++] = e; + } + } + + joined = strv_join(argv, " "); + if (!joined) + return log_oom(); + + log_info("Execing %s (%s)", name, joined); + execvpe(name, argv, envp); + + return log_error_errno(errno, "Failed to execp %s (%s): %m", name, joined); +} + +static int fork_and_exec_process(const char* child, char** argv, char **env, int fd) { + _cleanup_free_ char *joined = NULL; + pid_t parent_pid, child_pid; + + joined = strv_join(argv, " "); + if (!joined) + return log_oom(); + + parent_pid = getpid(); + + child_pid = fork(); + if (child_pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + + /* In the child */ + if (child_pid == 0) { + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + /* Make sure the child goes away when the parent dies */ + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + _exit(EXIT_FAILURE); + + /* Check whether our parent died before we were able + * to set the death signal */ + if (getppid() != parent_pid) + _exit(EXIT_SUCCESS); + + exec_process(child, argv, env, fd, 1); + _exit(EXIT_FAILURE); + } + + log_info("Spawned %s (%s) as PID %d", child, joined, child_pid); + return 0; +} + +static int do_accept(const char* name, char **argv, char **envp, int fd) { + _cleanup_free_ char *local = NULL, *peer = NULL; + _cleanup_close_ int fd_accepted = -1; + + fd_accepted = accept4(fd, NULL, NULL, 0); + if (fd_accepted < 0) + return log_error_errno(errno, "Failed to accept connection on fd:%d: %m", fd); + + getsockname_pretty(fd_accepted, &local); + getpeername_pretty(fd_accepted, true, &peer); + log_info("Connection from %s to %s", strna(peer), strna(local)); + + return fork_and_exec_process(name, argv, envp, fd_accepted); +} + +/* SIGCHLD handler. */ +static void sigchld_hdl(int sig) { + PROTECT_ERRNO; + + for (;;) { + siginfo_t si; + int r; + + si.si_pid = 0; + r = waitid(P_ALL, 0, &si, WEXITED|WNOHANG); + if (r < 0) { + if (errno != ECHILD) + log_error_errno(errno, "Failed to reap children: %m"); + return; + } + if (si.si_pid == 0) + return; + + log_info("Child %d died with code %d", si.si_pid, si.si_status); + } +} + +static int install_chld_handler(void) { + static const struct sigaction act = { + .sa_flags = SA_NOCLDSTOP, + .sa_handler = sigchld_hdl, + }; + + int r; + + r = sigaction(SIGCHLD, &act, 0); + if (r < 0) + return log_error_errno(errno, "Failed to install SIGCHLD handler: %m"); + + return 0; +} + +static void help(void) { + printf("%s [OPTIONS...]\n\n" + "Listen on sockets and launch child on connection.\n\n" + "Options:\n" + " -h --help Show this help and exit\n" + " --version Print version string and exit\n" + " -l --listen=ADDR Listen for raw connections at ADDR\n" + " -d --datagram Listen on datagram instead of stream socket\n" + " --seqpacket Listen on SOCK_SEQPACKET instead of stream socket\n" + " -a --accept Spawn separate child for each connection\n" + " -E --setenv=NAME[=VALUE] Pass an environment variable to children\n" + " --fdname=NAME[:NAME...] Specify names for file descriptors\n" + " --inetd Enable inetd file descriptor passing protocol\n" + "\n" + "Note: file descriptors from sd_listen_fds() will be passed through.\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_FDNAME, + ARG_SEQPACKET, + ARG_INETD, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "datagram", no_argument, NULL, 'd' }, + { "seqpacket", no_argument, NULL, ARG_SEQPACKET }, + { "listen", required_argument, NULL, 'l' }, + { "accept", no_argument, NULL, 'a' }, + { "setenv", required_argument, NULL, 'E' }, + { "environment", required_argument, NULL, 'E' }, /* legacy alias */ + { "fdname", required_argument, NULL, ARG_FDNAME }, + { "inetd", no_argument, NULL, ARG_INETD }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "+hl:aE:d", options, NULL)) >= 0) + switch(c) { + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case 'l': + r = strv_extend(&arg_listen, optarg); + if (r < 0) + return log_oom(); + + break; + + case 'd': + if (arg_socket_type == SOCK_SEQPACKET) { + log_error("--datagram may not be combined with --seqpacket."); + return -EINVAL; + } + + arg_socket_type = SOCK_DGRAM; + break; + + case ARG_SEQPACKET: + if (arg_socket_type == SOCK_DGRAM) { + log_error("--seqpacket may not be combined with --datagram."); + return -EINVAL; + } + + arg_socket_type = SOCK_SEQPACKET; + break; + + case 'a': + arg_accept = true; + break; + + case 'E': + r = strv_extend(&arg_setenv, optarg); + if (r < 0) + return log_oom(); + + break; + + case ARG_FDNAME: { + _cleanup_strv_free_ char **names; + char **s; + + names = strv_split(optarg, ":"); + if (!names) + return log_oom(); + + STRV_FOREACH(s, names) + if (!fdname_is_valid(*s)) { + _cleanup_free_ char *esc; + + esc = cescape(*s); + log_warning("File descriptor name \"%s\" is not valid.", esc); + } + + /* Empty optargs means one empty name */ + r = strv_extend_strv(&arg_fdnames, + strv_isempty(names) ? STRV_MAKE("") : names, + false); + if (r < 0) + return log_error_errno(r, "strv_extend_strv: %m"); + break; + } + + case ARG_INETD: + arg_inetd = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (optind == argc) { + log_error("%s: command to execute is missing.", + program_invocation_short_name); + return -EINVAL; + } + + if (arg_socket_type == SOCK_DGRAM && arg_accept) { + log_error("Datagram sockets do not accept connections. " + "The --datagram and --accept options may not be combined."); + return -EINVAL; + } + + arg_args = argv + optind; + + return 1 /* work to do */; +} + +int main(int argc, char **argv, char **envp) { + int r, n; + int epoll_fd = -1; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; + + r = install_chld_handler(); + if (r < 0) + return EXIT_FAILURE; + + n = open_sockets(&epoll_fd, arg_accept); + if (n < 0) + return EXIT_FAILURE; + if (n == 0) { + log_error("No sockets to listen on specified or passed in."); + return EXIT_FAILURE; + } + + for (;;) { + struct epoll_event event; + + r = epoll_wait(epoll_fd, &event, 1, -1); + if (r < 0) { + if (errno == EINTR) + continue; + + log_error_errno(errno, "epoll_wait() failed: %m"); + return EXIT_FAILURE; + } + + log_info("Communication attempt on fd %i.", event.data.fd); + if (arg_accept) { + r = do_accept(argv[optind], argv + optind, envp, event.data.fd); + if (r < 0) + return EXIT_FAILURE; + } else + break; + } + + exec_process(argv[optind], argv + optind, envp, SD_LISTEN_FDS_START, n); + + return EXIT_SUCCESS; +} diff --git a/src/systemd-analyze/.gitignore b/src/systemd-analyze/.gitignore new file mode 100644 index 0000000000..752ea236c8 --- /dev/null +++ b/src/systemd-analyze/.gitignore @@ -0,0 +1 @@ +/systemd-analyze diff --git a/src/systemd-analyze/Makefile b/src/systemd-analyze/Makefile new file mode 100644 index 0000000000..c8a3d805fd --- /dev/null +++ b/src/systemd-analyze/Makefile @@ -0,0 +1,39 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_analyze_SOURCES = \ + src/analyze/analyze.c \ + src/analyze/analyze-verify.c \ + src/analyze/analyze-verify.h + +systemd_analyze_CFLAGS = \ + $(AM_CFLAGS) \ + $(SECCOMP_CFLAGS) \ + $(MOUNT_CFLAGS) + +systemd_analyze_LDADD = \ + libcore.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-analyze/analyze-verify.c b/src/systemd-analyze/analyze-verify.c new file mode 100644 index 0000000000..5fd3ee49eb --- /dev/null +++ b/src/systemd-analyze/analyze-verify.c @@ -0,0 +1,304 @@ +/*** + This file is part of systemd. + + Copyright 2014 Zbigniew Jędrzejewski-Szmek + + 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 "alloc-util.h" +#include "analyze-verify.h" +#include "bus-error.h" +#include "bus-util.h" +#include "log.h" +#include "manager.h" +#include "pager.h" +#include "path-util.h" +#include "strv.h" +#include "unit-name.h" + +static int prepare_filename(const char *filename, char **ret) { + int r; + const char *name; + _cleanup_free_ char *abspath = NULL; + _cleanup_free_ char *dir = NULL; + _cleanup_free_ char *with_instance = NULL; + char *c; + + assert(filename); + assert(ret); + + r = path_make_absolute_cwd(filename, &abspath); + if (r < 0) + return r; + + name = basename(abspath); + if (!unit_name_is_valid(name, UNIT_NAME_ANY)) + return -EINVAL; + + if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { + r = unit_name_replace_instance(name, "i", &with_instance); + if (r < 0) + return r; + } + + dir = dirname_malloc(abspath); + if (!dir) + return -ENOMEM; + + if (with_instance) + c = path_join(NULL, dir, with_instance); + else + c = path_join(NULL, dir, name); + if (!c) + return -ENOMEM; + + *ret = c; + return 0; +} + +static int generate_path(char **var, char **filenames) { + char **filename; + + _cleanup_strv_free_ char **ans = NULL; + int r; + + STRV_FOREACH(filename, filenames) { + char *t; + + t = dirname_malloc(*filename); + if (!t) + return -ENOMEM; + + r = strv_consume(&ans, t); + if (r < 0) + return r; + } + + assert_se(strv_uniq(ans)); + + r = strv_extend(&ans, ""); + if (r < 0) + return r; + + *var = strv_join(ans, ":"); + if (!*var) + return -ENOMEM; + + return 0; +} + +static int verify_socket(Unit *u) { + int r; + + assert(u); + + if (u->type != UNIT_SOCKET) + return 0; + + /* Cannot run this without the service being around */ + + /* This makes sure instance is created if necessary. */ + r = socket_instantiate_service(SOCKET(u)); + if (r < 0) { + log_unit_error_errno(u, r, "Socket cannot be started, failed to create instance: %m"); + return r; + } + + /* This checks both type of sockets */ + if (UNIT_ISSET(SOCKET(u)->service)) { + Service *service; + + service = SERVICE(UNIT_DEREF(SOCKET(u)->service)); + log_unit_debug(u, "Using %s", UNIT(service)->id); + + if (UNIT(service)->load_state != UNIT_LOADED) { + log_unit_error(u, "Service %s not loaded, %s cannot be started.", UNIT(service)->id, u->id); + return -ENOENT; + } + } + + return 0; +} + +static int verify_executable(Unit *u, ExecCommand *exec) { + if (exec == NULL) + return 0; + + if (access(exec->path, X_OK) < 0) + return log_unit_error_errno(u, errno, "Command %s is not executable: %m", exec->path); + + return 0; +} + +static int verify_executables(Unit *u) { + ExecCommand *exec; + int r = 0, k; + unsigned i; + + assert(u); + + exec = u->type == UNIT_SOCKET ? SOCKET(u)->control_command : + u->type == UNIT_MOUNT ? MOUNT(u)->control_command : + u->type == UNIT_SWAP ? SWAP(u)->control_command : NULL; + k = verify_executable(u, exec); + if (k < 0 && r == 0) + r = k; + + if (u->type == UNIT_SERVICE) + for (i = 0; i < ELEMENTSOF(SERVICE(u)->exec_command); i++) { + k = verify_executable(u, SERVICE(u)->exec_command[i]); + if (k < 0 && r == 0) + r = k; + } + + if (u->type == UNIT_SOCKET) + for (i = 0; i < ELEMENTSOF(SOCKET(u)->exec_command); i++) { + k = verify_executable(u, SOCKET(u)->exec_command[i]); + if (k < 0 && r == 0) + r = k; + } + + return r; +} + +static int verify_documentation(Unit *u, bool check_man) { + char **p; + int r = 0, k; + + STRV_FOREACH(p, u->documentation) { + log_unit_debug(u, "Found documentation item: %s", *p); + + if (check_man && startswith(*p, "man:")) { + k = show_man_page(*p + 4, true); + if (k != 0) { + if (k < 0) + log_unit_error_errno(u, r, "Can't show %s: %m", *p); + else { + log_unit_error_errno(u, r, "man %s command failed with code %d", *p + 4, k); + k = -ENOEXEC; + } + if (r == 0) + r = k; + } + } + } + + /* Check remote URLs? */ + + return r; +} + +static int verify_unit(Unit *u, bool check_man) { + _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL; + int r, k; + + assert(u); + + if (log_get_max_level() >= LOG_DEBUG) + unit_dump(u, stdout, "\t"); + + log_unit_debug(u, "Creating %s/start job", u->id); + r = manager_add_job(u->manager, JOB_START, u, JOB_REPLACE, &err, NULL); + if (r < 0) + log_unit_error_errno(u, r, "Failed to create %s/start: %s", u->id, bus_error_message(&err, r)); + + k = verify_socket(u); + if (k < 0 && r == 0) + r = k; + + k = verify_executables(u); + if (k < 0 && r == 0) + r = k; + + k = verify_documentation(u, check_man); + if (k < 0 && r == 0) + r = k; + + return r; +} + +int verify_units(char **filenames, UnitFileScope scope, bool check_man) { + _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL; + _cleanup_free_ char *var = NULL; + Manager *m = NULL; + FILE *serial = NULL; + FDSet *fdset = NULL; + char **filename; + int r = 0, k; + + Unit *units[strv_length(filenames)]; + int i, count = 0; + + if (strv_isempty(filenames)) + return 0; + + /* set the path */ + r = generate_path(&var, filenames); + if (r < 0) + return log_error_errno(r, "Failed to generate unit load path: %m"); + + assert_se(set_unit_path(var) >= 0); + + r = manager_new(scope, true, &m); + if (r < 0) + return log_error_errno(r, "Failed to initialize manager: %m"); + + log_debug("Starting manager..."); + + r = manager_startup(m, serial, fdset); + if (r < 0) { + log_error_errno(r, "Failed to start manager: %m"); + goto finish; + } + + manager_clear_jobs(m); + + log_debug("Loading remaining units from the command line..."); + + STRV_FOREACH(filename, filenames) { + _cleanup_free_ char *prepared = NULL; + + log_debug("Handling %s...", *filename); + + k = prepare_filename(*filename, &prepared); + if (k < 0) { + log_error_errno(k, "Failed to prepare filename %s: %m", *filename); + if (r == 0) + r = k; + continue; + } + + k = manager_load_unit(m, NULL, prepared, &err, &units[count]); + if (k < 0) { + log_error_errno(k, "Failed to load %s: %m", *filename); + if (r == 0) + r = k; + } else + count++; + } + + for (i = 0; i < count; i++) { + k = verify_unit(units[i], check_man); + if (k < 0 && r == 0) + r = k; + } + +finish: + manager_free(m); + + return r; +} diff --git a/src/systemd-analyze/analyze-verify.h b/src/systemd-analyze/analyze-verify.h new file mode 100644 index 0000000000..d8204dc69c --- /dev/null +++ b/src/systemd-analyze/analyze-verify.h @@ -0,0 +1,26 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Zbigniew Jędrzejewski-Szmek + + 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 "path-lookup.h" + +int verify_units(char **filenames, UnitFileScope scope, bool check_man); diff --git a/src/systemd-analyze/analyze.c b/src/systemd-analyze/analyze.c new file mode 100644 index 0000000000..0e1eee16ec --- /dev/null +++ b/src/systemd-analyze/analyze.c @@ -0,0 +1,1485 @@ +/*** + This file is part of systemd. + + Copyright 2010-2013 Lennart Poettering + Copyright 2013 Simon Peeters + + 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 +#include +#include + +#include + +#include "alloc-util.h" +#include "analyze-verify.h" +#include "bus-error.h" +#include "bus-unit-util.h" +#include "bus-util.h" +#include "glob-util.h" +#include "hashmap.h" +#include "locale-util.h" +#include "log.h" +#include "pager.h" +#include "parse-util.h" +#include "special.h" +#include "strv.h" +#include "strxcpyx.h" +#include "terminal-util.h" +#include "unit-name.h" +#include "util.h" + +#define SCALE_X (0.1 / 1000.0) /* pixels per us */ +#define SCALE_Y (20.0) + +#define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0)) + +#define svg(...) printf(__VA_ARGS__) + +#define svg_bar(class, x1, x2, y) \ + svg(" \n", \ + (class), \ + SCALE_X * (x1), SCALE_Y * (y), \ + SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0) + +#define svg_text(b, x, y, format, ...) \ + do { \ + svg(" ", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \ + svg(format, ## __VA_ARGS__); \ + svg("\n"); \ + } while (false) + +static enum dot { + DEP_ALL, + DEP_ORDER, + DEP_REQUIRE +} arg_dot = DEP_ALL; +static char** arg_dot_from_patterns = NULL; +static char** arg_dot_to_patterns = NULL; +static usec_t arg_fuzz = 0; +static bool arg_no_pager = false; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static char *arg_host = NULL; +static bool arg_user = false; +static bool arg_man = true; + +struct boot_times { + usec_t firmware_time; + usec_t loader_time; + usec_t kernel_time; + usec_t kernel_done_time; + usec_t initrd_time; + usec_t userspace_time; + usec_t finish_time; + usec_t security_start_time; + usec_t security_finish_time; + usec_t generators_start_time; + usec_t generators_finish_time; + usec_t unitsload_start_time; + usec_t unitsload_finish_time; + + /* + * If we're analyzing the user instance, all timestamps will be offset + * by its own start-up timestamp, which may be arbitrarily big. + * With "plot", this causes arbitrarily wide output SVG files which almost + * completely consist of empty space. Thus we cancel out this offset. + * + * This offset is subtracted from times above by acquire_boot_times(), + * but it still needs to be subtracted from unit-specific timestamps + * (so it is stored here for reference). + */ + usec_t reverse_offset; +}; + +struct unit_times { + char *name; + usec_t activating; + usec_t activated; + usec_t deactivated; + usec_t deactivating; + usec_t time; +}; + +struct host_info { + char *hostname; + char *kernel_name; + char *kernel_release; + char *kernel_version; + char *os_pretty_name; + char *virtualization; + char *architecture; +}; + +static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *interface, const char *property, uint64_t *val) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(property); + assert(val); + + r = sd_bus_get_property_trivial( + bus, + "org.freedesktop.systemd1", + path, + interface, + property, + &error, + 't', val); + + if (r < 0) { + log_error("Failed to parse reply: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char *property, char ***strv) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(path); + assert(property); + assert(strv); + + r = sd_bus_get_property_strv( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + property, + &error, + strv); + if (r < 0) { + log_error("Failed to get unit property %s: %s", property, bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int compare_unit_time(const void *a, const void *b) { + return compare(((struct unit_times *)b)->time, + ((struct unit_times *)a)->time); +} + +static int compare_unit_start(const void *a, const void *b) { + return compare(((struct unit_times *)a)->activating, + ((struct unit_times *)b)->activating); +} + +static void free_unit_times(struct unit_times *t, unsigned n) { + struct unit_times *p; + + for (p = t; p < t + n; p++) + free(p->name); + + free(t); +} + +static void subtract_timestamp(usec_t *a, usec_t b) { + assert(a); + + if (*a > 0) { + assert(*a >= b); + *a -= b; + } +} + +static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) { + static struct boot_times times; + static bool cached = false; + + if (cached) + goto finish; + + assert_cc(sizeof(usec_t) == sizeof(uint64_t)); + + if (bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "FirmwareTimestampMonotonic", + ×.firmware_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LoaderTimestampMonotonic", + ×.loader_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "KernelTimestamp", + ×.kernel_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "InitRDTimestampMonotonic", + ×.initrd_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UserspaceTimestampMonotonic", + ×.userspace_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "FinishTimestampMonotonic", + ×.finish_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SecurityStartTimestampMonotonic", + ×.security_start_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SecurityFinishTimestampMonotonic", + ×.security_finish_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GeneratorsStartTimestampMonotonic", + ×.generators_start_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GeneratorsFinishTimestampMonotonic", + ×.generators_finish_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UnitsLoadStartTimestampMonotonic", + ×.unitsload_start_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UnitsLoadFinishTimestampMonotonic", + ×.unitsload_finish_time) < 0) + return -EIO; + + if (times.finish_time <= 0) { + log_error("Bootup is not yet finished. Please try again later."); + return -EINPROGRESS; + } + + if (arg_user) { + /* + * User-instance-specific timestamps processing + * (see comment to reverse_offset in struct boot_times). + */ + times.reverse_offset = times.userspace_time; + + times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time = times.userspace_time = 0; + subtract_timestamp(×.finish_time, times.reverse_offset); + + subtract_timestamp(×.security_start_time, times.reverse_offset); + subtract_timestamp(×.security_finish_time, times.reverse_offset); + + subtract_timestamp(×.generators_start_time, times.reverse_offset); + subtract_timestamp(×.generators_finish_time, times.reverse_offset); + + subtract_timestamp(×.unitsload_start_time, times.reverse_offset); + subtract_timestamp(×.unitsload_finish_time, times.reverse_offset); + } else { + if (times.initrd_time) + times.kernel_done_time = times.initrd_time; + else + times.kernel_done_time = times.userspace_time; + } + + cached = true; + +finish: + *bt = × + return 0; +} + +static void free_host_info(struct host_info *hi) { + + if (!hi) + return; + + free(hi->hostname); + free(hi->kernel_name); + free(hi->kernel_release); + free(hi->kernel_version); + free(hi->os_pretty_name); + free(hi->virtualization); + free(hi->architecture); + free(hi); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info*, free_host_info); + +static int acquire_time_data(sd_bus *bus, struct unit_times **out) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r, c = 0; + struct boot_times *boot_times = NULL; + struct unit_times *unit_times = NULL; + size_t size = 0; + UnitInfo u; + + r = acquire_boot_times(bus, &boot_times); + if (r < 0) + goto fail; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnits", + &error, &reply, + NULL); + if (r < 0) { + log_error("Failed to list units: %s", bus_error_message(&error, -r)); + goto fail; + } + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); + if (r < 0) { + bus_log_parse_error(r); + goto fail; + } + + while ((r = bus_parse_unit_info(reply, &u)) > 0) { + struct unit_times *t; + + if (!GREEDY_REALLOC(unit_times, size, c+1)) { + r = log_oom(); + goto fail; + } + + t = unit_times+c; + t->name = NULL; + + assert_cc(sizeof(usec_t) == sizeof(uint64_t)); + + if (bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "InactiveExitTimestampMonotonic", + &t->activating) < 0 || + bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "ActiveEnterTimestampMonotonic", + &t->activated) < 0 || + bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "ActiveExitTimestampMonotonic", + &t->deactivating) < 0 || + bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "InactiveEnterTimestampMonotonic", + &t->deactivated) < 0) { + r = -EIO; + goto fail; + } + + subtract_timestamp(&t->activating, boot_times->reverse_offset); + subtract_timestamp(&t->activated, boot_times->reverse_offset); + subtract_timestamp(&t->deactivating, boot_times->reverse_offset); + subtract_timestamp(&t->deactivated, boot_times->reverse_offset); + + if (t->activated >= t->activating) + t->time = t->activated - t->activating; + else if (t->deactivated >= t->activating) + t->time = t->deactivated - t->activating; + else + t->time = 0; + + if (t->activating == 0) + continue; + + t->name = strdup(u.id); + if (t->name == NULL) { + r = log_oom(); + goto fail; + } + c++; + } + if (r < 0) { + bus_log_parse_error(r); + goto fail; + } + + *out = unit_times; + return c; + +fail: + if (unit_times) + free_unit_times(unit_times, (unsigned) c); + return r; +} + +static int acquire_host_info(sd_bus *bus, struct host_info **hi) { + static const struct bus_properties_map hostname_map[] = { + { "Hostname", "s", NULL, offsetof(struct host_info, hostname) }, + { "KernelName", "s", NULL, offsetof(struct host_info, kernel_name) }, + { "KernelRelease", "s", NULL, offsetof(struct host_info, kernel_release) }, + { "KernelVersion", "s", NULL, offsetof(struct host_info, kernel_version) }, + { "OperatingSystemPrettyName", "s", NULL, offsetof(struct host_info, os_pretty_name) }, + {} + }; + + static const struct bus_properties_map manager_map[] = { + { "Virtualization", "s", NULL, offsetof(struct host_info, virtualization) }, + { "Architecture", "s", NULL, offsetof(struct host_info, architecture) }, + {} + }; + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(free_host_infop) struct host_info *host; + int r; + + host = new0(struct host_info, 1); + if (!host) + return log_oom(); + + r = bus_map_all_properties(bus, + "org.freedesktop.hostname1", + "/org/freedesktop/hostname1", + hostname_map, + host); + if (r < 0) + log_debug_errno(r, "Failed to get host information from systemd-hostnamed: %s", bus_error_message(&error, r)); + + r = bus_map_all_properties(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + manager_map, + host); + if (r < 0) + return log_error_errno(r, "Failed to get host information from systemd: %s", bus_error_message(&error, r)); + + *hi = host; + host = NULL; + + return 0; +} + +static int pretty_boot_time(sd_bus *bus, char **_buf) { + char ts[FORMAT_TIMESPAN_MAX]; + struct boot_times *t; + static char buf[4096]; + size_t size; + char *ptr; + int r; + + r = acquire_boot_times(bus, &t); + if (r < 0) + return r; + + ptr = buf; + size = sizeof(buf); + + size = strpcpyf(&ptr, size, "Startup finished in "); + if (t->firmware_time) + size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC)); + if (t->loader_time) + size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC)); + if (t->kernel_time) + size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC)); + if (t->initrd_time > 0) + size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC)); + + size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC)); + strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC)); + + ptr = strdup(buf); + if (!ptr) + return log_oom(); + + *_buf = ptr; + return 0; +} + +static void svg_graph_box(double height, double begin, double end) { + long long i; + + /* outside box, fill */ + svg("\n", + SCALE_X * (end - begin), SCALE_Y * height); + + for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) { + /* lines for each second */ + if (i % 5000000 == 0) + svg(" \n" + " %.01fs\n", + SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i); + else if (i % 1000000 == 0) + svg(" \n" + " %.01fs\n", + SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i); + else + svg(" \n", + SCALE_X * i, SCALE_X * i, SCALE_Y * height); + } +} + +static int analyze_plot(sd_bus *bus) { + _cleanup_(free_host_infop) struct host_info *host = NULL; + struct unit_times *times; + struct boot_times *boot; + int n, m = 1, y=0; + double width; + _cleanup_free_ char *pretty_times = NULL; + struct unit_times *u; + + n = acquire_boot_times(bus, &boot); + if (n < 0) + return n; + + n = pretty_boot_time(bus, &pretty_times); + if (n < 0) + return n; + + n = acquire_host_info(bus, &host); + if (n < 0) + return n; + + n = acquire_time_data(bus, ×); + if (n <= 0) + return n; + + qsort(times, n, sizeof(struct unit_times), compare_unit_start); + + width = SCALE_X * (boot->firmware_time + boot->finish_time); + if (width < 800.0) + width = 800.0; + + if (boot->firmware_time > boot->loader_time) + m++; + if (boot->loader_time) { + m++; + if (width < 1000.0) + width = 1000.0; + } + if (boot->initrd_time) + m++; + if (boot->kernel_time) + m++; + + for (u = times; u < times + n; u++) { + double text_start, text_width; + + if (u->activating < boot->userspace_time || + u->activating > boot->finish_time) { + u->name = mfree(u->name); + continue; + } + + /* If the text cannot fit on the left side then + * increase the svg width so it fits on the right. + * TODO: calculate the text width more accurately */ + text_width = 8.0 * strlen(u->name); + text_start = (boot->firmware_time + u->activating) * SCALE_X; + if (text_width > text_start && text_width + text_start > width) + width = text_width + text_start; + + if (u->deactivated > u->activating && u->deactivated <= boot->finish_time + && u->activated == 0 && u->deactivating == 0) + u->activated = u->deactivating = u->deactivated; + if (u->activated < u->activating || u->activated > boot->finish_time) + u->activated = boot->finish_time; + if (u->deactivating < u->activated || u->activated > boot->finish_time) + u->deactivating = boot->finish_time; + if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time) + u->deactivated = boot->finish_time; + m++; + } + + svg("\n" + "\n"); + + svg("\n\n", + 80.0 + width, 150.0 + (m * SCALE_Y) + + 5 * SCALE_Y /* legend */); + + /* write some basic info as a comment, including some help */ + svg("\n" + "\n" + "\n" + "\n" + "\n\n" + "\n\n", VERSION); + + /* style sheet */ + svg("\n \n\n\n"); + + svg("\n"); + svg("%s", pretty_times); + svg("%s %s (%s %s %s) %s %s", + isempty(host->os_pretty_name) ? "GNU/Linux" : host->os_pretty_name, + strempty(host->hostname), + strempty(host->kernel_name), + strempty(host->kernel_release), + strempty(host->kernel_version), + strempty(host->architecture), + strempty(host->virtualization)); + + svg("\n", 20.0 + (SCALE_X * boot->firmware_time)); + svg_graph_box(m, -(double) boot->firmware_time, boot->finish_time); + + if (boot->firmware_time) { + svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y); + svg_text(true, -(double) boot->firmware_time, y, "firmware"); + y++; + } + if (boot->loader_time) { + svg_bar("loader", -(double) boot->loader_time, 0, y); + svg_text(true, -(double) boot->loader_time, y, "loader"); + y++; + } + if (boot->kernel_time) { + svg_bar("kernel", 0, boot->kernel_done_time, y); + svg_text(true, 0, y, "kernel"); + y++; + } + if (boot->initrd_time) { + svg_bar("initrd", boot->initrd_time, boot->userspace_time, y); + svg_text(true, boot->initrd_time, y, "initrd"); + y++; + } + svg_bar("active", boot->userspace_time, boot->finish_time, y); + svg_bar("security", boot->security_start_time, boot->security_finish_time, y); + svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y); + svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y); + svg_text(true, boot->userspace_time, y, "systemd"); + y++; + + for (u = times; u < times + n; u++) { + char ts[FORMAT_TIMESPAN_MAX]; + bool b; + + if (!u->name) + continue; + + svg_bar("activating", u->activating, u->activated, y); + svg_bar("active", u->activated, u->deactivating, y); + svg_bar("deactivating", u->deactivating, u->deactivated, y); + + /* place the text on the left if we have passed the half of the svg width */ + b = u->activating * SCALE_X < width / 2; + if (u->time) + svg_text(b, u->activating, y, "%s (%s)", + u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC)); + else + svg_text(b, u->activating, y, "%s", u->name); + y++; + } + + svg("\n"); + + /* Legend */ + svg("\n"); + y++; + svg_bar("activating", 0, 300000, y); + svg_text(true, 400000, y, "Activating"); + y++; + svg_bar("active", 0, 300000, y); + svg_text(true, 400000, y, "Active"); + y++; + svg_bar("deactivating", 0, 300000, y); + svg_text(true, 400000, y, "Deactivating"); + y++; + svg_bar("security", 0, 300000, y); + svg_text(true, 400000, y, "Setting up security module"); + y++; + svg_bar("generators", 0, 300000, y); + svg_text(true, 400000, y, "Generators"); + y++; + svg_bar("unitsload", 0, 300000, y); + svg_text(true, 400000, y, "Loading unit files"); + y++; + + svg("\n\n"); + + svg("\n"); + + free_unit_times(times, (unsigned) n); + + n = 0; + return n; +} + +static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches, + bool last, struct unit_times *times, struct boot_times *boot) { + unsigned int i; + char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX]; + + for (i = level; i != 0; i--) + printf("%s", special_glyph(branches & (1 << (i-1)) ? TREE_VERTICAL : TREE_SPACE)); + + printf("%s", special_glyph(last ? TREE_RIGHT : TREE_BRANCH)); + + if (times) { + if (times->time) + printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED, name, + format_timespan(ts, sizeof(ts), times->activating - boot->userspace_time, USEC_PER_MSEC), + format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ANSI_NORMAL); + else if (times->activated > boot->userspace_time) + printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC)); + else + printf("%s", name); + } else + printf("%s", name); + printf("\n"); + + return 0; +} + +static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) { + _cleanup_free_ char *path = NULL; + + assert(bus); + assert(name); + assert(deps); + + path = unit_dbus_path_from_name(name); + if (path == NULL) + return -ENOMEM; + + return bus_get_unit_property_strv(bus, path, "After", deps); +} + +static Hashmap *unit_times_hashmap; + +static int list_dependencies_compare(const void *_a, const void *_b) { + const char **a = (const char**) _a, **b = (const char**) _b; + usec_t usa = 0, usb = 0; + struct unit_times *times; + + times = hashmap_get(unit_times_hashmap, *a); + if (times) + usa = times->activated; + times = hashmap_get(unit_times_hashmap, *b); + if (times) + usb = times->activated; + + return usb - usa; +} + +static int list_dependencies_one(sd_bus *bus, const char *name, unsigned int level, char ***units, + unsigned int branches) { + _cleanup_strv_free_ char **deps = NULL; + char **c; + int r = 0; + usec_t service_longest = 0; + int to_print = 0; + struct unit_times *times; + struct boot_times *boot; + + if (strv_extend(units, name)) + return log_oom(); + + r = list_dependencies_get_dependencies(bus, name, &deps); + if (r < 0) + return r; + + qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare); + + r = acquire_boot_times(bus, &boot); + if (r < 0) + return r; + + STRV_FOREACH(c, deps) { + times = hashmap_get(unit_times_hashmap, *c); + if (times + && times->activated + && times->activated <= boot->finish_time + && (times->activated >= service_longest + || service_longest == 0)) { + service_longest = times->activated; + break; + } + } + + if (service_longest == 0 ) + return r; + + STRV_FOREACH(c, deps) { + times = hashmap_get(unit_times_hashmap, *c); + if (times && times->activated && times->activated <= boot->finish_time && (service_longest - times->activated) <= arg_fuzz) + to_print++; + } + + if (!to_print) + return r; + + STRV_FOREACH(c, deps) { + times = hashmap_get(unit_times_hashmap, *c); + if (!times + || !times->activated + || times->activated > boot->finish_time + || service_longest - times->activated > arg_fuzz) + continue; + + to_print--; + + r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot); + if (r < 0) + return r; + + if (strv_contains(*units, *c)) { + r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0), + true, NULL, boot); + if (r < 0) + return r; + continue; + } + + r = list_dependencies_one(bus, *c, level + 1, units, + (branches << 1) | (to_print ? 1 : 0)); + if (r < 0) + return r; + + if (!to_print) + break; + } + return 0; +} + +static int list_dependencies(sd_bus *bus, const char *name) { + _cleanup_strv_free_ char **units = NULL; + char ts[FORMAT_TIMESPAN_MAX]; + struct unit_times *times; + int r; + const char *id; + _cleanup_free_ char *path = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + struct boot_times *boot; + + assert(bus); + + path = unit_dbus_path_from_name(name); + if (path == NULL) + return -ENOMEM; + + r = sd_bus_get_property( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "Id", + &error, + &reply, + "s"); + if (r < 0) { + log_error("Failed to get ID: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "s", &id); + if (r < 0) + return bus_log_parse_error(r); + + times = hashmap_get(unit_times_hashmap, id); + + r = acquire_boot_times(bus, &boot); + if (r < 0) + return r; + + if (times) { + if (times->time) + printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED, id, + format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ANSI_NORMAL); + else if (times->activated > boot->userspace_time) + printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC)); + else + printf("%s\n", id); + } + + return list_dependencies_one(bus, name, 0, &units, 0); +} + +static int analyze_critical_chain(sd_bus *bus, char *names[]) { + struct unit_times *times; + unsigned int i; + Hashmap *h; + int n, r; + + n = acquire_time_data(bus, ×); + if (n <= 0) + return n; + + h = hashmap_new(&string_hash_ops); + if (!h) + return -ENOMEM; + + for (i = 0; i < (unsigned)n; i++) { + r = hashmap_put(h, times[i].name, ×[i]); + if (r < 0) + return r; + } + unit_times_hashmap = h; + + pager_open(arg_no_pager, false); + + puts("The time after the unit is active or started is printed after the \"@\" character.\n" + "The time the unit takes to start is printed after the \"+\" character.\n"); + + if (!strv_isempty(names)) { + char **name; + STRV_FOREACH(name, names) + list_dependencies(bus, *name); + } else + list_dependencies(bus, SPECIAL_DEFAULT_TARGET); + + hashmap_free(h); + free_unit_times(times, (unsigned) n); + return 0; +} + +static int analyze_blame(sd_bus *bus) { + struct unit_times *times; + unsigned i; + int n; + + n = acquire_time_data(bus, ×); + if (n <= 0) + return n; + + qsort(times, n, sizeof(struct unit_times), compare_unit_time); + + pager_open(arg_no_pager, false); + + for (i = 0; i < (unsigned) n; i++) { + char ts[FORMAT_TIMESPAN_MAX]; + + if (times[i].time > 0) + printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name); + } + + free_unit_times(times, (unsigned) n); + return 0; +} + +static int analyze_time(sd_bus *bus) { + _cleanup_free_ char *buf = NULL; + int r; + + r = pretty_boot_time(bus, &buf); + if (r < 0) + return r; + + puts(buf); + return 0; +} + +static int graph_one_property(sd_bus *bus, const UnitInfo *u, const char* prop, const char *color, char* patterns[], char* from_patterns[], char* to_patterns[]) { + _cleanup_strv_free_ char **units = NULL; + char **unit; + int r; + bool match_patterns; + + assert(u); + assert(prop); + assert(color); + + match_patterns = strv_fnmatch(patterns, u->id, 0); + + if (!strv_isempty(from_patterns) && + !match_patterns && + !strv_fnmatch(from_patterns, u->id, 0)) + return 0; + + r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units); + if (r < 0) + return r; + + STRV_FOREACH(unit, units) { + bool match_patterns2; + + match_patterns2 = strv_fnmatch(patterns, *unit, 0); + + if (!strv_isempty(to_patterns) && + !match_patterns2 && + !strv_fnmatch(to_patterns, *unit, 0)) + continue; + + if (!strv_isempty(patterns) && !match_patterns && !match_patterns2) + continue; + + printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u->id, *unit, color); + } + + return 0; +} + +static int graph_one(sd_bus *bus, const UnitInfo *u, char *patterns[], char *from_patterns[], char *to_patterns[]) { + int r; + + assert(bus); + assert(u); + + if (IN_SET(arg_dot, DEP_ORDER, DEP_ALL)) { + r = graph_one_property(bus, u, "After", "green", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + } + + if (IN_SET(arg_dot, DEP_REQUIRE, DEP_ALL)) { + r = graph_one_property(bus, u, "Requires", "black", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + r = graph_one_property(bus, u, "Requisite", "darkblue", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + r = graph_one_property(bus, u, "Wants", "grey66", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + r = graph_one_property(bus, u, "Conflicts", "red", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + } + + return 0; +} + +static int expand_patterns(sd_bus *bus, char **patterns, char ***ret) { + _cleanup_strv_free_ char **expanded_patterns = NULL; + char **pattern; + int r; + + STRV_FOREACH(pattern, patterns) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *unit = NULL, *unit_id = NULL; + + if (strv_extend(&expanded_patterns, *pattern) < 0) + return log_oom(); + + if (string_is_glob(*pattern)) + continue; + + unit = unit_dbus_path_from_name(*pattern); + if (!unit) + return log_oom(); + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + unit, + "org.freedesktop.systemd1.Unit", + "Id", + &error, + &unit_id); + if (r < 0) + return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r)); + + if (!streq(*pattern, unit_id)) { + if (strv_extend(&expanded_patterns, unit_id) < 0) + return log_oom(); + } + } + + *ret = expanded_patterns; + expanded_patterns = NULL; /* do not free */ + + return 0; +} + +static int dot(sd_bus *bus, char* patterns[]) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_strv_free_ char **expanded_patterns = NULL; + _cleanup_strv_free_ char **expanded_from_patterns = NULL; + _cleanup_strv_free_ char **expanded_to_patterns = NULL; + int r; + UnitInfo u; + + r = expand_patterns(bus, patterns, &expanded_patterns); + if (r < 0) + return r; + + r = expand_patterns(bus, arg_dot_from_patterns, &expanded_from_patterns); + if (r < 0) + return r; + + r = expand_patterns(bus, arg_dot_to_patterns, &expanded_to_patterns); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnits", + &error, + &reply, + ""); + if (r < 0) { + log_error("Failed to list units: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); + if (r < 0) + return bus_log_parse_error(r); + + printf("digraph systemd {\n"); + + while ((r = bus_parse_unit_info(reply, &u)) > 0) { + + r = graph_one(bus, &u, expanded_patterns, expanded_from_patterns, expanded_to_patterns); + if (r < 0) + return r; + } + if (r < 0) + return bus_log_parse_error(r); + + printf("}\n"); + + log_info(" Color legend: black = Requires\n" + " dark blue = Requisite\n" + " dark grey = Wants\n" + " red = Conflicts\n" + " green = After\n"); + + if (on_tty()) + log_notice("-- You probably want to process this output with graphviz' dot tool.\n" + "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n"); + + return 0; +} + +static int dump(sd_bus *bus, char **args) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *text = NULL; + int r; + + if (!strv_isempty(args)) { + log_error("Too many arguments."); + return -E2BIG; + } + + pager_open(arg_no_pager, false); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Dump", + &error, + &reply, + ""); + if (r < 0) + return log_error_errno(r, "Failed issue method call: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "s", &text); + if (r < 0) + return bus_log_parse_error(r); + + fputs(text, stdout); + return 0; +} + +static int set_log_level(sd_bus *bus, char **args) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(args); + + if (strv_length(args) != 1) { + log_error("This command expects one argument only."); + return -E2BIG; + } + + r = sd_bus_set_property( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LogLevel", + &error, + "s", + args[0]); + if (r < 0) + return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r)); + + return 0; +} + +static int set_log_target(sd_bus *bus, char **args) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(args); + + if (strv_length(args) != 1) { + log_error("This command expects one argument only."); + return -E2BIG; + } + + r = sd_bus_set_property( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LogTarget", + &error, + "s", + args[0]); + if (r < 0) + return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r)); + + return 0; +} + +static void help(void) { + + pager_open(arg_no_pager, false); + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Profile systemd, show unit dependencies, check unit files.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --system Operate on system systemd instance\n" + " --user Operate on user systemd instance\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --order Show only order in the graph\n" + " --require Show only requirement in the graph\n" + " --from-pattern=GLOB Show only origins in the graph\n" + " --to-pattern=GLOB Show only destinations in the graph\n" + " --fuzz=SECONDS Also print also services which finished SECONDS\n" + " earlier than the latest in the branch\n" + " --man[=BOOL] Do [not] check for existence of man pages\n\n" + "Commands:\n" + " time Print time spent in the kernel\n" + " blame Print list of running units ordered by time to init\n" + " critical-chain Print a tree of the time critical chain of units\n" + " plot Output SVG graphic showing service initialization\n" + " dot Output dependency graph in dot(1) format\n" + " set-log-level LEVEL Set logging threshold for manager\n" + " set-log-target TARGET Set logging target for manager\n" + " dump Output state serialization of service manager\n" + " verify FILE... Check unit files for correctness\n" + , program_invocation_short_name); + + /* When updating this list, including descriptions, apply + * changes to shell-completion/bash/systemd-analyze and + * shell-completion/zsh/_systemd-analyze too. */ +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_ORDER, + ARG_REQUIRE, + ARG_USER, + ARG_SYSTEM, + ARG_DOT_FROM_PATTERN, + ARG_DOT_TO_PATTERN, + ARG_FUZZ, + ARG_NO_PAGER, + ARG_MAN, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "order", no_argument, NULL, ARG_ORDER }, + { "require", no_argument, NULL, ARG_REQUIRE }, + { "user", no_argument, NULL, ARG_USER }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN }, + { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN }, + { "fuzz", required_argument, NULL, ARG_FUZZ }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "man", optional_argument, NULL, ARG_MAN }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + {} + }; + + int r, c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_USER: + arg_user = true; + break; + + case ARG_SYSTEM: + arg_user = false; + break; + + case ARG_ORDER: + arg_dot = DEP_ORDER; + break; + + case ARG_REQUIRE: + arg_dot = DEP_REQUIRE; + break; + + case ARG_DOT_FROM_PATTERN: + if (strv_extend(&arg_dot_from_patterns, optarg) < 0) + return log_oom(); + + break; + + case ARG_DOT_TO_PATTERN: + if (strv_extend(&arg_dot_to_patterns, optarg) < 0) + return log_oom(); + + break; + + case ARG_FUZZ: + r = parse_sec(optarg, &arg_fuzz); + if (r < 0) + return r; + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_MAN: + if (optarg) { + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --man= argument."); + return -EINVAL; + } + + arg_man = !!r; + } else + arg_man = true; + + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option code."); + } + + return 1; /* work to do */ +} + +int main(int argc, char *argv[]) { + int r; + + setlocale(LC_ALL, ""); + setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */ + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (streq_ptr(argv[optind], "verify")) + r = verify_units(argv+optind+1, + arg_user ? UNIT_FILE_USER : UNIT_FILE_SYSTEM, + arg_man); + else { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + + r = bus_connect_transport_systemd(arg_transport, arg_host, arg_user, &bus); + if (r < 0) { + log_error_errno(r, "Failed to create bus connection: %m"); + goto finish; + } + + if (!argv[optind] || streq(argv[optind], "time")) + r = analyze_time(bus); + else if (streq(argv[optind], "blame")) + r = analyze_blame(bus); + else if (streq(argv[optind], "critical-chain")) + r = analyze_critical_chain(bus, argv+optind+1); + else if (streq(argv[optind], "plot")) + r = analyze_plot(bus); + else if (streq(argv[optind], "dot")) + r = dot(bus, argv+optind+1); + else if (streq(argv[optind], "dump")) + r = dump(bus, argv+optind+1); + else if (streq(argv[optind], "set-log-level")) + r = set_log_level(bus, argv+optind+1); + else if (streq(argv[optind], "set-log-target")) + r = set_log_target(bus, argv+optind+1); + else + log_error("Unknown operation '%s'.", argv[optind]); + } + +finish: + pager_close(); + + strv_free(arg_dot_from_patterns); + strv_free(arg_dot_to_patterns); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-ask-password/Makefile b/src/systemd-ask-password/Makefile new file mode 100644 index 0000000000..11f3c8038a --- /dev/null +++ b/src/systemd-ask-password/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_ask_password_SOURCES = \ + src/ask-password/ask-password.c + +systemd_ask_password_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-ask-password/ask-password.c b/src/systemd-ask-password/ask-password.c new file mode 100644 index 0000000000..6d53dd982c --- /dev/null +++ b/src/systemd-ask-password/ask-password.c @@ -0,0 +1,188 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include + +#include "ask-password-api.h" +#include "def.h" +#include "log.h" +#include "macro.h" +#include "strv.h" + +static const char *arg_icon = NULL; +static const char *arg_id = NULL; +static const char *arg_keyname = NULL; +static char *arg_message = NULL; +static usec_t arg_timeout = DEFAULT_TIMEOUT_USEC; +static bool arg_multiple = false; +static bool arg_no_output = false; +static AskPasswordFlags arg_flags = ASK_PASSWORD_PUSH_CACHE; + +static void help(void) { + printf("%s [OPTIONS...] MESSAGE\n\n" + "Query the user for a system passphrase, via the TTY or an UI agent.\n\n" + " -h --help Show this help\n" + " --icon=NAME Icon name\n" + " --id=ID Query identifier (e.g. \"cryptsetup:/dev/sda5\")\n" + " --keyname=NAME Kernel key name for caching passwords (e.g. \"cryptsetup\")\n" + " --timeout=SEC Timeout in seconds\n" + " --echo Do not mask input (useful for usernames)\n" + " --no-tty Ask question via agent even on TTY\n" + " --accept-cached Accept cached passwords\n" + " --multiple List multiple passwords if available\n" + " --no-output Do not print password to standard output\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_ICON = 0x100, + ARG_TIMEOUT, + ARG_ECHO, + ARG_NO_TTY, + ARG_ACCEPT_CACHED, + ARG_MULTIPLE, + ARG_ID, + ARG_KEYNAME, + ARG_NO_OUTPUT, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "icon", required_argument, NULL, ARG_ICON }, + { "timeout", required_argument, NULL, ARG_TIMEOUT }, + { "echo", no_argument, NULL, ARG_ECHO }, + { "no-tty", no_argument, NULL, ARG_NO_TTY }, + { "accept-cached", no_argument, NULL, ARG_ACCEPT_CACHED }, + { "multiple", no_argument, NULL, ARG_MULTIPLE }, + { "id", required_argument, NULL, ARG_ID }, + { "keyname", required_argument, NULL, ARG_KEYNAME }, + { "no-output", no_argument, NULL, ARG_NO_OUTPUT }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_ICON: + arg_icon = optarg; + break; + + case ARG_TIMEOUT: + if (parse_sec(optarg, &arg_timeout) < 0) { + log_error("Failed to parse --timeout parameter %s", optarg); + return -EINVAL; + } + break; + + case ARG_ECHO: + arg_flags |= ASK_PASSWORD_ECHO; + break; + + case ARG_NO_TTY: + arg_flags |= ASK_PASSWORD_NO_TTY; + break; + + case ARG_ACCEPT_CACHED: + arg_flags |= ASK_PASSWORD_ACCEPT_CACHED; + break; + + case ARG_MULTIPLE: + arg_multiple = true; + break; + + case ARG_ID: + arg_id = optarg; + break; + + case ARG_KEYNAME: + arg_keyname = optarg; + break; + + case ARG_NO_OUTPUT: + arg_no_output = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (argc > optind) { + arg_message = strv_join(argv + optind, " "); + if (!arg_message) + return log_oom(); + } + + return 1; +} + +int main(int argc, char *argv[]) { + _cleanup_strv_free_erase_ char **l = NULL; + usec_t timeout; + char **p; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (arg_timeout > 0) + timeout = now(CLOCK_MONOTONIC) + arg_timeout; + else + timeout = 0; + + r = ask_password_auto(arg_message, arg_icon, arg_id, arg_keyname, timeout, arg_flags, &l); + if (r < 0) { + log_error_errno(r, "Failed to query password: %m"); + goto finish; + } + + STRV_FOREACH(p, l) { + if (!arg_no_output) + puts(*p); + + if (!arg_multiple) + break; + } + +finish: + free(arg_message); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-backlight/Makefile b/src/systemd-backlight/Makefile new file mode 100644 index 0000000000..115285ec40 --- /dev/null +++ b/src/systemd-backlight/Makefile @@ -0,0 +1,43 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_BACKLIGHT),) +libexec_PROGRAMS += \ + systemd-backlight + +nodist_systemunit_DATA += \ + units/systemd-backlight@.service + +systemd_backlight_SOURCES = \ + src/backlight/backlight.c + +systemd_backlight_LDADD = \ + libshared.la +endif # ENABLE_BACKLIGHT + +EXTRA_DIST += \ + units/systemd-backlight@.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-backlight/backlight.c b/src/systemd-backlight/backlight.c new file mode 100644 index 0000000000..45be135a23 --- /dev/null +++ b/src/systemd-backlight/backlight.c @@ -0,0 +1,434 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "libudev.h" + +#include "alloc-util.h" +#include "def.h" +#include "escape.h" +#include "fileio.h" +#include "mkdir.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "string-util.h" +#include "udev-util.h" +#include "util.h" + +static struct udev_device *find_pci_or_platform_parent(struct udev_device *device) { + struct udev_device *parent; + const char *subsystem, *sysname; + + assert(device); + + parent = udev_device_get_parent(device); + if (!parent) + return NULL; + + subsystem = udev_device_get_subsystem(parent); + if (!subsystem) + return NULL; + + sysname = udev_device_get_sysname(parent); + if (!sysname) + return NULL; + + if (streq(subsystem, "drm")) { + const char *c; + + c = startswith(sysname, "card"); + if (!c) + return NULL; + + c += strspn(c, DIGITS); + if (*c == '-') { + /* A connector DRM device, let's ignore all but LVDS and eDP! */ + + if (!startswith(c, "-LVDS-") && + !startswith(c, "-Embedded DisplayPort-")) + return NULL; + } + + } else if (streq(subsystem, "pci")) { + const char *value; + + value = udev_device_get_sysattr_value(parent, "class"); + if (value) { + unsigned long class = 0; + + if (safe_atolu(value, &class) < 0) { + log_warning("Cannot parse PCI class %s of device %s:%s.", + value, subsystem, sysname); + return NULL; + } + + /* Graphics card */ + if (class == 0x30000) + return parent; + } + + } else if (streq(subsystem, "platform")) + return parent; + + return find_pci_or_platform_parent(parent); +} + +static bool same_device(struct udev_device *a, struct udev_device *b) { + assert(a); + assert(b); + + if (!streq_ptr(udev_device_get_subsystem(a), udev_device_get_subsystem(b))) + return false; + + if (!streq_ptr(udev_device_get_sysname(a), udev_device_get_sysname(b))) + return false; + + return true; +} + +static bool validate_device(struct udev *udev, struct udev_device *device) { + _cleanup_udev_enumerate_unref_ struct udev_enumerate *enumerate = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + struct udev_device *parent; + const char *v, *subsystem; + int r; + + assert(udev); + assert(device); + + /* Verify whether we should actually care for a specific + * backlight device. For backlight devices there might be + * multiple ways to access the same control: "firmware" + * (i.e. ACPI), "platform" (i.e. via the machine's EC) and + * "raw" (via the graphics card). In general we should prefer + * "firmware" (i.e. ACPI) or "platform" access over "raw" + * access, in order not to confuse the BIOS/EC, and + * compatibility with possible low-level hotkey handling of + * screen brightness. The kernel will already make sure to + * expose only one of "firmware" and "platform" for the same + * device to userspace. However, we still need to make sure + * that we use "raw" only if no "firmware" or "platform" + * device for the same device exists. */ + + subsystem = udev_device_get_subsystem(device); + if (!streq_ptr(subsystem, "backlight")) + return true; + + v = udev_device_get_sysattr_value(device, "type"); + if (!streq_ptr(v, "raw")) + return true; + + parent = find_pci_or_platform_parent(device); + if (!parent) + return true; + + subsystem = udev_device_get_subsystem(parent); + if (!subsystem) + return true; + + enumerate = udev_enumerate_new(udev); + if (!enumerate) + return true; + + r = udev_enumerate_add_match_subsystem(enumerate, "backlight"); + if (r < 0) + return true; + + r = udev_enumerate_scan_devices(enumerate); + if (r < 0) + return true; + + first = udev_enumerate_get_list_entry(enumerate); + udev_list_entry_foreach(item, first) { + _cleanup_udev_device_unref_ struct udev_device *other; + struct udev_device *other_parent; + const char *other_subsystem; + + other = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); + if (!other) + return true; + + if (same_device(device, other)) + continue; + + v = udev_device_get_sysattr_value(other, "type"); + if (!streq_ptr(v, "platform") && !streq_ptr(v, "firmware")) + continue; + + /* OK, so there's another backlight device, and it's a + * platform or firmware device, so, let's see if we + * can verify it belongs to the same device as + * ours. */ + other_parent = find_pci_or_platform_parent(other); + if (!other_parent) + continue; + + if (same_device(parent, other_parent)) { + /* Both have the same PCI parent, that means + * we are out. */ + log_debug("Skipping backlight device %s, since device %s is on same PCI device and takes precedence.", + udev_device_get_sysname(device), + udev_device_get_sysname(other)); + return false; + } + + other_subsystem = udev_device_get_subsystem(other_parent); + if (streq_ptr(other_subsystem, "platform") && streq_ptr(subsystem, "pci")) { + /* The other is connected to the platform bus + * and we are a PCI device, that also means we + * are out. */ + log_debug("Skipping backlight device %s, since device %s is a platform device and takes precedence.", + udev_device_get_sysname(device), + udev_device_get_sysname(other)); + return false; + } + } + + return true; +} + +static unsigned get_max_brightness(struct udev_device *device) { + int r; + const char *max_brightness_str; + unsigned max_brightness; + + max_brightness_str = udev_device_get_sysattr_value(device, "max_brightness"); + if (!max_brightness_str) { + log_warning("Failed to read 'max_brightness' attribute."); + return 0; + } + + r = safe_atou(max_brightness_str, &max_brightness); + if (r < 0) { + log_warning_errno(r, "Failed to parse 'max_brightness' \"%s\": %m", max_brightness_str); + return 0; + } + + if (max_brightness <= 0) { + log_warning("Maximum brightness is 0, ignoring device."); + return 0; + } + + return max_brightness; +} + +/* Some systems turn the backlight all the way off at the lowest levels. + * clamp_brightness clamps the saved brightness to at least 1 or 5% of + * max_brightness in case of 'backlight' subsystem. This avoids preserving + * an unreadably dim screen, which would otherwise force the user to + * disable state restoration. */ +static void clamp_brightness(struct udev_device *device, char **value, unsigned max_brightness) { + int r; + unsigned brightness, new_brightness, min_brightness; + const char *subsystem; + + r = safe_atou(*value, &brightness); + if (r < 0) { + log_warning_errno(r, "Failed to parse brightness \"%s\": %m", *value); + return; + } + + subsystem = udev_device_get_subsystem(device); + if (streq_ptr(subsystem, "backlight")) + min_brightness = MAX(1U, max_brightness/20); + else + min_brightness = 0; + + new_brightness = CLAMP(brightness, min_brightness, max_brightness); + if (new_brightness != brightness) { + char *old_value = *value; + + r = asprintf(value, "%u", new_brightness); + if (r < 0) { + log_oom(); + return; + } + + log_info("Saved brightness %s %s to %s.", old_value, + new_brightness > brightness ? + "too low; increasing" : "too high; decreasing", + *value); + + free(old_value); + } +} + +int main(int argc, char *argv[]) { + _cleanup_udev_unref_ struct udev *udev = NULL; + _cleanup_udev_device_unref_ struct udev_device *device = NULL; + _cleanup_free_ char *saved = NULL, *ss = NULL, *escaped_ss = NULL, *escaped_sysname = NULL, *escaped_path_id = NULL; + const char *sysname, *path_id; + unsigned max_brightness; + int r; + + if (argc != 3) { + log_error("This program requires two arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + r = mkdir_p("/var/lib/systemd/backlight", 0755); + if (r < 0) { + log_error_errno(r, "Failed to create backlight directory /var/lib/systemd/backlight: %m"); + return EXIT_FAILURE; + } + + udev = udev_new(); + if (!udev) { + log_oom(); + return EXIT_FAILURE; + } + + sysname = strchr(argv[2], ':'); + if (!sysname) { + log_error("Requires a subsystem and sysname pair specifying a backlight device."); + return EXIT_FAILURE; + } + + ss = strndup(argv[2], sysname - argv[2]); + if (!ss) { + log_oom(); + return EXIT_FAILURE; + } + + sysname++; + + if (!streq(ss, "backlight") && !streq(ss, "leds")) { + log_error("Not a backlight or LED device: '%s:%s'", ss, sysname); + return EXIT_FAILURE; + } + + errno = 0; + device = udev_device_new_from_subsystem_sysname(udev, ss, sysname); + if (!device) { + if (errno > 0) + log_error_errno(errno, "Failed to get backlight or LED device '%s:%s': %m", ss, sysname); + else + log_oom(); + + return EXIT_FAILURE; + } + + /* If max_brightness is 0, then there is no actual backlight + * device. This happens on desktops with Asus mainboards + * that load the eeepc-wmi module. + */ + max_brightness = get_max_brightness(device); + if (max_brightness == 0) + return EXIT_SUCCESS; + + escaped_ss = cescape(ss); + if (!escaped_ss) { + log_oom(); + return EXIT_FAILURE; + } + + escaped_sysname = cescape(sysname); + if (!escaped_sysname) { + log_oom(); + return EXIT_FAILURE; + } + + path_id = udev_device_get_property_value(device, "ID_PATH"); + if (path_id) { + escaped_path_id = cescape(path_id); + if (!escaped_path_id) { + log_oom(); + return EXIT_FAILURE; + } + + saved = strjoin("/var/lib/systemd/backlight/", escaped_path_id, ":", escaped_ss, ":", escaped_sysname, NULL); + } else + saved = strjoin("/var/lib/systemd/backlight/", escaped_ss, ":", escaped_sysname, NULL); + + if (!saved) { + log_oom(); + return EXIT_FAILURE; + } + + /* If there are multiple conflicting backlight devices, then + * their probing at boot-time might happen in any order. This + * means the validity checking of the device then is not + * reliable, since it might not see other devices conflicting + * with a specific backlight. To deal with this, we will + * actively delete backlight state files at shutdown (where + * device probing should be complete), so that the validity + * check at boot time doesn't have to be reliable. */ + + if (streq(argv[1], "load")) { + _cleanup_free_ char *value = NULL; + const char *clamp; + + if (shall_restore_state() == 0) + return EXIT_SUCCESS; + + if (!validate_device(udev, device)) + return EXIT_SUCCESS; + + r = read_one_line_file(saved, &value); + if (r < 0) { + + if (r == -ENOENT) + return EXIT_SUCCESS; + + log_error_errno(r, "Failed to read %s: %m", saved); + return EXIT_FAILURE; + } + + clamp = udev_device_get_property_value(device, "ID_BACKLIGHT_CLAMP"); + if (!clamp || parse_boolean(clamp) != 0) /* default to clamping */ + clamp_brightness(device, &value, max_brightness); + + r = udev_device_set_sysattr_value(device, "brightness", value); + if (r < 0) { + log_error_errno(r, "Failed to write system 'brightness' attribute: %m"); + return EXIT_FAILURE; + } + + } else if (streq(argv[1], "save")) { + const char *value; + + if (!validate_device(udev, device)) { + unlink(saved); + return EXIT_SUCCESS; + } + + value = udev_device_get_sysattr_value(device, "brightness"); + if (!value) { + log_error("Failed to read system 'brightness' attribute"); + return EXIT_FAILURE; + } + + r = write_string_file(saved, value, WRITE_STRING_FILE_CREATE); + if (r < 0) { + log_error_errno(r, "Failed to write %s: %m", saved); + return EXIT_FAILURE; + } + + } else { + log_error("Unknown verb %s.", argv[1]); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/src/systemd-binfmt/Makefile b/src/systemd-binfmt/Makefile new file mode 100644 index 0000000000..6f520311e4 --- /dev/null +++ b/src/systemd-binfmt/Makefile @@ -0,0 +1,56 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_BINFMT),) +systemd_binfmt_SOURCES = \ + src/binfmt/binfmt.c + +systemd_binfmt_LDADD = \ + libshared.la + +libexec_PROGRAMS += \ + systemd-binfmt + +dist_systemunit_DATA += \ + units/proc-sys-fs-binfmt_misc.automount \ + units/proc-sys-fs-binfmt_misc.mount + +nodist_systemunit_DATA += \ + units/systemd-binfmt.service + +INSTALL_DIRS += \ + $(prefix)/lib/binfmt.d \ + $(sysconfdir)/binfmt.d + +SYSINIT_TARGET_WANTS += \ + systemd-binfmt.service \ + proc-sys-fs-binfmt_misc.automount + +endif # ENABLE_BINFMT + +EXTRA_DIST += \ + units/systemd-binfmt.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-binfmt/binfmt.c b/src/systemd-binfmt/binfmt.c new file mode 100644 index 0000000000..eeef04fb1c --- /dev/null +++ b/src/systemd-binfmt/binfmt.c @@ -0,0 +1,203 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "conf-files.h" +#include "def.h" +#include "fd-util.h" +#include "fileio.h" +#include "log.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +static const char conf_file_dirs[] = CONF_PATHS_NULSTR("binfmt.d"); + +static int delete_rule(const char *rule) { + _cleanup_free_ char *x = NULL, *fn = NULL; + char *e; + + assert(rule[0]); + + x = strdup(rule); + if (!x) + return log_oom(); + + e = strchrnul(x+1, x[0]); + *e = 0; + + fn = strappend("/proc/sys/fs/binfmt_misc/", x+1); + if (!fn) + return log_oom(); + + return write_string_file(fn, "-1", 0); +} + +static int apply_rule(const char *rule) { + int r; + + delete_rule(rule); + + r = write_string_file("/proc/sys/fs/binfmt_misc/register", rule, 0); + if (r < 0) + return log_error_errno(r, "Failed to add binary format: %m"); + + return 0; +} + +static int apply_file(const char *path, bool ignore_enoent) { + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(path); + + r = search_and_fopen_nulstr(path, "re", NULL, conf_file_dirs, &f); + if (r < 0) { + if (ignore_enoent && r == -ENOENT) + return 0; + + return log_error_errno(r, "Failed to open file '%s', ignoring: %m", path); + } + + log_debug("apply: %s", path); + for (;;) { + char l[LINE_MAX], *p; + int k; + + if (!fgets(l, sizeof(l), f)) { + if (feof(f)) + break; + + return log_error_errno(errno, "Failed to read file '%s', ignoring: %m", path); + } + + p = strstrip(l); + if (!*p) + continue; + if (strchr(COMMENTS "\n", *p)) + continue; + + k = apply_rule(p); + if (k < 0 && r == 0) + r = k; + } + + return r; +} + +static void help(void) { + printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" + "Registers binary formats.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r, k; + + r = parse_argv(argc, argv); + if (r <= 0) + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + r = 0; + + if (argc > optind) { + int i; + + for (i = optind; i < argc; i++) { + k = apply_file(argv[i], false); + if (k < 0 && r == 0) + r = k; + } + } else { + _cleanup_strv_free_ char **files = NULL; + char **f; + + r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs); + if (r < 0) { + log_error_errno(r, "Failed to enumerate binfmt.d files: %m"); + goto finish; + } + + /* Flush out all rules */ + write_string_file("/proc/sys/fs/binfmt_misc/status", "-1", 0); + + STRV_FOREACH(f, files) { + k = apply_file(*f, true); + if (k < 0 && r == 0) + r = k; + } + } + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-cgls/Makefile b/src/systemd-cgls/Makefile new file mode 100644 index 0000000000..72f43638b1 --- /dev/null +++ b/src/systemd-cgls/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_cgls_SOURCES = \ + src/cgls/cgls.c + +systemd_cgls_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-cgls/cgls.c b/src/systemd-cgls/cgls.c new file mode 100644 index 0000000000..ed2846ee57 --- /dev/null +++ b/src/systemd-cgls/cgls.c @@ -0,0 +1,288 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "cgroup-show.h" +#include "cgroup-util.h" +#include "fileio.h" +#include "log.h" +#include "output-mode.h" +#include "pager.h" +#include "path-util.h" +#include "unit-name.h" +#include "util.h" + +static bool arg_no_pager = false; +static bool arg_kernel_threads = false; +static bool arg_all = false; +static int arg_full = -1; +static char* arg_machine = NULL; + +static void help(void) { + printf("%s [OPTIONS...] [CGROUP...]\n\n" + "Recursively show control group contents.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " -a --all Show all groups, including empty\n" + " -l --full Do not ellipsize output\n" + " -k Include kernel threads in output\n" + " -M --machine= Show container\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_NO_PAGER = 0x100, + ARG_VERSION, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "all", no_argument, NULL, 'a' }, + { "full", no_argument, NULL, 'l' }, + { "machine", required_argument, NULL, 'M' }, + {} + }; + + int c; + + assert(argc >= 1); + assert(argv); + + while ((c = getopt_long(argc, argv, "hkalM:", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case 'a': + arg_all = true; + break; + + case 'l': + arg_full = true; + break; + + case 'k': + arg_kernel_threads = true; + break; + + case 'M': + arg_machine = optarg; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +static int get_cgroup_root(char **ret) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ char *unit = NULL, *path = NULL; + const char *m; + int r; + + if (!arg_machine) { + r = cg_get_root_path(ret); + if (r == -ENOMEDIUM) + return log_error_errno(r, "Failed to get root control group path: No cgroup filesystem mounted on /sys/fs/cgroup"); + else if (r < 0) + return log_error_errno(r, "Failed to get root control group path: %m"); + + return 0; + } + + m = strjoina("/run/systemd/machines/", arg_machine); + r = parse_env_file(m, NEWLINE, "SCOPE", &unit, NULL); + if (r < 0) + return log_error_errno(r, "Failed to load machine data: %m"); + + path = unit_dbus_path_from_name(unit); + if (!path) + return log_oom(); + + r = bus_connect_transport_systemd(BUS_TRANSPORT_LOCAL, NULL, false, &bus); + if (r < 0) + return log_error_errno(r, "Failed to create bus connection: %m"); + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + path, + unit_dbus_interface_from_name(unit), + "ControlGroup", + &error, + ret); + if (r < 0) + return log_error_errno(r, "Failed to query unit control group path: %s", bus_error_message(&error, r)); + + return 0; +} + +static void show_cg_info(const char *controller, const char *path) { + + if (cg_unified() <= 0 && controller && !streq(controller, SYSTEMD_CGROUP_CONTROLLER)) + printf("Controller %s; ", controller); + + printf("Control group %s:\n", isempty(path) ? "/" : path); + fflush(stdout); +} + +int main(int argc, char *argv[]) { + int r, output_flags; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (!arg_no_pager) { + r = pager_open(arg_no_pager, false); + if (r > 0 && arg_full < 0) + arg_full = true; + } + + output_flags = + arg_all * OUTPUT_SHOW_ALL | + (arg_full > 0) * OUTPUT_FULL_WIDTH | + arg_kernel_threads * OUTPUT_KERNEL_THREADS; + + if (optind < argc) { + _cleanup_free_ char *root = NULL; + int i; + + r = get_cgroup_root(&root); + if (r < 0) + goto finish; + + for (i = optind; i < argc; i++) { + int q; + + if (path_startswith(argv[i], "/sys/fs/cgroup")) { + + printf("Directory %s:\n", argv[i]); + fflush(stdout); + + q = show_cgroup_by_path(argv[i], NULL, 0, output_flags); + } else { + _cleanup_free_ char *c = NULL, *p = NULL, *j = NULL; + const char *controller, *path; + + r = cg_split_spec(argv[i], &c, &p); + if (r < 0) { + log_error_errno(r, "Failed to split argument %s: %m", argv[i]); + goto finish; + } + + controller = c ?: SYSTEMD_CGROUP_CONTROLLER; + if (p) { + j = strjoin(root, "/", p, NULL); + if (!j) { + r = log_oom(); + goto finish; + } + + path_kill_slashes(j); + path = j; + } else + path = root; + + show_cg_info(controller, path); + + q = show_cgroup(controller, path, NULL, 0, output_flags); + } + + if (q < 0) + r = q; + } + + } else { + bool done = false; + + if (!arg_machine) { + _cleanup_free_ char *cwd = NULL; + + cwd = get_current_dir_name(); + if (!cwd) { + r = log_error_errno(errno, "Cannot determine current working directory: %m"); + goto finish; + } + + if (path_startswith(cwd, "/sys/fs/cgroup")) { + printf("Working directory %s:\n", cwd); + fflush(stdout); + + r = show_cgroup_by_path(cwd, NULL, 0, output_flags); + done = true; + } + } + + if (!done) { + _cleanup_free_ char *root = NULL; + + r = get_cgroup_root(&root); + if (r < 0) + goto finish; + + show_cg_info(SYSTEMD_CGROUP_CONTROLLER, root); + + printf("-.slice\n"); + r = show_cgroup(SYSTEMD_CGROUP_CONTROLLER, root, NULL, 0, output_flags); + } + } + + if (r < 0) + log_error_errno(r, "Failed to list cgroup tree: %m"); + +finish: + pager_close(); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-cgroups-agent/Makefile b/src/systemd-cgroups-agent/Makefile new file mode 100644 index 0000000000..5c7d9d03ab --- /dev/null +++ b/src/systemd-cgroups-agent/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_cgroups_agent_SOURCES = \ + src/cgroups-agent/cgroups-agent.c + +systemd_cgroups_agent_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-cgroups-agent/cgroups-agent.c b/src/systemd-cgroups-agent/cgroups-agent.c new file mode 100644 index 0000000000..d7c722ac3d --- /dev/null +++ b/src/systemd-cgroups-agent/cgroups-agent.c @@ -0,0 +1,67 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 + +#include "fd-util.h" +#include "log.h" +#include "socket-util.h" + +int main(int argc, char *argv[]) { + + static const union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/cgroups-agent", + }; + + _cleanup_close_ int fd = -1; + ssize_t n; + size_t l; + + if (argc != 2) { + log_error("Incorrect number of arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); + if (fd < 0) { + log_debug_errno(errno, "Failed to allocate socket: %m"); + return EXIT_FAILURE; + } + + l = strlen(argv[1]); + + n = sendto(fd, argv[1], l, 0, &sa.sa, SOCKADDR_UN_LEN(sa.un)); + if (n < 0) { + log_debug_errno(errno, "Failed to send cgroups agent message: %m"); + return EXIT_FAILURE; + } + + if ((size_t) n != l) { + log_debug("Datagram size mismatch"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/src/systemd-cgtop/Makefile b/src/systemd-cgtop/Makefile new file mode 100644 index 0000000000..5e379895a8 --- /dev/null +++ b/src/systemd-cgtop/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_cgtop_SOURCES = \ + src/cgtop/cgtop.c + +systemd_cgtop_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-cgtop/cgtop.c b/src/systemd-cgtop/cgtop.c new file mode 100644 index 0000000000..6cbea86070 --- /dev/null +++ b/src/systemd-cgtop/cgtop.c @@ -0,0 +1,1115 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "hashmap.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "stdio-util.h" +#include "terminal-util.h" +#include "unit-name.h" +#include "util.h" + +typedef struct Group { + char *path; + + bool n_tasks_valid:1; + bool cpu_valid:1; + bool memory_valid:1; + bool io_valid:1; + + uint64_t n_tasks; + + unsigned cpu_iteration; + nsec_t cpu_usage; + nsec_t cpu_timestamp; + double cpu_fraction; + + uint64_t memory; + + unsigned io_iteration; + uint64_t io_input, io_output; + nsec_t io_timestamp; + uint64_t io_input_bps, io_output_bps; +} Group; + +static unsigned arg_depth = 3; +static unsigned arg_iterations = (unsigned) -1; +static bool arg_batch = false; +static bool arg_raw = false; +static usec_t arg_delay = 1*USEC_PER_SEC; +static char* arg_machine = NULL; +static bool arg_recursive = true; + +static enum { + COUNT_PIDS, + COUNT_USERSPACE_PROCESSES, + COUNT_ALL_PROCESSES, +} arg_count = COUNT_PIDS; + +static enum { + ORDER_PATH, + ORDER_TASKS, + ORDER_CPU, + ORDER_MEMORY, + ORDER_IO, +} arg_order = ORDER_CPU; + +static enum { + CPU_PERCENT, + CPU_TIME, +} arg_cpu_type = CPU_PERCENT; + +static void group_free(Group *g) { + assert(g); + + free(g->path); + free(g); +} + +static void group_hashmap_clear(Hashmap *h) { + Group *g; + + while ((g = hashmap_steal_first(h))) + group_free(g); +} + +static void group_hashmap_free(Hashmap *h) { + group_hashmap_clear(h); + hashmap_free(h); +} + +static const char *maybe_format_bytes(char *buf, size_t l, bool is_valid, uint64_t t) { + if (!is_valid) + return "-"; + if (arg_raw) { + snprintf(buf, l, "%jd", t); + return buf; + } + return format_bytes(buf, l, t); +} + +static int process( + const char *controller, + const char *path, + Hashmap *a, + Hashmap *b, + unsigned iteration, + Group **ret) { + + Group *g; + int r; + + assert(controller); + assert(path); + assert(a); + + g = hashmap_get(a, path); + if (!g) { + g = hashmap_get(b, path); + if (!g) { + g = new0(Group, 1); + if (!g) + return -ENOMEM; + + g->path = strdup(path); + if (!g->path) { + group_free(g); + return -ENOMEM; + } + + r = hashmap_put(a, g->path, g); + if (r < 0) { + group_free(g); + return r; + } + } else { + r = hashmap_move_one(a, b, path); + if (r < 0) + return r; + + g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false; + } + } + + if (streq(controller, SYSTEMD_CGROUP_CONTROLLER) && IN_SET(arg_count, COUNT_ALL_PROCESSES, COUNT_USERSPACE_PROCESSES)) { + _cleanup_fclose_ FILE *f = NULL; + pid_t pid; + + r = cg_enumerate_processes(controller, path, &f); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + g->n_tasks = 0; + while (cg_read_pid(f, &pid) > 0) { + + if (arg_count == COUNT_USERSPACE_PROCESSES && is_kernel_thread(pid) > 0) + continue; + + g->n_tasks++; + } + + if (g->n_tasks > 0) + g->n_tasks_valid = true; + + } else if (streq(controller, "pids") && arg_count == COUNT_PIDS) { + _cleanup_free_ char *p = NULL, *v = NULL; + + r = cg_get_path(controller, path, "pids.current", &p); + if (r < 0) + return r; + + r = read_one_line_file(p, &v); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + r = safe_atou64(v, &g->n_tasks); + if (r < 0) + return r; + + if (g->n_tasks > 0) + g->n_tasks_valid = true; + + } else if (streq(controller, "cpuacct") && cg_unified() <= 0) { + _cleanup_free_ char *p = NULL, *v = NULL; + uint64_t new_usage; + nsec_t timestamp; + + r = cg_get_path(controller, path, "cpuacct.usage", &p); + if (r < 0) + return r; + + r = read_one_line_file(p, &v); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + r = safe_atou64(v, &new_usage); + if (r < 0) + return r; + + timestamp = now_nsec(CLOCK_MONOTONIC); + + if (g->cpu_iteration == iteration - 1 && + (nsec_t) new_usage > g->cpu_usage) { + + nsec_t x, y; + + x = timestamp - g->cpu_timestamp; + if (x < 1) + x = 1; + + y = (nsec_t) new_usage - g->cpu_usage; + g->cpu_fraction = (double) y / (double) x; + g->cpu_valid = true; + } + + g->cpu_usage = (nsec_t) new_usage; + g->cpu_timestamp = timestamp; + g->cpu_iteration = iteration; + + } else if (streq(controller, "memory")) { + _cleanup_free_ char *p = NULL, *v = NULL; + + if (cg_unified() <= 0) + r = cg_get_path(controller, path, "memory.usage_in_bytes", &p); + else + r = cg_get_path(controller, path, "memory.current", &p); + if (r < 0) + return r; + + r = read_one_line_file(p, &v); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + r = safe_atou64(v, &g->memory); + if (r < 0) + return r; + + if (g->memory > 0) + g->memory_valid = true; + + } else if ((streq(controller, "io") && cg_unified() > 0) || + (streq(controller, "blkio") && cg_unified() <= 0)) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *p = NULL; + bool unified = cg_unified() > 0; + uint64_t wr = 0, rd = 0; + nsec_t timestamp; + + r = cg_get_path(controller, path, unified ? "io.stat" : "blkio.io_service_bytes", &p); + if (r < 0) + return r; + + f = fopen(p, "re"); + if (!f) { + if (errno == ENOENT) + return 0; + return -errno; + } + + for (;;) { + char line[LINE_MAX], *l; + uint64_t k, *q; + + if (!fgets(line, sizeof(line), f)) + break; + + /* Trim and skip the device */ + l = strstrip(line); + l += strcspn(l, WHITESPACE); + l += strspn(l, WHITESPACE); + + if (unified) { + while (!isempty(l)) { + if (sscanf(l, "rbytes=%" SCNu64, &k)) + rd += k; + else if (sscanf(l, "wbytes=%" SCNu64, &k)) + wr += k; + + l += strcspn(l, WHITESPACE); + l += strspn(l, WHITESPACE); + } + } else { + if (first_word(l, "Read")) { + l += 4; + q = &rd; + } else if (first_word(l, "Write")) { + l += 5; + q = ≀ + } else + continue; + + l += strspn(l, WHITESPACE); + r = safe_atou64(l, &k); + if (r < 0) + continue; + + *q += k; + } + } + + timestamp = now_nsec(CLOCK_MONOTONIC); + + if (g->io_iteration == iteration - 1) { + uint64_t x, yr, yw; + + x = (uint64_t) (timestamp - g->io_timestamp); + if (x < 1) + x = 1; + + if (rd > g->io_input) + yr = rd - g->io_input; + else + yr = 0; + + if (wr > g->io_output) + yw = wr - g->io_output; + else + yw = 0; + + if (yr > 0 || yw > 0) { + g->io_input_bps = (yr * 1000000000ULL) / x; + g->io_output_bps = (yw * 1000000000ULL) / x; + g->io_valid = true; + } + } + + g->io_input = rd; + g->io_output = wr; + g->io_timestamp = timestamp; + g->io_iteration = iteration; + } + + if (ret) + *ret = g; + + return 0; +} + +static int refresh_one( + const char *controller, + const char *path, + Hashmap *a, + Hashmap *b, + unsigned iteration, + unsigned depth, + Group **ret) { + + _cleanup_closedir_ DIR *d = NULL; + Group *ours = NULL; + int r; + + assert(controller); + assert(path); + assert(a); + + if (depth > arg_depth) + return 0; + + r = process(controller, path, a, b, iteration, &ours); + if (r < 0) + return r; + + r = cg_enumerate_subgroups(controller, path, &d); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + for (;;) { + _cleanup_free_ char *fn = NULL, *p = NULL; + Group *child = NULL; + + r = cg_read_subgroup(d, &fn); + if (r < 0) + return r; + if (r == 0) + break; + + p = strjoin(path, "/", fn, NULL); + if (!p) + return -ENOMEM; + + path_kill_slashes(p); + + r = refresh_one(controller, p, a, b, iteration, depth + 1, &child); + if (r < 0) + return r; + + if (arg_recursive && + IN_SET(arg_count, COUNT_ALL_PROCESSES, COUNT_USERSPACE_PROCESSES) && + child && + child->n_tasks_valid && + streq(controller, SYSTEMD_CGROUP_CONTROLLER)) { + + /* Recursively sum up processes */ + + if (ours->n_tasks_valid) + ours->n_tasks += child->n_tasks; + else { + ours->n_tasks = child->n_tasks; + ours->n_tasks_valid = true; + } + } + } + + if (ret) + *ret = ours; + + return 1; +} + +static int refresh(const char *root, Hashmap *a, Hashmap *b, unsigned iteration) { + int r; + + assert(a); + + r = refresh_one(SYSTEMD_CGROUP_CONTROLLER, root, a, b, iteration, 0, NULL); + if (r < 0) + return r; + r = refresh_one("cpuacct", root, a, b, iteration, 0, NULL); + if (r < 0) + return r; + r = refresh_one("memory", root, a, b, iteration, 0, NULL); + if (r < 0) + return r; + r = refresh_one("io", root, a, b, iteration, 0, NULL); + if (r < 0) + return r; + r = refresh_one("blkio", root, a, b, iteration, 0, NULL); + if (r < 0) + return r; + r = refresh_one("pids", root, a, b, iteration, 0, NULL); + if (r < 0) + return r; + + return 0; +} + +static int group_compare(const void*a, const void *b) { + const Group *x = *(Group**)a, *y = *(Group**)b; + + if (arg_order != ORDER_TASKS || arg_recursive) { + /* Let's make sure that the parent is always before + * the child. Except when ordering by tasks and + * recursive summing is off, since that is actually + * not accumulative for all children. */ + + if (path_startswith(y->path, x->path)) + return -1; + if (path_startswith(x->path, y->path)) + return 1; + } + + switch (arg_order) { + + case ORDER_PATH: + break; + + case ORDER_CPU: + if (arg_cpu_type == CPU_PERCENT) { + if (x->cpu_valid && y->cpu_valid) { + if (x->cpu_fraction > y->cpu_fraction) + return -1; + else if (x->cpu_fraction < y->cpu_fraction) + return 1; + } else if (x->cpu_valid) + return -1; + else if (y->cpu_valid) + return 1; + } else { + if (x->cpu_usage > y->cpu_usage) + return -1; + else if (x->cpu_usage < y->cpu_usage) + return 1; + } + + break; + + case ORDER_TASKS: + if (x->n_tasks_valid && y->n_tasks_valid) { + if (x->n_tasks > y->n_tasks) + return -1; + else if (x->n_tasks < y->n_tasks) + return 1; + } else if (x->n_tasks_valid) + return -1; + else if (y->n_tasks_valid) + return 1; + + break; + + case ORDER_MEMORY: + if (x->memory_valid && y->memory_valid) { + if (x->memory > y->memory) + return -1; + else if (x->memory < y->memory) + return 1; + } else if (x->memory_valid) + return -1; + else if (y->memory_valid) + return 1; + + break; + + case ORDER_IO: + if (x->io_valid && y->io_valid) { + if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps) + return -1; + else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps) + return 1; + } else if (x->io_valid) + return -1; + else if (y->io_valid) + return 1; + } + + return path_compare(x->path, y->path); +} + +static void display(Hashmap *a) { + Iterator i; + Group *g; + Group **array; + signed path_columns; + unsigned rows, n = 0, j, maxtcpu = 0, maxtpath = 3; /* 3 for ellipsize() to work properly */ + char buffer[MAX3(21, FORMAT_BYTES_MAX, FORMAT_TIMESPAN_MAX)]; + + assert(a); + + if (on_tty()) + fputs(ANSI_HOME_CLEAR, stdout); + + array = alloca(sizeof(Group*) * hashmap_size(a)); + + HASHMAP_FOREACH(g, a, i) + if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid) + array[n++] = g; + + qsort_safe(array, n, sizeof(Group*), group_compare); + + /* Find the longest names in one run */ + for (j = 0; j < n; j++) { + unsigned cputlen, pathtlen; + + format_timespan(buffer, sizeof(buffer), (usec_t) (array[j]->cpu_usage / NSEC_PER_USEC), 0); + cputlen = strlen(buffer); + maxtcpu = MAX(maxtcpu, cputlen); + + pathtlen = strlen(array[j]->path); + maxtpath = MAX(maxtpath, pathtlen); + } + + if (arg_cpu_type == CPU_PERCENT) + xsprintf(buffer, "%6s", "%CPU"); + else + xsprintf(buffer, "%*s", maxtcpu, "CPU Time"); + + rows = lines(); + if (rows <= 10) + rows = 10; + + if (on_tty()) { + const char *on, *off; + + path_columns = columns() - 36 - strlen(buffer); + if (path_columns < 10) + path_columns = 10; + + on = ansi_highlight_underline(); + off = ansi_underline(); + + printf("%s%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s%s\n", + ansi_underline(), + arg_order == ORDER_PATH ? on : "", path_columns, "Control Group", + arg_order == ORDER_PATH ? off : "", + arg_order == ORDER_TASKS ? on : "", arg_count == COUNT_PIDS ? "Tasks" : arg_count == COUNT_USERSPACE_PROCESSES ? "Procs" : "Proc+", + arg_order == ORDER_TASKS ? off : "", + arg_order == ORDER_CPU ? on : "", buffer, + arg_order == ORDER_CPU ? off : "", + arg_order == ORDER_MEMORY ? on : "", "Memory", + arg_order == ORDER_MEMORY ? off : "", + arg_order == ORDER_IO ? on : "", "Input/s", + arg_order == ORDER_IO ? off : "", + arg_order == ORDER_IO ? on : "", "Output/s", + arg_order == ORDER_IO ? off : "", + ansi_normal()); + } else + path_columns = maxtpath; + + for (j = 0; j < n; j++) { + _cleanup_free_ char *ellipsized = NULL; + const char *path; + + if (on_tty() && j + 6 > rows) + break; + + g = array[j]; + + path = isempty(g->path) ? "/" : g->path; + ellipsized = ellipsize(path, path_columns, 33); + printf("%-*s", path_columns, ellipsized ?: path); + + if (g->n_tasks_valid) + printf(" %7" PRIu64, g->n_tasks); + else + fputs(" -", stdout); + + if (arg_cpu_type == CPU_PERCENT) { + if (g->cpu_valid) + printf(" %6.1f", g->cpu_fraction*100); + else + fputs(" -", stdout); + } else + printf(" %*s", maxtcpu, format_timespan(buffer, sizeof(buffer), (usec_t) (g->cpu_usage / NSEC_PER_USEC), 0)); + + printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->memory_valid, g->memory)); + printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_input_bps)); + printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_output_bps)); + + putchar('\n'); + } +} + +static void help(void) { + printf("%s [OPTIONS...]\n\n" + "Show top control groups by their resource usage.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -p --order=path Order by path\n" + " -t --order=tasks Order by number of tasks/processes\n" + " -c --order=cpu Order by CPU load (default)\n" + " -m --order=memory Order by memory load\n" + " -i --order=io Order by IO load\n" + " -r --raw Provide raw (not human-readable) numbers\n" + " --cpu=percentage Show CPU usage as percentage (default)\n" + " --cpu=time Show CPU usage as time\n" + " -P Count userspace processes instead of tasks (excl. kernel)\n" + " -k Count all processes instead of tasks (incl. kernel)\n" + " --recursive=BOOL Sum up process count recursively\n" + " -d --delay=DELAY Delay between updates\n" + " -n --iterations=N Run for N iterations before exiting\n" + " -b --batch Run in batch mode, accepting no input\n" + " --depth=DEPTH Maximum traversal depth (default: %u)\n" + " -M --machine= Show container\n" + , program_invocation_short_name, arg_depth); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_DEPTH, + ARG_CPU_TYPE, + ARG_ORDER, + ARG_RECURSIVE, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "delay", required_argument, NULL, 'd' }, + { "iterations", required_argument, NULL, 'n' }, + { "batch", no_argument, NULL, 'b' }, + { "raw", no_argument, NULL, 'r' }, + { "depth", required_argument, NULL, ARG_DEPTH }, + { "cpu", optional_argument, NULL, ARG_CPU_TYPE }, + { "order", required_argument, NULL, ARG_ORDER }, + { "recursive", required_argument, NULL, ARG_RECURSIVE }, + { "machine", required_argument, NULL, 'M' }, + {} + }; + + bool recursive_unset = false; + int c, r; + + assert(argc >= 1); + assert(argv); + + while ((c = getopt_long(argc, argv, "hptcmin:brd:kPM:", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_CPU_TYPE: + if (optarg) { + if (streq(optarg, "time")) + arg_cpu_type = CPU_TIME; + else if (streq(optarg, "percentage")) + arg_cpu_type = CPU_PERCENT; + else { + log_error("Unknown argument to --cpu=: %s", optarg); + return -EINVAL; + } + } else + arg_cpu_type = CPU_TIME; + + break; + + case ARG_DEPTH: + r = safe_atou(optarg, &arg_depth); + if (r < 0) { + log_error("Failed to parse depth parameter."); + return -EINVAL; + } + + break; + + case 'd': + r = parse_sec(optarg, &arg_delay); + if (r < 0 || arg_delay <= 0) { + log_error("Failed to parse delay parameter."); + return -EINVAL; + } + + break; + + case 'n': + r = safe_atou(optarg, &arg_iterations); + if (r < 0) { + log_error("Failed to parse iterations parameter."); + return -EINVAL; + } + + break; + + case 'b': + arg_batch = true; + break; + + case 'r': + arg_raw = true; + break; + + case 'p': + arg_order = ORDER_PATH; + break; + + case 't': + arg_order = ORDER_TASKS; + break; + + case 'c': + arg_order = ORDER_CPU; + break; + + case 'm': + arg_order = ORDER_MEMORY; + break; + + case 'i': + arg_order = ORDER_IO; + break; + + case ARG_ORDER: + if (streq(optarg, "path")) + arg_order = ORDER_PATH; + else if (streq(optarg, "tasks")) + arg_order = ORDER_TASKS; + else if (streq(optarg, "cpu")) + arg_order = ORDER_CPU; + else if (streq(optarg, "memory")) + arg_order = ORDER_MEMORY; + else if (streq(optarg, "io")) + arg_order = ORDER_IO; + else { + log_error("Invalid argument to --order=: %s", optarg); + return -EINVAL; + } + break; + + case 'k': + arg_count = COUNT_ALL_PROCESSES; + break; + + case 'P': + arg_count = COUNT_USERSPACE_PROCESSES; + break; + + case ARG_RECURSIVE: + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --recursive= argument: %s", optarg); + return r; + } + + arg_recursive = r; + recursive_unset = r == 0; + break; + + case 'M': + arg_machine = optarg; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (optind < argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + if (recursive_unset && arg_count == COUNT_PIDS) { + log_error("Non-recursive counting is only supported when counting processes, not tasks. Use -P or -k."); + return -EINVAL; + } + + return 1; +} + +static const char* counting_what(void) { + if (arg_count == COUNT_PIDS) + return "tasks"; + else if (arg_count == COUNT_ALL_PROCESSES) + return "all processes (incl. kernel)"; + else + return "userspace processes (excl. kernel)"; +} + +static int get_cgroup_root(char **ret) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ char *unit = NULL, *path = NULL; + const char *m; + int r; + + if (!arg_machine) { + r = cg_get_root_path(ret); + if (r < 0) + return log_error_errno(r, "Failed to get root control group path: %m"); + + return 0; + } + + m = strjoina("/run/systemd/machines/", arg_machine); + r = parse_env_file(m, NEWLINE, "SCOPE", &unit, NULL); + if (r < 0) + return log_error_errno(r, "Failed to load machine data: %m"); + + path = unit_dbus_path_from_name(unit); + if (!path) + return log_oom(); + + r = bus_connect_transport_systemd(BUS_TRANSPORT_LOCAL, NULL, false, &bus); + if (r < 0) + return log_error_errno(r, "Failed to create bus connection: %m"); + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + path, + unit_dbus_interface_from_name(unit), + "ControlGroup", + &error, + ret); + if (r < 0) + return log_error_errno(r, "Failed to query unit control group path: %s", bus_error_message(&error, r)); + + return 0; +} + +int main(int argc, char *argv[]) { + int r; + Hashmap *a = NULL, *b = NULL; + unsigned iteration = 0; + usec_t last_refresh = 0; + bool quit = false, immediate_refresh = false; + _cleanup_free_ char *root = NULL; + CGroupMask mask; + + log_parse_environment(); + log_open(); + + r = cg_mask_supported(&mask); + if (r < 0) { + log_error_errno(r, "Failed to determine supported controllers: %m"); + goto finish; + } + + arg_count = (mask & CGROUP_MASK_PIDS) ? COUNT_PIDS : COUNT_USERSPACE_PROCESSES; + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = get_cgroup_root(&root); + if (r < 0) { + log_error_errno(r, "Failed to get root control group path: %m"); + goto finish; + } + + a = hashmap_new(&string_hash_ops); + b = hashmap_new(&string_hash_ops); + if (!a || !b) { + r = log_oom(); + goto finish; + } + + signal(SIGWINCH, columns_lines_cache_reset); + + if (arg_iterations == (unsigned) -1) + arg_iterations = on_tty() ? 0 : 1; + + while (!quit) { + Hashmap *c; + usec_t t; + char key; + char h[FORMAT_TIMESPAN_MAX]; + + t = now(CLOCK_MONOTONIC); + + if (t >= last_refresh + arg_delay || immediate_refresh) { + + r = refresh(root, a, b, iteration++); + if (r < 0) { + log_error_errno(r, "Failed to refresh: %m"); + goto finish; + } + + group_hashmap_clear(b); + + c = a; + a = b; + b = c; + + last_refresh = t; + immediate_refresh = false; + } + + display(b); + + if (arg_iterations && iteration >= arg_iterations) + break; + + if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */ + fputs("\n", stdout); + fflush(stdout); + + if (arg_batch) + (void) usleep(last_refresh + arg_delay - t); + else { + r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL); + if (r == -ETIMEDOUT) + continue; + if (r < 0) { + log_error_errno(r, "Couldn't read key: %m"); + goto finish; + } + } + + if (on_tty()) { /* TTY: Clear any user keystroke */ + fputs("\r \r", stdout); + fflush(stdout); + } + + if (arg_batch) + continue; + + switch (key) { + + case ' ': + immediate_refresh = true; + break; + + case 'q': + quit = true; + break; + + case 'p': + arg_order = ORDER_PATH; + break; + + case 't': + arg_order = ORDER_TASKS; + break; + + case 'c': + arg_order = ORDER_CPU; + break; + + case 'm': + arg_order = ORDER_MEMORY; + break; + + case 'i': + arg_order = ORDER_IO; + break; + + case '%': + arg_cpu_type = arg_cpu_type == CPU_TIME ? CPU_PERCENT : CPU_TIME; + break; + + case 'k': + arg_count = arg_count != COUNT_ALL_PROCESSES ? COUNT_ALL_PROCESSES : COUNT_PIDS; + fprintf(stdout, "\nCounting: %s.", counting_what()); + fflush(stdout); + sleep(1); + break; + + case 'P': + arg_count = arg_count != COUNT_USERSPACE_PROCESSES ? COUNT_USERSPACE_PROCESSES : COUNT_PIDS; + fprintf(stdout, "\nCounting: %s.", counting_what()); + fflush(stdout); + sleep(1); + break; + + case 'r': + if (arg_count == COUNT_PIDS) + fprintf(stdout, "\n\aCannot toggle recursive counting, not available in task counting mode."); + else { + arg_recursive = !arg_recursive; + fprintf(stdout, "\nRecursive process counting: %s", yes_no(arg_recursive)); + } + fflush(stdout); + sleep(1); + break; + + case '+': + if (arg_delay < USEC_PER_SEC) + arg_delay += USEC_PER_MSEC*250; + else + arg_delay += USEC_PER_SEC; + + fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0)); + fflush(stdout); + sleep(1); + break; + + case '-': + if (arg_delay <= USEC_PER_MSEC*500) + arg_delay = USEC_PER_MSEC*250; + else if (arg_delay < USEC_PER_MSEC*1250) + arg_delay -= USEC_PER_MSEC*250; + else + arg_delay -= USEC_PER_SEC; + + fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0)); + fflush(stdout); + sleep(1); + break; + + case '?': + case 'h': + +#define ON ANSI_HIGHLIGHT +#define OFF ANSI_NORMAL + + fprintf(stdout, + "\t<" ON "p" OFF "> By path; <" ON "t" OFF "> By tasks/procs; <" ON "c" OFF "> By CPU; <" ON "m" OFF "> By memory; <" ON "i" OFF "> By I/O\n" + "\t<" ON "+" OFF "> Inc. delay; <" ON "-" OFF "> Dec. delay; <" ON "%%" OFF "> Toggle time; <" ON "SPACE" OFF "> Refresh\n" + "\t<" ON "P" OFF "> Toggle count userspace processes; <" ON "k" OFF "> Toggle count all processes\n" + "\t<" ON "r" OFF "> Count processes recursively; <" ON "q" OFF "> Quit"); + fflush(stdout); + sleep(3); + break; + + default: + if (key < ' ') + fprintf(stdout, "\nUnknown key '\\x%x'. Ignoring.", key); + else + fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key); + fflush(stdout); + sleep(1); + break; + } + } + + r = 0; + +finish: + group_hashmap_free(a); + group_hashmap_free(b); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-cryptsetup/Makefile b/src/systemd-cryptsetup/Makefile new file mode 100644 index 0000000000..7ab3605a31 --- /dev/null +++ b/src/systemd-cryptsetup/Makefile @@ -0,0 +1,59 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(HAVE_LIBCRYPTSETUP),) +libexec_PROGRAMS += \ + systemd-cryptsetup + +systemgenerator_PROGRAMS += \ + systemd-cryptsetup-generator + +dist_systemunit_DATA += \ + units/cryptsetup.target \ + units/cryptsetup-pre.target + +systemd_cryptsetup_SOURCES = \ + src/cryptsetup/cryptsetup.c + +systemd_cryptsetup_CFLAGS = \ + $(AM_CFLAGS) \ + $(LIBCRYPTSETUP_CFLAGS) + +systemd_cryptsetup_LDADD = \ + libshared.la \ + $(LIBCRYPTSETUP_LIBS) + +systemd_cryptsetup_generator_SOURCES = \ + src/cryptsetup/cryptsetup-generator.c + +systemd_cryptsetup_generator_LDADD = \ + libshared.la + +SYSINIT_TARGET_WANTS += \ + cryptsetup.target + +endif # HAVE_LIBCRYPTSETUP + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-cryptsetup/cryptsetup-generator.c b/src/systemd-cryptsetup/cryptsetup-generator.c new file mode 100644 index 0000000000..8ac5ab730a --- /dev/null +++ b/src/systemd-cryptsetup/cryptsetup-generator.c @@ -0,0 +1,509 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 "alloc-util.h" +#include "dropin.h" +#include "fd-util.h" +#include "fileio.h" +#include "fstab-util.h" +#include "generator.h" +#include "hashmap.h" +#include "log.h" +#include "mkdir.h" +#include "parse-util.h" +#include "path-util.h" +#include "proc-cmdline.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "util.h" + +typedef struct crypto_device { + char *uuid; + char *keyfile; + char *name; + char *options; + bool create; +} crypto_device; + +static const char *arg_dest = "/tmp"; +static bool arg_enabled = true; +static bool arg_read_crypttab = true; +static bool arg_whitelist = false; +static Hashmap *arg_disks = NULL; +static char *arg_default_options = NULL; +static char *arg_default_keyfile = NULL; + +static int create_disk( + const char *name, + const char *device, + const char *password, + const char *options) { + + _cleanup_free_ char *p = NULL, *n = NULL, *d = NULL, *u = NULL, *to = NULL, *e = NULL, + *filtered = NULL; + _cleanup_fclose_ FILE *f = NULL; + bool noauto, nofail, tmp, swap; + char *from; + int r; + + assert(name); + assert(device); + + noauto = fstab_test_yes_no_option(options, "noauto\0" "auto\0"); + nofail = fstab_test_yes_no_option(options, "nofail\0" "fail\0"); + tmp = fstab_test_option(options, "tmp\0"); + swap = fstab_test_option(options, "swap\0"); + + if (tmp && swap) { + log_error("Device '%s' cannot be both 'tmp' and 'swap'. Ignoring.", name); + return -EINVAL; + } + + e = unit_name_escape(name); + if (!e) + return log_oom(); + + r = unit_name_build("systemd-cryptsetup", e, ".service", &n); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + p = strjoin(arg_dest, "/", n, NULL); + if (!p) + return log_oom(); + + u = fstab_node_to_udev_node(device); + if (!u) + return log_oom(); + + r = unit_name_from_path(u, ".device", &d); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + f = fopen(p, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to create unit file %s: %m", p); + + fputs( + "# Automatically generated by systemd-cryptsetup-generator\n\n" + "[Unit]\n" + "Description=Cryptography Setup for %I\n" + "Documentation=man:crypttab(5) man:systemd-cryptsetup-generator(8) man:systemd-cryptsetup@.service(8)\n" + "SourcePath=/etc/crypttab\n" + "DefaultDependencies=no\n" + "Conflicts=umount.target\n" + "BindsTo=dev-mapper-%i.device\n" + "IgnoreOnIsolate=true\n" + "After=cryptsetup-pre.target\n", + f); + + if (!nofail) + fprintf(f, + "Before=cryptsetup.target\n"); + + if (password) { + if (STR_IN_SET(password, "/dev/urandom", "/dev/random", "/dev/hw_random")) + fputs("After=systemd-random-seed.service\n", f); + else if (!streq(password, "-") && !streq(password, "none")) { + _cleanup_free_ char *uu; + + uu = fstab_node_to_udev_node(password); + if (!uu) + return log_oom(); + + if (!path_equal(uu, "/dev/null")) { + + if (is_device_path(uu)) { + _cleanup_free_ char *dd = NULL; + + r = unit_name_from_path(uu, ".device", &dd); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + fprintf(f, "After=%1$s\nRequires=%1$s\n", dd); + } else + fprintf(f, "RequiresMountsFor=%s\n", password); + } + } + } + + if (is_device_path(u)) + fprintf(f, + "BindsTo=%s\n" + "After=%s\n" + "Before=umount.target\n", + d, d); + else + fprintf(f, + "RequiresMountsFor=%s\n", + u); + + r = generator_write_timeouts(arg_dest, device, name, options, &filtered); + if (r < 0) + return r; + + fprintf(f, + "\n[Service]\n" + "Type=oneshot\n" + "RemainAfterExit=yes\n" + "TimeoutSec=0\n" /* the binary handles timeouts anyway */ + "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n" + "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n", + name, u, strempty(password), strempty(filtered), + name); + + if (tmp) + fprintf(f, + "ExecStartPost=/sbin/mke2fs '/dev/mapper/%s'\n", + name); + + if (swap) + fprintf(f, + "ExecStartPost=/sbin/mkswap '/dev/mapper/%s'\n", + name); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write file %s: %m", p); + + from = strjoina("../", n); + + if (!noauto) { + + to = strjoin(arg_dest, "/", d, ".wants/", n, NULL); + if (!to) + return log_oom(); + + mkdir_parents_label(to, 0755); + if (symlink(from, to) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", to); + + free(to); + if (!nofail) + to = strjoin(arg_dest, "/cryptsetup.target.requires/", n, NULL); + else + to = strjoin(arg_dest, "/cryptsetup.target.wants/", n, NULL); + if (!to) + return log_oom(); + + mkdir_parents_label(to, 0755); + if (symlink(from, to) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", to); + } + + free(to); + to = strjoin(arg_dest, "/dev-mapper-", e, ".device.requires/", n, NULL); + if (!to) + return log_oom(); + + mkdir_parents_label(to, 0755); + if (symlink(from, to) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", to); + + if (!noauto && !nofail) { + _cleanup_free_ char *dmname; + dmname = strjoin("dev-mapper-", e, ".device", NULL); + if (!dmname) + return log_oom(); + + r = write_drop_in(arg_dest, dmname, 90, "device-timeout", + "# Automatically generated by systemd-cryptsetup-generator \n\n" + "[Unit]\nJobTimeoutSec=0"); + if (r < 0) + return log_error_errno(r, "Failed to write device drop-in: %m"); + } + + return 0; +} + +static void free_arg_disks(void) { + crypto_device *d; + + while ((d = hashmap_steal_first(arg_disks))) { + free(d->uuid); + free(d->keyfile); + free(d->name); + free(d->options); + free(d); + } + + hashmap_free(arg_disks); +} + +static crypto_device *get_crypto_device(const char *uuid) { + int r; + crypto_device *d; + + assert(uuid); + + d = hashmap_get(arg_disks, uuid); + if (!d) { + d = new0(struct crypto_device, 1); + if (!d) + return NULL; + + d->create = false; + d->keyfile = d->options = d->name = NULL; + + d->uuid = strdup(uuid); + if (!d->uuid) { + free(d); + return NULL; + } + + r = hashmap_put(arg_disks, d->uuid, d); + if (r < 0) { + free(d->uuid); + free(d); + return NULL; + } + } + + return d; +} + +static int parse_proc_cmdline_item(const char *key, const char *value) { + int r; + crypto_device *d; + _cleanup_free_ char *uuid = NULL, *uuid_value = NULL; + + if (STR_IN_SET(key, "luks", "rd.luks") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse luks switch %s. Ignoring.", value); + else + arg_enabled = r; + + } else if (STR_IN_SET(key, "luks.crypttab", "rd.luks.crypttab") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse luks crypttab switch %s. Ignoring.", value); + else + arg_read_crypttab = r; + + } else if (STR_IN_SET(key, "luks.uuid", "rd.luks.uuid") && value) { + + d = get_crypto_device(startswith(value, "luks-") ? value+5 : value); + if (!d) + return log_oom(); + + d->create = arg_whitelist = true; + + } else if (STR_IN_SET(key, "luks.options", "rd.luks.options") && value) { + + r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); + if (r == 2) { + d = get_crypto_device(uuid); + if (!d) + return log_oom(); + + free(d->options); + d->options = uuid_value; + uuid_value = NULL; + } else if (free_and_strdup(&arg_default_options, value) < 0) + return log_oom(); + + } else if (STR_IN_SET(key, "luks.key", "rd.luks.key") && value) { + + r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); + if (r == 2) { + d = get_crypto_device(uuid); + if (!d) + return log_oom(); + + free(d->keyfile); + d->keyfile = uuid_value; + uuid_value = NULL; + } else if (free_and_strdup(&arg_default_keyfile, value) < 0) + return log_oom(); + + } else if (STR_IN_SET(key, "luks.name", "rd.luks.name") && value) { + + r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); + if (r == 2) { + d = get_crypto_device(uuid); + if (!d) + return log_oom(); + + d->create = arg_whitelist = true; + + free(d->name); + d->name = uuid_value; + uuid_value = NULL; + } else + log_warning("Failed to parse luks name switch %s. Ignoring.", value); + + } + + return 0; +} + +static int add_crypttab_devices(void) { + struct stat st; + unsigned crypttab_line = 0; + _cleanup_fclose_ FILE *f = NULL; + + if (!arg_read_crypttab) + return 0; + + f = fopen("/etc/crypttab", "re"); + if (!f) { + if (errno != ENOENT) + log_error_errno(errno, "Failed to open /etc/crypttab: %m"); + return 0; + } + + if (fstat(fileno(f), &st) < 0) { + log_error_errno(errno, "Failed to stat /etc/crypttab: %m"); + return 0; + } + + for (;;) { + int r, k; + char line[LINE_MAX], *l, *uuid; + crypto_device *d = NULL; + _cleanup_free_ char *name = NULL, *device = NULL, *keyfile = NULL, *options = NULL; + + if (!fgets(line, sizeof(line), f)) + break; + + crypttab_line++; + + l = strstrip(line); + if (*l == '#' || *l == 0) + continue; + + k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &keyfile, &options); + if (k < 2 || k > 4) { + log_error("Failed to parse /etc/crypttab:%u, ignoring.", crypttab_line); + continue; + } + + uuid = startswith(device, "UUID="); + if (!uuid) + uuid = path_startswith(device, "/dev/disk/by-uuid/"); + if (!uuid) + uuid = startswith(name, "luks-"); + if (uuid) + d = hashmap_get(arg_disks, uuid); + + if (arg_whitelist && !d) { + log_info("Not creating device '%s' because it was not specified on the kernel command line.", name); + continue; + } + + r = create_disk(name, device, keyfile, (d && d->options) ? d->options : options); + if (r < 0) + return r; + + if (d) + d->create = false; + } + + return 0; +} + +static int add_proc_cmdline_devices(void) { + int r; + Iterator i; + crypto_device *d; + + HASHMAP_FOREACH(d, arg_disks, i) { + const char *options; + _cleanup_free_ char *device = NULL; + + if (!d->create) + continue; + + if (!d->name) { + d->name = strappend("luks-", d->uuid); + if (!d->name) + return log_oom(); + } + + device = strappend("UUID=", d->uuid); + if (!device) + return log_oom(); + + if (d->options) + options = d->options; + else if (arg_default_options) + options = arg_default_options; + else + options = "timeout=0"; + + r = create_disk(d->name, device, d->keyfile ?: arg_default_keyfile, options); + if (r < 0) + return r; + } + + return 0; +} + +int main(int argc, char *argv[]) { + int r = EXIT_FAILURE; + + if (argc > 1 && argc != 4) { + log_error("This program takes three or no arguments."); + return EXIT_FAILURE; + } + + if (argc > 1) + arg_dest = argv[1]; + + log_set_target(LOG_TARGET_SAFE); + log_parse_environment(); + log_open(); + + umask(0022); + + arg_disks = hashmap_new(&string_hash_ops); + if (!arg_disks) + goto cleanup; + + r = parse_proc_cmdline(parse_proc_cmdline_item); + if (r < 0) { + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + r = EXIT_FAILURE; + } + + if (!arg_enabled) { + r = EXIT_SUCCESS; + goto cleanup; + } + + if (add_crypttab_devices() < 0) + goto cleanup; + + if (add_proc_cmdline_devices() < 0) + goto cleanup; + + r = EXIT_SUCCESS; + +cleanup: + free_arg_disks(); + free(arg_default_options); + free(arg_default_keyfile); + + return r; +} diff --git a/src/systemd-cryptsetup/cryptsetup.c b/src/systemd-cryptsetup/cryptsetup.c new file mode 100644 index 0000000000..8024f80e36 --- /dev/null +++ b/src/systemd-cryptsetup/cryptsetup.c @@ -0,0 +1,757 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "ask-password-api.h" +#include "device-util.h" +#include "escape.h" +#include "fileio.h" +#include "log.h" +#include "mount-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +static const char *arg_type = NULL; /* CRYPT_LUKS1, CRYPT_TCRYPT or CRYPT_PLAIN */ +static char *arg_cipher = NULL; +static unsigned arg_key_size = 0; +static int arg_key_slot = CRYPT_ANY_SLOT; +static unsigned arg_keyfile_size = 0; +static unsigned arg_keyfile_offset = 0; +static char *arg_hash = NULL; +static char *arg_header = NULL; +static unsigned arg_tries = 3; +static bool arg_readonly = false; +static bool arg_verify = false; +static bool arg_discards = false; +static bool arg_tcrypt_hidden = false; +static bool arg_tcrypt_system = false; +static char **arg_tcrypt_keyfiles = NULL; +static uint64_t arg_offset = 0; +static uint64_t arg_skip = 0; +static usec_t arg_timeout = 0; + +/* Options Debian's crypttab knows we don't: + + precheck= + check= + checkargs= + noearly= + loud= + keyscript= +*/ + +static int parse_one_option(const char *option) { + assert(option); + + /* Handled outside of this tool */ + if (STR_IN_SET(option, "noauto", "auto", "nofail", "fail")) + return 0; + + if (startswith(option, "cipher=")) { + char *t; + + t = strdup(option+7); + if (!t) + return log_oom(); + + free(arg_cipher); + arg_cipher = t; + + } else if (startswith(option, "size=")) { + + if (safe_atou(option+5, &arg_key_size) < 0) { + log_error("size= parse failure, ignoring."); + return 0; + } + + if (arg_key_size % 8) { + log_error("size= not a multiple of 8, ignoring."); + return 0; + } + + arg_key_size /= 8; + + } else if (startswith(option, "key-slot=")) { + + arg_type = CRYPT_LUKS1; + if (safe_atoi(option+9, &arg_key_slot) < 0) { + log_error("key-slot= parse failure, ignoring."); + return 0; + } + + } else if (startswith(option, "tcrypt-keyfile=")) { + + arg_type = CRYPT_TCRYPT; + if (path_is_absolute(option+15)) { + if (strv_extend(&arg_tcrypt_keyfiles, option + 15) < 0) + return log_oom(); + } else + log_error("Key file path '%s' is not absolute. Ignoring.", option+15); + + } else if (startswith(option, "keyfile-size=")) { + + if (safe_atou(option+13, &arg_keyfile_size) < 0) { + log_error("keyfile-size= parse failure, ignoring."); + return 0; + } + + } else if (startswith(option, "keyfile-offset=")) { + + if (safe_atou(option+15, &arg_keyfile_offset) < 0) { + log_error("keyfile-offset= parse failure, ignoring."); + return 0; + } + + } else if (startswith(option, "hash=")) { + char *t; + + t = strdup(option+5); + if (!t) + return log_oom(); + + free(arg_hash); + arg_hash = t; + + } else if (startswith(option, "header=")) { + arg_type = CRYPT_LUKS1; + + if (!path_is_absolute(option+7)) { + log_error("Header path '%s' is not absolute, refusing.", option+7); + return -EINVAL; + } + + if (arg_header) { + log_error("Duplicate header= options, refusing."); + return -EINVAL; + } + + arg_header = strdup(option+7); + if (!arg_header) + return log_oom(); + + } else if (startswith(option, "tries=")) { + + if (safe_atou(option+6, &arg_tries) < 0) { + log_error("tries= parse failure, ignoring."); + return 0; + } + + } else if (STR_IN_SET(option, "readonly", "read-only")) + arg_readonly = true; + else if (streq(option, "verify")) + arg_verify = true; + else if (STR_IN_SET(option, "allow-discards", "discard")) + arg_discards = true; + else if (streq(option, "luks")) + arg_type = CRYPT_LUKS1; + else if (streq(option, "tcrypt")) + arg_type = CRYPT_TCRYPT; + else if (streq(option, "tcrypt-hidden")) { + arg_type = CRYPT_TCRYPT; + arg_tcrypt_hidden = true; + } else if (streq(option, "tcrypt-system")) { + arg_type = CRYPT_TCRYPT; + arg_tcrypt_system = true; + } else if (STR_IN_SET(option, "plain", "swap", "tmp")) + arg_type = CRYPT_PLAIN; + else if (startswith(option, "timeout=")) { + + if (parse_sec(option+8, &arg_timeout) < 0) { + log_error("timeout= parse failure, ignoring."); + return 0; + } + + } else if (startswith(option, "offset=")) { + + if (safe_atou64(option+7, &arg_offset) < 0) { + log_error("offset= parse failure, refusing."); + return -EINVAL; + } + + } else if (startswith(option, "skip=")) { + + if (safe_atou64(option+5, &arg_skip) < 0) { + log_error("skip= parse failure, refusing."); + return -EINVAL; + } + + } else if (!streq(option, "none")) + log_error("Encountered unknown /etc/crypttab option '%s', ignoring.", option); + + return 0; +} + +static int parse_options(const char *options) { + const char *word, *state; + size_t l; + int r; + + assert(options); + + FOREACH_WORD_SEPARATOR(word, l, options, ",", state) { + _cleanup_free_ char *o; + + o = strndup(word, l); + if (!o) + return -ENOMEM; + r = parse_one_option(o); + if (r < 0) + return r; + } + + /* sanity-check options */ + if (arg_type != NULL && !streq(arg_type, CRYPT_PLAIN)) { + if (arg_offset) + log_warning("offset= ignored with type %s", arg_type); + if (arg_skip) + log_warning("skip= ignored with type %s", arg_type); + } + + return 0; +} + +static void log_glue(int level, const char *msg, void *usrptr) { + log_debug("%s", msg); +} + +static int disk_major_minor(const char *path, char **ret) { + struct stat st; + + assert(path); + + if (stat(path, &st) < 0) + return -errno; + + if (!S_ISBLK(st.st_mode)) + return -EINVAL; + + if (asprintf(ret, "/dev/block/%d:%d", major(st.st_rdev), minor(st.st_rdev)) < 0) + return -errno; + + return 0; +} + +static char* disk_description(const char *path) { + + static const char name_fields[] = + "ID_PART_ENTRY_NAME\0" + "DM_NAME\0" + "ID_MODEL_FROM_DATABASE\0" + "ID_MODEL\0"; + + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + struct stat st; + const char *i; + int r; + + assert(path); + + if (stat(path, &st) < 0) + return NULL; + + if (!S_ISBLK(st.st_mode)) + return NULL; + + r = sd_device_new_from_devnum(&device, 'b', st.st_rdev); + if (r < 0) + return NULL; + + NULSTR_FOREACH(i, name_fields) { + const char *name; + + r = sd_device_get_property_value(device, i, &name); + if (r >= 0 && !isempty(name)) + return strdup(name); + } + + return NULL; +} + +static char *disk_mount_point(const char *label) { + _cleanup_free_ char *device = NULL; + _cleanup_endmntent_ FILE *f = NULL; + struct mntent *m; + + /* Yeah, we don't support native systemd unit files here for now */ + + if (asprintf(&device, "/dev/mapper/%s", label) < 0) + return NULL; + + f = setmntent("/etc/fstab", "r"); + if (!f) + return NULL; + + while ((m = getmntent(f))) + if (path_equal(m->mnt_fsname, device)) + return strdup(m->mnt_dir); + + return NULL; +} + +static int get_password(const char *vol, const char *src, usec_t until, bool accept_cached, char ***ret) { + _cleanup_free_ char *description = NULL, *name_buffer = NULL, *mount_point = NULL, *maj_min = NULL, *text = NULL, *escaped_name = NULL; + _cleanup_strv_free_erase_ char **passwords = NULL; + const char *name = NULL; + char **p, *id; + int r = 0; + + assert(vol); + assert(src); + assert(ret); + + description = disk_description(src); + mount_point = disk_mount_point(vol); + + if (description && streq(vol, description)) + /* If the description string is simply the + * volume name, then let's not show this + * twice */ + description = mfree(description); + + if (mount_point && description) + r = asprintf(&name_buffer, "%s (%s) on %s", description, vol, mount_point); + else if (mount_point) + r = asprintf(&name_buffer, "%s on %s", vol, mount_point); + else if (description) + r = asprintf(&name_buffer, "%s (%s)", description, vol); + + if (r < 0) + return log_oom(); + + name = name_buffer ? name_buffer : vol; + + if (asprintf(&text, "Please enter passphrase for disk %s!", name) < 0) + return log_oom(); + + if (src) + (void) disk_major_minor(src, &maj_min); + + if (maj_min) { + escaped_name = maj_min; + maj_min = NULL; + } else + escaped_name = cescape(name); + + if (!escaped_name) + return log_oom(); + + id = strjoina("cryptsetup:", escaped_name); + + r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", until, + ASK_PASSWORD_PUSH_CACHE | (accept_cached*ASK_PASSWORD_ACCEPT_CACHED), + &passwords); + if (r < 0) + return log_error_errno(r, "Failed to query password: %m"); + + if (arg_verify) { + _cleanup_strv_free_erase_ char **passwords2 = NULL; + + assert(strv_length(passwords) == 1); + + if (asprintf(&text, "Please enter passphrase for disk %s! (verification)", name) < 0) + return log_oom(); + + id = strjoina("cryptsetup-verification:", escaped_name); + + r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", until, ASK_PASSWORD_PUSH_CACHE, &passwords2); + if (r < 0) + return log_error_errno(r, "Failed to query verification password: %m"); + + assert(strv_length(passwords2) == 1); + + if (!streq(passwords[0], passwords2[0])) { + log_warning("Passwords did not match, retrying."); + return -EAGAIN; + } + } + + strv_uniq(passwords); + + STRV_FOREACH(p, passwords) { + char *c; + + if (strlen(*p)+1 >= arg_key_size) + continue; + + /* Pad password if necessary */ + c = new(char, arg_key_size); + if (!c) + return log_oom(); + + strncpy(c, *p, arg_key_size); + free(*p); + *p = c; + } + + *ret = passwords; + passwords = NULL; + + return 0; +} + +static int attach_tcrypt( + struct crypt_device *cd, + const char *name, + const char *key_file, + char **passwords, + uint32_t flags) { + + int r = 0; + _cleanup_free_ char *passphrase = NULL; + struct crypt_params_tcrypt params = { + .flags = CRYPT_TCRYPT_LEGACY_MODES, + .keyfiles = (const char **)arg_tcrypt_keyfiles, + .keyfiles_count = strv_length(arg_tcrypt_keyfiles) + }; + + assert(cd); + assert(name); + assert(key_file || (passwords && passwords[0])); + + if (arg_tcrypt_hidden) + params.flags |= CRYPT_TCRYPT_HIDDEN_HEADER; + + if (arg_tcrypt_system) + params.flags |= CRYPT_TCRYPT_SYSTEM_HEADER; + + if (key_file) { + r = read_one_line_file(key_file, &passphrase); + if (r < 0) { + log_error_errno(r, "Failed to read password file '%s': %m", key_file); + return -EAGAIN; + } + + params.passphrase = passphrase; + } else + params.passphrase = passwords[0]; + params.passphrase_size = strlen(params.passphrase); + + r = crypt_load(cd, CRYPT_TCRYPT, ¶ms); + if (r < 0) { + if (key_file && r == -EPERM) { + log_error("Failed to activate using password file '%s'.", key_file); + return -EAGAIN; + } + return r; + } + + return crypt_activate_by_volume_key(cd, name, NULL, 0, flags); +} + +static int attach_luks_or_plain(struct crypt_device *cd, + const char *name, + const char *key_file, + const char *data_device, + char **passwords, + uint32_t flags) { + int r = 0; + bool pass_volume_key = false; + + assert(cd); + assert(name); + assert(key_file || passwords); + + if (!arg_type || streq(arg_type, CRYPT_LUKS1)) { + r = crypt_load(cd, CRYPT_LUKS1, NULL); + if (r < 0) { + log_error("crypt_load() failed on device %s.\n", crypt_get_device_name(cd)); + return r; + } + + if (data_device) + r = crypt_set_data_device(cd, data_device); + } + + if ((!arg_type && r < 0) || streq_ptr(arg_type, CRYPT_PLAIN)) { + struct crypt_params_plain params = { + .offset = arg_offset, + .skip = arg_skip, + }; + const char *cipher, *cipher_mode; + _cleanup_free_ char *truncated_cipher = NULL; + + if (arg_hash) { + /* plain isn't a real hash type. it just means "use no hash" */ + if (!streq(arg_hash, "plain")) + params.hash = arg_hash; + } else if (!key_file) + /* for CRYPT_PLAIN, the behaviour of cryptsetup + * package is to not hash when a key file is provided */ + params.hash = "ripemd160"; + + if (arg_cipher) { + size_t l; + + l = strcspn(arg_cipher, "-"); + truncated_cipher = strndup(arg_cipher, l); + if (!truncated_cipher) + return log_oom(); + + cipher = truncated_cipher; + cipher_mode = arg_cipher[l] ? arg_cipher+l+1 : "plain"; + } else { + cipher = "aes"; + cipher_mode = "cbc-essiv:sha256"; + } + + /* for CRYPT_PLAIN limit reads + * from keyfile to key length, and + * ignore keyfile-size */ + arg_keyfile_size = arg_key_size; + + /* In contrast to what the name + * crypt_setup() might suggest this + * doesn't actually format anything, + * it just configures encryption + * parameters when used for plain + * mode. */ + r = crypt_format(cd, CRYPT_PLAIN, cipher, cipher_mode, NULL, NULL, arg_keyfile_size, ¶ms); + + /* hash == NULL implies the user passed "plain" */ + pass_volume_key = (params.hash == NULL); + } + + if (r < 0) + return log_error_errno(r, "Loading of cryptographic parameters failed: %m"); + + log_info("Set cipher %s, mode %s, key size %i bits for device %s.", + crypt_get_cipher(cd), + crypt_get_cipher_mode(cd), + crypt_get_volume_key_size(cd)*8, + crypt_get_device_name(cd)); + + if (key_file) { + r = crypt_activate_by_keyfile_offset(cd, name, arg_key_slot, key_file, arg_keyfile_size, arg_keyfile_offset, flags); + if (r < 0) { + log_error_errno(r, "Failed to activate with key file '%s': %m", key_file); + return -EAGAIN; + } + } else { + char **p; + + STRV_FOREACH(p, passwords) { + if (pass_volume_key) + r = crypt_activate_by_volume_key(cd, name, *p, arg_key_size, flags); + else + r = crypt_activate_by_passphrase(cd, name, arg_key_slot, *p, strlen(*p), flags); + + if (r >= 0) + break; + } + } + + return r; +} + +static int help(void) { + + printf("%s attach VOLUME SOURCEDEVICE [PASSWORD] [OPTIONS]\n" + "%s detach VOLUME\n\n" + "Attaches or detaches an encrypted block device.\n", + program_invocation_short_name, + program_invocation_short_name); + + return 0; +} + +int main(int argc, char *argv[]) { + int r = EXIT_FAILURE; + struct crypt_device *cd = NULL; + + if (argc <= 1) { + help(); + return EXIT_SUCCESS; + } + + if (argc < 3) { + log_error("This program requires at least two arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (streq(argv[1], "attach")) { + uint32_t flags = 0; + int k; + unsigned tries; + usec_t until; + crypt_status_info status; + const char *key_file = NULL; + + /* Arguments: systemd-cryptsetup attach VOLUME SOURCE-DEVICE [PASSWORD] [OPTIONS] */ + + if (argc < 4) { + log_error("attach requires at least two arguments."); + goto finish; + } + + if (argc >= 5 && + argv[4][0] && + !streq(argv[4], "-") && + !streq(argv[4], "none")) { + + if (!path_is_absolute(argv[4])) + log_error("Password file path '%s' is not absolute. Ignoring.", argv[4]); + else + key_file = argv[4]; + } + + if (argc >= 6 && argv[5][0] && !streq(argv[5], "-")) { + if (parse_options(argv[5]) < 0) + goto finish; + } + + /* A delicious drop of snake oil */ + mlockall(MCL_FUTURE); + + if (arg_header) { + log_debug("LUKS header: %s", arg_header); + k = crypt_init(&cd, arg_header); + } else + k = crypt_init(&cd, argv[3]); + if (k) { + log_error_errno(k, "crypt_init() failed: %m"); + goto finish; + } + + crypt_set_log_callback(cd, log_glue, NULL); + + status = crypt_status(cd, argv[2]); + if (status == CRYPT_ACTIVE || status == CRYPT_BUSY) { + log_info("Volume %s already active.", argv[2]); + r = EXIT_SUCCESS; + goto finish; + } + + if (arg_readonly) + flags |= CRYPT_ACTIVATE_READONLY; + + if (arg_discards) + flags |= CRYPT_ACTIVATE_ALLOW_DISCARDS; + + if (arg_timeout > 0) + until = now(CLOCK_MONOTONIC) + arg_timeout; + else + until = 0; + + arg_key_size = (arg_key_size > 0 ? arg_key_size : (256 / 8)); + + if (key_file) { + struct stat st; + + /* Ideally we'd do this on the open fd, but since this is just a + * warning it's OK to do this in two steps. */ + if (stat(key_file, &st) >= 0 && S_ISREG(st.st_mode) && (st.st_mode & 0005)) + log_warning("Key file %s is world-readable. This is not a good idea!", key_file); + } + + for (tries = 0; arg_tries == 0 || tries < arg_tries; tries++) { + _cleanup_strv_free_erase_ char **passwords = NULL; + + if (!key_file) { + k = get_password(argv[2], argv[3], until, tries == 0 && !arg_verify, &passwords); + if (k == -EAGAIN) + continue; + else if (k < 0) + goto finish; + } + + if (streq_ptr(arg_type, CRYPT_TCRYPT)) + k = attach_tcrypt(cd, argv[2], key_file, passwords, flags); + else + k = attach_luks_or_plain(cd, + argv[2], + key_file, + arg_header ? argv[3] : NULL, + passwords, + flags); + if (k >= 0) + break; + else if (k == -EAGAIN) { + key_file = NULL; + continue; + } else if (k != -EPERM) { + log_error_errno(k, "Failed to activate: %m"); + goto finish; + } + + log_warning("Invalid passphrase."); + } + + if (arg_tries != 0 && tries >= arg_tries) { + log_error("Too many attempts; giving up."); + r = EXIT_FAILURE; + goto finish; + } + + } else if (streq(argv[1], "detach")) { + int k; + + k = crypt_init_by_name(&cd, argv[2]); + if (k == -ENODEV) { + log_info("Volume %s already inactive.", argv[2]); + r = EXIT_SUCCESS; + goto finish; + } else if (k) { + log_error_errno(k, "crypt_init_by_name() failed: %m"); + goto finish; + } + + crypt_set_log_callback(cd, log_glue, NULL); + + k = crypt_deactivate(cd, argv[2]); + if (k < 0) { + log_error_errno(k, "Failed to deactivate: %m"); + goto finish; + } + + } else { + log_error("Unknown verb %s.", argv[1]); + goto finish; + } + + r = EXIT_SUCCESS; + +finish: + + if (cd) + crypt_free(cd); + + free(arg_cipher); + free(arg_hash); + free(arg_header); + strv_free(arg_tcrypt_keyfiles); + + return r; +} diff --git a/src/systemd-dbus1-generator/Makefile b/src/systemd-dbus1-generator/Makefile new file mode 100644 index 0000000000..1d7f0d1a81 --- /dev/null +++ b/src/systemd-dbus1-generator/Makefile @@ -0,0 +1,49 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemgenerator_PROGRAMS += \ + systemd-dbus1-generator + +systemd_dbus1_generator_SOURCES = \ + src/dbus1-generator/dbus1-generator.c + +systemd_dbus1_generator_LDADD = \ + libshared.la + +dbus1-generator-install-hook: + $(AM_V_at)$(MKDIR_P) $(DESTDIR)$(usergeneratordir) + $(AM_V_RM)rm -f $(DESTDIR)$(usergeneratordir)/systemd-dbus1-generator + $(AM_V_LN)$(LN_S) --relative -f $(DESTDIR)$(systemgeneratordir)/systemd-dbus1-generator $(DESTDIR)$(usergeneratordir)/systemd-dbus1-generator + +dbus1-generator-uninstall-hook: + rm -f $(DESTDIR)$(usergeneratordir)/systemd-dbus1-generator + +dist_xinitrc_SCRIPTS = \ + xorg/50-systemd-user.sh + +INSTALL_EXEC_HOOKS += dbus1-generator-install-hook +UNINSTALL_EXEC_HOOKS += dbus1-generator-uninstall-hook + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-dbus1-generator/dbus1-generator.c b/src/systemd-dbus1-generator/dbus1-generator.c new file mode 100644 index 0000000000..717cb9558e --- /dev/null +++ b/src/systemd-dbus1-generator/dbus1-generator.c @@ -0,0 +1,331 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 "alloc-util.h" +#include "bus-internal.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "conf-parser.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "mkdir.h" +#include "special.h" +#include "unit-name.h" +#include "util.h" + +static const char *arg_dest_late = "/tmp", *arg_dest = "/tmp"; + +static int create_dbus_files( + const char *path, + const char *name, + const char *service, + const char *exec, + const char *user, + const char *type) { + + _cleanup_free_ char *b = NULL, *s = NULL, *lnk = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(path); + assert(name); + assert(service || exec); + + if (!service) { + _cleanup_free_ char *a = NULL; + + s = strjoin("dbus-", name, ".service", NULL); + if (!s) + return log_oom(); + + a = strjoin(arg_dest_late, "/", s, NULL); + if (!a) + return log_oom(); + + f = fopen(a, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to create %s: %m", a); + + fprintf(f, + "# Automatically generated by systemd-dbus1-generator\n\n" + "[Unit]\n" + "SourcePath=%s\n" + "Description=DBUS1: %s\n" + "Documentation=man:systemd-dbus1-generator(8)\n\n" + "[Service]\n" + "ExecStart=%s\n" + "Type=dbus\n" + "BusName=%s\n", + path, + name, + exec, + name); + + if (user) + fprintf(f, "User=%s\n", user); + + + if (type) { + fprintf(f, "Environment=DBUS_STARTER_BUS_TYPE=%s\n", type); + + if (streq(type, "system")) + fprintf(f, "Environment=DBUS_STARTER_ADDRESS=" DEFAULT_SYSTEM_BUS_ADDRESS "\n"); + else if (streq(type, "session")) { + char *run; + + run = getenv("XDG_RUNTIME_DIR"); + if (!run) { + log_error("XDG_RUNTIME_DIR not set."); + return -EINVAL; + } + + fprintf(f, "Environment=DBUS_STARTER_ADDRESS="KERNEL_USER_BUS_ADDRESS_FMT ";" UNIX_USER_BUS_ADDRESS_FMT "\n", + getuid(), run); + } + } + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write %s: %m", a); + + f = safe_fclose(f); + + service = s; + } + + b = strjoin(arg_dest_late, "/", name, ".busname", NULL); + if (!b) + return log_oom(); + + f = fopen(b, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to create %s: %m", b); + + fprintf(f, + "# Automatically generated by systemd-dbus1-generator\n\n" + "[Unit]\n" + "SourcePath=%s\n" + "Description=DBUS1: %s\n" + "Documentation=man:systemd-dbus1-generator(8)\n\n" + "[BusName]\n" + "Name=%s\n" + "Service=%s\n" + "AllowWorld=talk\n", + path, + name, + name, + service); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write %s: %m", b); + + lnk = strjoin(arg_dest_late, "/" SPECIAL_BUSNAMES_TARGET ".wants/", name, ".busname", NULL); + if (!lnk) + return log_oom(); + + mkdir_parents_label(lnk, 0755); + if (symlink(b, lnk)) + return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); + + return 0; +} + +static int add_dbus(const char *path, const char *fname, const char *type) { + _cleanup_free_ char *name = NULL, *exec = NULL, *user = NULL, *service = NULL; + + const ConfigTableItem table[] = { + { "D-BUS Service", "Name", config_parse_string, 0, &name }, + { "D-BUS Service", "Exec", config_parse_string, 0, &exec }, + { "D-BUS Service", "User", config_parse_string, 0, &user }, + { "D-BUS Service", "SystemdService", config_parse_string, 0, &service }, + { }, + }; + + char *p; + int r; + + assert(path); + assert(fname); + + p = strjoina(path, "/", fname); + r = config_parse(NULL, p, NULL, + "D-BUS Service\0", + config_item_table_lookup, table, + true, false, true, NULL); + if (r < 0) + return r; + + if (!name) { + log_warning("Activation file %s lacks name setting, ignoring.", p); + return 0; + } + + if (!service_name_is_valid(name)) { + log_warning("Bus service name %s is not valid, ignoring.", name); + return 0; + } + + if (streq(name, "org.freedesktop.systemd1")) { + log_debug("Skipping %s, identified as systemd.", p); + return 0; + } + + if (service) { + if (!unit_name_is_valid(service, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) { + log_warning("Unit name %s is not valid, ignoring.", service); + return 0; + } + if (!endswith(service, ".service")) { + log_warning("Bus names can only activate services, ignoring %s.", p); + return 0; + } + } else { + if (streq(exec, "/bin/false") || !exec) { + log_warning("Neither service name nor binary path specified, ignoring %s.", p); + return 0; + } + + if (exec[0] != '/') { + log_warning("Exec= in %s does not start with an absolute path, ignoring.", p); + return 0; + } + } + + return create_dbus_files(p, name, service, exec, user, type); +} + +static int parse_dbus_fragments(const char *path, const char *type) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r; + + assert(path); + assert(type); + + d = opendir(path); + if (!d) { + if (errno == -ENOENT) + return 0; + + return log_error_errno(errno, "Failed to enumerate D-Bus activated services: %m"); + } + + r = 0; + FOREACH_DIRENT(de, d, goto fail) { + int q; + + if (!endswith(de->d_name, ".service")) + continue; + + q = add_dbus(path, de->d_name, type); + if (q < 0) + r = q; + } + + return r; + +fail: + return log_error_errno(errno, "Failed to read D-Bus services directory: %m"); +} + +static int link_busnames_target(const char *units) { + const char *f, *t; + + f = strjoina(units, "/" SPECIAL_BUSNAMES_TARGET); + t = strjoina(arg_dest, "/" SPECIAL_BASIC_TARGET ".wants/" SPECIAL_BUSNAMES_TARGET); + + mkdir_parents_label(t, 0755); + if (symlink(f, t) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", t); + + return 0; +} + +static int link_compatibility(const char *units) { + const char *f, *t; + + f = strjoina(units, "/systemd-bus-proxyd.socket"); + t = strjoina(arg_dest, "/" SPECIAL_DBUS_SOCKET); + mkdir_parents_label(t, 0755); + if (symlink(f, t) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", t); + + f = strjoina(units, "/systemd-bus-proxyd.socket"); + t = strjoina(arg_dest, "/" SPECIAL_SOCKETS_TARGET ".wants/systemd-bus-proxyd.socket"); + mkdir_parents_label(t, 0755); + if (symlink(f, t) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", t); + + t = strjoina(arg_dest, "/" SPECIAL_DBUS_SERVICE); + if (symlink("/dev/null", t) < 0) + return log_error_errno(errno, "Failed to mask %s: %m", t); + + return 0; +} + +int main(int argc, char *argv[]) { + const char *path, *type, *units; + int r, q; + + if (argc > 1 && argc != 4) { + log_error("This program takes three or no arguments."); + return EXIT_FAILURE; + } + + if (argc > 1) { + arg_dest = argv[1]; + arg_dest_late = argv[3]; + } + + log_set_target(LOG_TARGET_SAFE); + log_parse_environment(); + log_open(); + + umask(0022); + + if (!is_kdbus_available()) + return 0; + + r = cg_pid_get_owner_uid(0, NULL); + if (r >= 0) { + path = "/usr/share/dbus-1/services"; + type = "session"; + units = USER_DATA_UNIT_PATH; + } else if (r == -ENXIO) { + path = "/usr/share/dbus-1/system-services"; + type = "system"; + units = SYSTEM_DATA_UNIT_PATH; + } else + return log_error_errno(r, "Failed to determine whether we are running as user or system instance: %m"); + + r = parse_dbus_fragments(path, type); + + /* FIXME: One day this should just be pulled in statically from basic.target */ + q = link_busnames_target(units); + if (q < 0) + r = q; + + q = link_compatibility(units); + if (q < 0) + r = q; + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-debug-generator/Makefile b/src/systemd-debug-generator/Makefile new file mode 100644 index 0000000000..79684a8a7a --- /dev/null +++ b/src/systemd-debug-generator/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_debug_generator_SOURCES = \ + src/debug-generator/debug-generator.c + +systemd_debug_generator_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-debug-generator/debug-generator.c b/src/systemd-debug-generator/debug-generator.c new file mode 100644 index 0000000000..7e80af78e7 --- /dev/null +++ b/src/systemd-debug-generator/debug-generator.c @@ -0,0 +1,202 @@ +/*** + 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 "alloc-util.h" +#include "mkdir.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "special.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "util.h" + +static char *arg_default_unit = NULL; +static const char *arg_dest = "/tmp"; +static char **arg_mask = NULL; +static char **arg_wants = NULL; +static bool arg_debug_shell = false; + +static int parse_proc_cmdline_item(const char *key, const char *value) { + int r; + + assert(key); + + if (streq(key, "systemd.mask")) { + + if (!value) + log_error("Missing argument for systemd.mask= kernel command line parameter."); + else { + char *n; + + r = unit_name_mangle(value, UNIT_NAME_NOGLOB, &n); + if (r < 0) + return log_error_errno(r, "Failed to glob unit name: %m"); + + r = strv_consume(&arg_mask, n); + if (r < 0) + return log_oom(); + } + + } else if (streq(key, "systemd.wants")) { + + if (!value) + log_error("Missing argument for systemd.want= kernel command line parameter."); + else { + char *n; + + r = unit_name_mangle(value, UNIT_NAME_NOGLOB, &n); + if (r < 0) + return log_error_errno(r, "Failed to glob unit name: %m"); + + r = strv_consume(&arg_wants, n); + if (r < 0) + return log_oom(); + } + + } else if (streq(key, "systemd.debug-shell")) { + + if (value) { + r = parse_boolean(value); + if (r < 0) + log_error("Failed to parse systemd.debug-shell= argument '%s', ignoring.", value); + else + arg_debug_shell = r; + } else + arg_debug_shell = true; + } else if (streq(key, "systemd.unit")) { + + if (!value) + log_error("Missing argument for systemd.unit= kernel command line parameter."); + else { + r = free_and_strdup(&arg_default_unit, value); + if (r < 0) + return log_error_errno(r, "Failed to set default unit %s: %m", value); + } + } else if (!value) { + const char *target; + + target = runlevel_to_target(key); + if (target) { + r = free_and_strdup(&arg_default_unit, target); + if (r < 0) + return log_error_errno(r, "Failed to set default unit %s: %m", target); + } + } + + return 0; +} + +static int generate_mask_symlinks(void) { + char **u; + int r = 0; + + if (strv_isempty(arg_mask)) + return 0; + + STRV_FOREACH(u, arg_mask) { + _cleanup_free_ char *p = NULL; + + p = strjoin(arg_dest, "/", *u, NULL); + if (!p) + return log_oom(); + + if (symlink("/dev/null", p) < 0) + r = log_error_errno(errno, + "Failed to create mask symlink %s: %m", + p); + } + + return r; +} + +static int generate_wants_symlinks(void) { + char **u; + int r = 0; + + if (strv_isempty(arg_wants)) + return 0; + + STRV_FOREACH(u, arg_wants) { + _cleanup_free_ char *p = NULL, *f = NULL; + + p = strjoin(arg_dest, "/", arg_default_unit, ".wants/", *u, NULL); + if (!p) + return log_oom(); + + f = strappend(SYSTEM_DATA_UNIT_PATH "/", *u); + if (!f) + return log_oom(); + + mkdir_parents_label(p, 0755); + + if (symlink(f, p) < 0) + r = log_error_errno(errno, + "Failed to create wants symlink %s: %m", + p); + } + + return r; +} + +int main(int argc, char *argv[]) { + int r, q; + + if (argc > 1 && argc != 4) { + log_error("This program takes three or no arguments."); + return EXIT_FAILURE; + } + + if (argc > 1) + arg_dest = argv[2]; + + log_set_target(LOG_TARGET_SAFE); + log_parse_environment(); + log_open(); + + umask(0022); + + r = free_and_strdup(&arg_default_unit, SPECIAL_DEFAULT_TARGET); + if (r < 0) { + log_error_errno(r, "Failed to set default unit %s: %m", SPECIAL_DEFAULT_TARGET); + goto finish; + } + + r = parse_proc_cmdline(parse_proc_cmdline_item); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + + if (arg_debug_shell) { + r = strv_extend(&arg_wants, "debug-shell.service"); + if (r < 0) { + r = log_oom(); + goto finish; + } + } + + r = generate_mask_symlinks(); + + q = generate_wants_symlinks(); + if (q < 0) + r = q; + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + +} diff --git a/src/systemd-delta/Makefile b/src/systemd-delta/Makefile new file mode 100644 index 0000000000..b9124f86ea --- /dev/null +++ b/src/systemd-delta/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_delta_SOURCES = \ + src/delta/delta.c + +systemd_delta_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-delta/delta.c b/src/systemd-delta/delta.c new file mode 100644 index 0000000000..f32744def2 --- /dev/null +++ b/src/systemd-delta/delta.c @@ -0,0 +1,635 @@ +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + 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 +#include +#include +#include + +#include "alloc-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "hashmap.h" +#include "locale-util.h" +#include "log.h" +#include "pager.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "signal-util.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "util.h" + +static const char prefixes[] = + "/etc\0" + "/run\0" + "/usr/local/lib\0" + "/usr/local/share\0" + "/usr/lib\0" + "/usr/share\0" +#ifdef HAVE_SPLIT_USR + "/lib\0" +#endif + ; + +static const char suffixes[] = + "sysctl.d\0" + "tmpfiles.d\0" + "modules-load.d\0" + "binfmt.d\0" + "systemd/system\0" + "systemd/user\0" + "systemd/system-preset\0" + "systemd/user-preset\0" + "udev/rules.d\0" + "modprobe.d\0"; + +static const char have_dropins[] = + "systemd/system\0" + "systemd/user\0"; + +static bool arg_no_pager = false; +static int arg_diff = -1; + +static enum { + SHOW_MASKED = 1 << 0, + SHOW_EQUIVALENT = 1 << 1, + SHOW_REDIRECTED = 1 << 2, + SHOW_OVERRIDDEN = 1 << 3, + SHOW_UNCHANGED = 1 << 4, + SHOW_EXTENDED = 1 << 5, + + SHOW_DEFAULTS = + (SHOW_MASKED | SHOW_EQUIVALENT | SHOW_REDIRECTED | SHOW_OVERRIDDEN | SHOW_EXTENDED) +} arg_flags = 0; + +static int equivalent(const char *a, const char *b) { + _cleanup_free_ char *x = NULL, *y = NULL; + + x = canonicalize_file_name(a); + if (!x) + return -errno; + + y = canonicalize_file_name(b); + if (!y) + return -errno; + + return path_equal(x, y); +} + +static int notify_override_masked(const char *top, const char *bottom) { + if (!(arg_flags & SHOW_MASKED)) + return 0; + + printf("%s%s%s %s %s %s\n", + ansi_highlight_red(), "[MASKED]", ansi_normal(), + top, special_glyph(ARROW), bottom); + return 1; +} + +static int notify_override_equivalent(const char *top, const char *bottom) { + if (!(arg_flags & SHOW_EQUIVALENT)) + return 0; + + printf("%s%s%s %s %s %s\n", + ansi_highlight_green(), "[EQUIVALENT]", ansi_normal(), + top, special_glyph(ARROW), bottom); + return 1; +} + +static int notify_override_redirected(const char *top, const char *bottom) { + if (!(arg_flags & SHOW_REDIRECTED)) + return 0; + + printf("%s%s%s %s %s %s\n", + ansi_highlight(), "[REDIRECTED]", ansi_normal(), + top, special_glyph(ARROW), bottom); + return 1; +} + +static int notify_override_overridden(const char *top, const char *bottom) { + if (!(arg_flags & SHOW_OVERRIDDEN)) + return 0; + + printf("%s%s%s %s %s %s\n", + ansi_highlight(), "[OVERRIDDEN]", ansi_normal(), + top, special_glyph(ARROW), bottom); + return 1; +} + +static int notify_override_extended(const char *top, const char *bottom) { + if (!(arg_flags & SHOW_EXTENDED)) + return 0; + + printf("%s%s%s %s %s %s\n", + ansi_highlight(), "[EXTENDED]", ansi_normal(), + top, special_glyph(ARROW), bottom); + return 1; +} + +static int notify_override_unchanged(const char *f) { + if (!(arg_flags & SHOW_UNCHANGED)) + return 0; + + printf("[UNCHANGED] %s\n", f); + return 1; +} + +static int found_override(const char *top, const char *bottom) { + _cleanup_free_ char *dest = NULL; + int k; + pid_t pid; + + assert(top); + assert(bottom); + + if (null_or_empty_path(top) > 0) + return notify_override_masked(top, bottom); + + k = readlink_malloc(top, &dest); + if (k >= 0) { + if (equivalent(dest, bottom) > 0) + return notify_override_equivalent(top, bottom); + else + return notify_override_redirected(top, bottom); + } + + k = notify_override_overridden(top, bottom); + if (!arg_diff) + return k; + + putchar('\n'); + + fflush(stdout); + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork off diff: %m"); + else if (pid == 0) { + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + execlp("diff", "diff", "-us", "--", bottom, top, NULL); + log_error_errno(errno, "Failed to execute diff: %m"); + _exit(EXIT_FAILURE); + } + + wait_for_terminate_and_warn("diff", pid, false); + putchar('\n'); + + return k; +} + +static int enumerate_dir_d(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *toppath, const char *drop) { + _cleanup_free_ char *unit = NULL; + _cleanup_free_ char *path = NULL; + _cleanup_strv_free_ char **list = NULL; + char **file; + char *c; + int r; + + assert(!endswith(drop, "/")); + + path = strjoin(toppath, "/", drop, NULL); + if (!path) + return -ENOMEM; + + log_debug("Looking at %s", path); + + unit = strdup(drop); + if (!unit) + return -ENOMEM; + + c = strrchr(unit, '.'); + if (!c) + return -EINVAL; + *c = 0; + + r = get_files_in_directory(path, &list); + if (r < 0) + return log_error_errno(r, "Failed to enumerate %s: %m", path); + + STRV_FOREACH(file, list) { + Hashmap *h; + int k; + char *p; + char *d; + + if (!endswith(*file, ".conf")) + continue; + + p = strjoin(path, "/", *file, NULL); + if (!p) + return -ENOMEM; + d = p + strlen(toppath) + 1; + + log_debug("Adding at top: %s %s %s", d, special_glyph(ARROW), p); + k = hashmap_put(top, d, p); + if (k >= 0) { + p = strdup(p); + if (!p) + return -ENOMEM; + d = p + strlen(toppath) + 1; + } else if (k != -EEXIST) { + free(p); + return k; + } + + log_debug("Adding at bottom: %s %s %s", d, special_glyph(ARROW), p); + free(hashmap_remove(bottom, d)); + k = hashmap_put(bottom, d, p); + if (k < 0) { + free(p); + return k; + } + + h = hashmap_get(drops, unit); + if (!h) { + h = hashmap_new(&string_hash_ops); + if (!h) + return -ENOMEM; + hashmap_put(drops, unit, h); + unit = strdup(unit); + if (!unit) + return -ENOMEM; + } + + p = strdup(p); + if (!p) + return -ENOMEM; + + log_debug("Adding to drops: %s %s %s %s %s", + unit, special_glyph(ARROW), basename(p), special_glyph(ARROW), p); + k = hashmap_put(h, basename(p), p); + if (k < 0) { + free(p); + if (k != -EEXIST) + return k; + } + } + return 0; +} + +static int enumerate_dir(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *path, bool dropins) { + _cleanup_closedir_ DIR *d; + + assert(top); + assert(bottom); + assert(drops); + assert(path); + + log_debug("Looking at %s", path); + + d = opendir(path); + if (!d) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + for (;;) { + struct dirent *de; + int k; + char *p; + + errno = 0; + de = readdir(d); + if (!de) + return -errno; + + dirent_ensure_type(d, de); + + if (dropins && de->d_type == DT_DIR && endswith(de->d_name, ".d")) + enumerate_dir_d(top, bottom, drops, path, de->d_name); + + if (!dirent_is_file(de)) + continue; + + p = strjoin(path, "/", de->d_name, NULL); + if (!p) + return -ENOMEM; + + log_debug("Adding at top: %s %s %s", basename(p), special_glyph(ARROW), p); + k = hashmap_put(top, basename(p), p); + if (k >= 0) { + p = strdup(p); + if (!p) + return -ENOMEM; + } else if (k != -EEXIST) { + free(p); + return k; + } + + log_debug("Adding at bottom: %s %s %s", basename(p), special_glyph(ARROW), p); + free(hashmap_remove(bottom, basename(p))); + k = hashmap_put(bottom, basename(p), p); + if (k < 0) { + free(p); + return k; + } + } +} + +static int process_suffix(const char *suffix, const char *onlyprefix) { + const char *p; + char *f; + Hashmap *top, *bottom, *drops; + Hashmap *h; + char *key; + int r = 0, k; + Iterator i, j; + int n_found = 0; + bool dropins; + + assert(suffix); + assert(!startswith(suffix, "/")); + assert(!strstr(suffix, "//")); + + dropins = nulstr_contains(have_dropins, suffix); + + top = hashmap_new(&string_hash_ops); + bottom = hashmap_new(&string_hash_ops); + drops = hashmap_new(&string_hash_ops); + if (!top || !bottom || !drops) { + r = -ENOMEM; + goto finish; + } + + NULSTR_FOREACH(p, prefixes) { + _cleanup_free_ char *t = NULL; + + t = strjoin(p, "/", suffix, NULL); + if (!t) { + r = -ENOMEM; + goto finish; + } + + k = enumerate_dir(top, bottom, drops, t, dropins); + if (r == 0) + r = k; + } + + HASHMAP_FOREACH_KEY(f, key, top, i) { + char *o; + + o = hashmap_get(bottom, key); + assert(o); + + if (!onlyprefix || startswith(o, onlyprefix)) { + if (path_equal(o, f)) { + notify_override_unchanged(f); + } else { + k = found_override(f, o); + if (k < 0) + r = k; + else + n_found += k; + } + } + + h = hashmap_get(drops, key); + if (h) + HASHMAP_FOREACH(o, h, j) + if (!onlyprefix || startswith(o, onlyprefix)) + n_found += notify_override_extended(f, o); + } + +finish: + hashmap_free_free(top); + hashmap_free_free(bottom); + + HASHMAP_FOREACH_KEY(h, key, drops, i) { + hashmap_free_free(hashmap_remove(drops, key)); + hashmap_remove(drops, key); + free(key); + } + hashmap_free(drops); + + return r < 0 ? r : n_found; +} + +static int process_suffixes(const char *onlyprefix) { + const char *n; + int n_found = 0, r; + + NULSTR_FOREACH(n, suffixes) { + r = process_suffix(n, onlyprefix); + if (r < 0) + return r; + + n_found += r; + } + + return n_found; +} + +static int process_suffix_chop(const char *arg) { + const char *p; + + assert(arg); + + if (!path_is_absolute(arg)) + return process_suffix(arg, NULL); + + /* Strip prefix from the suffix */ + NULSTR_FOREACH(p, prefixes) { + const char *suffix; + + suffix = startswith(arg, p); + if (suffix) { + suffix += strspn(suffix, "/"); + if (*suffix) + return process_suffix(suffix, NULL); + else + return process_suffixes(arg); + } + } + + log_error("Invalid suffix specification %s.", arg); + return -EINVAL; +} + +static void help(void) { + printf("%s [OPTIONS...] [SUFFIX...]\n\n" + "Find overridden configuration files.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --diff[=1|0] Show a diff when overridden files differ\n" + " -t --type=LIST... Only display a selected set of override types\n" + , program_invocation_short_name); +} + +static int parse_flags(const char *flag_str, int flags) { + const char *word, *state; + size_t l; + + FOREACH_WORD_SEPARATOR(word, l, flag_str, ",", state) { + if (strneq("masked", word, l)) + flags |= SHOW_MASKED; + else if (strneq ("equivalent", word, l)) + flags |= SHOW_EQUIVALENT; + else if (strneq("redirected", word, l)) + flags |= SHOW_REDIRECTED; + else if (strneq("overridden", word, l)) + flags |= SHOW_OVERRIDDEN; + else if (strneq("unchanged", word, l)) + flags |= SHOW_UNCHANGED; + else if (strneq("extended", word, l)) + flags |= SHOW_EXTENDED; + else if (strneq("default", word, l)) + flags |= SHOW_DEFAULTS; + else + return -EINVAL; + } + return flags; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_NO_PAGER = 0x100, + ARG_DIFF, + ARG_VERSION + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "diff", optional_argument, NULL, ARG_DIFF }, + { "type", required_argument, NULL, 't' }, + {} + }; + + int c; + + assert(argc >= 1); + assert(argv); + + while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case 't': { + int f; + f = parse_flags(optarg, arg_flags); + if (f < 0) { + log_error("Failed to parse flags field."); + return -EINVAL; + } + arg_flags = f; + break; + } + + case ARG_DIFF: + if (!optarg) + arg_diff = 1; + else { + int b; + + b = parse_boolean(optarg); + if (b < 0) { + log_error("Failed to parse diff boolean."); + return -EINVAL; + } + + arg_diff = b; + } + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r, k, n_found = 0; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (arg_flags == 0) + arg_flags = SHOW_DEFAULTS; + + if (arg_diff < 0) + arg_diff = !!(arg_flags & SHOW_OVERRIDDEN); + else if (arg_diff) + arg_flags |= SHOW_OVERRIDDEN; + + pager_open(arg_no_pager, false); + + if (optind < argc) { + int i; + + for (i = optind; i < argc; i++) { + path_kill_slashes(argv[i]); + + k = process_suffix_chop(argv[i]); + if (k < 0) + r = k; + else + n_found += k; + } + + } else { + k = process_suffixes(NULL); + if (k < 0) + r = k; + else + n_found += k; + } + + if (r >= 0) + printf("%s%i overridden configuration files found.\n", n_found ? "\n" : "", n_found); + +finish: + pager_close(); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-detect-virt/Makefile b/src/systemd-detect-virt/Makefile new file mode 100644 index 0000000000..529a3a2561 --- /dev/null +++ b/src/systemd-detect-virt/Makefile @@ -0,0 +1,35 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_detect_virt_SOURCES = \ + src/detect-virt/detect-virt.c + +systemd_detect_virt_LDADD = \ + libshared.la + +INSTALL_EXEC_HOOKS += \ + systemd-detect-virt-install-hook + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-detect-virt/detect-virt.c b/src/systemd-detect-virt/detect-virt.c new file mode 100644 index 0000000000..5d51589a31 --- /dev/null +++ b/src/systemd-detect-virt/detect-virt.c @@ -0,0 +1,169 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include + +#include "util.h" +#include "virt.h" + +static bool arg_quiet = false; +static enum { + ANY_VIRTUALIZATION, + ONLY_VM, + ONLY_CONTAINER, + ONLY_CHROOT, +} arg_mode = ANY_VIRTUALIZATION; + +static void help(void) { + printf("%s [OPTIONS...]\n\n" + "Detect execution in a virtualized environment.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -c --container Only detect whether we are run in a container\n" + " -v --vm Only detect whether we are run in a VM\n" + " -r --chroot Detect whether we are run in a chroot() environment\n" + " -q --quiet Don't output anything, just set return value\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100 + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "container", no_argument, NULL, 'c' }, + { "vm", no_argument, NULL, 'v' }, + { "chroot", no_argument, NULL, 'r' }, + { "quiet", no_argument, NULL, 'q' }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hqcvr", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case 'q': + arg_quiet = true; + break; + + case 'c': + arg_mode = ONLY_CONTAINER; + break; + + case 'v': + arg_mode = ONLY_VM; + break; + + case 'r': + arg_mode = ONLY_CHROOT; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (optind < argc) { + log_error("%s takes no arguments.", program_invocation_short_name); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r; + + /* This is mostly intended to be used for scripts which want + * to detect whether we are being run in a virtualized + * environment or not */ + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + + switch (arg_mode) { + + case ONLY_VM: + r = detect_vm(); + if (r < 0) { + log_error_errno(r, "Failed to check for VM: %m"); + return EXIT_FAILURE; + } + + break; + + case ONLY_CONTAINER: + r = detect_container(); + if (r < 0) { + log_error_errno(r, "Failed to check for container: %m"); + return EXIT_FAILURE; + } + + break; + + case ONLY_CHROOT: + r = running_in_chroot(); + if (r < 0) { + log_error_errno(r, "Failed to check for chroot() environment: %m"); + return EXIT_FAILURE; + } + + return r ? EXIT_SUCCESS : EXIT_FAILURE; + + case ANY_VIRTUALIZATION: + default: + r = detect_virtualization(); + if (r < 0) { + log_error_errno(r, "Failed to check for virtualization: %m"); + return EXIT_FAILURE; + } + + break; + } + + if (!arg_quiet) + puts(virtualization_to_string(r)); + + return r != VIRTUALIZATION_NONE ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/systemd-escape/Makefile b/src/systemd-escape/Makefile new file mode 100644 index 0000000000..6589e06525 --- /dev/null +++ b/src/systemd-escape/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_escape_SOURCES = \ + src/escape/escape.c + +systemd_escape_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-escape/escape.c b/src/systemd-escape/escape.c new file mode 100644 index 0000000000..9f39049577 --- /dev/null +++ b/src/systemd-escape/escape.c @@ -0,0 +1,237 @@ +/*** + This file is part of systemd. + + Copyright 2014 Michael Biebl + + 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 +#include + +#include "alloc-util.h" +#include "log.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" + +static enum { + ACTION_ESCAPE, + ACTION_UNESCAPE, + ACTION_MANGLE +} arg_action = ACTION_ESCAPE; +static const char *arg_suffix = NULL; +static const char *arg_template = NULL; +static bool arg_path = false; + +static void help(void) { + printf("%s [OPTIONS...] [NAME...]\n\n" + "Show system and user paths.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --suffix=SUFFIX Unit suffix to append to escaped strings\n" + " --template=TEMPLATE Insert strings as instance into template\n" + " -u --unescape Unescape strings\n" + " -m --mangle Mangle strings\n" + " -p --path When escaping/unescaping assume the string is a path\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_SUFFIX, + ARG_TEMPLATE + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "suffix", required_argument, NULL, ARG_SUFFIX }, + { "template", required_argument, NULL, ARG_TEMPLATE }, + { "unescape", no_argument, NULL, 'u' }, + { "mangle", no_argument, NULL, 'm' }, + { "path", no_argument, NULL, 'p' }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hump", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_SUFFIX: + + if (unit_type_from_string(optarg) < 0) { + log_error("Invalid unit suffix type %s.", optarg); + return -EINVAL; + } + + arg_suffix = optarg; + break; + + case ARG_TEMPLATE: + + if (!unit_name_is_valid(optarg, UNIT_NAME_TEMPLATE)) { + log_error("Template name %s is not valid.", optarg); + return -EINVAL; + } + + arg_template = optarg; + break; + + case 'u': + arg_action = ACTION_UNESCAPE; + break; + + case 'm': + arg_action = ACTION_MANGLE; + break; + + case 'p': + arg_path = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (optind >= argc) { + log_error("Not enough arguments."); + return -EINVAL; + } + + if (arg_template && arg_suffix) { + log_error("--suffix= and --template= may not be combined."); + return -EINVAL; + } + + if ((arg_template || arg_suffix) && arg_action != ACTION_ESCAPE) { + log_error("--suffix= and --template= are not compatible with --unescape or --mangle."); + return -EINVAL; + } + + if (arg_path && !IN_SET(arg_action, ACTION_ESCAPE, ACTION_UNESCAPE)) { + log_error("--path may not be combined with --mangle."); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char *argv[]) { + char **i; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + STRV_FOREACH(i, argv + optind) { + _cleanup_free_ char *e = NULL; + + switch (arg_action) { + + case ACTION_ESCAPE: + if (arg_path) { + r = unit_name_path_escape(*i, &e); + if (r < 0) { + log_error_errno(r, "Failed to escape string: %m"); + goto finish; + } + } else { + e = unit_name_escape(*i); + if (!e) { + r = log_oom(); + goto finish; + } + } + + if (arg_template) { + char *x; + + r = unit_name_replace_instance(arg_template, e, &x); + if (r < 0) { + log_error_errno(r, "Failed to replace instance: %m"); + goto finish; + } + + free(e); + e = x; + } else if (arg_suffix) { + char *x; + + x = strjoin(e, ".", arg_suffix, NULL); + if (!x) { + r = log_oom(); + goto finish; + } + + free(e); + e = x; + } + + break; + + case ACTION_UNESCAPE: + if (arg_path) + r = unit_name_path_unescape(*i, &e); + else + r = unit_name_unescape(*i, &e); + + if (r < 0) { + log_error_errno(r, "Failed to unescape string: %m"); + goto finish; + } + break; + + case ACTION_MANGLE: + r = unit_name_mangle(*i, UNIT_NAME_NOGLOB, &e); + if (r < 0) { + log_error_errno(r, "Failed to mangle name: %m"); + goto finish; + } + break; + } + + if (i != argv+optind) + fputc(' ', stdout); + + fputs(e, stdout); + } + + fputc('\n', stdout); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-firstboot/Makefile b/src/systemd-firstboot/Makefile new file mode 100644 index 0000000000..931cf485e6 --- /dev/null +++ b/src/systemd-firstboot/Makefile @@ -0,0 +1,47 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_FIRSTBOOT),) +systemd_firstboot_SOURCES = \ + src/firstboot/firstboot.c + +systemd_firstboot_LDADD = \ + libshared.la \ + -lcrypt + +bin_PROGRAMS += \ + systemd-firstboot + +nodist_systemunit_DATA += \ + units/systemd-firstboot.service + +SYSINIT_TARGET_WANTS += \ + systemd-firstboot.service +endif # ENABLE_FIRSTBOOT + +EXTRA_DIST += \ + units/systemd-firstboot.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-firstboot/firstboot.c b/src/systemd-firstboot/firstboot.c new file mode 100644 index 0000000000..1e1a592b7c --- /dev/null +++ b/src/systemd-firstboot/firstboot.c @@ -0,0 +1,870 @@ +/*** + 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 +#include +#include + +#include "alloc-util.h" +#include "ask-password-api.h" +#include "copy.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "hostname-util.h" +#include "locale-util.h" +#include "mkdir.h" +#include "parse-util.h" +#include "path-util.h" +#include "random-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "time-util.h" +#include "umask-util.h" +#include "user-util.h" + +static char *arg_root = NULL; +static char *arg_locale = NULL; /* $LANG */ +static char *arg_locale_messages = NULL; /* $LC_MESSAGES */ +static char *arg_timezone = NULL; +static char *arg_hostname = NULL; +static sd_id128_t arg_machine_id = {}; +static char *arg_root_password = NULL; +static bool arg_prompt_locale = false; +static bool arg_prompt_timezone = false; +static bool arg_prompt_hostname = false; +static bool arg_prompt_root_password = false; +static bool arg_copy_locale = false; +static bool arg_copy_timezone = false; +static bool arg_copy_root_password = false; + +static bool press_any_key(void) { + char k = 0; + bool need_nl = true; + + printf("-- Press any key to proceed --"); + fflush(stdout); + + (void) read_one_char(stdin, &k, USEC_INFINITY, &need_nl); + + if (need_nl) + putchar('\n'); + + return k != 'q'; +} + +static void print_welcome(void) { + _cleanup_free_ char *pretty_name = NULL; + const char *os_release = NULL; + static bool done = false; + int r; + + if (done) + return; + + os_release = prefix_roota(arg_root, "/etc/os-release"); + r = parse_env_file(os_release, NEWLINE, + "PRETTY_NAME", &pretty_name, + NULL); + if (r == -ENOENT) { + + os_release = prefix_roota(arg_root, "/usr/lib/os-release"); + r = parse_env_file(os_release, NEWLINE, + "PRETTY_NAME", &pretty_name, + NULL); + } + + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to read os-release file: %m"); + + printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n", + isempty(pretty_name) ? "GNU/Linux" : pretty_name); + + press_any_key(); + + done = true; +} + +static int show_menu(char **x, unsigned n_columns, unsigned width, unsigned percentage) { + unsigned n, per_column, i, j; + unsigned break_lines, break_modulo; + + assert(n_columns > 0); + + n = strv_length(x); + per_column = (n + n_columns - 1) / n_columns; + + break_lines = lines(); + if (break_lines > 2) + break_lines--; + + /* The first page gets two extra lines, since we want to show + * a title */ + break_modulo = break_lines; + if (break_modulo > 3) + break_modulo -= 3; + + for (i = 0; i < per_column; i++) { + + for (j = 0; j < n_columns; j ++) { + _cleanup_free_ char *e = NULL; + + if (j * per_column + i >= n) + break; + + e = ellipsize(x[j * per_column + i], width, percentage); + if (!e) + return log_oom(); + + printf("%4u) %-*s", j * per_column + i + 1, width, e); + } + + putchar('\n'); + + /* on the first screen we reserve 2 extra lines for the title */ + if (i % break_lines == break_modulo) { + if (!press_any_key()) + return 0; + } + } + + return 0; +} + +static int prompt_loop(const char *text, char **l, bool (*is_valid)(const char *name), char **ret) { + int r; + + assert(text); + assert(is_valid); + assert(ret); + + for (;;) { + _cleanup_free_ char *p = NULL; + unsigned u; + + r = ask_string(&p, "%s %s (empty to skip): ", special_glyph(TRIANGULAR_BULLET), text); + if (r < 0) + return log_error_errno(r, "Failed to query user: %m"); + + if (isempty(p)) { + log_warning("No data entered, skipping."); + return 0; + } + + r = safe_atou(p, &u); + if (r >= 0) { + char *c; + + if (u <= 0 || u > strv_length(l)) { + log_error("Specified entry number out of range."); + continue; + } + + log_info("Selected '%s'.", l[u-1]); + + c = strdup(l[u-1]); + if (!c) + return log_oom(); + + free(*ret); + *ret = c; + return 0; + } + + if (!is_valid(p)) { + log_error("Entered data invalid."); + continue; + } + + free(*ret); + *ret = p; + p = 0; + return 0; + } +} + +static int prompt_locale(void) { + _cleanup_strv_free_ char **locales = NULL; + int r; + + if (arg_locale || arg_locale_messages) + return 0; + + if (!arg_prompt_locale) + return 0; + + r = get_locales(&locales); + if (r < 0) + return log_error_errno(r, "Cannot query locales list: %m"); + + print_welcome(); + + printf("\nAvailable Locales:\n\n"); + r = show_menu(locales, 3, 22, 60); + if (r < 0) + return r; + + putchar('\n'); + + r = prompt_loop("Please enter system locale name or number", locales, locale_is_valid, &arg_locale); + if (r < 0) + return r; + + if (isempty(arg_locale)) + return 0; + + r = prompt_loop("Please enter system message locale name or number", locales, locale_is_valid, &arg_locale_messages); + if (r < 0) + return r; + + return 0; +} + +static int process_locale(void) { + const char *etc_localeconf; + char* locales[3]; + unsigned i = 0; + int r; + + etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf"); + if (laccess(etc_localeconf, F_OK) >= 0) + return 0; + + if (arg_copy_locale && arg_root) { + + mkdir_parents(etc_localeconf, 0755); + r = copy_file("/etc/locale.conf", etc_localeconf, 0, 0644, 0); + if (r != -ENOENT) { + if (r < 0) + return log_error_errno(r, "Failed to copy %s: %m", etc_localeconf); + + log_info("%s copied.", etc_localeconf); + return 0; + } + } + + r = prompt_locale(); + if (r < 0) + return r; + + if (!isempty(arg_locale)) + locales[i++] = strjoina("LANG=", arg_locale); + if (!isempty(arg_locale_messages) && !streq(arg_locale_messages, arg_locale)) + locales[i++] = strjoina("LC_MESSAGES=", arg_locale_messages); + + if (i == 0) + return 0; + + locales[i] = NULL; + + mkdir_parents(etc_localeconf, 0755); + r = write_env_file(etc_localeconf, locales); + if (r < 0) + return log_error_errno(r, "Failed to write %s: %m", etc_localeconf); + + log_info("%s written.", etc_localeconf); + return 0; +} + +static int prompt_timezone(void) { + _cleanup_strv_free_ char **zones = NULL; + int r; + + if (arg_timezone) + return 0; + + if (!arg_prompt_timezone) + return 0; + + r = get_timezones(&zones); + if (r < 0) + return log_error_errno(r, "Cannot query timezone list: %m"); + + print_welcome(); + + printf("\nAvailable Time Zones:\n\n"); + r = show_menu(zones, 3, 22, 30); + if (r < 0) + return r; + + putchar('\n'); + + r = prompt_loop("Please enter timezone name or number", zones, timezone_is_valid, &arg_timezone); + if (r < 0) + return r; + + return 0; +} + +static int process_timezone(void) { + const char *etc_localtime, *e; + int r; + + etc_localtime = prefix_roota(arg_root, "/etc/localtime"); + if (laccess(etc_localtime, F_OK) >= 0) + return 0; + + if (arg_copy_timezone && arg_root) { + _cleanup_free_ char *p = NULL; + + r = readlink_malloc("/etc/localtime", &p); + if (r != -ENOENT) { + if (r < 0) + return log_error_errno(r, "Failed to read host timezone: %m"); + + mkdir_parents(etc_localtime, 0755); + if (symlink(p, etc_localtime) < 0) + return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime); + + log_info("%s copied.", etc_localtime); + return 0; + } + } + + r = prompt_timezone(); + if (r < 0) + return r; + + if (isempty(arg_timezone)) + return 0; + + e = strjoina("../usr/share/zoneinfo/", arg_timezone); + + mkdir_parents(etc_localtime, 0755); + if (symlink(e, etc_localtime) < 0) + return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime); + + log_info("%s written", etc_localtime); + return 0; +} + +static int prompt_hostname(void) { + int r; + + if (arg_hostname) + return 0; + + if (!arg_prompt_hostname) + return 0; + + print_welcome(); + putchar('\n'); + + for (;;) { + _cleanup_free_ char *h = NULL; + + r = ask_string(&h, "%s Please enter hostname for new system (empty to skip): ", special_glyph(TRIANGULAR_BULLET)); + if (r < 0) + return log_error_errno(r, "Failed to query hostname: %m"); + + if (isempty(h)) { + log_warning("No hostname entered, skipping."); + break; + } + + if (!hostname_is_valid(h, true)) { + log_error("Specified hostname invalid."); + continue; + } + + /* Get rid of the trailing dot that we allow, but don't want to see */ + arg_hostname = hostname_cleanup(h); + h = NULL; + break; + } + + return 0; +} + +static int process_hostname(void) { + const char *etc_hostname; + int r; + + etc_hostname = prefix_roota(arg_root, "/etc/hostname"); + if (laccess(etc_hostname, F_OK) >= 0) + return 0; + + r = prompt_hostname(); + if (r < 0) + return r; + + if (isempty(arg_hostname)) + return 0; + + mkdir_parents(etc_hostname, 0755); + r = write_string_file(etc_hostname, arg_hostname, WRITE_STRING_FILE_CREATE); + if (r < 0) + return log_error_errno(r, "Failed to write %s: %m", etc_hostname); + + log_info("%s written.", etc_hostname); + return 0; +} + +static int process_machine_id(void) { + const char *etc_machine_id; + char id[SD_ID128_STRING_MAX]; + int r; + + etc_machine_id = prefix_roota(arg_root, "/etc/machine-id"); + if (laccess(etc_machine_id, F_OK) >= 0) + return 0; + + if (sd_id128_equal(arg_machine_id, SD_ID128_NULL)) + return 0; + + mkdir_parents(etc_machine_id, 0755); + r = write_string_file(etc_machine_id, sd_id128_to_string(arg_machine_id, id), WRITE_STRING_FILE_CREATE); + if (r < 0) + return log_error_errno(r, "Failed to write machine id: %m"); + + log_info("%s written.", etc_machine_id); + return 0; +} + +static int prompt_root_password(void) { + const char *msg1, *msg2, *etc_shadow; + int r; + + if (arg_root_password) + return 0; + + if (!arg_prompt_root_password) + return 0; + + etc_shadow = prefix_roota(arg_root, "/etc/shadow"); + if (laccess(etc_shadow, F_OK) >= 0) + return 0; + + print_welcome(); + putchar('\n'); + + msg1 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter a new root password (empty to skip): "); + msg2 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter new root password again: "); + + for (;;) { + _cleanup_string_free_erase_ char *a = NULL, *b = NULL; + + r = ask_password_tty(msg1, NULL, 0, 0, NULL, &a); + if (r < 0) + return log_error_errno(r, "Failed to query root password: %m"); + + if (isempty(a)) { + log_warning("No password entered, skipping."); + break; + } + + r = ask_password_tty(msg2, NULL, 0, 0, NULL, &b); + if (r < 0) + return log_error_errno(r, "Failed to query root password: %m"); + + if (!streq(a, b)) { + log_error("Entered passwords did not match, please try again."); + continue; + } + + arg_root_password = a; + a = NULL; + break; + } + + return 0; +} + +static int write_root_shadow(const char *path, const struct spwd *p) { + _cleanup_fclose_ FILE *f = NULL; + assert(path); + assert(p); + + RUN_WITH_UMASK(0777) + f = fopen(path, "wex"); + if (!f) + return -errno; + + errno = 0; + if (putspent(p, f) != 0) + return errno > 0 ? -errno : -EIO; + + return fflush_and_check(f); +} + +static int process_root_password(void) { + + static const char table[] = + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + "./"; + + struct spwd item = { + .sp_namp = (char*) "root", + .sp_min = -1, + .sp_max = -1, + .sp_warn = -1, + .sp_inact = -1, + .sp_expire = -1, + .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */ + }; + + _cleanup_close_ int lock = -1; + char salt[3+16+1+1]; + uint8_t raw[16]; + unsigned i; + char *j; + + const char *etc_shadow; + int r; + + etc_shadow = prefix_roota(arg_root, "/etc/shadow"); + if (laccess(etc_shadow, F_OK) >= 0) + return 0; + + mkdir_parents(etc_shadow, 0755); + + lock = take_etc_passwd_lock(arg_root); + if (lock < 0) + return log_error_errno(lock, "Failed to take a lock: %m"); + + if (arg_copy_root_password && arg_root) { + struct spwd *p; + + errno = 0; + p = getspnam("root"); + if (p || errno != ENOENT) { + if (!p) { + if (!errno) + errno = EIO; + + return log_error_errno(errno, "Failed to find shadow entry for root: %m"); + } + + r = write_root_shadow(etc_shadow, p); + if (r < 0) + return log_error_errno(r, "Failed to write %s: %m", etc_shadow); + + log_info("%s copied.", etc_shadow); + return 0; + } + } + + r = prompt_root_password(); + if (r < 0) + return r; + + if (!arg_root_password) + return 0; + + r = dev_urandom(raw, 16); + if (r < 0) + return log_error_errno(r, "Failed to get salt: %m"); + + /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */ + assert_cc(sizeof(table) == 64 + 1); + j = stpcpy(salt, "$6$"); + for (i = 0; i < 16; i++) + j[i] = table[raw[i] & 63]; + j[i++] = '$'; + j[i] = 0; + + errno = 0; + item.sp_pwdp = crypt(arg_root_password, salt); + if (!item.sp_pwdp) { + if (!errno) + errno = EINVAL; + + return log_error_errno(errno, "Failed to encrypt password: %m"); + } + + item.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY); + + r = write_root_shadow(etc_shadow, &item); + if (r < 0) + return log_error_errno(r, "Failed to write %s: %m", etc_shadow); + + log_info("%s written.", etc_shadow); + return 0; +} + +static void help(void) { + printf("%s [OPTIONS...]\n\n" + "Configures basic settings of the system.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --root=PATH Operate on an alternate filesystem root\n" + " --locale=LOCALE Set primary locale (LANG=)\n" + " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n" + " --timezone=TIMEZONE Set timezone\n" + " --hostname=NAME Set host name\n" + " --machine-ID=ID Set machine ID\n" + " --root-password=PASSWORD Set root password\n" + " --root-password-file=FILE Set root password from file\n" + " --prompt-locale Prompt the user for locale settings\n" + " --prompt-timezone Prompt the user for timezone\n" + " --prompt-hostname Prompt the user for hostname\n" + " --prompt-root-password Prompt the user for root password\n" + " --prompt Prompt for all of the above\n" + " --copy-locale Copy locale from host\n" + " --copy-timezone Copy timezone from host\n" + " --copy-root-password Copy root password from host\n" + " --copy Copy locale, timezone, root password\n" + " --setup-machine-id Generate a new random machine ID\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_ROOT, + ARG_LOCALE, + ARG_LOCALE_MESSAGES, + ARG_TIMEZONE, + ARG_HOSTNAME, + ARG_MACHINE_ID, + ARG_ROOT_PASSWORD, + ARG_ROOT_PASSWORD_FILE, + ARG_PROMPT, + ARG_PROMPT_LOCALE, + ARG_PROMPT_TIMEZONE, + ARG_PROMPT_HOSTNAME, + ARG_PROMPT_ROOT_PASSWORD, + ARG_COPY, + ARG_COPY_LOCALE, + ARG_COPY_TIMEZONE, + ARG_COPY_ROOT_PASSWORD, + ARG_SETUP_MACHINE_ID, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "root", required_argument, NULL, ARG_ROOT }, + { "locale", required_argument, NULL, ARG_LOCALE }, + { "locale-messages", required_argument, NULL, ARG_LOCALE_MESSAGES }, + { "timezone", required_argument, NULL, ARG_TIMEZONE }, + { "hostname", required_argument, NULL, ARG_HOSTNAME }, + { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, + { "root-password", required_argument, NULL, ARG_ROOT_PASSWORD }, + { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE }, + { "prompt", no_argument, NULL, ARG_PROMPT }, + { "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE }, + { "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE }, + { "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME }, + { "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD }, + { "copy", no_argument, NULL, ARG_COPY }, + { "copy-locale", no_argument, NULL, ARG_COPY_LOCALE }, + { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE }, + { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD }, + { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID }, + {} + }; + + int r, c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_ROOT: + r = parse_path_argument_and_warn(optarg, true, &arg_root); + if (r < 0) + return r; + break; + + case ARG_LOCALE: + if (!locale_is_valid(optarg)) { + log_error("Locale %s is not valid.", optarg); + return -EINVAL; + } + + r = free_and_strdup(&arg_locale, optarg); + if (r < 0) + return log_oom(); + + break; + + case ARG_LOCALE_MESSAGES: + if (!locale_is_valid(optarg)) { + log_error("Locale %s is not valid.", optarg); + return -EINVAL; + } + + r = free_and_strdup(&arg_locale_messages, optarg); + if (r < 0) + return log_oom(); + + break; + + case ARG_TIMEZONE: + if (!timezone_is_valid(optarg)) { + log_error("Timezone %s is not valid.", optarg); + return -EINVAL; + } + + r = free_and_strdup(&arg_timezone, optarg); + if (r < 0) + return log_oom(); + + break; + + case ARG_ROOT_PASSWORD: + r = free_and_strdup(&arg_root_password, optarg); + if (r < 0) + return log_oom(); + break; + + case ARG_ROOT_PASSWORD_FILE: + arg_root_password = mfree(arg_root_password); + + r = read_one_line_file(optarg, &arg_root_password); + if (r < 0) + return log_error_errno(r, "Failed to read %s: %m", optarg); + + break; + + case ARG_HOSTNAME: + if (!hostname_is_valid(optarg, true)) { + log_error("Host name %s is not valid.", optarg); + return -EINVAL; + } + + hostname_cleanup(optarg); + r = free_and_strdup(&arg_hostname, optarg); + if (r < 0) + return log_oom(); + + break; + + case ARG_MACHINE_ID: + if (sd_id128_from_string(optarg, &arg_machine_id) < 0) { + log_error("Failed to parse machine id %s.", optarg); + return -EINVAL; + } + + break; + + case ARG_PROMPT: + arg_prompt_locale = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true; + break; + + case ARG_PROMPT_LOCALE: + arg_prompt_locale = true; + break; + + case ARG_PROMPT_TIMEZONE: + arg_prompt_timezone = true; + break; + + case ARG_PROMPT_HOSTNAME: + arg_prompt_hostname = true; + break; + + case ARG_PROMPT_ROOT_PASSWORD: + arg_prompt_root_password = true; + break; + + case ARG_COPY: + arg_copy_locale = arg_copy_timezone = arg_copy_root_password = true; + break; + + case ARG_COPY_LOCALE: + arg_copy_locale = true; + break; + + case ARG_COPY_TIMEZONE: + arg_copy_timezone = true; + break; + + case ARG_COPY_ROOT_PASSWORD: + arg_copy_root_password = true; + break; + + case ARG_SETUP_MACHINE_ID: + + r = sd_id128_randomize(&arg_machine_id); + if (r < 0) + return log_error_errno(r, "Failed to generate randomized machine ID: %m"); + + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r; + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + r = process_locale(); + if (r < 0) + goto finish; + + r = process_timezone(); + if (r < 0) + goto finish; + + r = process_hostname(); + if (r < 0) + goto finish; + + r = process_machine_id(); + if (r < 0) + goto finish; + + r = process_root_password(); + if (r < 0) + goto finish; + +finish: + free(arg_root); + free(arg_locale); + free(arg_locale_messages); + free(arg_timezone); + free(arg_hostname); + string_erase(arg_root_password); + free(arg_root_password); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-fsck/Makefile b/src/systemd-fsck/Makefile new file mode 100644 index 0000000000..9c981837f5 --- /dev/null +++ b/src/systemd-fsck/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_fsck_SOURCES = \ + src/fsck/fsck.c + +systemd_fsck_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-fsck/fsck.c b/src/systemd-fsck/fsck.c new file mode 100644 index 0000000000..d7f0829ffc --- /dev/null +++ b/src/systemd-fsck/fsck.c @@ -0,0 +1,487 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2014 Holger Hans Peter Freyther + + 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-error.h" +#include "bus-util.h" +#include "device-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "proc-cmdline.h" +#include "process-util.h" +#include "signal-util.h" +#include "socket-util.h" +#include "special.h" +#include "stdio-util.h" +#include "util.h" + +/* exit codes as defined in fsck(8) */ +enum { + FSCK_SUCCESS = 0, + FSCK_ERROR_CORRECTED = 1, + FSCK_SYSTEM_SHOULD_REBOOT = 2, + FSCK_ERRORS_LEFT_UNCORRECTED = 4, + FSCK_OPERATIONAL_ERROR = 8, + FSCK_USAGE_OR_SYNTAX_ERROR = 16, + FSCK_USER_CANCELLED = 32, + FSCK_SHARED_LIB_ERROR = 128, +}; + +static bool arg_skip = false; +static bool arg_force = false; +static bool arg_show_progress = false; +static const char *arg_repair = "-a"; + +static void start_target(const char *target, const char *mode) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + assert(target); + + r = bus_connect_system_systemd(&bus); + if (r < 0) { + log_error_errno(r, "Failed to get D-Bus connection: %m"); + return; + } + + log_info("Running request %s/start/replace", target); + + /* Start these units only if we can replace base.target with it */ + r = sd_bus_call_method(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartUnitReplace", + &error, + NULL, + "sss", "basic.target", target, mode); + + /* Don't print a warning if we aren't called during startup */ + if (r < 0 && !sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_JOB)) + log_error("Failed to start unit: %s", bus_error_message(&error, r)); +} + +static int parse_proc_cmdline_item(const char *key, const char *value) { + int r; + + assert(key); + + if (streq(key, "fsck.mode") && value) { + + if (streq(value, "auto")) + arg_force = arg_skip = false; + else if (streq(value, "force")) + arg_force = true; + else if (streq(value, "skip")) + arg_skip = true; + else + log_warning("Invalid fsck.mode= parameter '%s'. Ignoring.", value); + + } else if (streq(key, "fsck.repair") && value) { + + if (streq(value, "preen")) + arg_repair = "-a"; + else { + r = parse_boolean(value); + if (r > 0) + arg_repair = "-y"; + else if (r == 0) + arg_repair = "-n"; + else + log_warning("Invalid fsck.repair= parameter '%s'. Ignoring.", value); + } + } + +#ifdef HAVE_SYSV_COMPAT + else if (streq(key, "fastboot") && !value) { + log_warning("Please pass 'fsck.mode=skip' rather than 'fastboot' on the kernel command line."); + arg_skip = true; + + } else if (streq(key, "forcefsck") && !value) { + log_warning("Please pass 'fsck.mode=force' rather than 'forcefsck' on the kernel command line."); + arg_force = true; + } +#endif + + return 0; +} + +static void test_files(void) { + +#ifdef HAVE_SYSV_COMPAT + if (access("/fastboot", F_OK) >= 0) { + log_error("Please pass 'fsck.mode=skip' on the kernel command line rather than creating /fastboot on the root file system."); + arg_skip = true; + } + + if (access("/forcefsck", F_OK) >= 0) { + log_error("Please pass 'fsck.mode=force' on the kernel command line rather than creating /forcefsck on the root file system."); + arg_force = true; + } +#endif + + arg_show_progress = access("/run/systemd/show-status", F_OK) >= 0; +} + +static double percent(int pass, unsigned long cur, unsigned long max) { + /* Values stolen from e2fsck */ + + static const int pass_table[] = { + 0, 70, 90, 92, 95, 100 + }; + + if (pass <= 0) + return 0.0; + + if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0) + return 100.0; + + return (double) pass_table[pass-1] + + ((double) pass_table[pass] - (double) pass_table[pass-1]) * + (double) cur / (double) max; +} + +static int process_progress(int fd) { + _cleanup_fclose_ FILE *console = NULL, *f = NULL; + usec_t last = 0; + bool locked = false; + int clear = 0, r; + + /* No progress pipe to process? Then we are a NOP. */ + if (fd < 0) + return 0; + + f = fdopen(fd, "re"); + if (!f) { + safe_close(fd); + return -errno; + } + + console = fopen("/dev/console", "we"); + if (!console) + return -ENOMEM; + + for (;;) { + int pass, m; + unsigned long cur, max; + _cleanup_free_ char *device = NULL; + double p; + usec_t t; + + if (fscanf(f, "%i %lu %lu %ms", &pass, &cur, &max, &device) != 4) { + + if (ferror(f)) + r = log_warning_errno(errno, "Failed to read from progress pipe: %m"); + else if (feof(f)) + r = 0; + else { + log_warning("Failed to parse progress pipe data"); + r = -EBADMSG; + } + break; + } + + /* Only show one progress counter at max */ + if (!locked) { + if (flock(fileno(console), LOCK_EX|LOCK_NB) < 0) + continue; + + locked = true; + } + + /* Only update once every 50ms */ + t = now(CLOCK_MONOTONIC); + if (last + 50 * USEC_PER_MSEC > t) + continue; + + last = t; + + p = percent(pass, cur, max); + fprintf(console, "\r%s: fsck %3.1f%% complete...\r%n", device, p, &m); + fflush(console); + + if (m > clear) + clear = m; + } + + if (clear > 0) { + unsigned j; + + fputc('\r', console); + for (j = 0; j < (unsigned) clear; j++) + fputc(' ', console); + fputc('\r', console); + fflush(console); + } + + return r; +} + +static int fsck_progress_socket(void) { + static const union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/fsck.progress", + }; + + int fd, r; + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + return log_warning_errno(errno, "socket(): %m"); + + if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) { + r = log_full_errno(errno == ECONNREFUSED || errno == ENOENT ? LOG_DEBUG : LOG_WARNING, + errno, "Failed to connect to progress socket %s, ignoring: %m", sa.un.sun_path); + safe_close(fd); + return r; + } + + return fd; +} + +int main(int argc, char *argv[]) { + _cleanup_close_pair_ int progress_pipe[2] = { -1, -1 }; + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + const char *device, *type; + bool root_directory; + siginfo_t status; + struct stat st; + int r; + pid_t pid; + + if (argc > 2) { + log_error("This program expects one or no arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + r = parse_proc_cmdline(parse_proc_cmdline_item); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + + test_files(); + + if (!arg_force && arg_skip) { + r = 0; + goto finish; + } + + if (argc > 1) { + device = argv[1]; + + if (stat(device, &st) < 0) { + r = log_error_errno(errno, "Failed to stat %s: %m", device); + goto finish; + } + + if (!S_ISBLK(st.st_mode)) { + log_error("%s is not a block device.", device); + r = -EINVAL; + goto finish; + } + + r = sd_device_new_from_devnum(&dev, 'b', st.st_rdev); + if (r < 0) { + log_error_errno(r, "Failed to detect device %s: %m", device); + goto finish; + } + + root_directory = false; + } else { + struct timespec times[2]; + + /* Find root device */ + + if (stat("/", &st) < 0) { + r = log_error_errno(errno, "Failed to stat() the root directory: %m"); + goto finish; + } + + /* Virtual root devices don't need an fsck */ + if (major(st.st_dev) == 0) { + log_debug("Root directory is virtual or btrfs, skipping check."); + r = 0; + goto finish; + } + + /* check if we are already writable */ + times[0] = st.st_atim; + times[1] = st.st_mtim; + + if (utimensat(AT_FDCWD, "/", times, 0) == 0) { + log_info("Root directory is writable, skipping check."); + r = 0; + goto finish; + } + + r = sd_device_new_from_devnum(&dev, 'b', st.st_dev); + if (r < 0) { + log_error_errno(r, "Failed to detect root device: %m"); + goto finish; + } + + r = sd_device_get_devname(dev, &device); + if (r < 0) { + log_error_errno(r, "Failed to detect device node of root directory: %m"); + goto finish; + } + + root_directory = true; + } + + r = sd_device_get_property_value(dev, "ID_FS_TYPE", &type); + if (r >= 0) { + r = fsck_exists(type); + if (r < 0) + log_warning_errno(r, "Couldn't detect if fsck.%s may be used for %s, proceeding: %m", type, device); + else if (r == 0) { + log_info("fsck.%s doesn't exist, not checking file system on %s.", type, device); + goto finish; + } + } + + if (arg_show_progress) { + if (pipe(progress_pipe) < 0) { + r = log_error_errno(errno, "pipe(): %m"); + goto finish; + } + } + + pid = fork(); + if (pid < 0) { + r = log_error_errno(errno, "fork(): %m"); + goto finish; + } + if (pid == 0) { + char dash_c[sizeof("-C")-1 + DECIMAL_STR_MAX(int) + 1]; + int progress_socket = -1; + const char *cmdline[9]; + int i = 0; + + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + /* Close the reading side of the progress pipe */ + progress_pipe[0] = safe_close(progress_pipe[0]); + + /* Try to connect to a progress management daemon, if there is one */ + progress_socket = fsck_progress_socket(); + if (progress_socket >= 0) { + /* If this worked we close the progress pipe early, and just use the socket */ + progress_pipe[1] = safe_close(progress_pipe[1]); + xsprintf(dash_c, "-C%i", progress_socket); + } else if (progress_pipe[1] >= 0) { + /* Otherwise if we have the progress pipe to our own local handle, we use it */ + xsprintf(dash_c, "-C%i", progress_pipe[1]); + } else + dash_c[0] = 0; + + cmdline[i++] = "/sbin/fsck"; + cmdline[i++] = arg_repair; + cmdline[i++] = "-T"; + + /* + * Since util-linux v2.25 fsck uses /run/fsck/.lock files. + * The previous versions use flock for the device and conflict with + * udevd, see https://bugs.freedesktop.org/show_bug.cgi?id=79576#c5 + */ + cmdline[i++] = "-l"; + + if (!root_directory) + cmdline[i++] = "-M"; + + if (arg_force) + cmdline[i++] = "-f"; + + if (!isempty(dash_c)) + cmdline[i++] = dash_c; + + cmdline[i++] = device; + cmdline[i++] = NULL; + + execv(cmdline[0], (char**) cmdline); + _exit(FSCK_OPERATIONAL_ERROR); + } + + progress_pipe[1] = safe_close(progress_pipe[1]); + (void) process_progress(progress_pipe[0]); + progress_pipe[0] = -1; + + r = wait_for_terminate(pid, &status); + if (r < 0) { + log_error_errno(r, "waitid(): %m"); + goto finish; + } + + if (status.si_code != CLD_EXITED || (status.si_status & ~1)) { + + if (status.si_code == CLD_KILLED || status.si_code == CLD_DUMPED) + log_error("fsck terminated by signal %s.", signal_to_string(status.si_status)); + else if (status.si_code == CLD_EXITED) + log_error("fsck failed with error code %i.", status.si_status); + else + log_error("fsck failed due to unknown reason."); + + r = -EINVAL; + + if (status.si_code == CLD_EXITED && (status.si_status & FSCK_SYSTEM_SHOULD_REBOOT) && root_directory) + /* System should be rebooted. */ + start_target(SPECIAL_REBOOT_TARGET, "replace-irreversibly"); + else if (status.si_code == CLD_EXITED && (status.si_status & (FSCK_SYSTEM_SHOULD_REBOOT | FSCK_ERRORS_LEFT_UNCORRECTED))) + /* Some other problem */ + start_target(SPECIAL_EMERGENCY_TARGET, "replace"); + else { + log_warning("Ignoring error."); + r = 0; + } + + } else + r = 0; + + if (status.si_code == CLD_EXITED && (status.si_status & FSCK_ERROR_CORRECTED)) + (void) touch("/run/systemd/quotacheck"); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-fstab-generator/Makefile b/src/systemd-fstab-generator/Makefile new file mode 100644 index 0000000000..4d201612b8 --- /dev/null +++ b/src/systemd-fstab-generator/Makefile @@ -0,0 +1,33 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_fstab_generator_SOURCES = \ + src/fstab-generator/fstab-generator.c \ + src/core/mount-setup.c + +systemd_fstab_generator_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-fstab-generator/fstab-generator.c b/src/systemd-fstab-generator/fstab-generator.c new file mode 100644 index 0000000000..108522873e --- /dev/null +++ b/src/systemd-fstab-generator/fstab-generator.c @@ -0,0 +1,713 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fstab-util.h" +#include "generator.h" +#include "log.h" +#include "mkdir.h" +#include "mount-setup.h" +#include "mount-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "proc-cmdline.h" +#include "special.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "util.h" +#include "virt.h" + +static const char *arg_dest = "/tmp"; +static bool arg_fstab_enabled = true; +static char *arg_root_what = NULL; +static char *arg_root_fstype = NULL; +static char *arg_root_options = NULL; +static int arg_root_rw = -1; +static char *arg_usr_what = NULL; +static char *arg_usr_fstype = NULL; +static char *arg_usr_options = NULL; + +static int add_swap( + const char *what, + struct mntent *me, + bool noauto, + bool nofail) { + + _cleanup_free_ char *name = NULL, *unit = NULL, *lnk = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(what); + assert(me); + + if (access("/proc/swaps", F_OK) < 0) { + log_info("Swap not supported, ignoring fstab swap entry for %s.", what); + return 0; + } + + if (detect_container() > 0) { + log_info("Running in a container, ignoring fstab swap entry for %s.", what); + return 0; + } + + r = unit_name_from_path(what, ".swap", &name); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + unit = strjoin(arg_dest, "/", name, NULL); + if (!unit) + return log_oom(); + + f = fopen(unit, "wxe"); + if (!f) { + if (errno == EEXIST) + log_error("Failed to create swap unit file %s, as it already exists. Duplicate entry in /etc/fstab?", unit); + else + log_error_errno(errno, "Failed to create unit file %s: %m", unit); + return -errno; + } + + fprintf(f, + "# Automatically generated by systemd-fstab-generator\n\n" + "[Unit]\n" + "SourcePath=/etc/fstab\n" + "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n\n" + "[Swap]\n" + "What=%s\n", + what); + + if (!isempty(me->mnt_opts) && !streq(me->mnt_opts, "defaults")) + fprintf(f, "Options=%s\n", me->mnt_opts); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit file %s: %m", unit); + + /* use what as where, to have a nicer error message */ + r = generator_write_timeouts(arg_dest, what, what, me->mnt_opts, NULL); + if (r < 0) + return r; + + if (!noauto) { + lnk = strjoin(arg_dest, "/" SPECIAL_SWAP_TARGET, + nofail ? ".wants/" : ".requires/", name, NULL); + if (!lnk) + return log_oom(); + + mkdir_parents_label(lnk, 0755); + if (symlink(unit, lnk) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); + } + + return 0; +} + +static bool mount_is_network(struct mntent *me) { + assert(me); + + return fstab_test_option(me->mnt_opts, "_netdev\0") || + fstype_is_network(me->mnt_type); +} + +static bool mount_in_initrd(struct mntent *me) { + assert(me); + + return fstab_test_option(me->mnt_opts, "x-initrd.mount\0") || + streq(me->mnt_dir, "/usr"); +} + +static int write_idle_timeout(FILE *f, const char *where, const char *opts) { + _cleanup_free_ char *timeout = NULL; + char timespan[FORMAT_TIMESPAN_MAX]; + usec_t u; + int r; + + r = fstab_filter_options(opts, "x-systemd.idle-timeout\0", NULL, &timeout, NULL); + if (r < 0) + return log_warning_errno(r, "Failed to parse options: %m"); + if (r == 0) + return 0; + + r = parse_sec(timeout, &u); + if (r < 0) { + log_warning("Failed to parse timeout for %s, ignoring: %s", where, timeout); + return 0; + } + + fprintf(f, "TimeoutIdleSec=%s\n", format_timespan(timespan, sizeof(timespan), u, 0)); + + return 0; +} + +static int write_requires_after(FILE *f, const char *opts) { + _cleanup_strv_free_ char **names = NULL, **units = NULL; + _cleanup_free_ char *res = NULL; + char **s; + int r; + + assert(f); + assert(opts); + + r = fstab_extract_values(opts, "x-systemd.requires", &names); + if (r < 0) + return log_warning_errno(r, "Failed to parse options: %m"); + if (r == 0) + return 0; + + STRV_FOREACH(s, names) { + char *x; + + r = unit_name_mangle_with_suffix(*s, UNIT_NAME_NOGLOB, ".mount", &x); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + r = strv_consume(&units, x); + if (r < 0) + return log_oom(); + } + + if (units) { + res = strv_join(units, " "); + if (!res) + return log_oom(); + fprintf(f, "After=%1$s\nRequires=%1$s\n", res); + } + + return 0; +} + +static int write_requires_mounts_for(FILE *f, const char *opts) { + _cleanup_strv_free_ char **paths = NULL; + _cleanup_free_ char *res = NULL; + int r; + + assert(f); + assert(opts); + + r = fstab_extract_values(opts, "x-systemd.requires-mounts-for", &paths); + if (r < 0) + return log_warning_errno(r, "Failed to parse options: %m"); + if (r == 0) + return 0; + + res = strv_join(paths, " "); + if (!res) + return log_oom(); + + fprintf(f, "RequiresMountsFor=%s\n", res); + + return 0; +} + +static int add_mount( + const char *what, + const char *where, + const char *fstype, + const char *opts, + int passno, + bool noauto, + bool nofail, + bool automount, + const char *post, + const char *source) { + + _cleanup_free_ char + *name = NULL, *unit = NULL, *lnk = NULL, + *automount_name = NULL, *automount_unit = NULL, + *filtered = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(what); + assert(where); + assert(opts); + assert(post); + assert(source); + + if (streq_ptr(fstype, "autofs")) + return 0; + + if (!is_path(where)) { + log_warning("Mount point %s is not a valid path, ignoring.", where); + return 0; + } + + if (mount_point_is_api(where) || + mount_point_ignore(where)) + return 0; + + if (path_equal(where, "/")) { + if (noauto) + log_warning("Ignoring \"noauto\" for root device"); + if (nofail) + log_warning("Ignoring \"nofail\" for root device"); + if (automount) + log_warning("Ignoring automount option for root device"); + + noauto = nofail = automount = false; + } + + r = unit_name_from_path(where, ".mount", &name); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + unit = strjoin(arg_dest, "/", name, NULL); + if (!unit) + return log_oom(); + + f = fopen(unit, "wxe"); + if (!f) { + if (errno == EEXIST) + log_error("Failed to create mount unit file %s, as it already exists. Duplicate entry in /etc/fstab?", unit); + else + log_error_errno(errno, "Failed to create unit file %s: %m", unit); + return -errno; + } + + fprintf(f, + "# Automatically generated by systemd-fstab-generator\n\n" + "[Unit]\n" + "SourcePath=%s\n" + "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n", + source); + + if (!noauto && !nofail && !automount) + fprintf(f, "Before=%s\n", post); + + if (!automount && opts) { + r = write_requires_after(f, opts); + if (r < 0) + return r; + r = write_requires_mounts_for(f, opts); + if (r < 0) + return r; + } + + if (passno != 0) { + r = generator_write_fsck_deps(f, arg_dest, what, where, fstype); + if (r < 0) + return r; + } + + fprintf(f, + "\n" + "[Mount]\n" + "What=%s\n" + "Where=%s\n", + what, + where); + + if (!isempty(fstype) && !streq(fstype, "auto")) + fprintf(f, "Type=%s\n", fstype); + + r = generator_write_timeouts(arg_dest, what, where, opts, &filtered); + if (r < 0) + return r; + + if (!isempty(filtered) && !streq(filtered, "defaults")) + fprintf(f, "Options=%s\n", filtered); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit file %s: %m", unit); + + if (!noauto && !automount) { + lnk = strjoin(arg_dest, "/", post, nofail ? ".wants/" : ".requires/", name, NULL); + if (!lnk) + return log_oom(); + + mkdir_parents_label(lnk, 0755); + if (symlink(unit, lnk) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); + } + + if (automount) { + r = unit_name_from_path(where, ".automount", &automount_name); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + automount_unit = strjoin(arg_dest, "/", automount_name, NULL); + if (!automount_unit) + return log_oom(); + + fclose(f); + f = fopen(automount_unit, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to create unit file %s: %m", automount_unit); + + fprintf(f, + "# Automatically generated by systemd-fstab-generator\n\n" + "[Unit]\n" + "SourcePath=%s\n" + "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n", + source); + + fprintf(f, "Before=%s\n", post); + + if (opts) { + r = write_requires_after(f, opts); + if (r < 0) + return r; + r = write_requires_mounts_for(f, opts); + if (r < 0) + return r; + } + + fprintf(f, + "\n" + "[Automount]\n" + "Where=%s\n", + where); + + r = write_idle_timeout(f, where, opts); + if (r < 0) + return r; + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit file %s: %m", automount_unit); + + free(lnk); + lnk = strjoin(arg_dest, "/", post, nofail ? ".wants/" : ".requires/", automount_name, NULL); + if (!lnk) + return log_oom(); + + mkdir_parents_label(lnk, 0755); + if (symlink(automount_unit, lnk) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); + } + + return 0; +} + +static int parse_fstab(bool initrd) { + _cleanup_endmntent_ FILE *f = NULL; + const char *fstab_path; + struct mntent *me; + int r = 0; + + fstab_path = initrd ? "/sysroot/etc/fstab" : "/etc/fstab"; + f = setmntent(fstab_path, "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + log_error_errno(errno, "Failed to open %s: %m", fstab_path); + return -errno; + } + + while ((me = getmntent(f))) { + _cleanup_free_ char *where = NULL, *what = NULL; + bool noauto, nofail; + int k; + + if (initrd && !mount_in_initrd(me)) + continue; + + what = fstab_node_to_udev_node(me->mnt_fsname); + if (!what) + return log_oom(); + + if (is_device_path(what) && path_is_read_only_fs("sys") > 0) { + log_info("Running in a container, ignoring fstab device entry for %s.", what); + continue; + } + + where = initrd ? strappend("/sysroot/", me->mnt_dir) : strdup(me->mnt_dir); + if (!where) + return log_oom(); + + if (is_path(where)) + path_kill_slashes(where); + + noauto = fstab_test_yes_no_option(me->mnt_opts, "noauto\0" "auto\0"); + nofail = fstab_test_yes_no_option(me->mnt_opts, "nofail\0" "fail\0"); + log_debug("Found entry what=%s where=%s type=%s nofail=%s noauto=%s", + what, where, me->mnt_type, + yes_no(noauto), yes_no(nofail)); + + if (streq(me->mnt_type, "swap")) + k = add_swap(what, me, noauto, nofail); + else { + bool automount; + const char *post; + + automount = fstab_test_option(me->mnt_opts, + "comment=systemd.automount\0" + "x-systemd.automount\0"); + if (initrd) + post = SPECIAL_INITRD_FS_TARGET; + else if (mount_is_network(me)) + post = SPECIAL_REMOTE_FS_TARGET; + else + post = SPECIAL_LOCAL_FS_TARGET; + + k = add_mount(what, + where, + me->mnt_type, + me->mnt_opts, + me->mnt_passno, + noauto, + nofail, + automount, + post, + fstab_path); + } + + if (k < 0) + r = k; + } + + return r; +} + +static int add_sysroot_mount(void) { + _cleanup_free_ char *what = NULL; + const char *opts; + int r; + + if (isempty(arg_root_what)) { + log_debug("Could not find a root= entry on the kernel command line."); + return 0; + } + + what = fstab_node_to_udev_node(arg_root_what); + if (!what) + return log_oom(); + + if (!arg_root_options) + opts = arg_root_rw > 0 ? "rw" : "ro"; + else if (arg_root_rw >= 0 || + !fstab_test_option(arg_root_options, "ro\0" "rw\0")) + opts = strjoina(arg_root_options, ",", arg_root_rw > 0 ? "rw" : "ro"); + else + opts = arg_root_options; + + log_debug("Found entry what=%s where=/sysroot type=%s", what, strna(arg_root_fstype)); + + if (is_device_path(what)) { + r = generator_write_initrd_root_device_deps(arg_dest, what); + if (r < 0) + return r; + } + + return add_mount(what, + "/sysroot", + arg_root_fstype, + opts, + is_device_path(what) ? 1 : 0, + false, + false, + false, + SPECIAL_INITRD_ROOT_FS_TARGET, + "/proc/cmdline"); +} + +static int add_sysroot_usr_mount(void) { + _cleanup_free_ char *what = NULL; + const char *opts; + + if (!arg_usr_what && !arg_usr_fstype && !arg_usr_options) + return 0; + + if (arg_root_what && !arg_usr_what) { + arg_usr_what = strdup(arg_root_what); + + if (!arg_usr_what) + return log_oom(); + } + + if (arg_root_fstype && !arg_usr_fstype) { + arg_usr_fstype = strdup(arg_root_fstype); + + if (!arg_usr_fstype) + return log_oom(); + } + + if (arg_root_options && !arg_usr_options) { + arg_usr_options = strdup(arg_root_options); + + if (!arg_usr_options) + return log_oom(); + } + + if (!arg_usr_what) + return 0; + + what = fstab_node_to_udev_node(arg_usr_what); + if (!path_is_absolute(what)) { + log_debug("Skipping entry what=%s where=/sysroot/usr type=%s", what, strna(arg_usr_fstype)); + return -1; + } + + if (!arg_usr_options) + opts = arg_root_rw > 0 ? "rw" : "ro"; + else if (!fstab_test_option(arg_usr_options, "ro\0" "rw\0")) + opts = strjoina(arg_usr_options, ",", arg_root_rw > 0 ? "rw" : "ro"); + else + opts = arg_usr_options; + + log_debug("Found entry what=%s where=/sysroot/usr type=%s", what, strna(arg_usr_fstype)); + return add_mount(what, + "/sysroot/usr", + arg_usr_fstype, + opts, + 1, + false, + false, + false, + SPECIAL_INITRD_FS_TARGET, + "/proc/cmdline"); +} + +static int parse_proc_cmdline_item(const char *key, const char *value) { + int r; + + /* root=, usr=, usrfstype= and roofstype= may occur more than once, the last + * instance should take precedence. In the case of multiple rootflags= + * or usrflags= the arguments should be concatenated */ + + if (STR_IN_SET(key, "fstab", "rd.fstab") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse fstab switch %s. Ignoring.", value); + else + arg_fstab_enabled = r; + + } else if (streq(key, "root") && value) { + + if (free_and_strdup(&arg_root_what, value) < 0) + return log_oom(); + + } else if (streq(key, "rootfstype") && value) { + + if (free_and_strdup(&arg_root_fstype, value) < 0) + return log_oom(); + + } else if (streq(key, "rootflags") && value) { + char *o; + + o = arg_root_options ? + strjoin(arg_root_options, ",", value, NULL) : + strdup(value); + if (!o) + return log_oom(); + + free(arg_root_options); + arg_root_options = o; + + } else if (streq(key, "mount.usr") && value) { + + if (free_and_strdup(&arg_usr_what, value) < 0) + return log_oom(); + + } else if (streq(key, "mount.usrfstype") && value) { + + if (free_and_strdup(&arg_usr_fstype, value) < 0) + return log_oom(); + + } else if (streq(key, "mount.usrflags") && value) { + char *o; + + o = arg_usr_options ? + strjoin(arg_usr_options, ",", value, NULL) : + strdup(value); + if (!o) + return log_oom(); + + free(arg_usr_options); + arg_usr_options = o; + + } else if (streq(key, "rw") && !value) + arg_root_rw = true; + else if (streq(key, "ro") && !value) + arg_root_rw = false; + + return 0; +} + +int main(int argc, char *argv[]) { + int r = 0; + + if (argc > 1 && argc != 4) { + log_error("This program takes three or no arguments."); + return EXIT_FAILURE; + } + + if (argc > 1) + arg_dest = argv[1]; + + log_set_target(LOG_TARGET_SAFE); + log_parse_environment(); + log_open(); + + umask(0022); + + r = parse_proc_cmdline(parse_proc_cmdline_item); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + + /* Always honour root= and usr= in the kernel command line if we are in an initrd */ + if (in_initrd()) { + r = add_sysroot_mount(); + if (r == 0) + r = add_sysroot_usr_mount(); + } + + /* Honour /etc/fstab only when that's enabled */ + if (arg_fstab_enabled) { + int k; + + log_debug("Parsing /etc/fstab"); + + /* Parse the local /etc/fstab, possibly from the initrd */ + k = parse_fstab(false); + if (k < 0) + r = k; + + /* If running in the initrd also parse the /etc/fstab from the host */ + if (in_initrd()) { + log_debug("Parsing /sysroot/etc/fstab"); + + k = parse_fstab(true); + if (k < 0) + r = k; + } + } + + free(arg_root_what); + free(arg_root_fstype); + free(arg_root_options); + + free(arg_usr_what); + free(arg_usr_fstype); + free(arg_usr_options); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-getty-generator/Makefile b/src/systemd-getty-generator/Makefile new file mode 100644 index 0000000000..a070f1c424 --- /dev/null +++ b/src/systemd-getty-generator/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_getty_generator_SOURCES = \ + src/getty-generator/getty-generator.c + +systemd_getty_generator_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-getty-generator/getty-generator.c b/src/systemd-getty-generator/getty-generator.c new file mode 100644 index 0000000000..b15c76b5b8 --- /dev/null +++ b/src/systemd-getty-generator/getty-generator.c @@ -0,0 +1,233 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "log.h" +#include "mkdir.h" +#include "path-util.h" +#include "process-util.h" +#include "string-util.h" +#include "terminal-util.h" +#include "unit-name.h" +#include "util.h" +#include "virt.h" + +static const char *arg_dest = "/tmp"; + +static int add_symlink(const char *fservice, const char *tservice) { + char *from, *to; + int r; + + assert(fservice); + assert(tservice); + + from = strjoina(SYSTEM_DATA_UNIT_PATH "/", fservice); + to = strjoina(arg_dest, "/getty.target.wants/", tservice); + + mkdir_parents_label(to, 0755); + + r = symlink(from, to); + if (r < 0) { + /* In case console=hvc0 is passed this will very likely result in EEXIST */ + if (errno == EEXIST) + return 0; + + return log_error_errno(errno, "Failed to create symlink %s: %m", to); + } + + return 0; +} + +static int add_serial_getty(const char *tty) { + _cleanup_free_ char *n = NULL; + int r; + + assert(tty); + + log_debug("Automatically adding serial getty for /dev/%s.", tty); + + r = unit_name_from_path_instance("serial-getty", tty, ".service", &n); + if (r < 0) + return log_error_errno(r, "Failed to generate service name: %m"); + + return add_symlink("serial-getty@.service", n); +} + +static int add_container_getty(const char *tty) { + _cleanup_free_ char *n = NULL; + int r; + + assert(tty); + + log_debug("Automatically adding container getty for /dev/pts/%s.", tty); + + r = unit_name_from_path_instance("container-getty", tty, ".service", &n); + if (r < 0) + return log_error_errno(r, "Failed to generate service name: %m"); + + return add_symlink("container-getty@.service", n); +} + +static int verify_tty(const char *name) { + _cleanup_close_ int fd = -1; + const char *p; + + /* Some TTYs are weird and have been enumerated but don't work + * when you try to use them, such as classic ttyS0 and + * friends. Let's check that and open the device and run + * isatty() on it. */ + + p = strjoina("/dev/", name); + + /* O_NONBLOCK is essential here, to make sure we don't wait + * for DCD */ + fd = open(p, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC|O_NOFOLLOW); + if (fd < 0) + return -errno; + + errno = 0; + if (isatty(fd) <= 0) + return errno > 0 ? -errno : -EIO; + + return 0; +} + +int main(int argc, char *argv[]) { + + static const char virtualization_consoles[] = + "hvc0\0" + "xvc0\0" + "hvsi0\0" + "sclp_line0\0" + "ttysclp0\0" + "3270!tty1\0"; + + _cleanup_free_ char *active = NULL; + const char *j; + int r; + + if (argc > 1 && argc != 4) { + log_error("This program takes three or no arguments."); + return EXIT_FAILURE; + } + + if (argc > 1) + arg_dest = argv[1]; + + log_set_target(LOG_TARGET_SAFE); + log_parse_environment(); + log_open(); + + umask(0022); + + if (detect_container() > 0) { + _cleanup_free_ char *container_ttys = NULL; + + log_debug("Automatically adding console shell."); + + if (add_symlink("console-getty.service", "console-getty.service") < 0) + return EXIT_FAILURE; + + /* When $container_ttys is set for PID 1, spawn + * gettys on all ptys named therein. Note that despite + * the variable name we only support ptys here. */ + + r = getenv_for_pid(1, "container_ttys", &container_ttys); + if (r > 0) { + const char *word, *state; + size_t l; + + FOREACH_WORD(word, l, container_ttys, state) { + const char *t; + char tty[l + 1]; + + memcpy(tty, word, l); + tty[l] = 0; + + /* First strip off /dev/ if it is specified */ + t = path_startswith(tty, "/dev/"); + if (!t) + t = tty; + + /* Then, make sure it's actually a pty */ + t = path_startswith(t, "pts/"); + if (!t) + continue; + + if (add_container_getty(t) < 0) + return EXIT_FAILURE; + } + } + + /* Don't add any further magic if we are in a container */ + return EXIT_SUCCESS; + } + + if (read_one_line_file("/sys/class/tty/console/active", &active) >= 0) { + const char *word, *state; + size_t l; + + /* Automatically add in a serial getty on all active + * kernel consoles */ + FOREACH_WORD(word, l, active, state) { + _cleanup_free_ char *tty = NULL; + + tty = strndup(word, l); + if (!tty) { + log_oom(); + return EXIT_FAILURE; + } + + if (isempty(tty) || tty_is_vc(tty)) + continue; + + if (verify_tty(tty) < 0) + continue; + + /* We assume that gettys on virtual terminals are + * started via manual configuration and do this magic + * only for non-VC terminals. */ + + if (add_serial_getty(tty) < 0) + return EXIT_FAILURE; + } + } + + /* Automatically add in a serial getty on the first + * virtualizer console */ + NULSTR_FOREACH(j, virtualization_consoles) { + char *p; + + p = strjoina("/sys/class/tty/", j); + if (access(p, F_OK) < 0) + continue; + + if (add_serial_getty(j) < 0) + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/src/systemd-gpt-auto-generator/Makefile b/src/systemd-gpt-auto-generator/Makefile new file mode 100644 index 0000000000..177db8da7c --- /dev/null +++ b/src/systemd-gpt-auto-generator/Makefile @@ -0,0 +1,43 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(HAVE_BLKID),) +systemgenerator_PROGRAMS += \ + systemd-gpt-auto-generator + +systemd_gpt_auto_generator_SOURCES = \ + src/gpt-auto-generator/gpt-auto-generator.c \ + src/basic/blkid-util.h + +systemd_gpt_auto_generator_LDADD = \ + libshared.la \ + $(BLKID_LIBS) + +systemd_gpt_auto_generator_CFLAGS = \ + $(AM_CFLAGS) \ + $(BLKID_CFLAGS) +endif # HAVE_BLKID + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-gpt-auto-generator/gpt-auto-generator.c b/src/systemd-gpt-auto-generator/gpt-auto-generator.c new file mode 100644 index 0000000000..73e32da751 --- /dev/null +++ b/src/systemd-gpt-auto-generator/gpt-auto-generator.c @@ -0,0 +1,1042 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 +#include +#include + +#include "libudev.h" +#include + +#include "alloc-util.h" +#include "blkid-util.h" +#include "btrfs-util.h" +#include "dirent-util.h" +#include "efivars.h" +#include "fd-util.h" +#include "fileio.h" +#include "fstab-util.h" +#include "generator.h" +#include "gpt.h" +#include "missing.h" +#include "mkdir.h" +#include "mount-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "proc-cmdline.h" +#include "special.h" +#include "stat-util.h" +#include "string-util.h" +#include "udev-util.h" +#include "unit-name.h" +#include "util.h" +#include "virt.h" + +static const char *arg_dest = "/tmp"; +static bool arg_enabled = true; +static bool arg_root_enabled = true; +static bool arg_root_rw = false; + +static int add_cryptsetup(const char *id, const char *what, bool rw, char **device) { + _cleanup_free_ char *e = NULL, *n = NULL, *p = NULL, *d = NULL, *to = NULL; + _cleanup_fclose_ FILE *f = NULL; + char *from, *ret; + int r; + + assert(id); + assert(what); + assert(device); + + r = unit_name_from_path(what, ".device", &d); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + e = unit_name_escape(id); + if (!e) + return log_oom(); + + r = unit_name_build("systemd-cryptsetup", e, ".service", &n); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + p = strjoin(arg_dest, "/", n, NULL); + if (!p) + return log_oom(); + + f = fopen(p, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to create unit file %s: %m", p); + + fprintf(f, + "# Automatically generated by systemd-gpt-auto-generator\n\n" + "[Unit]\n" + "Description=Cryptography Setup for %%I\n" + "Documentation=man:systemd-gpt-auto-generator(8) man:systemd-cryptsetup@.service(8)\n" + "DefaultDependencies=no\n" + "Conflicts=umount.target\n" + "BindsTo=dev-mapper-%%i.device %s\n" + "Before=umount.target cryptsetup.target\n" + "After=%s\n" + "IgnoreOnIsolate=true\n" + "[Service]\n" + "Type=oneshot\n" + "RemainAfterExit=yes\n" + "TimeoutSec=0\n" /* the binary handles timeouts anyway */ + "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '' '%s'\n" + "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n", + d, d, + id, what, rw ? "" : "read-only", + id); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write file %s: %m", p); + + from = strjoina("../", n); + + to = strjoin(arg_dest, "/", d, ".wants/", n, NULL); + if (!to) + return log_oom(); + + mkdir_parents_label(to, 0755); + if (symlink(from, to) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", to); + + free(to); + to = strjoin(arg_dest, "/cryptsetup.target.requires/", n, NULL); + if (!to) + return log_oom(); + + mkdir_parents_label(to, 0755); + if (symlink(from, to) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", to); + + free(to); + to = strjoin(arg_dest, "/dev-mapper-", e, ".device.requires/", n, NULL); + if (!to) + return log_oom(); + + mkdir_parents_label(to, 0755); + if (symlink(from, to) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", to); + + free(p); + p = strjoin(arg_dest, "/dev-mapper-", e, ".device.d/50-job-timeout-sec-0.conf", NULL); + if (!p) + return log_oom(); + + mkdir_parents_label(p, 0755); + r = write_string_file(p, + "# Automatically generated by systemd-gpt-auto-generator\n\n" + "[Unit]\n" + "JobTimeoutSec=0\n", + WRITE_STRING_FILE_CREATE); /* the binary handles timeouts anyway */ + if (r < 0) + return log_error_errno(r, "Failed to write device drop-in: %m"); + + ret = strappend("/dev/mapper/", id); + if (!ret) + return log_oom(); + + *device = ret; + return 0; +} + +static int add_mount( + const char *id, + const char *what, + const char *where, + const char *fstype, + bool rw, + const char *options, + const char *description, + const char *post) { + + _cleanup_free_ char *unit = NULL, *lnk = NULL, *crypto_what = NULL, *p = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(id); + assert(what); + assert(where); + assert(description); + + log_debug("Adding %s: %s %s", where, what, strna(fstype)); + + if (streq_ptr(fstype, "crypto_LUKS")) { + + r = add_cryptsetup(id, what, rw, &crypto_what); + if (r < 0) + return r; + + what = crypto_what; + fstype = NULL; + } + + r = unit_name_from_path(where, ".mount", &unit); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + p = strjoin(arg_dest, "/", unit, NULL); + if (!p) + return log_oom(); + + f = fopen(p, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to create unit file %s: %m", unit); + + fprintf(f, + "# Automatically generated by systemd-gpt-auto-generator\n\n" + "[Unit]\n" + "Description=%s\n" + "Documentation=man:systemd-gpt-auto-generator(8)\n", + description); + + if (post) + fprintf(f, "Before=%s\n", post); + + r = generator_write_fsck_deps(f, arg_dest, what, where, fstype); + if (r < 0) + return r; + + fprintf(f, + "\n" + "[Mount]\n" + "What=%s\n" + "Where=%s\n", + what, where); + + if (fstype) + fprintf(f, "Type=%s\n", fstype); + + if (options) + fprintf(f, "Options=%s,%s\n", options, rw ? "rw" : "ro"); + else + fprintf(f, "Options=%s\n", rw ? "rw" : "ro"); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit file %s: %m", p); + + if (post) { + lnk = strjoin(arg_dest, "/", post, ".requires/", unit, NULL); + if (!lnk) + return log_oom(); + + mkdir_parents_label(lnk, 0755); + if (symlink(p, lnk) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); + } + + return 0; +} + +static bool path_is_busy(const char *where) { + int r; + + /* already a mountpoint; generators run during reload */ + r = path_is_mount_point(where, AT_SYMLINK_FOLLOW); + if (r > 0) + return false; + + /* the directory might not exist on a stateless system */ + if (r == -ENOENT) + return false; + + if (r < 0) + return true; + + /* not a mountpoint but it contains files */ + if (dir_is_empty(where) <= 0) + return true; + + return false; +} + +static int probe_and_add_mount( + const char *id, + const char *what, + const char *where, + bool rw, + const char *description, + const char *post) { + + _cleanup_blkid_free_probe_ blkid_probe b = NULL; + const char *fstype = NULL; + int r; + + assert(id); + assert(what); + assert(where); + assert(description); + + if (path_is_busy(where)) { + log_debug("%s already populated, ignoring.", where); + return 0; + } + + /* Let's check the partition type here, so that we know + * whether to do LUKS magic. */ + + errno = 0; + b = blkid_new_probe_from_filename(what); + if (!b) { + if (errno == 0) + return log_oom(); + return log_error_errno(errno, "Failed to allocate prober: %m"); + } + + blkid_probe_enable_superblocks(b, 1); + blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE); + + errno = 0; + r = blkid_do_safeprobe(b); + if (r == -2 || r == 1) /* no result or uncertain */ + return 0; + else if (r != 0) + return log_error_errno(errno ?: EIO, "Failed to probe %s: %m", what); + + /* add_mount is OK with fstype being NULL. */ + (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); + + return add_mount( + id, + what, + where, + fstype, + rw, + NULL, + description, + post); +} + +static int add_swap(const char *path) { + _cleanup_free_ char *name = NULL, *unit = NULL, *lnk = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(path); + + log_debug("Adding swap: %s", path); + + r = unit_name_from_path(path, ".swap", &name); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + unit = strjoin(arg_dest, "/", name, NULL); + if (!unit) + return log_oom(); + + f = fopen(unit, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to create unit file %s: %m", unit); + + fprintf(f, + "# Automatically generated by systemd-gpt-auto-generator\n\n" + "[Unit]\n" + "Description=Swap Partition\n" + "Documentation=man:systemd-gpt-auto-generator(8)\n\n" + "[Swap]\n" + "What=%s\n", + path); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit file %s: %m", unit); + + lnk = strjoin(arg_dest, "/" SPECIAL_SWAP_TARGET ".wants/", name, NULL); + if (!lnk) + return log_oom(); + + mkdir_parents_label(lnk, 0755); + if (symlink(unit, lnk) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); + + return 0; +} + +#ifdef ENABLE_EFI +static int add_automount( + const char *id, + const char *what, + const char *where, + const char *fstype, + bool rw, + const char *options, + const char *description, + usec_t timeout) { + + _cleanup_free_ char *unit = NULL, *lnk = NULL; + _cleanup_free_ char *opt, *p = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(id); + assert(where); + assert(description); + + if (options) + opt = strjoin(options, ",noauto", NULL); + else + opt = strdup("noauto"); + if (!opt) + return log_oom(); + + r = add_mount(id, + what, + where, + fstype, + rw, + opt, + description, + NULL); + if (r < 0) + return r; + + r = unit_name_from_path(where, ".automount", &unit); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + p = strjoin(arg_dest, "/", unit, NULL); + if (!p) + return log_oom(); + + f = fopen(p, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to create unit file %s: %m", unit); + + fprintf(f, + "# Automatically generated by systemd-gpt-auto-generator\n\n" + "[Unit]\n" + "Description=%s\n" + "Documentation=man:systemd-gpt-auto-generator(8)\n" + "[Automount]\n" + "Where=%s\n" + "TimeoutIdleSec=%lld\n", + description, + where, + (unsigned long long)timeout / USEC_PER_SEC); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit file %s: %m", p); + + lnk = strjoin(arg_dest, "/" SPECIAL_LOCAL_FS_TARGET ".wants/", unit, NULL); + if (!lnk) + return log_oom(); + mkdir_parents_label(lnk, 0755); + + if (symlink(p, lnk) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); + + return 0; +} + +static int add_boot(const char *what) { + _cleanup_blkid_free_probe_ blkid_probe b = NULL; + const char *fstype = NULL, *uuid = NULL; + sd_id128_t id, type_id; + int r; + + assert(what); + + if (!is_efi_boot()) { + log_debug("Not an EFI boot, ignoring /boot."); + return 0; + } + + if (in_initrd()) { + log_debug("In initrd, ignoring /boot."); + return 0; + } + + if (detect_container() > 0) { + log_debug("In a container, ignoring /boot."); + return 0; + } + + /* We create an .automount which is not overridden by the .mount from the fstab generator. */ + if (fstab_is_mount_point("/boot")) { + log_debug("/boot specified in fstab, ignoring."); + return 0; + } + + if (path_is_busy("/boot")) { + log_debug("/boot already populated, ignoring."); + return 0; + } + + r = efi_loader_get_device_part_uuid(&id); + if (r == -ENOENT) { + log_debug("EFI loader partition unknown."); + return 0; + } + + if (r < 0) { + log_error_errno(r, "Failed to read ESP partition UUID: %m"); + return r; + } + + errno = 0; + b = blkid_new_probe_from_filename(what); + if (!b) { + if (errno == 0) + return log_oom(); + return log_error_errno(errno, "Failed to allocate prober: %m"); + } + + blkid_probe_enable_partitions(b, 1); + blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); + + errno = 0; + r = blkid_do_safeprobe(b); + if (r == -2 || r == 1) /* no result or uncertain */ + return 0; + else if (r != 0) + return log_error_errno(errno ?: EIO, "Failed to probe %s: %m", what); + + (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); + if (!streq_ptr(fstype, "vfat")) { + log_debug("Partition for /boot is not a FAT filesystem, ignoring."); + return 0; + } + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &uuid, NULL); + if (r != 0) { + log_debug_errno(errno, "Partition for /boot does not have a UUID, ignoring."); + return 0; + } + + if (sd_id128_from_string(uuid, &type_id) < 0) { + log_debug("Partition for /boot does not have a valid UUID, ignoring."); + return 0; + } + + if (!sd_id128_equal(type_id, id)) { + log_debug("Partition for /boot does not appear to be the partition we are booted from."); + return 0; + } + + r = add_automount("boot", + what, + "/boot", + "vfat", + true, + "umask=0077", + "EFI System Partition Automount", + 120 * USEC_PER_SEC); + + return r; +} +#else +static int add_boot(const char *what) { + return 0; +} +#endif + +static int enumerate_partitions(dev_t devnum) { + + _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; + _cleanup_udev_device_unref_ struct udev_device *d = NULL; + _cleanup_blkid_free_probe_ blkid_probe b = NULL; + _cleanup_udev_unref_ struct udev *udev = NULL; + _cleanup_free_ char *boot = NULL, *home = NULL, *srv = NULL; + struct udev_list_entry *first, *item; + struct udev_device *parent = NULL; + const char *name, *node, *pttype, *devtype; + int boot_nr = -1, home_nr = -1, srv_nr = -1; + bool home_rw = true, srv_rw = true; + blkid_partlist pl; + int r, k; + dev_t pn; + + udev = udev_new(); + if (!udev) + return log_oom(); + + d = udev_device_new_from_devnum(udev, 'b', devnum); + if (!d) + return log_oom(); + + name = udev_device_get_devnode(d); + if (!name) + name = udev_device_get_syspath(d); + if (!name) { + log_debug("Device %u:%u does not have a name, ignoring.", + major(devnum), minor(devnum)); + return 0; + } + + parent = udev_device_get_parent(d); + if (!parent) { + log_debug("%s: not a partitioned device, ignoring.", name); + return 0; + } + + /* Does it have a devtype? */ + devtype = udev_device_get_devtype(parent); + if (!devtype) { + log_debug("%s: parent doesn't have a device type, ignoring.", name); + return 0; + } + + /* Is this a disk or a partition? We only care for disks... */ + if (!streq(devtype, "disk")) { + log_debug("%s: parent isn't a raw disk, ignoring.", name); + return 0; + } + + /* Does it have a device node? */ + node = udev_device_get_devnode(parent); + if (!node) { + log_debug("%s: parent device does not have device node, ignoring.", name); + return 0; + } + + log_debug("%s: root device %s.", name, node); + + pn = udev_device_get_devnum(parent); + if (major(pn) == 0) + return 0; + + errno = 0; + b = blkid_new_probe_from_filename(node); + if (!b) { + if (errno == 0) + return log_oom(); + + return log_error_errno(errno, "%s: failed to allocate prober: %m", node); + } + + blkid_probe_enable_partitions(b, 1); + blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); + + errno = 0; + r = blkid_do_safeprobe(b); + if (r == 1) + return 0; /* no results */ + else if (r == -2) { + log_warning("%s: probe gave ambiguous results, ignoring.", node); + return 0; + } else if (r != 0) + return log_error_errno(errno ?: EIO, "%s: failed to probe: %m", node); + + errno = 0; + r = blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL); + if (r != 0) { + if (errno == 0) + return 0; /* No partition table found. */ + + return log_error_errno(errno, "%s: failed to determine partition table type: %m", node); + } + + /* We only do this all for GPT... */ + if (!streq_ptr(pttype, "gpt")) { + log_debug("%s: not a GPT partition table, ignoring.", node); + return 0; + } + + errno = 0; + pl = blkid_probe_get_partitions(b); + if (!pl) { + if (errno == 0) + return log_oom(); + + return log_error_errno(errno, "%s: failed to list partitions: %m", node); + } + + e = udev_enumerate_new(udev); + if (!e) + return log_oom(); + + r = udev_enumerate_add_match_parent(e, parent); + if (r < 0) + return log_oom(); + + r = udev_enumerate_add_match_subsystem(e, "block"); + if (r < 0) + return log_oom(); + + r = udev_enumerate_scan_devices(e); + if (r < 0) + return log_error_errno(r, "%s: failed to enumerate partitions: %m", node); + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) { + _cleanup_udev_device_unref_ struct udev_device *q; + unsigned long long flags; + const char *stype, *subnode; + sd_id128_t type_id; + blkid_partition pp; + dev_t qn; + int nr; + + q = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); + if (!q) + continue; + + qn = udev_device_get_devnum(q); + if (major(qn) == 0) + continue; + + if (qn == devnum) + continue; + + if (qn == pn) + continue; + + subnode = udev_device_get_devnode(q); + if (!subnode) + continue; + + pp = blkid_partlist_devno_to_partition(pl, qn); + if (!pp) + continue; + + nr = blkid_partition_get_partno(pp); + if (nr < 0) + continue; + + stype = blkid_partition_get_type_string(pp); + if (!stype) + continue; + + if (sd_id128_from_string(stype, &type_id) < 0) + continue; + + flags = blkid_partition_get_flags(pp); + + if (sd_id128_equal(type_id, GPT_SWAP)) { + + if (flags & GPT_FLAG_NO_AUTO) + continue; + + if (flags & GPT_FLAG_READ_ONLY) { + log_debug("%s marked as read-only swap partition, which is bogus. Ignoring.", subnode); + continue; + } + + k = add_swap(subnode); + if (k < 0) + r = k; + + } else if (sd_id128_equal(type_id, GPT_ESP)) { + + /* We only care for the first /boot partition */ + if (boot && nr >= boot_nr) + continue; + + /* Note that we do not honour the "no-auto" + * flag for the ESP, as it is often unset, to + * hide it from Windows. */ + + boot_nr = nr; + + r = free_and_strdup(&boot, subnode); + if (r < 0) + return log_oom(); + + } else if (sd_id128_equal(type_id, GPT_HOME)) { + + if (flags & GPT_FLAG_NO_AUTO) + continue; + + /* We only care for the first /home partition */ + if (home && nr >= home_nr) + continue; + + home_nr = nr; + home_rw = !(flags & GPT_FLAG_READ_ONLY), + + r = free_and_strdup(&home, subnode); + if (r < 0) + return log_oom(); + + } else if (sd_id128_equal(type_id, GPT_SRV)) { + + if (flags & GPT_FLAG_NO_AUTO) + continue; + + /* We only care for the first /srv partition */ + if (srv && nr >= srv_nr) + continue; + + srv_nr = nr; + srv_rw = !(flags & GPT_FLAG_READ_ONLY), + + r = free_and_strdup(&srv, subnode); + if (r < 0) + return log_oom(); + } + } + + if (boot) { + k = add_boot(boot); + if (k < 0) + r = k; + } + + if (home) { + k = probe_and_add_mount("home", home, "/home", home_rw, "Home Partition", SPECIAL_LOCAL_FS_TARGET); + if (k < 0) + r = k; + } + + if (srv) { + k = probe_and_add_mount("srv", srv, "/srv", srv_rw, "Server Data Partition", SPECIAL_LOCAL_FS_TARGET); + if (k < 0) + r = k; + } + + return r; +} + +static int get_block_device(const char *path, dev_t *dev) { + struct stat st; + struct statfs sfs; + + assert(path); + assert(dev); + + /* Get's the block device directly backing a file system. If + * the block device is encrypted, returns the device mapper + * block device. */ + + if (lstat(path, &st)) + return -errno; + + if (major(st.st_dev) != 0) { + *dev = st.st_dev; + return 1; + } + + if (statfs(path, &sfs) < 0) + return -errno; + + if (F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC)) + return btrfs_get_block_device(path, dev); + + return 0; +} + +static int get_block_device_harder(const char *path, dev_t *dev) { + _cleanup_closedir_ DIR *d = NULL; + _cleanup_free_ char *p = NULL, *t = NULL; + struct dirent *de, *found = NULL; + const char *q; + unsigned maj, min; + dev_t dt; + int r; + + assert(path); + assert(dev); + + /* Gets the backing block device for a file system, and + * handles LUKS encrypted file systems, looking for its + * immediate parent, if there is one. */ + + r = get_block_device(path, &dt); + if (r <= 0) + return r; + + if (asprintf(&p, "/sys/dev/block/%u:%u/slaves", major(dt), minor(dt)) < 0) + return -ENOMEM; + + d = opendir(p); + if (!d) { + if (errno == ENOENT) + goto fallback; + + return -errno; + } + + FOREACH_DIRENT_ALL(de, d, return -errno) { + + if (STR_IN_SET(de->d_name, ".", "..")) + continue; + + if (!IN_SET(de->d_type, DT_LNK, DT_UNKNOWN)) + continue; + + if (found) /* Don't try to support multiple backing block devices */ + goto fallback; + + found = de; + } + + if (!found) + goto fallback; + + q = strjoina(p, "/", found->d_name, "/dev"); + + r = read_one_line_file(q, &t); + if (r == -ENOENT) + goto fallback; + if (r < 0) + return r; + + if (sscanf(t, "%u:%u", &maj, &min) != 2) + return -EINVAL; + + if (maj == 0) + goto fallback; + + *dev = makedev(maj, min); + return 1; + +fallback: + *dev = dt; + return 1; +} + +static int parse_proc_cmdline_item(const char *key, const char *value) { + int r; + + assert(key); + + if (STR_IN_SET(key, "systemd.gpt_auto", "rd.systemd.gpt_auto") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse gpt-auto switch \"%s\". Ignoring.", value); + else + arg_enabled = r; + + } else if (streq(key, "root") && value) { + + /* Disable root disk logic if there's a root= value + * specified (unless it happens to be "gpt-auto") */ + + arg_root_enabled = streq(value, "gpt-auto"); + + } else if (streq(key, "rw") && !value) + arg_root_rw = true; + else if (streq(key, "ro") && !value) + arg_root_rw = false; + + return 0; +} + +static int add_root_mount(void) { + +#ifdef ENABLE_EFI + int r; + + if (!is_efi_boot()) { + log_debug("Not a EFI boot, not creating root mount."); + return 0; + } + + r = efi_loader_get_device_part_uuid(NULL); + if (r == -ENOENT) { + log_debug("EFI loader partition unknown, exiting."); + return 0; + } else if (r < 0) + return log_error_errno(r, "Failed to read ESP partition UUID: %m"); + + /* OK, we have an ESP partition, this is fantastic, so let's + * wait for a root device to show up. A udev rule will create + * the link for us under the right name. */ + + if (in_initrd()) { + r = generator_write_initrd_root_device_deps(arg_dest, "/dev/gpt-auto-root"); + if (r < 0) + return 0; + } + + return add_mount( + "root", + "/dev/gpt-auto-root", + in_initrd() ? "/sysroot" : "/", + NULL, + arg_root_rw, + NULL, + "Root Partition", + in_initrd() ? SPECIAL_INITRD_ROOT_FS_TARGET : SPECIAL_LOCAL_FS_TARGET); +#else + return 0; +#endif +} + +static int add_mounts(void) { + dev_t devno; + int r; + + r = get_block_device_harder("/", &devno); + if (r < 0) + return log_error_errno(r, "Failed to determine block device of root file system: %m"); + else if (r == 0) { + r = get_block_device_harder("/usr", &devno); + if (r < 0) + return log_error_errno(r, "Failed to determine block device of /usr file system: %m"); + else if (r == 0) { + log_debug("Neither root nor /usr file system are on a (single) block device."); + return 0; + } + } + + return enumerate_partitions(devno); +} + +int main(int argc, char *argv[]) { + int r = 0; + + if (argc > 1 && argc != 4) { + log_error("This program takes three or no arguments."); + return EXIT_FAILURE; + } + + if (argc > 1) + arg_dest = argv[3]; + + log_set_target(LOG_TARGET_SAFE); + log_parse_environment(); + log_open(); + + umask(0022); + + if (detect_container() > 0) { + log_debug("In a container, exiting."); + return EXIT_SUCCESS; + } + + r = parse_proc_cmdline(parse_proc_cmdline_item); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + + if (!arg_enabled) { + log_debug("Disabled, exiting."); + return EXIT_SUCCESS; + } + + if (arg_root_enabled) + r = add_root_mount(); + + if (!in_initrd()) { + int k; + + k = add_mounts(); + if (k < 0) + r = k; + } + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-hibernate-resume/Makefile b/src/systemd-hibernate-resume/Makefile new file mode 100644 index 0000000000..baee443e84 --- /dev/null +++ b/src/systemd-hibernate-resume/Makefile @@ -0,0 +1,60 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_HIBERNATE),) +systemgenerator_PROGRAMS += \ + systemd-hibernate-resume-generator + +libexec_PROGRAMS += \ + systemd-hibernate-resume + +systemd_hibernate_resume_SOURCES = \ + src/hibernate-resume/hibernate-resume.c + +systemd_hibernate_resume_LDADD = \ + libshared.la + +systemd_hibernate_resume_generator_SOURCES = \ + src/hibernate-resume/hibernate-resume-generator.c + +systemd_hibernate_resume_generator_LDADD = \ + libshared.la + +dist_systemunit_DATA += \ + units/hibernate.target \ + units/hybrid-sleep.target + +nodist_systemunit_DATA += \ + units/systemd-hibernate.service \ + units/systemd-hibernate-resume@.service \ + units/systemd-hybrid-sleep.service +endif # ENABLE_HIBERNATE + +EXTRA_DIST += \ + units/systemd-hibernate.service.in \ + units/systemd-hibernate-resume@.service.in \ + units/systemd-hybrid-sleep.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-hibernate-resume/hibernate-resume-generator.c b/src/systemd-hibernate-resume/hibernate-resume-generator.c new file mode 100644 index 0000000000..d7ee80d58f --- /dev/null +++ b/src/systemd-hibernate-resume/hibernate-resume-generator.c @@ -0,0 +1,99 @@ +/*** + This file is part of systemd. + + Copyright 2014 Ivan Shapovalov + + 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 + +#include "alloc-util.h" +#include "fstab-util.h" +#include "log.h" +#include "mkdir.h" +#include "proc-cmdline.h" +#include "special.h" +#include "string-util.h" +#include "unit-name.h" +#include "util.h" + +static const char *arg_dest = "/tmp"; +static char *arg_resume_dev = NULL; + +static int parse_proc_cmdline_item(const char *key, const char *value) { + + if (streq(key, "resume") && value) { + free(arg_resume_dev); + arg_resume_dev = fstab_node_to_udev_node(value); + if (!arg_resume_dev) + return log_oom(); + } + + return 0; +} + +static int process_resume(void) { + _cleanup_free_ char *name = NULL, *lnk = NULL; + int r; + + if (!arg_resume_dev) + return 0; + + r = unit_name_from_path_instance("systemd-hibernate-resume", arg_resume_dev, ".service", &name); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + lnk = strjoin(arg_dest, "/" SPECIAL_SYSINIT_TARGET ".wants/", name, NULL); + if (!lnk) + return log_oom(); + + mkdir_parents_label(lnk, 0755); + if (symlink(SYSTEM_DATA_UNIT_PATH "/systemd-hibernate-resume@.service", lnk) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); + + return 0; +} + +int main(int argc, char *argv[]) { + int r = 0; + + if (argc > 1 && argc != 4) { + log_error("This program takes three or no arguments."); + return EXIT_FAILURE; + } + + if (argc > 1) + arg_dest = argv[1]; + + log_set_target(LOG_TARGET_SAFE); + log_parse_environment(); + log_open(); + + umask(0022); + + /* Don't even consider resuming outside of initramfs. */ + if (!in_initrd()) + return EXIT_SUCCESS; + + r = parse_proc_cmdline(parse_proc_cmdline_item); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + + r = process_resume(); + free(arg_resume_dev); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-hibernate-resume/hibernate-resume.c b/src/systemd-hibernate-resume/hibernate-resume.c new file mode 100644 index 0000000000..21df3c4461 --- /dev/null +++ b/src/systemd-hibernate-resume/hibernate-resume.c @@ -0,0 +1,82 @@ +/*** + This file is part of systemd. + + Copyright 2014 Ivan Shapovalov + + 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 +#include + +#include "alloc-util.h" +#include "fileio.h" +#include "log.h" +#include "util.h" + +int main(int argc, char *argv[]) { + struct stat st; + const char *device; + _cleanup_free_ char *major_minor = NULL; + int r; + + if (argc != 2) { + log_error("This program expects one argument."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + /* Refuse to run unless we are in an initrd() */ + if (!in_initrd()) + return EXIT_SUCCESS; + + device = argv[1]; + + if (stat(device, &st) < 0) { + log_error_errno(errno, "Failed to stat '%s': %m", device); + return EXIT_FAILURE; + } + + if (!S_ISBLK(st.st_mode)) { + log_error("Resume device '%s' is not a block device.", device); + return EXIT_FAILURE; + } + + if (asprintf(&major_minor, "%d:%d", major(st.st_rdev), minor(st.st_rdev)) < 0) { + log_oom(); + return EXIT_FAILURE; + } + + r = write_string_file("/sys/power/resume", major_minor, WRITE_STRING_FILE_CREATE); + if (r < 0) { + log_error_errno(r, "Failed to write '%s' to /sys/power/resume: %m", major_minor); + return EXIT_FAILURE; + } + + /* + * The write above shall not return. + * + * However, failed resume is a normal condition (may mean that there is + * no hibernation image). + */ + + log_info("Could not resume from '%s' (%s).", device, major_minor); + return EXIT_SUCCESS; +} diff --git a/src/systemd-hwdb/Makefile b/src/systemd-hwdb/Makefile new file mode 100644 index 0000000000..47f900aefc --- /dev/null +++ b/src/systemd-hwdb/Makefile @@ -0,0 +1,76 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_HWDB),) +INSTALL_DIRS += \ + $(sysconfdir)/udev/hwdb.d + +systemd_hwdb_SOURCES = \ + src/libsystemd/sd-hwdb/hwdb-internal.h \ + src/hwdb/hwdb.c + +systemd_hwdb_LDADD = \ + libshared.la + +bin_PROGRAMS += \ + systemd-hwdb + +dist_udevhwdb_DATA = \ + hwdb/20-pci-vendor-model.hwdb \ + hwdb/20-pci-classes.hwdb \ + hwdb/20-usb-vendor-model.hwdb \ + hwdb/20-usb-classes.hwdb \ + hwdb/20-sdio-vendor-model.hwdb \ + hwdb/20-sdio-classes.hwdb \ + hwdb/20-bluetooth-vendor-product.hwdb \ + hwdb/20-acpi-vendor.hwdb \ + hwdb/20-OUI.hwdb \ + hwdb/20-net-ifname.hwdb \ + hwdb/60-evdev.hwdb \ + hwdb/60-keyboard.hwdb \ + hwdb/70-mouse.hwdb \ + hwdb/70-pointingstick.hwdb + +SYSINIT_TARGET_WANTS += \ + systemd-hwdb-update.service + +# Update hwdb on installation. Do not bother if installing +# in DESTDIR, since this is likely for packaging purposes. +hwdb-update-hook: + -test -n "$(DESTDIR)" || $(bindir)/systemd-hwdb update + +INSTALL_DATA_HOOKS += \ + hwdb-update-hook + +hwdb-remove-hook: + -test -n "$(DESTDIR)" || rm -f /etc/udev/hwdb.bin +endif # ENABLE_HWDB + +EXTRA_DIST += \ + units/systemd-hwdb-update.service.in \ + hwdb/ids-update.pl \ + hwdb/sdio.ids + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-hwdb/hwdb.c b/src/systemd-hwdb/hwdb.c new file mode 100644 index 0000000000..1160dacdf1 --- /dev/null +++ b/src/systemd-hwdb/hwdb.c @@ -0,0 +1,739 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 +#include +#include +#include + +#include "alloc-util.h" +#include "conf-files.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "hwdb-internal.h" +#include "hwdb-util.h" +#include "mkdir.h" +#include "strbuf.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" +#include "verbs.h" + +/* + * Generic udev properties, key/value database based on modalias strings. + * Uses a Patricia/radix trie to index all matches for efficient lookup. + */ + +static const char *arg_hwdb_bin_dir = "/etc/udev"; +static const char *arg_root = ""; + +static const char * const conf_file_dirs[] = { + "/etc/udev/hwdb.d", + UDEVLIBEXECDIR "/hwdb.d", + NULL +}; + +/* in-memory trie objects */ +struct trie { + struct trie_node *root; + struct strbuf *strings; + + size_t nodes_count; + size_t children_count; + size_t values_count; +}; + +struct trie_node { + /* prefix, common part for all children of this node */ + size_t prefix_off; + + /* sorted array of pointers to children nodes */ + struct trie_child_entry *children; + uint8_t children_count; + + /* sorted array of key/value pairs */ + struct trie_value_entry *values; + size_t values_count; +}; + +/* children array item with char (0-255) index */ +struct trie_child_entry { + uint8_t c; + struct trie_node *child; +}; + +/* value array item with key/value pairs */ +struct trie_value_entry { + size_t key_off; + size_t value_off; +}; + +static int trie_children_cmp(const void *v1, const void *v2) { + const struct trie_child_entry *n1 = v1; + const struct trie_child_entry *n2 = v2; + + return n1->c - n2->c; +} + +static int node_add_child(struct trie *trie, struct trie_node *node, struct trie_node *node_child, uint8_t c) { + struct trie_child_entry *child; + + /* extend array, add new entry, sort for bisection */ + child = realloc(node->children, (node->children_count + 1) * sizeof(struct trie_child_entry)); + if (!child) + return -ENOMEM; + + node->children = child; + trie->children_count++; + node->children[node->children_count].c = c; + node->children[node->children_count].child = node_child; + node->children_count++; + qsort(node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp); + trie->nodes_count++; + + return 0; +} + +static struct trie_node *node_lookup(const struct trie_node *node, uint8_t c) { + struct trie_child_entry *child; + struct trie_child_entry search; + + search.c = c; + child = bsearch(&search, node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp); + if (child) + return child->child; + return NULL; +} + +static void trie_node_cleanup(struct trie_node *node) { + size_t i; + + for (i = 0; i < node->children_count; i++) + trie_node_cleanup(node->children[i].child); + free(node->children); + free(node->values); + free(node); +} + +static void trie_free(struct trie *trie) { + if (!trie) + return; + + if (trie->root) + trie_node_cleanup(trie->root); + + strbuf_cleanup(trie->strings); + free(trie); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct trie*, trie_free); + +static int trie_values_cmp(const void *v1, const void *v2, void *arg) { + const struct trie_value_entry *val1 = v1; + const struct trie_value_entry *val2 = v2; + struct trie *trie = arg; + + return strcmp(trie->strings->buf + val1->key_off, + trie->strings->buf + val2->key_off); +} + +static int trie_node_add_value(struct trie *trie, struct trie_node *node, + const char *key, const char *value) { + ssize_t k, v; + struct trie_value_entry *val; + + k = strbuf_add_string(trie->strings, key, strlen(key)); + if (k < 0) + return k; + v = strbuf_add_string(trie->strings, value, strlen(value)); + if (v < 0) + return v; + + if (node->values_count) { + struct trie_value_entry search = { + .key_off = k, + .value_off = v, + }; + + val = xbsearch_r(&search, node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie); + if (val) { + /* replace existing earlier key with new value */ + val->value_off = v; + return 0; + } + } + + /* extend array, add new entry, sort for bisection */ + val = realloc(node->values, (node->values_count + 1) * sizeof(struct trie_value_entry)); + if (!val) + return -ENOMEM; + trie->values_count++; + node->values = val; + node->values[node->values_count].key_off = k; + node->values[node->values_count].value_off = v; + node->values_count++; + qsort_r(node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie); + return 0; +} + +static int trie_insert(struct trie *trie, struct trie_node *node, const char *search, + const char *key, const char *value) { + size_t i = 0; + int err = 0; + + for (;;) { + size_t p; + uint8_t c; + struct trie_node *child; + + for (p = 0; (c = trie->strings->buf[node->prefix_off + p]); p++) { + _cleanup_free_ char *s = NULL; + ssize_t off; + _cleanup_free_ struct trie_node *new_child = NULL; + + if (c == search[i + p]) + continue; + + /* split node */ + new_child = new0(struct trie_node, 1); + if (!new_child) + return -ENOMEM; + + /* move values from parent to child */ + new_child->prefix_off = node->prefix_off + p+1; + new_child->children = node->children; + new_child->children_count = node->children_count; + new_child->values = node->values; + new_child->values_count = node->values_count; + + /* update parent; use strdup() because the source gets realloc()d */ + s = strndup(trie->strings->buf + node->prefix_off, p); + if (!s) + return -ENOMEM; + + off = strbuf_add_string(trie->strings, s, p); + if (off < 0) + return off; + + node->prefix_off = off; + node->children = NULL; + node->children_count = 0; + node->values = NULL; + node->values_count = 0; + err = node_add_child(trie, node, new_child, c); + if (err < 0) + return err; + + new_child = NULL; /* avoid cleanup */ + break; + } + i += p; + + c = search[i]; + if (c == '\0') + return trie_node_add_value(trie, node, key, value); + + child = node_lookup(node, c); + if (!child) { + ssize_t off; + + /* new child */ + child = new0(struct trie_node, 1); + if (!child) + return -ENOMEM; + + off = strbuf_add_string(trie->strings, search + i+1, strlen(search + i+1)); + if (off < 0) { + free(child); + return off; + } + + child->prefix_off = off; + err = node_add_child(trie, node, child, c); + if (err < 0) { + free(child); + return err; + } + + return trie_node_add_value(trie, child, key, value); + } + + node = child; + i++; + } +} + +struct trie_f { + FILE *f; + struct trie *trie; + uint64_t strings_off; + + uint64_t nodes_count; + uint64_t children_count; + uint64_t values_count; +}; + +/* calculate the storage space for the nodes, children arrays, value arrays */ +static void trie_store_nodes_size(struct trie_f *trie, struct trie_node *node) { + uint64_t i; + + for (i = 0; i < node->children_count; i++) + trie_store_nodes_size(trie, node->children[i].child); + + trie->strings_off += sizeof(struct trie_node_f); + for (i = 0; i < node->children_count; i++) + trie->strings_off += sizeof(struct trie_child_entry_f); + for (i = 0; i < node->values_count; i++) + trie->strings_off += sizeof(struct trie_value_entry_f); +} + +static int64_t trie_store_nodes(struct trie_f *trie, struct trie_node *node) { + uint64_t i; + struct trie_node_f n = { + .prefix_off = htole64(trie->strings_off + node->prefix_off), + .children_count = node->children_count, + .values_count = htole64(node->values_count), + }; + struct trie_child_entry_f *children = NULL; + int64_t node_off; + + if (node->children_count) { + children = new0(struct trie_child_entry_f, node->children_count); + if (!children) + return -ENOMEM; + } + + /* post-order recursion */ + for (i = 0; i < node->children_count; i++) { + int64_t child_off; + + child_off = trie_store_nodes(trie, node->children[i].child); + if (child_off < 0) { + free(children); + return child_off; + } + children[i].c = node->children[i].c; + children[i].child_off = htole64(child_off); + } + + /* write node */ + node_off = ftello(trie->f); + fwrite(&n, sizeof(struct trie_node_f), 1, trie->f); + trie->nodes_count++; + + /* append children array */ + if (node->children_count) { + fwrite(children, sizeof(struct trie_child_entry_f), node->children_count, trie->f); + trie->children_count += node->children_count; + free(children); + } + + /* append values array */ + for (i = 0; i < node->values_count; i++) { + struct trie_value_entry_f v = { + .key_off = htole64(trie->strings_off + node->values[i].key_off), + .value_off = htole64(trie->strings_off + node->values[i].value_off), + }; + + fwrite(&v, sizeof(struct trie_value_entry_f), 1, trie->f); + trie->values_count++; + } + + return node_off; +} + +static int trie_store(struct trie *trie, const char *filename) { + struct trie_f t = { + .trie = trie, + }; + _cleanup_free_ char *filename_tmp = NULL; + int64_t pos; + int64_t root_off; + int64_t size; + struct trie_header_f h = { + .signature = HWDB_SIG, + .tool_version = htole64(atoi(VERSION)), + .header_size = htole64(sizeof(struct trie_header_f)), + .node_size = htole64(sizeof(struct trie_node_f)), + .child_entry_size = htole64(sizeof(struct trie_child_entry_f)), + .value_entry_size = htole64(sizeof(struct trie_value_entry_f)), + }; + int err; + + /* calculate size of header, nodes, children entries, value entries */ + t.strings_off = sizeof(struct trie_header_f); + trie_store_nodes_size(&t, trie->root); + + err = fopen_temporary(filename , &t.f, &filename_tmp); + if (err < 0) + return err; + fchmod(fileno(t.f), 0444); + + /* write nodes */ + err = fseeko(t.f, sizeof(struct trie_header_f), SEEK_SET); + if (err < 0) { + fclose(t.f); + unlink_noerrno(filename_tmp); + return -errno; + } + root_off = trie_store_nodes(&t, trie->root); + h.nodes_root_off = htole64(root_off); + pos = ftello(t.f); + h.nodes_len = htole64(pos - sizeof(struct trie_header_f)); + + /* write string buffer */ + fwrite(trie->strings->buf, trie->strings->len, 1, t.f); + h.strings_len = htole64(trie->strings->len); + + /* write header */ + size = ftello(t.f); + h.file_size = htole64(size); + err = fseeko(t.f, 0, SEEK_SET); + if (err < 0) { + fclose(t.f); + unlink_noerrno(filename_tmp); + return -errno; + } + fwrite(&h, sizeof(struct trie_header_f), 1, t.f); + err = ferror(t.f); + if (err) + err = -errno; + fclose(t.f); + if (err < 0 || rename(filename_tmp, filename) < 0) { + unlink_noerrno(filename_tmp); + return err < 0 ? err : -errno; + } + + log_debug("=== trie on-disk ==="); + log_debug("size: %8"PRIi64" bytes", size); + log_debug("header: %8zu bytes", sizeof(struct trie_header_f)); + log_debug("nodes: %8"PRIu64" bytes (%8"PRIu64")", + t.nodes_count * sizeof(struct trie_node_f), t.nodes_count); + log_debug("child pointers: %8"PRIu64" bytes (%8"PRIu64")", + t.children_count * sizeof(struct trie_child_entry_f), t.children_count); + log_debug("value pointers: %8"PRIu64" bytes (%8"PRIu64")", + t.values_count * sizeof(struct trie_value_entry_f), t.values_count); + log_debug("string store: %8zu bytes", trie->strings->len); + log_debug("strings start: %8"PRIu64, t.strings_off); + + return 0; +} + +static int insert_data(struct trie *trie, char **match_list, char *line, const char *filename) { + char *value, **entry; + + value = strchr(line, '='); + if (!value) { + log_error("Error, key/value pair expected but got '%s' in '%s':", line, filename); + return -EINVAL; + } + + value[0] = '\0'; + value++; + + /* libudev requires properties to start with a space */ + while (isblank(line[0]) && isblank(line[1])) + line++; + + if (line[0] == '\0' || value[0] == '\0') { + log_error("Error, empty key or value '%s' in '%s':", line, filename); + return -EINVAL; + } + + STRV_FOREACH(entry, match_list) + trie_insert(trie, trie->root, *entry, line, value); + + return 0; +} + +static int import_file(struct trie *trie, const char *filename) { + enum { + HW_NONE, + HW_MATCH, + HW_DATA, + } state = HW_NONE; + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + _cleanup_strv_free_ char **match_list = NULL; + char *match = NULL; + int r; + + f = fopen(filename, "re"); + if (!f) + return -errno; + + while (fgets(line, sizeof(line), f)) { + size_t len; + char *pos; + + /* comment line */ + if (line[0] == '#') + continue; + + /* strip trailing comment */ + pos = strchr(line, '#'); + if (pos) + pos[0] = '\0'; + + /* strip trailing whitespace */ + len = strlen(line); + while (len > 0 && isspace(line[len-1])) + len--; + line[len] = '\0'; + + switch (state) { + case HW_NONE: + if (len == 0) + break; + + if (line[0] == ' ') { + log_error("Error, MATCH expected but got '%s' in '%s':", line, filename); + break; + } + + /* start of record, first match */ + state = HW_MATCH; + + match = strdup(line); + if (!match) + return -ENOMEM; + + r = strv_consume(&match_list, match); + if (r < 0) + return r; + + break; + + case HW_MATCH: + if (len == 0) { + log_error("Error, DATA expected but got empty line in '%s':", filename); + state = HW_NONE; + strv_clear(match_list); + break; + } + + /* another match */ + if (line[0] != ' ') { + match = strdup(line); + if (!match) + return -ENOMEM; + + r = strv_consume(&match_list, match); + if (r < 0) + return r; + + break; + } + + /* first data */ + state = HW_DATA; + insert_data(trie, match_list, line, filename); + break; + + case HW_DATA: + /* end of record */ + if (len == 0) { + state = HW_NONE; + strv_clear(match_list); + break; + } + + if (line[0] != ' ') { + log_error("Error, DATA expected but got '%s' in '%s':", line, filename); + state = HW_NONE; + strv_clear(match_list); + break; + } + + insert_data(trie, match_list, line, filename); + break; + }; + } + + return 0; +} + +static int hwdb_query(int argc, char *argv[], void *userdata) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + const char *key, *value; + const char *modalias; + int r; + + assert(argc >= 2); + assert(argv); + + modalias = argv[1]; + + r = sd_hwdb_new(&hwdb); + if (r < 0) + return r; + + SD_HWDB_FOREACH_PROPERTY(hwdb, modalias, key, value) + printf("%s=%s\n", key, value); + + return 0; +} + +static int hwdb_update(int argc, char *argv[], void *userdata) { + _cleanup_free_ char *hwdb_bin = NULL; + _cleanup_(trie_freep) struct trie *trie = NULL; + char **files, **f; + int r; + + trie = new0(struct trie, 1); + if (!trie) + return -ENOMEM; + + /* string store */ + trie->strings = strbuf_new(); + if (!trie->strings) + return -ENOMEM; + + /* index */ + trie->root = new0(struct trie_node, 1); + if (!trie->root) + return -ENOMEM; + + trie->nodes_count++; + + r = conf_files_list_strv(&files, ".hwdb", arg_root, conf_file_dirs); + if (r < 0) + return log_error_errno(r, "failed to enumerate hwdb files: %m"); + + STRV_FOREACH(f, files) { + log_debug("reading file '%s'", *f); + import_file(trie, *f); + } + strv_free(files); + + strbuf_complete(trie->strings); + + log_debug("=== trie in-memory ==="); + log_debug("nodes: %8zu bytes (%8zu)", + trie->nodes_count * sizeof(struct trie_node), trie->nodes_count); + log_debug("children arrays: %8zu bytes (%8zu)", + trie->children_count * sizeof(struct trie_child_entry), trie->children_count); + log_debug("values arrays: %8zu bytes (%8zu)", + trie->values_count * sizeof(struct trie_value_entry), trie->values_count); + log_debug("strings: %8zu bytes", + trie->strings->len); + log_debug("strings incoming: %8zu bytes (%8zu)", + trie->strings->in_len, trie->strings->in_count); + log_debug("strings dedup'ed: %8zu bytes (%8zu)", + trie->strings->dedup_len, trie->strings->dedup_count); + + hwdb_bin = strjoin(arg_root, "/", arg_hwdb_bin_dir, "/hwdb.bin", NULL); + if (!hwdb_bin) + return -ENOMEM; + + mkdir_parents(hwdb_bin, 0755); + r = trie_store(trie, hwdb_bin); + if (r < 0) + return log_error_errno(r, "Failure writing database %s: %m", hwdb_bin); + + return 0; +} + +static void help(void) { + printf("Usage: %s OPTIONS COMMAND\n\n" + "Update or query the hardware database.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --usr Generate in " UDEVLIBEXECDIR " instead of /etc/udev\n" + " -r --root=PATH Alternative root path in the filesystem\n\n" + "Commands:\n" + " update Update the hwdb database\n" + " query MODALIAS Query database and print result\n", + program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_USR, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "usr", no_argument, NULL, ARG_USR }, + { "root", required_argument, NULL, 'r' }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "ut:r:h", options, NULL)) >= 0) { + switch(c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_USR: + arg_hwdb_bin_dir = UDEVLIBEXECDIR; + break; + + case 'r': + arg_root = optarg; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unknown option"); + } + } + + return 1; +} + +static int hwdb_main(int argc, char *argv[]) { + const Verb verbs[] = { + { "update", 1, 1, 0, hwdb_update }, + { "query", 2, 2, 0, hwdb_query }, + {}, + }; + + return dispatch_verb(argc, argv, verbs, NULL); +} + +int main (int argc, char *argv[]) { + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = hwdb_main(argc, argv); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-notify/Makefile b/src/systemd-notify/Makefile new file mode 100644 index 0000000000..c5402385b9 --- /dev/null +++ b/src/systemd-notify/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_notify_SOURCES = \ + src/notify/notify.c + +systemd_notify_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-notify/notify.c b/src/systemd-notify/notify.c new file mode 100644 index 0000000000..b18fb5938f --- /dev/null +++ b/src/systemd-notify/notify.c @@ -0,0 +1,203 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "env-util.h" +#include "formats-util.h" +#include "log.h" +#include "parse-util.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +static bool arg_ready = false; +static pid_t arg_pid = 0; +static const char *arg_status = NULL; +static bool arg_booted = false; + +static void help(void) { + printf("%s [OPTIONS...] [VARIABLE=VALUE...]\n\n" + "Notify the init system about service status updates.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --ready Inform the init system about service start-up completion\n" + " --pid[=PID] Set main pid of daemon\n" + " --status=TEXT Set status text\n" + " --booted Check if the system was booted up with systemd\n", + program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_READY = 0x100, + ARG_VERSION, + ARG_PID, + ARG_STATUS, + ARG_BOOTED, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "ready", no_argument, NULL, ARG_READY }, + { "pid", optional_argument, NULL, ARG_PID }, + { "status", required_argument, NULL, ARG_STATUS }, + { "booted", no_argument, NULL, ARG_BOOTED }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_READY: + arg_ready = true; + break; + + case ARG_PID: + + if (optarg) { + if (parse_pid(optarg, &arg_pid) < 0) { + log_error("Failed to parse PID %s.", optarg); + return -EINVAL; + } + } else + arg_pid = getppid(); + + break; + + case ARG_STATUS: + arg_status = optarg; + break; + + case ARG_BOOTED: + arg_booted = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + } + + if (optind >= argc && + !arg_ready && + !arg_status && + !arg_pid && + !arg_booted) { + help(); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char* argv[]) { + _cleanup_free_ char *status = NULL, *cpid = NULL, *n = NULL; + _cleanup_strv_free_ char **final_env = NULL; + char* our_env[4]; + unsigned i = 0; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (arg_booted) + return sd_booted() <= 0; + + if (arg_ready) + our_env[i++] = (char*) "READY=1"; + + if (arg_status) { + status = strappend("STATUS=", arg_status); + if (!status) { + r = log_oom(); + goto finish; + } + + our_env[i++] = status; + } + + if (arg_pid > 0) { + if (asprintf(&cpid, "MAINPID="PID_FMT, arg_pid) < 0) { + r = log_oom(); + goto finish; + } + + our_env[i++] = cpid; + } + + our_env[i++] = NULL; + + final_env = strv_env_merge(2, our_env, argv + optind); + if (!final_env) { + r = log_oom(); + goto finish; + } + + if (strv_length(final_env) <= 0) { + r = 0; + goto finish; + } + + n = strv_join(final_env, "\n"); + if (!n) { + r = log_oom(); + goto finish; + } + + r = sd_pid_notify(arg_pid ? arg_pid : getppid(), false, n); + if (r < 0) { + log_error_errno(r, "Failed to notify init system: %m"); + goto finish; + } else if (r == 0) { + log_error("No status data could be sent: $NOTIFY_SOCKET was not set"); + r = -EOPNOTSUPP; + } + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-nspawn/.gitignore b/src/systemd-nspawn/.gitignore new file mode 100644 index 0000000000..85c81fff24 --- /dev/null +++ b/src/systemd-nspawn/.gitignore @@ -0,0 +1 @@ +/nspawn-gperf.c diff --git a/src/systemd-nspawn/Makefile b/src/systemd-nspawn/Makefile new file mode 100644 index 0000000000..e66a68c898 --- /dev/null +++ b/src/systemd-nspawn/Makefile @@ -0,0 +1,84 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_nspawn_SOURCES = \ + src/nspawn/nspawn.c \ + src/nspawn/nspawn-settings.c \ + src/nspawn/nspawn-settings.h \ + src/nspawn/nspawn-mount.c \ + src/nspawn/nspawn-mount.h \ + src/nspawn/nspawn-network.c \ + src/nspawn/nspawn-network.h \ + src/nspawn/nspawn-expose-ports.c \ + src/nspawn/nspawn-expose-ports.h \ + src/nspawn/nspawn-cgroup.c \ + src/nspawn/nspawn-cgroup.h \ + src/nspawn/nspawn-register.c \ + src/nspawn/nspawn-register.h \ + src/nspawn/nspawn-setuid.c \ + src/nspawn/nspawn-setuid.h \ + src/nspawn/nspawn-stub-pid1.c \ + src/nspawn/nspawn-stub-pid1.h \ + src/nspawn/nspawn-patch-uid.c \ + src/nspawn/nspawn-patch-uid.h \ + src/core/mount-setup.c \ + src/core/mount-setup.h \ + src/core/loopback-setup.c \ + src/core/loopback-setup.h \ + src/core/machine-id-setup.c \ + src/core/machine-id-setup.h + +nodist_systemd_nspawn_SOURCES = \ + src/nspawn/nspawn-gperf.c + +gperf_gperf_sources += \ + src/nspawn/nspawn-gperf.gperf + +systemd_nspawn_CFLAGS = \ + $(AM_CFLAGS) \ + $(BLKID_CFLAGS) \ + $(SECCOMP_CFLAGS) + +systemd_nspawn_LDADD = \ + libshared.la \ + $(BLKID_LIBS) + +ifneq ($(HAVE_LIBIPTC),) +systemd_nspawn_LDADD += \ + libfirewall.la +endif # HAVE_LIBIPTC + +test_patch_uid_SOURCES = \ + src/nspawn/nspawn-patch-uid.c \ + src/nspawn/nspawn-patch-uid.h \ + src/nspawn/test-patch-uid.c + +test_patch_uid_LDADD = \ + libshared.la + +manual_tests += \ + test-patch-uid + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-nspawn/nspawn-cgroup.c b/src/systemd-nspawn/nspawn-cgroup.c new file mode 100644 index 0000000000..f50f1ad6c2 --- /dev/null +++ b/src/systemd-nspawn/nspawn-cgroup.c @@ -0,0 +1,162 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 "alloc-util.h" +#include "cgroup-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "mkdir.h" +#include "nspawn-cgroup.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +int chown_cgroup(pid_t pid, uid_t uid_shift) { + _cleanup_free_ char *path = NULL, *fs = NULL; + _cleanup_close_ int fd = -1; + const char *fn; + int r; + + r = cg_pid_get_path(NULL, pid, &path); + if (r < 0) + return log_error_errno(r, "Failed to get container cgroup path: %m"); + + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs); + if (r < 0) + return log_error_errno(r, "Failed to get file system path for container cgroup: %m"); + + fd = open(fs, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", fs); + + FOREACH_STRING(fn, + ".", + "tasks", + "notify_on_release", + "cgroup.procs", + "cgroup.events", + "cgroup.clone_children", + "cgroup.controllers", + "cgroup.subtree_control") + if (fchownat(fd, fn, uid_shift, uid_shift, 0) < 0) + log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno, + "Failed to chown() cgroup file %s, ignoring: %m", fn); + + return 0; +} + +int sync_cgroup(pid_t pid, bool unified_requested) { + _cleanup_free_ char *cgroup = NULL; + char tree[] = "/tmp/unifiedXXXXXX", pid_string[DECIMAL_STR_MAX(pid) + 1]; + bool undo_mount = false; + const char *fn; + int unified, r; + + unified = cg_unified(); + if (unified < 0) + return log_error_errno(unified, "Failed to determine whether the unified hierarchy is used: %m"); + + if ((unified > 0) == unified_requested) + return 0; + + /* When the host uses the legacy cgroup setup, but the + * container shall use the unified hierarchy, let's make sure + * we copy the path from the name=systemd hierarchy into the + * unified hierarchy. Similar for the reverse situation. */ + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cgroup); + if (r < 0) + return log_error_errno(r, "Failed to get control group of " PID_FMT ": %m", pid); + + /* In order to access the unified hierarchy we need to mount it */ + if (!mkdtemp(tree)) + return log_error_errno(errno, "Failed to generate temporary mount point for unified hierarchy: %m"); + + if (unified) + r = mount("cgroup", tree, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, "none,name=systemd,xattr"); + else + r = mount("cgroup", tree, "cgroup2", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL); + if (r < 0) { + r = log_error_errno(errno, "Failed to mount unified hierarchy: %m"); + goto finish; + } + + undo_mount = true; + + fn = strjoina(tree, cgroup, "/cgroup.procs"); + (void) mkdir_parents(fn, 0755); + + sprintf(pid_string, PID_FMT, pid); + r = write_string_file(fn, pid_string, 0); + if (r < 0) + log_error_errno(r, "Failed to move process: %m"); + +finish: + if (undo_mount) + (void) umount(tree); + + (void) rmdir(tree); + return r; +} + +int create_subcgroup(pid_t pid, bool unified_requested) { + _cleanup_free_ char *cgroup = NULL; + const char *child; + int unified, r; + CGroupMask supported; + + /* In the unified hierarchy inner nodes may only only contain + * subgroups, but not processes. Hence, if we running in the + * unified hierarchy and the container does the same, and we + * did not create a scope unit for the container move us and + * the container into two separate subcgroups. */ + + if (!unified_requested) + return 0; + + unified = cg_unified(); + if (unified < 0) + return log_error_errno(unified, "Failed to determine whether the unified hierarchy is used: %m"); + if (unified == 0) + return 0; + + r = cg_mask_supported(&supported); + if (r < 0) + return log_error_errno(r, "Failed to determine supported controllers: %m"); + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &cgroup); + if (r < 0) + return log_error_errno(r, "Failed to get our control group: %m"); + + child = strjoina(cgroup, "/payload"); + r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, child, pid); + if (r < 0) + return log_error_errno(r, "Failed to create %s subcgroup: %m", child); + + child = strjoina(cgroup, "/supervisor"); + r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, child, 0); + if (r < 0) + return log_error_errno(r, "Failed to create %s subcgroup: %m", child); + + /* Try to enable as many controllers as possible for the new payload. */ + (void) cg_enable_everywhere(supported, supported, cgroup); + return 0; +} diff --git a/src/systemd-nspawn/nspawn-cgroup.h b/src/systemd-nspawn/nspawn-cgroup.h new file mode 100644 index 0000000000..1ff35a299a --- /dev/null +++ b/src/systemd-nspawn/nspawn-cgroup.h @@ -0,0 +1,27 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 + +int chown_cgroup(pid_t pid, uid_t uid_shift); +int sync_cgroup(pid_t pid, bool unified_requested); +int create_subcgroup(pid_t pid, bool unified_requested); diff --git a/src/systemd-nspawn/nspawn-expose-ports.c b/src/systemd-nspawn/nspawn-expose-ports.c new file mode 100644 index 0000000000..8122a14f7b --- /dev/null +++ b/src/systemd-nspawn/nspawn-expose-ports.c @@ -0,0 +1,245 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 "alloc-util.h" +#include "fd-util.h" +#include "firewall-util.h" +#include "in-addr-util.h" +#include "local-addresses.h" +#include "netlink-util.h" +#include "nspawn-expose-ports.h" +#include "parse-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "util.h" + +int expose_port_parse(ExposePort **l, const char *s) { + + const char *split, *e; + uint16_t container_port, host_port; + int protocol; + ExposePort *p; + int r; + + assert(l); + assert(s); + + if ((e = startswith(s, "tcp:"))) + protocol = IPPROTO_TCP; + else if ((e = startswith(s, "udp:"))) + protocol = IPPROTO_UDP; + else { + e = s; + protocol = IPPROTO_TCP; + } + + split = strchr(e, ':'); + if (split) { + char v[split - e + 1]; + + memcpy(v, e, split - e); + v[split - e] = 0; + + r = safe_atou16(v, &host_port); + if (r < 0 || host_port <= 0) + return -EINVAL; + + r = safe_atou16(split + 1, &container_port); + } else { + r = safe_atou16(e, &container_port); + host_port = container_port; + } + + if (r < 0 || container_port <= 0) + return -EINVAL; + + LIST_FOREACH(ports, p, *l) + if (p->protocol == protocol && p->host_port == host_port) + return -EEXIST; + + p = new(ExposePort, 1); + if (!p) + return -ENOMEM; + + p->protocol = protocol; + p->host_port = host_port; + p->container_port = container_port; + + LIST_PREPEND(ports, *l, p); + + return 0; +} + +void expose_port_free_all(ExposePort *p) { + + while (p) { + ExposePort *q = p; + LIST_REMOVE(ports, p, q); + free(q); + } +} + +int expose_port_flush(ExposePort* l, union in_addr_union *exposed) { + ExposePort *p; + int r, af = AF_INET; + + assert(exposed); + + if (!l) + return 0; + + if (in_addr_is_null(af, exposed)) + return 0; + + log_debug("Lost IP address."); + + LIST_FOREACH(ports, p, l) { + r = fw_add_local_dnat(false, + af, + p->protocol, + NULL, + NULL, 0, + NULL, 0, + p->host_port, + exposed, + p->container_port, + NULL); + if (r < 0) + log_warning_errno(r, "Failed to modify firewall: %m"); + } + + *exposed = IN_ADDR_NULL; + return 0; +} + +int expose_port_execute(sd_netlink *rtnl, ExposePort *l, union in_addr_union *exposed) { + _cleanup_free_ struct local_address *addresses = NULL; + _cleanup_free_ char *pretty = NULL; + union in_addr_union new_exposed; + ExposePort *p; + bool add; + int af = AF_INET, r; + + assert(exposed); + + /* Invoked each time an address is added or removed inside the + * container */ + + if (!l) + return 0; + + r = local_addresses(rtnl, 0, af, &addresses); + if (r < 0) + return log_error_errno(r, "Failed to enumerate local addresses: %m"); + + add = r > 0 && + addresses[0].family == af && + addresses[0].scope < RT_SCOPE_LINK; + + if (!add) + return expose_port_flush(l, exposed); + + new_exposed = addresses[0].address; + if (in_addr_equal(af, exposed, &new_exposed)) + return 0; + + in_addr_to_string(af, &new_exposed, &pretty); + log_debug("New container IP is %s.", strna(pretty)); + + LIST_FOREACH(ports, p, l) { + + r = fw_add_local_dnat(true, + af, + p->protocol, + NULL, + NULL, 0, + NULL, 0, + p->host_port, + &new_exposed, + p->container_port, + in_addr_is_null(af, exposed) ? NULL : exposed); + if (r < 0) + log_warning_errno(r, "Failed to modify firewall: %m"); + } + + *exposed = new_exposed; + return 0; +} + +int expose_port_send_rtnl(int send_fd) { + _cleanup_close_ int fd = -1; + int r; + + assert(send_fd >= 0); + + fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_ROUTE); + if (fd < 0) + return log_error_errno(errno, "Failed to allocate container netlink: %m"); + + /* Store away the fd in the socket, so that it stays open as + * long as we run the child */ + r = send_one_fd(send_fd, fd, 0); + if (r < 0) + return log_error_errno(r, "Failed to send netlink fd: %m"); + + return 0; +} + +int expose_port_watch_rtnl( + sd_event *event, + int recv_fd, + sd_netlink_message_handler_t handler, + union in_addr_union *exposed, + sd_netlink **ret) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + int fd, r; + + assert(event); + assert(recv_fd >= 0); + assert(ret); + + fd = receive_one_fd(recv_fd, 0); + if (fd < 0) + return log_error_errno(fd, "Failed to recv netlink fd: %m"); + + r = sd_netlink_open_fd(&rtnl, fd); + if (r < 0) { + safe_close(fd); + return log_error_errno(r, "Failed to create rtnl object: %m"); + } + + r = sd_netlink_add_match(rtnl, RTM_NEWADDR, handler, exposed); + if (r < 0) + return log_error_errno(r, "Failed to subscribe to RTM_NEWADDR messages: %m"); + + r = sd_netlink_add_match(rtnl, RTM_DELADDR, handler, exposed); + if (r < 0) + return log_error_errno(r, "Failed to subscribe to RTM_DELADDR messages: %m"); + + r = sd_netlink_attach_event(rtnl, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to add to even loop: %m"); + + *ret = rtnl; + rtnl = NULL; + + return 0; +} diff --git a/src/systemd-nspawn/nspawn-expose-ports.h b/src/systemd-nspawn/nspawn-expose-ports.h new file mode 100644 index 0000000000..0e9f8f7e88 --- /dev/null +++ b/src/systemd-nspawn/nspawn-expose-ports.h @@ -0,0 +1,44 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 +#include + +#include "in-addr-util.h" +#include "list.h" + +typedef struct ExposePort { + int protocol; + uint16_t host_port; + uint16_t container_port; + LIST_FIELDS(struct ExposePort, ports); +} ExposePort; + +void expose_port_free_all(ExposePort *p); +int expose_port_parse(ExposePort **l, const char *s); + +int expose_port_watch_rtnl(sd_event *event, int recv_fd, sd_netlink_message_handler_t handler, union in_addr_union *exposed, sd_netlink **ret); +int expose_port_send_rtnl(int send_fd); + +int expose_port_execute(sd_netlink *rtnl, ExposePort *l, union in_addr_union *exposed); +int expose_port_flush(ExposePort* l, union in_addr_union *exposed); diff --git a/src/systemd-nspawn/nspawn-gperf.gperf b/src/systemd-nspawn/nspawn-gperf.gperf new file mode 100644 index 0000000000..2b5d452662 --- /dev/null +++ b/src/systemd-nspawn/nspawn-gperf.gperf @@ -0,0 +1,44 @@ +%{ +#include +#include "conf-parser.h" +#include "nspawn-settings.h" +#include "nspawn-expose-ports.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name nspawn_gperf_hash +%define lookup-function-name nspawn_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +Exec.Boot, config_parse_boot, 0, 0 +Exec.ProcessTwo, config_parse_pid2, 0, 0 +Exec.Parameters, config_parse_strv, 0, offsetof(Settings, parameters) +Exec.Environment, config_parse_strv, 0, offsetof(Settings, environment) +Exec.User, config_parse_string, 0, offsetof(Settings, user) +Exec.Capability, config_parse_capability, 0, offsetof(Settings, capability) +Exec.DropCapability, config_parse_capability, 0, offsetof(Settings, drop_capability) +Exec.KillSignal, config_parse_signal, 0, offsetof(Settings, kill_signal) +Exec.Personality, config_parse_personality, 0, offsetof(Settings, personality) +Exec.MachineID, config_parse_id128, 0, offsetof(Settings, machine_id) +Exec.WorkingDirectory, config_parse_path, 0, offsetof(Settings, working_directory) +Exec.PrivateUsers, config_parse_private_users, 0, 0 +Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only) +Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode) +Files.Bind, config_parse_bind, 0, 0 +Files.BindReadOnly, config_parse_bind, 1, 0 +Files.TemporaryFileSystem, config_parse_tmpfs, 0, 0 +Files.PrivateUsersChown, config_parse_tristate, 0, offsetof(Settings, userns_chown) +Network.Private, config_parse_tristate, 0, offsetof(Settings, private_network) +Network.Interface, config_parse_strv, 0, offsetof(Settings, network_interfaces) +Network.MACVLAN, config_parse_strv, 0, offsetof(Settings, network_macvlan) +Network.IPVLAN, config_parse_strv, 0, offsetof(Settings, network_ipvlan) +Network.VirtualEthernet, config_parse_tristate, 0, offsetof(Settings, network_veth) +Network.VirtualEthernetExtra, config_parse_veth_extra, 0, 0 +Network.Bridge, config_parse_ifname, 0, offsetof(Settings, network_bridge) +Network.Zone, config_parse_network_zone, 0, 0 +Network.Port, config_parse_expose_port, 0, 0 diff --git a/src/systemd-nspawn/nspawn-mount.c b/src/systemd-nspawn/nspawn-mount.c new file mode 100644 index 0000000000..8e2d2d543c --- /dev/null +++ b/src/systemd-nspawn/nspawn-mount.c @@ -0,0 +1,943 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 + +#include "alloc-util.h" +#include "cgroup-util.h" +#include "escape.h" +#include "fs-util.h" +#include "label.h" +#include "mkdir.h" +#include "mount-util.h" +#include "nspawn-mount.h" +#include "parse-util.h" +#include "path-util.h" +#include "rm-rf.h" +#include "set.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" +#include "util.h" + +CustomMount* custom_mount_add(CustomMount **l, unsigned *n, CustomMountType t) { + CustomMount *c, *ret; + + assert(l); + assert(n); + assert(t >= 0); + assert(t < _CUSTOM_MOUNT_TYPE_MAX); + + c = realloc(*l, (*n + 1) * sizeof(CustomMount)); + if (!c) + return NULL; + + *l = c; + ret = *l + *n; + (*n)++; + + *ret = (CustomMount) { .type = t }; + + return ret; +} + +void custom_mount_free_all(CustomMount *l, unsigned n) { + unsigned i; + + for (i = 0; i < n; i++) { + CustomMount *m = l + i; + + free(m->source); + free(m->destination); + free(m->options); + + if (m->work_dir) { + (void) rm_rf(m->work_dir, REMOVE_ROOT|REMOVE_PHYSICAL); + free(m->work_dir); + } + + strv_free(m->lower); + } + + free(l); +} + +int custom_mount_compare(const void *a, const void *b) { + const CustomMount *x = a, *y = b; + int r; + + r = path_compare(x->destination, y->destination); + if (r != 0) + return r; + + if (x->type < y->type) + return -1; + if (x->type > y->type) + return 1; + + return 0; +} + +int bind_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only) { + _cleanup_free_ char *source = NULL, *destination = NULL, *opts = NULL; + const char *p = s; + CustomMount *m; + int r; + + assert(l); + assert(n); + + r = extract_many_words(&p, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination, NULL); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + if (r == 1) { + destination = strdup(source); + if (!destination) + return -ENOMEM; + } + + if (r == 2 && !isempty(p)) { + opts = strdup(p); + if (!opts) + return -ENOMEM; + } + + if (!path_is_absolute(source)) + return -EINVAL; + + if (!path_is_absolute(destination)) + return -EINVAL; + + m = custom_mount_add(l, n, CUSTOM_MOUNT_BIND); + if (!m) + return log_oom(); + + m->source = source; + m->destination = destination; + m->read_only = read_only; + m->options = opts; + + source = destination = opts = NULL; + return 0; +} + +int tmpfs_mount_parse(CustomMount **l, unsigned *n, const char *s) { + _cleanup_free_ char *path = NULL, *opts = NULL; + const char *p = s; + CustomMount *m; + int r; + + assert(l); + assert(n); + assert(s); + + r = extract_first_word(&p, &path, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + if (isempty(p)) + opts = strdup("mode=0755"); + else + opts = strdup(p); + if (!opts) + return -ENOMEM; + + if (!path_is_absolute(path)) + return -EINVAL; + + m = custom_mount_add(l, n, CUSTOM_MOUNT_TMPFS); + if (!m) + return -ENOMEM; + + m->destination = path; + m->options = opts; + + path = opts = NULL; + return 0; +} + +static int tmpfs_patch_options( + const char *options, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context, + char **ret) { + + char *buf = NULL; + + if (userns && uid_shift != 0) { + assert(uid_shift != UID_INVALID); + + if (options) + (void) asprintf(&buf, "%s,uid=" UID_FMT ",gid=" UID_FMT, options, uid_shift, uid_shift); + else + (void) asprintf(&buf, "uid=" UID_FMT ",gid=" UID_FMT, uid_shift, uid_shift); + if (!buf) + return -ENOMEM; + + options = buf; + } + +#ifdef HAVE_SELINUX + if (selinux_apifs_context) { + char *t; + + if (options) + t = strjoin(options, ",context=\"", selinux_apifs_context, "\"", NULL); + else + t = strjoin("context=\"", selinux_apifs_context, "\"", NULL); + if (!t) { + free(buf); + return -ENOMEM; + } + + free(buf); + buf = t; + } +#endif + + *ret = buf; + return !!buf; +} + +int mount_sysfs(const char *dest) { + const char *full, *top, *x; + int r; + + top = prefix_roota(dest, "/sys"); + r = path_check_fstype(top, SYSFS_MAGIC); + if (r < 0) + return log_error_errno(r, "Failed to determine filesystem type of %s: %m", top); + /* /sys might already be mounted as sysfs by the outer child in the + * !netns case. In this case, it's all good. Don't touch it because we + * don't have the right to do so, see https://github.com/systemd/systemd/issues/1555. + */ + if (r > 0) + return 0; + + full = prefix_roota(top, "/full"); + + (void) mkdir(full, 0755); + + if (mount("sysfs", full, "sysfs", MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) < 0) + return log_error_errno(errno, "Failed to mount sysfs to %s: %m", full); + + FOREACH_STRING(x, "block", "bus", "class", "dev", "devices", "kernel") { + _cleanup_free_ char *from = NULL, *to = NULL; + + from = prefix_root(full, x); + if (!from) + return log_oom(); + + to = prefix_root(top, x); + if (!to) + return log_oom(); + + (void) mkdir(to, 0755); + + if (mount(from, to, NULL, MS_BIND, NULL) < 0) + return log_error_errno(errno, "Failed to mount /sys/%s into place: %m", x); + + if (mount(NULL, to, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, NULL) < 0) + return log_error_errno(errno, "Failed to mount /sys/%s read-only: %m", x); + } + + if (umount(full) < 0) + return log_error_errno(errno, "Failed to unmount %s: %m", full); + + if (rmdir(full) < 0) + return log_error_errno(errno, "Failed to remove %s: %m", full); + + x = prefix_roota(top, "/fs/kdbus"); + (void) mkdir(x, 0755); + + if (mount(NULL, top, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, NULL) < 0) + return log_error_errno(errno, "Failed to make %s read-only: %m", top); + + return 0; +} + +int mount_all(const char *dest, + bool use_userns, bool in_userns, + bool use_netns, + uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context) { + + typedef struct MountPoint { + const char *what; + const char *where; + const char *type; + const char *options; + unsigned long flags; + bool fatal; + bool in_userns; + bool use_netns; + } MountPoint; + + static const MountPoint mount_table[] = { + { "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, true, true, false }, + { "/proc/sys", "/proc/sys", NULL, NULL, MS_BIND, true, true, false }, /* Bind mount first */ + { NULL, "/proc/sys", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, true, true, false }, /* Then, make it r/o */ + { "tmpfs", "/sys", "tmpfs", "mode=755", MS_NOSUID|MS_NOEXEC|MS_NODEV, true, false, true }, + { "sysfs", "/sys", "sysfs", NULL, MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, true, false, false }, + { "tmpfs", "/dev", "tmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME, true, false, false }, + { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true, false, false }, + { "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true, false, false }, + { "tmpfs", "/tmp", "tmpfs", "mode=1777", MS_STRICTATIME, true, false, false }, +#ifdef HAVE_SELINUX + { "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND, false, false, false }, /* Bind mount first */ + { NULL, "/sys/fs/selinux", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, false, false, false }, /* Then, make it r/o */ +#endif + }; + + unsigned k; + int r; + + for (k = 0; k < ELEMENTSOF(mount_table); k++) { + _cleanup_free_ char *where = NULL, *options = NULL; + const char *o; + + if (in_userns != mount_table[k].in_userns) + continue; + + if (!use_netns && mount_table[k].use_netns) + continue; + + where = prefix_root(dest, mount_table[k].where); + if (!where) + return log_oom(); + + r = path_is_mount_point(where, AT_SYMLINK_FOLLOW); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to detect whether %s is a mount point: %m", where); + + /* Skip this entry if it is not a remount. */ + if (mount_table[k].what && r > 0) + continue; + + r = mkdir_p(where, 0755); + if (r < 0) { + if (mount_table[k].fatal) + return log_error_errno(r, "Failed to create directory %s: %m", where); + + log_warning_errno(r, "Failed to create directory %s: %m", where); + continue; + } + + o = mount_table[k].options; + if (streq_ptr(mount_table[k].type, "tmpfs")) { + r = tmpfs_patch_options(o, use_userns, uid_shift, uid_range, selinux_apifs_context, &options); + if (r < 0) + return log_oom(); + if (r > 0) + o = options; + } + + if (mount(mount_table[k].what, + where, + mount_table[k].type, + mount_table[k].flags, + o) < 0) { + + if (mount_table[k].fatal) + return log_error_errno(errno, "mount(%s) failed: %m", where); + + log_warning_errno(errno, "mount(%s) failed, ignoring: %m", where); + } + } + + return 0; +} + +static int parse_mount_bind_options(const char *options, unsigned long *mount_flags, char **mount_opts) { + const char *p = options; + unsigned long flags = *mount_flags; + char *opts = NULL; + + assert(options); + + for (;;) { + _cleanup_free_ char *word = NULL; + int r = extract_first_word(&p, &word, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to extract mount option: %m"); + if (r == 0) + break; + + if (streq(word, "rbind")) + flags |= MS_REC; + else if (streq(word, "norbind")) + flags &= ~MS_REC; + else { + log_error("Invalid bind mount option: %s", word); + return -EINVAL; + } + } + + *mount_flags = flags; + /* in the future mount_opts will hold string options for mount(2) */ + *mount_opts = opts; + + return 0; +} + +static int mount_bind(const char *dest, CustomMount *m) { + struct stat source_st, dest_st; + const char *where; + unsigned long mount_flags = MS_BIND | MS_REC; + _cleanup_free_ char *mount_opts = NULL; + int r; + + assert(m); + + if (m->options) { + r = parse_mount_bind_options(m->options, &mount_flags, &mount_opts); + if (r < 0) + return r; + } + + if (stat(m->source, &source_st) < 0) + return log_error_errno(errno, "Failed to stat %s: %m", m->source); + + where = prefix_roota(dest, m->destination); + + if (stat(where, &dest_st) >= 0) { + if (S_ISDIR(source_st.st_mode) && !S_ISDIR(dest_st.st_mode)) { + log_error("Cannot bind mount directory %s on file %s.", m->source, where); + return -EINVAL; + } + + if (!S_ISDIR(source_st.st_mode) && S_ISDIR(dest_st.st_mode)) { + log_error("Cannot bind mount file %s on directory %s.", m->source, where); + return -EINVAL; + } + + } else if (errno == ENOENT) { + r = mkdir_parents_label(where, 0755); + if (r < 0) + return log_error_errno(r, "Failed to make parents of %s: %m", where); + + /* Create the mount point. Any non-directory file can be + * mounted on any non-directory file (regular, fifo, socket, + * char, block). + */ + if (S_ISDIR(source_st.st_mode)) + r = mkdir_label(where, 0755); + else + r = touch(where); + if (r < 0) + return log_error_errno(r, "Failed to create mount point %s: %m", where); + + } else { + return log_error_errno(errno, "Failed to stat %s: %m", where); + } + + if (mount(m->source, where, NULL, mount_flags, mount_opts) < 0) + return log_error_errno(errno, "mount(%s) failed: %m", where); + + if (m->read_only) { + r = bind_remount_recursive(where, true); + if (r < 0) + return log_error_errno(r, "Read-only bind mount failed: %m"); + } + + return 0; +} + +static int mount_tmpfs( + const char *dest, + CustomMount *m, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context) { + + const char *where, *options; + _cleanup_free_ char *buf = NULL; + int r; + + assert(dest); + assert(m); + + where = prefix_roota(dest, m->destination); + + r = mkdir_p_label(where, 0755); + if (r < 0 && r != -EEXIST) + return log_error_errno(r, "Creating mount point for tmpfs %s failed: %m", where); + + r = tmpfs_patch_options(m->options, userns, uid_shift, uid_range, selinux_apifs_context, &buf); + if (r < 0) + return log_oom(); + options = r > 0 ? buf : m->options; + + if (mount("tmpfs", where, "tmpfs", MS_NODEV|MS_STRICTATIME, options) < 0) + return log_error_errno(errno, "tmpfs mount to %s failed: %m", where); + + return 0; +} + +static char *joined_and_escaped_lower_dirs(char * const *lower) { + _cleanup_strv_free_ char **sv = NULL; + + sv = strv_copy(lower); + if (!sv) + return NULL; + + strv_reverse(sv); + + if (!strv_shell_escape(sv, ",:")) + return NULL; + + return strv_join(sv, ":"); +} + +static int mount_overlay(const char *dest, CustomMount *m) { + _cleanup_free_ char *lower = NULL; + const char *where, *options; + int r; + + assert(dest); + assert(m); + + where = prefix_roota(dest, m->destination); + + r = mkdir_label(where, 0755); + if (r < 0 && r != -EEXIST) + return log_error_errno(r, "Creating mount point for overlay %s failed: %m", where); + + (void) mkdir_p_label(m->source, 0755); + + lower = joined_and_escaped_lower_dirs(m->lower); + if (!lower) + return log_oom(); + + if (m->read_only) { + _cleanup_free_ char *escaped_source = NULL; + + escaped_source = shell_escape(m->source, ",:"); + if (!escaped_source) + return log_oom(); + + options = strjoina("lowerdir=", escaped_source, ":", lower); + } else { + _cleanup_free_ char *escaped_source = NULL, *escaped_work_dir = NULL; + + assert(m->work_dir); + (void) mkdir_label(m->work_dir, 0700); + + escaped_source = shell_escape(m->source, ",:"); + if (!escaped_source) + return log_oom(); + escaped_work_dir = shell_escape(m->work_dir, ",:"); + if (!escaped_work_dir) + return log_oom(); + + options = strjoina("lowerdir=", lower, ",upperdir=", escaped_source, ",workdir=", escaped_work_dir); + } + + if (mount("overlay", where, "overlay", m->read_only ? MS_RDONLY : 0, options) < 0) + return log_error_errno(errno, "overlay mount to %s failed: %m", where); + + return 0; +} + +int mount_custom( + const char *dest, + CustomMount *mounts, unsigned n, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context) { + + unsigned i; + int r; + + assert(dest); + + for (i = 0; i < n; i++) { + CustomMount *m = mounts + i; + + switch (m->type) { + + case CUSTOM_MOUNT_BIND: + r = mount_bind(dest, m); + break; + + case CUSTOM_MOUNT_TMPFS: + r = mount_tmpfs(dest, m, userns, uid_shift, uid_range, selinux_apifs_context); + break; + + case CUSTOM_MOUNT_OVERLAY: + r = mount_overlay(dest, m); + break; + + default: + assert_not_reached("Unknown custom mount type"); + } + + if (r < 0) + return r; + } + + return 0; +} + +static int mount_legacy_cgroup_hierarchy(const char *dest, const char *controller, const char *hierarchy, bool read_only) { + char *to; + int r; + + to = strjoina(strempty(dest), "/sys/fs/cgroup/", hierarchy); + + r = path_is_mount_point(to, 0); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to determine if %s is mounted already: %m", to); + if (r > 0) + return 0; + + mkdir_p(to, 0755); + + /* The superblock mount options of the mount point need to be + * identical to the hosts', and hence writable... */ + if (mount("cgroup", to, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, controller) < 0) + return log_error_errno(errno, "Failed to mount to %s: %m", to); + + /* ... hence let's only make the bind mount read-only, not the + * superblock. */ + if (read_only) { + if (mount(NULL, to, NULL, MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, NULL) < 0) + return log_error_errno(errno, "Failed to remount %s read-only: %m", to); + } + return 1; +} + +static int mount_legacy_cgroups( + const char *dest, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context) { + + _cleanup_set_free_free_ Set *controllers = NULL; + const char *cgroup_root; + int r; + + cgroup_root = prefix_roota(dest, "/sys/fs/cgroup"); + + (void) mkdir_p(cgroup_root, 0755); + + /* Mount a tmpfs to /sys/fs/cgroup if it's not mounted there yet. */ + r = path_is_mount_point(cgroup_root, AT_SYMLINK_FOLLOW); + if (r < 0) + return log_error_errno(r, "Failed to determine if /sys/fs/cgroup is already mounted: %m"); + if (r == 0) { + _cleanup_free_ char *options = NULL; + + r = tmpfs_patch_options("mode=755", userns, uid_shift, uid_range, selinux_apifs_context, &options); + if (r < 0) + return log_oom(); + + if (mount("tmpfs", cgroup_root, "tmpfs", MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, options) < 0) + return log_error_errno(errno, "Failed to mount /sys/fs/cgroup: %m"); + } + + if (cg_unified() > 0) + goto skip_controllers; + + controllers = set_new(&string_hash_ops); + if (!controllers) + return log_oom(); + + r = cg_kernel_controllers(controllers); + if (r < 0) + return log_error_errno(r, "Failed to determine cgroup controllers: %m"); + + for (;;) { + _cleanup_free_ char *controller = NULL, *origin = NULL, *combined = NULL; + + controller = set_steal_first(controllers); + if (!controller) + break; + + origin = prefix_root("/sys/fs/cgroup/", controller); + if (!origin) + return log_oom(); + + r = readlink_malloc(origin, &combined); + if (r == -EINVAL) { + /* Not a symbolic link, but directly a single cgroup hierarchy */ + + r = mount_legacy_cgroup_hierarchy(dest, controller, controller, true); + if (r < 0) + return r; + + } else if (r < 0) + return log_error_errno(r, "Failed to read link %s: %m", origin); + else { + _cleanup_free_ char *target = NULL; + + target = prefix_root(dest, origin); + if (!target) + return log_oom(); + + /* A symbolic link, a combination of controllers in one hierarchy */ + + if (!filename_is_valid(combined)) { + log_warning("Ignoring invalid combined hierarchy %s.", combined); + continue; + } + + r = mount_legacy_cgroup_hierarchy(dest, combined, combined, true); + if (r < 0) + return r; + + r = symlink_idempotent(combined, target); + if (r == -EINVAL) { + log_error("Invalid existing symlink for combined hierarchy"); + return r; + } + if (r < 0) + return log_error_errno(r, "Failed to create symlink for combined hierarchy: %m"); + } + } + +skip_controllers: + r = mount_legacy_cgroup_hierarchy(dest, "none,name=systemd,xattr", "systemd", false); + if (r < 0) + return r; + + if (mount(NULL, cgroup_root, NULL, MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, "mode=755") < 0) + return log_error_errno(errno, "Failed to remount %s read-only: %m", cgroup_root); + + return 0; +} + +static int mount_unified_cgroups(const char *dest) { + const char *p; + int r; + + assert(dest); + + p = prefix_roota(dest, "/sys/fs/cgroup"); + + (void) mkdir_p(p, 0755); + + r = path_is_mount_point(p, AT_SYMLINK_FOLLOW); + if (r < 0) + return log_error_errno(r, "Failed to determine if %s is mounted already: %m", p); + if (r > 0) { + p = prefix_roota(dest, "/sys/fs/cgroup/cgroup.procs"); + if (access(p, F_OK) >= 0) + return 0; + if (errno != ENOENT) + return log_error_errno(errno, "Failed to determine if mount point %s contains the unified cgroup hierarchy: %m", p); + + log_error("%s is already mounted but not a unified cgroup hierarchy. Refusing.", p); + return -EINVAL; + } + + if (mount("cgroup", p, "cgroup2", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) < 0) + return log_error_errno(errno, "Failed to mount unified cgroup hierarchy to %s: %m", p); + + return 0; +} + +int mount_cgroups( + const char *dest, + bool unified_requested, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context) { + + if (unified_requested) + return mount_unified_cgroups(dest); + else + return mount_legacy_cgroups(dest, userns, uid_shift, uid_range, selinux_apifs_context); +} + +int mount_systemd_cgroup_writable( + const char *dest, + bool unified_requested) { + + _cleanup_free_ char *own_cgroup_path = NULL; + const char *systemd_root, *systemd_own; + int r; + + assert(dest); + + r = cg_pid_get_path(NULL, 0, &own_cgroup_path); + if (r < 0) + return log_error_errno(r, "Failed to determine our own cgroup path: %m"); + + /* If we are living in the top-level, then there's nothing to do... */ + if (path_equal(own_cgroup_path, "/")) + return 0; + + if (unified_requested) { + systemd_own = strjoina(dest, "/sys/fs/cgroup", own_cgroup_path); + systemd_root = prefix_roota(dest, "/sys/fs/cgroup"); + } else { + systemd_own = strjoina(dest, "/sys/fs/cgroup/systemd", own_cgroup_path); + systemd_root = prefix_roota(dest, "/sys/fs/cgroup/systemd"); + } + + /* Make our own cgroup a (writable) bind mount */ + if (mount(systemd_own, systemd_own, NULL, MS_BIND, NULL) < 0) + return log_error_errno(errno, "Failed to turn %s into a bind mount: %m", own_cgroup_path); + + /* And then remount the systemd cgroup root read-only */ + if (mount(NULL, systemd_root, NULL, MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, NULL) < 0) + return log_error_errno(errno, "Failed to mount cgroup root read-only: %m"); + + return 0; +} + +int setup_volatile_state( + const char *directory, + VolatileMode mode, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context) { + + _cleanup_free_ char *buf = NULL; + const char *p, *options; + int r; + + assert(directory); + + if (mode != VOLATILE_STATE) + return 0; + + /* --volatile=state means we simply overmount /var + with a tmpfs, and the rest read-only. */ + + r = bind_remount_recursive(directory, true); + if (r < 0) + return log_error_errno(r, "Failed to remount %s read-only: %m", directory); + + p = prefix_roota(directory, "/var"); + r = mkdir(p, 0755); + if (r < 0 && errno != EEXIST) + return log_error_errno(errno, "Failed to create %s: %m", directory); + + options = "mode=755"; + r = tmpfs_patch_options(options, userns, uid_shift, uid_range, selinux_apifs_context, &buf); + if (r < 0) + return log_oom(); + if (r > 0) + options = buf; + + if (mount("tmpfs", p, "tmpfs", MS_STRICTATIME, options) < 0) + return log_error_errno(errno, "Failed to mount tmpfs to /var: %m"); + + return 0; +} + +int setup_volatile( + const char *directory, + VolatileMode mode, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context) { + + bool tmpfs_mounted = false, bind_mounted = false; + char template[] = "/tmp/nspawn-volatile-XXXXXX"; + _cleanup_free_ char *buf = NULL; + const char *f, *t, *options; + int r; + + assert(directory); + + if (mode != VOLATILE_YES) + return 0; + + /* --volatile=yes means we mount a tmpfs to the root dir, and + the original /usr to use inside it, and that read-only. */ + + if (!mkdtemp(template)) + return log_error_errno(errno, "Failed to create temporary directory: %m"); + + options = "mode=755"; + r = tmpfs_patch_options(options, userns, uid_shift, uid_range, selinux_apifs_context, &buf); + if (r < 0) + return log_oom(); + if (r > 0) + options = buf; + + if (mount("tmpfs", template, "tmpfs", MS_STRICTATIME, options) < 0) { + r = log_error_errno(errno, "Failed to mount tmpfs for root directory: %m"); + goto fail; + } + + tmpfs_mounted = true; + + f = prefix_roota(directory, "/usr"); + t = prefix_roota(template, "/usr"); + + r = mkdir(t, 0755); + if (r < 0 && errno != EEXIST) { + r = log_error_errno(errno, "Failed to create %s: %m", t); + goto fail; + } + + if (mount(f, t, NULL, MS_BIND|MS_REC, NULL) < 0) { + r = log_error_errno(errno, "Failed to create /usr bind mount: %m"); + goto fail; + } + + bind_mounted = true; + + r = bind_remount_recursive(t, true); + if (r < 0) { + log_error_errno(r, "Failed to remount %s read-only: %m", t); + goto fail; + } + + if (mount(template, directory, NULL, MS_MOVE, NULL) < 0) { + r = log_error_errno(errno, "Failed to move root mount: %m"); + goto fail; + } + + (void) rmdir(template); + + return 0; + +fail: + if (bind_mounted) + (void) umount(t); + + if (tmpfs_mounted) + (void) umount(template); + (void) rmdir(template); + return r; +} + +VolatileMode volatile_mode_from_string(const char *s) { + int b; + + if (isempty(s)) + return _VOLATILE_MODE_INVALID; + + b = parse_boolean(s); + if (b > 0) + return VOLATILE_YES; + if (b == 0) + return VOLATILE_NO; + + if (streq(s, "state")) + return VOLATILE_STATE; + + return _VOLATILE_MODE_INVALID; +} diff --git a/src/systemd-nspawn/nspawn-mount.h b/src/systemd-nspawn/nspawn-mount.h new file mode 100644 index 0000000000..0daf145412 --- /dev/null +++ b/src/systemd-nspawn/nspawn-mount.h @@ -0,0 +1,69 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 + +typedef enum VolatileMode { + VOLATILE_NO, + VOLATILE_YES, + VOLATILE_STATE, + _VOLATILE_MODE_MAX, + _VOLATILE_MODE_INVALID = -1 +} VolatileMode; + +typedef enum CustomMountType { + CUSTOM_MOUNT_BIND, + CUSTOM_MOUNT_TMPFS, + CUSTOM_MOUNT_OVERLAY, + _CUSTOM_MOUNT_TYPE_MAX, + _CUSTOM_MOUNT_TYPE_INVALID = -1 +} CustomMountType; + +typedef struct CustomMount { + CustomMountType type; + bool read_only; + char *source; /* for overlayfs this is the upper directory */ + char *destination; + char *options; + char *work_dir; + char **lower; +} CustomMount; + +CustomMount* custom_mount_add(CustomMount **l, unsigned *n, CustomMountType t); + +void custom_mount_free_all(CustomMount *l, unsigned n); +int bind_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only); +int tmpfs_mount_parse(CustomMount **l, unsigned *n, const char *s); + +int custom_mount_compare(const void *a, const void *b); + +int mount_all(const char *dest, bool use_userns, bool in_userns, bool use_netns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); +int mount_sysfs(const char *dest); + +int mount_cgroups(const char *dest, bool unified_requested, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); +int mount_systemd_cgroup_writable(const char *dest, bool unified_requested); + +int mount_custom(const char *dest, CustomMount *mounts, unsigned n, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); + +int setup_volatile(const char *directory, VolatileMode mode, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); +int setup_volatile_state(const char *directory, VolatileMode mode, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); + +VolatileMode volatile_mode_from_string(const char *s); diff --git a/src/systemd-nspawn/nspawn-network.c b/src/systemd-nspawn/nspawn-network.c new file mode 100644 index 0000000000..7052fb5804 --- /dev/null +++ b/src/systemd-nspawn/nspawn-network.c @@ -0,0 +1,694 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 + +#include "libudev.h" +#include +#include + +#include "alloc-util.h" +#include "ether-addr-util.h" +#include "lockfile-util.h" +#include "netlink-util.h" +#include "nspawn-network.h" +#include "siphash24.h" +#include "socket-util.h" +#include "stat-util.h" +#include "string-util.h" +#include "udev-util.h" +#include "util.h" + +#define HOST_HASH_KEY SD_ID128_MAKE(1a,37,6f,c7,46,ec,45,0b,ad,a3,d5,31,06,60,5d,b1) +#define CONTAINER_HASH_KEY SD_ID128_MAKE(c3,c4,f9,19,b5,57,b2,1c,e6,cf,14,27,03,9c,ee,a2) +#define VETH_EXTRA_HOST_HASH_KEY SD_ID128_MAKE(48,c7,f6,b7,ea,9d,4c,9e,b7,28,d4,de,91,d5,bf,66) +#define VETH_EXTRA_CONTAINER_HASH_KEY SD_ID128_MAKE(af,50,17,61,ce,f9,4d,35,84,0d,2b,20,54,be,ce,59) +#define MACVLAN_HASH_KEY SD_ID128_MAKE(00,13,6d,bc,66,83,44,81,bb,0c,f9,51,1f,24,a6,6f) + +static int remove_one_link(sd_netlink *rtnl, const char *name) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + if (isempty(name)) + return 0; + + r = sd_rtnl_message_new_link(rtnl, &m, RTM_DELLINK, 0); + if (r < 0) + return log_error_errno(r, "Failed to allocate netlink message: %m"); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, name); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface name: %m"); + + r = sd_netlink_call(rtnl, m, 0, NULL); + if (r == -ENODEV) /* Already gone */ + return 0; + if (r < 0) + return log_error_errno(r, "Failed to remove interface %s: %m", name); + + return 1; +} + +static int generate_mac( + const char *machine_name, + struct ether_addr *mac, + sd_id128_t hash_key, + uint64_t idx) { + + uint64_t result; + size_t l, sz; + uint8_t *v, *i; + int r; + + l = strlen(machine_name); + sz = sizeof(sd_id128_t) + l; + if (idx > 0) + sz += sizeof(idx); + + v = alloca(sz); + + /* fetch some persistent data unique to the host */ + r = sd_id128_get_machine((sd_id128_t*) v); + if (r < 0) + return r; + + /* combine with some data unique (on this host) to this + * container instance */ + i = mempcpy(v + sizeof(sd_id128_t), machine_name, l); + if (idx > 0) { + idx = htole64(idx); + memcpy(i, &idx, sizeof(idx)); + } + + /* Let's hash the host machine ID plus the container name. We + * use a fixed, but originally randomly created hash key here. */ + result = htole64(siphash24(v, sz, hash_key.bytes)); + + assert_cc(ETH_ALEN <= sizeof(result)); + memcpy(mac->ether_addr_octet, &result, ETH_ALEN); + + /* see eth_random_addr in the kernel */ + mac->ether_addr_octet[0] &= 0xfe; /* clear multicast bit */ + mac->ether_addr_octet[0] |= 0x02; /* set local assignment bit (IEEE802) */ + + return 0; +} + +static int add_veth( + sd_netlink *rtnl, + pid_t pid, + const char *ifname_host, + const struct ether_addr *mac_host, + const char *ifname_container, + const struct ether_addr *mac_container) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(rtnl); + assert(ifname_host); + assert(mac_host); + assert(ifname_container); + assert(mac_container); + + r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); + if (r < 0) + return log_error_errno(r, "Failed to allocate netlink message: %m"); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, ifname_host); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface name: %m"); + + r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, mac_host); + if (r < 0) + return log_error_errno(r, "Failed to add netlink MAC address: %m"); + + r = sd_netlink_message_open_container(m, IFLA_LINKINFO); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "veth"); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_open_container(m, VETH_INFO_PEER); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, ifname_container); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface name: %m"); + + r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, mac_container); + if (r < 0) + return log_error_errno(r, "Failed to add netlink MAC address: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); + if (r < 0) + return log_error_errno(r, "Failed to add netlink namespace field: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_call(rtnl, m, 0, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add new veth interfaces (%s:%s): %m", ifname_host, ifname_container); + + return 0; +} + +int setup_veth(const char *machine_name, + pid_t pid, + char iface_name[IFNAMSIZ], + bool bridge) { + + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + struct ether_addr mac_host, mac_container; + int r, i; + + assert(machine_name); + assert(pid > 0); + assert(iface_name); + + /* Use two different interface name prefixes depending whether + * we are in bridge mode or not. */ + snprintf(iface_name, IFNAMSIZ - 1, "%s-%s", + bridge ? "vb" : "ve", machine_name); + + r = generate_mac(machine_name, &mac_container, CONTAINER_HASH_KEY, 0); + if (r < 0) + return log_error_errno(r, "Failed to generate predictable MAC address for container side: %m"); + + r = generate_mac(machine_name, &mac_host, HOST_HASH_KEY, 0); + if (r < 0) + return log_error_errno(r, "Failed to generate predictable MAC address for host side: %m"); + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + r = add_veth(rtnl, pid, iface_name, &mac_host, "host0", &mac_container); + if (r < 0) + return r; + + i = (int) if_nametoindex(iface_name); + if (i <= 0) + return log_error_errno(errno, "Failed to resolve interface %s: %m", iface_name); + + return i; +} + +int setup_veth_extra( + const char *machine_name, + pid_t pid, + char **pairs) { + + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + uint64_t idx = 0; + char **a, **b; + int r; + + assert(machine_name); + assert(pid > 0); + + if (strv_isempty(pairs)) + return 0; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + STRV_FOREACH_PAIR(a, b, pairs) { + struct ether_addr mac_host, mac_container; + + r = generate_mac(machine_name, &mac_container, VETH_EXTRA_CONTAINER_HASH_KEY, idx); + if (r < 0) + return log_error_errno(r, "Failed to generate predictable MAC address for container side of extra veth link: %m"); + + r = generate_mac(machine_name, &mac_host, VETH_EXTRA_HOST_HASH_KEY, idx); + if (r < 0) + return log_error_errno(r, "Failed to generate predictable MAC address for container side of extra veth link: %m"); + + r = add_veth(rtnl, pid, *a, &mac_host, *b, &mac_container); + if (r < 0) + return r; + + idx++; + } + + return 0; +} + +static int join_bridge(sd_netlink *rtnl, const char *veth_name, const char *bridge_name) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r, bridge_ifi; + + assert(rtnl); + assert(veth_name); + assert(bridge_name); + + bridge_ifi = (int) if_nametoindex(bridge_name); + if (bridge_ifi <= 0) + return -errno; + + r = sd_rtnl_message_new_link(rtnl, &m, RTM_SETLINK, 0); + if (r < 0) + return r; + + r = sd_rtnl_message_link_set_flags(m, IFF_UP, IFF_UP); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, veth_name); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, IFLA_MASTER, bridge_ifi); + if (r < 0) + return r; + + r = sd_netlink_call(rtnl, m, 0, NULL); + if (r < 0) + return r; + + return bridge_ifi; +} + +static int create_bridge(sd_netlink *rtnl, const char *bridge_name) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, bridge_name); + if (r < 0) + return r; + + r = sd_netlink_message_open_container(m, IFLA_LINKINFO); + if (r < 0) + return r; + + r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "bridge"); + if (r < 0) + return r; + + r = sd_netlink_message_close_container(m); + if (r < 0) + return r; + + r = sd_netlink_message_close_container(m); + if (r < 0) + return r; + + r = sd_netlink_call(rtnl, m, 0, NULL); + if (r < 0) + return r; + + return 0; +} + +int setup_bridge(const char *veth_name, const char *bridge_name, bool create) { + _cleanup_release_lock_file_ LockFile bridge_lock = LOCK_FILE_INIT; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + int r, bridge_ifi; + unsigned n = 0; + + assert(veth_name); + assert(bridge_name); + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + if (create) { + /* We take a system-wide lock here, so that we can safely check whether there's still a member in the + * bridge before removing it, without risking interferance from other nspawn instances. */ + + r = make_lock_file("/run/systemd/nspawn-network-zone", LOCK_EX, &bridge_lock); + if (r < 0) + return log_error_errno(r, "Failed to take network zone lock: %m"); + } + + for (;;) { + bridge_ifi = join_bridge(rtnl, veth_name, bridge_name); + if (bridge_ifi >= 0) + return bridge_ifi; + if (bridge_ifi != -ENODEV || !create || n > 10) + return log_error_errno(bridge_ifi, "Failed to add interface %s to bridge %s: %m", veth_name, bridge_name); + + /* Count attempts, so that we don't enter an endless loop here. */ + n++; + + /* The bridge doesn't exist yet. Let's create it */ + r = create_bridge(rtnl, bridge_name); + if (r < 0) + return log_error_errno(r, "Failed to create bridge interface %s: %m", bridge_name); + + /* Try again, now that the bridge exists */ + } +} + +int remove_bridge(const char *bridge_name) { + _cleanup_release_lock_file_ LockFile bridge_lock = LOCK_FILE_INIT; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + const char *path; + int r; + + /* Removes the specified bridge, but only if it is currently empty */ + + if (isempty(bridge_name)) + return 0; + + r = make_lock_file("/run/systemd/nspawn-network-zone", LOCK_EX, &bridge_lock); + if (r < 0) + return log_error_errno(r, "Failed to take network zone lock: %m"); + + path = strjoina("/sys/class/net/", bridge_name, "/brif"); + + r = dir_is_empty(path); + if (r == -ENOENT) /* Already gone? */ + return 0; + if (r < 0) + return log_error_errno(r, "Can't detect if bridge %s is empty: %m", bridge_name); + if (r == 0) /* Still populated, leave it around */ + return 0; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + return remove_one_link(rtnl, bridge_name); +} + +static int parse_interface(struct udev *udev, const char *name) { + _cleanup_udev_device_unref_ struct udev_device *d = NULL; + char ifi_str[2 + DECIMAL_STR_MAX(int)]; + int ifi; + + ifi = (int) if_nametoindex(name); + if (ifi <= 0) + return log_error_errno(errno, "Failed to resolve interface %s: %m", name); + + sprintf(ifi_str, "n%i", ifi); + d = udev_device_new_from_device_id(udev, ifi_str); + if (!d) + return log_error_errno(errno, "Failed to get udev device for interface %s: %m", name); + + if (udev_device_get_is_initialized(d) <= 0) { + log_error("Network interface %s is not initialized yet.", name); + return -EBUSY; + } + + return ifi; +} + +int move_network_interfaces(pid_t pid, char **ifaces) { + _cleanup_udev_unref_ struct udev *udev = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + char **i; + int r; + + if (strv_isempty(ifaces)) + return 0; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + udev = udev_new(); + if (!udev) { + log_error("Failed to connect to udev."); + return -ENOMEM; + } + + STRV_FOREACH(i, ifaces) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int ifi; + + ifi = parse_interface(udev, *i); + if (ifi < 0) + return ifi; + + r = sd_rtnl_message_new_link(rtnl, &m, RTM_SETLINK, ifi); + if (r < 0) + return log_error_errno(r, "Failed to allocate netlink message: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); + if (r < 0) + return log_error_errno(r, "Failed to append namespace PID to netlink message: %m"); + + r = sd_netlink_call(rtnl, m, 0, NULL); + if (r < 0) + return log_error_errno(r, "Failed to move interface %s to namespace: %m", *i); + } + + return 0; +} + +int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces) { + _cleanup_udev_unref_ struct udev *udev = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + unsigned idx = 0; + char **i; + int r; + + if (strv_isempty(ifaces)) + return 0; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + udev = udev_new(); + if (!udev) { + log_error("Failed to connect to udev."); + return -ENOMEM; + } + + STRV_FOREACH(i, ifaces) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + _cleanup_free_ char *n = NULL; + struct ether_addr mac; + int ifi; + + ifi = parse_interface(udev, *i); + if (ifi < 0) + return ifi; + + r = generate_mac(machine_name, &mac, MACVLAN_HASH_KEY, idx++); + if (r < 0) + return log_error_errno(r, "Failed to create MACVLAN MAC address: %m"); + + r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); + if (r < 0) + return log_error_errno(r, "Failed to allocate netlink message: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_LINK, ifi); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface index: %m"); + + n = strappend("mv-", *i); + if (!n) + return log_oom(); + + strshorten(n, IFNAMSIZ-1); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, n); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface name: %m"); + + r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, &mac); + if (r < 0) + return log_error_errno(r, "Failed to add netlink MAC address: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); + if (r < 0) + return log_error_errno(r, "Failed to add netlink namespace field: %m"); + + r = sd_netlink_message_open_container(m, IFLA_LINKINFO); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "macvlan"); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_MACVLAN_MODE, MACVLAN_MODE_BRIDGE); + if (r < 0) + return log_error_errno(r, "Failed to append macvlan mode: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_call(rtnl, m, 0, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add new macvlan interfaces: %m"); + } + + return 0; +} + +int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces) { + _cleanup_udev_unref_ struct udev *udev = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + char **i; + int r; + + if (strv_isempty(ifaces)) + return 0; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + udev = udev_new(); + if (!udev) { + log_error("Failed to connect to udev."); + return -ENOMEM; + } + + STRV_FOREACH(i, ifaces) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + _cleanup_free_ char *n = NULL; + int ifi; + + ifi = parse_interface(udev, *i); + if (ifi < 0) + return ifi; + + r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); + if (r < 0) + return log_error_errno(r, "Failed to allocate netlink message: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_LINK, ifi); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface index: %m"); + + n = strappend("iv-", *i); + if (!n) + return log_oom(); + + strshorten(n, IFNAMSIZ-1); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, n); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface name: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); + if (r < 0) + return log_error_errno(r, "Failed to add netlink namespace field: %m"); + + r = sd_netlink_message_open_container(m, IFLA_LINKINFO); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "ipvlan"); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_append_u16(m, IFLA_IPVLAN_MODE, IPVLAN_MODE_L2); + if (r < 0) + return log_error_errno(r, "Failed to add ipvlan mode: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_call(rtnl, m, 0, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add new ipvlan interfaces: %m"); + } + + return 0; +} + +int veth_extra_parse(char ***l, const char *p) { + _cleanup_free_ char *a = NULL, *b = NULL; + int r; + + r = extract_first_word(&p, &a, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0 || !ifname_valid(a)) + return -EINVAL; + + r = extract_first_word(&p, &b, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0 || !ifname_valid(b)) { + free(b); + b = strdup(a); + if (!b) + return -ENOMEM; + } + + if (p) + return -EINVAL; + + r = strv_push_pair(l, a, b); + if (r < 0) + return -ENOMEM; + + a = b = NULL; + return 0; +} + +int remove_veth_links(const char *primary, char **pairs) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + char **a, **b; + int r; + + /* In some cases the kernel might pin the veth links between host and container even after the namespace + * died. Hence, let's better remove them explicitly too. */ + + if (isempty(primary) && strv_isempty(pairs)) + return 0; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + remove_one_link(rtnl, primary); + + STRV_FOREACH_PAIR(a, b, pairs) + remove_one_link(rtnl, *a); + + return 0; +} diff --git a/src/systemd-nspawn/nspawn-network.h b/src/systemd-nspawn/nspawn-network.h new file mode 100644 index 0000000000..3d8861e1e5 --- /dev/null +++ b/src/systemd-nspawn/nspawn-network.h @@ -0,0 +1,39 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 +#include + +int setup_veth(const char *machine_name, pid_t pid, char iface_name[IFNAMSIZ], bool bridge); +int setup_veth_extra(const char *machine_name, pid_t pid, char **pairs); + +int setup_bridge(const char *veth_name, const char *bridge_name, bool create); +int remove_bridge(const char *bridge_name); + +int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces); +int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces); + +int move_network_interfaces(pid_t pid, char **ifaces); + +int veth_extra_parse(char ***l, const char *p); + +int remove_veth_links(const char *primary, char **pairs); diff --git a/src/systemd-nspawn/nspawn-patch-uid.c b/src/systemd-nspawn/nspawn-patch-uid.c new file mode 100644 index 0000000000..c7382d412d --- /dev/null +++ b/src/systemd-nspawn/nspawn-patch-uid.c @@ -0,0 +1,469 @@ +/*** + This file is part of systemd. + + Copyright 2016 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 +#ifdef HAVE_ACL +#include +#endif +#include +#include +#include + +#include "acl-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "missing.h" +#include "nspawn-patch-uid.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" + +#ifdef HAVE_ACL + +static int get_acl(int fd, const char *name, acl_type_t type, acl_t *ret) { + char procfs_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; + acl_t acl; + + assert(fd >= 0); + assert(ret); + + if (name) { + _cleanup_close_ int child_fd = -1; + + child_fd = openat(fd, name, O_PATH|O_CLOEXEC|O_NOFOLLOW); + if (child_fd < 0) + return -errno; + + xsprintf(procfs_path, "/proc/self/fd/%i", child_fd); + acl = acl_get_file(procfs_path, type); + } else if (type == ACL_TYPE_ACCESS) + acl = acl_get_fd(fd); + else { + xsprintf(procfs_path, "/proc/self/fd/%i", fd); + acl = acl_get_file(procfs_path, type); + } + if (!acl) + return -errno; + + *ret = acl; + return 0; +} + +static int set_acl(int fd, const char *name, acl_type_t type, acl_t acl) { + char procfs_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; + int r; + + assert(fd >= 0); + assert(acl); + + if (name) { + _cleanup_close_ int child_fd = -1; + + child_fd = openat(fd, name, O_PATH|O_CLOEXEC|O_NOFOLLOW); + if (child_fd < 0) + return -errno; + + xsprintf(procfs_path, "/proc/self/fd/%i", child_fd); + r = acl_set_file(procfs_path, type, acl); + } else if (type == ACL_TYPE_ACCESS) + r = acl_set_fd(fd, acl); + else { + xsprintf(procfs_path, "/proc/self/fd/%i", fd); + r = acl_set_file(procfs_path, type, acl); + } + if (r < 0) + return -errno; + + return 0; +} + +static int shift_acl(acl_t acl, uid_t shift, acl_t *ret) { + _cleanup_(acl_freep) acl_t copy = NULL; + acl_entry_t i; + int r; + + assert(acl); + assert(ret); + + r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i); + if (r < 0) + return -errno; + while (r > 0) { + uid_t *old_uid, new_uid; + bool modify = false; + acl_tag_t tag; + + if (acl_get_tag_type(i, &tag) < 0) + return -errno; + + if (IN_SET(tag, ACL_USER, ACL_GROUP)) { + + /* We don't distuingish here between uid_t and gid_t, let's make sure the compiler checks that + * this is actually OK */ + assert_cc(sizeof(uid_t) == sizeof(gid_t)); + + old_uid = acl_get_qualifier(i); + if (!old_uid) + return -errno; + + new_uid = shift | (*old_uid & UINT32_C(0xFFFF)); + if (!uid_is_valid(new_uid)) + return -EINVAL; + + modify = new_uid != *old_uid; + if (modify && !copy) { + int n; + + /* There's no copy of the ACL yet? if so, let's create one, and start the loop from the + * beginning, so that we copy all entries, starting from the first, this time. */ + + n = acl_entries(acl); + if (n < 0) + return -errno; + + copy = acl_init(n); + if (!copy) + return -errno; + + /* Seek back to the beginning */ + r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i); + if (r < 0) + return -errno; + continue; + } + } + + if (copy) { + acl_entry_t new_entry; + + if (acl_create_entry(©, &new_entry) < 0) + return -errno; + + if (acl_copy_entry(new_entry, i) < 0) + return -errno; + + if (modify) + if (acl_set_qualifier(new_entry, &new_uid) < 0) + return -errno; + } + + r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i); + if (r < 0) + return -errno; + } + + *ret = copy; + copy = NULL; + + return !!*ret; +} + +static int patch_acls(int fd, const char *name, const struct stat *st, uid_t shift) { + _cleanup_(acl_freep) acl_t acl = NULL, shifted = NULL; + bool changed = false; + int r; + + assert(fd >= 0); + assert(st); + + /* ACLs are not supported on symlinks, there's no point in trying */ + if (S_ISLNK(st->st_mode)) + return 0; + + r = get_acl(fd, name, ACL_TYPE_ACCESS, &acl); + if (r == -EOPNOTSUPP) + return 0; + if (r < 0) + return r; + + r = shift_acl(acl, shift, &shifted); + if (r < 0) + return r; + if (r > 0) { + r = set_acl(fd, name, ACL_TYPE_ACCESS, shifted); + if (r < 0) + return r; + + changed = true; + } + + if (S_ISDIR(st->st_mode)) { + acl_free(acl); + acl_free(shifted); + + acl = shifted = NULL; + + r = get_acl(fd, name, ACL_TYPE_DEFAULT, &acl); + if (r < 0) + return r; + + r = shift_acl(acl, shift, &shifted); + if (r < 0) + return r; + if (r > 0) { + r = set_acl(fd, name, ACL_TYPE_DEFAULT, shifted); + if (r < 0) + return r; + + changed = true; + } + } + + return changed; +} + +#else + +static int patch_acls(int fd, const char *name, const struct stat *st, uid_t shift) { + return 0; +} + +#endif + +static int patch_fd(int fd, const char *name, const struct stat *st, uid_t shift) { + uid_t new_uid; + gid_t new_gid; + bool changed = false; + int r; + + assert(fd >= 0); + assert(st); + + new_uid = shift | (st->st_uid & UINT32_C(0xFFFF)); + new_gid = (gid_t) shift | (st->st_gid & UINT32_C(0xFFFF)); + + if (!uid_is_valid(new_uid) || !gid_is_valid(new_gid)) + return -EINVAL; + + if (st->st_uid != new_uid || st->st_gid != new_gid) { + if (name) + r = fchownat(fd, name, new_uid, new_gid, AT_SYMLINK_NOFOLLOW); + else + r = fchown(fd, new_uid, new_gid); + if (r < 0) + return -errno; + + /* The Linux kernel alters the mode in some cases of chown(). Let's undo this. */ + if (name && !S_ISLNK(st->st_mode)) + r = fchmodat(fd, name, st->st_mode, 0); + else + r = fchmod(fd, st->st_mode); + if (r < 0) + return -errno; + + changed = true; + } + + r = patch_acls(fd, name, st, shift); + if (r < 0) + return r; + + return r > 0 || changed; +} + +static int is_procfs_sysfs_or_suchlike(int fd) { + struct statfs sfs; + + assert(fd >= 0); + + if (fstatfs(fd, &sfs) < 0) + return -errno; + + return F_TYPE_EQUAL(sfs.f_type, BINFMTFS_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, CGROUP_SUPER_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, CGROUP2_SUPER_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, DEBUGFS_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, DEVPTS_SUPER_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, EFIVARFS_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, HUGETLBFS_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, MQUEUE_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, PROC_SUPER_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, PSTOREFS_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, SELINUX_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, SMACK_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, SYSFS_MAGIC); +} + +static int recurse_fd(int fd, bool donate_fd, const struct stat *st, uid_t shift, bool is_toplevel) { + bool changed = false; + int r; + + assert(fd >= 0); + + /* We generally want to permit crossing of mount boundaries when patching the UIDs/GIDs. However, we + * probably shouldn't do this for /proc and /sys if that is already mounted into place. Hence, let's + * stop the recursion when we hit a procfs or sysfs file system. */ + r = is_procfs_sysfs_or_suchlike(fd); + if (r < 0) + goto finish; + if (r > 0) { + r = 0; /* don't recurse */ + goto finish; + } + + r = patch_fd(fd, NULL, st, shift); + if (r == -EROFS) { + _cleanup_free_ char *name = NULL; + + if (!is_toplevel) { + /* When we hit a ready-only subtree we simply skip it, but log about it. */ + (void) fd_get_path(fd, &name); + log_debug("Skippping read-only file or directory %s.", strna(name)); + r = 0; + } + + goto finish; + } + if (r < 0) + goto finish; + + if (S_ISDIR(st->st_mode)) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + + if (!donate_fd) { + int copy; + + copy = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (copy < 0) { + r = -errno; + goto finish; + } + + fd = copy; + donate_fd = true; + } + + d = fdopendir(fd); + if (!d) { + r = -errno; + goto finish; + } + fd = -1; + + FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) { + struct stat fst; + + if (STR_IN_SET(de->d_name, ".", "..")) + continue; + + if (fstatat(dirfd(d), de->d_name, &fst, AT_SYMLINK_NOFOLLOW) < 0) { + r = -errno; + goto finish; + } + + if (S_ISDIR(fst.st_mode)) { + int subdir_fd; + + subdir_fd = openat(dirfd(d), de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + if (subdir_fd < 0) { + r = -errno; + goto finish; + + } + + r = recurse_fd(subdir_fd, true, &fst, shift, false); + if (r < 0) + goto finish; + if (r > 0) + changed = true; + + } else { + r = patch_fd(dirfd(d), de->d_name, &fst, shift); + if (r < 0) + goto finish; + if (r > 0) + changed = true; + } + } + } + + r = changed; + +finish: + if (donate_fd) + safe_close(fd); + + return r; +} + +static int fd_patch_uid_internal(int fd, bool donate_fd, uid_t shift, uid_t range) { + struct stat st; + int r; + + assert(fd >= 0); + + /* Recursively adjusts the UID/GIDs of all files of a directory tree. This is used to automatically fix up an + * OS tree to the used user namespace UID range. Note that this automatic adjustment only works for UID ranges + * following the concept that the upper 16bit of a UID identify the container, and the lower 16bit are the actual + * UID within the container. */ + + if ((shift & 0xFFFF) != 0) { + /* We only support containers where the shift starts at a 2^16 boundary */ + r = -EOPNOTSUPP; + goto finish; + } + + if (range != 0x10000) { + /* We only support containers with 16bit UID ranges for the patching logic */ + r = -EOPNOTSUPP; + goto finish; + } + + if (fstat(fd, &st) < 0) { + r = -errno; + goto finish; + } + + if ((uint32_t) st.st_uid >> 16 != (uint32_t) st.st_gid >> 16) { + /* We only support containers where the uid/gid container ID match */ + r = -EBADE; + goto finish; + } + + /* Try to detect if the range is already right. Of course, this a pretty drastic optimization, as we assume + * that if the top-level dir has the right upper 16bit assigned, then everything below will have too... */ + if (((uint32_t) (st.st_uid ^ shift) >> 16) == 0) + return 0; + + return recurse_fd(fd, donate_fd, &st, shift, true); + +finish: + if (donate_fd) + safe_close(fd); + + return r; +} + +int fd_patch_uid(int fd, uid_t shift, uid_t range) { + return fd_patch_uid_internal(fd, false, shift, range); +} + +int path_patch_uid(const char *path, uid_t shift, uid_t range) { + int fd; + + fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + if (fd < 0) + return -errno; + + return fd_patch_uid_internal(fd, true, shift, range); +} diff --git a/src/systemd-nspawn/nspawn-patch-uid.h b/src/systemd-nspawn/nspawn-patch-uid.h new file mode 100644 index 0000000000..55d0990016 --- /dev/null +++ b/src/systemd-nspawn/nspawn-patch-uid.h @@ -0,0 +1,23 @@ +/*** + This file is part of systemd. + + Copyright 2016 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 + +int fd_patch_uid(int fd, uid_t shift, uid_t range); +int path_patch_uid(const char *path, uid_t shift, uid_t range); diff --git a/src/systemd-nspawn/nspawn-register.c b/src/systemd-nspawn/nspawn-register.c new file mode 100644 index 0000000000..adef200cb5 --- /dev/null +++ b/src/systemd-nspawn/nspawn-register.c @@ -0,0 +1,236 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 "bus-error.h" +#include "bus-unit-util.h" +#include "bus-util.h" +#include "nspawn-register.h" +#include "stat-util.h" +#include "strv.h" +#include "util.h" + +int register_machine( + const char *machine_name, + pid_t pid, + const char *directory, + sd_id128_t uuid, + int local_ifindex, + const char *slice, + CustomMount *mounts, + unsigned n_mounts, + int kill_signal, + char **properties, + bool keep_unit, + const char *service) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + r = sd_bus_default_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to open system bus: %m"); + + if (keep_unit) { + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "RegisterMachineWithNetwork", + &error, + NULL, + "sayssusai", + machine_name, + SD_BUS_MESSAGE_APPEND_ID128(uuid), + service, + "container", + (uint32_t) pid, + strempty(directory), + local_ifindex > 0 ? 1 : 0, local_ifindex); + } else { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + char **i; + unsigned j; + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "CreateMachineWithNetwork"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sayssusai", + machine_name, + SD_BUS_MESSAGE_APPEND_ID128(uuid), + service, + "container", + (uint32_t) pid, + strempty(directory), + local_ifindex > 0 ? 1 : 0, local_ifindex); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + if (!isempty(slice)) { + r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_append(m, "(sv)", "DevicePolicy", "s", "strict"); + if (r < 0) + return bus_log_create_error(r); + + /* If you make changes here, also make sure to update + * systemd-nspawn@.service, to keep the device + * policies in sync regardless if we are run with or + * without the --keep-unit switch. */ + r = sd_bus_message_append(m, "(sv)", "DeviceAllow", "a(ss)", 9, + /* Allow the container to + * access and create the API + * device nodes, so that + * PrivateDevices= in the + * container can work + * fine */ + "/dev/null", "rwm", + "/dev/zero", "rwm", + "/dev/full", "rwm", + "/dev/random", "rwm", + "/dev/urandom", "rwm", + "/dev/tty", "rwm", + "/dev/net/tun", "rwm", + /* Allow the container + * access to ptys. However, + * do not permit the + * container to ever create + * these device nodes. */ + "/dev/pts/ptmx", "rw", + "char-pts", "rw"); + if (r < 0) + return bus_log_create_error(r); + + for (j = 0; j < n_mounts; j++) { + CustomMount *cm = mounts + j; + + if (cm->type != CUSTOM_MOUNT_BIND) + continue; + + r = is_device_node(cm->source); + if (r < 0) + return log_error_errno(r, "Failed to stat %s: %m", cm->source); + + if (r) { + r = sd_bus_message_append(m, "(sv)", "DeviceAllow", "a(ss)", 1, + cm->source, cm->read_only ? "r" : "rw"); + if (r < 0) + return log_error_errno(r, "Failed to append message arguments: %m"); + } + } + + if (kill_signal != 0) { + r = sd_bus_message_append(m, "(sv)", "KillSignal", "i", kill_signal); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "(sv)", "KillMode", "s", "mixed"); + if (r < 0) + return bus_log_create_error(r); + } + + STRV_FOREACH(i, properties) { + r = bus_append_unit_property_assignment(m, *i); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, NULL); + } + + if (r < 0) { + log_error("Failed to register machine: %s", bus_error_message(&error, r)); + return r; + } + + return 0; +} + +int terminate_machine(pid_t pid) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + const char *path; + int r; + + r = sd_bus_default_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to open system bus: %m"); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachineByPID", + &error, + &reply, + "u", + (uint32_t) pid); + if (r < 0) { + /* Note that the machine might already have been + * cleaned up automatically, hence don't consider it a + * failure if we cannot get the machine object. */ + log_debug("Failed to get machine: %s", bus_error_message(&error, r)); + return 0; + } + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + path, + "org.freedesktop.machine1.Machine", + "Terminate", + &error, + NULL, + NULL); + if (r < 0) { + log_debug("Failed to terminate machine: %s", bus_error_message(&error, r)); + return 0; + } + + return 0; +} diff --git a/src/systemd-nspawn/nspawn-register.h b/src/systemd-nspawn/nspawn-register.h new file mode 100644 index 0000000000..c7a50f7477 --- /dev/null +++ b/src/systemd-nspawn/nspawn-register.h @@ -0,0 +1,29 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 + +#include "nspawn-mount.h" + +int register_machine(const char *machine_name, pid_t pid, const char *directory, sd_id128_t uuid, int local_ifindex, const char *slice, CustomMount *mounts, unsigned n_mounts, int kill_signal, char **properties, bool keep_unit, const char *service); +int terminate_machine(pid_t pid); diff --git a/src/systemd-nspawn/nspawn-settings.c b/src/systemd-nspawn/nspawn-settings.c new file mode 100644 index 0000000000..5f1522cfb6 --- /dev/null +++ b/src/systemd-nspawn/nspawn-settings.c @@ -0,0 +1,516 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 "alloc-util.h" +#include "cap-list.h" +#include "conf-parser.h" +#include "nspawn-network.h" +#include "nspawn-settings.h" +#include "parse-util.h" +#include "process-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" +#include "util.h" + +int settings_load(FILE *f, const char *path, Settings **ret) { + _cleanup_(settings_freep) Settings *s = NULL; + int r; + + assert(path); + assert(ret); + + s = new0(Settings, 1); + if (!s) + return -ENOMEM; + + s->start_mode = _START_MODE_INVALID; + s->personality = PERSONALITY_INVALID; + s->userns_mode = _USER_NAMESPACE_MODE_INVALID; + s->uid_shift = UID_INVALID; + s->uid_range = UID_INVALID; + + s->read_only = -1; + s->volatile_mode = _VOLATILE_MODE_INVALID; + s->userns_chown = -1; + + s->private_network = -1; + s->network_veth = -1; + + r = config_parse(NULL, path, f, + "Exec\0" + "Network\0" + "Files\0", + config_item_perf_lookup, nspawn_gperf_lookup, + false, + false, + true, + s); + if (r < 0) + return r; + + /* Make sure that if userns_mode is set, userns_chown is set to something appropriate, and vice versa. Either + * both fields shall be initialized or neither. */ + if (s->userns_mode == USER_NAMESPACE_PICK) + s->userns_chown = true; + else if (s->userns_mode != _USER_NAMESPACE_MODE_INVALID && s->userns_chown < 0) + s->userns_chown = false; + + if (s->userns_chown >= 0 && s->userns_mode == _USER_NAMESPACE_MODE_INVALID) + s->userns_mode = USER_NAMESPACE_NO; + + *ret = s; + s = NULL; + + return 0; +} + +Settings* settings_free(Settings *s) { + + if (!s) + return NULL; + + strv_free(s->parameters); + strv_free(s->environment); + free(s->user); + free(s->working_directory); + + strv_free(s->network_interfaces); + strv_free(s->network_macvlan); + strv_free(s->network_ipvlan); + strv_free(s->network_veth_extra); + free(s->network_bridge); + free(s->network_zone); + expose_port_free_all(s->expose_ports); + + custom_mount_free_all(s->custom_mounts, s->n_custom_mounts); + free(s); + + return NULL; +} + +bool settings_private_network(Settings *s) { + assert(s); + + return + s->private_network > 0 || + s->network_veth > 0 || + s->network_bridge || + s->network_zone || + s->network_interfaces || + s->network_macvlan || + s->network_ipvlan || + s->network_veth_extra; +} + +bool settings_network_veth(Settings *s) { + assert(s); + + return + s->network_veth > 0 || + s->network_bridge || + s->network_zone; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_volatile_mode, volatile_mode, VolatileMode, "Failed to parse volatile mode"); + +int config_parse_expose_port( + 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) { + + Settings *s = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = expose_port_parse(&s->expose_ports, rvalue); + if (r == -EEXIST) { + log_syntax(unit, LOG_ERR, filename, line, r, "Duplicate port specification, ignoring: %s", rvalue); + return 0; + } + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse host port %s: %m", rvalue); + return 0; + } + + return 0; +} + +int config_parse_capability( + 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) { + + uint64_t u = 0, *result = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + for (;;) { + _cleanup_free_ char *word = NULL; + int cap; + + r = extract_first_word(&rvalue, &word, NULL, 0); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract capability string, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + break; + + cap = capability_from_name(word); + if (cap < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse capability, ignoring: %s", word); + continue; + } + + u |= 1 << ((uint64_t) cap); + } + + if (u == 0) + return 0; + + *result |= u; + return 0; +} + +int config_parse_id128( + 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) { + + sd_id128_t t, *result = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = sd_id128_from_string(rvalue, &t); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse 128bit ID/UUID, ignoring: %s", rvalue); + return 0; + } + + *result = t; + return 0; +} + +int config_parse_bind( + 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) { + + Settings *settings = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = bind_mount_parse(&settings->custom_mounts, &settings->n_custom_mounts, rvalue, ltype); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Invalid bind mount specification %s: %m", rvalue); + return 0; + } + + return 0; +} + +int config_parse_tmpfs( + 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) { + + Settings *settings = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = tmpfs_mount_parse(&settings->custom_mounts, &settings->n_custom_mounts, rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Invalid temporary file system specification %s: %m", rvalue); + return 0; + } + + return 0; +} + +int config_parse_veth_extra( + 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) { + + Settings *settings = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = veth_extra_parse(&settings->network_veth_extra, rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Invalid extra virtual Ethernet link specification %s: %m", rvalue); + return 0; + } + + return 0; +} + +int config_parse_network_zone( + 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) { + + Settings *settings = data; + _cleanup_free_ char *j = NULL; + + assert(filename); + assert(lvalue); + assert(rvalue); + + j = strappend("vz-", rvalue); + if (!ifname_valid(j)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid network zone name %s, ignoring: %m", rvalue); + return 0; + } + + free(settings->network_zone); + settings->network_zone = j; + j = NULL; + + return 0; +} + +int config_parse_boot( + 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) { + + Settings *settings = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse Boot= parameter %s, ignoring: %m", rvalue); + return 0; + } + + if (r > 0) { + if (settings->start_mode == START_PID2) + goto conflict; + + settings->start_mode = START_BOOT; + } else { + if (settings->start_mode == START_BOOT) + goto conflict; + + if (settings->start_mode < 0) + settings->start_mode = START_PID1; + } + + return 0; + +conflict: + log_syntax(unit, LOG_ERR, filename, line, r, "Conflicting Boot= or ProcessTwo= setting found. Ignoring."); + return 0; +} + +int config_parse_pid2( + 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) { + + Settings *settings = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse ProcessTwo= parameter %s, ignoring: %m", rvalue); + return 0; + } + + if (r > 0) { + if (settings->start_mode == START_BOOT) + goto conflict; + + settings->start_mode = START_PID2; + } else { + if (settings->start_mode == START_PID2) + goto conflict; + + if (settings->start_mode < 0) + settings->start_mode = START_PID1; + } + + return 0; + +conflict: + log_syntax(unit, LOG_ERR, filename, line, r, "Conflicting Boot= or ProcessTwo= setting found. Ignoring."); + return 0; +} + +int config_parse_private_users( + 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) { + + Settings *settings = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = parse_boolean(rvalue); + if (r == 0) { + /* no: User namespacing off */ + settings->userns_mode = USER_NAMESPACE_NO; + settings->uid_shift = UID_INVALID; + settings->uid_range = UINT32_C(0x10000); + } else if (r > 0) { + /* yes: User namespacing on, UID range is read from root dir */ + settings->userns_mode = USER_NAMESPACE_FIXED; + settings->uid_shift = UID_INVALID; + settings->uid_range = UINT32_C(0x10000); + } else if (streq(rvalue, "pick")) { + /* pick: User namespacing on, UID range is picked randomly */ + settings->userns_mode = USER_NAMESPACE_PICK; + settings->uid_shift = UID_INVALID; + settings->uid_range = UINT32_C(0x10000); + } else { + const char *range, *shift; + uid_t sh, rn; + + /* anything else: User namespacing on, UID range is explicitly configured */ + + range = strchr(rvalue, ':'); + if (range) { + shift = strndupa(rvalue, range - rvalue); + range++; + + r = safe_atou32(range, &rn); + if (r < 0 || rn <= 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "UID/GID range invalid, ignoring: %s", range); + return 0; + } + } else { + shift = rvalue; + rn = UINT32_C(0x10000); + } + + r = parse_uid(shift, &sh); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "UID/GID shift invalid, ignoring: %s", range); + return 0; + } + + settings->userns_mode = USER_NAMESPACE_FIXED; + settings->uid_shift = sh; + settings->uid_range = rn; + } + + return 0; +} diff --git a/src/systemd-nspawn/nspawn-settings.h b/src/systemd-nspawn/nspawn-settings.h new file mode 100644 index 0000000000..1c47e37912 --- /dev/null +++ b/src/systemd-nspawn/nspawn-settings.h @@ -0,0 +1,116 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 "macro.h" +#include "nspawn-expose-ports.h" +#include "nspawn-mount.h" + +typedef enum StartMode { + START_PID1, /* Run parameters as command line as process 1 */ + START_PID2, /* Use stub init process as PID 1, run parameters as command line as process 2 */ + START_BOOT, /* Search for init system, pass arguments as parameters */ + _START_MODE_MAX, + _START_MODE_INVALID = -1 +} StartMode; + +typedef enum UserNamespaceMode { + USER_NAMESPACE_NO, + USER_NAMESPACE_FIXED, + USER_NAMESPACE_PICK, + _USER_NAMESPACE_MODE_MAX, + _USER_NAMESPACE_MODE_INVALID = -1, +} UserNamespaceMode; + +typedef enum SettingsMask { + SETTING_START_MODE = 1 << 0, + SETTING_ENVIRONMENT = 1 << 1, + SETTING_USER = 1 << 2, + SETTING_CAPABILITY = 1 << 3, + SETTING_KILL_SIGNAL = 1 << 4, + SETTING_PERSONALITY = 1 << 5, + SETTING_MACHINE_ID = 1 << 6, + SETTING_NETWORK = 1 << 7, + SETTING_EXPOSE_PORTS = 1 << 8, + SETTING_READ_ONLY = 1 << 9, + SETTING_VOLATILE_MODE = 1 << 10, + SETTING_CUSTOM_MOUNTS = 1 << 11, + SETTING_WORKING_DIRECTORY = 1 << 12, + SETTING_USERNS = 1 << 13, + _SETTINGS_MASK_ALL = (1 << 14) -1 +} SettingsMask; + +typedef struct Settings { + /* [Run] */ + StartMode start_mode; + char **parameters; + char **environment; + char *user; + uint64_t capability; + uint64_t drop_capability; + int kill_signal; + unsigned long personality; + sd_id128_t machine_id; + char *working_directory; + UserNamespaceMode userns_mode; + uid_t uid_shift, uid_range; + + /* [Image] */ + int read_only; + VolatileMode volatile_mode; + CustomMount *custom_mounts; + unsigned n_custom_mounts; + int userns_chown; + + /* [Network] */ + int private_network; + int network_veth; + char *network_bridge; + char *network_zone; + char **network_interfaces; + char **network_macvlan; + char **network_ipvlan; + char **network_veth_extra; + ExposePort *expose_ports; +} Settings; + +int settings_load(FILE *f, const char *path, Settings **ret); +Settings* settings_free(Settings *s); + +bool settings_network_veth(Settings *s); +bool settings_private_network(Settings *s); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Settings*, settings_free); + +const struct ConfigPerfItem* nspawn_gperf_lookup(const char *key, unsigned length); + +int config_parse_capability(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); +int config_parse_id128(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); +int config_parse_expose_port(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); +int config_parse_volatile_mode(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); +int config_parse_bind(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); +int config_parse_tmpfs(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); +int config_parse_veth_extra(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); +int config_parse_network_zone(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); +int config_parse_boot(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); +int config_parse_pid2(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); +int config_parse_private_users(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); diff --git a/src/systemd-nspawn/nspawn-setuid.c b/src/systemd-nspawn/nspawn-setuid.c new file mode 100644 index 0000000000..ee15a47e93 --- /dev/null +++ b/src/systemd-nspawn/nspawn-setuid.c @@ -0,0 +1,273 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "mkdir.h" +#include "nspawn-setuid.h" +#include "process-util.h" +#include "signal-util.h" +#include "string-util.h" +#include "user-util.h" +#include "util.h" + +static int spawn_getent(const char *database, const char *key, pid_t *rpid) { + int pipe_fds[2]; + pid_t pid; + + assert(database); + assert(key); + assert(rpid); + + if (pipe2(pipe_fds, O_CLOEXEC) < 0) + return log_error_errno(errno, "Failed to allocate pipe: %m"); + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork getent child: %m"); + else if (pid == 0) { + int nullfd; + char *empty_env = NULL; + + if (dup3(pipe_fds[1], STDOUT_FILENO, 0) < 0) + _exit(EXIT_FAILURE); + + if (pipe_fds[0] > 2) + safe_close(pipe_fds[0]); + if (pipe_fds[1] > 2) + safe_close(pipe_fds[1]); + + nullfd = open("/dev/null", O_RDWR); + if (nullfd < 0) + _exit(EXIT_FAILURE); + + if (dup3(nullfd, STDIN_FILENO, 0) < 0) + _exit(EXIT_FAILURE); + + if (dup3(nullfd, STDERR_FILENO, 0) < 0) + _exit(EXIT_FAILURE); + + if (nullfd > 2) + safe_close(nullfd); + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + close_all_fds(NULL, 0); + + execle("/usr/bin/getent", "getent", database, key, NULL, &empty_env); + execle("/bin/getent", "getent", database, key, NULL, &empty_env); + _exit(EXIT_FAILURE); + } + + pipe_fds[1] = safe_close(pipe_fds[1]); + + *rpid = pid; + + return pipe_fds[0]; +} + +int change_uid_gid(const char *user, char **_home) { + char line[LINE_MAX], *x, *u, *g, *h; + const char *word, *state; + _cleanup_free_ uid_t *uids = NULL; + _cleanup_free_ char *home = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_close_ int fd = -1; + unsigned n_uids = 0; + size_t sz = 0, l; + uid_t uid; + gid_t gid; + pid_t pid; + int r; + + assert(_home); + + if (!user || streq(user, "root") || streq(user, "0")) { + /* Reset everything fully to 0, just in case */ + + r = reset_uid_gid(); + if (r < 0) + return log_error_errno(r, "Failed to become root: %m"); + + *_home = NULL; + return 0; + } + + /* First, get user credentials */ + fd = spawn_getent("passwd", user, &pid); + if (fd < 0) + return fd; + + f = fdopen(fd, "r"); + if (!f) + return log_oom(); + fd = -1; + + if (!fgets(line, sizeof(line), f)) { + + if (!ferror(f)) { + log_error("Failed to resolve user %s.", user); + return -ESRCH; + } + + log_error_errno(errno, "Failed to read from getent: %m"); + return -errno; + } + + truncate_nl(line); + + wait_for_terminate_and_warn("getent passwd", pid, true); + + x = strchr(line, ':'); + if (!x) { + log_error("/etc/passwd entry has invalid user field."); + return -EIO; + } + + u = strchr(x+1, ':'); + if (!u) { + log_error("/etc/passwd entry has invalid password field."); + return -EIO; + } + + u++; + g = strchr(u, ':'); + if (!g) { + log_error("/etc/passwd entry has invalid UID field."); + return -EIO; + } + + *g = 0; + g++; + x = strchr(g, ':'); + if (!x) { + log_error("/etc/passwd entry has invalid GID field."); + return -EIO; + } + + *x = 0; + h = strchr(x+1, ':'); + if (!h) { + log_error("/etc/passwd entry has invalid GECOS field."); + return -EIO; + } + + h++; + x = strchr(h, ':'); + if (!x) { + log_error("/etc/passwd entry has invalid home directory field."); + return -EIO; + } + + *x = 0; + + r = parse_uid(u, &uid); + if (r < 0) { + log_error("Failed to parse UID of user."); + return -EIO; + } + + r = parse_gid(g, &gid); + if (r < 0) { + log_error("Failed to parse GID of user."); + return -EIO; + } + + home = strdup(h); + if (!home) + return log_oom(); + + /* Second, get group memberships */ + fd = spawn_getent("initgroups", user, &pid); + if (fd < 0) + return fd; + + fclose(f); + f = fdopen(fd, "r"); + if (!f) + return log_oom(); + fd = -1; + + if (!fgets(line, sizeof(line), f)) { + if (!ferror(f)) { + log_error("Failed to resolve user %s.", user); + return -ESRCH; + } + + log_error_errno(errno, "Failed to read from getent: %m"); + return -errno; + } + + truncate_nl(line); + + wait_for_terminate_and_warn("getent initgroups", pid, true); + + /* Skip over the username and subsequent separator whitespace */ + x = line; + x += strcspn(x, WHITESPACE); + x += strspn(x, WHITESPACE); + + FOREACH_WORD(word, l, x, state) { + char c[l+1]; + + memcpy(c, word, l); + c[l] = 0; + + if (!GREEDY_REALLOC(uids, sz, n_uids+1)) + return log_oom(); + + r = parse_uid(c, &uids[n_uids++]); + if (r < 0) { + log_error("Failed to parse group data from getent."); + return -EIO; + } + } + + r = mkdir_parents(home, 0775); + if (r < 0) + return log_error_errno(r, "Failed to make home root directory: %m"); + + r = mkdir_safe(home, 0755, uid, gid); + if (r < 0 && r != -EEXIST) + return log_error_errno(r, "Failed to make home directory: %m"); + + (void) fchown(STDIN_FILENO, uid, gid); + (void) fchown(STDOUT_FILENO, uid, gid); + (void) fchown(STDERR_FILENO, uid, gid); + + if (setgroups(n_uids, uids) < 0) + return log_error_errno(errno, "Failed to set auxiliary groups: %m"); + + if (setresgid(gid, gid, gid) < 0) + return log_error_errno(errno, "setresgid() failed: %m"); + + if (setresuid(uid, uid, uid) < 0) + return log_error_errno(errno, "setresuid() failed: %m"); + + if (_home) { + *_home = home; + home = NULL; + } + + return 0; +} diff --git a/src/systemd-nspawn/nspawn-setuid.h b/src/systemd-nspawn/nspawn-setuid.h new file mode 100644 index 0000000000..b4968ba1fc --- /dev/null +++ b/src/systemd-nspawn/nspawn-setuid.h @@ -0,0 +1,22 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +int change_uid_gid(const char *user, char **ret); diff --git a/src/systemd-nspawn/nspawn-stub-pid1.c b/src/systemd-nspawn/nspawn-stub-pid1.c new file mode 100644 index 0000000000..2de87e3c63 --- /dev/null +++ b/src/systemd-nspawn/nspawn-stub-pid1.c @@ -0,0 +1,170 @@ +/*** + This file is part of systemd. + + Copyright 2016 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 +#include + +#include "fd-util.h" +#include "log.h" +#include "nspawn-stub-pid1.h" +#include "process-util.h" +#include "signal-util.h" +#include "time-util.h" +#include "def.h" + +int stub_pid1(void) { + enum { + STATE_RUNNING, + STATE_REBOOT, + STATE_POWEROFF, + } state = STATE_RUNNING; + + sigset_t fullmask, oldmask, waitmask; + usec_t quit_usec = USEC_INFINITY; + pid_t pid; + int r; + + /* Implements a stub PID 1, that reaps all processes and processes a couple of standard signals. This is useful + * for allowing arbitrary processes run in a container, and still have all zombies reaped. */ + + assert_se(sigfillset(&fullmask) >= 0); + assert_se(sigprocmask(SIG_BLOCK, &fullmask, &oldmask) >= 0); + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork child pid: %m"); + + if (pid == 0) { + /* Return in the child */ + assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) >= 0); + setsid(); + return 0; + } + + reset_all_signal_handlers(); + + log_close(); + close_all_fds(NULL, 0); + log_open(); + + rename_process("STUBINIT"); + + assert_se(sigemptyset(&waitmask) >= 0); + assert_se(sigset_add_many(&waitmask, + SIGCHLD, /* posix: process died */ + SIGINT, /* sysv: ctrl-alt-del */ + SIGRTMIN+3, /* systemd: halt */ + SIGRTMIN+4, /* systemd: poweroff */ + SIGRTMIN+5, /* systemd: reboot */ + SIGRTMIN+6, /* systemd: kexec */ + SIGRTMIN+13, /* systemd: halt */ + SIGRTMIN+14, /* systemd: poweroff */ + SIGRTMIN+15, /* systemd: reboot */ + SIGRTMIN+16, /* systemd: kexec */ + -1) >= 0); + + /* Note that we ignore SIGTERM (sysv's reexec), SIGHUP (reload), and all other signals here, since we don't + * support reexec/reloading in this stub process. */ + + for (;;) { + siginfo_t si; + usec_t current_usec; + + si.si_pid = 0; + r = waitid(P_ALL, 0, &si, WEXITED|WNOHANG); + if (r < 0) { + r = log_error_errno(errno, "Failed to reap children: %m"); + goto finish; + } + + current_usec = now(CLOCK_MONOTONIC); + + if (si.si_pid == pid || current_usec >= quit_usec) { + + /* The child we started ourselves died or we reached a timeout. */ + + if (state == STATE_REBOOT) { /* dispatch a queued reboot */ + (void) reboot(RB_AUTOBOOT); + r = log_error_errno(errno, "Failed to reboot: %m"); + goto finish; + + } else if (state == STATE_POWEROFF) + (void) reboot(RB_POWER_OFF); /* if this fails, fall back to normal exit. */ + + if (si.si_pid == pid && si.si_code == CLD_EXITED) + r = si.si_status; /* pass on exit code */ + else + r = 255; /* signal, coredump, timeout, … */ + + goto finish; + } + if (si.si_pid != 0) + /* We reaped something. Retry until there's nothing more to reap. */ + continue; + + if (quit_usec == USEC_INFINITY) + r = sigwaitinfo(&waitmask, &si); + else { + struct timespec ts; + r = sigtimedwait(&waitmask, &si, timespec_store(&ts, quit_usec - current_usec)); + } + if (r < 0) { + if (errno == EINTR) /* strace -p attach can result in EINTR, let's handle this nicely. */ + continue; + if (errno == EAGAIN) /* timeout reached */ + continue; + + r = log_error_errno(errno, "Failed to wait for signal: %m"); + goto finish; + } + + if (si.si_signo == SIGCHLD) + continue; /* Let's reap this */ + + if (state != STATE_RUNNING) + continue; + + /* Would love to use a switch() statement here, but SIGRTMIN is actually a function call, not a + * constant… */ + + if (si.si_signo == SIGRTMIN+3 || + si.si_signo == SIGRTMIN+4 || + si.si_signo == SIGRTMIN+13 || + si.si_signo == SIGRTMIN+14) + + state = STATE_POWEROFF; + + else if (si.si_signo == SIGINT || + si.si_signo == SIGRTMIN+5 || + si.si_signo == SIGRTMIN+6 || + si.si_signo == SIGRTMIN+15 || + si.si_signo == SIGRTMIN+16) + + state = STATE_REBOOT; + else + assert_not_reached("Got unexpected signal"); + + /* (void) kill_and_sigcont(pid, SIGTERM); */ + quit_usec = now(CLOCK_MONOTONIC) + DEFAULT_TIMEOUT_USEC; + } + +finish: + _exit(r < 0 ? EXIT_FAILURE : r); +} diff --git a/src/systemd-nspawn/nspawn-stub-pid1.h b/src/systemd-nspawn/nspawn-stub-pid1.h new file mode 100644 index 0000000000..36c1aaf5dd --- /dev/null +++ b/src/systemd-nspawn/nspawn-stub-pid1.h @@ -0,0 +1,22 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 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 . +***/ + +int stub_pid1(void); diff --git a/src/systemd-nspawn/nspawn.c b/src/systemd-nspawn/nspawn.c new file mode 100644 index 0000000000..bdf054e5c6 --- /dev/null +++ b/src/systemd-nspawn/nspawn.c @@ -0,0 +1,4116 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#ifdef HAVE_BLKID +#include +#endif +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SECCOMP +#include +#endif +#ifdef HAVE_SELINUX +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "alloc-util.h" +#include "barrier.h" +#include "base-filesystem.h" +#include "blkid-util.h" +#include "btrfs-util.h" +#include "cap-list.h" +#include "capability-util.h" +#include "cgroup-util.h" +#include "copy.h" +#include "dev-setup.h" +#include "env-util.h" +#include "fd-util.h" +#include "fdset.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "gpt.h" +#include "hostname-util.h" +#include "log.h" +#include "loopback-setup.h" +#include "machine-id-setup.h" +#include "machine-image.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "mount-util.h" +#include "netlink-util.h" +#include "nspawn-cgroup.h" +#include "nspawn-expose-ports.h" +#include "nspawn-mount.h" +#include "nspawn-network.h" +#include "nspawn-patch-uid.h" +#include "nspawn-register.h" +#include "nspawn-settings.h" +#include "nspawn-setuid.h" +#include "nspawn-stub-pid1.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "ptyfwd.h" +#include "random-util.h" +#include "rm-rf.h" +#ifdef HAVE_SECCOMP +#include "seccomp-util.h" +#endif +#include "selinux-util.h" +#include "signal-util.h" +#include "socket-util.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "udev-util.h" +#include "umask-util.h" +#include "user-util.h" +#include "util.h" + +/* Note that devpts's gid= parameter parses GIDs as signed values, hence we stay away from the upper half of the 32bit + * UID range here */ +#define UID_SHIFT_PICK_MIN ((uid_t) UINT32_C(0x00080000)) +#define UID_SHIFT_PICK_MAX ((uid_t) UINT32_C(0x6FFF0000)) + +typedef enum ContainerStatus { + CONTAINER_TERMINATED, + CONTAINER_REBOOTED +} ContainerStatus; + +typedef enum LinkJournal { + LINK_NO, + LINK_AUTO, + LINK_HOST, + LINK_GUEST +} LinkJournal; + +static char *arg_directory = NULL; +static char *arg_template = NULL; +static char *arg_chdir = NULL; +static char *arg_user = NULL; +static sd_id128_t arg_uuid = {}; +static char *arg_machine = NULL; +static const char *arg_selinux_context = NULL; +static const char *arg_selinux_apifs_context = NULL; +static const char *arg_slice = NULL; +static bool arg_private_network = false; +static bool arg_read_only = false; +static StartMode arg_start_mode = START_PID1; +static bool arg_ephemeral = false; +static LinkJournal arg_link_journal = LINK_AUTO; +static bool arg_link_journal_try = false; +static uint64_t arg_retain = + (1ULL << CAP_CHOWN) | + (1ULL << CAP_DAC_OVERRIDE) | + (1ULL << CAP_DAC_READ_SEARCH) | + (1ULL << CAP_FOWNER) | + (1ULL << CAP_FSETID) | + (1ULL << CAP_IPC_OWNER) | + (1ULL << CAP_KILL) | + (1ULL << CAP_LEASE) | + (1ULL << CAP_LINUX_IMMUTABLE) | + (1ULL << CAP_NET_BIND_SERVICE) | + (1ULL << CAP_NET_BROADCAST) | + (1ULL << CAP_NET_RAW) | + (1ULL << CAP_SETGID) | + (1ULL << CAP_SETFCAP) | + (1ULL << CAP_SETPCAP) | + (1ULL << CAP_SETUID) | + (1ULL << CAP_SYS_ADMIN) | + (1ULL << CAP_SYS_CHROOT) | + (1ULL << CAP_SYS_NICE) | + (1ULL << CAP_SYS_PTRACE) | + (1ULL << CAP_SYS_TTY_CONFIG) | + (1ULL << CAP_SYS_RESOURCE) | + (1ULL << CAP_SYS_BOOT) | + (1ULL << CAP_AUDIT_WRITE) | + (1ULL << CAP_AUDIT_CONTROL) | + (1ULL << CAP_MKNOD); +static CustomMount *arg_custom_mounts = NULL; +static unsigned arg_n_custom_mounts = 0; +static char **arg_setenv = NULL; +static bool arg_quiet = false; +static bool arg_share_system = false; +static bool arg_register = true; +static bool arg_keep_unit = false; +static char **arg_network_interfaces = NULL; +static char **arg_network_macvlan = NULL; +static char **arg_network_ipvlan = NULL; +static bool arg_network_veth = false; +static char **arg_network_veth_extra = NULL; +static char *arg_network_bridge = NULL; +static char *arg_network_zone = NULL; +static unsigned long arg_personality = PERSONALITY_INVALID; +static char *arg_image = NULL; +static VolatileMode arg_volatile_mode = VOLATILE_NO; +static ExposePort *arg_expose_ports = NULL; +static char **arg_property = NULL; +static UserNamespaceMode arg_userns_mode = USER_NAMESPACE_NO; +static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U; +static bool arg_userns_chown = false; +static int arg_kill_signal = 0; +static bool arg_unified_cgroup_hierarchy = false; +static SettingsMask arg_settings_mask = 0; +static int arg_settings_trusted = -1; +static char **arg_parameters = NULL; +static const char *arg_container_service_name = "systemd-nspawn"; + +static void help(void) { + printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" + "Spawn a minimal namespace container for debugging, testing and building.\n\n" + " -h --help Show this help\n" + " --version Print version string\n" + " -q --quiet Do not show status information\n" + " -D --directory=PATH Root directory for the container\n" + " --template=PATH Initialize root directory from template directory,\n" + " if missing\n" + " -x --ephemeral Run container with snapshot of root directory, and\n" + " remove it after exit\n" + " -i --image=PATH File system device or disk image for the container\n" + " -a --as-pid2 Maintain a stub init as PID1, invoke binary as PID2\n" + " -b --boot Boot up full system (i.e. invoke init)\n" + " --chdir=PATH Set working directory in the container\n" + " -u --user=USER Run the command under specified user or uid\n" + " -M --machine=NAME Set the machine name for the container\n" + " --uuid=UUID Set a specific machine UUID for the container\n" + " -S --slice=SLICE Place the container in the specified slice\n" + " --property=NAME=VALUE Set scope unit property\n" + " -U --private-users=pick Run within user namespace, pick UID/GID range automatically\n" + " --private-users[=UIDBASE[:NUIDS]]\n" + " Run within user namespace, user configured UID/GID range\n" + " --private-user-chown Adjust OS tree file ownership for private UID/GID range\n" + " --private-network Disable network in container\n" + " --network-interface=INTERFACE\n" + " Assign an existing network interface to the\n" + " container\n" + " --network-macvlan=INTERFACE\n" + " Create a macvlan network interface based on an\n" + " existing network interface to the container\n" + " --network-ipvlan=INTERFACE\n" + " Create a ipvlan network interface based on an\n" + " existing network interface to the container\n" + " -n --network-veth Add a virtual Ethernet connection between host\n" + " and container\n" + " --network-veth-extra=HOSTIF[:CONTAINERIF]\n" + " Add an additional virtual Ethernet link between\n" + " host and container\n" + " --network-bridge=INTERFACE\n" + " Add a virtual Ethernet connection between host\n" + " and container and add it to an existing bridge on\n" + " the host\n" + " --network-zone=NAME Add a virtual Ethernet connection to the container,\n" + " and add it to an automatically managed bridge interface\n" + " -p --port=[PROTOCOL:]HOSTPORT[:CONTAINERPORT]\n" + " Expose a container IP port on the host\n" + " -Z --selinux-context=SECLABEL\n" + " Set the SELinux security context to be used by\n" + " processes in the container\n" + " -L --selinux-apifs-context=SECLABEL\n" + " Set the SELinux security context to be used by\n" + " API/tmpfs file systems in the container\n" + " --capability=CAP In addition to the default, retain specified\n" + " capability\n" + " --drop-capability=CAP Drop the specified capability from the default set\n" + " --kill-signal=SIGNAL Select signal to use for shutting down PID 1\n" + " --link-journal=MODE Link up guest journal, one of no, auto, guest, \n" + " host, try-guest, try-host\n" + " -j Equivalent to --link-journal=try-guest\n" + " --read-only Mount the root directory read-only\n" + " --bind=PATH[:PATH[:OPTIONS]]\n" + " Bind mount a file or directory from the host into\n" + " the container\n" + " --bind-ro=PATH[:PATH[:OPTIONS]\n" + " Similar, but creates a read-only bind mount\n" + " --tmpfs=PATH:[OPTIONS] Mount an empty tmpfs to the specified directory\n" + " --overlay=PATH[:PATH...]:PATH\n" + " Create an overlay mount from the host to \n" + " the container\n" + " --overlay-ro=PATH[:PATH...]:PATH\n" + " Similar, but creates a read-only overlay mount\n" + " -E --setenv=NAME=VALUE Pass an environment variable to PID 1\n" + " --share-system Share system namespaces with host\n" + " --register=BOOLEAN Register container as machine\n" + " --keep-unit Do not register a scope for the machine, reuse\n" + " the service unit nspawn is running in\n" + " --volatile[=MODE] Run the system in volatile mode\n" + " --settings=BOOLEAN Load additional settings from .nspawn file\n" + , program_invocation_short_name); +} + + +static int custom_mounts_prepare(void) { + unsigned i; + int r; + + /* Ensure the mounts are applied prefix first. */ + qsort_safe(arg_custom_mounts, arg_n_custom_mounts, sizeof(CustomMount), custom_mount_compare); + + /* Allocate working directories for the overlay file systems that need it */ + for (i = 0; i < arg_n_custom_mounts; i++) { + CustomMount *m = &arg_custom_mounts[i]; + + if (path_equal(m->destination, "/") && arg_userns_mode != USER_NAMESPACE_NO) { + + if (arg_userns_chown) { + log_error("--private-users-chown may not be combined with custom root mounts."); + return -EINVAL; + } else if (arg_uid_shift == UID_INVALID) { + log_error("--private-users with automatic UID shift may not be combined with custom root mounts."); + return -EINVAL; + } + } + + if (m->type != CUSTOM_MOUNT_OVERLAY) + continue; + + if (m->work_dir) + continue; + + if (m->read_only) + continue; + + r = tempfn_random(m->source, NULL, &m->work_dir); + if (r < 0) + return log_error_errno(r, "Failed to generate work directory from %s: %m", m->source); + } + + return 0; +} + +static int detect_unified_cgroup_hierarchy(void) { + const char *e; + int r; + + /* Allow the user to control whether the unified hierarchy is used */ + e = getenv("UNIFIED_CGROUP_HIERARCHY"); + if (e) { + r = parse_boolean(e); + if (r < 0) + return log_error_errno(r, "Failed to parse $UNIFIED_CGROUP_HIERARCHY."); + + arg_unified_cgroup_hierarchy = r; + return 0; + } + + /* Otherwise inherit the default from the host system */ + r = cg_unified(); + if (r < 0) + return log_error_errno(r, "Failed to determine whether the unified cgroups hierarchy is used: %m"); + + arg_unified_cgroup_hierarchy = r; + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_PRIVATE_NETWORK, + ARG_UUID, + ARG_READ_ONLY, + ARG_CAPABILITY, + ARG_DROP_CAPABILITY, + ARG_LINK_JOURNAL, + ARG_BIND, + ARG_BIND_RO, + ARG_TMPFS, + ARG_OVERLAY, + ARG_OVERLAY_RO, + ARG_SHARE_SYSTEM, + ARG_REGISTER, + ARG_KEEP_UNIT, + ARG_NETWORK_INTERFACE, + ARG_NETWORK_MACVLAN, + ARG_NETWORK_IPVLAN, + ARG_NETWORK_BRIDGE, + ARG_NETWORK_ZONE, + ARG_NETWORK_VETH_EXTRA, + ARG_PERSONALITY, + ARG_VOLATILE, + ARG_TEMPLATE, + ARG_PROPERTY, + ARG_PRIVATE_USERS, + ARG_KILL_SIGNAL, + ARG_SETTINGS, + ARG_CHDIR, + ARG_PRIVATE_USERS_CHOWN, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "directory", required_argument, NULL, 'D' }, + { "template", required_argument, NULL, ARG_TEMPLATE }, + { "ephemeral", no_argument, NULL, 'x' }, + { "user", required_argument, NULL, 'u' }, + { "private-network", no_argument, NULL, ARG_PRIVATE_NETWORK }, + { "as-pid2", no_argument, NULL, 'a' }, + { "boot", no_argument, NULL, 'b' }, + { "uuid", required_argument, NULL, ARG_UUID }, + { "read-only", no_argument, NULL, ARG_READ_ONLY }, + { "capability", required_argument, NULL, ARG_CAPABILITY }, + { "drop-capability", required_argument, NULL, ARG_DROP_CAPABILITY }, + { "link-journal", required_argument, NULL, ARG_LINK_JOURNAL }, + { "bind", required_argument, NULL, ARG_BIND }, + { "bind-ro", required_argument, NULL, ARG_BIND_RO }, + { "tmpfs", required_argument, NULL, ARG_TMPFS }, + { "overlay", required_argument, NULL, ARG_OVERLAY }, + { "overlay-ro", required_argument, NULL, ARG_OVERLAY_RO }, + { "machine", required_argument, NULL, 'M' }, + { "slice", required_argument, NULL, 'S' }, + { "setenv", required_argument, NULL, 'E' }, + { "selinux-context", required_argument, NULL, 'Z' }, + { "selinux-apifs-context", required_argument, NULL, 'L' }, + { "quiet", no_argument, NULL, 'q' }, + { "share-system", no_argument, NULL, ARG_SHARE_SYSTEM }, + { "register", required_argument, NULL, ARG_REGISTER }, + { "keep-unit", no_argument, NULL, ARG_KEEP_UNIT }, + { "network-interface", required_argument, NULL, ARG_NETWORK_INTERFACE }, + { "network-macvlan", required_argument, NULL, ARG_NETWORK_MACVLAN }, + { "network-ipvlan", required_argument, NULL, ARG_NETWORK_IPVLAN }, + { "network-veth", no_argument, NULL, 'n' }, + { "network-veth-extra", required_argument, NULL, ARG_NETWORK_VETH_EXTRA}, + { "network-bridge", required_argument, NULL, ARG_NETWORK_BRIDGE }, + { "network-zone", required_argument, NULL, ARG_NETWORK_ZONE }, + { "personality", required_argument, NULL, ARG_PERSONALITY }, + { "image", required_argument, NULL, 'i' }, + { "volatile", optional_argument, NULL, ARG_VOLATILE }, + { "port", required_argument, NULL, 'p' }, + { "property", required_argument, NULL, ARG_PROPERTY }, + { "private-users", optional_argument, NULL, ARG_PRIVATE_USERS }, + { "private-users-chown", optional_argument, NULL, ARG_PRIVATE_USERS_CHOWN}, + { "kill-signal", required_argument, NULL, ARG_KILL_SIGNAL }, + { "settings", required_argument, NULL, ARG_SETTINGS }, + { "chdir", required_argument, NULL, ARG_CHDIR }, + {} + }; + + int c, r; + const char *p, *e; + uint64_t plus = 0, minus = 0; + bool mask_all_settings = false, mask_no_settings = false; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "+hD:u:abL:M:jS:Z:qi:xp:nU", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case 'D': + r = parse_path_argument_and_warn(optarg, false, &arg_directory); + if (r < 0) + return r; + break; + + case ARG_TEMPLATE: + r = parse_path_argument_and_warn(optarg, false, &arg_template); + if (r < 0) + return r; + break; + + case 'i': + r = parse_path_argument_and_warn(optarg, false, &arg_image); + if (r < 0) + return r; + break; + + case 'x': + arg_ephemeral = true; + break; + + case 'u': + r = free_and_strdup(&arg_user, optarg); + if (r < 0) + return log_oom(); + + arg_settings_mask |= SETTING_USER; + break; + + case ARG_NETWORK_ZONE: { + char *j; + + j = strappend("vz-", optarg); + if (!j) + return log_oom(); + + if (!ifname_valid(j)) { + log_error("Network zone name not valid: %s", j); + free(j); + return -EINVAL; + } + + free(arg_network_zone); + arg_network_zone = j; + + arg_network_veth = true; + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; + break; + } + + case ARG_NETWORK_BRIDGE: + + if (!ifname_valid(optarg)) { + log_error("Bridge interface name not valid: %s", optarg); + return -EINVAL; + } + + r = free_and_strdup(&arg_network_bridge, optarg); + if (r < 0) + return log_oom(); + + /* fall through */ + + case 'n': + arg_network_veth = true; + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; + break; + + case ARG_NETWORK_VETH_EXTRA: + r = veth_extra_parse(&arg_network_veth_extra, optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --network-veth-extra= parameter: %s", optarg); + + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; + break; + + case ARG_NETWORK_INTERFACE: + + if (!ifname_valid(optarg)) { + log_error("Network interface name not valid: %s", optarg); + return -EINVAL; + } + + if (strv_extend(&arg_network_interfaces, optarg) < 0) + return log_oom(); + + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; + break; + + case ARG_NETWORK_MACVLAN: + + if (!ifname_valid(optarg)) { + log_error("MACVLAN network interface name not valid: %s", optarg); + return -EINVAL; + } + + if (strv_extend(&arg_network_macvlan, optarg) < 0) + return log_oom(); + + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; + break; + + case ARG_NETWORK_IPVLAN: + + if (!ifname_valid(optarg)) { + log_error("IPVLAN network interface name not valid: %s", optarg); + return -EINVAL; + } + + if (strv_extend(&arg_network_ipvlan, optarg) < 0) + return log_oom(); + + /* fall through */ + + case ARG_PRIVATE_NETWORK: + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; + break; + + case 'b': + if (arg_start_mode == START_PID2) { + log_error("--boot and --as-pid2 may not be combined."); + return -EINVAL; + } + + arg_start_mode = START_BOOT; + arg_settings_mask |= SETTING_START_MODE; + break; + + case 'a': + if (arg_start_mode == START_BOOT) { + log_error("--boot and --as-pid2 may not be combined."); + return -EINVAL; + } + + arg_start_mode = START_PID2; + arg_settings_mask |= SETTING_START_MODE; + break; + + case ARG_UUID: + r = sd_id128_from_string(optarg, &arg_uuid); + if (r < 0) { + log_error("Invalid UUID: %s", optarg); + return r; + } + + arg_settings_mask |= SETTING_MACHINE_ID; + break; + + case 'S': + arg_slice = optarg; + break; + + case 'M': + if (isempty(optarg)) + arg_machine = mfree(arg_machine); + else { + if (!machine_name_is_valid(optarg)) { + log_error("Invalid machine name: %s", optarg); + return -EINVAL; + } + + r = free_and_strdup(&arg_machine, optarg); + if (r < 0) + return log_oom(); + + break; + } + + case 'Z': + arg_selinux_context = optarg; + break; + + case 'L': + arg_selinux_apifs_context = optarg; + break; + + case ARG_READ_ONLY: + arg_read_only = true; + arg_settings_mask |= SETTING_READ_ONLY; + break; + + case ARG_CAPABILITY: + case ARG_DROP_CAPABILITY: { + p = optarg; + for (;;) { + _cleanup_free_ char *t = NULL; + + r = extract_first_word(&p, &t, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to parse capability %s.", t); + + if (r == 0) + break; + + if (streq(t, "all")) { + if (c == ARG_CAPABILITY) + plus = (uint64_t) -1; + else + minus = (uint64_t) -1; + } else { + int cap; + + cap = capability_from_name(t); + if (cap < 0) { + log_error("Failed to parse capability %s.", t); + return -EINVAL; + } + + if (c == ARG_CAPABILITY) + plus |= 1ULL << (uint64_t) cap; + else + minus |= 1ULL << (uint64_t) cap; + } + } + + arg_settings_mask |= SETTING_CAPABILITY; + break; + } + + case 'j': + arg_link_journal = LINK_GUEST; + arg_link_journal_try = true; + break; + + case ARG_LINK_JOURNAL: + if (streq(optarg, "auto")) { + arg_link_journal = LINK_AUTO; + arg_link_journal_try = false; + } else if (streq(optarg, "no")) { + arg_link_journal = LINK_NO; + arg_link_journal_try = false; + } else if (streq(optarg, "guest")) { + arg_link_journal = LINK_GUEST; + arg_link_journal_try = false; + } else if (streq(optarg, "host")) { + arg_link_journal = LINK_HOST; + arg_link_journal_try = false; + } else if (streq(optarg, "try-guest")) { + arg_link_journal = LINK_GUEST; + arg_link_journal_try = true; + } else if (streq(optarg, "try-host")) { + arg_link_journal = LINK_HOST; + arg_link_journal_try = true; + } else { + log_error("Failed to parse link journal mode %s", optarg); + return -EINVAL; + } + + break; + + case ARG_BIND: + case ARG_BIND_RO: + r = bind_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, optarg, c == ARG_BIND_RO); + if (r < 0) + return log_error_errno(r, "Failed to parse --bind(-ro)= argument %s: %m", optarg); + + arg_settings_mask |= SETTING_CUSTOM_MOUNTS; + break; + + case ARG_TMPFS: + r = tmpfs_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --tmpfs= argument %s: %m", optarg); + + arg_settings_mask |= SETTING_CUSTOM_MOUNTS; + break; + + case ARG_OVERLAY: + case ARG_OVERLAY_RO: { + _cleanup_free_ char *upper = NULL, *destination = NULL; + _cleanup_strv_free_ char **lower = NULL; + CustomMount *m; + unsigned n = 0; + char **i; + + r = strv_split_extract(&lower, optarg, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r == -ENOMEM) + return log_oom(); + else if (r < 0) { + log_error("Invalid overlay specification: %s", optarg); + return r; + } + + STRV_FOREACH(i, lower) { + if (!path_is_absolute(*i)) { + log_error("Overlay path %s is not absolute.", *i); + return -EINVAL; + } + + n++; + } + + if (n < 2) { + log_error("--overlay= needs at least two colon-separated directories specified."); + return -EINVAL; + } + + if (n == 2) { + /* If two parameters are specified, + * the first one is the lower, the + * second one the upper directory. And + * we'll also define the destination + * mount point the same as the upper. */ + upper = lower[1]; + lower[1] = NULL; + + destination = strdup(upper); + if (!destination) + return log_oom(); + + } else { + upper = lower[n - 2]; + destination = lower[n - 1]; + lower[n - 2] = NULL; + } + + m = custom_mount_add(&arg_custom_mounts, &arg_n_custom_mounts, CUSTOM_MOUNT_OVERLAY); + if (!m) + return log_oom(); + + m->destination = destination; + m->source = upper; + m->lower = lower; + m->read_only = c == ARG_OVERLAY_RO; + + upper = destination = NULL; + lower = NULL; + + arg_settings_mask |= SETTING_CUSTOM_MOUNTS; + break; + } + + case 'E': { + char **n; + + if (!env_assignment_is_valid(optarg)) { + log_error("Environment variable assignment '%s' is not valid.", optarg); + return -EINVAL; + } + + n = strv_env_set(arg_setenv, optarg); + if (!n) + return log_oom(); + + strv_free(arg_setenv); + arg_setenv = n; + + arg_settings_mask |= SETTING_ENVIRONMENT; + break; + } + + case 'q': + arg_quiet = true; + break; + + case ARG_SHARE_SYSTEM: + arg_share_system = true; + break; + + case ARG_REGISTER: + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --register= argument: %s", optarg); + return r; + } + + arg_register = r; + break; + + case ARG_KEEP_UNIT: + arg_keep_unit = true; + break; + + case ARG_PERSONALITY: + + arg_personality = personality_from_string(optarg); + if (arg_personality == PERSONALITY_INVALID) { + log_error("Unknown or unsupported personality '%s'.", optarg); + return -EINVAL; + } + + arg_settings_mask |= SETTING_PERSONALITY; + break; + + case ARG_VOLATILE: + + if (!optarg) + arg_volatile_mode = VOLATILE_YES; + else { + VolatileMode m; + + m = volatile_mode_from_string(optarg); + if (m < 0) { + log_error("Failed to parse --volatile= argument: %s", optarg); + return -EINVAL; + } else + arg_volatile_mode = m; + } + + arg_settings_mask |= SETTING_VOLATILE_MODE; + break; + + case 'p': + r = expose_port_parse(&arg_expose_ports, optarg); + if (r == -EEXIST) + return log_error_errno(r, "Duplicate port specification: %s", optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse host port %s: %m", optarg); + + arg_settings_mask |= SETTING_EXPOSE_PORTS; + break; + + case ARG_PROPERTY: + if (strv_extend(&arg_property, optarg) < 0) + return log_oom(); + + break; + + case ARG_PRIVATE_USERS: + + r = optarg ? parse_boolean(optarg) : 1; + if (r == 0) { + /* no: User namespacing off */ + arg_userns_mode = USER_NAMESPACE_NO; + arg_uid_shift = UID_INVALID; + arg_uid_range = UINT32_C(0x10000); + } else if (r > 0) { + /* yes: User namespacing on, UID range is read from root dir */ + arg_userns_mode = USER_NAMESPACE_FIXED; + arg_uid_shift = UID_INVALID; + arg_uid_range = UINT32_C(0x10000); + } else if (streq(optarg, "pick")) { + /* pick: User namespacing on, UID range is picked randomly */ + arg_userns_mode = USER_NAMESPACE_PICK; + arg_uid_shift = UID_INVALID; + arg_uid_range = UINT32_C(0x10000); + } else { + _cleanup_free_ char *buffer = NULL; + const char *range, *shift; + + /* anything else: User namespacing on, UID range is explicitly configured */ + + range = strchr(optarg, ':'); + if (range) { + buffer = strndup(optarg, range - optarg); + if (!buffer) + return log_oom(); + shift = buffer; + + range++; + if (safe_atou32(range, &arg_uid_range) < 0 || arg_uid_range <= 0) { + log_error("Failed to parse UID range: %s", range); + return -EINVAL; + } + } else + shift = optarg; + + if (parse_uid(shift, &arg_uid_shift) < 0) { + log_error("Failed to parse UID: %s", optarg); + return -EINVAL; + } + + arg_userns_mode = USER_NAMESPACE_FIXED; + } + + arg_settings_mask |= SETTING_USERNS; + break; + + case 'U': + if (userns_supported()) { + arg_userns_mode = USER_NAMESPACE_PICK; + arg_uid_shift = UID_INVALID; + arg_uid_range = UINT32_C(0x10000); + + arg_settings_mask |= SETTING_USERNS; + } + + break; + + case ARG_PRIVATE_USERS_CHOWN: + arg_userns_chown = true; + + arg_settings_mask |= SETTING_USERNS; + break; + + case ARG_KILL_SIGNAL: + arg_kill_signal = signal_from_string_try_harder(optarg); + if (arg_kill_signal < 0) { + log_error("Cannot parse signal: %s", optarg); + return -EINVAL; + } + + arg_settings_mask |= SETTING_KILL_SIGNAL; + break; + + case ARG_SETTINGS: + + /* no → do not read files + * yes → read files, do not override cmdline, trust only subset + * override → read files, override cmdline, trust only subset + * trusted → read files, do not override cmdline, trust all + */ + + r = parse_boolean(optarg); + if (r < 0) { + if (streq(optarg, "trusted")) { + mask_all_settings = false; + mask_no_settings = false; + arg_settings_trusted = true; + + } else if (streq(optarg, "override")) { + mask_all_settings = false; + mask_no_settings = true; + arg_settings_trusted = -1; + } else + return log_error_errno(r, "Failed to parse --settings= argument: %s", optarg); + } else if (r > 0) { + /* yes */ + mask_all_settings = false; + mask_no_settings = false; + arg_settings_trusted = -1; + } else { + /* no */ + mask_all_settings = true; + mask_no_settings = false; + arg_settings_trusted = false; + } + + break; + + case ARG_CHDIR: + if (!path_is_absolute(optarg)) { + log_error("Working directory %s is not an absolute path.", optarg); + return -EINVAL; + } + + r = free_and_strdup(&arg_chdir, optarg); + if (r < 0) + return log_oom(); + + arg_settings_mask |= SETTING_WORKING_DIRECTORY; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (arg_share_system) + arg_register = false; + + if (arg_userns_mode == USER_NAMESPACE_PICK) + arg_userns_chown = true; + + if (arg_start_mode != START_PID1 && arg_share_system) { + log_error("--boot and --share-system may not be combined."); + return -EINVAL; + } + + if (arg_keep_unit && cg_pid_get_owner_uid(0, NULL) >= 0) { + log_error("--keep-unit may not be used when invoked from a user session."); + return -EINVAL; + } + + if (arg_directory && arg_image) { + log_error("--directory= and --image= may not be combined."); + return -EINVAL; + } + + if (arg_template && arg_image) { + log_error("--template= and --image= may not be combined."); + return -EINVAL; + } + + if (arg_template && !(arg_directory || arg_machine)) { + log_error("--template= needs --directory= or --machine=."); + return -EINVAL; + } + + if (arg_ephemeral && arg_template) { + log_error("--ephemeral and --template= may not be combined."); + return -EINVAL; + } + + if (arg_ephemeral && arg_image) { + log_error("--ephemeral and --image= may not be combined."); + return -EINVAL; + } + + if (arg_ephemeral && !IN_SET(arg_link_journal, LINK_NO, LINK_AUTO)) { + log_error("--ephemeral and --link-journal= may not be combined."); + return -EINVAL; + } + + if (arg_userns_mode != USER_NAMESPACE_NO && !userns_supported()) { + log_error("--private-users= is not supported, kernel compiled without user namespace support."); + return -EOPNOTSUPP; + } + + if (arg_userns_chown && arg_read_only) { + log_error("--read-only and --private-users-chown may not be combined."); + return -EINVAL; + } + + if (arg_network_bridge && arg_network_zone) { + log_error("--network-bridge= and --network-zone= may not be combined."); + return -EINVAL; + } + + if (argc > optind) { + arg_parameters = strv_copy(argv + optind); + if (!arg_parameters) + return log_oom(); + + arg_settings_mask |= SETTING_START_MODE; + } + + /* Load all settings from .nspawn files */ + if (mask_no_settings) + arg_settings_mask = 0; + + /* Don't load any settings from .nspawn files */ + if (mask_all_settings) + arg_settings_mask = _SETTINGS_MASK_ALL; + + arg_retain = (arg_retain | plus | (arg_private_network ? 1ULL << CAP_NET_ADMIN : 0)) & ~minus; + + r = detect_unified_cgroup_hierarchy(); + if (r < 0) + return r; + + e = getenv("SYSTEMD_NSPAWN_CONTAINER_SERVICE"); + if (e) + arg_container_service_name = e; + + return 1; +} + +static int verify_arguments(void) { + + if (arg_volatile_mode != VOLATILE_NO && arg_read_only) { + log_error("Cannot combine --read-only with --volatile. Note that --volatile already implies a read-only base hierarchy."); + return -EINVAL; + } + + if (arg_expose_ports && !arg_private_network) { + log_error("Cannot use --port= without private networking."); + return -EINVAL; + } + +#ifndef HAVE_LIBIPTC + if (arg_expose_ports) { + log_error("--port= is not supported, compiled without libiptc support."); + return -EOPNOTSUPP; + } +#endif + + if (arg_start_mode == START_BOOT && arg_kill_signal <= 0) + arg_kill_signal = SIGRTMIN+3; + + return 0; +} + +static int userns_lchown(const char *p, uid_t uid, gid_t gid) { + assert(p); + + if (arg_userns_mode == USER_NAMESPACE_NO) + return 0; + + if (uid == UID_INVALID && gid == GID_INVALID) + return 0; + + if (uid != UID_INVALID) { + uid += arg_uid_shift; + + if (uid < arg_uid_shift || uid >= arg_uid_shift + arg_uid_range) + return -EOVERFLOW; + } + + if (gid != GID_INVALID) { + gid += (gid_t) arg_uid_shift; + + if (gid < (gid_t) arg_uid_shift || gid >= (gid_t) (arg_uid_shift + arg_uid_range)) + return -EOVERFLOW; + } + + if (lchown(p, uid, gid) < 0) + return -errno; + + return 0; +} + +static int userns_mkdir(const char *root, const char *path, mode_t mode, uid_t uid, gid_t gid) { + const char *q; + + q = prefix_roota(root, path); + if (mkdir(q, mode) < 0) { + if (errno == EEXIST) + return 0; + return -errno; + } + + return userns_lchown(q, uid, gid); +} + +static int setup_timezone(const char *dest) { + _cleanup_free_ char *p = NULL, *q = NULL; + const char *where, *check, *what; + char *z, *y; + int r; + + assert(dest); + + /* Fix the timezone, if possible */ + r = readlink_malloc("/etc/localtime", &p); + if (r < 0) { + log_warning("/etc/localtime is not a symlink, not updating container timezone."); + return 0; + } + + z = path_startswith(p, "../usr/share/zoneinfo/"); + if (!z) + z = path_startswith(p, "/usr/share/zoneinfo/"); + if (!z) { + log_warning("/etc/localtime does not point into /usr/share/zoneinfo/, not updating container timezone."); + return 0; + } + + where = prefix_roota(dest, "/etc/localtime"); + r = readlink_malloc(where, &q); + if (r >= 0) { + y = path_startswith(q, "../usr/share/zoneinfo/"); + if (!y) + y = path_startswith(q, "/usr/share/zoneinfo/"); + + /* Already pointing to the right place? Then do nothing .. */ + if (y && streq(y, z)) + return 0; + } + + check = strjoina("/usr/share/zoneinfo/", z); + check = prefix_roota(dest, check); + if (laccess(check, F_OK) < 0) { + log_warning("Timezone %s does not exist in container, not updating container timezone.", z); + return 0; + } + + r = unlink(where); + if (r < 0 && errno != ENOENT) { + log_error_errno(errno, "Failed to remove existing timezone info %s in container: %m", where); + return 0; + } + + what = strjoina("../usr/share/zoneinfo/", z); + if (symlink(what, where) < 0) { + log_error_errno(errno, "Failed to correct timezone of container: %m"); + return 0; + } + + r = userns_lchown(where, 0, 0); + if (r < 0) + return log_warning_errno(r, "Failed to chown /etc/localtime: %m"); + + return 0; +} + +static int setup_resolv_conf(const char *dest) { + const char *where = NULL; + int r; + + assert(dest); + + if (arg_private_network) + return 0; + + /* Fix resolv.conf, if possible */ + where = prefix_roota(dest, "/etc/resolv.conf"); + + r = copy_file("/etc/resolv.conf", where, O_TRUNC|O_NOFOLLOW, 0644, 0); + if (r < 0) { + /* If the file already exists as symlink, let's + * suppress the warning, under the assumption that + * resolved or something similar runs inside and the + * symlink points there. + * + * If the disk image is read-only, there's also no + * point in complaining. + */ + log_full_errno(IN_SET(r, -ELOOP, -EROFS) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to copy /etc/resolv.conf to %s: %m", where); + return 0; + } + + r = userns_lchown(where, 0, 0); + if (r < 0) + log_warning_errno(r, "Failed to chown /etc/resolv.conf: %m"); + + return 0; +} + +static char* id128_format_as_uuid(sd_id128_t id, char s[37]) { + assert(s); + + snprintf(s, 37, + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + SD_ID128_FORMAT_VAL(id)); + + return s; +} + +static int setup_boot_id(const char *dest) { + const char *from, *to; + sd_id128_t rnd = {}; + char as_uuid[37]; + int r; + + if (arg_share_system) + return 0; + + /* Generate a new randomized boot ID, so that each boot-up of + * the container gets a new one */ + + from = prefix_roota(dest, "/run/proc-sys-kernel-random-boot-id"); + to = prefix_roota(dest, "/proc/sys/kernel/random/boot_id"); + + r = sd_id128_randomize(&rnd); + if (r < 0) + return log_error_errno(r, "Failed to generate random boot id: %m"); + + id128_format_as_uuid(rnd, as_uuid); + + r = write_string_file(from, as_uuid, WRITE_STRING_FILE_CREATE); + if (r < 0) + return log_error_errno(r, "Failed to write boot id: %m"); + + if (mount(from, to, NULL, MS_BIND, NULL) < 0) + r = log_error_errno(errno, "Failed to bind mount boot id: %m"); + else if (mount(NULL, to, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NODEV, NULL) < 0) + log_warning_errno(errno, "Failed to make boot id read-only: %m"); + + unlink(from); + return r; +} + +static int copy_devnodes(const char *dest) { + + static const char devnodes[] = + "null\0" + "zero\0" + "full\0" + "random\0" + "urandom\0" + "tty\0" + "net/tun\0"; + + const char *d; + int r = 0; + _cleanup_umask_ mode_t u; + + assert(dest); + + u = umask(0000); + + /* Create /dev/net, so that we can create /dev/net/tun in it */ + if (userns_mkdir(dest, "/dev/net", 0755, 0, 0) < 0) + return log_error_errno(r, "Failed to create /dev/net directory: %m"); + + NULSTR_FOREACH(d, devnodes) { + _cleanup_free_ char *from = NULL, *to = NULL; + struct stat st; + + from = strappend("/dev/", d); + to = prefix_root(dest, from); + + if (stat(from, &st) < 0) { + + if (errno != ENOENT) + return log_error_errno(errno, "Failed to stat %s: %m", from); + + } else if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) { + + log_error("%s is not a char or block device, cannot copy.", from); + return -EIO; + + } else { + if (mknod(to, st.st_mode, st.st_rdev) < 0) { + if (errno != EPERM) + return log_error_errno(errno, "mknod(%s) failed: %m", to); + + /* Some systems abusively restrict mknod but + * allow bind mounts. */ + r = touch(to); + if (r < 0) + return log_error_errno(r, "touch (%s) failed: %m", to); + if (mount(from, to, NULL, MS_BIND, NULL) < 0) + return log_error_errno(errno, "Both mknod and bind mount (%s) failed: %m", to); + } + + r = userns_lchown(to, 0, 0); + if (r < 0) + return log_error_errno(r, "chown() of device node %s failed: %m", to); + } + } + + return r; +} + +static int setup_pts(const char *dest) { + _cleanup_free_ char *options = NULL; + const char *p; + int r; + +#ifdef HAVE_SELINUX + if (arg_selinux_apifs_context) + (void) asprintf(&options, + "newinstance,ptmxmode=0666,mode=620,gid=" GID_FMT ",context=\"%s\"", + arg_uid_shift + TTY_GID, + arg_selinux_apifs_context); + else +#endif + (void) asprintf(&options, + "newinstance,ptmxmode=0666,mode=620,gid=" GID_FMT, + arg_uid_shift + TTY_GID); + + if (!options) + return log_oom(); + + /* Mount /dev/pts itself */ + p = prefix_roota(dest, "/dev/pts"); + if (mkdir(p, 0755) < 0) + return log_error_errno(errno, "Failed to create /dev/pts: %m"); + if (mount("devpts", p, "devpts", MS_NOSUID|MS_NOEXEC, options) < 0) + return log_error_errno(errno, "Failed to mount /dev/pts: %m"); + r = userns_lchown(p, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to chown /dev/pts: %m"); + + /* Create /dev/ptmx symlink */ + p = prefix_roota(dest, "/dev/ptmx"); + if (symlink("pts/ptmx", p) < 0) + return log_error_errno(errno, "Failed to create /dev/ptmx symlink: %m"); + r = userns_lchown(p, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to chown /dev/ptmx: %m"); + + /* And fix /dev/pts/ptmx ownership */ + p = prefix_roota(dest, "/dev/pts/ptmx"); + r = userns_lchown(p, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to chown /dev/pts/ptmx: %m"); + + return 0; +} + +static int setup_dev_console(const char *dest, const char *console) { + _cleanup_umask_ mode_t u; + const char *to; + int r; + + assert(dest); + assert(console); + + u = umask(0000); + + r = chmod_and_chown(console, 0600, arg_uid_shift, arg_uid_shift); + if (r < 0) + return log_error_errno(r, "Failed to correct access mode for TTY: %m"); + + /* We need to bind mount the right tty to /dev/console since + * ptys can only exist on pts file systems. To have something + * to bind mount things on we create a empty regular file. */ + + to = prefix_roota(dest, "/dev/console"); + r = touch(to); + if (r < 0) + return log_error_errno(r, "touch() for /dev/console failed: %m"); + + if (mount(console, to, NULL, MS_BIND, NULL) < 0) + return log_error_errno(errno, "Bind mount for /dev/console failed: %m"); + + return 0; +} + +static int setup_kmsg(const char *dest, int kmsg_socket) { + const char *from, *to; + _cleanup_umask_ mode_t u; + int fd, r; + + assert(kmsg_socket >= 0); + + u = umask(0000); + + /* We create the kmsg FIFO as /run/kmsg, but immediately + * delete it after bind mounting it to /proc/kmsg. While FIFOs + * on the reading side behave very similar to /proc/kmsg, + * their writing side behaves differently from /dev/kmsg in + * that writing blocks when nothing is reading. In order to + * avoid any problems with containers deadlocking due to this + * we simply make /dev/kmsg unavailable to the container. */ + from = prefix_roota(dest, "/run/kmsg"); + to = prefix_roota(dest, "/proc/kmsg"); + + if (mkfifo(from, 0600) < 0) + return log_error_errno(errno, "mkfifo() for /run/kmsg failed: %m"); + if (mount(from, to, NULL, MS_BIND, NULL) < 0) + return log_error_errno(errno, "Bind mount for /proc/kmsg failed: %m"); + + fd = open(from, O_RDWR|O_NDELAY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open fifo: %m"); + + /* Store away the fd in the socket, so that it stays open as + * long as we run the child */ + r = send_one_fd(kmsg_socket, fd, 0); + safe_close(fd); + + if (r < 0) + return log_error_errno(r, "Failed to send FIFO fd: %m"); + + /* And now make the FIFO unavailable as /run/kmsg... */ + (void) unlink(from); + + return 0; +} + +static int on_address_change(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { + union in_addr_union *exposed = userdata; + + assert(rtnl); + assert(m); + assert(exposed); + + expose_port_execute(rtnl, arg_expose_ports, exposed); + return 0; +} + +static int setup_hostname(void) { + + if (arg_share_system) + return 0; + + if (sethostname_idempotent(arg_machine) < 0) + return -errno; + + return 0; +} + +static int setup_journal(const char *directory) { + sd_id128_t this_id; + _cleanup_free_ char *d = NULL; + const char *p, *q; + bool try; + char id[33]; + int r; + + /* Don't link journals in ephemeral mode */ + if (arg_ephemeral) + return 0; + + if (arg_link_journal == LINK_NO) + return 0; + + try = arg_link_journal_try || arg_link_journal == LINK_AUTO; + + r = sd_id128_get_machine(&this_id); + if (r < 0) + return log_error_errno(r, "Failed to retrieve machine ID: %m"); + + if (sd_id128_equal(arg_uuid, this_id)) { + log_full(try ? LOG_WARNING : LOG_ERR, + "Host and machine ids are equal (%s): refusing to link journals", sd_id128_to_string(arg_uuid, id)); + if (try) + return 0; + return -EEXIST; + } + + r = userns_mkdir(directory, "/var", 0755, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to create /var: %m"); + + r = userns_mkdir(directory, "/var/log", 0755, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to create /var/log: %m"); + + r = userns_mkdir(directory, "/var/log/journal", 0755, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to create /var/log/journal: %m"); + + (void) sd_id128_to_string(arg_uuid, id); + + p = strjoina("/var/log/journal/", id); + q = prefix_roota(directory, p); + + if (path_is_mount_point(p, 0) > 0) { + if (try) + return 0; + + log_error("%s: already a mount point, refusing to use for journal", p); + return -EEXIST; + } + + if (path_is_mount_point(q, 0) > 0) { + if (try) + return 0; + + log_error("%s: already a mount point, refusing to use for journal", q); + return -EEXIST; + } + + r = readlink_and_make_absolute(p, &d); + if (r >= 0) { + if ((arg_link_journal == LINK_GUEST || + arg_link_journal == LINK_AUTO) && + path_equal(d, q)) { + + r = userns_mkdir(directory, p, 0755, 0, 0); + if (r < 0) + log_warning_errno(r, "Failed to create directory %s: %m", q); + return 0; + } + + if (unlink(p) < 0) + return log_error_errno(errno, "Failed to remove symlink %s: %m", p); + } else if (r == -EINVAL) { + + if (arg_link_journal == LINK_GUEST && + rmdir(p) < 0) { + + if (errno == ENOTDIR) { + log_error("%s already exists and is neither a symlink nor a directory", p); + return r; + } else + return log_error_errno(errno, "Failed to remove %s: %m", p); + } + } else if (r != -ENOENT) + return log_error_errno(r, "readlink(%s) failed: %m", p); + + if (arg_link_journal == LINK_GUEST) { + + if (symlink(q, p) < 0) { + if (try) { + log_debug_errno(errno, "Failed to symlink %s to %s, skipping journal setup: %m", q, p); + return 0; + } else + return log_error_errno(errno, "Failed to symlink %s to %s: %m", q, p); + } + + r = userns_mkdir(directory, p, 0755, 0, 0); + if (r < 0) + log_warning_errno(r, "Failed to create directory %s: %m", q); + return 0; + } + + if (arg_link_journal == LINK_HOST) { + /* don't create parents here — if the host doesn't have + * permanent journal set up, don't force it here */ + + if (mkdir(p, 0755) < 0 && errno != EEXIST) { + if (try) { + log_debug_errno(errno, "Failed to create %s, skipping journal setup: %m", p); + return 0; + } else + return log_error_errno(errno, "Failed to create %s: %m", p); + } + + } else if (access(p, F_OK) < 0) + return 0; + + if (dir_is_empty(q) == 0) + log_warning("%s is not empty, proceeding anyway.", q); + + r = userns_mkdir(directory, p, 0755, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to create %s: %m", q); + + if (mount(p, q, NULL, MS_BIND, NULL) < 0) + return log_error_errno(errno, "Failed to bind mount journal from host into guest: %m"); + + return 0; +} + +static int drop_capabilities(void) { + return capability_bounding_set_drop(arg_retain, false); +} + +static int reset_audit_loginuid(void) { + _cleanup_free_ char *p = NULL; + int r; + + if (arg_share_system) + return 0; + + r = read_one_line_file("/proc/self/loginuid", &p); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to read /proc/self/loginuid: %m"); + + /* Already reset? */ + if (streq(p, "4294967295")) + return 0; + + r = write_string_file("/proc/self/loginuid", "4294967295", 0); + if (r < 0) { + log_error_errno(r, + "Failed to reset audit login UID. This probably means that your kernel is too\n" + "old and you have audit enabled. Note that the auditing subsystem is known to\n" + "be incompatible with containers on old kernels. Please make sure to upgrade\n" + "your kernel or to off auditing with 'audit=0' on the kernel command line before\n" + "using systemd-nspawn. Sleeping for 5s... (%m)"); + + sleep(5); + } + + return 0; +} + +static int setup_seccomp(void) { + +#ifdef HAVE_SECCOMP + static const struct { + uint64_t capability; + int syscall_num; + } blacklist[] = { + { CAP_SYS_RAWIO, SCMP_SYS(iopl) }, + { CAP_SYS_RAWIO, SCMP_SYS(ioperm) }, + { CAP_SYS_BOOT, SCMP_SYS(kexec_load) }, + { CAP_SYS_ADMIN, SCMP_SYS(swapon) }, + { CAP_SYS_ADMIN, SCMP_SYS(swapoff) }, + { CAP_SYS_ADMIN, SCMP_SYS(open_by_handle_at) }, + { CAP_SYS_MODULE, SCMP_SYS(init_module) }, + { CAP_SYS_MODULE, SCMP_SYS(finit_module) }, + { CAP_SYS_MODULE, SCMP_SYS(delete_module) }, + { CAP_SYSLOG, SCMP_SYS(syslog) }, + }; + + scmp_filter_ctx seccomp; + unsigned i; + int r; + + seccomp = seccomp_init(SCMP_ACT_ALLOW); + if (!seccomp) + return log_oom(); + + r = seccomp_add_secondary_archs(seccomp); + if (r < 0) { + log_error_errno(r, "Failed to add secondary archs to seccomp filter: %m"); + goto finish; + } + + for (i = 0; i < ELEMENTSOF(blacklist); i++) { + if (arg_retain & (1ULL << blacklist[i].capability)) + continue; + + r = seccomp_rule_add(seccomp, SCMP_ACT_ERRNO(EPERM), blacklist[i].syscall_num, 0); + if (r == -EFAULT) + continue; /* unknown syscall */ + if (r < 0) { + log_error_errno(r, "Failed to block syscall: %m"); + goto finish; + } + } + + /* + Audit is broken in containers, much of the userspace audit + hookup will fail if running inside a container. We don't + care and just turn off creation of audit sockets. + + This will make socket(AF_NETLINK, *, NETLINK_AUDIT) fail + with EAFNOSUPPORT which audit userspace uses as indication + that audit is disabled in the kernel. + */ + + r = seccomp_rule_add( + seccomp, + SCMP_ACT_ERRNO(EAFNOSUPPORT), + SCMP_SYS(socket), + 2, + SCMP_A0(SCMP_CMP_EQ, AF_NETLINK), + SCMP_A2(SCMP_CMP_EQ, NETLINK_AUDIT)); + if (r < 0) { + log_error_errno(r, "Failed to add audit seccomp rule: %m"); + goto finish; + } + + r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); + if (r < 0) { + log_error_errno(r, "Failed to unset NO_NEW_PRIVS: %m"); + goto finish; + } + + r = seccomp_load(seccomp); + if (r == -EINVAL) { + log_debug_errno(r, "Kernel is probably not configured with CONFIG_SECCOMP. Disabling seccomp audit filter: %m"); + r = 0; + goto finish; + } + if (r < 0) { + log_error_errno(r, "Failed to install seccomp audit filter: %m"); + goto finish; + } + +finish: + seccomp_release(seccomp); + return r; +#else + return 0; +#endif + +} + +static int setup_propagate(const char *root) { + const char *p, *q; + int r; + + (void) mkdir_p("/run/systemd/nspawn/", 0755); + (void) mkdir_p("/run/systemd/nspawn/propagate", 0600); + p = strjoina("/run/systemd/nspawn/propagate/", arg_machine); + (void) mkdir_p(p, 0600); + + r = userns_mkdir(root, "/run/systemd", 0755, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to create /run/systemd: %m"); + + r = userns_mkdir(root, "/run/systemd/nspawn", 0755, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to create /run/systemd/nspawn: %m"); + + r = userns_mkdir(root, "/run/systemd/nspawn/incoming", 0600, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to create /run/systemd/nspawn/incoming: %m"); + + q = prefix_roota(root, "/run/systemd/nspawn/incoming"); + if (mount(p, q, NULL, MS_BIND, NULL) < 0) + return log_error_errno(errno, "Failed to install propagation bind mount."); + + if (mount(NULL, q, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL) < 0) + return log_error_errno(errno, "Failed to make propagation mount read-only"); + + return 0; +} + +static int setup_image(char **device_path, int *loop_nr) { + struct loop_info64 info = { + .lo_flags = LO_FLAGS_AUTOCLEAR|LO_FLAGS_PARTSCAN + }; + _cleanup_close_ int fd = -1, control = -1, loop = -1; + _cleanup_free_ char* loopdev = NULL; + struct stat st; + int r, nr; + + assert(device_path); + assert(loop_nr); + assert(arg_image); + + fd = open(arg_image, O_CLOEXEC|(arg_read_only ? O_RDONLY : O_RDWR)|O_NONBLOCK|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", arg_image); + + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to stat %s: %m", arg_image); + + if (S_ISBLK(st.st_mode)) { + char *p; + + p = strdup(arg_image); + if (!p) + return log_oom(); + + *device_path = p; + + *loop_nr = -1; + + r = fd; + fd = -1; + + return r; + } + + if (!S_ISREG(st.st_mode)) { + log_error("%s is not a regular file or block device.", arg_image); + return -EINVAL; + } + + control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + if (control < 0) + return log_error_errno(errno, "Failed to open /dev/loop-control: %m"); + + nr = ioctl(control, LOOP_CTL_GET_FREE); + if (nr < 0) + return log_error_errno(errno, "Failed to allocate loop device: %m"); + + if (asprintf(&loopdev, "/dev/loop%i", nr) < 0) + return log_oom(); + + loop = open(loopdev, O_CLOEXEC|(arg_read_only ? O_RDONLY : O_RDWR)|O_NONBLOCK|O_NOCTTY); + if (loop < 0) + return log_error_errno(errno, "Failed to open loop device %s: %m", loopdev); + + if (ioctl(loop, LOOP_SET_FD, fd) < 0) + return log_error_errno(errno, "Failed to set loopback file descriptor on %s: %m", loopdev); + + if (arg_read_only) + info.lo_flags |= LO_FLAGS_READ_ONLY; + + if (ioctl(loop, LOOP_SET_STATUS64, &info) < 0) + return log_error_errno(errno, "Failed to set loopback settings on %s: %m", loopdev); + + *device_path = loopdev; + loopdev = NULL; + + *loop_nr = nr; + + r = loop; + loop = -1; + + return r; +} + +#define PARTITION_TABLE_BLURB \ + "Note that the disk image needs to either contain only a single MBR partition of\n" \ + "type 0x83 that is marked bootable, or a single GPT partition of type " \ + "0FC63DAF-8483-4772-8E79-3D69D8477DE4 or follow\n" \ + " http://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/\n" \ + "to be bootable with systemd-nspawn." + +static int dissect_image( + int fd, + char **root_device, bool *root_device_rw, + char **home_device, bool *home_device_rw, + char **srv_device, bool *srv_device_rw, + bool *secondary) { + +#ifdef HAVE_BLKID + int home_nr = -1, srv_nr = -1; +#ifdef GPT_ROOT_NATIVE + int root_nr = -1; +#endif +#ifdef GPT_ROOT_SECONDARY + int secondary_root_nr = -1; +#endif + _cleanup_free_ char *home = NULL, *root = NULL, *secondary_root = NULL, *srv = NULL, *generic = NULL; + _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; + _cleanup_udev_device_unref_ struct udev_device *d = NULL; + _cleanup_blkid_free_probe_ blkid_probe b = NULL; + _cleanup_udev_unref_ struct udev *udev = NULL; + struct udev_list_entry *first, *item; + bool home_rw = true, root_rw = true, secondary_root_rw = true, srv_rw = true, generic_rw = true; + bool is_gpt, is_mbr, multiple_generic = false; + const char *pttype = NULL; + blkid_partlist pl; + struct stat st; + unsigned i; + int r; + + assert(fd >= 0); + assert(root_device); + assert(home_device); + assert(srv_device); + assert(secondary); + assert(arg_image); + + b = blkid_new_probe(); + if (!b) + return log_oom(); + + errno = 0; + r = blkid_probe_set_device(b, fd, 0, 0); + if (r != 0) { + if (errno == 0) + return log_oom(); + + return log_error_errno(errno, "Failed to set device on blkid probe: %m"); + } + + blkid_probe_enable_partitions(b, 1); + blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); + + errno = 0; + r = blkid_do_safeprobe(b); + if (r == -2 || r == 1) { + log_error("Failed to identify any partition table on\n" + " %s\n" + PARTITION_TABLE_BLURB, arg_image); + return -EINVAL; + } else if (r != 0) { + if (errno == 0) + errno = EIO; + return log_error_errno(errno, "Failed to probe: %m"); + } + + (void) blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL); + + is_gpt = streq_ptr(pttype, "gpt"); + is_mbr = streq_ptr(pttype, "dos"); + + if (!is_gpt && !is_mbr) { + log_error("No GPT or MBR partition table discovered on\n" + " %s\n" + PARTITION_TABLE_BLURB, arg_image); + return -EINVAL; + } + + errno = 0; + pl = blkid_probe_get_partitions(b); + if (!pl) { + if (errno == 0) + return log_oom(); + + log_error("Failed to list partitions of %s", arg_image); + return -errno; + } + + udev = udev_new(); + if (!udev) + return log_oom(); + + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to stat block device: %m"); + + d = udev_device_new_from_devnum(udev, 'b', st.st_rdev); + if (!d) + return log_oom(); + + for (i = 0;; i++) { + int n, m; + + if (i >= 10) { + log_error("Kernel partitions never appeared."); + return -ENXIO; + } + + e = udev_enumerate_new(udev); + if (!e) + return log_oom(); + + r = udev_enumerate_add_match_parent(e, d); + if (r < 0) + return log_oom(); + + r = udev_enumerate_scan_devices(e); + if (r < 0) + return log_error_errno(r, "Failed to scan for partition devices of %s: %m", arg_image); + + /* Count the partitions enumerated by the kernel */ + n = 0; + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) + n++; + + /* Count the partitions enumerated by blkid */ + m = blkid_partlist_numof_partitions(pl); + if (n == m + 1) + break; + if (n > m + 1) { + log_error("blkid and kernel partition list do not match."); + return -EIO; + } + if (n < m + 1) { + unsigned j; + + /* The kernel has probed fewer partitions than + * blkid? Maybe the kernel prober is still + * running or it got EBUSY because udev + * already opened the device. Let's reprobe + * the device, which is a synchronous call + * that waits until probing is complete. */ + + for (j = 0; j < 20; j++) { + + r = ioctl(fd, BLKRRPART, 0); + if (r < 0) + r = -errno; + if (r >= 0 || r != -EBUSY) + break; + + /* If something else has the device + * open, such as an udev rule, the + * ioctl will return EBUSY. Since + * there's no way to wait until it + * isn't busy anymore, let's just wait + * a bit, and try again. + * + * This is really something they + * should fix in the kernel! */ + + usleep(50 * USEC_PER_MSEC); + } + + if (r < 0) + return log_error_errno(r, "Failed to reread partition table: %m"); + } + + e = udev_enumerate_unref(e); + } + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) { + _cleanup_udev_device_unref_ struct udev_device *q; + const char *node; + unsigned long long flags; + blkid_partition pp; + dev_t qn; + int nr; + + errno = 0; + q = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); + if (!q) { + if (!errno) + errno = ENOMEM; + + return log_error_errno(errno, "Failed to get partition device of %s: %m", arg_image); + } + + qn = udev_device_get_devnum(q); + if (major(qn) == 0) + continue; + + if (st.st_rdev == qn) + continue; + + node = udev_device_get_devnode(q); + if (!node) + continue; + + pp = blkid_partlist_devno_to_partition(pl, qn); + if (!pp) + continue; + + flags = blkid_partition_get_flags(pp); + + nr = blkid_partition_get_partno(pp); + if (nr < 0) + continue; + + if (is_gpt) { + sd_id128_t type_id; + const char *stype; + + if (flags & GPT_FLAG_NO_AUTO) + continue; + + stype = blkid_partition_get_type_string(pp); + if (!stype) + continue; + + if (sd_id128_from_string(stype, &type_id) < 0) + continue; + + if (sd_id128_equal(type_id, GPT_HOME)) { + + if (home && nr >= home_nr) + continue; + + home_nr = nr; + home_rw = !(flags & GPT_FLAG_READ_ONLY); + + r = free_and_strdup(&home, node); + if (r < 0) + return log_oom(); + + } else if (sd_id128_equal(type_id, GPT_SRV)) { + + if (srv && nr >= srv_nr) + continue; + + srv_nr = nr; + srv_rw = !(flags & GPT_FLAG_READ_ONLY); + + r = free_and_strdup(&srv, node); + if (r < 0) + return log_oom(); + } +#ifdef GPT_ROOT_NATIVE + else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE)) { + + if (root && nr >= root_nr) + continue; + + root_nr = nr; + root_rw = !(flags & GPT_FLAG_READ_ONLY); + + r = free_and_strdup(&root, node); + if (r < 0) + return log_oom(); + } +#endif +#ifdef GPT_ROOT_SECONDARY + else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY)) { + + if (secondary_root && nr >= secondary_root_nr) + continue; + + secondary_root_nr = nr; + secondary_root_rw = !(flags & GPT_FLAG_READ_ONLY); + + r = free_and_strdup(&secondary_root, node); + if (r < 0) + return log_oom(); + } +#endif + else if (sd_id128_equal(type_id, GPT_LINUX_GENERIC)) { + + if (generic) + multiple_generic = true; + else { + generic_rw = !(flags & GPT_FLAG_READ_ONLY); + + r = free_and_strdup(&generic, node); + if (r < 0) + return log_oom(); + } + } + + } else if (is_mbr) { + int type; + + if (flags != 0x80) /* Bootable flag */ + continue; + + type = blkid_partition_get_type(pp); + if (type != 0x83) /* Linux partition */ + continue; + + if (generic) + multiple_generic = true; + else { + generic_rw = true; + + r = free_and_strdup(&root, node); + if (r < 0) + return log_oom(); + } + } + } + + if (root) { + *root_device = root; + root = NULL; + + *root_device_rw = root_rw; + *secondary = false; + } else if (secondary_root) { + *root_device = secondary_root; + secondary_root = NULL; + + *root_device_rw = secondary_root_rw; + *secondary = true; + } else if (generic) { + + /* There were no partitions with precise meanings + * around, but we found generic partitions. In this + * case, if there's only one, we can go ahead and boot + * it, otherwise we bail out, because we really cannot + * make any sense of it. */ + + if (multiple_generic) { + log_error("Identified multiple bootable Linux partitions on\n" + " %s\n" + PARTITION_TABLE_BLURB, arg_image); + return -EINVAL; + } + + *root_device = generic; + generic = NULL; + + *root_device_rw = generic_rw; + *secondary = false; + } else { + log_error("Failed to identify root partition in disk image\n" + " %s\n" + PARTITION_TABLE_BLURB, arg_image); + return -EINVAL; + } + + if (home) { + *home_device = home; + home = NULL; + + *home_device_rw = home_rw; + } + + if (srv) { + *srv_device = srv; + srv = NULL; + + *srv_device_rw = srv_rw; + } + + return 0; +#else + log_error("--image= is not supported, compiled without blkid support."); + return -EOPNOTSUPP; +#endif +} + +static int mount_device(const char *what, const char *where, const char *directory, bool rw) { +#ifdef HAVE_BLKID + _cleanup_blkid_free_probe_ blkid_probe b = NULL; + const char *fstype, *p; + int r; + + assert(what); + assert(where); + + if (arg_read_only) + rw = false; + + if (directory) + p = strjoina(where, directory); + else + p = where; + + errno = 0; + b = blkid_new_probe_from_filename(what); + if (!b) { + if (errno == 0) + return log_oom(); + return log_error_errno(errno, "Failed to allocate prober for %s: %m", what); + } + + blkid_probe_enable_superblocks(b, 1); + blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE); + + errno = 0; + r = blkid_do_safeprobe(b); + if (r == -1 || r == 1) { + log_error("Cannot determine file system type of %s", what); + return -EINVAL; + } else if (r != 0) { + if (errno == 0) + errno = EIO; + return log_error_errno(errno, "Failed to probe %s: %m", what); + } + + errno = 0; + if (blkid_probe_lookup_value(b, "TYPE", &fstype, NULL) < 0) { + if (errno == 0) + errno = EINVAL; + log_error("Failed to determine file system type of %s", what); + return -errno; + } + + if (streq(fstype, "crypto_LUKS")) { + log_error("nspawn currently does not support LUKS disk images."); + return -EOPNOTSUPP; + } + + if (mount(what, p, fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), NULL) < 0) + return log_error_errno(errno, "Failed to mount %s: %m", what); + + return 0; +#else + log_error("--image= is not supported, compiled without blkid support."); + return -EOPNOTSUPP; +#endif +} + +static int setup_machine_id(const char *directory) { + int r; + const char *etc_machine_id, *t; + _cleanup_free_ char *s = NULL; + + etc_machine_id = prefix_roota(directory, "/etc/machine-id"); + + r = read_one_line_file(etc_machine_id, &s); + if (r < 0) + return log_error_errno(r, "Failed to read machine ID from %s: %m", etc_machine_id); + + t = strstrip(s); + + if (!isempty(t)) { + r = sd_id128_from_string(t, &arg_uuid); + if (r < 0) + return log_error_errno(r, "Failed to parse machine ID from %s: %m", etc_machine_id); + } else { + if (sd_id128_is_null(arg_uuid)) { + r = sd_id128_randomize(&arg_uuid); + if (r < 0) + return log_error_errno(r, "Failed to generate random machine ID: %m"); + } + } + + r = machine_id_setup(directory, arg_uuid); + if (r < 0) + return log_error_errno(r, "Failed to setup machine ID: %m"); + + return 0; +} + +static int recursive_chown(const char *directory, uid_t shift, uid_t range) { + int r; + + assert(directory); + + if (arg_userns_mode == USER_NAMESPACE_NO || !arg_userns_chown) + return 0; + + r = path_patch_uid(directory, arg_uid_shift, arg_uid_range); + if (r == -EOPNOTSUPP) + return log_error_errno(r, "Automatic UID/GID adjusting is only supported for UID/GID ranges starting at multiples of 2^16 with a range of 2^16."); + if (r == -EBADE) + return log_error_errno(r, "Upper 16 bits of root directory UID and GID do not match."); + if (r < 0) + return log_error_errno(r, "Failed to adjust UID/GID shift of OS tree: %m"); + if (r == 0) + log_debug("Root directory of image is already owned by the right UID/GID range, skipping recursive chown operation."); + else + log_debug("Patched directory tree to match UID/GID range."); + + return r; +} + +static int mount_devices( + const char *where, + const char *root_device, bool root_device_rw, + const char *home_device, bool home_device_rw, + const char *srv_device, bool srv_device_rw) { + int r; + + assert(where); + + if (root_device) { + r = mount_device(root_device, arg_directory, NULL, root_device_rw); + if (r < 0) + return log_error_errno(r, "Failed to mount root directory: %m"); + } + + if (home_device) { + r = mount_device(home_device, arg_directory, "/home", home_device_rw); + if (r < 0) + return log_error_errno(r, "Failed to mount home directory: %m"); + } + + if (srv_device) { + r = mount_device(srv_device, arg_directory, "/srv", srv_device_rw); + if (r < 0) + return log_error_errno(r, "Failed to mount server data directory: %m"); + } + + return 0; +} + +static void loop_remove(int nr, int *image_fd) { + _cleanup_close_ int control = -1; + int r; + + if (nr < 0) + return; + + if (image_fd && *image_fd >= 0) { + r = ioctl(*image_fd, LOOP_CLR_FD); + if (r < 0) + log_debug_errno(errno, "Failed to close loop image: %m"); + *image_fd = safe_close(*image_fd); + } + + control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + if (control < 0) { + log_warning_errno(errno, "Failed to open /dev/loop-control: %m"); + return; + } + + r = ioctl(control, LOOP_CTL_REMOVE, nr); + if (r < 0) + log_debug_errno(errno, "Failed to remove loop %d: %m", nr); +} + +/* + * Return values: + * < 0 : wait_for_terminate() failed to get the state of the + * container, the container was terminated by a signal, or + * failed for an unknown reason. No change is made to the + * container argument. + * > 0 : The program executed in the container terminated with an + * error. The exit code of the program executed in the + * container is returned. The container argument has been set + * to CONTAINER_TERMINATED. + * 0 : The container is being rebooted, has been shut down or exited + * successfully. The container argument has been set to either + * CONTAINER_TERMINATED or CONTAINER_REBOOTED. + * + * That is, success is indicated by a return value of zero, and an + * error is indicated by a non-zero value. + */ +static int wait_for_container(pid_t pid, ContainerStatus *container) { + siginfo_t status; + int r; + + r = wait_for_terminate(pid, &status); + if (r < 0) + return log_warning_errno(r, "Failed to wait for container: %m"); + + switch (status.si_code) { + + case CLD_EXITED: + if (status.si_status == 0) { + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "Container %s exited successfully.", arg_machine); + + } else + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "Container %s failed with error code %i.", arg_machine, status.si_status); + + *container = CONTAINER_TERMINATED; + return status.si_status; + + case CLD_KILLED: + if (status.si_status == SIGINT) { + + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "Container %s has been shut down.", arg_machine); + *container = CONTAINER_TERMINATED; + return 0; + + } else if (status.si_status == SIGHUP) { + + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "Container %s is being rebooted.", arg_machine); + *container = CONTAINER_REBOOTED; + return 0; + } + + /* CLD_KILLED fallthrough */ + + case CLD_DUMPED: + log_error("Container %s terminated by signal %s.", arg_machine, signal_to_string(status.si_status)); + return -EIO; + + default: + log_error("Container %s failed due to unknown reason.", arg_machine); + return -EIO; + } + + return r; +} + +static int on_orderly_shutdown(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + pid_t pid; + + pid = PTR_TO_PID(userdata); + if (pid > 0) { + if (kill(pid, arg_kill_signal) >= 0) { + log_info("Trying to halt container. Send SIGTERM again to trigger immediate termination."); + sd_event_source_set_userdata(s, NULL); + return 0; + } + } + + sd_event_exit(sd_event_source_get_event(s), 0); + return 0; +} + +static int determine_names(void) { + int r; + + if (arg_template && !arg_directory && arg_machine) { + + /* If --template= was specified then we should not + * search for a machine, but instead create a new one + * in /var/lib/machine. */ + + arg_directory = strjoin("/var/lib/machines/", arg_machine, NULL); + if (!arg_directory) + return log_oom(); + } + + if (!arg_image && !arg_directory) { + if (arg_machine) { + _cleanup_(image_unrefp) Image *i = NULL; + + r = image_find(arg_machine, &i); + if (r < 0) + return log_error_errno(r, "Failed to find image for machine '%s': %m", arg_machine); + else if (r == 0) { + log_error("No image for machine '%s': %m", arg_machine); + return -ENOENT; + } + + if (i->type == IMAGE_RAW) + r = free_and_strdup(&arg_image, i->path); + else + r = free_and_strdup(&arg_directory, i->path); + if (r < 0) + return log_error_errno(r, "Invalid image directory: %m"); + + if (!arg_ephemeral) + arg_read_only = arg_read_only || i->read_only; + } else + arg_directory = get_current_dir_name(); + + if (!arg_directory && !arg_machine) { + log_error("Failed to determine path, please use -D or -i."); + return -EINVAL; + } + } + + if (!arg_machine) { + if (arg_directory && path_equal(arg_directory, "/")) + arg_machine = gethostname_malloc(); + else + arg_machine = strdup(basename(arg_image ?: arg_directory)); + + if (!arg_machine) + return log_oom(); + + hostname_cleanup(arg_machine); + if (!machine_name_is_valid(arg_machine)) { + log_error("Failed to determine machine name automatically, please use -M."); + return -EINVAL; + } + + if (arg_ephemeral) { + char *b; + + /* Add a random suffix when this is an + * ephemeral machine, so that we can run many + * instances at once without manually having + * to specify -M each time. */ + + if (asprintf(&b, "%s-%016" PRIx64, arg_machine, random_u64()) < 0) + return log_oom(); + + free(arg_machine); + arg_machine = b; + } + } + + return 0; +} + +static int determine_uid_shift(const char *directory) { + int r; + + if (arg_userns_mode == USER_NAMESPACE_NO) { + arg_uid_shift = 0; + return 0; + } + + if (arg_uid_shift == UID_INVALID) { + struct stat st; + + r = stat(directory, &st); + if (r < 0) + return log_error_errno(errno, "Failed to determine UID base of %s: %m", directory); + + arg_uid_shift = st.st_uid & UINT32_C(0xffff0000); + + if (arg_uid_shift != (st.st_gid & UINT32_C(0xffff0000))) { + log_error("UID and GID base of %s don't match.", directory); + return -EINVAL; + } + + arg_uid_range = UINT32_C(0x10000); + } + + if (arg_uid_shift > (uid_t) -1 - arg_uid_range) { + log_error("UID base too high for UID range."); + return -EINVAL; + } + + return 0; +} + +static int inner_child( + Barrier *barrier, + const char *directory, + bool secondary, + int kmsg_socket, + int rtnl_socket, + FDSet *fds) { + + _cleanup_free_ char *home = NULL; + char as_uuid[37]; + unsigned n_env = 1; + const char *envp[] = { + "PATH=" DEFAULT_PATH_SPLIT_USR, + NULL, /* container */ + NULL, /* TERM */ + NULL, /* HOME */ + NULL, /* USER */ + NULL, /* LOGNAME */ + NULL, /* container_uuid */ + NULL, /* LISTEN_FDS */ + NULL, /* LISTEN_PID */ + NULL + }; + + _cleanup_strv_free_ char **env_use = NULL; + int r; + + assert(barrier); + assert(directory); + assert(kmsg_socket >= 0); + + cg_unified_flush(); + + if (arg_userns_mode != USER_NAMESPACE_NO) { + /* Tell the parent, that it now can write the UID map. */ + (void) barrier_place(barrier); /* #1 */ + + /* Wait until the parent wrote the UID map */ + if (!barrier_place_and_sync(barrier)) { /* #2 */ + log_error("Parent died too early"); + return -ESRCH; + } + } + + r = mount_all(NULL, + arg_userns_mode != USER_NAMESPACE_NO, + true, + arg_private_network, + arg_uid_shift, + arg_uid_range, + arg_selinux_apifs_context); + + if (r < 0) + return r; + + r = mount_sysfs(NULL); + if (r < 0) + return r; + + /* Wait until we are cgroup-ified, so that we + * can mount the right cgroup path writable */ + if (!barrier_place_and_sync(barrier)) { /* #3 */ + log_error("Parent died too early"); + return -ESRCH; + } + + r = mount_systemd_cgroup_writable("", arg_unified_cgroup_hierarchy); + if (r < 0) + return r; + + r = reset_uid_gid(); + if (r < 0) + return log_error_errno(r, "Couldn't become new root: %m"); + + r = setup_boot_id(NULL); + if (r < 0) + return r; + + r = setup_kmsg(NULL, kmsg_socket); + if (r < 0) + return r; + kmsg_socket = safe_close(kmsg_socket); + + umask(0022); + + if (setsid() < 0) + return log_error_errno(errno, "setsid() failed: %m"); + + if (arg_private_network) + loopback_setup(); + + if (arg_expose_ports) { + r = expose_port_send_rtnl(rtnl_socket); + if (r < 0) + return r; + rtnl_socket = safe_close(rtnl_socket); + } + + r = drop_capabilities(); + if (r < 0) + return log_error_errno(r, "drop_capabilities() failed: %m"); + + setup_hostname(); + + if (arg_personality != PERSONALITY_INVALID) { + if (personality(arg_personality) < 0) + return log_error_errno(errno, "personality() failed: %m"); + } else if (secondary) { + if (personality(PER_LINUX32) < 0) + return log_error_errno(errno, "personality() failed: %m"); + } + +#ifdef HAVE_SELINUX + if (arg_selinux_context) + if (setexeccon((security_context_t) arg_selinux_context) < 0) + return log_error_errno(errno, "setexeccon(\"%s\") failed: %m", arg_selinux_context); +#endif + + r = change_uid_gid(arg_user, &home); + if (r < 0) + return r; + + /* LXC sets container=lxc, so follow the scheme here */ + envp[n_env++] = strjoina("container=", arg_container_service_name); + + envp[n_env] = strv_find_prefix(environ, "TERM="); + if (envp[n_env]) + n_env++; + + if ((asprintf((char**)(envp + n_env++), "HOME=%s", home ? home: "/root") < 0) || + (asprintf((char**)(envp + n_env++), "USER=%s", arg_user ? arg_user : "root") < 0) || + (asprintf((char**)(envp + n_env++), "LOGNAME=%s", arg_user ? arg_user : "root") < 0)) + return log_oom(); + + assert(!sd_id128_equal(arg_uuid, SD_ID128_NULL)); + + if (asprintf((char**)(envp + n_env++), "container_uuid=%s", id128_format_as_uuid(arg_uuid, as_uuid)) < 0) + return log_oom(); + + if (fdset_size(fds) > 0) { + r = fdset_cloexec(fds, false); + if (r < 0) + return log_error_errno(r, "Failed to unset O_CLOEXEC for file descriptors."); + + if ((asprintf((char **)(envp + n_env++), "LISTEN_FDS=%u", fdset_size(fds)) < 0) || + (asprintf((char **)(envp + n_env++), "LISTEN_PID=1") < 0)) + return log_oom(); + } + + env_use = strv_env_merge(2, envp, arg_setenv); + if (!env_use) + return log_oom(); + + /* Let the parent know that we are ready and + * wait until the parent is ready with the + * setup, too... */ + if (!barrier_place_and_sync(barrier)) { /* #4 */ + log_error("Parent died too early"); + return -ESRCH; + } + + if (arg_chdir) + if (chdir(arg_chdir) < 0) + return log_error_errno(errno, "Failed to change to specified working directory %s: %m", arg_chdir); + + if (arg_start_mode == START_PID2) { + r = stub_pid1(); + if (r < 0) + return r; + } + + /* Now, explicitly close the log, so that we + * then can close all remaining fds. Closing + * the log explicitly first has the benefit + * that the logging subsystem knows about it, + * and is thus ready to be reopened should we + * need it again. Note that the other fds + * closed here are at least the locking and + * barrier fds. */ + log_close(); + (void) fdset_close_others(fds); + + if (arg_start_mode == START_BOOT) { + char **a; + size_t m; + + /* Automatically search for the init system */ + + m = strv_length(arg_parameters); + a = newa(char*, m + 2); + memcpy_safe(a + 1, arg_parameters, m * sizeof(char*)); + a[1 + m] = NULL; + + a[0] = (char*) "/usr/lib/systemd/systemd"; + execve(a[0], a, env_use); + + a[0] = (char*) "/lib/systemd/systemd"; + execve(a[0], a, env_use); + + a[0] = (char*) "/sbin/init"; + execve(a[0], a, env_use); + } else if (!strv_isempty(arg_parameters)) + execvpe(arg_parameters[0], arg_parameters, env_use); + else { + if (!arg_chdir) + /* If we cannot change the directory, we'll end up in /, that is expected. */ + (void) chdir(home ?: "/root"); + + execle("/bin/bash", "-bash", NULL, env_use); + execle("/bin/sh", "-sh", NULL, env_use); + } + + r = -errno; + (void) log_open(); + return log_error_errno(r, "execv() failed: %m"); +} + +static int outer_child( + Barrier *barrier, + const char *directory, + const char *console, + const char *root_device, bool root_device_rw, + const char *home_device, bool home_device_rw, + const char *srv_device, bool srv_device_rw, + bool interactive, + bool secondary, + int pid_socket, + int uuid_socket, + int kmsg_socket, + int rtnl_socket, + int uid_shift_socket, + FDSet *fds) { + + pid_t pid; + ssize_t l; + int r; + + assert(barrier); + assert(directory); + assert(console); + assert(pid_socket >= 0); + assert(uuid_socket >= 0); + assert(kmsg_socket >= 0); + + cg_unified_flush(); + + if (prctl(PR_SET_PDEATHSIG, SIGKILL) < 0) + return log_error_errno(errno, "PR_SET_PDEATHSIG failed: %m"); + + if (interactive) { + close_nointr(STDIN_FILENO); + close_nointr(STDOUT_FILENO); + close_nointr(STDERR_FILENO); + + r = open_terminal(console, O_RDWR); + if (r != STDIN_FILENO) { + if (r >= 0) { + safe_close(r); + r = -EINVAL; + } + + return log_error_errno(r, "Failed to open console: %m"); + } + + if (dup2(STDIN_FILENO, STDOUT_FILENO) != STDOUT_FILENO || + dup2(STDIN_FILENO, STDERR_FILENO) != STDERR_FILENO) + return log_error_errno(errno, "Failed to duplicate console: %m"); + } + + r = reset_audit_loginuid(); + if (r < 0) + return r; + + /* Mark everything as slave, so that we still + * receive mounts from the real root, but don't + * propagate mounts to the real root. */ + if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) + return log_error_errno(errno, "MS_SLAVE|MS_REC failed: %m"); + + r = mount_devices(directory, + root_device, root_device_rw, + home_device, home_device_rw, + srv_device, srv_device_rw); + if (r < 0) + return r; + + r = determine_uid_shift(directory); + if (r < 0) + return r; + + if (arg_userns_mode != USER_NAMESPACE_NO) { + /* Let the parent know which UID shift we read from the image */ + l = send(uid_shift_socket, &arg_uid_shift, sizeof(arg_uid_shift), MSG_NOSIGNAL); + if (l < 0) + return log_error_errno(errno, "Failed to send UID shift: %m"); + if (l != sizeof(arg_uid_shift)) { + log_error("Short write while sending UID shift."); + return -EIO; + } + + if (arg_userns_mode == USER_NAMESPACE_PICK) { + /* When we are supposed to pick the UID shift, the parent will check now whether the UID shift + * we just read from the image is available. If yes, it will send the UID shift back to us, if + * not it will pick a different one, and send it back to us. */ + + l = recv(uid_shift_socket, &arg_uid_shift, sizeof(arg_uid_shift), 0); + if (l < 0) + return log_error_errno(errno, "Failed to recv UID shift: %m"); + if (l != sizeof(arg_uid_shift)) { + log_error("Short read while recieving UID shift."); + return -EIO; + } + } + + log_info("Selected user namespace base " UID_FMT " and range " UID_FMT ".", arg_uid_shift, arg_uid_range); + } + + /* Turn directory into bind mount */ + if (mount(directory, directory, NULL, MS_BIND|MS_REC, NULL) < 0) + return log_error_errno(errno, "Failed to make bind mount: %m"); + + r = recursive_chown(directory, arg_uid_shift, arg_uid_range); + if (r < 0) + return r; + + r = setup_volatile( + directory, + arg_volatile_mode, + arg_userns_mode != USER_NAMESPACE_NO, + arg_uid_shift, + arg_uid_range, + arg_selinux_context); + if (r < 0) + return r; + + r = setup_volatile_state( + directory, + arg_volatile_mode, + arg_userns_mode != USER_NAMESPACE_NO, + arg_uid_shift, + arg_uid_range, + arg_selinux_context); + if (r < 0) + return r; + + r = base_filesystem_create(directory, arg_uid_shift, (gid_t) arg_uid_shift); + if (r < 0) + return r; + + if (arg_read_only) { + r = bind_remount_recursive(directory, true); + if (r < 0) + return log_error_errno(r, "Failed to make tree read-only: %m"); + } + + r = mount_all(directory, + arg_userns_mode != USER_NAMESPACE_NO, + false, + arg_private_network, + arg_uid_shift, + arg_uid_range, + arg_selinux_apifs_context); + if (r < 0) + return r; + + r = copy_devnodes(directory); + if (r < 0) + return r; + + dev_setup(directory, arg_uid_shift, arg_uid_shift); + + r = setup_pts(directory); + if (r < 0) + return r; + + r = setup_propagate(directory); + if (r < 0) + return r; + + r = setup_dev_console(directory, console); + if (r < 0) + return r; + + r = setup_seccomp(); + if (r < 0) + return r; + + r = setup_timezone(directory); + if (r < 0) + return r; + + r = setup_resolv_conf(directory); + if (r < 0) + return r; + + r = setup_machine_id(directory); + if (r < 0) + return r; + + r = setup_journal(directory); + if (r < 0) + return r; + + r = mount_custom( + directory, + arg_custom_mounts, + arg_n_custom_mounts, + arg_userns_mode != USER_NAMESPACE_NO, + arg_uid_shift, + arg_uid_range, + arg_selinux_apifs_context); + if (r < 0) + return r; + + r = mount_cgroups( + directory, + arg_unified_cgroup_hierarchy, + arg_userns_mode != USER_NAMESPACE_NO, + arg_uid_shift, + arg_uid_range, + arg_selinux_apifs_context); + if (r < 0) + return r; + + r = mount_move_root(directory); + if (r < 0) + return log_error_errno(r, "Failed to move root directory: %m"); + + pid = raw_clone(SIGCHLD|CLONE_NEWNS| + (arg_share_system ? 0 : CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS) | + (arg_private_network ? CLONE_NEWNET : 0) | + (arg_userns_mode != USER_NAMESPACE_NO ? CLONE_NEWUSER : 0), + NULL); + if (pid < 0) + return log_error_errno(errno, "Failed to fork inner child: %m"); + if (pid == 0) { + pid_socket = safe_close(pid_socket); + uuid_socket = safe_close(uuid_socket); + uid_shift_socket = safe_close(uid_shift_socket); + + /* The inner child has all namespaces that are + * requested, so that we all are owned by the user if + * user namespaces are turned on. */ + + r = inner_child(barrier, directory, secondary, kmsg_socket, rtnl_socket, fds); + if (r < 0) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + l = send(pid_socket, &pid, sizeof(pid), MSG_NOSIGNAL); + if (l < 0) + return log_error_errno(errno, "Failed to send PID: %m"); + if (l != sizeof(pid)) { + log_error("Short write while sending PID."); + return -EIO; + } + + l = send(uuid_socket, &arg_uuid, sizeof(arg_uuid), MSG_NOSIGNAL); + if (l < 0) + return log_error_errno(errno, "Failed to send machine ID: %m"); + if (l != sizeof(arg_uuid)) { + log_error("Short write while sending machine ID."); + return -EIO; + } + + pid_socket = safe_close(pid_socket); + uuid_socket = safe_close(uuid_socket); + kmsg_socket = safe_close(kmsg_socket); + rtnl_socket = safe_close(rtnl_socket); + + return 0; +} + +static int uid_shift_pick(uid_t *shift, LockFile *ret_lock_file) { + unsigned n_tries = 100; + uid_t candidate; + int r; + + assert(shift); + assert(ret_lock_file); + assert(arg_userns_mode == USER_NAMESPACE_PICK); + assert(arg_uid_range == 0x10000U); + + candidate = *shift; + + (void) mkdir("/run/systemd/nspawn-uid", 0755); + + for (;;) { + char lock_path[strlen("/run/systemd/nspawn-uid/") + DECIMAL_STR_MAX(uid_t) + 1]; + _cleanup_release_lock_file_ LockFile lf = LOCK_FILE_INIT; + + if (--n_tries <= 0) + return -EBUSY; + + if (candidate < UID_SHIFT_PICK_MIN || candidate > UID_SHIFT_PICK_MAX) + goto next; + if ((candidate & UINT32_C(0xFFFF)) != 0) + goto next; + + xsprintf(lock_path, "/run/systemd/nspawn-uid/" UID_FMT, candidate); + r = make_lock_file(lock_path, LOCK_EX|LOCK_NB, &lf); + if (r == -EBUSY) /* Range already taken by another nspawn instance */ + goto next; + if (r < 0) + return r; + + /* Make some superficial checks whether the range is currently known in the user database */ + if (getpwuid(candidate)) + goto next; + if (getpwuid(candidate + UINT32_C(0xFFFE))) + goto next; + if (getgrgid(candidate)) + goto next; + if (getgrgid(candidate + UINT32_C(0xFFFE))) + goto next; + + *ret_lock_file = lf; + lf = (struct LockFile) LOCK_FILE_INIT; + *shift = candidate; + return 0; + + next: + random_bytes(&candidate, sizeof(candidate)); + candidate = (candidate % (UID_SHIFT_PICK_MAX - UID_SHIFT_PICK_MIN)) + UID_SHIFT_PICK_MIN; + candidate &= (uid_t) UINT32_C(0xFFFF0000); + } +} + +static int setup_uid_map(pid_t pid) { + char uid_map[strlen("/proc//uid_map") + DECIMAL_STR_MAX(uid_t) + 1], line[DECIMAL_STR_MAX(uid_t)*3+3+1]; + int r; + + assert(pid > 1); + + xsprintf(uid_map, "/proc/" PID_FMT "/uid_map", pid); + xsprintf(line, UID_FMT " " UID_FMT " " UID_FMT "\n", 0, arg_uid_shift, arg_uid_range); + r = write_string_file(uid_map, line, 0); + if (r < 0) + return log_error_errno(r, "Failed to write UID map: %m"); + + /* We always assign the same UID and GID ranges */ + xsprintf(uid_map, "/proc/" PID_FMT "/gid_map", pid); + r = write_string_file(uid_map, line, 0); + if (r < 0) + return log_error_errno(r, "Failed to write GID map: %m"); + + return 0; +} + +static int load_settings(void) { + _cleanup_(settings_freep) Settings *settings = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *p = NULL; + const char *fn, *i; + int r; + + /* If all settings are masked, there's no point in looking for + * the settings file */ + if ((arg_settings_mask & _SETTINGS_MASK_ALL) == _SETTINGS_MASK_ALL) + return 0; + + fn = strjoina(arg_machine, ".nspawn"); + + /* We first look in the admin's directories in /etc and /run */ + FOREACH_STRING(i, "/etc/systemd/nspawn", "/run/systemd/nspawn") { + _cleanup_free_ char *j = NULL; + + j = strjoin(i, "/", fn, NULL); + if (!j) + return log_oom(); + + f = fopen(j, "re"); + if (f) { + p = j; + j = NULL; + + /* By default, we trust configuration from /etc and /run */ + if (arg_settings_trusted < 0) + arg_settings_trusted = true; + + break; + } + + if (errno != ENOENT) + return log_error_errno(errno, "Failed to open %s: %m", j); + } + + if (!f) { + /* After that, let's look for a file next to the + * actual image we shall boot. */ + + if (arg_image) { + p = file_in_same_dir(arg_image, fn); + if (!p) + return log_oom(); + } else if (arg_directory) { + p = file_in_same_dir(arg_directory, fn); + if (!p) + return log_oom(); + } + + if (p) { + f = fopen(p, "re"); + if (!f && errno != ENOENT) + return log_error_errno(errno, "Failed to open %s: %m", p); + + /* By default, we do not trust configuration from /var/lib/machines */ + if (arg_settings_trusted < 0) + arg_settings_trusted = false; + } + } + + if (!f) + return 0; + + log_debug("Settings are trusted: %s", yes_no(arg_settings_trusted)); + + r = settings_load(f, p, &settings); + if (r < 0) + return r; + + /* Copy over bits from the settings, unless they have been + * explicitly masked by command line switches. */ + + if ((arg_settings_mask & SETTING_START_MODE) == 0 && + settings->start_mode >= 0) { + arg_start_mode = settings->start_mode; + + strv_free(arg_parameters); + arg_parameters = settings->parameters; + settings->parameters = NULL; + } + + if ((arg_settings_mask & SETTING_WORKING_DIRECTORY) == 0 && + settings->working_directory) { + free(arg_chdir); + arg_chdir = settings->working_directory; + settings->working_directory = NULL; + } + + if ((arg_settings_mask & SETTING_ENVIRONMENT) == 0 && + settings->environment) { + strv_free(arg_setenv); + arg_setenv = settings->environment; + settings->environment = NULL; + } + + if ((arg_settings_mask & SETTING_USER) == 0 && + settings->user) { + free(arg_user); + arg_user = settings->user; + settings->user = NULL; + } + + if ((arg_settings_mask & SETTING_CAPABILITY) == 0) { + uint64_t plus; + + plus = settings->capability; + if (settings_private_network(settings)) + plus |= (1ULL << CAP_NET_ADMIN); + + if (!arg_settings_trusted && plus != 0) { + if (settings->capability != 0) + log_warning("Ignoring Capability= setting, file %s is not trusted.", p); + } else + arg_retain |= plus; + + arg_retain &= ~settings->drop_capability; + } + + if ((arg_settings_mask & SETTING_KILL_SIGNAL) == 0 && + settings->kill_signal > 0) + arg_kill_signal = settings->kill_signal; + + if ((arg_settings_mask & SETTING_PERSONALITY) == 0 && + settings->personality != PERSONALITY_INVALID) + arg_personality = settings->personality; + + if ((arg_settings_mask & SETTING_MACHINE_ID) == 0 && + !sd_id128_is_null(settings->machine_id)) { + + if (!arg_settings_trusted) + log_warning("Ignoring MachineID= setting, file %s is not trusted.", p); + else + arg_uuid = settings->machine_id; + } + + if ((arg_settings_mask & SETTING_READ_ONLY) == 0 && + settings->read_only >= 0) + arg_read_only = settings->read_only; + + if ((arg_settings_mask & SETTING_VOLATILE_MODE) == 0 && + settings->volatile_mode != _VOLATILE_MODE_INVALID) + arg_volatile_mode = settings->volatile_mode; + + if ((arg_settings_mask & SETTING_CUSTOM_MOUNTS) == 0 && + settings->n_custom_mounts > 0) { + + if (!arg_settings_trusted) + log_warning("Ignoring TemporaryFileSystem=, Bind= and BindReadOnly= settings, file %s is not trusted.", p); + else { + custom_mount_free_all(arg_custom_mounts, arg_n_custom_mounts); + arg_custom_mounts = settings->custom_mounts; + arg_n_custom_mounts = settings->n_custom_mounts; + + settings->custom_mounts = NULL; + settings->n_custom_mounts = 0; + } + } + + if ((arg_settings_mask & SETTING_NETWORK) == 0 && + (settings->private_network >= 0 || + settings->network_veth >= 0 || + settings->network_bridge || + settings->network_zone || + settings->network_interfaces || + settings->network_macvlan || + settings->network_ipvlan || + settings->network_veth_extra)) { + + if (!arg_settings_trusted) + log_warning("Ignoring network settings, file %s is not trusted.", p); + else { + arg_network_veth = settings_network_veth(settings); + arg_private_network = settings_private_network(settings); + + strv_free(arg_network_interfaces); + arg_network_interfaces = settings->network_interfaces; + settings->network_interfaces = NULL; + + strv_free(arg_network_macvlan); + arg_network_macvlan = settings->network_macvlan; + settings->network_macvlan = NULL; + + strv_free(arg_network_ipvlan); + arg_network_ipvlan = settings->network_ipvlan; + settings->network_ipvlan = NULL; + + strv_free(arg_network_veth_extra); + arg_network_veth_extra = settings->network_veth_extra; + settings->network_veth_extra = NULL; + + free(arg_network_bridge); + arg_network_bridge = settings->network_bridge; + settings->network_bridge = NULL; + + free(arg_network_zone); + arg_network_zone = settings->network_zone; + settings->network_zone = NULL; + } + } + + if ((arg_settings_mask & SETTING_EXPOSE_PORTS) == 0 && + settings->expose_ports) { + + if (!arg_settings_trusted) + log_warning("Ignoring Port= setting, file %s is not trusted.", p); + else { + expose_port_free_all(arg_expose_ports); + arg_expose_ports = settings->expose_ports; + settings->expose_ports = NULL; + } + } + + if ((arg_settings_mask & SETTING_USERNS) == 0 && + settings->userns_mode != _USER_NAMESPACE_MODE_INVALID) { + + if (!arg_settings_trusted) + log_warning("Ignoring PrivateUsers= and PrivateUsersChown= settings, file %s is not trusted.", p); + else { + arg_userns_mode = settings->userns_mode; + arg_uid_shift = settings->uid_shift; + arg_uid_range = settings->uid_range; + arg_userns_chown = settings->userns_chown; + } + } + + return 0; +} + +int main(int argc, char *argv[]) { + + _cleanup_free_ char *device_path = NULL, *root_device = NULL, *home_device = NULL, *srv_device = NULL, *console = NULL; + bool root_device_rw = true, home_device_rw = true, srv_device_rw = true; + _cleanup_close_ int master = -1, image_fd = -1; + _cleanup_fdset_free_ FDSet *fds = NULL; + int r, n_fd_passed, loop_nr = -1; + char veth_name[IFNAMSIZ] = ""; + bool secondary = false, remove_subvol = false; + sigset_t mask_chld; + pid_t pid = 0; + int ret = EXIT_SUCCESS; + union in_addr_union exposed = {}; + _cleanup_release_lock_file_ LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT; + bool interactive, veth_created = false; + + log_parse_environment(); + log_open(); + + /* Make sure rename_process() in the stub init process can work */ + saved_argv = argv; + saved_argc = argc; + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (geteuid() != 0) { + log_error("Need to be root."); + r = -EPERM; + goto finish; + } + r = determine_names(); + if (r < 0) + goto finish; + + r = load_settings(); + if (r < 0) + goto finish; + + r = verify_arguments(); + if (r < 0) + goto finish; + + n_fd_passed = sd_listen_fds(false); + if (n_fd_passed > 0) { + r = fdset_new_listen_fds(&fds, false); + if (r < 0) { + log_error_errno(r, "Failed to collect file descriptors: %m"); + goto finish; + } + } + + if (arg_directory) { + assert(!arg_image); + + if (path_equal(arg_directory, "/") && !arg_ephemeral) { + log_error("Spawning container on root directory is not supported. Consider using --ephemeral."); + r = -EINVAL; + goto finish; + } + + if (arg_ephemeral) { + _cleanup_free_ char *np = NULL; + + /* If the specified path is a mount point we + * generate the new snapshot immediately + * inside it under a random name. However if + * the specified is not a mount point we + * create the new snapshot in the parent + * directory, just next to it. */ + r = path_is_mount_point(arg_directory, 0); + if (r < 0) { + log_error_errno(r, "Failed to determine whether directory %s is mount point: %m", arg_directory); + goto finish; + } + if (r > 0) + r = tempfn_random_child(arg_directory, "machine.", &np); + else + r = tempfn_random(arg_directory, "machine.", &np); + if (r < 0) { + log_error_errno(r, "Failed to generate name for snapshot: %m"); + goto finish; + } + + r = image_path_lock(np, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock); + if (r < 0) { + log_error_errno(r, "Failed to lock %s: %m", np); + goto finish; + } + + r = btrfs_subvol_snapshot(arg_directory, np, (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA); + if (r < 0) { + log_error_errno(r, "Failed to create snapshot %s from %s: %m", np, arg_directory); + goto finish; + } + + free(arg_directory); + arg_directory = np; + np = NULL; + + remove_subvol = true; + + } else { + r = image_path_lock(arg_directory, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock); + if (r == -EBUSY) { + log_error_errno(r, "Directory tree %s is currently busy.", arg_directory); + goto finish; + } + if (r < 0) { + log_error_errno(r, "Failed to lock %s: %m", arg_directory); + return r; + } + + if (arg_template) { + r = btrfs_subvol_snapshot(arg_template, arg_directory, (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA); + if (r == -EEXIST) { + if (!arg_quiet) + log_info("Directory %s already exists, not populating from template %s.", arg_directory, arg_template); + } else if (r < 0) { + log_error_errno(r, "Couldn't create snapshot %s from %s: %m", arg_directory, arg_template); + goto finish; + } else { + if (!arg_quiet) + log_info("Populated %s from template %s.", arg_directory, arg_template); + } + } + } + + if (arg_start_mode == START_BOOT) { + if (path_is_os_tree(arg_directory) <= 0) { + log_error("Directory %s doesn't look like an OS root directory (os-release file is missing). Refusing.", arg_directory); + r = -EINVAL; + goto finish; + } + } else { + const char *p; + + p = strjoina(arg_directory, "/usr/"); + if (laccess(p, F_OK) < 0) { + log_error("Directory %s doesn't look like it has an OS tree. Refusing.", arg_directory); + r = -EINVAL; + goto finish; + } + } + + } else { + char template[] = "/tmp/nspawn-root-XXXXXX"; + + assert(arg_image); + assert(!arg_template); + + r = image_path_lock(arg_image, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock); + if (r == -EBUSY) { + r = log_error_errno(r, "Disk image %s is currently busy.", arg_image); + goto finish; + } + if (r < 0) { + r = log_error_errno(r, "Failed to create image lock: %m"); + goto finish; + } + + if (!mkdtemp(template)) { + log_error_errno(errno, "Failed to create temporary directory: %m"); + r = -errno; + goto finish; + } + + arg_directory = strdup(template); + if (!arg_directory) { + r = log_oom(); + goto finish; + } + + image_fd = setup_image(&device_path, &loop_nr); + if (image_fd < 0) { + r = image_fd; + goto finish; + } + + r = dissect_image(image_fd, + &root_device, &root_device_rw, + &home_device, &home_device_rw, + &srv_device, &srv_device_rw, + &secondary); + if (r < 0) + goto finish; + } + + r = custom_mounts_prepare(); + if (r < 0) + goto finish; + + interactive = + isatty(STDIN_FILENO) > 0 && + isatty(STDOUT_FILENO) > 0; + + master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY); + if (master < 0) { + r = log_error_errno(errno, "Failed to acquire pseudo tty: %m"); + goto finish; + } + + r = ptsname_malloc(master, &console); + if (r < 0) { + r = log_error_errno(r, "Failed to determine tty name: %m"); + goto finish; + } + + if (arg_selinux_apifs_context) { + r = mac_selinux_apply(console, arg_selinux_apifs_context); + if (r < 0) + goto finish; + } + + if (unlockpt(master) < 0) { + r = log_error_errno(errno, "Failed to unlock tty: %m"); + goto finish; + } + + if (!arg_quiet) + log_info("Spawning container %s on %s.\nPress ^] three times within 1s to kill container.", + arg_machine, arg_image ?: arg_directory); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); + + assert_se(sigemptyset(&mask_chld) == 0); + assert_se(sigaddset(&mask_chld, SIGCHLD) == 0); + + if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) { + r = log_error_errno(errno, "Failed to become subreaper: %m"); + goto finish; + } + + for (;;) { + static const struct sigaction sa = { + .sa_handler = nop_signal_handler, + .sa_flags = SA_NOCLDSTOP, + }; + + _cleanup_release_lock_file_ LockFile uid_shift_lock = LOCK_FILE_INIT; + _cleanup_close_ int etc_passwd_lock = -1; + _cleanup_close_pair_ int + kmsg_socket_pair[2] = { -1, -1 }, + rtnl_socket_pair[2] = { -1, -1 }, + pid_socket_pair[2] = { -1, -1 }, + uuid_socket_pair[2] = { -1, -1 }, + uid_shift_socket_pair[2] = { -1, -1 }; + _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(pty_forward_freep) PTYForward *forward = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + ContainerStatus container_status; + char last_char = 0; + int ifi = 0; + ssize_t l; + + if (arg_userns_mode == USER_NAMESPACE_PICK) { + /* When we shall pick the UID/GID range, let's first lock /etc/passwd, so that we can safely + * check with getpwuid() if the specific user already exists. Note that /etc might be + * read-only, in which case this will fail with EROFS. But that's really OK, as in that case we + * can be reasonably sure that no users are going to be added. Note that getpwuid() checks are + * really just an extra safety net. We kinda assume that the UID range we allocate from is + * really ours. */ + + etc_passwd_lock = take_etc_passwd_lock(NULL); + if (etc_passwd_lock < 0 && etc_passwd_lock != -EROFS) { + log_error_errno(r, "Failed to take /etc/passwd lock: %m"); + goto finish; + } + } + + r = barrier_create(&barrier); + if (r < 0) { + log_error_errno(r, "Cannot initialize IPC barrier: %m"); + goto finish; + } + + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, kmsg_socket_pair) < 0) { + r = log_error_errno(errno, "Failed to create kmsg socket pair: %m"); + goto finish; + } + + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, rtnl_socket_pair) < 0) { + r = log_error_errno(errno, "Failed to create rtnl socket pair: %m"); + goto finish; + } + + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pid_socket_pair) < 0) { + r = log_error_errno(errno, "Failed to create pid socket pair: %m"); + goto finish; + } + + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, uuid_socket_pair) < 0) { + r = log_error_errno(errno, "Failed to create id socket pair: %m"); + goto finish; + } + + if (arg_userns_mode != USER_NAMESPACE_NO) + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, uid_shift_socket_pair) < 0) { + r = log_error_errno(errno, "Failed to create uid shift socket pair: %m"); + goto finish; + } + + /* Child can be killed before execv(), so handle SIGCHLD + * in order to interrupt parent's blocking calls and + * give it a chance to call wait() and terminate. */ + r = sigprocmask(SIG_UNBLOCK, &mask_chld, NULL); + if (r < 0) { + r = log_error_errno(errno, "Failed to change the signal mask: %m"); + goto finish; + } + + r = sigaction(SIGCHLD, &sa, NULL); + if (r < 0) { + r = log_error_errno(errno, "Failed to install SIGCHLD handler: %m"); + goto finish; + } + + pid = raw_clone(SIGCHLD|CLONE_NEWNS, NULL); + if (pid < 0) { + if (errno == EINVAL) + r = log_error_errno(errno, "clone() failed, do you have namespace support enabled in your kernel? (You need UTS, IPC, PID and NET namespacing built in): %m"); + else + r = log_error_errno(errno, "clone() failed: %m"); + + goto finish; + } + + if (pid == 0) { + /* The outer child only has a file system namespace. */ + barrier_set_role(&barrier, BARRIER_CHILD); + + master = safe_close(master); + + kmsg_socket_pair[0] = safe_close(kmsg_socket_pair[0]); + rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]); + pid_socket_pair[0] = safe_close(pid_socket_pair[0]); + uuid_socket_pair[0] = safe_close(uuid_socket_pair[0]); + uid_shift_socket_pair[0] = safe_close(uid_shift_socket_pair[0]); + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + r = outer_child(&barrier, + arg_directory, + console, + root_device, root_device_rw, + home_device, home_device_rw, + srv_device, srv_device_rw, + interactive, + secondary, + pid_socket_pair[1], + uuid_socket_pair[1], + kmsg_socket_pair[1], + rtnl_socket_pair[1], + uid_shift_socket_pair[1], + fds); + if (r < 0) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + barrier_set_role(&barrier, BARRIER_PARENT); + + fds = fdset_free(fds); + + kmsg_socket_pair[1] = safe_close(kmsg_socket_pair[1]); + rtnl_socket_pair[1] = safe_close(rtnl_socket_pair[1]); + pid_socket_pair[1] = safe_close(pid_socket_pair[1]); + uuid_socket_pair[1] = safe_close(uuid_socket_pair[1]); + uid_shift_socket_pair[1] = safe_close(uid_shift_socket_pair[1]); + + if (arg_userns_mode != USER_NAMESPACE_NO) { + /* The child just let us know the UID shift it might have read from the image. */ + l = recv(uid_shift_socket_pair[0], &arg_uid_shift, sizeof(arg_uid_shift), 0); + if (l < 0) { + r = log_error_errno(errno, "Failed to read UID shift: %m"); + goto finish; + } + if (l != sizeof(arg_uid_shift)) { + log_error("Short read while reading UID shift."); + r = EIO; + goto finish; + } + + if (arg_userns_mode == USER_NAMESPACE_PICK) { + /* If we are supposed to pick the UID shift, let's try to use the shift read from the + * image, but if that's already in use, pick a new one, and report back to the child, + * which one we now picked. */ + + r = uid_shift_pick(&arg_uid_shift, &uid_shift_lock); + if (r < 0) { + log_error_errno(r, "Failed to pick suitable UID/GID range: %m"); + goto finish; + } + + l = send(uid_shift_socket_pair[0], &arg_uid_shift, sizeof(arg_uid_shift), MSG_NOSIGNAL); + if (l < 0) { + r = log_error_errno(errno, "Failed to send UID shift: %m"); + goto finish; + } + if (l != sizeof(arg_uid_shift)) { + log_error("Short write while writing UID shift."); + r = -EIO; + goto finish; + } + } + } + + /* Wait for the outer child. */ + r = wait_for_terminate_and_warn("namespace helper", pid, NULL); + if (r < 0) + goto finish; + if (r != 0) { + r = -EIO; + goto finish; + } + pid = 0; + + /* And now retrieve the PID of the inner child. */ + l = recv(pid_socket_pair[0], &pid, sizeof(pid), 0); + if (l < 0) { + r = log_error_errno(errno, "Failed to read inner child PID: %m"); + goto finish; + } + if (l != sizeof(pid)) { + log_error("Short read while reading inner child PID."); + r = EIO; + goto finish; + } + + /* We also retrieve container UUID in case it was generated by outer child */ + l = recv(uuid_socket_pair[0], &arg_uuid, sizeof(arg_uuid), 0); + if (l < 0) { + r = log_error_errno(errno, "Failed to read container machine ID: %m"); + goto finish; + } + if (l != sizeof(arg_uuid)) { + log_error("Short read while reading container machined ID."); + r = EIO; + goto finish; + } + + log_debug("Init process invoked as PID " PID_FMT, pid); + + if (arg_userns_mode != USER_NAMESPACE_NO) { + if (!barrier_place_and_sync(&barrier)) { /* #1 */ + log_error("Child died too early."); + r = -ESRCH; + goto finish; + } + + r = setup_uid_map(pid); + if (r < 0) + goto finish; + + (void) barrier_place(&barrier); /* #2 */ + } + + if (arg_private_network) { + + r = move_network_interfaces(pid, arg_network_interfaces); + if (r < 0) + goto finish; + + if (arg_network_veth) { + r = setup_veth(arg_machine, pid, veth_name, + arg_network_bridge || arg_network_zone); + if (r < 0) + goto finish; + else if (r > 0) + ifi = r; + + if (arg_network_bridge) { + /* Add the interface to a bridge */ + r = setup_bridge(veth_name, arg_network_bridge, false); + if (r < 0) + goto finish; + if (r > 0) + ifi = r; + } else if (arg_network_zone) { + /* Add the interface to a bridge, possibly creating it */ + r = setup_bridge(veth_name, arg_network_zone, true); + if (r < 0) + goto finish; + if (r > 0) + ifi = r; + } + } + + r = setup_veth_extra(arg_machine, pid, arg_network_veth_extra); + if (r < 0) + goto finish; + + /* We created the primary and extra veth links now; let's remember this, so that we know to + remove them later on. Note that we don't bother with removing veth links that were created + here when their setup failed half-way, because in that case the kernel should be able to + remove them on its own, since they cannot be referenced by anything yet. */ + veth_created = true; + + r = setup_macvlan(arg_machine, pid, arg_network_macvlan); + if (r < 0) + goto finish; + + r = setup_ipvlan(arg_machine, pid, arg_network_ipvlan); + if (r < 0) + goto finish; + } + + if (arg_register) { + r = register_machine( + arg_machine, + pid, + arg_directory, + arg_uuid, + ifi, + arg_slice, + arg_custom_mounts, arg_n_custom_mounts, + arg_kill_signal, + arg_property, + arg_keep_unit, + arg_container_service_name); + if (r < 0) + goto finish; + } + + r = sync_cgroup(pid, arg_unified_cgroup_hierarchy); + if (r < 0) + goto finish; + + if (arg_keep_unit) { + r = create_subcgroup(pid, arg_unified_cgroup_hierarchy); + if (r < 0) + goto finish; + } + + r = chown_cgroup(pid, arg_uid_shift); + if (r < 0) + goto finish; + + /* Notify the child that the parent is ready with all + * its setup (including cgroup-ification), and that + * the child can now hand over control to the code to + * run inside the container. */ + (void) barrier_place(&barrier); /* #3 */ + + /* Block SIGCHLD here, before notifying child. + * process_pty() will handle it with the other signals. */ + assert_se(sigprocmask(SIG_BLOCK, &mask_chld, NULL) >= 0); + + /* Reset signal to default */ + r = default_signals(SIGCHLD, -1); + if (r < 0) { + log_error_errno(r, "Failed to reset SIGCHLD: %m"); + goto finish; + } + + /* Let the child know that we are ready and wait that the child is completely ready now. */ + if (!barrier_place_and_sync(&barrier)) { /* #4 */ + log_error("Child died too early."); + r = -ESRCH; + goto finish; + } + + /* At this point we have made use of the UID we picked, and thus nss-mymachines will make them appear + * in getpwuid(), thus we can release the /etc/passwd lock. */ + etc_passwd_lock = safe_close(etc_passwd_lock); + + sd_notifyf(false, + "READY=1\n" + "STATUS=Container running.\n" + "X_NSPAWN_LEADER_PID=" PID_FMT, pid); + + r = sd_event_new(&event); + if (r < 0) { + log_error_errno(r, "Failed to get default event source: %m"); + goto finish; + } + + if (arg_kill_signal > 0) { + /* Try to kill the init system on SIGINT or SIGTERM */ + sd_event_add_signal(event, NULL, SIGINT, on_orderly_shutdown, PID_TO_PTR(pid)); + sd_event_add_signal(event, NULL, SIGTERM, on_orderly_shutdown, PID_TO_PTR(pid)); + } else { + /* Immediately exit */ + sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); + sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); + } + + /* simply exit on sigchld */ + sd_event_add_signal(event, NULL, SIGCHLD, NULL, NULL); + + if (arg_expose_ports) { + r = expose_port_watch_rtnl(event, rtnl_socket_pair[0], on_address_change, &exposed, &rtnl); + if (r < 0) + goto finish; + + (void) expose_port_execute(rtnl, arg_expose_ports, &exposed); + } + + rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]); + + r = pty_forward_new(event, master, PTY_FORWARD_IGNORE_VHANGUP | (interactive ? 0 : PTY_FORWARD_READ_ONLY), &forward); + if (r < 0) { + log_error_errno(r, "Failed to create PTY forwarder: %m"); + goto finish; + } + + r = sd_event_loop(event); + if (r < 0) { + log_error_errno(r, "Failed to run event loop: %m"); + goto finish; + } + + pty_forward_get_last_char(forward, &last_char); + + forward = pty_forward_free(forward); + + if (!arg_quiet && last_char != '\n') + putc('\n', stdout); + + /* Kill if it is not dead yet anyway */ + if (arg_register && !arg_keep_unit) + terminate_machine(pid); + + /* Normally redundant, but better safe than sorry */ + kill(pid, SIGKILL); + + r = wait_for_container(pid, &container_status); + pid = 0; + + if (r < 0) + /* We failed to wait for the container, or the + * container exited abnormally */ + goto finish; + else if (r > 0 || container_status == CONTAINER_TERMINATED) { + /* The container exited with a non-zero + * status, or with zero status and no reboot + * was requested. */ + ret = r; + break; + } + + /* CONTAINER_REBOOTED, loop again */ + + if (arg_keep_unit) { + /* Special handling if we are running as a + * service: instead of simply restarting the + * machine we want to restart the entire + * service, so let's inform systemd about this + * with the special exit code 133. The service + * file uses RestartForceExitStatus=133 so + * that this results in a full nspawn + * restart. This is necessary since we might + * have cgroup parameters set we want to have + * flushed out. */ + ret = 133; + r = 0; + break; + } + + expose_port_flush(arg_expose_ports, &exposed); + + (void) remove_veth_links(veth_name, arg_network_veth_extra); + veth_created = false; + } + +finish: + sd_notify(false, + "STOPPING=1\n" + "STATUS=Terminating..."); + + if (pid > 0) + kill(pid, SIGKILL); + + /* Try to flush whatever is still queued in the pty */ + if (master >= 0) + (void) copy_bytes(master, STDOUT_FILENO, (uint64_t) -1, false); + + loop_remove(loop_nr, &image_fd); + + if (remove_subvol && arg_directory) { + int k; + + k = btrfs_subvol_remove(arg_directory, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); + if (k < 0) + log_warning_errno(k, "Cannot remove subvolume '%s', ignoring: %m", arg_directory); + } + + if (arg_machine) { + const char *p; + + p = strjoina("/run/systemd/nspawn/propagate/", arg_machine); + (void) rm_rf(p, REMOVE_ROOT); + } + + expose_port_flush(arg_expose_ports, &exposed); + + if (veth_created) + (void) remove_veth_links(veth_name, arg_network_veth_extra); + (void) remove_bridge(arg_network_zone); + + free(arg_directory); + free(arg_template); + free(arg_image); + free(arg_machine); + free(arg_user); + free(arg_chdir); + strv_free(arg_setenv); + free(arg_network_bridge); + strv_free(arg_network_interfaces); + strv_free(arg_network_macvlan); + strv_free(arg_network_ipvlan); + strv_free(arg_network_veth_extra); + strv_free(arg_parameters); + custom_mount_free_all(arg_custom_mounts, arg_n_custom_mounts); + expose_port_free_all(arg_expose_ports); + + return r < 0 ? EXIT_FAILURE : ret; +} diff --git a/src/systemd-nspawn/test-patch-uid.c b/src/systemd-nspawn/test-patch-uid.c new file mode 100644 index 0000000000..11c5321788 --- /dev/null +++ b/src/systemd-nspawn/test-patch-uid.c @@ -0,0 +1,61 @@ +/*** + This file is part of systemd. + + Copyright 2016 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 "log.h" +#include "nspawn-patch-uid.h" +#include "user-util.h" +#include "util.h" + +int main(int argc, char *argv[]) { + uid_t shift, range; + int r; + + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + + if (argc != 4) { + log_error("Expected PATH SHIFT RANGE parameters."); + return EXIT_FAILURE; + } + + r = parse_uid(argv[2], &shift); + if (r < 0) { + log_error_errno(r, "Failed to parse UID shift %s.", argv[2]); + return EXIT_FAILURE; + } + + r = parse_gid(argv[3], &range); + if (r < 0) { + log_error_errno(r, "Failed to parse UID range %s.", argv[3]); + return EXIT_FAILURE; + } + + r = path_patch_uid(argv[1], shift, range); + if (r < 0) { + log_error_errno(r, "Failed to patch directory tree: %m"); + return EXIT_FAILURE; + } + + log_info("Changed: %s", yes_no(r)); + + return EXIT_SUCCESS; +} diff --git a/src/systemd-path/Makefile b/src/systemd-path/Makefile new file mode 100644 index 0000000000..19eb6bd10a --- /dev/null +++ b/src/systemd-path/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_path_SOURCES = \ + src/path/path.c + +systemd_path_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-path/path.c b/src/systemd-path/path.c new file mode 100644 index 0000000000..80268ed874 --- /dev/null +++ b/src/systemd-path/path.c @@ -0,0 +1,198 @@ +/*** + 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 +#include +#include + +#include + +#include "alloc-util.h" +#include "log.h" +#include "macro.h" +#include "string-util.h" +#include "util.h" + +static const char *arg_suffix = NULL; + +static const char* const path_table[_SD_PATH_MAX] = { + [SD_PATH_TEMPORARY] = "temporary", + [SD_PATH_TEMPORARY_LARGE] = "temporary-large", + [SD_PATH_SYSTEM_BINARIES] = "system-binaries", + [SD_PATH_SYSTEM_INCLUDE] = "system-include", + [SD_PATH_SYSTEM_LIBRARY_PRIVATE] = "system-library-private", + [SD_PATH_SYSTEM_LIBRARY_ARCH] = "system-library-arch", + [SD_PATH_SYSTEM_SHARED] = "system-shared", + [SD_PATH_SYSTEM_CONFIGURATION_FACTORY] = "system-configuration-factory", + [SD_PATH_SYSTEM_STATE_FACTORY] = "system-state-factory", + [SD_PATH_SYSTEM_CONFIGURATION] = "system-configuration", + [SD_PATH_SYSTEM_RUNTIME] = "system-runtime", + [SD_PATH_SYSTEM_RUNTIME_LOGS] = "system-runtime-logs", + [SD_PATH_SYSTEM_STATE_PRIVATE] = "system-state-private", + [SD_PATH_SYSTEM_STATE_LOGS] = "system-state-logs", + [SD_PATH_SYSTEM_STATE_CACHE] = "system-state-cache", + [SD_PATH_SYSTEM_STATE_SPOOL] = "system-state-spool", + [SD_PATH_USER_BINARIES] = "user-binaries", + [SD_PATH_USER_LIBRARY_PRIVATE] = "user-library-private", + [SD_PATH_USER_LIBRARY_ARCH] = "user-library-arch", + [SD_PATH_USER_SHARED] = "user-shared", + [SD_PATH_USER_CONFIGURATION] = "user-configuration", + [SD_PATH_USER_RUNTIME] = "user-runtime", + [SD_PATH_USER_STATE_CACHE] = "user-state-cache", + [SD_PATH_USER] = "user", + [SD_PATH_USER_DOCUMENTS] = "user-documents", + [SD_PATH_USER_MUSIC] = "user-music", + [SD_PATH_USER_PICTURES] = "user-pictures", + [SD_PATH_USER_VIDEOS] = "user-videos", + [SD_PATH_USER_DOWNLOAD] = "user-download", + [SD_PATH_USER_PUBLIC] = "user-public", + [SD_PATH_USER_TEMPLATES] = "user-templates", + [SD_PATH_USER_DESKTOP] = "user-desktop", + [SD_PATH_SEARCH_BINARIES] = "search-binaries", + [SD_PATH_SEARCH_LIBRARY_PRIVATE] = "search-library-private", + [SD_PATH_SEARCH_LIBRARY_ARCH] = "search-library-arch", + [SD_PATH_SEARCH_SHARED] = "search-shared", + [SD_PATH_SEARCH_CONFIGURATION_FACTORY] = "search-configuration-factory", + [SD_PATH_SEARCH_STATE_FACTORY] = "search-state-factory", + [SD_PATH_SEARCH_CONFIGURATION] = "search-configuration", +}; + +static int list_homes(void) { + uint64_t i = 0; + int r = 0; + + for (i = 0; i < ELEMENTSOF(path_table); i++) { + _cleanup_free_ char *p = NULL; + int q; + + q = sd_path_home(i, arg_suffix, &p); + if (q == -ENXIO) + continue; + if (q < 0) { + log_error_errno(r, "Failed to query %s: %m", path_table[i]); + r = q; + continue; + } + + printf("%s: %s\n", path_table[i], p); + } + + return r; +} + +static int print_home(const char *n) { + uint64_t i = 0; + int r; + + for (i = 0; i < ELEMENTSOF(path_table); i++) { + if (streq(path_table[i], n)) { + _cleanup_free_ char *p = NULL; + + r = sd_path_home(i, arg_suffix, &p); + if (r < 0) + return log_error_errno(r, "Failed to query %s: %m", n); + + printf("%s\n", p); + return 0; + } + } + + log_error("Path %s not known.", n); + return -EOPNOTSUPP; +} + +static void help(void) { + printf("%s [OPTIONS...] [NAME...]\n\n" + "Show system and user paths.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --suffix=SUFFIX Suffix to append to paths\n", + program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_SUFFIX, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "suffix", required_argument, NULL, ARG_SUFFIX }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_SUFFIX: + arg_suffix = optarg; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +int main(int argc, char* argv[]) { + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (argc > optind) { + int i, q; + + for (i = optind; i < argc; i++) { + q = print_home(argv[i]); + if (q < 0) + r = q; + } + } else + r = list_homes(); + + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-quotacheck/Makefile b/src/systemd-quotacheck/Makefile new file mode 100644 index 0000000000..42ff1dc814 --- /dev/null +++ b/src/systemd-quotacheck/Makefile @@ -0,0 +1,46 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_QUOTACHECK),) +libexec_PROGRAMS += \ + systemd-quotacheck + +nodist_systemunit_DATA += \ + units/systemd-quotacheck.service + +systemd_quotacheck_SOURCES = \ + src/quotacheck/quotacheck.c + +systemd_quotacheck_LDADD = \ + libshared.la +endif # ENABLE_QUOTACHECK + +EXTRA_DIST += \ + units/systemd-quotacheck.service.in + +nodist_systemunit_DATA += \ + units/quotaon.service + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-quotacheck/quotacheck.c b/src/systemd-quotacheck/quotacheck.c new file mode 100644 index 0000000000..6d8c05f046 --- /dev/null +++ b/src/systemd-quotacheck/quotacheck.c @@ -0,0 +1,124 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include + +#include "proc-cmdline.h" +#include "process-util.h" +#include "signal-util.h" +#include "string-util.h" +#include "util.h" + +static bool arg_skip = false; +static bool arg_force = false; + +static int parse_proc_cmdline_item(const char *key, const char *value) { + + if (streq(key, "quotacheck.mode") && value) { + + if (streq(value, "auto")) + arg_force = arg_skip = false; + else if (streq(value, "force")) + arg_force = true; + else if (streq(value, "skip")) + arg_skip = true; + else + log_warning("Invalid quotacheck.mode= parameter '%s'. Ignoring.", value); + } + +#ifdef HAVE_SYSV_COMPAT + else if (streq(key, "forcequotacheck") && !value) { + log_warning("Please use 'quotacheck.mode=force' rather than 'forcequotacheck' on the kernel command line."); + arg_force = true; + } +#endif + + return 0; +} + +static void test_files(void) { + +#ifdef HAVE_SYSV_COMPAT + if (access("/forcequotacheck", F_OK) >= 0) { + log_error("Please pass 'quotacheck.mode=force' on the kernel command line rather than creating /forcequotacheck on the root file system."); + arg_force = true; + } +#endif +} + +int main(int argc, char *argv[]) { + + static const char * const cmdline[] = { + QUOTACHECK, + "-anug", + NULL + }; + + pid_t pid; + int r; + + if (argc > 1) { + log_error("This program takes no arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + r = parse_proc_cmdline(parse_proc_cmdline_item); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + + test_files(); + + if (!arg_force) { + if (arg_skip) + return EXIT_SUCCESS; + + if (access("/run/systemd/quotacheck", F_OK) < 0) + return EXIT_SUCCESS; + } + + pid = fork(); + if (pid < 0) { + log_error_errno(errno, "fork(): %m"); + return EXIT_FAILURE; + } else if (pid == 0) { + + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + execv(cmdline[0], (char**) cmdline); + _exit(1); /* Operational error */ + } + + r = wait_for_terminate_and_warn("quotacheck", pid, true); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-random-seed/Makefile b/src/systemd-random-seed/Makefile new file mode 100644 index 0000000000..d32f124c9a --- /dev/null +++ b/src/systemd-random-seed/Makefile @@ -0,0 +1,47 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_RANDOMSEED),) +libexec_PROGRAMS += \ + systemd-random-seed + +nodist_systemunit_DATA += \ + units/systemd-random-seed.service + +systemd_random_seed_SOURCES = \ + src/random-seed/random-seed.c + +systemd_random_seed_LDADD = \ + libshared.la + +SYSINIT_TARGET_WANTS += \ + systemd-random-seed.service + +endif # ENABLE_RANDOMSEED + +EXTRA_DIST += \ + units/systemd-random-seed.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-random-seed/random-seed.c b/src/systemd-random-seed/random-seed.c new file mode 100644 index 0000000000..6748bb9dd3 --- /dev/null +++ b/src/systemd-random-seed/random-seed.c @@ -0,0 +1,176 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "log.h" +#include "mkdir.h" +#include "string-util.h" +#include "util.h" + +#define POOL_SIZE_MIN 512 + +int main(int argc, char *argv[]) { + _cleanup_close_ int seed_fd = -1, random_fd = -1; + _cleanup_free_ void* buf = NULL; + size_t buf_size = 0; + ssize_t k; + int r, open_rw_error; + FILE *f; + bool refresh_seed_file = true; + + if (argc != 2) { + log_error("This program requires one argument."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + /* Read pool size, if possible */ + f = fopen("/proc/sys/kernel/random/poolsize", "re"); + if (f) { + if (fscanf(f, "%zu", &buf_size) > 0) + /* poolsize is in bits on 2.6, but we want bytes */ + buf_size /= 8; + + fclose(f); + } + + if (buf_size <= POOL_SIZE_MIN) + buf_size = POOL_SIZE_MIN; + + buf = malloc(buf_size); + if (!buf) { + r = log_oom(); + goto finish; + } + + r = mkdir_parents_label(RANDOM_SEED, 0755); + if (r < 0) { + log_error_errno(r, "Failed to create directory " RANDOM_SEED_DIR ": %m"); + goto finish; + } + + /* When we load the seed we read it and write it to the device + * and then immediately update the saved seed with new data, + * to make sure the next boot gets seeded differently. */ + + if (streq(argv[1], "load")) { + + seed_fd = open(RANDOM_SEED, O_RDWR|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600); + open_rw_error = -errno; + if (seed_fd < 0) { + refresh_seed_file = false; + + seed_fd = open(RANDOM_SEED, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (seed_fd < 0) { + bool missing = errno == ENOENT; + + log_full_errno(missing ? LOG_DEBUG : LOG_ERR, + open_rw_error, "Failed to open " RANDOM_SEED " for writing: %m"); + r = log_full_errno(missing ? LOG_DEBUG : LOG_ERR, + errno, "Failed to open " RANDOM_SEED " for reading: %m"); + if (missing) + r = 0; + + goto finish; + } + } + + random_fd = open("/dev/urandom", O_RDWR|O_CLOEXEC|O_NOCTTY, 0600); + if (random_fd < 0) { + random_fd = open("/dev/urandom", O_WRONLY|O_CLOEXEC|O_NOCTTY, 0600); + if (random_fd < 0) { + r = log_error_errno(errno, "Failed to open /dev/urandom: %m"); + goto finish; + } + } + + k = loop_read(seed_fd, buf, buf_size, false); + if (k < 0) + r = log_error_errno(k, "Failed to read seed from " RANDOM_SEED ": %m"); + else if (k == 0) { + r = 0; + log_debug("Seed file " RANDOM_SEED " not yet initialized, proceeding."); + } else { + (void) lseek(seed_fd, 0, SEEK_SET); + + r = loop_write(random_fd, buf, (size_t) k, false); + if (r < 0) + log_error_errno(r, "Failed to write seed to /dev/urandom: %m"); + } + + } else if (streq(argv[1], "save")) { + + seed_fd = open(RANDOM_SEED, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600); + if (seed_fd < 0) { + r = log_error_errno(errno, "Failed to open " RANDOM_SEED ": %m"); + goto finish; + } + + random_fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (random_fd < 0) { + r = log_error_errno(errno, "Failed to open /dev/urandom: %m"); + goto finish; + } + + } else { + log_error("Unknown verb '%s'.", argv[1]); + r = -EINVAL; + goto finish; + } + + if (refresh_seed_file) { + + /* This is just a safety measure. Given that we are root and + * most likely created the file ourselves the mode and owner + * should be correct anyway. */ + (void) fchmod(seed_fd, 0600); + (void) fchown(seed_fd, 0, 0); + + k = loop_read(random_fd, buf, buf_size, false); + if (k < 0) { + r = log_error_errno(k, "Failed to read new seed from /dev/urandom: %m"); + goto finish; + } + if (k == 0) { + log_error("Got EOF while reading from /dev/urandom."); + r = -EIO; + goto finish; + } + + r = loop_write(seed_fd, buf, (size_t) k, false); + if (r < 0) + log_error_errno(r, "Failed to write new random seed file: %m"); + } + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-rc-local-generator/Makefile b/src/systemd-rc-local-generator/Makefile new file mode 100644 index 0000000000..2e9b3e7a64 --- /dev/null +++ b/src/systemd-rc-local-generator/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_rc_local_generator_SOURCES = \ + src/rc-local-generator/rc-local-generator.c + +systemd_rc_local_generator_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-rc-local-generator/rc-local-generator.c b/src/systemd-rc-local-generator/rc-local-generator.c new file mode 100644 index 0000000000..618bbe428d --- /dev/null +++ b/src/systemd-rc-local-generator/rc-local-generator.c @@ -0,0 +1,101 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2011 Michal Schmidt + + 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 +#include + +#include "alloc-util.h" +#include "log.h" +#include "mkdir.h" +#include "string-util.h" +#include "util.h" + +#ifndef RC_LOCAL_SCRIPT_PATH_START +#define RC_LOCAL_SCRIPT_PATH_START "/etc/rc.d/rc.local" +#endif + +#ifndef RC_LOCAL_SCRIPT_PATH_STOP +#define RC_LOCAL_SCRIPT_PATH_STOP "/sbin/halt.local" +#endif + +static const char *arg_dest = "/tmp"; + +static int add_symlink(const char *service, const char *where) { + _cleanup_free_ char *from = NULL, *to = NULL; + int r; + + assert(service); + assert(where); + + from = strjoin(SYSTEM_DATA_UNIT_PATH, "/", service, NULL); + if (!from) + return log_oom(); + + to = strjoin(arg_dest, "/", where, ".wants/", service, NULL); + if (!to) + return log_oom(); + + mkdir_parents_label(to, 0755); + + r = symlink(from, to); + if (r < 0) { + if (errno == EEXIST) + return 0; + + return log_error_errno(errno, "Failed to create symlink %s: %m", to); + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r = EXIT_SUCCESS; + + if (argc > 1 && argc != 4) { + log_error("This program takes three or no arguments."); + return EXIT_FAILURE; + } + + if (argc > 1) + arg_dest = argv[1]; + + log_set_target(LOG_TARGET_SAFE); + log_parse_environment(); + log_open(); + + umask(0022); + + if (access(RC_LOCAL_SCRIPT_PATH_START, X_OK) >= 0) { + log_debug("Automatically adding rc-local.service."); + + if (add_symlink("rc-local.service", "multi-user.target") < 0) + r = EXIT_FAILURE; + } + + if (access(RC_LOCAL_SCRIPT_PATH_STOP, X_OK) >= 0) { + log_debug("Automatically adding halt-local.service."); + + if (add_symlink("halt-local.service", "final.target") < 0) + r = EXIT_FAILURE; + } + + return r; +} diff --git a/src/systemd-remount-fs/Makefile b/src/systemd-remount-fs/Makefile new file mode 100644 index 0000000000..91baa27bf1 --- /dev/null +++ b/src/systemd-remount-fs/Makefile @@ -0,0 +1,34 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_remount_fs_SOURCES = \ + src/remount-fs/remount-fs.c \ + src/core/mount-setup.c \ + src/core/mount-setup.h + +systemd_remount_fs_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-remount-fs/remount-fs.c b/src/systemd-remount-fs/remount-fs.c new file mode 100644 index 0000000000..6468d1eecd --- /dev/null +++ b/src/systemd-remount-fs/remount-fs.c @@ -0,0 +1,155 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include + +#include "exit-status.h" +#include "log.h" +#include "mount-setup.h" +#include "mount-util.h" +#include "path-util.h" +#include "process-util.h" +#include "signal-util.h" +#include "strv.h" +#include "util.h" + +/* Goes through /etc/fstab and remounts all API file systems, applying + * options that are in /etc/fstab that systemd might not have + * respected */ + +int main(int argc, char *argv[]) { + _cleanup_hashmap_free_free_ Hashmap *pids = NULL; + _cleanup_endmntent_ FILE *f = NULL; + struct mntent* me; + int r; + + if (argc > 1) { + log_error("This program takes no argument."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + f = setmntent("/etc/fstab", "r"); + if (!f) { + if (errno == ENOENT) { + r = 0; + goto finish; + } + + r = log_error_errno(errno, "Failed to open /etc/fstab: %m"); + goto finish; + } + + pids = hashmap_new(NULL); + if (!pids) { + r = log_oom(); + goto finish; + } + + while ((me = getmntent(f))) { + pid_t pid; + int k; + char *s; + + /* Remount the root fs, /usr and all API VFS */ + if (!mount_point_is_api(me->mnt_dir) && + !path_equal(me->mnt_dir, "/") && + !path_equal(me->mnt_dir, "/usr")) + continue; + + log_debug("Remounting %s", me->mnt_dir); + + pid = fork(); + if (pid < 0) { + r = log_error_errno(errno, "Failed to fork: %m"); + goto finish; + } + + if (pid == 0) { + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + (void) prctl(PR_SET_PDEATHSIG, SIGTERM); + + execv(MOUNT_PATH, STRV_MAKE(MOUNT_PATH, me->mnt_dir, "-o", "remount")); + + log_error_errno(errno, "Failed to execute " MOUNT_PATH ": %m"); + _exit(EXIT_FAILURE); + } + + /* Parent */ + + s = strdup(me->mnt_dir); + if (!s) { + r = log_oom(); + goto finish; + } + + k = hashmap_put(pids, PID_TO_PTR(pid), s); + if (k < 0) { + free(s); + r = log_oom(); + goto finish; + } + } + + r = 0; + while (!hashmap_isempty(pids)) { + siginfo_t si = {}; + char *s; + + if (waitid(P_ALL, 0, &si, WEXITED) < 0) { + + if (errno == EINTR) + continue; + + r = log_error_errno(errno, "waitid() failed: %m"); + goto finish; + } + + s = hashmap_remove(pids, PID_TO_PTR(si.si_pid)); + if (s) { + if (!is_clean_exit(si.si_code, si.si_status, NULL)) { + if (si.si_code == CLD_EXITED) + log_error(MOUNT_PATH " for %s exited with exit status %i.", s, si.si_status); + else + log_error(MOUNT_PATH " for %s terminated by signal %s.", s, signal_to_string(si.si_status)); + + r = -ENOEXEC; + } + + free(s); + } + } + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-reply-password/Makefile b/src/systemd-reply-password/Makefile new file mode 100644 index 0000000000..30283141aa --- /dev/null +++ b/src/systemd-reply-password/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_reply_password_SOURCES = \ + src/reply-password/reply-password.c + +systemd_reply_password_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-reply-password/reply-password.c b/src/systemd-reply-password/reply-password.c new file mode 100644 index 0000000000..17eab9772e --- /dev/null +++ b/src/systemd-reply-password/reply-password.c @@ -0,0 +1,96 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include + +#include "fd-util.h" +#include "log.h" +#include "macro.h" +#include "socket-util.h" +#include "string-util.h" +#include "util.h" + +static int send_on_socket(int fd, const char *socket_name, const void *packet, size_t size) { + union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + }; + + assert(fd >= 0); + assert(socket_name); + assert(packet); + + strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path)); + + if (sendto(fd, packet, size, MSG_NOSIGNAL, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) + return log_error_errno(errno, "Failed to send: %m"); + + return 0; +} + +int main(int argc, char *argv[]) { + _cleanup_close_ int fd = -1; + char packet[LINE_MAX]; + size_t length; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + if (argc != 3) { + log_error("Wrong number of arguments."); + return EXIT_FAILURE; + } + + if (streq(argv[1], "1")) { + + packet[0] = '+'; + if (!fgets(packet+1, sizeof(packet)-1, stdin)) { + r = log_error_errno(errno, "Failed to read password: %m"); + goto finish; + } + + truncate_nl(packet+1); + length = 1 + strlen(packet+1) + 1; + } else if (streq(argv[1], "0")) { + packet[0] = '-'; + length = 1; + } else { + log_error("Invalid first argument %s", argv[1]); + r = -EINVAL; + goto finish; + } + + fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) { + r = log_error_errno(errno, "socket() failed: %m"); + goto finish; + } + + r = send_on_socket(fd, argv[2], packet, length); + +finish: + memory_erase(packet, sizeof(packet)); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-rfkill/Makefile b/src/systemd-rfkill/Makefile new file mode 100644 index 0000000000..4cb5137e63 --- /dev/null +++ b/src/systemd-rfkill/Makefile @@ -0,0 +1,46 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_RFKILL),) +libexec_PROGRAMS += \ + systemd-rfkill + +nodist_systemunit_DATA += \ + units/systemd-rfkill.service + +dist_systemunit_DATA += \ + units/systemd-rfkill.socket + +systemd_rfkill_SOURCES = \ + src/rfkill/rfkill.c + +systemd_rfkill_LDADD = \ + libshared.la +endif # ENABLE_RFKILL + +EXTRA_DIST += \ + units/systemd-rfkill.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-rfkill/rfkill.c b/src/systemd-rfkill/rfkill.c new file mode 100644 index 0000000000..f0b0ad9275 --- /dev/null +++ b/src/systemd-rfkill/rfkill.c @@ -0,0 +1,426 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include "libudev.h" +#include + +#include "alloc-util.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "io-util.h" +#include "mkdir.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "string-table.h" +#include "string-util.h" +#include "udev-util.h" +#include "util.h" + +#define EXIT_USEC (5 * USEC_PER_SEC) + +static const char* const rfkill_type_table[NUM_RFKILL_TYPES] = { + [RFKILL_TYPE_ALL] = "all", + [RFKILL_TYPE_WLAN] = "wlan", + [RFKILL_TYPE_BLUETOOTH] = "bluetooth", + [RFKILL_TYPE_UWB] = "uwb", + [RFKILL_TYPE_WIMAX] = "wimax", + [RFKILL_TYPE_WWAN] = "wwan", + [RFKILL_TYPE_GPS] = "gps", + [RFKILL_TYPE_FM] = "fm", + [RFKILL_TYPE_NFC] = "nfc", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(rfkill_type, int); + +static int find_device( + struct udev *udev, + const struct rfkill_event *event, + struct udev_device **ret) { + + _cleanup_free_ char *sysname = NULL; + struct udev_device *device; + const char *name; + + assert(udev); + assert(event); + assert(ret); + + if (asprintf(&sysname, "rfkill%i", event->idx) < 0) + return log_oom(); + + device = udev_device_new_from_subsystem_sysname(udev, "rfkill", sysname); + if (!device) + return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open device: %m"); + + name = udev_device_get_sysattr_value(device, "name"); + if (!name) { + log_debug("Device has no name, ignoring."); + udev_device_unref(device); + return -ENOENT; + } + + log_debug("Operating on rfkill device '%s'.", name); + + *ret = device; + return 0; +} + +static int wait_for_initialized( + struct udev *udev, + struct udev_device *device, + struct udev_device **ret) { + + _cleanup_udev_monitor_unref_ struct udev_monitor *monitor = NULL; + struct udev_device *d; + const char *sysname; + int watch_fd, r; + + assert(udev); + assert(device); + assert(ret); + + if (udev_device_get_is_initialized(device) != 0) { + *ret = udev_device_ref(device); + return 0; + } + + assert_se(sysname = udev_device_get_sysname(device)); + + /* Wait until the device is initialized, so that we can get + * access to the ID_PATH property */ + + monitor = udev_monitor_new_from_netlink(udev, "udev"); + if (!monitor) + return log_error_errno(errno, "Failed to acquire monitor: %m"); + + r = udev_monitor_filter_add_match_subsystem_devtype(monitor, "rfkill", NULL); + if (r < 0) + return log_error_errno(r, "Failed to add rfkill udev match to monitor: %m"); + + r = udev_monitor_enable_receiving(monitor); + if (r < 0) + return log_error_errno(r, "Failed to enable udev receiving: %m"); + + watch_fd = udev_monitor_get_fd(monitor); + if (watch_fd < 0) + return log_error_errno(watch_fd, "Failed to get watch fd: %m"); + + /* Check again, maybe things changed */ + d = udev_device_new_from_subsystem_sysname(udev, "rfkill", sysname); + if (!d) + return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open device: %m"); + + if (udev_device_get_is_initialized(d) != 0) { + *ret = d; + return 0; + } + + for (;;) { + _cleanup_udev_device_unref_ struct udev_device *t = NULL; + + r = fd_wait_for_event(watch_fd, POLLIN, USEC_INFINITY); + if (r == -EINTR) + continue; + if (r < 0) + return log_error_errno(r, "Failed to watch udev monitor: %m"); + + t = udev_monitor_receive_device(monitor); + if (!t) + continue; + + if (streq_ptr(udev_device_get_sysname(device), sysname)) { + *ret = udev_device_ref(t); + return 0; + } + } +} + +static int determine_state_file( + struct udev *udev, + const struct rfkill_event *event, + struct udev_device *d, + char **ret) { + + _cleanup_udev_device_unref_ struct udev_device *device = NULL; + const char *path_id, *type; + char *state_file; + int r; + + assert(event); + assert(d); + assert(ret); + + r = wait_for_initialized(udev, d, &device); + if (r < 0) + return r; + + assert_se(type = rfkill_type_to_string(event->type)); + + path_id = udev_device_get_property_value(device, "ID_PATH"); + if (path_id) { + _cleanup_free_ char *escaped_path_id = NULL; + + escaped_path_id = cescape(path_id); + if (!escaped_path_id) + return log_oom(); + + state_file = strjoin("/var/lib/systemd/rfkill/", escaped_path_id, ":", type, NULL); + } else + state_file = strjoin("/var/lib/systemd/rfkill/", type, NULL); + + if (!state_file) + return log_oom(); + + *ret = state_file; + return 0; +} + +static int load_state( + int rfkill_fd, + struct udev *udev, + const struct rfkill_event *event) { + + _cleanup_udev_device_unref_ struct udev_device *device = NULL; + _cleanup_free_ char *state_file = NULL, *value = NULL; + struct rfkill_event we; + ssize_t l; + int b, r; + + assert(rfkill_fd >= 0); + assert(udev); + assert(event); + + if (shall_restore_state() == 0) + return 0; + + r = find_device(udev, event, &device); + if (r < 0) + return r; + + r = determine_state_file(udev, event, device, &state_file); + if (r < 0) + return r; + + r = read_one_line_file(state_file, &value); + if (r == -ENOENT) { + /* No state file? Then save the current state */ + + r = write_string_file(state_file, one_zero(event->soft), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); + if (r < 0) + return log_error_errno(r, "Failed to write state file %s: %m", state_file); + + log_debug("Saved state '%s' to %s.", one_zero(event->soft), state_file); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to read state file %s: %m", state_file); + + b = parse_boolean(value); + if (b < 0) + return log_error_errno(b, "Failed to parse state file %s: %m", state_file); + + we = (struct rfkill_event) { + .op = RFKILL_OP_CHANGE, + .idx = event->idx, + .soft = b, + }; + + l = write(rfkill_fd, &we, sizeof(we)); + if (l < 0) + return log_error_errno(errno, "Failed to restore rfkill state for %i: %m", event->idx); + if (l != sizeof(we)) { + log_error("Couldn't write rfkill event structure, too short."); + return -EIO; + } + + log_debug("Loaded state '%s' from %s.", one_zero(b), state_file); + return 0; +} + +static int save_state( + int rfkill_fd, + struct udev *udev, + const struct rfkill_event *event) { + + _cleanup_udev_device_unref_ struct udev_device *device = NULL; + _cleanup_free_ char *state_file = NULL; + int r; + + assert(rfkill_fd >= 0); + assert(udev); + assert(event); + + r = find_device(udev, event, &device); + if (r < 0) + return r; + + r = determine_state_file(udev, event, device, &state_file); + if (r < 0) + return r; + + r = write_string_file(state_file, one_zero(event->soft), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); + if (r < 0) + return log_error_errno(r, "Failed to write state file %s: %m", state_file); + + log_debug("Saved state '%s' to %s.", one_zero(event->soft), state_file); + return 0; +} + +int main(int argc, char *argv[]) { + _cleanup_udev_unref_ struct udev *udev = NULL; + _cleanup_close_ int rfkill_fd = -1; + bool ready = false; + int r, n; + + if (argc > 1) { + log_error("This program requires no arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + udev = udev_new(); + if (!udev) { + r = log_oom(); + goto finish; + } + + r = mkdir_p("/var/lib/systemd/rfkill", 0755); + if (r < 0) { + log_error_errno(r, "Failed to create rfkill directory: %m"); + goto finish; + } + + n = sd_listen_fds(false); + if (n < 0) { + r = log_error_errno(n, "Failed to determine whether we got any file descriptors passed: %m"); + goto finish; + } + if (n > 1) { + log_error("Got too many file descriptors."); + r = -EINVAL; + goto finish; + } + + if (n == 0) { + rfkill_fd = open("/dev/rfkill", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + if (rfkill_fd < 0) { + if (errno == ENOENT) { + log_debug_errno(errno, "Missing rfkill subsystem, or no device present, exiting."); + r = 0; + goto finish; + } + + r = log_error_errno(errno, "Failed to open /dev/rfkill: %m"); + goto finish; + } + } else { + rfkill_fd = SD_LISTEN_FDS_START; + + r = fd_nonblock(rfkill_fd, 1); + if (r < 0) { + log_error_errno(r, "Failed to make /dev/rfkill socket non-blocking: %m"); + goto finish; + } + } + + for (;;) { + struct rfkill_event event; + const char *type; + ssize_t l; + + l = read(rfkill_fd, &event, sizeof(event)); + if (l < 0) { + if (errno == EAGAIN) { + + if (!ready) { + /* Notify manager that we are + * now finished with + * processing whatever was + * queued */ + (void) sd_notify(false, "READY=1"); + ready = true; + } + + /* Hang around for a bit, maybe there's more coming */ + + r = fd_wait_for_event(rfkill_fd, POLLIN, EXIT_USEC); + if (r == -EINTR) + continue; + if (r < 0) { + log_error_errno(r, "Failed to poll() on device: %m"); + goto finish; + } + if (r > 0) + continue; + + log_debug("All events read and idle, exiting."); + break; + } + + log_error_errno(errno, "Failed to read from /dev/rfkill: %m"); + } + + if (l != RFKILL_EVENT_SIZE_V1) { + log_error("Read event structure of invalid size."); + r = -EIO; + goto finish; + } + + type = rfkill_type_to_string(event.type); + if (!type) { + log_debug("An rfkill device of unknown type %i discovered, ignoring.", event.type); + continue; + } + + switch (event.op) { + + case RFKILL_OP_ADD: + log_debug("A new rfkill device has been added with index %i and type %s.", event.idx, type); + (void) load_state(rfkill_fd, udev, &event); + break; + + case RFKILL_OP_DEL: + log_debug("An rfkill device has been removed with index %i and type %s", event.idx, type); + break; + + case RFKILL_OP_CHANGE: + log_debug("An rfkill device has changed state with index %i and type %s", event.idx, type); + (void) save_state(rfkill_fd, udev, &event); + break; + + default: + log_debug("Unknown event %i from /dev/rfkill for index %i and type %s, ignoring.", event.op, event.idx, type); + break; + } + } + + r = 0; + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-run/Makefile b/src/systemd-run/Makefile new file mode 100644 index 0000000000..afa2d0f34c --- /dev/null +++ b/src/systemd-run/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_run_SOURCES = \ + src/run/run.c + +systemd_run_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-run/run.c b/src/systemd-run/run.c new file mode 100644 index 0000000000..29b5131f70 --- /dev/null +++ b/src/systemd-run/run.c @@ -0,0 +1,1261 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 + +#include +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-unit-util.h" +#include "bus-util.h" +#include "calendarspec.h" +#include "env-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "ptyfwd.h" +#include "signal-util.h" +#include "spawn-polkit-agent.h" +#include "strv.h" +#include "terminal-util.h" +#include "unit-name.h" +#include "user-util.h" + +static bool arg_ask_password = true; +static bool arg_scope = false; +static bool arg_remain_after_exit = false; +static bool arg_no_block = false; +static const char *arg_unit = NULL; +static const char *arg_description = NULL; +static const char *arg_slice = NULL; +static bool arg_send_sighup = false; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static const char *arg_host = NULL; +static bool arg_user = false; +static const char *arg_service_type = NULL; +static const char *arg_exec_user = NULL; +static const char *arg_exec_group = NULL; +static int arg_nice = 0; +static bool arg_nice_set = false; +static char **arg_environment = NULL; +static char **arg_property = NULL; +static bool arg_pty = false; +static usec_t arg_on_active = 0; +static usec_t arg_on_boot = 0; +static usec_t arg_on_startup = 0; +static usec_t arg_on_unit_active = 0; +static usec_t arg_on_unit_inactive = 0; +static const char *arg_on_calendar = NULL; +static char **arg_timer_property = NULL; +static bool arg_quiet = false; + +static void polkit_agent_open_if_enabled(void) { + + /* Open the polkit agent as a child process if necessary */ + if (!arg_ask_password) + return; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return; + + polkit_agent_open(); +} + +static void help(void) { + printf("%s [OPTIONS...] {COMMAND} [ARGS...]\n\n" + "Run the specified command in a transient scope or service or timer\n" + "unit. If a timer option is specified and the unit specified with\n" + "the --unit option exists, the command can be omitted.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-ask-password Do not prompt for password\n" + " --user Run as user unit\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --scope Run this as scope rather than service\n" + " --unit=UNIT Run under the specified unit name\n" + " -p --property=NAME=VALUE Set unit property\n" + " --description=TEXT Description for unit\n" + " --slice=SLICE Run in the specified slice\n" + " --no-block Do not wait until operation finished\n" + " -r --remain-after-exit Leave service around until explicitly stopped\n" + " --send-sighup Send SIGHUP when terminating\n" + " --service-type=TYPE Service type\n" + " --uid=USER Run as system user\n" + " --gid=GROUP Run as system group\n" + " --nice=NICE Nice level\n" + " -E --setenv=NAME=VALUE Set environment\n" + " -t --pty Run service on pseudo tty\n" + " -q --quiet Suppress information messages during runtime\n\n" + "Timer options:\n\n" + " --on-active=SECONDS Run after SECONDS delay\n" + " --on-boot=SECONDS Run SECONDS after machine was booted up\n" + " --on-startup=SECONDS Run SECONDS after systemd activation\n" + " --on-unit-active=SECONDS Run SECONDS after the last activation\n" + " --on-unit-inactive=SECONDS Run SECONDS after the last deactivation\n" + " --on-calendar=SPEC Realtime timer\n" + " --timer-property=NAME=VALUE Set timer unit property\n", + program_invocation_short_name); +} + +static bool with_timer(void) { + return arg_on_active || arg_on_boot || arg_on_startup || arg_on_unit_active || arg_on_unit_inactive || arg_on_calendar; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_USER, + ARG_SYSTEM, + ARG_SCOPE, + ARG_UNIT, + ARG_DESCRIPTION, + ARG_SLICE, + ARG_SEND_SIGHUP, + ARG_SERVICE_TYPE, + ARG_EXEC_USER, + ARG_EXEC_GROUP, + ARG_NICE, + ARG_ON_ACTIVE, + ARG_ON_BOOT, + ARG_ON_STARTUP, + ARG_ON_UNIT_ACTIVE, + ARG_ON_UNIT_INACTIVE, + ARG_ON_CALENDAR, + ARG_TIMER_PROPERTY, + ARG_NO_BLOCK, + ARG_NO_ASK_PASSWORD, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "user", no_argument, NULL, ARG_USER }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "scope", no_argument, NULL, ARG_SCOPE }, + { "unit", required_argument, NULL, ARG_UNIT }, + { "description", required_argument, NULL, ARG_DESCRIPTION }, + { "slice", required_argument, NULL, ARG_SLICE }, + { "remain-after-exit", no_argument, NULL, 'r' }, + { "send-sighup", no_argument, NULL, ARG_SEND_SIGHUP }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "service-type", required_argument, NULL, ARG_SERVICE_TYPE }, + { "uid", required_argument, NULL, ARG_EXEC_USER }, + { "gid", required_argument, NULL, ARG_EXEC_GROUP }, + { "nice", required_argument, NULL, ARG_NICE }, + { "setenv", required_argument, NULL, 'E' }, + { "property", required_argument, NULL, 'p' }, + { "tty", no_argument, NULL, 't' }, /* deprecated */ + { "pty", no_argument, NULL, 't' }, + { "quiet", no_argument, NULL, 'q' }, + { "on-active", required_argument, NULL, ARG_ON_ACTIVE }, + { "on-boot", required_argument, NULL, ARG_ON_BOOT }, + { "on-startup", required_argument, NULL, ARG_ON_STARTUP }, + { "on-unit-active", required_argument, NULL, ARG_ON_UNIT_ACTIVE }, + { "on-unit-inactive", required_argument, NULL, ARG_ON_UNIT_INACTIVE }, + { "on-calendar", required_argument, NULL, ARG_ON_CALENDAR }, + { "timer-property", required_argument, NULL, ARG_TIMER_PROPERTY }, + { "no-block", no_argument, NULL, ARG_NO_BLOCK }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + {}, + }; + + int r, c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "+hrH:M:p:tq", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + case ARG_VERSION: + return version(); + + case ARG_USER: + arg_user = true; + break; + + case ARG_SYSTEM: + arg_user = false; + break; + + case ARG_SCOPE: + arg_scope = true; + break; + + case ARG_UNIT: + arg_unit = optarg; + break; + + case ARG_DESCRIPTION: + arg_description = optarg; + break; + + case ARG_SLICE: + arg_slice = optarg; + break; + + case ARG_SEND_SIGHUP: + arg_send_sighup = true; + break; + + case 'r': + arg_remain_after_exit = true; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_SERVICE_TYPE: + arg_service_type = optarg; + break; + + case ARG_EXEC_USER: + arg_exec_user = optarg; + break; + + case ARG_EXEC_GROUP: + arg_exec_group = optarg; + break; + + case ARG_NICE: + r = safe_atoi(optarg, &arg_nice); + if (r < 0 || arg_nice < PRIO_MIN || arg_nice >= PRIO_MAX) { + log_error("Failed to parse nice value"); + return -EINVAL; + } + + arg_nice_set = true; + break; + + case 'E': + if (strv_extend(&arg_environment, optarg) < 0) + return log_oom(); + + break; + + case 'p': + if (strv_extend(&arg_property, optarg) < 0) + return log_oom(); + + break; + + case 't': + arg_pty = true; + break; + + case 'q': + arg_quiet = true; + break; + + case ARG_ON_ACTIVE: + + r = parse_sec(optarg, &arg_on_active); + if (r < 0) { + log_error("Failed to parse timer value: %s", optarg); + return r; + } + + break; + + case ARG_ON_BOOT: + + r = parse_sec(optarg, &arg_on_boot); + if (r < 0) { + log_error("Failed to parse timer value: %s", optarg); + return r; + } + + break; + + case ARG_ON_STARTUP: + + r = parse_sec(optarg, &arg_on_startup); + if (r < 0) { + log_error("Failed to parse timer value: %s", optarg); + return r; + } + + break; + + case ARG_ON_UNIT_ACTIVE: + + r = parse_sec(optarg, &arg_on_unit_active); + if (r < 0) { + log_error("Failed to parse timer value: %s", optarg); + return r; + } + + break; + + case ARG_ON_UNIT_INACTIVE: + + r = parse_sec(optarg, &arg_on_unit_inactive); + if (r < 0) { + log_error("Failed to parse timer value: %s", optarg); + return r; + } + + break; + + case ARG_ON_CALENDAR: { + CalendarSpec *spec = NULL; + + r = calendar_spec_from_string(optarg, &spec); + if (r < 0) { + log_error("Invalid calendar spec: %s", optarg); + return r; + } + + calendar_spec_free(spec); + arg_on_calendar = optarg; + break; + } + + case ARG_TIMER_PROPERTY: + + if (strv_extend(&arg_timer_property, optarg) < 0) + return log_oom(); + + break; + + case ARG_NO_BLOCK: + arg_no_block = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if ((optind >= argc) && (!arg_unit || !with_timer())) { + log_error("Command line to execute required."); + return -EINVAL; + } + + if (arg_user && arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Execution in user context is not supported on non-local systems."); + return -EINVAL; + } + + if (arg_scope && arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Scope execution is not supported on non-local systems."); + return -EINVAL; + } + + if (arg_scope && (arg_remain_after_exit || arg_service_type)) { + log_error("--remain-after-exit and --service-type= are not supported in --scope mode."); + return -EINVAL; + } + + if (arg_pty && (with_timer() || arg_scope)) { + log_error("--pty is not compatible in timer or --scope mode."); + return -EINVAL; + } + + if (arg_pty && arg_transport == BUS_TRANSPORT_REMOTE) { + log_error("--pty is only supported when connecting to the local system or containers."); + return -EINVAL; + } + + if (arg_scope && with_timer()) { + log_error("Timer options are not supported in --scope mode."); + return -EINVAL; + } + + if (arg_timer_property && !with_timer()) { + log_error("--timer-property= has no effect without any other timer options."); + return -EINVAL; + } + + return 1; +} + +static int transient_unit_set_properties(sd_bus_message *m, char **properties) { + char **i; + int r; + + r = sd_bus_message_append(m, "(sv)", "Description", "s", arg_description); + if (r < 0) + return r; + + STRV_FOREACH(i, properties) { + r = bus_append_unit_property_assignment(m, *i); + if (r < 0) + return r; + } + + return 0; +} + +static int transient_cgroup_set_properties(sd_bus_message *m) { + int r; + assert(m); + + if (!isempty(arg_slice)) { + _cleanup_free_ char *slice; + + r = unit_name_mangle_with_suffix(arg_slice, UNIT_NAME_NOGLOB, ".slice", &slice); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); + if (r < 0) + return r; + } + + return 0; +} + +static int transient_kill_set_properties(sd_bus_message *m) { + assert(m); + + if (arg_send_sighup) + return sd_bus_message_append(m, "(sv)", "SendSIGHUP", "b", arg_send_sighup); + else + return 0; +} + +static int transient_service_set_properties(sd_bus_message *m, char **argv, const char *pty_path) { + int r; + + assert(m); + + r = transient_unit_set_properties(m, arg_property); + if (r < 0) + return r; + + r = transient_kill_set_properties(m); + if (r < 0) + return r; + + r = transient_cgroup_set_properties(m); + if (r < 0) + return r; + + if (arg_remain_after_exit) { + r = sd_bus_message_append(m, "(sv)", "RemainAfterExit", "b", arg_remain_after_exit); + if (r < 0) + return r; + } + + if (arg_service_type) { + r = sd_bus_message_append(m, "(sv)", "Type", "s", arg_service_type); + if (r < 0) + return r; + } + + if (arg_exec_user) { + r = sd_bus_message_append(m, "(sv)", "User", "s", arg_exec_user); + if (r < 0) + return r; + } + + if (arg_exec_group) { + r = sd_bus_message_append(m, "(sv)", "Group", "s", arg_exec_group); + if (r < 0) + return r; + } + + if (arg_nice_set) { + r = sd_bus_message_append(m, "(sv)", "Nice", "i", arg_nice); + if (r < 0) + return r; + } + + if (pty_path) { + const char *e; + + r = sd_bus_message_append(m, + "(sv)(sv)(sv)(sv)", + "StandardInput", "s", "tty", + "StandardOutput", "s", "tty", + "StandardError", "s", "tty", + "TTYPath", "s", pty_path); + if (r < 0) + return r; + + e = getenv("TERM"); + if (e) { + char *n; + + n = strjoina("TERM=", e); + r = sd_bus_message_append(m, + "(sv)", + "Environment", "as", 1, n); + if (r < 0) + return r; + } + } + + if (!strv_isempty(arg_environment)) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", "Environment"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'v', "as"); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(m, arg_environment); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + } + + /* Exec container */ + { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", "ExecStart"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'v', "a(sasb)"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "(sasb)"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'r', "sasb"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", argv[0]); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(m, argv); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "b", false); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + } + + return 0; +} + +static int transient_scope_set_properties(sd_bus_message *m) { + int r; + + assert(m); + + r = transient_unit_set_properties(m, arg_property); + if (r < 0) + return r; + + r = transient_kill_set_properties(m); + if (r < 0) + return r; + + r = transient_cgroup_set_properties(m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, (uint32_t) getpid()); + if (r < 0) + return r; + + return 0; +} + +static int transient_timer_set_properties(sd_bus_message *m) { + int r; + + assert(m); + + r = transient_unit_set_properties(m, arg_timer_property); + if (r < 0) + return r; + + /* Automatically clean up our transient timers */ + r = sd_bus_message_append(m, "(sv)", "RemainAfterElapse", "b", false); + if (r < 0) + return r; + + if (arg_on_active) { + r = sd_bus_message_append(m, "(sv)", "OnActiveSec", "t", arg_on_active); + if (r < 0) + return r; + } + + if (arg_on_boot) { + r = sd_bus_message_append(m, "(sv)", "OnBootSec", "t", arg_on_boot); + if (r < 0) + return r; + } + + if (arg_on_startup) { + r = sd_bus_message_append(m, "(sv)", "OnStartupSec", "t", arg_on_startup); + if (r < 0) + return r; + } + + if (arg_on_unit_active) { + r = sd_bus_message_append(m, "(sv)", "OnUnitActiveSec", "t", arg_on_unit_active); + if (r < 0) + return r; + } + + if (arg_on_unit_inactive) { + r = sd_bus_message_append(m, "(sv)", "OnUnitInactiveSec", "t", arg_on_unit_inactive); + if (r < 0) + return r; + } + + if (arg_on_calendar) { + r = sd_bus_message_append(m, "(sv)", "OnCalendar", "s", arg_on_calendar); + if (r < 0) + return r; + } + + return 0; +} + +static int make_unit_name(sd_bus *bus, UnitType t, char **ret) { + const char *unique, *id; + char *p; + int r; + + assert(bus); + assert(t >= 0); + assert(t < _UNIT_TYPE_MAX); + + r = sd_bus_get_unique_name(bus, &unique); + if (r < 0) { + sd_id128_t rnd; + + /* We couldn't get the unique name, which is a pretty + * common case if we are connected to systemd + * directly. In that case, just pick a random uuid as + * name */ + + r = sd_id128_randomize(&rnd); + if (r < 0) + return log_error_errno(r, "Failed to generate random run unit name: %m"); + + if (asprintf(ret, "run-r" SD_ID128_FORMAT_STR ".%s", SD_ID128_FORMAT_VAL(rnd), unit_type_to_string(t)) < 0) + return log_oom(); + + return 0; + } + + /* We managed to get the unique name, then let's use that to + * name our transient units. */ + + id = startswith(unique, ":1."); + if (!id) { + log_error("Unique name %s has unexpected format.", unique); + return -EINVAL; + } + + p = strjoin("run-u", id, ".", unit_type_to_string(t), NULL); + if (!p) + return log_oom(); + + *ret = p; + return 0; +} + +static int start_transient_service( + sd_bus *bus, + char **argv) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_free_ char *service = NULL, *pty_path = NULL; + _cleanup_close_ int master = -1; + int r; + + assert(bus); + assert(argv); + + if (arg_pty) { + + if (arg_transport == BUS_TRANSPORT_LOCAL) { + master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY); + if (master < 0) + return log_error_errno(errno, "Failed to acquire pseudo tty: %m"); + + r = ptsname_malloc(master, &pty_path); + if (r < 0) + return log_error_errno(r, "Failed to determine tty name: %m"); + + if (unlockpt(master) < 0) + return log_error_errno(errno, "Failed to unlock tty: %m"); + + } else if (arg_transport == BUS_TRANSPORT_MACHINE) { + _cleanup_(sd_bus_unrefp) sd_bus *system_bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *pty_reply = NULL; + const char *s; + + r = sd_bus_default_system(&system_bus); + if (r < 0) + return log_error_errno(r, "Failed to connect to system bus: %m"); + + r = sd_bus_call_method(system_bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "OpenMachinePTY", + &error, + &pty_reply, + "s", arg_host); + if (r < 0) { + log_error("Failed to get machine PTY: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(pty_reply, "hs", &master, &s); + if (r < 0) + return bus_log_parse_error(r); + + master = fcntl(master, F_DUPFD_CLOEXEC, 3); + if (master < 0) + return log_error_errno(errno, "Failed to duplicate master fd: %m"); + + pty_path = strdup(s); + if (!pty_path) + return log_oom(); + } else + assert_not_reached("Can't allocate tty via ssh"); + } + + if (!arg_no_block) { + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch jobs: %m"); + } + + if (arg_unit) { + r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".service", &service); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + } else { + r = make_unit_name(bus, UNIT_SERVICE, &service); + if (r < 0) + return r; + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); + if (r < 0) + return bus_log_create_error(r); + + /* Name and mode */ + r = sd_bus_message_append(m, "ss", service, "fail"); + if (r < 0) + return bus_log_create_error(r); + + /* Properties */ + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = transient_service_set_properties(m, argv, pty_path); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + /* Auxiliary units */ + r = sd_bus_message_append(m, "a(sa(sv))", 0); + if (r < 0) + return bus_log_create_error(r); + + polkit_agent_open_if_enabled(); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to start transient service unit: %s", bus_error_message(&error, r)); + + if (w) { + const char *object; + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, object, arg_quiet); + if (r < 0) + return r; + } + + if (master >= 0) { + _cleanup_(pty_forward_freep) PTYForward *forward = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + char last_char = 0; + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); + + (void) sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); + (void) sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); + + if (!arg_quiet) + log_info("Running as unit: %s\nPress ^] three times within 1s to disconnect TTY.", service); + + r = pty_forward_new(event, master, PTY_FORWARD_IGNORE_INITIAL_VHANGUP, &forward); + if (r < 0) + return log_error_errno(r, "Failed to create PTY forwarder: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + pty_forward_get_last_char(forward, &last_char); + + forward = pty_forward_free(forward); + + if (!arg_quiet && last_char != '\n') + fputc('\n', stdout); + + } else if (!arg_quiet) + log_info("Running as unit: %s", service); + + return 0; +} + +static int start_transient_scope( + sd_bus *bus, + char **argv) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_strv_free_ char **env = NULL, **user_env = NULL; + _cleanup_free_ char *scope = NULL; + const char *object = NULL; + int r; + + assert(bus); + assert(argv); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_oom(); + + if (arg_unit) { + r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".scope", &scope); + if (r < 0) + return log_error_errno(r, "Failed to mangle scope name: %m"); + } else { + r = make_unit_name(bus, UNIT_SCOPE, &scope); + if (r < 0) + return r; + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); + if (r < 0) + return bus_log_create_error(r); + + /* Name and Mode */ + r = sd_bus_message_append(m, "ss", scope, "fail"); + if (r < 0) + return bus_log_create_error(r); + + /* Properties */ + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = transient_scope_set_properties(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + /* Auxiliary units */ + r = sd_bus_message_append(m, "a(sa(sv))", 0); + if (r < 0) + return bus_log_create_error(r); + + polkit_agent_open_if_enabled(); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to start transient scope unit: %s", bus_error_message(&error, -r)); + return r; + } + + if (arg_nice_set) { + if (setpriority(PRIO_PROCESS, 0, arg_nice) < 0) + return log_error_errno(errno, "Failed to set nice level: %m"); + } + + if (arg_exec_group) { + gid_t gid; + + r = get_group_creds(&arg_exec_group, &gid); + if (r < 0) + return log_error_errno(r, "Failed to resolve group %s: %m", arg_exec_group); + + if (setresgid(gid, gid, gid) < 0) + return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid); + } + + if (arg_exec_user) { + const char *home, *shell; + uid_t uid; + gid_t gid; + + r = get_user_creds(&arg_exec_user, &uid, &gid, &home, &shell); + if (r < 0) + return log_error_errno(r, "Failed to resolve user %s: %m", arg_exec_user); + + r = strv_extendf(&user_env, "HOME=%s", home); + if (r < 0) + return log_oom(); + + r = strv_extendf(&user_env, "SHELL=%s", shell); + if (r < 0) + return log_oom(); + + r = strv_extendf(&user_env, "USER=%s", arg_exec_user); + if (r < 0) + return log_oom(); + + r = strv_extendf(&user_env, "LOGNAME=%s", arg_exec_user); + if (r < 0) + return log_oom(); + + if (!arg_exec_group) { + if (setresgid(gid, gid, gid) < 0) + return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid); + } + + if (setresuid(uid, uid, uid) < 0) + return log_error_errno(errno, "Failed to change UID to " UID_FMT ": %m", uid); + } + + env = strv_env_merge(3, environ, user_env, arg_environment); + if (!env) + return log_oom(); + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, object, arg_quiet); + if (r < 0) + return r; + + if (!arg_quiet) + log_info("Running scope as unit: %s", scope); + + execvpe(argv[0], argv, env); + + return log_error_errno(errno, "Failed to execute: %m"); +} + +static int start_transient_timer( + sd_bus *bus, + char **argv) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_free_ char *timer = NULL, *service = NULL; + const char *object = NULL; + int r; + + assert(bus); + assert(argv); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_oom(); + + if (arg_unit) { + switch (unit_name_to_type(arg_unit)) { + + case UNIT_SERVICE: + service = strdup(arg_unit); + if (!service) + return log_oom(); + + r = unit_name_change_suffix(service, ".timer", &timer); + if (r < 0) + return log_error_errno(r, "Failed to change unit suffix: %m"); + break; + + case UNIT_TIMER: + timer = strdup(arg_unit); + if (!timer) + return log_oom(); + + r = unit_name_change_suffix(timer, ".service", &service); + if (r < 0) + return log_error_errno(r, "Failed to change unit suffix: %m"); + break; + + default: + r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".service", &service); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + + r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".timer", &timer); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + + break; + } + } else { + r = make_unit_name(bus, UNIT_SERVICE, &service); + if (r < 0) + return r; + + r = unit_name_change_suffix(service, ".timer", &timer); + if (r < 0) + return log_error_errno(r, "Failed to change unit suffix: %m"); + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); + if (r < 0) + return bus_log_create_error(r); + + /* Name and Mode */ + r = sd_bus_message_append(m, "ss", timer, "fail"); + if (r < 0) + return bus_log_create_error(r); + + /* Properties */ + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = transient_timer_set_properties(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(sa(sv))"); + if (r < 0) + return bus_log_create_error(r); + + if (argv[0]) { + r = sd_bus_message_open_container(m, 'r', "sa(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", service); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = transient_service_set_properties(m, argv, NULL); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + polkit_agent_open_if_enabled(); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to start transient timer unit: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, object, arg_quiet); + if (r < 0) + return r; + + log_info("Running timer as unit: %s", timer); + if (argv[0]) + log_info("Will run service as unit: %s", service); + + return 0; +} + +int main(int argc, char* argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ char *description = NULL, *command = NULL; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (argc > optind && arg_transport == BUS_TRANSPORT_LOCAL) { + /* Patch in an absolute path */ + + r = find_binary(argv[optind], &command); + if (r < 0) { + log_error_errno(r, "Failed to find executable %s: %m", argv[optind]); + goto finish; + } + + argv[optind] = command; + } + + if (!arg_description) { + description = strv_join(argv + optind, " "); + if (!description) { + r = log_oom(); + goto finish; + } + + if (arg_unit && isempty(description)) { + r = free_and_strdup(&description, arg_unit); + if (r < 0) + goto finish; + } + + arg_description = description; + } + + r = bus_connect_transport_systemd(arg_transport, arg_host, arg_user, &bus); + if (r < 0) { + log_error_errno(r, "Failed to create bus connection: %m"); + goto finish; + } + + if (arg_scope) + r = start_transient_scope(bus, argv + optind); + else if (with_timer()) + r = start_transient_timer(bus, argv + optind); + else + r = start_transient_service(bus, argv + optind); + +finish: + strv_free(arg_environment); + strv_free(arg_property); + strv_free(arg_timer_property); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-shutdown/Makefile b/src/systemd-shutdown/Makefile new file mode 100644 index 0000000000..0695f3927d --- /dev/null +++ b/src/systemd-shutdown/Makefile @@ -0,0 +1,38 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_shutdown_SOURCES = \ + src/core/umount.c \ + src/core/umount.h \ + src/core/shutdown.c \ + src/core/mount-setup.c \ + src/core/mount-setup.h \ + src/core/killall.h \ + src/core/killall.c + +systemd_shutdown_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-stdio-bridge/Makefile b/src/systemd-stdio-bridge/Makefile new file mode 100644 index 0000000000..dd6d433fc6 --- /dev/null +++ b/src/systemd-stdio-bridge/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_stdio_bridge_SOURCES = \ + src/stdio-bridge/stdio-bridge.c + +systemd_stdio_bridge_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-stdio-bridge/stdio-bridge.c b/src/systemd-stdio-bridge/stdio-bridge.c new file mode 100644 index 0000000000..dce959cae3 --- /dev/null +++ b/src/systemd-stdio-bridge/stdio-bridge.c @@ -0,0 +1,302 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include + +#include +#include + +#include "bus-internal.h" +#include "bus-util.h" +#include "build.h" +#include "log.h" +#include "util.h" + +#define DEFAULT_BUS_PATH "unix:path=/run/dbus/system_bus_socket" + +const char *arg_bus_path = DEFAULT_BUS_PATH; + +static int help(void) { + + printf("%s [OPTIONS...]\n\n" + "STDIO or socket-activatable proxy to a given DBus endpoint.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --bus-path=PATH Path to the kernel bus (default: %s)\n", + program_invocation_short_name, DEFAULT_BUS_PATH); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "bus-path", required_argument, NULL, 'p' }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hsup:", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case '?': + return -EINVAL; + + case 'p': + arg_bus_path = optarg; + break; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + return 1; +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_bus_unrefp) sd_bus *a = NULL, *b = NULL; + sd_id128_t server_id; + bool is_unix; + int r, in_fd, out_fd; + + log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = sd_listen_fds(0); + if (r == 0) { + in_fd = STDIN_FILENO; + out_fd = STDOUT_FILENO; + } else if (r == 1) { + in_fd = SD_LISTEN_FDS_START; + out_fd = SD_LISTEN_FDS_START; + } else { + log_error("Illegal number of file descriptors passed\n"); + goto finish; + } + + is_unix = + sd_is_socket(in_fd, AF_UNIX, 0, 0) > 0 && + sd_is_socket(out_fd, AF_UNIX, 0, 0) > 0; + + r = sd_bus_new(&a); + if (r < 0) { + log_error_errno(r, "Failed to allocate bus: %m"); + goto finish; + } + + r = sd_bus_set_address(a, arg_bus_path); + if (r < 0) { + log_error_errno(r, "Failed to set address to connect to: %m"); + goto finish; + } + + r = sd_bus_negotiate_fds(a, is_unix); + if (r < 0) { + log_error_errno(r, "Failed to set FD negotiation: %m"); + goto finish; + } + + r = sd_bus_start(a); + if (r < 0) { + log_error_errno(r, "Failed to start bus client: %m"); + goto finish; + } + + r = sd_bus_get_bus_id(a, &server_id); + if (r < 0) { + log_error_errno(r, "Failed to get server ID: %m"); + goto finish; + } + + r = sd_bus_new(&b); + if (r < 0) { + log_error_errno(r, "Failed to allocate bus: %m"); + goto finish; + } + + r = sd_bus_set_fd(b, in_fd, out_fd); + if (r < 0) { + log_error_errno(r, "Failed to set fds: %m"); + goto finish; + } + + r = sd_bus_set_server(b, 1, server_id); + if (r < 0) { + log_error_errno(r, "Failed to set server mode: %m"); + goto finish; + } + + r = sd_bus_negotiate_fds(b, is_unix); + if (r < 0) { + log_error_errno(r, "Failed to set FD negotiation: %m"); + goto finish; + } + + r = sd_bus_set_anonymous(b, true); + if (r < 0) { + log_error_errno(r, "Failed to set anonymous authentication: %m"); + goto finish; + } + + r = sd_bus_start(b); + if (r < 0) { + log_error_errno(r, "Failed to start bus client: %m"); + goto finish; + } + + for (;;) { + _cleanup_(sd_bus_message_unrefp)sd_bus_message *m = NULL; + int events_a, events_b, fd; + uint64_t timeout_a, timeout_b, t; + struct timespec _ts, *ts; + + r = sd_bus_process(a, &m); + if (r < 0) { + log_error_errno(r, "Failed to process bus a: %m"); + goto finish; + } + + if (m) { + r = sd_bus_send(b, m, NULL); + if (r < 0) { + log_error_errno(r, "Failed to send message: %m"); + goto finish; + } + } + + if (r > 0) + continue; + + r = sd_bus_process(b, &m); + if (r < 0) { + /* treat 'connection reset by peer' as clean exit condition */ + if (r == -ECONNRESET) + r = 0; + + goto finish; + } + + if (m) { + r = sd_bus_send(a, m, NULL); + if (r < 0) { + log_error_errno(r, "Failed to send message: %m"); + goto finish; + } + } + + if (r > 0) + continue; + + fd = sd_bus_get_fd(a); + if (fd < 0) { + r = fd; + log_error_errno(r, "Failed to get fd: %m"); + goto finish; + } + + events_a = sd_bus_get_events(a); + if (events_a < 0) { + r = events_a; + log_error_errno(r, "Failed to get events mask: %m"); + goto finish; + } + + r = sd_bus_get_timeout(a, &timeout_a); + if (r < 0) { + log_error_errno(r, "Failed to get timeout: %m"); + goto finish; + } + + events_b = sd_bus_get_events(b); + if (events_b < 0) { + r = events_b; + log_error_errno(r, "Failed to get events mask: %m"); + goto finish; + } + + r = sd_bus_get_timeout(b, &timeout_b); + if (r < 0) { + log_error_errno(r, "Failed to get timeout: %m"); + goto finish; + } + + t = timeout_a; + if (t == (uint64_t) -1 || (timeout_b != (uint64_t) -1 && timeout_b < timeout_a)) + t = timeout_b; + + if (t == (uint64_t) -1) + ts = NULL; + else { + usec_t nw; + + nw = now(CLOCK_MONOTONIC); + if (t > nw) + t -= nw; + else + t = 0; + + ts = timespec_store(&_ts, t); + } + + { + struct pollfd p[3] = { + {.fd = fd, .events = events_a, }, + {.fd = STDIN_FILENO, .events = events_b & POLLIN, }, + {.fd = STDOUT_FILENO, .events = events_b & POLLOUT, }}; + + r = ppoll(p, ELEMENTSOF(p), ts, NULL); + } + if (r < 0) { + log_error("ppoll() failed: %m"); + goto finish; + } + } + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-system-update-generator/Makefile b/src/systemd-system-update-generator/Makefile new file mode 100644 index 0000000000..62f750c182 --- /dev/null +++ b/src/systemd-system-update-generator/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_system_update_generator_SOURCES = \ + src/system-update-generator/system-update-generator.c + +systemd_system_update_generator_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-system-update-generator/system-update-generator.c b/src/systemd-system-update-generator/system-update-generator.c new file mode 100644 index 0000000000..a3d677f068 --- /dev/null +++ b/src/systemd-system-update-generator/system-update-generator.c @@ -0,0 +1,73 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 + +#include "fs-util.h" +#include "log.h" +#include "string-util.h" +#include "util.h" + +/* + * Implements the logic described in + * http://freedesktop.org/wiki/Software/systemd/SystemUpdates + */ + +static const char *arg_dest = "/tmp"; + +static int generate_symlink(void) { + const char *p = NULL; + + if (laccess("/system-update", F_OK) < 0) { + if (errno == ENOENT) + return 0; + + log_error_errno(errno, "Failed to check for system update: %m"); + return -EINVAL; + } + + p = strjoina(arg_dest, "/default.target"); + if (symlink(SYSTEM_DATA_UNIT_PATH "/system-update.target", p) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", p); + + return 0; +} + +int main(int argc, char *argv[]) { + int r; + + if (argc > 1 && argc != 4) { + log_error("This program takes three or no arguments."); + return EXIT_FAILURE; + } + + if (argc > 1) + arg_dest = argv[2]; + + log_set_target(LOG_TARGET_SAFE); + log_parse_environment(); + log_open(); + + umask(0022); + + r = generate_symlink(); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-sysv-generator/Makefile b/src/systemd-sysv-generator/Makefile new file mode 100644 index 0000000000..f6de146408 --- /dev/null +++ b/src/systemd-sysv-generator/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_sysv_generator_SOURCES = \ + src/sysv-generator/sysv-generator.c + +systemd_sysv_generator_LDADD = \ + libcore.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-sysv-generator/sysv-generator.c b/src/systemd-sysv-generator/sysv-generator.c new file mode 100644 index 0000000000..fe4bbeeb75 --- /dev/null +++ b/src/systemd-sysv-generator/sysv-generator.c @@ -0,0 +1,1039 @@ +/*** + This file is part of systemd. + + Copyright 2014 Thomas H.P. Andersen + Copyright 2010 Lennart Poettering + Copyright 2011 Michal Schmidt + + 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 +#include + +#include "alloc-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "hashmap.h" +#include "hexdecoct.h" +#include "install.h" +#include "log.h" +#include "mkdir.h" +#include "path-lookup.h" +#include "path-util.h" +#include "set.h" +#include "special.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "util.h" + +typedef enum RunlevelType { + RUNLEVEL_UP, + RUNLEVEL_DOWN +} RunlevelType; + +static const struct { + const char *path; + const char *target; + const RunlevelType type; +} rcnd_table[] = { + /* Standard SysV runlevels for start-up */ + { "rc1.d", SPECIAL_RESCUE_TARGET, RUNLEVEL_UP }, + { "rc2.d", SPECIAL_MULTI_USER_TARGET, RUNLEVEL_UP }, + { "rc3.d", SPECIAL_MULTI_USER_TARGET, RUNLEVEL_UP }, + { "rc4.d", SPECIAL_MULTI_USER_TARGET, RUNLEVEL_UP }, + { "rc5.d", SPECIAL_GRAPHICAL_TARGET, RUNLEVEL_UP }, + + /* Standard SysV runlevels for shutdown */ + { "rc0.d", SPECIAL_POWEROFF_TARGET, RUNLEVEL_DOWN }, + { "rc6.d", SPECIAL_REBOOT_TARGET, RUNLEVEL_DOWN } + + /* Note that the order here matters, as we read the + directories in this order, and we want to make sure that + sysv_start_priority is known when we first load the + unit. And that value we only know from S links. Hence + UP must be read before DOWN */ +}; + +static const char *arg_dest = "/tmp"; + +typedef struct SysvStub { + char *name; + char *path; + char *description; + int sysv_start_priority; + char *pid_file; + char **before; + char **after; + char **wants; + char **wanted_by; + char **conflicts; + bool has_lsb; + bool reload; + bool loaded; +} SysvStub; + +static void free_sysvstub(SysvStub *s) { + if (!s) + return; + + free(s->name); + free(s->path); + free(s->description); + free(s->pid_file); + strv_free(s->before); + strv_free(s->after); + strv_free(s->wants); + strv_free(s->wanted_by); + strv_free(s->conflicts); + free(s); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub*, free_sysvstub); + +static void free_sysvstub_hashmapp(Hashmap **h) { + SysvStub *stub; + + while ((stub = hashmap_steal_first(*h))) + free_sysvstub(stub); + + hashmap_free(*h); +} + +static int add_symlink(const char *service, const char *where) { + const char *from, *to; + int r; + + assert(service); + assert(where); + + from = strjoina(arg_dest, "/", service); + to = strjoina(arg_dest, "/", where, ".wants/", service); + + mkdir_parents_label(to, 0755); + + r = symlink(from, to); + if (r < 0) { + if (errno == EEXIST) + return 0; + + return -errno; + } + + return 1; +} + +static int add_alias(const char *service, const char *alias) { + const char *link; + int r; + + assert(service); + assert(alias); + + link = strjoina(arg_dest, "/", alias); + + r = symlink(service, link); + if (r < 0) { + if (errno == EEXIST) + return 0; + + return -errno; + } + + return 1; +} + +static int generate_unit_file(SysvStub *s) { + _cleanup_fclose_ FILE *f = NULL; + const char *unit; + char **p; + int r; + + assert(s); + + if (!s->loaded) + return 0; + + unit = strjoina(arg_dest, "/", s->name); + + /* We might already have a symlink with the same name from a Provides:, + * or from backup files like /etc/init.d/foo.bak. Real scripts always win, + * so remove an existing link */ + if (is_symlink(unit) > 0) { + log_warning("Overwriting existing symlink %s with real service.", unit); + (void) unlink(unit); + } + + f = fopen(unit, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to create unit file %s: %m", unit); + + fprintf(f, + "# Automatically generated by systemd-sysv-generator\n\n" + "[Unit]\n" + "Documentation=man:systemd-sysv-generator(8)\n" + "SourcePath=%s\n", + s->path); + + if (s->description) + fprintf(f, "Description=%s\n", s->description); + + STRV_FOREACH(p, s->before) + fprintf(f, "Before=%s\n", *p); + STRV_FOREACH(p, s->after) + fprintf(f, "After=%s\n", *p); + STRV_FOREACH(p, s->wants) + fprintf(f, "Wants=%s\n", *p); + STRV_FOREACH(p, s->conflicts) + fprintf(f, "Conflicts=%s\n", *p); + + fprintf(f, + "\n[Service]\n" + "Type=forking\n" + "Restart=no\n" + "TimeoutSec=5min\n" + "IgnoreSIGPIPE=no\n" + "KillMode=process\n" + "GuessMainPID=no\n" + "RemainAfterExit=%s\n", + yes_no(!s->pid_file)); + + if (s->pid_file) + fprintf(f, "PIDFile=%s\n", s->pid_file); + + fprintf(f, + "ExecStart=%s start\n" + "ExecStop=%s stop\n", + s->path, s->path); + + if (s->reload) + fprintf(f, "ExecReload=%s reload\n", s->path); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit %s: %m", unit); + + STRV_FOREACH(p, s->wanted_by) { + r = add_symlink(s->name, *p); + if (r < 0) + log_warning_errno(r, "Failed to create 'Wants' symlink to %s, ignoring: %m", *p); + } + + return 1; +} + +static bool usage_contains_reload(const char *line) { + return (strcasestr(line, "{reload|") || + strcasestr(line, "{reload}") || + strcasestr(line, "{reload\"") || + strcasestr(line, "|reload|") || + strcasestr(line, "|reload}") || + strcasestr(line, "|reload\"")); +} + +static char *sysv_translate_name(const char *name) { + _cleanup_free_ char *c = NULL; + char *res; + + c = strdup(name); + if (!c) + return NULL; + + res = endswith(c, ".sh"); + if (res) + *res = 0; + + if (unit_name_mangle(c, UNIT_NAME_NOGLOB, &res) < 0) + return NULL; + + return res; +} + +static int sysv_translate_facility(const char *name, const char *filename, char **ret) { + + /* We silently ignore the $ prefix here. According to the LSB + * spec it simply indicates whether something is a + * standardized name or a distribution-specific one. Since we + * just follow what already exists and do not introduce new + * uses or names we don't care who introduced a new name. */ + + static const char * const table[] = { + /* LSB defined facilities */ + "local_fs", NULL, + "network", SPECIAL_NETWORK_ONLINE_TARGET, + "named", SPECIAL_NSS_LOOKUP_TARGET, + "portmap", SPECIAL_RPCBIND_TARGET, + "remote_fs", SPECIAL_REMOTE_FS_TARGET, + "syslog", NULL, + "time", SPECIAL_TIME_SYNC_TARGET, + }; + + char *filename_no_sh, *e, *m; + const char *n; + unsigned i; + int r; + + assert(name); + assert(filename); + assert(ret); + + n = *name == '$' ? name + 1 : name; + + for (i = 0; i < ELEMENTSOF(table); i += 2) { + if (!streq(table[i], n)) + continue; + + if (!table[i+1]) + return 0; + + m = strdup(table[i+1]); + if (!m) + return log_oom(); + + *ret = m; + return 1; + } + + /* If we don't know this name, fallback heuristics to figure + * out whether something is a target or a service alias. */ + + /* Facilities starting with $ are most likely targets */ + if (*name == '$') { + r = unit_name_build(n, NULL, ".target", ret); + if (r < 0) + return log_error_errno(r, "Failed to build name: %m"); + + return r; + } + + /* Strip ".sh" suffix from file name for comparison */ + filename_no_sh = strdupa(filename); + e = endswith(filename_no_sh, ".sh"); + if (e) { + *e = '\0'; + filename = filename_no_sh; + } + + /* Names equaling the file name of the services are redundant */ + if (streq_ptr(n, filename)) + return 0; + + /* Everything else we assume to be normal service names */ + m = sysv_translate_name(n); + if (!m) + return log_oom(); + + *ret = m; + return 1; +} + +static int handle_provides(SysvStub *s, unsigned line, const char *full_text, const char *text) { + int r; + + assert(s); + assert(full_text); + assert(text); + + for (;;) { + _cleanup_free_ char *word = NULL, *m = NULL; + + r = extract_first_word(&text, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); + if (r < 0) + return log_error_errno(r, "Failed to parse word from provides string: %m"); + if (r == 0) + break; + + r = sysv_translate_facility(word, basename(s->path), &m); + if (r <= 0) /* continue on error */ + continue; + + switch (unit_name_to_type(m)) { + + case UNIT_SERVICE: + log_debug("Adding Provides: alias '%s' for '%s'", m, s->name); + r = add_alias(s->name, m); + if (r < 0) + log_warning_errno(r, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s->path, line, m); + break; + + case UNIT_TARGET: + + /* NB: SysV targets which are provided by a + * service are pulled in by the services, as + * an indication that the generic service is + * now available. This is strictly one-way. + * The targets do NOT pull in SysV services! */ + + r = strv_extend(&s->before, m); + if (r < 0) + return log_oom(); + + r = strv_extend(&s->wants, m); + if (r < 0) + return log_oom(); + + if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET)) { + r = strv_extend(&s->before, SPECIAL_NETWORK_TARGET); + if (r < 0) + return log_oom(); + } + + break; + + case _UNIT_TYPE_INVALID: + log_warning("Unit name '%s' is invalid", m); + break; + + default: + log_warning("Unknown unit type for unit '%s'", m); + } + } + + return 0; +} + +static int handle_dependencies(SysvStub *s, unsigned line, const char *full_text, const char *text) { + int r; + + assert(s); + assert(full_text); + assert(text); + + for (;;) { + _cleanup_free_ char *word = NULL, *m = NULL; + bool is_before; + + r = extract_first_word(&text, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); + if (r < 0) + return log_error_errno(r, "Failed to parse word from provides string: %m"); + if (r == 0) + break; + + r = sysv_translate_facility(word, basename(s->path), &m); + if (r <= 0) /* continue on error */ + continue; + + is_before = startswith_no_case(full_text, "X-Start-Before:"); + + if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) { + /* the network-online target is special, as it needs to be actively pulled in */ + r = strv_extend(&s->after, m); + if (r < 0) + return log_oom(); + + r = strv_extend(&s->wants, m); + } else + r = strv_extend(is_before ? &s->before : &s->after, m); + if (r < 0) + return log_oom(); + } + + return 0; +} + +static int load_sysv(SysvStub *s) { + _cleanup_fclose_ FILE *f; + unsigned line = 0; + int r; + enum { + NORMAL, + DESCRIPTION, + LSB, + LSB_DESCRIPTION, + USAGE_CONTINUATION + } state = NORMAL; + _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL; + char *description; + bool supports_reload = false; + char l[LINE_MAX]; + + assert(s); + + f = fopen(s->path, "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open %s: %m", s->path); + } + + log_debug("Loading SysV script %s", s->path); + + FOREACH_LINE(l, f, goto fail) { + char *t; + + line++; + + t = strstrip(l); + if (*t != '#') { + /* Try to figure out whether this init script supports + * the reload operation. This heuristic looks for + * "Usage" lines which include the reload option. */ + if ( state == USAGE_CONTINUATION || + (state == NORMAL && strcasestr(t, "usage"))) { + if (usage_contains_reload(t)) { + supports_reload = true; + state = NORMAL; + } else if (t[strlen(t)-1] == '\\') + state = USAGE_CONTINUATION; + else + state = NORMAL; + } + + continue; + } + + if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) { + state = LSB; + s->has_lsb = true; + continue; + } + + if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) { + state = NORMAL; + continue; + } + + t++; + t += strspn(t, WHITESPACE); + + if (state == NORMAL) { + + /* Try to parse Red Hat style description */ + + if (startswith_no_case(t, "description:")) { + + size_t k; + const char *j; + + k = strlen(t); + if (k > 0 && t[k-1] == '\\') { + state = DESCRIPTION; + t[k-1] = 0; + } + + j = strstrip(t+12); + if (isempty(j)) + j = NULL; + + r = free_and_strdup(&chkconfig_description, j); + if (r < 0) + return log_oom(); + + } else if (startswith_no_case(t, "pidfile:")) { + const char *fn; + + state = NORMAL; + + fn = strstrip(t+8); + if (!path_is_absolute(fn)) { + log_error("[%s:%u] PID file not absolute. Ignoring.", s->path, line); + continue; + } + + r = free_and_strdup(&s->pid_file, fn); + if (r < 0) + return log_oom(); + } + + } else if (state == DESCRIPTION) { + + /* Try to parse Red Hat style description + * continuation */ + + size_t k; + char *j; + + k = strlen(t); + if (k > 0 && t[k-1] == '\\') + t[k-1] = 0; + else + state = NORMAL; + + j = strstrip(t); + if (!isempty(j)) { + char *d = NULL; + + if (chkconfig_description) + d = strjoin(chkconfig_description, " ", j, NULL); + else + d = strdup(j); + if (!d) + return log_oom(); + + free(chkconfig_description); + chkconfig_description = d; + } + + } else if (state == LSB || state == LSB_DESCRIPTION) { + + if (startswith_no_case(t, "Provides:")) { + state = LSB; + + r = handle_provides(s, line, t, t + 9); + if (r < 0) + return r; + + } else if (startswith_no_case(t, "Required-Start:") || + startswith_no_case(t, "Should-Start:") || + startswith_no_case(t, "X-Start-Before:") || + startswith_no_case(t, "X-Start-After:")) { + + state = LSB; + + r = handle_dependencies(s, line, t, strchr(t, ':') + 1); + if (r < 0) + return r; + + } else if (startswith_no_case(t, "Description:")) { + const char *j; + + state = LSB_DESCRIPTION; + + j = strstrip(t+12); + if (isempty(j)) + j = NULL; + + r = free_and_strdup(&long_description, j); + if (r < 0) + return log_oom(); + + } else if (startswith_no_case(t, "Short-Description:")) { + const char *j; + + state = LSB; + + j = strstrip(t+18); + if (isempty(j)) + j = NULL; + + r = free_and_strdup(&short_description, j); + if (r < 0) + return log_oom(); + + } else if (state == LSB_DESCRIPTION) { + + if (startswith(l, "#\t") || startswith(l, "# ")) { + const char *j; + + j = strstrip(t); + if (!isempty(j)) { + char *d = NULL; + + if (long_description) + d = strjoin(long_description, " ", t, NULL); + else + d = strdup(j); + if (!d) + return log_oom(); + + free(long_description); + long_description = d; + } + + } else + state = LSB; + } + } + } + + s->reload = supports_reload; + + /* We use the long description only if + * no short description is set. */ + + if (short_description) + description = short_description; + else if (chkconfig_description) + description = chkconfig_description; + else if (long_description) + description = long_description; + else + description = NULL; + + if (description) { + char *d; + + d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description); + if (!d) + return log_oom(); + + s->description = d; + } + + s->loaded = true; + return 0; + +fail: + return log_error_errno(errno, "Failed to read configuration file '%s': %m", s->path); +} + +static int fix_order(SysvStub *s, Hashmap *all_services) { + SysvStub *other; + Iterator j; + int r; + + assert(s); + + if (!s->loaded) + return 0; + + if (s->sysv_start_priority < 0) + return 0; + + HASHMAP_FOREACH(other, all_services, j) { + if (s == other) + continue; + + if (!other->loaded) + continue; + + if (other->sysv_start_priority < 0) + continue; + + /* If both units have modern headers we don't care + * about the priorities */ + if (s->has_lsb && other->has_lsb) + continue; + + if (other->sysv_start_priority < s->sysv_start_priority) { + r = strv_extend(&s->after, other->name); + if (r < 0) + return log_oom(); + + } else if (other->sysv_start_priority > s->sysv_start_priority) { + r = strv_extend(&s->before, other->name); + if (r < 0) + return log_oom(); + } else + continue; + + /* FIXME: Maybe we should compare the name here lexicographically? */ + } + + return 0; +} + +static int acquire_search_path(const char *def, const char *envvar, char ***ret) { + _cleanup_strv_free_ char **l = NULL; + const char *e; + int r; + + assert(def); + assert(envvar); + + e = getenv(envvar); + if (e) { + r = path_split_and_make_absolute(e, &l); + if (r < 0) + return log_error_errno(r, "Failed to make $%s search path absolute: %m", envvar); + } + + if (strv_isempty(l)) { + strv_free(l); + + l = strv_new(def, NULL); + if (!l) + return log_oom(); + } + + if (!path_strv_resolve_uniq(l, NULL)) + return log_oom(); + + *ret = l; + l = NULL; + + return 0; +} + +static int enumerate_sysv(const LookupPaths *lp, Hashmap *all_services) { + _cleanup_strv_free_ char **sysvinit_path = NULL; + char **path; + int r; + + assert(lp); + + r = acquire_search_path(SYSTEM_SYSVINIT_PATH, "SYSTEMD_SYSVINIT_PATH", &sysvinit_path); + if (r < 0) + return r; + + STRV_FOREACH(path, sysvinit_path) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + + d = opendir(*path); + if (!d) { + if (errno != ENOENT) + log_warning_errno(errno, "Opening %s failed, ignoring: %m", *path); + continue; + } + + FOREACH_DIRENT(de, d, log_error_errno(errno, "Failed to enumerate directory %s, ignoring: %m", *path)) { + _cleanup_free_ char *fpath = NULL, *name = NULL; + _cleanup_(free_sysvstubp) SysvStub *service = NULL; + struct stat st; + + if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) { + log_warning_errno(errno, "stat() failed on %s/%s, ignoring: %m", *path, de->d_name); + continue; + } + + if (!(st.st_mode & S_IXUSR)) + continue; + + if (!S_ISREG(st.st_mode)) + continue; + + name = sysv_translate_name(de->d_name); + if (!name) + return log_oom(); + + if (hashmap_contains(all_services, name)) + continue; + + r = unit_file_exists(UNIT_FILE_SYSTEM, lp, name); + if (r < 0 && !IN_SET(r, -ELOOP, -ERFKILL, -EADDRNOTAVAIL)) { + log_debug_errno(r, "Failed to detect whether %s exists, skipping: %m", name); + continue; + } else if (r != 0) { + log_debug("Native unit for %s already exists, skipping.", name); + continue; + } + + fpath = strjoin(*path, "/", de->d_name, NULL); + if (!fpath) + return log_oom(); + + service = new0(SysvStub, 1); + if (!service) + return log_oom(); + + service->sysv_start_priority = -1; + service->name = name; + service->path = fpath; + name = fpath = NULL; + + r = hashmap_put(all_services, service->name, service); + if (r < 0) + return log_oom(); + + service = NULL; + } + } + + return 0; +} + +static int set_dependencies_from_rcnd(const LookupPaths *lp, Hashmap *all_services) { + Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {}; + _cleanup_set_free_ Set *shutdown_services = NULL; + _cleanup_strv_free_ char **sysvrcnd_path = NULL; + SysvStub *service; + unsigned i; + Iterator j; + char **p; + int r; + + assert(lp); + + r = acquire_search_path(SYSTEM_SYSVRCND_PATH, "SYSTEMD_SYSVRCND_PATH", &sysvrcnd_path); + if (r < 0) + return r; + + STRV_FOREACH(p, sysvrcnd_path) { + for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) { + + _cleanup_closedir_ DIR *d = NULL; + _cleanup_free_ char *path = NULL; + struct dirent *de; + + path = strjoin(*p, "/", rcnd_table[i].path, NULL); + if (!path) { + r = log_oom(); + goto finish; + } + + d = opendir(path); + if (!d) { + if (errno != ENOENT) + log_warning_errno(errno, "Opening %s failed, ignoring: %m", path); + + continue; + } + + FOREACH_DIRENT(de, d, log_error_errno(errno, "Failed to enumerate directory %s, ignoring: %m", path)) { + _cleanup_free_ char *name = NULL, *fpath = NULL; + int a, b; + + if (de->d_name[0] != 'S' && de->d_name[0] != 'K') + continue; + + if (strlen(de->d_name) < 4) + continue; + + a = undecchar(de->d_name[1]); + b = undecchar(de->d_name[2]); + + if (a < 0 || b < 0) + continue; + + fpath = strjoin(*p, "/", de->d_name, NULL); + if (!fpath) { + r = log_oom(); + goto finish; + } + + name = sysv_translate_name(de->d_name + 3); + if (!name) { + r = log_oom(); + goto finish; + } + + service = hashmap_get(all_services, name); + if (!service) { + log_debug("Ignoring %s symlink in %s, not generating %s.", de->d_name, rcnd_table[i].path, name); + continue; + } + + if (de->d_name[0] == 'S') { + + if (rcnd_table[i].type == RUNLEVEL_UP) + service->sysv_start_priority = MAX(a*10 + b, service->sysv_start_priority); + + r = set_ensure_allocated(&runlevel_services[i], NULL); + if (r < 0) { + log_oom(); + goto finish; + } + + r = set_put(runlevel_services[i], service); + if (r < 0) { + log_oom(); + goto finish; + } + + } else if (de->d_name[0] == 'K' && + (rcnd_table[i].type == RUNLEVEL_DOWN)) { + + r = set_ensure_allocated(&shutdown_services, NULL); + if (r < 0) { + log_oom(); + goto finish; + } + + r = set_put(shutdown_services, service); + if (r < 0) { + log_oom(); + goto finish; + } + } + } + } + } + + + for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) + SET_FOREACH(service, runlevel_services[i], j) { + r = strv_extend(&service->before, rcnd_table[i].target); + if (r < 0) { + log_oom(); + goto finish; + } + r = strv_extend(&service->wanted_by, rcnd_table[i].target); + if (r < 0) { + log_oom(); + goto finish; + } + } + + SET_FOREACH(service, shutdown_services, j) { + r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET); + if (r < 0) { + log_oom(); + goto finish; + } + r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET); + if (r < 0) { + log_oom(); + goto finish; + } + } + + r = 0; + +finish: + for (i = 0; i < ELEMENTSOF(rcnd_table); i++) + set_free(runlevel_services[i]); + + return r; +} + +int main(int argc, char *argv[]) { + _cleanup_(free_sysvstub_hashmapp) Hashmap *all_services = NULL; + _cleanup_lookup_paths_free_ LookupPaths lp = {}; + SysvStub *service; + Iterator j; + int r; + + if (argc > 1 && argc != 4) { + log_error("This program takes three or no arguments."); + return EXIT_FAILURE; + } + + if (argc > 1) + arg_dest = argv[3]; + + log_set_target(LOG_TARGET_SAFE); + log_parse_environment(); + log_open(); + + umask(0022); + + r = lookup_paths_init(&lp, UNIT_FILE_SYSTEM, LOOKUP_PATHS_EXCLUDE_GENERATED, NULL); + if (r < 0) { + log_error_errno(r, "Failed to find lookup paths: %m"); + goto finish; + } + + all_services = hashmap_new(&string_hash_ops); + if (!all_services) { + r = log_oom(); + goto finish; + } + + r = enumerate_sysv(&lp, all_services); + if (r < 0) + goto finish; + + r = set_dependencies_from_rcnd(&lp, all_services); + if (r < 0) + goto finish; + + HASHMAP_FOREACH(service, all_services, j) + (void) load_sysv(service); + + HASHMAP_FOREACH(service, all_services, j) { + (void) fix_order(service, all_services); + (void) generate_unit_file(service); + } + + r = 0; + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-timesync/.gitignore b/src/systemd-timesync/.gitignore new file mode 100644 index 0000000000..35f4d76f79 --- /dev/null +++ b/src/systemd-timesync/.gitignore @@ -0,0 +1,2 @@ +/timesyncd.conf +/timesyncd-gperf.c diff --git a/src/systemd-timesync/Makefile b/src/systemd-timesync/Makefile new file mode 100644 index 0000000000..a329865019 --- /dev/null +++ b/src/systemd-timesync/Makefile @@ -0,0 +1,64 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_TIMESYNCD),) +systemd_timesyncd_SOURCES = \ + src/timesync/timesyncd.c \ + src/timesync/timesyncd-manager.c \ + src/timesync/timesyncd-manager.h \ + src/timesync/timesyncd-conf.c \ + src/timesync/timesyncd-conf.h \ + src/timesync/timesyncd-server.c \ + src/timesync/timesyncd-server.h + +nodist_systemd_timesyncd_SOURCES = \ + src/timesync/timesyncd-gperf.c + +systemd_timesyncd_LDADD = \ + libsystemd-network.la \ + libshared.la + +libexec_PROGRAMS += \ + systemd-timesyncd + +nodist_systemunit_DATA += \ + units/systemd-timesyncd.service + +GENERAL_ALIASES += \ + $(systemunitdir)/systemd-timesyncd.service $(pkgsysconfdir)/system/sysinit.target.wants/systemd-timesyncd.service + +nodist_pkgsysconf_DATA += \ + src/timesync/timesyncd.conf + +endif # ENABLE_TIMESYNCD + +gperf_gperf_sources += \ + src/timesync/timesyncd-gperf.gperf + +EXTRA_DIST += \ + units/systemd-timesyncd.service.in \ + src/timesync/timesyncd.conf.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-timesync/timesyncd-conf.c b/src/systemd-timesync/timesyncd-conf.c new file mode 100644 index 0000000000..20c64a3354 --- /dev/null +++ b/src/systemd-timesync/timesyncd-conf.c @@ -0,0 +1,106 @@ +/*** + This file is part of systemd. + + Copyright 2014 Kay Sievers, 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 "alloc-util.h" +#include "def.h" +#include "extract-word.h" +#include "string-util.h" +#include "timesyncd-conf.h" +#include "timesyncd-manager.h" +#include "timesyncd-server.h" + +int manager_parse_server_string(Manager *m, ServerType type, const char *string) { + ServerName *first; + int r; + + assert(m); + assert(string); + + first = type == SERVER_FALLBACK ? m->fallback_servers : m->system_servers; + + for (;;) { + _cleanup_free_ char *word = NULL; + bool found = false; + ServerName *n; + + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return log_error_errno(r, "Failed to parse timesyncd server syntax \"%s\": %m", string); + if (r == 0) + break; + + /* Filter out duplicates */ + LIST_FOREACH(names, n, first) + if (streq_ptr(n->string, word)) { + found = true; + break; + } + + if (found) + continue; + + r = server_name_new(m, NULL, type, word); + if (r < 0) + return r; + } + + return 0; +} + +int config_parse_servers( + 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) { + + Manager *m = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) + manager_flush_server_names(m, ltype); + else { + r = manager_parse_server_string(m, ltype, rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse NTP server string '%s'. Ignoring.", rvalue); + return 0; + } + } + + return 0; +} + +int manager_parse_config_file(Manager *m) { + assert(m); + + return config_parse_many(PKGSYSCONFDIR "/timesyncd.conf", + CONF_PATHS_NULSTR("systemd/timesyncd.conf.d"), + "Time\0", + config_item_perf_lookup, timesyncd_gperf_lookup, + false, m); +} diff --git a/src/systemd-timesync/timesyncd-conf.h b/src/systemd-timesync/timesyncd-conf.h new file mode 100644 index 0000000000..cba0724b1b --- /dev/null +++ b/src/systemd-timesync/timesyncd-conf.h @@ -0,0 +1,31 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Kay Sievers, 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 "conf-parser.h" +#include "timesyncd-manager.h" + +const struct ConfigPerfItem* timesyncd_gperf_lookup(const char *key, unsigned length); + +int manager_parse_server_string(Manager *m, ServerType type, const char *string); + +int config_parse_servers(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); + +int manager_parse_config_file(Manager *m); diff --git a/src/systemd-timesync/timesyncd-gperf.gperf b/src/systemd-timesync/timesyncd-gperf.gperf new file mode 100644 index 0000000000..29a2cfeef6 --- /dev/null +++ b/src/systemd-timesync/timesyncd-gperf.gperf @@ -0,0 +1,19 @@ +%{ +#include +#include "conf-parser.h" +#include "timesyncd-conf.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name timesyncdd_gperf_hash +%define lookup-function-name timesyncd_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +Time.NTP, config_parse_servers, SERVER_SYSTEM, 0 +Time.Servers, config_parse_servers, SERVER_SYSTEM, 0 +Time.FallbackNTP, config_parse_servers, SERVER_FALLBACK, 0 diff --git a/src/systemd-timesync/timesyncd-manager.c b/src/systemd-timesync/timesyncd-manager.c new file mode 100644 index 0000000000..6a4b52af69 --- /dev/null +++ b/src/systemd-timesync/timesyncd-manager.c @@ -0,0 +1,1156 @@ +/*** + This file is part of systemd. + + Copyright 2014 Kay Sievers, 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "list.h" +#include "log.h" +#include "missing.h" +#include "network-util.h" +#include "ratelimit.h" +#include "socket-util.h" +#include "sparse-endian.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "timesyncd-conf.h" +#include "timesyncd-manager.h" +#include "util.h" + +#ifndef ADJ_SETOFFSET +#define ADJ_SETOFFSET 0x0100 /* add 'time' to current time */ +#endif + +/* expected accuracy of time synchronization; used to adjust the poll interval */ +#define NTP_ACCURACY_SEC 0.2 + +/* + * "A client MUST NOT under any conditions use a poll interval less + * than 15 seconds." + */ +#define NTP_POLL_INTERVAL_MIN_SEC 32 +#define NTP_POLL_INTERVAL_MAX_SEC 2048 + +/* + * Maximum delta in seconds which the system clock is gradually adjusted + * (slew) to approach the network time. Deltas larger that this are set by + * letting the system time jump. The kernel's limit for adjtime is 0.5s. + */ +#define NTP_MAX_ADJUST 0.4 + +/* NTP protocol, packet header */ +#define NTP_LEAP_PLUSSEC 1 +#define NTP_LEAP_MINUSSEC 2 +#define NTP_LEAP_NOTINSYNC 3 +#define NTP_MODE_CLIENT 3 +#define NTP_MODE_SERVER 4 +#define NTP_FIELD_LEAP(f) (((f) >> 6) & 3) +#define NTP_FIELD_VERSION(f) (((f) >> 3) & 7) +#define NTP_FIELD_MODE(f) ((f) & 7) +#define NTP_FIELD(l, v, m) (((l) << 6) | ((v) << 3) | (m)) + +/* Maximum acceptable root distance in seconds. */ +#define NTP_MAX_ROOT_DISTANCE 5.0 + +/* Maximum number of missed replies before selecting another source. */ +#define NTP_MAX_MISSED_REPLIES 2 + +/* + * "NTP timestamps are represented as a 64-bit unsigned fixed-point number, + * in seconds relative to 0h on 1 January 1900." + */ +#define OFFSET_1900_1970 UINT64_C(2208988800) + +#define RETRY_USEC (30*USEC_PER_SEC) +#define RATELIMIT_INTERVAL_USEC (10*USEC_PER_SEC) +#define RATELIMIT_BURST 10 + +#define TIMEOUT_USEC (10*USEC_PER_SEC) + +struct ntp_ts { + be32_t sec; + be32_t frac; +} _packed_; + +struct ntp_ts_short { + be16_t sec; + be16_t frac; +} _packed_; + +struct ntp_msg { + uint8_t field; + uint8_t stratum; + int8_t poll; + int8_t precision; + struct ntp_ts_short root_delay; + struct ntp_ts_short root_dispersion; + char refid[4]; + struct ntp_ts reference_time; + struct ntp_ts origin_time; + struct ntp_ts recv_time; + struct ntp_ts trans_time; +} _packed_; + +static int manager_arm_timer(Manager *m, usec_t next); +static int manager_clock_watch_setup(Manager *m); +static int manager_listen_setup(Manager *m); +static void manager_listen_stop(Manager *m); + +static double ntp_ts_short_to_d(const struct ntp_ts_short *ts) { + return be16toh(ts->sec) + (be16toh(ts->frac) / 65536.0); +} + +static double ntp_ts_to_d(const struct ntp_ts *ts) { + return be32toh(ts->sec) + ((double)be32toh(ts->frac) / UINT_MAX); +} + +static double ts_to_d(const struct timespec *ts) { + return ts->tv_sec + (1.0e-9 * ts->tv_nsec); +} + +static int manager_timeout(sd_event_source *source, usec_t usec, void *userdata) { + _cleanup_free_ char *pretty = NULL; + Manager *m = userdata; + + assert(m); + assert(m->current_server_name); + assert(m->current_server_address); + + server_address_pretty(m->current_server_address, &pretty); + log_info("Timed out waiting for reply from %s (%s).", strna(pretty), m->current_server_name->string); + + return manager_connect(m); +} + +static int manager_send_request(Manager *m) { + _cleanup_free_ char *pretty = NULL; + struct ntp_msg ntpmsg = { + /* + * "The client initializes the NTP message header, sends the request + * to the server, and strips the time of day from the Transmit + * Timestamp field of the reply. For this purpose, all the NTP + * header fields are set to 0, except the Mode, VN, and optional + * Transmit Timestamp fields." + */ + .field = NTP_FIELD(0, 4, NTP_MODE_CLIENT), + }; + ssize_t len; + int r; + + assert(m); + assert(m->current_server_name); + assert(m->current_server_address); + + m->event_timeout = sd_event_source_unref(m->event_timeout); + + r = manager_listen_setup(m); + if (r < 0) + return log_warning_errno(r, "Failed to setup connection socket: %m"); + + /* + * Set transmit timestamp, remember it; the server will send that back + * as the origin timestamp and we have an indication that this is the + * matching answer to our request. + * + * The actual value does not matter, We do not care about the correct + * NTP UINT_MAX fraction; we just pass the plain nanosecond value. + */ + assert_se(clock_gettime(clock_boottime_or_monotonic(), &m->trans_time_mon) >= 0); + assert_se(clock_gettime(CLOCK_REALTIME, &m->trans_time) >= 0); + ntpmsg.trans_time.sec = htobe32(m->trans_time.tv_sec + OFFSET_1900_1970); + ntpmsg.trans_time.frac = htobe32(m->trans_time.tv_nsec); + + server_address_pretty(m->current_server_address, &pretty); + + len = sendto(m->server_socket, &ntpmsg, sizeof(ntpmsg), MSG_DONTWAIT, &m->current_server_address->sockaddr.sa, m->current_server_address->socklen); + if (len == sizeof(ntpmsg)) { + m->pending = true; + log_debug("Sent NTP request to %s (%s).", strna(pretty), m->current_server_name->string); + } else { + log_debug_errno(errno, "Sending NTP request to %s (%s) failed: %m", strna(pretty), m->current_server_name->string); + return manager_connect(m); + } + + /* re-arm timer with increasing timeout, in case the packets never arrive back */ + if (m->retry_interval > 0) { + if (m->retry_interval < NTP_POLL_INTERVAL_MAX_SEC * USEC_PER_SEC) + m->retry_interval *= 2; + } else + m->retry_interval = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC; + + r = manager_arm_timer(m, m->retry_interval); + if (r < 0) + return log_error_errno(r, "Failed to rearm timer: %m"); + + m->missed_replies++; + if (m->missed_replies > NTP_MAX_MISSED_REPLIES) { + r = sd_event_add_time( + m->event, + &m->event_timeout, + clock_boottime_or_monotonic(), + now(clock_boottime_or_monotonic()) + TIMEOUT_USEC, 0, + manager_timeout, m); + if (r < 0) + return log_error_errno(r, "Failed to arm timeout timer: %m"); + } + + return 0; +} + +static int manager_timer(sd_event_source *source, usec_t usec, void *userdata) { + Manager *m = userdata; + + assert(m); + + return manager_send_request(m); +} + +static int manager_arm_timer(Manager *m, usec_t next) { + int r; + + assert(m); + + if (next == 0) { + m->event_timer = sd_event_source_unref(m->event_timer); + return 0; + } + + if (m->event_timer) { + r = sd_event_source_set_time(m->event_timer, now(clock_boottime_or_monotonic()) + next); + if (r < 0) + return r; + + return sd_event_source_set_enabled(m->event_timer, SD_EVENT_ONESHOT); + } + + return sd_event_add_time( + m->event, + &m->event_timer, + clock_boottime_or_monotonic(), + now(clock_boottime_or_monotonic()) + next, 0, + manager_timer, m); +} + +static int manager_clock_watch(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + + assert(m); + + /* rearm timer */ + manager_clock_watch_setup(m); + + /* skip our own jumps */ + if (m->jumped) { + m->jumped = false; + return 0; + } + + /* resync */ + log_debug("System time changed. Resyncing."); + m->poll_resync = true; + + return manager_send_request(m); +} + +/* wake up when the system time changes underneath us */ +static int manager_clock_watch_setup(Manager *m) { + + struct itimerspec its = { + .it_value.tv_sec = TIME_T_MAX + }; + + int r; + + assert(m); + + m->event_clock_watch = sd_event_source_unref(m->event_clock_watch); + safe_close(m->clock_watch_fd); + + m->clock_watch_fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC); + if (m->clock_watch_fd < 0) + return log_error_errno(errno, "Failed to create timerfd: %m"); + + if (timerfd_settime(m->clock_watch_fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0) + return log_error_errno(errno, "Failed to set up timerfd: %m"); + + r = sd_event_add_io(m->event, &m->event_clock_watch, m->clock_watch_fd, EPOLLIN, manager_clock_watch, m); + if (r < 0) + return log_error_errno(r, "Failed to create clock watch event source: %m"); + + return 0; +} + +static int manager_adjust_clock(Manager *m, double offset, int leap_sec) { + struct timex tmx = {}; + int r; + + assert(m); + + /* + * For small deltas, tell the kernel to gradually adjust the system + * clock to the NTP time, larger deltas are just directly set. + */ + if (fabs(offset) < NTP_MAX_ADJUST) { + tmx.modes = ADJ_STATUS | ADJ_NANO | ADJ_OFFSET | ADJ_TIMECONST | ADJ_MAXERROR | ADJ_ESTERROR; + tmx.status = STA_PLL; + tmx.offset = offset * NSEC_PER_SEC; + tmx.constant = log2i(m->poll_interval_usec / USEC_PER_SEC) - 4; + tmx.maxerror = 0; + tmx.esterror = 0; + log_debug(" adjust (slew): %+.3f sec", offset); + } else { + tmx.modes = ADJ_STATUS | ADJ_NANO | ADJ_SETOFFSET; + + /* ADJ_NANO uses nanoseconds in the microseconds field */ + tmx.time.tv_sec = (long)offset; + tmx.time.tv_usec = (offset - tmx.time.tv_sec) * NSEC_PER_SEC; + + /* the kernel expects -0.3s as {-1, 7000.000.000} */ + if (tmx.time.tv_usec < 0) { + tmx.time.tv_sec -= 1; + tmx.time.tv_usec += NSEC_PER_SEC; + } + + m->jumped = true; + log_debug(" adjust (jump): %+.3f sec", offset); + } + + /* + * An unset STA_UNSYNC will enable the kernel's 11-minute mode, + * which syncs the system time periodically to the RTC. + * + * In case the RTC runs in local time, never touch the RTC, + * we have no way to properly handle daylight saving changes and + * mobile devices moving between time zones. + */ + if (m->rtc_local_time) + tmx.status |= STA_UNSYNC; + + switch (leap_sec) { + case 1: + tmx.status |= STA_INS; + break; + case -1: + tmx.status |= STA_DEL; + break; + } + + r = clock_adjtime(CLOCK_REALTIME, &tmx); + if (r < 0) + return -errno; + + /* If touch fails, there isn't much we can do. Maybe it'll work next time. */ + (void) touch("/var/lib/systemd/clock"); + + m->drift_ppm = tmx.freq / 65536; + + log_debug(" status : %04i %s\n" + " time now : %li.%03llu\n" + " constant : %li\n" + " offset : %+.3f sec\n" + " freq offset : %+li (%i ppm)\n", + tmx.status, tmx.status & STA_UNSYNC ? "unsync" : "sync", + tmx.time.tv_sec, (unsigned long long) (tmx.time.tv_usec / NSEC_PER_MSEC), + tmx.constant, + (double)tmx.offset / NSEC_PER_SEC, + tmx.freq, m->drift_ppm); + + return 0; +} + +static bool manager_sample_spike_detection(Manager *m, double offset, double delay) { + unsigned int i, idx_cur, idx_new, idx_min; + double jitter; + double j; + + assert(m); + + m->packet_count++; + + /* ignore initial sample */ + if (m->packet_count == 1) + return false; + + /* store the current data in our samples array */ + idx_cur = m->samples_idx; + idx_new = (idx_cur + 1) % ELEMENTSOF(m->samples); + m->samples_idx = idx_new; + m->samples[idx_new].offset = offset; + m->samples[idx_new].delay = delay; + + /* calculate new jitter value from the RMS differences relative to the lowest delay sample */ + jitter = m->samples_jitter; + for (idx_min = idx_cur, i = 0; i < ELEMENTSOF(m->samples); i++) + if (m->samples[i].delay > 0 && m->samples[i].delay < m->samples[idx_min].delay) + idx_min = i; + + j = 0; + for (i = 0; i < ELEMENTSOF(m->samples); i++) + j += pow(m->samples[i].offset - m->samples[idx_min].offset, 2); + m->samples_jitter = sqrt(j / (ELEMENTSOF(m->samples) - 1)); + + /* ignore samples when resyncing */ + if (m->poll_resync) + return false; + + /* always accept offset if we are farther off than the round-trip delay */ + if (fabs(offset) > delay) + return false; + + /* we need a few samples before looking at them */ + if (m->packet_count < 4) + return false; + + /* do not accept anything worse than the maximum possible error of the best sample */ + if (fabs(offset) > m->samples[idx_min].delay) + return true; + + /* compare the difference between the current offset to the previous offset and jitter */ + return fabs(offset - m->samples[idx_cur].offset) > 3 * jitter; +} + +static void manager_adjust_poll(Manager *m, double offset, bool spike) { + assert(m); + + if (m->poll_resync) { + m->poll_interval_usec = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC; + m->poll_resync = false; + return; + } + + /* set to minimal poll interval */ + if (!spike && fabs(offset) > NTP_ACCURACY_SEC) { + m->poll_interval_usec = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC; + return; + } + + /* increase polling interval */ + if (fabs(offset) < NTP_ACCURACY_SEC * 0.25) { + if (m->poll_interval_usec < NTP_POLL_INTERVAL_MAX_SEC * USEC_PER_SEC) + m->poll_interval_usec *= 2; + return; + } + + /* decrease polling interval */ + if (spike || fabs(offset) > NTP_ACCURACY_SEC * 0.75) { + if (m->poll_interval_usec > NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC) + m->poll_interval_usec /= 2; + return; + } +} + +static int manager_receive_response(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + struct ntp_msg ntpmsg; + + struct iovec iov = { + .iov_base = &ntpmsg, + .iov_len = sizeof(ntpmsg), + }; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(struct timeval))]; + } control; + union sockaddr_union server_addr; + struct msghdr msghdr = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + .msg_name = &server_addr, + .msg_namelen = sizeof(server_addr), + }; + struct cmsghdr *cmsg; + struct timespec *recv_time; + ssize_t len; + double origin, receive, trans, dest; + double delay, offset; + double root_distance; + bool spike; + int leap_sec; + int r; + + assert(source); + assert(m); + + if (revents & (EPOLLHUP|EPOLLERR)) { + log_warning("Server connection returned error."); + return manager_connect(m); + } + + len = recvmsg(fd, &msghdr, MSG_DONTWAIT); + if (len < 0) { + if (errno == EAGAIN) + return 0; + + log_warning("Error receiving message. Disconnecting."); + return manager_connect(m); + } + + /* Too short or too long packet? */ + if (iov.iov_len < sizeof(struct ntp_msg) || (msghdr.msg_flags & MSG_TRUNC)) { + log_warning("Invalid response from server. Disconnecting."); + return manager_connect(m); + } + + if (!m->current_server_name || + !m->current_server_address || + !sockaddr_equal(&server_addr, &m->current_server_address->sockaddr)) { + log_debug("Response from unknown server."); + return 0; + } + + recv_time = NULL; + CMSG_FOREACH(cmsg, &msghdr) { + if (cmsg->cmsg_level != SOL_SOCKET) + continue; + + switch (cmsg->cmsg_type) { + case SCM_TIMESTAMPNS: + recv_time = (struct timespec *) CMSG_DATA(cmsg); + break; + } + } + if (!recv_time) { + log_error("Invalid packet timestamp."); + return -EINVAL; + } + + if (!m->pending) { + log_debug("Unexpected reply. Ignoring."); + return 0; + } + + m->missed_replies = 0; + + /* check our "time cookie" (we just stored nanoseconds in the fraction field) */ + if (be32toh(ntpmsg.origin_time.sec) != m->trans_time.tv_sec + OFFSET_1900_1970 || + be32toh(ntpmsg.origin_time.frac) != m->trans_time.tv_nsec) { + log_debug("Invalid reply; not our transmit time. Ignoring."); + return 0; + } + + m->event_timeout = sd_event_source_unref(m->event_timeout); + + if (be32toh(ntpmsg.recv_time.sec) < TIME_EPOCH + OFFSET_1900_1970 || + be32toh(ntpmsg.trans_time.sec) < TIME_EPOCH + OFFSET_1900_1970) { + log_debug("Invalid reply, returned times before epoch. Ignoring."); + return manager_connect(m); + } + + if (NTP_FIELD_LEAP(ntpmsg.field) == NTP_LEAP_NOTINSYNC || + ntpmsg.stratum == 0 || ntpmsg.stratum >= 16) { + log_debug("Server is not synchronized. Disconnecting."); + return manager_connect(m); + } + + if (!IN_SET(NTP_FIELD_VERSION(ntpmsg.field), 3, 4)) { + log_debug("Response NTPv%d. Disconnecting.", NTP_FIELD_VERSION(ntpmsg.field)); + return manager_connect(m); + } + + if (NTP_FIELD_MODE(ntpmsg.field) != NTP_MODE_SERVER) { + log_debug("Unsupported mode %d. Disconnecting.", NTP_FIELD_MODE(ntpmsg.field)); + return manager_connect(m); + } + + root_distance = ntp_ts_short_to_d(&ntpmsg.root_delay) / 2 + ntp_ts_short_to_d(&ntpmsg.root_dispersion); + if (root_distance > NTP_MAX_ROOT_DISTANCE) { + log_debug("Server has too large root distance. Disconnecting."); + return manager_connect(m); + } + + /* valid packet */ + m->pending = false; + m->retry_interval = 0; + + /* Stop listening */ + manager_listen_stop(m); + + /* announce leap seconds */ + if (NTP_FIELD_LEAP(ntpmsg.field) & NTP_LEAP_PLUSSEC) + leap_sec = 1; + else if (NTP_FIELD_LEAP(ntpmsg.field) & NTP_LEAP_MINUSSEC) + leap_sec = -1; + else + leap_sec = 0; + + /* + * "Timestamp Name ID When Generated + * ------------------------------------------------------------ + * Originate Timestamp T1 time request sent by client + * Receive Timestamp T2 time request received by server + * Transmit Timestamp T3 time reply sent by server + * Destination Timestamp T4 time reply received by client + * + * The round-trip delay, d, and system clock offset, t, are defined as: + * d = (T4 - T1) - (T3 - T2) t = ((T2 - T1) + (T3 - T4)) / 2" + */ + origin = ts_to_d(&m->trans_time) + OFFSET_1900_1970; + receive = ntp_ts_to_d(&ntpmsg.recv_time); + trans = ntp_ts_to_d(&ntpmsg.trans_time); + dest = ts_to_d(recv_time) + OFFSET_1900_1970; + + offset = ((receive - origin) + (trans - dest)) / 2; + delay = (dest - origin) - (trans - receive); + + spike = manager_sample_spike_detection(m, offset, delay); + + manager_adjust_poll(m, offset, spike); + + log_debug("NTP response:\n" + " leap : %u\n" + " version : %u\n" + " mode : %u\n" + " stratum : %u\n" + " precision : %.6f sec (%d)\n" + " root distance: %.6f sec\n" + " reference : %.4s\n" + " origin : %.3f\n" + " receive : %.3f\n" + " transmit : %.3f\n" + " dest : %.3f\n" + " offset : %+.3f sec\n" + " delay : %+.3f sec\n" + " packet count : %"PRIu64"\n" + " jitter : %.3f%s\n" + " poll interval: " USEC_FMT "\n", + NTP_FIELD_LEAP(ntpmsg.field), + NTP_FIELD_VERSION(ntpmsg.field), + NTP_FIELD_MODE(ntpmsg.field), + ntpmsg.stratum, + exp2(ntpmsg.precision), ntpmsg.precision, + root_distance, + ntpmsg.stratum == 1 ? ntpmsg.refid : "n/a", + origin - OFFSET_1900_1970, + receive - OFFSET_1900_1970, + trans - OFFSET_1900_1970, + dest - OFFSET_1900_1970, + offset, delay, + m->packet_count, + m->samples_jitter, spike ? " spike" : "", + m->poll_interval_usec / USEC_PER_SEC); + + if (!spike) { + m->sync = true; + r = manager_adjust_clock(m, offset, leap_sec); + if (r < 0) + log_error_errno(r, "Failed to call clock_adjtime(): %m"); + } + + log_debug("interval/delta/delay/jitter/drift " USEC_FMT "s/%+.3fs/%.3fs/%.3fs/%+ippm%s", + m->poll_interval_usec / USEC_PER_SEC, offset, delay, m->samples_jitter, m->drift_ppm, + spike ? " (ignored)" : ""); + + if (!m->good) { + _cleanup_free_ char *pretty = NULL; + + m->good = true; + + server_address_pretty(m->current_server_address, &pretty); + log_info("Synchronized to time server %s (%s).", strna(pretty), m->current_server_name->string); + sd_notifyf(false, "STATUS=Synchronized to time server %s (%s).", strna(pretty), m->current_server_name->string); + } + + r = manager_arm_timer(m, m->poll_interval_usec); + if (r < 0) + return log_error_errno(r, "Failed to rearm timer: %m"); + + return 0; +} + +static int manager_listen_setup(Manager *m) { + union sockaddr_union addr = {}; + static const int tos = IPTOS_LOWDELAY; + static const int on = 1; + int r; + + assert(m); + + if (m->server_socket >= 0) + return 0; + + assert(!m->event_receive); + assert(m->current_server_address); + + addr.sa.sa_family = m->current_server_address->sockaddr.sa.sa_family; + + m->server_socket = socket(addr.sa.sa_family, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (m->server_socket < 0) + return -errno; + + r = bind(m->server_socket, &addr.sa, m->current_server_address->socklen); + if (r < 0) + return -errno; + + r = setsockopt(m->server_socket, SOL_SOCKET, SO_TIMESTAMPNS, &on, sizeof(on)); + if (r < 0) + return -errno; + + (void) setsockopt(m->server_socket, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)); + + return sd_event_add_io(m->event, &m->event_receive, m->server_socket, EPOLLIN, manager_receive_response, m); +} + +static void manager_listen_stop(Manager *m) { + assert(m); + + m->event_receive = sd_event_source_unref(m->event_receive); + m->server_socket = safe_close(m->server_socket); +} + +static int manager_begin(Manager *m) { + _cleanup_free_ char *pretty = NULL; + int r; + + assert(m); + assert_return(m->current_server_name, -EHOSTUNREACH); + assert_return(m->current_server_address, -EHOSTUNREACH); + + m->good = false; + m->missed_replies = NTP_MAX_MISSED_REPLIES; + if (m->poll_interval_usec == 0) + m->poll_interval_usec = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC; + + server_address_pretty(m->current_server_address, &pretty); + log_debug("Connecting to time server %s (%s).", strna(pretty), m->current_server_name->string); + sd_notifyf(false, "STATUS=Connecting to time server %s (%s).", strna(pretty), m->current_server_name->string); + + r = manager_clock_watch_setup(m); + if (r < 0) + return r; + + return manager_send_request(m); +} + +void manager_set_server_name(Manager *m, ServerName *n) { + assert(m); + + if (m->current_server_name == n) + return; + + m->current_server_name = n; + m->current_server_address = NULL; + + manager_disconnect(m); + + if (n) + log_debug("Selected server %s.", n->string); +} + +void manager_set_server_address(Manager *m, ServerAddress *a) { + assert(m); + + if (m->current_server_address == a) + return; + + m->current_server_address = a; + /* If a is NULL, we are just clearing the address, without + * changing the name. Keep the existing name in that case. */ + if (a) + m->current_server_name = a->name; + + manager_disconnect(m); + + if (a) { + _cleanup_free_ char *pretty = NULL; + server_address_pretty(a, &pretty); + log_debug("Selected address %s of server %s.", strna(pretty), a->name->string); + } +} + +static int manager_resolve_handler(sd_resolve_query *q, int ret, const struct addrinfo *ai, void *userdata) { + Manager *m = userdata; + int r; + + assert(q); + assert(m); + assert(m->current_server_name); + + m->resolve_query = sd_resolve_query_unref(m->resolve_query); + + if (ret != 0) { + log_debug("Failed to resolve %s: %s", m->current_server_name->string, gai_strerror(ret)); + + /* Try next host */ + return manager_connect(m); + } + + for (; ai; ai = ai->ai_next) { + _cleanup_free_ char *pretty = NULL; + ServerAddress *a; + + assert(ai->ai_addr); + assert(ai->ai_addrlen >= offsetof(struct sockaddr, sa_data)); + + if (!IN_SET(ai->ai_addr->sa_family, AF_INET, AF_INET6)) { + log_warning("Unsuitable address protocol for %s", m->current_server_name->string); + continue; + } + + r = server_address_new(m->current_server_name, &a, (const union sockaddr_union*) ai->ai_addr, ai->ai_addrlen); + if (r < 0) + return log_error_errno(r, "Failed to add server address: %m"); + + server_address_pretty(a, &pretty); + log_debug("Resolved address %s for %s.", pretty, m->current_server_name->string); + } + + if (!m->current_server_name->addresses) { + log_error("Failed to find suitable address for host %s.", m->current_server_name->string); + + /* Try next host */ + return manager_connect(m); + } + + manager_set_server_address(m, m->current_server_name->addresses); + + return manager_begin(m); +} + +static int manager_retry_connect(sd_event_source *source, usec_t usec, void *userdata) { + Manager *m = userdata; + + assert(m); + + return manager_connect(m); +} + +int manager_connect(Manager *m) { + int r; + + assert(m); + + manager_disconnect(m); + + m->event_retry = sd_event_source_unref(m->event_retry); + if (!ratelimit_test(&m->ratelimit)) { + log_debug("Slowing down attempts to contact servers."); + + r = sd_event_add_time(m->event, &m->event_retry, clock_boottime_or_monotonic(), now(clock_boottime_or_monotonic()) + RETRY_USEC, 0, manager_retry_connect, m); + if (r < 0) + return log_error_errno(r, "Failed to create retry timer: %m"); + + return 0; + } + + /* If we already are operating on some address, switch to the + * next one. */ + if (m->current_server_address && m->current_server_address->addresses_next) + manager_set_server_address(m, m->current_server_address->addresses_next); + else { + struct addrinfo hints = { + .ai_flags = AI_NUMERICSERV|AI_ADDRCONFIG, + .ai_socktype = SOCK_DGRAM, + }; + + /* Hmm, we are through all addresses, let's look for the next host instead */ + if (m->current_server_name && m->current_server_name->names_next) + manager_set_server_name(m, m->current_server_name->names_next); + else { + ServerName *f; + bool restart = true; + + /* Our current server name list is exhausted, + * let's find the next one to iterate. First + * we try the system list, then the link list. + * After having processed the link list we + * jump back to the system list. However, if + * both lists are empty, we change to the + * fallback list. */ + if (!m->current_server_name || m->current_server_name->type == SERVER_LINK) { + f = m->system_servers; + if (!f) + f = m->link_servers; + } else { + f = m->link_servers; + if (!f) + f = m->system_servers; + else + restart = false; + } + + if (!f) + f = m->fallback_servers; + + if (!f) { + manager_set_server_name(m, NULL); + log_debug("No server found."); + return 0; + } + + if (restart && !m->exhausted_servers && m->poll_interval_usec) { + log_debug("Waiting after exhausting servers."); + r = sd_event_add_time(m->event, &m->event_retry, clock_boottime_or_monotonic(), now(clock_boottime_or_monotonic()) + m->poll_interval_usec, 0, manager_retry_connect, m); + if (r < 0) + return log_error_errno(r, "Failed to create retry timer: %m"); + + m->exhausted_servers = true; + + /* Increase the polling interval */ + if (m->poll_interval_usec < NTP_POLL_INTERVAL_MAX_SEC * USEC_PER_SEC) + m->poll_interval_usec *= 2; + + return 0; + } + + m->exhausted_servers = false; + + manager_set_server_name(m, f); + } + + /* Tell the resolver to reread /etc/resolv.conf, in + * case it changed. */ + res_init(); + + /* Flush out any previously resolved addresses */ + server_name_flush_addresses(m->current_server_name); + + log_debug("Resolving %s...", m->current_server_name->string); + + r = sd_resolve_getaddrinfo(m->resolve, &m->resolve_query, m->current_server_name->string, "123", &hints, manager_resolve_handler, m); + if (r < 0) + return log_error_errno(r, "Failed to create resolver: %m"); + + return 1; + } + + r = manager_begin(m); + if (r < 0) + return r; + + return 1; +} + +void manager_disconnect(Manager *m) { + assert(m); + + m->resolve_query = sd_resolve_query_unref(m->resolve_query); + + m->event_timer = sd_event_source_unref(m->event_timer); + + manager_listen_stop(m); + + m->event_clock_watch = sd_event_source_unref(m->event_clock_watch); + m->clock_watch_fd = safe_close(m->clock_watch_fd); + + m->event_timeout = sd_event_source_unref(m->event_timeout); + + sd_notifyf(false, "STATUS=Idle."); +} + +void manager_flush_server_names(Manager *m, ServerType t) { + assert(m); + + if (t == SERVER_SYSTEM) + while (m->system_servers) + server_name_free(m->system_servers); + + if (t == SERVER_LINK) + while (m->link_servers) + server_name_free(m->link_servers); + + if (t == SERVER_FALLBACK) + while (m->fallback_servers) + server_name_free(m->fallback_servers); +} + +void manager_free(Manager *m) { + if (!m) + return; + + manager_disconnect(m); + manager_flush_server_names(m, SERVER_SYSTEM); + manager_flush_server_names(m, SERVER_LINK); + manager_flush_server_names(m, SERVER_FALLBACK); + + sd_event_source_unref(m->event_retry); + + sd_event_source_unref(m->network_event_source); + sd_network_monitor_unref(m->network_monitor); + + sd_resolve_unref(m->resolve); + sd_event_unref(m->event); + + free(m); +} + +static int manager_network_read_link_servers(Manager *m) { + _cleanup_strv_free_ char **ntp = NULL; + ServerName *n, *nx; + char **i; + int r; + + assert(m); + + r = sd_network_get_ntp(&ntp); + if (r < 0) + goto clear; + + LIST_FOREACH(names, n, m->link_servers) + n->marked = true; + + STRV_FOREACH(i, ntp) { + bool found = false; + + LIST_FOREACH(names, n, m->link_servers) + if (streq(n->string, *i)) { + n->marked = false; + found = true; + break; + } + + if (!found) { + r = server_name_new(m, NULL, SERVER_LINK, *i); + if (r < 0) + goto clear; + } + } + + LIST_FOREACH_SAFE(names, n, nx, m->link_servers) + if (n->marked) + server_name_free(n); + + return 0; + +clear: + manager_flush_server_names(m, SERVER_LINK); + return r; +} + +static int manager_network_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + bool connected, online; + int r; + + assert(m); + + sd_network_monitor_flush(m->network_monitor); + + manager_network_read_link_servers(m); + + /* check if the machine is online */ + online = network_is_online(); + + /* check if the client is currently connected */ + connected = m->server_socket >= 0 || m->resolve_query || m->exhausted_servers; + + if (connected && !online) { + log_info("No network connectivity, watching for changes."); + manager_disconnect(m); + + } else if (!connected && online) { + log_info("Network configuration changed, trying to establish connection."); + + if (m->current_server_address) + r = manager_begin(m); + else + r = manager_connect(m); + if (r < 0) + return r; + } + + return 0; +} + +static int manager_network_monitor_listen(Manager *m) { + int r, fd, events; + + assert(m); + + r = sd_network_monitor_new(&m->network_monitor, NULL); + if (r < 0) + return r; + + fd = sd_network_monitor_get_fd(m->network_monitor); + if (fd < 0) + return fd; + + events = sd_network_monitor_get_events(m->network_monitor); + if (events < 0) + return events; + + r = sd_event_add_io(m->event, &m->network_event_source, fd, events, manager_network_event_handler, m); + if (r < 0) + return r; + + return 0; +} + +int manager_new(Manager **ret) { + _cleanup_(manager_freep) Manager *m = NULL; + int r; + + assert(ret); + + m = new0(Manager, 1); + if (!m) + return -ENOMEM; + + m->server_socket = m->clock_watch_fd = -1; + + RATELIMIT_INIT(m->ratelimit, RATELIMIT_INTERVAL_USEC, RATELIMIT_BURST); + + r = manager_parse_server_string(m, SERVER_FALLBACK, NTP_SERVERS); + if (r < 0) + return r; + + r = sd_event_default(&m->event); + if (r < 0) + return r; + + sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); + sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); + + sd_event_set_watchdog(m->event, true); + + r = sd_resolve_default(&m->resolve); + if (r < 0) + return r; + + r = sd_resolve_attach_event(m->resolve, m->event, 0); + if (r < 0) + return r; + + r = manager_network_monitor_listen(m); + if (r < 0) + return r; + + manager_network_read_link_servers(m); + + *ret = m; + m = NULL; + + return 0; +} diff --git a/src/systemd-timesync/timesyncd-manager.h b/src/systemd-timesync/timesyncd-manager.h new file mode 100644 index 0000000000..fd25647725 --- /dev/null +++ b/src/systemd-timesync/timesyncd-manager.h @@ -0,0 +1,104 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Kay Sievers, 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 +#include + +#include "list.h" +#include "ratelimit.h" + +typedef struct Manager Manager; + +#include "timesyncd-server.h" + +struct Manager { + sd_event *event; + sd_resolve *resolve; + + LIST_HEAD(ServerName, system_servers); + LIST_HEAD(ServerName, link_servers); + LIST_HEAD(ServerName, fallback_servers); + + RateLimit ratelimit; + bool exhausted_servers; + + /* network */ + sd_event_source *network_event_source; + sd_network_monitor *network_monitor; + + /* peer */ + sd_resolve_query *resolve_query; + sd_event_source *event_receive; + ServerName *current_server_name; + ServerAddress *current_server_address; + int server_socket; + int missed_replies; + uint64_t packet_count; + sd_event_source *event_timeout; + bool good; + + /* last sent packet */ + struct timespec trans_time_mon; + struct timespec trans_time; + usec_t retry_interval; + bool pending; + + /* poll timer */ + sd_event_source *event_timer; + usec_t poll_interval_usec; + bool poll_resync; + + /* history data */ + struct { + double offset; + double delay; + } samples[8]; + unsigned int samples_idx; + double samples_jitter; + + /* last change */ + bool jumped; + bool sync; + int drift_ppm; + + /* watch for time changes */ + sd_event_source *event_clock_watch; + int clock_watch_fd; + + /* Retry connections */ + sd_event_source *event_retry; + + /* RTC runs in local time, leave it alone */ + bool rtc_local_time; +}; + +int manager_new(Manager **ret); +void manager_free(Manager *m); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); + +void manager_set_server_name(Manager *m, ServerName *n); +void manager_set_server_address(Manager *m, ServerAddress *a); +void manager_flush_server_names(Manager *m, ServerType t); + +int manager_connect(Manager *m); +void manager_disconnect(Manager *m); diff --git a/src/systemd-timesync/timesyncd-server.c b/src/systemd-timesync/timesyncd-server.c new file mode 100644 index 0000000000..6bda86fe6e --- /dev/null +++ b/src/systemd-timesync/timesyncd-server.c @@ -0,0 +1,150 @@ +/*** + This file is part of systemd. + + Copyright 2014 Kay Sievers, 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 "alloc-util.h" +#include "timesyncd-server.h" + +int server_address_new( + ServerName *n, + ServerAddress **ret, + const union sockaddr_union *sockaddr, + socklen_t socklen) { + + ServerAddress *a, *tail; + + assert(n); + assert(sockaddr); + assert(socklen >= offsetof(struct sockaddr, sa_data)); + assert(socklen <= sizeof(union sockaddr_union)); + + a = new0(ServerAddress, 1); + if (!a) + return -ENOMEM; + + memcpy(&a->sockaddr, sockaddr, socklen); + a->socklen = socklen; + + LIST_FIND_TAIL(addresses, n->addresses, tail); + LIST_INSERT_AFTER(addresses, n->addresses, tail, a); + a->name = n; + + if (ret) + *ret = a; + + return 0; +} + +ServerAddress* server_address_free(ServerAddress *a) { + if (!a) + return NULL; + + if (a->name) { + LIST_REMOVE(addresses, a->name->addresses, a); + + if (a->name->manager && a->name->manager->current_server_address == a) + manager_set_server_address(a->name->manager, NULL); + } + + free(a); + return NULL; +} + +int server_name_new( + Manager *m, + ServerName **ret, + ServerType type, + const char *string) { + + ServerName *n, *tail; + + assert(m); + assert(string); + + n = new0(ServerName, 1); + if (!n) + return -ENOMEM; + + n->type = type; + n->string = strdup(string); + if (!n->string) { + free(n); + return -ENOMEM; + } + + if (type == SERVER_SYSTEM) { + LIST_FIND_TAIL(names, m->system_servers, tail); + LIST_INSERT_AFTER(names, m->system_servers, tail, n); + } else if (type == SERVER_LINK) { + LIST_FIND_TAIL(names, m->link_servers, tail); + LIST_INSERT_AFTER(names, m->link_servers, tail, n); + } else if (type == SERVER_FALLBACK) { + LIST_FIND_TAIL(names, m->fallback_servers, tail); + LIST_INSERT_AFTER(names, m->fallback_servers, tail, n); + } else + assert_not_reached("Unknown server type"); + + n->manager = m; + + if (type != SERVER_FALLBACK && + m->current_server_name && + m->current_server_name->type == SERVER_FALLBACK) + manager_set_server_name(m, NULL); + + log_debug("Added new server %s.", string); + + if (ret) + *ret = n; + + return 0; +} + +ServerName *server_name_free(ServerName *n) { + if (!n) + return NULL; + + server_name_flush_addresses(n); + + if (n->manager) { + if (n->type == SERVER_SYSTEM) + LIST_REMOVE(names, n->manager->system_servers, n); + else if (n->type == SERVER_LINK) + LIST_REMOVE(names, n->manager->link_servers, n); + else if (n->type == SERVER_FALLBACK) + LIST_REMOVE(names, n->manager->fallback_servers, n); + else + assert_not_reached("Unknown server type"); + + if (n->manager->current_server_name == n) + manager_set_server_name(n->manager, NULL); + } + + log_debug("Removed server %s.", n->string); + + free(n->string); + free(n); + + return NULL; +} + +void server_name_flush_addresses(ServerName *n) { + assert(n); + + while (n->addresses) + server_address_free(n->addresses); +} diff --git a/src/systemd-timesync/timesyncd-server.h b/src/systemd-timesync/timesyncd-server.h new file mode 100644 index 0000000000..8a19e41d67 --- /dev/null +++ b/src/systemd-timesync/timesyncd-server.h @@ -0,0 +1,65 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Kay Sievers, 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 "list.h" +#include "socket-util.h" + +typedef struct ServerAddress ServerAddress; +typedef struct ServerName ServerName; + +typedef enum ServerType { + SERVER_SYSTEM, + SERVER_FALLBACK, + SERVER_LINK, +} ServerType; + +#include "timesyncd-manager.h" + +struct ServerAddress { + ServerName *name; + + union sockaddr_union sockaddr; + socklen_t socklen; + + LIST_FIELDS(ServerAddress, addresses); +}; + +struct ServerName { + Manager *manager; + + ServerType type; + char *string; + + bool marked:1; + + LIST_HEAD(ServerAddress, addresses); + LIST_FIELDS(ServerName, names); +}; + +int server_address_new(ServerName *n, ServerAddress **ret, const union sockaddr_union *sockaddr, socklen_t socklen); +ServerAddress* server_address_free(ServerAddress *a); +static inline int server_address_pretty(ServerAddress *a, char **pretty) { + return sockaddr_pretty(&a->sockaddr.sa, a->socklen, true, true, pretty); +} + +int server_name_new(Manager *m, ServerName **ret, ServerType type,const char *string); +ServerName *server_name_free(ServerName *n); +void server_name_flush_addresses(ServerName *n); diff --git a/src/systemd-timesync/timesyncd.c b/src/systemd-timesync/timesyncd.c new file mode 100644 index 0000000000..9e538a82f2 --- /dev/null +++ b/src/systemd-timesync/timesyncd.c @@ -0,0 +1,164 @@ +/*** + This file is part of systemd. + + Copyright 2014 Kay Sievers, 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 + +#include "capability-util.h" +#include "clock-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "network-util.h" +#include "signal-util.h" +#include "timesyncd-conf.h" +#include "timesyncd-manager.h" +#include "user-util.h" + +static int load_clock_timestamp(uid_t uid, gid_t gid) { + _cleanup_close_ int fd = -1; + usec_t min = TIME_EPOCH * USEC_PER_SEC; + usec_t ct; + int r; + + /* Let's try to make sure that the clock is always + * monotonically increasing, by saving the clock whenever we + * have a new NTP time, or when we shut down, and restoring it + * when we start again. This is particularly helpful on + * systems lacking a battery backed RTC. We also will adjust + * the time to at least the build time of systemd. */ + + fd = open("/var/lib/systemd/clock", O_RDWR|O_CLOEXEC, 0644); + if (fd >= 0) { + struct stat st; + usec_t stamp; + + /* check if the recorded time is later than the compiled-in one */ + r = fstat(fd, &st); + if (r >= 0) { + stamp = timespec_load(&st.st_mtim); + if (stamp > min) + min = stamp; + } + + /* Try to fix the access mode, so that we can still + touch the file after dropping priviliges */ + (void) fchmod(fd, 0644); + (void) fchown(fd, uid, gid); + + } else + /* create stamp file with the compiled-in date */ + (void) touch_file("/var/lib/systemd/clock", true, min, uid, gid, 0644); + + ct = now(CLOCK_REALTIME); + if (ct < min) { + struct timespec ts; + char date[FORMAT_TIMESTAMP_MAX]; + + log_info("System clock time unset or jumped backwards, restoring from recorded timestamp: %s", + format_timestamp(date, sizeof(date), min)); + + if (clock_settime(CLOCK_REALTIME, timespec_store(&ts, min)) < 0) + log_error_errno(errno, "Failed to restore system clock: %m"); + } + + return 0; +} + +int main(int argc, char *argv[]) { + _cleanup_(manager_freep) Manager *m = NULL; + const char *user = "systemd-timesync"; + uid_t uid; + gid_t gid; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_set_facility(LOG_CRON); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc != 1) { + log_error("This program does not take arguments."); + r = -EINVAL; + goto finish; + } + + r = get_user_creds(&user, &uid, &gid, NULL, NULL); + if (r < 0) { + log_error_errno(r, "Cannot resolve user name %s: %m", user); + goto finish; + } + + r = load_clock_timestamp(uid, gid); + if (r < 0) + goto finish; + + r = drop_privileges(uid, gid, (1ULL << CAP_SYS_TIME)); + if (r < 0) + goto finish; + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + + r = manager_new(&m); + if (r < 0) { + log_error_errno(r, "Failed to allocate manager: %m"); + goto finish; + } + + if (clock_is_localtime(NULL) > 0) { + log_info("The system is configured to read the RTC time in the local time zone. " + "This mode can not be fully supported. All system time to RTC updates are disabled."); + m->rtc_local_time = true; + } + + r = manager_parse_config_file(m); + if (r < 0) + log_warning_errno(r, "Failed to parse configuration file: %m"); + + log_debug("systemd-timesyncd running as pid " PID_FMT, getpid()); + sd_notify(false, + "READY=1\n" + "STATUS=Daemon is running"); + + if (network_is_online()) { + r = manager_connect(m); + if (r < 0) + goto finish; + } + + r = sd_event_loop(m->event); + if (r < 0) { + log_error_errno(r, "Failed to run event loop: %m"); + goto finish; + } + + /* if we got an authoritative time, store it in the file system */ + if (m->sync) + (void) touch("/var/lib/systemd/clock"); + + sd_event_get_exit_code(m->event, &r); + +finish: + sd_notify(false, + "STOPPING=1\n" + "STATUS=Shutting down..."); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-timesync/timesyncd.conf.in b/src/systemd-timesync/timesyncd.conf.in new file mode 100644 index 0000000000..b6a2ada273 --- /dev/null +++ b/src/systemd-timesync/timesyncd.conf.in @@ -0,0 +1,16 @@ +# This file is part of systemd. +# +# 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. +# +# Entries in this file show the compile time defaults. +# You can change settings by editing this file. +# Defaults can be restored by simply deleting this file. +# +# See timesyncd.conf(5) for details. + +[Time] +#NTP= +#FallbackNTP=@NTP_SERVERS@ diff --git a/src/systemd-tmpfiles/Makefile b/src/systemd-tmpfiles/Makefile new file mode 100644 index 0000000000..c193b9c37d --- /dev/null +++ b/src/systemd-tmpfiles/Makefile @@ -0,0 +1,84 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_TMPFILES),) +systemd_tmpfiles_SOURCES = \ + src/tmpfiles/tmpfiles.c + +systemd_tmpfiles_LDADD = \ + libshared.la + +bin_PROGRAMS += \ + systemd-tmpfiles + +dist_systemunit_DATA += \ + units/systemd-tmpfiles-clean.timer + +nodist_systemunit_DATA += \ + units/systemd-tmpfiles-setup-dev.service \ + units/systemd-tmpfiles-setup.service \ + units/systemd-tmpfiles-clean.service + +nodist_tmpfiles_DATA = \ + tmpfiles.d/systemd.conf \ + tmpfiles.d/etc.conf + +dist_tmpfiles_DATA = \ + tmpfiles.d/systemd-nologin.conf \ + tmpfiles.d/tmp.conf \ + tmpfiles.d/x11.conf \ + tmpfiles.d/var.conf \ + tmpfiles.d/home.conf \ + tmpfiles.d/systemd-nspawn.conf \ + tmpfiles.d/journal-nocow.conf + +ifneq ($(HAVE_SYSV_COMPAT),) +dist_tmpfiles_DATA += \ + tmpfiles.d/legacy.conf +endif # HAVE_SYSV_COMPAT + +SYSINIT_TARGET_WANTS += \ + systemd-tmpfiles-setup-dev.service \ + systemd-tmpfiles-setup.service + +dist_zshcompletion_data += \ + shell-completion/zsh/_systemd-tmpfiles + +TIMERS_TARGET_WANTS += \ + systemd-tmpfiles-clean.timer + +INSTALL_DIRS += \ + $(tmpfilesdir) \ + $(sysconfdir)/tmpfiles.d +endif # ENABLE_TMPFILES + +EXTRA_DIST += \ + tmpfiles.d/systemd.conf.m4 \ + tmpfiles.d/etc.conf.m4 \ + units/systemd-tmpfiles-setup-dev.service.in \ + units/systemd-tmpfiles-setup.service.in \ + units/systemd-tmpfiles-clean.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-tmpfiles/tmpfiles.c b/src/systemd-tmpfiles/tmpfiles.c new file mode 100644 index 0000000000..2053d35a67 --- /dev/null +++ b/src/systemd-tmpfiles/tmpfiles.c @@ -0,0 +1,2343 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering, Kay Sievers + Copyright 2015 Zbigniew Jędrzejewski-Szmek + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "acl-util.h" +#include "alloc-util.h" +#include "btrfs-util.h" +#include "capability-util.h" +#include "chattr-util.h" +#include "conf-files.h" +#include "copy.h" +#include "def.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "glob-util.h" +#include "io-util.h" +#include "label.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "mount-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "rm-rf.h" +#include "selinux-util.h" +#include "set.h" +#include "specifier.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "umask-util.h" +#include "user-util.h" +#include "util.h" + +/* This reads all files listed in /etc/tmpfiles.d/?*.conf and creates + * them in the file system. This is intended to be used to create + * properly owned directories beneath /tmp, /var/tmp, /run, which are + * volatile and hence need to be recreated on bootup. */ + +typedef enum ItemType { + /* These ones take file names */ + CREATE_FILE = 'f', + TRUNCATE_FILE = 'F', + CREATE_DIRECTORY = 'd', + TRUNCATE_DIRECTORY = 'D', + CREATE_SUBVOLUME = 'v', + CREATE_SUBVOLUME_INHERIT_QUOTA = 'q', + CREATE_SUBVOLUME_NEW_QUOTA = 'Q', + CREATE_FIFO = 'p', + CREATE_SYMLINK = 'L', + CREATE_CHAR_DEVICE = 'c', + CREATE_BLOCK_DEVICE = 'b', + COPY_FILES = 'C', + + /* These ones take globs */ + WRITE_FILE = 'w', + EMPTY_DIRECTORY = 'e', + SET_XATTR = 't', + RECURSIVE_SET_XATTR = 'T', + SET_ACL = 'a', + RECURSIVE_SET_ACL = 'A', + SET_ATTRIBUTE = 'h', + RECURSIVE_SET_ATTRIBUTE = 'H', + IGNORE_PATH = 'x', + IGNORE_DIRECTORY_PATH = 'X', + REMOVE_PATH = 'r', + RECURSIVE_REMOVE_PATH = 'R', + RELABEL_PATH = 'z', + RECURSIVE_RELABEL_PATH = 'Z', + ADJUST_MODE = 'm', /* legacy, 'z' is identical to this */ +} ItemType; + +typedef struct Item { + ItemType type; + + char *path; + char *argument; + char **xattrs; +#ifdef HAVE_ACL + acl_t acl_access; + acl_t acl_default; +#endif + uid_t uid; + gid_t gid; + mode_t mode; + usec_t age; + + dev_t major_minor; + unsigned attribute_value; + unsigned attribute_mask; + + bool uid_set:1; + bool gid_set:1; + bool mode_set:1; + bool age_set:1; + bool mask_perms:1; + bool attribute_set:1; + + bool keep_first_level:1; + + bool force:1; + + bool done:1; +} Item; + +typedef struct ItemArray { + Item *items; + size_t count; + size_t size; +} ItemArray; + +static bool arg_create = false; +static bool arg_clean = false; +static bool arg_remove = false; +static bool arg_boot = false; + +static char **arg_include_prefixes = NULL; +static char **arg_exclude_prefixes = NULL; +static char *arg_root = NULL; + +static const char conf_file_dirs[] = CONF_PATHS_NULSTR("tmpfiles.d"); + +#define MAX_DEPTH 256 + +static OrderedHashmap *items = NULL, *globs = NULL; +static Set *unix_sockets = NULL; + +static const Specifier specifier_table[] = { + { 'm', specifier_machine_id, NULL }, + { 'b', specifier_boot_id, NULL }, + { 'H', specifier_host_name, NULL }, + { 'v', specifier_kernel_release, NULL }, + {} +}; + +static bool needs_glob(ItemType t) { + return IN_SET(t, + WRITE_FILE, + IGNORE_PATH, + IGNORE_DIRECTORY_PATH, + REMOVE_PATH, + RECURSIVE_REMOVE_PATH, + EMPTY_DIRECTORY, + ADJUST_MODE, + RELABEL_PATH, + RECURSIVE_RELABEL_PATH, + SET_XATTR, + RECURSIVE_SET_XATTR, + SET_ACL, + RECURSIVE_SET_ACL, + SET_ATTRIBUTE, + RECURSIVE_SET_ATTRIBUTE); +} + +static bool takes_ownership(ItemType t) { + return IN_SET(t, + CREATE_FILE, + TRUNCATE_FILE, + CREATE_DIRECTORY, + EMPTY_DIRECTORY, + TRUNCATE_DIRECTORY, + CREATE_SUBVOLUME, + CREATE_SUBVOLUME_INHERIT_QUOTA, + CREATE_SUBVOLUME_NEW_QUOTA, + CREATE_FIFO, + CREATE_SYMLINK, + CREATE_CHAR_DEVICE, + CREATE_BLOCK_DEVICE, + COPY_FILES, + WRITE_FILE, + IGNORE_PATH, + IGNORE_DIRECTORY_PATH, + REMOVE_PATH, + RECURSIVE_REMOVE_PATH); +} + +static struct Item* find_glob(OrderedHashmap *h, const char *match) { + ItemArray *j; + Iterator i; + + ORDERED_HASHMAP_FOREACH(j, h, i) { + unsigned n; + + for (n = 0; n < j->count; n++) { + Item *item = j->items + n; + + if (fnmatch(item->path, match, FNM_PATHNAME|FNM_PERIOD) == 0) + return item; + } + } + + return NULL; +} + +static void load_unix_sockets(void) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + + if (unix_sockets) + return; + + /* We maintain a cache of the sockets we found in + * /proc/net/unix to speed things up a little. */ + + unix_sockets = set_new(&string_hash_ops); + if (!unix_sockets) + return; + + f = fopen("/proc/net/unix", "re"); + if (!f) + return; + + /* Skip header */ + if (!fgets(line, sizeof(line), f)) + goto fail; + + for (;;) { + char *p, *s; + int k; + + if (!fgets(line, sizeof(line), f)) + break; + + truncate_nl(line); + + p = strchr(line, ':'); + if (!p) + continue; + + if (strlen(p) < 37) + continue; + + p += 37; + p += strspn(p, WHITESPACE); + p += strcspn(p, WHITESPACE); /* skip one more word */ + p += strspn(p, WHITESPACE); + + if (*p != '/') + continue; + + s = strdup(p); + if (!s) + goto fail; + + path_kill_slashes(s); + + k = set_consume(unix_sockets, s); + if (k < 0 && k != -EEXIST) + goto fail; + } + + return; + +fail: + set_free_free(unix_sockets); + unix_sockets = NULL; +} + +static bool unix_socket_alive(const char *fn) { + assert(fn); + + load_unix_sockets(); + + if (unix_sockets) + return !!set_get(unix_sockets, (char*) fn); + + /* We don't know, so assume yes */ + return true; +} + +static int dir_is_mount_point(DIR *d, const char *subdir) { + + union file_handle_union h = FILE_HANDLE_INIT; + int mount_id_parent, mount_id; + int r_p, r; + + r_p = name_to_handle_at(dirfd(d), ".", &h.handle, &mount_id_parent, 0); + if (r_p < 0) + r_p = -errno; + + h.handle.handle_bytes = MAX_HANDLE_SZ; + r = name_to_handle_at(dirfd(d), subdir, &h.handle, &mount_id, 0); + if (r < 0) + r = -errno; + + /* got no handle; make no assumptions, return error */ + if (r_p < 0 && r < 0) + return r_p; + + /* got both handles; if they differ, it is a mount point */ + if (r_p >= 0 && r >= 0) + return mount_id_parent != mount_id; + + /* got only one handle; assume different mount points if one + * of both queries was not supported by the filesystem */ + if (r_p == -ENOSYS || r_p == -EOPNOTSUPP || r == -ENOSYS || r == -EOPNOTSUPP) + return true; + + /* return error */ + if (r_p < 0) + return r_p; + return r; +} + +static DIR* xopendirat_nomod(int dirfd, const char *path) { + DIR *dir; + + dir = xopendirat(dirfd, path, O_NOFOLLOW|O_NOATIME); + if (dir) + return dir; + + log_debug_errno(errno, "Cannot open %sdirectory \"%s\": %m", dirfd == AT_FDCWD ? "" : "sub", path); + if (errno != EPERM) + return NULL; + + dir = xopendirat(dirfd, path, O_NOFOLLOW); + if (!dir) + log_debug_errno(errno, "Cannot open %sdirectory \"%s\": %m", dirfd == AT_FDCWD ? "" : "sub", path); + + return dir; +} + +static DIR* opendir_nomod(const char *path) { + return xopendirat_nomod(AT_FDCWD, path); +} + +static int dir_cleanup( + Item *i, + const char *p, + DIR *d, + const struct stat *ds, + usec_t cutoff, + dev_t rootdev, + bool mountpoint, + int maxdepth, + bool keep_this_level) { + + struct dirent *dent; + struct timespec times[2]; + bool deleted = false; + int r = 0; + + while ((dent = readdir(d))) { + struct stat s; + usec_t age; + _cleanup_free_ char *sub_path = NULL; + + if (STR_IN_SET(dent->d_name, ".", "..")) + continue; + + if (fstatat(dirfd(d), dent->d_name, &s, AT_SYMLINK_NOFOLLOW) < 0) { + if (errno == ENOENT) + continue; + + /* FUSE, NFS mounts, SELinux might return EACCES */ + if (errno == EACCES) + log_debug_errno(errno, "stat(%s/%s) failed: %m", p, dent->d_name); + else + log_error_errno(errno, "stat(%s/%s) failed: %m", p, dent->d_name); + r = -errno; + continue; + } + + /* Stay on the same filesystem */ + if (s.st_dev != rootdev) { + log_debug("Ignoring \"%s/%s\": different filesystem.", p, dent->d_name); + continue; + } + + /* Try to detect bind mounts of the same filesystem instance; they + * do not differ in device major/minors. This type of query is not + * supported on all kernels or filesystem types though. */ + if (S_ISDIR(s.st_mode) && dir_is_mount_point(d, dent->d_name) > 0) { + log_debug("Ignoring \"%s/%s\": different mount of the same filesystem.", + p, dent->d_name); + continue; + } + + /* Do not delete read-only files owned by root */ + if (s.st_uid == 0 && !(s.st_mode & S_IWUSR)) { + log_debug("Ignoring \"%s/%s\": read-only and owner by root.", p, dent->d_name); + continue; + } + + sub_path = strjoin(p, "/", dent->d_name, NULL); + if (!sub_path) { + r = log_oom(); + goto finish; + } + + /* Is there an item configured for this path? */ + if (ordered_hashmap_get(items, sub_path)) { + log_debug("Ignoring \"%s\": a separate entry exists.", sub_path); + continue; + } + + if (find_glob(globs, sub_path)) { + log_debug("Ignoring \"%s\": a separate glob exists.", sub_path); + continue; + } + + if (S_ISDIR(s.st_mode)) { + + if (mountpoint && + streq(dent->d_name, "lost+found") && + s.st_uid == 0) { + log_debug("Ignoring \"%s\".", sub_path); + continue; + } + + if (maxdepth <= 0) + log_warning("Reached max depth on \"%s\".", sub_path); + else { + _cleanup_closedir_ DIR *sub_dir; + int q; + + sub_dir = xopendirat_nomod(dirfd(d), dent->d_name); + if (!sub_dir) { + if (errno != ENOENT) + r = log_error_errno(errno, "opendir(%s) failed: %m", sub_path); + + continue; + } + + q = dir_cleanup(i, sub_path, sub_dir, &s, cutoff, rootdev, false, maxdepth-1, false); + if (q < 0) + r = q; + } + + /* Note: if you are wondering why we don't + * support the sticky bit for excluding + * directories from cleaning like we do it for + * other file system objects: well, the sticky + * bit already has a meaning for directories, + * so we don't want to overload that. */ + + if (keep_this_level) { + log_debug("Keeping \"%s\".", sub_path); + continue; + } + + /* Ignore ctime, we change it when deleting */ + age = timespec_load(&s.st_mtim); + if (age >= cutoff) { + char a[FORMAT_TIMESTAMP_MAX]; + /* Follows spelling in stat(1). */ + log_debug("Directory \"%s\": modify time %s is too new.", + sub_path, + format_timestamp_us(a, sizeof(a), age)); + continue; + } + + age = timespec_load(&s.st_atim); + if (age >= cutoff) { + char a[FORMAT_TIMESTAMP_MAX]; + log_debug("Directory \"%s\": access time %s is too new.", + sub_path, + format_timestamp_us(a, sizeof(a), age)); + continue; + } + + log_debug("Removing directory \"%s\".", sub_path); + if (unlinkat(dirfd(d), dent->d_name, AT_REMOVEDIR) < 0) + if (errno != ENOENT && errno != ENOTEMPTY) { + log_error_errno(errno, "rmdir(%s): %m", sub_path); + r = -errno; + } + + } else { + /* Skip files for which the sticky bit is + * set. These are semantics we define, and are + * unknown elsewhere. See XDG_RUNTIME_DIR + * specification for details. */ + if (s.st_mode & S_ISVTX) { + log_debug("Skipping \"%s\": sticky bit set.", sub_path); + continue; + } + + if (mountpoint && S_ISREG(s.st_mode)) + if (s.st_uid == 0 && STR_IN_SET(dent->d_name, + ".journal", + "aquota.user", + "aquota.group")) { + log_debug("Skipping \"%s\".", sub_path); + continue; + } + + /* Ignore sockets that are listed in /proc/net/unix */ + if (S_ISSOCK(s.st_mode) && unix_socket_alive(sub_path)) { + log_debug("Skipping \"%s\": live socket.", sub_path); + continue; + } + + /* Ignore device nodes */ + if (S_ISCHR(s.st_mode) || S_ISBLK(s.st_mode)) { + log_debug("Skipping \"%s\": a device.", sub_path); + continue; + } + + /* Keep files on this level around if this is + * requested */ + if (keep_this_level) { + log_debug("Keeping \"%s\".", sub_path); + continue; + } + + age = timespec_load(&s.st_mtim); + if (age >= cutoff) { + char a[FORMAT_TIMESTAMP_MAX]; + /* Follows spelling in stat(1). */ + log_debug("File \"%s\": modify time %s is too new.", + sub_path, + format_timestamp_us(a, sizeof(a), age)); + continue; + } + + age = timespec_load(&s.st_atim); + if (age >= cutoff) { + char a[FORMAT_TIMESTAMP_MAX]; + log_debug("File \"%s\": access time %s is too new.", + sub_path, + format_timestamp_us(a, sizeof(a), age)); + continue; + } + + age = timespec_load(&s.st_ctim); + if (age >= cutoff) { + char a[FORMAT_TIMESTAMP_MAX]; + log_debug("File \"%s\": change time %s is too new.", + sub_path, + format_timestamp_us(a, sizeof(a), age)); + continue; + } + + log_debug("unlink \"%s\"", sub_path); + + if (unlinkat(dirfd(d), dent->d_name, 0) < 0) + if (errno != ENOENT) + r = log_error_errno(errno, "unlink(%s): %m", sub_path); + + deleted = true; + } + } + +finish: + if (deleted) { + usec_t age1, age2; + char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX]; + + /* Restore original directory timestamps */ + times[0] = ds->st_atim; + times[1] = ds->st_mtim; + + age1 = timespec_load(&ds->st_atim); + age2 = timespec_load(&ds->st_mtim); + log_debug("Restoring access and modification time on \"%s\": %s, %s", + p, + format_timestamp_us(a, sizeof(a), age1), + format_timestamp_us(b, sizeof(b), age2)); + if (futimens(dirfd(d), times) < 0) + log_error_errno(errno, "utimensat(%s): %m", p); + } + + return r; +} + +static int path_set_perms(Item *i, const char *path) { + _cleanup_close_ int fd = -1; + struct stat st; + + assert(i); + assert(path); + + /* We open the file with O_PATH here, to make the operation + * somewhat atomic. Also there's unfortunately no fchmodat() + * with AT_SYMLINK_NOFOLLOW, hence we emulate it here via + * O_PATH. */ + + fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH); + if (fd < 0) + return log_error_errno(errno, "Adjusting owner and mode for %s failed: %m", path); + + if (fstatat(fd, "", &st, AT_EMPTY_PATH) < 0) + return log_error_errno(errno, "Failed to fstat() file %s: %m", path); + + if (S_ISLNK(st.st_mode)) + log_debug("Skipping mode an owner fix for symlink %s.", path); + else { + char fn[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; + xsprintf(fn, "/proc/self/fd/%i", fd); + + /* not using i->path directly because it may be a glob */ + if (i->mode_set) { + mode_t m = i->mode; + + if (i->mask_perms) { + if (!(st.st_mode & 0111)) + m &= ~0111; + if (!(st.st_mode & 0222)) + m &= ~0222; + if (!(st.st_mode & 0444)) + m &= ~0444; + if (!S_ISDIR(st.st_mode)) + m &= ~07000; /* remove sticky/sgid/suid bit, unless directory */ + } + + if (m == (st.st_mode & 07777)) + log_debug("\"%s\" has right mode %o", path, st.st_mode); + else { + log_debug("chmod \"%s\" to mode %o", path, m); + if (chmod(fn, m) < 0) + return log_error_errno(errno, "chmod(%s) failed: %m", path); + } + } + + if ((i->uid != st.st_uid || i->gid != st.st_gid) && + (i->uid_set || i->gid_set)) { + log_debug("chown \"%s\" to "UID_FMT"."GID_FMT, + path, + i->uid_set ? i->uid : UID_INVALID, + i->gid_set ? i->gid : GID_INVALID); + if (chown(fn, + i->uid_set ? i->uid : UID_INVALID, + i->gid_set ? i->gid : GID_INVALID) < 0) + return log_error_errno(errno, "chown(%s) failed: %m", path); + } + } + + fd = safe_close(fd); + + return label_fix(path, false, false); +} + +static int parse_xattrs_from_arg(Item *i) { + const char *p; + int r; + + assert(i); + assert(i->argument); + + p = i->argument; + + for (;;) { + _cleanup_free_ char *name = NULL, *value = NULL, *xattr = NULL, *xattr_replaced = NULL; + + r = extract_first_word(&p, &xattr, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE); + if (r < 0) + log_warning_errno(r, "Failed to parse extended attribute '%s', ignoring: %m", p); + if (r <= 0) + break; + + r = specifier_printf(xattr, specifier_table, NULL, &xattr_replaced); + if (r < 0) + return log_error_errno(r, "Failed to replace specifiers in extended attribute '%s': %m", xattr); + + r = split_pair(xattr_replaced, "=", &name, &value); + if (r < 0) { + log_warning_errno(r, "Failed to parse extended attribute, ignoring: %s", xattr); + continue; + } + + if (isempty(name) || isempty(value)) { + log_warning("Malformed extended attribute found, ignoring: %s", xattr); + continue; + } + + if (strv_push_pair(&i->xattrs, name, value) < 0) + return log_oom(); + + name = value = NULL; + } + + return 0; +} + +static int path_set_xattrs(Item *i, const char *path) { + char **name, **value; + + assert(i); + assert(path); + + STRV_FOREACH_PAIR(name, value, i->xattrs) { + int n; + + n = strlen(*value); + log_debug("Setting extended attribute '%s=%s' on %s.", *name, *value, path); + if (lsetxattr(path, *name, *value, n, 0) < 0) { + log_error("Setting extended attribute %s=%s on %s failed: %m", *name, *value, path); + return -errno; + } + } + return 0; +} + +static int parse_acls_from_arg(Item *item) { +#ifdef HAVE_ACL + int r; + + assert(item); + + /* If force (= modify) is set, we will not modify the acl + * afterwards, so the mask can be added now if necessary. */ + + r = parse_acl(item->argument, &item->acl_access, &item->acl_default, !item->force); + if (r < 0) + log_warning_errno(r, "Failed to parse ACL \"%s\": %m. Ignoring", item->argument); +#else + log_warning_errno(ENOSYS, "ACLs are not supported. Ignoring"); +#endif + + return 0; +} + +#ifdef HAVE_ACL +static int path_set_acl(const char *path, const char *pretty, acl_type_t type, acl_t acl, bool modify) { + _cleanup_(acl_free_charpp) char *t = NULL; + _cleanup_(acl_freep) acl_t dup = NULL; + int r; + + /* Returns 0 for success, positive error if already warned, + * negative error otherwise. */ + + if (modify) { + r = acls_for_file(path, type, acl, &dup); + if (r < 0) + return r; + + r = calc_acl_mask_if_needed(&dup); + if (r < 0) + return r; + } else { + dup = acl_dup(acl); + if (!dup) + return -errno; + + /* the mask was already added earlier if needed */ + } + + r = add_base_acls_if_needed(&dup, path); + if (r < 0) + return r; + + t = acl_to_any_text(dup, NULL, ',', TEXT_ABBREVIATE); + log_debug("Setting %s ACL %s on %s.", + type == ACL_TYPE_ACCESS ? "access" : "default", + strna(t), pretty); + + r = acl_set_file(path, type, dup); + if (r < 0) + /* Return positive to indicate we already warned */ + return -log_error_errno(errno, + "Setting %s ACL \"%s\" on %s failed: %m", + type == ACL_TYPE_ACCESS ? "access" : "default", + strna(t), pretty); + + return 0; +} +#endif + +static int path_set_acls(Item *item, const char *path) { + int r = 0; +#ifdef HAVE_ACL + char fn[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; + _cleanup_close_ int fd = -1; + struct stat st; + + assert(item); + assert(path); + + fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH); + if (fd < 0) + return log_error_errno(errno, "Adjusting ACL of %s failed: %m", path); + + if (fstatat(fd, "", &st, AT_EMPTY_PATH) < 0) + return log_error_errno(errno, "Failed to fstat() file %s: %m", path); + + if (S_ISLNK(st.st_mode)) { + log_debug("Skipping ACL fix for symlink %s.", path); + return 0; + } + + xsprintf(fn, "/proc/self/fd/%i", fd); + + if (item->acl_access) + r = path_set_acl(fn, path, ACL_TYPE_ACCESS, item->acl_access, item->force); + + if (r == 0 && item->acl_default) + r = path_set_acl(fn, path, ACL_TYPE_DEFAULT, item->acl_default, item->force); + + if (r > 0) + return -r; /* already warned */ + else if (r == -EOPNOTSUPP) { + log_debug_errno(r, "ACLs not supported by file system at %s", path); + return 0; + } else if (r < 0) + log_error_errno(r, "ACL operation on \"%s\" failed: %m", path); +#endif + return r; +} + +#define ATTRIBUTES_ALL \ + (FS_NOATIME_FL | \ + FS_SYNC_FL | \ + FS_DIRSYNC_FL | \ + FS_APPEND_FL | \ + FS_COMPR_FL | \ + FS_NODUMP_FL | \ + FS_EXTENT_FL | \ + FS_IMMUTABLE_FL | \ + FS_JOURNAL_DATA_FL | \ + FS_SECRM_FL | \ + FS_UNRM_FL | \ + FS_NOTAIL_FL | \ + FS_TOPDIR_FL | \ + FS_NOCOW_FL) + +static int parse_attribute_from_arg(Item *item) { + + static const struct { + char character; + unsigned value; + } attributes[] = { + { 'A', FS_NOATIME_FL }, /* do not update atime */ + { 'S', FS_SYNC_FL }, /* Synchronous updates */ + { 'D', FS_DIRSYNC_FL }, /* dirsync behaviour (directories only) */ + { 'a', FS_APPEND_FL }, /* writes to file may only append */ + { 'c', FS_COMPR_FL }, /* Compress file */ + { 'd', FS_NODUMP_FL }, /* do not dump file */ + { 'e', FS_EXTENT_FL }, /* Top of directory hierarchies*/ + { 'i', FS_IMMUTABLE_FL }, /* Immutable file */ + { 'j', FS_JOURNAL_DATA_FL }, /* Reserved for ext3 */ + { 's', FS_SECRM_FL }, /* Secure deletion */ + { 'u', FS_UNRM_FL }, /* Undelete */ + { 't', FS_NOTAIL_FL }, /* file tail should not be merged */ + { 'T', FS_TOPDIR_FL }, /* Top of directory hierarchies*/ + { 'C', FS_NOCOW_FL }, /* Do not cow file */ + }; + + enum { + MODE_ADD, + MODE_DEL, + MODE_SET + } mode = MODE_ADD; + + unsigned value = 0, mask = 0; + const char *p; + + assert(item); + + p = item->argument; + if (p) { + if (*p == '+') { + mode = MODE_ADD; + p++; + } else if (*p == '-') { + mode = MODE_DEL; + p++; + } else if (*p == '=') { + mode = MODE_SET; + p++; + } + } + + if (isempty(p) && mode != MODE_SET) { + log_error("Setting file attribute on '%s' needs an attribute specification.", item->path); + return -EINVAL; + } + + for (; p && *p ; p++) { + unsigned i, v; + + for (i = 0; i < ELEMENTSOF(attributes); i++) + if (*p == attributes[i].character) + break; + + if (i >= ELEMENTSOF(attributes)) { + log_error("Unknown file attribute '%c' on '%s'.", *p, item->path); + return -EINVAL; + } + + v = attributes[i].value; + + SET_FLAG(value, v, (mode == MODE_ADD || mode == MODE_SET)); + + mask |= v; + } + + if (mode == MODE_SET) + mask |= ATTRIBUTES_ALL; + + assert(mask != 0); + + item->attribute_mask = mask; + item->attribute_value = value; + item->attribute_set = true; + + return 0; +} + +static int path_set_attribute(Item *item, const char *path) { + _cleanup_close_ int fd = -1; + struct stat st; + unsigned f; + int r; + + if (!item->attribute_set || item->attribute_mask == 0) + return 0; + + fd = open(path, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOATIME|O_NOFOLLOW); + if (fd < 0) { + if (errno == ELOOP) + return log_error_errno(errno, "Skipping file attributes adjustment on symlink %s.", path); + + return log_error_errno(errno, "Cannot open '%s': %m", path); + } + + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Cannot stat '%s': %m", path); + + /* Issuing the file attribute ioctls on device nodes is not + * safe, as that will be delivered to the drivers, not the + * file system containing the device node. */ + if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) { + log_error("Setting file flags is only supported on regular files and directories, cannot set on '%s'.", path); + return -EINVAL; + } + + f = item->attribute_value & item->attribute_mask; + + /* Mask away directory-specific flags */ + if (!S_ISDIR(st.st_mode)) + f &= ~FS_DIRSYNC_FL; + + r = chattr_fd(fd, f, item->attribute_mask); + if (r < 0) + log_full_errno(r == -ENOTTY ? LOG_DEBUG : LOG_WARNING, + r, + "Cannot set file attribute for '%s', value=0x%08x, mask=0x%08x: %m", + path, item->attribute_value, item->attribute_mask); + + return 0; +} + +static int write_one_file(Item *i, const char *path) { + _cleanup_close_ int fd = -1; + int flags, r = 0; + struct stat st; + + assert(i); + assert(path); + + flags = i->type == CREATE_FILE ? O_CREAT|O_APPEND|O_NOFOLLOW : + i->type == TRUNCATE_FILE ? O_CREAT|O_TRUNC|O_NOFOLLOW : 0; + + RUN_WITH_UMASK(0000) { + mac_selinux_create_file_prepare(path, S_IFREG); + fd = open(path, flags|O_NDELAY|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode); + mac_selinux_create_file_clear(); + } + + if (fd < 0) { + if (i->type == WRITE_FILE && errno == ENOENT) { + log_debug_errno(errno, "Not writing \"%s\": %m", path); + return 0; + } + + r = -errno; + if (!i->argument && errno == EROFS && stat(path, &st) == 0 && + (i->type == CREATE_FILE || st.st_size == 0)) + goto check_mode; + + return log_error_errno(r, "Failed to create file %s: %m", path); + } + + if (i->argument) { + _cleanup_free_ char *unescaped = NULL, *replaced = NULL; + + log_debug("%s to \"%s\".", i->type == CREATE_FILE ? "Appending" : "Writing", path); + + r = cunescape(i->argument, 0, &unescaped); + if (r < 0) + return log_error_errno(r, "Failed to unescape parameter to write: %s", i->argument); + + r = specifier_printf(unescaped, specifier_table, NULL, &replaced); + if (r < 0) + return log_error_errno(r, "Failed to replace specifiers in parameter to write '%s': %m", unescaped); + + r = loop_write(fd, replaced, strlen(replaced), false); + if (r < 0) + return log_error_errno(r, "Failed to write file \"%s\": %m", path); + } else + log_debug("\"%s\" has been created.", path); + + fd = safe_close(fd); + + if (stat(path, &st) < 0) + return log_error_errno(errno, "stat(%s) failed: %m", path); + + check_mode: + if (!S_ISREG(st.st_mode)) { + log_error("%s is not a file.", path); + return -EEXIST; + } + + r = path_set_perms(i, path); + if (r < 0) + return r; + + return 0; +} + +typedef int (*action_t)(Item *, const char *); + +static int item_do_children(Item *i, const char *path, action_t action) { + _cleanup_closedir_ DIR *d; + int r = 0; + + assert(i); + assert(path); + + /* This returns the first error we run into, but nevertheless + * tries to go on */ + + d = opendir_nomod(path); + if (!d) + return errno == ENOENT || errno == ENOTDIR ? 0 : -errno; + + for (;;) { + _cleanup_free_ char *p = NULL; + struct dirent *de; + int q; + + errno = 0; + de = readdir(d); + if (!de) { + if (errno > 0 && r == 0) + r = -errno; + + break; + } + + if (STR_IN_SET(de->d_name, ".", "..")) + continue; + + p = strjoin(path, "/", de->d_name, NULL); + if (!p) + return -ENOMEM; + + q = action(i, p); + if (q < 0 && q != -ENOENT && r == 0) + r = q; + + if (IN_SET(de->d_type, DT_UNKNOWN, DT_DIR)) { + q = item_do_children(i, p, action); + if (q < 0 && r == 0) + r = q; + } + } + + return r; +} + +static int glob_item(Item *i, action_t action, bool recursive) { + _cleanup_globfree_ glob_t g = { + .gl_closedir = (void (*)(void *)) closedir, + .gl_readdir = (struct dirent *(*)(void *)) readdir, + .gl_opendir = (void *(*)(const char *)) opendir_nomod, + .gl_lstat = lstat, + .gl_stat = stat, + }; + int r = 0, k; + char **fn; + + errno = 0; + k = glob(i->path, GLOB_NOSORT|GLOB_BRACE|GLOB_ALTDIRFUNC, NULL, &g); + if (k != 0 && k != GLOB_NOMATCH) + return log_error_errno(errno ?: EIO, "glob(%s) failed: %m", i->path); + + STRV_FOREACH(fn, g.gl_pathv) { + k = action(i, *fn); + if (k < 0 && r == 0) + r = k; + + if (recursive) { + k = item_do_children(i, *fn, action); + if (k < 0 && r == 0) + r = k; + } + } + + return r; +} + +typedef enum { + CREATION_NORMAL, + CREATION_EXISTING, + CREATION_FORCE, + _CREATION_MODE_MAX, + _CREATION_MODE_INVALID = -1 +} CreationMode; + +static const char *creation_mode_verb_table[_CREATION_MODE_MAX] = { + [CREATION_NORMAL] = "Created", + [CREATION_EXISTING] = "Found existing", + [CREATION_FORCE] = "Created replacement", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(creation_mode_verb, CreationMode); + +static int create_item(Item *i) { + _cleanup_free_ char *resolved = NULL; + struct stat st; + int r = 0; + int q = 0; + CreationMode creation; + + assert(i); + + log_debug("Running create action for entry %c %s", (char) i->type, i->path); + + switch (i->type) { + + case IGNORE_PATH: + case IGNORE_DIRECTORY_PATH: + case REMOVE_PATH: + case RECURSIVE_REMOVE_PATH: + return 0; + + case CREATE_FILE: + case TRUNCATE_FILE: + r = write_one_file(i, i->path); + if (r < 0) + return r; + break; + + case COPY_FILES: { + r = specifier_printf(i->argument, specifier_table, NULL, &resolved); + if (r < 0) + return log_error_errno(r, "Failed to substitute specifiers in copy source %s: %m", i->argument); + + log_debug("Copying tree \"%s\" to \"%s\".", resolved, i->path); + r = copy_tree(resolved, i->path, false); + + if (r == -EROFS && stat(i->path, &st) == 0) + r = -EEXIST; + + if (r < 0) { + struct stat a, b; + + if (r != -EEXIST) + return log_error_errno(r, "Failed to copy files to %s: %m", i->path); + + if (stat(resolved, &a) < 0) + return log_error_errno(errno, "stat(%s) failed: %m", resolved); + + if (stat(i->path, &b) < 0) + return log_error_errno(errno, "stat(%s) failed: %m", i->path); + + if ((a.st_mode ^ b.st_mode) & S_IFMT) { + log_debug("Can't copy to %s, file exists already and is of different type", i->path); + return 0; + } + } + + r = path_set_perms(i, i->path); + if (r < 0) + return r; + + break; + + case WRITE_FILE: + r = glob_item(i, write_one_file, false); + if (r < 0) + return r; + + break; + + case CREATE_DIRECTORY: + case TRUNCATE_DIRECTORY: + case CREATE_SUBVOLUME: + case CREATE_SUBVOLUME_INHERIT_QUOTA: + case CREATE_SUBVOLUME_NEW_QUOTA: + RUN_WITH_UMASK(0000) + mkdir_parents_label(i->path, 0755); + + if (IN_SET(i->type, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA)) { + + if (btrfs_is_subvol(isempty(arg_root) ? "/" : arg_root) <= 0) + + /* Don't create a subvolume unless the + * root directory is one, too. We do + * this under the assumption that if + * the root directory is just a plain + * directory (i.e. very light-weight), + * we shouldn't try to split it up + * into subvolumes (i.e. more + * heavy-weight). Thus, chroot() + * environments and suchlike will get + * a full brtfs subvolume set up below + * their tree only if they + * specifically set up a btrfs + * subvolume for the root dir too. */ + + r = -ENOTTY; + else { + RUN_WITH_UMASK((~i->mode) & 0777) + r = btrfs_subvol_make(i->path); + } + } else + r = 0; + + if (IN_SET(i->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY) || r == -ENOTTY) + RUN_WITH_UMASK(0000) + r = mkdir_label(i->path, i->mode); + + if (r < 0) { + int k; + + if (r != -EEXIST && r != -EROFS) + return log_error_errno(r, "Failed to create directory or subvolume \"%s\": %m", i->path); + + k = is_dir(i->path, false); + if (k == -ENOENT && r == -EROFS) + return log_error_errno(r, "%s does not exist and cannot be created as the file system is read-only.", i->path); + if (k < 0) + return log_error_errno(k, "Failed to check if %s exists: %m", i->path); + if (!k) { + log_warning("\"%s\" already exists and is not a directory.", i->path); + return 0; + } + + creation = CREATION_EXISTING; + } else + creation = CREATION_NORMAL; + + log_debug("%s directory \"%s\".", creation_mode_verb_to_string(creation), i->path); + + if (IN_SET(i->type, CREATE_SUBVOLUME_NEW_QUOTA, CREATE_SUBVOLUME_INHERIT_QUOTA)) { + r = btrfs_subvol_auto_qgroup(i->path, 0, i->type == CREATE_SUBVOLUME_NEW_QUOTA); + if (r == -ENOTTY) + log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (unsupported fs or dir not a subvolume): %m", i->path); + else if (r == -EROFS) + log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (fs is read-only).", i->path); + else if (r == -ENOPROTOOPT) + log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (quota support is disabled).", i->path); + else if (r < 0) + q = log_error_errno(r, "Failed to adjust quota for subvolume \"%s\": %m", i->path); + else if (r > 0) + log_debug("Adjusted quota for subvolume \"%s\".", i->path); + else if (r == 0) + log_debug("Quota for subvolume \"%s\" already in place, no change made.", i->path); + } + + /* fall through */ + + case EMPTY_DIRECTORY: + r = path_set_perms(i, i->path); + if (q < 0) + return q; + if (r < 0) + return r; + + break; + + case CREATE_FIFO: + RUN_WITH_UMASK(0000) { + mac_selinux_create_file_prepare(i->path, S_IFIFO); + r = mkfifo(i->path, i->mode); + mac_selinux_create_file_clear(); + } + + if (r < 0) { + if (errno != EEXIST) + return log_error_errno(errno, "Failed to create fifo %s: %m", i->path); + + if (lstat(i->path, &st) < 0) + return log_error_errno(errno, "stat(%s) failed: %m", i->path); + + if (!S_ISFIFO(st.st_mode)) { + + if (i->force) { + RUN_WITH_UMASK(0000) { + mac_selinux_create_file_prepare(i->path, S_IFIFO); + r = mkfifo_atomic(i->path, i->mode); + mac_selinux_create_file_clear(); + } + + if (r < 0) + return log_error_errno(r, "Failed to create fifo %s: %m", i->path); + creation = CREATION_FORCE; + } else { + log_warning("\"%s\" already exists and is not a fifo.", i->path); + return 0; + } + } else + creation = CREATION_EXISTING; + } else + creation = CREATION_NORMAL; + log_debug("%s fifo \"%s\".", creation_mode_verb_to_string(creation), i->path); + + r = path_set_perms(i, i->path); + if (r < 0) + return r; + + break; + } + + case CREATE_SYMLINK: { + r = specifier_printf(i->argument, specifier_table, NULL, &resolved); + if (r < 0) + return log_error_errno(r, "Failed to substitute specifiers in symlink target %s: %m", i->argument); + + mac_selinux_create_file_prepare(i->path, S_IFLNK); + r = symlink(resolved, i->path); + mac_selinux_create_file_clear(); + + if (r < 0) { + _cleanup_free_ char *x = NULL; + + if (errno != EEXIST) + return log_error_errno(errno, "symlink(%s, %s) failed: %m", resolved, i->path); + + r = readlink_malloc(i->path, &x); + if (r < 0 || !streq(resolved, x)) { + + if (i->force) { + mac_selinux_create_file_prepare(i->path, S_IFLNK); + r = symlink_atomic(resolved, i->path); + mac_selinux_create_file_clear(); + + if (r < 0) + return log_error_errno(r, "symlink(%s, %s) failed: %m", resolved, i->path); + + creation = CREATION_FORCE; + } else { + log_debug("\"%s\" is not a symlink or does not point to the correct path.", i->path); + return 0; + } + } else + creation = CREATION_EXISTING; + } else + + creation = CREATION_NORMAL; + log_debug("%s symlink \"%s\".", creation_mode_verb_to_string(creation), i->path); + break; + } + + case CREATE_BLOCK_DEVICE: + case CREATE_CHAR_DEVICE: { + mode_t file_type; + + if (have_effective_cap(CAP_MKNOD) == 0) { + /* In a container we lack CAP_MKNOD. We + shouldn't attempt to create the device node in + that case to avoid noise, and we don't support + virtualized devices in containers anyway. */ + + log_debug("We lack CAP_MKNOD, skipping creation of device node %s.", i->path); + return 0; + } + + file_type = i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR; + + RUN_WITH_UMASK(0000) { + mac_selinux_create_file_prepare(i->path, file_type); + r = mknod(i->path, i->mode | file_type, i->major_minor); + mac_selinux_create_file_clear(); + } + + if (r < 0) { + if (errno == EPERM) { + log_debug("We lack permissions, possibly because of cgroup configuration; " + "skipping creation of device node %s.", i->path); + return 0; + } + + if (errno != EEXIST) + return log_error_errno(errno, "Failed to create device node %s: %m", i->path); + + if (lstat(i->path, &st) < 0) + return log_error_errno(errno, "stat(%s) failed: %m", i->path); + + if ((st.st_mode & S_IFMT) != file_type) { + + if (i->force) { + + RUN_WITH_UMASK(0000) { + mac_selinux_create_file_prepare(i->path, file_type); + r = mknod_atomic(i->path, i->mode | file_type, i->major_minor); + mac_selinux_create_file_clear(); + } + + if (r < 0) + return log_error_errno(r, "Failed to create device node \"%s\": %m", i->path); + creation = CREATION_FORCE; + } else { + log_debug("%s is not a device node.", i->path); + return 0; + } + } else + creation = CREATION_EXISTING; + } else + creation = CREATION_NORMAL; + + log_debug("%s %s device node \"%s\" %u:%u.", + creation_mode_verb_to_string(creation), + i->type == CREATE_BLOCK_DEVICE ? "block" : "char", + i->path, major(i->mode), minor(i->mode)); + + r = path_set_perms(i, i->path); + if (r < 0) + return r; + + break; + } + + case ADJUST_MODE: + case RELABEL_PATH: + r = glob_item(i, path_set_perms, false); + if (r < 0) + return r; + break; + + case RECURSIVE_RELABEL_PATH: + r = glob_item(i, path_set_perms, true); + if (r < 0) + return r; + break; + + case SET_XATTR: + r = glob_item(i, path_set_xattrs, false); + if (r < 0) + return r; + break; + + case RECURSIVE_SET_XATTR: + r = glob_item(i, path_set_xattrs, true); + if (r < 0) + return r; + break; + + case SET_ACL: + r = glob_item(i, path_set_acls, false); + if (r < 0) + return r; + break; + + case RECURSIVE_SET_ACL: + r = glob_item(i, path_set_acls, true); + if (r < 0) + return r; + break; + + case SET_ATTRIBUTE: + r = glob_item(i, path_set_attribute, false); + if (r < 0) + return r; + break; + + case RECURSIVE_SET_ATTRIBUTE: + r = glob_item(i, path_set_attribute, true); + if (r < 0) + return r; + break; + } + + return 0; +} + +static int remove_item_instance(Item *i, const char *instance) { + int r; + + assert(i); + + switch (i->type) { + + case REMOVE_PATH: + if (remove(instance) < 0 && errno != ENOENT) + return log_error_errno(errno, "rm(%s): %m", instance); + + break; + + case TRUNCATE_DIRECTORY: + case RECURSIVE_REMOVE_PATH: + /* FIXME: we probably should use dir_cleanup() here + * instead of rm_rf() so that 'x' is honoured. */ + log_debug("rm -rf \"%s\"", instance); + r = rm_rf(instance, (i->type == RECURSIVE_REMOVE_PATH ? REMOVE_ROOT|REMOVE_SUBVOLUME : 0) | REMOVE_PHYSICAL); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "rm_rf(%s): %m", instance); + + break; + + default: + assert_not_reached("wut?"); + } + + return 0; +} + +static int remove_item(Item *i) { + assert(i); + + log_debug("Running remove action for entry %c %s", (char) i->type, i->path); + + switch (i->type) { + + case REMOVE_PATH: + case TRUNCATE_DIRECTORY: + case RECURSIVE_REMOVE_PATH: + return glob_item(i, remove_item_instance, false); + + default: + return 0; + } +} + +static int clean_item_instance(Item *i, const char* instance) { + _cleanup_closedir_ DIR *d = NULL; + struct stat s, ps; + bool mountpoint; + usec_t cutoff, n; + char timestamp[FORMAT_TIMESTAMP_MAX]; + + assert(i); + + if (!i->age_set) + return 0; + + n = now(CLOCK_REALTIME); + if (n < i->age) + return 0; + + cutoff = n - i->age; + + d = opendir_nomod(instance); + if (!d) { + if (errno == ENOENT || errno == ENOTDIR) { + log_debug_errno(errno, "Directory \"%s\": %m", instance); + return 0; + } + + log_error_errno(errno, "Failed to open directory %s: %m", instance); + return -errno; + } + + if (fstat(dirfd(d), &s) < 0) + return log_error_errno(errno, "stat(%s) failed: %m", i->path); + + if (!S_ISDIR(s.st_mode)) { + log_error("%s is not a directory.", i->path); + return -ENOTDIR; + } + + if (fstatat(dirfd(d), "..", &ps, AT_SYMLINK_NOFOLLOW) != 0) + return log_error_errno(errno, "stat(%s/..) failed: %m", i->path); + + mountpoint = s.st_dev != ps.st_dev || s.st_ino == ps.st_ino; + + log_debug("Cleanup threshold for %s \"%s\" is %s", + mountpoint ? "mount point" : "directory", + instance, + format_timestamp_us(timestamp, sizeof(timestamp), cutoff)); + + return dir_cleanup(i, instance, d, &s, cutoff, s.st_dev, mountpoint, + MAX_DEPTH, i->keep_first_level); +} + +static int clean_item(Item *i) { + assert(i); + + log_debug("Running clean action for entry %c %s", (char) i->type, i->path); + + switch (i->type) { + case CREATE_DIRECTORY: + case CREATE_SUBVOLUME: + case CREATE_SUBVOLUME_INHERIT_QUOTA: + case CREATE_SUBVOLUME_NEW_QUOTA: + case EMPTY_DIRECTORY: + case TRUNCATE_DIRECTORY: + case IGNORE_PATH: + case COPY_FILES: + clean_item_instance(i, i->path); + return 0; + case IGNORE_DIRECTORY_PATH: + return glob_item(i, clean_item_instance, false); + default: + return 0; + } +} + +static int process_item_array(ItemArray *array); + +static int process_item(Item *i) { + int r, q, p, t = 0; + _cleanup_free_ char *prefix = NULL; + + assert(i); + + if (i->done) + return 0; + + i->done = true; + + prefix = malloc(strlen(i->path) + 1); + if (!prefix) + return log_oom(); + + PATH_FOREACH_PREFIX(prefix, i->path) { + ItemArray *j; + + j = ordered_hashmap_get(items, prefix); + if (j) { + int s; + + s = process_item_array(j); + if (s < 0 && t == 0) + t = s; + } + } + + r = arg_create ? create_item(i) : 0; + q = arg_remove ? remove_item(i) : 0; + p = arg_clean ? clean_item(i) : 0; + + return t < 0 ? t : + r < 0 ? r : + q < 0 ? q : + p; +} + +static int process_item_array(ItemArray *array) { + unsigned n; + int r = 0, k; + + assert(array); + + for (n = 0; n < array->count; n++) { + k = process_item(array->items + n); + if (k < 0 && r == 0) + r = k; + } + + return r; +} + +static void item_free_contents(Item *i) { + assert(i); + free(i->path); + free(i->argument); + strv_free(i->xattrs); + +#ifdef HAVE_ACL + acl_free(i->acl_access); + acl_free(i->acl_default); +#endif +} + +static void item_array_free(ItemArray *a) { + unsigned n; + + if (!a) + return; + + for (n = 0; n < a->count; n++) + item_free_contents(a->items + n); + free(a->items); + free(a); +} + +static int item_compare(const void *a, const void *b) { + const Item *x = a, *y = b; + + /* Make sure that the ownership taking item is put first, so + * that we first create the node, and then can adjust it */ + + if (takes_ownership(x->type) && !takes_ownership(y->type)) + return -1; + if (!takes_ownership(x->type) && takes_ownership(y->type)) + return 1; + + return (int) x->type - (int) y->type; +} + +static bool item_compatible(Item *a, Item *b) { + assert(a); + assert(b); + assert(streq(a->path, b->path)); + + if (takes_ownership(a->type) && takes_ownership(b->type)) + /* check if the items are the same */ + return streq_ptr(a->argument, b->argument) && + + a->uid_set == b->uid_set && + a->uid == b->uid && + + a->gid_set == b->gid_set && + a->gid == b->gid && + + a->mode_set == b->mode_set && + a->mode == b->mode && + + a->age_set == b->age_set && + a->age == b->age && + + a->mask_perms == b->mask_perms && + + a->keep_first_level == b->keep_first_level && + + a->major_minor == b->major_minor; + + return true; +} + +static bool should_include_path(const char *path) { + char **prefix; + + STRV_FOREACH(prefix, arg_exclude_prefixes) + if (path_startswith(path, *prefix)) { + log_debug("Entry \"%s\" matches exclude prefix \"%s\", skipping.", + path, *prefix); + return false; + } + + STRV_FOREACH(prefix, arg_include_prefixes) + if (path_startswith(path, *prefix)) { + log_debug("Entry \"%s\" matches include prefix \"%s\".", path, *prefix); + return true; + } + + /* no matches, so we should include this path only if we + * have no whitelist at all */ + if (strv_length(arg_include_prefixes) == 0) + return true; + + log_debug("Entry \"%s\" does not match any include prefix, skipping.", path); + return false; +} + +static int parse_line(const char *fname, unsigned line, const char *buffer) { + + _cleanup_free_ char *action = NULL, *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL; + _cleanup_(item_free_contents) Item i = {}; + ItemArray *existing; + OrderedHashmap *h; + int r, pos; + bool force = false, boot = false; + + assert(fname); + assert(line >= 1); + assert(buffer); + + r = extract_many_words( + &buffer, + NULL, + EXTRACT_QUOTES, + &action, + &path, + &mode, + &user, + &group, + &age, + NULL); + if (r < 0) + return log_error_errno(r, "[%s:%u] Failed to parse line: %m", fname, line); + else if (r < 2) { + log_error("[%s:%u] Syntax error.", fname, line); + return -EIO; + } + + if (!isempty(buffer) && !streq(buffer, "-")) { + i.argument = strdup(buffer); + if (!i.argument) + return log_oom(); + } + + if (isempty(action)) { + log_error("[%s:%u] Command too short '%s'.", fname, line, action); + return -EINVAL; + } + + for (pos = 1; action[pos]; pos++) { + if (action[pos] == '!' && !boot) + boot = true; + else if (action[pos] == '+' && !force) + force = true; + else { + log_error("[%s:%u] Unknown modifiers in command '%s'", + fname, line, action); + return -EINVAL; + } + } + + if (boot && !arg_boot) { + log_debug("Ignoring entry %s \"%s\" because --boot is not specified.", + action, path); + return 0; + } + + i.type = action[0]; + i.force = force; + + r = specifier_printf(path, specifier_table, NULL, &i.path); + if (r < 0) { + log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, path); + return r; + } + + switch (i.type) { + + case CREATE_DIRECTORY: + case CREATE_SUBVOLUME: + case CREATE_SUBVOLUME_INHERIT_QUOTA: + case CREATE_SUBVOLUME_NEW_QUOTA: + case EMPTY_DIRECTORY: + case TRUNCATE_DIRECTORY: + case CREATE_FIFO: + case IGNORE_PATH: + case IGNORE_DIRECTORY_PATH: + case REMOVE_PATH: + case RECURSIVE_REMOVE_PATH: + case ADJUST_MODE: + case RELABEL_PATH: + case RECURSIVE_RELABEL_PATH: + if (i.argument) + log_warning("[%s:%u] %c lines don't take argument fields, ignoring.", fname, line, i.type); + + break; + + case CREATE_FILE: + case TRUNCATE_FILE: + break; + + case CREATE_SYMLINK: + if (!i.argument) { + i.argument = strappend("/usr/share/factory/", i.path); + if (!i.argument) + return log_oom(); + } + break; + + case WRITE_FILE: + if (!i.argument) { + log_error("[%s:%u] Write file requires argument.", fname, line); + return -EBADMSG; + } + break; + + case COPY_FILES: + if (!i.argument) { + i.argument = strappend("/usr/share/factory/", i.path); + if (!i.argument) + return log_oom(); + } else if (!path_is_absolute(i.argument)) { + log_error("[%s:%u] Source path is not absolute.", fname, line); + return -EBADMSG; + } + + path_kill_slashes(i.argument); + break; + + case CREATE_CHAR_DEVICE: + case CREATE_BLOCK_DEVICE: { + unsigned major, minor; + + if (!i.argument) { + log_error("[%s:%u] Device file requires argument.", fname, line); + return -EBADMSG; + } + + if (sscanf(i.argument, "%u:%u", &major, &minor) != 2) { + log_error("[%s:%u] Can't parse device file major/minor '%s'.", fname, line, i.argument); + return -EBADMSG; + } + + i.major_minor = makedev(major, minor); + break; + } + + case SET_XATTR: + case RECURSIVE_SET_XATTR: + if (!i.argument) { + log_error("[%s:%u] Set extended attribute requires argument.", fname, line); + return -EBADMSG; + } + r = parse_xattrs_from_arg(&i); + if (r < 0) + return r; + break; + + case SET_ACL: + case RECURSIVE_SET_ACL: + if (!i.argument) { + log_error("[%s:%u] Set ACLs requires argument.", fname, line); + return -EBADMSG; + } + r = parse_acls_from_arg(&i); + if (r < 0) + return r; + break; + + case SET_ATTRIBUTE: + case RECURSIVE_SET_ATTRIBUTE: + if (!i.argument) { + log_error("[%s:%u] Set file attribute requires argument.", fname, line); + return -EBADMSG; + } + r = parse_attribute_from_arg(&i); + if (r < 0) + return r; + break; + + default: + log_error("[%s:%u] Unknown command type '%c'.", fname, line, (char) i.type); + return -EBADMSG; + } + + if (!path_is_absolute(i.path)) { + log_error("[%s:%u] Path '%s' not absolute.", fname, line, i.path); + return -EBADMSG; + } + + path_kill_slashes(i.path); + + if (!should_include_path(i.path)) + return 0; + + if (arg_root) { + char *p; + + p = prefix_root(arg_root, i.path); + if (!p) + return log_oom(); + + free(i.path); + i.path = p; + } + + if (!isempty(user) && !streq(user, "-")) { + const char *u = user; + + r = get_user_creds(&u, &i.uid, NULL, NULL, NULL); + if (r < 0) { + log_error("[%s:%u] Unknown user '%s'.", fname, line, user); + return r; + } + + i.uid_set = true; + } + + if (!isempty(group) && !streq(group, "-")) { + const char *g = group; + + r = get_group_creds(&g, &i.gid); + if (r < 0) { + log_error("[%s:%u] Unknown group '%s'.", fname, line, group); + return r; + } + + i.gid_set = true; + } + + if (!isempty(mode) && !streq(mode, "-")) { + const char *mm = mode; + unsigned m; + + if (*mm == '~') { + i.mask_perms = true; + mm++; + } + + if (parse_mode(mm, &m) < 0) { + log_error("[%s:%u] Invalid mode '%s'.", fname, line, mode); + return -EBADMSG; + } + + i.mode = m; + i.mode_set = true; + } else + i.mode = IN_SET(i.type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA) ? 0755 : 0644; + + if (!isempty(age) && !streq(age, "-")) { + const char *a = age; + + if (*a == '~') { + i.keep_first_level = true; + a++; + } + + if (parse_sec(a, &i.age) < 0) { + log_error("[%s:%u] Invalid age '%s'.", fname, line, age); + return -EBADMSG; + } + + i.age_set = true; + } + + h = needs_glob(i.type) ? globs : items; + + existing = ordered_hashmap_get(h, i.path); + if (existing) { + unsigned n; + + for (n = 0; n < existing->count; n++) { + if (!item_compatible(existing->items + n, &i)) { + log_warning("[%s:%u] Duplicate line for path \"%s\", ignoring.", + fname, line, i.path); + return 0; + } + } + } else { + existing = new0(ItemArray, 1); + r = ordered_hashmap_put(h, i.path, existing); + if (r < 0) + return log_oom(); + } + + if (!GREEDY_REALLOC(existing->items, existing->size, existing->count + 1)) + return log_oom(); + + memcpy(existing->items + existing->count++, &i, sizeof(i)); + + /* Sort item array, to enforce stable ordering of application */ + qsort_safe(existing->items, existing->count, sizeof(Item), item_compare); + + zero(i); + return 0; +} + +static void help(void) { + printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" + "Creates, deletes and cleans up volatile and temporary files and directories.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --create Create marked files/directories\n" + " --clean Clean up marked directories\n" + " --remove Remove marked files/directories\n" + " --boot Execute actions only safe at boot\n" + " --prefix=PATH Only apply rules with the specified prefix\n" + " --exclude-prefix=PATH Ignore rules with the specified prefix\n" + " --root=PATH Operate on an alternate filesystem root\n", + program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_CREATE, + ARG_CLEAN, + ARG_REMOVE, + ARG_BOOT, + ARG_PREFIX, + ARG_EXCLUDE_PREFIX, + ARG_ROOT, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "create", no_argument, NULL, ARG_CREATE }, + { "clean", no_argument, NULL, ARG_CLEAN }, + { "remove", no_argument, NULL, ARG_REMOVE }, + { "boot", no_argument, NULL, ARG_BOOT }, + { "prefix", required_argument, NULL, ARG_PREFIX }, + { "exclude-prefix", required_argument, NULL, ARG_EXCLUDE_PREFIX }, + { "root", required_argument, NULL, ARG_ROOT }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_CREATE: + arg_create = true; + break; + + case ARG_CLEAN: + arg_clean = true; + break; + + case ARG_REMOVE: + arg_remove = true; + break; + + case ARG_BOOT: + arg_boot = true; + break; + + case ARG_PREFIX: + if (strv_push(&arg_include_prefixes, optarg) < 0) + return log_oom(); + break; + + case ARG_EXCLUDE_PREFIX: + if (strv_push(&arg_exclude_prefixes, optarg) < 0) + return log_oom(); + break; + + case ARG_ROOT: + r = parse_path_argument_and_warn(optarg, true, &arg_root); + if (r < 0) + return r; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (!arg_clean && !arg_create && !arg_remove) { + log_error("You need to specify at least one of --clean, --create or --remove."); + return -EINVAL; + } + + return 1; +} + +static int read_config_file(const char *fn, bool ignore_enoent) { + _cleanup_fclose_ FILE *_f = NULL; + FILE *f; + char line[LINE_MAX]; + Iterator iterator; + unsigned v = 0; + Item *i; + int r; + + assert(fn); + + if (streq(fn, "-")) { + log_debug("Reading config from stdin."); + fn = ""; + f = stdin; + } else { + r = search_and_fopen_nulstr(fn, "re", arg_root, conf_file_dirs, &_f); + if (r < 0) { + if (ignore_enoent && r == -ENOENT) { + log_debug_errno(r, "Failed to open \"%s\", ignoring: %m", fn); + return 0; + } + + return log_error_errno(r, "Failed to open '%s': %m", fn); + } + log_debug("Reading config file \"%s\".", fn); + f = _f; + } + + FOREACH_LINE(line, f, break) { + char *l; + int k; + + v++; + + l = strstrip(line); + if (*l == '#' || *l == 0) + continue; + + k = parse_line(fn, v, l); + if (k < 0 && r == 0) + r = k; + } + + /* we have to determine age parameter for each entry of type X */ + ORDERED_HASHMAP_FOREACH(i, globs, iterator) { + Iterator iter; + Item *j, *candidate_item = NULL; + + if (i->type != IGNORE_DIRECTORY_PATH) + continue; + + ORDERED_HASHMAP_FOREACH(j, items, iter) { + if (!IN_SET(j->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA)) + continue; + + if (path_equal(j->path, i->path)) { + candidate_item = j; + break; + } + + if ((!candidate_item && path_startswith(i->path, j->path)) || + (candidate_item && path_startswith(j->path, candidate_item->path) && (fnmatch(i->path, j->path, FNM_PATHNAME | FNM_PERIOD) == 0))) + candidate_item = j; + } + + if (candidate_item && candidate_item->age_set) { + i->age = candidate_item->age; + i->age_set = true; + } + } + + if (ferror(f)) { + log_error_errno(errno, "Failed to read from file %s: %m", fn); + if (r == 0) + r = -EIO; + } + + return r; +} + +int main(int argc, char *argv[]) { + int r, k; + ItemArray *a; + Iterator iterator; + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + mac_selinux_init(); + + items = ordered_hashmap_new(&string_hash_ops); + globs = ordered_hashmap_new(&string_hash_ops); + + if (!items || !globs) { + r = log_oom(); + goto finish; + } + + r = 0; + + if (optind < argc) { + int j; + + for (j = optind; j < argc; j++) { + k = read_config_file(argv[j], false); + if (k < 0 && r == 0) + r = k; + } + + } else { + _cleanup_strv_free_ char **files = NULL; + char **f; + + r = conf_files_list_nulstr(&files, ".conf", arg_root, conf_file_dirs); + if (r < 0) { + log_error_errno(r, "Failed to enumerate tmpfiles.d files: %m"); + goto finish; + } + + STRV_FOREACH(f, files) { + k = read_config_file(*f, true); + if (k < 0 && r == 0) + r = k; + } + } + + /* The non-globbing ones usually create things, hence we apply + * them first */ + ORDERED_HASHMAP_FOREACH(a, items, iterator) { + k = process_item_array(a); + if (k < 0 && r == 0) + r = k; + } + + /* The globbing ones usually alter things, hence we apply them + * second. */ + ORDERED_HASHMAP_FOREACH(a, globs, iterator) { + k = process_item_array(a); + if (k < 0 && r == 0) + r = k; + } + +finish: + while ((a = ordered_hashmap_steal_first(items))) + item_array_free(a); + + while ((a = ordered_hashmap_steal_first(globs))) + item_array_free(a); + + ordered_hashmap_free(items); + ordered_hashmap_free(globs); + + free(arg_include_prefixes); + free(arg_exclude_prefixes); + free(arg_root); + + set_free_free(unix_sockets); + + mac_selinux_finish(); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-tty-ask-password-agent/Makefile b/src/systemd-tty-ask-password-agent/Makefile new file mode 100644 index 0000000000..37b51da33b --- /dev/null +++ b/src/systemd-tty-ask-password-agent/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_tty_ask_password_agent_SOURCES = \ + src/tty-ask-password-agent/tty-ask-password-agent.c + +systemd_tty_ask_password_agent_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-tty-ask-password-agent/tty-ask-password-agent.c b/src/systemd-tty-ask-password-agent/tty-ask-password-agent.c new file mode 100644 index 0000000000..ee879c7b89 --- /dev/null +++ b/src/systemd-tty-ask-password-agent/tty-ask-password-agent.c @@ -0,0 +1,680 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "ask-password-api.h" +#include "conf-parser.h" +#include "def.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "mkdir.h" +#include "path-util.h" +#include "process-util.h" +#include "signal-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "util.h" +#include "utmp-wtmp.h" + +static enum { + ACTION_LIST, + ACTION_QUERY, + ACTION_WATCH, + ACTION_WALL +} arg_action = ACTION_QUERY; + +static bool arg_plymouth = false; +static bool arg_console = false; + +static int ask_password_plymouth( + const char *message, + usec_t until, + AskPasswordFlags flags, + const char *flag_file, + char ***ret) { + + static const union sockaddr_union sa = PLYMOUTH_SOCKET; + _cleanup_close_ int fd = -1, notify = -1; + _cleanup_free_ char *packet = NULL; + ssize_t k; + int r, n; + struct pollfd pollfd[2] = {}; + char buffer[LINE_MAX]; + size_t p = 0; + enum { + POLL_SOCKET, + POLL_INOTIFY + }; + + assert(ret); + + if (flag_file) { + notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK); + if (notify < 0) + return -errno; + + r = inotify_add_watch(notify, flag_file, IN_ATTRIB); /* for the link count */ + if (r < 0) + return -errno; + } + + fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) + return -errno; + + r = connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); + if (r < 0) + return -errno; + + if (flags & ASK_PASSWORD_ACCEPT_CACHED) { + packet = strdup("c"); + n = 1; + } else if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) + packet = NULL; + if (!packet) + return -ENOMEM; + + r = loop_write(fd, packet, n + 1, true); + if (r < 0) + return r; + + pollfd[POLL_SOCKET].fd = fd; + pollfd[POLL_SOCKET].events = POLLIN; + pollfd[POLL_INOTIFY].fd = notify; + pollfd[POLL_INOTIFY].events = POLLIN; + + for (;;) { + int sleep_for = -1, j; + + if (until > 0) { + usec_t y; + + y = now(CLOCK_MONOTONIC); + + if (y > until) { + r = -ETIME; + goto finish; + } + + sleep_for = (int) ((until - y) / USEC_PER_MSEC); + } + + if (flag_file && access(flag_file, F_OK) < 0) { + r = -errno; + goto finish; + } + + j = poll(pollfd, notify >= 0 ? 2 : 1, sleep_for); + if (j < 0) { + if (errno == EINTR) + continue; + + r = -errno; + goto finish; + } else if (j == 0) { + r = -ETIME; + goto finish; + } + + if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0) + flush_fd(notify); + + if (pollfd[POLL_SOCKET].revents == 0) + continue; + + k = read(fd, buffer + p, sizeof(buffer) - p); + if (k < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + + r = -errno; + goto finish; + } else if (k == 0) { + r = -EIO; + goto finish; + } + + p += k; + + if (p < 1) + continue; + + if (buffer[0] == 5) { + + if (flags & ASK_PASSWORD_ACCEPT_CACHED) { + /* Hmm, first try with cached + * passwords failed, so let's retry + * with a normal password request */ + packet = mfree(packet); + + if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) { + r = -ENOMEM; + goto finish; + } + + r = loop_write(fd, packet, n+1, true); + if (r < 0) + goto finish; + + flags &= ~ASK_PASSWORD_ACCEPT_CACHED; + p = 0; + continue; + } + + /* No password, because UI not shown */ + r = -ENOENT; + goto finish; + + } else if (buffer[0] == 2 || buffer[0] == 9) { + uint32_t size; + char **l; + + /* One or more answers */ + if (p < 5) + continue; + + memcpy(&size, buffer+1, sizeof(size)); + size = le32toh(size); + if (size + 5 > sizeof(buffer)) { + r = -EIO; + goto finish; + } + + if (p-5 < size) + continue; + + l = strv_parse_nulstr(buffer + 5, size); + if (!l) { + r = -ENOMEM; + goto finish; + } + + *ret = l; + break; + + } else { + /* Unknown packet */ + r = -EIO; + goto finish; + } + } + + r = 0; + +finish: + memory_erase(buffer, sizeof(buffer)); + return r; +} + +static int send_passwords(const char *socket_name, char **passwords) { + _cleanup_free_ char *packet = NULL; + _cleanup_close_ int socket_fd = -1; + union sockaddr_union sa = { .un.sun_family = AF_UNIX }; + size_t packet_length = 1; + char **p, *d; + int r; + + assert(socket_name); + + STRV_FOREACH(p, passwords) + packet_length += strlen(*p) + 1; + + packet = new(char, packet_length); + if (!packet) + return -ENOMEM; + + packet[0] = '+'; + + d = packet + 1; + STRV_FOREACH(p, passwords) + d = stpcpy(d, *p) + 1; + + socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); + if (socket_fd < 0) { + r = log_debug_errno(errno, "socket(): %m"); + goto finish; + } + + strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path)); + + r = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, SOCKADDR_UN_LEN(sa.un)); + if (r < 0) + r = log_debug_errno(errno, "sendto(): %m"); + +finish: + memory_erase(packet, packet_length); + return r; +} + +static int parse_password(const char *filename, char **wall) { + _cleanup_free_ char *socket_name = NULL, *message = NULL; + bool accept_cached = false, echo = false; + uint64_t not_after = 0; + unsigned pid = 0; + + const ConfigTableItem items[] = { + { "Ask", "Socket", config_parse_string, 0, &socket_name }, + { "Ask", "NotAfter", config_parse_uint64, 0, ¬_after }, + { "Ask", "Message", config_parse_string, 0, &message }, + { "Ask", "PID", config_parse_unsigned, 0, &pid }, + { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached }, + { "Ask", "Echo", config_parse_bool, 0, &echo }, + {} + }; + + int r; + + assert(filename); + + r = config_parse(NULL, filename, NULL, + NULL, + config_item_table_lookup, items, + true, false, true, NULL); + if (r < 0) + return r; + + if (!socket_name) { + log_error("Invalid password file %s", filename); + return -EBADMSG; + } + + if (not_after > 0 && now(CLOCK_MONOTONIC) > not_after) + return 0; + + if (pid > 0 && !pid_is_alive(pid)) + return 0; + + if (arg_action == ACTION_LIST) + printf("'%s' (PID %u)\n", message, pid); + + else if (arg_action == ACTION_WALL) { + char *_wall; + + if (asprintf(&_wall, + "%s%sPassword entry required for \'%s\' (PID %u).\r\n" + "Please enter password with the systemd-tty-ask-password-agent tool!", + strempty(*wall), + *wall ? "\r\n\r\n" : "", + message, + pid) < 0) + return log_oom(); + + free(*wall); + *wall = _wall; + + } else { + _cleanup_strv_free_erase_ char **passwords = NULL; + + assert(arg_action == ACTION_QUERY || + arg_action == ACTION_WATCH); + + if (access(socket_name, W_OK) < 0) { + if (arg_action == ACTION_QUERY) + log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid); + + return 0; + } + + if (arg_plymouth) + r = ask_password_plymouth(message, not_after, accept_cached ? ASK_PASSWORD_ACCEPT_CACHED : 0, filename, &passwords); + else { + char *password = NULL; + int tty_fd = -1; + + if (arg_console) { + tty_fd = acquire_terminal("/dev/console", false, false, false, USEC_INFINITY); + if (tty_fd < 0) + return log_error_errno(tty_fd, "Failed to acquire /dev/console: %m"); + + r = reset_terminal_fd(tty_fd, true); + if (r < 0) + log_warning_errno(r, "Failed to reset terminal, ignoring: %m"); + } + + r = ask_password_tty(message, NULL, not_after, echo ? ASK_PASSWORD_ECHO : 0, filename, &password); + + if (arg_console) { + tty_fd = safe_close(tty_fd); + release_terminal(); + } + + if (r >= 0) + r = strv_push(&passwords, password); + + if (r < 0) + string_free_erase(password); + } + + /* If the query went away, that's OK */ + if (IN_SET(r, -ETIME, -ENOENT)) + return 0; + + if (r < 0) + return log_error_errno(r, "Failed to query password: %m"); + + r = send_passwords(socket_name, passwords); + if (r < 0) + return log_error_errno(r, "Failed to send: %m"); + } + + return 0; +} + +static int wall_tty_block(void) { + _cleanup_free_ char *p = NULL; + dev_t devnr; + int fd, r; + + r = get_ctty_devnr(0, &devnr); + if (r == -ENXIO) /* We have no controlling tty */ + return -ENOTTY; + if (r < 0) + return log_error_errno(r, "Failed to get controlling TTY: %m"); + + if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0) + return log_oom(); + + mkdir_parents_label(p, 0700); + mkfifo(p, 0600); + + fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (fd < 0) + return log_debug_errno(errno, "Failed to open %s: %m", p); + + return fd; +} + +static bool wall_tty_match(const char *path, void *userdata) { + _cleanup_free_ char *p = NULL; + _cleanup_close_ int fd = -1; + struct stat st; + + if (!path_is_absolute(path)) + path = strjoina("/dev/", path); + + if (lstat(path, &st) < 0) { + log_debug_errno(errno, "Failed to stat %s: %m", path); + return true; + } + + if (!S_ISCHR(st.st_mode)) { + log_debug("%s is not a character device.", path); + return true; + } + + /* We use named pipes to ensure that wall messages suggesting + * password entry are not printed over password prompts + * already shown. We use the fact here that opening a pipe in + * non-blocking mode for write-only will succeed only if + * there's some writer behind it. Using pipes has the + * advantage that the block will automatically go away if the + * process dies. */ + + if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0) { + log_oom(); + return true; + } + + fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (fd < 0) { + log_debug_errno(errno, "Failed top open the wall pipe: %m"); + return 1; + } + + /* What, we managed to open the pipe? Then this tty is filtered. */ + return 0; +} + +static int show_passwords(void) { + _cleanup_closedir_ DIR *d; + struct dirent *de; + int r = 0; + + d = opendir("/run/systemd/ask-password"); + if (!d) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open /run/systemd/ask-password: %m"); + } + + FOREACH_DIRENT_ALL(de, d, return log_error_errno(errno, "Failed to read directory: %m")) { + _cleanup_free_ char *p = NULL, *wall = NULL; + int q; + + /* We only support /dev on tmpfs, hence we can rely on + * d_type to be reliable */ + + if (de->d_type != DT_REG) + continue; + + if (hidden_or_backup_file(de->d_name)) + continue; + + if (!startswith(de->d_name, "ask.")) + continue; + + p = strappend("/run/systemd/ask-password/", de->d_name); + if (!p) + return log_oom(); + + q = parse_password(p, &wall); + if (q < 0 && r == 0) + r = q; + + if (wall) + (void) utmp_wall(wall, NULL, NULL, wall_tty_match, NULL); + } + + return r; +} + +static int watch_passwords(void) { + enum { + FD_INOTIFY, + FD_SIGNAL, + _FD_MAX + }; + + _cleanup_close_ int notify = -1, signal_fd = -1, tty_block_fd = -1; + struct pollfd pollfd[_FD_MAX] = {}; + sigset_t mask; + int r; + + tty_block_fd = wall_tty_block(); + + (void) mkdir_p_label("/run/systemd/ask-password", 0755); + + notify = inotify_init1(IN_CLOEXEC); + if (notify < 0) + return log_error_errno(errno, "Failed to allocate directory watch: %m"); + + if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) + return log_error_errno(errno, "Failed to add /run/systemd/ask-password to directory watch: %m"); + + assert_se(sigemptyset(&mask) >= 0); + assert_se(sigset_add_many(&mask, SIGINT, SIGTERM, -1) >= 0); + assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) >= 0); + + signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); + if (signal_fd < 0) + return log_error_errno(errno, "Failed to allocate signal file descriptor: %m"); + + pollfd[FD_INOTIFY].fd = notify; + pollfd[FD_INOTIFY].events = POLLIN; + pollfd[FD_SIGNAL].fd = signal_fd; + pollfd[FD_SIGNAL].events = POLLIN; + + for (;;) { + r = show_passwords(); + if (r < 0) + log_error_errno(r, "Failed to show password: %m"); + + if (poll(pollfd, _FD_MAX, -1) < 0) { + if (errno == EINTR) + continue; + + return -errno; + } + + if (pollfd[FD_INOTIFY].revents != 0) + (void) flush_fd(notify); + + if (pollfd[FD_SIGNAL].revents != 0) + break; + } + + return 0; +} + +static void help(void) { + printf("%s [OPTIONS...]\n\n" + "Process system password requests.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --list Show pending password requests\n" + " --query Process pending password requests\n" + " --watch Continuously process password requests\n" + " --wall Continuously forward password requests to wall\n" + " --plymouth Ask question with Plymouth instead of on TTY\n" + " --console Ask question on /dev/console instead of current TTY\n", + program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_LIST = 0x100, + ARG_QUERY, + ARG_WATCH, + ARG_WALL, + ARG_PLYMOUTH, + ARG_CONSOLE, + ARG_VERSION + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "list", no_argument, NULL, ARG_LIST }, + { "query", no_argument, NULL, ARG_QUERY }, + { "watch", no_argument, NULL, ARG_WATCH }, + { "wall", no_argument, NULL, ARG_WALL }, + { "plymouth", no_argument, NULL, ARG_PLYMOUTH }, + { "console", no_argument, NULL, ARG_CONSOLE }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_LIST: + arg_action = ACTION_LIST; + break; + + case ARG_QUERY: + arg_action = ACTION_QUERY; + break; + + case ARG_WATCH: + arg_action = ACTION_WATCH; + break; + + case ARG_WALL: + arg_action = ACTION_WALL; + break; + + case ARG_PLYMOUTH: + arg_plymouth = true; + break; + + case ARG_CONSOLE: + arg_console = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (optind != argc) { + log_error("%s takes no arguments.", program_invocation_short_name); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (arg_console) { + (void) setsid(); + (void) release_terminal(); + } + + if (IN_SET(arg_action, ACTION_WATCH, ACTION_WALL)) + r = watch_passwords(); + else + r = show_passwords(); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-update-done/Makefile b/src/systemd-update-done/Makefile new file mode 100644 index 0000000000..ae592db650 --- /dev/null +++ b/src/systemd-update-done/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_update_done_SOURCES = \ + src/update-done/update-done.c + +systemd_update_done_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-update-done/update-done.c b/src/systemd-update-done/update-done.c new file mode 100644 index 0000000000..da306a4444 --- /dev/null +++ b/src/systemd-update-done/update-done.c @@ -0,0 +1,115 @@ +/*** + 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 "fd-util.h" +#include "io-util.h" +#include "selinux-util.h" +#include "util.h" + +#define MESSAGE \ + "This file was created by systemd-update-done. Its only \n" \ + "purpose is to hold a timestamp of the time this directory\n" \ + "was updated. See systemd-update-done.service(8).\n" + +static int apply_timestamp(const char *path, struct timespec *ts) { + struct timespec twice[2] = { + *ts, + *ts + }; + struct stat st; + + assert(path); + assert(ts); + + if (stat(path, &st) >= 0) { + /* Is the timestamp file already newer than the OS? If + * so, there's nothing to do. We ignore the nanosecond + * component of the timestamp, since some file systems + * do not support any better accuracy than 1s and we + * have no way to identify the accuracy + * available. Most notably ext4 on small disks (where + * 128 byte inodes are used) does not support better + * accuracy than 1s. */ + if (st.st_mtim.tv_sec > ts->tv_sec) + return 0; + + /* It is older? Then let's update it */ + if (utimensat(AT_FDCWD, path, twice, AT_SYMLINK_NOFOLLOW) < 0) { + + if (errno == EROFS) + return log_debug("Can't update timestamp file %s, file system is read-only.", path); + + return log_error_errno(errno, "Failed to update timestamp on %s: %m", path); + } + + } else if (errno == ENOENT) { + _cleanup_close_ int fd = -1; + int r; + + /* The timestamp file doesn't exist yet? Then let's create it. */ + + r = mac_selinux_create_file_prepare(path, S_IFREG); + if (r < 0) + return log_error_errno(r, "Failed to set SELinux context for %s: %m", path); + + fd = open(path, O_CREAT|O_EXCL|O_WRONLY|O_TRUNC|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0644); + mac_selinux_create_file_clear(); + + if (fd < 0) { + if (errno == EROFS) + return log_debug("Can't create timestamp file %s, file system is read-only.", path); + + return log_error_errno(errno, "Failed to create timestamp file %s: %m", path); + } + + (void) loop_write(fd, MESSAGE, strlen(MESSAGE), false); + + if (futimens(fd, twice) < 0) + return log_error_errno(errno, "Failed to update timestamp on %s: %m", path); + } else + log_error_errno(errno, "Failed to stat() timestamp file %s: %m", path); + + return 0; +} + +int main(int argc, char *argv[]) { + struct stat st; + int r, q = 0; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + if (stat("/usr", &st) < 0) { + log_error_errno(errno, "Failed to stat /usr: %m"); + return EXIT_FAILURE; + } + + r = mac_selinux_init(); + if (r < 0) { + log_error_errno(r, "SELinux setup failed: %m"); + goto finish; + } + + r = apply_timestamp("/etc/.updated", &st.st_mtim); + q = apply_timestamp("/var/.updated", &st.st_mtim); + +finish: + return r < 0 || q < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-update-utmp/Makefile b/src/systemd-update-utmp/Makefile new file mode 100644 index 0000000000..182c7534e7 --- /dev/null +++ b/src/systemd-update-utmp/Makefile @@ -0,0 +1,37 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_update_utmp_SOURCES = \ + src/update-utmp/update-utmp.c + +systemd_update_utmp_CFLAGS = \ + $(AM_CFLAGS) \ + $(AUDIT_CFLAGS) + +systemd_update_utmp_LDADD = \ + libshared.la \ + $(AUDIT_LIBS) + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-update-utmp/update-utmp.c b/src/systemd-update-utmp/update-utmp.c new file mode 100644 index 0000000000..fedcaef91c --- /dev/null +++ b/src/systemd-update-utmp/update-utmp.c @@ -0,0 +1,283 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include + +#ifdef HAVE_AUDIT +#include +#endif + +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "formats-util.h" +#include "log.h" +#include "macro.h" +#include "special.h" +#include "unit-name.h" +#include "util.h" +#include "utmp-wtmp.h" + +typedef struct Context { + sd_bus *bus; +#ifdef HAVE_AUDIT + int audit_fd; +#endif +} Context; + +static usec_t get_startup_time(Context *c) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + usec_t t = 0; + int r; + + assert(c); + + r = sd_bus_get_property_trivial( + c->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UserspaceTimestamp", + &error, + 't', &t); + if (r < 0) { + log_error_errno(r, "Failed to get timestamp: %s", bus_error_message(&error, r)); + return 0; + } + + return t; +} + +static int get_current_runlevel(Context *c) { + static const struct { + const int runlevel; + const char *special; + } table[] = { + /* The first target of this list that is active or has + * a job scheduled wins. We prefer runlevels 5 and 3 + * here over the others, since these are the main + * runlevels used on Fedora. It might make sense to + * change the order on some distributions. */ + { '5', SPECIAL_GRAPHICAL_TARGET }, + { '3', SPECIAL_MULTI_USER_TARGET }, + { '1', SPECIAL_RESCUE_TARGET }, + }; + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + unsigned i; + + assert(c); + + for (i = 0; i < ELEMENTSOF(table); i++) { + _cleanup_free_ char *state = NULL, *path = NULL; + + path = unit_dbus_path_from_name(table[i].special); + if (!path) + return log_oom(); + + r = sd_bus_get_property_string( + c->bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "ActiveState", + &error, + &state); + if (r < 0) + return log_warning_errno(r, "Failed to get state: %s", bus_error_message(&error, r)); + + if (streq(state, "active") || streq(state, "reloading")) + return table[i].runlevel; + } + + return 0; +} + +static int on_reboot(Context *c) { + int r = 0, q; + usec_t t; + + assert(c); + + /* We finished start-up, so let's write the utmp + * record and send the audit msg */ + +#ifdef HAVE_AUDIT + if (c->audit_fd >= 0) + if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_BOOT, "", "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 && + errno != EPERM) { + r = log_error_errno(errno, "Failed to send audit message: %m"); + } +#endif + + /* If this call fails it will return 0, which + * utmp_put_reboot() will then fix to the current time */ + t = get_startup_time(c); + + q = utmp_put_reboot(t); + if (q < 0) { + log_error_errno(q, "Failed to write utmp record: %m"); + r = q; + } + + return r; +} + +static int on_shutdown(Context *c) { + int r = 0, q; + + assert(c); + + /* We started shut-down, so let's write the utmp + * record and send the audit msg */ + +#ifdef HAVE_AUDIT + if (c->audit_fd >= 0) + if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_SHUTDOWN, "", "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 && + errno != EPERM) { + r = log_error_errno(errno, "Failed to send audit message: %m"); + } +#endif + + q = utmp_put_shutdown(); + if (q < 0) { + log_error_errno(q, "Failed to write utmp record: %m"); + r = q; + } + + return r; +} + +static int on_runlevel(Context *c) { + int r = 0, q, previous, runlevel; + + assert(c); + + /* We finished changing runlevel, so let's write the + * utmp record and send the audit msg */ + + /* First, get last runlevel */ + q = utmp_get_runlevel(&previous, NULL); + + if (q < 0) { + if (q != -ESRCH && q != -ENOENT) + return log_error_errno(q, "Failed to get current runlevel: %m"); + + previous = 0; + } + + /* Secondly, get new runlevel */ + runlevel = get_current_runlevel(c); + + if (runlevel < 0) + return runlevel; + + if (previous == runlevel) + return 0; + +#ifdef HAVE_AUDIT + if (c->audit_fd >= 0) { + _cleanup_free_ char *s = NULL; + + if (asprintf(&s, "old-level=%c new-level=%c", + previous > 0 ? previous : 'N', + runlevel > 0 ? runlevel : 'N') < 0) + return log_oom(); + + if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_RUNLEVEL, s, "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 && errno != EPERM) + r = log_error_errno(errno, "Failed to send audit message: %m"); + } +#endif + + q = utmp_put_runlevel(runlevel, previous); + if (q < 0 && q != -ESRCH && q != -ENOENT) { + log_error_errno(q, "Failed to write utmp record: %m"); + r = q; + } + + return r; +} + +int main(int argc, char *argv[]) { + Context c = { +#ifdef HAVE_AUDIT + .audit_fd = -1 +#endif + }; + int r; + + if (getppid() != 1) { + log_error("This program should be invoked by init only."); + return EXIT_FAILURE; + } + + if (argc != 2) { + log_error("This program requires one argument."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + +#ifdef HAVE_AUDIT + /* If the kernel lacks netlink or audit support, + * don't worry about it. */ + c.audit_fd = audit_open(); + if (c.audit_fd < 0 && errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT) + log_error_errno(errno, "Failed to connect to audit log: %m"); +#endif + r = bus_connect_system_systemd(&c.bus); + if (r < 0) { + log_error_errno(r, "Failed to get D-Bus connection: %m"); + r = -EIO; + goto finish; + } + + log_debug("systemd-update-utmp running as pid "PID_FMT, getpid()); + + if (streq(argv[1], "reboot")) + r = on_reboot(&c); + else if (streq(argv[1], "shutdown")) + r = on_shutdown(&c); + else if (streq(argv[1], "runlevel")) + r = on_runlevel(&c); + else { + log_error("Unknown command %s", argv[1]); + r = -EINVAL; + } + + log_debug("systemd-update-utmp stopped as pid "PID_FMT, getpid()); + +finish: +#ifdef HAVE_AUDIT + if (c.audit_fd >= 0) + audit_close(c.audit_fd); +#endif + + sd_bus_flush_close_unref(c.bus); + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-user-sessions/user-sessions.c b/src/systemd-user-sessions/user-sessions.c new file mode 100644 index 0000000000..9b29b5ba1d --- /dev/null +++ b/src/systemd-user-sessions/user-sessions.c @@ -0,0 +1,84 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 + +#include "fileio.h" +#include "fileio-label.h" +#include "log.h" +#include "selinux-util.h" +#include "string-util.h" +#include "util.h" + +int main(int argc, char*argv[]) { + + if (argc != 2) { + log_error("This program requires one argument."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + mac_selinux_init(); + + if (streq(argv[1], "start")) { + int r = 0; + + if (unlink("/run/nologin") < 0 && errno != ENOENT) + r = log_error_errno(errno, + "Failed to remove /run/nologin file: %m"); + + if (unlink("/etc/nologin") < 0 && errno != ENOENT) { + /* If the file doesn't exist and /etc simply + * was read-only (in which case unlink() + * returns EROFS even if the file doesn't + * exist), don't complain */ + + if (errno != EROFS || access("/etc/nologin", F_OK) >= 0) { + log_error_errno(errno, "Failed to remove /etc/nologin file: %m"); + return EXIT_FAILURE; + } + } + + if (r < 0) + return EXIT_FAILURE; + + } else if (streq(argv[1], "stop")) { + int r; + + r = write_string_file_atomic_label("/run/nologin", "System is going down."); + if (r < 0) { + log_error_errno(r, "Failed to create /run/nologin: %m"); + return EXIT_FAILURE; + } + + } else { + log_error("Unknown verb %s.", argv[1]); + return EXIT_FAILURE; + } + + mac_selinux_finish(); + + return EXIT_SUCCESS; +} diff --git a/src/systemd-vconsole/.gitignore b/src/systemd-vconsole/.gitignore new file mode 100644 index 0000000000..82741b2fb3 --- /dev/null +++ b/src/systemd-vconsole/.gitignore @@ -0,0 +1 @@ +/90-vconsole.rules diff --git a/src/systemd-vconsole/90-vconsole.rules.in b/src/systemd-vconsole/90-vconsole.rules.in new file mode 100644 index 0000000000..35b9ad5151 --- /dev/null +++ b/src/systemd-vconsole/90-vconsole.rules.in @@ -0,0 +1,10 @@ +# This file is part of systemd. +# +# 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. + +# Each vtcon keeps its own state of fonts. +# +ACTION=="add", SUBSYSTEM=="vtconsole", KERNEL=="vtcon*", RUN+="@rootlibexecdir@/systemd-vconsole-setup" diff --git a/src/systemd-vconsole/Makefile b/src/systemd-vconsole/Makefile new file mode 100644 index 0000000000..466829c1b9 --- /dev/null +++ b/src/systemd-vconsole/Makefile @@ -0,0 +1,50 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_VCONSOLE),) +systemd_vconsole_setup_SOURCES = \ + src/vconsole/vconsole-setup.c + +systemd_vconsole_setup_LDADD = \ + libshared.la + +libexec_PROGRAMS += \ + systemd-vconsole-setup + +nodist_udevrules_DATA += \ + src/vconsole/90-vconsole.rules + +nodist_systemunit_DATA += \ + units/systemd-vconsole-setup.service + +SYSINIT_TARGET_WANTS += \ + systemd-vconsole-setup.service +endif # ENABLE_VCONSOLE + +EXTRA_DIST += \ + src/vconsole/90-vconsole.rules.in \ + units/systemd-vconsole-setup.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-vconsole/vconsole-setup.c b/src/systemd-vconsole/vconsole-setup.c new file mode 100644 index 0000000000..1118118450 --- /dev/null +++ b/src/systemd-vconsole/vconsole-setup.c @@ -0,0 +1,332 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "io-util.h" +#include "locale-util.h" +#include "log.h" +#include "process-util.h" +#include "signal-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "terminal-util.h" +#include "util.h" +#include "virt.h" + +static bool is_vconsole(int fd) { + unsigned char data[1]; + + data[0] = TIOCL_GETFGCONSOLE; + return ioctl(fd, TIOCLINUX, data) >= 0; +} + +static int disable_utf8(int fd) { + int r = 0, k; + + if (ioctl(fd, KDSKBMODE, K_XLATE) < 0) + r = -errno; + + k = loop_write(fd, "\033%@", 3, false); + if (k < 0) + r = k; + + k = write_string_file("/sys/module/vt/parameters/default_utf8", "0", 0); + if (k < 0) + r = k; + + if (r < 0) + log_warning_errno(r, "Failed to disable UTF-8: %m"); + + return r; +} + +static int enable_utf8(int fd) { + int r = 0, k; + long current = 0; + + if (ioctl(fd, KDGKBMODE, ¤t) < 0 || current == K_XLATE) { + /* + * Change the current keyboard to unicode, unless it + * is currently in raw or off mode anyway. We + * shouldn't interfere with X11's processing of the + * key events. + * + * http://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html + * + */ + + if (ioctl(fd, KDSKBMODE, K_UNICODE) < 0) + r = -errno; + } + + k = loop_write(fd, "\033%G", 3, false); + if (k < 0) + r = k; + + k = write_string_file("/sys/module/vt/parameters/default_utf8", "1", 0); + if (k < 0) + r = k; + + if (r < 0) + log_warning_errno(r, "Failed to enable UTF-8: %m"); + + return r; +} + +static int keyboard_load_and_wait(const char *vc, const char *map, const char *map_toggle, bool utf8) { + const char *args[8]; + int i = 0, r; + pid_t pid; + + /* An empty map means kernel map */ + if (isempty(map)) + return 1; + + args[i++] = KBD_LOADKEYS; + args[i++] = "-q"; + args[i++] = "-C"; + args[i++] = vc; + if (utf8) + args[i++] = "-u"; + args[i++] = map; + if (map_toggle) + args[i++] = map_toggle; + args[i++] = NULL; + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + else if (pid == 0) { + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + execv(args[0], (char **) args); + _exit(EXIT_FAILURE); + } + + r = wait_for_terminate_and_warn(KBD_LOADKEYS, pid, true); + if (r < 0) + return r; + + return r == 0; +} + +static int font_load_and_wait(const char *vc, const char *font, const char *map, const char *unimap) { + const char *args[9]; + int i = 0, r; + pid_t pid; + + /* An empty font means kernel font */ + if (isempty(font)) + return 1; + + args[i++] = KBD_SETFONT; + args[i++] = "-C"; + args[i++] = vc; + args[i++] = font; + if (map) { + args[i++] = "-m"; + args[i++] = map; + } + if (unimap) { + args[i++] = "-u"; + args[i++] = unimap; + } + args[i++] = NULL; + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + else if (pid == 0) { + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + execv(args[0], (char **) args); + _exit(EXIT_FAILURE); + } + + r = wait_for_terminate_and_warn(KBD_SETFONT, pid, true); + if (r < 0) + return r; + + return r == 0; +} + +/* + * A newly allocated VT uses the font from the active VT. Here + * we update all possibly already allocated VTs with the configured + * font. It also allows to restart systemd-vconsole-setup.service, + * to apply a new font to all VTs. + */ +static void font_copy_to_all_vcs(int fd) { + struct vt_stat vcs = {}; + unsigned char map8[E_TABSZ]; + unsigned short map16[E_TABSZ]; + struct unimapdesc unimapd; + _cleanup_free_ struct unipair* unipairs = NULL; + int i, r; + + unipairs = new(struct unipair, USHRT_MAX); + if (!unipairs) { + log_oom(); + return; + } + + /* get active, and 16 bit mask of used VT numbers */ + r = ioctl(fd, VT_GETSTATE, &vcs); + if (r < 0) { + log_debug_errno(errno, "VT_GETSTATE failed, ignoring: %m"); + return; + } + + for (i = 1; i <= 15; i++) { + char vcname[strlen("/dev/vcs") + DECIMAL_STR_MAX(int)]; + _cleanup_close_ int vcfd = -1; + struct console_font_op cfo = {}; + + if (i == vcs.v_active) + continue; + + /* skip non-allocated ttys */ + xsprintf(vcname, "/dev/vcs%i", i); + if (access(vcname, F_OK) < 0) + continue; + + xsprintf(vcname, "/dev/tty%i", i); + vcfd = open_terminal(vcname, O_RDWR|O_CLOEXEC); + if (vcfd < 0) + continue; + + /* copy font from active VT, where the font was uploaded to */ + cfo.op = KD_FONT_OP_COPY; + cfo.height = vcs.v_active-1; /* tty1 == index 0 */ + (void) ioctl(vcfd, KDFONTOP, &cfo); + + /* copy map of 8bit chars */ + if (ioctl(fd, GIO_SCRNMAP, map8) >= 0) + (void) ioctl(vcfd, PIO_SCRNMAP, map8); + + /* copy map of 8bit chars -> 16bit Unicode values */ + if (ioctl(fd, GIO_UNISCRNMAP, map16) >= 0) + (void) ioctl(vcfd, PIO_UNISCRNMAP, map16); + + /* copy unicode translation table */ + /* unimapd is a ushort count and a pointer to an + array of struct unipair { ushort, ushort } */ + unimapd.entries = unipairs; + unimapd.entry_ct = USHRT_MAX; + if (ioctl(fd, GIO_UNIMAP, &unimapd) >= 0) { + struct unimapinit adv = { 0, 0, 0 }; + + (void) ioctl(vcfd, PIO_UNIMAPCLR, &adv); + (void) ioctl(vcfd, PIO_UNIMAP, &unimapd); + } + } +} + +int main(int argc, char **argv) { + const char *vc; + _cleanup_free_ char + *vc_keymap = NULL, *vc_keymap_toggle = NULL, + *vc_font = NULL, *vc_font_map = NULL, *vc_font_unimap = NULL; + _cleanup_close_ int fd = -1; + bool utf8, font_copy = false, font_ok, keyboard_ok; + int r = EXIT_FAILURE; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argv[1]) + vc = argv[1]; + else { + vc = "/dev/tty0"; + font_copy = true; + } + + fd = open_terminal(vc, O_RDWR|O_CLOEXEC); + if (fd < 0) { + log_error_errno(fd, "Failed to open %s: %m", vc); + return EXIT_FAILURE; + } + + if (!is_vconsole(fd)) { + log_error("Device %s is not a virtual console.", vc); + return EXIT_FAILURE; + } + + utf8 = is_locale_utf8(); + + r = parse_env_file("/etc/vconsole.conf", NEWLINE, + "KEYMAP", &vc_keymap, + "KEYMAP_TOGGLE", &vc_keymap_toggle, + "FONT", &vc_font, + "FONT_MAP", &vc_font_map, + "FONT_UNIMAP", &vc_font_unimap, + NULL); + + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to read /etc/vconsole.conf: %m"); + + /* Let the kernel command line override /etc/vconsole.conf */ + if (detect_container() <= 0) { + r = parse_env_file("/proc/cmdline", WHITESPACE, + "vconsole.keymap", &vc_keymap, + "vconsole.keymap.toggle", &vc_keymap_toggle, + "vconsole.font", &vc_font, + "vconsole.font.map", &vc_font_map, + "vconsole.font.unimap", &vc_font_unimap, + NULL); + + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to read /proc/cmdline: %m"); + } + + if (utf8) + (void) enable_utf8(fd); + else + (void) disable_utf8(fd); + + font_ok = font_load_and_wait(vc, vc_font, vc_font_map, vc_font_unimap) > 0; + keyboard_ok = keyboard_load_and_wait(vc, vc_keymap, vc_keymap_toggle, utf8) > 0; + + /* Only copy the font when we executed setfont successfully */ + if (font_copy && font_ok) + (void) font_copy_to_all_vcs(fd); + + return font_ok && keyboard_ok ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/systemd/Makefile b/src/systemd/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/systemd/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/systemd/_sd-common.h b/src/systemd/_sd-common.h deleted file mode 100644 index 3bb886be75..0000000000 --- a/src/systemd/_sd-common.h +++ /dev/null @@ -1,83 +0,0 @@ -#ifndef foosdcommonhfoo -#define foosdcommonhfoo - -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -/* This is a private header; never even think of including this directly! */ - -#if __INCLUDE_LEVEL__ <= 1 -#error "Do not include _sd-common.h directly; it is a private header." -#endif - -#ifndef _sd_printf_ -# if __GNUC__ >= 4 -# define _sd_printf_(a,b) __attribute__ ((format (printf, a, b))) -# else -# define _sd_printf_(a,b) -# endif -#endif - -#ifndef _sd_sentinel_ -# define _sd_sentinel_ __attribute__((sentinel)) -#endif - -#ifndef _sd_packed_ -# define _sd_packed_ __attribute__((packed)) -#endif - -#ifndef _sd_pure_ -# define _sd_pure_ __attribute__((pure)) -#endif - -#ifndef _SD_STRINGIFY -# define _SD_XSTRINGIFY(x) #x -# define _SD_STRINGIFY(x) _SD_XSTRINGIFY(x) -#endif - -#ifndef _SD_BEGIN_DECLARATIONS -# ifdef __cplusplus -# define _SD_BEGIN_DECLARATIONS \ - extern "C" { \ - struct _sd_useless_struct_to_allow_trailing_semicolon_ -# else -# define _SD_BEGIN_DECLARATIONS \ - struct _sd_useless_struct_to_allow_trailing_semicolon_ -# endif -#endif - -#ifndef _SD_END_DECLARATIONS -# ifdef __cplusplus -# define _SD_END_DECLARATIONS \ - } \ - struct _sd_useless_cpp_struct_to_allow_trailing_semicolon_ -# else -# define _SD_END_DECLARATIONS \ - struct _sd_useless_struct_to_allow_trailing_semicolon_ -# endif -#endif - -#define _SD_DEFINE_POINTER_CLEANUP_FUNC(type, func) \ - static __inline__ void func##p(type **p) { \ - if (*p) \ - func(*p); \ - } \ - struct _sd_useless_struct_to_allow_trailing_semicolon_ - -#endif diff --git a/src/systemd/sd-bus-protocol.h b/src/systemd/sd-bus-protocol.h deleted file mode 100644 index 623cee0c50..0000000000 --- a/src/systemd/sd-bus-protocol.h +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef foosdbusprotocolhfoo -#define foosdbusprotocolhfoo - -/*** - This file is part of systemd. - - Copyright 2013 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 "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -/* Types of message */ - -enum { - _SD_BUS_MESSAGE_TYPE_INVALID = 0, - SD_BUS_MESSAGE_METHOD_CALL, - SD_BUS_MESSAGE_METHOD_RETURN, - SD_BUS_MESSAGE_METHOD_ERROR, - SD_BUS_MESSAGE_SIGNAL, - _SD_BUS_MESSAGE_TYPE_MAX -}; - -/* Primitive types */ - -enum { - _SD_BUS_TYPE_INVALID = 0, - SD_BUS_TYPE_BYTE = 'y', - SD_BUS_TYPE_BOOLEAN = 'b', - SD_BUS_TYPE_INT16 = 'n', - SD_BUS_TYPE_UINT16 = 'q', - SD_BUS_TYPE_INT32 = 'i', - SD_BUS_TYPE_UINT32 = 'u', - SD_BUS_TYPE_INT64 = 'x', - SD_BUS_TYPE_UINT64 = 't', - SD_BUS_TYPE_DOUBLE = 'd', - SD_BUS_TYPE_STRING = 's', - SD_BUS_TYPE_OBJECT_PATH = 'o', - SD_BUS_TYPE_SIGNATURE = 'g', - SD_BUS_TYPE_UNIX_FD = 'h', - SD_BUS_TYPE_ARRAY = 'a', - SD_BUS_TYPE_VARIANT = 'v', - SD_BUS_TYPE_STRUCT = 'r', /* not actually used in signatures */ - SD_BUS_TYPE_STRUCT_BEGIN = '(', - SD_BUS_TYPE_STRUCT_END = ')', - SD_BUS_TYPE_DICT_ENTRY = 'e', /* not actually used in signatures */ - SD_BUS_TYPE_DICT_ENTRY_BEGIN = '{', - SD_BUS_TYPE_DICT_ENTRY_END = '}' -}; - -/* Well-known errors. Note that this is only a sanitized subset of the - * errors that the reference implementation generates. */ - -#define SD_BUS_ERROR_FAILED "org.freedesktop.DBus.Error.Failed" -#define SD_BUS_ERROR_NO_MEMORY "org.freedesktop.DBus.Error.NoMemory" -#define SD_BUS_ERROR_SERVICE_UNKNOWN "org.freedesktop.DBus.Error.ServiceUnknown" -#define SD_BUS_ERROR_NAME_HAS_NO_OWNER "org.freedesktop.DBus.Error.NameHasNoOwner" -#define SD_BUS_ERROR_NO_REPLY "org.freedesktop.DBus.Error.NoReply" -#define SD_BUS_ERROR_IO_ERROR "org.freedesktop.DBus.Error.IOError" -#define SD_BUS_ERROR_BAD_ADDRESS "org.freedesktop.DBus.Error.BadAddress" -#define SD_BUS_ERROR_NOT_SUPPORTED "org.freedesktop.DBus.Error.NotSupported" -#define SD_BUS_ERROR_LIMITS_EXCEEDED "org.freedesktop.DBus.Error.LimitsExceeded" -#define SD_BUS_ERROR_ACCESS_DENIED "org.freedesktop.DBus.Error.AccessDenied" -#define SD_BUS_ERROR_AUTH_FAILED "org.freedesktop.DBus.Error.AuthFailed" -#define SD_BUS_ERROR_NO_SERVER "org.freedesktop.DBus.Error.NoServer" -#define SD_BUS_ERROR_TIMEOUT "org.freedesktop.DBus.Error.Timeout" -#define SD_BUS_ERROR_NO_NETWORK "org.freedesktop.DBus.Error.NoNetwork" -#define SD_BUS_ERROR_ADDRESS_IN_USE "org.freedesktop.DBus.Error.AddressInUse" -#define SD_BUS_ERROR_DISCONNECTED "org.freedesktop.DBus.Error.Disconnected" -#define SD_BUS_ERROR_INVALID_ARGS "org.freedesktop.DBus.Error.InvalidArgs" -#define SD_BUS_ERROR_FILE_NOT_FOUND "org.freedesktop.DBus.Error.FileNotFound" -#define SD_BUS_ERROR_FILE_EXISTS "org.freedesktop.DBus.Error.FileExists" -#define SD_BUS_ERROR_UNKNOWN_METHOD "org.freedesktop.DBus.Error.UnknownMethod" -#define SD_BUS_ERROR_UNKNOWN_OBJECT "org.freedesktop.DBus.Error.UnknownObject" -#define SD_BUS_ERROR_UNKNOWN_INTERFACE "org.freedesktop.DBus.Error.UnknownInterface" -#define SD_BUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty" -#define SD_BUS_ERROR_PROPERTY_READ_ONLY "org.freedesktop.DBus.Error.PropertyReadOnly" -#define SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN "org.freedesktop.DBus.Error.UnixProcessIdUnknown" -#define SD_BUS_ERROR_INVALID_SIGNATURE "org.freedesktop.DBus.Error.InvalidSignature" -#define SD_BUS_ERROR_INCONSISTENT_MESSAGE "org.freedesktop.DBus.Error.InconsistentMessage" -#define SD_BUS_ERROR_MATCH_RULE_NOT_FOUND "org.freedesktop.DBus.Error.MatchRuleNotFound" -#define SD_BUS_ERROR_MATCH_RULE_INVALID "org.freedesktop.DBus.Error.MatchRuleInvalid" -#define SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED \ - "org.freedesktop.DBus.Error.InteractiveAuthorizationRequired" - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-bus-vtable.h b/src/systemd/sd-bus-vtable.h deleted file mode 100644 index e8f84eb545..0000000000 --- a/src/systemd/sd-bus-vtable.h +++ /dev/null @@ -1,141 +0,0 @@ -#ifndef foosdbusvtablehfoo -#define foosdbusvtablehfoo - -/*** - This file is part of systemd. - - Copyright 2013 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 "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -typedef struct sd_bus_vtable sd_bus_vtable; - -#include "sd-bus.h" - -enum { - _SD_BUS_VTABLE_START = '<', - _SD_BUS_VTABLE_END = '>', - _SD_BUS_VTABLE_METHOD = 'M', - _SD_BUS_VTABLE_SIGNAL = 'S', - _SD_BUS_VTABLE_PROPERTY = 'P', - _SD_BUS_VTABLE_WRITABLE_PROPERTY = 'W' -}; - -enum { - SD_BUS_VTABLE_DEPRECATED = 1ULL << 0, - SD_BUS_VTABLE_HIDDEN = 1ULL << 1, - SD_BUS_VTABLE_UNPRIVILEGED = 1ULL << 2, - SD_BUS_VTABLE_METHOD_NO_REPLY = 1ULL << 3, - SD_BUS_VTABLE_PROPERTY_CONST = 1ULL << 4, - SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE = 1ULL << 5, - SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION = 1ULL << 6, - SD_BUS_VTABLE_PROPERTY_EXPLICIT = 1ULL << 7, - _SD_BUS_VTABLE_CAPABILITY_MASK = 0xFFFFULL << 40 -}; - -#define SD_BUS_VTABLE_CAPABILITY(x) ((uint64_t) (((x)+1) & 0xFFFF) << 40) - -struct sd_bus_vtable { - /* Please do not initialize this structure directly, use the - * macros below instead */ - - uint8_t type:8; - uint64_t flags:56; - union { - struct { - size_t element_size; - } start; - struct { - const char *member; - const char *signature; - const char *result; - sd_bus_message_handler_t handler; - size_t offset; - } method; - struct { - const char *member; - const char *signature; - } signal; - struct { - const char *member; - const char *signature; - sd_bus_property_get_t get; - sd_bus_property_set_t set; - size_t offset; - } property; - } x; -}; - -#define SD_BUS_VTABLE_START(_flags) \ - { \ - .type = _SD_BUS_VTABLE_START, \ - .flags = _flags, \ - .x.start.element_size = sizeof(sd_bus_vtable), \ - } - -#define SD_BUS_METHOD_WITH_OFFSET(_member, _signature, _result, _handler, _offset, _flags) \ - { \ - .type = _SD_BUS_VTABLE_METHOD, \ - .flags = _flags, \ - .x.method.member = _member, \ - .x.method.signature = _signature, \ - .x.method.result = _result, \ - .x.method.handler = _handler, \ - .x.method.offset = _offset, \ - } -#define SD_BUS_METHOD(_member, _signature, _result, _handler, _flags) \ - SD_BUS_METHOD_WITH_OFFSET(_member, _signature, _result, _handler, 0, _flags) - -#define SD_BUS_SIGNAL(_member, _signature, _flags) \ - { \ - .type = _SD_BUS_VTABLE_SIGNAL, \ - .flags = _flags, \ - .x.signal.member = _member, \ - .x.signal.signature = _signature, \ - } - -#define SD_BUS_PROPERTY(_member, _signature, _get, _offset, _flags) \ - { \ - .type = _SD_BUS_VTABLE_PROPERTY, \ - .flags = _flags, \ - .x.property.member = _member, \ - .x.property.signature = _signature, \ - .x.property.get = _get, \ - .x.property.offset = _offset, \ - } - -#define SD_BUS_WRITABLE_PROPERTY(_member, _signature, _get, _set, _offset, _flags) \ - { \ - .type = _SD_BUS_VTABLE_WRITABLE_PROPERTY, \ - .flags = _flags, \ - .x.property.member = _member, \ - .x.property.signature = _signature, \ - .x.property.get = _get, \ - .x.property.set = _set, \ - .x.property.offset = _offset, \ - } - -#define SD_BUS_VTABLE_END \ - { \ - .type = _SD_BUS_VTABLE_END, \ - } - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-bus.h b/src/systemd/sd-bus.h deleted file mode 100644 index 295989cd69..0000000000 --- a/src/systemd/sd-bus.h +++ /dev/null @@ -1,456 +0,0 @@ -#ifndef foosdbushfoo -#define foosdbushfoo - -/*** - This file is part of systemd. - - Copyright 2013 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 -#include -#include - -#include "sd-event.h" -#include "sd-id128.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -/* Types */ - -typedef struct sd_bus sd_bus; -typedef struct sd_bus_message sd_bus_message; -typedef struct sd_bus_slot sd_bus_slot; -typedef struct sd_bus_creds sd_bus_creds; -typedef struct sd_bus_track sd_bus_track; - -typedef struct { - const char *name; - const char *message; - int _need_free; -} sd_bus_error; - -typedef struct { - const char* name; - int code; -} sd_bus_error_map; - -/* Flags */ - -enum { - SD_BUS_CREDS_PID = 1ULL << 0, - SD_BUS_CREDS_TID = 1ULL << 1, - SD_BUS_CREDS_PPID = 1ULL << 2, - SD_BUS_CREDS_UID = 1ULL << 3, - SD_BUS_CREDS_EUID = 1ULL << 4, - SD_BUS_CREDS_SUID = 1ULL << 5, - SD_BUS_CREDS_FSUID = 1ULL << 6, - SD_BUS_CREDS_GID = 1ULL << 7, - SD_BUS_CREDS_EGID = 1ULL << 8, - SD_BUS_CREDS_SGID = 1ULL << 9, - SD_BUS_CREDS_FSGID = 1ULL << 10, - SD_BUS_CREDS_SUPPLEMENTARY_GIDS = 1ULL << 11, - SD_BUS_CREDS_COMM = 1ULL << 12, - SD_BUS_CREDS_TID_COMM = 1ULL << 13, - SD_BUS_CREDS_EXE = 1ULL << 14, - SD_BUS_CREDS_CMDLINE = 1ULL << 15, - SD_BUS_CREDS_CGROUP = 1ULL << 16, - SD_BUS_CREDS_UNIT = 1ULL << 17, - SD_BUS_CREDS_SLICE = 1ULL << 18, - SD_BUS_CREDS_USER_UNIT = 1ULL << 19, - SD_BUS_CREDS_USER_SLICE = 1ULL << 20, - SD_BUS_CREDS_SESSION = 1ULL << 21, - SD_BUS_CREDS_OWNER_UID = 1ULL << 22, - SD_BUS_CREDS_EFFECTIVE_CAPS = 1ULL << 23, - SD_BUS_CREDS_PERMITTED_CAPS = 1ULL << 24, - SD_BUS_CREDS_INHERITABLE_CAPS = 1ULL << 25, - SD_BUS_CREDS_BOUNDING_CAPS = 1ULL << 26, - SD_BUS_CREDS_SELINUX_CONTEXT = 1ULL << 27, - SD_BUS_CREDS_AUDIT_SESSION_ID = 1ULL << 28, - SD_BUS_CREDS_AUDIT_LOGIN_UID = 1ULL << 29, - SD_BUS_CREDS_TTY = 1ULL << 30, - SD_BUS_CREDS_UNIQUE_NAME = 1ULL << 31, - SD_BUS_CREDS_WELL_KNOWN_NAMES = 1ULL << 32, - SD_BUS_CREDS_DESCRIPTION = 1ULL << 33, - SD_BUS_CREDS_AUGMENT = 1ULL << 63, /* special flag, if on sd-bus will augment creds struct, in a potentially race-full way. */ - _SD_BUS_CREDS_ALL = (1ULL << 34) -1 -}; - -enum { - SD_BUS_NAME_REPLACE_EXISTING = 1ULL << 0, - SD_BUS_NAME_ALLOW_REPLACEMENT = 1ULL << 1, - SD_BUS_NAME_QUEUE = 1ULL << 2 -}; - -/* Callbacks */ - -typedef int (*sd_bus_message_handler_t)(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); -typedef int (*sd_bus_property_get_t) (sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); -typedef int (*sd_bus_property_set_t) (sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *value, void *userdata, sd_bus_error *ret_error); -typedef int (*sd_bus_object_find_t) (sd_bus *bus, const char *path, const char *interface, void *userdata, void **ret_found, sd_bus_error *ret_error); -typedef int (*sd_bus_node_enumerator_t) (sd_bus *bus, const char *prefix, void *userdata, char ***ret_nodes, sd_bus_error *ret_error); -typedef int (*sd_bus_track_handler_t) (sd_bus_track *track, void *userdata); - -#include "sd-bus-protocol.h" -#include "sd-bus-vtable.h" - -/* Connections */ - -int sd_bus_default(sd_bus **ret); -int sd_bus_default_user(sd_bus **ret); -int sd_bus_default_system(sd_bus **ret); - -int sd_bus_open(sd_bus **ret); -int sd_bus_open_user(sd_bus **ret); -int sd_bus_open_system(sd_bus **ret); -int sd_bus_open_system_remote(sd_bus **ret, const char *host); -int sd_bus_open_system_machine(sd_bus **ret, const char *machine); - -int sd_bus_new(sd_bus **ret); - -int sd_bus_set_address(sd_bus *bus, const char *address); -int sd_bus_set_fd(sd_bus *bus, int input_fd, int output_fd); -int sd_bus_set_exec(sd_bus *bus, const char *path, char *const argv[]); -int sd_bus_get_address(sd_bus *bus, const char **address); -int sd_bus_set_bus_client(sd_bus *bus, int b); -int sd_bus_is_bus_client(sd_bus *bus); -int sd_bus_set_server(sd_bus *bus, int b, sd_id128_t bus_id); -int sd_bus_is_server(sd_bus *bus); -int sd_bus_set_anonymous(sd_bus *bus, int b); -int sd_bus_is_anonymous(sd_bus *bus); -int sd_bus_set_trusted(sd_bus *bus, int b); -int sd_bus_is_trusted(sd_bus *bus); -int sd_bus_set_monitor(sd_bus *bus, int b); -int sd_bus_is_monitor(sd_bus *bus); -int sd_bus_set_description(sd_bus *bus, const char *description); -int sd_bus_get_description(sd_bus *bus, const char **description); -int sd_bus_negotiate_creds(sd_bus *bus, int b, uint64_t creds_mask); -int sd_bus_negotiate_timestamp(sd_bus *bus, int b); -int sd_bus_negotiate_fds(sd_bus *bus, int b); -int sd_bus_can_send(sd_bus *bus, char type); -int sd_bus_get_creds_mask(sd_bus *bus, uint64_t *creds_mask); -int sd_bus_set_allow_interactive_authorization(sd_bus *bus, int b); -int sd_bus_get_allow_interactive_authorization(sd_bus *bus); - -int sd_bus_start(sd_bus *ret); - -int sd_bus_try_close(sd_bus *bus); -void sd_bus_close(sd_bus *bus); - -sd_bus *sd_bus_ref(sd_bus *bus); -sd_bus *sd_bus_unref(sd_bus *bus); -sd_bus *sd_bus_flush_close_unref(sd_bus *bus); - -void sd_bus_default_flush_close(void); - -int sd_bus_is_open(sd_bus *bus); - -int sd_bus_get_bus_id(sd_bus *bus, sd_id128_t *id); -int sd_bus_get_scope(sd_bus *bus, const char **scope); -int sd_bus_get_tid(sd_bus *bus, pid_t *tid); -int sd_bus_get_owner_creds(sd_bus *bus, uint64_t creds_mask, sd_bus_creds **ret); - -int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie); -int sd_bus_send_to(sd_bus *bus, sd_bus_message *m, const char *destination, uint64_t *cookie); -int sd_bus_call(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply); -int sd_bus_call_async(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec); - -int sd_bus_get_fd(sd_bus *bus); -int sd_bus_get_events(sd_bus *bus); -int sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec); -int sd_bus_process(sd_bus *bus, sd_bus_message **r); -int sd_bus_process_priority(sd_bus *bus, int64_t max_priority, sd_bus_message **r); -int sd_bus_wait(sd_bus *bus, uint64_t timeout_usec); -int sd_bus_flush(sd_bus *bus); - -sd_bus_slot* sd_bus_get_current_slot(sd_bus *bus); -sd_bus_message* sd_bus_get_current_message(sd_bus *bus); -sd_bus_message_handler_t sd_bus_get_current_handler(sd_bus *bus); -void* sd_bus_get_current_userdata(sd_bus *bus); - -int sd_bus_attach_event(sd_bus *bus, sd_event *e, int priority); -int sd_bus_detach_event(sd_bus *bus); -sd_event *sd_bus_get_event(sd_bus *bus); - -int sd_bus_add_filter(sd_bus *bus, sd_bus_slot **slot, sd_bus_message_handler_t callback, void *userdata); -int sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata); -int sd_bus_add_object(sd_bus *bus, sd_bus_slot **slot, const char *path, sd_bus_message_handler_t callback, void *userdata); -int sd_bus_add_fallback(sd_bus *bus, sd_bus_slot **slot, const char *prefix, sd_bus_message_handler_t callback, void *userdata); -int sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata); -int sd_bus_add_fallback_vtable(sd_bus *bus, sd_bus_slot **slot, const char *prefix, const char *interface, const sd_bus_vtable *vtable, sd_bus_object_find_t find, void *userdata); -int sd_bus_add_node_enumerator(sd_bus *bus, sd_bus_slot **slot, const char *path, sd_bus_node_enumerator_t callback, void *userdata); -int sd_bus_add_object_manager(sd_bus *bus, sd_bus_slot **slot, const char *path); - -/* Slot object */ - -sd_bus_slot* sd_bus_slot_ref(sd_bus_slot *slot); -sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot); - -sd_bus* sd_bus_slot_get_bus(sd_bus_slot *slot); -void *sd_bus_slot_get_userdata(sd_bus_slot *slot); -void *sd_bus_slot_set_userdata(sd_bus_slot *slot, void *userdata); -int sd_bus_slot_set_description(sd_bus_slot *slot, const char *description); -int sd_bus_slot_get_description(sd_bus_slot *slot, const char **description); - -sd_bus_message* sd_bus_slot_get_current_message(sd_bus_slot *slot); -sd_bus_message_handler_t sd_bus_slot_get_current_handler(sd_bus_slot *bus); -void *sd_bus_slot_get_current_userdata(sd_bus_slot *slot); - -/* Message object */ - -int sd_bus_message_new_signal(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member); -int sd_bus_message_new_method_call(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member); -int sd_bus_message_new_method_return(sd_bus_message *call, sd_bus_message **m); -int sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e); -int sd_bus_message_new_method_errorf(sd_bus_message *call, sd_bus_message **m, const char *name, const char *format, ...) _sd_printf_(4, 5); -int sd_bus_message_new_method_errno(sd_bus_message *call, sd_bus_message **m, int error, const sd_bus_error *e); -int sd_bus_message_new_method_errnof(sd_bus_message *call, sd_bus_message **m, int error, const char *format, ...) _sd_printf_(4, 5); - -sd_bus_message* sd_bus_message_ref(sd_bus_message *m); -sd_bus_message* sd_bus_message_unref(sd_bus_message *m); - -int sd_bus_message_get_type(sd_bus_message *m, uint8_t *type); -int sd_bus_message_get_cookie(sd_bus_message *m, uint64_t *cookie); -int sd_bus_message_get_reply_cookie(sd_bus_message *m, uint64_t *cookie); -int sd_bus_message_get_priority(sd_bus_message *m, int64_t *priority); - -int sd_bus_message_get_expect_reply(sd_bus_message *m); -int sd_bus_message_get_auto_start(sd_bus_message *m); -int sd_bus_message_get_allow_interactive_authorization(sd_bus_message *m); - -const char *sd_bus_message_get_signature(sd_bus_message *m, int complete); -const char *sd_bus_message_get_path(sd_bus_message *m); -const char *sd_bus_message_get_interface(sd_bus_message *m); -const char *sd_bus_message_get_member(sd_bus_message *m); -const char *sd_bus_message_get_destination(sd_bus_message *m); -const char *sd_bus_message_get_sender(sd_bus_message *m); -const sd_bus_error *sd_bus_message_get_error(sd_bus_message *m); -int sd_bus_message_get_errno(sd_bus_message *m); - -int sd_bus_message_get_monotonic_usec(sd_bus_message *m, uint64_t *usec); -int sd_bus_message_get_realtime_usec(sd_bus_message *m, uint64_t *usec); -int sd_bus_message_get_seqnum(sd_bus_message *m, uint64_t* seqnum); - -sd_bus* sd_bus_message_get_bus(sd_bus_message *m); -sd_bus_creds *sd_bus_message_get_creds(sd_bus_message *m); /* do not unref the result */ - -int sd_bus_message_is_signal(sd_bus_message *m, const char *interface, const char *member); -int sd_bus_message_is_method_call(sd_bus_message *m, const char *interface, const char *member); -int sd_bus_message_is_method_error(sd_bus_message *m, const char *name); -int sd_bus_message_is_empty(sd_bus_message *m); -int sd_bus_message_has_signature(sd_bus_message *m, const char *signature); - -int sd_bus_message_set_expect_reply(sd_bus_message *m, int b); -int sd_bus_message_set_auto_start(sd_bus_message *m, int b); -int sd_bus_message_set_allow_interactive_authorization(sd_bus_message *m, int b); - -int sd_bus_message_set_destination(sd_bus_message *m, const char *destination); -int sd_bus_message_set_priority(sd_bus_message *m, int64_t priority); - -int sd_bus_message_append(sd_bus_message *m, const char *types, ...); -int sd_bus_message_append_basic(sd_bus_message *m, char type, const void *p); -int sd_bus_message_append_array(sd_bus_message *m, char type, const void *ptr, size_t size); -int sd_bus_message_append_array_space(sd_bus_message *m, char type, size_t size, void **ptr); -int sd_bus_message_append_array_iovec(sd_bus_message *m, char type, const struct iovec *iov, unsigned n); -int sd_bus_message_append_array_memfd(sd_bus_message *m, char type, int memfd, uint64_t offset, uint64_t size); -int sd_bus_message_append_string_space(sd_bus_message *m, size_t size, char **s); -int sd_bus_message_append_string_iovec(sd_bus_message *m, const struct iovec *iov, unsigned n); -int sd_bus_message_append_string_memfd(sd_bus_message *m, int memfd, uint64_t offset, uint64_t size); -int sd_bus_message_append_strv(sd_bus_message *m, char **l); -int sd_bus_message_open_container(sd_bus_message *m, char type, const char *contents); -int sd_bus_message_close_container(sd_bus_message *m); -int sd_bus_message_copy(sd_bus_message *m, sd_bus_message *source, int all); - -int sd_bus_message_read(sd_bus_message *m, const char *types, ...); -int sd_bus_message_read_basic(sd_bus_message *m, char type, void *p); -int sd_bus_message_read_array(sd_bus_message *m, char type, const void **ptr, size_t *size); -int sd_bus_message_read_strv(sd_bus_message *m, char ***l); /* free the result! */ -int sd_bus_message_skip(sd_bus_message *m, const char *types); -int sd_bus_message_enter_container(sd_bus_message *m, char type, const char *contents); -int sd_bus_message_exit_container(sd_bus_message *m); -int sd_bus_message_peek_type(sd_bus_message *m, char *type, const char **contents); -int sd_bus_message_verify_type(sd_bus_message *m, char type, const char *contents); -int sd_bus_message_at_end(sd_bus_message *m, int complete); -int sd_bus_message_rewind(sd_bus_message *m, int complete); - -/* Bus management */ - -int sd_bus_get_unique_name(sd_bus *bus, const char **unique); -int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags); -int sd_bus_release_name(sd_bus *bus, const char *name); -int sd_bus_list_names(sd_bus *bus, char ***acquired, char ***activatable); /* free the results */ -int sd_bus_get_name_creds(sd_bus *bus, const char *name, uint64_t mask, sd_bus_creds **creds); /* unref the result! */ -int sd_bus_get_name_machine_id(sd_bus *bus, const char *name, sd_id128_t *machine); - -/* Convenience calls */ - -int sd_bus_call_method(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, sd_bus_message **reply, const char *types, ...); -int sd_bus_call_method_async(sd_bus *bus, sd_bus_slot **slot, const char *destination, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *userdata, const char *types, ...); -int sd_bus_get_property(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, sd_bus_message **reply, const char *type); -int sd_bus_get_property_trivial(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, char type, void *ret_ptr); -int sd_bus_get_property_string(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, char **ret); /* free the result! */ -int sd_bus_get_property_strv(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, char ***ret); /* free the result! */ -int sd_bus_set_property(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, const char *type, ...); - -int sd_bus_reply_method_return(sd_bus_message *call, const char *types, ...); -int sd_bus_reply_method_error(sd_bus_message *call, const sd_bus_error *e); -int sd_bus_reply_method_errorf(sd_bus_message *call, const char *name, const char *format, ...) _sd_printf_(3, 4); -int sd_bus_reply_method_errno(sd_bus_message *call, int error, const sd_bus_error *e); -int sd_bus_reply_method_errnof(sd_bus_message *call, int error, const char *format, ...) _sd_printf_(3, 4); - -int sd_bus_emit_signal(sd_bus *bus, const char *path, const char *interface, const char *member, const char *types, ...); - -int sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names); -int sd_bus_emit_properties_changed(sd_bus *bus, const char *path, const char *interface, const char *name, ...) _sd_sentinel_; - -int sd_bus_emit_object_added(sd_bus *bus, const char *path); -int sd_bus_emit_object_removed(sd_bus *bus, const char *path); -int sd_bus_emit_interfaces_added_strv(sd_bus *bus, const char *path, char **interfaces); -int sd_bus_emit_interfaces_added(sd_bus *bus, const char *path, const char *interface, ...) _sd_sentinel_; -int sd_bus_emit_interfaces_removed_strv(sd_bus *bus, const char *path, char **interfaces); -int sd_bus_emit_interfaces_removed(sd_bus *bus, const char *path, const char *interface, ...) _sd_sentinel_; - -int sd_bus_query_sender_creds(sd_bus_message *call, uint64_t mask, sd_bus_creds **creds); -int sd_bus_query_sender_privilege(sd_bus_message *call, int capability); - -/* Credential handling */ - -int sd_bus_creds_new_from_pid(sd_bus_creds **ret, pid_t pid, uint64_t creds_mask); -sd_bus_creds *sd_bus_creds_ref(sd_bus_creds *c); -sd_bus_creds *sd_bus_creds_unref(sd_bus_creds *c); -uint64_t sd_bus_creds_get_mask(const sd_bus_creds *c); -uint64_t sd_bus_creds_get_augmented_mask(const sd_bus_creds *c); - -int sd_bus_creds_get_pid(sd_bus_creds *c, pid_t *pid); -int sd_bus_creds_get_ppid(sd_bus_creds *c, pid_t *ppid); -int sd_bus_creds_get_tid(sd_bus_creds *c, pid_t *tid); -int sd_bus_creds_get_uid(sd_bus_creds *c, uid_t *uid); -int sd_bus_creds_get_euid(sd_bus_creds *c, uid_t *euid); -int sd_bus_creds_get_suid(sd_bus_creds *c, uid_t *suid); -int sd_bus_creds_get_fsuid(sd_bus_creds *c, uid_t *fsuid); -int sd_bus_creds_get_gid(sd_bus_creds *c, gid_t *gid); -int sd_bus_creds_get_egid(sd_bus_creds *c, gid_t *egid); -int sd_bus_creds_get_sgid(sd_bus_creds *c, gid_t *sgid); -int sd_bus_creds_get_fsgid(sd_bus_creds *c, gid_t *fsgid); -int sd_bus_creds_get_supplementary_gids(sd_bus_creds *c, const gid_t **gids); -int sd_bus_creds_get_comm(sd_bus_creds *c, const char **comm); -int sd_bus_creds_get_tid_comm(sd_bus_creds *c, const char **comm); -int sd_bus_creds_get_exe(sd_bus_creds *c, const char **exe); -int sd_bus_creds_get_cmdline(sd_bus_creds *c, char ***cmdline); -int sd_bus_creds_get_cgroup(sd_bus_creds *c, const char **cgroup); -int sd_bus_creds_get_unit(sd_bus_creds *c, const char **unit); -int sd_bus_creds_get_slice(sd_bus_creds *c, const char **slice); -int sd_bus_creds_get_user_unit(sd_bus_creds *c, const char **unit); -int sd_bus_creds_get_user_slice(sd_bus_creds *c, const char **slice); -int sd_bus_creds_get_session(sd_bus_creds *c, const char **session); -int sd_bus_creds_get_owner_uid(sd_bus_creds *c, uid_t *uid); -int sd_bus_creds_has_effective_cap(sd_bus_creds *c, int capability); -int sd_bus_creds_has_permitted_cap(sd_bus_creds *c, int capability); -int sd_bus_creds_has_inheritable_cap(sd_bus_creds *c, int capability); -int sd_bus_creds_has_bounding_cap(sd_bus_creds *c, int capability); -int sd_bus_creds_get_selinux_context(sd_bus_creds *c, const char **context); -int sd_bus_creds_get_audit_session_id(sd_bus_creds *c, uint32_t *sessionid); -int sd_bus_creds_get_audit_login_uid(sd_bus_creds *c, uid_t *loginuid); -int sd_bus_creds_get_tty(sd_bus_creds *c, const char **tty); -int sd_bus_creds_get_unique_name(sd_bus_creds *c, const char **name); -int sd_bus_creds_get_well_known_names(sd_bus_creds *c, char ***names); -int sd_bus_creds_get_description(sd_bus_creds *c, const char **name); - -/* Error structures */ - -#define SD_BUS_ERROR_MAKE_CONST(name, message) ((const sd_bus_error) {(name), (message), 0}) -#define SD_BUS_ERROR_NULL SD_BUS_ERROR_MAKE_CONST(NULL, NULL) - -void sd_bus_error_free(sd_bus_error *e); -int sd_bus_error_set(sd_bus_error *e, const char *name, const char *message); -int sd_bus_error_setf(sd_bus_error *e, const char *name, const char *format, ...) _sd_printf_(3, 4); -int sd_bus_error_set_const(sd_bus_error *e, const char *name, const char *message); -int sd_bus_error_set_errno(sd_bus_error *e, int error); -int sd_bus_error_set_errnof(sd_bus_error *e, int error, const char *format, ...) _sd_printf_(3, 4); -int sd_bus_error_set_errnofv(sd_bus_error *e, int error, const char *format, va_list ap) _sd_printf_(3,0); -int sd_bus_error_get_errno(const sd_bus_error *e); -int sd_bus_error_copy(sd_bus_error *dest, const sd_bus_error *e); -int sd_bus_error_is_set(const sd_bus_error *e); -int sd_bus_error_has_name(const sd_bus_error *e, const char *name); - -#define SD_BUS_ERROR_MAP(_name, _code) \ - { \ - .name = _name, \ - .code = _code, \ - } -#define SD_BUS_ERROR_MAP_END \ - { \ - .name = NULL, \ - .code = - 'x', \ - } - -int sd_bus_error_add_map(const sd_bus_error_map *map); - -/* Auxiliary macros */ - -#define SD_BUS_MESSAGE_APPEND_ID128(x) 16, \ - (x).bytes[0], (x).bytes[1], (x).bytes[2], (x).bytes[3], \ - (x).bytes[4], (x).bytes[5], (x).bytes[6], (x).bytes[7], \ - (x).bytes[8], (x).bytes[9], (x).bytes[10], (x).bytes[11], \ - (x).bytes[12], (x).bytes[13], (x).bytes[14], (x).bytes[15] - -#define SD_BUS_MESSAGE_READ_ID128(x) 16, \ - &(x).bytes[0], &(x).bytes[1], &(x).bytes[2], &(x).bytes[3], \ - &(x).bytes[4], &(x).bytes[5], &(x).bytes[6], &(x).bytes[7], \ - &(x).bytes[8], &(x).bytes[9], &(x).bytes[10], &(x).bytes[11], \ - &(x).bytes[12], &(x).bytes[13], &(x).bytes[14], &(x).bytes[15] - -/* Label escaping */ - -int sd_bus_path_encode(const char *prefix, const char *external_id, char **ret_path); -int sd_bus_path_encode_many(char **out, const char *path_template, ...); -int sd_bus_path_decode(const char *path, const char *prefix, char **ret_external_id); -int sd_bus_path_decode_many(const char *path, const char *path_template, ...); - -/* Tracking peers */ - -int sd_bus_track_new(sd_bus *bus, sd_bus_track **track, sd_bus_track_handler_t handler, void *userdata); -sd_bus_track* sd_bus_track_ref(sd_bus_track *track); -sd_bus_track* sd_bus_track_unref(sd_bus_track *track); - -sd_bus* sd_bus_track_get_bus(sd_bus_track *track); -void *sd_bus_track_get_userdata(sd_bus_track *track); -void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata); - -int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m); -int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m); -int sd_bus_track_add_name(sd_bus_track *track, const char *name); -int sd_bus_track_remove_name(sd_bus_track *track, const char *name); - -unsigned sd_bus_track_count(sd_bus_track *track); -const char* sd_bus_track_contains(sd_bus_track *track, const char *names); -const char* sd_bus_track_first(sd_bus_track *track); -const char* sd_bus_track_next(sd_bus_track *track); - -/* Define helpers so that __attribute__((cleanup(sd_bus_unrefp))) and similar may be used. */ -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus, sd_bus_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus, sd_bus_flush_close_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus_slot, sd_bus_slot_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus_message, sd_bus_message_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus_creds, sd_bus_creds_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus_track, sd_bus_track_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-daemon.h b/src/systemd/sd-daemon.h deleted file mode 100644 index e6787b0a64..0000000000 --- a/src/systemd/sd-daemon.h +++ /dev/null @@ -1,289 +0,0 @@ -#ifndef foosddaemonhfoo -#define foosddaemonhfoo - -/*** - This file is part of systemd. - - Copyright 2013 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 - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -/* - The following functionality is provided: - - - Support for logging with log levels on stderr - - File descriptor passing for socket-based activation - - Daemon startup and status notification - - Detection of systemd boots - - See sd-daemon(3) for more information. -*/ - -/* - Log levels for usage on stderr: - - fprintf(stderr, SD_NOTICE "Hello World!\n"); - - This is similar to printk() usage in the kernel. -*/ -#define SD_EMERG "<0>" /* system is unusable */ -#define SD_ALERT "<1>" /* action must be taken immediately */ -#define SD_CRIT "<2>" /* critical conditions */ -#define SD_ERR "<3>" /* error conditions */ -#define SD_WARNING "<4>" /* warning conditions */ -#define SD_NOTICE "<5>" /* normal but significant condition */ -#define SD_INFO "<6>" /* informational */ -#define SD_DEBUG "<7>" /* debug-level messages */ - -/* The first passed file descriptor is fd 3 */ -#define SD_LISTEN_FDS_START 3 - -/* - Returns how many file descriptors have been passed, or a negative - errno code on failure. Optionally, removes the $LISTEN_FDS and - $LISTEN_PID file descriptors from the environment (recommended, but - problematic in threaded environments). If r is the return value of - this function you'll find the file descriptors passed as fds - SD_LISTEN_FDS_START to SD_LISTEN_FDS_START+r-1. Returns a negative - errno style error code on failure. This function call ensures that - the FD_CLOEXEC flag is set for the passed file descriptors, to make - sure they are not passed on to child processes. If FD_CLOEXEC shall - not be set, the caller needs to unset it after this call for all file - descriptors that are used. - - See sd_listen_fds(3) for more information. -*/ -int sd_listen_fds(int unset_environment); - -int sd_listen_fds_with_names(int unset_environment, char ***names); - -/* - Helper call for identifying a passed file descriptor. Returns 1 if - the file descriptor is a FIFO in the file system stored under the - specified path, 0 otherwise. If path is NULL a path name check will - not be done and the call only verifies if the file descriptor - refers to a FIFO. Returns a negative errno style error code on - failure. - - See sd_is_fifo(3) for more information. -*/ -int sd_is_fifo(int fd, const char *path); - -/* - Helper call for identifying a passed file descriptor. Returns 1 if - the file descriptor is a special character device on the file - system stored under the specified path, 0 otherwise. - If path is NULL a path name check will not be done and the call - only verifies if the file descriptor refers to a special character. - Returns a negative errno style error code on failure. - - See sd_is_special(3) for more information. -*/ -int sd_is_special(int fd, const char *path); - -/* - Helper call for identifying a passed file descriptor. Returns 1 if - the file descriptor is a socket of the specified family (AF_INET, - ...) and type (SOCK_DGRAM, SOCK_STREAM, ...), 0 otherwise. If - family is 0 a socket family check will not be done. If type is 0 a - socket type check will not be done and the call only verifies if - the file descriptor refers to a socket. If listening is > 0 it is - verified that the socket is in listening mode. (i.e. listen() has - been called) If listening is == 0 it is verified that the socket is - not in listening mode. If listening is < 0 no listening mode check - is done. Returns a negative errno style error code on failure. - - See sd_is_socket(3) for more information. -*/ -int sd_is_socket(int fd, int family, int type, int listening); - -/* - Helper call for identifying a passed file descriptor. Returns 1 if - the file descriptor is an Internet socket, of the specified family - (either AF_INET or AF_INET6) and the specified type (SOCK_DGRAM, - SOCK_STREAM, ...), 0 otherwise. If version is 0 a protocol version - check is not done. If type is 0 a socket type check will not be - done. If port is 0 a socket port check will not be done. The - listening flag is used the same way as in sd_is_socket(). Returns a - negative errno style error code on failure. - - See sd_is_socket_inet(3) for more information. -*/ -int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port); - -/* - Helper call for identifying a passed file descriptor. Returns 1 if - the file descriptor is an AF_UNIX socket of the specified type - (SOCK_DGRAM, SOCK_STREAM, ...) and path, 0 otherwise. If type is 0 - a socket type check will not be done. If path is NULL a socket path - check will not be done. For normal AF_UNIX sockets set length to - 0. For abstract namespace sockets set length to the length of the - socket name (including the initial 0 byte), and pass the full - socket path in path (including the initial 0 byte). The listening - flag is used the same way as in sd_is_socket(). Returns a negative - errno style error code on failure. - - See sd_is_socket_unix(3) for more information. -*/ -int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length); - -/* - Helper call for identifying a passed file descriptor. Returns 1 if - the file descriptor is a POSIX Message Queue of the specified name, - 0 otherwise. If path is NULL a message queue name check is not - done. Returns a negative errno style error code on failure. - - See sd_is_mq(3) for more information. -*/ -int sd_is_mq(int fd, const char *path); - -/* - Informs systemd about changed daemon state. This takes a number of - newline separated environment-style variable assignments in a - string. The following variables are known: - - READY=1 Tells systemd that daemon startup is finished (only - relevant for services of Type=notify). The passed - argument is a boolean "1" or "0". Since there is - little value in signaling non-readiness the only - value daemons should send is "READY=1". - - STATUS=... Passes a single-line status string back to systemd - that describes the daemon state. This is free-form - and can be used for various purposes: general state - feedback, fsck-like programs could pass completion - percentages and failing programs could pass a human - readable error message. Example: "STATUS=Completed - 66% of file system check..." - - ERRNO=... If a daemon fails, the errno-style error code, - formatted as string. Example: "ERRNO=2" for ENOENT. - - BUSERROR=... If a daemon fails, the D-Bus error-style error - code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut" - - MAINPID=... The main pid of a daemon, in case systemd did not - fork off the process itself. Example: "MAINPID=4711" - - WATCHDOG=1 Tells systemd to update the watchdog timestamp. - Services using this feature should do this in - regular intervals. A watchdog framework can use the - timestamps to detect failed services. Also see - sd_watchdog_enabled() below. - - FDSTORE=1 Store the file descriptors passed along with the - message in the per-service file descriptor store, - and pass them to the main process again on next - invocation. This variable is only supported with - sd_pid_notify_with_fds(). - - Daemons can choose to send additional variables. However, it is - recommended to prefix variable names not listed above with X_. - - Returns a negative errno-style error code on failure. Returns > 0 - if systemd could be notified, 0 if it couldn't possibly because - systemd is not running. - - Example: When a daemon finished starting up, it could issue this - call to notify systemd about it: - - sd_notify(0, "READY=1"); - - See sd_notifyf() for more complete examples. - - See sd_notify(3) for more information. -*/ -int sd_notify(int unset_environment, const char *state); - -/* - Similar to sd_notify() but takes a format string. - - Example 1: A daemon could send the following after initialization: - - sd_notifyf(0, "READY=1\n" - "STATUS=Processing requests...\n" - "MAINPID=%lu", - (unsigned long) getpid()); - - Example 2: A daemon could send the following shortly before - exiting, on failure: - - sd_notifyf(0, "STATUS=Failed to start up: %s\n" - "ERRNO=%i", - strerror(errno), - errno); - - See sd_notifyf(3) for more information. -*/ -int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_(2,3); - -/* - Similar to sd_notify(), but send the message on behalf of another - process, if the appropriate permissions are available. -*/ -int sd_pid_notify(pid_t pid, int unset_environment, const char *state); - -/* - Similar to sd_notifyf(), but send the message on behalf of another - process, if the appropriate permissions are available. -*/ -int sd_pid_notifyf(pid_t pid, int unset_environment, const char *format, ...) _sd_printf_(3,4); - -/* - Similar to sd_pid_notify(), but also passes the specified fd array - to the service manager for storage. This is particularly useful for - FDSTORE=1 messages. -*/ -int sd_pid_notify_with_fds(pid_t pid, int unset_environment, const char *state, const int *fds, unsigned n_fds); - -/* - Returns > 0 if the system was booted with systemd. Returns < 0 on - error. Returns 0 if the system was not booted with systemd. Note - that all of the functions above handle non-systemd boots just - fine. You should NOT protect them with a call to this function. Also - note that this function checks whether the system, not the user - session is controlled by systemd. However the functions above work - for both user and system services. - - See sd_booted(3) for more information. -*/ -int sd_booted(void); - -/* - Returns > 0 if the service manager expects watchdog keep-alive - events to be sent regularly via sd_notify(0, "WATCHDOG=1"). Returns - 0 if it does not expect this. If the usec argument is non-NULL - returns the watchdog timeout in µs after which the service manager - will act on a process that has not sent a watchdog keep alive - message. This function is useful to implement services that - recognize automatically if they are being run under supervision of - systemd with WatchdogSec= set. It is recommended for clients to - generate keep-alive pings via sd_notify(0, "WATCHDOG=1") every half - of the returned time. - - See sd_watchdog_enabled(3) for more information. -*/ -int sd_watchdog_enabled(int unset_environment, uint64_t *usec); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-device.h b/src/systemd/sd-device.h deleted file mode 100644 index c1d07561d7..0000000000 --- a/src/systemd/sd-device.h +++ /dev/null @@ -1,101 +0,0 @@ -#ifndef foosddevicehfoo -#define foosddevicehfoo - -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2014-2015 Tom Gundersen - - 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 -#include - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -typedef struct sd_device sd_device; -typedef struct sd_device_enumerator sd_device_enumerator; - -/* device */ - -sd_device *sd_device_ref(sd_device *device); -sd_device *sd_device_unref(sd_device *device); - -int sd_device_new_from_syspath(sd_device **ret, const char *syspath); -int sd_device_new_from_devnum(sd_device **ret, char type, dev_t devnum); -int sd_device_new_from_subsystem_sysname(sd_device **ret, const char *subsystem, const char *sysname); -int sd_device_new_from_device_id(sd_device **ret, const char *id); - -int sd_device_get_parent(sd_device *child, sd_device **ret); -int sd_device_get_parent_with_subsystem_devtype(sd_device *child, const char *subsystem, const char *devtype, sd_device **ret); - -int sd_device_get_syspath(sd_device *device, const char **ret); -int sd_device_get_subsystem(sd_device *device, const char **ret); -int sd_device_get_devtype(sd_device *device, const char **ret); -int sd_device_get_devnum(sd_device *device, dev_t *devnum); -int sd_device_get_ifindex(sd_device *device, int *ifindex); -int sd_device_get_driver(sd_device *device, const char **ret); -int sd_device_get_devpath(sd_device *device, const char **ret); -int sd_device_get_devname(sd_device *device, const char **ret); -int sd_device_get_sysname(sd_device *device, const char **ret); -int sd_device_get_sysnum(sd_device *device, const char **ret); - -int sd_device_get_is_initialized(sd_device *device, int *initialized); -int sd_device_get_usec_since_initialized(sd_device *device, uint64_t *usec); - -const char *sd_device_get_tag_first(sd_device *device); -const char *sd_device_get_tag_next(sd_device *device); -const char *sd_device_get_devlink_first(sd_device *device); -const char *sd_device_get_devlink_next(sd_device *device); -const char *sd_device_get_property_first(sd_device *device, const char **value); -const char *sd_device_get_property_next(sd_device *device, const char **value); -const char *sd_device_get_sysattr_first(sd_device *device); -const char *sd_device_get_sysattr_next(sd_device *device); - -int sd_device_has_tag(sd_device *device, const char *tag); -int sd_device_get_property_value(sd_device *device, const char *key, const char **value); -int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **_value); - -int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, char *value); - -/* device enumerator */ - -int sd_device_enumerator_new(sd_device_enumerator **ret); -sd_device_enumerator *sd_device_enumerator_ref(sd_device_enumerator *enumerator); -sd_device_enumerator *sd_device_enumerator_unref(sd_device_enumerator *enumerator); - -sd_device *sd_device_enumerator_get_device_first(sd_device_enumerator *enumerator); -sd_device *sd_device_enumerator_get_device_next(sd_device_enumerator *enumerator); -sd_device *sd_device_enumerator_get_subsystem_first(sd_device_enumerator *enumerator); -sd_device *sd_device_enumerator_get_subsystem_next(sd_device_enumerator *enumerator); - -int sd_device_enumerator_add_match_subsystem(sd_device_enumerator *enumerator, const char *subsystem, int match); -int sd_device_enumerator_add_match_sysattr(sd_device_enumerator *enumerator, const char *sysattr, const char *value, int match); -int sd_device_enumerator_add_match_property(sd_device_enumerator *enumerator, const char *property, const char *value); -int sd_device_enumerator_add_match_sysname(sd_device_enumerator *enumerator, const char *sysname); -int sd_device_enumerator_add_match_tag(sd_device_enumerator *enumerator, const char *tag); -int sd_device_enumerator_add_match_parent(sd_device_enumerator *enumerator, sd_device *parent); -int sd_device_enumerator_allow_uninitialized(sd_device_enumerator *enumerator); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_device, sd_device_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_device_enumerator, sd_device_enumerator_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h deleted file mode 100644 index 20b8c2873f..0000000000 --- a/src/systemd/sd-dhcp-client.h +++ /dev/null @@ -1,158 +0,0 @@ -#ifndef foosddhcpclienthfoo -#define foosddhcpclienthfoo - -/*** - This file is part of systemd. - - Copyright (C) 2013 Intel Corporation. All rights reserved. - - 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 -#include -#include - -#include "sd-dhcp-lease.h" -#include "sd-event.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -enum { - SD_DHCP_CLIENT_EVENT_STOP = 0, - SD_DHCP_CLIENT_EVENT_IP_ACQUIRE = 1, - SD_DHCP_CLIENT_EVENT_IP_CHANGE = 2, - SD_DHCP_CLIENT_EVENT_EXPIRED = 3, - SD_DHCP_CLIENT_EVENT_RENEW = 4, -}; - -enum { - SD_DHCP_OPTION_PAD = 0, - SD_DHCP_OPTION_SUBNET_MASK = 1, - SD_DHCP_OPTION_TIME_OFFSET = 2, - SD_DHCP_OPTION_ROUTER = 3, - SD_DHCP_OPTION_DOMAIN_NAME_SERVER = 6, - SD_DHCP_OPTION_HOST_NAME = 12, - SD_DHCP_OPTION_BOOT_FILE_SIZE = 13, - SD_DHCP_OPTION_DOMAIN_NAME = 15, - SD_DHCP_OPTION_ROOT_PATH = 17, - SD_DHCP_OPTION_ENABLE_IP_FORWARDING = 19, - SD_DHCP_OPTION_ENABLE_IP_FORWARDING_NL = 20, - SD_DHCP_OPTION_POLICY_FILTER = 21, - SD_DHCP_OPTION_INTERFACE_MDR = 22, - SD_DHCP_OPTION_INTERFACE_TTL = 23, - SD_DHCP_OPTION_INTERFACE_MTU_AGING_TIMEOUT = 24, - SD_DHCP_OPTION_INTERFACE_MTU = 26, - SD_DHCP_OPTION_BROADCAST = 28, - SD_DHCP_OPTION_STATIC_ROUTE = 33, - SD_DHCP_OPTION_NTP_SERVER = 42, - SD_DHCP_OPTION_VENDOR_SPECIFIC = 43, - SD_DHCP_OPTION_REQUESTED_IP_ADDRESS = 50, - SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME = 51, - SD_DHCP_OPTION_OVERLOAD = 52, - SD_DHCP_OPTION_MESSAGE_TYPE = 53, - SD_DHCP_OPTION_SERVER_IDENTIFIER = 54, - SD_DHCP_OPTION_PARAMETER_REQUEST_LIST = 55, - SD_DHCP_OPTION_ERROR_MESSAGE = 56, - SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE = 57, - SD_DHCP_OPTION_RENEWAL_T1_TIME = 58, - SD_DHCP_OPTION_REBINDING_T2_TIME = 59, - SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER = 60, - SD_DHCP_OPTION_CLIENT_IDENTIFIER = 61, - SD_DHCP_OPTION_FQDN = 81, - SD_DHCP_OPTION_NEW_POSIX_TIMEZONE = 100, - SD_DHCP_OPTION_NEW_TZDB_TIMEZONE = 101, - SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE = 121, - SD_DHCP_OPTION_PRIVATE_BASE = 224, - SD_DHCP_OPTION_PRIVATE_LAST = 254, - SD_DHCP_OPTION_END = 255, -}; - -typedef struct sd_dhcp_client sd_dhcp_client; - -typedef void (*sd_dhcp_client_callback_t)(sd_dhcp_client *client, int event, void *userdata); -int sd_dhcp_client_set_callback( - sd_dhcp_client *client, - sd_dhcp_client_callback_t cb, - void *userdata); - -int sd_dhcp_client_set_request_option( - sd_dhcp_client *client, - uint8_t option); -int sd_dhcp_client_set_request_address( - sd_dhcp_client *client, - const struct in_addr *last_address); -int sd_dhcp_client_set_request_broadcast( - sd_dhcp_client *client, - int broadcast); -int sd_dhcp_client_set_index( - sd_dhcp_client *client, - int interface_index); -int sd_dhcp_client_set_mac( - sd_dhcp_client *client, - const uint8_t *addr, - size_t addr_len, - uint16_t arp_type); -int sd_dhcp_client_set_client_id( - sd_dhcp_client *client, - uint8_t type, - const uint8_t *data, - size_t data_len); -int sd_dhcp_client_set_iaid_duid( - sd_dhcp_client *client, - uint32_t iaid, - uint16_t duid_type, - const void *duid, - size_t duid_len); -int sd_dhcp_client_get_client_id( - sd_dhcp_client *client, - uint8_t *type, - const uint8_t **data, - size_t *data_len); -int sd_dhcp_client_set_mtu( - sd_dhcp_client *client, - uint32_t mtu); -int sd_dhcp_client_set_hostname( - sd_dhcp_client *client, - const char *hostname); -int sd_dhcp_client_set_vendor_class_identifier( - sd_dhcp_client *client, - const char *vci); -int sd_dhcp_client_get_lease( - sd_dhcp_client *client, - sd_dhcp_lease **ret); - -int sd_dhcp_client_stop(sd_dhcp_client *client); -int sd_dhcp_client_start(sd_dhcp_client *client); - -sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client); -sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client); - -int sd_dhcp_client_new(sd_dhcp_client **ret); - -int sd_dhcp_client_attach_event( - sd_dhcp_client *client, - sd_event *event, - int64_t priority); -int sd_dhcp_client_detach_event(sd_dhcp_client *client); -sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_client, sd_dhcp_client_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-dhcp-lease.h b/src/systemd/sd-dhcp-lease.h deleted file mode 100644 index 2f565ca825..0000000000 --- a/src/systemd/sd-dhcp-lease.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef foosddhcpleasehfoo -#define foosddhcpleasehfoo - -/*** - This file is part of systemd. - - Copyright (C) 2013 Intel Corporation. All rights reserved. - Copyright (C) 2014 Tom Gundersen - - 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 -#include -#include - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -typedef struct sd_dhcp_lease sd_dhcp_lease; -typedef struct sd_dhcp_route sd_dhcp_route; - -sd_dhcp_lease *sd_dhcp_lease_ref(sd_dhcp_lease *lease); -sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease); - -int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr); -int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint32_t *lifetime); -int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint32_t *t1); -int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint32_t *t2); -int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr); -int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr); -int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, struct in_addr *addr); -int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr); -int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr); -int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr); -int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr); -int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu); -int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname); -int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname); -int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path); -int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes); -int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len); -int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len); -int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **timezone); - -int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination); -int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length); -int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_lease, sd_dhcp_lease_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h deleted file mode 100644 index d4517a26d6..0000000000 --- a/src/systemd/sd-dhcp-server.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef foosddhcpserverhfoo -#define foosddhcpserverhfoo - -/*** - This file is part of systemd. - - Copyright (C) 2013 Intel Corporation. All rights reserved. - Copyright (C) 2014 Tom Gundersen - - 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 - -#include "sd-event.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -typedef struct sd_dhcp_server sd_dhcp_server; - -int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex); - -sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server); -sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server); - -int sd_dhcp_server_attach_event(sd_dhcp_server *client, sd_event *event, int64_t priority); -int sd_dhcp_server_detach_event(sd_dhcp_server *client); -sd_event *sd_dhcp_server_get_event(sd_dhcp_server *client); - -int sd_dhcp_server_is_running(sd_dhcp_server *server); - -int sd_dhcp_server_start(sd_dhcp_server *server); -int sd_dhcp_server_stop(sd_dhcp_server *server); - -int sd_dhcp_server_configure_pool(sd_dhcp_server *server, struct in_addr *address, unsigned char prefixlen, uint32_t offset, uint32_t size); - -int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *timezone); -int sd_dhcp_server_set_dns(sd_dhcp_server *server, const struct in_addr ntp[], unsigned n); -int sd_dhcp_server_set_ntp(sd_dhcp_server *server, const struct in_addr dns[], unsigned n); -int sd_dhcp_server_set_emit_router(sd_dhcp_server *server, int enabled); - -int sd_dhcp_server_set_max_lease_time(sd_dhcp_server *server, uint32_t t); -int sd_dhcp_server_set_default_lease_time(sd_dhcp_server *server, uint32_t t); - -int sd_dhcp_server_forcerenew(sd_dhcp_server *server); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_server, sd_dhcp_server_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h deleted file mode 100644 index 90f62eaca4..0000000000 --- a/src/systemd/sd-dhcp6-client.h +++ /dev/null @@ -1,135 +0,0 @@ -#ifndef foosddhcp6clienthfoo -#define foosddhcp6clienthfoo - -/*** - This file is part of systemd. - - Copyright (C) 2014 Intel Corporation. All rights reserved. - - 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 -#include - -#include "sd-dhcp6-lease.h" -#include "sd-event.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -enum { - SD_DHCP6_CLIENT_EVENT_STOP = 0, - SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE = 10, - SD_DHCP6_CLIENT_EVENT_RETRANS_MAX = 11, - SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE = 12, - SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST = 13, -}; - -enum { - SD_DHCP6_OPTION_CLIENTID = 1, - SD_DHCP6_OPTION_SERVERID = 2, - SD_DHCP6_OPTION_IA_NA = 3, - SD_DHCP6_OPTION_IA_TA = 4, - SD_DHCP6_OPTION_IAADDR = 5, - SD_DHCP6_OPTION_ORO = 6, - SD_DHCP6_OPTION_PREFERENCE = 7, - SD_DHCP6_OPTION_ELAPSED_TIME = 8, - SD_DHCP6_OPTION_RELAY_MSG = 9, - /* option code 10 is unassigned */ - SD_DHCP6_OPTION_AUTH = 11, - SD_DHCP6_OPTION_UNICAST = 12, - SD_DHCP6_OPTION_STATUS_CODE = 13, - SD_DHCP6_OPTION_RAPID_COMMIT = 14, - SD_DHCP6_OPTION_USER_CLASS = 15, - SD_DHCP6_OPTION_VENDOR_CLASS = 16, - SD_DHCP6_OPTION_VENDOR_OPTS = 17, - SD_DHCP6_OPTION_INTERFACE_ID = 18, - SD_DHCP6_OPTION_RECONF_MSG = 19, - SD_DHCP6_OPTION_RECONF_ACCEPT = 20, - - SD_DHCP6_OPTION_DNS_SERVERS = 23, /* RFC 3646 */ - SD_DHCP6_OPTION_DOMAIN_LIST = 24, /* RFC 3646 */ - - SD_DHCP6_OPTION_SNTP_SERVERS = 31, /* RFC 4075, deprecated */ - - /* option code 35 is unassigned */ - - SD_DHCP6_OPTION_NTP_SERVER = 56, /* RFC 5908 */ - - /* option codes 89-142 are unassigned */ - /* option codes 144-65535 are unassigned */ -}; - -typedef struct sd_dhcp6_client sd_dhcp6_client; - -typedef void (*sd_dhcp6_client_callback_t)(sd_dhcp6_client *client, int event, void *userdata); -int sd_dhcp6_client_set_callback( - sd_dhcp6_client *client, - sd_dhcp6_client_callback_t cb, - void *userdata); - -int sd_dhcp6_client_set_index( - sd_dhcp6_client *client, - int interface_index); -int sd_dhcp6_client_set_local_address( - sd_dhcp6_client *client, - const struct in6_addr *local_address); -int sd_dhcp6_client_set_mac( - sd_dhcp6_client *client, - const uint8_t *addr, - size_t addr_len, - uint16_t arp_type); -int sd_dhcp6_client_set_duid( - sd_dhcp6_client *client, - uint16_t duid_type, - const void *duid, - size_t duid_len); -int sd_dhcp6_client_set_iaid( - sd_dhcp6_client *client, - uint32_t iaid); -int sd_dhcp6_client_set_information_request( - sd_dhcp6_client *client, - int enabled); -int sd_dhcp6_client_get_information_request( - sd_dhcp6_client *client, - int *enabled); -int sd_dhcp6_client_set_request_option( - sd_dhcp6_client *client, - uint16_t option); - -int sd_dhcp6_client_get_lease( - sd_dhcp6_client *client, - sd_dhcp6_lease **ret); - -int sd_dhcp6_client_stop(sd_dhcp6_client *client); -int sd_dhcp6_client_start(sd_dhcp6_client *client); -int sd_dhcp6_client_is_running(sd_dhcp6_client *client); -int sd_dhcp6_client_attach_event( - sd_dhcp6_client *client, - sd_event *event, - int64_t priority); -int sd_dhcp6_client_detach_event(sd_dhcp6_client *client); -sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client); -sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client); -sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client); -int sd_dhcp6_client_new(sd_dhcp6_client **ret); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_client, sd_dhcp6_client_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-dhcp6-lease.h b/src/systemd/sd-dhcp6-lease.h deleted file mode 100644 index 184fbb8e0d..0000000000 --- a/src/systemd/sd-dhcp6-lease.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef foosddhcp6leasehfoo -#define foosddhcp6leasehfoo - -/*** - This file is part of systemd. - - Copyright (C) 2014 Tom Gundersen - Copyright (C) 2014-2015 Intel Corporation. All rights reserved. - - 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 - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -typedef struct sd_dhcp6_lease sd_dhcp6_lease; - -void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease); -int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, - struct in6_addr *addr, - uint32_t *lifetime_preferred, - uint32_t *lifetime_valid); - -int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, struct in6_addr **addrs); -int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains); -int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, - struct in6_addr **addrs); -int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn); - -sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease); -sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_lease, sd_dhcp6_lease_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-event.h b/src/systemd/sd-event.h deleted file mode 100644 index 531ace1c34..0000000000 --- a/src/systemd/sd-event.h +++ /dev/null @@ -1,142 +0,0 @@ -#ifndef foosdeventhfoo -#define foosdeventhfoo - -/*** - This file is part of systemd. - - Copyright 2013 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 -#include -#include -#include - -#include "_sd-common.h" - -/* - Why is this better than pure epoll? - - - Supports event source prioritization - - Scales better with a large number of time events because it does not require one timerfd each - - Automatically tries to coalesce timer events system-wide - - Handles signals and child PIDs -*/ - -_SD_BEGIN_DECLARATIONS; - -typedef struct sd_event sd_event; -typedef struct sd_event_source sd_event_source; - -enum { - SD_EVENT_OFF = 0, - SD_EVENT_ON = 1, - SD_EVENT_ONESHOT = -1 -}; - -enum { - SD_EVENT_INITIAL, - SD_EVENT_ARMED, - SD_EVENT_PENDING, - SD_EVENT_RUNNING, - SD_EVENT_EXITING, - SD_EVENT_FINISHED, - SD_EVENT_PREPARING -}; - -enum { - /* And everything in-between and outside is good too */ - SD_EVENT_PRIORITY_IMPORTANT = -100, - SD_EVENT_PRIORITY_NORMAL = 0, - SD_EVENT_PRIORITY_IDLE = 100 -}; - -typedef int (*sd_event_handler_t)(sd_event_source *s, void *userdata); -typedef int (*sd_event_io_handler_t)(sd_event_source *s, int fd, uint32_t revents, void *userdata); -typedef int (*sd_event_time_handler_t)(sd_event_source *s, uint64_t usec, void *userdata); -typedef int (*sd_event_signal_handler_t)(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata); -#if defined __USE_POSIX199309 || defined __USE_XOPEN_EXTENDED -typedef int (*sd_event_child_handler_t)(sd_event_source *s, const siginfo_t *si, void *userdata); -#else -typedef void* sd_event_child_handler_t; -#endif - -int sd_event_default(sd_event **e); - -int sd_event_new(sd_event **e); -sd_event* sd_event_ref(sd_event *e); -sd_event* sd_event_unref(sd_event *e); - -int sd_event_add_io(sd_event *e, sd_event_source **s, int fd, uint32_t events, sd_event_io_handler_t callback, void *userdata); -int sd_event_add_time(sd_event *e, sd_event_source **s, clockid_t clock, uint64_t usec, uint64_t accuracy, sd_event_time_handler_t callback, void *userdata); -int sd_event_add_signal(sd_event *e, sd_event_source **s, int sig, sd_event_signal_handler_t callback, void *userdata); -int sd_event_add_child(sd_event *e, sd_event_source **s, pid_t pid, int options, sd_event_child_handler_t callback, void *userdata); -int sd_event_add_defer(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata); -int sd_event_add_post(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata); -int sd_event_add_exit(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata); - -int sd_event_prepare(sd_event *e); -int sd_event_wait(sd_event *e, uint64_t usec); -int sd_event_dispatch(sd_event *e); -int sd_event_run(sd_event *e, uint64_t usec); -int sd_event_loop(sd_event *e); -int sd_event_exit(sd_event *e, int code); - -int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec); - -int sd_event_get_fd(sd_event *e); -int sd_event_get_state(sd_event *e); -int sd_event_get_tid(sd_event *e, pid_t *tid); -int sd_event_get_exit_code(sd_event *e, int *code); -int sd_event_set_watchdog(sd_event *e, int b); -int sd_event_get_watchdog(sd_event *e); - -sd_event_source* sd_event_source_ref(sd_event_source *s); -sd_event_source* sd_event_source_unref(sd_event_source *s); - -sd_event *sd_event_source_get_event(sd_event_source *s); -void* sd_event_source_get_userdata(sd_event_source *s); -void* sd_event_source_set_userdata(sd_event_source *s, void *userdata); - -int sd_event_source_set_description(sd_event_source *s, const char *description); -int sd_event_source_get_description(sd_event_source *s, const char **description); -int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t callback); -int sd_event_source_get_pending(sd_event_source *s); -int sd_event_source_get_priority(sd_event_source *s, int64_t *priority); -int sd_event_source_set_priority(sd_event_source *s, int64_t priority); -int sd_event_source_get_enabled(sd_event_source *s, int *enabled); -int sd_event_source_set_enabled(sd_event_source *s, int enabled); -int sd_event_source_get_io_fd(sd_event_source *s); -int sd_event_source_set_io_fd(sd_event_source *s, int fd); -int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events); -int sd_event_source_set_io_events(sd_event_source *s, uint32_t events); -int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revents); -int sd_event_source_get_time(sd_event_source *s, uint64_t *usec); -int sd_event_source_set_time(sd_event_source *s, uint64_t usec); -int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *usec); -int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec); -int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock); -int sd_event_source_get_signal(sd_event_source *s); -int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid); - -/* Define helpers so that __attribute__((cleanup(sd_event_unrefp))) and similar may be used. */ -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event, sd_event_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event_source, sd_event_source_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-hwdb.h b/src/systemd/sd-hwdb.h deleted file mode 100644 index 7105920492..0000000000 --- a/src/systemd/sd-hwdb.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef foosdhwdbhfoo -#define foosdhwdbhfoo - -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2014 Tom Gundersen - - 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 "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -typedef struct sd_hwdb sd_hwdb; - -sd_hwdb *sd_hwdb_ref(sd_hwdb *hwdb); -sd_hwdb *sd_hwdb_unref(sd_hwdb *hwdb); - -int sd_hwdb_new(sd_hwdb **ret); - -int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **value); - -int sd_hwdb_seek(sd_hwdb *hwdb, const char *modalias); -int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **key, const char **value); - -/* the inverse condition avoids ambiguity of dangling 'else' after the macro */ -#define SD_HWDB_FOREACH_PROPERTY(hwdb, modalias, key, value) \ - if (sd_hwdb_seek(hwdb, modalias) < 0) { } \ - else while (sd_hwdb_enumerate(hwdb, &(key), &(value)) > 0) - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_hwdb, sd_hwdb_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-id128.h b/src/systemd/sd-id128.h deleted file mode 100644 index 4dff0b9b81..0000000000 --- a/src/systemd/sd-id128.h +++ /dev/null @@ -1,115 +0,0 @@ -#ifndef foosdid128hfoo -#define foosdid128hfoo - -/*** - This file is part of systemd. - - Copyright 2011 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 - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -/* 128-bit ID APIs. See sd-id128(3) for more information. */ - -typedef union sd_id128 sd_id128_t; - -union sd_id128 { - uint8_t bytes[16]; - uint64_t qwords[2]; -}; - -#define SD_ID128_STRING_MAX 33 - -char *sd_id128_to_string(sd_id128_t id, char s[SD_ID128_STRING_MAX]); - -int sd_id128_from_string(const char *s, sd_id128_t *ret); - -int sd_id128_randomize(sd_id128_t *ret); - -int sd_id128_get_machine(sd_id128_t *ret); - -int sd_id128_get_boot(sd_id128_t *ret); - -#define SD_ID128_MAKE(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \ - ((const sd_id128_t) { .bytes = { 0x##v0, 0x##v1, 0x##v2, 0x##v3, 0x##v4, 0x##v5, 0x##v6, 0x##v7, \ - 0x##v8, 0x##v9, 0x##v10, 0x##v11, 0x##v12, 0x##v13, 0x##v14, 0x##v15 }}) - -#define SD_ID128_ARRAY(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \ - { .bytes = { 0x##v0, 0x##v1, 0x##v2, 0x##v3, 0x##v4, 0x##v5, 0x##v6, 0x##v7, \ - 0x##v8, 0x##v9, 0x##v10, 0x##v11, 0x##v12, 0x##v13, 0x##v14, 0x##v15 }} - -/* Note that SD_ID128_FORMAT_VAL will evaluate the passed argument 16 - * times. It is hence not a good idea to call this macro with an - * expensive function as parameter or an expression with side - * effects */ - -#define SD_ID128_FORMAT_STR "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" -#define SD_ID128_FORMAT_VAL(x) (x).bytes[0], (x).bytes[1], (x).bytes[2], (x).bytes[3], (x).bytes[4], (x).bytes[5], (x).bytes[6], (x).bytes[7], (x).bytes[8], (x).bytes[9], (x).bytes[10], (x).bytes[11], (x).bytes[12], (x).bytes[13], (x).bytes[14], (x).bytes[15] - -#define SD_ID128_CONST_STR(x) \ - ((const char[SD_ID128_STRING_MAX]) { \ - ((x).bytes[0] >> 4) >= 10 ? 'a' + ((x).bytes[0] >> 4) - 10 : '0' + ((x).bytes[0] >> 4), \ - ((x).bytes[0] & 15) >= 10 ? 'a' + ((x).bytes[0] & 15) - 10 : '0' + ((x).bytes[0] & 15), \ - ((x).bytes[1] >> 4) >= 10 ? 'a' + ((x).bytes[1] >> 4) - 10 : '0' + ((x).bytes[1] >> 4), \ - ((x).bytes[1] & 15) >= 10 ? 'a' + ((x).bytes[1] & 15) - 10 : '0' + ((x).bytes[1] & 15), \ - ((x).bytes[2] >> 4) >= 10 ? 'a' + ((x).bytes[2] >> 4) - 10 : '0' + ((x).bytes[2] >> 4), \ - ((x).bytes[2] & 15) >= 10 ? 'a' + ((x).bytes[2] & 15) - 10 : '0' + ((x).bytes[2] & 15), \ - ((x).bytes[3] >> 4) >= 10 ? 'a' + ((x).bytes[3] >> 4) - 10 : '0' + ((x).bytes[3] >> 4), \ - ((x).bytes[3] & 15) >= 10 ? 'a' + ((x).bytes[3] & 15) - 10 : '0' + ((x).bytes[3] & 15), \ - ((x).bytes[4] >> 4) >= 10 ? 'a' + ((x).bytes[4] >> 4) - 10 : '0' + ((x).bytes[4] >> 4), \ - ((x).bytes[4] & 15) >= 10 ? 'a' + ((x).bytes[4] & 15) - 10 : '0' + ((x).bytes[4] & 15), \ - ((x).bytes[5] >> 4) >= 10 ? 'a' + ((x).bytes[5] >> 4) - 10 : '0' + ((x).bytes[5] >> 4), \ - ((x).bytes[5] & 15) >= 10 ? 'a' + ((x).bytes[5] & 15) - 10 : '0' + ((x).bytes[5] & 15), \ - ((x).bytes[6] >> 4) >= 10 ? 'a' + ((x).bytes[6] >> 4) - 10 : '0' + ((x).bytes[6] >> 4), \ - ((x).bytes[6] & 15) >= 10 ? 'a' + ((x).bytes[6] & 15) - 10 : '0' + ((x).bytes[6] & 15), \ - ((x).bytes[7] >> 4) >= 10 ? 'a' + ((x).bytes[7] >> 4) - 10 : '0' + ((x).bytes[7] >> 4), \ - ((x).bytes[7] & 15) >= 10 ? 'a' + ((x).bytes[7] & 15) - 10 : '0' + ((x).bytes[7] & 15), \ - ((x).bytes[8] >> 4) >= 10 ? 'a' + ((x).bytes[8] >> 4) - 10 : '0' + ((x).bytes[8] >> 4), \ - ((x).bytes[8] & 15) >= 10 ? 'a' + ((x).bytes[8] & 15) - 10 : '0' + ((x).bytes[8] & 15), \ - ((x).bytes[9] >> 4) >= 10 ? 'a' + ((x).bytes[9] >> 4) - 10 : '0' + ((x).bytes[9] >> 4), \ - ((x).bytes[9] & 15) >= 10 ? 'a' + ((x).bytes[9] & 15) - 10 : '0' + ((x).bytes[9] & 15), \ - ((x).bytes[10] >> 4) >= 10 ? 'a' + ((x).bytes[10] >> 4) - 10 : '0' + ((x).bytes[10] >> 4), \ - ((x).bytes[10] & 15) >= 10 ? 'a' + ((x).bytes[10] & 15) - 10 : '0' + ((x).bytes[10] & 15), \ - ((x).bytes[11] >> 4) >= 10 ? 'a' + ((x).bytes[11] >> 4) - 10 : '0' + ((x).bytes[11] >> 4), \ - ((x).bytes[11] & 15) >= 10 ? 'a' + ((x).bytes[11] & 15) - 10 : '0' + ((x).bytes[11] & 15), \ - ((x).bytes[12] >> 4) >= 10 ? 'a' + ((x).bytes[12] >> 4) - 10 : '0' + ((x).bytes[12] >> 4), \ - ((x).bytes[12] & 15) >= 10 ? 'a' + ((x).bytes[12] & 15) - 10 : '0' + ((x).bytes[12] & 15), \ - ((x).bytes[13] >> 4) >= 10 ? 'a' + ((x).bytes[13] >> 4) - 10 : '0' + ((x).bytes[13] >> 4), \ - ((x).bytes[13] & 15) >= 10 ? 'a' + ((x).bytes[13] & 15) - 10 : '0' + ((x).bytes[13] & 15), \ - ((x).bytes[14] >> 4) >= 10 ? 'a' + ((x).bytes[14] >> 4) - 10 : '0' + ((x).bytes[14] >> 4), \ - ((x).bytes[14] & 15) >= 10 ? 'a' + ((x).bytes[14] & 15) - 10 : '0' + ((x).bytes[14] & 15), \ - ((x).bytes[15] >> 4) >= 10 ? 'a' + ((x).bytes[15] >> 4) - 10 : '0' + ((x).bytes[15] >> 4), \ - ((x).bytes[15] & 15) >= 10 ? 'a' + ((x).bytes[15] & 15) - 10 : '0' + ((x).bytes[15] & 15), \ - 0 }) - -_sd_pure_ static __inline__ int sd_id128_equal(sd_id128_t a, sd_id128_t b) { - return memcmp(&a, &b, 16) == 0; -} - -_sd_pure_ static __inline__ int sd_id128_is_null(sd_id128_t a) { - return a.qwords[0] == 0 && a.qwords[1] == 0; -} - -#define SD_ID128_NULL ((const sd_id128_t) { .qwords = { 0, 0 }}) - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-ipv4acd.h b/src/systemd/sd-ipv4acd.h deleted file mode 100644 index 9e3e14a30c..0000000000 --- a/src/systemd/sd-ipv4acd.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef foosdipv4acdfoo -#define foosdipv4acdfoo - -/*** - This file is part of systemd. - - Copyright (C) 2014 Axis Communications AB. All rights reserved. - Copyright (C) 2015 Tom Gundersen - - 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 - -#include "sd-event.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -enum { - SD_IPV4ACD_EVENT_STOP = 0, - SD_IPV4ACD_EVENT_BIND = 1, - SD_IPV4ACD_EVENT_CONFLICT = 2, -}; - -typedef struct sd_ipv4acd sd_ipv4acd; -typedef void (*sd_ipv4acd_callback_t)(sd_ipv4acd *ll, int event, void *userdata); - -int sd_ipv4acd_detach_event(sd_ipv4acd *ll); -int sd_ipv4acd_attach_event(sd_ipv4acd *ll, sd_event *event, int64_t priority); -int sd_ipv4acd_get_address(sd_ipv4acd *ll, struct in_addr *address); -int sd_ipv4acd_set_callback(sd_ipv4acd *ll, sd_ipv4acd_callback_t cb, void *userdata); -int sd_ipv4acd_set_mac(sd_ipv4acd *ll, const struct ether_addr *addr); -int sd_ipv4acd_set_index(sd_ipv4acd *ll, int interface_index); -int sd_ipv4acd_set_address(sd_ipv4acd *ll, const struct in_addr *address); -int sd_ipv4acd_is_running(sd_ipv4acd *ll); -int sd_ipv4acd_start(sd_ipv4acd *ll); -int sd_ipv4acd_stop(sd_ipv4acd *ll); -sd_ipv4acd *sd_ipv4acd_ref(sd_ipv4acd *ll); -sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *ll); -int sd_ipv4acd_new(sd_ipv4acd **ret); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ipv4acd, sd_ipv4acd_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-ipv4ll.h b/src/systemd/sd-ipv4ll.h deleted file mode 100644 index 6fa38a2243..0000000000 --- a/src/systemd/sd-ipv4ll.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef foosdipv4llfoo -#define foosdipv4llfoo - -/*** - This file is part of systemd. - - Copyright (C) 2014 Axis Communications AB. All rights reserved. - - 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 - -#include "sd-event.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -enum { - SD_IPV4LL_EVENT_STOP = 0, - SD_IPV4LL_EVENT_BIND = 1, - SD_IPV4LL_EVENT_CONFLICT = 2, -}; - -typedef struct sd_ipv4ll sd_ipv4ll; -typedef void (*sd_ipv4ll_callback_t)(sd_ipv4ll *ll, int event, void *userdata); - -int sd_ipv4ll_detach_event(sd_ipv4ll *ll); -int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority); -int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address); -int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata); -int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr); -int sd_ipv4ll_set_index(sd_ipv4ll *ll, int interface_index); -int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address); -int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, unsigned seed); -int sd_ipv4ll_is_running(sd_ipv4ll *ll); -int sd_ipv4ll_start(sd_ipv4ll *ll); -int sd_ipv4ll_stop(sd_ipv4ll *ll); -sd_ipv4ll *sd_ipv4ll_ref(sd_ipv4ll *ll); -sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll); -int sd_ipv4ll_new (sd_ipv4ll **ret); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ipv4ll, sd_ipv4ll_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-journal.h b/src/systemd/sd-journal.h deleted file mode 100644 index 9c36b27157..0000000000 --- a/src/systemd/sd-journal.h +++ /dev/null @@ -1,175 +0,0 @@ -#ifndef foosdjournalhfoo -#define foosdjournalhfoo - -/*** - This file is part of systemd. - - Copyright 2011 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 -#include -#include -#include - -#include "sd-id128.h" - -#include "_sd-common.h" - -/* Journal APIs. See sd-journal(3) for more information. */ - -_SD_BEGIN_DECLARATIONS; - -/* Write to daemon */ -int sd_journal_print(int priority, const char *format, ...) _sd_printf_(2, 3); -int sd_journal_printv(int priority, const char *format, va_list ap) _sd_printf_(2, 0); -int sd_journal_send(const char *format, ...) _sd_printf_(1, 0) _sd_sentinel_; -int sd_journal_sendv(const struct iovec *iov, int n); -int sd_journal_perror(const char *message); - -/* Used by the macros below. You probably don't want to call this directly. */ -int sd_journal_print_with_location(int priority, const char *file, const char *line, const char *func, const char *format, ...) _sd_printf_(5, 6); -int sd_journal_printv_with_location(int priority, const char *file, const char *line, const char *func, const char *format, va_list ap) _sd_printf_(5, 0); -int sd_journal_send_with_location(const char *file, const char *line, const char *func, const char *format, ...) _sd_printf_(4, 0) _sd_sentinel_; -int sd_journal_sendv_with_location(const char *file, const char *line, const char *func, const struct iovec *iov, int n); -int sd_journal_perror_with_location(const char *file, const char *line, const char *func, const char *message); - -/* implicitly add code location to messages sent, if this is enabled */ -#ifndef SD_JOURNAL_SUPPRESS_LOCATION - -#define sd_journal_print(priority, ...) sd_journal_print_with_location(priority, "CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, __VA_ARGS__) -#define sd_journal_printv(priority, format, ap) sd_journal_printv_with_location(priority, "CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, format, ap) -#define sd_journal_send(...) sd_journal_send_with_location("CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, __VA_ARGS__) -#define sd_journal_sendv(iovec, n) sd_journal_sendv_with_location("CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, iovec, n) -#define sd_journal_perror(message) sd_journal_perror_with_location("CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, message) - -#endif - -int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix); - -/* Browse journal stream */ - -typedef struct sd_journal sd_journal; - -/* Open flags */ -enum { - SD_JOURNAL_LOCAL_ONLY = 1 << 0, - SD_JOURNAL_RUNTIME_ONLY = 1 << 1, - SD_JOURNAL_SYSTEM = 1 << 2, - SD_JOURNAL_CURRENT_USER = 1 << 3, - SD_JOURNAL_OS_ROOT = 1 << 4, - - SD_JOURNAL_SYSTEM_ONLY = SD_JOURNAL_SYSTEM /* deprecated name */ -}; - -/* Wakeup event types */ -enum { - SD_JOURNAL_NOP, - SD_JOURNAL_APPEND, - SD_JOURNAL_INVALIDATE -}; - -int sd_journal_open(sd_journal **ret, int flags); -int sd_journal_open_directory(sd_journal **ret, const char *path, int flags); -int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags); -int sd_journal_open_files(sd_journal **ret, const char **paths, int flags); -int sd_journal_open_files_fd(sd_journal **ret, int fds[], unsigned n_fds, int flags); -int sd_journal_open_container(sd_journal **ret, const char *machine, int flags); /* deprecated */ -void sd_journal_close(sd_journal *j); - -int sd_journal_previous(sd_journal *j); -int sd_journal_next(sd_journal *j); - -int sd_journal_previous_skip(sd_journal *j, uint64_t skip); -int sd_journal_next_skip(sd_journal *j, uint64_t skip); - -int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret); -int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id); - -int sd_journal_set_data_threshold(sd_journal *j, size_t sz); -int sd_journal_get_data_threshold(sd_journal *j, size_t *sz); - -int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *l); -int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *l); -void sd_journal_restart_data(sd_journal *j); - -int sd_journal_add_match(sd_journal *j, const void *data, size_t size); -int sd_journal_add_disjunction(sd_journal *j); -int sd_journal_add_conjunction(sd_journal *j); -void sd_journal_flush_matches(sd_journal *j); - -int sd_journal_seek_head(sd_journal *j); -int sd_journal_seek_tail(sd_journal *j); -int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec); -int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec); -int sd_journal_seek_cursor(sd_journal *j, const char *cursor); - -int sd_journal_get_cursor(sd_journal *j, char **cursor); -int sd_journal_test_cursor(sd_journal *j, const char *cursor); - -int sd_journal_get_cutoff_realtime_usec(sd_journal *j, uint64_t *from, uint64_t *to); -int sd_journal_get_cutoff_monotonic_usec(sd_journal *j, const sd_id128_t boot_id, uint64_t *from, uint64_t *to); - -int sd_journal_get_usage(sd_journal *j, uint64_t *bytes); - -int sd_journal_query_unique(sd_journal *j, const char *field); -int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l); -void sd_journal_restart_unique(sd_journal *j); - -int sd_journal_enumerate_fields(sd_journal *j, const char **field); -void sd_journal_restart_fields(sd_journal *j); - -int sd_journal_get_fd(sd_journal *j); -int sd_journal_get_events(sd_journal *j); -int sd_journal_get_timeout(sd_journal *j, uint64_t *timeout_usec); -int sd_journal_process(sd_journal *j); -int sd_journal_wait(sd_journal *j, uint64_t timeout_usec); -int sd_journal_reliable_fd(sd_journal *j); - -int sd_journal_get_catalog(sd_journal *j, char **text); -int sd_journal_get_catalog_for_message_id(sd_id128_t id, char **text); - -int sd_journal_has_runtime_files(sd_journal *j); -int sd_journal_has_persistent_files(sd_journal *j); - -/* The inverse condition avoids ambiguity of dangling 'else' after the macro */ -#define SD_JOURNAL_FOREACH(j) \ - if (sd_journal_seek_head(j) < 0) { } \ - else while (sd_journal_next(j) > 0) - -/* The inverse condition avoids ambiguity of dangling 'else' after the macro */ -#define SD_JOURNAL_FOREACH_BACKWARDS(j) \ - if (sd_journal_seek_tail(j) < 0) { } \ - else while (sd_journal_previous(j) > 0) - -/* Iterate through the data fields of the current journal entry */ -#define SD_JOURNAL_FOREACH_DATA(j, data, l) \ - for (sd_journal_restart_data(j); sd_journal_enumerate_data((j), &(data), &(l)) > 0; ) - -/* Iterate through the all known values of a specific field */ -#define SD_JOURNAL_FOREACH_UNIQUE(j, data, l) \ - for (sd_journal_restart_unique(j); sd_journal_enumerate_unique((j), &(data), &(l)) > 0; ) - -/* Iterate through all known field names */ -#define SD_JOURNAL_FOREACH_FIELD(j, field) \ - for (sd_journal_restart_fields(j); sd_journal_enumerate_fields((j), &(field)) > 0; ) - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_journal, sd_journal_close); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-lldp.h b/src/systemd/sd-lldp.h deleted file mode 100644 index 5772d5794a..0000000000 --- a/src/systemd/sd-lldp.h +++ /dev/null @@ -1,177 +0,0 @@ -#ifndef foosdlldphfoo -#define foosdlldphfoo - -/*** - This file is part of systemd. - - Copyright (C) 2014 Tom Gundersen - Copyright (C) 2014 Susant Sahani - - 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 - -#include "sd-event.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -typedef struct sd_lldp sd_lldp; -typedef struct sd_lldp_neighbor sd_lldp_neighbor; - -/* IEEE 802.3AB Clause 9: TLV Types */ -enum { - SD_LLDP_TYPE_END = 0, - SD_LLDP_TYPE_CHASSIS_ID = 1, - SD_LLDP_TYPE_PORT_ID = 2, - SD_LLDP_TYPE_TTL = 3, - SD_LLDP_TYPE_PORT_DESCRIPTION = 4, - SD_LLDP_TYPE_SYSTEM_NAME = 5, - SD_LLDP_TYPE_SYSTEM_DESCRIPTION = 6, - SD_LLDP_TYPE_SYSTEM_CAPABILITIES = 7, - SD_LLDP_TYPE_MGMT_ADDRESS = 8, - SD_LLDP_TYPE_PRIVATE = 127, -}; - -/* IEEE 802.3AB Clause 9.5.2: Chassis subtypes */ -enum { - SD_LLDP_CHASSIS_SUBTYPE_RESERVED = 0, - SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT = 1, - SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS = 2, - SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT = 3, - SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS = 4, - SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS = 5, - SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME = 6, - SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED = 7, -}; - -/* IEEE 802.3AB Clause 9.5.3: Port subtype */ -enum { - SD_LLDP_PORT_SUBTYPE_RESERVED = 0, - SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS = 1, - SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT = 2, - SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS = 3, - SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS = 4, - SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME = 5, - SD_LLDP_PORT_SUBTYPE_AGENT_CIRCUIT_ID = 6, - SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED = 7, -}; - -enum { - SD_LLDP_SYSTEM_CAPABILITIES_OTHER = 1 << 0, - SD_LLDP_SYSTEM_CAPABILITIES_REPEATER = 1 << 1, - SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE = 1 << 2, - SD_LLDP_SYSTEM_CAPABILITIES_WLAN_AP = 1 << 3, - SD_LLDP_SYSTEM_CAPABILITIES_ROUTER = 1 << 4, - SD_LLDP_SYSTEM_CAPABILITIES_PHONE = 1 << 5, - SD_LLDP_SYSTEM_CAPABILITIES_DOCSIS = 1 << 6, - SD_LLDP_SYSTEM_CAPABILITIES_STATION = 1 << 7, - SD_LLDP_SYSTEM_CAPABILITIES_CVLAN = 1 << 8, - SD_LLDP_SYSTEM_CAPABILITIES_SVLAN = 1 << 9, - SD_LLDP_SYSTEM_CAPABILITIES_TPMR = 1 << 10, -}; - -#define SD_LLDP_SYSTEM_CAPABILITIES_ALL ((uint16_t) -1) - -#define SD_LLDP_SYSTEM_CAPABILITIES_ALL_ROUTERS \ - ((uint16_t) \ - (SD_LLDP_SYSTEM_CAPABILITIES_REPEATER| \ - SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE| \ - SD_LLDP_SYSTEM_CAPABILITIES_WLAN_AP| \ - SD_LLDP_SYSTEM_CAPABILITIES_ROUTER| \ - SD_LLDP_SYSTEM_CAPABILITIES_DOCSIS| \ - SD_LLDP_SYSTEM_CAPABILITIES_CVLAN| \ - SD_LLDP_SYSTEM_CAPABILITIES_SVLAN| \ - SD_LLDP_SYSTEM_CAPABILITIES_TPMR)) - -#define SD_LLDP_OUI_802_1 (uint8_t[]) { 0x00, 0x80, 0xc2 } -#define SD_LLDP_OUI_802_3 (uint8_t[]) { 0x00, 0x12, 0x0f } - -enum { - SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID = 1, - SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID = 2, - SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME = 3, - SD_LLDP_OUI_802_1_SUBTYPE_PROTOCOL_IDENTITY = 4, - SD_LLDP_OUI_802_1_SUBTYPE_VID_USAGE_DIGEST = 5, - SD_LLDP_OUI_802_1_SUBTYPE_MANAGEMENT_VID = 6, - SD_LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION = 7, -}; - -typedef enum sd_lldp_event { - SD_LLDP_EVENT_ADDED = 'a', - SD_LLDP_EVENT_REMOVED = 'r', - SD_LLDP_EVENT_UPDATED = 'u', - SD_LLDP_EVENT_REFRESHED = 'f', -} sd_lldp_event; - -typedef void (*sd_lldp_callback_t)(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata); - -int sd_lldp_new(sd_lldp **ret, int ifindex); -sd_lldp* sd_lldp_unref(sd_lldp *lldp); - -int sd_lldp_start(sd_lldp *lldp); -int sd_lldp_stop(sd_lldp *lldp); - -int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority); -int sd_lldp_detach_event(sd_lldp *lldp); - -int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata); - -/* Controls how much and what to store in the neighbors database */ -int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t n); -int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask); -int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *address); - -int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***neighbors); - -int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size); -sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n); -sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n); - -/* Access to LLDP frame metadata */ -int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address); -int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address); -int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size); - -/* High-level, direct, parsed out field access. These fields exist at most once, hence may be queried directly. */ -int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size); -int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret); -int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size); -int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret); -int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret); -int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret); -int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret); -int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret); -int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret); -int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret); - -/* Low-level, iterative TLV access. This is for evertyhing else, it iteratively goes through all available TLVs - * (including the ones covered with the calls above), and allows multiple TLVs for the same fields. */ -int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n); -int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n); -int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type); -int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type); -int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[3], uint8_t *subtype); -int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[3], uint8_t subtype); -int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp, sd_lldp_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp_neighbor, sd_lldp_neighbor_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-login.h b/src/systemd/sd-login.h deleted file mode 100644 index e3ecbd8378..0000000000 --- a/src/systemd/sd-login.h +++ /dev/null @@ -1,245 +0,0 @@ -#ifndef foosdloginhfoo -#define foosdloginhfoo - -/*** - This file is part of systemd. - - Copyright 2011 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 - -#include "_sd-common.h" - -/* - * A few points: - * - * Instead of returning an empty string array or empty uid array, we - * may return NULL. - * - * Free the data the library returns with libc free(). String arrays - * are NULL terminated, and you need to free the array itself, in - * addition to the strings contained. - * - * We return error codes as negative errno, kernel-style. On success, we - * return 0 or positive. - * - * These functions access data in /proc, /sys/fs/cgroup, and /run. All - * of these are virtual file systems; therefore, accesses are - * relatively cheap. - * - * See sd-login(3) for more information. - */ - -_SD_BEGIN_DECLARATIONS; - -/* Get session from PID. Note that 'shared' processes of a user are - * not attached to a session, but only attached to a user. This will - * return an error for system processes and 'shared' processes of a - * user. */ -int sd_pid_get_session(pid_t pid, char **session); - -/* Get UID of the owner of the session of the PID (or in case the - * process is a 'shared' user process, the UID of that user is - * returned). This will not return the UID of the process, but rather - * the UID of the owner of the cgroup that the process is in. This will - * return an error for system processes. */ -int sd_pid_get_owner_uid(pid_t pid, uid_t *uid); - -/* Get systemd non-slice unit (i.e. service) name from PID, for system - * services. This will return an error for non-service processes. */ -int sd_pid_get_unit(pid_t pid, char **unit); - -/* Get systemd non-slice unit (i.e. service) name from PID, for user - * services. This will return an error for non-user-service - * processes. */ -int sd_pid_get_user_unit(pid_t pid, char **unit); - -/* Get slice name from PID. */ -int sd_pid_get_slice(pid_t pid, char **slice); - -/* Get user slice name from PID. */ -int sd_pid_get_user_slice(pid_t pid, char **slice); - -/* Get machine name from PID, for processes assigned to a VM or - * container. This will return an error for non-machine processes. */ -int sd_pid_get_machine_name(pid_t pid, char **machine); - -/* Get the control group from a PID, relative to the root of the - * hierarchy. */ -int sd_pid_get_cgroup(pid_t pid, char **cgroup); - -/* Similar to sd_pid_get_session(), but retrieves data about the peer - * of a connected AF_UNIX socket */ -int sd_peer_get_session(int fd, char **session); - -/* Similar to sd_pid_get_owner_uid(), but retrieves data about the peer of - * a connected AF_UNIX socket */ -int sd_peer_get_owner_uid(int fd, uid_t *uid); - -/* Similar to sd_pid_get_unit(), but retrieves data about the peer of - * a connected AF_UNIX socket */ -int sd_peer_get_unit(int fd, char **unit); - -/* Similar to sd_pid_get_user_unit(), but retrieves data about the peer of - * a connected AF_UNIX socket */ -int sd_peer_get_user_unit(int fd, char **unit); - -/* Similar to sd_pid_get_slice(), but retrieves data about the peer of - * a connected AF_UNIX socket */ -int sd_peer_get_slice(int fd, char **slice); - -/* Similar to sd_pid_get_user_slice(), but retrieves data about the peer of - * a connected AF_UNIX socket */ -int sd_peer_get_user_slice(int fd, char **slice); - -/* Similar to sd_pid_get_machine_name(), but retrieves data about the - * peer of a connected AF_UNIX socket */ -int sd_peer_get_machine_name(int fd, char **machine); - -/* Similar to sd_pid_get_cgroup(), but retrieves data about the peer - * of a connected AF_UNIX socket. */ -int sd_peer_get_cgroup(pid_t pid, char **cgroup); - -/* Get state from UID. Possible states: offline, lingering, online, active, closing */ -int sd_uid_get_state(uid_t uid, char **state); - -/* Return primary session of user, if there is any */ -int sd_uid_get_display(uid_t uid, char **session); - -/* Return 1 if UID has session on seat. If require_active is true, this will - * look for active sessions only. */ -int sd_uid_is_on_seat(uid_t uid, int require_active, const char *seat); - -/* Return sessions of user. If require_active is true, this will look for - * active sessions only. Returns the number of sessions. - * If sessions is NULL, this will just return the number of sessions. */ -int sd_uid_get_sessions(uid_t uid, int require_active, char ***sessions); - -/* Return seats of user is on. If require_active is true, this will look for - * active seats only. Returns the number of seats. - * If seats is NULL, this will just return the number of seats. */ -int sd_uid_get_seats(uid_t uid, int require_active, char ***seats); - -/* Return 1 if the session is active. */ -int sd_session_is_active(const char *session); - -/* Return 1 if the session is remote. */ -int sd_session_is_remote(const char *session); - -/* Get state from session. Possible states: online, active, closing. - * This function is a more generic version of sd_session_is_active(). */ -int sd_session_get_state(const char *session, char **state); - -/* Determine user ID of session */ -int sd_session_get_uid(const char *session, uid_t *uid); - -/* Determine seat of session */ -int sd_session_get_seat(const char *session, char **seat); - -/* Determine the (PAM) service name this session was registered by. */ -int sd_session_get_service(const char *session, char **service); - -/* Determine the type of this session, i.e. one of "tty", "x11", "wayland", "mir" or "unspecified". */ -int sd_session_get_type(const char *session, char **type); - -/* Determine the class of this session, i.e. one of "user", "greeter" or "lock-screen". */ -int sd_session_get_class(const char *session, char **clazz); - -/* Determine the desktop brand of this session, i.e. something like "GNOME", "KDE" or "systemd-console". */ -int sd_session_get_desktop(const char *session, char **desktop); - -/* Determine the X11 display of this session. */ -int sd_session_get_display(const char *session, char **display); - -/* Determine the remote host of this session. */ -int sd_session_get_remote_host(const char *session, char **remote_host); - -/* Determine the remote user of this session (if provided by PAM). */ -int sd_session_get_remote_user(const char *session, char **remote_user); - -/* Determine the TTY of this session. */ -int sd_session_get_tty(const char *session, char **display); - -/* Determine the VT number of this session. */ -int sd_session_get_vt(const char *session, unsigned *vtnr); - -/* Return active session and user of seat */ -int sd_seat_get_active(const char *seat, char **session, uid_t *uid); - -/* Return sessions and users on seat. Returns number of sessions. - * If sessions is NULL, this returns only the number of sessions. */ -int sd_seat_get_sessions(const char *seat, char ***sessions, uid_t **uid, unsigned *n_uids); - -/* Return whether the seat is multi-session capable */ -int sd_seat_can_multi_session(const char *seat); - -/* Return whether the seat is TTY capable, i.e. suitable for showing console UIs */ -int sd_seat_can_tty(const char *seat); - -/* Return whether the seat is graphics capable, i.e. suitable for showing graphical UIs */ -int sd_seat_can_graphical(const char *seat); - -/* Return the class of machine */ -int sd_machine_get_class(const char *machine, char **clazz); - -/* Return the list if host-side network interface indices of a machine */ -int sd_machine_get_ifindices(const char *machine, int **ifindices); - -/* Get all seats, store in *seats. Returns the number of seats. If - * seats is NULL, this only returns the number of seats. */ -int sd_get_seats(char ***seats); - -/* Get all sessions, store in *sessions. Returns the number of - * sessions. If sessions is NULL, this only returns the number of sessions. */ -int sd_get_sessions(char ***sessions); - -/* Get all logged in users, store in *users. Returns the number of - * users. If users is NULL, this only returns the number of users. */ -int sd_get_uids(uid_t **users); - -/* Get all running virtual machines/containers */ -int sd_get_machine_names(char ***machines); - -/* Monitor object */ -typedef struct sd_login_monitor sd_login_monitor; - -/* Create a new monitor. Category must be NULL, "seat", "session", - * "uid", or "machine" to get monitor events for the specific category - * (or all). */ -int sd_login_monitor_new(const char *category, sd_login_monitor** ret); - -/* Destroys the passed monitor. Returns NULL. */ -sd_login_monitor* sd_login_monitor_unref(sd_login_monitor *m); - -/* Flushes the monitor */ -int sd_login_monitor_flush(sd_login_monitor *m); - -/* Get FD from monitor */ -int sd_login_monitor_get_fd(sd_login_monitor *m); - -/* Get poll() mask to monitor */ -int sd_login_monitor_get_events(sd_login_monitor *m); - -/* Get timeout for poll(), as usec value relative to CLOCK_MONOTONIC's epoch */ -int sd_login_monitor_get_timeout(sd_login_monitor *m, uint64_t *timeout_usec); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_login_monitor, sd_login_monitor_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h deleted file mode 100644 index 3c44d63021..0000000000 --- a/src/systemd/sd-messages.h +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef foosdmessageshfoo -#define foosdmessageshfoo - -/*** - This file is part of systemd. - - Copyright 2012 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 "sd-id128.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -/* Hey! If you add a new message here, you *must* also update the - * message catalog with an appropriate explanation */ - -/* And if you add a new ID here, make sure to generate a random one - * with journalctl --new-id128. Do not use any other IDs, and do not - * count them up manually. */ - -#define SD_MESSAGE_JOURNAL_START SD_ID128_MAKE(f7,73,79,a8,49,0b,40,8b,be,5f,69,40,50,5a,77,7b) -#define SD_MESSAGE_JOURNAL_STOP SD_ID128_MAKE(d9,3f,b3,c9,c2,4d,45,1a,97,ce,a6,15,ce,59,c0,0b) -#define SD_MESSAGE_JOURNAL_DROPPED SD_ID128_MAKE(a5,96,d6,fe,7b,fa,49,94,82,8e,72,30,9e,95,d6,1e) -#define SD_MESSAGE_JOURNAL_MISSED SD_ID128_MAKE(e9,bf,28,e6,e8,34,48,1b,b6,f4,8f,54,8a,d1,36,06) -#define SD_MESSAGE_JOURNAL_USAGE SD_ID128_MAKE(ec,38,7f,57,7b,84,4b,8f,a9,48,f3,3c,ad,9a,75,e6) - -#define SD_MESSAGE_COREDUMP SD_ID128_MAKE(fc,2e,22,bc,6e,e6,47,b6,b9,07,29,ab,34,a2,50,b1) - -#define SD_MESSAGE_SESSION_START SD_ID128_MAKE(8d,45,62,0c,1a,43,48,db,b1,74,10,da,57,c6,0c,66) -#define SD_MESSAGE_SESSION_STOP SD_ID128_MAKE(33,54,93,94,24,b4,45,6d,98,02,ca,83,33,ed,42,4a) -#define SD_MESSAGE_SEAT_START SD_ID128_MAKE(fc,be,fc,5d,a2,3d,42,80,93,f9,7c,82,a9,29,0f,7b) -#define SD_MESSAGE_SEAT_STOP SD_ID128_MAKE(e7,85,2b,fe,46,78,4e,d0,ac,cd,e0,4b,c8,64,c2,d5) -#define SD_MESSAGE_MACHINE_START SD_ID128_MAKE(24,d8,d4,45,25,73,40,24,96,06,83,81,a6,31,2d,f2) -#define SD_MESSAGE_MACHINE_STOP SD_ID128_MAKE(58,43,2b,d3,ba,ce,47,7c,b5,14,b5,63,81,b8,a7,58) - -#define SD_MESSAGE_TIME_CHANGE SD_ID128_MAKE(c7,a7,87,07,9b,35,4e,aa,a9,e7,7b,37,18,93,cd,27) -#define SD_MESSAGE_TIMEZONE_CHANGE SD_ID128_MAKE(45,f8,2f,4a,ef,7a,4b,bf,94,2c,e8,61,d1,f2,09,90) - -#define SD_MESSAGE_STARTUP_FINISHED SD_ID128_MAKE(b0,7a,24,9c,d0,24,41,4a,82,dd,00,cd,18,13,78,ff) - -#define SD_MESSAGE_SLEEP_START SD_ID128_MAKE(6b,bd,95,ee,97,79,41,e4,97,c4,8b,e2,7c,25,41,28) -#define SD_MESSAGE_SLEEP_STOP SD_ID128_MAKE(88,11,e6,df,2a,8e,40,f5,8a,94,ce,a2,6f,8e,bf,14) - -#define SD_MESSAGE_SHUTDOWN SD_ID128_MAKE(98,26,88,66,d1,d5,4a,49,9c,4e,98,92,1d,93,bc,40) - -#define SD_MESSAGE_UNIT_STARTING SD_ID128_MAKE(7d,49,58,e8,42,da,4a,75,8f,6c,1c,dc,7b,36,dc,c5) -#define SD_MESSAGE_UNIT_STARTED SD_ID128_MAKE(39,f5,34,79,d3,a0,45,ac,8e,11,78,62,48,23,1f,bf) -#define SD_MESSAGE_UNIT_STOPPING SD_ID128_MAKE(de,5b,42,6a,63,be,47,a7,b6,ac,3e,aa,c8,2e,2f,6f) -#define SD_MESSAGE_UNIT_STOPPED SD_ID128_MAKE(9d,1a,aa,27,d6,01,40,bd,96,36,54,38,aa,d2,02,86) -#define SD_MESSAGE_UNIT_FAILED SD_ID128_MAKE(be,02,cf,68,55,d2,42,8b,a4,0d,f7,e9,d0,22,f0,3d) -#define SD_MESSAGE_UNIT_RELOADING SD_ID128_MAKE(d3,4d,03,7f,ff,18,47,e6,ae,66,9a,37,0e,69,47,25) -#define SD_MESSAGE_UNIT_RELOADED SD_ID128_MAKE(7b,05,eb,c6,68,38,42,22,ba,a8,88,11,79,cf,da,54) - -#define SD_MESSAGE_SPAWN_FAILED SD_ID128_MAKE(64,12,57,65,1c,1b,4e,c9,a8,62,4d,7a,40,a9,e1,e7) - -#define SD_MESSAGE_FORWARD_SYSLOG_MISSED SD_ID128_MAKE(00,27,22,9c,a0,64,41,81,a7,6c,4e,92,45,8a,fa,2e) - -#define SD_MESSAGE_OVERMOUNTING SD_ID128_MAKE(1d,ee,03,69,c7,fc,47,36,b7,09,9b,38,ec,b4,6e,e7) - -#define SD_MESSAGE_LID_OPENED SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,6f) -#define SD_MESSAGE_LID_CLOSED SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,70) -#define SD_MESSAGE_SYSTEM_DOCKED SD_ID128_MAKE(f5,f4,16,b8,62,07,4b,28,92,7a,48,c3,ba,7d,51,ff) -#define SD_MESSAGE_SYSTEM_UNDOCKED SD_ID128_MAKE(51,e1,71,bd,58,52,48,56,81,10,14,4c,51,7c,ca,53) -#define SD_MESSAGE_POWER_KEY SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,71) -#define SD_MESSAGE_SUSPEND_KEY SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,72) -#define SD_MESSAGE_HIBERNATE_KEY SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,73) - -#define SD_MESSAGE_INVALID_CONFIGURATION SD_ID128_MAKE(c7,72,d2,4e,9a,88,4c,be,b9,ea,12,62,5c,30,6c,01) - -#define SD_MESSAGE_DNSSEC_FAILURE SD_ID128_MAKE(16,75,d7,f1,72,17,40,98,b1,10,8b,f8,c7,dc,8f,5d) -#define SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED SD_ID128_MAKE(4d,44,08,cf,d0,d1,44,85,91,84,d1,e6,5d,7c,8a,65) -#define SD_MESSAGE_DNSSEC_DOWNGRADE SD_ID128_MAKE(36,db,2d,fa,5a,90,45,e1,bd,4a,f5,f9,3e,1c,f0,57) - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-ndisc.h b/src/systemd/sd-ndisc.h deleted file mode 100644 index 29bcbe8e3e..0000000000 --- a/src/systemd/sd-ndisc.h +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef foosdndiscfoo -#define foosdndiscfoo - -/*** - This file is part of systemd. - - Copyright (C) 2014 Intel Corporation. All rights reserved. - - 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 - -#include "sd-event.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -enum { - SD_NDISC_EVENT_STOP = 0, - SD_NDISC_EVENT_TIMEOUT = 1, -}; - -typedef struct sd_ndisc sd_ndisc; - -typedef void(*sd_ndisc_router_callback_t)(sd_ndisc *nd, uint8_t flags, const struct in6_addr *gateway, unsigned lifetime, int pref, void *userdata); -typedef void(*sd_ndisc_prefix_onlink_callback_t)(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen, - unsigned lifetime, void *userdata); -typedef void(*sd_ndisc_prefix_autonomous_callback_t)(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen, - unsigned lifetime_prefered, unsigned lifetime_valid, void *userdata); -typedef void(*sd_ndisc_callback_t)(sd_ndisc *nd, int event, void *userdata); - -int sd_ndisc_set_callback(sd_ndisc *nd, - sd_ndisc_router_callback_t rcb, - sd_ndisc_prefix_onlink_callback_t plcb, - sd_ndisc_prefix_autonomous_callback_t pacb, - sd_ndisc_callback_t cb, - void *userdata); -int sd_ndisc_set_index(sd_ndisc *nd, int interface_index); -int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr); - -int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority); -int sd_ndisc_detach_event(sd_ndisc *nd); -sd_event *sd_ndisc_get_event(sd_ndisc *nd); - -sd_ndisc *sd_ndisc_ref(sd_ndisc *nd); -sd_ndisc *sd_ndisc_unref(sd_ndisc *nd); -int sd_ndisc_new(sd_ndisc **ret); - -int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu); - -int sd_ndisc_stop(sd_ndisc *nd); -int sd_ndisc_router_discovery_start(sd_ndisc *nd); - -#define SD_NDISC_ADDRESS_FORMAT_STR "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x" - -#define SD_NDISC_ADDRESS_FORMAT_VAL(address) \ - be16toh((address).s6_addr16[0]), \ - be16toh((address).s6_addr16[1]), \ - be16toh((address).s6_addr16[2]), \ - be16toh((address).s6_addr16[3]), \ - be16toh((address).s6_addr16[4]), \ - be16toh((address).s6_addr16[5]), \ - be16toh((address).s6_addr16[6]), \ - be16toh((address).s6_addr16[7]) - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc, sd_ndisc_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-netlink.h b/src/systemd/sd-netlink.h deleted file mode 100644 index 3ae110c080..0000000000 --- a/src/systemd/sd-netlink.h +++ /dev/null @@ -1,163 +0,0 @@ -#ifndef foosdnetlinkhfoo -#define foosdnetlinkhfoo - -/*** - This file is part of systemd. - - Copyright 2013 Tom Gundersen - - 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 -#include -#include -#include - -#include "sd-event.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -typedef struct sd_netlink sd_netlink; -typedef struct sd_netlink_message sd_netlink_message; - -/* callback */ - -typedef int (*sd_netlink_message_handler_t)(sd_netlink *nl, sd_netlink_message *m, void *userdata); - -/* bus */ -int sd_netlink_new_from_netlink(sd_netlink **nl, int fd); -int sd_netlink_open(sd_netlink **nl); -int sd_netlink_open_fd(sd_netlink **nl, int fd); -int sd_netlink_inc_rcvbuf(const sd_netlink *const rtnl, const int size); - -sd_netlink *sd_netlink_ref(sd_netlink *nl); -sd_netlink *sd_netlink_unref(sd_netlink *nl); - -int sd_netlink_send(sd_netlink *nl, sd_netlink_message *message, uint32_t *serial); -int sd_netlink_call_async(sd_netlink *nl, sd_netlink_message *message, - sd_netlink_message_handler_t callback, - void *userdata, uint64_t usec, uint32_t *serial); -int sd_netlink_call_async_cancel(sd_netlink *nl, uint32_t serial); -int sd_netlink_call(sd_netlink *nl, sd_netlink_message *message, uint64_t timeout, - sd_netlink_message **reply); - -int sd_netlink_get_events(sd_netlink *nl); -int sd_netlink_get_timeout(sd_netlink *nl, uint64_t *timeout); -int sd_netlink_process(sd_netlink *nl, sd_netlink_message **ret); -int sd_netlink_wait(sd_netlink *nl, uint64_t timeout); - -int sd_netlink_add_match(sd_netlink *nl, uint16_t match, sd_netlink_message_handler_t c, void *userdata); -int sd_netlink_remove_match(sd_netlink *nl, uint16_t match, sd_netlink_message_handler_t c, void *userdata); - -int sd_netlink_attach_event(sd_netlink *nl, sd_event *e, int64_t priority); -int sd_netlink_detach_event(sd_netlink *nl); - -int sd_netlink_message_append_string(sd_netlink_message *m, unsigned short type, const char *data); -int sd_netlink_message_append_flag(sd_netlink_message *m, unsigned short type); -int sd_netlink_message_append_u8(sd_netlink_message *m, unsigned short type, uint8_t data); -int sd_netlink_message_append_u16(sd_netlink_message *m, unsigned short type, uint16_t data); -int sd_netlink_message_append_u32(sd_netlink_message *m, unsigned short type, uint32_t data); -int sd_netlink_message_append_data(sd_netlink_message *m, unsigned short type, const void *data, size_t len); -int sd_netlink_message_append_in_addr(sd_netlink_message *m, unsigned short type, const struct in_addr *data); -int sd_netlink_message_append_in6_addr(sd_netlink_message *m, unsigned short type, const struct in6_addr *data); -int sd_netlink_message_append_ether_addr(sd_netlink_message *m, unsigned short type, const struct ether_addr *data); -int sd_netlink_message_append_cache_info(sd_netlink_message *m, unsigned short type, const struct ifa_cacheinfo *info); - -int sd_netlink_message_open_container(sd_netlink_message *m, unsigned short type); -int sd_netlink_message_open_container_union(sd_netlink_message *m, unsigned short type, const char *key); -int sd_netlink_message_close_container(sd_netlink_message *m); - -int sd_netlink_message_read_string(sd_netlink_message *m, unsigned short type, const char **data); -int sd_netlink_message_read_u8(sd_netlink_message *m, unsigned short type, uint8_t *data); -int sd_netlink_message_read_u16(sd_netlink_message *m, unsigned short type, uint16_t *data); -int sd_netlink_message_read_u32(sd_netlink_message *m, unsigned short type, uint32_t *data); -int sd_netlink_message_read_ether_addr(sd_netlink_message *m, unsigned short type, struct ether_addr *data); -int sd_netlink_message_read_cache_info(sd_netlink_message *m, unsigned short type, struct ifa_cacheinfo *info); -int sd_netlink_message_read_in_addr(sd_netlink_message *m, unsigned short type, struct in_addr *data); -int sd_netlink_message_read_in6_addr(sd_netlink_message *m, unsigned short type, struct in6_addr *data); -int sd_netlink_message_enter_container(sd_netlink_message *m, unsigned short type); -int sd_netlink_message_exit_container(sd_netlink_message *m); - -int sd_netlink_message_rewind(sd_netlink_message *m); - -sd_netlink_message *sd_netlink_message_next(sd_netlink_message *m); - -sd_netlink_message *sd_netlink_message_ref(sd_netlink_message *m); -sd_netlink_message *sd_netlink_message_unref(sd_netlink_message *m); - -int sd_netlink_message_request_dump(sd_netlink_message *m, int dump); -int sd_netlink_message_is_error(sd_netlink_message *m); -int sd_netlink_message_get_errno(sd_netlink_message *m); -int sd_netlink_message_get_type(sd_netlink_message *m, uint16_t *type); -int sd_netlink_message_set_flags(sd_netlink_message *m, uint16_t flags); -int sd_netlink_message_is_broadcast(sd_netlink_message *m); - -/* rtnl */ - -int sd_rtnl_message_new_link(sd_netlink *nl, sd_netlink_message **ret, uint16_t msg_type, int index); -int sd_rtnl_message_new_addr_update(sd_netlink *nl, sd_netlink_message **ret, int index, int family); -int sd_rtnl_message_new_addr(sd_netlink *nl, sd_netlink_message **ret, uint16_t msg_type, int index, int family); -int sd_rtnl_message_new_route(sd_netlink *nl, sd_netlink_message **ret, uint16_t nlmsg_type, int rtm_family, unsigned char rtm_protocol); -int sd_rtnl_message_new_neigh(sd_netlink *nl, sd_netlink_message **ret, uint16_t msg_type, int index, int nda_family); - -int sd_rtnl_message_get_family(sd_netlink_message *m, int *family); - -int sd_rtnl_message_addr_set_prefixlen(sd_netlink_message *m, unsigned char prefixlen); -int sd_rtnl_message_addr_set_scope(sd_netlink_message *m, unsigned char scope); -int sd_rtnl_message_addr_set_flags(sd_netlink_message *m, unsigned char flags); -int sd_rtnl_message_addr_get_family(sd_netlink_message *m, int *family); -int sd_rtnl_message_addr_get_prefixlen(sd_netlink_message *m, unsigned char *prefixlen); -int sd_rtnl_message_addr_get_scope(sd_netlink_message *m, unsigned char *scope); -int sd_rtnl_message_addr_get_flags(sd_netlink_message *m, unsigned char *flags); -int sd_rtnl_message_addr_get_ifindex(sd_netlink_message *m, int *ifindex); - -int sd_rtnl_message_link_set_flags(sd_netlink_message *m, unsigned flags, unsigned change); -int sd_rtnl_message_link_set_type(sd_netlink_message *m, unsigned type); -int sd_rtnl_message_link_set_family(sd_netlink_message *m, unsigned family); -int sd_rtnl_message_link_get_ifindex(sd_netlink_message *m, int *ifindex); -int sd_rtnl_message_link_get_flags(sd_netlink_message *m, unsigned *flags); -int sd_rtnl_message_link_get_type(sd_netlink_message *m, unsigned short *type); - -int sd_rtnl_message_route_set_dst_prefixlen(sd_netlink_message *m, unsigned char prefixlen); -int sd_rtnl_message_route_set_src_prefixlen(sd_netlink_message *m, unsigned char prefixlen); -int sd_rtnl_message_route_set_scope(sd_netlink_message *m, unsigned char scope); -int sd_rtnl_message_route_set_flags(sd_netlink_message *m, unsigned flags); -int sd_rtnl_message_route_set_table(sd_netlink_message *m, unsigned char table); -int sd_rtnl_message_route_get_flags(sd_netlink_message *m, unsigned *flags); -int sd_rtnl_message_route_get_family(sd_netlink_message *m, int *family); -int sd_rtnl_message_route_set_family(sd_netlink_message *m, int family); -int sd_rtnl_message_route_get_protocol(sd_netlink_message *m, unsigned char *protocol); -int sd_rtnl_message_route_get_scope(sd_netlink_message *m, unsigned char *scope); -int sd_rtnl_message_route_get_tos(sd_netlink_message *m, unsigned char *tos); -int sd_rtnl_message_route_get_table(sd_netlink_message *m, unsigned char *table); -int sd_rtnl_message_route_get_dst_prefixlen(sd_netlink_message *m, unsigned char *dst_len); -int sd_rtnl_message_route_get_src_prefixlen(sd_netlink_message *m, unsigned char *src_len); - -int sd_rtnl_message_neigh_set_flags(sd_netlink_message *m, uint8_t flags); -int sd_rtnl_message_neigh_set_state(sd_netlink_message *m, uint16_t state); -int sd_rtnl_message_neigh_get_family(sd_netlink_message *m, int *family); -int sd_rtnl_message_neigh_get_ifindex(sd_netlink_message *m, int *family); -int sd_rtnl_message_neigh_get_state(sd_netlink_message *m, uint16_t *state); -int sd_rtnl_message_neigh_get_flags(sd_netlink_message *m, uint8_t *flags); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_netlink, sd_netlink_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_netlink_message, sd_netlink_message_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-network.h b/src/systemd/sd-network.h deleted file mode 100644 index 0f13e2bae7..0000000000 --- a/src/systemd/sd-network.h +++ /dev/null @@ -1,176 +0,0 @@ -#ifndef foosdnetworkhfoo -#define foosdnetworkhfoo - -/*** - This file is part of systemd. - - Copyright 2011 Lennart Poettering - Copyright 2014 Tom Gundersen - - 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 - -#include "_sd-common.h" - -/* - * A few points: - * - * Instead of returning an empty string array or empty integer array, we - * may return NULL. - * - * Free the data the library returns with libc free(). String arrays - * are NULL terminated, and you need to free the array itself in - * addition to the strings contained. - * - * We return error codes as negative errno, kernel-style. On success, we - * return 0 or positive. - * - * These functions access data in /run. This is a virtual file system; - * therefore, accesses are relatively cheap. - * - * See sd-network(3) for more information. - */ - -_SD_BEGIN_DECLARATIONS; - -/* Get overall operational state - * Possible states: down, up, dormant, carrier, degraded, routable - * Possible return codes: - * -ENODATA: networkd is not aware of any links - */ -int sd_network_get_operational_state(char **state); - -/* Get DNS entries for all links. These are string representations of - * IP addresses */ -int sd_network_get_dns(char ***dns); - -/* Get NTP entries for all links. These are domain names or string - * representations of IP addresses */ -int sd_network_get_ntp(char ***ntp); - -/* Get the search domains for all links. */ -int sd_network_get_search_domains(char ***domains); - -/* Get the search domains for all links. */ -int sd_network_get_route_domains(char ***domains); - -/* Get setup state from ifindex. - * Possible states: - * pending: udev is still processing the link, we don't yet know if we will manage it - * failed: networkd failed to manage the link - * configuring: in the process of retrieving configuration or configuring the link - * configured: link configured successfully - * unmanaged: networkd is not handling the link - * linger: the link is gone, but has not yet been dropped by networkd - * Possible return codes: - * -ENODATA: networkd is not aware of the link - */ -int sd_network_link_get_setup_state(int ifindex, char **state); - -/* Get operational state from ifindex. - * Possible states: - * off: the device is powered down - * no-carrier: the device is powered up, but it does not yet have a carrier - * dormant: the device has a carrier, but is not yet ready for normal traffic - * carrier: the link has a carrier - * degraded: the link has carrier and addresses valid on the local link configured - * routable: the link has carrier and routable address configured - * Possible return codes: - * -ENODATA: networkd is not aware of the link - */ -int sd_network_link_get_operational_state(int ifindex, char **state); - -/* Get path to .network file applied to link */ -int sd_network_link_get_network_file(int ifindex, char **filename); - -/* Get DNS entries for a given link. These are string representations of - * IP addresses */ -int sd_network_link_get_dns(int ifindex, char ***ret); - -/* Get NTP entries for a given link. These are domain names or string - * representations of IP addresses */ -int sd_network_link_get_ntp(int ifindex, char ***ret); - -/* Indicates whether or not LLMNR should be enabled for the link - * Possible levels of support: yes, no, resolve - * Possible return codes: - * -ENODATA: networkd is not aware of the link - */ -int sd_network_link_get_llmnr(int ifindex, char **llmnr); - -/* Indicates whether or not MulticastDNS should be enabled for the - * link. - * Possible levels of support: yes, no, resolve - * Possible return codes: - * -ENODATA: networkd is not aware of the link - */ -int sd_network_link_get_mdns(int ifindex, char **mdns); - -/* Indicates whether or not DNSSEC should be enabled for the link - * Possible levels of support: yes, no, allow-downgrade - * Possible return codes: - * -ENODATA: networkd is not aware of the link - */ -int sd_network_link_get_dnssec(int ifindex, char **dnssec); - -/* Returns the list of per-interface DNSSEC negative trust anchors - * Possible return codes: - * -ENODATA: networkd is not aware of the link, or has no such data - */ -int sd_network_link_get_dnssec_negative_trust_anchors(int ifindex, char ***nta); - -/* Get the search DNS domain names for a given link. */ -int sd_network_link_get_search_domains(int ifindex, char ***domains); - -/* Get the route DNS domain names for a given link. */ -int sd_network_link_get_route_domains(int ifindex, char ***domains); - -/* Get the carrier interface indexes to which current link is bound to. */ -int sd_network_link_get_carrier_bound_to(int ifindex, int **ifindexes); - -/* Get the CARRIERS that are bound to current link. */ -int sd_network_link_get_carrier_bound_by(int ifindex, int **ifindexes); - -/* Get the timezone that was learnt on a specific link. */ -int sd_network_link_get_timezone(int ifindex, char **timezone); - -/* Monitor object */ -typedef struct sd_network_monitor sd_network_monitor; - -/* Create a new monitor. Category must be NULL, "links" or "leases". */ -int sd_network_monitor_new(sd_network_monitor **ret, const char *category); - -/* Destroys the passed monitor. Returns NULL. */ -sd_network_monitor* sd_network_monitor_unref(sd_network_monitor *m); - -/* Flushes the monitor */ -int sd_network_monitor_flush(sd_network_monitor *m); - -/* Get FD from monitor */ -int sd_network_monitor_get_fd(sd_network_monitor *m); - -/* Get poll() mask to monitor */ -int sd_network_monitor_get_events(sd_network_monitor *m); - -/* Get timeout for poll(), as usec value relative to CLOCK_MONOTONIC's epoch */ -int sd_network_monitor_get_timeout(sd_network_monitor *m, uint64_t *timeout_usec); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_network_monitor, sd_network_monitor_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-path.h b/src/systemd/sd-path.h deleted file mode 100644 index be6abdcd03..0000000000 --- a/src/systemd/sd-path.h +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef foosdpathhfoo -#define foosdpathhfoo - -/*** - 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 "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -enum { - /* Temporary files */ - SD_PATH_TEMPORARY = 0x0ULL, - SD_PATH_TEMPORARY_LARGE, - - /* Vendor supplied data */ - SD_PATH_SYSTEM_BINARIES, - SD_PATH_SYSTEM_INCLUDE, - SD_PATH_SYSTEM_LIBRARY_PRIVATE, - SD_PATH_SYSTEM_LIBRARY_ARCH, - SD_PATH_SYSTEM_SHARED, - SD_PATH_SYSTEM_CONFIGURATION_FACTORY, - SD_PATH_SYSTEM_STATE_FACTORY, - - /* System configuration, runtime, state, ... */ - SD_PATH_SYSTEM_CONFIGURATION, - SD_PATH_SYSTEM_RUNTIME, - SD_PATH_SYSTEM_RUNTIME_LOGS, - SD_PATH_SYSTEM_STATE_PRIVATE, - SD_PATH_SYSTEM_STATE_LOGS, - SD_PATH_SYSTEM_STATE_CACHE, - SD_PATH_SYSTEM_STATE_SPOOL, - - /* Vendor supplied data */ - SD_PATH_USER_BINARIES, - SD_PATH_USER_LIBRARY_PRIVATE, - SD_PATH_USER_LIBRARY_ARCH, - SD_PATH_USER_SHARED, - - /* User configuration, state, runtime ... */ - SD_PATH_USER_CONFIGURATION, /* takes both actual configuration (like /etc) and state (like /var/lib) */ - SD_PATH_USER_RUNTIME, - SD_PATH_USER_STATE_CACHE, - - /* User resources */ - SD_PATH_USER, /* $HOME itself */ - SD_PATH_USER_DOCUMENTS, - SD_PATH_USER_MUSIC, - SD_PATH_USER_PICTURES, - SD_PATH_USER_VIDEOS, - SD_PATH_USER_DOWNLOAD, - SD_PATH_USER_PUBLIC, - SD_PATH_USER_TEMPLATES, - SD_PATH_USER_DESKTOP, - - /* Search paths */ - SD_PATH_SEARCH_BINARIES, - SD_PATH_SEARCH_LIBRARY_PRIVATE, - SD_PATH_SEARCH_LIBRARY_ARCH, - SD_PATH_SEARCH_SHARED, - SD_PATH_SEARCH_CONFIGURATION_FACTORY, - SD_PATH_SEARCH_STATE_FACTORY, - SD_PATH_SEARCH_CONFIGURATION, - - _SD_PATH_MAX, -}; - -int sd_path_home(uint64_t type, const char *suffix, char **path); -int sd_path_search(uint64_t type, const char *suffix, char ***paths); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-resolve.h b/src/systemd/sd-resolve.h deleted file mode 100644 index 1c792dab39..0000000000 --- a/src/systemd/sd-resolve.h +++ /dev/null @@ -1,117 +0,0 @@ -#ifndef foosdresolvehfoo -#define foosdresolvehfoo - -/*** - This file is part of systemd. - - Copyright 2005-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 -#include -#include - -#include "sd-event.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -/* An opaque sd-resolve session structure */ -typedef struct sd_resolve sd_resolve; - -/* An opaque sd-resolve query structure */ -typedef struct sd_resolve_query sd_resolve_query; - -/* A callback on completion */ -typedef int (*sd_resolve_getaddrinfo_handler_t)(sd_resolve_query *q, int ret, const struct addrinfo *ai, void *userdata); -typedef int (*sd_resolve_getnameinfo_handler_t)(sd_resolve_query *q, int ret, const char *host, const char *serv, void *userdata); - -enum { - SD_RESOLVE_GET_HOST = UINT64_C(1), - SD_RESOLVE_GET_SERVICE = UINT64_C(2), - SD_RESOLVE_GET_BOTH = UINT64_C(3), -}; - -int sd_resolve_default(sd_resolve **ret); - -/* Allocate a new sd-resolve session. */ -int sd_resolve_new(sd_resolve **ret); - -/* Free a sd-resolve session. This destroys all attached - * sd_resolve_query objects automatically. */ -sd_resolve* sd_resolve_unref(sd_resolve *resolve); -sd_resolve* sd_resolve_ref(sd_resolve *resolve); - -/* Return the UNIX file descriptor to poll() for events on. Use this - * function to integrate sd-resolve with your custom main loop. */ -int sd_resolve_get_fd(sd_resolve *resolve); - -/* Return the poll() events (a combination of flags like POLLIN, - * POLLOUT, ...) to check for. */ -int sd_resolve_get_events(sd_resolve *resolve); - -/* Return the poll() timeout to pass. Returns (uint64_t) -1 as - * timeout if no timeout is needed. */ -int sd_resolve_get_timeout(sd_resolve *resolve, uint64_t *timeout_usec); - -/* Process pending responses. After this function is called, you can - * get the next completed query object(s) using - * sd_resolve_get_next(). */ -int sd_resolve_process(sd_resolve *resolve); - -/* Wait for a resolve event to complete. */ -int sd_resolve_wait(sd_resolve *resolve, uint64_t timeout_usec); - -int sd_resolve_get_tid(sd_resolve *resolve, pid_t *tid); - -int sd_resolve_attach_event(sd_resolve *resolve, sd_event *e, int64_t priority); -int sd_resolve_detach_event(sd_resolve *resolve); -sd_event *sd_resolve_get_event(sd_resolve *resolve); - -/* Issue a name-to-address query on the specified session. The - * arguments are compatible with those of libc's - * getaddrinfo(3). The function returns a new query object. When the - * query is completed, you may retrieve the results using - * sd_resolve_getaddrinfo_done(). */ -int sd_resolve_getaddrinfo(sd_resolve *resolve, sd_resolve_query **q, const char *node, const char *service, const struct addrinfo *hints, sd_resolve_getaddrinfo_handler_t callback, void *userdata); - -/* Issue an address-to-name query on the specified session. The - * arguments are compatible with those of libc's - * getnameinfo(3). The function returns a new query object. When the - * query is completed, you may retrieve the results using - * sd_resolve_getnameinfo_done(). Set gethost (resp. getserv) to non-zero - * if you want to query the hostname (resp. the service name). */ -int sd_resolve_getnameinfo(sd_resolve *resolve, sd_resolve_query **q, const struct sockaddr *sa, socklen_t salen, int flags, uint64_t get, sd_resolve_getnameinfo_handler_t callback, void *userdata); - -sd_resolve_query *sd_resolve_query_ref(sd_resolve_query* q); -sd_resolve_query *sd_resolve_query_unref(sd_resolve_query* q); - -/* Returns non-zero when the query operation specified by q has been completed. */ -int sd_resolve_query_is_done(sd_resolve_query*q); - -void *sd_resolve_query_get_userdata(sd_resolve_query *q); -void *sd_resolve_query_set_userdata(sd_resolve_query *q, void *userdata); - -sd_resolve *sd_resolve_query_get_resolve(sd_resolve_query *q); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_resolve, sd_resolve_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_resolve_query, sd_resolve_query_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-utf8.h b/src/systemd/sd-utf8.h deleted file mode 100644 index 6781983878..0000000000 --- a/src/systemd/sd-utf8.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef foosdutf8hfoo -#define foosdutf8hfoo - -/*** - This file is part of systemd. - - Copyright 2013 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 "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -_sd_pure_ const char *sd_utf8_is_valid(const char *s); -_sd_pure_ const char *sd_ascii_is_valid(const char *s); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/sysusers/Makefile b/src/sysusers/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/sysusers/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/sysusers/Makefile b/src/sysusers/Makefile new file mode 100644 index 0000000000..c9aaad0369 --- /dev/null +++ b/src/sysusers/Makefile @@ -0,0 +1,61 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_SYSUSERS),) +systemd_sysusers_SOURCES = \ + src/sysusers/sysusers.c + +systemd_sysusers_LDADD = \ + libshared.la + +bin_PROGRAMS += \ + systemd-sysusers + +nodist_systemunit_DATA += \ + units/systemd-sysusers.service + +SYSINIT_TARGET_WANTS += \ + systemd-sysusers.service + +nodist_sysusers_DATA = \ + sysusers.d/systemd.conf \ + sysusers.d/basic.conf + +ifneq ($(HAVE_REMOTE),) +nodist_sysusers_DATA += \ + sysusers.d/systemd-remote.conf +endif # HAVE_REMOTE + +INSTALL_DIRS += \ + $(sysusersdir) +endif # ENABLE_SYSUSERS + +EXTRA_DIST += \ + units/systemd-sysusers.service.in \ + sysusers.d/systemd.conf.m4 \ + sysusers.d/systemd-remote.conf.m4 \ + sysusers.d/basic.conf.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/sysv-generator/Makefile b/src/sysv-generator/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/sysv-generator/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/sysv-generator/sysv-generator.c b/src/sysv-generator/sysv-generator.c deleted file mode 100644 index fe4bbeeb75..0000000000 --- a/src/sysv-generator/sysv-generator.c +++ /dev/null @@ -1,1039 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Thomas H.P. Andersen - Copyright 2010 Lennart Poettering - Copyright 2011 Michal Schmidt - - 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 -#include - -#include "alloc-util.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "hashmap.h" -#include "hexdecoct.h" -#include "install.h" -#include "log.h" -#include "mkdir.h" -#include "path-lookup.h" -#include "path-util.h" -#include "set.h" -#include "special.h" -#include "stat-util.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "util.h" - -typedef enum RunlevelType { - RUNLEVEL_UP, - RUNLEVEL_DOWN -} RunlevelType; - -static const struct { - const char *path; - const char *target; - const RunlevelType type; -} rcnd_table[] = { - /* Standard SysV runlevels for start-up */ - { "rc1.d", SPECIAL_RESCUE_TARGET, RUNLEVEL_UP }, - { "rc2.d", SPECIAL_MULTI_USER_TARGET, RUNLEVEL_UP }, - { "rc3.d", SPECIAL_MULTI_USER_TARGET, RUNLEVEL_UP }, - { "rc4.d", SPECIAL_MULTI_USER_TARGET, RUNLEVEL_UP }, - { "rc5.d", SPECIAL_GRAPHICAL_TARGET, RUNLEVEL_UP }, - - /* Standard SysV runlevels for shutdown */ - { "rc0.d", SPECIAL_POWEROFF_TARGET, RUNLEVEL_DOWN }, - { "rc6.d", SPECIAL_REBOOT_TARGET, RUNLEVEL_DOWN } - - /* Note that the order here matters, as we read the - directories in this order, and we want to make sure that - sysv_start_priority is known when we first load the - unit. And that value we only know from S links. Hence - UP must be read before DOWN */ -}; - -static const char *arg_dest = "/tmp"; - -typedef struct SysvStub { - char *name; - char *path; - char *description; - int sysv_start_priority; - char *pid_file; - char **before; - char **after; - char **wants; - char **wanted_by; - char **conflicts; - bool has_lsb; - bool reload; - bool loaded; -} SysvStub; - -static void free_sysvstub(SysvStub *s) { - if (!s) - return; - - free(s->name); - free(s->path); - free(s->description); - free(s->pid_file); - strv_free(s->before); - strv_free(s->after); - strv_free(s->wants); - strv_free(s->wanted_by); - strv_free(s->conflicts); - free(s); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub*, free_sysvstub); - -static void free_sysvstub_hashmapp(Hashmap **h) { - SysvStub *stub; - - while ((stub = hashmap_steal_first(*h))) - free_sysvstub(stub); - - hashmap_free(*h); -} - -static int add_symlink(const char *service, const char *where) { - const char *from, *to; - int r; - - assert(service); - assert(where); - - from = strjoina(arg_dest, "/", service); - to = strjoina(arg_dest, "/", where, ".wants/", service); - - mkdir_parents_label(to, 0755); - - r = symlink(from, to); - if (r < 0) { - if (errno == EEXIST) - return 0; - - return -errno; - } - - return 1; -} - -static int add_alias(const char *service, const char *alias) { - const char *link; - int r; - - assert(service); - assert(alias); - - link = strjoina(arg_dest, "/", alias); - - r = symlink(service, link); - if (r < 0) { - if (errno == EEXIST) - return 0; - - return -errno; - } - - return 1; -} - -static int generate_unit_file(SysvStub *s) { - _cleanup_fclose_ FILE *f = NULL; - const char *unit; - char **p; - int r; - - assert(s); - - if (!s->loaded) - return 0; - - unit = strjoina(arg_dest, "/", s->name); - - /* We might already have a symlink with the same name from a Provides:, - * or from backup files like /etc/init.d/foo.bak. Real scripts always win, - * so remove an existing link */ - if (is_symlink(unit) > 0) { - log_warning("Overwriting existing symlink %s with real service.", unit); - (void) unlink(unit); - } - - f = fopen(unit, "wxe"); - if (!f) - return log_error_errno(errno, "Failed to create unit file %s: %m", unit); - - fprintf(f, - "# Automatically generated by systemd-sysv-generator\n\n" - "[Unit]\n" - "Documentation=man:systemd-sysv-generator(8)\n" - "SourcePath=%s\n", - s->path); - - if (s->description) - fprintf(f, "Description=%s\n", s->description); - - STRV_FOREACH(p, s->before) - fprintf(f, "Before=%s\n", *p); - STRV_FOREACH(p, s->after) - fprintf(f, "After=%s\n", *p); - STRV_FOREACH(p, s->wants) - fprintf(f, "Wants=%s\n", *p); - STRV_FOREACH(p, s->conflicts) - fprintf(f, "Conflicts=%s\n", *p); - - fprintf(f, - "\n[Service]\n" - "Type=forking\n" - "Restart=no\n" - "TimeoutSec=5min\n" - "IgnoreSIGPIPE=no\n" - "KillMode=process\n" - "GuessMainPID=no\n" - "RemainAfterExit=%s\n", - yes_no(!s->pid_file)); - - if (s->pid_file) - fprintf(f, "PIDFile=%s\n", s->pid_file); - - fprintf(f, - "ExecStart=%s start\n" - "ExecStop=%s stop\n", - s->path, s->path); - - if (s->reload) - fprintf(f, "ExecReload=%s reload\n", s->path); - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write unit %s: %m", unit); - - STRV_FOREACH(p, s->wanted_by) { - r = add_symlink(s->name, *p); - if (r < 0) - log_warning_errno(r, "Failed to create 'Wants' symlink to %s, ignoring: %m", *p); - } - - return 1; -} - -static bool usage_contains_reload(const char *line) { - return (strcasestr(line, "{reload|") || - strcasestr(line, "{reload}") || - strcasestr(line, "{reload\"") || - strcasestr(line, "|reload|") || - strcasestr(line, "|reload}") || - strcasestr(line, "|reload\"")); -} - -static char *sysv_translate_name(const char *name) { - _cleanup_free_ char *c = NULL; - char *res; - - c = strdup(name); - if (!c) - return NULL; - - res = endswith(c, ".sh"); - if (res) - *res = 0; - - if (unit_name_mangle(c, UNIT_NAME_NOGLOB, &res) < 0) - return NULL; - - return res; -} - -static int sysv_translate_facility(const char *name, const char *filename, char **ret) { - - /* We silently ignore the $ prefix here. According to the LSB - * spec it simply indicates whether something is a - * standardized name or a distribution-specific one. Since we - * just follow what already exists and do not introduce new - * uses or names we don't care who introduced a new name. */ - - static const char * const table[] = { - /* LSB defined facilities */ - "local_fs", NULL, - "network", SPECIAL_NETWORK_ONLINE_TARGET, - "named", SPECIAL_NSS_LOOKUP_TARGET, - "portmap", SPECIAL_RPCBIND_TARGET, - "remote_fs", SPECIAL_REMOTE_FS_TARGET, - "syslog", NULL, - "time", SPECIAL_TIME_SYNC_TARGET, - }; - - char *filename_no_sh, *e, *m; - const char *n; - unsigned i; - int r; - - assert(name); - assert(filename); - assert(ret); - - n = *name == '$' ? name + 1 : name; - - for (i = 0; i < ELEMENTSOF(table); i += 2) { - if (!streq(table[i], n)) - continue; - - if (!table[i+1]) - return 0; - - m = strdup(table[i+1]); - if (!m) - return log_oom(); - - *ret = m; - return 1; - } - - /* If we don't know this name, fallback heuristics to figure - * out whether something is a target or a service alias. */ - - /* Facilities starting with $ are most likely targets */ - if (*name == '$') { - r = unit_name_build(n, NULL, ".target", ret); - if (r < 0) - return log_error_errno(r, "Failed to build name: %m"); - - return r; - } - - /* Strip ".sh" suffix from file name for comparison */ - filename_no_sh = strdupa(filename); - e = endswith(filename_no_sh, ".sh"); - if (e) { - *e = '\0'; - filename = filename_no_sh; - } - - /* Names equaling the file name of the services are redundant */ - if (streq_ptr(n, filename)) - return 0; - - /* Everything else we assume to be normal service names */ - m = sysv_translate_name(n); - if (!m) - return log_oom(); - - *ret = m; - return 1; -} - -static int handle_provides(SysvStub *s, unsigned line, const char *full_text, const char *text) { - int r; - - assert(s); - assert(full_text); - assert(text); - - for (;;) { - _cleanup_free_ char *word = NULL, *m = NULL; - - r = extract_first_word(&text, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); - if (r < 0) - return log_error_errno(r, "Failed to parse word from provides string: %m"); - if (r == 0) - break; - - r = sysv_translate_facility(word, basename(s->path), &m); - if (r <= 0) /* continue on error */ - continue; - - switch (unit_name_to_type(m)) { - - case UNIT_SERVICE: - log_debug("Adding Provides: alias '%s' for '%s'", m, s->name); - r = add_alias(s->name, m); - if (r < 0) - log_warning_errno(r, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s->path, line, m); - break; - - case UNIT_TARGET: - - /* NB: SysV targets which are provided by a - * service are pulled in by the services, as - * an indication that the generic service is - * now available. This is strictly one-way. - * The targets do NOT pull in SysV services! */ - - r = strv_extend(&s->before, m); - if (r < 0) - return log_oom(); - - r = strv_extend(&s->wants, m); - if (r < 0) - return log_oom(); - - if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET)) { - r = strv_extend(&s->before, SPECIAL_NETWORK_TARGET); - if (r < 0) - return log_oom(); - } - - break; - - case _UNIT_TYPE_INVALID: - log_warning("Unit name '%s' is invalid", m); - break; - - default: - log_warning("Unknown unit type for unit '%s'", m); - } - } - - return 0; -} - -static int handle_dependencies(SysvStub *s, unsigned line, const char *full_text, const char *text) { - int r; - - assert(s); - assert(full_text); - assert(text); - - for (;;) { - _cleanup_free_ char *word = NULL, *m = NULL; - bool is_before; - - r = extract_first_word(&text, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); - if (r < 0) - return log_error_errno(r, "Failed to parse word from provides string: %m"); - if (r == 0) - break; - - r = sysv_translate_facility(word, basename(s->path), &m); - if (r <= 0) /* continue on error */ - continue; - - is_before = startswith_no_case(full_text, "X-Start-Before:"); - - if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) { - /* the network-online target is special, as it needs to be actively pulled in */ - r = strv_extend(&s->after, m); - if (r < 0) - return log_oom(); - - r = strv_extend(&s->wants, m); - } else - r = strv_extend(is_before ? &s->before : &s->after, m); - if (r < 0) - return log_oom(); - } - - return 0; -} - -static int load_sysv(SysvStub *s) { - _cleanup_fclose_ FILE *f; - unsigned line = 0; - int r; - enum { - NORMAL, - DESCRIPTION, - LSB, - LSB_DESCRIPTION, - USAGE_CONTINUATION - } state = NORMAL; - _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL; - char *description; - bool supports_reload = false; - char l[LINE_MAX]; - - assert(s); - - f = fopen(s->path, "re"); - if (!f) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open %s: %m", s->path); - } - - log_debug("Loading SysV script %s", s->path); - - FOREACH_LINE(l, f, goto fail) { - char *t; - - line++; - - t = strstrip(l); - if (*t != '#') { - /* Try to figure out whether this init script supports - * the reload operation. This heuristic looks for - * "Usage" lines which include the reload option. */ - if ( state == USAGE_CONTINUATION || - (state == NORMAL && strcasestr(t, "usage"))) { - if (usage_contains_reload(t)) { - supports_reload = true; - state = NORMAL; - } else if (t[strlen(t)-1] == '\\') - state = USAGE_CONTINUATION; - else - state = NORMAL; - } - - continue; - } - - if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) { - state = LSB; - s->has_lsb = true; - continue; - } - - if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) { - state = NORMAL; - continue; - } - - t++; - t += strspn(t, WHITESPACE); - - if (state == NORMAL) { - - /* Try to parse Red Hat style description */ - - if (startswith_no_case(t, "description:")) { - - size_t k; - const char *j; - - k = strlen(t); - if (k > 0 && t[k-1] == '\\') { - state = DESCRIPTION; - t[k-1] = 0; - } - - j = strstrip(t+12); - if (isempty(j)) - j = NULL; - - r = free_and_strdup(&chkconfig_description, j); - if (r < 0) - return log_oom(); - - } else if (startswith_no_case(t, "pidfile:")) { - const char *fn; - - state = NORMAL; - - fn = strstrip(t+8); - if (!path_is_absolute(fn)) { - log_error("[%s:%u] PID file not absolute. Ignoring.", s->path, line); - continue; - } - - r = free_and_strdup(&s->pid_file, fn); - if (r < 0) - return log_oom(); - } - - } else if (state == DESCRIPTION) { - - /* Try to parse Red Hat style description - * continuation */ - - size_t k; - char *j; - - k = strlen(t); - if (k > 0 && t[k-1] == '\\') - t[k-1] = 0; - else - state = NORMAL; - - j = strstrip(t); - if (!isempty(j)) { - char *d = NULL; - - if (chkconfig_description) - d = strjoin(chkconfig_description, " ", j, NULL); - else - d = strdup(j); - if (!d) - return log_oom(); - - free(chkconfig_description); - chkconfig_description = d; - } - - } else if (state == LSB || state == LSB_DESCRIPTION) { - - if (startswith_no_case(t, "Provides:")) { - state = LSB; - - r = handle_provides(s, line, t, t + 9); - if (r < 0) - return r; - - } else if (startswith_no_case(t, "Required-Start:") || - startswith_no_case(t, "Should-Start:") || - startswith_no_case(t, "X-Start-Before:") || - startswith_no_case(t, "X-Start-After:")) { - - state = LSB; - - r = handle_dependencies(s, line, t, strchr(t, ':') + 1); - if (r < 0) - return r; - - } else if (startswith_no_case(t, "Description:")) { - const char *j; - - state = LSB_DESCRIPTION; - - j = strstrip(t+12); - if (isempty(j)) - j = NULL; - - r = free_and_strdup(&long_description, j); - if (r < 0) - return log_oom(); - - } else if (startswith_no_case(t, "Short-Description:")) { - const char *j; - - state = LSB; - - j = strstrip(t+18); - if (isempty(j)) - j = NULL; - - r = free_and_strdup(&short_description, j); - if (r < 0) - return log_oom(); - - } else if (state == LSB_DESCRIPTION) { - - if (startswith(l, "#\t") || startswith(l, "# ")) { - const char *j; - - j = strstrip(t); - if (!isempty(j)) { - char *d = NULL; - - if (long_description) - d = strjoin(long_description, " ", t, NULL); - else - d = strdup(j); - if (!d) - return log_oom(); - - free(long_description); - long_description = d; - } - - } else - state = LSB; - } - } - } - - s->reload = supports_reload; - - /* We use the long description only if - * no short description is set. */ - - if (short_description) - description = short_description; - else if (chkconfig_description) - description = chkconfig_description; - else if (long_description) - description = long_description; - else - description = NULL; - - if (description) { - char *d; - - d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description); - if (!d) - return log_oom(); - - s->description = d; - } - - s->loaded = true; - return 0; - -fail: - return log_error_errno(errno, "Failed to read configuration file '%s': %m", s->path); -} - -static int fix_order(SysvStub *s, Hashmap *all_services) { - SysvStub *other; - Iterator j; - int r; - - assert(s); - - if (!s->loaded) - return 0; - - if (s->sysv_start_priority < 0) - return 0; - - HASHMAP_FOREACH(other, all_services, j) { - if (s == other) - continue; - - if (!other->loaded) - continue; - - if (other->sysv_start_priority < 0) - continue; - - /* If both units have modern headers we don't care - * about the priorities */ - if (s->has_lsb && other->has_lsb) - continue; - - if (other->sysv_start_priority < s->sysv_start_priority) { - r = strv_extend(&s->after, other->name); - if (r < 0) - return log_oom(); - - } else if (other->sysv_start_priority > s->sysv_start_priority) { - r = strv_extend(&s->before, other->name); - if (r < 0) - return log_oom(); - } else - continue; - - /* FIXME: Maybe we should compare the name here lexicographically? */ - } - - return 0; -} - -static int acquire_search_path(const char *def, const char *envvar, char ***ret) { - _cleanup_strv_free_ char **l = NULL; - const char *e; - int r; - - assert(def); - assert(envvar); - - e = getenv(envvar); - if (e) { - r = path_split_and_make_absolute(e, &l); - if (r < 0) - return log_error_errno(r, "Failed to make $%s search path absolute: %m", envvar); - } - - if (strv_isempty(l)) { - strv_free(l); - - l = strv_new(def, NULL); - if (!l) - return log_oom(); - } - - if (!path_strv_resolve_uniq(l, NULL)) - return log_oom(); - - *ret = l; - l = NULL; - - return 0; -} - -static int enumerate_sysv(const LookupPaths *lp, Hashmap *all_services) { - _cleanup_strv_free_ char **sysvinit_path = NULL; - char **path; - int r; - - assert(lp); - - r = acquire_search_path(SYSTEM_SYSVINIT_PATH, "SYSTEMD_SYSVINIT_PATH", &sysvinit_path); - if (r < 0) - return r; - - STRV_FOREACH(path, sysvinit_path) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - - d = opendir(*path); - if (!d) { - if (errno != ENOENT) - log_warning_errno(errno, "Opening %s failed, ignoring: %m", *path); - continue; - } - - FOREACH_DIRENT(de, d, log_error_errno(errno, "Failed to enumerate directory %s, ignoring: %m", *path)) { - _cleanup_free_ char *fpath = NULL, *name = NULL; - _cleanup_(free_sysvstubp) SysvStub *service = NULL; - struct stat st; - - if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) { - log_warning_errno(errno, "stat() failed on %s/%s, ignoring: %m", *path, de->d_name); - continue; - } - - if (!(st.st_mode & S_IXUSR)) - continue; - - if (!S_ISREG(st.st_mode)) - continue; - - name = sysv_translate_name(de->d_name); - if (!name) - return log_oom(); - - if (hashmap_contains(all_services, name)) - continue; - - r = unit_file_exists(UNIT_FILE_SYSTEM, lp, name); - if (r < 0 && !IN_SET(r, -ELOOP, -ERFKILL, -EADDRNOTAVAIL)) { - log_debug_errno(r, "Failed to detect whether %s exists, skipping: %m", name); - continue; - } else if (r != 0) { - log_debug("Native unit for %s already exists, skipping.", name); - continue; - } - - fpath = strjoin(*path, "/", de->d_name, NULL); - if (!fpath) - return log_oom(); - - service = new0(SysvStub, 1); - if (!service) - return log_oom(); - - service->sysv_start_priority = -1; - service->name = name; - service->path = fpath; - name = fpath = NULL; - - r = hashmap_put(all_services, service->name, service); - if (r < 0) - return log_oom(); - - service = NULL; - } - } - - return 0; -} - -static int set_dependencies_from_rcnd(const LookupPaths *lp, Hashmap *all_services) { - Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {}; - _cleanup_set_free_ Set *shutdown_services = NULL; - _cleanup_strv_free_ char **sysvrcnd_path = NULL; - SysvStub *service; - unsigned i; - Iterator j; - char **p; - int r; - - assert(lp); - - r = acquire_search_path(SYSTEM_SYSVRCND_PATH, "SYSTEMD_SYSVRCND_PATH", &sysvrcnd_path); - if (r < 0) - return r; - - STRV_FOREACH(p, sysvrcnd_path) { - for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) { - - _cleanup_closedir_ DIR *d = NULL; - _cleanup_free_ char *path = NULL; - struct dirent *de; - - path = strjoin(*p, "/", rcnd_table[i].path, NULL); - if (!path) { - r = log_oom(); - goto finish; - } - - d = opendir(path); - if (!d) { - if (errno != ENOENT) - log_warning_errno(errno, "Opening %s failed, ignoring: %m", path); - - continue; - } - - FOREACH_DIRENT(de, d, log_error_errno(errno, "Failed to enumerate directory %s, ignoring: %m", path)) { - _cleanup_free_ char *name = NULL, *fpath = NULL; - int a, b; - - if (de->d_name[0] != 'S' && de->d_name[0] != 'K') - continue; - - if (strlen(de->d_name) < 4) - continue; - - a = undecchar(de->d_name[1]); - b = undecchar(de->d_name[2]); - - if (a < 0 || b < 0) - continue; - - fpath = strjoin(*p, "/", de->d_name, NULL); - if (!fpath) { - r = log_oom(); - goto finish; - } - - name = sysv_translate_name(de->d_name + 3); - if (!name) { - r = log_oom(); - goto finish; - } - - service = hashmap_get(all_services, name); - if (!service) { - log_debug("Ignoring %s symlink in %s, not generating %s.", de->d_name, rcnd_table[i].path, name); - continue; - } - - if (de->d_name[0] == 'S') { - - if (rcnd_table[i].type == RUNLEVEL_UP) - service->sysv_start_priority = MAX(a*10 + b, service->sysv_start_priority); - - r = set_ensure_allocated(&runlevel_services[i], NULL); - if (r < 0) { - log_oom(); - goto finish; - } - - r = set_put(runlevel_services[i], service); - if (r < 0) { - log_oom(); - goto finish; - } - - } else if (de->d_name[0] == 'K' && - (rcnd_table[i].type == RUNLEVEL_DOWN)) { - - r = set_ensure_allocated(&shutdown_services, NULL); - if (r < 0) { - log_oom(); - goto finish; - } - - r = set_put(shutdown_services, service); - if (r < 0) { - log_oom(); - goto finish; - } - } - } - } - } - - - for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) - SET_FOREACH(service, runlevel_services[i], j) { - r = strv_extend(&service->before, rcnd_table[i].target); - if (r < 0) { - log_oom(); - goto finish; - } - r = strv_extend(&service->wanted_by, rcnd_table[i].target); - if (r < 0) { - log_oom(); - goto finish; - } - } - - SET_FOREACH(service, shutdown_services, j) { - r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET); - if (r < 0) { - log_oom(); - goto finish; - } - r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET); - if (r < 0) { - log_oom(); - goto finish; - } - } - - r = 0; - -finish: - for (i = 0; i < ELEMENTSOF(rcnd_table); i++) - set_free(runlevel_services[i]); - - return r; -} - -int main(int argc, char *argv[]) { - _cleanup_(free_sysvstub_hashmapp) Hashmap *all_services = NULL; - _cleanup_lookup_paths_free_ LookupPaths lp = {}; - SysvStub *service; - Iterator j; - int r; - - if (argc > 1 && argc != 4) { - log_error("This program takes three or no arguments."); - return EXIT_FAILURE; - } - - if (argc > 1) - arg_dest = argv[3]; - - log_set_target(LOG_TARGET_SAFE); - log_parse_environment(); - log_open(); - - umask(0022); - - r = lookup_paths_init(&lp, UNIT_FILE_SYSTEM, LOOKUP_PATHS_EXCLUDE_GENERATED, NULL); - if (r < 0) { - log_error_errno(r, "Failed to find lookup paths: %m"); - goto finish; - } - - all_services = hashmap_new(&string_hash_ops); - if (!all_services) { - r = log_oom(); - goto finish; - } - - r = enumerate_sysv(&lp, all_services); - if (r < 0) - goto finish; - - r = set_dependencies_from_rcnd(&lp, all_services); - if (r < 0) - goto finish; - - HASHMAP_FOREACH(service, all_services, j) - (void) load_sysv(service); - - HASHMAP_FOREACH(service, all_services, j) { - (void) fix_order(service, all_services); - (void) generate_unit_file(service); - } - - r = 0; - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/test/Makefile b/src/test/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/test/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/test/Makefile b/src/test/Makefile new file mode 100644 index 0000000000..f416b4aadf --- /dev/null +++ b/src/test/Makefile @@ -0,0 +1,35 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +test_id128_SOURCES = \ + src/test/test-id128.c + +test_id128_LDADD = \ + libshared.la + +tests += \ + test-id128 + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/test/test-condition.c b/src/test/test-condition.c index 8903d10db7..c4ff41dd0e 100644 --- a/src/test/test-condition.c +++ b/src/test/test-condition.c @@ -17,7 +17,7 @@ along with systemd; If not, see . ***/ -#include "sd-id128.h" +#include #include "alloc-util.h" #include "apparmor-util.h" diff --git a/src/test/test-daemon.c b/src/test/test-daemon.c index a7cb426282..b2cd3c7663 100644 --- a/src/test/test-daemon.c +++ b/src/test/test-daemon.c @@ -19,7 +19,7 @@ #include -#include "sd-daemon.h" +#include #include "strv.h" diff --git a/src/test/test-helper.h b/src/test/test-helper.h index ddb10f88fd..01068da347 100644 --- a/src/test/test-helper.h +++ b/src/test/test-helper.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-daemon.h" +#include #include "macro.h" diff --git a/src/test/test-id128.c b/src/test/test-id128.c index 96aa008c06..90c4860e2a 100644 --- a/src/test/test-id128.c +++ b/src/test/test-id128.c @@ -19,8 +19,8 @@ #include -#include "sd-daemon.h" -#include "sd-id128.h" +#include +#include #include "alloc-util.h" #include "macro.h" diff --git a/src/test/test-netlink-manual.c b/src/test/test-netlink-manual.c index bc6dd0926c..57e244eb79 100644 --- a/src/test/test-netlink-manual.c +++ b/src/test/test-netlink-manual.c @@ -23,7 +23,7 @@ #include #include -#include "sd-netlink.h" +#include #include "macro.h" #include "util.h" diff --git a/src/timedate/.gitignore b/src/timedate/.gitignore deleted file mode 100644 index 48757f0968..0000000000 --- a/src/timedate/.gitignore +++ /dev/null @@ -1 +0,0 @@ -org.freedesktop.timedate1.policy diff --git a/src/timedate/Makefile b/src/timedate/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/timedate/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/timedate/org.freedesktop.timedate1.conf b/src/timedate/org.freedesktop.timedate1.conf deleted file mode 100644 index 36557d5841..0000000000 --- a/src/timedate/org.freedesktop.timedate1.conf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/timedate/org.freedesktop.timedate1.policy.in b/src/timedate/org.freedesktop.timedate1.policy.in deleted file mode 100644 index aa30b70831..0000000000 --- a/src/timedate/org.freedesktop.timedate1.policy.in +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - The systemd Project - http://www.freedesktop.org/wiki/Software/systemd - - - <_description>Set system time - <_message>Authentication is required to set the system time. - - auth_admin_keep - auth_admin_keep - auth_admin_keep - - org.freedesktop.timedate1.set-timezone org.freedesktop.timedate1.set-ntp - - - - <_description>Set system timezone - <_message>Authentication is required to set the system timezone. - - auth_admin_keep - auth_admin_keep - auth_admin_keep - - - - - <_description>Set RTC to local timezone or UTC - <_message>Authentication is required to control whether - the RTC stores the local or UTC time. - - auth_admin_keep - auth_admin_keep - auth_admin_keep - - - - - <_description>Turn network time synchronization on or off - <_message>Authentication is required to control whether - network time synchronization shall be enabled. - - auth_admin_keep - auth_admin_keep - auth_admin_keep - - - - diff --git a/src/timedate/org.freedesktop.timedate1.service b/src/timedate/org.freedesktop.timedate1.service deleted file mode 100644 index 875f4bec78..0000000000 --- a/src/timedate/org.freedesktop.timedate1.service +++ /dev/null @@ -1,12 +0,0 @@ -# This file is part of systemd. -# -# 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. - -[D-BUS Service] -Name=org.freedesktop.timedate1 -Exec=/bin/false -User=root -SystemdService=dbus-org.freedesktop.timedate1.service diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c deleted file mode 100644 index a2270aff46..0000000000 --- a/src/timedate/timedatectl.c +++ /dev/null @@ -1,506 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 Lennart Poettering - Copyright 2013 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 -#include -#include -#include - -#include "sd-bus.h" - -#include "bus-error.h" -#include "bus-util.h" -#include "pager.h" -#include "parse-util.h" -#include "spawn-polkit-agent.h" -#include "strv.h" -#include "terminal-util.h" -#include "util.h" - -static bool arg_no_pager = false; -static bool arg_ask_password = true; -static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; -static char *arg_host = NULL; -static bool arg_adjust_system_clock = false; - -static void polkit_agent_open_if_enabled(void) { - - /* Open the polkit agent as a child process if necessary */ - if (!arg_ask_password) - return; - - if (arg_transport != BUS_TRANSPORT_LOCAL) - return; - - polkit_agent_open(); -} - -typedef struct StatusInfo { - usec_t time; - char *timezone; - - usec_t rtc_time; - bool rtc_local; - - bool ntp_enabled; - bool ntp_capable; - bool ntp_synced; -} StatusInfo; - -static void status_info_clear(StatusInfo *info) { - if (info) { - free(info->timezone); - zero(*info); - } -} - -static void print_status_info(const StatusInfo *i) { - char a[FORMAT_TIMESTAMP_MAX]; - struct tm tm; - time_t sec; - bool have_time = false; - const char *old_tz = NULL, *tz; - int r; - - assert(i); - - /* Save the old $TZ */ - tz = getenv("TZ"); - if (tz) - old_tz = strdupa(tz); - - /* Set the new $TZ */ - if (setenv("TZ", isempty(i->timezone) ? "UTC" : i->timezone, true) < 0) - log_warning_errno(errno, "Failed to set TZ environment variable, ignoring: %m"); - else - tzset(); - - if (i->time != 0) { - sec = (time_t) (i->time / USEC_PER_SEC); - have_time = true; - } else if (IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE)) { - sec = time(NULL); - have_time = true; - } else - log_warning("Could not get time from timedated and not operating locally, ignoring."); - - if (have_time) { - xstrftime(a, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)); - printf(" Local time: %.*s\n", (int) sizeof(a), a); - - xstrftime(a, "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)); - printf(" Universal time: %.*s\n", (int) sizeof(a), a); - } else { - printf(" Local time: %s\n", "n/a"); - printf(" Universal time: %s\n", "n/a"); - } - - if (i->rtc_time > 0) { - time_t rtc_sec; - - rtc_sec = (time_t) (i->rtc_time / USEC_PER_SEC); - xstrftime(a, "%a %Y-%m-%d %H:%M:%S", gmtime_r(&rtc_sec, &tm)); - printf(" RTC time: %.*s\n", (int) sizeof(a), a); - } else - printf(" RTC time: %s\n", "n/a"); - - if (have_time) - xstrftime(a, "%Z, %z", localtime_r(&sec, &tm)); - - /* Restore the $TZ */ - if (old_tz) - r = setenv("TZ", old_tz, true); - else - r = unsetenv("TZ"); - if (r < 0) - log_warning_errno(errno, "Failed to set TZ environment variable, ignoring: %m"); - else - tzset(); - - printf(" Time zone: %s (%.*s)\n" - " Network time on: %s\n" - "NTP synchronized: %s\n" - " RTC in local TZ: %s\n", - strna(i->timezone), (int) sizeof(a), have_time ? a : "n/a", - i->ntp_capable ? yes_no(i->ntp_enabled) : "n/a", - yes_no(i->ntp_synced), - yes_no(i->rtc_local)); - - if (i->rtc_local) - fputs("\n" ANSI_HIGHLIGHT - "Warning: The system is configured to read the RTC time in the local time zone.\n" - " This mode can not be fully supported. It will create various problems\n" - " with time zone changes and daylight saving time adjustments. The RTC\n" - " time is never updated, it relies on external facilities to maintain it.\n" - " If at all possible, use RTC in UTC by calling\n" - " 'timedatectl set-local-rtc 0'." ANSI_NORMAL "\n", stdout); -} - -static int show_status(sd_bus *bus, char **args, unsigned n) { - _cleanup_(status_info_clear) StatusInfo info = {}; - static const struct bus_properties_map map[] = { - { "Timezone", "s", NULL, offsetof(StatusInfo, timezone) }, - { "LocalRTC", "b", NULL, offsetof(StatusInfo, rtc_local) }, - { "NTP", "b", NULL, offsetof(StatusInfo, ntp_enabled) }, - { "CanNTP", "b", NULL, offsetof(StatusInfo, ntp_capable) }, - { "NTPSynchronized", "b", NULL, offsetof(StatusInfo, ntp_synced) }, - { "TimeUSec", "t", NULL, offsetof(StatusInfo, time) }, - { "RTCTimeUSec", "t", NULL, offsetof(StatusInfo, rtc_time) }, - {} - }; - int r; - - assert(bus); - - r = bus_map_all_properties(bus, - "org.freedesktop.timedate1", - "/org/freedesktop/timedate1", - map, - &info); - if (r < 0) - return log_error_errno(r, "Failed to query server: %m"); - - print_status_info(&info); - - return r; -} - -static int set_time(sd_bus *bus, char **args, unsigned n) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - bool relative = false, interactive = arg_ask_password; - usec_t t; - int r; - - assert(args); - assert(n == 2); - - polkit_agent_open_if_enabled(); - - r = parse_timestamp(args[1], &t); - if (r < 0) { - log_error("Failed to parse time specification: %s", args[1]); - return r; - } - - r = sd_bus_call_method(bus, - "org.freedesktop.timedate1", - "/org/freedesktop/timedate1", - "org.freedesktop.timedate1", - "SetTime", - &error, - NULL, - "xbb", (int64_t)t, relative, interactive); - if (r < 0) - log_error("Failed to set time: %s", bus_error_message(&error, -r)); - - return r; -} - -static int set_timezone(sd_bus *bus, char **args, unsigned n) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(args); - assert(n == 2); - - polkit_agent_open_if_enabled(); - - r = sd_bus_call_method(bus, - "org.freedesktop.timedate1", - "/org/freedesktop/timedate1", - "org.freedesktop.timedate1", - "SetTimezone", - &error, - NULL, - "sb", args[1], arg_ask_password); - if (r < 0) - log_error("Failed to set time zone: %s", bus_error_message(&error, -r)); - - return r; -} - -static int set_local_rtc(sd_bus *bus, char **args, unsigned n) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r, b; - - assert(args); - assert(n == 2); - - polkit_agent_open_if_enabled(); - - b = parse_boolean(args[1]); - if (b < 0) { - log_error("Failed to parse local RTC setting: %s", args[1]); - return b; - } - - r = sd_bus_call_method(bus, - "org.freedesktop.timedate1", - "/org/freedesktop/timedate1", - "org.freedesktop.timedate1", - "SetLocalRTC", - &error, - NULL, - "bbb", b, arg_adjust_system_clock, arg_ask_password); - if (r < 0) - log_error("Failed to set local RTC: %s", bus_error_message(&error, -r)); - - return r; -} - -static int set_ntp(sd_bus *bus, char **args, unsigned n) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int b, r; - - assert(args); - assert(n == 2); - - polkit_agent_open_if_enabled(); - - b = parse_boolean(args[1]); - if (b < 0) { - log_error("Failed to parse NTP setting: %s", args[1]); - return b; - } - - r = sd_bus_call_method(bus, - "org.freedesktop.timedate1", - "/org/freedesktop/timedate1", - "org.freedesktop.timedate1", - "SetNTP", - &error, - NULL, - "bb", b, arg_ask_password); - if (r < 0) - log_error("Failed to set ntp: %s", bus_error_message(&error, -r)); - - return r; -} - -static int list_timezones(sd_bus *bus, char **args, unsigned n) { - _cleanup_strv_free_ char **zones = NULL; - int r; - - assert(args); - assert(n == 1); - - r = get_timezones(&zones); - if (r < 0) - return log_error_errno(r, "Failed to read list of time zones: %m"); - - pager_open(arg_no_pager, false); - strv_print(zones); - - return 0; -} - -static void help(void) { - printf("%s [OPTIONS...] COMMAND ...\n\n" - "Query or change system time and date settings.\n\n" - " -h --help Show this help message\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-ask-password Do not prompt for password\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --adjust-system-clock Adjust system clock when changing local RTC mode\n\n" - "Commands:\n" - " status Show current time settings\n" - " set-time TIME Set system time\n" - " set-timezone ZONE Set system time zone\n" - " list-timezones Show known time zones\n" - " set-local-rtc BOOL Control whether RTC is in local time\n" - " set-ntp BOOL Enable or disable network time synchronization\n", - program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_ADJUST_SYSTEM_CLOCK, - ARG_NO_ASK_PASSWORD - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; - break; - - case 'M': - arg_transport = BUS_TRANSPORT_MACHINE; - arg_host = optarg; - break; - - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; - - case ARG_ADJUST_SYSTEM_CLOCK: - arg_adjust_system_clock = true; - break; - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) { - - static const struct { - const char* verb; - const enum { - MORE, - LESS, - EQUAL - } argc_cmp; - const int argc; - int (* const dispatch)(sd_bus *bus, char **args, unsigned n); - } verbs[] = { - { "status", LESS, 1, show_status }, - { "set-time", EQUAL, 2, set_time }, - { "set-timezone", EQUAL, 2, set_timezone }, - { "list-timezones", EQUAL, 1, list_timezones }, - { "set-local-rtc", EQUAL, 2, set_local_rtc }, - { "set-ntp", EQUAL, 2, set_ntp, }, - }; - - int left; - unsigned i; - - assert(argc >= 0); - assert(argv); - - left = argc - optind; - - if (left <= 0) - /* Special rule: no arguments means "status" */ - i = 0; - else { - if (streq(argv[optind], "help")) { - help(); - return 0; - } - - for (i = 0; i < ELEMENTSOF(verbs); i++) - if (streq(argv[optind], verbs[i].verb)) - break; - - if (i >= ELEMENTSOF(verbs)) { - log_error("Unknown operation %s", argv[optind]); - return -EINVAL; - } - } - - switch (verbs[i].argc_cmp) { - - case EQUAL: - if (left != verbs[i].argc) { - log_error("Invalid number of arguments."); - return -EINVAL; - } - - break; - - case MORE: - if (left < verbs[i].argc) { - log_error("Too few arguments."); - return -EINVAL; - } - - break; - - case LESS: - if (left > verbs[i].argc) { - log_error("Too many arguments."); - return -EINVAL; - } - - break; - - default: - assert_not_reached("Unknown comparison operator."); - } - - return verbs[i].dispatch(bus, argv + optind, left); -} - -int main(int argc, char *argv[]) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = bus_connect_transport(arg_transport, arg_host, false, &bus); - if (r < 0) { - log_error_errno(r, "Failed to create bus connection: %m"); - goto finish; - } - - r = timedatectl_main(bus, argc, argv); - -finish: - pager_close(); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/timedate/timedated.c b/src/timedate/timedated.c deleted file mode 100644 index ffec609c69..0000000000 --- a/src/timedate/timedated.c +++ /dev/null @@ -1,747 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 -#include - -#include "sd-bus.h" -#include "sd-event.h" -#include "sd-messages.h" - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-error.h" -#include "bus-util.h" -#include "clock-util.h" -#include "def.h" -#include "fileio-label.h" -#include "fs-util.h" -#include "path-util.h" -#include "selinux-util.h" -#include "strv.h" -#include "user-util.h" -#include "util.h" - -#define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n" -#define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n" - -static BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map timedated_errors[] = { - SD_BUS_ERROR_MAP("org.freedesktop.timedate1.NoNTPSupport", EOPNOTSUPP), - SD_BUS_ERROR_MAP_END -}; - -typedef struct Context { - char *zone; - bool local_rtc; - bool can_ntp; - bool use_ntp; - Hashmap *polkit_registry; -} Context; - -static void context_free(Context *c) { - assert(c); - - free(c->zone); - bus_verify_polkit_async_registry_free(c->polkit_registry); -} - -static int context_read_data(Context *c) { - _cleanup_free_ char *t = NULL; - int r; - - assert(c); - - r = get_timezone(&t); - if (r == -EINVAL) - log_warning_errno(r, "/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/."); - else if (r < 0) - log_warning_errno(r, "Failed to get target of /etc/localtime: %m"); - - free(c->zone); - c->zone = t; - t = NULL; - - c->local_rtc = clock_is_localtime(NULL) > 0; - - return 0; -} - -static int context_write_data_timezone(Context *c) { - _cleanup_free_ char *p = NULL; - int r = 0; - - assert(c); - - if (isempty(c->zone)) { - if (unlink("/etc/localtime") < 0 && errno != ENOENT) - r = -errno; - - return r; - } - - p = strappend("../usr/share/zoneinfo/", c->zone); - if (!p) - return log_oom(); - - r = symlink_atomic(p, "/etc/localtime"); - if (r < 0) - return r; - - return 0; -} - -static int context_write_data_local_rtc(Context *c) { - int r; - _cleanup_free_ char *s = NULL, *w = NULL; - - assert(c); - - r = read_full_file("/etc/adjtime", &s, NULL); - if (r < 0) { - if (r != -ENOENT) - return r; - - if (!c->local_rtc) - return 0; - - w = strdup(NULL_ADJTIME_LOCAL); - if (!w) - return -ENOMEM; - } else { - char *p; - const char *e = "\n"; /* default if there is less than 3 lines */ - const char *prepend = ""; - size_t a, b; - - p = strchrnul(s, '\n'); - if (*p == '\0') - /* only one line, no \n terminator */ - prepend = "\n0\n"; - else if (p[1] == '\0') { - /* only one line, with \n terminator */ - ++p; - prepend = "0\n"; - } else { - p = strchr(p+1, '\n'); - if (!p) { - /* only two lines, no \n terminator */ - prepend = "\n"; - p = s + strlen(s); - } else { - char *end; - /* third line might have a \n terminator or not */ - p++; - end = strchr(p, '\n'); - /* if we actually have a fourth line, use that as suffix "e", otherwise the default \n */ - if (end) - e = end; - } - } - - a = p - s; - b = strlen(e); - - w = new(char, a + (c->local_rtc ? 5 : 3) + strlen(prepend) + b + 1); - if (!w) - return -ENOMEM; - - *(char*) mempcpy(stpcpy(stpcpy(mempcpy(w, s, a), prepend), c->local_rtc ? "LOCAL" : "UTC"), e, b) = 0; - - if (streq(w, NULL_ADJTIME_UTC)) { - if (unlink("/etc/adjtime") < 0) - if (errno != ENOENT) - return -errno; - - return 0; - } - } - - mac_selinux_init(); - return write_string_file_atomic_label("/etc/adjtime", w); -} - -static int context_read_ntp(Context *c, sd_bus *bus) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *s; - int r; - - assert(c); - assert(bus); - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GetUnitFileState", - &error, - &reply, - "s", - "systemd-timesyncd.service"); - - if (r < 0) { - if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND) || - sd_bus_error_has_name(&error, "org.freedesktop.systemd1.LoadFailed") || - sd_bus_error_has_name(&error, "org.freedesktop.systemd1.NoSuchUnit")) - return 0; - - return r; - } - - r = sd_bus_message_read(reply, "s", &s); - if (r < 0) - return r; - - c->can_ntp = true; - c->use_ntp = STR_IN_SET(s, "enabled", "enabled-runtime"); - - return 0; -} - -static int context_start_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) { - int r; - - assert(bus); - assert(error); - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - enabled ? "StartUnit" : "StopUnit", - error, - NULL, - "ss", - "systemd-timesyncd.service", - "replace"); - if (r < 0) { - if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) || - sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") || - sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit")) - return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported."); - - return r; - } - - return 0; -} - -static int context_enable_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) { - int r; - - assert(bus); - assert(error); - - if (enabled) - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "EnableUnitFiles", - error, - NULL, - "asbb", 1, - "systemd-timesyncd.service", - false, true); - else - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "DisableUnitFiles", - error, - NULL, - "asb", 1, - "systemd-timesyncd.service", - false); - - if (r < 0) { - if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND)) - return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported."); - - return r; - } - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "Reload", - error, - NULL, - NULL); - if (r < 0) - return r; - - return 0; -} - -static int property_get_rtc_time( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - struct tm tm; - usec_t t; - int r; - - zero(tm); - r = clock_get_hwclock(&tm); - if (r == -EBUSY) { - log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp."); - t = 0; - } else if (r == -ENOENT) { - log_debug("/dev/rtc not found."); - t = 0; /* no RTC found */ - } else if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %m"); - else - t = (usec_t) timegm(&tm) * USEC_PER_SEC; - - return sd_bus_message_append(reply, "t", t); -} - -static int property_get_time( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME)); -} - -static int property_get_ntp_sync( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - return sd_bus_message_append(reply, "b", ntp_synced()); -} - -static int method_set_timezone(sd_bus_message *m, void *userdata, sd_bus_error *error) { - Context *c = userdata; - const char *z; - int interactive; - char *t; - int r; - - assert(m); - assert(c); - - r = sd_bus_message_read(m, "sb", &z, &interactive); - if (r < 0) - return r; - - if (!timezone_is_valid(z)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z); - - if (streq_ptr(z, c->zone)) - return sd_bus_reply_method_return(m, NULL); - - r = bus_verify_polkit_async( - m, - CAP_SYS_TIME, - "org.freedesktop.timedate1.set-timezone", - NULL, - interactive, - UID_INVALID, - &c->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - t = strdup(z); - if (!t) - return -ENOMEM; - - free(c->zone); - c->zone = t; - - /* 1. Write new configuration file */ - r = context_write_data_timezone(c); - if (r < 0) { - log_error_errno(r, "Failed to set time zone: %m"); - return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %m"); - } - - /* 2. Tell the kernel our timezone */ - clock_set_timezone(NULL); - - if (c->local_rtc) { - struct timespec ts; - struct tm *tm; - - /* 3. Sync RTC from system clock, with the new delta */ - assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); - assert_se(tm = localtime(&ts.tv_sec)); - clock_set_hwclock(tm); - } - - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE), - "TIMEZONE=%s", c->zone, - LOG_MESSAGE("Changed time zone to '%s'.", c->zone), - NULL); - - (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL); - - return sd_bus_reply_method_return(m, NULL); -} - -static int method_set_local_rtc(sd_bus_message *m, void *userdata, sd_bus_error *error) { - int lrtc, fix_system, interactive; - Context *c = userdata; - struct timespec ts; - int r; - - assert(m); - assert(c); - - r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive); - if (r < 0) - return r; - - if (lrtc == c->local_rtc) - return sd_bus_reply_method_return(m, NULL); - - r = bus_verify_polkit_async( - m, - CAP_SYS_TIME, - "org.freedesktop.timedate1.set-local-rtc", - NULL, - interactive, - UID_INVALID, - &c->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; - - c->local_rtc = lrtc; - - /* 1. Write new configuration file */ - r = context_write_data_local_rtc(c); - if (r < 0) { - log_error_errno(r, "Failed to set RTC to local/UTC: %m"); - return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %m"); - } - - /* 2. Tell the kernel our timezone */ - clock_set_timezone(NULL); - - /* 3. Synchronize clocks */ - assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); - - if (fix_system) { - struct tm tm; - - /* Sync system clock from RTC; first, - * initialize the timezone fields of - * struct tm. */ - if (c->local_rtc) - tm = *localtime(&ts.tv_sec); - else - tm = *gmtime(&ts.tv_sec); - - /* Override the main fields of - * struct tm, but not the timezone - * fields */ - if (clock_get_hwclock(&tm) >= 0) { - - /* And set the system clock - * with this */ - if (c->local_rtc) - ts.tv_sec = mktime(&tm); - else - ts.tv_sec = timegm(&tm); - - clock_settime(CLOCK_REALTIME, &ts); - } - - } else { - struct tm *tm; - - /* Sync RTC from system clock */ - if (c->local_rtc) - tm = localtime(&ts.tv_sec); - else - tm = gmtime(&ts.tv_sec); - - clock_set_hwclock(tm); - } - - log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC"); - - (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL); - - return sd_bus_reply_method_return(m, NULL); -} - -static int method_set_time(sd_bus_message *m, void *userdata, sd_bus_error *error) { - int relative, interactive; - Context *c = userdata; - int64_t utc; - struct timespec ts; - usec_t start; - struct tm* tm; - int r; - - assert(m); - assert(c); - - if (c->use_ntp) - return sd_bus_error_setf(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled"); - - /* this only gets used if dbus does not provide a timestamp */ - start = now(CLOCK_MONOTONIC); - - r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive); - if (r < 0) - return r; - - if (!relative && utc <= 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time"); - - if (relative && utc == 0) - return sd_bus_reply_method_return(m, NULL); - - if (relative) { - usec_t n, x; - - n = now(CLOCK_REALTIME); - x = n + utc; - - if ((utc > 0 && x < n) || - (utc < 0 && x > n)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow"); - - timespec_store(&ts, x); - } else - timespec_store(&ts, (usec_t) utc); - - r = bus_verify_polkit_async( - m, - CAP_SYS_TIME, - "org.freedesktop.timedate1.set-time", - NULL, - interactive, - UID_INVALID, - &c->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; - - /* adjust ts for time spent in program */ - r = sd_bus_message_get_monotonic_usec(m, &start); - /* when sd_bus_message_get_monotonic_usec() returns -ENODATA it does not modify &start */ - if (r < 0 && r != -ENODATA) - return r; - - timespec_store(&ts, timespec_load(&ts) + (now(CLOCK_MONOTONIC) - start)); - - /* Set system clock */ - if (clock_settime(CLOCK_REALTIME, &ts) < 0) { - log_error_errno(errno, "Failed to set local time: %m"); - return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m"); - } - - /* Sync down to RTC */ - if (c->local_rtc) - tm = localtime(&ts.tv_sec); - else - tm = gmtime(&ts.tv_sec); - clock_set_hwclock(tm); - - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_TIME_CHANGE), - "REALTIME="USEC_FMT, timespec_load(&ts), - LOG_MESSAGE("Changed local time to %s", ctime(&ts.tv_sec)), - NULL); - - return sd_bus_reply_method_return(m, NULL); -} - -static int method_set_ntp(sd_bus_message *m, void *userdata, sd_bus_error *error) { - int enabled, interactive; - Context *c = userdata; - int r; - - assert(m); - assert(c); - - r = sd_bus_message_read(m, "bb", &enabled, &interactive); - if (r < 0) - return r; - - if ((bool)enabled == c->use_ntp) - return sd_bus_reply_method_return(m, NULL); - - r = bus_verify_polkit_async( - m, - CAP_SYS_TIME, - "org.freedesktop.timedate1.set-ntp", - NULL, - interactive, - UID_INVALID, - &c->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; - - r = context_enable_ntp(sd_bus_message_get_bus(m), error, enabled); - if (r < 0) - return r; - - r = context_start_ntp(sd_bus_message_get_bus(m), error, enabled); - if (r < 0) - return r; - - c->use_ntp = enabled; - log_info("Set NTP to %s", enabled ? "enabled" : "disabled"); - - (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL); - - return sd_bus_reply_method_return(m, NULL); -} - -static const sd_bus_vtable timedate_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool, offsetof(Context, can_ntp), 0), - SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0), - SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0), - SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0), - SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_VTABLE_END, -}; - -static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - assert(c); - assert(event); - assert(_bus); - - r = sd_bus_default_system(&bus); - if (r < 0) - return log_error_errno(r, "Failed to get system bus connection: %m"); - - r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c); - if (r < 0) - return log_error_errno(r, "Failed to register object: %m"); - - r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0); - if (r < 0) - return log_error_errno(r, "Failed to register name: %m"); - - r = sd_bus_attach_event(bus, event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - - *_bus = bus; - bus = NULL; - - return 0; -} - -int main(int argc, char *argv[]) { - Context context = {}; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - if (argc != 1) { - log_error("This program takes no arguments."); - r = -EINVAL; - goto finish; - } - - r = sd_event_default(&event); - if (r < 0) { - log_error_errno(r, "Failed to allocate event loop: %m"); - goto finish; - } - - sd_event_set_watchdog(event, true); - - r = connect_bus(&context, event, &bus); - if (r < 0) - goto finish; - - (void) sd_bus_negotiate_timestamp(bus, true); - - r = context_read_data(&context); - if (r < 0) { - log_error_errno(r, "Failed to read time zone data: %m"); - goto finish; - } - - r = context_read_ntp(&context, bus); - if (r < 0) { - log_error_errno(r, "Failed to determine whether NTP is enabled: %m"); - goto finish; - } - - r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL); - if (r < 0) { - log_error_errno(r, "Failed to run event loop: %m"); - goto finish; - } - -finish: - context_free(&context); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/timesync/.gitignore b/src/timesync/.gitignore deleted file mode 100644 index 35f4d76f79..0000000000 --- a/src/timesync/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/timesyncd.conf -/timesyncd-gperf.c diff --git a/src/timesync/Makefile b/src/timesync/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/timesync/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/timesync/timesyncd-conf.c b/src/timesync/timesyncd-conf.c deleted file mode 100644 index 20c64a3354..0000000000 --- a/src/timesync/timesyncd-conf.c +++ /dev/null @@ -1,106 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Kay Sievers, 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 "alloc-util.h" -#include "def.h" -#include "extract-word.h" -#include "string-util.h" -#include "timesyncd-conf.h" -#include "timesyncd-manager.h" -#include "timesyncd-server.h" - -int manager_parse_server_string(Manager *m, ServerType type, const char *string) { - ServerName *first; - int r; - - assert(m); - assert(string); - - first = type == SERVER_FALLBACK ? m->fallback_servers : m->system_servers; - - for (;;) { - _cleanup_free_ char *word = NULL; - bool found = false; - ServerName *n; - - r = extract_first_word(&string, &word, NULL, 0); - if (r < 0) - return log_error_errno(r, "Failed to parse timesyncd server syntax \"%s\": %m", string); - if (r == 0) - break; - - /* Filter out duplicates */ - LIST_FOREACH(names, n, first) - if (streq_ptr(n->string, word)) { - found = true; - break; - } - - if (found) - continue; - - r = server_name_new(m, NULL, type, word); - if (r < 0) - return r; - } - - return 0; -} - -int config_parse_servers( - 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) { - - Manager *m = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) - manager_flush_server_names(m, ltype); - else { - r = manager_parse_server_string(m, ltype, rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse NTP server string '%s'. Ignoring.", rvalue); - return 0; - } - } - - return 0; -} - -int manager_parse_config_file(Manager *m) { - assert(m); - - return config_parse_many(PKGSYSCONFDIR "/timesyncd.conf", - CONF_PATHS_NULSTR("systemd/timesyncd.conf.d"), - "Time\0", - config_item_perf_lookup, timesyncd_gperf_lookup, - false, m); -} diff --git a/src/timesync/timesyncd-conf.h b/src/timesync/timesyncd-conf.h deleted file mode 100644 index cba0724b1b..0000000000 --- a/src/timesync/timesyncd-conf.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Kay Sievers, 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 "conf-parser.h" -#include "timesyncd-manager.h" - -const struct ConfigPerfItem* timesyncd_gperf_lookup(const char *key, unsigned length); - -int manager_parse_server_string(Manager *m, ServerType type, const char *string); - -int config_parse_servers(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); - -int manager_parse_config_file(Manager *m); diff --git a/src/timesync/timesyncd-gperf.gperf b/src/timesync/timesyncd-gperf.gperf deleted file mode 100644 index 29a2cfeef6..0000000000 --- a/src/timesync/timesyncd-gperf.gperf +++ /dev/null @@ -1,19 +0,0 @@ -%{ -#include -#include "conf-parser.h" -#include "timesyncd-conf.h" -%} -struct ConfigPerfItem; -%null_strings -%language=ANSI-C -%define slot-name section_and_lvalue -%define hash-function-name timesyncdd_gperf_hash -%define lookup-function-name timesyncd_gperf_lookup -%readonly-tables -%omit-struct-type -%struct-type -%includes -%% -Time.NTP, config_parse_servers, SERVER_SYSTEM, 0 -Time.Servers, config_parse_servers, SERVER_SYSTEM, 0 -Time.FallbackNTP, config_parse_servers, SERVER_FALLBACK, 0 diff --git a/src/timesync/timesyncd-manager.c b/src/timesync/timesyncd-manager.c deleted file mode 100644 index d5e16db3a0..0000000000 --- a/src/timesync/timesyncd-manager.c +++ /dev/null @@ -1,1156 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Kay Sievers, 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "fs-util.h" -#include "list.h" -#include "log.h" -#include "missing.h" -#include "network-util.h" -#include "ratelimit.h" -#include "socket-util.h" -#include "sparse-endian.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" -#include "timesyncd-conf.h" -#include "timesyncd-manager.h" -#include "util.h" - -#ifndef ADJ_SETOFFSET -#define ADJ_SETOFFSET 0x0100 /* add 'time' to current time */ -#endif - -/* expected accuracy of time synchronization; used to adjust the poll interval */ -#define NTP_ACCURACY_SEC 0.2 - -/* - * "A client MUST NOT under any conditions use a poll interval less - * than 15 seconds." - */ -#define NTP_POLL_INTERVAL_MIN_SEC 32 -#define NTP_POLL_INTERVAL_MAX_SEC 2048 - -/* - * Maximum delta in seconds which the system clock is gradually adjusted - * (slew) to approach the network time. Deltas larger that this are set by - * letting the system time jump. The kernel's limit for adjtime is 0.5s. - */ -#define NTP_MAX_ADJUST 0.4 - -/* NTP protocol, packet header */ -#define NTP_LEAP_PLUSSEC 1 -#define NTP_LEAP_MINUSSEC 2 -#define NTP_LEAP_NOTINSYNC 3 -#define NTP_MODE_CLIENT 3 -#define NTP_MODE_SERVER 4 -#define NTP_FIELD_LEAP(f) (((f) >> 6) & 3) -#define NTP_FIELD_VERSION(f) (((f) >> 3) & 7) -#define NTP_FIELD_MODE(f) ((f) & 7) -#define NTP_FIELD(l, v, m) (((l) << 6) | ((v) << 3) | (m)) - -/* Maximum acceptable root distance in seconds. */ -#define NTP_MAX_ROOT_DISTANCE 5.0 - -/* Maximum number of missed replies before selecting another source. */ -#define NTP_MAX_MISSED_REPLIES 2 - -/* - * "NTP timestamps are represented as a 64-bit unsigned fixed-point number, - * in seconds relative to 0h on 1 January 1900." - */ -#define OFFSET_1900_1970 UINT64_C(2208988800) - -#define RETRY_USEC (30*USEC_PER_SEC) -#define RATELIMIT_INTERVAL_USEC (10*USEC_PER_SEC) -#define RATELIMIT_BURST 10 - -#define TIMEOUT_USEC (10*USEC_PER_SEC) - -struct ntp_ts { - be32_t sec; - be32_t frac; -} _packed_; - -struct ntp_ts_short { - be16_t sec; - be16_t frac; -} _packed_; - -struct ntp_msg { - uint8_t field; - uint8_t stratum; - int8_t poll; - int8_t precision; - struct ntp_ts_short root_delay; - struct ntp_ts_short root_dispersion; - char refid[4]; - struct ntp_ts reference_time; - struct ntp_ts origin_time; - struct ntp_ts recv_time; - struct ntp_ts trans_time; -} _packed_; - -static int manager_arm_timer(Manager *m, usec_t next); -static int manager_clock_watch_setup(Manager *m); -static int manager_listen_setup(Manager *m); -static void manager_listen_stop(Manager *m); - -static double ntp_ts_short_to_d(const struct ntp_ts_short *ts) { - return be16toh(ts->sec) + (be16toh(ts->frac) / 65536.0); -} - -static double ntp_ts_to_d(const struct ntp_ts *ts) { - return be32toh(ts->sec) + ((double)be32toh(ts->frac) / UINT_MAX); -} - -static double ts_to_d(const struct timespec *ts) { - return ts->tv_sec + (1.0e-9 * ts->tv_nsec); -} - -static int manager_timeout(sd_event_source *source, usec_t usec, void *userdata) { - _cleanup_free_ char *pretty = NULL; - Manager *m = userdata; - - assert(m); - assert(m->current_server_name); - assert(m->current_server_address); - - server_address_pretty(m->current_server_address, &pretty); - log_info("Timed out waiting for reply from %s (%s).", strna(pretty), m->current_server_name->string); - - return manager_connect(m); -} - -static int manager_send_request(Manager *m) { - _cleanup_free_ char *pretty = NULL; - struct ntp_msg ntpmsg = { - /* - * "The client initializes the NTP message header, sends the request - * to the server, and strips the time of day from the Transmit - * Timestamp field of the reply. For this purpose, all the NTP - * header fields are set to 0, except the Mode, VN, and optional - * Transmit Timestamp fields." - */ - .field = NTP_FIELD(0, 4, NTP_MODE_CLIENT), - }; - ssize_t len; - int r; - - assert(m); - assert(m->current_server_name); - assert(m->current_server_address); - - m->event_timeout = sd_event_source_unref(m->event_timeout); - - r = manager_listen_setup(m); - if (r < 0) - return log_warning_errno(r, "Failed to setup connection socket: %m"); - - /* - * Set transmit timestamp, remember it; the server will send that back - * as the origin timestamp and we have an indication that this is the - * matching answer to our request. - * - * The actual value does not matter, We do not care about the correct - * NTP UINT_MAX fraction; we just pass the plain nanosecond value. - */ - assert_se(clock_gettime(clock_boottime_or_monotonic(), &m->trans_time_mon) >= 0); - assert_se(clock_gettime(CLOCK_REALTIME, &m->trans_time) >= 0); - ntpmsg.trans_time.sec = htobe32(m->trans_time.tv_sec + OFFSET_1900_1970); - ntpmsg.trans_time.frac = htobe32(m->trans_time.tv_nsec); - - server_address_pretty(m->current_server_address, &pretty); - - len = sendto(m->server_socket, &ntpmsg, sizeof(ntpmsg), MSG_DONTWAIT, &m->current_server_address->sockaddr.sa, m->current_server_address->socklen); - if (len == sizeof(ntpmsg)) { - m->pending = true; - log_debug("Sent NTP request to %s (%s).", strna(pretty), m->current_server_name->string); - } else { - log_debug_errno(errno, "Sending NTP request to %s (%s) failed: %m", strna(pretty), m->current_server_name->string); - return manager_connect(m); - } - - /* re-arm timer with increasing timeout, in case the packets never arrive back */ - if (m->retry_interval > 0) { - if (m->retry_interval < NTP_POLL_INTERVAL_MAX_SEC * USEC_PER_SEC) - m->retry_interval *= 2; - } else - m->retry_interval = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC; - - r = manager_arm_timer(m, m->retry_interval); - if (r < 0) - return log_error_errno(r, "Failed to rearm timer: %m"); - - m->missed_replies++; - if (m->missed_replies > NTP_MAX_MISSED_REPLIES) { - r = sd_event_add_time( - m->event, - &m->event_timeout, - clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()) + TIMEOUT_USEC, 0, - manager_timeout, m); - if (r < 0) - return log_error_errno(r, "Failed to arm timeout timer: %m"); - } - - return 0; -} - -static int manager_timer(sd_event_source *source, usec_t usec, void *userdata) { - Manager *m = userdata; - - assert(m); - - return manager_send_request(m); -} - -static int manager_arm_timer(Manager *m, usec_t next) { - int r; - - assert(m); - - if (next == 0) { - m->event_timer = sd_event_source_unref(m->event_timer); - return 0; - } - - if (m->event_timer) { - r = sd_event_source_set_time(m->event_timer, now(clock_boottime_or_monotonic()) + next); - if (r < 0) - return r; - - return sd_event_source_set_enabled(m->event_timer, SD_EVENT_ONESHOT); - } - - return sd_event_add_time( - m->event, - &m->event_timer, - clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()) + next, 0, - manager_timer, m); -} - -static int manager_clock_watch(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - - assert(m); - - /* rearm timer */ - manager_clock_watch_setup(m); - - /* skip our own jumps */ - if (m->jumped) { - m->jumped = false; - return 0; - } - - /* resync */ - log_debug("System time changed. Resyncing."); - m->poll_resync = true; - - return manager_send_request(m); -} - -/* wake up when the system time changes underneath us */ -static int manager_clock_watch_setup(Manager *m) { - - struct itimerspec its = { - .it_value.tv_sec = TIME_T_MAX - }; - - int r; - - assert(m); - - m->event_clock_watch = sd_event_source_unref(m->event_clock_watch); - safe_close(m->clock_watch_fd); - - m->clock_watch_fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC); - if (m->clock_watch_fd < 0) - return log_error_errno(errno, "Failed to create timerfd: %m"); - - if (timerfd_settime(m->clock_watch_fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0) - return log_error_errno(errno, "Failed to set up timerfd: %m"); - - r = sd_event_add_io(m->event, &m->event_clock_watch, m->clock_watch_fd, EPOLLIN, manager_clock_watch, m); - if (r < 0) - return log_error_errno(r, "Failed to create clock watch event source: %m"); - - return 0; -} - -static int manager_adjust_clock(Manager *m, double offset, int leap_sec) { - struct timex tmx = {}; - int r; - - assert(m); - - /* - * For small deltas, tell the kernel to gradually adjust the system - * clock to the NTP time, larger deltas are just directly set. - */ - if (fabs(offset) < NTP_MAX_ADJUST) { - tmx.modes = ADJ_STATUS | ADJ_NANO | ADJ_OFFSET | ADJ_TIMECONST | ADJ_MAXERROR | ADJ_ESTERROR; - tmx.status = STA_PLL; - tmx.offset = offset * NSEC_PER_SEC; - tmx.constant = log2i(m->poll_interval_usec / USEC_PER_SEC) - 4; - tmx.maxerror = 0; - tmx.esterror = 0; - log_debug(" adjust (slew): %+.3f sec", offset); - } else { - tmx.modes = ADJ_STATUS | ADJ_NANO | ADJ_SETOFFSET; - - /* ADJ_NANO uses nanoseconds in the microseconds field */ - tmx.time.tv_sec = (long)offset; - tmx.time.tv_usec = (offset - tmx.time.tv_sec) * NSEC_PER_SEC; - - /* the kernel expects -0.3s as {-1, 7000.000.000} */ - if (tmx.time.tv_usec < 0) { - tmx.time.tv_sec -= 1; - tmx.time.tv_usec += NSEC_PER_SEC; - } - - m->jumped = true; - log_debug(" adjust (jump): %+.3f sec", offset); - } - - /* - * An unset STA_UNSYNC will enable the kernel's 11-minute mode, - * which syncs the system time periodically to the RTC. - * - * In case the RTC runs in local time, never touch the RTC, - * we have no way to properly handle daylight saving changes and - * mobile devices moving between time zones. - */ - if (m->rtc_local_time) - tmx.status |= STA_UNSYNC; - - switch (leap_sec) { - case 1: - tmx.status |= STA_INS; - break; - case -1: - tmx.status |= STA_DEL; - break; - } - - r = clock_adjtime(CLOCK_REALTIME, &tmx); - if (r < 0) - return -errno; - - /* If touch fails, there isn't much we can do. Maybe it'll work next time. */ - (void) touch("/var/lib/systemd/clock"); - - m->drift_ppm = tmx.freq / 65536; - - log_debug(" status : %04i %s\n" - " time now : %li.%03llu\n" - " constant : %li\n" - " offset : %+.3f sec\n" - " freq offset : %+li (%i ppm)\n", - tmx.status, tmx.status & STA_UNSYNC ? "unsync" : "sync", - tmx.time.tv_sec, (unsigned long long) (tmx.time.tv_usec / NSEC_PER_MSEC), - tmx.constant, - (double)tmx.offset / NSEC_PER_SEC, - tmx.freq, m->drift_ppm); - - return 0; -} - -static bool manager_sample_spike_detection(Manager *m, double offset, double delay) { - unsigned int i, idx_cur, idx_new, idx_min; - double jitter; - double j; - - assert(m); - - m->packet_count++; - - /* ignore initial sample */ - if (m->packet_count == 1) - return false; - - /* store the current data in our samples array */ - idx_cur = m->samples_idx; - idx_new = (idx_cur + 1) % ELEMENTSOF(m->samples); - m->samples_idx = idx_new; - m->samples[idx_new].offset = offset; - m->samples[idx_new].delay = delay; - - /* calculate new jitter value from the RMS differences relative to the lowest delay sample */ - jitter = m->samples_jitter; - for (idx_min = idx_cur, i = 0; i < ELEMENTSOF(m->samples); i++) - if (m->samples[i].delay > 0 && m->samples[i].delay < m->samples[idx_min].delay) - idx_min = i; - - j = 0; - for (i = 0; i < ELEMENTSOF(m->samples); i++) - j += pow(m->samples[i].offset - m->samples[idx_min].offset, 2); - m->samples_jitter = sqrt(j / (ELEMENTSOF(m->samples) - 1)); - - /* ignore samples when resyncing */ - if (m->poll_resync) - return false; - - /* always accept offset if we are farther off than the round-trip delay */ - if (fabs(offset) > delay) - return false; - - /* we need a few samples before looking at them */ - if (m->packet_count < 4) - return false; - - /* do not accept anything worse than the maximum possible error of the best sample */ - if (fabs(offset) > m->samples[idx_min].delay) - return true; - - /* compare the difference between the current offset to the previous offset and jitter */ - return fabs(offset - m->samples[idx_cur].offset) > 3 * jitter; -} - -static void manager_adjust_poll(Manager *m, double offset, bool spike) { - assert(m); - - if (m->poll_resync) { - m->poll_interval_usec = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC; - m->poll_resync = false; - return; - } - - /* set to minimal poll interval */ - if (!spike && fabs(offset) > NTP_ACCURACY_SEC) { - m->poll_interval_usec = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC; - return; - } - - /* increase polling interval */ - if (fabs(offset) < NTP_ACCURACY_SEC * 0.25) { - if (m->poll_interval_usec < NTP_POLL_INTERVAL_MAX_SEC * USEC_PER_SEC) - m->poll_interval_usec *= 2; - return; - } - - /* decrease polling interval */ - if (spike || fabs(offset) > NTP_ACCURACY_SEC * 0.75) { - if (m->poll_interval_usec > NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC) - m->poll_interval_usec /= 2; - return; - } -} - -static int manager_receive_response(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - struct ntp_msg ntpmsg; - - struct iovec iov = { - .iov_base = &ntpmsg, - .iov_len = sizeof(ntpmsg), - }; - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(struct timeval))]; - } control; - union sockaddr_union server_addr; - struct msghdr msghdr = { - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = &control, - .msg_controllen = sizeof(control), - .msg_name = &server_addr, - .msg_namelen = sizeof(server_addr), - }; - struct cmsghdr *cmsg; - struct timespec *recv_time; - ssize_t len; - double origin, receive, trans, dest; - double delay, offset; - double root_distance; - bool spike; - int leap_sec; - int r; - - assert(source); - assert(m); - - if (revents & (EPOLLHUP|EPOLLERR)) { - log_warning("Server connection returned error."); - return manager_connect(m); - } - - len = recvmsg(fd, &msghdr, MSG_DONTWAIT); - if (len < 0) { - if (errno == EAGAIN) - return 0; - - log_warning("Error receiving message. Disconnecting."); - return manager_connect(m); - } - - /* Too short or too long packet? */ - if (iov.iov_len < sizeof(struct ntp_msg) || (msghdr.msg_flags & MSG_TRUNC)) { - log_warning("Invalid response from server. Disconnecting."); - return manager_connect(m); - } - - if (!m->current_server_name || - !m->current_server_address || - !sockaddr_equal(&server_addr, &m->current_server_address->sockaddr)) { - log_debug("Response from unknown server."); - return 0; - } - - recv_time = NULL; - CMSG_FOREACH(cmsg, &msghdr) { - if (cmsg->cmsg_level != SOL_SOCKET) - continue; - - switch (cmsg->cmsg_type) { - case SCM_TIMESTAMPNS: - recv_time = (struct timespec *) CMSG_DATA(cmsg); - break; - } - } - if (!recv_time) { - log_error("Invalid packet timestamp."); - return -EINVAL; - } - - if (!m->pending) { - log_debug("Unexpected reply. Ignoring."); - return 0; - } - - m->missed_replies = 0; - - /* check our "time cookie" (we just stored nanoseconds in the fraction field) */ - if (be32toh(ntpmsg.origin_time.sec) != m->trans_time.tv_sec + OFFSET_1900_1970 || - be32toh(ntpmsg.origin_time.frac) != m->trans_time.tv_nsec) { - log_debug("Invalid reply; not our transmit time. Ignoring."); - return 0; - } - - m->event_timeout = sd_event_source_unref(m->event_timeout); - - if (be32toh(ntpmsg.recv_time.sec) < TIME_EPOCH + OFFSET_1900_1970 || - be32toh(ntpmsg.trans_time.sec) < TIME_EPOCH + OFFSET_1900_1970) { - log_debug("Invalid reply, returned times before epoch. Ignoring."); - return manager_connect(m); - } - - if (NTP_FIELD_LEAP(ntpmsg.field) == NTP_LEAP_NOTINSYNC || - ntpmsg.stratum == 0 || ntpmsg.stratum >= 16) { - log_debug("Server is not synchronized. Disconnecting."); - return manager_connect(m); - } - - if (!IN_SET(NTP_FIELD_VERSION(ntpmsg.field), 3, 4)) { - log_debug("Response NTPv%d. Disconnecting.", NTP_FIELD_VERSION(ntpmsg.field)); - return manager_connect(m); - } - - if (NTP_FIELD_MODE(ntpmsg.field) != NTP_MODE_SERVER) { - log_debug("Unsupported mode %d. Disconnecting.", NTP_FIELD_MODE(ntpmsg.field)); - return manager_connect(m); - } - - root_distance = ntp_ts_short_to_d(&ntpmsg.root_delay) / 2 + ntp_ts_short_to_d(&ntpmsg.root_dispersion); - if (root_distance > NTP_MAX_ROOT_DISTANCE) { - log_debug("Server has too large root distance. Disconnecting."); - return manager_connect(m); - } - - /* valid packet */ - m->pending = false; - m->retry_interval = 0; - - /* Stop listening */ - manager_listen_stop(m); - - /* announce leap seconds */ - if (NTP_FIELD_LEAP(ntpmsg.field) & NTP_LEAP_PLUSSEC) - leap_sec = 1; - else if (NTP_FIELD_LEAP(ntpmsg.field) & NTP_LEAP_MINUSSEC) - leap_sec = -1; - else - leap_sec = 0; - - /* - * "Timestamp Name ID When Generated - * ------------------------------------------------------------ - * Originate Timestamp T1 time request sent by client - * Receive Timestamp T2 time request received by server - * Transmit Timestamp T3 time reply sent by server - * Destination Timestamp T4 time reply received by client - * - * The round-trip delay, d, and system clock offset, t, are defined as: - * d = (T4 - T1) - (T3 - T2) t = ((T2 - T1) + (T3 - T4)) / 2" - */ - origin = ts_to_d(&m->trans_time) + OFFSET_1900_1970; - receive = ntp_ts_to_d(&ntpmsg.recv_time); - trans = ntp_ts_to_d(&ntpmsg.trans_time); - dest = ts_to_d(recv_time) + OFFSET_1900_1970; - - offset = ((receive - origin) + (trans - dest)) / 2; - delay = (dest - origin) - (trans - receive); - - spike = manager_sample_spike_detection(m, offset, delay); - - manager_adjust_poll(m, offset, spike); - - log_debug("NTP response:\n" - " leap : %u\n" - " version : %u\n" - " mode : %u\n" - " stratum : %u\n" - " precision : %.6f sec (%d)\n" - " root distance: %.6f sec\n" - " reference : %.4s\n" - " origin : %.3f\n" - " receive : %.3f\n" - " transmit : %.3f\n" - " dest : %.3f\n" - " offset : %+.3f sec\n" - " delay : %+.3f sec\n" - " packet count : %"PRIu64"\n" - " jitter : %.3f%s\n" - " poll interval: " USEC_FMT "\n", - NTP_FIELD_LEAP(ntpmsg.field), - NTP_FIELD_VERSION(ntpmsg.field), - NTP_FIELD_MODE(ntpmsg.field), - ntpmsg.stratum, - exp2(ntpmsg.precision), ntpmsg.precision, - root_distance, - ntpmsg.stratum == 1 ? ntpmsg.refid : "n/a", - origin - OFFSET_1900_1970, - receive - OFFSET_1900_1970, - trans - OFFSET_1900_1970, - dest - OFFSET_1900_1970, - offset, delay, - m->packet_count, - m->samples_jitter, spike ? " spike" : "", - m->poll_interval_usec / USEC_PER_SEC); - - if (!spike) { - m->sync = true; - r = manager_adjust_clock(m, offset, leap_sec); - if (r < 0) - log_error_errno(r, "Failed to call clock_adjtime(): %m"); - } - - log_debug("interval/delta/delay/jitter/drift " USEC_FMT "s/%+.3fs/%.3fs/%.3fs/%+ippm%s", - m->poll_interval_usec / USEC_PER_SEC, offset, delay, m->samples_jitter, m->drift_ppm, - spike ? " (ignored)" : ""); - - if (!m->good) { - _cleanup_free_ char *pretty = NULL; - - m->good = true; - - server_address_pretty(m->current_server_address, &pretty); - log_info("Synchronized to time server %s (%s).", strna(pretty), m->current_server_name->string); - sd_notifyf(false, "STATUS=Synchronized to time server %s (%s).", strna(pretty), m->current_server_name->string); - } - - r = manager_arm_timer(m, m->poll_interval_usec); - if (r < 0) - return log_error_errno(r, "Failed to rearm timer: %m"); - - return 0; -} - -static int manager_listen_setup(Manager *m) { - union sockaddr_union addr = {}; - static const int tos = IPTOS_LOWDELAY; - static const int on = 1; - int r; - - assert(m); - - if (m->server_socket >= 0) - return 0; - - assert(!m->event_receive); - assert(m->current_server_address); - - addr.sa.sa_family = m->current_server_address->sockaddr.sa.sa_family; - - m->server_socket = socket(addr.sa.sa_family, SOCK_DGRAM | SOCK_CLOEXEC, 0); - if (m->server_socket < 0) - return -errno; - - r = bind(m->server_socket, &addr.sa, m->current_server_address->socklen); - if (r < 0) - return -errno; - - r = setsockopt(m->server_socket, SOL_SOCKET, SO_TIMESTAMPNS, &on, sizeof(on)); - if (r < 0) - return -errno; - - (void) setsockopt(m->server_socket, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)); - - return sd_event_add_io(m->event, &m->event_receive, m->server_socket, EPOLLIN, manager_receive_response, m); -} - -static void manager_listen_stop(Manager *m) { - assert(m); - - m->event_receive = sd_event_source_unref(m->event_receive); - m->server_socket = safe_close(m->server_socket); -} - -static int manager_begin(Manager *m) { - _cleanup_free_ char *pretty = NULL; - int r; - - assert(m); - assert_return(m->current_server_name, -EHOSTUNREACH); - assert_return(m->current_server_address, -EHOSTUNREACH); - - m->good = false; - m->missed_replies = NTP_MAX_MISSED_REPLIES; - if (m->poll_interval_usec == 0) - m->poll_interval_usec = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC; - - server_address_pretty(m->current_server_address, &pretty); - log_debug("Connecting to time server %s (%s).", strna(pretty), m->current_server_name->string); - sd_notifyf(false, "STATUS=Connecting to time server %s (%s).", strna(pretty), m->current_server_name->string); - - r = manager_clock_watch_setup(m); - if (r < 0) - return r; - - return manager_send_request(m); -} - -void manager_set_server_name(Manager *m, ServerName *n) { - assert(m); - - if (m->current_server_name == n) - return; - - m->current_server_name = n; - m->current_server_address = NULL; - - manager_disconnect(m); - - if (n) - log_debug("Selected server %s.", n->string); -} - -void manager_set_server_address(Manager *m, ServerAddress *a) { - assert(m); - - if (m->current_server_address == a) - return; - - m->current_server_address = a; - /* If a is NULL, we are just clearing the address, without - * changing the name. Keep the existing name in that case. */ - if (a) - m->current_server_name = a->name; - - manager_disconnect(m); - - if (a) { - _cleanup_free_ char *pretty = NULL; - server_address_pretty(a, &pretty); - log_debug("Selected address %s of server %s.", strna(pretty), a->name->string); - } -} - -static int manager_resolve_handler(sd_resolve_query *q, int ret, const struct addrinfo *ai, void *userdata) { - Manager *m = userdata; - int r; - - assert(q); - assert(m); - assert(m->current_server_name); - - m->resolve_query = sd_resolve_query_unref(m->resolve_query); - - if (ret != 0) { - log_debug("Failed to resolve %s: %s", m->current_server_name->string, gai_strerror(ret)); - - /* Try next host */ - return manager_connect(m); - } - - for (; ai; ai = ai->ai_next) { - _cleanup_free_ char *pretty = NULL; - ServerAddress *a; - - assert(ai->ai_addr); - assert(ai->ai_addrlen >= offsetof(struct sockaddr, sa_data)); - - if (!IN_SET(ai->ai_addr->sa_family, AF_INET, AF_INET6)) { - log_warning("Unsuitable address protocol for %s", m->current_server_name->string); - continue; - } - - r = server_address_new(m->current_server_name, &a, (const union sockaddr_union*) ai->ai_addr, ai->ai_addrlen); - if (r < 0) - return log_error_errno(r, "Failed to add server address: %m"); - - server_address_pretty(a, &pretty); - log_debug("Resolved address %s for %s.", pretty, m->current_server_name->string); - } - - if (!m->current_server_name->addresses) { - log_error("Failed to find suitable address for host %s.", m->current_server_name->string); - - /* Try next host */ - return manager_connect(m); - } - - manager_set_server_address(m, m->current_server_name->addresses); - - return manager_begin(m); -} - -static int manager_retry_connect(sd_event_source *source, usec_t usec, void *userdata) { - Manager *m = userdata; - - assert(m); - - return manager_connect(m); -} - -int manager_connect(Manager *m) { - int r; - - assert(m); - - manager_disconnect(m); - - m->event_retry = sd_event_source_unref(m->event_retry); - if (!ratelimit_test(&m->ratelimit)) { - log_debug("Slowing down attempts to contact servers."); - - r = sd_event_add_time(m->event, &m->event_retry, clock_boottime_or_monotonic(), now(clock_boottime_or_monotonic()) + RETRY_USEC, 0, manager_retry_connect, m); - if (r < 0) - return log_error_errno(r, "Failed to create retry timer: %m"); - - return 0; - } - - /* If we already are operating on some address, switch to the - * next one. */ - if (m->current_server_address && m->current_server_address->addresses_next) - manager_set_server_address(m, m->current_server_address->addresses_next); - else { - struct addrinfo hints = { - .ai_flags = AI_NUMERICSERV|AI_ADDRCONFIG, - .ai_socktype = SOCK_DGRAM, - }; - - /* Hmm, we are through all addresses, let's look for the next host instead */ - if (m->current_server_name && m->current_server_name->names_next) - manager_set_server_name(m, m->current_server_name->names_next); - else { - ServerName *f; - bool restart = true; - - /* Our current server name list is exhausted, - * let's find the next one to iterate. First - * we try the system list, then the link list. - * After having processed the link list we - * jump back to the system list. However, if - * both lists are empty, we change to the - * fallback list. */ - if (!m->current_server_name || m->current_server_name->type == SERVER_LINK) { - f = m->system_servers; - if (!f) - f = m->link_servers; - } else { - f = m->link_servers; - if (!f) - f = m->system_servers; - else - restart = false; - } - - if (!f) - f = m->fallback_servers; - - if (!f) { - manager_set_server_name(m, NULL); - log_debug("No server found."); - return 0; - } - - if (restart && !m->exhausted_servers && m->poll_interval_usec) { - log_debug("Waiting after exhausting servers."); - r = sd_event_add_time(m->event, &m->event_retry, clock_boottime_or_monotonic(), now(clock_boottime_or_monotonic()) + m->poll_interval_usec, 0, manager_retry_connect, m); - if (r < 0) - return log_error_errno(r, "Failed to create retry timer: %m"); - - m->exhausted_servers = true; - - /* Increase the polling interval */ - if (m->poll_interval_usec < NTP_POLL_INTERVAL_MAX_SEC * USEC_PER_SEC) - m->poll_interval_usec *= 2; - - return 0; - } - - m->exhausted_servers = false; - - manager_set_server_name(m, f); - } - - /* Tell the resolver to reread /etc/resolv.conf, in - * case it changed. */ - res_init(); - - /* Flush out any previously resolved addresses */ - server_name_flush_addresses(m->current_server_name); - - log_debug("Resolving %s...", m->current_server_name->string); - - r = sd_resolve_getaddrinfo(m->resolve, &m->resolve_query, m->current_server_name->string, "123", &hints, manager_resolve_handler, m); - if (r < 0) - return log_error_errno(r, "Failed to create resolver: %m"); - - return 1; - } - - r = manager_begin(m); - if (r < 0) - return r; - - return 1; -} - -void manager_disconnect(Manager *m) { - assert(m); - - m->resolve_query = sd_resolve_query_unref(m->resolve_query); - - m->event_timer = sd_event_source_unref(m->event_timer); - - manager_listen_stop(m); - - m->event_clock_watch = sd_event_source_unref(m->event_clock_watch); - m->clock_watch_fd = safe_close(m->clock_watch_fd); - - m->event_timeout = sd_event_source_unref(m->event_timeout); - - sd_notifyf(false, "STATUS=Idle."); -} - -void manager_flush_server_names(Manager *m, ServerType t) { - assert(m); - - if (t == SERVER_SYSTEM) - while (m->system_servers) - server_name_free(m->system_servers); - - if (t == SERVER_LINK) - while (m->link_servers) - server_name_free(m->link_servers); - - if (t == SERVER_FALLBACK) - while (m->fallback_servers) - server_name_free(m->fallback_servers); -} - -void manager_free(Manager *m) { - if (!m) - return; - - manager_disconnect(m); - manager_flush_server_names(m, SERVER_SYSTEM); - manager_flush_server_names(m, SERVER_LINK); - manager_flush_server_names(m, SERVER_FALLBACK); - - sd_event_source_unref(m->event_retry); - - sd_event_source_unref(m->network_event_source); - sd_network_monitor_unref(m->network_monitor); - - sd_resolve_unref(m->resolve); - sd_event_unref(m->event); - - free(m); -} - -static int manager_network_read_link_servers(Manager *m) { - _cleanup_strv_free_ char **ntp = NULL; - ServerName *n, *nx; - char **i; - int r; - - assert(m); - - r = sd_network_get_ntp(&ntp); - if (r < 0) - goto clear; - - LIST_FOREACH(names, n, m->link_servers) - n->marked = true; - - STRV_FOREACH(i, ntp) { - bool found = false; - - LIST_FOREACH(names, n, m->link_servers) - if (streq(n->string, *i)) { - n->marked = false; - found = true; - break; - } - - if (!found) { - r = server_name_new(m, NULL, SERVER_LINK, *i); - if (r < 0) - goto clear; - } - } - - LIST_FOREACH_SAFE(names, n, nx, m->link_servers) - if (n->marked) - server_name_free(n); - - return 0; - -clear: - manager_flush_server_names(m, SERVER_LINK); - return r; -} - -static int manager_network_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - bool connected, online; - int r; - - assert(m); - - sd_network_monitor_flush(m->network_monitor); - - manager_network_read_link_servers(m); - - /* check if the machine is online */ - online = network_is_online(); - - /* check if the client is currently connected */ - connected = m->server_socket >= 0 || m->resolve_query || m->exhausted_servers; - - if (connected && !online) { - log_info("No network connectivity, watching for changes."); - manager_disconnect(m); - - } else if (!connected && online) { - log_info("Network configuration changed, trying to establish connection."); - - if (m->current_server_address) - r = manager_begin(m); - else - r = manager_connect(m); - if (r < 0) - return r; - } - - return 0; -} - -static int manager_network_monitor_listen(Manager *m) { - int r, fd, events; - - assert(m); - - r = sd_network_monitor_new(&m->network_monitor, NULL); - if (r < 0) - return r; - - fd = sd_network_monitor_get_fd(m->network_monitor); - if (fd < 0) - return fd; - - events = sd_network_monitor_get_events(m->network_monitor); - if (events < 0) - return events; - - r = sd_event_add_io(m->event, &m->network_event_source, fd, events, manager_network_event_handler, m); - if (r < 0) - return r; - - return 0; -} - -int manager_new(Manager **ret) { - _cleanup_(manager_freep) Manager *m = NULL; - int r; - - assert(ret); - - m = new0(Manager, 1); - if (!m) - return -ENOMEM; - - m->server_socket = m->clock_watch_fd = -1; - - RATELIMIT_INIT(m->ratelimit, RATELIMIT_INTERVAL_USEC, RATELIMIT_BURST); - - r = manager_parse_server_string(m, SERVER_FALLBACK, NTP_SERVERS); - if (r < 0) - return r; - - r = sd_event_default(&m->event); - if (r < 0) - return r; - - sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); - sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); - - sd_event_set_watchdog(m->event, true); - - r = sd_resolve_default(&m->resolve); - if (r < 0) - return r; - - r = sd_resolve_attach_event(m->resolve, m->event, 0); - if (r < 0) - return r; - - r = manager_network_monitor_listen(m); - if (r < 0) - return r; - - manager_network_read_link_servers(m); - - *ret = m; - m = NULL; - - return 0; -} diff --git a/src/timesync/timesyncd-manager.h b/src/timesync/timesyncd-manager.h deleted file mode 100644 index efe3e60d3e..0000000000 --- a/src/timesync/timesyncd-manager.h +++ /dev/null @@ -1,104 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Kay Sievers, 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 "sd-event.h" -#include "sd-network.h" -#include "sd-resolve.h" - -#include "list.h" -#include "ratelimit.h" - -typedef struct Manager Manager; - -#include "timesyncd-server.h" - -struct Manager { - sd_event *event; - sd_resolve *resolve; - - LIST_HEAD(ServerName, system_servers); - LIST_HEAD(ServerName, link_servers); - LIST_HEAD(ServerName, fallback_servers); - - RateLimit ratelimit; - bool exhausted_servers; - - /* network */ - sd_event_source *network_event_source; - sd_network_monitor *network_monitor; - - /* peer */ - sd_resolve_query *resolve_query; - sd_event_source *event_receive; - ServerName *current_server_name; - ServerAddress *current_server_address; - int server_socket; - int missed_replies; - uint64_t packet_count; - sd_event_source *event_timeout; - bool good; - - /* last sent packet */ - struct timespec trans_time_mon; - struct timespec trans_time; - usec_t retry_interval; - bool pending; - - /* poll timer */ - sd_event_source *event_timer; - usec_t poll_interval_usec; - bool poll_resync; - - /* history data */ - struct { - double offset; - double delay; - } samples[8]; - unsigned int samples_idx; - double samples_jitter; - - /* last change */ - bool jumped; - bool sync; - int drift_ppm; - - /* watch for time changes */ - sd_event_source *event_clock_watch; - int clock_watch_fd; - - /* Retry connections */ - sd_event_source *event_retry; - - /* RTC runs in local time, leave it alone */ - bool rtc_local_time; -}; - -int manager_new(Manager **ret); -void manager_free(Manager *m); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); - -void manager_set_server_name(Manager *m, ServerName *n); -void manager_set_server_address(Manager *m, ServerAddress *a); -void manager_flush_server_names(Manager *m, ServerType t); - -int manager_connect(Manager *m); -void manager_disconnect(Manager *m); diff --git a/src/timesync/timesyncd-server.c b/src/timesync/timesyncd-server.c deleted file mode 100644 index 6bda86fe6e..0000000000 --- a/src/timesync/timesyncd-server.c +++ /dev/null @@ -1,150 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Kay Sievers, 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 "alloc-util.h" -#include "timesyncd-server.h" - -int server_address_new( - ServerName *n, - ServerAddress **ret, - const union sockaddr_union *sockaddr, - socklen_t socklen) { - - ServerAddress *a, *tail; - - assert(n); - assert(sockaddr); - assert(socklen >= offsetof(struct sockaddr, sa_data)); - assert(socklen <= sizeof(union sockaddr_union)); - - a = new0(ServerAddress, 1); - if (!a) - return -ENOMEM; - - memcpy(&a->sockaddr, sockaddr, socklen); - a->socklen = socklen; - - LIST_FIND_TAIL(addresses, n->addresses, tail); - LIST_INSERT_AFTER(addresses, n->addresses, tail, a); - a->name = n; - - if (ret) - *ret = a; - - return 0; -} - -ServerAddress* server_address_free(ServerAddress *a) { - if (!a) - return NULL; - - if (a->name) { - LIST_REMOVE(addresses, a->name->addresses, a); - - if (a->name->manager && a->name->manager->current_server_address == a) - manager_set_server_address(a->name->manager, NULL); - } - - free(a); - return NULL; -} - -int server_name_new( - Manager *m, - ServerName **ret, - ServerType type, - const char *string) { - - ServerName *n, *tail; - - assert(m); - assert(string); - - n = new0(ServerName, 1); - if (!n) - return -ENOMEM; - - n->type = type; - n->string = strdup(string); - if (!n->string) { - free(n); - return -ENOMEM; - } - - if (type == SERVER_SYSTEM) { - LIST_FIND_TAIL(names, m->system_servers, tail); - LIST_INSERT_AFTER(names, m->system_servers, tail, n); - } else if (type == SERVER_LINK) { - LIST_FIND_TAIL(names, m->link_servers, tail); - LIST_INSERT_AFTER(names, m->link_servers, tail, n); - } else if (type == SERVER_FALLBACK) { - LIST_FIND_TAIL(names, m->fallback_servers, tail); - LIST_INSERT_AFTER(names, m->fallback_servers, tail, n); - } else - assert_not_reached("Unknown server type"); - - n->manager = m; - - if (type != SERVER_FALLBACK && - m->current_server_name && - m->current_server_name->type == SERVER_FALLBACK) - manager_set_server_name(m, NULL); - - log_debug("Added new server %s.", string); - - if (ret) - *ret = n; - - return 0; -} - -ServerName *server_name_free(ServerName *n) { - if (!n) - return NULL; - - server_name_flush_addresses(n); - - if (n->manager) { - if (n->type == SERVER_SYSTEM) - LIST_REMOVE(names, n->manager->system_servers, n); - else if (n->type == SERVER_LINK) - LIST_REMOVE(names, n->manager->link_servers, n); - else if (n->type == SERVER_FALLBACK) - LIST_REMOVE(names, n->manager->fallback_servers, n); - else - assert_not_reached("Unknown server type"); - - if (n->manager->current_server_name == n) - manager_set_server_name(n->manager, NULL); - } - - log_debug("Removed server %s.", n->string); - - free(n->string); - free(n); - - return NULL; -} - -void server_name_flush_addresses(ServerName *n) { - assert(n); - - while (n->addresses) - server_address_free(n->addresses); -} diff --git a/src/timesync/timesyncd-server.h b/src/timesync/timesyncd-server.h deleted file mode 100644 index 8a19e41d67..0000000000 --- a/src/timesync/timesyncd-server.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Kay Sievers, 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 "list.h" -#include "socket-util.h" - -typedef struct ServerAddress ServerAddress; -typedef struct ServerName ServerName; - -typedef enum ServerType { - SERVER_SYSTEM, - SERVER_FALLBACK, - SERVER_LINK, -} ServerType; - -#include "timesyncd-manager.h" - -struct ServerAddress { - ServerName *name; - - union sockaddr_union sockaddr; - socklen_t socklen; - - LIST_FIELDS(ServerAddress, addresses); -}; - -struct ServerName { - Manager *manager; - - ServerType type; - char *string; - - bool marked:1; - - LIST_HEAD(ServerAddress, addresses); - LIST_FIELDS(ServerName, names); -}; - -int server_address_new(ServerName *n, ServerAddress **ret, const union sockaddr_union *sockaddr, socklen_t socklen); -ServerAddress* server_address_free(ServerAddress *a); -static inline int server_address_pretty(ServerAddress *a, char **pretty) { - return sockaddr_pretty(&a->sockaddr.sa, a->socklen, true, true, pretty); -} - -int server_name_new(Manager *m, ServerName **ret, ServerType type,const char *string); -ServerName *server_name_free(ServerName *n); -void server_name_flush_addresses(ServerName *n); diff --git a/src/timesync/timesyncd.c b/src/timesync/timesyncd.c deleted file mode 100644 index b67d672a6a..0000000000 --- a/src/timesync/timesyncd.c +++ /dev/null @@ -1,164 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Kay Sievers, 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 "sd-daemon.h" -#include "sd-event.h" - -#include "capability-util.h" -#include "clock-util.h" -#include "fd-util.h" -#include "fs-util.h" -#include "network-util.h" -#include "signal-util.h" -#include "timesyncd-conf.h" -#include "timesyncd-manager.h" -#include "user-util.h" - -static int load_clock_timestamp(uid_t uid, gid_t gid) { - _cleanup_close_ int fd = -1; - usec_t min = TIME_EPOCH * USEC_PER_SEC; - usec_t ct; - int r; - - /* Let's try to make sure that the clock is always - * monotonically increasing, by saving the clock whenever we - * have a new NTP time, or when we shut down, and restoring it - * when we start again. This is particularly helpful on - * systems lacking a battery backed RTC. We also will adjust - * the time to at least the build time of systemd. */ - - fd = open("/var/lib/systemd/clock", O_RDWR|O_CLOEXEC, 0644); - if (fd >= 0) { - struct stat st; - usec_t stamp; - - /* check if the recorded time is later than the compiled-in one */ - r = fstat(fd, &st); - if (r >= 0) { - stamp = timespec_load(&st.st_mtim); - if (stamp > min) - min = stamp; - } - - /* Try to fix the access mode, so that we can still - touch the file after dropping priviliges */ - (void) fchmod(fd, 0644); - (void) fchown(fd, uid, gid); - - } else - /* create stamp file with the compiled-in date */ - (void) touch_file("/var/lib/systemd/clock", true, min, uid, gid, 0644); - - ct = now(CLOCK_REALTIME); - if (ct < min) { - struct timespec ts; - char date[FORMAT_TIMESTAMP_MAX]; - - log_info("System clock time unset or jumped backwards, restoring from recorded timestamp: %s", - format_timestamp(date, sizeof(date), min)); - - if (clock_settime(CLOCK_REALTIME, timespec_store(&ts, min)) < 0) - log_error_errno(errno, "Failed to restore system clock: %m"); - } - - return 0; -} - -int main(int argc, char *argv[]) { - _cleanup_(manager_freep) Manager *m = NULL; - const char *user = "systemd-timesync"; - uid_t uid; - gid_t gid; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_set_facility(LOG_CRON); - log_parse_environment(); - log_open(); - - umask(0022); - - if (argc != 1) { - log_error("This program does not take arguments."); - r = -EINVAL; - goto finish; - } - - r = get_user_creds(&user, &uid, &gid, NULL, NULL); - if (r < 0) { - log_error_errno(r, "Cannot resolve user name %s: %m", user); - goto finish; - } - - r = load_clock_timestamp(uid, gid); - if (r < 0) - goto finish; - - r = drop_privileges(uid, gid, (1ULL << CAP_SYS_TIME)); - if (r < 0) - goto finish; - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - - r = manager_new(&m); - if (r < 0) { - log_error_errno(r, "Failed to allocate manager: %m"); - goto finish; - } - - if (clock_is_localtime(NULL) > 0) { - log_info("The system is configured to read the RTC time in the local time zone. " - "This mode can not be fully supported. All system time to RTC updates are disabled."); - m->rtc_local_time = true; - } - - r = manager_parse_config_file(m); - if (r < 0) - log_warning_errno(r, "Failed to parse configuration file: %m"); - - log_debug("systemd-timesyncd running as pid " PID_FMT, getpid()); - sd_notify(false, - "READY=1\n" - "STATUS=Daemon is running"); - - if (network_is_online()) { - r = manager_connect(m); - if (r < 0) - goto finish; - } - - r = sd_event_loop(m->event); - if (r < 0) { - log_error_errno(r, "Failed to run event loop: %m"); - goto finish; - } - - /* if we got an authoritative time, store it in the file system */ - if (m->sync) - (void) touch("/var/lib/systemd/clock"); - - sd_event_get_exit_code(m->event, &r); - -finish: - sd_notify(false, - "STOPPING=1\n" - "STATUS=Shutting down..."); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/timesync/timesyncd.conf.in b/src/timesync/timesyncd.conf.in deleted file mode 100644 index b6a2ada273..0000000000 --- a/src/timesync/timesyncd.conf.in +++ /dev/null @@ -1,16 +0,0 @@ -# This file is part of systemd. -# -# 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. -# -# Entries in this file show the compile time defaults. -# You can change settings by editing this file. -# Defaults can be restored by simply deleting this file. -# -# See timesyncd.conf(5) for details. - -[Time] -#NTP= -#FallbackNTP=@NTP_SERVERS@ diff --git a/src/tmpfiles/Makefile b/src/tmpfiles/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/tmpfiles/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c deleted file mode 100644 index 2053d35a67..0000000000 --- a/src/tmpfiles/tmpfiles.c +++ /dev/null @@ -1,2343 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering, Kay Sievers - Copyright 2015 Zbigniew Jędrzejewski-Szmek - - 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "acl-util.h" -#include "alloc-util.h" -#include "btrfs-util.h" -#include "capability-util.h" -#include "chattr-util.h" -#include "conf-files.h" -#include "copy.h" -#include "def.h" -#include "escape.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "glob-util.h" -#include "io-util.h" -#include "label.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "mount-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "rm-rf.h" -#include "selinux-util.h" -#include "set.h" -#include "specifier.h" -#include "stat-util.h" -#include "stdio-util.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "umask-util.h" -#include "user-util.h" -#include "util.h" - -/* This reads all files listed in /etc/tmpfiles.d/?*.conf and creates - * them in the file system. This is intended to be used to create - * properly owned directories beneath /tmp, /var/tmp, /run, which are - * volatile and hence need to be recreated on bootup. */ - -typedef enum ItemType { - /* These ones take file names */ - CREATE_FILE = 'f', - TRUNCATE_FILE = 'F', - CREATE_DIRECTORY = 'd', - TRUNCATE_DIRECTORY = 'D', - CREATE_SUBVOLUME = 'v', - CREATE_SUBVOLUME_INHERIT_QUOTA = 'q', - CREATE_SUBVOLUME_NEW_QUOTA = 'Q', - CREATE_FIFO = 'p', - CREATE_SYMLINK = 'L', - CREATE_CHAR_DEVICE = 'c', - CREATE_BLOCK_DEVICE = 'b', - COPY_FILES = 'C', - - /* These ones take globs */ - WRITE_FILE = 'w', - EMPTY_DIRECTORY = 'e', - SET_XATTR = 't', - RECURSIVE_SET_XATTR = 'T', - SET_ACL = 'a', - RECURSIVE_SET_ACL = 'A', - SET_ATTRIBUTE = 'h', - RECURSIVE_SET_ATTRIBUTE = 'H', - IGNORE_PATH = 'x', - IGNORE_DIRECTORY_PATH = 'X', - REMOVE_PATH = 'r', - RECURSIVE_REMOVE_PATH = 'R', - RELABEL_PATH = 'z', - RECURSIVE_RELABEL_PATH = 'Z', - ADJUST_MODE = 'm', /* legacy, 'z' is identical to this */ -} ItemType; - -typedef struct Item { - ItemType type; - - char *path; - char *argument; - char **xattrs; -#ifdef HAVE_ACL - acl_t acl_access; - acl_t acl_default; -#endif - uid_t uid; - gid_t gid; - mode_t mode; - usec_t age; - - dev_t major_minor; - unsigned attribute_value; - unsigned attribute_mask; - - bool uid_set:1; - bool gid_set:1; - bool mode_set:1; - bool age_set:1; - bool mask_perms:1; - bool attribute_set:1; - - bool keep_first_level:1; - - bool force:1; - - bool done:1; -} Item; - -typedef struct ItemArray { - Item *items; - size_t count; - size_t size; -} ItemArray; - -static bool arg_create = false; -static bool arg_clean = false; -static bool arg_remove = false; -static bool arg_boot = false; - -static char **arg_include_prefixes = NULL; -static char **arg_exclude_prefixes = NULL; -static char *arg_root = NULL; - -static const char conf_file_dirs[] = CONF_PATHS_NULSTR("tmpfiles.d"); - -#define MAX_DEPTH 256 - -static OrderedHashmap *items = NULL, *globs = NULL; -static Set *unix_sockets = NULL; - -static const Specifier specifier_table[] = { - { 'm', specifier_machine_id, NULL }, - { 'b', specifier_boot_id, NULL }, - { 'H', specifier_host_name, NULL }, - { 'v', specifier_kernel_release, NULL }, - {} -}; - -static bool needs_glob(ItemType t) { - return IN_SET(t, - WRITE_FILE, - IGNORE_PATH, - IGNORE_DIRECTORY_PATH, - REMOVE_PATH, - RECURSIVE_REMOVE_PATH, - EMPTY_DIRECTORY, - ADJUST_MODE, - RELABEL_PATH, - RECURSIVE_RELABEL_PATH, - SET_XATTR, - RECURSIVE_SET_XATTR, - SET_ACL, - RECURSIVE_SET_ACL, - SET_ATTRIBUTE, - RECURSIVE_SET_ATTRIBUTE); -} - -static bool takes_ownership(ItemType t) { - return IN_SET(t, - CREATE_FILE, - TRUNCATE_FILE, - CREATE_DIRECTORY, - EMPTY_DIRECTORY, - TRUNCATE_DIRECTORY, - CREATE_SUBVOLUME, - CREATE_SUBVOLUME_INHERIT_QUOTA, - CREATE_SUBVOLUME_NEW_QUOTA, - CREATE_FIFO, - CREATE_SYMLINK, - CREATE_CHAR_DEVICE, - CREATE_BLOCK_DEVICE, - COPY_FILES, - WRITE_FILE, - IGNORE_PATH, - IGNORE_DIRECTORY_PATH, - REMOVE_PATH, - RECURSIVE_REMOVE_PATH); -} - -static struct Item* find_glob(OrderedHashmap *h, const char *match) { - ItemArray *j; - Iterator i; - - ORDERED_HASHMAP_FOREACH(j, h, i) { - unsigned n; - - for (n = 0; n < j->count; n++) { - Item *item = j->items + n; - - if (fnmatch(item->path, match, FNM_PATHNAME|FNM_PERIOD) == 0) - return item; - } - } - - return NULL; -} - -static void load_unix_sockets(void) { - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - - if (unix_sockets) - return; - - /* We maintain a cache of the sockets we found in - * /proc/net/unix to speed things up a little. */ - - unix_sockets = set_new(&string_hash_ops); - if (!unix_sockets) - return; - - f = fopen("/proc/net/unix", "re"); - if (!f) - return; - - /* Skip header */ - if (!fgets(line, sizeof(line), f)) - goto fail; - - for (;;) { - char *p, *s; - int k; - - if (!fgets(line, sizeof(line), f)) - break; - - truncate_nl(line); - - p = strchr(line, ':'); - if (!p) - continue; - - if (strlen(p) < 37) - continue; - - p += 37; - p += strspn(p, WHITESPACE); - p += strcspn(p, WHITESPACE); /* skip one more word */ - p += strspn(p, WHITESPACE); - - if (*p != '/') - continue; - - s = strdup(p); - if (!s) - goto fail; - - path_kill_slashes(s); - - k = set_consume(unix_sockets, s); - if (k < 0 && k != -EEXIST) - goto fail; - } - - return; - -fail: - set_free_free(unix_sockets); - unix_sockets = NULL; -} - -static bool unix_socket_alive(const char *fn) { - assert(fn); - - load_unix_sockets(); - - if (unix_sockets) - return !!set_get(unix_sockets, (char*) fn); - - /* We don't know, so assume yes */ - return true; -} - -static int dir_is_mount_point(DIR *d, const char *subdir) { - - union file_handle_union h = FILE_HANDLE_INIT; - int mount_id_parent, mount_id; - int r_p, r; - - r_p = name_to_handle_at(dirfd(d), ".", &h.handle, &mount_id_parent, 0); - if (r_p < 0) - r_p = -errno; - - h.handle.handle_bytes = MAX_HANDLE_SZ; - r = name_to_handle_at(dirfd(d), subdir, &h.handle, &mount_id, 0); - if (r < 0) - r = -errno; - - /* got no handle; make no assumptions, return error */ - if (r_p < 0 && r < 0) - return r_p; - - /* got both handles; if they differ, it is a mount point */ - if (r_p >= 0 && r >= 0) - return mount_id_parent != mount_id; - - /* got only one handle; assume different mount points if one - * of both queries was not supported by the filesystem */ - if (r_p == -ENOSYS || r_p == -EOPNOTSUPP || r == -ENOSYS || r == -EOPNOTSUPP) - return true; - - /* return error */ - if (r_p < 0) - return r_p; - return r; -} - -static DIR* xopendirat_nomod(int dirfd, const char *path) { - DIR *dir; - - dir = xopendirat(dirfd, path, O_NOFOLLOW|O_NOATIME); - if (dir) - return dir; - - log_debug_errno(errno, "Cannot open %sdirectory \"%s\": %m", dirfd == AT_FDCWD ? "" : "sub", path); - if (errno != EPERM) - return NULL; - - dir = xopendirat(dirfd, path, O_NOFOLLOW); - if (!dir) - log_debug_errno(errno, "Cannot open %sdirectory \"%s\": %m", dirfd == AT_FDCWD ? "" : "sub", path); - - return dir; -} - -static DIR* opendir_nomod(const char *path) { - return xopendirat_nomod(AT_FDCWD, path); -} - -static int dir_cleanup( - Item *i, - const char *p, - DIR *d, - const struct stat *ds, - usec_t cutoff, - dev_t rootdev, - bool mountpoint, - int maxdepth, - bool keep_this_level) { - - struct dirent *dent; - struct timespec times[2]; - bool deleted = false; - int r = 0; - - while ((dent = readdir(d))) { - struct stat s; - usec_t age; - _cleanup_free_ char *sub_path = NULL; - - if (STR_IN_SET(dent->d_name, ".", "..")) - continue; - - if (fstatat(dirfd(d), dent->d_name, &s, AT_SYMLINK_NOFOLLOW) < 0) { - if (errno == ENOENT) - continue; - - /* FUSE, NFS mounts, SELinux might return EACCES */ - if (errno == EACCES) - log_debug_errno(errno, "stat(%s/%s) failed: %m", p, dent->d_name); - else - log_error_errno(errno, "stat(%s/%s) failed: %m", p, dent->d_name); - r = -errno; - continue; - } - - /* Stay on the same filesystem */ - if (s.st_dev != rootdev) { - log_debug("Ignoring \"%s/%s\": different filesystem.", p, dent->d_name); - continue; - } - - /* Try to detect bind mounts of the same filesystem instance; they - * do not differ in device major/minors. This type of query is not - * supported on all kernels or filesystem types though. */ - if (S_ISDIR(s.st_mode) && dir_is_mount_point(d, dent->d_name) > 0) { - log_debug("Ignoring \"%s/%s\": different mount of the same filesystem.", - p, dent->d_name); - continue; - } - - /* Do not delete read-only files owned by root */ - if (s.st_uid == 0 && !(s.st_mode & S_IWUSR)) { - log_debug("Ignoring \"%s/%s\": read-only and owner by root.", p, dent->d_name); - continue; - } - - sub_path = strjoin(p, "/", dent->d_name, NULL); - if (!sub_path) { - r = log_oom(); - goto finish; - } - - /* Is there an item configured for this path? */ - if (ordered_hashmap_get(items, sub_path)) { - log_debug("Ignoring \"%s\": a separate entry exists.", sub_path); - continue; - } - - if (find_glob(globs, sub_path)) { - log_debug("Ignoring \"%s\": a separate glob exists.", sub_path); - continue; - } - - if (S_ISDIR(s.st_mode)) { - - if (mountpoint && - streq(dent->d_name, "lost+found") && - s.st_uid == 0) { - log_debug("Ignoring \"%s\".", sub_path); - continue; - } - - if (maxdepth <= 0) - log_warning("Reached max depth on \"%s\".", sub_path); - else { - _cleanup_closedir_ DIR *sub_dir; - int q; - - sub_dir = xopendirat_nomod(dirfd(d), dent->d_name); - if (!sub_dir) { - if (errno != ENOENT) - r = log_error_errno(errno, "opendir(%s) failed: %m", sub_path); - - continue; - } - - q = dir_cleanup(i, sub_path, sub_dir, &s, cutoff, rootdev, false, maxdepth-1, false); - if (q < 0) - r = q; - } - - /* Note: if you are wondering why we don't - * support the sticky bit for excluding - * directories from cleaning like we do it for - * other file system objects: well, the sticky - * bit already has a meaning for directories, - * so we don't want to overload that. */ - - if (keep_this_level) { - log_debug("Keeping \"%s\".", sub_path); - continue; - } - - /* Ignore ctime, we change it when deleting */ - age = timespec_load(&s.st_mtim); - if (age >= cutoff) { - char a[FORMAT_TIMESTAMP_MAX]; - /* Follows spelling in stat(1). */ - log_debug("Directory \"%s\": modify time %s is too new.", - sub_path, - format_timestamp_us(a, sizeof(a), age)); - continue; - } - - age = timespec_load(&s.st_atim); - if (age >= cutoff) { - char a[FORMAT_TIMESTAMP_MAX]; - log_debug("Directory \"%s\": access time %s is too new.", - sub_path, - format_timestamp_us(a, sizeof(a), age)); - continue; - } - - log_debug("Removing directory \"%s\".", sub_path); - if (unlinkat(dirfd(d), dent->d_name, AT_REMOVEDIR) < 0) - if (errno != ENOENT && errno != ENOTEMPTY) { - log_error_errno(errno, "rmdir(%s): %m", sub_path); - r = -errno; - } - - } else { - /* Skip files for which the sticky bit is - * set. These are semantics we define, and are - * unknown elsewhere. See XDG_RUNTIME_DIR - * specification for details. */ - if (s.st_mode & S_ISVTX) { - log_debug("Skipping \"%s\": sticky bit set.", sub_path); - continue; - } - - if (mountpoint && S_ISREG(s.st_mode)) - if (s.st_uid == 0 && STR_IN_SET(dent->d_name, - ".journal", - "aquota.user", - "aquota.group")) { - log_debug("Skipping \"%s\".", sub_path); - continue; - } - - /* Ignore sockets that are listed in /proc/net/unix */ - if (S_ISSOCK(s.st_mode) && unix_socket_alive(sub_path)) { - log_debug("Skipping \"%s\": live socket.", sub_path); - continue; - } - - /* Ignore device nodes */ - if (S_ISCHR(s.st_mode) || S_ISBLK(s.st_mode)) { - log_debug("Skipping \"%s\": a device.", sub_path); - continue; - } - - /* Keep files on this level around if this is - * requested */ - if (keep_this_level) { - log_debug("Keeping \"%s\".", sub_path); - continue; - } - - age = timespec_load(&s.st_mtim); - if (age >= cutoff) { - char a[FORMAT_TIMESTAMP_MAX]; - /* Follows spelling in stat(1). */ - log_debug("File \"%s\": modify time %s is too new.", - sub_path, - format_timestamp_us(a, sizeof(a), age)); - continue; - } - - age = timespec_load(&s.st_atim); - if (age >= cutoff) { - char a[FORMAT_TIMESTAMP_MAX]; - log_debug("File \"%s\": access time %s is too new.", - sub_path, - format_timestamp_us(a, sizeof(a), age)); - continue; - } - - age = timespec_load(&s.st_ctim); - if (age >= cutoff) { - char a[FORMAT_TIMESTAMP_MAX]; - log_debug("File \"%s\": change time %s is too new.", - sub_path, - format_timestamp_us(a, sizeof(a), age)); - continue; - } - - log_debug("unlink \"%s\"", sub_path); - - if (unlinkat(dirfd(d), dent->d_name, 0) < 0) - if (errno != ENOENT) - r = log_error_errno(errno, "unlink(%s): %m", sub_path); - - deleted = true; - } - } - -finish: - if (deleted) { - usec_t age1, age2; - char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX]; - - /* Restore original directory timestamps */ - times[0] = ds->st_atim; - times[1] = ds->st_mtim; - - age1 = timespec_load(&ds->st_atim); - age2 = timespec_load(&ds->st_mtim); - log_debug("Restoring access and modification time on \"%s\": %s, %s", - p, - format_timestamp_us(a, sizeof(a), age1), - format_timestamp_us(b, sizeof(b), age2)); - if (futimens(dirfd(d), times) < 0) - log_error_errno(errno, "utimensat(%s): %m", p); - } - - return r; -} - -static int path_set_perms(Item *i, const char *path) { - _cleanup_close_ int fd = -1; - struct stat st; - - assert(i); - assert(path); - - /* We open the file with O_PATH here, to make the operation - * somewhat atomic. Also there's unfortunately no fchmodat() - * with AT_SYMLINK_NOFOLLOW, hence we emulate it here via - * O_PATH. */ - - fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH); - if (fd < 0) - return log_error_errno(errno, "Adjusting owner and mode for %s failed: %m", path); - - if (fstatat(fd, "", &st, AT_EMPTY_PATH) < 0) - return log_error_errno(errno, "Failed to fstat() file %s: %m", path); - - if (S_ISLNK(st.st_mode)) - log_debug("Skipping mode an owner fix for symlink %s.", path); - else { - char fn[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; - xsprintf(fn, "/proc/self/fd/%i", fd); - - /* not using i->path directly because it may be a glob */ - if (i->mode_set) { - mode_t m = i->mode; - - if (i->mask_perms) { - if (!(st.st_mode & 0111)) - m &= ~0111; - if (!(st.st_mode & 0222)) - m &= ~0222; - if (!(st.st_mode & 0444)) - m &= ~0444; - if (!S_ISDIR(st.st_mode)) - m &= ~07000; /* remove sticky/sgid/suid bit, unless directory */ - } - - if (m == (st.st_mode & 07777)) - log_debug("\"%s\" has right mode %o", path, st.st_mode); - else { - log_debug("chmod \"%s\" to mode %o", path, m); - if (chmod(fn, m) < 0) - return log_error_errno(errno, "chmod(%s) failed: %m", path); - } - } - - if ((i->uid != st.st_uid || i->gid != st.st_gid) && - (i->uid_set || i->gid_set)) { - log_debug("chown \"%s\" to "UID_FMT"."GID_FMT, - path, - i->uid_set ? i->uid : UID_INVALID, - i->gid_set ? i->gid : GID_INVALID); - if (chown(fn, - i->uid_set ? i->uid : UID_INVALID, - i->gid_set ? i->gid : GID_INVALID) < 0) - return log_error_errno(errno, "chown(%s) failed: %m", path); - } - } - - fd = safe_close(fd); - - return label_fix(path, false, false); -} - -static int parse_xattrs_from_arg(Item *i) { - const char *p; - int r; - - assert(i); - assert(i->argument); - - p = i->argument; - - for (;;) { - _cleanup_free_ char *name = NULL, *value = NULL, *xattr = NULL, *xattr_replaced = NULL; - - r = extract_first_word(&p, &xattr, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE); - if (r < 0) - log_warning_errno(r, "Failed to parse extended attribute '%s', ignoring: %m", p); - if (r <= 0) - break; - - r = specifier_printf(xattr, specifier_table, NULL, &xattr_replaced); - if (r < 0) - return log_error_errno(r, "Failed to replace specifiers in extended attribute '%s': %m", xattr); - - r = split_pair(xattr_replaced, "=", &name, &value); - if (r < 0) { - log_warning_errno(r, "Failed to parse extended attribute, ignoring: %s", xattr); - continue; - } - - if (isempty(name) || isempty(value)) { - log_warning("Malformed extended attribute found, ignoring: %s", xattr); - continue; - } - - if (strv_push_pair(&i->xattrs, name, value) < 0) - return log_oom(); - - name = value = NULL; - } - - return 0; -} - -static int path_set_xattrs(Item *i, const char *path) { - char **name, **value; - - assert(i); - assert(path); - - STRV_FOREACH_PAIR(name, value, i->xattrs) { - int n; - - n = strlen(*value); - log_debug("Setting extended attribute '%s=%s' on %s.", *name, *value, path); - if (lsetxattr(path, *name, *value, n, 0) < 0) { - log_error("Setting extended attribute %s=%s on %s failed: %m", *name, *value, path); - return -errno; - } - } - return 0; -} - -static int parse_acls_from_arg(Item *item) { -#ifdef HAVE_ACL - int r; - - assert(item); - - /* If force (= modify) is set, we will not modify the acl - * afterwards, so the mask can be added now if necessary. */ - - r = parse_acl(item->argument, &item->acl_access, &item->acl_default, !item->force); - if (r < 0) - log_warning_errno(r, "Failed to parse ACL \"%s\": %m. Ignoring", item->argument); -#else - log_warning_errno(ENOSYS, "ACLs are not supported. Ignoring"); -#endif - - return 0; -} - -#ifdef HAVE_ACL -static int path_set_acl(const char *path, const char *pretty, acl_type_t type, acl_t acl, bool modify) { - _cleanup_(acl_free_charpp) char *t = NULL; - _cleanup_(acl_freep) acl_t dup = NULL; - int r; - - /* Returns 0 for success, positive error if already warned, - * negative error otherwise. */ - - if (modify) { - r = acls_for_file(path, type, acl, &dup); - if (r < 0) - return r; - - r = calc_acl_mask_if_needed(&dup); - if (r < 0) - return r; - } else { - dup = acl_dup(acl); - if (!dup) - return -errno; - - /* the mask was already added earlier if needed */ - } - - r = add_base_acls_if_needed(&dup, path); - if (r < 0) - return r; - - t = acl_to_any_text(dup, NULL, ',', TEXT_ABBREVIATE); - log_debug("Setting %s ACL %s on %s.", - type == ACL_TYPE_ACCESS ? "access" : "default", - strna(t), pretty); - - r = acl_set_file(path, type, dup); - if (r < 0) - /* Return positive to indicate we already warned */ - return -log_error_errno(errno, - "Setting %s ACL \"%s\" on %s failed: %m", - type == ACL_TYPE_ACCESS ? "access" : "default", - strna(t), pretty); - - return 0; -} -#endif - -static int path_set_acls(Item *item, const char *path) { - int r = 0; -#ifdef HAVE_ACL - char fn[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; - _cleanup_close_ int fd = -1; - struct stat st; - - assert(item); - assert(path); - - fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH); - if (fd < 0) - return log_error_errno(errno, "Adjusting ACL of %s failed: %m", path); - - if (fstatat(fd, "", &st, AT_EMPTY_PATH) < 0) - return log_error_errno(errno, "Failed to fstat() file %s: %m", path); - - if (S_ISLNK(st.st_mode)) { - log_debug("Skipping ACL fix for symlink %s.", path); - return 0; - } - - xsprintf(fn, "/proc/self/fd/%i", fd); - - if (item->acl_access) - r = path_set_acl(fn, path, ACL_TYPE_ACCESS, item->acl_access, item->force); - - if (r == 0 && item->acl_default) - r = path_set_acl(fn, path, ACL_TYPE_DEFAULT, item->acl_default, item->force); - - if (r > 0) - return -r; /* already warned */ - else if (r == -EOPNOTSUPP) { - log_debug_errno(r, "ACLs not supported by file system at %s", path); - return 0; - } else if (r < 0) - log_error_errno(r, "ACL operation on \"%s\" failed: %m", path); -#endif - return r; -} - -#define ATTRIBUTES_ALL \ - (FS_NOATIME_FL | \ - FS_SYNC_FL | \ - FS_DIRSYNC_FL | \ - FS_APPEND_FL | \ - FS_COMPR_FL | \ - FS_NODUMP_FL | \ - FS_EXTENT_FL | \ - FS_IMMUTABLE_FL | \ - FS_JOURNAL_DATA_FL | \ - FS_SECRM_FL | \ - FS_UNRM_FL | \ - FS_NOTAIL_FL | \ - FS_TOPDIR_FL | \ - FS_NOCOW_FL) - -static int parse_attribute_from_arg(Item *item) { - - static const struct { - char character; - unsigned value; - } attributes[] = { - { 'A', FS_NOATIME_FL }, /* do not update atime */ - { 'S', FS_SYNC_FL }, /* Synchronous updates */ - { 'D', FS_DIRSYNC_FL }, /* dirsync behaviour (directories only) */ - { 'a', FS_APPEND_FL }, /* writes to file may only append */ - { 'c', FS_COMPR_FL }, /* Compress file */ - { 'd', FS_NODUMP_FL }, /* do not dump file */ - { 'e', FS_EXTENT_FL }, /* Top of directory hierarchies*/ - { 'i', FS_IMMUTABLE_FL }, /* Immutable file */ - { 'j', FS_JOURNAL_DATA_FL }, /* Reserved for ext3 */ - { 's', FS_SECRM_FL }, /* Secure deletion */ - { 'u', FS_UNRM_FL }, /* Undelete */ - { 't', FS_NOTAIL_FL }, /* file tail should not be merged */ - { 'T', FS_TOPDIR_FL }, /* Top of directory hierarchies*/ - { 'C', FS_NOCOW_FL }, /* Do not cow file */ - }; - - enum { - MODE_ADD, - MODE_DEL, - MODE_SET - } mode = MODE_ADD; - - unsigned value = 0, mask = 0; - const char *p; - - assert(item); - - p = item->argument; - if (p) { - if (*p == '+') { - mode = MODE_ADD; - p++; - } else if (*p == '-') { - mode = MODE_DEL; - p++; - } else if (*p == '=') { - mode = MODE_SET; - p++; - } - } - - if (isempty(p) && mode != MODE_SET) { - log_error("Setting file attribute on '%s' needs an attribute specification.", item->path); - return -EINVAL; - } - - for (; p && *p ; p++) { - unsigned i, v; - - for (i = 0; i < ELEMENTSOF(attributes); i++) - if (*p == attributes[i].character) - break; - - if (i >= ELEMENTSOF(attributes)) { - log_error("Unknown file attribute '%c' on '%s'.", *p, item->path); - return -EINVAL; - } - - v = attributes[i].value; - - SET_FLAG(value, v, (mode == MODE_ADD || mode == MODE_SET)); - - mask |= v; - } - - if (mode == MODE_SET) - mask |= ATTRIBUTES_ALL; - - assert(mask != 0); - - item->attribute_mask = mask; - item->attribute_value = value; - item->attribute_set = true; - - return 0; -} - -static int path_set_attribute(Item *item, const char *path) { - _cleanup_close_ int fd = -1; - struct stat st; - unsigned f; - int r; - - if (!item->attribute_set || item->attribute_mask == 0) - return 0; - - fd = open(path, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOATIME|O_NOFOLLOW); - if (fd < 0) { - if (errno == ELOOP) - return log_error_errno(errno, "Skipping file attributes adjustment on symlink %s.", path); - - return log_error_errno(errno, "Cannot open '%s': %m", path); - } - - if (fstat(fd, &st) < 0) - return log_error_errno(errno, "Cannot stat '%s': %m", path); - - /* Issuing the file attribute ioctls on device nodes is not - * safe, as that will be delivered to the drivers, not the - * file system containing the device node. */ - if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) { - log_error("Setting file flags is only supported on regular files and directories, cannot set on '%s'.", path); - return -EINVAL; - } - - f = item->attribute_value & item->attribute_mask; - - /* Mask away directory-specific flags */ - if (!S_ISDIR(st.st_mode)) - f &= ~FS_DIRSYNC_FL; - - r = chattr_fd(fd, f, item->attribute_mask); - if (r < 0) - log_full_errno(r == -ENOTTY ? LOG_DEBUG : LOG_WARNING, - r, - "Cannot set file attribute for '%s', value=0x%08x, mask=0x%08x: %m", - path, item->attribute_value, item->attribute_mask); - - return 0; -} - -static int write_one_file(Item *i, const char *path) { - _cleanup_close_ int fd = -1; - int flags, r = 0; - struct stat st; - - assert(i); - assert(path); - - flags = i->type == CREATE_FILE ? O_CREAT|O_APPEND|O_NOFOLLOW : - i->type == TRUNCATE_FILE ? O_CREAT|O_TRUNC|O_NOFOLLOW : 0; - - RUN_WITH_UMASK(0000) { - mac_selinux_create_file_prepare(path, S_IFREG); - fd = open(path, flags|O_NDELAY|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode); - mac_selinux_create_file_clear(); - } - - if (fd < 0) { - if (i->type == WRITE_FILE && errno == ENOENT) { - log_debug_errno(errno, "Not writing \"%s\": %m", path); - return 0; - } - - r = -errno; - if (!i->argument && errno == EROFS && stat(path, &st) == 0 && - (i->type == CREATE_FILE || st.st_size == 0)) - goto check_mode; - - return log_error_errno(r, "Failed to create file %s: %m", path); - } - - if (i->argument) { - _cleanup_free_ char *unescaped = NULL, *replaced = NULL; - - log_debug("%s to \"%s\".", i->type == CREATE_FILE ? "Appending" : "Writing", path); - - r = cunescape(i->argument, 0, &unescaped); - if (r < 0) - return log_error_errno(r, "Failed to unescape parameter to write: %s", i->argument); - - r = specifier_printf(unescaped, specifier_table, NULL, &replaced); - if (r < 0) - return log_error_errno(r, "Failed to replace specifiers in parameter to write '%s': %m", unescaped); - - r = loop_write(fd, replaced, strlen(replaced), false); - if (r < 0) - return log_error_errno(r, "Failed to write file \"%s\": %m", path); - } else - log_debug("\"%s\" has been created.", path); - - fd = safe_close(fd); - - if (stat(path, &st) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", path); - - check_mode: - if (!S_ISREG(st.st_mode)) { - log_error("%s is not a file.", path); - return -EEXIST; - } - - r = path_set_perms(i, path); - if (r < 0) - return r; - - return 0; -} - -typedef int (*action_t)(Item *, const char *); - -static int item_do_children(Item *i, const char *path, action_t action) { - _cleanup_closedir_ DIR *d; - int r = 0; - - assert(i); - assert(path); - - /* This returns the first error we run into, but nevertheless - * tries to go on */ - - d = opendir_nomod(path); - if (!d) - return errno == ENOENT || errno == ENOTDIR ? 0 : -errno; - - for (;;) { - _cleanup_free_ char *p = NULL; - struct dirent *de; - int q; - - errno = 0; - de = readdir(d); - if (!de) { - if (errno > 0 && r == 0) - r = -errno; - - break; - } - - if (STR_IN_SET(de->d_name, ".", "..")) - continue; - - p = strjoin(path, "/", de->d_name, NULL); - if (!p) - return -ENOMEM; - - q = action(i, p); - if (q < 0 && q != -ENOENT && r == 0) - r = q; - - if (IN_SET(de->d_type, DT_UNKNOWN, DT_DIR)) { - q = item_do_children(i, p, action); - if (q < 0 && r == 0) - r = q; - } - } - - return r; -} - -static int glob_item(Item *i, action_t action, bool recursive) { - _cleanup_globfree_ glob_t g = { - .gl_closedir = (void (*)(void *)) closedir, - .gl_readdir = (struct dirent *(*)(void *)) readdir, - .gl_opendir = (void *(*)(const char *)) opendir_nomod, - .gl_lstat = lstat, - .gl_stat = stat, - }; - int r = 0, k; - char **fn; - - errno = 0; - k = glob(i->path, GLOB_NOSORT|GLOB_BRACE|GLOB_ALTDIRFUNC, NULL, &g); - if (k != 0 && k != GLOB_NOMATCH) - return log_error_errno(errno ?: EIO, "glob(%s) failed: %m", i->path); - - STRV_FOREACH(fn, g.gl_pathv) { - k = action(i, *fn); - if (k < 0 && r == 0) - r = k; - - if (recursive) { - k = item_do_children(i, *fn, action); - if (k < 0 && r == 0) - r = k; - } - } - - return r; -} - -typedef enum { - CREATION_NORMAL, - CREATION_EXISTING, - CREATION_FORCE, - _CREATION_MODE_MAX, - _CREATION_MODE_INVALID = -1 -} CreationMode; - -static const char *creation_mode_verb_table[_CREATION_MODE_MAX] = { - [CREATION_NORMAL] = "Created", - [CREATION_EXISTING] = "Found existing", - [CREATION_FORCE] = "Created replacement", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(creation_mode_verb, CreationMode); - -static int create_item(Item *i) { - _cleanup_free_ char *resolved = NULL; - struct stat st; - int r = 0; - int q = 0; - CreationMode creation; - - assert(i); - - log_debug("Running create action for entry %c %s", (char) i->type, i->path); - - switch (i->type) { - - case IGNORE_PATH: - case IGNORE_DIRECTORY_PATH: - case REMOVE_PATH: - case RECURSIVE_REMOVE_PATH: - return 0; - - case CREATE_FILE: - case TRUNCATE_FILE: - r = write_one_file(i, i->path); - if (r < 0) - return r; - break; - - case COPY_FILES: { - r = specifier_printf(i->argument, specifier_table, NULL, &resolved); - if (r < 0) - return log_error_errno(r, "Failed to substitute specifiers in copy source %s: %m", i->argument); - - log_debug("Copying tree \"%s\" to \"%s\".", resolved, i->path); - r = copy_tree(resolved, i->path, false); - - if (r == -EROFS && stat(i->path, &st) == 0) - r = -EEXIST; - - if (r < 0) { - struct stat a, b; - - if (r != -EEXIST) - return log_error_errno(r, "Failed to copy files to %s: %m", i->path); - - if (stat(resolved, &a) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", resolved); - - if (stat(i->path, &b) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", i->path); - - if ((a.st_mode ^ b.st_mode) & S_IFMT) { - log_debug("Can't copy to %s, file exists already and is of different type", i->path); - return 0; - } - } - - r = path_set_perms(i, i->path); - if (r < 0) - return r; - - break; - - case WRITE_FILE: - r = glob_item(i, write_one_file, false); - if (r < 0) - return r; - - break; - - case CREATE_DIRECTORY: - case TRUNCATE_DIRECTORY: - case CREATE_SUBVOLUME: - case CREATE_SUBVOLUME_INHERIT_QUOTA: - case CREATE_SUBVOLUME_NEW_QUOTA: - RUN_WITH_UMASK(0000) - mkdir_parents_label(i->path, 0755); - - if (IN_SET(i->type, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA)) { - - if (btrfs_is_subvol(isempty(arg_root) ? "/" : arg_root) <= 0) - - /* Don't create a subvolume unless the - * root directory is one, too. We do - * this under the assumption that if - * the root directory is just a plain - * directory (i.e. very light-weight), - * we shouldn't try to split it up - * into subvolumes (i.e. more - * heavy-weight). Thus, chroot() - * environments and suchlike will get - * a full brtfs subvolume set up below - * their tree only if they - * specifically set up a btrfs - * subvolume for the root dir too. */ - - r = -ENOTTY; - else { - RUN_WITH_UMASK((~i->mode) & 0777) - r = btrfs_subvol_make(i->path); - } - } else - r = 0; - - if (IN_SET(i->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY) || r == -ENOTTY) - RUN_WITH_UMASK(0000) - r = mkdir_label(i->path, i->mode); - - if (r < 0) { - int k; - - if (r != -EEXIST && r != -EROFS) - return log_error_errno(r, "Failed to create directory or subvolume \"%s\": %m", i->path); - - k = is_dir(i->path, false); - if (k == -ENOENT && r == -EROFS) - return log_error_errno(r, "%s does not exist and cannot be created as the file system is read-only.", i->path); - if (k < 0) - return log_error_errno(k, "Failed to check if %s exists: %m", i->path); - if (!k) { - log_warning("\"%s\" already exists and is not a directory.", i->path); - return 0; - } - - creation = CREATION_EXISTING; - } else - creation = CREATION_NORMAL; - - log_debug("%s directory \"%s\".", creation_mode_verb_to_string(creation), i->path); - - if (IN_SET(i->type, CREATE_SUBVOLUME_NEW_QUOTA, CREATE_SUBVOLUME_INHERIT_QUOTA)) { - r = btrfs_subvol_auto_qgroup(i->path, 0, i->type == CREATE_SUBVOLUME_NEW_QUOTA); - if (r == -ENOTTY) - log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (unsupported fs or dir not a subvolume): %m", i->path); - else if (r == -EROFS) - log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (fs is read-only).", i->path); - else if (r == -ENOPROTOOPT) - log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (quota support is disabled).", i->path); - else if (r < 0) - q = log_error_errno(r, "Failed to adjust quota for subvolume \"%s\": %m", i->path); - else if (r > 0) - log_debug("Adjusted quota for subvolume \"%s\".", i->path); - else if (r == 0) - log_debug("Quota for subvolume \"%s\" already in place, no change made.", i->path); - } - - /* fall through */ - - case EMPTY_DIRECTORY: - r = path_set_perms(i, i->path); - if (q < 0) - return q; - if (r < 0) - return r; - - break; - - case CREATE_FIFO: - RUN_WITH_UMASK(0000) { - mac_selinux_create_file_prepare(i->path, S_IFIFO); - r = mkfifo(i->path, i->mode); - mac_selinux_create_file_clear(); - } - - if (r < 0) { - if (errno != EEXIST) - return log_error_errno(errno, "Failed to create fifo %s: %m", i->path); - - if (lstat(i->path, &st) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", i->path); - - if (!S_ISFIFO(st.st_mode)) { - - if (i->force) { - RUN_WITH_UMASK(0000) { - mac_selinux_create_file_prepare(i->path, S_IFIFO); - r = mkfifo_atomic(i->path, i->mode); - mac_selinux_create_file_clear(); - } - - if (r < 0) - return log_error_errno(r, "Failed to create fifo %s: %m", i->path); - creation = CREATION_FORCE; - } else { - log_warning("\"%s\" already exists and is not a fifo.", i->path); - return 0; - } - } else - creation = CREATION_EXISTING; - } else - creation = CREATION_NORMAL; - log_debug("%s fifo \"%s\".", creation_mode_verb_to_string(creation), i->path); - - r = path_set_perms(i, i->path); - if (r < 0) - return r; - - break; - } - - case CREATE_SYMLINK: { - r = specifier_printf(i->argument, specifier_table, NULL, &resolved); - if (r < 0) - return log_error_errno(r, "Failed to substitute specifiers in symlink target %s: %m", i->argument); - - mac_selinux_create_file_prepare(i->path, S_IFLNK); - r = symlink(resolved, i->path); - mac_selinux_create_file_clear(); - - if (r < 0) { - _cleanup_free_ char *x = NULL; - - if (errno != EEXIST) - return log_error_errno(errno, "symlink(%s, %s) failed: %m", resolved, i->path); - - r = readlink_malloc(i->path, &x); - if (r < 0 || !streq(resolved, x)) { - - if (i->force) { - mac_selinux_create_file_prepare(i->path, S_IFLNK); - r = symlink_atomic(resolved, i->path); - mac_selinux_create_file_clear(); - - if (r < 0) - return log_error_errno(r, "symlink(%s, %s) failed: %m", resolved, i->path); - - creation = CREATION_FORCE; - } else { - log_debug("\"%s\" is not a symlink or does not point to the correct path.", i->path); - return 0; - } - } else - creation = CREATION_EXISTING; - } else - - creation = CREATION_NORMAL; - log_debug("%s symlink \"%s\".", creation_mode_verb_to_string(creation), i->path); - break; - } - - case CREATE_BLOCK_DEVICE: - case CREATE_CHAR_DEVICE: { - mode_t file_type; - - if (have_effective_cap(CAP_MKNOD) == 0) { - /* In a container we lack CAP_MKNOD. We - shouldn't attempt to create the device node in - that case to avoid noise, and we don't support - virtualized devices in containers anyway. */ - - log_debug("We lack CAP_MKNOD, skipping creation of device node %s.", i->path); - return 0; - } - - file_type = i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR; - - RUN_WITH_UMASK(0000) { - mac_selinux_create_file_prepare(i->path, file_type); - r = mknod(i->path, i->mode | file_type, i->major_minor); - mac_selinux_create_file_clear(); - } - - if (r < 0) { - if (errno == EPERM) { - log_debug("We lack permissions, possibly because of cgroup configuration; " - "skipping creation of device node %s.", i->path); - return 0; - } - - if (errno != EEXIST) - return log_error_errno(errno, "Failed to create device node %s: %m", i->path); - - if (lstat(i->path, &st) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", i->path); - - if ((st.st_mode & S_IFMT) != file_type) { - - if (i->force) { - - RUN_WITH_UMASK(0000) { - mac_selinux_create_file_prepare(i->path, file_type); - r = mknod_atomic(i->path, i->mode | file_type, i->major_minor); - mac_selinux_create_file_clear(); - } - - if (r < 0) - return log_error_errno(r, "Failed to create device node \"%s\": %m", i->path); - creation = CREATION_FORCE; - } else { - log_debug("%s is not a device node.", i->path); - return 0; - } - } else - creation = CREATION_EXISTING; - } else - creation = CREATION_NORMAL; - - log_debug("%s %s device node \"%s\" %u:%u.", - creation_mode_verb_to_string(creation), - i->type == CREATE_BLOCK_DEVICE ? "block" : "char", - i->path, major(i->mode), minor(i->mode)); - - r = path_set_perms(i, i->path); - if (r < 0) - return r; - - break; - } - - case ADJUST_MODE: - case RELABEL_PATH: - r = glob_item(i, path_set_perms, false); - if (r < 0) - return r; - break; - - case RECURSIVE_RELABEL_PATH: - r = glob_item(i, path_set_perms, true); - if (r < 0) - return r; - break; - - case SET_XATTR: - r = glob_item(i, path_set_xattrs, false); - if (r < 0) - return r; - break; - - case RECURSIVE_SET_XATTR: - r = glob_item(i, path_set_xattrs, true); - if (r < 0) - return r; - break; - - case SET_ACL: - r = glob_item(i, path_set_acls, false); - if (r < 0) - return r; - break; - - case RECURSIVE_SET_ACL: - r = glob_item(i, path_set_acls, true); - if (r < 0) - return r; - break; - - case SET_ATTRIBUTE: - r = glob_item(i, path_set_attribute, false); - if (r < 0) - return r; - break; - - case RECURSIVE_SET_ATTRIBUTE: - r = glob_item(i, path_set_attribute, true); - if (r < 0) - return r; - break; - } - - return 0; -} - -static int remove_item_instance(Item *i, const char *instance) { - int r; - - assert(i); - - switch (i->type) { - - case REMOVE_PATH: - if (remove(instance) < 0 && errno != ENOENT) - return log_error_errno(errno, "rm(%s): %m", instance); - - break; - - case TRUNCATE_DIRECTORY: - case RECURSIVE_REMOVE_PATH: - /* FIXME: we probably should use dir_cleanup() here - * instead of rm_rf() so that 'x' is honoured. */ - log_debug("rm -rf \"%s\"", instance); - r = rm_rf(instance, (i->type == RECURSIVE_REMOVE_PATH ? REMOVE_ROOT|REMOVE_SUBVOLUME : 0) | REMOVE_PHYSICAL); - if (r < 0 && r != -ENOENT) - return log_error_errno(r, "rm_rf(%s): %m", instance); - - break; - - default: - assert_not_reached("wut?"); - } - - return 0; -} - -static int remove_item(Item *i) { - assert(i); - - log_debug("Running remove action for entry %c %s", (char) i->type, i->path); - - switch (i->type) { - - case REMOVE_PATH: - case TRUNCATE_DIRECTORY: - case RECURSIVE_REMOVE_PATH: - return glob_item(i, remove_item_instance, false); - - default: - return 0; - } -} - -static int clean_item_instance(Item *i, const char* instance) { - _cleanup_closedir_ DIR *d = NULL; - struct stat s, ps; - bool mountpoint; - usec_t cutoff, n; - char timestamp[FORMAT_TIMESTAMP_MAX]; - - assert(i); - - if (!i->age_set) - return 0; - - n = now(CLOCK_REALTIME); - if (n < i->age) - return 0; - - cutoff = n - i->age; - - d = opendir_nomod(instance); - if (!d) { - if (errno == ENOENT || errno == ENOTDIR) { - log_debug_errno(errno, "Directory \"%s\": %m", instance); - return 0; - } - - log_error_errno(errno, "Failed to open directory %s: %m", instance); - return -errno; - } - - if (fstat(dirfd(d), &s) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", i->path); - - if (!S_ISDIR(s.st_mode)) { - log_error("%s is not a directory.", i->path); - return -ENOTDIR; - } - - if (fstatat(dirfd(d), "..", &ps, AT_SYMLINK_NOFOLLOW) != 0) - return log_error_errno(errno, "stat(%s/..) failed: %m", i->path); - - mountpoint = s.st_dev != ps.st_dev || s.st_ino == ps.st_ino; - - log_debug("Cleanup threshold for %s \"%s\" is %s", - mountpoint ? "mount point" : "directory", - instance, - format_timestamp_us(timestamp, sizeof(timestamp), cutoff)); - - return dir_cleanup(i, instance, d, &s, cutoff, s.st_dev, mountpoint, - MAX_DEPTH, i->keep_first_level); -} - -static int clean_item(Item *i) { - assert(i); - - log_debug("Running clean action for entry %c %s", (char) i->type, i->path); - - switch (i->type) { - case CREATE_DIRECTORY: - case CREATE_SUBVOLUME: - case CREATE_SUBVOLUME_INHERIT_QUOTA: - case CREATE_SUBVOLUME_NEW_QUOTA: - case EMPTY_DIRECTORY: - case TRUNCATE_DIRECTORY: - case IGNORE_PATH: - case COPY_FILES: - clean_item_instance(i, i->path); - return 0; - case IGNORE_DIRECTORY_PATH: - return glob_item(i, clean_item_instance, false); - default: - return 0; - } -} - -static int process_item_array(ItemArray *array); - -static int process_item(Item *i) { - int r, q, p, t = 0; - _cleanup_free_ char *prefix = NULL; - - assert(i); - - if (i->done) - return 0; - - i->done = true; - - prefix = malloc(strlen(i->path) + 1); - if (!prefix) - return log_oom(); - - PATH_FOREACH_PREFIX(prefix, i->path) { - ItemArray *j; - - j = ordered_hashmap_get(items, prefix); - if (j) { - int s; - - s = process_item_array(j); - if (s < 0 && t == 0) - t = s; - } - } - - r = arg_create ? create_item(i) : 0; - q = arg_remove ? remove_item(i) : 0; - p = arg_clean ? clean_item(i) : 0; - - return t < 0 ? t : - r < 0 ? r : - q < 0 ? q : - p; -} - -static int process_item_array(ItemArray *array) { - unsigned n; - int r = 0, k; - - assert(array); - - for (n = 0; n < array->count; n++) { - k = process_item(array->items + n); - if (k < 0 && r == 0) - r = k; - } - - return r; -} - -static void item_free_contents(Item *i) { - assert(i); - free(i->path); - free(i->argument); - strv_free(i->xattrs); - -#ifdef HAVE_ACL - acl_free(i->acl_access); - acl_free(i->acl_default); -#endif -} - -static void item_array_free(ItemArray *a) { - unsigned n; - - if (!a) - return; - - for (n = 0; n < a->count; n++) - item_free_contents(a->items + n); - free(a->items); - free(a); -} - -static int item_compare(const void *a, const void *b) { - const Item *x = a, *y = b; - - /* Make sure that the ownership taking item is put first, so - * that we first create the node, and then can adjust it */ - - if (takes_ownership(x->type) && !takes_ownership(y->type)) - return -1; - if (!takes_ownership(x->type) && takes_ownership(y->type)) - return 1; - - return (int) x->type - (int) y->type; -} - -static bool item_compatible(Item *a, Item *b) { - assert(a); - assert(b); - assert(streq(a->path, b->path)); - - if (takes_ownership(a->type) && takes_ownership(b->type)) - /* check if the items are the same */ - return streq_ptr(a->argument, b->argument) && - - a->uid_set == b->uid_set && - a->uid == b->uid && - - a->gid_set == b->gid_set && - a->gid == b->gid && - - a->mode_set == b->mode_set && - a->mode == b->mode && - - a->age_set == b->age_set && - a->age == b->age && - - a->mask_perms == b->mask_perms && - - a->keep_first_level == b->keep_first_level && - - a->major_minor == b->major_minor; - - return true; -} - -static bool should_include_path(const char *path) { - char **prefix; - - STRV_FOREACH(prefix, arg_exclude_prefixes) - if (path_startswith(path, *prefix)) { - log_debug("Entry \"%s\" matches exclude prefix \"%s\", skipping.", - path, *prefix); - return false; - } - - STRV_FOREACH(prefix, arg_include_prefixes) - if (path_startswith(path, *prefix)) { - log_debug("Entry \"%s\" matches include prefix \"%s\".", path, *prefix); - return true; - } - - /* no matches, so we should include this path only if we - * have no whitelist at all */ - if (strv_length(arg_include_prefixes) == 0) - return true; - - log_debug("Entry \"%s\" does not match any include prefix, skipping.", path); - return false; -} - -static int parse_line(const char *fname, unsigned line, const char *buffer) { - - _cleanup_free_ char *action = NULL, *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL; - _cleanup_(item_free_contents) Item i = {}; - ItemArray *existing; - OrderedHashmap *h; - int r, pos; - bool force = false, boot = false; - - assert(fname); - assert(line >= 1); - assert(buffer); - - r = extract_many_words( - &buffer, - NULL, - EXTRACT_QUOTES, - &action, - &path, - &mode, - &user, - &group, - &age, - NULL); - if (r < 0) - return log_error_errno(r, "[%s:%u] Failed to parse line: %m", fname, line); - else if (r < 2) { - log_error("[%s:%u] Syntax error.", fname, line); - return -EIO; - } - - if (!isempty(buffer) && !streq(buffer, "-")) { - i.argument = strdup(buffer); - if (!i.argument) - return log_oom(); - } - - if (isempty(action)) { - log_error("[%s:%u] Command too short '%s'.", fname, line, action); - return -EINVAL; - } - - for (pos = 1; action[pos]; pos++) { - if (action[pos] == '!' && !boot) - boot = true; - else if (action[pos] == '+' && !force) - force = true; - else { - log_error("[%s:%u] Unknown modifiers in command '%s'", - fname, line, action); - return -EINVAL; - } - } - - if (boot && !arg_boot) { - log_debug("Ignoring entry %s \"%s\" because --boot is not specified.", - action, path); - return 0; - } - - i.type = action[0]; - i.force = force; - - r = specifier_printf(path, specifier_table, NULL, &i.path); - if (r < 0) { - log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, path); - return r; - } - - switch (i.type) { - - case CREATE_DIRECTORY: - case CREATE_SUBVOLUME: - case CREATE_SUBVOLUME_INHERIT_QUOTA: - case CREATE_SUBVOLUME_NEW_QUOTA: - case EMPTY_DIRECTORY: - case TRUNCATE_DIRECTORY: - case CREATE_FIFO: - case IGNORE_PATH: - case IGNORE_DIRECTORY_PATH: - case REMOVE_PATH: - case RECURSIVE_REMOVE_PATH: - case ADJUST_MODE: - case RELABEL_PATH: - case RECURSIVE_RELABEL_PATH: - if (i.argument) - log_warning("[%s:%u] %c lines don't take argument fields, ignoring.", fname, line, i.type); - - break; - - case CREATE_FILE: - case TRUNCATE_FILE: - break; - - case CREATE_SYMLINK: - if (!i.argument) { - i.argument = strappend("/usr/share/factory/", i.path); - if (!i.argument) - return log_oom(); - } - break; - - case WRITE_FILE: - if (!i.argument) { - log_error("[%s:%u] Write file requires argument.", fname, line); - return -EBADMSG; - } - break; - - case COPY_FILES: - if (!i.argument) { - i.argument = strappend("/usr/share/factory/", i.path); - if (!i.argument) - return log_oom(); - } else if (!path_is_absolute(i.argument)) { - log_error("[%s:%u] Source path is not absolute.", fname, line); - return -EBADMSG; - } - - path_kill_slashes(i.argument); - break; - - case CREATE_CHAR_DEVICE: - case CREATE_BLOCK_DEVICE: { - unsigned major, minor; - - if (!i.argument) { - log_error("[%s:%u] Device file requires argument.", fname, line); - return -EBADMSG; - } - - if (sscanf(i.argument, "%u:%u", &major, &minor) != 2) { - log_error("[%s:%u] Can't parse device file major/minor '%s'.", fname, line, i.argument); - return -EBADMSG; - } - - i.major_minor = makedev(major, minor); - break; - } - - case SET_XATTR: - case RECURSIVE_SET_XATTR: - if (!i.argument) { - log_error("[%s:%u] Set extended attribute requires argument.", fname, line); - return -EBADMSG; - } - r = parse_xattrs_from_arg(&i); - if (r < 0) - return r; - break; - - case SET_ACL: - case RECURSIVE_SET_ACL: - if (!i.argument) { - log_error("[%s:%u] Set ACLs requires argument.", fname, line); - return -EBADMSG; - } - r = parse_acls_from_arg(&i); - if (r < 0) - return r; - break; - - case SET_ATTRIBUTE: - case RECURSIVE_SET_ATTRIBUTE: - if (!i.argument) { - log_error("[%s:%u] Set file attribute requires argument.", fname, line); - return -EBADMSG; - } - r = parse_attribute_from_arg(&i); - if (r < 0) - return r; - break; - - default: - log_error("[%s:%u] Unknown command type '%c'.", fname, line, (char) i.type); - return -EBADMSG; - } - - if (!path_is_absolute(i.path)) { - log_error("[%s:%u] Path '%s' not absolute.", fname, line, i.path); - return -EBADMSG; - } - - path_kill_slashes(i.path); - - if (!should_include_path(i.path)) - return 0; - - if (arg_root) { - char *p; - - p = prefix_root(arg_root, i.path); - if (!p) - return log_oom(); - - free(i.path); - i.path = p; - } - - if (!isempty(user) && !streq(user, "-")) { - const char *u = user; - - r = get_user_creds(&u, &i.uid, NULL, NULL, NULL); - if (r < 0) { - log_error("[%s:%u] Unknown user '%s'.", fname, line, user); - return r; - } - - i.uid_set = true; - } - - if (!isempty(group) && !streq(group, "-")) { - const char *g = group; - - r = get_group_creds(&g, &i.gid); - if (r < 0) { - log_error("[%s:%u] Unknown group '%s'.", fname, line, group); - return r; - } - - i.gid_set = true; - } - - if (!isempty(mode) && !streq(mode, "-")) { - const char *mm = mode; - unsigned m; - - if (*mm == '~') { - i.mask_perms = true; - mm++; - } - - if (parse_mode(mm, &m) < 0) { - log_error("[%s:%u] Invalid mode '%s'.", fname, line, mode); - return -EBADMSG; - } - - i.mode = m; - i.mode_set = true; - } else - i.mode = IN_SET(i.type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA) ? 0755 : 0644; - - if (!isempty(age) && !streq(age, "-")) { - const char *a = age; - - if (*a == '~') { - i.keep_first_level = true; - a++; - } - - if (parse_sec(a, &i.age) < 0) { - log_error("[%s:%u] Invalid age '%s'.", fname, line, age); - return -EBADMSG; - } - - i.age_set = true; - } - - h = needs_glob(i.type) ? globs : items; - - existing = ordered_hashmap_get(h, i.path); - if (existing) { - unsigned n; - - for (n = 0; n < existing->count; n++) { - if (!item_compatible(existing->items + n, &i)) { - log_warning("[%s:%u] Duplicate line for path \"%s\", ignoring.", - fname, line, i.path); - return 0; - } - } - } else { - existing = new0(ItemArray, 1); - r = ordered_hashmap_put(h, i.path, existing); - if (r < 0) - return log_oom(); - } - - if (!GREEDY_REALLOC(existing->items, existing->size, existing->count + 1)) - return log_oom(); - - memcpy(existing->items + existing->count++, &i, sizeof(i)); - - /* Sort item array, to enforce stable ordering of application */ - qsort_safe(existing->items, existing->count, sizeof(Item), item_compare); - - zero(i); - return 0; -} - -static void help(void) { - printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" - "Creates, deletes and cleans up volatile and temporary files and directories.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --create Create marked files/directories\n" - " --clean Clean up marked directories\n" - " --remove Remove marked files/directories\n" - " --boot Execute actions only safe at boot\n" - " --prefix=PATH Only apply rules with the specified prefix\n" - " --exclude-prefix=PATH Ignore rules with the specified prefix\n" - " --root=PATH Operate on an alternate filesystem root\n", - program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_CREATE, - ARG_CLEAN, - ARG_REMOVE, - ARG_BOOT, - ARG_PREFIX, - ARG_EXCLUDE_PREFIX, - ARG_ROOT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "create", no_argument, NULL, ARG_CREATE }, - { "clean", no_argument, NULL, ARG_CLEAN }, - { "remove", no_argument, NULL, ARG_REMOVE }, - { "boot", no_argument, NULL, ARG_BOOT }, - { "prefix", required_argument, NULL, ARG_PREFIX }, - { "exclude-prefix", required_argument, NULL, ARG_EXCLUDE_PREFIX }, - { "root", required_argument, NULL, ARG_ROOT }, - {} - }; - - int c, r; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_CREATE: - arg_create = true; - break; - - case ARG_CLEAN: - arg_clean = true; - break; - - case ARG_REMOVE: - arg_remove = true; - break; - - case ARG_BOOT: - arg_boot = true; - break; - - case ARG_PREFIX: - if (strv_push(&arg_include_prefixes, optarg) < 0) - return log_oom(); - break; - - case ARG_EXCLUDE_PREFIX: - if (strv_push(&arg_exclude_prefixes, optarg) < 0) - return log_oom(); - break; - - case ARG_ROOT: - r = parse_path_argument_and_warn(optarg, true, &arg_root); - if (r < 0) - return r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (!arg_clean && !arg_create && !arg_remove) { - log_error("You need to specify at least one of --clean, --create or --remove."); - return -EINVAL; - } - - return 1; -} - -static int read_config_file(const char *fn, bool ignore_enoent) { - _cleanup_fclose_ FILE *_f = NULL; - FILE *f; - char line[LINE_MAX]; - Iterator iterator; - unsigned v = 0; - Item *i; - int r; - - assert(fn); - - if (streq(fn, "-")) { - log_debug("Reading config from stdin."); - fn = ""; - f = stdin; - } else { - r = search_and_fopen_nulstr(fn, "re", arg_root, conf_file_dirs, &_f); - if (r < 0) { - if (ignore_enoent && r == -ENOENT) { - log_debug_errno(r, "Failed to open \"%s\", ignoring: %m", fn); - return 0; - } - - return log_error_errno(r, "Failed to open '%s': %m", fn); - } - log_debug("Reading config file \"%s\".", fn); - f = _f; - } - - FOREACH_LINE(line, f, break) { - char *l; - int k; - - v++; - - l = strstrip(line); - if (*l == '#' || *l == 0) - continue; - - k = parse_line(fn, v, l); - if (k < 0 && r == 0) - r = k; - } - - /* we have to determine age parameter for each entry of type X */ - ORDERED_HASHMAP_FOREACH(i, globs, iterator) { - Iterator iter; - Item *j, *candidate_item = NULL; - - if (i->type != IGNORE_DIRECTORY_PATH) - continue; - - ORDERED_HASHMAP_FOREACH(j, items, iter) { - if (!IN_SET(j->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA)) - continue; - - if (path_equal(j->path, i->path)) { - candidate_item = j; - break; - } - - if ((!candidate_item && path_startswith(i->path, j->path)) || - (candidate_item && path_startswith(j->path, candidate_item->path) && (fnmatch(i->path, j->path, FNM_PATHNAME | FNM_PERIOD) == 0))) - candidate_item = j; - } - - if (candidate_item && candidate_item->age_set) { - i->age = candidate_item->age; - i->age_set = true; - } - } - - if (ferror(f)) { - log_error_errno(errno, "Failed to read from file %s: %m", fn); - if (r == 0) - r = -EIO; - } - - return r; -} - -int main(int argc, char *argv[]) { - int r, k; - ItemArray *a; - Iterator iterator; - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - mac_selinux_init(); - - items = ordered_hashmap_new(&string_hash_ops); - globs = ordered_hashmap_new(&string_hash_ops); - - if (!items || !globs) { - r = log_oom(); - goto finish; - } - - r = 0; - - if (optind < argc) { - int j; - - for (j = optind; j < argc; j++) { - k = read_config_file(argv[j], false); - if (k < 0 && r == 0) - r = k; - } - - } else { - _cleanup_strv_free_ char **files = NULL; - char **f; - - r = conf_files_list_nulstr(&files, ".conf", arg_root, conf_file_dirs); - if (r < 0) { - log_error_errno(r, "Failed to enumerate tmpfiles.d files: %m"); - goto finish; - } - - STRV_FOREACH(f, files) { - k = read_config_file(*f, true); - if (k < 0 && r == 0) - r = k; - } - } - - /* The non-globbing ones usually create things, hence we apply - * them first */ - ORDERED_HASHMAP_FOREACH(a, items, iterator) { - k = process_item_array(a); - if (k < 0 && r == 0) - r = k; - } - - /* The globbing ones usually alter things, hence we apply them - * second. */ - ORDERED_HASHMAP_FOREACH(a, globs, iterator) { - k = process_item_array(a); - if (k < 0 && r == 0) - r = k; - } - -finish: - while ((a = ordered_hashmap_steal_first(items))) - item_array_free(a); - - while ((a = ordered_hashmap_steal_first(globs))) - item_array_free(a); - - ordered_hashmap_free(items); - ordered_hashmap_free(globs); - - free(arg_include_prefixes); - free(arg_exclude_prefixes); - free(arg_root); - - set_free_free(unix_sockets); - - mac_selinux_finish(); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/tty-ask-password-agent/Makefile b/src/tty-ask-password-agent/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/tty-ask-password-agent/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/tty-ask-password-agent/tty-ask-password-agent.c b/src/tty-ask-password-agent/tty-ask-password-agent.c deleted file mode 100644 index ee879c7b89..0000000000 --- a/src/tty-ask-password-agent/tty-ask-password-agent.c +++ /dev/null @@ -1,680 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "ask-password-api.h" -#include "conf-parser.h" -#include "def.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "io-util.h" -#include "mkdir.h" -#include "path-util.h" -#include "process-util.h" -#include "signal-util.h" -#include "socket-util.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "util.h" -#include "utmp-wtmp.h" - -static enum { - ACTION_LIST, - ACTION_QUERY, - ACTION_WATCH, - ACTION_WALL -} arg_action = ACTION_QUERY; - -static bool arg_plymouth = false; -static bool arg_console = false; - -static int ask_password_plymouth( - const char *message, - usec_t until, - AskPasswordFlags flags, - const char *flag_file, - char ***ret) { - - static const union sockaddr_union sa = PLYMOUTH_SOCKET; - _cleanup_close_ int fd = -1, notify = -1; - _cleanup_free_ char *packet = NULL; - ssize_t k; - int r, n; - struct pollfd pollfd[2] = {}; - char buffer[LINE_MAX]; - size_t p = 0; - enum { - POLL_SOCKET, - POLL_INOTIFY - }; - - assert(ret); - - if (flag_file) { - notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK); - if (notify < 0) - return -errno; - - r = inotify_add_watch(notify, flag_file, IN_ATTRIB); /* for the link count */ - if (r < 0) - return -errno; - } - - fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (fd < 0) - return -errno; - - r = connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); - if (r < 0) - return -errno; - - if (flags & ASK_PASSWORD_ACCEPT_CACHED) { - packet = strdup("c"); - n = 1; - } else if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) - packet = NULL; - if (!packet) - return -ENOMEM; - - r = loop_write(fd, packet, n + 1, true); - if (r < 0) - return r; - - pollfd[POLL_SOCKET].fd = fd; - pollfd[POLL_SOCKET].events = POLLIN; - pollfd[POLL_INOTIFY].fd = notify; - pollfd[POLL_INOTIFY].events = POLLIN; - - for (;;) { - int sleep_for = -1, j; - - if (until > 0) { - usec_t y; - - y = now(CLOCK_MONOTONIC); - - if (y > until) { - r = -ETIME; - goto finish; - } - - sleep_for = (int) ((until - y) / USEC_PER_MSEC); - } - - if (flag_file && access(flag_file, F_OK) < 0) { - r = -errno; - goto finish; - } - - j = poll(pollfd, notify >= 0 ? 2 : 1, sleep_for); - if (j < 0) { - if (errno == EINTR) - continue; - - r = -errno; - goto finish; - } else if (j == 0) { - r = -ETIME; - goto finish; - } - - if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0) - flush_fd(notify); - - if (pollfd[POLL_SOCKET].revents == 0) - continue; - - k = read(fd, buffer + p, sizeof(buffer) - p); - if (k < 0) { - if (errno == EINTR || errno == EAGAIN) - continue; - - r = -errno; - goto finish; - } else if (k == 0) { - r = -EIO; - goto finish; - } - - p += k; - - if (p < 1) - continue; - - if (buffer[0] == 5) { - - if (flags & ASK_PASSWORD_ACCEPT_CACHED) { - /* Hmm, first try with cached - * passwords failed, so let's retry - * with a normal password request */ - packet = mfree(packet); - - if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) { - r = -ENOMEM; - goto finish; - } - - r = loop_write(fd, packet, n+1, true); - if (r < 0) - goto finish; - - flags &= ~ASK_PASSWORD_ACCEPT_CACHED; - p = 0; - continue; - } - - /* No password, because UI not shown */ - r = -ENOENT; - goto finish; - - } else if (buffer[0] == 2 || buffer[0] == 9) { - uint32_t size; - char **l; - - /* One or more answers */ - if (p < 5) - continue; - - memcpy(&size, buffer+1, sizeof(size)); - size = le32toh(size); - if (size + 5 > sizeof(buffer)) { - r = -EIO; - goto finish; - } - - if (p-5 < size) - continue; - - l = strv_parse_nulstr(buffer + 5, size); - if (!l) { - r = -ENOMEM; - goto finish; - } - - *ret = l; - break; - - } else { - /* Unknown packet */ - r = -EIO; - goto finish; - } - } - - r = 0; - -finish: - memory_erase(buffer, sizeof(buffer)); - return r; -} - -static int send_passwords(const char *socket_name, char **passwords) { - _cleanup_free_ char *packet = NULL; - _cleanup_close_ int socket_fd = -1; - union sockaddr_union sa = { .un.sun_family = AF_UNIX }; - size_t packet_length = 1; - char **p, *d; - int r; - - assert(socket_name); - - STRV_FOREACH(p, passwords) - packet_length += strlen(*p) + 1; - - packet = new(char, packet_length); - if (!packet) - return -ENOMEM; - - packet[0] = '+'; - - d = packet + 1; - STRV_FOREACH(p, passwords) - d = stpcpy(d, *p) + 1; - - socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); - if (socket_fd < 0) { - r = log_debug_errno(errno, "socket(): %m"); - goto finish; - } - - strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path)); - - r = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, SOCKADDR_UN_LEN(sa.un)); - if (r < 0) - r = log_debug_errno(errno, "sendto(): %m"); - -finish: - memory_erase(packet, packet_length); - return r; -} - -static int parse_password(const char *filename, char **wall) { - _cleanup_free_ char *socket_name = NULL, *message = NULL; - bool accept_cached = false, echo = false; - uint64_t not_after = 0; - unsigned pid = 0; - - const ConfigTableItem items[] = { - { "Ask", "Socket", config_parse_string, 0, &socket_name }, - { "Ask", "NotAfter", config_parse_uint64, 0, ¬_after }, - { "Ask", "Message", config_parse_string, 0, &message }, - { "Ask", "PID", config_parse_unsigned, 0, &pid }, - { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached }, - { "Ask", "Echo", config_parse_bool, 0, &echo }, - {} - }; - - int r; - - assert(filename); - - r = config_parse(NULL, filename, NULL, - NULL, - config_item_table_lookup, items, - true, false, true, NULL); - if (r < 0) - return r; - - if (!socket_name) { - log_error("Invalid password file %s", filename); - return -EBADMSG; - } - - if (not_after > 0 && now(CLOCK_MONOTONIC) > not_after) - return 0; - - if (pid > 0 && !pid_is_alive(pid)) - return 0; - - if (arg_action == ACTION_LIST) - printf("'%s' (PID %u)\n", message, pid); - - else if (arg_action == ACTION_WALL) { - char *_wall; - - if (asprintf(&_wall, - "%s%sPassword entry required for \'%s\' (PID %u).\r\n" - "Please enter password with the systemd-tty-ask-password-agent tool!", - strempty(*wall), - *wall ? "\r\n\r\n" : "", - message, - pid) < 0) - return log_oom(); - - free(*wall); - *wall = _wall; - - } else { - _cleanup_strv_free_erase_ char **passwords = NULL; - - assert(arg_action == ACTION_QUERY || - arg_action == ACTION_WATCH); - - if (access(socket_name, W_OK) < 0) { - if (arg_action == ACTION_QUERY) - log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid); - - return 0; - } - - if (arg_plymouth) - r = ask_password_plymouth(message, not_after, accept_cached ? ASK_PASSWORD_ACCEPT_CACHED : 0, filename, &passwords); - else { - char *password = NULL; - int tty_fd = -1; - - if (arg_console) { - tty_fd = acquire_terminal("/dev/console", false, false, false, USEC_INFINITY); - if (tty_fd < 0) - return log_error_errno(tty_fd, "Failed to acquire /dev/console: %m"); - - r = reset_terminal_fd(tty_fd, true); - if (r < 0) - log_warning_errno(r, "Failed to reset terminal, ignoring: %m"); - } - - r = ask_password_tty(message, NULL, not_after, echo ? ASK_PASSWORD_ECHO : 0, filename, &password); - - if (arg_console) { - tty_fd = safe_close(tty_fd); - release_terminal(); - } - - if (r >= 0) - r = strv_push(&passwords, password); - - if (r < 0) - string_free_erase(password); - } - - /* If the query went away, that's OK */ - if (IN_SET(r, -ETIME, -ENOENT)) - return 0; - - if (r < 0) - return log_error_errno(r, "Failed to query password: %m"); - - r = send_passwords(socket_name, passwords); - if (r < 0) - return log_error_errno(r, "Failed to send: %m"); - } - - return 0; -} - -static int wall_tty_block(void) { - _cleanup_free_ char *p = NULL; - dev_t devnr; - int fd, r; - - r = get_ctty_devnr(0, &devnr); - if (r == -ENXIO) /* We have no controlling tty */ - return -ENOTTY; - if (r < 0) - return log_error_errno(r, "Failed to get controlling TTY: %m"); - - if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0) - return log_oom(); - - mkdir_parents_label(p, 0700); - mkfifo(p, 0600); - - fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); - if (fd < 0) - return log_debug_errno(errno, "Failed to open %s: %m", p); - - return fd; -} - -static bool wall_tty_match(const char *path, void *userdata) { - _cleanup_free_ char *p = NULL; - _cleanup_close_ int fd = -1; - struct stat st; - - if (!path_is_absolute(path)) - path = strjoina("/dev/", path); - - if (lstat(path, &st) < 0) { - log_debug_errno(errno, "Failed to stat %s: %m", path); - return true; - } - - if (!S_ISCHR(st.st_mode)) { - log_debug("%s is not a character device.", path); - return true; - } - - /* We use named pipes to ensure that wall messages suggesting - * password entry are not printed over password prompts - * already shown. We use the fact here that opening a pipe in - * non-blocking mode for write-only will succeed only if - * there's some writer behind it. Using pipes has the - * advantage that the block will automatically go away if the - * process dies. */ - - if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0) { - log_oom(); - return true; - } - - fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); - if (fd < 0) { - log_debug_errno(errno, "Failed top open the wall pipe: %m"); - return 1; - } - - /* What, we managed to open the pipe? Then this tty is filtered. */ - return 0; -} - -static int show_passwords(void) { - _cleanup_closedir_ DIR *d; - struct dirent *de; - int r = 0; - - d = opendir("/run/systemd/ask-password"); - if (!d) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open /run/systemd/ask-password: %m"); - } - - FOREACH_DIRENT_ALL(de, d, return log_error_errno(errno, "Failed to read directory: %m")) { - _cleanup_free_ char *p = NULL, *wall = NULL; - int q; - - /* We only support /dev on tmpfs, hence we can rely on - * d_type to be reliable */ - - if (de->d_type != DT_REG) - continue; - - if (hidden_or_backup_file(de->d_name)) - continue; - - if (!startswith(de->d_name, "ask.")) - continue; - - p = strappend("/run/systemd/ask-password/", de->d_name); - if (!p) - return log_oom(); - - q = parse_password(p, &wall); - if (q < 0 && r == 0) - r = q; - - if (wall) - (void) utmp_wall(wall, NULL, NULL, wall_tty_match, NULL); - } - - return r; -} - -static int watch_passwords(void) { - enum { - FD_INOTIFY, - FD_SIGNAL, - _FD_MAX - }; - - _cleanup_close_ int notify = -1, signal_fd = -1, tty_block_fd = -1; - struct pollfd pollfd[_FD_MAX] = {}; - sigset_t mask; - int r; - - tty_block_fd = wall_tty_block(); - - (void) mkdir_p_label("/run/systemd/ask-password", 0755); - - notify = inotify_init1(IN_CLOEXEC); - if (notify < 0) - return log_error_errno(errno, "Failed to allocate directory watch: %m"); - - if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) - return log_error_errno(errno, "Failed to add /run/systemd/ask-password to directory watch: %m"); - - assert_se(sigemptyset(&mask) >= 0); - assert_se(sigset_add_many(&mask, SIGINT, SIGTERM, -1) >= 0); - assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) >= 0); - - signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); - if (signal_fd < 0) - return log_error_errno(errno, "Failed to allocate signal file descriptor: %m"); - - pollfd[FD_INOTIFY].fd = notify; - pollfd[FD_INOTIFY].events = POLLIN; - pollfd[FD_SIGNAL].fd = signal_fd; - pollfd[FD_SIGNAL].events = POLLIN; - - for (;;) { - r = show_passwords(); - if (r < 0) - log_error_errno(r, "Failed to show password: %m"); - - if (poll(pollfd, _FD_MAX, -1) < 0) { - if (errno == EINTR) - continue; - - return -errno; - } - - if (pollfd[FD_INOTIFY].revents != 0) - (void) flush_fd(notify); - - if (pollfd[FD_SIGNAL].revents != 0) - break; - } - - return 0; -} - -static void help(void) { - printf("%s [OPTIONS...]\n\n" - "Process system password requests.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --list Show pending password requests\n" - " --query Process pending password requests\n" - " --watch Continuously process password requests\n" - " --wall Continuously forward password requests to wall\n" - " --plymouth Ask question with Plymouth instead of on TTY\n" - " --console Ask question on /dev/console instead of current TTY\n", - program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_LIST = 0x100, - ARG_QUERY, - ARG_WATCH, - ARG_WALL, - ARG_PLYMOUTH, - ARG_CONSOLE, - ARG_VERSION - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "list", no_argument, NULL, ARG_LIST }, - { "query", no_argument, NULL, ARG_QUERY }, - { "watch", no_argument, NULL, ARG_WATCH }, - { "wall", no_argument, NULL, ARG_WALL }, - { "plymouth", no_argument, NULL, ARG_PLYMOUTH }, - { "console", no_argument, NULL, ARG_CONSOLE }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_LIST: - arg_action = ACTION_LIST; - break; - - case ARG_QUERY: - arg_action = ACTION_QUERY; - break; - - case ARG_WATCH: - arg_action = ACTION_WATCH; - break; - - case ARG_WALL: - arg_action = ACTION_WALL; - break; - - case ARG_PLYMOUTH: - arg_plymouth = true; - break; - - case ARG_CONSOLE: - arg_console = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (optind != argc) { - log_error("%s takes no arguments.", program_invocation_short_name); - return -EINVAL; - } - - return 1; -} - -int main(int argc, char *argv[]) { - int r; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (arg_console) { - (void) setsid(); - (void) release_terminal(); - } - - if (IN_SET(arg_action, ACTION_WATCH, ACTION_WALL)) - r = watch_passwords(); - else - r = show_passwords(); - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/udev/Makefile b/src/udev/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/udev/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/udev/Makefile b/src/udev/Makefile new file mode 100644 index 0000000000..1c8fbe52a6 --- /dev/null +++ b/src/udev/Makefile @@ -0,0 +1,181 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +INSTALL_DIRS += \ + $(sysconfdir)/udev/rules.d + +dist_network_DATA = \ + network/99-default.link \ + network/80-container-host0.network \ + network/80-container-ve.network \ + network/80-container-vz.network + +dist_udevrules_DATA += \ + rules/50-udev-default.rules \ + rules/60-block.rules \ + rules/60-drm.rules \ + rules/60-evdev.rules \ + rules/60-persistent-storage-tape.rules \ + rules/60-persistent-input.rules \ + rules/60-persistent-alsa.rules \ + rules/60-persistent-storage.rules \ + rules/60-serial.rules \ + rules/64-btrfs.rules \ + rules/70-mouse.rules \ + rules/75-net-description.rules \ + rules/78-sound-card.rules \ + rules/80-net-setup-link.rules + +nodist_udevrules_DATA += \ + rules/99-systemd.rules + +udevconfdir = $(sysconfdir)/udev +dist_udevconf_DATA = \ + src/udev/udev.conf + +pkgconfigdata_DATA += \ + src/udev/udev.pc + +EXTRA_DIST += \ + rules/99-systemd.rules.in \ + src/udev/udev.pc.in + +EXTRA_DIST += \ + units/systemd-udevd.service.in \ + units/systemd-udev-trigger.service.in \ + units/systemd-udev-settle.service.in + +SOCKETS_TARGET_WANTS += \ + systemd-udevd-control.socket \ + systemd-udevd-kernel.socket + +SYSINIT_TARGET_WANTS += \ + systemd-udevd.service \ + systemd-udev-trigger.service + +bin_PROGRAMS += \ + udevadm + +libexec_PROGRAMS += \ + systemd-udevd + +noinst_LTLIBRARIES += \ + libudev-core.la + +$(outdir)/keyboard-keys-list.txt: + $(AM_V_GEN)$(CPP) $(ALL_CPPFLAGS) -dM -include linux/input.h - < /dev/null | $(AWK) '/^#define[ \t]+KEY_[^ ]+[ \t]+[0-9K]/ { if ($$2 != "KEY_MAX") { print $$2 } }' > $@ + +$(outdir)/keyboard-keys-from-name.gperf: $(outdir)/keyboard-keys-list.txt + $(AM_V_GEN)$(AWK) 'BEGIN{ print "struct key { const char* name; unsigned short id; };"; print "%null-strings"; print "%%";} { print tolower(substr($$1 ,5)) ", " $$1 }' < $< > $@ + +$(outdir)/keyboard-keys-from-name.h: $(outdir)/keyboard-keys-from-name.gperf + $(AM_V_GPERF)$(GPERF) -L ANSI-C -t -N keyboard_lookup_key -H hash_key_name -p -C < $< > $@ + +gperf_txt_sources += \ + src/udev/keyboard-keys-list.txt + +libudev_core_la_SOURCES = \ + src/udev/udev.h \ + src/udev/udev-event.c \ + src/udev/udev-watch.c \ + src/udev/udev-node.c \ + src/udev/udev-rules.c \ + src/udev/udev-ctrl.c \ + src/udev/udev-builtin.c \ + src/udev/udev-builtin-btrfs.c \ + src/udev/udev-builtin-hwdb.c \ + src/udev/udev-builtin-input_id.c \ + src/udev/udev-builtin-keyboard.c \ + src/udev/udev-builtin-net_id.c \ + src/udev/udev-builtin-net_setup_link.c \ + src/udev/udev-builtin-path_id.c \ + src/udev/udev-builtin-usb_id.c \ + src/udev/net/link-config.h \ + src/udev/net/link-config.c \ + src/udev/net/ethtool-util.h \ + src/udev/net/ethtool-util.c + +nodist_libudev_core_la_SOURCES = \ + src/udev/keyboard-keys-from-name.h \ + src/udev/net/link-config-gperf.c + +gperf_gperf_sources += \ + src/udev/net/link-config-gperf.gperf + +libudev_core_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(BLKID_CFLAGS) \ + $(KMOD_CFLAGS) + +libudev_core_la_LIBADD = \ + libsystemd-network.la \ + libshared.la \ + $(BLKID_LIBS) \ + $(KMOD_LIBS) + +ifneq ($(HAVE_KMOD),) +libudev_core_la_SOURCES += \ + src/udev/udev-builtin-kmod.c + +dist_udevrules_DATA += \ + rules/80-drivers.rules +endif # HAVE_KMOD + +ifneq ($(HAVE_BLKID),) +libudev_core_la_SOURCES += \ + src/udev/udev-builtin-blkid.c +endif # HAVE_BLKID + +ifneq ($(HAVE_ACL),) +libudev_core_la_SOURCES += \ + src/udev/udev-builtin-uaccess.c \ + src/login/logind-acl.c \ + src/libsystemd/sd-login/sd-login.c \ + src/systemd/sd-login.h +endif # HAVE_ACL + +systemd_udevd_SOURCES = \ + src/udev/udevd.c + +systemd_udevd_LDADD = \ + libudev-core.la + +udevadm_SOURCES = \ + src/udev/udevadm.c \ + src/udev/udevadm-info.c \ + src/udev/udevadm-control.c \ + src/udev/udevadm-monitor.c \ + src/udev/udevadm-hwdb.c \ + src/udev/udevadm-settle.c \ + src/udev/udevadm-trigger.c \ + src/udev/udevadm-test.c \ + src/udev/udevadm-test-builtin.c \ + src/udev/udevadm-util.c \ + src/udev/udevadm-util.h + +udevadm_LDADD = \ + libudev-core.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/udev/ata_id/Makefile b/src/udev/ata_id/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/udev/ata_id/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/udev/ata_id/Makefile b/src/udev/ata_id/Makefile new file mode 100644 index 0000000000..00a8c37ac2 --- /dev/null +++ b/src/udev/ata_id/Makefile @@ -0,0 +1,35 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ata_id_SOURCES = \ + src/udev/ata_id/ata_id.c + +ata_id_LDADD = \ + libshared.la + +udevlibexec_PROGRAMS += \ + ata_id + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/udev/cdrom_id/Makefile b/src/udev/cdrom_id/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/udev/cdrom_id/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/udev/cdrom_id/Makefile b/src/udev/cdrom_id/Makefile new file mode 100644 index 0000000000..a9297413d3 --- /dev/null +++ b/src/udev/cdrom_id/Makefile @@ -0,0 +1,38 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +cdrom_id_SOURCES = \ + src/udev/cdrom_id/cdrom_id.c + +cdrom_id_LDADD = \ + libshared.la + +udevlibexec_PROGRAMS += \ + cdrom_id + +dist_udevrules_DATA += \ + rules/60-cdrom_id.rules + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/udev/collect/Makefile b/src/udev/collect/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/udev/collect/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/udev/collect/Makefile b/src/udev/collect/Makefile new file mode 100644 index 0000000000..60af3b7627 --- /dev/null +++ b/src/udev/collect/Makefile @@ -0,0 +1,35 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +collect_SOURCES = \ + src/udev/collect/collect.c + +collect_LDADD = \ + libshared.la + +udevlibexec_PROGRAMS += \ + collect + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/udev/mtd_probe/Makefile b/src/udev/mtd_probe/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/udev/mtd_probe/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/udev/mtd_probe/Makefile b/src/udev/mtd_probe/Makefile new file mode 100644 index 0000000000..d7392a8a3b --- /dev/null +++ b/src/udev/mtd_probe/Makefile @@ -0,0 +1,37 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +mtd_probe_SOURCES = \ + src/udev/mtd_probe/mtd_probe.c \ + src/udev/mtd_probe/mtd_probe.h \ + src/udev/mtd_probe/probe_smartmedia.c + +dist_udevrules_DATA += \ + rules/75-probe_mtd.rules + +udevlibexec_PROGRAMS += \ + mtd_probe + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/udev/net/Makefile b/src/udev/net/Makefile deleted file mode 120000 index 94aaae2c4d..0000000000 --- a/src/udev/net/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../Makefile \ No newline at end of file diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c index c66504102f..350cd24e9c 100644 --- a/src/udev/net/link-config.c +++ b/src/udev/net/link-config.c @@ -19,7 +19,7 @@ #include -#include "sd-netlink.h" +#include #include "alloc-util.h" #include "conf-files.h" diff --git a/src/udev/scsi_id/Makefile b/src/udev/scsi_id/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/udev/scsi_id/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/udev/scsi_id/Makefile b/src/udev/scsi_id/Makefile new file mode 100644 index 0000000000..7064a864f7 --- /dev/null +++ b/src/udev/scsi_id/Makefile @@ -0,0 +1,41 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +scsi_id_SOURCES =\ + src/udev/scsi_id/scsi_id.c \ + src/udev/scsi_id/scsi_serial.c \ + src/udev/scsi_id/scsi.h \ + src/udev/scsi_id/scsi_id.h + +scsi_id_LDADD = \ + libshared.la + +udevlibexec_PROGRAMS += \ + scsi_id + +EXTRA_DIST += \ + src/udev/scsi_id/README + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c index ed0ea5ce5f..62cd93264b 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -27,7 +27,7 @@ #include #include -#include "sd-id128.h" +#include #include "alloc-util.h" #include "efivars.h" diff --git a/src/udev/udev-builtin-hwdb.c b/src/udev/udev-builtin-hwdb.c index f4a065a97d..b96f39ba20 100644 --- a/src/udev/udev-builtin-hwdb.c +++ b/src/udev/udev-builtin-hwdb.c @@ -22,7 +22,7 @@ #include #include -#include "sd-hwdb.h" +#include #include "alloc-util.h" #include "hwdb-util.h" diff --git a/src/udev/udev-builtin-uaccess.c b/src/udev/udev-builtin-uaccess.c index 3ebe36f043..2c27116ae9 100644 --- a/src/udev/udev-builtin-uaccess.c +++ b/src/udev/udev-builtin-uaccess.c @@ -22,7 +22,7 @@ #include #include -#include "sd-login.h" +#include #include "login-util.h" #include "logind-acl.h" diff --git a/src/udev/udev.h b/src/udev/udev.h deleted file mode 100644 index 8433e8d9f2..0000000000 --- a/src/udev/udev.h +++ /dev/null @@ -1,216 +0,0 @@ -#pragma once - -/* - * Copyright (C) 2003 Greg Kroah-Hartman - * Copyright (C) 2003-2010 Kay Sievers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include - -#include "libudev.h" -#include "sd-netlink.h" - -#include "label.h" -#include "libudev-private.h" -#include "macro.h" -#include "strv.h" -#include "util.h" - -struct udev_event { - struct udev *udev; - struct udev_device *dev; - struct udev_device *dev_parent; - struct udev_device *dev_db; - char *name; - char *program_result; - mode_t mode; - uid_t uid; - gid_t gid; - struct udev_list seclabel_list; - struct udev_list run_list; - int exec_delay; - usec_t birth_usec; - sd_netlink *rtnl; - unsigned int builtin_run; - unsigned int builtin_ret; - bool inotify_watch; - bool inotify_watch_final; - bool group_set; - bool group_final; - bool owner_set; - bool owner_final; - bool mode_set; - bool mode_final; - bool name_final; - bool devlink_final; - bool run_final; -}; - -struct udev_watch { - struct udev_list_node node; - int handle; - char *name; -}; - -/* udev-rules.c */ -struct udev_rules; -struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names); -struct udev_rules *udev_rules_unref(struct udev_rules *rules); -bool udev_rules_check_timestamp(struct udev_rules *rules); -void udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event, - usec_t timeout_usec, usec_t timeout_warn_usec, - struct udev_list *properties_list); -int udev_rules_apply_static_dev_perms(struct udev_rules *rules); - -/* udev-event.c */ -struct udev_event *udev_event_new(struct udev_device *dev); -void udev_event_unref(struct udev_event *event); -size_t udev_event_apply_format(struct udev_event *event, const char *src, char *dest, size_t size); -int udev_event_apply_subsys_kernel(struct udev_event *event, const char *string, - char *result, size_t maxsize, int read_value); -int udev_event_spawn(struct udev_event *event, - usec_t timeout_usec, - usec_t timeout_warn_usec, - bool accept_failure, - const char *cmd, char *result, size_t ressize); -void udev_event_execute_rules(struct udev_event *event, - usec_t timeout_usec, usec_t timeout_warn_usec, - struct udev_list *properties_list, - struct udev_rules *rules); -void udev_event_execute_run(struct udev_event *event, usec_t timeout_usec, usec_t timeout_warn_usec); -int udev_build_argv(struct udev *udev, char *cmd, int *argc, char *argv[]); - -/* udev-watch.c */ -int udev_watch_init(struct udev *udev); -void udev_watch_restore(struct udev *udev); -void udev_watch_begin(struct udev *udev, struct udev_device *dev); -void udev_watch_end(struct udev *udev, struct udev_device *dev); -struct udev_device *udev_watch_lookup(struct udev *udev, int wd); - -/* udev-node.c */ -void udev_node_add(struct udev_device *dev, bool apply, - mode_t mode, uid_t uid, gid_t gid, - struct udev_list *seclabel_list); -void udev_node_remove(struct udev_device *dev); -void udev_node_update_old_links(struct udev_device *dev, struct udev_device *dev_old); - -/* udev-ctrl.c */ -struct udev_ctrl; -struct udev_ctrl *udev_ctrl_new(struct udev *udev); -struct udev_ctrl *udev_ctrl_new_from_fd(struct udev *udev, int fd); -int udev_ctrl_enable_receiving(struct udev_ctrl *uctrl); -struct udev_ctrl *udev_ctrl_unref(struct udev_ctrl *uctrl); -int udev_ctrl_cleanup(struct udev_ctrl *uctrl); -struct udev *udev_ctrl_get_udev(struct udev_ctrl *uctrl); -int udev_ctrl_get_fd(struct udev_ctrl *uctrl); -int udev_ctrl_send_set_log_level(struct udev_ctrl *uctrl, int priority, int timeout); -int udev_ctrl_send_stop_exec_queue(struct udev_ctrl *uctrl, int timeout); -int udev_ctrl_send_start_exec_queue(struct udev_ctrl *uctrl, int timeout); -int udev_ctrl_send_reload(struct udev_ctrl *uctrl, int timeout); -int udev_ctrl_send_ping(struct udev_ctrl *uctrl, int timeout); -int udev_ctrl_send_exit(struct udev_ctrl *uctrl, int timeout); -int udev_ctrl_send_set_env(struct udev_ctrl *uctrl, const char *key, int timeout); -int udev_ctrl_send_set_children_max(struct udev_ctrl *uctrl, int count, int timeout); -struct udev_ctrl_connection; -struct udev_ctrl_connection *udev_ctrl_get_connection(struct udev_ctrl *uctrl); -struct udev_ctrl_connection *udev_ctrl_connection_ref(struct udev_ctrl_connection *conn); -struct udev_ctrl_connection *udev_ctrl_connection_unref(struct udev_ctrl_connection *conn); -struct udev_ctrl_msg; -struct udev_ctrl_msg *udev_ctrl_receive_msg(struct udev_ctrl_connection *conn); -struct udev_ctrl_msg *udev_ctrl_msg_unref(struct udev_ctrl_msg *ctrl_msg); -int udev_ctrl_get_set_log_level(struct udev_ctrl_msg *ctrl_msg); -int udev_ctrl_get_stop_exec_queue(struct udev_ctrl_msg *ctrl_msg); -int udev_ctrl_get_start_exec_queue(struct udev_ctrl_msg *ctrl_msg); -int udev_ctrl_get_reload(struct udev_ctrl_msg *ctrl_msg); -int udev_ctrl_get_ping(struct udev_ctrl_msg *ctrl_msg); -int udev_ctrl_get_exit(struct udev_ctrl_msg *ctrl_msg); -const char *udev_ctrl_get_set_env(struct udev_ctrl_msg *ctrl_msg); -int udev_ctrl_get_set_children_max(struct udev_ctrl_msg *ctrl_msg); - -/* built-in commands */ -enum udev_builtin_cmd { -#ifdef HAVE_BLKID - UDEV_BUILTIN_BLKID, -#endif - UDEV_BUILTIN_BTRFS, - UDEV_BUILTIN_HWDB, - UDEV_BUILTIN_INPUT_ID, - UDEV_BUILTIN_KEYBOARD, -#ifdef HAVE_KMOD - UDEV_BUILTIN_KMOD, -#endif - UDEV_BUILTIN_NET_ID, - UDEV_BUILTIN_NET_LINK, - UDEV_BUILTIN_PATH_ID, - UDEV_BUILTIN_USB_ID, -#ifdef HAVE_ACL - UDEV_BUILTIN_UACCESS, -#endif - UDEV_BUILTIN_MAX -}; -struct udev_builtin { - const char *name; - int (*cmd)(struct udev_device *dev, int argc, char *argv[], bool test); - const char *help; - int (*init)(struct udev *udev); - void (*exit)(struct udev *udev); - bool (*validate)(struct udev *udev); - bool run_once; -}; -#ifdef HAVE_BLKID -extern const struct udev_builtin udev_builtin_blkid; -#endif -extern const struct udev_builtin udev_builtin_btrfs; -extern const struct udev_builtin udev_builtin_hwdb; -extern const struct udev_builtin udev_builtin_input_id; -extern const struct udev_builtin udev_builtin_keyboard; -#ifdef HAVE_KMOD -extern const struct udev_builtin udev_builtin_kmod; -#endif -extern const struct udev_builtin udev_builtin_net_id; -extern const struct udev_builtin udev_builtin_net_setup_link; -extern const struct udev_builtin udev_builtin_path_id; -extern const struct udev_builtin udev_builtin_usb_id; -extern const struct udev_builtin udev_builtin_uaccess; -void udev_builtin_init(struct udev *udev); -void udev_builtin_exit(struct udev *udev); -enum udev_builtin_cmd udev_builtin_lookup(const char *command); -const char *udev_builtin_name(enum udev_builtin_cmd cmd); -bool udev_builtin_run_once(enum udev_builtin_cmd cmd); -int udev_builtin_run(struct udev_device *dev, enum udev_builtin_cmd cmd, const char *command, bool test); -void udev_builtin_list(struct udev *udev); -bool udev_builtin_validate(struct udev *udev); -int udev_builtin_add_property(struct udev_device *dev, bool test, const char *key, const char *val); -int udev_builtin_hwdb_lookup(struct udev_device *dev, const char *prefix, const char *modalias, - const char *filter, bool test); - -/* udevadm commands */ -struct udevadm_cmd { - const char *name; - int (*cmd)(struct udev *udev, int argc, char *argv[]); - const char *help; - int debug; -}; -extern const struct udevadm_cmd udevadm_info; -extern const struct udevadm_cmd udevadm_trigger; -extern const struct udevadm_cmd udevadm_settle; -extern const struct udevadm_cmd udevadm_control; -extern const struct udevadm_cmd udevadm_monitor; -extern const struct udevadm_cmd udevadm_hwdb; -extern const struct udevadm_cmd udevadm_test; -extern const struct udevadm_cmd udevadm_test_builtin; diff --git a/src/udev/udevd.c b/src/udev/udevd.c index e9dd2f47c7..89006e6e3a 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -40,8 +40,8 @@ #include #include -#include "sd-daemon.h" -#include "sd-event.h" +#include +#include #include "alloc-util.h" #include "cgroup-util.h" diff --git a/src/udev/v4l_id/Makefile b/src/udev/v4l_id/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/udev/v4l_id/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/udev/v4l_id/Makefile b/src/udev/v4l_id/Makefile new file mode 100644 index 0000000000..0641af8065 --- /dev/null +++ b/src/udev/v4l_id/Makefile @@ -0,0 +1,38 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +v4l_id_SOURCES = \ + src/udev/v4l_id/v4l_id.c + +v4l_id_LDADD = \ + libshared.la + +udevlibexec_PROGRAMS += \ + v4l_id + +dist_udevrules_DATA += \ + rules/60-persistent-v4l.rules + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/update-done/Makefile b/src/update-done/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/update-done/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c deleted file mode 100644 index da306a4444..0000000000 --- a/src/update-done/update-done.c +++ /dev/null @@ -1,115 +0,0 @@ -/*** - 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 "fd-util.h" -#include "io-util.h" -#include "selinux-util.h" -#include "util.h" - -#define MESSAGE \ - "This file was created by systemd-update-done. Its only \n" \ - "purpose is to hold a timestamp of the time this directory\n" \ - "was updated. See systemd-update-done.service(8).\n" - -static int apply_timestamp(const char *path, struct timespec *ts) { - struct timespec twice[2] = { - *ts, - *ts - }; - struct stat st; - - assert(path); - assert(ts); - - if (stat(path, &st) >= 0) { - /* Is the timestamp file already newer than the OS? If - * so, there's nothing to do. We ignore the nanosecond - * component of the timestamp, since some file systems - * do not support any better accuracy than 1s and we - * have no way to identify the accuracy - * available. Most notably ext4 on small disks (where - * 128 byte inodes are used) does not support better - * accuracy than 1s. */ - if (st.st_mtim.tv_sec > ts->tv_sec) - return 0; - - /* It is older? Then let's update it */ - if (utimensat(AT_FDCWD, path, twice, AT_SYMLINK_NOFOLLOW) < 0) { - - if (errno == EROFS) - return log_debug("Can't update timestamp file %s, file system is read-only.", path); - - return log_error_errno(errno, "Failed to update timestamp on %s: %m", path); - } - - } else if (errno == ENOENT) { - _cleanup_close_ int fd = -1; - int r; - - /* The timestamp file doesn't exist yet? Then let's create it. */ - - r = mac_selinux_create_file_prepare(path, S_IFREG); - if (r < 0) - return log_error_errno(r, "Failed to set SELinux context for %s: %m", path); - - fd = open(path, O_CREAT|O_EXCL|O_WRONLY|O_TRUNC|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0644); - mac_selinux_create_file_clear(); - - if (fd < 0) { - if (errno == EROFS) - return log_debug("Can't create timestamp file %s, file system is read-only.", path); - - return log_error_errno(errno, "Failed to create timestamp file %s: %m", path); - } - - (void) loop_write(fd, MESSAGE, strlen(MESSAGE), false); - - if (futimens(fd, twice) < 0) - return log_error_errno(errno, "Failed to update timestamp on %s: %m", path); - } else - log_error_errno(errno, "Failed to stat() timestamp file %s: %m", path); - - return 0; -} - -int main(int argc, char *argv[]) { - struct stat st; - int r, q = 0; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - if (stat("/usr", &st) < 0) { - log_error_errno(errno, "Failed to stat /usr: %m"); - return EXIT_FAILURE; - } - - r = mac_selinux_init(); - if (r < 0) { - log_error_errno(r, "SELinux setup failed: %m"); - goto finish; - } - - r = apply_timestamp("/etc/.updated", &st.st_mtim); - q = apply_timestamp("/var/.updated", &st.st_mtim); - -finish: - return r < 0 || q < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/update-utmp/Makefile b/src/update-utmp/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/update-utmp/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/update-utmp/update-utmp.c b/src/update-utmp/update-utmp.c deleted file mode 100644 index 8ae4a8a833..0000000000 --- a/src/update-utmp/update-utmp.c +++ /dev/null @@ -1,283 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include - -#ifdef HAVE_AUDIT -#include -#endif - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "formats-util.h" -#include "log.h" -#include "macro.h" -#include "special.h" -#include "unit-name.h" -#include "util.h" -#include "utmp-wtmp.h" - -typedef struct Context { - sd_bus *bus; -#ifdef HAVE_AUDIT - int audit_fd; -#endif -} Context; - -static usec_t get_startup_time(Context *c) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - usec_t t = 0; - int r; - - assert(c); - - r = sd_bus_get_property_trivial( - c->bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "UserspaceTimestamp", - &error, - 't', &t); - if (r < 0) { - log_error_errno(r, "Failed to get timestamp: %s", bus_error_message(&error, r)); - return 0; - } - - return t; -} - -static int get_current_runlevel(Context *c) { - static const struct { - const int runlevel; - const char *special; - } table[] = { - /* The first target of this list that is active or has - * a job scheduled wins. We prefer runlevels 5 and 3 - * here over the others, since these are the main - * runlevels used on Fedora. It might make sense to - * change the order on some distributions. */ - { '5', SPECIAL_GRAPHICAL_TARGET }, - { '3', SPECIAL_MULTI_USER_TARGET }, - { '1', SPECIAL_RESCUE_TARGET }, - }; - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - unsigned i; - - assert(c); - - for (i = 0; i < ELEMENTSOF(table); i++) { - _cleanup_free_ char *state = NULL, *path = NULL; - - path = unit_dbus_path_from_name(table[i].special); - if (!path) - return log_oom(); - - r = sd_bus_get_property_string( - c->bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "ActiveState", - &error, - &state); - if (r < 0) - return log_warning_errno(r, "Failed to get state: %s", bus_error_message(&error, r)); - - if (streq(state, "active") || streq(state, "reloading")) - return table[i].runlevel; - } - - return 0; -} - -static int on_reboot(Context *c) { - int r = 0, q; - usec_t t; - - assert(c); - - /* We finished start-up, so let's write the utmp - * record and send the audit msg */ - -#ifdef HAVE_AUDIT - if (c->audit_fd >= 0) - if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_BOOT, "", "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 && - errno != EPERM) { - r = log_error_errno(errno, "Failed to send audit message: %m"); - } -#endif - - /* If this call fails it will return 0, which - * utmp_put_reboot() will then fix to the current time */ - t = get_startup_time(c); - - q = utmp_put_reboot(t); - if (q < 0) { - log_error_errno(q, "Failed to write utmp record: %m"); - r = q; - } - - return r; -} - -static int on_shutdown(Context *c) { - int r = 0, q; - - assert(c); - - /* We started shut-down, so let's write the utmp - * record and send the audit msg */ - -#ifdef HAVE_AUDIT - if (c->audit_fd >= 0) - if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_SHUTDOWN, "", "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 && - errno != EPERM) { - r = log_error_errno(errno, "Failed to send audit message: %m"); - } -#endif - - q = utmp_put_shutdown(); - if (q < 0) { - log_error_errno(q, "Failed to write utmp record: %m"); - r = q; - } - - return r; -} - -static int on_runlevel(Context *c) { - int r = 0, q, previous, runlevel; - - assert(c); - - /* We finished changing runlevel, so let's write the - * utmp record and send the audit msg */ - - /* First, get last runlevel */ - q = utmp_get_runlevel(&previous, NULL); - - if (q < 0) { - if (q != -ESRCH && q != -ENOENT) - return log_error_errno(q, "Failed to get current runlevel: %m"); - - previous = 0; - } - - /* Secondly, get new runlevel */ - runlevel = get_current_runlevel(c); - - if (runlevel < 0) - return runlevel; - - if (previous == runlevel) - return 0; - -#ifdef HAVE_AUDIT - if (c->audit_fd >= 0) { - _cleanup_free_ char *s = NULL; - - if (asprintf(&s, "old-level=%c new-level=%c", - previous > 0 ? previous : 'N', - runlevel > 0 ? runlevel : 'N') < 0) - return log_oom(); - - if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_RUNLEVEL, s, "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 && errno != EPERM) - r = log_error_errno(errno, "Failed to send audit message: %m"); - } -#endif - - q = utmp_put_runlevel(runlevel, previous); - if (q < 0 && q != -ESRCH && q != -ENOENT) { - log_error_errno(q, "Failed to write utmp record: %m"); - r = q; - } - - return r; -} - -int main(int argc, char *argv[]) { - Context c = { -#ifdef HAVE_AUDIT - .audit_fd = -1 -#endif - }; - int r; - - if (getppid() != 1) { - log_error("This program should be invoked by init only."); - return EXIT_FAILURE; - } - - if (argc != 2) { - log_error("This program requires one argument."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - -#ifdef HAVE_AUDIT - /* If the kernel lacks netlink or audit support, - * don't worry about it. */ - c.audit_fd = audit_open(); - if (c.audit_fd < 0 && errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT) - log_error_errno(errno, "Failed to connect to audit log: %m"); -#endif - r = bus_connect_system_systemd(&c.bus); - if (r < 0) { - log_error_errno(r, "Failed to get D-Bus connection: %m"); - r = -EIO; - goto finish; - } - - log_debug("systemd-update-utmp running as pid "PID_FMT, getpid()); - - if (streq(argv[1], "reboot")) - r = on_reboot(&c); - else if (streq(argv[1], "shutdown")) - r = on_shutdown(&c); - else if (streq(argv[1], "runlevel")) - r = on_runlevel(&c); - else { - log_error("Unknown command %s", argv[1]); - r = -EINVAL; - } - - log_debug("systemd-update-utmp stopped as pid "PID_FMT, getpid()); - -finish: -#ifdef HAVE_AUDIT - if (c.audit_fd >= 0) - audit_close(c.audit_fd); -#endif - - sd_bus_flush_close_unref(c.bus); - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/user-sessions/Makefile b/src/user-sessions/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/user-sessions/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/user-sessions/user-sessions.c b/src/user-sessions/user-sessions.c deleted file mode 100644 index 9b29b5ba1d..0000000000 --- a/src/user-sessions/user-sessions.c +++ /dev/null @@ -1,84 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 - -#include "fileio.h" -#include "fileio-label.h" -#include "log.h" -#include "selinux-util.h" -#include "string-util.h" -#include "util.h" - -int main(int argc, char*argv[]) { - - if (argc != 2) { - log_error("This program requires one argument."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - mac_selinux_init(); - - if (streq(argv[1], "start")) { - int r = 0; - - if (unlink("/run/nologin") < 0 && errno != ENOENT) - r = log_error_errno(errno, - "Failed to remove /run/nologin file: %m"); - - if (unlink("/etc/nologin") < 0 && errno != ENOENT) { - /* If the file doesn't exist and /etc simply - * was read-only (in which case unlink() - * returns EROFS even if the file doesn't - * exist), don't complain */ - - if (errno != EROFS || access("/etc/nologin", F_OK) >= 0) { - log_error_errno(errno, "Failed to remove /etc/nologin file: %m"); - return EXIT_FAILURE; - } - } - - if (r < 0) - return EXIT_FAILURE; - - } else if (streq(argv[1], "stop")) { - int r; - - r = write_string_file_atomic_label("/run/nologin", "System is going down."); - if (r < 0) { - log_error_errno(r, "Failed to create /run/nologin: %m"); - return EXIT_FAILURE; - } - - } else { - log_error("Unknown verb %s.", argv[1]); - return EXIT_FAILURE; - } - - mac_selinux_finish(); - - return EXIT_SUCCESS; -} diff --git a/src/vconsole/.gitignore b/src/vconsole/.gitignore deleted file mode 100644 index 82741b2fb3..0000000000 --- a/src/vconsole/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/90-vconsole.rules diff --git a/src/vconsole/90-vconsole.rules.in b/src/vconsole/90-vconsole.rules.in deleted file mode 100644 index 35b9ad5151..0000000000 --- a/src/vconsole/90-vconsole.rules.in +++ /dev/null @@ -1,10 +0,0 @@ -# This file is part of systemd. -# -# 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. - -# Each vtcon keeps its own state of fonts. -# -ACTION=="add", SUBSYSTEM=="vtconsole", KERNEL=="vtcon*", RUN+="@rootlibexecdir@/systemd-vconsole-setup" diff --git a/src/vconsole/Makefile b/src/vconsole/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/vconsole/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c deleted file mode 100644 index 1118118450..0000000000 --- a/src/vconsole/vconsole-setup.c +++ /dev/null @@ -1,332 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "io-util.h" -#include "locale-util.h" -#include "log.h" -#include "process-util.h" -#include "signal-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "terminal-util.h" -#include "util.h" -#include "virt.h" - -static bool is_vconsole(int fd) { - unsigned char data[1]; - - data[0] = TIOCL_GETFGCONSOLE; - return ioctl(fd, TIOCLINUX, data) >= 0; -} - -static int disable_utf8(int fd) { - int r = 0, k; - - if (ioctl(fd, KDSKBMODE, K_XLATE) < 0) - r = -errno; - - k = loop_write(fd, "\033%@", 3, false); - if (k < 0) - r = k; - - k = write_string_file("/sys/module/vt/parameters/default_utf8", "0", 0); - if (k < 0) - r = k; - - if (r < 0) - log_warning_errno(r, "Failed to disable UTF-8: %m"); - - return r; -} - -static int enable_utf8(int fd) { - int r = 0, k; - long current = 0; - - if (ioctl(fd, KDGKBMODE, ¤t) < 0 || current == K_XLATE) { - /* - * Change the current keyboard to unicode, unless it - * is currently in raw or off mode anyway. We - * shouldn't interfere with X11's processing of the - * key events. - * - * http://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html - * - */ - - if (ioctl(fd, KDSKBMODE, K_UNICODE) < 0) - r = -errno; - } - - k = loop_write(fd, "\033%G", 3, false); - if (k < 0) - r = k; - - k = write_string_file("/sys/module/vt/parameters/default_utf8", "1", 0); - if (k < 0) - r = k; - - if (r < 0) - log_warning_errno(r, "Failed to enable UTF-8: %m"); - - return r; -} - -static int keyboard_load_and_wait(const char *vc, const char *map, const char *map_toggle, bool utf8) { - const char *args[8]; - int i = 0, r; - pid_t pid; - - /* An empty map means kernel map */ - if (isempty(map)) - return 1; - - args[i++] = KBD_LOADKEYS; - args[i++] = "-q"; - args[i++] = "-C"; - args[i++] = vc; - if (utf8) - args[i++] = "-u"; - args[i++] = map; - if (map_toggle) - args[i++] = map_toggle; - args[i++] = NULL; - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork: %m"); - else if (pid == 0) { - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - execv(args[0], (char **) args); - _exit(EXIT_FAILURE); - } - - r = wait_for_terminate_and_warn(KBD_LOADKEYS, pid, true); - if (r < 0) - return r; - - return r == 0; -} - -static int font_load_and_wait(const char *vc, const char *font, const char *map, const char *unimap) { - const char *args[9]; - int i = 0, r; - pid_t pid; - - /* An empty font means kernel font */ - if (isempty(font)) - return 1; - - args[i++] = KBD_SETFONT; - args[i++] = "-C"; - args[i++] = vc; - args[i++] = font; - if (map) { - args[i++] = "-m"; - args[i++] = map; - } - if (unimap) { - args[i++] = "-u"; - args[i++] = unimap; - } - args[i++] = NULL; - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork: %m"); - else if (pid == 0) { - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - execv(args[0], (char **) args); - _exit(EXIT_FAILURE); - } - - r = wait_for_terminate_and_warn(KBD_SETFONT, pid, true); - if (r < 0) - return r; - - return r == 0; -} - -/* - * A newly allocated VT uses the font from the active VT. Here - * we update all possibly already allocated VTs with the configured - * font. It also allows to restart systemd-vconsole-setup.service, - * to apply a new font to all VTs. - */ -static void font_copy_to_all_vcs(int fd) { - struct vt_stat vcs = {}; - unsigned char map8[E_TABSZ]; - unsigned short map16[E_TABSZ]; - struct unimapdesc unimapd; - _cleanup_free_ struct unipair* unipairs = NULL; - int i, r; - - unipairs = new(struct unipair, USHRT_MAX); - if (!unipairs) { - log_oom(); - return; - } - - /* get active, and 16 bit mask of used VT numbers */ - r = ioctl(fd, VT_GETSTATE, &vcs); - if (r < 0) { - log_debug_errno(errno, "VT_GETSTATE failed, ignoring: %m"); - return; - } - - for (i = 1; i <= 15; i++) { - char vcname[strlen("/dev/vcs") + DECIMAL_STR_MAX(int)]; - _cleanup_close_ int vcfd = -1; - struct console_font_op cfo = {}; - - if (i == vcs.v_active) - continue; - - /* skip non-allocated ttys */ - xsprintf(vcname, "/dev/vcs%i", i); - if (access(vcname, F_OK) < 0) - continue; - - xsprintf(vcname, "/dev/tty%i", i); - vcfd = open_terminal(vcname, O_RDWR|O_CLOEXEC); - if (vcfd < 0) - continue; - - /* copy font from active VT, where the font was uploaded to */ - cfo.op = KD_FONT_OP_COPY; - cfo.height = vcs.v_active-1; /* tty1 == index 0 */ - (void) ioctl(vcfd, KDFONTOP, &cfo); - - /* copy map of 8bit chars */ - if (ioctl(fd, GIO_SCRNMAP, map8) >= 0) - (void) ioctl(vcfd, PIO_SCRNMAP, map8); - - /* copy map of 8bit chars -> 16bit Unicode values */ - if (ioctl(fd, GIO_UNISCRNMAP, map16) >= 0) - (void) ioctl(vcfd, PIO_UNISCRNMAP, map16); - - /* copy unicode translation table */ - /* unimapd is a ushort count and a pointer to an - array of struct unipair { ushort, ushort } */ - unimapd.entries = unipairs; - unimapd.entry_ct = USHRT_MAX; - if (ioctl(fd, GIO_UNIMAP, &unimapd) >= 0) { - struct unimapinit adv = { 0, 0, 0 }; - - (void) ioctl(vcfd, PIO_UNIMAPCLR, &adv); - (void) ioctl(vcfd, PIO_UNIMAP, &unimapd); - } - } -} - -int main(int argc, char **argv) { - const char *vc; - _cleanup_free_ char - *vc_keymap = NULL, *vc_keymap_toggle = NULL, - *vc_font = NULL, *vc_font_map = NULL, *vc_font_unimap = NULL; - _cleanup_close_ int fd = -1; - bool utf8, font_copy = false, font_ok, keyboard_ok; - int r = EXIT_FAILURE; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - if (argv[1]) - vc = argv[1]; - else { - vc = "/dev/tty0"; - font_copy = true; - } - - fd = open_terminal(vc, O_RDWR|O_CLOEXEC); - if (fd < 0) { - log_error_errno(fd, "Failed to open %s: %m", vc); - return EXIT_FAILURE; - } - - if (!is_vconsole(fd)) { - log_error("Device %s is not a virtual console.", vc); - return EXIT_FAILURE; - } - - utf8 = is_locale_utf8(); - - r = parse_env_file("/etc/vconsole.conf", NEWLINE, - "KEYMAP", &vc_keymap, - "KEYMAP_TOGGLE", &vc_keymap_toggle, - "FONT", &vc_font, - "FONT_MAP", &vc_font_map, - "FONT_UNIMAP", &vc_font_unimap, - NULL); - - if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to read /etc/vconsole.conf: %m"); - - /* Let the kernel command line override /etc/vconsole.conf */ - if (detect_container() <= 0) { - r = parse_env_file("/proc/cmdline", WHITESPACE, - "vconsole.keymap", &vc_keymap, - "vconsole.keymap.toggle", &vc_keymap_toggle, - "vconsole.font", &vc_font, - "vconsole.font.map", &vc_font_map, - "vconsole.font.unimap", &vc_font_unimap, - NULL); - - if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to read /proc/cmdline: %m"); - } - - if (utf8) - (void) enable_utf8(fd); - else - (void) disable_utf8(fd); - - font_ok = font_load_and_wait(vc, vc_font, vc_font_map, vc_font_unimap) > 0; - keyboard_ok = keyboard_load_and_wait(vc, vc_keymap, vc_keymap_toggle, utf8) > 0; - - /* Only copy the font when we executed setfont successfully */ - if (font_copy && font_ok) - (void) font_copy_to_all_vcs(fd); - - return font_ok && keyboard_ok ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/sysctl.d/Makefile b/sysctl.d/Makefile deleted file mode 120000 index bd1047548b..0000000000 --- a/sysctl.d/Makefile +++ /dev/null @@ -1 +0,0 @@ -../src/Makefile \ No newline at end of file diff --git a/system-preset/Makefile b/system-preset/Makefile deleted file mode 120000 index bd1047548b..0000000000 --- a/system-preset/Makefile +++ /dev/null @@ -1 +0,0 @@ -../src/Makefile \ No newline at end of file diff --git a/sysusers.d/Makefile b/sysusers.d/Makefile deleted file mode 120000 index bd1047548b..0000000000 --- a/sysusers.d/Makefile +++ /dev/null @@ -1 +0,0 @@ -../src/Makefile \ No newline at end of file diff --git a/test/Makefile b/test/Makefile index 987a32548f..bc794e0d6c 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,20 +1,933 @@ -# Just a little hook script to easy building when in this directory -.PHONY: all check clean - -all: - $(MAKE) -C .. - -clean: - @for i in TEST-[0-9]*; do \ - [ -d $$i ] || continue ; \ - [ -f $$i/Makefile ] || continue ; \ - make -C $$i clean ; \ - done - -check: - $(MAKE) -C .. all - @for i in TEST-[0-9]*; do \ - [ -d $$i ] || continue ; \ - [ -f $$i/Makefile ] || continue ; \ - make -C $$i all ; \ - done +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + + +manual_tests += \ + test-ns \ + test-cgroup \ + test-install \ + test-btrfs \ + test-acd \ + test-ipv4ll-manual \ + test-ask-password-api + +unsafe_tests = \ + test-hostname \ + test-ipcrm + +ifneq ($(HAVE_LIBIPTC),) +manual_tests += \ + test-firewall-util +endif # HAVE_LIBIPTC + +ifneq ($(HAVE_KMOD),) +manual_tests += \ + test-netlink-manual +endif # HAVE_KMOD + +tests += \ + test-daemon \ + test-log \ + test-loopback \ + test-engine \ + test-watchdog \ + test-cgroup-mask \ + test-job-type \ + test-env-util \ + test-strbuf \ + test-strv \ + test-path \ + test-path-util \ + test-strxcpyx \ + test-siphash24 \ + test-unit-name \ + test-unit-file \ + test-utf8 \ + test-ellipsize \ + test-util \ + test-cpu-set-util \ + test-hexdecoct \ + test-escape \ + test-alloc-util \ + test-proc-cmdline \ + test-io-util \ + test-glob-util \ + test-xattr-util \ + test-fs-util \ + test-web-util \ + test-stat-util \ + test-fd-util \ + test-string-util \ + test-extract-word \ + test-parse-util \ + test-user-util \ + test-hostname-util \ + test-process-util \ + test-terminal-util \ + test-path-lookup \ + test-barrier \ + test-tmpfiles \ + test-namespace \ + test-date \ + test-sleep \ + test-replace-var \ + test-sched-prio \ + test-calendarspec \ + test-strip-tab-ansi \ + test-cgroup-util \ + test-fstab-util \ + test-prioq \ + test-fileio \ + test-time \ + test-clock \ + test-hashmap \ + test-set \ + test-bitmap \ + test-list \ + test-unaligned \ + test-tables \ + test-device-nodes \ + test-xml \ + test-architecture \ + test-socket-util \ + test-fdset \ + test-conf-files \ + test-conf-parser \ + test-capability \ + test-async \ + test-ratelimit \ + test-condition \ + test-uid-range \ + test-locale-util \ + test-execute \ + test-copy \ + test-cap-list \ + test-sigbus \ + test-verbs \ + test-af-list \ + test-arphrd-list \ + test-dns-domain \ + test-install-root \ + test-rlimit-util \ + test-signal-util \ + test-selinux \ + test-sizeof + +ifneq ($(HAVE_ACL),) +tests += \ + test-acl-util +endif # HAVE_ACL + +EXTRA_DIST += \ + test/a.service \ + test/basic.target \ + test/b.service \ + test/c.service \ + test/daughter.service \ + test/d.service \ + test/end.service \ + test/e.service \ + test/f.service \ + test/grandchild.service \ + test/g.service \ + test/hello-after-sleep.target \ + test/hello.service \ + test/h.service \ + test/parent-deep.slice \ + test/parent.slice \ + test/sched_idle_bad.service \ + test/sched_idle_ok.service \ + test/sched_rr_bad.service \ + test/sched_rr_change.service \ + test/sched_rr_ok.service \ + test/shutdown.target \ + test/sleep.service \ + test/sockets.target \ + test/son.service \ + test/sysinit.target \ + test/testsuite.target \ + test/timers.target \ + test/unstoppable.service \ + test/test-path/paths.target \ + test/test-path/basic.target \ + test/test-path/sysinit.target \ + test/test-path/path-changed.service \ + test/test-path/path-directorynotempty.service \ + test/test-path/path-existsglob.service \ + test/test-path/path-exists.service \ + test/test-path/path-makedirectory.service \ + test/test-path/path-modified.service \ + test/test-path/path-mycustomunit.service \ + test/test-path/path-service.service \ + test/test-path/path-changed.path \ + test/test-path/path-directorynotempty.path \ + test/test-path/path-existsglob.path \ + test/test-path/path-exists.path \ + test/test-path/path-makedirectory.path \ + test/test-path/path-modified.path \ + test/test-path/path-unit.path \ + test/test-execute/exec-environment-empty.service \ + test/test-execute/exec-environment-multiple.service \ + test/test-execute/exec-environment.service \ + test/test-execute/exec-passenvironment-absent.service \ + test/test-execute/exec-passenvironment-empty.service \ + test/test-execute/exec-passenvironment-repeated.service \ + test/test-execute/exec-passenvironment.service \ + test/test-execute/exec-group.service \ + test/test-execute/exec-group-nfsnobody.service \ + test/test-execute/exec-ignoresigpipe-no.service \ + test/test-execute/exec-ignoresigpipe-yes.service \ + test/test-execute/exec-personality-x86-64.service \ + test/test-execute/exec-personality-x86.service \ + test/test-execute/exec-personality-s390.service \ + test/test-execute/exec-privatedevices-no.service \ + test/test-execute/exec-privatedevices-yes.service \ + test/test-execute/exec-privatetmp-no.service \ + test/test-execute/exec-privatetmp-yes.service \ + test/test-execute/exec-spec-interpolation.service \ + test/test-execute/exec-systemcallerrornumber.service \ + test/test-execute/exec-systemcallfilter-failing2.service \ + test/test-execute/exec-systemcallfilter-failing.service \ + test/test-execute/exec-systemcallfilter-not-failing2.service \ + test/test-execute/exec-systemcallfilter-not-failing.service \ + test/test-execute/exec-systemcallfilter-system-user.service \ + test/test-execute/exec-systemcallfilter-system-user-nfsnobody.service \ + test/test-execute/exec-user.service \ + test/test-execute/exec-user-nfsnobody.service \ + test/test-execute/exec-workingdirectory.service \ + test/test-execute/exec-umask-0177.service \ + test/test-execute/exec-umask-default.service \ + test/test-execute/exec-privatenetwork-yes.service \ + test/test-execute/exec-environmentfile.service \ + test/test-execute/exec-oomscoreadjust-positive.service \ + test/test-execute/exec-oomscoreadjust-negative.service \ + test/test-execute/exec-ioschedulingclass-best-effort.service \ + test/test-execute/exec-ioschedulingclass-idle.service \ + test/test-execute/exec-ioschedulingclass-none.service \ + test/test-execute/exec-ioschedulingclass-realtime.service \ + test/test-execute/exec-capabilityboundingset-invert.service \ + test/test-execute/exec-capabilityboundingset-merge.service \ + test/test-execute/exec-capabilityboundingset-reset.service \ + test/test-execute/exec-capabilityboundingset-simple.service \ + test/test-execute/exec-capabilityambientset.service \ + test/test-execute/exec-capabilityambientset-nfsnobody.service \ + test/test-execute/exec-capabilityambientset-merge.service \ + test/test-execute/exec-capabilityambientset-merge-nfsnobody.service \ + test/test-execute/exec-runtimedirectory.service \ + test/test-execute/exec-runtimedirectory-mode.service \ + test/test-execute/exec-runtimedirectory-owner.service \ + test/test-execute/exec-runtimedirectory-owner-nfsnobody.service \ + test/bus-policy/hello.conf \ + test/bus-policy/methods.conf \ + test/bus-policy/ownerships.conf \ + test/bus-policy/signals.conf \ + test/bus-policy/check-own-rules.conf \ + test/bus-policy/many-rules.conf \ + test/bus-policy/test.conf + + +EXTRA_DIST += \ + src/test/test-helper.h + +test_device_nodes_SOURCES = \ + src/test/test-device-nodes.c + +test_device_nodes_LDADD = \ + libshared.la + +test_engine_SOURCES = \ + src/test/test-engine.c + +test_engine_CFLAGS = \ + $(AM_CFLAGS) \ + $(SECCOMP_CFLAGS) \ + $(MOUNT_CFLAGS) + +test_engine_LDADD = \ + libcore.la + +test_job_type_SOURCES = \ + src/test/test-job-type.c + +test_job_type_CFLAGS = \ + $(AM_CFLAGS) \ + $(SECCOMP_CFLAGS) \ + $(MOUNT_CFLAGS) + +test_job_type_LDADD = \ + libcore.la + +test_ns_SOURCES = \ + src/test/test-ns.c + +test_ns_CFLAGS = \ + $(AM_CFLAGS) \ + $(SECCOMP_CFLAGS) + +test_ns_LDADD = \ + libcore.la + +test_loopback_SOURCES = \ + src/test/test-loopback.c + +test_loopback_LDADD = \ + libcore.la + +test_hostname_SOURCES = \ + src/test/test-hostname.c + +test_hostname_LDADD = \ + libcore.la + +test_dns_domain_SOURCES = \ + src/test/test-dns-domain.c + +test_dns_domain_LDADD = \ + libsystemd-network.la \ + libshared.la + + +ifneq ($(ENABLE_EFI),) +tests += \ + test-boot-timestamps + +test_boot_timestamps_SOURCES = \ + src/test/test-boot-timestamps.c + +test_boot_timestamps_LDADD = \ + libshared.la +endif # ENABLE_EFI + +test_unit_name_SOURCES = \ + src/test/test-unit-name.c + +test_unit_name_CFLAGS = \ + $(AM_CFLAGS) \ + $(SECCOMP_CFLAGS) \ + $(MOUNT_CFLAGS) + +test_unit_name_LDADD = \ + libcore.la + +test_unit_file_SOURCES = \ + src/test/test-unit-file.c + +test_unit_file_CFLAGS = \ + $(AM_CFLAGS) \ + $(SECCOMP_CFLAGS) \ + $(MOUNT_CFLAGS) + +test_unit_file_LDADD = \ + libcore.la + +test_utf8_SOURCES = \ + src/test/test-utf8.c + +test_utf8_LDADD = \ + libshared.la + +test_capability_SOURCES = \ + src/test/test-capability.c + +test_capability_LDADD = \ + libshared.la + +test_async_SOURCES = \ + src/test/test-async.c + +test_async_LDADD = \ + libshared.la + +test_locale_util_SOURCES = \ + src/test/test-locale-util.c + +test_locale_util_LDADD = \ + libshared.la + +test_copy_SOURCES = \ + src/test/test-copy.c + +test_copy_LDADD = \ + libshared.la + +test_sigbus_SOURCES = \ + src/test/test-sigbus.c + +test_sigbus_LDADD = \ + libshared.la + +test_condition_SOURCES = \ + src/test/test-condition.c + +test_condition_LDADD = \ + libshared.la + +test_fdset_SOURCES = \ + src/test/test-fdset.c + +test_fdset_LDADD = \ + libshared.la + +test_fstab_util_SOURCES = \ + src/test/test-fstab-util.c + +test_fstab_util_LDADD = \ + libshared.la + +test_ratelimit_SOURCES = \ + src/test/test-ratelimit.c + +test_ratelimit_LDADD = \ + libshared.la + +test_util_SOURCES = \ + src/test/test-util.c + +test_util_LDADD = \ + libshared.la + +test_hexdecoct_SOURCES = \ + src/test/test-hexdecoct.c + +test_hexdecoct_LDADD = \ + libbasic.la + +test_alloc_util_SOURCES = \ + src/test/test-alloc-util.c + +test_alloc_util_LDADD = \ + libbasic.la + +test_xattr_util_SOURCES = \ + src/test/test-xattr-util.c + +test_xattr_util_LDADD = \ + libbasic.la + +test_io_util_SOURCES = \ + src/test/test-io-util.c + +test_io_util_LDADD = \ + libbasic.la + +test_glob_util_SOURCES = \ + src/test/test-glob-util.c + +test_glob_util_LDADD = \ + libbasic.la + +test_fs_util_SOURCES = \ + src/test/test-fs-util.c + +test_fs_util_LDADD = \ + libbasic.la + +test_proc_cmdline_SOURCES = \ + src/test/test-proc-cmdline.c + +test_proc_cmdline_LDADD = \ + libbasic.la + +test_fd_util_SOURCES = \ + src/test/test-fd-util.c + +test_fd_util_LDADD = \ + libbasic.la + +test_web_util_SOURCES = \ + src/test/test-web-util.c + +test_web_util_LDADD = \ + libbasic.la + +test_cpu_set_util_SOURCES = \ + src/test/test-cpu-set-util.c + +test_cpu_set_util_LDADD = \ + libbasic.la + +test_stat_util_SOURCES = \ + src/test/test-stat-util.c + +test_stat_util_LDADD = \ + libbasic.la + +test_escape_SOURCES = \ + src/test/test-escape.c + +test_escape_LDADD = \ + libbasic.la + +test_string_util_SOURCES = \ + src/test/test-string-util.c + +test_string_util_LDADD = \ + libshared.la + +test_extract_word_SOURCES = \ + src/test/test-extract-word.c + +test_extract_word_LDADD = \ + libshared.la + +test_parse_util_SOURCES = \ + src/test/test-parse-util.c + +test_parse_util_LDADD = \ + libshared.la + +test_user_util_SOURCES = \ + src/test/test-user-util.c + +test_user_util_LDADD = \ + libshared.la + +test_hostname_util_SOURCES = \ + src/test/test-hostname-util.c + +test_hostname_util_LDADD = \ + libshared.la + +test_process_util_SOURCES = \ + src/test/test-process-util.c + +test_process_util_LDADD = \ + libshared.la + +test_terminal_util_SOURCES = \ + src/test/test-terminal-util.c + +test_terminal_util_LDADD = \ + libshared.la + +test_path_lookup_SOURCES = \ + src/test/test-path-lookup.c + +test_path_lookup_LDADD = \ + libshared.la + +test_uid_range_SOURCES = \ + src/test/test-uid-range.c + +test_uid_range_LDADD = \ + libshared.la + +test_cap_list_SOURCES = \ + src/test/test-cap-list.c + +test_cap_list_LDADD = \ + libshared.la + +test_socket_util_SOURCES = \ + src/test/test-socket-util.c + +test_socket_util_LDADD = \ + libshared.la + +test_barrier_SOURCES = \ + src/test/test-barrier.c + +test_barrier_LDADD = \ + libshared.la + +test_tmpfiles_SOURCES = \ + src/test/test-tmpfiles.c + +test_tmpfiles_LDADD = \ + libshared.la + +test_namespace_SOURCES = \ + src/test/test-namespace.c + +test_verbs_SOURCES = \ + src/test/test-verbs.c + +test_verbs_LDADD = \ + libshared.la + +test_install_root_SOURCES = \ + src/test/test-install-root.c + +test_install_root_LDADD = \ + libshared.la + +test_acl_util_SOURCES = \ + src/test/test-acl-util.c + +test_acl_util_LDADD = \ + libshared.la + +test_namespace_LDADD = \ + libcore.la + +test_rlimit_util_SOURCES = \ + src/test/test-rlimit-util.c + +test_rlimit_util_LDADD = \ + libshared.la + +test_ask_password_api_SOURCES = \ + src/test/test-ask-password-api.c + +test_ask_password_api_LDADD = \ + libshared.la + +test_signal_util_SOURCES = \ + src/test/test-signal-util.c + +test_signal_util_LDADD = \ + libshared.la + +test_selinux_SOURCES = \ + src/test/test-selinux.c + +test_selinux_LDADD = \ + libshared.la + +test_sizeof_SOURCES = \ + src/test/test-sizeof.c + +test_sizeof_LDADD = \ + libshared.la + +BUILT_SOURCES += \ + src/test/test-hashmap-ordered.c + +$(outdir)/test-hashmap-ordered.c: src/test/test-hashmap-plain.c + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)$(AWK) 'BEGIN { print "/* GENERATED FILE */\n#define ORDERED" } \ + { if (!match($$0, "^#include")) \ + gsub(/hashmap/, "ordered_hashmap"); \ + gsub(/HASHMAP/, "ORDERED_HASHMAP"); \ + gsub(/Hashmap/, "OrderedHashmap"); \ + print }' <$< >$@ + +nodist_test_hashmap_SOURCES = \ + src/test/test-hashmap-ordered.c + +test_hashmap_SOURCES = \ + src/test/test-hashmap.c \ + src/test/test-hashmap-plain.c + +test_hashmap_LDADD = \ + libshared.la + +test_set_SOURCES = \ + src/test/test-set.c + +test_set_LDADD = \ + libshared.la + +test_bitmap_SOURCES = \ + src/test/test-bitmap.c + +test_bitmap_LDADD = \ + libshared.la + +test_xml_SOURCES = \ + src/test/test-xml.c + +test_xml_LDADD = \ + libshared.la + +test_list_SOURCES = \ + src/test/test-list.c + +test_list_LDADD = \ + libshared.la + +test_unaligned_LDADD = \ + libshared.la + +test_unaligned_SOURCES = \ + src/test/test-unaligned.c + +test_tables_SOURCES = \ + src/test/test-tables.c \ + src/shared/test-tables.h \ + src/journal/journald-server.c \ + src/journal/journald-server.h + +test_tables_CPPFLAGS = \ + $(AM_CPPFLAGS) + +test_tables_CFLAGS = \ + $(AM_CFLAGS) \ + $(SECCOMP_CFLAGS) \ + $(MOUNT_CFLAGS) + +test_tables_LDADD = \ + libjournal-core.la \ + libcore.la \ + libudev-core.la + +test_prioq_SOURCES = \ + src/test/test-prioq.c + +test_prioq_LDADD = \ + libshared.la + +test_fileio_SOURCES = \ + src/test/test-fileio.c + +test_fileio_LDADD = \ + libshared.la + +test_time_SOURCES = \ + src/test/test-time.c + +test_time_LDADD = \ + libshared.la + +test_clock_SOURCES = \ + src/test/test-clock.c + +test_clock_LDADD = \ + libshared.la + +test_architecture_SOURCES = \ + src/test/test-architecture.c + +test_architecture_LDADD = \ + libshared.la + +test_log_SOURCES = \ + src/test/test-log.c + +test_log_LDADD = \ + libshared.la + +test_ipcrm_SOURCES = \ + src/test/test-ipcrm.c + +test_ipcrm_LDADD = \ + libshared.la + +test_btrfs_SOURCES = \ + src/test/test-btrfs.c + +test_btrfs_LDADD = \ + libshared.la + +ifneq ($(HAVE_LIBIPTC),) +test_firewall_util_SOURCES = \ + src/test/test-firewall-util.c + +test_firewall_util_CFLAGS = \ + $(AM_CFLAGS) \ + $(LIBIPTC_CFLAGS) + +test_firewall_util_LDADD = \ + libfirewall.la \ + libshared.la \ + $(LIBIPTC_LIBS) +endif # HAVE_LIBIPTC + +test_netlink_manual_SOURCES = \ + src/test/test-netlink-manual.c + +test_netlink_manual_CFLAGS = \ + $(AM_CFLAGS) \ + $(KMOD_CFLAGS) + +test_netlink_manual_LDADD = \ + libshared.la \ + $(KMOD_LIBS) + +test_ellipsize_SOURCES = \ + src/test/test-ellipsize.c + +test_ellipsize_LDADD = \ + libshared.la + +test_date_SOURCES = \ + src/test/test-date.c + +test_date_LDADD = \ + libshared.la + +test_sleep_SOURCES = \ + src/test/test-sleep.c + +test_sleep_LDADD = \ + libcore.la + +test_replace_var_SOURCES = \ + src/test/test-replace-var.c + +test_replace_var_LDADD = \ + libshared.la + +test_calendarspec_SOURCES = \ + src/test/test-calendarspec.c + +test_calendarspec_LDADD = \ + libshared.la + +test_strip_tab_ansi_SOURCES = \ + src/test/test-strip-tab-ansi.c + +test_strip_tab_ansi_LDADD = \ + libshared.la + +test_daemon_SOURCES = \ + src/test/test-daemon.c + +test_daemon_LDADD = \ + libshared.la + +test_cgroup_SOURCES = \ + src/test/test-cgroup.c + +test_cgroup_LDADD = \ + libshared.la + +test_cgroup_mask_SOURCES = \ + src/test/test-cgroup-mask.c + +test_cgroup_mask_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(MOUNT_CFLAGS) + +test_cgroup_mask_CFLAGS = \ + $(AM_CFLAGS) \ + $(SECCOMP_CFLAGS) + +test_cgroup_mask_LDADD = \ + libcore.la + +test_cgroup_util_SOURCES = \ + src/test/test-cgroup-util.c + +test_cgroup_util_LDADD = \ + libshared.la + +test_env_util_SOURCES = \ + src/test/test-env-util.c + +test_env_util_LDADD = \ + libshared.la + +test_strbuf_SOURCES = \ + src/test/test-strbuf.c + +test_strbuf_LDADD = \ + libshared.la + +test_strv_SOURCES = \ + src/test/test-strv.c + +test_strv_LDADD = \ + libshared.la + +test_path_util_SOURCES = \ + src/test/test-path-util.c + +test_path_util_LDADD = \ + libshared.la + +test_path_SOURCES = \ + src/test/test-path.c + +test_path_CFLAGS = \ + $(AM_CFLAGS) \ + $(MOUNT_CFLAGS) + +test_path_LDADD = \ + libcore.la + +test_execute_SOURCES = \ + src/test/test-execute.c + +test_execute_CFLAGS = \ + $(AM_CFLAGS) \ + $(MOUNT_CFLAGS) + +test_execute_LDADD = \ + libcore.la + +test_siphash24_SOURCES = \ + src/test/test-siphash24.c + +test_siphash24_LDADD = \ + libshared.la + +test_strxcpyx_SOURCES = \ + src/test/test-strxcpyx.c + +test_strxcpyx_LDADD = \ + libshared.la + +test_install_SOURCES = \ + src/test/test-install.c + +test_install_LDADD = \ + libshared.la + +test_watchdog_SOURCES = \ + src/test/test-watchdog.c + +test_watchdog_LDADD = \ + libshared.la + +test_sched_prio_SOURCES = \ + src/test/test-sched-prio.c + +test_sched_prio_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(MOUNT_CFLAGS) + +test_sched_prio_CFLAGS = \ + $(AM_CFLAGS) \ + $(SECCOMP_CFLAGS) + +test_sched_prio_LDADD = \ + libcore.la + +test_conf_files_SOURCES = \ + src/test/test-conf-files.c + +test_conf_files_LDADD = \ + libshared.la + +test_conf_parser_SOURCES = \ + src/test/test-conf-parser.c + +test_conf_parser_LDADD = \ + libshared.la + +test_af_list_SOURCES = \ + src/test/test-af-list.c + +test_af_list_LDADD = \ + libbasic.la + +test_arphrd_list_SOURCES = \ + src/test/test-arphrd-list.c + +test_arphrd_list_LDADD = \ + libbasic.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/test/TEST-01-BASIC/Makefile b/test/TEST-01-BASIC/Makefile deleted file mode 100644 index 5e89a29eff..0000000000 --- a/test/TEST-01-BASIC/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -all: - @make -s --no-print-directory -C ../.. all - @basedir=../.. TEST_BASE_DIR=../ ./test.sh --all -setup: - @make --no-print-directory -C ../.. all - @basedir=../.. TEST_BASE_DIR=../ ./test.sh --setup -clean: - @basedir=../.. TEST_BASE_DIR=../ ./test.sh --clean -run: - @basedir=../.. TEST_BASE_DIR=../ ./test.sh --run diff --git a/test/TEST-02-CRYPTSETUP/Makefile b/test/TEST-02-CRYPTSETUP/Makefile deleted file mode 120000 index e9f93b1104..0000000000 --- a/test/TEST-02-CRYPTSETUP/Makefile +++ /dev/null @@ -1 +0,0 @@ -../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-03-JOBS/Makefile b/test/TEST-03-JOBS/Makefile deleted file mode 120000 index e9f93b1104..0000000000 --- a/test/TEST-03-JOBS/Makefile +++ /dev/null @@ -1 +0,0 @@ -../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-04-JOURNAL/Makefile b/test/TEST-04-JOURNAL/Makefile deleted file mode 120000 index e9f93b1104..0000000000 --- a/test/TEST-04-JOURNAL/Makefile +++ /dev/null @@ -1 +0,0 @@ -../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-05-RLIMITS/Makefile b/test/TEST-05-RLIMITS/Makefile deleted file mode 120000 index e9f93b1104..0000000000 --- a/test/TEST-05-RLIMITS/Makefile +++ /dev/null @@ -1 +0,0 @@ -../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-06-SELINUX/Makefile b/test/TEST-06-SELINUX/Makefile deleted file mode 120000 index e9f93b1104..0000000000 --- a/test/TEST-06-SELINUX/Makefile +++ /dev/null @@ -1 +0,0 @@ -../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-07-ISSUE-1981/Makefile b/test/TEST-07-ISSUE-1981/Makefile deleted file mode 120000 index e9f93b1104..0000000000 --- a/test/TEST-07-ISSUE-1981/Makefile +++ /dev/null @@ -1 +0,0 @@ -../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-08-ISSUE-2730/Makefile b/test/TEST-08-ISSUE-2730/Makefile deleted file mode 120000 index e9f93b1104..0000000000 --- a/test/TEST-08-ISSUE-2730/Makefile +++ /dev/null @@ -1 +0,0 @@ -../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-09-ISSUE-2691/Makefile b/test/TEST-09-ISSUE-2691/Makefile deleted file mode 120000 index e9f93b1104..0000000000 --- a/test/TEST-09-ISSUE-2691/Makefile +++ /dev/null @@ -1 +0,0 @@ -../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-10-ISSUE-2467/Makefile b/test/TEST-10-ISSUE-2467/Makefile deleted file mode 120000 index e9f93b1104..0000000000 --- a/test/TEST-10-ISSUE-2467/Makefile +++ /dev/null @@ -1 +0,0 @@ -../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-11-ISSUE-3166/Makefile b/test/TEST-11-ISSUE-3166/Makefile deleted file mode 120000 index e9f93b1104..0000000000 --- a/test/TEST-11-ISSUE-3166/Makefile +++ /dev/null @@ -1 +0,0 @@ -../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-12-ISSUE-3171/Makefile b/test/TEST-12-ISSUE-3171/Makefile deleted file mode 120000 index e9f93b1104..0000000000 --- a/test/TEST-12-ISSUE-3171/Makefile +++ /dev/null @@ -1 +0,0 @@ -../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/test-efi-create-disk.sh b/test/test-efi-create-disk.sh deleted file mode 100755 index 56dd09abd7..0000000000 --- a/test/test-efi-create-disk.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -e - -# create GPT table with EFI System Partition -rm -f test-efi-disk.img -dd if=/dev/null of=test-efi-disk.img bs=1M seek=512 count=1 -parted --script test-efi-disk.img "mklabel gpt" "mkpart ESP fat32 1MiB 511MiB" "set 1 boot on" - -# create FAT32 file system -LOOP=$(losetup --show -f -P test-efi-disk.img) -mkfs.vfat -F32 ${LOOP}p1 -mkdir -p mnt -mount ${LOOP}p1 mnt - -mkdir -p mnt/EFI/{Boot,systemd} -cp systemd-bootx64.efi mnt/EFI/Boot/bootx64.efi - -[ -e /boot/shellx64.efi ] && cp /boot/shellx64.efi mnt/ - -mkdir mnt/EFI/Linux -echo -n "foo=yes bar=no root=/dev/fakeroot debug rd.break=initqueue" > mnt/cmdline.txt -objcopy \ - --add-section .osrel=/etc/os-release --change-section-vma .osrel=0x20000 \ - --add-section .cmdline=mnt/cmdline.txt --change-section-vma .cmdline=0x30000 \ - --add-section .splash=test/splash.bmp --change-section-vma .splash=0x40000 \ - --add-section .linux=/boot/$(cat /etc/machine-id)/$(uname -r)/linux --change-section-vma .linux=0x2000000 \ - --add-section .initrd=/boot/$(cat /etc/machine-id)/$(uname -r)/initrd --change-section-vma .initrd=0x3000000 \ - linuxx64.efi.stub mnt/EFI/Linux/linux-test.efi - -# install entries -mkdir -p mnt/loader/entries -echo -e "timeout 3\n" > mnt/loader/loader.conf -echo -e "title Test\nefi /test\n" > mnt/loader/entries/test.conf -echo -e "title Test2\nlinux /test2\noptions option=yes word number=1000 more\n" > mnt/loader/entries/test2.conf -echo -e "title Test3\nlinux /test3\n" > mnt/loader/entries/test3.conf -echo -e "title Test4\nlinux /test4\n" > mnt/loader/entries/test4.conf -echo -e "title Test5\nefi /test5\n" > mnt/loader/entries/test5.conf -echo -e "title Test6\nlinux /test6\n" > mnt/loader/entries/test6.conf - -sync -umount mnt -rmdir mnt -losetup -d $LOOP diff --git a/tmpfiles.d/Makefile b/tmpfiles.d/Makefile deleted file mode 120000 index bd1047548b..0000000000 --- a/tmpfiles.d/Makefile +++ /dev/null @@ -1 +0,0 @@ -../src/Makefile \ No newline at end of file diff --git a/units/Makefile b/units/Makefile deleted file mode 120000 index bd1047548b..0000000000 --- a/units/Makefile +++ /dev/null @@ -1 +0,0 @@ -../src/Makefile \ No newline at end of file diff --git a/units/user/Makefile b/units/user/Makefile deleted file mode 120000 index 50be21181f..0000000000 --- a/units/user/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../src/Makefile \ No newline at end of file -- cgit v1.2.3-54-g00ecf